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 "&" */
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