/*
 * FCRON - periodic command scheduler 
 *
 *  Copyright 2000-2007 Thibault Godouet <fcron@free.fr>
 *
 *  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
 * 
 *  The GNU General Public License can also be found in the file
 *  `LICENSE' that comes with the fcron source distribution.
 */

 /* $Id: fcrontab.c,v 1.74 2007/06/03 17:49:01 thib Exp thib $ */

/* 
 * The goal of this program is simple : giving a user interface to fcron
 * daemon, by allowing each user to see, modify, append or remove his 
 * fcrontabs. 
 * Fcron daemon use a specific formated format of file, so fcrontab generate
 * that kind of file from human readable files. In order allowing users to
 * see and modify their fcrontabs, the source file is always saved with the
 * formated one. 
 * Fcrontab makes a temporary formated file, and then sends a signal 
 * to the daemon to force it to update its configuration, remove the temp
 * file and save a new and final formated file.
 * That way, not the simple, allows the daemon to keep a maximum of 
 * informations like the time remaining before next execution, or the date
 * and hour of next execution.
 */

#include "fcrontab.h"

#include "allow.h"
#include "fileconf.h"
#include "temp_file.h"
#include "read_string.h"

char rcs_info[] = "$Id: fcrontab.c,v 1.74 2007/06/03 17:49:01 thib Exp thib $";

void info(void);
void usage(void);


/* used in temp_file() */
char *tmp_path = "/tmp/";

/* command line options */
char rm_opt = 0;
char list_opt = 0;
char edit_opt = 0;
char reinstall_opt = 0;
char ignore_prev = 0;
int file_opt = 0;

#ifdef DEBUG
char debug_opt = 1;       /* set to 1 if we are in debug mode */
#else
char debug_opt = 0;       /* set to 1 if we are in debug mode */
#endif

/* uid/gid of users/groups 
 * (we don't use the static UID or GID as we ask for user and group names
 * in the configure script) */
char *user = NULL;
char *runas = NULL;
uid_t uid = 0;
uid_t asuid = 0;
gid_t asgid = 0;
uid_t fcrontab_uid = 0;
gid_t fcrontab_gid = 0;
uid_t rootuid = 0;
gid_t rootgid = 0;

char need_sig = 0;           /* do we need to signal fcron daemon */

char orig_dir[PATH_LEN];
cf_t *file_base = NULL;
char buf[PATH_LEN];
char file[PATH_LEN];

/* needed by log part : */
char *prog_name = NULL;
char foreground = 1;
char dosyslog = 1;
pid_t daemon_pid = 0;

#ifdef HAVE_LIBPAM
int conv_pam(int num_msg, const struct pam_message **msgm,
	     struct pam_response **response, void *appdata_ptr);
pam_handle_t *pamh = NULL;
const struct pam_conv apamconv = { conv_pam, NULL };
#endif /* HAVE_LIBPAM */

void
info(void)
    /* print some informations about this program :
     * version, license */
{
    fprintf(stderr,
	    "fcrontab " VERSION_QUOTED " - user interface to daemon fcron\n"
	    "Copyright " COPYRIGHT_QUOTED " Thibault Godouet <fcron@free.fr>\n"
	    "This program is free software distributed WITHOUT ANY WARRANTY.\n"
            "See the GNU General Public License for more details.\n"
	);

    exit(EXIT_OK);

}


void
usage(void)
  /*  print a help message about command line options and exit */
{
    fprintf(stderr, 
	    "fcrontab [-n] file [user|-u user]\n"
	    "fcrontab { -l | -r | -e | -z } [-n] [user|-u user]\n"
	    "fcrontab -h\n"
	    "  -u user    specify user name.\n"
	    "  -l         list user's current fcrontab.\n"
	    "  -r         remove user's current fcrontab.\n"
	    "  -e         edit user's current fcrontab.\n"
	    "  -z         reinstall user's fcrontab from source code.\n"
	    "  -n         ignore previous version of file.\n"
	    "  -c f       make fcrontab use config file f.\n"
	    "  -d         set up debug mode.\n"
	    "  -h         display this help message.\n"
	    "  -V         display version & infos about fcrontab.\n"
	    "\n"
	);
    
    exit(EXIT_ERR);
}


