usermgr.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2006-2010 TUBITAK/UEKAE
  4. #
  5. # This program is free software; you can redistribute it and/or modify it under
  6. # the terms of the GNU General Public License as published by the Free
  7. # Software Foundation; either version 2 of the License, or (at your option)
  8. # any later version.
  9. #
  10. import os
  11. import glob
  12. import fcntl
  13. import random
  14. import shutil
  15. import hashlib
  16. from string import ascii_letters, digits
  17. from pardus.fileutils import FileLock
  18. try:
  19. import polkit
  20. except ImportError:
  21. pass
  22. # faces
  23. FACES = [
  24. "/usr/share/kde4/apps/kdm/pics/users",
  25. ]
  26. # messages
  27. invalid_username_msg = {
  28. "en": "User name is invalid.",
  29. "tr": "Kullanıcı adı geçersiz.",
  30. "fr": "Nom d'utilisateur invalide.",
  31. "es": "Nombre de usuario no es válido.",
  32. "de": "Benutzername nicht erlaubt.",
  33. "nl": "Gebruikernaam is ongeldig.",
  34. }
  35. invalid_realname_msg = {
  36. "en": "Real name is invalid.",
  37. "tr": "Gerçek isim geçersiz.",
  38. "fr": "Nom réel invalide.",
  39. "es": "Nombre real no es válido.",
  40. "de": "Dieser vollständige Name ist nicht erlaubt.",
  41. "nl": "Echte naam is ongeldig.",
  42. }
  43. short_password_msg = {
  44. "en": "Password is too short.",
  45. "tr": "Parola çok kısa.",
  46. "fr": "Mot de passe trop court.",
  47. "es": "Contraseña es demadiado corta.",
  48. "de": "Passwort ist zu kurz.",
  49. "nl": "Wachtwoord is te kort.",
  50. }
  51. name_password_msg = {
  52. "en": "Dont use your name as a password.",
  53. "tr": "Adınızı parola olarak kullanmayın.",
  54. "fr": "N'utilisez pas votre nom comme mot de passe.",
  55. "es": "No use su nombre como contraseña.",
  56. "de": "Benutzen Sie nicht Ihren Namen als Passwort.",
  57. "nl": "Uw naam niet als wachtwoord gebruiken.",
  58. }
  59. invalid_group_msg = {
  60. "en": "Invalid group name:",
  61. "tr": "Geçersiz grup adı:",
  62. "fr": "Nom de groupe invalide:",
  63. "es": "Nombre de grupo inválido:",
  64. "de": "Gruppenname nicht erlaubt:",
  65. "nl": "Ongeldige groepnaam:",
  66. }
  67. invalid_userid_msg = {
  68. "en": "Invalid user ID.",
  69. "tr": "Geçersiz kullanıcı numarası.",
  70. "fr": "Identifiant utilisateur invalide.",
  71. "es": "ID de usuario no válido.",
  72. "de": "User-ID nicht erlaubt.",
  73. "nl": "Gebruiker-id is ongeldig.",
  74. }
  75. used_userid_msg = {
  76. "en": "This user ID is already used.",
  77. "tr": "Bu kullanıcı numarası zaten kullanılmakta.",
  78. "fr": "Cet identifiant utilisateur est déjà utilisé.",
  79. "es": "Este ID de usuario ya está en uso.",
  80. "de": "Dieser user-ID is schon vergeben.",
  81. "nl": "Deze gebruiker-id is reeds in gebruik.",
  82. }
  83. used_username_msg = {
  84. "en": "This user name is already used.",
  85. "tr": "Bu kullanıcı adı zaten kullanılmakta.",
  86. "fr": "Ce nom d'utilisateur est déjà utilisé.",
  87. "es": "Este nombre de usuario ya está en uso.",
  88. "de": "Dieser Benutzername is schon vergeben.",
  89. "nl": "Deze gebruikernaam is reeds in gebruik.",
  90. }
  91. no_group_msg = {
  92. "en": "No such group exists.",
  93. "tr": "Böyle bir grup yok.",
  94. "fr": "Il n'existe aucun groupe de ce nom-là.",
  95. "es": "No existe el grupo.",
  96. "de": "Diese Gruppe gibt es nicht.",
  97. "nl": "Deze groep bestaat niet.",
  98. }
  99. no_user_msg = {
  100. "en": "No user with given ID.",
  101. "tr": "Verilen numaralı bir kullanıcı yok.",
  102. "fr": "Il n'existe aucun utilisateur avec et identifiant.",
  103. "es": "No existe usuario con éste ID.",
  104. "de": "Es gibt keien Benutzer mit dem angegebenen ID.",
  105. "nl": "Gebruiker met opgegeven ID bestaat niet.",
  106. }
  107. delete_root_msg = {
  108. "en": "You cant delete root user.",
  109. "tr": "Kök kullanıcıyı silemezsiniz.",
  110. "fr": "Vous ne pouvez pas supprimer l'administrateur.",
  111. "es": "No se puede eliminar al usuario root.",
  112. "de": "Benutzer ROOT darf nicht gelöscht werden.",
  113. "nl": "Systeembeheerder (root) kan niet verwijderd worden.",
  114. }
  115. invalid_groupid_msg = {
  116. "en": "Invalid group ID.",
  117. "tr": "Geçersiz grup numarası.",
  118. "fr": "Identifiant de groupe invalide.",
  119. "es": "ID del grupo inválido.",
  120. "de": "Gruppen-ID nicht zulässig.",
  121. "nl": "Groep-ID is ongeldig.",
  122. }
  123. used_groupid_msg = {
  124. "en": "This group ID is already used.",
  125. "tr": "Bu grup numarası zaten kullanılmakta.",
  126. "fr": "Cet identifiant de groupe est déjà utilisé.",
  127. "es": "Este ID de grupo ya está en uso.",
  128. "de": "Dieser Gruppen-ID is schon vergeben.",
  129. "nl": "Deze groep-ID is reeds in gebruik.",
  130. }
  131. used_groupname_msg = {
  132. "en": "This group name is already used.",
  133. "tr": "Bu grup adı zaten kullanılmakta.",
  134. "fr": "Ce nom de groupe est déjà utilisé.",
  135. "es": "Este nombre de grupo ya está en uso.",
  136. "de": "Dieser Gruppennam is schon vergeben.",
  137. "nl": "Deze groepnaam is reeds in gebruik.",
  138. }
  139. # parameters
  140. uid_minimum = 1000
  141. uid_maximum = 65000
  142. #
  143. def setFace(uid, homedir):
  144. files = []
  145. for directory in FACES:
  146. if os.path.exists(directory):
  147. for filename in os.listdir(directory):
  148. if filename.endswith(".png"):
  149. files.append(os.path.join(directory, filename))
  150. if len(files):
  151. icon = os.path.join(homedir, ".face.icon")
  152. shutil.copy(random.choice(files), icon)
  153. os.chmod(icon, 0644)
  154. os.chown(icon, uid, 100)
  155. def checkName(name):
  156. first_valid = ascii_letters
  157. valid = ascii_letters + "_-" + digits
  158. if len(name) == 0 or len(filter(lambda x: not x in valid, name)) != 0 or not name[0] in first_valid:
  159. fail(_(invalid_username_msg))
  160. def checkRealName(realname):
  161. if len(filter(lambda x: x == "\n" or x == ":", realname)) != 0:
  162. fail(_(invalid_realname_msg))
  163. def checkPassword(password, badlist):
  164. if len(password) < 1:
  165. fail(_(short_password_msg))
  166. if password in badlist:
  167. fail(_(name_password_msg))
  168. def checkGroupName(name):
  169. valid = ascii_letters + "_-"
  170. if name == "" or len(filter(lambda x: not x in valid, name)) != 0:
  171. fail(_(invalid_group_msg) + " " + name)
  172. #
  173. class User:
  174. def __init__(self):
  175. self.password = None
  176. def __str__(self):
  177. return "%s (%d, %d)\n %s\n %s\n %s\n %s" % (
  178. self.name, self.uid, self.gid,
  179. self.realname, self.homedir, self.shell,
  180. self.password
  181. )
  182. class Group:
  183. def __str__(self):
  184. s = "%s (%d)" % (self.name, self.gid)
  185. for name in self.members:
  186. s += "\n %s" % name
  187. return s
  188. class Database:
  189. passwd_path = "/etc/passwd"
  190. shadow_path = "/etc/shadow"
  191. group_path = "/etc/group"
  192. lock_path = "/etc/.pwd.lock"
  193. def __init__(self, for_read=False):
  194. self.lock = FileLock(self.lock_path)
  195. self.lock.lock(shared=for_read)
  196. self.users = {}
  197. self.users_by_name = {}
  198. self.groups = {}
  199. self.groups_by_name = {}
  200. for line in file(self.passwd_path):
  201. if line != "" and line != "\n":
  202. parts = line.rstrip("\n").split(":")
  203. user = User()
  204. user.name = parts[0]
  205. user.uid = int(parts[2])
  206. user.gid = int(parts[3])
  207. user.realname = parts[4]
  208. user.homedir = parts[5]
  209. user.shell = parts[6]
  210. self.users[user.uid] = user
  211. self.users_by_name[user.name] = user
  212. for line in file(self.shadow_path):
  213. if line != "" and line != "\n":
  214. parts = line.rstrip("\n").split(":")
  215. if self.users_by_name.has_key(parts[0]):
  216. user = self.users_by_name[parts[0]]
  217. user.password = parts[1]
  218. user.pwrest = parts[2:]
  219. for line in file(self.group_path):
  220. if line != "" and line != "\n":
  221. parts = line.rstrip("\n").split(":")
  222. group = Group()
  223. group.name = parts[0]
  224. group.gid = int(parts[2])
  225. group.members = parts[3].split(",")
  226. if "" in group.members:
  227. group.members.remove("")
  228. self.groups[group.gid] = group
  229. self.groups_by_name[group.name] = group
  230. def sync(self):
  231. lines = []
  232. keys = self.users.keys()
  233. keys.sort()
  234. for uid in keys:
  235. user = self.users[uid]
  236. lines.append("%s:x:%d:%d:%s:%s:%s\n" % (
  237. user.name, uid, user.gid,
  238. user.realname, user.homedir, user.shell
  239. ))
  240. f = file(self.passwd_path, "w")
  241. f.writelines(lines)
  242. f.close()
  243. lines = []
  244. keys = self.users.keys()
  245. keys.sort()
  246. for uid in keys:
  247. user = self.users[uid]
  248. if user.password:
  249. lines.append("%s:%s:%s\n" % (
  250. user.name,
  251. user.password,
  252. ":".join(user.pwrest)
  253. ))
  254. else:
  255. lines.append("%s::13094:0:99999:7:::\n" % user.name)
  256. f = file(self.shadow_path, "w")
  257. f.writelines(lines)
  258. f.close()
  259. lines = []
  260. keys = self.groups.keys()
  261. keys.sort()
  262. for gid in keys:
  263. group = self.groups[gid]
  264. lines.append("%s:x:%s:%s\n" % (group.name, gid, ",".join(group.members)))
  265. f = file(self.group_path, "w")
  266. f.writelines(lines)
  267. f.close()
  268. def set_groups(self, name, grouplist):
  269. for gid in self.groups.keys():
  270. g = self.groups[gid]
  271. if name in g.members:
  272. if not g.name in grouplist:
  273. g.members.remove(name)
  274. else:
  275. if g.name in grouplist:
  276. g.members.append(name)
  277. def next_uid(self):
  278. for i in range(uid_minimum, uid_maximum):
  279. if not self.users.has_key(i):
  280. return i
  281. def next_gid(self):
  282. for i in range(uid_minimum, uid_maximum):
  283. if not self.groups.has_key(i):
  284. return i
  285. def setup_home(uid, gid, path):
  286. if not os.path.exists(path):
  287. # Copy skeleton home dir
  288. os.system('/bin/cp -r %s "%s"' % ('/etc/skel', path))
  289. # Set a random face icon
  290. faces = glob.glob("/usr/share/*/apps/kdm/pics/users/*.png")
  291. if len(faces) > 0:
  292. facepath = os.path.join(path, '.face.icon')
  293. os.system('/bin/cp --remove-destination "%s" "%s"' % (random.choice(faces), facepath))
  294. os.chmod(facepath, 0644)
  295. # Set ownerships
  296. os.system('/bin/chown -R %d:%d "%s"' % (uid, gid, path))
  297. # Make sure at least top of the home dir's permissions are correct
  298. os.system('/bin/chown %d:%d "%s"' % (uid, gid, path))
  299. os.chmod(path, 0711)
  300. # methods
  301. def userList():
  302. def format(dict, uid):
  303. item = dict[uid]
  304. return (item.uid, item.name, item.realname)
  305. db = Database(for_read=True)
  306. return map(lambda x: format(db.users, x), db.users)
  307. def userInfo(uid):
  308. uid = int(uid)
  309. db = Database(for_read=True)
  310. if db.users.has_key(uid):
  311. u = db.users[uid]
  312. groups = []
  313. for item in db.groups.keys():
  314. if u.name in db.groups[item].members:
  315. groups.append(db.groups[item].name)
  316. grp = db.groups.get(u.gid, None)
  317. if grp:
  318. if grp.name in groups:
  319. groups.remove(grp.name)
  320. groups.insert(0, grp.name)
  321. ret = (
  322. u.name,
  323. u.realname,
  324. u.gid,
  325. u.homedir,
  326. u.shell,
  327. groups,
  328. )
  329. return ret
  330. else:
  331. fail(_(no_user_msg))
  332. def addUser(uid, name, realname, homedir, shell, password, groups, grants, blocks):
  333. if not realname:
  334. realname = ""
  335. if not homedir:
  336. homedir = "/home/" + name
  337. if not shell:
  338. shell = "/bin/bash"
  339. if not groups:
  340. groups = ["nogroup"]
  341. for item in groups:
  342. checkGroupName(item)
  343. checkName(name)
  344. checkRealName(realname)
  345. if password:
  346. checkPassword(password, (name, realname))
  347. db = Database()
  348. if uid == -1:
  349. uid = db.next_uid()
  350. else:
  351. try:
  352. uid = int(uid)
  353. if uid < 0 or uid > 65536:
  354. raise
  355. except:
  356. fail(_(invalid_userid_msg))
  357. if db.users.has_key(uid):
  358. fail(_(used_userid_msg))
  359. if db.users_by_name.has_key(name):
  360. fail(_(used_username_msg))
  361. # First group in the list is the user's main group
  362. g = db.groups_by_name.get(groups[0], None)
  363. if not g:
  364. fail(_(no_group_msg))
  365. gid = g.gid
  366. u = User()
  367. u.uid = uid
  368. u.gid = gid
  369. u.name = name
  370. u.realname = realname
  371. u.homedir = homedir
  372. u.shell = shell
  373. if password:
  374. u.password = shadowCrypt(password)
  375. else:
  376. u.password = "*"
  377. u.pwrest = [ "13094", "0", "99999", "7", "", "", "" ]
  378. db.users[uid] = u
  379. db.set_groups(name, groups)
  380. # No need to setup a real home dir for daemons
  381. if uid >= 1000 or homedir.startswith("/home/"):
  382. setup_home(uid, gid, homedir)
  383. setFace(uid, homedir)
  384. db.sync()
  385. for grant in grants:
  386. if grant != "":
  387. grantAuthorization(uid, grant)
  388. for block in blocks:
  389. if block != "":
  390. blockAuthorization(uid, block)
  391. return uid
  392. def setUser(uid, realname, homedir, shell, password, groups):
  393. uid = int(uid)
  394. db = Database()
  395. u = db.users.get(uid, None)
  396. if u:
  397. if realname:
  398. checkRealName(realname)
  399. u.realname = realname
  400. if homedir:
  401. u.homedir = homedir
  402. if shell:
  403. u.shell = shell
  404. if password:
  405. checkPassword(password, (u.name, u.realname, realname))
  406. u.password = shadowCrypt(password)
  407. if groups:
  408. # FIXME: check main group
  409. for item in groups:
  410. checkGroupName(item)
  411. db.set_groups(u.name, groups)
  412. db.sync()
  413. else:
  414. fail(_(no_user_msg))
  415. def deleteUser(uid, deletefiles):
  416. uid = int(uid)
  417. if uid == 0:
  418. fail(_(delete_root_msg))
  419. db = Database()
  420. u = db.users.get(uid, None)
  421. if u:
  422. #delete authorizations of user
  423. try:
  424. polkit.auth_revoke_all(uid)
  425. except:
  426. pass
  427. home = u.homedir[:]
  428. db.set_groups(u.name, [])
  429. del db.users[uid]
  430. db.sync()
  431. if deletefiles:
  432. os.system('/bin/rm -rf "%s"' % home)
  433. def groupList():
  434. def format(dict, gid):
  435. item = dict[gid]
  436. return (item.gid, item.name)
  437. db = Database(for_read=True)
  438. return map(lambda x: format(db.groups, x), db.groups)
  439. def addGroup(gid, name):
  440. checkGroupName(name)
  441. db = Database()
  442. if gid == -1:
  443. gid = db.next_gid()
  444. else:
  445. try:
  446. gid = int(gid)
  447. if gid < 0 or gid > 65536:
  448. raise
  449. except:
  450. fail(_(invalid_groupid_msg))
  451. if db.groups.has_key(gid):
  452. fail(_(used_groupid_msg))
  453. if db.groups_by_name.has_key(name):
  454. fail(_(used_groupname_msg))
  455. g = Group()
  456. g.gid = gid
  457. g.name = name
  458. g.members = []
  459. db.groups[gid] = g
  460. db.sync()
  461. return gid
  462. def deleteGroup(gid):
  463. gid = int(gid)
  464. db = Database()
  465. if db.groups.has_key(gid):
  466. del db.groups[gid]
  467. db.sync()
  468. #
  469. # Crypt function for shadow file
  470. #
  471. def shadowCrypt(password):
  472. des_salt = list('./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
  473. salt, magic = str(random.random())[-8:], '$1$'
  474. ctx = hashlib.md5(password)
  475. ctx.update(magic)
  476. ctx.update(salt)
  477. ctx1 = hashlib.md5(password)
  478. ctx1.update(salt)
  479. ctx1.update(password)
  480. final = ctx1.digest()
  481. for i in range(len(password), 0 , -16):
  482. if i > 16:
  483. ctx.update(final)
  484. else:
  485. ctx.update(final[:i])
  486. i = len(password)
  487. while i:
  488. if i & 1:
  489. ctx.update('\0')
  490. else:
  491. ctx.update(password[:1])
  492. i = i >> 1
  493. final = ctx.digest()
  494. for i in range(1000):
  495. ctx1 = hashlib.md5()
  496. if i & 1:
  497. ctx1.update(password)
  498. else:
  499. ctx1.update(final)
  500. if i % 3: ctx1.update(salt)
  501. if i % 7: ctx1.update(password)
  502. if i & 1:
  503. ctx1.update(final)
  504. else:
  505. ctx1.update(password)
  506. final = ctx1.digest()
  507. def _to64(v, n):
  508. r = ''
  509. while (n-1 >= 0):
  510. r = r + des_salt[v & 0x3F]
  511. v = v >> 6
  512. n = n - 1
  513. return r
  514. rv = magic + salt + '$'
  515. final = map(ord, final)
  516. l = (final[0] << 16) + (final[6] << 8) + final[12]
  517. rv = rv + _to64(l, 4)
  518. l = (final[1] << 16) + (final[7] << 8) + final[13]
  519. rv = rv + _to64(l, 4)
  520. l = (final[2] << 16) + (final[8] << 8) + final[14]
  521. rv = rv + _to64(l, 4)
  522. l = (final[3] << 16) + (final[9] << 8) + final[15]
  523. rv = rv + _to64(l, 4)
  524. l = (final[4] << 16) + (final[10] << 8) + final[5]
  525. rv = rv + _to64(l, 4)
  526. l = final[11]
  527. rv = rv + _to64(l, 2)
  528. return rv
  529. #
  530. # List authorizations by UID
  531. #
  532. def listUserAuthorizations(uid):
  533. actions = polkit.auth_list_uid(int(uid))
  534. auths = []
  535. for action in actions:
  536. action_info = polkit.action_info(action['action_id'])
  537. auths.append((action['action_id'], action['scope'], action_info['description'], action_info['policy_active'], action['negative']))
  538. return auths
  539. #
  540. # Grant authorization to user
  541. #
  542. def grantAuthorization(uid, action):
  543. uid = int(uid)
  544. if action == "*":
  545. for action_id in polkit.action_list():
  546. try:
  547. polkit.auth_revoke(uid, action_id)
  548. polkit.auth_add(action_id, polkit.SCOPE_ALWAYS, uid)
  549. except:
  550. return False
  551. else:
  552. try:
  553. polkit.auth_revoke(uid, action)
  554. polkit.auth_add(action, polkit.SCOPE_ALWAYS, uid)
  555. except:
  556. return False
  557. return True
  558. #
  559. # Revoke authorization of user
  560. #
  561. def revokeAuthorization(uid, action):
  562. uid = int(uid)
  563. if action == "*":
  564. for action_id in polkit.action_list():
  565. try:
  566. polkit.auth_revoke(uid, action_id)
  567. except:
  568. return False
  569. else:
  570. try:
  571. polkit.auth_revoke(uid, action)
  572. except:
  573. return False
  574. return True
  575. #
  576. # Block authorization of user
  577. #
  578. def blockAuthorization(uid, action):
  579. uid = int(uid)
  580. if action == "*":
  581. for action_id in polkit.action_list():
  582. try:
  583. polkit.auth_revoke(uid, action_id)
  584. polkit.auth_block(uid, action_id)
  585. except:
  586. return False
  587. else:
  588. try:
  589. polkit.auth_revoke(uid, action)
  590. polkit.auth_block(uid, action)
  591. except:
  592. return False
  593. return True