s4-rodc: Fix provision warnings by creating ntds objectGUID in provision
[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         self.lp = lp
42         if not auto_connect:
43             url = None
44         elif url is None and lp is not None:
45             url = lp.get("sam database")
46
47         super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
48                 session_info=session_info, credentials=credentials, flags=flags,
49                 options=options)
50
51         if global_schema:
52             dsdb.dsdb_set_global_schema(self)
53
54     def connect(self, url=None, flags=0, options=None):
55         if self.lp is not None:
56             url = self.lp.private_path(url)
57
58         super(SamDB, self).connect(url=url, flags=flags,
59                 options=options)
60
61     def domain_dn(self):
62         # find the DNs for the domain
63         res = self.search(base="",
64                           scope=ldb.SCOPE_BASE,
65                           expression="(defaultNamingContext=*)",
66                           attrs=["defaultNamingContext"])
67         assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
68         return res[0]["defaultNamingContext"][0]
69
70     def enable_account(self, filter):
71         """Enables an account
72         
73         :param filter: LDAP filter to find the user (eg samccountname=name)
74         """
75         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
76                           expression=filter, attrs=["userAccountControl"])
77         assert(len(res) == 1)
78         user_dn = res[0].dn
79
80         userAccountControl = int(res[0]["userAccountControl"][0])
81         if (userAccountControl & 0x2):
82             userAccountControl = userAccountControl & ~0x2 # remove disabled bit
83         if (userAccountControl & 0x20):
84             userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit
85
86         mod = """
87 dn: %s
88 changetype: modify
89 replace: userAccountControl
90 userAccountControl: %u
91 """ % (user_dn, userAccountControl)
92         self.modify_ldif(mod)
93         
94     def force_password_change_at_next_login(self, filter):
95         """Forces a password change at next login
96         
97         :param filter: LDAP filter to find the user (eg samccountname=name)
98         """
99         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
100                           expression=filter, attrs=[])
101         assert(len(res) == 1)
102         user_dn = res[0].dn
103
104         mod = """
105 dn: %s
106 changetype: modify
107 replace: pwdLastSet
108 pwdLastSet: 0
109 """ % (user_dn)
110         self.modify_ldif(mod)
111
112     def newuser(self, username, password,
113                 force_password_change_at_next_login_req=False):
114         """Adds a new user
115
116         :param username: Name of the new user
117         :param password: Password for the new user
118         :param force_password_change_at_next_login_req: Force password change
119         """
120         self.transaction_start()
121         try:
122             user_dn = "CN=%s,CN=Users,%s" % (username, self.domain_dn())
123
124             # The new user record. Note the reliance on the SAMLDB module which
125             # fills in the default informations
126             self.add({"dn": user_dn, 
127                 "sAMAccountName": username,
128                 "objectClass": "user"})
129
130             # Sets the password for it
131             self.setpassword("(dn=" + user_dn + ")", password,
132               force_password_change_at_next_login_req)
133
134         except:
135             self.transaction_cancel()
136             raise
137         else:
138             self.transaction_commit()
139
140     def setpassword(self, filter, password,
141                     force_change_at_next_login=False,
142                     username=None):
143         """Sets the password for a user
144         
145         Note: This call uses the "userPassword" attribute to set the password.
146         This works correctly on SAMBA 4 and on Windows DCs with
147         "2003 Native" or higer domain function level.
148
149         :param filter: LDAP filter to find the user (eg samccountname=name)
150         :param password: Password for the user
151         :param force_change_at_next_login: Force password change
152         """
153         self.transaction_start()
154         try:
155             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
156                               expression=filter, attrs=[])
157             if len(res) == 0:
158                 print('Unable to find user "%s"' % (username or filter))
159                 raise
160             assert(len(res) == 1)
161             user_dn = res[0].dn
162
163             setpw = """
164 dn: %s
165 changetype: modify
166 replace: userPassword
167 userPassword:: %s
168 """ % (user_dn, base64.b64encode(password))
169
170             self.modify_ldif(setpw)
171
172             if force_change_at_next_login:
173                 self.force_password_change_at_next_login(
174                   "(dn=" + str(user_dn) + ")")
175
176             #  modify the userAccountControl to remove the disabled bit
177             self.enable_account(filter)
178         except:
179             self.transaction_cancel()
180             raise
181         else:
182             self.transaction_commit()
183
184     def setexpiry(self, filter, expiry_seconds, no_expiry_req=False):
185         """Sets the account expiry for a user
186         
187         :param filter: LDAP filter to find the user (eg samccountname=name)
188         :param expiry_seconds: expiry time from now in seconds
189         :param no_expiry_req: if set, then don't expire password
190         """
191         self.transaction_start()
192         try:
193             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
194                           expression=filter,
195                           attrs=["userAccountControl", "accountExpires"])
196             assert(len(res) == 1)
197             user_dn = res[0].dn
198
199             userAccountControl = int(res[0]["userAccountControl"][0])
200             accountExpires     = int(res[0]["accountExpires"][0])
201             if no_expiry_req:
202                 userAccountControl = userAccountControl | 0x10000
203                 accountExpires = 0
204             else:
205                 userAccountControl = userAccountControl & ~0x10000
206                 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
207
208             setexp = """
209 dn: %s
210 changetype: modify
211 replace: userAccountControl
212 userAccountControl: %u
213 replace: accountExpires
214 accountExpires: %u
215 """ % (user_dn, userAccountControl, accountExpires)
216
217             self.modify_ldif(setexp)
218         except:
219             self.transaction_cancel()
220             raise
221         else:
222             self.transaction_commit()
223
224     def set_domain_sid(self, sid):
225         """Change the domain SID used by this LDB.
226
227         :param sid: The new domain sid to use.
228         """
229         dsdb.samdb_set_domain_sid(self, sid)
230
231     def get_domain_sid(self):
232         """Read the domain SID used by this LDB.
233
234         """
235         dsdb.samdb_get_domain_sid(self)
236
237     def set_invocation_id(self, invocation_id):
238         """Set the invocation id for this SamDB handle.
239
240         :param invocation_id: GUID of the invocation id.
241         """
242         dsdb.dsdb_set_ntds_invocation_id(self, invocation_id)
243
244     def get_invocation_id(self):
245         "Get the invocation_id id"
246         return dsdb.samdb_ntds_invocation_id(self)
247
248     def set_ntds_settings_dn(self, ntds_settings_dn):
249         """Set the NTDS Settings DN, as would be returned on the dsServiceName rootDSE attribute
250
251         This allows the DN to be set before the database fully exists
252
253         :param ntds_settings_dn: The new DN to use
254         """
255         dsdb.samdb_set_ntds_settings_dn(self, ntds_settings_dn)
256
257     invocation_id = property(get_invocation_id, set_invocation_id)
258
259     domain_sid = property(get_domain_sid, set_domain_sid)
260
261     def get_ntds_GUID(self):
262         "Get the NTDS objectGUID"
263         return dsdb.samdb_ntds_objectGUID(self)
264
265     def set_ntds_GUID(self, object_guid):
266         "Set the NTDS objectGUID"
267         return dsdb.dsdb_set_ntds_objectGUID(self, object_guid)
268
269     def server_site_name(self):
270         "Get the server site name"
271         return dsdb.samdb_server_site_name(self)
272
273     def load_partition_usn(self, base_dn):
274         return dsdb.dsdb_load_partition_usn(self, base_dn)