From 068a588b206be799352a8113e7ac5627500649e2 Mon Sep 17 00:00:00 2001 From: jgg <> Date: Wed, 29 Sep 1999 02:36:09 +0000 Subject: [PATCH] New mail gateway and dnszoneentry --- doc/samples/ud-zoneupdate | 2 +- templates/change-reply | 14 +++ ud-generate | 2 +- ud-info | 7 +- ud-mailgate | 223 +++++++++++++++++++++++++++++++++++++- ud-xearth | 4 +- userdir-ldap.conf | 1 + userdir_ldap.py | 24 +++- 8 files changed, 262 insertions(+), 15 deletions(-) create mode 100644 templates/change-reply diff --git a/doc/samples/ud-zoneupdate b/doc/samples/ud-zoneupdate index 50bf425..3adc8e5 100644 --- a/doc/samples/ud-zoneupdate +++ b/doc/samples/ud-zoneupdate @@ -3,5 +3,5 @@ set -e sed -e "s/[1-9].*; Serial.*$/`date +%Y%m%d00` ; Serial/" < $1 > $1.new mv -f $1.new $1 -ndc reload 2>&1 /dev/null +/usr/sbin/ndc reload 2>&1 /dev/null diff --git a/templates/change-reply b/templates/change-reply new file mode 100644 index 0000000..7987276 --- /dev/null +++ b/templates/change-reply @@ -0,0 +1,14 @@ +From: __FROM__ +Subject: DB Change Request + +Hello __EMAIL__! + +Your request to change your directory information has been processed. +Note that there is a propogation time for many of the entries so please +be patient. Here are the results: + +__RESULT__ + +Please email __ADMIN__ if you have any questions. + +__ATTR__ diff --git a/ud-generate b/ud-generate index a145b68..def3186 100755 --- a/ud-generate +++ b/ud-generate @@ -272,7 +272,7 @@ def GenMarkers(l,File): if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0: continue; try: - Line = "%8s %8s \"\""%(DecDegree(x,"latitude",1),DecDegree(x,"longitude",1)); + Line = "%8s %8s \"\""%(DecDegree(GetAttr(x,"latitude"),1),DecDegree(GetAttr(x,"longitude"),1)); Line = Sanitize(Line) + "\n"; F.write(Line); except: diff --git a/ud-info b/ud-info index 4245931..7159772 100755 --- a/ud-info +++ b/ud-info @@ -153,11 +153,8 @@ def PrintSshRSAKeys(Attrs): First = 1; else: print "%-24s:" % (""), - Split = string.split(x," "); - - if len(Split) != 4: - del Split[0]; - print Split[0],Split[1],Split[2][:8]+".."+Split[2][-8:],string.join(Split[3:]); + + print FormatSSHAuth(x); # Display all of the attributes in a numbered list def ShowAttrs(Attrs): diff --git a/ud-mailgate b/ud-mailgate index 922a1bb..a93d7cc 100755 --- a/ud-mailgate +++ b/ud-mailgate @@ -9,19 +9,234 @@ from userdir_ldap import *; ReplyTo = ConfModule.replyto; PingFrom = ConfModule.pingfrom; ChPassFrom = ConfModule.chpassfrom; +ChangeFrom = ConfModule.changefrom; ReplayCacheFile = ConfModule.replaycachefile; EX_TEMPFAIL = 75; EX_PERMFAIL = 65; # EX_DATAERR Error = 'Message Error'; +SeenRSA = 0; +SeenDNS = 0; +ArbChanges = {"c": "..", + "l": ".*", + "facsimiletelephonenumber": ".*", + "telephonenumber": ".*", + "postaladdress": ".*", + "postalcode": ".*", + "loginshell": ".*", + "emailforward": "^([^<>@]+@.+)?$", + "ircnick": ".*", + "onvacation": ".*", + "labeledurl": ".*"}; + +# Decode a GPS location from some common forms +def LocDecode(Str,Dir): + # Check for Decimal degrees, DGM, or DGMS + if re.match("^[+-]?[\d.]+$",Str) != None: + return Str; + + Deg = '0'; Min = None; Sec = None; Dr = Dir[0]; + + # Check for DDDxMM.MMMM where x = [nsew] + Match = re.match("^(\d+)(["+Dir+"])([\d.]+)$",Str); + if Match != None: + G = Match.groups(); + Deg = G[0]; Min = G[2]; Dr = G[1]; + + # Check for DD.DD x + Match = re.match("^([\d.]+) ?(["+Dir+"])$",Str); + if Match != None: + G = Match.groups(); + Deg = G[0]; Dr = G[1]; + + # Check for DD:MM.MM x + Match = re.match("^(\d+):([\d.]+) ?(["+Dir+"])$",Str); + if Match != None: + G = Match.groups(); + Deg = G[0]; Min = G[1]; Dr = G[2]; + + # Check for DD:MM:SS.SS x + Match = re.match("^(\d+):(\d+):([\d.]+) ?(["+Dir+"])$",Str); + if Match != None: + G = Match.groups(); + Deg = G[0]; Min = G[1]; Sec = G[2]; Dr = G[3]; + + # Some simple checks + if float(Deg) > 180: + raise "Failed","Bad degrees"; + if Min != None and float(Min) > 60: + raise "Failed","Bad minutes"; + if Sec != None and float(Sec) > 60: + raise "Failed","Bad seconds"; + + # Pad on an extra leading 0 to disambiguate small numbers + if len(Deg) <= 1 or Deg[1] == '.': + Deg = '0' + Deg; + if Min != None and (len(Min) <= 1 or Min[1] == '.'): + Min = '0' + Min; + if Sec != None and (len(Sec) <= 1 or Sec[1] == '.'): + Sec = '0' + Sec; + + # Construct a DGM/DGMS type value from the components. + Res = "+" + if Dr == Dir[1]: + Res = "-"; + Res = Res + Deg; + if Min != None: + Res = Res + Min; + if Sec != None: + Res = Res + Sec; + return Res; + +# Handle changing a set of arbitary fields +# : value +def DoArbChange(Str,Attrs): + Match = re.match("^([^ :]+): (.*)$",Str); + if Match == None: + return None; + G = Match.groups(); + + if ArbChanges.has_key(G[0]) == 0: + return None; + + if re.match(ArbChanges[G[0]],G[1]) == None: + raise Error, "Item does not match the required format"+ArbChanges[G[0]]; + + Attrs.append((ldap.MOD_REPLACE,G[0],G[1])); + return "Changed entry %s to %s"%(G[0],G[1]); + +# Handle a position change message, the line format is: +# Lat: -12412.23 Long: +12341.2342 +def DoPosition(Str,Attrs): + Match = re.match("^lat: ([+\-]?[\d:.ns]+(?: ?[ns])?) long: ([+\-]?[\d:.ew]+(?: ?[ew])?)$",string.lower(Str)); + if Match == None: + return None; + + G = Match.groups(); + try: + sLat = LocDecode(G[0],"ns"); + sLong = LocDecode(G[1],"ew"); + Lat = DecDegree(sLat,1); + Long = DecDegree(sLong,1); + except: + raise Error, "Positions were found, but they are not correctly formed"; + + Attrs.append((ldap.MOD_REPLACE,"latitude",sLat)); + Attrs.append((ldap.MOD_REPLACE,"longitude",sLong)); + return "Position set to %s/%s (%s/%s decimal degrees)"%(sLat,sLong,Lat,Long); + +# Handle a SSH RSA authentication key, the line format is: +# [options] 1024 35 13188913666680[..] [comment] +def DoSSH(Str,Attrs): + Match = SSHAuthSplit.match(Str); + if Match == None: + return None; + + global SeenRSA; + if SeenRSA: + Attrs.append((ldap.MOD_ADD,"sshrsaauthkey",Str)); + return "SSH Key added "+FormatSSHAuth(Str); + + Attrs.append((ldap.MOD_REPLACE,"sshrsaauthkey",Str)); + SeenRSA = 1; + return "SSH Keys replaced with "+FormatSSHAuth(Str); + +# Handle changing a dns entry +# host in a 12.12.12.12 +# host in cname foo.bar. <- Trailing dot is required +def DoDNS(Str,Attrs,DnRecord): + if re.match('^[\w-]+\s+in\s+a\s+\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$',\ + Str,re.IGNORECASE) == None and \ + re.match("^[\w-]+\s+in\s+cname\s+[\w.\-]+\.$",Str,re.IGNORECASE) == None: + return None; + + # Check if the name is already taken + G = re.match('^([\w-+]+)\s',Str).groups(); + + # Check for collisions + global l; + Rec = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"dnszoneentry="+G[0]+" *",["uid"]); + for x in Rec: + if GetAttr(x,"uid") != GetAttr(DnRecord,"uid"): + return "DNS entry is already owned by " + GetAttr(x,"uid") + + global SeenDNS; + if SeenDNS: + Attrs.append((ldap.MOD_ADD,"dnszoneentry",Str)); + return "DNS Entry added "+Str; + + Attrs.append((ldap.MOD_REPLACE,"dnszoneentry",Str)); + SeenDNS = 1; + return "DNS Entry replaced with "+Str; + +# Handle an [almost] arbitary change +def HandleChange(Reply,DnRecord,Key): + global PlainText; + Lines = string.split(PlainText,"\r\n"); + + Result = ""; + Attrs = []; + for Line in Lines: + Line = string.strip(Line); + if Line == "": + continue; + + # Try to process a command line + Result = Result + "> "+Line+"\n"; + Show = 0; + try: + if Line == "show": + Show = 1; + Res = "OK"; + else: + Res = DoPosition(Line,Attrs) or DoSSH(Line,Attrs) or \ + DoDNS(Line,Attrs,DnRecord) or DoArbChange(Line,Attrs); + except: + Res = None; + Result = Result + "==> %s: %s\n" %(sys.exc_type,sys.exc_value); + + # Fail, if someone tries to send someone elses signed email to the + # daemon then we want to abort ASAP. + if Res == None: + Result = Result + "Command is not understood. Halted\n"; + break; + Result = Result + Res + "\n"; + + # Connect to the ldap server + l = ldap.open(LDAPServer); + F = open(PassDir+"/pass-"+pwd.getpwuid(posix.getuid())[0],"r"); + AccessPass = string.split(string.strip(F.readline())," "); + F.close(); + + # Modify the record + l.simple_bind_s("uid="+AccessPass[0]+","+BaseDn,AccessPass[1]); + Dn = "uid=" + GetAttr(DnRecord,"uid") + "," + BaseDn; + l.modify_s(Dn,Attrs); + + Attribs = ""; + if Show == 1: + Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid="+GetAttr(DnRecord,"uid")); + if len(Attrs) == 0: + raise Error, "User not found" + Attribs = GPGEncrypt(PrettyShow(Attrs[0])+"\n","0x"+Key[1],Key[4]); + + Subst = {}; + Subst["__FROM__"] = ChangeFrom; + Subst["__EMAIL__"] = EmailAddress(DnRecord); + Subst["__ADMIN__"] = ReplyTo; + Subst["__RESULT__"] = Result; + Subst["__ATTR__"] = Attribs; + + return Reply + TemplateSubst(Subst,open(TemplatesDir+"change-reply","r").read()); + # Handle ping handles an email sent to the 'ping' address (ie this program # called with a ping argument) It replies with a dump of the public records. def HandlePing(Reply,DnRecord,Key): Subst = {}; Subst["__FROM__"] = PingFrom; Subst["__EMAIL__"] = EmailAddress(DnRecord); - Subst["__LDAPFIELDS__"] = PrettyShow(Attrs[0]); + Subst["__LDAPFIELDS__"] = PrettyShow(DnRecord); Subst["__ADMIN__"] = ReplyTo; return Reply + TemplateSubst(Subst,open(TemplatesDir+"ping-reply","r").read()); @@ -66,7 +281,7 @@ def HandleChPass(Reply,DnRecord,Key): Rec = [(ldap.MOD_REPLACE,"userPassword","{crypt}"+Pass)]; Dn = "uid=" + GetAttr(DnRecord,"uid") + "," + BaseDn; l.modify_s(Dn,Rec); - + return Reply; # Start of main program @@ -96,6 +311,7 @@ try: raise Error, "Null signature text"; # Extract the plain message text in the event of mime encoding + global PlainText; ErrMsg = "Problem stripping MIME headers from the decoded message" if Msg[1] == 1: try: @@ -116,6 +332,7 @@ try: # Connect to the ldap server ErrType = EX_TEMPFAIL; ErrMsg = "An error occured while performing the LDAP lookup"; + global l; l = ldap.open(LDAPServer); l.simple_bind_s("",""); @@ -146,6 +363,8 @@ try: if string.find(string.strip(PlainText),"Please change my Debian password") != 0: raise Error,"Please send a signed message where the first line of text is the string 'Please change my Debian password'"; Reply = HandleChPass(Reply,Attrs[0],Res[2]); + elif sys.argv[1] == "change": + Reply = HandleChange(Reply,Attrs[0],Res[2]); else: print sys.argv; raise Error, "Incorrect Invokation"; diff --git a/ud-xearth b/ud-xearth index 9679866..4f1773b 100755 --- a/ud-xearth +++ b/ud-xearth @@ -52,9 +52,9 @@ for x in Attrs: Count = Count + 1; try: if Anon != 0: - F.write("%8s %8s \"\"\n"%(DecDegree(x,"latitude",Anon),DecDegree(x,"longitude",Anon))); + F.write("%8s %8s \"\"\n"%(DecDegree(GetAttr(x,"latitude"),Anon),DecDegree(GetAttr(x,"longitude"),Anon))); else: - F.write("%16s %16s \"%s\" \t# %s\n"%(DecDegree(x,"latitude",Anon),DecDegree(x,"longitude",Anon),GetAttr(x,"uid"),EmailAddress(x))); + F.write("%16s %16s \"%s\" \t# %s\n"%(DecDegree(GetAttr(x,"latitude"),Anon),DecDegree(GetAttr(x,"longitude"),Anon),GetAttr(x,"uid"),EmailAddress(x))); except: Failed = Failed + 1; if Anon == 0: diff --git a/userdir-ldap.conf b/userdir-ldap.conf index 70344a4..41316b1 100644 --- a/userdir-ldap.conf +++ b/userdir-ldap.conf @@ -13,6 +13,7 @@ maildomain = "db.debian.org"; replyto = "admin@" + maildomain; pingfrom = "ping@" + maildomain; chpassfrom = "chpasswd@" + maildomain; +changefrom = "change@" + maildomain; templatesdir = "/etc/userdir-ldap/templates/"; replaycachefile = "/var/cache/userdir-ldap/replay"; #replaycachefile = "/tmp/replay"; diff --git a/userdir_ldap.py b/userdir_ldap.py index 21b215b..32b830e 100644 --- a/userdir_ldap.py +++ b/userdir_ldap.py @@ -21,7 +21,12 @@ PassDir = ConfModule.passdir; # This is a list of common last-name prefixes LastNamesPre = {"van": None, "le": None, "de": None, "di": None}; - + +# SSH Key splitting. The result is: +# (options,size,modulous,exponent,comment) +SSHAuthSplit = re.compile('^(.* )?(\d+) (\d+) (\d+) (.+)$'); +#'^([^\d](?:[^ "]+(?:".*")?)*)? ?(\d+) (\d+) (\d+) (.+)$'); + # Safely get an attribute from a tuple representing a dn and an attribute # list. It returns the first attribute if there are multi. def GetAttr(DnRecord,Attribute,Default = ""): @@ -170,9 +175,9 @@ def FlushOutstanding(l,Outstanding,Fast=0): return Outstanding; # Convert a lat/long attribute into Decimal degrees -def DecDegree(Attr,Type,Anon=0): - Parts = re.match('[+-]?(\d*)\\.?(\d*)?',GetAttr(Attr,Type)).groups(); - Val = string.atof(GetAttr(Attr,Type)); +def DecDegree(Posn,Anon=0): + Parts = re.match('[+-]?(\d*)\\.?(\d*)?',Posn).groups(); + Val = string.atof(Posn); if (abs(Val) >= 1806060.0): raise ValueError,"Too Big"; @@ -198,3 +203,14 @@ def DecDegree(Attr,Type,Anon=0): if Val >= 0: return "+" + Str; return Str; + +def FormatSSHAuth(Str): + Match = SSHAuthSplit.match(Str); + if Match == None: + return ""; + G = Match.groups(); + + # No options + if G[0] == None: + return "%s %s %s..%s %s"%(G[1],G[2],G[3][:8],G[3][-8:],G[4]); + return "%s %s %s %s..%s %s"%(G[0],G[1],G[2],G[3][:8],G[3][-8:],G[4]); -- 2.20.1