/* * git-imap-send - drops patches into an imap Drafts folder * derived from isync/mbsync - mailbox synchronizer * * Copyright (C) 2000-2002 Michael R. Elkins * Copyright (C) 2002-2004 Oswald Buddenhagen * Copyright (C) 2004 Theodore Y. Ts'o * 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 \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 ", 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; if (!val) return config_error_nonbool(key); 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; }