1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
2 /* camel-stream-process.c : stream over piped process
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * This library is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors: David Woodhouse <dwmw2@infradead.org>
19  *          Jeffrey Stedfast <fejj@ximian.com>
20  */
21 
22 #include "evolution-data-server-config.h"
23 
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <signal.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/ioctl.h>
32 #include <sys/socket.h>
33 #include <sys/types.h>
34 #include <sys/wait.h>
35 
36 #include <glib/gi18n-lib.h>
37 
38 #include "camel-file-utils.h"
39 #include "camel-stream-process.h"
40 
41 extern gint camel_verbose_debug;
42 
43 struct _CamelStreamProcessPrivate {
44 	gint sockfd;
45 	pid_t childpid;
46 };
47 
G_DEFINE_TYPE_WITH_PRIVATE(CamelStreamProcess,camel_stream_process,CAMEL_TYPE_STREAM)48 G_DEFINE_TYPE_WITH_PRIVATE (CamelStreamProcess, camel_stream_process, CAMEL_TYPE_STREAM)
49 
50 static void
51 stream_process_finalize (GObject *object)
52 {
53 	/* Ensure we clean up after ourselves -- kill
54 	 * the child process and reap it. */
55 	camel_stream_close (CAMEL_STREAM (object), NULL, NULL);
56 
57 	/* Chain up to parent's finalize() method. */
58 	G_OBJECT_CLASS (camel_stream_process_parent_class)->finalize (object);
59 }
60 
61 static gssize
stream_process_read(CamelStream * stream,gchar * buffer,gsize n,GCancellable * cancellable,GError ** error)62 stream_process_read (CamelStream *stream,
63                      gchar *buffer,
64                      gsize n,
65                      GCancellable *cancellable,
66                      GError **error)
67 {
68 	CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream);
69 	gint fd = stream_process->priv->sockfd;
70 
71 	return camel_read (fd, buffer, n, cancellable, error);
72 }
73 
74 static gssize
stream_process_write(CamelStream * stream,const gchar * buffer,gsize n,GCancellable * cancellable,GError ** error)75 stream_process_write (CamelStream *stream,
76                       const gchar *buffer,
77                       gsize n,
78                       GCancellable *cancellable,
79                       GError **error)
80 {
81 	CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream);
82 	gint fd = stream_process->priv->sockfd;
83 
84 	return camel_write (fd, buffer, n, cancellable, error);
85 }
86 
87 static gint
stream_process_close(CamelStream * object,GCancellable * cancellable,GError ** error)88 stream_process_close (CamelStream *object,
89                       GCancellable *cancellable,
90                       GError **error)
91 {
92 	CamelStreamProcess *stream = CAMEL_STREAM_PROCESS (object);
93 
94 	if (camel_verbose_debug)
95 		fprintf (
96 			stderr,
97 			"Process stream close. sockfd %d, childpid %d\n",
98 			stream->priv->sockfd, stream->priv->childpid);
99 
100 	if (stream->priv->sockfd != -1) {
101 		close (stream->priv->sockfd);
102 		stream->priv->sockfd = -1;
103 	}
104 
105 	if (stream->priv->childpid) {
106 		gint ret, i;
107 		for (i = 0; i < 4; i++) {
108 			ret = waitpid (stream->priv->childpid, NULL, WNOHANG);
109 			if (camel_verbose_debug)
110 				fprintf (
111 					stderr,
112 					"waitpid() for pid %d returned %d (errno %d)\n",
113 					stream->priv->childpid, ret, ret == -1 ? errno : 0);
114 			if (ret == stream->priv->childpid || errno == ECHILD)
115 				break;
116 			switch (i) {
117 			case 0:
118 				if (camel_verbose_debug)
119 					fprintf (
120 						stderr,
121 						"Sending SIGTERM to pid %d\n",
122 						stream->priv->childpid);
123 				kill (stream->priv->childpid, SIGTERM);
124 				break;
125 			case 2:
126 				if (camel_verbose_debug)
127 					fprintf (
128 						stderr,
129 						"Sending SIGKILL to pid %d\n",
130 						stream->priv->childpid);
131 				kill (stream->priv->childpid, SIGKILL);
132 				break;
133 			case 1:
134 			case 3:
135 				sleep (1);
136 				break;
137 			}
138 		}
139 
140 		stream->priv->childpid = 0;
141 	}
142 
143 	return 0;
144 }
145 
146 static gint
stream_process_flush(CamelStream * stream,GCancellable * cancellable,GError ** error)147 stream_process_flush (CamelStream *stream,
148                       GCancellable *cancellable,
149                       GError **error)
150 {
151 	return 0;
152 }
153 
154 static void
camel_stream_process_class_init(CamelStreamProcessClass * class)155 camel_stream_process_class_init (CamelStreamProcessClass *class)
156 {
157 	GObjectClass *object_class;
158 	CamelStreamClass *stream_class;
159 
160 	object_class = G_OBJECT_CLASS (class);
161 	object_class->finalize = stream_process_finalize;
162 
163 	stream_class = CAMEL_STREAM_CLASS (class);
164 	stream_class->read = stream_process_read;
165 	stream_class->write = stream_process_write;
166 	stream_class->close = stream_process_close;
167 	stream_class->flush = stream_process_flush;
168 }
169 
170 static void
camel_stream_process_init(CamelStreamProcess * stream)171 camel_stream_process_init (CamelStreamProcess *stream)
172 {
173 	stream->priv = camel_stream_process_get_instance_private (stream);
174 	stream->priv->sockfd = -1;
175 	stream->priv->childpid = 0;
176 }
177 
178 /**
179  * camel_stream_process_new:
180  *
181  * Returns a PROCESS stream.
182  *
183  * Returns: the stream
184  **/
185 CamelStream *
camel_stream_process_new(void)186 camel_stream_process_new (void)
187 {
188 	return g_object_new (CAMEL_TYPE_STREAM_PROCESS, NULL);
189 }
190 
191 G_GNUC_NORETURN static void
do_exec_command(gint fd,const gchar * command,gchar ** env)192 do_exec_command (gint fd,
193                  const gchar *command,
194                  gchar **env)
195 {
196 	gint i, maxopen;
197 
198 	/* Not a lot we can do if there's an error other than bail. */
199 	if (dup2 (fd, 0) == -1)
200 		exit (1);
201 	if (dup2 (fd, 1) == -1)
202 		exit (1);
203 
204 	/* What to do with stderr? Possibly put it through a separate pipe
205 	 * and bring up a dialog box with its output if anything does get
206 	 * spewed to it? It'd help the user understand what was going wrong
207 	 * with their command, but it's hard to do cleanly. For now we just
208 	 * leave it as it is. Perhaps we should close it and reopen /dev/null? */
209 
210 	maxopen = sysconf (_SC_OPEN_MAX);
211 	for (i = 3; i < maxopen; i++) {
212 		if (fcntl (i, F_SETFD, FD_CLOEXEC) == -1 && errno != EBADF) {
213 			/* Would g_debug() this, but it can cause deadlock on mutexes
214 			   in GLib in certain situations, thus rather ignore it at all.
215 			   It's also quite likely, definitely in the early stage, that
216 			   most of the file descriptors are not valid anyway. */
217 			/* g_debug ("%s: Call of 'fcntl (%d, F_SETFD, FD_CLOEXEC)' failed: %s", G_STRFUNC, i, g_strerror (errno)); */
218 		}
219 	}
220 
221 	setsid ();
222 #ifdef TIOCNOTTY
223 	/* Detach from the controlling tty if we have one. Otherwise,
224 	 * SSH might do something stupid like trying to use it instead
225 	 * of running $SSH_ASKPASS. Doh. */
226 	if ((fd = open ("/dev/tty", O_RDONLY)) != -1) {
227 		ioctl (fd, TIOCNOTTY, NULL);
228 		close (fd);
229 	}
230 #endif /* TIOCNOTTY */
231 
232 	/* Set up child's environment. We _add_ to it, don't use execle,
233 	 * because otherwise we'd destroy stuff like SSH_AUTH_SOCK etc. */
234 	for (; env && *env; env++)
235 		putenv (*env);
236 
237 	execl ("/bin/sh", "/bin/sh", "-c", command, NULL);
238 
239 	if (camel_verbose_debug)
240 		fprintf (stderr, "exec failed %d\n", errno);
241 
242 	exit (1);
243 }
244 
245 gint
camel_stream_process_connect(CamelStreamProcess * stream,const gchar * command,const gchar ** env,GError ** error)246 camel_stream_process_connect (CamelStreamProcess *stream,
247                               const gchar *command,
248                               const gchar **env,
249                               GError **error)
250 {
251 	gint sockfds[2];
252 
253 	g_return_val_if_fail (CAMEL_IS_STREAM_PROCESS (stream), -1);
254 	g_return_val_if_fail (command != NULL, -1);
255 
256 	if (stream->priv->sockfd != -1 || stream->priv->childpid)
257 		camel_stream_close (CAMEL_STREAM (stream), NULL, NULL);
258 
259 	if (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfds))
260 		goto fail;
261 
262 	stream->priv->childpid = fork ();
263 	if (!stream->priv->childpid) {
264 		do_exec_command (sockfds[1], command, (gchar **) env);
265 	} else if (stream->priv->childpid == -1) {
266 		close (sockfds[0]);
267 		close (sockfds[1]);
268 		stream->priv->sockfd = -1;
269 		goto fail;
270 	}
271 
272 	close (sockfds[1]);
273 	stream->priv->sockfd = sockfds[0];
274 
275 	return 0;
276 
277 fail:
278 	if (errno == EINTR)
279 		g_set_error (
280 			error, G_IO_ERROR,
281 			G_IO_ERROR_CANCELLED,
282 			_("Connection cancelled"));
283 	else
284 		g_set_error (
285 			error, G_IO_ERROR,
286 			g_io_error_from_errno (errno),
287 			_("Could not connect with command “%s”: %s"),
288 			command, g_strerror (errno));
289 
290 	return -1;
291 }
292