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 /* From https://www.postfix.org/XCLIENT_README.html I infer two generations of
14 protocol. The more recent one obviates the utility of the HELO attribute, since
15 it mandates the proxy always sending a HELO/EHLO smtp command following (a
16 successful) XCLIENT command, and that will carry a NELO name (which we assume,
17 though it isn't specified, will be the actual one presented to the proxy by the
18 possibly-new client). The same applies to the PROTO attribute. */
40 [XCLIENT_CMD_UNKNOWN] = { NULL },
41 [XCLIENT_CMD_ADDR] = { US"ADDR", 4 },
42 [XCLIENT_CMD_NAME] = { US"NAME", 4 },
43 [XCLIENT_CMD_PORT] = { US"PORT", 4 },
44 [XCLIENT_CMD_LOGIN] = { US"LOGIN", 5 },
45 [XCLIENT_CMD_DESTADDR] = { US"DESTADDR", 8 },
46 [XCLIENT_CMD_DESTPORT] = { US"DESTPORT", 8 },
48 [XCLIENT_CMD_HELO] = { US"HELO", 4 },
49 [XCLIENT_CMD_PROTO] = { US"PROTO", 5 },
53 /*************************************************
54 * XCLIENT proxy implementation *
55 *************************************************/
58 code points to the coded string
59 end points to the end of coded string
60 ptr where to put the pointer to the result, which is in
62 Returns: the number of bytes in the result, excluding the final zero;
63 -1 if the input is malformed
67 xclient_xtextdecode(uschar * code, uschar * end, uschar ** ptr)
69 return xtextdecode(string_copyn(code, end-code), ptr);
72 /*************************************************
73 * Check XCLIENT line and set sender_address *
74 *************************************************/
77 /* Check the format of a XCLIENT line.
79 s the data portion of the line (already past any white space)
80 resp result: smtp respose code
81 flag input: helo seen output: fail is fatal
83 Return: NULL on success, or error message
86 # define XCLIENT_UNAVAIL US"[UNAVAILABLE]"
87 # define XCLIENT_TEMPUNAVAIL US"[TEMPUNAVAIL]"
90 xclient_smtp_command(uschar * s, int * resp, BOOL * flag)
94 XCLIENT_READ_COMMAND = 0,
97 } state = XCLIENT_SKIP_SPACES;
98 enum xclient_cmd_e cmd;
101 && verify_check_host(&hosts_require_helo) == OK)
105 return US"no HELO/EHLO given";
108 /* If already in a proxy session, do not re-check permission.
109 Strictly we should avoid doing this for a Proxy-Protocol
110 session to avoid mixups. */
112 if(!proxy_session && verify_check_host(&hosts_xclient) == FAIL)
116 return US"XCLIENT command used when not advertised";
123 return US"mail transaction in progress";
128 s = US"XCLIENT must have at least one operand";
132 for (state = XCLIENT_SKIP_SPACES; *s; )
135 case XCLIENT_READ_COMMAND:
140 while (*s && *s != '=') s++;
144 s = string_sprintf("XCLIENT: missing value for parameter '%.*s'",
149 DEBUG(D_transport) debug_printf(" XCLIENT: cmd %.*s\n", len, word);
150 cmd = XCLIENT_CMD_UNKNOWN;
151 for (struct xclient_cmd * x = xclient_cmds + 1;
152 x < xclient_cmds + nelem(xclient_cmds); x++)
153 if (len == x->len && strncmpic(word, x->str, len) == 0)
155 cmd = x - xclient_cmds;
158 if (cmd == XCLIENT_CMD_UNKNOWN)
160 s = string_sprintf("XCLIENT: unrecognised parameter '%.*s'",
164 state = XCLIENT_READ_VALUE;
168 case XCLIENT_READ_VALUE:
170 int old_pool = store_pool;
174 word = ++s; /* skip the = */
178 DEBUG(D_transport) debug_printf(" XCLIENT: \tvalue %.*s\n", len, word);
180 { s = US"XCLIENT: zero-length value for param"; goto fatal_501; }
183 && ( strncmpic(word, XCLIENT_UNAVAIL, 13) == 0
184 || strncmpic(word, XCLIENT_TEMPUNAVAIL, 13) == 0
188 else if ((len = xclient_xtextdecode(word, s, &val)) == -1)
190 s = string_sprintf("failed xtext decode for XCLIENT: '%.*s'", len, word);
194 store_pool = POOL_PERM;
197 case XCLIENT_CMD_ADDR:
198 proxy_local_address = sender_host_address;
199 sender_host_address = val ? string_copyn(val, len) : NULL;
201 case XCLIENT_CMD_NAME:
202 sender_host_name = val ? string_copyn(val, len) : NULL;
204 case XCLIENT_CMD_PORT:
205 proxy_local_port = sender_host_port;
206 sender_host_port = val ? Uatoi(val) : 0;
208 case XCLIENT_CMD_DESTADDR:
209 proxy_external_address = val ? string_copyn(val, len) : NULL;
211 case XCLIENT_CMD_DESTPORT:
212 proxy_external_port = val ? Uatoi(val) : 0;
215 case XCLIENT_CMD_LOGIN:
218 authenticated_id = string_copyn(val, len);
219 sender_host_authenticated = US"xclient";
220 authentication_failed = FALSE;
224 authenticated_id = NULL;
225 sender_host_authenticated = NULL;
230 case XCLIENT_CMD_HELO:
231 sender_helo_name = val ? string_copyn(val, len) : NULL;
233 case XCLIENT_CMD_PROTO:
235 { store_pool = old_pool; s = US"missing proto for XCLIENT"; goto fatal_501; }
236 else if (len == 4 && strncmpic(val, US"SMTP", 4) == 0)
237 *esmtpflag = FALSE; /* function arg */
238 else if (len == 5 && strncmpic(val, US"ESMTP", 5) == 0)
241 { store_pool = old_pool; s = US"bad proto for XCLIENT"; goto fatal_501; }
245 store_pool = old_pool;
246 state = XCLIENT_SKIP_SPACES;
250 case XCLIENT_SKIP_SPACES:
251 Uskip_whitespace(&s);
252 state = XCLIENT_READ_COMMAND;
256 s = US"unhandled XCLIENT parameter type";
260 if (!proxy_local_address)
261 { s = US"missing ADDR for XCLIENT"; goto fatal_501; }
262 if (!proxy_local_port)
263 { s = US"missing PORT for XCLIENT"; goto fatal_501; }
264 if (state != XCLIENT_SKIP_SPACES)
265 { s = US"bad state parsing XCLIENT parameters"; goto fatal_501; }
267 host_build_sender_fullhost();
268 proxy_session = TRUE;
278 # undef XCLIENT_UNAVAIL
279 # undef XCLIENT_TEMPUNAVAIL
283 xclient_smtp_advertise_str(gstring * g)
285 g = string_catn(g, US"-XCLIENT ", 8);
286 for (int i = 1; i < nelem(xclient_cmds); i++)
288 g = string_catn(g, US" ", 1);
289 g = string_cat(g, xclient_cmds[i].str);
291 return string_catn(g, US"\r\n", 2);
295 #endif /*EXPERIMENTAL_XCLIENT*/
299 /* End of xclient.c */