s4:samdb.py - further rework
[amitay/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=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         """
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)
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_password_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_password_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_password_change_at_next_login:
184                 self.force_password_change_at_next_login(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, noexpiry=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 noexpiry: 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 noexpiry:
211                 userAccountControl = userAccountControl | 0x10000
212                 accountExpires = 0
213             else:
214                 userAccountControl = userAccountControl & ~0x10000
215                 accountExpires = glue.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