process.c revision b62cc08c
1/*
2 *
3Copyright 1989, 1998  The Open Group
4
5Permission to use, copy, modify, distribute, and sell this software and its
6documentation for any purpose is hereby granted without fee, provided that
7the above copyright notice appear in all copies and that both that
8copyright notice and this permission notice appear in supporting
9documentation.
10
11The above copyright notice and this permission notice shall be included in
12all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
17OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
21Except as contained in this notice, the name of The Open Group shall not be
22used in advertising or otherwise to promote the sale, use or other dealings
23in this Software without prior written authorization from The Open Group.
24 * *
25 * Original Author of "xauth" : Jim Fulton, MIT X Consortium
26 * Modified into "iceauth"    : Ralph Mor, X Consortium
27 */
28
29#include "iceauth.h"
30#include <ctype.h>
31#include <errno.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <signal.h>
35
36#define SECURERPC "SUN-DES-1"
37#define K5AUTH "KERBEROS-V5-1"
38
39#define ICEAUTH_DEFAULT_RETRIES 10	/* number of competitors we expect */
40#define ICEAUTH_DEFAULT_TIMEOUT 2	/* in seconds, be quick */
41#define ICEAUTH_DEFAULT_DEADTIME 600L	/* 10 minutes in seconds */
42
43typedef struct _AuthList {		/* linked list of entries */
44    struct _AuthList *next;
45    IceAuthFileEntry *auth;
46} AuthList;
47
48#define add_to_list(h,t,e) {if (t) (t)->next = (e); else (h) = (e); (t) = (e);}
49
50typedef int (*ProcessFunc)(const char *, int, int, char **);
51typedef int (*DoFunc)(const char *, int, IceAuthFileEntry *, char *);
52
53typedef struct _CommandTable {		/* commands that are understood */
54    char *name;				/* full name */
55    int minlen;				/* unique prefix */
56    int maxlen;				/* strlen(name) */
57    ProcessFunc processfunc;		/* handler */
58    char *helptext;			/* what to print for help */
59} CommandTable;
60
61struct _extract_data {			/* for iterating */
62    FILE *fp;				/* input source */
63    char *filename;			/* name of input */
64    Bool used_stdout;			/* whether or not need to close */
65    int nwritten;			/* number of entries written */
66    char *cmd;				/* for error messages */
67};
68
69struct _list_data {			/* for iterating */
70    FILE *fp;				/* output file */
71};
72
73
74/*
75 * private data
76 */
77static char *stdin_filename = "(stdin)";  /* for messages */
78static char *stdout_filename = "(stdout)";  /* for messages */
79static const char *Yes = "yes";		/* for messages */
80static const char *No = "no";			/* for messages */
81
82static int binaryEqual ( const char *a, const char *b, unsigned len );
83static void prefix ( const char *fn, int n );
84static void badcommandline ( const char *cmd );
85static char *skip_space ( char *s );
86static char *skip_nonspace ( char *s );
87static char **split_into_words ( char *src, int *argcp );
88static FILE *open_file ( char **filenamep, const char *mode, Bool *usedstdp, const char *srcfn, int srcln, const char *cmd );
89static int read_auth_entries ( FILE *fp, AuthList **headp, AuthList **tailp );
90static int cvthexkey ( char *hexstr, char **ptrp );
91static int dispatch_command ( const char *inputfilename, int lineno, int argc, char **argv, const CommandTable *tab, int *statusp );
92static void die ( int sig );
93static void catchsig ( int sig );
94static void register_signals ( void );
95static int write_auth_file ( char *tmp_nam, size_t tmp_nam_len );
96static void fprintfhex ( FILE *fp, unsigned int len, const char *cp );
97static int dump_entry ( const char *inputfilename, int lineno, IceAuthFileEntry *auth, char *data );
98static int extract_entry ( const char *inputfilename, int lineno, IceAuthFileEntry *auth, char *data );
99static int match_auth ( IceAuthFileEntry *a, IceAuthFileEntry *b, int *authDataSame );
100static int merge_entries ( AuthList **firstp, AuthList *second, int *nnewp, int *nreplp, int *ndupp );
101static int search_and_do ( const char *inputfilename, int lineno, int start, int argc, char *argv[], DoFunc do_func, char *data );
102static int remove_entry ( const char *inputfilename, int lineno, IceAuthFileEntry *auth, char *data );
103static int do_help ( const char *inputfilename, int lineno, int argc, char **argv );
104static int do_questionmark ( const char *inputfilename, int lineno, int argc, char **argv );
105static int do_list ( const char *inputfilename, int lineno, int argc, char **argv );
106static int do_merge ( const char *inputfilename, int lineno, int argc, char **argv );
107static int do_extract ( const char *inputfilename, int lineno, int argc, char **argv );
108static int do_add ( const char *inputfilename, int lineno, int argc, char **argv );
109static int do_remove ( const char *inputfilename, int lineno, int argc, char **argv );
110static int do_info ( const char *inputfilename, int lineno, int argc, char **argv );
111static int do_exit ( const char *inputfilename, int lineno, int argc, char **argv );
112static int do_quit ( const char *inputfilename, int lineno, int argc, char **argv );
113static int do_source ( const char *inputfilename, int lineno, int argc, char **argv );
114
115static const CommandTable command_table[] = {	/* table of known commands */
116{ "add", 2, 3, do_add,
117"\
118add       add an entry\n\
119          add protoname protodata netid authname authdata"
120},
121
122{ "exit", 3, 4, do_exit,
123"\
124exit      save changes and exit program"
125},
126
127{ "extract", 3, 7, do_extract,
128"\
129extract   extract entries into file\n\
130          extract filename <protoname=$> <protodata=$> <netid=$> <authname=$>"
131},
132
133{ "help", 1, 4, do_help,
134"\
135help      print help\n\
136          help <topic>"
137},
138
139{ "info", 1, 4, do_info,
140"\
141info      print information about entries"
142},
143
144{ "list", 1, 4, do_list,
145"\
146list      list entries\n\
147          list <protoname=$> <protodata=$> <netid=$> <authname=$>"
148},
149
150{ "merge", 1, 5, do_merge,
151"\
152merge     merge entries from files\n\
153          merge filename1 <filename2> <filename3> ..."
154},
155
156{ "quit", 1, 4, do_quit,
157"\
158quit      abort changes and exit program" },
159
160{ "remove", 1, 6, do_remove,
161"\
162remove    remove entries\n\
163          remove <protoname=$> <protodata=$> <netid=$> <authname=$>"
164},
165
166{ "source", 1, 6, do_source,
167"\
168source    read commands from file\n\
169          source filename"
170},
171
172{ "?", 1, 1, do_questionmark,
173"\
174?         list available commands" },
175
176{ NULL, 0, 0, NULL, NULL },
177};
178
179#define COMMAND_NAMES_PADDED_WIDTH 10	/* wider than anything above */
180
181
182static Bool okay_to_use_stdin = True;	/* set to false after using */
183
184static const char * const hex_table[] = {	/* for printing hex digits */
185    "00", "01", "02", "03", "04", "05", "06", "07",
186    "08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
187    "10", "11", "12", "13", "14", "15", "16", "17",
188    "18", "19", "1a", "1b", "1c", "1d", "1e", "1f",
189    "20", "21", "22", "23", "24", "25", "26", "27",
190    "28", "29", "2a", "2b", "2c", "2d", "2e", "2f",
191    "30", "31", "32", "33", "34", "35", "36", "37",
192    "38", "39", "3a", "3b", "3c", "3d", "3e", "3f",
193    "40", "41", "42", "43", "44", "45", "46", "47",
194    "48", "49", "4a", "4b", "4c", "4d", "4e", "4f",
195    "50", "51", "52", "53", "54", "55", "56", "57",
196    "58", "59", "5a", "5b", "5c", "5d", "5e", "5f",
197    "60", "61", "62", "63", "64", "65", "66", "67",
198    "68", "69", "6a", "6b", "6c", "6d", "6e", "6f",
199    "70", "71", "72", "73", "74", "75", "76", "77",
200    "78", "79", "7a", "7b", "7c", "7d", "7e", "7f",
201    "80", "81", "82", "83", "84", "85", "86", "87",
202    "88", "89", "8a", "8b", "8c", "8d", "8e", "8f",
203    "90", "91", "92", "93", "94", "95", "96", "97",
204    "98", "99", "9a", "9b", "9c", "9d", "9e", "9f",
205    "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
206    "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af",
207    "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7",
208    "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf",
209    "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7",
210    "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf",
211    "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
212    "d8", "d9", "da", "db", "dc", "dd", "de", "df",
213    "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7",
214    "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef",
215    "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
216    "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff",
217};
218
219static unsigned int hexvalues[256];	/* for parsing hex input */
220
221static mode_t original_umask = 0;	/* for restoring */
222
223
224/*
225 * private utility procedures
226 */
227
228#define copystring(s)	( s != NULL ? strdup(s) : NULL )
229
230static int
231binaryEqual (
232    register const char	*a,
233    register const char	*b,
234    register unsigned	len)
235
236{
237    while (len--)
238	if (*a++ != *b++)
239	    return 0;
240    return 1;
241}
242
243static void prefix (const char *fn, int n)
244{
245    fprintf (stderr, "%s: %s:%d:  ", ProgramName, fn, n);
246}
247
248static void badcommandline (const char *cmd)
249{
250    fprintf (stderr, "bad \"%s\" command line\n", cmd);
251}
252
253static char *skip_space (register char *s)
254{
255    if (!s) return NULL;
256
257    for ( ; *s && isascii(*s) && isspace(*s); s++)
258	;
259    return s;
260}
261
262
263static char *skip_nonspace (register char *s)
264{
265    if (!s) return NULL;
266
267    /* put quoting into loop if need be */
268    for ( ; *s && isascii(*s) && !isspace(*s); s++)
269	;
270    return s;
271}
272
273static char **split_into_words (  /* argvify string */
274    char *src,
275    int *argcp)
276{
277    char *jword;
278    char savec;
279    char **argv;
280    int cur, total;
281
282    *argcp = 0;
283#define WORDSTOALLOC 4			/* most lines are short */
284    argv = (char **) malloc (WORDSTOALLOC * sizeof (char *));
285    if (!argv) return NULL;
286    cur = 0;
287    total = WORDSTOALLOC;
288
289    /*
290     * split the line up into separate, nul-terminated tokens; the last
291     * "token" will point to the empty string so that it can be bashed into
292     * a null pointer.
293     */
294
295    do {
296	jword = skip_space (src);
297	src = skip_nonspace (jword);
298	savec = *src;
299	*src = '\0';
300	if (cur == total) {
301	    total += WORDSTOALLOC;
302	    argv = (char **) realloc (argv, total * sizeof (char *));
303	    if (!argv) return NULL;
304	}
305	argv[cur++] = jword;
306	if (savec) src++;		/* if not last on line advance */
307    } while (jword != src);
308
309    argv[--cur] = NULL;			/* smash empty token to end list */
310    *argcp = cur;
311    return argv;
312}
313
314
315static FILE *open_file (
316    char **filenamep,
317    const char *mode,
318    Bool *usedstdp,
319    const char *srcfn,
320    int srcln,
321    const char *cmd)
322{
323    FILE *fp;
324
325    if (strcmp (*filenamep, "-") == 0) {
326	*usedstdp = True;
327					/* select std descriptor to use */
328	if (mode[0] == 'r') {
329	    if (okay_to_use_stdin) {
330		okay_to_use_stdin = False;
331		*filenamep = stdin_filename;
332		return stdin;
333	    } else {
334		prefix (srcfn, srcln);
335		fprintf (stderr, "%s:  stdin already in use\n", cmd);
336		return NULL;
337	    }
338	} else {
339	    *filenamep = stdout_filename;
340	    return stdout;		/* always okay to use stdout */
341	}
342    }
343
344    fp = fopen (*filenamep, mode);
345    if (!fp) {
346	prefix (srcfn, srcln);
347	fprintf (stderr, "%s:  unable to open file %s\n", cmd, *filenamep);
348    }
349    return fp;
350}
351
352
353static int read_auth_entries (FILE *fp, AuthList **headp, AuthList **tailp)
354{
355    IceAuthFileEntry *auth;
356    AuthList *head, *tail;
357    int n;
358
359    head = tail = NULL;
360    n = 0;
361					/* put all records into linked list */
362    while ((auth = IceReadAuthFileEntry (fp)) != NULL) {
363	AuthList *l = (AuthList *) malloc (sizeof (AuthList));
364	if (!l) {
365	    fprintf (stderr,
366		     "%s:  unable to alloc entry reading auth file\n",
367		     ProgramName);
368	    exit (1);
369	}
370	l->next = NULL;
371	l->auth = auth;
372	if (tail) 			/* if not first time through append */
373	  tail->next = l;
374	else
375	  head = l;			/* first time through, so assign */
376	tail = l;
377	n++;
378    }
379    *headp = head;
380    *tailp = tail;
381    return n;
382}
383
384
385static int cvthexkey (	/* turn hex key string into octets */
386    char *hexstr,
387    char **ptrp)
388{
389    int i;
390    int len = 0;
391    char *retval, *s;
392    unsigned char *us;
393    char c;
394    char savec = '\0';
395
396    /* count */
397    for (s = hexstr; *s; s++) {
398	if (!isascii(*s)) return -1;
399	if (isspace(*s)) continue;
400	if (!isxdigit(*s)) return -1;
401	len++;
402    }
403
404    /* if odd then there was an error */
405    if ((len & 1) == 1) return -1;
406
407
408    /* now we know that the input is good */
409    len >>= 1;
410    retval = malloc (len);
411    if (!retval) {
412	fprintf (stderr, "%s:  unable to allocate %d bytes for hexkey\n",
413		 ProgramName, len);
414	return -1;
415    }
416
417    for (us = (unsigned char *) retval, i = len; i > 0; hexstr++) {
418	c = *hexstr;
419	if (isspace(c)) continue;	 /* already know it is ascii */
420	if (isupper(c))
421	    c = tolower(c);
422	if (savec) {
423#define atoh(c) ((c) - (((c) >= '0' && (c) <= '9') ? '0' : ('a'-10)))
424	    *us = (unsigned char)((atoh(savec) << 4) + atoh(c));
425#undef atoh
426	    savec = 0;		/* ready for next character */
427	    us++;
428	    i--;
429	} else {
430	    savec = c;
431	}
432    }
433    *ptrp = retval;
434    return len;
435}
436
437static int dispatch_command (
438    const char *inputfilename,
439    int lineno,
440    int argc,
441    char **argv,
442    const CommandTable *tab,
443    int *statusp)
444{
445    const CommandTable *ct;
446    char *cmd;
447    int n;
448					/* scan table for command */
449    cmd = argv[0];
450    n = strlen (cmd);
451    for (ct = tab; ct->name; ct++) {
452					/* look for unique prefix */
453	if (n >= ct->minlen && n <= ct->maxlen &&
454	    strncmp (cmd, ct->name, n) == 0) {
455	    *statusp = (*(ct->processfunc))(inputfilename, lineno, argc, argv);
456	    return 1;
457	}
458    }
459
460    *statusp = 1;
461    return 0;
462}
463
464
465static AuthList *iceauth_head = NULL;	/* list of auth entries */
466static Bool iceauth_existed = False;	/* if was present at initialize */
467static Bool iceauth_modified = False;	/* if added, removed, or merged */
468static Bool iceauth_allowed = True;	/* if allowed to write auth file */
469static char *iceauth_filename = NULL;
470static volatile Bool dieing = False;
471
472#ifdef RETSIGTYPE /* autoconf AC_TYPE_SIGNAL */
473# define _signal_t RETSIGTYPE
474#else /* Imake */
475#ifdef SIGNALRETURNSINT
476#define _signal_t int
477#else
478#define _signal_t void
479#endif
480#endif /* RETSIGTYPE */
481
482/* poor man's puts(), for under signal handlers */
483#define WRITES(fd, S) (void)write((fd), (S), strlen((S)))
484
485/* ARGSUSED */
486static _signal_t die (int sig)
487{
488    dieing = True;
489    _exit (auth_finalize ());
490    /* NOTREACHED */
491#ifdef SIGNALRETURNSINT
492    return -1;				/* for picky compilers */
493#endif
494}
495
496static _signal_t catchsig (int sig)
497{
498#ifdef SYSV
499    if (sig > 0) signal (sig, die);	/* re-establish signal handler */
500#endif
501    /*
502     * fileno() might not be reentrant, avoid it if possible, and use
503     * stderr instead of stdout
504     */
505#ifdef STDERR_FILENO
506    if (verbose && iceauth_modified) WRITES(STDERR_FILENO, "\r\n");
507#else
508    if (verbose && iceauth_modified) WRITES(fileno(stderr), "\r\n");
509#endif
510    die (sig);
511    /* NOTREACHED */
512#ifdef SIGNALRETURNSINT
513    return -1;				/* for picky compilers */
514#endif
515}
516
517static void register_signals (void)
518{
519    signal (SIGINT, catchsig);
520    signal (SIGTERM, catchsig);
521#ifdef SIGHUP
522    signal (SIGHUP, catchsig);
523#endif
524    return;
525}
526
527
528/*
529 * public procedures for parsing lines of input
530 */
531
532int auth_initialize ( char *authfilename )
533{
534    int n;
535    AuthList *head, *tail;
536    FILE *authfp;
537    Bool exists;
538
539    register_signals ();
540
541    bzero ((char *) hexvalues, sizeof hexvalues);
542    hexvalues['0'] = 0;
543    hexvalues['1'] = 1;
544    hexvalues['2'] = 2;
545    hexvalues['3'] = 3;
546    hexvalues['4'] = 4;
547    hexvalues['5'] = 5;
548    hexvalues['6'] = 6;
549    hexvalues['7'] = 7;
550    hexvalues['8'] = 8;
551    hexvalues['9'] = 9;
552    hexvalues['a'] = hexvalues['A'] = 0xa;
553    hexvalues['b'] = hexvalues['B'] = 0xb;
554    hexvalues['c'] = hexvalues['C'] = 0xc;
555    hexvalues['d'] = hexvalues['D'] = 0xd;
556    hexvalues['e'] = hexvalues['E'] = 0xe;
557    hexvalues['f'] = hexvalues['F'] = 0xf;
558
559    if (break_locks && verbose) {
560	printf ("Attempting to break locks on authority file %s\n",
561		authfilename);
562    }
563
564    iceauth_filename = strdup(authfilename);
565
566    if (ignore_locks) {
567	if (break_locks) IceUnlockAuthFile (authfilename);
568    } else {
569	n = IceLockAuthFile (authfilename, ICEAUTH_DEFAULT_RETRIES,
570			 ICEAUTH_DEFAULT_TIMEOUT,
571			 (break_locks ? 0L : ICEAUTH_DEFAULT_DEADTIME));
572	if (n != IceAuthLockSuccess) {
573	    char *reason = "unknown error";
574	    switch (n) {
575	      case IceAuthLockError:
576		reason = "error";
577		break;
578	      case IceAuthLockTimeout:
579		reason = "timeout";
580		break;
581	    }
582	    fprintf (stderr, "%s:  %s in locking authority file %s\n",
583		     ProgramName, reason, authfilename);
584	    return -1;
585	}
586    }
587
588    /* these checks can only be done reliably after the file is locked */
589    exists = (access (authfilename, F_OK) == 0);
590    if (exists && access (authfilename, W_OK) != 0) {
591	fprintf (stderr,
592	 "%s:  %s not writable, changes will be ignored\n",
593		 ProgramName, authfilename);
594	iceauth_allowed = False;
595    }
596
597    original_umask = umask (0077);	/* disallow non-owner access */
598
599    authfp = fopen (authfilename, "rb");
600    if (!authfp) {
601	int olderrno = errno;
602
603					/* if file there then error */
604	if (access (authfilename, F_OK) == 0) {	 /* then file does exist! */
605	    errno = olderrno;
606	    return -1;
607	}				/* else ignore it */
608	fprintf (stderr,
609		 "%s:  creating new authority file %s\n",
610		 ProgramName, authfilename);
611    } else {
612	iceauth_existed = True;
613	n = read_auth_entries (authfp, &head, &tail);
614	(void) fclose (authfp);
615	if (n < 0) {
616	    fprintf (stderr,
617		     "%s:  unable to read auth entries from file \"%s\"\n",
618		     ProgramName, authfilename);
619	    return -1;
620	}
621	iceauth_head = head;
622    }
623
624    iceauth_modified = False;
625
626    if (verbose) {
627	printf ("%s authority file %s\n",
628		ignore_locks ? "Ignoring locks on" : "Using", authfilename);
629    }
630    return 0;
631}
632
633static int write_auth_file (char *tmp_nam, size_t tmp_nam_len)
634{
635    FILE *fp;
636    AuthList *list;
637
638    if ((strlen(iceauth_filename) + 3) > tmp_nam_len) {
639	strncpy(tmp_nam, "filename too long", tmp_nam_len);
640	tmp_nam[tmp_nam_len - 1] = '\0';
641	return -1;
642    }
643
644    strcpy (tmp_nam, iceauth_filename);
645    strcat (tmp_nam, "-n");		/* for new */
646    (void) unlink (tmp_nam);
647    fp = fopen (tmp_nam, "wb");		/* umask is still set to 0077 */
648    if (!fp) {
649	fprintf (stderr, "%s:  unable to open tmp file \"%s\"\n",
650		 ProgramName, tmp_nam);
651	return -1;
652    }
653
654    for (list = iceauth_head; list; list = list->next)
655	IceWriteAuthFileEntry (fp, list->auth);
656
657    (void) fclose (fp);
658    return 0;
659}
660
661int auth_finalize (void)
662{
663    char temp_name[1024];			/* large filename size */
664
665    if (iceauth_modified) {
666	if (dieing) {
667	    if (verbose) {
668		/*
669		 * called from a signal handler -- printf is *not* reentrant; also
670		 * fileno() might not be reentrant, avoid it if possible, and use
671		 * stderr instead of stdout
672		 */
673#ifdef STDERR_FILENO
674		WRITES(STDERR_FILENO, "\nAborting changes to authority file ");
675		WRITES(STDERR_FILENO, iceauth_filename);
676		WRITES(STDERR_FILENO, "\n");
677#else
678		WRITES(fileno(stderr), "\nAborting changes to authority file ");
679		WRITES(fileno(stderr), iceauth_filename);
680		WRITES(fileno(stderr), "\n");
681#endif
682	    }
683	} else if (!iceauth_allowed) {
684	    fprintf (stderr,
685		     "%s:  %s not writable, changes ignored\n",
686		     ProgramName, iceauth_filename);
687	} else {
688	    if (verbose) {
689		printf ("%s authority file %s\n",
690			ignore_locks ? "Ignoring locks and writing" :
691			"Writing", iceauth_filename);
692	    }
693	    temp_name[0] = '\0';
694	    if (write_auth_file (temp_name, sizeof(temp_name)) == -1) {
695		fprintf (stderr,
696			 "%s:  unable to write authority file %s\n",
697			 ProgramName, temp_name);
698	    } else {
699		(void) unlink (iceauth_filename);
700#if defined(WIN32) || defined(__UNIXOS2__)
701		if (rename(temp_name, iceauth_filename) == -1)
702#else
703		if (link (temp_name, iceauth_filename) == -1)
704#endif
705		{
706		    fprintf (stderr,
707		     "%s:  unable to link authority file %s, use %s\n",
708			     ProgramName, iceauth_filename, temp_name);
709		} else {
710		    (void) unlink (temp_name);
711		}
712	    }
713	}
714    }
715
716    if (!ignore_locks && (iceauth_filename != NULL)) {
717	IceUnlockAuthFile (iceauth_filename);
718    }
719    (void) umask (original_umask);
720    return 0;
721}
722
723int process_command (
724    const char *inputfilename,
725    int lineno,
726    int argc,
727    char **argv)
728{
729    int status;
730
731    if (argc < 1 || !argv || !argv[0]) return 1;
732
733    if (dispatch_command (inputfilename, lineno, argc, argv,
734			  command_table, &status))
735      return status;
736
737    prefix (inputfilename, lineno);
738    fprintf (stderr, "unknown command \"%s\"\n", argv[0]);
739    return 1;
740}
741
742
743/*
744 * utility routines
745 */
746
747static void fprintfhex (
748    register FILE *fp,
749    unsigned int len,
750    const char *cp)
751{
752    const unsigned char *ucp = (const unsigned char *) cp;
753
754    for (; len > 0; len--, ucp++) {
755	register const char *s = hex_table[*ucp];
756	putc (s[0], fp);
757	putc (s[1], fp);
758    }
759    return;
760}
761
762/* ARGSUSED */
763static int dump_entry (
764    const char *inputfilename,
765    int lineno,
766    IceAuthFileEntry *auth,
767    char *data)
768{
769    struct _list_data *ld = (struct _list_data *) data;
770    FILE *fp = ld->fp;
771
772    fprintf (fp, "%s", auth->protocol_name);
773    putc (' ', fp);
774    if (auth->protocol_data_length > 0)
775	fprintfhex (fp, auth->protocol_data_length, auth->protocol_data);
776    else
777	fprintf (fp, "\"\"");
778    putc (' ', fp);
779    fprintf (fp, "%s", auth->network_id);
780    putc (' ', fp);
781    fprintf (fp, "%s", auth->auth_name);
782    putc (' ', fp);
783
784    if (auth->auth_data_length == 0)
785	fprintf (fp, "\"\"");
786    else if (!strcmp(auth->auth_name, SECURERPC) ||
787	!strcmp(auth->auth_name, K5AUTH))
788	fwrite (auth->auth_data, sizeof (char), auth->auth_data_length, fp);
789    else
790	fprintfhex (fp, auth->auth_data_length, auth->auth_data);
791    putc ('\n', fp);
792
793    return 0;
794}
795
796static int extract_entry (
797    const char *inputfilename,
798    int lineno,
799    IceAuthFileEntry *auth,
800    char *data)
801{
802    struct _extract_data *ed = (struct _extract_data *) data;
803
804    if (!ed->fp) {
805	ed->fp = open_file (&ed->filename, "wb",
806			    &ed->used_stdout,
807			    inputfilename, lineno, ed->cmd);
808	if (!ed->fp) {
809	    prefix (inputfilename, lineno);
810	    fprintf (stderr,
811		     "unable to open extraction file \"%s\"\n",
812		     ed->filename);
813	    return -1;
814	}
815    }
816    IceWriteAuthFileEntry (ed->fp, auth);
817    ed->nwritten++;
818
819    return 0;
820}
821
822
823static int match_auth (
824    register IceAuthFileEntry *a,
825    register IceAuthFileEntry *b,
826    int *authDataSame)
827{
828    int match = strcmp (a->protocol_name, b->protocol_name) == 0 &&
829	    strcmp (a->network_id, b->network_id) == 0 &&
830            strcmp (a->auth_name, b->auth_name) == 0;
831
832    if (match)
833    {
834	*authDataSame = (a->auth_data_length == b->auth_data_length &&
835	    binaryEqual (a->auth_data, b->auth_data, a->auth_data_length));
836    }
837    else
838	*authDataSame = 0;
839
840    return (match);
841}
842
843
844static int merge_entries (
845    AuthList **firstp, AuthList *second,
846    int *nnewp, int *nreplp, int *ndupp)
847{
848    AuthList *a, *b, *first, *tail;
849    int n = 0, nnew = 0, nrepl = 0, ndup = 0;
850
851    if (!second) return 0;
852
853    if (!*firstp) {			/* if nothing to merge into */
854	*firstp = second;
855	for (tail = *firstp, n = 1; tail->next; n++, tail = tail->next) ;
856	*nnewp = n;
857	*nreplp = 0;
858	*ndupp = 0;
859	return n;
860    }
861
862    first = *firstp;
863    /*
864     * find end of first list and stick second list on it
865     */
866    for (tail = first; tail->next; tail = tail->next) ;
867    tail->next = second;
868
869    /*
870     * run down list freeing duplicate entries; if an entry is okay, then
871     * bump the tail up to include it, otherwise, cut the entry out of
872     * the chain.
873     */
874    for (b = second; b; ) {
875	AuthList *next = b->next;	/* in case we free it */
876	int duplicate;
877
878	duplicate = 0;
879	a = first;
880	for (;;) {
881	    int authDataSame;
882	    if (match_auth (a->auth, b->auth, &authDataSame)) {
883		if (authDataSame)
884		{
885		    /* found a complete duplicate, ignore */
886		    duplicate = 1;
887		    break;
888		}
889		else
890		{
891		    /* found a duplicate, but auth data differs */
892
893		    AuthList tmp;		/* swap it in for old one */
894		    tmp = *a;
895		    *a = *b;
896		    *b = tmp;
897		    a->next = b->next;
898		    IceFreeAuthFileEntry (b->auth);
899		    free ((char *) b);
900		    b = NULL;
901		    tail->next = next;
902		    nrepl++;
903		    nnew--;
904		    break;
905		}
906	    }
907	    if (a == tail) break;	/* if have looked at left side */
908	    a = a->next;
909	}
910	if (!duplicate && b) {		/* if we didn't remove it */
911	    tail = b;			/* bump end of first list */
912	}
913	b = next;
914
915	if (duplicate)
916	    ndup++;
917	else
918	{
919	    n++;
920	    nnew++;
921	}
922    }
923
924    *nnewp = nnew;
925    *nreplp = nrepl;
926    *ndupp = ndup;
927    return n;
928
929}
930
931
932static int search_and_do (
933    const char *inputfilename,
934    int lineno,
935    int start,
936    int argc,
937    char *argv[],
938    DoFunc do_func,
939    char *data)
940{
941    int i;
942    int status = 0;
943    int errors = 0;
944    AuthList *l, *next;
945    char *protoname, *protodata, *netid, *authname;
946
947    for (l = iceauth_head; l; l = next)
948    {
949	next = l->next;
950
951	protoname = protodata = netid = authname = NULL;
952
953	for (i = start; i < argc; i++)
954	{
955	    if (!strncmp ("protoname=", argv[i], 10))
956		protoname = argv[i] + 10;
957	    else if (!strncmp ("protodata=", argv[i], 10))
958		protodata = argv[i] + 10;
959	    else if (!strncmp ("netid=", argv[i], 6))
960		netid = argv[i] + 6;
961	    else if (!strncmp ("authname=", argv[i], 9))
962		authname = argv[i] + 9;
963	}
964
965	status = 0;
966
967	if (protoname || protodata || netid || authname)
968	{
969	    if (protoname && strcmp (protoname, l->auth->protocol_name))
970		continue;
971
972	    if (protodata && !binaryEqual (protodata,
973		l->auth->protocol_data, l->auth->protocol_data_length))
974		continue;
975
976	    if (netid && strcmp (netid, l->auth->network_id))
977		continue;
978
979	    if (authname && strcmp (authname, l->auth->auth_name))
980		continue;
981
982	    status = (*do_func) (inputfilename, lineno, l->auth, data);
983
984	    if (status < 0)
985		break;
986	}
987    }
988
989    if (status < 0)
990	errors -= status;		/* since status is negative */
991
992    return (errors);
993}
994
995
996/* ARGSUSED */
997static int remove_entry (
998    const char *inputfilename,
999    int lineno,
1000    IceAuthFileEntry *auth,
1001    char *data)
1002{
1003    int *nremovedp = (int *) data;
1004    AuthList **listp = &iceauth_head;
1005    AuthList *list;
1006
1007    /*
1008     * unlink the auth we were asked to
1009     */
1010    while ((list = *listp)->auth != auth)
1011	listp = &list->next;
1012    *listp = list->next;
1013    IceFreeAuthFileEntry (list->auth);                    /* free the auth */
1014    free (list);				    /* free the link */
1015    iceauth_modified = True;
1016    (*nremovedp)++;
1017    return 1;
1018}
1019
1020/*
1021 * action routines
1022 */
1023
1024/*
1025 * help
1026 */
1027int print_help (
1028    FILE *fp,
1029    const char *cmd)
1030{
1031    const CommandTable *ct;
1032    int n = 0;
1033
1034    fprintf (fp, "\n");
1035    if (!cmd) {				/* if no cmd, print all help */
1036	for (ct = command_table; ct->name; ct++) {
1037	    fprintf (fp, "%s\n\n", ct->helptext);
1038	    n++;
1039	}
1040    } else {
1041	int len = strlen (cmd);
1042	for (ct = command_table; ct->name; ct++) {
1043	    if (strncmp (cmd, ct->name, len) == 0) {
1044		fprintf (fp, "%s\n\n", ct->helptext);
1045		n++;
1046	    }
1047	}
1048    }
1049
1050    return n;
1051}
1052
1053static int do_help (
1054    const char *inputfilename,
1055    int lineno,
1056    int argc,
1057    char **argv)
1058{
1059    char *cmd = (argc > 1 ? argv[1] : NULL);
1060    int n;
1061
1062    n = print_help (stdout, cmd);
1063
1064    if (n < 0 || (n == 0 && !cmd)) {
1065	prefix (inputfilename, lineno);
1066	fprintf (stderr, "internal error with help");
1067	if (cmd) {
1068	    fprintf (stderr, " on command \"%s\"", cmd);
1069	}
1070	fprintf (stderr, "\n");
1071	return 1;
1072    }
1073
1074    if (n == 0) {
1075	prefix (inputfilename, lineno);
1076	/* already know that cmd is set in this case */
1077	fprintf (stderr, "no help for noexistent command \"%s\"\n", cmd);
1078    }
1079
1080    return 0;
1081}
1082
1083/*
1084 * questionmark
1085 */
1086/* ARGSUSED */
1087static int do_questionmark (
1088    const char *inputfilename,
1089    int lineno,
1090    int argc,
1091    char **argv)
1092{
1093    const CommandTable *ct;
1094    int i;
1095#define WIDEST_COLUMN 72
1096    int col = WIDEST_COLUMN;
1097
1098    printf ("Commands:\n");
1099    for (ct = command_table; ct->name; ct++) {
1100	if ((col + ct->maxlen) > WIDEST_COLUMN) {
1101	    if (ct != command_table) {
1102		putc ('\n', stdout);
1103	    }
1104	    fputs ("        ", stdout);
1105	    col = 8;			/* length of string above */
1106	}
1107	fputs (ct->name, stdout);
1108	col += ct->maxlen;
1109	for (i = ct->maxlen; i < COMMAND_NAMES_PADDED_WIDTH; i++) {
1110	    putc (' ', stdout);
1111	    col++;
1112	}
1113    }
1114    if (col != 0) {
1115	putc ('\n', stdout);
1116    }
1117
1118    /* allow bad lines since this is help */
1119    return 0;
1120}
1121
1122/*
1123 * list [displayname ...]
1124 */
1125static int do_list (
1126    const char *inputfilename,
1127    int lineno,
1128    int argc,
1129    char **argv)
1130{
1131    struct _list_data ld;
1132
1133    ld.fp = stdout;
1134
1135    if (argc == 1) {
1136	register AuthList *l;
1137
1138	if (iceauth_head) {
1139	    for (l = iceauth_head; l; l = l->next) {
1140		dump_entry (inputfilename, lineno, l->auth, (char *) &ld);
1141	    }
1142	}
1143	return 0;
1144    }
1145    else
1146    {
1147	return (search_and_do (inputfilename, lineno, 1, argc, argv,
1148	    dump_entry, (char *) &ld));
1149    }
1150}
1151
1152/*
1153 * merge filename [filename ...]
1154 */
1155static int do_merge (
1156    const char *inputfilename,
1157    int lineno,
1158    int argc,
1159    char **argv)
1160{
1161    int i;
1162    int errors = 0;
1163    AuthList *head, *tail, *listhead, *listtail;
1164    int nentries, nnew, nrepl, ndup;
1165
1166    if (argc < 2) {
1167	prefix (inputfilename, lineno);
1168	badcommandline (argv[0]);
1169	return 1;
1170    }
1171
1172    listhead = listtail = NULL;
1173
1174    for (i = 1; i < argc; i++) {
1175	char *filename = argv[i];
1176	FILE *fp;
1177	Bool used_stdin = False;
1178
1179	fp = open_file (&filename, "rb",
1180			&used_stdin, inputfilename, lineno,
1181			argv[0]);
1182	if (!fp) {
1183	    errors++;
1184	    continue;
1185	}
1186
1187	head = tail = NULL;
1188	nentries = read_auth_entries (fp, &head, &tail);
1189	if (nentries == 0) {
1190	    prefix (inputfilename, lineno);
1191	    fprintf (stderr, "unable to read any entries from file \"%s\"\n",
1192		     filename);
1193	    errors++;
1194	} else {			/* link it in */
1195	    add_to_list (listhead, listtail, head);
1196 	}
1197
1198	if (!used_stdin) (void) fclose (fp);
1199    }
1200
1201    /*
1202     * if we have new entries, merge them in (freeing any duplicates)
1203     */
1204    if (listhead) {
1205	nentries = merge_entries (&iceauth_head, listhead,
1206	    &nnew, &nrepl, &ndup);
1207	if (verbose)
1208	  printf ("%d entries read in:  %d new, %d replacement%s\n",
1209	  	  nentries, nnew, nrepl, nrepl != 1 ? "s" : "");
1210	if (nentries > 0) iceauth_modified = True;
1211    }
1212
1213    return 0;
1214}
1215
1216/*
1217 * extract filename displayname [displayname ...]
1218 */
1219static int do_extract (
1220    const char *inputfilename,
1221    int lineno,
1222    int argc,
1223    char **argv)
1224{
1225    int errors;
1226    struct _extract_data ed;
1227
1228    if (argc < 3) {
1229	prefix (inputfilename, lineno);
1230	badcommandline (argv[0]);
1231	return 1;
1232    }
1233
1234    ed.fp = NULL;
1235    ed.filename = argv[1];
1236    ed.nwritten = 0;
1237    ed.cmd = argv[0];
1238
1239    errors = search_and_do (inputfilename, lineno, 2, argc, argv,
1240	extract_entry, (char *) &ed);
1241
1242    if (!ed.fp) {
1243	fprintf (stderr,
1244		 "No matches found, authority file \"%s\" not written\n",
1245		 ed.filename);
1246    } else {
1247	if (verbose) {
1248	    printf ("%d entries written to \"%s\"\n",
1249		    ed.nwritten, ed.filename);
1250	}
1251	if (!ed.used_stdout) {
1252	    (void) fclose (ed.fp);
1253	}
1254    }
1255
1256    return errors;
1257}
1258
1259
1260/*
1261 * add protoname protodata netid authname authdata
1262 */
1263static int do_add (
1264    const char *inputfilename,
1265    int lineno,
1266    int argc,
1267    char **argv)
1268{
1269    int n, nnew, nrepl, ndup;
1270    char *protoname;
1271    char *protodata_hex;
1272    char *protodata = NULL; /* not required */
1273    char *netid;
1274    char *authname;
1275    char *authdata_hex;
1276    char *authdata = NULL;
1277    int protodata_len, authdata_len;
1278    IceAuthFileEntry *auth = NULL;
1279    AuthList *list;
1280    int status = 0;
1281
1282    if (argc != 6 || !argv[1] || !argv[2] ||
1283	!argv[3] || !argv[4] || !argv[5])
1284    {
1285	prefix (inputfilename, lineno);
1286	badcommandline (argv[0]);
1287	return 1;
1288    }
1289
1290    protoname = argv[1];
1291    protodata_hex = argv[2];
1292    netid = argv[3];
1293    authname = argv[4];
1294    authdata_hex = argv[5];
1295
1296    protodata_len = strlen (protodata_hex);
1297    if (protodata_len > 0)
1298    {
1299	if (protodata_hex[0] == '"' && protodata_hex[protodata_len - 1] == '"')
1300	{
1301	    protodata = malloc (protodata_len - 1);
1302	    if (protodata)
1303	    {
1304		strncpy (protodata, protodata_hex + 1, protodata_len - 2);
1305		protodata_len -= 2;
1306	    }
1307	    else
1308		goto add_bad_malloc;
1309	}
1310	else
1311	{
1312	    protodata_len = cvthexkey (protodata_hex, &protodata);
1313	    if (protodata_len < 0)
1314	    {
1315		prefix (inputfilename, lineno);
1316		fprintf (stderr,
1317	       "protodata_hex contains odd number of or non-hex characters\n");
1318		return (1);
1319	    }
1320	}
1321    }
1322
1323    authdata_len = strlen (authdata_hex);
1324    if (authdata_hex[0] == '"' && authdata_hex[authdata_len - 1] == '"')
1325    {
1326	authdata = malloc (authdata_len - 1);
1327	if (authdata)
1328	{
1329	    strncpy (authdata, authdata_hex + 1, authdata_len - 2);
1330	    authdata_len -= 2;
1331	}
1332	else
1333	    goto add_bad_malloc;
1334    }
1335    else if (!strcmp (protoname, SECURERPC) || !strcmp (protoname, K5AUTH))
1336    {
1337	authdata = malloc (authdata_len + 1);
1338	if (authdata)
1339	    strcpy (authdata, authdata_hex);
1340	else
1341	    goto add_bad_malloc;
1342    }
1343    else
1344    {
1345	authdata_len = cvthexkey (authdata_hex, &authdata);
1346	if (authdata_len < 0)
1347	{
1348	    prefix (inputfilename, lineno);
1349	    fprintf (stderr,
1350	       "authdata_hex contains odd number of or non-hex characters\n");
1351	    free (protodata);
1352	    return (1);
1353	}
1354    }
1355
1356    auth = (IceAuthFileEntry *) malloc (sizeof (IceAuthFileEntry));
1357
1358    if (!auth)
1359	goto add_bad_malloc;
1360
1361    auth->protocol_name = copystring (protoname);
1362    auth->protocol_data_length = protodata_len;
1363    auth->protocol_data = protodata;
1364    auth->network_id = copystring (netid);
1365    auth->auth_name = copystring (authname);
1366    auth->auth_data_length = authdata_len;
1367    auth->auth_data = authdata;
1368
1369    if (!auth->protocol_name ||
1370	(!auth->protocol_data && auth->protocol_data_length > 0) ||
1371        !auth->network_id || !auth->auth_name ||
1372	(!auth->auth_data && auth->auth_data_length > 0))
1373    {
1374	goto add_bad_malloc;
1375    }
1376
1377    list = (AuthList *) malloc (sizeof (AuthList));
1378
1379    if (!list)
1380	goto add_bad_malloc;
1381
1382    list->next = NULL;
1383    list->auth = auth;
1384
1385    /*
1386     * merge it in; note that merge will deal with allocation
1387     */
1388
1389    n = merge_entries (&iceauth_head, list, &nnew, &nrepl, &ndup);
1390
1391    if (n > 0)
1392	iceauth_modified = True;
1393    else
1394    {
1395	prefix (inputfilename, lineno);
1396	if (ndup > 0)
1397	{
1398	    status = 0;
1399	    fprintf (stderr, "no records added - all duplicate\n");
1400	}
1401	else
1402	{
1403	    status = 1;
1404	    fprintf (stderr, "unable to merge in added record\n");
1405	}
1406	goto cant_add;
1407    }
1408
1409    return 0;
1410
1411
1412add_bad_malloc:
1413
1414    status = 1;
1415    prefix (inputfilename, lineno);
1416    fprintf (stderr, "unable to allocate memory to add an entry\n");
1417
1418cant_add:
1419
1420    if (protodata)
1421	free (protodata);
1422    if (authdata)
1423	free (authdata);
1424    if (auth)
1425    {
1426	if (auth->protocol_name)
1427	    free (auth->protocol_name);
1428	/* auth->protocol_data already freed,
1429	   since it's the same as protodata */
1430	if (auth->network_id)
1431	    free (auth->network_id);
1432	if (auth->auth_name)
1433	    free (auth->auth_name);
1434	/* auth->auth_data already freed,
1435	   since it's the same as authdata */
1436	free ((char *) auth);
1437    }
1438
1439    return status;
1440}
1441
1442/*
1443 * remove displayname
1444 */
1445static int do_remove (
1446    const char *inputfilename,
1447    int lineno,
1448    int argc,
1449    char **argv)
1450{
1451    int nremoved = 0;
1452    int errors;
1453
1454    if (argc < 2) {
1455	prefix (inputfilename, lineno);
1456	badcommandline (argv[0]);
1457	return 1;
1458    }
1459
1460    errors = search_and_do (inputfilename, lineno, 1, argc, argv,
1461	remove_entry, (char *) &nremoved);
1462    if (verbose) printf ("%d entries removed\n", nremoved);
1463    return errors;
1464}
1465
1466/*
1467 * info
1468 */
1469static int do_info (
1470    const char *inputfilename,
1471    int lineno,
1472    int argc,
1473    char **argv)
1474{
1475    int n;
1476    AuthList *l;
1477
1478    if (argc != 1) {
1479	prefix (inputfilename, lineno);
1480	badcommandline (argv[0]);
1481	return 1;
1482    }
1483
1484    for (l = iceauth_head, n = 0; l; l = l->next, n++) ;
1485
1486    printf ("Authority file:       %s\n",
1487	    iceauth_filename ? iceauth_filename : "(none)");
1488    printf ("File new:             %s\n", iceauth_existed ? No : Yes);
1489    printf ("File locked:          %s\n", ignore_locks ? No : Yes);
1490    printf ("Number of entries:    %d\n", n);
1491    printf ("Changes honored:      %s\n", iceauth_allowed ? Yes : No);
1492    printf ("Changes made:         %s\n", iceauth_modified ? Yes : No);
1493    printf ("Current input:        %s:%d\n", inputfilename, lineno);
1494    return 0;
1495}
1496
1497
1498/*
1499 * exit
1500 */
1501static Bool alldone = False;
1502
1503/* ARGSUSED */
1504static int do_exit (
1505    const char *inputfilename,
1506    int lineno,
1507    int argc,
1508    char **argv)
1509{
1510    /* allow bogus stuff */
1511    alldone = True;
1512    return 0;
1513}
1514
1515/*
1516 * quit
1517 */
1518/* ARGSUSED */
1519static int do_quit (
1520    const char *inputfilename,
1521    int lineno,
1522    int argc,
1523    char **argv)
1524{
1525    /* allow bogus stuff */
1526    die (0);
1527    /* NOTREACHED */
1528    return -1;				/* for picky compilers */
1529}
1530
1531
1532/*
1533 * source filename
1534 */
1535static int do_source (
1536    const char *inputfilename,
1537    int lineno,
1538    int argc,
1539    char **argv)
1540{
1541    char *script;
1542    char buf[BUFSIZ];
1543    FILE *fp;
1544    Bool used_stdin = False;
1545    int len;
1546    int errors = 0, status;
1547    int sublineno = 0;
1548    char **subargv;
1549    int subargc;
1550    Bool prompt = False;		/* only true if reading from tty */
1551
1552    if (argc != 2 || !argv[1]) {
1553	prefix (inputfilename, lineno);
1554	badcommandline (argv[0]);
1555	return 1;
1556    }
1557
1558    script = argv[1];
1559
1560    fp = open_file (&script, "r", &used_stdin, inputfilename, lineno, argv[0]);
1561    if (!fp) {
1562	return 1;
1563    }
1564
1565    if (verbose && used_stdin && isatty (fileno (fp))) prompt = True;
1566
1567    while (!alldone) {
1568	buf[0] = '\0';
1569	if (prompt) {
1570	    printf ("iceauth> ");
1571	    fflush (stdout);
1572	}
1573	if (fgets (buf, sizeof buf, fp) == NULL) break;
1574	sublineno++;
1575	len = strlen (buf);
1576	if (len == 0 || buf[0] == '#') continue;
1577	if (buf[len-1] != '\n') {
1578	    prefix (script, sublineno);
1579	    fprintf (stderr, "line too long\n");
1580	    errors++;
1581	    break;
1582	}
1583	buf[--len] = '\0';		/* remove new line */
1584	subargv = split_into_words (buf, &subargc);
1585	if (subargv) {
1586	    status = process_command (script, sublineno, subargc, subargv);
1587	    free ((char *) subargv);
1588	    errors += status;
1589	} else {
1590	    prefix (script, sublineno);
1591	    fprintf (stderr, "unable to break line into words\n");
1592	    errors++;
1593	}
1594    }
1595
1596    if (!used_stdin) {
1597	(void) fclose (fp);
1598    }
1599    return errors;
1600}
1601