/*
** This software is Copyright (c) 1989, 1990, 1991 by Kent Landfield.
**
** Permission is hereby granted to copy, distribute or otherwise 
** use any part of this package as long as you do not try to make 
** money from it or pretend that you wrote it.  This copyright 
** notice must be maintained in any copy made.
**
*/

#if !defined(lint) && !defined(SABER)
static char SID[] = "@(#)setup.c	2.2 2/23/91";
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include "cfg.h"
#include "maxart.h"

#ifdef STRDUP
extern char *strdup();
#endif

#define GAG(b) ((void) fprintf(errfp,"%s invalid variable, ignoring.\n",b))

char spooldir[MAXNAMLEN]     = { SPOOLDIR };
char problems_dir[MAXNAMLEN] = { PROBLEMS_DIR };

int default_owner = OWNER;
int default_group = GROUP;
int default_modes = MODES;
int default_type = CHRONOLOGICAL;
int default_patch_type = HISTORICAL;

char maxartname[MAXPATHLEN] = "";

char default_match[MAXMATCHLEN];

int num_newsgroups = 0;

/*
** compress -
** Used to  determine whether or not articles should be compressed 
** to save space. The command to execute is stored in compress.
*/
char compress[MAXNAMLEN] = { '\0' };

/*
** arch_command -
** Used to determine whether or not articles should be piped
** to an external command for the actual archiving.
*/
char arch_command[MAXNAMLEN] = { '\0' };

/*
** mail -
** If specified, all actions logged are mailed to the list of users 
** specified.  The user names are a comma seperated list. 
*/
char mail[MAXNAMLEN] = { '\0' };

/*
** checkhash -
** If specified, command to feed article to for transit damage.
*/
char checkhash[MAXNAMLEN] = { '\0' };

#ifdef NNTP
/*
** nntp -
** If specified, nntp contains the system name of the nntp server.
*/
char nntp[MAXNAMLEN] = { '\0' };
#endif /*NNTP*/

/*
** log -
** The location of the master log in which all actions are logged. 
** If not specified, all logged events are printed on stdout.
*/
char log[MAXNAMLEN] = { '\0' };

/*
** log_format -
** The format of each individual log file record. The format is
** then filled with information contained in the headers.
*/
char log_format[BUFSIZ] = { '\0' };

/*
** mindex -
** The location of the master index.
*/
char mindex[MAXNAMLEN] = { '\0' };

/*
** index_format -
** The format of each individual master index record. The format 
** is then filled with information contained in the headers.
*/
char index_format[BUFSIZ] = { '\0' };

char *config_file;
FILE *config;

struct stat stbuf;
struct passwd *pwent;

int get_group();
int get_owner();
int correct_modes();
int get_patch_type();
int get_archive_type();
int fclose();
int sscanf();
int stat();
int strlen();
int strcmp();
int strncmp();
char *strstrip();
char *strchr();
char *strcpy();
void error();
void get_spooldir();
void get_archive_basedir();
struct passwd *getpwnam();

struct restricted_dirs {
    char   *dirstr;            /* path of restricted directory */
};

static struct restricted_dirs base_dirs[] = {
{  "/"               },
{  "/bin"            },
{  "/dev"            },
{  "/dev/dsk"        },
{  "/dev/rdsk"       },
{  "/etc"            },
{  "/lib"            },
{  "/stand"          },
{  "/sbin"           },
{  "/sys"            },
{  "/usr/5bin"       },
{  "/usr/5include"   },
{  "/usr/5lib"       },
{  "/usr/adm"        },
{  "/usr/adm"        },
{  "/usr/boot"       },
{  "/usr/diag"       },
{  "/usr/etc"        },
{  "/usr/include"    },
{  "/usr/kvm"        },
{  "/usr/spool/uucp" },
{  "/usr/man"        },
{  "/usr/sccs"       },
{  "/usr/sys"        },
{  "/usr/ucb"        },
{  "/usr/ucbinclude" },
{  "/usr/ucblib"     },
{  "/usr/xpg2bin"    },
{  "/usr/xpg2include" },
{  "/usr/xpg2lib"    },
{  NULL              },
};

int config_line_count = 0;

/*
**  Reads lines from file, catting lines 
**  ending with a backslash together. 
*/

char *long_fgets(buffer, size, fp)
char *buffer;
int size;
FILE *fp;
{
    static char buf[BUFSIZ];
    extern char *strcat();
    register char *p;

    *buffer = 0;

    for (;;) {
        if (fgets(buf, BUFSIZ, fp) == NULL) {
            if (*buffer) {
                if ((p = strchr(buf, '\n')) != NULL) 
                    *p = 0;
                return buffer;
            }
            else
                return NULL;
        }

        if ((p = strchr(buf, '\n')) != NULL)
            *p = 0;

        if (*buf) {
            if (*(buf + strlen(buf) - 1) != '\\') {
                /* 
                ** If line would be too long,
                ** print warning and return empty 
                */
                if (strlen(buffer) + strlen(buf) >= size) {
                   (void) fprintf(errfp,"Config %d: Input line overflow, %d max\n",
                             config_line_count, size);
                    *buffer = 0;
                    return buffer;
                }
                (void) strcat(buffer, buf);
                return buffer;
            }
            else {
                *(buf + strlen(buf) - 1) = 0; /* Remove backslash */

                /* 
                ** If too long, print warning, read 
                ** all continuation lines and ignore 
                ** them, return empty line 
                */

                if (strlen(buffer) + strlen(buf) >= size) {
                    (void) fprintf(errfp,"Config %d: Input line overflow, %d max\n",
                           config_line_count, size);
                    while (*(buf + strlen(buf) - 1) == '\\' && fgets(buf, BUFSIZ, fp))
                          ;
                    *buffer = 0;
                    return buffer;
                }

                /* Add new line and read next one */

                (void) strcat(buffer, buf);
             }
         }
         else
             return buffer;

       } /* End for(ever) */
}


