source: npl/mailserver/dspam/dspam-3.10.2/src/mysql_drv.c @ c5c522c

gcc484ntopperl-5.22
Last change on this file since c5c522c was c5c522c, checked in by Edwin Eefting <edwin@datux.nl>, 8 years ago

initial commit, transferred from cleaned syn3 svn tree

  • Property mode set to 100644
File size: 101.8 KB
Line 
1/* $Id: mysql_drv.c,v 1.889 2011/10/01 10:22:17 sbajic Exp $ */
2
3/*
4 DSPAM
5 COPYRIGHT (C) 2002-2012 DSPAM PROJECT
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Affero General Public License as
9 published by the Free Software Foundation, either version 3 of the
10 License, or (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU Affero General Public License for more details.
16
17 You should have received a copy of the GNU Affero General Public License
18 along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20*/
21
22#ifdef HAVE_CONFIG_H
23#include <auto-config.h>
24#endif
25
26#include <string.h>
27#include <pwd.h>
28#include <sys/types.h>
29#include <sys/stat.h>
30#include <dirent.h>
31#include <unistd.h>
32#include <errno.h>
33#include <stdlib.h>
34#include <stdio.h>
35#include <fcntl.h>
36#include <signal.h>
37#include <mysql.h>
38#include <limits.h>
39
40/* Work around broken limits.h on debian etch (and possibly others?) */
41#if defined __GNUC__ && defined _GCC_LIMITS_H_ && ! defined ULLONG_MAX
42#  define ULLONG_MAX ULONG_LONG_MAX
43#endif
44
45#ifdef DAEMON
46#include <pthread.h>
47#endif
48
49#ifdef TIME_WITH_SYS_TIME
50#   include <sys/time.h>
51#   include <time.h>
52#else
53#   ifdef HAVE_SYS_TIME_H
54#       include <sys/time.h>
55#   else
56#       include <time.h>
57#   endif
58#endif
59
60
61#include "storage_driver.h"
62#include "mysql_drv.h"
63#include "libdspam.h"
64#include "config.h"
65#include "error.h"
66#include "language.h"
67#include "util.h"
68#include "pref.h"
69#include "config_shared.h"
70
71#define MYSQL_RUN_QUERY(A, B) mysql_query(A, B)
72#define MYSQL_RUN_REAL_QUERY(A, B, C) mysql_real_query(A, B, C)
73
74/*
75 * _mysql_drv_get_UIDInSignature()
76 *
77 * DESCRIPTION
78 *   The _mysql_drv_get_UIDInSignature() function is called to check if the
79 *   configuration option MySQLUIDInSignature is turned on or off.
80 *
81 * RETURN VALUES
82 *   Returns 1 if MySQLUIDInSignature is turned "on" and 0 if turned "off".
83 */
84/*
85static int _mysql_drv_get_UIDInSignature (DSPAM_CTX *CTX) {
86  static int uid_in_signature = -1;
87  if (uid_in_signature > -1) {
88    return uid_in_signature;
89  } else {
90    if (_ds_match_attribute(CTX->config->attributes, "MySQLUIDInSignature", "on"))
91      uid_in_signature = 1;
92    else
93      uid_in_signature = 0;
94  }
95  return uid_in_signature;
96}
97*/
98
99/*
100 * _mysql_drv_get_virtual_table()
101 *
102 * DESCRIPTION
103 *   The _mysql_drv_get_virtual_table() function is called to get the
104 *   virtual table name.
105 *
106 * RETURN VALUES
107 *   Returns the name of the virtual table if defined, otherwise returns
108 *   "dspam_virtual_uids".
109 */
110/*
111static char *_mysql_drv_get_virtual_table (DSPAM_CTX *CTX) {
112  static char *virtual_table = "*";
113  if (virtual_table[0] != '*') {
114    return virtual_table;
115  } else {
116    if ((virtual_table = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualTable")) == NULL) {
117      virtual_table = "dspam_virtual_uids";
118    }
119  }
120  return virtual_table;
121}
122*/
123
124/*
125 * _mysql_drv_get_virtual_uid_field()
126 *
127 * DESCRIPTION
128 *   The _mysql_drv_get_virtual_uid_field() function is called to get the
129 *   virtual uid field name.
130 *
131 * RETURN VALUES
132 *   Returns the name of the virtual uid field if defined, otherwise returns
133 *   "uid".
134 */
135/*
136static char *_mysql_drv_get_virtual_uid_field (DSPAM_CTX *CTX) {
137  static char *virtual_uid = "*";
138  if (virtual_uid[0] != '*') {
139    return virtual_uid;
140  } else {
141    if ((virtual_uid = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualUIDField")) == NULL) {
142      virtual_uid = "uid";
143    }
144  }
145  return virtual_uid;
146}
147*/
148
149/*
150 * _mysql_drv_get_virtual_username_field()
151 *
152 * DESCRIPTION
153 *   The _mysql_drv_get_virtual_username_field() function is called to get the
154 *   virtual username field name.
155 *
156 * RETURN VALUES
157 *   Returns the name of the virtual username field if defined, otherwise returns
158 *   "username".
159 */
160/*
161static char *_mysql_drv_get_virtual_username_field (DSPAM_CTX *CTX) {
162  static char *virtual_username = "*";
163  if (virtual_username[0] != '*') {
164    return virtual_username;
165  } else {
166    if ((virtual_username = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualUsernameField")) == NULL) {
167      virtual_username = "username";
168    }
169  }
170  return virtual_username;
171}
172*/
173
174/*
175 * _mysql_driver_get_max_packet()
176 *
177 * DESCRIPTION
178 *   The _mysql_driver_get_max_packet() function is called to get the maximum
179 *   size of db communication buffer.
180 *
181 * RETURN VALUES
182 *   Returns the maximum size for the db communication buffer. If the maximum
183 *   size can not be determined then 1000000 is returned.
184 */
185static unsigned long _mysql_driver_get_max_packet (MYSQL *dbh) {
186  static unsigned long drv_max_packet = 0;
187  if (drv_max_packet > 0) {
188    return drv_max_packet;
189  } else {
190    drv_max_packet = 1000000;
191  }
192  if (dbh) {
193    MYSQL_RES *result;
194    MYSQL_ROW row;
195    char scratch[128];
196    snprintf (scratch, sizeof (scratch), "SHOW VARIABLES WHERE variable_name='max_allowed_packet'");
197    if (MYSQL_RUN_QUERY (dbh, scratch) == 0) {
198      result = mysql_use_result (dbh);
199      if (result != NULL) {
200        row = mysql_fetch_row (result);
201        if (row != NULL) {
202          drv_max_packet = strtoul (row[1], NULL, 0);
203          if (drv_max_packet == ULONG_MAX && errno == ERANGE) {
204            LOGDEBUG("_ds_init_storage: failed converting %s to max_allowed_packet", row[1]);
205            drv_max_packet = 1000000;
206          }
207        }
208      }
209      mysql_free_result (result);
210    }
211  }
212  return drv_max_packet;
213}
214
215int
216dspam_init_driver (DRIVER_CTX *DTX)
217{
218#if defined(MYSQL4_INITIALIZATION) && defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40001
219  const char *server_default_groups[]=
220  { "server", "embedded", "mysql_SERVER", 0 };
221
222#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50003
223  if (mysql_library_init(0, NULL, (char**) server_default_groups)) {
224    LOGDEBUG("dspam_init_driver: failed initializing MySQL driver");
225    return EFAILURE;
226  }
227#else
228  if (mysql_server_init(0, NULL, (char**) server_default_groups)) {
229    LOGDEBUG("dspam_init_driver: failed initializing MySQL driver");
230    return EFAILURE;
231  }
232#endif
233#endif
234
235  if (DTX == NULL)
236    return 0;
237
238  /* Establish a series of stateful connections */
239
240  if (DTX->flags & DRF_STATEFUL) {
241    int i, connection_cache = 3;
242
243    if (_ds_read_attribute(DTX->CTX->config->attributes, "MySQLConnectionCache"))
244      connection_cache = atoi(_ds_read_attribute(DTX->CTX->config->attributes, "MySQLConnectionCache"));
245
246    DTX->connection_cache = connection_cache;
247    DTX->connections = calloc(1, sizeof(struct _ds_drv_connection *)*connection_cache);
248    if (DTX->connections == NULL) {
249      LOG(LOG_CRIT, ERR_MEM_ALLOC);
250      return EUNKNOWN;
251    }
252
253    for(i=0;i<connection_cache;i++) {
254      DTX->connections[i] = calloc(1, sizeof(struct _ds_drv_connection));
255      if (DTX->connections[i]) {
256#ifdef DAEMON
257        LOGDEBUG("dspam_init_driver: initializing lock %d", i);
258        pthread_mutex_init(&DTX->connections[i]->lock, NULL);
259#endif
260        DTX->connections[i]->dbh = (void *) _ds_connect(DTX->CTX);
261      }
262    }
263  }
264
265  return 0;
266}
267
268int
269dspam_shutdown_driver (DRIVER_CTX *DTX)
270{
271  if (DTX != NULL) {
272    if (DTX->flags & DRF_STATEFUL && DTX->connections) {
273      int i;
274
275      for(i=0;i<DTX->connection_cache;i++) {
276        if (DTX->connections[i]) {
277          if (DTX->connections[i]->dbh) {
278            _mysql_drv_dbh_t dbt = (_mysql_drv_dbh_t) DTX->connections[i]->dbh;
279            mysql_close(dbt->dbh_read);
280            if (dbt->dbh_write != dbt->dbh_read)
281              mysql_close(dbt->dbh_write);
282          }
283#ifdef DAEMON
284          LOGDEBUG("dspam_shutdown_driver: destroying lock %d", i);
285          pthread_mutex_destroy(&DTX->connections[i]->lock);
286#endif
287          free(DTX->connections[i]);
288        }
289      }
290
291      free(DTX->connections);
292      DTX->connections = NULL;
293    }
294  }
295
296#if defined(MYSQL4_INITIALIZATION) && defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40001
297#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50003
298  mysql_library_end();
299#else
300  mysql_server_end();
301#endif
302#endif
303  return 0;
304}
305
306int
307_mysql_drv_get_spamtotals (DSPAM_CTX * CTX)
308{
309  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
310  char query[512];
311  struct passwd *p;
312  char *name;
313  MYSQL_RES *result;
314  MYSQL_ROW row;
315  int query_rc = 0;
316  int query_errno = 0;
317  struct _ds_spam_totals user, group;
318  int uid = -1, gid = -1;
319  result = NULL;
320
321  if (s->dbt == NULL)
322  {
323    LOGDEBUG ("_mysql_drv_get_spamtotals: invalid database handle (NULL)");
324    return EINVAL;
325  }
326
327  memset(&s->control_totals, 0, sizeof(struct _ds_spam_totals));
328  if (CTX->flags & DSF_MERGED) {
329    memset(&s->merged_totals, 0, sizeof(struct _ds_spam_totals));
330    memset(&group, 0, sizeof(struct _ds_spam_totals));
331  }
332
333  memset(&CTX->totals, 0, sizeof(struct _ds_spam_totals));
334  memset(&user, 0, sizeof(struct _ds_spam_totals));
335
336  if (!CTX->group || CTX->flags & DSF_MERGED) {
337    p = _mysql_drv_getpwnam (CTX, CTX->username);
338    name = CTX->username;
339  } else {
340    p = _mysql_drv_getpwnam (CTX, CTX->group);
341    name = CTX->group;
342  }
343
344  if (p == NULL)
345  {
346    LOGDEBUG ("_mysql_drv_get_spamtotals: unable to _mysql_drv_getpwnam(%s)",
347              name);
348    if (!(CTX->flags & DSF_MERGED))
349      return EINVAL;
350  } else {
351    uid = (int) p->pw_uid;
352  }
353
354  if (CTX->group != NULL && CTX->flags & DSF_MERGED) {
355    p = _mysql_drv_getpwnam (CTX, CTX->group);
356    if (p == NULL)
357    {
358      LOGDEBUG ("_mysql_drv_get_spamtotals: unable to _mysql_drv_getpwnam(%s)",
359                CTX->group);
360      return EINVAL;
361    }
362    gid = (int) p->pw_uid;
363  }
364
365  if (gid < 0) gid = uid;
366
367  if (gid != uid)
368    snprintf (query, sizeof (query),
369              "SELECT uid,spam_learned,innocent_learned,"
370              "spam_misclassified,innocent_misclassified,"
371              "spam_corpusfed,innocent_corpusfed,"
372              "spam_classified,innocent_classified"
373              " FROM dspam_stats WHERE uid IN ('%d','%d')",
374              (int) uid, (int) gid);
375  else
376    snprintf (query, sizeof (query),
377              "SELECT uid,spam_learned,innocent_learned,"
378              "spam_misclassified,innocent_misclassified,"
379              "spam_corpusfed,innocent_corpusfed,"
380              "spam_classified,innocent_classified"
381              " FROM dspam_stats WHERE uid='%d'",
382              (int) uid);
383  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
384  if (query_rc) {
385    query_errno = mysql_errno (s->dbt->dbh_read);
386    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
387      /* Locking issue. Wait 1 second and then retry the transaction again */
388      sleep(1);
389      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
390    }
391  }
392  if (query_rc) {
393    _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query);
394    LOGDEBUG ("_mysql_drv_get_spamtotals: unable to run query: %s", query);
395    return EFAILURE;
396  }
397
398  result = mysql_use_result (s->dbt->dbh_read);
399  if (result == NULL) {
400    LOGDEBUG("_mysql_drv_get_spamtotals: failed mysql_use_result()");
401    return EFAILURE;
402  }
403
404  while ((row = mysql_fetch_row (result)) != NULL) {
405    int rid = atoi(row[0]);
406    if (rid == INT_MAX && errno == ERANGE) {
407      LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to rid", row[0]);
408      goto FAIL;
409    }
410    if (rid == uid) {
411      user.spam_learned                 = strtoul (row[1], NULL, 0);
412      if ((unsigned long int)user.spam_learned == ULONG_MAX && errno == ERANGE) {
413        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to user.spam_learned", row[1]);
414        goto FAIL;
415      }
416      user.innocent_learned             = strtoul (row[2], NULL, 0);
417      if ((unsigned long int)user.innocent_learned == ULONG_MAX && errno == ERANGE) {
418        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to user.innocent_learned", row[2]);
419        goto FAIL;
420      }
421      user.spam_misclassified           = strtoul (row[3], NULL, 0);
422      if ((unsigned long int)user.spam_misclassified == ULONG_MAX && errno == ERANGE) {
423        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to user.spam_misclassified", row[3]);
424        goto FAIL;
425      }
426      user.innocent_misclassified       = strtoul (row[4], NULL, 0);
427      if ((unsigned long int)user.innocent_misclassified == ULONG_MAX && errno == ERANGE) {
428        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to user.innocent_misclassified", row[4]);
429        goto FAIL;
430      }
431      user.spam_corpusfed               = strtoul (row[5], NULL, 0);
432      if ((unsigned long int)user.spam_corpusfed == ULONG_MAX && errno == ERANGE) {
433        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to user.spam_corpusfed", row[5]);
434        goto FAIL;
435      }
436      user.innocent_corpusfed           = strtoul (row[6], NULL, 0);
437      if ((unsigned long int)user.innocent_corpusfed == ULONG_MAX && errno == ERANGE) {
438        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to user.innocent_corpusfed", row[6]);
439        goto FAIL;
440      }
441      if (row[7] != NULL && row[8] != NULL) {
442        user.spam_classified            = strtoul (row[7], NULL, 0);
443        if ((unsigned long int)user.spam_classified == ULONG_MAX && errno == ERANGE) {
444          LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to user.spam_classified", row[7]);
445          goto FAIL;
446        }
447        user.innocent_classified        = strtoul (row[8], NULL, 0);
448        if ((unsigned long int)user.innocent_classified == ULONG_MAX && errno == ERANGE) {
449          LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to user.innocent_classified", row[8]);
450          goto FAIL;
451        }
452      } else {
453        user.spam_classified            = 0;
454        user.innocent_classified        = 0;
455      }
456    } else {
457      group.spam_learned                = strtoul (row[1], NULL, 0);
458      if ((unsigned long int)group.spam_learned == ULONG_MAX && errno == ERANGE) {
459        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to group.spam_learned", row[1]);
460        goto FAIL;
461      }
462      group.innocent_learned            = strtoul (row[2], NULL, 0);
463      if ((unsigned long int)group.innocent_learned == ULONG_MAX && errno == ERANGE) {
464        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to group.innocent_learned", row[2]);
465        goto FAIL;
466      }
467      group.spam_misclassified          = strtoul (row[3], NULL, 0);
468      if ((unsigned long int)group.spam_misclassified == ULONG_MAX && errno == ERANGE) {
469        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to group.spam_misclassified", row[3]);
470        goto FAIL;
471      }
472      group.innocent_misclassified      = strtoul (row[4], NULL, 0);
473      if ((unsigned long int)group.innocent_misclassified == ULONG_MAX && errno == ERANGE) {
474        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to group.innocent_misclassified", row[4]);
475        goto FAIL;
476      }
477      group.spam_corpusfed              = strtoul (row[5], NULL, 0);
478      if ((unsigned long int)group.spam_corpusfed == ULONG_MAX && errno == ERANGE) {
479        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to group.spam_corpusfed", row[5]);
480        goto FAIL;
481      }
482      group.innocent_corpusfed          = strtoul (row[6], NULL, 0);
483      if ((unsigned long int)group.innocent_corpusfed == ULONG_MAX && errno == ERANGE) {
484        LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to group.innocent_corpusfed", row[6]);
485        goto FAIL;
486      }
487      if (row[7] != NULL && row[8] != NULL) {
488        group.spam_classified           = strtoul (row[7], NULL, 0);
489        if ((unsigned long int)group.spam_classified == ULONG_MAX && errno == ERANGE) {
490          LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to group.spam_classified", row[7]);
491          goto FAIL;
492        }
493        group.innocent_classified       = strtoul (row[8], NULL, 0);
494        if ((unsigned long int)group.innocent_classified == ULONG_MAX && errno == ERANGE) {
495          LOGDEBUG("_mysql_drv_get_spamtotals: failed converting %s to group.innocent_classified", row[8]);
496          goto FAIL;
497        }
498      } else {
499        group.spam_classified           = 0;
500        group.innocent_classified       = 0;
501      }
502    }
503  }
504
505  mysql_free_result (result);
506  result = NULL;
507
508  if (CTX->flags & DSF_MERGED) {
509    memcpy(&s->merged_totals, &group, sizeof(struct _ds_spam_totals));
510    memcpy(&s->control_totals, &user, sizeof(struct _ds_spam_totals));
511    CTX->totals.spam_learned
512      = user.spam_learned + group.spam_learned;
513    CTX->totals.innocent_learned
514      = user.innocent_learned + group.innocent_learned;
515    CTX->totals.spam_misclassified
516      = user.spam_misclassified + group.spam_misclassified;
517    CTX->totals.innocent_misclassified
518      = user.innocent_misclassified + group.innocent_misclassified;
519    CTX->totals.spam_corpusfed
520      = user.spam_corpusfed + group.spam_corpusfed;
521    CTX->totals.innocent_corpusfed
522      = user.innocent_corpusfed + group.innocent_corpusfed;
523    CTX->totals.spam_classified
524      = user.spam_classified + group.spam_classified;
525    CTX->totals.innocent_classified
526      = user.innocent_classified + group.innocent_classified;
527  } else {
528    memcpy(&CTX->totals, &user, sizeof(struct _ds_spam_totals));
529    memcpy(&s->control_totals, &user, sizeof(struct _ds_spam_totals));
530  }
531
532  return 0;
533
534FAIL:
535  mysql_free_result (result);
536  result = NULL;
537  return EFAILURE;
538}
539
540int
541_mysql_drv_set_spamtotals (DSPAM_CTX * CTX)
542{
543  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
544  struct passwd *p;
545  char *name;
546  char query[1024];
547  int query_rc = 0;
548  int query_errno = 0;
549  int update_any = 0;
550  struct _ds_spam_totals user;
551
552  if (s->dbt == NULL)
553  {
554    LOGDEBUG ("_mysql_drv_set_spamtotals: invalid database handle (NULL)");
555    return EINVAL;
556  }
557
558  if (CTX->operating_mode == DSM_CLASSIFY)
559  {
560    _mysql_drv_get_spamtotals (CTX);    /* undo changes to in memory totals */
561    return 0;
562  }
563
564  if (!CTX->group || CTX->flags & DSF_MERGED) {
565    p = _mysql_drv_getpwnam (CTX, CTX->username);
566    name = CTX->username;
567  } else {
568    p = _mysql_drv_getpwnam (CTX, CTX->group);
569    name = CTX->group;
570  }
571
572  if (p == NULL)
573  {
574    LOGDEBUG ("_mysql_drv_set_spamtotals: unable to _mysql_drv_getpwnam(%s)",
575              name);
576    return EINVAL;
577  }
578
579  /* Subtract the group totals from our active set */
580  if (CTX->flags & DSF_MERGED) {
581    memcpy(&user, &CTX->totals, sizeof(struct _ds_spam_totals));
582    CTX->totals.innocent_learned -= s->merged_totals.innocent_learned;
583    CTX->totals.spam_learned -= s->merged_totals.spam_learned;
584    CTX->totals.innocent_misclassified -= s->merged_totals.innocent_misclassified;
585    CTX->totals.spam_misclassified -= s->merged_totals.spam_misclassified;
586    CTX->totals.innocent_corpusfed -= s->merged_totals.innocent_corpusfed;
587    CTX->totals.spam_corpusfed -= s->merged_totals.spam_corpusfed;
588    CTX->totals.innocent_classified -= s->merged_totals.innocent_classified;
589    CTX->totals.spam_classified -= s->merged_totals.spam_classified;
590
591    if (CTX->totals.innocent_learned < 0) CTX->totals.innocent_learned = 0;
592    if (CTX->totals.spam_learned < 0) CTX->totals.spam_learned = 0;
593    if (CTX->totals.innocent_misclassified < 0) CTX->totals.innocent_misclassified = 0;
594    if (CTX->totals.spam_misclassified < 0) CTX->totals.spam_misclassified = 0;
595    if (CTX->totals.innocent_corpusfed < 0) CTX->totals.innocent_corpusfed = 0;
596    if (CTX->totals.spam_corpusfed < 0) CTX->totals.spam_corpusfed = 0;
597    if (CTX->totals.innocent_classified < 0) CTX->totals.innocent_classified = 0;
598    if (CTX->totals.spam_classified < 0) CTX->totals.spam_classified = 0;
599  }
600
601  if (s->control_totals.innocent_learned == 0)
602  {
603    snprintf (query, sizeof (query),
604              "INSERT INTO dspam_stats (uid,spam_learned,innocent_learned,"
605              "spam_misclassified,innocent_misclassified,"
606              "spam_corpusfed,innocent_corpusfed,"
607              "spam_classified,innocent_classified)"
608              " VALUES (%d,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu)",
609              (int) p->pw_uid, CTX->totals.spam_learned,
610              CTX->totals.innocent_learned, CTX->totals.spam_misclassified,
611              CTX->totals.innocent_misclassified, CTX->totals.spam_corpusfed,
612              CTX->totals.innocent_corpusfed, CTX->totals.spam_classified,
613              CTX->totals.innocent_classified);
614    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
615    if (query_rc) {
616      query_errno = mysql_errno (s->dbt->dbh_write);
617      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
618        /* Locking issue. Wait 1 second and then retry the transaction again */
619        sleep(1);
620        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
621      }
622    }
623  } else {
624    query_rc = -1;
625  }
626
627  if (query_rc) {
628    /* Do not update stats if all values are zero (aka: no update needed) */
629    if (!(abs(CTX->totals.spam_learned           - s->control_totals.spam_learned) == 0 &&
630          abs(CTX->totals.innocent_learned       - s->control_totals.innocent_learned) == 0 &&
631          abs(CTX->totals.spam_misclassified     - s->control_totals.spam_misclassified) == 0 &&
632          abs(CTX->totals.innocent_misclassified - s->control_totals.innocent_misclassified) == 0 &&
633          abs(CTX->totals.spam_corpusfed         - s->control_totals.spam_corpusfed) == 0 &&
634          abs(CTX->totals.innocent_corpusfed     - s->control_totals.innocent_corpusfed) == 0 &&
635          abs(CTX->totals.spam_classified        - s->control_totals.spam_classified) == 0 &&
636          abs(CTX->totals.innocent_classified    - s->control_totals.innocent_classified) == 0)) {
637
638      buffer *buf;
639      buf = buffer_create (NULL);
640      if (buf == NULL) {
641        LOG (LOG_CRIT, ERR_MEM_ALLOC);
642        if (CTX->flags & DSF_MERGED)
643          memcpy(&CTX->totals, &user, sizeof(struct _ds_spam_totals));
644        return EUNKNOWN;
645      }
646
647      snprintf (query, sizeof (query),
648                "UPDATE dspam_stats SET ");
649      buffer_copy (buf, query);
650
651      /* Do not update spam learned if no change is needed */
652      if (abs (CTX->totals.spam_learned - s->control_totals.spam_learned) != 0) {
653        snprintf (query, sizeof (query),
654                  "%sspam_learned=spam_learned%s%d",
655                  (update_any) ? "," : "",
656                  (CTX->totals.spam_learned > s->control_totals.spam_learned) ? "+" : "-",
657                  abs (CTX->totals.spam_learned - s->control_totals.spam_learned));
658        update_any = 1;
659        buffer_cat (buf, query);
660      }
661      /* Do not update innocent learned if no change is needed */
662      if (abs (CTX->totals.innocent_learned - s->control_totals.innocent_learned) != 0) {
663        snprintf (query, sizeof (query),
664                  "%sinnocent_learned=innocent_learned%s%d",
665                  (update_any) ? "," : "",
666                  (CTX->totals.innocent_learned > s->control_totals.innocent_learned) ? "+" : "-",
667                  abs (CTX->totals.innocent_learned - s->control_totals.innocent_learned));
668        update_any = 1;
669        buffer_cat (buf, query);
670      }
671      /* Do not update spam misclassified if no change is needed */
672      if (abs (CTX->totals.spam_misclassified - s->control_totals.spam_misclassified) != 0) {
673        snprintf (query, sizeof (query),
674                  "%sspam_misclassified=spam_misclassified%s%d",
675                  (update_any) ? "," : "",
676                  (CTX->totals.spam_misclassified > s->control_totals.spam_misclassified) ? "+" : "-",
677                  abs (CTX->totals.spam_misclassified - s->control_totals.spam_misclassified));
678        update_any = 1;
679        buffer_cat (buf, query);
680      }
681      /* Do not update innocent misclassified if no change is needed */
682      if (abs (CTX->totals.innocent_misclassified - s->control_totals.innocent_misclassified) != 0) {
683        snprintf (query, sizeof (query),
684                  "%sinnocent_misclassified=innocent_misclassified%s%d",
685                  (update_any) ? "," : "",
686                  (CTX->totals.innocent_misclassified > s->control_totals.innocent_misclassified) ? "+" : "-",
687                  abs (CTX->totals.innocent_misclassified - s->control_totals.innocent_misclassified));
688        update_any = 1;
689        buffer_cat (buf, query);
690      }
691      /* Do not update spam corpusfed if no change is needed */
692      if (abs (CTX->totals.spam_corpusfed - s->control_totals.spam_corpusfed) != 0) {
693        snprintf (query, sizeof (query),
694                  "%sspam_corpusfed=spam_corpusfed%s%d",
695                  (update_any) ? "," : "",
696                  (CTX->totals.spam_corpusfed > s->control_totals.spam_corpusfed) ? "+" : "-",
697                  abs (CTX->totals.spam_corpusfed - s->control_totals.spam_corpusfed));
698        update_any = 1;
699        buffer_cat (buf, query);
700      }
701      /* Do not update innocent corpusfed if no change is needed */
702      if (abs (CTX->totals.innocent_corpusfed - s->control_totals.innocent_corpusfed) != 0) {
703        snprintf (query, sizeof (query),
704                  "%sinnocent_corpusfed=innocent_corpusfed%s%d",
705                  (update_any) ? "," : "",
706                  (CTX->totals.innocent_corpusfed > s->control_totals.innocent_corpusfed) ? "+" : "-",
707                  abs (CTX->totals.innocent_corpusfed - s->control_totals.innocent_corpusfed));
708        update_any = 1;
709        buffer_cat (buf, query);
710      }
711      /* Do not update spam classified if no change is needed */
712      if (abs (CTX->totals.spam_classified - s->control_totals.spam_classified) != 0) {
713        snprintf (query, sizeof (query),
714                  "%sspam_classified=spam_classified%s%d",
715                  (update_any) ? "," : "",
716                  (CTX->totals.spam_classified > s->control_totals.spam_classified) ? "+" : "-",
717                  abs (CTX->totals.spam_classified - s->control_totals.spam_classified));
718        update_any = 1;
719        buffer_cat (buf, query);
720      }
721      /* Do not update innocent classified if no change is needed */
722      if (abs (CTX->totals.innocent_classified - s->control_totals.innocent_classified) != 0) {
723        snprintf (query, sizeof (query),
724                  "%sinnocent_classified=innocent_classified%s%d",
725                  (update_any) ? "," : "",
726                  (CTX->totals.innocent_classified > s->control_totals.innocent_classified) ? "+" : "-",
727                  abs (CTX->totals.innocent_classified - s->control_totals.innocent_classified));
728        buffer_cat (buf, query);
729      }
730      snprintf (query, sizeof (query),
731                " WHERE uid=%d",
732                (int) p->pw_uid);
733      buffer_cat (buf, query);
734      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, buf->data);
735      if (query_rc) {
736        query_errno = mysql_errno (s->dbt->dbh_write);
737        if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
738          /* Locking issue. Wait 1 second and then retry the transaction again */
739          sleep(1);
740          query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, buf->data);
741        }
742      }
743
744      if (query_rc) {
745        _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), buf->data);
746        LOGDEBUG ("_mysql_drv_set_spamtotals: unable to run query: %s", buf->data);
747        if (CTX->flags & DSF_MERGED)
748          memcpy(&CTX->totals, &user, sizeof(struct _ds_spam_totals));
749        buffer_destroy (buf);
750        return EFAILURE;
751      }
752      buffer_destroy (buf);
753    }
754  }
755
756  if (CTX->flags & DSF_MERGED)
757    memcpy(&CTX->totals, &user, sizeof(struct _ds_spam_totals));
758
759  return 0;
760}
761
762int
763_ds_getall_spamrecords (DSPAM_CTX * CTX, ds_diction_t diction)
764{
765  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
766  struct passwd *p;
767  char *name;
768  buffer *query;
769  ds_term_t ds_term;
770  ds_cursor_t ds_c;
771  char scratch[1024];
772  char queryhead[1024];
773  MYSQL_RES *result;
774  MYSQL_ROW row;
775  int query_rc = 0;
776  int query_errno = 0;
777  struct _ds_spam_stat stat;
778  unsigned long long token = 0;
779  int uid = -1, gid = -1;
780  result = NULL;
781
782  if (diction->items < 1)
783    return 0;
784
785  if (s->dbt == NULL)
786  {
787    LOGDEBUG ("_ds_getall_spamrecords: invalid database handle (NULL)");
788    return EINVAL;
789  }
790
791  if (!CTX->group || CTX->flags & DSF_MERGED) {
792    p = _mysql_drv_getpwnam (CTX, CTX->username);
793    name = CTX->username;
794  } else {
795    p = _mysql_drv_getpwnam (CTX, CTX->group);
796    name = CTX->group;
797  }
798
799  if (p == NULL)
800  {
801    LOGDEBUG ("_ds_getall_spamrecords: unable to _mysql_drv_getpwnam(%s)",
802              name);
803    return EINVAL;
804  }
805
806  uid = (int) p->pw_uid;
807
808  if (CTX->group != NULL && CTX->flags & DSF_MERGED) {
809    p = _mysql_drv_getpwnam (CTX, CTX->group);
810    if (p == NULL)
811    {
812      LOGDEBUG ("_ds_getall_spamrecords: unable to _mysql_drv_getpwnam(%s)",
813                CTX->group);
814      return EINVAL;
815    }
816    gid = (int) p->pw_uid;
817  }
818
819  if (gid < 0) gid = uid;
820
821  stat.spam_hits     = 0;
822  stat.innocent_hits = 0;
823  stat.probability   = 0.00000;
824
825  /* Get the all spam records but split the query when the query size + 1024
826   * reaches max_allowed_packet
827   */
828  query = buffer_create (NULL);
829  if (query == NULL)
830  {
831    LOG (LOG_CRIT, ERR_MEM_ALLOC);
832    return EUNKNOWN;
833  }
834
835  if (uid != gid) {
836    snprintf (queryhead, sizeof(queryhead),
837            "SELECT uid,token,spam_hits,innocent_hits"
838            " FROM dspam_token_data WHERE uid IN (%d,%d) AND token IN (",
839            (int) uid, (int) gid);
840  } else {
841    snprintf (queryhead, sizeof(queryhead),
842            "SELECT uid,token,spam_hits,innocent_hits"
843            " FROM dspam_token_data WHERE uid=%d AND token IN (",
844            (int) uid);
845  }
846
847  ds_c = ds_diction_cursor(diction);
848  ds_term = ds_diction_next(ds_c);
849  while (ds_term) {
850    scratch[0] = 0;
851    buffer_copy(query, queryhead);
852    while (ds_term) {
853      snprintf (scratch, sizeof (scratch), "'%llu'", ds_term->key);
854      buffer_cat (query, scratch);
855      ds_term->s.innocent_hits = 0;
856      ds_term->s.spam_hits = 0;
857      ds_term->s.probability = 0.00000;
858      ds_term->s.status = 0;
859      if((unsigned long)(query->used + 1024) > _mysql_driver_get_max_packet(s->dbt->dbh_read)) {
860        LOGDEBUG("_ds_getall_spamrecords: Splitting query at %ld characters", query->used);
861        break;
862      }
863      ds_term = ds_diction_next(ds_c);
864      if (ds_term)
865        buffer_cat (query, ",");
866    }
867    buffer_cat (query, ")");
868
869#ifdef VERBOSE
870  LOGDEBUG ("mysql query length: %ld\n", query->used);
871  _mysql_drv_query_error ("VERBOSE DEBUG (INFO ONLY - NOT AN ERROR)", query->data);
872#endif
873
874    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query->data);
875    if (query_rc) {
876      query_errno = mysql_errno (s->dbt->dbh_read);
877      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
878        /* Locking issue. Wait 1 second and then retry the transaction again */
879        sleep(1);
880        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query->data);
881      }
882    }
883    if (query_rc) {
884      _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query->data);
885      LOGDEBUG ("_ds_getall_spamrecords: unable to run query: %s", query->data);
886      buffer_destroy(query);
887      ds_diction_close(ds_c);
888      return EFAILURE;
889    }
890    result = mysql_use_result (s->dbt->dbh_read);
891    if (result == NULL) {
892      LOGDEBUG("_ds_getall_spamrecords: failed mysql_use_result()");
893      buffer_destroy(query);
894      ds_diction_close(ds_c);
895      return EFAILURE;
896    }
897    while ((row = mysql_fetch_row (result)) != NULL) {
898      int rid = atoi(row[0]);
899      if (rid == INT_MAX && errno == ERANGE) {
900        LOGDEBUG("_ds_getall_spamrecords: failed converting %s to rid", row[0]);
901        ds_diction_close(ds_c);
902        goto FAIL;
903      }
904      token = strtoull (row[1], NULL, 0);
905      if (token == ULLONG_MAX && errno == ERANGE) {
906        LOGDEBUG("_ds_getall_spamrecords: failed converting %s to token", row[1]);
907        ds_diction_close(ds_c);
908        goto FAIL;
909      }
910      stat.spam_hits = strtoul (row[2], NULL, 0);
911      if ((unsigned long int)stat.spam_hits == ULONG_MAX && errno == ERANGE) {
912        LOGDEBUG("_ds_getall_spamrecords: failed converting %s to stat.spam_hits", row[2]);
913        ds_diction_close(ds_c);
914        goto FAIL;
915      }
916      stat.innocent_hits = strtoul (row[3], NULL, 0);
917      if ((unsigned long int)stat.innocent_hits == ULONG_MAX && errno == ERANGE) {
918        LOGDEBUG("_ds_getall_spamrecords: failed converting %s to stat.innocent_hits", row[3]);
919        ds_diction_close(ds_c);
920        goto FAIL;
921      }
922      stat.status = 0;
923      if (rid == uid)
924        stat.status |= TST_DISK;
925      ds_diction_addstat(diction, token, &stat);
926    }
927    mysql_free_result (result);
928    result = NULL;
929    ds_term = ds_diction_next(ds_c);
930  }
931  ds_diction_close(ds_c);
932  buffer_destroy (query);
933  mysql_free_result (result);
934  result = NULL;
935
936  /* Control token */
937  stat.spam_hits = 10;
938  stat.innocent_hits = 10;
939  stat.status = 0;
940  ds_diction_touch(diction, CONTROL_TOKEN, "$$CONTROL$$", 0);
941  ds_diction_addstat(diction, CONTROL_TOKEN, &stat);
942  s->control_token = CONTROL_TOKEN;
943  s->control_ih = 10;
944  s->control_sh = 10;
945
946  return 0;
947
948FAIL:
949  mysql_free_result (result);
950  result = NULL;
951  return EFAILURE;
952}
953
954int
955_ds_setall_spamrecords (DSPAM_CTX * CTX, ds_diction_t diction)
956{
957  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
958  struct _ds_spam_stat control, stat;
959  ds_term_t ds_term;
960  ds_cursor_t ds_c;
961  char queryhead[1024];
962  buffer *query;
963  char scratch[1024];
964  buffer *buf;
965  int query_rc = 0;
966  int query_errno = 0;
967  struct passwd *p;
968  char *name;
969  int update_any = 0;
970#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40100
971  char inserthead[1024];
972  buffer *insert;
973  int insert_any = 0;
974#endif
975
976  if (diction->items < 1)
977    return 0;
978
979  if (s->dbt == NULL)
980  {
981    LOGDEBUG ("_ds_setall_spamrecords: invalid database handle (NULL)");
982    return EINVAL;
983  }
984
985  if (CTX->operating_mode == DSM_CLASSIFY &&
986     (CTX->training_mode != DST_TOE ||
987     (diction->whitelist_token == 0 && (!(CTX->flags & DSF_NOISE)))))
988    return 0;
989
990  if (!CTX->group || CTX->flags & DSF_MERGED) {
991    p = _mysql_drv_getpwnam (CTX, CTX->username);
992    name = CTX->username;
993  } else {
994    p = _mysql_drv_getpwnam (CTX, CTX->group);
995    name = CTX->group;
996  }
997
998  if (p == NULL)
999  {
1000    LOGDEBUG ("_ds_setall_spamrecords: unable to _mysql_drv_getpwnam(%s)",
1001              name);
1002    return EINVAL;
1003  }
1004
1005  query = buffer_create (NULL);
1006  if (query == NULL)
1007  {
1008    LOG (LOG_CRIT, ERR_MEM_ALLOC);
1009    return EUNKNOWN;
1010  }
1011  buf = buffer_create (NULL);
1012  if (buf == NULL)
1013  {
1014    buffer_destroy(query);
1015    LOG (LOG_CRIT, ERR_MEM_ALLOC);
1016    return EUNKNOWN;
1017  }
1018
1019#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40100
1020  insert = buffer_create(NULL);
1021  if (insert == NULL)
1022  {
1023    buffer_destroy(query);
1024    buffer_destroy(buf);
1025    LOG (LOG_CRIT, ERR_MEM_ALLOC);
1026    return EUNKNOWN;
1027  }
1028#endif
1029
1030  ds_diction_getstat(diction, s->control_token, &control);
1031  snprintf (scratch, sizeof (scratch),
1032            "UPDATE dspam_token_data SET last_hit=CURRENT_DATE()");
1033  buffer_copy (buf, scratch);
1034  /* Do not update spam hits if no change is needed */
1035  int sh_adiff = abs(control.spam_hits - s->control_sh);
1036  if (sh_adiff != 0) {
1037    if (control.spam_hits > s->control_sh) {
1038      snprintf (scratch, sizeof (scratch),
1039                ",spam_hits=spam_hits+%d",
1040                sh_adiff);
1041    } else {
1042      snprintf (scratch, sizeof (scratch),
1043                ",spam_hits=IF(spam_hits<%d,0,spam_hits-%d)",
1044                sh_adiff + 1,
1045                sh_adiff);
1046    }
1047    buffer_cat (buf, scratch);
1048  }
1049  /* Do not update innocent hits if no change is needed */
1050  int ih_adiff = abs(control.innocent_hits - s->control_ih);
1051  if (ih_adiff != 0) {
1052    if (control.innocent_hits > s->control_ih) {
1053      snprintf (scratch, sizeof (scratch),
1054                ",innocent_hits=innocent_hits+%d",
1055                ih_adiff);
1056    } else {
1057      snprintf (scratch, sizeof (scratch),
1058                ",innocent_hits=IF(innocent_hits<%d,0,innocent_hits-%d)",
1059                ih_adiff + 1,
1060                ih_adiff);
1061    }
1062    buffer_cat (buf, scratch);
1063  }
1064  snprintf (scratch, sizeof (scratch),
1065            " WHERE uid=%d AND token IN (",
1066            (int) p->pw_uid);
1067  buffer_cat (buf, scratch);
1068
1069  /* Query head used for update */
1070  snprintf (queryhead, sizeof (queryhead),
1071            "%s",
1072            buf->data);
1073  buffer_destroy (buf);
1074
1075  buffer_copy (query, queryhead);
1076
1077#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40100
1078  snprintf (inserthead, sizeof(inserthead),
1079            "INSERT INTO dspam_token_data(uid,token,spam_hits,"
1080            "innocent_hits,last_hit) VALUES");
1081  buffer_copy (insert, inserthead);
1082#endif
1083
1084  /*
1085   *  Add each token in the diction to either an update or an insert queue
1086   */
1087
1088  ds_c = ds_diction_cursor(diction);
1089  ds_term = ds_diction_next(ds_c);
1090  while(ds_term)
1091  {
1092    int use_comma = 0;
1093    if (ds_term->key == s->control_token) {
1094      ds_term = ds_diction_next(ds_c);
1095      continue;
1096    }
1097
1098    /* Don't write lexical tokens if we're in TOE mode classifying */
1099
1100    if (CTX->training_mode == DST_TOE            &&
1101        CTX->operating_mode == DSM_CLASSIFY      &&
1102        ds_term->key != diction->whitelist_token &&
1103        (!ds_term->name || strncmp(ds_term->name, "bnr.", 4)))
1104    {
1105      ds_term = ds_diction_next(ds_c);
1106      continue;
1107    }
1108
1109    ds_diction_getstat(diction, ds_term->key, &stat);
1110
1111    /* Changed tokens are marked as "dirty" by libdspam */
1112
1113    if (!(stat.status & TST_DIRTY)) {
1114      ds_term = ds_diction_next(ds_c);
1115      continue;
1116    } else {
1117      stat.status &= ~TST_DIRTY;
1118    }
1119
1120    /* This token wasn't originally loaded from disk, so try an insert */
1121
1122    if (!(stat.status & TST_DISK))
1123    {
1124      char ins[1024];
1125#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40100
1126      snprintf (ins, sizeof (ins),
1127                "%s(%d,'%llu',%d,%d,CURRENT_DATE())",
1128                 (insert_any) ? "," : "",
1129                 (int) p->pw_uid,
1130                 ds_term->key,
1131                 stat.spam_hits > 0 ? 1 : 0,
1132                 stat.innocent_hits > 0 ? 1 : 0);
1133
1134      insert_any = 1;
1135      buffer_cat(insert, ins);
1136
1137      if((unsigned long)(insert->used + 1024) > _mysql_driver_get_max_packet(s->dbt->dbh_write)) {
1138        LOGDEBUG("_ds_setall_spamrecords: Splitting insert query at %ld characters", insert->used);
1139        if (insert_any) {
1140          snprintf (scratch, sizeof (scratch),
1141                    " ON DUPLICATE KEY UPDATE last_hit=CURRENT_DATE()");
1142          buffer_cat(insert, scratch);
1143          /* Do not update spam hits if no change is needed */
1144          if (abs(control.spam_hits - s->control_sh) != 0) {
1145            if (control.spam_hits > s->control_sh) {
1146              snprintf (scratch, sizeof (scratch), ",spam_hits=spam_hits+1");
1147            } else {
1148              snprintf (scratch, sizeof (scratch), ",spam_hits=IF(spam_hits<2,0,spam_hits-1)");
1149            }
1150            buffer_cat(insert, scratch);
1151          }
1152          /* Do not update innocent hits if no change is needed */
1153          if (abs(control.innocent_hits - s->control_ih) != 0) {
1154            if (control.innocent_hits > s->control_ih) {
1155              snprintf (scratch, sizeof (scratch), ",innocent_hits=innocent_hits+1");
1156            } else {
1157              snprintf (scratch, sizeof (scratch), ",innocent_hits=IF(innocent_hits<2,0,innocent_hits-1)");
1158            }
1159            buffer_cat(insert, scratch);
1160          }
1161          query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, insert->data);
1162          if (query_rc) {
1163            query_errno = mysql_errno (s->dbt->dbh_write);
1164            if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1165              /* Locking issue. Wait 1 second and then retry the transaction again */
1166              sleep(1);
1167              query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, insert->data);
1168            }
1169          }
1170          if (query_rc) {
1171            _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), insert->data);
1172            LOGDEBUG ("_ds_setall_spamrecords: unable to run insert query: %s", insert->data);
1173            buffer_destroy(insert);
1174            return EFAILURE;
1175          }
1176        }
1177        buffer_copy (insert, inserthead);
1178        insert_any = 0;
1179      }
1180#else
1181      snprintf(ins, sizeof (ins),
1182               "INSERT INTO dspam_token_data(uid,token,spam_hits,"
1183               "innocent_hits,last_hit) VALUES (%d,'%llu',%d,%d,"
1184               "CURRENT_DATE())",
1185               p->pw_uid,
1186               ds_term->key,
1187               stat.spam_hits > 0 ? 1 : 0,
1188               stat.innocent_hits > 0 ? 1 : 0);
1189
1190      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, ins);
1191      if (query_rc) {
1192        query_errno = mysql_errno (s->dbt->dbh_write);
1193        if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1194          /* Locking issue. Wait 1 second and then retry the transaction again */
1195          sleep(1);
1196          query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, ins);
1197        }
1198      }
1199      if (query_rc)
1200        stat.status |= TST_DISK;
1201#endif
1202    }
1203
1204    if (stat.status & TST_DISK) {
1205      snprintf (scratch, sizeof (scratch), "'%llu'", ds_term->key);
1206      buffer_cat (query, scratch);
1207      update_any = 1;
1208      use_comma = 1;
1209    }
1210
1211    ds_term->s.status |= TST_DISK;
1212
1213    ds_term = ds_diction_next(ds_c);
1214    if((unsigned long)(query->used + 1024) > _mysql_driver_get_max_packet(s->dbt->dbh_write)) {
1215      LOGDEBUG("_ds_setall_spamrecords: Splitting update query at %ld characters", query->used);
1216      buffer_cat (query, ")");
1217      if (update_any) {
1218        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query->data);
1219        if (query_rc) {
1220          query_errno = mysql_errno (s->dbt->dbh_write);
1221          if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1222            /* Locking issue. Wait 1 second and then retry the transaction again */
1223            sleep(1);
1224            query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query->data);
1225          }
1226        }
1227        if (query_rc) {
1228          _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query->data);
1229          LOGDEBUG ("_ds_setall_spamrecords: unable to run update query: %s", query->data);
1230          buffer_destroy(query);
1231          ds_diction_close(ds_c);
1232          return EFAILURE;
1233        }
1234      }
1235      buffer_copy (query, queryhead);
1236    } else if (ds_term && use_comma)
1237      buffer_cat (query, ",");
1238  }
1239  ds_diction_close(ds_c);
1240
1241  /* Just incase */
1242
1243  if (query->used && query->data[strlen (query->data) - 1] == ',')
1244  {
1245    query->used--;
1246    query->data[strlen (query->data) - 1] = 0;
1247  }
1248
1249  buffer_cat (query, ")");
1250
1251  LOGDEBUG("Control: [%ld %ld] [%lu %lu] Delta: [%lu %lu]",
1252    s->control_sh, s->control_ih,
1253    control.spam_hits, control.innocent_hits,
1254    control.spam_hits - s->control_sh, control.innocent_hits - s->control_ih);
1255
1256  if (update_any) {
1257    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query->data);
1258    if (query_rc) {
1259      query_errno = mysql_errno (s->dbt->dbh_write);
1260      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1261        /* Locking issue. Wait 1 second and then retry the transaction again */
1262        sleep(1);
1263        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query->data);
1264      }
1265    }
1266    if (query_rc) {
1267      _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query->data);
1268      LOGDEBUG ("_ds_setall_spamrecords: unable to run update query: %s", query->data);
1269      buffer_destroy(query);
1270      return EFAILURE;
1271    }
1272  }
1273  buffer_destroy (query);
1274
1275#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40100
1276  if (insert_any) {
1277    snprintf (scratch, sizeof (scratch),
1278              " ON DUPLICATE KEY UPDATE last_hit=CURRENT_DATE()");
1279    buffer_cat(insert, scratch);
1280    /* Do not update spam hits if no change is needed */
1281    if (abs(control.spam_hits - s->control_sh) != 0) {
1282      if (control.spam_hits > s->control_sh) {
1283        snprintf (scratch, sizeof (scratch), ",spam_hits=spam_hits+1");
1284      } else {
1285        snprintf (scratch, sizeof (scratch), ",spam_hits=IF(spam_hits<2,0,spam_hits-1)");
1286      }
1287      buffer_cat(insert, scratch);
1288    }
1289    /* Do not update innocent hits if no change is needed */
1290    if (abs(control.innocent_hits - s->control_ih) != 0) {
1291      if (control.innocent_hits > s->control_ih) {
1292        snprintf (scratch, sizeof (scratch), ",innocent_hits=innocent_hits+1");
1293      } else {
1294        snprintf (scratch, sizeof (scratch), ",innocent_hits=IF(innocent_hits<2,0,innocent_hits-1)");
1295      }
1296      buffer_cat(insert, scratch);
1297    }
1298    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, insert->data);
1299    if (query_rc) {
1300      query_errno = mysql_errno (s->dbt->dbh_write);
1301      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1302        /* Locking issue. Wait 1 second and then retry the transaction again */
1303        sleep(1);
1304        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, insert->data);
1305      }
1306    }
1307    if (query_rc) {
1308      _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), insert->data);
1309      LOGDEBUG ("_ds_setall_spamrecords: unable to run insert query: %s", insert->data);
1310      buffer_destroy(insert);
1311      return EFAILURE;
1312    }
1313  }
1314
1315  buffer_destroy(insert);
1316#endif
1317
1318  return 0;
1319}
1320
1321int
1322_ds_get_spamrecord (DSPAM_CTX * CTX, unsigned long long token,
1323                    struct _ds_spam_stat *stat)
1324{
1325  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
1326  char query[1024];
1327  struct passwd *p;
1328  char *name;
1329  MYSQL_RES *result;
1330  MYSQL_ROW row;
1331  int query_rc = 0;
1332  int query_errno = 0;
1333  result = NULL;
1334
1335  if (s->dbt == NULL)
1336  {
1337    LOGDEBUG ("_ds_get_spamrecord: invalid database handle (NULL)");
1338    return EINVAL;
1339  }
1340
1341  if (!CTX->group || CTX->flags & DSF_MERGED) {
1342    p = _mysql_drv_getpwnam (CTX, CTX->username);
1343    name = CTX->username;
1344  } else {
1345    p = _mysql_drv_getpwnam (CTX, CTX->group);
1346    name = CTX->group;
1347  }
1348
1349  if (p == NULL)
1350  {
1351    LOGDEBUG ("_ds_get_spamrecord: unable to _mysql_drv_getpwnam(%s)",
1352              name);
1353    return EINVAL;
1354  }
1355
1356  snprintf (query, sizeof (query),
1357          "SELECT spam_hits,innocent_hits FROM dspam_token_data"
1358          " WHERE uid=%d AND token IN ('%llu')", (int) p->pw_uid, token);
1359
1360  stat->probability = 0.00000;
1361  stat->spam_hits = 0;
1362  stat->innocent_hits = 0;
1363  stat->status &= ~TST_DISK;
1364
1365  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
1366  if (query_rc) {
1367    query_errno = mysql_errno (s->dbt->dbh_read);
1368    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1369      /* Locking issue. Wait 1 second and then retry the transaction again */
1370      sleep(1);
1371      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
1372    }
1373  }
1374  if (query_rc) {
1375    _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query);
1376    LOGDEBUG ("_ds_get_spamrecord: unable to run query: %s", query);
1377    return EFAILURE;
1378  }
1379
1380  result = mysql_use_result (s->dbt->dbh_read);
1381  if (result == NULL) {
1382    LOGDEBUG("_ds_get_spamrecord: failed mysql_use_result()");
1383    return EFAILURE;
1384  }
1385
1386  row = mysql_fetch_row (result);
1387  if (row == NULL)
1388  {
1389    mysql_free_result (result);
1390    result = NULL;
1391    return 0;
1392  }
1393
1394  stat->spam_hits = strtoul (row[0], NULL, 0);
1395  if ((unsigned long int)stat->spam_hits == ULONG_MAX && errno == ERANGE) {
1396    LOGDEBUG("_ds_get_spamrecord: failed converting %s to stat->spam_hits", row[0]);
1397    mysql_free_result (result);
1398    result = NULL;
1399    return EFAILURE;
1400  }
1401  stat->innocent_hits = strtoul (row[1], NULL, 0);
1402  if ((unsigned long int)stat->innocent_hits == ULONG_MAX && errno == ERANGE) {
1403    LOGDEBUG("_ds_get_spamrecord: failed converting %s to stat->innocent_hits", row[1]);
1404    mysql_free_result (result);
1405    result = NULL;
1406    return EFAILURE;
1407  }
1408  stat->status |= TST_DISK;
1409  mysql_free_result (result);
1410  result = NULL;
1411  return 0;
1412}
1413
1414int
1415_ds_set_spamrecord (DSPAM_CTX * CTX, unsigned long long token,
1416                    struct _ds_spam_stat *stat)
1417{
1418  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
1419  char query[1024];
1420  struct passwd *p;
1421  char *name;
1422  int query_rc = 0;
1423  int query_errno = 0;
1424
1425  if (s->dbt == NULL)
1426  {
1427    LOGDEBUG ("_ds_set_spamrecord: invalid database handle (NULL)");
1428    return EINVAL;
1429  }
1430
1431  if (CTX->operating_mode == DSM_CLASSIFY)
1432    return 0;
1433
1434  if (!CTX->group || CTX->flags & DSF_MERGED) {
1435    p = _mysql_drv_getpwnam (CTX, CTX->username);
1436    name = CTX->username;
1437  } else {
1438    p = _mysql_drv_getpwnam (CTX, CTX->group);
1439    name = CTX->group;
1440  }
1441
1442  if (p == NULL)
1443  {
1444    LOGDEBUG ("_ds_set_spamrecord: unable to _mysql_drv_getpwnam(%s)",
1445              name);
1446    return EINVAL;
1447  }
1448
1449#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40001
1450  /* It's either not on disk or the caller isn't using stat.status */
1451  if (!(stat->status & TST_DISK))
1452  {
1453    snprintf (query, sizeof (query),
1454              "INSERT INTO dspam_token_data (uid,token,spam_hits,innocent_hits,last_hit)"
1455              " VALUES (%d,'%llu',%lu,%lu,CURRENT_DATE())"
1456              " ON DUPLICATE KEY UPDATE"
1457              " spam_hits=%lu,"
1458              "innocent_hits=%lu,"
1459              "last_hit=CURRENT_DATE()",
1460              (int) p->pw_uid,
1461              token,
1462              stat->spam_hits, stat->innocent_hits,
1463              stat->spam_hits, stat->innocent_hits);
1464    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
1465    if (query_rc) {
1466      query_errno = mysql_errno (s->dbt->dbh_write);
1467      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1468        /* Locking issue. Wait 1 second and then retry the transaction again */
1469        sleep(1);
1470        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
1471      }
1472    }
1473  }
1474#else
1475  /* It's either not on disk or the caller isn't using stat.status */
1476  if (!(stat->status & TST_DISK))
1477  {
1478    snprintf (query, sizeof (query),
1479              "INSERT INTO dspam_token_data (uid,token,spam_hits,innocent_hits,last_hit)"
1480              " VALUES (%d,'%llu',%lu,%lu,CURRENT_DATE())",
1481              (int) p->pw_uid, token, stat->spam_hits, stat->innocent_hits);
1482    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
1483    if (query_rc) {
1484      query_errno = mysql_errno (s->dbt->dbh_write);
1485      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1486        /* Locking issue. Wait 1 second and then retry the transaction again */
1487        sleep(1);
1488        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
1489      }
1490    }
1491  } else {
1492    query_rc = -1;
1493  }
1494
1495  if ((stat->status & TST_DISK) || query_rc)
1496  {
1497    /* insert failed; try updating instead */
1498    snprintf (query, sizeof (query), "UPDATE dspam_token_data"
1499              " SET spam_hits=%lu,"
1500              "innocent_hits=%lu,"
1501              "last_hit=CURRENT_DATE(),"
1502              " WHERE uid=%d"
1503              " AND token='%llu'", stat->spam_hits,
1504              stat->innocent_hits, (int) p->pw_uid, token);
1505    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
1506    if (query_rc) {
1507      query_errno = mysql_errno (s->dbt->dbh_write);
1508      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1509        /* Locking issue. Wait 1 second and then retry the transaction again */
1510        sleep(1);
1511        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
1512      }
1513    }
1514  }
1515#endif
1516
1517  if (query_rc) {
1518    _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query);
1519    LOGDEBUG ("_ds_set_spamrecord: unable to run query: %s", query);
1520    return EFAILURE;
1521  }
1522
1523  return 0;
1524}
1525
1526int
1527_ds_init_storage (DSPAM_CTX * CTX, void *dbh)
1528{
1529  struct _mysql_drv_storage *s;
1530  _mysql_drv_dbh_t dbt = (_mysql_drv_dbh_t) dbh;
1531
1532  if (CTX == NULL) {
1533    return EINVAL;
1534  }
1535
1536  /* don't init if we're already initted */
1537  if (CTX->storage != NULL)
1538  {
1539    LOGDEBUG ("_ds_init_storage: storage already initialized");
1540    return EINVAL;
1541  }
1542
1543  if (dbt) {
1544    if (dbt->dbh_read)
1545      if (mysql_ping(dbt->dbh_read)) {
1546        LOGDEBUG ("_ds_init_storage: MySQL server not responding to mysql_ping()");
1547        return EFAILURE;
1548      }
1549  }
1550
1551  s = calloc (1, sizeof (struct _mysql_drv_storage));
1552  if (s == NULL)
1553  {
1554    LOG (LOG_CRIT, ERR_MEM_ALLOC);
1555    return EUNKNOWN;
1556  }
1557
1558  s->dbh_attached = (dbt) ? 1 : 0;
1559  s->u_getnextuser[0] = 0;
1560  memset(&s->p_getpwnam, 0, sizeof(struct passwd));
1561  memset(&s->p_getpwuid, 0, sizeof(struct passwd));
1562
1563  if (dbt)
1564    s->dbt = dbt;
1565  else
1566    s->dbt = _ds_connect(CTX);
1567
1568  if (s->dbt == NULL)
1569  {
1570    LOG (LOG_WARNING, "Unable to initialize handle to MySQL database");
1571    free(s);
1572    return EFAILURE;
1573  }
1574
1575  CTX->storage = s;
1576
1577  s->control_token = 0;
1578  s->control_ih = 0;
1579  s->control_sh = 0;
1580
1581  /* get spam totals on successful init */
1582  if (CTX->username != NULL)
1583  {
1584    if (_mysql_drv_get_spamtotals (CTX))
1585    {
1586      LOGDEBUG ("_ds_init_storage: unable to load totals. Using zero values.");
1587    }
1588  }
1589  else
1590  {
1591    memset (&CTX->totals, 0, sizeof (struct _ds_spam_totals));
1592    memset (&s->control_totals, 0, sizeof (struct _ds_spam_totals));
1593  }
1594
1595  return 0;
1596}
1597
1598int
1599_ds_shutdown_storage (DSPAM_CTX * CTX)
1600{
1601  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
1602
1603  if (CTX->storage == NULL)
1604  {
1605    LOGDEBUG ("_ds_shutdown_storage: called with NULL storage handle");
1606    return EINVAL;
1607  }
1608
1609  if (s->dbt == NULL)
1610  {
1611    LOGDEBUG ("_ds_shutdown_storage: invalid database handle (NULL)");
1612    return EINVAL;
1613  }
1614
1615  /* Store spam totals on shutdown */
1616  if (CTX->username != NULL && CTX->operating_mode != DSM_CLASSIFY)
1617  {
1618      _mysql_drv_set_spamtotals (CTX);
1619  }
1620
1621  if (s->iter_user != NULL) {
1622    mysql_free_result (s->iter_user);
1623    s->iter_user = NULL;
1624  }
1625
1626  if (s->iter_token != NULL) {
1627    mysql_free_result (s->iter_token);
1628    s->iter_token = NULL;
1629  }
1630
1631  if (s->iter_sig != NULL) {
1632    mysql_free_result (s->iter_sig);
1633    s->iter_sig = NULL;
1634  }
1635
1636  if (! s->dbh_attached) {
1637    mysql_close (s->dbt->dbh_read);
1638    if (s->dbt->dbh_write != s->dbt->dbh_read)
1639      mysql_close (s->dbt->dbh_write);
1640    if (s->dbt)
1641      free(s->dbt);
1642  }
1643  s->dbt = NULL;
1644
1645  if (s->p_getpwnam.pw_name)
1646    free(s->p_getpwnam.pw_name);
1647  if (s->p_getpwuid.pw_name)
1648    free(s->p_getpwuid.pw_name);
1649  free(s);
1650  CTX->storage = NULL;
1651
1652  return 0;
1653}
1654
1655int
1656_ds_create_signature_id (DSPAM_CTX * CTX, char *buf, size_t len)
1657{
1658  char session[64];
1659  char digit[6];
1660  int pid, j;
1661  struct passwd *p;
1662  char *name;
1663
1664  pid = getpid ();
1665  /* TODO
1666  if (_mysql_drv_get_UIDInSignature(CTX))
1667  */
1668  if (_ds_match_attribute(CTX->config->attributes, "MySQLUIDInSignature", "on"))
1669  {
1670    if (!CTX->group || CTX->flags & DSF_MERGED) {
1671      p = _mysql_drv_getpwnam (CTX, CTX->username);
1672      name = CTX->username;
1673    } else {
1674      p = _mysql_drv_getpwnam (CTX, CTX->group);
1675      name = CTX->group;
1676    }
1677
1678    if (!p) {
1679      LOG(LOG_ERR, "Unable to determine UID for %s", name);
1680      return EINVAL;
1681    }
1682    snprintf (session, sizeof (session), "%d,%8lx%d", (int) p->pw_uid,
1683              (long) time(NULL), pid);
1684  }
1685  else
1686    snprintf (session, sizeof (session), "%8lx%d", (long) time (NULL), pid);
1687
1688  for (j = 0; j < 2; j++)
1689  {
1690    snprintf (digit, 6, "%d", rand ());
1691    strlcat (session, digit, 64);
1692  }
1693
1694  strlcpy (buf, session, len);
1695  return 0;
1696}
1697
1698int
1699_ds_get_signature (DSPAM_CTX * CTX, struct _ds_spam_signature *SIG,
1700                   const char *signature)
1701{
1702  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
1703  struct passwd *p;
1704  char *name;
1705  unsigned long *lengths;
1706  char *mem;
1707  char query[256];
1708  MYSQL_RES *result;
1709  MYSQL_ROW row;
1710  int uid = -1;
1711  MYSQL *dbh;
1712  result = NULL;
1713
1714  if (s->dbt == NULL)
1715  {
1716    LOGDEBUG ("_ds_get_signature: invalid database handle (NULL)");
1717    return EINVAL;
1718  }
1719
1720  dbh = _mysql_drv_sig_write_handle(CTX, s);
1721
1722  if (!CTX->group || CTX->flags & DSF_MERGED) {
1723    p = _mysql_drv_getpwnam (CTX, CTX->username);
1724    name = CTX->username;
1725  } else {
1726    p = _mysql_drv_getpwnam (CTX, CTX->group);
1727    name = CTX->group;
1728  }
1729  if (p == NULL)
1730  {
1731    LOGDEBUG ("_ds_get_signature: unable to _mysql_drv_getpwnam(%s)",
1732              name);
1733    return EINVAL;
1734  }
1735
1736  /* TODO
1737  if (_mysql_drv_get_UIDInSignature(CTX))
1738  */
1739  if (_ds_match_attribute(CTX->config->attributes, "MySQLUIDInSignature", "on"))
1740  {
1741    char *u, *sig, *username;
1742    void *dbt = s->dbt;
1743    int dbh_attached = s->dbh_attached;
1744    sig = strdup(signature);
1745    u = strchr(sig, ',');
1746    if (!u) {
1747      LOGDEBUG("_ds_get_signature: unable to locate uid in signature");
1748      free(sig);
1749      sig = NULL;
1750      return EFAILURE;
1751    }
1752    u[0] = 0;
1753    u = sig;
1754    uid = atoi(u);
1755    free(sig);
1756    sig = NULL;
1757
1758    /* Change the context's username and reinitialize storage */
1759
1760    p = _mysql_drv_getpwuid (CTX, uid);
1761    if (p == NULL) {
1762      LOG(LOG_CRIT, "_ds_get_signature: _mysql_drv_getpwuid(%d) failed: aborting", uid);
1763      return EFAILURE;
1764    }
1765
1766    username = strdup(p->pw_name);
1767    _ds_shutdown_storage(CTX);
1768    free(CTX->username);
1769    CTX->username = username;
1770    _ds_init_storage(CTX, (dbh_attached) ? dbt : NULL);
1771    s = (struct _mysql_drv_storage *) CTX->storage;
1772
1773    dbh = _mysql_drv_sig_write_handle(CTX, s);
1774  }
1775
1776  if (uid == -1) {
1777    uid = (int) p->pw_uid;
1778  }
1779
1780  snprintf (query, sizeof (query),
1781            "SELECT data,length FROM dspam_signature_data WHERE uid=%d AND signature=\"%s\"",
1782            (int) uid, signature);
1783
1784  if (mysql_real_query (dbh, query, strlen (query)))
1785  {
1786    _mysql_drv_query_error (mysql_error (dbh), query);
1787    LOGDEBUG ("_ds_get_signature: unable to run query: %s", query);
1788    return EFAILURE;
1789  }
1790
1791  result = mysql_use_result (dbh);
1792  if (result == NULL) {
1793    LOGDEBUG("_ds_get_signature: failed mysql_use_result()");
1794    return EFAILURE;
1795  }
1796
1797  row = mysql_fetch_row (result);
1798  if (row == NULL)
1799  {
1800    LOGDEBUG("_ds_get_signature: mysql_fetch_row() failed");
1801    mysql_free_result (result);
1802    result = NULL;
1803    return EFAILURE;
1804  }
1805
1806  lengths = mysql_fetch_lengths (result);
1807  if (lengths == NULL || lengths[0] == 0)
1808  {
1809    LOGDEBUG("_ds_get_signature: mysql_fetch_lengths() failed");
1810    mysql_free_result (result);
1811    result = NULL;
1812    return EFAILURE;
1813  }
1814
1815  mem = malloc (lengths[0]);
1816  if (mem == NULL)
1817  {
1818    LOG (LOG_CRIT, ERR_MEM_ALLOC);
1819    mysql_free_result (result);
1820    result = NULL;
1821    return EUNKNOWN;
1822  }
1823
1824  memcpy (mem, row[0], lengths[0]);
1825  if (SIG->data)
1826    free(SIG->data);
1827  SIG->data = mem;
1828  SIG->length = strtoul (row[1], NULL, 0);
1829  if (SIG->length == ULONG_MAX && errno == ERANGE) {
1830    LOGDEBUG("_ds_get_signature: failed converting %s to signature data length", row[1]);
1831    SIG->length = lengths[0];
1832  }
1833  mysql_free_result (result);
1834  result = NULL;
1835
1836  return 0;
1837}
1838
1839int
1840_ds_set_signature (DSPAM_CTX * CTX, struct _ds_spam_signature *SIG,
1841                   const char *signature)
1842{
1843  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
1844  unsigned long length;
1845  char *mem;
1846  char scratch[1024];
1847  buffer *query;
1848  struct passwd *p;
1849  char *name;
1850
1851  if (s->dbt == NULL)
1852  {
1853    LOGDEBUG ("_ds_set_signature: invalid database handle (NULL)");
1854    return EINVAL;
1855  }
1856
1857  if (!CTX->group || CTX->flags & DSF_MERGED) {
1858    p = _mysql_drv_getpwnam (CTX, CTX->username);
1859    name = CTX->username;
1860  } else {
1861    p = _mysql_drv_getpwnam (CTX, CTX->group);
1862    name = CTX->group;
1863  }
1864
1865  if (p == NULL)
1866  {
1867    LOGDEBUG ("_ds_set_signature: unable to _mysql_drv_getpwnam(%s)",
1868              name);
1869    return EINVAL;
1870  }
1871
1872  query = buffer_create (NULL);
1873  if (query == NULL)
1874  {
1875    LOG (LOG_CRIT, ERR_MEM_ALLOC);
1876    return EUNKNOWN;
1877  }
1878
1879  mem = calloc(1, (SIG->length*2)+1);
1880  if (mem == NULL)
1881  {
1882    LOG (LOG_CRIT, ERR_MEM_ALLOC);
1883    buffer_destroy(query);
1884    return EUNKNOWN;
1885  }
1886
1887  length = mysql_real_escape_string (s->dbt->dbh_write, mem, SIG->data, SIG->length);
1888
1889  if(length+1024 > _mysql_driver_get_max_packet(s->dbt->dbh_write)) {
1890    LOG (LOG_WARNING, "_ds_set_signature: signature data to big to be inserted");
1891    LOG (LOG_WARNING, "_ds_set_signature: consider increasing max_allowed_packet to at least %llu",
1892              length+1025);
1893    return EINVAL;
1894  }
1895
1896  snprintf (scratch, sizeof (scratch),
1897            "INSERT INTO dspam_signature_data (uid,signature,length,created_on,data) VALUES (%d,\"%s\",%lu,CURRENT_DATE(),\"",
1898            (int) p->pw_uid, signature, (unsigned long) SIG->length);
1899  buffer_cat (query, scratch);
1900  buffer_cat (query, mem);
1901  buffer_cat (query, "\")");
1902  free(mem);
1903  mem = NULL;
1904
1905  if (mysql_real_query (s->dbt->dbh_write, query->data, query->used))
1906  {
1907    _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query->data);
1908    LOGDEBUG ("_ds_set_signature: unable to run query: %s", query->data);
1909    buffer_destroy(query);
1910    return EFAILURE;
1911  }
1912
1913  buffer_destroy(query);
1914  return 0;
1915}
1916
1917int
1918_ds_delete_signature (DSPAM_CTX * CTX, const char *signature)
1919{
1920  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
1921  struct passwd *p;
1922  char *name;
1923  char query[256];
1924  int query_rc = 0;
1925  int query_errno = 0;
1926
1927  if (s->dbt == NULL)
1928  {
1929    LOGDEBUG ("_ds_delete_signature: invalid database handle (NULL)");
1930    return EINVAL;
1931  }
1932
1933  if (!CTX->group || CTX->flags & DSF_MERGED) {
1934    p = _mysql_drv_getpwnam (CTX, CTX->username);
1935    name = CTX->username;
1936  } else {
1937    p = _mysql_drv_getpwnam (CTX, CTX->group);
1938    name = CTX->group;
1939  }
1940
1941  if (p == NULL)
1942  {
1943    LOGDEBUG ("_ds_delete_signature: unable to _mysql_drv_getpwnam(%s)",
1944              name);
1945    return EINVAL;
1946  }
1947
1948  snprintf (query, sizeof (query),
1949            "DELETE FROM dspam_signature_data WHERE uid=%d AND signature=\"%s\"",
1950            (int) p->pw_uid, signature);
1951  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
1952  if (query_rc) {
1953    query_errno = mysql_errno (s->dbt->dbh_write);
1954    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
1955      /* Locking issue. Wait 1 second and then retry the transaction again */
1956      sleep(1);
1957      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
1958    }
1959  }
1960  if (query_rc) {
1961    _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query);
1962    LOGDEBUG ("_ds_delete_signature: unable to run query: %s", query);
1963    return EFAILURE;
1964  }
1965
1966  return 0;
1967}
1968
1969int
1970_ds_verify_signature (DSPAM_CTX * CTX, const char *signature)
1971{
1972  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
1973  struct passwd *p;
1974  char *name;
1975  char query[256];
1976  MYSQL_RES *result;
1977  MYSQL_ROW row;
1978  int query_rc = 0;
1979  int query_errno = 0;
1980  result = NULL;
1981
1982  if (s->dbt == NULL)
1983  {
1984    LOGDEBUG ("_ds_verify_signature: invalid database handle (NULL)");
1985    return EINVAL;
1986  }
1987
1988  if (!CTX->group || CTX->flags & DSF_MERGED) {
1989    p = _mysql_drv_getpwnam (CTX, CTX->username);
1990    name = CTX->username;
1991  } else {
1992    p = _mysql_drv_getpwnam (CTX, CTX->group);
1993    name = CTX->group;
1994  }
1995
1996  if (p == NULL)
1997  {
1998    LOGDEBUG ("_ds_verify_signature: unable to _mysql_drv_getpwnam(%s)",
1999              name);
2000    return EINVAL;
2001  }
2002
2003  snprintf (query, sizeof (query),
2004            "SELECT signature FROM dspam_signature_data WHERE uid=%d AND signature=\"%s\"",
2005            (int) p->pw_uid, signature);
2006  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2007  if (query_rc) {
2008    query_errno = mysql_errno (s->dbt->dbh_read);
2009    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
2010      /* Locking issue. Wait 1 second and then retry the transaction again */
2011      sleep(1);
2012      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2013    }
2014  }
2015  if (query_rc) {
2016    _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query);
2017    LOGDEBUG ("_ds_verify_signature: unable to run query: %s", query);
2018    return EFAILURE;
2019  }
2020
2021  result = mysql_use_result (s->dbt->dbh_read);
2022  if (result == NULL) {
2023    return -1;
2024  }
2025
2026  row = mysql_fetch_row (result);
2027  if (row == NULL)
2028  {
2029    mysql_free_result (result);
2030    result = NULL;
2031    return -1;
2032  }
2033
2034  mysql_free_result (result);
2035  result = NULL;
2036  return 0;
2037}
2038
2039/*
2040 * _ds_get_nextuser()
2041 *
2042 * DESCRIPTION
2043 *   The _ds_get_nextuser() function is called to get the next user from the
2044 *   classification context. Calling this function repeatedly will return all
2045 *   users one by one.
2046 *
2047 * RETURN VALUES
2048 *   returns username on success, NULL on failure or when all usernames have
2049 *   already been returned for the classification context. When there are no
2050 *   more users to return then iter_user of the storage driver is set to NULL.
2051 */
2052
2053char *
2054_ds_get_nextuser (DSPAM_CTX * CTX)
2055{
2056  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
2057#ifndef VIRTUAL_USERS
2058  struct passwd *p;
2059#else
2060  char *virtual_table, *virtual_username;
2061#endif
2062  uid_t uid;
2063  char query[512];
2064  MYSQL_ROW row;
2065  int query_rc = 0;
2066  int query_errno = 0;
2067
2068#ifdef VIRTUAL_USERS
2069  if ((virtual_table
2070    = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualTable"))==NULL)
2071  { virtual_table = "dspam_virtual_uids"; }
2072
2073  if ((virtual_username = _ds_read_attribute(CTX->config->attributes,
2074    "MySQLVirtualUsernameField")) ==NULL)
2075  { virtual_username = "username"; }
2076#endif
2077
2078  if (s->dbt == NULL)
2079  {
2080    LOGDEBUG ("_ds_get_nextuser: invalid database handle (NULL)");
2081    return NULL;
2082  }
2083
2084  if (s->iter_user == NULL)
2085  {
2086#ifdef VIRTUAL_USERS
2087    /* TODO
2088    char *virtual_table, *virtual_username;
2089    virtual_table = _mysql_drv_get_virtual_table(CTX);
2090    virtual_username = _mysql_drv_get_virtual_username_field(CTX);
2091    */
2092    snprintf(query, sizeof(query), "SELECT DISTINCT %s FROM %s",
2093      virtual_username,
2094      virtual_table);
2095#else
2096    strncpy (query, "SELECT DISTINCT uid FROM dspam_stats", sizeof(query));
2097#endif
2098    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2099    if (query_rc) {
2100      query_errno = mysql_errno (s->dbt->dbh_read);
2101      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
2102        /* Locking issue. Wait 1 second and then retry the transaction again */
2103        sleep(1);
2104        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2105      }
2106    }
2107    if (query_rc) {
2108      _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query);
2109      LOGDEBUG ("_ds_get_nextuser: unable to run query: %s", query);
2110      return NULL;
2111    }
2112
2113    s->iter_user = mysql_use_result (s->dbt->dbh_read);
2114    if (s->iter_user == NULL)
2115      return NULL;
2116  }
2117
2118  row = mysql_fetch_row (s->iter_user);
2119  if (row == NULL)
2120  {
2121    mysql_free_result (s->iter_user);
2122    s->iter_user = NULL;
2123    return NULL;
2124  }
2125
2126  if (row[0]) {
2127    uid = (uid_t) atoi (row[0]);
2128    if (uid == INT_MAX && errno == ERANGE) {
2129      LOGDEBUG("_ds_get_nextuser: failed converting %s to uid", row[0]);
2130      return NULL;
2131    }
2132  } else {
2133    LOG (LOG_CRIT, "_ds_get_nextuser: detected empty or NULL uid in stats table");
2134    return NULL;
2135  }
2136#ifdef VIRTUAL_USERS
2137  strlcpy (s->u_getnextuser, row[0], sizeof (s->u_getnextuser));
2138#else
2139  p = _mysql_drv_getpwuid (CTX, uid);
2140  if (p == NULL)
2141    return NULL;
2142
2143  strlcpy (s->u_getnextuser, p->pw_name, sizeof (s->u_getnextuser));
2144#endif
2145
2146  return s->u_getnextuser;
2147}
2148
2149/*
2150 * _ds_get_nexttoken()
2151 *
2152 * DESCRIPTION
2153 *   The _ds_get_nexttoken() function is called to get the next token from the
2154 *   classification context. Calling this function repeatedly will return all
2155 *   tokens for a user or group one by one.
2156 *
2157 * RETURN VALUES
2158 *   returns token on success, NULL on failure or when all tokens have already
2159 *   been returned for the user or group. When there are no more tokens to return
2160 *   then iter_token of the storage driver is set to NULL.
2161 */
2162
2163struct _ds_storage_record *
2164_ds_get_nexttoken (DSPAM_CTX * CTX)
2165{
2166  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
2167  struct _ds_storage_record *st;
2168  char query[256];
2169  MYSQL_ROW row;
2170  int query_rc = 0;
2171  int query_errno = 0;
2172  struct passwd *p;
2173  char *name;
2174
2175  if (s->dbt == NULL)
2176  {
2177    LOGDEBUG ("_ds_get_nexttoken: invalid database handle (NULL)");
2178    return NULL;
2179  }
2180
2181  if (!CTX->group || CTX->flags & DSF_MERGED) {
2182    p = _mysql_drv_getpwnam (CTX, CTX->username);
2183    name = CTX->username;
2184  } else {
2185    p = _mysql_drv_getpwnam (CTX, CTX->group);
2186    name = CTX->group;
2187  }
2188
2189  if (p == NULL)
2190  {
2191    LOGDEBUG ("_ds_get_nexttoken: unable to _mysql_drv_getpwnam(%s)",
2192              name);
2193    return NULL;
2194  }
2195
2196  st = calloc (1,sizeof(struct _ds_storage_record));
2197  if (st == NULL)
2198  {
2199    LOG (LOG_CRIT, ERR_MEM_ALLOC);
2200    return NULL;
2201  }
2202
2203  if (s->iter_token == NULL)
2204  {
2205    snprintf (query, sizeof (query),
2206              "SELECT token,spam_hits,innocent_hits,unix_timestamp(last_hit) FROM dspam_token_data WHERE uid=%d",
2207              (int) p->pw_uid);
2208    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2209    if (query_rc) {
2210      query_errno = mysql_errno (s->dbt->dbh_read);
2211      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
2212        /* Locking issue. Wait 1 second and then retry the transaction again */
2213        sleep(1);
2214        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2215      }
2216    }
2217    if (query_rc) {
2218      _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query);
2219      LOGDEBUG ("_ds_get_nexttoken: unable to run query: %s", query);
2220      goto FAIL;
2221    }
2222
2223    s->iter_token = mysql_use_result (s->dbt->dbh_read);
2224    if (s->iter_token == NULL)
2225      goto FAIL;
2226  }
2227
2228  row = mysql_fetch_row (s->iter_token);
2229  if (row == NULL) {
2230    mysql_free_result (s->iter_token);
2231    s->iter_token = NULL;
2232    goto FAIL;
2233  }
2234
2235  st->token = strtoull (row[0], NULL, 0);
2236  if (st->token == ULLONG_MAX && errno == ERANGE) {
2237    LOGDEBUG("_ds_get_nexttoken: failed converting %s to st->token", row[0]);
2238    goto FAIL;
2239  }
2240  st->spam_hits = strtoul (row[1], NULL, 0);
2241  if ((unsigned long int)st->spam_hits == ULONG_MAX && errno == ERANGE) {
2242    LOGDEBUG("_ds_get_nexttoken: failed converting %s to st->spam_hits", row[1]);
2243    goto FAIL;
2244  }
2245  st->innocent_hits = strtoul (row[2], NULL, 0);
2246  if ((unsigned long int)st->innocent_hits == ULONG_MAX && errno == ERANGE) {
2247    LOGDEBUG("_ds_get_nexttoken: failed converting %s to st->innocent_hits", row[2]);
2248    goto FAIL;
2249  }
2250  st->last_hit = (time_t) strtol (row[3], NULL, 0);
2251  if (st->last_hit == LONG_MAX && errno == ERANGE) {
2252    LOGDEBUG("_ds_get_nexttoken: failed converting %s to st->last_hit", row[3]);
2253    goto FAIL;
2254  }
2255
2256  return st;
2257
2258FAIL:
2259  free(st);
2260  return NULL;
2261}
2262
2263/*
2264 * _ds_get_nextsignature()
2265 *
2266 * DESCRIPTION
2267 *   The _ds_get_nextsignature() function is called to get the next signature
2268 *   from the classification context. Calling this function repeatedly will return
2269 *   all signatures for a user or group one by one.
2270 *
2271 * RETURN VALUES
2272 *   returns signature on success, NULL on failure or when all signatures have
2273 *   already been returned for the user or group. When there are no more signatures
2274 *   to return then iter_sig of the storage driver is set to NULL.
2275 */
2276
2277struct _ds_storage_signature *
2278_ds_get_nextsignature (DSPAM_CTX * CTX)
2279{
2280  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
2281  struct _ds_storage_signature *st;
2282  unsigned long *lengths;
2283  char query[256];
2284  MYSQL_ROW row;
2285  struct passwd *p;
2286  char *name;
2287  char *mem;
2288
2289  if (s->dbt == NULL)
2290  {
2291    LOGDEBUG ("_ds_get_nextsignature: invalid database handle (NULL)");
2292    return NULL;
2293  }
2294
2295  if (!CTX->group || CTX->flags & DSF_MERGED) {
2296    p = _mysql_drv_getpwnam (CTX, CTX->username);
2297    name = CTX->username;
2298  } else {
2299    p = _mysql_drv_getpwnam (CTX, CTX->group);
2300    name = CTX->group;
2301  }
2302
2303  if (p == NULL)
2304  {
2305    LOGDEBUG ("_ds_get_nextsignature: unable to _mysql_drv_getpwnam(%s)",
2306              name);
2307    return NULL;
2308  }
2309
2310  st = calloc (1, sizeof (struct _ds_storage_signature));
2311  if (st == NULL)
2312  {
2313    LOG (LOG_CRIT, ERR_MEM_ALLOC);
2314    return NULL;
2315  }
2316
2317  if (s->iter_sig == NULL)
2318  {
2319    snprintf (query, sizeof (query),
2320              "SELECT data,signature,length,unix_timestamp(created_on) FROM dspam_signature_data WHERE uid=%d",
2321              (int) p->pw_uid);
2322    if (mysql_real_query (s->dbt->dbh_read, query, strlen (query)))
2323    {
2324      _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query);
2325      LOGDEBUG ("_ds_get_nextsignature: unable to run query: %s", query);
2326      goto FAIL;
2327    }
2328
2329    s->iter_sig = mysql_use_result (s->dbt->dbh_read);
2330    if (s->iter_sig == NULL)
2331      goto FAIL;
2332  }
2333
2334  row = mysql_fetch_row (s->iter_sig);
2335  if (row == NULL) {
2336    mysql_free_result (s->iter_sig);
2337    s->iter_sig = NULL;
2338    goto FAIL;
2339  }
2340
2341  lengths = mysql_fetch_lengths (s->iter_sig);
2342  if (lengths == NULL || lengths[0] == 0)
2343    goto FAIL;
2344
2345  mem = malloc (lengths[0]);
2346  if (mem == NULL)
2347  {
2348    LOG (LOG_CRIT, ERR_MEM_ALLOC);
2349    goto FAIL;
2350  }
2351
2352  memcpy (mem, row[0], lengths[0]);
2353  st->data = mem;
2354  strlcpy (st->signature, row[1], sizeof (st->signature));
2355  st->length = strtoul (row[2], NULL, 0);
2356  if ((unsigned long int)st->length == ULONG_MAX && errno == ERANGE) {
2357    LOGDEBUG("_ds_get_nextsignature: failed converting %s to st->length", row[2]);
2358    free(st->data);
2359    goto FAIL;
2360  }
2361  st->created_on = (time_t) strtol (row[3], NULL, 0);
2362  if (st->created_on == LONG_MAX && errno == ERANGE) {
2363    LOGDEBUG("_ds_get_nextsignature: failed converting %s to st->created_on", row[3]);
2364    free(st->data);
2365    goto FAIL;
2366  }
2367
2368  return st;
2369
2370FAIL:
2371  free(st);
2372  return NULL;
2373}
2374
2375struct passwd *
2376_mysql_drv_getpwnam (DSPAM_CTX * CTX, const char *name)
2377{
2378  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
2379  int query_rc = 0;
2380  int query_errno = 0;
2381#ifndef VIRTUAL_USERS
2382  struct passwd *q;
2383#if defined(_REENTRANT) && defined(HAVE_GETPWNAM_R)
2384  struct passwd pwbuf;
2385  char buf[1024];
2386#endif
2387
2388  if (name == NULL)
2389    return NULL;
2390
2391  if (s->p_getpwnam.pw_name != NULL)
2392  {
2393    /* cache the last name queried */
2394    if (name != NULL && !strcmp (s->p_getpwnam.pw_name, name))
2395      return &s->p_getpwnam;
2396
2397    free (s->p_getpwnam.pw_name);
2398    s->p_getpwnam.pw_name = NULL;
2399    s->p_getpwnam.pw_uid = 0;
2400  }
2401
2402#if defined(_REENTRANT) && defined(HAVE_GETPWNAM_R)
2403  if (getpwnam_r(name, &pwbuf, buf, sizeof(buf), &q))
2404    q = NULL;
2405#else
2406  q = getpwnam (name);
2407#endif
2408
2409  if (q == NULL)
2410    return NULL;
2411  s->p_getpwnam.pw_uid = q->pw_uid;
2412  s->p_getpwnam.pw_name = strdup (q->pw_name);
2413
2414  return &s->p_getpwnam;
2415#else
2416  char query[512];
2417  MYSQL_RES *result;
2418  MYSQL_ROW row;
2419  char *virtual_table, *virtual_uid, *virtual_username;
2420  char *sql_username;
2421  int name_size = MAX_USERNAME_LENGTH;
2422  result = NULL;
2423
2424  if ((virtual_table
2425    = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualTable"))==NULL)
2426  { virtual_table = "dspam_virtual_uids"; }
2427
2428  if ((virtual_uid
2429    = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualUIDField"))==NULL)
2430  { virtual_uid = "uid"; }
2431
2432  if ((virtual_username = _ds_read_attribute(CTX->config->attributes,
2433    "MySQLVirtualUsernameField")) ==NULL)
2434  { virtual_username = "username"; }
2435
2436  /* TODO
2437  virtual_table = _mysql_drv_get_virtual_table(CTX);
2438  virtual_uid = _mysql_drv_get_virtual_uid_field(CTX);
2439  virtual_username = _mysql_drv_get_virtual_username_field(CTX);
2440  */
2441
2442  if (s->p_getpwnam.pw_name != NULL)
2443  {
2444    /* cache the last name queried */
2445    if (name != NULL && !strcmp (s->p_getpwnam.pw_name, name)) {
2446      LOGDEBUG("_mysql_drv_getpwnam returning cached name %s.", name);
2447      return &s->p_getpwnam;
2448    }
2449
2450    free (s->p_getpwnam.pw_name);
2451    s->p_getpwnam.pw_name = NULL;
2452  }
2453
2454  if (name != NULL) {
2455    name_size = strlen(name);
2456  }
2457
2458  sql_username = malloc ((2 * name_size) + 1);
2459  if (sql_username == NULL)
2460  {
2461    LOGDEBUG("_mysql_drv_getpwnam returning NULL for name:  %s.  malloc() failed somehow.", name);
2462    return NULL;
2463  }
2464
2465  mysql_real_escape_string (s->dbt->dbh_read, sql_username, name,
2466                            strlen(name));
2467
2468  snprintf (query, sizeof (query),
2469            "SELECT %s FROM %s WHERE %s='%s'",
2470            virtual_uid, virtual_table, virtual_username, sql_username);
2471
2472  free (sql_username);
2473  sql_username = NULL;
2474
2475  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2476  if (query_rc) {
2477    query_errno = mysql_errno (s->dbt->dbh_read);
2478    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
2479      /* Locking issue. Wait 1 second and then retry the transaction again */
2480      sleep(1);
2481      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2482    }
2483  }
2484  if (query_rc) {
2485    _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query);
2486    LOGDEBUG ("_mysql_drv_getpwnam: unable to run query: %s", query);
2487    return NULL;
2488  }
2489
2490  result = mysql_use_result (s->dbt->dbh_read);
2491  if (result == NULL) {
2492    if (CTX->source == DSS_ERROR || CTX->operating_mode != DSM_PROCESS) {
2493      LOGDEBUG("_mysql_drv_getpwnam: returning NULL for query on name: %s that returned a null result", name);
2494      return NULL;
2495    }
2496    LOGDEBUG("_mysql_drv_getpwnam: setting, then returning passed name: %s after null MySQL result", name);
2497    return _mysql_drv_setpwnam (CTX, name);
2498  }
2499
2500  row = mysql_fetch_row (result);
2501  if (row == NULL)
2502  {
2503    mysql_free_result (result);
2504    result = NULL;
2505    if (CTX->source == DSS_ERROR || CTX->operating_mode != DSM_PROCESS) {
2506      LOGDEBUG("_mysql_drv_getpwnam: returning NULL for query on name: %s", name);
2507      return NULL;
2508    }
2509    LOGDEBUG("_mysql_drv_getpwnam: setting, then returning passed name: %s", name);
2510    return _mysql_drv_setpwnam (CTX, name);
2511  }
2512
2513  s->p_getpwnam.pw_uid = atoi(row[0]);
2514  if (s->p_getpwnam.pw_uid == INT_MAX && errno == ERANGE) {
2515    LOGDEBUG("_mysql_drv_getpwnam: failed converting %s to s->p_getpwnam.pw_uid", row[0]);
2516    mysql_free_result (result);
2517    result = NULL;
2518    return NULL;
2519  }
2520  if (name == NULL)
2521    s->p_getpwnam.pw_name = strdup("");
2522  else
2523    s->p_getpwnam.pw_name = strdup (name);
2524
2525  mysql_free_result (result);
2526  result = NULL;
2527  LOGDEBUG("_mysql_drv_getpwnam: successful returning struct for name: %s", s->p_getpwnam.pw_name);
2528  return &s->p_getpwnam;
2529
2530#endif
2531}
2532
2533struct passwd *
2534_mysql_drv_getpwuid (DSPAM_CTX * CTX, uid_t uid)
2535{
2536  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
2537  int query_rc = 0;
2538  int query_errno = 0;
2539#ifndef VIRTUAL_USERS
2540  struct passwd *q;
2541#if defined(_REENTRANT) && defined(HAVE_GETPWUID_R)
2542  struct passwd pwbuf;
2543  char buf[1024];
2544#endif
2545
2546  if (s->p_getpwuid.pw_name != NULL)
2547  {
2548    /* cache the last uid queried */
2549    if (s->p_getpwuid.pw_uid == uid)
2550    {
2551      return &s->p_getpwuid;
2552    }
2553    free(s->p_getpwuid.pw_name);
2554    s->p_getpwuid.pw_name = NULL;
2555  }
2556
2557#if defined(_REENTRANT) && defined(HAVE_GETPWUID_R)
2558  if (getpwuid_r(uid, &pwbuf, buf, sizeof(buf), &q))
2559    q = NULL;
2560#else
2561  q = getpwuid (uid);
2562#endif
2563
2564  if (q == NULL)
2565   return NULL;
2566
2567  if (s->p_getpwuid.pw_name) {
2568    free(s->p_getpwuid.pw_name);
2569    s->p_getpwuid.pw_name = NULL;
2570  }
2571
2572  memcpy (&s->p_getpwuid, q, sizeof (struct passwd));
2573  s->p_getpwuid.pw_name = strdup(q->pw_name);
2574
2575  return &s->p_getpwuid;
2576#else
2577  char query[512];
2578  MYSQL_RES *result;
2579  MYSQL_ROW row;
2580  char *virtual_table, *virtual_uid, *virtual_username;
2581  result = NULL;
2582
2583  if ((virtual_table
2584    = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualTable"))==NULL)
2585  { virtual_table = "dspam_virtual_uids"; }
2586
2587  if ((virtual_uid
2588    = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualUIDField"))==NULL)
2589  { virtual_uid = "uid"; }
2590
2591  if ((virtual_username = _ds_read_attribute(CTX->config->attributes,
2592    "MySQLVirtualUsernameField")) ==NULL)
2593  { virtual_username = "username"; }
2594
2595  /* TODO
2596  virtual_table = _mysql_drv_get_virtual_table(CTX);
2597  virtual_uid = _mysql_drv_get_virtual_uid_field(CTX);
2598  virtual_username = _mysql_drv_get_virtual_username_field(CTX);
2599  */
2600
2601  if (s->p_getpwuid.pw_name != NULL)
2602  {
2603    /* cache the last uid queried */
2604    if (s->p_getpwuid.pw_uid == uid)
2605      return &s->p_getpwuid;
2606    free (s->p_getpwuid.pw_name);
2607    s->p_getpwuid.pw_name = NULL;
2608  }
2609
2610  snprintf (query, sizeof (query),
2611            "SELECT %s FROM %s WHERE %s='%d'",
2612            virtual_username, virtual_table, virtual_uid, (int) uid);
2613
2614  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2615  if (query_rc) {
2616    query_errno = mysql_errno (s->dbt->dbh_read);
2617    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
2618      /* Locking issue. Wait 1 second and then retry the transaction again */
2619      sleep(1);
2620      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2621    }
2622  }
2623  if (query_rc) {
2624    _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query);
2625    LOGDEBUG ("_mysql_drv_getpwuid: unable to run query: %s", query);
2626    return NULL;
2627  }
2628
2629  result = mysql_use_result (s->dbt->dbh_read);
2630  if (result == NULL)
2631    return NULL;
2632
2633  row = mysql_fetch_row (result);
2634  if (row == NULL)
2635  {
2636    mysql_free_result (result);
2637    result = NULL;
2638    return NULL;
2639  }
2640
2641  if (row[0] == NULL)
2642  {
2643    mysql_free_result (result);
2644    result = NULL;
2645    return NULL;
2646  }
2647
2648  s->p_getpwuid.pw_name = strdup (row[0]);
2649  s->p_getpwuid.pw_uid = (int) uid;
2650
2651  mysql_free_result (result);
2652  result = NULL;
2653  return &s->p_getpwuid;
2654#endif
2655}
2656
2657void
2658_mysql_drv_query_error (const char *error, const char *query)
2659{
2660  FILE *file;
2661  char fn[MAX_FILENAME_LENGTH];
2662  char buf[128];
2663
2664  LOG (LOG_WARNING, "query error: %s: see sql.errors for more details",
2665       error);
2666
2667  snprintf (fn, sizeof (fn), "%s/sql.errors", LOGDIR);
2668
2669  file = fopen (fn, "a");
2670
2671  if (file == NULL)
2672  {
2673    LOG(LOG_ERR, ERR_IO_FILE_WRITE, fn, strerror (errno));
2674    return;
2675  }
2676
2677  fprintf (file, "[%s] %d: %s: %s\n", format_date_r(buf), (int) getpid (), error, query);
2678  fclose (file);
2679  return;
2680}
2681
2682#ifdef VIRTUAL_USERS
2683struct passwd *
2684_mysql_drv_setpwnam (DSPAM_CTX * CTX, const char *name)
2685{
2686  if (name == NULL)
2687    return NULL;
2688
2689  char query[512];
2690  int query_rc = 0;
2691  int query_errno = 0;
2692  char *virtual_table, *virtual_uid, *virtual_username;
2693  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
2694  char *sql_username;
2695
2696  if ((virtual_table
2697    = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualTable"))==NULL)
2698  { virtual_table = "dspam_virtual_uids"; }
2699
2700  if ((virtual_uid
2701    = _ds_read_attribute(CTX->config->attributes, "MySQLVirtualUIDField"))==NULL)
2702  { virtual_uid = "uid"; }
2703
2704  if ((virtual_username = _ds_read_attribute(CTX->config->attributes,
2705    "MySQLVirtualUsernameField")) ==NULL)
2706  { virtual_username = "username"; }
2707
2708  /* TODO
2709  virtual_table = _mysql_drv_get_virtual_table(CTX);
2710  virtual_uid = _mysql_drv_get_virtual_uid_field(CTX);
2711  virtual_username = _mysql_drv_get_virtual_username_field(CTX);
2712  */
2713
2714#ifdef EXT_LOOKUP
2715  LOGDEBUG("_mysql_drv_setpwnam: verified_user is %d", verified_user);
2716  if (verified_user == 0) {
2717    LOGDEBUG("_mysql_drv_setpwnam: External lookup verification of %s failed: not adding user", name);
2718    return NULL;
2719  }
2720#endif
2721
2722  sql_username = malloc (strlen(name) * 2 + 1);
2723  if (sql_username == NULL)
2724  {
2725    return NULL;
2726  }
2727
2728  mysql_real_escape_string (s->dbt->dbh_write, sql_username, name,
2729                            strlen(name));
2730
2731  snprintf (query, sizeof (query),
2732            "INSERT INTO %s (%s,%s) VALUES (NULL,'%s')",
2733            virtual_table, virtual_uid, virtual_username, sql_username);
2734
2735  free (sql_username);
2736  sql_username = NULL;
2737
2738  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
2739  if (query_rc) {
2740    query_errno = mysql_errno (s->dbt->dbh_write);
2741    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
2742      /* Locking issue. Wait 1 second and then retry the transaction again */
2743      sleep(1);
2744      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
2745    }
2746  }
2747  /* we need to fail, to prevent a potential loop - even if it was inserted
2748   * by another process */
2749  if (query_rc) {
2750    _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query);
2751    LOGDEBUG ("_mysql_drv_setpwnam: unable to run query: %s", query);
2752    return NULL;
2753  }
2754
2755  return _mysql_drv_getpwnam (CTX, name);
2756
2757}
2758#endif
2759
2760int
2761_ds_del_spamrecord (DSPAM_CTX * CTX, unsigned long long token)
2762{
2763  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
2764  struct passwd *p;
2765  char *name;
2766  char query[256];
2767  int query_rc = 0;
2768  int query_errno = 0;
2769
2770  if (s->dbt == NULL)
2771  {
2772    LOGDEBUG ("_ds_del_spamrecord: invalid database handle (NULL)");
2773    return EINVAL;
2774  }
2775
2776  if (!CTX->group || CTX->flags & DSF_MERGED) {
2777    p = _mysql_drv_getpwnam (CTX, CTX->username);
2778    name = CTX->username;
2779  } else {
2780    p = _mysql_drv_getpwnam (CTX, CTX->group);
2781    name = CTX->group;
2782  }
2783
2784  if (p == NULL)
2785  {
2786    LOGDEBUG ("_ds_del_spamrecord: unable to _mysql_drv_getpwnam(%s)",
2787              name);
2788    return EINVAL;
2789  }
2790  snprintf (query, sizeof (query),
2791          "DELETE FROM dspam_token_data WHERE uid=%d AND token=\"%llu\"",
2792          (int) p->pw_uid, token);
2793
2794  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
2795  if (query_rc) {
2796    query_errno = mysql_errno (s->dbt->dbh_write);
2797    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
2798      /* Locking issue. Wait 1 second and then retry the transaction again */
2799      sleep(1);
2800      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
2801    }
2802  }
2803  if (query_rc) {
2804    _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query);
2805    LOGDEBUG ("_ds_del_spamrecord: unable to run query: %s", query);
2806    return EFAILURE;
2807  }
2808
2809  return 0;
2810}
2811
2812int _ds_delall_spamrecords (DSPAM_CTX * CTX, ds_diction_t diction)
2813{
2814  struct _mysql_drv_storage *s = (struct _mysql_drv_storage *) CTX->storage;
2815  ds_term_t ds_term;
2816  ds_cursor_t ds_c;
2817  buffer *query;
2818  char scratch[1024];
2819  char queryhead[1024];
2820  int query_rc = 0;
2821  int query_errno = 0;
2822  struct passwd *p;
2823  char *name;
2824
2825  if (diction->items < 1)
2826    return 0;
2827
2828  if (s->dbt->dbh_write == NULL)
2829  {
2830    LOGDEBUG ("_ds_delall_spamrecords: invalid database handle (NULL)");
2831    return EINVAL;
2832  }
2833
2834  if (!CTX->group || CTX->flags & DSF_MERGED) {
2835    p = _mysql_drv_getpwnam (CTX, CTX->username);
2836    name = CTX->username;
2837  } else {
2838    p = _mysql_drv_getpwnam (CTX, CTX->group);
2839    name = CTX->group;
2840  }
2841
2842  if (p == NULL)
2843  {
2844    LOGDEBUG ("_ds_delall_spamrecords: unable to _mysql_drv_getpwnam(%s)",
2845              name);
2846    return EINVAL;
2847  }
2848
2849  query = buffer_create (NULL);
2850  if (query == NULL)
2851  {
2852    LOG (LOG_CRIT, ERR_MEM_ALLOC);
2853    return EUNKNOWN;
2854  }
2855
2856  snprintf (queryhead, sizeof(queryhead),
2857            "DELETE FROM dspam_token_data"
2858            " WHERE uid=%d AND token IN (",
2859            (int) p->pw_uid);
2860
2861  /* Delete the spam records but split the query when the query size + 1024
2862   * reaches max_allowed_packet
2863   */
2864  ds_c = ds_diction_cursor(diction);
2865  ds_term = ds_diction_next(ds_c);
2866  while (ds_term) {
2867    scratch[0] = 0;
2868    buffer_copy(query, queryhead);
2869    while (ds_term) {
2870      snprintf (scratch, sizeof (scratch), "'%llu'", ds_term->key);
2871      buffer_cat (query, scratch);
2872      ds_term = ds_diction_next(ds_c);
2873      if((unsigned long)(query->used + 1024) > _mysql_driver_get_max_packet(s->dbt->dbh_write) || !ds_term) {
2874        LOGDEBUG("_ds_delall_spamrecords: Splitting query at %lu characters", query->used);
2875        break;
2876      }
2877      buffer_cat (query, ",");
2878    }
2879    buffer_cat (query, ")");
2880
2881    query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query->data);
2882    if (query_rc) {
2883      query_errno = mysql_errno (s->dbt->dbh_write);
2884      if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
2885        /* Locking issue. Wait 1 second and then retry the transaction again */
2886        sleep(1);
2887        query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query->data);
2888      }
2889    }
2890    if (query_rc) {
2891      _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query->data);
2892      LOGDEBUG ("_ds_delall_spamrecords: unable to run query: %s", query->data);
2893      buffer_destroy(query);
2894      query = NULL;
2895      ds_diction_close(ds_c);
2896      return EFAILURE;
2897    }
2898  }
2899
2900  ds_diction_close(ds_c);
2901  buffer_destroy (query);
2902  query = NULL;
2903  return 0;
2904}
2905
2906#ifdef PREFERENCES_EXTENSION
2907DSPAM_CTX *_mysql_drv_init_tools(
2908 const char *home,
2909 config_t config,
2910 void *dbt,
2911 int mode)
2912{
2913  DSPAM_CTX *CTX;
2914  struct _mysql_drv_storage *s;
2915  int dbh_attached = (dbt) ? 1 : 0;
2916
2917  CTX = dspam_create (NULL, NULL, home, mode, 0);
2918
2919  if (CTX == NULL)
2920    return NULL;
2921
2922  _mysql_drv_set_attributes(CTX, config);
2923
2924  if (!dbt)
2925    dbt = _ds_connect(CTX);
2926
2927  if (!dbt)
2928    goto BAIL;
2929
2930  if (dspam_attach(CTX, dbt))
2931    goto BAIL;
2932
2933  s = (struct _mysql_drv_storage *) CTX->storage;
2934  s->dbh_attached = dbh_attached;
2935
2936  return CTX;
2937
2938BAIL:
2939  LOGDEBUG ("_mysql_drv_init_tools: Bailing and returning NULL!");
2940  dspam_destroy(CTX);
2941  return NULL;
2942}
2943
2944agent_pref_t _ds_pref_load(
2945  config_t config,
2946  const char *username,
2947  const char *home,
2948  void *dbt)
2949{
2950  struct _mysql_drv_storage *s;
2951  struct passwd *p;
2952  char query[512];
2953  int query_rc = 0;
2954  int query_errno = 0;
2955  MYSQL_RES *result;
2956  MYSQL_ROW row;
2957  DSPAM_CTX *CTX;
2958  agent_pref_t PTX;
2959  agent_attrib_t pref;
2960  int uid, i = 0;
2961  result = NULL;
2962
2963  CTX = _mysql_drv_init_tools(home, config, dbt, DSM_TOOLS);
2964  if (CTX == NULL)
2965  {
2966    LOG (LOG_WARNING, "_ds_pref_load: unable to initialize tools context");
2967    return NULL;
2968  }
2969
2970  s = (struct _mysql_drv_storage *) CTX->storage;
2971
2972  if (username != NULL) {
2973    p = _mysql_drv_getpwnam (CTX, username);
2974
2975    if (p == NULL)
2976    {
2977      LOGDEBUG ("_ds_pref_load: unable to _mysql_drv_getpwnam(%s)",
2978              username);
2979      dspam_destroy(CTX);
2980      return NULL;
2981    } else {
2982      uid = (int) p->pw_uid;
2983    }
2984  } else {
2985    uid = 0; /* Default Preferences */
2986  }
2987
2988  LOGDEBUG("Loading preferences for uid %d", uid);
2989
2990  snprintf(query, sizeof(query), "SELECT preference,value"
2991                              " FROM dspam_preferences WHERE uid=%d", (int) uid);
2992
2993  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
2994  if (query_rc) {
2995    query_errno = mysql_errno (s->dbt->dbh_read);
2996    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
2997      /* Locking issue. Wait 1 second and then retry the transaction again */
2998      sleep(1);
2999      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_read, query);
3000    }
3001  }
3002  if (query_rc) {
3003    _mysql_drv_query_error (mysql_error (s->dbt->dbh_read), query);
3004    LOGDEBUG ("_ds_pref_load: unable to run query: %s", query);
3005    dspam_destroy(CTX);
3006    return NULL;
3007  }
3008
3009  result = mysql_store_result (s->dbt->dbh_read);
3010  if (result == NULL) {
3011    dspam_destroy(CTX);
3012    return NULL;
3013  }
3014
3015  PTX = malloc(sizeof(agent_attrib_t )*(mysql_num_rows(result)+1));
3016  if (PTX == NULL) {
3017    LOG(LOG_CRIT, ERR_MEM_ALLOC);
3018    dspam_destroy(CTX);
3019    mysql_free_result(result);
3020    result = NULL;
3021    return NULL;
3022  }
3023
3024  PTX[0] = NULL;
3025
3026  row = mysql_fetch_row (result);
3027  if (row == NULL) {
3028    dspam_destroy(CTX);
3029    mysql_free_result(result);
3030    result = NULL;
3031    _ds_pref_free(PTX);
3032    free(PTX);
3033    return NULL;
3034  }
3035
3036  while(row != NULL) {
3037    char *p = row[0];
3038    char *q = row[1];
3039
3040    pref = malloc(sizeof(struct _ds_agent_attribute));
3041    if (pref == NULL) {
3042      LOG(LOG_CRIT, ERR_MEM_ALLOC);
3043      mysql_free_result(result);
3044      result = NULL;
3045      dspam_destroy(CTX);
3046      return PTX;
3047    }
3048
3049    pref->attribute = strdup(p);
3050    pref->value = strdup(q);
3051    PTX[i] = pref;
3052    PTX[i+1] = NULL;
3053    i++;
3054
3055    row = mysql_fetch_row (result);
3056  }
3057
3058  mysql_free_result(result);
3059  result = NULL;
3060  dspam_destroy(CTX);
3061  return PTX;
3062}
3063
3064int _ds_pref_set (
3065 config_t config,
3066 const char *username,
3067 const char *home,
3068 const char *preference,
3069 const char *value,
3070 void *dbt)
3071{
3072  struct _mysql_drv_storage *s;
3073  struct passwd *p;
3074  char query[512];
3075  int query_rc = 0;
3076  int query_errno = 0;
3077  DSPAM_CTX *CTX;
3078  int uid;
3079  char *m1, *m2;
3080
3081  CTX = _mysql_drv_init_tools(home, config, dbt, DSM_PROCESS);
3082  if (CTX == NULL) {
3083    LOG (LOG_WARNING, "_ds_pref_set: unable to initialize tools context");
3084    return EFAILURE;
3085  }
3086
3087  s = (struct _mysql_drv_storage *) CTX->storage;
3088
3089  if (username != NULL) {
3090    p = _mysql_drv_getpwnam (CTX, username);
3091
3092    if (p == NULL)
3093    {
3094      LOGDEBUG ("_ds_pref_set: unable to _mysql_drv_getpwnam(%s)",
3095              CTX->username);
3096      dspam_destroy(CTX);
3097      return EFAILURE;
3098    } else {
3099      uid = (int) p->pw_uid;
3100    }
3101  } else {
3102    uid = 0; /* Default Preferences */
3103  }
3104
3105  m1 = calloc(1, strlen(preference)*2+1);
3106  m2 = calloc(1, strlen(value)*2+1);
3107  if (m1 == NULL || m2 == NULL)
3108  {
3109    LOG (LOG_CRIT, ERR_MEM_ALLOC);
3110    dspam_destroy(CTX);
3111    free(m1);
3112    m1 = NULL;
3113    free(m2);
3114    m2 = NULL;
3115    return EFAILURE;
3116  }
3117
3118  mysql_real_escape_string (s->dbt->dbh_write, m1, preference, strlen(preference));
3119  mysql_real_escape_string (s->dbt->dbh_write, m2, value, strlen(value));
3120
3121  snprintf(query, sizeof(query), "DELETE FROM dspam_preferences"
3122    " WHERE uid=%d AND preference='%s'", (int) uid, m1);
3123
3124  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
3125  if (query_rc) {
3126    query_errno = mysql_errno (s->dbt->dbh_write);
3127    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
3128      /* Locking issue. Wait 1 second and then retry the transaction again */
3129      sleep(1);
3130      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
3131    }
3132  }
3133  if (query_rc) {
3134    _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query);
3135    LOGDEBUG ("_ds_pref_set: unable to run query: %s", query);
3136    goto FAIL;
3137  }
3138
3139  snprintf(query, sizeof(query), "INSERT INTO dspam_preferences"
3140    " (uid,preference,value) VALUES (%d,'%s','%s')", (int) uid, m1, m2);
3141
3142  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
3143  if (query_rc) {
3144    query_errno = mysql_errno (s->dbt->dbh_write);
3145    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
3146      /* Locking issue. Wait 1 second and then retry the transaction again */
3147      sleep(1);
3148      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
3149    }
3150  }
3151  if (query_rc) {
3152    _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query);
3153    LOGDEBUG ("_ds_pref_set: unable to run query: %s", query);
3154    goto FAIL;
3155  }
3156
3157  dspam_destroy(CTX);
3158  free(m1);
3159  m1 = NULL;
3160  free(m2);
3161  m2 = NULL;
3162  return 0;
3163
3164FAIL:
3165  LOGDEBUG("_ds_pref_set: failed");
3166  free(m1);
3167  m1 = NULL;
3168  free(m2);
3169  m2 = NULL;
3170  dspam_destroy(CTX);
3171  return EFAILURE;
3172}
3173
3174int _ds_pref_del (
3175 config_t config,
3176 const char *username,
3177 const char *home,
3178 const char *preference,
3179 void *dbt)
3180{
3181  struct _mysql_drv_storage *s;
3182  struct passwd *p;
3183  char query[512];
3184  int query_rc = 0;
3185  int query_errno = 0;
3186  DSPAM_CTX *CTX;
3187  int uid;
3188  char *m1;
3189
3190  CTX = _mysql_drv_init_tools(home, config, dbt, DSM_TOOLS);
3191  if (CTX == NULL) {
3192    LOG (LOG_WARNING, "_ds_pref_del: unable to initialize tools context");
3193    return EFAILURE;
3194  }
3195
3196  s = (struct _mysql_drv_storage *) CTX->storage;
3197
3198  if (username != NULL) {
3199    p = _mysql_drv_getpwnam (CTX, username);
3200
3201    if (p == NULL)
3202    {
3203      LOGDEBUG ("_ds_pref_del: unable to _mysql_drv_getpwnam(%s)",
3204              username);
3205      dspam_destroy(CTX);
3206      return EFAILURE;
3207    } else {
3208      uid = (int) p->pw_uid;
3209    }
3210  } else {
3211    uid = 0; /* Default Preferences */
3212  }
3213
3214  m1 = calloc(1, strlen(preference)*2+1);
3215  if (m1 == NULL)
3216  {
3217    LOG (LOG_CRIT, ERR_MEM_ALLOC);
3218    dspam_destroy(CTX);
3219    free(m1);
3220    m1 = NULL;
3221    return EFAILURE;
3222  }
3223
3224  mysql_real_escape_string (s->dbt->dbh_write, m1, preference, strlen(preference));
3225
3226  snprintf(query, sizeof(query), "DELETE FROM dspam_preferences"
3227    " WHERE uid=%d AND preference='%s'", (int) uid, m1);
3228
3229  query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
3230  if (query_rc) {
3231    query_errno = mysql_errno (s->dbt->dbh_write);
3232    if (query_errno == ER_LOCK_DEADLOCK || query_errno == ER_LOCK_WAIT_TIMEOUT || query_errno == ER_LOCK_OR_ACTIVE_TRANSACTION) {
3233      /* Locking issue. Wait 1 second and then retry the transaction again */
3234      sleep(1);
3235      query_rc = MYSQL_RUN_QUERY (s->dbt->dbh_write, query);
3236    }
3237  }
3238  if (query_rc) {
3239    _mysql_drv_query_error (mysql_error (s->dbt->dbh_write), query);
3240    LOGDEBUG ("_ds_pref_del: unable to run query: %s", query);
3241    goto FAIL;
3242  }
3243
3244  dspam_destroy(CTX);
3245  free(m1);
3246  m1 = NULL;
3247  return 0;
3248
3249FAIL:
3250  LOGDEBUG("_ds_pref_del: failed");
3251  free(m1);
3252  m1 = NULL;
3253  dspam_destroy(CTX);
3254  return EFAILURE;
3255}
3256
3257int _mysql_drv_set_attributes(DSPAM_CTX *CTX, config_t config) {
3258  int i, ret = 0;
3259  attribute_t t;
3260  char *profile;
3261
3262  profile = _ds_read_attribute(config, "DefaultProfile");
3263
3264  for(i=0;config[i];i++) {
3265    t = config[i];
3266
3267    while(t) {
3268
3269      if (!strncasecmp(t->key, "MySQL", 5))
3270      {
3271        if (profile == NULL || profile[0] == 0)
3272        {
3273          ret += dspam_addattribute(CTX, t->key, t->value);
3274        }
3275        else if (strchr(t->key, '.'))
3276        {
3277          if (!strcasecmp((strchr(t->key, '.')+1), profile)) {
3278            char *x = strdup(t->key);
3279            char *y = strchr(x, '.');
3280            y[0] = 0;
3281
3282            ret += dspam_addattribute(CTX, x, t->value);
3283            free(x);
3284            x = NULL;
3285          }
3286        }
3287      }
3288      t = t->next;
3289    }
3290  }
3291
3292  return 0;
3293}
3294
3295#else
3296/* Preference Stubs for Flat-File */
3297
3298agent_pref_t _ds_pref_load(config_t config, const char *user,
3299  const char *home, void *dbh)
3300{
3301  return _ds_ff_pref_load(config, user, home, dbh);
3302}
3303
3304int _ds_pref_set(config_t config, const char *user, const char *home,
3305  const char *attrib, const char *value, void *dbh)
3306{
3307  return _ds_ff_pref_set(config, user, home, attrib, value, dbh);
3308}
3309
3310int _ds_pref_del(config_t config, const char *user, const char *home,
3311  const char *attrib, void *dbh)
3312{
3313  return _ds_ff_pref_del(config, user, home, attrib, dbh);
3314}
3315#endif
3316
3317void *_ds_connect (DSPAM_CTX *CTX)
3318{
3319  _mysql_drv_dbh_t dbt = calloc(1, sizeof(struct _mysql_drv_dbh));
3320  dbt->dbh_read = _mysql_drv_connect(CTX, "MySQL");
3321  if (!dbt->dbh_read) {
3322    free(dbt);
3323    return NULL;
3324  }
3325  if (_ds_read_attribute(CTX->config->attributes, "MySQLWriteServer"))
3326    dbt->dbh_write = _mysql_drv_connect(CTX, "MySQLWrite");
3327  else
3328    dbt->dbh_write = dbt->dbh_read;
3329  return (void *) dbt;
3330}
3331
3332MYSQL *_mysql_drv_connect (DSPAM_CTX *CTX, const char *prefix)
3333{
3334  MYSQL *dbh;
3335  FILE *file;
3336  char filename[MAX_FILENAME_LENGTH];
3337  char buffer[128];
3338  char hostname[128] = { 0 };
3339  char user[64] = { 0 };
3340  char password[64] = { 0 };
3341  char db[64] = { 0 };
3342  int port = 3306, i = 0, real_connect_flag = 0;
3343  char *p;
3344  char attrib[128];
3345
3346  if (!prefix)
3347    prefix = "MySQL";
3348
3349  /* Read storage attributes */
3350  snprintf(attrib, sizeof(attrib), "%sServer", prefix);
3351  if ((p = _ds_read_attribute(CTX->config->attributes, attrib))) {
3352
3353    strlcpy(hostname, p, sizeof(hostname));
3354    if (strlen(p) >= sizeof(hostname))
3355    {
3356      LOG(LOG_WARNING, "Truncating MySQLServer to %d characters.",
3357          sizeof(hostname)-1);
3358    }
3359
3360    snprintf(attrib, sizeof(attrib), "%sPort", prefix);
3361    if (_ds_read_attribute(CTX->config->attributes, attrib)) {
3362      port = atoi(_ds_read_attribute(CTX->config->attributes, attrib));
3363      if (port == INT_MAX && errno == ERANGE) {
3364        LOGDEBUG("_mysql_drv_connect: failed converting %s to port", _ds_read_attribute(CTX->config->attributes, attrib));
3365        goto FAILURE;
3366      }
3367    }
3368    else
3369      port = 0;
3370
3371    snprintf(attrib, sizeof(attrib), "%sUser", prefix);
3372    if ((p = _ds_read_attribute(CTX->config->attributes, attrib)))
3373    {
3374      strlcpy(user, p, sizeof(user));
3375      if (strlen(p) >= sizeof(user))
3376      {
3377        LOG(LOG_WARNING, "Truncating MySQLUser to %d characters.",
3378            sizeof(user)-1);
3379      }
3380    }
3381    snprintf(attrib, sizeof(attrib), "%sPass", prefix);
3382    if ((p = _ds_read_attribute(CTX->config->attributes, attrib)))
3383    {
3384      strlcpy(password, p, sizeof(password));
3385      if (strlen(p) >= sizeof(password))
3386      {
3387        LOG(LOG_WARNING, "Truncating MySQLPass to %d characters.",
3388            sizeof(password)-1);
3389      }
3390    }
3391    snprintf(attrib, sizeof(attrib), "%sDb", prefix);
3392    if ((p = _ds_read_attribute(CTX->config->attributes, attrib)))
3393    {
3394      strlcpy(db, p, sizeof(db));
3395      if (strlen(p) >= sizeof(db))
3396      {
3397        LOG(LOG_WARNING, "Truncating MySQLDb to %d characters.",
3398            sizeof(db)-1);
3399      }
3400    }
3401
3402    snprintf(attrib, sizeof(attrib), "%sCompress", prefix);
3403    if (_ds_match_attribute(CTX->config->attributes, attrib, "true"))
3404      real_connect_flag = CLIENT_COMPRESS;
3405
3406  } else {
3407    if (!CTX->home) {
3408      LOG(LOG_ERR, ERR_AGENT_DSPAM_HOME);
3409      goto FAILURE;
3410    }
3411    snprintf (filename, MAX_FILENAME_LENGTH, "%s/mysql.data", CTX->home);
3412    file = fopen (filename, "r");
3413    if (file == NULL)
3414    {
3415      LOG (LOG_WARNING, "_mysql_drv_connect: unable to locate mysql configuration");
3416      goto FAILURE;
3417    }
3418
3419    db[0] = 0;
3420
3421    while (fgets (buffer, sizeof (buffer), file) != NULL)
3422    {
3423      chomp (buffer);
3424      if (!i)
3425        strlcpy (hostname, buffer, sizeof (hostname));
3426      else if (i == 1) {
3427        port = atoi (buffer);
3428        if (port == INT_MAX && errno == ERANGE) {
3429          fclose (file);
3430          LOGDEBUG("_mysql_drv_connect: failed converting %s to port", buffer);
3431          goto FAILURE;
3432        }
3433      }
3434      else if (i == 2)
3435        strlcpy (user, buffer, sizeof (user));
3436      else if (i == 3)
3437        strlcpy (password, buffer, sizeof (password));
3438      else if (i == 4)
3439        strlcpy (db, buffer, sizeof (db));
3440      i++;
3441    }
3442    fclose (file);
3443  }
3444
3445  if (db[0] == 0)
3446  {
3447    LOG (LOG_WARNING, "file %s: incomplete mysql connect data", filename);
3448    goto FAILURE;
3449  }
3450
3451  dbh = mysql_init(NULL);
3452  if (dbh == NULL)
3453  {
3454    LOGDEBUG
3455      ("_mysql_drv_connect: mysql_init: unable to initialize handle to database");
3456    goto FAILURE;
3457  }
3458
3459#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50013
3460  /* enable automatic reconnect for MySQL >= 5.0.13 */
3461  snprintf(attrib, sizeof(attrib), "%sReconnect", prefix);
3462  if (_ds_match_attribute(CTX->config->attributes, attrib, "true"))
3463  {
3464      my_bool reconnect = 1;
3465      mysql_options(dbh, MYSQL_OPT_RECONNECT, &reconnect);
3466  }
3467#endif
3468
3469  if (hostname[0] == '/')
3470  {
3471    if (!mysql_real_connect (dbh, NULL, user, password, db, 0, hostname,
3472                        real_connect_flag))
3473    {
3474      LOG (LOG_WARNING, "%s", mysql_error (dbh));
3475      mysql_close(dbh);
3476      goto FAILURE;
3477    }
3478  }
3479  else
3480  {
3481    if (!mysql_real_connect (dbh, hostname, user, password, db, port, NULL,
3482                        real_connect_flag))
3483    {
3484      LOG (LOG_WARNING, "%s", mysql_error (dbh));
3485      mysql_close(dbh);
3486      goto FAILURE;
3487    }
3488  }
3489
3490  return dbh;
3491
3492FAILURE:
3493  LOGDEBUG("_mysql_drv_connect: failed");
3494  return NULL;
3495}
3496
3497MYSQL * _mysql_drv_sig_write_handle(
3498    DSPAM_CTX *CTX,
3499    struct _mysql_drv_storage *s)
3500{
3501  if (_ds_match_attribute(CTX->config->attributes, "MySQLReadSignaturesFromWriteDb", "on"))
3502    return s->dbt->dbh_write;
3503  else
3504    return s->dbt->dbh_read;
3505}
Note: See TracBrowser for help on using the repository browser.