/*
 * $Id: vdelivermail.c,v 1.11.2.8 2006/06/29 19:36:43 tomcollins Exp $
 * Copyright (C) 1999-2003 Inter7 Internet Technologies, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

/* code review/cleanup/rewrite October 2004 through March, 2005
 * by Tom Collins <tom@tomlogic.com>
 *
 * maildrop mailfilter support added
 * by Andre Beckedorf <eviljazz@katastrophos.net> 2003-2006
 * http://www.katastrophos.net/andre/blog
 * (some parts based on some other code which source I forgot)
 * 
 */

/* include files */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include "config.h"
#include "vpopmail.h"
#include "vauth.h"
#include "maildirquota.h"
#ifdef MAKE_SEEKABLE
#include "seek.h"
#endif

/* Globals */
#define AUTH_SIZE 600
char TheUser[AUTH_SIZE];
char TheUserFull[AUTH_SIZE];
char TheDomain[AUTH_SIZE];
char TheDomainDir[AUTH_SIZE];
uid_t TheDomainUid;
gid_t TheDomainGid;
char TheDir[AUTH_SIZE];
char Cmd[AUTH_SIZE];
char Dir[AUTH_SIZE];
char CurrentDir[AUTH_SIZE];
char DeliveredTo[AUTH_SIZE];
struct vqpasswd *vpw;
ssize_t message_size = 0;
char bounce[AUTH_SIZE];
int CurrentQuotaSizeFd;

#ifdef QMAIL_EXT
/* the User with '-' and following chars out if any */
char TheUserExt[AUTH_SIZE]; 
#endif

#define FILE_SIZE 156
char loop_buf[FILE_SIZE];

#define MSG_BUF_SIZE 5000
char msgbuf[MSG_BUF_SIZE];

#define BUFF_SIZE 300
int fdm;

#define EXIT_BOUNCE 100
#define EXIT_DEFER 111
#define EXIT_OK 0
#define EXIT_OVERQUOTA EXIT_BOUNCE

#define MAILDROP "/usr/local/bin/maildrop"

/* from qmail's wait.h for run_command() */
#define wait_exitcode(w) ((w) >> 8)

/* Forward declarations */
int process_valias(void);
void get_arguments(int argc, char **argv);
ssize_t get_message_size();
void deliver_mail(char *address, char *quota);
int check_forward_deliver(char *dir);
int is_looping( char *address );
void run_command(char *prog);
void checkuser(void);
void usernotfound(void);
int domain_has_mailfilter(void);
void usemaildrop(void);
void postprocess(void);
int is_loop_match( const char *dt, const char *address);
int deliver_quota_warning(const char *dir, const char *q);


/* print an error string and then exit
 * vexit() never returns, so vexiterr() and vexit() should actually return void
 */
int vexiterr (int err, char *errstr)
{
    if (errstr) printf ("%s\n", errstr);
    return vexit(err);
}

/* 
 * The email message comes in on file descriptor 0 - stdin
 * The user to deliver the email to is in the EXT environment variable
 * The domain to deliver the email to is in the HOST environment variable
 */
int main(int argc, char **argv)
{
    /* get the arguments to the program and setup things */
    get_arguments(argc, argv);
 
#ifdef VALIAS
    /* process valiases if configured */
    if ( process_valias() == 1 )
        vexiterr (EXIT_OK, "vdelivermail: valiases processed");

    /* if the database is down, deferr */
    if ( verrori == VA_NO_AUTH_CONNECTION )
        vexiterr (EXIT_DEFER, "vdelivermail: deferred, database down");
#endif

    /* get the user from vpopmail database */
    if ((vpw=vauth_getpw(TheUser, TheDomain)) != NULL ) {
	    if (domain_has_mailfilter()) {
	        usemaildrop();
        } else checkuser();
    } 

#ifdef QMAIL_EXT
    /* try and find user that matches the QmailEXT address if: no user found,
     * and the QmailEXT address is different, meaning there was an extension 
     */
    else if ( strcmp(TheUser, TheUserExt) != 0 ) {
        /* get the user from vpopmail database */
        if ((vpw=vauth_getpw(TheUserExt, TheDomain)) != NULL ) {
            if (domain_has_mailfilter()) {  
                usemaildrop();
            } else checkuser();
        } else {
            usernotfound();
        }
    }
#endif

   /* Additions by Steve Fulton - steve@esoteric.ca 
    * - added error code handling for error codes generated by Autoresponder
    * 99 = deliver mail
    * 100 = bounce mail
    */ 

    else if ( verrori == 100) vexit(EXIT_BOUNCE);

    else if ( verrori == 99) vexit(EXIT_OK);

   /* End of additions by Steve Fulton */

    else if ( verrori != 0 ) vexit(EXIT_DEFER);

    else usernotfound();

    /* exit successfully and have qmail delete the email */
    return(vexit(EXIT_OK));
            
}

