Back to index

opendkim  2.6.4
opendkim-spam.c
Go to the documentation of this file.
00001 /*
00002 **  Copyright (c) 2011, The OpenDKIM Project.  All rights reserved.
00003 */
00004 
00005 #include "build-config.h"
00006 
00007 /* system includes */
00008 #include <sys/param.h>
00009 #include <sys/types.h>
00010 #include <stdio.h>
00011 #include <string.h>
00012 #include <sysexits.h>
00013 #include <unistd.h>
00014 #include <errno.h>
00015 #include <ctype.h>
00016 #include <stdlib.h>
00017 #include <assert.h>
00018 #ifdef HAVE_STDBOOL_H
00019 # include <stdbool.h>
00020 #endif /* HAVE_STDBOOL_H */
00021 
00022 #ifdef USE_ODBX
00023 /* opendbx includes */
00024 # include <odbx.h>
00025 #endif /* USE_ODBX */
00026 
00027 /* libopendkim includes */
00028 #include <dkim-strl.h>
00029 
00030 /* opendkim includes */
00031 #include "config.h"
00032 #include "stats.h"
00033 
00034 /* definitions, macros, etc. */
00035 #define       BUFRSZ        1024
00036 #define       CMDLINEOPTS   "b:c:d:fh:o:p:P:s:u:vV"
00037 #define       DEFDBBACKEND  SQL_BACKEND
00038 #undef DEFCONFFILE
00039 #define       DEFCONFFILE   CONFIG_BASE "/opendkim-spam.conf"
00040 #define       DEFDBHOST     "localhost"
00041 #define       DEFDBNAME     "opendkim"
00042 #define       DEFDBPASS     "opendkim"
00043 #define       DEFDBPORT     ""
00044 #define       DEFDBSPAMCOL  "uspam"
00045 #define       DEFDBUSER     "opendkim"
00046 #define       MAXHEADER     16384
00047 #define SPACES              "\r\n\t "
00048 
00049 #ifndef FALSE
00050 # define FALSE              0
00051 #endif /* ! FALSE */
00052 #ifndef TRUE
00053 # define TRUE        1
00054 #endif /* ! TRUE */
00055 
00056 /* globals */
00057 char *progname;
00058 
00059 /* config definition */
00060 struct configdef spam_config[] =
00061 {
00062        { "Background",                    CONFIG_TYPE_BOOLEAN, FALSE },
00063        { "DatabaseBackend",        CONFIG_TYPE_STRING,  FALSE },
00064        { "DatabaseHost",           CONFIG_TYPE_STRING,  FALSE },
00065        { "DatabaseName",           CONFIG_TYPE_STRING,  FALSE },
00066        { "DatabasePassword",              CONFIG_TYPE_STRING,  FALSE },
00067        { "DatabaseSpamColumn",            CONFIG_TYPE_STRING,  FALSE },
00068        { "DatabaseUser",           CONFIG_TYPE_STRING,  FALSE },
00069        { "StatisticsFile",         CONFIG_TYPE_STRING,  FALSE },
00070        { NULL,                            (u_int) -1,          FALSE }
00071 };
00072 
00073 /*
00074 **  USAGE -- usage message
00075 **
00076 **  Parameters:
00077 **     None.
00078 **
00079 **  Return value:
00080 **     EX_USAGE
00081 */
00082 
00083 int
00084 usage(void)
00085 {
00086        fprintf(stderr, "%s: usage: %s [options]\n"
00087                "\t-b backend  \tdatabase backend [%s]\n"
00088                "\t-c file     \tconfiguration file [%s]\n"
00089                "\t-d dbname   \tdatabase name [%s]\n"
00090                "\t-f          \trun in the foreground\n"
00091                "\t-h dbhost   \tdatabase hostname [%s]\n"
00092                "\t-o file     \tstatistics file\n"
00093                "\t-p dbpass   \tdatabase password [%s]\n"
00094                "\t-P dbport   \tdatabase port [%s]\n"
00095                "\t-s dbspamcol\tdatabase spam column name [%s]\n"
00096                "\t-u dbuser   \tdatabase user [%s]\n"
00097                "\t-v          \tbe more verbose\n"
00098                "\t-V          \tprint version number and exit\n",
00099                progname, progname,
00100                DEFDBBACKEND,
00101                DEFCONFFILE,
00102                DEFDBNAME,
00103                DEFDBHOST,
00104                DEFDBPASS,
00105                DEFDBPORT,
00106                DEFDBSPAMCOL,
00107                DEFDBUSER);
00108 
00109        return EX_USAGE;
00110 }
00111 
00112 /*
00113 **  MAIN -- program mainline
00114 **
00115 **  Parameters:
00116 **     argc, argv -- the usual
00117 **
00118 **  Return value:
00119 **     Exit status.
00120 */
00121 
00122 int
00123 main(int argc, char **argv)
00124 {
00125        _Bool dofork = TRUE;
00126        int c;
00127        int verbose = 0;
00128        int dberr;
00129        int repid;
00130        int msgid;
00131        char *p;
00132        char *prev;
00133        char *dbbackend = NULL;
00134        char *dbuser = NULL;
00135        char *dbpass = NULL;
00136        char *dbname = NULL;
00137        char *dbhost = NULL;
00138        char *dbspamcol = NULL;
00139        char *dbport = NULL;
00140        char *conffile = DEFCONFFILE;
00141        char *job = NULL;
00142        char *reporter = NULL;
00143        char *statsfile = NULL;
00144        FILE *sf;
00145        struct config *conf = NULL;
00146 #ifdef USE_ODBX
00147        odbx_t *db = NULL;
00148        odbx_result_t *result;
00149 #endif /* USE_ODBX */
00150        char buf[BUFRSZ + 1];
00151        char rcvd[MAXHEADER + 1];
00152 
00153        progname = (p = strrchr(argv[0], '/')) == NULL ? argv[0] : p + 1;
00154 
00155        while ((c = getopt(argc, argv, CMDLINEOPTS)) != -1)
00156        {
00157               switch (c)
00158               {
00159                 case 'b':
00160                      dbbackend = optarg;
00161                      break;
00162 
00163                 case 'c':
00164                      conffile = optarg;
00165                      break;
00166 
00167                 case 'd':
00168                      dbname = optarg;
00169                      break;
00170 
00171                 case 'f':
00172                      dofork = FALSE;
00173                      break;
00174 
00175                 case 'h':
00176                      dbhost = optarg;
00177                      break;
00178 
00179                 case 'P':
00180                      dbport = optarg;
00181                      break;
00182 
00183                 case 'o':
00184                      statsfile = optarg;
00185                      break;
00186 
00187                 case 'p':
00188                      dbpass = optarg;
00189                      break;
00190 
00191                 case 's':
00192                      dbspamcol = optarg;
00193                      break;
00194 
00195                 case 'u':
00196                      dbuser = optarg;
00197                      break;
00198 
00199                 case 'v':
00200                      verbose++;
00201                      break;
00202 
00203                 case 'V':
00204                      fprintf(stdout, "%s v%s\n", progname, VERSION);
00205                      return EX_OK;
00206 
00207                 default:
00208                      return usage();
00209               }
00210        }
00211 
00212        /* load from config file, if specified */
00213        if (conffile != NULL)
00214        {
00215               _Bool tmpf;
00216               unsigned int line;
00217               char path[MAXPATHLEN + 1];
00218 
00219               if (verbose >= 2)
00220               {
00221                      fprintf(stdout, "%s: loading configuration from %s\n",
00222                              progname, conffile);
00223               }
00224 
00225               memset(path, '\0', sizeof path);
00226               conf = config_load(conffile, spam_config, &line,
00227                                  path, sizeof path);
00228 
00229               if (conf == NULL)
00230               {
00231                      fprintf(stderr,
00232                              "%s: %s: configuration error at line %u: %s\n",
00233                              progname, path, line, config_error());
00234                      return EX_CONFIG;
00235 
00236               }
00237 
00238               /* extract values */
00239               if (dbbackend == NULL)
00240               {
00241                      (void) config_get(conf, "DatabaseBackend",
00242                                        &dbbackend, sizeof dbbackend);
00243               }
00244 
00245               if (dbhost == NULL)
00246               {
00247                      (void) config_get(conf, "DatabaseHost",
00248                                        &dbhost, sizeof dbhost);
00249               }
00250 
00251                 if (dbname == NULL)
00252               {
00253                      (void) config_get(conf, "DatabaseName",
00254                                        &dbname, sizeof dbname);
00255               }
00256 
00257               if (dbpass == NULL)
00258               {
00259                      (void) config_get(conf, "DatabasePassword",
00260                                        &dbpass, sizeof dbpass);
00261               }
00262 
00263               if (dbport == NULL)
00264               {
00265                      (void) config_get(conf, "DatabasePort",
00266                                        &dbport, sizeof dbport);
00267               }
00268 
00269               if (dbspamcol == NULL)
00270               {
00271                      (void) config_get(conf, "DatabaseSpamColumn",
00272                                        &dbspamcol, sizeof dbspamcol);
00273               }
00274 
00275                 if (dbuser == NULL)
00276               {
00277                      (void) config_get(conf, "DatabaseUser",
00278                                        &dbuser, sizeof dbuser);
00279               }
00280 
00281               if (statsfile == NULL)
00282               {
00283                      (void) config_get(conf, "StatisticsFile",
00284                                        &statsfile, sizeof statsfile);
00285               }
00286 
00287               if (config_get(conf, "Background", &tmpf, sizeof tmpf) == 1)
00288               {
00289                      if (dofork && !tmpf)
00290                             tmpf = FALSE;
00291               }
00292        }
00293 
00294        if (dbbackend == NULL)
00295               dbbackend = DEFDBBACKEND;
00296        if (dbhost == NULL)
00297               dbhost = DEFDBHOST;
00298        if (dbname == NULL)
00299               dbname = DEFDBNAME;
00300        if (dbpass == NULL)
00301               dbpass = DEFDBPASS;
00302        if (dbspamcol == NULL)
00303               dbspamcol = DEFDBSPAMCOL;
00304        if (dbuser == NULL)
00305               dbuser = DEFDBUSER;
00306 
00307        /* connect to the DB */
00308        if (statsfile != NULL)
00309        {
00310               sf = fopen(statsfile, "a");
00311               if (sf == NULL)
00312               {
00313                      fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
00314                              statsfile, strerror(errno));
00315                      return EX_OSERR;
00316               }
00317 
00318               /* write version if file is new */
00319               if (ftell(sf) == 0)
00320                      fprintf(sf, "V%d\n", DKIMS_VERSION);
00321        }
00322        else
00323        {
00324 #ifndef USE_ODBX
00325               fprintf(stderr, "%s: SQL not supported in this installation\n",
00326                       progname);
00327               return EX_SOFTWARE;
00328 
00329 #else /* ! USE_ODBX */
00330 
00331               dberr = odbx_init(&db, dbbackend, dbhost, dbport);
00332               if (dberr < 0)
00333               {
00334                      fprintf(stderr, "%s: odbx_init(): %s\n", progname,
00335                              odbx_error(NULL, dberr));
00336                      return EX_SOFTWARE;
00337               }
00338 
00339               if (verbose >= 1)
00340               {
00341                      fprintf(stdout, "%s: connected to database on %s\n",
00342                              progname, dbhost);
00343               }
00344 
00345               dberr = odbx_bind(db, dbname, dbuser, dbpass,
00346                                 ODBX_BIND_SIMPLE);
00347               if (dberr < 0)
00348               {
00349                      fprintf(stderr, "%s: odbx_bind(): %s\n", progname,
00350                              odbx_error(db, dberr));
00351                      (void) odbx_finish(db);
00352                      return EX_SOFTWARE;
00353               }
00354 
00355               if (verbose >= 2)
00356               {
00357                      fprintf(stdout, "%s: database binding successful\n",
00358                              progname);
00359               }
00360 #endif /* ! USE_ODBX */
00361        }
00362 
00363        /* read first Received:, extract reporter and job ID */
00364        memset(buf, '\0', sizeof buf);
00365        memset(rcvd, '\0', sizeof rcvd);
00366        while (fgets(buf, sizeof buf - 1, stdin) != NULL)
00367        {
00368               for (p = buf; *p != '\0'; p++)
00369               {
00370                      if (*p == '\r' || *p == '\n')
00371                      {
00372                             *p = '\0';
00373                             break;
00374                      }
00375               }
00376 
00377               if (rcvd[0] == '\0')
00378               {
00379                      if (strncasecmp(buf, "Received:", 9) == 0)
00380                             strlcat(rcvd, buf, sizeof rcvd);
00381               }
00382               else if (buf[0] == '\0' ||
00383                        (isascii(buf[0]) && !isspace(buf[0])))
00384               {
00385                      break;
00386               }
00387               else
00388               {
00389                      strlcat(rcvd, buf, sizeof rcvd);
00390               }
00391        }
00392 
00393        if (rcvd[0] == '\0')
00394        {
00395               fprintf(stderr, "%s: Received header field not found\n",
00396                       progname);
00397 
00398 #ifdef USE_ODBX
00399               if (db != NULL)
00400               {
00401                      (void) odbx_unbind(db);
00402                      (void) odbx_finish(db);
00403               }
00404               else
00405               {
00406 #endif /* USE_ODBX */
00407                      fclose(sf);
00408 #ifdef USE_ODBX
00409               }
00410 #endif /* USE_ODBX */
00411               return EX_DATAERR;
00412        }
00413 
00414        /*
00415        **  This is overly simplistic as it doesn't account for CFWS or
00416        **  escapes, but for now it's a good starting point.
00417        */
00418 
00419        prev = NULL;
00420        for (p = strtok(&rcvd[9], SPACES);
00421             p != NULL;
00422             p = strtok(NULL, SPACES))
00423        {
00424               if (prev != NULL)
00425               {
00426                      if (strcasecmp(prev, "id") == 0)
00427                             job = p;
00428                      else if (strcasecmp(prev, "by") == 0)
00429                             reporter = p;
00430               }
00431 
00432               prev = p;
00433        }
00434 
00435        if (job == NULL)
00436        {
00437               fprintf(stderr,
00438                       "%s: could not locate job ID in Received header field\n",
00439                       progname);
00440 
00441 #ifdef USE_ODBX
00442               if (db != NULL)
00443               {
00444                      (void) odbx_unbind(db);
00445                      (void) odbx_finish(db);
00446               }
00447               else
00448               {
00449 #endif /* USE_ODBX */
00450                      fclose(sf);
00451 #ifdef USE_ODBX
00452               }
00453 #endif /* USE_ODBX */
00454               return EX_DATAERR;
00455        }
00456        else if (reporter == NULL)
00457        {
00458               fprintf(stderr,
00459                       "%s: could not locate receiving host in Received header field\n",
00460                       progname);
00461 
00462 #ifdef USE_ODBX
00463               if (db != NULL)
00464               {
00465                      (void) odbx_unbind(db);
00466                      (void) odbx_finish(db);
00467               }
00468               else
00469               {
00470 #endif /* USE_ODBX */
00471                      fclose(sf);
00472 #ifdef USE_ODBX
00473               }
00474 #endif /* USE_ODBX */
00475               return EX_DATAERR;
00476        }
00477 
00478        for (p = job; *p != '\0'; p++)
00479        {
00480               if (*p == ';')
00481               {
00482                      *p = '\0';
00483                      break;
00484               }
00485        }
00486 
00487        if (sf != NULL)
00488        {
00489               fprintf(sf, "U%s\t%s\t0\t1\n", job, reporter);
00490               fclose(sf);
00491               return 0;
00492        }
00493 
00494 #ifdef USE_ODBX
00495        if (verbose >= 1)
00496        {
00497               fprintf(stdout, "%s: requesting reporter id for '%s'\n",
00498                       progname, reporter);
00499        }
00500 
00501        if (dofork)
00502        {
00503               pid_t pid;
00504 
00505               pid = fork();
00506               switch (pid)
00507               {
00508                 case -1:
00509                      fprintf(stderr, "%s: fork(): %s\n",
00510                              progname, strerror(errno));
00511                      (void) odbx_unbind(db);
00512                      (void) odbx_finish(db);
00513                      return EX_OSERR;
00514 
00515                 case 0:
00516                      (void) setsid();
00517                      /* XXX -- should probably dup2() /dev/null here */
00518                      break;
00519 
00520                 default:
00521                      return 0;
00522               }
00523        }
00524 
00525        /* retrieve reporter ID */
00526        snprintf(buf, sizeof buf, "SELECT id FROM reporters WHERE name = '%s'",
00527                 reporter);
00528        if (verbose >= 3)
00529               fprintf(stdout, ">>> %s\n", buf);
00530 
00531        dberr = odbx_query(db, buf, 0);
00532        if (dberr != ODBX_ERR_SUCCESS)
00533        {
00534               fprintf(stderr, "%s: odbx_query(): %s\n", progname,
00535                       odbx_error(db, dberr));
00536               (void) odbx_unbind(db);
00537               (void) odbx_finish(db);
00538               return EX_SOFTWARE;
00539        }
00540 
00541        repid = -1;
00542 
00543        while ((dberr = odbx_result(db, &result, NULL, 0)) != ODBX_RES_DONE)
00544        {
00545               if (dberr < 0)
00546               {
00547                      fprintf(stderr, "%s: odbx_result(): %s\n",
00548                              progname, odbx_error(db, dberr));
00549                      (void) odbx_unbind(db);
00550                      (void) odbx_finish(db);
00551                      return EX_SOFTWARE;
00552               }
00553 
00554               switch (dberr)
00555               {
00556                 case ODBX_RES_TIMEOUT:
00557                      fprintf(stderr, "%s: odbx_result(): timeout\n",
00558                              progname);
00559                      (void) odbx_unbind(db);
00560                      (void) odbx_finish(db);
00561                      return EX_SOFTWARE;
00562 
00563                 case ODBX_RES_NOROWS:
00564                      fprintf(stderr,
00565                              "%s: odbx_result(): unexpected return value\n",
00566                              progname);
00567                      (void) odbx_unbind(db);
00568                      (void) odbx_finish(db);
00569                      return EX_SOFTWARE;
00570 
00571                 case ODBX_RES_ROWS:
00572                      while ((dberr = odbx_row_fetch(result)) != ODBX_ROW_DONE)
00573                      {
00574                             if (dberr < 0)
00575                             {
00576                                    fprintf(stderr,
00577                                            "%s: odbx_row_fetch(): %s\n",
00578                                           progname,
00579                                            odbx_error(db, dberr));
00580                                    (void) odbx_unbind(db);
00581                                    (void) odbx_finish(db);
00582                                    return EX_SOFTWARE;
00583                             }
00584 
00585                             if (repid != -1)
00586                             {
00587                                    fprintf(stderr,
00588                                            "%s: duplicate entries for repoter '%s'\n",
00589                                           progname,
00590                                            reporter);
00591                                    (void) odbx_unbind(db);
00592                                    (void) odbx_finish(db);
00593                                    return EX_SOFTWARE;
00594                             }
00595 
00596                             repid = atoi(odbx_field_value(result, 0));
00597                             if (repid <= 0)
00598                             {
00599                                    fprintf(stderr,
00600                                            "%s: unexpected reporter id '%s'\n",
00601                                           progname,
00602                                            odbx_field_value(result, 0));
00603                                    (void) odbx_unbind(db);
00604                                    (void) odbx_finish(db);
00605                                    return EX_SOFTWARE;
00606                             }
00607                      }
00608 
00609                      break;
00610 
00611                 default:
00612                      assert(0);
00613               }
00614        }
00615 
00616        (void) odbx_result_finish(result);
00617 
00618        if (repid <= 0)
00619        {
00620               fprintf(stderr,
00621                       "%s: could not determine reporter id for '%s'\n",
00622                       progname, reporter);
00623 
00624               (void) odbx_unbind(db);
00625               (void) odbx_finish(db);
00626               return EX_DATAERR;
00627        }
00628 
00629        if (verbose >= 2)
00630               fprintf(stdout, "%s: reporter id = %d\n", progname, repid);
00631 
00632        if (verbose >= 1)
00633        {
00634               fprintf(stdout, "%s: requesting message id for '%s'\n",
00635                       progname, job);
00636        }
00637 
00638        /* get message ID */
00639        snprintf(buf, sizeof buf,
00640                 "SELECT MAX(id) FROM messages WHERE jobid = '%s' AND reporter = %d",
00641                 job, repid);
00642        if (verbose >= 3)
00643               fprintf(stdout, ">>> %s\n", buf);
00644        dberr = odbx_query(db, buf, 0);
00645        if (dberr != ODBX_ERR_SUCCESS)
00646        {
00647               fprintf(stderr, "%s: odbx_query(): %s\n", progname,
00648                       odbx_error(db, dberr));
00649               (void) odbx_unbind(db);
00650               (void) odbx_finish(db);
00651               return EX_SOFTWARE;
00652        }
00653 
00654        msgid = -1;
00655        while ((dberr = odbx_result(db, &result, NULL, 0)) != ODBX_RES_DONE)
00656        {
00657               if (dberr < 0)
00658               {
00659                      fprintf(stderr, "%s: odbx_result(): %s\n",
00660                              progname, odbx_error(db, dberr));
00661                      (void) odbx_unbind(db);
00662                      (void) odbx_finish(db);
00663                      return EX_SOFTWARE;
00664               }
00665 
00666               switch (dberr)
00667               {
00668                 case ODBX_RES_TIMEOUT:
00669                      fprintf(stderr, "%s: odbx_result(): timeout\n",
00670                              progname);
00671                      (void) odbx_unbind(db);
00672                      (void) odbx_finish(db);
00673                      return EX_SOFTWARE;
00674 
00675                 case ODBX_RES_NOROWS:
00676                      fprintf(stderr,
00677                              "%s: odbx_result(): unexpected return value\n",
00678                              progname);
00679                      (void) odbx_unbind(db);
00680                      (void) odbx_finish(db);
00681                      return EX_SOFTWARE;
00682 
00683                 case ODBX_RES_ROWS:
00684                      while ((dberr = odbx_row_fetch(result)) != ODBX_ROW_DONE)
00685                      {
00686                             if (dberr < 0)
00687                             {
00688                                    fprintf(stderr,
00689                                            "%s: odbx_row_fetch(): %s\n",
00690                                           progname,
00691                                            odbx_error(db, dberr));
00692                                    (void) odbx_unbind(db);
00693                                    (void) odbx_finish(db);
00694                                    return EX_SOFTWARE;
00695                             }
00696 
00697                             if (msgid != -1)
00698                             {
00699                                    fprintf(stderr,
00700                                            "%s: duplicate entries for job '%s' from reporter '%s'\n",
00701                                           progname,
00702                                            job, reporter);
00703                                    (void) odbx_unbind(db);
00704                                    (void) odbx_finish(db);
00705                                    return EX_SOFTWARE;
00706                             }
00707 
00708 
00709                             msgid = atoi(odbx_field_value(result, 0));
00710                             if (msgid <= 0)
00711                             {
00712                                    fprintf(stderr,
00713                                            "%s: unexpected message id '%s'\n",
00714                                           progname,
00715                                            odbx_field_value(result, 0));
00716                                    (void) odbx_unbind(db);
00717                                    (void) odbx_finish(db);
00718                                    return EX_SOFTWARE;
00719                             }
00720                      }
00721 
00722                      break;
00723 
00724                 default:
00725                      assert(0);
00726               }
00727        }
00728 
00729        (void) odbx_result_finish(result);
00730 
00731        if (msgid <= 0)
00732        {
00733               fprintf(stderr,
00734                       "%s: could not determine message id for '%s'\n",
00735                       progname, job);
00736 
00737               (void) odbx_unbind(db);
00738               (void) odbx_finish(db);
00739               return EX_DATAERR;
00740        }
00741 
00742        if (verbose >= 2)
00743               fprintf(stdout, "%s: message id = %d\n", progname, msgid);
00744 
00745        /* issue update */
00746        if (verbose >= 1)
00747               fprintf(stdout, "%s: updating record\n", progname);
00748 
00749        snprintf(buf, sizeof buf,
00750                 "UPDATE messages SET %s = %s + 1 WHERE id = %d AND %s >= 0",
00751                 dbspamcol, dbspamcol, msgid, dbspamcol);
00752        if (verbose >= 3)
00753               fprintf(stdout, ">>> %s\n", buf);
00754        dberr = odbx_query(db, buf, 0);
00755        if (dberr != ODBX_ERR_SUCCESS)
00756        {
00757               fprintf(stderr, "%s: odbx_query(): %s\n", progname,
00758                       odbx_error(db, dberr));
00759               (void) odbx_unbind(db);
00760               (void) odbx_finish(db);
00761               return EX_SOFTWARE;
00762        }
00763 
00764        for (;;)
00765        {
00766               dberr = odbx_result(db, &result, NULL, 0);
00767               if (dberr == ODBX_RES_DONE || dberr == ODBX_RES_NOROWS)
00768               {
00769                      break;
00770               }
00771               else if (dberr < 0)
00772               {
00773                      fprintf(stderr, "%s: odbx_result(): %s\n",
00774                              progname, odbx_error(db, dberr));
00775                      (void) odbx_unbind(db);
00776                      (void) odbx_finish(db);
00777                      return EX_SOFTWARE;
00778               }
00779 
00780               switch (dberr)
00781               {
00782                 case ODBX_RES_TIMEOUT:
00783                      fprintf(stderr, "%s: odbx_result(): timeout\n",
00784                              progname);
00785                      (void) odbx_unbind(db);
00786                      (void) odbx_finish(db);
00787                      return EX_SOFTWARE;
00788 
00789                 case ODBX_RES_ROWS:
00790                      fprintf(stderr,
00791                              "%s: odbx_result(): unexpected return value\n",
00792                              progname);
00793                      (void) odbx_unbind(db);
00794                      (void) odbx_finish(db);
00795                      return EX_SOFTWARE;
00796 
00797                 default:
00798                      assert(0);
00799               }
00800        }
00801 
00802        (void) odbx_result_finish(result);
00803 
00804        if (verbose >= 1)
00805               fprintf(stdout, "%s: record updated\n", progname);
00806 
00807        /* close down */
00808        (void) odbx_unbind(db);
00809        (void) odbx_finish(db);
00810 #endif /* USE_ODBX */
00811 
00812        return 0;
00813 }