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)
32 # This tries to search for a free UID. There are two possible ways to do
33 # this, one is to fetch all the entires and pick the highest, the other
34 # is to randomly guess uids until one is free. This uses the former.
35 # Regrettably ldap doesn't have an integer attribute comparision function
36 # so we can only cut the search down slightly
38 # [JT] This is broken with Woody LDAP and the Schema; for now just
39 # search through all UIDs.
41 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,
42 "uidNumber=*",["uidNumber", "gidNumber"]);
46 ID = int(GetAttr(I,"uidNumber","0"));
47 gids.append(int(GetAttr(I, "gidNumber","0")))
51 resGID = HighestUID + 1;
55 return (HighestUID + 1, resGID);
58 AdminUser = pwd.getpwuid(os.getuid())[0];
64 OldGPGKeyRings = GPGKeyRings;
65 userdir_gpg.GPGKeyRings = [];
66 (options, arguments) = getopt.getopt(sys.argv[1:], "hgu:man")
67 for (switch, val) in options:
69 print "Usage: ud-useradd <options>"
70 print "Available options:"
71 print " -h Show this help"
72 print " -u=<user> Admin user (defaults to current username)"
73 print " -m Force mail (for updates)"
74 print " -a Use old keyrings instead (??)"
75 print " -n Do not automatically assign UID/GIDs (useful for usergroups or non-default group membership"
76 print " -g Add a guest account"
78 elif (switch == '-u'):
80 elif (switch == '-m'):
82 elif (switch == '-a'):
83 userdir_gpg.GPGKeyRings = OldGPGKeyRings;
84 elif (switch == '-n'):
86 elif (switch == '-g'):
89 l = passwdAccessLDAP(BaseDn, AdminUser)
91 # Locate the key of the user we are adding
93 SetKeyrings(ConfModule.add_keyrings_guest.split(":"))
95 SetKeyrings(ConfModule.add_keyrings.split(":"))
98 Foo = raw_input("Who are you going to add (for a GPG search)? ");
102 Keys = GPGKeySearch(Foo);
105 print "Sorry, that search did not turn up any keys."
106 print "Has it been added to the Debian keyring already?"
109 print "Sorry, more than one key was found, please specify the key to use by\nfingerprint:";
115 print "A matching key was found:"
116 GPGPrintKeyInfo(Keys[0]);
119 # Crack up the email address from the key into a best guess
120 # first/middle/last name
121 Addr = SplitEmail(Keys[0][2]);
122 (cn,mn,sn) = NameSplit(re.sub('["]','',Addr[0]))
123 emailaddr = Addr[1] + '@' + Addr[2];
130 # Decide if we should use IDEA encryption
132 while len(Keys[0][1]) < 40:
133 Res = raw_input("Use PGP2.x compatibility [No/yes]? ");
141 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyFingerPrint=" + Keys[0][1]);
143 print "*** This key already belongs to",GetAttr(Attrs[0],"uid");
144 account = GetAttr(Attrs[0],"uid");
147 # Try to get a uniq account name
150 Res = raw_input("Login account [" + account + "]? ");
153 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=" + account);
155 privsub = "%s@debian.org"%(account);
157 Res = raw_input("That account already exists, update [No/yes]? ");
159 # Update mode, fetch the default values from the directory
161 privsub = GetAttr(Attrs[0],"privateSub");
162 gidNumber = GetAttr(Attrs[0],"gidNumber");
163 uidNumber = GetAttr(Attrs[0],"uidNumber");
164 emailaddr = GetAttr(Attrs[0],"emailForward");
165 cn = GetAttr(Attrs[0],"cn");
166 sn = GetAttr(Attrs[0],"sn");
167 mn = GetAttr(Attrs[0],"mn");
168 if privsub == None or privsub == "":
174 # Prompt for the first/last name and email address
175 Res = raw_input("First name [" + cn + "]? ");
178 Res = raw_input("Middle name [" + mn + "]? ");
183 Res = raw_input("Last name [" + sn + "]? ");
186 Res = raw_input("Email forwarding address [" + emailaddr + "]? ");
190 # Debian-Private subscription
191 if HavePrivateList and not GuestAccount:
192 Res = raw_input("Subscribe to debian-private (space is none) [" + privsub + "]? ");
200 gidNumber = DefaultGID
202 gidNumber = DebianGroups['guest']
204 (uidNumber, generatedGID) = GetFreeID(l)
209 Res = raw_input("User ID Number [%s]? " % (uidNumber));
214 Res = raw_input("Group ID Number (default group is %s, new usergroup %s) [%s]" % (DefaultGID, generatedGID, gidNumber));
217 gidNumber = int(Res);
219 gidNumber = Group2GID(l, Res);
221 if gidNumber == generatedGID:
227 res = raw_input("Expires in xx days [60] (0 to disable)")
228 if res == "": res = '60'
231 shadowExpire = int(time.time() / 3600 / 24) + exp
232 res = raw_input("Hosts to grant access to: ")
233 for h in res.split():
234 if not '.' in h: h = h + '.' + HostDomain
235 if exp > 0: h = h + " " + datetime.datetime.fromtimestamp( time.time() + exp * 24*3600 ).strftime("%Y%m%d")
239 # Generate a random password
240 if Update == 0 or ForceMail == 1:
241 Password = raw_input("User's Password (Enter for random)? ");
244 print "Randomizing and encrypting password"
245 Password = GenPass();
246 Pass = HashPass(Password);
248 # Use GPG to encrypt it, pass the fingerprint to ID it
249 CryptedPass = GPGEncrypt("Your new password is '" + Password + "'\n",\
250 "0x"+Keys[0][1],UsePGP2);
252 if CryptedPass == None:
253 raise "Error","Password Encryption failed"
255 Pass = HashPass(Password);
256 CryptedPass = "Your password has been set to the previously agreed value.";
261 # Now we have all the bits of information.
263 FullName = "%s %s %s" % (cn,mn,sn);
265 FullName = "%s %s" % (cn,sn);
266 print "------------";
267 print "Final information collected:"
268 print " %s <%s@%s>:" % (FullName,account,EmailAppend);
269 print " Assigned UID:",uidNumber," GID:", gidNumber;
270 print " Email forwarded to:",emailaddr
272 print " Private Subscription:",privsub;
273 print " GECOS Field: \"%s,,,,\"" % (FullName);
274 print " Login Shell: /bin/bash";
275 print " Key Fingerprint:",Keys[0][1];
277 print " ShadowExpire: %d (%s)"%(shadowExpire, datetime.datetime.fromtimestamp( shadowExpire * 24*3600 ).strftime("%Y%m%d") )
279 print " allowedHost: ", h
281 Res = raw_input("Continue [No/yes]? ");
285 # Initialize the substitution Map
290 encrealname = FullName.decode('us-ascii')
292 encrealname = str(email.Header.Header(FullName, 'utf-8', 200))
294 Subst["__ENCODED_REALNAME__"] = encrealname
295 Subst["__REALNAME__"] = FullName;
296 Subst["__WHOAMI__"] = pwd.getpwuid(os.getuid())[0];
297 Subst["__DATE__"] = time.strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime(time.time()));
298 Subst["__LOGIN__"] = account;
299 Subst["__PRIVATE__"] = privsub;
300 Subst["__EMAIL__"] = emailaddr
301 Subst["__PASSWORD__"] = CryptedPass;
303 # Submit the modification request
304 Dn = "uid=" + account + "," + BaseDn;
305 print "Updating LDAP directory..",
310 Details = [("uid",account),
311 ("objectClass", UserObjectClasses),
312 ("uidNumber",str(uidNumber)),
313 ("gidNumber",str(gidNumber)),
314 ("gecos",FullName+",,,,"),
315 ("loginShell","/bin/bash"),
316 ("keyFingerPrint",Keys[0][1]),
319 ("emailForward",emailaddr),
320 ("shadowLastChange",str(int(time.time()/24/60/60))),
322 ("shadowMax","99999"),
323 ("shadowWarning","7"),
324 ("userPassword","{crypt}"+Pass)];
326 Details.append(("mn",mn));
328 Details.append(("privateSub",privsub))
330 Details.append(("shadowExpire",str(shadowExpire)))
332 Details.append(("allowedHost",hostacl))
336 #Add user group if needed, then the actual user:
338 Dn = "gid=" + account + "," + BaseDn;
339 l.add_s(Dn,[("gid",account), ("gidNumber",str(gidNumber)), ("objectClass", GroupObjectClasses)])
342 Rec = [(ldap.MOD_REPLACE,"uidNumber",str(uidNumber)),
343 (ldap.MOD_REPLACE,"gidNumber",str(gidNumber)),
344 (ldap.MOD_REPLACE,"gecos",FullName+",,,,"),
345 (ldap.MOD_REPLACE,"loginShell","/bin/bash"),
346 (ldap.MOD_REPLACE,"keyFingerPrint",Keys[0][1]),
347 (ldap.MOD_REPLACE,"cn",cn),
348 (ldap.MOD_REPLACE,"mn",mn),
349 (ldap.MOD_REPLACE,"sn",sn),
350 (ldap.MOD_REPLACE,"emailForward",emailaddr),
351 (ldap.MOD_REPLACE,"shadowLastChange",str(int(time.time()/24/60/60))),
352 (ldap.MOD_REPLACE,"shadowMin","0"),
353 (ldap.MOD_REPLACE,"shadowMax","99999"),
354 (ldap.MOD_REPLACE,"shadowWarning","7"),
355 (ldap.MOD_REPLACE,"shadowInactive",""),
356 (ldap.MOD_REPLACE,"shadowExpire","")];
358 Rec.append((ldap.MOD_REPLACE,"privateSub",privsub));
360 Rec.append((ldap.MOD_REPLACE,"userPassword","{crypt}"+Pass));
366 # Abort email sends for an update operation
367 if Update == 1 and ForceMail == 0:
368 print "Account is not new, Not sending mails"
371 # Send the Welcome message
372 print "Sending Welcome Email"
373 templatepath = TemplatesDir + "/welcome-message-%d" % int(gidNumber)
374 if not os.path.exists(templatepath):
375 templatepath = TemplatesDir + "/welcome-message"
376 Reply = TemplateSubst(Subst,open(templatepath, "r").read())
377 Child = os.popen("/usr/sbin/sendmail -t","w");
378 #Child = os.popen("cat","w");
380 if Child.close() != None:
381 raise Error, "Sendmail gave a non-zero return code";
385 # vim:set shiftwidth=3: