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 - 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$"; +static const char rcsid[]="$Id$"; + +/* 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$"; @@ -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)) + { + /* */ + + 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$ +##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: +##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)'