From 5100704d64380cc532dac7e5da8c72a56dd94071 Mon Sep 17 00:00:00 2001 From: pallo Date: Sat, 25 Mar 2000 17:01:15 +0000 Subject: [PATCH] Added addressbook+LDAP functions. git-svn-id: https://svn.code.sf.net/p/squirrelmail/code/trunk/squirrelmail@345 7612ce4b-ef26-0410-bec9-ea0150e637f0 --- TODO | 4 +- functions/abook_ldap_server.php | 204 ++++++++++++++++++++++++ functions/abook_local_file.php | 220 ++++++++++++++++++++++++++ functions/addressbook.php | 269 ++++++++++++++++++++++++++++++++ src/addrbook_popup.php | 43 +++++ src/addrbook_search.php | 168 ++++++++++++++++++++ src/compose.php | 20 ++- 7 files changed, 925 insertions(+), 3 deletions(-) create mode 100644 functions/abook_ldap_server.php create mode 100644 functions/abook_local_file.php create mode 100644 functions/addressbook.php create mode 100644 src/addrbook_popup.php create mode 100644 src/addrbook_search.php diff --git a/TODO b/TODO index 493809c7..3c6f2f15 100644 --- a/TODO +++ b/TODO @@ -3,8 +3,7 @@ Ideas to be implemented initials = taken by that person - - LDAP support for address books - - Importing of address books +(pl/gf) Importing of address books (lme) Better email message body parsing - Use PHP4 Session management, get rid of cookies - Make it possible to save preferences in MySQL DB or on Filesystem @@ -23,3 +22,4 @@ Finished: (lme) (24.3.00) Saving sent messages (lme) (24.3.00) Fix "Seen" bug with UW IMAP server (lme) (24.3.00) Add "subscribe" to folders section +(pl) (25.3.00) LDAP support for address books diff --git a/functions/abook_ldap_server.php b/functions/abook_ldap_server.php new file mode 100644 index 00000000..3ed9e413 --- /dev/null +++ b/functions/abook_ldap_server.php @@ -0,0 +1,204 @@ + LDAP server hostname/IP-address + ** base => LDAP server root (base dn). Empty string allowed. + ** ? port => LDAP server TCP port number (default: 389) + ** ? charset => LDAP server charset (default: utf-8) + ** ? name => Name for LDAP server (default "LDAP: hostname") + ** Used to tag the result data + ** ? maxrows => Maximum # of rows in search result + ** + ** NOTE. This class should not be used directly. Use the + ** "AddressBook" class instead. + **/ + + class abook_ldap_server extends addressbook_backend { + var $btype = "remote"; + var $bname = "ldap_server"; + + // Parameters changed by class + var $sname = "LDAP"; // Service name + var $server = ""; // LDAP server name + var $port = 389; // LDAP server port + var $basedn = ""; // LDAP base DN + var $charset = "utf-8"; // LDAP server charset + var $linkid = false; // PHP LDAP link ID + var $bound = false; // True if LDAP server is bound + var $maxrows = 250; // Max rows in result + + + // Constructor. Connects to database + function abook_ldap_server($param) { + if(is_array($param)) { + $this->server = $param["host"]; + $this->basedn = $param["base"]; + if(!empty($param["port"])) + $this->port = $param["port"]; + if(!empty($param["charset"])) + $this->charset = strtolower($param["charset"]); + if(isset($param["maxrows"])) + $this->maxrows = $param["maxrows"]; + if(empty($param["name"])) + $this->sname = "LDAP: ".$param["host"]; + else + $this->sname = $param["name"]; + + $this->open(true); + } else { + $this->set_error(_("Invalid argument to constructor")); + } + } + + + // Open the LDAP server. New connection if $new is true + function open($new = false) { + $this->error = ""; + + // Connection is already open + if($this->linkid != false && !$new) + return true; + + $this->linkid = @ldap_connect($this->server, $this->port); + if(!$this->linkid) + if(function_exists("ldap_error")) + return $this->set_error(ldap_error($this->linkid)); + else + return $this->set_error("ldap_connect failed"); + + if(!@ldap_bind($this->linkid)) + if(function_exists("ldap_error")) + return $this->set_error(ldap_error($this->linkid)); + else + return $this->set_error("ldap_bind failed"); + + $this->bound = true; + + return true; + } + + + // Encode iso8859-1 string to the charset used by this LDAP server + function charset_encode($str) { + if($this->charset == "utf-8") { + if(function_exists("utf8_encode")) + return utf8_encode($str); + else + return $str; + } else { + return $str; + } + } + + + // Decode from charset used by this LDAP server to iso8859-1 + function charset_decode($str) { + if($this->charset == "utf-8") { + if(function_exists("utf8_decode")) + return utf8_decode($str); + else + return $str; + } else { + return $str; + } + } + + + // ========================== Public ======================== + + // Search the LDAP server + function search($expr) { + + // To be replaced by advanded search expression parsing + if(is_array($expr)) return false; + + // Encode the expression + $expr = $this->charset_encode($expr); + if(!ereg("\*", $expr)) + $expr = "*$expr*"; + $expression = "cn=$expr"; + + // Make sure connection is there + if(!$this->open()) + return false; + + // Do the search + $sret = @ldap_search($this->linkid, $this->basedn, $expression, + array("dn", "o", "ou", "sn", "givenname", + "cn", "mail", "telephonenumber")); + + // Should get error from server using the ldap_error() function, + // but it only exist in the PHP LDAP documentation. + if(!$sret) + if(function_exists("ldap_error")) + return $this->set_error(ldap_error($this->linkid)); + else + return $this->set_error("ldap_search failed"); + + if(@ldap_count_entries($this->linkid, $sret) <= 0) + return array(); + + // Get results + $ret = array(); + $returned_rows = 0; + $res = @ldap_get_entries($this->linkid, $sret); + for($i = 0 ; $i < $res["count"] ; $i++) { + $row = $res[$i]; + + // Extract data common for all e-mail addresses + // of an object. Use only the first name + $nickname = $this->charset_decode($row["dn"]); + $fullname = $this->charset_decode($row["cn"][0]); + + if(empty($row["telephonenumber"][0])) $phone = ""; + else $phone = $this->charset_decode($row["telephonenumber"][0]); + + if(!empty($row["ou"][0])) + $label = $this->charset_decode($row["ou"][0]); + else if(!empty($row["o"][0])) + $label = $this->charset_decode($row["o"][0]); + else + $label = ""; + + if(empty($row["givenname"][0])) $firstname = ""; + else $firstname = $this->charset_decode($row["givenname"][0]); + + if(empty($row["sn"][0])) $surname = ""; + else $surname = $this->charset_decode($row["sn"][0]); + + // Add one row to result for each e-mail address + for($j = 0 ; $j < $row["mail"]["count"] ; $j++) { + array_push($ret, array("nickname" => $nickname, + "name" => $fullname, + "firstname" => $firstname, + "lastname" => $surname, + "email" => $row["mail"][$j], + "label" => $label, + "phone" => $phone, + "backend" => $this->bnum, + "source" => &$this->sname)); + + // Limit number of hits + $returned_rows++; + if(($returned_rows >= $this->maxrows) && + ($this->maxrows > 0) ) { + ldap_free_result($sret); + return $ret; + } + + } + } + + ldap_free_result($sret); + return $ret; + } // end search() + + } +?> diff --git a/functions/abook_local_file.php b/functions/abook_local_file.php new file mode 100644 index 00000000..7e25b115 --- /dev/null +++ b/functions/abook_local_file.php @@ -0,0 +1,220 @@ + path to addressbook file + ** ? create => if true: file is created if it does not exist. + ** ? umask => umask set before opening file. + ** + ** NOTE. This class should not be used directly. Use the + ** "AddressBook" class instead. + **/ + + class abook_local_file extends addressbook_backend { + var $btype = "local"; + var $bname = "local_file"; + + var $filename = ""; + var $filehandle = 0; + var $create = false; + var $umask; + + // ========================== Private ======================= + + // Constructor + function abook_local_file($param) { + $this->sname = _("Personal address book"); + $this->umask = Umask(); + + if(is_array($param)) { + if(empty($param["filename"])) + return $this->set_error("Invalid parameters"); + if(!is_string($param["filename"])) + return $this->set_error($param["filename"] . ": ". + _("Not a file name")); + + $this->filename = $param["filename"]; + + if($param["create"]) + $this->create = true; + if(isset($param["umask"])) + $this->umask = $param["umask"]; + + $this->open(true); + } else { + $this->set_error(_("Invalid argument to constructor")); + } + } + + // Open the addressbook file and store the file pointer. + // Use $file as the file to open, or the class' own + // filename property. If $param is empty and file is + // open, do nothing. + function open($new = false) { + $this->error = ""; + $file = $this->filename; + $create = $this->create; + + // Return true is file is open and $new is unset + if($this->filehandle && !$new) + return true; + + // Check that new file exitsts + if((!(file_exists($file) && is_readable($file))) && !$create) + return $this->set_error("$file: " . + _("No such file or directory")); + + // Close old file, if any + if($this->filehandle) $this->close(); + + // Open file. First try to open for reading and writing, + // but fall back to read only. + umask($this->umask); + $fh = @fopen($file, "a+"); + if($fh) { + $this->filehandle = &$fh; + $this->filename = $file; + $this->writeable = true; + } else { + $fh = @fopen($file, "r"); + if($fh) { + $this->filehandle = &$fh; + $this->filename = $file; + $this->writeable = false; + } else { + return $this->set_error("$file: "._("Open failed")); + } + } + + return true; + } + + // Close the file and forget the filehandle + function close() { + @fclose($this->filehandle); + $this->filehandle = 0; + $this->filename = ""; + $this->writable = false; + } + + // ========================== Public ======================== + + // Search the file + function search($expr) { + + // To be replaced by advanded search expression parsing + if(is_array($expr)) return; + + // Make regexp from glob'ed expression + $expr = ereg_replace("\?", ".", $expr); + $expr = ereg_replace("\*", ".*", $expr); + + $res = array(); + if(!$this->open()) + return false; + + @rewind($this->filehandle); + + while ($row = @fgetcsv($this->filehandle, 2048, "|")) { + $line = join(" ", $row); + if(eregi($expr, $line)) { + array_push($res, array("nickname" => $row[0], + "name" => $row[1] . " " . $row[2], + "firstname" => $row[1], + "lastname" => $row[2], + "email" => $row[3], + "label" => $row[4], + "backend" => $this->bnum, + "source" => &$this->sname)); + } + } + + return $res; + } + + // Lookup alias + function lookup($alias) { + if(empty($alias)) + return array(); + + $alias = strtolower($alias); + + $this->open(); + @rewind($this->filehandle); + + while ($row = @fgetcsv($this->filehandle, 2048, "|")) { + if(strtolower($row[0]) == $alias) { + return array("nickname" => $row[0], + "name" => $row[1] . " " . $row[2], + "firstname" => $row[1], + "lastname" => $row[2], + "email" => $row[3], + "label" => $row[4], + "backend" => $this->bnum, + "source" => &$this->sname); + } + } + + return array(); + } + + // List all addresses + function list_addr() { + $res = array(); + $this->open(); + @rewind($this->filehandle); + + while ($row = @fgetcsv($this->filehandle, 2048, "|")) { + array_push($res, array("nickname" => $row[0], + "name" => $row[1] . " " . $row[2], + "firstname" => $row[1], + "lastname" => $row[2], + "email" => $row[3], + "label" => $row[4], + "backend" => $this->bnum, + "source" => &$this->sname)); + } + return $res; + } + + // Add address + function add($userdata) { + if(!$this->writeable) + return $this->set_error(_("Addressbook is read-only")); + + // See if user exist already + $ret = $this->lookup($userdata["nickname"]); + if(!empty($ret)) + return $this->set_error(sprintf(_("User '%s' already exist"), + $ret["nickname"])); + + // Here is the data to write + $data = sprintf("%s|%s|%s|%s|%s", $userdata["nickname"], + $userdata["firstname"], $userdata["lastname"], + $userdata["email"], $userdata["label"]); + // Strip linefeeds + $data = ereg_replace("[\r\n]", " ", $data); + // Add linefeed at end + $data = $data."\n"; + + // Reopen file, just to be sure + $this->open(true); + if(!$this->writeable) + return $this->set_error(_("Addressbook is read-only")); + + $r = fwrite($this->filehandle, $data); + if($r > 0) + return true; + + $this->set_error(_("Write to addressbook failed")); + return false; + } + + } +?> diff --git a/functions/addressbook.php b/functions/addressbook.php new file mode 100644 index 00000000..eec06b7e --- /dev/null +++ b/functions/addressbook.php @@ -0,0 +1,269 @@ +add_backend("local_file", Array("filename" => $filename, + "create" => true)); + if(!$r) { + print _("Error opening ") ."$filename"; + exit; + } + + + // Load configured LDAP servers + reset($ldap_server); + while(list($key,$param) = each($ldap_server)) + if(is_array($param)) + $abook->add_backend("ldap_server", $param); + + // Return the initialized object + return $abook; + } + + + + /** + ** This is the main address book class that connect all the + ** backends and provide services to the functions above. + ** + **/ + class AddressBook { + var $backends = array(); + var $numbackends = 0; + var $error = ""; + + // Constructor function. + function AddressBook() { + } + + // Return an array of backends of a given type, + // or all backends if no type is specified. + function get_backend_list($type = "") { + $ret = array(); + for($i = 1 ; $i <= $this->numbackends ; $i++) { + if(empty($type) || $type == $this->backends[$i]->btype) { + array_push($ret, &$this->backends[$i]); + } + } + return $ret; + } + + + // ========================== Public ======================== + + // Add a new backend. $backend is the name of a backend + // (without the abook_ prefix), and $param is an optional + // mixed variable that is passed to the backend constructor. + // See each of the backend classes for valid parameters. + function add_backend($backend, $param = "") { + $backend_name = "abook_".$backend; + eval("\$newback = new $backend_name(\$param);"); + if(!empty($newback->error)) { + $this->error = $newback->error; + return false; + } + + $this->numbackends++; + + $newback->bnum = $this->numbackends; + $this->backends[$this->numbackends] = $newback; + return $this->numbackends; + } + + + // Return a list of addresses matching expression in + // all backends of a given type. + function search($expression, $btype = "") { + $ret = array(); + + $sel = $this->get_backend_list($btype); + for($i = 0 ; $i < sizeof($sel) ; $i++) { + $backend = &$sel[$i]; + $backend->error = ""; + $res = $backend->search($expression); + if(is_array($res)) { + $ret = array_merge($ret, $res); + } else { + $this->error = $backend->error; + return false; + } + } + + return $ret; + } + + + // Return a sorted search + function s_search($expression, $btype = "") { + $ret = $this->search($expression, $btype); + + // Inline function - Not nice, but still.. + function cmp($a,$b) { + if($a["backend"] > $b["backend"]) + return 1; + else if($a["backend"] < $b["backend"]) + return -1; + + return (strtolower($a["name"]) > strtolower($b["name"])) ? 1 : -1; + } + + usort($ret, 'cmp'); + return $ret; + } + + + // Lookup an address by alias. Only possible in + // local backends. + function lookup($alias) { + $ret = array(); + + $sel = $this->get_backend_list("local"); + for($i = 0 ; $i < sizeof($sel) ; $i++) { + $backend = &$sel[$i]; + $backend->error = ""; + $res = $backend->lookup($alias); + if(is_array($res)) { + return $res; + } else { + $this->error = $backend->error; + return false; + } + } + + return $ret; + } + + + // Return all addresses + function list_addr() { + $ret = array(); + + $sel = $this->get_backend_list("local"); + for($i = 0 ; $i < sizeof($sel) ; $i++) { + $backend = &$sel[$i]; + $backend->error = ""; + $res = $backend->list_addr(); + if(is_array($res)) { + $ret = array_merge($ret, $res); + } else { + $this->error = $backend->error; + return false; + } + } + + return $ret; + } + + + // Create a new address from $userdata, in backend $bnum. + // Return the backend number that the/ address was added + // to, or false if it failed. + function add($userdata, $bnum) { + + // Validate data + if(!is_array($userdata)) { + $this->error = _("Invalid input data"); + return false; + } + if(empty($userdata["fullname"]) && + empty($userdata["lastname"])) { + $this->error = _("Name is missing"); + return false; + } + if(empty($userdata["email"])) { + $this->error = _("E-mail address is missing"); + return false; + } + if(empty($userdata["nickname"])) { + $userdata["nickname"] = $userdata["email"]; + } + + // Check that specified backend accept new entries + if(!$this->backends[$bnum]->writeable) { + $this->error = _("Addressbook is not writable"); + return false; + } + + // Add address to backend + $res = $this->backends[$bnum]->add($userdata); + if($res) { + return $bnum; + } else { + $this->error = $this->backends[$bnum]->error; + return false; + } + + return false; // Not reached + } + + } + + + /** + ** Generic backend that all other backends extend + **/ + class addressbook_backend { + + // Variables that all backends must provide. + var $btype = "dummy"; + var $bname = "dummy"; + var $sname = "Dummy backend"; + + // Variables common for all backends, but that + // should not be changed by the backends. + var $bnum = -1; + var $error = ""; + var $writeable = false; + + function set_error($string) { + $this->error = "[" . $this->sname . "] " . $string; + return false; + } + + + // ========================== Public ======================== + + function search($expression) { + $this->set_error("search not implemented"); + return false; + } + + function lookup($alias) { + $this->set_error("lookup not implemented"); + return false; + } + + function list_addr() { + $this->set_error("list_addr not implemented"); + return false; + } + + function add($userdata) { + $this->set_error("add not implemented"); + return false; + } + + } + +?> diff --git a/src/addrbook_popup.php b/src/addrbook_popup.php new file mode 100644 index 00000000..11b293e4 --- /dev/null +++ b/src/addrbook_popup.php @@ -0,0 +1,43 @@ + + + + + +<?php + printf("%s: %s", $org_title, _("Address Book")); +?> + + + + + + + + diff --git a/src/addrbook_search.php b/src/addrbook_search.php new file mode 100644 index 00000000..9202b137 --- /dev/null +++ b/src/addrbook_search.php @@ -0,0 +1,168 @@ + + + + + +<?php + printf("%s: %s", $org_title, _("Address Book")); +?> + + +"; + } else { + echo "\n"; + } + + // Just make a blank page and exit + if(($show == "blank") || (empty($query) && empty($show))) { + printf("


