1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-sendmail-transport.c: Sendmail-based transport class
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: Dan Winship <danw@ximian.com>
19  */
20 
21 #include "evolution-data-server-config.h"
22 
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <signal.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/wait.h>
29 
30 #include <glib/gi18n-lib.h>
31 
32 #include "camel-sendmail-settings.h"
33 #include "camel-sendmail-transport.h"
34 
G_DEFINE_TYPE(CamelSendmailTransport,camel_sendmail_transport,CAMEL_TYPE_TRANSPORT)35 G_DEFINE_TYPE (
36 	CamelSendmailTransport,
37 	camel_sendmail_transport, CAMEL_TYPE_TRANSPORT)
38 
39 static gchar *
40 sendmail_get_name (CamelService *service,
41                    gboolean brief)
42 {
43 	if (brief)
44 		return g_strdup (_("sendmail"));
45 	else
46 		return g_strdup (_("Mail delivery via the sendmail program"));
47 }
48 
49 static GPtrArray *
parse_sendmail_args(const gchar * binary,const gchar * args,const gchar * from_addr,CamelAddress * recipients)50 parse_sendmail_args (const gchar *binary,
51                      const gchar *args,
52                      const gchar *from_addr,
53                      CamelAddress *recipients)
54 {
55 	GPtrArray *args_arr;
56 	gint ii, len, argc = 0;
57 	gchar **argv = NULL;
58 
59 	g_return_val_if_fail (binary != NULL, NULL);
60 	g_return_val_if_fail (args != NULL, NULL);
61 	g_return_val_if_fail (from_addr != NULL, NULL);
62 
63 	len = camel_address_length (recipients);
64 
65 	args_arr = g_ptr_array_new_full (5, g_free);
66 	g_ptr_array_add (args_arr, g_strdup (binary));
67 
68 	if (args && g_shell_parse_argv (args, &argc, &argv, NULL) && argc > 0 && argv) {
69 		for (ii = 0; ii < argc; ii++) {
70 			const gchar *arg = argv[ii];
71 
72 			if (g_strcmp0 (arg, "%F") == 0) {
73 				g_ptr_array_add (args_arr, g_strdup (from_addr));
74 			} else if (g_strcmp0 (arg, "%R") == 0) {
75 				gint jj;
76 
77 				for (jj = 0; jj < len; jj++) {
78 					const gchar *addr = NULL;
79 
80 					if (!camel_internet_address_get (
81 						CAMEL_INTERNET_ADDRESS (recipients), jj, NULL, &addr)) {
82 
83 						/* should not happen, as the array is checked beforehand */
84 
85 						g_ptr_array_free (args_arr, TRUE);
86 						g_strfreev (argv);
87 
88 						return NULL;
89 					}
90 
91 					g_ptr_array_add (args_arr, g_strdup (addr));
92 				}
93 			} else {
94 				g_ptr_array_add (args_arr, g_strdup (arg));
95 			}
96 		}
97 
98 		g_strfreev (argv);
99 	}
100 
101 	g_ptr_array_add (args_arr, NULL);
102 
103 	return args_arr;
104 }
105 
106 static gboolean
sendmail_send_to_sync(CamelTransport * transport,CamelMimeMessage * message,CamelAddress * from,CamelAddress * recipients,gboolean * out_sent_message_saved,GCancellable * cancellable,GError ** error)107 sendmail_send_to_sync (CamelTransport *transport,
108                        CamelMimeMessage *message,
109                        CamelAddress *from,
110                        CamelAddress *recipients,
111 		       gboolean *out_sent_message_saved,
112                        GCancellable *cancellable,
113                        GError **error)
114 {
115 	CamelNameValueArray *previous_headers = NULL;
116 	const gchar *header_name = NULL, *header_value = NULL;
117 	const gchar *from_addr, *addr;
118 	GPtrArray *argv_arr;
119 	gint i, len, fd[2], nullfd, wstat;
120 	CamelStream *filter;
121 	CamelMimeFilter *crlf;
122 	sigset_t mask, omask;
123 	CamelStream *out;
124 	CamelSendmailSettings *settings;
125 	const gchar *binary = SENDMAIL_PATH;
126 	gchar *custom_binary = NULL, *custom_args = NULL;
127 	gboolean success;
128 	pid_t pid;
129 	guint ii;
130 
131 	success = camel_internet_address_get (
132 		CAMEL_INTERNET_ADDRESS (from), 0, NULL, &from_addr);
133 
134 	if (!success) {
135 		g_set_error (
136 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
137 			_("Failed to read From address"));
138 		return FALSE;
139 	}
140 
141 	settings = CAMEL_SENDMAIL_SETTINGS (camel_service_ref_settings (CAMEL_SERVICE (transport)));
142 
143 	if (!camel_sendmail_settings_get_send_in_offline (settings)) {
144 		CamelSession *session;
145 		gboolean is_online;
146 
147 		session = camel_service_ref_session (CAMEL_SERVICE (transport));
148 		is_online = session && camel_session_get_online (session);
149 		g_clear_object (&session);
150 
151 		if (!is_online) {
152 			g_set_error (
153 				error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE,
154 				_("Message send in offline mode is disabled"));
155 			return FALSE;
156 		}
157 	}
158 
159 	if (camel_sendmail_settings_get_use_custom_binary (settings)) {
160 		custom_binary = camel_sendmail_settings_dup_custom_binary (settings);
161 		if (custom_binary && *custom_binary)
162 			binary = custom_binary;
163 	}
164 
165 	if (camel_sendmail_settings_get_use_custom_args (settings)) {
166 		custom_args = camel_sendmail_settings_dup_custom_args (settings);
167 		/* means no arguments used */
168 		if (!custom_args)
169 			custom_args = g_strdup ("");
170 	}
171 
172 	g_object_unref (settings);
173 
174 	len = camel_address_length (recipients);
175 	for (i = 0; i < len; i++) {
176 		success = camel_internet_address_get (
177 			CAMEL_INTERNET_ADDRESS (recipients), i, NULL, &addr);
178 
179 		if (!success) {
180 			g_set_error (
181 				error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
182 				_("Could not parse recipient list"));
183 			g_free (custom_binary);
184 			g_free (custom_args);
185 
186 			return FALSE;
187 		}
188 	}
189 
190 	argv_arr = parse_sendmail_args (
191 		binary,
192 		custom_args ? custom_args : "-i -f %F -- %R",
193 		from_addr,
194 		recipients);
195 
196 	if (!argv_arr) {
197 		g_set_error (
198 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
199 			_("Could not parse arguments"));
200 
201 		g_free (custom_binary);
202 		g_free (custom_args);
203 
204 		return FALSE;
205 	}
206 
207 	/* unlink the bcc headers */
208 	previous_headers = camel_medium_dup_headers (CAMEL_MEDIUM (message));
209 	camel_medium_remove_header (CAMEL_MEDIUM (message), "Bcc");
210 
211 	if (pipe (fd) == -1) {
212 		g_set_error (
213 			error, G_IO_ERROR,
214 			g_io_error_from_errno (errno),
215 			_("Could not create pipe to “%s”: %s: "
216 			"mail not sent"), binary, g_strerror (errno));
217 
218 		/* restore the bcc headers */
219 		for (ii = 0; camel_name_value_array_get (previous_headers, ii, &header_name, &header_value); ii++) {
220 			if (!g_ascii_strcasecmp (header_name, "Bcc")) {
221 				camel_medium_add_header (CAMEL_MEDIUM (message), header_name, header_value);
222 			}
223 		}
224 
225 		camel_name_value_array_free (previous_headers);
226 		g_free (custom_binary);
227 		g_free (custom_args);
228 		g_ptr_array_free (argv_arr, TRUE);
229 
230 		return FALSE;
231 	}
232 
233 	/* Block SIGCHLD so the calling application doesn't notice
234 	 * sendmail exiting before we do.
235 	 */
236 	sigemptyset (&mask);
237 	sigaddset (&mask, SIGCHLD);
238 	sigprocmask (SIG_BLOCK, &mask, &omask);
239 
240 	pid = fork ();
241 	switch (pid) {
242 	case -1:
243 		g_set_error (
244 			error, G_IO_ERROR,
245 			g_io_error_from_errno (errno),
246 			_("Could not fork “%s”: %s: "
247 			"mail not sent"), binary, g_strerror (errno));
248 		close (fd[0]);
249 		close (fd[1]);
250 		sigprocmask (SIG_SETMASK, &omask, NULL);
251 
252 		/* restore the bcc headers */
253 		for (ii = 0; camel_name_value_array_get (previous_headers, ii, &header_name, &header_value); ii++) {
254 			if (!g_ascii_strcasecmp (header_name, "Bcc")) {
255 				camel_medium_add_header (CAMEL_MEDIUM (message), header_name, header_value);
256 			}
257 		}
258 
259 		camel_name_value_array_free (previous_headers);
260 		g_free (custom_binary);
261 		g_free (custom_args);
262 		g_ptr_array_free (argv_arr, TRUE);
263 
264 		return FALSE;
265 	case 0:
266 		/* Child process */
267 		nullfd = open ("/dev/null", O_RDWR);
268 		dup2 (fd[0], STDIN_FILENO);
269 		if (nullfd != -1) {
270 			/*dup2 (nullfd, STDOUT_FILENO);
271 			  dup2 (nullfd, STDERR_FILENO);*/
272 			close (nullfd);
273 		}
274 		close (fd[1]);
275 
276 		execv (binary, (gchar **) argv_arr->pdata);
277 		_exit (255);
278 	}
279 
280 	g_ptr_array_free (argv_arr, TRUE);
281 
282 	/* Parent process. Write the message out. */
283 	close (fd[0]);
284 	out = camel_stream_fs_new_with_fd (fd[1]);
285 
286 	/* XXX Workaround for lame sendmail implementations
287 	 *     that can't handle CRLF eoln sequences. */
288 	filter = camel_stream_filter_new (out);
289 	crlf = camel_mime_filter_crlf_new (
290 		CAMEL_MIME_FILTER_CRLF_DECODE,
291 		CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
292 	camel_stream_filter_add (CAMEL_STREAM_FILTER (filter), crlf);
293 	g_object_unref (crlf);
294 	g_object_unref (out);
295 
296 	out = (CamelStream *) filter;
297 	if (camel_data_wrapper_write_to_stream_sync (
298 		CAMEL_DATA_WRAPPER (message), out, cancellable, error) == -1
299 	    || camel_stream_close (out, cancellable, error) == -1) {
300 		g_object_unref (out);
301 		g_prefix_error (error, _("Could not send message: "));
302 
303 		/* Wait for sendmail to exit. */
304 		while (waitpid (pid, &wstat, 0) == -1 && errno == EINTR)
305 			;
306 
307 		sigprocmask (SIG_SETMASK, &omask, NULL);
308 
309 		/* restore the bcc headers */
310 		for (ii = 0; camel_name_value_array_get (previous_headers, ii, &header_name, &header_value); ii++) {
311 			if (!g_ascii_strcasecmp (header_name, "Bcc")) {
312 				camel_medium_add_header (CAMEL_MEDIUM (message), header_name, header_value);
313 			}
314 		}
315 
316 		camel_name_value_array_free (previous_headers);
317 		g_free (custom_binary);
318 		g_free (custom_args);
319 
320 		return FALSE;
321 	}
322 
323 	g_object_unref (out);
324 
325 	/* Wait for sendmail to exit. */
326 	while (waitpid (pid, &wstat, 0) == -1 && errno == EINTR)
327 		;
328 
329 	sigprocmask (SIG_SETMASK, &omask, NULL);
330 
331 	/* restore the bcc headers */
332 	for (ii = 0; camel_name_value_array_get (previous_headers, ii, &header_name, &header_value); ii++) {
333 		if (!g_ascii_strcasecmp (header_name, "Bcc")) {
334 			camel_medium_add_header (CAMEL_MEDIUM (message), header_name, header_value);
335 		}
336 	}
337 
338 	camel_name_value_array_free (previous_headers);
339 
340 	if (!WIFEXITED (wstat)) {
341 		g_set_error (
342 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
343 			_("“%s” exited with signal %s: mail not sent."),
344 			binary, g_strsignal (WTERMSIG (wstat)));
345 		g_free (custom_binary);
346 		g_free (custom_args);
347 
348 		return FALSE;
349 	} else if (WEXITSTATUS (wstat) != 0) {
350 		if (WEXITSTATUS (wstat) == 255) {
351 			g_set_error (
352 				error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
353 				_("Could not execute “%s”: mail not sent."),
354 				binary);
355 		} else {
356 			g_set_error (
357 				error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
358 				_("“%s” exited with status %d: "
359 				"mail not sent."),
360 				binary, WEXITSTATUS (wstat));
361 		}
362 		g_free (custom_binary);
363 		g_free (custom_args);
364 
365 		return FALSE;
366 	}
367 
368 	g_free (custom_binary);
369 	g_free (custom_args);
370 
371 	return TRUE;
372 }
373 
374 static void
camel_sendmail_transport_class_init(CamelSendmailTransportClass * class)375 camel_sendmail_transport_class_init (CamelSendmailTransportClass *class)
376 {
377 	CamelServiceClass *service_class;
378 	CamelTransportClass *transport_class;
379 
380 	service_class = CAMEL_SERVICE_CLASS (class);
381 	service_class->get_name = sendmail_get_name;
382 	service_class->settings_type = CAMEL_TYPE_SENDMAIL_SETTINGS;
383 
384 	transport_class = CAMEL_TRANSPORT_CLASS (class);
385 	transport_class->send_to_sync = sendmail_send_to_sync;
386 }
387 
388 static void
camel_sendmail_transport_init(CamelSendmailTransport * sendmail_transport)389 camel_sendmail_transport_init (CamelSendmailTransport *sendmail_transport)
390 {
391 }
392 
393