Coverage Report

Created: 2026-05-30 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-smtp/smtp-server-cmd-xclient.c
Line
Count
Source
1
/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
#include "istream.h"
6
#include "connection.h"
7
#include "smtp-syntax.h"
8
#include "smtp-reply.h"
9
10
#include "smtp-server-private.h"
11
12
/* XCLIENT command (http://www.postfix.org/XCLIENT_README.html) */
13
14
static bool
15
cmd_xclient_check_state(struct smtp_server_cmd_ctx *cmd)
16
0
{
17
0
  struct smtp_server_connection *conn = cmd->conn;
18
19
  /* http://www.postfix.org/XCLIENT_README.html:
20
21
     The XCLIENT command may be sent at any time, except in the middle
22
     of a mail delivery transaction (i.e. between MAIL and DOT, or MAIL
23
     and RSET). */
24
0
  if (conn->state.trans != NULL) {
25
0
    smtp_server_reply(cmd, 503, "5.5.0",
26
0
      "XCLIENT not permitted during a mail transaction");
27
0
    return FALSE;
28
0
  }
29
0
  return TRUE;
30
0
}
31
32
static void
33
cmd_xclient_completed(struct smtp_server_cmd_ctx *cmd,
34
          struct smtp_proxy_data *proxy_data)
35
0
{
36
0
  struct smtp_server_connection *conn = cmd->conn;
37
0
  struct smtp_server_command *command = cmd->cmd;
38
39
0
  i_assert(smtp_server_command_is_replied(command));
40
0
  if (!smtp_server_command_replied_success(command)) {
41
    /* failure */
42
0
    return;
43
0
  }
44
45
  /* success */
46
0
  smtp_server_connection_reset_state(conn);
47
0
  smtp_server_connection_set_proxy_data(conn, proxy_data);
48
0
}
49
50
static void
51
cmd_xclient_recheck(struct smtp_server_cmd_ctx *cmd,
52
        struct smtp_proxy_data *proxy_data ATTR_UNUSED)
53
0
{
54
0
  struct smtp_server_connection *conn = cmd->conn;
55
56
  /* all preceding commands have finished and now the transaction state is
57
     clear. This provides the opportunity to re-check the protocol state */
58
0
  if (!cmd_xclient_check_state(cmd))
59
0
    return;
60
0
  smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_XCLIENT, NULL);
61
62
  /* success; send greeting */
63
0
  smtp_server_reply(cmd, 220, NULL, "%s %s",
64
0
    conn->set.hostname, conn->set.login_greeting);
65
0
  return;
66
0
}
67
68
static void
69
smtp_server_cmd_xclient_extra_field(struct smtp_server_connection *conn,
70
            pool_t pool, const struct smtp_param *param,
71
            ARRAY_TYPE(smtp_proxy_data_field) *fields)
72
0
{
73
0
  struct smtp_proxy_data_field *field;
74
75
0
  if (conn->set.xclient_extensions == NULL ||
76
0
      !str_array_icase_find(conn->set.xclient_extensions, param->keyword))
77
0
    return;
78
79
0
  if (!array_is_created(fields))
80
0
    p_array_init(fields, pool, 8);
81
0
  field = array_append_space(fields);
82
0
  field->name = p_strdup(pool, param->keyword);
83
0
  field->value = p_strdup(pool, param->value);
84
0
}
85
86
void smtp_server_cmd_xclient(struct smtp_server_cmd_ctx *cmd,
87
           const char *params)