void
xexit(int exit_val)
    /* launch signal if needed and exit */
{
    pid_t pid = 0;

    if ( need_sig == 1 ) {
	
	/* fork and exec fcronsighup */
	switch ( pid = fork() ) {
	case 0:
	    /* child */
	    execl(BINDIREX "/fcronsighup", BINDIREX "/fcronsighup",
		  fcronconf, NULL);
	    die_e("Could not exec " BINDIREX " fcronsighup");
	    break;

	case -1:
	    die_e("Could not fork (fcron has not been signaled)");
	    break;

	default:
	    /* parent */
	    waitpid(pid, NULL, 0);
	    break;
	}
    }

#ifdef HAVE_LIBPAM
    pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
    pam_end(pamh, pam_close_session(pamh, PAM_SILENT));
#endif

    exit(exit_val);

}


int
copy_src(char *orig, char *dest)
    /* copy src file from orig to dest */
    /* we first copy the file to a temp file name, and then we rename it,
     * so as to avoid data loss if the filesystem is full. */
{
    int from = -1, to_fd = -1;
    int nb;
    char *copy_buf[LINE_LEN];

    char tmp_filename_str[PATH_LEN+4];
    int dest_path_len, tmp_filename_index;
    char *tmp_suffix_str = ".tmp";
    int max_dest_len = sizeof(tmp_filename_str)- sizeof(tmp_suffix_str);

    if ( (from = open(orig, O_RDONLY)) == -1) {
	error_e("copy: open(orig) : old source file kept");
	goto exiterr;
    }

    /* create it as fcrontab_uid (to avoid problem if user's uid changed)
     * except for root. Root requires filesystem uid root for security
     * reasons */
#ifdef USE_SETE_ID
    if (asuid == rootuid) {
	if (seteuid(rootuid) != 0)
	    die_e("seteuid(rootuid) : old source file kept");
    } 
    else {
    	if (seteuid(fcrontab_uid) != 0)
	    error_e("seteuid(fcrontab_uid[%d])", fcrontab_uid);
    }
#endif

    /* the temp file must be in the same directory as the dest file */
    dest_path_len = strlen(dest);
    strncpy(tmp_filename_str, dest, max_dest_len);
    tmp_filename_index = (dest_path_len > max_dest_len) ?
	max_dest_len : dest_path_len;
    strcpy(&tmp_filename_str[tmp_filename_index], tmp_suffix_str);

    to_fd = open(tmp_filename_str, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC,
		 S_IRUSR|S_IWUSR|S_IRGRP);
    if ( to_fd < 0 ) {
	error_e("could not open file %s", tmp_filename_str);
	goto exiterr;
    }

#ifdef USE_SETE_ID
    if (asuid != rootuid && seteuid(uid) != 0)
	die_e("seteuid(uid[%d]) : old source file kept", uid);
#endif
    if (asuid == rootuid ) {
	if ( fchmod(to_fd, S_IWUSR | S_IRUSR) != 0 ) {
	    error_e("Could not fchmod %s to 600", tmp_filename_str);
	    goto exiterr;
	}
	if ( fchown(to_fd, rootuid, fcrontab_gid) != 0 ) {
	    error_e("Could not fchown %s to root", tmp_filename_str);
	    goto exiterr;
	}
    }

    while ( (nb = read(from, copy_buf, sizeof(copy_buf))) != -1 && nb != 0 )
	if ( write(to_fd, copy_buf, nb) != nb ) {
	    error_e("Error while copying file (no space left ?)."
		    " Aborting : old source file kept");
	    goto exiterr;
	}

    close(from);
    close(to_fd);
    from = to_fd = -1;

    if ( rename(tmp_filename_str, dest) < 0 ) {
	error_e("Unable to rename %s to %s : old source file kept",
		tmp_filename_str, dest);
	goto exiterr;
    }
    
    return OK;

  exiterr:
    if ( from != -1 )
	close(from);
    if ( to_fd != -1 )
	close(to_fd);
    return ERR;
}


