Set RFC2307 attributes in samba-tool create
[kai/samba.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 import pwd
23 from getpass import getpass
24 from samba.auth import system_session
25 from samba.samdb import SamDB
26 from samba import (
27     dsdb,
28     gensec,
29     generate_random_password,
30     )
31 from samba.net import Net
32
33 from samba.netcmd import (
34     Command,
35     CommandError,
36     SuperCommand,
37     Option,
38     )
39
40
41 class cmd_user_create(Command):
42     """Create a new user.
43
44 This command creates a new user account in the Active Directory domain.  The username specified on the command is the sAMaccountName.
45
46 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).
47
48 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.
49
50 Unix (RFC2307) attributes may be added to the user account. Attributes taken from NSS are obtained on the local machine. Explicitly given values override values obtained from NSS. Configure 'idmap_ldb:use rfc2307 = Yes' to use these attributes for UID/GID mapping.
51
52 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.
53
54 Example1:
55 samba-tool user add User1 passw0rd --given-name=John --surname=Smith --must-change-at-next-login -H ldap://samba.samdom.example.com -Uadministrator%passw1rd
56
57 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.
58
59 Example2:
60 sudo samba-tool user add User2 passw2rd --given-name=Jane --surname=Doe --must-change-at-next-login
61
62 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.
63
64 Example3:
65 samba-tool user add User3 passw3rd --userou=OrgUnit
66
67 Example3 shows how to create a new user in the OrgUnit organizational unit.
68
69 Example4:
70 samba-tool user create User4 passw4rd --rfc2307-from-nss --gecos 'some text'
71
72 Example4 shows how to create a new user with Unix UID, GID and login-shell set from the local NSS and GECOS set to 'some text'.
73
74 """
75     synopsis = "%prog <username> [<password>] [options]"
76
77     takes_options = [
78         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
79                 metavar="URL", dest="H"),
80         Option("--must-change-at-next-login",
81                 help="Force password to be changed on next login",
82                 action="store_true"),
83         Option("--random-password",
84                 help="Generate random password",
85                 action="store_true"),
86         Option("--use-username-as-cn",
87                 help="Force use of username as user's CN",
88                 action="store_true"),
89         Option("--userou",
90                 help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
91                 type=str),
92         Option("--surname", help="User's surname", type=str),
93         Option("--given-name", help="User's given name", type=str),
94         Option("--initials", help="User's initials", type=str),
95         Option("--profile-path", help="User's profile path", type=str),
96         Option("--script-path", help="User's logon script path", type=str),
97         Option("--home-drive", help="User's home drive letter", type=str),
98         Option("--home-directory", help="User's home directory path", type=str),
99         Option("--job-title", help="User's job title", type=str),
100         Option("--department", help="User's department", type=str),
101         Option("--company", help="User's company", type=str),
102         Option("--description", help="User's description", type=str),
103         Option("--mail-address", help="User's email address", type=str),
104         Option("--internet-address", help="User's home page", type=str),
105         Option("--telephone-number", help="User's phone number", type=str),
106         Option("--physical-delivery-office", help="User's office location", type=str),
107         Option("--rfc2307-from-nss",
108                 help="Copy Unix user attributes from NSS (will be overridden by explicit UID/GID/GECOS/shell)",
109                 action="store_true"),
110         Option("--uid", help="User's Unix/RFC2307 username", type=str),
111         Option("--uid-number", help="User's Unix/RFC2307 numeric UID", type=int),
112         Option("--gid-number", help="User's Unix/RFC2307 primary GID number", type=int),
113         Option("--gecos", help="User's Unix/RFC2307 GECOS field", type=str),
114         Option("--login-shell", help="User's Unix/RFC2307 login shell", type=str),
115     ]
116
117     takes_args = ["username", "password?"]
118
119     takes_optiongroups = {
120         "sambaopts": options.SambaOptions,
121         "credopts": options.CredentialsOptions,
122         "versionopts": options.VersionOptions,
123         }
124
125     def run(self, username, password=None, credopts=None, sambaopts=None,
126             versionopts=None, H=None, must_change_at_next_login=False,
127             random_password=False, use_username_as_cn=False, userou=None,
128             surname=None, given_name=None, initials=None, profile_path=None,
129             script_path=None, home_drive=None, home_directory=None,
130             job_title=None, department=None, company=None, description=None,
131             mail_address=None, internet_address=None, telephone_number=None,
132             physical_delivery_office=None, rfc2307_from_nss=False,
133             uid=None, uid_number=None, gid_number=None, gecos=None, login_shell=None):
134
135         if random_password:
136             password = generate_random_password(128, 255)
137
138         while True:
139             if password is not None and password is not '':
140                 break
141             password = getpass("New Password: ")
142             passwordverify = getpass("Retype Password: ")
143             if not password == passwordverify:
144                 password = None
145                 self.outf.write("Sorry, passwords do not match.\n")
146
147         if rfc2307_from_nss:
148                 pwent = pwd.getpwnam(username)
149                 if uid is None:
150                     uid = username
151                 if uid_number is None:
152                     uid_number = pwent[2]
153                 if gid_number is None:
154                     gid_number = pwent[3]
155                 if gecos is None:
156                     gecos = pwent[4]
157                 if login_shell is None:
158                     login_shell = pwent[6]
159
160         lp = sambaopts.get_loadparm()
161         creds = credopts.get_credentials(lp)
162
163         try:
164             samdb = SamDB(url=H, session_info=system_session(),
165                           credentials=creds, lp=lp)
166             samdb.newuser(username, password, force_password_change_at_next_login_req=must_change_at_next_login,
167                           useusernameascn=use_username_as_cn, userou=userou, surname=surname, givenname=given_name, initials=initials,
168                           profilepath=profile_path, homedrive=home_drive, scriptpath=script_path, homedirectory=home_directory,
169                           jobtitle=job_title, department=department, company=company, description=description,
170                           mailaddress=mail_address, internetaddress=internet_address,
171                           telephonenumber=telephone_number, physicaldeliveryoffice=physical_delivery_office,
172                           uid=uid, uidnumber=uid_number, gidnumber=gid_number, gecos=gecos, loginshell=login_shell)
173         except Exception, e:
174             raise CommandError("Failed to add user '%s': " % username, e)
175
176         self.outf.write("User '%s' created successfully\n" % username)
177
178
179 class cmd_user_add(cmd_user_create):
180     __doc__ = cmd_user_create.__doc__
181     # take this print out after the add subcommand is removed.
182     # the add subcommand is deprecated but left in for now to allow people to
183     # migrate to create
184
185     def run(self, *args, **kwargs):
186         self.err.write(
187             "Note: samba-tool user add is deprecated.  "
188             "Please use samba-tool user create for the same function.\n")
189         return super(self, cmd_user_add).run(*args, **kwargs)
190
191
192 class cmd_user_delete(Command):
193     """Delete a user.
194
195 This command deletes a user account from the Active Directory domain.  The username specified on the command is the sAMAccountName.
196
197 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.
198
199 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.
200
201 Example1:
202 samba-tool user delete User1 -H ldap://samba.samdom.example.com --username=administrator --password=passw1rd
203
204 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.
205
206 Example2:
207 sudo samba-tool user delete User2
208
209 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.
210
211 """
212     synopsis = "%prog <username> [options]"
213
214     takes_options = [
215         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
216                metavar="URL", dest="H"),
217     ]
218
219     takes_args = ["username"]
220     takes_optiongroups = {
221         "sambaopts": options.SambaOptions,
222         "credopts": options.CredentialsOptions,
223         "versionopts": options.VersionOptions,
224         }
225
226     def run(self, username, credopts=None, sambaopts=None, versionopts=None,
227             H=None):
228         lp = sambaopts.get_loadparm()
229         creds = credopts.get_credentials(lp, fallback_machine=True)
230
231         try:
232             samdb = SamDB(url=H, session_info=system_session(),
233                           credentials=creds, lp=lp)
234             samdb.deleteuser(username)
235         except Exception, e:
236             raise CommandError('Failed to remove user "%s"' % username, e)
237         self.outf.write("Deleted user %s\n" % username)
238
239
240 class cmd_user_list(Command):
241     """List all users."""
242
243     synopsis = "%prog [options]"
244
245     takes_options = [
246         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
247                metavar="URL", dest="H"),
248         ]
249
250     takes_optiongroups = {
251         "sambaopts": options.SambaOptions,
252         "credopts": options.CredentialsOptions,
253         "versionopts": options.VersionOptions,
254         }
255
256     def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
257         lp = sambaopts.get_loadparm()
258         creds = credopts.get_credentials(lp, fallback_machine=True)
259
260         samdb = SamDB(url=H, session_info=system_session(),
261             credentials=creds, lp=lp)
262
263         domain_dn = samdb.domain_dn()
264         res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
265                     expression=("(&(objectClass=user)(userAccountControl:%s:=%u))"
266                     % (ldb.OID_COMPARATOR_AND, dsdb.UF_NORMAL_ACCOUNT)),
267                     attrs=["samaccountname"])
268         if (len(res) == 0):
269             return
270
271         for msg in res:
272             self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
273
274
275 class cmd_user_enable(Command):
276     """Enable an user.
277
278 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.
279
280 There are many reasons why an account may become disabled.  These include:
281 - If a user exceeds the account policy for logon attempts
282 - If an administrator disables the account
283 - If the account expires
284
285 The samba-tool user enable command allows an administrator to enable an account which has become disabled.
286
287 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.
288
289 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.
290
291 Example1:
292 samba-tool user enable Testuser1 --URL=ldap://samba.samdom.example.com --username=administrator --password=passw1rd
293
294 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.
295
296 Exampl2:
297 su samba-tool user enable Testuser2
298
299 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.
300
301 Example3:
302 samba-tool user enable --filter=samaccountname=Testuser3
303
304 Example3 shows how to enable a user in the domain against a local LDAP server.  It uses the --filter=samaccountname to specify the username.
305
306 """
307     synopsis = "%prog (<username>|--filter <filter>) [options]"
308
309
310     takes_optiongroups = {
311         "sambaopts": options.SambaOptions,
312         "versionopts": options.VersionOptions,
313         "credopts": options.CredentialsOptions,
314     }
315
316     takes_options = [
317         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
318                metavar="URL", dest="H"),
319         Option("--filter", help="LDAP Filter to set password on", type=str),
320         ]
321
322     takes_args = ["username?"]
323
324     def run(self, username=None, sambaopts=None, credopts=None,
325             versionopts=None, filter=None, H=None):
326         if username is None and filter is None:
327             raise CommandError("Either the username or '--filter' must be specified!")
328
329         if filter is None:
330             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
331
332         lp = sambaopts.get_loadparm()
333         creds = credopts.get_credentials(lp, fallback_machine=True)
334
335         samdb = SamDB(url=H, session_info=system_session(),
336             credentials=creds, lp=lp)
337         try:
338             samdb.enable_account(filter)
339         except Exception, msg:
340             raise CommandError("Failed to enable user '%s': %s" % (username or filter, msg))
341         self.outf.write("Enabled user '%s'\n" % (username or filter))
342
343
344 class cmd_user_disable(Command):
345     """Disable an user."""
346
347     synopsis = "%prog (<username>|--filter <filter>) [options]"
348
349     takes_options = [
350         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
351                metavar="URL", dest="H"),
352         Option("--filter", help="LDAP Filter to set password on", type=str),
353         ]
354
355     takes_args = ["username?"]
356
357     takes_optiongroups = {
358         "sambaopts": options.SambaOptions,
359         "credopts": options.CredentialsOptions,
360         "versionopts": options.VersionOptions,
361         }
362
363     def run(self, username=None, sambaopts=None, credopts=None,
364             versionopts=None, filter=None, H=None):
365         if username is None and filter is None:
366             raise CommandError("Either the username or '--filter' must be specified!")
367
368         if filter is None:
369             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
370
371         lp = sambaopts.get_loadparm()
372         creds = credopts.get_credentials(lp, fallback_machine=True)
373
374         samdb = SamDB(url=H, session_info=system_session(),
375             credentials=creds, lp=lp)
376         try:
377             samdb.disable_account(filter)
378         except Exception, msg:
379             raise CommandError("Failed to disable user '%s': %s" % (username or filter, msg))
380
381
382 class cmd_user_setexpiry(Command):
383     """Set the expiration of a user account.
384
385 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.
386
387 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.
388
389 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.
390
391 Example1:
392 samba-tool user setexpiry User1 --days=20 --URL=ldap://samba.samdom.example.com --username=administrator --password=passw1rd
393
394 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.
395
396 Exampl2:
397 su samba-tool user setexpiry User2
398
399 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.
400
401 Example3:
402 samba-tool user setexpiry --days=20 --filter=samaccountname=User3
403
404 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.
405
406 Example4:
407 samba-tool user setexpiry --noexpiry User4
408 Example4 shows how to set the account expiration so that it will never expire.  The username and sAMAccountName in this example is User4.
409
410 """
411     synopsis = "%prog (<username>|--filter <filter>) [options]"
412
413     takes_optiongroups = {
414         "sambaopts": options.SambaOptions,
415         "versionopts": options.VersionOptions,
416         "credopts": options.CredentialsOptions,
417     }
418
419     takes_options = [
420         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
421                metavar="URL", dest="H"),
422         Option("--filter", help="LDAP Filter to set password on", type=str),
423         Option("--days", help="Days to expiry", type=int, default=0),
424         Option("--noexpiry", help="Password does never expire", action="store_true", default=False),
425     ]
426
427     takes_args = ["username?"]
428
429     def run(self, username=None, sambaopts=None, credopts=None,
430             versionopts=None, H=None, filter=None, days=None, noexpiry=None):
431         if username is None and filter is None:
432             raise CommandError("Either the username or '--filter' must be specified!")
433
434         if filter is None:
435             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
436
437         lp = sambaopts.get_loadparm()
438         creds = credopts.get_credentials(lp)
439
440         samdb = SamDB(url=H, session_info=system_session(),
441             credentials=creds, lp=lp)
442
443         try:
444             samdb.setexpiry(filter, days*24*3600, no_expiry_req=noexpiry)
445         except Exception, msg:
446             # FIXME: Catch more specific exception
447             raise CommandError("Failed to set expiry for user '%s': %s" % (
448                 username or filter, msg))
449         self.outf.write("Set expiry for user '%s' to %u days\n" % (
450             username or filter, days))
451
452
453 class cmd_user_password(Command):
454     """Change password for a user account (the one provided in authentication).
455 """
456
457     synopsis = "%prog [options]"
458
459     takes_options = [
460         Option("--newpassword", help="New password", type=str),
461         ]
462
463     takes_optiongroups = {
464         "sambaopts": options.SambaOptions,
465         "credopts": options.CredentialsOptions,
466         "versionopts": options.VersionOptions,
467         }
468
469     def run(self, credopts=None, sambaopts=None, versionopts=None,
470                 newpassword=None):
471
472         lp = sambaopts.get_loadparm()
473         creds = credopts.get_credentials(lp)
474
475         # get old password now, to get the password prompts in the right order
476         old_password = creds.get_password()
477
478         net = Net(creds, lp, server=credopts.ipaddress)
479
480         password = newpassword
481         while True:
482             if password is not None and password is not '':
483                 break
484             password = getpass("New Password: ")
485             passwordverify = getpass("Retype Password: ")
486             if not password == passwordverify:
487                 password = None
488                 self.outf.write("Sorry, passwords do not match.\n")
489
490         try:
491             net.change_password(password)
492         except Exception, msg:
493             # FIXME: catch more specific exception
494             raise CommandError("Failed to change password : %s" % msg)
495         self.outf.write("Changed password OK\n")
496
497
498 class cmd_user_setpassword(Command):
499     """Set or reset the password of a user account.
500
501 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.
502
503 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.
504
505 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.
506
507 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.
508
509 Example1:
510 samba-tool user setpassword TestUser1 passw0rd --URL=ldap://samba.samdom.example.com -Uadministrator%passw1rd
511
512 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.
513
514 Example2:
515 sudo samba-tool user setpassword TestUser2 passw0rd --must-change-at-next-login
516
517 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.
518
519 Example3:
520 samba-tool user setpassword --filter=samaccountname=TestUser3 --password=passw0rd
521
522 Example3 shows how an administrator would reset TestUser3 user's password to passw0rd using the --filter= option to specify the username.
523
524 """
525     synopsis = "%prog (<username>|--filter <filter>) [options]"
526
527     takes_optiongroups = {
528         "sambaopts": options.SambaOptions,
529         "versionopts": options.VersionOptions,
530         "credopts": options.CredentialsOptions,
531     }
532
533     takes_options = [
534         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
535                metavar="URL", dest="H"),
536         Option("--filter", help="LDAP Filter to set password on", type=str),
537         Option("--newpassword", help="Set password", type=str),
538         Option("--must-change-at-next-login",
539                help="Force password to be changed on next login",
540                action="store_true"),
541         Option("--random-password",
542                 help="Generate random password",
543                 action="store_true"),
544         ]
545
546     takes_args = ["username?"]
547
548     def run(self, username=None, filter=None, credopts=None, sambaopts=None,
549             versionopts=None, H=None, newpassword=None,
550             must_change_at_next_login=False, random_password=False):
551         if filter is None and username is None:
552             raise CommandError("Either the username or '--filter' must be specified!")
553
554         if random_password:
555             password = generate_random_password(128, 255)
556         else:
557             password = newpassword
558
559         while 1:
560             if password is not None and password is not '':
561                 break
562             password = getpass("New Password: ")
563
564         if filter is None:
565             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
566
567         lp = sambaopts.get_loadparm()
568         creds = credopts.get_credentials(lp)
569
570         creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
571
572         samdb = SamDB(url=H, session_info=system_session(),
573                       credentials=creds, lp=lp)
574
575         try:
576             samdb.setpassword(filter, password,
577                               force_change_at_next_login=must_change_at_next_login,
578                               username=username)
579         except Exception, msg:
580             # FIXME: catch more specific exception
581             raise CommandError("Failed to set password for user '%s': %s" % (username or filter, msg))
582         self.outf.write("Changed password OK\n")
583
584
585 class cmd_user(SuperCommand):
586     """User management."""
587
588     subcommands = {}
589     subcommands["add"] = cmd_user_create()
590     subcommands["create"] = cmd_user_create()
591     subcommands["delete"] = cmd_user_delete()
592     subcommands["disable"] = cmd_user_disable()
593     subcommands["enable"] = cmd_user_enable()
594     subcommands["list"] = cmd_user_list()
595     subcommands["setexpiry"] = cmd_user_setexpiry()
596     subcommands["password"] = cmd_user_password()
597     subcommands["setpassword"] = cmd_user_setpassword()