| /* |
| * git-imap-send - drops patches into an imap Drafts folder |
| * derived from isync/mbsync - mailbox synchronizer |
| * |
| * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org> |
| * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net> |
| * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu> |
| * Copyright (C) 2006 Mike McCormack |
| * |
| * 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 Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include "cache.h" |
| |
| typedef struct store_conf { |
| char *name; |
| const char *path; /* should this be here? its interpretation is driver-specific */ |
| char *map_inbox; |
| char *trash; |
| unsigned max_size; /* off_t is overkill */ |
| unsigned trash_remote_new:1, trash_only_new:1; |
| } store_conf_t; |
| |
| typedef struct string_list { |
| struct string_list *next; |
| char string[1]; |
| } string_list_t; |
| |
| typedef struct channel_conf { |
| struct channel_conf *next; |
| char *name; |
| store_conf_t *master, *slave; |
| char *master_name, *slave_name; |
| char *sync_state; |
| string_list_t *patterns; |
| int mops, sops; |
| unsigned max_messages; /* for slave only */ |
| } channel_conf_t; |
| |
| typedef struct group_conf { |
| struct group_conf *next; |
| char *name; |
| string_list_t *channels; |
| } group_conf_t; |
| |
| /* For message->status */ |
| #define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */ |
| #define M_DEAD (1<<1) /* expunged */ |
| #define M_FLAGS (1<<2) /* flags fetched */ |
| |
| typedef struct message { |
| struct message *next; |
| /* string_list_t *keywords; */ |
| size_t size; /* zero implies "not fetched" */ |
| int uid; |
| unsigned char flags, status; |
| } message_t; |
| |
| typedef struct store { |
| store_conf_t *conf; /* foreign */ |
| |
| /* currently open mailbox */ |
| const char *name; /* foreign! maybe preset? */ |
| char *path; /* own */ |
| message_t *msgs; /* own */ |
| int uidvalidity; |
| unsigned char opts; /* maybe preset? */ |
| /* note that the following do _not_ reflect stats from msgs, but mailbox totals */ |
| int count; /* # of messages */ |
| int recent; /* # of recent messages - don't trust this beyond the initial read */ |
| } store_t; |
| |
| typedef struct { |
| char *data; |
| int len; |
| unsigned char flags; |
| unsigned int crlf:1; |
| } msg_data_t; |
| |
| #define DRV_OK 0 |
| #define DRV_MSG_BAD -1 |
| #define DRV_BOX_BAD -2 |
| #define DRV_STORE_BAD -3 |
| |
| static int Verbose, Quiet; |
| |
| static void imap_info( const char *, ... ); |
| static void imap_warn( const char *, ... ); |
| |
| static char *next_arg( char ** ); |
| |
| static void free_generic_messages( message_t * ); |
| |
| static int nfsnprintf( char *buf, int blen, const char *fmt, ... ); |
| |
| static int nfvasprintf(char **strp, const char *fmt, va_list ap) |
| { |
| int len; |
| char tmp[8192]; |
| |
| len = vsnprintf(tmp, sizeof(tmp), fmt, ap); |
| if (len < 0) |
| die("Fatal: Out of memory\n"); |
| if (len >= sizeof(tmp)) |
| die("imap command overflow !\n"); |
| *strp = xmemdupz(tmp, len); |
| return len; |
| } |
| |
| static void arc4_init( void ); |
| static unsigned char arc4_getbyte( void ); |
| |
| typedef struct imap_server_conf { |
| char *name; |
| char *tunnel; |
| char *host; |
| int port; |
| char *user; |
| char *pass; |
| } imap_server_conf_t; |
| |
| typedef struct imap_store_conf { |
| store_conf_t gen; |
| imap_server_conf_t *server; |
| unsigned use_namespace:1; |
| } imap_store_conf_t; |
| |
| #define NIL (void*)0x1 |
| #define LIST (void*)0x2 |
| |
| typedef struct _list { |
| struct _list *next, *child; |
| char *val; |
| int len; |
| } list_t; |
| |
| typedef struct { |
| int fd; |
| } Socket_t; |
| |
| typedef struct { |
| Socket_t sock; |
| int bytes; |
| int offset; |
| char buf[1024]; |
| } buffer_t; |
| |
| struct imap_cmd; |
| |
| typedef struct imap { |
| int uidnext; /* from SELECT responses */ |
| list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */ |
| unsigned caps, rcaps; /* CAPABILITY results */ |
| /* command queue */ |
| int nexttag, num_in_progress, literal_pending; |
| struct imap_cmd *in_progress, **in_progress_append; |
| buffer_t buf; /* this is BIG, so put it last */ |
| } imap_t; |
| |
| typedef struct imap_store { |
| store_t gen; |
| int uidvalidity; |
| imap_t *imap; |
| const char *prefix; |
| unsigned /*currentnc:1,*/ trashnc:1; |
| } imap_store_t; |
| |
| struct imap_cmd_cb { |
| int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt ); |
| void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response); |
| void *ctx; |
| char *data; |
| int dlen; |
| int uid; |
| unsigned create:1, trycreate:1; |
| }; |
| |
| struct imap_cmd { |
| struct imap_cmd *next; |
| struct imap_cmd_cb cb; |
| char *cmd; |
| int tag; |
| }; |
| |
| #define CAP(cap) (imap->caps & (1 << (cap))) |
| |
| enum CAPABILITY { |
| NOLOGIN = 0, |
| UIDPLUS, |
| LITERALPLUS, |
| NAMESPACE, |
| }; |
| |
| static const char *cap_list[] = { |
| "LOGINDISABLED", |
| "UIDPLUS", |
| "LITERAL+", |
| "NAMESPACE", |
| }; |
| |
| #define RESP_OK 0 |
| #define RESP_NO 1 |
| #define RESP_BAD 2 |
| |
| static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ); |
| |
| |
| static const char *Flags[] = { |
| "Draft", |
| "Flagged", |
| "Answered", |
| "Seen", |
| "Deleted", |
| }; |
| |
| static void |
| socket_perror( const char *func, Socket_t *sock, int ret ) |
| { |
| if (ret < 0) |
| perror( func ); |
| else |
| fprintf( stderr, "%s: unexpected EOF\n", func ); |
| } |
| |
| static int |
| socket_read( Socket_t *sock, char *buf, int len ) |
| { |
| ssize_t n = xread( sock->fd, buf, len ); |
| if (n <= 0) { |
| socket_perror( "read", sock, n ); |
| close( sock->fd ); |
| sock->fd = -1; |
| } |
| return n; |
| } |
| |
| static int |
| socket_write( Socket_t *sock, const char *buf, int len ) |
| { |
| int n = write_in_full( sock->fd, buf, len ); |
| if (n != len) { |
| socket_perror( "write", sock, n ); |
| close( sock->fd ); |
| sock->fd = -1; |
| } |
| return n; |
| } |
| |
| /* simple line buffering */ |
| static int |
| buffer_gets( buffer_t * b, char **s ) |
| { |
| int n; |
| int start = b->offset; |
| |
| *s = b->buf + start; |
| |
| for (;;) { |
| /* make sure we have enough data to read the \r\n sequence */ |
| if (b->offset + 1 >= b->bytes) { |
| if (start) { |
| /* shift down used bytes */ |
| *s = b->buf; |
| |
| assert( start <= b->bytes ); |
| n = b->bytes - start; |
| |
| if (n) |
| memmove(b->buf, b->buf + start, n); |
| b->offset -= start; |
| b->bytes = n; |
| start = 0; |
| } |
| |
| n = socket_read( &b->sock, b->buf + b->bytes, |
| sizeof(b->buf) - b->bytes ); |
| |
| if (n <= 0) |
| return -1; |
| |
| b->bytes += n; |
| } |
| |
| if (b->buf[b->offset] == '\r') { |
| assert( b->offset + 1 < b->bytes ); |
| if (b->buf[b->offset + 1] == '\n') { |
| b->buf[b->offset] = 0; /* terminate the string */ |
| b->offset += 2; /* next line */ |
| if (Verbose) |
| puts( *s ); |
| return 0; |
| } |
| } |
| |
| b->offset++; |
| } |
| /* not reached */ |
| } |
| |
| static void |
| imap_info( const char *msg, ... ) |
| { |
| va_list va; |
| |
| if (!Quiet) { |
| va_start( va, msg ); |
| vprintf( msg, va ); |
| va_end( va ); |
| fflush( stdout ); |
| } |
| } |
| |
| static void |
| imap_warn( const char *msg, ... ) |
| { |
| va_list va; |
| |
| if (Quiet < 2) { |
| va_start( va, msg ); |
| vfprintf( stderr, msg, va ); |
| va_end( va ); |
| } |
| } |
| |
| static char * |
| next_arg( char **s ) |
| { |
| char *ret; |
| |
| if (!s || !*s) |
| return NULL; |
| while (isspace( (unsigned char) **s )) |
| (*s)++; |
| if (!**s) { |
| *s = NULL; |
| return NULL; |
| } |
| if (**s == '"') { |
| ++*s; |
| ret = *s; |
| *s = strchr( *s, '"' ); |
| } else { |
| ret = *s; |
| while (**s && !isspace( (unsigned char) **s )) |
| (*s)++; |
| } |
| if (*s) { |
| if (**s) |
| *(*s)++ = 0; |
| if (!**s) |
| *s = NULL; |
| } |
| return ret; |
| } |
| |
| static void |
| free_generic_messages( message_t *msgs ) |
| { |
| message_t *tmsg; |
| |
| for (; msgs; msgs = tmsg) { |
| tmsg = msgs->next; |
| free( msgs ); |
| } |
| } |
| |
| static int |
| nfsnprintf( char *buf, int blen, const char *fmt, ... ) |
| { |
| int ret; |
| va_list va; |
| |
| va_start( va, fmt ); |
| if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) |
| die( "Fatal: buffer too small. Please report a bug.\n"); |
| va_end( va ); |
| return ret; |
| } |
| |
| static struct { |
| unsigned char i, j, s[256]; |
| } rs; |
| |
| static void |
| arc4_init( void ) |
| { |
| int i, fd; |
| unsigned char j, si, dat[128]; |
| |
| if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) { |
| fprintf( stderr, "Fatal: no random number source available.\n" ); |
| exit( 3 ); |
| } |
| if (read_in_full( fd, dat, 128 ) != 128) { |
| fprintf( stderr, "Fatal: cannot read random number source.\n" ); |
| exit( 3 ); |
| } |
| close( fd ); |
| |
| for (i = 0; i < 256; i++) |
| rs.s[i] = i; |
| for (i = j = 0; i < 256; i++) { |
| si = rs.s[i]; |
| j += si + dat[i & 127]; |
| rs.s[i] = rs.s[j]; |
| rs.s[j] = si; |
| } |
| rs.i = rs.j = 0; |
| |
| for (i = 0; i < 256; i++) |
| arc4_getbyte(); |
| } |
| |
| static unsigned char |
| arc4_getbyte( void ) |
| { |
| unsigned char si, sj; |
| |
| rs.i++; |
| si = rs.s[rs.i]; |
| rs.j += si; |
| sj = rs.s[rs.j]; |
| rs.s[rs.i] = sj; |
| rs.s[rs.j] = si; |
| return rs.s[(si + sj) & 0xff]; |
| } |
| |
| static struct imap_cmd * |
| v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, |
| const char *fmt, va_list ap ) |
| { |
| imap_t *imap = ctx->imap; |
| struct imap_cmd *cmd; |
| int n, bufl; |
| char buf[1024]; |
| |
| cmd = xmalloc( sizeof(struct imap_cmd) ); |
| nfvasprintf( &cmd->cmd, fmt, ap ); |
| cmd->tag = ++imap->nexttag; |
| |
| if (cb) |
| cmd->cb = *cb; |
| else |
| memset( &cmd->cb, 0, sizeof(cmd->cb) ); |
| |
| while (imap->literal_pending) |
| get_cmd_result( ctx, NULL ); |
| |
| bufl = nfsnprintf( buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ? |
| "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n", |
| cmd->tag, cmd->cmd, cmd->cb.dlen ); |
| if (Verbose) { |
| if (imap->num_in_progress) |
| printf( "(%d in progress) ", imap->num_in_progress ); |
| if (memcmp( cmd->cmd, "LOGIN", 5 )) |
| printf( ">>> %s", buf ); |
| else |
| printf( ">>> %d LOGIN <user> <pass>\n", cmd->tag ); |
| } |
| if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) { |
| free( cmd->cmd ); |
| free( cmd ); |
| if (cb && cb->data) |
| free( cb->data ); |
| return NULL; |
| } |
| if (cmd->cb.data) { |
| if (CAP(LITERALPLUS)) { |
| n = socket_write( &imap->buf.sock, cmd->cb.data, cmd->cb.dlen ); |
| free( cmd->cb.data ); |
| if (n != cmd->cb.dlen || |
| (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2) |
| { |
| free( cmd->cmd ); |
| free( cmd ); |
| return NULL; |
| } |
| cmd->cb.data = NULL; |
| } else |
| imap->literal_pending = 1; |
| } else if (cmd->cb.cont) |
| imap->literal_pending = 1; |
| cmd->next = NULL; |
| *imap->in_progress_append = cmd; |
| imap->in_progress_append = &cmd->next; |
| imap->num_in_progress++; |
| return cmd; |
| } |
| |
| static struct imap_cmd * |
| issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) |
| { |
| struct imap_cmd *ret; |
| va_list ap; |
| |
| va_start( ap, fmt ); |
| ret = v_issue_imap_cmd( ctx, cb, fmt, ap ); |
| va_end( ap ); |
| return ret; |
| } |
| |
| static int |
| imap_exec( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) |
| { |
| va_list ap; |
| struct imap_cmd *cmdp; |
| |
| va_start( ap, fmt ); |
| cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap ); |
| va_end( ap ); |
| if (!cmdp) |
| return RESP_BAD; |
| |
| return get_cmd_result( ctx, cmdp ); |
| } |
| |
| static int |
| imap_exec_m( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) |
| { |
| va_list ap; |
| struct imap_cmd *cmdp; |
| |
| va_start( ap, fmt ); |
| cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap ); |
| va_end( ap ); |
| if (!cmdp) |
| return DRV_STORE_BAD; |
| |
| switch (get_cmd_result( ctx, cmdp )) { |
| case RESP_BAD: return DRV_STORE_BAD; |
| case RESP_NO: return DRV_MSG_BAD; |
| default: return DRV_OK; |
| } |
| } |
| |
| static int |
| is_atom( list_t *list ) |
| { |
| return list && list->val && list->val != NIL && list->val != LIST; |
| } |
| |
| static int |
| is_list( list_t *list ) |
| { |
| return list && list->val == LIST; |
| } |
| |
| static void |
| free_list( list_t *list ) |
| { |
| list_t *tmp; |
| |
| for (; list; list = tmp) { |
| tmp = list->next; |
| if (is_list( list )) |
| free_list( list->child ); |
| else if (is_atom( list )) |
| free( list->val ); |
| free( list ); |
| } |
| } |
| |
| static int |
| parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) |
| { |
| list_t *cur; |
| char *s = *sp, *p; |
| int n, bytes; |
| |
| for (;;) { |
| while (isspace( (unsigned char)*s )) |
| s++; |
| if (level && *s == ')') { |
| s++; |
| break; |
| } |
| *curp = cur = xmalloc( sizeof(*cur) ); |
| curp = &cur->next; |
| cur->val = NULL; /* for clean bail */ |
| if (*s == '(') { |
| /* sublist */ |
| s++; |
| cur->val = LIST; |
| if (parse_imap_list_l( imap, &s, &cur->child, level + 1 )) |
| goto bail; |
| } else if (imap && *s == '{') { |
| /* literal */ |
| bytes = cur->len = strtol( s + 1, &s, 10 ); |
| if (*s != '}') |
| goto bail; |
| |
| s = cur->val = xmalloc( cur->len ); |
| |
| /* dump whats left over in the input buffer */ |
| n = imap->buf.bytes - imap->buf.offset; |
| |
| if (n > bytes) |
| /* the entire message fit in the buffer */ |
| n = bytes; |
| |
| memcpy( s, imap->buf.buf + imap->buf.offset, n ); |
| s += n; |
| bytes -= n; |
| |
| /* mark that we used part of the buffer */ |
| imap->buf.offset += n; |
| |
| /* now read the rest of the message */ |
| while (bytes > 0) { |
| if ((n = socket_read (&imap->buf.sock, s, bytes)) <= 0) |
| goto bail; |
| s += n; |
| bytes -= n; |
| } |
| |
| if (buffer_gets( &imap->buf, &s )) |
| goto bail; |
| } else if (*s == '"') { |
| /* quoted string */ |
| s++; |
| p = s; |
| for (; *s != '"'; s++) |
| if (!*s) |
| goto bail; |
| cur->len = s - p; |
| s++; |
| cur->val = xmemdupz(p, cur->len); |
| } else { |
| /* atom */ |
| p = s; |
| for (; *s && !isspace( (unsigned char)*s ); s++) |
| if (level && *s == ')') |
| break; |
| cur->len = s - p; |
| if (cur->len == 3 && !memcmp ("NIL", p, 3)) { |
| cur->val = NIL; |
| } else { |
| cur->val = xmemdupz(p, cur->len); |
| } |
| } |
| |
| if (!level) |
| break; |
| if (!*s) |
| goto bail; |
| } |
| *sp = s; |
| *curp = NULL; |
| return 0; |
| |
| bail: |
| *curp = NULL; |
| return -1; |
| } |
| |
| static list_t * |
| parse_imap_list( imap_t *imap, char **sp ) |
| { |
| list_t *head; |
| |
| if (!parse_imap_list_l( imap, sp, &head, 0 )) |
| return head; |
| free_list( head ); |
| return NULL; |
| } |
| |
| static list_t * |
| parse_list( char **sp ) |
| { |
| return parse_imap_list( NULL, sp ); |
| } |
| |
| static void |
| parse_capability( imap_t *imap, char *cmd ) |
| { |
| char *arg; |
| unsigned i; |
| |
| imap->caps = 0x80000000; |
| while ((arg = next_arg( &cmd ))) |
| for (i = 0; i < ARRAY_SIZE(cap_list); i++) |
| if (!strcmp( cap_list[i], arg )) |
| imap->caps |= 1 << i; |
| imap->rcaps = imap->caps; |
| } |
| |
| static int |
| parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s ) |
| { |
| imap_t *imap = ctx->imap; |
| char *arg, *p; |
| |
| if (*s != '[') |
| return RESP_OK; /* no response code */ |
| s++; |
| if (!(p = strchr( s, ']' ))) { |
| fprintf( stderr, "IMAP error: malformed response code\n" ); |
| return RESP_BAD; |
| } |
| *p++ = 0; |
| arg = next_arg( &s ); |
| if (!strcmp( "UIDVALIDITY", arg )) { |
| if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg ))) { |
| fprintf( stderr, "IMAP error: malformed UIDVALIDITY status\n" ); |
| return RESP_BAD; |
| } |
| } else if (!strcmp( "UIDNEXT", arg )) { |
| if (!(arg = next_arg( &s )) || !(imap->uidnext = atoi( arg ))) { |
| fprintf( stderr, "IMAP error: malformed NEXTUID status\n" ); |
| return RESP_BAD; |
| } |
| } else if (!strcmp( "CAPABILITY", arg )) { |
| parse_capability( imap, s ); |
| } else if (!strcmp( "ALERT", arg )) { |
| /* RFC2060 says that these messages MUST be displayed |
| * to the user |
| */ |
| for (; isspace( (unsigned char)*p ); p++); |
| fprintf( stderr, "*** IMAP ALERT *** %s\n", p ); |
| } else if (cb && cb->ctx && !strcmp( "APPENDUID", arg )) { |
| if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg )) || |
| !(arg = next_arg( &s )) || !(*(int *)cb->ctx = atoi( arg ))) |
| { |
| fprintf( stderr, "IMAP error: malformed APPENDUID status\n" ); |
| return RESP_BAD; |
| } |
| } |
| return RESP_OK; |
| } |
| |
| static int |
| get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) |
| { |
| imap_t *imap = ctx->imap; |
| struct imap_cmd *cmdp, **pcmdp, *ncmdp; |
| char *cmd, *arg, *arg1, *p; |
| int n, resp, resp2, tag; |
| |
| for (;;) { |
| if (buffer_gets( &imap->buf, &cmd )) |
| return RESP_BAD; |
| |
| arg = next_arg( &cmd ); |
| if (*arg == '*') { |
| arg = next_arg( &cmd ); |
| if (!arg) { |
| fprintf( stderr, "IMAP error: unable to parse untagged response\n" ); |
| return RESP_BAD; |
| } |
| |
| if (!strcmp( "NAMESPACE", arg )) { |
| imap->ns_personal = parse_list( &cmd ); |
| imap->ns_other = parse_list( &cmd ); |
| imap->ns_shared = parse_list( &cmd ); |
| } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) || |
| !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) { |
| if ((resp = parse_response_code( ctx, NULL, cmd )) != RESP_OK) |
| return resp; |
| } else if (!strcmp( "CAPABILITY", arg )) |
| parse_capability( imap, cmd ); |
| else if ((arg1 = next_arg( &cmd ))) { |
| if (!strcmp( "EXISTS", arg1 )) |
| ctx->gen.count = atoi( arg ); |
| else if (!strcmp( "RECENT", arg1 )) |
| ctx->gen.recent = atoi( arg ); |
| } else { |
| fprintf( stderr, "IMAP error: unable to parse untagged response\n" ); |
| return RESP_BAD; |
| } |
| } else if (!imap->in_progress) { |
| fprintf( stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" ); |
| return RESP_BAD; |
| } else if (*arg == '+') { |
| /* This can happen only with the last command underway, as |
| it enforces a round-trip. */ |
| cmdp = (struct imap_cmd *)((char *)imap->in_progress_append - |
| offsetof(struct imap_cmd, next)); |
| if (cmdp->cb.data) { |
| n = socket_write( &imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen ); |
| free( cmdp->cb.data ); |
| cmdp->cb.data = NULL; |
| if (n != (int)cmdp->cb.dlen) |
| return RESP_BAD; |
| } else if (cmdp->cb.cont) { |
| if (cmdp->cb.cont( ctx, cmdp, cmd )) |
| return RESP_BAD; |
| } else { |
| fprintf( stderr, "IMAP error: unexpected command continuation request\n" ); |
| return RESP_BAD; |
| } |
| if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2) |
| return RESP_BAD; |
| if (!cmdp->cb.cont) |
| imap->literal_pending = 0; |
| if (!tcmd) |
| return DRV_OK; |
| } else { |
| tag = atoi( arg ); |
| for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next) |
| if (cmdp->tag == tag) |
| goto gottag; |
| fprintf( stderr, "IMAP error: unexpected tag %s\n", arg ); |
| return RESP_BAD; |
| gottag: |
| if (!(*pcmdp = cmdp->next)) |
| imap->in_progress_append = pcmdp; |
| imap->num_in_progress--; |
| if (cmdp->cb.cont || cmdp->cb.data) |
| imap->literal_pending = 0; |
| arg = next_arg( &cmd ); |
| if (!strcmp( "OK", arg )) |
| resp = DRV_OK; |
| else { |
| if (!strcmp( "NO", arg )) { |
| if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp( cmd, "[TRYCREATE]", 11 ))) { /* SELECT, APPEND or UID COPY */ |
| p = strchr( cmdp->cmd, '"' ); |
| if (!issue_imap_cmd( ctx, NULL, "CREATE \"%.*s\"", strchr( p + 1, '"' ) - p + 1, p )) { |
| resp = RESP_BAD; |
| goto normal; |
| } |
| /* not waiting here violates the spec, but a server that does not |
| grok this nonetheless violates it too. */ |
| cmdp->cb.create = 0; |
| if (!(ncmdp = issue_imap_cmd( ctx, &cmdp->cb, "%s", cmdp->cmd ))) { |
| resp = RESP_BAD; |
| goto normal; |
| } |
| free( cmdp->cmd ); |
| free( cmdp ); |
| if (!tcmd) |
| return 0; /* ignored */ |
| if (cmdp == tcmd) |
| tcmd = ncmdp; |
| continue; |
| } |
| resp = RESP_NO; |
| } else /*if (!strcmp( "BAD", arg ))*/ |
| resp = RESP_BAD; |
| fprintf( stderr, "IMAP command '%s' returned response (%s) - %s\n", |
| memcmp (cmdp->cmd, "LOGIN", 5) ? |
| cmdp->cmd : "LOGIN <user> <pass>", |
| arg, cmd ? cmd : ""); |
| } |
| if ((resp2 = parse_response_code( ctx, &cmdp->cb, cmd )) > resp) |
| resp = resp2; |
| normal: |
| if (cmdp->cb.done) |
| cmdp->cb.done( ctx, cmdp, resp ); |
| if (cmdp->cb.data) |
| free( cmdp->cb.data ); |
| free( cmdp->cmd ); |
| free( cmdp ); |
| if (!tcmd || tcmd == cmdp) |
| return resp; |
| } |
| } |
| /* not reached */ |
| } |
| |
| static void |
| imap_close_server( imap_store_t *ictx ) |
| { |
| imap_t *imap = ictx->imap; |
| |
| if (imap->buf.sock.fd != -1) { |
| imap_exec( ictx, NULL, "LOGOUT" ); |
| close( imap->buf.sock.fd ); |
| } |
| free_list( imap->ns_personal ); |
| free_list( imap->ns_other ); |
| free_list( imap->ns_shared ); |
| free( imap ); |
| } |
| |
| static void |
| imap_close_store( store_t *ctx ) |
| { |
| imap_close_server( (imap_store_t *)ctx ); |
| free_generic_messages( ctx->msgs ); |
| free( ctx ); |
| } |
| |
| static store_t * |
| imap_open_store( imap_server_conf_t *srvc ) |
| { |
| imap_store_t *ctx; |
| imap_t *imap; |
| char *arg, *rsp; |
| struct hostent *he; |
| struct sockaddr_in addr; |
| int s, a[2], preauth; |
| pid_t pid; |
| |
| ctx = xcalloc( sizeof(*ctx), 1 ); |
| |
| ctx->imap = imap = xcalloc( sizeof(*imap), 1 ); |
| imap->buf.sock.fd = -1; |
| imap->in_progress_append = &imap->in_progress; |
| |
| /* open connection to IMAP server */ |
| |
| if (srvc->tunnel) { |
| imap_info( "Starting tunnel '%s'... ", srvc->tunnel ); |
| |
| if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) { |
| perror( "socketpair" ); |
| exit( 1 ); |
| } |
| |
| pid = fork(); |
| if (pid < 0) |
| _exit( 127 ); |
| if (!pid) { |
| if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1) |
| _exit( 127 ); |
| close( a[0] ); |
| close( a[1] ); |
| execl( "/bin/sh", "sh", "-c", srvc->tunnel, NULL ); |
| _exit( 127 ); |
| } |
| |
| close (a[0]); |
| |
| imap->buf.sock.fd = a[1]; |
| |
| imap_info( "ok\n" ); |
| } else { |
| memset( &addr, 0, sizeof(addr) ); |
| addr.sin_port = htons( srvc->port ); |
| addr.sin_family = AF_INET; |
| |
| imap_info( "Resolving %s... ", srvc->host ); |
| he = gethostbyname( srvc->host ); |
| if (!he) { |
| perror( "gethostbyname" ); |
| goto bail; |
| } |
| imap_info( "ok\n" ); |
| |
| addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]); |
| |
| s = socket( PF_INET, SOCK_STREAM, 0 ); |
| |
| imap_info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) ); |
| if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) { |
| close( s ); |
| perror( "connect" ); |
| goto bail; |
| } |
| imap_info( "ok\n" ); |
| |
| imap->buf.sock.fd = s; |
| |
| } |
| |
| /* read the greeting string */ |
| if (buffer_gets( &imap->buf, &rsp )) { |
| fprintf( stderr, "IMAP error: no greeting response\n" ); |
| goto bail; |
| } |
| arg = next_arg( &rsp ); |
| if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) { |
| fprintf( stderr, "IMAP error: invalid greeting response\n" ); |
| goto bail; |
| } |
| preauth = 0; |
| if (!strcmp( "PREAUTH", arg )) |
| preauth = 1; |
| else if (strcmp( "OK", arg ) != 0) { |
| fprintf( stderr, "IMAP error: unknown greeting response\n" ); |
| goto bail; |
| } |
| parse_response_code( ctx, NULL, rsp ); |
| if (!imap->caps && imap_exec( ctx, NULL, "CAPABILITY" ) != RESP_OK) |
| goto bail; |
| |
| if (!preauth) { |
| |
| imap_info ("Logging in...\n"); |
| if (!srvc->user) { |
| fprintf( stderr, "Skipping server %s, no user\n", srvc->host ); |
| goto bail; |
| } |
| if (!srvc->pass) { |
| char prompt[80]; |
| sprintf( prompt, "Password (%s@%s): ", srvc->user, srvc->host ); |
| arg = getpass( prompt ); |
| if (!arg) { |
| perror( "getpass" ); |
| exit( 1 ); |
| } |
| if (!*arg) { |
| fprintf( stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host ); |
| goto bail; |
| } |
| /* |
| * getpass() returns a pointer to a static buffer. make a copy |
| * for long term storage. |
| */ |
| srvc->pass = xstrdup( arg ); |
| } |
| if (CAP(NOLOGIN)) { |
| fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host ); |
| goto bail; |
| } |
| imap_warn( "*** IMAP Warning *** Password is being sent in the clear\n" ); |
| if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) { |
| fprintf( stderr, "IMAP error: LOGIN failed\n" ); |
| goto bail; |
| } |
| } /* !preauth */ |
| |
| ctx->prefix = ""; |
| ctx->trashnc = 1; |
| return (store_t *)ctx; |
| |
| bail: |
| imap_close_store( &ctx->gen ); |
| return NULL; |
| } |
| |
| static int |
| imap_make_flags( int flags, char *buf ) |
| { |
| const char *s; |
| unsigned i, d; |
| |
| for (i = d = 0; i < ARRAY_SIZE(Flags); i++) |
| if (flags & (1 << i)) { |
| buf[d++] = ' '; |
| buf[d++] = '\\'; |
| for (s = Flags[i]; *s; s++) |
| buf[d++] = *s; |
| } |
| buf[0] = '('; |
| buf[d++] = ')'; |
| return d; |
| } |
| |
| #define TUIDL 8 |
| |
| static int |
| imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) |
| { |
| imap_store_t *ctx = (imap_store_t *)gctx; |
| imap_t *imap = ctx->imap; |
| struct imap_cmd_cb cb; |
| char *fmap, *buf; |
| const char *prefix, *box; |
| int ret, i, j, d, len, extra, nocr; |
| int start, sbreak = 0, ebreak = 0; |
| char flagstr[128], tuid[TUIDL * 2 + 1]; |
| |
| memset( &cb, 0, sizeof(cb) ); |
| |
| fmap = data->data; |
| len = data->len; |
| nocr = !data->crlf; |
| extra = 0, i = 0; |
| if (!CAP(UIDPLUS) && uid) { |
| nloop: |
| start = i; |
| while (i < len) |
| if (fmap[i++] == '\n') { |
| extra += nocr; |
| if (i - 2 + nocr == start) { |
| sbreak = ebreak = i - 2 + nocr; |
| goto mktid; |
| } |
| if (!memcmp( fmap + start, "X-TUID: ", 8 )) { |
| extra -= (ebreak = i) - (sbreak = start) + nocr; |
| goto mktid; |
| } |
| goto nloop; |
| } |
| /* invalid message */ |
| free( fmap ); |
| return DRV_MSG_BAD; |
| mktid: |
| for (j = 0; j < TUIDL; j++) |
| sprintf( tuid + j * 2, "%02x", arc4_getbyte() ); |
| extra += 8 + TUIDL * 2 + 2; |
| } |
| if (nocr) |
| for (; i < len; i++) |
| if (fmap[i] == '\n') |
| extra++; |
| |
| cb.dlen = len + extra; |
| buf = cb.data = xmalloc( cb.dlen ); |
| i = 0; |
| if (!CAP(UIDPLUS) && uid) { |
| if (nocr) { |
| for (; i < sbreak; i++) |
| if (fmap[i] == '\n') { |
| *buf++ = '\r'; |
| *buf++ = '\n'; |
| } else |
| *buf++ = fmap[i]; |
| } else { |
| memcpy( buf, fmap, sbreak ); |
| buf += sbreak; |
| } |
| memcpy( buf, "X-TUID: ", 8 ); |
| buf += 8; |
| memcpy( buf, tuid, TUIDL * 2 ); |
| buf += TUIDL * 2; |
| *buf++ = '\r'; |
| *buf++ = '\n'; |
| i = ebreak; |
| } |
| if (nocr) { |
| for (; i < len; i++) |
| if (fmap[i] == '\n') { |
| *buf++ = '\r'; |
| *buf++ = '\n'; |
| } else |
| *buf++ = fmap[i]; |
| } else |
| memcpy( buf, fmap + i, len - i ); |
| |
| free( fmap ); |
| |
| d = 0; |
| if (data->flags) { |
| d = imap_make_flags( data->flags, flagstr ); |
| flagstr[d++] = ' '; |
| } |
| flagstr[d] = 0; |
| |
| if (!uid) { |
| box = gctx->conf->trash; |
| prefix = ctx->prefix; |
| cb.create = 1; |
| if (ctx->trashnc) |
| imap->caps = imap->rcaps & ~(1 << LITERALPLUS); |
| } else { |
| box = gctx->name; |
| prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix; |
| cb.create = 0; |
| } |
| cb.ctx = uid; |
| ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr ); |
| imap->caps = imap->rcaps; |
| if (ret != DRV_OK) |
| return ret; |
| if (!uid) |
| ctx->trashnc = 0; |
| else |
| gctx->count++; |
| |
| return DRV_OK; |
| } |
| |
| #define CHUNKSIZE 0x1000 |
| |
| static int |
| read_message( FILE *f, msg_data_t *msg ) |
| { |
| struct strbuf buf; |
| |
| memset(msg, 0, sizeof(*msg)); |
| strbuf_init(&buf, 0); |
| |
| do { |
| if (strbuf_fread(&buf, CHUNKSIZE, f) <= 0) |
| break; |
| } while (!feof(f)); |
| |
| msg->len = buf.len; |
| msg->data = strbuf_detach(&buf, NULL); |
| return msg->len; |
| } |
| |
| static int |
| count_messages( msg_data_t *msg ) |
| { |
| int count = 0; |
| char *p = msg->data; |
| |
| while (1) { |
| if (!prefixcmp(p, "From ")) { |
| count++; |
| p += 5; |
| } |
| p = strstr( p+5, "\nFrom "); |
| if (!p) |
| break; |
| p++; |
| } |
| return count; |
| } |
| |
| static int |
| split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs ) |
| { |
| char *p, *data; |
| |
| memset( msg, 0, sizeof *msg ); |
| if (*ofs >= all_msgs->len) |
| return 0; |
| |
| data = &all_msgs->data[ *ofs ]; |
| msg->len = all_msgs->len - *ofs; |
| |
| if (msg->len < 5 || prefixcmp(data, "From ")) |
| return 0; |
| |
| p = strchr( data, '\n' ); |
| if (p) { |
| p = &p[1]; |
| msg->len -= p-data; |
| *ofs += p-data; |
| data = p; |
| } |
| |
| p = strstr( data, "\nFrom " ); |
| if (p) |
| msg->len = &p[1] - data; |
| |
| msg->data = xmemdupz(data, msg->len); |
| *ofs += msg->len; |
| return 1; |
| } |
| |
| static imap_server_conf_t server = |
| { |
| NULL, /* name */ |
| NULL, /* tunnel */ |
| NULL, /* host */ |
| 0, /* port */ |
| NULL, /* user */ |
| NULL, /* pass */ |
| }; |
| |
| static char *imap_folder; |
| |
| static int |
| git_imap_config(const char *key, const char *val) |
| { |
| char imap_key[] = "imap."; |
| |
| if (strncmp( key, imap_key, sizeof imap_key - 1 )) |
| return 0; |
| key += sizeof imap_key - 1; |
| |
| if (!strcmp( "folder", key )) { |
| imap_folder = xstrdup( val ); |
| } else if (!strcmp( "host", key )) { |
| { |
| if (!prefixcmp(val, "imap:")) |
| val += 5; |
| if (!server.port) |
| server.port = 143; |
| } |
| if (!prefixcmp(val, "//")) |
| val += 2; |
| server.host = xstrdup( val ); |
| } |
| else if (!strcmp( "user", key )) |
| server.user = xstrdup( val ); |
| else if (!strcmp( "pass", key )) |
| server.pass = xstrdup( val ); |
| else if (!strcmp( "port", key )) |
| server.port = git_config_int( key, val ); |
| else if (!strcmp( "tunnel", key )) |
| server.tunnel = xstrdup( val ); |
| return 0; |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| msg_data_t all_msgs, msg; |
| store_t *ctx = NULL; |
| int uid = 0; |
| int ofs = 0; |
| int r; |
| int total, n = 0; |
| |
| /* init the random number generator */ |
| arc4_init(); |
| |
| git_config( git_imap_config ); |
| |
| if (!imap_folder) { |
| fprintf( stderr, "no imap store specified\n" ); |
| return 1; |
| } |
| |
| /* read the messages */ |
| if (!read_message( stdin, &all_msgs )) { |
| fprintf(stderr,"nothing to send\n"); |
| return 1; |
| } |
| |
| total = count_messages( &all_msgs ); |
| if (!total) { |
| fprintf(stderr,"no messages to send\n"); |
| return 1; |
| } |
| |
| /* write it to the imap server */ |
| ctx = imap_open_store( &server ); |
| if (!ctx) { |
| fprintf( stderr,"failed to open store\n"); |
| return 1; |
| } |
| |
| fprintf( stderr, "sending %d message%s\n", total, (total!=1)?"s":"" ); |
| ctx->name = imap_folder; |
| while (1) { |
| unsigned percent = n * 100 / total; |
| fprintf( stderr, "%4u%% (%d/%d) done\r", percent, n, total ); |
| if (!split_msg( &all_msgs, &msg, &ofs )) |
| break; |
| r = imap_store_msg( ctx, &msg, &uid ); |
| if (r != DRV_OK) break; |
| n++; |
| } |
| fprintf( stderr,"\n" ); |
| |
| imap_close_store( ctx ); |
| |
| return 0; |
| } |