process.c revision 9d794632
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 *, void *);
52
53typedef struct _CommandTable {		/* commands that are understood */
54    const char *name;			/* full name */
55    unsigned int minlen;		/* unique prefix */
56    unsigned int maxlen;		/* strlen(name) */
57    ProcessFunc processfunc;		/* handler */
58    const char *helptext;		/* what to print for help */
59} CommandTable;
60
61struct _extract_data {			/* for iterating */
62    FILE *fp;				/* input source */
63    const char *filename;		/* name of input */
64    Bool used_stdout;			/* whether or not need to close */
65    int nwritten;			/* number of entries written */
66    const 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 const char *stdin_filename = "(stdin)";  /* for messages */
78static const 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 ( const 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 ( const 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 ) _X_NORETURN;
93static void catchsig ( int sig ) _X_NORETURN;
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, void *data );
98static int extract_entry ( const char *inputfilename, int lineno, IceAuthFileEntry *auth, void *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, void *data );
102static int remove_entry ( const char *inputfilename, int lineno, IceAuthFileEntry *auth, void *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    const 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    const char *hexstr,
387    char **ptrp)
388{
389    unsigned int i;
390    unsigned int len = 0;
391    char *retval;
392    const char *s;
393    unsigned char *us;
394    char c;
395    char savec = '\0';
396
397    /* count */
398    for (s = hexstr; *s; s++) {
399	if (!isascii(*s)) return -1;
400	if (isspace(*s)) continue;
401	if (!isxdigit(*s)) return -1;
402	len++;
403    }
404
405    /* if 0 or odd, then there was an error */
406    if (len == 0 || (len & 1) == 1) return -1;
407
408
409    /* now we know that the input is good */
410    len >>= 1;
411    retval = malloc (len);
412    if (!retval) {
413	fprintf (stderr, "%s:  unable to allocate %d bytes for hexkey\n",
414		 ProgramName, len);
415	return -1;
416    }
417
418    for (us = (unsigned char *) retval, i = len; i > 0; hexstr++) {
419	c = *hexstr;
420	if (isspace(c)) continue;	 /* already know it is ascii */
421	if (isupper(c))
422	    c = tolower(c);
423	if (savec) {
424#define atoh(c) ((c) - (((c) >= '0' && (c) <= '9') ? '0' : ('a'-10)))
425	    *us = (unsigned char)((atoh(savec) << 4) + atoh(c));
426#undef atoh
427	    savec = 0;		/* ready for next character */
428	    us++;
429	    i--;
430	} else {
431	    savec = c;
432	}
433    }
434    *ptrp = retval;
435    return (int) len;
436}
437
438static int dispatch_command (
439    const char *inputfilename,
440    int lineno,
441    int argc,
442    char **argv,
443    const CommandTable *tab,
444    int *statusp)
445{
446    const CommandTable *ct;
447    const char *cmd;
448    size_t n;
449					/* scan table for command */
450    cmd = argv[0];
451    n = strlen (cmd);
452    for (ct = tab; ct->name; ct++) {
453					/* look for unique prefix */
454	if (n >= ct->minlen && n <= ct->maxlen &&
455	    strncmp (cmd, ct->name, n) == 0) {
456	    *statusp = (*(ct->processfunc))(inputfilename, lineno, argc, argv);
457	    return 1;
458	}
459    }
460
461    *statusp = 1;
462    return 0;
463}
464
465
466static AuthList *iceauth_head = NULL;	/* list of auth entries */
467static Bool iceauth_existed = False;	/* if was present at initialize */
468static Bool iceauth_modified = False;	/* if added, removed, or merged */
469static Bool iceauth_allowed = True;	/* if allowed to write auth file */
470static char *iceauth_filename = NULL;
471static volatile Bool dieing = False;
472
473/* poor man's puts(), for under signal handlers */
474#define WRITES(fd, S) (void)write((fd), (S), strlen((S)))
475
476/* ARGSUSED */
477static void die (_X_UNUSED int sig)
478{
479    dieing = True;
480    _exit (auth_finalize ());
481    /* NOTREACHED */
482}
483
484static void catchsig (int sig)
485{
486#ifdef SYSV
487    if (sig > 0) signal (sig, die);	/* re-establish signal handler */
488#endif
489    /*
490     * fileno() might not be reentrant, avoid it if possible, and use
491     * stderr instead of stdout
492     */
493#ifdef STDERR_FILENO
494    if (verbose && iceauth_modified) WRITES(STDERR_FILENO, "\r\n");
495#else
496    if (verbose && iceauth_modified) WRITES(fileno(stderr), "\r\n");
497#endif
498    die (sig);
499    /* NOTREACHED */
500}
501
502static void register_signals (void)
503{
504    signal (SIGINT, catchsig);
505    signal (SIGTERM, catchsig);
506#ifdef SIGHUP
507    signal (SIGHUP, catchsig);
508#endif
509    return;
510}
511
512
513/*
514 * public procedures for parsing lines of input
515 */
516
517int auth_initialize ( char *authfilename )
518{
519    int n;
520    AuthList *head, *tail;
521    FILE *authfp;
522    Bool exists;
523
524    register_signals ();
525
526    bzero ((char *) hexvalues, sizeof hexvalues);
527    hexvalues['0'] = 0;
528    hexvalues['1'] = 1;
529    hexvalues['2'] = 2;
530    hexvalues['3'] = 3;
531    hexvalues['4'] = 4;
532    hexvalues['5'] = 5;
533    hexvalues['6'] = 6;
534    hexvalues['7'] = 7;
535    hexvalues['8'] = 8;
536    hexvalues['9'] = 9;
537    hexvalues['a'] = hexvalues['A'] = 0xa;
538    hexvalues['b'] = hexvalues['B'] = 0xb;
539    hexvalues['c'] = hexvalues['C'] = 0xc;
540    hexvalues['d'] = hexvalues['D'] = 0xd;
541    hexvalues['e'] = hexvalues['E'] = 0xe;
542    hexvalues['f'] = hexvalues['F'] = 0xf;
543
544    if (break_locks && verbose) {
545	printf ("Attempting to break locks on authority file %s\n",
546		authfilename);
547    }
548
549    iceauth_filename = strdup(authfilename);
550
551    if (ignore_locks) {
552	if (break_locks) IceUnlockAuthFile (authfilename);
553    } else {
554	n = IceLockAuthFile (authfilename, ICEAUTH_DEFAULT_RETRIES,
555			 ICEAUTH_DEFAULT_TIMEOUT,
556			 (break_locks ? 0L : ICEAUTH_DEFAULT_DEADTIME));
557	if (n != IceAuthLockSuccess) {
558	    const char *reason = "unknown error";
559	    switch (n) {
560	      case IceAuthLockError:
561		reason = "error";
562		break;
563	      case IceAuthLockTimeout:
564		reason = "timeout";
565		break;
566	    }
567	    fprintf (stderr, "%s:  %s in locking authority file %s\n",
568		     ProgramName, reason, authfilename);
569	    return -1;
570	}
571    }
572
573    /* these checks can only be done reliably after the file is locked */
574    exists = (access (authfilename, F_OK) == 0);
575    if (exists && access (authfilename, W_OK) != 0) {
576	fprintf (stderr,
577	 "%s:  %s not writable, changes will be ignored\n",
578		 ProgramName, authfilename);
579	iceauth_allowed = False;
580    }
581
582    original_umask = umask (0077);	/* disallow non-owner access */
583
584    authfp = fopen (authfilename, "rb");
585    if (!authfp) {
586	int olderrno = errno;
587
588					/* if file there then error */
589	if (access (authfilename, F_OK) == 0) {	 /* then file does exist! */
590	    errno = olderrno;
591	    return -1;
592	}				/* else ignore it */
593	fprintf (stderr,
594		 "%s:  creating new authority file %s\n",
595		 ProgramName, authfilename);
596    } else {
597	iceauth_existed = True;
598	n = read_auth_entries (authfp, &head, &tail);
599	(void) fclose (authfp);
600	if (n < 0) {
601	    fprintf (stderr,
602		     "%s:  unable to read auth entries from file \"%s\"\n",
603		     ProgramName, authfilename);
604	    return -1;
605	}
606	iceauth_head = head;
607    }
608
609    iceauth_modified = False;
610
611    if (verbose) {
612	printf ("%s authority file %s\n",
613		ignore_locks ? "Ignoring locks on" : "Using", authfilename);
614    }
615    return 0;
616}
617
618static int write_auth_file (char *tmp_nam, size_t tmp_nam_len)
619{
620    FILE *fp;
621    AuthList *list;
622
623    if ((strlen(iceauth_filename) + 3) > tmp_nam_len) {
624	strncpy(tmp_nam, "filename too long", tmp_nam_len);
625	tmp_nam[tmp_nam_len - 1] = '\0';
626	return -1;
627    }
628
629    strcpy (tmp_nam, iceauth_filename);
630    strcat (tmp_nam, "-n");		/* for new */
631    (void) unlink (tmp_nam);
632    fp = fopen (tmp_nam, "wb");		/* umask is still set to 0077 */
633    if (!fp) {
634	fprintf (stderr, "%s:  unable to open tmp file \"%s\"\n",
635		 ProgramName, tmp_nam);
636	return -1;
637    }
638
639    for (list = iceauth_head; list; list = list->next)
640	IceWriteAuthFileEntry (fp, list->auth);
641
642    (void) fclose (fp);
643    return 0;
644}
645
646int auth_finalize (void)
647{
648    char temp_name[1024];			/* large filename size */
649
650    if (iceauth_modified) {
651	if (dieing) {
652	    if (verbose) {
653		/*
654		 * called from a signal handler -- printf is *not* reentrant; also
655		 * fileno() might not be reentrant, avoid it if possible, and use
656		 * stderr instead of stdout
657		 */
658#ifdef STDERR_FILENO
659		WRITES(STDERR_FILENO, "\nAborting changes to authority file ");
660		WRITES(STDERR_FILENO, iceauth_filename);
661		WRITES(STDERR_FILENO, "\n");
662#else
663		WRITES(fileno(stderr), "\nAborting changes to authority file ");
664		WRITES(fileno(stderr), iceauth_filename);
665		WRITES(fileno(stderr), "\n");
666#endif
667	    }
668	} else if (!iceauth_allowed) {
669	    fprintf (stderr,
670		     "%s:  %s not writable, changes ignored\n",
671		     ProgramName, iceauth_filename);
672	} else {
673	    if (verbose) {
674		printf ("%s authority file %s\n",
675			ignore_locks ? "Ignoring locks and writing" :
676			"Writing", iceauth_filename);
677	    }
678	    temp_name[0] = '\0';
679	    if (write_auth_file (temp_name, sizeof(temp_name)) == -1) {
680		fprintf (stderr,
681			 "%s:  unable to write authority file %s\n",
682			 ProgramName, temp_name);
683	    } else {
684		(void) unlink (iceauth_filename);
685#if defined(WIN32) || defined(__UNIXOS2__)
686		if (rename(temp_name, iceauth_filename) == -1)
687#else
688		/* Attempt to rename() if link() fails, since this may be on a FS that does not support hard links */
689		if (link (temp_name, iceauth_filename) == -1 && rename(temp_name, iceauth_filename) == -1)
690#endif
691		{
692		    fprintf (stderr,
693		     "%s:  unable to link authority file %s, use %s\n",
694			     ProgramName, iceauth_filename, temp_name);
695		} else {
696		    (void) unlink (temp_name);
697		}
698	    }
699	}
700    }
701
702    if (!ignore_locks && (iceauth_filename != NULL)) {
703	IceUnlockAuthFile (iceauth_filename);
704    }
705    (void) umask (original_umask);
706    return 0;
707}
708
709int process_command (
710    const char *inputfilename,
711    int lineno,
712    int argc,
713    char **argv)
714{
715    int status;
716
717    if (argc < 1 || !argv || !argv[0]) return 1;
718
719    if (dispatch_command (inputfilename, lineno, argc, argv,
720			  command_table, &status))
721      return status;
722
723    prefix (inputfilename, lineno);
724    fprintf (stderr, "unknown command \"%s\"\n", argv[0]);
725    return 1;
726}
727
728
729/*
730 * utility routines
731 */
732
733static void fprintfhex (
734    register FILE *fp,
735    unsigned int len,
736    const char *cp)
737{
738    const unsigned char *ucp = (const unsigned char *) cp;
739
740    for (; len > 0; len--, ucp++) {
741	register const char *s = hex_table[*ucp];
742	putc (s[0], fp);
743	putc (s[1], fp);
744    }
745    return;
746}
747
748/* ARGSUSED */
749static int dump_entry (
750    const char *inputfilename _X_UNUSED,
751    int lineno _X_UNUSED,
752    IceAuthFileEntry *auth,
753    void *data)
754{
755    struct _list_data *ld = (struct _list_data *) data;
756    FILE *fp = ld->fp;
757
758    fprintf (fp, "%s", auth->protocol_name);
759    putc (' ', fp);
760    if (auth->protocol_data_length > 0)
761	fprintfhex (fp, auth->protocol_data_length, auth->protocol_data);
762    else
763	fprintf (fp, "\"\"");
764    putc (' ', fp);
765    fprintf (fp, "%s", auth->network_id);
766    putc (' ', fp);
767    fprintf (fp, "%s", auth->auth_name);
768    putc (' ', fp);
769
770    if (auth->auth_data_length == 0)
771	fprintf (fp, "\"\"");
772    else if (!strcmp(auth->auth_name, SECURERPC) ||
773	!strcmp(auth->auth_name, K5AUTH))
774	fwrite (auth->auth_data, sizeof (char), auth->auth_data_length, fp);
775    else
776	fprintfhex (fp, auth->auth_data_length, auth->auth_data);
777    putc ('\n', fp);
778
779    return 0;
780}
781
782static int extract_entry (
783    const char *inputfilename,
784    int lineno,
785    IceAuthFileEntry *auth,
786    void *data)
787{
788    struct _extract_data *ed = (struct _extract_data *) data;
789
790    if (!ed->fp) {
791	ed->fp = open_file (&ed->filename, "wb",
792			    &ed->used_stdout,
793			    inputfilename, lineno, ed->cmd);
794	if (!ed->fp) {
795	    prefix (inputfilename, lineno);
796	    fprintf (stderr,
797		     "unable to open extraction file \"%s\"\n",
798		     ed->filename);
799	    return -1;
800	}
801    }
802    IceWriteAuthFileEntry (ed->fp, auth);
803    ed->nwritten++;
804
805    return 0;
806}
807
808
809static int match_auth (
810    register IceAuthFileEntry *a,
811    register IceAuthFileEntry *b,
812    int *authDataSame)
813{
814    int match = strcmp (a->protocol_name, b->protocol_name) == 0 &&
815	    strcmp (a->network_id, b->network_id) == 0 &&
816            strcmp (a->auth_name, b->auth_name) == 0;
817
818    if (match)
819    {
820	*authDataSame = (a->auth_data_length == b->auth_data_length &&
821	    binaryEqual (a->auth_data, b->auth_data, a->auth_data_length));
822    }
823    else
824	*authDataSame = 0;
825
826    return (match);
827}
828
829
830static int merge_entries (
831    AuthList **firstp, AuthList *second,
832    int *nnewp, int *nreplp, int *ndupp)
833{
834    AuthList *a, *b, *first, *tail;
835    int n = 0, nnew = 0, nrepl = 0, ndup = 0;
836
837    if (!second) return 0;
838
839    if (!*firstp) {			/* if nothing to merge into */
840	*firstp = second;
841	for (tail = *firstp, n = 1; tail->next; n++, tail = tail->next) ;
842	*nnewp = n;
843	*nreplp = 0;
844	*ndupp = 0;
845	return n;
846    }
847
848    first = *firstp;
849    /*
850     * find end of first list and stick second list on it
851     */
852    for (tail = first; tail->next; tail = tail->next) ;
853    tail->next = second;
854
855    /*
856     * run down list freeing duplicate entries; if an entry is okay, then
857     * bump the tail up to include it, otherwise, cut the entry out of
858     * the chain.
859     */
860    for (b = second; b; ) {
861	AuthList *next = b->next;	/* in case we free it */
862	int duplicate;
863
864	duplicate = 0;
865	a = first;
866	for (;;) {
867	    int authDataSame;
868	    if (match_auth (a->auth, b->auth, &authDataSame)) {
869		if (authDataSame)
870		{
871		    /* found a complete duplicate, ignore */
872		    duplicate = 1;
873		    break;
874		}
875		else
876		{
877		    /* found a duplicate, but auth data differs */
878
879		    AuthList tmp;		/* swap it in for old one */
880		    tmp = *a;
881		    *a = *b;
882		    *b = tmp;
883		    a->next = b->next;
884		    IceFreeAuthFileEntry (b->auth);
885		    free ((char *) b);
886		    b = NULL;
887		    tail->next = next;
888		    nrepl++;
889		    nnew--;
890		    break;
891		}
892	    }
893	    if (a == tail) break;	/* if have looked at left side */
894	    a = a->next;
895	}
896	if (!duplicate && b) {		/* if we didn't remove it */
897	    tail = b;			/* bump end of first list */
898	}
899	b = next;
900
901	if (duplicate)
902	    ndup++;
903	else
904	{
905	    n++;
906	    nnew++;
907	}
908    }
909
910    *nnewp = nnew;
911    *nreplp = nrepl;
912    *ndupp = ndup;
913    return n;
914
915}
916
917
918static int search_and_do (
919    const char *inputfilename,
920    int lineno,
921    int start,
922    int argc,
923    char *argv[],
924    DoFunc do_func,
925    void *data)
926{
927    int i;
928    int status = 0;
929    int errors = 0;
930    AuthList *l, *next;
931    char *protoname, *protodata, *netid, *authname;
932
933    for (l = iceauth_head; l; l = next)
934    {
935	next = l->next;
936
937	protoname = protodata = netid = authname = NULL;
938
939	for (i = start; i < argc; i++)
940	{
941	    if (!strncmp ("protoname=", argv[i], 10))
942		protoname = argv[i] + 10;
943	    else if (!strncmp ("protodata=", argv[i], 10))
944		protodata = argv[i] + 10;
945	    else if (!strncmp ("netid=", argv[i], 6))
946		netid = argv[i] + 6;
947	    else if (!strncmp ("authname=", argv[i], 9))
948		authname = argv[i] + 9;
949	}
950
951	status = 0;
952
953	if (protoname || protodata || netid || authname)
954	{
955	    if (protoname && strcmp (protoname, l->auth->protocol_name))
956		continue;
957
958	    if (protodata && !binaryEqual (protodata,
959		l->auth->protocol_data, l->auth->protocol_data_length))
960		continue;
961
962	    if (netid && strcmp (netid, l->auth->network_id))
963		continue;
964
965	    if (authname && strcmp (authname, l->auth->auth_name))
966		continue;
967
968	    status = (*do_func) (inputfilename, lineno, l->auth, data);
969
970	    if (status < 0)
971		break;
972	}
973    }
974
975    if (status < 0)
976	errors -= status;		/* since status is negative */
977
978    return (errors);
979}
980
981
982/* ARGSUSED */
983static int remove_entry (
984    const char *inputfilename _X_UNUSED,
985    int lineno _X_UNUSED,
986    IceAuthFileEntry *auth,
987    void *data)
988{
989    int *nremovedp = (int *) data;
990    AuthList **listp = &iceauth_head;
991    AuthList *list;
992
993    /*
994     * unlink the auth we were asked to
995     */
996    while ((list = *listp)->auth != auth)
997	listp = &list->next;
998    *listp = list->next;
999    IceFreeAuthFileEntry (list->auth);                    /* free the auth */
1000    free (list);				    /* free the link */
1001    iceauth_modified = True;
1002    (*nremovedp)++;
1003    return 1;
1004}
1005
1006/*
1007 * action routines
1008 */
1009
1010/*
1011 * help
1012 */
1013int print_help (
1014    FILE *fp,
1015    const char *cmd)
1016{
1017    const CommandTable *ct;
1018    int n = 0;
1019
1020    fprintf (fp, "\n");
1021    if (!cmd) {				/* if no cmd, print all help */
1022	for (ct = command_table; ct->name; ct++) {
1023	    fprintf (fp, "%s\n\n", ct->helptext);
1024	    n++;
1025	}
1026    } else {
1027	size_t len = strlen (cmd);
1028	for (ct = command_table; ct->name; ct++) {
1029	    if (strncmp (cmd, ct->name, len) == 0) {
1030		fprintf (fp, "%s\n\n", ct->helptext);
1031		n++;
1032	    }
1033	}
1034    }
1035
1036    return n;
1037}
1038
1039static int do_help (
1040    const char *inputfilename,
1041    int lineno,
1042    int argc,
1043    char **argv)
1044{
1045    char *cmd = (argc > 1 ? argv[1] : NULL);
1046    int n;
1047
1048    n = print_help (stdout, cmd);
1049
1050    if (n < 0 || (n == 0 && !cmd)) {
1051	prefix (inputfilename, lineno);
1052	fprintf (stderr, "internal error with help");
1053	if (cmd) {
1054	    fprintf (stderr, " on command \"%s\"", cmd);
1055	}
1056	fprintf (stderr, "\n");
1057	return 1;
1058    }
1059
1060    if (n == 0) {
1061	prefix (inputfilename, lineno);
1062	/* already know that cmd is set in this case */
1063	fprintf (stderr, "no help for noexistent command \"%s\"\n", cmd);
1064    }
1065
1066    return 0;
1067}
1068
1069/*
1070 * questionmark
1071 */
1072/* ARGSUSED */
1073static int do_questionmark (
1074    const char *inputfilename _X_UNUSED,
1075    int lineno _X_UNUSED,
1076    int argc _X_UNUSED,
1077    char **argv _X_UNUSED)
1078{
1079    const CommandTable *ct;
1080    unsigned int i;
1081#define WIDEST_COLUMN 72
1082    unsigned int col = WIDEST_COLUMN;
1083
1084    printf ("Commands:\n");
1085    for (ct = command_table; ct->name; ct++) {
1086	if ((col + ct->maxlen) > WIDEST_COLUMN) {
1087	    if (ct != command_table) {
1088		putc ('\n', stdout);
1089	    }
1090	    fputs ("        ", stdout);
1091	    col = 8;			/* length of string above */
1092	}
1093	fputs (ct->name, stdout);
1094	col += ct->maxlen;
1095	for (i = ct->maxlen; i < COMMAND_NAMES_PADDED_WIDTH; i++) {
1096	    putc (' ', stdout);
1097	    col++;
1098	}
1099    }
1100    if (col != 0) {
1101	putc ('\n', stdout);
1102    }
1103
1104    /* allow bad lines since this is help */
1105    return 0;
1106}
1107
1108/*
1109 * list [displayname ...]
1110 */
1111static int do_list (
1112    const char *inputfilename,
1113    int lineno,
1114    int argc,
1115    char **argv)
1116{
1117    struct _list_data ld;
1118
1119    ld.fp = stdout;
1120
1121    if (argc == 1) {
1122	register AuthList *l;
1123
1124	if (iceauth_head) {
1125	    for (l = iceauth_head; l; l = l->next) {
1126		dump_entry (inputfilename, lineno, l->auth, &ld);
1127	    }
1128	}
1129	return 0;
1130    }
1131    else
1132    {
1133	return (search_and_do (inputfilename, lineno, 1, argc, argv,
1134	    dump_entry, &ld));
1135    }
1136}
1137
1138/*
1139 * merge filename [filename ...]
1140 */
1141static int do_merge (
1142    const char *inputfilename,
1143    int lineno,
1144    int argc,
1145    char **argv)
1146{
1147    int i;
1148    int errors = 0;
1149    AuthList *head, *tail, *listhead, *listtail;
1150    int nentries, nnew, nrepl, ndup;
1151
1152    if (argc < 2) {
1153	prefix (inputfilename, lineno);
1154	badcommandline (argv[0]);
1155	return 1;
1156    }
1157
1158    listhead = listtail = NULL;
1159
1160    for (i = 1; i < argc; i++) {
1161	const char *filename = argv[i];
1162	FILE *fp;
1163	Bool used_stdin = False;
1164
1165	fp = open_file (&filename, "rb",
1166			&used_stdin, inputfilename, lineno,
1167			argv[0]);
1168	if (!fp) {
1169	    errors++;
1170	    continue;
1171	}
1172
1173	head = tail = NULL;
1174	nentries = read_auth_entries (fp, &head, &tail);
1175	if (nentries == 0) {
1176	    prefix (inputfilename, lineno);
1177	    fprintf (stderr, "unable to read any entries from file \"%s\"\n",
1178		     filename);
1179	    errors++;
1180	} else {			/* link it in */
1181	    add_to_list (listhead, listtail, head);
1182 	}
1183
1184	if (!used_stdin) (void) fclose (fp);
1185    }
1186
1187    /*
1188     * if we have new entries, merge them in (freeing any duplicates)
1189     */
1190    if (listhead) {
1191	nentries = merge_entries (&iceauth_head, listhead,
1192	    &nnew, &nrepl, &ndup);
1193	if (verbose)
1194	  printf ("%d entries read in:  %d new, %d replacement%s\n",
1195	  	  nentries, nnew, nrepl, nrepl != 1 ? "s" : "");
1196	if (nentries > 0) iceauth_modified = True;
1197    }
1198
1199    return 0;
1200}
1201
1202/*
1203 * extract filename displayname [displayname ...]
1204 */
1205static int do_extract (
1206    const char *inputfilename,
1207    int lineno,
1208    int argc,
1209    char **argv)
1210{
1211    int errors;
1212    struct _extract_data ed;
1213
1214    if (argc < 3) {
1215	prefix (inputfilename, lineno);
1216	badcommandline (argv[0]);
1217	return 1;
1218    }
1219
1220    ed.fp = NULL;
1221    ed.filename = argv[1];
1222    ed.nwritten = 0;
1223    ed.cmd = argv[0];
1224
1225    errors = search_and_do (inputfilename, lineno, 2, argc, argv,
1226	extract_entry, &ed);
1227
1228    if (!ed.fp) {
1229	fprintf (stderr,
1230		 "No matches found, authority file \"%s\" not written\n",
1231		 ed.filename);
1232    } else {
1233	if (verbose) {
1234	    printf ("%d entries written to \"%s\"\n",
1235		    ed.nwritten, ed.filename);
1236	}
1237	if (!ed.used_stdout) {
1238	    (void) fclose (ed.fp);
1239	}
1240    }
1241
1242    return errors;
1243}
1244
1245
1246/*
1247 * add protoname protodata netid authname authdata
1248 */
1249static int do_add (
1250    const char *inputfilename,
1251    int lineno,
1252    int argc,
1253    char **argv)
1254{
1255    int n, nnew, nrepl, ndup;
1256    char *protoname;
1257    char *protodata_hex;
1258    char *protodata = NULL; /* not required */
1259    char *netid;
1260    char *authname;
1261    char *authdata_hex;
1262    char *authdata = NULL;
1263    int protodata_len, authdata_len;
1264    IceAuthFileEntry *auth = NULL;
1265    AuthList *list;
1266    int status = 0;
1267
1268    if (argc != 6 || !argv[1] || !argv[2] ||
1269	!argv[3] || !argv[4] || !argv[5])
1270    {
1271	prefix (inputfilename, lineno);
1272	badcommandline (argv[0]);
1273	return 1;
1274    }
1275
1276    protoname = argv[1];
1277    protodata_hex = argv[2];
1278    netid = argv[3];
1279    authname = argv[4];
1280    authdata_hex = argv[5];
1281
1282    protodata_len = strlen (protodata_hex);
1283    if (protodata_len > 0)
1284    {
1285	if (protodata_hex[0] == '"' && protodata_hex[protodata_len - 1] == '"')
1286	{
1287	    protodata = malloc (protodata_len - 1);
1288	    if (protodata)
1289	    {
1290		strncpy (protodata, protodata_hex + 1, protodata_len - 2);
1291		protodata_len -= 2;
1292	    }
1293	    else
1294		goto add_bad_malloc;
1295	}
1296	else
1297	{
1298	    protodata_len = cvthexkey (protodata_hex, &protodata);
1299	    if (protodata_len < 0)
1300	    {
1301		prefix (inputfilename, lineno);
1302		fprintf (stderr,
1303	       "protodata_hex contains odd number of or non-hex characters\n");
1304		return (1);
1305	    }
1306	}
1307    }
1308
1309    authdata_len = strlen (authdata_hex);
1310    if (authdata_hex[0] == '"' && authdata_hex[authdata_len - 1] == '"')
1311    {
1312	authdata = malloc (authdata_len - 1);
1313	if (authdata)
1314	{
1315	    strncpy (authdata, authdata_hex + 1, authdata_len - 2);
1316	    authdata_len -= 2;
1317	}
1318	else
1319	    goto add_bad_malloc;
1320    }
1321    else if (!strcmp (protoname, SECURERPC) || !strcmp (protoname, K5AUTH))
1322    {
1323	authdata = malloc (authdata_len + 1);
1324	if (authdata)
1325	    strcpy (authdata, authdata_hex);
1326	else
1327	    goto add_bad_malloc;
1328    }
1329    else
1330    {
1331	authdata_len = cvthexkey (authdata_hex, &authdata);
1332	if (authdata_len < 0)
1333	{
1334	    prefix (inputfilename, lineno);
1335	    fprintf (stderr,
1336	       "authdata_hex contains odd number of or non-hex characters\n");
1337	    free (protodata);
1338	    return (1);
1339	}
1340    }
1341
1342    auth = (IceAuthFileEntry *) malloc (sizeof (IceAuthFileEntry));
1343
1344    if (!auth)
1345	goto add_bad_malloc;
1346
1347    auth->protocol_name = copystring (protoname);
1348    auth->protocol_data_length = protodata_len;
1349    auth->protocol_data = protodata;
1350    auth->network_id = copystring (netid);
1351    auth->auth_name = copystring (authname);
1352    auth->auth_data_length = authdata_len;
1353    auth->auth_data = authdata;
1354
1355    if (!auth->protocol_name ||
1356	(!auth->protocol_data && auth->protocol_data_length > 0) ||
1357        !auth->network_id || !auth->auth_name ||
1358	(!auth->auth_data && auth->auth_data_length > 0))
1359    {
1360	goto add_bad_malloc;
1361    }
1362
1363    list = (AuthList *) malloc (sizeof (AuthList));
1364
1365    if (!list)
1366	goto add_bad_malloc;
1367
1368    list->next = NULL;
1369    list->auth = auth;
1370
1371    /*
1372     * merge it in; note that merge will deal with allocation
1373     */
1374
1375    n = merge_entries (&iceauth_head, list, &nnew, &nrepl, &ndup);
1376
1377    if (n > 0)
1378	iceauth_modified = True;
1379    else
1380    {
1381	prefix (inputfilename, lineno);
1382	if (ndup > 0)
1383	{
1384	    status = 0;
1385	    fprintf (stderr, "no records added - all duplicate\n");
1386	}
1387	else
1388	{
1389	    status = 1;
1390	    fprintf (stderr, "unable to merge in added record\n");
1391	}
1392	goto cant_add;
1393    }
1394
1395    return 0;
1396
1397
1398add_bad_malloc:
1399
1400    status = 1;
1401    prefix (inputfilename, lineno);
1402    fprintf (stderr, "unable to allocate memory to add an entry\n");
1403
1404cant_add:
1405
1406    if (protodata)
1407	free (protodata);
1408    if (authdata)
1409	free (authdata);
1410    if (auth)
1411    {
1412	if (auth->protocol_name)
1413	    free (auth->protocol_name);
1414	/* auth->protocol_data already freed,
1415	   since it's the same as protodata */
1416	if (auth->network_id)
1417	    free (auth->network_id);
1418	if (auth->auth_name)
1419	    free (auth->auth_name);
1420	/* auth->auth_data already freed,
1421	   since it's the same as authdata */
1422	free ((char *) auth);
1423    }
1424
1425    return status;
1426}
1427
1428/*
1429 * remove displayname
1430 */
1431static int do_remove (
1432    const char *inputfilename,
1433    int lineno,
1434    int argc,
1435    char **argv)
1436{
1437    int nremoved = 0;
1438    int errors;
1439
1440    if (argc < 2) {
1441	prefix (inputfilename, lineno);
1442	badcommandline (argv[0]);
1443	return 1;
1444    }
1445
1446    errors = search_and_do (inputfilename, lineno, 1, argc, argv,
1447	remove_entry, &nremoved);
1448    if (verbose) printf ("%d entries removed\n", nremoved);
1449    return errors;
1450}
1451
1452/*
1453 * info
1454 */
1455static int do_info (
1456    const char *inputfilename,
1457    int lineno,
1458    int argc,
1459    char **argv)
1460{
1461    int n;
1462    AuthList *l;
1463
1464    if (argc != 1) {
1465	prefix (inputfilename, lineno);
1466	badcommandline (argv[0]);
1467	return 1;
1468    }
1469
1470    for (l = iceauth_head, n = 0; l; l = l->next, n++) ;
1471
1472    printf ("Authority file:       %s\n",
1473	    iceauth_filename ? iceauth_filename : "(none)");
1474    printf ("File new:             %s\n", iceauth_existed ? No : Yes);
1475    printf ("File locked:          %s\n", ignore_locks ? No : Yes);
1476    printf ("Number of entries:    %d\n", n);
1477    printf ("Changes honored:      %s\n", iceauth_allowed ? Yes : No);
1478    printf ("Changes made:         %s\n", iceauth_modified ? Yes : No);
1479    printf ("Current input:        %s:%d\n", inputfilename, lineno);
1480    return 0;
1481}
1482
1483
1484/*
1485 * exit
1486 */
1487static Bool alldone = False;
1488
1489/* ARGSUSED */
1490static int do_exit (
1491    const char *inputfilename _X_UNUSED,
1492    int lineno _X_UNUSED,
1493    int argc _X_UNUSED,
1494    char **argv _X_UNUSED)
1495{
1496    /* allow bogus stuff */
1497    alldone = True;
1498    return 0;
1499}
1500
1501/*
1502 * quit
1503 */
1504/* ARGSUSED */
1505static int do_quit (
1506    const char *inputfilename _X_UNUSED,
1507    int lineno _X_UNUSED,
1508    int argc _X_UNUSED,
1509    char **argv _X_UNUSED)
1510{
1511    /* allow bogus stuff */
1512    die (0);
1513    /* NOTREACHED */
1514    return -1;				/* for picky compilers */
1515}
1516
1517
1518/*
1519 * source filename
1520 */
1521static int do_source (
1522    const char *inputfilename,
1523    int lineno,
1524    int argc,
1525    char **argv)
1526{
1527    const char *script;
1528    char buf[BUFSIZ];
1529    FILE *fp;
1530    Bool used_stdin = False;
1531    size_t len;
1532    int errors = 0, status;
1533    int sublineno = 0;
1534    char **subargv;
1535    int subargc;
1536    Bool prompt = False;		/* only true if reading from tty */
1537
1538    if (argc != 2 || !argv[1]) {
1539	prefix (inputfilename, lineno);
1540	badcommandline (argv[0]);
1541	return 1;
1542    }
1543
1544    script = argv[1];
1545
1546    fp = open_file (&script, "r", &used_stdin, inputfilename, lineno, argv[0]);
1547    if (!fp) {
1548	return 1;
1549    }
1550
1551    if (verbose && used_stdin && isatty (fileno (fp))) prompt = True;
1552
1553    while (!alldone) {
1554	buf[0] = '\0';
1555	if (prompt) {
1556	    printf ("iceauth> ");
1557	    fflush (stdout);
1558	}
1559	if (fgets (buf, sizeof buf, fp) == NULL) break;
1560	sublineno++;
1561	len = strlen (buf);
1562	if (len == 0 || buf[0] == '#') continue;
1563	if (buf[len-1] != '\n') {
1564	    prefix (script, sublineno);
1565	    fprintf (stderr, "line too long\n");
1566	    errors++;
1567	    break;
1568	}
1569	buf[--len] = '\0';		/* remove new line */
1570	subargv = split_into_words (buf, &subargc);
1571	if (subargv) {
1572	    status = process_command (script, sublineno, subargc, subargv);
1573	    free ((char *) subargv);
1574	    errors += status;
1575	} else {
1576	    prefix (script, sublineno);
1577	    fprintf (stderr, "unable to break line into words\n");
1578	    errors++;
1579	}
1580    }
1581
1582    if (!used_stdin) {
1583	(void) fclose (fp);
1584    }
1585    return errors;
1586}
1587