1 /*
2  * ovdb.c
3  * ovdb 2.00
4  * Overview storage using Berkeley DB 4.4 or higher
5  *
6  * 2004-02-17 : Need to track search cursors, since it's possible that
7  *              ovdb_closesearch does not get called.  We now close
8  *              any cursors still open in ovdb_close, or they'd be in
9  *              the database indefinitely causing deadlocks.
10  * 2002-08-13 : Change BOOL to bool, remove portability to < 2.4.
11  * 2002-08-11 : Cleaned up use of sprintf and fixed a bunch of warnings.
12  * 2002-02-28 : Update getartinfo for the overview API change in 2.4.  This
13  *              breaks compatibility with INN 2.3.x....
14  * 2000-12-12 : Add support for Berkeley DB DB_SYSTEM_MEM option, controlled
15  *            : by ovdb.conf 'useshm' and 'shmkey'
16  * 2000-11-27 : Update for DB 3.2.x compatibility
17  * 2000-11-13 : New 'readserver' feature
18  * 2000-10-10 : ovdb_search now closes the cursor right after the last
19  *              record is read.
20  * 2000-10-05 : artnum member of struct datakey changed from ARTNUM to
21  *              u_int32_t.  OS's where sizeof(long)==8 will have to rebuild
22  *              their databases after this update.
23  * 2000-10-05 : from Dan Riley: struct datakey needs to be zero'd, for
24  *              64-bit OSs where the struct has internal padding bytes.
25  * 2000-09-29 : ovdb_expiregroup can now fix incorrect counts; use new
26  *              inn/version.h so can have same ovdb.c for 2.3.0, 2.3.1, and 2.4
27  * 2000-09-28 : low mark in ovdb_expiregroup still wasn't right
28  * 2000-09-27 : Further improvements to ovdb_expiregroup: restructured the
29  *              loop; now updates groupinfo as it goes along rather than
30  *              counting records at the end, which prevents a possible
31  *              deadlock.
32  * 2000-09-19 : *lo wasn't being set in ovdb_expiregroup
33  * 2000-09-15 : added ovdb_check_user(); tweaked some error msgs; fixed an
34  *              improper use of RENEW
35  * 2000-08-28 : New major release: version 2.00 (beta)
36  *    + "groupsbyname" and "groupstats" databases replaced with "groupinfo".
37  *    + ovdb_recover, ovdb_upgrade, and dbprocs are now deprecated; their
38  *         functionality is now in ovdb_init and ovdb_monitor.
39  *    + ovdb_init can upgrade a database from the old version of ovdb to
40  *         work with this version.
41  *    + Rewrote ovdb_expiregroup(); it can now re-write OV data rather
42  *         than simply deleting old keys (which can leave 'holes' that result
43  *         in inefficient disk-space use).
44  *    + Add "nocompact" to ovdb.conf, which controls whether ovdb_expiregroup()
45  *         rewrites OV data.
46  *    + No longer needs the Berkeley DB tools db_archive, db_checkpoint, and
47  *         db_deadlock.  That functionality is now in ovdb_monitor.
48  *    + ovdb_open() won't succeed if ovdb_monitor is not running.  This will
49  *         prevent the problems that happen if the database is not regularly
50  *         checkpointed and deadlock-tested.
51  *    + Internal group IDs (32-bit ints) are now reused.
52  *    + Add "maxlocks" to ovdb.conf, which will set the DB lk_max parameter.
53  *    + Pull "test" code out into ovdb_stat.  ovdb_stat will also provide
54  *         functionality similar to the Berkeley DB "db_stat" command.
55  *    + Update docs: write man pages for the new ovdb_* commands; update
56  *         ovdb.pod
57  *
58  * 2000-07-11 : fix possible alignment problem; add test code
59  * 2000-07-07 : bugfix: timestamp handling
60  * 2000-06-10 : Modified groupnum() interface; fix ovdb_add() to return false
61  *              for certain groupnum() errors
62  * 2000-06-08 : Added Berkeley DB 3.1.x compatibility
63  * 2000-04-09 : Tweak some default parameters; store aliased group info
64  * 2000-03-29 : Add DB_RMW flag to the 'get' of get-modify-put sequences
65  * 2000-02-17 : Update expire behavior to be consistent with current
66  *              ov3 and buffindexed
67  * 2000-01-13 : Fix to make compatible with unmodified nnrpd/article.c
68  * 2000-01-04 : Added data versioning
69  * 1999-12-20 : Added Berkeley DB 3.x compatibility
70  * 1999-12-06 : First Release -- H. Kehoe <hakehoe@avalon.net>
71  */
72 
73 #include "portable/system.h"
74 
75 #include "portable/socket.h"
76 #include <errno.h>
77 #include <fcntl.h>
78 #ifdef HAVE_LIMITS_H
79 #    include <limits.h>
80 #endif
81 #include <signal.h>
82 #ifdef HAVE_SYS_SELECT_H
83 #    include <sys/select.h>
84 #endif
85 #ifdef HAVE_ZLIB
86 #    include <zlib.h>
87 #    define MAX_UNZIP_SZ 100000
88 #    define COMPRESS_MIN 600
89 #endif
90 
91 #ifdef HAVE_SYS_TIME_H
92 #    include <sys/time.h>
93 #endif
94 #include <time.h>
95 
96 #include "conffile.h"
97 #include "inn/fdflag.h"
98 #include "inn/innconf.h"
99 #include "inn/libinn.h"
100 #include "inn/messages.h"
101 #include "inn/newsuser.h"
102 #include "inn/paths.h"
103 #include "inn/storage.h"
104 
105 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
106 #    include "portable/socket-unix.h"
107 #endif
108 
109 #include "inn/ov.h"
110 #include "ovdb-private.h"
111 #include "ovdb.h"
112 #include "ovinterface.h"
113 #ifndef HAVE_BDB
114 
115 /* Provide stub functions if we don't have db */
116 
117 bool
ovdb_open(int mode UNUSED)118 ovdb_open(int mode UNUSED)
119 {
120     warn("OVDB: ovdb support not enabled");
121     return false;
122 }
123 
124 bool
ovdb_groupstats(const char * group UNUSED,int * lo UNUSED,int * hi UNUSED,int * count UNUSED,int * flag UNUSED)125 ovdb_groupstats(const char *group UNUSED, int *lo UNUSED, int *hi UNUSED,
126                 int *count UNUSED, int *flag UNUSED)
127 {
128     return false;
129 }
130 
131 bool
ovdb_groupadd(const char * group UNUSED,ARTNUM lo UNUSED,ARTNUM hi UNUSED,char * flag UNUSED)132 ovdb_groupadd(const char *group UNUSED, ARTNUM lo UNUSED, ARTNUM hi UNUSED,
133               char *flag UNUSED)
134 {
135     return false;
136 }
137 
138 bool
ovdb_groupdel(const char * group UNUSED)139 ovdb_groupdel(const char *group UNUSED)
140 {
141     return false;
142 }
143 
144 bool
ovdb_add(const char * group UNUSED,ARTNUM artnum UNUSED,TOKEN token UNUSED,char * data UNUSED,int len UNUSED,time_t arrived UNUSED,time_t expires UNUSED)145 ovdb_add(const char *group UNUSED, ARTNUM artnum UNUSED, TOKEN token UNUSED,
146          char *data UNUSED, int len UNUSED, time_t arrived UNUSED,
147          time_t expires UNUSED)
148 {
149     return false;
150 }
151 
152 bool
ovdb_cancel(const char * group UNUSED,ARTNUM artnum UNUSED)153 ovdb_cancel(const char *group UNUSED, ARTNUM artnum UNUSED)
154 {
155     return false;
156 }
157 
158 void *
ovdb_opensearch(const char * group UNUSED,int low UNUSED,int high UNUSED)159 ovdb_opensearch(const char *group UNUSED, int low UNUSED, int high UNUSED)
160 {
161     return NULL;
162 }
163 
164 bool
ovdb_search(void * handle UNUSED,ARTNUM * artnum UNUSED,char ** data UNUSED,int * len UNUSED,TOKEN * token UNUSED,time_t * arrived UNUSED)165 ovdb_search(void *handle UNUSED, ARTNUM *artnum UNUSED, char **data UNUSED,
166             int *len UNUSED, TOKEN *token UNUSED, time_t *arrived UNUSED)
167 {
168     return false;
169 }
170 
171 void
ovdb_closesearch(void * handle UNUSED)172 ovdb_closesearch(void *handle UNUSED)
173 {
174 }
175 
176 bool
ovdb_getartinfo(const char * group UNUSED,ARTNUM artnum UNUSED,TOKEN * token UNUSED)177 ovdb_getartinfo(const char *group UNUSED, ARTNUM artnum UNUSED,
178                 TOKEN *token UNUSED)
179 {
180     return false;
181 }
182 
183 bool
ovdb_expiregroup(const char * group UNUSED,int * lo UNUSED,struct history * h UNUSED)184 ovdb_expiregroup(const char *group UNUSED, int *lo UNUSED,
185                  struct history *h UNUSED)
186 {
187     return false;
188 }
189 
190 bool
ovdb_ctl(OVCTLTYPE type UNUSED,void * val UNUSED)191 ovdb_ctl(OVCTLTYPE type UNUSED, void *val UNUSED)
192 {
193     return false;
194 }
195 
196 void
ovdb_close(void)197 ovdb_close(void)
198 {
199 }
200 
201 #else /* HAVE_BDB */
202 
203 #    define EXPIREGROUP_TXN_SIZE 100
204 #    define DELETE_TXN_SIZE      500
205 
206 int ovdb_data_ver = 0;
207 struct ovdb_conf ovdb_conf;
208 DB_ENV *OVDBenv = NULL;
209 
210 static int OVDBmode;
211 static bool Cutofflow;
212 static DB **dbs = NULL;
213 static int oneatatime = 0;
214 static int current_db = -1;
215 static time_t eo_start = 0;
216 static int clientmode = 0;
217 
218 static DB *groupinfo = NULL;
219 static DB *groupaliases = NULL;
220 
221 #    define OVDBtxn_nosync 1
222 #    define OVDBnumdbfiles 2
223 #    define OVDBpagesize   3
224 #    define OVDBcachesize  4
225 #    define OVDBminkey     5
226 #    define OVDBmaxlocks   6
227 #    define OVDBnocompact  7
228 #    define OVDBreadserver 8
229 #    define OVDBnumrsprocs 9
230 #    define OVDBmaxrsconn  10
231 #    define OVDBuseshm     11
232 #    define OVDBshmkey     12
233 #    define OVDBcompress   13
234 #    define OVDBncache     14
235 
236 /* clang-format off */
237 static CONFTOKEN toks[] = {
238     { OVDBtxn_nosync,   (char *) "txn_nosync"   },
239     { OVDBnumdbfiles,   (char *) "numdbfiles"   },
240     { OVDBpagesize,     (char *) "pagesize"     },
241     { OVDBcachesize,    (char *) "cachesize"    },
242     { OVDBminkey,       (char *) "minkey"       },
243     { OVDBmaxlocks,     (char *) "maxlocks"     },
244     { OVDBnocompact,    (char *) "nocompact"    },
245     { OVDBreadserver,   (char *) "readserver"   },
246     { OVDBnumrsprocs,   (char *) "numrsprocs"   },
247     { OVDBmaxrsconn,    (char *) "maxrsconn"    },
248     { OVDBuseshm,       (char *) "useshm"       },
249     { OVDBshmkey,       (char *) "shmkey"       },
250     { OVDBcompress,     (char *) "compress"     },
251     { OVDBncache,       (char *) "ncache"       },
252     { 0,                NULL                    }
253 };
254 /* clang-format on */
255 
256 #    define _PATH_OVDBCONF "ovdb.conf"
257 
258 /*********** readserver functions ***********/
259 
260 static int clientfd = -1;
261 
262 /* read client send and receive functions. */
263 
264 static int
csend(const void * data,int n)265 csend(const void *data, int n)
266 {
267     ssize_t status;
268 
269     if (n == 0)
270         return 0;
271     status = xwrite(clientfd, data, n);
272     if (status < 0)
273         syswarn("OVDB: rc: cant write");
274     return status;
275 }
276 
277 static int
crecv(void * data,int n)278 crecv(void *data, int n)
279 {
280     int r, p = 0;
281 
282     if (n == 0)
283         return 0;
284 
285     while (p < n) {
286         r = read(clientfd, (char *) data + p, n - p);
287         if (r <= 0) {
288             if (r < 0 && errno == EINTR)
289                 continue;
290             syswarn("OVDB: rc: cant read");
291             clientfd = -1;
292             exit(1);
293         }
294         p += r;
295     }
296     return p;
297 }
298 
299 /* Attempt to connect to the readserver.  If anything fails, we
300    return -1 so that ovdb_open can open the database directly. */
301 
302 static int
client_connect(void)303 client_connect(void)
304 {
305     ssize_t r;
306     size_t p = 0;
307     char *path;
308 #    ifdef HAVE_UNIX_DOMAIN_SOCKETS
309     struct sockaddr_un sa;
310 #    else
311     struct sockaddr_in sa;
312 #    endif
313     char banner[sizeof(OVDB_SERVER_BANNER)];
314     fd_set fds;
315     struct timeval timeout;
316 
317 #    ifdef HAVE_UNIX_DOMAIN_SOCKETS
318     clientfd = socket(AF_UNIX, SOCK_STREAM, 0);
319 #    else
320     clientfd = socket(AF_INET, SOCK_STREAM, 0);
321 #    endif
322     if (clientfd < 0) {
323         syswarn("OVDB: socket");
324         return -1;
325     }
326 
327     memset(&sa, 0, sizeof sa);
328 #    ifdef HAVE_UNIX_DOMAIN_SOCKETS
329     sa.sun_family = AF_UNIX;
330     path = concatpath(innconf->pathrun, OVDB_SERVER_SOCKET);
331     strlcpy(sa.sun_path, path, sizeof(sa.sun_path));
332     free(path);
333     r = connect(clientfd, (struct sockaddr *) &sa, SUN_LEN(&sa));
334 #    else
335     sa.sin_family = AF_INET;
336     sa.sin_port = 0;
337     sa.sin_addr.s_addr = htonl(0x7f000001UL);
338     bind(clientfd, (struct sockaddr *) &sa, sizeof sa);
339     sa.sin_port = htons(OVDB_SERVER_PORT);
340     r = connect(clientfd, (struct sockaddr *) &sa, sizeof sa);
341 #    endif
342     if (r != 0) {
343         syswarn("OVDB: rc: cant connect to server");
344         close(clientfd);
345         clientfd = -1;
346         return -1;
347     }
348 
349     while (p < sizeof(OVDB_SERVER_BANNER)) {
350         FD_ZERO(&fds);
351         FD_SET(clientfd, &fds);
352         timeout.tv_sec = 30;
353         timeout.tv_usec = 0;
354 
355         r = select(clientfd + 1, &fds, NULL, NULL, &timeout);
356 
357         if (r < 0) {
358             if (errno == EINTR)
359                 continue;
360             syswarn("OVDB: select");
361             close(clientfd);
362             clientfd = -1;
363             return -1;
364         }
365         if (r == 0) {
366             warn("OVDB: rc: timeout waiting for server");
367             close(clientfd);
368             clientfd = -1;
369             return -1;
370         }
371 
372         r = read(clientfd, banner + p, sizeof(OVDB_SERVER_BANNER) - p);
373         if (r <= 0) {
374             if (r < 0 && errno == EINTR)
375                 continue;
376             syswarn("OVDB: rc: cant read");
377             close(clientfd);
378             clientfd = -1;
379             return -1;
380         }
381         p += r;
382     }
383 
384     if (memcmp(banner, OVDB_SERVER_BANNER, sizeof(OVDB_SERVER_BANNER))) {
385         warn("OVDB: rc: unknown reply from server");
386         close(clientfd);
387         clientfd = -1;
388         return -1;
389     }
390     return 0;
391 }
392 
393 static void
client_disconnect(void)394 client_disconnect(void)
395 {
396     struct rs_cmd rs;
397 
398     if (clientfd != -1) {
399         rs.what = CMD_QUIT;
400         csend(&rs, sizeof(rs));
401     }
402     clientfd = -1;
403 }
404 
405 
406 /*********** internal functions ***********/
407 
408 static bool
conf_bool_val(char * str,bool * value)409 conf_bool_val(char *str, bool *value)
410 {
411     if (strcasecmp(str, "on") == 0 || strcasecmp(str, "true") == 0
412         || strcasecmp(str, "yes") == 0) {
413         *value = true;
414         return true;
415     }
416     if (strcasecmp(str, "off") == 0 || strcasecmp(str, "false") == 0
417         || strcasecmp(str, "no") == 0) {
418         *value = false;
419         return true;
420     }
421     return false;
422 }
423 
424 static bool
conf_long_val(char * str,long * value)425 conf_long_val(char *str, long *value)
426 {
427     long v;
428 
429     errno = 0;
430     v = strtol(str, NULL, 10);
431     if (v == 0 && errno != 0) {
432         return false;
433     }
434     *value = v;
435     return true;
436 }
437 
438 void
read_ovdb_conf(void)439 read_ovdb_conf(void)
440 {
441     static int confread = 0;
442     int done = 0;
443     char *path;
444     CONFFILE *f;
445     CONFTOKEN *tok;
446     bool b;
447     long l;
448 
449     if (confread)
450         return;
451 
452     /* defaults */
453     ovdb_conf.home = innconf->pathoverview;
454     ovdb_conf.txn_nosync = 1;
455     ovdb_conf.numdbfiles = 32;
456     ovdb_conf.pagesize = 8192;
457     ovdb_conf.cachesize = 8000 * 1024;
458     ovdb_conf.ncache = 1;
459     ovdb_conf.minkey = 0;
460     ovdb_conf.maxlocks = 4000;
461     ovdb_conf.nocompact = 1;
462     ovdb_conf.readserver = 1;
463     ovdb_conf.numrsprocs = 5;
464     ovdb_conf.maxrsconn = 0;
465     ovdb_conf.useshm = 0;
466     ovdb_conf.shmkey = 6400;
467     ovdb_conf.compress = 0;
468 
469     path = concatpath(innconf->pathetc, _PATH_OVDBCONF);
470     f = CONFfopen(path);
471     free(path);
472 
473     if (f) {
474         while (!done && (tok = CONFgettoken(toks, f))) {
475             switch (tok->type) {
476             case OVDBtxn_nosync:
477                 tok = CONFgettoken(0, f);
478                 if (!tok) {
479                     done = 1;
480                     continue;
481                 }
482                 if (conf_bool_val(tok->name, &b)) {
483                     ovdb_conf.txn_nosync = b;
484                 }
485                 break;
486             case OVDBnumdbfiles:
487                 tok = CONFgettoken(0, f);
488                 if (!tok) {
489                     done = 1;
490                     continue;
491                 }
492                 if (conf_long_val(tok->name, &l) && l > 0) {
493                     ovdb_conf.numdbfiles = l;
494                 }
495                 break;
496             case OVDBpagesize:
497                 tok = CONFgettoken(0, f);
498                 if (!tok) {
499                     done = 1;
500                     continue;
501                 }
502                 if (conf_long_val(tok->name, &l) && l > 0) {
503                     ovdb_conf.pagesize = l;
504                 }
505                 break;
506             case OVDBcachesize:
507                 tok = CONFgettoken(0, f);
508                 if (!tok) {
509                     done = 1;
510                     continue;
511                 }
512                 if (conf_long_val(tok->name, &l) && l > 0) {
513                     ovdb_conf.cachesize = l * 1024;
514                 }
515                 break;
516             case OVDBncache:
517                 tok = CONFgettoken(0, f);
518                 if (!tok) {
519                     done = 1;
520                     continue;
521                 }
522                 if (conf_long_val(tok->name, &l) && l > 0) {
523                     ovdb_conf.ncache = l;
524                 }
525                 break;
526             case OVDBminkey:
527                 tok = CONFgettoken(0, f);
528                 if (!tok) {
529                     done = 1;
530                     continue;
531                 }
532                 if (conf_long_val(tok->name, &l) && l > 1) {
533                     ovdb_conf.minkey = l;
534                 }
535                 break;
536             case OVDBmaxlocks:
537                 tok = CONFgettoken(0, f);
538                 if (!tok) {
539                     done = 1;
540                     continue;
541                 }
542                 if (conf_long_val(tok->name, &l) && l > 0) {
543                     ovdb_conf.maxlocks = l;
544                 }
545                 break;
546             case OVDBnocompact:
547                 tok = CONFgettoken(0, f);
548                 if (!tok) {
549                     done = 1;
550                     continue;
551                 }
552                 if (conf_long_val(tok->name, &l) && l >= 0) {
553                     ovdb_conf.nocompact = l;
554                 }
555                 break;
556             case OVDBreadserver:
557                 tok = CONFgettoken(0, f);
558                 if (!tok) {
559                     done = 1;
560                     continue;
561                 }
562                 if (conf_bool_val(tok->name, &b)) {
563                     ovdb_conf.readserver = b;
564                 }
565                 break;
566             case OVDBnumrsprocs:
567                 tok = CONFgettoken(0, f);
568                 if (!tok) {
569                     done = 1;
570                     continue;
571                 }
572                 if (conf_long_val(tok->name, &l) && l > 0) {
573                     ovdb_conf.numrsprocs = l;
574                 }
575                 break;
576             case OVDBmaxrsconn:
577                 tok = CONFgettoken(0, f);
578                 if (!tok) {
579                     done = 1;
580                     continue;
581                 }
582                 if (conf_long_val(tok->name, &l) && l >= 0) {
583                     ovdb_conf.maxrsconn = l;
584                 }
585                 break;
586             case OVDBuseshm:
587                 tok = CONFgettoken(0, f);
588                 if (!tok) {
589                     done = 1;
590                     continue;
591                 }
592                 if (conf_bool_val(tok->name, &b)) {
593                     ovdb_conf.useshm = b;
594                 }
595                 break;
596             case OVDBshmkey:
597                 tok = CONFgettoken(0, f);
598                 if (!tok) {
599                     done = 1;
600                     continue;
601                 }
602                 if (conf_long_val(tok->name, &l) && l >= 0) {
603                     ovdb_conf.shmkey = l;
604                 }
605                 break;
606             case OVDBcompress:
607 #    ifdef HAVE_ZLIB
608                 tok = CONFgettoken(0, f);
609                 if (!tok) {
610                     done = 1;
611                     continue;
612                 }
613                 if (conf_bool_val(tok->name, &b)) {
614                     ovdb_conf.compress = b;
615                 }
616 #    endif
617                 break;
618             }
619         }
620         CONFfclose(f);
621     }
622 
623     /* If user did not specify minkey, choose one based on pagesize */
624     if (ovdb_conf.minkey == 0) {
625         if (ovdb_conf.compress) {
626             ovdb_conf.minkey = ovdb_conf.pagesize / 1500;
627         } else {
628             ovdb_conf.minkey = ovdb_conf.pagesize / 2600;
629         }
630         if (ovdb_conf.minkey < 2)
631             ovdb_conf.minkey = 2;
632     }
633 
634     confread = 1;
635 }
636 
637 /* Function that db will use to report errors */
638 static void
OVDBerror(const DB_ENV * dbenv UNUSED,const char * db_errpfx UNUSED,const char * buffer)639 OVDBerror(const DB_ENV *dbenv UNUSED, const char *db_errpfx UNUSED,
640           const char *buffer)
641 {
642     warn("OVDB: %s", buffer);
643 }
644 
645 static u_int32_t _db_flags = 0;
646 
647 static int
open_db_file(int which)648 open_db_file(int which)
649 {
650     int ret;
651     char name[16];
652     DB_TXN *tid;
653 
654     if (dbs[which] != NULL)
655         return 0;
656 
657     snprintf(name, sizeof(name), "ov%05d", which);
658 
659     ret = db_create(&(dbs[which]), OVDBenv, 0);
660     if (ret != 0)
661         return ret;
662 
663     if (ovdb_conf.minkey > 0)
664         (dbs[which])->set_bt_minkey(dbs[which], ovdb_conf.minkey);
665     if (ovdb_conf.pagesize > 0)
666         (dbs[which])->set_pagesize(dbs[which], ovdb_conf.pagesize);
667 
668     TXN_START_NORETRY(t_open_db_file, tid);
669     ret = (dbs[which])
670               ->open(dbs[which], tid, name, NULL, DB_BTREE, _db_flags, 0666);
671     if (ret == 0)
672         TXN_COMMIT(t_open_db_file, tid);
673     if (ret != 0) {
674         (dbs[which])->close(dbs[which], 0);
675         dbs[which] = NULL;
676         return ret;
677     }
678     return 0;
679 }
680 
681 static void
close_db_file(int which)682 close_db_file(int which)
683 {
684     if (which == -1 || dbs[which] == NULL)
685         return;
686 
687     dbs[which]->close(dbs[which], 0);
688     dbs[which] = NULL;
689 }
690 
691 static int
which_db(const char * group)692 which_db(const char *group)
693 {
694     HASH grouphash;
695     unsigned int i;
696 
697     grouphash = Hash(group, strlen(group));
698     memcpy(&i, &grouphash, sizeof(i));
699     return i % ovdb_conf.numdbfiles;
700 }
701 
702 static DB *
get_db_bynum(int which)703 get_db_bynum(int which)
704 {
705     int ret;
706     if (which >= ovdb_conf.numdbfiles)
707         return NULL;
708     if (oneatatime) {
709         if (which != current_db && current_db != -1)
710             close_db_file(current_db);
711 
712         ret = open_db_file(which);
713         if (ret != 0)
714             warn("OVDB: open_db_file failed: %s", db_strerror(ret));
715 
716         current_db = which;
717     }
718     return (dbs[which]);
719 }
720 
721 
722 int
ovdb_getgroupinfo(const char * group,struct groupinfo * gi,int ignoredeleted,DB_TXN * tid,int getflags)723 ovdb_getgroupinfo(const char *group, struct groupinfo *gi, int ignoredeleted,
724                   DB_TXN *tid, int getflags)
725 {
726     int ret;
727     DBT key, val;
728 
729     if (group == NULL) /* just in case */
730         return DB_NOTFOUND;
731 
732     memset(&key, 0, sizeof key);
733     memset(&val, 0, sizeof val);
734 
735     key.data = (char *) group;
736     key.size = strlen(group);
737     val.data = gi;
738     val.ulen = sizeof(struct groupinfo);
739     val.flags = DB_DBT_USERMEM;
740 
741     ret = groupinfo->get(groupinfo, tid, &key, &val, getflags);
742     if (ret != 0)
743         return ret;
744 
745     if (val.size != sizeof(struct groupinfo)) {
746         warn("OVDB: wrong size for %s groupinfo (%u)", group, val.size);
747         return DB_NOTFOUND;
748     }
749 
750     if (ignoredeleted && (gi->status & GROUPINFO_DELETED))
751         return DB_NOTFOUND;
752 
753     return 0;
754 }
755 
756 #    define GROUPID_MAX_FREELIST 10240
757 #    define GROUPID_MIN_FREELIST 100
758 
759 /* allocate a new group ID and return in gno */
760 /* must be used in a transaction */
761 static int
groupid_new(group_id_t * gno,DB_TXN * tid)762 groupid_new(group_id_t *gno, DB_TXN *tid)
763 {
764     DBT key, val;
765     int ret, n;
766     group_id_t newgno, *freelist, one;
767 
768     memset(&key, 0, sizeof key);
769     memset(&val, 0, sizeof val);
770 
771     key.data = (char *) "!groupid_freelist";
772     key.size = sizeof("!groupid_freelist");
773 
774     ret = groupinfo->get(groupinfo, tid, &key, &val, DB_RMW);
775     if (ret != 0) {
776         if (ret == DB_NOTFOUND) {
777             val.size = sizeof(group_id_t);
778             val.data = &one;
779             one = 1;
780         } else {
781             return ret;
782         }
783     }
784 
785     if (val.size % sizeof(group_id_t)) {
786         warn("OVDB: invalid size (%d) for !groupid_freelist", val.size);
787         return EINVAL;
788     }
789 
790     n = val.size / sizeof(group_id_t);
791     freelist = xmalloc(n * sizeof(group_id_t));
792     memcpy(freelist, val.data, val.size);
793     if (n <= GROUPID_MIN_FREELIST) {
794         newgno = freelist[n - 1];
795         (freelist[n - 1])++;
796         val.data = freelist;
797     } else {
798         newgno = freelist[0];
799         val.data = &(freelist[1]);
800         val.size -= sizeof(group_id_t);
801     }
802 
803     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
804     if (ret != 0) {
805         free(freelist);
806         return ret;
807     }
808 
809     free(freelist);
810     *gno = newgno;
811     return 0;
812 }
813 
814 /* mark given group ID as "unused" */
815 /* must be used in a transaction */
816 static int
groupid_free(group_id_t gno,DB_TXN * tid)817 groupid_free(group_id_t gno, DB_TXN *tid)
818 {
819     DBT key, val;
820     int ret, n, i;
821     group_id_t *freelist;
822 
823     memset(&key, 0, sizeof key);
824     memset(&val, 0, sizeof val);
825 
826     key.data = (char *) "!groupid_freelist";
827     key.size = sizeof("!groupid_freelist");
828 
829     ret = groupinfo->get(groupinfo, tid, &key, &val, DB_RMW);
830     if (ret != 0) {
831         return ret;
832     }
833 
834     if (val.size % sizeof(group_id_t)) {
835         warn("OVDB: invalid size (%d) for !groupid_freelist", val.size);
836         return EINVAL;
837     }
838 
839     n = val.size / sizeof(group_id_t);
840     if (n > GROUPID_MAX_FREELIST)
841         return 0;
842     freelist = xmalloc((n + 1) * sizeof(group_id_t));
843     memcpy(freelist, val.data, val.size);
844 
845     if (gno >= freelist[n - 1]) { /* shouldn't happen */
846         free(freelist);
847         return 0;
848     }
849     for (i = 0; i < n - 1; i++) {
850         if (gno == freelist[i]) { /* already on freelist */
851             free(freelist);
852             return 0;
853         }
854     }
855 
856     freelist[n] = freelist[n - 1];
857     freelist[n - 1] = gno;
858     val.data = freelist;
859     val.size += sizeof(group_id_t);
860 
861     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
862 
863     free(freelist);
864     return ret;
865 }
866 
867 /* Must be called outside of a transaction because it makes its own
868    transactions */
869 static int
delete_all_records(int whichdb,group_id_t gno)870 delete_all_records(int whichdb, group_id_t gno)
871 {
872     DB *db;
873     DBC *dbcursor;
874     DBT key, val;
875     struct datakey dk;
876     int count;
877     int ret = 0;
878     DB_TXN *tid;
879 
880     memset(&key, 0, sizeof key);
881     memset(&val, 0, sizeof val);
882     memset(&dk, 0, sizeof dk);
883 
884     db = get_db_bynum(whichdb);
885     if (db == NULL)
886         return DB_NOTFOUND;
887 
888     dk.groupnum = gno;
889     dk.artnum = 0;
890 
891     while (1) {
892         TXN_START(t_del, tid);
893 
894         /* get a cursor to traverse the ov records and delete them */
895         ret = db->cursor(db, tid, &dbcursor, 0);
896         switch (ret) {
897         case 0:
898             break;
899         case TRYAGAIN:
900             TXN_RETRY(t_del, tid);
901         default:
902             TXN_ABORT(t_del, tid);
903             warn("OVDB: delete_all_records: db->cursor: %s", db_strerror(ret));
904             return ret;
905         }
906 
907         key.data = &dk;
908         key.size = sizeof dk;
909         val.flags = DB_DBT_PARTIAL;
910 
911         for (count = 0; count < DELETE_TXN_SIZE; count++) {
912             ret = dbcursor->c_get(dbcursor, &key, &val,
913                                   count ? DB_NEXT : DB_SET_RANGE);
914             switch (ret) {
915             case 0:
916                 break;
917             case DB_NOTFOUND:
918                 dbcursor->c_close(dbcursor);
919                 TXN_COMMIT(t_del, tid);
920                 return 0;
921             case TRYAGAIN:
922                 dbcursor->c_close(dbcursor);
923                 TXN_RETRY(t_del, tid);
924             default:
925                 warn("OVDB: delete_all_records: DBcursor->c_get: %s",
926                      db_strerror(ret));
927                 dbcursor->c_close(dbcursor);
928                 TXN_ABORT(t_del, tid);
929                 return ret;
930             }
931 
932             if (key.size == sizeof dk && memcmp(key.data, &gno, sizeof gno)) {
933                 break;
934             }
935 
936             ret = dbcursor->c_del(dbcursor, 0);
937             switch (ret) {
938             case 0:
939             case DB_KEYEMPTY:
940                 break;
941             case TRYAGAIN:
942                 dbcursor->c_close(dbcursor);
943                 TXN_RETRY(t_del, tid);
944             default:
945                 warn("OVDB: delete_all_records: DBcursor->c_del: %s",
946                      db_strerror(ret));
947                 dbcursor->c_close(dbcursor);
948                 TXN_ABORT(t_del, tid);
949                 return ret;
950             }
951         }
952         dbcursor->c_close(dbcursor);
953         TXN_COMMIT(t_del, tid);
954         if (count < DELETE_TXN_SIZE) {
955             break;
956         }
957     }
958     return 0;
959 }
960 
961 /* Make a temporary groupinfo key using the given db number and group ID.
962    Must be called in a transaction */
963 static int
mk_temp_groupinfo(int whichdb,group_id_t gno,DB_TXN * tid)964 mk_temp_groupinfo(int whichdb, group_id_t gno, DB_TXN *tid)
965 {
966     char keystr[1 + sizeof gno];
967     DBT key, val;
968     struct groupinfo gi;
969     int ret;
970 
971     memset(&key, 0, sizeof key);
972     memset(&val, 0, sizeof val);
973     memset(&gi, 0, sizeof gi);
974 
975     keystr[0] = 0;
976     memcpy(keystr + 1, &gno, sizeof gno);
977 
978     gi.current_db = whichdb;
979     gi.current_gid = gno;
980     gi.status = GROUPINFO_DELETED;
981 
982     key.data = keystr;
983     key.size = sizeof keystr;
984     val.data = &gi;
985     val.size = sizeof gi;
986 
987     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
988     switch (ret) {
989     case 0:
990         break;
991     default:
992         warn("OVDB: mk_temp_groupinfo: groupinfo->put: %s", db_strerror(ret));
993         goto fallthrough;
994     case TRYAGAIN:
995     fallthrough:
996         return ret;
997     }
998 
999     return 0;
1000 }
1001 
1002 /* Delete a temporary groupinfo key created by mk_temp_groupid, then
1003    frees the group id.
1004    Must NOT be called within a transaction. */
1005 static int
rm_temp_groupinfo(group_id_t gno)1006 rm_temp_groupinfo(group_id_t gno)
1007 {
1008     char keystr[1 + sizeof gno];
1009     DB_TXN *tid;
1010     DBT key;
1011     int ret = 0;
1012 
1013     memset(&key, 0, sizeof key);
1014 
1015     keystr[0] = 0;
1016     memcpy(keystr + 1, &gno, sizeof gno);
1017 
1018     key.data = keystr;
1019     key.size = sizeof keystr;
1020 
1021     TXN_START(t_tmp, tid);
1022 
1023     ret = groupinfo->del(groupinfo, tid, &key, 0);
1024     switch (ret) {
1025     case 0:
1026     case DB_NOTFOUND:
1027         break;
1028     case TRYAGAIN:
1029         TXN_RETRY(t_tmp, tid);
1030     default:
1031         TXN_ABORT(t_tmp, tid);
1032         warn("OVDB: rm_temp_groupinfo: groupinfo->del: %s", db_strerror(ret));
1033         return ret;
1034     }
1035 
1036     switch (groupid_free(gno, tid)) {
1037     case 0:
1038         break;
1039     case TRYAGAIN:
1040         TXN_RETRY(t_tmp, tid);
1041     default:
1042         TXN_ABORT(t_tmp, tid);
1043         warn("OVDB: rm_temp_groupinfo: groupid_free: %s", db_strerror(ret));
1044         return ret;
1045     }
1046 
1047     TXN_COMMIT(t_tmp, tid);
1048     return 0;
1049 }
1050 
1051 /* This function deletes overview records for deleted or forgotton groups */
1052 /* argument: 0 = process deleted groups   1 = process forgotton groups */
1053 static bool
delete_old_stuff(int forgotton)1054 delete_old_stuff(int forgotton)
1055 {
1056     DBT key, val;
1057     DBC *cursor;
1058     DB_TXN *tid;
1059     struct groupinfo gi;
1060     char **dellist = NULL;
1061     size_t *dellistsz = NULL;
1062     int listlen, listcount = 0;
1063     int i, ret = 0;
1064 
1065     TXN_START(t_dellist, tid);
1066     if (dellist != NULL) {
1067         for (i = 0; i < listcount; ++i)
1068             free(dellist[i]);
1069         free(dellist);
1070     }
1071     if (dellistsz != NULL)
1072         free(dellistsz);
1073     listlen = 20;
1074     listcount = 0;
1075     dellist = xmalloc(listlen * sizeof(char *));
1076     dellistsz = xmalloc(listlen * sizeof(size_t));
1077 
1078     memset(&key, 0, sizeof key);
1079     memset(&val, 0, sizeof val);
1080 
1081     val.data = &gi;
1082     val.ulen = val.dlen = sizeof gi;
1083     val.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL;
1084 
1085     /* get a cursor to traverse all of the groupinfo records */
1086     ret = groupinfo->cursor(groupinfo, tid, &cursor, 0);
1087     if (ret != 0) {
1088         warn("OVDB: delete_old_stuff: groupinfo->cursor: %s",
1089              db_strerror(ret));
1090         free(dellist);
1091         free(dellistsz);
1092         return false;
1093     }
1094 
1095     while ((ret = cursor->c_get(cursor, &key, &val, DB_NEXT)) == 0) {
1096         if (key.size == sizeof("!groupid_freelist")
1097             && !strcmp("!groupid_freelist", key.data))
1098             continue;
1099         if (val.size != sizeof(struct groupinfo)) {
1100             warn("OVDB: delete_old_stuff: wrong size for groupinfo record");
1101             continue;
1102         }
1103         if ((!forgotton && (gi.status & GROUPINFO_DELETED))
1104             || (forgotton && (gi.expired < eo_start))) {
1105             dellist[listcount] = xmalloc(key.size);
1106             memcpy(dellist[listcount], key.data, key.size);
1107             dellistsz[listcount] = key.size;
1108             listcount++;
1109             if (listcount >= listlen) {
1110                 listlen += 20;
1111                 dellist = xrealloc(dellist, listlen * sizeof(char *));
1112                 dellistsz = xrealloc(dellistsz, listlen * sizeof(size_t));
1113             }
1114         }
1115     }
1116     cursor->c_close(cursor);
1117     switch (ret) {
1118     case 0:
1119     case DB_NOTFOUND:
1120         TXN_COMMIT(t_dellist, tid);
1121         break;
1122     case TRYAGAIN:
1123         TXN_RETRY(t_dellist, tid);
1124     default:
1125         TXN_ABORT(t_dellist, tid);
1126         warn("OVDB: delete_old_stuff: cursor->c_get: %s", db_strerror(ret));
1127         for (i = 0; i < listcount; ++i)
1128             free(dellist[i]);
1129         free(dellist);
1130         free(dellistsz);
1131         return false;
1132     }
1133 
1134     for (i = 0; i < listcount; i++) {
1135         TXN_START(t_dos, tid);
1136 
1137         /* Can't use ovdb_getgroupinfo here */
1138         key.data = dellist[i];
1139         key.size = dellistsz[i];
1140         val.data = &gi;
1141         val.ulen = sizeof(struct groupinfo);
1142         val.flags = DB_DBT_USERMEM;
1143 
1144         ret = groupinfo->get(groupinfo, tid, &key, &val, 0);
1145 
1146         switch (ret) {
1147         case 0:
1148             break;
1149         case TRYAGAIN:
1150             TXN_RETRY(t_dos, tid);
1151         case DB_NOTFOUND:
1152             TXN_ABORT(t_dos, tid);
1153             continue;
1154         default:
1155             warn("OVDB: delete_old_stuff: groupinfo->get: %s",
1156                  db_strerror(ret));
1157             TXN_ABORT(t_dos, tid);
1158             continue;
1159         }
1160         if (val.size != sizeof(struct groupinfo)) {
1161             TXN_ABORT(t_dos, tid);
1162             continue;
1163         }
1164 
1165         /* This if() is true if this ISN'T a key created by mk_temp_groupinfo
1166          */
1167         if (*((char *) key.data) != 0 || key.size != 1 + sizeof(group_id_t)) {
1168 
1169             switch (mk_temp_groupinfo(gi.current_db, gi.current_gid, tid)) {
1170             case 0:
1171                 break;
1172             case TRYAGAIN:
1173                 TXN_RETRY(t_dos, tid);
1174             default:
1175                 TXN_ABORT(t_dos, tid);
1176                 continue;
1177             }
1178             if (gi.status & GROUPINFO_MOVING) {
1179                 switch (mk_temp_groupinfo(gi.new_db, gi.new_gid, tid)) {
1180                 case 0:
1181                     break;
1182                 case TRYAGAIN:
1183                     TXN_RETRY(t_dos, tid);
1184                 default:
1185                     TXN_ABORT(t_dos, tid);
1186                     continue;
1187                 }
1188             }
1189 
1190             /* delete the corresponding groupaliases record (if exists) */
1191             switch (groupaliases->del(groupaliases, tid, &key, 0)) {
1192             case 0:
1193             case DB_NOTFOUND:
1194                 break;
1195             case TRYAGAIN:
1196                 TXN_RETRY(t_dos, tid);
1197             default:
1198                 TXN_ABORT(t_dos, tid);
1199                 continue;
1200             }
1201 
1202             switch (groupinfo->del(groupinfo, tid, &key, 0)) {
1203             case 0:
1204             case DB_NOTFOUND:
1205                 break;
1206             case TRYAGAIN:
1207                 TXN_RETRY(t_dos, tid);
1208             default:
1209                 TXN_ABORT(t_dos, tid);
1210                 continue;
1211             }
1212         }
1213 
1214         TXN_COMMIT(t_dos, tid);
1215 
1216         if (delete_all_records(gi.current_db, gi.current_gid) == 0) {
1217             rm_temp_groupinfo(gi.current_gid);
1218         }
1219         if (gi.status & GROUPINFO_MOVING) {
1220             if (delete_all_records(gi.new_db, gi.new_gid) == 0) {
1221                 rm_temp_groupinfo(gi.new_gid);
1222             }
1223         }
1224     }
1225 
1226     for (i = 0; i < listcount; i++)
1227         free(dellist[i]);
1228     free(dellist);
1229     free(dellistsz);
1230     return true;
1231 }
1232 
1233 static int
count_records(struct groupinfo * gi)1234 count_records(struct groupinfo *gi)
1235 {
1236     int ret;
1237     DB *db;
1238     DBC *cursor;
1239     DBT key, val;
1240     struct datakey dk;
1241     u_int32_t artnum, newlow = 0;
1242 
1243     memset(&key, 0, sizeof key);
1244     memset(&val, 0, sizeof val);
1245     memset(&dk, 0, sizeof dk);
1246 
1247     db = get_db_bynum(gi->current_db);
1248     if (db == NULL)
1249         return DB_NOTFOUND;
1250 
1251     dk.groupnum = gi->current_gid;
1252     dk.artnum = 0;
1253     key.data = &dk;
1254     key.size = key.ulen = sizeof dk;
1255     key.flags = DB_DBT_USERMEM;
1256     val.flags = DB_DBT_PARTIAL;
1257 
1258     gi->count = 0;
1259 
1260     ret = db->cursor(db, NULL, &cursor, 0);
1261     if (ret)
1262         return ret;
1263 
1264     ret = cursor->c_get(cursor, &key, &val, DB_SET_RANGE);
1265     switch (ret) {
1266     case 0:
1267     case DB_NOTFOUND:
1268         break;
1269     default:
1270         cursor->c_close(cursor);
1271         return ret;
1272     }
1273     while (ret == 0 && key.size == sizeof(dk)
1274            && dk.groupnum == gi->current_gid) {
1275         artnum = ntohl(dk.artnum);
1276         if (newlow == 0 || newlow > artnum)
1277             newlow = artnum;
1278         if (artnum > gi->high)
1279             gi->high = artnum;
1280         gi->count++;
1281 
1282         ret = cursor->c_get(cursor, &key, &val, DB_NEXT);
1283     }
1284     cursor->c_close(cursor);
1285     if (gi->count == 0)
1286         gi->low = gi->high + 1;
1287     else
1288         gi->low = newlow;
1289 
1290     if (ret == DB_NOTFOUND)
1291         return 0;
1292     return ret;
1293 }
1294 
1295 
1296 /*
1297  * Locking: OVopen() calls ovdb_getlock(OVDB_LOCK_NORMAL).  This
1298  * aquires a read (shared) lock on the lockfile.  Multiple processes
1299  * can have this file locked at the same time.  That way, if there
1300  * are any processes that are using the database, the lock file will
1301  * have one or more shared locks on it.
1302  *
1303  * ovdb_init, when starting, calls ovdb_getlock(OVDB_LOCK_EXCLUSIVE).
1304  * This tries to get a write (exclusive) lock, which will fail if
1305  * anyone has a shared lock.  This way, ovdb_init can tell if there
1306  * are any processes using the database.  If not, and the excl. lock
1307  * succeeds, ovdb_init is free to do DB_RUNRECOVER.
1308  *
1309  * ovdb_getlock() in the "normal" lock mode calls ovdb_check_monitor,
1310  * which looks for the OVDB_MONITOR_PIDFILE.  If said file does not
1311  * exist, or the PID in it does not exist, it will fail.  This will
1312  * prevent OVopen() from opening the database if ovdb_monitor is not running.
1313  *
1314  * The OVDB_LOCK_ADMIN mode is used by ovdb_monitor to get a shared lock
1315  * without testing the pidfile.
1316  */
1317 static int lockfd = -1;
1318 bool
ovdb_getlock(int mode)1319 ovdb_getlock(int mode)
1320 {
1321     if (lockfd == -1) {
1322         char *lockfn = concatpath(innconf->pathrun, OVDB_LOCKFN);
1323         lockfd =
1324             open(lockfn, mode == OVDB_LOCK_NORMAL ? O_RDWR : O_CREAT | O_RDWR,
1325                  0660);
1326         if (lockfd == -1) {
1327             if (errno == ENOENT)
1328                 warn("OVDB: can not open database unless ovdb_monitor is"
1329                      " running; %s not found",
1330                      lockfn);
1331             free(lockfn);
1332             return false;
1333         }
1334         fdflag_close_exec(lockfd, true);
1335         free(lockfn);
1336     } else
1337         return true;
1338 
1339     if (mode == OVDB_LOCK_NORMAL) {
1340         if (!ovdb_check_pidfile(OVDB_MONITOR_PIDFILE)) {
1341             warn("OVDB: can not open database unless ovdb_monitor is"
1342                  " running");
1343             return false;
1344         }
1345     }
1346     if (mode == OVDB_LOCK_EXCLUSIVE) {
1347         if (!inn_lock_file(lockfd, INN_LOCK_WRITE, false)) {
1348             close(lockfd);
1349             lockfd = -1;
1350             return false;
1351         }
1352         return true;
1353     } else {
1354         if (!inn_lock_file(lockfd, INN_LOCK_READ, false)) {
1355             close(lockfd);
1356             lockfd = -1;
1357             return false;
1358         }
1359         return true;
1360     }
1361 }
1362 
1363 bool
ovdb_releaselock(void)1364 ovdb_releaselock(void)
1365 {
1366     bool r;
1367     if (lockfd == -1)
1368         return true;
1369     r = inn_lock_file(lockfd, INN_LOCK_UNLOCK, false);
1370     close(lockfd);
1371     lockfd = -1;
1372     return r;
1373 }
1374 
1375 bool
ovdb_check_pidfile(const char * file)1376 ovdb_check_pidfile(const char *file)
1377 {
1378     int f, pid;
1379     char buf[SMBUF];
1380     char *pidfn = concatpath(innconf->pathrun, file);
1381 
1382     f = open(pidfn, O_RDONLY);
1383     if (f == -1) {
1384         if (errno != ENOENT)
1385             syswarn("OVDB: can't open %s", pidfn);
1386         free(pidfn);
1387         return false;
1388     }
1389     memset(buf, 0, SMBUF);
1390     if (read(f, buf, SMBUF - 1) < 0) {
1391         syswarn("OVDB: can't read from %s", pidfn);
1392         free(pidfn);
1393         close(f);
1394         return false;
1395     }
1396     close(f);
1397     free(pidfn);
1398     pid = atoi(buf);
1399     if (pid <= 1) {
1400         return false;
1401     }
1402     if (kill(pid, 0) < 0 && errno == ESRCH) {
1403         return false;
1404     }
1405     return true;
1406 }
1407 
1408 /* Make sure the effective uid is that of the runasuser user. */
1409 bool
ovdb_check_user(void)1410 ovdb_check_user(void)
1411 {
1412     static int result = -1;
1413 
1414     if (result == -1) {
1415         int rv;
1416         uid_t uid;
1417 
1418         rv = get_news_uid_gid(&uid, false, false);
1419 
1420         if (rv != 0) {
1421             syswarn("OVDB: can't resolve runasuser user to a UID");
1422             return false;
1423         }
1424         result = (uid == geteuid());
1425     }
1426     return result;
1427 }
1428 
1429 static int
check_version(void)1430 check_version(void)
1431 {
1432     int ret;
1433     DB *vdb;
1434     DBT key, val;
1435     u_int32_t dv;
1436 
1437     /* open version db */
1438     ret = db_create(&vdb, OVDBenv, 0);
1439     if (ret != 0) {
1440         warn("OVDB: open: db_create: %s", db_strerror(ret));
1441         return ret;
1442     }
1443     ret = vdb->open(vdb, NULL, "version", NULL, DB_BTREE, _db_flags, 0666);
1444     if (ret != 0) {
1445         vdb->close(vdb, 0);
1446         warn("OVDB: open: version->open: %s", db_strerror(ret));
1447         return ret;
1448     }
1449     memset(&key, 0, sizeof key);
1450     memset(&val, 0, sizeof val);
1451     key.data = (char *) "dataversion";
1452     key.size = sizeof("dataversion");
1453     ret = vdb->get(vdb, NULL, &key, &val, 0);
1454     if (ret != 0) {
1455         if (ret != DB_NOTFOUND) {
1456             warn("OVDB: open: can't retrieve version: %s", db_strerror(ret));
1457             vdb->close(vdb, 0);
1458             return ret;
1459         }
1460     }
1461     if (ret == DB_NOTFOUND || val.size != sizeof dv) {
1462         dv = ovdb_conf.compress ? DATA_VERSION_COMPRESS : DATA_VERSION;
1463         if (!(OVDBmode & OV_WRITE)) {
1464             vdb->close(vdb, 0);
1465             return EACCES;
1466         }
1467         val.data = &dv;
1468         val.size = sizeof dv;
1469         ret = vdb->put(vdb, NULL, &key, &val, 0);
1470         if (ret != 0) {
1471             warn("OVDB: open: can't store version: %s", db_strerror(ret));
1472             vdb->close(vdb, 0);
1473             return ret;
1474         }
1475     } else
1476         memcpy(&dv, val.data, sizeof dv);
1477 
1478     if (dv > DATA_VERSION_COMPRESS) {
1479         warn("OVDB: can't open database: unknown version %d", dv);
1480         vdb->close(vdb, 0);
1481         return EINVAL;
1482     }
1483     if (dv < DATA_VERSION) {
1484         warn("OVDB: database is an old version, please run ovdb_init");
1485         vdb->close(vdb, 0);
1486         return EINVAL;
1487     }
1488 #    ifndef HAVE_ZLIB
1489     if (dv == DATA_VERSION_COMPRESS) {
1490         warn("OVDB: database is compressed but INN was not built with zlib");
1491         vdb->close(vdb, 0);
1492         return EINVAL;
1493     }
1494 #    endif
1495     if (ovdb_conf.compress && dv == DATA_VERSION && (OVDBmode & OV_WRITE)) {
1496         /* "Upgrade" the database to indicate there may be compressed
1497            records */
1498         dv = DATA_VERSION_COMPRESS;
1499         val.data = &dv;
1500         val.size = sizeof dv;
1501 
1502         ret = vdb->put(vdb, NULL, &key, &val, 0);
1503         if (ret != 0) {
1504             warn("OVDB: open: can't store version: %s", db_strerror(ret));
1505             vdb->close(vdb, 0);
1506             return ret;
1507         }
1508     }
1509     ovdb_data_ver = dv;
1510 
1511     /* The version db also stores some config values, which will override the
1512        corresponding ovdb.conf setting. */
1513 
1514     key.data = (char *) "numdbfiles";
1515     key.size = sizeof("numdbfiles");
1516     ret = vdb->get(vdb, NULL, &key, &val, 0);
1517     if (ret != 0) {
1518         if (ret != DB_NOTFOUND) {
1519             warn("OVDB: open: can't retrieve numdbfiles: %s",
1520                  db_strerror(ret));
1521             vdb->close(vdb, 0);
1522             return ret;
1523         }
1524     }
1525     if (ret == DB_NOTFOUND || val.size != sizeof(ovdb_conf.numdbfiles)) {
1526         if (!(OVDBmode & OV_WRITE)) {
1527             vdb->close(vdb, 0);
1528             return EACCES;
1529         }
1530         val.data = &(ovdb_conf.numdbfiles);
1531         val.size = sizeof(ovdb_conf.numdbfiles);
1532         ret = vdb->put(vdb, NULL, &key, &val, 0);
1533         if (ret != 0) {
1534             warn("OVDB: open: can't store numdbfiles: %s", db_strerror(ret));
1535             vdb->close(vdb, 0);
1536             return ret;
1537         }
1538     } else {
1539         memcpy(&(ovdb_conf.numdbfiles), val.data,
1540                sizeof(ovdb_conf.numdbfiles));
1541     }
1542 
1543     vdb->close(vdb, 0);
1544     return 0;
1545 }
1546 
1547 
1548 int
ovdb_open_berkeleydb(int mode,int flags)1549 ovdb_open_berkeleydb(int mode, int flags)
1550 {
1551     int ret;
1552     u_int32_t ai_flags =
1553         DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN;
1554 
1555     OVDBmode = mode;
1556     read_ovdb_conf();
1557 
1558     if (OVDBenv != NULL)
1559         return 0; /* already opened */
1560 
1561     if (OVDBmode & OV_WRITE) {
1562         _db_flags |= DB_CREATE;
1563         ai_flags |= DB_CREATE;
1564     } else {
1565         _db_flags |= DB_RDONLY;
1566     }
1567     if (flags & OVDB_RECOVER)
1568         ai_flags |= DB_RECOVER;
1569 
1570     ret = db_env_create(&OVDBenv, 0);
1571     if (ret != 0) {
1572         warn("OVDB: db_env_create: %s", db_strerror(ret));
1573         return ret;
1574     }
1575 
1576     if ((flags & (OVDB_UPGRADE | OVDB_RECOVER))
1577         == (OVDB_UPGRADE | OVDB_RECOVER))
1578         ai_flags |= DB_PRIVATE;
1579     if (!(ai_flags & DB_PRIVATE)) {
1580         if (ovdb_conf.useshm)
1581             ai_flags |= DB_SYSTEM_MEM;
1582         OVDBenv->set_shm_key(OVDBenv, ovdb_conf.shmkey);
1583     }
1584 
1585     OVDBenv->set_errcall(OVDBenv, OVDBerror);
1586     OVDBenv->set_cachesize(OVDBenv, 0, ovdb_conf.cachesize, ovdb_conf.ncache);
1587     OVDBenv->set_lk_max_locks(OVDBenv, ovdb_conf.maxlocks);
1588     OVDBenv->set_lk_max_lockers(OVDBenv, ovdb_conf.maxlocks);
1589     OVDBenv->set_lk_max_objects(OVDBenv, ovdb_conf.maxlocks);
1590 
1591     if (ovdb_conf.txn_nosync)
1592         OVDBenv->set_flags(OVDBenv, DB_TXN_NOSYNC, 1);
1593 
1594     if ((flags & (OVDB_UPGRADE | OVDB_RECOVER)) != OVDB_UPGRADE) {
1595         ret = OVDBenv->open(OVDBenv, ovdb_conf.home, ai_flags, 0666);
1596         if (ret != 0) {
1597             OVDBenv->close(OVDBenv, 0);
1598             OVDBenv = NULL;
1599             warn("OVDB: OVDBenv->open: %s", db_strerror(ret));
1600             return ret;
1601         }
1602     }
1603 
1604     return 0;
1605 }
1606 
1607 bool
ovdb_open(int mode)1608 ovdb_open(int mode)
1609 {
1610     int i, ret;
1611     DB_TXN *tid;
1612 
1613     if (OVDBenv != NULL || clientmode) {
1614         warn("OVDB: ovdb_open called more than once");
1615         return false;
1616     }
1617 
1618     read_ovdb_conf();
1619     if (ovdb_conf.readserver && mode == OV_READ)
1620         clientmode = 1;
1621 
1622     if (mode & OVDB_SERVER)
1623         clientmode = 0;
1624 
1625     if (clientmode) {
1626         if (client_connect() == 0)
1627             return true;
1628         clientmode = 0;
1629     }
1630     if (!ovdb_check_user()) {
1631         warn("OVDB: must be running as runasuser user to access overview");
1632         return false;
1633     }
1634     if (!ovdb_getlock(OVDB_LOCK_NORMAL)) {
1635         syswarn("OVDB: ovdb_getlock failed");
1636         return false;
1637     }
1638 
1639     if (ovdb_open_berkeleydb(mode, 0) != 0)
1640         return false;
1641 
1642     if (check_version() != 0)
1643         return false;
1644 
1645     if (mode & OV_WRITE || mode & OVDB_SERVER) {
1646         oneatatime = 0;
1647     } else {
1648         oneatatime = 1;
1649     }
1650 
1651     dbs = xcalloc(ovdb_conf.numdbfiles, sizeof(DB *));
1652 
1653     if (!oneatatime) {
1654         for (i = 0; i < ovdb_conf.numdbfiles; i++) {
1655             ret = open_db_file(i);
1656             if (ret != 0) {
1657                 warn("OVDB: open_db_file failed: %s", db_strerror(ret));
1658                 return false;
1659             }
1660         }
1661     }
1662 
1663     ret = db_create(&groupinfo, OVDBenv, 0);
1664     if (ret != 0) {
1665         warn("OVDB: open: db_create: %s", db_strerror(ret));
1666         return false;
1667     }
1668     TXN_START_NORETRY(t_open_groupinfo, tid);
1669     ret = groupinfo->open(groupinfo, tid, "groupinfo", NULL, DB_BTREE,
1670                           _db_flags, 0666);
1671     if (ret == 0)
1672         TXN_COMMIT(t_open_groupinfo, tid);
1673     if (ret != 0) {
1674         groupinfo->close(groupinfo, 0);
1675         warn("OVDB: open: groupinfo->open: %s", db_strerror(ret));
1676         return false;
1677     }
1678     ret = db_create(&groupaliases, OVDBenv, 0);
1679     if (ret != 0) {
1680         warn("OVDB: open: db_create: %s", db_strerror(ret));
1681         return false;
1682     }
1683     TXN_START_NORETRY(t_open_groupaliases, tid);
1684     ret = groupaliases->open(groupaliases, tid, "groupaliases", NULL, DB_HASH,
1685                              _db_flags, 0666);
1686     if (ret == 0)
1687         TXN_COMMIT(t_open_groupaliases, tid);
1688     if (ret != 0) {
1689         groupaliases->close(groupaliases, 0);
1690         warn("OVDB: open: groupaliases->open: %s", db_strerror(ret));
1691         return false;
1692     }
1693 
1694     Cutofflow = false;
1695     return true;
1696 }
1697 
1698 
1699 bool
ovdb_groupstats(const char * group,int * lo,int * hi,int * count,int * flag)1700 ovdb_groupstats(const char *group, int *lo, int *hi, int *count, int *flag)
1701 {
1702     int ret;
1703     struct groupinfo gi;
1704 
1705     if (clientmode) {
1706         struct rs_cmd rs;
1707         struct rs_groupstats repl;
1708 
1709         rs.what = CMD_GROUPSTATS;
1710         rs.grouplen = strlen(group) + 1;
1711 
1712         if (csend(&rs, sizeof(rs)) < 0)
1713             return false;
1714         if (csend(group, rs.grouplen) < 0)
1715             return false;
1716         crecv(&repl, sizeof(repl));
1717 
1718         if (repl.status != CMD_GROUPSTATS)
1719             return false;
1720 
1721         /* we don't use the alias yet, but the OV API will be extended
1722            at some point so that the alias is returned also */
1723         if (repl.aliaslen != 0) {
1724             char *buf = xmalloc(repl.aliaslen);
1725             crecv(buf, repl.aliaslen);
1726             free(buf);
1727         }
1728 
1729         if (lo)
1730             *lo = repl.lo;
1731         if (hi)
1732             *hi = repl.hi;
1733         if (count)
1734             *count = repl.count;
1735         if (flag)
1736             *flag = repl.flag;
1737         return true;
1738     }
1739 
1740     ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
1741     switch (ret) {
1742     case 0:
1743         break;
1744     case DB_NOTFOUND:
1745         return false;
1746     default:
1747         warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
1748         return false;
1749     }
1750 
1751     if (lo != NULL)
1752         *lo = gi.low;
1753     if (hi != NULL)
1754         *hi = gi.high;
1755     if (count != NULL)
1756         *count = gi.count;
1757     if (flag != NULL)
1758         *flag = gi.flag;
1759     return true;
1760 }
1761 
1762 bool
ovdb_groupadd(const char * group,ARTNUM lo,ARTNUM hi,char * flag)1763 ovdb_groupadd(const char *group, ARTNUM lo, ARTNUM hi, char *flag)
1764 {
1765     DBT key, val;
1766     struct groupinfo gi;
1767     DB_TXN *tid;
1768     int ret = 0;
1769     int new;
1770 
1771     memset(&key, 0, sizeof key);
1772     memset(&val, 0, sizeof val);
1773 
1774     TXN_START(t_groupadd, tid);
1775 
1776     if (tid == NULL)
1777         return false;
1778 
1779     new = 0;
1780     ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW);
1781     switch (ret) {
1782     case DB_NOTFOUND:
1783         new = 1;
1784         goto fallthrough0;
1785     case 0:
1786     fallthrough0:
1787         break;
1788     case TRYAGAIN:
1789         TXN_RETRY(t_groupadd, tid);
1790     default:
1791         TXN_ABORT(t_groupadd, tid);
1792         warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
1793         return false;
1794     }
1795 
1796     if (!new && (gi.status & GROUPINFO_DELETED)
1797         && !(gi.status & GROUPINFO_EXPIRING)
1798         && !(gi.status & GROUPINFO_MOVING)) {
1799         int s, c = 0;
1800         char g[MED_BUFFER];
1801 
1802         strlcpy(g, group, sizeof(g));
1803         s = strlen(g) + 1;
1804         key.data = g;
1805         key.size = s + sizeof(int);
1806         do {
1807             c++;
1808             memcpy(g + s, &c, sizeof(int));
1809             ret = groupinfo->get(groupinfo, tid, &key, &val, 0);
1810         } while (ret == 0);
1811         if (ret == TRYAGAIN) {
1812             TXN_RETRY(t_groupadd, tid);
1813         }
1814         val.data = &gi;
1815         val.size = sizeof(gi);
1816         ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
1817         switch (ret) {
1818         case 0:
1819             break;
1820         case TRYAGAIN:
1821             TXN_RETRY(t_groupadd, tid);
1822         default:
1823             TXN_ABORT(t_groupadd, tid);
1824             warn("OVDB: groupinfo->put: %s", db_strerror(ret));
1825             return false;
1826         }
1827         key.data = (char *) group;
1828         key.size = strlen(group);
1829         ret = groupinfo->del(groupinfo, tid, &key, 0);
1830         switch (ret) {
1831         case 0:
1832             break;
1833         case TRYAGAIN:
1834             TXN_RETRY(t_groupadd, tid);
1835         default:
1836             TXN_ABORT(t_groupadd, tid);
1837             warn("OVDB: groupinfo->del: %s", db_strerror(ret));
1838             return false;
1839         }
1840         new = 1;
1841     }
1842 
1843     if (new) {
1844         ret = groupid_new(&gi.current_gid, tid);
1845         switch (ret) {
1846         case 0:
1847             break;
1848         case TRYAGAIN:
1849             TXN_RETRY(t_groupadd, tid);
1850         default:
1851             TXN_ABORT(t_groupadd, tid);
1852             warn("OVDB: groupid_new: %s", db_strerror(ret));
1853             return false;
1854         }
1855         gi.low = lo ? lo : 1;
1856         gi.high = hi;
1857         gi.count = 0;
1858         gi.flag = *flag;
1859         gi.expired = time(NULL);
1860         gi.expiregrouppid = 0;
1861         gi.current_db = gi.new_db = which_db(group);
1862         gi.new_gid = gi.current_gid;
1863         gi.status = 0;
1864     } else {
1865         gi.flag = *flag;
1866     }
1867 
1868     key.data = (char *) group;
1869     key.size = strlen(group);
1870     val.data = &gi;
1871     val.size = sizeof gi;
1872 
1873     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
1874     switch (ret) {
1875     case 0:
1876         break;
1877     case TRYAGAIN:
1878         TXN_RETRY(t_groupadd, tid);
1879     default:
1880         TXN_ABORT(t_groupadd, tid);
1881         warn("OVDB: groupadd: groupinfo->put: %s", db_strerror(ret));
1882         return false;
1883     }
1884 
1885     if (*flag == NF_FLAG_ALIAS) {
1886         key.data = (char *) group;
1887         key.size = strlen(group);
1888         val.data = flag + 1;
1889         val.size = strcspn(flag, "\n") - 1;
1890 
1891         switch (ret = groupaliases->put(groupaliases, tid, &key, &val, 0)) {
1892         case 0:
1893             break;
1894         case TRYAGAIN:
1895             TXN_RETRY(t_groupadd, tid);
1896         default:
1897             TXN_ABORT(t_groupadd, tid);
1898             warn("OVDB: groupadd: groupaliases->put: %s", db_strerror(ret));
1899             return false;
1900         }
1901     }
1902 
1903     TXN_COMMIT(t_groupadd, tid);
1904     return true;
1905 }
1906 
1907 bool
ovdb_groupdel(const char * group)1908 ovdb_groupdel(const char *group)
1909 {
1910     DBT key, val;
1911     struct groupinfo gi;
1912     DB_TXN *tid;
1913     int ret = 0;
1914 
1915     memset(&key, 0, sizeof key);
1916     memset(&val, 0, sizeof val);
1917 
1918     /* We only need to set the deleted flag in groupinfo to prevent readers
1919        from seeing this group.  The actual overview records aren't deleted
1920        now, since that could take a significant amount of time (and innd
1921        is who normally calls this function).  The expireover run will
1922        clean up the deleted groups. */
1923 
1924     TXN_START(t_groupdel, tid);
1925 
1926     if (tid == NULL)
1927         return false;
1928 
1929     ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
1930     switch (ret) {
1931     case DB_NOTFOUND:
1932         return true;
1933     case 0:
1934         break;
1935     case TRYAGAIN:
1936         TXN_RETRY(t_groupdel, tid);
1937     default:
1938         warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
1939         TXN_ABORT(t_groupdel, tid);
1940         return false;
1941     }
1942 
1943     gi.status |= GROUPINFO_DELETED;
1944 
1945     key.data = (char *) group;
1946     key.size = strlen(group);
1947     val.data = &gi;
1948     val.size = sizeof gi;
1949 
1950     switch (ret = groupinfo->put(groupinfo, tid, &key, &val, 0)) {
1951     case 0:
1952         break;
1953     case TRYAGAIN:
1954         TXN_RETRY(t_groupdel, tid);
1955     default:
1956         TXN_ABORT(t_groupdel, tid);
1957         warn("OVDB: groupadd: groupinfo->put: %s", db_strerror(ret));
1958         return false;
1959     }
1960 
1961     switch (ret = groupaliases->del(groupaliases, tid, &key, 0)) {
1962     case 0:
1963     case DB_NOTFOUND:
1964         break;
1965     case TRYAGAIN:
1966         TXN_RETRY(t_groupdel, tid);
1967     default:
1968         warn("OVDB: groupdel: groupaliases->del: %s", db_strerror(ret));
1969         TXN_ABORT(t_groupdel, tid);
1970         return false;
1971     }
1972 
1973     TXN_COMMIT(t_groupdel, tid);
1974     return true;
1975 }
1976 
1977 bool
ovdb_add(const char * group,ARTNUM artnum,TOKEN token,char * data,int len,time_t arrived,time_t expires)1978 ovdb_add(const char *group, ARTNUM artnum, TOKEN token, char *data, int len,
1979          time_t arrived, time_t expires)
1980 {
1981     static size_t databuflen = 0;
1982     static char *databuf;
1983 #    ifdef HAVE_ZLIB
1984     uLong c_sz = 0;
1985 #    else
1986 #        define c_sz 0
1987 #    endif
1988     DB *db;
1989     DBT key, val;
1990     DB_TXN *tid;
1991     struct groupinfo gi;
1992     struct datakey dk;
1993     int ret = 0;
1994 
1995     memset(&dk, 0, sizeof dk);
1996 
1997     if (databuflen == 0) {
1998         databuflen = BIG_BUFFER;
1999         databuf = xmalloc(databuflen);
2000     }
2001 #    ifdef HAVE_ZLIB
2002     if (ovdb_conf.compress) {
2003         /* Allow for worst-case compression */
2004         c_sz = len + (len / 1000) + 20;
2005     }
2006 #    endif
2007 
2008     if (databuflen < len + sizeof(struct ovdata) + c_sz) {
2009         databuflen = len + sizeof(struct ovdata) + c_sz;
2010         databuf = xrealloc(databuf, databuflen);
2011     }
2012 
2013     /* Hmm...  Berkeley DB needs something like a 'struct iovec' so that we
2014        don't have to make a new buffer and copy everything in to it. */
2015 
2016     ((struct ovdata *) (void *) databuf)->token = token;
2017     ((struct ovdata *) (void *) databuf)->arrived = arrived;
2018     ((struct ovdata *) (void *) databuf)->expires = expires;
2019 
2020 #    ifdef HAVE_ZLIB
2021     if (ovdb_conf.compress && len > COMPRESS_MIN) {
2022         uint32_t sz;
2023         c_sz -= sizeof(uint32_t);
2024         ret = compress(
2025             (Bytef *) (databuf + sizeof(struct ovdata) + sizeof(uint32_t)),
2026             &c_sz, (Bytef *) data, (uLong) len);
2027         if (ret != Z_OK) {
2028             memcpy(databuf + sizeof(struct ovdata), data, len);
2029             len += sizeof(struct ovdata);
2030         } else {
2031             sz = htonl((uint32_t) len);
2032             memcpy(databuf + sizeof(struct ovdata), &sz, sizeof(uint32_t));
2033 
2034             /* The following line is mostly paranoia. Just want to make sure
2035                that the first byte is 0 (it should be 0 anyway), so ovdb_search
2036                recognizes this as compressed data. */
2037             *(databuf + sizeof(struct ovdata)) = 0;
2038 
2039             len = c_sz + sizeof(struct ovdata) + sizeof(uint32_t);
2040         }
2041     } else {
2042 #    endif
2043         memcpy(databuf + sizeof(struct ovdata), data, len);
2044         len += sizeof(struct ovdata);
2045 #    ifdef HAVE_ZLIB
2046     }
2047 #    endif
2048 
2049     memset(&key, 0, sizeof key);
2050     memset(&val, 0, sizeof val);
2051 
2052     /* start the transaction */
2053     TXN_START(t_add, tid);
2054 
2055     if (tid == NULL)
2056         return false;
2057 
2058     /* first, retrieve groupinfo */
2059     ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
2060     switch (ret) {
2061     case 0:
2062         break;
2063     case DB_NOTFOUND:
2064         TXN_ABORT(t_add, tid);
2065         return true;
2066     case TRYAGAIN:
2067         TXN_RETRY(t_add, tid);
2068     default:
2069         TXN_ABORT(t_add, tid);
2070         warn("OVDB: add: ovdb_getgroupinfo: %s", db_strerror(ret));
2071         return false;
2072     }
2073 
2074     /* adjust groupinfo */
2075     if (Cutofflow && gi.low > artnum) {
2076         TXN_ABORT(t_add, tid);
2077         return true;
2078     }
2079     if (gi.low == 0 || gi.low > artnum)
2080         gi.low = artnum;
2081     if (gi.high < artnum)
2082         gi.high = artnum;
2083     gi.count++;
2084 
2085     /* store groupinfo */
2086     key.data = (char *) group;
2087     key.size = strlen(group);
2088     val.data = &gi;
2089     val.size = sizeof gi;
2090 
2091     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
2092     switch (ret) {
2093     case 0:
2094         break;
2095     case TRYAGAIN:
2096         TXN_RETRY(t_add, tid);
2097     default:
2098         TXN_ABORT(t_add, tid);
2099         warn("OVDB: add: groupinfo->put: %s", db_strerror(ret));
2100         return false;
2101     }
2102 
2103     /* store overview */
2104     db = get_db_bynum(gi.current_db);
2105     if (db == NULL) {
2106         TXN_ABORT(t_add, tid);
2107         return false;
2108     }
2109     dk.groupnum = gi.current_gid;
2110     dk.artnum = htonl((u_int32_t) artnum);
2111 
2112     key.data = &dk;
2113     key.size = sizeof dk;
2114     val.data = databuf;
2115     val.size = len;
2116 
2117     ret = db->put(db, tid, &key, &val, 0);
2118     switch (ret) {
2119     case 0:
2120         break;
2121     case TRYAGAIN:
2122         TXN_RETRY(t_add, tid);
2123     default:
2124         TXN_ABORT(t_add, tid);
2125         warn("OVDB: add: db->put: %s", db_strerror(ret));
2126         return false;
2127     }
2128 
2129     if (artnum < gi.high && gi.status & GROUPINFO_MOVING) {
2130         /* If the GROUPINFO_MOVING flag is set, then expireover
2131            is writing overview records under a new groupid.
2132            If this overview record is not at the highmark,
2133            we need to also store it under the new groupid */
2134         db = get_db_bynum(gi.new_db);
2135         if (db == NULL) {
2136             TXN_ABORT(t_add, tid);
2137             return false;
2138         }
2139         dk.groupnum = gi.new_gid;
2140 
2141         ret = db->put(db, tid, &key, &val, 0);
2142         switch (ret) {
2143         case 0:
2144             break;
2145         case TRYAGAIN:
2146             TXN_RETRY(t_add, tid);
2147         default:
2148             TXN_ABORT(t_add, tid);
2149             warn("OVDB: add: db->put: %s", db_strerror(ret));
2150             return false;
2151         }
2152     }
2153 
2154     TXN_COMMIT(t_add, tid);
2155     return true;
2156 }
2157 
2158 bool
ovdb_cancel(const char * group UNUSED,ARTNUM artnum UNUSED)2159 ovdb_cancel(const char *group UNUSED, ARTNUM artnum UNUSED)
2160 {
2161     return true;
2162 }
2163 
2164 #    ifdef HAVE_ZLIB
2165 static char *
myuncompress(char * buf,size_t buflen,size_t * newlen)2166 myuncompress(char *buf, size_t buflen, size_t *newlen)
2167 {
2168     static char *dbuf = NULL;
2169     static uLongf dbuflen = 0, ulen;
2170     uint32_t sz;
2171     int ret;
2172 
2173     memcpy(&sz, buf, sizeof(sz));
2174     sz = ntohl(sz);
2175 
2176     if (sz >= dbuflen) {
2177         if (dbuflen == 0) {
2178             dbuflen = sz + 512;
2179             dbuf = xmalloc(dbuflen);
2180         } else {
2181             dbuflen = sz + 512;
2182             dbuf = xrealloc(dbuf, dbuflen);
2183         }
2184     }
2185     ulen = dbuflen - 1;
2186 
2187     ret = uncompress((Bytef *) dbuf, &ulen, (Bytef *) (buf + sizeof(uint32_t)),
2188                      buflen - sizeof(uint32_t));
2189     if (ret != Z_OK) {
2190         warn("OVDB: uncompress failed");
2191         return NULL;
2192     }
2193     dbuf[ulen] = 0; /* paranoia */
2194     if (newlen)
2195         *newlen = ulen;
2196     return dbuf;
2197 }
2198 #    endif
2199 
2200 struct ovdbsearch {
2201     DBC *cursor;
2202     group_id_t gid;
2203     u_int32_t firstart;
2204     u_int32_t lastart;
2205     int state;
2206 };
2207 
2208 /* Even though nnrpd only does one search at a time, a read server process
2209    could do many concurrent searches; hence we must keep track of an arbitrary
2210    number of open searches */
2211 static struct ovdbsearch **searches = NULL;
2212 static int nsearches = 0;
2213 static int maxsearches = 0;
2214 
2215 void *
ovdb_opensearch(const char * group,int low,int high)2216 ovdb_opensearch(const char *group, int low, int high)
2217 {
2218     DB *db;
2219     struct ovdbsearch *s;
2220     struct groupinfo gi;
2221     int ret;
2222 
2223     if (clientmode) {
2224         struct rs_cmd rs;
2225         struct rs_opensrch repl;
2226 
2227         rs.what = CMD_OPENSRCH;
2228         rs.grouplen = strlen(group) + 1;
2229         rs.artlo = low;
2230         rs.arthi = high;
2231 
2232         if (csend(&rs, sizeof(rs)) < 0)
2233             return NULL;
2234         if (csend(group, rs.grouplen) < 0)
2235             return NULL;
2236         crecv(&repl, sizeof(repl));
2237 
2238         if (repl.status != CMD_OPENSRCH)
2239             return NULL;
2240 
2241         return repl.handle;
2242     }
2243 
2244     ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
2245     switch (ret) {
2246     case 0:
2247         break;
2248     case DB_NOTFOUND:
2249         return NULL;
2250     default:
2251         warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
2252         return NULL;
2253     }
2254 
2255     s = xmalloc(sizeof(struct ovdbsearch));
2256     db = get_db_bynum(gi.current_db);
2257     if (db == NULL) {
2258         free(s);
2259         return NULL;
2260     }
2261 
2262     ret = db->cursor(db, NULL, &(s->cursor), 0);
2263     if (ret != 0) {
2264         warn("OVDB: opensearch: s->db->cursor: %s", db_strerror(ret));
2265         free(s);
2266         return NULL;
2267     }
2268 
2269     s->gid = gi.current_gid;
2270     s->firstart = low;
2271     s->lastart = high;
2272     s->state = 0;
2273 
2274     if (searches == NULL) {
2275         nsearches = 0;
2276         maxsearches = 50;
2277         searches = xmalloc(sizeof(struct ovdbsearch *) * maxsearches);
2278     }
2279     if (nsearches == maxsearches) {
2280         maxsearches += 50;
2281         searches =
2282             xrealloc(searches, sizeof(struct ovdbsearch *) * maxsearches);
2283     }
2284     searches[nsearches] = s;
2285     nsearches++;
2286 
2287     return (void *) s;
2288 }
2289 
2290 bool
ovdb_search(void * handle,ARTNUM * artnum,char ** data,int * len,TOKEN * token,time_t * arrived)2291 ovdb_search(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token,
2292             time_t *arrived)
2293 {
2294     struct ovdbsearch *s = (struct ovdbsearch *) handle;
2295     DBT key, val;
2296     u_int32_t flags;
2297     struct ovdata ovd;
2298     uint32_t sz = 0;
2299     struct datakey dk;
2300     int ret;
2301     char *dp;
2302 
2303     if (clientmode) {
2304         struct rs_cmd rs;
2305         struct rs_srch repl;
2306         static char *databuf;
2307         static int buflen = 0;
2308 
2309         rs.what = CMD_SRCH;
2310         rs.handle = handle;
2311 
2312         if (csend(&rs, sizeof(rs)) < 0)
2313             return false;
2314         if (crecv(&repl, sizeof(repl)) < 0)
2315             return false;
2316 
2317         if (repl.status != CMD_SRCH)
2318             return false;
2319         if (repl.len > buflen) {
2320             if (buflen == 0) {
2321                 buflen = repl.len + 512;
2322                 databuf = xmalloc(buflen);
2323             } else {
2324                 buflen = repl.len + 512;
2325                 databuf = xrealloc(databuf, buflen);
2326             }
2327         }
2328         crecv(databuf, repl.len);
2329 
2330         if (artnum)
2331             *artnum = repl.artnum;
2332         if (token)
2333             *token = repl.token;
2334         if (arrived)
2335             *arrived = repl.arrived;
2336         if (len)
2337             *len = repl.len;
2338         if (data)
2339             *data = databuf;
2340         return true;
2341     }
2342 
2343     switch (s->state) {
2344     case 0:
2345         flags = DB_SET_RANGE;
2346         memset(&dk, 0, sizeof dk);
2347         dk.groupnum = s->gid;
2348         dk.artnum = htonl(s->firstart);
2349         s->state = 1;
2350         break;
2351     case 1:
2352         flags = DB_NEXT;
2353         break;
2354     case 2:
2355         s->state = 3;
2356         return false;
2357     default:
2358         warn("OVDB: OVsearch called again after false return");
2359         return false;
2360     }
2361 
2362     memset(&key, 0, sizeof key);
2363     memset(&val, 0, sizeof val);
2364     key.data = &dk;
2365     key.size = key.ulen = sizeof(struct datakey);
2366     key.flags = DB_DBT_USERMEM;
2367 
2368     if (!data && !len) {
2369         /* caller doesn't need data, so we don't have to retrieve it all */
2370         val.flags |= DB_DBT_PARTIAL;
2371 
2372         if (token || arrived)
2373             val.dlen = sizeof(struct ovdata);
2374     }
2375 
2376     switch (ret = s->cursor->c_get(s->cursor, &key, &val, flags)) {
2377     case 0:
2378         break;
2379     case DB_NOTFOUND:
2380         s->state = 3;
2381         s->cursor->c_close(s->cursor);
2382         s->cursor = NULL;
2383         return false;
2384     default:
2385         warn("OVDB: search: c_get: %s", db_strerror(ret));
2386         s->state = 3;
2387         s->cursor->c_close(s->cursor);
2388         s->cursor = NULL;
2389         return false;
2390     }
2391 
2392     if (key.size != sizeof(struct datakey)) {
2393         s->state = 3;
2394         s->cursor->c_close(s->cursor);
2395         s->cursor = NULL;
2396         return false;
2397     }
2398 
2399     if (dk.groupnum != s->gid || ntohl(dk.artnum) > s->lastart) {
2400         s->state = 3;
2401         s->cursor->c_close(s->cursor);
2402         s->cursor = NULL;
2403         return false;
2404     }
2405 
2406     if (((len || data) && val.size <= sizeof(struct ovdata))
2407         || ((token || arrived) && val.size < sizeof(struct ovdata))) {
2408         warn("OVDB: search: bad value length");
2409         s->state = 3;
2410         s->cursor->c_close(s->cursor);
2411         s->cursor = NULL;
2412         return false;
2413     }
2414 
2415     if (ntohl(dk.artnum) == s->lastart) {
2416         s->state = 2;
2417         s->cursor->c_close(s->cursor);
2418         s->cursor = NULL;
2419     }
2420 
2421     if (artnum)
2422         *artnum = ntohl(dk.artnum);
2423 
2424     if (token || arrived)
2425         memcpy(&ovd, val.data, sizeof(struct ovdata));
2426     if (token)
2427         *token = ovd.token;
2428     if (arrived)
2429         *arrived = ovd.arrived;
2430 
2431     dp = (char *) val.data + sizeof(struct ovdata);
2432 #    ifdef HAVE_ZLIB
2433     if (val.size >= (sizeof(struct ovdata) + sizeof(uint32_t)) && *dp == 0) {
2434         /* data is compressed */
2435         memcpy(&sz, dp, sizeof(uint32_t));
2436         sz = ntohl(sz);
2437         if (sz > MAX_UNZIP_SZ) { /* sanity check */
2438             warn("OVDB: search: bogus sz: %d", sz);
2439             sz = 0;
2440         }
2441     }
2442 #    endif
2443 
2444     if (len) {
2445         if (sz) {
2446             *len = sz;
2447         } else {
2448             *len = val.size - sizeof(struct ovdata);
2449         }
2450     }
2451 
2452     if (data) {
2453 #    ifdef HAVE_ZLIB
2454         if (sz && val.size > sizeof(struct ovdata) + sizeof(uint32_t)) {
2455             *data = myuncompress(dp, val.size - sizeof(struct ovdata), NULL);
2456             if (*data == NULL) {
2457                 s->state = 3;
2458                 s->cursor->c_close(s->cursor);
2459                 s->cursor = NULL;
2460                 return false;
2461             }
2462         } else
2463 #    endif
2464             *data = dp;
2465     }
2466 
2467     return true;
2468 }
2469 
2470 void
ovdb_closesearch(void * handle)2471 ovdb_closesearch(void *handle)
2472 {
2473     int i;
2474     if (clientmode) {
2475         struct rs_cmd rs;
2476 
2477         rs.what = CMD_CLOSESRCH;
2478         rs.handle = handle;
2479         csend(&rs, sizeof(rs));
2480         /* no reply is sent for a CMD_CLOSESRCH */
2481     } else {
2482         struct ovdbsearch *s = (struct ovdbsearch *) handle;
2483 
2484         if (s->cursor)
2485             s->cursor->c_close(s->cursor);
2486 
2487         for (i = 0; i < nsearches; i++) {
2488             if (s == searches[i]) {
2489                 break;
2490             }
2491         }
2492         nsearches--;
2493         for (; i < nsearches; i++) {
2494             searches[i] = searches[i + 1];
2495         }
2496 
2497         free(handle);
2498     }
2499 }
2500 
2501 bool
ovdb_getartinfo(const char * group,ARTNUM artnum,TOKEN * token)2502 ovdb_getartinfo(const char *group, ARTNUM artnum, TOKEN *token)
2503 {
2504     int ret, cdb = 0;
2505     group_id_t cgid = 0;
2506     DB *db;
2507     DBT key, val;
2508     struct ovdata ovd;
2509     struct datakey dk;
2510     struct groupinfo gi;
2511     int pass = 0;
2512 
2513     if (clientmode) {
2514         struct rs_cmd rs;
2515         struct rs_artinfo repl;
2516 
2517         rs.what = CMD_ARTINFO;
2518         rs.grouplen = strlen(group) + 1;
2519         rs.artlo = artnum;
2520 
2521         if (csend(&rs, sizeof(rs)) < 0)
2522             return false;
2523         if (csend(group, rs.grouplen) < 0)
2524             return false;
2525         crecv(&repl, sizeof(repl));
2526 
2527         if (repl.status != CMD_ARTINFO)
2528             return false;
2529 
2530         if (token)
2531             *token = repl.token;
2532 
2533         return true;
2534     }
2535 
2536     while (1) {
2537         ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
2538         switch (ret) {
2539         case 0:
2540             break;
2541         case DB_NOTFOUND:
2542             return false;
2543         default:
2544             warn("OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
2545             return false;
2546         }
2547 
2548         if (pass) {
2549             /* This was our second groupinfo retrieval; because the article
2550                retrieval came up empty.  If the group ID hasn't changed
2551                since the first groupinfo retrieval, we can assume the article
2552                is definitely not there.  Otherwise, we'll try to retrieve
2553                it the article again. */
2554             if (cdb == gi.current_db && cgid == gi.current_gid)
2555                 return false;
2556         }
2557 
2558         db = get_db_bynum(gi.current_db);
2559         if (db == NULL)
2560             return false;
2561 
2562         memset(&dk, 0, sizeof dk);
2563         dk.groupnum = gi.current_gid;
2564         dk.artnum = htonl((u_int32_t) artnum);
2565 
2566         memset(&key, 0, sizeof key);
2567         memset(&val, 0, sizeof val);
2568 
2569         key.data = &dk;
2570         key.size = sizeof dk;
2571 
2572         /* caller doesn't need data, so we don't have to retrieve it all */
2573         val.flags = DB_DBT_PARTIAL;
2574 
2575         if (token)
2576             val.dlen = sizeof(struct ovdata);
2577 
2578         switch (ret = db->get(db, NULL, &key, &val, 0)) {
2579         case 0:
2580         case DB_NOTFOUND:
2581             break;
2582         default:
2583             warn("OVDB: getartinfo: db->get: %s", db_strerror(ret));
2584             return false;
2585         }
2586 
2587         if (ret == DB_NOTFOUND) {
2588             /* If the group is being moved (i.e., its group ID is going
2589                to change), there's a chance the article is now under the
2590                new ID.  So we'll grab the groupinfo again to check for
2591                that. */
2592             if (!pass && (gi.status & GROUPINFO_MOVING)) {
2593                 cdb = gi.current_db;
2594                 cgid = gi.current_gid;
2595                 pass++;
2596                 continue;
2597             }
2598             return false;
2599         }
2600         break;
2601     }
2602 
2603     if (token && val.size < sizeof(struct ovdata)) {
2604         warn("OVDB: getartinfo: data too short");
2605         return false;
2606     }
2607 
2608     if (token) {
2609         memcpy(&ovd, val.data, sizeof(struct ovdata));
2610         *token = ovd.token;
2611     }
2612     return true;
2613 }
2614 
2615 bool
ovdb_expiregroup(const char * group,int * lo,struct history * h)2616 ovdb_expiregroup(const char *group, int *lo, struct history *h)
2617 {
2618     DB *db, *ndb = NULL;
2619     DBT key, val, nkey, gkey, gval;
2620     DB_TXN *tid;
2621     DBC *cursor = NULL;
2622     int ret = 0, delete, old_db = 0, cleanup;
2623     struct groupinfo gi;
2624     struct ovdata ovd;
2625     struct datakey dk, ndk;
2626     group_id_t old_gid = 0;
2627     ARTHANDLE *ah;
2628     u_int32_t artnum = 0, currentart, lowest;
2629     int i, compact, done, currentcount, newcount;
2630 
2631     if (eo_start == 0) {
2632         eo_start = time(NULL);
2633         delete_old_stuff(0); /* remove deleted groups first */
2634     }
2635 
2636     /* Special case: when called with NULL group, we're to clean out
2637      * records for forgotton groups (groups removed from the active file
2638      * but not from overview).
2639      * This happens at the end of the expireover run, and only if all
2640      * of the groups in the active file have been processed.
2641      * delete_old_stuff(1) will remove groups that are in ovdb but
2642      * have not been processed during this run.
2643      */
2644 
2645     if (group == NULL)
2646         return delete_old_stuff(1);
2647 
2648     memset(&key, 0, sizeof key);
2649     memset(&nkey, 0, sizeof nkey);
2650     memset(&val, 0, sizeof val);
2651     memset(&dk, 0, sizeof dk);
2652     memset(&ndk, 0, sizeof ndk);
2653 
2654     TXN_START(t_expgroup_1, tid);
2655 
2656     if (tid == NULL)
2657         return false;
2658 
2659     cleanup = 0;
2660 
2661     ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
2662     switch (ret) {
2663     case 0:
2664         break;
2665     case TRYAGAIN:
2666         TXN_RETRY(t_expgroup_1, tid);
2667     default:
2668         warn("OVDB: expiregroup: ovdb_getgroupinfo failed: %s",
2669              db_strerror(ret));
2670         goto fallthroughnotfound;
2671     case DB_NOTFOUND:
2672     fallthroughnotfound:
2673         TXN_ABORT(t_expgroup_1, tid);
2674         return false;
2675     }
2676 
2677     if (gi.status & GROUPINFO_EXPIRING) {
2678         /* is there another expireover working on this group? */
2679         ret = kill(gi.expiregrouppid, 0);
2680         switch (ret) {
2681         case 0:
2682         case EPERM:
2683             TXN_ABORT(t_expgroup_1, tid);
2684             return false;
2685         }
2686 
2687         /* a previous expireover run must've died.  We'll clean
2688            up after it */
2689         if (gi.status & GROUPINFO_MOVING) {
2690             cleanup = 1;
2691             old_db = gi.new_db;
2692             old_gid = gi.new_gid;
2693 
2694             ret = mk_temp_groupinfo(old_db, old_gid, tid);
2695             switch (ret) {
2696             case 0:
2697                 break;
2698             case TRYAGAIN:
2699                 TXN_RETRY(t_expgroup_1, tid);
2700             default:
2701                 TXN_ABORT(t_expgroup_1, tid);
2702                 return false;
2703             }
2704             gi.status &= ~GROUPINFO_MOVING;
2705         }
2706     }
2707 
2708     if (gi.count < ovdb_conf.nocompact || ovdb_conf.nocompact == 0)
2709         compact = 1;
2710     else
2711         compact = 0;
2712 
2713     if (gi.count == 0)
2714         compact = 0;
2715 
2716     db = get_db_bynum(gi.current_db);
2717     if (db == NULL) {
2718         TXN_ABORT(t_expgroup_1, tid);
2719         return false;
2720     }
2721 
2722     gi.status |= GROUPINFO_EXPIRING;
2723     gi.expiregrouppid = getpid();
2724     if (compact) {
2725         gi.status |= GROUPINFO_MOVING;
2726         gi.new_db = gi.current_db;
2727         ndb = db;
2728         ret = groupid_new(&gi.new_gid, tid);
2729         switch (ret) {
2730         case 0:
2731             break;
2732         case TRYAGAIN:
2733             TXN_RETRY(t_expgroup_1, tid);
2734         default:
2735             TXN_ABORT(t_expgroup_1, tid);
2736             warn("OVDB: expiregroup: groupid_new: %s", db_strerror(ret));
2737             return false;
2738         }
2739     }
2740 
2741     key.data = (char *) group;
2742     key.size = strlen(group);
2743     val.data = &gi;
2744     val.size = sizeof gi;
2745 
2746     ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
2747     switch (ret) {
2748     case 0:
2749         break;
2750     case TRYAGAIN:
2751         TXN_RETRY(t_expgroup_1, tid);
2752     default:
2753         TXN_ABORT(t_expgroup_1, tid);
2754         warn("OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
2755         return false;
2756     }
2757     TXN_COMMIT(t_expgroup_1, tid);
2758 
2759     if (cleanup) {
2760         if (delete_all_records(old_db, old_gid) == 0) {
2761             rm_temp_groupinfo(old_gid);
2762         }
2763     }
2764 
2765     /*
2766      * The following loop iterates over the OV records for the group in
2767      * "batches", to limit transaction sizes.
2768      *
2769      * loop {
2770      *    start transaction
2771      *    get groupinfo
2772      *    process EXPIREGROUP_TXN_SIZE records
2773      *    write updated groupinfo
2774      *    commit transaction
2775      * }
2776      */
2777     currentart = 0;
2778     lowest = currentcount = 0;
2779 
2780     memset(&gkey, 0, sizeof gkey);
2781     memset(&gval, 0, sizeof gval);
2782     gkey.data = (char *) group;
2783     gkey.size = strlen(group);
2784     gval.data = &gi;
2785     gval.size = sizeof gi;
2786 
2787     while (1) {
2788         TXN_START(t_expgroup_loop, tid);
2789         if (tid == NULL)
2790             return false;
2791         done = 0;
2792         newcount = 0;
2793 
2794         ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW);
2795         switch (ret) {
2796         case 0:
2797             break;
2798         case TRYAGAIN:
2799             TXN_RETRY(t_expgroup_loop, tid);
2800         default:
2801             TXN_ABORT(t_expgroup_loop, tid);
2802             warn("OVDB: expiregroup: ovdb_getgroupinfo: %s", db_strerror(ret));
2803             return false;
2804         }
2805 
2806         ret = db->cursor(db, tid, &cursor, 0);
2807         switch (ret) {
2808         case 0:
2809             break;
2810         case TRYAGAIN:
2811             TXN_RETRY(t_expgroup_loop, tid);
2812         default:
2813             TXN_ABORT(t_expgroup_loop, tid);
2814             warn("OVDB: expiregroup: db->cursor: %s", db_strerror(ret));
2815             return false;
2816         }
2817 
2818         dk.groupnum = gi.current_gid;
2819         dk.artnum = htonl(currentart);
2820         key.data = &dk;
2821         key.size = key.ulen = sizeof dk;
2822         key.flags = DB_DBT_USERMEM;
2823 
2824         for (i = 0; i < EXPIREGROUP_TXN_SIZE; i++) {
2825             ret = cursor->c_get(cursor, &key, &val,
2826                                 i == 0 ? DB_SET_RANGE : DB_NEXT);
2827             switch (ret) {
2828             case 0:
2829             case DB_NOTFOUND:
2830                 break;
2831             case TRYAGAIN:
2832                 cursor->c_close(cursor);
2833                 TXN_RETRY(t_expgroup_loop, tid);
2834             default:
2835                 cursor->c_close(cursor);
2836                 TXN_ABORT(t_expgroup_loop, tid);
2837                 warn("OVDB: expiregroup: c_get: %s", db_strerror(ret));
2838                 return false;
2839             }
2840 
2841             /* stop if: there are no more keys, an unknown key is reached,
2842                or reach a different group */
2843 
2844             if (ret == DB_NOTFOUND || key.size != sizeof dk
2845                 || dk.groupnum != gi.current_gid) {
2846                 done++;
2847                 break;
2848             }
2849 
2850             artnum = ntohl(dk.artnum);
2851 
2852             delete = 0;
2853             if (val.size < sizeof ovd) {
2854                 delete = 1; /* must be corrupt, just delete it */
2855             } else {
2856                 char *p = (char *) val.data + sizeof(ovd);
2857                 size_t sz = val.size - sizeof(ovd);
2858 #    ifdef HAVE_ZLIB
2859                 if (*p == 0)
2860                     p = myuncompress(p, sz, &sz);
2861                 if (p == NULL) {
2862                     p = (char *) "";
2863                     delete = 1;
2864                 }
2865 #    endif
2866                 memcpy(&ovd, val.data, sizeof ovd);
2867 
2868                 ah = NULL;
2869                 if (!SMprobe(EXPENSIVESTAT, &ovd.token, NULL) || OVstatall) {
2870                     if ((ah = SMretrieve(ovd.token, RETR_STAT)) == NULL) {
2871                         delete = 1;
2872                     } else
2873                         SMfreearticle(ah);
2874                 } else {
2875                     if (!delete &&!OVhisthasmsgid(h, p)) {
2876                         delete = 1;
2877                     }
2878                 }
2879                 if (!delete &&innconf->groupbaseexpiry
2880                     && OVgroupbasedexpire(ovd.token, group, p, sz, ovd.arrived,
2881                                           ovd.expires)) {
2882                     delete = 1;
2883                 }
2884             }
2885 
2886             if (delete) {
2887                 if (!compact) {
2888                     switch (ret = cursor->c_del(cursor, 0)) {
2889                     case 0:
2890                     case DB_NOTFOUND:
2891                     case DB_KEYEMPTY:
2892                         break;
2893                     case TRYAGAIN:
2894                         cursor->c_close(cursor);
2895                         TXN_RETRY(t_expgroup_loop, tid);
2896                     default:
2897                         cursor->c_close(cursor);
2898                         TXN_ABORT(t_expgroup_loop, tid);
2899                         warn("OVDB: expiregroup: c_del: %s", db_strerror(ret));
2900                         return false;
2901                     }
2902                 }
2903                 if (gi.count > 0)
2904                     gi.count--;
2905             } else {
2906                 if (compact) {
2907                     ndk.groupnum = gi.new_gid;
2908                     ndk.artnum = dk.artnum;
2909                     nkey.data = &ndk;
2910                     nkey.size = sizeof ndk;
2911 
2912                     switch (ret = ndb->put(ndb, tid, &nkey, &val, 0)) {
2913                     case 0:
2914                         break;
2915                     case TRYAGAIN:
2916                         cursor->c_close(cursor);
2917                         TXN_RETRY(t_expgroup_loop, tid);
2918                     default:
2919                         cursor->c_close(cursor);
2920                         TXN_ABORT(t_expgroup_loop, tid);
2921                         warn("OVDB: expiregroup: ndb->put: %s",
2922                              db_strerror(ret));
2923                         return false;
2924                     }
2925                 }
2926                 newcount++;
2927                 if (lowest != (u_int32_t) -1)
2928                     if (lowest == 0 || artnum < lowest)
2929                         lowest = artnum;
2930             }
2931         }
2932         /* end of for loop */
2933 
2934         if (cursor->c_close(cursor) == TRYAGAIN) {
2935             TXN_RETRY(t_expgroup_loop, tid);
2936         }
2937 
2938         if (lowest != 0 && lowest != (u_int32_t) -1)
2939             gi.low = lowest;
2940 
2941         if (done) {
2942             if (compact) {
2943                 old_db = gi.current_db;
2944                 gi.current_db = gi.new_db;
2945                 old_gid = gi.current_gid;
2946                 gi.current_gid = gi.new_gid;
2947                 gi.status &= ~GROUPINFO_MOVING;
2948 
2949                 ret = mk_temp_groupinfo(old_db, old_gid, tid);
2950                 switch (ret) {
2951                 case 0:
2952                     break;
2953                 case TRYAGAIN:
2954                     TXN_RETRY(t_expgroup_loop, tid);
2955                 default:
2956                     TXN_ABORT(t_expgroup_loop, tid);
2957                     return false;
2958                 }
2959             }
2960 
2961             gi.status &= ~GROUPINFO_EXPIRING;
2962             gi.expired = time(NULL);
2963             if (gi.count == 0 && lowest == 0)
2964                 gi.low = gi.high + 1;
2965         }
2966 
2967         ret = groupinfo->put(groupinfo, tid, &gkey, &gval, 0);
2968         switch (ret) {
2969         case 0:
2970             break;
2971         case TRYAGAIN:
2972             TXN_RETRY(t_expgroup_loop, tid);
2973         default:
2974             TXN_ABORT(t_expgroup_loop, tid);
2975             warn("OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
2976             return false;
2977         }
2978         TXN_COMMIT(t_expgroup_loop, tid);
2979 
2980         currentcount += newcount;
2981         if (lowest != 0)
2982             lowest = (u_int32_t) -1;
2983 
2984         if (done)
2985             break;
2986 
2987         currentart = artnum + 1;
2988     }
2989 
2990     if (compact) {
2991         if (delete_all_records(old_db, old_gid) == 0) {
2992             rm_temp_groupinfo(old_gid);
2993         }
2994     }
2995 
2996     if (currentcount != gi.count) {
2997         notice("OVDB: expiregroup: recounting %s", group);
2998 
2999         TXN_START(t_expgroup_recount, tid);
3000         if (tid == NULL)
3001             return false;
3002 
3003         switch (ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW)) {
3004         case 0:
3005             break;
3006         case TRYAGAIN:
3007             TXN_RETRY(t_expgroup_recount, tid);
3008         default:
3009             TXN_ABORT(t_expgroup_recount, tid);
3010             warn("OVDB: expiregroup: ovdb_getgroupinfo: %s", db_strerror(ret));
3011             return false;
3012         }
3013 
3014         if (count_records(&gi) != 0) {
3015             TXN_ABORT(t_expgroup_recount, tid);
3016             return false;
3017         }
3018 
3019         ret = groupinfo->put(groupinfo, tid, &gkey, &gval, 0);
3020         switch (ret) {
3021         case 0:
3022             break;
3023         case TRYAGAIN:
3024             TXN_RETRY(t_expgroup_recount, tid);
3025         default:
3026             TXN_ABORT(t_expgroup_recount, tid);
3027             warn("OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
3028             return false;
3029         }
3030         TXN_COMMIT(t_expgroup_recount, tid);
3031     }
3032 
3033     if (lo)
3034         *lo = gi.low;
3035     return true;
3036 }
3037 
3038 bool
ovdb_ctl(OVCTLTYPE type,void * val)3039 ovdb_ctl(OVCTLTYPE type, void *val)
3040 {
3041     int *i;
3042     float *f;
3043     OVSORTTYPE *sorttype;
3044     bool *boolval;
3045 
3046     switch (type) {
3047     case OVSPACE:
3048         f = (float *) val;
3049         *f = -1.0f;
3050         return true;
3051     case OVSORT:
3052         sorttype = (OVSORTTYPE *) val;
3053         *sorttype = OVNEWSGROUP;
3054         return true;
3055     case OVCUTOFFLOW:
3056         Cutofflow = *(bool *) val;
3057         return true;
3058     case OVSTATICSEARCH:
3059         i = (int *) val;
3060         *i = true;
3061         return true;
3062     case OVCACHEKEEP:
3063     case OVCACHEFREE:
3064         boolval = (bool *) val;
3065         *boolval = false;
3066         return true;
3067     default:
3068         return false;
3069     }
3070 }
3071 
3072 void
ovdb_close_berkeleydb(void)3073 ovdb_close_berkeleydb(void)
3074 {
3075     if (OVDBenv) {
3076         /* close db environment */
3077         OVDBenv->close(OVDBenv, 0);
3078         OVDBenv = NULL;
3079     }
3080 }
3081 
3082 void
ovdb_close(void)3083 ovdb_close(void)
3084 {
3085     int i;
3086 
3087     if (clientmode) {
3088         client_disconnect();
3089         return;
3090     }
3091 
3092     while (searches != NULL && nsearches) {
3093         ovdb_closesearch(searches[0]);
3094     }
3095     if (searches != NULL) {
3096         free(searches);
3097         searches = NULL;
3098     }
3099 
3100     if (dbs) {
3101         /* close databases */
3102         for (i = 0; i < ovdb_conf.numdbfiles; i++)
3103             close_db_file(i);
3104 
3105         free(dbs);
3106         dbs = NULL;
3107     }
3108     if (groupinfo) {
3109         groupinfo->close(groupinfo, 0);
3110         groupinfo = NULL;
3111     }
3112     if (groupaliases) {
3113         groupaliases->close(groupaliases, 0);
3114         groupaliases = NULL;
3115     }
3116 
3117     ovdb_close_berkeleydb();
3118     ovdb_releaselock();
3119 }
3120 
3121 #endif /* HAVE_BDB */
3122