/*
 *
 * $Id: maildir.c,v 1.1 1998/03/24 08:35:50 alexis Exp $
 *
 * Alexis Yushin, UUNET/NL Development, 1998
 *
 * MAILDIR transport agent for zmailer
 *
 */
/*
 *      Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *      This will be free software, but only when it is finished.
 *      Copyright 1992-1997 Matti Aarnio -- MIME processing et.al.
 */
/* History:
 *
 * Based on code by Geoff Collyer.
 * Rewritten for Sun environment by Dennis Ferguson and Rayan Zachariassen.
 * RBIFF code originally added by Jean-Francois Lamy.
 * Heavily modified for ZMailer by Rayan Zachariassen.
 * Still more modifications by Matti Aarnio <mea@nic.funet.fi>
 */
#include "hostenv.h"
#include <stdio.h>     
#include <ctype.h>     
#include <errno.h>
#include <pwd.h>     
#include <sysexits.h>
#include <sys/param.h>
#include <fcntl.h>  
#include <sys/file.h> 
#include <sys/stat.h>   
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* F_LOCK is there at some systems.. */
#endif      
#include <string.h>
#include "mail.h"
#include "zsyslog.h"
#include "zmsignal.h"

#include "malloc.h"
#include "libz.h"
#include "ta.h"
#include "splay.h"   

#ifndef	DIRMODE
#define	DIRMODE 0755
#endif

#ifndef	UMASK
#define	UMASK 022
#endif

#ifndef MAXHOSTNAMELEN
#define	MAXHOSTNAMELEN 256
#endif

#ifndef DEFAULT_MAILDIR
#define	DEFAULT_MAILDIR	"/var/spool/maildir"
#endif

#ifndef DEFAULT_UID
#define	DEFAULT_UID	1
#endif

#ifndef	DEFAULT_CHANNEL
#define	DEFAULT_CHANNEL	"local"
#endif

char tmpfilename[MAXPATHLEN];
char newfilename[MAXPATHLEN];
char userdirname[MAXPATHLEN];

/* Default parameters... */
int uid		= DEFAULT_UID;
char *maildir	= DEFAULT_MAILDIR;
int hashlevel	= 0;

/* Local identity... */
char *progname;
char hostname[MAXHOSTNAMELEN];

extern void process(struct ctldesc *);
extern void deliver(struct ctldesc *, struct rcpt *);
extern int statdir(char *, int);

int     D_alloc = 0;
static let_buffer[8*BUFSIZ];

