OpenLDAP 整合方案 By Tommy Wu Version 0.07 有位 h 開頭, y 結尾的大大, 在很久很久之前就說要開個 LDAP 的課, 不知道他現在的鼻子長到什麼地步了. 我目前的公司用的 email 是使用 MS 的 Exchange Server (因為母公司 就是用這個, 且目前都用同一個 domain name, 只是我們這兒的使用者 的信件會由美國轉到本地的 Exchange Server 主機上頭), 不過... 因 為目前並非所有的員工都有 email, 也由於半個月之前, 母公司被別家 公司併購了, 好像交易不包含這間子公司, 所以, 我們目前除了少數的 使用者可以需要使用母公司的 email domain 來與客戶溝通外, 其他的 使用者並不適合繼續使用母公司的 email domain. 因此, 我的老闆決定 自己架設自己的 mail server 來使用. 當然, 要我來做, 我一點都不想用 Exchange Server 這種大怪獸, 我的 老闆對於 MS 的 server 產品也沒什麼信心, 所以, 就考慮使用 linux 上的 solution. 老闆只有一個要求, 在原本 Exchange Server 上面使 用 Address Book 的那種方法, 可否繼續存在. 也就是大家會有一個共 用的 Address Book, 且使用上必須要很方便. 另外就是, 我們目前有很多員工, 在上班的時候, 就只會使用到 browser, 這些機器上面裝的 Win2000, 就只有開機做一次使用者認證, 控制使用 者能用的功能, 其他就只用到 IE. 這個授權的費用... 數百台加一加也 是很可觀, 所以, 日後可能也會改用 linux 的桌面來取代. 所以, 很直覺的就想到使用 OpenLDAP 來管理使用者. 先把目前使用到的東西說一下: Debian GNU/Linux 3.0 OpenLDAP DaveDAP Postfix GroupMail Amavisd-new TrendMicro vscan Open Webmail 2.0 Netscape 7.02 phpBB 2.0.4 BugZilla 2.16.4 ... ### OpenLDAP ### http://www.openldap.org/ 首先, 參考下面的網址, 在 Debian 上使用 OpenLDAP 來管理使用者: http://www.shellhung.org/technical/ldapauth.html 在 slapd.conf 中的 schema 設定, 我們加上了 extension, misc, postix 等額外的 schema: # Schema and objectClass definitions include /etc/ldap/schema/core.schema include /etc/ldap/schema/cosine.schema include /etc/ldap/schema/nis.schema include /etc/ldap/schema/inetorgperson.schema include /etc/ldap/schema/extension.schema include /etc/ldap/schema/misc.schema include /etc/ldap/schema/postfix.schema 其中, extension.schema 內有提到要改一些其他的 schema 才能用. 另外, postfix.schema 內容如下: # postfix.schema begin attributetype ( 1.3.6.1.4.1.4203.666.1.200 NAME 'mailacceptinggeneralid' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} ) attributetype ( 1.3.6.1.4.1.4203.666.1.201 NAME 'maildrop' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} ) attributetype ( 1.3.6.1.4.1.4203.666.1.202 NAME 'mailquotum' EQUALITY integerMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) attributetype ( 1.3.6.1.4.1.4203.666.1.203 NAME 'relaydomain' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} ) # objectClass ( 1.3.6.1.4.1.4203.666.1.100 NAME 'AliasesUser' DESC 'Aliases Mail User' SUP top STRUCTURAL MAY ( mailacceptinggeneralid $ maildrop $ mailquotum $ relaydomain ) ) # postfix.schema end 這個 postfix.schema 我原本認為用不到, 但是在加上 aliases 要支援 LDAP 的時候, 一直沒辦法成功, 直到使用 maildrop/mailacceptinggeneralid 才正常. 然後, 在 ACL 的設定上, 如果你不打算提供 anonymous 的讀取權限, 可以把 by * read 改成 by dn=".+" read 這樣子就必須使用內部的使用者登入後才可以讀取資料. 另外, 在使用 migration-passwd.pl 轉換使用者的資料後, 發 現有些屬性並沒有加上, 所以我另外加上了下面的資料: objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: officePerson sn: xxx mail: xxx title::Ti9B ou::Ti9B o: xxx comment: xxx telephoneNumber::Ti9B homePhone::Ti9B mobile::Ti9B 最後, 把一些系統本身的使用者排除, 並不是全部使用 LDAP. 在 aliases 上的設定, 我使用下面的 ldif 來記錄: dn: ou=MailList,dc=xxx,dc=com objectClass: organizationalUnit ou: MailList 每一筆 alias 使用下面的 ldif 來記錄: dn: cn=some_group,ou=MailList,dc=xxx,dc=com cn: xxx sn: xxx objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: officePerson objectClass: AliasesUser mailacceptinggeneralid: xxx mail: xxxx o: xxxx comment: xxxx maildrop: xxxx 為了方便找 email 的 Address Book, 所以也使用 mail 屬性. 另外 postfix 好像就只認得 mailacceptinggeneralid 當做 Aliases, maildrop 當做實際的收信人, 所以, 只好將就使用. (依照 google 上找到的資料來看, 應該在 main.cf 中可以設 定要找的屬性, 與回傳的屬性, 但... 試過都不成功) 這樣子, 在 OpenLDAP 上就完成了. 最後, 編輯 /etc/libnss-ldap.conf 的內容, 把有關 LDAP 伺 服器的設定確定後, 把 nss_base_passwd, nss_base_shadow 改 為: nss_base_passwd ou=People,dc=xxxx,dc=com nss_base_shadow ou=People,dc=xxxx,dc=com 另外, 把 /etc/nsswitch.conf 中 passwd: compat group: compat shadow: compat 改為 passwd: compat ldap group: compat ldap shadow: compat ldap 以確定所以在 passwd 中的使用者與 LDAP 中的使用者都可以使用. 最後, 修改 /etc/pam.d/ 下的檔案 (幾乎每個都要). 把原本有使用到 pam_unix.so 的設定: auth required pam_unix.so nullok account required pam_unix.so session required pam_unix.so 改為: auth sufficient pam_ldap.so auth required pam_unix.so nullok try_first_pass account sufficient pam_ldap.so account required pam_unix.so session required pam_mkhomedir.so skel=/etc/skel umask=0022 session sufficient pam_ldap.so session required pam_unix.so 這樣子做好後, 就可以使用 LDAP 來管理一般的使用者了. 而且在使用者第一次使用的時候, 就自動的建立 home 目錄. (注意一下, 如果你使用的目錄不止一層, 則上一層必須自行建立, 否則會失敗. 例如, 我們的使用者上千位, 如果都放同一個 /home 下面, 會很難找, 所以就在 /home 下面建立 a, b, c....z 等目錄, 把該字母開頭的使用者的 home 建立在那個字母目錄之下.) 至於要放 session 或 auth, account 的位置 應該都可以, 依照你的需求來決定吧. 另外提一下, 對於 ftp/ssh/login 的使用, 我們用 pam_listfile.so 來管理可以使用的使用者: auth required pam_listfile.so item=user sense=allow file=/etc/listfile/loginusers onerr=fail 上面為同一行. 如此, 只有存在於該檔案的使用者可以提供該服務. ProFTPD 似乎不能直接 /etc/ftpusers 來設定提供服務的使用者, 所以 使用另外的檔案來處理就可以. 基本上, 在經過這樣子的處理之後, 多數在 linux 上提供的服務, 都可 以使用 LDAP 了, telnet/ftp/ssh.... 都可以正常使用, passwd 一樣可 以改密碼, 唯一會覺得怪怪的是, 如果 /etc/passwd 沒有同步, 在 login 之後, 看到的不會是自己的名字, 而是 I have no name!. PS. 關於這個 I have no name! 的問題, 我後來在另一台機器上, 發現登 入時並沒有這個情形, 而會顯示正確的名字, 但是在 OpenLDAP 執行的那 一台機器上就不行. (不過在把 /etc/libnss-ldap.conf 這個檔案屬性改 為 644 之後, 也可以正確查到名字) 另外, 實際使用上, 發現需要查詢 user/uid 的情形不少, 光利用 nscd 來 處理, 仍然有時會造成 slapd 的 loading 過重, 所以就把使用者的資料每 天固定轉到 /etc/passwd 來使用. 原本利用 ldap2pass.pl 這個工具來處理, 後來發現 perl 在處理 ldap 的資料時, 速度並不快, 且吃 cpu 不少. 而 php 似乎就沒有這個問題, 所以就自己寫了下面的 php script 來轉. ldap2pass.php #!/usr/bin/php4 -Cq 0) { $result_item = ldap_get_entries($id, $result); $cnt = $result_item["count"]; for ($i = 0; $i < $cnt; $i++) { $people_array[$i] = sprintf("%s:x:%s:%s:%s:%s:%s", $result_item[$i]["uid"][0], $result_item[$i]["uidnumber"][0], $result_item[$i]["gidnumber"][0], $result_item[$i]["gecos"][0], $result_item[$i]["homedirectory"][0], $result_item[$i]["loginshell"][0]); $ldap_cnt++; } } ldap_close($id); echo sprintf("Total %d users in LDAP.\n", $ldap_cnt); $fp = fopen($file, "rt"); if ($fp == 0) { echo "Can't open file: $file\n"; exit; } echo "Query from $file...\n"; $pwd_cnt = 0; while (!feof($fp)) { $buf = chop_newline(fgets($fp, 1024)); if ($buf == "") continue; list($login,$passwd,$uid,$gid,$gcos,$home,$shell) = split (":", $buf, 7); if ($uid < 2000 || $uid >= 65000) { $pwd_array[$pwd_cnt++] = $buf; } } fclose($fp); echo sprintf("Total %d system users in %s.\n", $pwd_cnt, $file); $fp = fopen($file, "wt"); if ($fp == 0) { echo "Can't open file: $file\n"; exit; } echo "\n"; if ($pwd_cnt > 0) { while (list($key, $val) = each($pwd_array)) { if ($val == "") continue; fputs($fp, $val . "\n"); echo "$val\n"; } } if ($ldap_cnt > 0) { while (list($key, $val) = each($people_array)) { if ($val == "") continue; fputs($fp, $val . "\n"); echo "$val\n"; } } fclose($fp); exit; function chop_newline($str) { return chop(preg_replace("(\r\n|\n|\r)", "", $str)); } ?> 另外, 在新增使用者的時候, 因為太多人了, 容易產生相同的 uid, 所以寫了 下面的 script 來查詢最後一個使用者的 uid. get_last_uid.php #!/usr/bin/php4 -Cq 0) { $result_item = ldap_get_entries($id, $result); $cnt = $result_item["count"]; for ($i = 0; $i < $cnt; $i++) { if ($result_item[$i]["uidnumber"][0] > $last_uid) { $last_uid = $result_item[$i]["uidnumber"][0]; $last_name = $result_item[$i]["uid"][0]; } } } ldap_close($id); echo sprintf("last uidnumber is %d, name is %s\n", $last_uid, $last_name); exit; ?> 這個 script 用來查看有無重複的 uid. get_dup_uid.php #!/usr/bin/php4 -Cq 0) { $result_item = ldap_get_entries($id, $result); $cnt = $result_item["count"]; for ($i = 0; $i < $cnt; $i++) { $uid_array[$i] = $result_item[$i]["uidnumber"][0] . ":" . $result_item[$i]["uid"][0]; } } ldap_close($id); if ($cnt <= 0) { echo "Empty list!\n"; exit; } sort($uid_array); $prompt = ""; $last_uidnumber = ""; while (list($key, $val) = each($uid_array)) { list($uidnumber, $uid) = split(":", $val, 2); if ($last_uidnumber == $uidnumber) { echo "$prompt $uid"; $prompt = ""; } else { if ($prompt == "") { echo "\n"; } $prompt = "$uidnumber: $uid"; } $last_uidnumber = $uidnumber; } exit; ?> 這個 script 用來新增使用者. 預設的密碼是 12345678. ldap_adduser.php #!/usr/bin/php4 -Cq $sleep_time) { echo "After $delay_time, still can't get user %uid information.\n"; exit; } sleep(1); $cnt++; } system("$passwd $uid"); } exit; function usage($errmsg) { global $progname; $fp = fopen("php://stderr", "w"); if ($errmsg != "" || $errmsg != false) fputs($fp, "\n$errmsg\n\n"); fwrite($fp, "$progname [-h|-?] {-u uid} {-n name} [-g group(s)] [-p] Options: -h, -? Show this screen -u uid Add user for uid -n name User Display Name -g group List groups for user, seperate by comma -p Ask password after added "); fclose($fp); exit; } ?> 這個 script 用來刪除使用者. ldap_deluser.php #!/usr/bin/php4 -Cq 目前由於把所有的使用者資料都放在 OpenLDAP 上面, 所以有很多服務的查 詢都會到 LDAP 的主機, 為了分散 loading, 也順便做備份, 所以就利用 OpenLDAP 的 replicate 功能來處理. 在 replicate 中, 可以分為 Master 與 Slave 兩種, 一般的資料查詢在兩 者都可以使用, 但是資料的異動就必須只能在 Master 中處理, 然後再更新 所有的 salve 伺服器. 在 master 伺服器上, 我們只要在 slapd.conf 中加上 replicate 的設定就 可以了. 因為目前只有使用 simple 的驗證方式, 密碼會寫在設定中, 所以 就另外用一個檔案設定, 然後把屬性改為 600, 由 slapd.conf 中再 include 進來. replica host=10.100.1.50:389 "binddn=cn=replicator,dc=xxxx,dc=com" bindmethod=simple credentials=password replica host=10.100.1.60:389 "binddn=cn=replicator,dc=xxxx,dc=com" bindmethod=simple credentials=password replica host=10.100.1.70:389 "binddn=cn=replicator,dc=xxxx,dc=com" bindmethod=simple credentials=password 可以設定很多個 slave 伺服器. 然後將 LDAP 的資料備份出來. 可以使用 ldapsearch 的方式取得. 也可以利 用 slapcat 的方式取出. 再用 slapadd 的方式或 ldapadd 的方式在 slave 伺服器上重設. 在 slave 伺服器上, 資料重設好之後, 更改 slapd.conf 的設定. 把 replogfile 的設定移除, 加上下面的設定: # 設定 root 的使用者 # 通常都設定是在 master 中的 replica 設定中的使用者 rootdn "cn=replicator,dc=link2support,dc=com" # root 的密碼, # 如果是用 ldapadd 建立資料庫的話, 在一開始還沒有資料的時候使用 #rootpw {MD5}xxxxxxxx== # 在 master 中 replica 的使用者 updatedn "cn=replicator,dc=link2support,dc=com" # 如果有異動的需求時, 把需求轉到 master 伺服器 updateref ldap://10.120.1.6/dc=xxx,dc=com 這樣子把 master 與 slave 中的 slapd 重新啟動就可以了. 要注意的是, 在 slave 中的的 ACL 設定, 不可以包含在 master 伺服器上有寫入 權限的設定 (所以 rootdn 也不可以設為在 master 伺服器中有權限寫入的使用者). 如此怎個複製的動作才會正常. (如果有權限寫入, 就會直接更改 slave 的資料, 並不會使用 updateref 轉到 master 處理, 資料就會不一致了) ### DaveDAP ### http://students.cs.byu.edu/~djsmith/davedap/ 由於使用者的資料都在 LDAP 之中, 所以當我們要新增, 修改, 刪除使用 者的時候, 都必須要去維護 LDAP 的資料. DaveDAP 是一個 Web 介面的 LDAP 維護程式, 使用上覺得還不錯, 並不會 很難使用, 各位也可以試看看. 當然, 這類的工具不少, 大家可以仔細選擇一下. 我們公司... 不用中文, 所以這些工具的中文相容問題也就不在考慮之內. ### Postfix + Amavisd-new + vscan + spamassassin ### http://www.postfix.org/ Postfix + OpenLDAP 可參考: http://phorum.study-area.org/viewtopic.php?t=15791 Amavisd-new 可參考: http://phorum.study-area.org/viewtopic.php?t=15602 先說明 Postfix 的 main.cf 設定: # user local_recipient_maps = $alias_maps unix:passwd.byname 用來查詢 local 有那些使用者. (移除 ldap 的設定, 基本上, 經測試過後發現, 只使用 unix:passwd.byname 時, 如果在 nsswitch.conf 中就設定查詢 ldap 的話, 在這兒就可以查詢到該使用者, 並不用另外加上 ldap 的設定) # alias alias_maps = hash:/etc/aliases ldap:ldapalias alias_database = hash:/etc/aliases ldapalias_server_host = localhost ldapalias_search_base = ou=maillist,dc=xxxx,dc=com ldapalias_query_filter = (mailacceptinggeneralid=%s) ldapalias_result_attribute = maildrop 用來查詢 aliases 的資料, 這兒前面有提過, 這個 filter 與 result 的屬性, 改用別的都不成功, 用這兩個就可以... :-( # virus, spam filter content_filter = smtp-amavis:[127.0.0.1]:10024 設定 amavis 的 filter. 必須在 master.cf 加上: # amavis smtp-amavis unix - - n - 2 smtp -o smtp_data_done_timeout=1200 -o disable_dns_lookups=yes localhost:10025 inet n - n - - smtpd -o content_filter= -o local_recipient_maps= -o relay_recipient_maps= -o smtpd_restriction_classes= -o smtpd_client_restrictions= -o smtpd_helo_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o mynetworks=127.0.0.0/8 -o mynetworks_style=host -o strict_rfc821_envelopes=no 然後在 amavisd.conf 指定 port 10025. # smtpd, sasl smtpd_helo_required = yes strict_rfc821_envelopes = yes disable_vrfy_command = yes smtpd_sender_restrictions = reject_unknown_sender_domain, reject_unknown_local_sender, permit_mynetworks, check_sender_access hash:/etc/postfix/access smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unknown_recipient_domain, reject_unauth_destination, check_relay_domains maps_rbl_domains = relays.ordb.org, list.dsbl.org, bl.spamcop.net, rbl.maps.vix.com, dul.maps.vix.com, relays.visi.com, inputs.relays.osirusoft.com, dialups.relays.osirusoft.com, spews.relays.osirusoft.com smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_maps_rbl smtpd_etrn_restrictions = permit_mynetworks reject 這些是設定 smtpd 使用 SASL 來認證, 與 reject 一些 ORDB 之類的網站. 另外 reject_unknown_local_sender 需要另外的 patch, 可參考: http://phorum.study-area.org/viewtopic.php?t=16323 在 /etc/postfix/sasl/ 目錄下產生一個 smtpd.conf 來指定使用 pam 來認證: pwcheck_method: pam 然後在 /etc/pam.d/smtp 這個檔案中, 設定使用 pam_ldap.so 來認證. transport_maps = hash:/etc/postfix/transport 設定 transport, 讓不同的信走不同的路線出去. (到母公司會走內部的線路) 最後, 提一下 vscan 的更新, 使用 ftp-mirror 每天 mirror ftp.antivirus.com 上 面的病毒碼檔案: package = antivirus ftp-server = ftp.antivirus.com remote-directory = /products/pattern local-directory = /home/ftp/pub/trend/pattern transfer-file-regexp += !/\/lpt[0-9][0-9][0-9]\.zip$/ transfer-directory-regexp += !/\/cpr\/$/ transfer-directory-regexp += !/\/99x\/$/ transfer-directory-regexp += !/\/old\/$/ 然後使用下面的 script 來更新病毒碼: #!/bin/bash cd /etc/iscan CURR_PTN=`ls lpt\$\vpn.* | sort | tail -n1 | sed -e 's/lpt\$vpn.//'` #echo "Current pattern is $CURR_PTN" cd /home/ftp/pub/trend/pattern LAST_PTN=`ls ptn*.tar | sort | tail -n1 | sed -e 's/ptn//' | sed -e 's/.tar//'` #echo "Last pattern is $LAST_PTN" if [ $LAST_PTN -le $CURR_PTN ]; then # echo "Current is the last one pattern now!" exit 0 fi cd /etc/iscan echo "Update pattern from $CURR_PTN to $LAST_PTN ..." tar xvf /home/ftp/pub/trend/pattern/ptn${LAST_PTN}.tar lpt\* echo "Remove old pattern ..." PTN_CNT=`ls lpt\$\vpn.* | wc -l | sed -e 's/ //'` if [ $PTN_CNT -ge 4 ]; then echo "more than 3" PTN_LST=`ls lpt\$\vpn.* | sort -r` cnt=0 for i in $PTN_LST; do if [ $cnt -ge 3 ]; then echo "remove $i" rm -f $i fi let "cnt += 1" done fi ### GroupMail ### 前面有關 aliases 的設定, 雖然可以運作, 但是在配合 Postfix 使用時, 發現當寄給一個 aliases 時, 如果其中有一個人的信寄不出去時 (可能是 quota 超過), 信會留在 queue 中, 等一段時間後, 又送一次... 這時所 有該 aliases 的人又收到一次. 造成一堆重複信件的問題. 所以, 決定改用 maillist 的方式來處理, 就安裝上 mailman 來使用. 在使用過一陣子後, 發現 mailman 的處理上不符合我們的需求 (比較難去控制 可以寄進來的人, 送信人也會被改變, 也不容易與我們的使用者維護整合). 所以決定自己用 php 寫一些 script 來使用. 程式很簡單, 我們利用了使用者在 ldap 中的 comment 這個屬性來存放群組, 每屬於一個群組就設一個 comment. (至於 group 這個群組就不設, 所有的人 都屬於這個群組) config.inc.php 參數設定檔. functions.php 共用的一些函式. gm_recv.php 接收信件的程式. 在 /etc/aliases 或者在 ldap 中設定來處理收信的動作. 例如, 給 test@xxx.com 就設定為 test: "|/xxx/xxx/gm_recv.php test" 來處理. #!/usr/bin/php4 -Cq gm_send.php 分送信件的程式, 在 /etc/cron.d 指定使用 nobody 這個使用者每分鐘處理一次. #!/usr/bin/php4 -Cq ", "", substr($buf, 13)); if (allow_sender($grp_name, $sender) == false) { writelog(sprintf("(%s) sender (%s) not allow!", $grp_name, $sender)); flock($fp, LOCK_UN); fclose($fp); move_error_msg(basename($file)); return 0; } $members = load_members($grp_name); if (is_array($members) == false || count($members) == 0) { writelog(sprintf("(%s) members empty!", $grp_name)); flock($fp, LOCK_UN); fclose($fp); move_error_msg(basename($file)); return 0; } $found = 0; while (feof($fp) == false) { $buf = chop_newline(fgets($fp, 4096)); if ($buf == "") break; if (strncasecmp($buf, "X-GroupMail: ", 13) == 0) { $old_grp = substr($buf, 13); if (strcasecmp($old_grp, $grp_name) == 0) { $found = 1; break; } } } if ($found != 0) { writelog(sprintf("(%s) X-GroupMail exist! looping!", $grp_name)); flock($fp, LOCK_UN); fclose($fp); move_error_msg(basename($file)); return 0; } fseek($fp, $pos, SEEK_SET); $smtp = fsockopen($smtp_server, $smtp_port, $errno, $errstr, 30); if ($smtp == 0) { writelog(sprintf("(%s) connect to %s:%d failed: (%d) %s", $grp_name, $smtp_server, $smtp_port, $errno, $errstr)); flock($fp, LOCK_UN); fclose($fp); return 0; } // server response while (1) { $msg = fgets($smtp, 256); if (substr($msg, 0, 3) != "220") { writelog(sprintf("(%s) error (220): %s", $grp_name, chop_newline($msg))); fclose($smtp); flock($fp, LOCK_UN); fclose($fp); return 0; } if (substr($msg, 3, 1) != "-") break; // multi-line response } fputs($smtp, "HELO " . $mydomain . "\n"); // server response while (1) { $msg = fgets($smtp, 256); if (substr($msg, 0, 3) != "250") { writelog(sprintf("(%s) error (250): %s", $grp_name, chop_newline($msg))); fclose($smtp); flock($fp, LOCK_UN); fclose($fp); return 0; } if (substr($msg, 3, 1) != "-") break; // multi-line response } fputs($smtp, "MAIL FROM:<" . $sender . ">\n"); // server response while (1) { $msg = fgets($smtp, 256); if (substr($msg, 0, 3) != "250") { writelog(sprintf("(%s) error (250): %s", $grp_name, chop_newline($msg))); fclose($smtp); flock($fp, LOCK_UN); fclose($fp); return 0; } if (substr($msg, 3, 1) != "-") break; // multi-line response } $cnt = 0; while (list($key, $val) = each($members)) { fputs($smtp, "RCPT TO:<" . $val . "@" . $mydomain . ">\n"); // server response $ok = 1; while (1) { $msg = fgets($smtp, 256); if (substr($msg, 0, 3) != "250") { $ok = 0; writelog(sprintf("(%s) error (250): %s", $grp_name, chop_newline($msg))); } if (substr($msg, 3, 1) != "-") break; // multi-line response } if ($ok != 0) $cnt++; } if ($cnt == 0) { fputs($smtp, "QUIT\n"); fclose($smtp); writelog(sprintf("(%s) members empty!", $grp_name)); flock($fp, LOCK_UN); fclose($fp); move_error_msg(basename($file)); return 0; } fputs($smtp, "DATA\n"); // server response while (1) { $msg = fgets($smtp, 256); if (substr($msg, 0, 3) != "354") { writelog(sprintf("(%s) error (354): %s", $grp_name, chop_newline($msg))); fclose($smtp); flock($fp, LOCK_UN); fclose($fp); return 0; } if (substr($msg, 3, 1) != "-") break; // multi-line response } fputs($smtp, "X-GroupMail: $grp_name\n"); while (feof($fp) == false) { $msg = chop_newline(fgets($fp, 4096)); if ($msg == ".") fputs($smtp, "..\n"); else fputs($smtp, "$msg\n"); } fputs($smtp, ".\n"); // server response while (1) { $msg = fgets($smtp, 256); if (substr($msg, 0, 3) != "250") { writelog(sprintf("(%s) error (250): %s", $grp_name, chop_newline($msg))); fclose($smtp); flock($fp, LOCK_UN); fclose($fp); return 0; } if (substr($msg, 3, 1) != "-") break; // multi-line response } fputs($smtp, "QUIT\n"); fclose($smtp); flock($fp, LOCK_UN); fclose($fp); unlink($file); writelog(sprintf("(%s) post from %s, size=%d, member=%d", $grp_name, $sender, $total, $cnt)); return 1; } ?> gm_list.php 由 ldap 中的資料產生對應的群組名單. 可以設定在 cron 中固定執行. #!/usr/bin/php4 -Cq $delay_times) { fclose($fp); writelog("lock failed!"); exit; } writelog(sprintf("lock failed! Will retry after %d seconds!", $delay_second)); sleep($delay_second); } $dir = $root_path . "lists/"; if ($dh = opendir($dir)) { while (($file = readdir($dh)) != false) { if ($file[0] == ".") continue; if (is_dir($dir . $file)) { process_list($file); } } closedir($dh); } flock($fp, LOCK_UN); fclose($fp); exit; function process_list($list) { global $root_path; $ldap_user = "cn=xxxx,dc=xxx,dc=com"; $ldap_passwd = "xxxxx"; echo "processing $list\n"; $grp_member = $root_path . "/lists/" . $list . "/members"; $id = ldap_connect("localhost", 389); if (!$id) { echo "ldap_connect failed!\n"; return 0; } if (!ldap_bind($id, $ldap_user, $ldap_passwd)) { echo "ldap_bind failed!\n"; ldap_close($id); return 0; } if ($list == "group") $filter = "(uid=*)"; else $filter = "(comment=" . $list . ")"; $result = ldap_search($id, "ou=people,dc=xxx,dc=com", $filter); $fp = fopen($grp_member, "wt"); if ($fp == 0) { echo "Can't create file: " . $grp_member . "\n"; ldap_close($id); return 0; } if (ldap_count_entries($id, $result) > 0) { $result_item = ldap_get_entries($id, $result); $cnt = $result_item["count"]; for ($i = 0; $i < $cnt; $i++) { fputs($fp, sprintf("%s\n", $result_item[$i]["uid"][0])); } echo sprintf("%d members.", $cnt); writelog(sprintf("%s: %d members.", $list, $cnt); } fclose($fp); ldap_close($id); } ?> 這幾個 scripts 並不會很難, 如果要使用, 請自行看一下內容來安裝看看. ### Open Webmail ### http://www.openwebmail.org/ Open Webmail + OpenLDAP 可參考: http://phorum.study-area.org/viewtopic.php?t=15688 主要就是使用 auth_pam.pl 來配合 pam_ldap.so 來認證使用者. 另外, 考慮到其中的 Global Address Book 也可以直接使用 OpenLDAP 上面的資料, 如此就不用維護兩份 Address Book. 原本我考慮直接在 openwebmail-abook.pl 之中, 加上直接去讀取 LDAP 資料的 perl 程式碼, 後來考慮到似乎不需要每次都去讀取, 只要每天 固定時間去查詢一次, 再更新到 Open Webmail 本身的 Address Book 就可以了. 如果你需要直接由 LDAP 讀取 Address Book, 可以修改下面的程式到 openwebmail-abook.pl 中的 'global_addressboot' 那段的程式碼. (並非直接可用, 你需要把下面程式碼讀出的資料放到 Open Webmail 使用的 %globaladdress, %globalnotes 兩個變數中) #!/usr/bin/perl use Net::LDAP; $username = "uid=xxxx,ou=people,dc=xxxx,dc=com"; $password = "xxxx"; $conn = new Net::LDAP("localhost",port=>389); if ($conn->bind(dn=>$username,password=>$password)) { print "Authentication Successful!\n"; } else { print "Authentication Failed!\n"; } $mesg = $conn->search(base=>"dc=xxxx,dc=com", scope=>"sub", filter=>"(mail=*)"); for ($i = 0; $i < $mesg->count; $i++) { my $entry = $mesg->entry($i); $cn = ($entry->get('cn'))[0]; $mail = ($entry->get('mail'))[0]; printf "$cn $mail \n"; } 如果你的 LDAP 允許 anonymous search, 可以省略上面的 bind 那段. 現在我們則使用下面的程式碼, 定時更新 Open Webmail 的 Address Book: #!/usr/bin/perl use Net::LDAP; $username = "uid=xxxx,ou=people,dc=xxxx,dc=com"; $password = "xxxx"; $conn = new Net::LDAP("localhost",port=>389); if ($conn->bind(dn=>$username,password=>$password)) { print "Authentication Successful!\n"; } else { print "Authentication Failed!\n"; } $mesg = $conn->search(base=>"dc=xxxx,dc=com", scope=>"sub", filter=>"(mail=*)"); open(ABOOK, '> /tmp/abook.lst'); for ($i = 0; $i < $mesg->count; $i++) { my $entry = $mesg->entry($i); $cn = ($entry->get('cn'))[0]; $mail = ($entry->get('mail'))[0]; print ABOOK $cn; print ABOOK "@@@"; print ABOOK $mail; print ABOOK "@@@"; print ABOOK $cn; print ABOOK "\n"; } close(ABOOK); 然後再去比對與現有的 Address Book 是否一致, 如果不一樣, 就更 新過去. 上述的 perl 程式可以正常運作, 但是效能似乎不佳, 就用 php 寫另一個來用. updata_abook.php #!/usr/bin/php4 -Cq 0) { $result_item = ldap_get_entries($id, $result); $cnt = $result_item["count"]; for ($i = 0; $i < $cnt; $i++) { $maillist_array[$i] = sprintf("%s@@@%s@@@%s", $result_item[$i]["cn"][0], $result_item[$i]["mail"][0], $result_item[$i]["cn"][0]); } $ml_cnt = $cnt; } $pl_cnt = 0; $filter = "(mail=*)"; $result = ldap_search($id, "ou=people,dc=xxx,dc=com", $filter); if (ldap_count_entries($id, $result) > 0) { $result_item = ldap_get_entries($id, $result); $cnt = $result_item["count"]; for ($i = 0; $i < $cnt; $i++) { $people_array[$i] = sprintf("%s@@@%s@@@%s", $result_item[$i]["cn"][0], $result_item[$i]["mail"][0], $result_item[$i]["cn"][0]); } $pl_cnt = $cnt; } ldap_close($id); if ($ml_cnt > 0) sort($maillist_array); if ($pl_cnt > 0) sort($people_array); $fp = fopen($file, "wt"); if ($fp == 0) { echo "Can't open file: $file\n"; exit; } if ($ml_cnt > 0) { while (list($key, $val) = each($maillist_array)) { fputs($fp, $val . "\n"); } } if ($pl_cnt > 0) { while (list($key, $val) = each($people_array)) { fputs($fp, $val . "\n"); } } fclose($fp); exit; ?> ### Netscape ### http://www.netscape.com/ 最後是 client 的使用, 我們試過 Outlook, Outlook Express, Netscape 等 程式, 發現在這之中, Netscape 使用 LDAP 的方法最簡單, 所以我們建議使用 者如果不使用 webmail, 就可以利用 Netscape 7.x 的版本. 只要在 Directory Server 的設定中, 設好 Base DN, 如果不使用 anonymous, 也設好 Bind DN, 就可以直接在 to: cc: bcc: 等地方直接使用. ### phpBB 2.0.4 ### http://www.phpbb.org/ 公司內部也要裝一個討論區, 首先想到的當然是 phpBB 這一套軟體了. 因為 phpBB 使用自己的資料庫來存放使用者資料, 但是我們想直接使用 LDAP 內的使用者與密碼, 所以只好改改 phpBB 的 source code 了. 為了不改變太多的地方, 我們決定只更改密碼輸入與更改的地方. 在檢查密碼 的地方, 直接去 OpenLDAP 檢查, 如果沒有通過, 再檢查是不是 phpBB 本身內 的使用者. 如果密碼認證在 OpenLDAP 那端就通過, 就看看 phpBB 本身的資料 庫內是否有這個使用者, 如果沒有, 就新增一筆, 且密碼欄位存入一個固定的 字串, 用來表示這個使用者一定要透過 OpenLDAP 認證才可以. 另外就是在使 用者修改密碼的地方, 如果是透過 OpenLDAP 認證, 就不允許改密碼. 新增一個 ldap.php, 檢查 LDAP 上的密碼. login.php, 檢查登入密碼的地方 diff -Nur phpBB2/login.php forum/login.php --- phpBB2/login.php 2003-01-15 21:34:09.000000000 +0800 +++ forum/login.php 2003-05-21 13:40:50.000000000 +0800 @@ -30,6 +30,7 @@ $phpbb_root_path = './'; include($phpbb_root_path . 'extension.inc'); include($phpbb_root_path . 'common.'.$phpEx); +include($phpbb_root_path . 'ldap.'.$phpEx); // // Set page ID for session management @@ -57,6 +58,130 @@ $username = isset($HTTP_POST_VARS['username']) ? $HTTP_POST_VARS['username'] : ''; $password = isset($HTTP_POST_VARS['password']) ? $HTTP_POST_VARS['password'] : ''; +// ----------------------------------------------------- +// Tommy Added BEGIN +$ldap_auth = 0; +if (auth_ldap_userpassword($username, $password) == 1) { + // auth OK + $ldap_auth = 1; + $sql = "SELECT user_id, username, user_password, user_active, user_level FROM " . USERS_TABLE . + " WHERE username = '" . str_replace("\'", "''", $username) . "'"; + if ( !($result = $db->sql_query($sql)) ) + { + message_die(GENERAL_ERROR, 'Error in obtaining userdata', '', __LINE__, __FILE__, $sql); + } + if( !($row = $db->sql_fetchrow($result)) ) { + // no data found, first login + $sql = "SELECT MAX(user_id) AS total FROM " . USERS_TABLE; + if ( !($result = $db->sql_query($sql)) ) + { + message_die(GENERAL_ERROR, 'Could not obtain next user_id information', '', __LINE__, __FILE__, $sql); + } + + if ( !($row = $db->sql_fetchrow($result)) ) + { + message_die(GENERAL_ERROR, 'Could not obtain next user_id information', '', __LINE__, __FILE__, $sql); + } + $user_id = $row['total'] + 1; + + $sql = "INSERT INTO " . USERS_TABLE . " (" . + "user_id," . + "username," . + "user_regdate," . + "user_password," . + "user_email," . + "user_icq," . + "user_website," . + "user_occ," . + "user_from," . + "user_interests," . + "user_sig," . + "user_sig_bbcode_uid," . + "user_avatar," . + "user_avatar_type," . + "user_viewemail," . + "user_aim," . + "user_yim," . + "user_msnm," . + "user_attachsig," . + "user_allowsmile," . + "user_allowhtml," . + "user_allowbbcode," . + "user_allow_viewonline," . + "user_notify," . + "user_notify_pm," . + "user_popup_pm," . + "user_timezone," . + "user_dateformat," . + "user_lang," . + "user_style," . + "user_level," . + "user_allow_pm," . + "user_active," . + "user_actkey" . + ") VALUES (" . + "$user_id," . + "'" . str_replace("\'", "''", $username) . "'," . + time() . "," . + "'" . str_replace("\'", "''", "12345678") . "'," . + "'" . str_replace("\'", "''", $username . "@xxx.com") . "'," . + "'" . str_replace("\'", "''", "") . "'," . + "'" . str_replace("\'", "''", "") . "'," . + "'" . str_replace("\'", "''", "") . "'," . + "'" . str_replace("\'", "''", "") . "'," . + "'" . str_replace("\'", "''", "") . "'," . + "'" . str_replace("\'", "''", "") . "'," . + "''," . + "''," . + "0," . + "0," . + "'" . str_replace("\'", "''", str_replace(' ', '+', "")) . "'," . + "'" . str_replace("\'", "''", "") . "'," . + "'" . str_replace("\'", "''", "") . "'," . + "1," . + "1," . + "1," . + "1," . + "1," . + "0," . + "0," . + "1," . + "8.00," . + "'" . str_replace("\'", "''", "D M d, Y g:i a") . "'," . + "'" . str_replace("\'", "''", "english") . "'," . + "1," . + "0," . + "1," . + "1," . + "''" . + ")"; + + if ( !($result = $db->sql_query($sql, BEGIN_TRANSACTION)) ) + { + message_die(GENERAL_ERROR, 'Could not insert data into users table', '', __LINE__, __FILE__, $sql); + } + + $sql = "INSERT INTO " . GROUPS_TABLE . " (group_name, group_description, group_single_user, group_moderator)" . + "VALUES ('', 'Personal User', 1, 0)"; + if ( !($result = $db->sql_query($sql)) ) + { + message_die(GENERAL_ERROR, 'Could not insert data into groups table', '', __LINE__, __FILE__, $sql); + } + + $group_id = $db->sql_nextid(); + + $sql = "INSERT INTO " . USER_GROUP_TABLE . " (user_id, group_id, user_pending)" . + "VALUES ($user_id, $group_id, 0)"; + + if( !($result = $db->sql_query($sql, END_TRANSACTION)) ) + { + message_die(GENERAL_ERROR, 'Could not insert data into user_group table', '', __LINE__, __FILE__, $sql); + } + } +} +// Tommy Added END +// ----------------------------------------------------- +// OLD Auth Code here... $sql = "SELECT user_id, username, user_password, user_active, user_level FROM " . USERS_TABLE . " WHERE username = '" . str_replace("\'", "''", $username) . "'"; @@ -73,7 +198,7 @@ } else { - if( md5($password) == $row['user_password'] && $row['user_active'] ) + if(($ldap_auth == 1 || ($row['user_password'] != "12345678" && md5($password) == $row['user_password'])) && $row['user_active'] ) { $autologin = ( isset($HTTP_POST_VARS['autologin']) ) ? TRUE : 0; overall_header.tpl, 把 register 的功能移除. diff -Nur phpBB2/templates/subSilver/overall_header.tpl forum/templates/subSilver/overall_header.tpl --- phpBB2/templates/subSilver/overall_header.tpl 2003-01-15 21:34:21.000000000 +0800 +++ forum/templates/subSilver/overall_header.tpl 2003-05-21 14:17:09.000000000 +0800 @@ -228,13 +228,17 @@
{L_INDEX} {SITENAME}
{SITE_DESCRIPTION}
 
