]> git.pld-linux.org Git - packages/courier-imap.git/blob - courier-imap-myownquery-fix.patch
- updated to 4.0.3
[packages/courier-imap.git] / courier-imap-myownquery-fix.patch
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
4 @@ -2,12 +2,13 @@
5  
6  
7  
8 -           Developer Notes for courier-imap-myownquery.patch
9 +                   Developer Notes and Usage Instructions
10 +                    of courier-imap-authmysql-myownquery
11  
12  
13  
14  
15 -                                                       document version: 1.03
16 +                                                       document version: 1.20
17                                                         author: Pawel Wilk
18  
19  
20 @@ -24,57 +25,739 @@
21  
22  
23  
24 +PART I - Usage Instructions
25  
26 -0 What's that?
27 +       1 What's that?
28 +       
29 +       2 When I'll need it?
30 +       
31 +       3 How does it work?
32 +         3.1 configuration variables
33 +         3.2 queries
34 +         3.3 substitutions
35 +         3.4 triggers
36 +       
37 +       4 Examples of usage
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
44 +
45 +PART II - Developer Notes
46 +
47 +       1 Modifications overview
48 +
49 +       2 Definitions
50 +
51 +       3 New data types
52 +         3.1 struct var_data
53 +         3.2 typedef size_t (*parsefunc)
54 +  
55 +       4 New functions
56 +         4.1 get_variable
57 +         4.2 parse_core
58 +         4.3 ParsePlugin_counter
59 +         4.4 ParsePlugin_builder
60 +         4.5 parse_string
61 +         4.6 validate_password
62 +         4.7 get_localpart
63 +         4.8 get_domain
64 +         4.9 get_username
65 +         4.10 parse_select_clause
66 +         4.11 parse_chpass_clause
67 +         4.12 auth_mysql_on_trigger
68  
69 -1 Modifications overview
70 +       5 Ideas and TODO
71  
72 -2 Definitions
73 +       6 Thanks
74  
75 -3 New data types
76 -  3.1 struct var_data
77 -  3.2 typedef size_t (*parsefunc)
78 -  
79 -4 New functions
80 -  4.1 get_variable
81 -  4.2 parse_core
82 -  4.3 ParsePlugin_counter
83 -  4.4 ParsePlugin_builder
84 -  4.5 parse_string
85 -  4.6 validate_password
86 -  4.7 get_localpart
87 -  4.8 get_domain
88 -  4.9 parse_select_clause
89 -  4.10 parse_chpass_clause
90 -  
91 -5 Ideas and TODO
92 -
93 -6 Thanks
94  
95  
96  
97 +//////////////////////////////// PART I - Usage ///////////////////////////////
98  
99                     *-----------------------
100 -                    0 What's that?
101 +                    1 What's that?
102                     *-----------------------
103  
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.
118  
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.
124  
125 -This patch was created using `diff -Nur` on courier-imap-1.3.12 source.
126  
127  
128  
129  
130 +                   *-----------------------
131 +                    2 When I'll need it?
132 +                   *-----------------------
133 +
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.
138 +
139 + o  When you have some great idea how to make the database structure 
140 +    more efficient due to your needs and your requirements.
141 +       
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. :)
145 +
146 +
147 +
148 +
149 +
150 +                   *-----------------------
151 +                    3 How does it work?
152 +                   *-----------------------
153 +
154 +There are three things which the feature concerns:
155 +
156 +- fetching clauses from the configuration file
157 +- doing substitution replacements inside of clauses
158 +- passing prepared query on to the mysql interface funtions
159 +
160 +3.1 configuration options
161 +
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:
164 +
165 +MYSQL_SERVER           (required)
166 +MYSQL_USERNAME         (required)
167 +MYSQL_PASSWORD         (required)
168 +
169 +               The server name, userid, and password used to log in.
170 +
171 +MYSQL_DATABASE         (required)
172 +
173 +               The name of the MySQL database we will open.
174 +
175 +DEFAULT_DOMAIN         (optional)
176 +
177 +               If DEFAULT_DOMAIN is defined, and someone tries to log in as
178 +               'user', we will look up 'user@DEFAULT_DOMAIN' instead.
179 +
180 +USER_DOMAIN_CONCAT     (optional)
181 +
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.
186 +
187 +USER_DOMAIN_SEPARATORS (optional)
188 +
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
194 +               authenticated.
195 +
196 +MYSQL_SELECT_CLAUSE    (required)
197 +MYSQL_CHPASS_CLAUSE    (required under some circumstances)
198 +
199 +               These are the major options you should use. See 3.2 section
200 +               for more info.
201 +
202 +MYSQL_ONSUCCESS_CLAUSE (optional)
203 +MYSQL_ONFAIL_CLAUSE    (optional)
204 +
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.
211 +
212 +The options which have no effect, and may be safetly left blank are:
213 +
214 +MYSQL_USER_TABLE
215 +MYSQL_CRYPT_PWFIELD
216 +MYSQL_CLEAR_PWFIELD
217 +MYSQL_UID_FIELD
218 +MYSQL_GID_FIELD
219 +MYSQL_LOGIN_FIELD
220 +MYSQL_HOME_FIELD
221 +MYSQL_NAME_FIELD
222 +MYSQL_MAILDIR_FIELD
223 +MYSQL_QUOTA_FIELD
224 +MYSQL_WHERE_CLAUSE
225 +
226 +3.2 queries
227 +
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
235 +password.
236 +
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:
240 +
241 +* username     - which is the currently logged user's username (or the
242 +                 username with domain if you want it)
243 +
244 +* cryptpw      - which is the user's crypted password
245 +
246 +* clearpw      - which is the user's plaintext password
247 +
248 +* uid          - which is a numerical UID value used as a process's UID when
249 +                 accessing the mailbox directory
250 +
251 +* gid          - as above, but refers to GID
252 +
253 +* home         - which contains full path to the user's home directory
254 +
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
258 +
259 +quota          - which describes a quota size for the mailbox
260 +
261 +fullname       - which may contain the user's fullname
262 +
263 +(The fields marked by the asterix sign are required and cannot have an
264 + empty results)
265 +
266 +So, the typical query clause may start with:
267 +
268 +MYSQL_SELECT_CLAUSE SELECT                                      \
269 + users.username,                                               \
270 + users.cryptpw,                                                        \
271 + users.clearpw,                                                \
272 + domains.uid,                                                  \
273 + domains.gid,                                                  \
274 + users.mailbox_path)                                           \
275 + ''                                                            \ 
276 + domains.quota,                                                        \
277 + ''                                                            \
278 +...
279 +
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).
284 +
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
289 +in the database:).
290 +
291 +3.3 substitutions
292 +
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.
302 +
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.
306 +
307 +Allowed substitution variables:
308 +
309 +context: MYSQL_SELECT_CLAUSE, MYSQL_ONFAIL_CLAUSE, MYSQL_ONSUCCESS_CLAUSE
310 +
311 +$(local_part)          will be replaced by currently verified user's username
312 +                       (without the domain part)
313 +
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
318 +
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
325 +
326 +context: MYSQL_CHPASS_CLAUSE
327 +
328 +$(local_part)           will be replaced by currently verified user's username
329 +                        (without the domain part)
330 +
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
335 +
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
342 +
343 +$(newpass)             will be replaced by currently authenticated user's
344 +                       new password to set up (plaintext password)
345 +
346 +$(newpass_crypt)       will be replaced by currently authenticated user's
347 +                       new password to set up (MD5 form created from entered
348 +                       plain form)
349 +
350 +3.4 triggers
351 +
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:
360 +
361 +MYSQL_ONSUCCESS_CLAUSE UPDATE users SET last_login=CURRENT_TIMESTAMP \
362 +                       WHERE username='$(username)';
363 +
364 +or, if you would like to know about last login failure for users you can try:
365 +
366 +MYSQL_ONFAIL_CLAUSE    UPDATE users SET last_bad_login=CURRENT_TIMESTAMP \
367 +                       WHERE username='$(username)';
368 +
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.
373 +
374 +
375 +
376 +
377 +
378 +
379 +                   *-----------------------
380 +                    4 Examples of usage
381 +                   *-----------------------
382 +
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:
387 +
388 + o  The database
389
390 + o  The users' data in the database
391
392 + o  The proper directories for keeping virtual mailboxes and a system user
393 +    which can read and write them
394
395 + o  The proper MySQL queries in your authmysqlrc configuration file
396 +
397 +4.1 corporate mail system
398 +
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.
402 +
403 +4.1.1 database structure
404 +
405 +Our goal here is to separate the data responsible for keeping mailbox
406 +credentials from the data, which describes a domain.
407 +
408 +Let's create some tables for our example, filled up with an example data:
409 +
410 +table:          domains
411 +
412 +purpose:        associates virtual domain with domain name and informations
413 +               necessary to access mailboxes withing the domain
414 +
415 +fields:         domain_name             - fully qualified domain name
416 +                path_prefix             - absolute pathname which points to
417 +                                          a directory where domain's mailboxes
418 +                                          are located
419 +                quota                   - default quota for each mailbox
420 +                uid                     - UID used to work on mailboxes
421 +                gid                     - GID used to work on mailboxes
422 +               
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 +       +----------------+-------------+-----+-----+----------+
430 +
431 +table:          users
432 +
433 +purpose:        associates virtual mailbox with user and domain name,
434 +               and with informations necessary to access mailbox
435 +
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
444 +
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 +       +----------+----------------+--------------+-----------+---------+ 
452 +
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).
456 +
457 +--------------------- cut here
458 +
459 +# Create the database called vmail.
460 +
461 +CREATE database vmail;
462 +
463 +# Create an example MySQL user, which can read, write and delete data from
464 +# vmail database. Username: vuser Password: secret_password
465 +
466 +GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
467 +             TO vuser@localhost
468 +             IDENTIFIED BY 'secret_password';
469 +
470 +FLUSH PRIVILEGES;
471 +
472 +# Create the tables.
473 +
474 +use vmail;
475 +
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))
483 +        );
484 +
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))
492 +        );
493 +
494 +# Create an example virtual domain entry
495 +# name  : exampledom.com
496 +# uid   : 555
497 +# gid   : 555
498 +# path  : /var/mail/x
499 +# quota : 10 Megs per mailbox
500 +
501 +INSERT INTO domains VALUES ('exampledom.com', '/var/mail/x', 555, 555,
502 +                            '10000000');
503 +
504 +# Create an example virtual user entry
505 +# username      : siefca
506 +# domain name   : exampledom.com
507 +# cryptpw       : $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
508 +# clearpw       : dupa.8
509 +# mailbox path  : s/siefca
510 +
511 +INSERT INTO users VALUES ('siefca', 'exampledom.com', 's/siefca',
512 +                         '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
513 +                         'dupa.8');
514 +
515 +--------------------- cut here
516 +
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.
521 +
522 +4.1.2 authdaemon configuration
523 +
524 +When our database is ready we can set up the configuration. :-) Go to
525 +authmysqlrc file and edit it.
526 +
527 +At the beginning we should take care about general informations, which
528 +are identifying our database:
529 +
530 +MYSQL_SERVER            localhost
531 +MYSQL_USERNAME          vuser
532 +MYSQL_PASSWORD          secret_password
533 +MYSQL_DATABASE          vmail
534 +
535 +Then we should add a clause responsible for authenticating user and
536 +fetching credentials:
537 +
538 +DEFAULT_DOMAIN          exampledom.com
539 +
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
549 +
550 +
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.
554 +
555 +We also should add some configuration for changing user's password:
556 +
557 +MYSQL_CHPASS_CLAUSE UPDATE                                             \
558 + users                                                                 \
559 + SET clearpw='$(newpass)',                                             \
560 +     cryptpw='$(newpass_crypt)'                                        \
561 + WHERE username='$(local_part)'                                        \
562 + AND   domain_name='$(domain)'
563 +
564 +Last things to have sure...
565 +Create a system user/group and a proper directory structure. In our example:
566 +
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
573 +
574 +Now, restart the authdaemon and see if it works. Try: telnet 0 pop3
575 +
576 +and type:
577 +
578 +USER siefca [ENTER]
579 +PASS dupa.8 [ENTER]
580 +
581 +You should get Ok response. ;)
582 +
583 +4.2 virtual mail domains provider
584 +
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.
591 +
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.
596 +
597 +4.2.1 database structure
598 +
599 +table:         domain_names
600 +
601 +purpose:       associates domain_id with domain name
602 +
603 +fields:                domain_name             - fully qualified domain name
604 +               domain_id               - domain identifier
605 +
606 +       +----------------+-----------+
607 +       | domain_name    | domain_id |
608 +       +----------------+-----------+
609 +       | exampledom.com |         1 |
610 +       | pld.org.pl     |         2 |
611 +       | pld.net.pl     |         2 |
612 +       | foobare.net.uk |         3 |
613 +       +----------------+-----------+
614 +
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. :)
617 +
618 +table:         domain_auth
619 +
620 +purpose:       associates domain_id with authentication credentials
621 +               which are common for all users in the virtual domain
622 +
623 +fields:                domain_id               - domain identifier
624 +               path_prefix             - absolute pathname which points to
625 +                                         a directory where domain's mailboxes
626 +                                         are located
627 +               quota                   - default quota for each mailbox
628 +               uid                     - UID used to work on mailboxes
629 +               gid                     - GID used to work on mailboxes
630 +               
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 +       +------------+---------------+--------+-------+-------+
638 +
639 +table:         domain_info
640 +
641 +purpose:       associates domain_id with additional informations
642 +
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
648 +
649 +       +------------+---------------+------------+----------+---------+
650 +       | domain_id  | registrant_id | nic_handle | owner_id | expires |
651 +       +------------+---------------+------------+----------+---------+
652 +
653 +       (we don't need to say anything more about this table indeed)
654 +
655 +table:         users
656 +
657 +purpose:       associates users' identifiers with domains' identifiers
658 +               and infers the credentials for various virtual mailboxes
659 +
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)
671 +
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 +       +------------+-----------+----------+-----------+-------+------------+
678 +
679 +    (you can add a realname column here, it doesn't fit to my terminal window:)
680 +
681 +--------------------- cut here
682 +
683 +# Create the database called vmail.
684 +
685 +CREATE database vmail;
686 +
687 +# Create an example MySQL user, which can read, write and delete data from
688 +# vmail database. Username: vuser Password: secret_password
689 +
690 +GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
691 +            TO vuser@localhost
692 +            IDENTIFIED BY 'secret_password';
693 +
694 +FLUSH PRIVILEGES;
695 +
696 +# Create the tables.
697 +
698 +use vmail;
699 +
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))
704 +       );
705 +
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)
713 +       );
714 +
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,
721 +        quota                 char(255),
722 +       path                  char(255) DEFAULT '' NOT NULL,
723 +        KEY username (username(128))
724 +       );
725 +       
726 +# Create an example virtual domain entry
727 +# id   : 1
728 +# name : exampledom.com
729 +# uid  : 15000
730 +# gid  : 15000
731 +# path : /var/mail/example
732 +# quota        : 20 Megs per mailbox
733 +
734 +INSERT INTO domain_names VALUES (1, 'exampledom.com');
735 +INSERT INTO domain_auth VALUES (1, '15000', '15000', '/var/mail/example',
736 +                               '20000000');
737 +
738 +# Create an example virtual user entry
739 +# username     : siefca
740 +# domain id    : 1 (points to exampledom.com)
741 +# cryptpw      : $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
742 +# clearpw      : dupa.8
743 +# name         : Pawel Wilk
744 +# quota                : NULL (we want it to be fetched from domain_auth table)
745 +# mailbox path : s/i/siefca
746 +
747 +INSERT INTO users VALUES ('siefca', 1, '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
748 +                         'dupa.8', 'Pawel Wilk', NULL, 's/i/siefca');
749 +
750 +--------------------- cut here
751 +
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
758 +with Courier-IMAP.
759 +
760 +
761 +4.2.2 authdaemon configuration
762 +
763 +DEFAULT_DOMAIN         exampledom.com
764 +
765 +MYSQL_SELECT_CLAUSE SELECT                                     \
766 + users.username,                                               \
767 + users.cryptpw,                                                        \
768 + users.plainpw,                                                        \
769 + domain_auth.uid,                                              \
770 + domain_auth.gid,                                              \
771 + CONCAT_WS('/',domain_auth.path_prefix,users.path),            \
772 + '',                                                           \
773 + IFNULL(users.quota, domain_auth.quota),                       \
774 + users.name                                                    \
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
780 +
781 +
782 +.
783 +.
784 +.
785 +.
786 +.
787 +.
788 +
789 +/////////////////////////// PART II - Developer Notes /////////////////////////
790  
791                     *-----------------------
792                      1 Modifications overview
793 @@ -85,7 +768,7 @@
794  Each modified set of instructions is marked by my e-mail address:
795  siefca@pld.org.pl
796  
797 -Changes in the current source code are related to:
798 +Changes in the source code are related to:
799  
800  - sections where the queries are constructed
801    (including memory allocation for the buffers)
802 @@ -102,6 +785,10 @@
803         newline as the second is replaced by two whitespaces while
804         putting into the buffer
805  
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
809 +
810  - sections where the query is constructed
811  
812         selection is made, depending on configuration variables which
813 @@ -130,7 +817,16 @@
814  MAX_SUBSTITUTION_LEN defines maximal length of a substitution variable's
815  identifier (name).
816  
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
819 +simplification.
820 +
821 +#define         DEF_CONCAT_STRING       "@"
822 +#define         DEF_SEPARATORS_SET      "@%"
823 +
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.
828  
829  
830  
831 @@ -179,7 +875,7 @@
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.
837  
838  
839  3.2 typedef size_t (*parsefunc)
840 @@ -414,14 +1110,17 @@
841  
842  SYNOPSIS
843  
844 -       static const char *get_localpart (const char *username);
845 +       static const char *get_localpart (const char *username,
846 +                                         const char *separators);
847  
848  DESCRIPTION
849  
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.
857  
858  RETURN VALUE
859  
860 @@ -438,16 +1137,22 @@
861  SYNOPSIS
862  
863         static const char *get_domain (const char *username, 
864 -                                      const char *defdomain);
865 +                                      const char *defdomain,
866 +                                      const char *separators);
867  
868  DESCRIPTION
869  
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
875 -       used instead.
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
883 +       part and a domain.
884  
885  RETURN VALUE
886  
887 @@ -455,7 +1160,36 @@
888         NULL if there was some error.
889  
890  
891 -4.9 parse_select_clause
892 +4.9 get_username
893 +
894 +NAME
895 +
896 +       get_username
897 +
898 +SYNOPSIS
899 +
900 +       static const char *get_username (const char *username,
901 +                                        const char *domainname,
902 +                                        const char *concat_str);
903 +
904 +DESCRIPTION
905 +
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.
909 +
910 +RETURN VALUE
911 +
912 +        Pointer to the static buffer containing output string or
913 +        NULL if there was some error.
914 +
915 +WARNINGS
916 +
917 +       This function does not any string cleaning, nor default domain
918 +       checking. It is designed to work on results of get_localpart() and
919 +       get_domain().
920 +
921 +4.10 parse_select_clause
922  
923  NAME
924  
925 @@ -465,7 +1199,9 @@
926  
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);
933  
934  DESCRIPTION
935  
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)
940 -       and $(domain).
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.
945         
946 -
947  RETURN VALUE
948  
949         Same as parse_string().
950  
951  
952 -4.10 parse_chpass_clause
953 +4.11 parse_chpass_clause
954  
955  NAME
956  
957 @@ -492,6 +1230,8 @@
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,
963                                           const char *newpass,
964                                           const char *newpass_crypt);
965                                                                     
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),
970 -       $(newpass_crypt).
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().
974  
975  RETURN VALUE
976  
977         Same as parse_string().
978  
979 +4.12 auth_mysql_on_trigger
980 +
981 +NAME
982 +
983 +       auth_mysql_on_trigger
984 +
985 +SYNOPSIS
986 +
987 +       int auth_mysql_on_trigger (const char *clause_name,
988 +                                  const char *username);
989 +
990 +DESCRIPTION
991 +
992 +       This function is responsible for calling out the MySQL queries in
993 +       depend which authentication state was reached.
994 +
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).
998 +
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.
1004 +
1005 +RETURN VALUE
1006 +
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
1010 +       work.
1011 +
1012  
1013  
1014  
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
1019 -
1020 -- add clauses:
1021 -
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
1027  
1028  
1029  
1030 @@ -534,10 +1307,12 @@
1031                      6 Thanks
1032                     *------------------------
1033  
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. :>
1042  
1043  ---------------------------------------------------------------------------
1044  
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
1048 @@ -31,7 +31,11 @@
1049         if ((user=strtok(authdata, "\n")) == 0 ||
1050                 (pass=strtok(0, "\n")) == 0)
1051         {
1052 -               errno=EPERM;
1053 +               if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1054 +                       errno=EACCES;
1055 +               else
1056 +                       errno=EPERM;
1057 +                       
1058                 return (0);
1059         }
1060  
1061 @@ -50,7 +54,11 @@
1062         {
1063                 if (authcheckpassword(pass,authinfo->cryptpw))
1064                 {
1065 -                       errno=EPERM;
1066 +                       if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1067 +                           errno=EACCES;
1068 +                       else
1069 +                           errno=EPERM;
1070 +       
1071                         return (0);     /* User/Password not found. */
1072                 }
1073         }
1074 @@ -58,13 +66,21 @@
1075         {
1076                 if (strcmp(pass, authinfo->clearpw))
1077                 {
1078 -                       errno=EPERM;
1079 +                       if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1080 +                           errno=EACCES;
1081 +                       else
1082 +                           errno=EPERM;
1083 +
1084                         return (0);
1085                 }
1086         }
1087         else
1088         {
1089 -               errno=EPERM;
1090 +               if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1091 +                   errno=EACCES;
1092 +               else
1093 +                   errno=EPERM;
1094 +
1095                 return (0);             /* Username not found */
1096         }
1097  
1098 @@ -132,6 +148,12 @@
1099                 (*callback_func)(&aa, callback_arg);
1100         }
1101  
1102 +       if (!auth_mysql_on_trigger("MYSQL_ONSUCCESS_CLAUSE", user))
1103 +       {
1104 +           errno=EACCES;
1105 +           return (0);
1106 +       }
1107 +
1108         return (strdup(authinfo->username));
1109  }
1110  
1111 @@ -153,7 +175,11 @@
1112         {
1113                 if (authcheckpassword(pass,authinfo->cryptpw))
1114                 {
1115 -                       errno=EPERM;
1116 +                       if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1117 +                           errno=EACCES;
1118 +                       else
1119 +                           errno=EPERM;
1120 +
1121                         return (-1);    /* User/Password not found. */
1122                 }
1123         }
1124 @@ -161,13 +187,21 @@
1125         {
1126                 if (strcmp(pass, authinfo->clearpw))
1127                 {
1128 -                       errno=EPERM;
1129 +                       if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1130 +                           errno=EACCES;
1131 +                       else
1132 +                           errno=EPERM;
1133 +
1134                         return (-1);
1135                 }
1136         }
1137         else
1138         {
1139 -               errno=EPERM;
1140 +               if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1141 +                       errno=EACCES;
1142 +               else
1143 +                       errno=EPERM;
1144 +
1145                 return (-1);
1146         }
1147  
1148 @@ -176,6 +210,13 @@
1149                 errno=EPERM;
1150                 return (-1);
1151         }
1152 +       
1153 +       if (!auth_mysql_on_trigger("MYSQL_ONSUCCESS_CLAUSE", user))
1154 +       {
1155 +               errno=EACCES;
1156 +               return (-1);
1157 +       }
1158 +       
1159         return (0);
1160  }
1161  
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
1165 @@ -21,6 +21,7 @@
1166         } ;
1167  
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();
1171  
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
1176 @@ -24,6 +24,9 @@
1177  #define                SV_BEGIN_LEN            ((sizeof(SV_BEGIN_MARK))-1)
1178  #define                SV_END_LEN              ((sizeof(SV_END_MARK))-1)
1179  
1180 +#define                DEF_CONCAT_STRING       "@"
1181 +#define                DEF_SEPARATORS_SET      "@%"
1182 +
1183  static const char rcsid[]="$Id$";
1184  
1185  /* siefca@pld.org.pl */
1186 @@ -426,21 +429,43 @@
1187                 return NULL;
1188         }       
1189         *pass_buf = '\0';
1190 -       
1191 +
1192         return output_buf;
1193  }
1194  
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)
1199 +{
1200 +static char    username_buf[400];
1201 +
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;
1207 +
1208 +       if (*domainname == '\0')
1209 +               strcpy (username_buf, username);
1210 +       else
1211 +               sprintf (username_buf, "%s%s%s", username, concat_str,
1212 +                                                domainname);
1213 +
1214 +       return (username_buf);
1215 +}
1216 +
1217 +/* siefca@pld.org.pl */
1218 +static const char *get_localpart (const char *username, const char *separators)
1219  {
1220  size_t         lbuf    = 0;
1221  const char     *l_end, *p;
1222  char           *q;
1223  static char    localpart_buf[130];
1224         
1225 -       if (!username || *username == '\0')     return NULL;
1226 +       if (!username || *username == '\0' ||
1227 +           !separators || *separators == '\0') return NULL;
1228         
1229 -       p = strchr(username,'@');
1230 +       p = strpbrk (username, separators);
1231         if (p)
1232         {
1233                 if ((p-username) > 128)
1234 @@ -469,21 +494,27 @@
1235  }
1236  
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)
1241  {
1242  static char    domain_buf[260];
1243  const char     *p;
1244  char           *q;
1245         
1246 -       if (!username || *username == '\0')     return NULL;
1247 -       p = strchr(username,'@');
1248 +       if (!username || *username == '\0' ||
1249 +           !separators || *separators == '\0') return NULL;
1250 +       
1251 +       p = strpbrk (username, separators);
1252         
1253         if (!p || *(p+1) == '\0')
1254         {
1255 -               if (defdomain && *defdomain)
1256 +               if (defdomain && *defdomain != '\0')
1257                         return defdomain;
1258                 else
1259 -                       return NULL;
1260 +                 {
1261 +                   *domain_buf = '\0';
1262 +                   return domain_buf;
1263 +                 }
1264         }
1265  
1266         p++;
1267 @@ -528,20 +559,27 @@
1268  
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)
1275  {
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}};
1281  
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')
1287                 return NULL;
1288         
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);
1295 +
1296 +       if (!vd[0].value || !vd[1].value || !vd[2].value)
1297                 return NULL;
1298         
1299         return (parse_string (clause, vd));
1300 @@ -549,12 +587,16 @@
1301  
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)
1310  {
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;
1324  
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);
1334         
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;
1339  
1340         return (parse_string (clause, vd));
1341  }
1342  
1343 +/* siefca@pld.org.pl */
1344 +int auth_mysql_on_trigger (const char *clause_name, const char *username)
1345 +{
1346 +char           *querybuf       =NULL;
1347 +const char     *concat_str     =NULL,
1348 +               *separators_set =NULL,
1349 +               *defdomain      =NULL,
1350 +               *on_clause      =NULL;
1351 +MYSQL_RES      *result;
1352 +
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);
1356 +
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;
1365 +       
1366 +       querybuf = parse_select_clause (on_clause,
1367 +                                       username,
1368 +                                       defdomain,
1369 +                                       concat_str,
1370 +                                       separators_set);
1371 +       
1372 +       if (!querybuf)  return (0);
1373 +
1374 +       if (mysql_query (mysql, querybuf))
1375 +       {
1376 +               /* <o.blasnik@nextra.de> */
1377 +
1378 +               auth_mysql_cleanup();
1379 +
1380 +               if (do_connect())
1381 +               {
1382 +                       free(querybuf);
1383 +                       return (1);
1384 +               }
1385 +
1386 +               if (mysql_query (mysql, querybuf))
1387 +               {
1388 +                       free(querybuf);
1389 +                       auth_mysql_cleanup();
1390 +                       /* Server went down, that's OK,
1391 +                       ** try again next time.
1392 +                       */
1393 +                       return (1);
1394 +               }
1395 +       }
1396 +       free(querybuf);
1397 +       result = mysql_store_result(mysql);
1398 +       if (result) mysql_free_result(result);
1399 +       
1400 +       return (1);
1401 +}
1402 +
1403 +
1404  struct authmysqluserinfo *auth_mysql_getuserinfo(const char *username)
1405  {
1406  const char *user_table =NULL;
1407 @@ -593,6 +699,8 @@
1408             *gid_field          =NULL,
1409             *quota_field        =NULL,
1410             *where_clause       =NULL,
1411 +           *concat_str         =NULL,
1412 +           *separators_set     =NULL,
1413             *select_clause      =NULL; /* siefca@pld.org.pl */
1414  
1415  static const char query[]=
1416 @@ -701,7 +809,19 @@
1417         else
1418         {
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");
1423 +
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;
1428 +
1429 +               querybuf = parse_select_clause (select_clause,
1430 +                                               username,
1431 +                                               defdomain,
1432 +                                               concat_str,
1433 +                                               separators_set);
1434                 if (!querybuf) return 0;
1435         }
1436  
1437 @@ -785,6 +905,8 @@
1438                     *where_clause       =NULL,
1439                     *user_table         =NULL,
1440                     *login_field        =NULL,
1441 +                   *concat_str        =NULL,
1442 +                   *separators_set     =NULL,  
1443                     *chpass_clause      =NULL; /* siefca@pld.org.pl */
1444  
1445         if (!mysql)
1446 @@ -834,14 +956,23 @@
1447         }
1448         else
1449         {
1450 +               concat_str = read_env ("USER_DOMAIN_CONCAT");
1451 +                separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1452 +               
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;
1457 +               
1458                 sql_buf=parse_chpass_clause(chpass_clause,
1459                                             user,
1460                                             defdomain,
1461 +                                           concat_str,
1462 +                                           separators_set,
1463                                             pass,
1464                                             newpass_crypt);
1465         }
1466         
1467 -
1468         if (!sql_buf)
1469         {
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 @@
1475  #
1476  # MYSQL_WHERE_CLAUSE   server='mailhost.example.com'
1477  
1478 -##NAME: MYSQL_SELECT_CLAUSE:0
1479 +##NAME: USER_DOMAIN_CONCAT:0
1480  #
1481 -# (EXPERIMENTAL)
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:
1486 -#
1487 -# username, cryptpw, uid, gid, clearpw, home, maildir, quota, fullname
1488 -#
1489 -# Enabling this option causes ignorance of any other field-related
1490 -# options, excluding default domain.
1491 -#
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.
1499  #
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   @
1503 +
1504 +##NAME: USER_DOMAIN_SEPARATORS:0
1505  #
1506 -# This example is a little bit modified adaptation of vmail-sql
1507 -# database scheme:
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.
1512  #
1513 -# MYSQL_SELECT_CLAUSE  SELECT popbox.local_part,                       \
1514 -#                      CONCAT('{MD5}', popbox.password_hash),          \
1515 -#                      domain.uid,                                     \
1516 -#                      domain.gid,                                     \
1517 -#                      popbox.clearpw,                                 \
1518 -#                      CONCAT(domain.path, '/', popbox.mbox_name),     \
1519 -#                      '',                                             \
1520 -#                      domain.quota,                                   \
1521 -#                      '',                                             \
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       @%+
1527 +
1528 +##NAME: MYSQL_SELECT_CLAUSE:0
1529 +#
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:
1534 +#
1535 +# username, cryptpw, clearpw, uid, gid, home, maildir, quota, fullname
1536 +#
1537 +# Enabling this option causes ignorance of any other field-related options.
1538 +#
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)
1543  #
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
1547 +# is not set.
1548 +#
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
1558 +
1559  ##NAME: MYSQL_CHPASS_CLAUSE:0
1560  #
1561 -# (EXPERIMENTAL)
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.
1568  #
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)
1575  #
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
1580 -#
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.
1588 +#
1589 +# MYSQL_CHPASS_CLAUSE UPDATE users             \
1590 +# SET clearpw='$(newpass)',                    \
1591 +#     cryptpw='$(newpass_crypt)'               \
1592 +# WHERE username='$(local_part)'               \
1593 +# AND domain_name='$(domain)'
1594 +
1595 +##NAME: MYSQL_ONSUCCESS_CLAUSE:0
1596 +#
1597 +# This is optional, MYSQL_ONSUCCESS_CLAUSE is a trigger - the query is performed
1598 +# each time user has successfuly logged in. [experimental]
1599 +#
1600 +# MYSQL_ONSUCCESS_CLAUSE       UPDATE users                            \
1601 +#                              SET last_ok=CURRENT_TIMESTAMP           \
1602 +#                              WHERE username='$(local_part)'          \
1603 +#                              AND domain_name='$(domain)'
1604 +
1605 +##NAME: MYSQL_ONFAIL_CLAUSE:0
1606 +#
1607 +# This is optional, MYSQL_ONFAIL_CLAUSE is a trigger - the query is performed
1608 +# each time user has successfuly logged in. [experimental]
1609  #
1610 +# MYSQL_ONFAIL_CLAUSE          UPDATE users                            \
1611 +#                              SET last_fail=CURRENT_TIMESTAMP         \
1612 +#                              WHERE username='$(local_part)'          \
1613 +#                              AND domain_name='$(domain)'
This page took 0.365863 seconds and 3 git commands to generate.