/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)grades.c	1.4	95/03/01 SMI"	/* from SVR4 bnu:grades.c 1.7 */

#include "uucp.h"
#include <grp.h>

#define G_EXT	0
#define	G_INT	1
#define	G_RES	2
#define	G_ACT	3
#define	G_IDF	4
#define	G_MAX	512	/* max number of fields in the Grades file line */
#define	SMBUF	128

#define	TYPE	0
#define FILE1	1
#define	FILE2	2
#define	USER	3
#define	OPTS	4
#define	FILE3	5

extern int rdfulline(), jsize(), gdirf(), gnamef();
extern void wfcommit();

static void	mailAdmin();		/* Send mail to administrator. */

/* 
 * chkgrp - checks to see the group has permission 
 *		to use a service grade queue.
 *
 * returns
 *
 *	SUCCESS - if the group has permissions
 *	FAIL - if group does not
 *
 */

static int
chkgrp(carray,na)
char **carray;
int na;
{
	struct group *grp;
	int i;
	gid_t gid;

	gid = getgid();
	grp = getgrgid(gid);

	for (i = G_IDF; i < na; i++) 
			if (EQUALS(carray[i], grp->gr_name))
				return(SUCCESS);

	return(FAIL);
}

/*
 * chkusr - checks the permission fields of the Grades file
 *	    to determine if the user can queue to a particular service grade.
 *
 * returns
 *
 *	SUCCESS - if the user can queue to the service grade.
 *	FAIL - if the user can not queue to this service grade.
 *
 */

static int
chkusr(carray, na)
char **carray;
int na;
{
	int i;

	/*
	 * start at the point where the users are supposed to be in the
	 * Grades file. Loop thru until the end of the user list is 
	 * found or the user name is found. If the user name is found then
	 * return TRUE. If the end of the list is found, return FAIL.
	 */

	DEBUG(9, "User (%s)\n", User);

	/* check for any user and return if so */

	if (EQUALS(carray[G_IDF], "Any"))
		return(SUCCESS);

	DEBUG(9, "Members of administrator defined service grade (%s)\n", carray[G_EXT]);

	for (i = G_IDF; i < na; i++) {
		DEBUG(9, "%s\n", carray[i]);
		if (EQUALS(User, carray[i]))
			return(SUCCESS);
	}

	return(FAIL);
}

/*
 *	fgrade - finds the appropiate queue to queue a job into
 *
 *	returns
 *		SUCCESS	-> found a queue
 *		FAIL	-> can't find a queue
 */

int
fgrade(scfile)
struct cs_struct *scfile;
{
	char fdgrade();
	FILE *cfd;
	char line[BUFSIZ];
	char *carray[G_MAX];
	long climit;

	/* Check for the default service grade first */

	if (strcmp(scfile->sgrade, "default") == 0) {
		scfile->grade = fdgrade();
		return(SUCCESS);
	}

	/* open grades file to begin a linear for the grade requested */

	cfd = fopen(GRADES, "r");

	/* loop until the file is empty or we find the grade we want */

	while (rdfulline(cfd, line, BUFSIZ) != 0) {
		(void) getargs(line, carray, G_MAX);

		/* check to see if this is the grade we want */

		if (!EQUALS(scfile->sgrade, carray[G_EXT])) 
			continue;

		if (jsize(scfile, carray[G_RES], &climit) != FAIL) {
			(void) fclose(cfd);
			scfile->grade = *carray[G_INT];
			return(SUCCESS);
		}
	}

	(void) fclose(cfd);

	(void) fprintf(stderr, gettext("Job size (%ld bytes)"
	    " exceeds maximum number of bytes (%ld bytes)"
	    " allowed into this service grade (%s).\n"
	    "Job queued to default grade.\n"),
	    scfile->jsize, climit, scfile->sgrade);

	scfile->grade = fdgrade();
	return(SUCCESS);
}

/*
 *	fdgrade - finds the default queue for this system
 *
 *	returns
 *		a one char name for the default queue
 *
 */

char
fdgrade()
{
	FILE *cfd;
	char line[BUFSIZ];
	char *carray[G_MAX];

	/* Check for the default grade first */

		cfd = fopen(GRADES, "r");

		/* loop until the end of the file is read */

		for (; rdfulline(cfd, line, BUFSIZ) != 0;) {

			/* parse the fields of this line */

			(void) getargs(line, carray, G_MAX);

			/* check to see if the administrator has defined 
			 * a default grade for the machine.
			 */

			if (strcmp(carray[G_EXT], "default") != 0)
				continue;

			/* default must be defined in the file
			 *  close the file, get the queue name, and return.
			 */

			(void) fclose(cfd);
			return(*carray[G_INT]);
		}

		/* no default defined in this file. close file.
		 * get our default queue and return.
		 */

		(void) fclose(cfd);
		return(D_QUEUE);
}

