/*
 * 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  1.2 7/13/90";
#endif not lint

#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <hesiod.h>
#include <netdb.h>
#include "popper.h"

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

#define LOCK_RETRY 3
#define LOCK_WAIT  5

#ifdef MOVE_MESSAGE
int check_for_move_message();
#endif

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

pop_dropcopy(p)
POP     *   p;
{
    int                     mfd;                    /*  File descriptor for 
                                                        the user's maildrop */
    int                     dfd;                    /*  File descriptor for 
                                                        the SERVER maildrop */
    char                    buffer[BUFSIZ];         /*  Read buffer */
    long                    offset;                 /*  Old/New boundary */
    int                     nchar;                  /*  Bytes written/read */
    int                     locked;                 /*  if drop is loced */
    int                     retry = 0;              /*  times to retry lock */

    /*  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 DEBUG

    /* Open for append,  this solves the crash recovery problem */
    if ((dfd = open(p->temp_drop,O_RDWR|O_APPEND|O_CREAT,0666)) == -1){
        pop_log(p,POP_ERROR,
            "%s: (%s) unable to create temporary maildrop: %s",
		p->client, p->temp_drop,
                (errno < sys_nerr) ? sys_errlist[errno] : "") ;
        return (POP_FAILURE);
    }

    /*  Lock the temporary maildrop. Try a few times if it doesn't succeed. */
    
    locked = 1;
    while(locked) {
        if ( flock (dfd, LOCK_EX|LOCK_NB) == -1 )            
	    switch(errno) {
	    case EWOULDBLOCK:
	      if(retry++ < LOCK_RETRY)  {
		  sleep(LOCK_WAIT);
		  continue;
	      }
	      pop_log(p, POP_PRIORITY, "%s: (%s) maildrop locked", 
		      p->client, p->temp_drop);
	      return pop_msg(p,POP_FAILURE,
		 "Maildrop for %s is busy. Please try again in a few moments.",
			     p->user);
	      /* NOTREACHED */

	    default:
	      pop_log(p, POP_PRIORITY, "%s: (%s) flock: %s", p->client, 
		      p->temp_drop,
		      (errno < sys_nerr) ? sys_errlist[errno] : "");
	      return pop_msg(p,POP_FAILURE,"flock: '%s': %s", p->temp_drop,
			     (errno < sys_nerr) ? sys_errlist[errno] : "");
	      /* NOTREACHED */
	    }
	else
	  locked = 0;
    }

    /* May have grown or shrunk between open and lock! */
    offset = lseek(dfd,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) ;
	    pop_log(p, POP_PRIORITY, "%s: (%s) flock: %s", p->client, 
		    p->drop_name,(errno < sys_nerr) ? sys_errlist[errno] : "");
            return pop_msg(p,POP_FAILURE, "flock: '%s': %s", p->temp_drop,
                (errno < sys_nerr) ? sys_errlist[errno] : "");
        }

        /*  Copy the actual mail drop into the temporary mail drop */
        while ( (nchar=read(mfd,buffer,BUFSIZ)) > 0 )
            if ( nchar != write(dfd,buffer,nchar) ) {
                nchar = -1 ;
                break ;
            }

        if ( nchar != 0 ) {
            /* Error adding new mail.  Truncate to original size,
               and leave the maildrop as is.  The user will not 
               see the new mail until the error goes away.
               Should let them process the current backlog,  in case
               the error is a quota problem requiring deletions! */
            (void)ftruncate(dfd,(int)offset) ;
        } 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);
    } else {
#ifdef MOVE_MESSAGE
	check_for_move_message(p, dfd);
#endif
    }

    /*  Acquire a stream pointer for the temporary maildrop */
    if ( (p->drop = fdopen(dfd,"a+")) == NULL ) {
        (void)close(dfd) ;
	pop_log(p, POP_PRIORITY, "%s: (%s) fdopen: %s", p->client, 
		p->temp_drop, (errno < sys_nerr) ? sys_errlist[errno] : "");
        return pop_msg(p,POP_FAILURE,"Cannot assign stream for %s",
            p->temp_drop);
    }

    rewind (p->drop);

    return(POP_SUCCESS);
}

#ifdef MOVE_MESSAGE
/*
 * This function is called if the user does not have a pobox file; it
 * checks against hesiod to see if the user is contacting the right
 * post office server.  If a hesiod record is found, and the po server
 * in the hesiod server is different from this post office, then try
 * to open the file MOVE_MESSAGE and copy it to the user's mailbox as
 * if it were a message.  This allows the user to receive a nice
 * message explaining that he/she is contacting the wrong host.  In
 * that file, if @H appears, it is substituted with the correct post
 * office server.  If @O appears, it is substituted with this po
 * server's hostname.  To display an '@' to the user, use @@.
 *
 * Written by tytso, 10/16/95
 */
int
check_for_move_message(p, dfd)
	POP *p;
	int dfd;
{
	struct hes_postoffice *	hesinfo;
	struct hostent *	h;
	struct sockaddr 	s1, s2;
	char                    buffer[BUFSIZ];         /*  Read buffer */
	char			*cp, *ats;
	FILE *			f;
	
	hesinfo = hes_getmailhost(p->client);
	if (!hesinfo || strcmp(hesinfo->po_type, "POP"))
		return (-1);

	/*
	 * Compare the address of the user's POP server with our
	 * address.  If they match, then the user is using the right
	 * mail server.  If they don't, then send them a message...
	 */
	if (!(h = gethostbyname(hesinfo->po_host)))
		return -1;
	memset(&s1, 0, sizeof(s1));
	s1.sa_family = h->h_addrtype;
	memcpy((char *)&s1.sa_data, h->h_addr, h->h_length);
	
	if (!(h = gethostbyname(p->myhost)))
		return -1;
	memset(&s2, 0, sizeof(s1));
	s2.sa_family = h->h_addrtype;
	memcpy((char *)&s2.sa_data, h->h_addr, h->h_length);
	
	if ((s1.sa_family == s2.sa_family) &&
	    memcmp(s1.sa_data, s2.sa_data, sizeof(s1.sa_data)) == 0)
		return -1;

	/*
	 * OK, the user must be on a different server.  Let's send her
	 * a special message....
	 */
	if ((f = fopen(MOVE_MESSAGE, "r")) == NULL)
		return -1;
	while (fgets(buffer, sizeof(buffer), f)) {
		cp = buffer;
		while ((ats = strchr(cp, '@'))) {
			*ats++ = '\0';
			write(dfd, cp, strlen(cp));
			if (*ats == 'H')
				write(dfd, hesinfo->po_host,
				      strlen(hesinfo->po_host));
			else if (*ats == 'O')
				write(dfd, p->myhost, strlen(p->myhost));
			else
				write(dfd, ats, 1);
			if (*ats)
				ats++;
			cp = ats;
		}
		write(dfd, cp, strlen(cp));
	}
	fclose(f);
	return 0;
}
#endif