void setup_defaults()
{
    char *sp;
    char *buf;
    char buffer[5000];
    char mode_str[128];

    char *sav_format();
    char *get_cmd();
    char *get_users();
#ifdef NNTP
    char *getnntp();
#endif /*NNTP*/
    FILE *efopen();

    config = efopen(config_file,"r");

    num = -1; /* initialize group structure index */
    
    while (long_fgets(buffer, sizeof buffer, config) != NULL) {
        config_line_count++;
        /* ignore comments and blank lines */
        if (*buffer == '#' || !*buffer) 
            continue;

        buf = buffer;

        /* strip leading spaces and tabs */
	while(*buf == ' ' || *buf == '\t')
             ++buf;

        /* if embedded comments, truncate at the comment */
        if ((sp = strchr(buf,'#')) && (! in_string(buf, sp))) {
	     *sp = '\0';
	}
    
        /* check to see if newsgroup entry */

        if (*buf == '$' && *(buf+1) == '$') {
	     if (++num >= num_newsgroups) {
		  int old_size = num_newsgroups;

		  if (num_newsgroups == 0) {
		       num_newsgroups = 1;
		       group = (struct group_archive *) malloc(sizeof(*group));
		  }
		  else {
		       num_newsgroups *= 2;
		       group = (struct group_archive *)
			    realloc(group, (sizeof(*group) * num_newsgroups));
		  }
		  if (! group) {
		       error("Out of memory!!\n",
			     "Too many newsgroups in config file.");
		  }
		  bzero(&group[old_size],
			&group[num_newsgroups] - &group[old_size]);
	     }

            sp = buf+2;
            while (*sp && !isspace(*sp))
		++sp;
            *sp = '\0';

            group[num].owner     = default_owner;
            group[num].group     = default_group;
            group[num].modes     = default_modes;
            group[num].type      = default_type;
            group[num].patch_type = default_patch_type;
            group[num].ng_name = strdup(strstrip(buf+2));
	    group[num].ng_path = strdup("");
            group[num].location  = strdup("");
            group[num].mail_list = strdup("");
            group[num].arc_done  = strdup("");
            group[num].logfile   = strdup("");
            group[num].index     = strdup("");
            group[num].patchlog  = strdup("");
            group[num].logformat = strdup("");
            group[num].indformat = strdup("");
            group[num].compress  = strdup("");
            group[num].checkhash = strdup("");
            group[num].match     = strdup("");
            group[num].arch_command = strdup("");

#ifdef NNTP
            group[num].nntp  = strdup("");
#endif /*NNTP*/
        }

        else if ((sp = strchr(buf,'=')) && (! in_string(buf, sp))) {
            sp++;
            		/* Global assignment */
            while (*sp == ' ' || *sp == '\t')
                sp++;

            if (!*sp)		/* is something still there ? */
		continue;

            switch(*buf) {
               case 'A': if (strncmp(buf, "ARCHIVE_CMD", 11) == 0)
                            (void) strcpy(arch_command, get_cmd(sp));
                         else
                            GAG(buf);
                         break;
               case 'C': if (strncmp(buf, "COMPRESS", 8) == 0)
                            (void) strcpy(compress, get_cmd(sp));
                         else if (strncmp(buf, "CHECKHASH", 9) == 0)
                            (void) strcpy(checkhash, get_cmd(sp));
                         else
                            GAG(buf);
                         break;
               case 'G': if (strncmp(buf,"GROUP",5) == 0)
                             default_group = get_group(sp);
                         else
                            GAG(buf);
                         break;
               case 'I': if (strncmp(buf, "INDEX_FORMAT", 12) == 0)
                            (void) strcpy(index_format, sav_format(sp));
                         else if (strncmp(buf, "INDEX", 3) == 0)
                            (void) strcpy(mindex, strstrip(sp));
                         else
                            GAG(buf);
                         break;
               case 'L': if (strncmp(buf, "LOG_FORMAT", 10) == 0)
                            (void) strcpy(log_format, sav_format(sp));
                         else if (strncmp(buf, "LOG", 3) == 0)
                            (void) strcpy(log, strstrip(sp));
                         else
                            GAG(buf);
                         break;
               case 'M': if (strncmp(buf, "MAIL",4) == 0) 
                             (void) strcpy(mail,get_users(sp));
                         else if (strncmp(buf, "MODE",4) == 0) 
                             default_modes = correct_modes(sp, mode_str);
                         else if (strncmp(buf, "MATCH", 5) == 0)
                             (void) strcpy(default_match, sp);
		         else if (strncmp(buf, "MAXARTFILE", 10) == 0) {
			      (void) strncpy(maxartname, sp, MAXPATHLEN);
			      maxartname[MAXPATHLEN-1] = '\0';
			 }
                         else
                             GAG(buf);
                         break;
#ifdef NNTP
               case 'N': if (strncmp(buf, "NNTP",4) == 0) 
                             (void) strcpy(nntp, getnntp(sp));
                         else 
                             GAG(buf);
                         break;
#endif /*NNTP*/
               case 'O': if (strncmp(buf,"OWNER",5) == 0)
                             default_owner = get_owner(sp);
                         else 
                             GAG(buf);
                         break;
               case 'P': if (strncmp(buf, "PROBLEMS", 8) == 0)
                           (void) strcpy(problems_dir, strstrip(sp));
                         else if (strncmp(buf,"PATCHES",7) == 0)
                           default_patch_type = get_patch_type("Global",sp);
                         else 
                             GAG(buf);
                         break;
               case 'S': if (strncmp(buf,"SPOOLDIR",8) == 0)
                             get_spooldir(sp);
                         else 
                             GAG(buf);
                         break;
               case 'T': if (strncmp(buf,"TYPE",4) == 0)
                             default_type = get_archive_type("Global", sp);
                         else 
                             GAG(buf);
                         break;
               default : (void) fprintf(errfp,
                                  "%s invalid global assignment, ignoring.\n",
                                  buf);
            }
        }
        else if ((sp = strchr(buf,':')) && (! in_string(buf, sp))) {
            sp++;
                /* group variable assignment */
            while (*sp == ' ' || *sp == '\t')
                sp++;

            if (!*sp)		/* is something still there ? */
		continue;

            switch(*buf) {
               case 'A': if (strncmp(buf, "ARCHIVE_CMD", 11) == 0) {
		            free(group[num].arch_command);
                            group[num].arch_command = strdup(get_cmd(sp));
			 }
	                 else if (strncmp(buf, "ARCHIVED_LOG", 11) == 0) {
			    free(group[num].arc_done);
                            group[num].arc_done = strdup(strstrip(sp));
		         }
                         else
                            GAG(buf);
                         break;
               case 'B': if (strncmp(buf, "BASEDIR",7) == 0) 
                            get_archive_basedir(sp);
                         break;
               case 'C': if (strncmp(buf, "COMPRESS", 8) == 0) {
                            free(group[num].compress);
                            group[num].compress = strdup(get_cmd(sp));
			 }
                         else if (strncmp(buf, "CHECKHASH", 9) == 0) {
			    free(group[num].checkhash);
                            group[num].checkhash = strdup(get_cmd(sp));
			 }
                         else
                            GAG(buf);
                         break;
               case 'G': if (strncmp(buf,"GROUP",5) == 0)
                             group[num].group = get_group(sp);
                         else
                            GAG(buf);
                         break;
               case 'I': if (strncmp(buf, "INDEX_FORMAT", 12) == 0) {
		            free(group[num].indformat);
                            group[num].indformat = strdup(sav_format(sp));
			 }
                         else if (strncmp(buf, "INDEX", 3) == 0) {
			     free(group[num].index);
                             group[num].index = strdup(strstrip(sp));
			 }
                         else
                             GAG(buf);
                         break;
               case 'L': if (strncmp(buf, "LOG_FORMAT", 10) == 0) {
		            free(group[num].logformat);
                            group[num].logformat = strdup(sav_format(sp));
			 }
                         else if (strncmp(buf, "LOG", 3) == 0) {
                            free(group[num].logfile);
			    group[num].logfile = strdup(strstrip(sp));
			 }
                         else
                            GAG(buf);
                         break;
               case 'M': if (strncmp(buf, "MAIL",4) == 0) {
		             free(group[num].mail_list);
			     group[num].mail_list = strdup(get_users(sp));
			 }
                         else if (strncmp(buf, "MODE",4) == 0) 
                             group[num].modes = correct_modes(sp, mode_str);
                         else if (strncmp(buf, "MATCH", 5) == 0) {
			     free(group[num].match);
                             /* Copy as it is! */
			     group[num].match = strdup(sp);
			 }
                         else 
                             GAG(buf);
                         break;
#ifdef NNTP
               case 'N': if (strncmp(buf, "NNTP",4) == 0) {
			     free(group[num].nntp);
                             group[num].nntp = strdup(getnntp(sp));
			 }
                         else 
                             GAG(buf);
                         break;
#endif /*NNTP*/
               case 'O': if (strncmp(buf,"OWNER",5) == 0)
                             group[num].owner = get_owner(sp);
                         else 
                             GAG(buf);
                         break;
               case 'P': if (strncmp(buf,"PATCHLOG",8) == 0) {
		             free(group[num].patchlog);
                             group[num].patchlog = strdup(strstrip(sp));
			 }
                         else if (strncmp(buf,"PATCHES",7) == 0)
                             group[num].patch_type = get_patch_type(group[num].ng_name,sp);
                         else 
                             GAG(buf);
                         break;
               case 'T': if (strncmp(buf,"TYPE",4) == 0)
                             group[num].type = get_archive_type(group[num].ng_name,sp);
                         else 
                             GAG(buf);
                         break;
               default : (void) fprintf(errfp,
                                  "%s invalid group assignment, ignoring.\n",
                                  buf);
            }
        }
        else /* no idea what it is */
            error("unknown line type", buf);
    }
    (void) fclose(config);
    config_line_count = 0;
}

