#include "args.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.
 */

/*
 * program to move file a to file b, obeying file locking.
 * meant to be suid'ed or sgid'ed.
 *
 * The point is to be able to move a user's mail file out of /usr/spool/mail,
 * to a file of their own.
 *
 * Must check accesses of REAL uid, to be sure that the file can be moved.
 * The user must have read access to the original, and write access to the
 * destination directory.  The destination file must not exist.
 */

#include "config.h"
#include "osfiles.h"
#include "compat.h"

#if defined(__osf__)
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
extern int  truncate ARGS((const char *, off_t)); /* supply missing prototype */
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif


#undef TRUE
#define TRUE 1
#undef FALSE
#define FALSE 0

FILE *infile = NULL;
FILE *outfile = NULL;

int main ARGS((int argc, char **argv));
static int exist ARGS((const char *fname));
static int lock ARGS((FILE *fp, const char *fname));
static int locked ARGS((const char *fname));
static int movefile ARGS((const char *from, const char *to));
static int readaccess ARGS((const char *fname));
static int unlock ARGS((FILE *fp, const char *fname));
static int writeaccess_dir ARGS((char *fname));
static void usage ARGS((const char *pname));

int
#if HAVE_STDC
main(int argc, char **argv)
#else /* K&R style */
main(argc, argv)
int argc;
char **argv;
#endif /* HAVE_STDC */
{
    if (argc != 3) {
	usage(argv[0]);
	exit(1);
    }
    fprintf(stderr,">>> MOVEMAIL: %s -> %s\n",argv[1],argv[2]);

    if (readaccess(argv[1]) &&		/* must be able to read the src */
	writeaccess_dir(argv[2]) &&	/* have write access to the directory*/
					/* of the destination file */
	!exist(argv[2]) &&		/* the destination cannot exist */
	(locked(argv[1])))		/* and the source cannot be locked */
    {
	return(!movefile(argv[1], argv[2]));	/* do it!! */
    }
    else
	return(1);
}

static void
#if HAVE_STDC
usage(const char *pname)
#else /* K&R style */
usage(pname)
const char *pname;
#endif /* HAVE_STDC */
{
    fprintf(stderr,"Usage: %s srcfile destfile\n", pname);
}

/*
 * check if the real user (no suid/sgid bits considered) is readable
 */
static int
#if HAVE_STDC
readaccess(const char *fname)
#else /* K&R style */
readaccess(fname)
const char *fname;
#endif /* HAVE_STDC */
{
    if (access(fname,R_OK)) {
	perror(fname);
	return(FALSE);
    }
    return(TRUE);
}

static int
#if HAVE_STDC
writeaccess_dir(char *fname)
#else /* K&R style */
writeaccess_dir(fname)
char *fname;
#endif /* HAVE_STDC */
{
    char *cp;
    char c;

    cp = rindex(fname,'/');
    if (cp) {
	c = *(++cp);
	*cp = '\0';
	if (access(fname,W_OK) != 0) {
	    perror(fname);
	    return(FALSE);
	}
	*cp = c;
	return(TRUE);
    }
    else if (access(".", W_OK) != 0){
	perror(fname);
	return(FALSE);
    }
    return(TRUE);
}

static int
#if HAVE_STDC
exist(const char *fname)
#else /* K&R style */
exist(fname)
const char *fname;
#endif /* HAVE_STDC */
{
    if (access(fname,F_OK) == 0) {
	fprintf(stderr,"%s: file exists\n", fname);
	return(TRUE);
    }
    return(FALSE);
}

static int
#if HAVE_STDC
locked(const char *fname)
#else /* K&R style */
locked(fname)
const char *fname;
#endif /* HAVE_STDC */
{
#ifdef COMMENT
#ifdef MAIL_USE_SETLK
    infile = fopen(fname,"r+");		/* SETLK needs RW access */
#else
    infile = fopen(fname,"r");		/* open the input file */
#endif
#else
    infile = fopen(fname,"r");		/* open the input file */
#endif	/* COMMENT */
    if (infile == NULL) {
	perror(fname);
	return(FALSE);
    }
    return(lock(infile, fname));	/* and lock it */
}

