/* gets the header "head" from msg. */ static int getheader(void *mc, const char *name, const char ***body) { sieve_test_message_t *msg = (sieve_test_message_t *)mc; *body = spool_getheader(msg->headers, name); if (!*body) return SIEVE_FAIL; return SIEVE_OK; }
EXPORTED const char *digest_recv_success(hdrcache_t hdrs) { const char **hdr = spool_getheader(hdrs, "Authentication-Info"); return (hdr ? hdr[0]: NULL); }
static int login(struct backend *s, const char *userid, sasl_callback_t *cb, const char **status, int noauth __attribute__((unused))) { int r = 0; socklen_t addrsize; struct sockaddr_storage saddr_l, saddr_r; char remoteip[60], localip[60]; static struct buf buf = BUF_INITIALIZER; sasl_security_properties_t secprops = { 0, 0xFF, PROT_BUFSIZE, 0, NULL, NULL }; /* default secprops */ const char *mech_conf, *pass, *clientout = NULL; struct auth_scheme_t *scheme = NULL; unsigned need_tls = 0, tls_done = 0, auth_done = 0, clientoutlen; hdrcache_t hdrs = NULL; if (status) *status = NULL; /* set the IP addresses */ addrsize = sizeof(struct sockaddr_storage); if (getpeername(s->sock, (struct sockaddr *) &saddr_r, &addrsize) || iptostring((struct sockaddr *) &saddr_r, addrsize, remoteip, 60)) { if (status) *status = "Failed to get remote IP address"; return SASL_FAIL; } addrsize = sizeof(struct sockaddr_storage); if (getsockname(s->sock, (struct sockaddr *) &saddr_l, &addrsize) || iptostring((struct sockaddr *) &saddr_l, addrsize, localip, 60)) { if (status) *status = "Failed to get local IP address"; return SASL_FAIL; } /* Create callbacks, if necessary */ if (!cb) { buf_setmap(&buf, s->hostname, strcspn(s->hostname, ".")); buf_appendcstr(&buf, "_password"); pass = config_getoverflowstring(buf_cstring(&buf), NULL); if (!pass) pass = config_getstring(IMAPOPT_PROXY_PASSWORD); cb = mysasl_callbacks(NULL, /* userid */ config_getstring(IMAPOPT_PROXY_AUTHNAME), config_getstring(IMAPOPT_PROXY_REALM), pass); s->sasl_cb = cb; } /* Create SASL context */ r = sasl_client_new(s->prot->sasl_service, s->hostname, localip, remoteip, cb, SASL_USAGE_FLAGS, &s->saslconn); if (r != SASL_OK) goto done; r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops); if (r != SASL_OK) goto done; /* Get SASL mechanism list. We can force a particular mechanism using a <shorthost>_mechs option */ buf_setmap(&buf, s->hostname, strcspn(s->hostname, ".")); buf_appendcstr(&buf, "_mechs"); if (!(mech_conf = config_getoverflowstring(buf_cstring(&buf), NULL))) { mech_conf = config_getstring(IMAPOPT_FORCE_SASL_CLIENT_MECH); } do { unsigned code; const char **hdr, *errstr, *serverin; char base64[BASE64_BUF_SIZE+1]; unsigned int serverinlen; struct body_t resp_body; #ifdef SASL_HTTP_REQUEST sasl_http_request_t httpreq = { "OPTIONS", /* Method */ "*", /* URI */ (u_char *) "", /* Empty body */ 0, /* Zero-length body */ 0 }; /* Persistent cxn? */ #endif /* Base64 encode any client response, if necessary */ if (clientout && scheme && (scheme->flags & AUTH_BASE64)) { r = sasl_encode64(clientout, clientoutlen, base64, BASE64_BUF_SIZE, &clientoutlen); if (r != SASL_OK) break; clientout = base64; } /* Send Authorization and/or Upgrade request to server */ prot_puts(s->out, "OPTIONS * HTTP/1.1\r\n"); prot_printf(s->out, "Host: %s\r\n", s->hostname); prot_printf(s->out, "User-Agent: %s\r\n", buf_cstring(&serverinfo)); if (scheme) { prot_printf(s->out, "Authorization: %s %s\r\n", scheme->name, clientout ? clientout : ""); prot_printf(s->out, "Authorize-As: %s\r\n", userid ? userid : "anonymous"); } else { prot_printf(s->out, "Upgrade: %s\r\n", TLS_VERSION); if (need_tls) { prot_puts(s->out, "Connection: Upgrade\r\n"); need_tls = 0; } prot_puts(s->out, "Authorization: \r\n"); } prot_puts(s->out, "\r\n"); prot_flush(s->out); serverin = clientout = NULL; serverinlen = clientoutlen = 0; /* Read response(s) from backend until final response or error */ do { resp_body.flags = BODY_DISCARD; r = http_read_response(s, METH_OPTIONS, &code, NULL, &hdrs, &resp_body, &errstr); if (r) { if (status) *status = errstr; break; } if (code == 101) { /* Switching Protocols */ if (tls_done) { r = HTTP_BAD_GATEWAY; if (status) *status = "TLS already active"; break; } else if (backend_starttls(s, NULL, NULL, NULL)) { r = HTTP_SERVER_ERROR; if (status) *status = "Unable to start TLS"; break; } else tls_done = 1; } } while (code < 200); switch (code) { default: /* Failure */ if (!r) { r = HTTP_BAD_GATEWAY; if (status) { buf_reset(&buf); buf_printf(&buf, "Unexpected status code from backend: %u", code); *status = buf_cstring(&buf); } } break; case 426: /* Upgrade Required */ if (tls_done) { r = HTTP_BAD_GATEWAY; if (status) *status = "TLS already active"; } else need_tls = 1; break; case 200: /* OK */ if (scheme->recv_success && (serverin = scheme->recv_success(hdrs))) { /* Process success data */ serverinlen = strlen(serverin); goto challenge; } break; case 401: /* Unauthorized */ if (auth_done) { r = SASL_BADAUTH; break; } if (!serverin) { int i = 0; hdr = spool_getheader(hdrs, "WWW-Authenticate"); if (!scheme) { unsigned avail_auth_schemes = 0; const char *mech = NULL; size_t len; /* Compare authentication schemes offered in * WWW-Authenticate header(s) to what we support */ buf_reset(&buf); for (i = 0; hdr && hdr[i]; i++) { len = strcspn(hdr[i], " "); for (scheme = auth_schemes; scheme->name; scheme++) { if (!strncmp(scheme->name, hdr[i], len) && !((scheme->flags & AUTH_NEED_PERSIST) && (resp_body.flags & BODY_CLOSE))) { /* Tag the scheme as available */ avail_auth_schemes |= (1 << scheme->idx); /* Add SASL-based schemes to SASL mech list */ if (scheme->saslmech) { if (buf_len(&buf)) buf_putc(&buf, ' '); buf_appendcstr(&buf, scheme->saslmech); } break; } } } /* If we have a mech_conf, use it */ if (mech_conf && buf_len(&buf)) { char *conf = xstrdup(mech_conf); char *newmechlist = intersect_mechlists(conf, (char *) buf_cstring(&buf)); if (newmechlist) { buf_setcstr(&buf, newmechlist); free(newmechlist); } else { syslog(LOG_DEBUG, "%s did not offer %s", s->hostname, mech_conf); buf_reset(&buf); } free(conf); } #ifdef SASL_HTTP_REQUEST /* Set HTTP request as specified above (REQUIRED) */ httpreq.non_persist = (resp_body.flags & BODY_CLOSE); sasl_setprop(s->saslconn, SASL_HTTP_REQUEST, &httpreq); #endif /* Try to start SASL exchange using available mechs */ r = sasl_client_start(s->saslconn, buf_cstring(&buf), NULL, /* no prompts */ NULL, NULL, /* no initial resp */ &mech); if (mech) { /* Find auth scheme associated with chosen SASL mech */ for (scheme = auth_schemes; scheme->name; scheme++) { if (scheme->saslmech && !strcmp(scheme->saslmech, mech)) break; } } else { /* No matching SASL mechs - try Basic */ scheme = &auth_schemes[AUTH_BASIC]; if (!(avail_auth_schemes & (1 << scheme->idx))) { need_tls = !tls_done; break; /* case 401 */ } } /* Find the associated WWW-Authenticate header */ for (i = 0; hdr && hdr[i]; i++) { len = strcspn(hdr[i], " "); if (!strncmp(scheme->name, hdr[i], len)) break; } } /* Get server challenge, if any */ if (hdr) { const char *p = strchr(hdr[i], ' '); serverin = p ? ++p : ""; serverinlen = strlen(serverin); } } challenge: if (serverin) { /* Perform the next step in the auth exchange */ if (scheme->idx == AUTH_BASIC) { /* Don't care about "realm" in server challenge */ const char *authid = callback_getdata(s->saslconn, cb, SASL_CB_AUTHNAME); pass = callback_getdata(s->saslconn, cb, SASL_CB_PASS); buf_reset(&buf); buf_printf(&buf, "%s:%s", authid, pass); clientout = buf_cstring(&buf); clientoutlen = buf_len(&buf); auth_done = 1; } else { /* Base64 decode any server challenge, if necessary */ if (serverin && (scheme->flags & AUTH_BASE64)) { r = sasl_decode64(serverin, serverinlen, base64, BASE64_BUF_SIZE, &serverinlen); if (r != SASL_OK) break; /* case 401 */ serverin = base64; } /* SASL mech (Digest, Negotiate, NTLM) */ r = sasl_client_step(s->saslconn, serverin, serverinlen, NULL, /* no prompts */ &clientout, &clientoutlen); if (r == SASL_OK) auth_done = 1; } } break; /* case 401 */ } } while (need_tls || clientout); done: if (hdrs) spool_free_hdrcache(hdrs); if (r && status && !*status) *status = sasl_errstring(r, NULL, NULL); return r; }
/* * file in the message structure 'm' from 'pin', assuming a dot-stuffed * stream a la lmtp. * * returns 0 on success, imap error code on failure */ static int savemsg(struct clientdata *cd, const struct lmtp_func *func, message_data_t *m) { FILE *f; struct stat sbuf; const char **body; int r; int nrcpts = m->rcpt_num; time_t now = time(NULL); static unsigned msgid_count = 0; char datestr[RFC822_DATETIME_MAX+1], tls_info[250] = ""; const char *skipheaders[] = { "Return-Path", /* need to remove (we add our own) */ NULL }; char *addbody, *fold[5], *p; int addlen, nfold, i; /* Copy to spool file */ f = func->spoolfile(m); if (!f) { prot_printf(cd->pout, "451 4.3.%c cannot create temporary file: %s\r\n", ( #ifdef EDQUOT errno == EDQUOT || #endif errno == ENOSPC) ? '1' : '2', error_message(errno)); return IMAP_IOERROR; } prot_printf(cd->pout, "354 go ahead\r\n"); if (m->return_path && func->addretpath) { /* add the return path */ char *rpath = m->return_path; const char *hostname = 0; clean_retpath(rpath); /* Append our hostname if there's no domain in address */ hostname = NULL; if (!strchr(rpath, '@') && strlen(rpath) > 0) { hostname = config_servername; } addlen = 2 + strlen(rpath) + (hostname ? 1 + strlen(hostname) : 0); addbody = xmalloc(addlen + 1); sprintf(addbody, "<%s%s%s>", rpath, hostname ? "@" : "", hostname ? hostname : ""); fprintf(f, "Return-Path: %s\r\n", addbody); spool_cache_header(xstrdup("Return-Path"), addbody, m->hdrcache); } /* add a received header */ time_to_rfc822(now, datestr, sizeof(datestr)); addlen = 8 + strlen(cd->lhlo_param) + strlen(cd->clienthost); if (m->authuser) addlen += 28 + strlen(m->authuser) + 5; /* +5 for ssf */ addlen += 25 + strlen(config_servername) + strlen(cyrus_version()); #ifdef HAVE_SSL if (cd->tls_conn) { addlen += 3 + tls_get_info(cd->tls_conn, tls_info, sizeof(tls_info)); } #endif addlen += 2 + strlen(datestr); p = addbody = xmalloc(addlen + 1); nfold = 0; p += sprintf(p, "from %s (%s)", cd->lhlo_param, cd->clienthost); fold[nfold++] = p; if (m->authuser) { const void *ssfp; sasl_ssf_t ssf; sasl_getprop(cd->conn, SASL_SSF, &ssfp); ssf = *((sasl_ssf_t *) ssfp); p += sprintf(p, " (authenticated user=%s bits=%d)", m->authuser, ssf); fold[nfold++] = p; } /* We are always atleast "with LMTPA" -- no unauth delivery */ p += sprintf(p, " by %s", config_servername); if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) { p += sprintf(p, " (Cyrus %s)", cyrus_version()); } p += sprintf(p, " with LMTP%s%s", cd->starttls_done ? "S" : "", cd->authenticated != NOAUTH ? "A" : ""); if (*tls_info) { fold[nfold++] = p; p += sprintf(p, " (%s)", tls_info); } strcat(p++, ";"); fold[nfold++] = p; p += sprintf(p, " %s", datestr); fprintf(f, "Received: "); for (i = 0, p = addbody; i < nfold; p = fold[i], i++) { fprintf(f, "%.*s\r\n\t", (int) (fold[i] - p), p); } fprintf(f, "%s\r\n", p); spool_cache_header(xstrdup("Received"), addbody, m->hdrcache); /* add any requested headers */ if (func->addheaders) { struct addheader *h; for (h = func->addheaders; h && h->name; h++) { fprintf(f, "%s: %s\r\n", h->name, h->body); spool_cache_header(xstrdup(h->name), xstrdup(h->body), m->hdrcache); } } /* fill the cache */ r = spool_fill_hdrcache(cd->pin, f, m->hdrcache, skipheaders); /* now, using our header cache, fill in the data that we want */ /* first check resent-message-id */ if ((body = msg_getheader(m, "resent-message-id")) && body[0][0]) { m->id = xstrdup(body[0]); } else if ((body = msg_getheader(m, "message-id")) && body[0][0]) { m->id = xstrdup(body[0]); } else if (body) { r = IMAP_MESSAGE_BADHEADER; /* empty message-id */ } else { /* no message-id, create one */ pid_t p = getpid(); m->id = xmalloc(40 + strlen(config_servername)); sprintf(m->id, "<cmu-lmtpd-%d-%d-%u@%s>", p, (int) now, msgid_count++, config_servername); fprintf(f, "Message-ID: %s\r\n", m->id); spool_cache_header(xstrdup("Message-ID"), xstrdup(m->id), m->hdrcache); } /* get date */ if (!(body = spool_getheader(m->hdrcache, "date"))) { /* no date, create one */ addbody = xstrdup(datestr); m->date = xstrdup(datestr); fprintf(f, "Date: %s\r\n", addbody); spool_cache_header(xstrdup("Date"), addbody, m->hdrcache); } else { m->date = xstrdup(body[0]); } if (!m->return_path && (body = msg_getheader(m, "return-path"))) { /* let's grab return_path */ m->return_path = xstrdup(body[0]); clean822space(m->return_path); clean_retpath(m->return_path); } r |= spool_copy_msg(cd->pin, f); if (r) { fclose(f); if (func->removespool) { /* remove the spool'd message */ func->removespool(m); } while (nrcpts--) { send_lmtp_error(cd->pout, r); } return r; } fflush(f); if (ferror(f)) { while (nrcpts--) { prot_printf(cd->pout, "451 4.3.%c cannot copy message to temporary file: %s\r\n", ( #ifdef EDQUOT errno == EDQUOT || #endif errno == ENOSPC) ? '1' : '2', error_message(errno)); } fclose(f); if (func->removespool) func->removespool(m); return IMAP_IOERROR; } if (fstat(fileno(f), &sbuf) == -1) { while (nrcpts--) { prot_printf(cd->pout, "451 4.3.2 cannot stat message temporary file: %s\r\n", error_message(errno)); } fclose(f); if (func->removespool) func->removespool(m); return IMAP_IOERROR; } m->size = sbuf.st_size; m->f = f; m->data = prot_new(fileno(f), 0); return 0; }
HIDDEN int ws_start_channel(struct transaction_t *txn, const char *protocol, int (*data_cb)(struct buf *inbuf, struct buf *outbuf, struct buf *logbuf, void **rock)) { int r; const char **hdr, *accept = NULL; wslay_event_context_ptr ev; struct ws_context *ctx; struct wslay_event_callbacks callbacks = { recv_cb, send_cb, NULL, NULL, NULL, NULL, on_msg_recv_cb }; /* Check for supported WebSocket version */ hdr = spool_getheader(txn->req_hdrs, "Sec-WebSocket-Version"); if (!hdr) { txn->error.desc = "Missing WebSocket version"; return HTTP_BAD_REQUEST; } else if (hdr[1]) { txn->error.desc = "Multiple WebSocket versions"; return HTTP_BAD_REQUEST; } else if (strcmp(hdr[0], WS_VERSION)) { txn->error.desc = "Unsupported WebSocket version"; return HTTP_UPGRADE; } if (protocol) { /* Check for supported WebSocket subprotocol */ int i, found = 0; hdr = spool_getheader(txn->req_hdrs, "Sec-WebSocket-Protocol"); if (!hdr) { txn->error.desc = "Missing WebSocket protocol"; return HTTP_BAD_REQUEST; } for (i = 0; !found && hdr[i]; i++) { tok_t tok = TOK_INITIALIZER(hdr[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); char *token; while ((token = tok_next(&tok))) { if (!strcmp(token, protocol)) { found = 1; break; } } tok_fini(&tok); } if (!found) { txn->error.desc = "Unsupported WebSocket protocol"; return HTTP_BAD_REQUEST; } } if (txn->flags.ver == VER_1_1) { unsigned char sha1buf[SHA1_DIGEST_LENGTH]; /* Check for WebSocket client key */ hdr = spool_getheader(txn->req_hdrs, "Sec-WebSocket-Key"); if (!hdr) { txn->error.desc = "Missing WebSocket client key"; return HTTP_BAD_REQUEST; } else if (hdr[1]) { txn->error.desc = "Multiple WebSocket client keys"; return HTTP_BAD_REQUEST; } else if (strlen(hdr[0]) != WS_CKEY_LEN) { txn->error.desc = "Invalid WebSocket client key"; return HTTP_BAD_REQUEST; } /* Create WebSocket accept key */ buf_setcstr(&txn->buf, hdr[0]); buf_appendcstr(&txn->buf, WS_GUID); xsha1((u_char *) buf_base(&txn->buf), buf_len(&txn->buf), sha1buf); buf_ensure(&txn->buf, WS_AKEY_LEN+1); accept = buf_base(&txn->buf); r = sasl_encode64((char *) sha1buf, SHA1_DIGEST_LENGTH, (char *) accept, WS_AKEY_LEN+1, NULL); if (r != SASL_OK) syslog(LOG_WARNING, "sasl_encode64: %d", r); } /* Create server context */ r = wslay_event_context_server_init(&ev, &callbacks, txn); if (r) { syslog(LOG_WARNING, "wslay_event_context_init: %s", wslay_strerror(r)); return HTTP_SERVER_ERROR; } /* Create channel context */ ctx = xzmalloc(sizeof(struct ws_context)); ctx->event = ev; ctx->accept = accept; ctx->protocol = protocol; ctx->data_cb = data_cb; txn->ws_ctx = ctx; /* Check for supported WebSocket extensions */ parse_extensions(txn); /* Prepare log buffer */ /* Add client data */ buf_printf(&ctx->log, "%s", txn->conn->clienthost); if (httpd_userid) buf_printf(&ctx->log, " as \"%s\"", httpd_userid); if ((hdr = spool_getheader(txn->req_hdrs, "User-Agent"))) { buf_printf(&ctx->log, " with \"%s\"", hdr[0]); if ((hdr = spool_getheader(txn->req_hdrs, "X-Client"))) buf_printf(&ctx->log, " by \"%s\"", hdr[0]); else if ((hdr = spool_getheader(txn->req_hdrs, "X-Requested-With"))) buf_printf(&ctx->log, " by \"%s\"", hdr[0]); } /* Add request-line */ buf_printf(&ctx->log, "; \"WebSocket/%s via %s\"", protocol ? protocol : "echo" , txn->req_line.ver); ctx->log_tail = buf_len(&ctx->log); /* Tell client that WebSocket negotiation has succeeded */ if (txn->conn->sess_ctx) { /* Treat WS data as chunked response */ txn->flags.te = TE_CHUNKED; response_header(HTTP_OK, txn); /* Force the response to the client immediately */ prot_flush(httpd_out); } else response_header(HTTP_SWITCH_PROT, txn); /* Set connection as non-blocking */ prot_NONBLOCK(txn->conn->pin); /* Don't do telemetry logging in prot layer */ prot_setlog(txn->conn->pin, PROT_NO_FD); prot_setlog(txn->conn->pout, PROT_NO_FD); return 0; }
/* Parse Sec-WebSocket-Extensions header(s) for interesting extensions */ static void parse_extensions(struct transaction_t *txn) { struct ws_context *ctx = (struct ws_context *) txn->ws_ctx; const char **ext_hdr = spool_getheader(txn->req_hdrs, "Sec-WebSocket-Extensions"); int i; /* Look for interesting extensions. Unknown == ignore */ for (i = 0; ext_hdr && ext_hdr[i]; i++) { tok_t ext = TOK_INITIALIZER(ext_hdr[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); char *token; while ((token = tok_next(&ext))) { struct ws_extension *extp = extensions; tok_t param; tok_initm(¶m, token, ";", TOK_TRIMLEFT|TOK_TRIMRIGHT); token = tok_next(¶m); /* Locate a matching extension */ while (extp->name && strcmp(token, extp->name)) extp++; /* Check if client wants per-message compression */ if (extp->flag == EXT_PMCE_DEFLATE) { unsigned client_max_wbits = MAX_WBITS; ctx->pmce.deflate.max_wbits = MAX_WBITS; /* Process parameters */ while ((token = tok_next(¶m))) { char *value = strchr(token, '='); if (value) *value++ = '\0'; if (!strcmp(token, "server_no_context_takeover")) { ctx->pmce.deflate.no_context = 1; } else if (!strcmp(token, "client_no_context_takeover")) { /* Don't HAVE to do anything here */ } else if (!strcmp(token, "server_max_window_bits")) { if (value) { if (*value == '"') value++; ctx->pmce.deflate.max_wbits = atoi(value); } else ctx->pmce.deflate.max_wbits = 0; /* force error */ } else if (!strcmp(token, "client_max_window_bits")) { if (value) { if (*value == '"') value++; client_max_wbits = atoi(value); } } } #ifdef HAVE_ZLIB /* Reconfigure compression context for raw deflate */ if (txn->zstrm) deflateEnd(txn->zstrm); else txn->zstrm = xmalloc(sizeof(z_stream)); if (deflateInit2(txn->zstrm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -ctx->pmce.deflate.max_wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) { free(txn->zstrm); txn->zstrm = NULL; } if (txn->zstrm) { /* Configure decompression context for raw deflate */ ctx->pmce.deflate.zstrm = xmalloc(sizeof(z_stream)); if (inflateInit2(ctx->pmce.deflate.zstrm, -client_max_wbits) != Z_OK) { free(ctx->pmce.deflate.zstrm); ctx->pmce.deflate.zstrm = NULL; } } #endif /* HAVE_ZLIB */ if (ctx->pmce.deflate.zstrm) { /* Compression has been enabled */ wslay_event_config_set_allowed_rsv_bits(ctx->event, WSLAY_RSV1_BIT); ctx->ext = extp->flag; } } tok_fini(¶m); } tok_fini(&ext); } }