# -*- coding: utf-8 -*- # # Copyright (C) 2006-2010 TUBITAK/UEKAE # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. # import os import glob import fcntl import random import shutil import hashlib from string import ascii_letters, digits from pardus.fileutils import FileLock try: import polkit except ImportError: pass # faces FACES = [ "/usr/share/kde4/apps/kdm/pics/users", ] # messages invalid_username_msg = { "en": "User name is invalid.", "tr": "Kullanıcı adı geçersiz.", "fr": "Nom d'utilisateur invalide.", "es": "Nombre de usuario no es válido.", "de": "Benutzername nicht erlaubt.", "nl": "Gebruikernaam is ongeldig.", } invalid_realname_msg = { "en": "Real name is invalid.", "tr": "Gerçek isim geçersiz.", "fr": "Nom réel invalide.", "es": "Nombre real no es válido.", "de": "Dieser vollständige Name ist nicht erlaubt.", "nl": "Echte naam is ongeldig.", } short_password_msg = { "en": "Password is too short.", "tr": "Parola çok kısa.", "fr": "Mot de passe trop court.", "es": "Contraseña es demadiado corta.", "de": "Passwort ist zu kurz.", "nl": "Wachtwoord is te kort.", } name_password_msg = { "en": "Dont use your name as a password.", "tr": "Adınızı parola olarak kullanmayın.", "fr": "N'utilisez pas votre nom comme mot de passe.", "es": "No use su nombre como contraseña.", "de": "Benutzen Sie nicht Ihren Namen als Passwort.", "nl": "Uw naam niet als wachtwoord gebruiken.", } invalid_group_msg = { "en": "Invalid group name:", "tr": "Geçersiz grup adı:", "fr": "Nom de groupe invalide:", "es": "Nombre de grupo inválido:", "de": "Gruppenname nicht erlaubt:", "nl": "Ongeldige groepnaam:", } invalid_userid_msg = { "en": "Invalid user ID.", "tr": "Geçersiz kullanıcı numarası.", "fr": "Identifiant utilisateur invalide.", "es": "ID de usuario no válido.", "de": "User-ID nicht erlaubt.", "nl": "Gebruiker-id is ongeldig.", } used_userid_msg = { "en": "This user ID is already used.", "tr": "Bu kullanıcı numarası zaten kullanılmakta.", "fr": "Cet identifiant utilisateur est déjà utilisé.", "es": "Este ID de usuario ya está en uso.", "de": "Dieser user-ID is schon vergeben.", "nl": "Deze gebruiker-id is reeds in gebruik.", } used_username_msg = { "en": "This user name is already used.", "tr": "Bu kullanıcı adı zaten kullanılmakta.", "fr": "Ce nom d'utilisateur est déjà utilisé.", "es": "Este nombre de usuario ya está en uso.", "de": "Dieser Benutzername is schon vergeben.", "nl": "Deze gebruikernaam is reeds in gebruik.", } no_group_msg = { "en": "No such group exists.", "tr": "Böyle bir grup yok.", "fr": "Il n'existe aucun groupe de ce nom-là.", "es": "No existe el grupo.", "de": "Diese Gruppe gibt es nicht.", "nl": "Deze groep bestaat niet.", } no_user_msg = { "en": "No user with given ID.", "tr": "Verilen numaralı bir kullanıcı yok.", "fr": "Il n'existe aucun utilisateur avec et identifiant.", "es": "No existe usuario con éste ID.", "de": "Es gibt keien Benutzer mit dem angegebenen ID.", "nl": "Gebruiker met opgegeven ID bestaat niet.", } delete_root_msg = { "en": "You cant delete root user.", "tr": "Kök kullanıcıyı silemezsiniz.", "fr": "Vous ne pouvez pas supprimer l'administrateur.", "es": "No se puede eliminar al usuario root.", "de": "Benutzer ROOT darf nicht gelöscht werden.", "nl": "Systeembeheerder (root) kan niet verwijderd worden.", } invalid_groupid_msg = { "en": "Invalid group ID.", "tr": "Geçersiz grup numarası.", "fr": "Identifiant de groupe invalide.", "es": "ID del grupo inválido.", "de": "Gruppen-ID nicht zulässig.", "nl": "Groep-ID is ongeldig.", } used_groupid_msg = { "en": "This group ID is already used.", "tr": "Bu grup numarası zaten kullanılmakta.", "fr": "Cet identifiant de groupe est déjà utilisé.", "es": "Este ID de grupo ya está en uso.", "de": "Dieser Gruppen-ID is schon vergeben.", "nl": "Deze groep-ID is reeds in gebruik.", } used_groupname_msg = { "en": "This group name is already used.", "tr": "Bu grup adı zaten kullanılmakta.", "fr": "Ce nom de groupe est déjà utilisé.", "es": "Este nombre de grupo ya está en uso.", "de": "Dieser Gruppennam is schon vergeben.", "nl": "Deze groepnaam is reeds in gebruik.", } # parameters uid_minimum = 1000 uid_maximum = 65000 # def setFace(uid, homedir): files = [] for directory in FACES: if os.path.exists(directory): for filename in os.listdir(directory): if filename.endswith(".png"): files.append(os.path.join(directory, filename)) if len(files): icon = os.path.join(homedir, ".face.icon") shutil.copy(random.choice(files), icon) os.chmod(icon, 0644) os.chown(icon, uid, 100) def checkName(name): first_valid = ascii_letters valid = ascii_letters + "_-" + digits if len(name) == 0 or len(filter(lambda x: not x in valid, name)) != 0 or not name[0] in first_valid: fail(_(invalid_username_msg)) def checkRealName(realname): if len(filter(lambda x: x == "\n" or x == ":", realname)) != 0: fail(_(invalid_realname_msg)) def checkPassword(password, badlist): if len(password) < 1: fail(_(short_password_msg)) if password in badlist: fail(_(name_password_msg)) def checkGroupName(name): valid = ascii_letters + "_-" if name == "" or len(filter(lambda x: not x in valid, name)) != 0: fail(_(invalid_group_msg) + " " + name) # class User: def __init__(self): self.password = None def __str__(self): return "%s (%d, %d)\n %s\n %s\n %s\n %s" % ( self.name, self.uid, self.gid, self.realname, self.homedir, self.shell, self.password ) class Group: def __str__(self): s = "%s (%d)" % (self.name, self.gid) for name in self.members: s += "\n %s" % name return s class Database: passwd_path = "/etc/passwd" shadow_path = "/etc/shadow" group_path = "/etc/group" lock_path = "/etc/.pwd.lock" def __init__(self, for_read=False): self.lock = FileLock(self.lock_path) self.lock.lock(shared=for_read) self.users = {} self.users_by_name = {} self.groups = {} self.groups_by_name = {} for line in file(self.passwd_path): if line != "" and line != "\n": parts = line.rstrip("\n").split(":") user = User() user.name = parts[0] user.uid = int(parts[2]) user.gid = int(parts[3]) user.realname = parts[4] user.homedir = parts[5] user.shell = parts[6] self.users[user.uid] = user self.users_by_name[user.name] = user for line in file(self.shadow_path): if line != "" and line != "\n": parts = line.rstrip("\n").split(":") if self.users_by_name.has_key(parts[0]): user = self.users_by_name[parts[0]] user.password = parts[1] user.pwrest = parts[2:] for line in file(self.group_path): if line != "" and line != "\n": parts = line.rstrip("\n").split(":") group = Group() group.name = parts[0] group.gid = int(parts[2]) group.members = parts[3].split(",") if "" in group.members: group.members.remove("") self.groups[group.gid] = group self.groups_by_name[group.name] = group def sync(self): lines = [] keys = self.users.keys() keys.sort() for uid in keys: user = self.users[uid] lines.append("%s:x:%d:%d:%s:%s:%s\n" % ( user.name, uid, user.gid, user.realname, user.homedir, user.shell )) f = file(self.passwd_path, "w") f.writelines(lines) f.close() lines = [] keys = self.users.keys() keys.sort() for uid in keys: user = self.users[uid] if user.password: lines.append("%s:%s:%s\n" % ( user.name, user.password, ":".join(user.pwrest) )) else: lines.append("%s::13094:0:99999:7:::\n" % user.name) f = file(self.shadow_path, "w") f.writelines(lines) f.close() lines = [] keys = self.groups.keys() keys.sort() for gid in keys: group = self.groups[gid] lines.append("%s:x:%s:%s\n" % (group.name, gid, ",".join(group.members))) f = file(self.group_path, "w") f.writelines(lines) f.close() def set_groups(self, name, grouplist): for gid in self.groups.keys(): g = self.groups[gid] if name in g.members: if not g.name in grouplist: g.members.remove(name) else: if g.name in grouplist: g.members.append(name) def next_uid(self): for i in range(uid_minimum, uid_maximum): if not self.users.has_key(i): return i def next_gid(self): for i in range(uid_minimum, uid_maximum): if not self.groups.has_key(i): return i def setup_home(uid, gid, path): if not os.path.exists(path): # Copy skeleton home dir os.system('/bin/cp -r %s "%s"' % ('/etc/skel', path)) # Set a random face icon faces = glob.glob("/usr/share/*/apps/kdm/pics/users/*.png") if len(faces) > 0: facepath = os.path.join(path, '.face.icon') os.system('/bin/cp --remove-destination "%s" "%s"' % (random.choice(faces), facepath)) os.chmod(facepath, 0644) # Set ownerships os.system('/bin/chown -R %d:%d "%s"' % (uid, gid, path)) # Make sure at least top of the home dir's permissions are correct os.system('/bin/chown %d:%d "%s"' % (uid, gid, path)) os.chmod(path, 0711) # methods def userList(): def format(dict, uid): item = dict[uid] return (item.uid, item.name, item.realname) db = Database(for_read=True) return map(lambda x: format(db.users, x), db.users) def userInfo(uid): uid = int(uid) db = Database(for_read=True) if db.users.has_key(uid): u = db.users[uid] groups = [] for item in db.groups.keys(): if u.name in db.groups[item].members: groups.append(db.groups[item].name) grp = db.groups.get(u.gid, None) if grp: if grp.name in groups: groups.remove(grp.name) groups.insert(0, grp.name) ret = ( u.name, u.realname, u.gid, u.homedir, u.shell, groups, ) return ret else: fail(_(no_user_msg)) def addUser(uid, name, realname, homedir, shell, password, groups, grants, blocks): if not realname: realname = "" if not homedir: homedir = "/home/" + name if not shell: shell = "/bin/bash" if not groups: groups = ["nogroup"] for item in groups: checkGroupName(item) checkName(name) checkRealName(realname) if password: checkPassword(password, (name, realname)) db = Database() if uid == -1: uid = db.next_uid() else: try: uid = int(uid) if uid < 0 or uid > 65536: raise except: fail(_(invalid_userid_msg)) if db.users.has_key(uid): fail(_(used_userid_msg)) if db.users_by_name.has_key(name): fail(_(used_username_msg)) # First group in the list is the user's main group g = db.groups_by_name.get(groups[0], None) if not g: fail(_(no_group_msg)) gid = g.gid u = User() u.uid = uid u.gid = gid u.name = name u.realname = realname u.homedir = homedir u.shell = shell if password: u.password = shadowCrypt(password) else: u.password = "*" u.pwrest = [ "13094", "0", "99999", "7", "", "", "" ] db.users[uid] = u db.set_groups(name, groups) # No need to setup a real home dir for daemons if uid >= 1000 or homedir.startswith("/home/"): setup_home(uid, gid, homedir) setFace(uid, homedir) db.sync() for grant in grants: if grant != "": grantAuthorization(uid, grant) for block in blocks: if block != "": blockAuthorization(uid, block) return uid def setUser(uid, realname, homedir, shell, password, groups): uid = int(uid) db = Database() u = db.users.get(uid, None) if u: if realname: checkRealName(realname) u.realname = realname if homedir: u.homedir = homedir if shell: u.shell = shell if password: checkPassword(password, (u.name, u.realname, realname)) u.password = shadowCrypt(password) if groups: # FIXME: check main group for item in groups: checkGroupName(item) db.set_groups(u.name, groups) db.sync() else: fail(_(no_user_msg)) def deleteUser(uid, deletefiles): uid = int(uid) if uid == 0: fail(_(delete_root_msg)) db = Database() u = db.users.get(uid, None) if u: #delete authorizations of user try: polkit.auth_revoke_all(uid) except: pass home = u.homedir[:] db.set_groups(u.name, []) del db.users[uid] db.sync() if deletefiles: os.system('/bin/rm -rf "%s"' % home) def groupList(): def format(dict, gid): item = dict[gid] return (item.gid, item.name) db = Database(for_read=True) return map(lambda x: format(db.groups, x), db.groups) def addGroup(gid, name): checkGroupName(name) db = Database() if gid == -1: gid = db.next_gid() else: try: gid = int(gid) if gid < 0 or gid > 65536: raise except: fail(_(invalid_groupid_msg)) if db.groups.has_key(gid): fail(_(used_groupid_msg)) if db.groups_by_name.has_key(name): fail(_(used_groupname_msg)) g = Group() g.gid = gid g.name = name g.members = [] db.groups[gid] = g db.sync() return gid def deleteGroup(gid): gid = int(gid) db = Database() if db.groups.has_key(gid): del db.groups[gid] db.sync() # # Crypt function for shadow file # def shadowCrypt(password): des_salt = list('./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') salt, magic = str(random.random())[-8:], '$1$' ctx = hashlib.md5(password) ctx.update(magic) ctx.update(salt) ctx1 = hashlib.md5(password) ctx1.update(salt) ctx1.update(password) final = ctx1.digest() for i in range(len(password), 0 , -16): if i > 16: ctx.update(final) else: ctx.update(final[:i]) i = len(password) while i: if i & 1: ctx.update('\0') else: ctx.update(password[:1]) i = i >> 1 final = ctx.digest() for i in range(1000): ctx1 = hashlib.md5() if i & 1: ctx1.update(password) else: ctx1.update(final) if i % 3: ctx1.update(salt) if i % 7: ctx1.update(password) if i & 1: ctx1.update(final) else: ctx1.update(password) final = ctx1.digest() def _to64(v, n): r = '' while (n-1 >= 0): r = r + des_salt[v & 0x3F] v = v >> 6 n = n - 1 return r rv = magic + salt + '$' final = map(ord, final) l = (final[0] << 16) + (final[6] << 8) + final[12] rv = rv + _to64(l, 4) l = (final[1] << 16) + (final[7] << 8) + final[13] rv = rv + _to64(l, 4) l = (final[2] << 16) + (final[8] << 8) + final[14] rv = rv + _to64(l, 4) l = (final[3] << 16) + (final[9] << 8) + final[15] rv = rv + _to64(l, 4) l = (final[4] << 16) + (final[10] << 8) + final[5] rv = rv + _to64(l, 4) l = final[11] rv = rv + _to64(l, 2) return rv # # List authorizations by UID # def listUserAuthorizations(uid): actions = polkit.auth_list_uid(int(uid)) auths = [] for action in actions: action_info = polkit.action_info(action['action_id']) auths.append((action['action_id'], action['scope'], action_info['description'], action_info['policy_active'], action['negative'])) return auths # # Grant authorization to user # def grantAuthorization(uid, action): uid = int(uid) if action == "*": for action_id in polkit.action_list(): try: polkit.auth_revoke(uid, action_id) polkit.auth_add(action_id, polkit.SCOPE_ALWAYS, uid) except: return False else: try: polkit.auth_revoke(uid, action) polkit.auth_add(action, polkit.SCOPE_ALWAYS, uid) except: return False return True # # Revoke authorization of user # def revokeAuthorization(uid, action): uid = int(uid) if action == "*": for action_id in polkit.action_list(): try: polkit.auth_revoke(uid, action_id) except: return False else: try: polkit.auth_revoke(uid, action) except: return False return True # # Block authorization of user # def blockAuthorization(uid, action): uid = int(uid) if action == "*": for action_id in polkit.action_list(): try: polkit.auth_revoke(uid, action_id) polkit.auth_block(uid, action_id) except: return False else: try: polkit.auth_revoke(uid, action) polkit.auth_block(uid, action) except: return False return True