/*
 * job_size - determines the size of a job
 *
 * returns
 *
 *	SUCCESS - if the size of the job can be determined 
 *	FAIL	- otherwise
 */

int
job_size(scfile)
struct cs_struct *scfile;
{
	extern int Dfileused;
	struct stat s;
	FILE *fp;
	char line[BUFSIZ];
	char *carray[G_MAX];
	int na;
	int nodfile = FALSE;
	int ret;

	scfile->jsize = 0;

	fp = fopen(scfile->file, "r");
 
	if (fp == NULL) {
		toCorrupt(scfile->file);
		errent(Ct_OPEN, scfile->file, errno, __FILE__,  __LINE__);
	}

	while (fgets(line, BUFSIZ, fp) != NULL) {
		na = getargs(line, carray, G_MAX);

		if (na < 6) {
			(void) fclose(fp);
			toCorrupt(scfile->file);
			errent("BAD NUMBER OF ARGUMENTS", scfile->file, 0, 
				__FILE__, __LINE__);
		}

		/* if the type of a transfer is not a push
		 * then don't try to determine the size of
		 * the data file, because you can't.
		 */

		if (*carray[TYPE] == 'R')
			continue;

		/* find the data dile that is to be transferred */

		if ((ret = stat(carray[FILE3], &s)) != 0) {
			if (errno == ENOENT) {
				nodfile = TRUE;
				ret = stat(carray[FILE1], &s);
			}
		}
		else
			Dfileused = TRUE;

		/*
		 * check to see if the return code from stat was 0
		 * if return code was not 0, write message to error
		 * log and quit. Otherwise, add size of file to job
		 * size and continue looping.
		 */

		if (ret != 0) {
			(void) fclose(fp);
			errent(Ct_STAT, nodfile ?
				carray[FILE1] : carray[FILE3], errno,
				__FILE__, __LINE__);
		}

		nodfile = FALSE;
		scfile->jsize += s.st_size;
	}
	(void) fclose(fp);
	return(SUCCESS);
}

/*
 * jsize - determines whether if a job is small enough to
 * 	   be placed in the appropiate queue.
 *
 * returns
 *
 *	SUCCESS - if the size of the job is less than or
 *		  equal to the number of bytes in the restriction
 *		  of the GRADES file.
 *
 *	FAIL	- otherwise
 */

int
jsize(scfile, climit, nlimit)
struct cs_struct *scfile;
char *climit;
long *nlimit;
{
#define ONE_K (1024)
#define ONE_MEG ((1024)*(1024))

	static void lcase();
	char rest[SMBUF];
	char msg[BUFSIZ], *p;

	if (EQUALS(climit, "Any"))
		return(SUCCESS);

	lcase(climit, rest, SMBUF);

	if (!(p = strchr(rest, 'k')) && (!(p = strchr(rest, 'm')))) {

		for(p = climit; *p; ++p) {
			if (isdigit(*p))
				continue;

			/* corrupt restriction field in the Grades file.
			 * report it to the uucp administrator.
			 */

			sprintf(msg, gettext("Error encountered in the"
			    " restrictions field of the Grades file."
			    "  Field contents (%s)."), climit);
			mailAdmin(msg);
			return(SUCCESS);
		}

		*nlimit = atol(climit);
	}
	else if (*p == 'k') {
		*p = '\0';
		*nlimit = (long) (atof(rest) * ONE_K);
	}
	else {
		*p = '\0';
		*nlimit = (long) (atof(rest) * ONE_MEG);
	}

	if (scfile->jsize <= *nlimit)
		return(SUCCESS);
	else
		return(FAIL);
}

static void
lcase(s, t, lim)
char s[], t[];
int lim;
{
	char *p;
	int i;
	

	p = s;

	for (i = 0; i < lim-1 && *p; i++)
		if (isupper(*p))
			t[i] = tolower(*p++);
		else
			t[i] = *p++;

	t[i] = '\0';
	return;
}

/*
 * mailAdmin - mail a message to the uucp administrator.
 *
 * returns:
 *
 *	nothing
 */

static void
mailAdmin (msg)

char *	msg;

