/*
 * Copyright (c) 1989 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#ifndef lint
static char copyright[] = "Copyright (c) 1990 Regents of the University of California.\nAll rights reserved.\n";
static char SccsId[] = "@(#)@(#)pop_dropcopy.c	2.6  2.6 4/3/91";
#endif


#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <ctype.h>
#ifdef SYSV
# include	<string.h>
# include 	<unistd.h>	/* Maybe /sys/unistd.h on HPUX */
# include	"flock.h"
# define	index(s,c)		strchr(s,c)
# define	rindex(s,c)		strrchr(s,c)
#else
# include <strings.h>
#endif
#include <sys/stat.h>
#include <sys/file.h>
#include <pwd.h>
#include "popper.h"

#if defined(SYSV) && !defined(L_XTND)
# define L_XTND SEEK_END
#endif

extern int      errno;
extern int      sys_nerr;
extern char    *sys_errlist[];

/*
 *  A more accurate From header checker extracted from /usr/bin/mail src.
 */

/*
 * Test to see if the passed string is a ctime(3) generated
 * date string as documented in the manual.  The template
 * below is used as the criterion of correctness.
 * Also, we check for a possible trailing time zone using
 * the tmztype template.
 */

/*
 * 'A'	An upper case char
 * 'a'	A lower case char
 * ' '	A space
 * '0'	A digit
 * 'O'	An optional digit or space
 * ':'	A colon
 * 'N'	A new line
 */
char ctype[] = "Aaa Aaa O0 00:00:00 0000";
char tmztype[] = "Aaa Aaa O0 00:00:00 AAA 0000";
char ctype2[] = "Aaa Aaa O0 00:00 0000";
char tmztype2[] = "Aaa Aaa O0 00:00 AAA 0000";

isdate(date)
	char date[];
{

	return cmatch(date, ctype) || cmatch(date, tmztype) ||
		cmatch(date, ctype2) || cmatch(date, tmztype2) ;
}

/*
 * Match the given string (cp) against the given template (tp).
 * Return 1 if they match, 0 if they don't
 */
cmatch(cp, tp)
	register char *cp, *tp;
{

	while (*cp && *tp)
		switch (*tp++) {
		case 'a':
			if (!islower(*cp++))
				return 0;
			break;
		case 'A':
			if (!isupper(*cp++))
				return 0;
			break;
		case ' ':
			if (*cp++ != ' ')
				return 0;
			break;
		case '0':
			if (!isdigit(*cp++))
				return 0;
			break;
		case 'O':
			if (*cp != ' ' && !isdigit(*cp))
				return 0;
			cp++;
			break;
		case ':':
			if (*cp++ != ':')
				return 0;
			break;
		case 'N':
			if (*cp++ != '\n')
				return 0;
			break;
		}
	if ((*cp != '\r' && *cp != '\n' && *cp) || *tp)
		return 0;
	return (1);
}


/*
 *  0 for not a from line
 *  1 for is a from line
 */

int
isfromline(cp)
char	*cp;
{
    if (!cp || (strlen(cp) < 5) || *cp++ != 'F' || *cp++ != 'r' || 
	*cp++ != 'o' || *cp++ != 'm' || *cp != ' ')
	return(0);

    /* Skip over spaces and tabs */
    while (*cp && (*cp == ' ' || *cp == '\t'))
	cp++;

    /* Skip over the from address */
    while (*cp && *cp != ' ' && *cp != '\t')
	cp++;

    /* Skip over more spaces and tabs */
    while (*cp && (*cp == ' ' || *cp == '\t'))
	cp++;

    /* We should now be at either ttysomething or the date */
    if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
	/* skip over the tty name to the next word */
	while (*cp && (*cp == ' ' || *cp == '\t'))
	    cp++;
	while (*cp && *cp != ' ' && *cp != '\t')
	    cp++;
    }

    return (isdate(cp));
}