int
remove_fcrontab(char rm_orig)
    /* remove user's fcrontab and tell daemon to update his conf */
    /* note : the binary fcrontab is removed by fcron */
{
    int return_val = OK;
    int fd;

    if ( rm_orig )
	explain("removing %s's fcrontab", user);

    /* remove source and formated file */
    if ( (rm_orig && remove(buf)) != 0 ) {
	if ( errno == ENOENT )
	    return_val = ENOENT;
	else
	    error_e("could not remove %s", buf);		
    }

#ifdef USE_SETE_ID
    if (seteuid(fcrontab_uid) != 0)
	error_e("seteuid(fcrontab_uid[%d])", fcrontab_uid);
#endif
	    
    /* try to remove the temp file in case he has not
     * been read by fcron daemon */
    snprintf(buf, sizeof(buf), "new.%s", user);
    remove(buf);

    /* finally create a file in order to tell the daemon
     * a file was removed, and launch a signal to daemon */
    snprintf(buf, sizeof(buf), "rm.%s", user);
    fd = open(buf, O_CREAT | O_TRUNC | O_EXCL, S_IRUSR | S_IWUSR);

#ifdef USE_SETE_ID
    if (seteuid(uid) != 0)
	die_e("seteuid(uid[%d])", uid);
#endif

    if ( fd == -1 ) {
	if ( errno != EEXIST )
	    error_e("Can't create file %s", buf);
    }
    else if ( asuid == rootuid && fchown(fd, rootuid, fcrontab_gid) != 0 )
	error_e("Could not fchown %s to root", buf);
    close(fd);
    
    need_sig = 1;

    return return_val;

}


int
write_file(char *file)
{
    int return_val = OK;

    if (ignore_prev == 1)
	/* if user wants to ignore previous version, we remove it *
	 * ( fcron daemon remove files no longer wanted before
	 *   adding new ones ) */
	remove_fcrontab(0);

    /* copy original file to fcrontabs dir */
    snprintf(buf, sizeof(buf), "%s.orig", user);
    if ( copy_src(file, buf) == ERR ) {
	return_val = ERR;
    }
    else {

	if ( file_base->cf_line_base == NULL ) {
	    /* no entries */
	    explain("%s's fcrontab contains no entries : removed.", user);
	    remove_fcrontab(0);
	} 
	else {
	    /* write the binary fcrontab on disk */
	    snprintf(buf, sizeof(buf), "new.%s", user);
	    if ( save_file(buf) != OK )
		return_val = ERR;
	}

    }

    return return_val;
}

int
make_file(char *file)
{
    explain("installing file %s for user %s", file, user);

    /* read file and create a list in memory */
    switch ( read_file(file) ) {
    case 2:
    case OK:

	if (write_file(file) == ERR)
	    return ERR;
	else
	    /* tell daemon to update the conf */
	    need_sig = 1;

	/* free memory used to store the list */
	delete_file(user);

	break;

    case ERR:
	return ERR;
    }

    return OK;
    
}


void
list_file(char *file)
{
    FILE *f = NULL;
    int c;

    explain("listing %s's fcrontab", user);

    if ( (f = fopen(file, "r")) == NULL ) {
	if ( errno == ENOENT ) {
	    explain("user %s has no fcrontab.", user);
	    return ;
	}
	else
	    die_e("User %s could not read file \"%s\"", user, file);
    }
    else {

	while ( (c = getc(f)) != EOF )
	    putchar(c);

	fclose(f);

    }

}

