Set more restrictive permissions on abook files - matches how pref files are treated...
[squirrelmail.git] / functions / abook_local_file.php
index 6dca53eb0a99e07315997449e8c6461e60aac156..fc05257a4231685866cbc4b3d66dfeb730859381 100644 (file)
@@ -1,10 +1,10 @@
 <?php
+
 /**
  * abook_local_file.php
  *
- * Copyright (c) 1999-2005 The SquirrelMail Project Team
- * Licensed under the GNU GPL. For full terms see the file COPYING.
- *
+ * @copyright &copy; 1999-2007 The SquirrelMail Project Team
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
  * @subpackage addressbook
@@ -81,6 +81,13 @@ class abook_local_file extends addressbook_backend {
      * @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 ======================= */
 
@@ -90,7 +97,7 @@ class abook_local_file extends addressbook_backend {
      * @return bool
      */
     function abook_local_file($param) {
-        $this->sname = _("Personal address book");
+        $this->sname = _("Personal Address Book");
         $this->umask = Umask();
 
         if(is_array($param)) {
@@ -122,6 +129,9 @@ class abook_local_file extends addressbook_backend {
             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 {
@@ -141,7 +151,7 @@ class abook_local_file extends addressbook_backend {
         $this->error = '';
         $file   = $this->filename;
         $create = $this->create;
-        $fopenmode = (($this->writeable && is_writable($file)) ? 'a+' : 'r');
+        $fopenmode = (($this->writeable && sq_is_writable($file)) ? 'a+' : 'r');
 
         /* Return true is file is open and $new is unset */
         if($this->filehandle && !$new) {
@@ -242,6 +252,7 @@ class abook_local_file extends addressbook_backend {
           return $this->set_error($this->filename . ':' . _("Unable to update"));
         }
         @unlink($this->filename . '.tmp');
+        @chmod($this->filename, 0600);
         $this->unlock();
         $this->open(true);
         return true;
@@ -259,6 +270,10 @@ class abook_local_file extends addressbook_backend {
         /* 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);
@@ -270,17 +285,32 @@ class abook_local_file extends addressbook_backend {
         }
         @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));
+                }
             }
         }
 
@@ -288,30 +318,53 @@ class abook_local_file extends addressbook_backend {
     }
 
     /**
-     * Lookup alias
-     * @param string $alias alias
-     * @return array search results
+     * Lookup an address by the indicated field.
+     *
+     * @param string  $value The value to look up
+     * @param integer $field The field to look in, should be one
+     *                       of the SM_ABOOK_FIELD_* constants
+     *                       defined in include/constants.php
+     *                       (OPTIONAL; defaults to nickname field)
+     *                       NOTE: uniqueness is only guaranteed
+     *                       when the nickname field is used here;
+     *                       otherwise, the first matching address
+     *                       is returned.
+     *
+     * @return array Array with lookup results when the value
+     *               was found, an empty array if the value was
+     *               not found.
+     *
      */
-    function lookup($alias) {
-        if(empty($alias)) {
+    function lookup($value, $field=SM_ABOOK_FIELD_NICKNAME) {
+        if(empty($value)) {
             return array();
         }
 
-        $alias = strtolower($alias);
+        $value = strtolower($value);
 
         $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[$field]) == $value) {
+                   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);
+                }
             }
         }
 
@@ -332,15 +385,26 @@ class abook_local_file extends addressbook_backend {
         $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;
     }
@@ -352,31 +416,40 @@ class abook_local_file extends addressbook_backend {
      */
     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 exists"),
-                        '&quot;' . $ret['nickname'] . '&quot;'));
+            // 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 */
@@ -393,7 +466,7 @@ class abook_local_file extends addressbook_backend {
         /* Test write result */
         if($r === FALSE) {
             /* Fail */
-            $this->set_error(_("Write to addressbook failed"));
+            $this->set_error(_("Write to address book failed"));
             return FALSE;
         }
 
@@ -407,7 +480,7 @@ class abook_local_file extends addressbook_backend {
      */
     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
@@ -420,7 +493,7 @@ class abook_local_file extends addressbook_backend {
         @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;
             }
@@ -444,37 +517,56 @@ class abook_local_file extends addressbook_backend {
      */
     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"),
-                        '&quot;' . $alias . '&quot;'));
+            // 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"));
         }
 
+        /* 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'],
+                                    2 => (!empty($userdata['lastname'])?$userdata['lastname']:''),
                                     3 => $userdata['email'],
-                                    4 => $userdata['label']);
+                                    4 => (!empty($userdata['label'])?$userdata['label']:''));
             }
         }
 
@@ -501,6 +593,4 @@ class abook_local_file extends addressbook_backend {
         }
         return $value;
     }
-
-} /* End of class abook_local_file */
-?>
\ No newline at end of file
+}