[comp.sources.unix] v11i008: Getty on/off programs for 4.[23] BSD

rs@uunet.UU.NET (Rich Salz) (08/14/87)

Submitted-by: tron@sc.nsc.com (Ronald S. Karr)
Posting-number: Volume 11, Issue 8
Archive-name: getty-enable

[  Since UUNET is not about to give me write permission to /etc/ttys :-),
   I just compiled this and haven't run it.  -r$  ]

The 4.3BSD ttys file format increases versatility, but the simple act
of enabling or disabling getty on a line is no longer easily writable
as a quick shell script.  Thus, I wrote this C program to do this for
me.  I modified it for 4.2BSD as well, just for fun.

   tron  |-<=>-|    ARPAnet: nsc!tron@sun.COM
  tron@sc.nsc.com   UUCPnet: {amdahl,decwrl,hplabs,pyramid,sun}!nsc!tron

#!/bin/sh
#
# This is a shell archive.
# Send this to /bin/sh to create the following files:
#	README
#	Makefile
#	enable.man
#	enable.c
#	enable42.c
#

echo "Extracting from shar archive number 1..."

if test ! -f "README"; then
	echo "	file - \"README\""
	sed 's/^X//' > "README" <<\EOF
XIn versions of UN*X before 4.3BSD, it was relatively simple to write
Xa quick shell script to turn gettys on or off on tty lines.  But under
X4.3BSD the more complex /etc/ttys file format makes it more difficult
Xto make a simple shell script for this, or at least one that can handle
Xerrors in a reasonable fashion.  Thus, I wrote this little program
Xto handle the new format.  Then, since I had written the program, anyway,
Xand much of the code is independent of file format, I wrote a second
Xversion to handle 4.2BSD consistently.  The version for 4.2BSD may
Xwork elsewhere if the code for AUTHGROUP and the rename(2) call are
Xreplaced with something appropriate for a different environment.
X
X   tron  |-<=>-|    ARPAnet: nsc!tron@sun.com
X  tron@sc.nsc.com   UUCPnet: {amdahl,decwrl,hplabs,pyramid,sun}!nsc!tron
EOF
	if test $? -ne 0; then
		echo "	WARNING:  status $? returned by sed"
	fi
else
	echo "	WARNING:  \"README\" already exists, will not overwrite"
fi

if test ! -f "Makefile"; then
	echo "	file - \"Makefile\""
	sed 's/^X//' > "Makefile" <<\EOF
X#!/bin/make -f
X# ENABLE/DISABLE TTYLINES (makefile)
X#
X# $Header: Makefile,v 1.1 87/03/06 20:44:14 tron Exp $
X# $Log:	Makefile,v $
X# Revision 1.1  87/03/06  20:44:14  tron
X# Remove system V comments.
X# 
X# Revision 1.0  87/03/06  19:36:43  tron
X# Initial revision
X# 
X
X# 4.3BSD systems use enable.c, which handles the new format
X# /etc/ttys file.  4.2 systems use enable42.c which handles
X# the older, less interesting, format.
XSRC=enable.c
X
XDESTDIR=/etc
XMANDIR=/usr/local/man/man8
XMANSUFFIX=8
X
X# users in this group are authorized to use these programs
XAUTHGROUP = "operator"
X
XCFLAGS=-O
X
Xall:	enable
X
Xenable:	${SRC}
X	cc ${CFLAGS} -o enable ${SRC}
X
Xinstall: enable
X	rm -f ${DESTDIR}/disable
X	install -s -m 4755 -o root enable ${DESTDIR}/enable
X	ln ${DESTDIR}/enable ${DESTDIR}/disable
X
Xinstallman: enable.man
X	install -c enable.man ${MANDIR}/enable.${MANSUFFIX}
X
Xlint:	${SRC}
X	lint ${SRC}
X
Xclean:;	rm *.o core a.out enable disable
EOF
	if test $? -ne 0; then
		echo "	WARNING:  status $? returned by sed"
	fi
else
	echo "	WARNING:  \"Makefile\" already exists, will not overwrite"
fi

if test ! -f "enable.man"; then
	echo "	file - \"enable.man\""
	sed 's/^X//' > "enable.man" <<\EOF
X'\"
X'\" $Header: enable.man,v 1.1 87/03/06 20:16:01 tron Exp $
X'\" $Log:	enable.man,v $
X'\" Revision 1.1  87/03/06  20:16:01  tron
X'\" Added information about the "operator" group permissions in BSD.
X'\" 
X'\" Revision 1.0  87/03/06  19:37:52  tron
X'\" Initial revision
X'\" 
X.TH ENABLE 8 "March 6, 1987"
X.UC 4
X.SH NAME
Xenable, disable \- enable, disable getty on tty lines
X.SH SYNOPSIS
X.B enable
Xtty ...
X.br
X.B disable
Xtty ...
X.br
X.SH DESCRIPTION
X.I Enable
Xsets the getty enable flag in
X.I /etc/ttys
Xfor the specified tty lines.
X.I Disable
Xresets the getty enable flag.
XBoth programs send the SIGHUP signal to init(8) when all changes
Xhave been made so init can respond to the configuration changes.
X.PP
XUnder 4BSD systems,
Xusers in the group "operator" can use
X.I enable
Xand
X.I disable
Xwithout needing to
X.I su(1)
Xto root.
X.SH DIAGNOSTICS
XComplains about anything that seems wrong,
Xand does a reasonably large amount
Xof checking.
X.SH FILES
X.ta 2i
X/etc/ttys	terminal information file
X.br
X/etc/nttys	temp file for making changes
X.br
X/etc/ottys	backup ttys file
X.br
X.SH AUTHOR
XRonald S. Karr
X.br
Xtron@sc.nsc.com
X.SH "SEE ALSO"
Xinit(8), ttys(5), getty(8), su(1)
X.SH BUGS
XOn 4.3BSD systems, there must be on "on" or "off" string in the
Xstatus field for this to work.
X.I Enable
Xand
X.I Disable
Xwill change this field,
Xbut will not create it if it does not exist.
EOF
	if test $? -ne 0; then
		echo "	WARNING:  status $? returned by sed"
	fi
