diff -ur courier-imap-1.5.3-orig/authlib/README.authmysql.myownquery courier-imap-1.5.3/authlib/README.authmysql.myownquery
--- courier-imap-1.5.3-orig/authlib/README.authmysql.myownquery	Tue Jan  8 06:01:22 2002
+++ courier-imap-1.5.3/authlib/README.authmysql.myownquery	Mon Oct 14 01:05:11 2002
@@ -2,13 +2,18 @@
 
 
 
-	    Developer Notes for courier-imap-myownquery.patch
 
+		    Developer Notes and Usage Instructions
+    				    
+				    of
+				    
+		       courier-imap-authmysql-myownquery
 
+				    by
+			
+			Pawel Wilk <siefca@kernel.pl>
 
 
-							document version: 1.03
-							author:	Pawel Wilk
 
 
 
@@ -19,75 +24,843 @@
 
 
 
+ .. table of contents..
 
 
+PREAMBLE
 
+PART I - Usage Instructions
 
+	1 What's that?
+	
+	2 When will I need it?
+	
+	3 How does it work?
+	  3.1 configuration variables
+	  3.2 queries
+	  3.3 substitutions
+	  3.4 triggers
+	  3.5 empty default domain name
+	  3.6 whitespaces in queries
+	
+	4 Examples of usage
+	  4.1 corporate mail system
+	    4.1.1 database structure
+	    4.1.2 authdaemon configuration
+	  4.2 virtual mail domains provider
+	    4.2.1 database structure
+	    4.2.2 authdaemon configuration
+
+PART II - Developer Notes
+
+	1 Modifications overview
+
+	2 Definitions
+
+	3 New data types
+	  3.1 struct var_data
+	  3.2 typedef size_t (*parsefunc)
+  
+	4 New functions
+	  4.1 get_variable
+	  4.2 parse_core
+	  4.3 ParsePlugin_counter
+	  4.4 ParsePlugin_builder
+	  4.5 parse_string
+	  4.6 validate_password
+	  4.7 get_localpart
+	  4.8 get_domain
+	  4.9 get_username
+	  4.10 parse_select_clause
+	  4.11 parse_chpass_clause
+	  4.12 auth_mysql_on_trigger
+	  4.13 auth_mysql_on_pass
+	  4.14 auth_mysql_checkpassword
 
+	5 Ideas and TODO
 
-0 What's that?
+	6 Thanks
 
-1 Modifications overview
 
-2 Definitions
 
-3 New data types
-  3.1 struct var_data
-  3.2 typedef size_t (*parsefunc)
-  
-4 New functions
-  4.1 get_variable
-  4.2 parse_core
-  4.3 ParsePlugin_counter
-  4.4 ParsePlugin_builder
-  4.5 parse_string
-  4.6 validate_password
-  4.7 get_localpart
-  4.8 get_domain
-  4.9 parse_select_clause
-  4.10 parse_chpass_clause
-  
-5 Ideas and TODO
 
-6 Thanks
 
 
+//////////////////////////////// PREAMBLE /////////////////////////////////////
+
+This is README document for "myownquery" patch for Courier's Authdaemon.
+This document version is 1.36
+
+* The patch, which this document describes is developed for Courier-IMAP
+  version 1.5.3 and the official patch revision is 2.
+
+* You can download the patch from the FTP server using URI:
+
+ftp://ftp.pld.org.pl/people/siefca/patches/courier/courier-imap-1.5.3-myownquery.patch
 
+   it should also be accessible on mirroring servers, which list can be
+   obtained under: http://www.pld.org.pl/
+
+* To know more about getting Courier see http://www.courier-mta.org/
+
+* This patch, including the documentation, is released under GNU GPL
+  license terms. You should look at the COPYING file present in
+  Courier sources.
+
+.
+.
+.
+.
+.
+.
+
+//////////////////////////////// PART I - Usage ///////////////////////////////
 
 		    *-----------------------
-		     0 What's that?
+		     1 What's that?
 		    *-----------------------
 
-Courier-imap-myownquery.patch allows administrator to set own MySQL queries
-used by authdaemon to authenticate user (including fetchig credentials) and to
-change user's password. It allows to construct SELECT or UPDATE clause in the
-configuration file (authmysqlrc) by adding two new configuration variables:
-MYSQL_SELECT_CLAUSE and MYSQL_CHPASS_CLAUSE. It may be useful in the mail
-environments where there is such a need to have different database structure
-and/or tables scheme than expected by authmysql module.
+Courier-imap-myownquery's features allow the administrator to set his
+own MySQL queries used by authdaemon to authenticate a user (including
+fetchig his credentials) and to change the user's password. It allows
+one to write a SELECT and UPDATE clause in the configuration file
+(authmysqlrc) using the new configuration options. It may be useful in
+mail environments where there is a need to have a different database
+structure and/or tables scheme than expected by authmysql module.
+
+It also implements a small parsing engine for substitution of
+variables which may appear in the SQL clauses, such as a username or a
+domain.
 
-It also implements a small parsing engine for substitution variables which
-may appear in the clauses and are used to put informations like username
-or domain into the right place of a query.
 
-This patch was created using `diff -Nur` on courier-imap-1.3.12 source.
 
 
 