void
edit_file(char *buf)
    /* copy file to a temp file, edit that file, and install it
       if necessary */
{
    char *cureditor = NULL;
    char editorcmd[PATH_LEN];
    pid_t pid;
    int status;
    struct stat st;
    time_t mtime = 0;
    char *tmp_str;
    FILE *f = NULL, *fi = NULL;
    int file = -1;
    int c;
    char correction = 0;
    short return_val = EXIT_OK;

    explain("fcrontab : editing %s's fcrontab", user);	

    if ((cureditor=getenv("VISUAL")) == NULL || strcmp(cureditor, "\0") == 0 )
	if((cureditor=getenv("EDITOR"))==NULL || strcmp(cureditor, "\0") == 0 )
	    cureditor = editor;
	
    file = temp_file(&tmp_str);
    if ( (fi = fdopen(file, "w")) == NULL ) {
	error_e("could not fdopen");
	goto exiterr;
    }
#ifndef USE_SETE_ID
    if (fchown(file, asuid, asgid) != 0) {
	error_e("Could not fchown %s to asuid and asgid", tmp_str);
	goto exiterr;
    }
#endif
    /* copy user's fcrontab (if any) to a temp file */
    if ( (f = fopen(buf, "r")) == NULL ) {
	if ( errno != ENOENT ) {
	    error_e("could not open file %s", buf);
	    goto exiterr;
	}
	else
	    fprintf(stderr, "no fcrontab for %s - using an empty one\n",
		    user);
    }
    else { 
	/* copy original file to temp file */
	while ( (c=getc(f)) != EOF ) {
	    if ( putc(c, fi) == EOF ) {
		error_e("could not write to file %s", buf);
		goto exiterr;
	    }
	}
	fclose(f);
	f = NULL;
    }

    fclose(fi);
    fi = NULL;
    close(file);
    file = -1;

    do {

	if ( stat(tmp_str, &st) == 0 )
	    mtime = st.st_mtime;
	else {
	    error_e("could not stat \"%s\"", buf);
	    goto exiterr;
	}

	switch ( pid = fork() ) {
	case 0:
	    /* child */
	    if ( uid != rootuid ) {
		if (setgid(asgid) < 0) {
		    error_e("setgid(asgid)");
		    goto exiterr;
		}
		if (setuid(asuid) < 0) {
		    error_e("setuid(asuid)");
		    goto exiterr;
		}
	    }
	    else {
		/* Some programs, like perl, require gid=egid : */
		if ( setgid(getgid()) < 0 ) {
		    error_e("setgid(getgid())");
		    goto exiterr;
		}
	    }
	    snprintf(editorcmd, sizeof(editorcmd), "%s %s", cureditor, tmp_str);
	    execlp(shell, shell, "-c", editorcmd, tmp_str, NULL);
	    error_e("Error while running \"%s\"", cureditor);
	    goto exiterr;

	case -1:
	    error_e("fork");
	    goto exiterr;

	default:
	    /* parent */
	    break ;
	}
	    
	/* only reached by parent */
	waitpid(pid, &status, 0);
	if ( ! WIFEXITED(status) ) {
	    fprintf(stderr, "Editor exited abnormally:"
		    " fcrontab is unchanged.\n");
	    goto exiterr;
	}

#ifndef USE_SETE_ID
	/* we have chown the tmp file to user's name : user may have
	 * linked the tmp file to a file owned by root. In that case, as
	 * fcrontab is setuid root, user may read some informations he is not
	 * allowed to read :
	 * we *must* check that the tmp file is not a link. */

	/* open the tmp file, chown it to root and chmod it to avoid race
	 * conditions */
	/* make sure that the tmp file is not a link */
	{
	    int fd = 0;
	    if ( (fd = open(tmp_str, O_RDONLY)) <= 0 ||
		 fstat(fd, &st) != 0 || ! S_ISREG(st.st_mode) ||
		 S_ISLNK(st.st_mode) || st.st_uid != asuid || st.st_nlink > 1){
		fprintf(stderr, "%s is not a valid regular file.\n", tmp_str);
		close(fd);
		goto exiterr;
	    }
	    if ( fchown(fd, rootuid, rootgid) != 0 || fchmod(fd, S_IRUSR|S_IWUSR) != 0 ){
		fprintf(stderr, "Can't chown or chmod %s.\n", tmp_str);
		close(fd);
		goto exiterr;
	    }
	    close(fd);
	}
#endif
	
	/* check if file has been modified */
	if ( stat(tmp_str, &st) != 0 ) {
	    error_e("could not stat %s", tmp_str);
	    goto exiterr;
	}    

	else if ( st.st_mtime > mtime || correction == 1) {

	    correction = 0;

	    switch ( read_file(tmp_str) ) {
	    case ERR:
		goto exiterr;
	    case 2:
		fprintf(stderr, "\nFile contains some errors. "
			"Ignore [i] or Correct [c] ? ");
		/* the 2nd getchar() is for the newline char (\n) */
		while ( (c = getchar())	&& c != 'i' && c != 'c' ) {
		    fprintf(stderr, "Please press c to correct, "
			    "or i to ignore: ");
		    while (c != '\n')
			c = getchar();
		}
		if ( c == 'c' ) {
		    /* free memory used to store the list */
		    delete_file(user);
		    correction = 1;
		}
		break;
	    default:
		break;
	    }

	}
	else {
	    fprintf(stderr, "Fcrontab is unchanged :"
		    " no need to install it.\n"); 
	    goto end;
	}

    } while ( correction == 1);

    if ( write_file(tmp_str) != OK )
	return_val = EXIT_ERR;
    else
	/* tell daemon to update the conf */
	need_sig = 1;
	

    /* free memory used to store the list */
    delete_file(user);
    
  end:
    if ( remove(tmp_str) != 0 )
	error_e("could not remove %s", tmp_str);
    free(tmp_str);
    xexit (return_val);

  exiterr:
    if ( remove(tmp_str) != 0 )
	error_e("could not remove %s", tmp_str);
    free(tmp_str);
    if ( f != NULL )
	fclose(f);
    if ( fi != NULL )
	fclose(fi);
    if ( file != -1 )
	close(file);
    xexit (EXIT_ERR);

}


