xref: /titanic_52/usr/src/cmd/bnu/uucp.c (revision 84ab085a13f931bc78e7415e7ce921dbaa14fcb3)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 /*
26  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
27  * Use is subject to license terms.
28  */
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 #include "uucp.h"
33 
34 /*
35  * uucp
36  * user id
37  * make a copy in spool directory
38  */
39 int Copy = 0;
40 static int _Transfer = 0;
41 char Nuser[32];
42 char *Ropt = " ";
43 char Optns[10];
44 char Uopts[BUFSIZ];
45 char Xopts[BUFSIZ];
46 char Sgrade[NAMESIZE];
47 int Mail = 0;
48 int Notify = 0;
49 
50 void cleanup(), ruux(), usage();
51 int eaccess(), guinfo(), vergrd(), gwd(), ckexpf(), uidstat(), uidxcp(),
52 	copy(), gtcfile();
53 void commitall(), wfabort(), mailst(), gename(), svcfile();
54 
55 char	Sfile[MAXFULLNAME];
56 
57 main(argc, argv, envp)
58 char *argv[];
59 char	**envp;
60 {
61 	char *jid();
62 	int	ret;
63 	int	errors = 0;
64 	char	*fopt, *sys2p;
65 	char	sys1[MAXFULLNAME], sys2[MAXFULLNAME];
66 	char	fwd1[MAXFULLNAME], fwd2[MAXFULLNAME];
67 	char	file1[MAXFULLNAME], file2[MAXFULLNAME];
68 	short	jflag = 0;	/* -j flag  Jobid printout */
69 	extern int	split();
70 
71 
72 	/* Set locale environment variables local definitions */
73 	(void) setlocale(LC_ALL, "");
74 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
75 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it wasn't */
76 #endif
77 	(void) textdomain(TEXT_DOMAIN);
78 
79 	/* this fails in some versions, but it doesn't hurt */
80 	Uid = getuid();
81 	Euid = geteuid();
82 	if (Uid == 0)
83 		(void) setuid(UUCPUID);
84 
85 	/* choose LOGFILE */
86 	(void) strcpy(Logfile, LOGUUCP);
87 
88 	Env = envp;
89 	fopt = NULL;
90 	(void) strcpy(Progname, "uucp");
91 	Pchar = 'U';
92 	*Uopts = NULLCHAR;
93 	*Xopts = NULLCHAR;
94 	*Sgrade = NULLCHAR;
95 
96 	if (eaccess(GRADES, 0) != -1) {
97 		Grade = 'A';
98 		Sgrades = TRUE;
99 		sprintf(Sgrade, "%s", "default");
100 	}
101 
102 	/*
103 	 * find name of local system
104 	 */
105 	uucpname(Myname);
106 	Optns[0] = '-';
107 	Optns[1] = 'd';
108 	Optns[2] = 'c';
109 	Optns[3] = Nuser[0] = Sfile[0] = NULLCHAR;
110 
111 	/*
112 	 * find id of user who spawned command to
113 	 * determine
114 	 */
115 	(void) guinfo(Uid, User);
116 
117 	/*
118 	 * create/append command log
119 	 */
120 	commandlog(argc,argv);
121 
122 	while ((ret = getopt(argc, argv, "Ccdfg:jmn:rs:x:")) != EOF) {
123 		switch (ret) {
124 
125 		/*
126 		 * make a copy of the file in the spool
127 		 * directory.
128 		 */
129 		case 'C':
130 			Copy = 1;
131 			Optns[2] = 'C';
132 			break;
133 
134 		/*
135 		 * not used (default)
136 		 */
137 		case 'c':
138 			break;
139 
140 		/*
141 		 * not used (default)
142 		 */
143 		case 'd':
144 			break;
145 		case 'f':
146 			Optns[1] = 'f';
147 			break;
148 
149 		/*
150 		 * set service grade
151 		 */
152 		case 'g':
153 			snprintf(Xopts, sizeof (Xopts), "-g%s", optarg);
154 			if (!Sgrades) {
155 				if (strlen(optarg) < (size_t)2 && isalnum(*optarg))
156 					Grade = *optarg;
157 				else {
158 					(void) fprintf(stderr, gettext("No"
159 					    " administrator defined service"
160 					    " grades available on this"
161 					    " machine.\n"));
162 					(void) fprintf(stderr, gettext("UUCP"
163 					    " service grades range from"
164 					    " [A-Z][a-z] only.\n"));
165 					cleanup(-1);
166 				}
167 			}
168 			else {
169 				(void) strncpy(Sgrade, optarg, NAMESIZE-1);
170 				Sgrade[NAMESIZE-1] = NULLCHAR;
171 				if (vergrd(Sgrade) != SUCCESS)
172 					cleanup(FAIL);
173 			}
174 			break;
175 
176 		case 'j':	/* job id */
177 			jflag = 1;
178 			break;
179 
180 		/*
181 		 * send notification to local user
182 		 */
183 		case 'm':
184 			Mail = 1;
185 			(void) strcat(Optns, "m");
186 			break;
187 
188 		/*
189 		 * send notification to user on remote
190 		 * if no user specified do not send notification
191 		 */
192 		case 'n':
193 			/*
194 			 * We should add "n" option to Optns only once,
195 			 * even if multiple -n option are passed to uucp
196 			 */
197 			if (!Notify) {
198 				(void) strlcat(Optns, "n", sizeof (Optns));
199 				Notify = 1;
200 			}
201 			(void) sprintf(Nuser, "%.8s", optarg);
202 
203 			/*
204 			 * We do the copy multiple times when multiple
205 			 * -n options are specified, but
206 			 * only the last -n value is used.
207 	 		 */
208 			(void) snprintf(Uopts, sizeof (Uopts), "-n%s ", Nuser);
209 
210 			break;
211 
212 		/*
213 		 * create JCL files but do not start uucico
214 		 */
215 		case 'r':
216 			Ropt = "-r";
217 			break;
218 
219 		/*
220 		 * return status file
221 		 */
222 		case 's':
223 			fopt = optarg;
224 			/* "m" needed for compatability */
225 			(void) strcat(Optns, "mo");
226 			break;
227 
228 		/*
229 		 * turn on debugging
230 		 */
231 		case 'x':
232 			Debug = atoi(optarg);
233 			if (Debug <= 0)
234 				Debug = 1;
235 #ifdef SMALL
236 			fprintf(stderr, gettext("WARNING: uucp built with SMALL"
237 			    " flag defined -- no debug info available\n"));
238 #endif /* SMALL */
239 			break;
240 
241 		default:
242 			usage();
243 			break;
244 		}
245 	}
246 	DEBUG(4, "\n\n** %s **\n", "START");
247 	gwd(Wrkdir);
248 	if (fopt) {
249 		if (*fopt != '/')
250 			(void) snprintf(Sfile, MAXFULLNAME, "%s/%s",
251 					Wrkdir, fopt);
252 		else
253 			(void) snprintf(Sfile, MAXFULLNAME, "%s", fopt);
254 
255 	}
256 	else
257 		if (strlcpy(Sfile, "dummy", sizeof (Sfile)) >= sizeof (Sfile))
258 			return (2);
259 
260 	/*
261 	 * work in WORKSPACE directory
262 	 */
263 	ret = chdir(WORKSPACE);
264 	if (ret != 0) {
265 		(void) fprintf(stderr, gettext("No work directory - %s -"
266 		    " get help\n"), WORKSPACE);
267 		cleanup(-12);
268 	}
269 
270 	if (Nuser[0] == NULLCHAR)
271 		(void) strcpy(Nuser, User);
272 	(void) strcpy(Loginuser, User);
273 	DEBUG(4, "UID %ld, ", (long) Uid);
274 	DEBUG(4, "User %s\n", User);
275 	if (argc - optind < 2) {
276 		usage();
277 	}
278 
279 	/*
280 	 * set up "to" system and file names
281 	 */
282 
283 	(void) split(argv[argc - 1], sys2, fwd2, file2);
284 	if (*sys2 != NULLCHAR) {
285 		(void) strncpy(Rmtname, sys2, MAXBASENAME);
286 		Rmtname[MAXBASENAME] = NULLCHAR;
287 
288 		/* get real Myname - it depends on who I'm calling--Rmtname */
289 		(void) mchFind(Rmtname);
290 		myName(Myname);
291 
292 		if (versys(sys2) != 0) {
293 			(void) fprintf(stderr,
294 			    gettext("bad system: %s\n"), sys2);
295 			cleanup(-EX_NOHOST);
296 		}
297 	}
298 
299 	DEBUG(9, "sys2: %s, ", sys2);
300 	DEBUG(9, "fwd2: %s, ", fwd2);
301 	DEBUG(9, "file2: %s\n", file2);
302 
303 	/*
304 	 * if there are more than 2 argsc, file2 is a directory
305 	 */
306 	if (argc - optind > 2)
307 		(void) strcat(file2, "/");
308 
309 	/*
310 	 * do each from argument
311 	 */
312 
313 	for ( ; optind < argc - 1; optind++) {
314 	    (void) split(argv[optind], sys1, fwd1, file1);
315 	    if (*sys1 != NULLCHAR) {
316 		if (versys(sys1) != 0) {
317 			(void) fprintf(stderr,
318 			    gettext("bad system: %s\n"), sys1);
319 			cleanup(-EX_NOHOST);
320 		}
321 	    }
322 
323 	    /*  source files can have at most one ! */
324 	    if (*fwd1 != NULLCHAR) {
325 		/* syntax error */
326 	        (void) fprintf(stderr,
327 		    gettext("illegal  syntax %s\n"), argv[optind]);
328 	        exit(2);
329 	    }
330 
331 	    /*
332 	     * check for required remote expansion of file names -- generate
333 	     *	and execute a uux command
334 	     * e.g.
335 	     *		uucp   owl!~/dan/..  ~/dan/
336 	     *
337 	     * NOTE: The source file part must be full path name.
338 	     *  If ~ it will be expanded locally - it assumes the remote
339 	     *  names are the same.
340 	     */
341 
342 	    if (*sys1 != NULLCHAR)
343 		if ((strchr(file1, '*') != NULL
344 		      || strchr(file1, '?') != NULL
345 		      || strchr(file1, '[') != NULL)) {
346 		        /* do a uux command */
347 		        if (ckexpf(file1) == FAIL)
348 			    exit(6);
349 			(void) strncpy(Rmtname, sys1, MAXBASENAME);
350 			Rmtname[MAXBASENAME] = NULLCHAR;
351 			/* get real Myname - it depends on who I'm calling--Rmtname */
352 			(void) mchFind(Rmtname);
353 			myName(Myname);
354 			if (*sys2 == NULLCHAR)
355 			    sys2p = Myname;
356 		        ruux(sys1, sys1, file1, sys2p, fwd2, file2);
357 		        continue;
358 		}
359 
360 	    /*
361 	     * check for forwarding -- generate and execute a uux command
362 	     * e.g.
363 	     *		uucp uucp.c raven!owl!~/dan/
364 	     */
365 
366 	    if (*fwd2 != NULLCHAR) {
367 	        ruux(sys2, sys1, file1, "", fwd2, file2);
368 	        continue;
369 	    }
370 
371 	    /*
372 	     * check for both source and destination on other systems --
373 	     *  generate and execute a uux command
374 	     */
375 
376 	    if (*sys1 != NULLCHAR )
377 		if ( (!EQUALS(Myname, sys1))
378 	    	  && *sys2 != NULLCHAR
379 	    	  && (!EQUALS(sys2, Myname)) ) {
380 		    ruux(sys2, sys1, file1, "", fwd2, file2);
381 	            continue;
382 	        }
383 
384 
385 	    sys2p = sys2;
386 	    if (*sys1 == NULLCHAR) {
387 		if (*sys2 == NULLCHAR)
388 		    sys2p = Myname;
389 		(void) strcpy(sys1, Myname);
390 	    } else {
391 		(void) strncpy(Rmtname, sys1, MAXBASENAME);
392 		Rmtname[MAXBASENAME] = NULLCHAR;
393 		/* get real Myname - it depends on who I'm calling--Rmtname */
394 		(void) mchFind(Rmtname);
395 		myName(Myname);
396 		if (*sys2 == NULLCHAR)
397 		    sys2p = Myname;
398 	    }
399 
400 	    DEBUG(4, "sys1 - %s, ", sys1);
401 	    DEBUG(4, "file1 - %s, ", file1);
402 	    DEBUG(4, "Rmtname - %s\n", Rmtname);
403 	    if (copy(sys1, file1, sys2p, file2))
404 	    	errors++;
405 	}
406 
407 	/* move the work files to their proper places */
408 	commitall();
409 
410 	/*
411 	 * Wait for all background uux processes to finish so
412 	 * that our caller will know that we're done with all
413 	 * input files and it's safe to remove them.
414 	 */
415 	while (wait(NULL) != -1)
416 		;
417 
418 	/*
419 	 * do not spawn daemon if -r option specified
420 	 */
421 	if (*Ropt != '-') {
422 #ifndef	V7
423 		long	limit;
424 		char	msg[100];
425 		limit = ulimit(1, (long) 0);
426 		if (limit < MINULIMIT)  {
427 			(void) sprintf(msg,
428 			    "ULIMIT (%ld) < MINULIMIT (%ld)", limit, MINULIMIT);
429 			logent(msg, "Low-ULIMIT");
430 		}
431 		else
432 #endif
433 			xuucico(Rmtname);
434 	}
435 	if (jflag) {
436 		(void) strncpy(Jobid, jid(), NAMESIZE);
437 		printf("%s\n", Jobid);
438 	}
439 	cleanup(errors);
440 	/*NOTREACHED*/
441 }
442 
443 /*
444  * cleanup lock files before exiting
445  */
446 void
447 cleanup(code)
448 register int	code;
449 {
450 	static int first = 1;
451 
452 	if (first) {
453 		first = 0;
454 		rmlock(CNULL);
455 		if (code != 0)
456 			wfabort();  /* this may be extreme -- abort all work */
457 	}
458 	if (code < 0) {
459 	       (void) fprintf(stderr,
460 		   gettext("uucp failed completely (%d)\n"), (-code));
461 		exit(-code);
462 	}
463 	else if (code > 0) {
464 		(void) fprintf(stderr, gettext(
465 		    "uucp failed partially: %d file(s) sent; %d error(s)\n"),
466 		 _Transfer, code);
467 		exit(code);
468 	}
469 	exit(code);
470 }
471 
472 /*
473  * generate copy files for s1!f1 -> s2!f2
474  *	Note: only one remote machine, other situations
475  *	have been taken care of in main.
476  * return:
477  *	0	-> success
478  * Non-zero     -> failure
479  */
480 int
481 copy(s1, f1, s2, f2)
482 char *s1, *f1, *s2, *f2;
483 {
484 	FILE *cfp;
485 	static FILE *syscfile();
486 	struct stat stbuf, stbuf1;
487 	int type, statret;
488 	char dfile[NAMESIZE];
489 	char cfile[NAMESIZE];
490 	char command[10+(2*MAXFULLNAME)];
491 	char file1[MAXFULLNAME], file2[MAXFULLNAME];
492 	char msg[BUFSIZ];
493 
494 	type = 0;
495 	(void) strcpy(file1, f1);
496 	(void) strcpy(file2, f2);
497 	if (!EQUALS(s1, Myname))
498 		type = 1;
499 	if (!EQUALS(s2, Myname))
500 		type = 2;
501 
502 	DEBUG(4, "copy: file1=<%s> ", file1);
503 	DEBUG(4, "file2=<%s>\n", file2);
504 	switch (type) {
505 	case 0:
506 
507 		/*
508 		 * all work here
509 		 */
510 		DEBUG(4, "all work here %d\n", type);
511 
512 		/*
513 		 * check access control permissions
514 		 */
515 		if (ckexpf(file1))
516 			 return(-6);
517 		if (ckexpf(file2))
518 			 return(-7);
519 
520 		setuid(Uid);
521 		if (chkperm(file1, file2, strchr(Optns, 'd')) &&
522 		    (access(file2, W_OK) == -1)) {
523 			(void) fprintf(stderr, gettext("permission denied\n"));
524 			cleanup(1);
525 		}
526 
527 		/*
528 		 * copy file locally
529 		 *
530 		 * Changed from uidxcp() to fic file made and owner
531 		 * being modified for existing files, and local file
532 		 * name expansion.
533 		 */
534 		DEBUG(2, "local copy: %s -> ", file1);
535 		DEBUG(2, "%s\n", file2);
536 
537 		sprintf(command, "cp %s %s", file1, file2);
538 		if ((cfp = popen(command, "r")) == NULL) {
539 			perror("popen");
540 			DEBUG(5, "popen failed - errno %d\n", errno);
541 			setuid(Euid);
542 			return (FAIL);
543 		}
544 		if (pclose(cfp) != 0) {
545 			DEBUG(5, "Copy failed - errno %d\n", errno);
546 			return (FAIL);
547 		}
548 		setuid(Euid);
549 
550 		/*
551 		 * if user specified -m, notify "local" user
552 		 */
553 		 if ( Mail ) {
554 		 	sprintf(msg,
555 		 	"REQUEST: %s!%s --> %s!%s (%s)\n(SYSTEM %s) copy succeeded\n",
556 		 	s1, file1, s2, file2, User, s2 );
557 		 	mailst(User, "copy succeeded", msg, "", "");
558 		}
559 		/*
560 		 * if user specified -n, notify "remote" user
561 		 */
562 		if ( Notify ) {
563 			sprintf(msg, "%s from %s!%s arrived\n",
564 				file2, s1, User );
565 			mailst(Nuser, msg, msg, "", "");
566 		}
567 		return(0);
568 	case 1:
569 
570 		/*
571 		 * receive file
572 		 */
573 		DEBUG(4, "receive file - %d\n", type);
574 
575 		/*
576 		 * expand source and destination file names
577 		 * and check access permissions
578 		 */
579 		if (file1[0] != '~')
580 			if (ckexpf(file1))
581 				 return(6);
582 		if (ckexpf(file2))
583 			 return(7);
584 
585 
586 		gename(DATAPRE, s2, Grade, dfile);
587 
588 		/*
589 		 * insert JCL card in file
590 		 */
591 		cfp = syscfile(cfile, s1);
592 		(void) fprintf(cfp,
593 	       	"R %s %s %s %s %s %o %s %s\n", file1, file2,
594 			User, Optns,
595 			*Sfile ? Sfile : "dummy",
596 			0777, Nuser, dfile);
597 		(void) fclose(cfp);
598 		(void) sprintf(msg, "%s!%s --> %s!%s", Rmtname, file1,
599 		    Myname, file2);
600 		logent(msg, "QUEUED");
601 		break;
602 	case 2:
603 
604 		/*
605 		 * send file
606 		 */
607 		if (ckexpf(file1))
608 			 return(6);
609 		/* XQTDIR hook enables 3rd party uux requests (cough) */
610 		DEBUG(4, "Workdir = <%s>\n", Wrkdir);
611 		if (file2[0] != '~' && !EQUALS(Wrkdir, XQTDIR))
612 			if (ckexpf(file2))
613 				 return(7);
614 		DEBUG(4, "send file - %d\n", type);
615 
616 		if (uidstat(file1, &stbuf) != 0) {
617 			(void) fprintf(stderr,
618 			    gettext("can't get status for file %s\n"), file1);
619 			return(8);
620 		}
621 		if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
622 			(void) fprintf(stderr,
623 			    gettext("directory name illegal - %s\n"), file1);
624 			return(9);
625 		}
626 		/* see if I can read this file as read uid, gid */
627 		if (access(file1, R_OK) != 0) {
628 			(void) fprintf(stderr,
629 			    gettext("uucp can't read (%s) mode (%o)\n"),
630 			    file1, stbuf.st_mode&0777);
631 			return(3);
632 		}
633 
634 		/*
635 		 * make a copy of file in spool directory
636 		 */
637 
638 		gename(DATAPRE, s2, Grade, dfile);
639 
640 		if (Copy || !READANY(file1) ) {
641 
642 			if (uidxcp(file1, dfile))
643 			    return(5);
644 
645 			(void) chmod(dfile, DFILEMODE);
646 		}
647 
648 		cfp = syscfile(cfile, s2);
649 		(void) fprintf(cfp, "S %s %s %s %s %s %lo %s %s\n",
650 		    file1, file2, User, Optns, dfile,
651 		    (long) stbuf.st_mode & LEGALMODE, Nuser, Sfile);
652 		(void) fclose(cfp);
653 		(void) sprintf(msg, "%s!%s --> %s!%s", Myname, file1,
654 		    Rmtname, file2);
655 		logent(msg, "QUEUED");
656 		break;
657 	}
658 	_Transfer++;
659 	return(0);
660 }
661 
662 
663 /*
664  *	syscfile(file, sys)
665  *	char	*file, *sys;
666  *
667  *	get the cfile for system sys (creat if need be)
668  *	return stream pointer
669  *
670  *	returns
671  *		stream pointer to open cfile
672  *
673  */
674 
675 static FILE	*
676 syscfile(file, sys)
677 char	*file, *sys;
678 {
679 	FILE	*cfp;
680 
681 	if (gtcfile(file, sys) == FAIL) {
682 		gename(CMDPRE, sys, Grade, file);
683 		ASSERT(access(file, 0) != 0, Fl_EXISTS, file, errno);
684 		cfp = fdopen(creat(file, CFILEMODE), "w");
685 		svcfile(file, sys, Sgrade);
686 	} else
687 		cfp = fopen(file, "a");
688 	ASSERT(cfp != NULL, Ct_OPEN, file, errno);
689 	return(cfp);
690 }
691 
692 
693 /*
694  * generate and execute a uux command
695  */
696 
697 void
698 ruux(rmt, sys1, file1, sys2, fwd2, file2)
699 char *rmt, *sys1, *file1, *sys2, *fwd2, *file2;
700 {
701     char cmd[BUFSIZ];
702     char xcmd[BUFSIZ];
703     char * xarg[6];
704     int narg = 0;
705     int i;
706 
707     /* get real Myname - it depends on who I'm calling--rmt */
708     (void) mchFind(rmt);
709     myName(Myname);
710 
711     xarg[narg++] = UUX;
712     xarg[narg++] = "-C";
713     if (*Xopts != NULLCHAR)
714 	xarg[narg++] = Xopts;
715     if (*Ropt  != ' ')
716 	xarg[narg++] = Ropt;
717 
718     (void) sprintf(cmd, "%s!uucp -C", rmt);
719 
720     if (*Uopts != NULLCHAR)
721 	(void) sprintf(cmd+strlen(cmd), " (%s) ", Uopts);
722 
723     if (*sys1 == NULLCHAR || EQUALS(sys1, Myname)) {
724         if (ckexpf(file1))
725   	    exit(6);
726 	(void) sprintf(cmd+strlen(cmd), " %s!%s ", sys1, file1);
727     }
728     else
729 	if (!EQUALS(rmt, sys1))
730 	    (void) sprintf(cmd+strlen(cmd), " (%s!%s) ", sys1, file1);
731 	else
732 	    (void) sprintf(cmd+strlen(cmd), " (%s) ", file1);
733 
734     if (*fwd2 != NULLCHAR) {
735 	if (*sys2 != NULLCHAR)
736 	    (void) sprintf(cmd+strlen(cmd),
737 		" (%s!%s!%s) ", sys2, fwd2, file2);
738 	else
739 	    (void) sprintf(cmd+strlen(cmd), " (%s!%s) ", fwd2, file2);
740     }
741     else {
742 	if (*sys2 == NULLCHAR || EQUALS(sys2, Myname))
743 	    if (ckexpf(file2))
744 		exit(7);
745 	(void) sprintf(cmd+strlen(cmd), " (%s!%s) ", sys2, file2);
746     }
747 
748     xarg[narg++] = cmd;
749     xarg[narg] = (char *) 0;
750 
751     xcmd[0] = NULLCHAR;
752     for (i=0; i < narg; i++) {
753 	strcat(xcmd, xarg[i]);
754 	strcat(xcmd, " ");
755     }
756     DEBUG(2, "cmd: %s\n", xcmd);
757     logent(xcmd, "QUEUED");
758 
759     if (fork() == 0) {
760 	ASSERT(setuid(getuid()) == 0, "setuid", "failed", 99);
761 	execv(UUX, xarg);
762 	exit(0);
763     }
764     return;
765 }
766 
767 void
768 usage()
769 {
770 
771 	(void) fprintf(stderr, gettext(
772 	"Usage:  %s [-c|-C] [-d|-f] [-g GRADE] [-jm] [-n USER]\\\n"
773 	"[-r] [-s FILE] [-x DEBUG_LEVEL] source-files destination-file\n"),
774 	Progname);
775 	cleanup(-2);
776 }
777