void error(msg1,msg2)
   char *msg1;
   char *msg2;
{
   if (config_line_count)
      (void) fprintf(errfp,"%s: config line %d: %s %s\n",
                   progname, config_line_count, msg1, msg2);
   else
      (void) fprintf(errfp,"%s: %s %s\n",progname,msg1, msg2);

    exit(1);
/*NOTREACHED*/
}

/*
** valid_base_directory
**
** Assure the directory specified in the configuration file 
** as the base directory for a newsgroup archive is not found 
** in the table of restricted base directories. 
**
** This kind of checking is almost insulting to me as an 
** administrator but, enough people asked me to put it in 
** so "this duds for you"..
*/

int valid_base_directory(argstr)
    char *argstr;
 {
    register char *rp;
    register char *dp;
    char wpath[MAXNAMLEN];
    char lastchar;
    struct restricted_dirs *pt;

    /* 
    ** First check to see if the base directory is any
    ** character other than a slash. We need to assure
    ** that "../../../etc" or ./etc is not allowed.  We
    ** need a valid absolute path with which to do relative
    ** path addressing. (Have I confused myself yet ?)
    */

    if (*argstr != '/') 
            return(FALSE);

    /* 
    ** Strip the string of duplicate '/'s.
    ** Also check to assure that the path specified
    ** does not contain the '..' sequence.
    */

    dp = argstr;
    rp = wpath;
    lastchar = ' ';

    while (*dp) {
       if (*dp != '/' || lastchar != '/') {
           lastchar = *dp;
           *rp++ = *dp;
       }
       if (*dp == '.' && lastchar == '.') {
           if ((*(dp+1) == '/') || (*(dp+1) == '\0'))
               return(FALSE);
       }
       ++dp;
    }
    *rp = '\0';

    /* 
    ** strip the string of trailing '/'s so
    ** I can use the simple checking below.
    */

    dp = wpath+(strlen(wpath)-1);
    while(*dp == '/' && dp > wpath)
        *dp = '\0';

    /* 
    ** check if they match 
    */

    pt = &base_dirs[0];
    while ((pt->dirstr) != NULL) {

        if (strcmp(wpath, pt->dirstr) == 0) 
            return(FALSE);

        pt++;
    }
    return(TRUE);
}