else
	echo "	WARNING:  \"enable.man\" already exists, will not overwrite"
fi

if test ! -f "enable.c"; then
	echo "	file - \"enable.c\""
	sed 's/^X//' > "enable.c" <<\EOF
X/*
X * ENABLE/DISABLE -- turn on/off tty lines (4.2BSD version).
X *
X * $Header: enable.c,v 1.3 87/03/06 20:49:13 tron Exp $
X * $Log:	enable.c,v $
X * Revision 1.3  87/03/06  20:49:13  tron
X * Cleaned up panic exitcodes.
X * 
X * Revision 1.2  87/03/06  19:48:57  tron
X * cleanup, readied for release, added code for handling signals.
X * 
X * Revision 1.1  87/03/06  05:13:43  tron
X * found one bug, incorrectly computing basename of program.
X * 
X * Revision 1.0  87/03/06  05:09:29  tron
X * Initial revision
X * 
X * usage:
X *	enable ttyline ...
X *	disable ttyline ...
X *
X * ttyline may be of the form "ttyname" or "/dev/ttyname" in which case
X * "/dev/" is stripped off.
X */
X
X#ifndef lint
Xstatic char rcsid[] = "$Header: enable.c,v 1.3 87/03/06 20:49:13 tron Exp $";
X#endif
X
X#include <stdio.h>
X#include <sysexits.h>
X#include <sys/types.h>
X#include <sys/file.h>
X#include <sys/param.h>
X#include <sys/stat.h>
X#include <signal.h>
X#include <grp.h>
X#include <strings.h>
X#include <ctype.h>
X
Xchar dev_prefix[] = "/dev/";	/* prefix for tty special files */
Xchar ttysfile[] = "/etc/ttys";	/* the file we are interested in */
Xchar tempfile[] = "/etc/nttys";	/* lock file, temp file for changes */
Xchar backup[] = "/etc/ottys";	/* in case of error, retain previous copy */
X
X#ifndef AUTHGROUP
X# define AUTHGROUP	"operator"
X#endif AUTHGROUP
X
Xchar auth_group[] = AUTHGROUP;	/* only root or users in this group are ok */
X
X/*
X * advance to the next char in a string.  If there is
X * not enough space allocated in the string to advance to
X * the next char, make it M_BUMP-chars larger.
X * M_INIT is a reasonable initial malloc region.
X *
X *	s - the string
X *	i - current index into string, range from 0 to a-1.
X *	a - current allocation size
X *
X * usage:
X *	NEXT(string,index,alloc_size) = new_character;
X */
X#define M_BUMP	64
X#define NEXT(s,i,a)  ((i)>=(a)?((a)+=M_BUMP,s=xrealloc((s),(a))):0),(s)[(i)++]
X#define M_INIT	(64-sizeof (int))
X
Xchar *program;			/* name this program was run as */
X
Xchar *xmalloc();		/* malloc(3) with panic on error */
Xchar *xrealloc();		/* realloc(3) with panic on error */
Xchar *getfield();		/* get next field from the ttys file */
Xvoid putfield();		/* write out field to the temp file */
Xvoid panic();			/* write error message and quit */
Xvoid security_check();		/* panic if user is not authorized for this */
Xint interupt();			/* routine to handle signals */
XFILE *temp = NULL;		/* temp file; if open, unlink on error */
X
Xextern int errno;		/* see intro(2) */
Xextern int sys_nerr;		/* number of known errors */
Xextern char *sys_errlist[];	/* description of known errors */
X
Xvoid
Xmain(argc, argv)
X    int argc;			/* number of arguments */
X    char *argv[];		/* vector of arguments */
X{
X    register char **ap;		/* pointer to arguments list */
X    char *argmap;		/* map of hits in tty file */
X    FILE *ttys;			/* ttys file */
X    int fd;			/* used for opening temp file */
X    struct stat statbuf;	/* status on ttys file */
X    register char *field;	/* current field from the ttys file */
X    int fieldno;		/* field number in the ttys file */
X    int match;			/* does the current line match anything? */
X    char *from;			/* status field to change on matching line */
X    char *to;			/* what to change that field to */
X    char *bl;			/* hold blanks from getfield() */
X    int i;			/* index */
X
X    /* first things first, trap signals */
X    (void) signal(SIGHUP, interupt);
X    (void) signal(SIGINT, interupt);
X    (void) signal(SIGTERM, interupt);
X
X    /* get basename of argv[0] for the name of the program */
X    program = rindex(*argv, '/');
X    if (program == NULL) {
X	program = *argv;
X    } else {
X	program++;
X    }
X
X    /* make sure the user is authorized to do this */
X    security_check();
X
X    /* what are we doing, anyway? */
X    if (strcmp(program, "enable") == 0) {
X	/* enable changes from off to on */
X	from = "off";
X	to = "on";
X    } else {
X	/* otherwise assume disable, changes from on to off */
X	from = "on";
X	to = "off";
X    }
X    argv++;			/* we don't care about argv[0] anymore */
X    argc--;
X
X    if (*argv == NULL) {
X	panic(EX_USAGE, "usage: %s ttyline ...\n", program);
X	/*NOTREACHED*/
X    }
X
X    /* allocate ttys hit map and zero it out */
X    argmap = xmalloc((unsigned)argc);
X    (void) bzero(argmap, argc);
X
X    /*
X     * cleanup the argument list
X     */
X    for (ap = argv; *ap; ap++) {
X	/*
X	 * if in dev directory, get basename
X	 */
X	if ((*ap)[0] == '/') {
X	    if (strncmp(*ap, dev_prefix, sizeof(dev_prefix) - 1) != 0) {
X		panic(EX_DATAERR, "%s: illegal argument: %s\n", program, *ap);
X		/*NOTREACHED*/
X	    }
X	    *ap += sizeof(dev_prefix)-1;
X	}
X    }
X
X    /*
X     * open the ttys file and get its mode
X     */
X    ttys = fopen(ttysfile, "r+");
X    if (ttys == NULL) {
X	panic(EX_OSFILE, "%s: could not open %s: %m\n", program, ttysfile);
X	/*NOTREACHED*/
X    }
X    if (fstat(fileno(ttys), &statbuf) < 0) {
X	panic(EX_OSFILE, "%s: could not stat %s: %m\n", program, ttysfile);
X	/*NOTREACHED*/
X    }
X
X    /*
X     * open temp file exclusively to prevent simultaneous edits
X     */
X    fd = open(tempfile, O_WRONLY|O_CREAT|O_EXCL, statbuf.st_mode);
X    if (fd < 0) {
X	panic(EX_OSFILE, "%s: open failed on %s: %m\n", program, tempfile);
X	/*NOTREACHED*/
X    }
X    temp = fdopen(fd, "w");
X    if (temp == NULL) {
X	panic(EX_OSERR, "%s: fdopen failed on %s: %m\n", program, tempfile);
X	/*NOTREACHED*/
X    }
X
X    /*
X     * loop through, grabbing fields from the ttys file and copying
X     * them to the temp file, altering the status fields as necessary
X     */
X    fieldno = 0;		/* field number within the current line */
X    match = 0;			/* is current line a match? */
X
X    while (field = getfield(&bl, ttys)) {
X	if (field[0] == '\n' || field[0] == '#') {
X	    /* end of a line, initialize for the next line */
X	    fieldno = 0;
X	    match = 0;
X	} else {
X	    fieldno++;
X	    if (fieldno == 1) {
X		/* does first field match one of the ttys to change */
X		for (ap = argv; *ap; ap++) {
X		    if (strcmp(*ap, field) == 0) {
X			match = 1; /* yes */
X			if (argmap[ap-argv] > 0) {
X			    (void)fprintf(stderr,
X					  "%s: warning: %s in %s twice\n",
X					  program, *ap, ttysfile);
X			}
X			argmap[ap-argv] = 1;
X			break;
X		    }
X		}
X	    } else if (fieldno > 3 && match) {
X		/* does status field match "on" or "off"? */
X		if (strcmp(from, field) == 0) {
X		    field = to;	/* change if so */
X		    argmap[ap-argv] |= 2; /* signify that field found */
X		} else if (strcmp(to, field) == 0) {
X		    (void) fprintf(stderr, "%s: %s is already %s\n",
X				   program, *ap, to);
X		}
X	    }
X	}
X
X	/* output the field to the temp file */
X	putfield(bl, field, temp);
X    }
X    putfield(bl, "", temp);	/* there could be stray spaces at the end */
X
X    /*
X     * check map for anything that wasn't found or changed properly
X     */
X    for (i = 0; i < argc; i++) {
X	if (argmap[i] == 0) {
X	    (void) fprintf(stderr, "%s: line %s not found in %s\n",
X			   program, argv[i], ttysfile);
X	} else if ((argmap[i] & 2) == 0) {
X	    (void) fprintf(stderr, "%s: line %s not turned %s\n",
X			   program, argv[i], to);
X	}
X    }
X
X    /* finish everything up */
X    (void) fclose(ttys);	/* close files */
X    if (fclose(temp) == EOF) {
X	    panic(EX_OSERR, "%s: could not close %s: %m\n", program, tempfile);
X	    /*NOTREACHED*/
X    }
X    (void) unlink(backup);	/* remove previous backup */
X    if (link(ttysfile, backup) < 0) { /* create a new backup */
X	panic(EX_OSERR, "%s: link from %s to %s failed: %m\n",
X	      program, ttysfile, backup);
X	/*NOTREACHED*/
X    }				/* install new ttys file */
X    if (rename(tempfile, ttysfile)) {
X	panic(EX_OSERR, "%s: rename from %s to %s failed: %m\n",
X	      program, tempfile, ttysfile);
X	/*NOTREACHED*/
X    }
X    if (kill(1, SIGHUP) < 0) {	/* inform init of changes */
X	panic(EX_OSERR, "%s: failed to notify init: %m\n", program);
X	/*NOTREACHED*/
X    }
X    exit(0);			/* all done, everything went okay (?) */
X    /*NOTREACHED*/
X}
X
X/*
X * the user must either be root or be in the auth_group.  Otherwise
X * panic with a security violation.
X */
Xvoid
Xsecurity_check()
X{
X    register i;			/* index */
X    register n;			/* index limit */
X    int gidset[NGROUPS];	/* set of user's groups */
X    struct group *gr;		/* group file entry */
X    register int gid;		/* group we are searching for in gidset */
X    uid_t getuid();		/* let's make lint happy */
X
X    /* are we root? */
X    if (getuid() == 0) return;	/* yes */
X
X    /* get id of authorized group */
X    gr = getgrnam(auth_group);
X    if (gr == NULL) {
X	panic(EX_SOFTWARE, "%s: no such group %s\n", program, auth_group);
X	/*NOTREACHED*/
X    }
X    gid = gr->gr_gid;
X
X    n = getgroups(NGROUPS, gidset);
X    /* search for authorized group id */
X    for (i = 0; i < n; i++) {
X	if (gidset[i] == gid) return; /* user looks okay to me */
X    }
X    /* not found */
X    panic(EX_NOPERM, "%s: you are not allowed to run this program\n", program);
X    /*NOTREACHED*/
X}
X
X/*
X * return the next field from the input.  A field is defined
X * as a sequence of non-space chars followed by a space char
X * or the comment char '#'.
X *
X * a field beginning with # contains a comment which ends in newline.
X * a field beginning with '\n' signals the end of an uncommented line.
X * a return value of NULL signifies end of file.
X * this routine panics on errors.
X */
Xchar *
Xgetfield(bl, stream)
X    char **bl;			/* return string of blanks here */
X    FILE *stream;		/* file to take the fields from */
X{
X    register int c;		/* current input char */
X    register char *s;		/* malloc region to store into */
X    static char *buf = NULL;	/* retained between calls */
X    register int i = 0;		/* index into s */
X    static unsigned a = M_INIT;	/* allocation region */
X    int inquote = 0;		/* set if we are inside of a quote */
X    int field;			/* index to start of field */
X
X    if (buf == NULL) {
X	s = buf = xmalloc(a);	/* first time, get the initial region */
X    } else {
X	s = buf;		/* get the region */
X    }
X
X    /*
X     * advance to next non space or tab
X     */
X    while ((c = getc(stream)) != EOF && (c == ' ' || c == '\t')) {
X	NEXT(s,i,a) = c;
X    }
X    NEXT(s,i,a) = '\0';		/* terminate string of blanks */
X
X    if (c == EOF) {
X	/* end of file */
X	*bl = buf = s;		/* return blanks */
X	return NULL;
X    }
X
X    field = i;			/* field starts at next char */
X    NEXT(s,i,a) = c;		/* all other cases will want this char */
X
X    /*
X     * first handle trivial cases
X     */
X    if (c == '\n') {
X	/* end of line */
X	NEXT(s,i,a) = '\0';	/* terminate string */
X	*bl = s;
X	buf = s;
X	return s+field;		/* return the newline */
X    }
X    if (c == '#') {
X	/* comment to end of line */
X	do {
X	    NEXT(s,i,a) = c = getc(stream);
X	} while (c != '\n');
X	NEXT(s,i,a) = '\0';	/* terminate the string */
X	buf = s;
X	*bl = s;
X	return s+field;		/* return the entire comment, plus newline */
X    }
X    /*
X     * now scan for the end of the field, as signified by a character
X     * which is a space, tab, newline or '#'.  Characters inside double
X     * quotes don't count, and " can be escaped in a quote as \".
X     */
X    if (c == '"') {
X	inquote = 1;		/* first char is a quote */
X    }
X    for (;;) {
X	c = getc(stream);	/* get next character */
X	if (inquote) {
X	    /* in a quote */
X	    if (c == '"') {
X		inquote = 0;	/* end of quote */
X	    } else if (c == '\\') {
X		/* escape next char in quote */
X		NEXT(s,i,a) = c;
X		if ((c = getc(stream)) == EOF || c == '\n') {
X		    break;	/* though watch out for EOF and newline */
X		}
X	    } else if (c == EOF || c == '\n') {
X		break;		/* EOF or end of line in string */
X	    }
X	} else {
X	    /* not in a quote: EOF, space or comment ends field */
X	    /* trap error on read */
X	    if ((c == EOF && !ferror(stream)) || isspace(c) || c == '#') {
X		NEXT(s,i,a) = '\0';
X		(void) ungetc(c, stream); /* put character back */
X		buf = s;
X		*bl = s;
X		return s+field;
X	    }
X	    if (c == EOF) {
X		break;		/* must have been a read error */
X	    }
X	    if (c == '"') {
X		inquote = 1;
X	    }
X	}
X	NEXT(s,i,a) = c;	/* put the char in the field */
X    }
X
X    /*
X     * if we reach this point, then a quote was not terminated, or there was
X     * a read error.
X     */
X    if (ferror(stream)) {
X	panic(EX_IOERR, "%s: read error in %s: %m\n", program, ttysfile);
X	/*NOTREACHED*/
X    }
X    panic(EX_DATAERR, "%s: unterminated quote in %s\n", program, ttysfile);
X    /*NOTREACHED*/
X}
X
X/*
X * write out the blanks, followed by the field, to the stream
X */
Xvoid
Xputfield(bl, field, stream)
X    char *bl;
X    char *field;
X    register FILE *stream;
X{
X    (void) fputs(bl, stream);	/* blanks go first */
X    (void) fputs(field, stream);
X    if (ferror(stream)) {
X	panic(EX_IOERR, "%s: error writing to %s: %m\n", program, tempfile);
X	/*NOTREACHED*/
X    }
X}
X
X/*
X * malloc and realloc with panics on error
X */
Xchar *
Xxmalloc(size)
X    unsigned size;
X{
X    register char *buf;
X    extern char *malloc();
X
X    buf = malloc(size);
X    if (buf == NULL) {
X	panic(EX_OSERR, "%s: could not malloc: %m\n", program);
X	/*NOTREACHED*/
X    }
X    return buf;
X}
X
Xchar *
Xxrealloc(oldbuf, size)
X    char *oldbuf;
X    unsigned size;
X{
X    register char *newbuf;
X    extern char *realloc();
X
X    newbuf = realloc(oldbuf, size);
X    if (newbuf == NULL) {
X	panic(EX_OSERR, "%s: could not realloc: %m\n", program);
X	/*NOTREACHED*/
X    }
X    return newbuf;
X}
X
X/*
X * signals are sent here to generate an error.
X */
Xint
Xinterupt()
X{
X	panic(EX_SOFTWARE, "\nsignal\n");
X	/*NOTREACHED*/
X}
X
X/*
X * process a panic string and exit with the given exit code
X * format is as with printf, only there are no numeric codes
X * within fields, and the %m field is replaced by a string
X * representing the current error.
X */
X/*VARARGS2*/
Xvoid
Xpanic(exitstat, fmt, args)
X    int exitstat;		/* call exit with this value */
X    char *fmt;			/* printf-like format string */
X    char args;			/* anchor for the rest of the args */
X{
X    register int c;		/* current format char */
X    register char *ap = &args;	/* pointer to args */
X    register int i;		/* index */
X    char *itoa();
X
X    /*
X     * process the format string
X     */
X    while (c = *fmt++) if (c != '%') {
X	/* not a percent field, just output the current char */
X	(void) putc(c, stderr);
X    } else {
X	switch(*fmt++) {
X	case 'm':		/* print the current error */
X	    if (errno >= sys_nerr || errno < 0) {
X		/* outside of the range of known errors */
X		(void) fputs("Error ", stderr);
X		(void) fputs(itoa((unsigned)errno, 10), stderr);
X	    } else {
X		(void) fputs(sys_errlist[errno], stderr);
X	    }
X	    break;
X	case 's':		/* output a string */
X	    (void) fputs(*(char **)ap, stderr);
X	    ap += sizeof(char *);
X	    break;
X	case 'c':		/* output a single character */
X	    (void) putc(*(int *)ap, stderr);
X	    ap += sizeof(int);
X	    break;
X	case 'd':		/* output a signed decimal integer */
X	    i = *(int *)ap;
X	    if (i < 0) {
X		(void) putc('-', stderr);
X		i = -i;
X	    }
X	    (void) fputs(itoa((unsigned)i, 10), stderr);
X	    ap += sizeof(int);
X	    break;
X	case 'u':		/* output an unsigned decimal interger */
X	    (void) fputs(itoa(*(unsigned *)ap, 10), stderr);
X	    ap += sizeof(unsigned);
X	    break;
X	case 'o':		/* an unsigned octal integer */
X	    (void) fputs(itoa(*(unsigned *)ap, 8), stderr);
X	    ap += sizeof(unsigned);
X	    break;
X	case 'x':		/* an unsigned hexadecimal integer */
X	    (void) fputs(itoa(*(unsigned *)ap, 16), stderr);
X	    ap += sizeof(char *);
X	    break;
X	case '%':		/* a percent sign */
X	    (void) putc('%', stderr);
X	}
X    }
X    /*
X     * if temp file open, remove it.
X     */
X    if (temp) {
X	(void) fclose(temp);
X	(void) unlink(tempfile);
X    }
X    exit(exitstat);
X    /*NOTREACHED*/
X}
X
X/*
X * convert an integer to a string representation in the given base
X */
Xchar *
Xitoa(n, base)
X    register unsigned n;	/* number to print */
X    register int base;		/* base to print it in */
X{
X    static char buf[64];	/* big enough to store any reasonable number */
X    register char *p;		/* pointer to buf */
X
X    p = buf+64;			/* point to end, and work back */
X    *--p = '\0';		/* null terminate the string */
X
X    /*
X     * fill in buf until we are done
X     */
X    while (n) {
X	*--p = "0123456789abcdefghijklmnopqrstuvwxyz"[n%base];
X	n /= base;
X    }
X    if (*p == '\0') {
X	*--p = '0';		/* handle case of the number 0 */
X    }
X    return p;			/* return the value; */
X}
EOF
	if test $? -ne 0; then
		echo "	WARNING:  status $? returned by sed"
	fi
