/* RE_SID: @(%)/tmp_mnt/vol/dosnfs/shades_SCCS/unix/pcnfsd/v2/src/SCCS/s.pcnfsd_misc.c 1.13 93/02/08 10:18:54 SMI */
/*
**=====================================================================
** Copyright (c) 1986-1993 by Sun Microsystems, Inc.
**	@(#)pcnfsd_misc.c	1.13	2/8/93
**=====================================================================
*/
#include "common.h"
/*
**=====================================================================
**             I N C L U D E   F I L E   S E C T I O N                *
**                                                                    *
** If your port requires different include files, add a suitable      *
** #define in the customization section, and make the inclusion or    *
** exclusion of the files conditional on this.                        *
**=====================================================================
*/
#include "pcnfsd.h"

#include <stdio.h>
#include <pwd.h>
#include <sys/file.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <memory.h>
#include <ctype.h>

#ifdef ISC_2_0
#include <sys/fcntl.h>
#endif

#ifdef SHADOW_SUPPORT
#include <shadow.h>
#endif

#ifdef WTMP
int wtmp_enabled = 1;
#endif

#ifdef USE_GETUSERSHELL
extern char *getusershell();
#endif

/*
**---------------------------------------------------------------------
** Other #define's 
**---------------------------------------------------------------------
*/

#define	zchar		0x5b
#define NOBODY	(uid_t)(-2)

char            tempstr[256];
extern char	sp_name[1024]; /* in pcnfsd_print.c */

#define	NUMUIDS	64
int		uidrsize = 0;
int		uidrlo[NUMUIDS];
int		uidrhi[NUMUIDS];
/*
**=====================================================================
**                      C O D E   S E C T I O N                       *                    **=====================================================================
*/
/*
**---------------------------------------------------------------------
**                          Support procedures 
**---------------------------------------------------------------------
*/

/*
** grab
**
** bulletproof malloc
*/
void *grab(n)
int n;
{
void *p;

	p = (void *)malloc(n);
	if(p == NULL) {
		msg_out("rpc.pcnfsd: malloc failure");
		exit(1);
	}
	return(p);
}

char *
my_strdup(s)
char *s;
{
char *r;
	r = (char *)grab(strlen(s)+1);
	strcpy(r, s);
	return(r);
}

/*
** This is the latest word on the security check. The following
** routine "suspicious()" returns non-zero if the character string
** passed to it contains any shell metacharacters.
** Callers will typically code
**
**	if(suspicious(some_parameter)) reject();
**
** Non-Unix-based systems: examine this carefully.
**/

int suspicious (s)
char *s;
{
	if(strpbrk(s, ";|&<>`'#!?*()[]^/") != NULL)
		return 1;
	return 0;
}

void
scramble(s1, s2)
char           *s1;
char           *s2;
{
	while (*s1) 
	      {
	      *s2++ = (*s1 ^ zchar) & 0x7f;
	      s1++;
	      }
	*s2 = 0;
}


/*
** build_uid_ranges(s)
*/
void
build_uid_ranges(s)
char *s;
{
	char *r1;
	char *r2;
	int v1;
	int v2;

	r1 = strtok(s, " ,\t");
	while(r1 && uidrsize < NUMUIDS) {
		if(r2 = strchr(r1, '-')) {
			*r2++ = '\0';
		}
		v1 = atoi(r1);
		if(v1 == 0 && *r1 != '0')	/* syntax error */
			continue;
		if(r2) {
			v2 = atoi(r2);
			if(v2 == 0 && *r2 != 0)	/* syntax */
				continue;
			if(v2 < v1)		/* botch */
				continue;
		} else
			v2 = v1;
		uidrlo[uidrsize] = v1;
		uidrhi[uidrsize] = v2;
		uidrsize++;
		r1 = strtok(NULL, " ,\t");
	}
}
/*
** val_uid(u)
**
** Return non-zero if the uid is legal - within range
** and zero otherwise.
**
** If uid_range is NULL, check for 101-60002. Otherwise
** check against ranges.
*/
int
val_uid(u)
int u;
{
	int i;

	if(uidrsize == 0)
		return(u >= 101 && u <= 60002);

	for (i = 0; i < uidrsize; i++)
		if (u >= uidrlo[i] && u <= uidrhi[i])
			return(1);
	return(0);
}


