1 /* Copyright (C) 2002, 2003 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 <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <syslog.h>
29 #include <sys/types.h>
30 #include <unistd.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <libgen.h>
34 #include <sys/wait.h>
35 #include <ctype.h>
36 
37 #include "mlmmj.h"
38 #include "mlmmj-sub.h"
39 #include "mylocking.h"
40 #include "wrappers.h"
41 #include "getlistaddr.h"
42 #include "getlistdelim.h"
43 #include "strgen.h"
44 #include "subscriberfuncs.h"
45 #include "log_error.h"
46 #include "mygetline.h"
47 #include "statctrl.h"
48 #include "prepstdreply.h"
49 #include "memory.h"
50 #include "ctrlvalues.h"
51 #include "chomp.h"
52 
moderate_sub(const char * listdir,const char * listaddr,const char * listdelim,const char * subaddr,const char * mlmmjsend,enum subtype typesub,enum subreason reasonsub)53 static void moderate_sub(const char *listdir, const char *listaddr,
54 		const char *listdelim, const char *subaddr,
55 		const char *mlmmjsend, enum subtype typesub, enum subreason reasonsub)
56 {
57 	int i, fd, status, nosubmodmails = 0;
58 	text *txt;
59 	memory_lines_state *mls;
60 	char *a = NULL, *queuefilename, *from, *listname, *listfqdn, *str;
61 	char *modfilename, *mods, *to, *replyto, *moderators = NULL;
62 	char *cookie, *obstruct;
63 	struct strlist *submods;
64 	pid_t childpid, pid;
65 
66 	/* generate the file in moderation/ */
67 	switch(typesub) {
68 		default:
69 		case SUB_NORMAL:
70 			str = concatstr(4, subaddr, "\n", "SUB_NORMAL", "\n");
71 			break;
72 		case SUB_DIGEST:
73 			str = concatstr(4, subaddr, "\n", "SUB_DIGEST", "\n");
74 			break;
75 		case SUB_NOMAIL:
76 			str = concatstr(4, subaddr, "\n", "SUB_NOMAIL", "\n");
77 			break;
78 		case SUB_BOTH:
79 			str = concatstr(4, subaddr, "\n", "SUB_BOTH", "\n");
80 			break;
81 	}
82 
83 	for (;;) {
84 		cookie = random_str();
85 		modfilename = concatstr(3, listdir, "/moderation/subscribe",
86 				cookie);
87 		fd = open(modfilename, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
88 		if (fd < 0) {
89 			if (errno == EEXIST) {
90 				myfree(cookie);
91 				myfree(modfilename);
92 				continue;
93 			}
94 			log_error(LOG_ARGS, "could not create %s"
95 					"ignoring request: %s", str);
96 			exit(EXIT_FAILURE);
97 		}
98 		break;
99 	}
100 
101 	if(writen(fd, str, strlen(str)) < 0) {
102 		log_error(LOG_ARGS, "could not write to %s"
103 				"ignoring request: %s", str);
104 		exit(EXIT_FAILURE);
105 	}
106 
107 	close(fd);
108 
109 	myfree(str);
110 
111 	submods = ctrlvalues(listdir, "submod");
112 	mods = concatstr(2, listdir, "/control/submod");
113 	/* check to see if there's adresses in the submod control file */
114 	for(i = 0; i < submods->count; i++)
115 		a = strchr(submods->strs[i], '@');
116 
117 	/* no addresses in submod control file, use owner */
118 	if(a == NULL) {
119 		/* free the submods struct from above */
120 		for(i = 0; i < submods->count; i++)
121 			myfree(submods->strs[i]);
122 		myfree(submods->strs);
123 		myfree(submods);
124 		submods = ctrlvalues(listdir, "owner");
125 		myfree(mods);
126 		mods = concatstr(2, listdir, "/control/owner");
127 	}
128 
129 	/* send mail to moderators about request pending */
130 	listdelim = getlistdelim(listdir);
131 	listfqdn = genlistfqdn(listaddr);
132 	listname = genlistname(listaddr);
133 
134 	from = concatstr(4, listname, listdelim, "owner@", listfqdn);
135 	to = concatstr(3, listname, "-moderators@", listfqdn);
136 	replyto = concatstr(6, listname, listdelim, "permit-", cookie,
137 			"@", listfqdn);
138 	obstruct = concatstr(6, listname, listdelim, "obstruct-", cookie,
139 			"@", listfqdn);
140 	myfree(cookie);
141 	for(i = 0; i < submods->count; i++) {
142 		printf("%s", submods->strs[i]);
143 		str = moderators;
144 		moderators = concatstr(3, moderators, submods->strs[i], "\n");
145 		myfree(str);
146 	}
147 	mls = init_memory_lines(moderators);
148 	myfree(moderators);
149 
150 	txt = open_text(listdir,
151 			"gatekeep", "sub",
152 			subreason_strs[reasonsub], subtype_strs[typesub],
153 			"submod-moderator");
154 	MY_ASSERT(txt);
155 	register_unformatted(txt, "subaddr", subaddr);
156 	register_unformatted(txt, "moderateaddr", replyto); /* DEPRECATED */
157 	register_unformatted(txt, "permitaddr", replyto);
158 	register_unformatted(txt, "obstructaddr", obstruct);
159 	register_unformatted(txt, "moderators", "%gatekeepers%"); /* DEPRECATED */
160 	register_formatted(txt, "gatekeepers",
161 			rewind_memory_lines, get_memory_line, mls);
162 	queuefilename = prepstdreply(txt, listdir, "$listowner$", to, replyto);
163 	MY_ASSERT(queuefilename);
164 	close_text(txt);
165 
166 	/* we might need to exec more than one mlmmj-send */
167 
168 	nosubmodmails = statctrl(listdir,"nosubmodmails");
169 
170 	if (nosubmodmails)
171 		childpid = -1;
172 	else {
173 		childpid = fork();
174 		if(childpid < 0)
175 			log_error(LOG_ARGS, "Could not fork; requester not notified");
176 	}
177 
178 	if(childpid != 0) {
179 		if(childpid > 0) {
180 			do /* Parent waits for the child */
181 				pid = waitpid(childpid, &status, 0);
182 			while(pid == -1 && errno == EINTR);
183 		}
184 		finish_memory_lines(mls);
185 		execl(mlmmjsend, mlmmjsend,
186 				"-a",
187 				"-l", "4",
188 				"-L", listdir,
189 				"-s", mods,
190 				"-F", from,
191 				"-R", replyto,
192 				"-m", queuefilename, (char *)NULL);
193 		log_error(LOG_ARGS, "execl() of '%s' failed", mlmmjsend);
194 		exit(EXIT_FAILURE);
195 	}
196 
197 	myfree(to);
198 	myfree(replyto);
199 
200 	/* send mail to requester that the list is submod'ed */
201 
202 	from = concatstr(4, listname, listdelim, "bounces-help@", listfqdn);
203 
204 	txt = open_text(listdir,
205 			"wait", "sub",
206 			subreason_strs[reasonsub], subtype_strs[typesub],
207 			"submod-requester");
208 	MY_ASSERT(txt);
209 	register_unformatted(txt, "subaddr", subaddr);
210 	register_unformatted(txt, "moderators", "%gatekeepers"); /* DEPRECATED */
211 	register_formatted(txt, "gatekeepers",
212 			rewind_memory_lines, get_memory_line, mls);
213 	queuefilename = prepstdreply(txt, listdir,
214 			"$listowner$", subaddr, NULL);
215 	MY_ASSERT(queuefilename);
216 	close_text(txt);
217 
218 	finish_memory_lines(mls);
219 	myfree(listname);
220 	myfree(listfqdn);
221 	execl(mlmmjsend, mlmmjsend,
222 				"-l", "1",
223 				"-L", listdir,
224 				"-T", subaddr,
225 				"-F", from,
226 				"-m", queuefilename, (char *)NULL);
227 	log_error(LOG_ARGS, "execl() of '%s' failed", mlmmjsend);
228 	exit(EXIT_FAILURE);
229 }
230 
getaddrandtype(const char * listdir,const char * modstr,char ** addrptr,enum subtype * subtypeptr)231 void getaddrandtype(const char *listdir, const char *modstr,
232 		char **addrptr, enum subtype *subtypeptr)
233 {
234 	int fd;
235 	char *readaddr, *readtype, *modfilename;
236 
237 	if (strncmp(modstr, "subscribe", 9) == 0)
238 			modstr += 9;
239 
240 	modfilename = concatstr(3, listdir, "/moderation/subscribe", modstr);
241 
242 	fd = open(modfilename, O_RDONLY);
243 	if(fd < 0) {
244 		log_error(LOG_ARGS, "Could not open %s", modfilename);
245 		exit(EXIT_FAILURE);
246 	}
247 
248 	readaddr = mygetline(fd);
249 	readtype = mygetline(fd);
250 
251 	close(fd);
252 
253 	if(readaddr == NULL || readtype == NULL) {
254 		log_error(LOG_ARGS, "Could not parse %s", modfilename);
255 		exit(EXIT_FAILURE);
256 	}
257 
258 	chomp(readaddr);
259 	*addrptr = readaddr;
260 
261 	if(strncmp(readtype, "SUB_NORMAL", 10) == 0) {
262 		*subtypeptr = SUB_NORMAL;
263 		goto freedone;
264 	}
265 
266 	if(strncmp(readtype, "SUB_DIGEST", 10) == 0) {
267 		*subtypeptr = SUB_DIGEST;
268 		goto freedone;
269 	}
270 
271 	if(strncmp(readtype, "SUB_NOMAIL", 10) == 0) {
272 		*subtypeptr = SUB_NOMAIL;
273 		goto freedone;
274 	}
275 
276 	if(strncmp(readtype, "SUB_BOTH", 8) == 0) {
277 		*subtypeptr = SUB_BOTH;
278 		goto freedone;
279 	}
280 
281 	log_error(LOG_ARGS, "Type %s not valid in %s", readtype,
282 			modfilename);
283 
284 freedone:
285 	myfree(readtype);
286 	unlink(modfilename);
287 	myfree(modfilename);
288 }
289 
confirm_sub(const char * listdir,const char * listaddr,const char * listdelim,const char * subaddr,const char * mlmmjsend,enum subtype typesub,enum subreason reasonsub)290 void confirm_sub(const char *listdir, const char *listaddr,
291 		const char *listdelim, const char *subaddr,
292 		const char *mlmmjsend, enum subtype typesub, enum subreason reasonsub)
293 {
294 	text *txt;
295 	char *queuefilename, *fromaddr, *listname, *listfqdn, *listtext;
296 
297 	listname = genlistname(listaddr);
298 	listfqdn = genlistfqdn(listaddr);
299 
300 	fromaddr = concatstr(4, listname, listdelim, "bounces-help@", listfqdn);
301 
302 	myfree(listname);
303 	myfree(listfqdn);
304 
305 	switch(typesub) {
306 		default:
307 		case SUB_NORMAL:
308 			listtext = mystrdup("sub-ok");
309 			break;
310 		case SUB_DIGEST:
311 			listtext = mystrdup("sub-ok-digest");
312 			break;
313 		case SUB_NOMAIL:
314 			listtext = mystrdup("sub-ok-nomail");
315 			break;
316 		case SUB_BOTH:
317 			/* No legacy list text as feature didn't exist. */
318 			listtext = mystrdup("sub-ok");
319 			break;
320 	}
321 
322 	txt = open_text(listdir, "finish", "sub",
323 			subreason_strs[reasonsub], subtype_strs[typesub],
324 			listtext);
325 	myfree(listtext);
326 	MY_ASSERT(txt);
327 	register_unformatted(txt, "subaddr", subaddr);
328 	queuefilename = prepstdreply(txt, listdir,
329 			"$helpaddr$", subaddr, NULL);
330 	MY_ASSERT(queuefilename);
331 	close_text(txt);
332 
333 	execlp(mlmmjsend, mlmmjsend,
334 				"-l", "1",
335 				"-L", listdir,
336 				"-T", subaddr,
337 				"-F", fromaddr,
338 				"-m", queuefilename, (char *)NULL);
339 	log_error(LOG_ARGS, "execlp() of '%s' failed", mlmmjsend);
340 	exit(EXIT_FAILURE);
341 }
342 
notify_sub(const char * listdir,const char * listaddr,const char * listdelim,const char * subaddr,const char * mlmmjsend,enum subtype typesub,enum subreason reasonsub)343 void notify_sub(const char *listdir, const char *listaddr,
344 		const char *listdelim, const char *subaddr,
345 		const char *mlmmjsend, enum subtype typesub, enum subreason reasonsub)
346 {
347 	char *listfqdn, *listname, *fromaddr, *tostr;
348 	text *txt;
349 	char *queuefilename = NULL, *listtext = NULL;
350 
351 	listname = genlistname(listaddr);
352 	listfqdn = genlistfqdn(listaddr);
353 
354 	fromaddr = concatstr(4, listname, listdelim, "bounces-help@", listfqdn);
355 	tostr = concatstr(4, listname, listdelim, "owner@", listfqdn);
356 
357 	myfree(listname);
358 	myfree(listfqdn);
359 
360 	switch(typesub) {
361 		default:
362 		case SUB_NORMAL:
363 			listtext = mystrdup("notifysub");
364 			break;
365 		case SUB_DIGEST:
366 			listtext = mystrdup("notifysub-digest");
367 			break;
368 		case SUB_NOMAIL:
369 			listtext = mystrdup("notifysub-nomail");
370 			break;
371 		case SUB_BOTH:
372 			/* No legacy list text as feature didn't exist. */
373 			listtext = mystrdup("notifysub");
374 			break;
375 	}
376 
377 	txt = open_text(listdir, "notify", "sub",
378 			subreason_strs[reasonsub], subtype_strs[typesub],
379 			listtext);
380 	myfree(listtext);
381 	MY_ASSERT(txt);
382 	register_unformatted(txt, "subaddr", subaddr);
383 	register_unformatted(txt, "newsub", subaddr); /* DEPRECATED */
384 	queuefilename = prepstdreply(txt, listdir,
385 			"$listowner$", "$listowner$", NULL);
386 	MY_ASSERT(queuefilename);
387 	close_text(txt);
388 
389 	execlp(mlmmjsend, mlmmjsend,
390 			"-l", "1",
391 			"-L", listdir,
392 			"-T", tostr,
393 			"-F", fromaddr,
394 			"-m", queuefilename, (char *)NULL);
395 
396 	log_error(LOG_ARGS, "execlp() of '%s' failed", mlmmjsend);
397 	exit(EXIT_FAILURE);
398 }
399 
generate_subconfirm(const char * listdir,const char * listaddr,const char * listdelim,const char * subaddr,const char * mlmmjsend,enum subtype typesub,enum subreason reasonsub)400 void generate_subconfirm(const char *listdir, const char *listaddr,
401 			 const char *listdelim, const char *subaddr,
402 			 const char *mlmmjsend, enum subtype typesub, enum subreason reasonsub)
403 {
404 	int subconffd;
405 	char *confirmaddr, *listname, *listfqdn, *confirmfilename = NULL;
406 	text *txt;
407 	char *listtext, *queuefilename = NULL, *fromaddr;
408 	char *randomstr = NULL, *tmpstr;
409 
410 	listname = genlistname(listaddr);
411 	listfqdn = genlistfqdn(listaddr);
412 
413         do {
414                 myfree(confirmfilename);
415                 myfree(randomstr);
416 		randomstr = random_str();
417                 confirmfilename = concatstr(3, listdir, "/subconf/",
418 					    randomstr);
419 
420                 subconffd = open(confirmfilename, O_RDWR|O_CREAT|O_EXCL,
421 						  S_IRUSR|S_IWUSR);
422 
423         } while ((subconffd < 0) && (errno == EEXIST));
424 
425 	if(subconffd < 0) {
426 		log_error(LOG_ARGS, "Could not open '%s'", confirmfilename);
427 		myfree(confirmfilename);
428                 myfree(randomstr);
429 		exit(EXIT_FAILURE);
430 	}
431 
432 	myfree(confirmfilename);
433 
434 	if(writen(subconffd, subaddr, strlen(subaddr)) < 0) {
435 		log_error(LOG_ARGS, "Could not write to subconffd");
436 		myfree(confirmfilename);
437                 myfree(randomstr);
438 		exit(EXIT_FAILURE);
439 	}
440 
441 	close(subconffd);
442 
443 	fromaddr = concatstr(6, listname, listdelim, "bounces-confsub-",
444 				randomstr, "@", listfqdn);
445 
446 	switch(typesub) {
447 		default:
448 		case SUB_NORMAL:
449 			listtext = mystrdup("sub-confirm");
450 			tmpstr = mystrdup("confsub-");
451 			break;
452 		case SUB_DIGEST:
453 			listtext = mystrdup("sub-confirm-digest");
454 			tmpstr = mystrdup("confsub-digest-");
455 			break;
456 		case SUB_NOMAIL:
457 			listtext = mystrdup("sub-confirm-nomail");
458 			tmpstr = mystrdup("confsub-nomail-");
459 			break;
460 		case SUB_BOTH:
461 			/* No legacy list text as feature didn't exist. */
462 			listtext = mystrdup("sub-confirm");
463 			tmpstr = mystrdup("confsub-both-");
464 			break;
465 	}
466 
467 	confirmaddr = concatstr(6, listname, listdelim, tmpstr, randomstr, "@",
468 				listfqdn);
469 
470 	myfree(randomstr);
471 	myfree(tmpstr);
472 
473 	txt = open_text(listdir, "confirm", "sub",
474 			subreason_strs[reasonsub], subtype_strs[typesub],
475 			listtext);
476 	myfree(listtext);
477 	MY_ASSERT(txt);
478 	register_unformatted(txt, "subaddr", subaddr);
479 	register_unformatted(txt, "confaddr", confirmaddr); /* DEPRECATED */
480 	register_unformatted(txt, "confirmaddr", confirmaddr);
481 	queuefilename = prepstdreply(txt, listdir,
482 			"$helpaddr$", subaddr, confirmaddr);
483 	MY_ASSERT(queuefilename);
484 	close_text(txt);
485 
486 	myfree(listname);
487 	myfree(listfqdn);
488 
489 	execlp(mlmmjsend, mlmmjsend,
490 				"-l", "1",
491 				"-L", listdir,
492 				"-T", subaddr,
493 				"-F", fromaddr,
494 				"-m", queuefilename, (char *)NULL);
495 	log_error(LOG_ARGS, "execlp() of '%s' failed", mlmmjsend);
496 	exit(EXIT_FAILURE);
497 }
498 
print_help(const char * prg)499 static void print_help(const char *prg)
500 {
501 	printf("Usage: %s -L /path/to/list {-a john@doe.org | -m str}\n"
502 	       "       [-c] [-C] [-f] [-h] [-L] [-d | -n] [-q] [-r | -R] [-s] [-U] [-V]\n"
503 	       " -a: Email address to subscribe \n"
504 	       " -c: Send welcome mail (unless requesting confirmation)\n"
505 	       " -C: Request mail confirmation (unless switching versions)\n"
506 	       " -d: Subscribe to digest of list\n"
507 	       " -f: Force subscription (do not moderate)\n"
508 	       " -h: This help\n"
509 	       " -L: Full path to list directory\n"
510 	       " -m: moderation string\n"
511 	       " -n: Subscribe to no mail version of list\n", prg);
512 	printf(" -q: Be quiet (don't notify owner about the subscription)\n"
513 	       " -r: Behave as if request arrived via email (internal use)\n"
514 	       " -R: Behave as if confirmation arrived via email (internal use)\n"
515 	       " -s: Don't send a mail to subscriber if already subscribed\n"
516 	       " -U: Don't switch to the user id of the listdir owner\n"
517 	       " -V: Print version\n"
518 	       "To ensure a silent subscription, use -f -q -s\n");
519 	exit(EXIT_SUCCESS);
520 }
521 
generate_subscribed(const char * listdir,const char * subaddr,const char * mlmmjsend,enum subtype typesub)522 void generate_subscribed(const char *listdir, const char *subaddr,
523 		const char *mlmmjsend, enum subtype typesub)
524 {
525 	text *txt;
526 	char *queuefilename, *fromaddr, *listname, *listfqdn, *listaddr;
527 	char *listdelim = getlistdelim(listdir);
528 
529 	listaddr = getlistaddr(listdir);
530 	listname = genlistname(listaddr);
531 	listfqdn = genlistfqdn(listaddr);
532 
533 	fromaddr = concatstr(4, listname, listdelim, "bounces-help@", listfqdn);
534 	myfree(listdelim);
535 
536 	txt = open_text(listdir,
537 			"deny", "sub", "subbed", subtype_strs[typesub],
538 			"sub-subscribed");
539 	MY_ASSERT(txt);
540 	register_unformatted(txt, "subaddr", subaddr);
541 	queuefilename = prepstdreply(txt, listdir,
542 			"$helpaddr$", subaddr, NULL);
543 	MY_ASSERT(queuefilename);
544 	close_text(txt);
545 
546 	myfree(listaddr);
547 	myfree(listname);
548 	myfree(listfqdn);
549 
550 	execlp(mlmmjsend, mlmmjsend,
551 				"-l", "1",
552 				"-L", listdir,
553 				"-T", subaddr,
554 				"-F", fromaddr,
555 				"-m", queuefilename, (char *)NULL);
556 	log_error(LOG_ARGS, "execlp() of '%s' failed", mlmmjsend);
557 
558 	exit(EXIT_FAILURE);
559 }
560 
subscribe_type(char * listdir,char * listaddr,char * listdelim,char * address,char * mlmmjsend,enum subtype typesub,enum subreason reasonsub)561 static void subscribe_type(char *listdir, char *listaddr, char *listdelim,
562 		char *address, char *mlmmjsend,
563 		enum subtype typesub, enum subreason reasonsub) {
564 	char *subfilename = NULL;
565 	char chstr[2], *subdir;
566 	char *subddirname = NULL, *sublockname;
567 	int groupwritable = 0, sublock, sublockfd, lock, subfilefd;
568 	struct stat st;
569 	size_t len;
570 
571 	switch(typesub) {
572 		default:
573 		case SUB_NORMAL:
574 			subdir = "/subscribers.d/";
575 			break;
576 		case SUB_DIGEST:
577 			subdir = "/digesters.d/";
578 			break;
579 		case SUB_NOMAIL:
580 			subdir = "/nomailsubs.d/";
581 			break;
582 	}
583 
584 	subddirname = concatstr(2, listdir, subdir);
585 	if (stat(subddirname, &st) == 0) {
586 		if(st.st_mode & S_IWGRP) {
587 			groupwritable = S_IRGRP|S_IWGRP;
588 			umask(S_IWOTH);
589 			setgid(st.st_gid);
590 		}
591 	}
592 
593 	chstr[0] = address[0];
594 	chstr[1] = '\0';
595 
596 	subfilename = concatstr(3, listdir, subdir, chstr);
597 
598 	sublockname = concatstr(5, listdir, subdir, ".", chstr, ".lock");
599 	sublockfd = open(sublockname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
600 	if(sublockfd < 0) {
601 		log_error(LOG_ARGS, "Error opening lock file %s",
602 				sublockname);
603 		myfree(sublockname);
604 		exit(EXIT_FAILURE);
605 	}
606 
607 	sublock = myexcllock(sublockfd);
608 	if(sublock < 0) {
609 		log_error(LOG_ARGS, "Error locking '%s' file",
610 				sublockname);
611 		myfree(sublockname);
612 		close(sublockfd);
613 		exit(EXIT_FAILURE);
614 	}
615 
616 	subfilefd = open(subfilename, O_RDWR|O_CREAT,
617 				S_IRUSR|S_IWUSR|groupwritable);
618 	if(subfilefd == -1) {
619 		log_error(LOG_ARGS, "Could not open '%s'", subfilename);
620 		myfree(sublockname);
621 		exit(EXIT_FAILURE);
622 	}
623 
624 	lock = myexcllock(subfilefd);
625 	if(lock) {
626 		log_error(LOG_ARGS, "Error locking subscriber file");
627 		close(subfilefd);
628 		close(sublockfd);
629 		myfree(sublockname);
630 		exit(EXIT_FAILURE);
631 	}
632 
633 	lseek(subfilefd, 0L, SEEK_END);
634 	len = strlen(address);
635 	address[len] = '\n';
636 	writen(subfilefd, address, len + 1);
637 	address[len] = 0;
638 	close(subfilefd);
639 	close(sublockfd);
640 	unlink(sublockname);
641 	myfree(sublockname);
642 }
643 
main(int argc,char ** argv)644 int main(int argc, char **argv)
645 {
646 	char *listaddr, *listdelim, *listdir = NULL;
647 	char *mlmmjsend, *mlmmjunsub, *bindir;
648 	char *address = NULL, *lowcaseaddr, *modstr = NULL;
649 	const char *flag = NULL;
650 	int opt, subconfirm = 0, confirmsub = 0, notifysub;
651 	int changeuid = 1, status, digest = 0, nomail = 0, both = 0;
652 	int nogensubscribed = 0;
653 	int force = 0, quiet = 0, i = 0;
654 	enum subtype subbed;
655 	struct stat st;
656 	pid_t pid, childpid = 0;
657 	uid_t uid;
658 	enum subtype typesub = SUB_NORMAL;
659 	enum subreason reasonsub = SUB_ADMIN;
660 
661 	CHECKFULLPATH(argv[0]);
662 
663 	log_set_name(argv[0]);
664 
665 	bindir = mydirname(argv[0]);
666 	mlmmjsend = concatstr(2, bindir, "/mlmmj-send");
667 	mlmmjunsub = concatstr(2, bindir, "/mlmmj-unsub");
668 	myfree(bindir);
669 
670 	while ((opt = getopt(argc, argv, "hbcCdfm:nsVUL:a:qrR")) != -1) {
671 		switch(opt) {
672 		case 'a':
673 			address = optarg;
674 			break;
675 		case 'b':
676 			both = 1;
677 			break;
678 		case 'c':
679 			confirmsub = 1;
680 			break;
681 		case 'C':
682 			subconfirm = 1;
683 			break;
684 		case 'd':
685 			digest = 1;
686 			break;
687 		case 'f':
688 			force = 1;
689 			break;
690 		case 'h':
691 			print_help(argv[0]);
692 			break;
693 		case 'L':
694 			listdir = optarg;
695 			break;
696 		case 'm':
697 			modstr = optarg;
698 			break;
699 		case 'n':
700 			nomail = 1;
701 			break;
702 		case 'q':
703 			quiet = 1;
704 			break;
705 		case 'r':
706 			reasonsub = SUB_REQUEST;
707 			break;
708 		case 'R':
709 			reasonsub = SUB_CONFIRM;
710 			break;
711 		case 's':
712 			nogensubscribed = 1;
713 			break;
714 		case 'U':
715 			changeuid = 0;
716 			break;
717 		case 'V':
718 			print_version(argv[0]);
719 			exit(0);
720 		}
721 	}
722 
723 	if(listdir == NULL) {
724 		fprintf(stderr, "You have to specify -L\n");
725 		fprintf(stderr, "%s -h for help\n", argv[0]);
726 		exit(EXIT_FAILURE);
727 	}
728 
729 	if(address == NULL && modstr == NULL) {
730 		fprintf(stderr, "You have to specify -a or -m\n");
731 		fprintf(stderr, "%s -h for help\n", argv[0]);
732 		exit(EXIT_FAILURE);
733 	}
734 
735 	if(both + digest + nomail > 1) {
736 		fprintf(stderr, "Specify at most one of -b, -d and -n\n");
737 		fprintf(stderr, "%s -h for help\n", argv[0]);
738 		exit(EXIT_FAILURE);
739 	}
740 
741 	if(digest)
742 		typesub = SUB_DIGEST;
743 	if(nomail)
744 		typesub = SUB_NOMAIL;
745 	if(both)
746 		typesub = SUB_BOTH;
747 
748 	if(reasonsub == SUB_CONFIRM && subconfirm) {
749 		fprintf(stderr, "Cannot specify both -C and -R\n");
750 		fprintf(stderr, "%s -h for help\n", argv[0]);
751 		exit(EXIT_FAILURE);
752 	}
753 
754 	if(modstr) {
755 		getaddrandtype(listdir, modstr, &address, &typesub);
756 		reasonsub = SUB_PERMIT;
757 	}
758 
759 	if(strchr(address, '@') == NULL) {
760 		log_error(LOG_ARGS, "No '@' sign in '%s', not subscribing",
761 				address);
762 		exit(EXIT_SUCCESS);
763 	}
764 
765 	/* Make the address lowercase */
766 	lowcaseaddr = mystrdup(address);
767 	i = 0;
768 	while(lowcaseaddr[i]) {
769 		lowcaseaddr[i] = tolower(lowcaseaddr[i]);
770 		i++;
771 	}
772 	address = lowcaseaddr;
773 
774 	/* get the list address */
775 	listaddr = getlistaddr(listdir);
776 	if(strncasecmp(listaddr, address, strlen(listaddr)) == 0) {
777 		printf("Cannot subscribe the list address to the list\n");
778 		exit(EXIT_SUCCESS);  /* XXX is this success? */
779 	}
780 
781 	if(changeuid) {
782 		uid = getuid();
783 		if(!uid && stat(listdir, &st) == 0) {
784 			printf("Changing to uid %d, owner of %s.\n",
785 					(int)st.st_uid, listdir);
786 			if(setuid(st.st_uid) < 0) {
787 				perror("setuid");
788 				fprintf(stderr, "Continuing as uid %d\n",
789 						(int)uid);
790 			}
791 		}
792 	}
793 
794 	subbed = is_subbed(listdir, address, 1);
795 
796 	if(subbed == typesub) {
797 		if(!nogensubscribed)
798 			generate_subscribed(listdir, address, mlmmjsend,
799 					typesub);
800 		return EXIT_SUCCESS;
801 	} else if(subbed != SUB_NONE) {
802 		reasonsub = SUB_SWITCH;
803 		/* If we want to subscribe to both, we can just subscribe the
804 		 * missing version, so don't unsub. */
805 		if (!(typesub == SUB_BOTH &&
806 				subbed != SUB_NOMAIL)) {
807 			childpid = fork();
808 			if(childpid < 0) {
809 				log_error(LOG_ARGS, "Could not fork; "
810 				"not unsubscribed from current version");
811 			}
812 			if (childpid == 0) {
813 				if (subbed == SUB_BOTH) {
814 					if (typesub == SUB_NORMAL) flag = "-d";
815 					if (typesub == SUB_DIGEST) flag = "-N";
816 				}
817 				execlp(mlmmjunsub, mlmmjunsub,
818 						"-L", listdir, "-q",
819 						"-a", address, flag,
820 						(char *)NULL);
821 				log_error(LOG_ARGS, "execlp() of '%s' failed",
822 						mlmmjunsub);
823 				exit(EXIT_FAILURE);
824 			}
825 		}
826 	}
827 
828 	if(childpid > 0) {
829 		do /* Parent waits for the child */
830 			pid = waitpid(childpid, &status, 0);
831 		while(pid == -1 && errno == EINTR);
832 	}
833 
834 	listdelim = getlistdelim(listdir);
835 
836 	if(subbed == SUB_NONE && subconfirm)
837 			generate_subconfirm(listdir, listaddr, listdelim,
838 			address, mlmmjsend, typesub, reasonsub);
839 
840 	if(modstr == NULL && subbed == SUB_NONE && !force &&
841 			statctrl(listdir, "submod")) {
842 		moderate_sub(listdir, listaddr, listdelim,
843 				address, mlmmjsend, typesub, reasonsub);
844 	}
845 
846 	if (typesub == SUB_BOTH) {
847 		if (subbed != SUB_NORMAL) {
848 			subscribe_type(listdir, listaddr, listdelim, address,
849 					mlmmjsend, SUB_NORMAL, reasonsub);
850 		}
851 		if (subbed != SUB_DIGEST) {
852 			subscribe_type(listdir, listaddr, listdelim, address,
853 					mlmmjsend, SUB_DIGEST, reasonsub);
854 		}
855 	} else if (!(subbed == SUB_BOTH && typesub != SUB_NOMAIL)) {
856 		subscribe_type(listdir, listaddr, listdelim, address,
857 				mlmmjsend, typesub, reasonsub);
858 	}
859 
860 	if(confirmsub) {
861 		childpid = fork();
862 
863 		if(childpid < 0) {
864 			log_error(LOG_ARGS, "Could not fork; owner not notified");
865 			confirm_sub(listdir, listaddr, listdelim, address,
866 					mlmmjsend, typesub, reasonsub);
867 		}
868 
869 		if(childpid > 0) {
870 			do /* Parent waits for the child */
871 				pid = waitpid(childpid, &status, 0);
872 			while(pid == -1 && errno == EINTR);
873 		}
874 
875 		/* child confirms subscription */
876 		if(childpid == 0)
877 			confirm_sub(listdir, listaddr, listdelim, address,
878 					mlmmjsend, typesub, reasonsub);
879 	}
880 
881 	notifysub = !quiet && reasonsub != SUB_SWITCH &&
882 			statctrl(listdir, "notifysub");
883 
884 	/* Notify list owner about subscription */
885 	if (notifysub)
886 		notify_sub(listdir, listaddr, listdelim, address, mlmmjsend,
887 				typesub, reasonsub);
888 
889 	myfree(address);
890 	myfree(listaddr);
891 	myfree(listdelim);
892 
893 	return EXIT_SUCCESS;
894 }
895