int
install_stdin(void)
    /* install what we get through stdin */
{
    int tmp_fd = 0;
    FILE *tmp_file = NULL;
    char *tmp_str = NULL;
    int c;
    short return_val = EXIT_OK;
	    	    
    tmp_fd = temp_file(&tmp_str);
    
    if( (tmp_file = fdopen(tmp_fd, "w")) == NULL )
	die_e("Could not fdopen file %s", tmp_str);

    while ( (c = getc(stdin)) != EOF )
	putc(c, tmp_file);

    /* the following closes tmp_fd as well because it was fdopen()ed: */
    fclose(tmp_file);

    if ( make_file(tmp_str) == ERR )
	goto exiterr;
    else
	goto exit;

  exiterr:
	return_val = EXIT_ERR;    
  exit:
    if ( remove(tmp_str) != 0 )
	error_e("Could not remove %s", tmp_str);
    free(tmp_str);
    return return_val;

}

void
reinstall(char *buf)
{
    int i = 0;

    explain("reinstalling %s's fcrontab", user);

    if ( (i = open(buf, O_RDONLY)) < 0) {
	if ( errno == ENOENT ) {
	    fprintf(stderr, "Could not reinstall: user %s has no fcrontab\n",
		    user);
	}
	else
	    fprintf(stderr, "Could not open \"%s\": %s\n", buf,
		    strerror(errno));

	xexit(EXIT_ERR);
    }

    close(0); dup2(i, 0);
    close(i);

    xexit(install_stdin());

}


#ifdef HAVE_LIBPAM
int
conv_pam(int num_msg, const struct pam_message **msgm, struct pam_response **response,
	 void *appdata_ptr)
    /* text based conversation for pam. */
{
    int count = 0;
    struct pam_response *reply;

    if (num_msg <= 0 )
	return PAM_CONV_ERR;

    reply = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response));
    if (reply == NULL) {
	debug("no memory for responses");
	return PAM_CONV_ERR;
    }

    for (count = 0; count < num_msg; ++count) {
	char *string = NULL;

	switch ( msgm[count]->msg_style ) {
	case PAM_PROMPT_ECHO_OFF:
	    string = read_string(CONV_ECHO_OFF,msgm[count]->msg);
	    if (string == NULL) {
		goto failed_conversation;
	    }
	    break;
	case PAM_PROMPT_ECHO_ON:
	    string = read_string(CONV_ECHO_ON,msgm[count]->msg);
	    if (string == NULL) {
		goto failed_conversation;
	    }
	    break;
	case PAM_ERROR_MSG:
	    if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
		goto failed_conversation;
	    }
	    break;
	case PAM_TEXT_INFO:
	    if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
		goto failed_conversation;
	    }
	    break;
	default:
	    fprintf(stderr, "erroneous conversation (%d)\n"
		    ,msgm[count]->msg_style);
	    goto failed_conversation;
	}

	if (string) {                         /* must add to reply array */
	    /* add string to list of responses */

	    reply[count].resp_retcode = 0;
	    reply[count].resp = string;
	    string = NULL;
	}
    }

    /* New (0.59+) behavior is to always have a reply - this is
       compatable with the X/Open (March 1997) spec. */
    *response = reply;
    reply = NULL;

    return PAM_SUCCESS;

