+from sys import version_info
+if version_info.major == 2: # for python2
+ import codecs
+ sys.stdin = codecs.getreader('utf-8')(sys.stdin)
+ sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
+ import io
+ open=lambda f,m='r': io.open(f,m,encoding='utf-8')
+
+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)
+
+def parse_args():
+ global service
+ global inputfile
+ global header
+ global groupfiledir
+ global sitesfile
+ global group
+ global user
+ global of
+ global prefix
+ global key_prefix
+
+ ap = argparse.ArgumentParser(description='process secnet sites files')
+ ap.add_argument('--userv', '-u', action='store_true',
+ help='userv service fragment update mode')
+ ap.add_argument('--conf-key-prefix', action=ActionNoYes,
+ default=True,
+ help='prefix conf file key names derived from sites data')
+ ap.add_argument('--prefix', '-P', nargs=1,
+ help='set prefix')
+ ap.add_argument('arg',nargs=argparse.REMAINDER)
+ av = ap.parse_args()
+ #print(repr(av), file=sys.stderr)
+ service = 1 if av.userv else 0
+ prefix = '' if av.prefix is None else av.prefix[0]
+ key_prefix = av.conf_key_prefix
+ if service:
+ if len(av.arg)!=4:
+ print("Wrong number of arguments")
+ sys.exit(1)
+ (header, groupfiledir, sitesfile, group) = av.arg
+ group = Tainted(group,0,'command line')
+ # untrusted argument from caller
+ if "USERV_USER" not in os.environ:
+ print("Environment variable USERV_USER not found")
+ sys.exit(1)
+ user=os.environ["USERV_USER"]
+ # Check that group is in USERV_GROUP
+ if "USERV_GROUP" not in os.environ:
+ print("Environment variable USERV_GROUP not found")
+ sys.exit(1)
+ ugs=os.environ["USERV_GROUP"]
+ ok=0
+ for i in ugs.split():
+ if group==i: ok=1
+ if not ok:
+ print("caller not in group %s"%group)
+ sys.exit(1)
+ else:
+ if len(av.arg)>3:
+ print("Too many arguments")
+ sys.exit(1)
+ (inputfile, outputfile) = (av.arg + [None]*2)[0:2]
+ if outputfile is None: of=sys.stdout
+ else: of=open(outputfile,'w')
+
+parse_args()
+