void get_archive_basedir(s)
char *s;
{
    free(group[num].location);
    group[num].location = strdup(strstrip(s));

    if (!valid_base_directory(group[num].location))
        error(group[num].ng_name," - Invalid archive base directory!");
}

int correct_modes(s,mode_string)
char *s;
char *mode_string;
{
    register int c;
    register int i;

    i = 0;
    (void) sscanf(s, "%s", mode_string);
    while ((c = *mode_string++) >= '0' && c <= '7')
        i = (i << 3) + (c - '0');
    mode_string--;
    return(i);
}


char *get_cmd(cmd)
char *cmd;
{
    static char *rp;
    char *kp;

    rp = strstrip(cmd);

    /*
    ** Here an external command needs to be verified.
    ** To do so, the options must be removed. I am being
    ** real lazy here but what the hey..
    ** If a space is found after the cmdline is striped
    ** put a null there and then replace it with a 
    ** space after the check... 
    */
   
    if ((kp = strchr(rp,' ')) != NULL)
        *kp = '\0';
    else if ((kp = strchr(rp,'\t')) != NULL)
        *kp = '\0';

    /* need to assure the user has specified */
    /* a valid executable path.              */

    if (stat(rp, &stbuf) != 0) 
        error("Can't find specified command -", rp);

    if (kp != NULL)  /* replace the space.. */
        *kp = ' ';

    return(rp);
}

#ifdef NNTP
char *getnntp(loc)
char *loc;
{
    static char *rp;

    rp = strstrip(loc);
     
    /*
    ** check to assure that the user does not wish to negate 
    ** a global declaration for the nntp server.
    */

    if ((strncmp(rp,"LOCAL",5) == 0) || (strncmp(rp, "local",5) == 0))
        return("");

    return(rp);
}
#endif /*NNTP*/
        


int get_group(valstr)
char *valstr;
{
    char *wp;
    struct group *grent;
    struct group *getgrnam();
    
    /* group specified by names but */
    /* needs to be numbers          */

    wp = strstrip(valstr);

    if ((grent = getgrnam(wp)) == NULL)
         error("Invalid system group:",wp);
    return(grent->gr_gid);
}


int get_owner(valstr)
char *valstr;
{
    char *wp;

    /* owner specified by names but */
    /* needs to be numbers          */

    wp = strstrip(valstr);

    if ((pwent = getpwnam(wp)) == NULL)
         error("Invalid user:",wp);
    return(pwent->pw_uid);
}

