/* * Get capabilities from the server, and parse them according to * details in the protocol_t, so that the CAPA() macro and perhaps * the backend_get_cap_params() function will notice them. Any * capabilities previously parsed are forgotten. * * The server might give us capabilities for free just because we * connected (or did a STARTTLS or logged in); in this case, call * with a non-zero value for @automatic. Otherwise, we send a * protocol-specific command to the server to tickle it into * disgorging some capabilities. * * Returns: 1 if any capabilities were found, 0 otherwise. */ static int ask_capability(struct backend *s, int dobanner, int automatic) { struct protstream *pout = s->out, *pin = s->in; const struct protocol_t *prot = s->prot; int matches = 0; char str[4096]; const char *resp; resp = (automatic == AUTO_CAPA_BANNER) ? prot->banner.resp : prot->capa_cmd.resp; if (!automatic) { /* no capability command */ if (!prot->capa_cmd.cmd) return -1; /* request capabilities of server */ prot_printf(pout, "%s", prot->capa_cmd.cmd); if (prot->capa_cmd.arg) prot_printf(pout, " %s", prot->capa_cmd.arg); prot_printf(pout, "\r\n"); prot_flush(pout); } forget_capabilities(s); do { if (prot_fgets(str, sizeof(str), pin) == NULL) break; matches |= parse_capability(s, str); if (!resp) { /* multiline response with no distinct end (IMAP banner) */ prot_NONBLOCK(pin); } if (dobanner) strncpy(s->banner, str, sizeof(s->banner)); /* look for the end of the capabilities */ } while (!resp || strncasecmp(str, resp, strlen(resp))); prot_BLOCK(pin); post_parse_capability(s); return matches; }
static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb, char *s) { struct imap *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->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->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; }
struct backend *backend_connect(struct backend *ret_backend, const char *server, struct protocol_t *prot, const char *userid, sasl_callback_t *cb, const char **auth_status) { /* need to (re)establish connection to server or create one */ int sock = -1; int r; int err = -1; int ask = 1; /* should we explicitly ask for capabilities? */ struct addrinfo hints, *res0 = NULL, *res; struct sockaddr_un sunsock; char buf[2048]; struct sigaction action; struct backend *ret; char rsessionid[MAX_SESSIONID_SIZE]; if (!ret_backend) { ret = xzmalloc(sizeof(struct backend)); strlcpy(ret->hostname, server, sizeof(ret->hostname)); ret->timeout = NULL; } else ret = ret_backend; if (server[0] == '/') { /* unix socket */ res0 = &hints; memset(res0, 0, sizeof(struct addrinfo)); res0->ai_family = PF_UNIX; res0->ai_socktype = SOCK_STREAM; res0->ai_addr = (struct sockaddr *) &sunsock; res0->ai_addrlen = sizeof(sunsock.sun_family) + strlen(server) + 1; #ifdef SIN6_LEN res0->ai_addrlen += sizeof(sunsock.sun_len); sunsock.sun_len = res0->ai_addrlen; #endif sunsock.sun_family = AF_UNIX; strlcpy(sunsock.sun_path, server, sizeof(sunsock.sun_path)); /* XXX set that we are preauthed */ /* change hostname to 'config_servername' */ strlcpy(ret->hostname, config_servername, sizeof(ret->hostname)); } else { /* inet socket */ memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; err = getaddrinfo(server, prot->service, &hints, &res0); if (err) { syslog(LOG_ERR, "getaddrinfo(%s) failed: %s", server, gai_strerror(err)); goto error; } } /* Setup timeout */ timedout = 0; action.sa_flags = 0; action.sa_handler = timed_out; sigemptyset(&action.sa_mask); if(sigaction(SIGALRM, &action, NULL) < 0) { syslog(LOG_ERR, "Setting timeout in backend_connect failed: sigaction: %m"); /* continue anyway */ } for (res = res0; res; res = res->ai_next) { sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock < 0) continue; alarm(config_getint(IMAPOPT_CLIENT_TIMEOUT)); if (connect(sock, res->ai_addr, res->ai_addrlen) >= 0) break; if(errno == EINTR && timedout == 1) errno = ETIMEDOUT; close(sock); sock = -1; } /* Remove timeout code */ alarm(0); signal(SIGALRM, SIG_IGN); if (sock < 0) { if (res0 != &hints) freeaddrinfo(res0); syslog(LOG_ERR, "connect(%s) failed: %m", server); goto error; } memcpy(&ret->addr, res->ai_addr, res->ai_addrlen); if (res0 != &hints) freeaddrinfo(res0); ret->in = prot_new(sock, 0); ret->out = prot_new(sock, 1); ret->sock = sock; prot_setflushonread(ret->in, ret->out); ret->prot = prot; /* use literal+ to send literals */ prot_setisclient(ret->in, 1); prot_setisclient(ret->out, 1); if (prot->banner.auto_capa) { /* try to get the capabilities from the banner */ r = ask_capability(ret, /*dobanner*/1, AUTO_CAPA_BANNER); if (r) { /* found capabilities in banner -> don't ask */ ask = 0; } } else { do { /* read the initial greeting */ if (!prot_fgets(buf, sizeof(buf), ret->in)) { syslog(LOG_ERR, "backend_connect(): couldn't read initial greeting: %s", ret->in->error ? ret->in->error : "(null)"); goto error; } } while (strncasecmp(buf, prot->banner.resp, strlen(prot->banner.resp))); strncpy(ret->banner, buf, 2048); } if (ask) { /* get the capabilities */ ask_capability(ret, /*dobanner*/0, AUTO_CAPA_NO); } /* now need to authenticate to backend server, unless we're doing LMTP/CSYNC on a UNIX socket (deliver/sync_client) */ if ((server[0] != '/') || (strcmp(prot->sasl_service, "lmtp") && strcmp(prot->sasl_service, "csync"))) { char *old_mechlist = backend_get_cap_params(ret, CAPA_AUTH); const char *my_status; if ((r = backend_authenticate(ret, userid, cb, &my_status))) { syslog(LOG_ERR, "couldn't authenticate to backend server: %s", sasl_errstring(r, NULL, NULL)); free(old_mechlist); goto error; } else { const void *ssf; sasl_getprop(ret->saslconn, SASL_SSF, &ssf); if (*((sasl_ssf_t *) ssf)) { /* if we have a SASL security layer, compare SASL mech lists before/after AUTH to check for a MITM attack */ char *new_mechlist; int auto_capa = (prot->sasl_cmd.auto_capa == AUTO_CAPA_AUTH_SSF); if (!strcmp(prot->service, "sieve")) { /* XXX Hack to handle ManageSieve servers. * No way to tell from protocol if server will * automatically send capabilities, so we treat it * as optional. */ char ch; /* wait and probe for possible auto-capability response */ usleep(250000); prot_NONBLOCK(ret->in); if ((ch = prot_getc(ret->in)) != EOF) { prot_ungetc(ch, ret->in); } else { auto_capa = AUTO_CAPA_AUTH_NO; } prot_BLOCK(ret->in); } ask_capability(ret, /*dobanner*/0, auto_capa); new_mechlist = backend_get_cap_params(ret, CAPA_AUTH); if (new_mechlist && old_mechlist && strcmp(new_mechlist, old_mechlist)) { syslog(LOG_ERR, "possible MITM attack:" "list of available SASL mechanisms changed"); free(new_mechlist); free(old_mechlist); goto error; } free(new_mechlist); } else if (prot->sasl_cmd.auto_capa == AUTO_CAPA_AUTH_OK) { /* try to get the capabilities from the AUTH success response */ forget_capabilities(ret); parse_capability(ret, my_status); post_parse_capability(ret); } if (!(strcmp(prot->service, "imap") && (strcmp(prot->service, "pop3")))) { parse_sessionid(my_status, rsessionid); syslog(LOG_NOTICE, "proxy %s sessionid=<%s> remote=<%s>", userid, session_id(), rsessionid); } } if (auth_status) *auth_status = my_status; free(old_mechlist); } /* start compression if requested and both client/server support it */ if (config_getswitch(IMAPOPT_PROXY_COMPRESS) && ret && CAPA(ret, CAPA_COMPRESS) && prot->compress_cmd.cmd && do_compress(ret, &prot->compress_cmd)) { syslog(LOG_ERR, "couldn't enable compression on backend server"); goto error; } return ret; error: forget_capabilities(ret); if (ret->in) { prot_free(ret->in); ret->in = NULL; } if (ret->out) { prot_free(ret->out); ret->out = NULL; } if (sock >= 0) close(sock); if (ret->saslconn) { sasl_dispose(&ret->saslconn); ret->saslconn = NULL; } if (!ret_backend) free(ret); return NULL; }
static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd) { struct imap *imap = ctx->imap; struct imap_cmd *cmdp, **pcmdp; char *cmd, *arg, *arg1; 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)) { /* rfc2342 NAMESPACE response. */ skip_list(&cmd); /* Personal mailboxes */ skip_list(&cmd); /* Others' mailboxes */ skip_list(&cmd); /* Shared mailboxes */ } 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))) { ; /* * Unhandled response-data with at least two words. * Ignore it. * * NEEDSWORK: Previously this case handled '<num> EXISTS' * and '<num> RECENT' but as a probably-unintended side * effect it ignores other unrecognized two-word * responses. imap-send doesn't ever try to read * messages or mailboxes these days, so consider * eliminating this case. */ } 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 {
static int backend_login(struct backend *ret, const char *userid, sasl_callback_t *cb, const char **auth_status, int noauth) { int r = 0; int ask = 1; /* should we explicitly ask for capabilities? */ char buf[2048]; struct protocol_t *prot = ret->prot; if (prot->type != TYPE_STD) return -1; if (prot->u.std.banner.auto_capa) { /* try to get the capabilities from the banner */ r = ask_capability(ret, /*dobanner*/1, AUTO_CAPA_BANNER); if (r) { /* found capabilities in banner -> don't ask */ ask = 0; } } else { do { /* read the initial greeting */ if (!prot_fgets(buf, sizeof(buf), ret->in)) { syslog(LOG_ERR, "backend_login(): couldn't read initial greeting: %s", ret->in->error ? ret->in->error : "(null)"); return -1; } } while (strncasecmp(buf, prot->u.std.banner.resp, strlen(prot->u.std.banner.resp))); xstrncpy(ret->banner, buf, 2048); } if (ask) { /* get the capabilities */ ask_capability(ret, /*dobanner*/0, AUTO_CAPA_NO); } /* now need to authenticate to backend server, unless we're doing LMTP/CSYNC on a UNIX socket (deliver/sync_client) */ if (!noauth) { char *old_mechlist = backend_get_cap_params(ret, CAPA_AUTH); const char *my_status; if ((r = backend_authenticate(ret, userid, cb, &my_status))) { syslog(LOG_ERR, "couldn't authenticate to backend server: %s", sasl_errstring(r, NULL, NULL)); free(old_mechlist); return -1; } else { const void *ssf; sasl_getprop(ret->saslconn, SASL_SSF, &ssf); if (*((sasl_ssf_t *) ssf)) { /* if we have a SASL security layer, compare SASL mech lists before/after AUTH to check for a MITM attack */ char *new_mechlist; int auto_capa = (prot->u.std.sasl_cmd.auto_capa == AUTO_CAPA_AUTH_SSF); if (!strcmp(prot->service, "sieve")) { /* XXX Hack to handle ManageSieve servers. * No way to tell from protocol if server will * automatically send capabilities, so we treat it * as optional. */ char ch; /* wait and probe for possible auto-capability response */ usleep(250000); prot_NONBLOCK(ret->in); if ((ch = prot_getc(ret->in)) != EOF) { prot_ungetc(ch, ret->in); } else { auto_capa = AUTO_CAPA_AUTH_NO; } prot_BLOCK(ret->in); } ask_capability(ret, /*dobanner*/0, auto_capa); new_mechlist = backend_get_cap_params(ret, CAPA_AUTH); if (new_mechlist && old_mechlist && strcmp(new_mechlist, old_mechlist)) { syslog(LOG_ERR, "possible MITM attack:" "list of available SASL mechanisms changed"); if (new_mechlist) free(new_mechlist); if (old_mechlist) free(old_mechlist); return -1; } free(new_mechlist); } else if (prot->u.std.sasl_cmd.auto_capa == AUTO_CAPA_AUTH_OK) { /* try to get the capabilities from the AUTH success response */ forget_capabilities(ret); parse_capability(ret, my_status); post_parse_capability(ret); } if (!(strcmp(prot->service, "imap") && (strcmp(prot->service, "pop3")))) { char rsessionid[MAX_SESSIONID_SIZE]; parse_sessionid(my_status, rsessionid); syslog(LOG_NOTICE, "auditlog: proxy %s sessionid=<%s> remote=<%s>", userid, session_id(), rsessionid); } } if (auth_status) *auth_status = my_status; free(old_mechlist); } /* start compression if requested and both client/server support it */ if (config_getswitch(IMAPOPT_PROXY_COMPRESS) && CAPA(ret, CAPA_COMPRESS) && prot->u.std.compress_cmd.cmd) { r = do_compress(ret, &prot->u.std.compress_cmd); if (r) { syslog(LOG_NOTICE, "couldn't enable compression on backend server: %s", error_message(r)); r = 0; /* not a fail-level error */ } } return 0; }
/* * Get capabilities from the server, and parse them according to * details in the protocol_t, so that the CAPA() macro and perhaps * the backend_get_cap_params() function will notice them. Any * capabilities previously parsed are forgotten. * * The server might give us capabilities for free just because we * connected (or did a STARTTLS or logged in); in this case, call * with a non-zero value for @automatic. Otherwise, we send a * protocol-specific command to the server to tickle it into * disgorging some capabilities. * * Returns: 1 if any capabilities were found, 0 otherwise. */ static int ask_capability(struct backend *s, int dobanner, int automatic) { struct protstream *pout = s->out, *pin = s->in; const struct protocol_t *prot = s->prot; int matches = 0; char str[4096]; const char *resp; if (prot->type != TYPE_STD) return 0; resp = (automatic == AUTO_CAPA_BANNER) ? prot->u.std.banner.resp : prot->u.std.capa_cmd.resp; if (!automatic) { /* no capability command */ if (!prot->u.std.capa_cmd.cmd) return -1; /* request capabilities of server */ prot_printf(pout, "%s", prot->u.std.capa_cmd.cmd); if (prot->u.std.capa_cmd.arg) prot_printf(pout, " %s", prot->u.std.capa_cmd.arg); prot_printf(pout, "\r\n"); prot_flush(pout); } forget_capabilities(s); do { if (prot_fgets(str, sizeof(str), pin) == NULL) break; matches |= parse_capability(s, str); if (!resp) { /* multiline response with no distinct end (IMAP banner) */ prot_NONBLOCK(pin); } if (dobanner) { // This routine would always take the last line, which for // starttls against backends would amount to a tagged // "ok completed" response. // Banner is always untagged if (!strncmp(str, "* ", 2)) { // The last untagged response however is a new set // of capabilities, and not the banner. // // Banner also has "server ready" some place if (strstr(str, "server ready")) { xstrncpy(s->banner, str, sizeof(s->banner)); } } } /* look for the end of the capabilities */ } while (!resp || strncasecmp(str, resp, strlen(resp))); prot_BLOCK(pin); post_parse_capability(s); return matches; }