1 #include "policyd.h"
2 
3 
4 /*
5  *
6  *
7  *                           Policy Daemon
8  *
9  *  policy daemon is used in conjuction with postfix to combat spam.
10  *
11  *  Copyright (C) 2004 Cami Sardinha (cami@mweb.co.za)
12  *
13  *
14  *  This program is free software; you can redistribute it and/or modify it
15  *  under the terms of the  GNU General  Public License as published by the
16  *  Free Software Foundation;  either version 2 of the License, or (at your
17  *  option) any later version.
18  *
19  *  This program  is  distributed  in the hope that  it will be useful, but
20  *  WITHOUT  WARRANTY; without even the implied warranty of MERCHANTABILITY
21  *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  *  for more details.
23  *
24  *  You should have received a copy of the GNU General Public License along
25  *  with this program; if not, write to the Free  Software Foundation Inc.,
26  *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27  *
28  *
29  *
30  */
31 
32 /*
33  * function: w_mysql_query
34  *  purpose: wrapper function for mysql_query()
35  *   return: 0=success, -1=failure
36  */
37 int
w_mysql_query(unsigned int volatile fd,const char * function)38 w_mysql_query(unsigned int volatile fd, const char *function)
39 {
40 
41   /* cut down latency, do not do any queries for 30 seconds if 3 failures occur */
42   if(mysql_failure_count >= 3)
43   {
44 
45     /* has 30 seconds passed? */
46     if(timenow >= (last_mysql_failure+30))
47     {                                 /* timer has expired     */
48       mysql_failure_count=0;
49       mysql_close(mysql);
50       mysql = db_connect(MYSQLDBASE); /* reconnect to database */
51     } else {                          /* timer has not expired */
52 
53       return (-1);                    /* so we fail the query  */
54     }
55   }
56 
57   /* catch query timeouts */
58   if (sigsetjmp (sjmp, 1))
59   {
60     if(DEBUG > 2)
61       logmessage("%s()/mysql_query(): mysql_query timeout number: %d\n",
62         function, mysql_failure_count);
63 
64     mysql_failure_count++;
65     last_mysql_failure=timenow;
66 
67     /* reset the timer */
68     alarm (0);
69 
70     /* unset the signal handler */
71     signal (SIGALRM, SIG_DFL);
72 
73     mysql_array[fd][0] = -1;
74     return (-1); /* failure */
75   }
76 
77   /* set the SIGALRM handler */
78   signal (SIGALRM, (void *) sigalrm_handler);
79 
80   /* mysql timeout */
81   alarm(mysql_timeout);
82 
83   /* fire off query */
84   if (mysql_query(mysql, mysqlquery_array[fd]) != 0)
85   {
86     /* reset the timer */
87     alarm (0);
88 
89     /* unset the signal handler */
90     signal (SIGALRM, SIG_DFL);
91 
92     return (-1);
93   }
94 
95   /* reset the timer */
96   alarm (0);
97 
98   /* unset the signal handler */
99   signal (SIGALRM, SIG_DFL);
100 
101   /* ensure that we only go into bypass mode after 3 consecutive failures */
102   mysql_failure_count=0;
103 
104   return (0); /* success */
105 }
106 
107 
108 
109 
110 /*
111  * function: db_doquery
112  *  purpose: do mysql queries
113  *   return: 0=success, -1=failure
114  */
115 int
db_doquery(unsigned int volatile fd)116 db_doquery(unsigned int volatile fd)
117 {
118 
119   if(DEBUG > 1)
120     logmessage("DEBUG: fd: %d db_doquery(): %s\n", fd, mysqlquery_array[fd]);
121 
122   /* fire off query */
123   if (w_mysql_query(fd, "db_doquery") != 0)
124     goto err;
125 
126   /* select() result */
127   if (mysql_field_count(mysql) > 0)
128   {
129     int num_fields, i=0;
130     MYSQL_RES   *res;
131     MYSQL_ROW    row, end_row;
132 
133     if (!(res = mysql_store_result(mysql)))
134       goto err;
135 
136     num_fields = mysql_num_fields(res);
137     while ((row = mysql_fetch_row(res)))
138     {
139       for (end_row=row+num_fields;row<end_row;++row,i++)
140       {
141         if(DEBUG > 1)
142           logmessage("DEBUG: fd: %d row: %d data: %d (recieved)\n", fd, i, atol((char *)*row));
143 
144         mysql_array[fd][i]=atol(row ? (char*)*row : "0"); /* return seconds from epoch */
145 
146         if(DEBUG > 1)
147           logmessage("DEBUG: fd: %d row: %d data: %d (extracted)\n", fd, i, mysql_array[fd][i]);
148       }
149     }
150     mysql_free_result(res);
151   } else {
152 
153     /* update() result */
154     mysql_array[fd][0]=mysql_affected_rows(mysql);
155   }
156 
157 
158   return (0); /* success */
159 
160 err:
161 
162   if(DEBUG > 1)
163     logmessage("db_doquery()/mysql_query(): %s -> %s\n", mysql_error(mysql), mysqlquery_array[fd]);
164 
165   mysql_array[fd][0] = -1;
166   return (-1); /* failure */
167 }
168 
169 
170 
171 
172 /*
173  * function: db_charquery
174  *  purpose: do mysql query
175  *   return: 0=success, -1=failure
176  *   return: multi-dimensional char array
177  */
178 int
db_charquery(unsigned int volatile fd)179 db_charquery(unsigned int volatile fd)
180 {
181 
182   if(DEBUG > 1)
183     logmessage("DEBUG: fd: %d db_charquery(): %s\n", fd, mysqlquery_array[fd]);
184 
185   /* fire off query */
186   if (w_mysql_query(fd, "db_charquery") != 0)
187     goto err;
188 
189   /* get rid of stale data */
190   for(t=0 ; t<20 ; t++)
191     memset(mysqlchar_array[fd][t], 0x00, 64);
192 
193   /* select() result */
194   if (mysql_field_count(mysql) > 0)
195   {
196     int num_fields, i=0;
197     MYSQL_RES   *res;
198     MYSQL_ROW    row, end_row;
199 
200     if (!(res = mysql_store_result(mysql)))
201       goto err;
202 
203     num_fields = mysql_num_fields(res);
204     while ((row = mysql_fetch_row(res)))
205     {
206       for (end_row=row+num_fields;row<end_row;++row)
207       {
208         if(DEBUG > 1)
209           logmessage("DEBUG: fd: %d row: %d data: %s (recieved)\n", fd, i, (char *)*row);
210 
211         strncpy(mysqlchar_array[fd][i], (char*)*row, 64);
212 
213         if(DEBUG > 1)
214           logmessage("DEBUG: fd: %d row: %d data: %s (extracted)\n", fd, i, mysqlchar_array[fd][i]);
215         i++;
216       }
217     }
218     mysql_free_result(res);
219   }
220 
221   return (0); /* success */
222 
223 err:
224 
225   if(DEBUG > 1)
226     logmessage("db_charquery()/mysql_query(): %s -> %s\n", mysql_error(mysql), mysqlquery_array[fd]);
227 
228   mysql_array[fd][0] = -1;
229   return (-1); /* failure */
230 }
231 
232 
233 
234 
235 /*
236  * function: db_optquery
237  *  purpose: do mysql opt in/out queries
238  *   return: 0=success, -1=failure
239  */
240 int
db_optquery(unsigned int volatile fd)241 db_optquery(unsigned int volatile fd)
242 {
243 
244   if(DEBUG > 1)
245     logmessage("DEBUG: fd: %d, db_optquery(): %s\n", fd, mysqlquery_array[fd]);
246 
247   /* fire off query */
248   if (w_mysql_query(fd, "db_optquery") != 0)
249     goto err;
250 
251   /* select() result */
252   if (mysql_field_count(mysql) > 0)
253   {
254     int num_fields, i=0;
255     MYSQL_RES   *res;
256     MYSQL_ROW    row, end_row;
257 
258     if (!(res = mysql_store_result(mysql)))
259       goto err;
260 
261     num_fields = mysql_num_fields(res);
262     while ((row = mysql_fetch_row(res)))
263     {
264       for (end_row=row+num_fields;row<end_row;++row,i++)
265       {
266         if(DEBUG > 1)
267           logmessage("DEBUG: fd: %d row: %d data: %d (recieved)\n", fd, i, atoi((char *)*row));
268 
269         mysql_optarray[fd][0]=atol(row ? (char*)*row : "0");
270 
271         if(DEBUG > 1)
272           logmessage("DEBUG: fd: %d row: %d data: %d (extracted)\n", fd, i, mysql_optarray[fd][0]);
273       }
274     }
275     mysql_free_result(res);
276   }
277 
278   return (0); /* success */
279 
280 err:
281 
282   if(DEBUG > 1)
283     logmessage("db_optquery()/mysql_query(): %s -> %s\n", mysql_error(mysql), mysqlquery_array[fd]);
284 
285   mysql_array[fd][0] = -1;
286   return (-1); /* failure */
287 }
288 
289 
290 
291 
292 /*
293  * function: db_deletequery
294  *  purpose: expire triplet in database
295  *   return: 0=success, -1=failure
296  */
297 int
db_deletequery(unsigned int volatile fd)298 db_deletequery(unsigned int volatile fd)
299 {
300   count=0;
301 
302 start:
303 
304   if(DEBUG > 1)
305     logmessage("DEBUG: fd: %d, db_deletequery(): %s\n", fd, mysqlquery_array[fd]);
306 
307   /* fire off query */
308   if (w_mysql_query(fd, "db_deletequery") != 0)
309     goto err;
310 
311   /* select() result */
312   if (mysql_field_count(mysql) > 0)
313   {
314     int num_fields, i=0;
315     MYSQL_RES   *res;
316     MYSQL_ROW    row, end_row;
317 
318     if (!(res = mysql_store_result(mysql)))
319       goto err;
320 
321     num_fields = mysql_num_fields(res);
322     while ((row = mysql_fetch_row(res)))
323     {
324       for (end_row=row+num_fields;row<end_row;++row,i++)
325       {
326         logmessage("%ld\n", atol(row ? (char*)*row : "0"));
327       }
328     }
329     mysql_free_result(res);
330   } else {
331     /* MySQL does not handle extremely large deletes very well */
332     if((int)mysql_affected_rows(mysql) == 100000)
333     {
334       count=count+(int)mysql_affected_rows(mysql);
335       goto start;
336     }
337 
338     /* didnt reach 100000 deletes */
339     count=count+(int)mysql_affected_rows(mysql);
340 
341   }
342 
343   logmessage("expired: %d records\n", count);
344 
345   return (0); /* success */
346 
347 err:
348 
349   if(count != 0)
350     logmessage("expired: %d records\n", count);
351 
352   if(DEBUG > 1)
353     logmessage("db_deletequery()/mysql_query(): %s -> %s\n", mysql_error(mysql), mysqlquery_array[fd]);
354 
355   mysql_array[fd][0] = -1;
356   return (-1); /* failure */
357 }
358 
359 
360 
361 /*
362  * function: db_deletequery
363  *  purpose: expire triplet in database
364  *   return: 0=success, -1=failure
365  */
366 int
db_printquery(unsigned int volatile fd)367 db_printquery(unsigned int volatile fd)
368 {
369   if(DEBUG > 1)
370     logmessage("DEBUG: fd: %d, db_printquery(): %s\n", fd, mysqlquery_array[fd]);
371 
372   /* fire off query */
373   if (w_mysql_query(fd, "db_printquery") != 0)
374     goto err;
375 
376   /* select() result */
377   if (mysql_field_count(mysql) > 0)
378   {
379     int num_fields;
380     MYSQL_RES   *res;
381     MYSQL_ROW    row;
382 
383     if (!(res = mysql_store_result(mysql)))
384       goto err;
385 
386     num_fields = mysql_num_fields(res);
387     while ((row = mysql_fetch_row(res)))
388     {
389       unsigned long *lengths;
390       lengths = mysql_fetch_lengths(res);
391       logmessage("%s\t->  %s", row[0], row[1]);
392     }
393     mysql_free_result(res);
394   }
395 
396 
397   return (0); /* success */
398 
399 err:
400 
401   if(DEBUG > 1)
402     logmessage("db_printquery()/mysql_query(): %s -> %s\n", mysql_error(mysql), mysqlquery_array[fd]);
403 
404   mysql_array[fd][0] = -1;
405   return (-1); /* failure */
406 }
407 
408 
409 
410 /*
411  * function: db_connect
412  *  purpose: open connection to mysql database
413  *   return: MySQL structure
414  */
db_connect(const char * dbname)415 MYSQL *db_connect(const char *dbname)
416 {
417   /* initializes mysql structure */
418 
419   /*
420    * volatile:
421    *
422    * It's a potential problem, because Sun doesn't guarrantee that automatic
423    * variables are preserved when the saved function's context is restored.
424    *
425    *                                                  Leandro Santi
426    */
427   MYSQL * volatile db=mysql_init(NULL);
428   if(!db)
429   {
430     logmessage("mysql_init(): no memory\n");
431     exit(-1);
432   }
433   logmessage("connecting to mysql database: %s\n", MYSQLHOST);
434 
435   /* catch connection timeouts */
436   if (sigsetjmp (sjmp, 1))
437   {
438     if(DEBUG > 1)
439       logmessage("db_connect()/mysql_real_connect(): connection timeout\n");
440 
441     /* if the connection fails, that counts as a hard failure */
442     mysql_failure_count++;
443     last_mysql_failure=timenow;
444 
445     logmessage("NOT connected..\n");
446     return (db); /* failure */
447   }
448 
449   /* set the SIGALRM handler */
450   signal (SIGALRM, (void *) sigalrm_handler);
451 
452   /* mysql timeout */
453   alarm(mysql_timeout);
454 
455 #if defined(MYSQL_VERSION_ID)
456 #  if MYSQL_VERSION_ID >= 50003 && MYSQL_VERSION_ID < 50013
457    /* hack to allow 5.0.3 => 5.0.13 to reconnect */
458    db->reconnect = 1;
459 #  elif MYSQL_VERSION_ID >= 50013
460    mysql_options(db, MYSQL_OPT_RECONNECT, "1");
461 #  endif
462 #endif
463 
464   /* fire off query */
465   /* connect to mysql server */
466   if(!mysql_real_connect(db, MYSQLHOST, MYSQLUSER, MYSQLPASS, dbname, MYSQLPORT, NULL, 0))
467   {
468     logmessage("mysql_real_connect(): %s\n", mysql_error(db));
469     mysql_failure_count++;
470     last_mysql_failure=timenow;
471   } else {
472     logmessage("connected..\n");
473   }
474 
475   /* reset the timer & unset the signal handler */
476   alarm (0);           signal (SIGALRM, SIG_DFL);
477 
478   return (db);
479 }
480 
481 
482 
483 
484 /*
485  * function: database_probe
486  *  purpose: check to see if connection to the database is open
487  *   return: 0=success, -20=failure
488  */
489 int
database_probe(unsigned int fd)490 database_probe(unsigned int fd)
491 {
492   if(DEBUG > 1)
493     logmessage("DEBUG: fd: %d database_probe(): mysql_ping\n", fd);
494 
495   /* catch query timeouts */
496   if (sigsetjmp (sjmp, 1))
497   {
498     if(DEBUG > 2)
499       logmessage("db_doquery()/mysql_query(): mysql_query timeout -> %s\n", mysqlquery_array[fd]);
500 
501     /* if the probe fails, that counts as a hard failure */
502     mysql_failure_count++;
503     last_mysql_failure=timenow;
504 
505     return (-20); /* failure */
506   }
507 
508   /* set the SIGALRM handler */
509   signal (SIGALRM, (void *) sigalrm_handler);
510 
511   /* mysql timeout */
512   alarm (mysql_timeout);
513 
514   /* reconnect to the database, only after 120 seconds has passed */
515   if(DEBUG > 1)
516     logmessage("DEBUG: fd: %d database_probe(): reconnecting..\n", fd);
517 
518   mysql_close(mysql);
519   mysql = db_connect(MYSQLDBASE);
520 
521   /* reset the timer & unset the signal handler */
522   alarm (0);
523   signal (SIGALRM, SIG_DFL);
524 
525   return (0);
526 }
527 
528 /* EOF */
529