Experimental_XCLIENT. Bug 2702
[exim.git] / src / src / xclient.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2023 */
6 /* See the file NOTICE for conditions of use and distribution. */
7 /* SPDX-License-Identifier: GPL-2.0-or-later */
8
9 #include "exim.h"
10
11 #ifdef EXPERIMENTAL_XCLIENT
12
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. */
19
20 # define XCLIENT_V2
21
22 enum xclient_cmd_e {
23   XCLIENT_CMD_UNKNOWN,
24   XCLIENT_CMD_ADDR,
25   XCLIENT_CMD_NAME,
26   XCLIENT_CMD_PORT,
27   XCLIENT_CMD_LOGIN,
28   XCLIENT_CMD_DESTADDR,
29   XCLIENT_CMD_DESTPORT,
30 # ifdef XCLIENT_V1
31   XCLIENT_CMD_HELO,
32   XCLIENT_CMD_PROTO,
33 # endif
34 };
35
36 struct xclient_cmd {
37   const uschar *        str;
38   unsigned              len;
39 } xclient_cmds[] = {
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 },
47 # ifdef XCLIENT_V1
48   [XCLIENT_CMD_HELO] =  { US"HELO",  4 },
49   [XCLIENT_CMD_PROTO] = { US"PROTO", 5 },
50 # endif
51 };
52
53 /*************************************************
54 *          XCLIENT proxy implementation          *
55 *************************************************/
56
57 /* Arguments:
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
61               dynamic store
62 Returns:      the number of bytes in the result, excluding the final zero;
63               -1 if the input is malformed
64 */
65
66 static int
67 xclient_xtextdecode(uschar * code, uschar * end, uschar ** ptr)
68 {
69 return auth_xtextdecode(string_copyn(code, end-code), ptr);
70 }
71
72 /*************************************************
73 *   Check XCLIENT line and set sender_address    *
74 *************************************************/
75
76
77 /* Check the format of a XCLIENT line.
78 Arguments:
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
82
83 Return: NULL on success, or error message
84 */
85
86 # define XCLIENT_UNAVAIL     US"[UNAVAILABLE]"
87 # define XCLIENT_TEMPUNAVAIL US"[TEMPUNAVAIL]"
88
89 uschar *
90 xclient_smtp_command(uschar * s, int * resp, BOOL * flag)
91 {
92 uschar * word = s;
93 enum {
94   XCLIENT_READ_COMMAND = 0,
95   XCLIENT_READ_VALUE,
96   XCLIENT_SKIP_SPACES
97 } state = XCLIENT_SKIP_SPACES;
98 enum xclient_cmd_e cmd;
99
100 if (  !flag
101    && verify_check_host(&hosts_require_helo) == OK)
102   {
103   *resp = 503;
104   *flag = FALSE;
105   return US"no HELO/EHLO given";
106   }
107
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. */
111
112 if(!proxy_session && verify_check_host(&hosts_xclient) == FAIL)
113   {
114   *resp = 550;
115   *flag = TRUE;
116   return US"XCLIENT command used when not advertised";
117   }
118
119 if (sender_address)
120   {
121   *resp = 503;
122   *flag = FALSE;
123   return US"mail transaction in progress";
124   }
125
126 if (!*word)
127   {
128   s = US"XCLIENT must have at least one operand";
129   goto fatal_501;
130   }
131
132 for (state = XCLIENT_SKIP_SPACES; *s; )
133   switch (state)
134     {
135     case XCLIENT_READ_COMMAND:
136       {
137       int len;
138
139       word = s;
140       while (*s && *s != '=') s++;
141       len = s - word;
142       if (!*s)
143         {
144         s = string_sprintf("XCLIENT: missing value for parameter '%.*s'",
145                           len, word);
146         goto fatal_501;
147         }
148
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)
154           {
155           cmd = x - xclient_cmds;
156           break;
157           }
158       if (cmd == XCLIENT_CMD_UNKNOWN)
159         {
160         s = string_sprintf("XCLIENT: unrecognised parameter '%.*s'",
161                           len, word);
162         goto fatal_501;
163         }
164       state = XCLIENT_READ_VALUE;
165       }
166       break;
167
168     case XCLIENT_READ_VALUE:
169       {
170       int old_pool = store_pool;
171       int len;
172       uschar * val;
173
174       word = ++s;                       /* skip the = */
175       while (*s && !isspace(*s)) s++;
176       len = s - word;
177
178       DEBUG(D_transport) debug_printf(" XCLIENT: \tvalue %.*s\n", len, word);
179       if (len == 0)
180         { s = US"XCLIENT: zero-length value for param"; goto fatal_501; }
181
182       if (  len == 13
183          && (  strncmpic(word, XCLIENT_UNAVAIL, 13) == 0
184             || strncmpic(word, XCLIENT_TEMPUNAVAIL, 13) == 0
185          )  )
186         val = NULL;
187
188       else if ((len = xclient_xtextdecode(word, s, &val)) == -1)
189         {
190         s = string_sprintf("failed xtext decode for XCLIENT: '%.*s'", len, word);
191         goto fatal_501;
192         }
193
194       store_pool = POOL_PERM;
195       switch (cmd)
196         {
197         case XCLIENT_CMD_ADDR:
198           proxy_local_address = sender_host_address;
199           sender_host_address = val ? string_copyn(val, len) : NULL;
200           break;
201         case XCLIENT_CMD_NAME:
202           sender_host_name = val ? string_copyn(val, len) : NULL;
203           break;
204         case XCLIENT_CMD_PORT:
205           proxy_local_port = sender_host_port;
206           sender_host_port = val ? Uatoi(val) : 0;
207           break;
208         case XCLIENT_CMD_DESTADDR:
209           proxy_external_address = val ? string_copyn(val, len) : NULL;
210           break;
211         case XCLIENT_CMD_DESTPORT:
212           proxy_external_port = val ? Uatoi(val) : 0;
213           break;
214
215         case XCLIENT_CMD_LOGIN:
216           if (val)
217             {
218             authenticated_id = string_copyn(val, len);
219             sender_host_authenticated = US"xclient";
220             authentication_failed = FALSE;
221             }
222           else
223             {
224             authenticated_id = NULL;
225             sender_host_authenticated = NULL;
226             }
227           break;
228
229 # ifdef XCLIENT_V1
230         case XCLIENT_CMD_HELO:
231           sender_helo_name = val ? string_copyn(val, len) : NULL;
232           break;
233         case XCLIENT_CMD_PROTO:
234           if (!val)
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)
239             *esmtpflag = TRUE;
240           else
241             { store_pool = old_pool; s = US"bad proto for XCLIENT"; goto fatal_501; }
242           break;
243 # endif
244         }
245       store_pool = old_pool;
246       state = XCLIENT_SKIP_SPACES;
247       break;
248       }
249
250     case XCLIENT_SKIP_SPACES:
251       while (*s && isspace (*s)) s++;
252       state = XCLIENT_READ_COMMAND;
253       break;
254
255     default:
256       s = US"unhandled XCLIENT parameter type";
257       goto fatal_501;
258     }
259
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; }
266
267 host_build_sender_fullhost();
268 proxy_session = TRUE;
269 *resp = 220;
270 return NULL;
271
272 fatal_501:
273   *flag = TRUE;
274   *resp = 501;
275   return s;
276 }
277
278 # undef XCLIENT_UNAVAIL
279 # undef XCLIENT_TEMPUNAVAIL
280
281
282 gstring *
283 xclient_smtp_advertise_str(gstring * g)
284 {
285 g = string_catn(g, US"-XCLIENT ", 8);
286 for (int i = 1; i < nelem(xclient_cmds); i++)
287   {
288   g = string_catn(g, US" ", 1);
289   g = string_cat(g, xclient_cmds[i].str);
290   }
291 return string_catn(g, US"\r\n", 2);
292 }
293
294
295 #endif  /*EXPERIMENTAL_XCLIENT*/
296
297 /* vi: aw ai sw=2
298 */
299 /* End of xclient.c */