1 diff -ur courier-imap-1.4.2.orig/authlib/README.authmysql.myownquery courier-imap-1.4.2/authlib/README.authmysql.myownquery
2 --- courier-imap-1.4.2.orig/authlib/README.authmysql.myownquery Tue Jan 8 06:01:22 2002
3 +++ courier-imap-1.4.2/authlib/README.authmysql.myownquery Thu Feb 14 00:14:33 2002
8 - Developer Notes for courier-imap-myownquery.patch
9 + Developer Notes and Usage Instructions
10 + of courier-imap-authmysql-myownquery
15 - document version: 1.03
16 + document version: 1.20
24 +PART I - Usage Instructions
29 + 2 When I'll need it?
32 + 3.1 configuration variables
38 + 4.1 corporate mail system
39 + 4.1.1 database structure
40 + 4.1.2 authdaemon configuration
41 + 4.2 virtual mail domains provider
42 + 4.2.1 database structure
43 + 4.2.2 authdaemon configuration
45 +PART II - Developer Notes
47 + 1 Modifications overview
53 + 3.2 typedef size_t (*parsefunc)
58 + 4.3 ParsePlugin_counter
59 + 4.4 ParsePlugin_builder
61 + 4.6 validate_password
65 + 4.10 parse_select_clause
66 + 4.11 parse_chpass_clause
67 + 4.12 auth_mysql_on_trigger
69 -1 Modifications overview
77 - 3.2 typedef size_t (*parsefunc)
82 - 4.3 ParsePlugin_counter
83 - 4.4 ParsePlugin_builder
85 - 4.6 validate_password
88 - 4.9 parse_select_clause
89 - 4.10 parse_chpass_clause
97 +//////////////////////////////// PART I - Usage ///////////////////////////////
99 *-----------------------
102 *-----------------------
104 -Courier-imap-myownquery.patch allows administrator to set own MySQL queries
105 -used by authdaemon to authenticate user (including fetchig credentials) and to
106 -change user's password. It allows to construct SELECT or UPDATE clause in the
107 -configuration file (authmysqlrc) by adding two new configuration variables:
108 -MYSQL_SELECT_CLAUSE and MYSQL_CHPASS_CLAUSE. It may be useful in the mail
109 -environments where there is such a need to have different database structure
110 -and/or tables scheme than expected by authmysql module.
111 +Courier-imap-myownquery feature allows administrator to set his own MySQL
112 +queries used by authdaemon to authenticate a user (including fetchig his
113 +credentials) and to change the user's password. It allows to construct
114 +SELECT and UPDATE clause in the configuration file (authmysqlrc) using
115 +new configuration variables. It may be useful in the mail environments where
116 +there is such a need to have different database structure and/or tables
117 +scheme than expected by authmysql module.
119 It also implements a small parsing engine for substitution variables which
120 -may appear in the clauses and are used to put informations like username
121 -or domain into the right place of a query.
122 +may appear in the clauses and are used to put information like a username
123 +or a domain into the right place within the query.
125 -This patch was created using `diff -Nur` on courier-imap-1.3.12 source.
130 + *-----------------------
131 + 2 When I'll need it?
132 + *-----------------------
134 + o When you already have some MySQL database filled up with the data
135 + and there is no chance to change the whole structure to make it
136 + working with standard authmysql table. Typical situation is when all the
137 + data required to authenticate a user is arranged in more than one table.
139 + o When you have some great idea how to make the database structure
140 + more efficient due to your needs and your requirements.
142 + o When doing something 'by-myself' is in your style and you just want
143 + to create your own database, just to feel the pleasure of doing
144 + something original. :)
150 + *-----------------------
151 + 3 How does it work?
152 + *-----------------------
154 +There are three things which the feature concerns:
156 +- fetching clauses from the configuration file
157 +- doing substitution replacements inside of clauses
158 +- passing prepared query on to the mysql interface funtions
160 +3.1 configuration options
162 +You can apply your own MySQL queries using a set of the configuration options.
163 +The options you'll need to make the authmysql your slave are:
165 +MYSQL_SERVER (required)
166 +MYSQL_USERNAME (required)
167 +MYSQL_PASSWORD (required)
169 + The server name, userid, and password used to log in.
171 +MYSQL_DATABASE (required)
173 + The name of the MySQL database we will open.
175 +DEFAULT_DOMAIN (optional)
177 + If DEFAULT_DOMAIN is defined, and someone tries to log in as
178 + 'user', we will look up 'user@DEFAULT_DOMAIN' instead.
180 +USER_DOMAIN_CONCAT (optional)
182 + The USER_DOMAIN_CONCAT defines a character(s) used to
183 + concatenate a local part and a domain while parsing
184 + the $(username) substitution variable (see section 3.3
185 + for more info). If it's not defined the @ sign is assumed.
187 +USER_DOMAIN_SEPARATORS (optional)
189 + This may contain the set of characters used by parsing
190 + routines to split local part of the virtual mailbox name
191 + from the part which describes the domain name. If it's not
192 + defined the set containing @% is assumed, so the user can
193 + enter either: user@domain or user%domain when he wants to be
196 +MYSQL_SELECT_CLAUSE (required)
197 +MYSQL_CHPASS_CLAUSE (required under some circumstances)
199 + These are the major options you should use. See 3.2 section
202 +MYSQL_ONSUCCESS_CLAUSE (optional)
203 +MYSQL_ONFAIL_CLAUSE (optional)
205 + These are used to do a MySQL query whether user has passed
206 + the authentication verification (MYSQL_ONSUCCESS_CLAUSE)
207 + or there was the authentication failure (MYSQL_ONFAIL_CLAUSE).
208 + Query results have no meaning. You can use the same
209 + substitution variables in your query as with
210 + MYSQL_SELECT_CLAUSE. See 3.4 section for more info.
212 +The options which have no effect, and may be safetly left blank are:
228 +The feature adds two configuration options (clauses), which are parsed first,
229 +and then applied as MySQL queries to MySQL interface routines. These options
230 +are: MYSQL_SELECT_CLAUSE and MYSQL_CHPASS_CLAUSE. After each option a number of
231 +spaces and/or tabs is allowed, and then MySQL query is expected. For better
232 +look, your queries can have line breaks. Each line break should be preceded by
233 +the backslash sign. Look into examples chapter (4) to see how it should look
234 +like. First clause is used to authenticate a user, and the second to change his
237 +You should note that a query identified by MYSQL_SELECT_CLAUSE should return
238 +fixed number (9) of fields and each field should match the variable expected
239 +by authentication routines. These fields are:
241 +* username - which is the currently logged user's username (or the
242 + username with domain if you want it)
244 +* cryptpw - which is the user's crypted password
246 +* clearpw - which is the user's plaintext password
248 +* uid - which is a numerical UID value used as a process's UID when
249 + accessing the mailbox directory
251 +* gid - as above, but refers to GID
253 +* home - which contains full path to the user's home directory
255 +maildir - which contains the directory name inside the user's home
256 + which is treated as INBOX folder when accessing mailbox
257 + - if it's empty then the 'Maildir' string is used
259 +quota - which describes a quota size for the mailbox
261 +fullname - which may contain the user's fullname
263 +(The fields marked by the asterix sign are required and cannot have an
266 +So, the typical query clause may start with:
268 +MYSQL_SELECT_CLAUSE SELECT \
274 + users.mailbox_path) \
280 +Note that in this short example we're assuming that we have two tables
281 +(users and domains) and INBOX path is always called 'Maildir' and
282 +we're not using the fullname field (the query will always return an empty
283 +string in its place).
285 +Also note that you may discard one of the password fields if you don't want
286 +to use an authentication mechanism, which needs it. For example, if you don't
287 +want to use MD5-CRAM you may put '' into the place of clearpw (because, for
288 +example you're in paranoid mode and you don't even want to keep plain passwords
293 +Substitutions are strings, which may appear in your query, and which have a
294 +special meaning. You can also call them substitution variables. If substitution
295 +variable is known for a clause context then it is parsed. If it isn't known the
296 +error is generated. In the default compilation of authmysql module any
297 +substitution variable is declared inside of two substrings - the first is a
298 +dollar sign concatenated with opening parenthesis, and the second is a closing
299 +parenthesis sign. First symbol identifies beginning of a substitution variable,
300 +and the second closes it. The string between the beginning and the closing
301 +symbol is called substitution variable's name.
303 +When, as I said before, the name is known to the parsing routine the
304 +substitution is made and the proper value appears in place of the substitution
305 +variable, while passing on the query for later processing.
307 +Allowed substitution variables:
309 +context: MYSQL_SELECT_CLAUSE, MYSQL_ONFAIL_CLAUSE, MYSQL_ONSUCCESS_CLAUSE
311 +$(local_part) will be replaced by currently verified user's username
312 + (without the domain part)
314 +$(domain) will be replaced by currently verified user's domain
315 + name (if present, or if not present but the
316 + DEFAULT_DOMAIN was used) or by the empty, zero-length
317 + string if the domain cannot be obtained
319 +$(username) will be replaced by currently verified user's username
320 + concatenated with the given domain name using symbol
321 + defined by USER_DOMAIN_CONCAT - if the domiain name
322 + cannot be obtained (even by looking up DEFAULT_DOMAIN)
323 + the separation sign will not appear and only the given
324 + username will be presented
326 +context: MYSQL_CHPASS_CLAUSE
328 +$(local_part) will be replaced by currently verified user's username
329 + (without the domain part)
331 +$(domain) will be replaced by currently verified user's domain
332 + name (if present, or if not present but the
333 + DEFAULT_DOMAIN was used) or by the empty, zero-length
334 + string if the domain cannot be obtained
336 +$(username) will be replaced by currently verified user's username
337 + concatenated with the given domain name using symbol
338 + defined by USER_DOMAIN_CONCAT - if the domiain name
339 + cannot be obtained (even by looking up DEFAULT_DOMAIN)
340 + the separation sign will not appear and only the given
341 + username will be presented
343 +$(newpass) will be replaced by currently authenticated user's
344 + new password to set up (plaintext password)
346 +$(newpass_crypt) will be replaced by currently authenticated user's
347 + new password to set up (MD5 form created from entered
352 +Triggers are MySQL queries, which are performed depending on authentication
353 +state. Currently, there are two triggers which you may use. First is called
354 +MYSQL_ONSUCCESS_CLAUSE and it is performed when the authentication succeedes.
355 +The second is called MYSQL_ONFAIL_CLAUSE and has the reverse meaning. You can
356 +declare triggers in the authmysqlrc configuration file. They can be used to
357 +arrange some logging facility in the database or just to keep last times
358 +of the successful/failed login tries. The typical trigger, which puts last
359 +login date into the users' table can look like this:
361 +MYSQL_ONSUCCESS_CLAUSE UPDATE users SET last_login=CURRENT_TIMESTAMP \
362 + WHERE username='$(username)';
364 +or, if you would like to know about last login failure for users you can try:
366 +MYSQL_ONFAIL_CLAUSE UPDATE users SET last_bad_login=CURRENT_TIMESTAMP \
367 + WHERE username='$(username)';
369 +Note, that YOU CAN use the triggers even if you aren't using
370 +MYSQL_SELECT_CLAUSE. Also note, that there is such a possibility that ONFAIL
371 +trigger may be performed without a proper username. Take it into consideration
372 +when creating queries to avoid messy data on INSERT operations.
379 + *-----------------------
380 + 4 Examples of usage
381 + *-----------------------
383 +The "ownquery" feature gives you possibility to adapt an authentication query
384 +to the database. So the first thing you have to do is to design the database
385 +structure you need, whithout being grieved at what structure authentication
386 +routines like. You have to take care about four essential things:
390 + o The users' data in the database
392 + o The proper directories for keeping virtual mailboxes and a system user
393 + which can read and write them
395 + o The proper MySQL queries in your authmysqlrc configuration file
397 +4.1 corporate mail system
399 +This example is concerned about a corporate mail system with a small
400 +count of served virtual domains. The database scheme was derived from tpop3d
401 +documentation and modified a bit.
403 +4.1.1 database structure
405 +Our goal here is to separate the data responsible for keeping mailbox
406 +credentials from the data, which describes a domain.
408 +Let's create some tables for our example, filled up with an example data:
412 +purpose: associates virtual domain with domain name and informations
413 + necessary to access mailboxes withing the domain
415 +fields: domain_name - fully qualified domain name
416 + path_prefix - absolute pathname which points to
417 + a directory where domain's mailboxes
419 + quota - default quota for each mailbox
420 + uid - UID used to work on mailboxes
421 + gid - GID used to work on mailboxes
423 + +----------------+-------------+-----+-----+----------+
424 + | domain_name | path_prefix | uid | gid | quota |
425 + +----------------+-------------+-----+-----+----------+
426 + | exampledom.com | /var/mail/x | 555 | 555 | 10000000 |
427 + | pld.org.pl | /var/mail/p | 556 | 556 | 20000000 |
428 + | pld.net.pl | /var/mail/p | 556 | 556 | 20000000 |
429 + +----------------+-------------+-----+-----+----------+
433 +purpose: associates virtual mailbox with user and domain name,
434 + and with informations necessary to access mailbox
436 +fields: username - user login name (mailbox name)
437 + domain_name - fully qualified domain name
438 + mailbox_path - relative pathname for mailbox
439 + (will be appended to the path_prefix
440 + from domain_auth table to specify
441 + user's mailbox location)
442 + cryptpw - crypted password
443 + plainpw - plaintext password
445 + +----------+----------------+--------------+------------+--------+
446 + | username | domain_name | mailbox_path | cryptpw | plainpw |
447 + +----------+----------------+--------------+-----------+---------+
448 + | siefca | pld.org.pl | s/siefca | $1$fs45.. | dupa.8 |
449 + | siefca | pld.net.pl | s/siefca | $1$fs45.. | dupa.8 |
450 + | f00bar | exampledom.com | foobar | $1$g44w.. | secret |
451 + +----------+----------------+--------------+-----------+---------+
453 +Using MySQL monitor you can create these tables entering CREATE sequences.
454 +Be sure to connect to the database using administrative MySQL account
455 +(usualy: mysql -u mysql -p).
457 +--------------------- cut here
459 +# Create the database called vmail.
461 +CREATE database vmail;
463 +# Create an example MySQL user, which can read, write and delete data from
464 +# vmail database. Username: vuser Password: secret_password
466 +GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
468 + IDENTIFIED BY 'secret_password';
472 +# Create the tables.
476 +CREATE TABLE domains (
477 + domain_name char(255) DEFAULT '',
478 + path_prefix char(255) DEFAULT '' NOT NULL,
479 + uid int(10) unsigned DEFAULT '15000' NOT NULL,
480 + gid int(10) unsigned DEFAULT '15000' NOT NULL,
481 + quota char(255) DEFAULT '2000000' NOT NULL,
482 + KEY domain_name (domain_name(255))
485 +CREATE TABLE users (
486 + username char(128) DEFAULT '' NOT NULL,
487 + domain_name char(255) DEFAULT '',
488 + mailbox_path char(255) DEFAULT '' NOT NULL,
489 + cryptpw char(128) DEFAULT '' NOT NULL,
490 + clearpw char(128) DEFAULT '' NOT NULL,
491 + KEY username (username(128))
494 +# Create an example virtual domain entry
495 +# name : exampledom.com
498 +# path : /var/mail/x
499 +# quota : 10 Megs per mailbox
501 +INSERT INTO domains VALUES ('exampledom.com', '/var/mail/x', 555, 555,
504 +# Create an example virtual user entry
506 +# domain name : exampledom.com
507 +# cryptpw : $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
509 +# mailbox path : s/siefca
511 +INSERT INTO users VALUES ('siefca', 'exampledom.com', 's/siefca',
512 + '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
515 +--------------------- cut here
517 +Note: If you would like to have your passwords more safe then just omit the
518 + clearpw column and put '' into the config-query in its place while
519 + doing SELECT on a database. But be ware - you'll be unable to use
520 + authentication methods which needs it, like MD5_CRAM.
522 +4.1.2 authdaemon configuration
524 +When our database is ready we can set up the configuration. :-) Go to
525 +authmysqlrc file and edit it.
527 +At the beginning we should take care about general informations, which
528 +are identifying our database:
530 +MYSQL_SERVER localhost
531 +MYSQL_USERNAME vuser
532 +MYSQL_PASSWORD secret_password
533 +MYSQL_DATABASE vmail
535 +Then we should add a clause responsible for authenticating user and
536 +fetching credentials:
538 +DEFAULT_DOMAIN exampledom.com
540 +MYSQL_SELECT_CLAUSE SELECT \
541 + users.username, users.cryptpw, users.clearpw, \
542 + domains.uid, domains.gid, \
543 + CONCAT_WS('/',domains.path_prefix,users.mailbox_path), \
544 + '', domains.quota, '' \
545 + FROM users, domains \
546 + WHERE domains.domain_name='$(domain)' \
547 + AND users.username='$(local_part)' \
548 + AND domains.domain_name=users.domain_name
551 +Note the '' in the place of field which tells where user's INBOX resides
552 +and in place of realname field. You should use '' if you want to put an empty
553 +value as a query result for some field.
555 +We also should add some configuration for changing user's password:
557 +MYSQL_CHPASS_CLAUSE UPDATE \
559 + SET clearpw='$(newpass)', \
560 + cryptpw='$(newpass_crypt)' \
561 + WHERE username='$(local_part)' \
562 + AND domain_name='$(domain)'
564 +Last things to have sure...
565 +Create a system user/group and a proper directory structure. In our example:
567 +groupadd -g 555 xdomain
568 +useradd -u 555 -g 555 xdomain
569 +mkdir -p /var/mail/x/s/siefca
570 +chmod -R 0770 /var/mail/x
571 +maildirmake /var/mail/x/s/siefca/Maildir
572 +chown -R xdomain.xdomain /var/mail/x
574 +Now, restart the authdaemon and see if it works. Try: telnet 0 pop3
581 +You should get Ok response. ;)
583 +4.2 virtual mail domains provider
585 +Let's consider more complicated database scheme, where is a need to
586 +associate a lot of information with the domain name, including registrant
587 +information, owner, etc. That implies data separation between domain name,
588 +user and domain additional informations (which are unwanted when
589 +authentication process takes place). By the proper data separation I mean
590 +avoiding unwanted redundancy in the database.
592 +Currently applied example doesn't care about the update password problem.
593 +This is due to current abilities of MySQL and authdaemon (authmysql).
594 +MySQL doesn't support subsequent SELECTs on UPDATE operation, and authmysql
595 +doesn't supports batched queries at the moment.
597 +4.2.1 database structure
601 +purpose: associates domain_id with domain name
603 +fields: domain_name - fully qualified domain name
604 + domain_id - domain identifier
606 + +----------------+-----------+
607 + | domain_name | domain_id |
608 + +----------------+-----------+
609 + | exampledom.com | 1 |
612 + | foobare.net.uk | 3 |
613 + +----------------+-----------+
615 +Note, that for pld.org.pl and pld.net.pl the domain identifiers are the same.
616 +We can create a domain aliases in such a way. :)
620 +purpose: associates domain_id with authentication credentials
621 + which are common for all users in the virtual domain
623 +fields: domain_id - domain identifier
624 + path_prefix - absolute pathname which points to
625 + a directory where domain's mailboxes
627 + quota - default quota for each mailbox
628 + uid - UID used to work on mailboxes
629 + gid - GID used to work on mailboxes
631 + +------------+---------------+--------+-------+-------+
632 + | domain_id | path_prefix | quota | uid | gid |
633 + +------------+---------------+--------+-------+-------+
634 + | 1 | /var/mail/ex | 100000 | 15000 | 15000 |
635 + | 2 | /var/mail/pld | 555500 | 15001 | 15000 |
636 + | 3 | /home/f0/mail | 8000 | 15002 | 15000 |
637 + +------------+---------------+--------+-------+-------+
641 +purpose: associates domain_id with additional informations
643 +fields: domain_id - domain identifier
644 + registrant_id - registrant identifier
645 + nic_handle - NIC handle
646 + owner_id - domain's owner identifier
647 + expires - domain's expiration date
649 + +------------+---------------+------------+----------+---------+
650 + | domain_id | registrant_id | nic_handle | owner_id | expires |
651 + +------------+---------------+------------+----------+---------+
653 + (we don't need to say anything more about this table indeed)
657 +purpose: associates users' identifiers with domains' identifiers
658 + and infers the credentials for various virtual mailboxes
660 +fields: username - user's login name
661 + domain_id - domain identifier
662 + cryptpw - crypted password
663 + plainpw - plaintext password
664 + quota - user's mailbox quota
665 + (will override quota value set for
666 + the whole virtual domain)
667 + path - relative pathname for mailbox
668 + (will be appended to the path_prefix
669 + from domain_auth table to specify
670 + user's mailbox location)
672 + +------------+-----------+----------+-----------+-------+------------+
673 + | username | domain_id | cryptpw | plainpw | quota | path |
674 + +------------+-----------+----------+-----------+-------+------------+
675 + | foobar | 1 | $1$hlIeE | dupa.8 | NULL | f/o/foobar |
676 + | breeder | 2 | $1$TWsdf | ziarno128 | 77777 | brd |
677 + +------------+-----------+----------+-----------+-------+------------+
679 + (you can add a realname column here, it doesn't fit to my terminal window:)
681 +--------------------- cut here
683 +# Create the database called vmail.
685 +CREATE database vmail;
687 +# Create an example MySQL user, which can read, write and delete data from
688 +# vmail database. Username: vuser Password: secret_password
690 +GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
692 + IDENTIFIED BY 'secret_password';
696 +# Create the tables.
700 +CREATE TABLE domain_names (
701 + domain_id int(10) unsigned NOT NULL,
702 + domain_name char(255) DEFAULT '' NOT NULL,
703 + KEY domain_name (domain_name(255))
706 +CREATE TABLE domain_auth (
707 + domain_id int(10) unsigned DEFAULT 1 NOT NULL,
708 + uid int(10) unsigned DEFAULT '15000' NOT NULL,
709 + gid int(10) unsigned DEFAULT '15000' NOT NULL,
710 + path_prefix char(255) DEFAULT '' NOT NULL,
711 + quota char(255) DEFAULT '20000000' NOT NULL,
712 + KEY domain_id (domain_id)
715 +CREATE TABLE users (
716 + username char(128) DEFAULT '' NOT NULL,
717 + domain_id int(10) unsigned DEFAULT 1 NOT NULL,
718 + cryptpw char(128) DEFAULT '' NOT NULL,
719 + plainpw char(128) DEFAULT '' NOT NULL,
720 + name char(128) DEFAULT '' NOT NULL,
722 + path char(255) DEFAULT '' NOT NULL,
723 + KEY username (username(128))
726 +# Create an example virtual domain entry
728 +# name : exampledom.com
731 +# path : /var/mail/example
732 +# quota : 20 Megs per mailbox
734 +INSERT INTO domain_names VALUES (1, 'exampledom.com');
735 +INSERT INTO domain_auth VALUES (1, '15000', '15000', '/var/mail/example',
738 +# Create an example virtual user entry
740 +# domain id : 1 (points to exampledom.com)
741 +# cryptpw : $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
744 +# quota : NULL (we want it to be fetched from domain_auth table)
745 +# mailbox path : s/i/siefca
747 +INSERT INTO users VALUES ('siefca', 1, '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
748 + 'dupa.8', 'Pawel Wilk', NULL, 's/i/siefca');
750 +--------------------- cut here
752 +Ok, we've done what we need. Don't forget to create system user with UID and
753 +GID set to 15000, and a directory containing mailboxes (in this case:
754 +/var/mail/example) owned by system user I've mentioned above.
755 +There is also necessary to create Maildir folder structure for our user
756 +inside the virtual domain directory - you can configure your MTA agent to do
757 +such thing when first message arrive or use maildirmake tool, which comes
761 +4.2.2 authdaemon configuration
763 +DEFAULT_DOMAIN exampledom.com
765 +MYSQL_SELECT_CLAUSE SELECT \
771 + CONCAT_WS('/',domain_auth.path_prefix,users.path), \
773 + IFNULL(users.quota, domain_auth.quota), \
775 + FROM users, domain_names, domain_auth \
776 + WHERE domain_names.domain_name='$(domain)' \
777 + AND users.username='$(local_part)' \
778 + AND domain_names.domain_id=users.domain_id \
779 + AND domain_names.domain_id=domain_auth.domain_id
789 +/////////////////////////// PART II - Developer Notes /////////////////////////
791 *-----------------------
792 1 Modifications overview
794 Each modified set of instructions is marked by my e-mail address:
797 -Changes in the current source code are related to:
798 +Changes in the source code are related to:
800 - sections where the queries are constructed
801 (including memory allocation for the buffers)
803 newline as the second is replaced by two whitespaces while
804 putting into the buffer
806 + i've also added USER_DOMAIN_CONCAT and USER_DOMAIN_SEPARATORS
807 + configuration options - they're used by get_localpart(), get_domain()
808 + and get_username() functions, which are described below
810 - sections where the query is constructed
812 selection is made, depending on configuration variables which
814 MAX_SUBSTITUTION_LEN defines maximal length of a substitution variable's
817 -The last two definitions are just for code simplification.
818 +The last two definitions (SV_BEGIN_LEN and SV_END_LEN) are just for code
821 +#define DEF_CONCAT_STRING "@"
822 +#define DEF_SEPARATORS_SET "@%"
824 +The first (DEF_CONCAT_STRING) is used to set the defaults for a
825 +concatenation string, used when parsing $(username) substitution variable.
826 +The second (DEF_SEPARATORS_SET) is the set of characters, which are treated as
827 +separators when splitting local part from the domain.
832 In this example we've declared that $(some) in the query should be
833 replaced by 'replacement' text, and replacement for $(anotha) will
834 be defined in the code before passing on the array pointer to
835 -the paring function.
836 +the general parsing function.
839 3.2 typedef size_t (*parsefunc)
840 @@ -414,14 +1110,17 @@
844 - static const char *get_localpart (const char *username);
845 + static const char *get_localpart (const char *username,
846 + const char *separators);
850 This function detaches local part of an e-mail address
851 from string pointed with username and puts it to the
852 buffer of the fixed length. All necessary cleaning is
853 - made on the result string.
854 + made on the result string. String pointed with separators
855 + refers to a set of characters, which are treated as
856 + separation signs between local part and a domain.
860 @@ -438,16 +1137,22 @@
863 static const char *get_domain (const char *username,
864 - const char *defdomain);
865 + const char *defdomain,
866 + const char *separators);
870 This function detaches domain part of an e-mail address
871 from string pointed with username and puts it to the
872 buffer of the fixed length. All necessary cleaning is
873 - made on the result string. If function cannot find domain
874 - part in the string the string pointed by defdomain is
876 + made on the result string. If the function cannot find a domain
877 + part in the string then the string pointed to by defdomain is
878 + used instead. If this function cannot find a domain part
879 + as well as it cannot obtain the default domain (it's empty string
880 + or the defdomain pointer is NULL) the returned result string is an
881 + empty string. The string pointed with separators refers to a set
882 + of characters, which are treated as separation signs between local
887 @@ -455,7 +1160,36 @@
888 NULL if there was some error.
891 -4.9 parse_select_clause
900 + static const char *get_username (const char *username,
901 + const char *domainname,
902 + const char *concat_str);
906 + This function concatenates the localpart with a domain name
907 + using the string pointed with concat_str. If the domain is
908 + empty or NULL the result comes without binding string.
912 + Pointer to the static buffer containing output string or
913 + NULL if there was some error.
917 + This function does not any string cleaning, nor default domain
918 + checking. It is designed to work on results of get_localpart() and
921 +4.10 parse_select_clause
927 static char *parse_select_clause (const char *clause,
928 const char *username,
929 - const char *defdomain);
930 + const char *defdomain
931 + const char *concat_str,
932 + const char *separators_set);
936 @@ -473,15 +1209,17 @@
937 function. It parses a query pointed by caluse. username
938 and defdomain strings are used to replace corresponding
939 substitution strings if present in the query: $(local_part)
941 + and $(domain). The separators_set is passed to get_username()
942 + and get_domain() invocations, and the concat_str is passed
943 + to get_username() function, which is responsible for replacing
944 + $(username) substitution variable.
949 Same as parse_string().
952 -4.10 parse_chpass_clause
953 +4.11 parse_chpass_clause
958 static char *parse_chpass_clause (const char *clause,
959 const char *username,
960 const char *defdomain,
961 + const char *separators_set,
962 + const char *concat_str,
964 const char *newpass_crypt);
966 @@ -502,12 +1242,47 @@
967 defdomain, newpass and newpass_crypt strings are used to
968 replace corresponding substitution strings if present in
969 the query: $(local_part), $(domain), $(newpass),
971 + $(newpass_crypt). The separators_set and the concat_str
972 + are passed to get_localpart(), get_domain(), and get_username()
973 + functions as described in the entry for parse_select_clause().
977 Same as parse_string().
979 +4.12 auth_mysql_on_trigger
983 + auth_mysql_on_trigger
987 + int auth_mysql_on_trigger (const char *clause_name,
988 + const char *username);
992 + This function is responsible for calling out the MySQL queries in
993 + depend which authentication state was reached.
995 + The clause_name should contain the name of a clause, which can be found
996 + in the configuration file, and the username is simply the string used
997 + as username (including the domain if entered).
999 + This function reads DEFAULT_DOMAIN, USER_DOMAIN_CONCAT and
1000 + USER_DOMAIN_SEPARATORS from the configuration file using read_env(),
1001 + then it uses parse_select_clause() to parse the query obtained using
1002 + read_env(clause_name), and then it calls querying subroutines to
1003 + perform the action.
1007 + This function returns 1 on success and 0 on failure. The query results
1008 + are simply discarded. If a trigger's clause is not defined in the
1009 + configuration file the 1 is returned and function silently ends its
1015 @@ -520,11 +1295,9 @@
1016 strings after split (problem?)
1017 - allow admin to set a group name instead of numerical group id
1018 - allow admin to set a username instead of numerical user id
1022 - - MYSQL_PRESELECT_CLAUSE (query which comes before MYSQL_SELECT_CLAUSE)
1023 - - MYSQL_POSTSELECT_CLAUSE (query which comes after MYSQL_SELECT_CLAUSE)
1024 +- allow batched queries and register variables for keeping results
1025 +- put the parsing routines into separate files to make possible of sharing it
1026 + by more authentication modules
1030 @@ -534,10 +1307,12 @@
1032 *------------------------
1034 -At the beginning this patch was messy indeed. :> I would like to thank
1035 +At the beginning the patch was messy indeed. :> I would like to thank
1036 Sam Varshavchik for pointing me a lot how to make it more fast and solid.
1037 I would also thank Philip Hazel, Chris Lightfoot and Mike Bremford which
1038 -by their software capabilities inspired me to write it.
1039 +by their software capabilities inspired me to write it. Oliver Oblasnik
1040 +remainded me to make the documentation more friendly for those who are
1041 +not programmers and just want to use it. :>
1043 ---------------------------------------------------------------------------
1045 diff -ur courier-imap-1.4.2.orig/authlib/authmysql.c courier-imap-1.4.2/authlib/authmysql.c
1046 --- courier-imap-1.4.2.orig/authlib/authmysql.c Sun Jun 24 01:42:05 2001
1047 +++ courier-imap-1.4.2/authlib/authmysql.c Sun Feb 10 04:54:37 2002
1049 if ((user=strtok(authdata, "\n")) == 0 ||
1050 (pass=strtok(0, "\n")) == 0)
1053 + if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1063 if (authcheckpassword(pass,authinfo->cryptpw))
1066 + if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1071 return (0); /* User/Password not found. */
1076 if (strcmp(pass, authinfo->clearpw))
1079 + if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1090 + if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1095 return (0); /* Username not found */
1098 @@ -132,6 +148,12 @@
1099 (*callback_func)(&aa, callback_arg);
1102 + if (!auth_mysql_on_trigger("MYSQL_ONSUCCESS_CLAUSE", user))
1108 return (strdup(authinfo->username));
1111 @@ -153,7 +175,11 @@
1113 if (authcheckpassword(pass,authinfo->cryptpw))
1116 + if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1121 return (-1); /* User/Password not found. */
1124 @@ -161,13 +187,21 @@
1126 if (strcmp(pass, authinfo->clearpw))
1129 + if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1140 + if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1148 @@ -176,6 +210,13 @@
1153 + if (!auth_mysql_on_trigger("MYSQL_ONSUCCESS_CLAUSE", user))
1162 diff -ur courier-imap-1.4.2.orig/authlib/authmysql.h courier-imap-1.4.2/authlib/authmysql.h
1163 --- courier-imap-1.4.2.orig/authlib/authmysql.h Mon Aug 6 05:12:39 2001
1164 +++ courier-imap-1.4.2/authlib/authmysql.h Sun Feb 10 04:56:30 2002
1168 extern struct authmysqluserinfo *auth_mysql_getuserinfo(const char *);
1169 +extern int auth_mysql_on_trigger (const char *clause_name, const char *username);
1170 extern void auth_mysql_cleanup();
1172 extern int auth_mysql_setpass(const char *, const char *);
1173 diff -ur courier-imap-1.4.2.orig/authlib/authmysqllib.c courier-imap-1.4.2/authlib/authmysqllib.c
1174 --- courier-imap-1.4.2.orig/authlib/authmysqllib.c Thu Jan 10 05:44:06 2002
1175 +++ courier-imap-1.4.2/authlib/authmysqllib.c Sun Feb 10 14:35:10 2002
1177 #define SV_BEGIN_LEN ((sizeof(SV_BEGIN_MARK))-1)
1178 #define SV_END_LEN ((sizeof(SV_END_MARK))-1)
1180 +#define DEF_CONCAT_STRING "@"
1181 +#define DEF_SEPARATORS_SET "@%"
1183 static const char rcsid[]="$Id$";
1185 /* siefca@pld.org.pl */
1186 @@ -426,21 +429,43 @@
1195 /* siefca@pld.org.pl */
1196 -static const char *get_localpart (const char *username)
1197 +static const char *get_username (const char *username, const char *domainname,
1198 + const char *concat_str)
1200 +static char username_buf[400];
1202 + if (!username || !domainname || !concat_str ||
1203 + *username == '\0' || *concat_str == '\0') return NULL;
1204 + if (( strlen(username) +
1205 + strlen(concat_str) +
1206 + strlen(domainname)) > 397) return NULL;
1208 + if (*domainname == '\0')
1209 + strcpy (username_buf, username);
1211 + sprintf (username_buf, "%s%s%s", username, concat_str,
1214 + return (username_buf);
1217 +/* siefca@pld.org.pl */
1218 +static const char *get_localpart (const char *username, const char *separators)
1221 const char *l_end, *p;
1223 static char localpart_buf[130];
1225 - if (!username || *username == '\0') return NULL;
1226 + if (!username || *username == '\0' ||
1227 + !separators || *separators == '\0') return NULL;
1229 - p = strchr(username,'@');
1230 + p = strpbrk (username, separators);
1233 if ((p-username) > 128)
1234 @@ -469,21 +494,27 @@
1237 /* siefca@pld.org.pl */
1238 -static const char *get_domain (const char *username, const char *defdomain)
1239 +static const char *get_domain (const char *username, const char *defdomain,
1240 + const char *separators)
1242 static char domain_buf[260];
1246 - if (!username || *username == '\0') return NULL;
1247 - p = strchr(username,'@');
1248 + if (!username || *username == '\0' ||
1249 + !separators || *separators == '\0') return NULL;
1251 + p = strpbrk (username, separators);
1253 if (!p || *(p+1) == '\0')
1255 - if (defdomain && *defdomain)
1256 + if (defdomain && *defdomain != '\0')
1261 + *domain_buf = '\0';
1262 + return domain_buf;
1267 @@ -528,20 +559,27 @@
1269 /* siefca@pld.org.pl */
1270 static char *parse_select_clause (const char *clause, const char *username,
1271 - const char *defdomain)
1272 + const char *defdomain,
1273 + const char *concat_str,
1274 + const char *separators_set)
1276 static struct var_data vd[]={
1277 {"local_part", NULL, sizeof("local_part"), 0},
1278 {"domain", NULL, sizeof("domain"), 0},
1279 + {"username", NULL, sizeof("username"), 0},
1280 {NULL, NULL, 0, 0}};
1282 if (clause == NULL || *clause == '\0' ||
1283 - !username || *username == '\0')
1284 + !username || *username == '\0' ||
1285 + !concat_str || *concat_str == '\0' ||
1286 + !separators_set || *separators_set == '\0')
1289 - vd[0].value = get_localpart (username);
1290 - vd[1].value = get_domain (username, defdomain);
1291 - if (!vd[0].value || !vd[1].value)
1292 + vd[0].value = get_localpart (username, separators_set);
1293 + vd[1].value = get_domain (username, defdomain, separators_set);
1294 + vd[2].value = get_username (vd[0].value, vd[1].value, concat_str);
1296 + if (!vd[0].value || !vd[1].value || !vd[2].value)
1299 return (parse_string (clause, vd));
1300 @@ -549,12 +587,16 @@
1302 /* siefca@pld.org.pl */
1303 static char *parse_chpass_clause (const char *clause, const char *username,
1304 - const char *defdomain, const char *newpass,
1305 + const char *defdomain,
1306 + const char *separators_set,
1307 + const char *concat_str,
1308 + const char *newpass,
1309 const char *newpass_crypt)
1311 static struct var_data vd[]={
1312 {"local_part", NULL, sizeof("local_part"), 0},
1313 {"domain", NULL, sizeof("domain"), 0},
1314 + {"username", NULL, sizeof("username"), 0},
1315 {"newpass", NULL, sizeof("newpass"), 0},
1316 {"newpass_crypt", NULL, sizeof("newpass_crypt"), 0},
1317 {NULL, NULL, 0, 0}};
1318 @@ -562,19 +604,83 @@
1319 if (clause == NULL || *clause == '\0' ||
1320 !username || *username == '\0' ||
1321 !newpass || *newpass == '\0' ||
1322 + !separators_set || *separators_set == '\0' ||
1323 !newpass_crypt || *newpass_crypt == '\0') return NULL;
1325 - vd[0].value = get_localpart (username);
1326 - vd[1].value = get_domain (username, defdomain);
1327 - vd[2].value = validate_password (newpass);
1328 - vd[3].value = validate_password (newpass_crypt);
1329 + vd[0].value = get_localpart (username, separators_set);
1330 + vd[1].value = get_domain (username, defdomain, separators_set);
1331 + vd[3].value = get_username (vd[0].value, vd[1].value, concat_str);
1332 + vd[4].value = validate_password (newpass);
1333 + vd[5].value = validate_password (newpass_crypt);
1335 if (!vd[0].value || !vd[1].value ||
1336 - !vd[2].value || !vd[3].value) return NULL;
1337 + !vd[2].value || !vd[3].value ||
1338 + !vd[4].value || !vd[5].value) return NULL;
1340 return (parse_string (clause, vd));
1343 +/* siefca@pld.org.pl */
1344 +int auth_mysql_on_trigger (const char *clause_name, const char *username)
1346 +char *querybuf =NULL;
1347 +const char *concat_str =NULL,
1348 + *separators_set =NULL,
1353 + if (!clause_name || *clause_name == '\0') return (0);
1354 + on_clause = read_env (clause_name);
1355 + if (!on_clause || *on_clause == '\0') return (1);
1357 + defdomain = read_env ("DEFAULT_DOMAIN");
1358 + concat_str = read_env ("USER_DOMAIN_CONCAT");
1359 + separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1360 + if (!defdomain) defdomain = "";
1361 + if (!concat_str || *concat_str == '\0')
1362 + concat_str = DEF_CONCAT_STRING;
1363 + if (!separators_set || *separators_set == '\0')
1364 + separators_set = DEF_SEPARATORS_SET;
1366 + querybuf = parse_select_clause (on_clause,
1372 + if (!querybuf) return (0);
1374 + if (mysql_query (mysql, querybuf))
1376 + /* <o.blasnik@nextra.de> */
1378 + auth_mysql_cleanup();
1386 + if (mysql_query (mysql, querybuf))
1389 + auth_mysql_cleanup();
1390 + /* Server went down, that's OK,
1391 + ** try again next time.
1397 + result = mysql_store_result(mysql);
1398 + if (result) mysql_free_result(result);
1404 struct authmysqluserinfo *auth_mysql_getuserinfo(const char *username)
1406 const char *user_table =NULL;
1410 *where_clause =NULL,
1411 + *concat_str =NULL,
1412 + *separators_set =NULL,
1413 *select_clause =NULL; /* siefca@pld.org.pl */
1415 static const char query[]=
1416 @@ -701,7 +809,19 @@
1419 /* siefca@pld.org.pl */
1420 - querybuf=parse_select_clause (select_clause, username, defdomain);
1421 + concat_str = read_env ("USER_DOMAIN_CONCAT");
1422 + separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1424 + if (!concat_str || *concat_str == '\0')
1425 + concat_str = DEF_CONCAT_STRING;
1426 + if (!separators_set || *separators_set == '\0')
1427 + separators_set = DEF_SEPARATORS_SET;
1429 + querybuf = parse_select_clause (select_clause,
1434 if (!querybuf) return 0;
1438 *where_clause =NULL,
1441 + *concat_str =NULL,
1442 + *separators_set =NULL,
1443 *chpass_clause =NULL; /* siefca@pld.org.pl */
1446 @@ -834,14 +956,23 @@
1450 + concat_str = read_env ("USER_DOMAIN_CONCAT");
1451 + separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1453 + if (!concat_str || *concat_str == '\0')
1454 + concat_str = DEF_CONCAT_STRING;
1455 + if (!separators_set || *separators_set == '\0')
1456 + separators_set = DEF_SEPARATORS_SET;
1458 sql_buf=parse_chpass_clause(chpass_clause,
1470 free(newpass_crypt);
1471 diff -ur courier-imap-1.4.2.orig/authlib/authmysqlrc courier-imap-1.4.2/authlib/authmysqlrc
1472 --- courier-imap-1.4.2.orig/authlib/authmysqlrc Tue Jan 8 06:20:46 2002
1473 +++ courier-imap-1.4.2/authlib/authmysqlrc Thu Feb 14 00:19:43 2002
1474 @@ -141,65 +141,91 @@
1476 # MYSQL_WHERE_CLAUSE server='mailhost.example.com'
1478 -##NAME: MYSQL_SELECT_CLAUSE:0
1479 +##NAME: USER_DOMAIN_CONCAT:0
1482 -# This is optional, MYSQL_SELECT_CLAUSE can be set when you have a database,
1483 -# which is structuraly different from proposed. The fixed string will
1484 -# be used to do a SELECT operation on database, which should return fields
1485 -# in order specified bellow:
1487 -# username, cryptpw, uid, gid, clearpw, home, maildir, quota, fullname
1489 -# Enabling this option causes ignorance of any other field-related
1490 -# options, excluding default domain.
1492 -# There are two variables, which you can use. Substitution will be made
1493 -# for them, so you can put entered username (local part) and domain name
1494 -# in the right place of your query. These variables are:
1495 -# $(local_part) and $(domain)
1496 +# This is optional. Here you can set the string used to concatenate
1497 +# usename with domain part while expanding the $(username) substitution
1498 +# variable. If it's not set the '@' character is used.
1500 -# If a $(domain) is empty (not given by the remote user) the default domain
1501 -# name is used in its place.
1502 +# USER_DOMAIN_CONCAT @
1504 +##NAME: USER_DOMAIN_SEPARATORS:0
1506 -# This example is a little bit modified adaptation of vmail-sql
1508 +# This is optional. Using this option you can set the set of characters
1509 +# which are treated as separators when splitting entered username into the
1510 +# local part and the domain name. If it's not set the default set @% is used,
1511 +# do the user can log on using user@domain or user%domain.
1513 -# MYSQL_SELECT_CLAUSE SELECT popbox.local_part, \
1514 -# CONCAT('{MD5}', popbox.password_hash), \
1517 -# popbox.clearpw, \
1518 -# CONCAT(domain.path, '/', popbox.mbox_name), \
1522 -# FROM popbox, domain \
1523 -# WHERE popbox.local_part = '$(local_part)' \
1524 -# AND popbox.domain_name = '$(domain)' \
1525 -# AND popbox.domain_name = domain.domain_name
1526 +# USER_DOMAIN_SEPARATORS @%+
1528 +##NAME: MYSQL_SELECT_CLAUSE:0
1530 +# This is optional, MYSQL_SELECT_CLAUSE can be set when you have a database,
1531 +# which is structuraly different from proposed. You can type here your MySQL
1532 +# query, which will be used to fetch user's credentials, and which should
1533 +# return fields in order specified bellow:
1535 +# username, cryptpw, clearpw, uid, gid, home, maildir, quota, fullname
1537 +# Enabling this option causes ignorance of any other field-related options.
1539 +# There also are variables, which you can use. Substitution will be made
1540 +# for them, so you can pass currently entered username and a domain name
1541 +# up to the right place within your query. These variables are:
1542 +# $(local_part) , $(domain) , $(username)
1544 +# If a $(domain) is empty (not given by the remote user) the default domain
1545 +# name is used in its place. $(username) is a local part concatenated with
1546 +# domain name using symbol defined in USER_DOMAIN_CONCAT or '@' if this option
1549 +# MYSQL_SELECT_CLAUSE SELECT \
1550 +# users.username, users.cryptpw, users.clearpw, \
1551 +# domains.uid, domains.gid, \
1552 +# CONCAT_WS('/',domains.path_prefix,users.mailbox_path), \
1553 +# '', domains.quota, '' \
1554 +# FROM users, domains \
1555 +# WHERE domains.domain_name='$(domain)' \
1556 +# AND users.username='$(local_part)' \
1557 +# AND domains.domain_name=users.domain_name
1559 ##NAME: MYSQL_CHPASS_CLAUSE:0
1562 # This is optional, MYSQL_CHPASS_CLAUSE can be set when you have a database,
1563 -# which is structuraly different from proposed. The fixed string will
1564 -# be used to do an UPDATE operation on database. In other words, it is
1565 -# used, when changing password.
1566 +# which is structuraly different from proposed. You can use it to set up
1567 +# a MySQL query used to change user's password.
1569 # There are four variables, which you can use. Substitution will be made
1570 -# for them, so you can put entered username (local part) and domain name
1571 +# for them, so you can put the currently entered username and the domain name
1572 # in the right place of your query. There variables are:
1573 -# $(local_part) , $(domain) , $(newpass) , $(newpass_crypt)
1574 +# $(local_part) , $(domain) , $(username) , $(newpass) , $(newpass_crypt)
1576 # If a $(domain) is empty (not given by the remote user) the default domain
1577 -# name is used in its place.
1578 -# $(newpass) contains plain password
1579 -# $(newpass_crypt) contains its crypted form
1581 -# MYSQL_CHPASS_CLAUSE UPDATE popbox \
1582 -# SET clearpw='$(newpass)', \
1583 -# password_hash='$(newpass_crypt)' \
1584 -# WHERE local_part='$(local_part)' \
1585 -# AND domain_name='$(domain)'
1586 +# name is used in its place. $(newpass) contains plain password and
1587 +# $(newpass_crypt) contains its crypted form.
1589 +# MYSQL_CHPASS_CLAUSE UPDATE users \
1590 +# SET clearpw='$(newpass)', \
1591 +# cryptpw='$(newpass_crypt)' \
1592 +# WHERE username='$(local_part)' \
1593 +# AND domain_name='$(domain)'
1595 +##NAME: MYSQL_ONSUCCESS_CLAUSE:0
1597 +# This is optional, MYSQL_ONSUCCESS_CLAUSE is a trigger - the query is performed
1598 +# each time user has successfuly logged in. [experimental]
1600 +# MYSQL_ONSUCCESS_CLAUSE UPDATE users \
1601 +# SET last_ok=CURRENT_TIMESTAMP \
1602 +# WHERE username='$(local_part)' \
1603 +# AND domain_name='$(domain)'
1605 +##NAME: MYSQL_ONFAIL_CLAUSE:0
1607 +# This is optional, MYSQL_ONFAIL_CLAUSE is a trigger - the query is performed
1608 +# each time user has successfuly logged in. [experimental]
1610 +# MYSQL_ONFAIL_CLAUSE UPDATE users \
1611 +# SET last_fail=CURRENT_TIMESTAMP \
1612 +# WHERE username='$(local_part)' \
1613 +# AND domain_name='$(domain)'