c22e79b67ff5398b5a07b5bcec683676c5b62235
[metze/samba/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             assert(len(res) == 1)
191             user_dn = res[0].dn
192
193             setpw = """
194 dn: %s
195 changetype: modify
196 replace: userPassword
197 userPassword:: %s
198 """ % (user_dn, base64.b64encode(password))
199
200             self.modify_ldif(setpw)
201
202             if force_password_change_at_next_login:
203                 self.force_password_change_at_next_login(user_dn)
204
205             #  modify the userAccountControl to remove the disabled bit
206             self.enable_account(user_dn)
207         except:
208             self.transaction_cancel()
209             raise
210         self.transaction_commit()
211
212     def setexpiry(self, user, expiry_seconds, noexpiry):
213         """Set the account expiry for a user
214         
215         :param expiry_seconds: expiry time from now in seconds
216         :param noexpiry: if set, then don't expire password
217         """
218         self.transaction_start()
219         try:
220             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
221                               expression=("(samAccountName=%s)" % user),
222                               attrs=["userAccountControl", "accountExpires"])
223             assert len(res) == 1
224             userAccountControl = int(res[0]["userAccountControl"][0])
225             accountExpires     = int(res[0]["accountExpires"][0])
226             if noexpiry:
227                 userAccountControl = userAccountControl | 0x10000
228                 accountExpires = 0
229             else:
230                 userAccountControl = userAccountControl & ~0x10000
231                 accountExpires = glue.unix2nttime(expiry_seconds + int(time.time()))
232
233             mod = """
234 dn: %s
235 changetype: modify
236 replace: userAccountControl
237 userAccountControl: %u
238 replace: accountExpires
239 accountExpires: %u
240 """ % (res[0].dn, userAccountControl, accountExpires)
241             # now change the database
242             self.modify_ldif(mod)
243         except:
244             self.transaction_cancel()
245             raise
246         self.transaction_commit();
247