/************************************************* * Exim - an Internet mail transport agent * *************************************************/ /* Copyright (c) The Exim Maintainers 2023 - 2024 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "exim.h" #ifdef EXPERIMENTAL_XCLIENT /* From https://www.postfix.org/XCLIENT_README.html I infer two generations of protocol. The more recent one obviates the utility of the HELO attribute, since it mandates the proxy always sending a HELO/EHLO smtp command following (a successful) XCLIENT command, and that will carry a NELO name (which we assume, though it isn't specified, will be the actual one presented to the proxy by the possibly-new client). The same applies to the PROTO attribute. */ # define XCLIENT_V2 enum xclient_cmd_e { XCLIENT_CMD_UNKNOWN, XCLIENT_CMD_ADDR, XCLIENT_CMD_NAME, XCLIENT_CMD_PORT, XCLIENT_CMD_LOGIN, XCLIENT_CMD_DESTADDR, XCLIENT_CMD_DESTPORT, # ifdef XCLIENT_V1 XCLIENT_CMD_HELO, XCLIENT_CMD_PROTO, # endif }; struct xclient_cmd { const uschar * str; unsigned len; } xclient_cmds[] = { [XCLIENT_CMD_UNKNOWN] = { NULL }, [XCLIENT_CMD_ADDR] = { US"ADDR", 4 }, [XCLIENT_CMD_NAME] = { US"NAME", 4 }, [XCLIENT_CMD_PORT] = { US"PORT", 4 }, [XCLIENT_CMD_LOGIN] = { US"LOGIN", 5 }, [XCLIENT_CMD_DESTADDR] = { US"DESTADDR", 8 }, [XCLIENT_CMD_DESTPORT] = { US"DESTPORT", 8 }, # ifdef XCLIENT_V1 [XCLIENT_CMD_HELO] = { US"HELO", 4 }, [XCLIENT_CMD_PROTO] = { US"PROTO", 5 }, # endif }; /************************************************* * XCLIENT proxy implementation * *************************************************/ /* Arguments: code points to the coded string end points to the end of coded string ptr where to put the pointer to the result, which is in dynamic store Returns: the number of bytes in the result, excluding the final zero; -1 if the input is malformed */ static int xclient_xtextdecode(uschar * code, uschar * end, uschar ** ptr) { return auth_xtextdecode(string_copyn(code, end-code), ptr); } /************************************************* * Check XCLIENT line and set sender_address * *************************************************/ /* Check the format of a XCLIENT line. Arguments: s the data portion of the line (already past any white space) resp result: smtp respose code flag input: helo seen output: fail is fatal Return: NULL on success, or error message */ # define XCLIENT_UNAVAIL US"[UNAVAILABLE]" # define XCLIENT_TEMPUNAVAIL US"[TEMPUNAVAIL]" uschar * xclient_smtp_command(uschar * s, int * resp, BOOL * flag) { uschar * word = s; enum { XCLIENT_READ_COMMAND = 0, XCLIENT_READ_VALUE, XCLIENT_SKIP_SPACES } state = XCLIENT_SKIP_SPACES; enum xclient_cmd_e cmd; if ( !flag && verify_check_host(&hosts_require_helo) == OK) { *resp = 503; *flag = FALSE; return US"no HELO/EHLO given"; } /* If already in a proxy session, do not re-check permission. Strictly we should avoid doing this for a Proxy-Protocol session to avoid mixups. */ if(!proxy_session && verify_check_host(&hosts_xclient) == FAIL) { *resp = 550; *flag = TRUE; return US"XCLIENT command used when not advertised"; } if (sender_address) { *resp = 503; *flag = FALSE; return US"mail transaction in progress"; } if (!*word) { s = US"XCLIENT must have at least one operand"; goto fatal_501; } for (state = XCLIENT_SKIP_SPACES; *s; ) switch (state) { case XCLIENT_READ_COMMAND: { int len; word = s; while (*s && *s != '=') s++; len = s - word; if (!*s) { s = string_sprintf("XCLIENT: missing value for parameter '%.*s'", len, word); goto fatal_501; } DEBUG(D_transport) debug_printf(" XCLIENT: cmd %.*s\n", len, word); cmd = XCLIENT_CMD_UNKNOWN; for (struct xclient_cmd * x = xclient_cmds + 1; x < xclient_cmds + nelem(xclient_cmds); x++) if (len == x->len && strncmpic(word, x->str, len) == 0) { cmd = x - xclient_cmds; break; } if (cmd == XCLIENT_CMD_UNKNOWN) { s = string_sprintf("XCLIENT: unrecognised parameter '%.*s'", len, word); goto fatal_501; } state = XCLIENT_READ_VALUE; } break; case XCLIENT_READ_VALUE: { int old_pool = store_pool; int len; uschar * val; word = ++s; /* skip the = */ Uskip_nonwhite(&s); len = s - word; DEBUG(D_transport) debug_printf(" XCLIENT: \tvalue %.*s\n", len, word); if (len == 0) { s = US"XCLIENT: zero-length value for param"; goto fatal_501; } if ( len == 13 && ( strncmpic(word, XCLIENT_UNAVAIL, 13) == 0 || strncmpic(word, XCLIENT_TEMPUNAVAIL, 13) == 0 ) ) val = NULL; else if ((len = xclient_xtextdecode(word, s, &val)) == -1) { s = string_sprintf("failed xtext decode for XCLIENT: '%.*s'", len, word); goto fatal_501; } store_pool = POOL_PERM; switch (cmd) { case XCLIENT_CMD_ADDR: proxy_local_address = sender_host_address; sender_host_address = val ? string_copyn(val, len) : NULL; break; case XCLIENT_CMD_NAME: sender_host_name = val ? string_copyn(val, len) : NULL; break; case XCLIENT_CMD_PORT: proxy_local_port = sender_host_port; sender_host_port = val ? Uatoi(val) : 0; break; case XCLIENT_CMD_DESTADDR: proxy_external_address = val ? string_copyn(val, len) : NULL; break; case XCLIENT_CMD_DESTPORT: proxy_external_port = val ? Uatoi(val) : 0; break; case XCLIENT_CMD_LOGIN: if (val) { authenticated_id = string_copyn(val, len); sender_host_authenticated = US"xclient"; authentication_failed = FALSE; } else { authenticated_id = NULL; sender_host_authenticated = NULL; } break; # ifdef XCLIENT_V1 case XCLIENT_CMD_HELO: sender_helo_name = val ? string_copyn(val, len) : NULL; break; case XCLIENT_CMD_PROTO: if (!val) { store_pool = old_pool; s = US"missing proto for XCLIENT"; goto fatal_501; } else if (len == 4 && strncmpic(val, US"SMTP", 4) == 0) *esmtpflag = FALSE; /* function arg */ else if (len == 5 && strncmpic(val, US"ESMTP", 5) == 0) *esmtpflag = TRUE; else { store_pool = old_pool; s = US"bad proto for XCLIENT"; goto fatal_501; } break; # endif } store_pool = old_pool; state = XCLIENT_SKIP_SPACES; break; } case XCLIENT_SKIP_SPACES: Uskip_whitespace(&s); state = XCLIENT_READ_COMMAND; break; default: s = US"unhandled XCLIENT parameter type"; goto fatal_501; } if (!proxy_local_address) { s = US"missing ADDR for XCLIENT"; goto fatal_501; } if (!proxy_local_port) { s = US"missing PORT for XCLIENT"; goto fatal_501; } if (state != XCLIENT_SKIP_SPACES) { s = US"bad state parsing XCLIENT parameters"; goto fatal_501; } host_build_sender_fullhost(); proxy_session = TRUE; *resp = 220; return NULL; fatal_501: *flag = TRUE; *resp = 501; return s; } # undef XCLIENT_UNAVAIL # undef XCLIENT_TEMPUNAVAIL gstring * xclient_smtp_advertise_str(gstring * g) { g = string_catn(g, US"-XCLIENT ", 8); for (int i = 1; i < nelem(xclient_cmds); i++) { g = string_catn(g, US" ", 1); g = string_cat(g, xclient_cmds[i].str); } return string_catn(g, US"\r\n", 2); } #endif /*EXPERIMENTAL_XCLIENT*/ /* vi: aw ai sw=2 */ /* End of xclient.c */