s4-samba-tool: add password verification in change user pass
[obnox/samba/samba-obnox.git] / source4 / scripting / python / samba / netcmd / user.py
1 # user management
2 #
3 # Copyright Jelmer Vernooij 2010 <jelmer@samba.org>
4 # Copyright Theresa Halloran 2011 <theresahalloran@gmail.com>
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import samba.getopt as options
21 import ldb
22 from getpass import getpass
23 from samba.auth import system_session
24 from samba.samdb import SamDB
25 from samba import (
26     dsdb,
27     gensec,
28     generate_random_password,
29     )
30 from samba.net import Net
31
32 from samba.netcmd import (
33     Command,
34     CommandError,
35     SuperCommand,
36     Option,
37     )
38
39
40 class cmd_user_create(Command):
41     """Creates a new user
42
43 This command creates a new user account in the Active Directory domain.  The username specified on the command is the sAMaccountName.
44
45 User accounts may represent physical entities, such as people or may be used as service accounts for applications.  User accounts are also referred to as security principals and are assigned a security identifier (SID).
46
47 A user account enables a user to logon to a computer and domain with an identity that can be authenticated.  To maximize security, each user should have their own unique user account and password.  A user's access to domain resources is based on permissions assigned to the user account.
48
49 The command may be run from the root userid or another authorized userid.  The -H or --URL= option can be used to execute the command against a remote server.
50
51 Example1:
52 samba-tool user add User1 passw0rd --given-name=John --surname=Smith --must-change-at-next-login -H ldap://samba.samdom.example.com -Uadministrator%passw1rd
53
54 Example1 shows how to create a new user in the domain against a remote LDAP server.  The -H parameter is used to specify the remote target server.  The -U option is used to pass the userid and password authorized to issue the command remotely.
55
56 Example2:
57 sudo samba-tool user add User2 passw2rd --given-name=Jane --surname=Doe --must-change-at-next-login
58
59 Example2 shows how to create a new user in the domain against the local server.   sudo is used so a user may run the command as root.  In this example, after User2 is created, he/she will be forced to change their password when they logon.
60
61 Example3:
62 samba-tool user add User3 passw3rd --userou=OrgUnit
63
64 Example3 shows how to create a new user in the OrgUnit organizational unit.
65
66 """
67     synopsis = "%prog <username> [<password>] [options]"
68
69     takes_options = [
70         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
71                 metavar="URL", dest="H"),
72         Option("--must-change-at-next-login",
73                 help="Force password to be changed on next login",
74                 action="store_true"),
75         Option("--random-password",
76                 help="Generate random password",
77                 action="store_true"),
78         Option("--use-username-as-cn",
79                 help="Force use of username as user's CN",
80                 action="store_true"),
81         Option("--userou",
82                 help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
83                 type=str),
84         Option("--surname", help="User's surname", type=str),
85         Option("--given-name", help="User's given name", type=str),
86         Option("--initials", help="User's initials", type=str),
87         Option("--profile-path", help="User's profile path", type=str),
88         Option("--script-path", help="User's logon script path", type=str),
89         Option("--home-drive", help="User's home drive letter", type=str),
90         Option("--home-directory", help="User's home directory path", type=str),
91         Option("--job-title", help="User's job title", type=str),
92         Option("--department", help="User's department", type=str),
93         Option("--company", help="User's company", type=str),
94         Option("--description", help="User's description", type=str),
95         Option("--mail-address", help="User's email address", type=str),
96         Option("--internet-address", help="User's home page", type=str),
97         Option("--telephone-number", help="User's phone number", type=str),
98         Option("--physical-delivery-office", help="User's office location", type=str),
99     ]
100
101     takes_args = ["username", "password?"]
102
103     takes_optiongroups = {
104         "sambaopts": options.SambaOptions,
105         "credopts": options.CredentialsOptions,
106         "versionopts": options.VersionOptions,
107         }
108
109     def run(self, username, password=None, credopts=None, sambaopts=None,
110             versionopts=None, H=None, must_change_at_next_login=False, random_password=False,
111             use_username_as_cn=False, userou=None, surname=None, given_name=None, initials=None,
112             profile_path=None, script_path=None, home_drive=None, home_directory=None,
113             job_title=None, department=None, company=None, description=None,
114             mail_address=None, internet_address=None, telephone_number=None, physical_delivery_office=None):
115
116         if random_password:
117             password = generate_random_password(128, 255)
118
119         while True:
120             if password is not None and password is not '':
121                 break
122             password = getpass("New Password: ")
123             passwordverify = getpass("Retype Password: ")
124             if not password == passwordverify:
125                 password = None
126                 self.outf.write("Sorry, passwords do not match.\n")
127
128         lp = sambaopts.get_loadparm()
129         creds = credopts.get_credentials(lp)
130
131         try:
132             samdb = SamDB(url=H, session_info=system_session(),
133                           credentials=creds, lp=lp)
134             samdb.newuser(username, password,
135                           force_password_change_at_next_login_req=must_change_at_next_login,
136                           useusernameascn=use_username_as_cn, userou=userou, surname=surname, givenname=given_name, initials=initials,
137                           profilepath=profile_path, homedrive=home_drive, scriptpath=script_path, homedirectory=home_directory,
138                           jobtitle=job_title, department=department, company=company, description=description,
139                           mailaddress=mail_address, internetaddress=internet_address,
140                           telephonenumber=telephone_number, physicaldeliveryoffice=physical_delivery_office)
141         except Exception, e:
142             raise CommandError("Failed to add user '%s': " % username, e)
143
144         self.outf.write("User '%s' created successfully\n" % username)
145
146
147 class cmd_user_add(cmd_user_create):
148     __doc__ = cmd_user_create.__doc__
149     # take this print out after the add subcommand is removed.
150     # the add subcommand is deprecated but left in for now to allow people to migrate to create
151
152     def run(self, *args, **kwargs):
153         self.err.write("\nNote: samba-tool user add is deprecated.  Please use samba-tool user create for the same function.\n")
154         return super(self, cmd_user_add).run(*args, **kwargs)
155
156
157 class cmd_user_delete(Command):
158     """Deletes a user
159
160 This command deletes a user account from the Active Directory domain.  The username specified on the command is the sAMAccountName.
161
162 Once the account is deleted, all permissions and memberships associated with that account are deleted.  If a new user account is added with the same name as a previously deleted account name, the new user does not have the previous permissions.  The new account user will be assigned a new security identifier (SID) and permissions and memberships will have to be added.
163
164 The command may be run from the root userid or another authorized userid.  The -H or --URL= option can be used to execute the command against a remote server.
165
166 Example1:
167 samba-tool user delete User1 -H ldap://samba.samdom.example.com --username=administrator --password=passw1rd
168
169 Example1 shows how to delete a user in the domain against a remote LDAP server.  The -H parameter is used to specify the remote target server.  The --username= and --password= options are used to pass the username and password of a user that exists on the remote server and is authorized to issue the command on that server.
170
171 Example2:
172 sudo samba-tool user delete User2
173
174 Example2 shows how to delete a user in the domain against the local server.   sudo is used so a user may run the command as root.
175
176 """
177     synopsis = "%prog <username> [options]"
178
179     takes_options = [
180         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
181                metavar="URL", dest="H"),
182     ]
183
184     takes_args = ["username"]
185     takes_optiongroups = {
186         "sambaopts": options.SambaOptions,
187         "credopts": options.CredentialsOptions,
188         "versionopts": options.VersionOptions,
189         }
190
191     def run(self, username, credopts=None, sambaopts=None, versionopts=None, H=None):
192         lp = sambaopts.get_loadparm()
193         creds = credopts.get_credentials(lp, fallback_machine=True)
194
195         try:
196             samdb = SamDB(url=H, session_info=system_session(),
197                           credentials=creds, lp=lp)
198             samdb.deleteuser(username)
199         except Exception, e:
200             raise CommandError('Failed to remove user "%s"' % username, e)
201         self.outf.write("Deleted user %s\n" % username)
202
203
204 class cmd_user_list(Command):
205     """List all users"""
206
207     synopsis = "%prog [options]"
208
209     takes_options = [
210         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
211                metavar="URL", dest="H"),
212         ]
213
214     takes_optiongroups = {
215         "sambaopts": options.SambaOptions,
216         "credopts": options.CredentialsOptions,
217         "versionopts": options.VersionOptions,
218         }
219
220     def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
221         lp = sambaopts.get_loadparm()
222         creds = credopts.get_credentials(lp, fallback_machine=True)
223
224         samdb = SamDB(url=H, session_info=system_session(),
225             credentials=creds, lp=lp)
226
227         domain_dn = samdb.domain_dn()
228         res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
229                     expression=("(&(objectClass=user)(userAccountControl:%s:=%u))"
230                     % (ldb.OID_COMPARATOR_AND, dsdb.UF_NORMAL_ACCOUNT)),
231                     attrs=["samaccountname"])
232         if (len(res) == 0):
233             return
234
235         for msg in res:
236             self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
237
238
239 class cmd_user_enable(Command):
240     """Enables a user
241
242 This command enables a user account for logon to an Active Directory domain.  The username specified on the command is the sAMAccountName.  The username may also be specified using the --filter option.
243
244 There are many reasons why an account may become disabled.  These include:
245 - If a user exceeds the account policy for logon attempts
246 - If an administrator disables the account
247 - If the account expires
248
249 The samba-tool user enable command allows an administrator to enable an account which has become disabled.
250
251 Additionally, the enable function allows an administrator to have a set of created user accounts defined and setup with default permissions that can be easily enabled for use.
252
253 The command may be run from the root userid or another authorized userid.  The -H or --URL= option can be used to execute the command against a remote server.
254
255 Example1:
256 samba-tool user enable Testuser1 --URL=ldap://samba.samdom.example.com --username=administrator --password=passw1rd
257
258 Example1 shows how to enable a user in the domain against a remote LDAP server.  The --URL parameter is used to specify the remote target server.  The --username= and --password= options are used to pass the username and password of a user that exists on the remote server and is authorized to update that server.
259
260 Exampl2:
261 su samba-tool user enable Testuser2
262
263 Example2 shows how to enable user Testuser2 for use in the domain on the local server.   sudo is used so a user may run the command as root.
264
265 Example3:
266 samba-tool user enable --filter=samaccountname=Testuser3
267
268 Example3 shows how to enable a user in the domain against a local LDAP server.  It uses the --filter=samaccountname to specify the username.
269
270 """
271     synopsis = "%prog (<username>|--filter <filter>) [options]"
272
273
274     takes_optiongroups = {
275         "sambaopts": options.SambaOptions,
276         "versionopts": options.VersionOptions,
277         "credopts": options.CredentialsOptions,
278     }
279
280     takes_options = [
281         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
282                metavar="URL", dest="H"),
283         Option("--filter", help="LDAP Filter to set password on", type=str),
284         ]
285
286     takes_args = ["username?"]
287
288     def run(self, username=None, sambaopts=None, credopts=None,
289             versionopts=None, filter=None, H=None):
290         if username is None and filter is None:
291             raise CommandError("Either the username or '--filter' must be specified!")
292
293         if filter is None:
294             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
295
296         lp = sambaopts.get_loadparm()
297         creds = credopts.get_credentials(lp, fallback_machine=True)
298
299         samdb = SamDB(url=H, session_info=system_session(),
300             credentials=creds, lp=lp)
301         try:
302             samdb.enable_account(filter)
303         except Exception, msg:
304             raise CommandError("Failed to enable user '%s': %s" % (username or filter, msg))
305         self.outf.write("Enabled user '%s'\n" % (username or filter))
306
307
308 class cmd_user_disable(Command):
309     """Disable a user"""
310
311     synopsis = "%prog (<username>|--filter <filter>) [options]"
312
313     takes_options = [
314         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
315                metavar="URL", dest="H"),
316         Option("--filter", help="LDAP Filter to set password on", type=str),
317         ]
318
319     takes_args = ["username?"]
320
321     takes_optiongroups = {
322         "sambaopts": options.SambaOptions,
323         "credopts": options.CredentialsOptions,
324         "versionopts": options.VersionOptions,
325         }
326
327     def run(self, username=None, sambaopts=None, credopts=None,
328             versionopts=None, filter=None, H=None):
329         if username is None and filter is None:
330             raise CommandError("Either the username or '--filter' must be specified!")
331
332         if filter is None:
333             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
334
335         lp = sambaopts.get_loadparm()
336         creds = credopts.get_credentials(lp, fallback_machine=True)
337
338         samdb = SamDB(url=H, session_info=system_session(),
339             credentials=creds, lp=lp)
340         try:
341             samdb.disable_account(filter)
342         except Exception, msg:
343             raise CommandError("Failed to disable user '%s': %s" % (username or filter, msg))
344
345
346 class cmd_user_setexpiry(Command):
347     """Sets the expiration of a user account
348
349 This command sets the expiration of a user account.  The username specified on the command is the sAMAccountName.  The username may also be specified using the --filter option.
350
351 When a user account expires, it becomes disabled and the user is unable to logon.  The administrator may issue the samba-tool user enable command to enable the account for logon.  The permissions and memberships associated with the account are retained when the account is enabled.
352
353 The command may be run from the root userid or another authorized userid.  The -H or --URL= option can be used to execute the command on a remote server.
354
355 Example1:
356 samba-tool user setexpiry User1 --days=20 --URL=ldap://samba.samdom.example.com --username=administrator --password=passw1rd
357
358 Example1 shows how to set the expiration of an account in a remote LDAP server.  The --URL parameter is used to specify the remote target server.  The --username= and --password= options are used to pass the username and password of a user that exists on the remote server and is authorized to update that server.
359
360 Exampl2:
361 su samba-tool user setexpiry User2
362
363 Example2 shows how to set the account expiration of user User2 so it will never expire.  The user in this example resides on the  local server.   sudo is used so a user may run the command as root.
364
365 Example3:
366 samba-tool user setexpiry --days=20 --filter=samaccountname=User3
367
368 Example3 shows how to set the account expiration date to end of day 20 days from the current day.  The username or sAMAccountName is specified using the --filter= paramter and the username in this example is User3.
369
370 Example4:
371 samba-tool user setexpiry --noexpiry User4
372 Example4 shows how to set the account expiration so that it will never expire.  The username and sAMAccountName in this example is User4.
373
374 """
375     synopsis = "%prog (<username>|--filter <filter>) [options]"
376
377     takes_optiongroups = {
378         "sambaopts": options.SambaOptions,
379         "versionopts": options.VersionOptions,
380         "credopts": options.CredentialsOptions,
381     }
382
383     takes_options = [
384         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
385                metavar="URL", dest="H"),
386         Option("--filter", help="LDAP Filter to set password on", type=str),
387         Option("--days", help="Days to expiry", type=int, default=0),
388         Option("--noexpiry", help="Password does never expire", action="store_true", default=False),
389     ]
390
391     takes_args = ["username?"]
392
393     def run(self, username=None, sambaopts=None, credopts=None,
394             versionopts=None, H=None, filter=None, days=None, noexpiry=None):
395         if username is None and filter is None:
396             raise CommandError("Either the username or '--filter' must be specified!")
397
398         if filter is None:
399             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
400
401         lp = sambaopts.get_loadparm()
402         creds = credopts.get_credentials(lp)
403
404         samdb = SamDB(url=H, session_info=system_session(),
405             credentials=creds, lp=lp)
406
407         try:
408             samdb.setexpiry(filter, days*24*3600, no_expiry_req=noexpiry)
409         except Exception, msg:
410             # FIXME: Catch more specific exception
411             raise CommandError("Failed to set expiry for user '%s': %s" % (
412                 username or filter, msg))
413         self.outf.write("Set expiry for user '%s' to %u days\n" % (
414             username or filter, days))
415
416
417 class cmd_user_password(Command):
418     """Change password for a user account (the one provided in authentication)
419 """
420
421     synopsis = "%prog [options]"
422
423     takes_options = [
424         Option("--newpassword", help="New password", type=str),
425         ]
426
427     takes_optiongroups = {
428         "sambaopts": options.SambaOptions,
429         "credopts": options.CredentialsOptions,
430         "versionopts": options.VersionOptions,
431         }
432
433     def run(self, credopts=None, sambaopts=None, versionopts=None,
434                 newpassword=None):
435
436         lp = sambaopts.get_loadparm()
437         creds = credopts.get_credentials(lp)
438
439         # get old password now, to get the password prompts in the right order
440         old_password = creds.get_password()
441
442         net = Net(creds, lp, server=credopts.ipaddress)
443
444         password = newpassword
445         while True:
446             if password is not None and password is not '':
447                 break
448             password = getpass("New Password: ")
449             passwordverify = getpass("Retype Password: ")
450             if not password == passwordverify:
451                 password = None
452                 self.outf.write("Sorry, passwords do not match.\n")
453
454         try:
455             net.change_password(password)
456         except Exception, msg:
457             # FIXME: catch more specific exception
458             raise CommandError("Failed to change password : %s" % msg)
459         self.outf.write("Changed password OK\n")
460
461
462 class cmd_user_setpassword(Command):
463     """Sets or resets the password of a user account
464
465 This command sets or resets the logon password for a user account.  The username specified on the command is the sAMAccountName.  The username may also be specified using the --filter option.
466
467 If the password is not specified on the command through the --newpassword parameter, the user is prompted for the password to be entered through the command line.
468
469 It is good security practice for the administrator to use the --must-change-at-next-login option which requires that when the user logs on to the account for the first time following the password change, he/she must change the password.
470
471 The command may be run from the root userid or another authorized userid.  The -H or --URL= option can be used to execute the command against a remote server.
472
473 Example1:
474 samba-tool user setpassword TestUser1 passw0rd --URL=ldap://samba.samdom.example.com -Uadministrator%passw1rd
475
476 Example1 shows how to set the password of user TestUser1 on a remote LDAP server.  The --URL parameter is used to specify the remote target server.  The -U option is used to pass the username and password of a user that exists on the remote server and is authorized to update the server.
477
478 Example2:
479 sudo samba-tool user setpassword TestUser2 passw0rd --must-change-at-next-login
480
481 Example2 shows how an administrator would reset the TestUser2 user's password to passw0rd.  The user is running under the root userid using the sudo command.  In this example the user TestUser2 must change their password the next time they logon to the account.
482
483 Example3:
484 samba-tool user setpassword --filter=samaccountname=TestUser3 --password=passw0rd
485
486 Example3 shows how an administrator would reset TestUser3 user's password to passw0rd using the --filter= option to specify the username.
487
488 """
489     synopsis = "%prog (<username>|--filter <filter>) [options]"
490
491     takes_optiongroups = {
492         "sambaopts": options.SambaOptions,
493         "versionopts": options.VersionOptions,
494         "credopts": options.CredentialsOptions,
495     }
496
497     takes_options = [
498         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
499                metavar="URL", dest="H"),
500         Option("--filter", help="LDAP Filter to set password on", type=str),
501         Option("--newpassword", help="Set password", type=str),
502         Option("--must-change-at-next-login",
503                help="Force password to be changed on next login",
504                action="store_true"),
505         Option("--random-password",
506                 help="Generate random password",
507                 action="store_true"),
508         ]
509
510     takes_args = ["username?"]
511
512     def run(self, username=None, filter=None, credopts=None, sambaopts=None,
513             versionopts=None, H=None, newpassword=None,
514             must_change_at_next_login=False, random_password=False):
515         if filter is None and username is None:
516             raise CommandError("Either the username or '--filter' must be specified!")
517
518         if random_password:
519             password = generate_random_password(128, 255)
520         else:
521             password = newpassword
522
523         while 1:
524             if password is not None and password is not '':
525                 break
526             password = getpass("New Password: ")
527
528         if filter is None:
529             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
530
531         lp = sambaopts.get_loadparm()
532         creds = credopts.get_credentials(lp)
533
534         creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
535
536         samdb = SamDB(url=H, session_info=system_session(),
537                       credentials=creds, lp=lp)
538
539         try:
540             samdb.setpassword(filter, password,
541                               force_change_at_next_login=must_change_at_next_login,
542                               username=username)
543         except Exception, msg:
544             # FIXME: catch more specific exception
545             raise CommandError("Failed to set password for user '%s': %s" % (username or filter, msg))
546         self.outf.write("Changed password OK\n")
547
548
549 class cmd_user(SuperCommand):
550     """User management"""
551
552     subcommands = {}
553     subcommands["add"] = cmd_user_create()
554     subcommands["create"] = cmd_user_create()
555     subcommands["delete"] = cmd_user_delete()
556     subcommands["disable"] = cmd_user_disable()
557     subcommands["enable"] = cmd_user_enable()
558     subcommands["list"] = cmd_user_list()
559     subcommands["setexpiry"] = cmd_user_setexpiry()
560     subcommands["password"] = cmd_user_password()
561     subcommands["setpassword"] = cmd_user_setpassword()