s4-python: Move set_global_schema to pydsdb.
[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 glue
29 import ldb
30 from samba.idmap import IDmapDB
31 import pwd
32 import time
33 import base64
34
35 __docformat__ = "restructuredText"
36
37 class SamDB(samba.Ldb):
38     """The SAM database."""
39
40     def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
41                  credentials=None, flags=0, options=None, global_schema=False):
42         self.lp = lp
43         if url is None:
44             url = lp.get("sam database")
45
46         super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
47                 session_info=session_info, credentials=credentials, flags=flags,
48                 options=options)
49
50         if global_schema:
51             dsdb.dsdb_set_global_schema(self)
52
53     def connect(self, url=None, flags=0, options=None):
54         super(SamDB, self).connect(url=self.lp.private_path(url), flags=flags,
55                 options=options)
56
57     def domain_dn(self):
58         # find the DNs for the domain
59         res = self.search(base="",
60                           scope=ldb.SCOPE_BASE,
61                           expression="(defaultNamingContext=*)",
62                           attrs=["defaultNamingContext"])
63         assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
64         return res[0]["defaultNamingContext"][0]
65
66     def enable_account(self, filter):
67         """Enables an account
68         
69         :param filter: LDAP filter to find the user (eg samccountname=name)
70         """
71         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
72                           expression=filter, attrs=["userAccountControl"])
73         assert(len(res) == 1)
74         user_dn = res[0].dn
75
76         userAccountControl = int(res[0]["userAccountControl"][0])
77         if (userAccountControl & 0x2):
78             userAccountControl = userAccountControl & ~0x2 # remove disabled bit
79         if (userAccountControl & 0x20):
80             userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit
81
82         mod = """
83 dn: %s
84 changetype: modify
85 replace: userAccountControl
86 userAccountControl: %u
87 """ % (user_dn, userAccountControl)
88         self.modify_ldif(mod)
89         
90     def force_password_change_at_next_login(self, filter):
91         """Forces a password change at next login
92         
93         :param filter: LDAP filter to find the user (eg samccountname=name)
94         """
95         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
96                           expression=filter, attrs=[])
97         assert(len(res) == 1)
98         user_dn = res[0].dn
99
100         mod = """
101 dn: %s
102 changetype: modify
103 replace: pwdLastSet
104 pwdLastSet: 0
105 """ % (user_dn)
106         self.modify_ldif(mod)
107
108     def newuser(self, username, unixname, password,
109                 force_password_change_at_next_login_req=False):
110         """Adds a new user
111
112         Note: This call adds also the ID mapping for winbind; therefore it works
113         *only* on SAMBA 4.
114         
115         :param username: Name of the new user
116         :param unixname: Name of the unix user to map to
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             # Gets the user SID (for the account mapping setup)
135             res = self.search(user_dn, scope=ldb.SCOPE_BASE,
136                               expression="objectclass=*",
137                               attrs=["objectSid"])
138             assert len(res) == 1
139             user_sid = self.schema_format_value("objectSid", res[0]["objectSid"][0])
140             
141             try:
142                 idmap = IDmapDB(lp=self.lp)
143
144                 user = pwd.getpwnam(unixname)
145
146                 # setup ID mapping for this UID
147                 idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2])
148
149             except KeyError:
150                 pass
151         except:
152             self.transaction_cancel()
153             raise
154         self.transaction_commit()
155
156     def setpassword(self, filter, password, force_change_at_next_login=False):
157         """Sets the password for a user
158         
159         Note: This call uses the "userPassword" attribute to set the password.
160         This works correctly on SAMBA 4 and on Windows DCs with
161         "2003 Native" or higer domain function level.
162
163         :param filter: LDAP filter to find the user (eg samccountname=name)
164         :param password: Password for the user
165         :param force_change_at_next_login: Force password change
166         """
167         self.transaction_start()
168         try:
169             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
170                               expression=filter, attrs=[])
171             assert(len(res) == 1)
172             user_dn = res[0].dn
173
174             setpw = """
175 dn: %s
176 changetype: modify
177 replace: userPassword
178 userPassword:: %s
179 """ % (user_dn, base64.b64encode(password))
180
181             self.modify_ldif(setpw)
182
183             if force_change_at_next_login:
184                 self.force_password_change_at_next_login(
185                   "(dn=" + str(user_dn) + ")")
186
187             #  modify the userAccountControl to remove the disabled bit
188             self.enable_account(filter)
189         except:
190             self.transaction_cancel()
191             raise
192         self.transaction_commit()
193
194     def setexpiry(self, filter, expiry_seconds, no_expiry_req=False):
195         """Sets the account expiry for a user
196         
197         :param filter: LDAP filter to find the user (eg samccountname=name)
198         :param expiry_seconds: expiry time from now in seconds
199         :param no_expiry_req: if set, then don't expire password
200         """
201         self.transaction_start()
202         try:
203             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
204                           expression=filter,
205                           attrs=["userAccountControl", "accountExpires"])
206             assert(len(res) == 1)
207             user_dn = res[0].dn
208
209             userAccountControl = int(res[0]["userAccountControl"][0])
210             accountExpires     = int(res[0]["accountExpires"][0])
211             if no_expiry_req:
212                 userAccountControl = userAccountControl | 0x10000
213                 accountExpires = 0
214             else:
215                 userAccountControl = userAccountControl & ~0x10000
216                 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
217
218             setexp = """
219 dn: %s
220 changetype: modify
221 replace: userAccountControl
222 userAccountControl: %u
223 replace: accountExpires
224 accountExpires: %u
225 """ % (user_dn, userAccountControl, accountExpires)
226
227             self.modify_ldif(setexp)
228         except:
229             self.transaction_cancel()
230             raise
231         self.transaction_commit()
232
233     def set_domain_sid(self, sid):
234         """Change the domain SID used by this LDB.
235
236         :param sid: The new domain sid to use.
237         """
238         dsdb.samdb_set_domain_sid(self, sid)
239
240     def get_domain_sid(self):
241         """Read the domain SID used by this LDB.
242
243         """
244         dsdb.samdb_get_domain_sid(self)
245
246     def set_invocation_id(self, invocation_id):
247         """Set the invocation id for this SamDB handle.
248
249         :param invocation_id: GUID of the invocation id.
250         """
251         dsdb.dsdb_set_ntds_invocation_id(self, invocation_id)
252
253     def get_invocation_id(self):
254         "Get the invocation_id id"
255         return dsdb.samdb_ntds_invocation_id(self)
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 server_site_name(self):
266         "Get the server site name"
267         return dsdb.samdb_server_site_name(self)