1 # Manages Password Settings Objects
3 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import samba.getopt as options
21 from samba.samdb import SamDB
22 from samba.netcmd import (Command, CommandError, Option, SuperCommand)
23 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
24 from samba.auth import system_session
28 NEVER_TIMESTAMP = int(-0x8000000000000000)
30 def pso_container(samdb):
31 return "CN=Password Settings Container,CN=System,%s" % samdb.domain_dn()
33 def timestamp_to_mins(timestamp_str):
34 """Converts a timestamp in -100 nanosecond units to minutes"""
35 # treat a timestamp of 'never' the same as zero (this should work OK for
36 # most settings, and it displays better than trying to convert
37 # -0x8000000000000000 to minutes)
38 if int(timestamp_str) == NEVER_TIMESTAMP:
41 return abs(int(timestamp_str)) / (1e7 * 60)
43 def timestamp_to_days(timestamp_str):
44 """Converts a timestamp in -100 nanosecond units to days"""
45 return timestamp_to_mins(timestamp_str) / (60 * 24)
47 def mins_to_timestamp(mins):
48 """Converts a value in minutes to -100 nanosecond units"""
49 timestamp = -int((1e7) * 60 * mins)
52 def days_to_timestamp(days):
53 """Converts a value in days to -100 nanosecond units"""
54 timestamp = mins_to_timestamp(days * 60 * 24)
57 def show_pso_by_dn(outf, samdb, dn, show_applies_to=True):
58 """Displays the password settings for a PSO specified by DN"""
60 # map from the boolean LDB value to the CLI string the user sees
61 on_off_str = {"TRUE": "on", "FALSE": "off"}
63 pso_attrs = ['name', 'msDS-PasswordSettingsPrecedence',
64 'msDS-PasswordReversibleEncryptionEnabled',
65 'msDS-PasswordHistoryLength', 'msDS-MinimumPasswordLength',
66 'msDS-PasswordComplexityEnabled', 'msDS-MinimumPasswordAge',
67 'msDS-MaximumPasswordAge', 'msDS-LockoutObservationWindow',
68 'msDS-LockoutThreshold', 'msDS-LockoutDuration',
71 res = samdb.search(dn, scope=ldb.SCOPE_BASE, attrs=pso_attrs)
73 outf.write("Password information for PSO '%s'\n" % pso_res['name'])
76 outf.write("Precedence (lowest is best): %s\n" %
77 pso_res['msDS-PasswordSettingsPrecedence'])
78 bool_str = str(pso_res['msDS-PasswordComplexityEnabled'])
79 outf.write("Password complexity: %s\n" % on_off_str[bool_str])
80 bool_str = str(pso_res['msDS-PasswordReversibleEncryptionEnabled'])
81 outf.write("Store plaintext passwords: %s\n" % on_off_str[bool_str])
82 outf.write("Password history length: %s\n" %
83 pso_res['msDS-PasswordHistoryLength'])
84 outf.write("Minimum password length: %s\n" %
85 pso_res['msDS-MinimumPasswordLength'])
86 outf.write("Minimum password age (days): %d\n" %
87 timestamp_to_days(pso_res['msDS-MinimumPasswordAge'][0]))
88 outf.write("Maximum password age (days): %d\n" %
89 timestamp_to_days(pso_res['msDS-MaximumPasswordAge'][0]))
90 outf.write("Account lockout duration (mins): %d\n" %
91 timestamp_to_mins(pso_res['msDS-LockoutDuration'][0]))
92 outf.write("Account lockout threshold (attempts): %s\n" %
93 pso_res['msDS-LockoutThreshold'])
94 outf.write("Reset account lockout after (mins): %d\n" %
95 timestamp_to_mins(pso_res['msDS-LockoutObservationWindow'][0]))
98 if 'msDS-PSOAppliesTo' in pso_res:
99 outf.write("\nPSO applies directly to %d groups/users:\n" %
100 len(pso_res['msDS-PSOAppliesTo']))
101 for dn in pso_res['msDS-PSOAppliesTo']:
102 outf.write(" %s\n" % dn)
104 outf.write("\nNote: PSO does not apply to any users or groups.\n")
106 def check_pso_valid(samdb, pso_dn, name):
107 """Gracefully bail out if we can't view/modify the PSO specified"""
108 # the base scope search for the PSO throws an error if it doesn't exist
110 res = samdb.search(pso_dn, scope=ldb.SCOPE_BASE,
111 attrs=['msDS-PasswordSettingsPrecedence'])
112 except ldb.LdbError as e:
113 if e.args[0] == ldb.ERR_NO_SUCH_OBJECT:
114 raise CommandError("Unable to find PSO '%s'" % name)
117 # users need admin permission to modify/view a PSO. In this case, the
118 # search succeeds, but it doesn't return any attributes
119 if 'msDS-PasswordSettingsPrecedence' not in res[0]:
120 raise CommandError("You may not have permission to view/modify PSOs")
122 def show_pso_for_user(outf, samdb, username):
123 """Displays the password settings for a specific user"""
125 search_filter = "(&(sAMAccountName=%s)(objectClass=user))" % username
127 res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
128 expression=search_filter,
129 attrs=['msDS-ResultantPSO', 'msDS-PSOApplied'])
132 outf.write("User '%s' not found.\n" % username)
133 elif 'msDS-ResultantPSO' not in res[0]:
134 outf.write("No PSO applies to user '%s'. The default domain settings apply.\n"
136 outf.write("Refer to 'samba-tool domain passwordsettings show'.\n")
138 # sanity-check user has permissions to view PSO details (non-admin
139 # users can view msDS-ResultantPSO, but not the actual PSO details)
140 check_pso_valid(samdb, res[0]['msDS-ResultantPSO'][0], "???")
141 outf.write("The following PSO settings apply to user '%s'.\n\n" %
143 show_pso_by_dn(outf, samdb, res[0]['msDS-ResultantPSO'][0],
144 show_applies_to=False)
145 # PSOs that apply directly to a user don't necessarily have the best
146 # precedence, which could be a little confusing for PSO management
147 if 'msDS-PSOApplied' in res[0]:
148 outf.write("\nNote: PSO applies directly to user (any group PSOs are overridden)\n")
150 outf.write("\nPSO applies to user via group membership.\n")
152 def make_pso_ldb_msg(outf, samdb, pso_dn, create, lockout_threshold=None,
153 complexity=None, precedence=None, store_plaintext=None,
154 history_length=None, min_pwd_length=None,
155 min_pwd_age=None, max_pwd_age=None, lockout_duration=None,
156 reset_account_lockout_after=None):
157 """Packs the given PSO settings into an LDB message"""
160 m.dn = ldb.Dn(samdb, pso_dn)
163 ldb_oper = ldb.FLAG_MOD_ADD
164 m["msDS-objectClass"] = ldb.MessageElement("msDS-PasswordSettings",
165 ldb_oper, "objectClass")
167 ldb_oper = ldb.FLAG_MOD_REPLACE
169 if precedence is not None:
170 m["msDS-PasswordSettingsPrecedence"] = ldb.MessageElement(str(precedence),
171 ldb_oper, "msDS-PasswordSettingsPrecedence")
173 if complexity is not None:
174 bool_str = "TRUE" if complexity == "on" else "FALSE"
175 m["msDS-PasswordComplexityEnabled"] = ldb.MessageElement(bool_str,
176 ldb_oper, "msDS-PasswordComplexityEnabled")
178 if store_plaintext is not None:
179 bool_str = "TRUE" if store_plaintext == "on" else "FALSE"
180 m["msDS-msDS-PasswordReversibleEncryptionEnabled"] = \
181 ldb.MessageElement(bool_str, ldb_oper,
182 "msDS-PasswordReversibleEncryptionEnabled")
184 if history_length is not None:
185 m["msDS-PasswordHistoryLength"] = ldb.MessageElement(str(history_length),
186 ldb_oper, "msDS-PasswordHistoryLength")
188 if min_pwd_length is not None:
189 m["msDS-MinimumPasswordLength"] = ldb.MessageElement(str(min_pwd_length),
190 ldb_oper, "msDS-MinimumPasswordLength")
192 if min_pwd_age is not None:
193 min_pwd_age_ticks = days_to_timestamp(min_pwd_age)
194 m["msDS-MinimumPasswordAge"] = ldb.MessageElement(min_pwd_age_ticks,
195 ldb_oper, "msDS-MinimumPasswordAge")
197 if max_pwd_age is not None:
198 # Windows won't let you set max-pwd-age to zero. Here we take zero to
199 # mean 'never expire' and use the timestamp corresponding to 'never'
201 max_pwd_age_ticks = str(NEVER_TIMESTAMP)
203 max_pwd_age_ticks = days_to_timestamp(max_pwd_age)
204 m["msDS-MaximumPasswordAge"] = ldb.MessageElement(max_pwd_age_ticks,
205 ldb_oper, "msDS-MaximumPasswordAge")
207 if lockout_duration is not None:
208 lockout_duration_ticks = mins_to_timestamp(lockout_duration)
209 m["msDS-LockoutDuration"] = ldb.MessageElement(lockout_duration_ticks,
210 ldb_oper, "msDS-LockoutDuration")
212 if lockout_threshold is not None:
213 m["msDS-LockoutThreshold"] = ldb.MessageElement(str(lockout_threshold),
214 ldb_oper, "msDS-LockoutThreshold")
216 if reset_account_lockout_after is not None:
217 observation_window_ticks = mins_to_timestamp(reset_account_lockout_after)
218 m["msDS-LockoutObservationWindow"] = ldb.MessageElement(observation_window_ticks,
219 ldb_oper, "msDS-LockoutObservationWindow")
223 def check_pso_constraints(min_pwd_length=None, history_length=None,
224 min_pwd_age=None, max_pwd_age=None):
225 """Checks PSO settings fall within valid ranges"""
227 # check values as per section 3.1.1.5.2.2 Constraints in MS-ADTS spec
228 if history_length is not None and history_length > 1024:
229 raise CommandError("Bad password history length: valid range is 0 to 1024")
231 if min_pwd_length is not None and min_pwd_length > 255:
232 raise CommandError("Bad minimum password length: valid range is 0 to 255")
234 if min_pwd_age is not None and max_pwd_age is not None:
235 # note max-age=zero is a special case meaning 'never expire'
236 if min_pwd_age >= max_pwd_age and max_pwd_age != 0:
237 raise CommandError("Minimum password age must be less than the maximum age")
240 # the same args are used for both create and set commands
241 pwd_settings_options = [
242 Option("--complexity", type="choice", choices=["on","off"],
243 help="The password complexity (on | off)."),
244 Option("--store-plaintext", type="choice", choices=["on","off"],
245 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off)."),
246 Option("--history-length",
247 help="The password history length (<integer>).", type=int),
248 Option("--min-pwd-length",
249 help="The minimum password length (<integer>).", type=int),
250 Option("--min-pwd-age",
251 help="The minimum password age (<integer in days>). Default is domain setting.", type=int),
252 Option("--max-pwd-age",
253 help="The maximum password age (<integer in days>). Default is domain setting.", type=int),
254 Option("--account-lockout-duration",
255 help="The the length of time an account is locked out after exeeding the limit on bad password attempts (<integer in mins>). Default is domain setting", type=int),
256 Option("--account-lockout-threshold",
257 help="The number of bad password attempts allowed before locking out the account (<integer>). Default is domain setting.", type=int),
258 Option("--reset-account-lockout-after",
259 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer in mins>). Default is domain setting.", type=int)]
261 def num_options_in_args(options, args):
263 Returns the number of options specified that are present in the args.
264 (There can be other args besides just the ones we're interested in, which
265 is why argc on its own is not enough)
270 # The option should be a sub-string of the CLI argument for a match
275 class cmd_domain_pwdsettings_pso_create(Command):
276 """Creates a new Password Settings Object (PSO).
278 PSOs are a way to tailor different password settings (lockout policy,
279 minimum password length, etc) for specific users or groups.
281 The psoname is a unique name for the new Password Settings Object.
282 When multiple PSOs apply to a user, the precedence determines which PSO
283 will take effect. The PSO with the lowest precedence will take effect.
285 For most arguments, the default value (if unspecified) is the current
286 domain passwordsettings value. To see these values, enter the command
287 'samba-tool domain passwordsettings show'.
289 To apply the new PSO to user(s) or group(s), enter the command
290 'samba-tool domain passwordsettings pso apply'.
293 synopsis = "%prog <psoname> <precedence> [options]"
295 takes_optiongroups = {
296 "sambaopts": options.SambaOptions,
297 "versionopts": options.VersionOptions,
298 "credopts": options.CredentialsOptions,
301 takes_options = pwd_settings_options + [
302 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
303 metavar="URL", dest="H")
305 takes_args = ["psoname", "precedence"]
307 def run(self, psoname, precedence, H=None, min_pwd_age=None,
308 max_pwd_age=None, complexity=None, store_plaintext=None,
309 history_length=None, min_pwd_length=None,
310 account_lockout_duration=None, account_lockout_threshold=None,
311 reset_account_lockout_after=None, credopts=None, sambaopts=None,
313 lp = sambaopts.get_loadparm()
314 creds = credopts.get_credentials(lp)
316 samdb = SamDB(url=H, session_info=system_session(),
317 credentials=creds, lp=lp)
320 precedence = int(precedence)
322 raise CommandError("The PSO's precedence should be a numerical value. Try --help")
324 # sanity-check that the PSO doesn't already exist
325 pso_dn = "CN=%s,%s" % (psoname, pso_container(samdb))
327 res = samdb.search(pso_dn, scope=ldb.SCOPE_BASE)
328 except ldb.LdbError as e:
329 if e.args[0] == ldb.ERR_NO_SUCH_OBJECT:
334 raise CommandError("PSO '%s' already exists" % psoname)
336 # we expect the user to specify at least one password-policy setting,
337 # otherwise there's no point in creating a PSO
338 num_pwd_args = num_options_in_args(pwd_settings_options, self.raw_argv)
339 if num_pwd_args == 0:
340 raise CommandError("Please specify at least one password policy setting. Try --help")
342 # it's unlikely that the user will specify all 9 password policy
343 # settings on the CLI - current domain password-settings as the default
344 # values for unspecified arguments
345 if num_pwd_args < len(pwd_settings_options):
346 self.message("Not all password policy options have been specified.")
347 self.message("For unspecified options, the current domain password settings will be used as the default values.")
349 # lookup the current domain password-settings
350 res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_BASE,
351 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
352 "minPwdAge", "maxPwdAge", "lockoutDuration",
353 "lockoutThreshold", "lockOutObservationWindow"])
354 assert(len(res) == 1)
356 # use the domain settings for any missing arguments
357 pwd_props = int(res[0]["pwdProperties"][0])
358 if complexity is None:
359 prop_flag = DOMAIN_PASSWORD_COMPLEX
360 complexity = "on" if pwd_props & prop_flag else "off"
362 if store_plaintext is None:
363 prop_flag = DOMAIN_PASSWORD_STORE_CLEARTEXT
364 store_plaintext = "on" if pwd_props & prop_flag else "off"
366 if history_length is None:
367 history_length = int(res[0]["pwdHistoryLength"][0])
369 if min_pwd_length is None:
370 min_pwd_length = int(res[0]["minPwdLength"][0])
372 if min_pwd_age is None:
373 min_pwd_age = timestamp_to_days(res[0]["minPwdAge"][0])
375 if max_pwd_age is None:
376 max_pwd_age = timestamp_to_days(res[0]["maxPwdAge"][0])
378 if account_lockout_duration is None:
379 account_lockout_duration = \
380 timestamp_to_mins(res[0]["lockoutDuration"][0])
382 if account_lockout_threshold is None:
383 account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
385 if reset_account_lockout_after is None:
386 reset_account_lockout_after = \
387 timestamp_to_mins(res[0]["lockOutObservationWindow"][0])
389 check_pso_constraints(max_pwd_age=max_pwd_age, min_pwd_age=min_pwd_age,
390 history_length=history_length,
391 min_pwd_length=min_pwd_length)
393 # pack the settings into an LDB message
394 m = make_pso_ldb_msg(self.outf, samdb, pso_dn, create=True,
395 complexity=complexity, precedence=precedence,
396 store_plaintext=store_plaintext,
397 history_length=history_length,
398 min_pwd_length=min_pwd_length,
399 min_pwd_age=min_pwd_age, max_pwd_age=max_pwd_age,
400 lockout_duration=account_lockout_duration,
401 lockout_threshold=account_lockout_threshold,
402 reset_account_lockout_after=reset_account_lockout_after)
407 self.message("PSO successfully created: %s" % pso_dn)
408 # display the new PSO's settings
409 show_pso_by_dn(self.outf, samdb, pso_dn, show_applies_to=False)
410 except ldb.LdbError as e:
412 if num == ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS:
413 raise CommandError("Administrator permissions are needed to create a PSO.")
415 raise CommandError("Failed to create PSO '%s': %s" %(pso_dn, msg))
417 class cmd_domain_pwdsettings_pso_set(Command):
418 """Modifies a Password Settings Object (PSO)."""
420 synopsis = "%prog <psoname> [options]"
422 takes_optiongroups = {
423 "sambaopts": options.SambaOptions,
424 "versionopts": options.VersionOptions,
425 "credopts": options.CredentialsOptions,
428 takes_options = pwd_settings_options + [
429 Option("--precedence", type=int,
430 help="This PSO's precedence relative to other PSOs. Lower precedence is better (<integer>)."),
431 Option("-H", "--URL", help="LDB URL for database or target server",
432 type=str, metavar="URL", dest="H"),
434 takes_args = ["psoname"]
436 def run(self, psoname, H=None, precedence=None, min_pwd_age=None,
437 max_pwd_age=None, complexity=None, store_plaintext=None,
438 history_length=None, min_pwd_length=None,
439 account_lockout_duration=None, account_lockout_threshold=None,
440 reset_account_lockout_after=None, credopts=None, sambaopts=None,
442 lp = sambaopts.get_loadparm()
443 creds = credopts.get_credentials(lp)
445 samdb = SamDB(url=H, session_info=system_session(),
446 credentials=creds, lp=lp)
448 # sanity-check the PSO exists
449 pso_dn = "CN=%s,%s" % (psoname, pso_container(samdb))
450 check_pso_valid(samdb, pso_dn, psoname)
452 # we expect the user to specify at least one password-policy setting
453 num_pwd_args = num_options_in_args(pwd_settings_options, self.raw_argv)
454 if num_pwd_args == 0 and precedence is None:
455 raise CommandError("Please specify at least one password policy setting. Try --help")
457 if min_pwd_age is not None or max_pwd_age is not None:
458 # if we're modifying either the max or min pwd-age, check the max is
459 # always larger. We may have to fetch the PSO's setting to verify this
460 res = samdb.search(pso_dn, scope=ldb.SCOPE_BASE,
461 attrs=['msDS-MinimumPasswordAge',
462 'msDS-MaximumPasswordAge'])
463 if min_pwd_age is None:
464 min_pwd_age = timestamp_to_days(res[0]['msDS-MinimumPasswordAge'][0])
466 if max_pwd_age is None:
467 max_pwd_age = timestamp_to_days(res[0]['msDS-MaximumPasswordAge'][0])
469 check_pso_constraints(max_pwd_age=max_pwd_age, min_pwd_age=min_pwd_age,
470 history_length=history_length,
471 min_pwd_length=min_pwd_length)
473 # pack the settings into an LDB message
474 m = make_pso_ldb_msg(self.outf, samdb, pso_dn, create=False,
475 complexity=complexity, precedence=precedence,
476 store_plaintext=store_plaintext,
477 history_length=history_length,
478 min_pwd_length=min_pwd_length,
479 min_pwd_age=min_pwd_age, max_pwd_age=max_pwd_age,
480 lockout_duration=account_lockout_duration,
481 lockout_threshold=account_lockout_threshold,
482 reset_account_lockout_after=reset_account_lockout_after)
487 self.message("Successfully updated PSO: %s" % pso_dn)
488 # display the new PSO's settings
489 show_pso_by_dn(self.outf, samdb, pso_dn, show_applies_to=False)
490 except ldb.LdbError as e:
492 raise CommandError("Failed to update PSO '%s': %s" %(pso_dn, msg))
495 class cmd_domain_pwdsettings_pso_delete(Command):
496 """Deletes a Password Settings Object (PSO)."""
498 synopsis = "%prog <psoname> [options]"
500 takes_optiongroups = {
501 "sambaopts": options.SambaOptions,
502 "versionopts": options.VersionOptions,
503 "credopts": options.CredentialsOptions,
507 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
508 metavar="URL", dest="H")
510 takes_args = ["psoname"]
512 def run(self, psoname, H=None, credopts=None, sambaopts=None,
514 lp = sambaopts.get_loadparm()
515 creds = credopts.get_credentials(lp)
517 samdb = SamDB(url=H, session_info=system_session(),
518 credentials=creds, lp=lp)
520 pso_dn = "CN=%s,%s" % (psoname, pso_container(samdb))
521 # sanity-check the PSO exists
522 check_pso_valid(samdb, pso_dn, psoname)
525 self.message("Deleted PSO %s" % psoname)
529 """Compares two PSO LDB search results"""
530 a_precedence = int(a['msDS-PasswordSettingsPrecedence'][0])
531 b_precedence = int(b['msDS-PasswordSettingsPrecedence'][0])
532 return a_precedence - b_precedence
534 class cmd_domain_pwdsettings_pso_list(Command):
535 """Lists all Password Settings Objects (PSOs)."""
537 synopsis = "%prog [options]"
539 takes_optiongroups = {
540 "sambaopts": options.SambaOptions,
541 "versionopts": options.VersionOptions,
542 "credopts": options.CredentialsOptions,
546 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
547 metavar="URL", dest="H")
550 def run(self, H=None, credopts=None, sambaopts=None, versionopts=None):
551 lp = sambaopts.get_loadparm()
552 creds = credopts.get_credentials(lp)
554 samdb = SamDB(url=H, session_info=system_session(),
555 credentials=creds, lp=lp)
557 res = samdb.search(pso_container(samdb), scope=ldb.SCOPE_SUBTREE,
558 attrs=['name', 'msDS-PasswordSettingsPrecedence'],
559 expression="(objectClass=msDS-PasswordSettings)")
561 # an unprivileged search against Windows returns nothing here. On Samba
562 # we get the PSO names, but not their attributes
563 if len(res) == 0 or 'msDS-PasswordSettingsPrecedence' not in res[0]:
564 self.outf.write("No PSOs are present, or you don't have permission to view them.\n")
567 # sort the PSOs so they're displayed in order of precedence
568 pso_list = sorted(res, cmp=pso_cmp)
570 self.outf.write("Precedence | PSO name\n")
571 self.outf.write("--------------------------------------------------\n")
574 precedence = pso['msDS-PasswordSettingsPrecedence']
575 self.outf.write("%-10s | %s\n" %(precedence, pso['name']))
577 class cmd_domain_pwdsettings_pso_show(Command):
578 """Display a Password Settings Object's details."""
580 synopsis = "%prog <psoname> [options]"
582 takes_optiongroups = {
583 "sambaopts": options.SambaOptions,
584 "versionopts": options.VersionOptions,
585 "credopts": options.CredentialsOptions,
589 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
590 metavar="URL", dest="H")
592 takes_args = ["psoname"]
594 def run(self, psoname, H=None, credopts=None, sambaopts=None,
596 lp = sambaopts.get_loadparm()
597 creds = credopts.get_credentials(lp)
599 samdb = SamDB(url=H, session_info=system_session(),
600 credentials=creds, lp=lp)
602 pso_dn = "CN=%s,%s" % (psoname, pso_container(samdb))
603 check_pso_valid(samdb, pso_dn, psoname)
604 show_pso_by_dn(self.outf, samdb, pso_dn)
607 class cmd_domain_pwdsettings_pso_show_user(Command):
608 """Displays the Password Settings that apply to a user."""
610 synopsis = "%prog <username> [options]"
612 takes_optiongroups = {
613 "sambaopts": options.SambaOptions,
614 "versionopts": options.VersionOptions,
615 "credopts": options.CredentialsOptions,
619 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
620 metavar="URL", dest="H")
622 takes_args = ["username"]
624 def run(self, username, H=None, credopts=None, sambaopts=None,
626 lp = sambaopts.get_loadparm()
627 creds = credopts.get_credentials(lp)
629 samdb = SamDB(url=H, session_info=system_session(),
630 credentials=creds, lp=lp)
632 show_pso_for_user(self.outf, samdb, username)
635 class cmd_domain_pwdsettings_pso_apply(Command):
636 """Applies a PSO's password policy to a user or group.
638 When a PSO is applied to a group, it will apply to all users (and groups)
639 that are members of that group. If a PSO applies directly to a user, it
640 will override any group membership PSOs for that user.
642 When multiple PSOs apply to a user, either directly or through group
643 membership, the PSO with the lowest precedence will take effect.
646 synopsis = "%prog <psoname> <user-or-group-name> [options]"
648 takes_optiongroups = {
649 "sambaopts": options.SambaOptions,
650 "versionopts": options.VersionOptions,
651 "credopts": options.CredentialsOptions,
655 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
656 metavar="URL", dest="H")
658 takes_args = ["psoname", "user_or_group"]
660 def run(self, psoname, user_or_group, H=None, credopts=None,
661 sambaopts=None, versionopts=None):
662 lp = sambaopts.get_loadparm()
663 creds = credopts.get_credentials(lp)
665 samdb = SamDB(url=H, session_info=system_session(),
666 credentials=creds, lp=lp)
668 pso_dn = "CN=%s,%s" % (psoname, pso_container(samdb))
669 # sanity-check the PSO exists
670 check_pso_valid(samdb, pso_dn, psoname)
672 # lookup the user/group by account-name to gets its DN
673 search_filter = "(sAMAccountName=%s)" % user_or_group
674 res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
675 expression=search_filter)
678 raise CommandError("The specified user or group '%s' was not found"
681 # modify the PSO to apply to the user/group specified
682 target_dn = str(res[0].dn)
684 m.dn = ldb.Dn(samdb, pso_dn)
685 m["msDS-PSOAppliesTo"] = ldb.MessageElement(target_dn, ldb.FLAG_MOD_ADD,
689 except ldb.LdbError as e:
691 # most likely error - PSO already applies to that user/group
692 if num == ldb.ERR_ATTRIBUTE_OR_VALUE_EXISTS:
693 raise CommandError("PSO '%s' already applies to '%s'"
694 % (psoname, user_or_group))
696 raise CommandError("Failed to update PSO '%s': %s" %(psoname,
699 self.message("PSO '%s' applied to '%s'" %(psoname, user_or_group))
702 class cmd_domain_pwdsettings_pso_unapply(Command):
703 """Updates a PSO to no longer apply to a user or group."""
705 synopsis = "%prog <psoname> <user-or-group-name> [options]"
707 takes_optiongroups = {
708 "sambaopts": options.SambaOptions,
709 "versionopts": options.VersionOptions,
710 "credopts": options.CredentialsOptions,
714 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
715 metavar="URL", dest="H"),
717 takes_args = ["psoname", "user_or_group"]
719 def run(self, psoname, user_or_group, H=None, credopts=None,
720 sambaopts=None, versionopts=None):
721 lp = sambaopts.get_loadparm()
722 creds = credopts.get_credentials(lp)
724 samdb = SamDB(url=H, session_info=system_session(),
725 credentials=creds, lp=lp)
727 pso_dn = "CN=%s,%s" % (psoname, pso_container(samdb))
728 # sanity-check the PSO exists
729 check_pso_valid(samdb, pso_dn, psoname)
731 # lookup the user/group by account-name to gets its DN
732 search_filter = "(sAMAccountName=%s)" % user_or_group
733 res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
734 expression=search_filter)
737 raise CommandError("The specified user or group '%s' was not found"
740 # modify the PSO to apply to the user/group specified
741 target_dn = str(res[0].dn)
743 m.dn = ldb.Dn(samdb, pso_dn)
744 m["msDS-PSOAppliesTo"] = ldb.MessageElement(target_dn, ldb.FLAG_MOD_DELETE,
748 except ldb.LdbError as e:
750 # most likely error - PSO doesn't apply to that user/group
751 if num == ldb.ERR_NO_SUCH_ATTRIBUTE:
752 raise CommandError("PSO '%s' doesn't apply to '%s'"
753 % (psoname, user_or_group))
755 raise CommandError("Failed to update PSO '%s': %s" %(psoname,
757 self.message("PSO '%s' no longer applies to '%s'" %(psoname, user_or_group))
759 class cmd_domain_passwordsettings_pso(SuperCommand):
760 """Manage fine-grained Password Settings Objects (PSOs)."""
763 subcommands["apply"] = cmd_domain_pwdsettings_pso_apply()
764 subcommands["create"] = cmd_domain_pwdsettings_pso_create()
765 subcommands["delete"] = cmd_domain_pwdsettings_pso_delete()
766 subcommands["list"] = cmd_domain_pwdsettings_pso_list()
767 subcommands["set"] = cmd_domain_pwdsettings_pso_set()
768 subcommands["show"] = cmd_domain_pwdsettings_pso_show()
769 subcommands["show-user"] = cmd_domain_pwdsettings_pso_show_user()
770 subcommands["unapply"] = cmd_domain_pwdsettings_pso_unapply()