/* 
 * Get the command line arguments and the environment variables.
 * Force addresses to be lower case and set the default domain
 */
void get_arguments(int argc, char **argv)
{
 char *tmpstr; 

    if (argc != 3) {
        /* exit with temporary error */
        vexiterr (EXIT_DEFER, "vdelivermail: invalid syntax in .qmail-default file");
    }

    /* get the last parameter in the .qmail-default file */
    strncpy(bounce, argv[2], sizeof(bounce)); 

    if ((tmpstr=getenv("EXT")) == NULL ) {
        vexiterr (EXIT_BOUNCE, "vdelivermail: no EXT environment varilable");
    }
    snprintf (TheUser, sizeof(TheUser), "%s", tmpstr);

    if ((tmpstr=getenv("HOST")) == NULL ) {
        vexiterr (EXIT_BOUNCE, "vdelivermail: no HOST environment varilable");
    }
    snprintf (TheDomain, sizeof(TheDomain), "%s", tmpstr);
    
    postprocess();
}

void postprocess(void)
{
#ifdef QMAIL_EXT
 int i;
#endif

    lowerit(TheUser);
    lowerit(TheDomain);

    if ( is_username_valid(TheUser) != 0 )
        vexiterr (EXIT_BOUNCE, "invalid username");
    if ( is_domain_valid(TheDomain) != 0 )
        vexiterr (EXIT_BOUNCE, "invalid domain name");

    strncpy(TheUserFull, TheUser, sizeof(TheUserFull));

#ifdef QMAIL_EXT
    /* !! Shouldn't this work its way backwards, and try all possibilities?
     * e.g., a-b-c-d should try a-b-c then a-b then a, instead of just a?
     */
    /* delete the '-' and following chars if any and store in TheUserExt */
    for(i = 0; (TheUser[i] != 0) && (TheUser[i] != '-' ); i++) {
        TheUserExt[i] = TheUser[i];
    }
    TheUserExt[i] = 0;

    if ( is_username_valid(TheUserExt) != 0 ) {
        vexit(EXIT_BOUNCE);
    }

#endif

    vget_assign(TheDomain, TheDomainDir, sizeof(TheDomainDir), &TheDomainUid, &TheDomainGid);

}

#ifdef VALIAS
/* 
 * Process any valiases for this user@domain
 * 
 * This will look up any valiases in vpopmail and
 * deliver the email to the entries
 *
 * Return 1 if aliases found
 * Return 0 if no aliases found 
 */
int process_valias(void)
{
 int found = 0;
 char *tmpstr;
 char tmpuser[350];
 char def[150];
 int i;
 int j=0;

    /* Get the first alias for this user@domain */
    tmpstr = valias_select( TheUser, TheDomain );

    /* check for wildcard if there's no match */
    if(tmpstr == NULL) {
        for(i=strlen(TheUser);i >= 0 && j != 1;--i) {
            if(TheUser[i-1]=='-') {
                tmpuser[0] = '\0';
                strncat(tmpuser,TheUser,i); 
                strcat(tmpuser, "default");
                tmpstr = valias_select( tmpuser, TheDomain );
                if(tmpstr != NULL) {
                    sprintf(def, "DEFAULT=%s", &TheUser[i]);
                    putenv(def);
                    j = 1;
                }
            }
        }
    }

    /* tmpstr will be NULL if there are no more aliases */
    while (tmpstr != NULL ) {

        /* We found one */
        found = 1;

        /* deliver the mail */
        deliver_mail(tmpstr, "");

        /* Get the next alias for this user@domain */
        tmpstr = valias_select_next();
    }

#ifdef QMAIL_EXT 
    /* try and find alias that matches the QmailEXT address 
     * if: no alias found, 
     * and the QmailEXT address is different, meaning there was an extension 
     */
    if ( (!found) && ( strcmp(TheUser, TheUserExt) != 0 )  ) {
        /* Get the first alias for this user@domain */
        tmpstr = valias_select( TheUserExt, TheDomain );

        /* tmpstr will be NULL if there are no more aliases */
        while (tmpstr != NULL ) {

            /* We found one */
            found = 1;

            /* deliver the mail */
            deliver_mail(tmpstr, "");

            /* Get the next alias for this user@domain */
            tmpstr = valias_select_next();
        } 
    }    
#endif

    /* Return whether we found an alias or not */
    return(found);
}
#endif