int
main(argc, argv)
     int argc;
     char **argv;
{
    int c;
    char *t;
    char *channel = DEFAULT_CHANNEL;
    char *host = NULL;
    struct ctldesc *dp;
    char file[MAXPATHLEN+128];
    struct passwd *pwd;

    /* Take care of signals in zmailer way... */
    RETSIGTYPE (*oldsig) __((int));
    SIGNAL_HANDLESAVE(SIGINT, SIG_IGN, oldsig);
    if (oldsig != SIG_IGN) {
	SIGNAL_HANDLE(SIGINT, wantout);
    }
    SIGNAL_HANDLESAVE(SIGTERM, SIG_IGN, oldsig);
    if (oldsig != SIG_IGN) {
	SIGNAL_HANDLE(SIGTERM, wantout);
    }
    SIGNAL_HANDLESAVE(SIGQUIT, SIG_IGN, oldsig);
    if (oldsig != SIG_IGN) {
	SIGNAL_HANDLE(SIGQUIT, wantout);
    }
    SIGNAL_HANDLESAVE(SIGHUP, SIG_IGN, oldsig);
    if (oldsig != SIG_IGN) {
	SIGNAL_HANDLE(SIGHUP, wantout);
    }

    /* Obtain local identity... */
    if((progname = rindex(argv[0], '/')) == NULL) {
	progname = argv[0];
    } else {
	progname++;
    }
    if(gethostname(hostname, MAXHOSTNAMELEN) == -1) {
	fprintf(stderr, "%s: cannot get local host name: %s\n", progname,
	    strerror(errno));
	return EX_OSERR;
    }

    /* Make sure we have permissions and we use them... */
    if(geteuid() != 0 || getuid() != 0) {
	fprintf(stderr, "%s: not running as root\n", progname);
	return EX_NOPERM;
    }
    setreuid(0, 0);
    umask(UMASK);

    /* Get POSTOFFICE environment */
    if((t = getzenv("POSTOFFICE")) != NULL) {
	postoffice = t;
    }

    /* Get MAILDIR environment */
    if((t = getzenv("MAILDIR")) != NULL) {
	maildir = t;
    }

    /* Parse options.... */
    while((c = getopt(argc, argv, "c:d:u:h:VH")) != EOF) {
	switch(c) {
	case 'c':
	    channel = optarg;
	    break;
	case 'd':
	    maildir = optarg;
	    break;
	case 'u':
	    if((pwd = getpwnam(optarg)) == NULL) {
		fprintf(stderr, "%s: unknown user %s\n", progname, optarg);
		return EX_USAGE;
	    }
	    uid = pwd->pw_uid;
	    endpwent();
	    break;
	case 'V':
	    prversion("maildir");
	    return EX_OK;
	    /* NOTREACHED */
	case 'H':
	    hashlevel++;
	    break;
	case 'h':
	    host = strdup(optarg);
	    break;
	default:
	    fprintf(stderr, "usage: %s [-VH] -c channel -d maildir -u uid\n",
		progname);
	    return EX_USAGE;
	}
    }
    zopenlog("maildir", LOG_PID, LOG_MAIL);

    /* Core business... */
    while(!getout) {
	setreuid(-1, 0);
	printf("#hungry\n");
	fflush(stdout);
	if(fgets(file, sizeof(file), stdin) == NULL) {
	    break;
	}
	if(index(file, '\n') == NULL) {
	    break;
	}
	if(!strcmp(file, "#idle\n")) {
	    continue;
	}
	if(emptyline(file, sizeof(file))) {
	    break;
	}
	if((t = index(file, '\t'))) {
	    if(host) {
		free(host);
	    }
	    host = strdup(t+1);
	    *t = 0;
	}
	notary_setxdelay(0);
	dp = ctlopen(file, channel, host, &getout, NULL, NULL, NULL, NULL);
        if (dp == NULL) {
	    printf("#resync %s\n",file);
	    fflush(stdout);
	    continue;
	}
	setreuid(-1, uid);
	process(dp);
	ctlclose(dp);
    }
    return EX_OK;
}

/*
 * Processes message
 */
void
process(dp)
    struct ctldesc *dp;
{
    struct rcpt *rp;

    /* Deliver message to every recipient... */
    for (rp = dp->recipients; rp != NULL; rp = rp->next) {
	if(lseek(dp->msgfd, (off_t)dp->msgbodyoffset, SEEK_SET) < 0L) {
	    warning("Cannot seek to message body in %s! (%m)", dp->msgfile);
	    diagnostic(rp, EX_TEMPFAIL, 0, "cannot seek to message body!");
	} else {
	    deliver(dp, rp);
	}
    }
}

/*
 * Deliver message
 */
void
deliver(dp, rp)
    struct ctldesc *dp;
    struct rcpt *rp;
{
    FILE *fp;
    int c, s;
    char *t;
    time_t starttime, endtime;
    
    time(&starttime);
    notary_setxdelay(0);

    /* Lets see if maildir is there... */
    if(statdir(maildir, 0)) {
	notaryreport(rp->addr->user, "failed",
	    "5.3.5 (No system mail directory)",
	    "x-local; 566 (No system mail directory)");
	diagnostic(rp, EX_TEMPFAIL, 0, "No system mail directory: %s",
	    maildir);
	return;
    }
    /* What's our base direcotory? */
    t = index(strncpy(userdirname, maildir, MAXPATHLEN), '\0');
    for(c = 1; c <= hashlevel; c++) {
	*t++ = '/';
	if(rp->addr->user[c-1] == '\0') {
	    break;
	}
	*t++ = rp->addr->user[c-1];
	*t   = '\0';
    }
    *t++ = '/';
    strcpy(t, rp->addr->user);
    t = index(t, '\0');

    /* Make sure temporary directory is there... */
    strcpy(t, "/tmp");
    if(statdir(userdirname, 1)) {
	notaryreport(rp->addr->user, "failed",
	    "5.3.5 (Cannot create temporary mail directory)",
	    "x-local; 566 (Cannot create temporary mail directory)");
	diagnostic(rp, EX_TEMPFAIL, 0,
	    "Cannot create temporary mail directory %s for user %s",
		userdirname, rp->addr->user);
	return;
    }

