s4: Simplify two lines in the "samdb.py" file (cosmetic)
[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, session_info=None, credentials=None, 
39                  modules_dir=None, lp=None, options=None):
40         """Open the Sam Database.
41
42         :param url: URL of the database.
43         """
44         self.lp = lp
45         super(SamDB, self).__init__(session_info=session_info, credentials=credentials,
46                                     modules_dir=modules_dir, lp=lp, options=options)
47         glue.dsdb_set_global_schema(self)
48         if url:
49             self.connect(url)
50         else:
51             self.connect(lp.get("sam database"))
52
53     def connect(self, url):
54         super(SamDB, self).connect(self.lp.private_path(url))
55
56     def add_foreign(self, domaindn, sid, desc):
57         """Add a foreign security principle."""
58         add = """
59 dn: CN=%s,CN=ForeignSecurityPrincipals,%s
60 objectClass: top
61 objectClass: foreignSecurityPrincipal
62 description: %s
63 """ % (sid, domaindn, desc)
64         # deliberately ignore errors from this, as the records may
65         # already exist
66         for msg in self.parse_ldif(add):
67             self.add(msg[1])
68
69     def add_stock_foreign_sids(self):
70         domaindn = self.domain_dn()
71         self.add_foreign(domaindn, "S-1-5-7", "Anonymous")
72         self.add_foreign(domaindn, "S-1-1-0", "World")
73         self.add_foreign(domaindn, "S-1-5-2", "Network")
74         self.add_foreign(domaindn, "S-1-5-18", "System")
75         self.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
76
77     def enable_account(self, user_dn):
78         """Enable an account.
79         
80         :param user_dn: Dn of the account to enable.
81         """
82         res = self.search(user_dn, ldb.SCOPE_BASE, None, ["userAccountControl"])
83         assert len(res) == 1
84         userAccountControl = int(res[0]["userAccountControl"][0])
85         if (userAccountControl & 0x2):
86             userAccountControl = userAccountControl & ~0x2 # remove disabled bit
87         if (userAccountControl & 0x20):
88             userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit
89
90         mod = """
91 dn: %s
92 changetype: modify
93 replace: userAccountControl
94 userAccountControl: %u
95 """ % (user_dn, userAccountControl)
96         self.modify_ldif(mod)
97
98         
99     def force_password_change_at_next_login(self, user_dn):
100         """Force a password change at next login
101         
102         :param user_dn: Dn of the account to force password change on
103         """
104         mod = """
105 dn: %s
106 changetype: modify
107 replace: pwdLastSet
108 pwdLastSet: 0
109 """ % (user_dn)
110         self.modify_ldif(mod)
111
112     def domain_dn(self):
113         # find the DNs for the domain and the domain users group
114         res = self.search("", scope=ldb.SCOPE_BASE, 
115                           expression="(defaultNamingContext=*)", 
116                           attrs=["defaultNamingContext"])
117         assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
118         return res[0]["defaultNamingContext"][0]
119
120     def newuser(self, username, unixname, password, force_password_change_at_next_login=False):
121         """add a new user record.
122         
123         :param username: Name of the new user.
124         :param unixname: Name of the unix user to map to.
125         :param password: Password for the new user
126         """
127         # connect to the sam 
128         self.transaction_start()
129         try:
130             domain_dn = self.domain_dn()
131             assert(domain_dn is not None)
132             user_dn = "CN=%s,CN=Users,%s" % (username, domain_dn)
133
134             #
135             #  the new user record. note the reliance on the samdb module to 
136             #  fill in a sid, guid etc
137             #
138             #  now the real work
139             self.add({"dn": user_dn, 
140                 "sAMAccountName": username,
141                 "userPassword": password,
142                 "objectClass": "user"})
143
144             res = self.search(user_dn, scope=ldb.SCOPE_BASE,
145                               expression="objectclass=*",
146                               attrs=["objectSid"])
147             assert len(res) == 1
148             user_sid = self.schema_format_value("objectSid", res[0]["objectSid"][0])
149             
150             try:
151                 idmap = IDmapDB(lp=self.lp)
152
153                 user = pwd.getpwnam(unixname)
154                 # setup ID mapping for this UID
155                 
156                 idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2])
157
158             except KeyError:
159                 pass
160
161             if force_password_change_at_next_login:
162                 self.force_password_change_at_next_login(user_dn)
163
164             #  modify the userAccountControl to remove the disabled bit
165             self.enable_account(user_dn)
166         except:
167             self.transaction_cancel()
168             raise
169         self.transaction_commit()
170
171     def setpassword(self, filter, password, force_password_change_at_next_login=False):
172         """Set a password on a user record
173         
174         :param filter: LDAP filter to find the user (eg samccountname=name)
175         :param password: Password for the user
176         """
177         # connect to the sam 
178         self.transaction_start()
179         try:
180             # find the DNs for the domain
181             res = self.search("", scope=ldb.SCOPE_BASE, 
182                               expression="(defaultNamingContext=*)", 
183                               attrs=["defaultNamingContext"])
184             assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
185             domain_dn = res[0]["defaultNamingContext"][0]
186             assert(domain_dn is not None)
187
188             res = self.search(domain_dn, scope=ldb.SCOPE_SUBTREE, 
189                               expression=filter,
190                               attrs=[])
191             assert(len(res) == 1)
192             user_dn = res[0].dn
193
194             setpw = """
195 dn: %s
196 changetype: modify
197 replace: userPassword
198 userPassword:: %s
199 """ % (user_dn, base64.b64encode(password))
200
201             self.modify_ldif(setpw)
202
203             if force_password_change_at_next_login:
204                 self.force_password_change_at_next_login(user_dn)
205
206             #  modify the userAccountControl to remove the disabled bit
207             self.enable_account(user_dn)
208         except:
209             self.transaction_cancel()
210             raise
211         self.transaction_commit()
212
213     def set_domain_sid(self, sid):
214         """Change the domain SID used by this SamDB.
215
216         :param sid: The new domain sid to use.
217         """
218         glue.samdb_set_domain_sid(self, sid)
219
220     def attach_schema_from_ldif(self, pf, df):
221         glue.dsdb_attach_schema_from_ldif(self, pf, df)
222
223     def convert_schema_to_openldap(self, target, mapping):
224         return glue.dsdb_convert_schema_to_openldap(self, target, mapping)
225
226     def set_invocation_id(self, invocation_id):
227         """Set the invocation id for this SamDB handle.
228         
229         :param invocation_id: GUID of the invocation id.
230         """
231         glue.dsdb_set_ntds_invocation_id(self, invocation_id)
232
233     def set_opaque_integer(self, name, value):
234         """Set an integer as an opaque (a flag or other value) value on the database
235         
236         :param name: The name for the opaque value
237         :param value: The integer value
238         """
239         glue.dsdb_set_opaque_integer(self, name, value)
240
241     def setexpiry(self, user, expiry_seconds, noexpiry):
242         """Set the account expiry for a user
243         
244         :param expiry_seconds: expiry time from now in seconds
245         :param noexpiry: if set, then don't expire password
246         """
247         self.transaction_start()
248         try:
249             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
250                               expression=("(samAccountName=%s)" % user),
251                               attrs=["userAccountControl", "accountExpires"])
252             assert len(res) == 1
253             userAccountControl = int(res[0]["userAccountControl"][0])
254             accountExpires     = int(res[0]["accountExpires"][0])
255             if noexpiry:
256                 userAccountControl = userAccountControl | 0x10000
257                 accountExpires = 0
258             else:
259                 userAccountControl = userAccountControl & ~0x10000
260                 accountExpires = glue.unix2nttime(expiry_seconds + int(time.time()))
261
262             mod = """
263 dn: %s
264 changetype: modify
265 replace: userAccountControl
266 userAccountControl: %u
267 replace: accountExpires
268 accountExpires: %u
269 """ % (res[0].dn, userAccountControl, accountExpires)
270             # now change the database
271             self.modify_ldif(mod)
272         except:
273             self.transaction_cancel()
274             raise
275         self.transaction_commit();
276