/* Forks off qmail-inject.  Returns PID of child, or 0 for failure. */
pid_t qmail_inject_open(char *address)
{
 int pim[2];
 pid_t pid;
 static char *binqqargs[4];

    if ( pipe(pim) == -1) return 0;

    switch(pid=vfork()){
      case -1:
        close(pim[0]);
        close(pim[1]);
        printf ("Unable to fork: %d.", errno);
        return 0;
      case 0:
        close(pim[1]);
        if (vfd_move(0,pim[0]) == -1 ) _exit(-1);
        binqqargs[0] = QMAILINJECT;
        binqqargs[1] = "--";
        binqqargs[2] = (*address == '&' ? &address[1] : &address[0]);
        execv(*binqqargs, binqqargs);
        printf ("Unable to launch qmail-inject.");
        exit (EXIT_DEFER);    /* child's exit caught later */
    }
    fdm = pim[1];
    close(pim[0]);
    return(pid);
}

int domain_has_mailfilter(void)
{
    FILE *fs;
    snprintf(Dir, AUTH_SIZE, "%s/mailfilter", TheDomainDir);
    if ( (fs = fopen(Dir,"r")) == NULL ) {
        return(0);
    }
    fclose(fs);
    return(1);
}

void usemaildrop(void)
{
    /* printf("using mailfilter at %s/mailfilter \n", TheDomainDir); */
    snprintf(Cmd, AUTH_SIZE, "| /usr/bin/env HOME=%s VDOMAINDIR=%s VUSERDIR=%s VUSER=%s VDOMAIN=%s preline \"%s\" -V 2 %s/mailfilter",
      TheDomainDir, TheDomainDir, vpw->pw_dir, TheUser, TheDomain, MAILDROP, TheDomainDir);

    printf(Cmd);
        
    deliver_mail(Cmd, "");
} 

int fdcopy (int write_fd, int read_fd, const char *extra_headers, size_t headerlen)
{
  char msgbuf[4096];
  ssize_t file_count;

    /* write the Return-Path: and Delivered-To: headers */
    if (headerlen > 0) {
        if (write(write_fd, extra_headers, headerlen) != headerlen) return -1;
    }
    
    /* read it in chunks and write it to the new file */
    while ((file_count = read(read_fd, msgbuf, sizeof(msgbuf))) > 0) {
        if ( write(write_fd, msgbuf, file_count) == -1 ) return -1;
    }
    
    return 0;
}

void read_quota_from_maildir (const char *maildir, char *qbuf, size_t qlen)
{
  FILE *quota_file;
  char quotafn[FILE_SIZE];
  char *newline;

    if (qlen == 0) return;
    
    *qbuf = '\0';
    snprintf (quotafn, sizeof(quotafn), "%s/maildirsize", maildir);
    quota_file = fopen (quotafn, "r");
    if (quota_file == NULL) {
      /* add code here to use current user's quota if maildir matches */
      if (vpw == NULL) {
        // fprintf (stderr, "Don't know quota for %s\n", maildir);
      } else {
        char *usermaildir;
        
        usermaildir = malloc(strlen(vpw->pw_dir) + sizeof("/Maildir") + 1);
        if (usermaildir) {
          sprintf (usermaildir, "%s/Maildir", vpw->pw_dir);
          if (strcmp (usermaildir, maildir) == 0)
          	snprintf (qbuf, qlen, "%s", vpw->pw_shell);
          // fprintf (stderr, "Quota for %s (from user entry) is %s\n", maildir, quota);
          free (usermaildir);
        }
      }
    } else {
      fgets (qbuf, qlen, quota_file);
      fclose (quota_file);
      newline = strchr (qbuf, '\n');
      if (newline != NULL) *newline = '\0';
      // fprintf (stderr, "Quota for %s (from maildirsize) is %s\n", maildir, quota);
    }
}

/* save email in maildir, starting with extra_headers and then msgsize bytes
 * read from read_fd
 *
 * Return: 0 = success, -1 = over quota, -2 = system failure, -3 = looping
 */
 
