build: Change bin/default/python -> bin/python symlink to bin/default/python_modules
[bbaumbach/samba-autobuild/.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         if uid_number or gid_number:
164             if not lp.get("idmap_ldb:use rfc2307"):
165                 self.outf.write("You are setting a Unix/RFC2307 UID or GID. You may want to set 'idmap_ldb:use rfc2307 = Yes' to use those attributes for XID/SID-mapping.\n")
166
167         try:
168             samdb = SamDB(url=H, session_info=system_session(),
169                           credentials=creds, lp=lp)
170             samdb.newuser(username, password, force_password_change_at_next_login_req=must_change_at_next_login,
171                           useusernameascn=use_username_as_cn, userou=userou, surname=surname, givenname=given_name, initials=initials,
172                           profilepath=profile_path, homedrive=home_drive, scriptpath=script_path, homedirectory=home_directory,
173                           jobtitle=job_title, department=department, company=company, description=description,
174                           mailaddress=mail_address, internetaddress=internet_address,
175                           telephonenumber=telephone_number, physicaldeliveryoffice=physical_delivery_office,
176                           uid=uid, uidnumber=uid_number, gidnumber=gid_number, gecos=gecos, loginshell=login_shell)
177         except Exception, e:
178             raise CommandError("Failed to add user '%s': " % username, e)
179
180         self.outf.write("User '%s' created successfully\n" % username)
181
182
183 class cmd_user_add(cmd_user_create):
184     __doc__ = cmd_user_create.__doc__
185     # take this print out after the add subcommand is removed.
186     # the add subcommand is deprecated but left in for now to allow people to
187     # migrate to create
188
189     def run(self, *args, **kwargs):
190         self.err.write(
191             "Note: samba-tool user add is deprecated.  "
192             "Please use samba-tool user create for the same function.\n")
193         return super(self, cmd_user_add).run(*args, **kwargs)
194
195
196 class cmd_user_delete(Command):
197     """Delete a user.
198
199 This command deletes a user account from the Active Directory domain.  The username specified on the command is the sAMAccountName.
200
201 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.
202
203 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.
204
205 Example1:
206 samba-tool user delete User1 -H ldap://samba.samdom.example.com --username=administrator --password=passw1rd
207
208 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.
209
210 Example2:
211 sudo samba-tool user delete User2
212
213 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.
214
215 """
216     synopsis = "%prog <username> [options]"
217
218     takes_options = [
219         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
220                metavar="URL", dest="H"),
221     ]
222
223     takes_args = ["username"]
224     takes_optiongroups = {
225         "sambaopts": options.SambaOptions,
226         "credopts": options.CredentialsOptions,
227         "versionopts": options.VersionOptions,
228         }
229
230     def run(self, username, credopts=None, sambaopts=None, versionopts=None,
231             H=None):
232         lp = sambaopts.get_loadparm()
233         creds = credopts.get_credentials(lp, fallback_machine=True)
234
235         try:
236             samdb = SamDB(url=H, session_info=system_session(),
237                           credentials=creds, lp=lp)
238             samdb.deleteuser(username)
239         except Exception, e:
240             raise CommandError('Failed to remove user "%s"' % username, e)
241         self.outf.write("Deleted user %s\n" % username)
242
243
244 class cmd_user_list(Command):
245     """List all users."""
246
247     synopsis = "%prog [options]"
248
249     takes_options = [
250         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
251                metavar="URL", dest="H"),
252         ]
253
254     takes_optiongroups = {
255         "sambaopts": options.SambaOptions,
256         "credopts": options.CredentialsOptions,
257         "versionopts": options.VersionOptions,
258         }
259
260     def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
261         lp = sambaopts.get_loadparm()
262         creds = credopts.get_credentials(lp, fallback_machine=True)
263
264         samdb = SamDB(url=H, session_info=system_session(),
265             credentials=creds, lp=lp)
266
267         domain_dn = samdb.domain_dn()
268         res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
269                     expression=("(&(objectClass=user)(userAccountControl:%s:=%u))"
270                     % (ldb.OID_COMPARATOR_AND, dsdb.UF_NORMAL_ACCOUNT)),
271                     attrs=["samaccountname"])
272         if (len(res) == 0):
273             return
274
275         for msg in res:
276             self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
277
278
279 class cmd_user_enable(Command):
280     """Enable an user.
281
282 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.
283
284 There are many reasons why an account may become disabled.  These include:
285 - If a user exceeds the account policy for logon attempts
286 - If an administrator disables the account
287 - If the account expires
288
289 The samba-tool user enable command allows an administrator to enable an account which has become disabled.
290
291 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.
292
293 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.
294
295 Example1:
296 samba-tool user enable Testuser1 --URL=ldap://samba.samdom.example.com --username=administrator --password=passw1rd
297
298 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.
299
300 Example2:
301 su samba-tool user enable Testuser2
302
303 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.
304
305 Example3:
306 samba-tool user enable --filter=samaccountname=Testuser3
307
308 Example3 shows how to enable a user in the domain against a local LDAP server.  It uses the --filter=samaccountname to specify the username.
309
310 """
311     synopsis = "%prog (<username>|--filter <filter>) [options]"
312
313
314     takes_optiongroups = {
315         "sambaopts": options.SambaOptions,
316         "versionopts": options.VersionOptions,
317         "credopts": options.CredentialsOptions,
318     }
319
320     takes_options = [
321         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
322                metavar="URL", dest="H"),
323         Option("--filter", help="LDAP Filter to set password on", type=str),
324         ]
325
326     takes_args = ["username?"]
327
328     def run(self, username=None, sambaopts=None, credopts=None,
329             versionopts=None, filter=None, H=None):
330         if username is None and filter is None:
331             raise CommandError("Either the username or '--filter' must be specified!")
332
333         if filter is None:
334             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
335
336         lp = sambaopts.get_loadparm()
337         creds = credopts.get_credentials(lp, fallback_machine=True)
338
339         samdb = SamDB(url=H, session_info=system_session(),
340             credentials=creds, lp=lp)
341         try:
342             samdb.enable_account(filter)
343         except Exception, msg:
344             raise CommandError("Failed to enable user '%s': %s" % (username or filter, msg))
345         self.outf.write("Enabled user '%s'\n" % (username or filter))
346
347
348 class cmd_user_disable(Command):
349     """Disable an user."""
350
351     synopsis = "%prog (<username>|--filter <filter>) [options]"
352
353     takes_options = [
354         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
355                metavar="URL", dest="H"),
356         Option("--filter", help="LDAP Filter to set password on", type=str),
357         ]
358
359     takes_args = ["username?"]
360
361     takes_optiongroups = {
362         "sambaopts": options.SambaOptions,
363         "credopts": options.CredentialsOptions,
364         "versionopts": options.VersionOptions,
365         }
366
367     def run(self, username=None, sambaopts=None, credopts=None,
368             versionopts=None, filter=None, H=None):
369         if username is None and filter is None:
370             raise CommandError("Either the username or '--filter' must be specified!")
371
372         if filter is None:
373             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
374
375         lp = sambaopts.get_loadparm()
376         creds = credopts.get_credentials(lp, fallback_machine=True)
377
378         samdb = SamDB(url=H, session_info=system_session(),
379             credentials=creds, lp=lp)
380         try:
381             samdb.disable_account(filter)
382         except Exception, msg:
383             raise CommandError("Failed to disable user '%s': %s" % (username or filter, msg))
384
385
386 class cmd_user_setexpiry(Command):
387     """Set the expiration of a user account.
388
389 The user can either be specified by their sAMAccountName or using the --filter option.
390
391 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.
392
393 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.
394
395 Example1:
396 samba-tool user setexpiry User1 --days=20 --URL=ldap://samba.samdom.example.com --username=administrator --password=passw1rd
397
398 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.
399
400 Example2:
401 su samba-tool user setexpiry User2
402
403 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.
404
405 Example3:
406 samba-tool user setexpiry --days=20 --filter=samaccountname=User3
407
408 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.
409
410 Example4:
411 samba-tool user setexpiry --noexpiry User4
412 Example4 shows how to set the account expiration so that it will never expire.  The username and sAMAccountName in this example is User4.
413
414 """
415     synopsis = "%prog (<username>|--filter <filter>) [options]"
416
417     takes_optiongroups = {
418         "sambaopts": options.SambaOptions,
419         "versionopts": options.VersionOptions,
420         "credopts": options.CredentialsOptions,
421     }
422
423     takes_options = [
424         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
425                metavar="URL", dest="H"),
426         Option("--filter", help="LDAP Filter to set password on", type=str),
427         Option("--days", help="Days to expiry", type=int, default=0),
428         Option("--noexpiry", help="Password does never expire", action="store_true", default=False),
429     ]
430
431     takes_args = ["username?"]
432
433     def run(self, username=None, sambaopts=None, credopts=None,
434             versionopts=None, H=None, filter=None, days=None, noexpiry=None):
435         if username is None and filter is None:
436             raise CommandError("Either the username or '--filter' must be specified!")
437
438         if filter is None:
439             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
440
441         lp = sambaopts.get_loadparm()
442         creds = credopts.get_credentials(lp)
443
444         samdb = SamDB(url=H, session_info=system_session(),
445             credentials=creds, lp=lp)
446
447         try:
448             samdb.setexpiry(filter, days*24*3600, no_expiry_req=noexpiry)
449         except Exception, msg:
450             # FIXME: Catch more specific exception
451             raise CommandError("Failed to set expiry for user '%s': %s" % (
452                 username or filter, msg))
453         if days:
454             self.outf.write("Expiry for user '%s' set to %u days.\n" % (
455                 username or filter, days))
456         else:
457             self.outf.write("Expiry for user '%s' disabled.\n" % (
458                 username or filter))
459
460
461 class cmd_user_password(Command):
462     """Change password for a user account (the one provided in authentication).
463 """
464
465     synopsis = "%prog [options]"
466
467     takes_options = [
468         Option("--newpassword", help="New password", type=str),
469         ]
470
471     takes_optiongroups = {
472         "sambaopts": options.SambaOptions,
473         "credopts": options.CredentialsOptions,
474         "versionopts": options.VersionOptions,
475         }
476
477     def run(self, credopts=None, sambaopts=None, versionopts=None,
478                 newpassword=None):
479
480         lp = sambaopts.get_loadparm()
481         creds = credopts.get_credentials(lp)
482
483         # get old password now, to get the password prompts in the right order
484         old_password = creds.get_password()
485
486         net = Net(creds, lp, server=credopts.ipaddress)
487
488         password = newpassword
489         while True:
490             if password is not None and password is not '':
491                 break
492             password = getpass("New Password: ")
493             passwordverify = getpass("Retype Password: ")
494             if not password == passwordverify:
495                 password = None
496                 self.outf.write("Sorry, passwords do not match.\n")
497
498         try:
499             net.change_password(password)
500         except Exception, msg:
501             # FIXME: catch more specific exception
502             raise CommandError("Failed to change password : %s" % msg)
503         self.outf.write("Changed password OK\n")
504
505
506 class cmd_user_setpassword(Command):
507     """Set or reset the password of a user account.
508
509 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.
510
511 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.
512
513 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.
514
515 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.
516
517 Example1:
518 samba-tool user setpassword TestUser1 --newpassword=passw0rd --URL=ldap://samba.samdom.example.com -Uadministrator%passw1rd
519
520 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.
521
522 Example2:
523 sudo samba-tool user setpassword TestUser2 --newpassword=passw0rd --must-change-at-next-login
524
525 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.
526
527 Example3:
528 samba-tool user setpassword --filter=samaccountname=TestUser3 --newpassword=passw0rd
529
530 Example3 shows how an administrator would reset TestUser3 user's password to passw0rd using the --filter= option to specify the username.
531
532 """
533     synopsis = "%prog (<username>|--filter <filter>) [options]"
534
535     takes_optiongroups = {
536         "sambaopts": options.SambaOptions,
537         "versionopts": options.VersionOptions,
538         "credopts": options.CredentialsOptions,
539     }
540
541     takes_options = [
542         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
543                metavar="URL", dest="H"),
544         Option("--filter", help="LDAP Filter to set password on", type=str),
545         Option("--newpassword", help="Set password", type=str),
546         Option("--must-change-at-next-login",
547                help="Force password to be changed on next login",
548                action="store_true"),
549         Option("--random-password",
550                 help="Generate random password",
551                 action="store_true"),
552         ]
553
554     takes_args = ["username?"]
555
556     def run(self, username=None, filter=None, credopts=None, sambaopts=None,
557             versionopts=None, H=None, newpassword=None,
558             must_change_at_next_login=False, random_password=False):
559         if filter is None and username is None:
560             raise CommandError("Either the username or '--filter' must be specified!")
561
562         if random_password:
563             password = generate_random_password(128, 255)
564         else:
565             password = newpassword
566
567         while 1:
568             if password is not None and password is not '':
569                 break
570             password = getpass("New Password: ")
571
572         if filter is None:
573             filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
574
575         lp = sambaopts.get_loadparm()
576         creds = credopts.get_credentials(lp)
577
578         creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
579
580         samdb = SamDB(url=H, session_info=system_session(),
581                       credentials=creds, lp=lp)
582
583         try:
584             samdb.setpassword(filter, password,
585                               force_change_at_next_login=must_change_at_next_login,
586                               username=username)
587         except Exception, msg:
588             # FIXME: catch more specific exception
589             raise CommandError("Failed to set password for user '%s': %s" % (username or filter, msg))
590         self.outf.write("Changed password OK\n")
591
592
593 class cmd_user(SuperCommand):
594     """User management."""
595
596     subcommands = {}
597     subcommands["add"] = cmd_user_create()
598     subcommands["create"] = cmd_user_create()
599     subcommands["delete"] = cmd_user_delete()
600     subcommands["disable"] = cmd_user_disable()
601     subcommands["enable"] = cmd_user_enable()
602     subcommands["list"] = cmd_user_list()
603     subcommands["setexpiry"] = cmd_user_setexpiry()
604     subcommands["password"] = cmd_user_password()
605     subcommands["setpassword"] = cmd_user_setpassword()