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