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