4 # Copyright (c) 1999-2000 Jason Gunthorpe <jgg@debian.org>
5 # Copyright (c) 2001-2003 James Troup <troup@debian.org>
6 # Copyright (c) 2004 Joey Schulze <joey@infodrom.org>
7 # Copyright (c) 2008,2009,2010 Peter Palfrader <peter@palfrader.org>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 import re, time, ldap, getopt, sys, os, pwd;
27 from userdir_ldap import *;
28 from userdir_gpg import *;
30 HavePrivateList = getattr(ConfModule, "haveprivatelist", True)
31 DefaultGroup = getattr(ConfModule, "defaultgroup", 'users')
33 # This tries to search for a free UID. There are two possible ways to do
34 # this, one is to fetch all the entires and pick the highest, the other
35 # is to randomly guess uids until one is free. This uses the former.
36 # Regrettably ldap doesn't have an integer attribute comparision function
37 # so we can only cut the search down slightly
39 def ShouldIgnoreID(uid):
40 for i in IgnoreUsersForUIDNumberGen:
42 if i.search(uid) is not None:
44 except AttributeError:
50 # [JT] This is broken with Woody LDAP and the Schema; for now just
51 # search through all UIDs.
53 Attrs = l.search_s(BaseBaseDn,ldap.SCOPE_SUBTREE,
54 "uidNumber=*",["uidNumber", "gidNumber", "uid"]);
59 ID = int(GetAttr(I,"uidNumber","0"));
61 gids.append(int(GetAttr(I, "gidNumber","0")))
62 uid = GetAttr(I, "uid", None)
63 if ID > HighestUID and not uid is None and not ShouldIgnoreID(uid):
66 resUID = HighestUID + 1;
67 while resUID in uids or resUID in gids:
70 return (resUID, resUID)
73 AdminUser = pwd.getpwuid(os.getuid())[0];
79 OldGPGKeyRings = GPGKeyRings;
80 userdir_gpg.GPGKeyRings = [];
81 (options, arguments) = getopt.getopt(sys.argv[1:], "hgu:man")
82 for (switch, val) in options:
84 print "Usage: ud-useradd <options>"
85 print "Available options:"
86 print " -h Show this help"
87 print " -u=<user> Admin user (defaults to current username)"
88 print " -m Force mail (for updates)"
89 print " -a Use old keyrings instead (??)"
90 print " -n Do not automatically assign UID/GIDs"
91 print " -g Add a guest account"
93 elif (switch == '-u'):
95 elif (switch == '-m'):
97 elif (switch == '-a'):
98 userdir_gpg.GPGKeyRings = OldGPGKeyRings;
99 elif (switch == '-n'):
101 elif (switch == '-g'):
104 l = passwdAccessLDAP(BaseDn, AdminUser)
106 # Locate the key of the user we are adding
108 SetKeyrings(ConfModule.add_keyrings_guest.split(":"))
110 SetKeyrings(ConfModule.add_keyrings.split(":"))
113 Foo = raw_input("Who are you going to add (for a GPG search)? ");
117 Keys = GPGKeySearch(Foo);
120 print "Sorry, that search did not turn up any keys."
121 print "Has it been added to the Debian keyring already?"
124 print "Sorry, more than one key was found, please specify the key to use by\nfingerprint:";
130 print "A matching key was found:"
131 GPGPrintKeyInfo(Keys[0]);
134 # Crack up the email address from the key into a best guess
135 # first/middle/last name
136 Addr = SplitEmail(Keys[0][2]);
137 (cn,mn,sn) = NameSplit(re.sub('["]','',Addr[0]))
138 emailaddr = Addr[1] + '@' + Addr[2];
145 # Decide if we should use IDEA encryption
147 while len(Keys[0][1]) < 40:
148 Res = raw_input("Use PGP2.x compatibility [No/yes]? ");
156 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyFingerPrint=" + Keys[0][1]);
158 print "*** This key already belongs to",GetAttr(Attrs[0],"uid");
159 account = GetAttr(Attrs[0],"uid");
162 # Try to get a uniq account name
165 Res = raw_input("Login account [" + account + "]? ");
168 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=" + account);
170 privsub = "%s@debian.org"%(account);
172 Res = raw_input("That account already exists, update [No/yes]? ");
174 # Update mode, fetch the default values from the directory
176 privsub = GetAttr(Attrs[0],"privateSub");
177 gidNumber = GetAttr(Attrs[0],"gidNumber");
178 uidNumber = GetAttr(Attrs[0],"uidNumber");
179 emailaddr = GetAttr(Attrs[0],"emailForward");
180 cn = GetAttr(Attrs[0],"cn");
181 sn = GetAttr(Attrs[0],"sn");
182 mn = GetAttr(Attrs[0],"mn");
183 if privsub == None or privsub == "":
189 # Prompt for the first/last name and email address
190 Res = raw_input("First name [" + cn + "]? ");
193 Res = raw_input("Middle name [" + mn + "]? ");
198 Res = raw_input("Last name [" + sn + "]? ");
201 Res = raw_input("Email forwarding address [" + emailaddr + "]? ");
205 # Debian-Private subscription
206 if HavePrivateList and not GuestAccount:
207 Res = raw_input("Subscribe to debian-private (space is none) [" + privsub + "]? ");
214 (uidNumber, generatedGID) = GetFreeID(l)
216 gidNumber = generatedGID
222 Res = raw_input("User ID Number [%s]? " % (uidNumber));
227 Res = raw_input("Group ID Number (new usergroup is %s) [%s]" % (generatedGID, gidNumber));
230 gidNumber = int(Res);
232 gidNumber = Group2GID(l, Res);
234 if gidNumber != generatedGID:
238 supplementaryGid = 'guest'
240 supplementaryGid = DefaultGroup
245 res = raw_input("Expires in xx days [60] (0 to disable)")
246 if res == "": res = '60'
249 shadowExpire = int(time.time() / 3600 / 24) + exp
250 res = raw_input("Hosts to grant access to: ")
251 for h in res.split():
252 if not '.' in h: h = h + '.' + HostDomain
253 if exp > 0: h = h + " " + datetime.datetime.fromtimestamp( time.time() + exp * 24*3600 ).strftime("%Y%m%d")
257 # Generate a random password
258 if Update == 0 or ForceMail == 1:
259 Password = raw_input("User's Password (Enter for random)? ");
262 print "Randomizing and encrypting password"
263 Password = GenPass();
264 Pass = HashPass(Password);
266 # Use GPG to encrypt it, pass the fingerprint to ID it
267 CryptedPass = GPGEncrypt("Your new password is '" + Password + "'\n",\
268 "0x"+Keys[0][1],UsePGP2);
270 if CryptedPass == None:
271 raise "Error","Password Encryption failed"
273 Pass = HashPass(Password);
274 CryptedPass = "Your password has been set to the previously agreed value.";
279 # Now we have all the bits of information.
281 FullName = "%s %s %s" % (cn,mn,sn);
283 FullName = "%s %s" % (cn,sn);
284 print "------------";
285 print "Final information collected:"
286 print " %s <%s@%s>:" % (FullName,account,EmailAppend);
287 print " Assigned UID:",uidNumber," GID:", gidNumber;
288 print " supplementary group:",supplementaryGid
289 print " Email forwarded to:",emailaddr
291 print " Private Subscription:",privsub;
292 print " GECOS Field: \"%s,,,,\"" % (FullName);
293 print " Login Shell: /bin/bash";
294 print " Key Fingerprint:",Keys[0][1];
296 print " ShadowExpire: %d (%s)"%(shadowExpire, datetime.datetime.fromtimestamp( shadowExpire * 24*3600 ).strftime("%Y%m%d") )
298 print " allowedHost: ", h
300 Res = raw_input("Continue [No/yes]? ");
304 # Initialize the substitution Map
309 encrealname = FullName.decode('us-ascii')
311 encrealname = str(email.Header.Header(FullName, 'utf-8', 200))
313 Subst["__ENCODED_REALNAME__"] = encrealname
314 Subst["__REALNAME__"] = FullName;
315 Subst["__WHOAMI__"] = pwd.getpwuid(os.getuid())[0];
316 Subst["__DATE__"] = time.strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime(time.time()));
317 Subst["__LOGIN__"] = account;
318 Subst["__PRIVATE__"] = privsub;
319 Subst["__EMAIL__"] = emailaddr
320 Subst["__PASSWORD__"] = CryptedPass;
322 # Submit the modification request
323 Dn = "uid=" + account + "," + BaseDn;
324 print "Updating LDAP directory..",
329 Details = [("uid",account),
330 ("objectClass", UserObjectClasses),
331 ("uidNumber",str(uidNumber)),
332 ("gidNumber",str(gidNumber)),
333 ("supplementaryGid",supplementaryGid),
334 ("gecos",FullName+",,,,"),
335 ("loginShell","/bin/bash"),
336 ("keyFingerPrint",Keys[0][1]),
339 ("emailForward",emailaddr),
340 ("shadowLastChange",str(int(time.time()/24/60/60))),
342 ("shadowMax","99999"),
343 ("shadowWarning","7"),
344 ("userPassword","{crypt}"+Pass)];
346 Details.append(("mn",mn));
348 Details.append(("privateSub",privsub))
350 Details.append(("shadowExpire",str(shadowExpire)))
352 Details.append(("allowedHost",hostacl))
356 #Add user group if needed, then the actual user:
358 Dn = "gid=" + account + "," + BaseDn;
359 l.add_s(Dn,[("gid",account), ("gidNumber",str(gidNumber)), ("objectClass", GroupObjectClasses)])
362 Rec = [(ldap.MOD_REPLACE,"uidNumber",str(uidNumber)),
363 (ldap.MOD_REPLACE,"gidNumber",str(gidNumber)),
364 (ldap.MOD_REPLACE,"gecos",FullName+",,,,"),
365 (ldap.MOD_REPLACE,"loginShell","/bin/bash"),
366 (ldap.MOD_REPLACE,"keyFingerPrint",Keys[0][1]),
367 (ldap.MOD_REPLACE,"cn",cn),
368 (ldap.MOD_REPLACE,"mn",mn),
369 (ldap.MOD_REPLACE,"sn",sn),
370 (ldap.MOD_REPLACE,"emailForward",emailaddr),
371 (ldap.MOD_REPLACE,"shadowLastChange",str(int(time.time()/24/60/60))),
372 (ldap.MOD_REPLACE,"shadowMin","0"),
373 (ldap.MOD_REPLACE,"shadowMax","99999"),
374 (ldap.MOD_REPLACE,"shadowWarning","7"),
375 (ldap.MOD_REPLACE,"shadowInactive",""),
376 (ldap.MOD_REPLACE,"shadowExpire","")];
378 Rec.append((ldap.MOD_REPLACE,"privateSub",privsub));
380 Rec.append((ldap.MOD_REPLACE,"userPassword","{crypt}"+Pass));
386 # Abort email sends for an update operation
387 if Update == 1 and ForceMail == 0:
388 print "Account is not new, Not sending mails"
391 # Send the Welcome message
392 print "Sending Welcome Email"
393 templatepath = TemplatesDir + "/welcome-message-%s" % supplementaryGid
394 if not os.path.exists(templatepath):
395 templatepath = TemplatesDir + "/welcome-message"
396 Reply = TemplateSubst(Subst,open(templatepath, "r").read())
397 Child = os.popen("/usr/sbin/sendmail -t","w");
398 #Child = os.popen("cat","w");
400 if Child.close() != None:
401 raise Error, "Sendmail gave a non-zero return code";
405 # vim:set shiftwidth=3: