1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  *
4  * Copyright (C) 1997-2013 Stuart Parmenter and others,
5  *                         See the file AUTHORS for a list.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20  * 02111-1307, USA.
21  */
22 
23 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
24 # include "config.h"
25 #endif                          /* HAVE_CONFIG_H */
26 #include "libbalsa.h"
27 
28 #include <glib.h>
29 
30 #include <string.h>
31 #include <stdlib.h>
32 #include <sys/utsname.h>
33 #include <sys/stat.h>
34 #include <stdarg.h>
35 #include <unistd.h>
36 
37 #ifdef HAVE_NOTIFY
38 #include <libnotify/notify.h>
39 #endif
40 
41 #if ENABLE_LDAP
42 #include <ldap.h>
43 #endif
44 
45 #if HAVE_COMPFACE
46 #include <compface.h>
47 #endif                          /* HAVE_COMPFACE */
48 
49 #if HAVE_GTKSOURCEVIEW
50 #include <gtksourceview/gtksourceview.h>
51 #include <gtksourceview/gtksourcebuffer.h>
52 /* note GtkSourceview 1 and 2 have a slightly different API */
53 #if (HAVE_GTKSOURCEVIEW == 1)
54 #  include <gtksourceview/gtksourcetag.h>
55 #  include <gtksourceview/gtksourcetagstyle.h>
56 #else
57 #  include <gtksourceview/gtksourcelanguage.h>
58 #  include <gtksourceview/gtksourcelanguagemanager.h>
59 #  include <gtksourceview/gtksourcestylescheme.h>
60 #  include <gtksourceview/gtksourcestyleschememanager.h>
61 #endif
62 #endif
63 
64 #include "misc.h"
65 #include "missing.h"
66 #include <glib/gi18n.h>
67 
68 #ifdef BALSA_USE_THREADS
69 static pthread_t main_thread_id;
70 #endif
71 
72 
73 void
libbalsa_message(const char * fmt,...)74 libbalsa_message(const char *fmt, ...)
75 {
76     va_list va_args;
77 
78     va_start(va_args, fmt);
79     libbalsa_information_varg(NULL, LIBBALSA_INFORMATION_MESSAGE,
80                               fmt, va_args);
81     va_end(va_args);
82 }
83 
84 void
libbalsa_init(LibBalsaInformationFunc information_callback)85 libbalsa_init(LibBalsaInformationFunc information_callback)
86 {
87     struct utsname utsname;
88 
89 #ifdef HAVE_NOTIFY
90     notify_init("Basics");
91 #endif
92 
93 #ifdef BALSA_USE_THREADS
94 #if !GLIB_CHECK_VERSION(2, 35, 0)
95     if (!g_thread_supported()) {
96 	g_error("Threads have not been initialised.");
97     }
98 #endif                          /* !GLIB_CHECK_VERSION(2, 35, 0) */
99     main_thread_id = pthread_self();
100 #endif
101 
102     uname(&utsname);
103 
104     libbalsa_real_information_func = information_callback;
105 
106     g_mime_init(GMIME_ENABLE_RFC2047_WORKAROUNDS);
107 
108     GMIME_TYPE_DATA_WRAPPER;
109     GMIME_TYPE_FILTER;
110     GMIME_TYPE_FILTER_CRLF;
111     GMIME_TYPE_PARSER;
112     GMIME_TYPE_STREAM;
113     GMIME_TYPE_STREAM_BUFFER;
114     GMIME_TYPE_STREAM_MEM;
115     GMIME_TYPE_STREAM_NULL;
116 
117     /* Register our types to avoid possible race conditions. See
118        output of "valgrind --tool=helgrind --log-file=balsa.log balsa"
119        Mailbox type registration is needed also for
120        libbalsa_mailbox_new_from_config() to work. */
121     LIBBALSA_TYPE_MAILBOX_LOCAL;
122     LIBBALSA_TYPE_MAILBOX_POP3;
123     LIBBALSA_TYPE_MAILBOX_IMAP;
124     LIBBALSA_TYPE_MAILBOX_MBOX;
125     LIBBALSA_TYPE_MAILBOX_MH;
126     LIBBALSA_TYPE_MAILBOX_MAILDIR;
127     LIBBALSA_TYPE_MESSAGE;
128 
129     LIBBALSA_TYPE_ADDRESS_BOOK_VCARD;
130     LIBBALSA_TYPE_ADDRESS_BOOK_EXTERN;
131     LIBBALSA_TYPE_ADDRESS_BOOK_LDIF;
132 #if ENABLE_LDAP
133     LIBBALSA_TYPE_ADDRESS_BOOK_LDAP;
134 #endif
135 #if HAVE_SQLITE
136     LIBBALSA_TYPE_ADDRESS_BOOK_GPE;
137 #endif
138 #if HAVE_RUBRICA
139     LIBBALSA_TYPE_ADDRESS_BOOK_RUBRICA;
140 #endif
141 }
142 
143 
144 /* libbalsa_rot:
145    return rot13'ed string.
146 */
147 gchar *
libbalsa_rot(const gchar * pass)148 libbalsa_rot(const gchar * pass)
149 {
150     gchar *buff;
151     gint len = 0, i = 0;
152 
153     /*PKGW: let's do the assert() BEFORE we coredump... */
154 
155     len = strlen(pass);
156     buff = g_strdup(pass);
157 
158     for (i = 0; i < len; i++) {
159 	if ((buff[i] <= 'M' && buff[i] >= 'A')
160 	    || (buff[i] <= 'm' && buff[i] >= 'a'))
161 	    buff[i] += 13;
162 	else if ((buff[i] <= 'Z' && buff[i] >= 'N')
163 		 || (buff[i] <= 'z' && buff[i] >= 'n'))
164 	    buff[i] -= 13;
165     }
166     return buff;
167 }
168 
169 
170 /* libbalsa_guess_email_address:
171    Email address can be determined in four ways:
172    1. Using the environment variable 'EMAIL'
173 
174    2. The file '/etc/mailname' should contain the external host
175       address for the host. Prepend the username (`username`@`cat
176       /etc/mailname`).
177 
178    3. Append the domainname to the user name.
179    4. Append the hostname to the user name.
180 
181 */
182 gchar*
libbalsa_guess_email_address(void)183 libbalsa_guess_email_address(void)
184 {
185     /* Q: Find this location with configure? or at run-time? */
186     static const gchar* MAILNAME_FILE = "/etc/mailname";
187     char hostbuf[512];
188     FILE *mailname_in = NULL;
189 
190     gchar* preset, *domain;
191     if(g_getenv("EMAIL") != NULL){                  /* 1. */
192         preset = g_strdup(g_getenv("EMAIL"));
193     } else if( (mailname_in = fopen(MAILNAME_FILE, "r")) != NULL
194               && fgets(hostbuf, sizeof(hostbuf)-1, mailname_in)){ /* 2. */
195         hostbuf[sizeof(hostbuf)-1] = '\0';
196         preset = g_strconcat(g_get_user_name(), "@", hostbuf, NULL);
197 
198     }else if((domain = libbalsa_get_domainname())){ /* 3. */
199         preset = g_strconcat(g_get_user_name(), "@", domain, NULL);
200         g_free(domain);
201     } else {                                        /* 4. */
202         gethostname(hostbuf, 511);
203         preset = g_strconcat(g_get_user_name(), "@", hostbuf, NULL);
204     }
205     if (mailname_in)
206         fclose(mailname_in);
207     return preset;
208 }
209 
210 /* libbalsa_guess_mail_spool
211 
212    Returns an allocated gchar * with our best guess of the user's
213    mail spool file.
214 */
215 gchar *
libbalsa_guess_mail_spool(void)216 libbalsa_guess_mail_spool(void)
217 {
218     gchar *env;
219     gchar *spool;
220     static const gchar *guesses[] = {
221 	"/var/mail/",
222 	"/var/spool/mail/",
223 	"/usr/spool/mail/",
224 	"/usr/mail/",
225 	NULL
226     };
227 
228     if ((env = getenv("MAIL")) != NULL)
229 	return g_strdup(env);
230 
231     if ((env = getenv("USER")) != NULL) {
232         int i;
233 
234 	for (i = 0; guesses[i] != NULL; i++) {
235 	    spool = g_strconcat(guesses[i], env, NULL);
236 
237 	    if (g_file_test(spool, G_FILE_TEST_EXISTS))
238 		return spool;
239 
240 	    g_free(spool);
241 	}
242     }
243 
244     /* libmutt's configure.in indicates that this
245      * ($HOME/mailbox) exists on
246      * some systems, and it's a good enough default if we
247      * can't guess it any other way. */
248     return g_strconcat(g_get_home_dir(), "/mailbox", NULL);
249 }
250 
251 
libbalsa_ldap_exists(const gchar * server)252 gboolean libbalsa_ldap_exists(const gchar *server)
253 {
254 #if ENABLE_LDAP
255     LDAP *ldap;
256     ldap_initialize(&ldap, server);
257 
258     if(ldap) {
259 	ldap_unbind_ext(ldap, NULL, NULL);
260 	return TRUE;
261     }
262 #endif /* #if ENABLE_LDAP */
263 
264     return FALSE;
265 }
266 
267 gchar*
libbalsa_date_to_utf8(const time_t * date,const gchar * date_string)268 libbalsa_date_to_utf8(const time_t *date, const gchar *date_string)
269 {
270     struct tm footime;
271     gchar rettime[128];
272 
273     g_return_val_if_fail(date != NULL, NULL);
274     g_return_val_if_fail(date_string != NULL, NULL);
275 
276     if (!*date)
277         /* Missing "Date:" field?  It is required by RFC 2822. */
278         return NULL;
279 
280     localtime_r(date, &footime);
281 
282     strftime(rettime, sizeof(rettime), date_string, &footime);
283 
284     return g_locale_to_utf8(rettime, -1, NULL, NULL, NULL);
285 }
286 
287 LibBalsaMessageStatus
libbalsa_get_icon_from_flags(LibBalsaMessageFlag flags)288 libbalsa_get_icon_from_flags(LibBalsaMessageFlag flags)
289 {
290     LibBalsaMessageStatus icon;
291     if (flags & LIBBALSA_MESSAGE_FLAG_DELETED)
292 	icon = LIBBALSA_MESSAGE_STATUS_DELETED;
293     else if (flags & LIBBALSA_MESSAGE_FLAG_NEW)
294 	icon = LIBBALSA_MESSAGE_STATUS_UNREAD;
295     else if (flags & LIBBALSA_MESSAGE_FLAG_FLAGGED)
296 	icon = LIBBALSA_MESSAGE_STATUS_FLAGGED;
297     else if (flags & LIBBALSA_MESSAGE_FLAG_REPLIED)
298 	icon = LIBBALSA_MESSAGE_STATUS_REPLIED;
299     else
300 	icon = LIBBALSA_MESSAGE_STATUS_ICONS_NUM;
301     return icon;
302 }
303 
304 
305 #ifdef BALSA_USE_THREADS
306 #include <pthread.h>
307 typedef struct {
308     pthread_mutex_t lock;
309     pthread_cond_t condvar;
310     int (*cb)(void *arg);
311     void *arg;
312     int res;
313 } AskData;
314 
315 /* ask_cert_idle:
316    called in MT mode by the main thread.
317  */
318 static gboolean
ask_idle(gpointer data)319 ask_idle(gpointer data)
320 {
321     AskData* ad = (AskData*)data;
322     printf("ask_idle: ENTER %p\n", data);
323     gdk_threads_enter();
324     ad->res = (ad->cb)(ad->arg);
325     gdk_threads_leave();
326     pthread_cond_signal(&ad->condvar);
327     printf("ask_idle: LEAVE %p\n", data);
328     return FALSE;
329 }
330 
331 /* libbalsa_ask_mt:
332    executed with GDK UNLOCKED. see mailbox_imap_open() and
333    imap_dir_cb()/imap_folder_imap_dir().
334 */
335 static int
libbalsa_ask(gboolean (* cb)(void * arg),void * arg)336 libbalsa_ask(gboolean (*cb)(void *arg), void *arg)
337 {
338     AskData ad;
339 
340     if (pthread_self() == main_thread_id) {
341         int ret;
342         printf("Main thread asks the following question.\n");
343         gdk_threads_enter();
344         ret = cb(arg);
345         gdk_threads_leave();
346         return ret;
347     }
348     printf("Side thread asks the following question.\n");
349     pthread_mutex_init(&ad.lock, NULL);
350     pthread_cond_init(&ad.condvar, NULL);
351     ad.cb  = cb;
352     ad.arg = arg;
353 
354     pthread_mutex_lock(&ad.lock);
355     pthread_cond_init(&ad.condvar, NULL);
356     g_idle_add(ask_idle, &ad);
357     pthread_cond_wait(&ad.condvar, &ad.lock);
358 
359     pthread_cond_destroy(&ad.condvar);
360     pthread_mutex_unlock(&ad.lock);
361     return ad.res;
362 }
363 #else /* BALSA_USE_THREADS */
364 static gboolean
libbalsa_ask(gboolean (* cb)(void * arg),void * arg)365 libbalsa_ask(gboolean (*cb)(void *arg), void *arg)
366 {
367     return cb(arg);
368 }
369 #endif /* BALSA_USE_THREADS */
370 
371 
372 #if defined(USE_SSL)
373 #include <openssl/ssl.h>
374 #include <openssl/x509.h>
375 #include <openssl/err.h>
376 static int libbalsa_ask_for_cert_acceptance(X509 *cert,
377 					    const char *explanation);
378 static char*
asn1time_to_string(ASN1_UTCTIME * tm)379 asn1time_to_string(ASN1_UTCTIME *tm)
380 {
381     char buf[64];
382     BIO *bio  = BIO_new(BIO_s_mem());
383     strncpy(buf, _("Invalid date"), sizeof(buf)); buf[sizeof(buf)-1]='\0';
384 
385     if(ASN1_TIME_print(bio, tm)) {
386         int cnt;
387         cnt = BIO_read(bio, buf, sizeof(buf)-1);
388         buf[cnt] = '\0';
389     }
390     BIO_free(bio);
391     return g_strdup(buf);
392 }
393 
394 static char*
x509_get_part(char * line,const char * ndx)395 x509_get_part (char *line, const char *ndx)
396 {
397     static char ret[256];
398     char *c;
399 
400     strncpy (ret, _("Unknown"), sizeof (ret)); ret[sizeof(ret)-1]='\0';
401 
402     c = strstr(line, ndx);
403     if (c) {
404         char *c2;
405 
406         c += strlen (ndx);
407         c2 = strchr (c, '/');
408         if (c2)
409             *c2 = '\0';
410         strncpy (ret, c, sizeof (ret));
411         if (c2)
412             *c2 = '/';
413     }
414 
415     return ret;
416 }
417 static void
x509_fingerprint(char * s,unsigned len,X509 * cert)418 x509_fingerprint (char *s, unsigned len, X509 * cert)
419 {
420     unsigned j, i, n, c;
421     unsigned char md[EVP_MAX_MD_SIZE];
422 
423 
424     X509_digest(cert, EVP_md5(), md, &n);
425     if(len<3*n) n = len/3;
426     for (j=i=0; j<n; j++) {
427         c = (md[j] >>4) & 0xF; s[i++] = c<10 ? c + '0' : c+'A'-10;
428         c = md[j] & 0xF;       s[i++] = c<10 ? c + '0' : c+'A'-10;
429         if(j<n-1) s[i++] = ':';
430     }
431     s[i] = '\0';
432 }
433 
434 static GList *accepted_certs = NULL; /* certs accepted for this session */
435 
436 #ifdef BALSA_USE_THREADS
437 static pthread_mutex_t certificate_lock = PTHREAD_MUTEX_INITIALIZER;
438 #define LOCK_CERTIFICATES   pthread_mutex_lock(&certificate_lock)
439 #define UNLOCK_CERTIFICATES pthread_mutex_unlock(&certificate_lock)
440 #else
441 #define LOCK_CERTIFICATES
442 #define UNLOCK_CERTIFICATES
443 #endif
444 
445 void
libbalsa_certs_destroy(void)446 libbalsa_certs_destroy(void)
447 {
448     LOCK_CERTIFICATES;
449     g_list_foreach(accepted_certs, (GFunc)X509_free, NULL);
450     g_list_free(accepted_certs);
451     accepted_certs = NULL;
452     UNLOCK_CERTIFICATES;
453 }
454 
455 /* compare Example 10-7 in the OpenSSL book */
456 gboolean
libbalsa_is_cert_known(X509 * cert,long vfy_result)457 libbalsa_is_cert_known(X509* cert, long vfy_result)
458 {
459     X509 *tmpcert = NULL;
460     FILE *fp;
461     gchar *cert_name;
462     gboolean res = FALSE;
463     GList *lst;
464 
465     LOCK_CERTIFICATES;
466     for(lst = accepted_certs; lst; lst = lst->next) {
467         int res = X509_cmp(cert, lst->data);
468         if(res == 0) {
469 	    UNLOCK_CERTIFICATES;
470             return TRUE;
471 	}
472     }
473 
474     cert_name = g_strconcat(g_get_home_dir(), "/.balsa/certificates", NULL);
475 
476     fp = fopen(cert_name, "rt");
477     g_free(cert_name);
478     if(fp) {
479         /*
480         printf("Looking for cert: %s\n",
481                X509_NAME_oneline(X509_get_subject_name (cert),
482                                  buf, sizeof (buf)));
483         */
484         res = FALSE;
485         while ((tmpcert = PEM_read_X509(fp, NULL, NULL, NULL)) != NULL) {
486             res = X509_cmp(cert, tmpcert)==0;
487             X509_free(tmpcert);
488             if(res) break;
489         }
490         ERR_clear_error();
491         fclose(fp);
492     }
493     UNLOCK_CERTIFICATES;
494 
495     if(!res) {
496 	const char *reason = X509_verify_cert_error_string(vfy_result);
497 	res = libbalsa_ask_for_cert_acceptance(cert, reason);
498 	LOCK_CERTIFICATES;
499 	if(res == 2) {
500 	    cert_name = g_strconcat(g_get_home_dir(),
501 				    "/.balsa/certificates", NULL);
502             libbalsa_assure_balsa_dir();
503 	    fp = fopen(cert_name, "a");
504 	    if (fp) {
505 		if(PEM_write_X509 (fp, cert))
506 		    res = TRUE;
507 		fclose(fp);
508 	    }
509 	    g_free(cert_name);
510 	}
511 	if(res == 1)
512 	    accepted_certs =
513 		g_list_prepend(accepted_certs, X509_dup(cert));
514 	UNLOCK_CERTIFICATES;
515     }
516 
517     return res;
518 }
519 
520 /* libbalsa_ask_for_cert_acceptance():
521    returns:
522    OP_EXIT on reject.
523    OP_SAVE - on accept and save.
524    OP_MAX - on accept once.
525    TODO: check treading issues.
526 
527 */
528 struct AskCertData {
529     X509 *certificate;
530     const char *explanation;
531 };
532 
533 static int
ask_cert_real(void * data)534 ask_cert_real(void *data)
535 {
536     static const char *part[] =
537         {"/CN=", "/Email=", "/O=", "/OU=", "/L=", "/ST=", "/C="};
538 
539     struct AskCertData *acd = (struct AskCertData*)data;
540     X509 *cert = acd->certificate;
541     char buf[256]; /* fingerprint requires EVP_MAX_MD_SIZE*3 */
542     char *name = NULL, *c, *valid_from, *valid_until;
543     GtkWidget* dialog, *label;
544     unsigned i;
545 
546     GString* str = g_string_new("");
547 
548     g_string_printf(str, _("Authenticity of this certificate "
549                            "could not be verified.\n"
550                            "<b>Reason:</b> %s\n"
551                            "<b>This certificate belongs to:</b>\n"),
552                     acd->explanation);
553 
554     name = X509_NAME_oneline(X509_get_subject_name (cert), buf, sizeof (buf));
555     for (i = 0; i < ELEMENTS(part); i++) {
556         g_string_append(str, x509_get_part (name, part[i]));
557         g_string_append_c(str, '\n');
558     }
559 
560     g_string_append(str, _("\n<b>This certificate was issued by:</b>\n"));
561     name = X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof (buf));
562     for (i = 0; i < ELEMENTS(part); i++) {
563         g_string_append(str, x509_get_part (name, part[i]));
564         g_string_append_c(str, '\n');
565     }
566 
567     buf[0] = '\0';
568     x509_fingerprint (buf, sizeof (buf), cert);
569     valid_from  = asn1time_to_string(X509_get_notBefore(cert));
570     valid_until = asn1time_to_string(X509_get_notAfter(cert)),
571     c = g_strdup_printf(_("<b>This certificate is valid</b>\n"
572                           "from %s\n"
573                           "to %s\n"
574                           "<b>Fingerprint:</b> %s"),
575                         valid_from, valid_until,
576                         buf);
577     g_string_append(str, c); g_free(c);
578     g_free(valid_from); g_free(valid_until);
579 
580     /* This string uses markup, so we must replace "&" with "&amp;" */
581     c = str->str;
582     while ((c = strchr(c, '&'))) {
583         gssize pos;
584 
585         pos = (c - str->str) + 1;
586         g_string_insert(str, pos, "amp;");
587         c = str->str + pos;
588     }
589 
590     dialog = gtk_dialog_new_with_buttons(_("SSL/TLS certificate"), NULL,
591                                          GTK_DIALOG_MODAL,
592                                          _("_Accept Once"), 0,
593                                          _("Accept&_Save"), 1,
594                                          _("_Reject"), GTK_RESPONSE_CANCEL,
595                                          NULL);
596     gtk_window_set_wmclass(GTK_WINDOW(dialog), "tls_cert_dialog", "Balsa");
597     label = gtk_label_new(str->str);
598     g_string_free(str, TRUE);
599     gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
600     gtk_box_pack_start(GTK_BOX
601                        (gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
602                        label, TRUE, TRUE, 1);
603     gtk_widget_show(label);
604 
605     switch(gtk_dialog_run(GTK_DIALOG(dialog))) {
606     case 0: i = 1; break;
607     case 1: i = 2; break;
608     case GTK_RESPONSE_CANCEL:
609     default: i=0; break;
610     }
611     gtk_widget_destroy(dialog);
612     /* Process some events to let the window disappear:
613      * not really necessary but helps with debugging. */
614    while(gtk_events_pending())
615         gtk_main_iteration_do(FALSE);
616     printf("%s returns %d\n", __FUNCTION__, i);
617     return i;
618 }
619 
620 static int
libbalsa_ask_for_cert_acceptance(X509 * cert,const char * explanation)621 libbalsa_ask_for_cert_acceptance(X509 *cert, const char *explanation)
622 {
623     struct AskCertData acd;
624     acd.certificate = cert;
625     acd.explanation = explanation;
626     return libbalsa_ask(ask_cert_real, &acd);
627 }
628 #endif /* WITH_SSL */
629 
630 
631 static int
ask_timeout_real(void * data)632 ask_timeout_real(void *data)
633 {
634     const char *host = (const char*)data;
635     GtkWidget* dialog;
636     int i;
637 
638     dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO,
639                                     GTK_BUTTONS_YES_NO,
640                                     _("Connection to %s timed out. Abort?"),
641                                     host);
642     gtk_window_set_wmclass(GTK_WINDOW(dialog), "timeout_dialog", "Balsa");
643     switch(gtk_dialog_run(GTK_DIALOG(dialog))) {
644     case GTK_RESPONSE_YES: i = 1; break;
645     case GTK_RESPONSE_NO: i = 0; break;
646     default: printf("Unknown response. Defaulting to 'yes'.\n");
647         i = 1;
648     }
649     gtk_widget_destroy(dialog);
650     /* Process some events to let the window disappear:
651      * not really necessary but helps with debugging. */
652    while(gtk_events_pending())
653         gtk_main_iteration_do(FALSE);
654     printf("%s returns %d\n", __FUNCTION__, i);
655     return i;
656 }
657 
658 gboolean
libbalsa_abort_on_timeout(const char * host)659 libbalsa_abort_on_timeout(const char *host)
660 {  /* It appears not to be entirely thread safe... Some locks do not
661       get released as they should be. */
662     char *hostname;
663 
664     hostname = g_alloca (strlen (host) + 1);
665     strcpy (hostname, host);
666 
667     return libbalsa_ask(ask_timeout_real, hostname) != 0;
668 }
669 
670 
671 #ifdef BALSA_USE_THREADS
672 pthread_t
libbalsa_get_main_thread(void)673 libbalsa_get_main_thread(void)
674 {
675     return main_thread_id;
676 }
677 
678 gboolean
libbalsa_am_i_subthread(void)679 libbalsa_am_i_subthread(void)
680 {
681     return pthread_self() != main_thread_id;
682 }
683 #endif /* BALSA_USE_THREADS */
684 
685 #ifdef BALSA_USE_THREADS
686 #include "libbalsa_private.h"	/* for prototypes */
687 static pthread_mutex_t mailbox_mutex = PTHREAD_MUTEX_INITIALIZER;
688 static pthread_cond_t  mailbox_cond  = PTHREAD_COND_INITIALIZER;
689 
690 /* Lock/unlock a mailbox; no argument checking--we'll assume the caller
691  * took care of that.
692  */
693 #define LIBBALSA_DEBUG_THREADS FALSE
694 void
libbalsa_lock_mailbox(LibBalsaMailbox * mailbox)695 libbalsa_lock_mailbox(LibBalsaMailbox * mailbox)
696 {
697     pthread_t thread_id = pthread_self();
698 
699     pthread_mutex_lock(&mailbox_mutex);
700 
701     if (mailbox->thread_id && mailbox->thread_id != thread_id)
702         while (mailbox->lock)
703             pthread_cond_wait(&mailbox_cond, &mailbox_mutex);
704 
705     /* We'll assume that no-one would destroy a mailbox while we've been
706      * trying to lock it. If they have, we have larger problems than
707      * this reference! */
708     mailbox->lock++;
709     mailbox->thread_id = thread_id;
710 
711     pthread_mutex_unlock(&mailbox_mutex);
712 }
713 
714 void
libbalsa_unlock_mailbox(LibBalsaMailbox * mailbox)715 libbalsa_unlock_mailbox(LibBalsaMailbox * mailbox)
716 {
717     pthread_t self;
718 
719     self = pthread_self();
720 
721     pthread_mutex_lock(&mailbox_mutex);
722 
723     if (mailbox->lock == 0 || self != mailbox->thread_id) {
724 	g_warning("Not holding mailbox lock!!!");
725         pthread_mutex_unlock(&mailbox_mutex);
726 	return;
727     }
728 
729     if(--mailbox->lock == 0) {
730         mailbox->thread_id = 0;
731         pthread_cond_broadcast(&mailbox_cond);
732     }
733 
734     pthread_mutex_unlock(&mailbox_mutex);
735 }
736 
737 #endif				/* BALSA_USE_THREADS */
738 
739 /* Initialized by the front end. */
740 void (*libbalsa_progress_set_text) (LibBalsaProgress * progress,
741                                     const gchar * text, guint total);
742 void (*libbalsa_progress_set_fraction) (LibBalsaProgress * progress,
743                                         gdouble fraction);
744 void (*libbalsa_progress_set_activity) (gboolean set, const gchar * text);
745 
746 /*
747  * Face and X-Face header support.
748  */
749 gchar *
libbalsa_get_header_from_path(const gchar * header,const gchar * path,gsize * size,GError ** err)750 libbalsa_get_header_from_path(const gchar * header, const gchar * path,
751                               gsize * size, GError ** err)
752 {
753     gchar *buf, *content;
754     size_t name_len;
755     gchar *p, *q;
756 
757     if (!g_file_get_contents(path, &buf, size, err))
758         return NULL;
759 
760     content = buf;
761     name_len = strlen(header);
762     if (g_ascii_strncasecmp(content, header, name_len) == 0)
763         /* Skip header and trailing colon: */
764         content += name_len + 1;
765 
766     /* Unfold. */
767     for (p = q = content; *p; p++)
768         if (*p != '\r' && *p != '\n')
769             *q++ = *p;
770     *q = '\0';
771 
772     content = g_strdup(content);
773     g_free(buf);
774 
775     return content;
776 }
777 
778 GtkWidget *
libbalsa_get_image_from_face_header(const gchar * content,GError ** err)779 libbalsa_get_image_from_face_header(const gchar * content, GError ** err)
780 {
781     GMimeStream *stream;
782     GMimeStream *stream_filter;
783     GMimeFilter *filter;
784     GByteArray *array;
785     GtkWidget *image = NULL;
786 
787     stream = g_mime_stream_mem_new();
788     stream_filter = g_mime_stream_filter_new(stream);
789 
790     filter = g_mime_filter_basic_new(GMIME_CONTENT_ENCODING_BASE64, FALSE);
791     g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter), filter);
792     g_object_unref(filter);
793 
794     g_mime_stream_write_string(stream_filter, content);
795     g_object_unref(stream_filter);
796 
797     array = GMIME_STREAM_MEM(stream)->buffer;
798     if (array->len == 0)
799         g_set_error(err, LIBBALSA_IMAGE_ERROR,
800                     LIBBALSA_IMAGE_ERROR_NO_DATA, _("No image data"));
801     else {
802         GdkPixbufLoader *loader =
803             gdk_pixbuf_loader_new_with_type("png", NULL);
804 
805         gdk_pixbuf_loader_write(loader, array->data, array->len, err);
806         gdk_pixbuf_loader_close(loader, *err ? NULL : err);
807 
808         if (!*err)
809             image = gtk_image_new_from_pixbuf(gdk_pixbuf_loader_get_pixbuf
810                                               (loader));
811         g_object_unref(loader);
812     }
813     g_object_unref(stream);
814 
815     return image;
816 }
817 
818 #if HAVE_COMPFACE
819 GtkWidget *
libbalsa_get_image_from_x_face_header(const gchar * content,GError ** err)820 libbalsa_get_image_from_x_face_header(const gchar * content, GError ** err)
821 {
822     gchar buf[2048];
823     GdkPixbuf *pixbuf;
824     guchar *pixels;
825     gint lines;
826     const gchar *p;
827     GtkWidget *image = NULL;
828 
829     strncpy(buf, content, sizeof buf - 1);
830 
831     switch (uncompface(buf)) {
832     case -1:
833         g_set_error(err, LIBBALSA_IMAGE_ERROR, LIBBALSA_IMAGE_ERROR_FORMAT,
834                     _("Invalid input format"));
835         return image;
836     case -2:
837         g_set_error(err, LIBBALSA_IMAGE_ERROR, LIBBALSA_IMAGE_ERROR_BUFFER,
838                     _("Internal buffer overrun"));
839         return image;
840     }
841 
842     pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 48, 48);
843     pixels = gdk_pixbuf_get_pixels(pixbuf);
844 
845     p = buf;
846     for (lines = 48; lines > 0; --lines) {
847         guint x[3];
848         gint j, k;
849         guchar *q;
850 
851         if (sscanf(p, "%8x,%8x,%8x,", &x[0], &x[1], &x[2]) != 3) {
852             g_set_error(err, LIBBALSA_IMAGE_ERROR,
853                         LIBBALSA_IMAGE_ERROR_BAD_DATA,
854                         /* Translators: please do not translate Face. */
855                         _("Bad X-Face data"));
856             g_object_unref(pixbuf);
857             return image;
858         }
859         for (j = 0, q = pixels; j < 3; j++)
860             for (k = 15; k >= 0; --k){
861                 guchar c = x[j] & (1 << k) ? 0x00 : 0xff;
862                 *q++ = c;       /* red   */
863                 *q++ = c;       /* green */
864                 *q++ = c;       /* blue  */
865             }
866         p = strchr(p, '\n') + 1;
867         pixels += gdk_pixbuf_get_rowstride(pixbuf);
868     }
869 
870     image = gtk_image_new_from_pixbuf(pixbuf);
871     g_object_unref(pixbuf);
872 
873     return image;
874 }
875 #endif                          /* HAVE_COMPFACE */
876 
877 #if HAVE_GTKSOURCEVIEW
878 GtkWidget *
libbalsa_source_view_new(gboolean highlight_phrases)879 libbalsa_source_view_new(gboolean highlight_phrases)
880 {
881     GtkSourceBuffer *sbuffer;
882     GtkWidget *sview;
883 
884 
885     static GtkSourceLanguageManager * lm = NULL;
886     static GtkSourceStyleScheme * scheme = NULL;
887     static GtkSourceLanguage * src_lang = NULL;
888 
889     /* initialise the source language manager if necessary */
890     if (!lm) {
891 	const gchar * const * lm_dpaths;
892 
893 	if ((lm = gtk_source_language_manager_new()) &&
894 	    (lm_dpaths = gtk_source_language_manager_get_search_path(lm))) {
895 	    gchar ** lm_rpaths;
896 	    gint n;
897 
898 	    /* add the balsa share path to the language manager's paths - we
899 	     * cannot simply replace it as it still wants to see the
900 	     * RelaxNG schema... */
901 	    for (n = 0; lm_dpaths[n]; n++);
902 	    lm_rpaths = g_new0(gchar *, n + 2);
903 	    for (n = 0; lm_dpaths[n]; n++)
904 		lm_rpaths[n] = g_strdup(lm_dpaths[n]);
905 	    lm_rpaths[n] = g_strdup(BALSA_DATA_PREFIX "/gtksourceview-2.0");
906 	    gtk_source_language_manager_set_search_path(lm, lm_rpaths);
907 	    g_strfreev(lm_rpaths);
908 
909 	    /* try to load the language */
910 	    if ((src_lang =
911 		 gtk_source_language_manager_get_language(lm, "balsa"))) {
912 		GtkSourceStyleSchemeManager *smgr =
913 		    gtk_source_style_scheme_manager_new();
914 		gchar * sm_paths[] = {
915 		    BALSA_DATA_PREFIX "/gtksourceview-2.0",
916 		    NULL };
917 
918 		/* try to load the colouring scheme */
919 		gtk_source_style_scheme_manager_set_search_path(smgr, sm_paths);
920 		scheme = gtk_source_style_scheme_manager_get_scheme(smgr, "balsa-mail");
921 	    }
922 	}
923     }
924 
925     /* create a new buffer and set the language and scheme */
926     sbuffer = gtk_source_buffer_new(NULL);
927     if (src_lang)
928 	gtk_source_buffer_set_language(sbuffer, src_lang);
929     if (scheme)
930 	gtk_source_buffer_set_style_scheme(sbuffer, scheme);
931     gtk_source_buffer_set_highlight_syntax(sbuffer, TRUE);
932     gtk_source_buffer_set_highlight_matching_brackets(sbuffer, FALSE);
933 
934     /* create & return the source view */
935     sview = gtk_source_view_new_with_buffer(sbuffer);
936     g_object_unref(sbuffer);
937 
938     return sview;
939 }
940 #endif  /* HAVE_GTKSOURCEVIEW */
941 
942 /*
943  * Error domains for GError:
944  */
945 
946 GQuark
libbalsa_scanner_error_quark(void)947 libbalsa_scanner_error_quark(void)
948 {
949     static GQuark quark = 0;
950     if (quark == 0)
951         quark = g_quark_from_static_string("libbalsa-scanner-error-quark");
952     return quark;
953 }
954 
955 GQuark
libbalsa_mailbox_error_quark(void)956 libbalsa_mailbox_error_quark(void)
957 {
958     static GQuark quark = 0;
959     if (quark == 0)
960         quark = g_quark_from_static_string("libbalsa-mailbox-error-quark");
961     return quark;
962 }
963 
964 GQuark
libbalsa_image_error_quark(void)965 libbalsa_image_error_quark(void)
966 {
967     static GQuark quark = 0;
968     if (quark == 0)
969         quark = g_quark_from_static_string("libbalsa-image-error-quark");
970     return quark;
971 }
972