int get_archive_type(ngname, s)
char *ngname;
char *s;
{
    int return_type = default_type;

    if (strcmp(s, "Archive-Name") == 0) 
        return_type = ARCHIVE_NAME;
    else if (strcmp(s, "Volume-Issue") == 0) 
        return_type = VOLUME_ISSUE;
    else if (strcmp(s, "Chronological") == 0) 
        return_type = CHRONOLOGICAL;
    else if (strcmp(s, "Article-Number") == 0) 
        return_type = ARTICLE_NUMBER;
    else if (strcmp(s, "Comp-Archives") == 0) 
        return_type = COMP_ARCHIVES;
    else if (strcmp(s, "External-Command") == 0) 
        return_type = EXTERNAL_COMMAND;
    else if (strcmp(s, "Only-Archive-Name") == 0) 
        return_type = ONLY_ARCHIVE_NAME;
    else {
        (void) fprintf(errfp,"%s: %s: %s %s\n", progname,
                   ngname, "Invalid Archive Type:", s);
        (void) fprintf(errfp,"\tTYPE Must be %s, %s, %s, %s, %s, %s or %s\n",
                   "Archive-Name",  "Volume-Issue", "Comp-Archives",
                   "External-Command", "Only-Archive-Name", 
                   "Chronological", "Article-Number");
        exit(1);
    }
    return(return_type);
}

void get_spooldir(s)
char *s;
{
    static char *rp;

    rp = strstrip(s);

    /* need to assure the user has specified */
    /* a valid directory path for the base   */
    /* directory for the news subsystem..    */
    
    if (stat(rp, &stbuf) != 0) 
        error("Can't find SPOOLDIR -", rp);
    
    (void) strcpy(spooldir, rp);
}

char *get_users(s)
char *s;
{
    char *strcat();

    static char users[512];
    char tmp_users[512];
    char *list, *name;
    char *cp, *dp;
    register int i;

    /* prepare the string for saving by stripping any spaces */

    for (i = 0; i < sizeof users; i++)
       users[i] = '\0';

    cp = s;
    dp = users;
    while (*cp) {
          if (*cp != ' ' && *cp != '\t')
              *dp++ = *cp;
          ++cp;
    }
      
    /* Need to check the specified user list */
    /* to assure that all users are valid.   */

    (void) strcpy(tmp_users, users);
    *users = '\0';

    name = tmp_users;

    while (name != NULL) {
        /* is there additional users specified ? */
	if ((list = strchr(name,',')) != NULL) {
             list++;
             *(list-1) = '\0';
        }

#ifdef CHECK_LOGNAME 
        /* check if user is found in passwd file */
        if ((pwent = getpwnam(name)) == NULL) 
            error("Invalid user:",name);
#endif /* CHECK_LOGNAME */

        if (*users != '\0') {
            (void) strcat(users, ",");
            (void) strcat(users, name);
        }
        else 
            (void) strcpy(users, name);
        name = list;
    }
    return(users);
}

/*
** get a specified format from the buffer
**	Must allow for spaces and tabs so they
**      need to be passed intact in the format.
*/
char *sav_format(s)
    char *s;
{
    static char *cp;
    char *dp;
    
    if ((cp = strchr(s,'"')) != NULL && 
        (dp = strchr(++cp,'"')) != NULL) {
        *dp = '\0';
    }
    else
        cp = NULL;
    return(cp);
}

int get_patch_type(ngname,s)
char *ngname;
char *s;
{
    int return_type = default_type;

    if (strcmp(s, "Package") == 0) 
        return_type = PACKAGE;
    else if (strcmp(s, "Historical") == 0) 
        return_type = HISTORICAL;
    else {
        (void) fprintf(errfp,"%s: %s: %s %s\n", progname,
                   ngname, "Invalid Patches Type:", s);
        (void) fprintf(errfp,"\tPATCHES Must be %s, or %s\n",
                   "Historical", "Package");
        exit(1);
    }
    return(return_type);
}

int in_string(buffer, pointer)
char *buffer, *pointer;
{
     int in_string;

     for (in_string = 0; buffer < pointer; buffer++) {
	  if (*buffer == '\\') {
	       buffer++;
	  }
	  else if (*buffer == '"') {
	       in_string = !in_string;
	  }
     }

     return(in_string);
}

	       
