goldfish@CONCOUR.CS.CONCORDIA.CA (03/08/91)
Below are two "shar" files I have created containing a command line argument handler and two useful routines for handling password queries. Apologies to all if this is not the correct place to post sources, however I received several requests on this group and several suggestions on how to write the programs, so I thought this would be an appropriate place for the sources. The routines should work on most-anything that maintains some semblance of the UNIX /etc/passwd paradigm, except that I have not tested it on other platforms ... :-( The problem with Sun is that they don't ship a modern C compiler, just this anachronistic throwback to the days of Lunar landings and SIXTEEN BIT mainframes. I also have not had time to get it running on our DECStations, since I don't remember seeing a debugger which required a manual in a long time (I currently use DDE and Turbo C). I will re-post this bunch to Comp.Sources or some-such when I get the time to fix it, however it runs on Apollo Domain-OS 10.2 RIGHT NOW. so laziness aside, these programs will work (until HP's next "foot-hunt") To install these programs, unpack the "shar" files (there are two of them) into separate directories and edit the "Makefile" in each to point to things in the right place. (the "userinfo" program needs binaries from the "argtok" package.) You should be able to safely run the programs with little fear of damage, since they never write anywhere save stdout and stderr. The man pages are self explanatory and give examples of how to use the programs. I don't describe myself as an expert "C" hacker, so comments as to form are welcome. Since most software usually has a disclaimer: (-: The author of the software makes no assertions :-) (-: whatsoever that the software or documentation :-) (-: contained herein performs according to its :-) (-: stated function, the function for which its use :-) (-: is intended, or indeed any useful function :-) (-: whatsoever. :-) (-:I am not limiting the use of this code in any :-) (-: way, or requesting any royalties, however if :-) (-: anyone has an unmarried Jewish Princess to :-) (-: spare, I would not complain. If you want to be :-) (-: nice, leave my name and E-Mail address in the :-) (-: source code; if you want to be nasty, claim it :-) (-: was your idea and I rewrote it to add lots of :-) (-: bugs :-) Oh YES! if you really want to thank me, send me source of the program "Shar", my good version dissappeared and this was generated on a PC. -- Paul Goldsmith <goldfish@concour.cs.concordia.ca> (514) 848-3031 (Shirley Maclaine told me there would be LIFETIMES like this) ********************* First SHAR file *********************** #!/bin/sh echo extracting Makefile ... cat >Makefile <<xzyyz # Makefile for userinfo # -- Goldfish 90/09/14 ############################################################ # Global assignments ############################################################ CC=cc # the preceding assignment points to the "personal" # include directory INCLUDE=$(HOME)/Include # the preceding assignment points to the "personal" # library directory LIBRARY=$(HOME)/Lib # This is the directory to which the "production" # binary goes BIN=/site/bin # This is the directory to which the "production" man # page goes MAN=/usr/man/mann ############################################################ # options and flags ############################################################ COMPILEOPTION=-g -I$(INCLUDE) ############################################################ # First action performs the complete compile ############################################################ all: userinfo freeunixno ############################################################ # Compile and link the parts of freeunixno ############################################################ freeunixno: freeunixno.o $(CC) $(COMPILEOPTION) freeunixno.o $(LIBRARY)/argtok.o $(LIBRARY)/bstran.o -o freeunixno freeunixno.o: freeunixno.c $(CC) $(COMPILEOPTION) -c freeunixno.c freeunixno.c: freeunixno.c,v co freeunixno.c ############################################################ # Compile and link the parts of userinfo ############################################################ userinfo: userinfo.o $(CC) $(COMPILEOPTION) userinfo.o $(LIBRARY)/argtok.o $(LIBRARY)/bstran.o -o userinfo userinfo.o: userinfo.c $(CC) $(COMPILEOPTION) -c userinfo.c userinfo.c: userinfo.c,v co userinfo.c ############################################################ # execute the current version for testing purposes ############################################################ test: testuserinfo testfreeunixno testuserinfo: userinfo echo root | ./userinfo root - ./userinfo -pw ./test.pw -all -f 'Name:\t"%u"\tPassword:\t"%p"\ User No:\t"%U"\tGroup:\t"%G"\ GECOS:\ "%i"\ \tName: (last)"%l"\t(first)"%f"\tsub-0:"%0"\ \tOffice:\t"%o"\tsub-1:"%1"\ \tRoom:\t"%r"\tsub-2:"%2"\ \tPhone:\t"%t"\tsub-3:"%3"\ \t\tsub-4:"%4"\ \t\tsub-5:"%5"\ \t\tsub-6:"%6"\ \t\tsub-7:"%7"\ \t\tsub-8:"%8"\ \tID:\t"%I"\tsub-9:"%9"\ Home:\t"%h"\ Shell:\t"%s"' testfreeunixno: freeunixno ./freeunixno -lo0 -hi20 -count 2 # ./freeunixno -lo0 -hi20 ############################################################ # remove re-creatable files ############################################################ clean: rm *.o *~ *.BAK *.bak arg ############################################################ # install global files onto appropriate libraries ############################################################ production: userinfoproduction freeunixnoproduction userinfoproduction: $(BIN)/userinfo $(MAN)/userinfo.n freeunixnoproduction: $(BIN)/freeunixno $(MAN)/freeunixno.n ############################################################ # install userinfo ############################################################ $(BIN)/userinfo: userinfo userinfo.n cp userinfo $(BIN)/userinfo userinfo.n: userinfo.n,v co userinfo.n $(MAN)/userinfo.n: userinfo.n cp userinfo.n $(MAN)/userinfo.n ############################################################ # install freeunixno ############################################################ $(BIN)/freeunixno: freeunixno cp freeunixno $(BIN)/freeunixno freeunixno.n: freeunixno.n,v co freeunixno.n $(MAN)/freeunixno.n: freeunixno.n cp freeunixno.n $(MAN)/freeunixno.n ############################################################ # end of Makefile ############################################################ xzyyz echo extracting freeunixno.c ... cat >freeunixno.c <<xzyyz #include <stdio.h> #include <math.h> #include <pwd.h> #include <grp.h> #include "argtok.h" #include "bstran.h" #define TRUE 1 #define FALSE 0 #define DEFFMT "%5d\n" #define DEFLO "0" #define DEFHI "32767" #define DEFCOUNT "32767" #define USERNOSPACE 32768 #define STARTTICK 16 /* purpose: to return a range of unix numbers * * comments: This program is used in place of "grep" calls to locate * * Notes: * * Author: Paul Goldsmith 90/09/13 */ char *silent; main(int argc, char *argv[] ) { int i, ihi, ilo, icount; struct passwd *pass; char *format = argtok(argc, argv, "-f", DEFFMT); char *lo = argtok(argc, argv, "-lo", DEFLO); char *hi = argtok(argc, argv, "-hi", DEFHI); char *count = argtok(argc, argv, "-count", DEFCOUNT); char numberspace[USERNOSPACE]; char wkfmt[256]; register int tickcount; ilo = atoi(lo); ihi = atoi(hi); icount = atoi(count); silent = argtok(argc, argv, "-s", 0); tickcount = (silent ? -1 : STARTTICK); while ( pass = getpwent() ) { if ( ! tickcount--) { fputc ( '.', stderr ); fflush (stderr); tickcount = STARTTICK; } numberspace[pass->pw_uid]=TRUE; } if ( ! silent ) fputc ( '\n', stderr ); if ( strcmp(format, "-") == 0 ) { format = wkfmt; } for ( i = ((ilo>0)?ilo:0) ; (i <= ((ihi<USERNOSPACE)?ihi:USERNOSPACE) ) && (icount > 0) ; i++ ){ if ( ! numberspace[i]) { if ( format == wkfmt ) { if ( gets (wkfmt) ) { printf(bstran(wkfmt), i); }else { return; } }else { printf(format,i); } icount--; } } } xzyyz echo extracting test.pw ... cat >test.pw <<xzyyz Name:Password:UserNo:GroupNo:LASTNAME firstname, Office, (Room), Phone, fill-4, fill-5, fill-6, fill-7, fill-8, ID Number:/home/path/name:/bin/shell xzyyz echo extracting userinfo.c ... cat >userinfo.c <<xzyyz /* $Log: userinfo.c,v $ * Revision 1.7 91/01/30 14:22:21 goldfish * include a "%H" real home token, tidy up docs, use bstran instead of bschar * * Revision 1.6 91/01/18 14:13:18 root * *** empty log message *** * * Revision 1.5 91/01/18 14:12:15 root * remove default trailing newline from format string * * Revision 1.4 91/01/17 11:58:59 root * finetune main.c * * Revision 1.3 91/01/16 16:50:01 root * fix "-version flag" * fix "-all" flag. * reformat error messages. * * Revision 1.2 91/01/16 16:22:35 root * cosmetic revisions to accomodate RCS; add a "-version" flag. * */ #include <string.h> #include <stdio.h> #include <pwd.h> #include <grp.h> #include "argtok.h" #include "bstran.h" #include <sys/param.h> #define TRUE 1 #define FALSE 0 #define DEFFMT "%u:%p:%U:%G:%i:%h:%s\n" #define INVALIDGROUP "**INVALID**" #define MAXUSERNAMELEN 127 #define MAXFILENAMELEN 127 #define NULLPWFILE "" #define VERSION "$Revision: 1.7 $\n" /* purpose: to query password registry reliably using unix calls * * comments: This program is used in place of "grep" calls to locate * information in the /etc/passwd file. I employs the (hopefully) * reliable c function call "getpwnam()". Considerable flexibility is * afforded in interpreting the output. * * * Author: Paul Goldsmith 90/07/11 */ int chdir(); char *getwd(); char *silent; char *vraipath ( char *name, char *vrainame) { if ( ! chdir(name) ) { if ( getwd (vrainame) ) { return vrainame ; }else { return 0 ; } }else { return 0 ; } }; char bschar( char tc ) { switch (tc) { case 'n' : return '\n'; case 't' : return '\t'; case 'v' : return '\v'; case 'b' : return '\b'; case 'r' : return '\r'; case 'f' : return '\f'; case 'a' : return '\a'; case '\\' : return '\\'; case '?' : return '\?'; default : return tc ; } }; struct gecosfields { char *firstname; char *lastname; char *office; char *room; char *phone; char *spare[5]; char *id; }; char *eatwhitespace ( char * str) { int length; char *begstr = str; if ( begstr == 0 ) return 0 ; while ( isspace(*begstr) ) { begstr++; }; length = strlen(begstr); while ( --length >= 0 && isspace(begstr[length]) ) { begstr[length] = 0; }; return begstr; } void splitgecos ( char *gecos, struct gecosfields *fields ) { char *dblspc; fields->lastname = eatwhitespace(strtok(gecos, ",")); dblspc = fields->lastname; while ( (*dblspc != 0 ) && strncmp( " ", dblspc, 2) ) { dblspc++; } if ( *dblspc != 0 ) { *dblspc = 0; dblspc += 2; } fields->firstname = eatwhitespace(dblspc); fields->office = eatwhitespace(strtok (0, ",")); fields->room = eatwhitespace(strtok (0, ",")); fields->phone = eatwhitespace(strtok (0, ",")); fields->spare[0] = eatwhitespace(strtok (0, ",")); fields->spare[1] = eatwhitespace(strtok (0, ",")); fields->spare[2] = eatwhitespace(strtok (0, ",")); fields->spare[3] = eatwhitespace(strtok (0, ",")); fields->spare[4] = eatwhitespace(strtok (0, ",")); fields->id = eatwhitespace(strtok (0, ",")); } printpasswd (char *format, struct passwd *pass) { struct group *grp = getgrgid(pass->pw_gid); struct gecosfields gecos; char scr[256]; splitgecos( strcpy(scr,pass->pw_gecos), &gecos); { char *c; for (c = format; *c; c++) { if (*c == '%') { switch (*(++c)) { case '%': putchar('%'); break; case 'u': printf("%s", pass->pw_name); break; case 'p': printf("%s", pass->pw_passwd); break; case 'U': printf("%d", pass->pw_uid); break; case 'G': printf("%d", pass->pw_gid); break; case 'g': printf("%s", ( (grp) ? (grp->gr_name) : (INVALIDGROUP)) ); break; /* case 'q': printf("%d", pass->pw_quota); break; */ case 'c': printf("%s", pass->pw_comment); break; case 'i': printf("%s", pass->pw_gecos); break; case '0': case 'l': printf("%s", gecos.lastname); break; case 'f': printf("%s", gecos.firstname); break; case '1': case 'o': printf("%s", gecos.office); break; case '2': case 'r': printf("%s", gecos.room); break; case '3': case 't': printf("%s", gecos.phone); break; case '4': printf("%s", gecos.spare[0]); break; case '5': printf("%s", gecos.spare[1]); break; case '6': printf("%s", gecos.spare[2]); break; case '7': printf("%s", gecos.spare[3]); break; case '8': printf("%s", gecos.spare[4]); break; case '9': case 'I': printf("%s", gecos.id); break; case 'h': printf("%s", pass->pw_dir); break; case 'H': { char path[MAXFILENAMELEN] ; if ( vraipath( pass->pw_dir, path) ) printf("%s", path); } break; case 's': printf("%s", pass->pw_shell); break; default: putchar('%'); putchar(*c); break; } } else { putchar(*c); } } } }; printuser (char *format, char *name) { struct passwd *pass; struct group *grp; struct gecosfields gecos; if (pass = getpwnam(name)) { printpasswd( format, pass); } else if (!silent) { fprintf(stderr, "user %s not found\n", name); } }; printall (char *format) { struct passwd *pass; struct group *grp; struct gecosfields gecos; while (pass = getpwent() ) { printpasswd( format, pass); } return (int)pass; }; main(int argc, char *argv[] ) { char **user = 0; char *pwfile = argtok(argc, argv, "-pw", NULLPWFILE); char *format = argtok(argc, argv, "-f", DEFFMT); char *all = argtok(argc, argv, "-all", 0); char *version = argtok(argc, argv, "-version", 0); silent = argtok(argc, argv, "-c", 0); if ( version ) { fputs(VERSION, stderr ); return 0; } if ( *pwfile ) { endpwent(); setpwfile(pwfile); } if ( all ) { return printall (format) ; }else { for (user = argv, user++; *user; user++) { if (! strcmp(*user, "-")){ char name[MAXUSERNAMELEN]; while ( gets(name) ) { printuser (format, name); } }else { printuser (format, *user); } } } }; xzyyz echo extracting userinfo.n ... cat >userinfo.n <<xzyyz .\" $Log$ .TH USERINFO .SH NAME userinfo \- display USER information from .B /etc/passwd .SH VERSION .B $Revision$ .SH SYNOPSIS .B userinfo tokenized display of information from .B /etc/passwd and .B /etc/group files with .I printf(3) style output .SH DESCRIPTION .I userinfo is a command which will accept a list of user names from either the command line or standard input. The parameter flag "-all" selects all the records in the password file. .I userinfo locates the users with the .I getpwnam(3) and .I getpwent(3) calls which on simple non\-networked systems resolves to the files .I /etc/passwd and .I /etc/group. .I Userinfo writes the information about the users to .I stdout. An optional format string may be given on the command line. It allows for a mixture of text and fields from .I /etc/passwd and .I /etc/group to be output in lieu of the default format. The default format is exactly what appears in the .I /etc/passwd file. This program may be used in place of .I grep(3) and .I awk(3) calls to locate information in the .I /etc/passwd file. It employs the reliable .B c function call .I getpwnam(3). Considerable flexibility is afforded in interpreting the output. .br .SH ARGUMENTS .B names ... \- .SH COMMAND LINE OPTIONS .TP .B \- read a list of names, one per line from .B stdin; the .B \- argument may appear anywhere in the list of names and will be inserted in place in that list .TP .B \-c operate silently; omits some optional warning message .TP .B \-f format string .TP .B \-pw name of an alternate password file. The default is: .I /etc/passwd .TP .B \-all Process .B all records in the password file instead of a selection. This option overrides all selections .TP .B \-version displays the version number and date of .Iuserinfo .SH EXECUTION The parameters are a list of names of the user being queried. The "-f" parameter denotes the format string. If it is omitted, the user information is written in the format of the /etc/passwd file. If a format string is included it is interpreted as follows: .TP .B %u user name .TP .B %p password (encrypted) .TP .B %U user number .TP .B %G group number .TP .B %g group name .TP .B %q quota .TP .B %c comment .TP .B %i GECOS fields. The GECOS fields are individually accessable. Their tokens follow below. .TP .B %h home directory .TP .B %H Fully qualified home directory pathname containing no soft links. Output is a null string if the pathname does not resolve to a real directory. .TP .B %s shell .TP .B %% "%" The following fields are components of the GECOS fields. .TP .B %l %0 GECOS last\-name field. (The lastname and firstname fields are delimited by a doublespace (" "). This is a local standard only. Also, by convention, the last name is in uppercase.) .TP .B %f GECOS first\-name field. (See above note This is a local standard only. Also, by convention, the last name is in lowercase.) .TP .B %o %1 GECOS office field. .TP .B %r %2 GECOS room field. .TP .B %t %3 GECOS Telephone number field. .TP .B %4 GECOS Fourth field (starting from zero). .TP .B %5 GECOS Fifth field (starting from zero). .TP .B %6 GECOS Sixth field (starting from zero). .TP .B %7 GECOS Seventh field (starting from zero). This field is used locally to hold the Apollo "organization" field. It does not get coppied into the actual password file. .TP .B %8 GECOS Eighth field (starting from zero). This field usually contains a "F" if the application form is correctly submitted, or is blank if no form has been submitted. < .TP .B %I %9 GECOS ID Number field. Locally defined to contain the unique ID number for each user. This field is confidential and must not be imbedded into the public "/etc/passwd" file. All other character seqences are printed verbatim. .SH EXAMPLES: The following example will print the .I /etc/passwd file entry for .B root .IP userinfo root .P or .IP echo root | userinfo - .P The following prints .I "user is root with home /" .IP userinfo root -f \"user is %u with home %h\\n\" .P The following prints a list of all account names with full user name and ID number .IP userinfo -all -f \"%u\\t%l\\t%f\\t%i\\n\" .SH NOTES Arguments and options may be freely mixed in any order. See argtok(3), a local package for command line argument parsing, for details. Remember to place the \"\\n\" at the end of the format string, or the entire output will be run together. .SH FILES .B /etc/passwd .B /etc/group .PD .SH SEE ALSO ypcat(8)[on Sun], grep(1), printf(3) .SH DIAGNOSTICS invalid user names will be echoed to .I stderr as: .br .B user .I username .B not found .br . .SH BUGS User names read from standard input must be stripped of leading and trailing blanks. The names are taken verbatim. Apollo maintains its password registry in a manner that will occasionally lock it for brief periods of time. The command will almost never fail on two consecutive tries when the network is functioning properly. xzyyz echo done exit 0 exit 0 *************** Second SHAR File *************** #!/bin/sh echo extracting Makefile ... cat >Makefile <<xzyyz # Makefile for argtok # -- Goldfish 90/09/14 ############################################################ # Global assignments ############################################################ CC=cc # the preceding assignment points to the "personal" include # directory INCLUDE=$(HOME)/Include # the preceding assignment points to the "personal" library # directory LIBRARY=$(HOME)/Lib # This is the directory to which the "production" BIN=/site/bin ############################################################ # options and flags ############################################################ #COMPILEOPTION=-g COMPILEOPTION= ############################################################ # First action performs the complete compile ############################################################ all: tools test ############################################################ # Compile and link the parts of the program ############################################################ tools: .o/main.o .o/argtok.o argtok.h .o/bstran.o bstran.h $(CC) $(COMPILEOPTION) .o/main.o .o/bstran.o .o/argtok.o -o tools .o/main.o: main.c .o/argtok.o argtok.h .o/bstran.o bstran.h $(CC) $(COMPILEOPTION) -c main.c -o .o/main.o .o/argtok.o: argtok.c argtok.h $(CC) $(COMPILEOPTION) -c argtok.c -o .o/argtok.o .o/bstran.o: bstran.c bstran.h $(CC) $(COMPILEOPTION) -c bstran.c -o .o/bstran.o ############################################################ # execute the current version for testing purposes ############################################################ test: tools echo "hello\\ttab\\nlinebreak" \ | ./tools -a one two three -b=four -c=five -d six -e | fmt ############################################################ # remove re-creatable files ############################################################ clean: rm -f .o/*.o *~ *.BAK *.bak tools ############################################################ # install global files onto appropriate libraries ############################################################ production: $(INCLUDE)/argtok.h $(LIBRARY)/argtok.o $(INCLUDE)/bstran.h $(LIBRARY)/bstran.o $(LIBRARY)/argtok.o: .o/argtok.o cp .o/argtok.o $(LIBRARY)/argtok.o $(INCLUDE)/argtok.h: argtok.h cp argtok.h $(INCLUDE)/argtok.h $(LIBRARY)/bstran.o: .o/bstran.o cp .o/bstran.o $(LIBRARY)/bstran.o $(INCLUDE)/bstran.h: bstran.h cp bstran.h $(INCLUDE)/bstran.h ############################################################ # end of Makefile ############################################################ xzyyz echo extracting argtok.c ... cat >argtok.c <<xzyyz #include "argtok.h" char *cutfirstelt(char **list) { char *first = *list; /* save first element */ while (list[0] = list[1]) list++; /* bump each element back one */ return first; } char *ARGTOK(int *argc, char **argv, char *token, char *deflt) { char **cur; char *value = deflt; int toklen = strlen(token); for (cur = argv; *cur; cur++) { if (!strncmp(token, *cur, toklen)) { if (strlen(*cur) == toklen) { /* token value is next parameter */ if (deflt) cutfirstelt(cur); /* token is not flag; leave */ /* flag on arg list */ value = cutfirstelt(cur); /* cut token value from list */ } else { /* token value is parm tail */ value = cutfirstelt(cur) + toklen; /* cut token parm from list and */ /* set value to parm tail */ }; break; } if (!strcmp("--",token) ) break; /* stop scanning at double minus */ } for (*argc = 0, cur = argv; *(cur++) ; (*argc)++); /* count remaining args */ return value; }; xzyyz echo extracting argtok.h ... cat >argtok.h <<xzyyz #define ARGDELIM "--" #define argtok(c,v,t,d) ARGTOK(&(c),v,t,d) char *ARGTOK( int *argc, char **argv, char *token, char *deflt); xzyyz echo extracting bstran.c ... cat >bstran.c <<xzyyz #include "bstran.h" char *bstran( char *ts ) { char *curin, *curout; for ( curout = curin = ts ; *curin ; curin++, curout++ ) { if ( *curin == '\\' ) { switch ( *(++curin) ) { case '\000' : *curout = '\000' ; return ts; case 'n' : *curout = '\n'; break ; case 't' : *curout = '\t'; break ; case 'v' : *curout = '\v'; break ; case 'b' : *curout = '\b'; break ; case 'r' : *curout = '\r'; break ; case 'f' : *curout = '\f'; break ; case 'a' : *curout = '\a'; break ; case '\\' : *curout = '\\'; break ; case '?' : *curout = '\?'; break ; default : *curout = *curin ; } } else { *curout = *curin; } } *curout = '\000'; return ts; }; xzyyz echo extracting bstran.h ... cat >bstran.h <<xzyyz char *bstran( char *ts ); xzyyz echo extracting main.c ... cat >main.c <<xzyyz #include <stdio.h> #include "argtok.h" #include "bstran.h" #define booltostr(b) (b ? "True" : "False") main(int argc, char **argv, char **envp) { char *a, *b, *c, *d, *e, *f, *g; a = argtok(argc, argv, "-a", "a not found"); b = argtok(argc, argv, "-b", "b not found"); c = argtok(argc, argv, "-c", 0); d = argtok(argc, argv, "-d", "d not found"); e = argtok(argc, argv, "-e", "e not found"); f = argtok(argc, argv, "-f", "f not found"); g = argtok(argc, argv, "-g", 0); printf("a=[%s] ", a); printf("b=[%s] ", b); printf("c=[%s] ", booltostr(c)); printf("d=[%s] ", d); printf("e=[%s] ", e); printf("f=[%s] ", f); printf("g=[%s] ", booltostr(g)); printf(" %d remaining args", argc); while (*argv) { printf(" [%s]", *argv); argv++; } { char work [256]; while (gets (work)) { printf ("\ninput is: [%s]\n", work ); printf ("output is: [%s]\n", bstran(work) ); } } } xzyyz echo done exit 0 exit 0