s4-rodc: Set am_rodc flag during provision
[kamenim/samba.git] / source4 / scripting / python / samba / samdb.py
1 #!/usr/bin/python
2
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
5 # Copyright (C) Matthias Dieter Wallnoefer 2009
6 #
7 # Based on the original in EJS:
8 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
9 #   
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #   
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #   
20 # You should have received a copy of the GNU General Public License
21 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 #
23
24 """Convenience functions for using the SAM."""
25
26 import dsdb
27 import samba
28 import ldb
29 from samba.idmap import IDmapDB
30 import pwd
31 import time
32 import base64
33
34 __docformat__ = "restructuredText"
35
36 class SamDB(samba.Ldb):
37     """The SAM database."""
38
39     def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
40                  credentials=None, flags=0, options=None, global_schema=True, auto_connect=True,
41                  am_rodc=False):
42         self.lp = lp
43         if not auto_connect:
44             url = None
45         elif url is None and lp is not None:
46             url = lp.get("sam database")
47
48         super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
49                 session_info=session_info, credentials=credentials, flags=flags,
50                 options=options)
51
52         if global_schema:
53             dsdb.dsdb_set_global_schema(self)
54
55         dsdb.dsdb_set_am_rodc(self, am_rodc)
56
57     def connect(self, url=None, flags=0, options=None):
58         if self.lp is not None:
59             url = self.lp.private_path(url)
60
61         super(SamDB, self).connect(url=url, flags=flags,
62                 options=options)
63
64     def domain_dn(self):
65         # find the DNs for the domain
66         res = self.search(base="",
67                           scope=ldb.SCOPE_BASE,
68                           expression="(defaultNamingContext=*)",
69                           attrs=["defaultNamingContext"])
70         assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
71         return res[0]["defaultNamingContext"][0]
72
73     def enable_account(self, filter):
74         """Enables an account
75         
76         :param filter: LDAP filter to find the user (eg samccountname=name)
77         """
78         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
79                           expression=filter, attrs=["userAccountControl"])
80         assert(len(res) == 1)
81         user_dn = res[0].dn
82
83         userAccountControl = int(res[0]["userAccountControl"][0])
84         if (userAccountControl & 0x2):
85             userAccountControl = userAccountControl & ~0x2 # remove disabled bit
86         if (userAccountControl & 0x20):
87             userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit
88
89         mod = """
90 dn: %s
91 changetype: modify
92 replace: userAccountControl
93 userAccountControl: %u
94 """ % (user_dn, userAccountControl)
95         self.modify_ldif(mod)
96         
97     def force_password_change_at_next_login(self, filter):
98         """Forces a password change at next login
99         
100         :param filter: LDAP filter to find the user (eg samccountname=name)
101         """
102         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
103                           expression=filter, attrs=[])
104         assert(len(res) == 1)
105         user_dn = res[0].dn
106
107         mod = """
108 dn: %s
109 changetype: modify
110 replace: pwdLastSet
111 pwdLastSet: 0
112 """ % (user_dn)
113         self.modify_ldif(mod)
114
115     def newuser(self, username, password,
116                 force_password_change_at_next_login_req=False):
117         """Adds a new user
118
119         :param username: Name of the new user
120         :param password: Password for the new user
121         :param force_password_change_at_next_login_req: Force password change
122         """
123         self.transaction_start()
124         try:
125             user_dn = "CN=%s,CN=Users,%s" % (username, self.domain_dn())
126
127             # The new user record. Note the reliance on the SAMLDB module which
128             # fills in the default informations
129             self.add({"dn": user_dn, 
130                 "sAMAccountName": username,
131                 "objectClass": "user"})
132
133             # Sets the password for it
134             self.setpassword("(dn=" + user_dn + ")", password,
135               force_password_change_at_next_login_req)
136
137         except:
138             self.transaction_cancel()
139             raise
140         else:
141             self.transaction_commit()
142
143     def setpassword(self, filter, password,
144                     force_change_at_next_login=False,
145                     username=None):
146         """Sets the password for a user
147         
148         Note: This call uses the "userPassword" attribute to set the password.
149         This works correctly on SAMBA 4 and on Windows DCs with
150         "2003 Native" or higer domain function level.
151
152         :param filter: LDAP filter to find the user (eg samccountname=name)
153         :param password: Password for the user
154         :param force_change_at_next_login: Force password change
155         """
156         self.transaction_start()
157         try:
158             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
159                               expression=filter, attrs=[])
160             if len(res) == 0:
161                 print('Unable to find user "%s"' % (username or filter))
162                 raise
163             assert(len(res) == 1)
164             user_dn = res[0].dn
165
166             setpw = """
167 dn: %s
168 changetype: modify
169 replace: userPassword
170 userPassword:: %s
171 """ % (user_dn, base64.b64encode(password))
172
173             self.modify_ldif(setpw)
174
175             if force_change_at_next_login:
176                 self.force_password_change_at_next_login(
177                   "(dn=" + str(user_dn) + ")")
178
179             #  modify the userAccountControl to remove the disabled bit
180             self.enable_account(filter)
181         except:
182             self.transaction_cancel()
183             raise
184         else:
185             self.transaction_commit()
186
187     def setexpiry(self, filter, expiry_seconds, no_expiry_req=False):
188         """Sets the account expiry for a user
189         
190         :param filter: LDAP filter to find the user (eg samccountname=name)
191         :param expiry_seconds: expiry time from now in seconds
192         :param no_expiry_req: if set, then don't expire password
193         """
194         self.transaction_start()
195         try:
196             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
197                           expression=filter,
198                           attrs=["userAccountControl", "accountExpires"])
199             assert(len(res) == 1)
200             user_dn = res[0].dn
201
202             userAccountControl = int(res[0]["userAccountControl"][0])
203             accountExpires     = int(res[0]["accountExpires"][0])
204             if no_expiry_req:
205                 userAccountControl = userAccountControl | 0x10000
206                 accountExpires = 0
207             else:
208                 userAccountControl = userAccountControl & ~0x10000
209                 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
210
211             setexp = """
212 dn: %s
213 changetype: modify
214 replace: userAccountControl
215 userAccountControl: %u
216 replace: accountExpires
217 accountExpires: %u
218 """ % (user_dn, userAccountControl, accountExpires)
219
220             self.modify_ldif(setexp)
221         except:
222             self.transaction_cancel()
223             raise
224         else:
225             self.transaction_commit()
226
227     def set_domain_sid(self, sid):
228         """Change the domain SID used by this LDB.
229
230         :param sid: The new domain sid to use.
231         """
232         dsdb.samdb_set_domain_sid(self, sid)
233
234     def get_domain_sid(self):
235         """Read the domain SID used by this LDB.
236
237         """
238         dsdb.samdb_get_domain_sid(self)
239
240     def set_invocation_id(self, invocation_id):
241         """Set the invocation id for this SamDB handle.
242
243         :param invocation_id: GUID of the invocation id.
244         """
245         dsdb.dsdb_set_ntds_invocation_id(self, invocation_id)
246
247     def get_invocation_id(self):
248         "Get the invocation_id id"
249         return dsdb.samdb_ntds_invocation_id(self)
250
251     def set_ntds_settings_dn(self, ntds_settings_dn):
252         """Set the NTDS Settings DN, as would be returned on the dsServiceName rootDSE attribute
253
254         This allows the DN to be set before the database fully exists
255
256         :param ntds_settings_dn: The new DN to use
257         """
258         dsdb.samdb_set_ntds_settings_dn(self, ntds_settings_dn)
259
260     invocation_id = property(get_invocation_id, set_invocation_id)
261
262     domain_sid = property(get_domain_sid, set_domain_sid)
263
264     def get_ntds_GUID(self):
265         "Get the NTDS objectGUID"
266         return dsdb.samdb_ntds_objectGUID(self)
267
268     def server_site_name(self):
269         "Get the server site name"
270         return dsdb.samdb_server_site_name(self)
271
272     def load_partition_usn(self, base_dn):
273         return dsdb.dsdb_load_partition_usn(self, base_dn)