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