xref: /freebsd/usr.sbin/cron/cron/popen.c (revision c697fb7f)
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
28 static char sccsid[] = "@(#)popen.c	5.7 (Berkeley) 2/14/89";
29 #endif
30 static const char rcsid[] =
31   "$FreeBSD$";
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  */
54 static PID_T *pids;
55 static int fds;
56 
57 FILE *
58 cron_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 
215 pfree:
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 
228 int
229 cron_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