1 /*
2  * e-mail-migrate.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  *
17  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
18  *
19  */
20 
21 #include "evolution-config.h"
22 
23 #include "e-mail-migrate.h"
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <utime.h>
31 #include <unistd.h>
32 #include <dirent.h>
33 #include <regex.h>
34 #include <errno.h>
35 #include <ctype.h>
36 
37 #include <glib/gi18n.h>
38 #include <glib/gstdio.h>
39 
40 #include <gtk/gtk.h>
41 
42 #include <libxml/tree.h>
43 #include <libxml/parser.h>
44 #include <libxml/xmlmemory.h>
45 
46 #include <shell/e-shell.h>
47 #include <shell/e-shell-migrate.h>
48 
49 #include <libemail-engine/libemail-engine.h>
50 
51 #include "e-mail-backend.h"
52 #include "em-utils.h"
53 
54 #define d(x) x
55 
56 /* 1.4 upgrade functions */
57 
58 static GtkProgressBar *progress;
59 
60 static void
em_migrate_set_progress(double percent)61 em_migrate_set_progress (double percent)
62 {
63 	gchar text[5];
64 
65 	snprintf (text, sizeof (text), "%d%%", (gint) (percent * 100.0f));
66 
67 	gtk_progress_bar_set_fraction (progress, percent);
68 	gtk_progress_bar_set_text (progress, text);
69 
70 	while (gtk_events_pending ())
71 		gtk_main_iteration ();
72 }
73 
74 enum {
75 	CP_UNIQUE = 0,
76 	CP_OVERWRITE,
77 	CP_APPEND
78 };
79 
80 static gint open_flags[3] = {
81 	O_WRONLY | O_CREAT | O_TRUNC,
82 	O_WRONLY | O_CREAT | O_TRUNC,
83 	O_WRONLY | O_CREAT | O_APPEND,
84 };
85 
86 static gboolean
cp(const gchar * src,const gchar * dest,gboolean show_progress,gint mode)87 cp (const gchar *src,
88     const gchar *dest,
89     gboolean show_progress,
90     gint mode)
91 {
92 	const gint nreadbuf = 65535;
93 	guchar *readbuf = NULL;
94 	gssize nread, nwritten;
95 	gint errnosav, readfd, writefd;
96 	gsize total = 0;
97 	struct stat st;
98 	struct utimbuf ut;
99 
100 	/* if the dest file exists and has content, abort - we don't
101 	 * want to corrupt their existing data */
102 	if (g_stat (dest, &st) == 0 && st.st_size > 0 && mode == CP_UNIQUE) {
103 		errno = EEXIST;
104 		return FALSE;
105 	}
106 
107 	if (g_stat (src, &st) == -1
108 	    || (readfd = g_open (src, O_RDONLY | O_BINARY, 0)) == -1)
109 		return FALSE;
110 
111 	if ((writefd = g_open (dest, open_flags[mode] | O_BINARY, 0666)) == -1) {
112 		errnosav = errno;
113 		close (readfd);
114 		errno = errnosav;
115 		return FALSE;
116 	}
117 
118 	readbuf = g_new0 (guchar, nreadbuf);
119 	do {
120 		do {
121 			nread = read (readfd, readbuf, nreadbuf);
122 		} while (nread == -1 && errno == EINTR);
123 
124 		if (nread == 0)
125 			break;
126 		else if (nread < 0)
127 			goto exception;
128 
129 		do {
130 			nwritten = write (writefd, readbuf, nread);
131 		} while (nwritten == -1 && errno == EINTR);
132 
133 		if (nwritten < nread)
134 			goto exception;
135 
136 		total += nwritten;
137 		if (show_progress)
138 			em_migrate_set_progress (((gdouble) total) / ((gdouble) st.st_size));
139 	} while (total < st.st_size);
140 
141 	#ifndef G_OS_WIN32
142 	if (fsync (writefd) == -1)
143 		goto exception;
144 	#endif
145 
146 	close (readfd);
147 	if (close (writefd) == -1)
148 		goto failclose;
149 
150 	ut.actime = st.st_atime;
151 	ut.modtime = st.st_mtime;
152 	utime (dest, &ut);
153 	if (chmod (dest, st.st_mode) == -1) {
154 		g_warning ("%s: Failed to chmod '%s': %s", G_STRFUNC, dest, g_strerror (errno));
155 	}
156 
157 	g_free (readbuf);
158 
159 	return TRUE;
160 
161  exception:
162 
163 	errnosav = errno;
164 	close (readfd);
165 	close (writefd);
166 	errno = errnosav;
167 
168  failclose:
169 
170 	errnosav = errno;
171 	unlink (dest);
172 	errno = errnosav;
173 
174 	g_free (readbuf);
175 
176 	return FALSE;
177 }
178 
179 static gboolean
emm_setup_initial(const gchar * data_dir)180 emm_setup_initial (const gchar *data_dir)
181 {
182 	GDir *dir;
183 	const gchar *d;
184 	gchar *local = NULL, *base;
185 	const gchar * const *language_names;
186 
187 	/* special-case - this means brand new install of evolution */
188 	/* FIXME: create default folders and stuff... */
189 
190 	d (printf ("Setting up initial mail tree\n"));
191 
192 	base = g_build_filename (data_dir, "local", NULL);
193 	if (g_mkdir_with_parents (base, 0700) == -1 && errno != EEXIST) {
194 		g_free (base);
195 		return FALSE;
196 	}
197 
198 	/* e.g. try en-AU then en, etc */
199 	language_names = g_get_language_names ();
200 	while (*language_names != NULL) {
201 		local = g_build_filename (
202 			EVOLUTION_PRIVDATADIR, "default",
203 			*language_names, "mail", "local", NULL);
204 		if (g_file_test (local, G_FILE_TEST_EXISTS))
205 			break;
206 		g_free (local);
207 		local = NULL;
208 		language_names++;
209 	}
210 
211 	/* Make sure we found one. */
212 	g_return_val_if_fail (local != NULL, FALSE);
213 
214 	dir = g_dir_open (local, 0, NULL);
215 	if (dir) {
216 		while ((d = g_dir_read_name (dir))) {
217 			gchar *src, *dest;
218 
219 			src = g_build_filename (local, d, NULL);
220 			dest = g_build_filename (base, d, NULL);
221 
222 			cp (src, dest, FALSE, CP_UNIQUE);
223 			g_free (dest);
224 			g_free (src);
225 		}
226 		g_dir_close (dir);
227 	}
228 
229 	g_free (local);
230 	g_free (base);
231 
232 	return TRUE;
233 }
234 
235 static void
em_rename_view_in_folder(gpointer data,gpointer user_data)236 em_rename_view_in_folder (gpointer data,
237                           gpointer user_data)
238 {
239 	const gchar *filename = data;
240 	const gchar *views_dir = user_data;
241 	gchar *folderpos, *dotpos;
242 
243 	g_return_if_fail (filename != NULL);
244 	g_return_if_fail (views_dir != NULL);
245 
246 	folderpos = strstr (filename, "-folder:__");
247 	if (!folderpos)
248 		folderpos = strstr (filename, "-folder___");
249 	if (!folderpos)
250 		return;
251 
252 	/* points on 'f' from the "folder" word */
253 	folderpos++;
254 	dotpos = strrchr (filename, '.');
255 	if (folderpos < dotpos && g_str_equal (dotpos, ".xml")) {
256 		GChecksum *checksum;
257 		gchar *oldname, *newname, *newfile;
258 		const gchar *md5_string;
259 
260 		*dotpos = 0;
261 
262 		/* use MD5 checksum of the folder URI, to not depend on its length */
263 		checksum = g_checksum_new (G_CHECKSUM_MD5);
264 		g_checksum_update (checksum, (const guchar *) folderpos, -1);
265 
266 		*folderpos = 0;
267 		md5_string = g_checksum_get_string (checksum);
268 		newfile = g_strconcat (filename, md5_string, ".xml", NULL);
269 		*folderpos = 'f';
270 		*dotpos = '.';
271 
272 		oldname = g_build_filename (views_dir, filename, NULL);
273 		newname = g_build_filename (views_dir, newfile, NULL);
274 
275 		if (g_rename (oldname, newname) == -1) {
276 			g_warning (
277 				"%s: Failed to rename '%s' to '%s': %s", G_STRFUNC,
278 				oldname, newname, g_strerror (errno));
279 		}
280 
281 		g_checksum_free (checksum);
282 		g_free (oldname);
283 		g_free (newname);
284 		g_free (newfile);
285 	}
286 }
287 
288 static void
em_rename_folder_views(EShellBackend * shell_backend)289 em_rename_folder_views (EShellBackend *shell_backend)
290 {
291 	const gchar *config_dir;
292 	gchar *views_dir;
293 	GDir *dir;
294 
295 	g_return_if_fail (shell_backend != NULL);
296 
297 	config_dir = e_shell_backend_get_config_dir (shell_backend);
298 	views_dir = g_build_filename (config_dir, "views", NULL);
299 
300 	dir = g_dir_open (views_dir, 0, NULL);
301 	if (dir) {
302 		GSList *to_rename = NULL;
303 		const gchar *filename;
304 
305 		while (filename = g_dir_read_name (dir), filename) {
306 			if (strstr (filename, "-folder:__") ||
307 			    strstr (filename, "-folder___"))
308 				to_rename = g_slist_prepend (to_rename, g_strdup (filename));
309 		}
310 
311 		g_dir_close (dir);
312 
313 		g_slist_foreach (to_rename, em_rename_view_in_folder, views_dir);
314 		g_slist_free_full (to_rename, g_free);
315 	}
316 
317 	g_free (views_dir);
318 }
319 
320 static gboolean
em_maybe_update_filter_rule_part(xmlNodePtr part)321 em_maybe_update_filter_rule_part (xmlNodePtr part)
322 {
323 	xmlNodePtr values;
324 	xmlChar *name, *value;
325 
326 	name = xmlGetProp (part, (xmlChar *) "name");
327 	if (name) {
328 		if (g_strcmp0 ((const gchar *) name, "completed-on") != 0) {
329 			xmlFree (name);
330 			return FALSE;
331 		}
332 
333 		xmlFree (name);
334 	} else {
335 		return FALSE;
336 	}
337 
338 	xmlSetProp (part, (xmlChar *) "name", (xmlChar *) "follow-up");
339 
340 	values = part->children;
341 	while (values) {
342 		if (g_strcmp0 ((const gchar *) values->name, "value") == 0) {
343 			name = xmlGetProp (values, (xmlChar *) "name");
344 			if (name) {
345 				if (g_strcmp0 ((const gchar *) name, "date-spec-type") == 0) {
346 					xmlSetProp (values, (xmlChar *) "name", (xmlChar *) "match-type");
347 
348 					value = xmlGetProp (values, (xmlChar *) "value");
349 					if (value) {
350 						if (g_strcmp0 ((const gchar *) value, "is set") == 0)
351 							xmlSetProp (values, (xmlChar *) "value", (xmlChar *) "is completed");
352 						else if (g_strcmp0 ((const gchar *) value, "is not set") == 0)
353 							xmlSetProp (values, (xmlChar *) "value", (xmlChar *) "is not completed");
354 
355 						xmlFree (value);
356 					}
357 				}
358 
359 				xmlFree (name);
360 			}
361 		}
362 
363 		values = values->next;
364 	}
365 
366 	return TRUE;
367 }
368 
369 static void
em_update_filter_rules_file(const gchar * filename)370 em_update_filter_rules_file (const gchar *filename)
371 {
372 	xmlNodePtr set, rule, root;
373 	xmlDocPtr doc;
374 	gboolean changed = FALSE;
375 
376 	if (!filename || !*filename || !g_file_test (filename, G_FILE_TEST_IS_REGULAR))
377 		return;
378 
379 	doc = e_xml_parse_file (filename);
380 	if (!doc)
381 		return;
382 
383 	root = xmlDocGetRootElement (doc);
384 	set = root && g_strcmp0 ((const gchar *) root->name, "filteroptions") == 0 ? root->children : NULL;
385 	while (set) {
386 		if (g_strcmp0 ((const gchar *) set->name, "ruleset") == 0) {
387 			rule = set->children;
388 			while (rule) {
389 				if (g_strcmp0 ((const gchar *) rule->name, "rule") == 0) {
390 					xmlNodePtr partset;
391 
392 					partset = rule->children;
393 					while (partset) {
394 						if (g_strcmp0 ((const gchar *) partset->name, "partset") == 0) {
395 							xmlNodePtr part;
396 
397 							part = partset->children;
398 							while (part) {
399 								if (g_strcmp0 ((const gchar *) part->name, "part") == 0) {
400 									changed = em_maybe_update_filter_rule_part (part) || changed;
401 								}
402 
403 								part = part->next;
404 							}
405 						}
406 
407 						partset = partset->next;
408 					}
409 				}
410 
411 				rule = rule->next;
412 			}
413 		}
414 
415 		set = set->next;
416 	}
417 
418 	if (changed)
419 		e_xml_save_file (filename, doc);
420 
421 	xmlFreeDoc (doc);
422 }
423 
424 static void
em_update_filter_rules(EShellBackend * shell_backend)425 em_update_filter_rules (EShellBackend *shell_backend)
426 {
427 	const gchar *config_dir;
428 	gchar *filename;
429 
430 	g_return_if_fail (shell_backend != NULL);
431 
432 	config_dir = e_shell_backend_get_config_dir (shell_backend);
433 
434 	filename = g_build_filename (config_dir, "filters.xml", NULL);
435 	em_update_filter_rules_file (filename);
436 	g_free (filename);
437 
438 	filename = g_build_filename (config_dir, "searches.xml", NULL);
439 	em_update_filter_rules_file (filename);
440 	g_free (filename);
441 
442 	filename = g_build_filename (config_dir, "vfolders.xml", NULL);
443 	em_update_filter_rules_file (filename);
444 	g_free (filename);
445 }
446 
447 static void
unset_initial_setup_write_finished_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)448 unset_initial_setup_write_finished_cb (GObject *source_object,
449 				       GAsyncResult *result,
450 				       gpointer user_data)
451 {
452 	ESource *source;
453 	GError *local_error = NULL;
454 
455 	g_return_if_fail (E_IS_SOURCE (source_object));
456 	g_return_if_fail (result != NULL);
457 
458 	source = E_SOURCE (source_object);
459 
460 	if (!e_source_write_finish (source, result, &local_error)) {
461 		g_warning ("%s: Failed to save source '%s' (%s): %s", G_STRFUNC, e_source_get_uid (source),
462 			e_source_get_display_name (source), local_error ? local_error->message : "Unknown error");
463 	}
464 
465 	g_clear_error (&local_error);
466 }
467 
468 static void
em_unset_initial_setup_for_accounts(EShellBackend * shell_backend)469 em_unset_initial_setup_for_accounts (EShellBackend *shell_backend)
470 {
471 	ESourceRegistry *registry;
472 	GList *sources, *link;
473 
474 	g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));
475 
476 	registry = e_shell_get_registry (e_shell_backend_get_shell (shell_backend));
477 	sources = e_source_registry_list_sources (registry, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
478 
479 	for (link = sources; link; link = g_list_next (link)) {
480 		ESource *source = link->data;
481 		ESourceMailAccount *mail_account;
482 
483 		mail_account = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
484 		if (e_source_mail_account_get_needs_initial_setup (mail_account)) {
485 			e_source_mail_account_set_needs_initial_setup (mail_account, FALSE);
486 
487 			e_source_write (source, NULL, unset_initial_setup_write_finished_cb, NULL);
488 		}
489 	}
490 
491 	g_list_free_full (sources, g_object_unref);
492 }
493 
494 /* The default value for this key changed from 'false' to 'true' in 3.27.90,
495    but existing users can be affected by the change when they never changed
496    the option, thus make sure their value will remain 'false' here. */
497 static void
em_ensure_global_view_setting_key(EShellBackend * shell_backend)498 em_ensure_global_view_setting_key (EShellBackend *shell_backend)
499 {
500 	GSettings *settings;
501 	GVariant *value;
502 
503 	settings = e_util_ref_settings ("org.gnome.evolution.mail");
504 
505 	value = g_settings_get_user_value (settings, "global-view-setting");
506 	if (value)
507 		g_variant_unref (value);
508 	else
509 		g_settings_set_boolean (settings, "global-view-setting", FALSE);
510 
511 	g_clear_object (&settings);
512 }
513 
514 gboolean
e_mail_migrate(EShellBackend * shell_backend,gint major,gint minor,gint micro,GError ** error)515 e_mail_migrate (EShellBackend *shell_backend,
516                 gint major,
517                 gint minor,
518                 gint micro,
519                 GError **error)
520 {
521 	const gchar *data_dir;
522 
523 	data_dir = e_shell_backend_get_data_dir (shell_backend);
524 
525 	if (major == 0)
526 		return emm_setup_initial (data_dir);
527 
528 	if (major <= 2 || (major == 3 && minor < 4))
529 		em_rename_folder_views (shell_backend);
530 
531 	if (major <= 2 || (major == 3 && minor < 17))
532 		em_update_filter_rules (shell_backend);
533 
534 	if (major <= 2 || (major == 3 && minor < 19) || (major == 3 && minor == 19 && micro < 90))
535 		em_unset_initial_setup_for_accounts (shell_backend);
536 
537 	if (major <= 2 || (major == 3 && minor < 27) || (major == 3 && minor == 27 && micro < 90))
538 		em_ensure_global_view_setting_key (shell_backend);
539 
540 	return TRUE;
541 }
542