int deliver_to_maildir (
    const char *maildir,
    const char *extra_headers,
    int read_fd,
    ssize_t msgsize)
{
#ifdef HOST_NAME_MAX
  char hostname[HOST_NAME_MAX];
#else
  char hostname[256];
#endif
  long unsigned pid, tm;
  char local_file_tmp[FILE_SIZE];
  char local_file_new[FILE_SIZE];
  size_t headerlen;
  int write_fd;
  char quota[80];

    headerlen = strlen (extra_headers);
    msgsize += headerlen;
    
    /* Format the email file name */
    /* we get the whole hostname, even though we only use 32 chars of it */
    gethostname(hostname, sizeof(hostname));
    pid = (long unsigned) getpid();
    tm = (long unsigned) time((time_t *) NULL);
    snprintf(local_file_tmp, sizeof(local_file_tmp), "%stmp/%lu.%lu.%.32s,S=%lu",
        maildir, tm, pid, hostname, (long unsigned) msgsize);
    snprintf(local_file_new, sizeof(local_file_new), "%snew/%lu.%lu.%.32s,S=%lu",
        maildir, tm, pid, hostname, (long unsigned) msgsize);

    read_quota_from_maildir (maildir, quota, sizeof(quota));

    /* open the new email file */
    if ((write_fd=open(local_file_tmp, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR)) == -1) {
        if (errno == EDQUOT) return -1;
        printf("can not open new email file errno=%d file=%s\n", 
            errno, local_file_tmp);
        return(-2);
    }

    if (fdcopy (write_fd, read_fd, extra_headers, headerlen) != 0) {
        /* Did the write fail because we were over quota? */
        if ( errno == EDQUOT ) {
            close(write_fd);
            unlink (local_file_tmp);
            return -1;
        } else {
            printf ("write failed errno = %d\n", errno);
            close(write_fd);
            unlink (local_file_tmp);
            return -2;
        }
    }

    /* completed write to tmp directory, now move it into the new directory */

    /* sync the data to disk and close the file */
    errno = 0;
    if ( 
#ifdef FILE_SYNC
#ifdef HAVE_FDATASYNC
        fdatasync (write_fd) == 0 &&
#else
        fsync (write_fd) == 0 &&
#endif
#endif
        close (write_fd) == 0 ) {

        /* if this succeeds link the file to the new directory */
        if ( link( local_file_tmp, local_file_new ) == 0 ) {
            /* file was successfully delivered, remove temp file */
            if ( unlink(local_file_tmp) != 0 ) {
                /* not a critical error */
                printf("unlink failed %s errno = %d\n", local_file_tmp, errno);
            }
            if (*quota) maildir_addquota (maildir, -1, quota, msgsize, 1);
            return 0;
        } else if (rename(local_file_tmp, local_file_new) == 0) {
            /* file was successfully delivered */
            if (*quota) maildir_addquota (maildir, -1, quota, msgsize, 1);
            return 0;
        } else {
            /* even rename failed, time to give up */
            printf("rename failed %s %s errno = %d\n", 
                local_file_tmp, local_file_new, errno);
            unlink(local_file_tmp);  /* remove temp file */
            return -2;
        }
    }

    /* return failure (sync/close failed, message NOT delivered) */
    return -2;
}

/* 
 * Deliver an email to an address
 * calls vexit on failure
 */