int init_dropinfo(p, dfd)
POP *p;
int dfd;
{
    MsgInfoList         *   mp;         /* Pointer to message info list */
    int			    msg_num;	/* Current message number */
    int			    nchar;
    int			    inheader;
    char                    buffer[BUFSIZ];         /*  Read buffer */

    /*  Allocate memory for message information structures */
    p->mlp = (MsgInfoList *)calloc((unsigned)ALLOC_MSGS,sizeof(MsgInfoList));
    if (p->mlp == NULL){
        return pop_msg (p,POP_FAILURE,
            "Can't build message list for '%s': Out of memory", p->user);
    }

    /* Is the first line a recognizable From line? */
    fgets(buffer,MAXMSGLINELEN,p->drop);
    if (! isfromline(buffer))
      simple_from = TRUE;
    else
      simple_from = FALSE;

    rewind(p->drop);

    inheader = 0;
    msg_num = 0;

    p->session_time = time((time_t *)0);
    p->msg_count = ALLOC_MSGS;
    p->uidl_seq = 0;

    for (p->drop_size=0, mp=p->mlp - 1; fgets(buffer, MAXMSGLINELEN, p->drop);) {
	nchar = strlen(buffer);

	if (simple_from ? (strncmp(buffer,"From ",5) == 0) : isfromline(buffer)) {

            if (++msg_num > p->msg_count) {
                p->mlp=(MsgInfoList *) realloc(p->mlp,
                    (p->msg_count+=ALLOC_MSGS)*sizeof(MsgInfoList));
                if (p->mlp == NULL){
                    p->msg_count = 0;
                    return pop_msg (p,POP_FAILURE,
			    "Can't build message list for '%s': Out of memory",
				p->user);
                }
                mp = p->mlp + msg_num - 2;
            }
#ifdef DEBUG
            if(p->debug && msg_num != 1)
                pop_log(p,POP_DEBUG,
                    "Msg %d at offset %d is %d octets long and has %u lines.",
                        mp->number,mp->offset,mp->length,mp->lines);
#endif
            ++mp;
            mp->number = msg_num;
            mp->length = 0;
            mp->lines = 0;
            mp->offset = ftell(p->drop) - nchar;
            mp->del_flag = FALSE;
            mp->retr_flag = FALSE;
#ifdef DEBUG
            if(p->debug)
                pop_log(p,POP_DEBUG, "Msg %d being added to list", mp->number);
#endif
	    inheader = 1;
        }

	if (inheader && (strncasecmp(buffer,"Status:",7) == 0)) {
	    if (index(buffer, 'R') != NULL)
		mp->retr_flag = TRUE;
	}

        mp->length += nchar;
        p->drop_size += nchar;
        mp->lines++;
    }

    p->msg_count = msg_num;

    return(POP_SUCCESS);
}


/* 
 *  use to be dropinfo:   Extract information about the POP maildrop and store 
 *  it for use by the other POP routines.
 *
 *  Now, the copy and info collection are done at the same time.
 */

