xref: /openbsd/usr.bin/mail/popen.c (revision 5b133f3f)
1 /*	$OpenBSD: popen.c,v 1.40 2023/03/08 04:43:11 guenther Exp $	*/
2 /*	$NetBSD: popen.c,v 1.6 1997/05/13 06:48:42 mikel Exp $	*/
3 
4 /*
5  * Copyright (c) 1980, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include "rcv.h"
34 #include <sys/wait.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #include <stdarg.h>
38 #include "extern.h"
39 
40 #define READ 0
41 #define WRITE 1
42 
43 struct fp {
44 	FILE *fp;
45 	int pipe;
46 	pid_t pid;
47 	struct fp *link;
48 };
49 static struct fp *fp_head;
50 
51 struct child {
52 	pid_t pid;
53 	char done;
54 	char free;
55 	int status;
56 	struct child *link;
57 };
58 static struct child *child, *child_freelist = NULL;
59 
60 static struct child *findchild(pid_t, int);
61 static void delchild(struct child *);
62 static pid_t file_pid(FILE *);
63 static int handle_spool_locks(int);
64 
65 FILE *
Fopen(char * file,char * mode)66 Fopen(char *file, char *mode)
67 {
68 	FILE *fp;
69 
70 	if ((fp = fopen(file, mode)) != NULL) {
71 		register_file(fp, 0, 0);
72 		(void)fcntl(fileno(fp), F_SETFD, FD_CLOEXEC);
73 	}
74 	return(fp);
75 }
76 
77 FILE *
Fdopen(int fd,char * mode)78 Fdopen(int fd, char *mode)
79 {
80 	FILE *fp;
81 
82 	if ((fp = fdopen(fd, mode)) != NULL) {
83 		register_file(fp, 0, 0);
84 		(void)fcntl(fileno(fp), F_SETFD, FD_CLOEXEC);
85 	}
86 	return(fp);
87 }
88 
89 int
Fclose(FILE * fp)90 Fclose(FILE *fp)
91 {
92 
93 	unregister_file(fp);
94 	return(fclose(fp));
95 }
96 
97 FILE *
Popen(char * cmd,char * mode)98 Popen(char *cmd, char *mode)
99 {
100 	int p[2];
101 	int myside, hisside, fd0, fd1;
102 	pid_t pid;
103 	sigset_t nset;
104 	FILE *fp;
105 
106 	if (pipe(p) == -1)
107 		return(NULL);
108 	(void)fcntl(p[READ], F_SETFD, FD_CLOEXEC);
109 	(void)fcntl(p[WRITE], F_SETFD, FD_CLOEXEC);
110 	if (*mode == 'r') {
111 		myside = p[READ];
112 		hisside = fd0 = fd1 = p[WRITE];
113 	} else {
114 		myside = p[WRITE];
115 		hisside = fd0 = p[READ];
116 		fd1 = -1;
117 	}
118 	sigemptyset(&nset);
119 	pid = start_command(value("SHELL"), &nset, fd0, fd1, "-c", cmd, NULL);
120 	if (pid < 0) {
121 		(void)close(p[READ]);
122 		(void)close(p[WRITE]);
123 		return(NULL);
124 	}
125 	(void)close(hisside);
126 	if ((fp = fdopen(myside, mode)) != NULL)
127 		register_file(fp, 1, pid);
128 	return(fp);
129 }
130 
131 int
Pclose(FILE * ptr)132 Pclose(FILE *ptr)
133 {
134 	int i;
135 	sigset_t nset, oset;
136 
137 	i = file_pid(ptr);
138 	unregister_file(ptr);
139 	(void)fclose(ptr);
140 	sigemptyset(&nset);
141 	sigaddset(&nset, SIGINT);
142 	sigaddset(&nset, SIGHUP);
143 	sigprocmask(SIG_BLOCK, &nset, &oset);
144 	i = wait_child(i);
145 	sigprocmask(SIG_SETMASK, &oset, NULL);
146 	return(i);
147 }
148 
149 void
close_all_files(void)150 close_all_files(void)
151 {
152 
153 	while (fp_head)
154 		if (fp_head->pipe)
155 			(void)Pclose(fp_head->fp);
156 		else
157 			(void)Fclose(fp_head->fp);
158 }
159 
160 void
register_file(FILE * fp,int pipe,pid_t pid)161 register_file(FILE *fp, int pipe, pid_t pid)
162 {
163 	struct fp *fpp;
164 
165 	if ((fpp = malloc(sizeof(*fpp))) == NULL)
166 		err(1, "malloc");
167 	fpp->fp = fp;
168 	fpp->pipe = pipe;
169 	fpp->pid = pid;
170 	fpp->link = fp_head;
171 	fp_head = fpp;
172 }
173 
174 void
unregister_file(FILE * fp)175 unregister_file(FILE *fp)
176 {
177 	struct fp **pp, *p;
178 
179 	for (pp = &fp_head; (p = *pp) != NULL; pp = &p->link)
180 		if (p->fp == fp) {
181 			*pp = p->link;
182 			(void)free(p);
183 			return;
184 		}
185 	errx(1, "Invalid file pointer");
186 }
187 
188 static pid_t
file_pid(FILE * fp)189 file_pid(FILE *fp)
190 {
191 	struct fp *p;
192 
193 	for (p = fp_head; p; p = p->link)
194 		if (p->fp == fp)
195 			return(p->pid);
196 	errx(1, "Invalid file pointer");
197 	/*NOTREACHED*/
198 }
199 
200 /*
201  * Run a command without a shell, with optional arguments and splicing
202  * of stdin (-1 means none) and stdout.  The command name can be a sequence
203  * of words.
204  * Signals must be handled by the caller.
205  * "nset" contains the signals to ignore in the new process.
206  * SIGINT is enabled unless it's in "nset".
207  */
208 pid_t
start_commandv(char * cmd,sigset_t * nset,int infd,int outfd,va_list args)209 start_commandv(char *cmd, sigset_t *nset, int infd, int outfd, va_list args)
210 {
211 	pid_t pid;
212 
213 	if ((pid = fork()) == -1) {
214 		warn("fork");
215 		return(-1);
216 	}
217 	if (pid == 0) {
218 		char *argv[100];
219 		int i = getrawlist(cmd, argv, sizeof(argv)/ sizeof(*argv));
220 
221 		while ((argv[i++] = va_arg(args, char *)))
222 			;
223 		argv[i] = NULL;
224 		prepare_child(nset, infd, outfd);
225 		execvp(argv[0], argv);
226 		warn("%s", argv[0]);
227 		_exit(1);
228 	}
229 	return(pid);
230 }
231 
232 int
run_command(char * cmd,sigset_t * nset,int infd,int outfd,...)233 run_command(char *cmd, sigset_t *nset, int infd, int outfd, ...)
234 {
235 	pid_t pid;
236 	va_list args;
237 
238 	va_start(args, outfd);
239 	pid = start_commandv(cmd, nset, infd, outfd, args);
240 	va_end(args);
241 	if (pid < 0)
242 		return(-1);
243 	return(wait_command(pid));
244 }
245 
246 int
start_command(char * cmd,sigset_t * nset,int infd,int outfd,...)247 start_command(char *cmd, sigset_t *nset, int infd, int outfd, ...)
248 {
249 	va_list args;
250 	int r;
251 
252 	va_start(args, outfd);
253 	r = start_commandv(cmd, nset, infd, outfd, args);
254 	va_end(args);
255 	return(r);
256 }
257 
258 void
prepare_child(sigset_t * nset,int infd,int outfd)259 prepare_child(sigset_t *nset, int infd, int outfd)
260 {
261 	int i;
262 	sigset_t eset;
263 
264 	/*
265 	 * All file descriptors other than 0, 1, and 2 are supposed to be
266 	 * close-on-exec.
267 	 */
268 	if (infd > 0) {
269 		dup2(infd, 0);
270 	} else if (infd != 0) {
271 		/* we don't want the child stealing my stdin input */
272 		close(0);
273 		open(_PATH_DEVNULL, O_RDONLY, 0);
274 	}
275 	if (outfd >= 0 && outfd != 1)
276 		dup2(outfd, 1);
277 	if (nset == NULL)
278 		return;
279 	if (nset != NULL) {
280 		for (i = 1; i < NSIG; i++)
281 			if (sigismember(nset, i))
282 				(void)signal(i, SIG_IGN);
283 	}
284 	if (nset == NULL || !sigismember(nset, SIGINT))
285 		(void)signal(SIGINT, SIG_DFL);
286 	sigemptyset(&eset);
287 	(void)sigprocmask(SIG_SETMASK, &eset, NULL);
288 }
289 
290 int
wait_command(pid_t pid)291 wait_command(pid_t pid)
292 {
293 
294 	if (wait_child(pid) < 0) {
295 		puts("Fatal error in process.");
296 		return(-1);
297 	}
298 	return(0);
299 }
300 
301 static struct child *
findchild(pid_t pid,int dont_alloc)302 findchild(pid_t pid, int dont_alloc)
303 {
304 	struct child **cpp;
305 
306 	for (cpp = &child; *cpp != NULL && (*cpp)->pid != pid;
307 	     cpp = &(*cpp)->link)
308 			;
309 	if (*cpp == NULL) {
310 		if (dont_alloc)
311 			return(NULL);
312 		if (child_freelist) {
313 			*cpp = child_freelist;
314 			child_freelist = (*cpp)->link;
315 		} else {
316 			*cpp = malloc(sizeof(struct child));
317 			if (*cpp == NULL)
318 				err(1, "malloc");
319 		}
320 		(*cpp)->pid = pid;
321 		(*cpp)->done = (*cpp)->free = 0;
322 		(*cpp)->link = NULL;
323 	}
324 	return(*cpp);
325 }
326 
327 static void
delchild(struct child * cp)328 delchild(struct child *cp)
329 {
330 	struct child **cpp;
331 
332 	for (cpp = &child; *cpp != cp; cpp = &(*cpp)->link)
333 		;
334 	*cpp = cp->link;
335 	cp->link = child_freelist;
336 	child_freelist = cp;
337 }
338 
339 void
sigchild(int signo)340 sigchild(int signo)
341 {
342 	pid_t pid;
343 	int status;
344 	struct child *cp;
345 	int save_errno = errno;
346 
347 	while ((pid = waitpid((pid_t)-1, &status, WNOHANG)) > 0) {
348 		cp = findchild(pid, 1);
349 		if (!cp)
350 			continue;
351 		if (cp->free)
352 			delchild(cp);
353 		else {
354 			cp->done = 1;
355 			cp->status = status;
356 		}
357 	}
358 	errno = save_errno;
359 }
360 
361 int wait_status;
362 
363 /*
364  * Wait for a specific child to die.
365  */
366 int
wait_child(pid_t pid)367 wait_child(pid_t pid)
368 {
369 	struct child *cp;
370 	sigset_t nset, oset;
371 	pid_t rv = 0;
372 
373 	sigemptyset(&nset);
374 	sigaddset(&nset, SIGCHLD);
375 	sigprocmask(SIG_BLOCK, &nset, &oset);
376 	/*
377 	 * If we have not already waited on the pid (via sigchild)
378 	 * wait on it now.  Otherwise, use the wait status stashed
379 	 * by sigchild.
380 	 */
381 	cp = findchild(pid, 1);
382 	if (cp == NULL || !cp->done)
383 		rv = waitpid(pid, &wait_status, 0);
384 	else
385 		wait_status = cp->status;
386 	if (cp != NULL)
387 		delchild(cp);
388 	sigprocmask(SIG_SETMASK, &oset, NULL);
389 	if (rv == -1 || (WIFEXITED(wait_status) && WEXITSTATUS(wait_status)))
390 		return(-1);
391 	else
392 		return(0);
393 }
394 
395 /*
396  * Mark a child as don't care.
397  */
398 void
free_child(pid_t pid)399 free_child(pid_t pid)
400 {
401 	struct child *cp;
402 	sigset_t nset, oset;
403 
404 	sigemptyset(&nset);
405 	sigaddset(&nset, SIGCHLD);
406 	sigprocmask(SIG_BLOCK, &nset, &oset);
407 	if ((cp = findchild(pid, 0)) != NULL) {
408 		if (cp->done)
409 			delchild(cp);
410 		else
411 			cp->free = 1;
412 	}
413 	sigprocmask(SIG_SETMASK, &oset, NULL);
414 }
415 
416 /*
417  * Lock(1)/unlock(0) mail spool using lockspool(1).
418  * Returns 1 for success, 0 for failure, -1 for bad usage.
419  */
420 static int
handle_spool_locks(int action)421 handle_spool_locks(int action)
422 {
423 	static FILE *lockfp = NULL;
424 
425 	if (action == 0) {
426 		/* Clear the lock */
427 		if (lockfp == NULL) {
428 			fputs("handle_spool_locks: no spool lock to remove.\n",
429 			    stderr);
430 			return(-1);
431 		}
432 		(void)Pclose(lockfp);
433 		lockfp = NULL;
434 	} else if (action == 1) {
435 		char *cmd;
436 		char buf[sizeof(_PATH_LOCKSPOOL) + LOGIN_NAME_MAX + 1];
437 
438 		/* XXX - lockspool requires root for user arg, we do not */
439 		if (uflag) {
440 			snprintf(buf, sizeof(buf), "%s %s", _PATH_LOCKSPOOL,
441 			    myname);
442 			cmd = buf;
443 		} else
444 			cmd = _PATH_LOCKSPOOL;
445 
446 		/* Create the lock */
447 		lockfp = Popen(cmd, "r");
448 		if (lockfp == NULL)
449 			return(0);
450 		if (getc(lockfp) != '1') {
451 			Pclose(lockfp);
452 			lockfp = NULL;
453 			return(0);
454 		}
455 	} else {
456 		(void)fprintf(stderr, "handle_spool_locks: unknown action %d\n",
457 		    action);
458 		return(-1);
459 	}
460 
461 	return(1);
462 }
463 
464 int
spool_lock(void)465 spool_lock(void)
466 {
467 
468 	return(handle_spool_locks(1));
469 }
470 
471 int
spool_unlock(void)472 spool_unlock(void)
473 {
474 
475 	return(handle_spool_locks(0));
476 }
477