failed_conversation:

    if (reply) {
	for (count=0; count<num_msg; ++count) {
	    if (reply[count].resp == NULL) {
		continue;
	    }
	    switch (msgm[count]->msg_style) {
	    case PAM_PROMPT_ECHO_ON:
	    case PAM_PROMPT_ECHO_OFF:
		Overwrite(reply[count].resp);
		free(reply[count].resp);
		break;
	    case PAM_ERROR_MSG:
	    case PAM_TEXT_INFO:
		/* should not actually be able to get here... */
		free(reply[count].resp);
	    }                                            
	    reply[count].resp = NULL;
	}
	/* forget reply too */
	free(reply);
	reply = NULL;
    }

    return PAM_CONV_ERR;
}
#endif /* HAVE_LIBPAM */


void
parseopt(int argc, char *argv[])
  /* set options */
{

    int c;
    extern char *optarg;
    extern int optind, opterr, optopt;
    struct passwd *pass;
#ifdef SYSFCRONTAB
    char is_sysfcrontab = 0;
#endif

    /* constants and variables defined by command line */

    while(1) {
	c = getopt(argc, argv, "u:lrezdnhVc:");
	if (c == EOF) break;
	switch (c) {

	case 'V':
	    info(); break;

	case 'h':
	    usage(); break;

	case 'u':
	    if (uid != rootuid) {
		fprintf(stderr, "must be privileged to use -u\n");
		xexit(EXIT_ERR);
	    }
	    user = strdup2(optarg) ; 
	    break;

	case 'd':
	    debug_opt = 1; break;

	case 'l':
	    if (rm_opt || edit_opt || reinstall_opt) {
		fprintf(stderr, "Only one of the options -l, -r, -e and -z"
			"may be used simultaneously.\n");
		xexit(EXIT_ERR);
	    }
	    list_opt = 1;
	    rm_opt = edit_opt = reinstall_opt = 0;
	    break;

	case 'r':
	    if (list_opt || edit_opt || reinstall_opt) {
		fprintf(stderr, "Only one of the options -l, -r, -e and -z"
			"may be used simultaneously.\n");
		xexit(EXIT_ERR);
	    }
	    rm_opt = 1;
	    list_opt = edit_opt = reinstall_opt = 0;
	    break;

	case 'e':
	    if (list_opt || rm_opt || reinstall_opt) {
		fprintf(stderr, "Only one of the options -l, -r, -e and -z"
			"may be used simultaneously.\n");
		xexit(EXIT_ERR);
	    }
	    edit_opt = 1;
	    list_opt = rm_opt = reinstall_opt = 0;
	    break;

	case 'z':
	    if (list_opt || rm_opt || edit_opt) {
		fprintf(stderr, "Only one of the options -l, -r, -e and -z"
			"may be used simultaneously.\n");
		xexit(EXIT_ERR);
	    }
	    reinstall_opt = ignore_prev = 1;
	    list_opt = rm_opt = edit_opt = 0;
	    break;

	case 'n':
	    ignore_prev = 1;
	    break;
	    
	case 'c':
	    if ( optarg[0] == '/' ) {
		Set(fcronconf, optarg);
	    }
	    else {
		char buf[PATH_LEN];
		snprintf(buf, sizeof(buf), "%s/%s", orig_dir, optarg);
		Set(fcronconf, buf);
	    }
	    break;

	case ':':
	    fprintf(stderr, "(setopt) Missing parameter.\n");
	    usage();

	case '?':
	    usage();

	default:
	    fprintf(stderr, "(setopt) Warning: getopt returned %c.\n", c);
	}
    }

    /* read fcron.conf and update global parameters */
    read_conf();
    
    /* read the file name and/or user and check validity of the arguments */
    if (argc - optind > 2)
	usage();
    else if (argc - optind == 2 ) {
	if ( list_opt + rm_opt + edit_opt + reinstall_opt == 0 )
	    file_opt = optind++;
	else
	    usage();

	if (uid != rootuid) {
	    fprintf(stderr, "must be privileged to use -u\n");
	    xexit(EXIT_ERR);
	}
	Set(user, argv[optind]); 
    }
    else if (argc - optind == 1) {
	if ( list_opt + rm_opt + edit_opt + reinstall_opt == 0 )
	    file_opt = optind;
	else {
	    if (uid != rootuid) {
		fprintf(stderr, "must be privileged to use [user|-u user]\n");
		xexit(EXIT_ERR);
	    }
	    Set(user, argv[optind]); 	    
	}
    }
    else if (list_opt + rm_opt + edit_opt + reinstall_opt != 1)
	usage();

    if ( user == NULL ) {
	/* get user's name using getpwuid() */
	if ( ! (pass = getpwuid(uid)) )
	    die_e("user \"%s\" is not in passwd file. Aborting.", USERNAME);
	/* we need to strdup2 the value given by getpwuid() because we free
	 * file->cf_user in delete_file */
	user = strdup2(pass->pw_name);
	asuid = pass->pw_uid;
	asgid = pass->pw_gid;
    }
    else {
#ifdef SYSFCRONTAB
  	if ( strcmp(user, SYSFCRONTAB) == 0 ) {
	    is_sysfcrontab = 1;
	    asuid = rootuid;
	    asgid = rootgid;
	}
	else
#endif /* def SYSFCRONTAB */ 
	    {
		errno = 0;
		if ( (pass = getpwnam(user)) ) {
		    asuid = pass->pw_uid;
		    asgid = pass->pw_gid;
		}
		else
		    die_e("user \"%s\" is not in passwd file. Aborting.", user);
	    }
    }

    if ( 
#ifdef SYSFCRONTAB
	! is_sysfcrontab &&
#endif
	! is_allowed(user) ) {
	die("User \"%s\" is not allowed to use %s. Aborting.",
	    user, prog_name);	    
    }

#ifdef SYSFCRONTAB
    if ( is_sysfcrontab )
	runas = ROOTNAME;
    else
#endif
	runas = user;

}


