view src/mlmmj-bounce.c @ 756:f8f3f4525a2b

Cosmetic changes to help messages
author Ben Schmidt
date Fri, 08 Oct 2010 01:47:46 +1100
parents b72bcb7e08a2
children c3ee2bfaeb02
line wrap: on
line source

/* Copyright (C) 2004 Morten K. Poulsen <morten at afdelingp.dk>
 *
 * $Id$
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <dirent.h>
#include <sys/mman.h>
#include <string.h>
#include <ctype.h>

#include "getlistaddr.h"
#include "getlistdelim.h"
#include "mlmmj.h"
#include "strgen.h"
#include "wrappers.h"
#include "log_error.h"
#include "subscriberfuncs.h"
#include "mygetline.h"
#include "chomp.h"
#include "prepstdreply.h"
#include "memory.h"
#include "find_email_adr.h"
#include "gethdrline.h"

char *fetchindexes(const char *bouncefile)
{
	int fd;
	char *indexstr = NULL, *s, *start, *line, *cur, *colon, *next;
	size_t len;
	struct stat st;

	fd = open(bouncefile, O_RDONLY);
	if(fd < 0) {
		log_error(LOG_ARGS, "Could not open bounceindexfile %s",
				bouncefile);
		return NULL;
	}

	if(fstat(fd, &st) < 0) {
		log_error(LOG_ARGS, "Could not open bounceindexfile %s",
					bouncefile);
	}

	if(st.st_size == 0) {
		log_error(LOG_ARGS, "Bounceindexfile %s is size 0",
					bouncefile);
		return NULL;
	}

	start = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
	if(start == MAP_FAILED) {
		log_error(LOG_ARGS, "Could not mmap bounceindexfile");
		return NULL;
	}


	for(next = cur = start; next < start + st.st_size; next++) {
		if(*next == '\n') {
			len = next - cur;
			line = mymalloc(len + 1);
			strncpy(line, cur, len);
			line[len] = '\0';
			cur = next + 1;
		} else
	 		continue;

		colon = strchr(line, ':');
		MY_ASSERT(colon);
		*colon = '\0';
		s = indexstr;
		indexstr = concatstr(4, s, "        ", line, "\n");
		myfree(s);
		myfree(line);
	}

	munmap(start, st.st_size);
	close(fd);

	return indexstr;
}

void do_probe(const char *listdir, const char *mlmmjsend, const char *addr)
{
	char *myaddr, *from, *a, *indexstr, *queuefilename, *listaddr;
	char *listfqdn, *listname, *probefile, *listdelim=getlistdelim(listdir);
	char *maildata[] = { "bouncenumbers", NULL };
	int fd;
	time_t t;

	myaddr = mystrdup(addr);

	listaddr = getlistaddr(listdir);
	listname = genlistname(listaddr);
	listfqdn = genlistfqdn(listaddr);

	from = concatstr(6, listname, listdelim, "bounces-probe-", myaddr, "@",
			 listfqdn);

	myfree(listaddr);
	myfree(listdelim);
	myfree(listfqdn);
	myfree(listname);

	a = strrchr(myaddr, '=');
	if (!a) {
		myfree(myaddr);
		myfree(from);
		log_error(LOG_ARGS, "do_probe(): malformed address");
		exit(EXIT_FAILURE);

	}
	*a = '@';

	indexstr = fetchindexes(addr);
	if(indexstr == NULL) {
		log_error(LOG_ARGS, "Could not fetch bounceindexes");
		exit(EXIT_FAILURE);
	}

	maildata[1] = indexstr;
	queuefilename = prepstdreply(listdir, "bounce-probe", "$listowner$",
					myaddr, NULL, 1, maildata, NULL);
	MY_ASSERT(queuefilename);
	myfree(indexstr);

	probefile = concatstr(4, listdir, "/bounce/", addr, "-probe");
	MY_ASSERT(probefile);
	t = time(NULL);
	a = mymalloc(32);
	snprintf(a, 31, "%ld", (long int)t);
	a[31] = '\0';
	unlink(probefile);
	fd = open(probefile, O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR|S_IWUSR);
	if(fd < 0)
		log_error(LOG_ARGS, "Could not open %s", probefile);
	else
		if(writen(fd, a, strlen(a)) < 0)
			log_error(LOG_ARGS, "Could not write time in probe");

	myfree(probefile);

	execlp(mlmmjsend, mlmmjsend,
				"-l", "5",
				"-L", listdir,
				"-T", myaddr,
				"-F", from,
				"-m", queuefilename, (char *)NULL);

	log_error(LOG_ARGS, "execlp() of '%s' failed", mlmmjsend);

	exit(EXIT_FAILURE);
}

char *dsnparseaddr(const char *mailname)
{
	int fd, indsn = 0, i;
	char *line, *linedup, *search, *addr = NULL;
	struct email_container emails = { 0, NULL };

	fd = open(mailname, O_RDONLY);
	if(fd < 0) {
		log_error(LOG_ARGS, "Could not open bounceindexfile %s",
				mailname);
		return NULL;
	}

	while((line = gethdrline(fd))) {
		linedup = mystrdup(line);
		for(i = 0; line[i]; i++)
			linedup[i] = tolower(line[i]);
		search = strstr(linedup, "message/delivery-status");
		myfree(linedup);
		if(search)
			indsn = 1;
		if(indsn) {
			i = strncasecmp(line, "Final-Recipient:", 16);
			if(i == 0) {
				find_email_adr(line, &emails);
				if(emails.emailcount > 0) {
					addr = mystrdup(emails.emaillist[0]);
					for(i = 0; i < emails.emailcount; i++)
						myfree(emails.emaillist[i]);
					myfree(emails.emaillist);
				} else {
					addr = NULL;
				}
				myfree(line);
				return addr;
			}
		}
		myfree(line);
	}

	return NULL;
}

static void print_help(const char *prg)
{
	printf("Usage: %s -L /path/to/list\n"
	       "       -a john=doe.org [-n num | -p]\n"
	       " -a: Address string that bounces\n"
	       " -h: This help\n"
	       " -L: Full path to list directory\n"
	       " -n: Message number in the archive\n"
	       " -p: Send out a probe\n"
	       " -V: Print version\n", prg);

	exit(EXIT_SUCCESS);
}

int main(int argc, char **argv)
{
	int opt, fd, dsnbounce = 0, i = 0;
	char *listdir = NULL, *address = NULL, *number = NULL;
	char *bindir, *mlmmjsend, *savename;
	char *mailname = NULL, *bfilename, *a, *buf, *lowcaseaddr;
	size_t len;
	time_t t;
	int probe = 0;
	struct stat st;
	uid_t uid;

	log_set_name(argv[0]);

	CHECKFULLPATH(argv[0]);

	bindir = mydirname(argv[0]);
	mlmmjsend = concatstr(2, bindir, "/mlmmj-send");
	myfree(bindir);

	while ((opt = getopt(argc, argv, "hdVL:a:n:m:p")) != -1) {
		switch(opt) {
		case 'L':
			listdir = optarg;
			break;
		case 'a':
			address = optarg;
			break;
		case 'd':
			dsnbounce = 1;
			break;
		case 'm':
			mailname = optarg;
			break;
		case 'n':
			number = optarg;
			break;
		case 'p':
			probe = 1;
			break;
		case 'h':
			print_help(argv[0]);
			break;
		case 'V':
			print_version(argv[0]);
			exit(0);
		}
	}

	if(listdir == NULL || (address == NULL && dsnbounce == 0)
				|| (number == NULL && probe == 0)) {
		fprintf(stderr,
			"You have to specify -L, -a or -d and -n or -p\n");
		fprintf(stderr, "%s -h for help\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	/* Lets make sure no random user tries to do bouncehandling */
	if(listdir) {
		if(stat(listdir, &st) == 0) {
			uid = getuid();
			if(uid && uid != st.st_uid) {
				log_error(LOG_ARGS,
					"Have to invoke either as root "
					"or as the user owning listdir");
				writen(STDERR_FILENO,
					"Have to invoke either as root "
					"or as the user owning listdir\n", 60);
				exit(EXIT_FAILURE);
			}
		} else {
			log_error(LOG_ARGS, "Could not stat %s", listdir);
			exit(EXIT_FAILURE);
		}
	}

	if(dsnbounce) {
		address = dsnparseaddr(mailname);

		/* Delete the mailfile, no need for it anymore */
		if(mailname)
			unlink(mailname);

		if(address == NULL)
			exit(EXIT_SUCCESS);

		a = strrchr(address, '@');
		MY_ASSERT(a);
		*a = '=';
	}

	/* Make the address lowercase */
	lowcaseaddr = mystrdup(address);
	i = 0;
	while(lowcaseaddr[i]) {
		lowcaseaddr[i] = tolower(lowcaseaddr[i]);
		i++;
	}
	address = lowcaseaddr;

	if(number != NULL && probe != 0) {
		fprintf(stderr, "You can only specify one of -n or -p\n");
		fprintf(stderr, "%s -h for help\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	if (probe) {
		/* send out a probe */
		do_probe(listdir, mlmmjsend, address);
		/* do_probe() will never return */
		exit(EXIT_FAILURE);
	}

#if 0
	log_error(LOG_ARGS, "listdir = [%s] address = [%s] number = [%s]", listdir, address, number);
#endif

	/* check if it's sub/unsub requests bouncing, and in that case
	 * simply remove the confirmation file. Variablenames address and
	 * number are a bit misleading in this case due to the different
	 * construction of the sub/unsub confirmation From header.
	 */
	if(strcmp(number, "confsub") == 0) {
		a = concatstr(3, listdir, "/subconf/", address);
		unlink(a);
		myfree(a);
		if(mailname)
			unlink(mailname);
		exit(EXIT_SUCCESS);
	}
	if(strcmp(number, "confunsub") == 0) {
		a = concatstr(3, listdir, "/unsubconf/", address);
		unlink(a);
		myfree(a);
		if(mailname)
			unlink(mailname);
		exit(EXIT_SUCCESS);
	}
	/* Below checks for bounce probes bouncing. If they do, simply remove
	 * the probe file and exit successfully. Yes, I know the variables
	 * have horrible names, but please bear with me.
	 */
	if(strcmp(number, "probe") == 0) {
		a = concatstr(4, listdir, "/bounce/", address, "-probe");
		unlink(a);
		unlink(mailname);
		myfree(a);
		exit(EXIT_SUCCESS);
	}

	/* save the filename with '=' before replacing it with '@' */
	bfilename = concatstr(3, listdir, "/bounce/", address);

	a = strrchr(address, '=');
	if (!a) {
		if(mailname)
			unlink(mailname);
		exit(EXIT_SUCCESS);  /* ignore malformed address */
	}
	*a = '@';

	/* make sure it's a subscribed address */
	if(is_subbed(listdir, address)) {
		log_error(LOG_ARGS, "%s is bouncing but not subscribed?",
				address);
		if(mailname)
			unlink(mailname);
		myfree(bfilename);
		exit(EXIT_SUCCESS); /* Not subbed, so exit silently */
	}

	if(lstat(bfilename, &st) == 0) {
		if((st.st_mode & S_IFLNK) == S_IFLNK) {
			log_error(LOG_ARGS, "%s is a symbolic link",
					bfilename);
			exit(EXIT_FAILURE);
		}
	}

	if ((fd = open(bfilename, O_WRONLY|O_APPEND|O_CREAT,
			S_IRUSR|S_IWUSR)) < 0) {
		log_error(LOG_ARGS, "Could not open '%s'", bfilename);
		myfree(bfilename);
		exit(EXIT_FAILURE);
	}

	/* TODO check that the message is not already bounced */

	/* XXX How long can the string representation of an integer be?
	 * It is not a security issue (we use snprintf()), but it would be
	 * bad mojo to cut the timestamp field  -- mortenp 20040427 */

	/* int + ":" + int + " # Wed Jun 30 21:49:08 1993\n" + NUL */
	len = 20 + 1 + 20 + 28 + 1;

	buf = mymalloc(len);
	if (!buf) exit(EXIT_FAILURE);

	t = time(NULL);
	snprintf(buf, len-26, "%s:%ld # ", number, (long int)t);
	ctime_r(&t, buf+strlen(buf));
	writen(fd, buf, strlen(buf));
	close(fd);

	if(mailname) {
		savename = concatstr(2, bfilename, ".lastmsg");
		rename(mailname, savename);
		myfree(savename);
	}

	myfree(bfilename);
	if(dsnbounce && address)
		myfree(address);

	return EXIT_SUCCESS;
}