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