else
	echo "	WARNING:  \"enable.c\" already exists, will not overwrite"
fi

if test ! -f "enable42.c"; then
	echo "	file - \"enable42.c\""
	sed 's/^X//' > "enable42.c" <<\EOF
X/*
X * ENABLE/DISABLE -- turn on/off tty lines (non-4.3BSD version)
X *
X * $Header: enable42.c,v 1.3 87/03/06 21:14:23 tron Exp $
X * $Log:	enable42.c,v $
X * Revision 1.3  87/03/06  21:14:23  tron
X * no uid_t type in 4.2BSD.
X * 
X * Revision 1.2  87/03/06  20:47:51  tron
X * Cleaned up panic exitcodes.
X * 
X * Revision 1.1  87/03/06  19:47:30  tron
X * cleanup, readied for release, installed code to handle signals.
X * 
X * Revision 1.0  87/03/06  18:24:48  tron
X * 4.3BSD enable modified for other UNIX systems
X * 
X * Revision 1.1  87/03/06  05:13:43  tron
X * found one bug, incorrectly computing basename of program.
X * 
X * Revision 1.0  87/03/06  05:09:29  tron
X * Initial revision
X * 
X * usage:
X *	enable ttyline ...
X *	disable ttyline ...
X *
X * ttyline may be of the form "ttyname" or "/dev/ttyname" in which case
X * "/dev/" is stripped off.
X */
X
X#ifndef lint
Xstatic char rcsid[] = "$Header: enable42.c,v 1.3 87/03/06 21:14:23 tron Exp $";
X#endif
X
X#include <stdio.h>
X#include <sysexits.h>
X#include <sys/types.h>
X#include <sys/file.h>
X#include <sys/param.h>
X#include <sys/stat.h>
X#include <signal.h>
X#include <grp.h>
X#include <strings.h>
X#include <ctype.h>
X
Xchar dev_prefix[] = "/dev/";	/* prefix for tty special files */
Xchar ttysfile[] = "/etc/ttys";	/* the file we are interested in */
Xchar tempfile[] = "/etc/nttys";	/* lock file, temp file for changes */
Xchar backup[] = "/etc/ottys";	/* in case of error, retain previous copy */
X
X#ifndef AUTHGROUP
X# define AUTHGROUP	"operator"
X#endif AUTHGROUP
X
Xchar auth_group[] = AUTHGROUP;	/* only root or users in this group are ok */
X
X#define MAXLINE	64		/* maximum size for a ttys file line */
X
Xchar *program;			/* name this program was run as */
X
Xchar *xmalloc();		/* malloc(3) with panic on error */
Xchar *getline();		/* get next line from the ttys file */
Xvoid putline();			/* write out line to the temp file */
Xvoid panic();			/* write error message and quit */
Xvoid security_check();		/* panic if user is not authorized for this */
Xint interupt();			/* routine to handle signals */
XFILE *temp = NULL;		/* temp file; if open, unlink on error */
X
Xextern int errno;		/* see intro(2) */
Xextern int sys_nerr;		/* number of known errors */
Xextern char *sys_errlist[];	/* description of known errors */
X
Xvoid
Xmain(argc, argv)
X    int argc;			/* number of arguments */
X    char *argv[];		/* vector of arguments */
X{
X    register char **ap;		/* pointer to arguments list */
X    char *argmap;		/* map of hits in tty file */
X    FILE *ttys;			/* ttys file */
X    int fd;			/* used for opening temp file */
X    struct stat statbuf;	/* status on ttys file */
X    register char *line;	/* current line from the ttys file */
X    char to;			/* how to set the status line */
X    int i;			/* index */
X
X    /* first things first, trap signals */
X    (void) signal(SIGHUP, interupt);
X    (void) signal(SIGINT, interupt);
X    (void) signal(SIGTERM, interupt);
X
X    /* get basename of argv[0] for the name of the program */
X    program = rindex(*argv, '/');
X    if (program == NULL) {
X	program = *argv;
X    } else {
X	program++;
X    }
X
X    /* make sure the user is authorized to do this */
X    security_check();
X
X    /* what are we doing, anyway? */
X    if (strcmp(program, "enable") == 0) {
X	/* enable changes from off to on */
X	to = '1';
X    } else {
X	/* otherwise assume disable, changes from on to off */
X	to = '0';
X    }
X    argv++;			/* we don't care about argv[0] anymore */
X    argc--;
X
X    if (*argv == NULL) {
X	panic(EX_USAGE, "usage: %s ttyline ...\n", program);
X	/*NOTREACHED*/
X    }
X
X    /* allocate ttys hit map and zero it out */
X    argmap = xmalloc((unsigned)argc);
X    (void) bzero(argmap, argc);
X
X    /*
X     * cleanup the argument list
X     */
X    for (ap = argv; *ap; ap++) {
X	/*
X	 * if in dev directory, get basename
X	 */
X	if ((*ap)[0] == '/') {
X	    if (strncmp(*ap, dev_prefix, sizeof(dev_prefix) - 1) != 0) {
X		panic(EX_DATAERR, "%s: illegal argument: %s\n", program, *ap);
X		/*NOTREACHED*/
X	    }
X	    *ap += sizeof(dev_prefix)-1;
X	}
X    }
X
X    /*
X     * open the ttys file and get its mode
X     */
X    ttys = fopen(ttysfile, "r+");
X    if (ttys == NULL) {
X	panic(EX_OSFILE, "%s: could not open %s: %m\n", program, ttysfile);
X	/*NOTREACHED*/
X    }
X    if (fstat(fileno(ttys), &statbuf) < 0) {
X	panic(EX_OSFILE, "%s: could not stat %s: %m\n", program, ttysfile);
X	/*NOTREACHED*/
X    }
X
X    /*
X     * open temp file exclusively to prevent simultaneous edits
X     */
X    fd = open(tempfile, O_WRONLY|O_CREAT|O_EXCL, statbuf.st_mode);
X    if (fd < 0) {
X	panic(EX_OSFILE, "%s: open failed on %s: %m\n", program, tempfile);
X	/*NOTREACHED*/
X    }
X    temp = fdopen(fd, "w");
X    if (temp == NULL) {
X	panic(EX_OSERR, "%s: fdopen failed on %s: %m\n", program, tempfile);
X	/*NOTREACHED*/
X    }
X
X    /*
X     * loop through, grabbing fields from the ttys file and copying
X     * them to the temp file, altering the status fields as necessary
X     */
X    while (line = getline(ttys)) {
X	/* is this one of the lines we are interested in? */
X	for (ap = argv; *ap; ap++) {
X	    if (strcmp(*ap, line+2) == 0) {
X		/* yes */
X		if (argmap[ap-argv] > 0) {
X		    (void)fprintf(stderr,
X				  "%s: warning: %s in %s twice\n",
X				  program, *ap, ttysfile);
X		}
X		argmap[ap-argv] = 1;
X		if (line[0] == to) {
X		    (void) fprintf(stderr, "%s: %s is already %s\n",
X				   program, *ap, to=='1'? "on": "off");
X		}
X		line[0] = to;
X		break;
X	    }
X	}
X
X	/* output the field to the temp file */
X	putline(line, temp);
X    }
X
X    /*
X     * check map for anything that wasn't found
X     */
X    for (i = 0; i < argc; i++) {
X	if (argmap[i] == 0) {
X	    (void) fprintf(stderr, "%s: line %s not found in %s\n",
X			   program, argv[i], ttysfile);
X	}
X    }
X
X    /* finish everything up */
X    (void) fclose(ttys);	/* close files */
X    if (fclose(temp) == EOF) {
X	panic(EX_OSERR, "%s: could not close %s: %m\n", program, temp);
X	/*NOTREACHED*/
X    }
X    (void) unlink(backup);	/* remove previous backup */
X    if (link(ttysfile, backup) < 0) { /* create a new backup */
X	panic(EX_OSERR, "%s: link from %s to %s failed: %m\n",
X	      program, ttysfile, backup);
X	/*NOTREACHED*/
X    }				/* install new ttys file */
X    if (rename(tempfile, ttysfile)) {
X	panic(EX_OSERR, "%s: rename from %s to %s failed: %m\n",
X	      program, tempfile, ttysfile);
X	/*NOTREACHED*/
X    }
X    if (kill(1, SIGHUP) < 0) {	/* inform init of changes */
X	panic(EX_OSERR, "%s: failed to notify init: %m\n", program);
X	/*NOTREACHED*/
X    }
X    exit(0);			/* all done, everything went okay (?) */
X    /*NOTREACHED*/
X}
X
X/*
X * the user must either be root or be in the auth_group.  Otherwise
X * panic with a security violation.
X */
Xvoid
Xsecurity_check()
X{
X    register i;			/* index */
X    register n;			/* index limit */
X    int gidset[NGROUPS];	/* set of user's groups */
X    struct group *gr;		/* group file entry */
X    register int gid;		/* group we are searching for in gidset */
X
X    /* are we root? */
X    if (getuid() == 0) return;	/* yes */
X
X    /* get id of authorized group */
X    gr = getgrnam(auth_group);
X    if (gr == NULL) {
X	panic(EX_SOFTWARE, "%s: no such group %s\n", program, auth_group);
X	/*NOTREACHED*/
X    }
X    gid = gr->gr_gid;
X
X    n = getgroups(NGROUPS, gidset);
X    /* search for authorized group id */
X    for (i = 0; i < n; i++) {
X	if (gidset[i] == gid) return; /* user looks okay to me */
X    }
X    /* not found */
X    panic(EX_NOPERM, "%s: you are not allowed to run this program\n", program);
X    /*NOTREACHED*/
X}
X
X/*
X * return the next line from the ttys file.
X * panic if there is anything obviously wrong with it.
X * return NULL on end of file.
X */
Xchar *
Xgetline(stream)
X    FILE *stream;		/* file to take the fields from */
X{
X    static char buf[MAXLINE+1];	/* put the input line here */
X    register char *s;		/* return value from fgets */
X    register int l;
X
X    s = fgets(buf, MAXLINE, stream);
X    if (s == NULL && ferror(stream)) {
X	panic(EX_IOERR, "%s: read error in %s: %m\n", program, ttysfile);
X	/*NOTREACHED*/
X    }
X    if (s) {
X	l = strlen(s);
X	if (s[l-1] != '\n' || (l < 4 && (s[l-1] = '\0', 1))) {
X	    panic(EX_DATAERR, "%s: illegal line in %s: %s\n",
X		  program, ttysfile, s);
X	    /*NOTREACHED*/
X	}
X    }
X    s[l-1] = '\0';		/* strip the newline */
X    return s;
X}
X
X/*
X * write out the given string to the file.  panic on error.
X */
Xvoid
Xputline(line, stream)
X    char *line;
X    FILE *stream;
X{
X    (void) fputs(line, stream);
X    (void) putc('\n', stream);
X    if (ferror(stream)) {
X	panic(EX_IOERR, "%s: error writing to %s: %m\n", program, tempfile);
X	/*NOTREACHED*/
X    }
X}
X
X/*
X * malloc with panic on error
X */
Xchar *
Xxmalloc(size)
X    unsigned size;
X{
X    register char *buf;
X    extern char *malloc();
X
X    buf = malloc(size);
X    if (buf == NULL) {
X	panic(EX_OSERR, "%s: could not malloc: %m\n", program);
X	/*NOTREACHED*/
X    }
X    return buf;
X}
X
X/*
X * signals are sent here to generate an error.
X */
Xint
Xinterupt()
X{
X	panic(EX_SOFTWARE, "\nsignal\n");
X	/*NOTREACHED*/
X}
X
X/*
X * process a panic string and exit with the given exit code
X * format is as with printf, only there are no numeric codes
X * within fields, and the %m field is replaced by a string
X * representing the current error.
X */
X/*VARARGS2*/
Xvoid
Xpanic(exitstat, fmt, args)
X    int exitstat;		/* call exit with this value */
X    char *fmt;			/* printf-like format string */
X    char args;			/* anchor for the rest of the args */
X{
X    register int c;		/* current format char */
X    register char *ap = &args;	/* pointer to args */
X    register int i;		/* index */
X    char *itoa();
X
X    /*
X     * process the format string
X     */
X    while (c = *fmt++) if (c != '%') {
X	/* not a percent field, just output the current char */
X	(void) putc(c, stderr);
X    } else {
X	switch(*fmt++) {
X	case 'm':		/* print the current error */
X	    if (errno >= sys_nerr || errno < 0) {
X		/* outside of the range of known errors */
X		(void) fputs("Error ", stderr);
X		(void) fputs(itoa((unsigned)errno, 10), stderr);
X	    } else {
X		(void) fputs(sys_errlist[errno], stderr);
X	    }
X	    break;
X	case 's':		/* output a string */
X	    (void) fputs(*(char **)ap, stderr);
X	    ap += sizeof(char *);
X	    break;
X	case 'c':		/* output a single character */
X	    (void) putc(*(int *)ap, stderr);
X	    ap += sizeof(int);
X	    break;
X	case 'd':		/* output a signed decimal integer */
X	    i = *(int *)ap;
X	    if (i < 0) {
X		(void) putc('-', stderr);
X		i = -i;
X	    }
X	    (void) fputs(itoa((unsigned)i, 10), stderr);
X	    ap += sizeof(int);
X	    break;
X	case 'u':		/* output an unsigned decimal interger */
X	    (void) fputs(itoa(*(unsigned *)ap, 10), stderr);
X	    ap += sizeof(unsigned);
X	    break;
X	case 'o':		/* an unsigned octal integer */
X	    (void) fputs(itoa(*(unsigned *)ap, 8), stderr);
X	    ap += sizeof(unsigned);
X	    break;
X	case 'x':		/* an unsigned hexadecimal integer */
X	    (void) fputs(itoa(*(unsigned *)ap, 16), stderr);
X	    ap += sizeof(char *);
X	    break;
X	case '%':		/* a percent sign */
X	    (void) putc('%', stderr);
X	}
X    }
X    /*
X     * if temp file open, remove it.
X     */
X    if (temp) {
X	(void) fclose(temp);
X	(void) unlink(tempfile);
X    }
X    exit(exitstat);
X    /*NOTREACHED*/
X}
X
X/*
X * convert an integer to a string representation in the given base
X */
Xchar *
Xitoa(n, base)
X    register unsigned n;	/* number to print */
X    register int base;		/* base to print it in */
X{
X    static char buf[64];	/* big enough to store any reasonable number */
X    register char *p;		/* pointer to buf */
X
X    p = buf+64;			/* point to end, and work back */
X    *--p = '\0';		/* null terminate the string */
X
X    /*
X     * fill in buf until we are done
X     */
X    while (n) {
X	*--p = "0123456789abcdefghijklmnopqrstuvwxyz"[n%base];
X	n /= base;
X    }
X    if (*p == '\0') {
X	*--p = '0';		/* handle case of the number 0 */
X    }
X    return p;			/* return the value; */
X}
EOF
	if test $? -ne 0; then
		echo "	WARNING:  status $? returned by sed"
	fi
