1#!/usr/bin/python 2 3# Unix SMB/CIFS implementation. 4# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008 5# Copyright (C) Matthias Dieter Wallnoefer 2009 6# 7# Based on the original in EJS: 8# Copyright (C) Andrew Tridgell <tridge@samba.org> 2005 9# 10# This program is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 3 of the License, or 13# (at your option) any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program. If not, see <http://www.gnu.org/licenses/>. 22# 23 24"""Convenience functions for using the SAM.""" 25 26import samba 27import glue 28import ldb 29from samba.idmap import IDmapDB 30import pwd 31import time 32import base64 33 34__docformat__ = "restructuredText" 35 36class SamDB(samba.Ldb): 37 """The SAM database.""" 38 39 def __init__(self, url=None, lp=None, modules_dir=None, session_info=None, 40 credentials=None, flags=0, options=None): 41 """Opens the SAM Database 42 For parameter meanings see the super class (samba.Ldb) 43 """ 44 45 self.lp = lp 46 if url is None: 47 url = lp.get("sam database") 48 49 super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir, 50 session_info=session_info, credentials=credentials, flags=flags, 51 options=options) 52 53 glue.dsdb_set_global_schema(self) 54 55 def connect(self, url=None, flags=0, options=None): 56 super(SamDB, self).connect(url=self.lp.private_path(url), flags=flags, 57 options=options) 58 59 def domain_dn(self): 60 # find the DNs for the domain 61 res = self.search(base="", 62 scope=ldb.SCOPE_BASE, 63 expression="(defaultNamingContext=*)", 64 attrs=["defaultNamingContext"]) 65 assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None) 66 return res[0]["defaultNamingContext"][0] 67 68 def enable_account(self, filter): 69 """Enables an account 70 71 :param filter: LDAP filter to find the user (eg samccountname=name) 72 """ 73 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE, 74 expression=filter, attrs=["userAccountControl"]) 75 assert(len(res) == 1) 76 user_dn = res[0].dn 77 78 userAccountControl = int(res[0]["userAccountControl"][0]) 79 if (userAccountControl & 0x2): 80 userAccountControl = userAccountControl & ~0x2 # remove disabled bit 81 if (userAccountControl & 0x20): 82 userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit 83 84 mod = """ 85dn: %s 86changetype: modify 87replace: userAccountControl 88userAccountControl: %u 89""" % (user_dn, userAccountControl) 90 self.modify_ldif(mod) 91 92 def force_password_change_at_next_login(self, filter): 93 """Forces a password change at next login 94 95 :param filter: LDAP filter to find the user (eg samccountname=name) 96 """ 97 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE, 98 expression=filter, attrs=[]) 99 assert(len(res) == 1) 100 user_dn = res[0].dn 101 102 mod = """ 103dn: %s 104changetype: modify 105replace: pwdLastSet 106pwdLastSet: 0 107""" % (user_dn) 108 self.modify_ldif(mod) 109 110 def newuser(self, username, unixname, password, force_password_change_at_next_login_req=False): 111 """Adds a new user 112 113 Note: This call adds also the ID mapping for winbind; therefore it works 114 *only* on SAMBA 4. 115 116 :param username: Name of the new user 117 :param unixname: Name of the unix user to map to 118 :param password: Password for the new user 119 :param force_password_change_at_next_login_req: Force password change 120 """ 121 self.transaction_start() 122 try: 123 user_dn = "CN=%s,CN=Users,%s" % (username, self.domain_dn()) 124 125 # The new user record. Note the reliance on the SAMLDB module which 126 # fills in the default informations 127 self.add({"dn": user_dn, 128 "sAMAccountName": username, 129 "objectClass": "user"}) 130 131 # Sets the password for it 132 self.setpassword("(dn=" + user_dn + ")", password, 133 force_password_change_at_next_login_req) 134 135 # Gets the user SID (for the account mapping setup) 136 res = self.search(user_dn, scope=ldb.SCOPE_BASE, 137 expression="objectclass=*", 138 attrs=["objectSid"]) 139 assert len(res) == 1 140 user_sid = self.schema_format_value("objectSid", res[0]["objectSid"][0]) 141 142 try: 143 idmap = IDmapDB(lp=self.lp) 144 145 user = pwd.getpwnam(unixname) 146 147 # setup ID mapping for this UID 148 idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2]) 149 150 except KeyError: 151 pass 152 except: 153 self.transaction_cancel() 154 raise 155 self.transaction_commit() 156 157 def setpassword(self, filter, password, force_password_change_at_next_login_req=False): 158 """Sets the password for a user 159 160 Note: This call uses the "userPassword" attribute to set the password. 161 This works correctly on SAMBA 4 and on Windows DCs with 162 "2003 Native" or higer domain function level. 163 164 :param filter: LDAP filter to find the user (eg samccountname=name) 165 :param password: Password for the user 166 :param force_password_change_at_next_login_req: Force password change 167 """ 168 self.transaction_start() 169 try: 170 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE, 171 expression=filter, attrs=[]) 172 assert(len(res) == 1) 173 user_dn = res[0].dn 174 175 setpw = """ 176dn: %s 177changetype: modify 178replace: userPassword 179userPassword:: %s 180""" % (user_dn, base64.b64encode(password)) 181 182 self.modify_ldif(setpw) 183 184 if force_password_change_at_next_login_req: 185 self.force_password_change_at_next_login( 186 "(dn=" + str(user_dn) + ")") 187 188 # modify the userAccountControl to remove the disabled bit 189 self.enable_account(filter) 190 except: 191 self.transaction_cancel() 192 raise 193 self.transaction_commit() 194 195 def setexpiry(self, filter, expiry_seconds, no_expiry_req=False): 196 """Sets the account expiry for a user 197 198 :param filter: LDAP filter to find the user (eg samccountname=name) 199 :param expiry_seconds: expiry time from now in seconds 200 :param no_expiry_req: if set, then don't expire password 201 """ 202 self.transaction_start() 203 try: 204 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE, 205 expression=filter, 206 attrs=["userAccountControl", "accountExpires"]) 207 assert(len(res) == 1) 208 user_dn = res[0].dn 209 210 userAccountControl = int(res[0]["userAccountControl"][0]) 211 accountExpires = int(res[0]["accountExpires"][0]) 212 if no_expiry_req: 213 userAccountControl = userAccountControl | 0x10000 214 accountExpires = 0 215 else: 216 userAccountControl = userAccountControl & ~0x10000 217 accountExpires = glue.unix2nttime(expiry_seconds + int(time.time())) 218 219 setexp = """ 220dn: %s 221changetype: modify 222replace: userAccountControl 223userAccountControl: %u 224replace: accountExpires 225accountExpires: %u 226""" % (user_dn, userAccountControl, accountExpires) 227 228 self.modify_ldif(setexp) 229 except: 230 self.transaction_cancel() 231 raise 232 self.transaction_commit(); 233 234