Merge branch 'master' of git://git.samba.org/samba
[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 samba
27 import glue
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):
41         """Opens the SAM Database
42         For parameter meanings see the super class (samba.Ldb)
43         """
44
45         self.lp = lp
46         if url is None:
47                 url = lp.get("sam database")
48
49         super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
50                 session_info=session_info, credentials=credentials, flags=flags,
51                 options=options)
52
53         glue.dsdb_set_global_schema(self)
54
55     def connect(self, url=None, flags=0, options=None):
56         super(SamDB, self).connect(url=self.lp.private_path(url), flags=flags,
57                 options=options)
58
59     def domain_dn(self):
60         # find the DNs for the domain
61         res = self.search(base="",
62                           scope=ldb.SCOPE_BASE,
63                           expression="(defaultNamingContext=*)",
64                           attrs=["defaultNamingContext"])
65         assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
66         return res[0]["defaultNamingContext"][0]
67
68     def enable_account(self, filter):
69         """Enables an account
70         
71         :param filter: LDAP filter to find the user (eg samccountname=name)
72         """
73         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
74                           expression=filter, attrs=["userAccountControl"])
75         assert(len(res) == 1)
76         user_dn = res[0].dn
77
78         userAccountControl = int(res[0]["userAccountControl"][0])
79         if (userAccountControl & 0x2):
80             userAccountControl = userAccountControl & ~0x2 # remove disabled bit
81         if (userAccountControl & 0x20):
82             userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit
83
84         mod = """
85 dn: %s
86 changetype: modify
87 replace: userAccountControl
88 userAccountControl: %u
89 """ % (user_dn, userAccountControl)
90         self.modify_ldif(mod)
91         
92     def force_password_change_at_next_login(self, filter):
93         """Forces a password change at next login
94         
95         :param filter: LDAP filter to find the user (eg samccountname=name)
96         """
97         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
98                           expression=filter, attrs=[])
99         assert(len(res) == 1)
100         user_dn = res[0].dn
101
102         mod = """
103 dn: %s
104 changetype: modify
105 replace: pwdLastSet
106 pwdLastSet: 0
107 """ % (user_dn)
108         self.modify_ldif(mod)
109
110     def newuser(self, username, unixname, password, force_password_change_at_next_login_req=False):
111         """Adds a new user
112
113         Note: This call adds also the ID mapping for winbind; therefore it works
114         *only* on SAMBA 4.
115         
116         :param username: Name of the new user
117         :param unixname: Name of the unix user to map to
118         :param password: Password for the new user
119         :param force_password_change_at_next_login_req: Force password change
120         """
121         self.transaction_start()
122         try:
123             user_dn = "CN=%s,CN=Users,%s" % (username, self.domain_dn())
124
125             # The new user record. Note the reliance on the SAMLDB module which
126             # fills in the default informations
127             self.add({"dn": user_dn, 
128                 "sAMAccountName": username,
129                 "objectClass": "user"})
130
131             # Sets the password for it
132             self.setpassword("(dn=" + user_dn + ")", password,
133               force_password_change_at_next_login_req)
134
135             # Gets the user SID (for the account mapping setup)
136             res = self.search(user_dn, scope=ldb.SCOPE_BASE,
137                               expression="objectclass=*",
138                               attrs=["objectSid"])
139             assert len(res) == 1
140             user_sid = self.schema_format_value("objectSid", res[0]["objectSid"][0])
141             
142             try:
143                 idmap = IDmapDB(lp=self.lp)
144
145                 user = pwd.getpwnam(unixname)
146
147                 # setup ID mapping for this UID
148                 idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2])
149
150             except KeyError:
151                 pass
152         except:
153             self.transaction_cancel()
154             raise
155         self.transaction_commit()
156
157     def setpassword(self, filter, password, force_password_change_at_next_login_req=False):
158         """Sets the password for a user
159         
160         Note: This call uses the "userPassword" attribute to set the password.
161         This works correctly on SAMBA 4 and on Windows DCs with
162         "2003 Native" or higer domain function level.
163
164         :param filter: LDAP filter to find the user (eg samccountname=name)
165         :param password: Password for the user
166         :param force_password_change_at_next_login_req: Force password change
167         """
168         self.transaction_start()
169         try:
170             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
171                               expression=filter, attrs=[])
172             assert(len(res) == 1)
173             user_dn = res[0].dn
174
175             setpw = """
176 dn: %s
177 changetype: modify
178 replace: userPassword
179 userPassword:: %s
180 """ % (user_dn, base64.b64encode(password))
181
182             self.modify_ldif(setpw)
183
184             if force_password_change_at_next_login_req:
185                 self.force_password_change_at_next_login(
186                   "(dn=" + str(user_dn) + ")")
187
188             #  modify the userAccountControl to remove the disabled bit
189             self.enable_account(filter)
190         except:
191             self.transaction_cancel()
192             raise
193         self.transaction_commit()
194
195     def setexpiry(self, filter, expiry_seconds, no_expiry_req=False):
196         """Sets the account expiry for a user
197         
198         :param filter: LDAP filter to find the user (eg samccountname=name)
199         :param expiry_seconds: expiry time from now in seconds
200         :param no_expiry_req: if set, then don't expire password
201         """
202         self.transaction_start()
203         try:
204             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
205                               expression=filter,
206                               attrs=["userAccountControl", "accountExpires"])
207             assert(len(res) == 1)
208             user_dn = res[0].dn
209
210             userAccountControl = int(res[0]["userAccountControl"][0])
211             accountExpires     = int(res[0]["accountExpires"][0])
212             if no_expiry_req:
213                 userAccountControl = userAccountControl | 0x10000
214                 accountExpires = 0
215             else:
216                 userAccountControl = userAccountControl & ~0x10000
217                 accountExpires = glue.unix2nttime(expiry_seconds + int(time.time()))
218
219             setexp = """
220 dn: %s
221 changetype: modify
222 replace: userAccountControl
223 userAccountControl: %u
224 replace: accountExpires
225 accountExpires: %u
226 """ % (user_dn, userAccountControl, accountExpires)
227
228             self.modify_ldif(setexp)
229         except:
230             self.transaction_cancel()
231             raise
232         self.transaction_commit();
233