struct passwd  *
get_password(usrnam, msgpp)
char           *usrnam;
char	      **msgpp;
{
struct passwd  *p;
static struct passwd localp;
char           *pswd;
char           *ushell;
int		ok = 0;


#ifdef SHADOW_SUPPORT
struct spwd    *sp;
int             shadowfile;
long		today;
long		expday;
#endif

#ifdef SHADOW_SUPPORT
	/*
        **--------------------------------------------------------------
	** Check the existence of SHADOW.  If it is there, then we are
	** running a two-password-file system.
        **--------------------------------------------------------------
	*/
	if (access(SHADOW, 0))
	   shadowfile = 0;	/* SHADOW is not there */
	else
	   shadowfile = 1;

	setpwent();
	if (shadowfile)
	   (void) setspent();	/* Setting the shadow password
					 * file */
	if ((p = getpwnam(usrnam)) == (struct passwd *)NULL ||
	   (shadowfile && (sp = getspnam(usrnam)) == (struct spwd *)NULL))
	return ((struct passwd *)NULL);

	if (shadowfile) 
           {
/*
 * New - check password expiry situation
 *
 *	compute today (as days since Jan.1 1970)
 */
		today = (long)time((time_t *)0)/86400;
/*
 *	If sp_expire is greater than zero and is less than today, the password
 *	has expired.
 */
		if(sp->sp_expire > 0 && today > sp->sp_expire) {
			*msgpp = "This account may no longer be used.";
			return ((struct passwd *)NULL);
		}
/*
 *	If sp_max is greater zero, the password will expire on
 *	sp_lstchg+sp_max. If today is greater than this, it already did.
 */
		if(sp->sp_max > 0) {
			expday = sp->sp_max+sp->sp_lstchg;
			if(today > expday) {
				*msgpp = "The password for this account has expired.";
				return ((struct passwd *)NULL);
			}
/*
 *	Next, subtract the warning period, sp_warn, from
 *	expday. This is the first day on which warnings should
 *	be issued. If today >= expday, set msgp, but proceed.
 */
			if(sp->sp_warn >= 0) {
				expday -= sp->sp_warn;
				if(today > expday) {
					*msgpp = "The password for this account will shortly expire. Please change it.";
				}
			}
		}
	   pswd = sp->sp_pwdp;
	   (void) endspent();
	   } 
        else
	   pswd = p->pw_passwd;

#else
	p = getpwnam(usrnam);
	if (p == (struct passwd *)NULL)
		return ((struct passwd *)NULL);
	pswd = p->pw_passwd;
#endif

#ifdef ISC_2_0
	/* 
        **-----------------------------------------------------------
	** We may have an 'x' in which case look in /etc/shadow ..
        **-----------------------------------------------------------
        */
	if (((strlen(pswd)) == 1) && pswd[0] == 'x') 
	   {
	   struct spwd    *shadow = getspnam(usrnam);

	   if (!shadow)
	      return ((struct passwd *)NULL);
	   pswd = shadow->sp_pwdp;
	   }
#endif
	localp = *p;
	localp.pw_passwd = pswd;
/*
 * Fix SDR#pcn1857: If the shell field is null, this entry is legal
 * (assuming that the default shell, "/bin/sh" is always legal).
 */
	if(localp.pw_shell == NULL || strlen(localp.pw_shell) == 0)
		return(&localp);

#ifdef USE_GETUSERSHELL

	setusershell();
	while(ushell = getusershell()){
		if(!strcmp(ushell, localp.pw_shell)) {
			ok = 1;
			break;
		}
	}
	endusershell();
	if(!ok)
		return ((struct passwd *)NULL);
#else
/*
 * the best we can do is to ensure that the shell ends in "sh"
 */
	ushell = localp.pw_shell;
	if((int)strlen(ushell) < 2)
		return ((struct passwd *)NULL);
	ushell += strlen(ushell) - 2;
	if(strcmp(ushell, "sh"))
		return ((struct passwd *)NULL);

#endif
	if(!val_uid(localp.pw_uid)) {
		*msgpp = "No access via pcnfsd to this account.";
		return((struct passwd *)NULL);
	}
	return (&localp);
}


