X-Git-Url: https://vcs.fsf.org/?p=squirrelmail.git;a=blobdiff_plain;f=functions%2Fabook_local_file.php;h=65ae8256f24372c3976c47a07577a4cf65e7d68a;hp=8b3198d29bd5709d102d800ab9324bc7f07a82b9;hb=daf777102978219856d97b6b4e93f8b5815db234;hpb=baa5999492b2a69dc6e1db11e0008c7fd74ea0a2 diff --git a/functions/abook_local_file.php b/functions/abook_local_file.php index 8b3198d2..65ae8256 100644 --- a/functions/abook_local_file.php +++ b/functions/abook_local_file.php @@ -3,36 +3,99 @@ /** * abook_local_file.php * - * Copyright (c) 1999-2002 The SquirrelMail Project Team - * Licensed under the GNU GPL. For full terms see the file COPYING. + * @copyright © 1999-2007 The SquirrelMail Project Team + * @license http://opensource.org/licenses/gpl-license.php GNU Public License + * @version $Id$ + * @package squirrelmail + * @subpackage addressbook + */ + +/** + * Backend for address book as a pipe separated file * - * Backend for addressbook as a pipe separated file + * Stores the address book in a local file * * An array with the following elements must be passed to * the class constructor (elements marked ? are optional): - * - * filename => path to addressbook file - * ? create => if true: file is created if it does not exist. - * ? umask => umask set before opening file. - * + *
+ *   filename  => path to addressbook file
+ * ? create    => if true: file is created if it does not exist.
+ * ? umask     => umask set before opening file.
+ * ? name      => name of address book.
+ * ? detect_writeable => detect address book access permissions by
+ *                checking file permissions.
+ * ? writeable => allow writing into address book. Used only when
+ *                detect_writeable is set to false.
+ * ? listing   => enable/disable listing
+ *
* NOTE. This class should not be used directly. Use the * "AddressBook" class instead. - * - * $Id$ + * @package squirrelmail */ - class abook_local_file extends addressbook_backend { + /** + * Backend type + * @var string + */ var $btype = 'local'; + /** + * Backend name + * @var string + */ var $bname = 'local_file'; - var $filename = ''; + /** + * File used to store data + * @var string + */ + var $filename = ''; + /** + * File handle + * @var object + */ var $filehandle = 0; - var $create = false; + /** + * Create file, if it not present + * @var bool + */ + var $create = false; + /** + * Detect, if address book is writeable by checking file permisions + * @var bool + */ + var $detect_writeable = true; + /** + * Control write access to address book + * + * Option does not have any effect, if 'detect_writeable' is 'true' + * @var bool + */ + var $writeable = false; + /** + * controls listing of address book + * @var bool + */ + var $listing = true; + /** + * Umask of the file + * @var string + */ var $umask; + /** + * Sets max entry size (number of bytes used for all address book fields + * (including escapes) + 4 delimiters + 1 linefeed) + * @var integer + * @since 1.5.2 + */ + var $line_length = 2048; /* ========================== Private ======================= */ - /* Constructor */ + /** + * Constructor + * @param array $param backend options + * @return bool + */ function abook_local_file($param) { $this->sname = _("Personal address book"); $this->umask = Umask(); @@ -48,66 +111,93 @@ class abook_local_file extends addressbook_backend { $this->filename = $param['filename']; - if($param['create']) { - $this->create = true; + if(isset($param['create'])) { + $this->create = $param['create']; } if(isset($param['umask'])) { $this->umask = $param['umask']; } - if(!empty($param['name'])) { + if(isset($param['name'])) { $this->sname = $param['name']; } - + if(isset($param['detect_writeable'])) { + $this->detect_writeable = $param['detect_writeable']; + } + if(!empty($param['writeable'])) { + $this->writeable = $param['writeable']; + } + if(isset($param['listing'])) { + $this->listing = $param['listing']; + } + if(isset($param['line_length']) && ! empty($param['line_length'])) { + $this->line_length = (int) $param['line_length']; + } + $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. */ + /** + * 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. + * @param bool $new is file already opened + * @return bool + */ function open($new = false) { $this->error = ''; $file = $this->filename; $create = $this->create; - + $fopenmode = (($this->writeable && is_writable($file)) ? 'a+' : 'r'); + /* 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; + if (! $this->detect_writeable) { + $fh = @fopen($file,$fopenmode); + if ($fh) { + $this->filehandle = &$fh; + $this->filename = $file; + } else { + return $this->set_error("$file: " . _("Open failed")); + } } else { - $fh = @fopen($file, 'r'); + /* Open file. First try to open for reading and writing, + * but fall back to read only. */ + $fh = @fopen($file, 'a+'); if($fh) { $this->filehandle = &$fh; $this->filename = $file; - $this->writeable = false; + $this->writeable = true; } else { - return $this->set_error("$file: " . _("Open failed")); + $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 */ + /** Close the file and forget the filehandle */ function close() { @fclose($this->filehandle); $this->filehandle = 0; @@ -115,10 +205,10 @@ class abook_local_file extends addressbook_backend { $this->writable = false; } - /* Lock the datafile - try 20 times in 5 seconds */ + /** Lock the datafile - try 20 times in 5 seconds */ function lock() { for($i = 0 ; $i < 20 ; $i++) { - if(flock($this->filehandle, 2 + 4)) + if(flock($this->filehandle, 2 + 4)) return true; else usleep(250000); @@ -126,250 +216,361 @@ class abook_local_file extends addressbook_backend { return false; } - /* Lock the datafile */ + /** Unlock the datafile */ function unlock() { return flock($this->filehandle, 3); } - /* Overwrite the file with data from $rows - * NOTE! Previous locks are broken by this function */ + /** + * Overwrite the file with data from $rows + * NOTE! Previous locks are broken by this function + * @param array $rows new data + * @return bool + */ function overwrite(&$rows) { $this->unlock(); - $newfh = @fopen($this->filename .'.tmp', 'w'); + $newfh = @fopen($this->filename.'.tmp', 'w'); + if(!$newfh) { - return $this->set_error($this->filename .'.tmp: '. _("Open failed")); + return $this->set_error($this->filename. '.tmp:' . _("Open failed")); } - - for($i = 0 ; $i < sizeof($rows) ; $i++) { + + for($i = 0, $cnt=sizeof($rows) ; $i < $cnt ; $i++) { if(is_array($rows[$i])) { - for($j = 0 ; $j < count($rows[$i]) ; $j++) { + for($j = 0, $cnt_part=count($rows[$i]) ; $j < $cnt_part ; $j++) { $rows[$i][$j] = $this->quotevalue($rows[$i][$j]); } - fwrite($newfh, join('|', $rows[$i]) . "\n"); + $tmpwrite = sq_fwrite($newfh, join('|', $rows[$i]) . "\n"); + if ($tmpwrite === FALSE) { + return $this->set_error($this->filename . '.tmp:' . _("Write failed")); + } } - } + } fclose($newfh); if (!@copy($this->filename . '.tmp' , $this->filename)) { - return $this->set_error($file->filename.':' . _("Unable to update")); + return $this->set_error($this->filename . ':' . _("Unable to update")); } - @unlink( $this->filename .'.tmp'); + @unlink($this->filename . '.tmp'); $this->unlock(); $this->open(true); return true; } - + /* ========================== Public ======================== */ - - /* Search the file */ + + /** + * Search the file + * @param string $expr search expression + * @return array search results + */ function search($expr) { /* To be replaced by advanded search expression parsing */ if(is_array($expr)) { return; } - + + // don't allow wide search when listing is disabled. + if ($expr=='*' && ! $this->listing) + return array(); + /* Make regexp from glob'ed expression * May want to quote other special characters like (, ), -, [, ], etc. */ $expr = str_replace('?', '.', $expr); $expr = str_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)); + + while ($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { + if (count($row)<5) { + /** + * address book is corrupted. + */ + global $oTemplate; + error_box(_("Address book is corrupted. Required fields are missing.")); + $oTemplate->display('footer.tpl'); + die(); + } else { + $line = join(' ', $row); + /** + * TODO: regexp search is supported only in local_file backend. + * Do we check format of regexp or ignore errors? + */ + // errors on eregi call are suppressed in order to prevent display of regexp compilation errors + if(@eregi($expr, $line)) { + array_push($res, array('nickname' => $row[0], + 'name' => $this->fullname($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 */ + + /** + * Lookup alias + * @param string $alias alias + * @return array search results + */ 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); + + while ($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { + if (count($row)<5) { + /** + * address book is corrupted. + */ + global $oTemplate; + error_box(_("Address book is corrupted. Required fields are missing.")); + $oTemplate->display('footer.tpl'); + die(); + } else { + if(strtolower($row[0]) == $alias) { + return array('nickname' => $row[0], + 'name' => $this->fullname($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 */ + /** + * List all addresses + * @return array list of all addresses + */ function list_addr() { $res = array(); + + if(isset($this->listing) && !$this->listing) { + return 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)); + + while ($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { + if (count($row)<5) { + /** + * address book is corrupted. Don't be nice to people that + * violate address book formating. + */ + global $oTemplate; + error_box(_("Address book is corrupted. Required fields are missing.")); + $oTemplate->display('footer.tpl'); + die(); + } else { + array_push($res, array('nickname' => $row[0], + 'name' => $this->fullname($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 */ + /** + * Add address + * @param array $userdata new data + * @return bool + */ function add($userdata) { if(!$this->writeable) { - return $this->set_error(_("Addressbook is read-only")); + return $this->set_error(_("Address book is read-only")); } /* See if user exists already */ $ret = $this->lookup($userdata['nickname']); if(!empty($ret)) { - return $this->set_error(sprintf(_("User '%s' already exist"), - $ret['nickname'])); + // i18n: don't use html formating in translation + return $this->set_error(sprintf(_("User \"%s\" already exists"),$ret['nickname'])); } - + /* Here is the data to write */ $data = $this->quotevalue($userdata['nickname']) . '|' . $this->quotevalue($userdata['firstname']) . '|' . - $this->quotevalue($userdata['lastname']) . '|' . + $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) . '|' . $this->quotevalue($userdata['email']) . '|' . - $this->quotevalue($userdata['label']); + $this->quotevalue((!empty($userdata['label'])?$userdata['label']:'')); /* Strip linefeeds */ $data = ereg_replace("[\r\n]", ' ', $data); + + /** + * Make sure that entry fits into allocated record space. + * One byte is reserved for linefeed + */ + if (strlen($data) >= $this->line_length) { + return $this->set_error(_("Address book entry is too big")); + } + /* 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")); + return $this->set_error(_("Address book is read-only")); } - + /* Lock the file */ if(!$this->lock()) { return $this->set_error(_("Could not lock datafile")); } - + /* Write */ - $r = fwrite($this->filehandle, $data); - + $r = sq_fwrite($this->filehandle, $data); + /* Unlock file */ $this->unlock(); - - /* Test write result and exit if OK */ - if($r > 0) return true; - - /* Fail */ - $this->set_error(_("Write to addressbook failed")); - return false; + + /* Test write result */ + if($r === FALSE) { + /* Fail */ + $this->set_error(_("Write to address book failed")); + return FALSE; + } + + return TRUE; } - /* Delete address */ + /** + * Delete address + * @param string $alias alias that has to be deleted + * @return bool + */ function remove($alias) { if(!$this->writeable) { - return $this->set_error(_("Addressbook is read-only")); + return $this->set_error(_("Address book is read-only")); } - + /* Lock the file to make sure we're the only process working * on it. */ if(!$this->lock()) { return $this->set_error(_("Could not lock datafile")); } - + /* Read file into memory, ignoring nicknames to delete */ @rewind($this->filehandle); $i = 0; $rows = array(); - while($row = @fgetcsv($this->filehandle, 2048, '|')) { + while($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { if(!in_array($row[0], $alias)) { $rows[$i++] = $row; } } - + /* Write data back */ if(!$this->overwrite($rows)) { $this->unlock(); return false; } - + $this->unlock(); return true; } - /* Modify address */ + /** + * Modify address + * @param string $alias modified alias + * @param array $userdata new data + * @return bool true, if operation successful + */ function modify($alias, $userdata) { if(!$this->writeable) { - return $this->set_error(_("Addressbook is read-only")); + return $this->set_error(_("Address book is read-only")); } - + /* See if user exists */ $ret = $this->lookup($alias); if(empty($ret)) { - return $this->set_error(sprintf(_("User '%s' does not exist"), - $alias)); + // i18n: don't use html formating in translation + return $this->set_error(sprintf(_("User \"%s\" does not exist"),$alias)); + } + + /* If the alias changed, see if the new alias exists */ + if (strtolower($alias) != strtolower($userdata['nickname'])) { + $ret = $this->lookup($userdata['nickname']); + if (!empty($ret)) { + return $this->set_error(sprintf(_("User \"%s\" already exists"), $userdata['nickname'])); + } } - + /* Lock the file to make sure we're the only process working * on it. */ if(!$this->lock()) { return $this->set_error(_("Could not lock datafile")); } - - /* Read file into memory, modifying the data for the + + /* calculate userdata size */ + $data = $this->quotevalue($userdata['nickname']) . '|' + . $this->quotevalue($userdata['firstname']) . '|' + . $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) . '|' + . $this->quotevalue($userdata['email']) . '|' + . $this->quotevalue((!empty($userdata['label'])?$userdata['label']:'')); + /* make sure that it fits into allocated space */ + if (strlen($data) >= $this->line_length) { + return $this->set_error(_("Address book entry is too big")); + } + + /* Read file into memory, modifying the data for the * user identified by $alias */ $this->open(true); @rewind($this->filehandle); $i = 0; $rows = array(); - while($row = @fgetcsv($this->filehandle, 2048, '|')) { + while($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { if(strtolower($row[0]) != strtolower($alias)) { $rows[$i++] = $row; } else { $rows[$i++] = array(0 => $userdata['nickname'], 1 => $userdata['firstname'], - 2 => $userdata['lastname'], - 3 => $userdata['email'], - 4 => $userdata['label']); + 2 => (!empty($userdata['lastname'])?$userdata['lastname']:''), + 3 => $userdata['email'], + 4 => (!empty($userdata['label'])?$userdata['label']:'')); } } - + /* Write data back */ if(!$this->overwrite($rows)) { $this->unlock(); return false; } - + $this->unlock(); return true; } - - /* Function for quoting values before saving */ + + /** + * Function for quoting values before saving + * @param string $value string that has to be quoted + * @param string quoted string + */ function quotevalue($value) { /* Quote the field if it contains | or ". Double quotes need to * be replaced with "" */ @@ -378,6 +579,4 @@ class abook_local_file extends addressbook_backend { } return $value; } - -} /* End of class abook_local_file */ -?> +}