1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2023 - 2024 */
6 /* See the file NOTICE for conditions of use and distribution. */
7 /* SPDX-License-Identifier: GPL-2.0-or-later */
11 #ifdef EXPERIMENTAL_XCLIENT
13 /* This is a proxy protocol.
15 From https://www.postfix.org/XCLIENT_README.html I infer two generations of
16 protocol. The more recent one obviates the utility of the HELO attribute, since
17 it mandates the proxy always sending a HELO/EHLO smtp command following (a
18 successful) XCLIENT command, and that will carry a NELO name (which we assume,
19 though it isn't specified, will be the actual one presented to the proxy by the
20 possibly-new client). The same applies to the PROTO attribute. */
42 [XCLIENT_CMD_UNKNOWN] = { NULL },
43 [XCLIENT_CMD_ADDR] = { US"ADDR", 4 },
44 [XCLIENT_CMD_NAME] = { US"NAME", 4 },
45 [XCLIENT_CMD_PORT] = { US"PORT", 4 },
46 [XCLIENT_CMD_LOGIN] = { US"LOGIN", 5 },
47 [XCLIENT_CMD_DESTADDR] = { US"DESTADDR", 8 },
48 [XCLIENT_CMD_DESTPORT] = { US"DESTPORT", 8 },
50 [XCLIENT_CMD_HELO] = { US"HELO", 4 },
51 [XCLIENT_CMD_PROTO] = { US"PROTO", 5 },
55 /*************************************************
56 * XCLIENT proxy implementation *
57 *************************************************/
60 code points to the coded string
61 end points to the end of coded string
62 ptr where to put the pointer to the result, which is in
64 Returns: the number of bytes in the result, excluding the final zero;
65 -1 if the input is malformed
69 xclient_xtextdecode(uschar * code, uschar * end, uschar ** ptr)
71 return xtextdecode(string_copyn(code, end-code), ptr);
74 /*************************************************
75 * Check XCLIENT line and set sender_address *
76 *************************************************/
79 /* Check the format of a XCLIENT line.
81 s the data portion of the line (already past any white space)
82 resp result: smtp respose code
83 flag input: helo seen output: fail is fatal
85 Return: NULL on success, or error message
88 # define XCLIENT_UNAVAIL US"[UNAVAILABLE]"
89 # define XCLIENT_TEMPUNAVAIL US"[TEMPUNAVAIL]"
92 xclient_smtp_command(uschar * s, int * resp, BOOL * flag)
96 XCLIENT_READ_COMMAND = 0,
99 } state = XCLIENT_SKIP_SPACES;
100 enum xclient_cmd_e cmd;
103 && verify_check_host(&hosts_require_helo) == OK)
107 return US"no HELO/EHLO given";
110 /* If already in a proxy session, do not re-check permission.
111 Strictly we should avoid doing this for a Proxy-Protocol
112 session to avoid mixups. */
114 if(!proxy_session && verify_check_host(&hosts_xclient) == FAIL)
118 return US"XCLIENT command used when not advertised";
125 return US"mail transaction in progress";
130 s = US"XCLIENT must have at least one operand";
134 for (state = XCLIENT_SKIP_SPACES; *s; )
137 case XCLIENT_READ_COMMAND:
142 while (*s && *s != '=') s++;
146 s = string_sprintf("XCLIENT: missing value for parameter '%.*s'",
151 DEBUG(D_transport) debug_printf(" XCLIENT: cmd %.*s\n", len, word);
152 cmd = XCLIENT_CMD_UNKNOWN;
153 for (struct xclient_cmd * x = xclient_cmds + 1;
154 x < xclient_cmds + nelem(xclient_cmds); x++)
155 if (len == x->len && strncmpic(word, x->str, len) == 0)
157 cmd = x - xclient_cmds;
160 if (cmd == XCLIENT_CMD_UNKNOWN)
162 s = string_sprintf("XCLIENT: unrecognised parameter '%.*s'",
166 state = XCLIENT_READ_VALUE;
170 case XCLIENT_READ_VALUE:
172 int old_pool = store_pool;
176 word = ++s; /* skip the = */
180 DEBUG(D_transport) debug_printf(" XCLIENT: \tvalue %.*s\n", len, word);
182 { s = US"XCLIENT: zero-length value for param"; goto fatal_501; }
185 && ( strncmpic(word, XCLIENT_UNAVAIL, 13) == 0
186 || strncmpic(word, XCLIENT_TEMPUNAVAIL, 13) == 0
190 else if ((len = xclient_xtextdecode(word, s, &val)) == -1)
192 s = string_sprintf("failed xtext decode for XCLIENT: '%.*s'", len, word);
196 store_pool = POOL_PERM;
199 case XCLIENT_CMD_ADDR:
200 proxy_local_address = sender_host_address;
201 sender_host_address = val ? string_copyn(val, len) : NULL;
203 case XCLIENT_CMD_NAME:
204 sender_host_name = val ? string_copyn(val, len) : NULL;
206 case XCLIENT_CMD_PORT:
207 proxy_local_port = sender_host_port;
208 sender_host_port = val ? Uatoi(val) : 0;
210 case XCLIENT_CMD_DESTADDR:
211 proxy_external_address = val ? string_copyn(val, len) : NULL;
213 case XCLIENT_CMD_DESTPORT:
214 proxy_external_port = val ? Uatoi(val) : 0;
217 case XCLIENT_CMD_LOGIN:
220 authenticated_id = string_copyn(val, len);
221 sender_host_authenticated = US"xclient";
222 authentication_failed = FALSE;
226 authenticated_id = NULL;
227 sender_host_authenticated = NULL;
232 case XCLIENT_CMD_HELO:
233 sender_helo_name = val ? string_copyn(val, len) : NULL;
235 case XCLIENT_CMD_PROTO:
237 { store_pool = old_pool; s = US"missing proto for XCLIENT"; goto fatal_501; }
238 else if (len == 4 && strncmpic(val, US"SMTP", 4) == 0)
239 *esmtpflag = FALSE; /* function arg */
240 else if (len == 5 && strncmpic(val, US"ESMTP", 5) == 0)
243 { store_pool = old_pool; s = US"bad proto for XCLIENT"; goto fatal_501; }
247 store_pool = old_pool;
248 state = XCLIENT_SKIP_SPACES;
252 case XCLIENT_SKIP_SPACES:
253 Uskip_whitespace(&s);
254 state = XCLIENT_READ_COMMAND;
258 s = US"unhandled XCLIENT parameter type";
262 if (!proxy_local_address)
263 { s = US"missing ADDR for XCLIENT"; goto fatal_501; }
264 if (!proxy_local_port)
265 { s = US"missing PORT for XCLIENT"; goto fatal_501; }
266 if (state != XCLIENT_SKIP_SPACES)
267 { s = US"bad state parsing XCLIENT parameters"; goto fatal_501; }
269 host_build_sender_fullhost();
270 proxy_session = TRUE;
280 # undef XCLIENT_UNAVAIL
281 # undef XCLIENT_TEMPUNAVAIL
285 xclient_smtp_advertise_str(gstring * g)
287 g = string_catn(g, US"-XCLIENT ", 8);
288 for (int i = 1; i < nelem(xclient_cmds); i++)
290 g = string_catn(g, US" ", 1);
291 g = string_cat(g, xclient_cmds[i].str);
293 return string_catn(g, US"\r\n", 2);
297 #endif /*EXPERIMENTAL_XCLIENT*/
301 /* End of xclient.c */