/*
**---------------------------------------------------------------------
**                      Print support procedures 
**---------------------------------------------------------------------
*/

char           *
mapfont(f, i, b)
	char            f;
	char            i;
	char            b;
{
	static char     fontname[64];

	fontname[0] = 0;	/* clear it out */

	switch (f) {
	case 'c':
		(void)strcpy(fontname, "Courier");
		break;
	case 'h':
		(void)strcpy(fontname, "Helvetica");
		break;
	case 't':
		(void)strcpy(fontname, "Times");
		break;
	default:
		(void)strcpy(fontname, "Times-Roman");
		goto finis ;
	}
	if (i != 'o' && b != 'b') {	/* no bold or oblique */
		if (f == 't')	/* special case Times */
			(void)strcat(fontname, "-Roman");
		goto finis;
	}
	(void)strcat(fontname, "-");
	if (b == 'b')
		(void)strcat(fontname, "Bold");
	if (i == 'o')		/* o-blique */
		(void)strcat(fontname, f == 't' ? "Italic" : "Oblique");

finis:	return (&fontname[0]);
}

/*
 * run_ps630 performs the Diablo 630 emulation filtering process. ps630
 * was broken in certain Sun releases: it would not accept point size or
 * font changes. If your version is fixed, undefine the symbol
 * PS630_IS_BROKEN and rebuild pc-nfsd.
 */
/* #define PS630_IS_BROKEN 1 */

void
run_ps630(f, opts)
	char           *f;
	char           *opts;
{
	char            temp_file[256];
	char            commbuf[256];
	int             i;

	(void)strcpy(temp_file, f);
	(void)strcat(temp_file, "X");	/* intermediate file name */

#ifndef PS630_IS_BROKEN
	(void)sprintf(commbuf, "ps630 -s %c%c -p %s -f ",
		opts[2], opts[3], temp_file);
	(void)strcat(commbuf, mapfont(opts[4], opts[5], opts[6]));
	(void)strcat(commbuf, " -F ");
	(void)strcat(commbuf, mapfont(opts[7], opts[8], opts[9]));
	(void)strcat(commbuf, "  ");
	(void)strcat(commbuf, f);
#else	/* PS630_IS_BROKEN */
	/*
	 * The pitch and font features of ps630 appear to be broken at
	 * this time.
	 */
	(void)sprintf(commbuf, "ps630 -p %s %s", temp_file, f);
#endif	/* PS630_IS_BROKEN */


	if (i = system(commbuf)) {
		/*
		 * Under (un)certain conditions, ps630 may return -1 even
		 * if it worked. Hence the commenting out of this error
		 * report.
		 */
		 /* (void)fprintf(stderr, "\n\nrun_ps630 rc = %d\n", i) */ ;
		/* exit(1); */
	}
	if (rename(temp_file, f)) {
		perror("run_ps630: rename");
		exit(1);
	}
	return;
}




/*
**---------------------------------------------------------------------
**                      WTMP update support 
**---------------------------------------------------------------------
*/


#ifdef WTMP
#ifdef SVR4

#include <utmpx.h>
#include <sac.h>

void
wlogin(name)
        char *name;
{

extern char *getcallername();

	struct utmpx ut;

	if(!wtmp_enabled)
		return;
	memset((char *)&ut, 0, sizeof (ut));
	ut.ut_type = USER_PROCESS;
	ut.ut_id[0] = 'n';
	ut.ut_id[1] = 'f';
	ut.ut_id[2] = 's';
	ut.ut_id[3] = SC_WILDC;
        (void) strncpy(ut.ut_name,name,sizeof ut.ut_name);
        (void) time (&ut.ut_tv.tv_sec);
        (void) strncpy(ut.ut_host, getcallername(), sizeof ut.ut_host);
	ut.ut_syslen = strlen(ut.ut_host) + 1; /* hdr file says include null */
        (void) strcpy(ut.ut_line, "PC-NFS login");
	(void)updwtmpx(WTMPX_FILE, &ut);
}


#else SVR4

#include <utmp.h>
#define WTMP_PATH "/usr/adm/wtmp"