88
198
{
89
198
  struct smtp_server_connection *conn = cmd->conn;
90
198
  struct smtp_server_command *command = cmd->cmd;
91
198
  const struct smtp_server_callbacks *callbacks = conn->callbacks;
92
198
  struct smtp_proxy_data *proxy_data;
93
198
  ARRAY_TYPE(smtp_proxy_data_field) extra_fields = ARRAY_INIT;
94
198
  const char *const *argv;
95
96
  /* xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value )
97
     attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN )
98
     attribute-value = xtext
99
   */
100
101
198
  if ((conn->set.capabilities & SMTP_CAPABILITY_XCLIENT) == 0) {
102
198
    smtp_server_reply(cmd,
103
198
      502, "5.5.1", "Unsupported command");
104
198
    return;
105
198
  }
106
107
  /* check transaction state as far as possible */
108
0
  if (!cmd_xclient_check_state(cmd))
109
0
    return;
110
111
  /* check whether client is trusted */
112
0
  if (!smtp_server_connection_is_trusted(conn)) {
113
0
    smtp_server_reply(cmd, 550, "5.7.14",
114
0
      "You are not from trusted IP");
115
0
    return;
116
0
  }
117
118
0
  proxy_data = p_new(cmd->pool, struct smtp_proxy_data, 1);
119
120
0
  argv = t_strsplit(params, " ");
121
0
  for (; *argv != NULL; argv++) {
122
0
    struct smtp_param param;
123
0
    const char *error;
124
125
0
    if (smtp_param_parse(pool_datastack_create(), *argv,
126
0
      &param, &error) < 0) {
127
0
      smtp_server_reply(cmd, 501, "5.5.4",
128
0
        "Invalid parameter: %s", error);
129
0
      return;
130
0
    }
131
132
0
    param.keyword = t_str_ucase(param.keyword);
133
134
0
    if (smtp_xtext_parse(param.value, &param.value, &error) < 0) {
135
0
      smtp_server_reply(cmd, 501, "5.5.4",
136
0
        "Invalid %s parameter: %s",
137
0
        param.keyword, error);
138
0
      return;
139
0
    }
140
141
0
    if (strcmp(param.keyword, "ADDR") == 0) {
142
0
      bool ipv6 = FALSE;
143
0
      if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
144
0
        continue;
145
0
      if (str_begins_icase(param.value, "IPV6:", &param.value))
146
0
        ipv6 = TRUE;
147
0
      if (net_addr2ip(param.value, &proxy_data->source_ip) < 0 ||
148
0
        (ipv6 && proxy_data->source_ip.family != AF_INET6)) {
149
0
        smtp_server_reply(cmd, 501, "5.5.4",
150
0
          "Invalid ADDR parameter");
151
0
        return;
152
0
      }
153
0
    } else if (strcmp(param.keyword, "DESTADDR") == 0) {
154
0
      bool ipv6 = FALSE;
155
0
      if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
156
0
        continue;
157
0
      if (str_begins_icase(param.value, "IPV6:", &param.value))
158
0
        ipv6 = TRUE;
159
0
      if (net_addr2ip(param.value, &proxy_data->dest_ip) < 0 ||
160
0
        (ipv6 && proxy_data->dest_ip.family != AF_INET6)) {
161
0
        smtp_server_reply(cmd, 501, "5.5.4",
162
0
          "Invalid DESTADDR parameter");
163
0
        return;
164
0
      }
165
0
    } else if (strcmp(param.keyword, "HELO") == 0) {
166
0
      if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
167
0
        continue;
168
0
      if (smtp_helo_domain_parse
169
0
        (param.value, TRUE, &proxy_data->helo) >= 0)
170
0
        proxy_data->helo =
171
0
          p_strdup(cmd->pool, proxy_data->helo);
172
0
    } else if (strcmp(param.keyword, "LOGIN") == 0) {
173
0
      if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
174
0
        continue;
175
0
      proxy_data->login = p_strdup(cmd->pool, param.value);
176
0
    } else if (strcmp(param.keyword, "PORT") == 0) {
177
0
      if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
178
0
        continue;
179
0
      if (net_str2port(param.value, &proxy_data->source_port) < 0) {
180
0
        smtp_server_reply(cmd, 501, "5.5.4",
181
0
          "Invalid PORT parameter");
182
0
        return;
183
0
      }
184
0
    } else if (strcmp(param.keyword, "DESTPORT") == 0) {
185
0
      if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
186
0
        continue;
187
0
      if (net_str2port(param.value, &proxy_data->dest_port) < 0) {
188
0
        smtp_server_reply(cmd, 501, "5.5.4",
189
0
          "Invalid DESTPORT parameter");
190
0
        return;
191
0
      }
192
0
    } else if (strcmp(param.keyword, "PROTO") == 0) {
193
0
      param.value = t_str_ucase(param.value);
194
0
      if (strcmp(param.value, "SMTP") == 0)
195
0
        proxy_data->proto = SMTP_PROXY_PROTOCOL_SMTP;
196
0
      else if (strcmp(param.value, "ESMTP") == 0)
197
0
        proxy_data->proto = SMTP_PROXY_PROTOCOL_ESMTP;
198
0
      else if (strcmp(param.value, "LMTP") == 0)
199
0
        proxy_data->proto = SMTP_PROXY_PROTOCOL_LMTP;
200
0
      else {
201
0
        smtp_server_reply(cmd, 501, "5.5.4",
202
0
          "Invalid PROTO parameter");
203
0
        return;
204
0
      }
205
0
    } else if (strcmp(param.keyword, "SESSION") == 0) {
206
0
      if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
207
0
        continue;
208
0
      proxy_data->session = p_strdup(cmd->pool, param.value);
209
0
    } else if (strcmp(param.keyword, "CLIENT-TRANSPORT") == 0) {
210
0
      if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
211
0
        continue;
212
0
      proxy_data->client_transport =
213
0
        p_strdup(cmd->pool, param.value);
214
0
    } else if (strcmp(param.keyword, "DESTNAME") == 0) {
215
0
      if (!connection_is_valid_dns_name(param.value)) {
216
0
        smtp_server_reply(cmd, 501, "5.5.4",
217
0
              "Invalid DESTNAME parameter");
218
0
        return;
219
0
      }
220
0
      proxy_data->local_name = p_strdup(cmd->pool, param.value);
221
0
    } else if (strcmp(param.keyword, "TIMEOUT") == 0) {
222
0
      if (str_to_uint(param.value,
223
0
        &proxy_data->timeout_secs) < 0) {
224
0
        smtp_server_reply(cmd, 501, "5.5.4",
225
0
          "Invalid TIMEOUT parameter");
226
0
        return;
227
0
      }
228
0
    } else if (strcmp(param.keyword, "TTL") == 0) {
229
0
      if (str_to_uint(param.value,
230
0
        &proxy_data->ttl_plus_1) < 0) {
231
0
        smtp_server_reply(cmd, 501, "5.5.4",
232
0
          "Invalid TTL parameter");
233
0
        return;
234
0
      }
235
0
      proxy_data->ttl_plus_1++;
236
0
    } else {
237
0
      smtp_server_cmd_xclient_extra_field(conn,
238
0
        cmd->pool, &param, &extra_fields);
239
0
    }
240
0
  }
241
242
0
  if (array_is_created(&extra_fields)) {
243
0
    proxy_data->extra_fields = array_get(&extra_fields,
244
0
      &proxy_data->extra_fields_count);
245
0
  }
246
247
0
  smtp_server_command_input_lock(cmd);
248
249
0
  smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT,
250
0
             cmd_xclient_recheck, proxy_data);
251
0
  smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED,
252
0
             cmd_xclient_completed, proxy_data);
253
254
0
  if (conn->state.state == SMTP_SERVER_STATE_GREETING) {
255
0
    smtp_server_connection_set_state(
256
0
      conn, SMTP_SERVER_STATE_XCLIENT, NULL);
257
0
  }
258
259
0
  smtp_server_command_ref(command);
260
0
  if (callbacks != NULL && callbacks->conn_cmd_xclient != NULL) {
261
    /* specific implementation of XCLIENT command */
262
0
    callbacks->conn_cmd_xclient(conn->context, cmd, proxy_data);
263
0
  }
264
0
  smtp_server_command_unref(&command);
265
0
}