evolution-3.6.4/modules/spamassassin/evolution-spamassassin.c

No issues found

   1 /*
   2  * evolution-spamassassin.c
   3  *
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2 of the License, or (at your option) version 3.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  16  *
  17  */
  18 
  19 #include <config.h>
  20 #include <errno.h>
  21 #include <sys/types.h>
  22 #include <sys/wait.h>
  23 #include <glib/gstdio.h>
  24 #include <glib/gi18n-lib.h>
  25 
  26 #include <camel/camel.h>
  27 
  28 #include <shell/e-shell.h>
  29 #include <e-util/e-mktemp.h>
  30 #include <libemail-engine/e-mail-junk-filter.h>
  31 
  32 /* Standard GObject macros */
  33 #define E_TYPE_SPAM_ASSASSIN \
  34 	(e_spam_assassin_get_type ())
  35 #define E_SPAM_ASSASSIN(obj) \
  36 	(G_TYPE_CHECK_INSTANCE_CAST \
  37 	((obj), E_TYPE_SPAM_ASSASSIN, ESpamAssassin))
  38 
  39 #ifndef SPAMASSASSIN_BINARY
  40 #define SPAMASSASSIN_BINARY "/usr/bin/spamassassin"
  41 #endif
  42 
  43 #ifndef SA_LEARN_BINARY
  44 #define SA_LEARN_BINARY "/usr/bin/sa-learn"
  45 #endif
  46 
  47 #ifndef SPAMC_BINARY
  48 #define SPAMC_BINARY "/usr/bin/spamc"
  49 #endif
  50 
  51 #ifndef SPAMD_BINARY
  52 #define SPAMD_BINARY "/usr/bin/spamd"
  53 #endif
  54 
  55 /* For starting our own daemon. */
  56 #define DAEMON_MAX_RETRIES 100
  57 #define DAEMON_RETRY_DELAY 0.05  /* seconds */
  58 
  59 #define SPAM_ASSASSIN_EXIT_STATUS_SUCCESS	0
  60 #define SPAM_ASSASSIN_EXIT_STATUS_ERROR		-1
  61 
  62 typedef struct _ESpamAssassin ESpamAssassin;
  63 typedef struct _ESpamAssassinClass ESpamAssassinClass;
  64 
  65 struct _ESpamAssassin {
  66 	EMailJunkFilter parent;
  67 
  68 	GMutex *socket_path_mutex;
  69 
  70 	gchar *pid_file;
  71 	gchar *socket_path;
  72 	gchar *spamc_binary;
  73 	gchar *spamd_binary;
  74 	gint version;
  75 
  76 	gboolean local_only;
  77 	gboolean use_daemon;
  78 	gboolean version_set;
  79 
  80 	/* spamc/spamd state */
  81 	gboolean spamd_tested;
  82 	gboolean spamd_using_allow_tell;
  83 	gboolean system_spamd_available;
  84 	gboolean use_spamc;
  85 };
  86 
  87 struct _ESpamAssassinClass {
  88 	EMailJunkFilterClass parent_class;
  89 };
  90 
  91 enum {
  92 	PROP_0,
  93 	PROP_LOCAL_ONLY,
  94 	PROP_SPAMC_BINARY,
  95 	PROP_SPAMD_BINARY,
  96 	PROP_SOCKET_PATH,
  97 	PROP_USE_DAEMON
  98 };
  99 
 100 /* Module Entry Points */
 101 void e_module_load (GTypeModule *type_module);
 102 void e_module_unload (GTypeModule *type_module);
 103 
 104 /* Forward Declarations */
 105 GType e_spam_assassin_get_type (void);
 106 static void e_spam_assassin_interface_init (CamelJunkFilterInterface *interface);
 107 
 108 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
 109 	ESpamAssassin,
 110 	e_spam_assassin,
 111 	E_TYPE_MAIL_JUNK_FILTER, 0,
 112 	G_IMPLEMENT_INTERFACE_DYNAMIC (
 113 		CAMEL_TYPE_JUNK_FILTER,
 114 		e_spam_assassin_interface_init))
 115 
 116 #ifdef G_OS_UNIX
 117 static void
 118 spam_assassin_cancelled_cb (GCancellable *cancellable,
 119                             GPid *pid)
 120 {
 121 	/* XXX On UNIX-like systems we can safely assume a GPid is the
 122 	 *     process ID and use it to terminate the process via signal. */
 123 	kill (*pid, SIGTERM);
 124 }
 125 #endif
 126 
 127 static void
 128 spam_assassin_exited_cb (GPid *pid,
 129                          gint status,
 130                          gpointer user_data)
 131 {
 132 	struct {
 133 		GMainLoop *loop;
 134 		gint exit_code;
 135 	} *source_data = user_data;
 136 
 137 	if (WIFEXITED (status))
 138 		source_data->exit_code = WEXITSTATUS (status);
 139 	else
 140 		source_data->exit_code = SPAM_ASSASSIN_EXIT_STATUS_ERROR;
 141 
 142 	g_main_loop_quit (source_data->loop);
 143 }
 144 
 145 static gint
 146 spam_assassin_command_full (const gchar **argv,
 147                             CamelMimeMessage *message,
 148                             const gchar *input_data,
 149                             GByteArray *output_buffer,
 150                             gboolean wait_for_termination,
 151                             GCancellable *cancellable,
 152                             GError **error)
 153 {
 154 	GMainContext *context;
 155 	GSpawnFlags flags = 0;
 156 	GSource *source;
 157 	GPid child_pid;
 158 	gint standard_input;
 159 	gint standard_output;
 160 	gulong handler_id = 0;
 161 	gboolean success;
 162 
 163 	struct {
 164 		GMainLoop *loop;
 165 		gint exit_code;
 166 	} source_data;
 167 
 168 	if (wait_for_termination)
 169 		flags |= G_SPAWN_DO_NOT_REAP_CHILD;
 170 	if (output_buffer == NULL)
 171 		flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
 172 	flags |= G_SPAWN_STDERR_TO_DEV_NULL;
 173 
 174 	/* Spawn SpamAssassin with an open stdin pipe. */
 175 	success = g_spawn_async_with_pipes (
 176 		NULL,
 177 		(gchar **) argv,
 178 		NULL,
 179 		flags,
 180 		NULL, NULL,
 181 		&child_pid,
 182 		&standard_input,
 183 		(output_buffer != NULL) ? &standard_output : NULL,
 184 		NULL,
 185 		error);
 186 
 187 	if (!success) {
 188 		gchar *command_line;
 189 
 190 		command_line = g_strjoinv (" ", (gchar **) argv);
 191 		g_prefix_error (
 192 			error, _("Failed to spawn SpamAssassin (%s): "),
 193 			command_line);
 194 		g_free (command_line);
 195 
 196 		return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
 197 	}
 198 
 199 	if (message != NULL) {
 200 		CamelStream *stream;
 201 		gssize bytes_written;
 202 
 203 		/* Stream the CamelMimeMessage to SpamAssassin. */
 204 		stream = camel_stream_fs_new_with_fd (standard_input);
 205 		bytes_written = camel_data_wrapper_write_to_stream_sync (
 206 			CAMEL_DATA_WRAPPER (message),
 207 			stream, cancellable, error);
 208 		success = (bytes_written >= 0) &&
 209 			(camel_stream_close (stream, cancellable, error) == 0);
 210 		g_object_unref (stream);
 211 
 212 		if (!success) {
 213 			g_spawn_close_pid (child_pid);
 214 			g_prefix_error (
 215 				error, _("Failed to stream mail "
 216 				"message content to SpamAssassin: "));
 217 			return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
 218 		}
 219 
 220 	} else if (input_data != NULL) {
 221 		gssize bytes_written;
 222 
 223 		/* Write raw data directly to SpamAssassin. */
 224 		bytes_written = camel_write (
 225 			standard_input, input_data,
 226 			strlen (input_data), cancellable, error);
 227 		success = (bytes_written >= 0);
 228 
 229 		close (standard_input);
 230 
 231 		if (!success) {
 232 			g_spawn_close_pid (child_pid);
 233 			g_prefix_error (
 234 				error, _("Failed to write '%s' "
 235 				"to SpamAssassin: "), input_data);
 236 			return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
 237 		}
 238 	}
 239 
 240 	if (output_buffer != NULL) {
 241 		CamelStream *input_stream;
 242 		CamelStream *output_stream;
 243 		gssize bytes_written;
 244 
 245 		input_stream = camel_stream_fs_new_with_fd (standard_output);
 246 
 247 		output_stream = camel_stream_mem_new ();
 248 		camel_stream_mem_set_byte_array (
 249 			CAMEL_STREAM_MEM (output_stream), output_buffer);
 250 
 251 		bytes_written = camel_stream_write_to_stream (
 252 			input_stream, output_stream, cancellable, error);
 253 		g_byte_array_append (output_buffer, (guint8 *) "", 1);
 254 		success = (bytes_written >= 0);
 255 
 256 		g_object_unref (input_stream);
 257 		g_object_unref (output_stream);
 258 
 259 		if (!success) {
 260 			g_spawn_close_pid (child_pid);
 261 			g_prefix_error (
 262 				error, _("Failed to read "
 263 				"output from SpamAssassin: "));
 264 			return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
 265 		}
 266 	}
 267 
 268 	/* XXX I'm not sure if we should call g_spawn_close_pid()
 269 	 *     here or not.  Only really matters on Windows anyway. */
 270 	if (!wait_for_termination)
 271 		return 0;
 272 
 273 	/* Wait for the SpamAssassin process to terminate
 274 	 * using GLib's main loop for better portability. */
 275 
 276 	context = g_main_context_new ();
 277 
 278 	source = g_child_watch_source_new (child_pid);
 279 	g_source_set_callback (
 280 		source, (GSourceFunc)
 281 		spam_assassin_exited_cb,
 282 		&source_data, NULL);
 283 	g_source_attach (source, context);
 284 	g_source_unref (source);
 285 
 286 	source_data.loop = g_main_loop_new (context, TRUE);
 287 	source_data.exit_code = 0;
 288 
 289 #ifdef G_OS_UNIX
 290 	if (G_IS_CANCELLABLE (cancellable))
 291 		handler_id = g_cancellable_connect (
 292 			cancellable,
 293 			G_CALLBACK (spam_assassin_cancelled_cb),
 294 			&child_pid, (GDestroyNotify) NULL);
 295 #endif
 296 
 297 	g_main_loop_run (source_data.loop);
 298 
 299 	if (handler_id > 0)
 300 		g_cancellable_disconnect (cancellable, handler_id);
 301 
 302 	g_main_loop_unref (source_data.loop);
 303 	source_data.loop = NULL;
 304 
 305 	g_main_context_unref (context);
 306 
 307 	/* Clean up. */
 308 
 309 	g_spawn_close_pid (child_pid);
 310 
 311 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
 312 		source_data.exit_code = SPAM_ASSASSIN_EXIT_STATUS_ERROR;
 313 
 314 	else if (source_data.exit_code == SPAM_ASSASSIN_EXIT_STATUS_ERROR)
 315 		g_set_error_literal (
 316 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
 317 			_("SpamAssassin either crashed or "
 318 			"failed to process a mail message"));
 319 
 320 	return source_data.exit_code;
 321 }
 322 
 323 static gint
 324 spam_assassin_command (const gchar **argv,
 325                        CamelMimeMessage *message,
 326                        const gchar *input_data,
 327                        GCancellable *cancellable,
 328                        GError **error)
 329 {
 330 	return spam_assassin_command_full (
 331 		argv, message, input_data, NULL, TRUE, cancellable, error);
 332 }
 333 
 334 static gboolean
 335 spam_assassin_get_local_only (ESpamAssassin *extension)
 336 {
 337 	return extension->local_only;
 338 }
 339 
 340 static void
 341 spam_assassin_set_local_only (ESpamAssassin *extension,
 342                               gboolean local_only)
 343 {
 344 	if (extension->local_only == local_only)
 345 		return;
 346 
 347 	extension->local_only = local_only;
 348 
 349 	g_object_notify (G_OBJECT (extension), "local-only");
 350 }
 351 
 352 static const gchar *
 353 spam_assassin_get_spamc_binary (ESpamAssassin *extension)
 354 {
 355 	return extension->spamc_binary;
 356 }
 357 
 358 static void
 359 spam_assassin_set_spamc_binary (ESpamAssassin *extension,
 360                                 const gchar *spamc_binary)
 361 {
 362 	if (g_strcmp0 (extension->spamc_binary, spamc_binary) == 0)
 363 		return;
 364 
 365 	g_free (extension->spamc_binary);
 366 	extension->spamc_binary = g_strdup (spamc_binary);
 367 
 368 	g_object_notify (G_OBJECT (extension), "spamc-binary");
 369 }
 370 
 371 static const gchar *
 372 spam_assassin_get_spamd_binary (ESpamAssassin *extension)
 373 {
 374 	return extension->spamd_binary;
 375 }
 376 
 377 static void
 378 spam_assassin_set_spamd_binary (ESpamAssassin *extension,
 379                                 const gchar *spamd_binary)
 380 {
 381 	if (g_strcmp0 (extension->spamd_binary, spamd_binary) == 0)
 382 		return;
 383 
 384 	g_free (extension->spamd_binary);
 385 	extension->spamd_binary = g_strdup (spamd_binary);
 386 
 387 	g_object_notify (G_OBJECT (extension), "spamd-binary");
 388 }
 389 
 390 static const gchar *
 391 spam_assassin_get_socket_path (ESpamAssassin *extension)
 392 {
 393 	return extension->socket_path;
 394 }
 395 
 396 static void
 397 spam_assassin_set_socket_path (ESpamAssassin *extension,
 398                                const gchar *socket_path)
 399 {
 400 	if (g_strcmp0 (extension->socket_path, socket_path) == 0)
 401 		return;
 402 
 403 	g_free (extension->socket_path);
 404 	extension->socket_path = g_strdup (socket_path);
 405 
 406 	g_object_notify (G_OBJECT (extension), "socket-path");
 407 }
 408 
 409 static gboolean
 410 spam_assassin_get_use_daemon (ESpamAssassin *extension)
 411 {
 412 	return extension->use_daemon;
 413 }
 414 
 415 static void
 416 spam_assassin_set_use_daemon (ESpamAssassin *extension,
 417                               gboolean use_daemon)
 418 {
 419 	if ((extension->use_daemon ? 1 : 0) == (use_daemon ? 1 : 0))
 420 		return;
 421 
 422 	extension->use_daemon = use_daemon;
 423 
 424 	g_object_notify (G_OBJECT (extension), "use-daemon");
 425 }
 426 
 427 static gboolean
 428 spam_assassin_get_version (ESpamAssassin *extension,
 429                            gint *spam_assassin_version,
 430                            GCancellable *cancellable,
 431                            GError **error)
 432 {
 433 	GByteArray *output_buffer;
 434 	gint exit_code;
 435 	guint ii;
 436 
 437 	const gchar *argv[] = {
 438 		SA_LEARN_BINARY,
 439 		"--version",
 440 		NULL
 441 	};
 442 
 443 	if (extension->version_set) {
 444 		if (spam_assassin_version != NULL)
 445 			*spam_assassin_version = extension->version;
 446 		return TRUE;
 447 	}
 448 
 449 	output_buffer = g_byte_array_new ();
 450 
 451 	exit_code = spam_assassin_command_full (
 452 		argv, NULL, NULL, output_buffer, TRUE, cancellable, error);
 453 
 454 	if (exit_code != 0) {
 455 		g_byte_array_free (output_buffer, TRUE);
 456 		return FALSE;
 457 	}
 458 
 459 	for (ii = 0; ii < output_buffer->len; ii++) {
 460 		if (g_ascii_isdigit (output_buffer->data[ii])) {
 461 			guint8 ch = output_buffer->data[ii];
 462 			extension->version = (ch - '0');
 463 			extension->version_set = TRUE;
 464 			break;
 465 		}
 466 	}
 467 
 468 	if (spam_assassin_version != NULL)
 469 		*spam_assassin_version = extension->version;
 470 
 471 	g_byte_array_free (output_buffer, TRUE);
 472 
 473 	return TRUE;
 474 }
 475 
 476 static void
 477 spam_assassin_test_spamd_allow_tell (ESpamAssassin *extension)
 478 {
 479 	gint exit_code;
 480 	GError *error = NULL;
 481 
 482 	const gchar *argv[] = {
 483 		SPAMC_BINARY,
 484 		"--learntype=forget",
 485 		NULL
 486 	};
 487 
 488 	/* Check if spamd is running with --allow-tell. */
 489 
 490 	exit_code = spam_assassin_command (argv, NULL, "\n", NULL, &error);
 491 	extension->spamd_using_allow_tell = (exit_code == 0);
 492 
 493 	if (error != NULL) {
 494 		g_warning ("%s", error->message);
 495 		g_error_free (error);
 496 	}
 497 }
 498 
 499 static gboolean
 500 spam_assassin_test_spamd_running (ESpamAssassin *extension,
 501                                   gboolean system_spamd)
 502 {
 503 	const gchar *argv[5];
 504 	gint exit_code;
 505 	gint ii = 0;
 506 	GError *error = NULL;
 507 
 508 	g_mutex_lock (extension->socket_path_mutex);
 509 
 510 	argv[ii++] = extension->spamc_binary;
 511 	argv[ii++] = "--no-safe-fallback";
 512 	if (!system_spamd) {
 513 		argv[ii++] = "--socket";
 514 		argv[ii++] = extension->socket_path;
 515 	}
 516 	argv[ii] = NULL;
 517 
 518 	g_assert (ii < G_N_ELEMENTS (argv));
 519 
 520 	exit_code = spam_assassin_command (
 521 		argv, NULL, "From test@127.0.0.1", NULL, &error);
 522 
 523 	if (error != NULL) {
 524 		g_warning ("%s", error->message);
 525 		g_error_free (error);
 526 	}
 527 
 528 	g_mutex_unlock (extension->socket_path_mutex);
 529 
 530 	return (exit_code == 0);
 531 }
 532 
 533 static void
 534 spam_assassin_kill_our_own_daemon (ESpamAssassin *extension)
 535 {
 536 	gint pid;
 537 	gchar *contents = NULL;
 538 	GError *error = NULL;
 539 
 540 	g_mutex_lock (extension->socket_path_mutex);
 541 
 542 	g_free (extension->socket_path);
 543 	extension->socket_path = NULL;
 544 
 545 	g_mutex_unlock (extension->socket_path_mutex);
 546 
 547 	if (extension->pid_file == NULL)
 548 		return;
 549 
 550 	g_file_get_contents (extension->pid_file, &contents, NULL, &error);
 551 
 552 	if (error != NULL) {
 553 		g_warn_if_fail (contents == NULL);
 554 		g_warning ("%s", error->message);
 555 		g_error_free (error);
 556 		return;
 557 	}
 558 
 559 	g_return_if_fail (contents != NULL);
 560 
 561 	pid = atoi (contents);
 562 	g_free (contents);
 563 
 564 	if (pid > 0 && kill (pid, SIGTERM) == 0)
 565 		waitpid (pid, NULL, 0);
 566 }
 567 
 568 static void
 569 spam_assassin_prepare_for_quit (EShell *shell,
 570                                 EActivity *activity,
 571                                 ESpamAssassin *extension)
 572 {
 573 	spam_assassin_kill_our_own_daemon (extension);
 574 }
 575 
 576 static gboolean
 577 spam_assassin_start_our_own_daemon (ESpamAssassin *extension)
 578 {
 579 	const gchar *argv[8];
 580 	const gchar *user_runtime_dir;
 581 	gchar *pid_file;
 582 	gchar *socket_path;
 583 	gboolean started = FALSE;
 584 	gint exit_code;
 585 	gint ii = 0;
 586 	gint fd;
 587 	GError *error = NULL;
 588 
 589 	g_mutex_lock (extension->socket_path_mutex);
 590 
 591 	/* Don't put the PID files in Evolution's tmp directory
 592 	 * (as defined in e-mktemp.c) because that gets cleaned
 593 	 * every few hours, and these files need to persist. */
 594 	user_runtime_dir = g_get_user_runtime_dir ();
 595 
 596 	pid_file = g_build_filename (
 597 		user_runtime_dir, "spamd-pid-file-XXXXXX", NULL);
 598 
 599 	socket_path = g_build_filename (
 600 		user_runtime_dir, "spamd-socket-path-XXXXXX", NULL);
 601 
 602 	/* The template filename is modified in place. */
 603 	fd = g_mkstemp (pid_file);
 604 	if (fd >= 0) {
 605 		close (fd);
 606 		g_unlink (pid_file);
 607 	} else {
 608 		g_warning (
 609 			"Failed to create spamd-pid-file: %s",
 610 			g_strerror (errno));
 611 		goto exit;
 612 	}
 613 
 614 	/* The template filename is modified in place. */
 615 	fd = g_mkstemp (socket_path);
 616 	if (fd >= 0) {
 617 		close (fd);
 618 		g_unlink (socket_path);
 619 	} else {
 620 		g_warning (
 621 			"Failed to create spamd-socket-path: %s",
 622 			g_strerror (errno));
 623 		goto exit;
 624 	}
 625 
 626 	argv[ii++] = extension->spamd_binary;
 627 	argv[ii++] = "--socketpath";
 628 	argv[ii++] = socket_path;
 629 
 630 	if (spam_assassin_get_local_only (extension))
 631 		argv[ii++] = "--local";
 632 
 633 	argv[ii++] = "--max-children=1";
 634 	argv[ii++] = "--pidfile";
 635 	argv[ii++] = pid_file;
 636 	argv[ii] = NULL;
 637 
 638 	g_assert (ii < G_N_ELEMENTS (argv));
 639 
 640 	exit_code = spam_assassin_command_full (
 641 		argv, NULL, NULL, NULL, FALSE, NULL, &error);
 642 
 643 	if (error != NULL) {
 644 		g_warning ("%s", error->message);
 645 		g_error_free (error);
 646 		goto exit;
 647 	}
 648 
 649 	if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS) {
 650 		/* Wait for the socket path to appear. */
 651 		for (ii = 0; ii < DAEMON_MAX_RETRIES; ii++) {
 652 			if (g_file_test (socket_path, G_FILE_TEST_EXISTS)) {
 653 				started = TRUE;
 654 				break;
 655 			}
 656 			g_usleep (DAEMON_RETRY_DELAY * G_USEC_PER_SEC);
 657 		}
 658 	}
 659 
 660 	/* Set these directly to avoid emitting "notify" signals. */
 661 	if (started) {
 662 		g_free (extension->pid_file);
 663 		extension->pid_file = pid_file;
 664 		pid_file = NULL;
 665 
 666 		g_free (extension->socket_path);
 667 		extension->socket_path = socket_path;
 668 		socket_path = NULL;
 669 
 670 		/* XXX EMailSession is too prone to reference leaks to leave
 671 		 *     this for our finalize() method.  We want to be sure to
 672 		 *     kill the spamd process we started when Evolution shuts
 673 		 *     down, so connect to an EShell signal instead. */
 674 		g_signal_connect (
 675 			e_shell_get_default (), "prepare-for-quit",
 676 			G_CALLBACK (spam_assassin_prepare_for_quit),
 677 			extension);
 678 	}
 679 
 680 exit:
 681 	g_free (pid_file);
 682 	g_free (socket_path);
 683 
 684 	g_mutex_unlock (extension->socket_path_mutex);
 685 
 686 	return started;
 687 }
 688 
 689 static void
 690 spam_assassin_test_spamd (ESpamAssassin *extension)
 691 {
 692 	const gchar *spamd_binary;
 693 	gboolean try_system_spamd;
 694 
 695 	/* XXX SpamAssassin could really benefit from a D-Bus interface
 696 	 *     these days.  These tests are just needlessly painful for
 697 	 *     clients trying to talk to an already-running spamd. */
 698 
 699 	extension->use_spamc = FALSE;
 700 	spamd_binary = extension->spamd_binary;
 701 	try_system_spamd = (g_strcmp0 (spamd_binary, SPAMD_BINARY) == 0);
 702 
 703 	if (extension->local_only && try_system_spamd) {
 704 		gint exit_code;
 705 
 706 		/* Run a shell command to check for a running
 707 		 * spamd process with a -L/--local option or a
 708 		 * -p/--port option. */
 709 
 710 		const gchar *argv[] = {
 711 			"/bin/sh",
 712 			"-c",
 713 			"ps ax | grep -v grep | "
 714 			"grep -E 'spamd.*(\\-L|\\-\\-local)' | "
 715 			"grep -E -v '\\ \\-p\\ |\\ \\-\\-port\\ '",
 716 			NULL
 717 		};
 718 
 719 		exit_code = spam_assassin_command (
 720 			argv, NULL, NULL, NULL, NULL);
 721 		try_system_spamd = (exit_code == 0);
 722 	}
 723 
 724 	/* Try to use the system spamd first. */
 725 	if (try_system_spamd) {
 726 		if (spam_assassin_test_spamd_running (extension, TRUE)) {
 727 			extension->use_spamc = TRUE;
 728 			extension->system_spamd_available = TRUE;
 729 		}
 730 	}
 731 
 732 	/* If there's no system spamd running, try
 733 	 * to use one with a user specified socket. */
 734 	if (!extension->use_spamc && extension->socket_path != NULL) {
 735 		if (spam_assassin_test_spamd_running (extension, FALSE)) {
 736 			extension->use_spamc = TRUE;
 737 			extension->system_spamd_available = FALSE;
 738 		}
 739 	}
 740 
 741 	/* Still unsuccessful?  Try to start our own spamd. */
 742 	if (!extension->use_spamc) {
 743 		extension->use_spamc =
 744 			spam_assassin_start_our_own_daemon (extension) &&
 745 			spam_assassin_test_spamd_running (extension, FALSE);
 746 	}
 747 }
 748 
 749 static void
 750 spam_assassin_set_property (GObject *object,
 751                             guint property_id,
 752                             const GValue *value,
 753                             GParamSpec *pspec)
 754 {
 755 	switch (property_id) {
 756 		case PROP_LOCAL_ONLY:
 757 			spam_assassin_set_local_only (
 758 				E_SPAM_ASSASSIN (object),
 759 				g_value_get_boolean (value));
 760 			return;
 761 
 762 		case PROP_SPAMC_BINARY:
 763 			spam_assassin_set_spamc_binary (
 764 				E_SPAM_ASSASSIN (object),
 765 				g_value_get_string (value));
 766 			return;
 767 
 768 		case PROP_SPAMD_BINARY:
 769 			spam_assassin_set_spamd_binary (
 770 				E_SPAM_ASSASSIN (object),
 771 				g_value_get_string (value));
 772 			return;
 773 
 774 		case PROP_SOCKET_PATH:
 775 			spam_assassin_set_socket_path (
 776 				E_SPAM_ASSASSIN (object),
 777 				g_value_get_string (value));
 778 			return;
 779 
 780 		case PROP_USE_DAEMON:
 781 			spam_assassin_set_use_daemon (
 782 				E_SPAM_ASSASSIN (object),
 783 				g_value_get_boolean (value));
 784 			return;
 785 	}
 786 
 787 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 788 }
 789 
 790 static void
 791 spam_assassin_get_property (GObject *object,
 792                             guint property_id,
 793                             GValue *value,
 794                             GParamSpec *pspec)
 795 {
 796 	switch (property_id) {
 797 		case PROP_LOCAL_ONLY:
 798 			g_value_set_boolean (
 799 				value, spam_assassin_get_local_only (
 800 				E_SPAM_ASSASSIN (object)));
 801 			return;
 802 
 803 		case PROP_SPAMC_BINARY:
 804 			g_value_set_string (
 805 				value, spam_assassin_get_spamc_binary (
 806 				E_SPAM_ASSASSIN (object)));
 807 			return;
 808 
 809 		case PROP_SPAMD_BINARY:
 810 			g_value_set_string (
 811 				value, spam_assassin_get_spamd_binary (
 812 				E_SPAM_ASSASSIN (object)));
 813 			return;
 814 
 815 		case PROP_SOCKET_PATH:
 816 			g_value_set_string (
 817 				value, spam_assassin_get_socket_path (
 818 				E_SPAM_ASSASSIN (object)));
 819 			return;
 820 
 821 		case PROP_USE_DAEMON:
 822 			g_value_set_boolean (
 823 				value, spam_assassin_get_use_daemon (
 824 				E_SPAM_ASSASSIN (object)));
 825 			return;
 826 	}
 827 
 828 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 829 }
 830 
 831 static void
 832 spam_assassin_finalize (GObject *object)
 833 {
 834 	ESpamAssassin *extension = E_SPAM_ASSASSIN (object);
 835 
 836 	g_mutex_free (extension->socket_path_mutex);
 837 
 838 	g_free (extension->pid_file);
 839 	g_free (extension->socket_path);
 840 	g_free (extension->spamc_binary);
 841 	g_free (extension->spamd_binary);
 842 
 843 	/* Chain up to parent's finalize() method. */
 844 	G_OBJECT_CLASS (e_spam_assassin_parent_class)->finalize (object);
 845 }
 846 
 847 static gboolean
 848 spam_assassin_available (EMailJunkFilter *junk_filter)
 849 {
 850 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
 851 	gboolean available;
 852 	GError *error = NULL;
 853 
 854 	available = spam_assassin_get_version (extension, NULL, NULL, &error);
 855 
 856 	/* XXX These tests block like crazy so maybe this isn't the best
 857 	 *     place to be doing this, but the first available() call is
 858 	 *     done at startup before the UI is shown.  So hopefully the
 859 	 *     delay will not be noticeable. */
 860 	if (available && extension->use_daemon && !extension->spamd_tested) {
 861 		extension->spamd_tested = TRUE;
 862 		spam_assassin_test_spamd (extension);
 863 		spam_assassin_test_spamd_allow_tell (extension);
 864 	}
 865 
 866 	if (error != NULL) {
 867 		g_warning ("%s", error->message);
 868 		g_error_free (error);
 869 	}
 870 
 871 	return available;
 872 }
 873 
 874 static GtkWidget *
 875 spam_assassin_new_config_widget (EMailJunkFilter *junk_filter)
 876 {
 877 	GtkWidget *box;
 878 	GtkWidget *widget;
 879 	GtkWidget *container;
 880 	gchar *markup;
 881 
 882 	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
 883 
 884 	markup = g_markup_printf_escaped (
 885 		"<b>%s</b>", _("SpamAssassin Options"));
 886 	widget = gtk_label_new (markup);
 887 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
 888 	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
 889 	gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
 890 	gtk_widget_show (widget);
 891 	g_free (markup);
 892 
 893 	widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
 894 	gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
 895 	gtk_widget_show (widget);
 896 
 897 	container = widget;
 898 
 899 	widget = gtk_check_button_new_with_mnemonic (
 900 		_("I_nclude remote tests"));
 901 	gtk_widget_set_margin_left (widget, 12);
 902 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
 903 	gtk_widget_show (widget);
 904 
 905 	g_object_bind_property (
 906 		junk_filter, "local-only",
 907 		widget, "active",
 908 		G_BINDING_BIDIRECTIONAL |
 909 		G_BINDING_SYNC_CREATE |
 910 		G_BINDING_INVERT_BOOLEAN);
 911 
 912 	markup = g_markup_printf_escaped (
 913 		"<small>%s</small>",
 914 		_("This will make SpamAssassin more reliable, but slower."));
 915 	widget = gtk_label_new (markup);
 916 	gtk_widget_set_margin_left (widget, 36);
 917 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
 918 	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
 919 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
 920 	gtk_widget_show (widget);
 921 	g_free (markup);
 922 
 923 	return box;
 924 }
 925 
 926 static gboolean
 927 spam_assassin_classify (CamelJunkFilter *junk_filter,
 928                         CamelMimeMessage *message,
 929                         CamelJunkStatus *status,
 930                         GCancellable *cancellable,
 931                         GError **error)
 932 {
 933 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
 934 	const gchar *argv[7];
 935 	gboolean using_spamc;
 936 	gint exit_code;
 937 	gint ii = 0;
 938 
 939 	g_mutex_lock (extension->socket_path_mutex);
 940 
 941 	using_spamc = (extension->use_spamc && extension->use_daemon);
 942 
 943 	if (using_spamc) {
 944 		argv[ii++] = extension->spamc_binary;
 945 		argv[ii++] = "--check";
 946 		argv[ii++] = "--timeout=60";
 947 		if (!extension->system_spamd_available) {
 948 			argv[ii++] = "--socket";
 949 			argv[ii++] = extension->socket_path;
 950 		}
 951 	} else {
 952 		argv[ii++] = SPAMASSASSIN_BINARY;
 953 		argv[ii++] = "--exit-code";
 954 		if (extension->local_only)
 955 			argv[ii++] = "--local";
 956 	}
 957 	argv[ii] = NULL;
 958 
 959 	g_assert (ii < G_N_ELEMENTS (argv));
 960 
 961 	exit_code = spam_assassin_command (
 962 		argv, message, NULL, cancellable, error);
 963 
 964 	/* For either program, exit code 0 means the message is ham. */
 965 	if (exit_code == 0)
 966 		*status = CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK;
 967 
 968 	/* spamassassin(1) only specifies zero and non-zero exit codes. */
 969 	else if (!using_spamc)
 970 		*status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK;
 971 
 972 	/* Whereas spamc(1) explicitly states exit code 1 means spam. */
 973 	else if (exit_code == 1)
 974 		*status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK;
 975 
 976 	/* Consider any other spamc(1) exit code to be inconclusive
 977 	 * since it most likely failed to process the message. */
 978 	else
 979 		*status = CAMEL_JUNK_STATUS_INCONCLUSIVE;
 980 
 981 	/* Check that the return value and GError agree. */
 982 	if (exit_code != SPAM_ASSASSIN_EXIT_STATUS_ERROR)
 983 		g_warn_if_fail (error == NULL || *error == NULL);
 984 	else
 985 		g_warn_if_fail (error == NULL || *error != NULL);
 986 
 987 	g_mutex_unlock (extension->socket_path_mutex);
 988 
 989 	return (exit_code != SPAM_ASSASSIN_EXIT_STATUS_ERROR);
 990 }
 991 
 992 static gboolean
 993 spam_assassin_learn_junk (CamelJunkFilter *junk_filter,
 994                           CamelMimeMessage *message,
 995                           GCancellable *cancellable,
 996                           GError **error)
 997 {
 998 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
 999 	const gchar *argv[5];
1000 	gint exit_code;
1001 	gint ii = 0;
1002 
1003 	if (extension->spamd_using_allow_tell) {
1004 		argv[ii++] = extension->spamc_binary;
1005 		argv[ii++] = "--learntype=spam";
1006 	} else {
1007 		argv[ii++] = SA_LEARN_BINARY;
1008 		argv[ii++] = "--spam";
1009 		if (extension->version >= 3)
1010 			argv[ii++] = "--no-sync";
1011 		else
1012 			argv[ii++] = "--no-rebuild";
1013 		if (extension->local_only)
1014 			argv[ii++] = "--local";
1015 	}
1016 	argv[ii] = NULL;
1017 
1018 	g_assert (ii < G_N_ELEMENTS (argv));
1019 
1020 	exit_code = spam_assassin_command (
1021 		argv, message, NULL, cancellable, error);
1022 
1023 	/* Check that the return value and GError agree. */
1024 	if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS)
1025 		g_warn_if_fail (error == NULL || *error == NULL);
1026 	else
1027 		g_warn_if_fail (error == NULL || *error != NULL);
1028 
1029 	return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS);
1030 }
1031 
1032 static gboolean
1033 spam_assassin_learn_not_junk (CamelJunkFilter *junk_filter,
1034                               CamelMimeMessage *message,
1035                               GCancellable *cancellable,
1036                               GError **error)
1037 {
1038 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
1039 	const gchar *argv[5];
1040 	gint exit_code;
1041 	gint ii = 0;
1042 
1043 	if (extension->spamd_using_allow_tell) {
1044 		argv[ii++] = extension->spamc_binary;
1045 		argv[ii++] = "--learntype=ham";
1046 	} else {
1047 		argv[ii++] = SA_LEARN_BINARY;
1048 		argv[ii++] = "--ham";
1049 		if (extension->version >= 3)
1050 			argv[ii++] = "--no-sync";
1051 		else
1052 			argv[ii++] = "--no-rebuild";
1053 		if (extension->local_only)
1054 			argv[ii++] = "--local";
1055 	}
1056 	argv[ii] = NULL;
1057 
1058 	g_assert (ii < G_N_ELEMENTS (argv));
1059 
1060 	exit_code = spam_assassin_command (
1061 		argv, message, NULL, cancellable, error);
1062 
1063 	/* Check that the return value and GError agree. */
1064 	if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS)
1065 		g_warn_if_fail (error == NULL || *error == NULL);
1066 	else
1067 		g_warn_if_fail (error == NULL || *error != NULL);
1068 
1069 	return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS);
1070 }
1071 
1072 static gboolean
1073 spam_assassin_synchronize (CamelJunkFilter *junk_filter,
1074                            GCancellable *cancellable,
1075                            GError **error)
1076 {
1077 	ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
1078 	const gchar *argv[4];
1079 	gint exit_code;
1080 	gint ii = 0;
1081 
1082 	/* If we're using a spamd that allows learning,
1083 	 * there's no need to synchronize anything. */
1084 	if (extension->spamd_using_allow_tell)
1085 		return TRUE;
1086 
1087 	argv[ii++] = SA_LEARN_BINARY;
1088 	if (extension->version >= 3)
1089 		argv[ii++] = "--sync";
1090 	else
1091 		argv[ii++] = "--rebuild";
1092 	if (extension->local_only)
1093 		argv[ii++] = "--local";
1094 	argv[ii] = NULL;
1095 
1096 	g_assert (ii < G_N_ELEMENTS (argv));
1097 
1098 	exit_code = spam_assassin_command (
1099 		argv, NULL, NULL, cancellable, error);
1100 
1101 	/* Check that the return value and GError agree. */
1102 	if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS)
1103 		g_warn_if_fail (error == NULL || *error == NULL);
1104 	else
1105 		g_warn_if_fail (error == NULL || *error != NULL);
1106 
1107 	return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS);
1108 }
1109 
1110 static void
1111 e_spam_assassin_class_init (ESpamAssassinClass *class)
1112 {
1113 	GObjectClass *object_class;
1114 	EMailJunkFilterClass *junk_filter_class;
1115 
1116 	object_class = G_OBJECT_CLASS (class);
1117 	object_class->set_property = spam_assassin_set_property;
1118 	object_class->get_property = spam_assassin_get_property;
1119 	object_class->finalize = spam_assassin_finalize;
1120 
1121 	junk_filter_class = E_MAIL_JUNK_FILTER_CLASS (class);
1122 	junk_filter_class->filter_name = "SpamAssassin";
1123 	junk_filter_class->display_name = _("SpamAssassin");
1124 	junk_filter_class->available = spam_assassin_available;
1125 	junk_filter_class->new_config_widget = spam_assassin_new_config_widget;
1126 
1127 	g_object_class_install_property (
1128 		object_class,
1129 		PROP_LOCAL_ONLY,
1130 		g_param_spec_boolean (
1131 			"local-only",
1132 			"Local Only",
1133 			"Do not use tests requiring DNS lookups",
1134 			TRUE,
1135 			G_PARAM_READWRITE));
1136 
1137 	g_object_class_install_property (
1138 		object_class,
1139 		PROP_SPAMC_BINARY,
1140 		g_param_spec_string (
1141 			"spamc-binary",
1142 			"spamc Binary",
1143 			"File path for the spamc binary",
1144 			NULL,
1145 			G_PARAM_READWRITE));
1146 
1147 	g_object_class_install_property (
1148 		object_class,
1149 		PROP_SPAMD_BINARY,
1150 		g_param_spec_string (
1151 			"spamd-binary",
1152 			"spamd Binary",
1153 			"File path for the spamd binary",
1154 			NULL,
1155 			G_PARAM_READWRITE));
1156 
1157 	g_object_class_install_property (
1158 		object_class,
1159 		PROP_SOCKET_PATH,
1160 		g_param_spec_string (
1161 			"socket-path",
1162 			"Socket Path",
1163 			"Socket path for a SpamAssassin daemon",
1164 			NULL,
1165 			G_PARAM_READWRITE));
1166 
1167 	g_object_class_install_property (
1168 		object_class,
1169 		PROP_USE_DAEMON,
1170 		g_param_spec_boolean (
1171 			"use-daemon",
1172 			"Use Daemon",
1173 			"Whether to use a SpamAssassin daemon",
1174 			FALSE,
1175 			G_PARAM_READWRITE));
1176 }
1177 
1178 static void
1179 e_spam_assassin_class_finalize (ESpamAssassinClass *class)
1180 {
1181 }
1182 
1183 static void
1184 e_spam_assassin_interface_init (CamelJunkFilterInterface *interface)
1185 {
1186 	interface->classify = spam_assassin_classify;
1187 	interface->learn_junk = spam_assassin_learn_junk;
1188 	interface->learn_not_junk = spam_assassin_learn_not_junk;
1189 	interface->synchronize = spam_assassin_synchronize;
1190 }
1191 
1192 static void
1193 e_spam_assassin_init (ESpamAssassin *extension)
1194 {
1195 	GSettings *settings;
1196 
1197 	extension->socket_path_mutex = g_mutex_new ();
1198 
1199 	settings = g_settings_new ("org.gnome.evolution.spamassassin");
1200 
1201 	g_settings_bind (
1202 		settings, "local-only",
1203 		extension, "local-only",
1204 		G_SETTINGS_BIND_DEFAULT);
1205 	g_settings_bind (
1206 		settings, "spamc-binary",
1207 		G_OBJECT (extension), "spamc-binary",
1208 		G_SETTINGS_BIND_DEFAULT);
1209 	g_settings_bind (
1210 		settings, "spamd-binary",
1211 		G_OBJECT (extension), "spamd-binary",
1212 		G_SETTINGS_BIND_DEFAULT);
1213 	g_settings_bind (
1214 		settings, "socket-path",
1215 		extension, "socket-path",
1216 		G_SETTINGS_BIND_DEFAULT);
1217 	g_settings_bind (
1218 		settings, "use-daemon",
1219 		extension, "use-daemon",
1220 		G_SETTINGS_BIND_DEFAULT);
1221 
1222 	g_object_unref (settings);
1223 
1224 	if (extension->spamc_binary == NULL)
1225 		extension->spamc_binary = g_strdup (SPAMC_BINARY);
1226 
1227 	if (extension->spamd_binary == NULL)
1228 		extension->spamd_binary = g_strdup (SPAMD_BINARY);
1229 }
1230 
1231 G_MODULE_EXPORT void
1232 e_module_load (GTypeModule *type_module)
1233 {
1234 	e_spam_assassin_register_type (type_module);
1235 }
1236 
1237 G_MODULE_EXPORT void
1238 e_module_unload (GTypeModule *type_module)
1239 {
1240 }