void
wlogin(name)
        char *name;
{

extern char *getcallername();
        struct utmp ut;

        int fd;

	if(!wtmp_enabled)
		return;
	memset((char *)&ut, 0, sizeof (ut));
        (void) strcpy(ut.ut_line, "PC-NFS");
        (void) strncpy(ut.ut_name,name,sizeof ut.ut_name);
        ut.ut_time = time( (time_t *) 0);
        (void) strncpy(ut.ut_host, getcallername(), sizeof ut.ut_host);
        if ((fd = open(WTMP_PATH, O_WRONLY|O_APPEND, 0)) >= 0) {
                (void)write(fd, (char *)&ut, sizeof(ut));
                (void)close(fd);
        }
}
#endif SVR4
#endif WTMP

/*
**---------------------------------------------------------------------
**                      Run-process-as-user procedures 
**---------------------------------------------------------------------
*/


#define	READER_FD	0
#define	WRITER_FD	1

static int      child_pid;

static char     cached_user[64] = "";
static uid_t    cached_uid;
static gid_t    cached_gid;

#ifndef SUNOS_403C
static	struct sigaction old_action;
static	struct sigaction new_action;
#endif SUNOS_403C
static	struct itimerval timer;

int interrupted = 0;
static	FILE *pipe_handle;

static	void myhandler()
{
 interrupted = 1;
 fclose(pipe_handle);
 kill(child_pid, SIGKILL);
 msg_out("rpc.pcnfsd: su_popen timeout - killed child process");
}

void start_watchdog(n)
int n;
{
	/*
	 * Setup SIGALRM handler, force interrupt of ongoing syscall
	 */

#ifndef SUNOS_403C
	new_action.sa_handler = myhandler;
	sigemptyset(&(new_action.sa_mask));
	new_action.sa_flags = 0;
#ifdef SA_INTERRUPT
	new_action.sa_flags |= SA_INTERRUPT;
#endif
	sigaction(SIGALRM, &new_action, &old_action);
#else SUNOS_403C
	signal(SIGALRM, myhandler);
#endif SUNOS_403C

	/*
	 * Set interval timer for n seconds
	 */
	timer.it_interval.tv_sec = 0;
	timer.it_interval.tv_usec = 0;
	timer.it_value.tv_sec = n;
	timer.it_value.tv_usec = 0;
	setitimer(ITIMER_REAL, &timer, NULL);
	interrupted = 0;

}

void stop_watchdog()
{
	/*
	 * Cancel timer
	 */

	timer.it_interval.tv_sec = 0;
	timer.it_interval.tv_usec = 0;
	timer.it_value.tv_sec = 0;
	timer.it_value.tv_usec = 0;
	setitimer(ITIMER_REAL, &timer, NULL);

	/*
 	 * restore old signal handling
	 */
#ifndef SUNOS_403C
	sigaction(SIGALRM, &old_action, NULL);
#else SUNOS_403C
	signal(SIGALRM, SIG_IGN);
#endif SUNOS_403C
}



FILE           *
su_popen(user, cmd, maxtime)
	char           *user;
	char           *cmd;
	int		maxtime;
{
	int             p[2];
	int             parent_fd, child_fd, pid;
	struct passwd *pw;
	char	tmp[256];

	if (strcmp(cached_user, user)) {
		pw = getpwnam(user);
		if (!pw)
			pw = getpwnam("nobody");
		if (pw) {
			cached_uid = pw->pw_uid;
			cached_gid = pw->pw_gid;
			strcpy(cached_user, user);
		} else {
			cached_uid = NOBODY;
			cached_gid = (gid_t) NOBODY;
			cached_user[0] = '\0';
		}
	}
	if(cached_uid != NOBODY && !val_uid((int)cached_uid)) {
		sprintf(tmp, "rpc.pcnfsd: rejecting request from user %s (uid %d)",
			user, (int)cached_uid);
		msg_out(tmp);
		return(NULL);
	};
	if (pipe(p) < 0) {
		msg_out("rpc.pcnfsd: unable to create pipe in su_popen");
		return (NULL);
	}
	parent_fd = p[READER_FD];
	child_fd = p[WRITER_FD];
	if ((pid = fork()) == 0) {
		int             i;

		for (i = 0; i < 10; i++)
			if (i != child_fd)
				(void) close(i);
		if (child_fd != 1) {
			(void) dup2(child_fd, 1);
			(void) close(child_fd);
		}
		dup2(1, 2);	/* let's get stderr as well */

		(void) setgid(cached_gid);
		(void) setuid(cached_uid);

		(void) execl("/bin/sh", "sh", "-c", cmd, (char *) NULL);
		_exit(255);
	}
	if (pid == -1) {
		msg_out("rpc.pcnfsd: fork failed");
		close(parent_fd);
		close(child_fd);
		return (NULL);
	}
	child_pid = pid;
	close(child_fd);
	start_watchdog(maxtime);
	pipe_handle = fdopen(parent_fd, "r");
	return (pipe_handle);
}

