1 /* Copyright (C) 2004 Mads Martin Joergensen <mmj at mmj.dk>
2 *
3 * $Id$
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <dirent.h>
31 #include <time.h>
32 #include <fcntl.h>
33 #include <sys/wait.h>
34 #include <signal.h>
35
36 #include "mlmmj-maintd.h"
37 #include "mlmmj.h"
38 #include "strgen.h"
39 #include "chomp.h"
40 #include "log_error.h"
41 #include "mygetline.h"
42 #include "wrappers.h"
43 #include "memory.h"
44 #include "ctrlvalue.h"
45 #include "statctrl.h"
46 #include "send_digest.h"
47 #include "mylocking.h"
48 #include "log_oper.h"
49
print_help(const char * prg)50 static void print_help(const char *prg)
51 {
52 printf("Usage: %s [-L | -d] /path/to/dir [-F]\n"
53 " -d: Full path to directory with listdirs\n"
54 " Use this to run maintenance on all list directories\n"
55 " in that directory.\n"
56 " -L: Full path to one list directory\n"
57 " -F: Don't fork, performing one maintenance run only.\n"
58 " This option should be used when one wants to\n"
59 " avoid running another daemon, and use e.g. "
60 "cron to control it instead.\n", prg);
61 exit(EXIT_SUCCESS);
62 }
63
mydaemon(const char * rootdir)64 static int mydaemon(const char *rootdir)
65 {
66 int i;
67 pid_t pid;
68
69 if((pid = fork()) < 0)
70 return -1;
71 else if (pid)
72 exit(EXIT_SUCCESS); /* parent says bye bye */
73
74 if(setsid() < 0) {
75 log_error(LOG_ARGS, "Could not setsid()");
76 return -1;
77 }
78
79 if(signal(SIGHUP, SIG_IGN) == SIG_ERR) {
80 log_error(LOG_ARGS, "Could not signal(SIGHUP, SIG_IGN)");
81 return -1;
82 }
83
84 if((pid = fork()) < 0)
85 return -1;
86 else if (pid)
87 exit(EXIT_SUCCESS); /* parent says bye bye */
88
89 if(chdir(rootdir) < 0)
90 log_error(LOG_ARGS, "Could not chdir(%s)", rootdir);
91
92 i = sysconf(_SC_OPEN_MAX);
93 if(i < 0)
94 i = 256;
95 while(i >= 0)
96 close(i--);
97
98 open("/dev/null", O_RDONLY);
99 open("/dev/null", O_RDWR);
100 open("/dev/null", O_RDWR);
101
102 return 0;
103 }
104
delolder(const char * dirname,time_t than)105 int delolder(const char *dirname, time_t than)
106 {
107 DIR *dir;
108 struct dirent *dp;
109 struct stat st;
110 time_t t;
111
112 if(chdir(dirname) < 0) {
113 log_error(LOG_ARGS, "Could not chdir(%s)", dirname);
114 return -1;
115 }
116 if((dir = opendir(dirname)) == NULL) {
117 log_error(LOG_ARGS, "Could not opendir(%s)", dirname);
118 return -1;
119 }
120
121 while((dp = readdir(dir)) != NULL) {
122 if(stat(dp->d_name, &st) < 0) {
123 log_error(LOG_ARGS, "Could not stat(%s)", dp->d_name);
124 continue;
125 }
126 if(!S_ISREG(st.st_mode))
127 continue;
128 t = time(NULL);
129 if(t - st.st_mtime > than)
130 unlink(dp->d_name);
131 }
132 closedir(dir);
133
134 return 0;
135 }
136
137
clean_moderation(const char * listdir)138 int clean_moderation(const char *listdir)
139 {
140
141 time_t modreqlife = 0;
142 char *modreqlifestr;
143 char *moddirname;
144 int ret;
145
146 modreqlifestr = ctrlvalue(listdir, "modreqlife");
147 if(modreqlifestr) {
148 modreqlife = atol(modreqlifestr);
149 myfree(modreqlifestr);
150 }
151 if(modreqlife == 0)
152 modreqlife = MODREQLIFE;
153
154 moddirname = concatstr(2, listdir, "/moderation");
155 ret = delolder(moddirname, modreqlife);
156
157 myfree(moddirname);
158
159 return ret;
160 }
161
clean_discarded(const char * listdir)162 int clean_discarded(const char *listdir)
163 {
164 char *discardeddirname = concatstr(2, listdir, "/queue/discarded");
165 int ret = delolder(discardeddirname, DISCARDEDLIFE);
166
167 myfree(discardeddirname);
168
169 return ret;
170 }
171
clean_subconf(const char * listdir)172 int clean_subconf(const char *listdir)
173 {
174 char *subconfdirname = concatstr(2, listdir, "/subconf");
175 int ret = delolder(subconfdirname, CONFIRMLIFE);
176
177 myfree(subconfdirname);
178
179 return ret;
180 }
181
clean_unsubconf(const char * listdir)182 int clean_unsubconf(const char *listdir)
183 {
184 char *unsubconfdirname = concatstr(2, listdir, "/unsubconf");
185 int ret = delolder(unsubconfdirname, CONFIRMLIFE);
186
187 myfree(unsubconfdirname);
188
189 return ret;
190 }
191
discardmail(const char * old,const char * new,time_t age)192 int discardmail(const char *old, const char *new, time_t age)
193 {
194 struct stat st;
195 time_t t;
196 int fd, ret = 0;
197
198 fd = open(old, O_RDWR);
199 if(fd < 0)
200 return 0;
201
202 if(myexcllock(fd) < 0) {
203 close(fd);
204 return 0;
205 }
206
207 stat(old, &st);
208 t = time(NULL);
209
210 if(t - st.st_mtime > age) {
211 if(rename(old, new) < 0)
212 ret = 0;
213 else
214 ret = 1;
215 }
216
217 close(fd);
218 return ret;
219 }
220
resend_queue(const char * listdir,const char * mlmmjsend)221 int resend_queue(const char *listdir, const char *mlmmjsend)
222 {
223 DIR *queuedir;
224 struct dirent *dp;
225 char *mailname, *fromname, *toname, *reptoname, *from, *to, *repto;
226 char *ch, *dirname = concatstr(2, listdir, "/queue/");
227 char *bouncelifestr;
228 pid_t childpid, pid;
229 struct stat st;
230 int fromfd, tofd, fd, status, err = 0;
231 time_t t, bouncelife = 0;
232
233 if(chdir(dirname) < 0) {
234 log_error(LOG_ARGS, "Could not chdir(%s)", dirname);
235 myfree(dirname);
236 return 1;
237 }
238
239 if((queuedir = opendir(dirname)) == NULL) {
240 log_error(LOG_ARGS, "Could not opendir(%s)", dirname);
241 myfree(dirname);
242 return 1;
243 }
244 myfree(dirname);
245
246 while((dp = readdir(queuedir)) != NULL) {
247 mailname = concatstr(3, listdir, "/queue/", dp->d_name);
248
249 if(stat(mailname, &st) < 0) {
250 log_error(LOG_ARGS, "Could not stat(%s)", mailname);
251 myfree(mailname);
252 continue;
253 }
254
255 if(!S_ISREG(st.st_mode)) {
256 myfree(mailname);
257 continue;
258 }
259
260 if(strchr(dp->d_name, '.')) {
261 ch = strrchr(mailname, '.');
262 MY_ASSERT(ch);
263 *ch = '\0';
264 /* delete orphaned sidecar files */
265 if(stat(mailname, &st) < 0) {
266 if(errno == ENOENT) {
267 *ch = '.';
268 unlink(mailname);
269 }
270 }
271 myfree(mailname);
272 continue;
273 }
274
275 fromname = concatstr(2, mailname, ".mailfrom");
276 toname = concatstr(2, mailname, ".reciptto");
277 reptoname = concatstr(2, mailname, ".reply-to");
278
279 fromfd = open(fromname, O_RDONLY);
280 if(fromfd < 0)
281 err = errno;
282 tofd = open(toname, O_RDONLY);
283
284 if((fromfd < 0 && err == ENOENT) ||
285 (tofd < 0 && errno == ENOENT)) {
286 /* only delete old files to avoid deleting
287 mail currently being sent */
288 t = time(NULL);
289 if(stat(mailname, &st) == 0) {
290 if(t - st.st_mtime > (time_t)36000) {
291 unlink(mailname);
292 /* avoid leaving orphans */
293 unlink(fromname);
294 unlink(toname);
295 unlink(reptoname);
296 }
297 }
298 myfree(mailname);
299 myfree(fromname);
300 myfree(toname);
301 myfree(reptoname);
302 if(fromfd >= 0)
303 close(fromfd);
304 if(tofd >= 0)
305 close(tofd);
306 continue;
307 }
308
309 from = mygetline(fromfd);
310 chomp(from);
311 close(fromfd);
312 myfree(fromname);
313 to = mygetline(tofd);
314 chomp(to);
315 close(tofd);
316 myfree(toname);
317 fd = open(reptoname, O_RDONLY);
318 if(fd < 0) {
319 myfree(reptoname);
320 repto = NULL;
321 } else {
322 repto = mygetline(fd);
323 chomp(repto);
324 close(fd);
325 myfree(reptoname);
326 }
327
328 /* before we try again, check and see if it's old */
329 bouncelifestr = ctrlvalue(listdir, "bouncelife");
330 if(bouncelifestr) {
331 bouncelife = atol(bouncelifestr);
332 myfree(bouncelifestr);
333 }
334 if(bouncelife == 0)
335 bouncelife = BOUNCELIFE;
336
337 t = time(NULL);
338 if(t - st.st_mtime > bouncelife) {
339 unlink(mailname);
340 myfree(mailname);
341 myfree(from);
342 myfree(to);
343 myfree(repto);
344 continue;
345 }
346
347 childpid = fork();
348
349 if(childpid < 0) {
350 myfree(mailname);
351 myfree(from);
352 myfree(to);
353 myfree(repto);
354 log_error(LOG_ARGS, "Could not fork");
355 continue;
356 }
357
358 if(childpid > 0) {
359 myfree(mailname);
360 myfree(from);
361 myfree(to);
362 myfree(repto);
363 do /* Parent waits for the child */
364 pid = waitpid(childpid, &status, 0);
365 while(pid == -1 && errno == EINTR);
366 } else {
367 if(repto) {
368 execlp(mlmmjsend, mlmmjsend,
369 "-l", "1",
370 "-L", listdir,
371 "-m", mailname,
372 "-F", from,
373 "-T", to,
374 "-R", repto,
375 "-a", (char *)NULL);
376 } else {
377 execlp(mlmmjsend, mlmmjsend,
378 "-l", "1",
379 "-L", listdir,
380 "-m", mailname,
381 "-F", from,
382 "-T", to,
383 "-a", (char *)NULL);
384 }
385 log_error(LOG_ARGS, "Could not execlp %s",
386 mlmmjsend);
387 /* This is the child. Exit on failure. */
388 exit(EXIT_FAILURE);
389 }
390 }
391
392 closedir(queuedir);
393
394 return 0;
395 }
396
resend_requeue(const char * listdir,const char * mlmmjsend)397 int resend_requeue(const char *listdir, const char *mlmmjsend)
398 {
399 DIR *queuedir;
400 struct dirent *dp;
401 char *dirname = concatstr(2, listdir, "/requeue/");
402 char *archivefilename, *subfilename, *subnewname;
403 struct stat st;
404 pid_t childpid, pid;
405 time_t t;
406 int status, fromrequeuedir;
407
408 if(chdir(dirname) < 0) {
409 log_error(LOG_ARGS, "Could not chdir(%s)", dirname);
410 myfree(dirname);
411 return 1;
412 }
413
414 if((queuedir = opendir(dirname)) == NULL) {
415 log_error(LOG_ARGS, "Could not opendir(%s)", dirname);
416 myfree(dirname);
417 return 1;
418 }
419
420 while((dp = readdir(queuedir)) != NULL) {
421 if((strcmp(dp->d_name, "..") == 0) ||
422 (strcmp(dp->d_name, ".") == 0))
423 continue;
424
425 if(stat(dp->d_name, &st) < 0) {
426 log_error(LOG_ARGS, "Could not stat(%s)",dp->d_name);
427 continue;
428 }
429
430 if(!S_ISDIR(st.st_mode))
431 continue;
432
433 /* Remove old empty directories */
434 t = time(NULL);
435 if(t - st.st_mtime > (time_t)3600)
436 if(rmdir(dp->d_name) == 0)
437 continue;
438
439 archivefilename = concatstr(3, listdir, "/archive/",
440 dp->d_name);
441
442 /* Explicitly initialize for each mail we examine */
443 fromrequeuedir = 0;
444
445 if(stat(archivefilename, &st) < 0) {
446 /* Might be it's just not moved to the archive
447 * yet because it's still getting sent, so just
448 * continue
449 */
450 myfree(archivefilename);
451
452 /* If the list is set not to archive we want to look
453 * in /requeue/ for a mailfile
454 */
455 archivefilename = concatstr(4, listdir, "/requeue/",
456 dp->d_name, "/mailfile");
457 if(stat(archivefilename, &st) < 0) {
458 myfree(archivefilename);
459 continue;
460 }
461 fromrequeuedir = 1;
462 }
463 subfilename = concatstr(3, dirname, dp->d_name, "/subscribers");
464 if(stat(subfilename, &st) < 0) {
465 if (fromrequeuedir)
466 unlink(archivefilename);
467 myfree(archivefilename);
468 myfree(subfilename);
469 continue;
470 }
471
472 subnewname = concatstr(2, subfilename, ".resending");
473
474 if(rename(subfilename, subnewname) < 0) {
475 log_error(LOG_ARGS, "Could not rename(%s, %s)",
476 subfilename, subnewname);
477 myfree(archivefilename);
478 myfree(subfilename);
479 myfree(subnewname);
480 continue;
481 }
482 myfree(subfilename);
483
484 childpid = fork();
485
486 if(childpid < 0) {
487 myfree(archivefilename);
488 myfree(subnewname);
489 log_error(LOG_ARGS, "Could not fork");
490 continue;
491 }
492
493 if(childpid > 0) {
494 myfree(archivefilename);
495 myfree(subnewname);
496 do /* Parent waits for the child */
497 pid = waitpid(childpid, &status, 0);
498 while(pid == -1 && errno == EINTR);
499 } else {
500 execlp(mlmmjsend, mlmmjsend,
501 "-l", "3",
502 "-L", listdir,
503 "-m", archivefilename,
504 "-s", subnewname,
505 "-a",
506 "-D", (char *)NULL);
507 log_error(LOG_ARGS, "Could not execlp %s",
508 mlmmjsend);
509 /* This is the child. Exit on failure. */
510 exit(EXIT_FAILURE);
511 }
512 }
513
514 closedir(queuedir);
515
516 myfree(dirname);
517
518 return 0;
519 }
520
clean_nolongerbouncing(const char * listdir)521 int clean_nolongerbouncing(const char *listdir)
522 {
523 DIR *bouncedir;
524 char *dirname = concatstr(2, listdir, "/bounce/");
525 char *filename, *probetimestr, *s;
526 int probefd;
527 time_t probetime, t;
528 struct dirent *dp;
529 struct stat st;
530
531 if(chdir(dirname) < 0) {
532 log_error(LOG_ARGS, "Could not chdir(%s)", dirname);
533 myfree(dirname);
534 return 1;
535 }
536
537 if((bouncedir = opendir(dirname)) == NULL) {
538 log_error(LOG_ARGS, "Could not opendir(%s)", dirname);
539 myfree(dirname);
540 return 1;
541 }
542
543 myfree(dirname);
544
545 while((dp = readdir(bouncedir)) != NULL) {
546 if((strcmp(dp->d_name, "..") == 0) ||
547 (strcmp(dp->d_name, ".") == 0))
548 continue;
549
550 filename = mystrdup(dp->d_name);
551
552 s = strrchr(filename, '-');
553 if(s && (strcmp(s, "-probe") == 0)) {
554 if(stat(filename, &st) < 0) {
555 log_error(LOG_ARGS, "Could not stat(%s)",
556 filename);
557 myfree(filename);
558 continue;
559 }
560
561 probefd = open(filename, O_RDONLY);
562 if(probefd < 0) {
563 myfree(filename);
564 continue;
565 }
566 probetimestr = mygetline(probefd);
567 if(probetimestr == NULL) {
568 myfree(filename);
569 continue;
570 }
571 close(probefd);
572 chomp(probetimestr);
573 probetime = (time_t)strtol(probetimestr, NULL, 10);
574 myfree(probetimestr);
575 t = time(NULL);
576 if(t - probetime > WAITPROBE) {
577 unlink(filename);
578 /* remove -probe onwards from filename */
579 *s = '\0';
580 unlink(filename);
581 s = concatstr(2, filename, ".lastmsg");
582 unlink(s);
583 myfree(s);
584 }
585 }
586 myfree(filename);
587 }
588
589 closedir(bouncedir);
590
591 return 0;
592 }
593
probe_bouncers(const char * listdir,const char * mlmmjbounce)594 int probe_bouncers(const char *listdir, const char *mlmmjbounce)
595 {
596 DIR *bouncedir;
597 char *dirname = concatstr(2, listdir, "/bounce/");
598 char *probefile, *s;
599 struct dirent *dp;
600 struct stat st;
601 pid_t pid, childpid;
602 int status;
603
604 if (statctrl(listdir, "nobounceprobe")) {
605 return 0;
606 }
607
608 if(chdir(dirname) < 0) {
609 log_error(LOG_ARGS, "Could not chdir(%s)", dirname);
610 myfree(dirname);
611 return 1;
612 }
613
614 if((bouncedir = opendir(dirname)) == NULL) {
615 log_error(LOG_ARGS, "Could not opendir(%s)", dirname);
616 myfree(dirname);
617 return 1;
618 }
619
620 myfree(dirname);
621
622 while((dp = readdir(bouncedir)) != NULL) {
623 if((strcmp(dp->d_name, "..") == 0) ||
624 (strcmp(dp->d_name, ".") == 0))
625 continue;
626
627 s = strrchr(dp->d_name, '-');
628 if(s && (strcmp(s, "-probe") == 0))
629 continue;
630
631 s = strrchr(dp->d_name, '.');
632 if(s && (strcmp(s, ".lastmsg") == 0))
633 continue;
634
635 if(stat(dp->d_name, &st) < 0) {
636 log_error(LOG_ARGS, "Could not stat(%s)", dp->d_name);
637 continue;
638 }
639
640 probefile = concatstr(2, dp->d_name, "-probe");
641
642 /* Skip files which already have a probe out */
643 if(stat(probefile, &st) == 0) {
644 myfree(probefile);
645 continue;
646 }
647 myfree(probefile);
648
649 childpid = fork();
650
651 if(childpid < 0) {
652 log_error(LOG_ARGS, "Could not fork");
653 continue;
654 }
655
656 if(childpid > 0) {
657 do /* Parent waits for the child */
658 pid = waitpid(childpid, &status, 0);
659 while(pid == -1 && errno == EINTR);
660 } else {
661 probefile = mystrdup(dp->d_name);
662 execlp(mlmmjbounce, mlmmjbounce,
663 "-L", listdir,
664 "-a", probefile,
665 "-p", (char *)NULL);
666 log_error(LOG_ARGS, "Could not execlp %s",
667 mlmmjbounce);
668 /* This is the child. Exit on failure. */
669 exit(EXIT_FAILURE);
670 }
671 }
672 closedir(bouncedir);
673
674 return 0;
675 }
676
unsub_bouncers(const char * listdir,const char * mlmmjunsub)677 int unsub_bouncers(const char *listdir, const char *mlmmjunsub)
678 {
679 DIR *bouncedir;
680 char *dirname = concatstr(2, listdir, "/bounce/");
681 char *probefile, *address, *a, *firstbounce, *bouncedata;
682 char *bouncelifestr;
683 struct dirent *dp;
684 struct stat st;
685 pid_t pid, childpid;
686 int status, fd;
687 time_t bouncetime, t, bouncelife = 0;
688
689 if(chdir(dirname) < 0) {
690 log_error(LOG_ARGS, "Could not chdir(%s)", dirname);
691 myfree(dirname);
692 return 1;
693 }
694
695 if((bouncedir = opendir(dirname)) == NULL) {
696 log_error(LOG_ARGS, "Could not opendir(%s)", dirname);
697 myfree(dirname);
698 return 1;
699 }
700
701 myfree(dirname);
702
703 bouncelifestr = ctrlvalue(listdir, "bouncelife");
704 if(bouncelifestr) {
705 bouncelife = atol(bouncelifestr);
706 myfree(bouncelifestr);
707 }
708
709 if(bouncelife == 0)
710 bouncelife = BOUNCELIFE;
711
712 while((dp = readdir(bouncedir)) != NULL) {
713 if((strcmp(dp->d_name, "..") == 0) ||
714 (strcmp(dp->d_name, ".") == 0))
715 continue;
716
717 a = strrchr(dp->d_name, '-');
718 if(a && (strcmp(a, "-probe") == 0))
719 continue;
720
721 a = strrchr(dp->d_name, '.');
722 if(a && (strcmp(a, ".lastmsg") == 0))
723 continue;
724
725 if(stat(dp->d_name, &st) < 0) {
726 log_error(LOG_ARGS, "Could not stat(%s)", dp->d_name);
727 continue;
728 }
729
730 probefile = concatstr(2, dp->d_name, "-probe");
731
732 /* Skip files which already have a probe out */
733 if(stat(probefile, &st) == 0) {
734 myfree(probefile);
735 continue;
736 }
737 myfree(probefile);
738
739 /* Get the first line of the bounce file to check if it's
740 * been bouncing for long enough
741 */
742 fd = open(dp->d_name, O_RDONLY);
743 if(fd < 0) {
744 log_error(LOG_ARGS, "Could not open %s", dp->d_name);
745 continue;
746 }
747 firstbounce = mygetline(fd);
748 close(fd);
749 if(firstbounce == NULL)
750 continue;
751
752 /* End the string at the comment */
753 a = strchr(firstbounce, '#');
754 if(a == NULL) {
755 myfree(firstbounce);
756 continue;
757 }
758 *a = '\0';
759 bouncedata = mystrdup(a+1); /* Save for the log */
760 chomp(bouncedata);
761 a = strchr(firstbounce, ':');
762 if(a == NULL) {
763 myfree(firstbounce);
764 myfree(bouncedata);
765 continue;
766 }
767
768 a++; /* Increase to first digit */
769 bouncetime = (time_t)strtol(a, NULL, 10);
770 myfree(firstbounce);
771 t = time(NULL);
772 if(t - bouncetime < bouncelife + WAITPROBE) {
773 myfree(bouncedata);
774 continue; /* ok, don't unsub this one */
775 }
776
777 /* Ok, go ahead and unsubscribe the address */
778 address = mystrdup(dp->d_name);
779 a = strchr(address, '=');
780 if(a == NULL) { /* skip malformed */
781 myfree(address);
782 myfree(bouncedata);
783 continue;
784 }
785 *a = '@';
786
787 childpid = fork();
788
789 if(childpid < 0) {
790 log_error(LOG_ARGS, "Could not fork");
791 myfree(address);
792 myfree(bouncedata);
793 continue;
794 }
795
796 if(childpid > 0) {
797 log_oper(listdir, OPLOGFNAME, "mlmmj-maintd: %s"
798 " unsubscribed due to bouncing since"
799 " %s", address, bouncedata);
800 myfree(address);
801 myfree(bouncedata);
802 do /* Parent waits for the child */
803 pid = waitpid(childpid, &status, 0);
804 while(pid == -1 && errno == EINTR);
805 unlink(dp->d_name);
806 a = concatstr(2, dp->d_name, ".lastmsg");
807 unlink(a);
808 myfree(a);
809 } else {
810 execlp(mlmmjunsub, mlmmjunsub,
811 "-L", listdir,
812 "-b", "-a", address, (char *)NULL);
813 log_error(LOG_ARGS, "Could not execlp %s",
814 mlmmjunsub);
815 /* This is the child. Exit on failure. */
816 exit(EXIT_FAILURE);
817 }
818 }
819 closedir(bouncedir);
820
821 return 0;
822 }
823
run_digests(const char * listdir,const char * mlmmjsend)824 int run_digests(const char *listdir, const char *mlmmjsend)
825 {
826 char *lasttimestr, *lastindexstr, *lastissuestr;
827 char *digestname, *indexname;
828 char *digestintervalstr, *digestmaxmailsstr;
829 char *s1, *s2, *s3;
830 time_t digestinterval, t, lasttime;
831 long digestmaxmails, lastindex, index, lastissue;
832 int fd, indexfd, lock;
833 size_t lenbuf, lenstr;
834
835 if (statctrl(listdir, "noarchive")) {
836 return 0;
837 }
838
839 digestintervalstr = ctrlvalue(listdir, "digestinterval");
840 if (digestintervalstr) {
841 digestinterval = (time_t)atol(digestintervalstr);
842 myfree(digestintervalstr);
843 } else {
844 digestinterval = (time_t)DIGESTINTERVAL;
845 }
846
847 digestmaxmailsstr = ctrlvalue(listdir, "digestmaxmails");
848 if (digestmaxmailsstr) {
849 digestmaxmails = atol(digestmaxmailsstr);
850 myfree(digestmaxmailsstr);
851 } else {
852 digestmaxmails = DIGESTMAXMAILS;
853 }
854
855 digestname = concatstr(2, listdir, "/lastdigest");
856 fd = open(digestname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
857 if (fd < 0) {
858 log_error(LOG_ARGS, "Could not open '%s'", digestname);
859 myfree(digestname);
860 return 1;
861 }
862
863 lock = myexcllock(fd);
864 if(lock) {
865 log_error(LOG_ARGS, "Error locking lastdigest");
866 myfree(digestname);
867 close(fd);
868 return 1;
869 }
870
871 s1 = mygetline(fd);
872
873 /* Syntax is lastindex:lasttime or lastindex:lasttime:lastissue */
874 if (s1 && (lasttimestr = strchr(s1, ':'))) {
875 *(lasttimestr++) = '\0';
876 if ((lastissuestr = strchr(lasttimestr, ':'))) {
877 *(lastissuestr++) = '\0';
878 lastissue = atol(lastissuestr);
879 } else {
880 lastissue = 0;
881 }
882 lasttime = atol(lasttimestr);
883 lastindexstr = s1;
884 lastindex = atol(lastindexstr);
885 } else {
886 if (s1 && (strlen(s1) > 0)) {
887 log_error(LOG_ARGS, "'%s' contains malformed data",
888 digestname);
889 myfree(digestname);
890 myfree(s1);
891 close(fd);
892 return 1;
893 }
894 /* If lastdigest is empty, we start from scratch */
895 lasttime = 0;
896 lastindex = 0;
897 lastissue = 0;
898 }
899
900 indexname = concatstr(2, listdir, "/index");
901 indexfd = open(indexname, O_RDONLY);
902 if (indexfd < 0) {
903 log_error(LOG_ARGS, "Could not open '%s'", indexname);
904 myfree(digestname);
905 myfree(indexname);
906 myfree(s1);
907 close(fd);
908 return 1;
909 }
910 s2 = mygetline(indexfd);
911 close(indexfd);
912 if (!s2) {
913 /* If we don't have an index, no mails have been sent to the
914 * list, and therefore we don't need to send a digest */
915 myfree(digestname);
916 myfree(indexname);
917 myfree(s1);
918 close(fd);
919 return 1;
920 }
921
922 index = atol(s2);
923 t = time(NULL);
924
925 if ((t - lasttime >= digestinterval) ||
926 (index - lastindex >= digestmaxmails)) {
927
928 if (index > lastindex+digestmaxmails)
929 index = lastindex+digestmaxmails;
930
931 if (index > lastindex) {
932 lastissue++;
933 send_digest(listdir, lastindex+1, index, lastissue, NULL, mlmmjsend);
934 }
935
936 if (lseek(fd, 0, SEEK_SET) < 0) {
937 log_error(LOG_ARGS, "Could not seek '%s'", digestname);
938 } else {
939 /* index + ':' + time + ':' + issue + '\n' + '\0' */
940 lenbuf = 20 + 1 + 20 + 1 + 20 + 2;
941 s3 = mymalloc(lenbuf);
942 lenstr = snprintf(s3, lenbuf, "%ld:%ld:%ld\n", index, (long)t, lastissue);
943 if (lenstr >= lenbuf)
944 lenstr = lenbuf - 1;
945 if (writen(fd, s3, lenstr) == -1) {
946 log_error(LOG_ARGS, "Could not write new '%s'",
947 digestname);
948 }
949 myfree(s3);
950 }
951 }
952
953 myfree(digestname);
954 myfree(indexname);
955 myfree(s1);
956 myfree(s2);
957 close(fd);
958
959 return 0;
960 }
961
do_maintenance(const char * listdir,const char * mlmmjsend,const char * mlmmjbounce,const char * mlmmjunsub)962 void do_maintenance(const char *listdir, const char *mlmmjsend,
963 const char *mlmmjbounce, const char *mlmmjunsub)
964 {
965 char *random, *logname, *logstr;
966 char timenow[64];
967 struct stat st;
968 int maintdlogfd;
969 uid_t uid = getuid();
970 time_t t;
971
972 if(!listdir)
973 return;
974
975 if(stat(listdir, &st) < 0) {
976 log_error(LOG_ARGS, "Could not stat(%s) "
977 "No maintenance run performed.", listdir);
978 return;
979 }
980
981 if(uid == 0) { /* We're root. Do something about it.*/
982 if(setuid(st.st_uid) < 0) {
983 log_error(LOG_ARGS, "Could not setuid listdir owner.");
984 return;
985 }
986 } else if(uid != st.st_uid) {
987 log_error(LOG_ARGS,
988 "User ID not equal to the ID of %s. No "
989 "maintenance run performed.", listdir);
990 return;
991 }
992
993 if(chdir(listdir) < 0) {
994 log_error(LOG_ARGS, "Could not chdir(%s). "
995 "No maintenance run performed.", listdir);
996 return;
997 }
998
999 random = random_str();
1000 logname = concatstr(3, listdir, "/maintdlog-", random);
1001 myfree(random);
1002
1003 maintdlogfd = open(logname, O_WRONLY|O_EXCL|O_CREAT, S_IRUSR|S_IWUSR);
1004 if(maintdlogfd < 0) {
1005 log_error(LOG_ARGS, "Could not open %s", logname);
1006 myfree(logname);
1007 return;
1008 }
1009
1010 t = time(NULL);
1011 if(ctime_r(&t, timenow))
1012 WRITEMAINTLOG4(3, "Starting maintenance run at ",
1013 timenow, "\n");
1014
1015
1016 WRITEMAINTLOG4(3, "clean_moderation(", listdir, ");\n");
1017 clean_moderation(listdir);
1018
1019 WRITEMAINTLOG4(3, "clean_discarded(", listdir, ");\n");
1020 clean_discarded(listdir);
1021
1022 WRITEMAINTLOG4(3, "clean_subconf(", listdir, ");\n");
1023 clean_subconf(listdir);
1024
1025 WRITEMAINTLOG4(3, "clean_unsubconf(", listdir, ");\n");
1026 clean_unsubconf(listdir);
1027
1028 WRITEMAINTLOG6(5, "resend_queue(", listdir, ", ", mlmmjsend,
1029 ");\n");
1030 resend_queue(listdir, mlmmjsend);
1031
1032 WRITEMAINTLOG6(5, "resend_requeue(", listdir, ", ", mlmmjsend,
1033 ");\n");
1034 resend_requeue(listdir, mlmmjsend);
1035
1036 WRITEMAINTLOG4(3, "clean_nolongerbouncing(", listdir, ");\n");
1037 clean_nolongerbouncing(listdir);
1038
1039 WRITEMAINTLOG6(5, "unsub_bouncers(", listdir, ", ",
1040 mlmmjunsub, ");\n");
1041 unsub_bouncers(listdir, mlmmjunsub);
1042
1043 WRITEMAINTLOG6(5, "probe_bouncers(", listdir, ", ",
1044 mlmmjbounce, ");\n");
1045 probe_bouncers(listdir, mlmmjbounce);
1046
1047 WRITEMAINTLOG6(5, "run_digests(", listdir, ", ", mlmmjsend,
1048 ");\n");
1049 run_digests(listdir, mlmmjsend);
1050
1051 close(maintdlogfd);
1052
1053 logstr = concatstr(3, listdir, "/", MAINTD_LOGFILE);
1054
1055 if(rename(logname, logstr) < 0)
1056 log_error(LOG_ARGS, "Could not rename(%s,%s)",
1057 logname, logstr);
1058
1059 myfree(logname);
1060 myfree(logstr);
1061 }
1062
main(int argc,char ** argv)1063 int main(int argc, char **argv)
1064 {
1065 int opt, daemonize = 1, ret = 0;
1066 char *bindir, *listdir = NULL, *mlmmjsend, *mlmmjbounce, *mlmmjunsub;
1067 char *dirlists = NULL, *s, *listiter;
1068 struct stat st;
1069 struct dirent *dp;
1070 DIR *dirp;
1071
1072 CHECKFULLPATH(argv[0]);
1073
1074 log_set_name(argv[0]);
1075
1076 while ((opt = getopt(argc, argv, "hFVL:d:")) != -1) {
1077 switch(opt) {
1078 case 'd':
1079 dirlists = optarg;
1080 break;
1081 case 'F':
1082 daemonize = 0;
1083 break;
1084 case 'L':
1085 listdir = optarg;
1086 break;
1087 case 'h':
1088 print_help(argv[0]);
1089 break;
1090 case 'V':
1091 print_version(argv[0]);
1092 exit(EXIT_SUCCESS);
1093 }
1094 }
1095
1096 if(listdir == NULL && dirlists == NULL) {
1097 fprintf(stderr, "You have to specify -d or -L\n");
1098 fprintf(stderr, "%s -h for help\n", argv[0]);
1099 exit(EXIT_FAILURE);
1100 }
1101
1102 if(listdir && dirlists) {
1103 fprintf(stderr, "You have to specify either -d or -L\n");
1104 fprintf(stderr, "%s -h for help\n", argv[0]);
1105 exit(EXIT_FAILURE);
1106 }
1107
1108 bindir = mydirname(argv[0]);
1109 mlmmjsend = concatstr(2, bindir, "/mlmmj-send");
1110 mlmmjbounce = concatstr(2, bindir, "/mlmmj-bounce");
1111 mlmmjunsub = concatstr(2, bindir, "/mlmmj-unsub");
1112 myfree(bindir);
1113
1114 if(daemonize) {
1115 if(dirlists)
1116 ret = mydaemon(dirlists);
1117 else
1118 ret = mydaemon(listdir);
1119 }
1120
1121 if(daemonize && ret < 0) {
1122 log_error(LOG_ARGS, "Could not daemonize. Only one "
1123 "maintenance run will be done.");
1124 daemonize = 0;
1125 }
1126
1127 while(1) {
1128 if(listdir) {
1129 do_maintenance(listdir, mlmmjsend, mlmmjbounce,
1130 mlmmjunsub);
1131 goto mainsleep;
1132 }
1133
1134 if(chdir(dirlists) < 0) {
1135 log_error(LOG_ARGS, "Could not chdir(%s).",
1136 dirlists);
1137 myfree(mlmmjbounce);
1138 myfree(mlmmjsend);
1139 myfree(mlmmjunsub);
1140 exit(EXIT_FAILURE);
1141 }
1142
1143 if((dirp = opendir(dirlists)) == NULL) {
1144 log_error(LOG_ARGS, "Could not opendir(%s).",
1145 dirlists);
1146 myfree(mlmmjbounce);
1147 myfree(mlmmjsend);
1148 myfree(mlmmjunsub);
1149 exit(EXIT_FAILURE);
1150 }
1151
1152 while((dp = readdir(dirp)) != NULL) {
1153 if((strcmp(dp->d_name, "..") == 0) ||
1154 (strcmp(dp->d_name, ".") == 0))
1155 continue;
1156
1157 listiter = concatstr(3, dirlists, "/", dp->d_name);
1158
1159 if(stat(listiter, &st) < 0) {
1160 log_error(LOG_ARGS, "Could not stat(%s)",
1161 listiter);
1162 myfree(listiter);
1163 continue;
1164 }
1165
1166 if(!S_ISDIR(st.st_mode)) {
1167 myfree(listiter);
1168 continue;
1169 }
1170
1171 s = concatstr(2, listiter, "/control/listaddress");
1172 ret = stat(s, &st);
1173 myfree(s);
1174
1175 if(ret < 0) { /* If ret < 0 it's not a listiter */
1176 myfree(listiter);
1177 continue;
1178 }
1179
1180 do_maintenance(listiter, mlmmjsend, mlmmjbounce,
1181 mlmmjunsub);
1182
1183 myfree(listiter);
1184 }
1185
1186 closedir(dirp);
1187
1188 mainsleep:
1189 if(!daemonize)
1190 break;
1191 else
1192 sleep(MAINTD_SLEEP);
1193 }
1194
1195 myfree(mlmmjbounce);
1196 myfree(mlmmjsend);
1197 myfree(mlmmjunsub);
1198
1199 log_free_name();
1200
1201 exit(EXIT_SUCCESS);
1202 }
1203