[sork] patch: customsql option and other goodies

Kendrick Vargas ken at hudat.com
Sat Apr 17 01:38:53 PDT 2004


Hi there folks. This is my first post, so bear with me... I probably
shouldn't be making it this early in the morning :-)

Yesterday I decided to play with the new CVS HEAD of Horde and all of it's
supporting modules. I've gotta say I'm very impressed. Notably I'm very
happy now that I can change passwords from Horde directly. Keeps me from
having to maintain a seperate "admin" interface for users to log in and
change their passwords at and change forwarding address, etc...

The passwd module worked great right out of the box. I use mysql tables
for all so I was able to configure the password module to use that (with
custom queries) to allow users to change their passwords. Worked great.
The forwards module, however, was a different story.

I maintain the forwards table on my server in mysql and access it from
postfix directly from the database. It's a very simple source -> target
mapping table, however none of the configuration options for forwards fit
what I needed. It struck me as odd that there was no option to set up
custom SQL forwarding tables, so I went ahead and wrote one :-)

Attached is a driver called "customsql" which essentially allows the
administrator to specify three queries for checking, setting, and
disabling forwarding, as well as 2 column names for returning the
keeplocal and target address values. I modified the conf.xml file to
reflect these additions.

I also added configuration settings to make the password requirement and
the keeplocal requirement optional. I did this because in my system there
is no way to keep a local copy of email if you are forwarding to another
address, therefore I don't want a checkbox for it. I also personally don't
see the point of asking for the password from the user when they are
allready logged into horde.

The templates and javascript files, as well as the logic in main.php
reflect these paramaters as now being options. I tested this all and it
works beautifully.

There were some things I didn't quite understand. It seems that when you
"get" the current user's forwarding settings, you use horde's password for
the user and then md5 crypt it. How does that work if the password isn't
plaintext? Also, why is the username from horde decomposed and then
constantly recomposed in the drivers? Wouldn't it be easier to just leave
it alone? Also, why aren't the ldap and sql drivers for exim called
exim-sql and exim-ldap? I originally wanted to call the driver sql (makes
the most sense) but couldn't because the name was allready taken by the
exim specific sql entry.

Anyways. It's like, late. I added myself as an author on the customsql
driver, hope you don't mind :-) I also don't know how the CVS tags in the
header will be affected, I know they're important but I don't really know
how to set them.

Thanks for a great set of modules. I hope these changes make it in to make
the forward module more flexible! Yay horde (and sork ;-) ! :-)
			-peace

-- 
Let he who is without clue kiss my ass
-------------- next part --------------
diff -Nrub forwards-orig/config/conf.xml forwards/config/conf.xml
--- forwards-orig/config/conf.xml	2004-01-26 03:56:43.000000000 -0500
+++ forwards/config/conf.xml	2004-04-17 04:12:32.000000000 -0400
@@ -131,7 +131,61 @@
      </configsection>
     </configsection>
    </case>
+
+   <case name="customsql" desc="Custom SQL based forwarding driver">
+    <configdescription>
+      This driver allows for custom SQL queries into which variables
+      will be substituted. The current FULL username will be provided as \U,
+      target address will be provided as \T, the given password will be
+      provided as \P, and the option to keep a local copy of forwarded
+      addresses will be a "Y" or "N" value provided as \L. If the query
+      to check the forwarding status returns no rows, it will be assumed
+      that forwarding is not enabled.
+    </configdescription>
+    <configsection name="params">
+     <configswitch name="driverconfig" desc="Database Configuration">horde
+      <case name="horde" desc="Horde defaults">
+      </case>
+      <case name="custom" desc="Custom parameters">
+       <configenum name="phptype" desc="What database backend should we use?">mysql
+        <values>
+         <value desc="MySQL">mysql</value>
+         <value desc="PostgreSQL">pgsql</value>
+         <value desc="ODBC">odbc</value>
+         <value desc="Oracle">oci8</value>
+        </values>
+       </configenum>
+       <configstring name="hostspec" desc="Database server/host/ODBC dsn">localhost</configstring>
+       <configstring name="username" desc="Username to connect to the database as">horde</configstring>
+       <configstring name="password" required="false" desc="Password to connect with">****</configstring>
+       <configstring name="database" desc="Database name to use">horde</configstring>
+       <configswitch name="protocol" desc="How should we connect to the database?">unix
+        <case name="unix" desc="UNIX Sockets">
+         <configstring name="socket" required="false" desc="Location of UNIX socket, if using one">/var/lib/mysql/mysql.sock</configstring>
+        </case>
+        <case name="tcp" desc="TCP/IP">
+         <configinteger name="port" required="false" desc="Port the DB is running on, if non-standard">3306</configinteger>
+        </case>
   </configswitch>