int
main(int argc, char **argv)
{

#ifdef HAVE_LIBPAM
    int    retcode = 0;
    const char * const * env;
#endif
    struct passwd *pass;

    rootuid = get_user_uid_safe(ROOTNAME);
    rootgid = get_group_gid_safe(ROOTGROUP);

    memset(buf, 0, sizeof(buf));
    memset(file, 0, sizeof(file));

    if (strrchr(argv[0],'/')==NULL) prog_name = argv[0];
    else prog_name = strrchr(argv[0],'/')+1;
    
    uid = getuid();

    errno = 0;
    if ( ! (pass = getpwnam(USERNAME)) )
	die_e("user \"%s\" is not in passwd file. Aborting.", USERNAME);
    fcrontab_uid = pass->pw_uid;
    fcrontab_gid = pass->pw_gid;

    /* get current dir */
#ifdef USE_SETE_ID
    if (seteuid(uid) != 0) 
	die_e("Could not change euid to %d", uid); 
#endif
    if ( getcwd(orig_dir, sizeof(orig_dir)) == NULL )
	die_e("getcwd");
#ifdef USE_SETE_ID
    if (seteuid(fcrontab_uid) != 0) 
	die_e("Couldn't change euid to fcrontab_uid[%d]",fcrontab_uid);
#endif

    /* interpret command line options */
    parseopt(argc, argv);

#ifdef USE_SETE_ID

#ifdef HAVE_LIBPAM
    /* Open PAM session for the user and obtain any security
       credentials we might need */

    /* FIXME: remove some #ifdef //////////////////////// */
    /* FIXME: should really uid=euid when calling PAM ? */
#ifdef USE_SETE_ID
    if (seteuid(uid) != 0) 
	die_e("Could not change euid to %d", uid); 
#endif
    debug("username: %s", user);
    retcode = pam_start("fcrontab", user, &apamconv, &pamh);
    if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not start PAM");
    retcode = pam_authenticate(pamh, 0);    /* is user really user? */
    if (retcode != PAM_SUCCESS)
	die_pame(pamh, retcode, "Could not authenticate user using PAM (%d)", retcode);
    retcode = pam_acct_mgmt(pamh, 0); /* permitted access? */
    if (retcode != PAM_SUCCESS)
	die_pame(pamh, retcode, "Could not init PAM account management (%d)", retcode);
    retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
    if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not set PAM credentials");
    retcode = pam_open_session(pamh, 0);
    if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not open PAM session");

    env = (const char * const *) pam_getenvlist(pamh);
    while (env && *env) {
	if (putenv((char*) *env)) die_e("Could not copy PAM environment");
	env++;
    }

    /* Close the log here, because PAM calls openlog(3) and
       our log messages could go to the wrong facility */
    xcloselog();
    /* FIXME: remove some #ifdef //////////////////////// */
    /* FIXME: should really uid=euid when calling PAM ? */
#ifdef USE_SETE_ID
    if (seteuid(fcrontab_uid) != 0) 
	die_e("Couldn't change euid to fcrontab_uid[%d]",fcrontab_uid);
#endif
#endif /* USE_PAM */

    if (uid != fcrontab_uid)
	if (seteuid(fcrontab_uid) != 0) 
	    die_e("Couldn't change euid to fcrontab_uid[%d]",fcrontab_uid);
    /* change directory */
    if (chdir(fcrontabs) != 0) {
	error_e("Could not chdir to %s", fcrontabs);
	xexit (EXIT_ERR);
    }
    /* get user's permissions */
    if (seteuid(uid) != 0) 
	die_e("Could not change euid to %d", uid); 
    if (setegid(fcrontab_gid) != 0) 
	die_e("Could not change egid to " GROUPNAME "[%d]", fcrontab_gid); 

#else /* USE_SETE_ID */

    if (setuid(rootuid) != 0 ) 
	die_e("Could not change uid to rootuid"); 
    if (setgid(rootgid) != 0)
    	die_e("Could not change gid to rootgid");
    /* change directory */
    if (chdir(fcrontabs) != 0) {
	error_e("Could not chdir to %s", fcrontabs);
	xexit (EXIT_ERR);
    }
#endif /* USE_SETE_ID */
    
    /* this program is seteuid : we set default permission mode
     * to 640 for a normal user, 600 for root, for security reasons */
    if ( asuid == rootuid )
	umask(066);  /* octal : '0' + number in octal notation */
    else
	umask(026);

    snprintf(buf, sizeof(buf), "%s.orig", user);

    /* determine what action should be taken */
    if ( file_opt ) {

	if ( strcmp(argv[file_opt], "-") == 0 )

	    xexit(install_stdin());

	else {

	    if ( *argv[file_opt] != '/' )
		/* this is just the file name, not the path : complete it */
		snprintf(file, sizeof(file), "%s/%s", orig_dir, argv[file_opt]);
	    else {
		strncpy(file, argv[file_opt], sizeof(file) - 1);
		file[sizeof(file)-1] = '\0';
	    }

	    if (make_file(file) == OK)
		xexit(EXIT_OK);
	    else
		xexit(EXIT_ERR);

	}

    } 

    /* remove user's entries */
    if ( rm_opt == 1 ) {
	if ( remove_fcrontab(1) == ENOENT )
	    fprintf(stderr, "no fcrontab for %s\n", user);
	xexit (EXIT_OK);
    }

    /* list user's entries */
    if ( list_opt == 1 ) {
	list_file(buf);
	xexit(EXIT_OK);
    }


    /* edit user's entries */
    if ( edit_opt == 1 ) {
	edit_file(buf);
	xexit(EXIT_OK);
    }

    /* reinstall user's entries */
    if ( reinstall_opt == 1 ) {
	reinstall(buf);
	xexit(EXIT_OK);
    }

    /* never reached */
    return EXIT_OK;
}


syntax highlighted by Code2HTML, v. 0.9.1