s4/torture: fixed lots of crash bugs in the DRS tests
[ira/wip.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 enable_account(self, user_dn):
59         """Enable an account.
60         
61         :param user_dn: Dn of the account to enable.
62         """
63         res = self.search(user_dn, ldb.SCOPE_BASE, None, ["userAccountControl"])
64         assert len(res) == 1
65         userAccountControl = int(res[0]["userAccountControl"][0])
66         if (userAccountControl & 0x2):
67             userAccountControl = userAccountControl & ~0x2 # remove disabled bit
68         if (userAccountControl & 0x20):
69             userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit
70
71         mod = """
72 dn: %s
73 changetype: modify
74 replace: userAccountControl
75 userAccountControl: %u
76 """ % (user_dn, userAccountControl)
77         self.modify_ldif(mod)
78         
79     def force_password_change_at_next_login(self, user_dn):
80         """Force a password change at next login
81         
82         :param user_dn: Dn of the account to force password change on
83         """
84         mod = """
85 dn: %s
86 changetype: modify
87 replace: pwdLastSet
88 pwdLastSet: 0
89 """ % (user_dn)
90         self.modify_ldif(mod)
91
92     def domain_dn(self):
93         # find the DNs for the domain
94         res = self.search(base="",
95                           scope=ldb.SCOPE_BASE,
96                           expression="(defaultNamingContext=*)", 
97                           attrs=["defaultNamingContext"])
98         assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
99         return res[0]["defaultNamingContext"][0]
100
101     def newuser(self, username, unixname, password, force_password_change_at_next_login=False):
102         """add a new user record.
103         
104         :param username: Name of the new user.
105         :param unixname: Name of the unix user to map to.
106         :param password: Password for the new user
107         """
108         # connect to the sam 
109         self.transaction_start()
110         try:
111             user_dn = "CN=%s,CN=Users,%s" % (username, self.domain_dn())
112
113             #
114             #  the new user record. note the reliance on the samdb module to 
115             #  fill in a sid, guid etc
116             #
117             #  now the real work
118             self.add({"dn": user_dn, 
119                 "sAMAccountName": username,
120                 "userPassword": password,
121                 "objectClass": "user"})
122
123             res = self.search(user_dn, scope=ldb.SCOPE_BASE,
124                               expression="objectclass=*",
125                               attrs=["objectSid"])
126             assert len(res) == 1
127             user_sid = self.schema_format_value("objectSid", res[0]["objectSid"][0])
128             
129             try:
130                 idmap = IDmapDB(lp=self.lp)
131
132                 user = pwd.getpwnam(unixname)
133                 # setup ID mapping for this UID
134                 
135                 idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2])
136
137             except KeyError:
138                 pass
139
140             if force_password_change_at_next_login:
141                 self.force_password_change_at_next_login(user_dn)
142
143             #  modify the userAccountControl to remove the disabled bit
144             self.enable_account(user_dn)
145         except:
146             self.transaction_cancel()
147             raise
148         self.transaction_commit()
149
150     def setpassword(self, filter, password, force_password_change_at_next_login=False):
151         """Set a password on a user record
152         
153         :param filter: LDAP filter to find the user (eg samccountname=name)
154         :param password: Password for the user
155         """
156         # connect to the sam 
157         self.transaction_start()
158         try:
159             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
160                               expression=filter, attrs=[])
161             assert(len(res) == 1)
162             user_dn = res[0].dn
163
164             mod = ldb.Message()
165             mod.dn = user_dn
166
167             glue.samdb_set_password(samdb=self, user_dn=str(user_dn),
168                         dom_dn=self.domain_dn(), mod=mod, new_password=password,
169                         user_change=False)
170
171             self.modify(mod)
172
173             if force_password_change_at_next_login:
174                 self.force_password_change_at_next_login(user_dn)
175
176             #  modify the userAccountControl to remove the disabled bit
177             self.enable_account(user_dn)
178         except:
179             self.transaction_cancel()
180             raise
181         self.transaction_commit()
182
183     def setexpiry(self, user, expiry_seconds, noexpiry):
184         """Set the account expiry for a user
185         
186         :param expiry_seconds: expiry time from now in seconds
187         :param noexpiry: if set, then don't expire password
188         """
189         self.transaction_start()
190         try:
191             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
192                               expression=("(samAccountName=%s)" % user),
193                               attrs=["userAccountControl", "accountExpires"])
194             assert len(res) == 1
195             userAccountControl = int(res[0]["userAccountControl"][0])
196             accountExpires     = int(res[0]["accountExpires"][0])
197             if noexpiry:
198                 userAccountControl = userAccountControl | 0x10000
199                 accountExpires = 0
200             else:
201                 userAccountControl = userAccountControl & ~0x10000
202                 accountExpires = glue.unix2nttime(expiry_seconds + int(time.time()))
203
204             mod = """
205 dn: %s
206 changetype: modify
207 replace: userAccountControl
208 userAccountControl: %u
209 replace: accountExpires
210 accountExpires: %u
211 """ % (res[0].dn, userAccountControl, accountExpires)
212             # now change the database
213             self.modify_ldif(mod)
214         except:
215             self.transaction_cancel()
216             raise
217         self.transaction_commit();
218