void deliver_mail(char *address, char *quota)
{
 pid_t inject_pid = 0;
 int child;
 unsigned int xcode;
 FILE *fs;
 char tmp_file[256];
 char maildirquota[80];

    /* This is a comment, ignore it */
    if ( *address == '#' ) return;

    /* check if the email is looping to this user */
    if ( is_looping( address ) == 1 ) {
        vexiterr (EXIT_BOUNCE, "mail is looping");
    }
    
#ifdef MAKE_SEEKABLE
    /* at this point we know the message size - if its 0 drop out silently */
    if (message_size==0)
        return;
#endif

    /* rewind the message */
    lseek(0,0L,SEEK_SET);

    /* This is an command */
    if ( *address == '|' ) { 

        /* run the command */ 
        run_command(address);
        return;
      }

    /* Starts with '.' or '/', then it's an mbox or maildir delivery */
    else if ((*address == '.') || (*address == '/')) {
        /* check for mbox delivery and exit accordingly */
        if (address[strlen(address)-1] != '/') {
            printf ("can't handle mbox delivery for %s", address);
            vexit(EXIT_DEFER);
        }

        if ((quota == NULL) || (*quota == '\0')) {
          read_quota_from_maildir (address, maildirquota, sizeof(maildirquota));
          quota = maildirquota;
        }
        
        /* if the user has a quota set */
        if ( strncmp(quota, "NOQUOTA", 2) != 0 ) {

            /* If the user is over their quota, return it back
             * to the sender.
             */
            if (user_over_maildirquota(address,format_maildirquota(quota))==1) {

                /* check for over quota message in domain */
                sprintf(tmp_file, "%s/.over-quota.msg",TheDomainDir);
                if ( (fs=fopen(tmp_file, "r")) == NULL ) {
                    /* if no domain over quota then check in vpopmail dir */
                    sprintf(tmp_file, "%s/%s/.over-quota.msg",VPOPMAILDIR,DOMAINS_DIR);
                    fs=fopen(tmp_file, "r");
                }

                if ( fs == NULL ) {
                    printf("user is over quota\n");
                } else {
                    while( fgets( tmp_file, sizeof(tmp_file), fs ) != NULL ) {
                        fputs(tmp_file, stdout);
                    }
                    fclose(fs);
                }
                deliver_quota_warning(address, format_maildirquota(quota));
                vexiterr (EXIT_OVERQUOTA, "");
            }
            if (QUOTA_WARN_PERCENT >= 0 &&
                vmaildir_readquota(address, format_maildirquota(quota))
                    >= QUOTA_WARN_PERCENT) {
                deliver_quota_warning(address, format_maildirquota(quota));
            }
        }

#ifdef DOMAIN_QUOTAS
    /* bk: check domain quota */
        if (domain_over_maildirquota(address)==1)
        {
            /* check for over quota message in domain */
            sprintf(tmp_file, "%s/.over-quota.msg",TheDomainDir);
            if ( (fs=fopen(tmp_file, "r")) == NULL ) {
                /* if no domain over quota then check in vpopmail dir */
                sprintf(tmp_file, "%s/%s/.over-quota.msg",VPOPMAILDIR,DOMAINS_DIR);
                fs=fopen(tmp_file, "r");
            }

            if ( fs == NULL ) {
                printf("domain is over quota\n");
            } else {
                while( fgets( tmp_file, sizeof(tmp_file), fs ) != NULL ) {
                    fputs(tmp_file, stdout);
                }
                fclose(fs);
            }
            deliver_quota_warning(address, format_maildirquota(quota));
            vexiterr (EXIT_OVERQUOTA, "");
        }
#endif

        /* Set the Delivered-To: header */
        if ( strcmp( address, bounce) == 0 ) {
            snprintf(DeliveredTo, sizeof(DeliveredTo), 
                "%sDelivered-To: %s@%s\n", getenv("RPLINE"), 
                 TheUser, TheDomain);
        } else {
            snprintf(DeliveredTo, sizeof(DeliveredTo), 
                "%sDelivered-To: %s\n", getenv("RPLINE"), 
                maildir_to_email(address));
        }
    
        switch (deliver_to_maildir (address, DeliveredTo, 0, message_size)) {
            case -1:
                vexiterr (EXIT_OVERQUOTA, "user is over quota");
                break;
            case -2:
                vexiterr (EXIT_DEFER, "system error");
                break;
            case -3:
                vexiterr (EXIT_BOUNCE, "mail is looping");
                break;
            default:
                return;
        }

    /* must be an email address */
    } else {
      char *dtline;
      char *atpos;
      int dtlen;

      if (*address=='&') ++address;  /* will this case ever happen? */
      inject_pid = qmail_inject_open(address);
      if (inject_pid == 0) vexiterr (EXIT_DEFER, "system error, can't open qmail-inject");
      
      /* use the DTLINE variable, but skip past the dash in 
       * domain-user@domain 
       */

      if ( (dtline = getenv("DTLINE")) != NULL ) {
        dtlen = strlen(dtline);
        /* make sure dtline is at least as long as "Delivered-To: " */
        if (dtlen < 15) {
          dtline = NULL;
        } else {
          dtline += 14;  /* skip "Delivered-To: " */
          dtlen -= 14;
          atpos = strchr (dtline, '@');
          /* ex: dtline = "x-y-z.com-fred@x-y-z.com\n"
           * dtlen = 25, atpos = dtline+14
           * add 25 - 14 - 1 = 10 bytes to dtline,
           * now points to "fred@x-y-z.com\n".
           */
          if (atpos != NULL) {
            dtline += (dtlen - (atpos - dtline) - 1);
          }
        }
      }
      if (dtline == NULL) {
        snprintf(DeliveredTo, sizeof(DeliveredTo), 
          "%sDelivered-To: %s\n", getenv("RPLINE"), address);
      } else {
        snprintf(DeliveredTo, sizeof(DeliveredTo), 
          "%sDelivered-To: %s", getenv("RPLINE"), dtline);
      }
      
      if (fdcopy (fdm, 0, DeliveredTo, strlen(DeliveredTo)) != 0) {
          printf ("write to qmail-inject failed: %d\n", errno);
          close(fdm);
          waitpid(inject_pid,&child,0);
          vexiterr (EXIT_DEFER, "system error calling qmail-inject");
      }

      close(fdm);
      waitpid(inject_pid,&child,0);
      xcode = wait_exitcode(child);
      if (xcode == 0) return;
      vexiterr (xcode, "system error calling qmail-inject");
    }
}


