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