"Fossies" - the Fresh Open Source Software Archive

Member "dovecot-2.3.8/src/imap-login/imap-proxy.c" (8 Oct 2019, 15605 Bytes) of package /linux/misc/dovecot-2.3.8.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "imap-proxy.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.3.7.2_vs_2.3.8.

    1 /* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
    2 
    3 #include "login-common.h"
    4 #include "array.h"
    5 #include "ioloop.h"
    6 #include "istream.h"
    7 #include "ostream.h"
    8 #include "base64.h"
    9 #include "str.h"
   10 #include "str-sanitize.h"
   11 #include "safe-memset.h"
   12 #include "dsasl-client.h"
   13 #include "imap-login-client.h"
   14 #include "client-authenticate.h"
   15 #include "imap-resp-code.h"
   16 #include "imap-quote.h"
   17 #include "imap-proxy.h"
   18 
   19 static const char *imap_proxy_sent_state_names[IMAP_PROXY_SENT_STATE_COUNT] = {
   20     "id", "starttls", "capability",
   21     "authenticate", "auth-continue", "login"
   22 };
   23 static const char *imap_proxy_rcvd_state_names[IMAP_PROXY_RCVD_STATE_COUNT] = {
   24     "none", "banner", "id", "starttls", "capability",
   25     "auth-continue", "login"
   26 };
   27 
   28 static void proxy_write_id(struct imap_client *client, string_t *str)
   29 {
   30     i_assert(client->common.proxy_ttl > 1);
   31 
   32     str_append(str, "I ID (");
   33     if (client->common.client_id != NULL &&
   34         str_len(client->common.client_id) > 0) {
   35         str_append_str(str, client->common.client_id);
   36         str_append_c(str, ' ');
   37     }
   38     str_printfa(str, "\"x-session-id\" \"%s\" "
   39             "\"x-originating-ip\" \"%s\" "
   40             "\"x-originating-port\" \"%u\" "
   41             "\"x-connected-ip\" \"%s\" "
   42             "\"x-connected-port\" \"%u\" "
   43             "\"x-proxy-ttl\" \"%u\"",
   44             client_get_session_id(&client->common),
   45             net_ip2addr(&client->common.ip),
   46             client->common.remote_port,
   47             net_ip2addr(&client->common.local_ip),
   48             client->common.local_port,
   49             client->common.proxy_ttl - 1);
   50 
   51     /* append any forward_ variables to request */
   52     for(const char *const *ptr = client->common.auth_passdb_args; *ptr != NULL; ptr++) {
   53         if (strncasecmp(*ptr, "forward_", 8) == 0) {
   54             const char *key = t_strconcat("x-forward-",
   55                               t_strcut((*ptr)+8, '='),
   56                               NULL);
   57             const char *val = i_strchr_to_next(*ptr, '=');
   58             str_append_c(str, ' ');
   59             imap_append_string(str, key);
   60             str_append_c(str, ' ');
   61             imap_append_nstring(str, val);
   62         }
   63     }
   64 
   65     str_append(str, ")\r\n");
   66 }
   67 
   68 static void proxy_free_password(struct client *client)
   69 {
   70     if (client->proxy_password == NULL)
   71         return;
   72 
   73     safe_memset(client->proxy_password, 0, strlen(client->proxy_password));
   74     i_free_and_null(client->proxy_password);
   75 }
   76 
   77 static int proxy_write_starttls(struct imap_client *client, string_t *str)
   78 {
   79     enum login_proxy_ssl_flags ssl_flags = login_proxy_get_ssl_flags(client->common.login_proxy);
   80     if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) != 0) {
   81         if (client->proxy_backend_capability != NULL &&
   82             !str_array_icase_find(t_strsplit(client->proxy_backend_capability, " "), "STARTTLS")) {
   83             client_log_err(&client->common,
   84             "proxy: Remote doesn't support STARTTLS");
   85             return -1;
   86         }
   87         str_append(str, "S STARTTLS\r\n");
   88         client->proxy_sent_state |= IMAP_PROXY_SENT_STATE_STARTTLS;
   89         return 1;
   90     }
   91     return 0;
   92 }
   93 
   94 static int proxy_write_login(struct imap_client *client, string_t *str)
   95 {
   96     struct dsasl_client_settings sasl_set;
   97     const unsigned char *output;
   98     size_t len;
   99     const char *mech_name, *error;
  100 
  101     /* Send CAPABILITY command if we don't know the capabilities yet.
  102        Also as kind of a Dovecot-backend workaround if the client insisted
  103        on sending CAPABILITY command (even though our banner already sent
  104        it), send the (unnecessary) CAPABILITY command to backend as well
  105        to avoid sending the CAPABILITY reply twice (untagged and OK resp
  106        code). */
  107     if (!client->proxy_capability_request_sent &&
  108         (client->proxy_backend_capability == NULL ||
  109          client->client_ignores_capability_resp_code)) {
  110         client->proxy_capability_request_sent = TRUE;
  111         client->proxy_sent_state |= IMAP_PROXY_SENT_STATE_CAPABILITY;
  112         str_append(str, "C CAPABILITY\r\n");
  113         if (client->common.proxy_nopipelining) {
  114             /* authenticate only after receiving C OK reply. */
  115             return 0;
  116         }
  117     }
  118 
  119     if (client->common.proxy_mech == NULL) {
  120         /* logging in normally - use LOGIN command */
  121         if (client->proxy_logindisabled &&
  122             login_proxy_get_ssl_flags(client->common.login_proxy) == 0) {
  123             client_log_err(&client->common,
  124                 "proxy: Remote advertised LOGINDISABLED and SSL/TLS not enabled");
  125             return -1;
  126         }
  127         str_append(str, "L LOGIN ");
  128         imap_append_string(str, client->common.proxy_user);
  129         str_append_c(str, ' ');
  130         imap_append_string(str, client->common.proxy_password);
  131         str_append(str, "\r\n");
  132 
  133         client->proxy_sent_state |= IMAP_PROXY_SENT_STATE_LOGIN;
  134         proxy_free_password(&client->common);
  135         return 0;
  136     }
  137 
  138     i_assert(client->common.proxy_sasl_client == NULL);
  139     i_zero(&sasl_set);
  140     sasl_set.authid = client->common.proxy_master_user != NULL ?
  141         client->common.proxy_master_user : client->common.proxy_user;
  142     sasl_set.authzid = client->common.proxy_user;
  143     sasl_set.password = client->common.proxy_password;
  144     client->common.proxy_sasl_client =
  145         dsasl_client_new(client->common.proxy_mech, &sasl_set);
  146     mech_name = dsasl_client_mech_get_name(client->common.proxy_mech);
  147 
  148     str_append(str, "L AUTHENTICATE ");
  149     str_append(str, mech_name);
  150     if (client->proxy_sasl_ir) {
  151         if (dsasl_client_output(client->common.proxy_sasl_client,
  152                     &output, &len, &error) < 0) {
  153             client_log_err(&client->common, t_strdup_printf(
  154                 "proxy: SASL mechanism %s init failed: %s",
  155                 mech_name, error));
  156             return -1;
  157         }
  158         str_append_c(str, ' ');
  159         if (len == 0)
  160             str_append_c(str, '=');
  161         else
  162             base64_encode(output, len, str);
  163     }
  164     str_append(str, "\r\n");
  165     proxy_free_password(&client->common);
  166     client->proxy_sent_state |= IMAP_PROXY_SENT_STATE_AUTHENTICATE;
  167     return 0;
  168 }
  169 
  170 static int proxy_input_banner(struct imap_client *client,
  171                   struct ostream *output, const char *line)
  172 {
  173     const char *const *capabilities = NULL;
  174     string_t *str;
  175     int ret;
  176 
  177     if (!str_begins(line, "* OK ")) {
  178         client_log_err(&client->common, t_strdup_printf(
  179             "proxy: Remote returned invalid banner: %s",
  180             str_sanitize(line, 160)));
  181         return -1;
  182     }
  183 
  184     str = t_str_new(128);
  185     if (str_begins(line + 5, "[CAPABILITY ")) {
  186         capabilities = t_strsplit(t_strcut(line + 5 + 12, ']'), " ");
  187         if (str_array_icase_find(capabilities, "SASL-IR"))
  188             client->proxy_sasl_ir = TRUE;
  189         if (str_array_icase_find(capabilities, "LOGINDISABLED"))
  190             client->proxy_logindisabled = TRUE;
  191         i_free(client->proxy_backend_capability);
  192         client->proxy_backend_capability =
  193             i_strdup(t_strcut(line + 5 + 12, ']'));
  194         if (str_array_icase_find(capabilities, "ID") &&
  195             !client->common.proxy_not_trusted) {
  196             client->proxy_sent_state |= IMAP_PROXY_SENT_STATE_ID;
  197             proxy_write_id(client, str);
  198             if (client->common.proxy_nopipelining) {
  199                 /* write login or starttls after I OK */
  200                 o_stream_nsend(output, str_data(str), str_len(str));
  201                 return 0;
  202             }
  203         }
  204     }
  205 
  206     if ((ret = proxy_write_starttls(client, str)) < 0) {
  207         return -1;
  208     } else if (ret == 0) {
  209         if (proxy_write_login(client, str) < 0)
  210             return -1;
  211     }
  212 
  213     o_stream_nsend(output, str_data(str), str_len(str));
  214     return 0;
  215 }
  216 
  217 static void
  218 client_send_login_reply(struct imap_client *client, string_t *str,
  219             const char *line)
  220 {
  221     const char *capability;
  222     bool tagged_capability;
  223 
  224     capability = client->proxy_backend_capability;
  225     tagged_capability = strncasecmp(line, "[CAPABILITY ", 12) == 0;
  226     if (tagged_capability)
  227         capability = t_strcut(line + 12, ']');
  228 
  229     if (client->client_ignores_capability_resp_code && capability != NULL) {
  230         /* client has used CAPABILITY command, so it didn't understand
  231            the capabilities in the banner. send the backend's untagged
  232            CAPABILITY reply and hope that the client understands it */
  233         str_printfa(str, "* CAPABILITY %s\r\n", capability);
  234     }
  235     str_append(str, client->cmd_tag);
  236     str_append(str, " OK ");
  237     if (!client->client_ignores_capability_resp_code &&
  238         !tagged_capability && capability != NULL) {
  239         str_printfa(str, "[CAPABILITY %s] ", capability);
  240         if (*line == '[') {
  241             /* we need to send the capability.
  242                skip over this resp-code */
  243             while (*line != ']' && *line != '\0')
  244                 line++;
  245             if (*line == ' ') line++;
  246         }
  247     }
  248     str_append(str, line);
  249     str_append(str, "\r\n");
  250 }
  251 
  252 int imap_proxy_parse_line(struct client *client, const char *line)
  253 {
  254     struct imap_client *imap_client = (struct imap_client *)client;
  255     struct ostream *output;
  256     string_t *str;
  257     const unsigned char *data;
  258     size_t data_len;
  259     const char *error;
  260     int ret;
  261 
  262     i_assert(!client->destroyed);
  263 
  264     output = login_proxy_get_ostream(client->login_proxy);
  265     if (!imap_client->proxy_seen_banner) {
  266         /* this is a banner */
  267         imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_BANNER;
  268         imap_client->proxy_seen_banner = TRUE;
  269         if (proxy_input_banner(imap_client, output, line) < 0) {
  270             client_proxy_failed(client, TRUE);
  271             return -1;
  272         }
  273         return 0;
  274     } else if (*line == '+') {
  275         /* AUTHENTICATE started. finish it. */
  276         if (client->proxy_sasl_client == NULL) {
  277             /* used literals with LOGIN command, just ignore. */
  278             return 0;
  279         }
  280         imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_AUTHENTICATE;
  281         imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_AUTH_CONTINUE;
  282 
  283         str = t_str_new(128);
  284         if (line[1] != ' ' ||
  285             base64_decode(line+2, strlen(line+2), NULL, str) < 0) {
  286             client_log_err(client,
  287                 "proxy: Server sent invalid base64 data in AUTHENTICATE response");
  288             client_proxy_failed(client, TRUE);
  289             return -1;
  290         }
  291         ret = dsasl_client_input(client->proxy_sasl_client,
  292                      str_data(str), str_len(str), &error);
  293         if (ret == 0) {
  294             ret = dsasl_client_output(client->proxy_sasl_client,
  295                           &data, &data_len, &error);
  296         }
  297         if (ret < 0) {
  298             client_log_err(client, t_strdup_printf(
  299                 "proxy: Server sent invalid authentication data: %s",
  300                 error));
  301             client_proxy_failed(client, TRUE);
  302             return -1;
  303         }
  304         i_assert(ret == 0);
  305 
  306         str_truncate(str, 0);
  307         base64_encode(data, data_len, str);
  308         str_append(str, "\r\n");
  309 
  310         imap_client->proxy_sent_state |= IMAP_PROXY_SENT_STATE_AUTH_CONTINUE;
  311         o_stream_nsend(output, str_data(str), str_len(str));
  312         return 0;
  313     } else if (str_begins(line, "S ")) {
  314         imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_STARTTLS;
  315         imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_STARTTLS;
  316 
  317         if (!str_begins(line, "S OK ")) {
  318             /* STARTTLS failed */
  319             client_log_err(client, t_strdup_printf(
  320                 "proxy: Remote STARTTLS failed: %s",
  321                 str_sanitize(line + 5, 160)));
  322             client_proxy_failed(client, TRUE);
  323             return -1;
  324         }
  325         /* STARTTLS successful, begin TLS negotiation. */
  326         if (login_proxy_starttls(client->login_proxy) < 0) {
  327             client_proxy_failed(client, TRUE);
  328             return -1;
  329         }
  330         /* i/ostreams changed. */
  331         output = login_proxy_get_ostream(client->login_proxy);
  332         str = t_str_new(128);
  333         if (proxy_write_login(imap_client, str) < 0) {
  334             client_proxy_failed(client, TRUE);
  335             return -1;
  336         }
  337         o_stream_nsend(output, str_data(str), str_len(str));
  338         return 1;
  339     } else if (str_begins(line, "L OK ")) {
  340         /* Login successful. Send this line to client. */
  341         imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_LOGIN;
  342         imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_LOGIN;
  343         str = t_str_new(128);
  344         client_send_login_reply(imap_client, str, line + 5);
  345         o_stream_nsend(client->output, str_data(str), str_len(str));
  346 
  347         client_proxy_finish_destroy_client(client);
  348         return 1;
  349     } else if (str_begins(line, "L ")) {
  350         imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_LOGIN;
  351         imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_LOGIN;
  352 
  353         line += 2;
  354         if (client->set->auth_verbose) {
  355             const char *log_line = line;
  356 
  357             if (strncasecmp(log_line, "NO ", 3) == 0)
  358                 log_line += 3;
  359             client_proxy_log_failure(client, log_line);
  360         }
  361 #define STR_NO_IMAP_RESP_CODE_AUTHFAILED "NO ["IMAP_RESP_CODE_AUTHFAILED"]"
  362         if (str_begins(line, STR_NO_IMAP_RESP_CODE_AUTHFAILED)) {
  363             /* the remote sent a generic "authentication failed"
  364                error. replace it with our one, so that in case
  365                the remote is sending a different error message
  366                an attacker can't find out what users exist in
  367                the system. */
  368             client_send_reply_code(client, IMAP_CMD_REPLY_NO,
  369                            IMAP_RESP_CODE_AUTHFAILED,
  370                            AUTH_FAILED_MSG);
  371         } else if (str_begins(line, "NO [")) {
  372             /* remote sent some other resp-code. forward it. */
  373             client_send_raw(client, t_strconcat(
  374                 imap_client->cmd_tag, " ", line, "\r\n", NULL));
  375         } else {
  376             /* there was no [resp-code], so remote isn't Dovecot
  377                v1.2+. we could either forward the line as-is and
  378                leak information about what users exist in this
  379                system, or we could hide other errors than password
  380                failures. since other errors are pretty rare,
  381                it's safer to just hide them. they're still
  382                available in logs though. */
  383             client_send_reply_code(client, IMAP_CMD_REPLY_NO,
  384                            IMAP_RESP_CODE_AUTHFAILED,
  385                            AUTH_FAILED_MSG);
  386         }
  387 
  388         client->proxy_auth_failed = TRUE;
  389         client_proxy_failed(client, FALSE);
  390         return -1;
  391     } else if (strncasecmp(line, "* CAPABILITY ", 13) == 0) {
  392         i_free(imap_client->proxy_backend_capability);
  393         imap_client->proxy_backend_capability = i_strdup(line + 13);
  394         return 0;
  395     } else if (str_begins(line, "C ")) {
  396         /* Reply to CAPABILITY command we sent */
  397         imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_CAPABILITY;
  398         imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_CAPABILITY;
  399         if (str_begins(line, "C OK ") &&
  400             client->proxy_password != NULL) {
  401             /* pipelining was disabled, send the login now. */
  402             str = t_str_new(128);
  403             if (proxy_write_login(imap_client, str) < 0)
  404                 return -1;
  405             o_stream_nsend(output, str_data(str), str_len(str));
  406             return 1;
  407         }
  408         return 0;
  409     } else if (strncasecmp(line, "I ", 2) == 0) {
  410         /* Reply to ID command we sent, ignore it unless
  411            pipelining is disabled, in which case send
  412            either STARTTLS or login */
  413         imap_client->proxy_sent_state &= ~IMAP_PROXY_SENT_STATE_ID;
  414         imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_ID;
  415 
  416         if (client->proxy_nopipelining) {
  417             str = t_str_new(128);
  418             if ((ret = proxy_write_starttls(imap_client, str)) < 0) {
  419                 return -1;
  420             } else if (ret == 0) {
  421                 if (proxy_write_login(imap_client, str) < 0)
  422                     return -1;
  423             }
  424             o_stream_nsend(output, str_data(str), str_len(str));
  425             return 1;
  426         }
  427         return 0;
  428     } else if (strncasecmp(line, "* ID ", 5) == 0) {
  429         /* Reply to ID command we sent, ignore it */
  430         return 0;
  431     } else if (str_begins(line, "* ")) {
  432         /* untagged reply. just forward it. */
  433         client_send_raw(client, t_strconcat(line, "\r\n", NULL));
  434         return 0;
  435     } else {
  436         /* tagged reply, shouldn't happen. */
  437         client_log_err(client, t_strdup_printf(
  438             "proxy: Unexpected input, ignoring: %s",
  439             str_sanitize(line, 160)));
  440         return 0;
  441     }
  442 }
  443 
  444 void imap_proxy_reset(struct client *client)
  445 {
  446     struct imap_client *imap_client = (struct imap_client *)client;
  447 
  448     imap_client->proxy_sasl_ir = FALSE;
  449     imap_client->proxy_logindisabled = FALSE;
  450     imap_client->proxy_seen_banner = FALSE;
  451     imap_client->proxy_capability_request_sent = FALSE;
  452     imap_client->proxy_sent_state = 0;
  453     imap_client->proxy_rcvd_state = IMAP_PROXY_RCVD_STATE_NONE;
  454 }
  455 
  456 void imap_proxy_error(struct client *client, const char *text)
  457 {
  458     client_send_reply_code(client, IMAP_CMD_REPLY_NO,
  459                    IMAP_RESP_CODE_UNAVAILABLE, text);
  460 }
  461 
  462 const char *imap_proxy_get_state(struct client *client)
  463 {
  464     struct imap_client *imap_client = (struct imap_client *)client;
  465     string_t *str = t_str_new(128);
  466 
  467     for (unsigned int i = 0; i < IMAP_PROXY_SENT_STATE_COUNT; i++) {
  468         if ((imap_client->proxy_sent_state & (1 << i)) != 0) {
  469             if (str_len(str) > 0)
  470                 str_append_c(str, '+');
  471             str_append(str, imap_proxy_sent_state_names[i]);
  472         }
  473     }
  474     str_append_c(str, '/');
  475     str_append(str, imap_proxy_rcvd_state_names[imap_client->proxy_rcvd_state]);
  476     return str_c(str);
  477 }