1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "str.h"
5 #include "array.h"
6 #include "smtp-parser.h"
7
8 #include "smtp-syntax.h"
9
10 #include <ctype.h>
11
12 /*
13 * String
14 */
15
smtp_string_parse(const char * string,const char ** value_r,const char ** error_r)16 int smtp_string_parse(const char *string, const char **value_r,
17 const char **error_r)
18 {
19 struct smtp_parser parser;
20
21 *value_r = NULL;
22 *error_r = NULL;
23
24 if (string == NULL || *string == '\0') {
25 *value_r = "";
26 return 0;
27 }
28
29 smtp_parser_init(&parser, pool_datastack_create(), string);
30
31 if (smtp_parser_parse_string(&parser, value_r) < 0) {
32 *error_r = parser.error;
33 return -1;
34 }
35 if (parser.cur < parser.end) {
36 *error_r = "Invalid character in string";
37 return -1;
38 }
39 return 1;
40 }
41
smtp_string_write(string_t * out,const char * value)42 void smtp_string_write(string_t *out, const char *value)
43 {
44 bool quoted = FALSE;
45 const unsigned char *p, *pend, *pblock;
46 size_t begin = str_len(out);
47
48 if (value == NULL)
49 return;
50 p = (const unsigned char *)value;
51 pend = p + strlen(value);
52 while (p < pend) {
53 pblock = p;
54 while (p < pend && smtp_char_is_atext(*p))
55 p++;
56
57 if (!quoted && p < pend) {
58 quoted = TRUE;
59 str_insert(out, begin, "\"");
60 }
61
62 str_append_data(out, pblock, p-pblock);
63 if (p >= pend)
64 break;
65
66 i_assert(quoted);
67 i_assert(smtp_char_is_qpair(*p));
68
69 if (!smtp_char_is_qtext(*p))
70 str_append_c(out, '\\');
71 str_append_c(out, *p);
72
73 p++;
74 }
75
76 if (quoted)
77 str_append_c(out, '\"');
78 }
79
80 /*
81 * Xtext encoding
82 */
83
smtp_xtext_decode(string_t * out,const char * xtext,bool allow_nul,const char ** error_r)84 int smtp_xtext_decode(string_t *out, const char *xtext, bool allow_nul,
85 const char **error_r)
86 {
87 struct smtp_parser parser;
88
89 if (xtext == NULL || *xtext == '\0')
90 return 1;
91
92 smtp_parser_init(&parser, pool_datastack_create(), xtext);
93
94 if (smtp_parser_parse_xtext(&parser, out) < 0) {
95 *error_r = parser.error;
96 return -1;
97 }
98 if (parser.cur < parser.end) {
99 *error_r = "Invalid character in xtext";
100 return -1;
101 }
102 if (!allow_nul && strlen(str_c(out)) != str_len(out)) {
103 *error_r = "Encountered NUL character in xtext";
104 return -1;
105 }
106 return 1;
107 }
108
smtp_xtext_parse(const char * xtext,const char ** value_r,const char ** error_r)109 int smtp_xtext_parse(const char *xtext, const char **value_r,
110 const char **error_r)
111 {
112 string_t *value;
113 int ret;
114
115 *value_r = NULL;
116 *error_r = NULL;
117
118 if (xtext == NULL || *xtext == '\0') {
119 *value_r = "";
120 return 1;
121 }
122
123 value = t_str_new(256);
124 ret = smtp_xtext_decode(value, xtext, FALSE, error_r);
125 if (ret <= 0)
126 return ret;
127
128 *value_r = str_c(value);
129 return 1;
130 }
131
smtp_xtext_encode(string_t * out,const unsigned char * data,size_t size)132 void smtp_xtext_encode(string_t *out, const unsigned char *data, size_t size)
133 {
134 const unsigned char *p, *pbegin, *pend;
135
136 p = data;
137 pend = p + size;
138 while (p < pend) {
139 pbegin = p;
140 while (p < pend && smtp_char_is_xtext(*p))
141 p++;
142
143 str_append_data(out, pbegin, p-pbegin);
144 if (p >= pend)
145 break;
146
147 str_printfa(out, "+%02X", (unsigned int)*p);
148 p++;
149 }
150 }
151
152 /*
153 * HELO domain
154 */
155
smtp_helo_domain_parse(const char * helo,bool allow_literal,const char ** domain_r)156 int smtp_helo_domain_parse(const char *helo, bool allow_literal,
157 const char **domain_r)
158 {
159 struct smtp_parser parser;
160 int ret;
161
162 smtp_parser_init(&parser, pool_datastack_create(), helo);
163
164 ret = smtp_parser_parse_domain(&parser, domain_r);
165 if (ret == 0) {
166 if (allow_literal) {
167 ret = smtp_parser_parse_address_literal(
168 &parser, domain_r, NULL);
169 }
170 }
171
172 if (ret <= 0 || (parser.cur < parser.end && *parser.cur != ' '))
173 return -1;
174 return 0;
175 }
176
177 /*
178 * EHLO reply
179 */
180
smtp_ehlo_keyword_is_valid(const char * keyword)181 bool smtp_ehlo_keyword_is_valid(const char *keyword)
182 {
183 const char *p;
184
185 for (p = keyword; *p != '\0'; p++) {
186 if (!i_isalnum(*p))
187 return FALSE;
188 }
189 return TRUE;
190 }
191
smtp_ehlo_param_is_valid(const char * param)192 bool smtp_ehlo_param_is_valid(const char *param)
193 {
194 const char *p;
195
196 for (p = param; *p != '\0'; p++) {
197 if (!smtp_char_is_ehlo_param(*p))
198 return FALSE;
199 }
200 return TRUE;
201 }
202
smtp_ehlo_params_are_valid(const char * const * params)203 bool smtp_ehlo_params_are_valid(const char *const *params)
204 {
205 if (params == NULL)
206 return TRUE;
207
208 while (*params != NULL) {
209 if (!smtp_ehlo_param_is_valid(*params))
210 return FALSE;
211 params++;
212 }
213
214 return TRUE;
215 }
216
smtp_ehlo_params_str_is_valid(const char * params)217 bool smtp_ehlo_params_str_is_valid(const char *params)
218 {
219 const char *p;
220 bool space = FALSE;
221
222 for (p = params; *p != '\0'; p++) {
223 if (*p == ' ') {
224 if (space)
225 return FALSE;
226 space = TRUE;
227 continue;
228 }
229 space = FALSE;
230
231 if (!smtp_char_is_ehlo_param(*p))
232 return FALSE;
233 }
234 return TRUE;
235 }
236
237 static int
smtp_parse_ehlo_line(struct smtp_parser * parser,const char ** key_r,const char * const ** params_r)238 smtp_parse_ehlo_line(struct smtp_parser *parser, const char **key_r,
239 const char *const **params_r)
240 {
241 const unsigned char *pbegin = parser->cur;
242 ARRAY_TYPE(const_string) params = ARRAY_INIT;
243 const char *param;
244
245 /* ehlo-line = ehlo-keyword *( SP ehlo-param )
246 ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-")
247 ; additional syntax of ehlo-params depends on
248 ; ehlo-keyword
249 ehlo-param = 1*(%d33-126)
250 ; any CHAR excluding <SP> and all
251 ; control characters (US-ASCII 0-31 and 127
252 ; inclusive)
253 */
254
255 if (parser->cur >= parser->end || !i_isalnum(*parser->cur)) {
256 parser->error = "Unexpected character in EHLO keyword";
257 return -1;
258 }
259 parser->cur++;
260
261 while (parser->cur < parser->end &&
262 (i_isalnum(*parser->cur) || *parser->cur == '-'))
263 parser->cur++;
264
265 *key_r = p_strdup_until(parser->pool, pbegin, parser->cur);
266
267 if (parser->cur >= parser->end) {
268 *params_r = p_new(parser->pool, const char *, 1);
269 return 1;
270 }
271 if (*parser->cur != ' ') {
272 parser->error = "Unexpected character in EHLO keyword";
273 return -1;
274 }
275 parser->cur++;
276
277 pbegin = parser->cur;
278 p_array_init(¶ms, parser->pool, 32);
279 while (parser->cur < parser->end) {
280 if (*parser->cur == ' ') {
281 if (parser->cur+1 >= parser->end ||
282 *(parser->cur+1) == ' ') {
283 parser->error =
284 "Missing EHLO parameter after ' '";
285 return -1;
286 }
287 param = p_strdup_until(parser->pool, pbegin,
288 parser->cur);
289 array_push_back(¶ms, ¶m);
290 pbegin = parser->cur + 1;
291 } else if (!smtp_char_is_ehlo_param(*parser->cur)) {
292 parser->error =
293 "Unexpected character in EHLO parameter";
294 return -1;
295 }
296 parser->cur++;
297 }
298
299 param = p_strdup_until(parser->pool, pbegin, parser->cur);
300 array_push_back(¶ms, ¶m);
301 array_append_zero(¶ms);
302 *params_r = array_front(¶ms);
303 return 1;
304 }
305
smtp_ehlo_line_parse(const char * ehlo_line,const char ** key_r,const char * const ** params_r,const char ** error_r)306 int smtp_ehlo_line_parse(const char *ehlo_line, const char **key_r,
307 const char *const **params_r, const char **error_r)
308 {
309 struct smtp_parser parser;
310
311 *key_r = NULL;
312 *params_r = NULL;
313 *error_r = NULL;
314
315 if (ehlo_line == NULL || *ehlo_line == '\0') {
316 *error_r = "Parameter is empty";
317 return -1;
318 }
319
320 smtp_parser_init(&parser, pool_datastack_create(), ehlo_line);
321
322 if (smtp_parse_ehlo_line(&parser, key_r, params_r) <= 0) {
323 *error_r = parser.error;
324 return -1;
325 }
326 return 1;
327 }
328