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(&params, 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(&params, &param);
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(&params, &param);
301 	array_append_zero(&params);
302 	*params_r = array_front(&params);
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