util.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2019 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. util.py: Low-level routines imported by other modules in the MMGen suite
  20. """
  21. import sys,os,time,stat,re
  22. from hashlib import sha256
  23. from string import hexdigits,digits
  24. from mmgen.color import *
  25. from mmgen.exception import *
  26. from mmgen.globalvars import *
  27. if g.platform == 'win':
  28. def msg_r(s):
  29. try:
  30. g.stderr.write(s)
  31. g.stderr.flush()
  32. except:
  33. os.write(2,s.encode())
  34. def Msg_r(s):
  35. try:
  36. g.stdout.write(s)
  37. g.stdout.flush()
  38. except:
  39. os.write(1,s.encode())
  40. def msg(s): msg_r(s + '\n')
  41. def Msg(s): Msg_r(s + '\n')
  42. else:
  43. def msg_r(s):
  44. g.stderr.write(s)
  45. g.stderr.flush()
  46. def Msg_r(s):
  47. g.stdout.write(s)
  48. g.stdout.flush()
  49. def msg(s): g.stderr.write(s + '\n')
  50. def Msg(s): g.stdout.write(s + '\n')
  51. def msgred(s): msg(red(s))
  52. def rmsg(s): msg(red(s))
  53. def rmsg_r(s): msg_r(red(s))
  54. def ymsg(s): msg(yellow(s))
  55. def ymsg_r(s): msg_r(yellow(s))
  56. def gmsg(s): msg(green(s))
  57. def gmsg_r(s): msg_r(green(s))
  58. def bmsg(s): msg(blue(s))
  59. def bmsg_r(s): msg_r(blue(s))
  60. def mmsg(*args):
  61. for d in args: Msg(repr(d))
  62. def mdie(*args):
  63. mmsg(*args); sys.exit(0)
  64. def die_wait(delay,ev=0,s=''):
  65. assert isinstance(delay,int)
  66. assert isinstance(ev,int)
  67. if s: msg(s)
  68. time.sleep(delay)
  69. sys.exit(ev)
  70. def die_pause(ev=0,s=''):
  71. assert isinstance(ev,int)
  72. if s: msg(s)
  73. input('Press ENTER to exit')
  74. sys.exit(ev)
  75. def die(ev=0,s=''):
  76. assert isinstance(ev,int)
  77. if s: msg(s)
  78. sys.exit(ev)
  79. def Die(ev=0,s=''):
  80. assert isinstance(ev,int)
  81. if s: Msg(s)
  82. sys.exit(ev)
  83. def rdie(ev=0,s=''): die(ev,red(s))
  84. def ydie(ev=0,s=''): die(ev,yellow(s))
  85. def pp_fmt(d):
  86. import pprint
  87. return pprint.PrettyPrinter(indent=4,compact=True).pformat(d)
  88. def pp_msg(d):
  89. msg(pp_fmt(d))
  90. CUR_HIDE = '\033[?25l'
  91. CUR_SHOW = '\033[?25h'
  92. def set_for_type(val,refval,desc,invert_bool=False,src=None):
  93. src_str = (''," in '{}'".format(src))[bool(src)]
  94. if type(refval) == bool:
  95. v = str(val).lower()
  96. if v in ('true','yes','1'): ret = True
  97. elif v in ('false','no','none','0'): ret = False
  98. else: die(1,"'{}': invalid value for '{}'{} (must be of type '{}')".format(
  99. val,desc,src_str,'bool'))
  100. if invert_bool: ret = not ret
  101. else:
  102. try:
  103. ret = type(refval)((val,not val)[invert_bool])
  104. except:
  105. die(1,"'{}': invalid value for '{}'{} (must be of type '{}')".format(
  106. val,desc,src_str,type(refval).__name__))
  107. return ret
  108. # From 'man dd':
  109. # c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024,
  110. # GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y.
  111. def parse_bytespec(nbytes):
  112. smap = (('c', 1),
  113. ('w', 2),
  114. ('b', 512),
  115. ('kB', 1000),
  116. ('K', 1024),
  117. ('MB', 1000*1000),
  118. ('M', 1024*1024),
  119. ('GB', 1000*1000*1000),
  120. ('G', 1024*1024*1024),
  121. ('TB', 1000*1000*1000*1000),
  122. ('T', 1024*1024*1024*1024))
  123. import re
  124. m = re.match(r'([0123456789.]+)(.*)',nbytes)
  125. if m:
  126. if m.group(2):
  127. for k,v in smap:
  128. if k == m.group(2):
  129. from decimal import Decimal
  130. return int(Decimal(m.group(1)) * v)
  131. else:
  132. msg("Valid byte specifiers: '{}'".format("' '".join([i[0] for i in smap])))
  133. elif '.' in nbytes:
  134. raise ValueError('fractional bytes not allowed')
  135. else:
  136. return int(nbytes)
  137. die(1,"'{}': invalid byte specifier".format(nbytes))
  138. def check_or_create_dir(path):
  139. try:
  140. os.listdir(path)
  141. except:
  142. try:
  143. os.makedirs(path,0o700)
  144. except:
  145. die(2,"ERROR: unable to read or create path '{}'".format(path))
  146. from mmgen.opts import opt
  147. def qmsg(s,alt=None):
  148. if opt.quiet:
  149. if alt != None: msg(alt)
  150. else: msg(s)
  151. def qmsg_r(s,alt=None):
  152. if opt.quiet:
  153. if alt != None: msg_r(alt)
  154. else: msg_r(s)
  155. def vmsg(s,force=False):
  156. if opt.verbose or force: msg(s)
  157. def vmsg_r(s,force=False):
  158. if opt.verbose or force: msg_r(s)
  159. def Vmsg(s,force=False):
  160. if opt.verbose or force: Msg(s)
  161. def Vmsg_r(s,force=False):
  162. if opt.verbose or force: Msg_r(s)
  163. def dmsg(s):
  164. if opt.debug: msg(s)
  165. def suf(arg,suf_type='s'):
  166. suf_types = { 's': '', 'es': '', 'ies': 'y' }
  167. assert suf_type in suf_types,'invalid suffix type'
  168. if isinstance(arg,int):
  169. n = arg
  170. elif isinstance(arg,(list,tuple,set,dict)):
  171. n = len(arg)
  172. else:
  173. die(2,'{}: invalid parameter for suf()'.format(arg))
  174. return suf_types[suf_type] if n == 1 else suf_type
  175. def get_extension(f):
  176. a,b = os.path.splitext(f)
  177. return ('',b[1:])[len(b) > 1]
  178. def remove_extension(f,e):
  179. a,b = os.path.splitext(f)
  180. return (f,a)[len(b)>1 and b[1:]==e]
  181. def make_chksum_N(s,nchars,sep=False):
  182. if isinstance(s,str): s = s.encode()
  183. if nchars%4 or not (4 <= nchars <= 64): return False
  184. s = sha256(sha256(s).digest()).hexdigest().upper()
  185. sep = ('',' ')[bool(sep)]
  186. return sep.join([s[i*4:i*4+4] for i in range(nchars//4)])
  187. def make_chksum_8(s,sep=False):
  188. from mmgen.obj import HexStr
  189. s = HexStr(sha256(sha256(s).digest()).hexdigest()[:8].upper(),case='upper')
  190. return '{} {}'.format(s[:4],s[4:]) if sep else s
  191. def make_chksum_6(s):
  192. from mmgen.obj import HexStr
  193. if isinstance(s,str): s = s.encode()
  194. return HexStr(sha256(s).hexdigest()[:6])
  195. def is_chksum_6(s): return len(s) == 6 and is_hex_str_lc(s)
  196. def make_iv_chksum(s): return sha256(s).hexdigest()[:8].upper()
  197. def splitN(s,n,sep=None): # always return an n-element list
  198. ret = s.split(sep,n-1)
  199. return ret + ['' for i in range(n-len(ret))]
  200. def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list
  201. def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
  202. def split_into_cols(col_wid,s):
  203. return ' '.join([s[col_wid*i:col_wid*(i+1)] for i in range(len(s)//col_wid+1)]).rstrip()
  204. def capfirst(s): # different from str.capitalize() - doesn't downcase any uc in string
  205. return s if len(s) == 0 else s[0].upper() + s[1:]
  206. def decode_timestamp(s):
  207. # tz_save = open('/etc/timezone').read().rstrip()
  208. os.environ['TZ'] = 'UTC'
  209. ts = time.strptime(s,'%Y%m%d_%H%M%S')
  210. t = time.mktime(ts)
  211. # os.environ['TZ'] = tz_save
  212. return int(t)
  213. def make_timestamp(secs=None):
  214. t = int(secs) if secs else time.time()
  215. tv = time.gmtime(t)[:6]
  216. return '{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}'.format(*tv)
  217. def make_timestr(secs=None):
  218. t = int(secs) if secs else time.time()
  219. tv = time.gmtime(t)[:6]
  220. return '{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}'.format(*tv)
  221. def secs_to_dhms(secs):
  222. dsecs = secs//3600
  223. return '{}{:02d}:{:02d}:{:02d}'.format(
  224. ('','{} day{}, '.format(dsecs//24,suf(dsecs//24)))[dsecs > 24],
  225. dsecs % 24, (secs//60) % 60, secs % 60)
  226. def secs_to_hms(secs):
  227. return '{:02d}:{:02d}:{:02d}'.format(secs//3600, (secs//60) % 60, secs % 60)
  228. def secs_to_ms(secs):
  229. return '{:02d}:{:02d}'.format(secs//60, secs % 60)
  230. def is_digits(s): return set(list(s)) <= set(list(digits))
  231. def is_int(s):
  232. try:
  233. int(str(s))
  234. return True
  235. except:
  236. return False
  237. def is_hex_str(s): return set(list(s.lower())) <= set(list(hexdigits.lower()))
  238. def is_hex_str_lc(s): return set(list(s)) <= set(list(hexdigits.lower()))
  239. def is_hex_str_uc(s): return set(list(s)) <= set(list(hexdigits.upper()))
  240. def is_utf8(s):
  241. return is_ascii(s,enc='utf8')
  242. def is_ascii(s,enc='ascii'):
  243. try: s.decode(enc)
  244. except: return False
  245. else: return True
  246. def match_ext(addr,ext):
  247. return addr.split('.')[-1] == ext
  248. def file_exists(f):
  249. try:
  250. os.stat(f)
  251. return True
  252. except:
  253. return False
  254. def file_is_readable(f):
  255. from stat import S_IREAD
  256. try:
  257. assert os.stat(f).st_mode & S_IREAD
  258. except:
  259. return False
  260. else:
  261. return True
  262. def get_from_brain_opt_params():
  263. l,p = opt.from_brain.split(',')
  264. return(int(l),p)
  265. def remove_whitespace(s):
  266. return s.translate(dict((ord(ws),None) for ws in '\t\r\n '))
  267. def pretty_format(s,width=80,pfx=''):
  268. out = []
  269. while(s):
  270. if len(s) <= width:
  271. out.append(s)
  272. break
  273. i = s[:width].rfind(' ')
  274. out.append(s[:i])
  275. s = s[i+1:]
  276. return pfx + ('\n'+pfx).join(out)
  277. def block_format(data,gw=2,cols=8,line_nums=None,data_is_hex=False):
  278. assert line_nums in (None,'hex','dec'),"'line_nums' must be one of None, 'hex' or 'dec'"
  279. ln_fs = '{:06x}: ' if line_nums == 'hex' else '{:06}: '
  280. bytes_per_chunk = gw
  281. if data_is_hex:
  282. gw *= 2
  283. nchunks = len(data)//gw + bool(len(data)%gw)
  284. return ''.join(
  285. ('' if (line_nums == None or i % cols) else ln_fs.format(i*bytes_per_chunk))
  286. + data[i*gw:i*gw+gw]
  287. + (' ' if (not cols or (i+1) % cols) else '\n')
  288. for i in range(nchunks)
  289. ).rstrip() + '\n'
  290. def pretty_hexdump(data,gw=2,cols=8,line_nums=None):
  291. return block_format(data.hex(),gw,cols,line_nums,data_is_hex=True)
  292. def decode_pretty_hexdump(data):
  293. from string import hexdigits
  294. pat = r'^[{}]+:\s+'.format(hexdigits)
  295. lines = [re.sub(pat,'',l) for l in data.splitlines()]
  296. try:
  297. return bytes.fromhex(''.join((''.join(lines).split())))
  298. except:
  299. msg('Data not in hexdump format')
  300. return False
  301. def strip_comments(line):
  302. return re.sub(r'\s+$','',re.sub(r'#.*','',line,1))
  303. def remove_comments(lines):
  304. return [m for m in [strip_comments(l) for l in lines] if m != '']
  305. def get_hash_params(hash_preset):
  306. if hash_preset in g.hash_presets:
  307. return g.hash_presets[hash_preset] # N,p,r,buflen
  308. else: # Shouldn't be here
  309. die(3,"{}: invalid 'hash_preset' value".format(hash_preset))
  310. def compare_chksums(chk1,desc1,chk2,desc2,hdr='',die_on_fail=False,verbose=False):
  311. if not chk1 == chk2:
  312. fs = "{} ERROR: {} checksum ({}) doesn't match {} checksum ({})"
  313. m = fs.format((hdr+':\n ' if hdr else 'CHECKSUM'),desc2,chk2,desc1,chk1)
  314. if die_on_fail:
  315. die(3,m)
  316. else:
  317. vmsg(m,force=verbose)
  318. return False
  319. vmsg('{} checksum OK ({})'.format(capfirst(desc1),chk1))
  320. return True
  321. def compare_or_die(val1, desc1, val2, desc2, e='Error'):
  322. if val1 != val2:
  323. die(3,"{}: {} ({}) doesn't match {} ({})".format(e,desc2,val2,desc1,val1))
  324. dmsg('{} OK ({})'.format(capfirst(desc2),val2))
  325. return True
  326. def open_file_or_exit(filename,mode,silent=False):
  327. try:
  328. return open(filename, mode)
  329. except:
  330. op = ('writing','reading')['r' in mode]
  331. die(2,("Unable to open file '{}' for {}".format(filename,op),'')[silent])
  332. def check_file_type_and_access(fname,ftype,blkdev_ok=False):
  333. a = ((os.R_OK,'read'),(os.W_OK,'writ'))
  334. access,m = a[ftype in ('output file','output directory')]
  335. ok_types = [
  336. (stat.S_ISREG,'regular file'),
  337. (stat.S_ISLNK,'symbolic link')
  338. ]
  339. if blkdev_ok: ok_types.append((stat.S_ISBLK,'block device'))
  340. if ftype == 'output directory': ok_types = [(stat.S_ISDIR, 'output directory')]
  341. try: mode = os.stat(fname).st_mode
  342. except:
  343. raise FileNotFound("Requested {} '{}' not found".format(ftype,fname))
  344. for t in ok_types:
  345. if t[0](mode): break
  346. else:
  347. die(1,"Requested {} '{}' is not a {}".format(ftype,fname,' or '.join([t[1] for t in ok_types])))
  348. if not os.access(fname, access):
  349. die(1,"Requested {} '{}' is not {}able by you".format(ftype,fname,m))
  350. return True
  351. def check_infile(f,blkdev_ok=False):
  352. return check_file_type_and_access(f,'input file',blkdev_ok=blkdev_ok)
  353. def check_outfile(f,blkdev_ok=False):
  354. return check_file_type_and_access(f,'output file',blkdev_ok=blkdev_ok)
  355. def check_outdir(f):
  356. return check_file_type_and_access(f,'output directory')
  357. def check_wallet_extension(fn):
  358. from mmgen.seed import SeedSource
  359. if not SeedSource.ext_to_type(get_extension(fn)):
  360. raise BadFileExtension("'{}': unrecognized seed source file extension".format(fn))
  361. def make_full_path(outdir,outfile):
  362. return os.path.normpath(os.path.join(outdir, os.path.basename(outfile)))
  363. def get_seed_file(cmd_args,nargs,invoked_as=None):
  364. from mmgen.filename import find_file_in_dir
  365. from mmgen.seed import Wallet
  366. wf = find_file_in_dir(Wallet,g.data_dir)
  367. wd_from_opt = bool(opt.hidden_incog_input_params or opt.in_fmt) # have wallet data from opt?
  368. import mmgen.opts as opts
  369. if len(cmd_args) + (wd_from_opt or bool(wf)) < nargs:
  370. if not wf:
  371. msg('No default wallet found, and no other seed source was specified')
  372. opts.usage()
  373. elif len(cmd_args) > nargs:
  374. opts.usage()
  375. elif len(cmd_args) == nargs and wf and invoked_as != 'gen':
  376. qmsg('Warning: overriding default wallet with user-supplied wallet')
  377. if cmd_args or wf:
  378. check_infile(cmd_args[0] if cmd_args else wf)
  379. return cmd_args[0] if cmd_args else (wf,None)[wd_from_opt]
  380. def get_new_passphrase(desc,passchg=False):
  381. w = '{}passphrase for {}'.format(('','new ')[bool(passchg)], desc)
  382. if opt.passwd_file:
  383. pw = ' '.join(get_words_from_file(opt.passwd_file,w))
  384. elif opt.echo_passphrase:
  385. pw = ' '.join(get_words_from_user('Enter {}: '.format(w)))
  386. else:
  387. from mmgen.common import mswin_pw_warning
  388. mswin_pw_warning()
  389. for i in range(g.passwd_max_tries):
  390. pw = ' '.join(get_words_from_user('Enter {}: '.format(w)))
  391. pw2 = ' '.join(get_words_from_user('Repeat passphrase: '))
  392. dmsg('Passphrases: [{}] [{}]'.format(pw,pw2))
  393. if pw == pw2:
  394. vmsg('Passphrases match'); break
  395. else: msg('Passphrases do not match. Try again.')
  396. else:
  397. die(2,'User failed to duplicate passphrase in {} attempts'.format(g.passwd_max_tries))
  398. if pw == '': qmsg('WARNING: Empty passphrase')
  399. return pw
  400. def confirm_or_raise(message,q,expect='YES',exit_msg='Exiting at user request'):
  401. m = message.strip()
  402. if m: msg(m)
  403. a = q+' ' if q[0].isupper() else 'Are you sure you want to {}?\n'.format(q)
  404. b = "Type uppercase '{}' to confirm: ".format(expect)
  405. if my_raw_input(a+b).strip() != expect:
  406. raise UserNonConfirmation(exit_msg)
  407. def write_data_to_file( outfile,data,desc='data',
  408. ask_write=False,
  409. ask_write_prompt='',
  410. ask_write_default_yes=True,
  411. ask_overwrite=True,
  412. ask_tty=True,
  413. no_tty=False,
  414. quiet=False,
  415. binary=False,
  416. ignore_opt_outdir=False,
  417. check_data=False,
  418. cmp_data=None):
  419. if quiet: ask_tty = ask_overwrite = False
  420. if opt.quiet: ask_overwrite = False
  421. if ask_write_default_yes == False or ask_write_prompt:
  422. ask_write = True
  423. def do_stdout():
  424. qmsg('Output to STDOUT requested')
  425. if g.stdin_tty:
  426. if no_tty:
  427. die(2,'Printing {} to screen is not allowed'.format(desc))
  428. if (ask_tty and not opt.quiet) or binary:
  429. confirm_or_raise('','output {} to screen'.format(desc))
  430. else:
  431. try: of = os.readlink('/proc/{}/fd/1'.format(os.getpid())) # Linux
  432. except: of = None # Windows
  433. if of:
  434. if of[:5] == 'pipe:':
  435. if no_tty:
  436. die(2,'Writing {} to pipe is not allowed'.format(desc))
  437. if ask_tty and not opt.quiet:
  438. confirm_or_raise('','output {} to pipe'.format(desc))
  439. msg('')
  440. of2,pd = os.path.relpath(of),os.path.pardir
  441. msg("Redirecting output to file '{}'".format((of2,of)[of2[:len(pd)] == pd]))
  442. else:
  443. msg('Redirecting output to file')
  444. if binary and g.platform == 'win':
  445. import msvcrt
  446. msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
  447. sys.stdout.write(data.decode() if isinstance(data,bytes) else data)
  448. def do_file(outfile,ask_write_prompt):
  449. if opt.outdir and not ignore_opt_outdir and not os.path.isabs(outfile):
  450. outfile = make_full_path(opt.outdir,outfile)
  451. if ask_write:
  452. if not ask_write_prompt: ask_write_prompt = 'Save {}?'.format(desc)
  453. if not keypress_confirm(ask_write_prompt,
  454. default_yes=ask_write_default_yes):
  455. die(1,'{} not saved'.format(capfirst(desc)))
  456. hush = False
  457. if file_exists(outfile) and ask_overwrite:
  458. q = "File '{}' already exists\nOverwrite?".format(outfile)
  459. confirm_or_raise('',q)
  460. msg("Overwriting file '{}'".format(outfile))
  461. hush = True
  462. # not atomic, but better than nothing
  463. # if cmp_data is empty, file can be either empty or non-existent
  464. if check_data:
  465. try:
  466. d = open(outfile,('r','rb')[bool(binary)]).read()
  467. except:
  468. d = ''
  469. finally:
  470. if d != cmp_data:
  471. m = "{} in file '{}' has been altered by some other program! Aborting file write"
  472. die(3,m.format(desc,outfile))
  473. # To maintain portability, always open files in binary mode
  474. # If 'binary' option not set, encode/decode data before writing and after reading
  475. f = open_file_or_exit(outfile,'wb')
  476. try:
  477. f.write(data if binary else data.encode())
  478. except:
  479. die(2,"Failed to write {} to file '{}'".format(desc,outfile))
  480. f.close
  481. if not (hush or quiet):
  482. msg("{} written to file '{}'".format(capfirst(desc),outfile))
  483. return True
  484. if opt.stdout or outfile in ('','-'):
  485. do_stdout()
  486. elif sys.stdin.isatty() and not sys.stdout.isatty():
  487. do_stdout()
  488. else:
  489. do_file(outfile,ask_write_prompt)
  490. def get_words_from_user(prompt):
  491. words = my_raw_input(prompt, echo=opt.echo_passphrase).split()
  492. dmsg('Sanitized input: [{}]'.format(' '.join(words)))
  493. return words
  494. def get_words_from_file(infile,desc,quiet=False):
  495. if not quiet:
  496. qmsg("Getting {} from file '{}'".format(desc,infile))
  497. f = open_file_or_exit(infile, 'rb')
  498. try: words = f.read().decode().split()
  499. except: die(1,'{} data must be UTF-8 encoded.'.format(capfirst(desc)))
  500. f.close()
  501. dmsg('Sanitized input: [{}]'.format(' '.join(words)))
  502. return words
  503. def get_words(infile,desc,prompt):
  504. if infile:
  505. return get_words_from_file(infile,desc)
  506. else:
  507. return get_words_from_user(prompt)
  508. def mmgen_decrypt_file_maybe(fn,desc='',quiet=False,silent=False):
  509. d = get_data_from_file(fn,desc,binary=True,quiet=quiet,silent=silent)
  510. have_enc_ext = get_extension(fn) == g.mmenc_ext
  511. if have_enc_ext or not is_utf8(d):
  512. m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
  513. qmsg("{} {} '{}'".format(m,desc,fn))
  514. from mmgen.crypto import mmgen_decrypt_retry
  515. d = mmgen_decrypt_retry(d,desc)
  516. return d
  517. def get_lines_from_file(fn,desc='',trim_comments=False,quiet=False,silent=False):
  518. dec = mmgen_decrypt_file_maybe(fn,desc,quiet=quiet,silent=silent)
  519. ret = dec.decode().splitlines()
  520. if trim_comments: ret = remove_comments(ret)
  521. dmsg("Got {} lines from file '{}'".format(len(ret),fn))
  522. return ret
  523. def get_data_from_user(desc='data'): # user input MUST be UTF-8
  524. p = ('','Enter {}: '.format(desc))[g.stdin_tty]
  525. data = my_raw_input(p,echo=opt.echo_passphrase)
  526. dmsg('User input: [{}]'.format(data))
  527. return data
  528. def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False,quiet=False):
  529. if not opt.quiet and not silent and not quiet and desc:
  530. qmsg("Getting {} from file '{}'".format(desc,infile))
  531. if dash and infile == '-':
  532. data = os.fdopen(0,'rb').read(g.max_input_size+1)
  533. else:
  534. data = open_file_or_exit(infile,'rb',silent=silent).read(g.max_input_size+1)
  535. if not binary:
  536. data = data.decode()
  537. if len(data) == g.max_input_size + 1:
  538. raise MaxInputSizeExceeded('Too much input data! Max input data size: {} bytes'.format(g.max_input_size))
  539. return data
  540. def pwfile_reuse_warning():
  541. if 'passwd_file_used' in globals():
  542. qmsg("Reusing passphrase from file '{}' at user request".format(opt.passwd_file))
  543. return True
  544. globals()['passwd_file_used'] = True
  545. return False
  546. def get_mmgen_passphrase(desc,passchg=False):
  547. prompt ='Enter {}passphrase for {}: '.format(('','old ')[bool(passchg)],desc)
  548. if opt.passwd_file:
  549. pwfile_reuse_warning()
  550. return ' '.join(get_words_from_file(opt.passwd_file,'passphrase'))
  551. else:
  552. from mmgen.common import mswin_pw_warning
  553. mswin_pw_warning()
  554. return ' '.join(get_words_from_user(prompt))
  555. def my_raw_input(prompt,echo=True,insert_txt='',use_readline=True):
  556. try: import readline
  557. except: use_readline = False # Windows
  558. if use_readline and sys.stdout.isatty():
  559. def st_hook(): readline.insert_text(insert_txt)
  560. readline.set_startup_hook(st_hook)
  561. else:
  562. msg_r(prompt)
  563. prompt = ''
  564. from mmgen.term import kb_hold_protect
  565. kb_hold_protect()
  566. if g.test_suite_popen_spawn:
  567. msg(prompt)
  568. sys.stderr.flush()
  569. reply = os.read(0,4096).decode()
  570. elif echo or not sys.stdin.isatty():
  571. reply = input(prompt)
  572. else:
  573. from getpass import getpass
  574. if g.platform == 'win':
  575. # MSWin hack - getpass('foo') doesn't flush stderr
  576. msg_r(prompt.strip()) # getpass('') adds a space
  577. sys.stderr.flush()
  578. reply = getpass('')
  579. else:
  580. reply = getpass(prompt)
  581. kb_hold_protect()
  582. try:
  583. return reply.strip()
  584. except:
  585. die(1,'User input must be UTF-8 encoded.')
  586. def keypress_confirm(prompt,default_yes=False,verbose=False,no_nl=False,complete_prompt=False):
  587. from mmgen.term import get_char
  588. q = ('(y/N)','(Y/n)')[bool(default_yes)]
  589. p = prompt if complete_prompt else '{} {}: '.format(prompt,q)
  590. nl = ('\n','\r{}\r'.format(' '*len(p)))[no_nl]
  591. if opt.accept_defaults:
  592. msg(p)
  593. return default_yes
  594. while True:
  595. r = get_char(p).strip(b'\n\r')
  596. if not r:
  597. if default_yes: msg_r(nl); return True
  598. else: msg_r(nl); return False
  599. elif r in b'yY': msg_r(nl); return True
  600. elif r in b'nN': msg_r(nl); return False
  601. else:
  602. if verbose: msg('\nInvalid reply')
  603. else: msg_r('\r')
  604. def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
  605. from mmgen.term import get_char
  606. while True:
  607. reply = get_char('{}: '.format(prompt)).strip(b'\n\r')
  608. if reply in chars.encode() or (enter_ok and not reply):
  609. msg('')
  610. return reply.decode()
  611. if verbose: msg('\nInvalid reply')
  612. else: msg_r('\r')
  613. def do_pager(text):
  614. pagers = ['less','more']
  615. end_msg = '\n(end of text)\n\n'
  616. # --- Non-MSYS Windows code deleted ---
  617. # raw, chop, horiz scroll 8 chars, disable buggy line chopping in MSYS
  618. os.environ['LESS'] = (('--shift 8 -RS'),('-cR -#1'))[g.platform=='win']
  619. if 'PAGER' in os.environ and os.environ['PAGER'] != pagers[0]:
  620. pagers = [os.environ['PAGER']] + pagers
  621. for pager in pagers:
  622. try:
  623. from subprocess import run
  624. m = text + ('' if pager == 'less' else end_msg)
  625. p = run([pager],input=m.encode(),check=True)
  626. msg_r('\r')
  627. except:
  628. pass
  629. else:
  630. break
  631. else:
  632. Msg(text+end_msg)
  633. def do_license_msg(immed=False):
  634. if opt.quiet or g.no_license or opt.yes or not g.stdin_tty: return
  635. import mmgen.license as gpl
  636. p = "Press 'w' for conditions and warranty info, or 'c' to continue:"
  637. msg(gpl.warning)
  638. prompt = '{} '.format(p.strip())
  639. from mmgen.term import get_char
  640. while True:
  641. reply = get_char(prompt, immed_chars=('','wc')[bool(immed)])
  642. if reply == b'w':
  643. do_pager(gpl.conditions)
  644. elif reply == b'c':
  645. msg(''); break
  646. else:
  647. msg_r('\r')
  648. msg('')
  649. def get_daemon_cfg_options(cfg_keys):
  650. cfg_file = os.path.join(g.proto.daemon_data_dir,g.proto.name+'.conf')
  651. try:
  652. lines = get_lines_from_file(cfg_file,'',silent=bool(opt.quiet))
  653. kv_pairs = [l.split('=') for l in lines]
  654. cfg = {k:v for k,v in kv_pairs if k in cfg_keys}
  655. except:
  656. vmsg("Warning: '{}' does not exist or is unreadable".format(cfg_file))
  657. cfg = {}
  658. for k in set(cfg_keys) - set(cfg.keys()): cfg[k] = ''
  659. return cfg
  660. def get_coin_daemon_auth_cookie():
  661. f = os.path.join(g.proto.daemon_data_dir,g.proto.daemon_data_subdir,'.cookie')
  662. return get_lines_from_file(f,'')[0] if file_is_readable(f) else ''
  663. def rpc_init(reinit=False):
  664. if not 'rpc' in g.proto.mmcaps:
  665. die(1,'Coin daemon operations not supported for coin {}!'.format(g.coin))
  666. if g.rpch != None and not reinit: return g.rpch
  667. from mmgen.rpc import init_daemon
  668. g.rpch = init_daemon(g.proto.daemon_family)
  669. return g.rpch
  670. def format_par(s,indent=0,width=80,as_list=False):
  671. words,lines = s.split(),[]
  672. assert width >= indent + 4,'width must be >= indent + 4'
  673. while words:
  674. line = ''
  675. while len(line) <= (width-indent) and words:
  676. if line and len(line) + len(words[0]) + 1 > width-indent: break
  677. line += ('',' ')[bool(line)] + words.pop(0)
  678. lines.append(' '*indent + line)
  679. return lines if as_list else '\n'.join(lines) + '\n'
  680. # module loading magic for tx.py and tw.py
  681. def altcoin_subclass(cls,mod_id,cls_name):
  682. if cls.__name__ != cls_name: return cls
  683. mod_dir = g.proto.base_coin.lower()
  684. pname = g.proto.class_pfx if hasattr(g.proto,'class_pfx') else capfirst(g.proto.name)
  685. tname = 'Token' if g.token else ''
  686. import importlib
  687. modname = 'mmgen.altcoins.{}.{}'.format(mod_dir,mod_id)
  688. clsname = '{}{}{}'.format(pname,tname,cls_name)
  689. try:
  690. return getattr(importlib.import_module(modname),clsname)
  691. except ImportError:
  692. return cls
  693. # decorator for TrackingWallet
  694. def write_mode(orig_func):
  695. def f(self,*args,**kwargs):
  696. if self.mode != 'w':
  697. m = '{} opened in read-only mode: cannot execute method {}()'
  698. die(1,m.format(type(self).__name__,locals()['orig_func'].__name__))
  699. return orig_func(self,*args,**kwargs)
  700. return f