/* Check if the vpopmail user has a .qmail file in their directory
 * and foward to each email address, Maildir or program 
 *  that is found there in that file
 *
 * Return: 1 if we found and delivered email
 *       : 0 if not found
 *       : -1 if no user .qmail file 
 *
 */
int check_forward_deliver(char *dir)
{
 static char qmail_line[500];
 char tmpbuf[500];
 FILE *fs;
 int i;
 int return_value = 0;
   
    chdir(dir);

    /* format the file name */
    if ( (fs = fopen(".qmail","r")) == NULL ) {

        /* no file, so just return */
        return(-1);
    }

    /* format a simple loop checker name */
    snprintf(tmpbuf, sizeof(tmpbuf), "%s@%s", TheUser, TheDomain);

    /* read the file, line by line */
    while ( fgets(qmail_line, sizeof(qmail_line), fs ) != NULL ) {
        if (*qmail_line == '#') {
            return_value = 1;
            continue;
        }

        /* remove the trailing new line */
        for(i=0;qmail_line[i]!=0;++i) {
            if (qmail_line[i] == '\n') qmail_line[i] = 0;
        }

        /* simple loop check, if they are sending it to themselves
         * then skip this line
         */
        if ( strcmp( qmail_line, tmpbuf) == 0 ) continue;
        /* check for &user@example.com as well */
        if ((*qmail_line == '&') && (strcmp (qmail_line + 1, tmpbuf) == 0))
            continue;

        deliver_mail(qmail_line, "");

        return_value = 1;
    }

    /* close the file */
    fclose(fs);

    /* return if we found one or not */
    return(return_value);
}

void sig_catch(sig,f)
int sig;
void (*f)();
{
#ifdef HAVE_SIGACTION
  struct sigaction sa;
  sa.sa_handler = f;
  sa.sa_flags = 0;
  sigemptyset(&sa.sa_mask);
  sigaction(sig,&sa,(struct sigaction *) 0);
#else
  signal(sig,f); /* won't work under System V, even nowadays---dorks */
#endif
}


/* open a pipe to a command 
 * return the pid or -1 if error
 */
void run_command(char *prog)
{
 int child;
 char *(args[4]);
 int wstat;

 while ((*prog == ' ') || (*prog == '|')) ++prog;

 switch(child = fork())
  {
   case -1:
     printf("Unable to fork: %d.", errno); 
     vexit(EXIT_DEFER);
   case 0:
     args[0] = "/bin/sh"; args[1] = "-c"; args[2] = prog; args[3] = 0;
     sig_catch(SIGPIPE,SIG_DFL);
     execv(*args,args);
     printf("Unable to run /bin/sh: %d.", errno);
     exit(EXIT_DEFER);    /* the child's exit code will get caught below */
  }

  wait(&wstat);
  waitpid(wstat,&child,0);
  switch(wait_exitcode(wstat))
   {
    case 64: case 65: case 70: case 76: case 77: case 78: case 100: case 112: vexit(EXIT_BOUNCE);
    case 99: vexit(99);	/* not sure about this, when does it exit 99? */
    case 0: break;
    default: vexit(EXIT_DEFER);
   }

}

/* Check for a looping message
 * This is done by checking for a matching line
 * in the email headers for Delivered-To: which
 * we put in each email
 * 
 * Return 1 if looping
 * Return 0 if not looping
 * Return -1 on error 
 */
int is_looping( char *address ) 
{
 char *dtline;

    /* if we don't know the message size then get it */
    if ( message_size == 0 ) {
        /* read the message to get the size */
        message_size = get_message_size();
    }
    if (*address=='&') ++address;

    /* check the DTLINE */
    dtline = getenv("DTLINE");
    if ( dtline != NULL && is_loop_match(dtline, address) == 1 ) {
      return(1);
    }

#ifdef MAKE_SEEKABLE
    if (!Seekable(0))
        MakeSeekable(stdin);
#endif

    lseek(0,0L,SEEK_SET);
    while(fgets(loop_buf,sizeof(loop_buf),stdin)!=NULL){

        /* if we find the line, return error (looping) */
        if (is_loop_match(loop_buf, address)==1 ) {
            /* return the loop found */
            return(1);
        } else if (*loop_buf == '\r' || *loop_buf == '\n') {
            /* end of headers return not found looping message value */
            return(0);
        }
    }

    /* if we get here then the there is either no body 
     * or the logic above failed and we scanned
     * the whole email, headers and body. 
     */
    return(0);
}

/* 
 * Get the size of the email message 
 * return the size 
 */