else
	echo "	WARNING:  \"enable42.c\" already exists, will not overwrite"
fi

echo "Inspecting for damage in transit..."
ERROR=
EXPECT=`wc -c "README" 2>&1 | awk '{print 0 + $1}'`
if test $EXPECT -ne 812; then
	echo "	ERROR:  \"README\":  expected 812 chars, got $EXPECT"
	ERROR=1
fi
EXPECT=`wc -c "Makefile" 2>&1 | awk '{print 0 + $1}'`
if test $EXPECT -ne 928; then
	echo "	ERROR:  \"Makefile\":  expected 928 chars, got $EXPECT"
	ERROR=1
fi
EXPECT=`wc -c "enable.man" 2>&1 | awk '{print 0 + $1}'`
if test $EXPECT -ne 1356; then
	echo "	ERROR:  \"enable.man\":  expected 1356 chars, got $EXPECT"
	ERROR=1
fi
EXPECT=`wc -c "enable.c" 2>&1 | awk '{print 0 + $1}'`
if test $EXPECT -ne 16200; then
	echo "	ERROR:  \"enable.c\":  expected 16200 chars, got $EXPECT"
	ERROR=1
fi
EXPECT=`wc -c "enable42.c" 2>&1 | awk '{print 0 + $1}'`
if test $EXPECT -ne 11781; then
	echo "	ERROR:  \"enable42.c\":  expected 11781 chars, got $EXPECT"
	ERROR=1
fi


if test -z "$ERROR"; then echo "	No errors."; fi

# end of last shar archive out of 1

exit 0

-- 

Rich $alz
Cronus Project, BBN Labs			rsalz@bbn.com
Moderator, comp.sources.unix			sources W
    ws
us
u