Add a -h for ud-useradd
[mirror/userdir-ldap.git] / ud-useradd
1 #!/usr/bin/env python
2 # -*- mode: python -*-
3
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>
8 #
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.
13 #
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.
18 #
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.
22
23 import re, time, ldap, getopt, sys, os, pwd;
24 import email.Header
25
26 from userdir_ldap import *;
27 from userdir_gpg import *;
28
29 HavePrivateList = getattr(ConfModule, "haveprivatelist", True)
30
31 # This tries to search for a free UID. There are two possible ways to do
32 # this, one is to fetch all the entires and pick the highest, the other
33 # is to randomly guess uids until one is free. This uses the former.
34 # Regrettably ldap doesn't have an integer attribute comparision function
35 # so we can only cut the search down slightly
36
37 # [JT] This is broken with Woody LDAP and the Schema; for now just
38 #      search through all UIDs.
39 def GetFreeID(l):
40    Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,
41                       "uidNumber=*",["uidNumber", "gidNumber"]);
42    HighestUID = 0;
43    gids = [];
44    for I in Attrs:
45       ID = int(GetAttr(I,"uidNumber","0"));
46       gids.append(int(GetAttr(I, "gidNumber","0")))
47       if ID > HighestUID:
48          HighestUID = ID;
49
50    resGID = HighestUID + 1;
51    while resGID in gids:
52       resGID += 1
53
54    return (HighestUID + 1, resGID);
55
56 # Main starts here
57 AdminUser = pwd.getpwuid(os.getuid())[0];
58
59 # Process options
60 ForceMail = 0;
61 NoAutomaticIDs = 0;
62 OldGPGKeyRings = GPGKeyRings;
63 userdir_gpg.GPGKeyRings = [];
64 (options, arguments) = getopt.getopt(sys.argv[1:], "hu:man")
65 for (switch, val) in options:
66    if (switch == '-h'):
67       print "Usage: ud-useradd <options>"
68       print "Available options:"
69       print "        -h         Show this help"
70       print "        -u=<user>  Admin user (defaults to current username)"
71       print "        -m         Force mail (for updates)"
72       print "        -a         Use old keyrings instead (??)"
73       print "        -n         Do not automatically assign UID/GIDs (useful for usergroups or non-default group membership"
74       sys.exit(0)
75    elif (switch == '-u'):
76       AdminUser = val;
77    elif (switch == '-m'):
78       ForceMail = 1;
79    elif (switch == '-a'):
80       userdir_gpg.GPGKeyRings = OldGPGKeyRings;
81    elif (switch == '-n'):
82       NoAutomaticIDs = 1;
83
84 l = passwdAccessLDAP(BaseDn, AdminUser)
85
86 # Locate the key of the user we are adding
87 SetKeyrings(ConfModule.add_keyrings.split(":"))
88 while (1):
89    Foo = raw_input("Who are you going to add (for a GPG search)? ");
90    if Foo == "":
91       sys.exit(0);
92
93    Keys = GPGKeySearch(Foo);
94
95    if len(Keys) == 0:
96       print "Sorry, that search did not turn up any keys."
97       print "Has it been added to the Debian keyring already?"
98       continue;
99    if len(Keys) > 1:
100       print "Sorry, more than one key was found, please specify the key to use by\nfingerprint:";
101       for i in Keys:
102          GPGPrintKeyInfo(i);
103       continue;
104
105    print
106    print "A matching key was found:"
107    GPGPrintKeyInfo(Keys[0]);
108    break;
109
110 # Crack up the email address from the key into a best guess
111 # first/middle/last name
112 Addr = SplitEmail(Keys[0][2]);
113 (cn,mn,sn) = NameSplit(re.sub('["]','',Addr[0]))
114 emailaddr = Addr[1] + '@' + Addr[2];
115 account = Addr[1];
116
117 privsub = emailaddr
118 gidNumber = 0;
119 uidNumber = 0;
120
121 # Decide if we should use IDEA encryption
122 UsePGP2 = 0;
123 while len(Keys[0][1]) < 40:
124    Res = raw_input("Use PGP2.x compatibility [No/yes]? ");
125    if Res == "yes":
126       UsePGP2 = 1;
127       break;
128    if Res == "":
129       break;
130
131 Update = 0
132 Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"keyFingerPrint=" + Keys[0][1]);
133 if len(Attrs) != 0:
134    print "*** This key already belongs to",GetAttr(Attrs[0],"uid");
135    account = GetAttr(Attrs[0],"uid");
136    Update = 1
137
138 # Try to get a uniq account name
139 while 1:
140    if Update == 0:
141       Res = raw_input("Login account [" + account + "]? ");
142       if Res != "":
143          account = Res;
144    Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=" + account);
145    if len(Attrs) == 0:
146       privsub = "%s@debian.org"%(account);
147       break;
148    Res = raw_input("That account already exists, update [No/yes]? ");
149    if Res == "yes":
150       # Update mode, fetch the default values from the directory
151       Update = 1;
152       privsub = GetAttr(Attrs[0],"privateSub");
153       gidNumber = GetAttr(Attrs[0],"gidNumber");
154       uidNumber = GetAttr(Attrs[0],"uidNumber");
155       emailaddr = GetAttr(Attrs[0],"emailForward");
156       cn = GetAttr(Attrs[0],"cn");
157       sn = GetAttr(Attrs[0],"sn");
158       mn = GetAttr(Attrs[0],"mn");
159       if privsub == None or privsub == "":
160          privsub = " ";
161       break;
162    else:
163       sys.exit(1)
164
165 # Prompt for the first/last name and email address
166 Res = raw_input("First name [" + cn + "]? ");
167 if Res != "":
168    cn = Res;
169 Res = raw_input("Middle name [" + mn + "]? ");
170 if Res == " ":
171    mn = ""
172 elif Res != "":
173    mn = Res;
174 Res = raw_input("Last name [" + sn + "]? ");
175 if Res != "":
176    sn = Res;
177 Res = raw_input("Email forwarding address [" + emailaddr + "]? ");
178 if Res != "":
179    emailaddr = Res;
180
181 # Debian-Private subscription
182 if HavePrivateList:
183    Res = raw_input("Subscribe to debian-private (space is none) [" + privsub + "]? ");
184    if Res != "":
185       privsub = Res;
186 else:
187    privsub = " "
188
189 (uidNumber, generatedGID) = GetFreeID(l)
190 if not gidNumber:
191    gidNumber = DefaultGID
192 UserGroup = 0
193
194 if NoAutomaticIDs:
195    # UID
196    if not Update:
197       Res = raw_input("User ID Number [%s]? " % (uidNumber));
198       if Res != "":
199          uidNumber = Res;
200    
201    # GID
202    Res = raw_input("Group ID Number (default group is %s, new usergroup %s) [%s]" % (DefaultGID, generatedGID, gidNumber));
203    if Res != "":
204       if Res.isdigit():
205          gidNumber = int(Res);
206       else:
207          gidNumber = Group2GID(l, Res);
208    
209    if gidNumber == generatedGID:
210       UserGroup = 1
211
212 # Generate a random password
213 if Update == 0 or ForceMail == 1:
214    Password = raw_input("User's Password (Enter for random)? ");
215
216    if Password == "":
217       print "Randomizing and encrypting password"
218       Password = GenPass();
219       Pass = HashPass(Password);
220
221       # Use GPG to encrypt it, pass the fingerprint to ID it
222       CryptedPass = GPGEncrypt("Your new password is '" + Password + "'\n",\
223                                "0x"+Keys[0][1],UsePGP2);
224       Password = None;
225       if CryptedPass == None:
226         raise "Error","Password Encryption failed"
227    else:
228       Pass = HashPass(Password);
229       CryptedPass = "Your password has been set to the previously agreed value.";
230 else:
231    CryptedPass = "";
232    Pass = None;
233
234 # Now we have all the bits of information.
235 if mn != "":
236    FullName = "%s %s %s" % (cn,mn,sn);
237 else:
238    FullName = "%s %s" % (cn,sn);
239 print "------------";
240 print "Final information collected:"
241 print " %s <%s@%s>:" % (FullName,account,EmailAppend);
242 print "   Assigned UID:",uidNumber," GID:", gidNumber;
243 print "   Email forwarded to:",emailaddr
244 if HavePrivateList:
245    print "   Private Subscription:",privsub;
246 print "   GECOS Field: \"%s,,,,\"" % (FullName);
247 print "   Login Shell: /bin/bash";
248 print "   Key Fingerprint:",Keys[0][1];
249 Res = raw_input("Continue [No/yes]? ");
250 if Res != "yes":
251    sys.exit(1);
252
253 # Initialize the substitution Map
254 Subst = {}
255
256 encrealname = ''
257 try:
258   encrealname = FullName.decode('us-ascii')
259 except UnicodeError:
260   encrealname = str(email.Header.Header(FullName, 'utf-8', 200))
261
262 Subst["__ENCODED_REALNAME__"] = encrealname
263 Subst["__REALNAME__"] = FullName;
264 Subst["__WHOAMI__"] = pwd.getpwuid(os.getuid())[0];
265 Subst["__DATE__"] = time.strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime(time.time()));
266 Subst["__LOGIN__"] = account;
267 Subst["__PRIVATE__"] = privsub;
268 Subst["__EMAIL__"] = emailaddr
269 Subst["__PASSWORD__"] = CryptedPass;
270
271 # Submit the modification request
272 Dn = "uid=" + account + "," + BaseDn;
273 print "Updating LDAP directory..",
274 sys.stdout.flush();
275
276 if Update == 0:
277    # New account
278    Details = [("uid",account),
279               ("objectClass", UserObjectClasses),
280               ("uidNumber",str(uidNumber)),
281               ("gidNumber",str(gidNumber)),
282               ("gecos",FullName+",,,,"),
283               ("loginShell","/bin/bash"),
284               ("keyFingerPrint",Keys[0][1]),
285               ("cn",cn),
286               ("sn",sn),
287               ("emailForward",emailaddr),
288               ("shadowLastChange",str(int(time.time()/24/60/60))),
289               ("shadowMin","0"),
290               ("shadowMax","99999"),
291               ("shadowWarning","7"),
292               ("userPassword","{crypt}"+Pass)];
293    if mn:
294       Details.append(("mn",mn));
295    if privsub != " ":
296       Details.append(("privateSub",privsub))
297    l.add_s(Dn,Details);
298
299    #Add user group if needed, then the actual user:
300    if UserGroup == 1:
301       Dn = "gid=" + account + "," + BaseDn;
302       l.add_s(Dn,[("gid",account), ("gidNumber",str(gidNumber)), ("objectClass", GroupObjectClasses)])
303 else:
304    # Modification
305    Rec = [(ldap.MOD_REPLACE,"uidNumber",str(uidNumber)),
306           (ldap.MOD_REPLACE,"gidNumber",str(gidNumber)),
307           (ldap.MOD_REPLACE,"gecos",FullName+",,,,"),
308           (ldap.MOD_REPLACE,"loginShell","/bin/bash"),
309           (ldap.MOD_REPLACE,"keyFingerPrint",Keys[0][1]),
310           (ldap.MOD_REPLACE,"cn",cn),
311           (ldap.MOD_REPLACE,"mn",mn),
312           (ldap.MOD_REPLACE,"sn",sn),
313           (ldap.MOD_REPLACE,"emailForward",emailaddr),
314           (ldap.MOD_REPLACE,"shadowLastChange",str(int(time.time()/24/60/60))),
315           (ldap.MOD_REPLACE,"shadowMin","0"),
316           (ldap.MOD_REPLACE,"shadowMax","99999"),
317           (ldap.MOD_REPLACE,"shadowWarning","7"),
318           (ldap.MOD_REPLACE,"shadowInactive",""),
319           (ldap.MOD_REPLACE,"shadowExpire","")];
320    if privsub != " ":
321       Rec.append((ldap.MOD_REPLACE,"privateSub",privsub));
322    if Pass != None:
323       Rec.append((ldap.MOD_REPLACE,"userPassword","{crypt}"+Pass));
324    # Do it
325    l.modify_s(Dn,Rec);
326
327 print;
328
329 # Abort email sends for an update operation
330 if Update == 1 and ForceMail == 0:
331    print "Account is not new, Not sending mails"
332    sys.exit(0);
333
334 # Send the Welcome message
335 print "Sending Welcome Email"
336 templatepath = TemplatesDir + "/welcome-message-%d" % int(gidNumber)
337 if not os.path.exists(templatepath):
338    templatepath = TemplatesDir + "/welcome-message"
339 Reply = TemplateSubst(Subst,open(templatepath, "r").read())
340 Child = os.popen("/usr/sbin/sendmail -t","w");
341 #Child = os.popen("cat","w");
342 Child.write(Reply);
343 if Child.close() != None:
344    raise Error, "Sendmail gave a non-zero return code";
345
346 # vim:set et:
347 # vim:set ts=3:
348 # vim:set shiftwidth=3: