xref: /freebsd/usr.sbin/cron/cron/popen.c (revision 4b9d6057)
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 static const char rcsid[] =
28     "$Id: popen.c,v 1.3 1998/08/14 00:32:41 vixie Exp $";
29 #endif /* not lint */
30 
31 #include "cron.h"
32 #if defined(LOGIN_CAP)
33 # include <login_cap.h>
34 #endif
35 
36 #define MAX_ARGS 100
37 #define WANT_GLOBBING 0
38 
39 /*
40  * Special version of popen which avoids call to shell.  This insures no one
41  * may create a pipe to a hidden program as a side effect of a list or dir
42  * command.
43  */
44 static PID_T *pids;
45 static int fds;
46 
47 FILE *
48 cron_popen(char *program, char *type, entry *e, PID_T *pidptr)
49 {
50 	char *cp;
51 	FILE *iop;
52 	int argc, pdes[2];
53 	PID_T pid;
54 	char *usernm;
55 	char *argv[MAX_ARGS + 1];
56 # if defined(LOGIN_CAP)
57 	struct passwd	*pwd;
58 	login_cap_t *lc;
59 # endif
60 #if WANT_GLOBBING
61 	char **pop, *vv[2];
62 	int gargc;
63 	char *gargv[1000];
64 	extern char **glob(), **copyblk();
65 #endif
66 
67 	if ((*type != 'r' && *type != 'w') || type[1] != '\0')
68 		return (NULL);
69 
70 	if (!pids) {
71 		if ((fds = sysconf(_SC_OPEN_MAX)) <= 0)
72 			return (NULL);
73 		if (!(pids = calloc(fds, sizeof(PID_T))))
74 			return (NULL);
75 	}
76 	if (pipe(pdes) < 0)
77 		return (NULL);
78 
79 	/* break up string into pieces */
80 	for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL)
81 		if (!(argv[argc++] = strtok(cp, " \t\n")))
82 			break;
83 	argv[MAX_ARGS] = NULL;
84 
85 #if WANT_GLOBBING
86 	/* glob each piece */
87 	gargv[0] = argv[0];
88 	for (gargc = argc = 1; argv[argc]; argc++) {
89 		if (!(pop = glob(argv[argc]))) {	/* globbing failed */
90 			vv[0] = argv[argc];
91 			vv[1] = NULL;
92 			pop = copyblk(vv);
93 		}
94 		argv[argc] = (char *)pop;		/* save to free later */
95 		while (*pop && gargc < 1000)
96 			gargv[gargc++] = *pop++;
97 	}
98 	gargv[gargc] = NULL;
99 #endif
100 
101 	iop = NULL;
102 	switch(pid = fork()) {
103 	case -1:			/* error */
104 		(void)close(pdes[0]);
105 		(void)close(pdes[1]);
106 		goto pfree;
107 		/* NOTREACHED */
108 	case 0:				/* child */
109 		if (e != NULL) {
110 #ifdef SYSLOG
111 			closelog();
112 #endif
113 
114 			/* get new pgrp, void tty, etc.
115 			 */
116 			(void) setsid();
117 		}
118 		if (*type == 'r') {
119 			/* Do not share our parent's stdin */
120 			(void)close(0);
121 			(void)open(_PATH_DEVNULL, O_RDWR);
122 			if (pdes[1] != 1) {
123 				dup2(pdes[1], 1);
124 				dup2(pdes[1], 2);	/* stderr, too! */
125 				(void)close(pdes[1]);
126 			}
127 			(void)close(pdes[0]);
128 		} else {
129 			if (pdes[0] != 0) {
130 				dup2(pdes[0], 0);
131 				(void)close(pdes[0]);
132 			}
133 			/* Hack: stdout gets revoked */
134 			(void)close(1);
135 			(void)open(_PATH_DEVNULL, O_RDWR);
136 			(void)close(2);
137 			(void)open(_PATH_DEVNULL, O_RDWR);
138 			(void)close(pdes[1]);
139 		}
140 		if (e != NULL) {
141 			/* Set user's entire context, but skip the environment
142 			 * as cron provides a separate interface for this
143 			 */
144 			usernm = env_get("LOGNAME", e->envp);
145 # if defined(LOGIN_CAP)
146 			if ((pwd = getpwnam(usernm)) == NULL)
147 				pwd = getpwuid(e->uid);
148 			lc = NULL;
149 			if (pwd != NULL) {
150 				pwd->pw_gid = e->gid;
151 				if (e->class != NULL)
152 					lc = login_getclass(e->class);
153 			}
154 			if (pwd &&
155 			    setusercontext(lc, pwd, e->uid,
156 				    LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
157 				(void) endpwent();
158 			else {
159 				/* fall back to the old method */
160 				(void) endpwent();
161 # endif
162 				/*
163 				 * Set our directory, uid and gid.  Set gid
164 				 * first since once we set uid, we've lost
165 				 * root privileges.
166 				 */
167 				if (setgid(e->gid) != 0)
168 					_exit(ERROR_EXIT);
169 # if defined(BSD)
170 				if (initgroups(usernm, e->gid) != 0)
171 					_exit(ERROR_EXIT);
172 # endif
173 				if (setlogin(usernm) != 0)
174 					_exit(ERROR_EXIT);
175 				if (setuid(e->uid) != 0)
176 					_exit(ERROR_EXIT);
177 				/* we aren't root after this..*/
178 #if defined(LOGIN_CAP)
179 			}
180 			if (lc != NULL)
181 				login_close(lc);
182 #endif
183 			chdir(env_get("HOME", e->envp));
184 		}
185 #if WANT_GLOBBING
186 		execvp(gargv[0], gargv);
187 #else
188 		execvp(argv[0], argv);
189 #endif
190 		_exit(1);
191 	}
192 	/* parent; assume fdopen can't fail...  */
193 	if (*type == 'r') {
194 		iop = fdopen(pdes[0], type);
195 		(void)close(pdes[1]);
196 	} else {
197 		iop = fdopen(pdes[1], type);
198 		(void)close(pdes[0]);
199 	}
200 	pids[fileno(iop)] = pid;
201 
202 pfree:
203 #if WANT_GLOBBING
204 	for (argc = 1; argv[argc] != NULL; argc++) {
205 /*		blkfree((char **)argv[argc]);	*/
206 		free((char *)argv[argc]);
207 	}
208 #endif
209 
210 	*pidptr = pid;
211 
212 	return (iop);
213 }
214 
215 int
216 cron_pclose(FILE *iop)
217 {
218 	int fdes;
219 	int omask;
220 	WAIT_T stat_loc;
221 	PID_T pid;
222 
223 	/*
224 	 * pclose returns -1 if stream is not associated with a
225 	 * `popened' command, or, if already `pclosed'.
226 	 */
227 	if (pids == 0 || pids[fdes = fileno(iop)] == 0)
228 		return (-1);
229 	(void)fclose(iop);
230 	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
231 	while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1)
232 		;
233 	(void)sigsetmask(omask);
234 	pids[fdes] = 0;
235 	return (pid == -1 ? -1 : WEXITSTATUS(stat_loc));
236 }
237