%s

\n\n", + _("Search results will display here")); + exit; + } + + // Create search form + if($show == "form") { + printf("
\n", + $PHP_SELF); + printf(""); + printf("
\n"); + printf(" %s:\n\n", + _("Search for")); + printf(" \n", + htmlspecialchars($query)); + printf("\n"); + printf(" ", + _("Search")); + printf("\n"); + printf("\n", + _("Close window")); + printf("
\n"); + } + + // Include JavaScript code if this is search results + if(!empty($query)) { +?> + + +s_search($query); + + if(!is_array($res)) { + printf("


%s.

\n\n", + _("No persons matching your search was found")); + exit; + } + + // List search results + $line = 0; + print ""; + print "\n"; + + while(list($key, $row) = each($res)) { + printf("\n", + ($line % 2) ? " bgcolor=\"$color[0]\"" : "", $row["email"], + $row["email"], $row["name"], $row["email"], $row["label"], + $row["source"]); + $line++; + } + print "
 NameE-mailInfoSource
To | Cc%s %s %s %s
"; + } +?> + + diff --git a/src/compose.php b/src/compose.php index 000eb0f0..c92d1d55 100644 --- a/src/compose.php +++ b/src/compose.php @@ -146,7 +146,16 @@ $reply_subj = decodeHeader($reply_subj); $forward_subj = decodeHeader($forward_subj); - echo "\n
\n\n"; + + echo "\n\n"; echo "\n"; echo " \n"; @@ -179,6 +188,15 @@ echo "
"; echo " \n"; echo " \n"; + + echo "\n"; + echo " \n"; echo "
\n"; echo _("Subject:"); -- 2.25.1