Back to index

opendkim  2.6.2
opendkim-importstats.c
Go to the documentation of this file.
00001 /*
00002 **  Copyright (c) 2010-2012, The OpenDKIM Project.  All rights reserved.
00003 **
00004 **  $Id: opendkim-importstats.c,v 1.11 2010/10/25 17:16:00 cm-msk Exp $
00005 */
00006 
00007 #ifndef lint
00008 static char opendkim_importstats_c_id[] = "$Id: opendkim-importstats.c,v 1.11 2010/10/25 17:16:00 cm-msk Exp $";
00009 #endif /* ! lint */
00010 
00011 /* system includes */
00012 #include <sys/param.h>
00013 #include <sys/types.h>
00014 #include <sysexits.h>
00015 #include <assert.h>
00016 #include <unistd.h>
00017 #include <string.h>
00018 #include <time.h>
00019 #include <stdio.h>
00020 #include <stdlib.h>
00021 #include <errno.h>
00022 
00023 /* OpenDKIM includes */
00024 #include "build-config.h"
00025 #include "stats.h"
00026 
00027 /* libopendkim includes */
00028 #include <dkim-strl.h>
00029 #include <dkim.h>
00030 
00031 /* libodbx includes */
00032 #ifdef USE_ODBX
00033 # include <odbx.h>
00034 #else /* USE_ODBX */
00035 # error OpenDBX is required for opendkim-importstats
00036 #endif /* USE_ODBX */
00037 
00038 /* macros, definitions */
00039 #define       CMDLINEOPTS   "d:EFh:mP:p:rSs:u:vx"
00040 
00041 #define       DEFDBHOST     "localhost"
00042 #define       DEFDBNAME     "opendkim"
00043 #define       DEFDBSCHEME   SQL_BACKEND
00044 #define       DEFDBUSER     "opendkim"
00045 
00046 #define       MAXLINE              2048
00047 #define       MAXREPORTER   256
00048 
00049 /* data structures */
00050 struct table
00051 {
00052        char *        tbl_left;
00053        char *        tbl_right;
00054 };
00055 
00056 /* globals */
00057 char *progname;
00058 char reporter[MAXREPORTER + 1];
00059 int verbose;
00060 
00061 struct table last_insert_id[] =
00062 {
00063        { "mysql",    "LAST_INSERT_ID()" },
00064        { "sqlite3",  "LAST_INSERT_ROWID()" },
00065        { "pgsql",    "LASTVAL()" },
00066        { NULL,              NULL }
00067 };
00068 
00069 /*
00070 **  SANITIZE -- sanitize a string
00071 **
00072 **  Parameters:
00073 **     db -- DB handle
00074 **     in -- input string
00075 **     out -- output buffer
00076 **     len -- bytes available at "out"
00077 **
00078 **  Return value:
00079 **     0 == string was safe
00080 **     1 == string was not safe
00081 */
00082 
00083 int
00084 sanitize(odbx_t *db, char *in, char *out, size_t len)
00085 {
00086        size_t outlen;
00087 
00088        assert(db != NULL);
00089        assert(in != NULL);
00090        assert(out != NULL);
00091 
00092        memset(out, '\0', len);
00093 
00094        outlen = len;
00095 
00096        (void) odbx_escape(db, in, strlen(in), out, &outlen);
00097 
00098        return (strncmp(in, out, outlen) != 0);
00099 }
00100 
00101 /*
00102 **  DUMPFIELDS -- dump array of fields for debugging
00103 **
00104 **  Parameters:
00105 **     out -- output stream
00106 **     fields -- array of fields
00107 **     n -- length of fields array
00108 **
00109 **  Return value:
00110 **     None.
00111 */
00112 
00113 void
00114 dumpfields(FILE *out, char **fields, int n)
00115 {
00116        int c;
00117 
00118        for (c = 0; c < n; c++)
00119               fprintf(out, "\t%d = '%s'\n", c, fields[c]);
00120 }
00121 
00122 /*
00123 **  FINDINLIST -- see if a particular string appears in another list
00124 **
00125 **  Parameters:
00126 **     str -- string to find
00127 **     list -- colon-separated list of strings to search
00128 **
00129 **  Return value:
00130 **     1 -- "str" found in "list"
00131 **     0 -- "str" not found in "list"
00132 */
00133 
00134 int
00135 findinlist(char *str, char *list)
00136 {
00137        int len;
00138        char *p;
00139        char *q;
00140 
00141        assert(str != NULL);
00142        assert(list != NULL);
00143 
00144        len = strlen(str);
00145 
00146        q = list;
00147 
00148        for (p = list; ; p++)
00149        {
00150               if (*p == ':' || *p == '\0')
00151               {
00152                      if (p - q == len && strncasecmp(str, q, len) == 0)
00153                             return 1;
00154 
00155                      q = p + 1;
00156               }
00157 
00158               if (*p == '\0')
00159                      break;
00160        }
00161 
00162        return 0;
00163 }
00164 
00165 /*
00166 **  SQL_MKTIME -- convert a UNIX time_t (as a string) to an SQL time string
00167 **
00168 **  Parameters:
00169 **     in -- input time
00170 **     out -- output buffer
00171 **     outlen -- bytes available at "out"
00172 **
00173 **  Return value:
00174 **     0 -- error
00175 **     >0 -- bytes of "out" used
00176 */
00177 
00178 int
00179 sql_mktime(const char *in, char *out, size_t outlen)
00180 {
00181        time_t convert;
00182        struct tm *local;
00183        char *p;
00184 
00185        assert(in != NULL);
00186        assert(out != NULL);
00187 
00188        errno = 0;
00189        convert = strtoul(in, &p, 10);
00190        if (errno != 0 || *p != '\0')
00191               return 0;
00192 
00193        local = localtime(&convert);
00194 
00195        return strftime(out, outlen, "%Y-%m-%d %H:%M:%S", local);
00196 }
00197 
00198 /*
00199 **  SQL_GET_INT -- retrieve a single integer from an SQL query
00200 **
00201 **  Parameters:
00202 **     db -- DB handle
00203 **     sql -- SQL query to perform
00204 **
00205 **  Return value:
00206 **     -1 -- error
00207 **     0 -- no record matched
00208 **     1 -- extracted value
00209 */
00210 
00211 int
00212 sql_get_int(odbx_t *db, char *sql)
00213 {
00214        int out = 0;
00215        int err;
00216        odbx_result_t *result = NULL;
00217 
00218        assert(db != NULL);
00219        assert(sql != NULL);
00220 
00221        if (verbose > 0)
00222               fprintf(stderr, "> %s\n", sql);
00223 
00224        err = odbx_query(db, sql, strlen(sql));
00225        if (err < 0)
00226        {
00227               fprintf(stderr, "%s: odbx_query(): %s\n",
00228                       progname, odbx_error(db, err));
00229               return -1;
00230        }
00231 
00232        err = odbx_result(db, &result, NULL, 0);
00233        if (err < 0)
00234        {
00235               fprintf(stderr, "%s: odbx_result(): %s\n",
00236                       progname, odbx_error(db, err));
00237               return -1;
00238        }
00239 
00240        for (;;)
00241        {
00242               err = odbx_row_fetch(result);
00243               if (err == ODBX_ROW_DONE)
00244               {
00245                      break;
00246               }
00247               else if (err < 0)
00248               {
00249                      fprintf(stderr, "%s: odbx_row_fetch(): %s\n",
00250                              progname, odbx_error(db, err));
00251                      return -1;
00252               }
00253 
00254               if (out == 0)
00255               {
00256                      char *p;
00257                      const char *op;
00258 
00259                      op = odbx_field_value(result, 0);
00260                      if (op == NULL)
00261                      {
00262                             fprintf(stderr, "%s: unexpected NULL value\n",
00263                                     progname);
00264                             odbx_result_finish(result);
00265                             return -1;
00266                      }
00267 
00268                      out = strtol(op, &p, 10);
00269                      if (*p != '\0')
00270                      {
00271                             fprintf(stderr, "%s: malformed integer\n",
00272                                     progname);
00273                             odbx_result_finish(result);
00274                             return -1;
00275                      }
00276               }
00277        }
00278 
00279        odbx_result_finish(result);
00280 
00281        return out;
00282 }
00283 
00284 /*
00285 **  SQL_DO -- perform an SQL operation with no result
00286 **
00287 **  Parameters:
00288 **     db -- DB handle
00289 **     sql -- SQL action to perform
00290 **
00291 **  Return value:
00292 **     -1 -- error
00293 **     0 -- success
00294 */
00295 
00296 int
00297 sql_do(odbx_t *db, char *sql)
00298 {
00299        int err;
00300        odbx_result_t *result = NULL;
00301 
00302        assert(db != NULL);
00303        assert(sql != NULL);
00304 
00305        if (verbose > 0)
00306               fprintf(stderr, "> %s\n", sql);
00307 
00308        err = odbx_query(db, sql, strlen(sql));
00309        if (err < 0)
00310        {
00311               fprintf(stderr, "%s: odbx_query(): %s\n",
00312                       progname, odbx_error(db, err));
00313               return -1;
00314        }
00315 
00316        err = odbx_result(db, &result, NULL, 0);
00317        if (err < 0)
00318        {
00319               fprintf(stderr, "%s: odbx_result(): %s\n",
00320                       progname, odbx_error(db, err));
00321               return -1;
00322        }
00323 
00324        if (err != 2)
00325               return -1;
00326 
00327        odbx_result_finish(result);
00328 
00329        return 0;
00330 }
00331 
00332 /*
00333 **  USAGE -- print usage message and exit
00334 **
00335 **  Parameters:
00336 **     None.
00337 **
00338 **  Return value:
00339 **     EX_USAGE
00340 */
00341 
00342 int
00343 usage(void)
00344 {
00345        fprintf(stderr, "%s: usage: %s [options]\n"
00346                        "\t-d dbname  \tdatabase name (default: \"%s\")\n"
00347                        "\t-E         \tinput errors are fatal\n"
00348                        "\t-F         \tdump parsed fields on errors\n"
00349                        "\t-h dbhost  \tdatabase host/address (default: \"%s\")\n"
00350                        "\t-m         \tinput is in email format\n"
00351                        "\t-P dbport  \tdatabase port\n"
00352                        "\t-p dbpasswd\tdatabase password\n"
00353                        "\t-r         \tdon't add unknown reporters\n"
00354                        "\t-S         \tdon't skip duplicate messages\n"
00355                        "\t-s dbscheme\tdatabase scheme (default: \"%s\")\n"
00356                        "\t-u dbuser  \tdatabase user (default: \"%s\")\n"
00357                        "\t-v         \tincrease verbose output\n"
00358 #ifdef _FFR_STATSEXT
00359                        "\t-x         \timport extension records\n"
00360 #endif /* _FFR_STATSEXT */
00361                ,
00362                progname, progname, DEFDBNAME, DEFDBHOST, DEFDBSCHEME,
00363                DEFDBUSER);
00364 
00365        return EX_USAGE;
00366 }
00367 
00368 /*
00369 **  MAIN -- program mainline
00370 **
00371 **  Parameters:
00372 **     argc, argv -- the usual
00373 **
00374 **  Return value:
00375 **     Exit status.
00376 */
00377 
00378 int
00379 main(int argc, char **argv)
00380 {
00381        int c;
00382        int n;
00383        int extensions = 0;
00384        int nfields = 0;
00385        int line;
00386        int err;
00387        int mail = 0;
00388        int dontskip = 0;
00389        int fatalerrors = 0;
00390        int showfields = 0;
00391        int skipsigs = 0;
00392        int norepadd = 0;
00393        int repid;
00394        int domid;
00395        int addrid;
00396        int msgid;
00397        int sigid;
00398        int hdrid;
00399        int inversion = -1;
00400        char *p;
00401        char *lastrow = NULL;
00402        char *dbhost = DEFDBHOST;
00403        char *dbname = DEFDBNAME;
00404        char *dbscheme = DEFDBSCHEME;
00405        char *dbuser = DEFDBUSER;
00406        char *dbpassword = NULL;
00407        char *dbport = NULL;
00408        char **fields = NULL;
00409        odbx_t *db = NULL;
00410        char buf[MAXLINE + 1];
00411        char timebuf[MAXLINE + 1];
00412        char sql[MAXLINE + 1];
00413        char safesql[MAXLINE * 2 + 1];
00414 
00415        progname = (p = strrchr(argv[0], '/')) == NULL ? argv[0] : p + 1;
00416 
00417        verbose = 0;
00418 
00419        while ((c = getopt(argc, argv, CMDLINEOPTS)) != -1)
00420        {
00421               switch (c)
00422               {
00423                 case 'd':
00424                      dbname = optarg;
00425                      break;
00426 
00427                 case 'E':
00428                      fatalerrors = 1;
00429                      break;
00430 
00431                 case 'F':
00432                      showfields = 1;
00433                      break;
00434 
00435                 case 'h':
00436                      dbhost = optarg;
00437                      break;
00438 
00439                 case 'm':
00440                      mail = 1;
00441                      break;
00442 
00443                 case 'P':
00444                      dbport = optarg;
00445                      break;
00446 
00447                 case 'p':
00448                      dbpassword = optarg;
00449                      break;
00450 
00451                 case 'r':
00452                      norepadd = 1;
00453                      break;
00454 
00455                 case 'S':
00456                      dontskip = 1;
00457                      break;
00458 
00459                 case 's':
00460                      dbscheme = optarg;
00461                      break;
00462 
00463                 case 'u':
00464                      dbuser = optarg;
00465                      break;
00466 
00467                 case 'v':
00468                      verbose++;
00469                      break;
00470 
00471 #ifdef _FFR_STATSEXT
00472                 case 'x':
00473                      extensions = 1;
00474                      break;
00475 #endif /* _FFR_STATSEXT */
00476 
00477                 default:
00478                      return usage();
00479               }
00480        }
00481 
00482        for (c = 0; ; c++)
00483        {
00484               if (strcasecmp(last_insert_id[c].tbl_left, dbscheme) == 0)
00485               {
00486                      lastrow = last_insert_id[c].tbl_right;
00487                      break;
00488               }
00489        }
00490 
00491        if (lastrow == NULL)
00492        {
00493               fprintf(stderr, "%s: scheme \"%s\" not currently supported\n",
00494                       progname, dbscheme);
00495               return EX_SOFTWARE;
00496        }
00497 
00498        /* try to connect to the database */
00499        if (odbx_init(&db, dbscheme, dbhost, dbport) < 0)
00500        {
00501               fprintf(stderr, "%s: odbx_init() failed\n", progname);
00502               return EX_TEMPFAIL;
00503        }
00504 
00505        /* bind with user, password, database information */
00506        err = odbx_bind(db, dbname, dbuser, dbpassword, ODBX_BIND_SIMPLE);
00507        if (err < 0)
00508        {
00509               fprintf(stderr, "%s: odbx_bind(): %s\n", progname,
00510                       odbx_error(db, err));
00511               (void) odbx_finish(db);
00512               return EX_TEMPFAIL;
00513        }
00514 
00515        /* initialize stuff */
00516        memset(buf, '\0', sizeof buf);
00517        memset(reporter, '\0', sizeof reporter);
00518        line = 0;
00519        repid = 0;
00520        msgid = 0;
00521        sigid = 0;
00522 
00523        /* read lines from stdin */
00524        while (fgets(buf, sizeof buf - 1, stdin) != NULL)
00525        {
00526               line++;
00527 
00528               /* eat the newline */
00529               for (p = buf; *p != '\0'; p++)
00530               {
00531                      if (*p == '\n')
00532                      {
00533                             *p = '\0';
00534                             break;
00535                      }
00536               }
00537 
00538               if (mail == 1)
00539               {
00540                      if (strlen(buf) > 0)
00541                             continue;
00542 
00543                      mail = 0;
00544                      continue;
00545               }
00546 
00547               /* first byte identifies the record type */
00548               c = buf[0];
00549 
00550               /* reset fields array */
00551               if (fields != NULL)
00552                      memset(fields, '\0', sizeof(char *) * nfields);
00553 
00554               /* now break out the fields */
00555               n = 0;
00556               for (p = strtok(buf + 1, "\t");
00557                    p != NULL;
00558                    p = strtok(NULL, "\t"))
00559               {
00560                      if (nfields == n)
00561                      {
00562                             int newnf;
00563                             size_t newsz;
00564                             char **new;
00565 
00566                             newnf = MAX(nfields * 2, 8);
00567                             newsz = sizeof(char *) * newnf;
00568 
00569                             if (nfields == 0)
00570                                    new = (char **) malloc(newsz);
00571                             else
00572                                    new = (char **) realloc(fields, newsz);
00573 
00574                             if (new == NULL)
00575                             {
00576                                    fprintf(stderr,
00577                                            "%s: %salloc(): %s\n",
00578                                            progname,
00579                                            fields == NULL ? "m" : "re",
00580                                            strerror(errno));
00581                                    (void) odbx_finish(db);
00582                                    return EX_SOFTWARE;
00583                             }
00584 
00585                             nfields = newnf;
00586                             fields = new;
00587                      }
00588 
00589                      fields[n++] = p;
00590               }
00591 
00592               sigid = 0;
00593               hdrid = 0;
00594 
00595               /* processing section for messages */
00596               if (c == '\0')
00597               {
00598                      continue;
00599               }
00600               else if (c == 'V')
00601               {
00602                      if (n != 1)
00603                      {
00604                             fprintf(stderr,
00605                                     "%s: unexpected version field count (%d) at input line %d\n",
00606                                     progname, n, line);
00607 
00608                             if (showfields == 1)
00609                                    dumpfields(stderr, fields, n);
00610 
00611                             if (fatalerrors == 1)
00612                             {
00613                                    (void) odbx_finish(db);
00614                                    return EX_DATAERR;
00615                             }
00616 
00617                             continue;
00618                      }
00619 
00620                      inversion = atoi(fields[0]);
00621               }
00622               else if (c == 'M')
00623               {
00624                      if (inversion != DKIMS_VERSION)
00625                      {
00626                             fprintf(stderr,
00627                                     "%s: ignoring old format at input line %d\n",
00628                                     progname, line);
00629 
00630                             continue;
00631                      }
00632 
00633                      if (n != DKIMS_MI_MAX + 1)
00634                      {
00635                             fprintf(stderr,
00636                                     "%s: unexpected message field count (%d) at input line %d\n",
00637                                     progname, n, line);
00638 
00639                             if (showfields == 1)
00640                                    dumpfields(stderr, fields, n);
00641 
00642                             if (fatalerrors == 1)
00643                             {
00644                                    (void) odbx_finish(db);
00645                                    return EX_DATAERR;
00646                             }
00647 
00648                             continue;
00649                      }
00650 
00651                      skipsigs = 0;
00652 
00653                      /* get, or create, the reporter ID if needed */
00654                      if (strcasecmp(reporter, fields[1]) != 0)
00655                      {
00656                             (void) sanitize(db, fields[1], safesql,
00657                                             sizeof safesql);
00658 
00659                             snprintf(sql, sizeof sql,
00660                                      "SELECT id FROM reporters WHERE name = '%s'",
00661                                      safesql);
00662 
00663                             repid = sql_get_int(db, sql);
00664                             if (repid == -1)
00665                             {
00666                                    (void) odbx_finish(db);
00667                                    return EX_SOFTWARE;
00668                             }
00669                             else if (repid == 0)
00670                             {
00671                                    if (norepadd == 1)
00672                                    {
00673                                           fprintf(stderr,
00674                                                   "%s: no such reporter '%s' at line %d\n",
00675                                                   progname, fields[1],
00676                                                   line);
00677 
00678                                           skipsigs = 1;
00679 
00680                                           continue;
00681                                    }
00682 
00683                                    snprintf(sql, sizeof sql,
00684                                             "INSERT INTO reporters (name) VALUES ('%s')",
00685                                             safesql);
00686 
00687                                    repid = sql_do(db, sql);
00688                                    if (repid == -1)
00689                                    {
00690                                           (void) odbx_finish(db);
00691                                           return EX_SOFTWARE;
00692                                    }
00693 
00694                                    snprintf(sql, sizeof sql,
00695                                             "SELECT %s", lastrow);
00696 
00697                                    repid = sql_get_int(db, sql);
00698                                    if (repid == -1)
00699                                    {
00700                                           (void) odbx_finish(db);
00701                                           return EX_SOFTWARE;
00702                                    }
00703                                    else if (repid == 0)
00704                                    {
00705                                           fprintf(stderr,
00706                                                   "%s: failed to create reporter record for '%s'\n",
00707                                                   progname,
00708                                                   fields[1]);
00709                                           (void) odbx_finish(db);
00710                                           return EX_SOFTWARE;
00711                                    }
00712                             }
00713 
00714                             strlcpy(reporter, fields[1], sizeof reporter);
00715                      }
00716 
00717                      /* get, or create, the domain ID if needed */
00718                      (void) sanitize(db, fields[2], safesql,
00719                                      sizeof safesql);
00720 
00721                      snprintf(sql, sizeof sql,
00722                               "SELECT id FROM domains WHERE name = '%s'",
00723                               safesql);
00724 
00725                      domid = sql_get_int(db, sql);
00726                      if (domid == -1)
00727                      {
00728                             (void) odbx_finish(db);
00729                             return EX_SOFTWARE;
00730                      }
00731                      else if (domid == 0)
00732                      {
00733                             snprintf(sql, sizeof sql,
00734                                      "INSERT INTO domains (name) VALUES ('%s')",
00735                                      safesql);
00736 
00737                             domid = sql_do(db, sql);
00738                             if (domid == -1)
00739                             {
00740                                    (void) odbx_finish(db);
00741                                    return EX_SOFTWARE;
00742                             }
00743 
00744                             snprintf(sql, sizeof sql,
00745                                      "SELECT %s", lastrow);
00746 
00747                             domid = sql_get_int(db, sql);
00748                             if (domid == -1)
00749                             {
00750                                    (void) odbx_finish(db);
00751                                    return EX_SOFTWARE;
00752                             }
00753                             else if (domid == 0)
00754                             {
00755                                    fprintf(stderr,
00756                                            "%s: failed to create domain record for '%s'\n",
00757                                            progname, fields[2]);
00758                                    (void) odbx_finish(db);
00759                                    return EX_SOFTWARE;
00760                             }
00761                      }
00762 
00763                      /* get, or create, the IP address ID if needed */
00764                      (void) sanitize(db, fields[3], safesql,
00765                                      sizeof safesql);
00766 
00767                      snprintf(sql, sizeof sql,
00768                               "SELECT id FROM ipaddrs WHERE addr = '%s'",
00769                               safesql);
00770 
00771                      addrid = sql_get_int(db, sql);
00772                      if (addrid == -1)
00773                      {
00774                             (void) odbx_finish(db);
00775                             return EX_SOFTWARE;
00776                      }
00777                      else if (addrid == 0)
00778                      {
00779                             snprintf(sql, sizeof sql,
00780                                      "INSERT INTO ipaddrs (addr) VALUES ('%s')",
00781                                      safesql);
00782 
00783                             addrid = sql_do(db, sql);
00784                             if (addrid == -1)
00785                             {
00786                                    /* repeat the get */
00787                                    snprintf(sql, sizeof sql,
00788                                             "SELECT id FROM ipaddrs WHERE addr = '%s'",
00789                                             safesql);
00790 
00791                                    addrid = sql_get_int(db, sql);
00792                                    if (addrid == -1)
00793                                    {
00794                                           (void) odbx_finish(db);
00795                                           return EX_SOFTWARE;
00796                                    }
00797                             }
00798                             else
00799                             {
00800                                    snprintf(sql, sizeof sql,
00801                                             "SELECT %s", lastrow);
00802 
00803                                    addrid = sql_get_int(db, sql);
00804                                    if (addrid == -1)
00805                                    {
00806                                           (void) odbx_finish(db);
00807                                           return EX_SOFTWARE;
00808                                    }
00809                                    else if (addrid == 0)
00810                                    {
00811                                           fprintf(stderr,
00812                                                   "%s: failed to create IP address record for '%s'\n",
00813                                                   progname, fields[3]);
00814                                           (void) odbx_finish(db);
00815                                           return EX_SOFTWARE;
00816                                    }
00817                             }
00818                      }
00819 
00820                      /* verify data safety */
00821                      if (sanitize(db, fields[0], safesql, sizeof safesql) ||
00822                          sanitize(db, fields[4], safesql, sizeof safesql) ||
00823                          sanitize(db, fields[5], safesql, sizeof safesql) ||
00824                          sanitize(db, fields[6], safesql, sizeof safesql) ||
00825                          sanitize(db, fields[7], safesql, sizeof safesql) ||
00826                          sanitize(db, fields[8], safesql, sizeof safesql))
00827                      {
00828                             fprintf(stderr,
00829                                     "%s: unsafe data at input line %d\n",
00830                                     progname, line);
00831 
00832                             skipsigs = 1;
00833 
00834                             continue;
00835                      }
00836 
00837                      /* see if this is a duplicate */
00838                      (void) sql_mktime(fields[4], timebuf, sizeof timebuf);
00839                      snprintf(sql, sizeof sql,
00840                               "SELECT id FROM messages WHERE jobid = '%s' AND reporter = %d AND msgtime = '%s'",
00841                               fields[0], repid, timebuf);
00842 
00843                      msgid = sql_get_int(db, sql);
00844                      if (msgid == -1)
00845                      {
00846                             (void) odbx_finish(db);
00847                             return EX_SOFTWARE;
00848                      }
00849                      else if (msgid != 0)
00850                      {
00851                             if (dontskip == 0)
00852                             {
00853                                    fprintf(stderr,
00854                                            "%s: skipping duplicate message at line %d\n",
00855                                            progname, line);
00856                                    skipsigs = 1;
00857                             }
00858 
00859                             continue;
00860                      }
00861 
00862                      (void) sql_mktime(fields[4], timebuf, sizeof timebuf);
00863                      snprintf(sql, sizeof sql,
00864                               "INSERT INTO messages (jobid, reporter, from_domain, ip, msgtime, size, sigcount, atps, spam) VALUES ('%s', %d, %d, %d, '%s', %s, %s, %s, %s)",
00865                               fields[0],  /* jobid */
00866                               repid,             /* reporter */
00867                               domid,             /* from_domain */
00868                               addrid,     /* ip */
00869                               timebuf,    /* msgtime */
00870                               fields[5],  /* size */
00871                               fields[6],  /* sigcount */
00872                               fields[7],  /* atps */
00873                               fields[8]); /* spam */
00874 
00875                      msgid = sql_do(db, sql);
00876                      if (msgid == -1)
00877                      {
00878                             (void) odbx_finish(db);
00879                             return EX_SOFTWARE;
00880                      }
00881 
00882                      /* get back the message ID */
00883                      snprintf(sql, sizeof sql, "SELECT %s", lastrow);
00884 
00885                      msgid = sql_get_int(db, sql);
00886                      if (msgid == -1)
00887                      {
00888                             (void) odbx_finish(db);
00889                             return EX_SOFTWARE;
00890                      }
00891                      else if (msgid == 0)
00892                      {
00893                             fprintf(stderr,
00894                                     "%s: failed to create message record for '%s'\n",
00895                                     progname, fields[0]);
00896                             (void) odbx_finish(db);
00897                             return EX_SOFTWARE;
00898                      }
00899               }
00900 
00901               /* processing section for signatures */
00902               else if (c == 'S')
00903               {
00904                      int changed;
00905 
00906                      if (inversion != DKIMS_VERSION)
00907                      {
00908                             fprintf(stderr,
00909                                     "%s: ignoring old format at input line %d\n",
00910                                     progname, line);
00911 
00912                             continue;
00913                      }
00914 
00915                      if (n != DKIMS_SI_MAX + 1)
00916                      {
00917                             fprintf(stderr,
00918                                     "%s: unexpected signature field count (%d) at input line %d\n",
00919                                     progname, n, line);
00920                             continue;
00921                      }
00922                      else if (msgid <= 0)
00923                      {
00924                             fprintf(stderr,
00925                                     "%s: signature record before message record at input line %d\n",
00926                                     progname, line);
00927                             continue;
00928                      }
00929                      else if (skipsigs == 1)
00930                      {
00931                             continue;
00932                      }
00933 
00934                      /* get, or create, the domain ID if needed */
00935                      (void) sanitize(db, fields[0], safesql,
00936                                      sizeof safesql);
00937 
00938                      snprintf(sql, sizeof sql,
00939                               "SELECT id FROM domains WHERE name = '%s'",
00940                               safesql);
00941 
00942                      domid = sql_get_int(db, sql);
00943                      if (domid == -1)
00944                      {
00945                             (void) odbx_finish(db);
00946                             return EX_SOFTWARE;
00947                      }
00948                      else if (domid == 0)
00949                      {
00950                             snprintf(sql, sizeof sql,
00951                                      "INSERT INTO domains (name) VALUES ('%s')",
00952                                      safesql);
00953 
00954                             domid = sql_do(db, sql);
00955                             if (domid == -1)
00956                             {
00957                                    (void) odbx_finish(db);
00958                                    return EX_SOFTWARE;
00959                             }
00960 
00961                             snprintf(sql, sizeof sql, "SELECT %s",
00962                                      lastrow);
00963 
00964                             domid = sql_get_int(db, sql);
00965                             if (domid == -1)
00966                             {
00967                                    (void) odbx_finish(db);
00968                                    return EX_SOFTWARE;
00969                             }
00970                             else if (domid == 0)
00971                             {
00972                                    fprintf(stderr,
00973                                            "%s: failed to create domain record for '%s'\n",
00974                                            progname, fields[0]);
00975                                    (void) odbx_finish(db);
00976                                    return EX_SOFTWARE;
00977                             }
00978                      }
00979 
00980                      if (sanitize(db, fields[1], safesql, sizeof safesql) ||
00981                          sanitize(db, fields[2], safesql, sizeof safesql) ||
00982                          sanitize(db, fields[3], safesql, sizeof safesql) ||
00983                          sanitize(db, fields[4], safesql, sizeof safesql) ||
00984                          sanitize(db, fields[5], safesql, sizeof safesql))
00985                      {
00986                             fprintf(stderr,
00987                                     "%s: unsafe data at input line %d\n",
00988                                     progname, line);
00989                             continue;
00990                      }
00991 
00992                      snprintf(sql, sizeof sql,
00993                               "INSERT INTO signatures (message, domain, pass, fail_body, siglength, sigerror, dnssec) VALUES (%d, %d, %s, %s, %s, %s, %s)",
00994                               msgid,             /* message */
00995                               domid,             /* domain */
00996                               fields[1],  /* pass */
00997                               fields[2],  /* fail_body */
00998                               fields[3],  /* siglength */
00999                               fields[4],  /* sigerror */
01000                               fields[5]); /* dnssec */
01001 
01002                      sigid = sql_do(db, sql);
01003                      if (sigid == -1)
01004                      {
01005                             (void) odbx_finish(db);
01006                             return EX_SOFTWARE;
01007                      }
01008 
01009                      /* get back the signature ID */
01010                      snprintf(sql, sizeof sql, "SELECT %s", lastrow);
01011 
01012                      sigid = sql_get_int(db, sql);
01013                      if (sigid == -1)
01014                      {
01015                             (void) odbx_finish(db);
01016                             return EX_SOFTWARE;
01017                      }
01018                      else if (sigid == 0)
01019                      {
01020                             fprintf(stderr,
01021                                     "%s: failed to create signature record for input line %d\n",
01022                                     progname, line);
01023                             (void) odbx_finish(db);
01024                             return EX_SOFTWARE;
01025                      }
01026               }
01027 
01028               /* processing section for message status updates */
01029               else if (c == 'U' && inversion == DKIMS_VERSION)
01030               {
01031                      if (n != 4)
01032                      {
01033                             fprintf(stderr,
01034                                     "%s: unexpected update field count (%d) at input line %d\n",
01035                                     progname, n, line);
01036 
01037                             if (showfields == 1)
01038                                    dumpfields(stderr, fields, n);
01039 
01040                             continue;
01041                      }
01042 
01043                      /* get the reporter ID */
01044                      if (strcasecmp(reporter, fields[1]) != 0)
01045                      {
01046                             (void) sanitize(db, fields[1], safesql,
01047                                             sizeof safesql);
01048 
01049                             snprintf(sql, sizeof sql,
01050                                      "SELECT id FROM reporters WHERE name = '%s'",
01051                                      safesql);
01052 
01053                             repid = sql_get_int(db, sql);
01054                             if (repid == -1)
01055                             {
01056                                    (void) odbx_finish(db);
01057                                    return EX_SOFTWARE;
01058                             }
01059                             else if (repid == 0)
01060                             {
01061                                    if (norepadd == 1)
01062                                    {
01063                                           fprintf(stderr,
01064                                                   "%s: no such reporter '%s' at line %d\n",
01065                                                   progname, fields[1],
01066                                                   line);
01067                                    }
01068 
01069                                    continue;
01070                             }
01071 
01072                             strlcpy(reporter, fields[1], sizeof reporter);
01073                      }
01074 
01075                      /* verify data safety */
01076                      if (sanitize(db, fields[0], safesql, sizeof safesql) ||
01077                          sanitize(db, fields[2], safesql, sizeof safesql) ||
01078                          sanitize(db, fields[3], safesql, sizeof safesql))
01079                      {
01080                             fprintf(stderr,
01081                                     "%s: unsafe data at input line %d\n",
01082                                     progname, line);
01083 
01084                             continue;
01085                      }
01086 
01087                      /* get the message ID */
01088                      if (strcmp(fields[2], "0") == 0)
01089                      {
01090                             snprintf(sql, sizeof sql,
01091                                      "SELECT MAX(id) FROM messages WHERE jobid = '%s' AND reporter = %d",
01092                                      fields[0], repid);
01093                      }
01094                      else
01095                      {
01096                             (void) sql_mktime(fields[2], timebuf,
01097                                               sizeof timebuf);
01098                             snprintf(sql, sizeof sql,
01099                                      "SELECT id FROM messages WHERE jobid = '%s' AND reporter = %d AND msgtime = '%s'",
01100                                      fields[0], repid, timebuf);
01101                      }
01102 
01103                      msgid = sql_get_int(db, sql);
01104                      if (msgid == -1)
01105                      {
01106                             (void) odbx_finish(db);
01107                             return EX_SOFTWARE;
01108                      }
01109                      else if (msgid == 0)
01110                      {
01111                             fprintf(stderr,
01112                                     "%s: unknown message for update at line %d\n",
01113                                     progname, line);
01114                             continue;
01115                      }
01116 
01117                      snprintf(sql, sizeof sql,
01118                               "UPDATE messages SET spam = %s WHERE id = %d",
01119                               fields[3],  /* spam */
01120                               msgid);     /* message ID */
01121 
01122                      msgid = sql_do(db, sql);
01123                      if (msgid == -1)
01124                      {
01125                             (void) odbx_finish(db);
01126                             return EX_SOFTWARE;
01127                      }
01128               }
01129 
01130 #ifdef _FFR_STATSEXT
01131               /* processing section for extensions */
01132               else if (c == 'X')
01133               {
01134                      if (inversion != DKIMS_VERSION)
01135                      {
01136                             fprintf(stderr,
01137                                     "%s: ignoring old format at input line %d\n",
01138                                     progname, line);
01139 
01140                             continue;
01141                      }
01142 
01143                      if (n != 2)
01144                      {
01145                             fprintf(stderr,
01146                                     "%s: unexpected extension field count (%d) at input line %d\n",
01147                                     progname, n, line);
01148 
01149                             if (showfields == 1)
01150                                    dumpfields(stderr, fields, n);
01151 
01152                             continue;
01153                      }
01154                      else if (msgid <= 0)
01155                      {
01156                             fprintf(stderr,
01157                                     "%s: extension record before message record at input line %d\n",
01158                                     progname, line);
01159                             continue;
01160                      }
01161                      else if (skipsigs == 1 || extensions == 0)
01162                      {
01163                             continue;
01164                      }
01165 
01166                      if (sanitize(db, fields[0], safesql, sizeof safesql) ||
01167                          sanitize(db, fields[1], safesql, sizeof safesql))
01168                      {
01169                             fprintf(stderr,
01170                                     "%s: unsafe data at input line %d\n",
01171                                     progname, line);
01172                             continue;
01173                      }
01174 
01175                      snprintf(sql, sizeof sql,
01176                               "UPDATE messages SET %s = %s WHERE id = %d",
01177                               fields[0], fields[1], msgid);
01178 
01179                      err = sql_do(db, sql);
01180                      if (err == -1)
01181                      {
01182                             (void) odbx_finish(db);
01183                             return EX_SOFTWARE;
01184                      }
01185               }
01186 #endif /* _FFR_STATSEXT */
01187 
01188               /* unknown record type */
01189               else
01190               {
01191                      fprintf(stderr,
01192                              "%s: unknown record type '%c' at input line %d\n",
01193                              progname, c, line);
01194 
01195                      if (fatalerrors == 1)
01196                      {
01197                             (void) odbx_finish(db);
01198                             return EX_DATAERR;
01199                      }
01200               }
01201        }
01202 
01203        if (fields != NULL)
01204               free(fields);
01205 
01206        if (ferror(stdin))
01207        {
01208               fprintf(stderr, "%s: fgets(): %s\n", progname,
01209                       strerror(errno));
01210               (void) odbx_finish(db);
01211               return EX_OSERR;
01212        }
01213 
01214        /* unbind */
01215        err = odbx_unbind(db);
01216        if (err < 0)
01217        {
01218               fprintf(stderr, "%s: odbx_unbind(): %s\n", progname,
01219                       odbx_error(db, err));
01220               (void) odbx_finish(db);
01221               return EX_SOFTWARE;
01222        }
01223 
01224        /* shut down */
01225        if (odbx_finish(db) < 0)
01226        {
01227               fprintf(stderr, "%s: odbx_finish() failed\n", progname);
01228               return EX_SOFTWARE;
01229        }
01230 
01231        return EX_OK;
01232 }