+      </case>
+     </configswitch>
+     <configdescription>
+      Queries and columns relavant to the forwarding tables.
+     </configdescription>
+     <configstring name="query_select" desc="Query to retrieve forwarding status"/>
+     <configstring name="query_set" desc="Query to set forwarding address"/>
+     <configstring name="query_disable" desc="Query to disable forwarding"/>
+     <configstring name="column_target" desc="Column specifying the target forwarding address"/>
+     <configstring name="column_keeplocal" desc="Column telling us if we are keeping a local copy"/>
+    </configsection>
+   </case>
+  </configswitch>
+ </configsection>
+
+ <configsection name="required">
+  <configheader>Required Options</configheader>
+  <configboolean name="keeplocal" desc="Setting this option will allow a user the choice of keeping a local copy of forwarded mail.">true</configboolean>
+  <configboolean name="password" desc="Setting this option will require the user to enter a password when changing their forwarding status.">true</configboolean>
  </configsection>
 
  <configsection name="user">
diff -Nrub forwards-orig/lib/Driver/customsql.php forwards/lib/Driver/customsql.php
--- forwards-orig/lib/Driver/customsql.php	1969-12-31 19:00:00.000000000 -0500
+++ forwards/lib/Driver/customsql.php	2004-04-17 04:10:39.000000000 -0400
@@ -0,0 +1,283 @@
+<?php
+/**
+ * $Horde: forwards/lib/Driver/sql.php,v 1.14 2004/03/30 17:38:20 chuck Exp $
+ *
+ * Copyright 2001-2004 Ilya Krel and Mike Cochrane
+ *
+ * See the enclosed file LICENSE for license information (BSD). If you
+ * did not receive this file, see http://www.horde.org/licenses/bsdl.php.
+ *
+ * Forwards_Driver_sql:: implements the Forwards_Driver API for SQL servers.
+ *
+ * @author  Ilya Krel <mail at krel.org.org>
+ * @author  Mike Cochrane <mike at graftonhall.co.nz>
+ * @author  Kendrick Vargas <ken at hudat.com>
+ * @version $Revision: 1.14 $
+ * @since   Forwards 2.1
+ * @package Forwards
+ */
+class Forwards_Driver_customsql extends Forwards_Driver {
+
+    /** file pointer to the sql connection. */
+    var $_db;
+
+    /** boolean which contains state of sql connection */
+    var $_connected = false;
+
+    /**
+     * Constructs a new sql Forwards_Driver object.
+     *
+     * @param array  $params        A hash containing connection parameters.
+     */
+    function Forwards_Driver_customsql($params = null)
+    {
+        if (is_null($params)) {
+            $params = Horde::getDriverConfig('', 'sql');
+        }
+        parent::Forwards_Driver($params);
+    }
+
+    /**
+     * Disconnect from the SQL server and clean up the connection.
+     *
+     * @return boolean true on success, false on failure.
+     */
+    function _disconnect()
+    {
+        if ($this->_connected) {
+            $this->_connected = false;
+            return $this->_db->disconnect();
+        }
+
+        return true;
+    }
+
+    /**
+     * Begins forwarding of mail for a user.
+     *
+     * @param string        $user     The username to enable forwarding for.
+     * @param string        $realm    The realm of the user.
+     * @param string        $pass     The password of the user.
+     *
+     * @param string        $target   The email address that mail should
+     *                                be forwarded to.
+     *
+     * @param optional boolean $keeplocal A flag that if true causes a
+     *                                    copy of forwarded email to be
+     *                                     kept in the local mailbox.
+     *
+     * @return boolean  Returns true on success, false on error.
+     */
+    function enableForwarding($user, $realm, $password, $target, $keeplocal)
+    {
+        // _connect() will die with Horde::fatal() upon failure.
+        $this->_connect();
+        $keeplocal = $keeplocal ? "Yes" : "No";
+
+        // Build the SQL query.
+        $query = $this->_params['query_set'];
+        $query = str_replace("\U", $this->_db->quote(Auth::getAuth()), $query);
+        $query = str_replace("\T", $this->_db->quote($target), $query);
+        $query = str_replace("\L", $this->_db->quote($keeplocal), $query);
+        $query = str_replace("\P", $this->_db->quote($password), $query);
+
+        // Execute the query.
+        $result = $this->_db->query($query);
+
+        if (!is_a($result, 'PEAR_Error')) {
+            if ($result === DB_OK) {
+                $this->_disconnect();
+                return true;
+            }
+        }
+
+        $this->_disconnect();
+        return false;
+    }
+
+    /**
+     * Stops forwarding of mail for a user.
+     *
+     * @param string        $user      The username of the user.
+     * @param string        $realm    The realm of the user.
+     * @param string        $pass      The password of the user.
+     *
+     * @return boolean  Returns true on success, false on error.
+     */
+    function disableForwarding($user, $realm, $password)
+    {
+        // _connect() will die with Horde::fatal() upon failure.
+        $this->_connect();
+
+        // Build the SQL query.
+        $query = $this->_params['query_disable'];
+        $query = str_replace("\U", $this->_db->quote(Auth::getAuth()), $query);
+        $query = str_replace("\P", $this->_db->quote($password), $query);
+
+        // Execute the query.
+        $result = $this->_db->query($query);
+
+        if (!is_a($result, 'PEAR_Error')) {
+            if ($result === DB_OK) {
+                $this->_disconnect();
+                return true;
+            }
+        }
+
+        $this->_disconnect();
+        return false;
+    }
+
+    /**
+     * Retrieves status of mail redirection for a user
+     *
+     * @param string        $user      The username of the user to check.
+     *
+     * @param string        $realm    The realm of the user to check.
+     *
+     * @return mixed        Returns 'Y' if forwarding is enabled, or false.
+     */
+    function isEnabledForwarding($user, $realm, $password)
+    {
+        // Build username.
+        $myuser = $this->_buildUsername($user, $realm);
+
+        // get current details
+        $current_details = $this->_getUserDetails($myuser, $realm, $password);
+        if ($current_details === false) {
+            return false;
+        } else {
+            return 'Y';
+        }
+
+    }
+
+    /**
+     * Returns true if a local copy of forwarded messages is being kept
+     *
+     * @param string        $user      The username of the user to check.
+     *
+     * @param string        $realm    The realm of the user to check.
+     *
+     * @return boolean      Returns true if retain local copy is enabled
+     *                      else false.
+     */
+    function isKeepLocal($user, $realm, $password)
+    {
+        // Build username.
+        $myuser = $this->_buildUsername($user, $realm);
+
+        // Get current details.
+        $current_details = $this->_getUserDetails($myuser, $realm, $password);
+        if ($current_details === false) {
+            return false;
+        }
+
+        // Check retain copy flag.
+        if ($current_details[$this->_params['column_keeplocal']] == 'Y') {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Retrieves current target of mail redirection
+     *
+     * @param string        $user      The username of the user.
+     * @param string        $realm    The realm of the user.
+     *
+     * @return string   A string of current forwarding address or false.
+     */
+    function currentTarget($user, $realm, $password)
+    {
+        $current_details = $this->_getUserDetails($user, $realm, $password);
+
+        // Check current forwarding mail address.
+        $target = $current_details[$this->_params['column_target']];
+        return $target;
+    }
+
+    /**
+     * Retreive relevant line from sql server
+     *
+     * @param   $user            The username for which to retrieve details.
+     * @param   $realm           The realm (domain) for the user.
+     * @param   $password        The password for user.
+     *
+     * @return  Mixed            Mysql result resource or (boolean) False.
+     */
+    function _getUserDetails($user, $realm, $password)
+    {
+        // _connect() will die with Horde::fatal() upon failure.
+        $this->_connect();
+
+        // Build the SQL query.
+        $query = $this->_params['query_select'];
+        $query = str_replace("\U", $this->_db->quote(Auth::getAuth()), $query);
+        $query = str_replace("\P", $this->_db->quote($password), $query);
+
+        // Execute the query.
+        $result = $this->_db->query($query);
+
+        if (!is_a($result, 'PEAR_Error')) {
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+
+            $this->_disconnect();
+            if (is_array($row)) {
+                return $row;
+            } else {
+                $result->free();
+                return false;
+            }
+        }
+
+        $this->_disconnect();
+        return false;
+    }
+
+    /**
+     * Builds a username based on presence of realm.
+     *
+     * @return string  $user@$realm or $user.
+     */
+    function _buildUsername($user, $realm)
+    {
+        if ($realm === 'default' ||
+            $realm === '') {
+            return $user;
+        } else {
+            return $user . '@' . $realm;
+        }
+    }
+
+    /**
+     * Do an sql connect and login as user with privilege to change
+     * passwd.
+     *
+     * @return   boolean   True or False based on success of connect
+     */
+    function _connect()
+    {
+        if (!$this->_connected) {
+            Horde::assertDriverConfig($this->_params, 'server',
+                array('phptype', 'hostspec', 'username', 'database', 'query_select', 'query_set', 'query_disable', 'column_target', 'column_keeplocal'),
+                'authentication SQL');
+
+            // Connect to SQL server using supplied parameters.
+            include_once 'DB.php';
+            $this->_db = &DB::connect($this->_params,
+                                      array('persistent' => !empty($this->_params['persistent'])));
+            if (is_a($this->_db, 'PEAR_Error')) {
+                Horde::fatal(PEAR::raiseError(_("Unable to connect to SQL server.")), __FILE__, __LINE__);
+            }
+
+            // Enable the "portability" option.
+            $this->_db->setOption('optimize', 'portability');
+            $this->_connected = true;
+        }
+
+        return true;
+    }
+
+}
diff -Nrub forwards-orig/main.php forwards/main.php
--- forwards-orig/main.php	2004-03-30 12:38:21.000000000 -0500
+++ forwards/main.php	2004-04-17 04:06:58.000000000 -0400
@@ -35,7 +35,7 @@
     }
 
     $oldpassword = Util::getFormData('oldpassword', false);
-    if (!$oldpassword) {
+    if ($conf['required']['password'] && !$oldpassword) {
         $notification->push(_("You must give your password"), 'horde.warning');
         $forwardmode = 'error';
     }
diff -Nrub forwards-orig/templates/main/javascript.inc forwards/templates/main/javascript.inc
--- forwards-orig/templates/main/javascript.inc	2002-11-14 22:41:41.000000000 -0500
+++ forwards/templates/main/javascript.inc	2004-04-17 04:04:56.000000000 -0400
@@ -1,9 +1,9 @@
 <script language="JavaScript" type="text/javascript">
 <!--
 
-function submit_form()
+function submit_form(checkPassword)
 {
-    if (document.forwards.oldpassword.value == "") {
+    if (checkPassword && document.forwards.oldpassword.value == "") {
         alert('You must provide your password');
         document.forwards.oldpassword.focus();
         return false;
diff -Nrub forwards-orig/templates/main/main.inc forwards/templates/main/main.inc
--- forwards-orig/templates/main/main.inc	2003-09-16 19:04:17.000000000 -0400
+++ forwards/templates/main/main.inc	2004-04-17 04:03:28.000000000 -0400
@@ -30,24 +30,30 @@
         <?php echo Util::formInput() ?>
         <input type="radio" name="mode" value="set" checked="checked">
         <?php echo _("Set/install a forward to:") ?>&nbsp;
-        <input type="text" name="where" value="<?php echo $current_target ?>" size="63" maxlength="150">
+        <input type="text" name="where" value="<?php echo $current_target ?>" size="63" maxlength="150"><br>
 
+<?php if ($conf['required']['keeplocal']): ?>
         <blockquote>
             <input type="checkbox" name="metoo">
             <?php echo _("Keep a copy in your local mailbox?") ?>
         </blockquote>
+<?php endif; ?>
 
         <input type="radio" name="mode" value="unset">
         <?php echo _("Unset/remove a forward") ?>
 
         <br /><br />
+
+<?php if ($conf['required']['password']): ?>
         <?php echo _("For your protection and safety, you must identify yourself with your login password to verify this change.") ?>&nbsp;
         <?php echo _("Then submit the form so that your forward can be updated.") ?>
         <br />
         <input type="hidden" name="userid" value="<?php echo Auth::getAuth() ?>">
         <?php echo _("Your password:") ?> &nbsp;
-        <input type="password" name="oldpassword" size="16" maxlength="32">&nbsp;
-        <input class="button" type="submit" name="submit" value="<?php echo _("Submit") ?>" onclick="return submit_form();">
+<?php endif; ?>
+
+        <input type="<?php echo $conf['required']['password'] ? 'password' : 'hidden' ?>" name="oldpassword" size="16" maxlength="32">&nbsp;
+        <input class="button" type="submit" name="submit" value="<?php echo _("Submit") ?>" onclick="return submit_form(<?php echo $conf['required']['password'] ? 'true' : 'false' ?>);">
         </form>
     </td>
 </tr>


More information about the sork mailing list