{
	char	cmd[BUFSIZ];		/* Place to build mail command. */
	FILE *	mail;			/* Channel to write mail on. */

	(void) sprintf(cmd, "%s %s %s", PATH, MAIL, "uucp");
	if ((mail = popen(cmd, "w")) != (FILE *) NULL)
	{
		(void) fprintf(mail, "To: uucp\nSubject: %s\n\n%s\n",
		    gettext("Grades file problem"), msg);
		(void) pclose(mail);
	}

	/*
	 * Ignore popen failure.  There is not much that we can do if
	 * it fails, since we are already trying to notify the administrator
	 * of a problem.
	 */
	return;
}

/*
 * putdfiles - moves any and all of the D. to the spool directory for
 * 	       a C. file.
 *
 * returns
 *
 *	nothing
 */

void
putdfiles(scfile)
struct cs_struct scfile;
{
	FILE *fp;
	char line[BUFSIZ];
	char *carray[G_MAX];
	int na;
	struct stat s;

	fp = fopen(scfile.file, "r");

	if (fp == NULL) {
		toCorrupt(scfile.file);
		errent(Ct_OPEN, scfile.file, errno, __FILE__, __LINE__);
	}

	while (fgets(line, BUFSIZ, fp) != NULL) {

		na = getargs(line, carray, G_MAX);
		if (na < 6) {
			(void) fclose(fp);
			toCorrupt(scfile.file);
			errent("BAD NUMBER OF ARGUMENTS", scfile.file, 0,
				__FILE__, __LINE__);
		}

		if (*carray[TYPE] == 'R')
			continue;

	    	/* move D. file to the spool area */

		if (stat(carray[FILE3], &s) != -1)
			wfcommit(carray[FILE3], carray[FILE3], scfile.sys);
	}

	(void) fclose(fp);
	return;
}

/*
 * reads a line from a file and takes care of comment lines
 * and continuations (\) in last column.
 *
 * return:
 *	the number of chars that are placed in line.
 */

int
rdfulline(fd, line, lim)
FILE *fd;
char *line;
int lim;
{
	register char *p, *c;
	char buf[BUFSIZ];
	size_t blr, btox;

	p = line;
	for (;fgets(buf, BUFSIZ, fd) != NULL;) {
		/* check to see if it is a comment */

		if (buf[0] == '#')
			continue;

		/* remove trailing white space */
		c = &buf[strlen(buf)-1];
		while (c>=buf && (*c == '\n' || *c == '\t' || *c == ' ') )
			*c-- = NULLCHAR;

		if (buf[0] == '\n' || buf[0] == NULLCHAR)
			continue;

		blr = lim - 1 - (p - line);
		btox = blr < strlen(buf) ? blr : strlen(buf);

		if (btox <= 0)
			break;

		(void) strncpy(p, buf, btox);
		p += btox - 1;

		if ( *(p-1) == '\\')
			p--;
		else
			break;
	}

	*++p = '\0';
	return(p-line-1);
}

/*	upermit - checks to determine if the user has permissions
 *	to use administrator defined service grade.
 *
 *	returns
 *		SUCCESS -> if the user can queue to this service grade.
 *		FAIL -> if the user cannot queue to this service grade.
 */

int
upermit(carray, na)
char **carray;
int na;
{
#define G_USR "user"
#define G_NUSR "non-user"
#define G_GRP "group"
#define G_NGRP "non-group"

	static void lcase();
	char actn[SMBUF];
	char ufld[SMBUF];
	char msg[BUFSIZ];

	(void) strcpy(actn, carray[G_ACT]);
 
	lcase(actn, ufld, SMBUF);

	if (EQUALS(ufld, G_USR))
		return(chkusr(carray,na));

	if (EQUALS(ufld, G_NUSR))
		return((chkusr(carray, na) != SUCCESS) ? SUCCESS : FAIL);

	if (EQUALS(ufld, G_GRP))
		return(chkgrp(carray, na));

	if (EQUALS(ufld, G_NGRP))
		return((chkgrp(carray, na) != SUCCESS) ? SUCCESS : FAIL);

	(void) sprintf(msg, gettext("Error encountered in action field of"
	    " the Grades file. Field contents (%s)."), carray[G_ACT]);
	mailAdmin(msg);
	return(FAIL);
}

/*
 *	vergrd - verify if the grade name is a valid administrator
 *		 defined service grade name and if the user has the
 *		 appropiate permission to use this grade.
 *
 *	returns
 *		SUCCESS	-> grade is valid and user is 
 *			   permitted to use this grade.
 *		FAIL	-> otherwise
 *
 */