/*
 * move from "from" to "to"
 */
static int
#if HAVE_STDC
movefile(const char *from, const char *to)
#else /* K&R style */
movefile(from, to)
const char *from;
const char *to;
#endif /* HAVE_STDC */
{
    int c;
    outfile = fopen(to, "w");
    if (outfile == NULL) {		/* can't open....stop */
	perror(to);
	unlock(infile,from);		/* but unlock the locked file */
	return(FALSE);
    }
    while((c = getc(infile)) != EOF)	/* copy */
	if (putc(c,outfile) == EOF) {
	    fclose(outfile);
	    unlink(to);
	    unlock(infile,from);
	    return(FALSE);
	}
    if (fclose(outfile) == EOF) {	/* error flushing */
	unlink(to);
	unlock(infile,from);
	return(FALSE);
    }
    if (unlink(from) < 0) {		/* unlink the original, */
	truncate(from,0);		/* or at least truncate it */
    }
    unlock(infile,from);		/* unlock the source. */
    return(TRUE);
}

static int
#if HAVE_STDC
lock(FILE *fp, const char *fname)
#else /* K&R style */
lock(fp, fname)
FILE *fp;
const char *fname;
#endif /* HAVE_STDC */
{
    int fd = fileno(fp);
#ifdef MAIL_USE_SETLK
    struct flock flk;
    flk.l_type = F_WRLCK;
    flk.l_whence = 0;
    flk.l_start = 0;
    flk.l_len = 0;
    if (fcntl (fd, F_SETLK, &flk) < 0) {
	fprintf(stderr,"lock(%s): set lock failed (fcntl errno=%d)\n",
		fname,errno);
        return (FALSE);
    }
#endif
#ifdef MAIL_USE_FLOCK
    if (flock(fd, LOCK_EX|LOCK_NB) < 0) {
	fprintf(stderr,"lock(%s): set lock failed (flock errno=%d)\n",
		fname,errno);
# ifdef MAIL_USE_SETLK
	flk.l_type = F_UNLCK;
	(void) fcntl (fd, F_SETLK, &flk);
# endif
	return(FALSE);
    }
#endif
#ifdef MAIL_USE_LOCKFILE
  {
    char buf[MAXPATHLEN];
    sprintf(buf,"%s.lock", fname);
    if ((fd = open(buf,O_CREAT|O_EXCL|O_WRONLY, 0)) < 0) {
	if (errno == EEXIST)
	  fprintf(stderr,"lock(%s): Exclusive access denied (open errno=%d)\n",
		  fname, errno);
	else
	  perror(buf);
# ifdef MAIL_USE_FLOCK
	(void) flock(fd,LOCK_EX|LOCK_UN);
# endif
# ifdef MAIL_USE_SETLK
	flk.l_type = F_UNLCK;
	(void) fcntl (fd, F_SETLK, &flk);
# endif
	return(FALSE);
    }
  }
#endif
    return(TRUE);
}

static int
#if HAVE_STDC
unlock(FILE *fp, const char *fname)
#else /* K&R style */
unlock(fp,fname)
FILE *fp;
const char *fname;
#endif /* HAVE_STDC */
{
    int fd = fileno(fp);
#ifdef MAIL_USE_SETLK
    struct flock flk;
    flk.l_type = F_UNLCK;
    flk.l_whence = 0;
    flk.l_start = 0;
    flk.l_len = 0;
    (void) fcntl (fd, F_SETLK, &flk);
#endif
#ifdef MAIL_USE_FLOCK
    (void) flock(fd,LOCK_EX|LOCK_UN);
#endif
#ifdef MAIL_USE_LOCKFILE
  {
    char buf[MAXPATHLEN];
    sprintf(buf,"%s.lock", fname);
    (void) unlink(buf);
  }
#endif
    return(TRUE);
}