ssize_t get_message_size()
{
 ssize_t message_size;
 ssize_t bytes;

#ifdef MAKE_SEEKABLE
    if (!Seekable(0))
        MakeSeekable(stdin);
#endif

    if ( lseek(0, 0L,SEEK_SET) < 0 ) {
        printf("lseek error %d\n", errno);
        return(-1);
    }

    /* couldn't we lseek to SEEK_END and use return value of lseek? */
    
    message_size = 0;
    while((bytes=read(0,msgbuf,sizeof(msgbuf)))>0) {
        message_size += bytes;
    }
    
    /* rewind file */
    lseek (0, 0L, SEEK_SET);
    
    return(message_size);
}

/*
 * check for locked account
 * deliver to .qmail file if any
 * deliver to user if no .qmail file
 */
void checkuser() 
{
 struct stat mystatbuf;

    if (vpw->pw_gid & BOUNCE_MAIL ) {
        printf("vdelivermail: account is locked email bounced %s@%s\n",
            TheUser, TheDomain);
        vexit(EXIT_BOUNCE);
    }

    /* If their directory path is empty make them a new one */
    if ( vpw->pw_dir == NULL || vpw->pw_dir[0]==0 ) {
        if ( make_user_dir(vpw->pw_name, TheDomain, 
               TheDomainUid, TheDomainGid)==NULL){
            vexiterr (EXIT_BOUNCE, "Auto creation of maildir failed. (#5.9.8)");
        }
        /* Re-read the vpw entry, because we need to lookup the newly created
         * pw_dir entry
         */
        if ((vpw=vauth_getpw(TheUser, TheDomain)) == NULL ) {
           vexiterr (EXIT_BOUNCE, "Failed to vauth_getpw(). (#5.9.8.1)");
        }
    }

    /* check if the directory exists and create if needed */
    if ( stat(vpw->pw_dir, &mystatbuf ) == -1 ) {
        if ( vmake_maildir(TheDomain, vpw->pw_dir )!= VA_SUCCESS ) {
            vexiterr (EXIT_BOUNCE, "Auto re-creation of maildir failed. (#5.9.9)");
        }
    }

    /* check for a .qmail file in their Maildir
     * If it exists, then deliver to the contents and exit
     */
    if ( check_forward_deliver(vpw->pw_dir) == 1 ) {
        vexit(EXIT_OK);
    }

    snprintf(TheDir, sizeof(TheDir), "%s/Maildir/", vpw->pw_dir);

    deliver_mail(TheDir, vpw->pw_shell);
}


/*
 * the vpopmail user does not exist. Follow the rest of
 * the directions in the .qmail-default file
 */
void usernotfound() 
{
 char DomainsDir[AUTH_SIZE] = "";
 char *pbegin;
 char *pend;
 int already_processed = 0;

    /* read the full message to avoid SIGPIPE if maildrop is calling us */
    if (message_size == 0) message_size = get_message_size();

    if ( strcmp(bounce, DELETE_ALL) == 0 ) {
      /* If they want to delete email for non existant users
       * then just exit 0. Qmail will delete the email for us
       */

        vexit(EXIT_OK);

    } else if ( strcmp(bounce, BOUNCE_ALL) == 0 ) {
      /* If they want to bounce the email back then
       * print a message and exit 100
       */
      FILE *fs;
      char tmp_file[256];

        sprintf(tmp_file, "%s/.no-user.msg",TheDomainDir);
        if ( (fs=fopen(tmp_file, "r")) == NULL ) {
            /* if no domain no user then check in vpopmail dir */
            sprintf(tmp_file, "%s/%s/.no-user.msg",VPOPMAILDIR,DOMAINS_DIR);
            fs=fopen(tmp_file, "r");
        }
        if ( fs == NULL ) {
             printf("Sorry, no mailbox here by that name. (#5.1.1)\n");
        } else {
            while( fgets( tmp_file, sizeof(tmp_file), fs ) != NULL ) {
               fputs(tmp_file, stdout);
            }
            fclose(fs);
        }

        vexit(EXIT_BOUNCE);

    }

    /* check if it is a path add the /Maildir/ for delivery */
    if ( bounce[0] == '/' ) {
        if (bounce[strlen(bounce)-1] != '/') strcat( bounce, "/");
        printf ("user does not exist, but will deliver to %s\n", bounce);
        if( check_forward_deliver(bounce) == 1 )
            vexit(EXIT_OK);
        else
		{
            strcat(DomainsDir, VPOPMAILDIR);
            strcat(DomainsDir, "/" DOMAINS_DIR "/");

            /* check if directory is subdirectory of VPOPMAILDIR/domains/ */
            pbegin = strstr(bounce, DomainsDir);
            if (pbegin != NULL) {
                pbegin = pbegin + strlen(DomainsDir);
                pend = strchr(pbegin, '/');
                
                if (pend != NULL) {
                    strncpy(TheDomain, pbegin, pend - pbegin);
                    TheDomain[pend - pbegin] = 0;
                                       
                    pbegin = pend + 1;
                    pend = strchr(pbegin, '/');
                    
                    if (pend != NULL) {
                        strncpy(TheUser, pbegin, pend - pbegin);
                        TheUser[pend - pbegin] = 0;
			/*
                        printf("TheUser: %s\n", TheUser);
                        printf("TheDomain: %s\n", TheDomain);
			*/
                        strcpy(bounce, BOUNCE_ALL);
                        bounce[strlen(BOUNCE_ALL)] = 0;
                        /*
			printf("bounce: %s \n", bounce);
			*/
                        
                        /* validate new TheUser and TheDomain data and
                           get additional data */
                        postprocess();

                        /* get the user from vpopmail database */
                        if ((vpw=vauth_getpw(TheUser, TheDomain)) != NULL ) {
                            if (domain_has_mailfilter()) {
                                usemaildrop();
                                already_processed = 1;                                
                            } else {
                                strcat( bounce, "Maildir/");                            
                            }
                        } 
                        /*
                        printf("TheUser: %s\n", TheUser);
                        printf("TheDomain: %s\n", TheDomain);
                        printf("TheDomainDir: %s\n", TheDomainDir);
                        printf("vpw->pw_dir: %s\n", vpw->pw_dir);
                        printf("TheUserFull: %s\n", TheUserFull);
			*/
                    } else { /* on failure fallback */
                        strcat( bounce, "Maildir/");
                    }
                } else { /* on failure fallback */
                    strcat( bounce, "Maildir/");                
                }
            } else {
                strcat( bounce, "Maildir/");
            }
            
		}
    }

    if (!already_processed)
	    deliver_mail(bounce, "");
}

/* 
 * Deliver an quota warning message
 * Return 0 on success
 * Return less than zero on failure
 * 
 * -1 = user is over quota
 * -2 = system failure
 * -3 = mail is looping 
 */
int deliver_quota_warning(const char *dir, const char *q)
{
 time_t tm;
 int fd, read_fd;
 int err;
 char newdir[400];
 struct stat     sb;
 char quotawarnmsg[BUFF_SIZE];

    sprintf (quotawarnmsg, "%s%s", dir, "/quotawarn");
    time(&tm);

    /* Send only one warning every 24 hours */
    if (stat(quotawarnmsg, &sb) == 0 && ((sb.st_mtime + 86400) > tm)) {
        return 0;
    }

    fd = open(quotawarnmsg, O_WRONLY|O_CREAT|O_TRUNC, 0644);
    if (!fd) {
        printf ("couldn't update quotawarn timestamp\n");
        return -2;
    }
    /* write zero bytes, but update file's timestamp */
    write(fd, quotawarnmsg, 0);
    close(fd);

    /* Look in the domain for a .quotawarn.msg */
    sprintf(quotawarnmsg, "%s/.quotawarn.msg", TheDomainDir);
    if ( ((read_fd = open(quotawarnmsg, O_RDONLY)) < 0) || 
           (stat(quotawarnmsg, &sb) != 0)) {

        /* if that fails look in vpopmail dir */
        sprintf(quotawarnmsg, "%s/%s/.quotawarn.msg", VPOPMAILDIR, DOMAINS_DIR);
        if ( ((read_fd = open(quotawarnmsg, O_RDONLY)) < 0) || 
              (stat(quotawarnmsg, &sb) != 0)) {
            return 0;
        }
    }

    /* Don't include Return-Path in quota warnings (since they're computer
     * generated) but add a Date header.
     */
    if ( strcmp( dir, bounce) == 0 ) {
        snprintf(DeliveredTo, sizeof(DeliveredTo), 
            "%s%s", getenv("DTLINE"), date_header());
    } else {
        strcpy(newdir, dir);
        snprintf(DeliveredTo, sizeof(DeliveredTo), 
            "Delivered-To: %s\n%s", 
            maildir_to_email(newdir), date_header());
    }

    err = deliver_to_maildir (dir, DeliveredTo, read_fd, sb.st_size);

    close (read_fd);
    
    if (err != 0) return err;
    
    /* return success */
    return(0);
}

int is_loop_match( const char *dt, const char *address)
{
    char compare[255];
 
    /* make sure dt is really a delivered to header */
    if (strncasecmp ("Delivered-To: ", dt, 14) != 0) return 0;
    
    snprintf (compare, sizeof(compare), "%s\n", address);

    return (strcasecmp (compare, (dt+14)) == 0);
}
