PageRenderTime 33ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/iRedMail/tools/create_mail_user_OpenLDAP.py

https://bitbucket.org/zhb/iredmail
Python | 311 lines | 262 code | 14 blank | 35 comment | 3 complexity | e05792e012e0505fca58a9c4c0a76601 MD5 | raw file
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Author: Zhang Huangbin <zhb _at_ iredmail.org>
  4. # Purpose: Add new OpenLDAP user for postfix mail server.
  5. # Project: iRedMail (http://www.iredmail.org/)
  6. # --------------------------- WARNING ------------------------------
  7. # This script only works under iRedMail >= 0.4.0 due to ldap schema
  8. # changes.
  9. # ------------------------------------------------------------------
  10. # ---------------------------- USAGE -------------------------------
  11. # Put your user list in a csv format file, e.g. users.csv, and then
  12. # import users listed in the file:
  13. #
  14. # $ python create_mail_user_OpenLDAP.py users.csv
  15. #
  16. # ------------------------------------------------------------------
  17. # ------------------------- SETTINGS -------------------------------
  18. # LDAP server address.
  19. LDAP_URI = 'ldap://127.0.0.1:389'
  20. # LDAP base dn.
  21. BASEDN = 'o=domains,dc=example,dc=com'
  22. # Bind dn/password
  23. BINDDN = 'cn=Manager,dc=example,dc=com'
  24. BINDPW = 'password'
  25. # Storage base directory.
  26. STORAGE_BASE_DIRECTORY = '/var/vmail/vmail1'
  27. # Append timestamp in maildir path.
  28. APPEND_TIMESTAMP_IN_MAILDIR = True
  29. # Get base directory and storage node.
  30. std = STORAGE_BASE_DIRECTORY.rstrip('/').split('/')
  31. STORAGE_NODE = std.pop()
  32. STORAGE_BASE = '/'.join(std)
  33. # Hashed maildir: True, False.
  34. # Example:
  35. # domain: domain.ltd,
  36. # user: zhang (zhang@domain.ltd)
  37. #
  38. # - hashed: d/do/domain.ltd/z/zh/zha/zhang/
  39. # - normal: domain.ltd/zhang/
  40. HASHED_MAILDIR = True
  41. # Default password schemes.
  42. # Multiple passwords are supported if you separate schemes with '+'.
  43. # For example: 'SSHA+NTLM', 'CRAM-MD5+SSHA', 'CRAM-MD5+SSHA+MD5'.
  44. DEFAULT_PASSWORD_SCHEME = 'SSHA'
  45. # Do not prefix password scheme name in password hash.
  46. HASHES_WITHOUT_PREFIXED_PASSWORD_SCHEME = ['NTLM']
  47. # ------------------------------------------------------------------
  48. import os
  49. import sys
  50. import time
  51. from subprocess import Popen, PIPE
  52. from base64 import b64encode
  53. import re
  54. try:
  55. import ldap
  56. import ldif
  57. except ImportError:
  58. print '''
  59. Error: You don't have python-ldap installed, Please install it first.
  60. You can install it like this:
  61. - On RHEL/CentOS 5.x:
  62. $ sudo yum install python-ldap
  63. - On Debian & Ubuntu:
  64. $ sudo apt-get install python-ldap
  65. '''
  66. sys.exit()
  67. def usage():
  68. print '''
  69. CSV file format:
  70. domain name, username, password, [common name], [quota_in_bytes], [groups]
  71. Example #1:
  72. iredmail.org, zhang, plain_password, Zhang Huangbin, 104857600, group1:group2
  73. Example #2:
  74. iredmail.org, zhang, plain_password, Zhang Huangbin, ,
  75. Example #3:
  76. iredmail.org, zhang, plain_password, , 104857600, group1:group2
  77. Note:
  78. - Domain name, username and password are REQUIRED, others are optional:
  79. + common name.
  80. * It will be the same as username if it's empty.
  81. * Non-ascii character is allowed in this field, they will be
  82. encoded automaticly. Such as Chinese, Korea, Japanese, etc.
  83. + quota. It will be 0 (unlimited quota) if it's empty.
  84. + groups.
  85. * valid group name (hr@a.cn): hr
  86. * incorrect group name: hr@a.cn
  87. * Do *NOT* include domain name in group name, it will be
  88. appended automaticly.
  89. * Multiple groups must be seperated by colon.
  90. - Leading and trailing Space will be ignored.
  91. '''
  92. def conv_mail_to_user_dn(email):
  93. """Convert email address to ldap dn of normail mail user."""
  94. if email.count('@') != 1:
  95. return ''
  96. user, domain = email.split('@')
  97. # User DN format.
  98. # mail=user@domain.ltd,domainName=domain.ltd,[LDAP_BASEDN]
  99. dn = 'mail=%s,ou=Users,domainName=%s,%s' % (email, domain, BASEDN)
  100. return dn
  101. def generate_password_hash(p, pwscheme=None):
  102. """Generate password for LDAP mail user and admin."""
  103. p = str(p).strip()
  104. if not pwscheme:
  105. pwscheme = DEFAULT_PASSWORD_SCHEME
  106. # Supports returning multiple passwords.
  107. pw_schemes = pwscheme.split('+')
  108. pws = []
  109. for scheme in pw_schemes:
  110. if scheme == 'PLAIN':
  111. pws.append(p)
  112. else:
  113. pw = generate_password_with_doveadmpw(scheme, p)
  114. if scheme in HASHES_WITHOUT_PREFIXED_PASSWORD_SCHEME:
  115. pw = pw.lstrip('{' + scheme + '}')
  116. pws.append(pw)
  117. return pws
  118. def generate_ssha_password(p):
  119. p = str(p).strip()
  120. salt = os.urandom(8)
  121. try:
  122. from hashlib import sha1
  123. pw = sha1(p)
  124. except ImportError:
  125. import sha
  126. pw = sha.new(p)
  127. pw.update(salt)
  128. return "{SSHA}" + b64encode(pw.digest() + salt)
  129. def generate_password_with_doveadmpw(scheme, plain_password):
  130. """Generate password hash with `doveadm pw` command.
  131. Return SSHA instead if no 'doveadm' command found or other error raised."""
  132. # scheme: CRAM-MD5, NTLM
  133. scheme = scheme.upper()
  134. p = str(plain_password).strip()
  135. try:
  136. pp = Popen(['doveadm', 'pw', '-s', scheme, '-p', p],
  137. stdout=PIPE)
  138. pw = pp.communicate()[0]
  139. if scheme in HASHES_WITHOUT_PREFIXED_PASSWORD_SCHEME:
  140. pw.lstrip('{' + scheme + '}')
  141. # remove '\n'
  142. pw = pw.strip()
  143. return pw
  144. except:
  145. return generate_ssha_password(p)
  146. def ldif_mailuser(domain, username, passwd, cn, quota, groups=''):
  147. # Append timestamp in maildir path
  148. DATE = time.strftime('%Y.%m.%d.%H.%M.%S')
  149. TIMESTAMP_IN_MAILDIR = ''
  150. if APPEND_TIMESTAMP_IN_MAILDIR:
  151. TIMESTAMP_IN_MAILDIR = '-%s' % DATE
  152. if quota == '':
  153. quota = '0'
  154. # Remove SPACE in username.
  155. username = str(username).lower().strip().replace(' ', '')
  156. if cn == '':
  157. cn = username
  158. mail = username + '@' + domain
  159. dn = conv_mail_to_user_dn(mail)
  160. # Get group list.
  161. if groups.strip() != '':
  162. groups = groups.strip().split(':')
  163. for i in range(len(groups)):
  164. groups[i] = groups[i] + '@' + domain
  165. maildir_domain = str(domain).lower()
  166. if HASHED_MAILDIR is True:
  167. str1 = str2 = str3 = username[0]
  168. if len(username) >= 3:
  169. str2 = username[1]
  170. str3 = username[2]
  171. elif len(username) == 2:
  172. str2 = str3 = username[1]
  173. maildir_user = "%s/%s/%s/%s%s/" % (str1, str2, str3, username, TIMESTAMP_IN_MAILDIR, )
  174. mailMessageStore = maildir_domain + '/' + maildir_user
  175. else:
  176. mailMessageStore = "%s/%s%s/" % (domain, username, TIMESTAMP_IN_MAILDIR)
  177. homeDirectory = STORAGE_BASE_DIRECTORY + '/' + mailMessageStore
  178. mailMessageStore = STORAGE_NODE + '/' + mailMessageStore
  179. ldif = [
  180. ('objectClass', ['inetOrgPerson', 'mailUser', 'shadowAccount', 'amavisAccount']),
  181. ('mail', [mail]),
  182. ('userPassword', generate_password_hash(passwd)),
  183. ('mailQuota', [quota]),
  184. ('cn', [cn]),
  185. ('sn', [username]),
  186. ('uid', [username]),
  187. ('storageBaseDirectory', [STORAGE_BASE]),
  188. ('mailMessageStore', [mailMessageStore]),
  189. ('homeDirectory', [homeDirectory]),
  190. ('accountStatus', ['active']),
  191. ('enabledService', ['internal', 'doveadm', 'lib-storage', 'indexer-worker', 'dsync',
  192. 'mail', 'smtp', 'smtpsecured',
  193. 'pop3', 'pop3secured', 'imap', 'imapsecured',
  194. 'deliver', 'lda', 'forward', 'senderbcc', 'recipientbcc',
  195. 'managesieve', 'managesievesecured',
  196. 'sieve', 'sievesecured', 'lmtp', 'sogo',
  197. 'shadowaddress',
  198. 'displayedInGlobalAddressBook']),
  199. ('memberOfGroup', groups),
  200. # shadowAccount integration.
  201. ('shadowLastChange', ['0']),
  202. # Amavisd integration.
  203. ('amavisLocal', ['TRUE'])]
  204. return dn, ldif
  205. if len(sys.argv) != 2 or len(sys.argv) > 2:
  206. print """Usage: $ python %s users.csv""" % sys.argv[0]
  207. usage()
  208. sys.exit()
  209. else:
  210. CSV = sys.argv[1]
  211. if not os.path.exists(CSV):
  212. print '''Erorr: file not exist:''', CSV
  213. sys.exit()
  214. ldif_file = CSV + '.ldif'
  215. # Remove exist LDIF file.
  216. if os.path.exists(ldif_file):
  217. print '''< INFO > Remove exist file:''', ldif_file
  218. os.remove(ldif_file)
  219. # Read user list.
  220. userList = open(CSV, 'rb')
  221. # Convert to LDIF format.
  222. for entry in userList.readlines():
  223. entry = entry.rstrip()
  224. domain, username, passwd, cn, quota, groups = re.split('\s?,\s?', entry)
  225. dn, data = ldif_mailuser(domain, username, passwd, cn, quota, groups)
  226. # Write LDIF data.
  227. result = open(ldif_file, 'a')
  228. ldif_writer = ldif.LDIFWriter(result)
  229. ldif_writer.unparse(dn, data)
  230. ldif_file_path = os.path.abspath(ldif_file)
  231. print "< INFO > User data are stored in %s, you can verify it before importing it." % ldif_file_path
  232. print "< INFO > You can import it with below command:"
  233. print "ldapadd -x -D %s -W -f %s" % (BINDDN, ldif_file_path)
  234. # Prompt to import user data.
  235. '''
  236. answer = raw_input("Would you like to import them now? [y|N]").lower().strip()
  237. if answer == 'y':
  238. # Import data.
  239. conn = ldap.initialize(LDAP_URI)
  240. conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3) # Use LDAP v3
  241. conn.bind_s(BINDDN, BINDPW)
  242. conn.unbind()
  243. else:
  244. pass
  245. '''