|
|
1.1 root 1: /* uux.c
2: Prepare to execute a command on a remote system.
3:
4: Copyright (C) 1991, 1992 Ian Lance Taylor
5:
6: This file is part of the Taylor UUCP package.
7:
8: This program is free software; you can redistribute it and/or
9: modify it under the terms of the GNU General Public License as
10: published by the Free Software Foundation; either version 2 of the
11: License, or (at your option) any later version.
12:
13: This program is distributed in the hope that it will be useful, but
14: WITHOUT ANY WARRANTY; without even the implied warranty of
15: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16: General Public License for more details.
17:
18: You should have received a copy of the GNU General Public License
19: along with this program; if not, write to the Free Software
20: Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21:
22: The author of the program may be contacted at [email protected] or
23: c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254.
24: */
25:
26: #include "uucp.h"
27:
28: #if USE_RCS_ID
29: const char uux_rcsid[] = "$Id: uux.c,v 1.1 93/07/30 08:00:09 bin Exp Locker: bin $";
30: #endif
31:
32: #include "uudefs.h"
33: #include "uuconf.h"
34: #include "system.h"
35: #include "sysdep.h"
36: #include "getopt.h"
37:
38: #include <ctype.h>
39: #include <errno.h>
40:
41: /* These character lists should, perhaps, be in sysdep.h. */
42:
43: /* This is the list of shell metacharacters that we check for. If one
44: of these is present, we request uuxqt to execute the command with
45: /bin/sh. Otherwise we let it execute using execve. */
46:
47: #define ZSHELLCHARS "\"'`*?[;&()|<>\\$"
48:
49: /* This is the list of word separators. We break filename arguments
50: at these characters. */
51: #define ZSHELLSEPS ";&*|<> \t"
52:
53: /* This is the list of word separators without the redirection
54: operators. */
55: #define ZSHELLNONREDIRSEPS ";&*| \t"
56:
57: /* The program name. */
58: char abProgram[] = "uux";
59:
60: /* The name of the execute file. */
61: const char *zXxqt_name;
62:
63: /* The execute file we are creating. */
64: static FILE *eXxqt_file;
65:
66: /* A list of commands to be spooled. */
67: static struct scmd *pasXcmds;
68: static int cXcmds;
69:
70: /* A file to close if we're forced to exit. */
71: static FILE *eXclose;
72:
73: /* Local functions. */
74: static void uxusage P((void));
75: static void uxadd_xqt_line P((int bchar, const char *z1, const char *z2));
76: static void uxadd_send_file P((const char *zfrom, const char *zto,
77: const char *zoptions, const char *ztemp,
78: const char *zforward,
79: const struct uuconf_system *qxqtsys,
80: const char *zxqtloc,
81: int bgrade));
82: static void uxcopy_stdin P((FILE *e));
83: static void uxrecord_file P((const char *zfile));
84: static void uxabort P((void));
85:
86: /* Long getopt options. */
87: static const struct option asXlongopts[] = { { NULL, 0, NULL, 0 } };
88:
89: /* The main routine. */
90:
91: int
92: main (argc, argv)
93: int argc;
94: char **argv;
95: {
96: /* -a: requestor address for status reports. */
97: const char *zrequestor = NULL;
98: /* -b: if true, return standard input on error. */
99: boolean fretstdin = FALSE;
100: /* -c,-C: if true, copy to spool directory. */
101: boolean fcopy = FALSE;
102: /* -c: set if -c appears explicitly; if it and -l appear, then if the
103: link fails we don't copy the file. */
104: boolean fdontcopy = FALSE;
105: /* -I: configuration file name. */
106: const char *zconfig = NULL;
107: /* -j: output job id. */
108: boolean fjobid = FALSE;
109: /* -g: job grade. */
110: char bgrade = BDEFAULT_UUX_GRADE;
111: /* -l: link file to spool directory. */
112: boolean flink = FALSE;
113: /* -n: do not notify upon command completion. */
114: boolean fno_ack = FALSE;
115: /* -p: read standard input for command standard input. */
116: boolean fread_stdin = FALSE;
117: /* -r: do not start uucico when finished. */
118: boolean fuucico = TRUE;
119: /* -s: report status to named file. */
120: const char *zstatus_file = NULL;
121: /* -W: only expand local file names. */
122: boolean fexpand = TRUE;
123: /* -z: report status only on error. */
124: boolean ferror_ack = FALSE;
125: int iopt;
126: pointer puuconf;
127: int iuuconf;
128: const char *zlocalname;
129: const char *zxqtloc;
130: int i;
131: size_t clen;
132: char *zargs;
133: char *zarg;
134: char *zcmd;
135: const char *zsys;
136: char *zexclam;
137: boolean fgetcwd;
138: const char *zuser;
139: struct uuconf_system sxqtsys;
140: boolean fxqtlocal;
141: char *zforward;
142: char **pzargs;
143: int calloc_args;
144: int cargs;
145: char abxqt_tname[CFILE_NAME_LEN];
146: char abxqt_xname[CFILE_NAME_LEN];
147: const char *zinput_from;
148: const char *zinput_to;
149: const char *zinput_temp;
150: boolean finputcopied;
151: char *zcall_system;
152: boolean fcall_any;
153: struct uuconf_system slocalsys;
154: boolean fneedshell;
155: char *zfullcmd;
156: boolean fexit;
157:
158: /* We need to be able to read a single - as an option, which getopt
159: won't do. So that we can still use getopt, we run through the
160: options looking for an option "-"; if we find one we change it to
161: "-p", which is equivalent to "-". */
162: for (i = 1; i < argc; i++)
163: {
164: if (argv[i][0] != '-')
165: break;
166: if (argv[i][1] == '\0')
167: argv[i] = zbufcpy ("-p");
168: else
169: {
170: const char *z;
171:
172: for (z = argv[i] + 1; *z != '\0'; z++)
173: {
174: /* If the option takes an argument, and the argument is
175: not appended, then skip the next argument. */
176: if (*z == 'a' || *z == 'g' || *z == 'I'
177: || *z == 's' || *z == 'x')
178: {
179: if (z[1] == '\0')
180: i++;
181: break;
182: }
183: }
184: }
185: }
186:
187: /* The leading + in the getopt string means to stop processing
188: options as soon as a non-option argument is seen. */
189: while ((iopt = getopt_long (argc, argv, "+a:bcCg:I:jlnprs:Wx:z",
190: asXlongopts, (int *) NULL)) != EOF)
191: {
192: switch (iopt)
193: {
194: case 'a':
195: /* Set requestor name: mail address to which status reports
196: should be sent. */
197: zrequestor = optarg;
198: break;
199:
200: case 'b':
201: /* Return standard input on error. */
202: fretstdin = TRUE;
203: break;
204:
205: case 'c':
206: /* Do not copy local files to spool directory. */
207: fcopy = FALSE;
208: fdontcopy = TRUE;
209: break;
210:
211: case 'C':
212: /* Copy local files to spool directory. */
213: fcopy = TRUE;
214: break;
215:
216: case 'I':
217: /* Configuration file name. */
218: if (fsysdep_other_config (optarg))
219: zconfig = optarg;
220: break;
221:
222: case 'j':
223: /* Output jobid. */
224: fjobid = TRUE;
225: break;
226:
227: case 'g':
228: /* Set job grade. */
229: bgrade = optarg[0];
230: break;
231:
232: case 'l':
233: /* Link file to spool directory. */
234: flink = TRUE;
235: break;
236:
237: case 'n':
238: /* Do not notify upon command completion. */
239: fno_ack = TRUE;
240: break;
241:
242: case 'p':
243: /* Read standard input for command standard input. */
244: fread_stdin = TRUE;
245: break;
246:
247: case 'r':
248: /* Do not start uucico when finished. */
249: fuucico = FALSE;
250: break;
251:
252: case 's':
253: /* Report status to named file. */
254: zstatus_file = optarg;
255: break;
256:
257: case 'W':
258: /* Only expand local file names. */
259: fexpand = FALSE;
260: break;
261:
262: case 'x':
263: #if DEBUG > 1
264: /* Set debugging level. */
265: iDebug |= idebug_parse (optarg);
266: #endif
267: break;
268:
269: case 'z':
270: /* Report status only on error. */
271: ferror_ack = TRUE;
272: break;
273:
274: case 0:
275: /* Long option found and flag set. */
276: break;
277:
278: default:
279: uxusage ();
280: break;
281: }
282: }
283:
284: if (! UUCONF_GRADE_LEGAL (bgrade))
285: {
286: ulog (LOG_ERROR, "Ignoring illegal grade");
287: bgrade = BDEFAULT_UUX_GRADE;
288: }
289:
290: if (optind == argc)
291: uxusage ();
292:
293: iuuconf = uuconf_init (&puuconf, (const char *) NULL, zconfig);
294: if (iuuconf != UUCONF_SUCCESS)
295: ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
296:
297: #if DEBUG > 1
298: {
299: const char *zdebug;
300:
301: iuuconf = uuconf_debuglevel (puuconf, &zdebug);
302: if (iuuconf != UUCONF_SUCCESS)
303: ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
304: if (zdebug != NULL)
305: iDebug |= idebug_parse (zdebug);
306: }
307: #endif
308:
309: /* The command and files arguments could be quoted in any number of
310: ways, so we split them apart ourselves. We do this before
311: calling usysdep_initialize because we want to set fgetcwd
312: correctly. */
313: clen = 1;
314: for (i = optind; i < argc; i++)
315: clen += strlen (argv[i]) + 1;
316:
317: zargs = zbufalc (clen);
318: *zargs = '\0';
319: for (i = optind; i < argc; i++)
320: {
321: strcat (zargs, argv[i]);
322: strcat (zargs, " ");
323: }
324:
325: /* The first argument is the command to execute. */
326: clen = strcspn (zargs, ZSHELLSEPS);
327: zcmd = zbufalc (clen + 1);
328: strncpy (zcmd, zargs, clen);
329: zcmd[clen] = '\0';
330: zargs += clen;
331:
332: /* Split the arguments out into an array. We break the arguments
333: into alternating sequences of characters not in ZSHELLSEPS
334: and characters in ZSHELLSEPS. We remove whitespace. We
335: separate the redirection characters '>' and '<' into their
336: own arguments to make them easier to process below. */
337: calloc_args = 10;
338: pzargs = (char **) xmalloc (calloc_args * sizeof (char *));
339: cargs = 0;
340:
341: for (zarg = strtok (zargs, " \t");
342: zarg != NULL;
343: zarg = strtok ((char *) NULL, " \t"))
344: {
345: while (*zarg != '\0')
346: {
347: if (cargs + 1 >= calloc_args)
348: {
349: calloc_args += 10;
350: pzargs = (char **) xrealloc ((pointer) pzargs,
351: calloc_args * sizeof (char *));
352: }
353:
354: clen = strcspn (zarg, ZSHELLSEPS);
355: if (clen > 0)
356: {
357: pzargs[cargs] = zbufalc (clen + 1);
358: memcpy (pzargs[cargs], zarg, clen);
359: pzargs[cargs][clen] = '\0';
360: ++cargs;
361: zarg += clen;
362: }
363:
364: /* We deliberately separate '>' and '<' out. */
365: if (*zarg != '\0')
366: {
367: clen = strspn (zarg, ZSHELLNONREDIRSEPS);
368: if (clen == 0)
369: clen = 1;
370: pzargs[cargs] = zbufalc (clen + 1);
371: memcpy (pzargs[cargs], zarg, clen);
372: pzargs[cargs][clen] = '\0';
373: ++cargs;
374: zarg += clen;
375: }
376: }
377: }
378:
379: /* Now look through the arguments to see if we are going to need the
380: current working directory. We don't try to make a precise
381: determination, just a conservative one. The basic idea is that
382: we don't want to get the cwd for 'foo!rmail - user' (note that we
383: don't examine the command itself). */
384: fgetcwd = FALSE;
385: for (i = 0; i < cargs; i++)
386: {
387: if (pzargs[i][0] == '(')
388: continue;
389: zexclam = strrchr (pzargs[i], '!');
390: if (zexclam != NULL && fsysdep_needs_cwd (zexclam + 1))
391: {
392: fgetcwd = TRUE;
393: break;
394: }
395: if ((pzargs[i][0] == '<' || pzargs[i][0] == '>')
396: && i + 1 < cargs
397: && strchr (pzargs[i + 1], '!') == NULL
398: && fsysdep_needs_cwd (pzargs[i + 1]))
399: {
400: fgetcwd = TRUE;
401: break;
402: }
403: }
404:
405: #ifdef SIGINT
406: usysdep_signal (SIGINT);
407: #endif
408: #ifdef SIGHUP
409: usysdep_signal (SIGHUP);
410: #endif
411: #ifdef SIGQUIT
412: usysdep_signal (SIGQUIT);
413: #endif
414: #ifdef SIGTERM
415: usysdep_signal (SIGTERM);
416: #endif
417: #ifdef SIGPIPE
418: usysdep_signal (SIGPIPE);
419: #endif
420:
421: usysdep_initialize (puuconf, INIT_SUID | (fgetcwd ? INIT_GETCWD : 0));
422:
423: ulog_fatal_fn (uxabort);
424:
425: zuser = zsysdep_login_name ();
426:
427: /* Get the local system name. */
428: iuuconf = uuconf_localname (puuconf, &zlocalname);
429: if (iuuconf == UUCONF_NOT_FOUND)
430: {
431: zlocalname = zsysdep_localname ();
432: if (zlocalname == NULL)
433: exit (EXIT_FAILURE);
434: }
435: else if (iuuconf != UUCONF_SUCCESS)
436: ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
437:
438: /* Get the local system information. */
439: iuuconf = uuconf_system_info (puuconf, zlocalname, &slocalsys);
440: if (iuuconf != UUCONF_SUCCESS)
441: {
442: if (iuuconf != UUCONF_NOT_FOUND)
443: ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
444: iuuconf = uuconf_system_local (puuconf, &slocalsys);
445: if (iuuconf != UUCONF_SUCCESS)
446: ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
447: }
448:
449: /* Figure out which system the command is to be executed on. Some
450: mailers apparently pass local!rmail, so we must explicitly check
451: for that. */
452: zexclam = strchr (zcmd, '!');
453: while (zexclam != NULL)
454: {
455: *zexclam = '\0';
456: if (strcmp (zcmd, zlocalname) == 0)
457: ;
458: else if (slocalsys.uuconf_pzalias == NULL)
459: break;
460: else
461: {
462: char **pzal;
463:
464: for (pzal = slocalsys.uuconf_pzalias; *pzal != NULL; pzal++)
465: if (strcmp (zcmd, *pzal) == 0)
466: break;
467: if (*pzal == NULL)
468: break;
469: }
470: zcmd = zexclam + 1;
471: zexclam = strchr (zcmd, '!');
472: }
473: if (zexclam == NULL)
474: {
475: zsys = zlocalname;
476: fxqtlocal = TRUE;
477: zforward = NULL;
478: }
479: else
480: {
481: zsys = zcmd;
482: zcmd = zexclam + 1;
483: fxqtlocal = FALSE;
484:
485: /* See if we must forward this command through other systems
486: (e.g. uux a!b!cmd). */
487: zexclam = strrchr (zcmd, '!');
488: if (zexclam == NULL)
489: zforward = NULL;
490: else
491: {
492: clen = zexclam - zcmd;
493: zforward = zbufalc (clen);
494: memcpy (zforward, zcmd, clen);
495: zforward[clen] = '\0';
496: zcmd = zexclam + 1;
497: }
498: }
499:
500: if (fxqtlocal)
501: sxqtsys = slocalsys;
502: else
503: {
504: iuuconf = uuconf_system_info (puuconf, zsys, &sxqtsys);
505: if (iuuconf != UUCONF_SUCCESS)
506: {
507: if (iuuconf != UUCONF_NOT_FOUND)
508: ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
509: if (! funknown_system (puuconf, zsys, &sxqtsys))
510: ulog (LOG_FATAL, "%s: System not found", zsys);
511: }
512: }
513:
514: /* Get the local name the remote system know us as. */
515: zxqtloc = sxqtsys.uuconf_zlocalname;
516: if (zxqtloc == NULL)
517: zxqtloc = zlocalname;
518:
519: /* We can send this as an E command if the execution is on a
520: different, directly connected, system and the only file used is
521: the standard input and comes from this system. This is true of
522: the common cases of rmail and rnews. We get an execute file name
523: here in case we need it. */
524: if (fxqtlocal)
525: zXxqt_name = zsysdep_xqt_file_name ();
526: else
527: zXxqt_name = zsysdep_data_file_name (&sxqtsys, zxqtloc, bgrade, TRUE,
528: abxqt_tname, (char *) NULL,
529: abxqt_xname);
530: if (zXxqt_name == NULL)
531: uxabort ();
532:
533: uxrecord_file (zXxqt_name);
534:
535: /* Look through the arguments. Any argument containing an
536: exclamation point character is interpreted as a file name, and is
537: sent to the appropriate system. */
538: zinput_from = NULL;
539: zinput_to = NULL;
540: zinput_temp = NULL;
541: finputcopied = FALSE;
542: zcall_system = NULL;
543: fcall_any = FALSE;
544:
545: for (i = 0; i < cargs; i++)
546: {
547: const char *zsystem;
548: char *zfile;
549: char *zforw;
550: boolean finput, foutput;
551: boolean flocal, fonxqt;
552:
553: /* Check for a parenthesized argument; remove the parentheses
554: and otherwise ignore it (this is how an exclamation point is
555: quoted). */
556: if (pzargs[i][0] == '(')
557: {
558: clen = strlen (pzargs[i]);
559: if (pzargs[i][clen - 1] != ')')
560: ulog (LOG_ERROR, "Mismatched parentheses");
561: else
562: pzargs[i][clen - 1] = '\0';
563: ++pzargs[i];
564: continue;
565: }
566:
567: /* Check whether we are doing a redirection. */
568: finput = FALSE;
569: foutput = FALSE;
570: if (i + 1 < cargs)
571: {
572: if (pzargs[i][0] == '<')
573: finput = TRUE;
574: else if (pzargs[i][0] == '>')
575: foutput = TRUE;
576: if (finput || foutput)
577: {
578: pzargs[i] = NULL;
579: i++;
580: }
581: }
582:
583: zexclam = strchr (pzargs[i], '!');
584:
585: /* If there is no exclamation point and no redirection, this
586: argument is left untouched. */
587: if (zexclam == NULL && ! finput && ! foutput)
588: continue;
589:
590: /* Get the system name and file name for this file. */
591: if (zexclam == NULL)
592: {
593: zsystem = zlocalname;
594: zfile = pzargs[i];
595: flocal = TRUE;
596: zforw = NULL;
597: }
598: else
599: {
600: *zexclam = '\0';
601: zsystem = pzargs[i];
602: if (*zsystem != '\0')
603: flocal = FALSE;
604: else
605: {
606: zsystem = zlocalname;
607: flocal = TRUE;
608: }
609: zfile = zexclam + 1;
610: zexclam = strrchr (zfile, '!');
611: if (zexclam == NULL)
612: zforw = NULL;
613: else
614: {
615: if (flocal)
616: ulog (LOG_FATAL, "!%s: Can't figure out where to get file",
617: zfile);
618: *zexclam = '\0';
619: zforw = zfile;
620: zfile = zexclam + 1;
621: }
622: }
623:
624: /* Check if the file is already on the execution system. */
625: if (flocal)
626: fonxqt = fxqtlocal;
627: else if (fxqtlocal)
628: fonxqt = FALSE;
629: else if (zforward == NULL ? zforw != NULL : zforw == NULL)
630: fonxqt = FALSE;
631: else if (zforward != NULL
632: && zforw != NULL
633: && strcmp (zforward, zforw) != 0)
634: fonxqt = FALSE;
635: else if (strcmp (zsystem, sxqtsys.uuconf_zname) == 0)
636: fonxqt = TRUE;
637: else if (sxqtsys.uuconf_pzalias == NULL)
638: fonxqt = FALSE;
639: else
640: {
641: char **pzal;
642:
643: fonxqt = FALSE;
644: for (pzal = sxqtsys.uuconf_pzalias; *pzal != NULL; pzal++)
645: {
646: if (strcmp (zsystem, *pzal) == 0)
647: {
648: fonxqt = TRUE;
649: break;
650: }
651: }
652: }
653:
654: /* Turn the file into an absolute path. */
655: if (flocal)
656: zfile = zsysdep_local_file_cwd (zfile, sxqtsys.uuconf_zpubdir);
657: else if (fexpand)
658: zfile = zsysdep_add_cwd (zfile);
659: if (zfile == NULL)
660: uxabort ();
661:
662: /* Check for output redirection. */
663: if (foutput)
664: {
665: if (flocal)
666: {
667: if (! fin_directory_list (zfile,
668: sxqtsys.uuconf_pzremote_receive,
669: sxqtsys.uuconf_zpubdir, TRUE,
670: FALSE, (const char *) NULL))
671: ulog (LOG_FATAL, "Not permitted to create %s", zfile);
672: }
673:
674: /* There are various cases of output redirection.
675:
676: uux cmd >out: The command is executed on the local
677: system, and the output file is placed on the local
678: system (fonxqt is TRUE).
679:
680: uux cmd >a!out: The command is executed on the local
681: system, and the output file is sent to a.
682:
683: uux a!cmd >out: The command is executed on a, and the
684: output file is returned to the local system (flocal
685: is TRUE).
686:
687: uux a!cmd >a!out: The command is executed on a, and the
688: output file is left on a (fonxqt is TRUE).
689:
690: uux a!cmd >b!out: The command is executed on a, and the
691: output file is sent to b; traditionally, I believe
692: that b is relative to a, rather than to the local
693: system. However, this essentially contradicts the
694: previous two cases, in which the output file is
695: relative to the local system.
696:
697: Now, the cases that we don't handle.
698:
699: uux cmd >a!b!out: The command is executed on the local
700: system, and the output file is sent to b via a. This
701: requires the local uuxqt to support forwarding of the
702: output file.
703:
704: uux a!b!cmd >out: The command is executed on b, which is
705: reached via a. Probably the output file is intended
706: for the local system, in which case the uuxqt on b
707: must support forwarding of the output file.
708:
709: uux a!b!cmd >c!out: Is c relative to b or to the local
710: system? If it's relative to b this is easy to
711: handle. Otherwise, we must arrange for the file to
712: be sent back to the local system and for the local
713: system to send it on to c.
714:
715: There are many variations of the last case. It's not at
716: all clear to me how they should be handled. */
717: if (zforward != NULL || zforw != NULL)
718: ulog (LOG_FATAL, "May not forward standard output");
719:
720: if (fonxqt)
721: uxadd_xqt_line ('O', zfile, (const char *) NULL);
722: else if (flocal)
723: uxadd_xqt_line ('O', zfile, zxqtloc);
724: else
725: uxadd_xqt_line ('O', zfile, zsystem);
726: pzargs[i] = NULL;
727: continue;
728: }
729:
730: if (finput)
731: {
732: if (fread_stdin)
733: ulog (LOG_FATAL, "Standard input specified twice");
734: pzargs[i] = NULL;
735: }
736:
737: if (flocal)
738: {
739: char *zuse;
740: char *zdata;
741: char abtname[CFILE_NAME_LEN];
742: char abdname[CFILE_NAME_LEN];
743:
744: /* It's a local file. If requested by -C, copy the file to
745: the spool directory. If requested by -l, link the file
746: to the spool directory; if the link fails, we copy the
747: file, unless -c was explictly used. If the execution is
748: occurring on the local system, we force the copy as well,
749: because otherwise we would have to have some way to tell
750: uuxqt not to move the file. If the file is being shipped
751: to another system, we must set up a transfer request.
752: First make sure the user has legitimate access, since we
753: are running setuid. */
754: if (! fsysdep_access (zfile))
755: uxabort ();
756:
757: zdata = zsysdep_data_file_name (&sxqtsys, zxqtloc, bgrade, FALSE,
758: abtname, abdname, (char *) NULL);
759: if (zdata == NULL)
760: uxabort ();
761:
762: if (fcopy || flink || fxqtlocal)
763: {
764: boolean fdid;
765:
766: uxrecord_file (zdata);
767:
768: fdid = FALSE;
769: if (flink)
770: {
771: boolean fworked;
772:
773: if (! fsysdep_link (zfile, zdata, &fworked))
774: uxabort ();
775:
776: if (fworked)
777: fdid = TRUE;
778: else if (fdontcopy)
779: ulog (LOG_FATAL, "%s: Can't link to spool directory",
780: zfile);
781: }
782:
783: if (! fdid)
784: {
785: openfile_t efile;
786:
787: efile = esysdep_user_fopen (zfile, TRUE, TRUE);
788: if (! ffileisopen (efile))
789: uxabort ();
790: if (! fcopy_open_file (efile, zdata, FALSE, TRUE))
791: uxabort ();
792: (void) ffileclose (efile);
793: }
794:
795: zuse = abtname;
796: }
797: else
798: {
799: /* We don't actually use the spool file name, but we
800: need a name to use as the destination. */
801: ubuffree (zdata);
802: /* Make sure the daemon can access the file. */
803: if (! fsysdep_daemon_access (zfile))
804: uxabort ();
805: if (! fin_directory_list (zfile, sxqtsys.uuconf_pzlocal_send,
806: sxqtsys.uuconf_zpubdir, TRUE,
807: TRUE, zuser))
808: ulog (LOG_FATAL, "Not permitted to send from %s",
809: zfile);
810:
811: zuse = zfile;
812: }
813:
814: if (fxqtlocal)
815: {
816: if (finput)
817: uxadd_xqt_line ('I', zuse, (char *) NULL);
818: else
819: pzargs[i] = zuse;
820: }
821: else
822: {
823: finputcopied = fcopy || flink;
824:
825: if (finput)
826: {
827: zinput_from = zuse;
828: zinput_to = zbufcpy (abdname);
829: zinput_temp = zbufcpy (abtname);
830: }
831: else
832: {
833: char *zbase;
834:
835: uxadd_send_file (zuse, abdname,
836: finputcopied ? "C" : "c",
837: abtname, zforward, &sxqtsys,
838: zxqtloc, bgrade);
839: zbase = zsysdep_base_name (zfile);
840: if (zbase == NULL)
841: uxabort ();
842: uxadd_xqt_line ('F', abdname, zbase);
843: pzargs[i] = zbase;
844: }
845: }
846: }
847: else if (fonxqt)
848: {
849: /* The file is already on the system where the command is to
850: be executed. */
851: if (finput)
852: uxadd_xqt_line ('I', zfile, (const char *) NULL);
853: else
854: pzargs[i] = zfile;
855: }
856: else
857: {
858: struct uuconf_system sfromsys;
859: char abtname[CFILE_NAME_LEN];
860: struct scmd s;
861: char *zjobid;
862:
863: /* We need to request a remote file. */
864: iuuconf = uuconf_system_info (puuconf, zsystem, &sfromsys);
865: if (iuuconf != UUCONF_SUCCESS)
866: {
867: if (iuuconf != UUCONF_NOT_FOUND)
868: ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
869: if (! funknown_system (puuconf, zsystem, &sfromsys))
870: ulog (LOG_FATAL, "%s: System not found", zsystem);
871: }
872:
873: if (fonxqt)
874: {
875: /* The file is already on the system where the command is to
876: be executed. */
877: if (finput)
878: uxadd_xqt_line ('I', zfile, (const char *) NULL);
879: else
880: pzargs[i] = zfile;
881: }
882: else
883: {
884: char *zdata;
885:
886: if (! sfromsys.uuconf_fcall_transfer
887: && ! sfromsys.uuconf_fcalled_transfer)
888: ulog (LOG_FATAL,
889: "Not permitted to transfer files to or from %s",
890: sfromsys.uuconf_zname);
891:
892: if (zforw != NULL)
893: {
894: /* This is ``uux cmd a!b!file''. To make this work,
895: we would have to be able to set up a request to a
896: to fetch file from b and send it to us. But it
897: turns out that that will not work, because when a
898: sends us the file we will put it in a's spool
899: directory, not the local system spool directory.
900: So we won't have any way to find it. This is not
901: a conceptual problem, and it could doubtless be
902: solved. Please feel free to solve it and send me
903: the solution. */
904: ulog (LOG_FATAL, "File forwarding not supported");
905: }
906:
907: /* We must request the file from the remote system to
908: this one. */
909: zdata = zsysdep_data_file_name (&slocalsys, zxqtloc, bgrade,
910: FALSE, abtname, (char *) NULL,
911: (char *) NULL);
912: if (zdata == NULL)
913: uxabort ();
914: ubuffree (zdata);
915:
916: /* Request the file. The special option '9' is a signal
917: to uucico that it's OK to receive a file into the
918: spool directory; normally such requests are rejected.
919: This privilege is easy to abuse. */
920: s.bcmd = 'R';
921: s.pseq = NULL;
922: s.zfrom = zfile;
923: s.zto = zbufcpy (abtname);
924: s.zuser = zuser;
925: s.zoptions = "9";
926: s.ztemp = "";
927: s.imode = 0600;
928: s.znotify = "";
929: s.cbytes = -1;
930: s.zcmd = NULL;
931: s.ipos = 0;
932:
933: zjobid = zsysdep_spool_commands (&sfromsys, bgrade, 1, &s);
934: if (zjobid == NULL)
935: uxabort ();
936:
937: if (fjobid)
938: printf ("%s\n", zjobid);
939:
940: ubuffree (zjobid);
941:
942: if (fcall_any)
943: {
944: ubuffree (zcall_system);
945: zcall_system = NULL;
946: }
947: else
948: {
949: fcall_any = TRUE;
950: zcall_system = zbufcpy (sfromsys.uuconf_zname);
951: }
952:
953: if (fxqtlocal)
954: {
955: /* Tell the command execution to wait until the file
956: has been received, and tell it the real file
957: name. */
958: if (finput)
959: {
960: uxadd_xqt_line ('F', abtname, (char *) NULL);
961: uxadd_xqt_line ('I', abtname, (char *) NULL);
962: }
963: else
964: {
965: char *zbase;
966:
967: zbase = zsysdep_base_name (zfile);
968: if (zbase == NULL)
969: uxabort ();
970: uxadd_xqt_line ('F', abtname, zbase);
971: pzargs[i] = zbase;
972: }
973: }
974: else
975: {
976: char abxtname[CFILE_NAME_LEN];
977: char *zbase;
978: char *zxqt;
979: FILE *e;
980:
981: /* Now we must arrange to forward the file on to the
982: execution system. We need to get a name to give
983: the file on the execution system (abxtname). */
984: zdata = zsysdep_data_file_name (&sxqtsys, zxqtloc,
985: bgrade, TRUE, abxtname,
986: (char *) NULL,
987: (char *) NULL);
988: if (zdata == NULL)
989: uxabort ();
990: ubuffree (zdata);
991:
992: zbase = zsysdep_base_name (zfile);
993: if (zbase == NULL)
994: uxabort ();
995:
996: zxqt = zsysdep_xqt_file_name ();
997: if (zxqt == NULL)
998: uxabort ();
999: e = esysdep_fopen (zxqt, FALSE, FALSE, TRUE);
1000: if (e == NULL)
1001: uxabort ();
1002: uxrecord_file (zxqt);
1003:
1004: fprintf (e, "U %s %s\n", zsysdep_login_name (),
1005: zlocalname);
1006: fprintf (e, "F %s %s\n", abtname, zbase);
1007: fprintf (e, "C uucp -C -W -d -g %c %s %s!", bgrade,
1008: zbase, sxqtsys.uuconf_zname);
1009: if (zforward != NULL)
1010: fprintf (e, "%s!", zforward);
1011: fprintf (e, "%s\n", abxtname);
1012:
1013: if (fclose (e) != 0)
1014: ulog (LOG_FATAL, "fclose: %s", strerror (errno));
1015:
1016: if (finput)
1017: {
1018: uxadd_xqt_line ('F', abxtname, (char *) NULL);
1019: uxadd_xqt_line ('I', abxtname, (char *) NULL);
1020: ubuffree (zbase);
1021: }
1022: else
1023: {
1024: uxadd_xqt_line ('F', abxtname, zbase);
1025: pzargs[i] = zbase;
1026: }
1027: }
1028: }
1029:
1030: (void) uuconf_system_free (puuconf, &sfromsys);
1031: }
1032: }
1033:
1034: /* If standard input is to be read from the stdin of uux, we read it
1035: here into a temporary file and send it to the execute system. */
1036: if (fread_stdin)
1037: {
1038: char *zdata;
1039: char abtname[CFILE_NAME_LEN];
1040: char abdname[CFILE_NAME_LEN];
1041: FILE *e;
1042:
1043: zdata = zsysdep_data_file_name (&sxqtsys, zxqtloc, bgrade, FALSE,
1044: abtname, abdname, (char *) NULL);
1045: if (zdata == NULL)
1046: uxabort ();
1047:
1048: e = esysdep_fopen (zdata, FALSE, FALSE, TRUE);
1049: if (e == NULL)
1050: uxabort ();
1051:
1052: eXclose = e;
1053: uxrecord_file (zdata);
1054:
1055: uxcopy_stdin (e);
1056:
1057: eXclose = NULL;
1058: if (fclose (e) != 0)
1059: ulog (LOG_FATAL, "fclose: %s", strerror (errno));
1060:
1061: if (fxqtlocal)
1062: uxadd_xqt_line ('I', abtname, (const char *) NULL);
1063: else
1064: {
1065: zinput_from = zbufcpy (abtname);
1066: zinput_to = zbufcpy (abdname);
1067: zinput_temp = zinput_from;
1068: finputcopied = TRUE;
1069: }
1070: }
1071:
1072: /* If we are returning standard input, or we're putting the status
1073: in a file, we can't use an E command. */
1074: if (fretstdin)
1075: uxadd_xqt_line ('B', (const char *) NULL, (const char *) NULL);
1076:
1077: if (zstatus_file != NULL)
1078: uxadd_xqt_line ('M', zstatus_file, (const char *) NULL);
1079:
1080: /* Get the complete command line, and decide whether the command
1081: needs to be executed by the shell. */
1082: fneedshell = FALSE;
1083:
1084: if (zcmd[strcspn (zcmd, ZSHELLCHARS)] != '\0')
1085: fneedshell = TRUE;
1086:
1087: clen = strlen (zcmd) + 1;
1088: for (i = 0; i < cargs; i++)
1089: {
1090: if (pzargs[i] != NULL)
1091: {
1092: clen += strlen (pzargs[i]) + 1;
1093: if (pzargs[i][strcspn (pzargs[i], ZSHELLCHARS)] != '\0')
1094: fneedshell = TRUE;
1095: }
1096: }
1097:
1098: zfullcmd = zbufalc (clen);
1099:
1100: strcpy (zfullcmd, zcmd);
1101: for (i = 0; i < cargs; i++)
1102: {
1103: if (pzargs[i] != NULL)
1104: {
1105: strcat (zfullcmd, " ");
1106: strcat (zfullcmd, pzargs[i]);
1107: }
1108: }
1109:
1110: /* If we haven't written anything to the execution file yet, and we
1111: have a standard input file, and we're not forwarding, then every
1112: other option can be handled in an E command. */
1113: if (eXxqt_file == NULL && zinput_from != NULL && zforward == NULL)
1114: {
1115: struct scmd s;
1116: char aboptions[10];
1117: char *zoptions;
1118:
1119: /* Set up an E command. */
1120: s.bcmd = 'E';
1121: s.pseq = NULL;
1122: s.zuser = zuser;
1123: s.zfrom = zinput_from;
1124: s.zto = zinput_to;
1125: s.zoptions = aboptions;
1126: zoptions = aboptions;
1127: *zoptions++ = finputcopied ? 'C' : 'c';
1128: if (fno_ack)
1129: *zoptions++ = 'N';
1130: if (ferror_ack)
1131: *zoptions++ = 'Z';
1132: if (zrequestor != NULL)
1133: *zoptions++ = 'R';
1134: if (fneedshell)
1135: *zoptions++ = 'e';
1136: *zoptions = '\0';
1137: s.ztemp = zinput_temp;
1138: s.imode = 0666;
1139: if (zrequestor == NULL)
1140: zrequestor = "\"\"";
1141: s.znotify = zrequestor;
1142: s.cbytes = -1;
1143: s.zcmd = zfullcmd;
1144: s.ipos = 0;
1145:
1146: ++cXcmds;
1147: pasXcmds = (struct scmd *) xrealloc ((pointer) pasXcmds,
1148: cXcmds * sizeof (struct scmd));
1149: pasXcmds[cXcmds - 1] = s;
1150: }
1151: else
1152: {
1153: /* Finish up the execute file. */
1154: uxadd_xqt_line ('U', zuser, zxqtloc);
1155: if (zinput_from != NULL)
1156: {
1157: uxadd_xqt_line ('F', zinput_to, (char *) NULL);
1158: uxadd_xqt_line ('I', zinput_to, (char *) NULL);
1159: uxadd_send_file (zinput_from, zinput_to,
1160: finputcopied ? "C" : "c",
1161: zinput_temp, zforward, &sxqtsys, zxqtloc,
1162: bgrade);
1163: }
1164: if (fno_ack)
1165: uxadd_xqt_line ('N', (const char *) NULL, (const char *) NULL);
1166: if (ferror_ack)
1167: uxadd_xqt_line ('Z', (const char *) NULL, (const char *) NULL);
1168: if (zrequestor != NULL)
1169: uxadd_xqt_line ('R', zrequestor, (const char *) NULL);
1170: if (fneedshell)
1171: uxadd_xqt_line ('e', (const char *) NULL, (const char *) NULL);
1172: uxadd_xqt_line ('C', zfullcmd, (const char *) NULL);
1173: if (fclose (eXxqt_file) != 0)
1174: ulog (LOG_FATAL, "fclose: %s", strerror (errno));
1175: eXxqt_file = NULL;
1176:
1177: /* If the execution is to occur on another system, we must now
1178: arrange to copy the execute file to this system. */
1179: if (! fxqtlocal)
1180: uxadd_send_file (abxqt_tname, abxqt_xname, "C", abxqt_tname,
1181: zforward, &sxqtsys, zxqtloc, bgrade);
1182: }
1183:
1184: /* If we got a signal, get out before spooling anything. */
1185: if (FGOT_SIGNAL ())
1186: uxabort ();
1187:
1188: /* From here on in, it's too late. We don't call uxabort. */
1189: if (cXcmds > 0)
1190: {
1191: char *zjobid;
1192:
1193: if (! sxqtsys.uuconf_fcall_transfer
1194: && ! sxqtsys.uuconf_fcalled_transfer)
1195: ulog (LOG_FATAL, "Not permitted to transfer files to or from %s",
1196: sxqtsys.uuconf_zname);
1197:
1198: zjobid = zsysdep_spool_commands (&sxqtsys, bgrade, cXcmds, pasXcmds);
1199: if (zjobid == NULL)
1200: {
1201: ulog_close ();
1202: usysdep_exit (FALSE);
1203: }
1204:
1205: if (fjobid)
1206: printf ("%s\n", zjobid);
1207:
1208: ubuffree (zjobid);
1209:
1210: if (fcall_any)
1211: {
1212: ubuffree (zcall_system);
1213: zcall_system = NULL;
1214: }
1215: else
1216: {
1217: fcall_any = TRUE;
1218: zcall_system = zbufcpy (sxqtsys.uuconf_zname);
1219: }
1220: }
1221:
1222: /* If all that worked, make a log file entry. All log file reports
1223: up to this point went to stderr. */
1224: ulog_to_file (puuconf, TRUE);
1225: ulog_system (sxqtsys.uuconf_zname);
1226: ulog_user (zuser);
1227:
1228: ulog (LOG_NORMAL, "Queuing %s", zfullcmd);
1229:
1230: ulog_close ();
1231:
1232: if (! fuucico)
1233: fexit = TRUE;
1234: else
1235: {
1236: if (zcall_system != NULL)
1237: fexit = fsysdep_run ("uucico", "-s", zcall_system);
1238: else if (fcall_any)
1239: fexit = fsysdep_run ("uucico", "-r1", (const char *) NULL);
1240: else
1241: fexit = TRUE;
1242: }
1243:
1244: usysdep_exit (fexit);
1245:
1246: /* Avoid error about not returning a value. */
1247: return 0;
1248: }
1249:
1250: /* Report command usage. */
1251:
1252: static void
1253: uxusage ()
1254: {
1255: fprintf (stderr,
1256: "Taylor UUCP version %s, copyright (C) 1991, 1992 Ian Lance Taylor\n",
1257: VERSION);
1258: fprintf (stderr,
1259: "Usage: uux [options] [-] command\n");
1260: fprintf (stderr,
1261: " -,-p: Read standard input for standard input of command\n");
1262: fprintf (stderr,
1263: " -c: Do not copy local files to spool directory (default)\n");
1264: fprintf (stderr,
1265: " -C: Copy local files to spool directory\n");
1266: fprintf (stderr,
1267: " -l: link local files to spool directory\n");
1268: fprintf (stderr,
1269: " -g grade: Set job grade (must be alphabetic)\n");
1270: fprintf (stderr,
1271: " -n: Do not report completion status\n");
1272: fprintf (stderr,
1273: " -z: Report completion status only on error\n");
1274: fprintf (stderr,
1275: " -r: Do not start uucico daemon\n");
1276: fprintf (stderr,
1277: " -a address: Address to mail status report to\n");
1278: fprintf (stderr,
1279: " -b: Return standard input with status report\n");
1280: fprintf (stderr,
1281: " -s file: Report completion status to file\n");
1282: fprintf (stderr,
1283: " -j: Report job id\n");
1284: fprintf (stderr,
1285: " -x debug: Set debugging level\n");
1286: #if HAVE_TAYLOR_CONFIG
1287: fprintf (stderr,
1288: " -I file: Set configuration file to use\n");
1289: #endif /* HAVE_TAYLOR_CONFIG */
1290: exit (EXIT_FAILURE);
1291: }
1292:
1293: /* Add a line to the execute file. */
1294:
1295: static void
1296: uxadd_xqt_line (bchar, z1, z2)
1297: int bchar;
1298: const char *z1;
1299: const char *z2;
1300: {
1301: if (eXxqt_file == NULL)
1302: {
1303: eXxqt_file = esysdep_fopen (zXxqt_name, FALSE, FALSE, TRUE);
1304: if (eXxqt_file == NULL)
1305: uxabort ();
1306: }
1307:
1308: if (z1 == NULL)
1309: fprintf (eXxqt_file, "%c\n", bchar);
1310: else if (z2 == NULL)
1311: fprintf (eXxqt_file, "%c %s\n", bchar, z1);
1312: else
1313: fprintf (eXxqt_file, "%c %s %s\n", bchar, z1, z2);
1314: }
1315:
1316: /* Add a file to be sent to the execute system. */
1317:
1318: static void
1319: uxadd_send_file (zfrom, zto, zoptions, ztemp, zforward, qxqtsys, zxqtloc,
1320: bgrade)
1321: const char *zfrom;
1322: const char *zto;
1323: const char *zoptions;
1324: const char *ztemp;
1325: const char *zforward;
1326: const struct uuconf_system *qxqtsys;
1327: const char *zxqtloc;
1328: int bgrade;
1329: {
1330: struct scmd s;
1331:
1332: if (zforward != NULL)
1333: {
1334: char *zbase;
1335: char *zxqt;
1336: char abtname[CFILE_NAME_LEN];
1337: char abdname[CFILE_NAME_LEN];
1338: char abxname[CFILE_NAME_LEN];
1339: FILE *e;
1340:
1341: /* We want to forward this file through the first execution
1342: system to other systems. We set up a remote execution of
1343: uucp to forward the file. */
1344: zbase = zsysdep_base_name (zfrom);
1345: if (zbase == NULL)
1346: uxabort ();
1347:
1348: zxqt = zsysdep_data_file_name (qxqtsys, zxqtloc, bgrade, TRUE, abtname,
1349: abdname, abxname);
1350: if (zxqt == NULL)
1351: uxabort ();
1352: e = esysdep_fopen (zxqt, FALSE, FALSE, TRUE);
1353: if (e == NULL)
1354: uxabort ();
1355: uxrecord_file (zxqt);
1356:
1357: fprintf (e, "U %s %s\n", zsysdep_login_name (), zxqtloc);
1358: fprintf (e, "F %s %s\n", abdname, zbase);
1359: fprintf (e, "C uucp -C -W -d -g %c %s %s!%s\n",
1360: bgrade, zbase, zforward, zto);
1361:
1362: ubuffree (zbase);
1363:
1364: if (fclose (e) != 0)
1365: ulog (LOG_FATAL, "fclose: %s", strerror (errno));
1366:
1367: /* Send the execution file. */
1368: s.bcmd = 'S';
1369: s.pseq = NULL;
1370: s.zfrom = zbufcpy (abtname);
1371: s.zto = zbufcpy (abxname);
1372: s.zuser = zsysdep_login_name ();
1373: s.zoptions = "C";
1374: s.ztemp = s.zfrom;
1375: s.imode = 0666;
1376: s.znotify = NULL;
1377: s.cbytes = -1;
1378: s.zcmd = NULL;
1379: s.ipos = 0;
1380:
1381: ++cXcmds;
1382: pasXcmds = (struct scmd *) xrealloc ((pointer) pasXcmds,
1383: cXcmds * sizeof (struct scmd));
1384: pasXcmds[cXcmds - 1] = s;
1385:
1386: /* Send the data file to abdname where the execution file will
1387: expect it. */
1388: zto = abdname;
1389: }
1390:
1391: s.bcmd = 'S';
1392: s.pseq = NULL;
1393: s.zfrom = zbufcpy (zfrom);
1394: s.zto = zbufcpy (zto);
1395: s.zuser = zsysdep_login_name ();
1396: s.zoptions = zbufcpy (zoptions);
1397: s.ztemp = zbufcpy (ztemp);
1398: s.imode = 0666;
1399: s.znotify = "";
1400: s.cbytes = -1;
1401: s.zcmd = NULL;
1402: s.ipos = 0;
1403:
1404: ++cXcmds;
1405: pasXcmds = (struct scmd *) xrealloc ((pointer) pasXcmds,
1406: cXcmds * sizeof (struct scmd));
1407: pasXcmds[cXcmds - 1] = s;
1408: }
1409:
1410: /* Copy stdin to a file. This is a separate function because it may
1411: call setjmp. */
1412:
1413: static void
1414: uxcopy_stdin (e)
1415: FILE *e;
1416: {
1417: CATCH_PROTECT size_t cread;
1418: char ab[1024];
1419:
1420: do
1421: {
1422: size_t cwrite;
1423:
1424: /* I want to use fread here, but there is a bug in some versions
1425: of SVR4 which causes fread to return less than a complete
1426: buffer even if EOF has not been reached. This is not online
1427: time, so speed is not critical, but it's still quite annoying
1428: to have to use an inefficient algorithm. */
1429: cread = 0;
1430: if (fsysdep_catch ())
1431: {
1432: usysdep_start_catch ();
1433:
1434: while (cread < sizeof (ab))
1435: {
1436: int b;
1437:
1438: if (FGOT_SIGNAL ())
1439: uxabort ();
1440:
1441: /* There's an unimportant race here. If the user hits
1442: ^C between the FGOT_SIGNAL we just did and the time
1443: we enter getchar, we won't know about the signal
1444: (unless we're doing a longjmp, but we normally
1445: aren't). It's not a big problem, because the user
1446: can just hit ^C again. */
1447: b = getchar ();
1448: if (b == EOF)
1449: break;
1450: ab[cread] = b;
1451: ++cread;
1452: }
1453: }
1454:
1455: usysdep_end_catch ();
1456:
1457: if (FGOT_SIGNAL ())
1458: uxabort ();
1459:
1460: if (cread > 0)
1461: {
1462: cwrite = fwrite (ab, sizeof (char), cread, e);
1463: if (cwrite != cread)
1464: ulog (LOG_FATAL, "fwrite: Wrote %d when attempted %d",
1465: (int) cwrite, (int) cread);
1466: }
1467: }
1468: while (cread == sizeof ab);
1469: }
1470:
1471: /* Keep track of all files we have created so that we can delete them
1472: if we get a signal. The argument will be on the heap. */
1473:
1474: static int cXfiles;
1475: static const char **pXaz;
1476:
1477: static void
1478: uxrecord_file (zfile)
1479: const char *zfile;
1480: {
1481: pXaz = (const char **) xrealloc ((pointer) pXaz,
1482: (cXfiles + 1) * sizeof (const char *));
1483: pXaz[cXfiles] = zfile;
1484: ++cXfiles;
1485: }
1486:
1487: /* Delete all the files we have recorded and exit. */
1488:
1489: static void
1490: uxabort ()
1491: {
1492: int i;
1493:
1494: if (eXxqt_file != NULL)
1495: (void) fclose (eXxqt_file);
1496: if (eXclose != NULL)
1497: (void) fclose (eXclose);
1498: for (i = 0; i < cXfiles; i++)
1499: (void) remove (pXaz[i]);
1500: ulog_close ();
1501: usysdep_exit (FALSE);
1502: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.