int
vergrd(grade)
char *grade;
{
	FILE *cfd;
	char line[BUFSIZ];
	char *carray[G_MAX];
	int na;

	/* Check for the default grade first */

	if (EQUALS(grade, "default"))
		return(SUCCESS);

	/* open grades file to begin a linear for the grade requested */

	cfd = fopen(GRADES, "r");

	/* loop until the file is empty or we find the grade we want */

	while (rdfulline(cfd, line, BUFSIZ) != 0) {
		na = getargs(line, carray, G_MAX);

		/* check to see if this is the grade we want */

		if (!EQUALS(grade, carray[G_EXT])) 
			continue;

		/* check for the permission on this grade */

		if (upermit(carray, na) != FAIL) {
			(void) fclose(cfd);
			return(SUCCESS);
		}
		else {
			(void) fclose(cfd);
			(void) fprintf(stderr, gettext("User does not have"
			    " permission to use this service grade (%s).\n"
			    "Job has not been queued.\n"
			    "Use (uuglist) to find which service grades"
			    " you can queue to.\n"), grade);
			return(FAIL);
		}
	}

	(void) fclose(cfd);

	(void) fprintf(stderr, gettext(
	    "Service grade (%s) does not exist on this machine."
	    "  Job not queued.\n"
	    "Use (uuglist) to find which service grades are available on"
	    " this machine.\n"), grade);
	return(FAIL);
}

/*
 * wfremove - removes a C. file from the Workspace directory and all of its
 * D. files.
 */

void
wfremove(file)
char *file;
{
	FILE *fp;
	char line[BUFSIZ];
	char *carray[G_MAX];
	int na;
	struct stat s;

	fp = fopen(file, "r");

	if (fp == NULL) {
		toCorrupt(file);
		errent(Ct_OPEN, file, errno, __FILE__, __LINE__);
	}
 
	while (fgets(line, BUFSIZ, fp) != NULL) {
		na = getargs(line, carray, G_MAX);

		if (na < 6) {
			(void) fclose(fp);
			toCorrupt(file);
			errent("BAD NUMBER OF ARGUMENTS", file, 0,
				__FILE__, __LINE__);
		}

		if (*carray[TYPE] == 'R')
			continue;

	    	/* remove D. file */

	    	DEBUG(4, "Removing data file (%s)\n", carray[FILE3]);

		if ((stat(carray[FILE3], &s) != -1) && (unlink(carray[FILE3]) != 0)) {
			(void) fclose(fp);
			toCorrupt(file);
			toCorrupt(carray[FILE3]);
			errent(Ct_UNLINK, carray[FILE3], errno, __FILE__,
				__LINE__);
		}
	}

	(void) fclose(fp);

	DEBUG(4, "Removing work file (%s)\n", file);

	if (unlink(file) != 0) {
		toCorrupt(file);
		errent(Ct_UNLINK, file, errno, __FILE__, __LINE__);
	}
	return;
}

/*
 * findgrade - finds the highest priority job grade that is not locked
 * and that has jobs.
 *
 * job grade name is null, if no job grade is found.
 */

void
findgrade(dir, jobgrade)
char *dir, *jobgrade;
{
	char prevgrade[MAXBASENAME+1], curgrade[MAXBASENAME+1],
	     gradedir[MAXBASENAME+1];
	char lockname[MAXFULLNAME];
	char Cfile[MAXBASENAME+1];
	DIR *p, *q;

	*prevgrade = NULLCHAR;
	p = opendir(dir);
	ASSERT(p != NULL, Ct_OPEN, dir, errno);

	while (gdirf(p, gradedir, dir) == TRUE) {
		(void) sprintf(lockname, "%s.%.*s.%s", LOCKPRE, SYSNSIZE,
		    Rmtname, gradedir);
		if (cklock(lockname) == FAIL)
			continue;
		q = opendir(gradedir);		
		ASSERT(q != NULL, Ct_OPEN, gradedir, errno);
		while (gnamef(q, Cfile) == TRUE) {
			if (Cfile[0] == CMDPRE) {
				if (*prevgrade == NULLCHAR) {
					(void) strcpy(prevgrade, gradedir);
					break;
				}
				(void) strcpy(curgrade, gradedir);
				if (strcmp(curgrade, prevgrade) < 0)
					(void) strcpy(prevgrade, curgrade);
			}
		}
		closedir(q);
	}
	closedir(p);
	(void) strncpy(jobgrade, prevgrade, MAXBASENAME);
	jobgrade[MAXBASENAME] = NULLCHAR;
	return;
}
