xref: /illumos-gate/usr/src/cmd/bnu/uucp.c (revision 598f4ceed9327d2d6c2325dd67cae3aa06f7fea6)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 #include "uucp.h"
30 
31 /*
32  * uucp
33  * user id
34  * make a copy in spool directory
35  */
36 int Copy = 0;
37 static int _Transfer = 0;
38 char Nuser[32];
39 char *Ropt = " ";
40 char Optns[10];
41 char Uopts[BUFSIZ];
42 char Xopts[BUFSIZ];
43 char Sgrade[NAMESIZE];
44 int Mail = 0;
45 int Notify = 0;
46 
47 void cleanup(), ruux(), usage();
48 int eaccess(), guinfo(), vergrd(), gwd(), ckexpf(), uidstat(), uidxcp(),
49 	copy(), gtcfile();
50 void commitall(), wfabort(), mailst(), gename(), svcfile();
51 
52 char	Sfile[MAXFULLNAME];
53 
54 int
55 main(argc, argv, envp)
56 int argc;
57 char *argv[];
58 char	**envp;
59 {
60 	char *jid();
61 	int	ret;
62 	int	errors = 0;
63 	char	*fopt, *sys2p;
64 	char	sys1[MAXFULLNAME], sys2[MAXFULLNAME];
65 	char	fwd1[MAXFULLNAME], fwd2[MAXFULLNAME];
66 	char	file1[MAXFULLNAME], file2[MAXFULLNAME];
67 	short	jflag = 0;	/* -j flag  Jobid printout */
68 	extern int	split();
69 
70 
71 	/* Set locale environment variables local definitions */
72 	(void) setlocale(LC_ALL, "");
73 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
74 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it wasn't */
75 #endif
76 	(void) textdomain(TEXT_DOMAIN);
77 
78 	/* this fails in some versions, but it doesn't hurt */
79 	Uid = getuid();
80 	Euid = geteuid();
81 	if (Uid == 0)
82 		(void) setuid(UUCPUID);
83 
84 	/* choose LOGFILE */
85 	(void) strcpy(Logfile, LOGUUCP);
86 
87 	Env = envp;
88 	fopt = NULL;
89 	(void) strcpy(Progname, "uucp");
90 	Pchar = 'U';
91 	*Uopts = NULLCHAR;
92 	*Xopts = NULLCHAR;
93 	*Sgrade = NULLCHAR;
94 
95 	if (eaccess(GRADES, 0) != -1) {
96 		Grade = 'A';
97 		Sgrades = TRUE;
98 		sprintf(Sgrade, "%s", "default");
99 	}
100 
101 	/*
102 	 * find name of local system
103 	 */
104 	uucpname(Myname);
105 	Optns[0] = '-';
106 	Optns[1] = 'd';
107 	Optns[2] = 'c';
108 	Optns[3] = Nuser[0] = Sfile[0] = NULLCHAR;
109 
110 	/*
111 	 * find id of user who spawned command to
112 	 * determine
113 	 */
114 	(void) guinfo(Uid, User);
115 
116 	/*
117 	 * create/append command log
118 	 */
119 	commandlog(argc,argv);
120 
121 	while ((ret = getopt(argc, argv, "Ccdfg:jmn:rs:x:")) != EOF) {
122 		switch (ret) {
123 
124 		/*
125 		 * make a copy of the file in the spool
126 		 * directory.
127 		 */
128 		case 'C':
129 			Copy = 1;
130 			Optns[2] = 'C';
131 			break;
132 
133 		/*
134 		 * not used (default)
135 		 */
136 		case 'c':
137 			break;
138 
139 		/*
140 		 * not used (default)
141 		 */
142 		case 'd':
143 			break;
144 		case 'f':
145 			Optns[1] = 'f';
146 			break;
147 
148 		/*
149 		 * set service grade
150 		 */
151 		case 'g':
152 			snprintf(Xopts, sizeof (Xopts), "-g%s", optarg);
153 			if (!Sgrades) {
154 				if (strlen(optarg) < (size_t)2 && isalnum(*optarg))
155 					Grade = *optarg;
156 				else {
157 					(void) fprintf(stderr, gettext("No"
158 					    " administrator defined service"
159 					    " grades available on this"
160 					    " machine.\n"));
161 					(void) fprintf(stderr, gettext("UUCP"
162 					    " service grades range from"
163 					    " [A-Z][a-z] only.\n"));
164 					cleanup(-1);
165 				}
166 			}
167 			else {
168 				(void) strncpy(Sgrade, optarg, NAMESIZE-1);
169 				Sgrade[NAMESIZE-1] = NULLCHAR;
170 				if (vergrd(Sgrade) != SUCCESS)
171 					cleanup(FAIL);
172 			}
173 			break;
174 
175 		case 'j':	/* job id */
176 			jflag = 1;
177 			break;
178 
179 		/*
180 		 * send notification to local user
181 		 */
182 		case 'm':
183 			Mail = 1;
184 			(void) strcat(Optns, "m");
185 			break;
186 
187 		/*
188 		 * send notification to user on remote
189 		 * if no user specified do not send notification
190 		 */
191 		case 'n':
192 			/*
193 			 * We should add "n" option to Optns only once,
194 			 * even if multiple -n option are passed to uucp
195 			 */
196 			if (!Notify) {
197 				(void) strlcat(Optns, "n", sizeof (Optns));
198 				Notify = 1;
199 			}
200 			(void) sprintf(Nuser, "%.8s", optarg);
201 
202 			/*
203 			 * We do the copy multiple times when multiple
204 			 * -n options are specified, but
205 			 * only the last -n value is used.
206 	 		 */
207 			(void) snprintf(Uopts, sizeof (Uopts), "-n%s ", Nuser);
208 
209 			break;
210 
211 		/*
212 		 * create JCL files but do not start uucico
213 		 */
214 		case 'r':
215 			Ropt = "-r";
216 			break;
217 
218 		/*
219 		 * return status file
220 		 */
221 		case 's':
222 			fopt = optarg;
223 			/* "m" needed for compatability */
224 			(void) strcat(Optns, "mo");
225 			break;
226 
227 		/*
228 		 * turn on debugging
229 		 */
230 		case 'x':
231 			Debug = atoi(optarg);
232 			if (Debug <= 0)
233 				Debug = 1;
234 #ifdef SMALL
235 			fprintf(stderr, gettext("WARNING: uucp built with SMALL"
236 			    " flag defined -- no debug info available\n"));
237 #endif /* SMALL */
238 			break;
239 
240 		default:
241 			usage();
242 			break;
243 		}
244 	}
245 	DEBUG(4, "\n\n** %s **\n", "START");
246 	gwd(Wrkdir);
247 	if (fopt) {
248 		if (*fopt != '/')
249 			(void) snprintf(Sfile, MAXFULLNAME, "%s/%s",
250 					Wrkdir, fopt);
251 		else
252 			(void) snprintf(Sfile, MAXFULLNAME, "%s", fopt);
253 
254 	}
255 	else
256 		if (strlcpy(Sfile, "dummy", sizeof (Sfile)) >= sizeof (Sfile))
257 			return (2);
258 
259 	/*
260 	 * work in WORKSPACE directory
261 	 */
262 	ret = chdir(WORKSPACE);
263 	if (ret != 0) {
264 		(void) fprintf(stderr, gettext("No work directory - %s -"
265 		    " get help\n"), WORKSPACE);
266 		cleanup(-12);
267 	}
268 
269 	if (Nuser[0] == NULLCHAR)
270 		(void) strcpy(Nuser, User);
271 	(void) strcpy(Loginuser, User);
272 	DEBUG(4, "UID %ld, ", (long) Uid);
273 	DEBUG(4, "User %s\n", User);
274 	if (argc - optind < 2) {
275 		usage();
276 	}
277 
278 	/*
279 	 * set up "to" system and file names
280 	 */
281 
282 	(void) split(argv[argc - 1], sys2, fwd2, file2);
283 	if (*sys2 != NULLCHAR) {
284 		(void) strncpy(Rmtname, sys2, MAXBASENAME);
285 		Rmtname[MAXBASENAME] = NULLCHAR;
286 
287 		/* get real Myname - it depends on who I'm calling--Rmtname */
288 		(void) mchFind(Rmtname);
289 		myName(Myname);
290 
291 		if (versys(sys2) != 0) {
292 			(void) fprintf(stderr,
293 			    gettext("bad system: %s\n"), sys2);
294 			cleanup(-EX_NOHOST);
295 		}
296 	}
297 
298 	DEBUG(9, "sys2: %s, ", sys2);
299 	DEBUG(9, "fwd2: %s, ", fwd2);
300 	DEBUG(9, "file2: %s\n", file2);
301 
302 	/*
303 	 * if there are more than 2 argsc, file2 is a directory
304 	 */
305 	if (argc - optind > 2)
306 		(void) strcat(file2, "/");
307 
308 	/*
309 	 * do each from argument
310 	 */
311 
312 	for ( ; optind < argc - 1; optind++) {
313 	    (void) split(argv[optind], sys1, fwd1, file1);
314 	    if (*sys1 != NULLCHAR) {
315 		if (versys(sys1) != 0) {
316 			(void) fprintf(stderr,
317 			    gettext("bad system: %s\n"), sys1);
318 			cleanup(-EX_NOHOST);
319 		}
320 	    }
321 
322 	    /*  source files can have at most one ! */
323 	    if (*fwd1 != NULLCHAR) {
324 		/* syntax error */
325 	        (void) fprintf(stderr,
326 		    gettext("illegal  syntax %s\n"), argv[optind]);
327 	        exit(2);
328 	    }
329 
330 	    /*
331 	     * check for required remote expansion of file names -- generate
332 	     *	and execute a uux command
333 	     * e.g.
334 	     *		uucp   owl!~/dan/..  ~/dan/
335 	     *
336 	     * NOTE: The source file part must be full path name.
337 	     *  If ~ it will be expanded locally - it assumes the remote
338 	     *  names are the same.
339 	     */
340 
341 	    if (*sys1 != NULLCHAR)
342 		if ((strchr(file1, '*') != NULL
343 		      || strchr(file1, '?') != NULL
344 		      || strchr(file1, '[') != NULL)) {
345 		        /* do a uux command */
346 		        if (ckexpf(file1) == FAIL)
347 			    exit(6);
348 			(void) strncpy(Rmtname, sys1, MAXBASENAME);
349 			Rmtname[MAXBASENAME] = NULLCHAR;
350 			/* get real Myname - it depends on who I'm calling--Rmtname */
351 			(void) mchFind(Rmtname);
352 			myName(Myname);
353 			if (*sys2 == NULLCHAR)
354 			    sys2p = Myname;
355 		        ruux(sys1, sys1, file1, sys2p, fwd2, file2);
356 		        continue;
357 		}
358 
359 	    /*
360 	     * check for forwarding -- generate and execute a uux command
361 	     * e.g.
362 	     *		uucp uucp.c raven!owl!~/dan/
363 	     */
364 
365 	    if (*fwd2 != NULLCHAR) {
366 	        ruux(sys2, sys1, file1, "", fwd2, file2);
367 	        continue;
368 	    }
369 
370 	    /*
371 	     * check for both source and destination on other systems --
372 	     *  generate and execute a uux command
373 	     */
374 
375 	    if (*sys1 != NULLCHAR )
376 		if ( (!EQUALS(Myname, sys1))
377 	    	  && *sys2 != NULLCHAR
378 	    	  && (!EQUALS(sys2, Myname)) ) {
379 		    ruux(sys2, sys1, file1, "", fwd2, file2);
380 	            continue;
381 	        }
382 
383 
384 	    sys2p = sys2;
385 	    if (*sys1 == NULLCHAR) {
386 		if (*sys2 == NULLCHAR)
387 		    sys2p = Myname;
388 		(void) strcpy(sys1, Myname);
389 	    } else {
390 		(void) strncpy(Rmtname, sys1, MAXBASENAME);
391 		Rmtname[MAXBASENAME] = NULLCHAR;
392 		/* get real Myname - it depends on who I'm calling--Rmtname */
393 		(void) mchFind(Rmtname);
394 		myName(Myname);
395 		if (*sys2 == NULLCHAR)
396 		    sys2p = Myname;
397 	    }
398 
399 	    DEBUG(4, "sys1 - %s, ", sys1);
400 	    DEBUG(4, "file1 - %s, ", file1);
401 	    DEBUG(4, "Rmtname - %s\n", Rmtname);
402 	    if (copy(sys1, file1, sys2p, file2))
403 	    	errors++;
404 	}
405 
406 	/* move the work files to their proper places */
407 	commitall();
408 
409 	/*
410 	 * Wait for all background uux processes to finish so
411 	 * that our caller will know that we're done with all
412 	 * input files and it's safe to remove them.
413 	 */
414 	while (wait(NULL) != -1)
415 		;
416 
417 	/*
418 	 * do not spawn daemon if -r option specified
419 	 */
420 	if (*Ropt != '-') {
421 #ifndef	V7
422 		long	limit;
423 		char	msg[100];
424 		limit = ulimit(1, (long) 0);
425 		if (limit < MINULIMIT)  {
426 			(void) sprintf(msg,
427 			    "ULIMIT (%ld) < MINULIMIT (%ld)", limit, MINULIMIT);
428 			logent(msg, "Low-ULIMIT");
429 		}
430 		else
431 #endif
432 			xuucico(Rmtname);
433 	}
434 	if (jflag) {
435 		(void) strncpy(Jobid, jid(), NAMESIZE);
436 		printf("%s\n", Jobid);
437 	}
438 	cleanup(errors);
439 	/*NOTREACHED*/
440 	return (0);
441 }
442 
443 /*
444  * cleanup lock files before exiting
445  */
446 void
447 cleanup(code)
448 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 static FILE *syscfile();
473 /*
474  * generate copy files for s1!f1 -> s2!f2
475  *	Note: only one remote machine, other situations
476  *	have been taken care of in main.
477  * return:
478  *	0	-> success
479  * Non-zero     -> failure
480  */
481 int
482 copy(s1, f1, s2, f2)
483 char *s1, *f1, *s2, *f2;
484 {
485 	FILE *cfp;
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