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