diff options
author | Not Zed <NotZed@Ximian.com> | 2004-09-23 04:12:30 +0000 |
---|---|---|
committer | Michael Zucci <zucchi@src.gnome.org> | 2004-09-23 04:12:30 +0000 |
commit | c607f54b43b3c30f000030155a56a24f1e895259 (patch) | |
tree | 76a149a0865beff8add29f8e342ec548ac7917ea | |
parent | 078dc566198a67680be5cfaf4b205a7e4fc89bb3 (diff) | |
download | evolution-data-server-EVOLUTION_2_0_1.tar.gz |
** See bug #47821.EVOLUTION_2_0_1
2004-09-13 Not Zed <NotZed@Ximian.com>
** See bug #47821.
* camel-service.c: removed the old hostent based hostname interfaces.
* camel-sasl-kerberos4.c (krb4_challenge): new hostname interfaces.
* camel-sasl-gssapi.c (gssapi_challenge): new hostname interfaces.
* camel-sasl-digest-md5.c (digest_md5_challenge): use new hostname
interfaces.
(generate_response): just take hostname directly, not hostent.
* camel-mime-utils.c (camel_header_msgid_generate): use new
hostname interfaces.
* providers/smtp/camel-smtp-transport.c (connect_to_server): fixed
to use new addrinfo apis.
* providers/pop3/camel-pop3-store.c (connect_to_server): fixed to
use new addrinfo apis.
* camel-tcp-stream-ssl.c (stream_connect): try all addresses
supplied.
* camel-tcp-stream.c (camel_tcp_stream_get_remote_address)
(camel_tcp_stream_get_local_address): return a sockaddr now, and
also the address length. Fixed all implementations and callers.
(camel_tcp_stream_connect): use addrinfo rather than hostent for
host AND port info. Fixed all implementations and callers.
-rw-r--r-- | camel/ChangeLog | 32 | ||||
-rw-r--r-- | camel/camel-http-stream.c | 29 | ||||
-rw-r--r-- | camel/camel-mime-utils.c | 4314 | ||||
-rw-r--r-- | camel/camel-sasl-digest-md5.c | 22 | ||||
-rw-r--r-- | camel/camel-sasl-gssapi.c | 346 | ||||
-rw-r--r-- | camel/camel-sasl-kerberos4.c | 14 | ||||
-rw-r--r-- | camel/camel-service.c | 582 | ||||
-rw-r--r-- | camel/camel-service.h | 61 | ||||
-rw-r--r-- | camel/camel-tcp-stream-raw.c | 147 | ||||
-rw-r--r-- | camel/camel-tcp-stream-ssl.c | 160 | ||||
-rw-r--r-- | camel/camel-tcp-stream.c | 89 | ||||
-rw-r--r-- | camel/camel-tcp-stream.h | 29 | ||||
-rw-r--r-- | camel/providers/imap/camel-imap-store.c | 3258 | ||||
-rw-r--r-- | camel/providers/imapp/camel-imapp-store.c | 36 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-store.c | 34 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-store.c | 51 | ||||
-rw-r--r-- | camel/providers/smtp/camel-smtp-transport.c | 94 | ||||
-rw-r--r-- | camel/providers/smtp/camel-smtp-transport.h | 13 |
18 files changed, 8682 insertions, 629 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog index df85f4a34..668fea1d9 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -1,3 +1,35 @@ +2004-09-13 Not Zed <NotZed@Ximian.com> + + ** See bug #47821. + + * camel-service.c: removed the old hostent based hostname interfaces. + + * camel-sasl-kerberos4.c (krb4_challenge): new hostname interfaces. + + * camel-sasl-gssapi.c (gssapi_challenge): new hostname interfaces. + + * camel-sasl-digest-md5.c (digest_md5_challenge): use new hostname + interfaces. + (generate_response): just take hostname directly, not hostent. + + * camel-mime-utils.c (camel_header_msgid_generate): use new + hostname interfaces. + + * providers/smtp/camel-smtp-transport.c (connect_to_server): fixed + to use new addrinfo apis. + + * providers/pop3/camel-pop3-store.c (connect_to_server): fixed to + use new addrinfo apis. + + * camel-tcp-stream-ssl.c (stream_connect): try all addresses + supplied. + + * camel-tcp-stream.c (camel_tcp_stream_get_remote_address) + (camel_tcp_stream_get_local_address): return a sockaddr now, and + also the address length. Fixed all implementations and callers. + (camel_tcp_stream_connect): use addrinfo rather than hostent for + host AND port info. Fixed all implementations and callers. + 2004-09-22 Not Zed <NotZed@Ximian.com> * camel-folder-summary.c (camel_folder_summary_decode_token): diff --git a/camel/camel-http-stream.c b/camel/camel-http-stream.c index 5937c23f7..53c0241a2 100644 --- a/camel/camel-http-stream.c +++ b/camel/camel-http-stream.c @@ -172,9 +172,12 @@ static CamelStream * http_connect (CamelHttpStream *http, CamelURL *url) { CamelStream *stream = NULL; - struct hostent *host; + struct addrinfo *ai, hints = { 0 }; int errsave; - + char *serv; + + d(printf("connecting to http stream @ '%s'\n", url->host)); + if (!strcasecmp (url->protocol, "https")) { #ifdef HAVE_SSL stream = camel_tcp_stream_ssl_new (http->session, url->host, SSL_FLAGS); @@ -187,24 +190,30 @@ http_connect (CamelHttpStream *http, CamelURL *url) errno = EINVAL; return NULL; } - - printf("connecting to http stream @ '%s'\n", url->host); - host = camel_gethostbyname (url->host, NULL); - if (!host) { - errno = EHOSTUNREACH; + if (url->port) { + serv = g_alloca(16); + sprintf(serv, "%d", url->port); + } else { + serv = url->protocol; + } + hints.ai_socktype = SOCK_STREAM; + + ai = camel_getaddrinfo(url->host, serv, &hints, NULL); + if (ai == NULL) { + camel_object_unref (stream); return NULL; } - if (camel_tcp_stream_connect (CAMEL_TCP_STREAM (stream), host, url->port ? url->port : 80) == -1) { + if (camel_tcp_stream_connect (CAMEL_TCP_STREAM (stream), ai) == -1) { errsave = errno; camel_object_unref (stream); - camel_free_host (host); + camel_freeaddrinfo(ai); errno = errsave; return NULL; } - camel_free_host (host); + camel_freeaddrinfo(ai); http->raw = stream; http->read = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_READ); diff --git a/camel/camel-mime-utils.c b/camel/camel-mime-utils.c new file mode 100644 index 000000000..3c1d4baf1 --- /dev/null +++ b/camel/camel-mime-utils.c @@ -0,0 +1,4314 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2000-2003 Ximian Inc. + * + * Authors: Michael Zucchi <notzed@ximian.com> + * Jeffrey Stedfast <fejj@ximian.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* dont touch this file without my permission - Michael */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/param.h> /* for MAXHOSTNAMELEN */ +#include <sys/stat.h> +#include <pthread.h> +#include <unistd.h> +#include <regex.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <time.h> + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 1024 +#endif + +#include <glib.h> +#include <gal/util/e-iconv.h> +#include <e-util/e-time-utils.h> + +#include "camel-mime-utils.h" +#include "camel-charset-map.h" +#include "camel-service.h" /* for camel_gethostbyname() */ +#include "camel-utf8.h" + +#ifndef CLEAN_DATE +#include "broken-date-parser.h" +#endif + +#if 0 +int strdup_count = 0; +int malloc_count = 0; +int free_count = 0; + +#define g_strdup(x) (strdup_count++, g_strdup(x)) +#define g_malloc(x) (malloc_count++, g_malloc(x)) +#define g_free(x) (free_count++, g_free(x)) +#endif + +/* for all non-essential warnings ... */ +#define w(x) + +#define d(x) +#define d2(x) + +#define CAMEL_UUENCODE_CHAR(c) ((c) ? (c) + ' ' : '`') +#define CAMEL_UUDECODE_CHAR(c) (((c) - ' ') & 077) + +static char *base64_alphabet = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static unsigned char tohex[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +unsigned short camel_mime_special_table[256]; +static unsigned char camel_mime_base64_rank[256]; + +/* Used by table initialisation code for special characters */ +#define CHARS_LWSP " \t\n\r" +#define CHARS_TSPECIAL "()<>@,;:\\\"/[]?=" +#define CHARS_SPECIAL "()<>@,;:\\\".[]" +#define CHARS_CSPECIAL "()\\\r" /* not in comments */ +#define CHARS_DSPECIAL "[]\\\r \t" /* not in domains */ +#define CHARS_ESPECIAL "()<>@,;:\"/[]?.=_" /* list of characters that must be encoded. + encoded word in text specials: rfc 2047 5(1)*/ +#define CHARS_PSPECIAL "!*+-/" /* list of additional characters that can be left unencoded. + encoded word in phrase specials: rfc 2047 5(3) */ +#define CHARS_ATTRCHAR "*\'% " /* extra non-included attribute-chars */ + +static void +header_remove_bits(unsigned short bit, unsigned char *vals) +{ + int i; + + for (i=0;vals[i];i++) + camel_mime_special_table[vals[i]] &= ~ bit; +} + +static void +header_init_bits(unsigned short bit, unsigned short bitcopy, int remove, unsigned char *vals) +{ + int i; + int len = strlen(vals); + + if (!remove) { + for (i=0;i<len;i++) { + camel_mime_special_table[vals[i]] |= bit; + } + if (bitcopy) { + for (i=0;i<256;i++) { + if (camel_mime_special_table[i] & bitcopy) + camel_mime_special_table[i] |= bit; + } + } + } else { + for (i=0;i<256;i++) + camel_mime_special_table[i] |= bit; + for (i=0;i<len;i++) { + camel_mime_special_table[vals[i]] &= ~bit; + } + if (bitcopy) { + for (i=0;i<256;i++) { + if (camel_mime_special_table[i] & bitcopy) + camel_mime_special_table[i] &= ~bit; + } + } + } +} + +static void +header_decode_init(void) +{ + int i; + + for (i=0;i<256;i++) { + camel_mime_special_table[i] = 0; + if (i<32 || i==127) + camel_mime_special_table[i] |= CAMEL_MIME_IS_CTRL; + else if (i < 127) + camel_mime_special_table[i] |= CAMEL_MIME_IS_ATTRCHAR; + if ((i>=32 && i<=60) || (i>=62 && i<=126) || i==9) + camel_mime_special_table[i] |= (CAMEL_MIME_IS_QPSAFE|CAMEL_MIME_IS_ESAFE); + if ((i>='0' && i<='9') || (i>='a' && i<='z') || (i>='A' && i<= 'Z')) + camel_mime_special_table[i] |= CAMEL_MIME_IS_PSAFE; + } + camel_mime_special_table[' '] |= CAMEL_MIME_IS_SPACE; + header_init_bits(CAMEL_MIME_IS_LWSP, 0, 0, CHARS_LWSP); + header_init_bits(CAMEL_MIME_IS_TSPECIAL, CAMEL_MIME_IS_CTRL, 0, CHARS_TSPECIAL); + header_init_bits(CAMEL_MIME_IS_SPECIAL, 0, 0, CHARS_SPECIAL); + header_init_bits(CAMEL_MIME_IS_DSPECIAL, 0, FALSE, CHARS_DSPECIAL); + header_remove_bits(CAMEL_MIME_IS_ESAFE, CHARS_ESPECIAL); + header_remove_bits(CAMEL_MIME_IS_ATTRCHAR, CHARS_TSPECIAL CHARS_ATTRCHAR); + header_init_bits(CAMEL_MIME_IS_PSAFE, 0, 0, CHARS_PSPECIAL); +} + +static void +base64_init(void) +{ + int i; + + memset(camel_mime_base64_rank, 0xff, sizeof(camel_mime_base64_rank)); + for (i=0;i<64;i++) { + camel_mime_base64_rank[(unsigned int)base64_alphabet[i]] = i; + } + camel_mime_base64_rank['='] = 0; +} + +/* call this when finished encoding everything, to + flush off the last little bit */ +size_t +camel_base64_encode_close(unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save) +{ + int c1, c2; + unsigned char *outptr = out; + + if (inlen>0) + outptr += camel_base64_encode_step(in, inlen, break_lines, outptr, state, save); + + c1 = ((unsigned char *)save)[1]; + c2 = ((unsigned char *)save)[2]; + + d(printf("mode = %d\nc1 = %c\nc2 = %c\n", + (int)((char *)save)[0], + (int)((char *)save)[1], + (int)((char *)save)[2])); + + switch (((char *)save)[0]) { + case 2: + outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ]; + g_assert(outptr[2] != 0); + goto skip; + case 1: + outptr[2] = '='; + skip: + outptr[0] = base64_alphabet[ c1 >> 2 ]; + outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )]; + outptr[3] = '='; + outptr += 4; + break; + } + if (break_lines) + *outptr++ = '\n'; + + *save = 0; + *state = 0; + + return outptr-out; +} + +/* + performs an 'encode step', only encodes blocks of 3 characters to the + output at a time, saves left-over state in state and save (initialise to + 0 on first invocation). +*/ +size_t +camel_base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save) +{ + register unsigned char *inptr, *outptr; + + if (len<=0) + return 0; + + inptr = in; + outptr = out; + + d(printf("we have %d chars, and %d saved chars\n", len, ((char *)save)[0])); + + if (len + ((char *)save)[0] > 2) { + unsigned char *inend = in+len-2; + register int c1, c2, c3; + register int already; + + already = *state; + + switch (((char *)save)[0]) { + case 1: c1 = ((unsigned char *)save)[1]; goto skip1; + case 2: c1 = ((unsigned char *)save)[1]; + c2 = ((unsigned char *)save)[2]; goto skip2; + } + + /* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */ + while (inptr < inend) { + c1 = *inptr++; + skip1: + c2 = *inptr++; + skip2: + c3 = *inptr++; + *outptr++ = base64_alphabet[ c1 >> 2 ]; + *outptr++ = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ]; + *outptr++ = base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ]; + *outptr++ = base64_alphabet[ c3 & 0x3f ]; + /* this is a bit ugly ... */ + if (break_lines && (++already)>=19) { + *outptr++='\n'; + already = 0; + } + } + + ((char *)save)[0] = 0; + len = 2-(inptr-inend); + *state = already; + } + + d(printf("state = %d, len = %d\n", + (int)((char *)save)[0], + len)); + + if (len>0) { + register char *saveout; + + /* points to the slot for the next char to save */ + saveout = & (((char *)save)[1]) + ((char *)save)[0]; + + /* len can only be 0 1 or 2 */ + switch(len) { + case 2: *saveout++ = *inptr++; + case 1: *saveout++ = *inptr++; + } + ((char *)save)[0]+=len; + } + + d(printf("mode = %d\nc1 = %c\nc2 = %c\n", + (int)((char *)save)[0], + (int)((char *)save)[1], + (int)((char *)save)[2])); + + return outptr-out; +} + + +/** + * camel_base64_decode_step: decode a chunk of base64 encoded data + * @in: input stream + * @len: max length of data to decode + * @out: output stream + * @state: holds the number of bits that are stored in @save + * @save: leftover bits that have not yet been decoded + * + * Decodes a chunk of base64 encoded data + **/ +size_t +camel_base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save) +{ + register unsigned char *inptr, *outptr; + unsigned char *inend, c; + register unsigned int v; + int i; + + inend = in+len; + outptr = out; + + /* convert 4 base64 bytes to 3 normal bytes */ + v=*save; + i=*state; + inptr = in; + while (inptr<inend) { + c = camel_mime_base64_rank[*inptr++]; + if (c != 0xff) { + v = (v<<6) | c; + i++; + if (i==4) { + *outptr++ = v>>16; + *outptr++ = v>>8; + *outptr++ = v; + i=0; + } + } + } + + *save = v; + *state = i; + + /* quick scan back for '=' on the end somewhere */ + /* fortunately we can drop 1 output char for each trailing = (upto 2) */ + i=2; + while (inptr>in && i) { + inptr--; + if (camel_mime_base64_rank[*inptr] != 0xff) { + if (*inptr == '=' && outptr>out) + outptr--; + i--; + } + } + + /* if i!= 0 then there is a truncation error! */ + return outptr-out; +} + +char * +camel_base64_encode_simple (const char *data, size_t len) +{ + unsigned char *out; + int state = 0, outlen; + unsigned int save = 0; + + out = g_malloc (len * 4 / 3 + 5); + outlen = camel_base64_encode_close ((unsigned char *)data, len, FALSE, + out, &state, &save); + out[outlen] = '\0'; + return (char *)out; +} + +size_t +camel_base64_decode_simple (char *data, size_t len) +{ + int state = 0; + unsigned int save = 0; + + return camel_base64_decode_step ((unsigned char *)data, len, + (unsigned char *)data, &state, &save); +} + +/** + * camel_uuencode_close: uuencode a chunk of data + * @in: input stream + * @len: input stream length + * @out: output stream + * @uubuf: temporary buffer of 60 bytes + * @state: holds the number of bits that are stored in @save + * @save: leftover bits that have not yet been encoded + * + * Returns the number of bytes encoded. Call this when finished + * encoding data with camel_uuencode_step to flush off the last little + * bit. + **/ +size_t +camel_uuencode_close (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save) +{ + register unsigned char *outptr, *bufptr; + register guint32 saved; + int uulen, uufill, i; + + outptr = out; + + if (len > 0) + outptr += camel_uuencode_step (in, len, out, uubuf, state, save); + + uufill = 0; + + saved = *save; + i = *state & 0xff; + uulen = (*state >> 8) & 0xff; + + bufptr = uubuf + ((uulen / 3) * 4); + + if (i > 0) { + while (i < 3) { + saved <<= 8 | 0; + uufill++; + i++; + } + + if (i == 3) { + /* convert 3 normal bytes into 4 uuencoded bytes */ + unsigned char b0, b1, b2; + + b0 = saved >> 16; + b1 = saved >> 8 & 0xff; + b2 = saved & 0xff; + + *bufptr++ = CAMEL_UUENCODE_CHAR ((b0 >> 2) & 0x3f); + *bufptr++ = CAMEL_UUENCODE_CHAR (((b0 << 4) | ((b1 >> 4) & 0xf)) & 0x3f); + *bufptr++ = CAMEL_UUENCODE_CHAR (((b1 << 2) | ((b2 >> 6) & 0x3)) & 0x3f); + *bufptr++ = CAMEL_UUENCODE_CHAR (b2 & 0x3f); + + i = 0; + saved = 0; + uulen += 3; + } + } + + if (uulen > 0) { + int cplen = ((uulen / 3) * 4); + + *outptr++ = CAMEL_UUENCODE_CHAR ((uulen - uufill) & 0xff); + memcpy (outptr, uubuf, cplen); + outptr += cplen; + *outptr++ = '\n'; + uulen = 0; + } + + *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff); + *outptr++ = '\n'; + + *save = 0; + *state = 0; + + return outptr - out; +} + + +/** + * camel_uuencode_step: uuencode a chunk of data + * @in: input stream + * @len: input stream length + * @out: output stream + * @uubuf: temporary buffer of 60 bytes + * @state: holds the number of bits that are stored in @save + * @save: leftover bits that have not yet been encoded + * + * Returns the number of bytes encoded. Performs an 'encode step', + * only encodes blocks of 45 characters to the output at a time, saves + * left-over state in @uubuf, @state and @save (initialize to 0 on first + * invocation). + **/ +size_t +camel_uuencode_step (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save) +{ + register unsigned char *inptr, *outptr, *bufptr; + unsigned char *inend; + register guint32 saved; + int uulen, i; + + saved = *save; + i = *state & 0xff; + uulen = (*state >> 8) & 0xff; + + inptr = in; + inend = in + len; + + outptr = out; + + bufptr = uubuf + ((uulen / 3) * 4); + + while (inptr < inend) { + while (uulen < 45 && inptr < inend) { + while (i < 3 && inptr < inend) { + saved = (saved << 8) | *inptr++; + i++; + } + + if (i == 3) { + /* convert 3 normal bytes into 4 uuencoded bytes */ + unsigned char b0, b1, b2; + + b0 = saved >> 16; + b1 = saved >> 8 & 0xff; + b2 = saved & 0xff; + + *bufptr++ = CAMEL_UUENCODE_CHAR ((b0 >> 2) & 0x3f); + *bufptr++ = CAMEL_UUENCODE_CHAR (((b0 << 4) | ((b1 >> 4) & 0xf)) & 0x3f); + *bufptr++ = CAMEL_UUENCODE_CHAR (((b1 << 2) | ((b2 >> 6) & 0x3)) & 0x3f); + *bufptr++ = CAMEL_UUENCODE_CHAR (b2 & 0x3f); + + i = 0; + saved = 0; + uulen += 3; + } + } + + if (uulen >= 45) { + *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff); + memcpy (outptr, uubuf, ((uulen / 3) * 4)); + outptr += ((uulen / 3) * 4); + *outptr++ = '\n'; + uulen = 0; + bufptr = uubuf; + } + } + + *save = saved; + *state = ((uulen & 0xff) << 8) | (i & 0xff); + + return outptr - out; +} + + +/** + * camel_uudecode_step: uudecode a chunk of data + * @in: input stream + * @inlen: max length of data to decode ( normally strlen(in) ??) + * @out: output stream + * @state: holds the number of bits that are stored in @save + * @save: leftover bits that have not yet been decoded + * + * Returns the number of bytes decoded. Performs a 'decode step' on + * a chunk of uuencoded data. Assumes the "begin <mode> <file name>" + * line has been stripped off. + **/ +size_t +camel_uudecode_step (unsigned char *in, size_t len, unsigned char *out, int *state, guint32 *save) +{ + register unsigned char *inptr, *outptr; + unsigned char *inend, ch; + register guint32 saved; + gboolean last_was_eoln; + int uulen, i; + + if (*state & CAMEL_UUDECODE_STATE_END) + return 0; + + saved = *save; + i = *state & 0xff; + uulen = (*state >> 8) & 0xff; + if (uulen == 0) + last_was_eoln = TRUE; + else + last_was_eoln = FALSE; + + inend = in + len; + outptr = out; + + inptr = in; + while (inptr < inend) { + if (*inptr == '\n' || last_was_eoln) { + if (last_was_eoln && *inptr != '\n') { + uulen = CAMEL_UUDECODE_CHAR (*inptr); + last_was_eoln = FALSE; + if (uulen == 0) { + *state |= CAMEL_UUDECODE_STATE_END; + break; + } + } else { + last_was_eoln = TRUE; + } + + inptr++; + continue; + } + + ch = *inptr++; + + if (uulen > 0) { + /* save the byte */ + saved = (saved << 8) | ch; + i++; + if (i == 4) { + /* convert 4 uuencoded bytes to 3 normal bytes */ + unsigned char b0, b1, b2, b3; + + b0 = saved >> 24; + b1 = saved >> 16 & 0xff; + b2 = saved >> 8 & 0xff; + b3 = saved & 0xff; + + if (uulen >= 3) { + *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4; + *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2; + *outptr++ = CAMEL_UUDECODE_CHAR (b2) << 6 | CAMEL_UUDECODE_CHAR (b3); + } else { + if (uulen >= 1) { + *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4; + } + if (uulen >= 2) { + *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2; + } + } + + i = 0; + saved = 0; + uulen -= 3; + } + } else { + break; + } + } + + *save = saved; + *state = (*state & CAMEL_UUDECODE_STATE_MASK) | ((uulen & 0xff) << 8) | (i & 0xff); + + return outptr - out; +} + + +/* complete qp encoding */ +size_t +camel_quoted_decode_close(unsigned char *in, size_t len, unsigned char *out, int *state, int *save) +{ + register unsigned char *outptr = out; + int last; + + if (len>0) + outptr += camel_quoted_encode_step(in, len, outptr, state, save); + + last = *state; + if (last != -1) { + /* space/tab must be encoded if it's the last character on + the line */ + if (camel_mime_is_qpsafe(last) && last!=' ' && last!=9) { + *outptr++ = last; + } else { + *outptr++ = '='; + *outptr++ = tohex[(last>>4) & 0xf]; + *outptr++ = tohex[last & 0xf]; + } + } + + *save = 0; + *state = -1; + + return outptr-out; +} + +/* perform qp encoding, initialise state to -1 and save to 0 on first invocation */ +size_t +camel_quoted_encode_step (unsigned char *in, size_t len, unsigned char *out, int *statep, int *save) +{ + register guchar *inptr, *outptr, *inend; + unsigned char c; + register int sofar = *save; /* keeps track of how many chars on a line */ + register int last = *statep; /* keeps track if last char to end was a space cr etc */ + + inptr = in; + inend = in + len; + outptr = out; + while (inptr < inend) { + c = *inptr++; + if (c == '\r') { + if (last != -1) { + *outptr++ = '='; + *outptr++ = tohex[(last >> 4) & 0xf]; + *outptr++ = tohex[last & 0xf]; + sofar += 3; + } + last = c; + } else if (c == '\n') { + if (last != -1 && last != '\r') { + *outptr++ = '='; + *outptr++ = tohex[(last >> 4) & 0xf]; + *outptr++ = tohex[last & 0xf]; + } + *outptr++ = '\n'; + sofar = 0; + last = -1; + } else { + if (last != -1) { + if (camel_mime_is_qpsafe(last)) { + *outptr++ = last; + sofar++; + } else { + *outptr++ = '='; + *outptr++ = tohex[(last >> 4) & 0xf]; + *outptr++ = tohex[last & 0xf]; + sofar += 3; + } + } + + if (camel_mime_is_qpsafe(c)) { + if (sofar > 74) { + *outptr++ = '='; + *outptr++ = '\n'; + sofar = 0; + } + + /* delay output of space char */ + if (c==' ' || c=='\t') { + last = c; + } else { + *outptr++ = c; + sofar++; + last = -1; + } + } else { + if (sofar > 72) { + *outptr++ = '='; + *outptr++ = '\n'; + sofar = 3; + } else + sofar += 3; + + *outptr++ = '='; + *outptr++ = tohex[(c >> 4) & 0xf]; + *outptr++ = tohex[c & 0xf]; + last = -1; + } + } + } + *save = sofar; + *statep = last; + + return (outptr - out); +} + +/* + FIXME: this does not strip trailing spaces from lines (as it should, rfc 2045, section 6.7) + Should it also canonicalise the end of line to CR LF?? + + Note: Trailing rubbish (at the end of input), like = or =x or =\r will be lost. +*/ + +size_t +camel_quoted_decode_step(unsigned char *in, size_t len, unsigned char *out, int *savestate, int *saveme) +{ + register unsigned char *inptr, *outptr; + unsigned char *inend, c; + int state, save; + + inend = in+len; + outptr = out; + + d(printf("quoted-printable, decoding text '%.*s'\n", len, in)); + + state = *savestate; + save = *saveme; + inptr = in; + while (inptr<inend) { + switch (state) { + case 0: + while (inptr<inend) { + c = *inptr++; + if (c=='=') { + state = 1; + break; + } +#ifdef CANONICALISE_EOL + /*else if (c=='\r') { + state = 3; + } else if (c=='\n') { + *outptr++ = '\r'; + *outptr++ = c; + } */ +#endif + else { + *outptr++ = c; + } + } + break; + case 1: + c = *inptr++; + if (c=='\n') { + /* soft break ... unix end of line */ + state = 0; + } else { + save = c; + state = 2; + } + break; + case 2: + c = *inptr++; + if (isxdigit(c) && isxdigit(save)) { + c = toupper(c); + save = toupper(save); + *outptr++ = (((save>='A'?save-'A'+10:save-'0')&0x0f) << 4) + | ((c>='A'?c-'A'+10:c-'0')&0x0f); + } else if (c=='\n' && save == '\r') { + /* soft break ... canonical end of line */ + } else { + /* just output the data */ + *outptr++ = '='; + *outptr++ = save; + *outptr++ = c; + } + state = 0; + break; +#ifdef CANONICALISE_EOL + case 3: + /* convert \r -> to \r\n, leaves \r\n alone */ + c = *inptr++; + if (c=='\n') { + *outptr++ = '\r'; + *outptr++ = c; + } else { + *outptr++ = '\r'; + *outptr++ = '\n'; + *outptr++ = c; + } + state = 0; + break; +#endif + } + } + + *savestate = state; + *saveme = save; + + return outptr-out; +} + +/* + this is for the "Q" encoding of international words, + which is slightly different than plain quoted-printable (mainly by allowing 0x20 <> _) +*/ +static size_t +quoted_decode(const unsigned char *in, size_t len, unsigned char *out) +{ + register const unsigned char *inptr; + register unsigned char *outptr; + unsigned const char *inend; + unsigned char c, c1; + int ret = 0; + + inend = in+len; + outptr = out; + + d(printf("decoding text '%.*s'\n", len, in)); + + inptr = in; + while (inptr<inend) { + c = *inptr++; + if (c=='=') { + /* silently ignore truncated data? */ + if (inend-in>=2) { + c = toupper(*inptr++); + c1 = toupper(*inptr++); + *outptr++ = (((c>='A'?c-'A'+10:c-'0')&0x0f) << 4) + | ((c1>='A'?c1-'A'+10:c1-'0')&0x0f); + } else { + ret = -1; + break; + } + } else if (c=='_') { + *outptr++ = 0x20; + } else if (c==' ' || c==0x09) { + /* FIXME: this is an error! ignore for now ... */ + ret = -1; + break; + } else { + *outptr++ = c; + } + } + if (ret==0) { + return outptr-out; + } + return 0; +} + +/* rfc2047 version of quoted-printable */ +/* safemask is the mask to apply to the camel_mime_special_table to determine what + characters can safely be included without encoding */ +static size_t +quoted_encode (const unsigned char *in, size_t len, unsigned char *out, unsigned short safemask) +{ + register const unsigned char *inptr, *inend; + unsigned char *outptr; + unsigned char c; + + inptr = in; + inend = in + len; + outptr = out; + while (inptr < inend) { + c = *inptr++; + if (c==' ') { + *outptr++ = '_'; + } else if (camel_mime_special_table[c] & safemask) { + *outptr++ = c; + } else { + *outptr++ = '='; + *outptr++ = tohex[(c >> 4) & 0xf]; + *outptr++ = tohex[c & 0xf]; + } + } + + d(printf("encoding '%.*s' = '%.*s'\n", len, in, outptr-out, out)); + + return (outptr - out); +} + + +static void +header_decode_lwsp(const char **in) +{ + const char *inptr = *in; + char c; + + d2(printf("is ws: '%s'\n", *in)); + + while (camel_mime_is_lwsp(*inptr) || (*inptr =='(' && *inptr != '\0')) { + while (camel_mime_is_lwsp(*inptr) && inptr != '\0') { + d2(printf("(%c)", *inptr)); + inptr++; + } + d2(printf("\n")); + + /* check for comments */ + if (*inptr == '(') { + int depth = 1; + inptr++; + while (depth && (c=*inptr) && *inptr != '\0') { + if (c=='\\' && inptr[1]) { + inptr++; + } else if (c=='(') { + depth++; + } else if (c==')') { + depth--; + } + inptr++; + } + } + } + *in = inptr; +} + +/* decode rfc 2047 encoded string segment */ +static char * +rfc2047_decode_word(const char *in, size_t len) +{ + const char *inptr = in+2; + const char *inend = in+len-2; + const char *inbuf; + const char *charset; + char *encname, *p; + int tmplen; + size_t ret; + char *decword = NULL; + char *decoded = NULL; + char *outbase = NULL; + char *outbuf; + size_t inlen, outlen; + gboolean retried = FALSE; + iconv_t ic; + + d(printf("rfc2047: decoding '%.*s'\n", len, in)); + + /* quick check to see if this could possibly be a real encoded word */ + if (len < 8 || !(in[0] == '=' && in[1] == '?' && in[len-1] == '=' && in[len-2] == '?')) { + d(printf("invalid\n")); + return NULL; + } + + /* skip past the charset to the encoding type */ + inptr = memchr (inptr, '?', inend-inptr); + if (inptr != NULL && inptr < inend + 2 && inptr[2] == '?') { + d(printf("found ?, encoding is '%c'\n", inptr[0])); + inptr++; + tmplen = inend-inptr-2; + decword = g_alloca (tmplen); /* this will always be more-than-enough room */ + switch(toupper(inptr[0])) { + case 'Q': + inlen = quoted_decode(inptr+2, tmplen, decword); + break; + case 'B': { + int state = 0; + unsigned int save = 0; + + inlen = camel_base64_decode_step((char *)inptr+2, tmplen, decword, &state, &save); + /* if state != 0 then error? */ + break; + } + default: + /* uhhh, unknown encoding type - probably an invalid encoded word string */ + return NULL; + } + d(printf("The encoded length = %d\n", inlen)); + if (inlen > 0) { + /* yuck, all this snot is to setup iconv! */ + tmplen = inptr - in - 3; + encname = g_alloca (tmplen + 1); + memcpy (encname, in + 2, tmplen); + encname[tmplen] = '\0'; + + /* rfc2231 updates rfc2047 encoded words... + * The ABNF given in RFC 2047 for encoded-words is: + * encoded-word := "=?" charset "?" encoding "?" encoded-text "?=" + * This specification changes this ABNF to: + * encoded-word := "=?" charset ["*" language] "?" encoding "?" encoded-text "?=" + */ + + /* trim off the 'language' part if it's there... */ + p = strchr (encname, '*'); + if (p) + *p = '\0'; + + charset = e_iconv_charset_name (encname); + + inbuf = decword; + + outlen = inlen * 6 + 16; + outbase = g_alloca (outlen); + outbuf = outbase; + + retry: + ic = e_iconv_open ("UTF-8", charset); + if (ic != (iconv_t) -1) { + ret = e_iconv (ic, &inbuf, &inlen, &outbuf, &outlen); + if (ret != (size_t) -1) { + e_iconv (ic, NULL, 0, &outbuf, &outlen); + *outbuf = 0; + decoded = g_strdup (outbase); + } + e_iconv_close (ic); + } else { + w(g_warning ("Cannot decode charset, header display may be corrupt: %s: %s", + charset, strerror (errno))); + + if (!retried) { + charset = e_iconv_locale_charset (); + if (!charset) + charset = "iso-8859-1"; + + retried = TRUE; + goto retry; + } + + /* we return the encoded word here because we've got to return valid utf8 */ + decoded = g_strndup (in, inlen); + } + } + } + + d(printf("decoded '%s'\n", decoded)); + + return decoded; +} + +/* ok, a lot of mailers are BROKEN, and send iso-latin1 encoded + headers, when they should just be sticking to US-ASCII + according to the rfc's. Anyway, since the conversion to utf-8 + is trivial, just do it here without iconv */ +static GString * +append_latin1 (GString *out, const char *in, size_t len) +{ + unsigned int c; + + while (len) { + c = (unsigned int)*in++; + len--; + if (c & 0x80) { + out = g_string_append_c (out, 0xc0 | ((c >> 6) & 0x3)); /* 110000xx */ + out = g_string_append_c (out, 0x80 | (c & 0x3f)); /* 10xxxxxx */ + } else { + out = g_string_append_c (out, c); + } + } + return out; +} + +static int +append_8bit (GString *out, const char *inbuf, size_t inlen, const char *charset) +{ + char *outbase, *outbuf; + size_t outlen; + iconv_t ic; + + ic = e_iconv_open ("UTF-8", charset); + if (ic == (iconv_t) -1) + return FALSE; + + outlen = inlen * 6 + 16; + outbuf = outbase = g_malloc(outlen); + + if (e_iconv (ic, &inbuf, &inlen, &outbuf, &outlen) == (size_t) -1) { + w(g_warning("Conversion to '%s' failed: %s", charset, strerror (errno))); + g_free(outbase); + e_iconv_close (ic); + return FALSE; + } + + e_iconv (ic, NULL, NULL, &outbuf, &outlen); + + *outbuf = 0; + g_string_append(out, outbase); + g_free(outbase); + e_iconv_close (ic); + + return TRUE; + +} + +static void +append_quoted_pair (GString *str, const char *in, int inlen) +{ + register const char *inptr = in; + const char *inend = in + inlen; + char c; + + while (inptr < inend) { + c = *inptr++; + if (c == '\\' && inptr < inend) + g_string_append_c (str, *inptr++); + else + g_string_append_c (str, c); + } +} + +/* decodes a simple text, rfc822 + rfc2047 */ +static char * +header_decode_text (const char *in, size_t inlen, int ctext, const char *default_charset) +{ + GString *out; + const char *inptr, *inend, *start, *chunk, *locale_charset; + void (* append) (GString *, const char *, int); + char *dword = NULL; + guint32 mask; + + locale_charset = e_iconv_locale_charset (); + + if (ctext) { + mask = (CAMEL_MIME_IS_SPECIAL | CAMEL_MIME_IS_SPACE | CAMEL_MIME_IS_CTRL); + append = append_quoted_pair; + } else { + mask = (CAMEL_MIME_IS_LWSP); + append = g_string_append_len; + } + + out = g_string_new (""); + inptr = in; + inend = inptr + inlen; + chunk = NULL; + + while (inptr < inend) { + start = inptr; + while (inptr < inend && camel_mime_is_type (*inptr, mask)) + inptr++; + + if (inptr == inend) { + append (out, start, inptr - start); + break; + } else if (dword == NULL) { + append (out, start, inptr - start); + } else { + chunk = start; + } + + start = inptr; + while (inptr < inend && !camel_mime_is_type (*inptr, mask)) + inptr++; + + dword = rfc2047_decode_word(start, inptr-start); + if (dword) { + g_string_append(out, dword); + g_free(dword); + } else { + if (!chunk) + chunk = start; + + if ((default_charset == NULL || !append_8bit (out, chunk, inptr-chunk, default_charset)) + && (locale_charset == NULL || !append_8bit(out, chunk, inptr-chunk, locale_charset))) + append_latin1(out, chunk, inptr-chunk); + } + + chunk = NULL; + } + + dword = out->str; + g_string_free (out, FALSE); + + return dword; +} + +char * +camel_header_decode_string (const char *in, const char *default_charset) +{ + if (in == NULL) + return NULL; + return header_decode_text (in, strlen (in), FALSE, default_charset); +} + +char * +camel_header_format_ctext (const char *in, const char *default_charset) +{ + if (in == NULL) + return NULL; + return header_decode_text (in, strlen (in), TRUE, default_charset); +} + +/* how long a sequence of pre-encoded words should be less than, to attempt to + fit into a properly folded word. Only a guide. */ +#define CAMEL_FOLD_PREENCODED (24) + +/* FIXME: needs a way to cache iconv opens for different charsets? */ +static void +rfc2047_encode_word(GString *outstring, const char *in, size_t len, const char *type, unsigned short safemask) +{ + iconv_t ic = (iconv_t) -1; + char *buffer, *out, *ascii; + size_t inlen, outlen, enclen, bufflen; + const char *inptr, *p; + int first = 1; + + d(printf("Converting [%d] '%.*s' to %s\n", len, len, in, type)); + + /* convert utf8->encoding */ + bufflen = len * 6 + 16; + buffer = g_alloca (bufflen); + inlen = len; + inptr = in; + + ascii = g_alloca (bufflen); + + if (strcasecmp (type, "UTF-8") != 0) + ic = e_iconv_open (type, "UTF-8"); + + while (inlen) { + size_t convlen, proclen; + int i; + + /* break up words into smaller bits, what we really want is encoded + overhead < 75, + but we'll just guess what that means in terms of input chars, and assume its good enough */ + + out = buffer; + outlen = bufflen; + + if (ic == (iconv_t) -1) { + /* native encoding case, the easy one (?) */ + /* we work out how much we can convert, and still be in length */ + /* proclen will be the result of input characters that we can convert, to the nearest + (approximated) valid utf8 char */ + convlen = 0; + proclen = 0; + p = inptr; + i = 0; + while (p < (in+len) && convlen < (75 - strlen("=?utf-8?q\?\?="))) { + unsigned char c = *p++; + + if (c >= 0xc0) + proclen = i; + i++; + if (c < 0x80) + proclen = i; + if (camel_mime_special_table[c] & safemask) + convlen += 1; + else + convlen += 3; + } + /* well, we probably have broken utf8, just copy it anyway what the heck */ + if (proclen == 0) { + w(g_warning("Appear to have truncated utf8 sequence")); + proclen = inlen; + } + memcpy(out, inptr, proclen); + inptr += proclen; + inlen -= proclen; + out += proclen; + } else { + /* well we could do similar, but we can't (without undue effort), we'll just break it up into + hopefully-small-enough chunks, and leave it at that */ + convlen = MIN(inlen, CAMEL_FOLD_PREENCODED); + p = inptr; + if (e_iconv (ic, &inptr, &convlen, &out, &outlen) == (size_t) -1 && errno != EINVAL) { + w(g_warning("Conversion problem: conversion truncated: %s", strerror (errno))); + /* blah, we include it anyway, better than infinite loop ... */ + inptr = p + convlen; + } else { + /* make sure we flush out any shift state */ + e_iconv (ic, NULL, 0, &out, &outlen); + } + inlen -= (inptr - p); + } + + enclen = out-buffer; + + if (enclen) { + /* create token */ + out = ascii; + if (first) + first = 0; + else + *out++ = ' '; + out += sprintf (out, "=?%s?Q?", type); + out += quoted_encode (buffer, enclen, out, safemask); + sprintf (out, "?="); + + d(printf("converted part = %s\n", ascii)); + + g_string_append (outstring, ascii); + } + } + + if (ic != (iconv_t) -1) + e_iconv_close (ic); +} + + +/* TODO: Should this worry about quotes?? */ +char * +camel_header_encode_string (const unsigned char *in) +{ + const unsigned char *inptr = in, *start, *word; + gboolean last_was_encoded = FALSE; + gboolean last_was_space = FALSE; + int encoding; + GString *out; + char *outstr; + + g_return_val_if_fail (g_utf8_validate (in, -1, NULL), NULL); + + if (in == NULL) + return NULL; + + /* do a quick us-ascii check (the common case?) */ + while (*inptr) { + if (*inptr > 127) + break; + inptr++; + } + if (*inptr == '\0') + return g_strdup (in); + + /* This gets each word out of the input, and checks to see what charset + can be used to encode it. */ + /* TODO: Work out when to merge subsequent words, or across word-parts */ + out = g_string_new (""); + inptr = in; + encoding = 0; + word = NULL; + start = inptr; + while (inptr && *inptr) { + gunichar c; + const char *newinptr; + + newinptr = g_utf8_next_char (inptr); + c = g_utf8_get_char (inptr); + if (newinptr == NULL || !g_unichar_validate (c)) { + w(g_warning ("Invalid UTF-8 sequence encountered (pos %d, char '%c'): %s", + (inptr-in), inptr[0], in)); + inptr++; + continue; + } + + if (c < 256 && camel_mime_is_lwsp (c) && !last_was_space) { + /* we've reached the end of a 'word' */ + if (word && !(last_was_encoded && encoding)) { + /* output lwsp between non-encoded words */ + g_string_append_len (out, start, word - start); + start = word; + } + + switch (encoding) { + case 0: + g_string_append_len (out, start, inptr - start); + last_was_encoded = FALSE; + break; + case 1: + if (last_was_encoded) + g_string_append_c (out, ' '); + + rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE); + last_was_encoded = TRUE; + break; + case 2: + if (last_was_encoded) + g_string_append_c (out, ' '); + + rfc2047_encode_word (out, start, inptr - start, + camel_charset_best (start, inptr - start), CAMEL_MIME_IS_ESAFE); + last_was_encoded = TRUE; + break; + } + + last_was_space = TRUE; + start = inptr; + word = NULL; + encoding = 0; + } else if (c > 127 && c < 256) { + encoding = MAX (encoding, 1); + last_was_space = FALSE; + } else if (c >= 256) { + encoding = MAX (encoding, 2); + last_was_space = FALSE; + } else if (!camel_mime_is_lwsp (c)) { + last_was_space = FALSE; + } + + if (!(c < 256 && camel_mime_is_lwsp (c)) && !word) + word = inptr; + + inptr = newinptr; + } + + if (inptr - start) { + if (word && !(last_was_encoded && encoding)) { + g_string_append_len (out, start, word - start); + start = word; + } + + switch (encoding) { + case 0: + g_string_append_len (out, start, inptr - start); + break; + case 1: + if (last_was_encoded) + g_string_append_c (out, ' '); + + rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE); + break; + case 2: + if (last_was_encoded) + g_string_append_c (out, ' '); + + rfc2047_encode_word (out, start, inptr - start, + camel_charset_best (start, inptr - start - 1), CAMEL_MIME_IS_ESAFE); + break; + } + } + + outstr = out->str; + g_string_free (out, FALSE); + + return outstr; +} + +/* apply quoted-string rules to a string */ +static void +quote_word(GString *out, gboolean do_quotes, const char *start, size_t len) +{ + int i, c; + + /* TODO: What about folding on long lines? */ + if (do_quotes) + g_string_append_c(out, '"'); + for (i=0;i<len;i++) { + c = *start++; + if (c == '\"' || c=='\\' || c=='\r') + g_string_append_c(out, '\\'); + g_string_append_c(out, c); + } + if (do_quotes) + g_string_append_c(out, '"'); +} + +/* incrementing possibility for the word type */ +enum _phrase_word_t { + WORD_ATOM, + WORD_QSTRING, + WORD_2047 +}; + +struct _phrase_word { + const unsigned char *start, *end; + enum _phrase_word_t type; + int encoding; +}; + +static gboolean +word_types_compatable (enum _phrase_word_t type1, enum _phrase_word_t type2) +{ + switch (type1) { + case WORD_ATOM: + return type2 == WORD_QSTRING; + case WORD_QSTRING: + return type2 != WORD_2047; + case WORD_2047: + return type2 == WORD_2047; + default: + return FALSE; + } +} + +/* split the input into words with info about each word + * merge common word types clean up */ +static GList * +header_encode_phrase_get_words (const unsigned char *in) +{ + const unsigned char *inptr = in, *start, *last; + struct _phrase_word *word; + enum _phrase_word_t type; + int encoding, count = 0; + GList *words = NULL; + + /* break the input into words */ + type = WORD_ATOM; + last = inptr; + start = inptr; + encoding = 0; + while (inptr && *inptr) { + gunichar c; + const char *newinptr; + + newinptr = g_utf8_next_char (inptr); + c = g_utf8_get_char (inptr); + + if (!g_unichar_validate (c)) { + w(g_warning ("Invalid UTF-8 sequence encountered (pos %d, char '%c'): %s", + (inptr - in), inptr[0], in)); + inptr++; + continue; + } + + inptr = newinptr; + if (g_unichar_isspace (c)) { + if (count > 0) { + word = g_new0 (struct _phrase_word, 1); + word->start = start; + word->end = last; + word->type = type; + word->encoding = encoding; + words = g_list_append (words, word); + count = 0; + } + + start = inptr; + type = WORD_ATOM; + encoding = 0; + } else { + count++; + if (c < 128) { + if (!camel_mime_is_atom (c)) + type = MAX (type, WORD_QSTRING); + } else if (c > 127 && c < 256) { + type = WORD_2047; + encoding = MAX (encoding, 1); + } else if (c >= 256) { + type = WORD_2047; + encoding = MAX (encoding, 2); + } + } + + last = inptr; + } + + if (count > 0) { + word = g_new0 (struct _phrase_word, 1); + word->start = start; + word->end = last; + word->type = type; + word->encoding = encoding; + words = g_list_append (words, word); + } + + return words; +} + +#define MERGED_WORD_LT_FOLDLEN(wordlen, type) ((type) == WORD_2047 ? (wordlen) < CAMEL_FOLD_PREENCODED : (wordlen) < (CAMEL_FOLD_SIZE - 8)) + +static gboolean +header_encode_phrase_merge_words (GList **wordsp) +{ + GList *wordl, *nextl, *words = *wordsp; + struct _phrase_word *word, *next; + gboolean merged = FALSE; + + /* scan the list, checking for words of similar types that can be merged */ + wordl = words; + while (wordl) { + word = wordl->data; + nextl = g_list_next (wordl); + + while (nextl) { + next = nextl->data; + /* merge nodes of the same type AND we are not creating too long a string */ + if (word_types_compatable (word->type, next->type)) { + if (MERGED_WORD_LT_FOLDLEN (next->end - word->start, MAX (word->type, next->type))) { + /* the resulting word type is the MAX of the 2 types */ + word->type = MAX(word->type, next->type); + + word->end = next->end; + words = g_list_remove_link (words, nextl); + g_list_free_1 (nextl); + g_free (next); + + nextl = g_list_next (wordl); + + merged = TRUE; + } else { + /* if it is going to be too long, make sure we include the + separating whitespace */ + word->end = next->start; + break; + } + } else { + break; + } + } + + wordl = g_list_next (wordl); + } + + *wordsp = words; + + return merged; +} + +/* encodes a phrase sequence (different quoting/encoding rules to strings) */ +char * +camel_header_encode_phrase (const unsigned char *in) +{ + struct _phrase_word *word = NULL, *last_word = NULL; + GList *words, *wordl; + GString *out; + char *outstr; + + if (in == NULL) + return NULL; + + words = header_encode_phrase_get_words (in); + if (!words) + return NULL; + + while (header_encode_phrase_merge_words (&words)) + ; + + out = g_string_new (""); + + /* output words now with spaces between them */ + wordl = words; + while (wordl) { + const char *start; + size_t len; + + word = wordl->data; + + /* append correct number of spaces between words */ + if (last_word && !(last_word->type == WORD_2047 && word->type == WORD_2047)) { + /* one or both of the words are not encoded so we write the spaces out untouched */ + len = word->start - last_word->end; + out = g_string_append_len (out, last_word->end, len); + } + + switch (word->type) { + case WORD_ATOM: + out = g_string_append_len (out, word->start, word->end - word->start); + break; + case WORD_QSTRING: + quote_word (out, TRUE, word->start, word->end - word->start); + break; + case WORD_2047: + if (last_word && last_word->type == WORD_2047) { + /* include the whitespace chars between these 2 words in the + resulting rfc2047 encoded word. */ + len = word->end - last_word->end; + start = last_word->end; + + /* encoded words need to be separated by linear whitespace */ + g_string_append_c (out, ' '); + } else { + len = word->end - word->start; + start = word->start; + } + + if (word->encoding == 1) + rfc2047_encode_word (out, start, len, "ISO-8859-1", CAMEL_MIME_IS_PSAFE); + else + rfc2047_encode_word (out, start, len, + camel_charset_best (start, len), CAMEL_MIME_IS_PSAFE); + break; + } + + g_free (last_word); + wordl = g_list_next (wordl); + + last_word = word; + } + + /* and we no longer need the list */ + g_free (word); + g_list_free (words); + + outstr = out->str; + g_string_free (out, FALSE); + + return outstr; +} + + +/* these are all internal parser functions */ + +static char * +decode_token (const char **in) +{ + const char *inptr = *in; + const char *start; + + header_decode_lwsp (&inptr); + start = inptr; + while (camel_mime_is_ttoken (*inptr)) + inptr++; + if (inptr > start) { + *in = inptr; + return g_strndup (start, inptr - start); + } else { + return NULL; + } +} + +char * +camel_header_token_decode(const char *in) +{ + if (in == NULL) + return NULL; + + return decode_token(&in); +} + +/* + <"> * ( <any char except <"> \, cr / \ <any char> ) <"> +*/ +static char * +header_decode_quoted_string(const char **in) +{ + const char *inptr = *in; + char *out = NULL, *outptr; + size_t outlen; + int c; + + header_decode_lwsp(&inptr); + if (*inptr == '"') { + const char *intmp; + int skip = 0; + + /* first, calc length */ + inptr++; + intmp = inptr; + while ( (c = *intmp++) && c!= '"') { + if (c=='\\' && *intmp) { + intmp++; + skip++; + } + } + outlen = intmp-inptr-skip; + out = outptr = g_malloc(outlen+1); + while ( (c = *inptr++) && c!= '"') { + if (c=='\\' && *inptr) { + c = *inptr++; + } + *outptr++ = c; + } + *outptr = '\0'; + } + *in = inptr; + return out; +} + +static char * +header_decode_atom(const char **in) +{ + const char *inptr = *in, *start; + + header_decode_lwsp(&inptr); + start = inptr; + while (camel_mime_is_atom(*inptr)) + inptr++; + *in = inptr; + if (inptr > start) + return g_strndup(start, inptr-start); + else + return NULL; +} + +static char * +header_decode_word (const char **in) +{ + const char *inptr = *in; + + header_decode_lwsp (&inptr); + if (*inptr == '"') { + *in = inptr; + return header_decode_quoted_string (in); + } else { + *in = inptr; + return header_decode_atom (in); + } +} + +static char * +header_decode_value(const char **in) +{ + const char *inptr = *in; + + header_decode_lwsp(&inptr); + if (*inptr == '"') { + d(printf("decoding quoted string\n")); + return header_decode_quoted_string(in); + } else if (camel_mime_is_ttoken(*inptr)) { + d(printf("decoding token\n")); + /* this may not have the right specials for all params? */ + return decode_token(in); + } + return NULL; +} + +/* should this return -1 for no int? */ +int +camel_header_decode_int(const char **in) +{ + const char *inptr = *in; + int c, v=0; + + header_decode_lwsp(&inptr); + while ( (c=*inptr++ & 0xff) + && isdigit(c) ) { + v = v*10+(c-'0'); + } + *in = inptr-1; + return v; +} + +#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 (inptr[1]) && isxdigit (inptr[2])) { + *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]); + inptr += 3; + } else + *outptr++ = *inptr++; + } else + *outptr++ = *inptr++; + } + + *outptr = '\0'; + + return outbuf; +} + +/* Tries to convert @in @from charset @to charset. Any failure, we get no data out rather than partial conversion */ +static char * +header_convert(const char *to, const char *from, const char *in, size_t inlen) +{ + iconv_t ic; + size_t outlen, ret; + char *outbuf, *outbase, *result = NULL; + + ic = e_iconv_open(to, from); + if (ic == (iconv_t) -1) + return NULL; + + outlen = inlen * 6 + 16; + outbuf = outbase = g_malloc(outlen); + + ret = e_iconv(ic, &in, &inlen, &outbuf, &outlen); + if (ret != (size_t) -1) { + e_iconv(ic, NULL, 0, &outbuf, &outlen); + *outbuf = '\0'; + result = g_strdup(outbase); + } + e_iconv_close(ic); + g_free(outbase); + + return result; +} + +/* an rfc2184 encoded string looks something like: + * us-ascii'en'This%20is%20even%20more%20 + */ + +static char * +rfc2184_decode (const char *in, size_t len) +{ + const char *inptr = in; + const char *inend = in + len; + const char *charset; + char *decoded, *decword, *encoding; + + inptr = memchr (inptr, '\'', len); + if (!inptr) + return NULL; + + encoding = g_alloca(inptr-in+1); + memcpy(encoding, in, inptr-in); + encoding[inptr-in] = 0; + charset = e_iconv_charset_name (encoding); + + inptr = memchr (inptr + 1, '\'', inend - inptr - 1); + if (!inptr) + return NULL; + inptr++; + if (inptr >= inend) + return NULL; + + decword = hex_decode (inptr, inend - inptr); + decoded = header_convert("UTF-8", charset, decword, strlen(decword)); + g_free(decword); + + return decoded; +} + +char * +camel_header_param (struct _camel_header_param *p, const char *name) +{ + while (p && strcasecmp (p->name, name) != 0) + p = p->next; + if (p) + return p->value; + return NULL; +} + +struct _camel_header_param * +camel_header_set_param (struct _camel_header_param **l, const char *name, const char *value) +{ + struct _camel_header_param *p = (struct _camel_header_param *)l, *pn; + + if (name == NULL) + return NULL; + + while (p->next) { + pn = p->next; + if (!strcasecmp (pn->name, name)) { + g_free (pn->value); + if (value) { + pn->value = g_strdup (value); + return pn; + } else { + p->next = pn->next; + g_free (pn->name); + g_free (pn); + return NULL; + } + } + p = pn; + } + + if (value == NULL) + return NULL; + + pn = g_malloc (sizeof (*pn)); + pn->next = 0; + pn->name = g_strdup (name); + pn->value = g_strdup (value); + p->next = pn; + + return pn; +} + +const char * +camel_content_type_param (CamelContentType *t, const char *name) +{ + if (t==NULL) + return NULL; + return camel_header_param (t->params, name); +} + +void +camel_content_type_set_param (CamelContentType *t, const char *name, const char *value) +{ + camel_header_set_param (&t->params, name, value); +} + +/** + * camel_content_type_is: + * @ct: A content type specifier, or #NULL. + * @type: A type to check against. + * @subtype: A subtype to check against, or "*" to match any subtype. + * + * Returns #TRUE if the content type @ct is of type @type/@subtype. + * The subtype of "*" will match any subtype. If @ct is #NULL, then + * it will match the type "text/plain". + * + * Return value: #TRUE or #FALSE depending on the matching of the type. + **/ +int +camel_content_type_is(CamelContentType *ct, const char *type, const char *subtype) +{ + /* no type == text/plain or text/"*" */ + if (ct==NULL || (ct->type == NULL && ct->subtype == NULL)) { + return (!strcasecmp(type, "text") + && (!strcasecmp(subtype, "plain") + || !strcasecmp(subtype, "*"))); + } + + return (ct->type != NULL + && (!strcasecmp(ct->type, type) + && ((ct->subtype != NULL + && !strcasecmp(ct->subtype, subtype)) + || !strcasecmp("*", subtype)))); +} + +void +camel_header_param_list_free(struct _camel_header_param *p) +{ + struct _camel_header_param *n; + + while (p) { + n = p->next; + g_free(p->name); + g_free(p->value); + g_free(p); + p = n; + } +} + +CamelContentType * +camel_content_type_new(const char *type, const char *subtype) +{ + CamelContentType *t = g_malloc(sizeof(*t)); + + t->type = g_strdup(type); + t->subtype = g_strdup(subtype); + t->params = NULL; + t->refcount = 1; + return t; +} + +void +camel_content_type_ref(CamelContentType *ct) +{ + if (ct) + ct->refcount++; +} + + +void +camel_content_type_unref(CamelContentType *ct) +{ + if (ct) { + if (ct->refcount <= 1) { + camel_header_param_list_free(ct->params); + g_free(ct->type); + g_free(ct->subtype); + g_free(ct); + } else { + ct->refcount--; + } + } +} + +/* for decoding email addresses, canonically */ +static char * +header_decode_domain(const char **in) +{ + const char *inptr = *in, *start; + int go = TRUE; + char *ret; + GString *domain = g_string_new(""); + + /* domain ref | domain literal */ + header_decode_lwsp(&inptr); + while (go) { + if (*inptr == '[') { /* domain literal */ + domain = g_string_append_c(domain, '['); + inptr++; + header_decode_lwsp(&inptr); + start = inptr; + while (camel_mime_is_dtext(*inptr)) { + domain = g_string_append_c(domain, *inptr); + inptr++; + } + if (*inptr == ']') { + domain = g_string_append_c(domain, ']'); + inptr++; + } else { + w(g_warning("closing ']' not found in domain: %s", *in)); + } + } else { + char *a = header_decode_atom(&inptr); + if (a) { + domain = g_string_append(domain, a); + g_free(a); + } else { + w(g_warning("missing atom from domain-ref")); + break; + } + } + header_decode_lwsp(&inptr); + if (*inptr == '.') { /* next sub-domain? */ + domain = g_string_append_c(domain, '.'); + inptr++; + header_decode_lwsp(&inptr); + } else + go = FALSE; + } + + *in = inptr; + + ret = domain->str; + g_string_free(domain, FALSE); + return ret; +} + +static char * +header_decode_addrspec(const char **in) +{ + const char *inptr = *in; + char *word; + GString *addr = g_string_new(""); + + header_decode_lwsp(&inptr); + + /* addr-spec */ + word = header_decode_word (&inptr); + if (word) { + addr = g_string_append(addr, word); + header_decode_lwsp(&inptr); + g_free(word); + while (*inptr == '.' && word) { + inptr++; + addr = g_string_append_c(addr, '.'); + word = header_decode_word (&inptr); + if (word) { + addr = g_string_append(addr, word); + header_decode_lwsp(&inptr); + g_free(word); + } else { + w(g_warning("Invalid address spec: %s", *in)); + } + } + if (*inptr == '@') { + inptr++; + addr = g_string_append_c(addr, '@'); + word = header_decode_domain(&inptr); + if (word) { + addr = g_string_append(addr, word); + g_free(word); + } else { + w(g_warning("Invalid address, missing domain: %s", *in)); + } + } else { + w(g_warning("Invalid addr-spec, missing @: %s", *in)); + } + } else { + w(g_warning("invalid addr-spec, no local part")); + } + + /* FIXME: return null on error? */ + + *in = inptr; + word = addr->str; + g_string_free(addr, FALSE); + return word; +} + +/* + address: + word *('.' word) @ domain | + *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain | + + 1*word ':' [ word ... etc (mailbox, as above) ] ';' + */ + +/* mailbox: + word *( '.' word ) '@' domain + *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain + */ + +static struct _camel_header_address * +header_decode_mailbox(const char **in, const char *charset) +{ + const char *inptr = *in; + char *pre; + int closeme = FALSE; + GString *addr; + GString *name = NULL; + struct _camel_header_address *address = NULL; + const char *comment = NULL; + + addr = g_string_new(""); + + /* for each address */ + pre = header_decode_word (&inptr); + header_decode_lwsp(&inptr); + if (!(*inptr == '.' || *inptr == '@' || *inptr==',' || *inptr=='\0')) { + /* ',' and '\0' required incase it is a simple address, no @ domain part (buggy writer) */ + name = g_string_new (""); + while (pre) { + char *text, *last; + + /* perform internationalised decoding, and append */ + text = camel_header_decode_string (pre, charset); + g_string_append (name, text); + last = pre; + g_free(text); + + pre = header_decode_word (&inptr); + if (pre) { + size_t l = strlen (last); + size_t p = strlen (pre); + + /* dont append ' ' between sucsessive encoded words */ + if ((l>6 && last[l-2] == '?' && last[l-1] == '=') + && (p>6 && pre[0] == '=' && pre[1] == '?')) { + /* dont append ' ' */ + } else { + name = g_string_append_c(name, ' '); + } + } else { + /* Fix for stupidly-broken-mailers that like to put '.''s in names unquoted */ + /* see bug #8147 */ + while (!pre && *inptr && *inptr != '<') { + w(g_warning("Working around stupid mailer bug #5: unescaped characters in names")); + name = g_string_append_c(name, *inptr++); + pre = header_decode_word (&inptr); + } + } + g_free(last); + } + header_decode_lwsp(&inptr); + if (*inptr == '<') { + closeme = TRUE; + try_address_again: + inptr++; + header_decode_lwsp(&inptr); + if (*inptr == '@') { + while (*inptr == '@') { + inptr++; + header_decode_domain(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == ',') { + inptr++; + header_decode_lwsp(&inptr); + } + } + if (*inptr == ':') { + inptr++; + } else { + w(g_warning("broken route-address, missing ':': %s", *in)); + } + } + pre = header_decode_word (&inptr); + header_decode_lwsp(&inptr); + } else { + w(g_warning("broken address? %s", *in)); + } + } + + if (pre) { + addr = g_string_append(addr, pre); + } else { + w(g_warning("No local-part for email address: %s", *in)); + } + + /* should be at word '.' localpart */ + while (*inptr == '.' && pre) { + inptr++; + g_free(pre); + pre = header_decode_word (&inptr); + addr = g_string_append_c(addr, '.'); + if (pre) + addr = g_string_append(addr, pre); + comment = inptr; + header_decode_lwsp(&inptr); + } + g_free(pre); + + /* now at '@' domain part */ + if (*inptr == '@') { + char *dom; + + inptr++; + addr = g_string_append_c(addr, '@'); + comment = inptr; + dom = header_decode_domain(&inptr); + addr = g_string_append(addr, dom); + g_free(dom); + } else if (*inptr != '>' || !closeme) { + /* If we get a <, the address was probably a name part, lets try again shall we? */ + /* Another fix for seriously-broken-mailers */ + if (*inptr && *inptr != ',') { + char *text; + + w(g_warning("We didn't get an '@' where we expected in '%s', trying again", *in)); + w(g_warning("Name is '%s', Addr is '%s' we're at '%s'\n", name?name->str:"<UNSET>", addr->str, inptr)); + + /* need to keep *inptr, as try_address_again will drop the current character */ + if (*inptr == '<') + closeme = TRUE; + else + g_string_append_c(addr, *inptr); + + /* check for address is encoded word ... */ + text = camel_header_decode_string(addr->str, charset); + if (name == NULL) { + name = addr; + addr = g_string_new(""); + if (text) { + g_string_truncate(name, 0); + g_string_append(name, text); + } + } else { + g_string_append(name, text?text:addr->str); + g_string_truncate(addr, 0); + } + g_free(text); + + /* or maybe that we've added up a bunch of broken bits to make an encoded word */ + text = rfc2047_decode_word(name->str, name->len); + if (text) { + g_string_truncate(name, 0); + g_string_append(name, text); + g_free(text); + } + + goto try_address_again; + } + w(g_warning("invalid address, no '@' domain part at %c: %s", *inptr, *in)); + } + + if (closeme) { + header_decode_lwsp(&inptr); + if (*inptr == '>') { + inptr++; + } else { + w(g_warning("invalid route address, no closing '>': %s", *in)); + } + } else if (name == NULL && comment != NULL && inptr>comment) { /* check for comment after address */ + char *text, *tmp; + const char *comstart, *comend; + + /* this is a bit messy, we go from the last known position, because + decode_domain/etc skip over any comments on the way */ + /* FIXME: This wont detect comments inside the domain itself, + but nobody seems to use that feature anyway ... */ + + d(printf("checking for comment from '%s'\n", comment)); + + comstart = strchr(comment, '('); + if (comstart) { + comstart++; + header_decode_lwsp(&inptr); + comend = inptr-1; + while (comend > comstart && comend[0] != ')') + comend--; + + if (comend > comstart) { + d(printf(" looking at subset '%.*s'\n", comend-comstart, comstart)); + tmp = g_strndup (comstart, comend-comstart); + text = camel_header_decode_string (tmp, charset); + name = g_string_new (text); + g_free (tmp); + g_free (text); + } + } + } + + *in = inptr; + + if (addr->len > 0) { + if (!g_utf8_validate (addr->str, addr->len, NULL)) { + /* workaround for invalid addr-specs containing 8bit chars (see bug #42170 for details) */ + const char *locale_charset; + GString *out; + + locale_charset = e_iconv_locale_charset (); + + out = g_string_new (""); + + if ((charset == NULL || !append_8bit (out, addr->str, addr->len, charset)) + && (locale_charset == NULL || !append_8bit (out, addr->str, addr->len, locale_charset))) + append_latin1 (out, addr->str, addr->len); + + g_string_free (addr, TRUE); + addr = out; + } + + address = camel_header_address_new_name(name ? name->str : "", addr->str); + } + + d(printf("got mailbox: %s\n", addr->str)); + + g_string_free(addr, TRUE); + if (name) + g_string_free(name, TRUE); + + return address; +} + +static struct _camel_header_address * +header_decode_address(const char **in, const char *charset) +{ + const char *inptr = *in; + char *pre; + GString *group = g_string_new(""); + struct _camel_header_address *addr = NULL, *member; + + /* pre-scan, trying to work out format, discard results */ + header_decode_lwsp(&inptr); + while ((pre = header_decode_word (&inptr))) { + group = g_string_append(group, pre); + group = g_string_append(group, " "); + g_free(pre); + } + header_decode_lwsp(&inptr); + if (*inptr == ':') { + d(printf("group detected: %s\n", group->str)); + addr = camel_header_address_new_group(group->str); + /* that was a group spec, scan mailbox's */ + inptr++; + /* FIXME: check rfc 2047 encodings of words, here or above in the loop */ + header_decode_lwsp(&inptr); + if (*inptr != ';') { + int go = TRUE; + do { + member = header_decode_mailbox(&inptr, charset); + if (member) + camel_header_address_add_member(addr, member); + header_decode_lwsp(&inptr); + if (*inptr == ',') + inptr++; + else + go = FALSE; + } while (go); + if (*inptr == ';') { + inptr++; + } else { + w(g_warning("Invalid group spec, missing closing ';': %s", *in)); + } + } else { + inptr++; + } + *in = inptr; + } else { + addr = header_decode_mailbox(in, charset); + } + + g_string_free(group, TRUE); + + return addr; +} + +static char * +header_msgid_decode_internal(const char **in) +{ + const char *inptr = *in; + char *msgid = NULL; + + d(printf("decoding Message-ID: '%s'\n", *in)); + + header_decode_lwsp(&inptr); + if (*inptr == '<') { + inptr++; + header_decode_lwsp(&inptr); + msgid = header_decode_addrspec(&inptr); + if (msgid) { + header_decode_lwsp(&inptr); + if (*inptr == '>') { + inptr++; + } else { + w(g_warning("Missing closing '>' on message id: %s", *in)); + } + } else { + w(g_warning("Cannot find message id in: %s", *in)); + } + } else { + w(g_warning("missing opening '<' on message id: %s", *in)); + } + *in = inptr; + + return msgid; +} + +char * +camel_header_msgid_decode(const char *in) +{ + if (in == NULL) + return NULL; + + return header_msgid_decode_internal(&in); +} + +char * +camel_header_contentid_decode (const char *in) +{ + const char *inptr = in; + gboolean at = FALSE; + GString *addr; + char *buf; + + d(printf("decoding Content-ID: '%s'\n", in)); + + header_decode_lwsp (&inptr); + + /* some lame mailers quote the Content-Id */ + if (*inptr == '"') + inptr++; + + /* make sure the content-id is not "" which can happen if we get a + * content-id such as <.@> (which Eudora likes to use...) */ + if ((buf = camel_header_msgid_decode (inptr)) != NULL && *buf) + return buf; + + g_free (buf); + + /* ugh, not a valid msg-id - try to get something useful out of it then? */ + inptr = in; + header_decode_lwsp (&inptr); + if (*inptr == '<') { + inptr++; + header_decode_lwsp (&inptr); + } + + /* Eudora has been known to use <.@> as a content-id */ + if (!(buf = header_decode_word (&inptr)) && !strchr (".@", *inptr)) + return NULL; + + addr = g_string_new (""); + header_decode_lwsp (&inptr); + while (buf != NULL || *inptr == '.' || (*inptr == '@' && !at)) { + if (buf != NULL) { + g_string_append (addr, buf); + g_free (buf); + buf = NULL; + } + + if (!at) { + if (*inptr == '.') { + g_string_append_c (addr, *inptr++); + buf = header_decode_word (&inptr); + } else if (*inptr == '@') { + g_string_append_c (addr, *inptr++); + buf = header_decode_word (&inptr); + at = TRUE; + } + } else if (strchr (".[]", *inptr)) { + g_string_append_c (addr, *inptr++); + buf = header_decode_atom (&inptr); + } + + header_decode_lwsp (&inptr); + } + + buf = addr->str; + g_string_free (addr, FALSE); + + return buf; +} + +void +camel_header_references_list_append_asis(struct _camel_header_references **list, char *ref) +{ + struct _camel_header_references *w = (struct _camel_header_references *)list, *n; + while (w->next) + w = w->next; + n = g_malloc(sizeof(*n)); + n->id = ref; + n->next = 0; + w->next = n; +} + +int +camel_header_references_list_size(struct _camel_header_references **list) +{ + int count = 0; + struct _camel_header_references *w = *list; + while (w) { + count++; + w = w->next; + } + return count; +} + +void +camel_header_references_list_clear(struct _camel_header_references **list) +{ + struct _camel_header_references *w = *list, *n; + while (w) { + n = w->next; + g_free(w->id); + g_free(w); + w = n; + } + *list = NULL; +} + +static void +header_references_decode_single (const char **in, struct _camel_header_references **head) +{ + struct _camel_header_references *ref; + const char *inptr = *in; + char *id, *word; + + while (*inptr) { + header_decode_lwsp (&inptr); + if (*inptr == '<') { + id = header_msgid_decode_internal (&inptr); + if (id) { + ref = g_malloc (sizeof (struct _camel_header_references)); + ref->next = *head; + ref->id = id; + *head = ref; + break; + } + } else { + word = header_decode_word (&inptr); + if (word) + g_free (word); + else if (*inptr != '\0') + inptr++; /* Stupid mailer tricks */ + } + } + + *in = inptr; +} + +/* TODO: why is this needed? Can't the other interface also work? */ +struct _camel_header_references * +camel_header_references_inreplyto_decode (const char *in) +{ + struct _camel_header_references *ref = NULL; + + if (in == NULL || in[0] == '\0') + return NULL; + + header_references_decode_single (&in, &ref); + + return ref; +} + +/* generate a list of references, from most recent up */ +struct _camel_header_references * +camel_header_references_decode (const char *in) +{ + struct _camel_header_references *refs = NULL; + + if (in == NULL || in[0] == '\0') + return NULL; + + while (*in) + header_references_decode_single (&in, &refs); + + return refs; +} + +struct _camel_header_references * +camel_header_references_dup(const struct _camel_header_references *list) +{ + struct _camel_header_references *new = NULL, *tmp; + + while (list) { + tmp = g_new(struct _camel_header_references, 1); + tmp->next = new; + tmp->id = g_strdup(list->id); + new = tmp; + list = list->next; + } + return new; +} + +struct _camel_header_address * +camel_header_mailbox_decode(const char *in, const char *charset) +{ + if (in == NULL) + return NULL; + + return header_decode_mailbox(&in, charset); +} + +struct _camel_header_address * +camel_header_address_decode(const char *in, const char *charset) +{ + const char *inptr = in, *last; + struct _camel_header_address *list = NULL, *addr; + + d(printf("decoding To: '%s'\n", in)); + + if (in == NULL) + return NULL; + + header_decode_lwsp(&inptr); + if (*inptr == 0) + return NULL; + + do { + last = inptr; + addr = header_decode_address(&inptr, charset); + if (addr) + camel_header_address_list_append(&list, addr); + header_decode_lwsp(&inptr); + if (*inptr == ',') + inptr++; + else + break; + } while (inptr != last); + + if (*inptr) { + w(g_warning("Invalid input detected at %c (%d): %s\n or at: %s", *inptr, inptr-in, in, inptr)); + } + + if (inptr == last) { + w(g_warning("detected invalid input loop at : %s", last)); + } + + return list; +} + +struct _camel_header_newsgroup * +camel_header_newsgroups_decode(const char *in) +{ + const char *inptr = in; + register char c; + struct _camel_header_newsgroup *head, *last, *ng; + const char *start; + + head = NULL; + last = (struct _camel_header_newsgroup *)&head; + + do { + header_decode_lwsp(&inptr); + start = inptr; + while ((c = *inptr++) && !camel_mime_is_lwsp(c) && c != ',') + ; + if (start != inptr-1) { + ng = g_malloc(sizeof(*ng)); + ng->newsgroup = g_strndup(start, inptr-start-1); + ng->next = NULL; + last->next = ng; + last = ng; + } + } while (c); + + return head; +} + +void +camel_header_newsgroups_free(struct _camel_header_newsgroup *ng) +{ + while (ng) { + struct _camel_header_newsgroup *nng = ng->next; + + g_free(ng->newsgroup); + g_free(ng); + ng = nng; + } +} + +/* this must be kept in sync with the header */ +static const char *encodings[] = { + "", + "7bit", + "8bit", + "base64", + "quoted-printable", + "binary", + "x-uuencode", +}; + +const char * +camel_transfer_encoding_to_string (CamelTransferEncoding encoding) +{ + if (encoding >= sizeof (encodings) / sizeof (encodings[0])) + encoding = 0; + + return encodings[encoding]; +} + +CamelTransferEncoding +camel_transfer_encoding_from_string (const char *string) +{ + int i; + + if (string != NULL) { + for (i = 0; i < sizeof (encodings) / sizeof (encodings[0]); i++) + if (!strcasecmp (string, encodings[i])) + return i; + } + + return CAMEL_TRANSFER_ENCODING_DEFAULT; +} + +void +camel_header_mime_decode(const char *in, int *maj, int *min) +{ + const char *inptr = in; + int major=-1, minor=-1; + + d(printf("decoding MIME-Version: '%s'\n", in)); + + if (in != NULL) { + header_decode_lwsp(&inptr); + if (isdigit(*inptr)) { + major = camel_header_decode_int(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == '.') { + inptr++; + header_decode_lwsp(&inptr); + if (isdigit(*inptr)) + minor = camel_header_decode_int(&inptr); + } + } + } + + if (maj) + *maj = major; + if (min) + *min = minor; + + d(printf("major = %d, minor = %d\n", major, minor)); +} + +struct _rfc2184_param { + struct _camel_header_param param; + int index; +}; + +static int +rfc2184_param_cmp(const void *ap, const void *bp) +{ + const struct _rfc2184_param *a = *(void **)ap; + const struct _rfc2184_param *b = *(void **)bp; + int res; + + res = strcmp(a->param.name, b->param.name); + if (res == 0) { + if (a->index > b->index) + res = 1; + else if (a->index < b->index) + res = -1; + } + + return res; +} + +/* NB: Steals name and value */ +static struct _camel_header_param * +header_append_param(struct _camel_header_param *last, char *name, char *value) +{ + struct _camel_header_param *node; + + /* This handles - + 8 bit data in parameters, illegal, tries to convert using locale, or just safens it up. + rfc2047 ecoded parameters, illegal, decodes them anyway. Some Outlook & Mozilla do this? + */ + node = g_malloc(sizeof(*node)); + last->next = node; + node->next = NULL; + node->name = name; + if (strncmp(value, "=?", 2) == 0 + && (node->value = header_decode_text(value, strlen(value), FALSE, NULL))) { + g_free(value); + } else if (!g_utf8_validate(value, -1, NULL)) { + const char * charset = e_iconv_locale_charset(); + + if ((node->value = header_convert("UTF-8", charset?charset:"ISO-8859-1", value, strlen(value)))) { + g_free(value); + } else { + node->value = value; + for (;*value;value++) + if (!isascii((unsigned char)*value)) + *value = '_'; + } + } else + node->value = value; + + return node; +} + +static struct _camel_header_param * +header_decode_param_list (const char **in) +{ + struct _camel_header_param *head = NULL, *last = (struct _camel_header_param *)&head; + GPtrArray *split = NULL; + const char *inptr = *in; + struct _rfc2184_param *work; + char *tmp; + + /* Dump parameters into the output list, in the order found. RFC 2184 split parameters are kept in an array */ + header_decode_lwsp(&inptr); + while (*inptr == ';') { + char *name; + char *value = NULL; + + inptr++; + name = decode_token(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == '=') { + inptr++; + value = header_decode_value(&inptr); + } + + if (name && value) { + char *index = strchr(name, '*'); + + if (index) { + if (index[1] == 0) { + /* VAL*="foo", decode immediately and append */ + *index = 0; + tmp = rfc2184_decode(value, strlen(value)); + if (tmp) { + g_free(value); + value = tmp; + } + last = header_append_param(last, name, value); + } else { + /* VAL*1="foo", save for later */ + *index++ = 0; + work = g_malloc(sizeof(*work)); + work->param.name = name; + work->param.value = value; + work->index = atoi(index); + if (split == NULL) + split = g_ptr_array_new(); + g_ptr_array_add(split, work); + } + } else { + last = header_append_param(last, name, value); + } + } else { + g_free(name); + g_free(value); + } + + header_decode_lwsp(&inptr); + } + + /* Rejoin any RFC 2184 split parameters in the proper order */ + /* Parameters with the same index will be concatenated in undefined order */ + if (split) { + GString *value = g_string_new(""); + struct _rfc2184_param *first; + int i; + + qsort(split->pdata, split->len, sizeof(split->pdata[0]), rfc2184_param_cmp); + first = split->pdata[0]; + for (i=0;i<split->len;i++) { + work = split->pdata[i]; + if (split->len-1 == i) + g_string_append(value, work->param.value); + if (split->len-1 == i || strcmp(work->param.name, first->param.name) != 0) { + tmp = rfc2184_decode(value->str, value->len); + if (tmp == NULL) + tmp = g_strdup(value->str); + + last = header_append_param(last, g_strdup(first->param.name), tmp); + g_string_truncate(value, 0); + first = work; + } + if (split->len-1 != i) + g_string_append(value, work->param.value); + } + g_string_free(value, TRUE); + for (i=0;i<split->len;i++) { + work = split->pdata[i]; + g_free(work->param.name); + g_free(work->param.value); + g_free(work); + } + g_ptr_array_free(split, TRUE); + } + + *in = inptr; + + return head; +} + +struct _camel_header_param * +camel_header_param_list_decode(const char *in) +{ + if (in == NULL) + return NULL; + + return header_decode_param_list(&in); +} + +static char * +header_encode_param (const unsigned char *in, gboolean *encoded) +{ + const unsigned char *inptr = in; + unsigned char *outbuf = NULL; + const char *charset; + int encoding; + GString *out; + guint32 c; + + *encoded = FALSE; + + g_return_val_if_fail (in != NULL, NULL); + + /* do a quick us-ascii check (the common case?) */ + while (*inptr) { + if (*inptr > 127) + break; + inptr++; + } + + if (*inptr == '\0') + return g_strdup (in); + + inptr = in; + encoding = 0; + while ( encoding !=2 && (c = camel_utf8_getc(&inptr)) ) { + if (c > 127 && c < 256) + encoding = MAX (encoding, 1); + else if (c >= 256) + encoding = MAX (encoding, 2); + } + + if (encoding == 2) + charset = camel_charset_best(in, strlen(in)); + else + charset = "iso-8859-1"; + + if (g_ascii_strcasecmp(charset, "UTF-8") != 0 + && (outbuf = header_convert(charset, "UTF-8", in, strlen(in)))) { + inptr = outbuf; + } else { + charset = "UTF-8"; + inptr = in; + } + + /* FIXME: set the 'language' as well, assuming we can get that info...? */ + out = g_string_new (charset); + g_string_append(out, "''"); + + while ( (c = *inptr++) ) { + if (camel_mime_is_attrchar(c)) + g_string_append_c (out, c); + else + g_string_append_printf (out, "%%%c%c", tohex[(c >> 4) & 0xf], tohex[c & 0xf]); + } + g_free (outbuf); + + outbuf = out->str; + g_string_free (out, FALSE); + *encoded = TRUE; + + return outbuf; +} + +void +camel_header_param_list_format_append (GString *out, struct _camel_header_param *p) +{ + int used = out->len; + + while (p) { + gboolean encoded = FALSE; + gboolean quote = FALSE; + int here = out->len; + size_t nlen, vlen; + char *value; + + if (!p->value) { + p = p->next; + continue; + } + + value = header_encode_param (p->value, &encoded); + if (!value) { + w(g_warning ("appending parameter %s=%s violates rfc2184", p->name, p->value)); + value = g_strdup (p->value); + } + + if (!encoded) { + char *ch; + + for (ch = value; *ch; ch++) { + if (camel_mime_is_tspecial (*ch) || camel_mime_is_lwsp (*ch)) + break; + } + + quote = ch && *ch; + } + + nlen = strlen (p->name); + vlen = strlen (value); + + if (used + nlen + vlen > CAMEL_FOLD_SIZE - 8) { + out = g_string_append (out, ";\n\t"); + here = out->len; + used = 0; + } else + out = g_string_append (out, "; "); + + if (nlen + vlen > CAMEL_FOLD_SIZE - 8) { + /* we need to do special rfc2184 parameter wrapping */ + int maxlen = CAMEL_FOLD_SIZE - (nlen + 8); + char *inptr, *inend; + int i = 0; + + inptr = value; + inend = value + vlen; + + while (inptr < inend) { + char *ptr = inptr + MIN (inend - inptr, maxlen); + + if (encoded && ptr < inend) { + /* be careful not to break an encoded char (ie %20) */ + char *q = ptr; + int j = 2; + + for ( ; j > 0 && q > inptr && *q != '%'; j--, q--); + if (*q == '%') + ptr = q; + } + + if (i != 0) { + g_string_append (out, ";\n\t"); + here = out->len; + used = 0; + } + + g_string_append_printf (out, "%s*%d%s=", p->name, i++, encoded ? "*" : ""); + if (encoded || !quote) + g_string_append_len (out, inptr, ptr - inptr); + else + quote_word (out, TRUE, inptr, ptr - inptr); + + d(printf ("wrote: %s\n", out->str + here)); + + used += (out->len - here); + + inptr = ptr; + } + } else { + g_string_append_printf (out, "%s%s=", p->name, encoded ? "*" : ""); + + if (encoded || !quote) + g_string_append (out, value); + else + quote_word (out, TRUE, value, vlen); + + used += (out->len - here); + } + + g_free (value); + + p = p->next; + } +} + +char * +camel_header_param_list_format(struct _camel_header_param *p) +{ + GString *out = g_string_new(""); + char *ret; + + camel_header_param_list_format_append(out, p); + ret = out->str; + g_string_free(out, FALSE); + return ret; +} + +CamelContentType * +camel_content_type_decode(const char *in) +{ + const char *inptr = in; + char *type, *subtype = NULL; + CamelContentType *t = NULL; + + if (in==NULL) + return NULL; + + type = decode_token(&inptr); + header_decode_lwsp(&inptr); + if (type) { + if (*inptr == '/') { + inptr++; + subtype = decode_token(&inptr); + } + if (subtype == NULL && (!strcasecmp(type, "text"))) { + w(g_warning("text type with no subtype, resorting to text/plain: %s", in)); + subtype = g_strdup("plain"); + } + if (subtype == NULL) { + w(g_warning("MIME type with no subtype: %s", in)); + } + + t = camel_content_type_new(type, subtype); + t->params = header_decode_param_list(&inptr); + g_free(type); + g_free(subtype); + } else { + g_free(type); + d(printf("cannot find MIME type in header (2) '%s'", in)); + } + return t; +} + +void +camel_content_type_dump(CamelContentType *ct) +{ + struct _camel_header_param *p; + + printf("Content-Type: "); + if (ct==NULL) { + printf("<NULL>\n"); + return; + } + printf("%s / %s", ct->type, ct->subtype); + p = ct->params; + if (p) { + while (p) { + printf(";\n\t%s=\"%s\"", p->name, p->value); + p = p->next; + } + } + printf("\n"); +} + +char * +camel_content_type_format (CamelContentType *ct) +{ + GString *out; + char *ret; + + if (ct == NULL) + return NULL; + + out = g_string_new (""); + if (ct->type == NULL) { + g_string_append_printf (out, "text/plain"); + w(g_warning ("Content-Type with no main type")); + } else if (ct->subtype == NULL) { + w(g_warning ("Content-Type with no sub type: %s", ct->type)); + if (!strcasecmp (ct->type, "multipart")) + g_string_append_printf (out, "%s/mixed", ct->type); + else + g_string_append_printf (out, "%s", ct->type); + } else { + g_string_append_printf (out, "%s/%s", ct->type, ct->subtype); + } + camel_header_param_list_format_append (out, ct->params); + + ret = out->str; + g_string_free (out, FALSE); + + return ret; +} + +char * +camel_content_type_simple (CamelContentType *ct) +{ + if (ct->type == NULL) { + w(g_warning ("Content-Type with no main type")); + return g_strdup ("text/plain"); + } else if (ct->subtype == NULL) { + w(g_warning ("Content-Type with no sub type: %s", ct->type)); + if (!strcasecmp (ct->type, "multipart")) + return g_strdup_printf ("%s/mixed", ct->type); + else + return g_strdup (ct->type); + } else + return g_strdup_printf ("%s/%s", ct->type, ct->subtype); +} + +char * +camel_content_transfer_encoding_decode (const char *in) +{ + if (in) + return decode_token (&in); + + return NULL; +} + +CamelContentDisposition * +camel_content_disposition_decode(const char *in) +{ + CamelContentDisposition *d = NULL; + const char *inptr = in; + + if (in == NULL) + return NULL; + + d = g_malloc(sizeof(*d)); + d->refcount = 1; + d->disposition = decode_token(&inptr); + if (d->disposition == NULL) + w(g_warning("Empty disposition type")); + d->params = header_decode_param_list(&inptr); + return d; +} + +void +camel_content_disposition_ref(CamelContentDisposition *d) +{ + if (d) + d->refcount++; +} + +void +camel_content_disposition_unref(CamelContentDisposition *d) +{ + if (d) { + if (d->refcount<=1) { + camel_header_param_list_free(d->params); + g_free(d->disposition); + g_free(d); + } else { + d->refcount--; + } + } +} + +char * +camel_content_disposition_format(CamelContentDisposition *d) +{ + GString *out; + char *ret; + + if (d==NULL) + return NULL; + + out = g_string_new(""); + if (d->disposition) + out = g_string_append(out, d->disposition); + else + out = g_string_append(out, "attachment"); + camel_header_param_list_format_append(out, d->params); + + ret = out->str; + g_string_free(out, FALSE); + return ret; +} + +/* hrm, is there a library for this shit? */ +static struct { + char *name; + int offset; +} tz_offsets [] = { + { "UT", 0 }, + { "GMT", 0 }, + { "EST", -500 }, /* these are all US timezones. bloody yanks */ + { "EDT", -400 }, + { "CST", -600 }, + { "CDT", -500 }, + { "MST", -700 }, + { "MDT", -600 }, + { "PST", -800 }, + { "PDT", -700 }, + { "Z", 0 }, + { "A", -100 }, + { "M", -1200 }, + { "N", 100 }, + { "Y", 1200 }, +}; + +static char *tz_months [] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static char *tz_days [] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +char * +camel_header_format_date(time_t time, int offset) +{ + struct tm tm; + + d(printf("offset = %d\n", offset)); + + d(printf("converting date %s", ctime(&time))); + + time += ((offset / 100) * (60*60)) + (offset % 100)*60; + + d(printf("converting date %s", ctime(&time))); + + gmtime_r (&time, &tm); + + return g_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %+05d", + tz_days[tm.tm_wday], + tm.tm_mday, tz_months[tm.tm_mon], + tm.tm_year + 1900, + tm.tm_hour, tm.tm_min, tm.tm_sec, + offset); +} + +/* convert a date to time_t representation */ +/* this is an awful mess oh well */ +time_t +camel_header_decode_date(const char *in, int *saveoffset) +{ + const char *inptr = in; + char *monthname; + gboolean foundmonth; + int year, offset = 0; + struct tm tm; + int i; + time_t t; + + if (in == NULL) { + if (saveoffset) + *saveoffset = 0; + return 0; + } + + d(printf ("\ndecoding date '%s'\n", inptr)); + + memset (&tm, 0, sizeof(tm)); + + header_decode_lwsp (&inptr); + if (!isdigit (*inptr)) { + char *day = decode_token (&inptr); + /* we dont really care about the day, it's only for display */ + if (day) { + d(printf ("got day: %s\n", day)); + g_free (day); + header_decode_lwsp (&inptr); + if (*inptr == ',') { + inptr++; + } else { +#ifndef CLEAN_DATE + return parse_broken_date (in, saveoffset); +#else + if (saveoffset) + *saveoffset = 0; + return 0; +#endif /* ! CLEAN_DATE */ + } + } + } + tm.tm_mday = camel_header_decode_int(&inptr); +#ifndef CLEAN_DATE + if (tm.tm_mday == 0) { + return parse_broken_date (in, saveoffset); + } +#endif /* ! CLEAN_DATE */ + + monthname = decode_token(&inptr); + foundmonth = FALSE; + if (monthname) { + for (i=0;i<sizeof(tz_months)/sizeof(tz_months[0]);i++) { + if (!strcasecmp(tz_months[i], monthname)) { + tm.tm_mon = i; + foundmonth = TRUE; + break; + } + } + g_free(monthname); + } +#ifndef CLEAN_DATE + if (!foundmonth) { + return parse_broken_date (in, saveoffset); + } +#endif /* ! CLEAN_DATE */ + + year = camel_header_decode_int(&inptr); + if (year < 69) { + tm.tm_year = 100 + year; + } else if (year < 100) { + tm.tm_year = year; + } else if (year >= 100 && year < 1900) { + tm.tm_year = year; + } else { + tm.tm_year = year - 1900; + } + /* get the time ... yurck */ + tm.tm_hour = camel_header_decode_int(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == ':') + inptr++; + tm.tm_min = camel_header_decode_int(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == ':') + inptr++; + tm.tm_sec = camel_header_decode_int(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == '+' + || *inptr == '-') { + offset = (*inptr++)=='-'?-1:1; + offset = offset * camel_header_decode_int(&inptr); + d(printf("abs signed offset = %d\n", offset)); + if (offset < -1200 || offset > 1400) + offset = 0; + } else if (isdigit(*inptr)) { + offset = camel_header_decode_int(&inptr); + d(printf("abs offset = %d\n", offset)); + if (offset < -1200 || offset > 1400) + offset = 0; + } else { + char *tz = decode_token(&inptr); + + if (tz) { + for (i=0;i<sizeof(tz_offsets)/sizeof(tz_offsets[0]);i++) { + if (!strcasecmp(tz_offsets[i].name, tz)) { + offset = tz_offsets[i].offset; + break; + } + } + g_free(tz); + } + /* some broken mailers seem to put in things like GMT+1030 instead of just +1030 */ + header_decode_lwsp(&inptr); + if (*inptr == '+' || *inptr == '-') { + int sign = (*inptr++)=='-'?-1:1; + offset = offset + (camel_header_decode_int(&inptr)*sign); + } + d(printf("named offset = %d\n", offset)); + } + + t = e_mktime_utc(&tm); + + /* t is now GMT of the time we want, but not offset by the timezone ... */ + + d(printf(" gmt normalized? = %s\n", ctime(&t))); + + /* this should convert the time to the GMT equiv time */ + t -= ( (offset/100) * 60*60) + (offset % 100)*60; + + d(printf(" gmt normalized for timezone? = %s\n", ctime(&t))); + + d({ + char *tmp; + tmp = camel_header_format_date(t, offset); + printf(" encoded again: %s\n", tmp); + g_free(tmp); + }); + + if (saveoffset) + *saveoffset = offset; + + return t; +} + +char * +camel_header_location_decode(const char *in) +{ + int quote = 0; + GString *out = g_string_new(""); + char c, *res; + + /* Sigh. RFC2557 says: + * content-location = "Content-Location:" [CFWS] URI [CFWS] + * where URI is restricted to the syntax for URLs as + * defined in Uniform Resource Locators [URL] until + * IETF specifies other kinds of URIs. + * + * But Netscape puts quotes around the URI when sending web + * pages. + * + * Which is required as defined in rfc2017 [3.1]. Although + * outlook doesn't do this. + * + * Since we get headers already unfolded, we need just drop + * all whitespace. URL's cannot contain whitespace or quoted + * characters, even when included in quotes. + */ + + header_decode_lwsp(&in); + if (*in == '"') { + in++; + quote = 1; + } + + while ( (c = *in++) ) { + if (quote && c=='"') + break; + if (!camel_mime_is_lwsp(c)) + g_string_append_c(out, c); + } + + res = g_strdup(out->str); + g_string_free(out, TRUE); + + return res; +} + +/* extra rfc checks */ +#define CHECKS + +#ifdef CHECKS +static void +check_header(struct _camel_header_raw *h) +{ + unsigned char *p; + + p = h->value; + while (p && *p) { + if (!isascii(*p)) { + w(g_warning("Appending header violates rfc: %s: %s", h->name, h->value)); + return; + } + p++; + } +} +#endif + +void +camel_header_raw_append_parse(struct _camel_header_raw **list, const char *header, int offset) +{ + register const char *in; + size_t fieldlen; + char *name; + + in = header; + while (camel_mime_is_fieldname(*in) || *in==':') + in++; + fieldlen = in-header-1; + while (camel_mime_is_lwsp(*in)) + in++; + if (fieldlen == 0 || header[fieldlen] != ':') { + printf("Invalid header line: '%s'\n", header); + return; + } + name = g_alloca (fieldlen + 1); + memcpy(name, header, fieldlen); + name[fieldlen] = 0; + + camel_header_raw_append(list, name, in, offset); +} + +void +camel_header_raw_append(struct _camel_header_raw **list, const char *name, const char *value, int offset) +{ + struct _camel_header_raw *l, *n; + + d(printf("Header: %s: %s\n", name, value)); + + n = g_malloc(sizeof(*n)); + n->next = NULL; + n->name = g_strdup(name); + n->value = g_strdup(value); + n->offset = offset; +#ifdef CHECKS + check_header(n); +#endif + l = (struct _camel_header_raw *)list; + while (l->next) { + l = l->next; + } + l->next = n; + + /* debug */ +#if 0 + if (!strcasecmp(name, "To")) { + printf("- Decoding To\n"); + camel_header_to_decode(value); + } else if (!strcasecmp(name, "Content-type")) { + printf("- Decoding content-type\n"); + camel_content_type_dump(camel_content_type_decode(value)); + } else if (!strcasecmp(name, "MIME-Version")) { + printf("- Decoding mime version\n"); + camel_header_mime_decode(value); + } +#endif +} + +static struct _camel_header_raw * +header_raw_find_node(struct _camel_header_raw **list, const char *name) +{ + struct _camel_header_raw *l; + + l = *list; + while (l) { + if (!strcasecmp(l->name, name)) + break; + l = l->next; + } + return l; +} + +const char * +camel_header_raw_find(struct _camel_header_raw **list, const char *name, int *offset) +{ + struct _camel_header_raw *l; + + l = header_raw_find_node(list, name); + if (l) { + if (offset) + *offset = l->offset; + return l->value; + } else + return NULL; +} + +const char * +camel_header_raw_find_next(struct _camel_header_raw **list, const char *name, int *offset, const char *last) +{ + struct _camel_header_raw *l; + + if (last == NULL || name == NULL) + return NULL; + + l = *list; + while (l && l->value != last) + l = l->next; + return camel_header_raw_find(&l, name, offset); +} + +static void +header_raw_free(struct _camel_header_raw *l) +{ + g_free(l->name); + g_free(l->value); + g_free(l); +} + +void +camel_header_raw_remove(struct _camel_header_raw **list, const char *name) +{ + struct _camel_header_raw *l, *p; + + /* the next pointer is at the head of the structure, so this is safe */ + p = (struct _camel_header_raw *)list; + l = *list; + while (l) { + if (!strcasecmp(l->name, name)) { + p->next = l->next; + header_raw_free(l); + l = p->next; + } else { + p = l; + l = l->next; + } + } +} + +void +camel_header_raw_replace(struct _camel_header_raw **list, const char *name, const char *value, int offset) +{ + camel_header_raw_remove(list, name); + camel_header_raw_append(list, name, value, offset); +} + +void +camel_header_raw_clear(struct _camel_header_raw **list) +{ + struct _camel_header_raw *l, *n; + l = *list; + while (l) { + n = l->next; + header_raw_free(l); + l = n; + } + *list = NULL; +} + +char * +camel_header_msgid_generate (void) +{ + static pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER; +#define COUNT_LOCK() pthread_mutex_lock (&count_lock) +#define COUNT_UNLOCK() pthread_mutex_unlock (&count_lock) + char host[MAXHOSTNAMELEN]; + char *name; + static int count = 0; + char *msgid; + int retval; + struct addrinfo *ai = NULL, hints = { 0 }; + + retval = gethostname (host, sizeof (host)); + if (retval == 0 && *host) { + hints.ai_flags = AI_CANONNAME; + ai = camel_getaddrinfo(host, NULL, &hints, NULL); + if (ai && ai->ai_canonname) + name = ai->ai_canonname; + else + name = host; + } else + name = "localhost.localdomain"; + + COUNT_LOCK (); + msgid = g_strdup_printf ("%d.%d.%d.camel@%s", (int) time (NULL), getpid (), count++, name); + COUNT_UNLOCK (); + + if (ai) + camel_freeaddrinfo(ai); + + return msgid; +} + + +static struct { + char *name; + char *pattern; + regex_t regex; +} mail_list_magic[] = { + /* List-Post: <mailto:gnome-hackers@gnome.org> */ + /* List-Post: <mailto:gnome-hackers> */ + { "List-Post", "[ \t]*<mailto:([^@>]+)@?([^ \n\t\r>]*)" }, + /* List-Id: GNOME stuff <gnome-hackers.gnome.org> */ + /* List-Id: <gnome-hackers.gnome.org> */ + /* List-Id: <gnome-hackers> */ + /* This old one wasn't very useful: { "List-Id", " *([^<]+)" },*/ + { "List-Id", "[^<]*<([^\\.>]+)\\.?([^ \n\t\r>]*)" }, + /* Mailing-List: list gnome-hackers@gnome.org; contact gnome-hackers-owner@gnome.org */ + { "Mailing-List", "[ \t]*list ([^@]+)@?([^ \n\t\r>;]*)" }, + /* Originator: gnome-hackers@gnome.org */ + { "Originator", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" }, + /* X-Mailing-List: <gnome-hackers@gnome.org> arcive/latest/100 */ + /* X-Mailing-List: gnome-hackers@gnome.org */ + /* X-Mailing-List: gnome-hackers */ + /* X-Mailing-List: <gnome-hackers> */ + { "X-Mailing-List", "[ \t]*<?([^@>]+)@?([^ \n\t\r>]*)" }, + /* X-Loop: gnome-hackers@gnome.org */ + { "X-Loop", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" }, + /* X-List: gnome-hackers */ + /* X-List: gnome-hackers@gnome.org */ + { "X-List", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" }, + /* Sender: owner-gnome-hackers@gnome.org */ + /* Sender: owner-gnome-hacekrs */ + { "Sender", "[ \t]*owner-([^@]+)@?([^ @\n\t\r>]*)" }, + /* Sender: gnome-hackers-owner@gnome.org */ + /* Sender: gnome-hackers-owner */ + { "Sender", "[ \t]*([^@]+)-owner@?([^ @\n\t\r>]*)" }, + /* Delivered-To: mailing list gnome-hackers@gnome.org */ + /* Delivered-To: mailing list gnome-hackers */ + { "Delivered-To", "[ \t]*mailing list ([^@]+)@?([^ \n\t\r>]*)" }, + /* Sender: owner-gnome-hackers@gnome.org */ + /* Sender: <owner-gnome-hackers@gnome.org> */ + /* Sender: owner-gnome-hackers */ + /* Sender: <owner-gnome-hackers> */ + { "Return-Path", "[ \t]*<?owner-([^@>]+)@?([^ \n\t\r>]*)" }, + /* X-BeenThere: gnome-hackers@gnome.org */ + /* X-BeenThere: gnome-hackers */ + { "X-BeenThere", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" }, + /* List-Unsubscribe: <mailto:gnome-hackers-unsubscribe@gnome.org> */ + { "List-Unsubscribe", "<mailto:(.+)-unsubscribe@([^ \n\t\r>]*)" }, +}; + +char * +camel_header_raw_check_mailing_list(struct _camel_header_raw **list) +{ + const char *v; + regmatch_t match[3]; + int i; + + for (i = 0; i < sizeof (mail_list_magic) / sizeof (mail_list_magic[0]); i++) { + v = camel_header_raw_find (list, mail_list_magic[i].name, NULL); + if (v != NULL && regexec (&mail_list_magic[i].regex, v, 3, match, 0) == 0 && match[1].rm_so != -1) { + char *list; + int len1, len2; + + len1 = match[1].rm_eo - match[1].rm_so; + len2 = match[2].rm_eo - match[2].rm_so; + + list = g_malloc(len1+len2+2); + memcpy(list, v + match[1].rm_so, len1); + if (len2) { + list[len1] = '@'; + memcpy(list+len1+1, v+match[2].rm_so, len2); + list[len1+len2+1]=0; + } else { + list[len1] = 0; + } + + return list; + } + } + + return NULL; +} + +/* ok, here's the address stuff, what a mess ... */ +struct _camel_header_address * +camel_header_address_new (void) +{ + struct _camel_header_address *h; + h = g_malloc0(sizeof(*h)); + h->type = CAMEL_HEADER_ADDRESS_NONE; + h->refcount = 1; + return h; +} + +struct _camel_header_address * +camel_header_address_new_name(const char *name, const char *addr) +{ + struct _camel_header_address *h; + h = camel_header_address_new(); + h->type = CAMEL_HEADER_ADDRESS_NAME; + h->name = g_strdup(name); + h->v.addr = g_strdup(addr); + return h; +} + +struct _camel_header_address * +camel_header_address_new_group (const char *name) +{ + struct _camel_header_address *h; + + h = camel_header_address_new(); + h->type = CAMEL_HEADER_ADDRESS_GROUP; + h->name = g_strdup(name); + return h; +} + +void +camel_header_address_ref(struct _camel_header_address *h) +{ + if (h) + h->refcount++; +} + +void +camel_header_address_unref(struct _camel_header_address *h) +{ + if (h) { + if (h->refcount <= 1) { + if (h->type == CAMEL_HEADER_ADDRESS_GROUP) { + camel_header_address_list_clear(&h->v.members); + } else if (h->type == CAMEL_HEADER_ADDRESS_NAME) { + g_free(h->v.addr); + } + g_free(h->name); + g_free(h); + } else { + h->refcount--; + } + } +} + +void +camel_header_address_set_name(struct _camel_header_address *h, const char *name) +{ + if (h) { + g_free(h->name); + h->name = g_strdup(name); + } +} + +void +camel_header_address_set_addr(struct _camel_header_address *h, const char *addr) +{ + if (h) { + if (h->type == CAMEL_HEADER_ADDRESS_NAME + || h->type == CAMEL_HEADER_ADDRESS_NONE) { + h->type = CAMEL_HEADER_ADDRESS_NAME; + g_free(h->v.addr); + h->v.addr = g_strdup(addr); + } else { + g_warning("Trying to set the address on a group"); + } + } +} + +void +camel_header_address_set_members(struct _camel_header_address *h, struct _camel_header_address *group) +{ + if (h) { + if (h->type == CAMEL_HEADER_ADDRESS_GROUP + || h->type == CAMEL_HEADER_ADDRESS_NONE) { + h->type = CAMEL_HEADER_ADDRESS_GROUP; + camel_header_address_list_clear(&h->v.members); + /* should this ref them? */ + h->v.members = group; + } else { + g_warning("Trying to set the members on a name, not group"); + } + } +} + +void +camel_header_address_add_member(struct _camel_header_address *h, struct _camel_header_address *member) +{ + if (h) { + if (h->type == CAMEL_HEADER_ADDRESS_GROUP + || h->type == CAMEL_HEADER_ADDRESS_NONE) { + h->type = CAMEL_HEADER_ADDRESS_GROUP; + camel_header_address_list_append(&h->v.members, member); + } + } +} + +void +camel_header_address_list_append_list(struct _camel_header_address **l, struct _camel_header_address **h) +{ + if (l) { + struct _camel_header_address *n = (struct _camel_header_address *)l; + + while (n->next) + n = n->next; + n->next = *h; + } +} + + +void +camel_header_address_list_append(struct _camel_header_address **l, struct _camel_header_address *h) +{ + if (h) { + camel_header_address_list_append_list(l, &h); + h->next = NULL; + } +} + +void +camel_header_address_list_clear(struct _camel_header_address **l) +{ + struct _camel_header_address *a, *n; + a = *l; + while (a) { + n = a->next; + camel_header_address_unref(a); + a = n; + } + *l = NULL; +} + +/* if encode is true, then the result is suitable for mailing, otherwise + the result is suitable for display only (and may not even be re-parsable) */ +static void +header_address_list_encode_append (GString *out, int encode, struct _camel_header_address *a) +{ + char *text; + + while (a) { + switch (a->type) { + case CAMEL_HEADER_ADDRESS_NAME: + if (encode) + text = camel_header_encode_phrase (a->name); + else + text = a->name; + if (text && *text) + g_string_append_printf (out, "%s <%s>", text, a->v.addr); + else + g_string_append (out, a->v.addr); + if (encode) + g_free (text); + break; + case CAMEL_HEADER_ADDRESS_GROUP: + if (encode) + text = camel_header_encode_phrase (a->name); + else + text = a->name; + g_string_append_printf (out, "%s: ", text); + header_address_list_encode_append (out, encode, a->v.members); + g_string_append_printf (out, ";"); + if (encode) + g_free (text); + break; + default: + g_warning ("Invalid address type"); + break; + } + a = a->next; + if (a) + g_string_append (out, ", "); + } +} + +char * +camel_header_address_list_encode (struct _camel_header_address *a) +{ + GString *out; + char *ret; + + if (a == NULL) + return NULL; + + out = g_string_new (""); + header_address_list_encode_append (out, TRUE, a); + ret = out->str; + g_string_free (out, FALSE); + + return ret; +} + +char * +camel_header_address_list_format (struct _camel_header_address *a) +{ + GString *out; + char *ret; + + if (a == NULL) + return NULL; + + out = g_string_new (""); + + header_address_list_encode_append (out, FALSE, a); + ret = out->str; + g_string_free (out, FALSE); + + return ret; +} + +char * +camel_header_address_fold (const char *in, size_t headerlen) +{ + size_t len, outlen; + const char *inptr = in, *space, *p, *n; + GString *out; + char *ret; + int i, needunfold = FALSE; + + if (in == NULL) + return NULL; + + /* first, check to see if we even need to fold */ + len = headerlen + 2; + p = in; + while (*p) { + n = strchr (p, '\n'); + if (n == NULL) { + len += strlen (p); + break; + } + + needunfold = TRUE; + len += n-p; + + if (len >= CAMEL_FOLD_SIZE) + break; + len = 0; + p = n + 1; + } + if (len < CAMEL_FOLD_SIZE) + return g_strdup (in); + + /* we need to fold, so first unfold (if we need to), then process */ + if (needunfold) + inptr = in = camel_header_unfold (in); + + out = g_string_new (""); + outlen = headerlen + 2; + while (*inptr) { + space = strchr (inptr, ' '); + if (space) { + len = space - inptr + 1; + } else { + len = strlen (inptr); + } + + d(printf("next word '%.*s'\n", len, inptr)); + + if (outlen + len > CAMEL_FOLD_SIZE) { + d(printf("outlen = %d wordlen = %d\n", outlen, len)); + /* strip trailing space */ + if (out->len > 0 && out->str[out->len-1] == ' ') + g_string_truncate (out, out->len-1); + g_string_append (out, "\n\t"); + outlen = 1; + } + + outlen += len; + for (i = 0; i < len; i++) { + g_string_append_c (out, inptr[i]); + } + + inptr += len; + } + ret = out->str; + g_string_free (out, FALSE); + + if (needunfold) + g_free ((char *)in); + + return ret; +} + +/* simple header folding */ +/* will work even if the header is already folded */ +char * +camel_header_fold(const char *in, size_t headerlen) +{ + size_t len, outlen, i; + const char *inptr = in, *space, *p, *n; + GString *out; + char *ret; + int needunfold = FALSE; + + if (in == NULL) + return NULL; + + /* first, check to see if we even need to fold */ + len = headerlen + 2; + p = in; + while (*p) { + n = strchr(p, '\n'); + if (n == NULL) { + len += strlen (p); + break; + } + + needunfold = TRUE; + len += n-p; + + if (len >= CAMEL_FOLD_SIZE) + break; + len = 0; + p = n + 1; + } + if (len < CAMEL_FOLD_SIZE) + return g_strdup(in); + + /* we need to fold, so first unfold (if we need to), then process */ + if (needunfold) + inptr = in = camel_header_unfold(in); + + out = g_string_new(""); + outlen = headerlen+2; + while (*inptr) { + space = strchr(inptr, ' '); + if (space) { + len = space-inptr+1; + } else { + len = strlen(inptr); + } + d(printf("next word '%.*s'\n", len, inptr)); + if (outlen + len > CAMEL_FOLD_SIZE) { + d(printf("outlen = %d wordlen = %d\n", outlen, len)); + /* strip trailing space */ + if (out->len > 0 && out->str[out->len-1] == ' ') + g_string_truncate(out, out->len-1); + g_string_append(out, "\n\t"); + outlen = 1; + /* check for very long words, just cut them up */ + while (outlen+len > CAMEL_FOLD_MAX_SIZE) { + for (i=0;i<CAMEL_FOLD_MAX_SIZE-outlen;i++) + g_string_append_c(out, inptr[i]); + inptr += CAMEL_FOLD_MAX_SIZE-outlen; + len -= CAMEL_FOLD_MAX_SIZE-outlen; + g_string_append(out, "\n\t"); + outlen = 1; + } + } + outlen += len; + for (i=0;i<len;i++) { + g_string_append_c(out, inptr[i]); + } + inptr += len; + } + ret = out->str; + g_string_free(out, FALSE); + + if (needunfold) + g_free((char *)in); + + return ret; +} + +char * +camel_header_unfold(const char *in) +{ + char *out = g_malloc(strlen(in)+1); + const char *inptr = in; + char c, *o = out; + + o = out; + while ((c = *inptr++)) { + if (c == '\n') { + if (camel_mime_is_lwsp(*inptr)) { + do { + inptr++; + } while (camel_mime_is_lwsp(*inptr)); + *o++ = ' '; + } else { + *o++ = c; + } + } else { + *o++ = c; + } + } + *o = 0; + + return out; +} + +void +camel_mime_utils_init(void) +{ + int i, errcode, regex_compilation_failed=0; + + /* Init tables */ + header_decode_init(); + base64_init(); + + /* precompile regex's for speed at runtime */ + for (i = 0; i < G_N_ELEMENTS (mail_list_magic); i++) { + errcode = regcomp(&mail_list_magic[i].regex, mail_list_magic[i].pattern, REG_EXTENDED|REG_ICASE); + if (errcode != 0) { + char *errstr; + size_t len; + + len = regerror(errcode, &mail_list_magic[i].regex, NULL, 0); + errstr = g_malloc0(len + 1); + regerror(errcode, &mail_list_magic[i].regex, errstr, len); + + g_warning("Internal error, compiling regex failed: %s: %s", mail_list_magic[i].pattern, errstr); + g_free(errstr); + regex_compilation_failed++; + } + } + + g_assert(regex_compilation_failed == 0); +} + + +void +camel_mime_utils_shutdown (void) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (mail_list_magic); i++) + regfree (&mail_list_magic[i].regex); +} diff --git a/camel/camel-sasl-digest-md5.c b/camel/camel-sasl-digest-md5.c index e421bd529..91bbcda70 100644 --- a/camel/camel-sasl-digest-md5.c +++ b/camel/camel-sasl-digest-md5.c @@ -623,7 +623,7 @@ compute_response (struct _DigestResponse *resp, const char *passwd, gboolean cli } static struct _DigestResponse * -generate_response (struct _DigestChallenge *challenge, struct hostent *host, +generate_response (struct _DigestChallenge *challenge, const char *host, const char *protocol, const char *user, const char *passwd) { struct _DigestResponse *resp; @@ -659,7 +659,7 @@ generate_response (struct _DigestChallenge *challenge, struct hostent *host, /* create the URI */ uri = g_new0 (struct _DigestURI, 1); uri->type = g_strdup (protocol); - uri->host = g_strdup (host->h_name); + uri->host = g_strdup (host); uri->name = NULL; resp->uri = uri; @@ -795,10 +795,10 @@ digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex) struct _param *rspauth; GByteArray *ret = NULL; gboolean abort = FALSE; - struct hostent *h; const char *ptr; guchar out[33]; char *tokens; + struct addrinfo *ai, hints; /* Need to wait for the server */ if (!token) @@ -829,12 +829,20 @@ digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex) "\"Quality of Protection\" token\n")); return NULL; } - - h = camel_service_gethost (sasl->service, ex); - priv->response = generate_response (priv->challenge, h, sasl->service_name, + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, NULL); + if (ai && ai->ai_canonname) + ptr = ai->ai_canonname; + else + ptr = "localhost.localdomain"; + + priv->response = generate_response (priv->challenge, ptr, sasl->service_name, sasl->service->url->user, sasl->service->url->passwd); - camel_free_host(h); + if (ai) + camel_freeaddrinfo(ai); ret = digest_response (priv->response); break; diff --git a/camel/camel-sasl-gssapi.c b/camel/camel-sasl-gssapi.c new file mode 100644 index 000000000..1efbefee1 --- /dev/null +++ b/camel/camel-sasl-gssapi.c @@ -0,0 +1,346 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 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 <config.h> +#endif + +#ifdef HAVE_KRB5 + +#include <string.h> +#ifdef HAVE_ET_COM_ERR_H +#include <et/com_err.h> +#else +#include <com_err.h> +#endif +#ifdef HAVE_MIT_KRB5 +#include <gssapi/gssapi.h> +#include <gssapi/gssapi_generic.h> +#else /* HAVE_HEIMDAL_KRB5 */ +#include <gssapi.h> +#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE +#endif +#include <errno.h> + +#ifndef GSS_C_OID_KRBV5_DES +#define GSS_C_OID_KRBV5_DES GSS_C_NO_OID +#endif + +#include "camel-sasl-gssapi.h" + +CamelServiceAuthType camel_sasl_gssapi_authtype = { + N_("GSSAPI"), + + N_("This option will connect to the server using " + "Kerberos 5 authentication."), + + "GSSAPI", + FALSE +}; + +enum { + GSSAPI_STATE_INIT, + GSSAPI_STATE_CONTINUE_NEEDED, + GSSAPI_STATE_COMPLETE, + GSSAPI_STATE_AUTHENTICATED +}; + +#define GSSAPI_SECURITY_LAYER_NONE (1 << 0) +#define GSSAPI_SECURITY_LAYER_INTEGRITY (1 << 1) +#define GSSAPI_SECURITY_LAYER_PRIVACY (1 << 2) + +#define DESIRED_SECURITY_LAYER GSSAPI_SECURITY_LAYER_NONE + +struct _CamelSaslGssapiPrivate { + int state; + gss_ctx_id_t ctx; + gss_name_t target; +}; + + +static GByteArray *gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex); + + +static CamelSaslClass *parent_class = NULL; + + +static void +camel_sasl_gssapi_class_init (CamelSaslGssapiClass *klass) +{ + CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (klass); + + parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ())); + + /* virtual method overload */ + camel_sasl_class->challenge = gssapi_challenge; +} + +static void +camel_sasl_gssapi_init (gpointer object, gpointer klass) +{ + CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object); + + gssapi->priv = g_new (struct _CamelSaslGssapiPrivate, 1); + gssapi->priv->state = GSSAPI_STATE_INIT; + gssapi->priv->ctx = GSS_C_NO_CONTEXT; + gssapi->priv->target = GSS_C_NO_NAME; +} + +static void +camel_sasl_gssapi_finalize (CamelObject *object) +{ + CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object); + guint32 status; + + if (gssapi->priv->ctx != GSS_C_NO_CONTEXT) + gss_delete_sec_context (&status, &gssapi->priv->ctx, GSS_C_NO_BUFFER); + + if (gssapi->priv->target != GSS_C_NO_NAME) + gss_release_name (&status, &gssapi->priv->target); + + g_free (gssapi->priv); +} + + +CamelType +camel_sasl_gssapi_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + type = camel_type_register ( + camel_sasl_get_type (), + "CamelSaslGssapi", + sizeof (CamelSaslGssapi), + sizeof (CamelSaslGssapiClass), + (CamelObjectClassInitFunc) camel_sasl_gssapi_class_init, + NULL, + (CamelObjectInitFunc) camel_sasl_gssapi_init, + (CamelObjectFinalizeFunc) camel_sasl_gssapi_finalize); + } + + return type; +} + +static void +gssapi_set_exception (OM_uint32 major, OM_uint32 minor, CamelException *ex) +{ + const char *str; + + switch (major) { + case GSS_S_BAD_MECH: + str = _("The specified mechanism is not supported by the " + "provided credential, or is unrecognized by the " + "implementation."); + break; + case GSS_S_BAD_NAME: + str = _("The provided target_name parameter was ill-formed."); + break; + case GSS_S_BAD_NAMETYPE: + str = _("The provided target_name parameter contained an " + "invalid or unsupported type of name."); + break; + case GSS_S_BAD_BINDINGS: + str = _("The input_token contains different channel " + "bindings to those specified via the " + "input_chan_bindings parameter."); + break; + case GSS_S_BAD_SIG: + str = _("The input_token contains an invalid signature, or a " + "signature that could not be verified."); + break; + case GSS_S_NO_CRED: + str = _("The supplied credentials were not valid for context " + "initiation, or the credential handle did not " + "reference any credentials."); + break; + case GSS_S_NO_CONTEXT: + str = _("The supplied context handle did not refer to a valid context."); + break; + case GSS_S_DEFECTIVE_TOKEN: + str = _("The consistency checks performed on the input_token failed."); + break; + case GSS_S_DEFECTIVE_CREDENTIAL: + str = _("The consistency checks performed on the credential failed."); + break; + case GSS_S_CREDENTIALS_EXPIRED: + str = _("The referenced credentials have expired."); + break; + case GSS_S_FAILURE: + str = error_message (minor); + break; + default: + str = _("Bad authentication response from server."); + } + + camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, str); +} + +static GByteArray * +gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex) +{ + struct _CamelSaslGssapiPrivate *priv = CAMEL_SASL_GSSAPI (sasl)->priv; + OM_uint32 major, minor, flags, time; + gss_buffer_desc inbuf, outbuf; + GByteArray *challenge = NULL; + gss_buffer_t input_token; + struct hostent *h; + int conf_state; + gss_qop_t qop; + gss_OID mech; + char *str; + struct addrinfo *ai, hints; + + switch (priv->state) { + case GSSAPI_STATE_INIT: + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, ex); + if (ai == NULL) + return NULL; + + str = g_strdup_printf("%s@%s", sasl->service_name, ai->ai_canonname); + camel_freeaddrinfo(ai); + + inbuf.value = str; + inbuf.length = strlen (str); + major = gss_import_name (&minor, &inbuf, gss_nt_service_name, &priv->target); + g_free (str); + + if (major != GSS_S_COMPLETE) { + gssapi_set_exception (major, minor, ex); + return NULL; + } + + input_token = GSS_C_NO_BUFFER; + + goto challenge; + break; + case GSSAPI_STATE_CONTINUE_NEEDED: + if (token == NULL) { + camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Bad authentication response from server.")); + return NULL; + } + + inbuf.value = token->data; + inbuf.length = token->len; + input_token = &inbuf; + + challenge: + major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &priv->ctx, priv->target, + GSS_C_OID_KRBV5_DES, GSS_C_MUTUAL_FLAG | + GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, + 0, GSS_C_NO_CHANNEL_BINDINGS, + input_token, &mech, &outbuf, &flags, &time); + + switch (major) { + case GSS_S_COMPLETE: + priv->state = GSSAPI_STATE_COMPLETE; + break; + case GSS_S_CONTINUE_NEEDED: + priv->state = GSSAPI_STATE_CONTINUE_NEEDED; + break; + default: + gssapi_set_exception (major, minor, ex); + return NULL; + } + + challenge = g_byte_array_new (); + g_byte_array_append (challenge, outbuf.value, outbuf.length); +#ifndef HAVE_HEIMDAL_KRB5 + gss_release_buffer (&minor, &outbuf); +#endif + break; + case GSSAPI_STATE_COMPLETE: + if (token == NULL) { + camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Bad authentication response from server.")); + return NULL; + } + + inbuf.value = token->data; + inbuf.length = token->len; + + major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop); + if (major != GSS_S_COMPLETE) { + gssapi_set_exception (major, minor, ex); + return NULL; + } + + if (outbuf.length < 4) { + camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Bad authentication response from server.")); +#ifndef HAVE_HEIMDAL_KRB5 + gss_release_buffer (&minor, &outbuf); +#endif + return NULL; + } + + /* check that our desired security layer is supported */ + if ((((unsigned char *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) { + camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Unsupported security layer.")); +#ifndef HAVE_HEIMDAL_KRB5 + gss_release_buffer (&minor, &outbuf); +#endif + return NULL; + } + + inbuf.length = 4 + strlen (sasl->service->url->user); + inbuf.value = str = g_malloc (inbuf.length); + memcpy (inbuf.value, outbuf.value, 4); + str[0] = DESIRED_SECURITY_LAYER; + memcpy (str + 4, sasl->service->url->user, inbuf.length - 4); + +#ifndef HAVE_HEIMDAL_KRB5 + gss_release_buffer (&minor, &outbuf); +#endif + + major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf); + if (major != GSS_S_COMPLETE) { + gssapi_set_exception (major, minor, ex); + g_free (str); + return NULL; + } + + g_free (str); + challenge = g_byte_array_new (); + g_byte_array_append (challenge, outbuf.value, outbuf.length); + +#ifndef HAVE_HEIMDAL_KRB5 + gss_release_buffer (&minor, &outbuf); +#endif + + priv->state = GSSAPI_STATE_AUTHENTICATED; + + sasl->authenticated = TRUE; + break; + default: + return NULL; + } + + return challenge; +} + +#endif /* HAVE_KRB5 */ diff --git a/camel/camel-sasl-kerberos4.c b/camel/camel-sasl-kerberos4.c index e225c1a26..fd366e61d 100644 --- a/camel/camel-sasl-kerberos4.c +++ b/camel/camel-sasl-kerberos4.c @@ -129,6 +129,7 @@ krb4_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex) KTEXT_ST authenticator; CREDENTIALS credentials; guint32 plus1; + struct addrinfo *ai, hints; /* Need to wait for the server */ if (!token) @@ -142,12 +143,17 @@ krb4_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex) memcpy (&priv->nonce_n, token->data, 4); priv->nonce_h = ntohl (priv->nonce_n); + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, ex); + if (ai == NULL) + goto lose; + /* Our response is an authenticator including that number. */ - h = camel_service_gethost (sasl->service, ex); - inst = g_strndup (h->h_name, strcspn (h->h_name, ".")); + inst = g_strndup (ai->ai_canonname, strcspn (ai->ai_canonname, ".")); camel_strdown (inst); - realm = g_strdup (krb_realmofhost (h->h_name)); - camel_free_host(h); + realm = g_strdup (krb_realmofhost (ai->ai_canonname)); + camel_freeaddrinfo(ai); status = krb_mk_req (&authenticator, sasl->service_name, inst, realm, priv->nonce_h); if (status == KSUCCESS) { status = krb_get_cred (sasl->service_name, inst, realm, &credentials); diff --git a/camel/camel-service.c b/camel/camel-service.c index d108f124c..31c39614f 100644 --- a/camel/camel-service.c +++ b/camel/camel-service.c @@ -34,6 +34,8 @@ #include <pthread.h> #include <errno.h> +#include <sys/poll.h> + #include "e-util/e-msgport.h" #include "e-util/e-host-utils.h" @@ -642,67 +644,226 @@ camel_service_query_auth_types (CamelService *service, CamelException *ex) return ret; } -/* URL utility routines */ - -/** - * camel_service_gethost: - * @service: a CamelService - * @ex: a CamelException - * - * This is a convenience function to do a gethostbyname on the host - * for the service's URL. - * - * Return value: a (statically-allocated) hostent. - **/ -struct hostent * -camel_service_gethost (CamelService *service, CamelException *ex) -{ - char *hostname; - - if (service->url->host) - hostname = service->url->host; - else - hostname = "localhost"; - - return camel_gethostbyname (hostname, ex); -} - -#ifdef offsetof -#define STRUCT_OFFSET(type, field) ((gint) offsetof (type, field)) -#else -#define STRUCT_OFFSET(type, field) ((gint) ((gchar*) &((type *) 0)->field)) -#endif - -struct _lookup_msg { +/* ********************************************************************** */ +struct _addrinfo_msg { EMsg msg; unsigned int cancelled:1; + + /* for host lookup */ const char *name; - int len; - int type; + const char *service; int result; - int herr; + const struct addrinfo *hints; + struct addrinfo **res; + + /* for host lookup emulation */ +#ifdef NEED_ADDRINFO struct hostent hostbuf; int hostbuflen; char *hostbufmem; +#endif + + /* for name lookup */ + const struct sockaddr *addr; + socklen_t addrlen; + char *host; + int hostlen; + char *serv; + int servlen; + int flags; }; +static void +cs_freeinfo(struct _addrinfo_msg *msg) +{ + g_free(msg->host); + g_free(msg->serv); +#ifdef NEED_ADDRINFO + g_free(msg->hostbufmem); +#endif + g_free(msg); +} + +/* returns -1 if cancelled */ +static int +cs_waitinfo(void *(worker)(void *), struct _addrinfo_msg *msg, const char *error, CamelException *ex) +{ + EMsgPort *reply_port; + pthread_t id; + int err, cancel_fd, cancel = 0, fd; + + cancel_fd = camel_operation_cancel_fd(NULL); + if (cancel_fd == -1) { + worker(msg); + return 0; + } + + reply_port = msg->msg.reply_port = e_msgport_new(); + fd = e_msgport_fd(msg->msg.reply_port); + if ((err = pthread_create(&id, NULL, worker, msg)) == 0) { + struct pollfd polls[2]; + int status; + + polls[0].fd = fd; + polls[0].events = POLLIN; + polls[1].fd = cancel_fd; + polls[1].events = POLLIN; + + d(printf("waiting for name return/cancellation in main process\n")); + do { + polls[0].revents = 0; + polls[1].revents = 0; + status = poll(polls, 2, -1); + } while (status == -1 && errno == EINTR); + + if (status == -1 || (polls[1].revents & POLLIN)) { + if (status == -1) + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", error, g_strerror(errno)); + else + camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); + + /* We cancel so if the thread impl is decent it causes immediate exit. + We detach so we dont need to wait for it to exit if it isn't. + We check the reply port incase we had a reply in the mean time, which we free later */ + d(printf("Cancelling lookup thread and leaving it\n")); + msg->cancelled = 1; + pthread_detach(id); + pthread_cancel(id); + cancel = 1; + } else { + struct _addrinfo_msg *reply = (struct _addrinfo_msg *)e_msgport_get(reply_port); + + g_assert(reply == msg); + d(printf("waiting for child to exit\n")); + pthread_join(id, NULL); + d(printf("child done\n")); + } + } else { + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s: %s", _("cannot create thread"), g_strerror(err)); + } + e_msgport_destroy(reply_port); + + return cancel; +} + +#ifdef NEED_ADDRINFO static void * -get_hostbyname(void *data) +cs_getaddrinfo(void *data) { - struct _lookup_msg *info = data; + struct _addrinfo_msg *msg = data; + int herr; + struct hostent h; + struct addrinfo *res, *last = NULL; + struct sockaddr_in *sin; + in_port_t port = 0; + int i; + + /* This is a pretty simplistic emulation of getaddrinfo */ - while ((info->result = e_gethostbyname_r(info->name, &info->hostbuf, info->hostbufmem, info->hostbuflen, &info->herr)) == ERANGE) { - d(printf("gethostbyname fialed?\n")); + while ((msg->result = e_gethostbyname_r(msg->name, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) { pthread_testcancel(); - info->hostbuflen *= 2; - info->hostbufmem = g_realloc(info->hostbufmem, info->hostbuflen); + msg->hostbuflen *= 2; + msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen); } - - d(printf("gethostbyname ok?\n")); /* If we got cancelled, dont reply, just free it */ + if (msg->cancelled) + goto cancel; + + /* FIXME: map error numbers across */ + if (msg->result != 0) + goto reply; + + /* check hints matched */ + if (msg->hints && msg->hints->ai_family && msg->hints->ai_family != h.h_addrtype) { + msg->result = EAI_FAMILY; + goto reply; + } + + /* we only support ipv4 for this interface, even if it could supply ipv6 */ + if (h.h_addrtype != AF_INET) { + msg->result = EAI_FAMILY; + goto reply; + } + + /* check service mapping */ + if (msg->service) { + const char *p = msg->service; + + while (*p) { + if (*p < '0' || *p > '9') + break; + p++; + } + + if (*p) { + const char *socktype = NULL; + struct servent *serv; + + if (msg->hints && msg->hints->ai_socktype) { + if (msg->hints->ai_socktype == SOCK_STREAM) + socktype = "tcp"; + else if (msg->hints->ai_socktype == SOCK_DGRAM) + socktype = "udp"; + } + + serv = getservbyname(msg->service, socktype); + if (serv == NULL) { + msg->result = EAI_NONAME; + goto reply; + } + port = serv->s_port; + } else { + port = htons(strtoul(msg->service, NULL, 10)); + } + } + + for (i=0;h.h_addr_list[i];i++) { + res = g_malloc0(sizeof(*res)); + if (msg->hints) { + res->ai_flags = msg->hints->ai_flags; + if (msg->hints->ai_flags & AI_CANONNAME) + res->ai_canonname = g_strdup(h.h_name); + res->ai_socktype = msg->hints->ai_socktype; + res->ai_protocol = msg->hints->ai_protocol; + } else { + res->ai_flags = 0; + res->ai_socktype = SOCK_STREAM; /* fudge */ + res->ai_protocol = 0; /* fudge */ + } + res->ai_family = AF_INET; + res->ai_addrlen = sizeof(*sin); + res->ai_addr = g_malloc(sizeof(*sin)); + sin = (struct sockaddr_in *)res->ai_addr; + sin->sin_family = AF_INET; + sin->sin_port = port; + memcpy(&sin->sin_addr, h.h_addr_list[i], sizeof(sin->sin_addr)); + + if (last == NULL) { + *msg->res = last = res; + } else { + last->ai_next = res; + last = res; + } + } +reply: + e_msgport_reply((EMsg *)msg); + return NULL; +cancel: + cs_freeinfo(msg); + return NULL; +} +#else +static void * +cs_getaddrinfo(void *data) +{ + struct _addrinfo_msg *info = data; + + do { + info->result = getaddrinfo(info->name, info->service, info->hints, info->res); + } while (info->result == EAI_AGAIN); + if (info->cancelled) { - g_free(info->hostbufmem); g_free(info); } else { e_msgport_reply((EMsg *)info); @@ -710,239 +871,186 @@ get_hostbyname(void *data) return NULL; } +#endif /* NEED_ADDRINFO */ -struct hostent * -camel_gethostbyname (const char *name, CamelException *exout) +struct addrinfo * +camel_getaddrinfo(const char *name, const char *service, const struct addrinfo *hints, CamelException *ex) { - int fdmax, status, fd, cancel_fd; - struct _lookup_msg *msg; - CamelException ex; - + struct _addrinfo_msg *msg; + struct addrinfo *res = NULL; +#ifndef ENABLE_IPv6 + struct addrinfo *myhints; +#endif g_return_val_if_fail(name != NULL, NULL); if (camel_operation_cancel_check(NULL)) { - camel_exception_set (exout, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); + camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); return NULL; } - camel_exception_init(&ex); camel_operation_start_transient(NULL, _("Resolving: %s"), name); - msg = g_malloc0(sizeof(*msg)); - msg->hostbuflen = 1024; - msg->hostbufmem = g_malloc(msg->hostbuflen); - msg->name = name; - msg->result = -1; - - cancel_fd = camel_operation_cancel_fd(NULL); - if (cancel_fd == -1) { - get_hostbyname(msg); - } else { - EMsgPort *reply_port; - pthread_t id; - fd_set rdset; - int err; - - reply_port = msg->msg.reply_port = e_msgport_new(); - fd = e_msgport_fd(msg->msg.reply_port); - if ((err = pthread_create(&id, NULL, get_hostbyname, msg)) == 0) { - d(printf("waiting for name return/cancellation in main process\n")); - do { - FD_ZERO(&rdset); - FD_SET(cancel_fd, &rdset); - FD_SET(fd, &rdset); - fdmax = MAX(fd, cancel_fd) + 1; - status = select(fdmax, &rdset, NULL, 0, NULL); - } while (status == -1 && errno == EINTR); - - if (status == -1 || FD_ISSET(cancel_fd, &rdset)) { - if (status == -1) - camel_exception_setv(&ex, CAMEL_EXCEPTION_SYSTEM, _("Failure in name lookup: %s"), g_strerror(errno)); - else - camel_exception_setv(&ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); - - /* We cancel so if the thread impl is decent it causes immediate exit. - We detach so we dont need to wait for it to exit if it isn't. - We check the reply port incase we had a reply in the mean time, which we free later */ - d(printf("Cancelling lookup thread and leaving it\n")); - msg->cancelled = 1; - pthread_detach(id); - pthread_cancel(id); - msg = (struct _lookup_msg *)e_msgport_get(reply_port); - } else { - struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port); - - g_assert(reply == msg); - d(printf("waiting for child to exit\n")); - pthread_join(id, NULL); - d(printf("child done\n")); - } - } else { - camel_exception_setv(&ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: cannot create thread: %s"), g_strerror(err)); - } - e_msgport_destroy(reply_port); + /* force ipv4 addresses only */ +#ifndef ENABLE_IPv6 + if (hints == NULL) { + memset(&myhints, 0, sizeof(myhints)); + hints = &myhints; } - - camel_operation_end(NULL); - - if (!camel_exception_is_set(&ex)) { - if (msg->result == 0) - return &msg->hostbuf; - if (msg->herr == HOST_NOT_FOUND || msg->herr == NO_DATA) - camel_exception_setv (&ex, CAMEL_EXCEPTION_SYSTEM, - _("Host lookup failed: %s: host not found"), name); - else - camel_exception_setv (&ex, CAMEL_EXCEPTION_SYSTEM, - _("Host lookup failed: %s: unknown reason"), name); - } + hints->ai_family = AF_INET; +#endif - if (msg) { - g_free(msg->hostbufmem); - g_free(msg); - } + msg = g_malloc0(sizeof(*msg)); + msg->name = name; + msg->service = service; + msg->hints = hints; + msg->res = &res; +#ifdef NEED_ADDRINFO + msg->hostbuflen = 1024; + msg->hostbufmem = g_malloc(msg->hostbuflen); +#endif + if (cs_waitinfo(cs_getaddrinfo, msg, _("Host lookup failed"), ex) == 0) + cs_freeinfo(msg); + else + res = NULL; - camel_exception_xfer(exout, &ex); + camel_operation_end(NULL); - return NULL; + return res; } -static void * -get_hostbyaddr (void *data) +void +camel_freeaddrinfo(struct addrinfo *host) { - struct _lookup_msg *info = data; - - while ((info->result = e_gethostbyaddr_r (info->name, info->len, info->type, &info->hostbuf, - info->hostbufmem, info->hostbuflen, &info->herr)) == ERANGE) { - d(printf ("gethostbyaddr fialed?\n")); - pthread_testcancel (); - info->hostbuflen *= 2; - info->hostbufmem = g_realloc (info->hostbufmem, info->hostbuflen); +#ifdef NEED_ADDRINFO + while (host) { + struct addrinfo *next = host->ai_next; + + g_free(host->ai_canonname); + g_free(host->ai_addr); + g_free(host); + host = next; } - - d(printf ("gethostbyaddr ok?\n")); - - if (info->cancelled) { - g_free(info->hostbufmem); - g_free(info); - } else { - e_msgport_reply((EMsg *)info); - } - - return NULL; +#else + freeaddrinfo(host); +#endif } - -struct hostent * -camel_gethostbyaddr (const char *addr, int len, int type, CamelException *exout) +#ifdef NEED_ADDRINFO +static void * +cs_getnameinfo(void *data) { - int fdmax, status, fd, cancel_fd; - struct _lookup_msg *msg; - CamelException ex; + struct _addrinfo_msg *msg = data; + int herr; + struct hostent h; + struct sockaddr_in *sin = (struct sockaddr_in *)msg->addr; - g_return_val_if_fail (addr != NULL, NULL); - - if (camel_operation_cancel_check (NULL)) { - camel_exception_set (exout, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); + /* FIXME: error code */ + if (msg->addr->sa_family != AF_INET) { + msg->result = -1; return NULL; } - camel_exception_init(&ex); - camel_operation_start_transient (NULL, _("Resolving address")); - - msg = g_malloc0 (sizeof (struct _lookup_msg)); - msg->hostbuflen = 1024; - msg->hostbufmem = g_malloc (msg->hostbuflen); - msg->name = addr; - msg->len = len; - msg->type = type; - msg->result = -1; - - cancel_fd = camel_operation_cancel_fd (NULL); - if (cancel_fd == -1) { - get_hostbyaddr (msg); - } else { - EMsgPort *reply_port; - pthread_t id; - fd_set rdset; - int err; - - reply_port = msg->msg.reply_port = e_msgport_new (); - fd = e_msgport_fd (msg->msg.reply_port); - if ((err = pthread_create (&id, NULL, get_hostbyaddr, msg)) == 0) { - d(printf("waiting for name return/cancellation in main process\n")); - do { - FD_ZERO(&rdset); - FD_SET(cancel_fd, &rdset); - FD_SET(fd, &rdset); - fdmax = MAX(fd, cancel_fd) + 1; - status = select (fdmax, &rdset, NULL, 0, NULL); - } while (status == -1 && errno == EINTR); - - if (status == -1 || FD_ISSET(cancel_fd, &rdset)) { - if (status == -1) - camel_exception_setv(&ex, CAMEL_EXCEPTION_SYSTEM, _("Failure in name lookup: %s"), g_strerror(errno)); - else - camel_exception_setv(&ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); - - /* We cancel so if the thread impl is decent it causes immediate exit. - We detach so we dont need to wait for it to exit if it isn't. - We check the reply port incase we had a reply in the mean time, which we free later */ - d(printf("Cancelling lookup thread and leaving it\n")); - msg->cancelled = 1; - pthread_detach(id); - pthread_cancel(id); - msg = (struct _lookup_msg *)e_msgport_get(reply_port); - } else { - struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port); - - g_assert(reply == msg); - d(printf("waiting for child to exit\n")); - pthread_join(id, NULL); - d(printf("child done\n")); - } - } else { - camel_exception_setv(&ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: cannot create thread: %s"), g_strerror(err)); - } + /* FIXME: honour getnameinfo flags: do we care, not really */ - - e_msgport_destroy (reply_port); + while ((msg->result = e_gethostbyaddr_r((const char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET, &h, + msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) { + pthread_testcancel (); + msg->hostbuflen *= 2; + msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen); } - camel_operation_end (NULL); - - if (!camel_exception_is_set(&ex)) { - if (msg->result == 0) - return &msg->hostbuf; + if (msg->cancelled) + goto cancel; - if (msg->herr == HOST_NOT_FOUND || msg->herr == NO_DATA) - camel_exception_setv (&ex, CAMEL_EXCEPTION_SYSTEM, - _("Host lookup failed: host not found")); - else - camel_exception_setv (&ex, CAMEL_EXCEPTION_SYSTEM, - _("Host lookup failed: unknown reason")); + if (msg->host) { + g_free(msg->host); + if (msg->result == 0 && h.h_name && h.h_name[0]) { + msg->host = g_strdup(h.h_name); + } else { + unsigned char *in = (unsigned char *)&sin->sin_addr; + + /* sin_addr is always network order which is big-endian */ + msg->host = g_strdup_printf("%u.%u.%u.%u", in[0], in[1], in[2], in[3]); + } } - if (msg) { - g_free(msg->hostbufmem); - g_free(msg); - } + /* we never actually use this anyway */ + if (msg->serv) + sprintf(msg->serv, "%d", sin->sin_port); + + e_msgport_reply((EMsg *)msg); + return NULL; +cancel: + cs_freeinfo(msg); + return NULL; +} +#else +static void * +cs_getnameinfo(void *data) +{ + struct _addrinfo_msg *msg = data; - camel_exception_xfer(exout, &ex); + /* there doens't appear to be a return code which says host or serv buffers are too short, lengthen them */ + do { + msg->result = getnameinfo(msg->addr, msg->addrlen, msg->host, msg->hostlen, msg->serv, msg->servlen, msg->flags); + } while (msg->result == EAI_AGAIN); + + if (msg->cancelled) + cs_freeinfo(msg); + else + e_msgport_reply((EMsg *)msg); return NULL; } +#endif -void camel_free_host(struct hostent *h) +int +camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, char **host, char **serv, int flags, CamelException *ex) { - struct _lookup_msg *msg; + struct _addrinfo_msg *msg; + int result; - g_return_if_fail(h != NULL); + if (camel_operation_cancel_check(NULL)) { + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); + return -1; + } - /* yeah this looks ugly but it is safe. we passed out a reference to inside our structure, this maps it - to the base structure, so we can free everything right without having to keep track of it separately */ - msg = (struct _lookup_msg *)(((char *)h) - STRUCT_OFFSET(struct _lookup_msg, hostbuf)); + camel_operation_start_transient(NULL, _("Resolving address")); - g_free(msg->hostbufmem); + msg = g_malloc0(sizeof(*msg)); + msg->addr = sa; + msg->addrlen = salen; + if (host) { + msg->hostlen = NI_MAXHOST; + msg->host = g_malloc(msg->hostlen); + msg->host[0] = 0; + } + if (serv) { + msg->servlen = NI_MAXSERV; + msg->serv = g_malloc(msg->servlen); + msg->serv[0] = 0; + } + msg->flags = flags; +#ifdef NEED_ADDRINFO + msg->hostbuflen = 1024; + msg->hostbufmem = g_malloc(msg->hostbuflen); +#endif + cs_waitinfo(cs_getnameinfo, msg, _("Name lookup failed"), ex); + + result = msg->result; + + if (host) + *host = g_strdup(msg->host); + if (serv) + *serv = g_strdup(msg->serv); + + g_free(msg->host); + g_free(msg->serv); g_free(msg); + + camel_operation_end(NULL); + + return result; } + diff --git a/camel/camel-service.h b/camel/camel-service.h index 587749e24..f49472cc5 100644 --- a/camel/camel-service.h +++ b/camel/camel-service.h @@ -135,14 +135,61 @@ CamelProvider * camel_service_get_provider (CamelService *service); GList * camel_service_query_auth_types (CamelService *service, CamelException *ex); -/* convenience functions */ -struct hostent * camel_service_gethost (CamelService *service, - CamelException *ex); +#ifdef NEED_ADDRINFO +/* Some of this is copied from GNU's netdb.h + + Copyright (C) 1996-2002, 2003, 2004 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. +*/ +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; -/* cancellable dns lookup */ -struct hostent * camel_gethostbyname (const char *name, CamelException *ex); -struct hostent * camel_gethostbyaddr (const char *addr, int len, int type, CamelException *ex); -void camel_free_host (struct hostent *h); +#define AI_CANONNAME 0x0002 /* Request for canonical name. */ +#define AI_NUMERICHOST 0x0004 /* Don't use name resolution. */ + +/* Error values for `getaddrinfo' function. */ +#define EAI_BADFLAGS -1 /* Invalid value for `ai_flags' field. */ +#define EAI_NONAME -2 /* NAME or SERVICE is unknown. */ +#define EAI_AGAIN -3 /* Temporary failure in name resolution. */ +#define EAI_FAIL -4 /* Non-recoverable failure in name res. */ +#define EAI_NODATA -5 /* No address associated with NAME. */ +#define EAI_FAMILY -6 /* `ai_family' not supported. */ +#define EAI_SOCKTYPE -7 /* `ai_socktype' not supported. */ +#define EAI_SERVICE -8 /* SERVICE not supported for `ai_socktype'. */ +#define EAI_ADDRFAMILY -9 /* Address family for NAME not supported. */ +#define EAI_MEMORY -10 /* Memory allocation failure. */ +#define EAI_SYSTEM -11 /* System error returned in `errno'. */ +#define EAI_OVERFLOW -12 /* Argument buffer overflow. */ + +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 + +#define NI_NUMERICHOST 1 /* Don't try to look up hostname. */ +#define NI_NUMERICSERV 2 /* Don't convert port number to name. */ +#define NI_NOFQDN 4 /* Only return nodename portion. */ +#define NI_NAMEREQD 8 /* Don't return numeric addresses. */ +#define NI_DGRAM 16 /* Look up UDP service rather than TCP. */ +#endif + +/* new hostname interfaces */ +struct addrinfo *camel_getaddrinfo(const char *name, const char *service, + const struct addrinfo *hints, CamelException *ex); +void camel_freeaddrinfo(struct addrinfo *host); +int camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, char **host, char **serv, + int flags, CamelException *ex); /* Standard Camel function */ CamelType camel_service_get_type (void); diff --git a/camel/camel-tcp-stream-raw.c b/camel/camel-tcp-stream-raw.c index 04e47ac64..93f3fbdba 100644 --- a/camel/camel-tcp-stream-raw.c +++ b/camel/camel-tcp-stream-raw.c @@ -20,7 +20,6 @@ * */ - #ifdef HAVE_CONFIG_H #include <config.h> #endif @@ -48,11 +47,11 @@ static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n); static int stream_flush (CamelStream *stream); static int stream_close (CamelStream *stream); -static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port); +static int stream_connect (CamelTcpStream *stream, struct addrinfo *host); static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data); static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data); -static CamelTcpAddress *stream_get_local_address (CamelTcpStream *stream); -static CamelTcpAddress *stream_get_remote_address (CamelTcpStream *stream); +static struct sockaddr *stream_get_local_address (CamelTcpStream *stream, socklen_t *len); +static struct sockaddr *stream_get_remote_address (CamelTcpStream *stream, socklen_t *len); static void camel_tcp_stream_raw_class_init (CamelTcpStreamRawClass *camel_tcp_stream_raw_class) @@ -267,13 +266,8 @@ stream_close (CamelStream *stream) /* this is a 'cancellable' connect, cancellable from camel_operation_cancel etc */ /* returns -1 & errno == EINTR if the connection was cancelled */ static int -socket_connect (struct hostent *h, int port) +socket_connect(struct addrinfo *h) { -#ifdef ENABLE_IPv6 - struct sockaddr_in6 sin6; -#endif - struct sockaddr_in sin; - struct sockaddr *saddr; struct timeval tv; socklen_t len; int cancel_fd; @@ -286,31 +280,17 @@ socket_connect (struct hostent *h, int port) return -1; } - /* setup connect, we do it using a nonblocking socket so we can poll it */ -#ifdef ENABLE_IPv6 - if (h->h_addrtype == AF_INET6) { - sin6.sin6_port = htons (port); - sin6.sin6_family = h->h_addrtype; - memcpy (&sin6.sin6_addr, h->h_addr, sizeof (sin6.sin6_addr)); - saddr = (struct sockaddr *) &sin6; - len = sizeof (sin6); - } else { -#endif - sin.sin_port = htons (port); - sin.sin_family = h->h_addrtype; - memcpy (&sin.sin_addr, h->h_addr, sizeof (sin.sin_addr)); - saddr = (struct sockaddr *) &sin; - len = sizeof (sin); -#ifdef ENABLE_IPv6 + if (h->ai_socktype != SOCK_STREAM) { + errno = EINVAL; + return -1; } -#endif - - if ((fd = socket (h->h_addrtype, SOCK_STREAM, 0)) == -1) + + if ((fd = socket (h->ai_family, SOCK_STREAM, 0)) == -1) return -1; cancel_fd = camel_operation_cancel_fd (NULL); if (cancel_fd == -1) { - if (connect (fd, saddr, len) == -1) { + if (connect (fd, h->ai_addr, h->ai_addrlen) == -1) { errnosav = errno; close (fd); errno = errnosav; @@ -325,7 +305,7 @@ socket_connect (struct hostent *h, int port) flags = fcntl (fd, F_GETFL); fcntl (fd, F_SETFL, flags | O_NONBLOCK); - if (connect (fd, saddr, len) == 0) { + if (connect (fd, h->ai_addr, h->ai_addrlen) == 0) { fcntl (fd, F_SETFL, flags); return fd; } @@ -383,21 +363,22 @@ socket_connect (struct hostent *h, int port) } static int -stream_connect (CamelTcpStream *stream, struct hostent *host, int port) +stream_connect (CamelTcpStream *stream, struct addrinfo *host) { CamelTcpStreamRaw *raw = CAMEL_TCP_STREAM_RAW (stream); - int fd; g_return_val_if_fail (host != NULL, -1); - - if ((fd = socket_connect (host, port)) == -1) - return -1; - - raw->sockfd = fd; - - return 0; -} + while (host) { + raw->sockfd = socket_connect(host); + if (raw->sockfd != -1) + return 0; + + host = host->ai_next; + } + + return -1; +} static int get_sockopt_level (const CamelSockOptData *data) @@ -496,72 +477,42 @@ stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data) sizeof (data->value)); } +static struct sockaddr * +stream_get_local_address (CamelTcpStream *stream, socklen_t *len) +{ #ifdef ENABLE_IPv6 -#define MIN_SOCKADDR_BUFLEN (sizeof (struct sockaddr_in6)) + struct sockaddr_in6 sin; #else -#define MIN_SOCKADDR_BUFLEN (sizeof (struct sockaddr_in)) + struct sockaddr_in sin; #endif + struct sockaddr *saddr = (struct sockaddr *)&sin; -static CamelTcpAddress * -stream_get_local_address (CamelTcpStream *stream) -{ - unsigned char buf[MIN_SOCKADDR_BUFLEN]; -#ifdef ENABLE_IPv6 - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) buf; -#endif - struct sockaddr_in *sin = (struct sockaddr_in *) buf; - struct sockaddr *saddr = (struct sockaddr *) buf; - gpointer address; - socklen_t len; - int family; - - len = MIN_SOCKADDR_BUFLEN; - - if (getsockname (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, &len) == -1) + *len = sizeof(sin); + if (getsockname (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, len) == -1) return NULL; - - if (saddr->sa_family == AF_INET) { - family = CAMEL_TCP_ADDRESS_IPv4; - address = &sin->sin_addr; -#ifdef ENABLE_IPv6 - } else if (saddr->sa_family == AF_INET6) { - family = CAMEL_TCP_ADDRESS_IPv6; - address = &sin6->sin6_addr; -#endif - } else - return NULL; - - return camel_tcp_address_new (family, sin->sin_port, len, address); + + saddr = g_malloc(*len); + memcpy(saddr, &sin, *len); + + return saddr; } -static CamelTcpAddress * -stream_get_remote_address (CamelTcpStream *stream) +static struct sockaddr * +stream_get_remote_address (CamelTcpStream *stream, socklen_t *len) { - unsigned char buf[MIN_SOCKADDR_BUFLEN]; #ifdef ENABLE_IPv6 - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) buf; -#endif - struct sockaddr_in *sin = (struct sockaddr_in *) buf; - struct sockaddr *saddr = (struct sockaddr *) buf; - gpointer address; - socklen_t len; - int family; - - len = MIN_SOCKADDR_BUFLEN; - - if (getpeername (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, &len) == -1) - return NULL; - - if (saddr->sa_family == AF_INET) { - family = CAMEL_TCP_ADDRESS_IPv4; - address = &sin->sin_addr; -#ifdef ENABLE_IPv6 - } else if (saddr->sa_family == AF_INET6) { - family = CAMEL_TCP_ADDRESS_IPv6; - address = &sin6->sin6_addr; + struct sockaddr_in6 sin; +#else + struct sockaddr_in sin; #endif - } else + struct sockaddr *saddr = (struct sockaddr *)&sin; + + *len = sizeof(sin); + if (getpeername (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, len) == -1) return NULL; - - return camel_tcp_address_new (family, sin->sin_port, len, address); + + saddr = g_malloc(*len); + memcpy(saddr, &sin, *len); + + return saddr; } diff --git a/camel/camel-tcp-stream-ssl.c b/camel/camel-tcp-stream-ssl.c index 791083186..39bf89d46 100644 --- a/camel/camel-tcp-stream-ssl.c +++ b/camel/camel-tcp-stream-ssl.c @@ -80,11 +80,11 @@ static int stream_close (CamelStream *stream); static PRFileDesc *enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd); -static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port); +static int stream_connect (CamelTcpStream *stream, struct addrinfo *host); static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data); static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data); -static CamelTcpAddress *stream_get_local_address (CamelTcpStream *stream); -static CamelTcpAddress *stream_get_remote_address (CamelTcpStream *stream); +static struct sockaddr *stream_get_local_address (CamelTcpStream *stream, socklen_t *len); +static struct sockaddr *stream_get_remote_address (CamelTcpStream *stream, socklen_t *len); struct _CamelTcpStreamSSLPrivate { PRFileDesc *sockfd; @@ -1024,30 +1024,58 @@ enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd) } static int -stream_connect (CamelTcpStream *stream, struct hostent *host, int port) +sockaddr_to_praddr(struct sockaddr *s, int len, PRNetAddr *addr) +{ + /* We assume the ip addresses are the same size - they have to be anyway. + We could probably just use memcpy *shrug* */ + + memset(addr, 0, sizeof(*addr)); + + if (s->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)s; + + if (len < sizeof(*sin)) + return -1; + + addr->inet.family = PR_AF_INET; + addr->inet.port = sin->sin_port; + memcpy(&addr->inet.ip, &sin->sin_addr, sizeof(addr->inet.ip)); + + return 0; + } +#ifdef ENABLE_IPv6 + else if (s->sa_family == PR_AF_INET6) { + struct sockaddr_in6 *sin = (struct sockaddr_in6 *)s; + + if (len < sizeof(*sin)) + return -1; + + addr->ipv6.family = PR_AF_INET6; + addr->ipv6.port = sin->sin6_port; + addr->ipv6.flowinfo = sin->sin6_flowinfo; + memcpy(&addr->ipv6.ip, &sin->sin6_addr, sizeof(addr->ipv6.ip)); + addr->ipv6.scope_id = sin->sin6_scope_id; + + return 0; + } +#endif + + return -1; +} + +static int +socket_connect(CamelTcpStream *stream, struct addrinfo *host) { CamelTcpStreamSSL *ssl = CAMEL_TCP_STREAM_SSL (stream); PRNetAddr netaddr; PRFileDesc *fd, *cancel_fd; - - g_return_val_if_fail (host != NULL, -1); - - memset ((void *) &netaddr, 0, sizeof (PRNetAddr)); -#ifdef ENABLE_IPv6 - if (host->h_addrtype == AF_INET6) - memcpy (&netaddr.ipv6.ip, host->h_addr, sizeof (netaddr.ipv6.ip)); - else - memcpy (&netaddr.inet.ip, host->h_addr, sizeof (netaddr.inet.ip)); -#else - memcpy (&netaddr.inet.ip, host->h_addr, sizeof (netaddr.inet.ip)); -#endif - - if (PR_InitializeNetAddr (PR_IpAddrNull, port, &netaddr) == PR_FAILURE) { - set_errno (PR_GetError ()); + + if (sockaddr_to_praddr(host->ai_addr, host->ai_addrlen, &netaddr) != 0) { + errno = EINVAL; return -1; } - fd = PR_OpenTCPSocket (host->h_addrtype); + fd = PR_OpenTCPSocket(netaddr.raw.family); if (fd == NULL) { set_errno (PR_GetError ()); return -1; @@ -1126,6 +1154,17 @@ stream_connect (CamelTcpStream *stream, struct hostent *host, int port) return 0; } +static int +stream_connect(CamelTcpStream *stream, struct addrinfo *host) +{ + while (host) { + if (socket_connect(stream, host) == 0) + return 0; + host = host->ai_next; + } + + return -1; +} static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data) @@ -1157,56 +1196,61 @@ stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data) return 0; } -static CamelTcpAddress * -stream_get_local_address (CamelTcpStream *stream) +static struct sockaddr * +sockaddr_from_praddr(PRNetAddr *addr, socklen_t *len) +{ + /* We assume the ip addresses are the same size - they have to be anyway */ + + if (addr->raw.family == PR_AF_INET) { + struct sockaddr_in *sin = g_malloc0(sizeof(*sin)); + + sin->sin_family = AF_INET; + sin->sin_port = addr->inet.port; + memcpy(&sin->sin_addr, &addr->inet.ip, sizeof(sin->sin_addr)); + *len = sizeof(*sin); + + return (struct sockaddr *)sin; + } +#ifdef ENABLE_IPv6 + else if (addr->raw.family == PR_AF_INET6) { + struct sockaddr_in6 *sin = g_malloc0(sizeof(*sin)); + + sin->sin6_family = AF_INET6; + sin->sin6_port = addr->ipv6.port; + sin->sin6_flowinfo = addr->ipv6.flowinfo; + memcpy(&sin->sin6_addr, &addr->ipv6.ip, sizeof(sin->sin6_addr)); + sin->sin6_scope_id = addr->ipv6.scope_id; + *len = sizeof(*sin); + + return (struct sockaddr *)sin; + } +#endif + + return NULL; +} + +static struct sockaddr * +stream_get_local_address(CamelTcpStream *stream, socklen_t *len) { PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd; - int family, length; - gpointer address; PRNetAddr addr; - PR_GetSockName (sockfd, &addr); - - if (addr.inet.family == PR_AF_INET) { - family = CAMEL_TCP_ADDRESS_IPv4; - address = &addr.inet.ip; - length = 4; -#ifdef ENABLE_IPv6 - } else if (addr.inet.family == PR_AF_INET6) { - family = CAMEL_TCP_ADDRESS_IPv6; - address = &addr.ipv6.ip; - length = 16; -#endif - } else + if (PR_GetSockName(sockfd, &addr) != PR_SUCCESS) return NULL; - - return camel_tcp_address_new (family, addr.inet.port, length, address); + + return sockaddr_from_praddr(&addr, len); } -static CamelTcpAddress * -stream_get_remote_address (CamelTcpStream *stream) +static struct sockaddr * +stream_get_remote_address (CamelTcpStream *stream, socklen_t *len) { PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd; - int family, length; - gpointer address; PRNetAddr addr; - PR_GetPeerName (sockfd, &addr); - - if (addr.inet.family == PR_AF_INET) { - family = CAMEL_TCP_ADDRESS_IPv4; - address = &addr.inet.ip; - length = sizeof (addr.inet.ip); -#ifdef ENABLE_IPv6 - } else if (addr.inet.family == PR_AF_INET6) { - family = CAMEL_TCP_ADDRESS_IPv6; - address = &addr.ipv6.ip; - length = sizeof (addr.ipv6.ip); -#endif - } else + if (PR_GetPeerName(sockfd, &addr) != PR_SUCCESS) return NULL; - - return camel_tcp_address_new (family, addr.inet.port, length, address); + + return sockaddr_from_praddr(&addr, len); } #endif /* HAVE_NSS */ diff --git a/camel/camel-tcp-stream.c b/camel/camel-tcp-stream.c index fbbcbec45..3aa2d0d36 100644 --- a/camel/camel-tcp-stream.c +++ b/camel/camel-tcp-stream.c @@ -35,12 +35,11 @@ static CamelStreamClass *parent_class = NULL; /* Returns the class for a CamelTcpStream */ #define CTS_CLASS(so) CAMEL_TCP_STREAM_CLASS (CAMEL_OBJECT_GET_CLASS(so)) -static int tcp_connect (CamelTcpStream *stream, struct hostent *host, int port); +static int tcp_connect (CamelTcpStream *stream, struct addrinfo *host); static int tcp_getsockopt (CamelTcpStream *stream, CamelSockOptData *data); static int tcp_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data); -static CamelTcpAddress *tcp_get_local_address (CamelTcpStream *stream); -static CamelTcpAddress *tcp_get_remote_address (CamelTcpStream *stream); - +static struct sockaddr *tcp_get_local_address (CamelTcpStream *stream, socklen_t *len); +static struct sockaddr *tcp_get_remote_address (CamelTcpStream *stream, socklen_t *len); static void camel_tcp_stream_class_init (CamelTcpStreamClass *camel_tcp_stream_class) @@ -84,7 +83,7 @@ camel_tcp_stream_get_type (void) static int -tcp_connect (CamelTcpStream *stream, struct hostent *host, int port) +tcp_connect (CamelTcpStream *stream, struct addrinfo *host) { w(g_warning ("CamelTcpStream::connect called on default implementation")); return -1; @@ -93,22 +92,21 @@ tcp_connect (CamelTcpStream *stream, struct hostent *host, int port) /** * camel_tcp_stream_connect: * @stream: a CamelTcpStream object. - * @host: a hostent value - * @port: port + * @host: A linked list of addrinfo structures to try to connect, in + * the order of most likely to least likely to work. * * Create a socket and connect based upon the data provided. * * Return value: zero on success or -1 on fail. **/ int -camel_tcp_stream_connect (CamelTcpStream *stream, struct hostent *host, int port) +camel_tcp_stream_connect (CamelTcpStream *stream, struct addrinfo *host) { g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), -1); - return CTS_CLASS (stream)->connect (stream, host, port); + return CTS_CLASS (stream)->connect (stream, host); } - static int tcp_getsockopt (CamelTcpStream *stream, CamelSockOptData *data) { @@ -116,7 +114,6 @@ tcp_getsockopt (CamelTcpStream *stream, CamelSockOptData *data) return -1; } - /** * camel_tcp_stream_getsockopt: * @stream: tcp stream object @@ -134,7 +131,6 @@ camel_tcp_stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data) return CTS_CLASS (stream)->getsockopt (stream, data); } - static int tcp_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data) { @@ -142,7 +138,6 @@ tcp_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data) return -1; } - /** * camel_tcp_stream_setsockopt: * @stream: tcp stream object @@ -160,9 +155,8 @@ camel_tcp_stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *dat return CTS_CLASS (stream)->setsockopt (stream, data); } - -static CamelTcpAddress * -tcp_get_local_address (CamelTcpStream *stream) +static struct sockaddr * +tcp_get_local_address (CamelTcpStream *stream, socklen_t *len) { w(g_warning ("CamelTcpStream::get_local_address called on default implementation")); return NULL; @@ -171,23 +165,24 @@ tcp_get_local_address (CamelTcpStream *stream) /** * camel_tcp_stream_get_local_address: * @stream: tcp stream object + * @len: Pointer to address length which must be supplied. * * Get the local address of @stream. * * Return value: the stream's local address (which must be freed with - * camel_tcp_address_free()) if the stream is connected, or %NULL if not. + * g_free()) if the stream is connected, or %NULL if not. **/ -CamelTcpAddress * -camel_tcp_stream_get_local_address (CamelTcpStream *stream) +struct sockaddr * +camel_tcp_stream_get_local_address (CamelTcpStream *stream, socklen_t *len) { g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), NULL); + g_return_val_if_fail(len != NULL, NULL); - return CTS_CLASS (stream)->get_local_address (stream); + return CTS_CLASS (stream)->get_local_address (stream, len); } - -static CamelTcpAddress * -tcp_get_remote_address (CamelTcpStream *stream) +static struct sockaddr * +tcp_get_remote_address (CamelTcpStream *stream, socklen_t *len) { w(g_warning ("CamelTcpStream::get_remote_address called on default implementation")); return NULL; @@ -196,54 +191,18 @@ tcp_get_remote_address (CamelTcpStream *stream) /** * camel_tcp_stream_get_remote_address: * @stream: tcp stream object + * @len: Pointer to address length, which must be supplied. * * Get the remote address of @stream. * * Return value: the stream's remote address (which must be freed with - * camel_tcp_address_free()) if the stream is connected, or %NULL if not. + * g_free()) if the stream is connected, or %NULL if not. **/ -CamelTcpAddress * -camel_tcp_stream_get_remote_address (CamelTcpStream *stream) +struct sockaddr * +camel_tcp_stream_get_remote_address (CamelTcpStream *stream, socklen_t *len) { g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), NULL); - - return CTS_CLASS (stream)->get_remote_address (stream); -} + g_return_val_if_fail(len != NULL, NULL); - -/** - * camel_tcp_address_new: - * @family: the address family - * @port: the port number (in network byte order) - * @length: the length of @address - * @address: the address data (family dependent, in network byte order) - * - * Return value: a new CamelTcpAddress. - **/ -CamelTcpAddress * -camel_tcp_address_new (CamelTcpAddressFamily family, gushort port, - gushort length, gpointer address) -{ - CamelTcpAddress *addr; - - addr = g_malloc (sizeof (CamelTcpAddress) + length - 1); - addr->family = family; - addr->port = port; - addr->length = length; - memcpy (&addr->address, address, length); - - return addr; -} - - -/** - * camel_tcp_address_free: - * @address: the address - * - * Frees @address. - **/ -void -camel_tcp_address_free (CamelTcpAddress *address) -{ - g_free (address); + return CTS_CLASS (stream)->get_remote_address (stream, len); } diff --git a/camel/camel-tcp-stream.h b/camel/camel-tcp-stream.h index 9472da10e..b7be61dbe 100644 --- a/camel/camel-tcp-stream.h +++ b/camel/camel-tcp-stream.h @@ -88,18 +88,6 @@ typedef struct _CamelSockOptData { } value; } CamelSockOptData; -typedef enum { - CAMEL_TCP_ADDRESS_IPv4, - CAMEL_TCP_ADDRESS_IPv6 -} CamelTcpAddressFamily; - -typedef struct { - CamelTcpAddressFamily family; - gushort port, length; - guint8 address[1]; -} CamelTcpAddress; - - struct _CamelTcpStream { CamelStream parent_object; @@ -109,29 +97,24 @@ typedef struct { CamelStreamClass parent_class; /* Virtual methods */ - int (*connect) (CamelTcpStream *stream, struct hostent *host, int port); + int (*connect) (CamelTcpStream *stream, struct addrinfo *host); int (*getsockopt) (CamelTcpStream *stream, CamelSockOptData *data); int (*setsockopt) (CamelTcpStream *stream, const CamelSockOptData *data); - CamelTcpAddress * (*get_local_address) (CamelTcpStream *stream); - CamelTcpAddress * (*get_remote_address) (CamelTcpStream *stream); + struct sockaddr * (*get_local_address) (CamelTcpStream *stream, socklen_t *len); + struct sockaddr * (*get_remote_address) (CamelTcpStream *stream, socklen_t *len); } CamelTcpStreamClass; /* Standard Camel function */ CamelType camel_tcp_stream_get_type (void); /* public methods */ -int camel_tcp_stream_connect (CamelTcpStream *stream, struct hostent *host, int port); +int camel_tcp_stream_connect (CamelTcpStream *stream, struct addrinfo *host); int camel_tcp_stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data); int camel_tcp_stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data); -CamelTcpAddress *camel_tcp_stream_get_local_address (CamelTcpStream *stream); -CamelTcpAddress *camel_tcp_stream_get_remote_address (CamelTcpStream *stream); - -CamelTcpAddress *camel_tcp_address_new (CamelTcpAddressFamily family, - gushort port, gushort length, - gpointer address); -void camel_tcp_address_free (CamelTcpAddress *address); +struct sockaddr *camel_tcp_stream_get_local_address (CamelTcpStream *stream, socklen_t *len); +struct sockaddr *camel_tcp_stream_get_remote_address (CamelTcpStream *stream, socklen_t *len); #ifdef __cplusplus } diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c new file mode 100644 index 000000000..c0c0bcc8a --- /dev/null +++ b/camel/providers/imap/camel-imap-store.c @@ -0,0 +1,3258 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* camel-imap-store.c : class for an imap store */ + +/* + * Authors: + * Dan Winship <danw@ximian.com> + * Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2000, 2003 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "e-util/e-path.h" + +#include "camel-imap-store.h" +#include "camel-imap-store-summary.h" +#include "camel-imap-folder.h" +#include "camel-imap-utils.h" +#include "camel-imap-command.h" +#include "camel-imap-summary.h" +#include "camel-imap-message-cache.h" +#include "camel-disco-diary.h" +#include "camel-file-utils.h" +#include "camel-folder.h" +#include "camel-exception.h" +#include "camel-session.h" +#include "camel-stream.h" +#include "camel-stream-buffer.h" +#include "camel-stream-fs.h" +#include "camel-stream-process.h" +#include "camel-tcp-stream-raw.h" +#include "camel-tcp-stream-ssl.h" +#include "camel-url.h" +#include "camel-sasl.h" +#include "camel-utf8.h" +#include "camel-string-utils.h" + +#include "camel-imap-private.h" +#include "camel-private.h" + +#include "camel-debug.h" + +#define d(x) + +/* Specified in RFC 2060 */ +#define IMAP_PORT 143 +#define SIMAP_PORT 993 + +static CamelDiscoStoreClass *parent_class = NULL; + +static char imap_tag_prefix = 'A'; + +static void construct (CamelService *service, CamelSession *session, + CamelProvider *provider, CamelURL *url, + CamelException *ex); + +static int imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args); +static int imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args); + +static char *imap_get_name (CamelService *service, gboolean brief); + +static gboolean can_work_offline (CamelDiscoStore *disco_store); +static gboolean imap_connect_online (CamelService *service, CamelException *ex); +static gboolean imap_connect_offline (CamelService *service, CamelException *ex); +static gboolean imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex); +static gboolean imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex); +static void imap_noop (CamelStore *store, CamelException *ex); +static CamelFolder *imap_get_junk(CamelStore *store, CamelException *ex); +static CamelFolder *imap_get_trash(CamelStore *store, CamelException *ex); +static GList *query_auth_types (CamelService *service, CamelException *ex); +static guint hash_folder_name (gconstpointer key); +static gint compare_folder_name (gconstpointer a, gconstpointer b); +static CamelFolder *get_folder_online (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex); +static CamelFolder *get_folder_offline (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex); + +static CamelFolderInfo *create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex); +static void delete_folder (CamelStore *store, const char *folder_name, CamelException *ex); +static void rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex); +static CamelFolderInfo *get_folder_info_online (CamelStore *store, + const char *top, + guint32 flags, + CamelException *ex); +static CamelFolderInfo *get_folder_info_offline (CamelStore *store, + const char *top, + guint32 flags, + CamelException *ex); +static gboolean folder_subscribed (CamelStore *store, const char *folder_name); +static void subscribe_folder (CamelStore *store, const char *folder_name, + CamelException *ex); +static void unsubscribe_folder (CamelStore *store, const char *folder_name, + CamelException *ex); + +static void get_folders_online (CamelImapStore *imap_store, const char *pattern, + GPtrArray *folders, gboolean lsub, CamelException *ex); + + +static void imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, const char *folder_name, CamelException *ex); +static gboolean imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name, CamelException *ex); +static void imap_forget_folder(CamelImapStore *imap_store, const char *folder_name, CamelException *ex); +static void imap_set_server_level (CamelImapStore *store); + +static void +camel_imap_store_class_init (CamelImapStoreClass *camel_imap_store_class) +{ + CamelObjectClass *camel_object_class = + CAMEL_OBJECT_CLASS (camel_imap_store_class); + CamelServiceClass *camel_service_class = + CAMEL_SERVICE_CLASS (camel_imap_store_class); + CamelStoreClass *camel_store_class = + CAMEL_STORE_CLASS (camel_imap_store_class); + CamelDiscoStoreClass *camel_disco_store_class = + CAMEL_DISCO_STORE_CLASS (camel_imap_store_class); + + parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ())); + + /* virtual method overload */ + camel_object_class->setv = imap_setv; + camel_object_class->getv = imap_getv; + + camel_service_class->construct = construct; + camel_service_class->query_auth_types = query_auth_types; + camel_service_class->get_name = imap_get_name; + + camel_store_class->hash_folder_name = hash_folder_name; + camel_store_class->compare_folder_name = compare_folder_name; + camel_store_class->create_folder = create_folder; + camel_store_class->delete_folder = delete_folder; + camel_store_class->rename_folder = rename_folder; + camel_store_class->free_folder_info = camel_store_free_folder_info_full; + camel_store_class->folder_subscribed = folder_subscribed; + camel_store_class->subscribe_folder = subscribe_folder; + camel_store_class->unsubscribe_folder = unsubscribe_folder; + camel_store_class->noop = imap_noop; + camel_store_class->get_trash = imap_get_trash; + camel_store_class->get_junk = imap_get_junk; + + camel_disco_store_class->can_work_offline = can_work_offline; + camel_disco_store_class->connect_online = imap_connect_online; + camel_disco_store_class->connect_offline = imap_connect_offline; + camel_disco_store_class->disconnect_online = imap_disconnect_online; + camel_disco_store_class->disconnect_offline = imap_disconnect_offline; + camel_disco_store_class->get_folder_online = get_folder_online; + camel_disco_store_class->get_folder_offline = get_folder_offline; + camel_disco_store_class->get_folder_resyncing = get_folder_online; + camel_disco_store_class->get_folder_info_online = get_folder_info_online; + camel_disco_store_class->get_folder_info_offline = get_folder_info_offline; + camel_disco_store_class->get_folder_info_resyncing = get_folder_info_online; +} + +static gboolean +free_key (gpointer key, gpointer value, gpointer user_data) +{ + g_free (key); + return TRUE; +} + +static void +camel_imap_store_finalize (CamelObject *object) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (object); + + /* This frees current_folder, folders, authtypes, streams, and namespace. */ + camel_service_disconnect((CamelService *)imap_store, TRUE, NULL); + + if (imap_store->summary) { + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + camel_object_unref(imap_store->summary); + } + + if (imap_store->base_url) + g_free (imap_store->base_url); + if (imap_store->storage_path) + g_free (imap_store->storage_path); +} + +static void +camel_imap_store_init (gpointer object, gpointer klass) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (object); + + imap_store->istream = NULL; + imap_store->ostream = NULL; + + imap_store->dir_sep = '\0'; + imap_store->current_folder = NULL; + imap_store->connected = FALSE; + imap_store->preauthed = FALSE; + + imap_store->tag_prefix = imap_tag_prefix++; + if (imap_tag_prefix > 'Z') + imap_tag_prefix = 'A'; +} + +CamelType +camel_imap_store_get_type (void) +{ + static CamelType camel_imap_store_type = CAMEL_INVALID_TYPE; + + if (camel_imap_store_type == CAMEL_INVALID_TYPE) { + camel_imap_store_type = + camel_type_register (CAMEL_DISCO_STORE_TYPE, + "CamelImapStore", + sizeof (CamelImapStore), + sizeof (CamelImapStoreClass), + (CamelObjectClassInitFunc) camel_imap_store_class_init, + NULL, + (CamelObjectInitFunc) camel_imap_store_init, + (CamelObjectFinalizeFunc) camel_imap_store_finalize); + } + + return camel_imap_store_type; +} + +static void +construct (CamelService *service, CamelSession *session, + CamelProvider *provider, CamelURL *url, + CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (service); + CamelStore *store = CAMEL_STORE (service); + char *tmp; + CamelURL *summary_url; + + CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex); + if (camel_exception_is_set (ex)) + return; + + imap_store->storage_path = camel_session_get_storage_path (session, service, ex); + if (!imap_store->storage_path) + return; + + /* FIXME */ + imap_store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD | + CAMEL_URL_HIDE_PARAMS | + CAMEL_URL_HIDE_AUTH)); + + imap_store->parameters = 0; + if (camel_url_get_param (url, "use_lsub")) + store->flags |= CAMEL_STORE_SUBSCRIPTIONS; + if (camel_url_get_param (url, "namespace")) { + imap_store->parameters |= IMAP_PARAM_OVERRIDE_NAMESPACE; + g_free(imap_store->namespace); + imap_store->namespace = g_strdup (camel_url_get_param (url, "namespace")); + } + if (camel_url_get_param (url, "check_all")) + imap_store->parameters |= IMAP_PARAM_CHECK_ALL; + if (camel_url_get_param (url, "filter")) { + imap_store->parameters |= IMAP_PARAM_FILTER_INBOX; + store->flags |= CAMEL_STORE_FILTER_INBOX; + } + if (camel_url_get_param (url, "filter_junk")) + imap_store->parameters |= IMAP_PARAM_FILTER_JUNK; + if (camel_url_get_param (url, "filter_junk_inbox")) + imap_store->parameters |= IMAP_PARAM_FILTER_JUNK_INBOX; + + /* setup/load the store summary */ + tmp = alloca(strlen(imap_store->storage_path)+32); + sprintf(tmp, "%s/.ev-store-summary", imap_store->storage_path); + imap_store->summary = camel_imap_store_summary_new(); + camel_store_summary_set_filename((CamelStoreSummary *)imap_store->summary, tmp); + summary_url = camel_url_new(imap_store->base_url, NULL); + camel_store_summary_set_uri_base((CamelStoreSummary *)imap_store->summary, summary_url); + camel_url_free(summary_url); + if (camel_store_summary_load((CamelStoreSummary *)imap_store->summary) == 0) { + CamelImapStoreSummary *is = imap_store->summary; + + if (is->namespace) { + /* if namespace has changed, clear folder list */ + if (imap_store->namespace && strcmp(imap_store->namespace, is->namespace->full_name) != 0) { + camel_store_summary_clear((CamelStoreSummary *)is); + } else { + imap_store->namespace = g_strdup(is->namespace->full_name); + imap_store->dir_sep = is->namespace->sep; + } + } + + imap_store->capabilities = is->capabilities; + imap_set_server_level(imap_store); + } +} + +static int +imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args) +{ + CamelImapStore *store = (CamelImapStore *) object; + guint32 tag, flags; + int i; + + for (i = 0; i < args->argc; i++) { + tag = args->argv[i].tag; + + /* make sure this is an arg we're supposed to handle */ + if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST || + (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100) + continue; + + switch (tag) { + case CAMEL_IMAP_STORE_NAMESPACE: + if (strcmp (store->namespace, args->argv[i].ca_str) != 0) { + g_free (store->namespace); + store->namespace = g_strdup (args->argv[i].ca_str); + /* the current imap code will need to do a reconnect for this to take effect */ + /*reconnect = TRUE;*/ + } + break; + case CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE: + flags = args->argv[i].ca_int ? IMAP_PARAM_OVERRIDE_NAMESPACE : 0; + flags |= (store->parameters & ~IMAP_PARAM_OVERRIDE_NAMESPACE); + + if (store->parameters != flags) { + store->parameters = flags; + /* the current imap code will need to do a reconnect for this to take effect */ + /*reconnect = TRUE;*/ + } + break; + case CAMEL_IMAP_STORE_CHECK_ALL: + flags = args->argv[i].ca_int ? IMAP_PARAM_CHECK_ALL : 0; + flags |= (store->parameters & ~IMAP_PARAM_CHECK_ALL); + store->parameters = flags; + /* no need to reconnect for this option to take effect... */ + break; + case CAMEL_IMAP_STORE_FILTER_INBOX: + flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_INBOX : 0; + flags |= (store->parameters & ~IMAP_PARAM_FILTER_INBOX); + store->parameters = flags; + /* no need to reconnect for this option to take effect... */ + break; + case CAMEL_IMAP_STORE_FILTER_JUNK: + flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_JUNK : 0; + store->parameters = flags | (store->parameters & ~IMAP_PARAM_FILTER_JUNK); + break; + case CAMEL_IMAP_STORE_FILTER_JUNK_INBOX: + flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_JUNK_INBOX : 0; + store->parameters = flags | (store->parameters & ~IMAP_PARAM_FILTER_JUNK_INBOX); + break; + default: + /* error?? */ + continue; + } + + /* let our parent know that we've handled this arg */ + camel_argv_ignore (args, i); + } + + /* FIXME: if we need to reconnect for a change to take affect, + we need to do it here... or, better yet, somehow chain it + up to CamelService's setv implementation. */ + + return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args); +} + +static int +imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args) +{ + CamelImapStore *store = (CamelImapStore *) object; + guint32 tag; + int i; + + for (i = 0; i < args->argc; i++) { + tag = args->argv[i].tag; + + /* make sure this is an arg we're supposed to handle */ + if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST || + (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100) + continue; + + switch (tag) { + case CAMEL_IMAP_STORE_NAMESPACE: + *args->argv[i].ca_str = store->namespace; + break; + case CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE: + *args->argv[i].ca_int = store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE ? TRUE : FALSE; + break; + case CAMEL_IMAP_STORE_CHECK_ALL: + *args->argv[i].ca_int = store->parameters & IMAP_PARAM_CHECK_ALL ? TRUE : FALSE; + break; + case CAMEL_IMAP_STORE_FILTER_INBOX: + *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_INBOX ? TRUE : FALSE; + break; + case CAMEL_IMAP_STORE_FILTER_JUNK: + *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_JUNK ? TRUE : FALSE; + break; + case CAMEL_IMAP_STORE_FILTER_JUNK_INBOX: + *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_JUNK_INBOX ? TRUE : FALSE; + break; + default: + /* error? */ + break; + } + } + + return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args); +} + +static char * +imap_get_name (CamelService *service, gboolean brief) +{ + if (brief) + return g_strdup_printf (_("IMAP server %s"), service->url->host); + else + return g_strdup_printf (_("IMAP service for %s on %s"), + service->url->user, service->url->host); +} + +static void +imap_set_server_level (CamelImapStore *store) +{ + if (store->capabilities & IMAP_CAPABILITY_IMAP4REV1) { + store->server_level = IMAP_LEVEL_IMAP4REV1; + store->capabilities |= IMAP_CAPABILITY_STATUS; + } else if (store->capabilities & IMAP_CAPABILITY_IMAP4) + store->server_level = IMAP_LEVEL_IMAP4; + else + store->server_level = IMAP_LEVEL_UNKNOWN; +} + +static struct { + const char *name; + guint32 flag; +} capabilities[] = { + { "IMAP4", IMAP_CAPABILITY_IMAP4 }, + { "IMAP4REV1", IMAP_CAPABILITY_IMAP4REV1 }, + { "STATUS", IMAP_CAPABILITY_STATUS }, + { "NAMESPACE", IMAP_CAPABILITY_NAMESPACE }, + { "UIDPLUS", IMAP_CAPABILITY_UIDPLUS }, + { "LITERAL+", IMAP_CAPABILITY_LITERALPLUS }, + { "STARTTLS", IMAP_CAPABILITY_STARTTLS }, + { NULL, 0 } +}; + +static gboolean +imap_get_capability (CamelService *service, CamelException *ex) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (service); + CamelImapResponse *response; + char *result, *capa, *lasts; + int i; + + CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock); + + /* Find out the IMAP capabilities */ + /* We assume we have utf8 capable search until a failed search tells us otherwise */ + store->capabilities = IMAP_CAPABILITY_utf8_search; + store->authtypes = g_hash_table_new (g_str_hash, g_str_equal); + response = camel_imap_command (store, NULL, ex, "CAPABILITY"); + if (!response) + return FALSE; + result = camel_imap_response_extract (store, response, "CAPABILITY ", ex); + if (!result) + return FALSE; + + /* Skip over "* CAPABILITY ". */ + capa = result + 13; + for (capa = strtok_r (capa, " ", &lasts); capa; + capa = strtok_r (NULL, " ", &lasts)) { + if (!strncmp (capa, "AUTH=", 5)) { + g_hash_table_insert (store->authtypes, + g_strdup (capa + 5), + GINT_TO_POINTER (1)); + continue; + } + for (i = 0; capabilities[i].name; i++) { + if (strcasecmp (capa, capabilities[i].name) == 0) { + store->capabilities |= capabilities[i].flag; + break; + } + } + } + g_free (result); + + imap_set_server_level (store); + + if (store->summary->capabilities != store->capabilities) { + store->summary->capabilities = store->capabilities; + camel_store_summary_touch((CamelStoreSummary *)store->summary); + camel_store_summary_save((CamelStoreSummary *)store->summary); + } + + return TRUE; +} + +enum { + USE_SSL_NEVER, + USE_SSL_ALWAYS, + USE_SSL_WHEN_POSSIBLE +}; + +#define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3) +#define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS) + +static gboolean +connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelException *ex) +{ + CamelImapStore *store = (CamelImapStore *) service; + CamelImapResponse *response; + CamelStream *tcp_stream; + CamelSockOptData sockopt; + gboolean force_imap4 = FALSE; + int clean_quit; + int ret; + char *buf; + struct addrinfo *ai, hints = { 0 }; + char *serv; + + /* FIXME: this connect stuff is duplicated everywhere */ + + if (service->url->port) { + serv = g_alloca(16); + sprintf(serv, "%d", service->url->port); + } else + serv = "imap"; + + if (ssl_mode != USE_SSL_NEVER) { +#ifdef HAVE_SSL + if (try_starttls) { + tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS); + } else { + if (service->url->port == 0) + serv = "imaps"; + tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS); + } +#else + if (!try_starttls && service->url->port == 0) + serv = "imaps"; + + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, + _("Could not connect to %s (port %s): %s"), + service->url->host, serv, + _("SSL unavailable")); + return FALSE; +#endif /* HAVE_SSL */ + } else { + tcp_stream = camel_tcp_stream_raw_new (); + } + + hints.ai_socktype = SOCK_STREAM; + ai = camel_getaddrinfo(service->url->host, serv, &hints, ex); + if (ai == NULL) { + camel_object_unref(tcp_stream); + return FALSE; + } + + ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai); + camel_freeaddrinfo(ai); + if (ret == -1) { + if (errno == EINTR) + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, + _("Connection cancelled")); + else + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, + _("Could not connect to %s (port %s): %s"), + service->url->host, serv, g_strerror (errno)); + + camel_object_unref (tcp_stream); + + return FALSE; + } + + store->ostream = tcp_stream; + store->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ); + + store->connected = TRUE; + store->preauthed = FALSE; + store->command = 0; + + /* Disable Nagle - we send a lot of small requests which nagle slows down */ + sockopt.option = CAMEL_SOCKOPT_NODELAY; + sockopt.value.no_delay = TRUE; + camel_tcp_stream_setsockopt((CamelTcpStream *)tcp_stream, &sockopt); + + /* Set keepalive - needed for some hosts/router configurations, we're idle a lot */ + sockopt.option = CAMEL_SOCKOPT_KEEPALIVE; + sockopt.value.keep_alive = TRUE; + camel_tcp_stream_setsockopt((CamelTcpStream *)tcp_stream, &sockopt); + + /* Read the greeting, if any, and deal with PREAUTH */ + if (camel_imap_store_readline (store, &buf, ex) < 0) { + if (store->istream) { + camel_object_unref (store->istream); + store->istream = NULL; + } + + if (store->ostream) { + camel_object_unref (store->ostream); + store->ostream = NULL; + } + + store->connected = FALSE; + + return FALSE; + } + + if (!strncmp(buf, "* PREAUTH", 9)) + store->preauthed = TRUE; + + if (strstr (buf, "Courier-IMAP")) { + /* Courier-IMAP is braindamaged. So far this flag only + * works around the fact that Courier-IMAP is known to + * give invalid BODY responses seemingly because its + * MIME parser sucks. In any event, we can't rely on + * them so we always have to request the full messages + * rather than getting individual parts. */ + store->braindamaged = TRUE; + } else if (strstr (buf, "WEB.DE") || strstr (buf, "Mail2World")) { + /* This is a workaround for servers which advertise + * IMAP4rev1 but which can sometimes subtly break in + * various ways if we try to use IMAP4rev1 queries. + * + * WEB.DE: when querying for HEADER.FIELDS.NOT, it + * returns an empty literal for the headers. Many + * complaints about empty message-list fields on the + * mailing lists and probably a few bugzilla bugs as + * well. + * + * Mail2World (aka NamePlanet): When requesting + * message info's, it ignores the fact that we + * requested BODY.PEEK[HEADER.FIELDS.NOT (RECEIVED)] + * and so the responses are incomplete. See bug #58766 + * for details. + **/ + force_imap4 = TRUE; + } + + g_free (buf); + + /* get the imap server capabilities */ + if (!imap_get_capability (service, ex)) { + if (store->istream) { + camel_object_unref (store->istream); + store->istream = NULL; + } + + if (store->ostream) { + camel_object_unref (store->ostream); + store->ostream = NULL; + } + + store->connected = FALSE; + return FALSE; + } + + if (force_imap4) { + store->capabilities &= ~IMAP_CAPABILITY_IMAP4REV1; + store->server_level = IMAP_LEVEL_IMAP4; + } + +#ifdef HAVE_SSL + if (ssl_mode == USE_SSL_WHEN_POSSIBLE) { + if (store->capabilities & IMAP_CAPABILITY_STARTTLS) + goto starttls; + } else if (ssl_mode == USE_SSL_ALWAYS) { + if (try_starttls) { + if (store->capabilities & IMAP_CAPABILITY_STARTTLS) { + /* attempt to toggle STARTTLS mode */ + goto starttls; + } else { + /* server doesn't support STARTTLS, abort */ + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to connect to IMAP server %s in secure mode: %s"), + service->url->host, _("SSL/TLS extension not supported.")); + /* we have the possibility of quitting cleanly here */ + clean_quit = TRUE; + goto exception; + } + } + } +#endif /* HAVE_SSL */ + + return TRUE; + +#ifdef HAVE_SSL + starttls: + + /* as soon as we send a STARTTLS command, all hope is lost of a clean QUIT if problems arise */ + clean_quit = FALSE; + + response = camel_imap_command (store, NULL, ex, "STARTTLS"); + if (!response) { + camel_object_unref (store->istream); + camel_object_unref (store->ostream); + store->istream = store->ostream = NULL; + return FALSE; + } + + camel_imap_response_free_without_processing (store, response); + + /* Okay, now toggle SSL/TLS mode */ + if (camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)) == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to connect to IMAP server %s in secure mode: %s"), + service->url->host, _("SSL negotiations failed")); + goto exception; + } + + /* rfc2595, section 4 states that after a successful STLS + command, the client MUST discard prior CAPA responses */ + if (!imap_get_capability (service, ex)) { + if (store->istream) { + camel_object_unref (store->istream); + store->istream = NULL; + } + + if (store->ostream) { + camel_object_unref (store->ostream); + store->ostream = NULL; + } + + store->connected = FALSE; + + return FALSE; + } + + return TRUE; + + exception: + + if (clean_quit && store->connected) { + /* try to disconnect cleanly */ + response = camel_imap_command (store, NULL, ex, "LOGOUT"); + if (response) + camel_imap_response_free_without_processing (store, response); + } + + if (store->istream) { + camel_object_unref (store->istream); + store->istream = NULL; + } + + if (store->ostream) { + camel_object_unref (store->ostream); + store->ostream = NULL; + } + + store->connected = FALSE; + + return FALSE; +#endif /* HAVE_SSL */ +} + +static gboolean +connect_to_server_process (CamelService *service, const char *cmd, CamelException *ex) +{ + CamelImapStore *store = (CamelImapStore *) service; + CamelStream *cmd_stream; + int ret, i = 0; + char *buf; + char *cmd_copy; + char *full_cmd; + char *child_env[7]; + + /* Put full details in the environment, in case the connection + program needs them */ + buf = camel_url_to_string(service->url, 0); + child_env[i++] = g_strdup_printf("URL=%s", buf); + g_free(buf); + + child_env[i++] = g_strdup_printf("URLHOST=%s", service->url->host); + if (service->url->port) + child_env[i++] = g_strdup_printf("URLPORT=%d", service->url->port); + if (service->url->user) + child_env[i++] = g_strdup_printf("URLUSER=%s", service->url->user); + if (service->url->passwd) + child_env[i++] = g_strdup_printf("URLPASSWD=%s", service->url->passwd); + if (service->url->path) + child_env[i++] = g_strdup_printf("URLPATH=%s", service->url->path); + child_env[i] = NULL; + + /* Now do %h, %u, etc. substitution in cmd */ + buf = cmd_copy = g_strdup(cmd); + + full_cmd = g_strdup(""); + + for(;;) { + char *pc; + char *tmp; + char *var; + int len; + + pc = strchr(buf, '%'); + ignore: + if (!pc) { + tmp = g_strdup_printf("%s%s", full_cmd, buf); + g_free(full_cmd); + full_cmd = tmp; + break; + } + + len = pc - buf; + + var = NULL; + + switch(pc[1]) { + case 'h': + var = service->url->host; + break; + case 'u': + var = service->url->user; + break; + } + if (!var) { + /* If there wasn't a valid %-code, with an actual + variable to insert, pretend we didn't see the % */ + pc = strchr(pc + 1, '%'); + goto ignore; + } + tmp = g_strdup_printf("%s%.*s%s", full_cmd, len, buf, var); + g_free(full_cmd); + full_cmd = tmp; + buf = pc + 2; + } + + g_free(cmd_copy); + + cmd_stream = camel_stream_process_new (); + + ret = camel_stream_process_connect (CAMEL_STREAM_PROCESS(cmd_stream), + full_cmd, (const char **)child_env); + + while (i) + g_free(child_env[--i]); + + if (ret == -1) { + if (errno == EINTR) + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, + _("Connection cancelled")); + else + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, + _("Could not connect with command \"%s\": %s"), + full_cmd, g_strerror (errno)); + + camel_object_unref (cmd_stream); + g_free (full_cmd); + return FALSE; + } + g_free (full_cmd); + + store->ostream = cmd_stream; + store->istream = camel_stream_buffer_new (cmd_stream, CAMEL_STREAM_BUFFER_READ); + + store->connected = TRUE; + store->preauthed = FALSE; + store->command = 0; + + /* Read the greeting, if any, and deal with PREAUTH */ + if (camel_imap_store_readline (store, &buf, ex) < 0) { + if (store->istream) { + camel_object_unref (store->istream); + store->istream = NULL; + } + + if (store->ostream) { + camel_object_unref (store->ostream); + store->ostream = NULL; + } + + store->connected = FALSE; + return FALSE; + } + if (!strncmp(buf, "* PREAUTH", 9)) + store->preauthed = TRUE; + g_free (buf); + + /* get the imap server capabilities */ + if (!imap_get_capability (service, ex)) { + if (store->istream) { + camel_object_unref (store->istream); + store->istream = NULL; + } + + if (store->ostream) { + camel_object_unref (store->ostream); + store->ostream = NULL; + } + + store->connected = FALSE; + return FALSE; + } + + return TRUE; + +} + +static struct { + char *value; + int mode; +} ssl_options[] = { + { "", USE_SSL_ALWAYS }, + { "always", USE_SSL_ALWAYS }, + { "when-possible", USE_SSL_WHEN_POSSIBLE }, + { "never", USE_SSL_NEVER }, + { NULL, USE_SSL_NEVER }, +}; + +static gboolean +connect_to_server_wrapper (CamelService *service, CamelException *ex) +{ + const char *command; +#ifdef HAVE_SSL + const char *use_ssl; + int i, ssl_mode; +#endif + command = camel_url_get_param (service->url, "command"); + if (command) + return connect_to_server_process (service, command, ex); + +#ifdef HAVE_SSL + use_ssl = camel_url_get_param (service->url, "use_ssl"); + if (use_ssl) { + for (i = 0; ssl_options[i].value; i++) + if (!strcmp (ssl_options[i].value, use_ssl)) + break; + ssl_mode = ssl_options[i].mode; + } else + ssl_mode = USE_SSL_NEVER; + + if (ssl_mode == USE_SSL_ALWAYS) { + /* First try the ssl port */ + if (!connect_to_server (service, ssl_mode, FALSE, ex)) { + if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) { + /* The ssl port seems to be unavailable, lets try STARTTLS */ + camel_exception_clear (ex); + return connect_to_server (service, ssl_mode, TRUE, ex); + } else { + return FALSE; + } + } + + return TRUE; + } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) { + /* If the server supports STARTTLS, use it */ + return connect_to_server (service, ssl_mode, TRUE, ex); + } else { + /* User doesn't care about SSL */ + return connect_to_server (service, ssl_mode, FALSE, ex); + } +#else + return connect_to_server (service, USE_SSL_NEVER, FALSE, ex); +#endif +} + +extern CamelServiceAuthType camel_imap_password_authtype; + +static GList * +query_auth_types (CamelService *service, CamelException *ex) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (service); + CamelServiceAuthType *authtype; + GList *sasl_types, *t, *next; + gboolean connected; + + if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex)) + return NULL; + + CAMEL_SERVICE_LOCK (store, connect_lock); + connected = connect_to_server_wrapper (service, ex); + CAMEL_SERVICE_UNLOCK (store, connect_lock); + if (!connected) + return NULL; + + sasl_types = camel_sasl_authtype_list (FALSE); + for (t = sasl_types; t; t = next) { + authtype = t->data; + next = t->next; + + if (!g_hash_table_lookup (store->authtypes, authtype->authproto)) { + sasl_types = g_list_remove_link (sasl_types, t); + g_list_free_1 (t); + } + } + + return g_list_prepend (sasl_types, &camel_imap_password_authtype); +} + +/* folder_name is path name */ +static CamelFolderInfo * +imap_build_folder_info(CamelImapStore *imap_store, const char *folder_name) +{ + CamelURL *url; + const char *name; + CamelFolderInfo *fi; + + fi = g_malloc0(sizeof(*fi)); + + fi->full_name = g_strdup(folder_name); + fi->unread = 0; + fi->total = 0; + + url = camel_url_new (imap_store->base_url, NULL); + g_free (url->path); + url->path = g_strdup_printf ("/%s", folder_name); + fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL); + camel_url_free(url); + name = strrchr (fi->full_name, '/'); + if (name == NULL) + name = fi->full_name; + else + name++; + fi->name = g_strdup (name); + + return fi; +} + +static void +imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, + const char *folder_name, CamelException *ex) +{ + CamelFolderInfo *fi; + CamelStoreInfo *si; + + si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name); + if (si) { + if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) { + si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED; + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } + + if (imap_store->renaming) { + /* we don't need to emit a "folder_unsubscribed" signal + if we are in the process of renaming folders, so we + are done here... */ + return; + + } + + fi = imap_build_folder_info(imap_store, folder_name); + camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_unsubscribed", fi); + camel_folder_info_free (fi); +} + +static void +imap_forget_folder (CamelImapStore *imap_store, const char *folder_name, CamelException *ex) +{ + CamelFolderSummary *summary; + CamelImapMessageCache *cache; + char *summary_file, *state_file; + char *journal_file; + char *folder_dir, *storage_path; + CamelFolderInfo *fi; + const char *name; + + name = strrchr (folder_name, imap_store->dir_sep); + if (name) + name++; + else + name = folder_name; + + storage_path = g_strdup_printf ("%s/folders", imap_store->storage_path); + folder_dir = e_path_to_physical (storage_path, folder_name); + g_free (storage_path); + if (access (folder_dir, F_OK) != 0) { + g_free (folder_dir); + goto event; + } + + summary_file = g_strdup_printf ("%s/summary", folder_dir); + summary = camel_imap_summary_new (summary_file); + if (!summary) { + g_free (summary_file); + g_free (folder_dir); + goto event; + } + + cache = camel_imap_message_cache_new (folder_dir, summary, ex); + if (cache) + camel_imap_message_cache_clear (cache); + + camel_object_unref (cache); + camel_object_unref (summary); + + unlink (summary_file); + g_free (summary_file); + + journal_file = g_strdup_printf ("%s/journal", folder_dir); + unlink (journal_file); + g_free (journal_file); + + state_file = g_strdup_printf ("%s/cmeta", folder_dir); + unlink (state_file); + g_free (state_file); + + rmdir (folder_dir); + g_free (folder_dir); + + event: + + camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, folder_name); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + + fi = imap_build_folder_info(imap_store, folder_name); + camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_deleted", fi); + camel_folder_info_free (fi); +} + +static gboolean +imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name, + CamelException *ex) +{ + CamelImapResponse *response; + + response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %F", + full_name); + + if (response) { + gboolean stillthere = response->untagged->len != 0; + + camel_imap_response_free_without_processing (imap_store, response); + + return stillthere; + } + + /* if the command was rejected, there must be some other error, + assume it worked so we dont blow away the folder unecessarily */ + return TRUE; +} + +/* This is a little 'hack' to avoid the deadlock conditions that would otherwise + ensue when calling camel_folder_refresh_info from inside a lock */ +/* NB: on second thougts this is probably not entirely safe, but it'll do for now */ +/* No, its definetly not safe. So its been changed to copy the folders first */ +/* the alternative is to: + make the camel folder->lock recursive (which should probably be done) + or remove it from camel_folder_refresh_info, and use another locking mechanism */ +/* also see get_folder_info_online() for the same hack repeated */ +static void +imap_store_refresh_folders (CamelImapStore *store, CamelException *ex) +{ + GPtrArray *folders; + int i; + + folders = camel_object_bag_list(CAMEL_STORE (store)->folders); + + for (i = 0; i <folders->len; i++) { + CamelFolder *folder = folders->pdata[i]; + + /* NB: we can have vtrash folders also in our store ... bit hacky */ + if (!CAMEL_IS_IMAP_FOLDER(folder)) { + camel_object_unref(folder); + continue; + } + + CAMEL_IMAP_FOLDER (folder)->need_rescan = TRUE; + if (!camel_exception_is_set(ex)) + CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex); + + if (camel_exception_is_set (ex) && + imap_check_folder_still_extant (store, folder->full_name, ex) == FALSE) { + gchar *namedup; + + /* the folder was deleted (may happen when we come back online + * after being offline */ + + namedup = g_strdup (folder->full_name); + camel_object_unref(folder); + imap_folder_effectively_unsubscribed (store, namedup, ex); + imap_forget_folder (store, namedup, ex); + g_free (namedup); + } else + camel_object_unref(folder); + } + + g_ptr_array_free (folders, TRUE); +} + +static gboolean +try_auth (CamelImapStore *store, const char *mech, CamelException *ex) +{ + CamelSasl *sasl; + CamelImapResponse *response; + char *resp; + char *sasl_resp; + + CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock); + + response = camel_imap_command (store, NULL, ex, "AUTHENTICATE %s", mech); + if (!response) + return FALSE; + + sasl = camel_sasl_new ("imap", mech, CAMEL_SERVICE (store)); + while (!camel_sasl_authenticated (sasl)) { + resp = camel_imap_response_extract_continuation (store, response, ex); + if (!resp) + goto lose; + + sasl_resp = camel_sasl_challenge_base64 (sasl, imap_next_word (resp), ex); + g_free (resp); + if (camel_exception_is_set (ex)) + goto break_and_lose; + + response = camel_imap_command_continuation (store, sasl_resp, strlen (sasl_resp), ex); + g_free (sasl_resp); + if (!response) + goto lose; + } + + resp = camel_imap_response_extract_continuation (store, response, NULL); + if (resp) { + /* Oops. SASL claims we're done, but the IMAP server + * doesn't think so... + */ + g_free (resp); + goto lose; + } + + camel_object_unref (sasl); + + return TRUE; + + break_and_lose: + /* Get the server out of "waiting for continuation data" mode. */ + response = camel_imap_command_continuation (store, "*", 1, NULL); + if (response) + camel_imap_response_free (store, response); + + lose: + if (!camel_exception_is_set (ex)) { + camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Bad authentication response from server.")); + } + + camel_object_unref (sasl); + + return FALSE; +} + +static gboolean +imap_auth_loop (CamelService *service, CamelException *ex) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (service); + CamelSession *session = camel_service_get_session (service); + CamelServiceAuthType *authtype = NULL; + CamelImapResponse *response; + char *errbuf = NULL; + gboolean authenticated = FALSE; + const char *auth_domain; + + CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock); + auth_domain = camel_url_get_param (service->url, "auth-domain"); + + if (store->preauthed) { + if (camel_verbose_debug) + fprintf(stderr, "Server %s has preauthenticated us.\n", + service->url->host); + return TRUE; + } + + if (service->url->authmech) { + if (!g_hash_table_lookup (store->authtypes, service->url->authmech)) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("IMAP server %s does not support requested " + "authentication type %s"), + service->url->host, + service->url->authmech); + return FALSE; + } + + authtype = camel_sasl_authtype (service->url->authmech); + if (!authtype) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("No support for authentication type %s"), + service->url->authmech); + return FALSE; + } + + if (!authtype->need_password) { + authenticated = try_auth (store, authtype->authproto, ex); + if (!authenticated) + return FALSE; + } + } + + while (!authenticated) { + if (errbuf) { + /* We need to un-cache the password before prompting again */ + camel_session_forget_password (session, service, auth_domain, "password", ex); + g_free (service->url->passwd); + service->url->passwd = NULL; + } + + if (!service->url->passwd) { + char *prompt; + + prompt = g_strdup_printf (_("%sPlease enter the IMAP " + "password for %s@%s"), + errbuf ? errbuf : "", + service->url->user, + service->url->host); + service->url->passwd = + camel_session_get_password (session, service, auth_domain, + prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex); + g_free (prompt); + g_free (errbuf); + errbuf = NULL; + + if (!service->url->passwd) { + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, + _("You didn't enter a password.")); + return FALSE; + } + } + + if (!store->connected) { + /* Some servers (eg, courier) will disconnect on + * a bad password. So reconnect here. + */ + if (!connect_to_server_wrapper (service, ex)) + return FALSE; + } + + if (authtype) + authenticated = try_auth (store, authtype->authproto, ex); + else { + response = camel_imap_command (store, NULL, ex, + "LOGIN %S %S", + service->url->user, + service->url->passwd); + if (response) { + camel_imap_response_free (store, response); + authenticated = TRUE; + } + } + if (!authenticated) { + if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL) + return FALSE; + + errbuf = g_strdup_printf (_("Unable to authenticate " + "to IMAP server.\n%s\n\n"), + camel_exception_get_description (ex)); + camel_exception_clear (ex); + } + } + + return TRUE; +} + +static gboolean +can_work_offline (CamelDiscoStore *disco_store) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (disco_store); + + return camel_store_summary_count((CamelStoreSummary *)store->summary) != 0; +} + +static gboolean +imap_connect_online (CamelService *service, CamelException *ex) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (service); + CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service); + CamelImapResponse *response; + /*struct _namespaces *namespaces;*/ + char *result, *name, *path; + int i; + size_t len; + CamelImapStoreNamespace *ns; + + CAMEL_SERVICE_LOCK (store, connect_lock); + if (!connect_to_server_wrapper (service, ex) || + !imap_auth_loop (service, ex)) { + CAMEL_SERVICE_UNLOCK (store, connect_lock); + camel_service_disconnect (service, TRUE, NULL); + return FALSE; + } + + /* Get namespace and hierarchy separator */ + if ((store->capabilities & IMAP_CAPABILITY_NAMESPACE) && + !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) { + response = camel_imap_command (store, NULL, ex, "NAMESPACE"); + if (!response) + goto done; + + result = camel_imap_response_extract (store, response, "NAMESPACE", ex); + if (!result) + goto done; + +#if 0 + /* new code... */ + namespaces = imap_parse_namespace_response (result); + imap_namespaces_destroy (namespaces); + /* end new code */ +#endif + + name = camel_strstrcase (result, "NAMESPACE (("); + if (name) { + char *sep; + + name += 12; + store->namespace = imap_parse_string ((const char **) &name, &len); + if (name && *name++ == ' ') { + sep = imap_parse_string ((const char **) &name, &len); + if (sep) { + store->dir_sep = *sep; + g_free (sep); + } + } + } + g_free (result); + } + + if (!store->namespace) + store->namespace = g_strdup (""); + + if (!store->dir_sep) { + if (store->server_level >= IMAP_LEVEL_IMAP4REV1) { + /* This idiom means "tell me the hierarchy separator + * for the given path, even if that path doesn't exist. + */ + response = camel_imap_command (store, NULL, ex, + "LIST %S \"\"", + store->namespace); + } else { + /* Plain IMAP4 doesn't have that idiom, so we fall back + * to "tell me about this folder", which will fail if + * the folder doesn't exist (eg, if namespace is ""). + */ + response = camel_imap_command (store, NULL, ex, + "LIST \"\" %S", + store->namespace); + } + if (!response) + goto done; + + result = camel_imap_response_extract (store, response, "LIST", NULL); + if (result) { + imap_parse_list_response (store, result, NULL, &store->dir_sep, NULL); + g_free (result); + } + if (!store->dir_sep) { + store->dir_sep = '/'; /* Guess */ + } + } + + /* canonicalize the namespace to end with dir_sep */ + len = strlen (store->namespace); + if (len && store->namespace[len - 1] != store->dir_sep) { + gchar *tmp; + + tmp = g_strdup_printf ("%s%c", store->namespace, store->dir_sep); + g_free (store->namespace); + store->namespace = tmp; + } + + ns = camel_imap_store_summary_namespace_new(store->summary, store->namespace, store->dir_sep); + camel_imap_store_summary_namespace_set(store->summary, ns); + + if (CAMEL_STORE (store)->flags & CAMEL_STORE_SUBSCRIPTIONS) { + gboolean haveinbox = FALSE; + GPtrArray *folders; + char *pattern; + + /* this pre-fills the summary, and checks that lsub is useful */ + folders = g_ptr_array_new (); + pattern = g_strdup_printf ("%s*", store->namespace); + get_folders_online (store, pattern, folders, TRUE, ex); + g_free (pattern); + + for (i = 0; i < folders->len; i++) { + CamelFolderInfo *fi = folders->pdata[i]; + + haveinbox = haveinbox || !strcasecmp (fi->full_name, "INBOX"); + + if (fi->flags & (CAMEL_IMAP_FOLDER_MARKED | CAMEL_IMAP_FOLDER_UNMARKED)) + store->capabilities |= IMAP_CAPABILITY_useful_lsub; + camel_folder_info_free (fi); + } + + /* if the namespace is under INBOX, check INBOX explicitly */ + if (!g_ascii_strncasecmp (store->namespace, "INBOX", 5) && !camel_exception_is_set (ex)) { + gboolean just_subscribed = FALSE; + gboolean need_subscribe = FALSE; + + recheck: + g_ptr_array_set_size (folders, 0); + get_folders_online (store, "INBOX", folders, TRUE, ex); + + for (i = 0; i < folders->len; i++) { + CamelFolderInfo *fi = folders->pdata[i]; + + /* this should always be TRUE if folders->len > 0 */ + if (!strcasecmp (fi->full_name, "INBOX")) { + haveinbox = TRUE; + + /* if INBOX is marked as \NoSelect then it is probably + because it has not been subscribed to */ + if (!need_subscribe) + need_subscribe = fi->flags & CAMEL_FOLDER_NOSELECT; + } + + camel_folder_info_free (fi); + } + + need_subscribe = !haveinbox || need_subscribe; + if (need_subscribe && !just_subscribed && !camel_exception_is_set (ex)) { + /* in order to avoid user complaints, force a subscription to INBOX */ + response = camel_imap_command (store, NULL, ex, "SUBSCRIBE INBOX"); + if (response != NULL) { + /* force a re-check which will pre-fill the summary and + also get any folder flags present on the INBOX */ + camel_imap_response_free (store, response); + just_subscribed = TRUE; + goto recheck; + } + } + } + + g_ptr_array_free (folders, TRUE); + } + + path = g_strdup_printf ("%s/journal", store->storage_path); + disco_store->diary = camel_disco_diary_new (disco_store, path, ex); + g_free (path); + + done: + /* save any changes we had */ + camel_store_summary_save((CamelStoreSummary *)store->summary); + + CAMEL_SERVICE_UNLOCK (store, connect_lock); + + if (camel_exception_is_set (ex)) + camel_service_disconnect (service, TRUE, NULL); + else if (camel_disco_diary_empty (disco_store->diary)) + imap_store_refresh_folders (store, ex); + + return !camel_exception_is_set (ex); +} + +static gboolean +imap_connect_offline (CamelService *service, CamelException *ex) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (service); + CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service); + char *path; + + path = g_strdup_printf ("%s/journal", store->storage_path); + disco_store->diary = camel_disco_diary_new (disco_store, path, ex); + g_free (path); + if (!disco_store->diary) + return FALSE; + + imap_store_refresh_folders (store, ex); + + store->connected = !camel_exception_is_set (ex); + return store->connected; +} + +static gboolean +imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (service); + CamelDiscoStore *disco = CAMEL_DISCO_STORE (service); + + store->connected = FALSE; + if (store->current_folder) { + camel_object_unref (store->current_folder); + store->current_folder = NULL; + } + + if (store->authtypes) { + g_hash_table_foreach_remove (store->authtypes, + free_key, NULL); + g_hash_table_destroy (store->authtypes); + store->authtypes = NULL; + } + + if (store->namespace && !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) { + g_free (store->namespace); + store->namespace = NULL; + } + + if (disco->diary) { + camel_object_unref (disco->diary); + disco->diary = NULL; + } + + return TRUE; +} + +static gboolean +imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (service); + CamelImapResponse *response; + + if (store->connected && clean) { + response = camel_imap_command (store, NULL, NULL, "LOGOUT"); + camel_imap_response_free (store, response); + } + + if (store->istream) { + camel_object_unref (store->istream); + store->istream = NULL; + } + + if (store->ostream) { + camel_object_unref (store->ostream); + store->ostream = NULL; + } + + imap_disconnect_offline (service, clean, ex); + + return TRUE; +} + + +static gboolean +imap_summary_is_dirty (CamelFolderSummary *summary) +{ + CamelMessageInfo *info; + int max, i; + + max = camel_folder_summary_count (summary); + for (i = 0; i < max; i++) { + info = camel_folder_summary_index (summary, i); + if (info && (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) + return TRUE; + } + + return FALSE; +} + +static void +imap_noop (CamelStore *store, CamelException *ex) +{ + CamelImapStore *imap_store = (CamelImapStore *) store; + CamelDiscoStore *disco = (CamelDiscoStore *) store; + CamelImapResponse *response; + CamelFolder *current_folder; + + if (camel_disco_store_status (disco) != CAMEL_DISCO_STORE_ONLINE) + return; + + CAMEL_SERVICE_LOCK (imap_store, connect_lock); + + current_folder = imap_store->current_folder; + if (current_folder && imap_summary_is_dirty (current_folder->summary)) { + /* let's sync the flags instead. NB: must avoid folder lock */ + ((CamelFolderClass *)((CamelObject *)current_folder)->klass)->sync(current_folder, FALSE, ex); + } else { + response = camel_imap_command (imap_store, NULL, ex, "NOOP"); + if (response) + camel_imap_response_free (imap_store, response); + } + + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); +} + +static CamelFolder * +imap_get_trash(CamelStore *store, CamelException *ex) +{ + CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_trash(store, ex); + + if (folder) { + char *state = g_build_filename(((CamelImapStore *)store)->storage_path, "system", "Trash.cmeta", NULL); + + camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL); + g_free(state); + /* no defaults? */ + camel_object_state_read(folder); + } + + return folder; +} + +static CamelFolder * +imap_get_junk(CamelStore *store, CamelException *ex) +{ + CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_junk(store, ex); + + if (folder) { + char *state = g_build_filename(((CamelImapStore *)store)->storage_path, "system", "Junk.cmeta", NULL); + + camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL); + g_free(state); + /* no defaults? */ + camel_object_state_read(folder); + } + + return folder; +} + +static guint +hash_folder_name (gconstpointer key) +{ + if (g_ascii_strcasecmp (key, "INBOX") == 0) + return g_str_hash ("INBOX"); + else + return g_str_hash (key); +} + +static gint +compare_folder_name (gconstpointer a, gconstpointer b) +{ + gconstpointer aname = a, bname = b; + + if (g_ascii_strcasecmp (a, "INBOX") == 0) + aname = "INBOX"; + if (g_ascii_strcasecmp (b, "INBOX") == 0) + bname = "INBOX"; + return g_str_equal (aname, bname); +} + +struct imap_status_item { + struct imap_status_item *next; + char *name; + guint32 value; +}; + +static void +imap_status_item_free (struct imap_status_item *items) +{ + struct imap_status_item *next; + + while (items != NULL) { + next = items->next; + g_free (items->name); + g_free (items); + items = next; + } +} + +static struct imap_status_item * +get_folder_status (CamelImapStore *imap_store, const char *folder_name, const char *type) +{ + struct imap_status_item *items, *item, *tail; + CamelImapResponse *response; + char *status, *name, *p; + + /* FIXME: we assume the server is STATUS-capable */ + + response = camel_imap_command (imap_store, NULL, NULL, + "STATUS %F (%s)", + folder_name, + type); + + if (!response) { + CamelException ex; + + camel_exception_init (&ex); + if (imap_check_folder_still_extant (imap_store, folder_name, &ex) == FALSE) { + imap_folder_effectively_unsubscribed (imap_store, folder_name, &ex); + imap_forget_folder (imap_store, folder_name, &ex); + } + camel_exception_clear (&ex); + return NULL; + } + + if (!(status = camel_imap_response_extract (imap_store, response, "STATUS", NULL))) + return NULL; + + p = status + strlen ("* STATUS "); + while (*p == ' ') + p++; + + /* skip past the mailbox string */ + if (*p == '"') { + p++; + while (*p != '\0') { + if (*p == '"' && p[-1] != '\\') { + p++; + break; + } + + p++; + } + } else { + while (*p != ' ') + p++; + } + + while (*p == ' ') + p++; + + if (*p++ != '(') { + g_free (status); + return NULL; + } + + while (*p == ' ') + p++; + + if (*p == ')') { + g_free (status); + return NULL; + } + + items = NULL; + tail = (struct imap_status_item *) &items; + + do { + name = p; + while (*p != ' ') + p++; + + item = g_malloc (sizeof (struct imap_status_item)); + item->next = NULL; + item->name = g_strndup (name, p - name); + item->value = strtoul (p, &p, 10); + + tail->next = item; + tail = item; + + while (*p == ' ') + p++; + } while (*p != ')'); + + g_free (status); + + return items; +} + +static CamelFolder * +get_folder_online (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelImapResponse *response; + CamelFolder *new_folder; + char *folder_dir, *storage_path; + + if (!camel_imap_store_connected (imap_store, ex)) + return NULL; + + if (!strcasecmp (folder_name, "INBOX")) + folder_name = "INBOX"; + + /* Lock around the whole lot to check/create atomically */ + CAMEL_SERVICE_LOCK (imap_store, connect_lock); + if (imap_store->current_folder) { + camel_object_unref (imap_store->current_folder); + imap_store->current_folder = NULL; + } + response = camel_imap_command (imap_store, NULL, ex, "SELECT %F", folder_name); + if (!response) { + char *folder_real, *parent_name, *parent_real; + const char *c; + + if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL) { + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + return NULL; + } + + camel_exception_clear (ex); + + if (!(flags & CAMEL_STORE_FOLDER_CREATE)) { + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER, + _("No such folder %s"), folder_name); + return NULL; + } + + if ((parent_name = strrchr (folder_name, '/'))) { + parent_name = g_strndup (folder_name, parent_name - folder_name); + parent_real = camel_imap_store_summary_path_to_full (imap_store->summary, parent_name, imap_store->dir_sep); + } else { + parent_real = NULL; + } + + c = parent_name ? parent_name : folder_name; + while (*c && *c != imap_store->dir_sep && !strchr ("#%*", *c)) + c++; + + if (*c != '\0') { + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH, + _("The folder name \"%s\" is invalid because it contains the character \"%c\""), + folder_name, *c); + g_free (parent_name); + g_free (parent_real); + return NULL; + } + + if (parent_real != NULL) { + gboolean need_convert = FALSE; + char *resp, *thisone; + guint32 flags; + int i; + + if (!(response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S", parent_real))) { + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + g_free (parent_name); + g_free (parent_real); + return NULL; + } + + /* FIXME: does not handle unexpected circumstances very well */ + for (i = 0; i < response->untagged->len; i++) { + resp = response->untagged->pdata[i]; + + if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone)) + continue; + + if (!strcmp (parent_name, thisone)) { + if (flags & CAMEL_FOLDER_NOINFERIORS) + need_convert = TRUE; + } + + g_free (thisone); + } + + camel_imap_response_free (imap_store, response); + + /* if not, check if we can delete it and recreate it */ + if (need_convert) { + struct imap_status_item *items, *item; + guint32 messages = 0; + CamelException lex; + char *name; + + item = items = get_folder_status (imap_store, parent_name, "MESSAGES"); + while (item != NULL) { + if (!g_ascii_strcasecmp (item->name, "MESSAGES")) { + messages = item->value; + break; + } + + item = item->next; + } + + imap_status_item_free (items); + + if (messages > 0) { + camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE, + _("The parent folder is not allowed to contain subfolders")); + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + g_free (parent_name); + g_free (parent_real); + return NULL; + } + + /* delete the old parent and recreate it */ + camel_exception_init (&lex); + delete_folder (store, parent_name, &lex); + if (camel_exception_is_set (&lex)) { + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + camel_exception_xfer (ex, &lex); + g_free (parent_name); + g_free (parent_real); + return NULL; + } + + /* add the dirsep to the end of parent_name */ + name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep); + response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", + name); + g_free (name); + + if (!response) { + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + g_free (parent_name); + g_free (parent_real); + return NULL; + } else + camel_imap_response_free (imap_store, response); + } + + g_free (parent_real); + } + + g_free (parent_name); + + folder_real = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, imap_store->dir_sep); + response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", folder_real); + + if (response) { + camel_imap_store_summary_add_from_full(imap_store->summary, folder_real, imap_store->dir_sep); + + camel_imap_response_free (imap_store, response); + + response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name); + } + g_free(folder_real); + if (!response) { + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + return NULL; + } + } else if (flags & CAMEL_STORE_FOLDER_EXCL) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Cannot create folder `%s': folder exists."), + folder_name); + + camel_imap_response_free_without_processing (imap_store, response); + + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + + return NULL; + } + + storage_path = g_strdup_printf("%s/folders", imap_store->storage_path); + folder_dir = e_path_to_physical (storage_path, folder_name); + g_free(storage_path); + new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex); + g_free (folder_dir); + if (new_folder) { + CamelException local_ex; + + imap_store->current_folder = new_folder; + camel_object_ref (new_folder); + camel_exception_init (&local_ex); + camel_imap_folder_selected (new_folder, response, &local_ex); + + if (camel_exception_is_set (&local_ex)) { + camel_exception_xfer (ex, &local_ex); + camel_object_unref (imap_store->current_folder); + imap_store->current_folder = NULL; + camel_object_unref (new_folder); + new_folder = NULL; + } + } + camel_imap_response_free_without_processing (imap_store, response); + + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + + return new_folder; +} + +static CamelFolder * +get_folder_offline (CamelStore *store, const char *folder_name, + guint32 flags, CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelFolder *new_folder; + char *folder_dir, *storage_path; + + if (!imap_store->connected && + !camel_service_connect (CAMEL_SERVICE (store), ex)) + return NULL; + + if (!strcasecmp (folder_name, "INBOX")) + folder_name = "INBOX"; + + storage_path = g_strdup_printf("%s/folders", imap_store->storage_path); + folder_dir = e_path_to_physical (storage_path, folder_name); + g_free(storage_path); + if (!folder_dir || access (folder_dir, F_OK) != 0) { + g_free (folder_dir); + camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER, + _("No such folder %s"), folder_name); + return NULL; + } + + new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex); + g_free (folder_dir); + + return new_folder; +} + +static void +delete_folder (CamelStore *store, const char *folder_name, CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelImapResponse *response; + + if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex)) + return; + + /* make sure this folder isn't currently SELECTed */ + response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX"); + if (response) { + camel_imap_response_free_without_processing (imap_store, response); + + CAMEL_SERVICE_LOCK (imap_store, connect_lock); + + if (imap_store->current_folder) + camel_object_unref (imap_store->current_folder); + /* no need to actually create a CamelFolder for INBOX */ + imap_store->current_folder = NULL; + + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + } else + return; + + response = camel_imap_command (imap_store, NULL, ex, "DELETE %F", + folder_name); + + if (response) { + camel_imap_response_free (imap_store, response); + imap_forget_folder (imap_store, folder_name, ex); + } +} + +static void +manage_subscriptions (CamelStore *store, const char *old_name, gboolean subscribe) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelStoreInfo *si; + int olen = strlen(old_name); + const char *path; + int i, count; + + count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary); + for (i=0;i<count;i++) { + si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i); + if (si) { + path = camel_store_info_path(imap_store->summary, si); + if (strncmp(path, old_name, olen) == 0) { + if (subscribe) + subscribe_folder(store, path, NULL); + else + unsubscribe_folder(store, path, NULL); + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } + } +} + +static void +rename_folder_info (CamelImapStore *imap_store, const char *old_name, const char *new_name) +{ + int i, count; + CamelStoreInfo *si; + int olen = strlen(old_name); + const char *path; + char *npath, *nfull; + + count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary); + for (i=0;i<count;i++) { + si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i); + if (si == NULL) + continue; + path = camel_store_info_path(imap_store->summary, si); + if (strncmp(path, old_name, olen) == 0) { + if (strlen(path) > olen) + npath = g_strdup_printf("%s/%s", new_name, path+olen+1); + else + npath = g_strdup(new_name); + nfull = camel_imap_store_summary_path_to_full(imap_store->summary, npath, imap_store->dir_sep); + + /* workaround for broken server (courier uses '.') that doesn't rename + subordinate folders as required by rfc 2060 */ + if (imap_store->dir_sep == '.') { + CamelImapResponse *response; + + response = camel_imap_command (imap_store, NULL, NULL, "RENAME %F %S", path, nfull); + if (response) + camel_imap_response_free (imap_store, response); + } + + camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_STORE_INFO_PATH, npath); + camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_IMAP_STORE_INFO_FULL_NAME, nfull); + + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + g_free(nfull); + g_free(npath); + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } +} + +static void +rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelImapResponse *response; + char *oldpath, *newpath, *storage_path, *new_name; + + if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex)) + return; + + /* make sure this folder isn't currently SELECTed - it's + actually possible to rename INBOX but if you do another + INBOX will immediately be created by the server */ + response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX"); + if (response) { + camel_imap_response_free_without_processing (imap_store, response); + + CAMEL_SERVICE_LOCK (imap_store, connect_lock); + + if (imap_store->current_folder) + camel_object_unref (imap_store->current_folder); + /* no need to actually create a CamelFolder for INBOX */ + imap_store->current_folder = NULL; + + CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); + } else + return; + + imap_store->renaming = TRUE; + + if (store->flags & CAMEL_STORE_SUBSCRIPTIONS) + manage_subscriptions(store, old_name, FALSE); + + new_name = camel_imap_store_summary_path_to_full(imap_store->summary, new_name_in, imap_store->dir_sep); + response = camel_imap_command (imap_store, NULL, ex, "RENAME %F %S", old_name, new_name); + + if (!response) { + if (store->flags & CAMEL_STORE_SUBSCRIPTIONS) + manage_subscriptions(store, old_name, TRUE); + g_free(new_name); + imap_store->renaming = FALSE; + return; + } + + camel_imap_response_free (imap_store, response); + + /* rename summary, and handle broken server */ + rename_folder_info(imap_store, old_name, new_name_in); + + if (store->flags & CAMEL_STORE_SUBSCRIPTIONS) + manage_subscriptions(store, new_name_in, TRUE); + + storage_path = g_strdup_printf("%s/folders", imap_store->storage_path); + oldpath = e_path_to_physical (storage_path, old_name); + newpath = e_path_to_physical (storage_path, new_name_in); + g_free(storage_path); + + /* So do we care if this didn't work? Its just a cache? */ + if (rename (oldpath, newpath) == -1) { + g_warning ("Could not rename message cache '%s' to '%s': %s: cache reset", + oldpath, newpath, strerror (errno)); + } + + g_free (oldpath); + g_free (newpath); + g_free(new_name); + + imap_store->renaming = FALSE; +} + +static CamelFolderInfo * +create_folder (CamelStore *store, const char *parent_name, + const char *folder_name, CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + char *full_name, *resp, *thisone, *parent_real, *real_name; + CamelImapResponse *response; + CamelException internal_ex; + CamelFolderInfo *root = NULL; + gboolean need_convert; + int i = 0, flags; + const char *c; + + if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex)) + return NULL; + if (!parent_name) + parent_name = ""; + + c = folder_name; + while (*c && *c != imap_store->dir_sep && !strchr ("#%*", *c)) + c++; + + if (*c != '\0') { + camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH, + _("The folder name \"%s\" is invalid because it contains the character \"%c\""), + folder_name, *c); + return NULL; + } + + /* check if the parent allows inferiors */ + + /* FIXME: use storesummary directly */ + parent_real = camel_imap_store_summary_full_from_path(imap_store->summary, parent_name); + if (parent_real == NULL) { + camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE, + _("Unknown parent folder: %s"), parent_name); + return NULL; + } + + need_convert = FALSE; + response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S", + parent_real); + if (!response) /* whoa, this is bad */ { + g_free(parent_real); + return NULL; + } + + /* FIXME: does not handle unexpected circumstances very well */ + for (i = 0; i < response->untagged->len; i++) { + resp = response->untagged->pdata[i]; + + if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone)) + continue; + + if (strcmp (thisone, parent_name) == 0) { + if (flags & CAMEL_FOLDER_NOINFERIORS) + need_convert = TRUE; + break; + } + } + + camel_imap_response_free (imap_store, response); + + camel_exception_init (&internal_ex); + + /* if not, check if we can delete it and recreate it */ + if (need_convert) { + struct imap_status_item *items, *item; + guint32 messages = 0; + char *name; + + item = items = get_folder_status (imap_store, parent_name, "MESSAGES"); + while (item != NULL) { + if (!g_ascii_strcasecmp (item->name, "MESSAGES")) { + messages = item->value; + break; + } + + item = item->next; + } + + imap_status_item_free (items); + + if (messages > 0) { + camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE, + _("The parent folder is not allowed to contain subfolders")); + g_free(parent_real); + return NULL; + } + + /* delete the old parent and recreate it */ + delete_folder (store, parent_name, &internal_ex); + if (camel_exception_is_set (&internal_ex)) { + camel_exception_xfer (ex, &internal_ex); + return NULL; + } + + /* add the dirsep to the end of parent_name */ + name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep); + response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", + name); + g_free (name); + + if (!response) { + g_free(parent_real); + return NULL; + } else + camel_imap_response_free (imap_store, response); + + root = imap_build_folder_info(imap_store, parent_name); + } + + /* ok now we can create the folder */ + real_name = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, imap_store->dir_sep); + full_name = imap_concat (imap_store, parent_real, real_name); + g_free(real_name); + response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", full_name); + + if (response) { + CamelImapStoreInfo *si; + CamelFolderInfo *fi; + + camel_imap_response_free (imap_store, response); + + si = camel_imap_store_summary_add_from_full(imap_store->summary, full_name, imap_store->dir_sep); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + fi = imap_build_folder_info(imap_store, camel_store_info_path(imap_store->summary, si)); + fi->flags |= CAMEL_FOLDER_NOCHILDREN; + if (root) { + root->child = fi; + fi->parent = root; + } else { + root = fi; + } + camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root); + } else if (root) { + /* need to re-recreate the folder we just deleted */ + camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root); + camel_folder_info_free(root); + root = NULL; + } + + g_free (full_name); + g_free(parent_real); + + return root; +} + +static CamelFolderInfo * +parse_list_response_as_folder_info (CamelImapStore *imap_store, + const char *response) +{ + CamelFolderInfo *fi; + int flags; + char sep, *dir, *path; + CamelURL *url; + CamelImapStoreInfo *si; + guint32 newflags; + + if (!imap_parse_list_response (imap_store, response, &flags, &sep, &dir)) + return NULL; + + /* FIXME: should use imap_build_folder_info, note the differences with param setting tho */ + + si = camel_imap_store_summary_add_from_full(imap_store->summary, dir, sep?sep:'/'); + if (si == NULL) + return NULL; + + newflags = (si->info.flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) | (flags & ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED); + if (si->info.flags != newflags) { + si->info.flags = newflags; + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + } + + fi = g_new0 (CamelFolderInfo, 1); + fi->name = g_strdup(camel_store_info_name(imap_store->summary, si)); + fi->full_name = g_strdup(camel_store_info_path(imap_store->summary, si)); + if (!g_ascii_strcasecmp(fi->full_name, "inbox")) + flags |= CAMEL_FOLDER_SYSTEM; + /* HACK: some servers report noinferiors for all folders (uw-imapd) + We just translate this into nochildren, and let the imap layer enforce + it. See create folder */ + if (flags & CAMEL_FOLDER_NOINFERIORS) + flags = (fi->flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN; + fi->flags = flags; + + url = camel_url_new (imap_store->base_url, NULL); + path = alloca(strlen(fi->full_name)+2); + sprintf(path, "/%s", fi->full_name); + camel_url_set_path(url, path); + + if (flags & CAMEL_FOLDER_NOSELECT || fi->name[0] == 0) + camel_url_set_param (url, "noselect", "yes"); + fi->uri = camel_url_to_string (url, 0); + camel_url_free (url); + + /* FIXME: redundant */ + if (flags & CAMEL_IMAP_FOLDER_UNMARKED) + fi->unread = -1; + + return fi; +} + +/* returns true if full_name is a sub-folder of top, or is top */ +static int +imap_is_subfolder(const char *full_name, const char *top) +{ + size_t len = strlen(top); + + /* Looks for top being a full-path subset of full_name. + Handle IMAP Inbox case insensitively */ + + if (g_ascii_strncasecmp(top, "inbox", 5) == 0 + && (top[5] == 0 || top[5] == '/') + && g_ascii_strncasecmp(full_name, "inbox", 5) == 0 + && (full_name[5] == 0 || full_name[5] == '/')) { + full_name += 5; + top += 5; + len -= 5; + } + + return top[0] == 0 + || (strncmp(full_name, top, len) == 0 + && (full_name[len] == 0 + || full_name[len] == '/')); +} + +/* this is used when lsub doesn't provide very useful information */ +static GPtrArray * +get_subscribed_folders (CamelImapStore *imap_store, const char *top, CamelException *ex) +{ + GPtrArray *names, *folders; + int i; + CamelStoreInfo *si; + CamelImapResponse *response; + CamelFolderInfo *fi; + char *result; + int haveinbox = FALSE; + + if (camel_debug("imap:folder_info")) + printf(" get_subscribed folders\n"); + + folders = g_ptr_array_new (); + names = g_ptr_array_new (); + for (i=0;(si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i));i++) { + if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED + && imap_is_subfolder(camel_store_info_path(imap_store->summary, si), top)) { + g_ptr_array_add(names, (char *)camel_imap_store_info_full_name(imap_store->summary, si)); + haveinbox = haveinbox || strcasecmp(camel_imap_store_info_full_name(imap_store->summary, si), "INBOX") == 0; + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } + + if (!haveinbox) + g_ptr_array_add (names, "INBOX"); + + for (i = 0; i < names->len; i++) { + response = camel_imap_command (imap_store, NULL, ex, + "LIST \"\" %S", + names->pdata[i]); + if (!response) + break; + + result = camel_imap_response_extract (imap_store, response, "LIST", NULL); + if (!result) { + camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, names->pdata[i]); + g_ptr_array_remove_index_fast (names, i); + i--; + continue; + } + + fi = parse_list_response_as_folder_info (imap_store, result); + g_free (result); + if (!fi) + continue; + + if (!imap_is_subfolder(fi->full_name, top)) { + camel_folder_info_free (fi); + continue; + } + + g_ptr_array_add (folders, fi); + } + + g_ptr_array_free (names, TRUE); + + return folders; +} + +static int imap_match_pattern(char dir_sep, const char *pattern, const char *name) +{ + char p, n; + + p = *pattern++; + n = *name++; + while (n && p) { + if (n == p) { + p = *pattern++; + n = *name++; + } else if (p == '%') { + if (n != dir_sep) { + n = *name++; + } else { + p = *pattern++; + } + } else if (p == '*') { + return TRUE; + } else + return FALSE; + } + + return n == 0 && (p == '%' || p == 0); +} + +static void +get_folders_online (CamelImapStore *imap_store, const char *pattern, + GPtrArray *folders, gboolean lsub, CamelException *ex) +{ + CamelImapResponse *response; + CamelFolderInfo *fi; + char *list; + int i, count; + GHashTable *present; + CamelStoreInfo *si; + + response = camel_imap_command (imap_store, NULL, ex, + "%s \"\" %S", lsub ? "LSUB" : "LIST", + pattern); + if (!response) + return; + + present = g_hash_table_new(g_str_hash, g_str_equal); + for (i = 0; i < response->untagged->len; i++) { + list = response->untagged->pdata[i]; + fi = parse_list_response_as_folder_info (imap_store, list); + if (fi) { + g_ptr_array_add(folders, fi); + g_hash_table_insert(present, fi->full_name, fi); + } + } + camel_imap_response_free (imap_store, response); + + /* update our summary to match the server */ + count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary); + for (i=0;i<count;i++) { + si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i); + if (si == NULL) + continue; + + if (imap_match_pattern(imap_store->dir_sep, pattern, camel_imap_store_info_full_name(imap_store->summary, si))) { + if (g_hash_table_lookup(present, camel_store_info_path(imap_store->summary, si)) != NULL) { + if (lsub && (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) { + si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED; + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + } + } else { + if (lsub) { + if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) { + si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED; + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + } + } else { + camel_store_summary_remove((CamelStoreSummary *)imap_store->summary, si); + count--; + i--; + } + } + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } + g_hash_table_destroy(present); +} + +#if 0 +static void +dumpfi(CamelFolderInfo *fi) +{ + int depth; + CamelFolderInfo *n = fi; + + if (fi == NULL) + return; + + depth = 0; + while (n->parent) { + depth++; + n = n->parent; + } + + while (fi) { + printf("%-40s %-30s %*s\n", fi->path, fi->full_name, depth*2+strlen(fi->url), fi->url); + if (fi->child) + dumpfi(fi->child); + fi = fi->sibling; + } +} +#endif + +static void +fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags) +{ + CamelFolder *folder; + + fi->unread = -1; + fi->total = -1; + folder = camel_object_bag_peek(store->folders, fi->full_name); + if (folder) { + if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0) + /* we use connect lock for everything, so this should be safe */ + CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, NULL); + fi->unread = camel_folder_get_unread_message_count(folder); + fi->total = camel_folder_get_message_count(folder); + camel_object_unref(folder); + } else { + char *storage_path, *folder_dir, *path; + CamelFolderSummary *s; + + /* This is a lot of work for one path! */ + storage_path = g_strdup_printf("%s/folders", ((CamelImapStore *)store)->storage_path); + folder_dir = e_path_to_physical(storage_path, fi->full_name); + path = g_strdup_printf("%s/summary", folder_dir); + s = (CamelFolderSummary *)camel_object_new(camel_imap_summary_get_type()); + camel_folder_summary_set_build_content(s, TRUE); + camel_folder_summary_set_filename(s, path); + if (camel_folder_summary_header_load(s) != -1) { + fi->unread = s->unread_count; + fi->total = s->saved_count; + } + g_free(storage_path); + g_free(folder_dir); + g_free(path); + + camel_object_unref(s); + } +} + +/* NB: We should have connect_lock at this point */ +static void +get_folder_counts(CamelImapStore *imap_store, CamelFolderInfo *fi, CamelException *ex) +{ + GSList *q; + CamelFolder *folder; + + /* non-recursive breath first search */ + + q = g_slist_append(NULL, fi); + + while (q) { + fi = q->data; + q = g_slist_remove_link(q, q); + + while (fi) { + /* ignore noselect folders, and check only inbox if we only check inbox */ + if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0 + && ( (imap_store->parameters & IMAP_PARAM_CHECK_ALL) + || g_ascii_strcasecmp(fi->full_name, "inbox") == 0) ) { + + /* For the current folder, poke it to check for new + * messages and then report that number, rather than + * doing a STATUS command. + */ + if (imap_store->current_folder && strcmp(imap_store->current_folder->full_name, fi->full_name) == 0) { + /* we bypass the folder locking otherwise we can deadlock. we use the command lock for + any operations anyway so this is 'safe'. See comment above imap_store_refresh_folders() for info */ + CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(imap_store->current_folder))->refresh_info(imap_store->current_folder, ex); + fi->unread = camel_folder_get_unread_message_count (imap_store->current_folder); + fi->total = camel_folder_get_message_count(imap_store->current_folder); + } else { + struct imap_status_item *items, *item; + + fi->unread = -1; + fi->total = -1; + + item = items = get_folder_status (imap_store, fi->full_name, "MESSAGES UNSEEN"); + while (item != NULL) { + if (!g_ascii_strcasecmp (item->name, "MESSAGES")) { + fi->total = item->value; + } else if (!g_ascii_strcasecmp (item->name, "UNSEEN")) { + fi->unread = item->value; + } + + item = item->next; + } + + imap_status_item_free (items); + + /* if we have this folder open, and the unread count has changed, update */ + folder = camel_object_bag_peek(CAMEL_STORE(imap_store)->folders, fi->full_name); + if (folder) { + if (fi->unread != camel_folder_get_unread_message_count(folder)) { + CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex); + fi->unread = camel_folder_get_unread_message_count(folder); + fi->total = camel_folder_get_message_count(folder); + } + camel_object_unref(folder); + } + } + } else { + /* since its cheap, get it if they're open/consult summary file */ + fill_fi((CamelStore *)imap_store, fi, 0); + } + + if (fi->child) + q = g_slist_append(q, fi->child); + fi = fi->next; + } + } +} + +/* imap needs to treat inbox case insensitive */ +/* we'll assume the names are normalised already */ +static guint folder_hash(const void *ap) +{ + const char *a = ap; + + if (strcasecmp(a, "INBOX") == 0) + a = "INBOX"; + + return g_str_hash(a); +} + +static int folder_eq(const void *ap, const void *bp) +{ + const char *a = ap; + const char *b = bp; + + if (strcasecmp(a, "INBOX") == 0) + a = "INBOX"; + if (strcasecmp(b, "INBOX") == 0) + b = "INBOX"; + + return g_str_equal(a, b); +} + +static GSList * +get_folders_add_folders(GSList *p, int recurse, GHashTable *infos, GPtrArray *folders, GPtrArray *folders_out) +{ + CamelFolderInfo *oldfi, *fi; + int i; + + /* This is a nasty mess, because some servers will return + broken results from LIST or LSUB if you use '%'. e.g. you + may get (many) duplicate names, and worse, names may have + conflicting flags. */ + for (i=0; i<folders->len; i++) { + fi = folders->pdata[i]; + oldfi = g_hash_table_lookup(infos, fi->full_name); + if (oldfi == NULL) { + d(printf(" new folder '%s'\n", fi->full_name)); + g_hash_table_insert(infos, fi->full_name, fi); + if (recurse) + p = g_slist_prepend(p, fi); + g_ptr_array_add(folders_out, fi); + } else { + d(printf(" old folder '%s', old flags %08x new flags %08x\n", fi->full_name, oldfi->flags, fi->flags)); + + /* need to special-case noselect, since it also affects the uri */ + if ((oldfi->flags & CAMEL_FOLDER_NOSELECT) != 0 + && (fi->flags & CAMEL_FOLDER_NOSELECT) == 0) { + g_free(oldfi->uri); + oldfi->uri = fi->uri; + fi->uri = NULL; + } + + /* some flags are anded together, some are or'd */ + + oldfi->flags = (oldfi->flags & fi->flags & (CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_NOINFERIORS)) + | ((oldfi->flags | fi->flags) & ~(CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_NOINFERIORS)); + + camel_folder_info_free(fi); + } + } + + g_ptr_array_set_size(folders, 0); + + return p; +} + +static GPtrArray * +get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + GSList *q, *p = NULL; + GHashTable *infos; + int i; + GPtrArray *folders, *folders_out; + CamelFolderInfo *fi; + char *name; + int depth = 0; + int haveinbox = 0; + static int imap_max_depth = 0; + + if (!camel_imap_store_connected (imap_store, ex)) + return NULL; + + if (camel_debug("imap:folder_info")) + printf(" get_folders\n"); + + /* allow megalomaniacs to override the max of 10 */ + if (imap_max_depth == 0) { + name = getenv("CAMEL_IMAP_MAX_DEPTH"); + if (name) { + imap_max_depth = atoi (name); + imap_max_depth = MIN (MAX (imap_max_depth, 0), 2); + } else + imap_max_depth = 10; + } + + infos = g_hash_table_new(folder_hash, folder_eq); + + /* get starting point & strip trailing '/' */ + if (top[0] == 0) { + if (imap_store->namespace) { + top = imap_store->namespace; + i = strlen(top)-1; + name = g_malloc(i+2); + strcpy(name, top); + while (i>0 && name[i] == imap_store->dir_sep) + name[i--] = 0; + } else + name = g_strdup(""); + } else { + name = camel_imap_store_summary_full_from_path(imap_store->summary, top); + if (name == NULL) + name = camel_imap_store_summary_path_to_full(imap_store->summary, top, imap_store->dir_sep); + } + + d(printf("\n\nList '%s' %s\n", name, flags&CAMEL_STORE_FOLDER_INFO_RECURSIVE?"RECURSIVE":"NON-RECURSIVE")); + + folders_out = g_ptr_array_new(); + folders = g_ptr_array_new(); + + /* first get working list of names */ + get_folders_online (imap_store, name[0]?name:"%", folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex); + if (camel_exception_is_set(ex)) + goto fail; + for (i=0; i<folders->len && !haveinbox; i++) { + fi = folders->pdata[i]; + haveinbox = (strcasecmp(fi->full_name, "INBOX")) == 0; + } + + if (!haveinbox && top == imap_store->namespace) { + get_folders_online (imap_store, "INBOX", folders, + flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex); + + if (camel_exception_is_set (ex)) + goto fail; + } + + p = get_folders_add_folders(p, TRUE, infos, folders, folders_out); + + /* p is a reversed list of pending folders for the next level, q is the list of folders for this */ + while (p) { + q = g_slist_reverse(p); + + p = NULL; + while (q) { + fi = q->data; + + q = g_slist_remove_link(q, q); + + d(printf("Checking parent folder '%s'\n", fi->full_name)); + + /* First if we're not recursive mode on the top level, and we know it has or doesn't + or can't have children, no need to go further - a bit ugly */ + if ( top == imap_store->namespace + && (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) == 0 + && (fi->flags & (CAMEL_FOLDER_CHILDREN|CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) != 0) { + /* do nothing */ + d(printf(" not interested in folder right now ...\n")); + } + /* Otherwise, if this has (or might have) children, scan it */ + else if ( (fi->flags & (CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) == 0 + || (fi->flags & CAMEL_FOLDER_CHILDREN) != 0) { + char *n, *real; + + real = camel_imap_store_summary_full_from_path(imap_store->summary, fi->full_name); + n = imap_concat(imap_store, real?real:fi->full_name, "%"); + get_folders_online(imap_store, n, folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex); + g_free(n); + g_free(real); + + if (camel_exception_is_set (ex)) + goto fail; + + if (folders->len > 0) + fi->flags |= CAMEL_FOLDER_CHILDREN; + + p = get_folders_add_folders(p, (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) && depth<imap_max_depth, + infos, folders, folders_out); + } + } + depth++; + } + + g_ptr_array_free(folders, TRUE); + g_hash_table_destroy(infos); + g_free(name); + + return folders_out; +fail: + g_ptr_array_free(folders, TRUE); + g_ptr_array_free(folders_out, TRUE); + g_hash_table_destroy(infos); + g_slist_free (p); + g_free(name); + + return NULL; +} + +static CamelFolderInfo * +get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelFolderInfo *tree = NULL; + GPtrArray *folders; + + if (top == NULL) + top = ""; + + if (camel_debug("imap:folder_info")) + printf("get folder info online\n"); + + CAMEL_SERVICE_LOCK(store, connect_lock); + + if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) + && !(imap_store->capabilities & IMAP_CAPABILITY_useful_lsub) + && (imap_store->parameters & IMAP_PARAM_CHECK_ALL)) + folders = get_subscribed_folders(imap_store, top, ex); + else + folders = get_folders(store, top, flags, ex); + + if (folders == NULL) + goto done; + + tree = camel_folder_info_build(folders, top, '/', TRUE); + g_ptr_array_free(folders, TRUE); + + if (!(flags & CAMEL_STORE_FOLDER_INFO_FAST)) + get_folder_counts(imap_store, tree, ex); + + d(dumpfi(tree)); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); +done: + CAMEL_SERVICE_UNLOCK(store, connect_lock); + + return tree; +} + +static gboolean +get_one_folder_offline (const char *physical_path, const char *path, gpointer data) +{ + GPtrArray *folders = data; + CamelImapStore *imap_store = folders->pdata[0]; + CamelFolderInfo *fi; + CamelStoreInfo *si; + + if (*path != '/') + return TRUE; + + /* folder_info_build will insert parent nodes as necessary and mark + * them as noselect, which is information we actually don't have at + * the moment. So let it do the right thing by bailing out if it's + * not a folder we're explicitly interested in. + */ + + si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, path+1); + if (si) { + if ((((CamelStore *)imap_store)->flags & CAMEL_STORE_SUBSCRIPTIONS) == 0 + || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) { + fi = imap_build_folder_info(imap_store, path+1); + fi->flags = si->flags; + /* HACK: some servers report noinferiors for all folders (uw-imapd) + We just translate this into nochildren, and let the imap layer enforce + it. See create folder */ + if (fi->flags & CAMEL_FOLDER_NOINFERIORS) + fi->flags = (fi->flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN; + + if (si->flags & CAMEL_FOLDER_NOSELECT) { + CamelURL *url = camel_url_new(fi->uri, NULL); + + camel_url_set_param (url, "noselect", "yes"); + g_free(fi->uri); + fi->uri = camel_url_to_string (url, 0); + camel_url_free (url); + } else { + fill_fi((CamelStore *)imap_store, fi, 0); + } + g_ptr_array_add (folders, fi); + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } + + return TRUE; +} + +static CamelFolderInfo * +get_folder_info_offline (CamelStore *store, const char *top, + guint32 flags, CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelFolderInfo *fi; + GPtrArray *folders; + char *storage_path; + + if (camel_debug("imap:folder_info")) + printf("get folder info offline\n"); + + if (!imap_store->connected && + !camel_service_connect (CAMEL_SERVICE (store), ex)) + return NULL; + + if ((store->flags & CAMEL_STORE_SUBSCRIPTIONS) && + !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)) { + camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex); + return NULL; + } + + /* FIXME: obey other flags */ + + folders = g_ptr_array_new (); + + /* A kludge to avoid having to pass a struct to the callback */ + g_ptr_array_add (folders, imap_store); + storage_path = g_strdup_printf("%s/folders", imap_store->storage_path); + if (!e_path_find_folders (storage_path, get_one_folder_offline, folders)) { + camel_disco_store_check_online (CAMEL_DISCO_STORE (imap_store), ex); + fi = NULL; + } else { + g_ptr_array_remove_index_fast (folders, 0); + fi = camel_folder_info_build (folders, "", '/', TRUE); + } + g_free(storage_path); + + g_ptr_array_free (folders, TRUE); + return fi; +} + +static gboolean +folder_subscribed (CamelStore *store, const char *folder_name) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelStoreInfo *si; + int truth = FALSE; + + si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name); + if (si) { + truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0; + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } + + return truth; +} + +/* Note: folder_name must match a folder as listed with get_folder_info() -> full_name */ +static void +subscribe_folder (CamelStore *store, const char *folder_name, + CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelImapResponse *response; + CamelFolderInfo *fi; + CamelStoreInfo *si; + + if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex)) + return; + if (!camel_imap_store_connected (imap_store, ex)) + return; + + response = camel_imap_command (imap_store, NULL, ex, + "SUBSCRIBE %F", folder_name); + if (!response) + return; + camel_imap_response_free (imap_store, response); + + si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name); + if (si) { + if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) { + si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED; + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } + + if (imap_store->renaming) { + /* we don't need to emit a "folder_subscribed" signal + if we are in the process of renaming folders, so we + are done here... */ + return; + } + + fi = imap_build_folder_info(imap_store, folder_name); + fi->flags |= CAMEL_FOLDER_NOCHILDREN; + + camel_object_trigger_event (CAMEL_OBJECT (store), "folder_subscribed", fi); + camel_folder_info_free (fi); +} + +static void +unsubscribe_folder (CamelStore *store, const char *folder_name, + CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelImapResponse *response; + + if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex)) + return; + if (!camel_imap_store_connected (imap_store, ex)) + return; + + response = camel_imap_command (imap_store, NULL, ex, + "UNSUBSCRIBE %F", folder_name); + if (!response) + return; + camel_imap_response_free (imap_store, response); + + imap_folder_effectively_unsubscribed (imap_store, folder_name, ex); +} + +#if 0 +static gboolean +folder_flags_have_changed (CamelFolder *folder) +{ + CamelMessageInfo *info; + int i, max; + + max = camel_folder_summary_count (folder->summary); + for (i = 0; i < max; i++) { + info = camel_folder_summary_index (folder->summary, i); + if (!info) + continue; + if (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) { + return TRUE; + } + } + + return FALSE; +} +#endif + + +gboolean +camel_imap_store_connected (CamelImapStore *store, CamelException *ex) +{ + if (store->istream == NULL || !store->connected) + return camel_service_connect (CAMEL_SERVICE (store), ex); + return TRUE; +} + + +/* FIXME: please god, when will the hurting stop? Thus function is so + fucking broken it's not even funny. */ +ssize_t +camel_imap_store_readline (CamelImapStore *store, char **dest, CamelException *ex) +{ + CamelStreamBuffer *stream; + char linebuf[1024]; + GByteArray *ba; + ssize_t nread; + + g_return_val_if_fail (CAMEL_IS_IMAP_STORE (store), -1); + g_return_val_if_fail (dest, -1); + + *dest = NULL; + + /* Check for connectedness. Failed (or cancelled) operations will + * close the connection. We can't expect a read to have any + * meaning if we reconnect, so always set an exception. + */ + + if (!camel_imap_store_connected (store, ex)) + return -1; + + stream = CAMEL_STREAM_BUFFER (store->istream); + + ba = g_byte_array_new (); + while ((nread = camel_stream_buffer_gets (stream, linebuf, sizeof (linebuf))) > 0) { + g_byte_array_append (ba, linebuf, nread); + if (linebuf[nread - 1] == '\n') + break; + } + + if (nread <= 0) { + if (errno == EINTR) + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled")); + else + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, + _("Server unexpectedly disconnected: %s"), + g_strerror (errno)); + + camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL); + g_byte_array_free (ba, TRUE); + return -1; + } + + if (camel_verbose_debug) { + fprintf (stderr, "received: "); + fwrite (ba->data, 1, ba->len, stderr); + } + + /* camel-imap-command.c:imap_read_untagged expects the CRLFs + to be stripped off and be nul-terminated *sigh* */ + nread = ba->len - 1; + ba->data[nread] = '\0'; + if (ba->data[nread - 1] == '\r') { + ba->data[nread - 1] = '\0'; + nread--; + } + + *dest = ba->data; + g_byte_array_free (ba, FALSE); + + return nread; +} diff --git a/camel/providers/imapp/camel-imapp-store.c b/camel/providers/imapp/camel-imapp-store.c index 290d3c0a1..2176c91cc 100644 --- a/camel/providers/imapp/camel-imapp-store.c +++ b/camel/providers/imapp/camel-imapp-store.c @@ -189,29 +189,32 @@ connect_to_server (CamelService *service, int ssl_mode, int try_starttls) CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service); CamelStream * volatile tcp_stream = NULL; CamelIMAPPStream * volatile imap_stream = NULL; - struct hostent *h = NULL; - int ret, port; + int ret; CamelException *ex; ex = camel_exception_new(); CAMEL_TRY { + char *serv; + struct addrinfo *ai, hints = { 0 }; + /* parent class connect initialization */ CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex); if (ex->id) camel_exception_throw_ex(ex); - h = camel_service_gethost(service, ex); - if (ex->id) - camel_exception_throw_ex(ex); - - port = service->url->port ? service->url->port : IMAP_PORT; - + if (service->url->port) { + serv = g_alloca(16); + sprintf(serv, "%d", service->url->port); + } else + serv = "imap"; + #ifdef HAVE_SSL if (camel_url_get_param (service->url, "use_ssl")) { if (try_starttls) tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS); else { - port = service->url->port ? service->url->port : 995; + if (service->url->port == 0) + serv = "imaps"; tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS); } } else { @@ -220,16 +223,21 @@ connect_to_server (CamelService *service, int ssl_mode, int try_starttls) #else tcp_stream = camel_tcp_stream_raw_new (); #endif /* HAVE_SSL */ - - ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port); - camel_free_host (h); + + hints.ai_socktype = SOCK_STREAM; + ai = camel_getaddrinfo(service->url->host, serv, &hints, ex); + if (ex->id) + camel_exception_throw_ex(ex); + + ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai); + camel_freeaddrinfo(ai); if (ret == -1) { if (errno == EINTR) camel_exception_throw(CAMEL_EXCEPTION_USER_CANCEL, _("Connection cancelled")); else camel_exception_throw(CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not connect to %s (port %d): %s"), - service->url->host, port, strerror(errno)); + _("Could not connect to %s (port %s): %s"), + service->url->host, serv, strerror(errno)); } imap_stream = (CamelIMAPPStream *)camel_imapp_stream_new(tcp_stream); diff --git a/camel/providers/nntp/camel-nntp-store.c b/camel/providers/nntp/camel-nntp-store.c index 31048e7a1..c03fe4a7d 100644 --- a/camel/providers/nntp/camel-nntp-store.c +++ b/camel/providers/nntp/camel-nntp-store.c @@ -162,9 +162,10 @@ connect_to_server (CamelService *service, int ssl_mode, CamelException *ex) gboolean retval = FALSE; unsigned char *buf; unsigned int len; - struct hostent *h; - int port, ret; + int ret; char *path; + struct addrinfo *ai, hints = { 0 }; + char *serv; CAMEL_NNTP_STORE_LOCK(store, command_lock); @@ -181,15 +182,17 @@ connect_to_server (CamelService *service, int ssl_mode, CamelException *ex) camel_data_cache_set_expire_age (store->cache, 60*60*24*14); camel_data_cache_set_expire_access (store->cache, 60*60*24*5); } - - if (!(h = camel_service_gethost (service, ex))) - goto fail; - - port = service->url->port ? service->url->port : NNTP_PORT; + + if (service->url->port) { + serv = g_alloca(16); + sprintf(serv, "%d", service->url->port); + } else + serv = "nntp"; #ifdef HAVE_SSL if (ssl_mode != USE_SSL_NEVER) { - port = service->url->port ? service->url->port : NNTPS_PORT; + if (service->url->port == 0) + serv = "nntps"; tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3); } else { tcp_stream = camel_tcp_stream_raw_new (); @@ -197,17 +200,24 @@ connect_to_server (CamelService *service, int ssl_mode, CamelException *ex) #else tcp_stream = camel_tcp_stream_raw_new (); #endif /* HAVE_SSL */ + + hints.ai_socktype = SOCK_STREAM; + ai = camel_getaddrinfo(service->url->host, serv, &hints, ex); + if (ai == NULL) { + camel_object_unref(tcp_stream); + goto fail; + } - ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port); - camel_free_host (h); + ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai); + camel_freeaddrinfo(ai); if (ret == -1) { if (errno == EINTR) camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Connection cancelled")); else camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not connect to %s (port %d): %s"), - service->url->host, port, g_strerror (errno)); + _("Could not connect to %s (port %s): %s"), + service->url->host, serv, g_strerror (errno)); camel_object_unref (tcp_stream); diff --git a/camel/providers/pop3/camel-pop3-store.c b/camel/providers/pop3/camel-pop3-store.c index 96f9295b5..f2d903b1c 100644 --- a/camel/providers/pop3/camel-pop3-store.c +++ b/camel/providers/pop3/camel-pop3-store.c @@ -148,52 +148,59 @@ connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelE CamelPOP3Store *store = CAMEL_POP3_STORE (service); CamelStream *tcp_stream; CamelPOP3Command *pc; - struct hostent *h; guint32 flags = 0; int clean_quit; - int ret, port; - - h = camel_service_gethost (service, ex); - if (!h) - return FALSE; - - port = service->url->port ? service->url->port : 110; - + int ret; + struct addrinfo *ai, hints = { 0 }; + char *serv; + + if (service->url->port) { + serv = g_alloca(16); + sprintf(serv, "%d", service->url->port); + } else + serv = "pop3"; + if (ssl_mode != USE_SSL_NEVER) { #ifdef HAVE_SSL if (try_starttls) { tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS); } else { - port = service->url->port ? service->url->port : 995; + if (service->url->port == 0) + serv = "pop3s"; tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS); } #else - if (!try_starttls) - port = service->url->port ? service->url->port : 995; + if (!try_starttls && service->url->port == 0) + serv = "pop3s"; camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not connect to %s (port %d): %s"), - service->url->host, port, + _("Could not connect to %s (port %s): %s"), + service->url->host, serv, _("SSL unavailable")); - camel_free_host (h); - return FALSE; #endif /* HAVE_SSL */ } else { tcp_stream = camel_tcp_stream_raw_new (); } + + hints.ai_socktype = SOCK_STREAM; + ai = camel_getaddrinfo(service->url->host, serv, &hints, ex); + if (ai == NULL) { + camel_object_unref(tcp_stream); + return FALSE; + } - ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port); - camel_free_host (h); + ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai); + camel_freeaddrinfo(ai); if (ret == -1) { if (errno == EINTR) camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Connection cancelled")); else camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not connect to POP server %s (port %d): %s"), - service->url->host, port, g_strerror (errno)); + _("Could not connect to POP server %s (port %s): %s"), + service->url->host, serv, g_strerror (errno)); camel_object_unref (tcp_stream); @@ -211,8 +218,8 @@ connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelE if (!(store->engine = camel_pop3_engine_new (tcp_stream, flags))) { camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Failed to read a valid greeting from POP server %s (port %d)"), - service->url->host, port); + _("Failed to read a valid greeting from POP server %s (port %s)"), + service->url->host, serv); return FALSE; } diff --git a/camel/providers/smtp/camel-smtp-transport.c b/camel/providers/smtp/camel-smtp-transport.c index 3245cc68a..fc313ed4d 100644 --- a/camel/providers/smtp/camel-smtp-transport.c +++ b/camel/providers/smtp/camel-smtp-transport.c @@ -237,53 +237,60 @@ connect_to_server (CamelService *service, int try_starttls, CamelException *ex) CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service); CamelStream *tcp_stream; char *respbuf = NULL; - struct hostent *h; - int port, ret; + int ret; + struct addrinfo *ai, hints = { 0 }; + char *serv; if (!CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex)) return FALSE; - h = camel_service_gethost (service, ex); - if (!h) - return FALSE; - /* set some smtp transport defaults */ transport->flags &= CAMEL_SMTP_TRANSPORT_USE_SSL; /* reset all but ssl flags */ transport->authtypes = NULL; - - port = service->url->port ? service->url->port : SMTP_PORT; + + if (service->url->port) { + serv = g_alloca(16); + sprintf(serv, "%d", service->url->port); + } else + serv = "smtp"; if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL) { #ifdef HAVE_SSL if (try_starttls) { tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS); } else { - port = service->url->port ? service->url->port : 465; + if (service->url->port == 0) + serv = "smtps"; tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS); } #else - if (!try_starttls) - port = service->url->port ? service->url->port : 465; + if (!try_starttls && service->url->port == 0) + serv = "smtps"; camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not connect to %s (port %d): %s"), - service->url->host, port, + _("Could not connect to %s (port %s): %s"), + service->url->host, serv, _("SSL unavailable")); - - camel_free_host (h); - + return FALSE; #endif /* HAVE_SSL */ } else { tcp_stream = camel_tcp_stream_raw_new (); } + + hints.ai_socktype = SOCK_STREAM; + ai = camel_getaddrinfo(service->url->host, serv, &hints, ex); + if (ai == NULL) { + camel_object_unref(tcp_stream); + return FALSE; + } - ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port); - camel_free_host (h); + ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai); + camel_freeaddrinfo(ai); if (ret == -1) { camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not connect to %s (port %d): %s"), - service->url->host, port, + _("Could not connect to %s (port %s): %s"), + service->url->host, serv, g_strerror (errno)); camel_object_unref (tcp_stream); @@ -294,7 +301,7 @@ connect_to_server (CamelService *service, int try_starttls, CamelException *ex) transport->connected = TRUE; /* get the localaddr - needed later by smtp_helo */ - transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream)); + transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream), &transport->localaddrlen); transport->ostream = tcp_stream; transport->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ); @@ -580,7 +587,7 @@ smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex) transport->ostream = NULL; } - camel_tcp_address_free (transport->localaddr); + g_free(transport->localaddr); transport->localaddr = NULL; transport->connected = FALSE; @@ -865,10 +872,7 @@ smtp_helo (CamelSmtpTransport *transport, CamelException *ex) { /* say hello to the server */ char *name = NULL, *cmdbuf = NULL, *respbuf = NULL; - struct hostent *host; - CamelException err; const char *token; - int af; /* these are flags that we set, so unset them in case we are being called a second time (ie, after a STARTTLS) */ @@ -883,42 +887,10 @@ smtp_helo (CamelSmtpTransport *transport, CamelException *ex) } camel_operation_start_transient (NULL, _("SMTP Greeting")); - - /* get the local host name */ - camel_exception_init (&err); -#ifdef ENABLE_IPv6 - af = transport->localaddr->family == CAMEL_TCP_ADDRESS_IPv6 ? AF_INET6 : AF_INET; -#else - af = AF_INET; -#endif - host = camel_gethostbyaddr ((char *) &transport->localaddr->address, - transport->localaddr->length, af, &err); - - camel_exception_clear (&err); - - if (host && host->h_name && *host->h_name) { - name = g_strdup (host->h_name); - } else { -#ifdef ENABLE_IPv6 - char ip[MAXHOSTNAMELEN + 1]; - const char *proto; - - proto = transport->localaddr->family == CAMEL_TCP_ADDRESS_IPv6 ? "IPv6:" : ""; - name = g_strdup_printf ("[%s%s]", proto, inet_ntop (af, transport->localaddr->address, ip, MAXHOSTNAMELEN)); -#else - /* We *could* use inet_ntoa() here, but it's probably - not worth it since we would have to worry about - some systems not having inet_ntoa() */ - name = g_strdup_printf ("[%d.%d.%d.%d]", - transport->localaddr->address[0], - transport->localaddr->address[1], - transport->localaddr->address[2], - transport->localaddr->address[3]); -#endif - } - - if (host) - camel_free_host (host); + + /* this can't really fail with the flags we're using, it should fallback to numerical */ + if (camel_getnameinfo(transport->localaddr, transport->localaddrlen, &name, NULL, 0, NULL) != 0) + name = g_strdup("localhost.localdomain"); /* hiya server! how are you today? */ if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) diff --git a/camel/providers/smtp/camel-smtp-transport.h b/camel/providers/smtp/camel-smtp-transport.h index ef15f2b07..87fcafb58 100644 --- a/camel/providers/smtp/camel-smtp-transport.h +++ b/camel/providers/smtp/camel-smtp-transport.h @@ -22,17 +22,14 @@ * USA */ - #ifndef CAMEL_SMTP_TRANSPORT_H #define CAMEL_SMTP_TRANSPORT_H 1 - #ifdef __cplusplus extern "C" { #pragma } #endif /* __cplusplus */ - #include "camel-transport.h" #include "camel-tcp-stream.h" @@ -41,7 +38,6 @@ extern "C" { #define CAMEL_SMTP_TRANSPORT_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_SMTP_TRANSPORT_TYPE, CamelSmtpTransportClass)) #define CAMEL_IS_SMTP_TRANSPORT(o) (CAMEL_CHECK_TYPE((o), CAMEL_SMTP_TRANSPORT_TYPE)) - #define CAMEL_SMTP_TRANSPORT_IS_ESMTP (1 << 0) #define CAMEL_SMTP_TRANSPORT_8BITMIME (1 << 1) #define CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES (1 << 2) @@ -63,20 +59,17 @@ typedef struct { guint32 flags; gboolean connected; - CamelTcpAddress *localaddr; + struct sockaddr *localaddr; + socklen_t localaddrlen; GHashTable *authtypes; - } CamelSmtpTransport; - - typedef struct { CamelTransportClass parent_class; } CamelSmtpTransportClass; - /* Standard Camel function */ CamelType camel_smtp_transport_get_type (void); @@ -85,5 +78,3 @@ CamelType camel_smtp_transport_get_type (void); #endif /* __cplusplus */ #endif /* CAMEL_SMTP_TRANSPORT_H */ - - |