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: src/usr.sbin/cron/cron/popen.c,v 1.15 2006/06/11 21:13:49 maxim Exp $";
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 noone
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)
59	char *program, *type;
60	entry *e;
61{
62	register char *cp;
63	FILE *iop;
64	int argc, pdes[2];
65	PID_T pid;
66#ifndef __APPLE__
67	char *usernm;
68#endif
69	char *argv[MAX_ARGS + 1];
70# if defined(LOGIN_CAP)
71	struct passwd	*pwd;
72	login_cap_t *lc;
73# endif
74#if WANT_GLOBBING
75	char **pop, *vv[2];
76	int gargc;
77	char *gargv[1000];
78	extern char **glob(), **copyblk();
79#endif
80
81	if ((*type != 'r' && *type != 'w') || type[1])
82		return(NULL);
83
84	if (!pids) {
85		if ((fds = getdtablesize()) <= 0)
86			return(NULL);
87		if (!(pids = (PID_T *)malloc((u_int)(fds * sizeof(PID_T)))))
88			return(NULL);
89		bzero((char *)pids, fds * sizeof(PID_T));
90	}
91	if (pipe(pdes) < 0)
92		return(NULL);
93
94	/* break up string into pieces */
95	for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL)
96		if (!(argv[argc++] = strtok(cp, " \t\n")))
97			break;
98	argv[MAX_ARGS] = NULL;
99
100#if WANT_GLOBBING
101	/* glob each piece */
102	gargv[0] = argv[0];
103	for (gargc = argc = 1; argv[argc]; argc++) {
104		if (!(pop = glob(argv[argc]))) {	/* globbing failed */
105			vv[0] = argv[argc];
106			vv[1] = NULL;
107			pop = copyblk(vv);
108		}
109		argv[argc] = (char *)pop;		/* save to free later */
110		while (*pop && gargc < 1000)
111			gargv[gargc++] = *pop++;
112	}
113	gargv[gargc] = NULL;
114#endif
115
116	iop = NULL;
117	switch(pid = vfork()) {
118	case -1:			/* error */
119		(void)close(pdes[0]);
120		(void)close(pdes[1]);
121		goto pfree;
122		/* NOTREACHED */
123	case 0:				/* child */
124		if (e != NULL) {
125#ifdef SYSLOG
126			closelog();
127#endif
128
129			/* get new pgrp, void tty, etc.
130			 */
131			(void) setsid();
132		}
133		if (*type == 'r') {
134			/* Do not share our parent's stdin */
135			(void)close(0);
136			(void)open(_PATH_DEVNULL, O_RDWR);
137			if (pdes[1] != 1) {
138				dup2(pdes[1], 1);
139				dup2(pdes[1], 2);	/* stderr, too! */
140				(void)close(pdes[1]);
141			}
142			(void)close(pdes[0]);
143		} else {
144			if (pdes[0] != 0) {
145				dup2(pdes[0], 0);
146				(void)close(pdes[0]);
147			}
148			/* Hack: stdout gets revoked */
149			(void)close(1);
150			(void)open(_PATH_DEVNULL, O_RDWR);
151			(void)close(2);
152			(void)open(_PATH_DEVNULL, O_RDWR);
153			(void)close(pdes[1]);
154		}
155		if (e != NULL) {
156			/* Set user's entire context, but skip the environment
157			 * as cron provides a separate interface for this
158			 */
159#ifdef __APPLE__
160			struct passwd *pwd;
161			uid_t uid;
162			gid_t gid;
163
164			if ((pwd = getpwnam(e->uname))) {
165				char envstr[MAXPATHLEN + sizeof "HOME="];
166
167				uid = pwd->pw_uid;
168				gid = pwd->pw_gid;
169
170				if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
171					warn("user account expired: %s", e->uname);
172					_exit(ERROR_EXIT);
173				}
174
175				sprintf(envstr, "HOME=%s", pwd->pw_dir);
176				e->envp = env_set(e->envp, envstr);
177				if (e->envp == NULL) {
178					warn("env_set(%s)", envstr);
179					_exit(ERROR_EXIT);
180				}
181			} else {
182				warn("getpwnam(\"%s\")", e->uname);
183				_exit(ERROR_EXIT);
184			}
185
186			if (strlen(e->gname) > 0) {
187				struct group *gr = getgrnam(e->gname);
188				if (gr) {
189					gid = gr->gr_gid;
190				} else {
191					warn("getgrnam(\"%s\")", e->gname);
192					_exit(ERROR_EXIT);
193				}
194			}
195#else /* __APPLE__ */
196			usernm = env_get("LOGNAME", e->envp);
197#endif /* __APPLE__ */
198# if defined(LOGIN_CAP)
199			if ((pwd = getpwnam(usernm)) == NULL)
200				pwd = getpwuid(e->uid);
201			lc = NULL;
202			if (pwd != NULL) {
203				pwd->pw_gid = e->gid;
204				if (e->class != NULL)
205					lc = login_getclass(e->class);
206			}
207			if (pwd &&
208			    setusercontext(lc, pwd, e->uid,
209				    LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
210				(void) endpwent();
211			else {
212				/* fall back to the old method */
213				(void) endpwent();
214# endif
215				/*
216				 * Set our directory, uid and gid.  Set gid
217				 * first since once we set uid, we've lost
218				 * root privileges.
219				 */
220#ifdef __APPLE__
221				if(setgid(gid)) {
222					warn("setgid failed");
223					_exit(ERROR_EXIT);
224				}
225# if defined(BSD)
226				if (initgroups(e->uname, gid) != 0) {
227					warn("initgroups failed");
228					_exit(ERROR_EXIT);
229				}
230# endif
231				if (setlogin(e->uname) != 0) {
232					warn("setlogin failed");
233					_exit(ERROR_EXIT);
234				}
235				if(setuid(uid))	{	/* we aren't root after this..*/
236					warn("setuid failed");
237					_exit(ERROR_EXIT);
238				}
239#else /* __APPLE__ */
240				if (setgid(e->gid) != 0)
241					_exit(ERROR_EXIT);
242# if defined(BSD)
243				if (initgroups(usernm, e->gid) != 0)
244					_exit(ERROR_EXIT);
245# endif
246				if (setlogin(usernm) != 0)
247					_exit(ERROR_EXIT);
248				if (setuid(e->uid) != 0)
249					_exit(ERROR_EXIT);
250				/* we aren't root after this..*/
251#endif /* __APPLE__ */
252#if defined(LOGIN_CAP)
253			}
254			if (lc != NULL)
255				login_close(lc);
256#endif
257			chdir(env_get("HOME", e->envp));
258		}
259#if WANT_GLOBBING
260		execvp(gargv[0], gargv);
261#else
262		execvp(argv[0], argv);
263#endif
264		_exit(1);
265	}
266	/* parent; assume fdopen can't fail...  */
267	if (*type == 'r') {
268		iop = fdopen(pdes[0], type);
269		(void)close(pdes[1]);
270	} else {
271		iop = fdopen(pdes[1], type);
272		(void)close(pdes[0]);
273	}
274	pids[fileno(iop)] = pid;
275
276pfree:
277#if WANT_GLOBBING
278	for (argc = 1; argv[argc] != NULL; argc++) {
279/*		blkfree((char **)argv[argc]);	*/
280		free((char *)argv[argc]);
281	}
282#endif
283	return(iop);
284}
285
286int
287cron_pclose(iop)
288	FILE *iop;
289{
290	register int fdes;
291	int omask;
292	WAIT_T stat_loc;
293	PID_T pid;
294
295	/*
296	 * pclose returns -1 if stream is not associated with a
297	 * `popened' command, or, if already `pclosed'.
298	 */
299	if (pids == 0 || pids[fdes = fileno(iop)] == 0)
300		return(-1);
301	(void)fclose(iop);
302	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
303	while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1)
304		;
305	(void)sigsetmask(omask);
306	pids[fdes] = 0;
307	return (pid == -1 ? -1 : WEXITSTATUS(stat_loc));
308}
309