util.py 27 KB

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