static int send_forward(const char *forwardto, char *return_path, struct protstream *file) { FILE *sm; const char *smbuf[10]; int sm_stat; char buf[1024]; pid_t sm_pid; int body = 0, skip; smbuf[0] = "sendmail"; smbuf[1] = "-i"; /* ignore dots */ if (return_path && *return_path) { smbuf[2] = "-f"; smbuf[3] = return_path; } else { smbuf[2] = "-f"; smbuf[3] = "<>"; } smbuf[4] = "--"; smbuf[5] = forwardto; smbuf[6] = NULL; sm_pid = open_sendmail(smbuf, &sm); if (sm == NULL) { return -1; } prot_rewind(file); while (prot_fgets(buf, sizeof(buf), file)) { if (!body && buf[0] == '\r' && buf[1] == '\n') { /* blank line between header and body */ body = 1; } skip = 0; if (!body) { if (!strncasecmp(buf, "Return-Path:", 12)) { /* strip the Return-Path */ skip = 1; } } do { if (!skip) fwrite(buf, strlen(buf), 1, sm); } while (buf[strlen(buf)-1] != '\n' && prot_fgets(buf, sizeof(buf), file)); } fclose(sm); while (waitpid(sm_pid, &sm_stat, 0) < 0); return sm_stat; /* sendmail exit value */ }
int backend_ping(struct backend *s) { char buf[1024]; if (!s || !s->prot->ping_cmd.cmd) return 0; if (s->sock == -1) return -1; /* Disconnected Socket */ prot_printf(s->out, "%s\r\n", s->prot->ping_cmd.cmd); prot_flush(s->out); for (;;) { if (!prot_fgets(buf, sizeof(buf), s->in)) { /* connection closed? */ return -1; } else if (s->prot->ping_cmd.unsol && !strncmp(s->prot->ping_cmd.unsol, buf, strlen(s->prot->ping_cmd.unsol))) { /* unsolicited response */ continue; } else { /* success/fail response */ return strncmp(s->prot->ping_cmd.ok, buf, strlen(s->prot->ping_cmd.ok)); } } }
EXPORTED int backend_ping(struct backend *s, const char *userid) { char buf[1024]; struct simple_cmd_t *ping_cmd; if (!s) return 0; if (s->sock == -1) return -1; /* Disconnected Socket */ if (s->prot->type == TYPE_SPEC) return s->prot->u.spec.ping(s, userid); ping_cmd = &s->prot->u.std.ping_cmd; if (!ping_cmd->cmd) return 0; prot_printf(s->out, "%s\r\n", ping_cmd->cmd); prot_flush(s->out); for (;;) { if (!prot_fgets(buf, sizeof(buf), s->in)) { /* connection closed? */ return -1; } else if (ping_cmd->unsol && !strncmp(ping_cmd->unsol, buf, strlen(ping_cmd->unsol))) { /* unsolicited response */ continue; } else { /* success/fail response */ return strncmp(ping_cmd->ok, buf, strlen(ping_cmd->ok)); } } }
void backend_disconnect(struct backend *s) { char buf[1024]; if (!s || s->sock == -1) return; if (!prot_error(s->in)) { if (s->prot->logout_cmd.cmd) { prot_printf(s->out, "%s\r\n", s->prot->logout_cmd.cmd); prot_flush(s->out); for (;;) { if (!prot_fgets(buf, sizeof(buf), s->in)) { /* connection closed? */ break; } else if (s->prot->logout_cmd.unsol && !strncmp(s->prot->logout_cmd.unsol, buf, strlen(s->prot->logout_cmd.unsol))) { /* unsolicited response */ continue; } else { /* success/fail response -- don't care either way */ break; } } } } /* Flush the incoming buffer */ prot_NONBLOCK(s->in); prot_fill(s->in); #ifdef HAVE_SSL /* Free tlsconn */ if (s->tlsconn) { tls_reset_servertls(&s->tlsconn); s->tlsconn = NULL; } #endif /* HAVE_SSL */ /* close/free socket & prot layer */ cyrus_close_sock(s->sock); s->sock = -1; prot_free(s->in); prot_free(s->out); s->in = s->out = NULL; /* Free saslconn */ if(s->saslconn) { sasl_dispose(&(s->saslconn)); s->saslconn = NULL; } /* free last_result buffer */ buf_free(&s->last_result); forget_capabilities(s); }
EXPORTED int backend_starttls( struct backend *s, struct tls_cmd_t *tls_cmd, const char *c_cert_file, const char *c_key_file) { #ifndef HAVE_SSL return -1; #else char *auth_id = NULL; int *layerp = NULL; int r = 0; if (tls_cmd) { char buf[2048]; /* send starttls command */ prot_printf(s->out, "%s\r\n", tls_cmd->cmd); prot_flush(s->out); /* check response */ if (!prot_fgets(buf, sizeof(buf), s->in) || strncmp(buf, tls_cmd->ok, strlen(tls_cmd->ok))) return -1; } r = tls_init_clientengine(5, c_cert_file, c_key_file); if (r == -1) return -1; /* SASL and openssl have different ideas about whether ssf is signed */ layerp = (int *) &s->ext_ssf; r = tls_start_clienttls(s->in->fd, s->out->fd, layerp, &auth_id, &s->tlsconn, &s->tlssess); if (r == -1) return -1; if (s->saslconn) { r = sasl_setprop(s->saslconn, SASL_SSF_EXTERNAL, &s->ext_ssf); if (r == SASL_OK) r = sasl_setprop(s->saslconn, SASL_AUTH_EXTERNAL, auth_id); if (auth_id) free(auth_id); if (r != SASL_OK) return -1; } prot_settls(s->in, s->tlsconn); prot_settls(s->out, s->tlsconn); ask_capability(s, /*dobanner*/1, tls_cmd->auto_capa); return 0; #endif /* HAVE_SSL */ }
static int do_starttls(struct backend *s) { #ifndef HAVE_SSL return -1; #else const struct tls_cmd_t *tls_cmd = &s->prot->tls_cmd; char buf[2048]; int r; int *layerp; char *auth_id; sasl_ssf_t ssf; /* send starttls command */ prot_printf(s->out, "%s\r\n", tls_cmd->cmd); prot_flush(s->out); /* check response */ if (!prot_fgets(buf, sizeof(buf), s->in) || strncmp(buf, tls_cmd->ok, strlen(tls_cmd->ok))) return -1; r = tls_init_clientengine(5, "", ""); if (r == -1) return -1; /* SASL and openssl have different ideas about whether ssf is signed */ layerp = (int *) &ssf; r = tls_start_clienttls(s->in->fd, s->out->fd, layerp, &auth_id, &s->tlsconn, &s->tlssess); if (r == -1) return -1; r = sasl_setprop(s->saslconn, SASL_SSF_EXTERNAL, &ssf); if (r == SASL_OK) r = sasl_setprop(s->saslconn, SASL_AUTH_EXTERNAL, auth_id); if (auth_id) free(auth_id); if (r != SASL_OK) return -1; prot_settls(s->in, s->tlsconn); prot_settls(s->out, s->tlsconn); ask_capability(s, /*dobanner*/1, s->prot->tls_cmd.auto_capa); return 0; #endif /* HAVE_SSL */ }
/* * 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 do_compress(struct backend *s, struct simple_cmd_t *compress_cmd) { #ifndef HAVE_ZLIB return -1; #else char buf[1024]; /* send compress command */ prot_printf(s->out, "%s\r\n", compress_cmd->cmd); prot_flush(s->out); /* check response */ if (!prot_fgets(buf, sizeof(buf), s->in) || strncmp(buf, compress_cmd->ok, strlen(compress_cmd->ok))) return -1; prot_setcompress(s->in); prot_setcompress(s->out); return 0; #endif /* HAVE_ZLIB */ }
static int smtpclient_read(smtpclient_t *sm, smtp_readcb_t *cb, void *rock) { char buf[513]; /* Maximum length of reply line, see RFC 5321, 4.5.3.1.5. */ int r = IMAP_IOERROR; do { /* Read next reply line. */ if (!prot_fgets(buf, 513, sm->backend->in)) { r = IMAP_IOERROR; return r; } buf[512] = '\0'; /* Parse reply line. */ if (!isdigit(buf[0]) || !isdigit(buf[1]) || !isdigit(buf[2])) { return IMAP_PROTOCOL_ERROR; } if (buf[3] != '-' && !isspace(buf[3])) { return IMAP_PROTOCOL_ERROR; } char *p = memchr(buf + 4, '\n', 508); if (p == NULL) { return IMAP_PROTOCOL_ERROR; } if (*(p-1) == '\r') { --p; } *p = '\0'; /* Call callback. */ memcpy(sm->resp.code, buf, 3); buf_setcstr(&sm->resp.text, buf + 4); sm->resp.is_last = isspace(buf[3]); r = cb(sm, rock); } while (!r && !sm->resp.is_last); return r; }
/* NOTE: success_data will need to be free()d by the caller */ int saslserver(sasl_conn_t *conn, const char *mech, const char *init_resp, const char *resp_prefix, const char *continuation, const char *empty_chal, struct protstream *pin, struct protstream *pout, int *sasl_result, char **success_data) { char base64[BASE64_BUF_SIZE+1]; char *clientin = NULL; unsigned int clientinlen = 0; const char *serverout = NULL; unsigned int serveroutlen = 0; int r = SASL_OK; if (success_data) *success_data = NULL; /* initial response */ if (init_resp) { clientin = base64; if (!strcmp(init_resp, "=")) { /* zero-length initial response */ base64[0] = '\0'; } else { r = sasl_decode64(init_resp, strlen(init_resp), clientin, BASE64_BUF_SIZE, &clientinlen); } } /* start the exchange */ if (r == SASL_OK) r = sasl_server_start(conn, mech, clientin, clientinlen, &serverout, &serveroutlen); while (r == SASL_CONTINUE) { char *p; /* send the challenge to the client */ if (serveroutlen) { r = sasl_encode64(serverout, serveroutlen, base64, BASE64_BUF_SIZE, NULL); if (r != SASL_OK) break; serverout = base64; } else { serverout = empty_chal; } prot_printf(pout, "%s%s\r\n", continuation, serverout); prot_flush(pout); /* get response from the client */ if (!prot_fgets(base64, BASE64_BUF_SIZE, pin) || strncasecmp(base64, resp_prefix, strlen(resp_prefix))) { if (sasl_result) *sasl_result = SASL_FAIL; return IMAP_SASL_PROTERR; } /* trim CRLF */ p = base64 + strlen(base64) - 1; if (p >= base64 && *p == '\n') *p-- = '\0'; if (p >= base64 && *p == '\r') *p-- = '\0'; /* trim prefix */ p = base64 + strlen(resp_prefix); /* check if client cancelled */ if (p[0] == '*') { if(sasl_result) *sasl_result = SASL_BADPROT; return IMAP_SASL_CANCEL; } /* decode the response */ clientin = base64; r = sasl_decode64(p, strlen(p), clientin, BASE64_BUF_SIZE, &clientinlen); if (r != SASL_OK) break; /* do the next step */ r = sasl_server_step(conn, clientin, clientinlen, &serverout, &serveroutlen); } /* success data */ if (r == SASL_OK && serverout && success_data) { r = sasl_encode64(serverout, serveroutlen, base64, BASE64_BUF_SIZE, NULL); if (r == SASL_OK) *success_data = (char *) xstrdup(base64); } if (sasl_result) *sasl_result = r; return (r == SASL_OK ? 0 : IMAP_SASL_FAIL); }
int main(int argc, char *argv[]) { extern char *optarg; int opt; char *alt_config = NULL, *port = "119"; const char *peer = NULL, *server = "localhost", *wildmat = "*"; char *authname = NULL, *password = NULL; int psock = -1, ssock = -1; struct protstream *pin, *pout, *sin, *sout; char buf[BUFFERSIZE]; char sfile[1024] = ""; int fd = -1, i, offered, rejected, accepted, failed; time_t stamp; strarray_t resp = STRARRAY_INITIALIZER; int newnews = 1; char *datefmt = "%y%m%d %H%M%S"; if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { fatal("must run as the Cyrus user", EC_USAGE); } while ((opt = getopt(argc, argv, "C:s:w:f:a:p:ny")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; break; case 's': /* server */ server = xstrdup(optarg); if ((port = strchr(server, ':'))) *port++ = '\0'; else port = "119"; break; case 'w': /* wildmat */ wildmat = optarg; break; case 'f': /* timestamp file */ snprintf(sfile, sizeof(sfile), "%s", optarg); break; case 'a': /* authname */ authname = optarg; break; case 'p': /* password */ password = optarg; break; case 'n': /* no newnews */ newnews = 0; break; case 'y': /* newsserver is y2k compliant */ datefmt = "%Y%m%d %H%M%S"; break; default: usage(); /* NOTREACHED */ } } if (argc - optind < 1) { usage(); /* NOTREACHED */ } peer = argv[optind++]; cyrus_init(alt_config, "fetchnews", 0, 0); /* connect to the peer */ /* xxx configurable port number? */ if ((psock = init_net(peer, "119", &pin, &pout)) < 0) { fprintf(stderr, "connection to %s failed\n", peer); cyrus_done(); exit(-1); } /* read the initial greeting */ if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("20", buf, 2)) { syslog(LOG_ERR, "peer not available"); goto quit; } if (authname) { /* authenticate to peer */ /* XXX this should be modified to support SASL and STARTTLS */ prot_printf(pout, "AUTHINFO USER %s\r\n", authname); if (!prot_fgets(buf, sizeof(buf), pin)) { syslog(LOG_ERR, "AUTHINFO USER terminated abnormally"); goto quit; } else if (!strncmp("381", buf, 3)) { /* password required */ if (!password) password = cyrus_getpass("Please enter the password: "******"failed to get password\n"); goto quit; } prot_printf(pout, "AUTHINFO PASS %s\r\n", password); if (!prot_fgets(buf, sizeof(buf), pin)) { syslog(LOG_ERR, "AUTHINFO PASS terminated abnormally"); goto quit; } } if (strncmp("281", buf, 3)) { /* auth failed */ goto quit; } } /* change to reader mode - not always necessary, so ignore result */ prot_printf(pout, "MODE READER\r\n"); prot_fgets(buf, sizeof(buf), pin); if (newnews) { struct tm ctime, *ptime; /* fetch the server's current time */ prot_printf(pout, "DATE\r\n"); if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("111 ", buf, 4)) { syslog(LOG_ERR, "error fetching DATE"); goto quit; } /* parse and normalize the server time */ memset(&ctime, 0, sizeof(struct tm)); sscanf(buf+4, "%4d%02d%02d%02d%02d%02d", &ctime.tm_year, &ctime.tm_mon, &ctime.tm_mday, &ctime.tm_hour, &ctime.tm_min, &ctime.tm_sec); ctime.tm_year -= 1900; ctime.tm_mon--; ctime.tm_isdst = -1; /* read the previous timestamp */ if (!sfile[0]) { char oldfile[1024]; snprintf(sfile, sizeof(sfile), "%s/fetchnews.stamp", config_dir); /* upgrade from the old stamp filename to the new */ snprintf(oldfile, sizeof(oldfile), "%s/newsstamp", config_dir); rename(oldfile, sfile); } if ((fd = open(sfile, O_RDWR | O_CREAT, 0644)) == -1) { syslog(LOG_ERR, "cannot open %s", sfile); goto quit; } if (lock_nonblocking(fd) == -1) { syslog(LOG_ERR, "cannot lock %s: %m", sfile); goto quit; } if (read(fd, &stamp, sizeof(stamp)) < (int) sizeof(stamp)) { /* XXX do something better here */ stamp = 0; } /* ask for new articles */ if (stamp) stamp -= 180; /* adjust back 3 minutes */ ptime = gmtime(&stamp); ptime->tm_isdst = -1; strftime(buf, sizeof(buf), datefmt, ptime); prot_printf(pout, "NEWNEWS %s %s GMT\r\n", wildmat, buf); if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("230", buf, 3)) { syslog(LOG_ERR, "peer doesn't support NEWNEWS"); newnews = 0; } /* prepare server's current time as new timestamp */ stamp = mktime(&ctime); /* adjust for local timezone XXX We need to do this because we use gmtime() above. We can't change this, otherwise we'd be incompatible with an old localtime timestamp. */ stamp += gmtoff_of(&ctime, stamp); } if (!newnews) { prot_printf(pout, "LIST ACTIVE %s\r\n", wildmat); if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("215", buf, 3)) { syslog(LOG_ERR, "peer doesn't support LIST ACTIVE"); goto quit; } } /* process the NEWNEWS/LIST ACTIVE list */ while (prot_fgets(buf, sizeof(buf), pin)) { if (buf[0] == '.') break; strarray_append(&resp, buf); } if (buf[0] != '.') { syslog(LOG_ERR, "%s terminated abnormally", newnews ? "NEWNEWS" : "LIST ACTIVE"); goto quit; } if (!resp.count) { /* nothing matches our wildmat */ goto quit; } /* connect to the server */ if ((ssock = init_net(server, port, &sin, &sout)) < 0) { fprintf(stderr, "connection to %s failed\n", server); goto quit; } /* read the initial greeting */ if (!prot_fgets(buf, sizeof(buf), sin) || strncmp("20", buf, 2)) { syslog(LOG_ERR, "server not available"); goto quit; } /* fetch and store articles */ offered = rejected = accepted = failed = 0; if (newnews) { /* response is a list of msgids */ for (i = 0; i < resp.count; i++) { /* find the end of the msgid */ *(strrchr(resp.data[i], '>') + 1) = '\0'; offered++; if (fetch(resp.data[i], 1, pin, pout, sin, sout, &rejected, &accepted, &failed)) { goto quit; } } /* write the current timestamp */ lseek(fd, 0, SEEK_SET); if (write(fd, &stamp, sizeof(stamp)) < (int) sizeof(stamp)) syslog(LOG_ERR, "error writing %s", sfile); lock_unlock(fd); close(fd); } else { char group[BUFFERSIZE], msgid[BUFFERSIZE], lastbuf[50]; const char *data; unsigned long low, high, last, cur; int start; size_t datalen; struct txn *tid = NULL; newsrc_init(NULL, 0); /* * response is a list of groups. * select each group, and STAT each article we haven't seen yet. */ for (i = 0; i < resp.count; i++) { /* parse the LIST ACTIVE response */ sscanf(resp.data[i], "%s %lu %lu", group, &high, &low); last = 0; if (!cyrusdb_fetchlock(newsrc_db, group, strlen(group), &data, &datalen, &tid)) { last = strtoul(data, NULL, 10); } if (high <= last) continue; /* select the group */ prot_printf(pout, "GROUP %s\r\n", group); if (!prot_fgets(buf, sizeof(buf), pin)) { syslog(LOG_ERR, "GROUP terminated abnormally"); continue; } else if (strncmp("211", buf, 3)) break; for (start = 1, cur = low > last ? low : ++last;; cur++) { if (start) { /* STAT the first article we haven't seen */ prot_printf(pout, "STAT %lu\r\n", cur); } else { /* continue with the NEXT article */ prot_printf(pout, "NEXT\r\n"); } if (!prot_fgets(buf, sizeof(buf), pin)) { syslog(LOG_ERR, "STAT/NEXT terminated abnormally"); cur--; break; } if (!strncmp("223", buf, 3)) { /* parse the STAT/NEXT response */ sscanf(buf, "223 %lu %s", &cur, msgid); /* find the end of the msgid */ *(strrchr(msgid, '>') + 1) = '\0'; if (fetch(msgid, 0, pin, pout, sin, sout, &rejected, &accepted, &failed)) { cur--; break; } offered++; start = 0; } /* have we reached the highwater mark? */ if (cur >= high) break; } snprintf(lastbuf, sizeof(lastbuf), "%lu", cur); cyrusdb_store(newsrc_db, group, strlen(group), lastbuf, strlen(lastbuf)+1, &tid); } if (tid) cyrusdb_commit(newsrc_db, tid); newsrc_done(); } syslog(LOG_NOTICE, "fetchnews: %s offered %d; %s rejected %d, accepted %d, failed %d", peer, offered, server, rejected, accepted, failed); quit: if (psock >= 0) { prot_printf(pout, "QUIT\r\n"); prot_flush(pout); /* Flush the incoming buffer */ prot_NONBLOCK(pin); prot_fill(pin); /* close/free socket & prot layer */ close(psock); prot_free(pin); prot_free(pout); } if (ssock >= 0) { prot_printf(sout, "QUIT\r\n"); prot_flush(sout); /* Flush the incoming buffer */ prot_NONBLOCK(sin); prot_fill(sin); /* close/free socket & prot layer */ close(psock); prot_free(sin); prot_free(sout); } cyrus_done(); return 0; }
static int fetch(char *msgid, int bymsgid, struct protstream *pin, struct protstream *pout, struct protstream *sin, struct protstream *sout, int *rejected, int *accepted, int *failed) { char buf[4096]; /* see if we want this article */ prot_printf(sout, "IHAVE %s\r\n", msgid); if (!prot_fgets(buf, sizeof(buf), sin)) { syslog(LOG_ERR, "IHAVE terminated abnormally"); return -1; } else if (strncmp("335", buf, 3)) { /* don't want it */ (*rejected)++; return 0; } /* fetch the article */ if (bymsgid) prot_printf(pout, "ARTICLE %s\r\n", msgid); else prot_printf(pout, "ARTICLE\r\n"); if (!prot_fgets(buf, sizeof(buf), pin)) { syslog(LOG_ERR, "ARTICLE terminated abnormally"); return -1; } else if (strncmp("220", buf, 3)) { /* doh! the article doesn't exist, terminate IHAVE */ prot_printf(sout, ".\r\n"); } else { /* store the article */ while (prot_fgets(buf, sizeof(buf), pin)) { if (buf[0] == '.') { if (buf[1] == '\r' && buf[2] == '\n') { /* End of message */ prot_printf(sout, ".\r\n"); break; } else if (buf[1] != '.') { /* Add missing dot-stuffing */ prot_putc('.', sout); } } do { /* look for malformed lines with NUL CR LF */ if (buf[strlen(buf)-1] != '\n' && strlen(buf)+2 < sizeof(buf)-1 && buf[strlen(buf)+2] == '\n') { strlcat(buf, "\r\n", sizeof(buf)); } prot_printf(sout, "%s", buf); } while (buf[strlen(buf)-1] != '\n' && prot_fgets(buf, sizeof(buf), pin)); } if (buf[0] != '.') { syslog(LOG_ERR, "ARTICLE terminated abnormally"); return -1; } } /* see how we did */ if (!prot_fgets(buf, sizeof(buf), sin)) { syslog(LOG_ERR, "IHAVE terminated abnormally"); return -1; } else if (!strncmp("235", buf, 3)) (*accepted)++; else (*failed)++; return 0; }
/* This handles piping of the LSUB command, because we have to figure out * what mailboxes actually exist before passing them to the end user. * * It is also needed if we are doing a FIND MAILBOXES, for that we do an * LSUB on the backend anyway, because the semantics of FIND do not allow * it to return nonexistant mailboxes (RFC1176), but we need to really dumb * down the response when this is the case. */ int pipe_lsub(struct backend *s, const char *userid, const char *tag, int force_notfatal, const char *resp) { int taglen = strlen(tag); int c; int r = PROXY_OK; int exist_r; static struct buf tagb, cmd, sep, name; struct buf flags = BUF_INITIALIZER; const char *end_strip_flags[] = { " \\NonExistent)", "\\NonExistent)", " \\Noselect)", "\\Noselect)", NULL }; const char *mid_strip_flags[] = { "\\NonExistent ", "\\Noselect ", NULL }; assert(s); assert(s->timeout); s->timeout->mark = time(NULL) + IDLE_TIMEOUT; while(1) { c = getword(s->in, &tagb); if(c == EOF) { if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } if(!strncmp(tag, tagb.s, taglen)) { char buf[2048]; if(!prot_fgets(buf, sizeof(buf), s->in)) { if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* Got the end of the response */ buf_appendcstr(&s->last_result, buf); buf_cstring(&s->last_result); switch (buf[0]) { case 'O': case 'o': r = PROXY_OK; break; case 'N': case 'n': r = PROXY_NO; break; case 'B': case 'b': r = PROXY_BAD; break; default: /* huh? no result? */ if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; break; } break; /* we're done */ } c = getword(s->in, &cmd); if(c == EOF) { if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } if(strncasecmp("LSUB", cmd.s, 4) && strncasecmp("LIST", cmd.s, 4)) { prot_printf(imapd_out, "%s %s ", tagb.s, cmd.s); r = pipe_to_end_of_response(s, force_notfatal); if (r != PROXY_OK) goto out; } else { /* build up the response bit by bit */ int i; buf_reset(&flags); c = prot_getc(s->in); while(c != ')' && c != EOF) { buf_putc(&flags, c); c = prot_getc(s->in); } if(c != EOF) { /* terminate string */ buf_putc(&flags, ')'); buf_cstring(&flags); /* get the next character */ c = prot_getc(s->in); } if(c != ' ') { if(s == backend_current && !force_notfatal) fatal("Bad LSUB response from selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* Check for flags that we should remove * (e.g. Noselect, NonExistent) */ for(i=0; end_strip_flags[i]; i++) buf_replace_all(&flags, end_strip_flags[i], ")"); for (i=0; mid_strip_flags[i]; i++) buf_replace_all(&flags, mid_strip_flags[i], NULL); /* Get separator */ c = getastring(s->in, s->out, &sep); if(c != ' ') { if(s == backend_current && !force_notfatal) fatal("Bad LSUB response from selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* Get name */ c = getastring(s->in, s->out, &name); if(c == '\r') c = prot_getc(s->in); if(c != '\n') { if(s == backend_current && !force_notfatal) fatal("Bad LSUB response from selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; goto out; } /* lookup name */ exist_r = 1; char *intname = mboxname_from_external(name.s, &imapd_namespace, userid); mbentry_t *mbentry = NULL; exist_r = mboxlist_lookup(intname, &mbentry, NULL); free(intname); if(!exist_r && (mbentry->mbtype & MBTYPE_RESERVE)) exist_r = IMAP_MAILBOX_RESERVED; mboxlist_entry_free(&mbentry); /* send our response */ /* we need to set \Noselect if it's not in our mailboxes.db */ if (resp[0] == 'L') { if(!exist_r) { prot_printf(imapd_out, "* %s %s \"%s\" ", resp, flags.s, sep.s); } else { prot_printf(imapd_out, "* %s (\\Noselect) \"%s\" ", resp, sep.s); } prot_printstring(imapd_out, name.s); prot_printf(imapd_out, "\r\n"); } else if(resp[0] == 'M' && !exist_r) { /* Note that it has to exist for a find response */ prot_printf(imapd_out, "* %s ", resp); prot_printastring(imapd_out, name.s); prot_printf(imapd_out, "\r\n"); } } } /* while(1) */ out: buf_free(&flags); return r; }
/* copy our current input to 's' until we hit a true EOL. 'optimistic_literal' is how happy we should be about assuming that a command will go through by converting synchronizing literals of size less than optimistic_literal to nonsync returns 0 on success, <0 on big failure, >0 on full command not sent */ int pipe_command(struct backend *s, int optimistic_literal) { char buf[2048]; char eol[128]; int sl; s->timeout->mark = time(NULL) + IDLE_TIMEOUT; eol[0] = '\0'; /* again, the complication here are literals */ for (;;) { if (!prot_fgets(buf, sizeof(buf), imapd_in)) { /* uh oh */ return -1; } sl = strlen(buf); if (sl == (sizeof(buf) - 1) && buf[sl-1] != '\n') { /* only got part of a line */ strcpy(eol, buf + sl - 64); /* and write this out, except for what we've saved */ prot_write(s->out, buf, sl - 64); continue; } else { int i, nonsynch = 0, islit = 0, litlen = 0; if (sl < 64) { strcat(eol, buf); } else { /* write out what we have, and copy the last 64 characters to eol */ prot_printf(s->out, "%s", eol); prot_write(s->out, buf, sl - 64); strcpy(eol, buf + sl - 64); } /* now determine if eol has a literal in it */ i = strlen(eol); if (i >= 4 && eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') { /* possible literal */ i -= 4; if (eol[i] == '+') { nonsynch = 1; i--; } while (i > 0 && eol[i] != '{' && Uisdigit(eol[i])) { i--; } if (eol[i] == '{') { islit = 1; litlen = atoi(eol + i + 1); } } if (islit) { if (nonsynch) { prot_write(s->out, eol, strlen(eol)); } else if (!nonsynch && (litlen <= optimistic_literal)) { prot_printf(imapd_out, "+ i am an optimist\r\n"); prot_write(s->out, eol, strlen(eol) - 3); /* need to insert a + to turn it into a nonsynch */ prot_printf(s->out, "+}\r\n"); } else { /* we do a standard synchronizing literal */ prot_write(s->out, eol, strlen(eol)); /* but here the game gets tricky... */ prot_fgets(buf, sizeof(buf), s->in); /* but for now we cheat */ prot_write(imapd_out, buf, strlen(buf)); if (buf[0] != '+' && buf[1] != ' ') { /* char *p = strchr(buf, ' '); */ /* strncpy(s->last_result, p + 1, LAST_RESULT_LEN);*/ /* stop sending command now */ return 1; } } /* gobble literal and sent it onward */ while (litlen > 0) { int j = (litlen > (int) sizeof(buf) ? (int) sizeof(buf) : litlen); j = prot_read(imapd_in, buf, j); if(!j) { /* EOF or other error */ return -1; } prot_write(s->out, buf, j); litlen -= j; } eol[0] = '\0'; /* have to keep going for the send of the command */ continue; } else { /* no literal, so we're done! */ prot_write(s->out, eol, strlen(eol)); return 0; } } } }
/* pipe_response() reads from 's->in' until either the tagged response starting with 'tag' appears, or if 'tag' is NULL, to the end of the current line. If 'include_last' is set, the last/tagged line is included in the output, otherwise the last/tagged line is stored in 's->last_result'. In either case, the result of the tagged command is returned. 's->last_result' assumes that tagged responses don't contain literals. Unfortunately, the IMAP grammar allows them force_notfatal says to not fatal() if we lose connection to backend_current even though it is in 95% of the cases, a good idea... */ static int pipe_response(struct backend *s, const char *tag, int include_last, int force_notfatal) { char buf[2048]; char eol[128]; unsigned sl; int cont = 0, last = !tag, r = PROXY_OK; size_t taglen = 0; s->timeout->mark = time(NULL) + IDLE_TIMEOUT; if (tag) { taglen = strlen(tag); if(taglen >= sizeof(buf) + 1) { fatal("tag too large",EC_TEMPFAIL); } } buf_reset(&s->last_result); /* the only complication here are literals */ do { /* if 'cont' is set, we're looking at the continuation to a very long line. if 'last' is set, we've seen the tag we're looking for, we're just reading the end of the line. */ if (!cont) eol[0] = '\0'; if (!prot_fgets(buf, sizeof(buf), s->in)) { /* uh oh */ if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); return PROXY_NOCONNECTION; } sl = strlen(buf); if (tag) { /* Check for the tagged line */ if (!cont && buf[taglen] == ' ' && !strncmp(tag, buf, taglen)) { switch (buf[taglen + 1]) { case 'O': case 'o': r = PROXY_OK; break; case 'N': case 'n': r = PROXY_NO; break; case 'B': case 'b': r = PROXY_BAD; break; default: /* huh? no result? */ if(s == backend_current && !force_notfatal) fatal("Lost connection to selected backend", EC_UNAVAILABLE); proxy_downserver(s); r = PROXY_NOCONNECTION; break; } last = 1; } if (last && !include_last) { /* Store the tagged line */ buf_appendcstr(&s->last_result, buf+taglen+1); buf_cstring(&s->last_result); } } if (sl == (sizeof(buf) - 1) && buf[sl-1] != '\n') { /* only got part of a line */ /* we save the last 64 characters in case it has important literal information */ strcpy(eol, buf + sl - 64); /* write out this part, but we have to keep reading until we hit the end of the line */ if (!last || include_last) prot_write(imapd_out, buf, sl); cont = 1; continue; } else { /* we got the end of the line */ int i; int litlen = 0, islit = 0; if (!last || include_last) prot_write(imapd_out, buf, sl); /* now we have to see if this line ends with a literal */ if (sl < 64) { strcat(eol, buf); } else { strcat(eol, buf + sl - 63); } /* eol now contains the last characters from the line; we want to see if we've hit a literal */ i = strlen(eol); if (i >= 4 && eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') { /* possible literal */ i -= 4; while (i > 0 && eol[i] != '{' && Uisdigit(eol[i])) { i--; } if (eol[i] == '{') { islit = 1; litlen = atoi(eol + i + 1); } } /* copy the literal over */ if (islit) { while (litlen > 0) { int j = (litlen > (int) sizeof(buf) ? (int) sizeof(buf) : litlen); j = prot_read(s->in, buf, j); if(!j) { /* EOF or other error */ return -1; } if (!last || include_last) prot_write(imapd_out, buf, j); litlen -= j; } /* none of our saved information has any relevance now */ eol[0] = '\0'; /* have to keep going for the end of the line */ cont = 1; continue; } } /* ok, let's read another line */ cont = 0; } while (!last || cont); return r; }
/* * 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; }
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; }
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; }