bugzilla_ldapsync.rb 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #!/usr/local/bin/ruby
  2. # Queries an LDAP server for all email addresses (tested against Exchange 5.5),
  3. # and makes nice bugzilla user entries out of them. Also disables Bugzilla users
  4. # that are not found in LDAP.
  5. # $Id$
  6. require 'ldap'
  7. require 'dbi'
  8. require 'getoptlong'
  9. opts = GetoptLong.new(
  10. ['--dbname', '-d', GetoptLong::OPTIONAL_ARGUMENT],
  11. ['--dbpassword', '-p', GetoptLong::OPTIONAL_ARGUMENT],
  12. ['--dbuser', '-u', GetoptLong::OPTIONAL_ARGUMENT],
  13. ['--dbpassfile', '-P', GetoptLong::OPTIONAL_ARGUMENT],
  14. ['--ldaphost', '-h', GetoptLong::REQUIRED_ARGUMENT],
  15. ['--ldapbase', '-b', GetoptLong::OPTIONAL_ARGUMENT],
  16. ['--ldapquery', '-q', GetoptLong::OPTIONAL_ARGUMENT],
  17. ['--maildomain', '-m', GetoptLong::OPTIONAL_ARGUMENT],
  18. ['--noremove', '-n', GetoptLong::OPTIONAL_ARGUMENT],
  19. ['--defaultpass', '-D', GetoptLong::OPTIONAL_ARGUMENT],
  20. ['--checkmode', '-c', GetoptLong::OPTIONAL_ARGUMENT]
  21. )
  22. # in hash to make it easy
  23. optHash = Hash.new
  24. opts.each do |opt, arg|
  25. optHash[opt]=arg
  26. end
  27. # grab password from file if it's an option
  28. if optHash['--dbpassfile']
  29. dbPassword=File.open(optHash['--dbpassfile'], 'r').readlines[0].chomp!
  30. else
  31. dbPassword=optHash['--dbpassword'] || nil
  32. end
  33. # make bad assumptions.
  34. dbName = optHash['--dbname'] || 'bugzilla'
  35. dbUser = optHash['--dbuser'] || 'bugzilla'
  36. ldapHost = optHash['--ldaphost'] || 'ldap'
  37. ldapBase = optHash['--ldapbase'] || ''
  38. mailDomain = optHash['--maildomain'] || `domainname`.chomp!
  39. ldapQuery = optHash['--ldapquery'] || "(&(objectclass=person)(rfc822Mailbox=*@#{mailDomain}))"
  40. checkMode = optHash['--checkmode'] || nil
  41. noRemove = optHash['--noremove'] || nil
  42. defaultPass = optHash['--defaultpass'] || 'bugzilla'
  43. if (! dbPassword)
  44. puts "bugzilla_ldapsync v1.3 (c) 2003 Thomas Stromberg <thomas+bugzilla@stromberg.org>"
  45. puts ""
  46. puts " -d | --dbname name of MySQL database [#{dbName}]"
  47. puts " -u | --dbuser username for MySQL database [#{dbUser}]"
  48. puts " -p | --dbpassword password for MySQL user [#{dbPassword}]"
  49. puts " -P | --dbpassfile filename containing password for MySQL user"
  50. puts " -h | --ldaphost hostname for LDAP server [#{ldapHost}]"
  51. puts " -b | --ldapbase Base of LDAP query, for instance, o=Bugzilla.com"
  52. puts " -q | --ldapquery LDAP query, uses maildomain [#{ldapQuery}]"
  53. puts " -m | --maildomain e-mail domain to use records from"
  54. puts " -n | --noremove do not remove Bugzilla users that are not in LDAP"
  55. puts " -c | --checkmode checkmode, does not perform any SQL changes"
  56. puts " -D | --defaultpass default password for new users [#{defaultPass}]"
  57. puts
  58. puts "example:"
  59. puts
  60. puts " bugzilla_ldapsync.rb -c -u taskzilla -P /tmp/test -d taskzilla -h bhncmail -m \"bowebellhowell.com\""
  61. exit
  62. end
  63. if (checkMode)
  64. puts '(checkmode enabled, no SQL writes will actually happen)'
  65. puts "ldapquery is #{ldapQuery}"
  66. puts
  67. end
  68. bugzillaUsers = Hash.new
  69. ldapUsers = Hash.new
  70. encPassword = defaultPass.crypt('xx')
  71. sqlNewUser = "INSERT INTO profiles VALUES ('', ?, '#{encPassword}', ?, '', 1, NULL, '0000-00-00 00:00:00');"
  72. # presumes that the MySQL database is local.
  73. dbh = DBI.connect("DBI:Mysql:#{dbName}", dbUser, dbPassword)
  74. # select all e-mail addresses where there is no disabledtext defined. Only valid users, please!
  75. dbh.select_all('select login_name, realname, disabledtext from profiles') { |row|
  76. login = row[0].downcase
  77. bugzillaUsers[login] = Hash.new
  78. bugzillaUsers[login]['desc'] = row[1]
  79. bugzillaUsers[login]['disabled'] = row[2]
  80. #puts "bugzilla has #{login} - \"#{bugzillaUsers[login]['desc']}\" (#{bugzillaUsers[login]['disabled']})"
  81. }
  82. LDAP::Conn.new(ldapHost, 389).bind{|conn|
  83. sub = nil
  84. # perform the query, but only get the e-mail address, location, and name returned to us.
  85. conn.search(ldapBase, LDAP::LDAP_SCOPE_SUBTREE, ldapQuery,
  86. ['rfc822Mailbox', 'physicalDeliveryOfficeName', 'cn']) { |entry|
  87. # Get the users first (primary) e-mail address, but I only want what's before the @ sign.
  88. entry.vals("rfc822Mailbox")[0] =~ /([\w\.-]+)\@/
  89. email = $1
  90. # We put the officename in the users description, and nothing otherwise.
  91. if entry.vals("physicalDeliveryOfficeName")
  92. location = entry.vals("physicalDeliveryOfficeName")[0]
  93. else
  94. location = ''
  95. end
  96. # for whatever reason, we get blank entries. Do some double checking here.
  97. if (email && (email.length > 4) && (location !~ /Generic/) && (entry.vals("cn")))
  98. if (location.length > 2)
  99. desc = entry.vals("cn")[0] + " (" + location + ")"
  100. else
  101. desc = entry.vals("cn")[0]
  102. end
  103. # take care of the whitespace.
  104. desc.sub!("\s+$", "")
  105. desc.sub!("^\s+", "")
  106. # dumb hack. should be properly escaped, and apostrophes should never ever ever be in email.
  107. email.sub!("\'", "%")
  108. email.sub!('%', "\'")
  109. email=email.downcase
  110. ldapUsers[email.downcase] = Hash.new
  111. ldapUsers[email.downcase]['desc'] = desc
  112. ldapUsers[email.downcase]['disabled'] = nil
  113. #puts "ldap has #{email} - #{ldapUsers[email.downcase]['desc']}"
  114. end
  115. }
  116. }
  117. # This is the loop that takes the users that we found in Bugzilla originally, and
  118. # checks to see if they are still in the LDAP server. If they are not, away they go!
  119. ldapUsers.each_key { |user|
  120. # user does not exist at all.
  121. #puts "checking ldap user #{user}"
  122. if (! bugzillaUsers[user])
  123. puts "+ Adding #{user} - #{ldapUsers[user]['desc']}"
  124. if (! checkMode)
  125. dbh.do(sqlNewUser, user, ldapUsers[user]['desc'])
  126. end
  127. # short-circuit now.
  128. next
  129. end
  130. if (bugzillaUsers[user]['desc'] != ldapUsers[user]['desc'])
  131. puts "* Changing #{user} from \"#{bugzillaUsers[user]['desc']}\" to \"#{ldapUsers[user]['desc']}\""
  132. if (! checkMode)
  133. # not efficient.
  134. dbh.do("UPDATE profiles SET realname = ? WHERE login_name = ?", ldapUsers[user]['desc'], user)
  135. end
  136. end
  137. if (bugzillaUsers[user]['disabled'].length > 0)
  138. puts "+ Enabling #{user} (was \"#{bugzillaUsers[user]['disabled']}\")"
  139. if (! checkMode)
  140. dbh.do("UPDATE profiles SET disabledtext = NULL WHERE login_name=\"#{user}\"")
  141. end
  142. end
  143. }
  144. if (! noRemove)
  145. bugzillaUsers.each_key { |user|
  146. if ((bugzillaUsers[user]['disabled'].length < 1) && (! ldapUsers[user]))
  147. puts "- Disabling #{user} (#{bugzillaUsers[user]['disabled']})"
  148. if (! checkMode)
  149. dbh.do("UPDATE profiles SET disabledtext = \'auto-disabled by ldap sync\' WHERE login_name=\"#{user}\"")
  150. end
  151. end
  152. }
  153. end
  154. dbh.disconnect