v98 1
v99 2
v99 3
v99 4
v99 5
v99 6
v99 7
v99 8
v99 9
v99 10
v99 11
v99 12
v99 13
v99 14
v99 15
v99 16
v99 17
v98 18 import os, sys, shutil, argparse, re, tempfile, gnupg, subprocess
v99 19
v99 20
v98 21 vver = 98
v99 22
v99 23
v99 24
v99 25
v99 26
v99 27
v99 28
v99 29
v99 30
v99 31 prolog = '''\
v99 32 (C) 2015 NoSuchlAbs.
v99 33 You do not have, nor can you ever acquire the right to use, copy or distribute
v99 34 this software ; Should you use this software for any purpose, or copy and
v99 35 distribute it to anyone or in any manner, you are breaking the laws of whatever
v99 36 soi-disant jurisdiction, and you promise to continue doing so for the indefinite
v99 37 future. In any case, please always : read and understand any software ;
v99 38 verify any PGP signatures that you use - for any purpose.
v99 39 '''
v99 40
v99 41 intro = "V (ver. {0}K)\n".format(vver)
v99 42
v99 43
v99 44 def toposort(unsorted):
v99 45 sorted = []
v99 46 unsorted = dict(unsorted)
v99 47 while unsorted:
v99 48 acyclic = False
v99 49 for node, edges in unsorted.items():
v99 50 for edge in edges:
v99 51 if edge in unsorted:
v99 52 break
v99 53 else:
v99 54 acyclic = True
v99 55 del unsorted[node]
v99 56 sorted.append((node, edges))
v99 57 if not acyclic:
v99 58 fatal("Cyclic graph!")
v99 59 return sorted
v99 60
v99 61
v98 62 vpatch_path = "vpatch"
v99 63 verbose = False
v99 64
v99 65 def fatal(msg):
v99 66 sys.stderr.write(msg + "\n")
v99 67 exit(1)
v99 68
v99 69 def spew(msg):
v99 70 if verbose:
v99 71 print msg
v99 72
v99 73
v99 74 def dir_files(dir):
v99 75 return sorted([os.path.join(dir, fn) for fn in next(os.walk(dir))[2]])
v99 76
v99 77
v99 78
v99 79 gpgtmp = tempfile.mkdtemp()
v99 80 gpg = gnupg.GPG(gnupghome=gpgtmp)
v99 81 gpg.encoding = 'utf-8'
v99 82
v99 83
v99 84 pubkeys = {}
v99 85
v99 86
v99 87 patches = []
v99 88
v99 89
v99 90 banners = {}
v99 91
v99 92
v99 93 roots = []
v99 94
v99 95
v99 96 desc = {}
v99 97 desc['false'] = 'false'
v99 98
v99 99
v99 100
v99 101 def vpdata(path, exp, cache):
v99 102 l = cache.get(path)
v99 103 if not l:
v99 104 l = []
v99 105 patch = open(path, 'r').read()
v99 106 for m in re.findall(exp, patch, re.MULTILINE):
v99 107 l += [{'p':m[0], 'h':m[1]}]
v99 108 cache[path] = l
v99 109 return l
v99 110
v99 111
v99 112 pcache = {}
v99 113 def parents(vpatch):
v99 114 parents = vpdata(vpatch, r'^--- (\S+) (\S+)$', pcache)
v99 115 if not parents:
v99 116 fatal("{0} is INVALID, check whether it IS a vpatch!".format(vpatch))
v99 117 return parents
v99 118
v99 119
v99 120 ccache = {}
v99 121 def children(vpatch):
v99 122 children = vpdata(vpatch, r'^\+\+\+ (\S+) (\S+)$', ccache)
v99 123 if not children:
v99 124 fatal("{0} is INVALID, check whether it IS a vpatch!".format(vpatch))
v99 125
v99 126 for child in children:
v99 127 h = child['h']
v99 128 if h != 'false':
v99 129 desc[h] = vpatch
v99 130 return children
v99 131
v99 132
v99 133
v99 134 def find_roots(patchset):
v99 135 rset = []
v99 136
v99 137 for p in patchset:
v99 138 if all(p['h'] == 'false' for p in parents(p)):
v99 139 rset += [p]
v99 140 spew("Found a Root: '{0}'".format(p))
v99 141 return rset
v99 142
v99 143
v99 144 def get_ante(vpatch):
v99 145 ante = {}
v99 146 for p in parents(vpatch):
v99 147 pp = desc.get(p['h'])
v99 148 if not ante.get(pp):
v99 149 ante[pp] = []
v99 150 ante[pp] += [p['p']]
v99 151 return ante
v99 152
v99 153
v99 154 def get_desc(vpatch):
v99 155 des = {}
v99 156 for p in patches:
v99 157 ante = get_ante(p)
v99 158 if vpatch in ante.keys():
v99 159 des[p] = ante[vpatch]
v99 160 return des
v99 161
v99 162
v99 163
v99 164
v99 165 def disp_vp(vpatch):
v99 166 seals = ', '.join(map(str, banners[vpatch]))
v99 167 if seals == '':
v99 168 seals = 'WILD'
v99 169 return "{0} ({1})".format(vpatch, seals)
v99 170
v99 171
v99 172
v99 173
v99 174 def c_wot(args):
v99 175 for k in pubkeys.values():
v99 176 print "{0}:{1} ({2})".format(k['handle'], k['fp'], k['id'])
v99 177
v99 178
v99 179 def c_flow(args):
v99 180 for p in patches:
v99 181 print disp_vp(p)
v99 182
v99 183
v99 184 def c_roots(args):
v99 185 for r in roots:
v99 186 print "Root: " + disp_vp(r)
v99 187
v99 188
v99 189 def c_ante(args):
v99 190 ante = get_ante(args.query)
v99 191 for p in ante.keys():
v99 192 if p != 'false':
v99 193 print "{0} [{1}]".format(disp_vp(p), '; '.join(map(str, ante[p])))
v99 194
v99 195
v99 196 def c_desc(args):
v99 197 des = get_desc(args.query)
v99 198 for d in des.keys():
v99 199 print "Descendant: {0} [{1}]".format(disp_vp(d), '; '.join(map(str, des[d])))
v99 200
v99 201
v99 202 def c_press(args):
v99 203 print "Pressing using head: {0} to path: '{1}'".format(args.head, args.dest)
v99 204 headpos = patches.index(args.head)
v99 205 seq = patches[:headpos + 1]
v98 206 if os.path.exists(args.dest):
v98 207 print "Warning: target {0} already exists".format(args.dest)
v98 208 else:
v98 209 os.mkdir(args.dest)
v99 210 for p in seq:
v99 211 print "Using: {0}".format(disp_vp(p))
v98 212 out = subprocess.Popen([vpatch_path], cwd=args.dest, stdin=subprocess.PIPE,
v98 213 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
v98 214 with open(p, "r") as inp:
v98 215 body = inp.read()
v98 216 stdout, stderr = out.communicate(body)
v98 217 print stdout,
v98 218 if out.returncode != 0:
v98 219 fatal(("-----------------------------------\n" +
v98 220 "There was an error while pressing:\n" +
v98 221 "{0}\n" +
v98 222 "Result in {1} might be in an invalid state!\n" +
v98 223 "-----------------------------------").format(stderr.strip(), args.dest))
v99 224 print "Completed Pressing using head: {0} to path: '{1}'".format(args.head, args.dest)
v99 225
v99 226
v99 227 def c_origin(args):
v99 228 o = desc.get(args.query)
v99 229 if o:
v99 230 print disp_vp(o)
v99 231 else:
v99 232 print "No origin known."
v99 233
v99 234
v99 235
v99 236
v99 237
v99 238 parser = argparse.ArgumentParser(description=intro, epilog=prolog)
v99 239
v99 240
v99 241 parser.add_argument('-v', dest='verbose', default=False,
v99 242 action="store_true", help='Verbose.')
v99 243
v99 244
v99 245 parser.add_argument('-wild', dest='wild', default=False,
v99 246 action="store_true", help='Permit wild (UNSEALED!) vpatches.')
v99 247
v99 248
v99 249 parser.add_argument('-fingers', dest='fingers', default=False,
v99 250 action="store_true", help='Prefix keyid to all WoT handles.')
v99 251
v99 252
v99 253
v99 254 parser.add_argument('--wot', dest='wot', default=os.path.join(os.path.expanduser('~'), '.wot'),
v99 255 action="store", help='Use WoT in given directory. (Default: ~/.wot)')
v99 256
v99 257
v99 258
v99 259 parser.add_argument('--seals', dest='seals', default=os.path.join(os.path.expanduser('~'), '.seals'),
v99 260 action="store", help='Use Seals in given directory. (Default: ~/.seals)')
v99 261
v99 262
v99 263 parser.add_argument('vpatches', help='Vpatch directory to operate on. [REQUIRED]')
v99 264
v99 265
v99 266 subparsers = parser.add_subparsers(help='Command [REQUIRED]')
v99 267
v99 268 parser_w = subparsers.add_parser('w', help='Display WoT.')
v99 269 parser_w.set_defaults(f=c_wot)
v99 270
v99 271 parser_r = subparsers.add_parser('r', help='Display Roots.')
v99 272 parser_r.set_defaults(f=c_roots)
v99 273
v99 274 parser_a = subparsers.add_parser('a', help='Display Antecedents [PATCH]')
v99 275 parser_a.set_defaults(f=c_ante)
v99 276 parser_a.add_argument('query', action="store", help='Patch.')
v99 277
v99 278 parser_d = subparsers.add_parser('d', help='Display Descendants [PATCH]')
v99 279 parser_d.set_defaults(f=c_desc)
v99 280 parser_d.add_argument('query', action="store", help='Patch.')
v99 281
v99 282 parser_l = subparsers.add_parser('f', help='Compute Flow.')
v99 283 parser_l.set_defaults(f=c_flow)
v99 284
v99 285 parser_p = subparsers.add_parser('p', help='Press [HEADPATCH AND DESTINATION]')
v99 286 parser_p.set_defaults(f=c_press)
v99 287 parser_p.add_argument('head', action="store", help='Head patch.')
v99 288 parser_p.add_argument('dest', action="store", help='Destionation directory.')
v99 289
v99 290 parser_o = subparsers.add_parser('o', help='Find Origin [SHA512]')
v99 291 parser_o.set_defaults(f=c_origin)
v99 292 parser_o.add_argument('query', action="store", help='SHA512 to search for.')
v99 293
v99 294
v99 295
v99 296
v99 297
v99 298 def reqdir(path):
v99 299 if (not (os.path.isdir(path))):
v99 300 fatal("Directory '{0}' does not exist!".format(path))
v99 301 return path
v99 302
v99 303
v99 304 def main():
v99 305 global verbose, pubkeys, patches, roots, banners
v99 306
v99 307 args = parser.parse_args()
v99 308 verbose = args.verbose
v99 309
v99 310
v99 311 pdir = reqdir(args.vpatches)
v99 312 sdir = reqdir(args.seals)
v99 313 wdir = reqdir(args.wot)
v99 314
v99 315 spew("Using patches from:" + pdir)
v99 316 spew("Using signatures from:" + sdir)
v99 317 spew("Using wot from:" + wdir)
v99 318
v99 319 pfiles = dir_files(pdir)
v99 320 sfiles = dir_files(sdir)
v99 321 wfiles = dir_files(wdir)
v99 322
v99 323
v99 324 handle = {}
v99 325 for w in wfiles:
v99 326 pubkey = open(w, 'r').read()
v99 327 impkey = gpg.import_keys(pubkey)
v99 328 for fp in impkey.fingerprints:
v99 329 handle[fp] = os.path.splitext(os.path.basename(w))[0]
v99 330
v99 331 for k in gpg.list_keys():
v99 332 name = handle[k['fingerprint']]
v99 333 if args.fingers:
v99 334 name += '-' + k['keyid']
v98 335 uids = ', '.join(map(str, k['uids']))
v99 336 pubkeys[k['keyid']] = {'fp':k['fingerprint'],
v98 337 'id':uids,
v99 338 'handle':name}
v98 339 for subkey in k.get('subkeys', []):
v98 340 keyid, fingerprint = subkey[0], subkey[2]
v98 341 pubkeys[keyid] = {'fp':fingerprint,
v98 342 'id':uids,
v98 343 'handle':name}
v99 344
v99 345
v99 346 for p in pfiles:
v99 347 pt = os.path.basename(p)
v99 348 banners[p] = []
v99 349 for s in sfiles:
v99 350 sig = os.path.basename(s)
v99 351
v99 352 if sig.find(pt) == 0:
v99 353 v = gpg.verify_file(open(s, 'r'), data_filename=p)
v99 354 if v.valid:
v99 355 banners[p] += [pubkeys[v.key_id]['handle']]
v99 356 else:
v99 357 fatal("---------------------------------------------------------------------\n" +
v99 358 "WARNING: {0} is an INVALID seal for {1} !\n".format(sig, pt) +
v99 359 "Check that this user is in your WoT, and that this key has not expired.\n" +
v99 360 "Otherwise remove the invalid seal from your SEALS directory.\n" +
v99 361 "---------------------------------------------------------------------")
v99 362
v99 363
v99 364 for p in pfiles:
v99 365 if banners.get(p) or args.wild:
v99 366 patches += [p]
v99 367 children(p)
v99 368 parents(p)
v99 369
v99 370 roots = find_roots(patches)
v99 371 if not roots:
v99 372 fatal('No roots found!')
v99 373
v99 374
v99 375 l = []
v99 376 for p in patches:
v99 377 l += [(p, get_desc(p).keys())]
v99 378 s = map(lambda x:x[0], toposort(l))
v99 379 patches = s[::-1]
v99 380
v99 381
v99 382 args.f(args)
v99 383
v99 384
v99 385 shutil.rmtree(gpgtmp)
v99 386
v99 387
v99 388
v99 389 if __name__ == '__main__' :
v99 390 main()
v99 391
v99 392