1 diff -ur courier-imap-1.5.3-orig/authlib/README.authmysql.myownquery courier-imap-1.5.3/authlib/README.authmysql.myownquery
2 --- courier-imap-1.5.3-orig/authlib/README.authmysql.myownquery Tue Jan 8 06:01:22 2002
3 +++ courier-imap-1.5.3/authlib/README.authmysql.myownquery Mon Oct 14 01:05:11 2002
8 - Developer Notes for courier-imap-myownquery.patch
10 + Developer Notes and Usage Instructions
14 + courier-imap-authmysql-myownquery
18 + Pawel Wilk <siefca@kernel.pl>
21 - document version: 1.03
30 + .. table of contents..
35 +PART I - Usage Instructions
39 + 2 When will I need it?
42 + 3.1 configuration variables
46 + 3.5 empty default domain name
47 + 3.6 whitespaces in queries
50 + 4.1 corporate mail system
51 + 4.1.1 database structure
52 + 4.1.2 authdaemon configuration
53 + 4.2 virtual mail domains provider
54 + 4.2.1 database structure
55 + 4.2.2 authdaemon configuration
57 +PART II - Developer Notes
59 + 1 Modifications overview
65 + 3.2 typedef size_t (*parsefunc)
70 + 4.3 ParsePlugin_counter
71 + 4.4 ParsePlugin_builder
73 + 4.6 validate_password
77 + 4.10 parse_select_clause
78 + 4.11 parse_chpass_clause
79 + 4.12 auth_mysql_on_trigger
80 + 4.13 auth_mysql_on_pass
81 + 4.14 auth_mysql_checkpassword
88 -1 Modifications overview
94 - 3.2 typedef size_t (*parsefunc)
99 - 4.3 ParsePlugin_counter
100 - 4.4 ParsePlugin_builder
102 - 4.6 validate_password
105 - 4.9 parse_select_clause
106 - 4.10 parse_chpass_clause
113 +//////////////////////////////// PREAMBLE /////////////////////////////////////
115 +This is README document for "myownquery" patch for Courier's Authdaemon.
116 +This document version is 1.36
118 +* The patch, which this document describes is developed for Courier-IMAP
119 + version 1.5.3 and the official patch revision is 2.
121 +* You can download the patch from the FTP server using URI:
123 +ftp://ftp.pld.org.pl/people/siefca/patches/courier/courier-imap-1.5.3-myownquery.patch
125 + it should also be accessible on mirroring servers, which list can be
126 + obtained under: http://www.pld.org.pl/
128 +* To know more about getting Courier see http://www.courier-mta.org/
130 +* This patch, including the documentation, is released under GNU GPL
131 + license terms. You should look at the COPYING file present in
141 +//////////////////////////////// PART I - Usage ///////////////////////////////
143 *-----------------------
146 *-----------------------
148 -Courier-imap-myownquery.patch allows administrator to set own MySQL queries
149 -used by authdaemon to authenticate user (including fetchig credentials) and to
150 -change user's password. It allows to construct SELECT or UPDATE clause in the
151 -configuration file (authmysqlrc) by adding two new configuration variables:
152 -MYSQL_SELECT_CLAUSE and MYSQL_CHPASS_CLAUSE. It may be useful in the mail
153 -environments where there is such a need to have different database structure
154 -and/or tables scheme than expected by authmysql module.
155 +Courier-imap-myownquery's features allow the administrator to set his
156 +own MySQL queries used by authdaemon to authenticate a user (including
157 +fetchig his credentials) and to change the user's password. It allows
158 +one to write a SELECT and UPDATE clause in the configuration file
159 +(authmysqlrc) using the new configuration options. It may be useful in
160 +mail environments where there is a need to have a different database
161 +structure and/or tables scheme than expected by authmysql module.
163 +It also implements a small parsing engine for substitution of
164 +variables which may appear in the SQL clauses, such as a username or a
167 -It also implements a small parsing engine for substitution variables which
168 -may appear in the clauses and are used to put informations like username
169 -or domain into the right place of a query.
171 -This patch was created using `diff -Nur` on courier-imap-1.3.12 source.
175 + *-----------------------
176 + 2 When will I need it?
177 + *-----------------------
179 + o When you already have some MySQL database filled up with the data
180 + and there is no chance to change the whole structure to make it
181 + work with a standard authmysql table. Typical situation is when
183 + data required to authenticate a user is arranged in more than one
186 + o When you have some great idea how to make the database structure
187 + more efficient due to your needs and your requirements.
189 + o When doing something 'by-myself' is in your style and you just want
190 + to create your own database, just to feel the pleasure of doing
191 + something original. :)
197 + *-----------------------
198 + 3 How does it work?
199 + *-----------------------
201 +There are three things which the feature concerns:
203 +- fetching clauses from the configuration file
204 +- doing substitution replacements inside of SQL clauses
205 +- passing prepared query on to the mysql interface funtions
207 +3.1 configuration options
209 +You can apply your own MySQL queries using a set of the configuration
210 +options. The options you'll need to make the authmysql your slave
213 +MYSQL_SERVER (required)
214 +MYSQL_USERNAME (required)
215 +MYSQL_PASSWORD (required)
217 + The server name, userid, and password used to log in.
219 +MYSQL_DATABASE (required)
221 + The name of the MySQL database we will open.
223 +DEFAULT_DOMAIN (optional)
225 + If DEFAULT_DOMAIN is defined, and someone tries to log
226 + in as 'user', we will look up 'user@DEFAULT_DOMAIN'
229 +USER_DOMAIN_SEPARATORS (optional)
231 + This may contain the set of characters used by parsing
232 + routines to split local part of the virtual mailbox
233 + name from the part which describes the domain name. If
234 + it's not defined the set containing @% is assumed, so
235 + the user can enter either: user@domain or user%domain
237 +MYSQL_SELECT_CLAUSE (required)
238 +MYSQL_CHPASS_CLAUSE (required under some circumstances)
240 + These are the major options you should use. See 3.2
241 + section for more info.
243 +ON_PASS_OK_CLAUSE (optional)
244 +ON_PASS_FAIL_CLAUSE (optional)
245 +ON_PASS_CHANGE_CLAUSE (optional)
247 + These are used to do a MySQL query whether user has
248 + passed the authentication verification
249 + (ON_PASS_OK_CLAUSE) or there was the authentication
250 + failure (ON_PASS_FAIL_CLAUSE), or whether user has
251 + changed his password (ON_PASS_CHANGE_CLAUSE).
252 + Query results have no meaning. You can use the same
253 + substitution variables in your query as with
254 + MYSQL_SELECT_CLAUSE. See 3.4 section for more info.
256 +The options which have no effect, and may be safetly left blank are:
272 +The feature adds two configuration options (clauses), which are parsed
273 +first, and then applied as MySQL queries to MySQL interface
274 +routines. These options are: MYSQL_SELECT_CLAUSE and
275 +MYSQL_CHPASS_CLAUSE. After each option a number of spaces and/or tabs
276 +is allowed, and then MySQL query is expected. For better look, your
277 +queries can have line breaks. Each line break should be preceded by
278 +the backslash sign. Look into examples chapter (4) to see how it
279 +should look like. First clause is used to authenticate a user, and the
280 +second to change his password.
282 +You should note that a query identified by MYSQL_SELECT_CLAUSE should
283 +return fixed number (9) of fields and each field should match the
284 +variable expected by authentication routines. These fields are:
287 +* username - which is the currently logged user's username (or the
288 + username with domain if you want it)
290 + cryptpw - which is the user's crypted password
292 + clearpw - which is the user's plaintext password
294 +* uid - which is a numerical UID value used as a process's UID when
295 + accessing the mailbox directory
297 +* gid - as above, but refers to GID
299 +* home - which contains full path to the user's home directory
301 + maildir - which contains the directory name inside the user's home;
302 + treated as INBOX folder when accessing mailbox - if it's
303 + empty then the 'Maildir' string is used
305 +quota - which describes a quota size for the mailbox
307 +fullname - which may contain the user's fullname
309 +(The fields marked by the asterix sign are required and cannot have an
310 + empty results. In case of passwords, at least one of the shown fields
311 + should contain some result.)
313 +So, the typical query clause may start with:
315 +MYSQL_SELECT_CLAUSE SELECT \
321 + users.mailbox_path) \
327 +Note that in this short example we're assuming that we have two tables
328 +(users and domains) and INBOX path is always called 'Maildir' and
329 +we're not using the fullname field (the query will always return an
330 +empty string in its place).
332 +Also note that you may discard one of the password fields if you don't
333 +want to use an authentication mechanism, which needs it. For example,
334 +if you don't want to use MD5-CRAM you may put '' into the place of
335 +clearpw (because, for example you're in paranoid mode and you don't
336 +even want to keep plain passwords in the database:).
340 +Substitutions are strings, which may appear in your query, and which
341 +have a special meaning. You can also call them substitution
342 +variables. If substitution variable is known for a clause context then
343 +it is parsed. If it isn't known the error is generated. In the default
344 +compilation of authmysql module any substitution variable is declared
345 +inside of two substrings - the first is a dollar sign concatenated
346 +with opening parenthesis, and the second is a closing parenthesis
347 +sign. First symbol identifies beginning of a substitution variable,
348 +and the second closes it. The string between the beginning and the
349 +closing symbol is called substitution variable's name.
351 +When, as I said before, the name is known to the parsing routine the
352 +substitution is made and the proper value appears in place of the
353 +substitution variable, while passing on the query for later
356 +Allowed substitution variables:
358 +context: MYSQL_SELECT_CLAUSE, ON_PASS_FAIL_CLAUSE, ON_PASS_OK_CLAUSE,
359 + ON_PASS_CHANGE_CLAUSE
361 +$(local_part) will be replaced by currently verified user's username
362 + (without the domain part)
364 +$(domain) will be replaced by currently verified user's domain
365 + name (if present, or if not present but the
366 + DEFAULT_DOMAIN was used) or by the empty,
368 + string if the domain cannot be obtained
370 +$(username) will be replaced by currently verified user's username
371 + concatenated with the given domain name using
372 + @ symbol -- if the domiain name cannot be
373 + obtained (even looking up DEFAULT_DOMAIN) the
374 + separation sign will not appear and only the
375 + given username will be presented
377 +context: MYSQL_CHPASS_CLAUSE
379 +$(local_part) will be replaced by currently verified user's username
380 + (without the domain part)
382 +$(domain) will be replaced by currently verified user's domain
383 + name (if present, or if not present but the
384 + DEFAULT_DOMAIN was used) or by the empty, zero-length
385 + string if the domain cannot be obtained
387 +$(username) will be replaced by currently verified user's username
388 + concatenated with the given domain name using
389 + @ symbol -- if the domiain name cannot be
390 + obtained (even by looking up DEFAULT_DOMAIN)
391 + the separation sign will not appear and only
392 + the given username will be presented
394 +$(newpass) will be replaced by currently authenticated user's
395 + new password to set up (plaintext password)
397 +$(newpass_crypt) will be replaced by currently authenticated user's
398 + new password to set up (MD5 form created from
399 + entered plain form)
403 +Triggers are MySQL queries, which are performed depending on
404 +authentication state. Currently, there are three triggers which you
405 +may use. First is called ON_PASS_OK_CLAUSE and it is performed when
406 +the authentication succeedes. The second is called
407 +ON_PASS_FAIL_CLAUSE and has the reverse meaning. The third, which name
408 +is ON_PASS_CHANGE_CLAUSE is performed whenever user has changed his
411 +You can declare triggers in the authmysqlrc configuration file. They
412 +can be used to arrange some logging facility in the database or just
413 +to keep last times of the successful/failed login tries. The typical
414 +trigger, which puts last login date into the users' table can look
417 +ON_PASS_OK_CLAUSE UPDATE users SET last_login=CURRENT_TIMESTAMP \
418 + WHERE username='$(username)';
420 +or, if you would like to know about last login failure for users you can try:
422 +ON_PASS_FAIL_CLAUSE UPDATE users SET last_bad_login=CURRENT_TIMESTAMP \
423 + WHERE username='$(username)';
425 +and/or, if you want to know last password changes you can use:
427 +ON_PASS_CHANGE_CLAUSE UPDATE users SET pw_change=CURRENT_TIMESTAMP \
428 + WHERE username='$(username)';
430 +Note, that YOU CAN use the triggers even if you aren't using
431 +MYSQL_SELECT_CLAUSE. Also note, that if the entered username
432 +doesn't match any real user ON_PASS_FAIL_CLAUSE will be simply
433 +discarded. To watch brute force attacs against known usernames
434 +you have to use log files. ;]
436 +3.5 empty default domain name
438 +Sometimes happens, that you want to allow user to log in without
439 +having a domain name entered and you expect it will be treated as an
440 +empty string, neither an error, nor default domain. In that case you
441 +should leave DEFAULT_DOMAIN option unset in authmysqlrc file and your
442 +database should have empty (not NULL) string fields for users without
443 +the domain name specified.
445 +3.6 whitespaces in queries
447 +In a few examples, here and in authmysqlrc file, I used to put many
448 +whitespaces and tabs to make the examples more clear for reader.
449 +However, it is recommended to not torture authdaemon's parser in
450 +that way and to remove unnecessary characters. ;]
452 +For example, the clause:
454 +MYSQL_CHPASS_CLAUSE UPDATE \
456 + SET clearpw='$(newpass)', \
457 + cryptpw='$(newpass_crypt)' \
458 + WHERE username='$(local_part)' \
459 + AND domain_name='$(domain)'
461 +can be safetly rewritten as:
463 +MYSQL_CHPASS_CLAUSE UPDATE users \
464 +SET clearpw='$(newpass)', cryptpw='$(newpass_crypt)' \
465 +WHERE username='$(local_part)' AND domain_name='$(domain)'
472 + *-----------------------
473 + 4 Examples of usage
474 + *-----------------------
476 +The "ownquery" feature gives you possibility to adapt an
477 +authentication query to the database. So the first thing you have to
478 +do is to design the database structure you need, whithout being
479 +grieved at what structure authentication routines like. You have to
480 +take care about four essential things:
484 + o The users' data in the database
486 + o The proper directories for keeping virtual mailboxes and a system
487 + user which can read and write them
489 + o The proper MySQL queries in your authmysqlrc configuration file
491 +4.1 corporate mail system
493 +This example is concerned about a corporate mail system with a small
494 +ammount of served virtual domains. The database scheme was derived
495 +from tpop3d documentation and modified a bit.
497 +4.1.1 database structure
499 +Our goal here is to separate the data responsible for keeping mailbox
500 +credentials from the data describing domains.
502 +Let's create some tables for our example, filled up with an example
507 +purpose: associates virtual domain with domain name and informations
508 + necessary to access mailboxes withing the domain
510 +fields: domain_name - fully qualified domain name
511 + path_prefix - absolute pathname which points to
512 + a directory where domain's mailboxes
514 + quota - default quota for each mailbox
515 + uid - UID used to work on mailboxes
516 + gid - GID used to work on mailboxes
518 + +----------------+-------------+-----+-----+----------+
519 + | domain_name | path_prefix | uid | gid | quota |
520 + +----------------+-------------+-----+-----+----------+
521 + | exampledom.com | /var/mail/x | 555 | 555 | 10000000 |
522 + | pld.org.pl | /var/mail/p | 556 | 556 | 20000000 |
523 + | pld.net.pl | /var/mail/p | 556 | 556 | 20000000 |
524 + +----------------+-------------+-----+-----+----------+
528 +purpose: associates virtual mailbox with user and domain name,
529 + and with informations necessary to access mailbox
531 +fields: username - user login name (mailbox name)
532 + domain_name - fully qualified domain name
533 + mailbox_path - relative pathname for mailbox
534 + (will be appended to the path_prefix
535 + from domain_auth table to specify
536 + user's mailbox location)
537 + cryptpw - crypted password
538 + plainpw - plaintext password
540 + +----------+----------------+--------------+------------+--------+
541 + | username | domain_name | mailbox_path | cryptpw | plainpw |
542 + +----------+----------------+--------------+-----------+---------+
543 + | siefca | pld.org.pl | s/siefca | $1$fs45.. | dupa.8 |
544 + | siefca | pld.net.pl | s/siefca | $1$fs45.. | dupa.8 |
545 + | f00bar | exampledom.com | foobar | $1$g44w.. | secret |
546 + +----------+----------------+--------------+-----------+---------+
548 +Using MySQL monitor you can create these tables entering CREATE
549 +sequences. Be sure to connect to the database using administrative
550 +MySQL account (usualy: mysql -u mysql -p).
552 +--------------------- cut here
554 +# Create the database called vmail.
556 +CREATE database vmail;
558 +# Create an example MySQL user, which can read, write and delete data
559 +# from vmail database. Username: vuser Password: secret_password
561 +GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
563 + IDENTIFIED BY 'secret_password';
567 +# Create the tables.
571 +CREATE TABLE domains (
572 + domain_name char(255) DEFAULT '',
573 + path_prefix char(255) DEFAULT '' NOT NULL,
574 + uid int(10) unsigned DEFAULT '15000' NOT NULL,
575 + gid int(10) unsigned DEFAULT '15000' NOT NULL,
576 + quota char(255) DEFAULT '2000000' NOT NULL,
577 + KEY domain_name (domain_name(255))
580 +CREATE TABLE users (
581 + username char(128) DEFAULT '' NOT NULL,
582 + domain_name char(255) DEFAULT '',
583 + mailbox_path char(255) DEFAULT '' NOT NULL,
584 + cryptpw char(128) DEFAULT '' NOT NULL,
585 + clearpw char(128) DEFAULT '' NOT NULL,
586 + KEY username (username(128))
589 +# Create an example virtual domain entry
590 +# name : exampledom.com
593 +# path : /var/mail/x
594 +# quota : 10 Megs per mailbox
596 +INSERT INTO domains VALUES ('exampledom.com', '/var/mail/x', 555, 555,
599 +# Create an example virtual user entry
601 +# domain name : exampledom.com
602 +# cryptpw : $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
604 +# mailbox path : s/siefca
606 +INSERT INTO users VALUES ('siefca', 'exampledom.com', 's/siefca',
607 + '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
610 +--------------------- cut here
612 +Note: If you would like to have your passwords more safe, then just
613 + omit the clearpw column and put '' into the config-query in
614 + its place while doing SELECT on a database. But be ware -
615 + you'll be unable to use authentication methods which needs it,
618 +4.1.2 authdaemon configuration
620 +When our database is ready we can set up the configuration. :-) Go to
621 +authmysqlrc file and edit it.
623 +At the beginning we should take care about general informations, which
624 +are identifying our database:
626 +MYSQL_SERVER localhost
627 +MYSQL_USERNAME vuser
628 +MYSQL_PASSWORD secret_password
629 +MYSQL_DATABASE vmail
631 +Then we should add a clause responsible for authenticating user and
632 +fetching credentials:
634 +DEFAULT_DOMAIN exampledom.com
636 +MYSQL_SELECT_CLAUSE SELECT \
637 + users.username, users.cryptpw, users.clearpw, \
638 + domains.uid, domains.gid, \
639 + CONCAT_WS('/',domains.path_prefix,users.mailbox_path), \
640 + '', domains.quota, '' \
641 + FROM users, domains \
642 + WHERE domains.domain_name='$(domain)' \
643 + AND users.username='$(local_part)' \
644 + AND domains.domain_name=users.domain_name
647 +Note the '' in the place of field which tells where user's INBOX
648 +resides and in place of realname field. You should use '' if you want
649 +to put an empty value as a query result for some field.
651 +We also should add some configuration for changing user's password:
653 +MYSQL_CHPASS_CLAUSE UPDATE \
655 + SET clearpw='$(newpass)', \
656 + cryptpw='$(newpass_crypt)' \
657 + WHERE username='$(local_part)' \
658 + AND domain_name='$(domain)'
661 +Create a system user/group and a proper directory structure. In our example:
663 +groupadd -g 555 xdomain
664 +useradd -u 555 -g 555 xdomain
665 +mkdir -p /var/mail/x/s/siefca
666 +chmod -R 0770 /var/mail/x
667 +maildirmake /var/mail/x/s/siefca/Maildir
668 +chown -R xdomain.xdomain /var/mail/x
670 +Now, restart the authdaemon and see if it works. Try: telnet 0 pop3
677 +You should get Ok response. ;)
679 +4.2 virtual mail domains provider
681 +Let's consider more complicated database scheme, where there is a need
682 +to associate a lot of information with the domain name, including
683 +registrant information, owner, etc. That implies data separation
684 +between domain name, user and domain additional informations (which
685 +are unwanted when authentication process takes place). By proper data
686 +separation I mean avoiding unwanted redundancy in the database.
688 +Currently applied example doesn't care about the update password
689 +problem. This is due to current abilities of MySQL and authdaemon
690 +(authmysql). MySQL doesn't support subsequent SELECTs on UPDATE
691 +operation, and authmysql doesn't supports batched queries at the
694 +4.2.1 database structure
698 +purpose: associates domain_id with domain name
700 +fields: domain_name - fully qualified domain name
701 + domain_id - domain identifier
703 + +----------------+-----------+
704 + | domain_name | domain_id |
705 + +----------------+-----------+
706 + | exampledom.com | 1 |
709 + | foobare.net.uk | 3 |
710 + +----------------+-----------+
712 +Note, that for pld.org.pl and pld.net.pl the domain identifiers are
713 +the same. We can create a domain aliases in such a way. :)
717 +purpose: associates domain_id with authentication credentials
718 + which are common for all users in the virtual domain
720 +fields: domain_id - domain identifier
721 + path_prefix - absolute pathname which points to
722 + a directory where domain's mailboxes
724 + quota - default quota for each mailbox
725 + uid - UID used to work on mailboxes
726 + gid - GID used to work on mailboxes
728 + +------------+---------------+--------+-------+-------+
729 + | domain_id | path_prefix | quota | uid | gid |
730 + +------------+---------------+--------+-------+-------+
731 + | 1 | /var/mail/ex | 100000 | 15000 | 15000 |
732 + | 2 | /var/mail/pld | 555500 | 15001 | 15000 |
733 + | 3 | /home/f0/mail | 8000 | 15002 | 15000 |
734 + +------------+---------------+--------+-------+-------+
738 +purpose: associates domain_id with additional informations
740 +fields: domain_id - domain identifier
741 + registrant_id - registrant identifier
742 + nic_handle - NIC handle
743 + owner_id - domain's owner identifier
744 + expires - domain's expiration date
746 + +------------+---------------+------------+----------+---------+
747 + | domain_id | registrant_id | nic_handle | owner_id | expires |
748 + +------------+---------------+------------+----------+---------+
750 + (we don't need to say anything more about this table indeed)
754 +purpose: associates users' identifiers with domains' identifiers
755 + and infers the credentials for various virtual mailboxes
757 +fields: username - user's login name
758 + domain_id - domain identifier
759 + cryptpw - crypted password
760 + plainpw - plaintext password
761 + quota - user's mailbox quota
762 + (will override quota value set for
763 + the whole virtual domain)
764 + path - relative pathname for mailbox
765 + (will be appended to the path_prefix
766 + from domain_auth table to specify
767 + user's mailbox location)
769 + +------------+-----------+----------+-----------+-------+------------+
770 + | username | domain_id | cryptpw | plainpw | quota | path |
771 + +------------+-----------+----------+-----------+-------+------------+
772 + | foobar | 1 | $1$hlIeE | dupa.8 | NULL | f/o/foobar |
773 + | breeder | 2 | $1$TWsdf | ziarno128 | 77777 | brd |
774 + +------------+-----------+----------+-----------+-------+------------+
776 + (you can add a realname column here, it doesn't fit to my terminal window:)
778 +--------------------- cut here
780 +# Create the database called vmail.
782 +CREATE database vmail;
784 +# Create an example MySQL user, which can read, write and delete data
785 +# from vmail database. Username: vuser Password: secret_password
787 +GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
789 + IDENTIFIED BY 'secret_password';
793 +# Create the tables.
797 +CREATE TABLE domain_names (
798 + domain_id int(10) unsigned NOT NULL,
799 + domain_name char(255) DEFAULT '' NOT NULL,
800 + KEY domain_name (domain_name(255))
803 +CREATE TABLE domain_auth (
804 + domain_id int(10) unsigned DEFAULT 1 NOT NULL,
805 + uid int(10) unsigned DEFAULT '15000' NOT NULL,
806 + gid int(10) unsigned DEFAULT '15000' NOT NULL,
807 + path_prefix char(255) DEFAULT '' NOT NULL,
808 + quota char(255) DEFAULT '20000000' NOT NULL,
809 + KEY domain_id (domain_id)
812 +CREATE TABLE users (
813 + username char(128) DEFAULT '' NOT NULL,
814 + domain_id int(10) unsigned DEFAULT 1 NOT NULL,
815 + cryptpw char(128) DEFAULT '' NOT NULL,
816 + plainpw char(128) DEFAULT '' NOT NULL,
817 + name char(128) DEFAULT '' NOT NULL,
819 + path char(255) DEFAULT '' NOT NULL,
820 + KEY username (username(128))
823 +# Create an example virtual domain entry
825 +# name : exampledom.com
828 +# path : /var/mail/example
829 +# quota : 20 Megs per mailbox
831 +INSERT INTO domain_names VALUES (1, 'exampledom.com');
832 +INSERT INTO domain_auth VALUES (1, '15000', '15000', '/var/mail/example',
835 +# Create an example virtual user entry
837 +# domain id : 1 (points to exampledom.com)
838 +# cryptpw : $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
841 +# quota : NULL (we want it to be fetched from domain_auth table)
842 +# mailbox path : s/i/siefca
844 +INSERT INTO users VALUES ('siefca', 1, '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
845 + 'dupa.8', 'Pawel Wilk', NULL, 's/i/siefca');
847 +--------------------- cut here
849 +Ok, we've done what we need. Don't forget to create system user with
850 +UID and GID set to 15000, and a directory containing mailboxes (in
851 +this case: /var/mail/example) owned by system user I've mentioned
852 +above. There is also necessary to create Maildir folder structure for
853 +our user inside the virtual domain directory - you can configure your
854 +MTA agent to do such thing when first message arrive or use
855 +maildirmake tool, which comes with Courier-IMAP.
858 +4.2.2 authdaemon configuration
860 +DEFAULT_DOMAIN exampledom.com
862 +MYSQL_SELECT_CLAUSE SELECT \
868 + CONCAT_WS('/',domain_auth.path_prefix,users.path), \
870 + IFNULL(users.quota, domain_auth.quota), \
872 + FROM users, domain_names, domain_auth \
873 + WHERE domain_names.domain_name='$(domain)' \
874 + AND users.username='$(local_part)' \
875 + AND domain_names.domain_id=users.domain_id \
876 + AND domain_names.domain_id=domain_auth.domain_id
886 +/////////////////////////// PART II - Developer Notes /////////////////////////
888 *-----------------------
889 1 Modifications overview
890 *-----------------------
892 -Modified files: authmysqllib.c authmysqlrc
893 +Modified files: authmysqllib.c authmysql.c authmysql.h authmysqlrc
895 Each modified set of instructions is marked by my e-mail address:
898 -Changes in the current source code are related to:
899 +Changes in the source code are related to:
901 -- sections where the queries are constructed
902 +- sections where the queries are constructed [authmysqllib.c]
903 (including memory allocation for the buffers)
905 when MYSQL_SELECT_CLAUSE or MYSQL_CHPASS_CLAUSE is
907 passing over current memory allocation and query construction
910 -- section where the configuration file is read
911 +- section where the configuration file is read [authmysqllib.c]
913 i've had to modify read_env() function to allow line breaks
914 - - now each sequence of the backslash as a first character and
915 + -- now each sequence of the backslash as a first character and
916 newline as the second is replaced by two whitespaces while
917 putting into the buffer
919 -- sections where the query is constructed
920 + i've also added USER_DOMAIN_SEPARATORS configuration option --
921 + it is used by get_localpart(), get_domain() and get_username()
922 + functions, which are described below
924 +- sections where the query is constructed [authmysqllib.c]
926 selection is made, depending on configuration variables which
927 - are set or not - if own query is used
928 + are set or not -- if own query is used
930 +- sections where the user is authenticated against the authinfo [authmysql.c]
932 + i've detached a part of code responsible for authentication
933 + against crypted and plain password -- now it is in stub
934 + function called auth_mysql_checkpassword() -- due to obtain
935 + more clean code in auth_mysql_login() and
936 + auth_mysql_changepw() around trigger calling functions
940 @@ -123,14 +908,20 @@
942 These definitions allows to change substitution marks in an easy way.
943 SV_BEGIN_MARK refers to sequence of characters treated as a prefix of
944 -each substitution variable and SV_END_MARK refers to string which is
945 -a closing suffix. If the expected substitution variable is called
946 +each substitution variable and SV_END_MARK refers to string which is a
947 +closing suffix. If the expected substitution variable is called
948 'local_part' (without apostrophes) then '$(local_part)' is a valid
949 -string representation for SV_BEGIN_MARK set to "$(" and SV_END_MARK to ")".
950 -MAX_SUBSTITUTION_LEN defines maximal length of a substitution variable's
952 +string representation for SV_BEGIN_MARK set to "$(" and SV_END_MARK to
953 +")". MAX_SUBSTITUTION_LEN defines maximal length of a substitution
954 +variable's identifier (name).
956 +The last two definitions (SV_BEGIN_LEN and SV_END_LEN) are just for
957 +code simplification.
959 +#define DEF_SEPARATORS_SET "@%"
961 -The last two definitions are just for code simplification.
962 +The DEF_SEPARATORS_SET directive defines the set of characters, which
963 +are treated as separators when splitting local part from the domain.
967 @@ -152,10 +943,10 @@
971 -This structure holds information needed by parsing routines.
972 -Using var_data array you may specify a set of string substitutions
973 -which should be done while parsing a query. Last element in array
974 -should have all fields set to zero (null).
975 +This structure holds information needed by parsing routines. Using
976 +var_data array you may specify a set of string substitutions which
977 +should be done while parsing a query. Last element in array should
978 +have all fields set to zero (null).
980 name field - should contain substituted variable name
981 value - should contain string which replaces it
985 explanation: size is used to increase speed of calculation proccess
986 - value_length is used to cache length of a value during the
987 - parsing subroutines - it helps when substitution variable
988 - occures more than once within the query
989 + value_length is used to cache length of a value during
990 + the parsing subroutines - it helps when substitution
991 + variable occures more than once within the query
995 @@ -177,18 +968,19 @@
998 In this example we've declared that $(some) in the query should be
999 -replaced by 'replacement' text, and replacement for $(anotha) will
1000 -be defined in the code before passing on the array pointer to
1001 -the paring function.
1002 +replaced by 'replacement' text, and replacement for $(anotha) will be
1003 +set later in the code, before passing on the array pointer to the
1004 +general parsing function.
1007 3.2 typedef size_t (*parsefunc)
1009 typedef int (*parsefunc)(const char *, size_t, void *);
1011 -This type definition refers to the function pointer, which is used
1012 -to pass plugin functions into the core parsing subroutine. This definition
1013 -is included to simplify the declaration of the parse_core() function.
1014 +This type definition refers to the function pointer, which is used to
1015 +pass plugin functions into the core parsing subroutine. This
1016 +definition is included to simplify the declaration of the parse_core()
1021 @@ -230,6 +1022,10 @@
1022 structure of var_data type, which contains variable definition
1023 of a given name. It returns NULL on error or failure.
1027 + authlib/authmysqllib.c
1032 @@ -285,6 +1081,11 @@
1034 This function returns -1 if an error has occured and 0 if
1035 everything went good.
1039 + authlib/authmysqllib.c
1042 4.3 ParsePlugin_counter
1044 @@ -314,6 +1115,11 @@
1045 This function returns the variable size or -1 if an error
1046 has occured, 0 if everything went good.
1050 + authlib/authmysqllib.c
1053 4.4 ParsePlugin_builder
1056 @@ -333,7 +1139,7 @@
1057 type pointer and refers to the (char *) pointer variable.
1058 After each call it shifts the value of pointer variable (char *)
1059 incrementing it by len bytes. Be careful when using this function
1060 - - its changes the given pointer value. Always operate on an
1061 + - it changes the given pointer value. Always operate on an
1062 additional pointer type variable when passing it as the third
1065 @@ -342,6 +1148,10 @@
1066 This function returns the variable size or -1 if an error
1067 has occured, 0 if everything went good.
1071 + authlib/authmysqllib.c
1076 @@ -353,7 +1163,7 @@
1080 - This function parses the string pointed with source according to the
1081 + This function parses the string pointed to by source according to the
1082 replacement instructions set in var_data array, which is passed with
1083 its pointer vdt. It produces changed string located in newly allocated
1085 @@ -377,6 +1187,10 @@
1086 Function returns pointer to the result buffer or NULL
1087 if an error has occured.
1091 + authlib/authmysqllib.c
1095 This function allocates some amount of memory using standard
1096 @@ -405,6 +1219,10 @@
1097 It returns a pointer to the static buffer which contains
1098 validated password string or NULL if an error has occured.
1102 + authlib/authmysqllib.c
1107 @@ -414,20 +1232,28 @@
1111 - static const char *get_localpart (const char *username);
1112 + static const char *get_localpart (const char *username,
1113 + const char *separators);
1117 This function detaches local part of an e-mail address
1118 from string pointed with username and puts it to the
1119 buffer of the fixed length. All necessary cleaning is
1120 - made on the result string.
1121 + made on the result string. String pointed with separators
1122 + refers to a set of characters, which are treated as
1123 + separation signs between local part and a domain.
1127 Pointer to the static buffer containing local part or
1128 NULL if there was some error.
1132 + authlib/authmysqllib.c
1138 @@ -438,24 +1264,67 @@
1141 static const char *get_domain (const char *username,
1142 - const char *defdomain);
1143 + const char *defdomain,
1144 + const char *separators);
1148 This function detaches domain part of an e-mail address
1149 from string pointed with username and puts it to the
1150 buffer of the fixed length. All necessary cleaning is
1151 - made on the result string. If function cannot find domain
1152 - part in the string the string pointed by defdomain is
1154 + made on the result string. If the function cannot find a domain
1155 + part in the string then the string pointed to by defdomain is
1156 + used instead. If this function cannot find a domain part
1157 + as well as it cannot obtain the default domain (it's empty string
1158 + or the defdomain pointer is NULL) the returned result string is an
1159 + empty string. The string pointed with separators refers to a set
1160 + of characters, which are treated as separation signs between local
1161 + part and a domain.
1165 Pointer to the static buffer containing domain name or
1166 NULL if there was some error.
1170 + authlib/authmysqllib.c
1172 -4.9 parse_select_clause
1182 + static const char *get_username (const char *username,
1183 + const char *domainname);
1187 + This function concatenates the localpart with a domain name
1188 + using the @ symbol. If the domain is empty or NULL the result
1189 + comes without binding symbol.
1193 + Pointer to the static buffer containing output string or
1194 + NULL if there was some error.
1198 + authlib/authmysqllib.c
1202 + This function does not any string cleaning, nor default domain
1203 + checking. It is designed to work on results of get_localpart() and
1207 +4.10 parse_select_clause
1211 @@ -465,23 +1334,34 @@
1213 static char *parse_select_clause (const char *clause,
1214 const char *username,
1215 - const char *defdomain);
1216 + const char *defdomain
1217 + const char *separators_set);
1221 This function is a simple wrapper to the parse_string()
1222 function. It parses a query pointed by caluse. username
1223 - and defdomain strings are used to replace corresponding
1224 - substitution strings if present in the query: $(local_part)
1226 + and defdomain strings are used to create corresponding
1227 + substitution strings if present in the query: $(local_part),
1228 + $(domain), and $(username). Note, that username parameter
1229 + may contain 'user@domain' form here, so the call to
1230 + get_localpart() and get_domain() function will split it
1231 + into two parts, then calling get_username() function will join
1232 + it again using the @ symbol. This trick is wanted as long as
1233 + we'd like to have possibility to split the local part from the
1234 + domain by using dynamic symbols set. The separators_set is
1235 + passed to get_localpart() and get_domain() invocations.
1240 Same as parse_string().
1244 + authlib/authmysqllib.c
1246 -4.10 parse_chpass_clause
1248 +4.11 parse_chpass_clause
1252 @@ -492,6 +1372,7 @@
1253 static char *parse_chpass_clause (const char *clause,
1254 const char *username,
1255 const char *defdomain,
1256 + const char *separators_set,
1257 const char *newpass,
1258 const char *newpass_crypt);
1260 @@ -502,12 +1383,115 @@
1261 defdomain, newpass and newpass_crypt strings are used to
1262 replace corresponding substitution strings if present in
1263 the query: $(local_part), $(domain), $(newpass),
1265 + $(newpass_crypt). The separators_set is passed to
1266 + get_localpart() and get_domain() functions as described in the
1267 + entry for parse_select_clause().
1271 Same as parse_string().
1275 + authlib/authmysqllib.c
1278 +4.12 auth_mysql_on_trigger
1282 + auth_mysql_on_trigger
1286 + int auth_mysql_on_trigger (const char *clause_name,
1287 + const char *username);
1291 + This function is responsible for calling out the MySQL queries
1292 + depending on which authentication state was reached.
1294 + The clause_name should contain the name of a clause, which can
1295 + be found in the configuration file, and the username is simply
1296 + the string used as username (including the domain if entered).
1298 + This function reads DEFAULT_DOMAIN and USER_DOMAIN_SEPARATORS
1299 + from the configuration file using read_env(), then it uses
1300 + parse_select_clause() to parse the query obtained using
1301 + read_env(clause_name), and then it calls querying subroutines
1302 + to perform the action.
1306 + This function returns 0 on success and -1 on failure. The
1307 + query results are simply discarded. If a trigger's clause is
1308 + not defined in the configuration file the 1 is returned and
1309 + function silently ends its work.
1313 + authlib/authmysqllib.c
1316 +4.13 auth_mysql_on_pass
1320 + auth_mysql_on_pass
1324 + static int auth_mysql_on_pass(const char *clause,
1325 + struct authmysqluserinfo *authinfo);
1329 + This function is responsible for invoking trigger MySQL
1330 + clauses whenever user is authenticated or not.
1331 + This is a stub function, which calls auth_mysql_on_trigger().
1332 + Firstly, it does a simple checks in authinfo structure --
1333 + it looks for a valid username field. If username is not set
1334 + or it's empty the fuction does nothing. This behavior follows
1335 + the need, that if there wasn't any valid username then we
1336 + shouldn't touch the database.
1340 + It returns 0 in case everything went fine, -1 if there was some
1345 + authlib/authmysql.c
1348 +4.14 auth_mysql_checkpassword
1351 + auth_mysql_checkpassword
1355 + static int auth_mysql_checkpassword(struct authmysqluserinfo *authinfo, const char *pass);
1359 + This function is a wrapper, which checks user's entered
1360 + password against one found in a database. Function tries to
1361 + authenticate user against his crypted password and if it's
1362 + impossible it tries the plain form -- by impossible we mean
1363 + the authinfo->cryptpw set to NULL.
1367 + Function returns 0 if the password was correct, -1 if user
1368 + applied bad password of the username wasn't found.
1372 + authlib/authmysql.c
1377 @@ -516,15 +1500,10 @@
1379 *------------------------
1381 -- solve problem with fixed buffer length of local part and the domain part
1382 - strings after split (problem?)
1383 - allow admin to set a group name instead of numerical group id
1384 - allow admin to set a username instead of numerical user id
1388 - - MYSQL_PRESELECT_CLAUSE (query which comes before MYSQL_SELECT_CLAUSE)
1389 - - MYSQL_POSTSELECT_CLAUSE (query which comes after MYSQL_SELECT_CLAUSE)
1390 +- put the parsing routines into separate files to make possible of sharing it
1391 + by more authentication modules
1395 @@ -534,10 +1513,20 @@
1397 *------------------------
1399 -At the beginning this patch was messy indeed. :> I would like to thank
1400 -Sam Varshavchik for pointing me a lot how to make it more fast and solid.
1401 -I would also thank Philip Hazel, Chris Lightfoot and Mike Bremford which
1402 -by their software capabilities inspired me to write it.
1403 +At the beginning the patch was messy indeed. :> I would like to thank:
1405 ----------------------------------------------------------------------------
1407 + for pointing me a lot, how to make it more fast and solid
1409 +Philip Hazel, Chris Lightfoot, Mike Bremford
1410 + which by their software's capabilities inspired me to write it
1413 + which remainded me to make the documentation more friendly for
1414 + those who are not programmers and just want to use it
1417 + for reviewing this document just before it was published
1419 +---------------------------------------------------------------------------
1420 + Any comments and suggestions are welcome.
1421 diff -ur courier-imap-1.5.3-orig/authlib/authmysql.c courier-imap-1.5.3/authlib/authmysql.c
1422 --- courier-imap-1.5.3-orig/authlib/authmysql.c Sun Jun 24 01:42:05 2001
1423 +++ courier-imap-1.5.3/authlib/authmysql.c Sun Oct 13 23:12:06 2002
1425 #include "authmysql.h"
1426 #include "authstaticlist.h"
1428 -static const char rcsid[]="$Id$";
1429 +static const char rcsid[]="$Id$";
1431 +/* siefca@pld.org.pl */
1432 +static int auth_mysql_on_pass(const char *clause, struct authmysqluserinfo *authinfo)
1434 + if (authinfo->username && *(authinfo->username)!='\0') /* do it if user was found */
1436 + if (auth_mysql_on_trigger(clause, authinfo->username))
1438 + return (-1); /* MySQL error or something critical.. */
1445 +/* siefca@pld.org.pl */
1446 +static int auth_mysql_checkpassword(struct authmysqluserinfo *authinfo,
1449 + if (authinfo->cryptpw)
1451 + if (authcheckpassword(pass,authinfo->cryptpw))
1453 + return (-1); /* User/Password not found. */
1456 + else if (authinfo->clearpw)
1458 + if (strcmp(pass, authinfo->clearpw))
1471 static char *auth_mysql_login(const char *service, char *authdata,
1477 - if (authinfo->cryptpw)
1478 + /* siefca@pld.org.pl */
1479 + if (auth_mysql_checkpassword(authinfo,pass))
1481 - if (authcheckpassword(pass,authinfo->cryptpw))
1483 + if (auth_mysql_on_pass("ON_PASS_FAIL_CLAUSE", authinfo))
1487 - return (0); /* User/Password not found. */
1490 - else if (authinfo->clearpw)
1492 - if (strcmp(pass, authinfo->clearpw))
1503 - return (0); /* Username not found */
1504 + if (auth_mysql_on_pass("ON_PASS_OK_CLAUSE", authinfo))
1511 if (callback_func == 0)
1512 @@ -149,26 +186,23 @@
1516 - if (authinfo->cryptpw)
1517 + /* siefca@pld.org.pl */
1518 + if (auth_mysql_checkpassword(authinfo, pass))
1520 - if (authcheckpassword(pass,authinfo->cryptpw))
1523 - return (-1); /* User/Password not found. */
1526 - else if (authinfo->clearpw)
1528 - if (strcmp(pass, authinfo->clearpw))
1530 + if (auth_mysql_on_pass("ON_PASS_FAIL_CLAUSE", authinfo))
1543 + if (auth_mysql_on_pass("ON_PASS_OK_CLAUSE", authinfo))
1550 if (auth_mysql_setpass(user, newpass))
1551 @@ -176,6 +210,14 @@
1556 + /* siefca@pld.org.pl */
1557 + if (auth_mysql_on_pass("ON_PASS_CHANGE_CLAUSE", authinfo))
1569 char *auth_mysql(const char *service, const char *authtype, char *authdata,
1572 void (*callback_func)(struct authinfo *, void *), void *callback_arg)
1574 if (strcmp(authtype, AUTHTYPE_LOGIN) == 0)
1575 diff -ur courier-imap-1.5.3-orig/authlib/authmysql.h courier-imap-1.5.3/authlib/authmysql.h
1576 --- courier-imap-1.5.3-orig/authlib/authmysql.h Mon Aug 6 05:12:39 2001
1577 +++ courier-imap-1.5.3/authlib/authmysql.h Sat Sep 28 00:01:07 2002
1581 extern struct authmysqluserinfo *auth_mysql_getuserinfo(const char *);
1582 +extern int auth_mysql_on_trigger (const char *clause_name, const char *username);
1583 extern void auth_mysql_cleanup();
1585 extern int auth_mysql_setpass(const char *, const char *);
1586 diff -ur courier-imap-1.5.3-orig/authlib/authmysqllib.c courier-imap-1.5.3/authlib/authmysqllib.c
1587 --- courier-imap-1.5.3-orig/authlib/authmysqllib.c Wed May 29 19:24:03 2002
1588 +++ courier-imap-1.5.3/authlib/authmysqllib.c Sun Oct 13 22:58:09 2002
1590 #define SV_END_MARK ")"
1591 #define SV_BEGIN_LEN ((sizeof(SV_BEGIN_MARK))-1)
1592 #define SV_END_LEN ((sizeof(SV_END_MARK))-1)
1593 +#define DEF_SEPARATORS_SET "@%"
1596 static const char rcsid[]="$Id$";
1602 - "\n", len, begin);
1603 + "\n", (int) len, begin);
1607 @@ -364,14 +366,14 @@
1608 t_size = t_end-t_begin+1;/* text field length */
1611 - if ( (outfn (t_begin, t_size, result)) == -1 )
1612 + if ( (outfn (t_begin, t_size, result)))
1615 /* work on variable */
1616 v_ptr = get_variable (v_begin, v_size, vdt);
1617 if (!v_ptr) return -1;
1619 - if ( (outfn (v_ptr->value, v_ptr->value_length, result)) == -1 )
1620 + if ( (outfn (v_ptr->value, v_ptr->value_length, result)))
1626 /* work on last part of text if any */
1628 - if ( (outfn (q, strlen(q), result)) == -1 )
1629 + if ( (outfn (q, strlen(q), result)))
1633 @@ -426,21 +428,45 @@
1642 /* siefca@pld.org.pl */
1643 -static const char *get_localpart (const char *username)
1644 +static const char *get_username (const char *username, const char *domainname)
1648 +static char username_buf[400];
1650 + if (!username || *username == '\0') return NULL;
1651 + u_len=strlen(username);
1652 + if (( u_len + (domainname ? strlen(domainname) : 0)
1653 + ) > 397) return NULL;
1655 + strcpy (username_buf, username);
1656 + if (domainname && *domainname != '\0')
1658 + p = username_buf + u_len;
1660 + strcpy(p, domainname);
1663 + return (username_buf);
1666 +/* siefca@pld.org.pl */
1667 +static const char *get_localpart (const char *username, const char *separators)
1670 const char *l_end, *p;
1672 static char localpart_buf[130];
1674 - if (!username || *username == '\0') return NULL;
1675 + if (!username || *username == '\0' ||
1676 + !separators || *separators == '\0') return NULL;
1678 - p = strchr(username,'@');
1679 + p = strpbrk (username, separators);
1682 if ((p-username) > 128)
1683 @@ -469,21 +495,27 @@
1686 /* siefca@pld.org.pl */
1687 -static const char *get_domain (const char *username, const char *defdomain)
1688 +static const char *get_domain (const char *username, const char *defdomain,
1689 + const char *separators)
1691 static char domain_buf[260];
1695 - if (!username || *username == '\0') return NULL;
1696 - p = strchr(username,'@');
1697 + if (!username || *username == '\0' ||
1698 + !separators || *separators == '\0') return NULL;
1700 + p = strpbrk (username, separators);
1702 if (!p || *(p+1) == '\0')
1704 - if (defdomain && *defdomain)
1705 + if (defdomain && *defdomain != '\0')
1710 + *domain_buf = '\0';
1711 + return domain_buf;
1716 @@ -531,20 +563,25 @@
1718 /* siefca@pld.org.pl */
1719 static char *parse_select_clause (const char *clause, const char *username,
1720 - const char *defdomain)
1721 + const char *defdomain,
1722 + const char *separators_set)
1724 static struct var_data vd[]={
1725 {"local_part", NULL, sizeof("local_part"), 0},
1726 {"domain", NULL, sizeof("domain"), 0},
1727 + {"username", NULL, sizeof("username"), 0},
1728 {NULL, NULL, 0, 0}};
1730 - if (clause == NULL || *clause == '\0' ||
1731 - !username || *username == '\0')
1732 + if (!clause || !username || !separators_set ||
1733 + *clause == '\0' || *username == '\0' ||
1734 + *separators_set == '\0')
1737 - vd[0].value = get_localpart (username);
1738 - vd[1].value = get_domain (username, defdomain);
1739 - if (!vd[0].value || !vd[1].value)
1741 + vd[0].value = get_localpart (username, separators_set);
1742 + vd[1].value = get_domain (username, defdomain, separators_set);
1743 + vd[2].value = get_username (vd[0].value, vd[1].value);
1745 + if (!vd[0].value || !vd[1].value || !vd[2].value)
1748 return (parse_string (clause, vd));
1749 @@ -552,12 +589,15 @@
1751 /* siefca@pld.org.pl */
1752 static char *parse_chpass_clause (const char *clause, const char *username,
1753 - const char *defdomain, const char *newpass,
1754 + const char *defdomain,
1755 + const char *separators_set,
1756 + const char *newpass,
1757 const char *newpass_crypt)
1759 static struct var_data vd[]={
1760 {"local_part", NULL, sizeof("local_part"), 0},
1761 {"domain", NULL, sizeof("domain"), 0},
1762 + {"username", NULL, sizeof("username"), 0},
1763 {"newpass", NULL, sizeof("newpass"), 0},
1764 {"newpass_crypt", NULL, sizeof("newpass_crypt"), 0},
1765 {NULL, NULL, 0, 0}};
1766 @@ -565,19 +605,81 @@
1767 if (clause == NULL || *clause == '\0' ||
1768 !username || *username == '\0' ||
1769 !newpass || *newpass == '\0' ||
1770 + !separators_set || *separators_set == '\0' ||
1771 !newpass_crypt || *newpass_crypt == '\0') return NULL;
1773 - vd[0].value = get_localpart (username);
1774 - vd[1].value = get_domain (username, defdomain);
1775 - vd[2].value = validate_password (newpass);
1776 - vd[3].value = validate_password (newpass_crypt);
1777 + vd[0].value = get_localpart (username, separators_set);
1778 + vd[1].value = get_domain (username, defdomain, separators_set);
1779 + vd[3].value = get_username (vd[0].value, vd[1].value);
1780 + vd[4].value = validate_password (newpass);
1781 + vd[5].value = validate_password (newpass_crypt);
1783 if (!vd[0].value || !vd[1].value ||
1784 - !vd[2].value || !vd[3].value) return NULL;
1785 + !vd[2].value || !vd[3].value ||
1786 + !vd[4].value || !vd[5].value) return NULL;
1788 return (parse_string (clause, vd));
1791 +/* siefca@pld.org.pl */
1792 +int auth_mysql_on_trigger (const char *clause_name, const char *username)
1794 +char *querybuf =NULL;
1795 +const char *separators_set =NULL,
1800 + if (!clause_name || *clause_name == '\0')
1803 + on_clause = read_env (clause_name);
1804 + if (!on_clause || *on_clause == '\0')
1805 + return (0); /* ok! not in use */
1807 + defdomain = read_env ("DEFAULT_DOMAIN");
1808 + separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1809 + if (!defdomain) defdomain = "";
1810 + if (!separators_set || *separators_set == '\0')
1811 + separators_set = DEF_SEPARATORS_SET;
1813 + querybuf = parse_select_clause (on_clause,
1818 + if (!querybuf) return (-1);
1820 + if (mysql_query (mysql, querybuf))
1822 + /* <o.blasnik@nextra.de> */
1824 + auth_mysql_cleanup();
1832 + if (mysql_query (mysql, querybuf))
1835 + auth_mysql_cleanup();
1836 + /* Server went down, that's OK,
1837 + ** try again next time.
1843 + result = mysql_store_result(mysql);
1844 + if (result) mysql_free_result(result);
1850 struct authmysqluserinfo *auth_mysql_getuserinfo(const char *username)
1852 const char *user_table =NULL;
1856 *where_clause =NULL,
1857 + *separators_set =NULL,
1858 *select_clause =NULL; /* siefca@pld.org.pl */
1860 static const char query[]=
1861 @@ -704,7 +807,15 @@
1864 /* siefca@pld.org.pl */
1865 - querybuf=parse_select_clause (select_clause, username, defdomain);
1866 + separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1868 + if (!separators_set || *separators_set == '\0')
1869 + separators_set = DEF_SEPARATORS_SET;
1871 + querybuf = parse_select_clause (select_clause,
1875 if (!querybuf) return 0;
1879 *where_clause =NULL,
1882 + *separators_set =NULL,
1883 *chpass_clause =NULL; /* siefca@pld.org.pl */
1886 @@ -837,13 +949,18 @@
1890 + separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1892 + if (!separators_set || *separators_set == '\0')
1893 + separators_set = DEF_SEPARATORS_SET;
1895 sql_buf=parse_chpass_clause(chpass_clause,
1906 diff -ur courier-imap-1.5.3-orig/authlib/authmysqlrc courier-imap-1.5.3/authlib/authmysqlrc
1907 --- courier-imap-1.5.3-orig/authlib/authmysqlrc Thu Apr 4 06:36:29 2002
1908 +++ courier-imap-1.5.3/authlib/authmysqlrc Mon Oct 14 00:02:59 2002
1913 # Copyright 2000 Double Precision, Inc. See COPYING for
1914 # distribution information.
1915 @@ -141,65 +141,99 @@
1917 # MYSQL_WHERE_CLAUSE server='mailhost.example.com'
1919 -##NAME: MYSQL_SELECT_CLAUSE:0
1922 -# This is optional, MYSQL_SELECT_CLAUSE can be set when you have a database,
1923 -# which is structuraly different from proposed. The fixed string will
1924 -# be used to do a SELECT operation on database, which should return fields
1925 -# in order specified bellow:
1926 +##NAME: USER_DOMAIN_SEPARATORS:0
1928 -# username, cryptpw, uid, gid, clearpw, home, maildir, quota, fullname
1929 +# This is optional. Using this option you can set the set of characters
1930 +# which are treated as separators when splitting entered username into the
1931 +# local part and the domain name. If it's not set the defaults @% are used,
1932 +# so the user can authenticate using user@domain or user%domain form.
1933 +# See README.authmysql.myownquery for more information
1935 -# Enabling this option causes ignorance of any other field-related
1936 -# options, excluding default domain.
1937 +# USER_DOMAIN_SEPARATORS @%+
1939 +##NAME: MYSQL_SELECT_CLAUSE:0
1941 -# There are two variables, which you can use. Substitution will be made
1942 -# for them, so you can put entered username (local part) and domain name
1943 -# in the right place of your query. These variables are:
1944 -# $(local_part) and $(domain)
1945 +# This is optional, MYSQL_SELECT_CLAUSE can be set when you have a database,
1946 +# which is structuraly different from proposed. You can type here your MySQL
1947 +# query, which will be used to fetch user's credentials, and which should
1948 +# return fields in order specified bellow:
1950 +# username, cryptpw, clearpw, uid, gid, home, maildir, quota, fullname
1952 +# Enabling this option causes ignorance of any other field-related options.
1954 +# There also are variables, which you can use. Substitution will be made
1955 +# for them, so you can pass currently entered username and a domain name
1956 +# up to the right place within your query. These variables are:
1957 +# $(local_part) , $(domain) , $(username)
1959 # If a $(domain) is empty (not given by the remote user) the default domain
1960 -# name is used in its place.
1962 -# This example is a little bit modified adaptation of vmail-sql
1965 -# MYSQL_SELECT_CLAUSE SELECT popbox.local_part, \
1966 -# CONCAT('{MD5}', popbox.password_hash), \
1967 -# popbox.clearpw, \
1970 -# CONCAT(domain.path, '/', popbox.mbox_name), \
1974 -# FROM popbox, domain \
1975 -# WHERE popbox.local_part = '$(local_part)' \
1976 -# AND popbox.domain_name = '$(domain)' \
1977 -# AND popbox.domain_name = domain.domain_name
1979 +# name is used in its place. $(username) is a local part concatenated with
1980 +# domain name using symbol defined in USER_DOMAIN_CONCAT or '@' if this option
1982 +# See README.authmysql.myownquery for more information
1984 +# MYSQL_SELECT_CLAUSE SELECT \
1985 +# users.username, users.cryptpw, users.clearpw, \
1986 +# domains.uid, domains.gid, \
1987 +# CONCAT_WS('/',domains.path_prefix,users.mailbox_path), \
1988 +# '', domains.quota, '' \
1989 +# FROM users, domains \
1990 +# WHERE domains.domain_name='$(domain)' \
1991 +# AND users.username='$(local_part)' \
1992 +# AND domains.domain_name=users.domain_name
1994 ##NAME: MYSQL_CHPASS_CLAUSE:0
1997 # This is optional, MYSQL_CHPASS_CLAUSE can be set when you have a database,
1998 -# which is structuraly different from proposed. The fixed string will
1999 -# be used to do an UPDATE operation on database. In other words, it is
2000 -# used, when changing password.
2001 +# which is structuraly different from proposed. You can use it to set up
2002 +# a MySQL query used to change user's password.
2004 # There are four variables, which you can use. Substitution will be made
2005 -# for them, so you can put entered username (local part) and domain name
2006 -# in the right place of your query. There variables are:
2007 -# $(local_part) , $(domain) , $(newpass) , $(newpass_crypt)
2008 +# for them, so you can put the currently entered username and the domain name
2009 +# in the right place of your query. These variables are:
2010 +# $(local_part) , $(domain) , $(username) , $(newpass) , $(newpass_crypt)
2012 # If a $(domain) is empty (not given by the remote user) the default domain
2013 -# name is used in its place.
2014 -# $(newpass) contains plain password
2015 -# $(newpass_crypt) contains its crypted form
2017 -# MYSQL_CHPASS_CLAUSE UPDATE popbox \
2018 -# SET clearpw='$(newpass)', \
2019 -# password_hash='$(newpass_crypt)' \
2020 -# WHERE local_part='$(local_part)' \
2021 -# AND domain_name='$(domain)'
2023 +# name is used in its place. $(newpass) contains plain password and
2024 +# $(newpass_crypt) contains its crypted form.
2025 +# See README.authmysql.myownquery for more information
2027 +# MYSQL_CHPASS_CLAUSE UPDATE users \
2028 +# SET clearpw='$(newpass)', \
2029 +# cryptpw='$(newpass_crypt)' \
2030 +# WHERE username='$(local_part)' \
2031 +# AND domain_name='$(domain)'
2033 +##NAME: ON_PASS_OK_CLAUSE:0
2035 +# This is optional, ON_PASS_OK_CLAUSE is a trigger -- the query
2036 +# is performed each time user has successfuly logged in.
2037 +# See README.authmysql.myownquery for more information
2039 +# ON_PASS_OK_CLAUSE UPDATE users \
2040 +# SET last_ok=CURRENT_TIMESTAMP \
2041 +# WHERE username='$(local_part)' \
2042 +# AND domain_name='$(domain)'
2044 +##NAME: ON_PASS_FAIL_CLAUSE:0
2046 +# This is optional, ON_PASS_FAIL_CLAUSE is a trigger -- the query
2047 +# is performed each time user has NOT logged in, cause of bad password.
2048 +# See README.authmysql.myownquery for more information
2050 +# ON_PASS_FAIL_CLAUSE UPDATE users \
2051 +# SET last_fail=CURRENT_TIMESTAMP \
2052 +# WHERE username='$(local_part)' \
2053 +# AND domain_name='$(domain)'
2055 +##NAME: ON_PASS_CHANGE_CLAUSE:0
2057 +# This is optional, ON_PASS_CHANGE_CLAUSE is a trigger -- the query
2058 +# is performed each time user has successfuly changed his password.
2059 +# See README.authmysql.myownquery for more information
2061 +# ON_PASS_CHANGE_CLAUSE UPDATE users \
2062 +# SET pw_change=CURRENT_TIMESTAMP \
2063 +# WHERE username='$(local_part)' \
2064 +# AND domain_name='$(domain)'