Merge branch 'master' of /home/tridge/samba/git/combined
[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 #
6 # Based on the original in EJS:
7 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
8 #   
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
13 #   
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #   
19 # You should have received a copy of the GNU General Public License
20 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 #
22
23 """Convenience functions for using the SAM."""
24
25 import samba
26 import glue
27 import ldb
28 from samba.idmap import IDmapDB
29 import pwd
30 import time
31 import base64
32
33 __docformat__ = "restructuredText"
34
35 class SamDB(samba.Ldb):
36     """The SAM database."""
37
38     def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
39                  credentials=None, flags=0, options=None):
40         """Opens the SAM Database
41         For parameter meanings see the super class (samba.Ldb)
42         """
43
44         self.lp = lp
45         if url is None:
46                 url = lp.get("sam database")
47
48         super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
49                 session_info=session_info, credentials=credentials, flags=flags,
50                 options=options)
51
52         glue.dsdb_set_global_schema(self)
53
54     def connect(self, url=None, flags=0, options=None):
55         super(SamDB, self).connect(url=self.lp.private_path(url), flags=flags,
56                 options=options)
57
58     def domain_dn(self):
59         # find the DNs for the domain
60         res = self.search(base="",
61                           scope=ldb.SCOPE_BASE,
62                           expression="(defaultNamingContext=*)",
63                           attrs=["defaultNamingContext"])
64         assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
65         return res[0]["defaultNamingContext"][0]
66
67     def enable_account(self, filter):
68         """Enables an account
69         
70         :param filter: LDAP filter to find the user (eg samccountname=name)
71         """
72         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
73                           expression=filter, attrs=["userAccountControl"])
74         assert(len(res) == 1)
75         user_dn = res[0].dn
76
77         userAccountControl = int(res[0]["userAccountControl"][0])
78         if (userAccountControl & 0x2):
79             userAccountControl = userAccountControl & ~0x2 # remove disabled bit
80         if (userAccountControl & 0x20):
81             userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit
82
83         mod = """
84 dn: %s
85 changetype: modify
86 replace: userAccountControl
87 userAccountControl: %u
88 """ % (user_dn, userAccountControl)
89         self.modify_ldif(mod)
90         
91     def force_password_change_at_next_login(self, filter):
92         """Forces a password change at next login
93         
94         :param filter: LDAP filter to find the user (eg samccountname=name)
95         """
96         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
97                           expression=filter, attrs=[])
98         assert(len(res) == 1)
99         user_dn = res[0].dn
100
101         mod = """
102 dn: %s
103 changetype: modify
104 replace: pwdLastSet
105 pwdLastSet: 0
106 """ % (user_dn)
107         self.modify_ldif(mod)
108
109     def newuser(self, username, unixname, password, force_password_change_at_next_login=False):
110         """Adds a new user
111
112         Note: This call uses the "userPassword" attribute to set the password.
113         This works correctly on SAMBA 4 DCs and on Windows DCs with
114         "2003 Native" or higer domain function level.
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         # connect to the sam 
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                 "userPassword": password,
130                 "objectClass": "user"})
131
132             res = self.search(user_dn, scope=ldb.SCOPE_BASE,
133                               expression="objectclass=*",
134                               attrs=["objectSid"])
135             assert len(res) == 1
136             user_sid = self.schema_format_value("objectSid", res[0]["objectSid"][0])
137             
138             try:
139                 idmap = IDmapDB(lp=self.lp)
140
141                 user = pwd.getpwnam(unixname)
142
143                 # setup ID mapping for this UID
144                 idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2])
145
146             except KeyError:
147                 pass
148
149             if force_password_change_at_next_login:
150                 self.force_password_change_at_next_login("(dn=" + user_dn + ")")
151
152             #  modify the userAccountControl to remove the disabled bit
153             self.enable_account("(dn=" + user_dn + ")")
154         except:
155             self.transaction_cancel()
156             raise
157         self.transaction_commit()
158
159     def setpassword(self, filter, password, force_password_change_at_next_login=False):
160         """Sets the password for a user
161         
162         Note: This call uses the "userPassword" attribute to set the password.
163         This works correctly on SAMBA 4 DCs and on Windows DCs with
164         "2003 Native" or higer domain function level.
165
166         :param filter: LDAP filter to find the user (eg samccountname=name)
167         :param password: Password for the user
168         :param force_password_change_at_next_login: Force password change
169         """
170         self.transaction_start()
171         try:
172             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
173                               expression=filter, attrs=[])
174             assert(len(res) == 1)
175             user_dn = res[0].dn
176
177             setpw = """
178 dn: %s
179 changetype: modify
180 replace: userPassword
181 userPassword:: %s
182 """ % (user_dn, base64.b64encode(password))
183
184             self.modify_ldif(setpw)
185
186             if force_password_change_at_next_login:
187                 self.force_password_change_at_next_login(user_dn)
188
189             #  modify the userAccountControl to remove the disabled bit
190             self.enable_account(filter)
191         except:
192             self.transaction_cancel()
193             raise
194         self.transaction_commit()
195
196     def setexpiry(self, filter, expiry_seconds, noexpiry=False):
197         """Sets the account expiry for a user
198         
199         :param filter: LDAP filter to find the user (eg samccountname=name)
200         :param expiry_seconds: expiry time from now in seconds
201         :param noexpiry: if set, then don't expire password
202         """
203         self.transaction_start()
204         try:
205             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
206                               expression=filter,
207                               attrs=["userAccountControl", "accountExpires"])
208             assert len(res) == 1
209             user_dn = res[0].dn
210
211             userAccountControl = int(res[0]["userAccountControl"][0])
212             accountExpires     = int(res[0]["accountExpires"][0])
213             if noexpiry:
214                 userAccountControl = userAccountControl | 0x10000
215                 accountExpires = 0
216             else:
217                 userAccountControl = userAccountControl & ~0x10000
218                 accountExpires = glue.unix2nttime(expiry_seconds + int(time.time()))
219
220             setexp = """
221 dn: %s
222 changetype: modify
223 replace: userAccountControl
224 userAccountControl: %u
225 replace: accountExpires
226 accountExpires: %u
227 """ % (user_dn, userAccountControl, accountExpires)
228
229             self.modify_ldif(setexp)
230         except:
231             self.transaction_cancel()
232             raise
233         self.transaction_commit();
234