Merge branch 'master' of ssh://git.samba.org/data/git/samba into wspp-schema
[kai/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, session_info=None, credentials=None, 
39                  modules_dir=None, lp=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)
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 = res[0]["userAccountControl"][0]
85         userAccountControl = int(userAccountControl)
86         if (userAccountControl & 0x2):
87             userAccountControl = userAccountControl & ~0x2 # remove disabled bit
88         if (userAccountControl & 0x20):
89             userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit
90
91         mod = """
92 dn: %s
93 changetype: modify
94 replace: userAccountControl
95 userAccountControl: %u
96 """ % (user_dn, userAccountControl)
97         self.modify_ldif(mod)
98
99     def domain_dn(self):
100         # find the DNs for the domain and the domain users group
101         res = self.search("", scope=ldb.SCOPE_BASE, 
102                           expression="(defaultNamingContext=*)", 
103                           attrs=["defaultNamingContext"])
104         assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
105         return res[0]["defaultNamingContext"][0]
106
107     def newuser(self, username, unixname, password):
108         """add a new user record.
109         
110         :param username: Name of the new user.
111         :param unixname: Name of the unix user to map to.
112         :param password: Password for the new user
113         """
114         # connect to the sam 
115         self.transaction_start()
116         try:
117             domain_dn = self.domain_dn()
118             assert(domain_dn is not None)
119             user_dn = "CN=%s,CN=Users,%s" % (username, domain_dn)
120
121             #
122             #  the new user record. note the reliance on the samdb module to 
123             #  fill in a sid, guid etc
124             #
125             #  now the real work
126             self.add({"dn": user_dn, 
127                 "sAMAccountName": username,
128                 "userPassword": password,
129                 "objectClass": "user"})
130
131             res = self.search(user_dn, scope=ldb.SCOPE_BASE,
132                               expression="objectclass=*",
133                               attrs=["objectSid"])
134             assert len(res) == 1
135             user_sid = self.schema_format_value("objectSid", res[0]["objectSid"][0])
136             
137             try:
138                 idmap = IDmapDB(lp=self.lp)
139
140                 user = pwd.getpwnam(unixname)
141                 # setup ID mapping for this UID
142                 
143                 idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2])
144
145             except KeyError:
146                 pass
147
148             #  modify the userAccountControl to remove the disabled bit
149             self.enable_account(user_dn)
150         except:
151             self.transaction_cancel()
152             raise
153         self.transaction_commit()
154
155     def setpassword(self, filter, password):
156         """Set a password on a user record
157         
158         :param filter: LDAP filter to find the user (eg samccountname=name)
159         :param password: Password for the user
160         """
161         # connect to the sam 
162         self.transaction_start()
163         try:
164             # find the DNs for the domain
165             res = self.search("", scope=ldb.SCOPE_BASE, 
166                               expression="(defaultNamingContext=*)", 
167                               attrs=["defaultNamingContext"])
168             assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
169             domain_dn = res[0]["defaultNamingContext"][0]
170             assert(domain_dn is not None)
171
172             res = self.search(domain_dn, scope=ldb.SCOPE_SUBTREE, 
173                               expression=filter,
174                               attrs=[])
175             assert(len(res) == 1)
176             user_dn = res[0].dn
177
178             setpw = """
179 dn: %s
180 changetype: modify
181 replace: userPassword
182 userPassword:: %s
183 """ % (user_dn, base64.b64encode(password))
184
185             self.modify_ldif(setpw)
186
187             #  modify the userAccountControl to remove the disabled bit
188             self.enable_account(user_dn)
189         except:
190             self.transaction_cancel()
191             raise
192         self.transaction_commit()
193
194     def set_domain_sid(self, sid):
195         """Change the domain SID used by this SamDB.
196
197         :param sid: The new domain sid to use.
198         """
199         glue.samdb_set_domain_sid(self, sid)
200
201     def attach_schema_from_ldif(self, pf, df):
202         glue.dsdb_attach_schema_from_ldif(self, pf, df)
203
204     def convert_schema_to_openldap(self, target, mapping):
205         return glue.dsdb_convert_schema_to_openldap(self, target, mapping)
206
207     def set_invocation_id(self, invocation_id):
208         """Set the invocation id for this SamDB handle.
209         
210         :param invocation_id: GUID of the invocation id.
211         """
212         glue.dsdb_set_ntds_invocation_id(self, invocation_id)
213
214     def setexpiry(self, user, expiry_seconds, noexpiry):
215         """Set the password expiry for a user
216         
217         :param expiry_seconds: expiry time from now in seconds
218         :param noexpiry: if set, then don't expire password
219         """
220         self.transaction_start()
221         try:
222             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
223                               expression=("(samAccountName=%s)" % user),
224                               attrs=["userAccountControl", "accountExpires"])
225             assert len(res) == 1
226             userAccountControl = int(res[0]["userAccountControl"][0])
227             accountExpires     = int(res[0]["accountExpires"][0])
228             if noexpiry:
229                 userAccountControl = userAccountControl | 0x10000
230                 accountExpires = 0
231             else:
232                 userAccountControl = userAccountControl & ~0x10000
233                 accountExpires = glue.unix2nttime(expiry_seconds + int(time.time()))
234
235             mod = """
236 dn: %s
237 changetype: modify
238 replace: userAccountControl
239 userAccountControl: %u
240 replace: accountExpires
241 accountExpires: %u
242 """ % (res[0].dn, userAccountControl, accountExpires)
243             # now change the database
244             self.modify_ldif(mod)
245         except:
246             self.transaction_cancel()
247             raise
248         self.transaction_commit();