]>
Commit | Line | Data |
---|---|---|
0d6c173a | 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)' |