+max={'rsa_bits':8200,'name':33,'dh_bits':8200,'algname':127}
+
+def debugrepr(*args):
+ if debug_level > 0:
+ print(repr(args), file=sys.stderr)
+
+def base91s_encode(bindata):
+ return base91.encode(bindata).replace('"',"-")
+
+def base91s_decode(string):
+ return base91.decode(string.replace("-",'"'))
+
+class Tainted:
+ def __init__(self,s,tline=None,tfile=None):
+ self._s=s
+ self._ok=None
+ self._line=line if tline is None else tline
+ self._file=file if tfile is None else tfile
+ def __eq__(self,e):
+ return self._s==e
+ def __ne__(self,e):
+ # for Python2
+ return not self.__eq__(e)
+ def __str__(self):
+ raise RuntimeError('direct use of Tainted value')
+ def __repr__(self):
+ return 'Tainted(%s)' % repr(self._s)
+
+ def _bad(self,what,why):
+ assert(self._ok is not True)
+ self._ok=False
+ complain('bad parameter: %s: %s' % (what, why))
+ return False
+
+ def _max_ok(self,what,maxlen):
+ if len(self._s) > maxlen:
+ return self._bad(what,'too long (max %d)' % maxlen)
+ return True
+
+ def _re_ok(self,bad,what,maxlen=None):
+ if maxlen is None: maxlen=max[what]
+ self._max_ok(what,maxlen)
+ if self._ok is False: return False
+ if bad.search(self._s):
+ #print(repr(self), file=sys.stderr)
+ return self._bad(what,'bad syntax')
+ return True
+
+ def _rtnval(self, is_ok, ifgood, ifbad=''):
+ if is_ok:
+ assert(self._ok is not False)
+ self._ok=True
+ return ifgood
+ else:
+ assert(self._ok is not True)
+ self._ok=False
+ return ifbad
+
+ def _rtn(self, is_ok, ifbad=''):
+ return self._rtnval(is_ok, self._s, ifbad)
+
+ def raw(self):
+ return self._s
+ def raw_mark_ok(self):
+ # caller promises to throw if syntax was dangeorus
+ return self._rtn(True)
+
+ def output(self):
+ if self._ok is False: return ''
+ if self._ok is True: return self._s
+ print('%s:%d: unchecked/unknown additional data "%s"' %
+ (self._file,self._line,self._s),
+ file=sys.stderr)
+ sys.exit(1)
+
+ bad_name=re.compile(r'^[^a-zA-Z]|[^-_0-9a-zA-Z]')
+ # secnet accepts _ at start of names, but we reserve that
+ bad_name_counter=0
+ def name(self,what='name'):
+ ok=self._re_ok(Tainted.bad_name,what)
+ return self._rtn(ok,
+ '_line%d_%s' % (self._line, id(self)))
+
+ def keyword(self):
+ ok=self._s in keywords or self._s in levels
+ if not ok:
+ complain('unknown keyword %s' % self._s)
+ return self._rtn(ok)
+
+ bad_hex=re.compile(r'[^0-9a-fA-F]')
+ def bignum_16(self,kind,what):
+ maxlen=(max[kind+'_bits']+3)/4
+ ok=self._re_ok(Tainted.bad_hex,what,maxlen)
+ return self._rtn(ok)
+
+ bad_num=re.compile(r'[^0-9]')
+ def bignum_10(self,kind,what):
+ maxlen=math.ceil(max[kind+'_bits'] / math.log10(2))
+ ok=self._re_ok(Tainted.bad_num,what,maxlen)
+ return self._rtn(ok)
+
+ def number(self,minn,maxx,what='number'):
+ # not for bignums
+ ok=self._re_ok(Tainted.bad_num,what,10)
+ if ok:
+ v=int(self._s)
+ if v<minn or v>maxx:
+ ok=self._bad(what,'out of range %d..%d'
+ % (minn,maxx))
+ return self._rtnval(ok,v,minn)
+
+ def hexid(self,byteslen,what):
+ ok=self._re_ok(Tainted.bad_hex,what,byteslen*2)
+ if ok:
+ if len(self._s) < byteslen*2:
+ ok=self._bad(what,'too short')
+ return self._rtn(ok,ifbad='00'*byteslen)
+
+ bad_host=re.compile(r'[^-\][_.:0-9a-zA-Z]')
+ # We permit _ so we can refer to special non-host domains
+ # which have A and AAAA RRs. This is a crude check and we may
+ # still produce config files with syntactically invalid
+ # domains or addresses, but that is OK.
+ def host(self):
+ ok=self._re_ok(Tainted.bad_host,'host/address',255)
+ return self._rtn(ok)
+
+ bad_email=re.compile(r'[^-._0-9a-z@!$%^&*=+~/]')
+ # ^ This does not accept all valid email addresses. That's
+ # not really possible with this input syntax. It accepts
+ # all ones that don't require quoting anywhere in email
+ # protocols (and also accepts some invalid ones).
+ def email(self):
+ ok=self._re_ok(Tainted.bad_email,'email address',1023)
+ return self._rtn(ok)
+
+ bad_groupname=re.compile(r'^[^_A-Za-z]|[^-+_0-9A-Za-z]')
+ def groupname(self):
+ ok=self._re_ok(Tainted.bad_groupname,'group name',64)
+ return self._rtn(ok)
+
+ bad_base91=re.compile(r'[^!-~]|[\'\"\\]')
+ def base91(self,what='base91'):
+ ok=self._re_ok(Tainted.bad_base91,what,4096)
+ return self._rtn(ok)
+
+class ArgActionLambda(argparse.Action):
+ def __init__(self, fn, **kwargs):
+ self.fn=fn
+ argparse.Action.__init__(self,**kwargs)
+ def __call__(self,ap,ns,values,option_string):
+ self.fn(values,ns,ap,option_string)
+
+class PkmBase():
+ def site_start(self,pubkeys_path):
+ self._pa=pubkeys_path
+ self._fs = FilterState()
+ def site_serial(self,serial): pass
+ def write_key(self,k): pass
+ def site_finish(self,confw): pass
+
+class PkmSingle(PkmBase):
+ opt = 'single'
+ help = 'write one public key per site to sites.conf'
+ def site_start(self,pubkeys_path):
+ PkmBase.site_start(self,pubkeys_path)
+ self._outk = []
+ def write_key(self,k):
+ if k.okforonlykey(output_version,self._fs):
+ self._outk.append(k)
+ def site_finish(self,confw):
+ if len(self._outk) == 0:
+ complain("site with no public key");
+ elif len(self._outk) != 1:
+ debugrepr('outk ', self._outk)
+ complain(
+ "site with multiple public keys, without --pubkeys-install (maybe --output-version=1 would help"
+ )
+ else:
+ confw.write("key %s;\n"%str(self._outk[0]))
+
+class PkmInstall(PkmBase):
+ opt = 'install'
+ help = 'install public keys in public key directory'
+ def site_start(self,pubkeys_path):
+ PkmBase.site_start(self,pubkeys_path)
+ self._pw=open(self._pa+'~tmp','w')
+ def site_serial(self,serial):
+ self._pw.write('serial %s\n' % serial)
+ def write_key(self,k):
+ wout=k.forpub(output_version,self._fs)
+ self._pw.write(' '.join(wout))
+ self._pw.write('\n')
+ def site_finish(self,confw):
+ self._pw.close()
+ os.rename(self._pa+'~tmp',self._pa+'~update')
+ confw.write("peer-keys \"%s\";\n"%self._pa);
+