+		    *-----------------------
+		     2 When will I need it?
+		    *-----------------------
+
+ o When you already have some MySQL database filled up with the data
+    and there is no chance to change the whole structure to make it
+    work with a standard authmysql table. Typical situation is when
+    all the
+    data required to authenticate a user is arranged in more than one
+    table.
+
+ o When you have some great idea how to make the database structure
+    more efficient due to your needs and your requirements.
+	
+ o When doing something 'by-myself' is in your style and you just want
+    to create your own database, just to feel the pleasure of doing
+    something original. :)
 
 
+
+
+
+		    *-----------------------
+		     3 How does it work?
+		    *-----------------------
+
+There are three things which the feature concerns:
+
+- fetching clauses from the configuration file
+- doing substitution replacements inside of SQL clauses
+- passing prepared query on to the mysql interface funtions
+
+3.1 configuration options
+
+You can apply your own MySQL queries using a set of the configuration
+options.  The options you'll need to make the authmysql your slave
+are:
+
+MYSQL_SERVER		(required)
+MYSQL_USERNAME		(required)
+MYSQL_PASSWORD		(required)
+
+		The server name, userid, and password used to log in.
+
+MYSQL_DATABASE (required)
+
+		The name of the MySQL database we will open.
+
+DEFAULT_DOMAIN (optional)
+
+		If DEFAULT_DOMAIN is defined, and someone tries to log
+		in as 'user', we will look up 'user@DEFAULT_DOMAIN'
+		instead.
+
+USER_DOMAIN_SEPARATORS (optional)
+
+		This may contain the set of characters used by parsing
+		routines to split local part of the virtual mailbox
+		name from the part which describes the domain name. If
+		it's not defined the set containing @% is assumed, so
+		the user can enter either: user@domain or user%domain
+
+MYSQL_SELECT_CLAUSE	(required)
+MYSQL_CHPASS_CLAUSE	(required under some circumstances)
+
+		These are the major options you should use. See 3.2
+		section for more info.
+
+ON_PASS_OK_CLAUSE	(optional)
+ON_PASS_FAIL_CLAUSE	(optional)
+ON_PASS_CHANGE_CLAUSE	(optional)
+
+		These are used to do a MySQL query whether user has
+		passed the authentication verification
+		(ON_PASS_OK_CLAUSE) or there was the authentication
+		failure (ON_PASS_FAIL_CLAUSE), or whether user has
+		changed his password (ON_PASS_CHANGE_CLAUSE).
+		Query results have no meaning. You can use the same
+		substitution variables in your query as with
+		MYSQL_SELECT_CLAUSE. See 3.4 section for more info.
+
+The options which have no effect, and may be safetly left blank are:
+
+MYSQL_USER_TABLE
+MYSQL_CRYPT_PWFIELD
+MYSQL_CLEAR_PWFIELD
+MYSQL_UID_FIELD
+MYSQL_GID_FIELD
+MYSQL_LOGIN_FIELD
+MYSQL_HOME_FIELD
+MYSQL_NAME_FIELD
+MYSQL_MAILDIR_FIELD
+MYSQL_QUOTA_FIELD
+MYSQL_WHERE_CLAUSE
+
+3.2 queries
+
+The feature adds two configuration options (clauses), which are parsed
+first, and then applied as MySQL queries to MySQL interface
+routines. These options are: MYSQL_SELECT_CLAUSE and
+MYSQL_CHPASS_CLAUSE. After each option a number of spaces and/or tabs
+is allowed, and then MySQL query is expected. For better look, your
+queries can have line breaks. Each line break should be preceded by
+the backslash sign. Look into examples chapter (4) to see how it
+should look like. First clause is used to authenticate a user, and the
+second to change his password.
+
+You should note that a query identified by MYSQL_SELECT_CLAUSE should
+return fixed number (9) of fields and each field should match the
+variable expected by authentication routines. These fields are:
+
+
+* username - which is the currently logged user's username (or the
+             username with domain if you want it)
+
+  cryptpw - which is the user's crypted password
+*
+  clearpw - which is the user's plaintext password
+
+* uid - which is a numerical UID value used as a process's UID when
+        accessing the mailbox directory
+
+* gid - as above, but refers to GID
+
+* home - which contains full path to the user's home directory
+
+  maildir - which contains the directory name inside the user's home;
+	    treated as INBOX folder when accessing mailbox - if it's
+	    empty then the 'Maildir' string is used
+
+quota - which describes a quota size for the mailbox
+
+fullname - which may contain the user's fullname
+
+(The fields marked by the asterix sign are required and cannot have an
+ empty results. In case of passwords, at least one of the shown fields
+ should contain some result.)
+
+So, the typical query clause may start with:
+
+MYSQL_SELECT_CLAUSE SELECT                                      \
+ users.username,						\
+ users.cryptpw,							\
+ users.clearpw,                  				\
+ domains.uid,							\
+ domains.gid,                                      		\
+ users.mailbox_path)         					\
+ ''								\ 
+ domains.quota,							\
+ ''			                                        \
+...
+
+Note that in this short example we're assuming that we have two tables
+(users and domains) and INBOX path is always called 'Maildir' and
+we're not using the fullname field (the query will always return an
+empty string in its place).
+
+Also note that you may discard one of the password fields if you don't
+want to use an authentication mechanism, which needs it. For example,
+if you don't want to use MD5-CRAM you may put '' into the place of
+clearpw (because, for example you're in paranoid mode and you don't
+even want to keep plain passwords in the database:).
+
+3.3 substitutions
+
+Substitutions are strings, which may appear in your query, and which
+have a special meaning. You can also call them substitution
+variables. If substitution variable is known for a clause context then
+it is parsed. If it isn't known the error is generated. In the default
+compilation of authmysql module any substitution variable is declared
+inside of two substrings - the first is a dollar sign concatenated
+with opening parenthesis, and the second is a closing parenthesis
+sign. First symbol identifies beginning of a substitution variable,
+and the second closes it. The string between the beginning and the
+closing symbol is called substitution variable's name.
+
+When, as I said before, the name is known to the parsing routine the
+substitution is made and the proper value appears in place of the
+substitution variable, while passing on the query for later
+processing.
+
+Allowed substitution variables:
+
+context: MYSQL_SELECT_CLAUSE, ON_PASS_FAIL_CLAUSE, ON_PASS_OK_CLAUSE,
+	 ON_PASS_CHANGE_CLAUSE
+
+$(local_part) 		will be replaced by currently verified user's username
+			(without the domain part)
+
+$(domain) 		will be replaced by currently verified user's domain
+			name (if present, or if not present but the
+			DEFAULT_DOMAIN was used) or by the empty,
+			zero-length
+			string if the domain cannot be obtained
+
+$(username) 		will be replaced by currently verified user's username
+			concatenated with the given domain name using
+			@ symbol -- if the domiain name cannot be
+			obtained (even looking up DEFAULT_DOMAIN) the
+			separation sign will not appear and only the
+			given username will be presented
+
+context: MYSQL_CHPASS_CLAUSE
+
+$(local_part)           will be replaced by currently verified user's username
+                        (without the domain part)
+
+$(domain)               will be replaced by currently verified user's domain
+                        name (if present, or if not present but the
+                        DEFAULT_DOMAIN was used) or by the empty, zero-length
+                        string if the domain cannot be obtained
+
+$(username)             will be replaced by currently verified user's username
+                        concatenated with the given domain name using
+                        @ symbol -- if the domiain name cannot be
+                        obtained (even by looking up DEFAULT_DOMAIN)
+                        the separation sign will not appear and only
+                        the given username will be presented
+
+$(newpass)		will be replaced by currently authenticated user's
+			new password to set up (plaintext password)
+
+$(newpass_crypt) 	will be replaced by currently authenticated user's
+			new password to set up (MD5 form created from
+			entered plain form)
+
+3.4 triggers
+
+Triggers are MySQL queries, which are performed depending on
+authentication state. Currently, there are three triggers which you
+may use. First is called ON_PASS_OK_CLAUSE and it is performed when
+the authentication succeedes.  The second is called
+ON_PASS_FAIL_CLAUSE and has the reverse meaning. The third, which name
+is ON_PASS_CHANGE_CLAUSE is performed whenever user has changed his
+password.
+
+You can declare triggers in the authmysqlrc configuration file. They
+can be used to arrange some logging facility in the database or just
+to keep last times of the successful/failed login tries. The typical
+trigger, which puts last login date into the users' table can look
+like this:
+
+ON_PASS_OK_CLAUSE	UPDATE users SET last_login=CURRENT_TIMESTAMP \
+			WHERE username='$(username)';
+
+or, if you would like to know about last login failure for users you can try:
+
+ON_PASS_FAIL_CLAUSE	UPDATE users SET last_bad_login=CURRENT_TIMESTAMP \
+			WHERE username='$(username)';
+
+and/or, if you want to know last password changes you can use:
+
+ON_PASS_CHANGE_CLAUSE	UPDATE users SET pw_change=CURRENT_TIMESTAMP \
+			WHERE username='$(username)';
+
+Note, that YOU CAN use the triggers even if you aren't using
+MYSQL_SELECT_CLAUSE. Also note, that if the entered username
+doesn't match any real user ON_PASS_FAIL_CLAUSE will be simply
+discarded. To watch brute force attacs against known usernames
+you have to use log files. ;]
+
+3.5 empty default domain name
+
+Sometimes happens, that you want to allow user to log in without
+having a domain name entered and you expect it will be treated as an
+empty string, neither an error, nor default domain. In that case you
+should leave DEFAULT_DOMAIN option unset in authmysqlrc file and your
+database should have empty (not NULL) string fields for users without
+the domain name specified.
+
+3.6 whitespaces in queries
+
+In a few examples, here and in authmysqlrc file, I used to put many
+whitespaces and tabs to make the examples more clear for reader.
+However, it is recommended to not torture authdaemon's parser in
+that way and to remove unnecessary characters. ;]
+
+For example, the clause:
+
+MYSQL_CHPASS_CLAUSE UPDATE                                       \
+ users                                                           \
+ SET clearpw='$(newpass)',                                       \
+     cryptpw='$(newpass_crypt)'                                  \
+ WHERE username='$(local_part)'                                  \
+ AND   domain_name='$(domain)'
+
+can be safetly rewritten as:
+
+MYSQL_CHPASS_CLAUSE UPDATE users \
+SET clearpw='$(newpass)', cryptpw='$(newpass_crypt)' \
+WHERE username='$(local_part)' AND domain_name='$(domain)'
+
+
+
+
+
+
+		    *-----------------------
+		     4 Examples of usage
+		    *-----------------------
+
+The "ownquery" feature gives you possibility to adapt an
+authentication query to the database. So the first thing you have to
+do is to design the database structure you need, whithout being
+grieved at what structure authentication routines like. You have to
+take care about four essential things:
+
+ o  The database
+ 
+ o  The users' data in the database
+ 
+ o  The proper directories for keeping virtual mailboxes and a system
+    user which can read and write them
+ 
+ o  The proper MySQL queries in your authmysqlrc configuration file
+
+4.1 corporate mail system
+
+This example is concerned about a corporate mail system with a small
+ammount of served virtual domains. The database scheme was derived
+from tpop3d documentation and modified a bit.
+
+4.1.1 database structure
+
+Our goal here is to separate the data responsible for keeping mailbox
+credentials from the data describing domains.
+
+Let's create some tables for our example, filled up with an example
+data:
+
+table:          domains
+
+purpose:        associates virtual domain with domain name and informations
+		necessary to access mailboxes withing the domain
+
+fields:         domain_name         - fully qualified domain name
+                path_prefix         - absolute pathname which points to
+                                      a directory where domain's mailboxes
+                                      are located
+                quota               - default quota for each mailbox
+                uid                 - UID used to work on mailboxes
+                gid                 - GID used to work on mailboxes
+		
+        +----------------+-------------+-----+-----+----------+
+        | domain_name    | path_prefix | uid | gid | quota    |
+        +----------------+-------------+-----+-----+----------+
+        | exampledom.com | /var/mail/x | 555 | 555 | 10000000 |
+	| pld.org.pl     | /var/mail/p | 556 | 556 | 20000000 |
+	| pld.net.pl     | /var/mail/p | 556 | 556 | 20000000 |
+	+----------------+-------------+-----+-----+----------+
+
+table:          users
+
+purpose:        associates virtual mailbox with user and domain name,
+		and with informations necessary to access mailbox
+
+fields:         username	     - user login name (mailbox name)
+		domain_name          - fully qualified domain name
+                mailbox_path         - relative pathname for mailbox
+                                       (will be appended to the path_prefix
+                                        from domain_auth table to specify
+				        user's mailbox location)
+                cryptpw              - crypted password
+                plainpw              - plaintext password
+
+        +----------+----------------+--------------+------------+--------+
+	| username | domain_name    | mailbox_path | cryptpw   | plainpw |
+	+----------+----------------+--------------+-----------+---------+
+	| siefca   | pld.org.pl     | s/siefca     | $1$fs45.. | dupa.8  |
+	| siefca   | pld.net.pl     | s/siefca     | $1$fs45.. | dupa.8  |
+	| f00bar   | exampledom.com | foobar       | $1$g44w.. | secret  |
+	+----------+----------------+--------------+-----------+---------+ 
+
+Using MySQL monitor you can create these tables entering CREATE
+sequences.  Be sure to connect to the database using administrative
+MySQL account (usualy: mysql -u mysql -p).
+
+--------------------- cut here
+
+# Create the database called vmail.
+
+CREATE database vmail;
+
+# Create an example MySQL user, which can read, write and delete data
+# from vmail database. Username: vuser Password: secret_password
+
+GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
+             TO vuser@localhost
+             IDENTIFIED BY 'secret_password';
+
+FLUSH PRIVILEGES;
+
+# Create the tables.
+
+use vmail;
+
+CREATE TABLE domains (
+        domain_name           char(255) DEFAULT '',
+        path_prefix           char(255) DEFAULT '' NOT NULL,
+        uid                   int(10) unsigned DEFAULT '15000' NOT NULL,
+        gid                   int(10) unsigned DEFAULT '15000' NOT NULL,
+        quota                 char(255) DEFAULT '2000000' NOT NULL,
+        KEY domain_name (domain_name(255))
+        );
+
+CREATE TABLE users (
+        username              char(128) DEFAULT '' NOT NULL,
+        domain_name           char(255) DEFAULT '',
+        mailbox_path          char(255) DEFAULT '' NOT NULL,
+        cryptpw               char(128) DEFAULT '' NOT NULL,
+        clearpw               char(128) DEFAULT '' NOT NULL,
+        KEY username (username(128))
+        );
+
+# Create an example virtual domain entry
+# name  : exampledom.com
+# uid   : 555
+# gid   : 555
+# path  : /var/mail/x
+# quota : 10 Megs per mailbox
+
+INSERT INTO domains VALUES ('exampledom.com', '/var/mail/x', 555, 555,
+                            '10000000');
+
+# Create an example virtual user entry
+# username      : siefca
+# domain name   : exampledom.com
+# cryptpw       : $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
+# clearpw       : dupa.8
+# mailbox path  : s/siefca
+
+INSERT INTO users VALUES ('siefca', 'exampledom.com', 's/siefca',
+			  '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
+			  'dupa.8');
+
+--------------------- cut here
+
+Note: If you would like to have your passwords more safe, then just
+	omit the clearpw column and put '' into the config-query in
+	its place while doing SELECT on a database. But be ware -
+	you'll be unable to use authentication methods which needs it,
+	like MD5_CRAM.
+
+4.1.2 authdaemon configuration
+
+When our database is ready we can set up the configuration. :-) Go to
+authmysqlrc file and edit it.
+
+At the beginning we should take care about general informations, which
+are identifying our database:
+
+MYSQL_SERVER            localhost
+MYSQL_USERNAME          vuser
+MYSQL_PASSWORD          secret_password
+MYSQL_DATABASE          vmail
+
+Then we should add a clause responsible for authenticating user and
+fetching credentials:
+
+DEFAULT_DOMAIN          exampledom.com
+
+MYSQL_SELECT_CLAUSE SELECT                                      \
+ users.username, users.cryptpw, users.clearpw,                  \
+ domains.uid, domains.gid,                                      \
+ CONCAT_WS('/',domains.path_prefix,users.mailbox_path),         \
+ '', domains.quota, ''                                          \
+ FROM users, domains                                            \
+  WHERE domains.domain_name='$(domain)'                         \
+    AND users.username='$(local_part)'                          \
+    AND domains.domain_name=users.domain_name
+
+
+Note the '' in the place of field which tells where user's INBOX
+resides and in place of realname field. You should use '' if you want
+to put an empty value as a query result for some field.
+
+We also should add some configuration for changing user's password:
+
+MYSQL_CHPASS_CLAUSE UPDATE                                       \
+ users                                                           \
+ SET clearpw='$(newpass)',                                       \
+     cryptpw='$(newpass_crypt)'                                  \
+ WHERE username='$(local_part)'                                  \
+ AND   domain_name='$(domain)'
+
+And finally...
+Create a system user/group and a proper directory structure. In our example:
+
+groupadd -g 555 xdomain
+useradd -u 555 -g 555 xdomain
+mkdir -p /var/mail/x/s/siefca
+chmod -R 0770 /var/mail/x
+maildirmake /var/mail/x/s/siefca/Maildir
+chown -R xdomain.xdomain /var/mail/x
+
+Now, restart the authdaemon and see if it works. Try: telnet 0 pop3
+
+and type:
+
+USER siefca [ENTER]
+PASS dupa.8 [ENTER]
+
+You should get Ok response. ;)
+
+4.2 virtual mail domains provider
+
+Let's consider more complicated database scheme, where there is a need
+to associate a lot of information with the domain name, including
+registrant information, owner, etc. That implies data separation
+between domain name, user and domain additional informations (which
+are unwanted when authentication process takes place). By proper data
+separation I mean avoiding unwanted redundancy in the database.
+
+Currently applied example doesn't care about the update password
+problem.  This is due to current abilities of MySQL and authdaemon
+(authmysql).  MySQL doesn't support subsequent SELECTs on UPDATE
+operation, and authmysql doesn't supports batched queries at the
+moment.
+
+4.2.1 database structure
+
+table:		domain_names
+
+purpose:	associates domain_id with domain name
+
+fields:		domain_name		- fully qualified domain name
+		domain_id		- domain identifier
+
+	+----------------+-----------+
+	| domain_name    | domain_id |
+	+----------------+-----------+
+	| exampledom.com |         1 |
+	| pld.org.pl     |         2 |
+	| pld.net.pl     |         2 |
+	| foobare.net.uk |         3 |
+	+----------------+-----------+
+
+Note, that for pld.org.pl and pld.net.pl the domain identifiers are
+the same.  We can create a domain aliases in such a way. :)
+
+table:		domain_auth
+
+purpose:	associates domain_id with authentication credentials
+		which are common for all users in the virtual domain
+
+fields:		domain_id		- domain identifier
+		path_prefix		- absolute pathname which points to
+					  a directory where domain's mailboxes
+					  are located
+		quota			- default quota for each mailbox
+		uid			- UID used to work on mailboxes
+		gid			- GID used to work on mailboxes
+		
+	+------------+---------------+--------+-------+-------+
+	| domain_id  | path_prefix   | quota  | uid   | gid   |
+	+------------+---------------+--------+-------+-------+
+	|          1 | /var/mail/ex  | 100000 | 15000 | 15000 | 
+	|          2 | /var/mail/pld | 555500 | 15001 | 15000 |
+	|          3 | /home/f0/mail |   8000 | 15002 | 15000 |
+	+------------+---------------+--------+-------+-------+
+
+table:		domain_info
+
+purpose:	associates domain_id with additional informations
+
+fields:		domain_id		- domain identifier
+		registrant_id		- registrant identifier
+		nic_handle		- NIC handle
+		owner_id		- domain's owner identifier
+		expires			- domain's expiration date
+
+	+------------+---------------+------------+----------+---------+
+	| domain_id  | registrant_id | nic_handle | owner_id | expires |
+	+------------+---------------+------------+----------+---------+
+
+	(we don't need to say anything more about this table indeed)
+
+table:		users
+
+purpose:	associates users' identifiers with domains' identifiers
+		and infers the credentials for various virtual mailboxes
+
+fields:		username		- user's login name
+		domain_id		- domain identifier
+		cryptpw			- crypted password
+		plainpw			- plaintext password
+		quota			- user's mailbox quota
+					  (will override quota value set for
+					  the whole virtual domain)
+		path			- relative pathname for mailbox
+					  (will be appended to the path_prefix
+					  from domain_auth table to specify
+					  user's mailbox location)
+
+	+------------+-----------+----------+-----------+-------+------------+
+	| username   | domain_id | cryptpw  | plainpw   | quota | path       |
+	+------------+-----------+----------+-----------+-------+------------+
+	| foobar     |         1 | $1$hlIeE | dupa.8    | NULL  | f/o/foobar |
+	| breeder    |         2 | $1$TWsdf | ziarno128 | 77777 | brd        |
+	+------------+-----------+----------+-----------+-------+------------+
+
+    (you can add a realname column here, it doesn't fit to my terminal window:)
+
+--------------------- cut here
+
+# Create the database called vmail.
+
+CREATE database vmail;
+
+# Create an example MySQL user, which can read, write and delete data
+# from vmail database. Username: vuser Password: secret_password
+
+GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
+	     TO vuser@localhost
+	     IDENTIFIED BY 'secret_password';
+
+FLUSH PRIVILEGES;
+
+# Create the tables.
+
+use vmail;
+
+CREATE TABLE domain_names (
+	domain_id	      int(10) unsigned NOT NULL,
+	domain_name	      char(255) DEFAULT '' NOT NULL,
+	KEY domain_name (domain_name(255))
+	);
+
+CREATE TABLE domain_auth (
+	domain_id	      int(10) unsigned DEFAULT 1 NOT NULL,
+	uid		      int(10) unsigned DEFAULT '15000' NOT NULL,
+	gid		      int(10) unsigned DEFAULT '15000' NOT NULL,
+	path_prefix	      char(255) DEFAULT '' NOT NULL,
+	quota                 char(255) DEFAULT '20000000' NOT NULL,
+	KEY domain_id (domain_id)
+	);
+
+CREATE TABLE users (
+        username	      char(128) DEFAULT '' NOT NULL,
+	domain_id	      int(10) unsigned DEFAULT 1 NOT NULL,
+        cryptpw               char(128) DEFAULT '' NOT NULL,
+	plainpw		      char(128) DEFAULT '' NOT NULL,
+        name                  char(128) DEFAULT '' NOT NULL,
+        quota                 char(255),
+	path		      char(255) DEFAULT '' NOT NULL,
+        KEY username (username(128))
+	);
+	
+# Create an example virtual domain entry
+# id	: 1
+# name	: exampledom.com
+# uid	: 15000
+# gid	: 15000
+# path	: /var/mail/example
+# quota	: 20 Megs per mailbox
+
+INSERT INTO domain_names VALUES (1, 'exampledom.com');
+INSERT INTO domain_auth VALUES (1, '15000', '15000', '/var/mail/example',
+				'20000000');
+
+# Create an example virtual user entry
+# username	: siefca
+# domain id	: 1 (points to exampledom.com)
+# cryptpw	: $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
+# clearpw	: dupa.8
+# name		: Pawel Wilk
+# quota		: NULL (we want it to be fetched from domain_auth table)
+# mailbox path	: s/i/siefca
+
+INSERT INTO users VALUES ('siefca', 1, '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
+			  'dupa.8', 'Pawel Wilk', NULL, 's/i/siefca');
+
+--------------------- cut here
+
+Ok, we've done what we need. Don't forget to create system user with
+UID and GID set to 15000, and a directory containing mailboxes (in
+this case: /var/mail/example) owned by system user I've mentioned
+above.  There is also necessary to create Maildir folder structure for
+our user inside the virtual domain directory - you can configure your
+MTA agent to do such thing when first message arrive or use
+maildirmake tool, which comes with Courier-IMAP.
+
+
+4.2.2 authdaemon configuration
+
+DEFAULT_DOMAIN		exampledom.com
+
+MYSQL_SELECT_CLAUSE SELECT					\
+ users.username,						\
+ users.cryptpw,							\
+ users.plainpw,							\
+ domain_auth.uid,						\
+ domain_auth.gid,						\
+ CONCAT_WS('/',domain_auth.path_prefix,users.path),	 	\
+ '',								\
+ IFNULL(users.quota, domain_auth.quota),			\
+ users.name					 		\
+ FROM users, domain_names, domain_auth 				\
+ WHERE domain_names.domain_name='$(domain)' 			\
+  AND users.username='$(local_part)' 				\
+  AND domain_names.domain_id=users.domain_id 			\
+  AND domain_names.domain_id=domain_auth.domain_id
+
+
+.
+.
+.
+.
+.
+.
+
+/////////////////////////// PART II - Developer Notes /////////////////////////
+
 		    *-----------------------
 		     1 Modifications overview
 		    *-----------------------
 
-Modified files:	authmysqllib.c authmysqlrc
+Modified files:	authmysqllib.c authmysql.c authmysql.h authmysqlrc
 
 Each modified set of instructions is marked by my e-mail address:
 siefca@pld.org.pl
 
-Changes in the current source code are related to:
+Changes in the source code are related to:
 
-- sections where the queries are constructed
+- sections where the queries are constructed [authmysqllib.c]
   (including memory allocation for the buffers)
 
 	when MYSQL_SELECT_CLAUSE or MYSQL_CHPASS_CLAUSE is
@@ -95,17 +868,29 @@
 	passing over current memory allocation and query construction
 	subroutines
 	  
-- section where the configuration file is read
+- section where the configuration file is read [authmysqllib.c]
 
 	i've had to modify read_env() function to allow line breaks
-	- now each sequence of the backslash as a first character and
+	-- now each sequence of the backslash as a first character and
 	newline as the second is replaced by two whitespaces while
 	putting into the buffer
 
-- sections where the query is constructed
+	i've also added USER_DOMAIN_SEPARATORS configuration option --
+	it is used by get_localpart(), get_domain() and get_username()
+	functions, which are described below
+
+- sections where the query is constructed [authmysqllib.c]
 
 	selection is made, depending on configuration variables which
-	are set or not - if own query is used 
+	are set or not -- if own query is used 
+
+- sections where the user is authenticated against the authinfo [authmysql.c]
+
+	i've detached a part of code responsible for authentication
+	against crypted and plain password -- now it is in stub
+	function called auth_mysql_checkpassword() -- due to obtain
+	more clean code in auth_mysql_login() and
+	auth_mysql_changepw() around trigger calling functions
 
 
 
@@ -123,14 +908,20 @@
 
 These definitions allows to change substitution marks in an easy way.
 SV_BEGIN_MARK refers to sequence of characters treated as a prefix of
-each substitution variable and SV_END_MARK refers to string which is
-a closing suffix. If the expected substitution variable is called
+each substitution variable and SV_END_MARK refers to string which is a
+closing suffix. If the expected substitution variable is called
 'local_part' (without apostrophes) then '$(local_part)' is a valid
-string representation for SV_BEGIN_MARK set to "$(" and SV_END_MARK to ")".
-MAX_SUBSTITUTION_LEN defines maximal length of a substitution variable's
-identifier (name).
+string representation for SV_BEGIN_MARK set to "$(" and SV_END_MARK to
+")".  MAX_SUBSTITUTION_LEN defines maximal length of a substitution
+variable's identifier (name).
+
+The last two definitions (SV_BEGIN_LEN and SV_END_LEN) are just for
+code simplification.
+
+#define         DEF_SEPARATORS_SET      "@%"
 
-The last two definitions are just for code simplification.
+The DEF_SEPARATORS_SET directive defines the set of characters, which
+are treated as separators when splitting local part from the domain.
 
 
 
@@ -152,10 +943,10 @@
 	size_t value_length;
 	} ;
 
-This structure holds information needed by parsing routines.
-Using var_data array you may specify a set of string substitutions
-which should be done while parsing a query. Last element in array
-should have all fields set to zero (null). 
+This structure holds information needed by parsing routines.  Using
+var_data array you may specify a set of string substitutions which
+should be done while parsing a query. Last element in array should
+have all fields set to zero (null).
 
 name field	- should contain substituted variable name
 value 		- should contain string which replaces it
@@ -164,9 +955,9 @@
 
 
 explanation: size is used to increase speed of calculation proccess
-	     value_length is used to cache length of a value during the
-	     parsing subroutines - it helps when substitution variable
-	     occures more than once within the query
+	     value_length is used to cache length of a value during
+	     the parsing subroutines - it helps when substitution
+	     variable occures more than once within the query
  
 Example:
 
@@ -177,18 +968,19 @@
 };
 
 In this example we've declared that $(some) in the query should be
-replaced by 'replacement' text, and replacement for $(anotha) will
-be defined in the code before passing on the array pointer to
-the paring function.
+replaced by 'replacement' text, and replacement for $(anotha) will be
+set later in the code, before passing on the array pointer to the
+general parsing function.
 
 
 3.2 typedef size_t (*parsefunc)
 
 typedef int (*parsefunc)(const char *, size_t, void *);
 
-This type definition refers to the function pointer, which is used
-to pass plugin functions into the core parsing subroutine. This definition
-is included to simplify the declaration of the parse_core() function.
+This type definition refers to the function pointer, which is used to
+pass plugin functions into the core parsing subroutine. This
+definition is included to simplify the declaration of the parse_core()
+function.
 
 
 
@@ -230,6 +1022,10 @@
 	structure of var_data type, which contains variable definition
 	of a given name. It returns NULL on error or failure.
 
+FILES
+
+	authlib/authmysqllib.c
+
 
 4.2 parse_core
 
@@ -285,6 +1081,11 @@
 
 	This function returns -1 if an error has occured and 0 if
 	everything went good.
+
+FILES
+
+	authlib/authmysqllib.c
+
 	
 4.3 ParsePlugin_counter
 
@@ -314,6 +1115,11 @@
 	This function returns the variable size or -1 if an error
 	has occured, 0 if everything went good.
 
+FILES
+
+	authlib/authmysqllib.c
+
+
 4.4 ParsePlugin_builder
 
 NAME
@@ -333,7 +1139,7 @@
 	type pointer and refers to the (char *) pointer variable.
 	After each call it shifts the value of pointer variable (char *)
 	incrementing it by len bytes. Be careful when using this function
-	- its changes the given pointer value. Always operate on an
+	- it changes the given pointer value. Always operate on an
 	additional pointer type variable when passing it as the third
 	argument.
 
@@ -342,6 +1148,10 @@
 	This function returns the variable size or -1 if an error
 	has occured, 0 if everything went good.
 
+FILES
+
+	authlib/authmysqllib.c
+
 4.5 parse_string
 
 NAME
@@ -353,7 +1163,7 @@
 
 DESCRIPTION
 
-	This function parses the string pointed with source according to the
+	This function parses the string pointed to by source according to the
 	replacement instructions set in var_data array, which is passed with
 	its pointer vdt. It produces changed string located in newly allocated
 	memory area.
@@ -377,6 +1187,10 @@
 	Function returns pointer to the result buffer or NULL
 	if an error has occured.
 
+FILES
+
+	authlib/authmysqllib.c
+
 WARNINGS
 
 	This function allocates some amount of memory using standard
@@ -405,6 +1219,10 @@
 	It returns a pointer to the static buffer which contains
 	validated password string or NULL if an error has occured.
 
+FILES
+
+	authlib/authmysqllib.c
+
 
 4.7 get_localpart
 
@@ -414,20 +1232,28 @@
 
 SYNOPSIS
 
-	static const char *get_localpart (const char *username);
+	static const char *get_localpart (const char *username,
+					  const char *separators);
 
 DESCRIPTION
 
 	This function detaches local part of an e-mail address
 	from string pointed with username and puts it to the
 	buffer of the fixed length. All necessary cleaning is
-	made on the result string.
+	made on the result string. String pointed with separators
+	refers to a set of characters, which are treated as
+	separation signs between local part and a domain.
 
 RETURN VALUE
 
 	Pointer to the static buffer containing local part or
 	NULL if there was some error.
 
+FILES
+
+	authlib/authmysqllib.c
+
+
 
 4.8 get_domain
 
@@ -438,24 +1264,67 @@
 SYNOPSIS
 
 	static const char *get_domain (const char *username, 
-				       const char *defdomain);
+				       const char *defdomain,
+				       const char *separators);
 
 DESCRIPTION
 
         This function detaches domain part of an e-mail address
 	from string pointed with username and puts it to the
 	buffer of the fixed length. All necessary cleaning is
-	made on the result string. If function cannot find domain
-	part in the string the string pointed by defdomain is
-	used instead.
+	made on the result string. If the function cannot find a domain
+	part in the string then the string pointed to by defdomain is
+	used instead. If this function cannot find a domain part
+	as well as it cannot obtain the default domain (it's empty string
+	or the defdomain pointer is NULL) the returned result string is an
+	empty string. The string pointed with separators refers to a set
+	of characters, which are treated as separation signs between local
+	part and a domain.
 
 RETURN VALUE
 
         Pointer to the static buffer containing domain name or
 	NULL if there was some error.
 
+FILES
+
+	authlib/authmysqllib.c
 
-4.9 parse_select_clause
+
+4.9 get_username
+
+NAME
+
+	get_username
+
+SYNOPSIS
+
+	static const char *get_username (const char *username,
+					 const char *domainname);
+
+DESCRIPTION
+
+	This function concatenates the localpart with a domain name
+	using the @ symbol. If the domain is empty or NULL the result
+	comes without binding symbol.
+
+RETURN VALUE
+
+        Pointer to the static buffer containing output string or
+        NULL if there was some error.
+
+FILES
+
+	authlib/authmysqllib.c
+
+WARNINGS
+
+	This function does not any string cleaning, nor default domain
+	checking. It is designed to work on results of get_localpart() and
+	get_domain().
+
+
+4.10 parse_select_clause
 
 NAME
 
@@ -465,23 +1334,34 @@
 
 	static char *parse_select_clause (const char *clause,
 					  const char *username,
-                                  	  const char *defdomain);
+                                  	  const char *defdomain
+        	                          const char *separators_set);
 
 DESCRIPTION
 
 	This function is a simple wrapper to the parse_string()
 	function. It parses a query pointed by caluse. username
-	and defdomain strings are used to replace corresponding
-	substitution strings if present in the query: $(local_part)
-	and $(domain).
+	and defdomain strings are used to create corresponding
+	substitution strings if present in the query: $(local_part),
+	$(domain), and $(username). Note, that username parameter
+	may contain 'user@domain' form here, so the call to
+	get_localpart() and get_domain() function will split it
+	into two parts, then calling get_username() function will join
+	it again using the @ symbol. This trick is wanted as long as
+	we'd like to have possibility to split the local part from the
+	domain by using dynamic symbols set. The separators_set is
+	passed to get_localpart() and get_domain() invocations.
 	
-
 RETURN VALUE
 
 	Same as parse_string().
 
+FILES
+
+	authlib/authmysqllib.c
 
-4.10 parse_chpass_clause
+
+4.11 parse_chpass_clause
 
 NAME
 
@@ -492,6 +1372,7 @@
         static char *parse_chpass_clause (const char *clause,
                                           const char *username,
                                           const char *defdomain,
+	                                  const char *separators_set,
 					  const char *newpass,
 					  const char *newpass_crypt);
 								    
@@ -502,12 +1383,115 @@
 	defdomain, newpass and newpass_crypt strings are used to
 	replace corresponding substitution strings if present in
 	the query: $(local_part), $(domain), $(newpass),
-	$(newpass_crypt).
+	$(newpass_crypt). The separators_set is passed to
+	get_localpart() and get_domain() functions as described in the
+	entry for parse_select_clause().
 
 RETURN VALUE
 
 	Same as parse_string().
 
+FILES
+
+	authlib/authmysqllib.c
+
+
+4.12 auth_mysql_on_trigger
+
+NAME
+
+	auth_mysql_on_trigger
+
+SYNOPSIS
+
+	int auth_mysql_on_trigger (const char *clause_name,
+				   const char *username);
+
+DESCRIPTION
+
+	This function is responsible for calling out the MySQL queries
+	depending on which authentication state was reached.
+
+	The clause_name should contain the name of a clause, which can
+	be found in the configuration file, and the username is simply
+	the string used as username (including the domain if entered).
+
+	This function reads DEFAULT_DOMAIN and USER_DOMAIN_SEPARATORS
+	from the configuration file using read_env(), then it uses
+	parse_select_clause() to parse the query obtained using
+	read_env(clause_name), and then it calls querying subroutines
+	to perform the action.
+
+RETURN VALUE
+
+	This function returns 0 on success and -1 on failure. The
+	query results are simply discarded. If a trigger's clause is
+	not defined in the configuration file the 1 is returned and
+	function silently ends its work.
+
+FILES
+
+	authlib/authmysqllib.c
+
+
+4.13 auth_mysql_on_pass
+
+NAME
+
+	auth_mysql_on_pass
+
+SYNOPSIS
+
+	static int auth_mysql_on_pass(const char *clause,
+				      struct authmysqluserinfo *authinfo);
+
+DESCRIPTION
+
+	This function is responsible for invoking trigger MySQL
+	clauses whenever user is authenticated or not.
+	This is a stub function, which calls auth_mysql_on_trigger().
+	Firstly, it does a simple checks in authinfo structure --
+	it looks for a valid username field. If username is not set
+	or it's empty the fuction does nothing. This behavior follows
+	the need, that if there wasn't any valid username then we
+	shouldn't touch the database.
+
+RETURN VALUE
+
+	It returns 0 in case everything went fine, -1 if there was some
+	error.
+
+FILES
+
+	authlib/authmysql.c
+
+
+4.14 auth_mysql_checkpassword
+
+NAME
+	auth_mysql_checkpassword
+
+SYNOPSIS
+
+	static int auth_mysql_checkpassword(struct authmysqluserinfo *authinfo,                                                                                                          const char *pass);
+	
+DESCRIPTION
+
+	This function is a wrapper, which checks user's entered
+	password against one found in a database. Function tries to
+	authenticate user against his crypted password and if it's
+	impossible it tries the plain form -- by impossible we mean
+	the authinfo->cryptpw set to NULL.
+
+RETURN VALUE
+
+	Function returns 0 if the password was correct, -1 if user
+	applied bad password of the username wasn't found.
+
+FILES
+
+	authlib/authmysql.c
+
 
 
 
@@ -516,15 +1500,10 @@
 		     5 Ideas and TODO
 		    *------------------------
 
-- solve problem with fixed buffer length of local part and the domain part
-  strings after split						(problem?)
 - allow admin to set a group name instead of numerical group id
 - allow admin to set a username instead of numerical user id
-
-- add clauses:
-
-  - MYSQL_PRESELECT_CLAUSE (query which comes before MYSQL_SELECT_CLAUSE)
-  - MYSQL_POSTSELECT_CLAUSE (query which comes after MYSQL_SELECT_CLAUSE)
+- put the parsing routines into separate files to make possible of sharing it
+  by more authentication modules
 
 
 
@@ -534,10 +1513,20 @@
 		     6 Thanks
 		    *------------------------
 
-At the beginning this patch was messy indeed. :> I would like to thank
-Sam Varshavchik for pointing me a lot how to make it more fast and solid.
-I would also thank Philip Hazel, Chris Lightfoot and Mike Bremford which
-by their software capabilities inspired me to write it.
+At the beginning the patch was messy indeed. :> I would like to thank:
 
----------------------------------------------------------------------------
+Sam Varshavchik
+  for pointing me a lot, how to make it more fast and solid
+	    
+Philip Hazel, Chris Lightfoot, Mike Bremford
+  which by their software's capabilities inspired me to write it
+		
+Oliver Oblasnik
+  which remainded me to make the documentation more friendly for
+  those who are not programmers and just want to use it
+  
+Jacek Surazski
+  for reviewing this document just before it was published
 
+---------------------------------------------------------------------------
+				  Any comments and suggestions are welcome.
diff -ur courier-imap-1.5.3-orig/authlib/authmysql.c courier-imap-1.5.3/authlib/authmysql.c
--- courier-imap-1.5.3-orig/authlib/authmysql.c	Sun Jun 24 01:42:05 2001
+++ courier-imap-1.5.3/authlib/authmysql.c	Sun Oct 13 23:12:06 2002
@@ -19,7 +19,47 @@
 #include	"authmysql.h"
 #include	"authstaticlist.h"
 
-static const char rcsid[]="$Id: authmysql.c,v 1.11 2001/06/21 01:44:04 mrsam Exp $";
+static const char rcsid[]="$Id: authmysql.c,v 1.12 2002/08/19 15:30:45 mrsam Exp $";
+
+/* siefca@pld.org.pl */
+static int auth_mysql_on_pass(const char *clause, struct authmysqluserinfo *authinfo)
+{
+    	if (authinfo->username && *(authinfo->username)!='\0') /* do it if user was found */
+    	{
+    		if (auth_mysql_on_trigger(clause, authinfo->username))
+		{
+	    		return (-1); /* MySQL error or something critical.. */
+		}
+    	}
+  
+    	return (0);
+}
+
+/* siefca@pld.org.pl */
+static int auth_mysql_checkpassword(struct authmysqluserinfo *authinfo,
+				    const char *pass)
+{
+	if (authinfo->cryptpw)
+	{
+		if (authcheckpassword(pass,authinfo->cryptpw))
+		{
+	    		return (-1); /* User/Password not found. */
+		}
+	}
+    	else if (authinfo->clearpw)
+	{
+		if (strcmp(pass, authinfo->clearpw))
+		{
+			return (-1);
+		}
+	}
+	else
+	{
+		return (-1);
+	}
+    
+	return (0);
+}
 
 static char *auth_mysql_login(const char *service, char *authdata,
 	int issession,
@@ -46,26 +86,23 @@
 		return (0);
 	}
 
-	if (authinfo->cryptpw)
+	/* siefca@pld.org.pl */
+	if (auth_mysql_checkpassword(authinfo,pass))
 	{
-		if (authcheckpassword(pass,authinfo->cryptpw))
-		{
+		if (auth_mysql_on_pass("ON_PASS_FAIL_CLAUSE", authinfo))
+			errno=EACCES;
+	    	else
 			errno=EPERM;
-			return (0);	/* User/Password not found. */
-		}
-	}
-	else if (authinfo->clearpw)
-	{
-		if (strcmp(pass, authinfo->clearpw))
-		{
-			errno=EPERM;
-			return (0);
-		}
+	    
+	    	return(0);
 	}
 	else
 	{
-		errno=EPERM;
-		return (0);		/* Username not found */
+		if (auth_mysql_on_pass("ON_PASS_OK_CLAUSE", authinfo))
+		{
+			errno=EACCES;
+			return(0);
+	    	}
 	}
 
 	if (callback_func == 0)
@@ -149,26 +186,23 @@
 		return (-1);
 	}
 
-	if (authinfo->cryptpw)
+	/* siefca@pld.org.pl */
+	if (auth_mysql_checkpassword(authinfo, pass))
 	{
-		if (authcheckpassword(pass,authinfo->cryptpw))
-		{
-			errno=EPERM;
-			return (-1);	/* User/Password not found. */
-		}
-	}
-	else if (authinfo->clearpw)
-	{
-		if (strcmp(pass, authinfo->clearpw))
-		{
+		if (auth_mysql_on_pass("ON_PASS_FAIL_CLAUSE", authinfo))
+			errno=EACCES;
+		else
 			errno=EPERM;
-			return (-1);
-		}
+
+		return(-1);
 	}
 	else
 	{
-		errno=EPERM;
-		return (-1);
+		if (auth_mysql_on_pass("ON_PASS_OK_CLAUSE", authinfo))
+	    	{
+			errno=EACCES;
+			return(-1);
+	    	}
 	}
 
 	if (auth_mysql_setpass(user, newpass))
@@ -176,6 +210,14 @@
 		errno=EPERM;
 		return (-1);
 	}
+	
+	/* siefca@pld.org.pl */
+	if (auth_mysql_on_pass("ON_PASS_CHANGE_CLAUSE", authinfo))
+	{
+	    errno=EACCES;
+	    return (-1);
+	}
+
 	return (0);
 }
 
@@ -314,7 +356,7 @@
 #endif
 
 char *auth_mysql(const char *service, const char *authtype, char *authdata,
-		int issession,
+		 int issession,
 	void (*callback_func)(struct authinfo *, void *), void *callback_arg)
 {
 	if (strcmp(authtype, AUTHTYPE_LOGIN) == 0)
diff -ur courier-imap-1.5.3-orig/authlib/authmysql.h courier-imap-1.5.3/authlib/authmysql.h
--- courier-imap-1.5.3-orig/authlib/authmysql.h	Mon Aug  6 05:12:39 2001
+++ courier-imap-1.5.3/authlib/authmysql.h	Sat Sep 28 00:01:07 2002
@@ -21,6 +21,7 @@
 	} ;
 
 extern struct authmysqluserinfo *auth_mysql_getuserinfo(const char *);
+extern int auth_mysql_on_trigger (const char *clause_name, const char *username);
 extern void auth_mysql_cleanup();
 
 extern int auth_mysql_setpass(const char *, const char *);
diff -ur courier-imap-1.5.3-orig/authlib/authmysqllib.c courier-imap-1.5.3/authlib/authmysqllib.c
--- courier-imap-1.5.3-orig/authlib/authmysqllib.c	Wed May 29 19:24:03 2002
+++ courier-imap-1.5.3/authlib/authmysqllib.c	Sun Oct 13 22:58:09 2002
@@ -23,6 +23,8 @@
 #define		SV_END_MARK		")"
 #define		SV_BEGIN_LEN		((sizeof(SV_BEGIN_MARK))-1)
 #define		SV_END_LEN		((sizeof(SV_END_MARK))-1)
+#define		DEF_SEPARATORS_SET	"@%"
+
 
 static const char rcsid[]="$Id: authmysqllib.c,v 1.19 2002/05/26 16:48:47 mrsam Exp $";
 
@@ -268,7 +270,7 @@
 			 SV_BEGIN_MARK
 			 "%.*s"
 			 SV_END_MARK
-			 "\n", len, begin);
+			 "\n", (int) len, begin);
 	
 	return NULL;
 }
@@ -364,14 +366,14 @@
 		t_size	= t_end-t_begin+1;/* text field length		    */
 
 		/* work on text */
-		if ( (outfn (t_begin, t_size, result)) == -1 )
+		if ( (outfn (t_begin, t_size, result)))
 			return -1;
 		
 		/* work on variable */
 		v_ptr = get_variable (v_begin, v_size, vdt);
 		if (!v_ptr) return -1;
 		
-		if ( (outfn (v_ptr->value, v_ptr->value_length, result)) == -1 )
+		if ( (outfn (v_ptr->value, v_ptr->value_length, result)))
 			return -1;
 		
 		q = e + 1;
@@ -379,7 +381,7 @@
 
 	/* work on last part of text if any */
 	if (*q != '\0')
-		if ( (outfn (q, strlen(q), result)) == -1 )
+		if ( (outfn (q, strlen(q), result)))
 			return -1;
 
 	return 0;
@@ -426,21 +428,45 @@
 		return NULL;
 	}	
 	*pass_buf = '\0';
-	
+
 	return output_buf;
 }
 
 /* siefca@pld.org.pl */
