+max={'rsa_bits':8200,'name':33,'dh_bits':8200}
+
+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): 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):
+ ok=self._re_ok(Tainted.bad_name,'name')
+ 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)
+
+ 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)
+