git-imap-send: Support SSL
[git/git.git] / imap-send.c
CommitLineData
f2561fda
MM
1/*
2 * git-imap-send - drops patches into an imap Drafts folder
3 * derived from isync/mbsync - mailbox synchronizer
4 *
5 * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
6 * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net>
7 * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
8 * Copyright (C) 2006 Mike McCormack
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 */
24
25#include "cache.h"
684ec6c6
RS
26#ifdef NO_OPENSSL
27typedef void *SSL;
28#endif
f2561fda 29
f2561fda
MM
30typedef struct store_conf {
31 char *name;
32 const char *path; /* should this be here? its interpretation is driver-specific */
33 char *map_inbox;
34 char *trash;
35 unsigned max_size; /* off_t is overkill */
36 unsigned trash_remote_new:1, trash_only_new:1;
37} store_conf_t;
38
39typedef struct string_list {
40 struct string_list *next;
41 char string[1];
42} string_list_t;
43
44typedef struct channel_conf {
45 struct channel_conf *next;
46 char *name;
47 store_conf_t *master, *slave;
48 char *master_name, *slave_name;
49 char *sync_state;
50 string_list_t *patterns;
51 int mops, sops;
52 unsigned max_messages; /* for slave only */
53} channel_conf_t;
54
55typedef struct group_conf {
56 struct group_conf *next;
57 char *name;
58 string_list_t *channels;
59} group_conf_t;
60
61/* For message->status */
62#define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
63#define M_DEAD (1<<1) /* expunged */
64#define M_FLAGS (1<<2) /* flags fetched */
65
66typedef struct message {
67 struct message *next;
68 /* string_list_t *keywords; */
69 size_t size; /* zero implies "not fetched" */
70 int uid;
71 unsigned char flags, status;
72} message_t;
73
74typedef struct store {
75 store_conf_t *conf; /* foreign */
76
77 /* currently open mailbox */
78 const char *name; /* foreign! maybe preset? */
79 char *path; /* own */
80 message_t *msgs; /* own */
81 int uidvalidity;
82 unsigned char opts; /* maybe preset? */
83 /* note that the following do _not_ reflect stats from msgs, but mailbox totals */
84 int count; /* # of messages */
85 int recent; /* # of recent messages - don't trust this beyond the initial read */
86} store_t;
87
88typedef struct {
89 char *data;
90 int len;
91 unsigned char flags;
2bda77e0 92 unsigned int crlf:1;
f2561fda
MM
93} msg_data_t;
94
95#define DRV_OK 0
96#define DRV_MSG_BAD -1
97#define DRV_BOX_BAD -2
98#define DRV_STORE_BAD -3
99
100static int Verbose, Quiet;
101
51dcaa96
SP
102static void imap_info( const char *, ... );
103static void imap_warn( const char *, ... );
f2561fda
MM
104
105static char *next_arg( char ** );
106
107static void free_generic_messages( message_t * );
108
f2561fda
MM
109static int nfsnprintf( char *buf, int blen, const char *fmt, ... );
110
19247e55
PH
111static int nfvasprintf(char **strp, const char *fmt, va_list ap)
112{
113 int len;
114 char tmp[8192];
115
116 len = vsnprintf(tmp, sizeof(tmp), fmt, ap);
117 if (len < 0)
118 die("Fatal: Out of memory\n");
119 if (len >= sizeof(tmp))
120 die("imap command overflow !\n");
121 *strp = xmemdupz(tmp, len);
122 return len;
123}
f2561fda
MM
124
125static void arc4_init( void );
126static unsigned char arc4_getbyte( void );
127
128typedef struct imap_server_conf {
129 char *name;
130 char *tunnel;
131 char *host;
132 int port;
133 char *user;
134 char *pass;
684ec6c6
RS
135 int use_ssl;
136 int ssl_verify;
f2561fda
MM
137} imap_server_conf_t;
138
139typedef struct imap_store_conf {
140 store_conf_t gen;
141 imap_server_conf_t *server;
142 unsigned use_namespace:1;
143} imap_store_conf_t;
144
145#define NIL (void*)0x1
146#define LIST (void*)0x2
147
148typedef struct _list {
149 struct _list *next, *child;
150 char *val;
151 int len;
152} list_t;
153
154typedef struct {
155 int fd;
684ec6c6 156 SSL *ssl;
f2561fda
MM
157} Socket_t;
158
159typedef struct {
160 Socket_t sock;
161 int bytes;
162 int offset;
163 char buf[1024];
164} buffer_t;
165
166struct imap_cmd;
167
168typedef struct imap {
169 int uidnext; /* from SELECT responses */
170 list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
171 unsigned caps, rcaps; /* CAPABILITY results */
172 /* command queue */
173 int nexttag, num_in_progress, literal_pending;
174 struct imap_cmd *in_progress, **in_progress_append;
175 buffer_t buf; /* this is BIG, so put it last */
176} imap_t;
177
178typedef struct imap_store {
179 store_t gen;
180 int uidvalidity;
181 imap_t *imap;
182 const char *prefix;
183 unsigned /*currentnc:1,*/ trashnc:1;
184} imap_store_t;
185
186struct imap_cmd_cb {
187 int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt );
188 void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response);
189 void *ctx;
190 char *data;
191 int dlen;
192 int uid;
193 unsigned create:1, trycreate:1;
194};
195
196struct imap_cmd {
197 struct imap_cmd *next;
198 struct imap_cmd_cb cb;
199 char *cmd;
200 int tag;
201};
202
203#define CAP(cap) (imap->caps & (1 << (cap)))
204
205enum CAPABILITY {
206 NOLOGIN = 0,
207 UIDPLUS,
208 LITERALPLUS,
209 NAMESPACE,
684ec6c6 210 STARTTLS,
f2561fda
MM
211};
212
213static const char *cap_list[] = {
214 "LOGINDISABLED",
215 "UIDPLUS",
216 "LITERAL+",
217 "NAMESPACE",
684ec6c6 218 "STARTTLS",
f2561fda
MM
219};
220
221#define RESP_OK 0
222#define RESP_NO 1
223#define RESP_BAD 2
224
225static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd );
226
227
228static const char *Flags[] = {
229 "Draft",
230 "Flagged",
231 "Answered",
232 "Seen",
233 "Deleted",
234};
235
684ec6c6
RS
236#ifndef NO_OPENSSL
237static void ssl_socket_perror(const char *func)
238{
239 fprintf(stderr, "%s: %s\n", func, ERR_error_string(ERR_get_error(), 0));
240}
241#endif
242
f2561fda
MM
243static void
244socket_perror( const char *func, Socket_t *sock, int ret )
245{
684ec6c6
RS
246#ifndef NO_OPENSSL
247 if (sock->ssl) {
248 int sslerr = SSL_get_error(sock->ssl, ret);
249 switch (sslerr) {
250 case SSL_ERROR_NONE:
251 break;
252 case SSL_ERROR_SYSCALL:
253 perror("SSL_connect");
254 break;
255 default:
256 ssl_socket_perror("SSL_connect");
257 break;
258 }
259 } else
260#endif
261 {
262 if (ret < 0)
263 perror(func);
264 else
265 fprintf(stderr, "%s: unexpected EOF\n", func);
266 }
267}
268
269static int ssl_socket_connect(Socket_t *sock, int use_tls_only, int verify)
270{
271#ifdef NO_OPENSSL
272 fprintf(stderr, "SSL requested but SSL support not compiled in\n");
273 return -1;
274#else
275 SSL_METHOD *meth;
276 SSL_CTX *ctx;
277 int ret;
278
279 SSL_library_init();
280 SSL_load_error_strings();
281
282 if (use_tls_only)
283 meth = TLSv1_method();
f2561fda 284 else
684ec6c6
RS
285 meth = SSLv23_method();
286
287 if (!meth) {
288 ssl_socket_perror("SSLv23_method");
289 return -1;
290 }
291
292 ctx = SSL_CTX_new(meth);
293
294 if (verify)
295 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
296
297 if (!SSL_CTX_set_default_verify_paths(ctx)) {
298 ssl_socket_perror("SSL_CTX_set_default_verify_paths");
299 return -1;
300 }
301 sock->ssl = SSL_new(ctx);
302 if (!sock->ssl) {
303 ssl_socket_perror("SSL_new");
304 return -1;
305 }
306 if (!SSL_set_fd(sock->ssl, sock->fd)) {
307 ssl_socket_perror("SSL_set_fd");
308 return -1;
309 }
310
311 ret = SSL_connect(sock->ssl);
312 if (ret <= 0) {
313 socket_perror("SSL_connect", sock, ret);
314 return -1;
315 }
316
317 return 0;
318#endif
f2561fda
MM
319}
320
321static int
322socket_read( Socket_t *sock, char *buf, int len )
323{
684ec6c6
RS
324 ssize_t n;
325#ifndef NO_OPENSSL
326 if (sock->ssl)
327 n = SSL_read(sock->ssl, buf, len);
328 else
329#endif
330 n = xread( sock->fd, buf, len );
f2561fda
MM
331 if (n <= 0) {
332 socket_perror( "read", sock, n );
333 close( sock->fd );
334 sock->fd = -1;
335 }
336 return n;
337}
338
339static int
554fe20d 340socket_write( Socket_t *sock, const char *buf, int len )
f2561fda 341{
684ec6c6
RS
342 int n;
343#ifndef NO_OPENSSL
344 if (sock->ssl)
345 n = SSL_write(sock->ssl, buf, len);
346 else
347#endif
348 n = write_in_full( sock->fd, buf, len );
f2561fda
MM
349 if (n != len) {
350 socket_perror( "write", sock, n );
351 close( sock->fd );
352 sock->fd = -1;
353 }
354 return n;
355}
356
684ec6c6
RS
357static void socket_shutdown(Socket_t *sock)
358{
359#ifndef NO_OPENSSL
360 if (sock->ssl) {
361 SSL_shutdown(sock->ssl);
362 SSL_free(sock->ssl);
363 }
364#endif
365 close(sock->fd);
366}
367
f2561fda
MM
368/* simple line buffering */
369static int
370buffer_gets( buffer_t * b, char **s )
371{
372 int n;
373 int start = b->offset;
374
375 *s = b->buf + start;
376
377 for (;;) {
378 /* make sure we have enough data to read the \r\n sequence */
379 if (b->offset + 1 >= b->bytes) {
380 if (start) {
381 /* shift down used bytes */
382 *s = b->buf;
383
384 assert( start <= b->bytes );
385 n = b->bytes - start;
386
387 if (n)
173a9cbe 388 memmove(b->buf, b->buf + start, n);
f2561fda
MM
389 b->offset -= start;
390 b->bytes = n;
391 start = 0;
392 }
393
394 n = socket_read( &b->sock, b->buf + b->bytes,
395 sizeof(b->buf) - b->bytes );
396
397 if (n <= 0)
398 return -1;
399
400 b->bytes += n;
401 }
402
403 if (b->buf[b->offset] == '\r') {
404 assert( b->offset + 1 < b->bytes );
405 if (b->buf[b->offset + 1] == '\n') {
406 b->buf[b->offset] = 0; /* terminate the string */
407 b->offset += 2; /* next line */
408 if (Verbose)
409 puts( *s );
410 return 0;
411 }
412 }
413
414 b->offset++;
415 }
416 /* not reached */
417}
418
419static void
51dcaa96 420imap_info( const char *msg, ... )
f2561fda
MM
421{
422 va_list va;
423
424 if (!Quiet) {
425 va_start( va, msg );
426 vprintf( msg, va );
427 va_end( va );
428 fflush( stdout );
429 }
430}
431
432static void
51dcaa96 433imap_warn( const char *msg, ... )
f2561fda
MM
434{
435 va_list va;
436
437 if (Quiet < 2) {
438 va_start( va, msg );
439 vfprintf( stderr, msg, va );
440 va_end( va );
441 }
442}
443
444static char *
445next_arg( char **s )
446{
447 char *ret;
448
449 if (!s || !*s)
5142db69 450 return NULL;
f2561fda
MM
451 while (isspace( (unsigned char) **s ))
452 (*s)++;
453 if (!**s) {
5142db69
RS
454 *s = NULL;
455 return NULL;
f2561fda
MM
456 }
457 if (**s == '"') {
458 ++*s;
459 ret = *s;
460 *s = strchr( *s, '"' );
461 } else {
462 ret = *s;
463 while (**s && !isspace( (unsigned char) **s ))
464 (*s)++;
465 }
466 if (*s) {
467 if (**s)
468 *(*s)++ = 0;
469 if (!**s)
5142db69 470 *s = NULL;
f2561fda
MM
471 }
472 return ret;
473}
474
475static void
476free_generic_messages( message_t *msgs )
477{
478 message_t *tmsg;
479
480 for (; msgs; msgs = tmsg) {
481 tmsg = msgs->next;
482 free( msgs );
483 }
484}
485
f2561fda
MM
486static int
487nfsnprintf( char *buf, int blen, const char *fmt, ... )
488{
489 int ret;
490 va_list va;
491
492 va_start( va, fmt );
493 if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
494 die( "Fatal: buffer too small. Please report a bug.\n");
495 va_end( va );
496 return ret;
497}
498
f2561fda
MM
499static struct {
500 unsigned char i, j, s[256];
501} rs;
502
503static void
504arc4_init( void )
505{
506 int i, fd;
507 unsigned char j, si, dat[128];
508
509 if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) {
510 fprintf( stderr, "Fatal: no random number source available.\n" );
511 exit( 3 );
512 }
93d26e4c 513 if (read_in_full( fd, dat, 128 ) != 128) {
f2561fda
MM
514 fprintf( stderr, "Fatal: cannot read random number source.\n" );
515 exit( 3 );
516 }
517 close( fd );
518
519 for (i = 0; i < 256; i++)
520 rs.s[i] = i;
521 for (i = j = 0; i < 256; i++) {
522 si = rs.s[i];
523 j += si + dat[i & 127];
524 rs.s[i] = rs.s[j];
525 rs.s[j] = si;
526 }
527 rs.i = rs.j = 0;
528
529 for (i = 0; i < 256; i++)
530 arc4_getbyte();
531}
532
533static unsigned char
534arc4_getbyte( void )
535{
536 unsigned char si, sj;
537
538 rs.i++;
539 si = rs.s[rs.i];
540 rs.j += si;
541 sj = rs.s[rs.j];
542 rs.s[rs.i] = sj;
543 rs.s[rs.j] = si;
544 return rs.s[(si + sj) & 0xff];
545}
546
547static struct imap_cmd *
548v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb,
549 const char *fmt, va_list ap )
550{
551 imap_t *imap = ctx->imap;
552 struct imap_cmd *cmd;
553 int n, bufl;
554 char buf[1024];
555
556 cmd = xmalloc( sizeof(struct imap_cmd) );
557 nfvasprintf( &cmd->cmd, fmt, ap );
558 cmd->tag = ++imap->nexttag;
559
560 if (cb)
561 cmd->cb = *cb;
562 else
563 memset( &cmd->cb, 0, sizeof(cmd->cb) );
564
565 while (imap->literal_pending)
5142db69 566 get_cmd_result( ctx, NULL );
f2561fda
MM
567
568 bufl = nfsnprintf( buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ?
569 "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n",
570 cmd->tag, cmd->cmd, cmd->cb.dlen );
571 if (Verbose) {
572 if (imap->num_in_progress)
573 printf( "(%d in progress) ", imap->num_in_progress );
574 if (memcmp( cmd->cmd, "LOGIN", 5 ))
575 printf( ">>> %s", buf );
576 else
577 printf( ">>> %d LOGIN <user> <pass>\n", cmd->tag );
578 }
579 if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) {
580 free( cmd->cmd );
581 free( cmd );
8e0f7003 582 if (cb)
f2561fda
MM
583 free( cb->data );
584 return NULL;
585 }
586 if (cmd->cb.data) {
587 if (CAP(LITERALPLUS)) {
588 n = socket_write( &imap->buf.sock, cmd->cb.data, cmd->cb.dlen );
589 free( cmd->cb.data );
590 if (n != cmd->cb.dlen ||
591 (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2)
592 {
593 free( cmd->cmd );
594 free( cmd );
595 return NULL;
596 }
5142db69 597 cmd->cb.data = NULL;
f2561fda
MM
598 } else
599 imap->literal_pending = 1;
600 } else if (cmd->cb.cont)
601 imap->literal_pending = 1;
5142db69 602 cmd->next = NULL;
f2561fda
MM
603 *imap->in_progress_append = cmd;
604 imap->in_progress_append = &cmd->next;
605 imap->num_in_progress++;
606 return cmd;
607}
608
609static struct imap_cmd *
610issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
611{
612 struct imap_cmd *ret;
613 va_list ap;
614
615 va_start( ap, fmt );
616 ret = v_issue_imap_cmd( ctx, cb, fmt, ap );
617 va_end( ap );
618 return ret;
619}
620
621static int
622imap_exec( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
623{
624 va_list ap;
625 struct imap_cmd *cmdp;
626
627 va_start( ap, fmt );
628 cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
629 va_end( ap );
630 if (!cmdp)
631 return RESP_BAD;
632
633 return get_cmd_result( ctx, cmdp );
634}
635
636static int
637imap_exec_m( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
638{
639 va_list ap;
640 struct imap_cmd *cmdp;
641
642 va_start( ap, fmt );
643 cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
644 va_end( ap );
645 if (!cmdp)
646 return DRV_STORE_BAD;
647
648 switch (get_cmd_result( ctx, cmdp )) {
649 case RESP_BAD: return DRV_STORE_BAD;
650 case RESP_NO: return DRV_MSG_BAD;
651 default: return DRV_OK;
652 }
653}
654
655static int
656is_atom( list_t *list )
657{
658 return list && list->val && list->val != NIL && list->val != LIST;
659}
660
661static int
662is_list( list_t *list )
663{
664 return list && list->val == LIST;
665}
666
667static void
668free_list( list_t *list )
669{
670 list_t *tmp;
671
672 for (; list; list = tmp) {
673 tmp = list->next;
674 if (is_list( list ))
675 free_list( list->child );
676 else if (is_atom( list ))
677 free( list->val );
678 free( list );
679 }
680}
681
682static int
683parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level )
684{
685 list_t *cur;
686 char *s = *sp, *p;
687 int n, bytes;
688
689 for (;;) {
690 while (isspace( (unsigned char)*s ))
691 s++;
692 if (level && *s == ')') {
693 s++;
694 break;
695 }
696 *curp = cur = xmalloc( sizeof(*cur) );
697 curp = &cur->next;
5142db69 698 cur->val = NULL; /* for clean bail */
f2561fda
MM
699 if (*s == '(') {
700 /* sublist */
701 s++;
702 cur->val = LIST;
703 if (parse_imap_list_l( imap, &s, &cur->child, level + 1 ))
704 goto bail;
705 } else if (imap && *s == '{') {
706 /* literal */
707 bytes = cur->len = strtol( s + 1, &s, 10 );
708 if (*s != '}')
709 goto bail;
710
711 s = cur->val = xmalloc( cur->len );
712
713 /* dump whats left over in the input buffer */
714 n = imap->buf.bytes - imap->buf.offset;
715
716 if (n > bytes)
717 /* the entire message fit in the buffer */
718 n = bytes;
719
720 memcpy( s, imap->buf.buf + imap->buf.offset, n );
721 s += n;
722 bytes -= n;
723
724 /* mark that we used part of the buffer */
725 imap->buf.offset += n;
726
727 /* now read the rest of the message */
728 while (bytes > 0) {
729 if ((n = socket_read (&imap->buf.sock, s, bytes)) <= 0)
730 goto bail;
731 s += n;
732 bytes -= n;
733 }
734
735 if (buffer_gets( &imap->buf, &s ))
736 goto bail;
737 } else if (*s == '"') {
738 /* quoted string */
739 s++;
740 p = s;
741 for (; *s != '"'; s++)
742 if (!*s)
743 goto bail;
744 cur->len = s - p;
745 s++;
182af834 746 cur->val = xmemdupz(p, cur->len);
f2561fda
MM
747 } else {
748 /* atom */
749 p = s;
750 for (; *s && !isspace( (unsigned char)*s ); s++)
751 if (level && *s == ')')
752 break;
753 cur->len = s - p;
182af834 754 if (cur->len == 3 && !memcmp ("NIL", p, 3)) {
f2561fda 755 cur->val = NIL;
182af834
PH
756 } else {
757 cur->val = xmemdupz(p, cur->len);
f2561fda
MM
758 }
759 }
760
761 if (!level)
762 break;
763 if (!*s)
764 goto bail;
765 }
766 *sp = s;
5142db69 767 *curp = NULL;
f2561fda
MM
768 return 0;
769
770 bail:
5142db69 771 *curp = NULL;
f2561fda
MM
772 return -1;
773}
774
775static list_t *
776parse_imap_list( imap_t *imap, char **sp )
777{
778 list_t *head;
779
780 if (!parse_imap_list_l( imap, sp, &head, 0 ))
781 return head;
782 free_list( head );
783 return NULL;
784}
785
786static list_t *
787parse_list( char **sp )
788{
5142db69 789 return parse_imap_list( NULL, sp );
f2561fda
MM
790}
791
792static void
793parse_capability( imap_t *imap, char *cmd )
794{
795 char *arg;
796 unsigned i;
797
798 imap->caps = 0x80000000;
799 while ((arg = next_arg( &cmd )))
800 for (i = 0; i < ARRAY_SIZE(cap_list); i++)
801 if (!strcmp( cap_list[i], arg ))
802 imap->caps |= 1 << i;
803 imap->rcaps = imap->caps;
804}
805
806static int
807parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s )
808{
809 imap_t *imap = ctx->imap;
810 char *arg, *p;
811
812 if (*s != '[')
813 return RESP_OK; /* no response code */
814 s++;
815 if (!(p = strchr( s, ']' ))) {
816 fprintf( stderr, "IMAP error: malformed response code\n" );
817 return RESP_BAD;
818 }
819 *p++ = 0;
820 arg = next_arg( &s );
821 if (!strcmp( "UIDVALIDITY", arg )) {
822 if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg ))) {
823 fprintf( stderr, "IMAP error: malformed UIDVALIDITY status\n" );
824 return RESP_BAD;
825 }
826 } else if (!strcmp( "UIDNEXT", arg )) {
827 if (!(arg = next_arg( &s )) || !(imap->uidnext = atoi( arg ))) {
828 fprintf( stderr, "IMAP error: malformed NEXTUID status\n" );
829 return RESP_BAD;
830 }
831 } else if (!strcmp( "CAPABILITY", arg )) {
832 parse_capability( imap, s );
833 } else if (!strcmp( "ALERT", arg )) {
834 /* RFC2060 says that these messages MUST be displayed
835 * to the user
836 */
837 for (; isspace( (unsigned char)*p ); p++);
838 fprintf( stderr, "*** IMAP ALERT *** %s\n", p );
839 } else if (cb && cb->ctx && !strcmp( "APPENDUID", arg )) {
840 if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg )) ||
841 !(arg = next_arg( &s )) || !(*(int *)cb->ctx = atoi( arg )))
842 {
843 fprintf( stderr, "IMAP error: malformed APPENDUID status\n" );
844 return RESP_BAD;
845 }
846 }
847 return RESP_OK;
848}
849
850static int
851get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
852{
853 imap_t *imap = ctx->imap;
854 struct imap_cmd *cmdp, **pcmdp, *ncmdp;
855 char *cmd, *arg, *arg1, *p;
856 int n, resp, resp2, tag;
857
858 for (;;) {
859 if (buffer_gets( &imap->buf, &cmd ))
860 return RESP_BAD;
861
862 arg = next_arg( &cmd );
863 if (*arg == '*') {
864 arg = next_arg( &cmd );
865 if (!arg) {
866 fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
867 return RESP_BAD;
868 }
869
870 if (!strcmp( "NAMESPACE", arg )) {
871 imap->ns_personal = parse_list( &cmd );
872 imap->ns_other = parse_list( &cmd );
873 imap->ns_shared = parse_list( &cmd );
874 } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) ||
875 !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) {
5142db69 876 if ((resp = parse_response_code( ctx, NULL, cmd )) != RESP_OK)
f2561fda
MM
877 return resp;
878 } else if (!strcmp( "CAPABILITY", arg ))
879 parse_capability( imap, cmd );
880 else if ((arg1 = next_arg( &cmd ))) {
881 if (!strcmp( "EXISTS", arg1 ))
882 ctx->gen.count = atoi( arg );
883 else if (!strcmp( "RECENT", arg1 ))
884 ctx->gen.recent = atoi( arg );
885 } else {
886 fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
887 return RESP_BAD;
888 }
889 } else if (!imap->in_progress) {
890 fprintf( stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" );
891 return RESP_BAD;
892 } else if (*arg == '+') {
893 /* This can happen only with the last command underway, as
894 it enforces a round-trip. */
895 cmdp = (struct imap_cmd *)((char *)imap->in_progress_append -
896 offsetof(struct imap_cmd, next));
897 if (cmdp->cb.data) {
898 n = socket_write( &imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen );
899 free( cmdp->cb.data );
5142db69 900 cmdp->cb.data = NULL;
f2561fda
MM
901 if (n != (int)cmdp->cb.dlen)
902 return RESP_BAD;
903 } else if (cmdp->cb.cont) {
904 if (cmdp->cb.cont( ctx, cmdp, cmd ))
905 return RESP_BAD;
906 } else {
907 fprintf( stderr, "IMAP error: unexpected command continuation request\n" );
908 return RESP_BAD;
909 }
910 if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2)
911 return RESP_BAD;
912 if (!cmdp->cb.cont)
913 imap->literal_pending = 0;
914 if (!tcmd)
915 return DRV_OK;
916 } else {
917 tag = atoi( arg );
918 for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
919 if (cmdp->tag == tag)
920 goto gottag;
921 fprintf( stderr, "IMAP error: unexpected tag %s\n", arg );
922 return RESP_BAD;
923 gottag:
924 if (!(*pcmdp = cmdp->next))
925 imap->in_progress_append = pcmdp;
926 imap->num_in_progress--;
927 if (cmdp->cb.cont || cmdp->cb.data)
928 imap->literal_pending = 0;
929 arg = next_arg( &cmd );
930 if (!strcmp( "OK", arg ))
931 resp = DRV_OK;
932 else {
933 if (!strcmp( "NO", arg )) {
934 if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp( cmd, "[TRYCREATE]", 11 ))) { /* SELECT, APPEND or UID COPY */
935 p = strchr( cmdp->cmd, '"' );
5142db69 936 if (!issue_imap_cmd( ctx, NULL, "CREATE \"%.*s\"", strchr( p + 1, '"' ) - p + 1, p )) {
f2561fda
MM
937 resp = RESP_BAD;
938 goto normal;
939 }
940 /* not waiting here violates the spec, but a server that does not
941 grok this nonetheless violates it too. */
942 cmdp->cb.create = 0;
943 if (!(ncmdp = issue_imap_cmd( ctx, &cmdp->cb, "%s", cmdp->cmd ))) {
944 resp = RESP_BAD;
945 goto normal;
946 }
947 free( cmdp->cmd );
948 free( cmdp );
949 if (!tcmd)
950 return 0; /* ignored */
951 if (cmdp == tcmd)
952 tcmd = ncmdp;
953 continue;
954 }
955 resp = RESP_NO;
956 } else /*if (!strcmp( "BAD", arg ))*/
957 resp = RESP_BAD;
958 fprintf( stderr, "IMAP command '%s' returned response (%s) - %s\n",
959 memcmp (cmdp->cmd, "LOGIN", 5) ?
960 cmdp->cmd : "LOGIN <user> <pass>",
961 arg, cmd ? cmd : "");
962 }
963 if ((resp2 = parse_response_code( ctx, &cmdp->cb, cmd )) > resp)
964 resp = resp2;
965 normal:
966 if (cmdp->cb.done)
967 cmdp->cb.done( ctx, cmdp, resp );
8e0f7003 968 free( cmdp->cb.data );
f2561fda
MM
969 free( cmdp->cmd );
970 free( cmdp );
971 if (!tcmd || tcmd == cmdp)
972 return resp;
973 }
974 }
975 /* not reached */
976}
977
978static void
979imap_close_server( imap_store_t *ictx )
980{
981 imap_t *imap = ictx->imap;
982
983 if (imap->buf.sock.fd != -1) {
5142db69 984 imap_exec( ictx, NULL, "LOGOUT" );
684ec6c6 985 socket_shutdown( &imap->buf.sock );
f2561fda
MM
986 }
987 free_list( imap->ns_personal );
988 free_list( imap->ns_other );
989 free_list( imap->ns_shared );
990 free( imap );
991}
992
993static void
994imap_close_store( store_t *ctx )
995{
996 imap_close_server( (imap_store_t *)ctx );
997 free_generic_messages( ctx->msgs );
998 free( ctx );
999}
1000
1001static store_t *
1002imap_open_store( imap_server_conf_t *srvc )
1003{
1004 imap_store_t *ctx;
1005 imap_t *imap;
1006 char *arg, *rsp;
1007 struct hostent *he;
1008 struct sockaddr_in addr;
1009 int s, a[2], preauth;
c9bc159d 1010 pid_t pid;
f2561fda
MM
1011
1012 ctx = xcalloc( sizeof(*ctx), 1 );
1013
1014 ctx->imap = imap = xcalloc( sizeof(*imap), 1 );
1015 imap->buf.sock.fd = -1;
1016 imap->in_progress_append = &imap->in_progress;
1017
1018 /* open connection to IMAP server */
1019
1020 if (srvc->tunnel) {
51dcaa96 1021 imap_info( "Starting tunnel '%s'... ", srvc->tunnel );
f2561fda
MM
1022
1023 if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) {
1024 perror( "socketpair" );
1025 exit( 1 );
1026 }
1027
c9bc159d
PD
1028 pid = fork();
1029 if (pid < 0)
1030 _exit( 127 );
1031 if (!pid) {
f2561fda
MM
1032 if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1)
1033 _exit( 127 );
1034 close( a[0] );
1035 close( a[1] );
8e7f9035 1036 execl( "/bin/sh", "sh", "-c", srvc->tunnel, NULL );
f2561fda
MM
1037 _exit( 127 );
1038 }
1039
1040 close (a[0]);
1041
1042 imap->buf.sock.fd = a[1];
1043
51dcaa96 1044 imap_info( "ok\n" );
f2561fda
MM
1045 } else {
1046 memset( &addr, 0, sizeof(addr) );
1047 addr.sin_port = htons( srvc->port );
1048 addr.sin_family = AF_INET;
1049
51dcaa96 1050 imap_info( "Resolving %s... ", srvc->host );
f2561fda
MM
1051 he = gethostbyname( srvc->host );
1052 if (!he) {
1053 perror( "gethostbyname" );
1054 goto bail;
1055 }
51dcaa96 1056 imap_info( "ok\n" );
f2561fda
MM
1057
1058 addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
1059
1060 s = socket( PF_INET, SOCK_STREAM, 0 );
1061
51dcaa96 1062 imap_info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
f2561fda
MM
1063 if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) {
1064 close( s );
1065 perror( "connect" );
1066 goto bail;
1067 }
f2561fda
MM
1068
1069 imap->buf.sock.fd = s;
1070
684ec6c6
RS
1071 if (srvc->use_ssl &&
1072 ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
1073 close(s);
1074 goto bail;
1075 }
1076 imap_info( "ok\n" );
f2561fda
MM
1077 }
1078
1079 /* read the greeting string */
1080 if (buffer_gets( &imap->buf, &rsp )) {
1081 fprintf( stderr, "IMAP error: no greeting response\n" );
1082 goto bail;
1083 }
1084 arg = next_arg( &rsp );
1085 if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) {
1086 fprintf( stderr, "IMAP error: invalid greeting response\n" );
1087 goto bail;
1088 }
1089 preauth = 0;
1090 if (!strcmp( "PREAUTH", arg ))
1091 preauth = 1;
1092 else if (strcmp( "OK", arg ) != 0) {
1093 fprintf( stderr, "IMAP error: unknown greeting response\n" );
1094 goto bail;
1095 }
5142db69
RS
1096 parse_response_code( ctx, NULL, rsp );
1097 if (!imap->caps && imap_exec( ctx, NULL, "CAPABILITY" ) != RESP_OK)
f2561fda
MM
1098 goto bail;
1099
1100 if (!preauth) {
684ec6c6
RS
1101#ifndef NO_OPENSSL
1102 if (!srvc->use_ssl && CAP(STARTTLS)) {
1103 if (imap_exec(ctx, 0, "STARTTLS") != RESP_OK)
1104 goto bail;
1105 if (ssl_socket_connect(&imap->buf.sock, 1,
1106 srvc->ssl_verify))
1107 goto bail;
1108 /* capabilities may have changed, so get the new capabilities */
1109 if (imap_exec(ctx, 0, "CAPABILITY") != RESP_OK)
1110 goto bail;
1111 }
1112#endif
51dcaa96 1113 imap_info ("Logging in...\n");
f2561fda
MM
1114 if (!srvc->user) {
1115 fprintf( stderr, "Skipping server %s, no user\n", srvc->host );
1116 goto bail;
1117 }
1118 if (!srvc->pass) {
1119 char prompt[80];
1120 sprintf( prompt, "Password (%s@%s): ", srvc->user, srvc->host );
1121 arg = getpass( prompt );
1122 if (!arg) {
1123 perror( "getpass" );
1124 exit( 1 );
1125 }
1126 if (!*arg) {
1127 fprintf( stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host );
1128 goto bail;
1129 }
1130 /*
1131 * getpass() returns a pointer to a static buffer. make a copy
1132 * for long term storage.
1133 */
9befac47 1134 srvc->pass = xstrdup( arg );
f2561fda
MM
1135 }
1136 if (CAP(NOLOGIN)) {
1137 fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host );
1138 goto bail;
1139 }
684ec6c6
RS
1140 if (!imap->buf.sock.ssl)
1141 imap_warn( "*** IMAP Warning *** Password is being "
1142 "sent in the clear\n" );
5142db69 1143 if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
f2561fda
MM
1144 fprintf( stderr, "IMAP error: LOGIN failed\n" );
1145 goto bail;
1146 }
1147 } /* !preauth */
1148
1149 ctx->prefix = "";
1150 ctx->trashnc = 1;
1151 return (store_t *)ctx;
1152
1153 bail:
1154 imap_close_store( &ctx->gen );
5142db69 1155 return NULL;
f2561fda
MM
1156}
1157
1158static int
1159imap_make_flags( int flags, char *buf )
1160{
1161 const char *s;
1162 unsigned i, d;
1163
1164 for (i = d = 0; i < ARRAY_SIZE(Flags); i++)
1165 if (flags & (1 << i)) {
1166 buf[d++] = ' ';
1167 buf[d++] = '\\';
1168 for (s = Flags[i]; *s; s++)
1169 buf[d++] = *s;
1170 }
1171 buf[0] = '(';
1172 buf[d++] = ')';
1173 return d;
1174}
1175
1176#define TUIDL 8
1177
1178static int
1179imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
1180{
1181 imap_store_t *ctx = (imap_store_t *)gctx;
1182 imap_t *imap = ctx->imap;
1183 struct imap_cmd_cb cb;
1184 char *fmap, *buf;
1185 const char *prefix, *box;
1186 int ret, i, j, d, len, extra, nocr;
1187 int start, sbreak = 0, ebreak = 0;
1188 char flagstr[128], tuid[TUIDL * 2 + 1];
1189
1190 memset( &cb, 0, sizeof(cb) );
1191
1192 fmap = data->data;
1193 len = data->len;
1194 nocr = !data->crlf;
1195 extra = 0, i = 0;
1196 if (!CAP(UIDPLUS) && uid) {
1197 nloop:
1198 start = i;
1199 while (i < len)
1200 if (fmap[i++] == '\n') {
1201 extra += nocr;
1202 if (i - 2 + nocr == start) {
1203 sbreak = ebreak = i - 2 + nocr;
1204 goto mktid;
1205 }
1206 if (!memcmp( fmap + start, "X-TUID: ", 8 )) {
1207 extra -= (ebreak = i) - (sbreak = start) + nocr;
1208 goto mktid;
1209 }
1210 goto nloop;
1211 }
1212 /* invalid message */
1213 free( fmap );
1214 return DRV_MSG_BAD;
1215 mktid:
1216 for (j = 0; j < TUIDL; j++)
1217 sprintf( tuid + j * 2, "%02x", arc4_getbyte() );
1218 extra += 8 + TUIDL * 2 + 2;
1219 }
1220 if (nocr)
1221 for (; i < len; i++)
1222 if (fmap[i] == '\n')
1223 extra++;
1224
1225 cb.dlen = len + extra;
1226 buf = cb.data = xmalloc( cb.dlen );
1227 i = 0;
1228 if (!CAP(UIDPLUS) && uid) {
1229 if (nocr) {
1230 for (; i < sbreak; i++)
1231 if (fmap[i] == '\n') {
1232 *buf++ = '\r';
1233 *buf++ = '\n';
1234 } else
1235 *buf++ = fmap[i];
1236 } else {
1237 memcpy( buf, fmap, sbreak );
1238 buf += sbreak;
1239 }
1240 memcpy( buf, "X-TUID: ", 8 );
1241 buf += 8;
1242 memcpy( buf, tuid, TUIDL * 2 );
1243 buf += TUIDL * 2;
1244 *buf++ = '\r';
1245 *buf++ = '\n';
1246 i = ebreak;
1247 }
1248 if (nocr) {
1249 for (; i < len; i++)
1250 if (fmap[i] == '\n') {
1251 *buf++ = '\r';
1252 *buf++ = '\n';
1253 } else
1254 *buf++ = fmap[i];
1255 } else
1256 memcpy( buf, fmap + i, len - i );
1257
1258 free( fmap );
1259
1260 d = 0;
1261 if (data->flags) {
1262 d = imap_make_flags( data->flags, flagstr );
1263 flagstr[d++] = ' ';
1264 }
1265 flagstr[d] = 0;
1266
1267 if (!uid) {
1268 box = gctx->conf->trash;
1269 prefix = ctx->prefix;
1270 cb.create = 1;
1271 if (ctx->trashnc)
1272 imap->caps = imap->rcaps & ~(1 << LITERALPLUS);
1273 } else {
1274 box = gctx->name;
1275 prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix;
1276 cb.create = 0;
1277 }
1278 cb.ctx = uid;
1279 ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr );
1280 imap->caps = imap->rcaps;
1281 if (ret != DRV_OK)
1282 return ret;
1283 if (!uid)
1284 ctx->trashnc = 0;
1285 else
1286 gctx->count++;
1287
1288 return DRV_OK;
1289}
1290
1291#define CHUNKSIZE 0x1000
1292
1293static int
1294read_message( FILE *f, msg_data_t *msg )
1295{
635d043f 1296 struct strbuf buf;
f2561fda 1297
635d043f
PH
1298 memset(msg, 0, sizeof(*msg));
1299 strbuf_init(&buf, 0);
1300
1301 do {
1302 if (strbuf_fread(&buf, CHUNKSIZE, f) <= 0)
f2561fda 1303 break;
635d043f
PH
1304 } while (!feof(f));
1305
1306 msg->len = buf.len;
b315c5c0 1307 msg->data = strbuf_detach(&buf, NULL);
f2561fda
MM
1308 return msg->len;
1309}
1310
1311static int
1312count_messages( msg_data_t *msg )
1313{
1314 int count = 0;
1315 char *p = msg->data;
1316
1317 while (1) {
1968d77d 1318 if (!prefixcmp(p, "From ")) {
f2561fda
MM
1319 count++;
1320 p += 5;
1321 }
1322 p = strstr( p+5, "\nFrom ");
1323 if (!p)
1324 break;
1325 p++;
1326 }
1327 return count;
1328}
1329
1330static int
1331split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs )
1332{
1333 char *p, *data;
1334
1335 memset( msg, 0, sizeof *msg );
1336 if (*ofs >= all_msgs->len)
1337 return 0;
1338
1339 data = &all_msgs->data[ *ofs ];
1340 msg->len = all_msgs->len - *ofs;
1341
1968d77d 1342 if (msg->len < 5 || prefixcmp(data, "From "))
f2561fda
MM
1343 return 0;
1344
e0b08307
MA
1345 p = strchr( data, '\n' );
1346 if (p) {
1347 p = &p[1];
1348 msg->len -= p-data;
1349 *ofs += p-data;
1350 data = p;
1351 }
1352
f2561fda
MM
1353 p = strstr( data, "\nFrom " );
1354 if (p)
1355 msg->len = &p[1] - data;
1356
182af834 1357 msg->data = xmemdupz(data, msg->len);
f2561fda 1358 *ofs += msg->len;
a6080a0a 1359 return 1;
f2561fda
MM
1360}
1361
1362static imap_server_conf_t server =
1363{
1364 NULL, /* name */
1365 NULL, /* tunnel */
1366 NULL, /* host */
1367 0, /* port */
1368 NULL, /* user */
1369 NULL, /* pass */
684ec6c6
RS
1370 0, /* use_ssl */
1371 1, /* ssl_verify */
f2561fda
MM
1372};
1373
1374static char *imap_folder;
1375
1376static int
ef90d6d4 1377git_imap_config(const char *key, const char *val, void *cb)
f2561fda
MM
1378{
1379 char imap_key[] = "imap.";
1380
1381 if (strncmp( key, imap_key, sizeof imap_key - 1 ))
1382 return 0;
3c17c34a
JH
1383
1384 if (!val)
1385 return config_error_nonbool(key);
1386
f2561fda
MM
1387 key += sizeof imap_key - 1;
1388
1389 if (!strcmp( "folder", key )) {
9befac47 1390 imap_folder = xstrdup( val );
f2561fda 1391 } else if (!strcmp( "host", key )) {
684ec6c6
RS
1392 if (!prefixcmp(val, "imap:"))
1393 val += 5;
1394 else if (!prefixcmp(val, "imaps:")) {
1395 val += 6;
1396 server.use_ssl = 1;
f2561fda 1397 }
1968d77d 1398 if (!prefixcmp(val, "//"))
f2561fda 1399 val += 2;
9befac47 1400 server.host = xstrdup( val );
f2561fda
MM
1401 }
1402 else if (!strcmp( "user", key ))
9befac47 1403 server.user = xstrdup( val );
f2561fda 1404 else if (!strcmp( "pass", key ))
9befac47 1405 server.pass = xstrdup( val );
f2561fda
MM
1406 else if (!strcmp( "port", key ))
1407 server.port = git_config_int( key, val );
1408 else if (!strcmp( "tunnel", key ))
9befac47 1409 server.tunnel = xstrdup( val );
684ec6c6
RS
1410 else if (!strcmp( "sslverify", key ))
1411 server.ssl_verify = git_config_bool( key, val );
f2561fda
MM
1412 return 0;
1413}
1414
1415int
1416main(int argc, char **argv)
1417{
1418 msg_data_t all_msgs, msg;
5142db69 1419 store_t *ctx = NULL;
f2561fda
MM
1420 int uid = 0;
1421 int ofs = 0;
1422 int r;
1423 int total, n = 0;
a0406b94 1424 int nongit_ok;
f2561fda
MM
1425
1426 /* init the random number generator */
1427 arc4_init();
1428
a0406b94 1429 setup_git_directory_gently(&nongit_ok);
ef90d6d4 1430 git_config(git_imap_config, NULL);
f2561fda 1431
684ec6c6
RS
1432 if (!server.port)
1433 server.port = server.use_ssl ? 993 : 143;
1434
f2561fda
MM
1435 if (!imap_folder) {
1436 fprintf( stderr, "no imap store specified\n" );
1437 return 1;
1438 }
5b67b8e2 1439 if (!server.host) {
34b5cd1f
JK
1440 if (!server.tunnel) {
1441 fprintf( stderr, "no imap host specified\n" );
1442 return 1;
1443 }
1444 server.host = "tunnel";
5b67b8e2 1445 }
f2561fda
MM
1446
1447 /* read the messages */
1448 if (!read_message( stdin, &all_msgs )) {
1449 fprintf(stderr,"nothing to send\n");
1450 return 1;
1451 }
1452
1cd88cc9
MM
1453 total = count_messages( &all_msgs );
1454 if (!total) {
1455 fprintf(stderr,"no messages to send\n");
1456 return 1;
1457 }
1458
f2561fda
MM
1459 /* write it to the imap server */
1460 ctx = imap_open_store( &server );
1461 if (!ctx) {
1462 fprintf( stderr,"failed to open store\n");
1463 return 1;
1464 }
1465
f2561fda
MM
1466 fprintf( stderr, "sending %d message%s\n", total, (total!=1)?"s":"" );
1467 ctx->name = imap_folder;
1468 while (1) {
1469 unsigned percent = n * 100 / total;
1470 fprintf( stderr, "%4u%% (%d/%d) done\r", percent, n, total );
1471 if (!split_msg( &all_msgs, &msg, &ofs ))
1472 break;
1473 r = imap_store_msg( ctx, &msg, &uid );
1474 if (r != DRV_OK) break;
1475 n++;
1476 }
1477 fprintf( stderr,"\n" );
1478
1479 imap_close_store( ctx );
1480
1481 return 0;
1482}