-static const char *get_localpart (const char *username)
+static const char *get_username (const char *username, const char *domainname)
+{
+size_t u_len;
+char *p;
+static char	username_buf[400];
+
+	if (!username || *username == '\0') return NULL;
+	u_len=strlen(username);
+	if (( u_len + (domainname ? strlen(domainname) : 0)
+	    ) > 397) return NULL;
+
+	strcpy (username_buf, username);
+	if (domainname && *domainname != '\0')
+	  {
+	    p = username_buf + u_len;
+	    *p='@'; p++;
+	    strcpy(p, domainname);
+	  }
+	
+	return (username_buf);
+}
+
+/* siefca@pld.org.pl */
+static const char *get_localpart (const char *username, const char *separators)
 {
 size_t		lbuf	= 0;
 const char	*l_end, *p;
 char		*q;
 static char	localpart_buf[130];
 	
-	if (!username || *username == '\0')	return NULL;
+	if (!username || *username == '\0' ||
+	    !separators || *separators == '\0')	return NULL;
 	
-	p = strchr(username,'@');
+	p = strpbrk (username, separators);
 	if (p)
 	{
 		if ((p-username) > 128)
@@ -469,21 +495,27 @@
 }
 
 /* siefca@pld.org.pl */
-static const char *get_domain (const char *username, const char *defdomain)
+static const char *get_domain (const char *username, const char *defdomain,
+			       const char *separators)
 {
 static char	domain_buf[260];
 const char	*p;
 char		*q;
 	
-	if (!username || *username == '\0')	return NULL;
-	p = strchr(username,'@');
+	if (!username || *username == '\0' ||
+	    !separators || *separators == '\0')	return NULL;
+	
+	p = strpbrk (username, separators);
 	
 	if (!p || *(p+1) == '\0')
 	{
-		if (defdomain && *defdomain)
+		if (defdomain && *defdomain != '\0')
 			return defdomain;
 		else
-			return NULL;
+		  {
+		    *domain_buf = '\0';
+		    return domain_buf;
+		  }
 	}
 
 	p++;
@@ -531,20 +563,25 @@
 
 /* siefca@pld.org.pl */
 static char *parse_select_clause (const char *clause, const char *username,
-				  const char *defdomain)
+				  const char *defdomain,
+				  const char *separators_set)
 {
 static struct var_data vd[]={
 	    {"local_part",	NULL,	sizeof("local_part"),	0},
 	    {"domain",		NULL,	sizeof("domain"),	0},
+	    {"username",        NULL,   sizeof("username"),     0},
 	    {NULL,		NULL,	0,			0}};
 
-	if (clause == NULL || *clause == '\0' ||
-	    !username || *username == '\0')
+	if (!clause || !username || !separators_set ||
+	    *clause == '\0' || *username == '\0' ||
+	    *separators_set == '\0')
 		return NULL;
-	
-	vd[0].value	= get_localpart (username);
-	vd[1].value	= get_domain (username, defdomain);
-	if (!vd[0].value || !vd[1].value)
+
+	vd[0].value	= get_localpart (username, separators_set);
+	vd[1].value	= get_domain (username, defdomain, separators_set);
+	vd[2].value     = get_username (vd[0].value, vd[1].value);
+
+	if (!vd[0].value || !vd[1].value || !vd[2].value)
 		return NULL;
 	
 	return (parse_string (clause, vd));
@@ -552,12 +589,15 @@
 
 /* siefca@pld.org.pl */
 static char *parse_chpass_clause (const char *clause, const char *username,
-				  const char *defdomain, const char *newpass,
+				  const char *defdomain,
+				  const char *separators_set,
+				  const char *newpass,
 				  const char *newpass_crypt)
 {
 static struct var_data vd[]={
 	    {"local_part",	NULL,	sizeof("local_part"),		0},
 	    {"domain",		NULL,	sizeof("domain"),		0},
+	    {"username",	NULL,	sizeof("username"),		0},
 	    {"newpass",		NULL, 	sizeof("newpass"),		0},
 	    {"newpass_crypt",	NULL,	sizeof("newpass_crypt"),	0},
 	    {NULL,		NULL,	0,				0}};
@@ -565,19 +605,81 @@
 	if (clause == NULL || *clause == '\0'		||
 	    !username || *username == '\0'		||
 	    !newpass || *newpass == '\0'		||
+	    !separators_set || *separators_set == '\0'	||
 	    !newpass_crypt || *newpass_crypt == '\0')	return NULL;
 
-	vd[0].value	= get_localpart (username);
-	vd[1].value	= get_domain (username, defdomain);
-	vd[2].value	= validate_password (newpass);
-	vd[3].value	= validate_password (newpass_crypt);
+	vd[0].value	= get_localpart (username, separators_set);
+	vd[1].value	= get_domain (username, defdomain, separators_set);
+	vd[3].value	= get_username (vd[0].value, vd[1].value);
+	vd[4].value	= validate_password (newpass);
+	vd[5].value	= validate_password (newpass_crypt);
 	
 	if (!vd[0].value || !vd[1].value ||
-	    !vd[2].value || !vd[3].value)	return NULL;
+	    !vd[2].value || !vd[3].value ||
+	    !vd[4].value || !vd[5].value)	return NULL;
 
 	return (parse_string (clause, vd));
 }
 
+/* siefca@pld.org.pl */
+int auth_mysql_on_trigger (const char *clause_name, const char *username)
+{
+char		*querybuf	=NULL;
+const char	*separators_set	=NULL,
+		*defdomain	=NULL,
+		*on_clause	=NULL;
+MYSQL_RES	*result;
+
+	if (!clause_name || *clause_name == '\0')
+	  return (-1);
+	
+	on_clause = read_env (clause_name);
+	if (!on_clause || *on_clause == '\0')
+	  return (0); /* ok! not in use */
+
+	defdomain = read_env ("DEFAULT_DOMAIN");	
+	separators_set = read_env ("USER_DOMAIN_SEPARATORS");
+	if (!defdomain) defdomain = "";
+	if (!separators_set || *separators_set == '\0')
+		separators_set = DEF_SEPARATORS_SET;
+	
+	querybuf = parse_select_clause (on_clause,
+					username,
+					defdomain,
+					separators_set);
+	
+	if (!querybuf)	return (-1);
+
+	if (mysql_query (mysql, querybuf))
+	{
+		/* <o.blasnik@nextra.de> */
+
+		auth_mysql_cleanup();
+
+		if (do_connect())
+		{
+			free(querybuf);
+			return (-1);
+		}
+
+		if (mysql_query (mysql, querybuf))
+		{
+			free(querybuf);
+			auth_mysql_cleanup();
+			/* Server went down, that's OK,
+			** try again next time.
+			*/
+			return (-1);
+		}
+	}
+	free(querybuf);
+	result = mysql_store_result(mysql);
+	if (result) mysql_free_result(result);
+	
+	return (0);
+}
+
+
 struct authmysqluserinfo *auth_mysql_getuserinfo(const char *username)
 {
 const char *user_table	=NULL;
@@ -596,6 +698,7 @@
 	    *gid_field		=NULL,
 	    *quota_field	=NULL,
 	    *where_clause	=NULL,
+	    *separators_set	=NULL,
 	    *select_clause	=NULL; /* siefca@pld.org.pl */
 
 static const char query[]=
@@ -704,7 +807,15 @@
 	else
 	{
 		/* siefca@pld.org.pl */
-		querybuf=parse_select_clause (select_clause, username, defdomain);
+		separators_set = read_env ("USER_DOMAIN_SEPARATORS");
+
+		if (!separators_set || *separators_set == '\0')
+			separators_set = DEF_SEPARATORS_SET;
+
+		querybuf = parse_select_clause (select_clause,
+						username,
+						defdomain,
+						separators_set);
 		if (!querybuf) return 0;
 	}
 
@@ -788,6 +899,7 @@
 		    *where_clause	=NULL,
 		    *user_table		=NULL,
 		    *login_field	=NULL,
+		    *separators_set     =NULL,	
 		    *chpass_clause	=NULL; /* siefca@pld.org.pl */
 
 	if (!mysql)
@@ -837,13 +949,18 @@
 	}
 	else
 	{
+                separators_set = read_env ("USER_DOMAIN_SEPARATORS");
+		
+		if (!separators_set || *separators_set == '\0')
+			separators_set = DEF_SEPARATORS_SET;
+		
 		sql_buf=parse_chpass_clause(chpass_clause,
 					    user,
 					    defdomain,
+					    separators_set,
 					    pass,
 					    newpass_crypt_ptr);
 	}
-	
 
 	if (!sql_buf)
 	{
diff -ur courier-imap-1.5.3-orig/authlib/authmysqlrc courier-imap-1.5.3/authlib/authmysqlrc
--- courier-imap-1.5.3-orig/authlib/authmysqlrc	Thu Apr  4 06:36:29 2002
+++ courier-imap-1.5.3/authlib/authmysqlrc	Mon Oct 14 00:02:59 2002
@@ -1,4 +1,4 @@
-##VERSION: $Id: authmysqlrc,v 1.10 2002/04/02 23:41:41 mrsam Exp $
+##VERSION: $Id: authmysqlrc,v 1.9 2002/01/08 05:01:22 mrsam Exp $
 #
 # Copyright 2000 Double Precision, Inc.  See COPYING for
 # distribution information.
@@ -141,65 +141,99 @@
 #
 # MYSQL_WHERE_CLAUSE	server='mailhost.example.com'
 
-##NAME: MYSQL_SELECT_CLAUSE:0
-#
-# (EXPERIMENTAL)
-# This is optional, MYSQL_SELECT_CLAUSE can be set when you have a database,
-# which is structuraly different from proposed. The fixed string will
-# be used to do a SELECT operation on database, which should return fields
-# in order specified bellow:
+##NAME: USER_DOMAIN_SEPARATORS:0
 #
-# username, cryptpw, uid, gid, clearpw, home, maildir, quota, fullname
+# This is optional. Using this option you can set the set of characters
+# which are treated as separators when splitting entered username into the
+# local part and the domain name. If it's not set the defaults @% are used,
+# so the user can authenticate using user@domain or user%domain form.
+# See README.authmysql.myownquery for more information
 #
-# Enabling this option causes ignorance of any other field-related
-# options, excluding default domain.
+# USER_DOMAIN_SEPARATORS	@%+
+
+##NAME: MYSQL_SELECT_CLAUSE:0
 #
-# There are two variables, which you can use. Substitution will be made
-# for them, so you can put entered username (local part) and domain name
-# in the right place of your query. These variables are:
-#	 	$(local_part) and $(domain)
+# This is optional, MYSQL_SELECT_CLAUSE can be set when you have a database,
+# which is structuraly different from proposed. You can type here your MySQL
+# query, which will be used to fetch user's credentials, and which should
+# return fields in order specified bellow:
+#
+# username, cryptpw, clearpw, uid, gid, home, maildir, quota, fullname
+#
+# Enabling this option causes ignorance of any other field-related options.
+#
+# There also are variables, which you can use. Substitution will be made
+# for them, so you can pass currently entered username and a domain name
+# up to the right place within your query. These variables are:
+# $(local_part) , $(domain) , $(username)
 #
 # If a $(domain) is empty (not given by the remote user) the default domain
-# name is used in its place.
-#
-# This example is a little bit modified adaptation of vmail-sql
-# database scheme:
-#
-# MYSQL_SELECT_CLAUSE	SELECT popbox.local_part,			\
-#			CONCAT('{MD5}', popbox.password_hash),		\
-#			popbox.clearpw,					\
-#			domain.uid,					\
-#			domain.gid,					\
-#			CONCAT(domain.path, '/', popbox.mbox_name),	\
-#			'',						\
-#			domain.quota,					\
-#			'',						\
-#			FROM popbox, domain				\
-#			WHERE popbox.local_part = '$(local_part)'	\
-#			AND popbox.domain_name = '$(domain)'		\
-#			AND popbox.domain_name = domain.domain_name
-#
+# name is used in its place. $(username) is a local part concatenated with
+# domain name using symbol defined in USER_DOMAIN_CONCAT or '@' if this option
+# is not set.
+# See README.authmysql.myownquery for more information
+#
+# MYSQL_SELECT_CLAUSE SELECT					\
+# users.username, users.cryptpw, users.clearpw,			\
+# domains.uid, domains.gid,					\
+# CONCAT_WS('/',domains.path_prefix,users.mailbox_path),	\
+# '', domains.quota, ''						\
+# FROM users, domains						\
+#  WHERE domains.domain_name='$(domain)'			\
+#    AND users.username='$(local_part)'				\
+#    AND domains.domain_name=users.domain_name
+
 ##NAME: MYSQL_CHPASS_CLAUSE:0
 #
-# (EXPERIMENTAL)
 # This is optional, MYSQL_CHPASS_CLAUSE can be set when you have a database,
-# which is structuraly different from proposed. The fixed string will
-# be used to do an UPDATE operation on database. In other words, it is
-# used, when changing password.
+# which is structuraly different from proposed. You can use it to set up
+# a MySQL query used to change user's password.
 #
 # There are four variables, which you can use. Substitution will be made
-# for them, so you can put entered username (local part) and domain name
-# in the right place of your query. There variables are:
-# 	$(local_part) , $(domain) , $(newpass) , $(newpass_crypt)
+# for them, so you can put the currently entered username and the domain name
+# in the right place of your query. These variables are:
+# $(local_part) , $(domain) , $(username) , $(newpass) , $(newpass_crypt)
 #
 # If a $(domain) is empty (not given by the remote user) the default domain
-# name is used in its place.
-# $(newpass) contains plain password
-# $(newpass_crypt) contains its crypted form
-#
-# MYSQL_CHPASS_CLAUSE	UPDATE	popbox					\
-#			SET	clearpw='$(newpass)',			\
-#				password_hash='$(newpass_crypt)'	\
-#			WHERE	local_part='$(local_part)'		\
-#			AND	domain_name='$(domain)'
-#
+# name is used in its place. $(newpass) contains plain password and
+# $(newpass_crypt) contains its crypted form.
+# See README.authmysql.myownquery for more information
+#
+# MYSQL_CHPASS_CLAUSE UPDATE users		\
+# SET clearpw='$(newpass)',			\
+#     cryptpw='$(newpass_crypt)'		\
+# WHERE username='$(local_part)'		\
+# AND domain_name='$(domain)'
+
+##NAME: ON_PASS_OK_CLAUSE:0
+#
+# This is optional, ON_PASS_OK_CLAUSE is a trigger -- the query
+# is performed each time user has successfuly logged in.
+# See README.authmysql.myownquery for more information
+#
+# ON_PASS_OK_CLAUSE		UPDATE users				\
+#				SET last_ok=CURRENT_TIMESTAMP		\
+#				WHERE username='$(local_part)'		\
+#				AND domain_name='$(domain)'
+
+##NAME: ON_PASS_FAIL_CLAUSE:0
+#
+# This is optional, ON_PASS_FAIL_CLAUSE is a trigger -- the query
+# is performed each time user has NOT logged in, cause of bad password.
+# See README.authmysql.myownquery for more information
+#
+# ON_PASS_FAIL_CLAUSE		UPDATE users				\
+#				SET last_fail=CURRENT_TIMESTAMP		\
+#				WHERE username='$(local_part)'		\
+#				AND domain_name='$(domain)'
+
+##NAME: ON_PASS_CHANGE_CLAUSE:0
+#
+# This is optional, ON_PASS_CHANGE_CLAUSE is a trigger -- the query
+# is performed each time user has successfuly changed his password.
+# See README.authmysql.myownquery for more information
+#
+# ON_PASS_CHANGE_CLAUSE		UPDATE users				\
+#				SET pw_change=CURRENT_TIMESTAMP		\
+#				WHERE username='$(local_part)'		\
+#				AND domain_name='$(domain)'
