#include "args.h"
#include "config.h"

/*
 * Copyright (c) 1986, 2014 by The Trustees of Columbia University in
 * the City of New York.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  + Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  + Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 *  + Neither the name of Columbia University nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 */

#include "mm.h"
#include "parse.h"
#include "cmds.h"
#include "rd.h"

static int fetchmail ARGS((msgvec *nf, char *name));
static int move_mail ARGS((char *from, char *tofile, int quiet));

#define tempfile ".mm-newmail"

extern FILE *header_pipe;
extern int append_new_mail;
extern int check_interval;		/* polling interval */
extern int continuous_check;
extern string movemail_path;

/* return from newmail and set the alarm for next time */
#ifndef HAVE_BSD_SIGNALS		/* XXX some other way? */
#define nmreturn(val)   { alarm(0); return(val); }
#else
#define nmreturn(val)   { alarm(check_interval); return(val); }
#endif

static int gotmail=false, sawmail=false;
static int in_here = false;

int
#if HAVE_STDC
new_mail (int quiet)
          				/* explicit "check" or automatic? */
#else /* K&R style */
new_mail (quiet)
int quiet;				/* explicit "check" or automatic? */
#endif /* HAVE_STDC */
{
    keylist im;
    static msgvec *nf, *nmf;
    int type;
    static struct stat mailbuf;
    struct stat oldbuf, sbuf;
    int result = true;
    string new_mail_filename;
    int getting_mail;
    int no_movemail = 0;

    /* if they're busy and don't want to be bothered, don't interrupt */
    if (quiet && !continuous_check && mode != MM_TOP_LEVEL)
	nmreturn (result);

    if (nocheck) {			/* fdc 4 Dec 2005 */
	nmreturn (result);
    }
    /*
      movemail_path is empty if we have some other process delivering
      mail asynchronously into ~/.mm-newmail  - fdc, 6 Dec 2005.
    */
    if (!movemail_path) {
	no_movemail = 1;
    } else if (!movemail_path[0]) {
	no_movemail = 1;
    }
    oldbuf = mailbuf;
    gotmail = false;

    check_mtime(cf, NULL);		/* Check if mail file was touched */

    /* don't bother reading in new mail if there is an inconsistency */

    if (in_here) {			/* in the middle? */
	if (cf->flags & MF_WRITERR) {	/* write error leave things alone */
	    if (!quiet)
	      fprintf(stderr,
		      "Cannot check for new mail until this file is saved\n");
	    nmreturn (result);
	} else {			/* write error was cleared */
	    unlink(nf->filename);
	    (*msg_ops[nf->type].close)(nf->filep);
	    free (nf->msgs);
	    free (nf);
	    nf = nil;
	    in_here = false;
	}
    }
    /* use mail_directory if defined and not "." ("." hard to find later) */
    sprintf(new_mail_filename,"%s/%s",
	    (strcmp(".",mail_directory) && mail_directory[0] != '\0') ?
	    mail_directory : HOME, tempfile);
    getting_mail = ((cf != NULL) && !(cf->flags & MF_RDONLY) &&
		    (cf->flags & MF_MAILBOX)); /* getting or just looking? */

    /* check for leftover (orphaned) .mm-newmail */
    if (getting_mail) {
	nmf = (msgvec *) malloc(sizeof(msgvec));
	if (!nmf)
	  return(false);		/* XXX */
	
	bzero (nmf, sizeof (msgvec));
	strcpy(nmf->filename, new_mail_filename); /* i.e. .mm-newmail */
	if (stat(nmf->filename,&sbuf) == 0) {
#ifdef COMMENT
	    if (!no_movemail)
	      printf(">>> NEW_MAIL: HAVE ORPHANED .mm-newmail\n");
#endif /* COMMENT */
	    switch (mail_probe (nmf->filename,&type)) { /* can we read this? */
	      case PR_NAME:
		break;
	      case PR_NOEX:
		break;
	      case PR_PERM:
		if (!quiet)
		  cmxprintf("?Cannot read file: %s\n", nmf->filename);
		break;
	      case PR_EMPTY:
		unlink(nmf->filename);
		break;	
	      case PR_NOTOK:
		if (!quiet)
		  cmxprintf("?File is damaged or in unknown format: %s\n",
			    nmf->filename);
		break;
	      default:
		nmf->type = type;
		if (same_file (nmf->filename, cf->filename)) {
		    if (!quiet)
		      cmxprintf ("?Primary mail file cannot be %s.\n",
				 nmf->filename);
		    nmreturn (result);
		}
		if (!fetchmail(nmf,nil)) {
		    nf = nmf;
		    nmreturn (result);
		}
	    }
	}
    }
    /* Check for (new) incoming mail */

    if (use_pop) {			/* If using POP server.... */
	if (stat(new_mail_filename,&sbuf) < 0) { /* If no .mm-newmail */
	    move_mail("-", new_mail_filename, quiet); /* Try to get one */
	    if (stat(new_mail_filename,&sbuf) < 0)  /* Look again */
	      nmreturn(0);
	}
	/* fetchmail wants gotmail to be false before it will ring the bell! */
	gotmail = false;
	nf = (msgvec *) malloc(sizeof(msgvec));
	bzero(nf,sizeof(msgvec));
	nf->type = type;
	strcpy (nf->filename, new_mail_filename);
	if (same_file (cf->filename, new_mail_filename)) {
	    cmxprintf ("?Primary mail file cannot be %s.\n",
		       new_mail_filename);
	    nmreturn(0);
	}
	/* Is it a readable and valid mail file? */
	switch (mail_probe (new_mail_filename, &type)){
	  case PR_NAME:
	    if (!quiet)
	      cmxprintf("?Badly formed filename: %s\n", new_mail_filename);
	    nmreturn(0);
	  case PR_NOEX:
	    nmreturn(0);
	  case PR_PERM:
	    if (!quiet)
	      cmxprintf("?Cannot read file: %s\n", new_mail_filename);
	    nmreturn(0);
	  case PR_EMPTY:
	    nmreturn(0);
	  case PR_NOTOK:
	    if (!quiet)
	      cmxprintf("?File is damaged or in unknown format: %s\n",
			new_mail_filename);
	    nmreturn(0);
	}
	nf->type = type;
	if (!fetchmail(nf,nil)) {
	    return(true);
	}
	result = sawmail || gotmail;
	nmreturn (result);
    }
    /*
      movemail_path is empty if we have some other process delivering
      mail asynchronously into ~/.mm-newmail  - fdc, 6 Dec 2005.
    */
    if (no_movemail)
      nmreturn (result);

    /* Come here if using direct delivery through spool directory */

    if (incoming_mail == NULL) {
	incoming_mail = (keylist) malloc(2 * sizeof(char *));
#ifdef MM_PANIX
	/* PANIX uses a different scheme with no central mail spool;
	   each user has their own, "~user/.mailspool/user" which
	   is defined by the MAIL environment variable.
	*/
	{
	    char *myspooldir = getenv("MAIL");
	    if (myspooldir) {
		incoming_mail[0] = malloc(strlen(myspooldir)+1);
		strcpy(incoming_mail[0], myspooldir);
	    }
	    else incoming_mail[0] = NULL;
	}
	if (incoming_mail[0] == NULL)
#endif	/* PANIX */
	{
	    int n = 0;
#ifdef COLUMBIA
/*
  At CU with something like 100,000 email IDs, the spool directory is
  three levels deep: .../user_name[0]/user_name[1]/user_name.  If user_name
  is only one char long (which never happens), all 3 levels are the same.
*/
	    struct stat dummy;
	    if (stat("/var/spool/.splitmail",&dummy) == 0)
	      n = 4;
#endif /* COLUMBIA */

	    incoming_mail[0] = malloc(strlen(user_name) +
				  sizeof (SPOOL_DIRECTORY) + n + 2);
#ifdef COLUMBIA
	    if (n > 0)
	      sprintf(incoming_mail[0],
		      "%s/%c/%c/%s",
		      SPOOL_DIRECTORY,
		      user_name[0],
		      user_name[1] ? user_name[1] : user_name[0],
		      user_name
		      );
	    else
#endif /* COLUMBIA */
	      sprintf(incoming_mail[0], "%s/%s", SPOOL_DIRECTORY, user_name);
	}
	incoming_mail[1] = NULL;
    }
    for (im = incoming_mail; im && *im; im++) { /* loop over inboxes */
#ifdef COMMENT
	printf(">>> NEW_MAIL: INBOX=MM>%s\n",*im);
#endif /* COMMENT */
	if (stat(*im,&sbuf) < 0)
	  continue;
#ifdef MM_PANIX
	/* Minimal support for the Maildir format. */
	if (S_ISDIR(sbuf.st_mode)) {
	    static const char* mdsubdirs[] = { "/cur", "/new", "/tmp" };
	    size_t iml = strlen(*im);
	    char *mdp = malloc(iml + 4 /*subdir*/ + 1 /*null*/);
	    struct stat mdbuf;
	    int sdi;

	    strcpy(mdp, *im);
	    for (sdi=0; sdi<(sizeof(mdsubdirs)/sizeof(mdsubdirs[0])); ++sdi) {
		strcpy(mdp + iml, mdsubdirs[sdi]);
		if (stat(mdp, &mdbuf) < 0) goto out;
		if (!S_ISDIR(mdbuf.st_mode)) goto out;
		if (sbuf.st_mtime < mdbuf.st_mtime)
		  sbuf.st_mtime = mdbuf.st_mtime;
	    }
	    type = 0xDEADBEEF;
	    goto noprobe;
	  out: free(mdp);
	}
#endif /* MM_PANIX */

	switch (mail_probe (*im, &type)) {      /* can we read this? */
	  case PR_NAME:
	    if (!quiet)
	      cmxprintf("?Badly formed filename: %s\n", *im);
	    continue;
	  case PR_NOEX:
	    continue;
	  case PR_PERM:
	    if (!quiet)
	      cmxprintf("?Cannot read file: %s\n", *im);
	    continue;
	  case PR_EMPTY:
	    continue;
	  case PR_NOTOK:
	    if (!quiet)
	      cmxprintf("?File is damaged or in unknown format: %s\n", *im);
	    continue;
	}
#ifdef MM_PANIX
	noprobe:
#endif /* MM_PANIX */
	if (!getting_mail) {
	    if (sbuf.st_mtime > oldbuf.st_mtime || !quiet) {
		printf("You have new mail in %s.\n", *im);
		if (sbuf.st_mtime > mailbuf.st_mtime)
		    mailbuf = sbuf;
		sawmail = true;
	    }
	    continue;
	}
	nf = (msgvec *) malloc(sizeof(msgvec));
	bzero(nf,sizeof(msgvec));
	nf->type = type;
	strcpy (nf->filename, new_mail_filename);
	if (same_file (cf->filename, new_mail_filename)) {
	    cmxprintf ("?Primary mail file cannot be %s.\n",
		       new_mail_filename);
	    nf = (msgvec *)safe_free(nf);
	    continue;
	}
	if (same_file (new_mail_filename, *im)) {
	    cmxprintf ("?Incoming mail file cannot be %s.\n",
		       new_mail_filename);
	    nf = (msgvec *)safe_free(nf);
	    continue;
	}
	if (same_file (cf->filename, *im)) {
	    cmxprintf ("?Incoming mail file cannot be primary mail file %s.\n",
		       cf->filename);
	    nf = (msgvec *)safe_free(nf);
	    continue;
	}
	if (move_mail (*im, new_mail_filename, quiet) != 0)
	  nmreturn (result);
	switch (mail_probe (new_mail_filename, &type)){ /* can we read this? */
	  case PR_NAME:
	    if (!quiet)
	      cmxprintf("?Badly formed filename: %s\n", new_mail_filename);
	    continue;
	  case PR_NOEX:
	    continue;
	  case PR_PERM:
	    if (!quiet)
	      cmxprintf("?Cannot read file: %s\n", new_mail_filename);
	    continue;
	  case PR_EMPTY:
	    continue;
	  case PR_NOTOK:
	    if (!quiet)
	      cmxprintf("?File is damaged or in unknown format: %s\n",
			new_mail_filename);
	    continue;
	}
	nf->type = type;

	if (!fetchmail(nf,*im))
	    return(true);
    }
    result = sawmail || gotmail;
    nmreturn (result);
}

static int
#if HAVE_STDC
fetchmail(msgvec *nf, char *name)
#else /* K&R style */
fetchmail(nf,name)
msgvec *nf;
char *name;
#endif /* HAVE_STDC */
{
    int j, err;
    keylist k;

    if ((*msg_ops[nf->type].open)(nf,0) != 0) { /* open file */
	cmxprintf("?Could not open %s.\n", nf->filename);
	if (name != nil) {
	    cmxprintf("%s was moved to %s!\n", name, nf->filename);
	}
	return(false);
    }
    in_here = true;			/* read the mail in. */
    cf->msgs = (message *)safe_realloc(cf->msgs, (cf->count + nf->count + 1) *
				       sizeof(message));
    if (!cf->msgs) {
	fprintf (stderr, "Out of memory!  New mail is in %s\n",
		 nf->filename);
	fflush (stderr);		/* just in case */
	return(false);
    }
    bcopy(&nf->msgs[1], &cf->msgs[cf->count+1],
	  nf->count * sizeof(message));
    if (!gotmail)
	printf("\007");			/* XXX add newline? flush bell? */
    if (nf->count >= cmcsb._cmrmx)
	header_pipe = more_pipe_open(cmcsb._cmoj,crt_filter);
    else
	header_pipe = cmcsb._cmoj;
    header_print(0);
    for (j = cf->count+1; j <= cf->count + nf->count; j++) {
	cf->msgs[j].flags |= (M_RECENT|M_MODIFIED);
	header_print(j);
    }
    header_print(-1);
    if (header_pipe == cmcsb._cmoj) {	/* didn't open the pipe */
	if (cmcsb._cmoj)
	    fflush(cmcsb._cmoj);
    }
    else
	more_pipe_close(header_pipe);
    header_pipe = NULL;

    cf->count += nf->count;

    for(k = nf->keywords; k && *k; k++)
	cf->keywords = add_keyword(*k, cf->keywords);
    nf->keywords = free_keylist(nf->keywords);
    /*
     * Grow the sequence-encoding bit vectors if necessary.
     * Zeroing the new bits isn't necessary since the sequence
     * is explicitly bounded by <sequence_t>->last.
     */
    if (!((sequence_bits(cf->sequence) = (unsigned char *)
	   realloc (sequence_bits(cf->sequence), cf->count/NBBY+1)) &&
	  (sequence_bits(cf->prev_sequence) = (unsigned char *)
	   realloc (sequence_bits(cf->prev_sequence), cf->count/NBBY+1)) &&
	  (sequence_bits(cf->read_sequence) = (unsigned char *)
	   realloc (sequence_bits(cf->read_sequence), cf->count/NBBY+1))))
	panic("out of memory in newmail");

    if (append_new_mail == SET_NO) {
	cf->flags |= MF_DIRTY;		/* make sure all mail gets saved */
	err = !update(&cf,UPD_ALWAYS);
    }
    else {
	err = !apnd_update(&cf, nf->count); /* save by appending */
	if (err)
	    cf->flags |= MF_DIRTY;	/* will update next time around */
    }

    if (err) {				/* can we save it? */
#ifdef EDQUOT
	if (errno == EDQUOT) {
	    fprintf(stderr, "\n\
Try suspending MM, and making space by deleting some files.  Then, use the\n\
WRITE command to save your new mail!  If you can't make any space, your mail\n\
is still in %s, and the new messages are in %s\n",
		    mail_file, nf->filename);
	}
	else
#endif /* EDQUOT */
	{
	    if (name != NULL)
		fprintf (stderr,"New mail from %s has been moved to %s\n",
			 name, nf->filename);
	}
	alarm(0);
	return(false);
    }
    else {
	in_here = false;
	unlink(nf->filename);	/* it's saved, delete the new mail */
	(*msg_ops[nf->type].close)(nf->filep);
	if (nf->msgs) free (nf->msgs);
	if (nf) free (nf);
	nf = nil;
    }
    gotmail = true;
    return(true);
}

#define POPPWLEN 255
char poppw[POPPWLEN+1] = { 0, 0 };

prompt_pw(pstr) char *pstr; {		/* Prompt for password */
    char *cmpasswd();
    char *passwd;
    passwd = cmpasswd(pstr);
    if (passwd == NULL) {
	cmxerr("?Invalid characters in password");
    }
    if ((int)strlen(passwd) > POPPWLEN)
      printf("?Password too long, max=%d\n",POPPWLEN);
    strncpy(poppw,passwd,POPPWLEN);
}

static long pop_t1 = 0L, pop_t2 = 0L;

static int
#if HAVE_STDC
move_mail(char *from, char *tofile, int quiet)
#else /* K&R style */
move_mail(from, tofile, quiet)
char *from, *tofile;
int quiet;
#endif /* HAVE_STDC */
{
    int ret = 0;
    extern string movemail_path;
    char *movemail_argv[5];
    long xx = 0L, zz = 0L;
 
    movemail_argv[0] = movemail_path;
    movemail_argv[1] = use_pop ? "-" : from;
    movemail_argv[2] = tofile;
    movemail_argv[3] = use_pop ? (char *)poppw : nil;
    movemail_argv[4] = nil;

    if (!movemail_path)			/* No movemail */
      return (0);
    if (!movemail_path[0])
      return (0);
    if (movemail_path[0] == '-' && !movemail_path[1])
      return (0);

    xx = 31L;
    if (use_pop) {
	pop_t2 = time(NULL);
	xx = pop_t2 - pop_t1;
#ifdef COMMENT
	printf(">>> MOVE_MAIL: SEC SINCE LAST POP ACCESS: %ld\n",xx);
#endif /* COMMENT */
	zz = check_interval;
	if (zz < 30L)
	  zz = 30L;
    }
    if (xx > zz) {
	fix_signals_for_fork (true);
	if (use_pop && !poppw[0])
	  prompt_pw("POP Password:");
#ifdef COMMENT
	printf(">>> MOVE_MAIL CALLING %s...\n",movemail_path);
#endif /* COMMENT */
	ret = mm_execute(movemail_path, movemail_argv);
	fix_signals_for_fork (false);
	pop_t1 = pop_t2;
    }
    if (ret != 0 && !quiet)
      fprintf (stderr, "Could not get mail from %s\n", from);
    return (ret);
}
