From: Joerg Jaspert Date: Mon, 12 May 2008 22:12:56 +0000 (+0200) Subject: First version of a check for ssh keys X-Git-Tag: userdir-ldap-0.3.22~1^2~2 X-Git-Url: https://git.adam-barratt.org.uk/?p=mirror%2Fuserdir-ldap.git;a=commitdiff_plain;h=0159c3e12f39119617f4c93319a1500284f8958a;hp=759553491799cf3c2875c67a0c1ff92c98a4d309 First version of a check for ssh keys --- diff --git a/TODO b/TODO new file mode 100644 index 0000000..2e26cfd --- /dev/null +++ b/TODO @@ -0,0 +1,16 @@ + - Not done + * Top priority + . Partially done + o Done + d Deferrable + D Deferred + X Abandoned + +- db.d.o/machines.cgi should group machines by purpose (RT#275) +- some mails from the mail gateway should use an empty envelope sender (RT#593) +- add aliasnames (db, buildd, ..) to ssh_known_hosts +- get rid of openssh patch requirement +- use --delete in ud-replicate's rsync? +- fix ud-useradd emails with non-ascii in subject +- allow to keep further auth tokens in the LDAP, for example for SMTP AUTH or + SIP stuff. Or OpenID. Or whatever. diff --git a/debian/changelog b/debian/changelog index c6fc647..8716d70 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,31 @@ -userdir-ldap (0.3.15+xxx) XXunstable; urgency=low +userdir-ldap (0.3.20) unstable; urgency=low + + * Teach ud-mailgate about ipv6 addresses (RT#193). + * Sanitize DNS entries somewhat before inserting them into LDAP. + + -- Peter Palfrader Mon, 21 Apr 2008 13:29:36 +0200 + +userdir-ldap (0.3.19) unstable; urgency=low + + * New [KEYRING] flag to indicate the debian keyring should be synced + to this host. + + -- Peter Palfrader Fri, 18 Apr 2008 14:33:50 +0200 + +userdir-ldap (0.3.18) unstable; urgency=low + + * Various ud-fingerserv fixes. + + -- Peter Palfrader Thu, 17 Apr 2008 19:48:11 +0200 + +userdir-ldap (0.3.17) unstable; urgency=low + + * Calling dh_installdeb before dh_pysupport was probably not the smartest + move. Reorder. + + -- Peter Palfrader Wed, 16 Apr 2008 19:59:42 +0200 + +userdir-ldap (0.3.16) unstable; urgency=low [ Peter Palfrader ] * Ship userdir-ldap.schema with the package, add a note that it is @@ -12,6 +39,10 @@ userdir-ldap (0.3.15+xxx) XXunstable; urgency=low * ud-roleadd: Do not try to make role accounts of objectClass inetOrgPerson, that doesn't work. * Add myself to uploaders. + * Create /var/lib/misc/thishost as a symlink to the hostname in postinst. + * Sleep for a random time, up to two minutes, in ud-replicate when not + called interactively. This is to prevent DoSing the db server when + many clients come at the same time. [ Mark Hymers ] * ud-userimport, ud-groupadd, ud-roleadd, ud-useradd, userdir_ldap.py: @@ -60,7 +91,7 @@ userdir-ldap (0.3.15+xxx) XXunstable; urgency=low * Build manpages at build time (add Build-Depend on yodl) * Install built manpages - -- Stephen Gran Mon, 07 Jan 2008 01:50:15 +0000 + -- Peter Palfrader Wed, 16 Apr 2008 14:10:12 +0200 userdir-ldap (0.3.15) unstable; urgency=low diff --git a/debian/postinst b/debian/postinst index e748473..ec8cc5f 100644 --- a/debian/postinst +++ b/debian/postinst @@ -4,5 +4,9 @@ if [ "$1" = "configure" ] then test ! -f /usr/local/bin/ud-replicate || rm -f /usr/local/bin/ud-replicate + + if ! [ -e /var/lib/misc/thishost ]; then + ln -s "`hostname -f`" /var/lib/misc/thishost + fi fi exit 0 diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..ddfd7c4 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,10 @@ +#! /bin/bash -e +# +#DEBHELPER# +if [ "$1" = "purge" ] +then + if [ -L /var/lib/misc/thishost ]; then + rm /var/lib/misc/thishost + fi +fi +exit 0 diff --git a/debian/rules b/debian/rules index 6ac2da5..414f1fc 100755 --- a/debian/rules +++ b/debian/rules @@ -27,8 +27,8 @@ binary-indep: build dh_installman dh_fixperms dh_compress - dh_installdeb dh_pysupport + dh_installdeb dh_gencontrol dh_md5sums dh_builddeb diff --git a/ud-fingerserv b/ud-fingerserv index 1c977d8..0794596 100755 --- a/ud-fingerserv +++ b/ud-fingerserv @@ -3,6 +3,7 @@ # (c) 1999 Randolph Chung. Licensed under the GPL. # (c) 2004 Martin Schulze. Licensed under the GPL. +# Copyright (c) 2008 Peter Palfrader use lib '/var/www/userdir-ldap/'; #use lib '/home/randolph/projects/userdir-ldap/web'; @@ -43,9 +44,9 @@ $SIG{CHLD} = \&Reaper; &help if (defined($opts{h})); my $logfh; -unless ($opt{i} || $opt{f}) { - die "Need logfile unless running foreground\n" unless (defined($opt{l})); - open ($logfh, $opt{l}) or die "Can't open logfile: $!\n"; +unless ($opts{i} || $opts{f}) { + die "Need logfile unless running foreground\n" unless (defined($opts{l})); + open ($logfh, $opts{l}) or die "Can't open logfile: $!\n"; } else { $logfh = \*STDOUT; } @@ -76,7 +77,7 @@ if (!$use_inetd) { Listen => SOMAXCONN, Reuse => 1); - mydie "Cannot listen on finger port" unless $server; + mydie("Cannot listen on finger port") unless $server; &log("[Server listening for connections]"); my ($pid, $client, $hostinfo); @@ -84,7 +85,7 @@ if (!$use_inetd) { while ($client = $server->accept()) { &log("Forking to handle client request") if (defined($opts{v})); next if $pid = fork; # parent - mydie "fork: $!" unless defined $pid; + mydie("fork: $!") unless defined $pid; # child $client->autoflush(1); diff --git a/ud-generate b/ud-generate index 8537f0c..d2f00ab 100755 --- a/ud-generate +++ b/ud-generate @@ -6,6 +6,7 @@ # Copyright (c) 2003-2004 James Troup # Copyright (c) 2004-2005,7 Joey Schulze # Copyright (c) 2001-2007 Ryan Murray +# Copyright (c) 2008 Peter Palfrader # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,7 +22,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha +import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil from userdir_ldap import *; global Allowed; @@ -35,6 +36,8 @@ CurrentHost = ""; EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$"); BSMTPCheck = re.compile(".*mx 0 (gluck)\.debian\.org\..*",re.DOTALL); DNSZone = ".debian.net" +Keyrings = [ "/org/keyring.debian.org/keyrings/debian-keyring.gpg", + "/org/keyring.debian.org/keyrings/debian-keyring.pgp" ] def Sanitize(Str): return Str.translate(string.maketrans("\n\r\t","$$$")) @@ -715,12 +718,23 @@ def GenSSHKnown(l,File): x[1].has_key("sshRSAHostKey") == 0: continue; Host = GetAttr(x,"hostname"); + HostNames = [ Host ] SHost = Host.find(".") + if SHost != None: HostNames += [Host[0:SHost]] + + IPAdressesT = None + IPAdresses = [] + # get IP adresses back as "proto adress" to distinguish between v4 and v6 + try: + IPAdressesT = set([ (a[0],a[4][0]) for a in socket.getaddrinfo(Host, None)]) + except: + if code[0] != -2: raise + for addr in IPAdressesT: + if addr[0] == socket.AF_INET: IPAdresses += [addr[1], "::ffff:"+addr[1]] + else: IPAdresses += [addr[1]] + for I in x[1]["sshRSAHostKey"]: - if SHost == None: - Line = "%s,%s %s" %(Host,socket.gethostbyname(Host),I); - else: - Line = "%s,%s,%s %s" %(Host,Host[0:SHost],socket.gethostbyname(Host),I); + Line = "%s,%s %s" %(",".join(HostNames + IPAdresses), I); Line = Sanitize(Line) + "\n"; F.write(Line); # Oops, something unspeakable happened. @@ -759,6 +773,10 @@ def GenHosts(l,File): raise; Done(File,F,None); +def GenKeyrings(l,OutDir): + for k in Keyrings: + shutil.copy(k, OutDir) + # Connect to the ldap server l = ldap.open(LDAPServer); F = open(PassDir+"/pass-"+pwd.getpwuid(os.getuid())[0],"r"); @@ -812,10 +830,11 @@ GenMailBool(l,GlobalDir+"mail-callout","mailCallout"); GenMailList(l,GlobalDir+"mail-rbl","mailRBL"); GenMailList(l,GlobalDir+"mail-rhsbl","mailRHSBL"); GenMailList(l,GlobalDir+"mail-whitelist","mailWhitelist"); +GenKeyrings(l,GlobalDir); # Compatibility. GenForward(l,GlobalDir+"forward-alias"); - + while(1): Line = F.readline(); if Line == "": @@ -886,3 +905,11 @@ while(1): if ExtraList.has_key("[PRIVATE]"): DoLink(GlobalDir,OutDir,"debian-private") + + if ExtraList.has_key("[KEYRING]"): + for k in Keyrings: + DoLink(GlobalDir,OutDir,os.path.basename(k)) + else: + for k in Keyrings: + try: posix.remove(OutDir+os.path.basename(k)); + except: pass; diff --git a/ud-info b/ud-info index 8fde99a..e533a9e 100755 --- a/ud-info +++ b/ud-info @@ -55,7 +55,8 @@ AttrInfo = {"cn": ["First Name", 101], "mailWhitelist": ["Mail Whitelist",24], "comment": ["Comment",116], "userPassword": ["Crypted Password",117], - "dnsZoneEntry": ["d.net Entry",118]}; + "dnsZoneEntry": ["d.net Entry",118], + "VoIP": ["VoIP Address",119]}; AttrPrompt = {"cn": ["Common name or first name"], "mn": ["Middle name (or initial if it ends in a dot)"], @@ -89,7 +90,8 @@ AttrPrompt = {"cn": ["Common name or first name"], "dnsZoneEntry": ["DNS Zone fragment associated this this user"], "labeledURI": ["Web home page"], "jabberJID": ["Jabber ID"], - "icqUin": ["ICQ UIN Number"]}; + "icqUin": ["ICQ UIN Number"], + "VoIP": ["VoIP Address"]}; # Create a map of IDs to desc,value,attr OrderedIndex = {}; diff --git a/ud-mailgate b/ud-mailgate index cf82b56..94a4a71 100755 --- a/ud-mailgate +++ b/ud-mailgate @@ -1,9 +1,14 @@ #!/usr/bin/env python # -*- mode: python -*- -import userdir_gpg, userdir_ldap, sys, traceback, time, ldap, os; -import pwd -from userdir_gpg import *; -from userdir_ldap import *; + +# Prior copyright probably rmurray, troup, joey, jgg -- weasel 2008 +# Copyright (c) 2008 Peter Palfrader +# Copyright (c) 2008 Joerg Jaspert + +import userdir_gpg, userdir_ldap, sys, traceback, time, ldap, os, commands +import pwd, tmpfile +from userdir_gpg import * +from userdir_ldap import * # Error codes from /usr/include/sysexits.h ReplyTo = ConfModule.replyto; @@ -11,6 +16,7 @@ PingFrom = ConfModule.pingfrom; ChPassFrom = ConfModule.chpassfrom; ChangeFrom = ConfModule.changefrom; ReplayCacheFile = ConfModule.replaycachefile; +SSHFingerprintFile = ConfModule.fingerprintfile EX_TEMPFAIL = 75; EX_PERMFAIL = 65; # EX_DATAERR @@ -23,6 +29,8 @@ mailWhitelist = {} SeenList = {} DNS = {} +SSHFingerprint = re.compile('^(\d+) ([0-9a-f\:]{47}) (.+)$') + ArbChanges = {"c": "..", "l": ".*", "facsimileTelephoneNumber": ".*", @@ -40,6 +48,7 @@ ArbChanges = {"c": "..", "mailDisableMessage": ".*", "mailGreylisting": "^(TRUE|FALSE)$", "mailCallout": "^(TRUE|FALSE)$", + "VoIP": ".*", }; DelItems = {"c": None, @@ -67,6 +76,7 @@ DelItems = {"c": None, "mailRHSBL": None, "mailWhitelist": None, "mailDisableMessage": None, + "VoIP": None, }; # Decode a GPS location from some common forms @@ -212,16 +222,46 @@ def DoPosition(Str,Attrs): Attrs.append((ldap.MOD_REPLACE,"longitude",sLong)); return "Position set to %s/%s (%s/%s decimal degrees)"%(sLat,sLong,Lat,Long); +# Load bad ssh fingerprints +def LoadBadSSH(): + f = open(SSHFingerprintFile, "r") + bad = [] + FingerprintLine = re.compile('^([0-9a-f\:]{47}).*$') + for line in f.readlines(): + Match = FingerprintLine.match(line) + if Match is not None: + g = Match.groups() + bad.append(g[0]) + return bad + # Handle an SSH authentication key, the line format is: # [options] 1024 35 13188913666680[..] [comment] -def DoSSH(Str,Attrs): +def DoSSH(Str,Attrs, badkeys): Match = SSH2AuthSplit.match(Str); if Match == None: Match = re.compile('^1024 (\d+) ').match(Str) if Match is not None: return "SSH1 keys not supported anymore" return None; - + + (fd, path) = tempfile.mkstemp("", "sshkeytry") + f = open(path, "w") + f.write(Str) + f.close + (result, output) = commands.getstatusoutput("ssh-keygen -f %s -l" % (path)) + os.remove(path) + if (result != 0): + sys.stderr.write("ssh-keygen -l invocation failed!\n%s\n" % (output)) + sys.exit(result) + + Match = SSHFingerprint.match(output) + + g = Match.groups() + if (g[0] < 1024): + return "SSH keys must have at least 1024 bits, not added" + elif g[0] in badkeys: + return "Submitted SSH Key known to be bad and insecure, not added" + global SeenKey; if SeenKey: Attrs.append((ldap.MOD_ADD,"sshRSAAuthKey",Str)); @@ -232,17 +272,28 @@ def DoSSH(Str,Attrs): 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 +# host IN A 12.12.12.12 +# host IN AAAA 1234::5678 +# host IN CNAME foo.bar. <- Trailing dot is required +# host IN MX foo.bar. <- Trailing dot is required def DoDNS(Str,Attrs,DnRecord): - cname = re.match("^[-\w]+\s+in\s+cname\s+[-\w.]+\.$",Str,re.IGNORECASE); - 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 cname == None and \ - re.match("^[-\w]+\s+in\s+mx\s+\d{1,3}\s+[-\w.]+\.$",Str,re.IGNORECASE) == None: - return None; + cnamerecord = re.match("^[-\w]+\s+IN\s+CNAME\s+([-\w.]+\.)$",Str,re.IGNORECASE) + arecord = re.match('^[-\w]+\s+IN\s+A\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$',Str,re.IGNORECASE) + mxrecord = re.match("^[-\w]+\s+IN\s+MX\s+(\d{1,3})\s+([-\w.]+\.)$",Str,re.IGNORECASE) + #aaaarecord = re.match('^[-\w]+\s+IN\s+AAAA\s+((?:[0-9a-f]{1,4})(?::[0-9a-f]{1,4})*(?::(?:(?::[0-9a-f]{1,4})*|:))?)$',Str,re.IGNORECASE) + aaaarecord = re.match('^[-\w]+\s+IN\s+AAAA\s+([A-F0-9:]{2,39})$',Str,re.IGNORECASE) + + if cnamerecord == None and\ + arecord == None and\ + mxrecord == None and\ + aaaarecord == None: + return None; # Check if the name is already taken - G = re.match('^([-\w+]+)\s',Str).groups(); + G = re.match('^([-\w+]+)\s',Str) + if G == None: + raise Error, "Hostname not found although we already passed record syntax checks" + hostname = G.group(1) # Check for collisions global l; @@ -250,7 +301,7 @@ def DoDNS(Str,Attrs,DnRecord): # since we accept either. It'd probably be better to parse the # incoming string in order to construct what we feed LDAP rather # than just passing it through as is.] - filter = "(|(dnsZoneEntry=%s *)(dnsZoneEntry=%s *))" % (G[0], G[0]) + filter = "(|(dnsZoneEntry=%s *)(dnsZoneEntry=%s *))" % (hostname, hostname) Rec = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,filter,["uid"]); for x in Rec: if GetAttr(x,"uid") != GetAttr(DnRecord,"uid"): @@ -259,24 +310,59 @@ def DoDNS(Str,Attrs,DnRecord): global SeenDNS; global DNS; - if cname: - if DNS.has_key(G[0]): + if cnamerecord: + if DNS.has_key(hostname): return "CNAME and other RR types not allowed: "+Str else: - DNS[G[0]] = 2 + DNS[hostname] = 2 else: - if DNS.has_key(G[0]) and DNS[G[0]] == 2: + if DNS.has_key(hostname) and DNS[hostname] == 2: return "CNAME and other RR types not allowed: "+Str else: - DNS[G[0]] = 1 - + DNS[hostname] = 1 + + if cnamerecord != None: + sanitized = "%s IN CNAME %s" % (hostname, cnamerecord.group(1)) + elif arecord != None: + ipaddress = arecord.group(1) + for quad in ipaddress.split('.'): + if not (int(quad) >=0 and int(quad) <= 255): + return "Invalid quad %s in IP address %s in line %s" %(quad, ipaddress, Str) + sanitized = "%s IN A %s"% (hostname, ipaddress) + elif mxrecord != None: + priority = mxrecord.group(1) + mx = mxrecord.group(2) + sanitized = "%s IN MX %s %s" % (hostname, priority, mx) + elif aaaarecord != None: + ipv6address = aaaarecord.group(1) + parts = ipv6address.split(':') + if len(parts) > 8: + return "Invalid IPv6 address (%s): too many parts"%(ipv6address) + if len(parts) <= 2: + return "Invalid IPv6 address (%s): too few parts"%(ipv6address) + if parts[0] == "": + parts.pop(0) + if parts[-1] == "": + parts.pop(-1) + seenEmptypart = False + for p in parts: + if len(p) > 4: + return "Invalid IPv6 address (%s): part %s is longer than 4 characters"%(ipv6address, p) + if p == "": + if seenEmptypart: + return "Invalid IPv6 address (%s): more than one :: (nothing in between colons) is not allowed"%(ipv6address) + seenEmptypart = True + sanitized = "%s IN AAAA %s" % (hostname, ipv6address) + else: + raise Error, "None of the types I recognize was it. I shouldn't be here. confused." + if SeenDNS: - Attrs.append((ldap.MOD_ADD,"dnsZoneEntry",Str)); - return "DNS Entry added "+Str; - - Attrs.append((ldap.MOD_REPLACE,"dnsZoneEntry",Str)); + Attrs.append((ldap.MOD_ADD,"dnsZoneEntry",sanitized)); + return "DNS Entry added "+sanitized; + + Attrs.append((ldap.MOD_REPLACE,"dnsZoneEntry",sanitized)); SeenDNS = 1; - return "DNS Entry replaced with "+Str; + return "DNS Entry replaced with "+sanitized; # Handle an RBL list (mailRBL, mailRHSBL, mailWhitelist) def DoRBL(Str,Attrs): @@ -319,11 +405,12 @@ def HandleChange(Reply,DnRecord,Key): try: if Line == "show": Show = 1; - Res = "OK"; + Res = "OK"; else: - Res = DoPosition(Line,Attrs) or DoDNS(Line,Attrs,DnRecord) or \ - DoArbChange(Line,Attrs) or DoSSH(Line,Attrs) or \ - DoDel(Line,Attrs) or DoRBL(Line,Attrs); + badkeys = LoadBadSSH() + Res = DoPosition(Line,Attrs) or DoDNS(Line,Attrs,DnRecord) or \ + DoArbChange(Line,Attrs) or DoSSH(Line,Attrs,badkeys) or \ + DoDel(Line,Attrs) or DoRBL(Line,Attrs) except: Res = None; Result = Result + "==> %s: %s\n" %(sys.exc_type,sys.exc_value); diff --git a/ud-replicate b/ud-replicate index 0c17297..e0f8fe7 100755 --- a/ud-replicate +++ b/ud-replicate @@ -1,8 +1,9 @@ -#! /bin/sh +#! /bin/bash # Copyright (c) 1999-2001 Jason Gunthorpe # Copyright (c) 2002-2003,2006 Ryan Murray # Copyright (c) 2004-2005 Joey Schulze +# Copyright (c) 2008 Peter Palfrader # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,6 +25,7 @@ set -e if [ -z "$TERM" -o "$TERM" = "dumb" ] then exec > /dev/null 2>&1 + sleep $(( $RANDOM % 120 )) else verbose=-v fi diff --git a/ud-roleadd b/ud-roleadd index f6810f1..2117308 100755 --- a/ud-roleadd +++ b/ud-roleadd @@ -4,6 +4,7 @@ # Copyright (c) 1999-2000 Jason Gunthorpe # Copyright (c) 2001-2003 James Troup # Copyright (c) 2004-2005 Joey Schulze +# Copyright (c) 2007 Peter Palfrader # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/userdir-ldap.conf b/userdir-ldap.conf index 048e907..d3a37bb 100644 --- a/userdir-ldap.conf +++ b/userdir-ldap.conf @@ -24,6 +24,7 @@ changefrom = "change@" + maildomain; templatesdir = "/etc/userdir-ldap/templates/"; replaycachefile = "/var/cache/userdir-ldap/mail/replay"; #replaycachefile = "/tmp/replay"; +fingerprintfile = "/etc/userdir-ldap/badfingerprints" # Echelon ech_errorlog = "/org/db.debian.org/mail/Log/ech-errors.log" diff --git a/userdir-ldap.schema b/userdir-ldap.schema index d089f0b..d31d0e7 100644 --- a/userdir-ldap.schema +++ b/userdir-ldap.schema @@ -3,6 +3,7 @@ # XXX # - [PP] Now version controlled in db.d.o bzr repository - 2007-12-25 # - [HE] Add 'purpose', 'physicalHost' to debianServer - 2007-12-25 +# - [zobel] Add 'VoIP' - 2008-05-10 # # 0.7 [RM] # - Add 'gender' and 'birthDate' to debianDeveloper @@ -96,6 +97,7 @@ # .32 - mailDisableMessage # .33 - purpose # .34 - physicalHost +# .35 - VoIP # # .3 - experimental LDAP objectClasses # .1 - debianDeveloper @@ -350,6 +352,13 @@ attributetype ( 1.3.6.1.4.1.9586.100.4.2.34 SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) +attributetype ( 1.3.6.1.4.1.9586.100.4.2.35 + NAME 'VoIP' + DESC 'VoIP URL to communicate with that person' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) + # Public object classes objectclass ( 1.3.6.1.4.1.9586.100.4.1.1