--- /dev/null
+diff -ur courier-imap-1.5.3.20020921.orig/authlib/README.authmysql.myownquery courier-imap-1.5.3.20020921/authlib/README.authmysql.myownquery
+--- courier-imap-1.5.3.20020921.orig/authlib/README.authmysql.myownquery Tue Jan 8 06:01:22 2002
++++ courier-imap-1.5.3.20020921/authlib/README.authmysql.myownquery Sat Sep 28 01:19:55 2002
+@@ -2,13 +2,18 @@
+
+
+
+- Developer Notes for courier-imap-myownquery.patch
++ Developer Notes and Usage Instructions
++
++ of
++
++ courier-imap-authmysql-myownquery
+
+
+
++ document version: 1.30
++ patch for version: 1.5.3.20020921
++ author: Pawel Wilk <siefca@kernel.pl>
+
+- document version: 1.03
+- author: Pawel Wilk
+
+
+
+@@ -21,71 +26,749 @@
+
+
+
++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
++
++ 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
+
++ 5 Ideas and TODO
+
++ 6 Thanks
+
+-0 What's that?
+
+-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
++//////////////////////////////// PART I - Usage ///////////////////////////////
++
++ *-----------------------
++ 1 What's that?
++ *-----------------------
++
++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.
+
+-6 Thanks
++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.
+
+
+
+
++
++ *-----------------------
++ 2 When will I need it?
+ *-----------------------
+- 0 What's that?
++
++ 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_CONCAT (optional)
++
++ The USER_DOMAIN_CONCAT defines a character(s) used to
++ concatenate a local part and a domain while parsing
++ the $(username) substitution variable (see section 3.3
++ for more info). If it's not defined the @ sign is assumed.
++
++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 when he wants to be
++ authenticated.
++
++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.
++
++MYSQL_ONSUCCESS_CLAUSE (optional)
++MYSQL_ONFAIL_CLAUSE (optional)
++
++ These are used to do a MySQL query whether user has passed
++ the authentication verification (MYSQL_ONSUCCESS_CLAUSE)
++ or there was the authentication failure (MYSQL_ONFAIL_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
++ which is 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)
++
++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, MYSQL_ONFAIL_CLAUSE, MYSQL_ONSUCCESS_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
++ defined by USER_DOMAIN_CONCAT - 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
++
++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
++ defined by USER_DOMAIN_CONCAT - 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 two triggers which you may use. First is called
++MYSQL_ONSUCCESS_CLAUSE and it is performed when the authentication succeedes.
++The second is called MYSQL_ONFAIL_CLAUSE and has the reverse meaning. 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:
++
++MYSQL_ONSUCCESS_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:
++
++MYSQL_ONFAIL_CLAUSE UPDATE users SET last_bad_login=CURRENT_TIMESTAMP \
++ WHERE username='$(username)';
+
+-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.
++Note, that YOU CAN use the triggers even if you aren't using
++MYSQL_SELECT_CLAUSE. Also note, that there is such a possibility that ONFAIL
++trigger may be performed without a proper username. Take it into consideration
++when creating queries to avoid messy data on INSERT operations.
+
+-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.
+
+
+
+
++ *-----------------------
++ 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, which describes a domain.
++
++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
+ (including memory allocation for the buffers)
+@@ -102,6 +785,10 @@
+ newline as the second is replaced by two whitespaces while
+ putting into the buffer
+
++ i've also added USER_DOMAIN_CONCAT and USER_DOMAIN_SEPARATORS
++ configuration options - they're used by get_localpart(), get_domain()
++ and get_username() functions, which are described below
++
+ - sections where the query is constructed
+
+ selection is made, depending on configuration variables which
+@@ -130,7 +817,16 @@
+ MAX_SUBSTITUTION_LEN defines maximal length of a substitution variable's
+ identifier (name).
+
+-The last two definitions are just for code simplification.
++The last two definitions (SV_BEGIN_LEN and SV_END_LEN) are just for code
++simplification.
++
++#define DEF_CONCAT_STRING "@"
++#define DEF_SEPARATORS_SET "@%"
++
++The first (DEF_CONCAT_STRING) is used to set the defaults for a
++concatenation string, used when parsing $(username) substitution variable.
++The second (DEF_SEPARATORS_SET) is the set of characters, which are treated as
++separators when splitting local part from the domain.
+
+
+
+@@ -179,7 +875,7 @@
+ 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.
++the general parsing function.
+
+
+ 3.2 typedef size_t (*parsefunc)
+@@ -230,6 +926,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 +985,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 +1019,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 +1043,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 +1052,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 +1067,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 +1091,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 +1123,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 +1136,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 +1168,68 @@
+ 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,
++ const char *concat_str);
++
++DESCRIPTION
++
++ This function concatenates the localpart with a domain name
++ using the string pointed with concat_str. If the domain is
++ empty or NULL the result comes without binding string.
++
++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,7 +1239,9 @@
+
+ static char *parse_select_clause (const char *clause,
+ const char *username,
+- const char *defdomain);
++ const char *defdomain
++ const char *concat_str,
++ const char *separators_set);
+
+ DESCRIPTION
+
+@@ -473,15 +1249,21 @@
+ 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 $(domain). The separators_set is passed to get_username()
++ and get_domain() invocations, and the concat_str is passed
++ to get_username() function, which is responsible for replacing
++ $(username) substitution variable.
+
+-
+ RETURN VALUE
+
+ Same as parse_string().
+
++FILES
++
++ authlib/authmysqllib.c
+
+-4.10 parse_chpass_clause
++
++4.11 parse_chpass_clause
+
+ NAME
+
+@@ -492,6 +1274,8 @@
+ static char *parse_chpass_clause (const char *clause,
+ const char *username,
+ const char *defdomain,
++ const char *separators_set,
++ const char *concat_str,
+ const char *newpass,
+ const char *newpass_crypt);
+
+@@ -502,12 +1286,56 @@
+ 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 and the concat_str
++ are passed to get_localpart(), get_domain(), and get_username()
++ 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, USER_DOMAIN_CONCAT 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 1 on success and 0 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/authmysql.h
++ authlib/authmysql.c
+
+
+
+@@ -520,11 +1348,9 @@
+ 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)
++- allow batched queries and register variables for keeping results
++- put the parsing routines into separate files to make possible of sharing it
++ by more authentication modules
+
+
+
+@@ -534,10 +1360,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
+
+ ---------------------------------------------------------------------------
+
+diff -ur courier-imap-1.5.3.20020921.orig/authlib/authmysql.c courier-imap-1.5.3.20020921/authlib/authmysql.c
+--- courier-imap-1.5.3.20020921.orig/authlib/authmysql.c Mon Aug 19 17:52:28 2002
++++ courier-imap-1.5.3.20020921/authlib/authmysql.c Sat Sep 28 00:01:07 2002
+@@ -31,7 +31,11 @@
+ if ((user=strtok(authdata, "\n")) == 0 ||
+ (pass=strtok(0, "\n")) == 0)
+ {
+- errno=EPERM;
++ if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
++ errno=EACCES;
++ else
++ errno=EPERM;
++
+ return (0);
+ }
+
+@@ -50,7 +54,11 @@
+ {
+ if (authcheckpassword(pass,authinfo->cryptpw))
+ {
+- errno=EPERM;
++ if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
++ errno=EACCES;
++ else
++ errno=EPERM;
++
+ return (0); /* User/Password not found. */
+ }
+ }
+@@ -58,13 +66,21 @@
+ {
+ if (strcmp(pass, authinfo->clearpw))
+ {
+- errno=EPERM;
++ if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
++ errno=EACCES;
++ else
++ errno=EPERM;
++
+ return (0);
+ }
+ }
+ else
+ {
+- errno=EPERM;
++ if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
++ errno=EACCES;
++ else
++ errno=EPERM;
++
+ return (0); /* Username not found */
+ }
+
+@@ -132,6 +148,12 @@
+ (*callback_func)(&aa, callback_arg);
+ }
+
++ if (!auth_mysql_on_trigger("MYSQL_ONSUCCESS_CLAUSE", user))
++ {
++ errno=EACCES;
++ return (0);
++ }
++
+ return (strdup(authinfo->username));
+ }
+
+@@ -153,7 +175,11 @@
+ {
+ if (authcheckpassword(pass,authinfo->cryptpw))
+ {
+- errno=EPERM;
++ if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
++ errno=EACCES;
++ else
++ errno=EPERM;
++
+ return (-1); /* User/Password not found. */
+ }
+ }
+@@ -161,13 +187,21 @@
+ {
+ if (strcmp(pass, authinfo->clearpw))
+ {
+- errno=EPERM;
++ if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
++ errno=EACCES;
++ else
++ errno=EPERM;
++
+ return (-1);
+ }
+ }
+ else
+ {
+- errno=EPERM;
++ if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
++ errno=EACCES;
++ else
++ errno=EPERM;
++
+ return (-1);
+ }
+
+@@ -176,6 +210,13 @@
+ errno=EPERM;
+ return (-1);
+ }
++
++ if (!auth_mysql_on_trigger("MYSQL_ONSUCCESS_CLAUSE", user))
++ {
++ errno=EACCES;
++ return (-1);
++ }
++
+ return (0);
+ }
+
+diff -ur courier-imap-1.5.3.20020921.orig/authlib/authmysql.h courier-imap-1.5.3.20020921/authlib/authmysql.h
+--- courier-imap-1.5.3.20020921.orig/authlib/authmysql.h Mon Aug 6 05:12:39 2001
++++ courier-imap-1.5.3.20020921/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.20020921.orig/authlib/authmysqllib.c courier-imap-1.5.3.20020921/authlib/authmysqllib.c
+--- courier-imap-1.5.3.20020921.orig/authlib/authmysqllib.c Sun Aug 11 22:01:25 2002
++++ courier-imap-1.5.3.20020921/authlib/authmysqllib.c Sat Sep 28 06:06:41 2002
+@@ -24,6 +24,9 @@
+ #define SV_BEGIN_LEN ((sizeof(SV_BEGIN_MARK))-1)
+ #define SV_END_LEN ((sizeof(SV_END_MARK))-1)
+
++#define DEF_CONCAT_STRING "@"
++#define DEF_SEPARATORS_SET "@%"
++
+ static const char rcsid[]="$Id$";
+
+ /* siefca@pld.org.pl */
+@@ -268,7 +271,7 @@
+ SV_BEGIN_MARK
+ "%.*s"
+ SV_END_MARK
+- "\n", len, begin);
++ "\n", (int) len, begin);
+
+ return NULL;
+ }
+@@ -426,21 +429,43 @@
+ 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,
++ const char *concat_str)
++{
++static char username_buf[400];
++
++ if (!username || !domainname || !concat_str ||
++ *username == '\0' || *concat_str == '\0') return NULL;
++ if (( strlen(username) +
++ strlen(concat_str) +
++ strlen(domainname)) > 397) return NULL;
++
++ if (*domainname == '\0')
++ strcpy (username_buf, username);
++ else
++ sprintf (username_buf, "%s%s%s", username, concat_str,
++ 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 +494,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++;
+@@ -536,20 +567,27 @@
+
+ /* siefca@pld.org.pl */
+ static char *parse_select_clause (const char *clause, const char *username,
+- const char *defdomain)
++ const char *defdomain,
++ const char *concat_str,
++ 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')
++ !username || *username == '\0' ||
++ !concat_str || *concat_str == '\0' ||
++ !separators_set || *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, concat_str);
++
++ if (!vd[0].value || !vd[1].value || !vd[2].value)
+ return NULL;
+
+ return (parse_string (clause, vd));
+@@ -557,12 +595,16 @@
+
+ /* 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 *concat_str,
++ 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}};
+@@ -570,19 +612,83 @@
+ 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, concat_str);
++ 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 *concat_str =NULL,
++ *separators_set =NULL,
++ *defdomain =NULL,
++ *on_clause =NULL;
++MYSQL_RES *result;
++
++ if (!clause_name || *clause_name == '\0') return (0);
++ on_clause = read_env (clause_name);
++ if (!on_clause || *on_clause == '\0') return (1);
++
++ defdomain = read_env ("DEFAULT_DOMAIN");
++ concat_str = read_env ("USER_DOMAIN_CONCAT");
++ separators_set = read_env ("USER_DOMAIN_SEPARATORS");
++ if (!defdomain) defdomain = "";
++ if (!concat_str || *concat_str == '\0')
++ concat_str = DEF_CONCAT_STRING;
++ if (!separators_set || *separators_set == '\0')
++ separators_set = DEF_SEPARATORS_SET;
++
++ querybuf = parse_select_clause (on_clause,
++ username,
++ defdomain,
++ concat_str,
++ separators_set);
++
++ if (!querybuf) return (0);
++
++ 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 (1);
++}
++
++
+ struct authmysqluserinfo *auth_mysql_getuserinfo(const char *username)
+ {
+ const char *user_table =NULL;
+@@ -601,6 +707,8 @@
+ *gid_field =NULL,
+ *quota_field =NULL,
+ *where_clause =NULL,
++ *concat_str =NULL,
++ *separators_set =NULL,
+ *select_clause =NULL; /* siefca@pld.org.pl */
+
+ static const char query[]=
+@@ -709,7 +817,19 @@
+ else
+ {
+ /* siefca@pld.org.pl */
+- querybuf=parse_select_clause (select_clause, username, defdomain);
++ concat_str = read_env ("USER_DOMAIN_CONCAT");
++ separators_set = read_env ("USER_DOMAIN_SEPARATORS");
++
++ if (!concat_str || *concat_str == '\0')
++ concat_str = DEF_CONCAT_STRING;
++ if (!separators_set || *separators_set == '\0')
++ separators_set = DEF_SEPARATORS_SET;
++
++ querybuf = parse_select_clause (select_clause,
++ username,
++ defdomain,
++ concat_str,
++ separators_set);
+ if (!querybuf) return 0;
+ }
+
+@@ -793,6 +913,8 @@
+ *where_clause =NULL,
+ *user_table =NULL,
+ *login_field =NULL,
++ *concat_str =NULL,
++ *separators_set =NULL,
+ *chpass_clause =NULL; /* siefca@pld.org.pl */
+
+ if (!mysql)
+@@ -842,13 +964,22 @@
+ }
+ else
+ {
++ concat_str = read_env ("USER_DOMAIN_CONCAT");
++ separators_set = read_env ("USER_DOMAIN_SEPARATORS");
++
++ if (!concat_str || *concat_str == '\0')
++ concat_str = DEF_CONCAT_STRING;
++ if (!separators_set || *separators_set == '\0')
++ separators_set = DEF_SEPARATORS_SET;
++
+ sql_buf=parse_chpass_clause(chpass_clause,
+ user,
+ defdomain,
++ concat_str,
++ separators_set,
+ pass,
+ newpass_crypt_ptr);
+ }
+-
+
+ if (!sql_buf)
+ {
+diff -ur courier-imap-1.5.3.20020921.orig/authlib/authmysqlrc courier-imap-1.5.3.20020921/authlib/authmysqlrc
+--- courier-imap-1.5.3.20020921.orig/authlib/authmysqlrc Thu Apr 4 06:36:29 2002
++++ courier-imap-1.5.3.20020921/authlib/authmysqlrc Sat Sep 28 02:46:41 2002
+@@ -1,4 +1,4 @@
+-##VERSION: $Id$
++##VERSION: $Id$
+ #
+ # 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:
+-#
+-# username, cryptpw, uid, gid, clearpw, home, maildir, quota, fullname
++##NAME: USER_DOMAIN_CONCAT:0
+ #
+-# Enabling this option causes ignorance of any other field-related
+-# options, excluding default domain.
++# This is optional. Here, you can write the string used to concatenate
++# username with domain part while expanding the $(username) substitution
++# variable. If it's not set the '@' character is used.
++# See README.authmysql.myownquery for more information
+ #
+-# 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)
++# USER_DOMAIN_CONCAT @
++
++##NAME: USER_DOMAIN_SEPARATORS:0
+ #
+-# If a $(domain) is empty (not given by the remote user) the default domain
+-# name is used in its place.
++# 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
+ #
+-# This example is a little bit modified adaptation of vmail-sql
+-# database scheme:
++# USER_DOMAIN_SEPARATORS @%+
++
++##NAME: MYSQL_SELECT_CLAUSE:0
+ #
+-# 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
++# 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. $(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: MYSQL_ONSUCCESS_CLAUSE:0
++# (EXPERIMENTAL)
++#
++# This is optional, MYSQL_ONSUCCESS_CLAUSE is a trigger - the query is performed
++# each time user has successfuly logged in.
++# See README.authmysql.myownquery for more information
++#
++# MYSQL_ONSUCCESS_CLAUSE UPDATE users \
++# SET last_ok=CURRENT_TIMESTAMP \
++# WHERE username='$(local_part)' \
++# AND domain_name='$(domain)'
++
++##NAME: MYSQL_ONFAIL_CLAUSE:0
++# (EXPERIMENTAL)
+ #
++# This is optional, MYSQL_ONFAIL_CLAUSE is a trigger - the query is performed
++# each time user has successfuly logged in.
++# See README.authmysql.myownquery for more information
++#
++# MYSQL_ONFAIL_CLAUSE UPDATE users \
++# SET last_fail=CURRENT_TIMESTAMP \
++# WHERE username='$(local_part)' \
++# AND domain_name='$(domain)'