1 /*
2 ** Copyright 1998 - 2000 Double Precision, Inc.  See COPYING for
3 ** distribution information.
4 */
5 
6 /* Based on code by Christian Loitsch <courier-imap@abc.fgecko.com> */
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <errno.h>
12 
13 #include <unistd.h>
14 /* for fork */
15 #include <sys/types.h>
16 /*  used to avoid zombies */
17 #include <signal.h>
18 #include <sys/wait.h>
19 #include <sys/time.h>
20 #include <sys/select.h>
21 
22 #include	"auth.h"
23 #include	"authcustom.h"
24 #include	"courierauthdebug.h"
25 
26 #include	"courierauthdebug.h"
27 
28 #include	"authpipelib.h"
29 #include	"authpiperc.h"
30 
31 static int lastIn = -1;
32 static int lastOut = -1;
33 static pid_t childPID = -1;
34 
35 static void eliminatePipe(pid_t child);
36 
execChild(int to[],int from[])37 static void execChild(int to[], int from[])
38 {
39 	DPRINTF("executing %s", PIPE_PROGRAM);
40 
41 	close(STDIN_FILENO); dup2(to[0], STDIN_FILENO);
42 	close(STDOUT_FILENO); dup2(from[1], STDOUT_FILENO);
43 	close(to[0]); close(to[1]); close(from[0]); close(from[1]);
44 
45 	execl(PIPE_PROGRAM, PIPE_PROGRAM, NULL);
46 
47 	DPRINTF("pipe: failed to execute %s: %s",PIPE_PROGRAM, strerror(errno));
48 	exit(1);
49 }
50 
closePipe(void)51 void closePipe(void)
52 {
53 	DPRINTF("closing pipe");
54 	if (lastIn >= 0)	{ close(lastIn); lastIn = -1; }
55 	if (lastOut >= 0)	{ close (lastOut); lastOut = -1; }
56 	if (childPID > 1)	{ eliminatePipe(childPID); childPID = -1; }
57 }
58 
forkPipe(int * dataIn,int * dataOut,pid_t * child)59 static int forkPipe(int *dataIn, int *dataOut, pid_t *child)
60 {
61 	int to[2], from[2];
62 
63 	/* let's create 2 pipes */
64 	if(pipe(to) < 0) {
65 		DPRINTF("pipe: failed to create pipe: %s", strerror(errno));
66 		return 1;
67 	}
68 
69 	if(pipe(from) < 0) {
70 		DPRINTF("pipe: failed to create pipe: %s", strerror(errno));
71 		close(to[0]); close(to[1]);
72 		return 1;
73 	}
74 
75 	DPRINTF("attempting to fork");
76 	*child = fork();
77 	if(*child < 0) {
78 		DPRINTF("pipe: failed to fork: %s", strerror(errno));
79 		close(to[0]); close(to[1]); close(from[0]); close(from[1]);
80 		return 1;
81 	}
82 
83 	/* child */
84 	if(*child == 0) execChild(to, from);
85 
86 	/* parent */
87 	DPRINTF("Pipe auth. started Pipe-program (pid %d)", *child);
88 
89 	close(to[0]); close(from[1]);
90 	*dataIn = from[0]; *dataOut = to[1];
91 	return 0;
92 }
93 
94 /* kills and waits for child
95  * in a quite inefficient way, but this shouldn't happen very often */
eliminatePipe(pid_t child)96 static void eliminatePipe(pid_t child)
97 {
98 	unsigned int seconds;
99 
100 	/* let's first look, if child is already terminated */
101 	DPRINTF("trying to wait for child (WNOHANG) (pid %d)", child);
102 	if (waitpid(child, NULL, WNOHANG) > 0) return;
103 
104 	DPRINTF("sleep 2 seconds and try again to wait for pid %d", child);
105 	/* let's give the pipe-program a few seconds to terminate */
106 	sleep(2);  /* don't care if interrupted earlier */
107 	if (waitpid(child, NULL, WNOHANG) > 0) return;
108 
109 	/* let's TERM it */
110 	DPRINTF("killing (SIGTERM) child pid %d", child);
111 	kill(child, SIGTERM);
112 
113 	/* give it a few seconds */
114 	for (seconds = 10; seconds > 0; sleep(1), seconds--)
115 		if (waitpid(child, NULL, WNOHANG) > 0) return;
116 
117 	/* ok, let's KILL it */
118 	DPRINTF("killing (SIGKILL) child pid %d", child);
119 	if (kill(child, SIGKILL) == 0)
120 	{
121 		/* and wait, unless we have a kernel bug, it MUST terminate */
122 		DPRINTF("waitpiding for child pid (blocking!) %d)", child);
123 		waitpid(child, NULL, 0);
124 	}
125 	else
126 	{
127 
128 		DPRINTF("error when sending sigkill to %d", child);
129 		if (errno != ESRCH) return;
130 		/* strange, we can not kill our own child with SIGKILL*/
131 
132 		/* errno indicates process does not exist, maybe it's dead
133 		 * by now, let's try 1 final time, else, ignore it */
134 		DPRINTF("maybe because already dead (pid: %d)", child);
135 		waitpid(child, NULL, WNOHANG);
136 	}
137 }
138 
getPipe(int * dataIn,int * dataOut)139 int getPipe(int *dataIn, int *dataOut)
140 {
141 	int rv;
142 
143 	if (childPID > 1)
144 	{
145 		/* Simple test if the child is still usable: do a read
146 		** poll on dataIn. If the child has closed the pipe,
147 		** or there is spurious data, the fd will be ready. */
148 		fd_set fdr;
149 		struct timeval tv;
150 		FD_ZERO(&fdr);
151 		FD_SET(lastIn, &fdr);
152 		tv.tv_sec=0;
153 		tv.tv_usec=0;
154 		rv = select(lastIn+1, &fdr, 0, 0, &tv);
155 		if (rv == 0)
156 		{
157 			DPRINTF("reusing pipe, with in: %d out: %d", lastIn, lastOut);
158 			*dataIn = lastIn;
159 			*dataOut = lastOut;
160 			return 0;
161 		}
162 		if (rv < 0)
163 			perror("authpipe: getPipe: select");
164 		else
165 		{
166 			DPRINTF("child died or sent spurious data (pid: %d)", childPID);
167 		}
168 	}
169 
170 	/* ok pipe was not usable; either this is the first call, or
171 	 * the pipe broke the connection.
172 	 * We have to clean up and start a new one */
173 
174 	closePipe();
175 	DPRINTF("forking new one");
176 	rv = forkPipe(&lastIn, &lastOut, &childPID);
177 	if (rv)
178 	{
179 		DPRINTF("couldn't fork new pipe");
180 		lastIn = -1;
181 		lastOut = -1;
182 		childPID = -1;
183 	}
184 	else
185 	{
186 		DPRINTF("new pipe has in: %d, out: %d", lastIn, lastOut);
187 		*dataIn = lastIn;
188 		*dataOut = lastOut;
189 	}
190 	return rv;
191 }
192 
193