chris@umcp-cs.UUCP (Chris Torek) (09/03/85)
# Newacct is a program that creates an "account request" form, # has the user fill it out, then sends mail off to someone to # get the account installed. We use it for account requests on # our local machines. It could certainly be fancier, but it # works well enough as is. # # No manual entry (sorry), but the program is self explanatory. # You will need 4.2BSD (or remove the gethostname() system calls) # and the Maryland Window Library to run it (or convert it to # curses). # # It also uses the error() routine we have in our C library. I've # included the Vax/Sun/NS32000 version with the rest of the code. : Run this shell script with "sh" not "csh" PATH=:/bin:/usr/bin:/usr/ucb export PATH all=FALSE if [ x$1 = x-a ]; then all=TRUE fi /bin/echo 'Extracting newacct.c' sed 's/^X//' <<'//go.sysin dd *' >newacct.c #ifndef lint static char rcsid[] = "$Header: /usr/src/local/bin/RCS/newacct.c,v 1.5 85/09/02 18:23:24 bin Exp $"; #endif X/* * New Account Request program * * Version 3.0, 2 Sep 1985, Chris Torek. * * Allows one to fill in an "entry form" for a new account. * % cc -O -o newacct newacct.c -lwinlib -ltermlib */ #include <stdio.h> #include <local/window.h> #include <pwd.h> #include <grp.h> #include <signal.h> #include <ctype.h> X/* * conffile contains configuration info in the form "keyword=[ \t]*value\n". * It can override all the other strings given below. */ char conffile[] = "/usr/adm/newacct.conf"; char mailcommand[200] = "/usr/ucb/mail -s 'new account' account-master"; char homedir[50] = "/u"; char defaultgroup[20] = "misc2"; char defaultmachine[100]; char machinenames[300]; char dictionary[100] = "/usr/dict/words"; char localdict[100] = "/usr/dict/local"; char badpasswds[100] = "/usr/adm/badpasswds"; struct conf { char *c_name; char *c_value; int c_size; } confitems[] = { #define ENTRY(n,s) n, s, sizeof s ENTRY ("mailcmd", mailcommand), ENTRY ("home", homedir), ENTRY ("group", defaultgroup), ENTRY ("defaultmachine", defaultmachine), ENTRY ("machines", machinenames), ENTRY ("dictionary", dictionary), ENTRY ("localdict", localdict), ENTRY ("badpasswds", badpasswds), 0, 0, 0 #undef ENTRY }; #define ROWS 15 /* Number of rows required */ #define COLS 76 /* Number of columns required */ int Uid, Gid, Which; Win *MainWin, *ErrorWin; char *Machines[30]; extern int errno; int VerifyLogin (), VerifyGroup (), VerifyPassword (); int VerifyMachine (), VerifyOK (); #define NFIELDS (sizeof Fields / sizeof *Fields) struct entry { char *e_descr; /* Description */ char *e_space; /* Ptr to space for entry */ int e_size; /* Size of entry (# chars) */ int e_rsize; /* Real size (for passwd, which grows) */ int e_noecho; /* True if should blank-type (for passwd) */ int (*e_verify) (); /* Verification function: true => good */ } Fields[] = { #define X_LOGIN 0 "Login name:", 0, 8, 8, 0, VerifyLogin, #define X_FULLNAME 1 "Full name:", 0, 20, 20, 0, VerifyOK, #define X_GROUP 2 "Group:", 0, 8, 8, 0, VerifyGroup, #define X_PASSWD 3 "Password:", 0, 8, 14, 1, VerifyPassword, #define X_OFFICE 4 "Office:", 0, 10, 10, 0, VerifyOK, #define X_OPHONE 5 "Office phone:", 0, 12, 12, 0, VerifyOK, #define X_HPHONE 6 "Home phone:", 0, 12, 12, 0, VerifyOK, #define X_OTHER 7 "Other info:", 0, 20, 20, 0, VerifyOK, #define X_FUND 8 "Account:", 0, 20, 20, 0, VerifyOK, #define X_INCHARGE 9 "Person in charge:", 0, 20, 20, 0, VerifyOK, #define X_MACHINE 10 "Machine name:", 0, 10, 10, 0, VerifyMachine, }; FILE *popen (); struct passwd *getpwent (); struct group *getgrnam (); char *malloc (), *index (), *strcpy (); #define CTRL(c) ((c) & 0x1f) #define move(y,x) WAcursor(MainWin, y, x) #define refresh() (WRCurRow=MainWin->w_cursor.row, \ WRCurCol=MainWin->w_cursor.col, Wrefresh(0)) main (argc, argv) int argc; char **argv; { register int c, i; register struct entry *ep; int rrows, rcols; if (argc > 1) error (1, 0, "usage: %s\n", *argv); Configure (); i = strlen (homedir); if (i == 0 || homedir[i - 1] != '/') { homedir[i] = '/'; homedir[i + 1] = 0; } MakeMachines (); if (Winit (0, 0)) { printf ("Sorry, your terminal won't run this program.\n"); printf ("Here is your free consolation fortune:\n\n"); fflush (stdout); execl ("/usr/games/fortune", "fortune", (char *)0); execl ("/usr/local/bin/fortune", "fortune", (char *)0); error (1, errno, "So much for \"fortune\""); } Wscreensize (&rrows, &rcols); if (rcols < COLS || rrows < ROWS) { Wcleanup (); printf ("Sorry, but your screen is too small.\n"); exit (1); } if ((MainWin = Wopen (0, 0, 0, COLS, ROWS, 0, 0)) == 0) { Wcleanup (); printf ("Something is wrong (window open failed)\n"); printf ("Contact the system administrator\n"); exit (1); } Woncursor (MainWin, 0); ep = Fields; for (i = 0; i < NFIELDS; i++) { register char *p = malloc (ep -> e_rsize + 1); if (p == 0) error (1, errno, "malloc failed - consult newacct guru"); *p = 0; ep -> e_space = p; ep++; } WSetRealCursor++; center ("Please fill in this form"); ep = Fields; for (i = 0; i < NFIELDS; i++) { move (i / 2 + 2, (i & 1) ? COLS / 2 : 0); Wputs (ep -> e_descr, MainWin); ep++; } move (8, 0); center ("^L Redraw screen ^D Done, send message "); move (9, 0); center ("^M Move to next entry ^U Move to previous entry"); move (10, 0); center ("Type to change, use ESC to cancel change"); move (13, 0); center ("If you don't know what to enter for something, leave it blank"); strcpy (Fields[X_GROUP].e_space, defaultgroup); Which = X_GROUP; sel (0); strcpy (Fields[X_MACHINE].e_space, defaultmachine); Which = X_MACHINE; sel (0); Which = 0; sel (1); for (;;) { refresh (); c = getchar (); if (ErrorWin) Whide (ErrorWin); switch (c) { case CTRL ('l'): ScreenGarbaged++; break; case '\r': case '\n': case CTRL ('n'): sel (0); if (++Which >= NFIELDS) Which = 0; sel (1); break; case CTRL ('u'): case '\b': sel (0); if (--Which < 0) Which = NFIELDS - 1; sel (1); break; case CTRL ('d'): case EOF: save (); exit (0); default: if (c < ' ') gripe ("That key means nothing"); else { ChangeIt (c); sel (1); } break; } } } X/* * Center a string on the screen */ center (s) register char *s; { register int nb; register char *p = s; while (*p++); p--; nb = (COLS - (p - s)) / 2; while (--nb >= 0) Wputc (' ', MainWin); Wputs (s, MainWin); } X/* * Change one of the strings (the variable Which tells which one) */ ChangeIt (firstc) int firstc; { register int c, inlen = 0; register char *s = Fields[Which].e_space; char instr[81], remember[81]; int noecho, maxlen, legal; strcpy (remember, s); *s = 0; sel (1); Wsetmode (MainWin, WINVERSE); maxlen = Fields[Which].e_size; noecho = Fields[Which].e_noecho; for (c = firstc;; c = getchar ()) { if (ErrorWin) Whide (ErrorWin); if (c == '\r' || c == '\n' || c == CTRL ('u') || c == EOF || c == CTRL ('d')) { strcpy (s, inlen ? instr : remember); legal = (*Fields[Which].e_verify) (s); if (c != EOF) ungetc (c, stdin); if (legal) return 1; if (c != EOF) c = getchar (); /* un-ungetc */ strcpy (s, remember); } else if (c == 033) { strcpy (s, remember); return 0; } else if (c == '\b') { if (inlen) { Wputs ("\b \b", MainWin); instr[--inlen] = 0; } else gripe ("Can't backspace - at left margin"); } else if (c >= ' ' && c <= 0177 && c != ':' && c != ',' && c != ';') { if (inlen >= maxlen) gripe ("Can't enter any more characters"); else { Wputc (noecho ? ' ' : c, MainWin); instr[inlen++] = c; instr[inlen] = 0; } } else if (c == CTRL ('l')) ScreenGarbaged++; refresh (); } } X/* * Mark or unmark the current string with inverse video */ sel (inv) int inv; { register char *s = Fields[Which].e_space; register int bl, bl2; int y, x; y = Which / 2 + 2; x = Which % 2 ? COLS / 2 + 18 : 18; move (y, x); Wsetmode (MainWin, inv ? WINVERSE : 0); bl = Fields[Which].e_rsize - strlen (s); bl2 = Fields[Which].e_rsize - Fields[Which].e_size; if (bl < 0) bl2 += bl; Wputs (s, MainWin); while (--bl >= 0) Wputc (' ', MainWin); Wsetmode (MainWin, 0); while (--bl2 >= 0) Wputc (' ', MainWin); move (y, x); } X/* * Save the results of the session. Actually, send mail to someone. */ save () { register struct passwd *pw; register struct group *g; register int i = 1; register char *s; char remarks[1024], passwdentry[300]; Wcleanup (); /* Find a unique userid and the group id */ setpwent (); while ((pw = getpwent ()) != NULL) { if (pw -> pw_uid >= i) i = pw -> pw_uid + 1; } setgrent (); g = getgrnam (Fields[X_GROUP].e_space); /* Make an entry for the passwd file */ sprintf (passwdentry, "%s:%s:%d:%d:%s,%s,%s,%s,%s:%s%s:/bin/csh", Fields[X_LOGIN].e_space, Fields[X_PASSWD].e_space, i, g ? g -> gr_gid : 0, Fields[X_FULLNAME].e_space, Fields[X_OFFICE].e_space, Fields[X_OPHONE].e_space, Fields[X_HPHONE].e_space, Fields[X_OTHER].e_space, homedir, Fields[X_LOGIN].e_space); printf ("\ Oh, one other thing: please type in any additional\n\ information that could be helpful (e.g., why you are\n\ requesting this account), then enter a line consisting\n\ of a \".\" (period) on a line by itself.\n"); fflush (stdout); s = remarks; while (fgets (s, sizeof remarks - (s - remarks), stdin)) { if (strcmp (s, ".\n") == 0) break; s += strlen (s); } *s = 0; if ((i = fork ()) == 0) { /* Child, send mail */ register FILE *f = popen (mailcommand, "w"); if (f == 0) error (1, errno, "popen (\"%s\", \"w\") failed", mailcommand); fprintf (f, "\ %s Account Request:\n\ %s\n\ (group is %s, account %s, contact is %s)\n\ Remarks: %s\n", Fields[X_MACHINE].e_space, passwdentry, Fields[X_GROUP].e_space, Fields[X_FUND].e_space, Fields[X_INCHARGE].e_space, remarks); pclose (f); _exit (0); } if (i <= 0) error (1, errno, "fork"); printf ("\nYour account will be ready in a few days.\n"); } X/* * Field verification stuff */ VerifyOK () { return 1; /* call it OK */ } X/* * We may only bother checking name-in-use for local machine names. * This is a mistake if you are using several machines as a single * distributed system. If you really don't like this, turn the * disabled code back on. */ VerifyLogin () { register int i; #ifdef notdef static char thishost[sizeof defaultmachine]; if (thishost[0] == 0) (void) gethostname (thishost, sizeof thishost); if (strcmp (Fields[X_MACHINE].e_space, thishost)) return 1; #endif i = getpwnam (Fields[X_LOGIN].e_space) == 0; if (i == 0) gripe ("That name is in use"); return i; } VerifyGroup () { register int i = getgrnam (Fields[X_GROUP].e_space) != 0; if (i == 0) gripe ("There is no such group"); return i; } VerifyMachine (p) register char *p; { register char **l = Machines; register char *s; char buf[BUFSIZ]; while ((s = *l++) != 0) if (*s == *p && strcmp (s, p) == 0) return 1; strcpy (buf, "No such machine. Try one of:"); p = buf + strlen (buf); l = Machines; while ((s = *l++) != 0) { *p++ = ' '; while ((*p = *s++) != 0) p++; } *p++ = '.'; *p = 0; gripe (buf); return 0; } VerifyPassword (p) register char *p; { register int i, c; register char *np, *sp; long salt; char saltc[2]; static char buf[BUFSIZ]; if (!*p) { gripe ("You must specify a password"); return 0; } if (strlen (p) < 6) { gripe ("Must be at least 6 characters"); return 0; } if (alldigits (p)) { gripe ("Must have at least one non-digit"); return 0; } /* Try login name */ if (try (Fields[X_LOGIN].e_space, p)) { tooeasy: gripe ("Too easy to guess"); return 0; } /* Try full name, all pieces */ strcpy (buf, Fields[X_FULLNAME].e_space); for (np = buf; np && *np;) { if ((sp = index (np, ' ')) != 0) *sp = 0; if (try (np, p)) goto tooeasy; np = sp ? sp + 1 : 0; } /* Try word lists */ if (lookup (p, dictionary) || lookup (p, localdict) || lookup (p, badpasswds)) goto tooeasy; time (&salt); salt += getpid (); saltc[0] = salt & 077; saltc[1] = (salt >> 6) & 077; for (i = 0; i < 2; i++) { c = saltc[i] + '.'; if (c >= '9') c += 7; if (c > 'Z') c += 6; saltc[i] = c; } strcpy (p, crypt (p, saltc)); } alldigits (p) register char *p; { while (isdigit (*p++)); return (*p == 0); } try (guess, pwd) register char *guess; register char *pwd; { register int c1, c2; while (c1 = *guess++) { c2 = *pwd++; if (isupper (c1)) c1 = tolower (c1); if (isupper (c2)) c2 = tolower (c2); if (c1 != c2) return 0; } return *pwd == 0; } X/* * Gripe about something the user did. */ gripe (s) char *s; { if (!ErrorWin) { ErrorWin = Wopen (1, 0, 11, COLS, 1, 0, 0); Woncursor (ErrorWin, 0); Wwrap (ErrorWin, 0); } else Wunhide (ErrorWin); Wsetmode (ErrorWin, 0); WAcursor (ErrorWin, 0, 0); Wclear (ErrorWin, 0); Wsetmode (ErrorWin, WINVERSE); Wputs (s, ErrorWin); Ding (); } X/* * Read the configuration file. */ Configure () { register FILE *cf; register char *p; register struct conf *c; register char *val; int lineno; char buf[BUFSIZ]; /* first set up the default machine name */ (void) gethostname (defaultmachine, sizeof defaultmachine); if ((cf = fopen (conffile, "r")) == 0) return; lineno = 0; while (fgets (buf, sizeof buf, cf)) { lineno++; if (buf[0] == '#') /* comment */ continue; if ((p = index (buf, '\n')) != 0) *p = 0; if (buf[0] == 0) /* ignore blank lines */ continue; if ((p = index (buf, '=')) == 0) { p = "malformed line"; badconf: error (0, 0, "\"%s\", line %d: bad conf file (%s)", conffile, lineno, p); error (1, 0, "consult newacct guru"); /*NOTREACHED*/ } *p = 0; val = p + 1; while (isspace (*val)) val++; for (c = confitems; c -> c_name; c++) if (*c -> c_name == *buf && strcmp (c -> c_name, buf) == 0) goto found; *p = '='; p = "unrecognized keyword"; goto badconf; found: if (strlen (val) >= c -> c_size) { *p = '='; p = "value too long"; goto badconf; } (void) strcpy (c -> c_value, val); } (void) fclose (cf); } X/* * Turn the whitespace-separated list of machine names into a list * of pointers. */ MakeMachines () { register char *p, **l; l = Machines; p = machinenames; while (*p) { *l++ = p; while (*p) { /* while there's more of this name */ if (isspace (*p)) { *p++ = 0; while (isspace (*p)) p++; break; /* end there's more of this name */ } p++; } } *l = 0; } FILE *wordfile; X/* * Perform a case independent binary search for target in the (sorted) * word list in file filenam. */ lookup (target, filenam) char *target, *filenam; { register int c; long high, low, mid; char key[10], word[80]; #define CMP(s,t) ((*s) == (*t) ? strcmp (s, t) : (*s) - (*t)) if (wordfile != NULL) fclose (wordfile); if ((wordfile = fopen (filenam, "r")) == NULL) return 0; strcpy (key, target); lower (key); low = 0; fseek (wordfile, 0L, 2); high = ftell (wordfile); for (;;) { mid = (high + low) >> 1; fseek (wordfile, mid, 0); do { mid++; c = getc (wordfile); } while (c != EOF && c != '\n'); if (!getword (word)) break; c = CMP (key, word); if (c == 0) /* found it */ return 1; if (c < 0) { /* too far */ if (high == mid) break; /* stop spinning the wheels */ high = mid; } else /* not far enough */ low = mid; } /* at this point we've narrowed the range as much as we can; now search until either we find the word, or we go past the key. */ fseek (wordfile, low, 0); for (;;) { if (!getword (word)) return (0); c = CMP (key, word); if (c < 0) return 0; if (c == 0) return 1; } #undef CMP } X/* * Read a word and lowercasify it. */ getword (w) char *w; { register char *p = w; register int c; while ((c = getc (wordfile)) != '\n') { if (c == EOF) if (p == w) return 0; else break; *p++ = c; } *p = 0; lower (w); return 1; } X/* * Lowercasify a word. */ lower (s) register char *s; { register int c; while ((c = *s) != 0) { if (isupper (c)) *s = tolower (c); s++; } } //go.sysin dd * if [ `wc -c < newacct.c` != 16480 ]; then made=FALSE /bin/echo 'error transmitting "newacct.c" --' /bin/echo 'length should be 16480, not' `wc -c < newacct.c` else made=TRUE fi if [ $all = TRUE ]; then /bin/echo ' Changing owner to "bin"' /etc/chown bin newacct.c else /bin/echo ' Original owner was "bin"' fi if [ $made = TRUE ]; then /bin/chmod 644 newacct.c /bin/echo -n ' '; /bin/ls -ld newacct.c fi /bin/echo 'Extracting newacct.conf' sed 's/^X//' <<'//go.sysin dd *' >newacct.conf # This file controls the operation of the newacct program. # Keywords: # mailcmd home group defaultmachine machines dictionary # localdict badpasswds mailcmd=/usr/ucb/mail -s 'account request' account-master home= /usr group= misc machines=gymble gyre mimsy tove //go.sysin dd * if [ `wc -c < newacct.conf` != 264 ]; then made=FALSE /bin/echo 'error transmitting "newacct.conf" --' /bin/echo 'length should be 264, not' `wc -c < newacct.conf` else made=TRUE fi if [ $all = TRUE ]; then /bin/echo ' Changing owner to "root"' /etc/chown root newacct.conf else /bin/echo ' Original owner was "root"' fi if [ $made = TRUE ]; then /bin/chmod 644 newacct.conf /bin/echo -n ' '; /bin/ls -ld newacct.conf fi /bin/echo 'Extracting error.c' sed 's/^X//' <<'//go.sysin dd *' >error.c #include <stdio.h> char *_argv0; /* argv[0], set by C startup code */ X/* * error - University of Maryland specific (sigh) * * Useful for printing error messages. Will print the program name * and (optionally) the system error associated with the values in * <errno.h>. * * Note that the type (and even the existence!) of ``arg'' is undefined. */ error(quit, e, fmt, arg) int quit; register int e; char *fmt; { extern char *sys_errlist[]; extern int sys_nerr; register char *p = _argv0; if (p != NULL) { #ifdef optional char *s, *rindex(); if ((s = rindex(p, '/')) != NULL) p = s + 1; #endif (void) fprintf(stderr, "%s: ", p); } _doprnt(fmt, &arg, stderr); /* magic */ if (e > 0) { if (e < sys_nerr) (void) fprintf(stderr, ": %s", sys_errlist[e]); else (void) fprintf(stderr, ": unknown error number %d", e); } (void) putc('\n', stderr); (void) fflush(stderr); if (quit) exit(quit); } //go.sysin dd * if [ `wc -c < error.c` != 934 ]; then made=FALSE /bin/echo 'error transmitting "error.c" --' /bin/echo 'length should be 934, not' `wc -c < error.c` else made=TRUE fi if [ $all = TRUE ]; then /bin/echo ' Changing owner to "bin"' /etc/chown bin error.c else /bin/echo ' Original owner was "bin"' fi if [ $made = TRUE ]; then /bin/chmod 664 error.c /bin/echo -n ' '; /bin/ls -ld error.c fi -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 4251) UUCP: seismo!umcp-cs!chris CSNet: chris@umcp-cs ARPA: chris@maryland