1/*
2 * Copyright (c) 1988 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * This code is derived from software written by Ken Arnold and
6 * published in UNIX Review, Vol. 6, No. 8.
7 *
8 * Redistribution and use in source and binary forms are permitted
9 * provided that the above copyright notice and this paragraph are
10 * duplicated in all such forms and that any documentation,
11 * advertising materials, and other materials related to such
12 * distribution and use acknowledge that the software was developed
13 * by the University of California, Berkeley.  The name of the
14 * University may not be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19 *
20 */
21
22/* this came out of the ftpd sources; it's been modified to avoid the
23 * globbing stuff since we don't need it.  also execvp instead of execv.
24 */
25
26#ifndef lint
27#if 0
28static char sccsid[] = "@(#)popen.c	5.7 (Berkeley) 2/14/89";
29#endif
30static const char rcsid[] =
31  "$FreeBSD: stable/11/usr.sbin/cron/cron/popen.c 358255 2020-02-23 03:13:38Z kevans $";
32#endif /* not lint */
33
34#include "cron.h"
35#include <sys/signal.h>
36#include <fcntl.h>
37#include <paths.h>
38#if defined(SYSLOG)
39# include <syslog.h>
40#endif
41#if defined(LOGIN_CAP)
42# include <login_cap.h>
43#endif
44
45
46#define MAX_ARGS 100
47#define WANT_GLOBBING 0
48
49/*
50 * Special version of popen which avoids call to shell.  This insures no one
51 * may create a pipe to a hidden program as a side effect of a list or dir
52 * command.
53 */
54static PID_T *pids;
55static int fds;
56
57FILE *
58cron_popen(program, type, e, pidptr)
59	char *program, *type;
60	entry *e;
61	PID_T *pidptr;
62{
63	register char *cp;
64	FILE *iop;
65	int argc, pdes[2];
66	PID_T pid;
67	char *usernm;
68	char *argv[MAX_ARGS + 1];
69# if defined(LOGIN_CAP)
70	struct passwd	*pwd;
71	login_cap_t *lc;
72# endif
73#if WANT_GLOBBING
74	char **pop, *vv[2];
75	int gargc;
76	char *gargv[1000];
77	extern char **glob(), **copyblk();
78#endif
79
80	if ((*type != 'r' && *type != 'w') || type[1])
81		return(NULL);
82
83	if (!pids) {
84		if ((fds = getdtablesize()) <= 0)
85			return(NULL);
86		if (!(pids = calloc(fds, sizeof(PID_T))))
87			return(NULL);
88	}
89	if (pipe(pdes) < 0)
90		return(NULL);
91
92	/* break up string into pieces */
93	for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL)
94		if (!(argv[argc++] = strtok(cp, " \t\n")))
95			break;
96	argv[MAX_ARGS] = NULL;
97
98#if WANT_GLOBBING
99	/* glob each piece */
100	gargv[0] = argv[0];
101	for (gargc = argc = 1; argv[argc]; argc++) {
102		if (!(pop = glob(argv[argc]))) {	/* globbing failed */
103			vv[0] = argv[argc];
104			vv[1] = NULL;
105			pop = copyblk(vv);
106		}
107		argv[argc] = (char *)pop;		/* save to free later */
108		while (*pop && gargc < 1000)
109			gargv[gargc++] = *pop++;
110	}
111	gargv[gargc] = NULL;
112#endif
113
114	iop = NULL;
115	switch(pid = fork()) {
116	case -1:			/* error */
117		(void)close(pdes[0]);
118		(void)close(pdes[1]);
119		goto pfree;
120		/* NOTREACHED */
121	case 0:				/* child */
122		if (e != NULL) {
123#ifdef SYSLOG
124			closelog();
125#endif
126
127			/* get new pgrp, void tty, etc.
128			 */
129			(void) setsid();
130		}
131		if (*type == 'r') {
132			/* Do not share our parent's stdin */
133			(void)close(0);
134			(void)open(_PATH_DEVNULL, O_RDWR);
135			if (pdes[1] != 1) {
136				dup2(pdes[1], 1);
137				dup2(pdes[1], 2);	/* stderr, too! */
138				(void)close(pdes[1]);
139			}
140			(void)close(pdes[0]);
141		} else {
142			if (pdes[0] != 0) {
143				dup2(pdes[0], 0);
144				(void)close(pdes[0]);
145			}
146			/* Hack: stdout gets revoked */
147			(void)close(1);
148			(void)open(_PATH_DEVNULL, O_RDWR);
149			(void)close(2);
150			(void)open(_PATH_DEVNULL, O_RDWR);
151			(void)close(pdes[1]);
152		}
153		if (e != NULL) {
154			/* Set user's entire context, but skip the environment
155			 * as cron provides a separate interface for this
156			 */
157			usernm = env_get("LOGNAME", e->envp);
158# if defined(LOGIN_CAP)
159			if ((pwd = getpwnam(usernm)) == NULL)
160				pwd = getpwuid(e->uid);
161			lc = NULL;
162			if (pwd != NULL) {
163				pwd->pw_gid = e->gid;
164				if (e->class != NULL)
165					lc = login_getclass(e->class);
166			}
167			if (pwd &&
168			    setusercontext(lc, pwd, e->uid,
169				    LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
170				(void) endpwent();
171			else {
172				/* fall back to the old method */
173				(void) endpwent();
174# endif
175				/*
176				 * Set our directory, uid and gid.  Set gid
177				 * first since once we set uid, we've lost
178				 * root privileges.
179				 */
180				if (setgid(e->gid) != 0)
181					_exit(ERROR_EXIT);
182# if defined(BSD)
183				if (initgroups(usernm, e->gid) != 0)
184					_exit(ERROR_EXIT);
185# endif
186				if (setlogin(usernm) != 0)
187					_exit(ERROR_EXIT);
188				if (setuid(e->uid) != 0)
189					_exit(ERROR_EXIT);
190				/* we aren't root after this..*/
191#if defined(LOGIN_CAP)
192			}
193			if (lc != NULL)
194				login_close(lc);
195#endif
196			chdir(env_get("HOME", e->envp));
197		}
198#if WANT_GLOBBING
199		execvp(gargv[0], gargv);
200#else
201		execvp(argv[0], argv);
202#endif
203		_exit(1);
204	}
205	/* parent; assume fdopen can't fail...  */
206	if (*type == 'r') {
207		iop = fdopen(pdes[0], type);
208		(void)close(pdes[1]);
209	} else {
210		iop = fdopen(pdes[1], type);
211		(void)close(pdes[0]);
212	}
213	pids[fileno(iop)] = pid;
214
215pfree:
216#if WANT_GLOBBING
217	for (argc = 1; argv[argc] != NULL; argc++) {
218/*		blkfree((char **)argv[argc]);	*/
219		free((char *)argv[argc]);
220	}
221#endif
222
223	*pidptr = pid;
224
225	return(iop);
226}
227
228int
229cron_pclose(iop)
230	FILE *iop;
231{
232	register int fdes;
233	int omask;
234	WAIT_T stat_loc;
235	PID_T pid;
236
237	/*
238	 * pclose returns -1 if stream is not associated with a
239	 * `popened' command, or, if already `pclosed'.
240	 */
241	if (pids == 0 || pids[fdes = fileno(iop)] == 0)
242		return(-1);
243	(void)fclose(iop);
244	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
245	while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1)
246		;
247	(void)sigsetmask(omask);
248	pids[fdes] = 0;
249	return (pid == -1 ? -1 : WEXITSTATUS(stat_loc));
250}
251