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