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