do_drop_copy(p, mfd, dfd)
int	mfd, dfd;
POP	*p;
{
    char                    buffer[BUFSIZ];         /*  Read buffer */
    MsgInfoList         *   mp;                     /*  Pointer to message 
                                                        info list */
    int                     nchar;                  /*  Bytes written/read */
    int			    inheader;		    /*  Header section flag */
    int			    uidlfound;		    /*  UIDL header flag */
    int			    msg_num;

    FILE		    *mail_drop;		    /*  Streams for fids */

    msg_num = p->msg_count;
    p->msg_count = (((p->msg_count - 1) / ALLOC_MSGS) + 1) * ALLOC_MSGS;

    /*  Acquire a stream pointer for the maildrop */
    if ( (mail_drop = fdopen(mfd,"r")) == NULL ) {
        (void)close(mfd) ;
        return pop_msg(p,POP_FAILURE,"Cannot assign stream for %s",
            p->drop_name);
    }

    rewind (mail_drop);

    /* Is the first line a recognizable From line? */
    fgets(buffer,MAXMSGLINELEN,mail_drop);
    if (! isfromline(buffer))
      simple_from = TRUE;
    else
      simple_from = FALSE;

    rewind (mail_drop);

    /*  Scan the file, loading the message information list with 
        information about each message */

    inheader = 0;
    uidlfound = 1;

    for (p->drop_size = 0, mp = p->mlp + msg_num - 1;
				      fgets(buffer,MAXMSGLINELEN,mail_drop);) {
	nchar = strlen(buffer);

	if (inheader && *buffer == '\n') {
	    inheader = 0;

	    if (!uidlfound) {
	        sprintf(buffer,"%s %ld.%03d\n",
					MSGUIDL,p->session_time,p->uidl_seq++);
		if (fputs(buffer, p->drop) == EOF) {
                    return pop_msg (p,POP_FAILURE,
                        "Unable to copy mail spool file to temp pop dropbox %s",
                            p->temp_drop);
		}

	        mp->length += strlen(buffer);
	        p->drop_size += strlen(buffer);
	        mp->lines++;

		strcpy(buffer, "\n");
		nchar = 1;
	    }
	}

	if (fputs(buffer, p->drop) == EOF) {
	    return pop_msg (p,POP_FAILURE,
		"Unable to copy mail spool file to temp pop dropbox %s",
		    p->temp_drop);
	}

	if (simple_from ? (strncmp(buffer,"From ",5) == 0) : isfromline(buffer)) {
            if (++msg_num > p->msg_count) {
                p->mlp=(MsgInfoList *) realloc(p->mlp,
                    (p->msg_count+=ALLOC_MSGS)*sizeof(MsgInfoList));
                if (p->mlp == NULL){
                    (void)close (mfd);
                    (void)close (dfd);
                    p->msg_count = 0;
                    return pop_msg (p,POP_FAILURE,
                        "Can't build message list for '%s': Out of memory",
                            p->user);
                }
                mp = p->mlp + msg_num - 2;
            }
#ifdef DEBUG
            if(p->debug && msg_num != 1)
                pop_log(p,POP_DEBUG,
                    "Msg %d at offset %d is %d octets long and has %u lines.",
                        mp->number,mp->offset,mp->length,mp->lines);
#endif
            ++mp;
            mp->number = msg_num;
            mp->length = 0;
            mp->lines = 0;
            mp->offset = ftell(p->drop) - nchar;
            mp->del_flag = FALSE;
            mp->retr_flag = FALSE;
#ifdef DEBUG
            if(p->debug)
                pop_log(p,POP_DEBUG, "Msg %d being added to list", mp->number);
#endif
	    inheader = 1;
	    uidlfound = 0;
        } else if (inheader && !strncasecmp(buffer, MSGUIDL, strlen(MSGUIDL))) {
	    uidlfound = 1;
	}

	if (inheader && (strncasecmp(buffer,"Status:",7) == 0)) {
	    if (index(buffer, 'R') != NULL)
		mp->retr_flag = TRUE;
	}

        mp->length += nchar;
        p->drop_size += nchar;
        mp->lines++;
    }

    p->msg_count = msg_num;

#ifdef DEBUG
    if(p->debug && msg_num > 0) {
        register    i;
        for (i = 0, mp = p->mlp; i < p->msg_count; i++, mp++)
            pop_log(p,POP_DEBUG,
                "Msg %d at offset %d is %d octets long and has %u lines.",
                    mp->number,mp->offset,mp->length,mp->lines);
    }
#endif

    if (fflush(p->drop) == EOF)
        return pop_msg(p,POP_FAILURE,"Flush of temp pop dropbox %s failed",
	    p->temp_drop);

    return(POP_SUCCESS);
}

/* 
 *  dropcopy:   Make a temporary copy of the user's mail drop and 
 *  save a stream pointer for it.
 */

pop_dropcopy(p,pwp)
POP     *   p;
struct passwd	*	pwp;
{
    int                     mfd;                    /*  File descriptor for 
                                                        the user's maildrop */
    int                     dfd;                    /*  File descriptor for 
                                                        the SERVER maildrop */
    FILE		    *tf;		    /*  The temp file */
    int			    tfn;		    
    char		    template[POP_TMPSIZE];  /*  Temp name holder */
    char                    buffer[BUFSIZ];         /*  Read buffer */
    long                    offset;                 /*  Old/New boundary */
    int                     nchar;                  /*  Bytes written/read */
    struct stat             mybuf;                  /*  For lstat() */

    /*  Create a temporary maildrop into which to copy the updated maildrop */
    (void)sprintf(p->temp_drop,POP_DROP,p->user);

#ifdef DEBUG
    if(p->debug)
        pop_log(p,POP_DEBUG,"Creating temporary maildrop '%s'",
            p->temp_drop);
#endif

    /* Here we work to make sure the user doesn't cause us to remove or
     * write over existing files by limiting how much work we do while
     * running as root.
     */

    /* First create a unique file.  Would prefer mkstemp, but Ultrix...*/
    strcpy(template,POP_TMPDROP);
    if (((tfn=mkstemp(template)) == -1) ||
	((tf=fdopen(tfn, "w+")) == NULL)) {	/* failure, bail out	*/
        pop_log(p,POP_PRIORITY,
            "Unable to create temporary temporary maildrop '%s': %s",template,
                (errno < sys_nerr) ? sys_errlist[errno] : "") ;
        return pop_msg(p,POP_FAILURE,
		"System error, can't create temporary file.");
    }