    /* Make sure new directory is there... */
    strcpy(t, "/new");
    if(statdir(userdirname, 1)) {
	notaryreport(rp->addr->user, "failed",
	    "5.3.5 (Cannot create new mail directory)",
	    "x-local; 566 (Cannot create new mail directory)");
	diagnostic(rp, EX_TEMPFAIL, 0,
	    "Cannot create new mail directory %s for user %s",
		userdirname, rp->addr->user);
	return;
    }

    sprintf(t, "/tmp/%lu.%u%u.%s", time(NULL), dp->ctlid, rp->id, hostname);
    strcpy(tmpfilename, userdirname);
    strncpy(t, "/new", 4);
    strcpy(newfilename, userdirname);

    /* Make sure user dir contains only user directory... */
    *t = '\0';

    /* Put the mail... */
    if((fp = fopen(tmpfilename, "w")) == NULL) {
            notaryreport(rp->addr->user, "failed",
                "5.2.1 (Creation of temporary file failed)",
                "x-local; 500 (Creation of temporary file failed)");
            diagnostic(rp, EX_TEMPFAIL, 0,
		 "Creation of temporary file %s failed", tmpfilename);
            return;
    }

    /* Put the mail in... */
    writeheaders(rp, fp, "\n", 0, 0, NULL);
    fflush(fp);
    fprintf(fp, "\n");
    fflush(fp);

    if(ferror(fp)) {
	notaryreport(rp->addr->user, "failed",
	    "5.2.2 (Write to user's mailbox failed)",
	    "x-local; 500 (Write to user's mailbox failed)");
	diagnostic(rp, EX_IOERR, 0, "write to %s failed", tmpfilename);
	fclose(fp);
	unlink(tmpfilename);
	return;
    }

    /* Just copy the message... */
    while((c = read(dp->msgfd, &let_buffer, sizeof(let_buffer))) > 0) {
	if(fwrite(&let_buffer, 1, c, fp) != c) {
	    notaryreport(rp->addr->user, "failed",
		       "5.2.2 (Write to user's mailbox failed)",
		       "x-local; 500 (Write to user's mailbox failed)");
	    diagnostic(rp, EX_IOERR, 0, "write to %s failed", tmpfilename);
	    fclose(fp);
	    unlink(tmpfilename);
	    return;
	}
    }

    fflush(fp);
    fclose(fp);

    /* More errors handling required... */
    if(link(tmpfilename, newfilename)) {
	notaryreport(rp->addr->user, "failed",
	    "5.2.2 (Write to user mailbox failed)",
	    "x-local; 500 (Write to user mailbox failed)");
	diagnostic(rp, EX_TEMPFAIL, 0, "write to %s failed", tmpfilename);
	fclose(fp);
	unlink(tmpfilename);
	return;
    }
    unlink(tmpfilename);
    
    time(&endtime);
    notary_setxdelay((int)(endtime-starttime));
    notaryreport(rp->addr->user,"delivery",
       "2.2.0 (Delivered successfully)",
       "x-local; 250 (Delivered successfully)");
    diagnostic(rp, EX_OK, 0, "Ok");
}

int
statdir(name, make)
    char *name;
    int make;
{
    struct stat st;
    char *t, *z;

    if(stat(name, &st) == 0) {
	if(!S_ISDIR(st.st_mode)) {
	    return -1;
	} else {
	    return 0;
	}
    }
    if(!make) {
	return -1;
    }
    /* Otherwise try to make it... */
    /* We want name to end with '/' */
    if(((z = index(name, '\0')) != NULL) && (z > name)) {
	if(*(z - 1) != '/') {
	    *z = '/';
	    *(z + 1) = '\0';
	}
    }
    /* For every component... */
    for(t = index(name, '/'); t != NULL; t = index(t + 1, '/')) {
	if(t == name) {	/* Skip the first '/' */
	   continue;
	}
	*t = '\0';	/* Chop... */
	if(stat(name, &st) == 0) {
	    if(!S_ISDIR(st.st_mode)) {
		break;
	    } else {
		*t = '/';
		continue;
	    }
	}
	if(errno != ENOENT) {
	    break;
	}
	if(mkdir(name, DIRMODE) != 0) {
	    break;
	}
	*t = '/';
    }
    if(t) {
	*t = '\0';
	*z = '\0';
	return -1;
    }
    *z = '\0';
    if(!S_ISDIR(st.st_mode)) {
	return -1;
    }
    return 0;
}
