/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Jeffrey Stedfast * * Copyright 2002 Ximian, Inc. (www.ximian.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include struct _storeinfo { char *base_url; char *namespace; char dir_sep; GPtrArray *folders; }; static char find_dir_sep (const char *lsub_response) { register const unsigned char *inptr; const unsigned char *inend; inptr = (const unsigned char *) lsub_response; inend = inptr + strlen (inptr); if (strncmp (inptr, "* LSUB (", 8)) return '\0'; inptr += 8; while (inptr < inend && *inptr != ')') inptr++; if (inptr >= inend) return '\0'; inptr++; while (inptr < inend && isspace ((int) *inptr)) inptr++; if (inptr >= inend) return '\0'; if (*inptr == '\"') inptr++; return inptr < inend ? *inptr : '\0'; } static void si_free (gpointer key, gpointer val, gpointer user_data) { struct _storeinfo *si = val; int i; g_free (si->base_url); g_free (si->namespace); if (si->folders) { for (i = 0; i < si->folders->len; i++) g_free (si->folders->pdata[i]); g_ptr_array_free (si->folders, TRUE); } g_free (si); } static char * get_base_url (const char *protocol, const char *uri) { unsigned char *base_url, *p; p = (unsigned char *) uri + strlen (protocol) + 1; if (!strncmp (p, "//", 2)) p += 2; base_url = p; p = strchr (p, '/'); base_url = g_strdup_printf ("%s://%.*s", protocol, p ? (int) (p - base_url) : (int) strlen (base_url), base_url); return base_url; } static char * imap_namespace (const char *uri) { unsigned char *name, *p; if ((name = strstr (uri, ";namespace=\"")) == NULL) return NULL; name += strlen (";namespace=\""); p = name; while (*p && *p != '\"') p++; return g_strndup (name, p - name); } static unsigned char tohex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; static char * hex_encode (const char *in, size_t len) { const unsigned char *inend = in + len; unsigned char *inptr, *outptr; char *outbuf; outptr = outbuf = g_malloc ((len * 3) + 1); inptr = (unsigned char *) in; while (inptr < inend) { if (*inptr > 127 || isspace ((int) *inptr)) { *outptr++ = '%'; *outptr++ = tohex[(*inptr >> 4) & 0xf]; *outptr++ = tohex[*inptr & 0xf]; inptr++; } else *outptr++ = *inptr++; } *outptr = '\0'; return outbuf; } #define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10) static char * hex_decode (const char *in, size_t len) { const unsigned char *inend = in + len; unsigned char *inptr, *outptr; char *outbuf; outptr = outbuf = g_malloc (len + 1); inptr = (unsigned char *) in; while (inptr < inend) { if (*inptr == '%') { if (isxdigit ((int) inptr[1]) && isxdigit ((int) inptr[2])) { *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]); inptr += 3; } else *outptr++ = *inptr++; } else *outptr++ = *inptr++; } *outptr = '\0'; return outbuf; } static char * find_folder (GPtrArray *folders, const char *folder, char *dir_sep) { const unsigned char *inptr, *inend; int inlen, len, diff, i; int quoted; len = strlen (folder); for (i = 0; i < folders->len; i++) { inptr = folders->pdata[i]; inend = inptr + strlen (inptr); if (strncmp (inptr, "* LSUB (", 8)) continue; inptr += 8; while (inptr < inend && *inptr != ')') inptr++; if (inptr >= inend) continue; inptr++; while (inptr < inend && isspace ((int) *inptr)) inptr++; if (inptr >= inend) continue; /* skip over the dir sep */ if (*inptr == '\"') inptr++; *dir_sep = *inptr++; if (*inptr == '\"') inptr++; if (inptr >= inend) continue; while (inptr < inend && isspace ((int) *inptr)) inptr++; if (inptr >= inend) continue; if (*inptr == '\"') { inptr++; quoted = 1; } else quoted = 0; inlen = strlen (inptr) - quoted; if (len > inlen) continue; diff = inlen - len; if (!strncmp (inptr + diff, folder, len)) return hex_encode (inptr, inlen); } *dir_sep = '\0'; return NULL; } static char * imap_url_upgrade (GHashTable *imap_sources, const char *uri) { struct _storeinfo *si; unsigned char *base_url, *folder, *p, *new = NULL; char dir_sep; base_url = get_base_url ("imap", uri); fprintf (stderr, "checking for %s... ", base_url); if (!(si = g_hash_table_lookup (imap_sources, base_url))) { fprintf (stderr, "not found.\n"); g_warning ("Unknown imap account: %s", base_url); g_free (base_url); return NULL; } fprintf (stderr, "found.\n"); p = (unsigned char *) uri + strlen (base_url) + 1; if (!strcmp (p, "INBOX")) { new = g_strdup_printf ("%s/INBOX", base_url); g_free (base_url); return new; } p = hex_decode (p, strlen (p)); fprintf (stderr, "checking for folder %s on %s... ", p, base_url); folder = find_folder (si->folders, p, &dir_sep); if (folder == NULL) { fprintf (stderr, "not found.\n"); folder = p; if (si->namespace) { if (!si->dir_sep) { fprintf (stderr, "checking for directory separator in namespace param... "); if (*si->namespace == '/') { dir_sep = '/'; } else { p = si->namespace; while (*p && !ispunct ((int) *p)) p++; dir_sep = (char) *p; } } else { dir_sep = si->dir_sep; } if (dir_sep) { fprintf (stderr, "found: '%c'\n", dir_sep); p = folder; folder = hex_encode (folder, strlen (folder)); if (si->namespace[strlen (si->namespace) - 1] == dir_sep) new = g_strdup_printf ("%s/%s%s", base_url, si->namespace, folder); else new = g_strdup_printf ("%s/%s%c%s", base_url, si->namespace, dir_sep, folder); g_free (folder); folder = p; p = new + strlen (base_url) + 1; while (*p) { if (*p == dir_sep) *p = '/'; p++; } } else { fprintf (stderr, "not found."); g_warning ("Cannot update settings for imap folder %s: unknown directory separator", uri); } } else { g_warning ("Cannot update settings for imap folder %s: unknown namespace", uri); } g_free (base_url); g_free (folder); return new; } else g_free (p); fprintf (stderr, "found.\n"); new = g_strdup_printf ("%s/%s", base_url, folder); g_free (folder); if (!si->dir_sep) si->dir_sep = dir_sep; if (dir_sep) { p = new + strlen (base_url) + 1; while (*p) { if (*p == dir_sep) *p = '/'; p++; } } g_free (base_url); return new; } static char * exchange_url_upgrade (const char *uri) { unsigned char *base_url, *folder; char *url; base_url = get_base_url ("exchange", uri); folder = (unsigned char *) uri + strlen (base_url) + 1; if (strncmp (folder, "exchange/", 9)) return g_strdup (uri); folder += 9; while (*folder && *folder != '/') folder++; if (*folder == '/') folder++; folder = hex_decode (folder, strlen (folder)); url = g_strdup_printf ("%s/personal/%s", base_url, folder); g_free (base_url); g_free (folder); return url; } static int mailer_upgrade_account_info (Bonobo_ConfigDatabase db, const char *key, int num, GHashTable *imap_sources) { char *path, *uri, *new; int i; for (i = 0; i < num; i++) { path = g_strdup_printf ("/Mail/Accounts/account_%s_folder_uri_%d", key, i); uri = bonobo_config_get_string (db, path, NULL); if (uri) { if (!strncmp (uri, "imap:", 5)) { new = imap_url_upgrade (imap_sources, uri); if (new) { bonobo_config_set_string (db, path, new, NULL); g_free (new); } } else if (!strncmp (uri, "exchange:", 9)) { new = exchange_url_upgrade (uri); bonobo_config_set_string (db, path, new, NULL); g_free (new); } } g_free (uri); g_free (path); } return 0; } static int mailer_upgrade_xml_file (GHashTable *imap_sources, const char *filename) { unsigned char *buffer, *inptr, *start, *uri, *new; ssize_t nread = 0, nwritten, n; gboolean url_need_upgrade; struct stat st; size_t len; char *bak; int fd; bak = g_strdup_printf ("%s.bak-1.0", filename); if (stat (bak, &st) != -1) { /* seems we have already converted this file? */ fprintf (stderr, "\n%s already exists, assuming %s has already been upgraded\n", bak, filename); g_free (bak); return 0; } if (stat (filename, &st) == -1 || (fd = open (filename, O_RDONLY)) == -1) { /* file doesn't exist? I guess nothing to upgrade here */ fprintf (stderr, "\nCould not open %s: %s\n", filename, strerror (errno)); g_free (bak); return 0; } start = buffer = g_malloc (st.st_size + 1); do { do { n = read (fd, buffer + nread, st.st_size - nread); } while (n == -1 && errno == EINTR); if (n > 0) nread += n; } while (n != -1 && nread < st.st_size); buffer[nread] = '\0'; if (nread < st.st_size) { /* failed to load the entire file? */ fprintf (stderr, "\nFailed to load %s: %s\n", filename, strerror (errno)); g_free (buffer); g_free (bak); close (fd); return -1; } close (fd); inptr = buffer; url_need_upgrade = FALSE; do { inptr = strstr (inptr, "uri=\""); if (inptr) { inptr += 5; url_need_upgrade = !strncmp (inptr, "imap:", 5) || !strncmp (inptr, "exchange:", 9); } } while (inptr && !url_need_upgrade); if (inptr == NULL) { /* no imap urls in this xml file, so no need to "upgrade" it */ fprintf (stdout, "\nNo updates required for %s\n", filename); g_free (buffer); g_free (bak); return 0; } if (rename (filename, bak) == -1) { /* failed to backup xml file */ fprintf (stderr, "\nFailed to create backup file %s: %s\n", bak, strerror (errno)); g_free (buffer); g_free (bak); return -1; } if ((fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) { /* failed to create new xml file */ fprintf (stderr, "\nFailed to create new %s: %s\n", filename, strerror (errno)); rename (bak, filename); g_free (buffer); g_free (bak); return -1; } while (inptr != NULL) { len = inptr - start; nwritten = 0; do { do { n = write (fd, start + nwritten, len - nwritten); } while (n == -1 && errno == EINTR); if (n > 0) nwritten += n; } while (n != -1 && nwritten < len); if (nwritten < len) goto exception; start = inptr; while (*start && *start != '"') start++; uri = g_strndup (inptr, start - inptr); if (!strncmp (uri, "imap:", 5)) { if ((new = imap_url_upgrade (imap_sources, uri)) == NULL) { new = uri; uri = NULL; } } else if (!strncmp (uri, "exchange:", 9)) { new = exchange_url_upgrade (uri); } else { new = uri; uri = NULL; } g_free (uri); nwritten = 0; len = strlen (new); do { do { n = write (fd, new + nwritten, len - nwritten); } while (n == -1 && errno == EINTR); if (n > 0) nwritten += n; } while (n != -1 && nwritten < len); g_free (new); if (nwritten < len) goto exception; inptr = start; url_need_upgrade = FALSE; do { inptr = strstr (inptr, "uri=\""); if (inptr) { inptr += 5; url_need_upgrade = !strncmp (inptr, "imap:", 5) || !strncmp (inptr, "exchange:", 9); } } while (inptr && !url_need_upgrade); } nwritten = 0; len = strlen (start); do { do { n = write (fd, start + nwritten, len - nwritten); } while (n == -1 && errno == EINTR); if (n > 0) nwritten += n; } while (n != -1 && nwritten < len); if (nwritten < len) goto exception; if (fsync (fd) == -1) goto exception; close (fd); g_free (buffer); fprintf (stdout, "\nSuccessfully upgraded %s\nPrevious settings saved in %s\n\n", filename, bak); g_free (bak); return 0; exception: fprintf (stderr, "\nFailed to save updated settings to %s: %s\n\n", filename, strerror (errno)); close (fd); g_free (buffer); unlink (filename); rename (bak, filename); g_free (bak); return -1; } static char * shortcuts_upgrade_uri (GHashTable *accounts, GHashTable *imap_sources, const char *account, const char *folder) { char *url, *name, *decoded, *new = NULL; struct _storeinfo *si; int type; type = GPOINTER_TO_INT ((si = g_hash_table_lookup (accounts, account))); if (type == 1) { /* exchange */ decoded = hex_decode (folder, strlen (folder)); name = g_strdup_printf ("personal/%s", decoded); g_free (decoded); return name; } else { /* imap */ url = g_strdup_printf ("%s/%s", si->base_url, folder); new = imap_url_upgrade (imap_sources, url); g_free (url); if (new) { name = new + strlen (si->base_url) + 1; name = hex_decode (name, strlen (name)); g_free (new); return name; } } return NULL; } static int shortcuts_upgrade_xml_file (GHashTable *accounts, GHashTable *imap_sources, const char *filename) { unsigned char *buffer, *inptr, *start, *folder, *new, *p, *account = NULL; ssize_t nread = 0, nwritten, n; gboolean url_need_upgrade; struct stat st; size_t len; char *bak; int fd; bak = g_strdup_printf ("%s.bak-1.0", filename); if (stat (bak, &st) != -1) { /* seems we have already converted this file? */ fprintf (stderr, "\n%s already exists, assuming %s has already been upgraded\n", bak, filename); g_free (bak); return 0; } if (stat (filename, &st) == -1 || (fd = open (filename, O_RDONLY)) == -1) { /* file doesn't exist? I guess nothing to upgrade here */ fprintf (stderr, "\nCould not open %s: %s\n", filename, strerror (errno)); g_free (bak); return 0; } start = buffer = g_malloc (st.st_size + 1); do { do { n = read (fd, buffer + nread, st.st_size - nread); } while (n == -1 && errno == EINTR); if (n > 0) nread += n; } while (n != -1 && nread < st.st_size); buffer[nread] = '\0'; if (nread < st.st_size) { /* failed to load the entire file? */ fprintf (stderr, "\nFailed to load %s: %s\n", filename, strerror (errno)); g_free (buffer); g_free (bak); close (fd); return -1; } close (fd); inptr = buffer; url_need_upgrade = FALSE; do { g_free (account); inptr = strstr (inptr, ">evolution:/"); if (inptr) { inptr += 12; p = account = inptr; while (*p && *p != '/') p++; account = g_strndup (account, p - account); url_need_upgrade = GPOINTER_TO_INT (g_hash_table_lookup (accounts, account)); } } while (inptr && !url_need_upgrade); if (inptr == NULL) { /* no imap urls in this xml file, so no need to "upgrade" it */ fprintf (stdout, "\nNo updates required for %s\n", filename); g_free (buffer); g_free (bak); return 0; } if (rename (filename, bak) == -1) { /* failed to backup xml file */ fprintf (stderr, "\nFailed to create backup file %s: %s\n", bak, strerror (errno)); g_free (buffer); g_free (bak); return -1; } if ((fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) { /* failed to create new xml file */ fprintf (stderr, "\nFailed to create new %s: %s\n", filename, strerror (errno)); rename (bak, filename); g_free (buffer); g_free (bak); return -1; } while (inptr != NULL) { len = inptr - start; nwritten = 0; do { do { n = write (fd, start + nwritten, len - nwritten); } while (n == -1 && errno == EINTR); if (n > 0) nwritten += n; } while (n != -1 && nwritten < len); if (nwritten < len) goto exception; if (!(start = strstr (inptr, ""))) { start = inptr; while (*start && *start != '<') start++; } folder = g_strndup (inptr, start - inptr); new = shortcuts_upgrade_uri (accounts, imap_sources, account, folder); g_free (account); account = NULL; g_free (folder); nwritten = 0; len = strlen (new); do { do { n = write (fd, new + nwritten, len - nwritten); } while (n == -1 && errno == EINTR); if (n > 0) nwritten += n; } while (n != -1 && nwritten < len); g_free (new); if (nwritten < len) goto exception; inptr = start; url_need_upgrade = FALSE; do { g_free (account); inptr = strstr (inptr, ">evolution:/"); if (inptr) { inptr += 12; p = account = inptr; while (*p && *p != '/') p++; account = g_strndup (account, p - account); url_need_upgrade = GPOINTER_TO_INT (g_hash_table_lookup (accounts, account)); } } while (inptr && !url_need_upgrade); } nwritten = 0; len = strlen (start); do { do { n = write (fd, start + nwritten, len - nwritten); } while (n == -1 && errno == EINTR); if (n > 0) nwritten += n; } while (n != -1 && nwritten < len); if (nwritten < len) goto exception; if (fsync (fd) == -1) goto exception; close (fd); g_free (buffer); fprintf (stdout, "\nSuccessfully upgraded %s\nPrevious settings saved in %s\n\n", filename, bak); g_free (bak); return 0; exception: fprintf (stderr, "\nFailed to save updated settings to %s: %s\n\n", filename, strerror (errno)); close (fd); g_free (buffer); unlink (filename); rename (bak, filename); g_free (bak); return -1; } static int mailer_upgrade (Bonobo_ConfigDatabase db) { GHashTable *imap_sources, *accounts; char *path, *uri, *bak; char *account; int num, i; if ((num = bonobo_config_get_long_with_default (db, "/Mail/Accounts/num", 0, NULL)) == 0) { /* nothing to upgrade */ return 0; } accounts = g_hash_table_new (g_str_hash, g_str_equal); imap_sources = g_hash_table_new (g_str_hash, g_str_equal); for (i = 0; i < num; i++) { struct _storeinfo *si; struct stat st; char *string; guint32 tmp; FILE *fp; int j; path = g_strdup_printf ("/Mail/Accounts/source_url_%d", i); uri = bonobo_config_get_string (db, path, NULL); g_free (path); if (uri && !strncmp (uri, "imap:", 5)) { path = g_strdup_printf ("/Mail/Accounts/account_name_%d", i); account = bonobo_config_get_string (db, path, NULL); g_free (path); si = g_new (struct _storeinfo, 1); si->base_url = get_base_url ("imap", uri); si->namespace = imap_namespace (uri); si->dir_sep = '\0'; si->folders = NULL; path = si->base_url + 7; path = g_strdup_printf ("%s/evolution/mail/imap/%s/storeinfo", getenv ("HOME"), path); if (stat (path, &st) != -1 && (fp = fopen (path, "r")) != NULL) { camel_file_util_decode_uint32 (fp, &tmp); camel_file_util_decode_uint32 (fp, &tmp); j = 0; si->folders = g_ptr_array_new (); while (camel_file_util_decode_string (fp, &string) != -1) { if (j++ > 0) { g_ptr_array_add (si->folders, string); } else { if (!si->namespace) si->namespace = string; else g_free (string); camel_file_util_decode_uint32 (fp, &tmp); si->dir_sep = (char) tmp & 0xff; } } fclose (fp); } g_free (path); if (si->folders && si->folders->len > 0) si->dir_sep = find_dir_sep (si->folders->pdata[0]); g_hash_table_insert (imap_sources, si->base_url, si); if (account) g_hash_table_insert (accounts, account, si); } else if (uri && !strncmp (uri, "exchange:", 9)) { path = g_strdup_printf ("/Mail/Accounts/account_name_%d", i); account = bonobo_config_get_string (db, path, NULL); g_free (path); if (account) g_hash_table_insert (accounts, account, GINT_TO_POINTER (1)); } g_free (uri); } if (g_hash_table_size (accounts) == 0) { /* user doesn't have any imap/exchange accounts - nothing to upgrade */ g_hash_table_destroy (imap_sources); return 0; } /* upgrade user's account info (bug #29135) */ mailer_upgrade_account_info (db, "drafts", num, imap_sources); mailer_upgrade_account_info (db, "sent", num, imap_sources); /* upgrade user's filters/vfolders (bug #24451) */ path = g_strdup_printf ("%s/evolution/filters.xml", getenv ("HOME")); mailer_upgrade_xml_file (imap_sources, path); g_free (path); path = g_strdup_printf ("%s/evolution/vfolders.xml", getenv ("HOME")); mailer_upgrade_xml_file (imap_sources, path); g_free (path); /* upgrade user's shortcuts (there's no bug # for this one) */ path = g_strdup_printf ("%s/evolution/shortcuts.xml", getenv ("HOME")); shortcuts_upgrade_xml_file (accounts, imap_sources, path); g_free (path); g_hash_table_foreach (imap_sources, si_free, NULL); g_hash_table_destroy (imap_sources); path = g_strdup_printf ("%s/evolution/mail/imap", getenv ("HOME")); bak = g_strdup_printf ("%s.bak-1.0", path); if (rename (path, bak) == -1) fprintf (stderr, "\nFailed to backup Evolution 1.0's IMAP cache: %s\n", strerror (errno)); g_free (path); g_free (bak); return 0; } static Bonobo_ConfigDatabase get_config_db (void) { Bonobo_ConfigDatabase db; CORBA_Environment ev; CORBA_exception_init (&ev); db = bonobo_get_object ("wombat:", "Bonobo/ConfigDatabase", &ev); if (BONOBO_EX (&ev) || db == CORBA_OBJECT_NIL) { fprintf (stderr, "get_config_db(): Could not get the config database object '%s'", bonobo_exception_get_text (&ev)); db = CORBA_OBJECT_NIL; } CORBA_exception_free (&ev); return db; } static int upgrade (void) { Bonobo_ConfigDatabase db; CORBA_Environment ev; if ((db = get_config_db ()) == CORBA_OBJECT_NIL) g_error ("Could not get config db"); mailer_upgrade (db); CORBA_exception_init (&ev); Bonobo_ConfigDatabase_sync (db, &ev); gtk_main_quit (); return FALSE; } int main (int argc, char **argv) { CORBA_ORB orb; gnome_init ("evolution-upgrade", "1.0", argc, argv); if ((orb = oaf_init (argc, argv)) == NULL) g_error ("Cannot init oaf"); if (bonobo_init (orb, CORBA_OBJECT_NIL, CORBA_OBJECT_NIL) == FALSE) g_error ("Cannot init bonobo"); gtk_idle_add ((GtkFunction) upgrade, NULL); bonobo_main (); return 0; }