use a base64 encoded password when changing passwords
[amitay/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_file(self, pf, df)
203
204     def set_invocation_id(self, invocation_id):
205         """Set the invocation id for this SamDB handle.
206         
207         :param invocation_id: GUID of the invocation id.
208         """
209         glue.dsdb_set_ntds_invocation_id(self, invocation_id)
210
211     def setexpiry(self, user, expiry_seconds, noexpiry):
212         """Set the password expiry for a user
213         
214         :param expiry_seconds: expiry time from now in seconds
215         :param noexpiry: if set, then don't expire password
216         """
217         self.transaction_start()
218         try:
219             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
220                               expression=("(samAccountName=%s)" % user),
221                               attrs=["userAccountControl", "accountExpires"])
222             assert len(res) == 1
223             userAccountControl = int(res[0]["userAccountControl"][0])
224             accountExpires     = int(res[0]["accountExpires"][0])
225             if noexpiry:
226                 userAccountControl = userAccountControl | 0x10000
227                 accountExpires = 0
228             else:
229                 userAccountControl = userAccountControl & ~0x10000
230                 accountExpires = glue.unix2nttime(expiry_seconds + int(time.time()))
231
232             mod = """
233 dn: %s
234 changetype: modify
235 replace: userAccountControl
236 userAccountControl: %u
237 replace: accountExpires
238 accountExpires: %u
239 """ % (res[0].dn, userAccountControl, accountExpires)
240             # now change the database
241             self.modify_ldif(mod)
242         except:
243             self.transaction_cancel()
244             raise
245         self.transaction_commit();