usercp_register.php, 更改密碼的地方 diff -Nur phpBB2/includes/usercp_register.php forum/includes/usercp_register.php --- phpBB2/includes/usercp_register.php 2003-01-15 21:34:11.000000000 +0800 +++ forum/includes/usercp_register.php 2003-05-21 13:43:31.000000000 +0800 @@ -272,7 +272,7 @@ $row = $db->sql_fetchrow($result); - if ( $row['user_password'] != md5($cur_password) ) + if ( $row['user_password'] == "12345678" || $row['user_password'] != md5($cur_password) ) { $error = TRUE; $error_msg .= ( ( isset($error_msg) ) ? '
' : '' ) . $lang['Current_password_mismatch']; @@ -291,7 +291,6 @@ $error = TRUE; $error_msg .= ( ( isset($error_msg) ) ? '
' : '' ) . $lang['Password_mismatch']; } - // // Do a ban check on this email address // @@ -308,6 +307,7 @@ if ( $mode == 'editprofile' ) { +/* $sql = "SELECT user_password FROM " . USERS_TABLE . " WHERE user_id = $user_id"; @@ -325,10 +325,12 @@ $error = TRUE; $error_msg .= ( ( isset($error_msg) ) ? '
' : '' ) . $lang['Current_password_mismatch']; } +*/ } } $username_sql = ''; +/* if ( $board_config['allow_namechange'] || $mode == 'register' ) { if ( empty($username) ) @@ -354,7 +356,7 @@ } } } - +*/ if ( $signature != '' ) { if ( strlen($signature) > $board_config['max_sig_chars'] ) 這樣子改了之後, 就可以利用 OpenLDAP 來使用 phpBB 了. ### BugZilla 2.16.4 ### http://www.bugzilla.org/ 其實原本 BugZilla 就有支援使用 LDAP 的使用者了. 不過... 由於這原本是 Netscape 公司內部使用的軟體, 所以使用的 LDAP 模組當然就是 Netscape 那 一套 perldap + Directory C-SDK. 這個部份似乎在 Netscape 轉手之後, 就 沒什麼維護了, 所以... 線上的說明似乎有點問題, 我試過了一天都沒有辦法 成功的編譯出可以正常使用的 perldap 來配合使用. :( 加上之前有碰過 Net::LDAP 模組, 所以, 最後決定直接修改這部份的程式碼, 改用 Net::LDAP 來處理 (反正也不多, 就是一開始 login 的那幾行) 首先, 在 defparams.pl 設定下面幾個參數: useLDAP (設 1 表示使用 LDAP) LDAPServer (你的 LDAP server) LDAPBaseDN (你的使用者 uid 之後的 base dn) LDAPmailattribute (使用者屬性中 email 的欄位名稱) 然後修改 CGI.pl 如下: --- CGI.old.pl 2004-01-11 10:27:41.000000000 +0800 +++ CGI.pl 2004-01-11 11:23:34.000000000 +0800 @@ -27,6 +27,7 @@ # Contains some global routines used throughout the CGI scripts of Bugzilla. +use Net::LDAP; use diagnostics; use strict; use lib "."; @@ -657,7 +658,9 @@ if($LDAPserver =~ /:/) { ($LDAPserver, $LDAPport) = split(":",$LDAPserver); } - my $LDAPconn = new Mozilla::LDAP::Conn($LDAPserver,$LDAPport); +# tommy wu modified +# my $LDAPconn = new Mozilla::LDAP::Conn($LDAPserver,$LDAPport); + my $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport); if(!$LDAPconn) { print "Content-type: text/html\n\n"; PutHeader("Unable to connect to LDAP server"); @@ -683,37 +686,22 @@ exit; } - # We've got our anonymous bind; let's look up this user. - my $dnEntry = $LDAPconn->search(Param("LDAPBaseDN"),"subtree","uid=".$::FORM{"LDAP_login"}); - if(!$dnEntry) { - print "Content-type: text/html\n\n"; - PutHeader("Login Failed"); - print "The username or password you entered is not valid.\n"; - print "Please click Back and try again.\n"; - PutFooter(); - exit; - } - - # Now we get the DN from this search. Once we've got that, we're - # done with the anonymous bind, so we close it. - my $userDN = $dnEntry->getDN; - $LDAPconn->close; + my $ldapMsg = $LDAPconn->bind("uid=" . $::FORM{"LDAP_login"} . "," . Param("LDAPBaseDN"), password => $::FORM{"LDAP_password"}); - # Now we attempt to bind as the specified user. - $LDAPconn = new Mozilla::LDAP::Conn($LDAPserver,$LDAPport,$userDN,$::FORM{"LDAP_password"}); - if(!$LDAPconn) { + if ($ldapMsg->is_error() != 0) { + $LDAPconn->unbind; print "Content-type: text/html\n\n"; - PutHeader("Login Failed"); + PutHeader("Login Failed:" . $ldapMsg->is_error()); print "The username or password you entered is not valid.\n"; print "Please click Back and try again.\n"; PutFooter(); exit; } - - # And now we're going to repeat the search, so that we can get the - # mail attribute for this user. - my $userEntry = $LDAPconn->search(Param("LDAPBaseDN"),"subtree","uid=".$::FORM{"LDAP_login"}); - if(!$userEntry->exists(Param("LDAPmailattribute"))) { + my $ldapMsg = $LDAPconn->search(base => Param("LDAPBaseDN"), + filter => "(uid=" . $::FORM{"LDAP_login"} . ")"); + $enteredlogin = ($ldapMsg->entry(0)->get_value(Param("LDAPmailattribute")))[0]; + $LDAPconn->unbind; + if (!$enteredlogin) { print "Content-type: text/html\n\n"; PutHeader("LDAP authentication error"); print "I was unable to retrieve the ".Param("LDAPmailattribute"); @@ -723,10 +711,6 @@ exit; } - # Mozilla::LDAP::Entry->getValues returns an array for the attribute - # requested, even if there's only one entry. - $enteredlogin = ($userEntry->getValues(Param("LDAPmailattribute")))[0]; - # We're going to need the cryptpwd for this user from the database # so that we can set the cookie below, even though we're not going # to use it for authentication. @@ -736,13 +720,70 @@ # Bugzilla's database yet, so we've got to add them. if($realcryptpwd eq "") { # We'll want the user's name for this. - my $userRealName = ($userEntry->getValues("displayName"))[0]; - if($userRealName eq "") { - $userRealName = ($userEntry->getValues("cn"))[0]; - } + my $userRealName = ($ldapMsg->entry(0)->get_value('cn'))[0]; InsertNewUser($enteredlogin, $userRealName); $realcryptpwd = PasswordForLogin($enteredlogin); } + # We've got our anonymous bind; let's look up this user. +# my $dnEntry = $LDAPconn->search(Param("LDAPBaseDN"),"subtree","uid=".$::FORM{"LDAP_login"}); +# if(!$dnEntry) { +# print "Content-type: text/html\n\n"; +# PutHeader("Login Failed"); +# print "The username or password you entered is not valid.\n"; +# print "Please click Back and try again.\n"; +# PutFooter(); +# exit; +# } + + # Now we get the DN from this search. Once we've got that, we're + # done with the anonymous bind, so we close it. +# my $userDN = $dnEntry->getDN; +# $LDAPconn->close; + + # Now we attempt to bind as the specified user. +# $LDAPconn = new Mozilla::LDAP::Conn($LDAPserver,$LDAPport,$userDN,$::FORM{"LDAP_password"}); +# if(!$LDAPconn) { +# print "Content-type: text/html\n\n"; +# PutHeader("Login Failed"); +# print "The username or password you entered is not valid.\n"; +# print "Please click Back and try again.\n"; +# PutFooter(); +# exit; +# } + + # And now we're going to repeat the search, so that we can get the + # mail attribute for this user. +# my $userEntry = $LDAPconn->search(Param("LDAPBaseDN"),"subtree","uid=".$::FORM{"LDAP_login"}); +# if(!$userEntry->exists(Param("LDAPmailattribute"))) { +# print "Content-type: text/html\n\n"; +# PutHeader("LDAP authentication error"); +# print "I was unable to retrieve the ".Param("LDAPmailattribute"); +# print " attribute from the LDAP server. Please contact "; +# print Param("maintainer")." and notify him of this error.\n"; +# PutFooter(); +# exit; +# } + + # Mozilla::LDAP::Entry->getValues returns an array for the attribute + # requested, even if there's only one entry. +# $enteredlogin = ($userEntry->getValues(Param("LDAPmailattribute")))[0]; + + # We're going to need the cryptpwd for this user from the database + # so that we can set the cookie below, even though we're not going + # to use it for authentication. +# $realcryptpwd = PasswordForLogin($enteredlogin); + + # If we don't get a result, then we've got a user who isn't in + # Bugzilla's database yet, so we've got to add them. +# if($realcryptpwd eq "") { + # We'll want the user's name for this. +# my $userRealName = ($userEntry->getValues("displayName"))[0]; +# if($userRealName eq "") { +# $userRealName = ($userEntry->getValues("cn"))[0]; +# } +# InsertNewUser($enteredlogin, $userRealName); +# $realcryptpwd = PasswordForLogin($enteredlogin); +# } } # end LDAP authentication # And now, if we've logged in via either method, then we need to set 經過這樣子的修改之後, 我們就可以不需要 perldap 而使用 BugZilla 的 LDAP 功能. 其他... 還有許多的服務, 目前還沒做到, 所以本文應該還會繼續修正. 本文的最新版本可以由下列的網頁取得: http://www.teatime.com.tw/~tommy/doc/openldap.txt ChangeLog: 0.07 add BugZilla 2.16.4 0.06 add phpBB 2.0.4 add openldap replicate setting 0.05 add ldap_adduser.php/ldap_deluser.php 0.04 remove ldap in postfix local user maps remove blackholes.mail-abuse.org use php program to replace mailman and some perl script 0.03 change aliases to mailman maillist 0.02 add pam_mkhomedir.so 'I have no name!' fixed 0.01 Initital.
 {L_FAQ}{L_FAQ}   {L_SEARCH}{L_SEARCH}   {L_MEMBERLIST}{L_MEMBERLIST}   {L_USERGROUPS}{L_USERGROUPS}  -  {L_REGISTER}{L_REGISTER}  +   +