    /* Now give this file to the user	*/
    (void) chown(template,pwp->pw_uid, pwp->pw_gid);
    /* (void) chmod(template,0600); umask now handles this */

    /* Now link this file to the temporary maildrop.  If this fails it
     * is probably because the temporary maildrop already exists.  If so,
     * this is ok.  We can just go on our way, because by the time we try
     * to write into the file we will be running as the user.
     */
    (void) link(template,p->temp_drop);
    (void) fclose(tf);
    (void) unlink(template);

    /* Now we run as the user. */
    (void) setuid(pwp->pw_uid);
    (void) setgid(pwp->pw_gid);

#ifdef DEBUG
    if(p->debug)pop_log(p,POP_DEBUG,"uid = %d, gid = %d",getuid(),getgid());
#endif

    /* Open for append,  this solves the crash recovery problem */
    if ((dfd = open(p->temp_drop,O_RDWR|O_APPEND|O_CREAT,0600)) == -1){
        pop_log(p,POP_PRIORITY,
            "Unable to open temporary maildrop '%s': %s",p->temp_drop,
                (errno < sys_nerr) ? sys_errlist[errno] : "") ;
        return pop_msg(p,POP_FAILURE,
		"System error, can't open temporary file, do you own it?");
    }

    /*  Lock the temporary maildrop */
    if ( flock (dfd, LOCK_EX|LOCK_NB) == -1 ) {
	switch(errno) {
	    case EWOULDBLOCK:
		return pop_msg(p,POP_FAILURE,
		     "%s lock busy!  Is another session active?",p->temp_drop);
		/* NOTREACHED */
	    default:
		return pop_msg(p,POP_FAILURE,"flock: '%s': %s", p->temp_drop,
		    (errno < sys_nerr) ? sys_errlist[errno] : "");
		/* NOTREACHED */
	}
    }
    
    /* check for race conditions involving unlink.  See pop_updt.c */
    /* s-dorner@uiuc.edu, 12/91 */
    {
      struct stat byName, byFd;
      if (stat(p->temp_drop, &byName) || fstat(dfd, &byFd) ||
	  byName.st_ino != byFd.st_ino)
      {
        return pop_msg(p,POP_FAILURE,
		"Maildrop being unlocked; try again.");
      }
    }
    
    /*  Acquire a stream pointer for the temporary maildrop */
    if ( (p->drop = fdopen(dfd,"r+")) == NULL ) {
        (void)close(dfd) ;
        return pop_msg(p,POP_FAILURE,"Cannot assign stream for %s",
            p->temp_drop);
    }

    if (init_dropinfo(p, dfd) != POP_SUCCESS)
	return(POP_FAILURE);

    /*  Acquire a stream pointer for the temporary maildrop */
    if ( (p->drop = freopen(p->temp_drop,"a+",p->drop)) == NULL ) {
        (void)close(dfd) ;
        return pop_msg(p,POP_FAILURE,"Cannot reopen stream for %s",
            p->temp_drop);
    }

    /* Get the location of the end of the file */
    offset = lseek((int)fileno(p->drop),0,L_XTND);

    /*  Open the user's maildrop, If this fails,  no harm in assuming empty */
    if ((mfd = open(p->drop_name,O_RDWR)) > 0) {
        /*  Lock the maildrop */
        if (flock (mfd,LOCK_EX) == -1)
	{
            (void)close(mfd) ;
            return pop_msg(p,POP_FAILURE, "flock: '%s': %s", p->temp_drop,
                (errno < sys_nerr) ? sys_errlist[errno] : "");
        }

	/* New routine to copy and get dropbox info all at the same time */
	nchar = do_drop_copy(p, mfd, dfd);

        if ( nchar != POP_SUCCESS ) {
	    /* pop_dropcopy has integrated the info gathering pass into
	       the copy pass so now the information of the dropfile is
	       inconsistant if there is an error.  Now we exit instead
	       of trying to continue with what is available.
	    */
            (void)ftruncate(dfd,(int)offset) ;
	    return(nchar);
        } else {
            /* Mail transferred!  Zero the mail drop NOW,  that we
               do not have to do gymnastics to figure out what's new
               and what is old later */
            (void)ftruncate(mfd,0) ;
        }

        /*  Close the actual mail drop */
        (void)close (mfd);
    } 

    if ((p->bulldir != NULL) && (pop_bull(p, pwp) != POP_SUCCESS)) {
	/* Return pop drop back to what it was before the bulletins */
	(void)ftruncate(dfd,(int)offset);
    }

    return(POP_SUCCESS);
}

