xref: /illumos-gate/usr/src/cmd/bnu/uuxqt.c (revision 6451fdbca2f79129a3a09d2fe3f6dd4d062bebff)
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 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 #include "uucp.h"
33 #include "log.h"
34 
35 /*
36  * execute commands set up by a uux command,
37  * usually from a remote machine - set by uucp.
38  */
39 
40 #ifndef	V7
41 #define	LOGNAME	"LOGNAME=uucp"
42 #else
43 #define	LOGNAME	"USER=uucp"
44 #endif
45 
46 #define	C_COMMAND	1
47 #define	C_FILE		2
48 #define	BAD_COMMAND	1
49 #define	BAD_FILE	2
50 #define	USAGEPREFIX	"Usage:"
51 #define	USAGE		"[-x DEBUG] [-s SYSTEM]"
52 
53 char	_Xfile[MAXFULLNAME];
54 char	_Cmd[2 * BUFSIZ];	/* build up command buffer */
55 int	_CargType;		/* argument type of next C argument */
56 
57 static void retosndr(), uucpst();
58 static int chkFile();
59 static int doFileChk();
60 void cleanup(), xprocess();
61 
62 int
63 main(argc, argv, envp)
64 int argc;
65 char *argv[];
66 char *envp[];
67 {
68 	DIR	 *fp1;
69 	struct	limits limitval;
70 	int	ret, maxnumb;
71 	char	dirname[MAXFULLNAME], lockname[MAXFULLNAME];
72 	void	onintr();
73 
74 	/* Set locale environment variables local definitions */
75 	(void) setlocale(LC_ALL, "");
76 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
77 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it wasn't */
78 #endif
79 	(void) textdomain(TEXT_DOMAIN);
80 
81 	(void) signal(SIGILL, onintr);
82 	(void) signal(SIGTRAP, onintr);
83 	(void) signal(SIGIOT, onintr);
84 	(void) signal(SIGEMT, onintr);
85 	(void) signal(SIGFPE, onintr);
86 	(void) signal(SIGBUS, onintr);
87 	(void) signal(SIGSEGV, onintr);
88 	(void) signal(SIGSYS, onintr);
89 	(void) signal(SIGPIPE, onintr);
90 	(void) signal(SIGTERM, SIG_IGN);
91 
92 	/* choose LOGFILE */
93 	(void) strcpy(Logfile, LOGUUXQT);
94 
95 	/*
96 	 * get local system name
97 	 */
98 	Env = envp;
99 	Nstat.t_qtime = time((time_t *)0);
100 	(void) strcpy(Progname, "uuxqt");
101 	Pchar = 'Q';
102 	uucpname(Myname);
103 	Ofn = 1;
104 	Ifn = 0;
105 	dirname[0] = dirname[MAXFULLNAME-1] = NULLCHAR;
106 	while ((ret = getopt(argc, argv, "s:x:")) != EOF) {
107 		switch (ret) {
108 
109 		/*
110 		 * debugging level
111 		 */
112 		case 'x':
113 			Debug = atoi(optarg);
114 			if (Debug <= 0)
115 				Debug = 1;
116 			break;
117 
118 		case 's':
119 			/*
120 			 * fake out uuxqt and use the argument as if
121 			 * it were the spool directory for the purpose
122 			 * of determining what subdirectories to search
123 			 *  EX:	mkdir /tmp/foo; touch /tmp/foo/[baz, gorp]
124 			 *	uuxqt -s/tmp/foo
125 			 * this will cause uuxqt to only run on the sub
126 			 * baz and gorp in the Spool directory.  Trust me.
127 			 */
128 			(void) strlcpy(dirname, optarg,
129 			    (MAXFULLNAME - sizeof (SEQLOCK)));
130 			break;
131 
132 		default:
133 			(void) fprintf(stderr, "%s %s %s\n",
134 			    gettext(USAGEPREFIX), Progname, gettext(USAGE));
135 			exit(1);
136 		}
137 	}
138 	if (argc != optind) {
139 		(void) fprintf(stderr, "%s %s %s\n",
140 		    gettext(USAGEPREFIX), Progname, gettext(USAGE));
141 		exit(1);
142 	}
143 
144 	DEBUG(4, "\n\n** START **\n%s", "");
145 	acInit("rexe");
146 	scInit("rexe");
147 	if (scanlimit("uuxqt", &limitval) == FAIL) {
148 	    DEBUG(1, "No limits for uuxqt in %s\n", LIMITS);
149 	} else {
150 	    maxnumb = limitval.totalmax;
151 	    if (maxnumb < 0) {
152 		DEBUG(4, "Non-positive limit for uuxqt in %s\n", LIMITS);
153 		DEBUG(1, "No limits for uuxqt\n%s", "");
154 	    } else {
155 		DEBUG(4, "Uuxqt limit %d -- ", maxnumb);
156 		ret = cuantos(X_LOCKPRE, X_LOCKDIR);
157 		DEBUG(4, "found %d -- ", ret);
158 		if (maxnumb >= 0 && ret >= maxnumb) {
159 		    DEBUG(4, "exiting.%s\n", "");
160 		    exit(0);
161 		}
162 		DEBUG(4, "continuing.%s\n", "");
163 	    }
164 	}
165 
166 	/*
167 	 * determine user who started uuxqt (in principle)
168 	 */
169 	strcpy(User, "uucp");	/* in case all else fails (can't happen) */
170 	Uid = getuid();
171 	Euid = geteuid();	/* this should be UUCPUID */
172 	guinfo(Euid, User);
173 
174 	if (Uid == 0)
175 		(void) setuid(UUCPUID);
176 
177 	setuucp(User);
178 	DEBUG(4, "User - %s\n", User);
179 	guinfo(Uid, Loginuser);
180 
181 
182 
183 	DEBUG(4, "process\n%s", "");
184 
185 	fp1 = opendir(Spool);
186 	ASSERT(fp1 != NULL, Ct_OPEN, Spool, errno);
187 	if (dirname[0] != NULLCHAR) {
188 		/* look for special characters in remote name */
189 		if (strpbrk(dirname, Shchar) != NULL) {
190 		    /* ignore naughty name */
191 		    DEBUG(4, "Bad remote name '%s'", dirname);
192 		    errent("BAD REMOTE NAME", dirname, 0, __FILE__, __LINE__);
193 		    closedir(fp1);
194 		    cleanup(101);
195 		}
196 
197 
198 		(void) snprintf(lockname, sizeof (lockname), "%s.%s",
199 		    X_LOCK, dirname);
200 		if (mklock(lockname) == SUCCESS) {
201 			xprocess(dirname);
202 			rmlock(CNULL);
203 		}
204 	} else {
205 		while (gdirf(fp1, dirname, Spool) == TRUE) {
206 			if (strpbrk(dirname, Shchar) != NULL) {
207 				/* skip naughty names */
208 				errent("BAD REMOTE NAME", dirname, 0,
209 				    __FILE__, __LINE__);
210 				continue;
211 			}
212 			(void) snprintf(lockname, sizeof (lockname), "%s.%s",
213 			    X_LOCK, dirname);
214 			if (mklock(lockname) != SUCCESS)
215 				continue;
216 			xprocess(dirname);
217 			rmlock(CNULL);
218 		}
219 	}
220 
221 	closedir(fp1);
222 	cleanup(0);
223 	/* NOTREACHED */
224 	return (0);
225 }
226 
227 void
228 cleanup(code)
229 int	code;
230 {
231 	rmlock(CNULL);
232 	exit(code);
233 }
234 
235 /*
236  * catch signal then cleanup and exit
237  */
238 void
239 onintr(inter)
240 int	inter;
241 {
242 	char	str[30];
243 	(void) signal(inter, SIG_IGN);
244 	(void) sprintf(str, "QSIGNAL %d", inter);
245 	logent(str, "QCAUGHT");
246 	acEndexe(cpucycle(), PARTIAL);	/* stop collecting accounting log */
247 	cleanup(-inter);
248 }
249 
250 #define	XCACHESIZE (4096 / (MAXBASENAME + 1))
251 static char	xcache[XCACHESIZE][MAXBASENAME + 1];	/* cache for X. files */
252 static int	xcachesize = 0;			/* how many left? */
253 
254 /*
255  * stash an X. file so we can process them sorted first by grade, then by
256  * sequence number
257  */
258 static void
259 xstash(file)
260 char	*file;
261 {
262 	if (xcachesize < XCACHESIZE) {
263 		DEBUG(4, "stashing %s\n", file);
264 		(void) strlcpy(xcache[xcachesize++], file, (MAXBASENAME + 1));
265 	}
266 }
267 
268 /*
269  * xcompare
270  *	comparison routine for for qsort()
271  */
272 static int
273 xcompare(f1, f2)
274 const void	*f1, *f2;
275 {
276 	/* assumes file name is X.siteG1234 */
277 	/* use -strcmp() so that xstash is sorted largest first */
278 	/* pull files out of the stash from largest index to smallest */
279 
280 	return (-strcmp((char *)f1 + strlen((char *)f1) - 5,
281 	    (char *)f2 + strlen((char *)f2) - 5));
282 }
283 
284 /*
285  * xsort
286  *	sort the cached X. files,
287  *	largest (last) to smallest (next to be processed)
288  */
289 static void
290 xsort()
291 {
292 	DEBUG(4, "xsort:  first was %s\n", xcache[0]);
293 	qsort(xcache, xcachesize, MAXBASENAME + 1, xcompare);
294 	DEBUG(4, "xsort:  first is %s\n", xcache[0]);
295 }
296 
297 /*
298  * xget
299  *	return smallest X. file in cache
300  *	(hint:  it's the last one in the array)
301  */
302 static int
303 xget(file)
304 char	*file;
305 {
306 	if (xcachesize > 0) {
307 		strlcpy(file, xcache[--xcachesize], (MAXBASENAME + 1));
308 		DEBUG(4, "xget: returning %s\n", file);
309 		return (1);
310 	} else {
311 		/* avoid horror of xcachesize < 0 (impossible, you say?)! */
312 		xcachesize = 0;
313 		return (0);
314 	}
315 }
316 
317 
318 /*
319  * get a file to execute
320  *	file	-> a read to return filename in
321  * returns:
322  *	0	-> no file
323  *	1	-> file to execute
324  */
325 int
326 gt_Xfile(file, dir)
327 char	*file, *dir;
328 {
329 	DIR *pdir;
330 
331 	if (xcachesize == 0) {
332 		/* open spool directory */
333 		pdir = opendir(dir);
334 		/* this was an ASSERT, but it's not so bad as all that */
335 		if (pdir == NULL)
336 			return (0);
337 
338 		/* scan spool directory looking for X. files to stash */
339 		while (gnamef(pdir, file) == TRUE) {
340 			DEBUG(4, "gt_Xfile got %s\n", file);
341 			/* look for x prefix */
342 			if (file[0] != XQTPRE)
343 				continue;
344 
345 			/* check to see if required files have arrived */
346 			if (gotfiles(file))
347 				xstash(file);
348 			if (xcachesize >= XCACHESIZE)
349 				break;
350 		}
351 		closedir(pdir);
352 		xsort();
353 	}
354 
355 	return (xget(file));
356 }
357 
358 /*
359  * check for needed files
360  *	file	-> name of file to check
361  * return:
362  *	0	-> not ready
363  *	1	-> all files ready
364  */
365 int
366 gotfiles(file)
367 char	*file;
368 {
369 	FILE *fp;
370 	struct stat stbuf;
371 	char	buf[BUFSIZ], rqfile[MAXNAMESIZE];
372 
373 	fp = fopen(file, "r");
374 	if (fp == NULL)
375 		return (FALSE);
376 
377 	while (fgets(buf, BUFSIZ, fp) != NULL) {
378 		DEBUG(4, "%s\n", buf);
379 
380 		/*
381 		 * look at required files
382 		 */
383 		if (buf[0] != X_RQDFILE)
384 			continue;
385 		(void) sscanf(&buf[1], "%63s", rqfile);
386 
387 		/*
388 		 * expand file name
389 		 */
390 		expfile(rqfile);
391 
392 		/*
393 		 * see if file exists
394 		 */
395 		if (stat(rqfile, &stbuf) == -1) {
396 			fclose(fp);
397 			return (FALSE);
398 		}
399 	}
400 
401 	fclose(fp);
402 	return (TRUE);
403 }
404 
405 /*
406  * remove execute files to x-directory
407  *
408  * _Xfile is a global
409  * return:
410  *	none
411  */
412 void
413 rm_Xfiles()
414 {
415 	FILE *fp;
416 	char	buf[BUFSIZ], file[MAXNAMESIZE], tfile[MAXNAMESIZE];
417 	char	tfull[MAXFULLNAME];
418 
419 	if ((fp = fopen(_Xfile, "r")) == NULL) {
420 		DEBUG(4, "rm_Xfiles: can't read %s\n", _Xfile);
421 		return;
422 	}
423 
424 	/*
425 	 * (void) unlink each file belonging to job
426 	 */
427 	while (fgets(buf, BUFSIZ, fp) != NULL) {
428 		if (buf[0] != X_RQDFILE)
429 			continue;
430 		if (sscanf(&buf[1], "%63s%63s", file, tfile) < 2)
431 			continue;
432 		(void) snprintf(tfull, sizeof (tfull), "%s/%s", XQTDIR, tfile);
433 		(void) unlink(tfull);
434 	}
435 	fclose(fp);
436 }
437 
438 /*
439  * move execute files to x-directory
440  *	_Xfile is a global
441  * return:
442  *	none
443  */
444 void
445 mv_Xfiles()
446 {
447 	FILE *fp;
448 	char	buf[BUFSIZ], ffile[MAXFULLNAME], tfile[MAXNAMESIZE];
449 	char	tfull[MAXFULLNAME];
450 
451 	if ((fp = fopen(_Xfile, "r")) == NULL) {
452 		DEBUG(4, "mv_Xfiles: can't read %s\n", _Xfile);
453 		return;
454 	}
455 
456 	while (fgets(buf, BUFSIZ, fp) != NULL) {
457 		if (buf[0] != X_RQDFILE)
458 			continue;
459 		if (sscanf(&buf[1], "%63s%63s", ffile, tfile) < 2)
460 			continue;
461 
462 		/*
463 		 * expand file names and move to
464 		 * execute directory
465 		 * Make files readable by anyone
466 		 */
467 		expfile(ffile);
468 		(void) snprintf(tfull, sizeof (tfull), "%s/%s", XQTDIR, tfile);
469 
470 		if (chkpth(ffile, CK_READ) == FAIL)
471 			continue;	/* execution will fail later */
472 		if (chkpth(tfull, CK_WRITE) == FAIL) {
473 			/*
474 			 * tfull will have been canonicalized. If
475 			 * it still points to XQTDIR, allow us to
476 			 * write there.
477 			 */
478 			if (!PREFIX(XQTDIR, tfull))
479 				continue;	/* execution will fail later */
480 			/* otherwise, keep going */
481 		}
482 
483 		ASSERT(xmv(ffile, tfull) == 0, "XMV ERROR", tfull, errno);
484 		chmod(tfull, PUB_FILEMODE);
485 	}
486 	fclose(fp);
487 }
488 
489 /*
490  * undo what mv_Xfiles did
491  *	_Xfile is a global
492  * return:
493  *	none
494  */
495 void
496 unmv_Xfiles()
497 {
498 	FILE *fp;
499 	char	buf[BUFSIZ], ffile[MAXNAMESIZE], tfile[MAXNAMESIZE];
500 	char	tfull[MAXFULLNAME], ffull[MAXFULLNAME], xfull[MAXFULLNAME];
501 
502 	(void) snprintf(xfull, MAXFULLNAME, "%s/%s", RemSpool, _Xfile);
503 	if ((fp = fopen(xfull, "r")) == NULL) {
504 		DEBUG(4, "unmv_Xfiles: can't read %s\n", xfull);
505 		return;
506 	}
507 
508 	while (fgets(buf, BUFSIZ, fp) != NULL) {
509 		if (buf[0] != X_RQDFILE)
510 			continue;
511 		if (sscanf(&buf[1], "%63s%63s", ffile, tfile) < 2)
512 			continue;
513 
514 		/*
515 		 * expand file names and move back to
516 		 * spool directory
517 		 * Make files readable by uucp
518 		 */
519 		(void) snprintf(ffull, MAXFULLNAME, "%s/%s", RemSpool, ffile);
520 		/* i know we're in .Xqtdir, but ... */
521 		(void) snprintf(tfull, MAXFULLNAME, "%s/%s", XQTDIR, tfile);
522 
523 		if (chkpth(ffull, CK_WRITE) == FAIL ||
524 		    chkpth(tfull, CK_READ) == FAIL)
525 			continue;
526 
527 		ASSERT(xmv(tfull, ffull) == 0, "XMV ERROR", ffull, errno);
528 		(void) chmod(ffull, (mode_t)0600);
529 	}
530 	fclose(fp);
531 }
532 
533 /*
534  * chkpart - checks the string (ptr points to it) for illegal command or
535  *  file permission restriction - called recursively
536  *  to check lines that have `string` or (string) form.
537  *  _Cmd is the buffer where the command is built up.
538  *  _CargType is the type of the next C line argument
539  *
540  * Return:
541  *	BAD_FILE if a non permitted file is found
542  *	BAD_COMMAND if non permitted command is found
543  *	0 - ok
544  */
545 
546 static int
547 chkpart(char *ptr)
548 {
549 	char	prm[BUFSIZ], whitesp[BUFSIZ], rqtcmd[BUFSIZ], xcmd[BUFSIZ];
550 	char	savechar[2]; /* one character string with NULL */
551 	int	ret;
552 
553 	/* _CargType is the arg type for this iteration (cmd or file) */
554 	while ((ptr = getprm(ptr, whitesp, prm)) != NULL) {
555 	    DEBUG(4, "prm='%s'\n", prm);
556 	    switch (*prm) {
557 
558 	    /* End of command delimiter */
559 	    case ';':
560 	    case '^':
561 	    case '&':
562 	    case '|':
563 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
564 		(void) strlcat(_Cmd, prm, sizeof (_Cmd));
565 		_CargType = C_COMMAND;
566 		continue;
567 
568 	    /* Other delimiter */
569 	    case '>':
570 	    case '<':
571 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
572 		(void) strlcat(_Cmd, prm, sizeof (_Cmd));
573 		continue;
574 
575 	    case '`':	/* don't allow any ` commands */
576 	    case '\\':
577 		return (BAD_COMMAND);
578 
579 	    /* Some allowable quoted string */
580 	    case '(':
581 	    case '"':
582 	    case '\'':
583 		/* must recurse */
584 		savechar[0] = *prm;
585 		savechar[1] = NULLCHAR;
586 		/* put leading white space & first char into command */
587 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
588 		(void) strlcat(_Cmd, savechar, sizeof (_Cmd));
589 		savechar[0] = prm[strlen(prm)-1];
590 		prm[strlen(prm)-1] = NULLCHAR; /* delete last character */
591 
592 		/* recurse */
593 		if (ret = chkpart(prm+1)) { /* failed */
594 			return (ret);
595 		}
596 		/* put last char into command */
597 		(void) strlcat(_Cmd, savechar, sizeof (_Cmd));
598 		continue;
599 
600 	    case '2':
601 		if (*(prm+1) == '>') {
602 		    (void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
603 		    (void) strlcat(_Cmd, prm, sizeof (_Cmd));
604 		    continue;
605 		}
606 		/* fall through if not "2>" */
607 
608 	    default:	/* check for command or file */
609 		break;
610 	    }
611 
612 	    if (_CargType == C_COMMAND) {
613 		(void) strlcpy(rqtcmd, prm, sizeof (rqtcmd));
614 		if (*rqtcmd == '~')
615 		    expfile(rqtcmd);
616 		if ((cmdOK(rqtcmd, xcmd)) == FALSE)
617 			return (BAD_COMMAND);
618 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
619 		(void) strlcat(_Cmd, xcmd, sizeof (_Cmd));
620 		_CargType = C_FILE;
621 		continue;
622 	    }
623 
624 	    (void) strlcpy(rqtcmd, prm, sizeof (rqtcmd));
625 	    if (*rqtcmd == '~')
626 		expfile(rqtcmd);
627 	    if (chkFile(rqtcmd)) {
628 		return (BAD_FILE);
629 	    } else {
630 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
631 		(void) strlcat(_Cmd, rqtcmd, sizeof (_Cmd));
632 	    }
633 	}
634 	if (whitesp[0] != '\0')
635 		/* restore any trailing white space */
636 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
637 	return (0);	/* all ok */
638 }
639 
640 /*
641  * chkFile - try to find a path name in the prm.
642  * 	if found, check it for access permission.
643  *
644  * check file access permissions
645  * if ! in name assume that access on local machine is required
646  *
647  * Return:
648  *	BAD_FILE - not permitted
649  *	0 - ok
650  */
651 
652 static int
653 chkFile(char *prm)
654 {
655 	char	*p, buf[BUFSIZ];
656 
657 	(void) strlcpy(buf, prm, sizeof (buf));
658 	switch (*prm) {
659 	case '~':
660 	case '/':
661 	    if (doFileChk(buf))
662 		return (BAD_FILE);
663 	    else
664 		return (0);
665 	    /*NOTREACHED*/
666 
667 	case '!':
668 	    return (chkFile(buf+1));
669 	    /*NOTREACHED*/
670 
671 	default:
672 	    break;
673 	}
674 
675 	if ((p = strchr(buf, '!')) == NULL) {  /* no "!", look for "/" */
676 	    if ((p = strchr(buf, '/')) == NULL) {  /* ok */
677 		return (0);
678 	    }
679 	    if (doFileChk(p))
680 		return (BAD_FILE);
681 	    else
682 		return (0);
683 	}
684 
685 	/* there is at least one '!' - see if it refers to my system */
686 	if (PREFIX(Myname, buf))	/*  my system so far, check further */
687 	    return (chkFile(p+1));	/* recurse with thing after '!' */
688 	else				/* not my system - not my worry */
689 	    return (0);
690 }
691 
692 /*
693  * doFileChk - check file path permission
694  * NOTE: file is assumed to be a buffer that expfile an
695  *  write into.
696  * Return
697  *	BAD_FILE - not allowed
698  *	0 - ok
699  */
700 
701 static int
702 doFileChk(char *file)
703 {
704 	expfile(file);
705 	DEBUG(7, "fullname: %s\n", file);
706 	if (chkpth(file, CK_READ) == FAIL ||
707 	    chkpth(file, CK_WRITE) == FAIL)
708 		return (BAD_FILE);
709 	else
710 		return (0);
711 }
712 
713 
714 /*
715  * return stuff to user
716  *	user	-> user to notify
717  *	rmt	-> system name where user resides
718  *	file	-> file to return (generally contains input)
719  *	cmd	-> command that was to be executed
720  *	buf	-> user friendly face saving uplifting edifying missive
721  *	errfile	-> stderr output from cmd xeqn
722  * return:
723  *	none
724  */
725 static void
726 retosndr(user, rmt, file, cmd, buf, errfile)
727 char	*user, *rmt, *file, *cmd, *buf, *errfile;
728 {
729 	char	ruser[BUFSIZ], msg[BUFSIZ], subj[BUFSIZ];
730 
731 	(void) snprintf(msg, sizeof (msg), "%s\t[%s %s (%s)]\n\t%s\n%s\n",
732 	    gettext("remote execution"), gettext("uucp job"),
733 	    *Jobid ? Jobid : &_Xfile[2], timeStamp(), cmd, buf);
734 
735 	DEBUG(5, "retosndr %s, ", msg);
736 
737 	if (EQUALS(rmt, Myname))
738 		(void) strlcpy(ruser, user, sizeof (ruser));
739 	else
740 		(void) snprintf(ruser, sizeof (ruser), "%s!%s", rmt, user);
741 
742 	(void) strlcpy(subj, gettext("remote execution status"), sizeof (subj));
743 	mailst(ruser, subj, msg, file, errfile);
744 }
745 
746 
747 /*
748  * uucpst - send the status message back using a uucp command
749  * NOTE - this would be better if the file could be appended.
750  * - suggestion for the future - if rmail would take a file name
751  * instead of just person, then that facility would be correct,
752  * and this routine would not be needed.
753  */
754 
755 static void
756 uucpst(rmt, tofile, errfile, cmd, buf)
757 char	*rmt, *tofile, *errfile, *cmd, *buf;
758 {
759 	char	arg[MAXFULLNAME], tmp[NAMESIZE], msg[BUFSIZ];
760 	pid_t pid, ret;
761 	int status;
762 	FILE *fp, *fi;
763 
764 	(void) snprintf(msg, sizeof (msg), "%s %s (%s) %s\n\t%s\n%s\n",
765 	    gettext("uucp job"), *Jobid ? Jobid : &_Xfile[2], timeStamp(),
766 	    gettext("remote execution"), cmd, buf);
767 
768 	(void) snprintf(tmp, sizeof (tmp), "%s.%ld", rmt, (long)getpid());
769 	if ((fp = fopen(tmp, "w")) == NULL)
770 		return;
771 	(void) fprintf(fp, "%s\n", msg);
772 
773 	/* copy back stderr */
774 	if (*errfile != '\0' && NOTEMPTY(errfile) &&
775 	    (fi = fopen(errfile, "r")) != NULL) {
776 		fputs("\n\t===== stderr was =====\n", fp);
777 		if (xfappend(fi, fp) != SUCCESS)
778 			fputs("\n\t===== well, i tried =====\n", fp);
779 		(void) fclose(fi);
780 		fputc('\n', fp);
781 	}
782 
783 
784 	(void) fclose(fp);
785 	(void) snprintf(arg, sizeof (arg), "%s!%s", rmt, tofile);
786 
787 	/* start uucp */
788 
789 	if ((pid = vfork()) == 0) {
790 		(void) close(0);
791 		(void) close(1);
792 		(void) close(2);
793 		(void) open("/dev/null", 2);
794 		(void) open("/dev/null", 2);
795 		(void) open("/dev/null", 2);
796 		(void) signal(SIGINT, SIG_IGN);
797 		(void) signal(SIGHUP, SIG_IGN);
798 		(void) signal(SIGQUIT, SIG_IGN);
799 		ucloselog();
800 
801 		(void) execle("/usr/bin/uucp", "UUCP",
802 		    "-C", tmp, arg, (char *)0, Env);
803 		_exit(100);
804 	}
805 
806 	if (pid == -1)
807 	    return;
808 
809 	while ((ret = wait(&status)) != pid)
810 	    if (ret == -1 && errno != EINTR)
811 		break;
812 
813 	(void) unlink(tmp);
814 }
815 
816 void
817 xprocess(dirname)
818 char *dirname;
819 {
820     char 	fdgrade();	/* returns default service grade on system */
821     int		return_stdin;	/* return stdin for failed commands */
822     int		cmdok, ret, badfiles;
823     mode_t	mask;
824     int		send_zero;	/* return successful completion status */
825     int		send_nonzero;	/* return unsuccessful completion status */
826     int		send_nothing;	/* request for no exit status */
827     int		store_status;	/* store status of command in local file */
828     char	lbuf[BUFSIZ];
829     char	dqueue;		/* var to hold the default service grade */
830     char	*errname = "";	/* name of local stderr output file */
831     char	*p;
832     char	sendsys[MAXNAMESIZE];
833     char	dfile[MAXFULLNAME], cfile[MAXFULLNAME], incmd[BUFSIZ];
834     char	errDfile[BUFSIZ];
835     char	fin[MAXFULLNAME];
836     char	fout[MAXFULLNAME], sysout[NAMESIZE];
837     char	ferr[MAXFULLNAME], syserr[NAMESIZE];
838     char	file[MAXFULLNAME], tempname[NAMESIZE];
839     char	_Sfile[MAXFULLNAME];	/* name of local file for status */
840     FILE	*xfp, *fp;
841     struct	stat sb;
842     char	buf[BUFSIZ], user[BUFSIZ], retaddr[BUFSIZ], retuser[BUFSIZ],
843 		msgbuf[BUFSIZ];
844     char	origsys[MAXFULLNAME], origuser[MAXFULLNAME];
845 
846     (void) strlcpy(Rmtname, dirname, sizeof (Rmtname));
847     chremdir(Rmtname);
848     (void) mchFind(Rmtname);
849     while (gt_Xfile(_Xfile, RemSpool) > 0) {
850 	DEBUG(4, "_Xfile - %s\n", _Xfile);
851 
852 	if ((xfp = fopen(_Xfile, "r")) == NULL) {
853 	    toCorrupt(_Xfile);
854 	    continue;
855 	}
856 	ASSERT(xfp != NULL, Ct_OPEN, _Xfile, errno);
857 
858 	if (stat(_Xfile, &sb) != -1)
859 	    Nstat.t_qtime = sb.st_mtime;
860 	/*
861 	 * initialize to defaults
862 	 */
863 	(void) strlcpy(user, User, sizeof (user));
864 	(void) strcpy(fin, "/dev/null");
865 	(void) strcpy(fout, "/dev/null");
866 	(void) strcpy(ferr, "/dev/null");
867 	(void) sprintf(sysout, "%.*s", MAXBASENAME, Myname);
868 	(void) sprintf(syserr, "%.*s", MAXBASENAME, Myname);
869 	badfiles = 0;
870 	*incmd = *retaddr = *retuser = *Jobid = NULLCHAR;
871 	initSeq();
872 	send_zero = send_nonzero = send_nothing = 0;
873 	store_status = 0;
874 	return_stdin = 0;
875 
876 	while (fgets(buf, BUFSIZ, xfp) != NULL) {
877 	    /*
878 	     * interpret JCL card
879 	     */
880 	    switch (buf[0]) {
881 		case X_USER:
882 			/*
883 			 * user name
884 			 * (ignore Rmtname)
885 			 * The utmpx username field is 32 characters long;
886 			 * UUCP usage truncates system name to 14 bytes.
887 			 */
888 			(void) sscanf(&buf[1], "%32s%14s", user, origsys);
889 			(void) strlcpy(origuser, user, sizeof (origuser));
890 			break;
891 
892 		case X_STDIN:
893 			/*
894 			 * standard input
895 			 */
896 			(void) sscanf(&buf[1], "%256s", fin);
897 			expfile(fin);
898 			if (chkpth(fin, CK_READ)) {
899 				DEBUG(4, "badfile - in: %s\n", fin);
900 				badfiles = 1;
901 			}
902 			break;
903 
904 		case X_STDOUT:
905 			/*
906 			 * standard output
907 			 */
908 			(void) sscanf(&buf[1], "%256s%14s", fout, sysout);
909 			if ((p = strpbrk(sysout, "!/")) != NULL)
910 				*p = NULLCHAR;	/* these are dangerous */
911 			if (*sysout != NULLCHAR && !EQUALS(sysout, Myname))
912 				break;
913 
914 			expfile(fout);
915 			if (chkpth(fout, CK_WRITE)) {
916 				badfiles = 1;
917 				DEBUG(4, "badfile - out: %s\n", fout);
918 			}
919 			break;
920 
921 		case X_STDERR:	/* standard error */
922 			(void) sscanf(&buf[1], "%256s%14s", ferr, syserr);
923 			if ((p = strpbrk(syserr, "!/")) != NULL)
924 				*p = NULLCHAR;	/* these are dangerous */
925 			if (*syserr != NULLCHAR && !EQUALS(syserr, Myname))
926 				break;
927 
928 			expfile(ferr);
929 			if (chkpth(ferr, CK_WRITE)) {
930 				badfiles = 1;
931 				DEBUG(4, "badfile - error: %s\n", ferr);
932 			}
933 			break;
934 
935 
936 		case X_CMD:	/* command to execute */
937 			(void) strlcpy(incmd, &buf[2], sizeof (incmd));
938 			if (*(incmd + strlen(incmd) - 1) == '\n')
939 				*(incmd + strlen(incmd) - 1) = NULLCHAR;
940 			break;
941 
942 		case X_MAILF:	/* put status in _Sfile */
943 			store_status = 1;
944 			(void) sscanf(&buf[1], "%256s", _Sfile);
945 			break;
946 
947 		case X_SENDNOTHING:	/* no failure notification */
948 			send_nothing++;
949 			break;
950 
951 		case X_SENDZERO:	/* success notification */
952 			send_zero++;
953 			break;
954 
955 		case X_NONZERO:	/* failure notification */
956 			send_nonzero++;
957 			break;
958 
959 		case X_BRINGBACK:  /* return stdin on command failure */
960 			return_stdin = 1;
961 			break;
962 
963 
964 		case X_RETADDR:
965 			/*
966 			 * return address -- is user's name
967 			 * put "Rmtname!" in front of it so mail
968 			 * will always get back to remote system.
969 			 */
970 			(void) sscanf(&buf[1], "%s", retuser);
971 
972 			/*
973 			 * Creates string of Rmtname!Rmtname!user which
974 			 * confuses rmail.
975 			 * (void) strcat(strcat(strcpy(retaddr, Rmtname), "!"),
976 			 *	retuser);
977 			 */
978 			break;
979 
980 		case X_JOBID:
981 			/*
982 			 * job id for notification
983 			 * (should be MAXBASENAME, not 14, but no can do)
984 			 */
985 			(void) sscanf(&buf[1], "%14s", Jobid);
986 			break;
987 
988 		default:
989 			break;
990 	    }
991 	}
992 
993 	fclose(xfp);
994 	DEBUG(4, "fin - %s, ", fin);
995 	DEBUG(4, "fout - %s, ", fout);
996 	DEBUG(4, "ferr - %s, ", ferr);
997 	DEBUG(4, "sysout - %s, ", sysout);
998 	DEBUG(4, "syserr - %s, ", syserr);
999 	DEBUG(4, "user - %s\n", user);
1000 	DEBUG(4, "incmd - %s\n", incmd);
1001 
1002 	scRexe(origsys, origuser, Loginuser, incmd);
1003 
1004 	if (retuser[0] != NULLCHAR)
1005 	    (void) strlcpy(user, retuser, sizeof (user)); /* pick on this guy */
1006 
1007 	/* get rid of stuff that can be dangerous */
1008 	if ((p = strpbrk(user, Shchar)) != NULL) {
1009 	    *p = NULLCHAR;
1010 	}
1011 
1012 	if (incmd[0] == NULLCHAR) {
1013 	    /* this is a bad X. file - just get rid of it */
1014 	    toCorrupt(_Xfile);
1015 	    continue;
1016 	}
1017 
1018 	/*
1019 	 * send_nothing must be explicitly requested to avert failure status
1020 	 * send_zero must be explicitly requested for success notification
1021 	 */
1022 	if (!send_nothing)
1023 		send_nonzero++;
1024 
1025 	/*
1026 	 * command execution
1027 	 */
1028 
1029 	/*
1030 	 * generate a temporary file (if necessary)
1031 	 * to hold output to be shipped back
1032 	 */
1033 	if (EQUALS(fout, "/dev/null"))
1034 	    (void) strcpy(dfile, "/dev/null");
1035 	else {
1036 	    gename(DATAPRE, sysout, 'O', tempname);
1037 	    (void) snprintf(dfile, sizeof (dfile), "%s/%s", WORKSPACE,
1038 		tempname);
1039 	}
1040 
1041 	/*
1042 	 * generate a temporary file (if necessary)
1043 	 * to hold errors to be shipped back
1044 	 */
1045 /*
1046  * This is what really should be done. However for compatibility
1047  * for the interim at least, we will always create temp file
1048  * so we can return error output. If this temp file IS conditionally
1049  * created, we must remove the unlink() of errDfile at the end
1050  * because it may REALLY be /dev/null.
1051  *	if (EQUALS(ferr, "/dev/null"))
1052  *	    (void) strcpy(errDfile, "/dev/null");
1053  *	else {
1054  */
1055 	    gename(DATAPRE, syserr, 'E', tempname);
1056 	    (void) snprintf(errDfile, sizeof (errDfile), "%s/%s",
1057 		WORKSPACE, tempname);
1058 /*
1059  *	}
1060  */
1061 
1062 	/* initialize command line */
1063 	/* set up two environment variables, remote machine name */
1064 	/* and remote user name if available from R line */
1065 	/*
1066 	 * xcu4 requires that uucp *does* expand wildcards and uux *does not*
1067 	 * expand wild cards... Further restrictions are that uux must work
1068 	 * with every other uucp / uux that initiated a request, so nothing
1069 	 * strange can been done to communicate that it was uucp that sent
1070 	 * the request and not uux,  What we settle on here is looking for
1071 	 * the command name uucp and expanding wildcards in only that case.
1072 	 * It is true that a user can spoof this using uux, but in reality
1073 	 * this would be identical to using the uucp command to start with.
1074 	 */
1075 	if (strncmp(incmd, "uucp ", 5) == 0) {
1076 		(void) snprintf(_Cmd, sizeof (_Cmd),
1077 		    "%s %s UU_MACHINE=%s UU_USER=%s "
1078 		    " export UU_MACHINE UU_USER PATH; ",
1079 		    PATH, LOGNAME, Rmtname, user);
1080 	} else {
1081 		(void) snprintf(_Cmd, sizeof (_Cmd),
1082 		    "%s %s UU_MACHINE=%s UU_USER=%s "
1083 		    " export UU_MACHINE UU_USER PATH; set -f; ",
1084 		    PATH, LOGNAME, Rmtname, user);
1085 	}
1086 
1087 	/*
1088 	 * check to see if command can be executed
1089 	 */
1090 	_CargType = C_COMMAND;	/* the first thing is a command */
1091 	cmdok = chkpart(incmd);
1092 
1093 	if (badfiles || (cmdok == BAD_COMMAND) || cmdok == BAD_FILE) {
1094 	    if (cmdok == BAD_COMMAND) {
1095 		(void) snprintf(lbuf, sizeof (lbuf), "%s!%s XQT DENIED",
1096 		    Rmtname, user);
1097 		(void) snprintf(msgbuf, sizeof (msgbuf),
1098 		    "execution permission denied to %s!%s", Rmtname, user);
1099 	    } else {
1100 		(void) snprintf(lbuf, sizeof (lbuf),
1101 		    "%s!%s XQT - STDIN/STDOUT/FILE ACCESS DENIED",
1102 		    Rmtname, user);
1103 		(void) snprintf(msgbuf, sizeof (msgbuf),
1104 		    "file access denied to %s!%s", Rmtname, user);
1105 	    }
1106 	    logent(incmd, lbuf);
1107 	    DEBUG(4, "bad command %s\n", incmd);
1108 
1109 	    scWlog(); /* log security vialotion */
1110 
1111 	    if (send_nonzero)
1112 		retosndr(user, Rmtname, return_stdin ? fin : "",
1113 		    incmd, msgbuf, "");
1114 	    if (store_status)
1115 		    uucpst(Rmtname, _Sfile, "", incmd, msgbuf);
1116 	    goto rmfiles;
1117 	}
1118 
1119 	(void) snprintf(lbuf, sizeof (lbuf), "%s!%s XQT", Rmtname, user);
1120 	logent(_Cmd, lbuf);
1121 	DEBUG(4, "cmd %s\n", _Cmd);
1122 
1123 	/* move files to execute directory and change to that directory */
1124 
1125 	mv_Xfiles();
1126 
1127 	ASSERT(chdir(XQTDIR) == 0, Ct_CHDIR, XQTDIR, errno);
1128 	acRexe(&_Xfile[2], origsys, origuser, Myname, Loginuser, incmd);
1129 
1130 	/* invoke shell to execute command */
1131 
1132 	mask = umask(0);
1133 	DEBUG(7, "full cmd: %s\n", _Cmd);
1134 
1135 	cpucycle();
1136 	ret = shio(_Cmd, fin, dfile, errDfile);
1137 	if (ret == 0)
1138 		acEndexe(cpucycle(), COMPLETE);
1139 	else
1140 		acEndexe(cpucycle(), PARTIAL);
1141 
1142 	umask(mask);
1143 	if (ret == -1) {	/* -1 means the fork() failed */
1144 		unmv_Xfiles();	/* put things back */
1145 		errent(Ct_FORK, buf, errno, __FILE__, __LINE__);
1146 		cleanup(1);
1147 	}
1148 
1149 	if (ret == 0) {				/* exit == signal == 0 */
1150 	    (void) strcpy(msgbuf, "exited normally");
1151 	} else {				/* exit != 0 */
1152 	    int exitcode = (ret >> 8) & 0377;
1153 
1154 	    if (exitcode) {
1155 		/* exit != 0 */
1156 		(void) snprintf(msgbuf, sizeof (msgbuf),
1157 		    "exited with status %d", exitcode);
1158 	    } else {
1159 		/* signal != 0 */
1160 		(void) snprintf(msgbuf, sizeof (msgbuf),
1161 		    "terminated by signal %d", ret & 0177);
1162 	    }
1163 	    DEBUG(5, "%s\n", msgbuf);
1164 	    (void) snprintf(lbuf, sizeof (lbuf), "%s - %s", incmd, msgbuf);
1165 	    logent(lbuf, "COMMAND FAIL");
1166 	}
1167 
1168 	/* change back to spool directory */
1169 
1170 	chremdir(Rmtname);
1171 
1172 	/* remove file */
1173 
1174 	rm_Xfiles();
1175 
1176 	/*
1177 	 * We used to append stderr to stdout. Since stderr can
1178 	 * now be specified separately, never append it to stdout.
1179 	 * It can still be gotten via -s status file option.
1180 	 */
1181 
1182 	if (!EQUALS(fout, "/dev/null")) {
1183 	    /*
1184 	     * if output is on this machine copy output
1185 	     * there, otherwise spawn job to send to send
1186 	     * output elsewhere.
1187 	     */
1188 
1189 	    if (EQUALS(sysout, Myname)) {
1190 		if ((xmv(dfile, fout)) != 0) {
1191 		    logent("FAILED", "COPY");
1192 		    scWrite();
1193 		    (void) snprintf(msgbuf + strlen(msgbuf),
1194 			(sizeof (msgbuf) - strlen(msgbuf)),
1195 			"\nCould not move stdout to %s,", fout);
1196 		    if (putinpub(fout, dfile, origuser) == 0)
1197 			(void) snprintf(msgbuf + strlen(msgbuf),
1198 			    (sizeof (msgbuf) - strlen(msgbuf)),
1199 			    "\n\tstdout left in %s.", fout);
1200 		    else
1201 			(void) strlcat(msgbuf, " stdout lost.",
1202 			    sizeof (msgbuf));
1203 		}
1204 	    } else {
1205 		char *bname;
1206 
1207 		if (eaccess(GRADES, 04) != -1)
1208 			dqueue = fdgrade();
1209 		else
1210 			dqueue = Grade;
1211 		gename(CMDPRE, sysout, dqueue, tempname);
1212 		(void) snprintf(cfile, sizeof (cfile), "%s/%s",
1213 		    WORKSPACE, tempname);
1214 		fp = fdopen(ret = creat(cfile, CFILEMODE), "w");
1215 		ASSERT(ret >= 0 && fp != NULL, Ct_OPEN, cfile, errno);
1216 		bname = BASENAME(dfile, '/');
1217 		(void) fprintf(fp, "S %s %s %s -d %s 0666\n",
1218 		    bname, fout, user, bname);
1219 		fclose(fp);
1220 		(void) snprintf(sendsys, sizeof (sendsys), "%s/%c", sysout,
1221 		    dqueue);
1222 		sendsys[MAXNAMESIZE-1] = '\0';
1223 		wfcommit(dfile, BASENAME(dfile, '/'), sendsys);
1224 		wfcommit(cfile, BASENAME(cfile, '/'), sendsys);
1225 	    }
1226 	}
1227 	if (!EQUALS(ferr, "/dev/null")) {
1228 	    /*
1229 	     * if stderr is on this machine copy output
1230 	     * there, otherwise spawn job to send to send
1231 	     * it elsewhere.
1232 	     */
1233 	    if (EQUALS(syserr, Myname)) {
1234 		errname = ferr;
1235 		if ((xmv(errDfile, ferr)) != 0) {
1236 		    logent("FAILED", "COPY");
1237 		    scWrite();
1238 		    (void) snprintf(msgbuf + strlen(msgbuf),
1239 			(sizeof (msgbuf) - strlen(msgbuf)),
1240 			"\nCould not move stderr to %s,", ferr);
1241 		    if (putinpub(ferr, errDfile, origuser) == 0) {
1242 			(void) snprintf(msgbuf+strlen(msgbuf),
1243 			    (sizeof (msgbuf) - strlen(msgbuf)),
1244 			    "\n\tstderr left in %s", ferr);
1245 		    } else {
1246 			errname = errDfile;
1247 			(void) strlcat(msgbuf, " stderr lost.",
1248 			    sizeof (msgbuf));
1249 		    }
1250 		}
1251 	    } else {
1252 		char *bname;
1253 
1254 		if (eaccess(GRADES, 04) != -1)
1255 			dqueue = fdgrade();
1256 		else
1257 			dqueue = Grade;
1258 		gename(CMDPRE, syserr, dqueue, tempname);
1259 		(void) snprintf(cfile, sizeof (cfile), "%s/%s",
1260 		    WORKSPACE, tempname);
1261 		fp = fdopen(ret = creat(cfile, CFILEMODE), "w");
1262 		ASSERT(ret >= 0 && fp != NULL, Ct_OPEN, cfile, errno);
1263 		bname = BASENAME(errDfile, '/');
1264 		(void) fprintf(fp, "S %s %s %s -d %s 0666\n",
1265 		    bname, ferr, user, bname);
1266 		fclose(fp);
1267 		(void) snprintf(sendsys, sizeof (sendsys), "%s/%c",
1268 		    syserr, dqueue);
1269 		sendsys[MAXNAMESIZE-1] = '\0';
1270 		wfcommit(errDfile, BASENAME(errDfile, '/'), sendsys);
1271 		wfcommit(cfile, BASENAME(cfile, '/'), sendsys);
1272 	    }
1273 	} else {
1274 /*
1275  * If we conditionally create stderr tempfile, we must
1276  * remove this unlink() since errDfile may REALLY be /dev/null
1277  */
1278 	    unlink(errDfile);
1279 	}
1280 
1281 	if (ret == 0) {
1282 	    if (send_zero)
1283 		retosndr(user, Rmtname, "", incmd, msgbuf, "");
1284 	    if (store_status)
1285 		uucpst(Rmtname, _Sfile, "", incmd, msgbuf);
1286 	} else {
1287 	    if (send_nonzero)
1288 		retosndr(user, Rmtname, return_stdin ? fin : "",
1289 			incmd, msgbuf, errname);
1290 	    if (store_status)
1291 		uucpst(Rmtname, _Sfile, errname, incmd, msgbuf);
1292 	}
1293 
1294 rmfiles:
1295 
1296 	/* delete job files in spool directory */
1297 	xfp = fopen(_Xfile, "r");
1298 	ASSERT(xfp != NULL, Ct_OPEN, _Xfile, errno);
1299 	while (fgets(buf, BUFSIZ, xfp) != NULL) {
1300 		if (buf[0] != X_RQDFILE)
1301 			continue;
1302 		(void) sscanf(&buf[1], "%63s", file);
1303 		expfile(file);
1304 		if (chkpth(file, CK_WRITE) != FAIL)
1305 			(void) unlink(file);
1306 	}
1307 	(void) unlink(_Xfile);
1308 	fclose(xfp);
1309     }
1310 }
1311