]> git.pld-linux.org Git - packages/courier-imap.git/blob - courier-imap-1.5.3.20020921-myownquery.patch
afcd2926217f19f656d30cc70f7f66bc3b49c0a8
[packages/courier-imap.git] / courier-imap-1.5.3.20020921-myownquery.patch
1 diff -ur courier-imap-1.5.3.20020921.orig/authlib/README.authmysql.myownquery courier-imap-1.5.3.20020921/authlib/README.authmysql.myownquery
2 --- courier-imap-1.5.3.20020921.orig/authlib/README.authmysql.myownquery        Tue Jan  8 06:01:22 2002
3 +++ courier-imap-1.5.3.20020921/authlib/README.authmysql.myownquery     Sat Sep 28 01:19:55 2002
4 @@ -2,13 +2,18 @@
5  
6  
7  
8 -           Developer Notes for courier-imap-myownquery.patch
9 +                   Developer Notes and Usage Instructions
10 +                                   
11 +                                   of
12 +                                   
13 +                      courier-imap-authmysql-myownquery
14  
15  
16  
17 +                                       document version: 1.30
18 +                                       patch for version: 1.5.3.20020921
19 +                                       author: Pawel Wilk <siefca@kernel.pl>
20  
21 -                                                       document version: 1.03
22 -                                                       author: Pawel Wilk
23  
24  
25  
26 @@ -21,71 +26,749 @@
27  
28  
29  
30 +PART I - Usage Instructions
31  
32 +       1 What's that?
33 +       
34 +       2 When will I need it?
35 +       
36 +       3 How does it work?
37 +         3.1 configuration variables
38 +         3.2 queries
39 +         3.3 substitutions
40 +         3.4 triggers
41 +       
42 +       4 Examples of usage
43 +         4.1 corporate mail system
44 +           4.1.1 database structure
45 +           4.1.2 authdaemon configuration
46 +         4.2 virtual mail domains provider
47 +           4.2.1 database structure
48 +           4.2.2 authdaemon configuration
49 +
50 +PART II - Developer Notes
51 +
52 +       1 Modifications overview
53 +
54 +       2 Definitions
55 +
56 +       3 New data types
57 +         3.1 struct var_data
58 +         3.2 typedef size_t (*parsefunc)
59 +  
60 +       4 New functions
61 +         4.1 get_variable
62 +         4.2 parse_core
63 +         4.3 ParsePlugin_counter
64 +         4.4 ParsePlugin_builder
65 +         4.5 parse_string
66 +         4.6 validate_password
67 +         4.7 get_localpart
68 +         4.8 get_domain
69 +         4.9 get_username
70 +         4.10 parse_select_clause
71 +         4.11 parse_chpass_clause
72 +         4.12 auth_mysql_on_trigger
73  
74 +       5 Ideas and TODO
75  
76 +       6 Thanks
77  
78 -0 What's that?
79  
80 -1 Modifications overview
81  
82 -2 Definitions
83  
84 -3 New data types
85 -  3.1 struct var_data
86 -  3.2 typedef size_t (*parsefunc)
87 -  
88 -4 New functions
89 -  4.1 get_variable
90 -  4.2 parse_core
91 -  4.3 ParsePlugin_counter
92 -  4.4 ParsePlugin_builder
93 -  4.5 parse_string
94 -  4.6 validate_password
95 -  4.7 get_localpart
96 -  4.8 get_domain
97 -  4.9 parse_select_clause
98 -  4.10 parse_chpass_clause
99 -  
100 -5 Ideas and TODO
101 +//////////////////////////////// PART I - Usage ///////////////////////////////
102 +
103 +                   *-----------------------
104 +                    1 What's that?
105 +                   *-----------------------
106 +
107 +Courier-imap-myownquery's features allow the administrator to set his own MySQL
108 +queries used by authdaemon to authenticate a user (including fetchig his
109 +credentials) and to change the user's password. It allows one to write a
110 +SELECT and UPDATE clause in the configuration file (authmysqlrc) using
111 +the new configuration options. It may be useful in mail environments where
112 +there is a need to have a different database structure and/or tables
113 +scheme than expected by authmysql module.
114  
115 -6 Thanks
116 +It also implements a small parsing engine for substitution of variables which
117 +may appear in the SQL clauses, such as a username or a domain.
118  
119  
120  
121  
122 +
123 +                   *-----------------------
124 +                    2 When will I need it?
125                     *-----------------------
126 -                    0 What's that?
127 +
128 + o  When you already have some MySQL database filled up with the data
129 +    and there is no chance to change the whole structure to make it
130 +    work with a standard authmysql table. Typical situation is when all the
131 +    data required to authenticate a user is arranged in more than one table.
132 +
133 + o  When you have some great idea how to make the database structure 
134 +    more efficient due to your needs and your requirements.
135 +       
136 + o  When doing something 'by-myself' is in your style and you just want
137 +    to create your own database, just to feel the pleasure of doing
138 +    something original. :)
139 +
140 +
141 +
142 +
143 +
144                     *-----------------------
145 +                    3 How does it work?
146 +                   *-----------------------
147 +
148 +There are three things which the feature concerns:
149 +
150 +- fetching clauses from the configuration file
151 +- doing substitution replacements inside of SQL clauses
152 +- passing prepared query on to the mysql interface funtions
153 +
154 +3.1 configuration options
155 +
156 +You can apply your own MySQL queries using a set of the configuration options.
157 +The options you'll need to make the authmysql your slave are:
158 +
159 +MYSQL_SERVER           (required)
160 +MYSQL_USERNAME         (required)
161 +MYSQL_PASSWORD         (required)
162 +
163 +               The server name, userid, and password used to log in.
164 +
165 +MYSQL_DATABASE         (required)
166 +
167 +               The name of the MySQL database we will open.
168 +
169 +DEFAULT_DOMAIN         (optional)
170 +
171 +               If DEFAULT_DOMAIN is defined, and someone tries to log in as
172 +               'user', we will look up 'user@DEFAULT_DOMAIN' instead.
173 +
174 +USER_DOMAIN_CONCAT     (optional)
175 +
176 +               The USER_DOMAIN_CONCAT defines a character(s) used to
177 +               concatenate a local part and a domain while parsing
178 +               the $(username) substitution variable (see section 3.3
179 +               for more info). If it's not defined the @ sign is assumed.
180 +
181 +USER_DOMAIN_SEPARATORS (optional)
182 +
183 +               This may contain the set of characters used by parsing
184 +               routines to split local part of the virtual mailbox name
185 +               from the part which describes the domain name. If it's not
186 +               defined the set containing @% is assumed, so the user can
187 +               enter either: user@domain or user%domain when he wants to be
188 +               authenticated.
189 +
190 +MYSQL_SELECT_CLAUSE    (required)
191 +MYSQL_CHPASS_CLAUSE    (required under some circumstances)
192 +
193 +               These are the major options you should use. See 3.2 section
194 +               for more info.
195 +
196 +MYSQL_ONSUCCESS_CLAUSE (optional)
197 +MYSQL_ONFAIL_CLAUSE    (optional)
198 +
199 +               These are used to do a MySQL query whether user has passed
200 +               the authentication verification (MYSQL_ONSUCCESS_CLAUSE)
201 +               or there was the authentication failure (MYSQL_ONFAIL_CLAUSE).
202 +               Query results have no meaning. You can use the same
203 +               substitution variables in your query as with
204 +               MYSQL_SELECT_CLAUSE. See 3.4 section for more info.
205 +
206 +The options which have no effect, and may be safetly left blank are:
207 +
208 +MYSQL_USER_TABLE
209 +MYSQL_CRYPT_PWFIELD
210 +MYSQL_CLEAR_PWFIELD
211 +MYSQL_UID_FIELD
212 +MYSQL_GID_FIELD
213 +MYSQL_LOGIN_FIELD
214 +MYSQL_HOME_FIELD
215 +MYSQL_NAME_FIELD
216 +MYSQL_MAILDIR_FIELD
217 +MYSQL_QUOTA_FIELD
218 +MYSQL_WHERE_CLAUSE
219 +
220 +3.2 queries
221 +
222 +The feature adds two configuration options (clauses), which are parsed first,
223 +and then applied as MySQL queries to MySQL interface routines. These options
224 +are: MYSQL_SELECT_CLAUSE and MYSQL_CHPASS_CLAUSE. After each option a number of
225 +spaces and/or tabs is allowed, and then MySQL query is expected. For better
226 +look, your queries can have line breaks. Each line break should be preceded by
227 +the backslash sign. Look into examples chapter (4) to see how it should look
228 +like. First clause is used to authenticate a user, and the second to change his
229 +password.
230 +
231 +You should note that a query identified by MYSQL_SELECT_CLAUSE should return
232 +fixed number (9) of fields and each field should match the variable expected
233 +by authentication routines. These fields are:
234 +
235 +* username     - which is the currently logged user's username (or the
236 +                 username with domain if you want it)
237 +
238 +* cryptpw      - which is the user's crypted password
239 +
240 +* clearpw      - which is the user's plaintext password
241 +
242 +* uid          - which is a numerical UID value used as a process's UID when
243 +                 accessing the mailbox directory
244 +
245 +* gid          - as above, but refers to GID
246 +
247 +* home         - which contains full path to the user's home directory
248 +
249 +maildir                - which contains the directory name inside the user's home
250 +                 which is treated as INBOX folder when accessing mailbox
251 +                 - if it's empty then the 'Maildir' string is used
252 +
253 +quota          - which describes a quota size for the mailbox
254 +
255 +fullname       - which may contain the user's fullname
256 +
257 +(The fields marked by the asterix sign are required and cannot have an
258 + empty results)
259 +
260 +So, the typical query clause may start with:
261 +
262 +MYSQL_SELECT_CLAUSE SELECT                                      \
263 + users.username,                                               \
264 + users.cryptpw,                                                        \
265 + users.clearpw,                                                \
266 + domains.uid,                                                  \
267 + domains.gid,                                                  \
268 + users.mailbox_path)                                           \
269 + ''                                                            \ 
270 + domains.quota,                                                        \
271 + ''                                                            \
272 +...
273 +
274 +Note that in this short example we're assuming that we have two tables
275 +(users and domains) and INBOX path is always called 'Maildir' and
276 +we're not using the fullname field (the query will always return an empty
277 +string in its place).
278 +
279 +Also note that you may discard one of the password fields if you don't want
280 +to use an authentication mechanism, which needs it. For example, if you don't
281 +want to use MD5-CRAM you may put '' into the place of clearpw (because, for
282 +example you're in paranoid mode and you don't even want to keep plain passwords
283 +in the database:).
284 +
285 +3.3 substitutions
286 +
287 +Substitutions are strings, which may appear in your query, and which have a
288 +special meaning. You can also call them substitution variables. If substitution
289 +variable is known for a clause context then it is parsed. If it isn't known the
290 +error is generated. In the default compilation of authmysql module any
291 +substitution variable is declared inside of two substrings - the first is a 
292 +dollar sign concatenated with opening parenthesis, and the second is a closing
293 +parenthesis sign. First symbol identifies beginning of a substitution variable,
294 +and the second closes it. The string between the beginning and the closing
295 +symbol is called substitution variable's name.
296 +
297 +When, as I said before, the name is known to the parsing routine the
298 +substitution is made and the proper value appears in place of the substitution
299 +variable, while passing on the query for later processing.
300 +
301 +Allowed substitution variables:
302 +
303 +context: MYSQL_SELECT_CLAUSE, MYSQL_ONFAIL_CLAUSE, MYSQL_ONSUCCESS_CLAUSE
304 +
305 +$(local_part)          will be replaced by currently verified user's username
306 +                       (without the domain part)
307 +
308 +$(domain)              will be replaced by currently verified user's domain
309 +                       name (if present, or if not present but the 
310 +                       DEFAULT_DOMAIN was used) or by the empty, zero-length
311 +                       string if the domain cannot be obtained
312 +
313 +$(username)            will be replaced by currently verified user's username
314 +                       concatenated with the given domain name using symbol
315 +                       defined by USER_DOMAIN_CONCAT - if the domiain name
316 +                       cannot be obtained (even by looking up DEFAULT_DOMAIN)
317 +                       the separation sign will not appear and only the given
318 +                       username will be presented
319 +
320 +context: MYSQL_CHPASS_CLAUSE
321 +
322 +$(local_part)           will be replaced by currently verified user's username
323 +                        (without the domain part)
324 +
325 +$(domain)               will be replaced by currently verified user's domain
326 +                        name (if present, or if not present but the
327 +                        DEFAULT_DOMAIN was used) or by the empty, zero-length
328 +                        string if the domain cannot be obtained
329 +
330 +$(username)             will be replaced by currently verified user's username
331 +                        concatenated with the given domain name using symbol
332 +                        defined by USER_DOMAIN_CONCAT - if the domiain name
333 +                        cannot be obtained (even by looking up DEFAULT_DOMAIN)
334 +                        the separation sign will not appear and only the given
335 +                        username will be presented
336 +
337 +$(newpass)             will be replaced by currently authenticated user's
338 +                       new password to set up (plaintext password)
339 +
340 +$(newpass_crypt)       will be replaced by currently authenticated user's
341 +                       new password to set up (MD5 form created from entered
342 +                       plain form)
343 +
344 +3.4 triggers
345 +
346 +Triggers are MySQL queries, which are performed depending on authentication
347 +state. Currently, there are two triggers which you may use. First is called
348 +MYSQL_ONSUCCESS_CLAUSE and it is performed when the authentication succeedes.
349 +The second is called MYSQL_ONFAIL_CLAUSE and has the reverse meaning. You can
350 +declare triggers in the authmysqlrc configuration file. They can be used to
351 +arrange some logging facility in the database or just to keep last times
352 +of the successful/failed login tries. The typical trigger, which puts last
353 +login date into the users' table can look like this:
354 +
355 +MYSQL_ONSUCCESS_CLAUSE UPDATE users SET last_login=CURRENT_TIMESTAMP \
356 +                       WHERE username='$(username)';
357 +
358 +or, if you would like to know about last login failure for users you can try:
359 +
360 +MYSQL_ONFAIL_CLAUSE    UPDATE users SET last_bad_login=CURRENT_TIMESTAMP \
361 +                       WHERE username='$(username)';
362  
363 -Courier-imap-myownquery.patch allows administrator to set own MySQL queries
364 -used by authdaemon to authenticate user (including fetchig credentials) and to
365 -change user's password. It allows to construct SELECT or UPDATE clause in the
366 -configuration file (authmysqlrc) by adding two new configuration variables:
367 -MYSQL_SELECT_CLAUSE and MYSQL_CHPASS_CLAUSE. It may be useful in the mail
368 -environments where there is such a need to have different database structure
369 -and/or tables scheme than expected by authmysql module.
370 +Note, that YOU CAN use the triggers even if you aren't using
371 +MYSQL_SELECT_CLAUSE. Also note, that there is such a possibility that ONFAIL
372 +trigger may be performed without a proper username. Take it into consideration
373 +when creating queries to avoid messy data on INSERT operations.
374  
375 -It also implements a small parsing engine for substitution variables which
376 -may appear in the clauses and are used to put informations like username
377 -or domain into the right place of a query.
378  
379 -This patch was created using `diff -Nur` on courier-imap-1.3.12 source.
380  
381  
382  
383  
384 +                   *-----------------------
385 +                    4 Examples of usage
386 +                   *-----------------------
387 +
388 +The "ownquery" feature gives you possibility to adapt an authentication query
389 +to the database. So the first thing you have to do is to design the database
390 +structure you need, whithout being grieved at what structure authentication
391 +routines like. You have to take care about four essential things:
392 +
393 + o  The database
394
395 + o  The users' data in the database
396
397 + o  The proper directories for keeping virtual mailboxes and a system user
398 +    which can read and write them
399
400 + o  The proper MySQL queries in your authmysqlrc configuration file
401 +
402 +4.1 corporate mail system
403 +
404 +This example is concerned about a corporate mail system with a small
405 +ammount of served virtual domains. The database scheme was derived from tpop3d
406 +documentation and modified a bit.
407 +
408 +4.1.1 database structure
409 +
410 +Our goal here is to separate the data responsible for keeping mailbox
411 +credentials from the data, which describes a domain.
412 +
413 +Let's create some tables for our example, filled up with an example data:
414 +
415 +table:          domains
416 +
417 +purpose:        associates virtual domain with domain name and informations
418 +               necessary to access mailboxes withing the domain
419 +
420 +fields:         domain_name             - fully qualified domain name
421 +                path_prefix             - absolute pathname which points to
422 +                                          a directory where domain's mailboxes
423 +                                          are located
424 +                quota                   - default quota for each mailbox
425 +                uid                     - UID used to work on mailboxes
426 +                gid                     - GID used to work on mailboxes
427 +               
428 +        +----------------+-------------+-----+-----+----------+
429 +        | domain_name    | path_prefix | uid | gid | quota    |
430 +        +----------------+-------------+-----+-----+----------+
431 +        | exampledom.com | /var/mail/x | 555 | 555 | 10000000 |
432 +       | pld.org.pl     | /var/mail/p | 556 | 556 | 20000000 |
433 +       | pld.net.pl     | /var/mail/p | 556 | 556 | 20000000 |
434 +       +----------------+-------------+-----+-----+----------+
435 +
436 +table:          users
437 +
438 +purpose:        associates virtual mailbox with user and domain name,
439 +               and with informations necessary to access mailbox
440 +
441 +fields:         username               - user login name (mailbox name)
442 +               domain_name             - fully qualified domain name
443 +                mailbox_path            - relative pathname for mailbox
444 +                                          (will be appended to the path_prefix
445 +                                          from domain_auth table to specify
446 +                                         user's mailbox location)
447 +                cryptpw                 - crypted password
448 +                plainpw                 - plaintext password
449 +
450 +        +----------+----------------+--------------+------------+--------+
451 +       | username | domain_name    | mailbox_path | cryptpw   | plainpw |
452 +       +----------+----------------+--------------+-----------+---------+
453 +       | siefca   | pld.org.pl     | s/siefca     | $1$fs45.. | dupa.8  |
454 +       | siefca   | pld.net.pl     | s/siefca     | $1$fs45.. | dupa.8  |
455 +       | f00bar   | exampledom.com | foobar       | $1$g44w.. | secret  |
456 +       +----------+----------------+--------------+-----------+---------+ 
457 +
458 +Using MySQL monitor you can create these tables entering CREATE sequences.
459 +Be sure to connect to the database using administrative MySQL account
460 +(usualy: mysql -u mysql -p).
461 +
462 +--------------------- cut here
463 +
464 +# Create the database called vmail.
465 +
466 +CREATE database vmail;
467 +
468 +# Create an example MySQL user, which can read, write and delete data from
469 +# vmail database. Username: vuser Password: secret_password
470 +
471 +GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
472 +             TO vuser@localhost
473 +             IDENTIFIED BY 'secret_password';
474 +
475 +FLUSH PRIVILEGES;
476 +
477 +# Create the tables.
478 +
479 +use vmail;
480 +
481 +CREATE TABLE domains (
482 +        domain_name           char(255) DEFAULT '',
483 +        path_prefix           char(255) DEFAULT '' NOT NULL,
484 +        uid                   int(10) unsigned DEFAULT '15000' NOT NULL,
485 +        gid                   int(10) unsigned DEFAULT '15000' NOT NULL,
486 +        quota                 char(255) DEFAULT '2000000' NOT NULL,
487 +        KEY domain_name (domain_name(255))
488 +        );
489 +
490 +CREATE TABLE users (
491 +        username              char(128) DEFAULT '' NOT NULL,
492 +        domain_name           char(255) DEFAULT '',
493 +        mailbox_path          char(255) DEFAULT '' NOT NULL,
494 +        cryptpw               char(128) DEFAULT '' NOT NULL,
495 +        clearpw               char(128) DEFAULT '' NOT NULL,
496 +        KEY username (username(128))
497 +        );
498 +
499 +# Create an example virtual domain entry
500 +# name  : exampledom.com
501 +# uid   : 555
502 +# gid   : 555
503 +# path  : /var/mail/x
504 +# quota : 10 Megs per mailbox
505 +
506 +INSERT INTO domains VALUES ('exampledom.com', '/var/mail/x', 555, 555,
507 +                            '10000000');
508 +
509 +# Create an example virtual user entry
510 +# username      : siefca
511 +# domain name   : exampledom.com
512 +# cryptpw       : $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
513 +# clearpw       : dupa.8
514 +# mailbox path  : s/siefca
515 +
516 +INSERT INTO users VALUES ('siefca', 'exampledom.com', 's/siefca',
517 +                         '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
518 +                         'dupa.8');
519 +
520 +--------------------- cut here
521 +
522 +Note:  If you would like to have your passwords more safe then just omit the
523 +       clearpw column and put '' into the config-query in its place while
524 +       doing SELECT on a database. But be ware - you'll be unable to use
525 +       authentication methods which needs it, like MD5_CRAM.
526 +
527 +4.1.2 authdaemon configuration
528 +
529 +When our database is ready we can set up the configuration. :-) Go to
530 +authmysqlrc file and edit it.
531 +
532 +At the beginning we should take care about general informations, which
533 +are identifying our database:
534 +
535 +MYSQL_SERVER            localhost
536 +MYSQL_USERNAME          vuser
537 +MYSQL_PASSWORD          secret_password
538 +MYSQL_DATABASE          vmail
539 +
540 +Then we should add a clause responsible for authenticating user and
541 +fetching credentials:
542 +
543 +DEFAULT_DOMAIN          exampledom.com
544 +
545 +MYSQL_SELECT_CLAUSE SELECT                                      \
546 + users.username, users.cryptpw, users.clearpw,                  \
547 + domains.uid, domains.gid,                                      \
548 + CONCAT_WS('/',domains.path_prefix,users.mailbox_path),         \
549 + '', domains.quota, ''                                          \
550 + FROM users, domains                                            \
551 +  WHERE domains.domain_name='$(domain)'                         \
552 +    AND users.username='$(local_part)'                          \
553 +    AND domains.domain_name=users.domain_name
554 +
555 +
556 +Note the '' in the place of field which tells where user's INBOX resides
557 +and in place of realname field. You should use '' if you want to put an empty
558 +value as a query result for some field.
559 +
560 +We also should add some configuration for changing user's password:
561 +
562 +MYSQL_CHPASS_CLAUSE UPDATE                                             \
563 + users                                                                 \
564 + SET clearpw='$(newpass)',                                             \
565 +     cryptpw='$(newpass_crypt)'                                        \
566 + WHERE username='$(local_part)'                                        \
567 + AND   domain_name='$(domain)'
568 +
569 +And finally...
570 +Create a system user/group and a proper directory structure. In our example:
571 +
572 +groupadd -g 555 xdomain
573 +useradd -u 555 -g 555 xdomain
574 +mkdir -p /var/mail/x/s/siefca
575 +chmod -R 0770 /var/mail/x
576 +maildirmake /var/mail/x/s/siefca/Maildir
577 +chown -R xdomain.xdomain /var/mail/x
578 +
579 +Now, restart the authdaemon and see if it works. Try: telnet 0 pop3
580 +
581 +and type:
582 +
583 +USER siefca [ENTER]
584 +PASS dupa.8 [ENTER]
585 +
586 +You should get Ok response. ;)
587 +
588 +4.2 virtual mail domains provider
589 +
590 +Let's consider more complicated database scheme, where there is a need to
591 +associate a lot of information with the domain name, including registrant
592 +information, owner, etc. That implies data separation between domain name,
593 +user and domain additional informations (which are unwanted when 
594 +authentication process takes place). By proper data separation I mean 
595 +avoiding unwanted redundancy in the database.
596 +
597 +Currently applied example doesn't care about the update password problem.
598 +This is due to current abilities of MySQL and authdaemon (authmysql).
599 +MySQL doesn't support subsequent SELECTs on UPDATE operation, and authmysql
600 +doesn't supports batched queries at the moment.
601 +
602 +4.2.1 database structure
603 +
604 +table:         domain_names
605 +
606 +purpose:       associates domain_id with domain name
607 +
608 +fields:                domain_name             - fully qualified domain name
609 +               domain_id               - domain identifier
610 +
611 +       +----------------+-----------+
612 +       | domain_name    | domain_id |
613 +       +----------------+-----------+
614 +       | exampledom.com |         1 |
615 +       | pld.org.pl     |         2 |
616 +       | pld.net.pl     |         2 |
617 +       | foobare.net.uk |         3 |
618 +       +----------------+-----------+
619 +
620 +Note, that for pld.org.pl and pld.net.pl the domain identifiers are the same.
621 +We can create a domain aliases in such a way. :)
622 +
623 +table:         domain_auth
624 +
625 +purpose:       associates domain_id with authentication credentials
626 +               which are common for all users in the virtual domain
627 +
628 +fields:                domain_id               - domain identifier
629 +               path_prefix             - absolute pathname which points to
630 +                                         a directory where domain's mailboxes
631 +                                         are located
632 +               quota                   - default quota for each mailbox
633 +               uid                     - UID used to work on mailboxes
634 +               gid                     - GID used to work on mailboxes
635 +               
636 +       +------------+---------------+--------+-------+-------+
637 +       | domain_id  | path_prefix   | quota  | uid   | gid   |
638 +       +------------+---------------+--------+-------+-------+
639 +       |          1 | /var/mail/ex  | 100000 | 15000 | 15000 | 
640 +       |          2 | /var/mail/pld | 555500 | 15001 | 15000 |
641 +       |          3 | /home/f0/mail |   8000 | 15002 | 15000 |
642 +       +------------+---------------+--------+-------+-------+
643 +
644 +table:         domain_info
645 +
646 +purpose:       associates domain_id with additional informations
647 +
648 +fields:                domain_id               - domain identifier
649 +               registrant_id           - registrant identifier
650 +               nic_handle              - NIC handle
651 +               owner_id                - domain's owner identifier
652 +               expires                 - domain's expiration date
653 +
654 +       +------------+---------------+------------+----------+---------+
655 +       | domain_id  | registrant_id | nic_handle | owner_id | expires |
656 +       +------------+---------------+------------+----------+---------+
657 +
658 +       (we don't need to say anything more about this table indeed)
659 +
660 +table:         users
661 +
662 +purpose:       associates users' identifiers with domains' identifiers
663 +               and infers the credentials for various virtual mailboxes
664 +
665 +fields:                username                - user's login name
666 +               domain_id               - domain identifier
667 +               cryptpw                 - crypted password
668 +               plainpw                 - plaintext password
669 +               quota                   - user's mailbox quota
670 +                                         (will override quota value set for
671 +                                         the whole virtual domain)
672 +               path                    - relative pathname for mailbox
673 +                                         (will be appended to the path_prefix
674 +                                         from domain_auth table to specify
675 +                                         user's mailbox location)
676 +
677 +       +------------+-----------+----------+-----------+-------+------------+
678 +       | username   | domain_id | cryptpw  | plainpw   | quota | path       |
679 +       +------------+-----------+----------+-----------+-------+------------+
680 +       | foobar     |         1 | $1$hlIeE | dupa.8    | NULL  | f/o/foobar |
681 +       | breeder    |         2 | $1$TWsdf | ziarno128 | 77777 | brd        |
682 +       +------------+-----------+----------+-----------+-------+------------+
683 +
684 +    (you can add a realname column here, it doesn't fit to my terminal window:)
685 +
686 +--------------------- cut here
687 +
688 +# Create the database called vmail.
689 +
690 +CREATE database vmail;
691 +
692 +# Create an example MySQL user, which can read, write and delete data from
693 +# vmail database. Username: vuser Password: secret_password
694 +
695 +GRANT SELECT,INSERT,UPDATE,DELETE ON vmail.*
696 +            TO vuser@localhost
697 +            IDENTIFIED BY 'secret_password';
698 +
699 +FLUSH PRIVILEGES;
700 +
701 +# Create the tables.
702 +
703 +use vmail;
704 +
705 +CREATE TABLE domain_names (
706 +       domain_id             int(10) unsigned NOT NULL,
707 +       domain_name           char(255) DEFAULT '' NOT NULL,
708 +       KEY domain_name (domain_name(255))
709 +       );
710 +
711 +CREATE TABLE domain_auth (
712 +       domain_id             int(10) unsigned DEFAULT 1 NOT NULL,
713 +       uid                   int(10) unsigned DEFAULT '15000' NOT NULL,
714 +       gid                   int(10) unsigned DEFAULT '15000' NOT NULL,
715 +       path_prefix           char(255) DEFAULT '' NOT NULL,
716 +       quota                 char(255) DEFAULT '20000000' NOT NULL,
717 +       KEY domain_id (domain_id)
718 +       );
719 +
720 +CREATE TABLE users (
721 +        username             char(128) DEFAULT '' NOT NULL,
722 +       domain_id             int(10) unsigned DEFAULT 1 NOT NULL,
723 +        cryptpw               char(128) DEFAULT '' NOT NULL,
724 +       plainpw               char(128) DEFAULT '' NOT NULL,
725 +        name                  char(128) DEFAULT '' NOT NULL,
726 +        quota                 char(255),
727 +       path                  char(255) DEFAULT '' NOT NULL,
728 +        KEY username (username(128))
729 +       );
730 +       
731 +# Create an example virtual domain entry
732 +# id   : 1
733 +# name : exampledom.com
734 +# uid  : 15000
735 +# gid  : 15000
736 +# path : /var/mail/example
737 +# quota        : 20 Megs per mailbox
738 +
739 +INSERT INTO domain_names VALUES (1, 'exampledom.com');
740 +INSERT INTO domain_auth VALUES (1, '15000', '15000', '/var/mail/example',
741 +                               '20000000');
742 +
743 +# Create an example virtual user entry
744 +# username     : siefca
745 +# domain id    : 1 (points to exampledom.com)
746 +# cryptpw      : $1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/
747 +# clearpw      : dupa.8
748 +# name         : Pawel Wilk
749 +# quota                : NULL (we want it to be fetched from domain_auth table)
750 +# mailbox path : s/i/siefca
751 +
752 +INSERT INTO users VALUES ('siefca', 1, '$1$wIfVZ8uK$qhagYAcIoZpQM83Et7c1e/',
753 +                         'dupa.8', 'Pawel Wilk', NULL, 's/i/siefca');
754 +
755 +--------------------- cut here
756 +
757 +Ok, we've done what we need. Don't forget to create system user with UID and
758 +GID set to 15000, and a directory containing mailboxes (in this case:
759 +/var/mail/example) owned by system user I've mentioned above.
760 +There is also necessary to create Maildir folder structure for our user
761 +inside the virtual domain directory - you can configure your MTA agent to do
762 +such thing when first message arrive or use maildirmake tool, which comes
763 +with Courier-IMAP.
764 +
765 +
766 +4.2.2 authdaemon configuration
767 +
768 +DEFAULT_DOMAIN         exampledom.com
769 +
770 +MYSQL_SELECT_CLAUSE SELECT                                     \
771 + users.username,                                               \
772 + users.cryptpw,                                                        \
773 + users.plainpw,                                                        \
774 + domain_auth.uid,                                              \
775 + domain_auth.gid,                                              \
776 + CONCAT_WS('/',domain_auth.path_prefix,users.path),            \
777 + '',                                                           \
778 + IFNULL(users.quota, domain_auth.quota),                       \
779 + users.name                                                    \
780 + FROM users, domain_names, domain_auth                                 \
781 + WHERE domain_names.domain_name='$(domain)'                    \
782 +  AND users.username='$(local_part)'                           \
783 +  AND domain_names.domain_id=users.domain_id                   \
784 +  AND domain_names.domain_id=domain_auth.domain_id
785 +
786 +
787 +.
788 +.
789 +.
790 +.
791 +.
792 +.
793 +
794 +/////////////////////////// PART II - Developer Notes /////////////////////////
795  
796                     *-----------------------
797                      1 Modifications overview
798                     *-----------------------
799  
800 -Modified files:        authmysqllib.c authmysqlrc
801 +Modified files:        authmysqllib.c authmysql.c authmysql.h authmysqlrc
802  
803  Each modified set of instructions is marked by my e-mail address:
804  siefca@pld.org.pl
805  
806 -Changes in the current source code are related to:
807 +Changes in the source code are related to:
808  
809  - sections where the queries are constructed
810    (including memory allocation for the buffers)
811 @@ -102,6 +785,10 @@
812         newline as the second is replaced by two whitespaces while
813         putting into the buffer
814  
815 +       i've also added USER_DOMAIN_CONCAT and USER_DOMAIN_SEPARATORS
816 +       configuration options - they're used by get_localpart(), get_domain()
817 +       and get_username() functions, which are described below
818 +
819  - sections where the query is constructed
820  
821         selection is made, depending on configuration variables which
822 @@ -130,7 +817,16 @@
823  MAX_SUBSTITUTION_LEN defines maximal length of a substitution variable's
824  identifier (name).
825  
826 -The last two definitions are just for code simplification.
827 +The last two definitions (SV_BEGIN_LEN and SV_END_LEN) are just for code
828 +simplification.
829 +
830 +#define         DEF_CONCAT_STRING       "@"
831 +#define         DEF_SEPARATORS_SET      "@%"
832 +
833 +The first (DEF_CONCAT_STRING) is used to set the defaults for a
834 +concatenation string, used when parsing $(username) substitution variable.
835 +The second (DEF_SEPARATORS_SET) is the set of characters, which are treated as
836 +separators when splitting local part from the domain.
837  
838  
839  
840 @@ -179,7 +875,7 @@
841  In this example we've declared that $(some) in the query should be
842  replaced by 'replacement' text, and replacement for $(anotha) will
843  be defined in the code before passing on the array pointer to
844 -the paring function.
845 +the general parsing function.
846  
847  
848  3.2 typedef size_t (*parsefunc)
849 @@ -230,6 +926,10 @@
850         structure of var_data type, which contains variable definition
851         of a given name. It returns NULL on error or failure.
852  
853 +FILES
854 +
855 +       authlib/authmysqllib.c
856 +
857  
858  4.2 parse_core
859  
860 @@ -285,6 +985,11 @@
861  
862         This function returns -1 if an error has occured and 0 if
863         everything went good.
864 +
865 +FILES
866 +
867 +       authlib/authmysqllib.c
868 +
869         
870  4.3 ParsePlugin_counter
871  
872 @@ -314,6 +1019,11 @@
873         This function returns the variable size or -1 if an error
874         has occured, 0 if everything went good.
875  
876 +FILES
877 +
878 +       authlib/authmysqllib.c
879 +
880 +
881  4.4 ParsePlugin_builder
882  
883  NAME
884 @@ -333,7 +1043,7 @@
885         type pointer and refers to the (char *) pointer variable.
886         After each call it shifts the value of pointer variable (char *)
887         incrementing it by len bytes. Be careful when using this function
888 -       - its changes the given pointer value. Always operate on an
889 +       - it changes the given pointer value. Always operate on an
890         additional pointer type variable when passing it as the third
891         argument.
892  
893 @@ -342,6 +1052,10 @@
894         This function returns the variable size or -1 if an error
895         has occured, 0 if everything went good.
896  
897 +FILES
898 +
899 +       authlib/authmysqllib.c
900 +
901  4.5 parse_string
902  
903  NAME
904 @@ -353,7 +1067,7 @@
905  
906  DESCRIPTION
907  
908 -       This function parses the string pointed with source according to the
909 +       This function parses the string pointed to by source according to the
910         replacement instructions set in var_data array, which is passed with
911         its pointer vdt. It produces changed string located in newly allocated
912         memory area.
913 @@ -377,6 +1091,10 @@
914         Function returns pointer to the result buffer or NULL
915         if an error has occured.
916  
917 +FILES
918 +
919 +       authlib/authmysqllib.c
920 +
921  WARNINGS
922  
923         This function allocates some amount of memory using standard
924 @@ -405,6 +1123,10 @@
925         It returns a pointer to the static buffer which contains
926         validated password string or NULL if an error has occured.
927  
928 +FILES
929 +
930 +       authlib/authmysqllib.c
931 +
932  
933  4.7 get_localpart
934  
935 @@ -414,20 +1136,28 @@
936  
937  SYNOPSIS
938  
939 -       static const char *get_localpart (const char *username);
940 +       static const char *get_localpart (const char *username,
941 +                                         const char *separators);
942  
943  DESCRIPTION
944  
945         This function detaches local part of an e-mail address
946         from string pointed with username and puts it to the
947         buffer of the fixed length. All necessary cleaning is
948 -       made on the result string.
949 +       made on the result string. String pointed with separators
950 +       refers to a set of characters, which are treated as
951 +       separation signs between local part and a domain.
952  
953  RETURN VALUE
954  
955         Pointer to the static buffer containing local part or
956         NULL if there was some error.
957  
958 +FILES
959 +
960 +       authlib/authmysqllib.c
961 +
962 +
963  
964  4.8 get_domain
965  
966 @@ -438,24 +1168,68 @@
967  SYNOPSIS
968  
969         static const char *get_domain (const char *username, 
970 -                                      const char *defdomain);
971 +                                      const char *defdomain,
972 +                                      const char *separators);
973  
974  DESCRIPTION
975  
976          This function detaches domain part of an e-mail address
977         from string pointed with username and puts it to the
978         buffer of the fixed length. All necessary cleaning is
979 -       made on the result string. If function cannot find domain
980 -       part in the string the string pointed by defdomain is
981 -       used instead.
982 +       made on the result string. If the function cannot find a domain
983 +       part in the string then the string pointed to by defdomain is
984 +       used instead. If this function cannot find a domain part
985 +       as well as it cannot obtain the default domain (it's empty string
986 +       or the defdomain pointer is NULL) the returned result string is an
987 +       empty string. The string pointed with separators refers to a set
988 +       of characters, which are treated as separation signs between local
989 +       part and a domain.
990  
991  RETURN VALUE
992  
993          Pointer to the static buffer containing domain name or
994         NULL if there was some error.
995  
996 +FILES
997 +
998 +       authlib/authmysqllib.c
999 +
1000  
1001 -4.9 parse_select_clause
1002 +4.9 get_username
1003 +
1004 +NAME
1005 +
1006 +       get_username
1007 +
1008 +SYNOPSIS
1009 +
1010 +       static const char *get_username (const char *username,
1011 +                                        const char *domainname,
1012 +                                        const char *concat_str);
1013 +
1014 +DESCRIPTION
1015 +
1016 +       This function concatenates the localpart with a domain name
1017 +       using the string pointed with concat_str. If the domain is
1018 +       empty or NULL the result comes without binding string.
1019 +
1020 +RETURN VALUE
1021 +
1022 +        Pointer to the static buffer containing output string or
1023 +        NULL if there was some error.
1024 +
1025 +FILES
1026 +
1027 +       authlib/authmysqllib.c
1028 +
1029 +WARNINGS
1030 +
1031 +       This function does not any string cleaning, nor default domain
1032 +       checking. It is designed to work on results of get_localpart() and
1033 +       get_domain().
1034 +
1035 +
1036 +4.10 parse_select_clause
1037  
1038  NAME
1039  
1040 @@ -465,7 +1239,9 @@
1041  
1042         static char *parse_select_clause (const char *clause,
1043                                           const char *username,
1044 -                                         const char *defdomain);
1045 +                                         const char *defdomain
1046 +                                         const char *concat_str,
1047 +                                         const char *separators_set);
1048  
1049  DESCRIPTION
1050  
1051 @@ -473,15 +1249,21 @@
1052         function. It parses a query pointed by caluse. username
1053         and defdomain strings are used to replace corresponding
1054         substitution strings if present in the query: $(local_part)
1055 -       and $(domain).
1056 +       and $(domain). The separators_set is passed to get_username()
1057 +       and get_domain() invocations, and the concat_str is passed
1058 +       to get_username() function, which is responsible for replacing
1059 +       $(username) substitution variable.
1060         
1061 -
1062  RETURN VALUE
1063  
1064         Same as parse_string().
1065  
1066 +FILES
1067 +
1068 +       authlib/authmysqllib.c
1069  
1070 -4.10 parse_chpass_clause
1071 +
1072 +4.11 parse_chpass_clause
1073  
1074  NAME
1075  
1076 @@ -492,6 +1274,8 @@
1077          static char *parse_chpass_clause (const char *clause,
1078                                            const char *username,
1079                                            const char *defdomain,
1080 +                                         const char *separators_set,
1081 +                                         const char *concat_str,
1082                                           const char *newpass,
1083                                           const char *newpass_crypt);
1084                                                                     
1085 @@ -502,12 +1286,56 @@
1086         defdomain, newpass and newpass_crypt strings are used to
1087         replace corresponding substitution strings if present in
1088         the query: $(local_part), $(domain), $(newpass),
1089 -       $(newpass_crypt).
1090 +       $(newpass_crypt). The separators_set and the concat_str
1091 +       are passed to get_localpart(), get_domain(), and get_username()
1092 +       functions as described in the entry for parse_select_clause().
1093  
1094  RETURN VALUE
1095  
1096         Same as parse_string().
1097  
1098 +FILES
1099 +
1100 +       authlib/authmysqllib.c
1101 +
1102 +
1103 +4.12 auth_mysql_on_trigger
1104 +
1105 +NAME
1106 +
1107 +       auth_mysql_on_trigger
1108 +
1109 +SYNOPSIS
1110 +
1111 +       int auth_mysql_on_trigger (const char *clause_name,
1112 +                                  const char *username);
1113 +
1114 +DESCRIPTION
1115 +
1116 +       This function is responsible for calling out the MySQL queries 
1117 +       depending on which authentication state was reached.
1118 +
1119 +       The clause_name should contain the name of a clause, which can be found
1120 +       in the configuration file, and the username is simply the string used
1121 +       as username (including the domain if entered).
1122 +
1123 +       This function reads DEFAULT_DOMAIN, USER_DOMAIN_CONCAT and
1124 +       USER_DOMAIN_SEPARATORS from the configuration file using read_env(),
1125 +       then it uses parse_select_clause() to parse the query obtained using
1126 +       read_env(clause_name), and then it calls querying subroutines to
1127 +       perform the action.
1128 +
1129 +RETURN VALUE
1130 +
1131 +       This function returns 1 on success and 0 on failure. The query results
1132 +       are simply discarded. If a trigger's clause is not defined in the
1133 +       configuration file the 1 is returned and function silently ends its
1134 +       work.
1135 +
1136 +FILES
1137 +
1138 +       authlib/authmysql.h
1139 +       authlib/authmysql.c
1140  
1141  
1142  
1143 @@ -520,11 +1348,9 @@
1144    strings after split                                          (problem?)
1145  - allow admin to set a group name instead of numerical group id
1146  - allow admin to set a username instead of numerical user id
1147 -
1148 -- add clauses:
1149 -
1150 -  - MYSQL_PRESELECT_CLAUSE (query which comes before MYSQL_SELECT_CLAUSE)
1151 -  - MYSQL_POSTSELECT_CLAUSE (query which comes after MYSQL_SELECT_CLAUSE)
1152 +- allow batched queries and register variables for keeping results
1153 +- put the parsing routines into separate files to make possible of sharing it
1154 +  by more authentication modules
1155  
1156  
1157  
1158 @@ -534,10 +1360,20 @@
1159                      6 Thanks
1160                     *------------------------
1161  
1162 -At the beginning this patch was messy indeed. :> I would like to thank
1163 -Sam Varshavchik for pointing me a lot how to make it more fast and solid.
1164 -I would also thank Philip Hazel, Chris Lightfoot and Mike Bremford which
1165 -by their software capabilities inspired me to write it.
1166 +At the beginning the patch was messy indeed. :> I would like to thank:
1167 +
1168 +Sam Varshavchik
1169 +  for pointing me a lot, how to make it more fast and solid
1170 +           
1171 +Philip Hazel, Chris Lightfoot, Mike Bremford
1172 +  which by their software's capabilities inspired me to write it
1173 +               
1174 +Oliver Oblasnik
1175 +  which remainded me to make the documentation more friendly for
1176 +  those who are not programmers and just want to use it
1177 +  
1178 +Jacek Surazski
1179 +  for reviewing this document just before it was published
1180  
1181  ---------------------------------------------------------------------------
1182  
1183 diff -ur courier-imap-1.5.3.20020921.orig/authlib/authmysql.c courier-imap-1.5.3.20020921/authlib/authmysql.c
1184 --- courier-imap-1.5.3.20020921.orig/authlib/authmysql.c        Mon Aug 19 17:52:28 2002
1185 +++ courier-imap-1.5.3.20020921/authlib/authmysql.c     Sat Sep 28 00:01:07 2002
1186 @@ -31,7 +31,11 @@
1187         if ((user=strtok(authdata, "\n")) == 0 ||
1188                 (pass=strtok(0, "\n")) == 0)
1189         {
1190 -               errno=EPERM;
1191 +               if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1192 +                       errno=EACCES;
1193 +               else
1194 +                       errno=EPERM;
1195 +                       
1196                 return (0);
1197         }
1198  
1199 @@ -50,7 +54,11 @@
1200         {
1201                 if (authcheckpassword(pass,authinfo->cryptpw))
1202                 {
1203 -                       errno=EPERM;
1204 +                       if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1205 +                           errno=EACCES;
1206 +                       else
1207 +                           errno=EPERM;
1208 +       
1209                         return (0);     /* User/Password not found. */
1210                 }
1211         }
1212 @@ -58,13 +66,21 @@
1213         {
1214                 if (strcmp(pass, authinfo->clearpw))
1215                 {
1216 -                       errno=EPERM;
1217 +                       if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1218 +                           errno=EACCES;
1219 +                       else
1220 +                           errno=EPERM;
1221 +
1222                         return (0);
1223                 }
1224         }
1225         else
1226         {
1227 -               errno=EPERM;
1228 +               if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1229 +                   errno=EACCES;
1230 +               else
1231 +                   errno=EPERM;
1232 +
1233                 return (0);             /* Username not found */
1234         }
1235  
1236 @@ -132,6 +148,12 @@
1237                 (*callback_func)(&aa, callback_arg);
1238         }
1239  
1240 +       if (!auth_mysql_on_trigger("MYSQL_ONSUCCESS_CLAUSE", user))
1241 +       {
1242 +           errno=EACCES;
1243 +           return (0);
1244 +       }
1245 +
1246         return (strdup(authinfo->username));
1247  }
1248  
1249 @@ -153,7 +175,11 @@
1250         {
1251                 if (authcheckpassword(pass,authinfo->cryptpw))
1252                 {
1253 -                       errno=EPERM;
1254 +                       if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1255 +                           errno=EACCES;
1256 +                       else
1257 +                           errno=EPERM;
1258 +
1259                         return (-1);    /* User/Password not found. */
1260                 }
1261         }
1262 @@ -161,13 +187,21 @@
1263         {
1264                 if (strcmp(pass, authinfo->clearpw))
1265                 {
1266 -                       errno=EPERM;
1267 +                       if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1268 +                           errno=EACCES;
1269 +                       else
1270 +                           errno=EPERM;
1271 +
1272                         return (-1);
1273                 }
1274         }
1275         else
1276         {
1277 -               errno=EPERM;
1278 +               if (!auth_mysql_on_trigger("MYSQL_ONFAIL_CLAUSE", user))
1279 +                       errno=EACCES;
1280 +               else
1281 +                       errno=EPERM;
1282 +
1283                 return (-1);
1284         }
1285  
1286 @@ -176,6 +210,13 @@
1287                 errno=EPERM;
1288                 return (-1);
1289         }
1290 +       
1291 +       if (!auth_mysql_on_trigger("MYSQL_ONSUCCESS_CLAUSE", user))
1292 +       {
1293 +               errno=EACCES;
1294 +               return (-1);
1295 +       }
1296 +       
1297         return (0);
1298  }
1299  
1300 diff -ur courier-imap-1.5.3.20020921.orig/authlib/authmysql.h courier-imap-1.5.3.20020921/authlib/authmysql.h
1301 --- courier-imap-1.5.3.20020921.orig/authlib/authmysql.h        Mon Aug  6 05:12:39 2001
1302 +++ courier-imap-1.5.3.20020921/authlib/authmysql.h     Sat Sep 28 00:01:07 2002
1303 @@ -21,6 +21,7 @@
1304         } ;
1305  
1306  extern struct authmysqluserinfo *auth_mysql_getuserinfo(const char *);
1307 +extern int auth_mysql_on_trigger (const char *clause_name, const char *username);
1308  extern void auth_mysql_cleanup();
1309  
1310  extern int auth_mysql_setpass(const char *, const char *);
1311 diff -ur courier-imap-1.5.3.20020921.orig/authlib/authmysqllib.c courier-imap-1.5.3.20020921/authlib/authmysqllib.c
1312 --- courier-imap-1.5.3.20020921.orig/authlib/authmysqllib.c     Sun Aug 11 22:01:25 2002
1313 +++ courier-imap-1.5.3.20020921/authlib/authmysqllib.c  Sat Sep 28 06:06:41 2002
1314 @@ -24,6 +24,9 @@
1315  #define                SV_BEGIN_LEN            ((sizeof(SV_BEGIN_MARK))-1)
1316  #define                SV_END_LEN              ((sizeof(SV_END_MARK))-1)
1317  
1318 +#define                DEF_CONCAT_STRING       "@"
1319 +#define                DEF_SEPARATORS_SET      "@%"
1320 +
1321  static const char rcsid[]="$Id$";
1322  
1323  /* siefca@pld.org.pl */
1324 @@ -268,7 +271,7 @@
1325                          SV_BEGIN_MARK
1326                          "%.*s"
1327                          SV_END_MARK
1328 -                        "\n", len, begin);
1329 +                        "\n", (int) len, begin);
1330         
1331         return NULL;
1332  }
1333 @@ -426,21 +429,43 @@
1334                 return NULL;
1335         }       
1336         *pass_buf = '\0';
1337 -       
1338 +
1339         return output_buf;
1340  }
1341  
1342  /* siefca@pld.org.pl */
1343 -static const char *get_localpart (const char *username)
1344 +static const char *get_username (const char *username, const char *domainname,
1345 +                                const char *concat_str)
1346 +{
1347 +static char    username_buf[400];
1348 +
1349 +       if (!username || !domainname || !concat_str ||
1350 +           *username == '\0' || *concat_str == '\0')   return NULL;
1351 +       if ((   strlen(username)   +
1352 +               strlen(concat_str) +
1353 +               strlen(domainname)) > 397)      return NULL;
1354 +
1355 +       if (*domainname == '\0')
1356 +               strcpy (username_buf, username);
1357 +       else
1358 +               sprintf (username_buf, "%s%s%s", username, concat_str,
1359 +                                                domainname);
1360 +
1361 +       return (username_buf);
1362 +}
1363 +
1364 +/* siefca@pld.org.pl */
1365 +static const char *get_localpart (const char *username, const char *separators)
1366  {
1367  size_t         lbuf    = 0;
1368  const char     *l_end, *p;
1369  char           *q;
1370  static char    localpart_buf[130];
1371         
1372 -       if (!username || *username == '\0')     return NULL;
1373 +       if (!username || *username == '\0' ||
1374 +           !separators || *separators == '\0') return NULL;
1375         
1376 -       p = strchr(username,'@');
1377 +       p = strpbrk (username, separators);
1378         if (p)
1379         {
1380                 if ((p-username) > 128)
1381 @@ -469,21 +494,27 @@
1382  }
1383  
1384  /* siefca@pld.org.pl */
1385 -static const char *get_domain (const char *username, const char *defdomain)
1386 +static const char *get_domain (const char *username, const char *defdomain,
1387 +                              const char *separators)
1388  {
1389  static char    domain_buf[260];
1390  const char     *p;
1391  char           *q;
1392         
1393 -       if (!username || *username == '\0')     return NULL;
1394 -       p = strchr(username,'@');
1395 +       if (!username || *username == '\0' ||
1396 +           !separators || *separators == '\0') return NULL;
1397 +       
1398 +       p = strpbrk (username, separators);
1399         
1400         if (!p || *(p+1) == '\0')
1401         {
1402 -               if (defdomain && *defdomain)
1403 +               if (defdomain && *defdomain != '\0')
1404                         return defdomain;
1405                 else
1406 -                       return NULL;
1407 +                 {
1408 +                   *domain_buf = '\0';
1409 +                   return domain_buf;
1410 +                 }
1411         }
1412  
1413         p++;
1414 @@ -536,20 +567,27 @@
1415  
1416  /* siefca@pld.org.pl */
1417  static char *parse_select_clause (const char *clause, const char *username,
1418 -                                 const char *defdomain)
1419 +                                 const char *defdomain,
1420 +                                 const char *concat_str,
1421 +                                 const char *separators_set)
1422  {
1423  static struct var_data vd[]={
1424             {"local_part",      NULL,   sizeof("local_part"),   0},
1425             {"domain",          NULL,   sizeof("domain"),       0},
1426 +           {"username",        NULL,   sizeof("username"),     0},
1427             {NULL,              NULL,   0,                      0}};
1428  
1429         if (clause == NULL || *clause == '\0' ||
1430 -           !username || *username == '\0')
1431 +           !username || *username == '\0' ||
1432 +           !concat_str || *concat_str == '\0' ||
1433 +           !separators_set || *separators_set == '\0')
1434                 return NULL;
1435         
1436 -       vd[0].value     = get_localpart (username);
1437 -       vd[1].value     = get_domain (username, defdomain);
1438 -       if (!vd[0].value || !vd[1].value)
1439 +       vd[0].value     = get_localpart (username, separators_set);
1440 +       vd[1].value     = get_domain (username, defdomain, separators_set);
1441 +       vd[2].value     = get_username (vd[0].value, vd[1].value, concat_str);
1442 +
1443 +       if (!vd[0].value || !vd[1].value || !vd[2].value)
1444                 return NULL;
1445         
1446         return (parse_string (clause, vd));
1447 @@ -557,12 +595,16 @@
1448  
1449  /* siefca@pld.org.pl */
1450  static char *parse_chpass_clause (const char *clause, const char *username,
1451 -                                 const char *defdomain, const char *newpass,
1452 +                                 const char *defdomain,
1453 +                                 const char *separators_set,
1454 +                                 const char *concat_str,
1455 +                                 const char *newpass,
1456                                   const char *newpass_crypt)
1457  {
1458  static struct var_data vd[]={
1459             {"local_part",      NULL,   sizeof("local_part"),           0},
1460             {"domain",          NULL,   sizeof("domain"),               0},
1461 +           {"username",        NULL,   sizeof("username"),             0},
1462             {"newpass",         NULL,   sizeof("newpass"),              0},
1463             {"newpass_crypt",   NULL,   sizeof("newpass_crypt"),        0},
1464             {NULL,              NULL,   0,                              0}};
1465 @@ -570,19 +612,83 @@
1466         if (clause == NULL || *clause == '\0'           ||
1467             !username || *username == '\0'              ||
1468             !newpass || *newpass == '\0'                ||
1469 +           !separators_set || *separators_set == '\0'  ||
1470             !newpass_crypt || *newpass_crypt == '\0')   return NULL;
1471  
1472 -       vd[0].value     = get_localpart (username);
1473 -       vd[1].value     = get_domain (username, defdomain);
1474 -       vd[2].value     = validate_password (newpass);
1475 -       vd[3].value     = validate_password (newpass_crypt);
1476 +       vd[0].value     = get_localpart (username, separators_set);
1477 +       vd[1].value     = get_domain (username, defdomain, separators_set);
1478 +       vd[3].value     = get_username (vd[0].value, vd[1].value, concat_str);
1479 +       vd[4].value     = validate_password (newpass);
1480 +       vd[5].value     = validate_password (newpass_crypt);
1481         
1482         if (!vd[0].value || !vd[1].value ||
1483 -           !vd[2].value || !vd[3].value)       return NULL;
1484 +           !vd[2].value || !vd[3].value ||
1485 +           !vd[4].value || !vd[5].value)       return NULL;
1486  
1487         return (parse_string (clause, vd));
1488  }
1489  
1490 +/* siefca@pld.org.pl */
1491 +int auth_mysql_on_trigger (const char *clause_name, const char *username)
1492 +{
1493 +char           *querybuf       =NULL;
1494 +const char     *concat_str     =NULL,
1495 +               *separators_set =NULL,
1496 +               *defdomain      =NULL,
1497 +               *on_clause      =NULL;
1498 +MYSQL_RES      *result;
1499 +
1500 +       if (!clause_name || *clause_name == '\0') return (0);
1501 +       on_clause = read_env (clause_name);
1502 +       if (!on_clause || *on_clause == '\0')   return (1);
1503 +
1504 +       defdomain = read_env ("DEFAULT_DOMAIN");        
1505 +       concat_str = read_env ("USER_DOMAIN_CONCAT");
1506 +       separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1507 +       if (!defdomain) defdomain = "";
1508 +       if (!concat_str || *concat_str == '\0')
1509 +               concat_str = DEF_CONCAT_STRING;
1510 +       if (!separators_set || *separators_set == '\0')
1511 +               separators_set = DEF_SEPARATORS_SET;
1512 +       
1513 +       querybuf = parse_select_clause (on_clause,
1514 +                                       username,
1515 +                                       defdomain,
1516 +                                       concat_str,
1517 +                                       separators_set);
1518 +       
1519 +       if (!querybuf)  return (0);
1520 +
1521 +       if (mysql_query (mysql, querybuf))
1522 +       {
1523 +               /* <o.blasnik@nextra.de> */
1524 +
1525 +               auth_mysql_cleanup();
1526 +
1527 +               if (do_connect())
1528 +               {
1529 +                       free(querybuf);
1530 +                       return (1);
1531 +               }
1532 +
1533 +               if (mysql_query (mysql, querybuf))
1534 +               {
1535 +                       free(querybuf);
1536 +                       auth_mysql_cleanup();
1537 +                       /* Server went down, that's OK,
1538 +                       ** try again next time.
1539 +                       */
1540 +                       return (1);
1541 +               }
1542 +       }
1543 +       free(querybuf);
1544 +       result = mysql_store_result(mysql);
1545 +       if (result) mysql_free_result(result);
1546 +       
1547 +       return (1);
1548 +}
1549 +
1550 +
1551  struct authmysqluserinfo *auth_mysql_getuserinfo(const char *username)
1552  {
1553  const char *user_table =NULL;
1554 @@ -601,6 +707,8 @@
1555             *gid_field          =NULL,
1556             *quota_field        =NULL,
1557             *where_clause       =NULL,
1558 +           *concat_str         =NULL,
1559 +           *separators_set     =NULL,
1560             *select_clause      =NULL; /* siefca@pld.org.pl */
1561  
1562  static const char query[]=
1563 @@ -709,7 +817,19 @@
1564         else
1565         {
1566                 /* siefca@pld.org.pl */
1567 -               querybuf=parse_select_clause (select_clause, username, defdomain);
1568 +               concat_str = read_env ("USER_DOMAIN_CONCAT");
1569 +               separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1570 +
1571 +               if (!concat_str || *concat_str == '\0')
1572 +                       concat_str = DEF_CONCAT_STRING;
1573 +               if (!separators_set || *separators_set == '\0')
1574 +                       separators_set = DEF_SEPARATORS_SET;
1575 +
1576 +               querybuf = parse_select_clause (select_clause,
1577 +                                               username,
1578 +                                               defdomain,
1579 +                                               concat_str,
1580 +                                               separators_set);
1581                 if (!querybuf) return 0;
1582         }
1583  
1584 @@ -793,6 +913,8 @@
1585                     *where_clause       =NULL,
1586                     *user_table         =NULL,
1587                     *login_field        =NULL,
1588 +                   *concat_str        =NULL,
1589 +                   *separators_set     =NULL,  
1590                     *chpass_clause      =NULL; /* siefca@pld.org.pl */
1591  
1592         if (!mysql)
1593 @@ -842,13 +964,22 @@
1594         }
1595         else
1596         {
1597 +               concat_str = read_env ("USER_DOMAIN_CONCAT");
1598 +                separators_set = read_env ("USER_DOMAIN_SEPARATORS");
1599 +               
1600 +               if (!concat_str || *concat_str == '\0')
1601 +                       concat_str = DEF_CONCAT_STRING;
1602 +               if (!separators_set || *separators_set == '\0')
1603 +                       separators_set = DEF_SEPARATORS_SET;
1604 +               
1605                 sql_buf=parse_chpass_clause(chpass_clause,
1606                                             user,
1607                                             defdomain,
1608 +                                           concat_str,
1609 +                                           separators_set,
1610                                             pass,
1611                                             newpass_crypt_ptr);
1612         }
1613 -       
1614  
1615         if (!sql_buf)
1616         {
1617 diff -ur courier-imap-1.5.3.20020921.orig/authlib/authmysqlrc courier-imap-1.5.3.20020921/authlib/authmysqlrc
1618 --- courier-imap-1.5.3.20020921.orig/authlib/authmysqlrc        Thu Apr  4 06:36:29 2002
1619 +++ courier-imap-1.5.3.20020921/authlib/authmysqlrc     Sat Sep 28 02:46:41 2002
1620 @@ -1,4 +1,4 @@
1621 -##VERSION: $Id$
1622 +##VERSION: $Id$
1623  #
1624  # Copyright 2000 Double Precision, Inc.  See COPYING for
1625  # distribution information.
1626 @@ -141,65 +141,99 @@
1627  #
1628  # MYSQL_WHERE_CLAUSE   server='mailhost.example.com'
1629  
1630 -##NAME: MYSQL_SELECT_CLAUSE:0
1631 -#
1632 -# (EXPERIMENTAL)
1633 -# This is optional, MYSQL_SELECT_CLAUSE can be set when you have a database,
1634 -# which is structuraly different from proposed. The fixed string will
1635 -# be used to do a SELECT operation on database, which should return fields
1636 -# in order specified bellow:
1637 -#
1638 -# username, cryptpw, uid, gid, clearpw, home, maildir, quota, fullname
1639 +##NAME: USER_DOMAIN_CONCAT:0
1640  #
1641 -# Enabling this option causes ignorance of any other field-related
1642 -# options, excluding default domain.
1643 +# This is optional. Here, you can write the string used to concatenate
1644 +# username with domain part while expanding the $(username) substitution
1645 +# variable. If it's not set the '@' character is used.
1646 +# See README.authmysql.myownquery for more information
1647  #
1648 -# There are two variables, which you can use. Substitution will be made
1649 -# for them, so you can put entered username (local part) and domain name
1650 -# in the right place of your query. These variables are:
1651 -#              $(local_part) and $(domain)
1652 +# USER_DOMAIN_CONCAT   @
1653 +
1654 +##NAME: USER_DOMAIN_SEPARATORS:0
1655  #
1656 -# If a $(domain) is empty (not given by the remote user) the default domain
1657 -# name is used in its place.
1658 +# This is optional. Using this option you can set the set of characters
1659 +# which are treated as separators when splitting entered username into the
1660 +# local part and the domain name. If it's not set the defaults @% are used,
1661 +# so the user can authenticate using user@domain or user%domain form.
1662 +# See README.authmysql.myownquery for more information
1663  #
1664 -# This example is a little bit modified adaptation of vmail-sql
1665 -# database scheme:
1666 +# USER_DOMAIN_SEPARATORS       @%+
1667 +
1668 +##NAME: MYSQL_SELECT_CLAUSE:0
1669  #
1670 -# MYSQL_SELECT_CLAUSE  SELECT popbox.local_part,                       \
1671 -#                      CONCAT('{MD5}', popbox.password_hash),          \
1672 -#                      popbox.clearpw,                                 \
1673 -#                      domain.uid,                                     \
1674 -#                      domain.gid,                                     \
1675 -#                      CONCAT(domain.path, '/', popbox.mbox_name),     \
1676 -#                      '',                                             \
1677 -#                      domain.quota,                                   \
1678 -#                      '',                                             \
1679 -#                      FROM popbox, domain                             \
1680 -#                      WHERE popbox.local_part = '$(local_part)'       \
1681 -#                      AND popbox.domain_name = '$(domain)'            \
1682 -#                      AND popbox.domain_name = domain.domain_name
1683 +# This is optional, MYSQL_SELECT_CLAUSE can be set when you have a database,
1684 +# which is structuraly different from proposed. You can type here your MySQL
1685 +# query, which will be used to fetch user's credentials, and which should
1686 +# return fields in order specified bellow:
1687 +#
1688 +# username, cryptpw, clearpw, uid, gid, home, maildir, quota, fullname
1689 +#
1690 +# Enabling this option causes ignorance of any other field-related options.
1691 +#
1692 +# There also are variables, which you can use. Substitution will be made
1693 +# for them, so you can pass currently entered username and a domain name
1694 +# up to the right place within your query. These variables are:
1695 +# $(local_part) , $(domain) , $(username)
1696  #
1697 +# If a $(domain) is empty (not given by the remote user) the default domain
1698 +# name is used in its place. $(username) is a local part concatenated with
1699 +# domain name using symbol defined in USER_DOMAIN_CONCAT or '@' if this option
1700 +# is not set.
1701 +# See README.authmysql.myownquery for more information
1702 +#
1703 +# MYSQL_SELECT_CLAUSE SELECT                                   \
1704 +# users.username, users.cryptpw, users.clearpw,                        \
1705 +# domains.uid, domains.gid,                                    \
1706 +# CONCAT_WS('/',domains.path_prefix,users.mailbox_path),       \
1707 +# '', domains.quota, ''                                                \
1708 +# FROM users, domains                                          \
1709 +#  WHERE domains.domain_name='$(domain)'                       \
1710 +#    AND users.username='$(local_part)'                                \
1711 +#    AND domains.domain_name=users.domain_name
1712 +
1713  ##NAME: MYSQL_CHPASS_CLAUSE:0
1714  #
1715 -# (EXPERIMENTAL)
1716  # This is optional, MYSQL_CHPASS_CLAUSE can be set when you have a database,
1717 -# which is structuraly different from proposed. The fixed string will
1718 -# be used to do an UPDATE operation on database. In other words, it is
1719 -# used, when changing password.
1720 +# which is structuraly different from proposed. You can use it to set up
1721 +# a MySQL query used to change user's password.
1722  #
1723  # There are four variables, which you can use. Substitution will be made
1724 -# for them, so you can put entered username (local part) and domain name
1725 -# in the right place of your query. There variables are:
1726 -#      $(local_part) , $(domain) , $(newpass) , $(newpass_crypt)
1727 +# for them, so you can put the currently entered username and the domain name
1728 +# in the right place of your query. These variables are:
1729 +# $(local_part) , $(domain) , $(username) , $(newpass) , $(newpass_crypt)
1730  #
1731  # If a $(domain) is empty (not given by the remote user) the default domain
1732 -# name is used in its place.
1733 -# $(newpass) contains plain password
1734 -# $(newpass_crypt) contains its crypted form
1735 -#
1736 -# MYSQL_CHPASS_CLAUSE  UPDATE  popbox                                  \
1737 -#                      SET     clearpw='$(newpass)',                   \
1738 -#                              password_hash='$(newpass_crypt)'        \
1739 -#                      WHERE   local_part='$(local_part)'              \
1740 -#                      AND     domain_name='$(domain)'
1741 +# name is used in its place. $(newpass) contains plain password and
1742 +# $(newpass_crypt) contains its crypted form.
1743 +# See README.authmysql.myownquery for more information
1744 +#
1745 +# MYSQL_CHPASS_CLAUSE UPDATE users             \
1746 +# SET clearpw='$(newpass)',                    \
1747 +#     cryptpw='$(newpass_crypt)'               \
1748 +# WHERE username='$(local_part)'               \
1749 +# AND domain_name='$(domain)'
1750 +
1751 +##NAME: MYSQL_ONSUCCESS_CLAUSE:0
1752 +# (EXPERIMENTAL)
1753 +#
1754 +# This is optional, MYSQL_ONSUCCESS_CLAUSE is a trigger - the query is performed
1755 +# each time user has successfuly logged in.
1756 +# See README.authmysql.myownquery for more information
1757 +#
1758 +# MYSQL_ONSUCCESS_CLAUSE       UPDATE users                            \
1759 +#                              SET last_ok=CURRENT_TIMESTAMP           \
1760 +#                              WHERE username='$(local_part)'          \
1761 +#                              AND domain_name='$(domain)'
1762 +
1763 +##NAME: MYSQL_ONFAIL_CLAUSE:0
1764 +# (EXPERIMENTAL)
1765  #
1766 +# This is optional, MYSQL_ONFAIL_CLAUSE is a trigger - the query is performed
1767 +# each time user has successfuly logged in.
1768 +# See README.authmysql.myownquery for more information
1769 +#
1770 +# MYSQL_ONFAIL_CLAUSE          UPDATE users                            \
1771 +#                              SET last_fail=CURRENT_TIMESTAMP         \
1772 +#                              WHERE username='$(local_part)'          \
1773 +#                              AND domain_name='$(domain)'
This page took 0.841937 seconds and 2 git commands to generate.