int
su_pclose(ptr)
	FILE           *ptr;
{
	int             pid, status;

	stop_watchdog();

	fclose(ptr);
	if (child_pid == -1)
		return (-1);
	while ((pid = wait(&status)) != child_pid && pid != -1);
	return (pid == -1 ? -1 : status);
}


/*
** The following routine reads a file "/etc/pcnfsd.conf" if present,
** and uses it to replace certain builtin elements, like the
** name of the print spool directory. The configuration file
** Is the usual kind: Comments begin with '#', blank lines are ignored,
** and valid lines are of the form
**
**	<keyword><whitespace><value>
**
** The following keywords are recognized:
**
**	spooldir dirname
**	printer name alias-for command
**	wtmp yes|no
*/
void
config_from_file()
{
FILE *fd;
char buff[1024];
char *cp;
char *kw;
char *val;
char *arg1;
char *arg2;

	if((fd = fopen("/etc/pcnfsd.conf", "r")) == NULL)
		return;
	while(fgets(buff, 1024, fd)) {
		cp = strchr(buff, '\n');
		*cp = '\0';
		cp = strchr(buff, '#');
		if(cp)
			*cp = '\0';
		kw = strtok(buff, " \t");
		if(kw == NULL)
			continue;
		val = strtok(NULL, " \t");
		if(val == NULL)
			continue;
		if(!mystrcasecmp(kw, "spooldir")) {
			strcpy(sp_name, val);
			continue;
		}
		if(!mystrcasecmp(kw, "uidrange")) {
			build_uid_ranges(val);
			continue;
		}
#ifdef WTMP
		if(!mystrcasecmp(kw, "wtmp")) {
			/* assume default is YES, just look for negatives */
			if(!mystrcasecmp(val, "no") ||
			   !mystrcasecmp(val, "off") ||
			   !mystrcasecmp(val, "disable") ||
			   !strcmp(val, "0"))
				wtmp_enabled = 0;;
			continue;
		}
#endif	
		if(!mystrcasecmp(kw, "printer")) {
			arg1 = strtok(NULL, " \t");
			arg2 = (arg1 == NULL ? NULL : strtok(NULL, ""));
			(void)add_printer_alias(val, arg1, arg2);
			continue;
		}
/*
** Add new cases here
*/
	}
	fclose(fd);
}


/*
** The following are replacements for the SunOS library
** routines strcasecmp and strncasecmp, which SVR4 doesn't
** include.
**
 * The following is introduced - regretfully - to deal with the broken
 * toupper/tolower in libc
 */
static int ToUpper(c)
char c;
{
	return(islower(c) ? toupper(c) : c);
}

int mystrcasecmp(s1, s2)
	char *s1, *s2;
{

	while (ToUpper(*s1) == ToUpper(*s2++))
		if (*s1++ == '\0')
			return(0);
	return(ToUpper(*s1) - ToUpper(*--s2));
}

int mystrncasecmp(s1, s2, n)
	char *s1, *s2;
	int n;
{

	while (--n >= 0 && ToUpper(*s1) == ToUpper(*s2++))
		if (*s1++ == '\0')
			return(0);
	return(n <= 0 ? 0 : ToUpper(*s1) - ToUpper(*--s2));
}


/*
** strembedded - returns true if s1 is embedded (in any case) in s2
*/

int strembedded(s1, s2)
char *s1;
char *s2;
{
	while(*s2) {
		if(!mystrcasecmp(s1, s2))
			return 1;
		s2++;
	}
	return 0;
}
