#include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_OPENSSL #include #include #endif #include "imap.h" struct imap_ll { int w_socket; int r_socket; time_t timeout_time; char buffer[1024]; char *bufferp; size_t buffer_left; int current_tag_number; #ifdef USE_OPENSSL SSL *ssl; #endif char *selected_folder; size_t selected_folder_namelen; char *selected_folder_uidvalidity; int selected_folder_writable; long selected_folder_exists; }; struct imap_ll * imap_ll_connect(const char *host, const char *port) { struct imap_ll *ll; struct addrinfo hints, *res, *res0; int error; int s; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(host, port, &hints, &res0); if (error) { fprintf(stderr, "getaddrinfo \"%s\":\"%s\": %s\n", host, port, gai_strerror(error) ); return NULL; } s = -1; for (res = res0; res; res = res->ai_next) { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s < 0) { fprintf(stderr, "socket \"%s\":\"%s\": %s\n", host, port, strerror(errno) ); continue; } if (connect(s, res->ai_addr, res->ai_addrlen) < 0) { fprintf(stderr, "connect \"%s\":\"%s\": %s\n", host, port, strerror(errno) ); close(s); s = -1; continue; } break; } if (s == -1) { if (!res0) { fprintf(stderr, "server \"%s\":\"%s\": no available addresses\n", host, port ); } return NULL; } ll = malloc(sizeof(*ll)); if (!ll) { fprintf(stderr, "malloc failed\n"); close(s); return NULL; } memset(ll, 0, sizeof(*ll)); ll->w_socket = ll->r_socket = s; ll->current_tag_number = -1; #ifdef USE_OPENSSL ll->ssl = NULL; #endif fcntl(s, F_SETFL, O_NONBLOCK); return ll; } struct imap_ll * imap_ll_pipe_connect(const char *cmd) { struct imap_ll *ll; pid_t child; int status; int r[2], w[2]; if (pipe(&(r[0])) < 0) { fprintf(stderr, "pipe1 failed\n"); return NULL; } if (pipe(&(w[0])) < 0) { fprintf(stderr, "pipe2 failed\n"); close(r[0]); close(r[1]); return NULL; } if ((child = fork()) < 0) { fprintf(stderr, "fork failed\n"); return NULL; } if (child == 0) { close(r[0]); close(w[1]); if (r[1] != STDOUT_FILENO) { dup2(r[1], STDOUT_FILENO); close(r[1]); } if (w[0] != STDIN_FILENO) { dup2(w[0], STDIN_FILENO); close(w[0]); } execl("/bin/sh", "sh", "-c", cmd, NULL); _exit(1); } close(r[1]); close(w[0]); ll = malloc(sizeof(*ll)); if (!ll) { fprintf(stderr, "malloc failed\n"); close(r[0]); close(w[1]); kill(child, SIGTERM); waitpid(child, &status, 0); return NULL; } memset(ll, 0, sizeof(*ll)); ll->w_socket = w[1]; ll->r_socket = r[0]; ll->current_tag_number = -1; #ifdef USE_OPENSSL ll->ssl = NULL; #endif fcntl(w[1], F_SETFL, O_NONBLOCK); fcntl(r[0], F_SETFL, O_NONBLOCK); return ll; } void imap_ll_timeout(struct imap_ll *ll, int seconds) { ll->timeout_time = time(NULL) + seconds; } static struct imap_ll_tokenlist * mktoken(int type, char *leaf, size_t len) { struct imap_ll_tokenlist *tl; tl = malloc(sizeof(*tl)); if (!tl) { fprintf(stderr, "imap_ll mktoken: malloc failed (fatal)\n"); sleep(1); _exit(0); } tl->type = type; tl->first = tl->last = NULL; tl->parent = NULL; tl->leaf = leaf; tl->leaflen = len; if (leaf) leaf[len] = 0; return tl; } static void growlist(struct imap_ll_tokenlist **nodep, struct imap_ll_tokenlist *tl) { if ((*nodep)->last) { (*nodep)->last->next = tl; } else { (*nodep)->first = tl; } (*nodep)->last = tl; tl->parent = *nodep; tl->next = NULL; if (!(tl->leaf)) { *nodep = tl; } } void imap_ll_freeline(struct imap_ll_tokenlist *t) { struct imap_ll_tokenlist *t2; if (t) { if (t->leaf) free(t->leaf); for (t2 = t->first; t2; t2 = t2->next) { imap_ll_freeline(t2); } free(t); } } #define MKPRETOKEN(alloc_size) { \ curtoken = malloc((alloc_size) + 1); \ if (!curtoken) { \ fprintf(stderr, "imap_ll_waitline(%d): malloc failed (fatal)\n", (alloc_size)); \ sleep(1); \ _exit(0); \ } \ curtoken_now = 0; \ curtoken_max = (alloc_size); \ } struct imap_ll_tokenlist * imap_ll_waitline(struct imap_ll *ll) { int timeout, n; #ifdef USE_OPENSSL int ret2; #endif char cur; struct imap_ll_tokenlist *line = NULL; struct imap_ll_tokenlist *node = NULL; int linestate = 0; int recv_tag; char *curtoken = NULL; size_t curtoken_now; size_t curtoken_max; struct pollfd pfd; for (;;) { while (ll->buffer_left) { cur = *(ll->bufferp); ll->bufferp++; ll->buffer_left--; if ((cur == 10) && (linestate < 7)) { if (curtoken && (linestate == 4)) { growlist(&node, mktoken(TLTYPE_ATOM, curtoken, curtoken_now)); } else if (curtoken) { free(curtoken); } curtoken = NULL; return line; } switch (linestate) { case 0: /* expect start of line */ if (cur == '*') { linestate = 1; } else if (cur == 't') { linestate = 2; recv_tag = 0; } else if (cur == '+') { line = mktoken(TLTYPE_CONTINUATION, NULL, 0); linestate = 3; } else { linestate = 3; } break; case 1: /* expect space after untagged prefix */ if (cur == ' ') { node = line = mktoken(TLTYPE_UNTAGGED, NULL, 0); linestate = 4; } else { linestate = 3; } break; case 2: /* expect tag */ if (cur == ' ') { if (recv_tag == ll->current_tag_number) { node = line = mktoken(TLTYPE_TAGGED, NULL, 0); linestate = 4; } else { /* wrong tag */ linestate = 3; } } else if ((cur >= '0') && (cur <= '9')) { recv_tag *= 10; recv_tag += cur - '0'; } else { linestate = 3; } break; case 3: /* error -- discard until end of line */ break; case 4: /* looking for start of next "thing" */ if (cur == 34) { linestate = 5; MKPRETOKEN(50); } else if (cur == 123) { linestate = 6; recv_tag = 0; } else if ( (cur == 91) /* square bracket */ && (line == node) /* top level */ && (line->first) /* not first in list */ && /* list has exactly 1 */ (line->first == line->last) ) { growlist(&node, mktoken(TLTYPE_SQLIST, NULL, 0)); } else if (cur == 40) { growlist(&node, mktoken(TLTYPE_LIST, NULL, 0)); } else if (( (node->type == TLTYPE_LIST) && (cur == 41) ) || ( (node->type == TLTYPE_SQLIST) && (cur == 93) )) { if (curtoken) { growlist(&node, mktoken(TLTYPE_ATOM, curtoken, curtoken_now)); curtoken = NULL; } node = node->parent; } else if ( (cur == ' ') || (cur == 9) || (cur == 13) ) { if (curtoken) { growlist(&node, mktoken(TLTYPE_ATOM, curtoken, curtoken_now)); curtoken = NULL; } } else { if (!curtoken) { MKPRETOKEN(25); } token_extend: if (curtoken_now >= curtoken_max) { curtoken_max *= 2; curtoken = realloc(curtoken, curtoken_max + 1); if (!curtoken) { fprintf(stderr, "imap_ll_waitline(size=%d): malloc failed (fatal)\n", (int)curtoken_max); sleep(1); _exit(0); } } curtoken[curtoken_now++] = cur; } break; case 5: /* quoted string */ if (cur == 34) { growlist(&node, mktoken(TLTYPE_STRING, curtoken, curtoken_now)); curtoken = NULL; linestate = 4; } else { goto token_extend; } break; case 6: /* literal count */ if (cur == 125) { linestate = 7; } else if ((cur >= '0') && (cur <= '9')) { recv_tag *= 10; recv_tag += cur - '0'; } else { linestate = 3; } break; case 7: /* CRLF after {nn} */ if (cur == 13) break; if (cur != 10) { linestate = 3; } MKPRETOKEN(recv_tag); if (recv_tag == 0) { growlist(&node, mktoken(TLTYPE_STRING, curtoken, curtoken_now)); curtoken = NULL; linestate = 4; } else { linestate = 8; } break; case 8: /* literal contents */ curtoken[curtoken_now++] = cur; if (curtoken_now == curtoken_max) { growlist(&node, mktoken(TLTYPE_STRING, curtoken, curtoken_now)); curtoken = NULL; linestate = 4; } break; } } #ifdef USE_OPENSSL if (ll->ssl) { while ((n = SSL_read(ll->ssl, &((ll->buffer)[0]), sizeof(ll->buffer))) <= 0) { ret2 = SSL_get_error(ll->ssl, n); if ((ret2 == SSL_ERROR_WANT_READ) || (ret2 == SSL_ERROR_WANT_WRITE)) { if (ret2 == SSL_ERROR_WANT_READ) { pfd.fd = ll->r_socket; pfd.events = POLLIN; } else { pfd.fd = ll->w_socket; pfd.events = POLLOUT; } timeout = ll->timeout_time - time(NULL); if (timeout < 0) timeout = 0; if ((n = poll(&pfd, 1, timeout*1000)) < 0) { if (errno == EINTR) continue; goto pollerr; } if (n == 0) goto polltimeout; } else { ERR_print_errors_fp(stderr); imap_ll_freeline(line); return NULL; } } } else { #endif pfd.fd = ll->r_socket; pfd.events = POLLIN; repoll: timeout = ll->timeout_time - time(NULL); if (timeout < 0) timeout = 0; if ((n = poll(&pfd, 1, timeout*1000)) < 0) { if (errno == EINTR) goto repoll; #ifdef USE_OPENSSL pollerr: #endif fprintf(stderr, "imap_ll_waitline: poll: %s\n", strerror(errno)); imap_ll_freeline(line); return NULL; } if (n == 0) { #ifdef USE_OPENSSL polltimeout: #endif fprintf(stderr, "imap_ll_waitline: timeout\n"); imap_ll_freeline(line); return NULL; } if ((n = read(ll->r_socket, &((ll->buffer)[0]), sizeof(ll->buffer))) < 0) { if (errno == EAGAIN) continue; if (errno == EINTR) continue; fprintf(stderr, "imap_ll_waitline: read: %s\n", strerror(errno)); imap_ll_freeline(line); return NULL; } if (n == 0) { fprintf(stderr, "imap_ll_waitline: read: EOF\n"); imap_ll_freeline(line); return NULL; } #ifdef USE_OPENSSL } #endif ll->buffer_left = n; ll->bufferp = &((ll->buffer)[0]); } } /* this may overestimate the size a bit */ static size_t string_len(struct imap_ll_tokenlist *t) { size_t result; switch (t->type) { case TLTYPE_UNTAGGED: result = 2; break; case TLTYPE_TAGGED: result = 15; break; case TLTYPE_LIST: case TLTYPE_SQLIST: result = 2; break; case TLTYPE_ATOM: result = t->leaflen; break; case TLTYPE_STRING: /* worst case is if it's sent as a literal */ result = 20 + t->leaflen; break; case TLTYPE_CONTINUATION: /* shouldn't be used */ result = 2; break; case TLTYPE_POP: case TLTYPE_END: case TLTYPE_SUB: default: result = 0; } for (t = t->first; t; t = t->next) { result++; result += string_len(t); } return result; } static char * format_cmd(char *out, struct imap_ll *ll, struct imap_ll_tokenlist *t) { int i; char close = 0; switch (t->type) { case TLTYPE_UNTAGGED: *(out++) = '*'; *(out++) = ' '; case TLTYPE_TAGGED: out += sprintf(out, "t%d ", ++(ll->current_tag_number)); break; case TLTYPE_LIST: *(out++) = '('; close = ')'; break; case TLTYPE_SQLIST: *(out++) = '['; close = ']'; break; case TLTYPE_ATOM: memcpy(out, t->leaf, t->leaflen); out += t->leaflen; break; case TLTYPE_STRING: for (i = 0; i < t->leaflen; i++) { if ( (t->leaf[i] < 32) || (t->leaf[i] == 34) || (t->leaf[i] > 126) ) break; } if (i < t->leaflen) { /* send as litteral */ out += sprintf(out, "{%d+}\015\012", (int)(t->leaflen)); memcpy(out, t->leaf, t->leaflen); out += t->leaflen; } else { /* send as regular string */ *(out++) = 34; memcpy(out, t->leaf, t->leaflen); out += t->leaflen; *(out++) = 34; } break; case TLTYPE_CONTINUATION: *(out++) = '+'; *(out++) = ' '; break; case TLTYPE_END: case TLTYPE_POP: case TLTYPE_SUB: break; } i = 0; for (t = t->first; t; t = t->next) { if (i) { *(out++) = ' '; } else { i = 1; } out = format_cmd(out, ll, t); } if (close) { *(out++) = close; } return out; } static int imap_ll_write(struct imap_ll *ll, const char *p, const char *cmdend) { size_t len; struct pollfd pfd; int ctimeout, n; #ifdef USE_OPENSSL int ret2; if (ll->ssl) { while (p < cmdend) { n = SSL_write(ll->ssl, p, cmdend-p); if (n > 0) { p += n; } else { ret2 = SSL_get_error(ll->ssl, n); if ((ret2 == SSL_ERROR_WANT_READ) || (ret2 == SSL_ERROR_WANT_WRITE)) { if (ret2 == SSL_ERROR_WANT_READ) { pfd.fd = ll->r_socket; pfd.events = POLLIN; } else { pfd.fd = ll->w_socket; pfd.events = POLLOUT; } ctimeout = ll->timeout_time - time(NULL); if (ctimeout < 0) ctimeout = 0; if ((n = poll(&pfd, 1, ctimeout*1000)) < 0) { if (errno == EINTR) continue; goto pollerr; } if (n == 0) goto polltimeout; } else { ERR_print_errors_fp(stderr); return 0; } } } } else { #endif pfd.fd = ll->w_socket; pfd.events = POLLOUT; while (p < cmdend) { repoll: ctimeout = ll->timeout_time - time(NULL); if (ctimeout < 0) ctimeout = 0; if ((n = poll(&pfd, 1, ctimeout*1000)) < 0) { if (errno == EINTR) goto repoll; #ifdef USE_OPENSSL pollerr: #endif fprintf(stderr, "imap_ll_command: poll: %s\n", strerror(errno)); return 0; } if (n == 0) { #ifdef USE_OPENSSL polltimeout: #endif fprintf(stderr, "imap_ll_command: timeout\n"); return 0; } len = write(ll->w_socket, p, cmdend-p); if (len < 0) { if (errno == EAGAIN) continue; if (errno == EINTR) continue; fprintf(stderr, "imap_ll_command: write: %s\n", strerror(errno)); return 0; } else if (len == 0) { fprintf(stderr, "imap_ll_command: short write\n"); return 0; } p += len; } #ifdef USE_OPENSSL } #endif return 1; } struct imap_ll_tokenlist * imap_ll_command(struct imap_ll *ll, struct imap_ll_tokenlist *cmd, int timeout) { size_t len; char *cmds, *cmdend; struct imap_ll_tokenlist *result, *cur; len = string_len(cmd); cmds = malloc(len+1); if (!cmds) { fprintf(stderr, "imap_ll_command: malloc failed\n"); return NULL; } cmdend = format_cmd(cmds, ll, cmd); *(cmdend++) = 13; *(cmdend++) = 10; imap_ll_timeout(ll, timeout); if (!imap_ll_write(ll, cmds, cmdend)) { free(cmds); return NULL; } free(cmds); result = mktoken(TLTYPE_LIST, NULL, 0); for (;;) { if (!(cur = imap_ll_waitline(ll))) { imap_ll_freeline(result); return NULL; } cur->parent = result; cur->next = NULL; if (result->first) { result->last->next = cur; } else { result->first = cur; } result->last = cur; if (cur->type == TLTYPE_TAGGED) { cur->next = NULL; return result; } } } void imap_ll_logout(struct imap_ll *ll) { size_t len; struct imap_ll_tokenlist *cur; char cmd[25]; len = sprintf(cmd, "t%d LOGOUT\015\012", ++(ll->current_tag_number)); imap_ll_timeout(ll, 5); if (!imap_ll_write(ll, cmd, cmd+len)) return; imap_ll_timeout(ll, 5); for (;;) { if (!(cur = imap_ll_waitline(ll))) { return; } if (cur->type == TLTYPE_TAGGED) { break; } imap_ll_freeline(cur); } imap_ll_freeline(cur); } struct imap_ll_tokenlist * imap_ll_build(enum imap_ll_tltype maintype, ...) { va_list ap; struct imap_ll_tokenlist *top = NULL; struct imap_ll_tokenlist *cur = NULL; struct imap_ll_tokenlist *l; char *s; size_t slen; int descend; enum imap_ll_tltype next_type = maintype; va_start(ap, maintype); for (;;) { switch (next_type) { default: case TLTYPE_END: va_end(ap); return top; case TLTYPE_SUB: l = va_arg(ap, struct imap_ll_tokenlist *); descend = 0; break; case TLTYPE_ATOM: case TLTYPE_STRING: s = va_arg(ap, char *); slen = va_arg(ap, size_t); if (slen == -1) slen = strlen(s); l = malloc(sizeof(*l)); if (!l) { nomem: fprintf(stderr, "imap_ll_build: malloc failure\n"); imap_ll_freeline(top); va_end(ap); return NULL; } l->type = next_type; l->leaf = malloc(slen+1); if (!(l->leaf)) { free(l); goto nomem; } memcpy(l->leaf, s, slen); l->leaf[slen] = 0; l->leaflen = slen; descend = 0; break; case TLTYPE_TAGGED: case TLTYPE_UNTAGGED: case TLTYPE_LIST: case TLTYPE_SQLIST: l = malloc(sizeof(*l)); if (!l) goto nomem; l->type = next_type; l->leaf = NULL; descend = 1; break; case TLTYPE_CONTINUATION: l = malloc(sizeof(*l)); if (!l) goto nomem; l->type = TLTYPE_CONTINUATION; l->leaf = NULL; descend = 0; break; case TLTYPE_POP: break; } if (next_type == TLTYPE_POP) { cur = cur->parent; } else { if (next_type != TLTYPE_SUB) l->first = l->last = NULL; l->parent = cur; l->next = NULL; if (cur) { if (cur->last) { cur->last->next = l; cur->last = l; } else { cur->first = cur->last = l; } } else { top = cur = l; } if (descend) cur = l; } next_type = va_arg(ap, int); } } void imap_ll_append(struct imap_ll_tokenlist *list, struct imap_ll_tokenlist *item) { if (list->last) { list->last->next = item; } else { list->first = item; } list->last = item; item->parent = list; } const char * imap_ll_status(struct imap_ll_tokenlist *t) { t = t->last->first; if (!t) return "BAD"; if ((t->type) != TLTYPE_ATOM) return "BAD"; return t->leaf; } int imap_ll_is_trycreate(struct imap_ll_tokenlist *t) { return ( (0 == strcmp(imap_ll_status(t), "NO")) && (t->last->first->next) && ((t->last->first->next->type) == TLTYPE_SQLIST) && (t->last->first->next->first) && ((t->last->first->next->first->type) == TLTYPE_ATOM) && (0 == strcmp(t->last->first->next->first->leaf, "TRYCREATE")) ); } #ifdef USE_OPENSSL enum imap_ll_starttls_result imap_ll_starttls(struct imap_ll *ll, SSL_CTX *sslctx, const char *servername) { struct imap_ll_tokenlist *cmd, *l; const char *status; struct pollfd pfd; int ret, ret2, timeout; SSL *ssl; long vr; X509 *peer; size_t servernamelen, certnamelen; char buf[256]; char buf2[256]; ssl = SSL_new(sslctx); if (!ssl) { ERR_print_errors_fp(stderr); } cmd = imap_ll_build(TLTYPE_TAGGED, TLTYPE_ATOM, "STARTTLS", (size_t)-1, TLTYPE_END); if (!cmd) { fprintf(stderr, "could not build STARTTLS command\n"); SSL_free(ll->ssl); ll->ssl = NULL; return IMAP_LL_STARTTLS_FAILED_PROCEED; } l = imap_ll_command(ll, cmd, 5); imap_ll_freeline(cmd); status = imap_ll_status(l); if (0 == strcmp(status, "NO")) { fprintf(stderr, "STARTTLS not accepted by server\n"); imap_ll_freeline(l); SSL_free(ll->ssl); ll->ssl = NULL; return IMAP_LL_STARTTLS_FAILED_PROCEED; } else if (0 != strcmp(status, "OK")) { fprintf(stderr, "STARTTLS not understood by server\n"); imap_ll_freeline(l); SSL_free(ll->ssl); ll->ssl = NULL; return IMAP_LL_STARTTLS_FAILED_PROCEED; } imap_ll_freeline(l); SSL_set_rfd(ssl, ll->r_socket); SSL_set_wfd(ssl, ll->w_socket); imap_ll_timeout(ll, 10); /* reset clock */ for (;;) { ret = SSL_connect(ssl); if (ret == 1) break; ret2 = SSL_get_error(ssl, ret); if ((ret2 == SSL_ERROR_WANT_READ) || (ret2 == SSL_ERROR_WANT_WRITE)) { if (ret2 == SSL_ERROR_WANT_READ) { pfd.fd = ll->r_socket; pfd.events = POLLIN; } else { pfd.fd = ll->w_socket; pfd.events = POLLOUT; } timeout = ll->timeout_time - time(NULL); if (timeout < 0) timeout = 0; if ((ret = poll(&pfd, 1, timeout*1000)) < 0) { if (errno == EINTR) continue; fprintf(stderr, "STARTTLS poll error: %s\n", strerror(errno)); SSL_free(ssl); return IMAP_LL_STARTTLS_FAILED; } if (ret == 0) { fprintf(stderr, "STARTTLS timeout\n"); SSL_free(ssl); return IMAP_LL_STARTTLS_FAILED; } } else { ERR_error_string(ERR_get_error(), &(buf[0])); fprintf(stderr, "STARTTLS: %s\n", buf); SSL_free(ssl); return IMAP_LL_STARTTLS_FAILED; } } if ((vr = SSL_get_verify_result(ssl)) != X509_V_OK) { fprintf(stderr, "STARTTLS: ceritifcate verification failed (%ld)\n", vr); certfail: SSL_free(ssl); return IMAP_LL_STARTTLS_FAILED_CERT; } if (!(peer = SSL_get_peer_certificate(ssl))) { fprintf(stderr, "STARTTLS: no peer certificate?\n"); goto certfail; } if (X509_NAME_get_text_by_NID(X509_get_subject_name(peer), NID_commonName, buf, sizeof(buf)) == -1) { fprintf(stderr, "STARTTLS: no common name in certificate?\n"); goto certfail; } if (!servername) goto certok; /* caller does not want us to check */ if (0 != strcasecmp(buf, servername)){ /* Certificate name does not match exactly. Check for a wildcard certificate name. The only valid form for certificate naming wildcards is an asterisk as the first character followed by any number of non-wild characters */ if (buf[0] == '*') { servernamelen = strlen(servername); certnamelen = strlen(buf+1); if ( (servernamelen >= certnamelen) && (0 == strcasecmp( buf+1, servername + (servernamelen-certnamelen)) ) ) { goto certok; } } snprintf(buf2, sizeof(buf2), "%s", servername); fprintf(stderr, "STARTTLS: server name \"%s\" != certificate common name \"%s\"\n", buf2, buf); goto certfail; } certok: ll->ssl = ssl; return IMAP_LL_STARTTLS_SUCCESS; } #endif void imap_ll_pprint(struct imap_ll_tokenlist *t, int indent, FILE *out) { int plusindent = 0; int i; const char *close = NULL; switch (t->type) { case TLTYPE_UNTAGGED: for (i = 0; i < indent; i++) fputc(9, out); fprintf(out, "*\n"); break; case TLTYPE_TAGGED: break; case TLTYPE_LIST: plusindent = 1; for (i = 0; i < indent; i++) fputc(9, out); fprintf(out, "(\n"); close = ")\n"; break; case TLTYPE_SQLIST: plusindent = 1; for (i = 0; i < indent; i++) fputc(9, out); fprintf(out, "[\n"); close = "]\n"; break; case TLTYPE_ATOM: case TLTYPE_STRING: for (i = 0; i < indent; i++) fputc(9, out); if ((t->type) == TLTYPE_STRING) fputc(34, out); for (i = 0; i < (t->leaflen); i++) { if ( (t->leaf[i] >= ' ') && (t->leaf[i] < 127) && (t->leaf[i] != 34) && (t->leaf[i] != 92) ) { fputc(t->leaf[i], out); } else { fprintf(out, "\\x%02x", t->leaf[i]); } } if ((t->type) == TLTYPE_STRING) fputc(34, out); fputc('\n', out); break; case TLTYPE_CONTINUATION: for (i = 0; i < indent; i++) fputc(9, out); fputs("+ \n", out); break; case TLTYPE_END: case TLTYPE_POP: case TLTYPE_SUB: break; } indent += plusindent; for (t = t->first; t; t = t->next) { imap_ll_pprint(t, indent, out); } indent -= plusindent; if (close) { for (i = 0; i < indent; i++) fputc(9, out); fputs(close, out); } } static struct imap_ll_tokenlist * find_capability_list_in_untagged_response(struct imap_ll_tokenlist *l) { for (l = l->first; l; l = l->next) { if ( ((l->type) == TLTYPE_UNTAGGED) && ((l->first->type) == TLTYPE_ATOM) && (0 == strcmp(l->first->leaf, "CAPABILITY")) ) return l->first->next; } fprintf(stderr, "IMAP client cannot find capabilities list\n"); return NULL; } static struct imap_ll_tokenlist * ask_for_capabilities(struct imap_ll *ll) { struct imap_ll_tokenlist *cmd, *result; cmd = imap_ll_build( TLTYPE_TAGGED, TLTYPE_ATOM, "CAPABILITY", (size_t)-1, TLTYPE_END ); result = imap_ll_command(ll, cmd, 10); imap_ll_freeline(cmd); return result; } /* caller should free whatever this function fills into freeme_p */ static struct imap_ll_tokenlist * find_capability_list_from_greeting(struct imap_ll *ll, struct imap_ll_tokenlist *l, struct imap_ll_tokenlist **freeme_p) { *freeme_p = NULL; if ( (!(l->first)) || (!(l->first->next)) || (l->first->next->type != TLTYPE_SQLIST) || (!(l->first->next->first)) || (!(l->first->next->first->leaf)) || (0 != strcmp(l->first->next->first->leaf, "CAPABILITY")) ) { /* Server did not send capabilities in greeting. */ *freeme_p = l = ask_for_capabilities(ll); return find_capability_list_in_untagged_response(l); } else { return l->first->next->first->next; } } #ifdef USE_OPENSSL static int have_capability(struct imap_ll_tokenlist *l, const char *cap) { for (; l; l = l->next) { if ((l->leaf) && (0 == strcmp(l->leaf, cap))) return 1; } return 0; } #endif static int need_capability(struct imap_ll_tokenlist *l, const char *cap) { for (; l; l = l->next) { if ((l->leaf) && (0 == strcmp(l->leaf, cap))) return 1; } fprintf(stderr, "IMAP server missing required capability %s\n", cap); return 0; } enum imap_login_result imap_login( struct imap_ll *ll, const char *username, size_t username_len, const char *password, size_t password_len #ifdef USE_OPENSSL , SSL_CTX *sslctx, const char *servername #endif ) { struct imap_ll_tokenlist *cmd, *result, *caps, *freeme; enum imap_login_result ret; const char *status; freeme = NULL; imap_ll_timeout(ll, 60); if (!(result = imap_ll_waitline(ll))) { fprintf(stderr, "error reading IMAP server greeting\n"); return imap_login_error; } if ( (!(result->first)) || ((result->first->type) != TLTYPE_ATOM) ) { fprintf(stderr, "cannot parse IMAP server greeting\n"); imap_ll_freeline(result); return imap_login_error; } if (0 == strcmp(result->first->leaf, "BYE")) { imap_ll_freeline(result); fprintf(stderr, "IMAP server rejected connection\n"); return imap_login_error; } else if (0 == strcmp(result->first->leaf, "PREAUTH")) { if (!(caps = find_capability_list_from_greeting(ll, result, &freeme))) { imap_ll_freeline(result); if (freeme) imap_ll_freeline(freeme); return imap_login_error; } goto logged_in; } else if (0 != strcmp(result->first->leaf, "OK")) { fprintf(stderr, "IMAP server unacceptable greeting\n"); return imap_login_error; } if (!(caps = find_capability_list_from_greeting(ll, result, &freeme))) { imap_ll_freeline(result); if (freeme) imap_ll_freeline(freeme); return imap_login_error; } #ifdef USE_OPENSSL if (have_capability(caps, "STARTTLS")) { switch (imap_ll_starttls(ll, sslctx, servername)) { case IMAP_LL_STARTTLS_FAILED_PROCEED: break; case IMAP_LL_STARTTLS_FAILED: case IMAP_LL_STARTTLS_FAILED_CERT: if (freeme) imap_ll_freeline(freeme); imap_ll_freeline(result); return imap_login_error; case IMAP_LL_STARTTLS_SUCCESS: if (freeme) imap_ll_freeline(freeme); freeme = NULL; imap_ll_freeline(result); result = ask_for_capabilities(ll); if (!(caps = find_capability_list_in_untagged_response(result))) { imap_ll_freeline(result); return imap_login_error; } } } #endif if (!need_capability(caps, "LITERAL+")) { ret = imap_login_error; goto exit; } if ((!username) || (!password)) { fprintf(stderr, "IMAP login credentials are needed\n"); ret = imap_login_denied; goto exit; } if (!need_capability(caps, "AUTH=PLAIN")) { ret = imap_login_error; goto exit; } if (freeme) imap_ll_freeline(freeme); imap_ll_freeline(result); cmd = imap_ll_build( TLTYPE_TAGGED, TLTYPE_ATOM, "LOGIN", (size_t)-1, TLTYPE_STRING, username, username_len, TLTYPE_STRING, password, password_len, TLTYPE_END ); result = imap_ll_command(ll, cmd, 40); imap_ll_freeline(cmd); status = imap_ll_status(result); if (0 == strcmp(status, "OK")) { if (!(caps = find_capability_list_from_greeting(ll, result->last, &freeme))) { if (freeme) imap_ll_freeline(freeme); imap_ll_freeline(result); return imap_login_error; } logged_in: ret = imap_login_ok; if (!need_capability(caps, "LITERAL+")) ret = imap_login_error; exit: imap_ll_freeline(result); if (freeme) imap_ll_freeline(freeme); return ret; } else if (0 == strcmp(status, "NO")) { fprintf(stderr, "IMAP authentication failed\n"); imap_ll_freeline(result); return imap_login_denied; } fprintf(stderr, "IMAP server did not understand login command\n"); imap_ll_freeline(result); return imap_login_error; } const char * imap_select( struct imap_ll *ll, const char *foldername, size_t foldername_len, int need_write, long *exists_p ) { const char *command_name; struct imap_ll_tokenlist *cmd, *result, *l, *l2; char *uidvalidity = NULL; long exists = 0; if (foldername_len == -1) foldername_len = strlen(foldername); if ( (ll->selected_folder) && ((ll->selected_folder_namelen) == foldername_len) && (0 == memcmp(ll->selected_folder, foldername, foldername_len)) && ((!need_write) || (ll->selected_folder_writable)) ) { if (exists_p) *exists_p = ll->selected_folder_exists; return ll->selected_folder_uidvalidity; } command_name = need_write ? "SELECT" : "EXAMINE"; cmd = imap_ll_build( TLTYPE_TAGGED, TLTYPE_ATOM, command_name, (size_t)-1, TLTYPE_STRING, foldername, foldername_len, TLTYPE_END ); result = imap_ll_command(ll, cmd, 40); imap_ll_freeline(cmd); if (!result) { fprintf(stderr, "unable to %s folder \"%s\": no response from IMAP server\n", command_name, foldername); return NULL; } if (0 != strcmp(imap_ll_status(result), "OK")) { fprintf(stderr, "unable to %s folder \"%s\":\n", command_name, foldername); imap_ll_pprint(result, 0, stderr); imap_ll_freeline(result); return NULL; } for (l = result->first; l != result->last; l = l->next) { /* Check if this is UIDVALIDITY */ l2 = l->first; if (!l2) continue; if (l2->type != TLTYPE_ATOM) continue; if (0 == strcmp(l2->leaf, "OK")) { l2 = l2->next; if (!l2) continue; if (l2->type != TLTYPE_SQLIST) continue; l2 = l2->first; if (!l2) continue; if (l2->type != TLTYPE_ATOM) continue; if (0 != strcmp(l2->leaf, "UIDVALIDITY")) continue; l2 = l2->next; if (!l2) continue; if (l2->type != TLTYPE_ATOM) continue; uidvalidity = l2->leaf; } else { if (!(l2->next)) continue; if ((l2->next->type) != TLTYPE_ATOM) continue; if (0 != strcmp(l2->next->leaf, "EXISTS")) continue; exists = atol(l2->leaf); } } if (!uidvalidity) { fprintf(stderr, "no uidvalidity in EXAMINE response for folder \"%s\":\n", foldername); imap_ll_pprint(result, 0, stderr); imap_ll_freeline(result); return NULL; } if (ll->selected_folder) free(ll->selected_folder); if (ll->selected_folder_uidvalidity) free(ll->selected_folder_uidvalidity); ll->selected_folder = malloc(foldername_len); if (ll->selected_folder) { memcpy(ll->selected_folder, foldername, foldername_len); } ll->selected_folder_namelen = foldername_len; ll->selected_folder_uidvalidity = strdup(uidvalidity); ll->selected_folder_writable = need_write; ll->selected_folder_exists = exists; if (exists_p) *exists_p = exists; imap_ll_freeline(result); return ll->selected_folder_uidvalidity; }