1 /*++
2 /* NAME
3 /*	smtpd_proxy 3
4 /* SUMMARY
5 /*	SMTP server pass-through proxy client
6 /* SYNOPSIS
7 /*	#include <smtpd.h>
8 /*	#include <smtpd_proxy.h>
9 /*
10 /*	typedef struct {
11 /* .in +4
12 /*		VSTREAM *stream;	/* SMTP proxy or replay log */
13 /*		VSTRING *buffer;	/* last SMTP proxy response */
14 /*		/* other fields... */
15 /* .in -4
16 /*	} SMTPD_PROXY;
17 /*
18 /*	int	smtpd_proxy_create(state, flags, service, timeout,
19 /*					ehlo_name, mail_from)
20 /*	SMTPD_STATE *state;
21 /*	int	flags;
22 /*	const char *service;
23 /*	int	timeout;
24 /*	const char *ehlo_name;
25 /*	const char *mail_from;
26 /*
27 /*	int	proxy->cmd(state, expect, format, ...)
28 /*	SMTPD_PROXY *proxy;
29 /*	SMTPD_STATE *state;
30 /*	int	expect;
31 /*	const char *format;
32 /*
33 /*	void	smtpd_proxy_free(state)
34 /*	SMTPD_STATE *state;
35 /*
36 /*	int	smtpd_proxy_parse_opts(param_name, param_val)
37 /*	const char *param_name;
38 /*	const char *param_val;
39 /* RECORD-LEVEL ROUTINES
40 /*	int	proxy->rec_put(proxy->stream, rec_type, data, len)
41 /*	SMTPD_PROXY *proxy;
42 /*	int	rec_type;
43 /*	const char *data;
44 /*	ssize_t	len;
45 /*
46 /*	int	proxy->rec_fprintf(proxy->stream, rec_type, format, ...)
47 /*	SMTPD_PROXY *proxy;
48 /*	int	rec_type;
49 /*	cont char *format;
50 /* DESCRIPTION
51 /*	The functions in this module implement a pass-through proxy
52 /*	client.
53 /*
54 /*	In order to minimize the intrusiveness of pass-through
55 /*	proxying, 1) the proxy server must support the same MAIL
56 /*	FROM/RCPT syntax that Postfix supports, 2) the record-level
57 /*	routines for message content proxying have the same interface
58 /*	as the routines that are used for non-proxied mail.
59 /*
60 /*	smtpd_proxy_create() takes a description of a before-queue
61 /*	filter.  Depending on flags, it either arranges to buffer
62 /*	up commands and message content until the entire message
63 /*	is received, or it immediately connects to the proxy service,
64 /*	sends EHLO, sends client information with the XFORWARD
65 /*	command if possible, sends the MAIL FROM command, and
66 /*	receives the reply.
67 /*	A non-zero result value means trouble: either the proxy is
68 /*	unavailable, or it did not send the expected reply.
69 /*	All results are reported via the proxy->buffer field in a
70 /*	form that can be sent to the SMTP client.  An unexpected
71 /*	2xx or 3xx proxy server response is replaced by a generic
72 /*	error response to avoid support problems.
73 /*	In case of error, smtpd_proxy_create() updates the
74 /*	state->error_mask and state->err fields, and leaves the
75 /*	SMTPD_PROXY handle in an unconnected state.  Destroy the
76 /*	handle after reporting the error reply in the proxy->buffer
77 /*	field.
78 /*
79 /*	proxy->cmd() formats and either buffers up the command and
80 /*	expected response until the entire message is received, or
81 /*	it immediately sends the specified command to the proxy
82 /*	server, and receives the proxy server reply.
83 /*	A non-zero result value means trouble: either the proxy is
84 /*	unavailable, or it did not send the expected reply.
85 /*	All results are reported via the proxy->buffer field in a
86 /*	form that can be sent to the SMTP client.  An unexpected
87 /*	2xx or 3xx proxy server response is replaced by a generic
88 /*	error response to avoid support problems.
89 /*	In case of error, proxy->cmd() updates the state->error_mask
90 /*	and state->err fields.
91 /*
92 /*	smtpd_proxy_free() destroys a proxy server handle and resets
93 /*	the state->proxy field.
94 /*
95 /*	smtpd_proxy_parse_opts() parses main.cf processing options.
96 /*
97 /*	proxy->rec_put() is a rec_put() clone that either buffers
98 /*	up arbitrary message content records until the entire message
99 /*	is received, or that immediately sends it to the proxy
100 /*	server.
101 /*	All data is expected to be in SMTP dot-escaped form.
102 /*	All errors are reported as a REC_TYPE_ERROR result value,
103 /*	with the state->error_mask, state->err and proxy-buffer
104 /*	fields given appropriate values.
105 /*
106 /*	proxy->rec_fprintf() is a rec_fprintf() clone that formats
107 /*	message content and either buffers up the record until the
108 /*	entire message is received, or that immediately sends it
109 /*	to the proxy server.
110 /*	All data is expected to be in SMTP dot-escaped form.
111 /*	All errors are reported as a REC_TYPE_ERROR result value,
112 /*	with the state->error_mask, state->err and proxy-buffer
113 /*	fields given appropriate values.
114 /*
115 /*	Arguments:
116 /* .IP flags
117 /*	Zero, or SMTPD_PROXY_FLAG_SPEED_ADJUST to buffer up the entire
118 /*	message before contacting a before-queue content filter.
119 /*	Note: when this feature is requested, the before-queue
120 /*	filter MUST use the same 2xx, 4xx or 5xx reply code for all
121 /*	recipients of a multi-recipient message.
122 /* .IP server
123 /*	The SMTP proxy server host:port. The host or host: part is optional.
124 /*	This argument is not duplicated.
125 /* .IP timeout
126 /*	Time limit for connecting to the proxy server and for
127 /*	sending and receiving proxy server commands and replies.
128 /* .IP ehlo_name
129 /*	The EHLO Hostname that will be sent to the proxy server.
130 /*	This argument is not duplicated.
131 /* .IP mail_from
132 /*	The MAIL FROM command. This argument is not duplicated.
133 /* .IP state
134 /*	SMTP server state.
135 /* .IP expect
136 /*	Expected proxy server reply status code range. A warning is logged
137 /*	when an unexpected reply is received. Specify one of the following:
138 /* .RS
139 /* .IP SMTPD_PROX_WANT_OK
140 /*	The caller expects a reply in the 200 range.
141 /* .IP SMTPD_PROX_WANT_MORE
142 /*	The caller expects a reply in the 300 range.
143 /* .IP SMTPD_PROX_WANT_ANY
144 /*	The caller has no expectation. Do not warn for unexpected replies.
145 /* .IP SMTPD_PROX_WANT_NONE
146 /*	Do not bother waiting for a reply.
147 /* .RE
148 /* .IP format
149 /*	A format string.
150 /* .IP stream
151 /*	Connection to proxy server.
152 /* .IP data
153 /*	Pointer to the content of one message content record.
154 /* .IP len
155 /*	The length of a message content record.
156 /* SEE ALSO
157 /*	smtpd(8) Postfix smtp server
158 /* DIAGNOSTICS
159 /*	Panic: internal API violations.
160 /*
161 /*	Fatal errors: memory allocation problem.
162 /*
163 /*	Warnings: unexpected response from proxy server, unable
164 /*	to connect to proxy server, proxy server read/write error,
165 /*	proxy speed-adjust buffer read/write error.
166 /* LICENSE
167 /* .ad
168 /* .fi
169 /*	The Secure Mailer license must be distributed with this software.
170 /* AUTHOR(S)
171 /*	Wietse Venema
172 /*	IBM T.J. Watson Research
173 /*	P.O. Box 704
174 /*	Yorktown Heights, NY 10598, USA
175 /*--*/
176 
177 /* System library. */
178 
179 #include <sys_defs.h>
180 #include <ctype.h>
181 #include <unistd.h>
182 
183 #ifdef STRCASECMP_IN_STRINGS_H
184 #include <strings.h>
185 #endif
186 
187 /* Utility library. */
188 
189 #include <msg.h>
190 #include <vstream.h>
191 #include <vstring.h>
192 #include <stringops.h>
193 #include <connect.h>
194 #include <name_code.h>
195 #include <mymalloc.h>
196 
197 /* Global library. */
198 
199 #include <mail_error.h>
200 #include <smtp_stream.h>
201 #include <cleanup_user.h>
202 #include <mail_params.h>
203 #include <rec_type.h>
204 #include <mail_proto.h>
205 #include <xtext.h>
206 #include <record.h>
207 #include <mail_queue.h>
208 
209 /* Application-specific. */
210 
211 #include <smtpd.h>
212 #include <smtpd_proxy.h>
213 
214  /*
215   * XFORWARD server features, recognized by the pass-through proxy client.
216   */
217 #define SMTPD_PROXY_XFORWARD_NAME  (1<<0)	/* client name */
218 #define SMTPD_PROXY_XFORWARD_ADDR  (1<<1)	/* client address */
219 #define SMTPD_PROXY_XFORWARD_PROTO (1<<2)	/* protocol */
220 #define SMTPD_PROXY_XFORWARD_HELO  (1<<3)	/* client helo */
221 #define SMTPD_PROXY_XFORWARD_IDENT (1<<4)	/* message identifier */
222 #define SMTPD_PROXY_XFORWARD_DOMAIN (1<<5)	/* origin type */
223 #define SMTPD_PROXY_XFORWARD_PORT  (1<<6)	/* client port */
224 
225  /*
226   * Spead-matching: we use an unlinked file for transient storage.
227   */
228 static VSTREAM *smtpd_proxy_replay_stream;
229 
230  /*
231   * Forward declarations.
232   */
233 static void smtpd_proxy_fake_server_reply(SMTPD_STATE *, int);
234 static int smtpd_proxy_rdwr_error(SMTPD_STATE *, int);
235 static int PRINTFLIKE(3, 4) smtpd_proxy_cmd(SMTPD_STATE *, int, const char *,...);
236 static int smtpd_proxy_rec_put(VSTREAM *, int, const char *, ssize_t);
237 
238  /*
239   * SLMs.
240   */
241 #define STR(x)	vstring_str(x)
242 #define LEN(x)	VSTRING_LEN(x)
243 #define STREQ(x, y)	(strcmp((x), (y)) == 0)
244 
245 /* smtpd_proxy_xforward_flush - flush forwarding information */
246 
smtpd_proxy_xforward_flush(SMTPD_STATE * state,VSTRING * buf)247 static int smtpd_proxy_xforward_flush(SMTPD_STATE *state, VSTRING *buf)
248 {
249     int     ret;
250 
251     if (VSTRING_LEN(buf) > 0) {
252 	ret = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK,
253 			      XFORWARD_CMD "%s", STR(buf));
254 	VSTRING_RESET(buf);
255 	return (ret);
256     }
257     return (0);
258 }
259 
260 /* smtpd_proxy_xforward_send - send forwarding information */
261 
smtpd_proxy_xforward_send(SMTPD_STATE * state,VSTRING * buf,const char * name,int value_available,const char * value)262 static int smtpd_proxy_xforward_send(SMTPD_STATE *state, VSTRING *buf,
263 				             const char *name,
264 				             int value_available,
265 				             const char *value)
266 {
267     size_t  new_len;
268     int     ret;
269 
270 #define CONSTR_LEN(s)	(sizeof(s) - 1)
271 #define PAYLOAD_LIMIT	(512 - CONSTR_LEN("250 " XFORWARD_CMD "\r\n"))
272 
273     if (!value_available)
274 	value = XFORWARD_UNAVAILABLE;
275 
276     /*
277      * Encode the attribute value.
278      */
279     if (state->expand_buf == 0)
280 	state->expand_buf = vstring_alloc(100);
281     xtext_quote(state->expand_buf, value, "");
282 
283     /*
284      * How much space does this attribute need? SPACE name = value.
285      */
286     new_len = strlen(name) + strlen(STR(state->expand_buf)) + 2;
287     if (new_len > PAYLOAD_LIMIT)
288 	msg_warn("%s command payload %s=%.10s... exceeds SMTP protocol limit",
289 		 XFORWARD_CMD, name, value);
290 
291     /*
292      * Flush the buffer if we need to, and store the attribute.
293      */
294     if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT)
295 	if ((ret = smtpd_proxy_xforward_flush(state, buf)) < 0)
296 	    return (ret);
297     vstring_sprintf_append(buf, " %s=%s", name, STR(state->expand_buf));
298 
299     return (0);
300 }
301 
302 /* smtpd_proxy_connect - open proxy connection */
303 
smtpd_proxy_connect(SMTPD_STATE * state)304 static int smtpd_proxy_connect(SMTPD_STATE *state)
305 {
306     SMTPD_PROXY *proxy = state->proxy;
307     int     fd;
308     char   *lines;
309     char   *words;
310     VSTRING *buf;
311     int     bad;
312     char   *word;
313     static const NAME_CODE known_xforward_features[] = {
314 	XFORWARD_NAME, SMTPD_PROXY_XFORWARD_NAME,
315 	XFORWARD_ADDR, SMTPD_PROXY_XFORWARD_ADDR,
316 	XFORWARD_PORT, SMTPD_PROXY_XFORWARD_PORT,
317 	XFORWARD_PROTO, SMTPD_PROXY_XFORWARD_PROTO,
318 	XFORWARD_HELO, SMTPD_PROXY_XFORWARD_HELO,
319 	XFORWARD_IDENT, SMTPD_PROXY_XFORWARD_IDENT,
320 	XFORWARD_DOMAIN, SMTPD_PROXY_XFORWARD_DOMAIN,
321 	0, 0,
322     };
323     int     server_xforward_features;
324     int     (*connect_fn) (const char *, int, int);
325     const char *endpoint;
326 
327     /*
328      * Find connection method (default inet)
329      */
330     if (strncasecmp("unix:", proxy->service_name, 5) == 0) {
331 	endpoint = proxy->service_name + 5;
332 	connect_fn = unix_connect;
333     } else {
334 	if (strncasecmp("inet:", proxy->service_name, 5) == 0)
335 	    endpoint = proxy->service_name + 5;
336 	else
337 	    endpoint = proxy->service_name;
338 	connect_fn = inet_connect;
339     }
340 
341     /*
342      * Connect to proxy.
343      */
344     if ((fd = connect_fn(endpoint, BLOCKING, proxy->timeout)) < 0) {
345 	msg_warn("connect to proxy filter %s: %m", proxy->service_name);
346 	return (smtpd_proxy_rdwr_error(state, 0));
347     }
348     proxy->service_stream = vstream_fdopen(fd, O_RDWR);
349     /* Needed by our DATA-phase record emulation routines. */
350     vstream_control(proxy->service_stream,
351 		    CA_VSTREAM_CTL_CONTEXT((void *) state),
352 		    CA_VSTREAM_CTL_END);
353     /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
354     if (connect_fn == inet_connect)
355 	vstream_tweak_tcp(proxy->service_stream);
356     smtp_timeout_setup(proxy->service_stream, proxy->timeout);
357 
358     /*
359      * Get server greeting banner.
360      *
361      * If this fails then we have a problem because the proxy should always
362      * accept our connection. Make up our own response instead of passing
363      * back a negative greeting banner: the proxy open is delayed to the
364      * point that the client expects a MAIL FROM or RCPT TO reply.
365      */
366     if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", "")) {
367 	smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
368 	smtpd_proxy_close(state);
369 	return (-1);
370     }
371 
372     /*
373      * Send our own EHLO command. If this fails then we have a problem
374      * because the proxy should always accept our EHLO command. Make up our
375      * own response instead of passing back a negative EHLO reply: the proxy
376      * open is delayed to the point that the remote SMTP client expects a
377      * MAIL FROM or RCPT TO reply.
378      */
379     if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s",
380 			proxy->ehlo_name)) {
381 	smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
382 	smtpd_proxy_close(state);
383 	return (-1);
384     }
385 
386     /*
387      * Parse the EHLO reply and see if we can forward logging information.
388      */
389     server_xforward_features = 0;
390     lines = STR(proxy->reply);
391     while ((words = mystrtok(&lines, "\n")) != 0) {
392 	if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) {
393 	    if (strcasecmp(word, XFORWARD_CMD) == 0)
394 		while ((word = mystrtok(&words, " \t")) != 0)
395 		    server_xforward_features |=
396 			name_code(known_xforward_features,
397 				  NAME_CODE_FLAG_NONE, word);
398 	}
399     }
400 
401     /*
402      * Send XFORWARD attributes. For robustness, explicitly specify what SMTP
403      * session attributes are known and unknown. Make up our own response
404      * instead of passing back a negative XFORWARD reply: the proxy open is
405      * delayed to the point that the remote SMTP client expects a MAIL FROM
406      * or RCPT TO reply.
407      */
408     if (server_xforward_features) {
409 	buf = vstring_alloc(100);
410 	bad =
411 	    (((server_xforward_features & SMTPD_PROXY_XFORWARD_NAME)
412 	      && smtpd_proxy_xforward_send(state, buf, XFORWARD_NAME,
413 				  IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)),
414 					   FORWARD_NAME(state)))
415 	     || ((server_xforward_features & SMTPD_PROXY_XFORWARD_ADDR)
416 		 && smtpd_proxy_xforward_send(state, buf, XFORWARD_ADDR,
417 				  IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)),
418 					      FORWARD_ADDR(state)))
419 	     || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PORT)
420 		 && smtpd_proxy_xforward_send(state, buf, XFORWARD_PORT,
421 				  IS_AVAIL_CLIENT_PORT(FORWARD_PORT(state)),
422 					      FORWARD_PORT(state)))
423 	     || ((server_xforward_features & SMTPD_PROXY_XFORWARD_HELO)
424 		 && smtpd_proxy_xforward_send(state, buf, XFORWARD_HELO,
425 				  IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)),
426 					      FORWARD_HELO(state)))
427 	     || ((server_xforward_features & SMTPD_PROXY_XFORWARD_IDENT)
428 		 && smtpd_proxy_xforward_send(state, buf, XFORWARD_IDENT,
429 				IS_AVAIL_CLIENT_IDENT(FORWARD_IDENT(state)),
430 					      FORWARD_IDENT(state)))
431 	     || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PROTO)
432 		 && smtpd_proxy_xforward_send(state, buf, XFORWARD_PROTO,
433 				IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)),
434 					      FORWARD_PROTO(state)))
435 	     || ((server_xforward_features & SMTPD_PROXY_XFORWARD_DOMAIN)
436 		 && smtpd_proxy_xforward_send(state, buf, XFORWARD_DOMAIN, 1,
437 			 STREQ(FORWARD_DOMAIN(state), MAIL_ATTR_RWR_LOCAL) ?
438 				  XFORWARD_DOM_LOCAL : XFORWARD_DOM_REMOTE))
439 	     || smtpd_proxy_xforward_flush(state, buf));
440 	vstring_free(buf);
441 	if (bad) {
442 	    smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
443 	    smtpd_proxy_close(state);
444 	    return (-1);
445 	}
446     }
447 
448     /*
449      * Pass-through the remote SMTP client's MAIL FROM command. If this
450      * fails, then we have a problem because the proxy should always accept
451      * any MAIL FROM command that was accepted by us.
452      */
453     if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s",
454 			proxy->mail_from) != 0) {
455 	/* NOT: smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); */
456 	smtpd_proxy_close(state);
457 	return (-1);
458     }
459     return (0);
460 }
461 
462 /* smtpd_proxy_fake_server_reply - produce generic error response */
463 
smtpd_proxy_fake_server_reply(SMTPD_STATE * state,int status)464 static void smtpd_proxy_fake_server_reply(SMTPD_STATE *state, int status)
465 {
466     const CLEANUP_STAT_DETAIL *detail;
467 
468     /*
469      * Either we have no server reply (connection refused), or we have an
470      * out-of-protocol server reply, so we make up a generic server error
471      * response instead.
472      */
473     detail = cleanup_stat_detail(status);
474     vstring_sprintf(state->proxy->reply,
475 		    "%d %s Error: %s",
476 		    detail->smtp, detail->dsn, detail->text);
477 }
478 
479 /* smtpd_proxy_replay_rdwr_error - report replay log I/O error */
480 
smtpd_proxy_replay_rdwr_error(SMTPD_STATE * state)481 static int smtpd_proxy_replay_rdwr_error(SMTPD_STATE *state)
482 {
483 
484     /*
485      * Log an appropriate warning message.
486      */
487     msg_warn("proxy speed-adjust log I/O error: %m");
488 
489     /*
490      * Set the appropriate flags and server reply.
491      */
492     state->error_mask |= MAIL_ERROR_RESOURCE;
493     /* Update state->err in case we are past the client's DATA command. */
494     state->err |= CLEANUP_STAT_PROXY;
495     smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
496     return (-1);
497 }
498 
499 /* smtpd_proxy_rdwr_error - report proxy communication error */
500 
smtpd_proxy_rdwr_error(SMTPD_STATE * state,int err)501 static int smtpd_proxy_rdwr_error(SMTPD_STATE *state, int err)
502 {
503     const char *myname = "smtpd_proxy_rdwr_error";
504     SMTPD_PROXY *proxy = state->proxy;
505 
506     /*
507      * Sanity check.
508      */
509     if (err != 0 && err != SMTP_ERR_NONE && proxy == 0)
510 	msg_panic("%s: proxy error %d without proxy handle", myname, err);
511 
512     /*
513      * Log an appropriate warning message.
514      */
515     switch (err) {
516     case 0:
517     case SMTP_ERR_NONE:
518 	break;
519     case SMTP_ERR_EOF:
520 	msg_warn("lost connection with proxy %s", proxy->service_name);
521 	break;
522     case SMTP_ERR_TIME:
523 	msg_warn("timeout talking to proxy %s", proxy->service_name);
524 	break;
525     default:
526 	msg_panic("%s: unknown proxy %s error %d",
527 		  myname, proxy->service_name, err);
528     }
529 
530     /*
531      * Set the appropriate flags and server reply.
532      */
533     state->error_mask |= MAIL_ERROR_SOFTWARE;
534     /* Update state->err in case we are past the client's DATA command. */
535     state->err |= CLEANUP_STAT_PROXY;
536     smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY);
537     return (-1);
538 }
539 
540 /* smtpd_proxy_replay_send - replay saved SMTP session from speed-match log */
541 
smtpd_proxy_replay_send(SMTPD_STATE * state)542 static int smtpd_proxy_replay_send(SMTPD_STATE *state)
543 {
544     const char *myname = "smtpd_proxy_replay_send";
545     static VSTRING *replay_buf = 0;
546     SMTPD_PROXY *proxy = state->proxy;
547     int     rec_type;
548     int     expect = SMTPD_PROX_WANT_BAD;
549 
550     /*
551      * Sanity check.
552      */
553     if (smtpd_proxy_replay_stream == 0)
554 	msg_panic("%s: no before-queue filter speed-adjust log", myname);
555 
556     /*
557      * Errors first.
558      */
559     if (vstream_ferror(smtpd_proxy_replay_stream)
560 	|| vstream_feof(smtpd_proxy_replay_stream)
561 	|| rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END
562 	|| vstream_fflush(smtpd_proxy_replay_stream))
563 	/* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */
564 	return (smtpd_proxy_replay_rdwr_error(state));
565 
566     /*
567      * Delayed connection to the before-queue filter.
568      */
569     if (smtpd_proxy_connect(state) < 0)
570 	return (-1);
571 
572     /*
573      * Replay the speed-match log. We do sanity check record content, but we
574      * don't implement a protocol state engine here, since we are reading
575      * from a file that we just wrote ourselves.
576      *
577      * This is different than the MailChannels patented solution that
578      * multiplexes a large number of slowed-down inbound connections over a
579      * small number of fast connections to a local MTA.
580      *
581      * - MailChannels receives mail directly from the Internet. It uses one
582      * connection to the local MTA to reject invalid recipients before
583      * receiving the entire email message at reduced bit rates, and then uses
584      * a different connection to quickly deliver the message to the local
585      * MTA.
586      *
587      * - Postfix receives mail directly from the Internet. The Postfix SMTP
588      * server rejects invalid recipients before receiving the entire message
589      * over the Internet, and then delivers the message quickly to a local
590      * SMTP-based content filter.
591      */
592     if (replay_buf == 0)
593 	replay_buf = vstring_alloc(100);
594     if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0)
595 	return (smtpd_proxy_replay_rdwr_error(state));
596 
597     for (;;) {
598 	switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf,
599 				   REC_FLAG_NONE)) {
600 
601 	    /*
602 	     * Message content.
603 	     */
604 	case REC_TYPE_NORM:
605 	case REC_TYPE_CONT:
606 	    if (smtpd_proxy_rec_put(proxy->service_stream, rec_type,
607 				    STR(replay_buf), LEN(replay_buf)) < 0)
608 		return (-1);
609 	    break;
610 
611 	    /*
612 	     * Expected server reply type.
613 	     */
614 	case REC_TYPE_RCPT:
615 	    if (!alldig(STR(replay_buf))
616 		|| (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD)
617 		msg_panic("%s: malformed server reply type: %s",
618 			  myname, STR(replay_buf));
619 	    break;
620 
621 	    /*
622 	     * Client command, or void. Bail out on the first negative proxy
623 	     * response. This is OK, because the filter must use the same
624 	     * reply code for all recipients of a multi-recipient message.
625 	     */
626 	case REC_TYPE_FROM:
627 	    if (expect == SMTPD_PROX_WANT_BAD)
628 		msg_panic("%s: missing server reply type", myname);
629 	    if (smtpd_proxy_cmd(state, expect, "%s", STR(replay_buf)) < 0)
630 		return (-1);
631 	    expect = SMTPD_PROX_WANT_BAD;
632 	    break;
633 
634 	    /*
635 	     * Explicit end marker, instead of implicit EOF.
636 	     */
637 	case REC_TYPE_END:
638 	    return (0);
639 
640 	    /*
641 	     * Errors.
642 	     */
643 	case REC_TYPE_ERROR:
644 	    return (smtpd_proxy_replay_rdwr_error(state));
645 	default:
646 	    msg_panic("%s: unexpected record type; %d", myname, rec_type);
647 	}
648     }
649 }
650 
651 /* smtpd_proxy_save_cmd - save SMTP command + expected response to replay log */
652 
smtpd_proxy_save_cmd(SMTPD_STATE * state,int expect,const char * fmt,...)653 static int PRINTFLIKE(3, 4) smtpd_proxy_save_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
654 {
655     va_list ap;
656 
657     /*
658      * Errors first.
659      */
660     if (vstream_ferror(smtpd_proxy_replay_stream)
661 	|| vstream_feof(smtpd_proxy_replay_stream))
662 	return (smtpd_proxy_replay_rdwr_error(state));
663 
664     /*
665      * Save the expected reply first, so that the replayer can safely
666      * overwrite the input buffer with the command.
667      */
668     rec_fprintf(smtpd_proxy_replay_stream, REC_TYPE_RCPT, "%d", expect);
669 
670     /*
671      * The command can be omitted at the start of an SMTP session. This is
672      * not documented as part of the official interface because it is used
673      * only internally to this module.
674      */
675 
676     /*
677      * Save the command to the replay log, and send it to the before-queue
678      * filter after we have received the entire message.
679      */
680     va_start(ap, fmt);
681     rec_vfprintf(smtpd_proxy_replay_stream, REC_TYPE_FROM, fmt, ap);
682     va_end(ap);
683 
684     /*
685      * If we just saved the "." command, replay the log.
686      */
687     return (strcmp(fmt, ".") ? 0 : smtpd_proxy_replay_send(state));
688 }
689 
690 /* smtpd_proxy_cmd - send command to proxy, receive reply */
691 
smtpd_proxy_cmd(SMTPD_STATE * state,int expect,const char * fmt,...)692 static int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
693 {
694     SMTPD_PROXY *proxy = state->proxy;
695     va_list ap;
696     char   *cp;
697     int     last_char;
698     int     err = 0;
699     static VSTRING *buffer = 0;
700 
701     /*
702      * Errors first. Be prepared for delayed errors from the DATA phase.
703      */
704     if (vstream_ferror(proxy->service_stream)
705 	|| vstream_feof(proxy->service_stream)
706 	|| (err = vstream_setjmp(proxy->service_stream)) != 0) {
707 	return (smtpd_proxy_rdwr_error(state, err));
708     }
709 
710     /*
711      * Format the command.
712      */
713     va_start(ap, fmt);
714     vstring_vsprintf(proxy->request, fmt, ap);
715     va_end(ap);
716 
717     /*
718      * The command can be omitted at the start of an SMTP session. This is
719      * not documented as part of the official interface because it is used
720      * only internally to this module.
721      */
722     if (LEN(proxy->request) > 0) {
723 
724 	/*
725 	 * Optionally log the command first, so that we can see in the log
726 	 * what the program is trying to do.
727 	 */
728 	if (msg_verbose)
729 	    msg_info("> %s: %s", proxy->service_name, STR(proxy->request));
730 
731 	/*
732 	 * Send the command to the proxy server. Since we're going to read a
733 	 * reply immediately, there is no need to flush buffers.
734 	 */
735 	smtp_fputs(STR(proxy->request), LEN(proxy->request),
736 		   proxy->service_stream);
737     }
738 
739     /*
740      * Early return if we don't want to wait for a server reply (such as
741      * after sending QUIT).
742      */
743     if (expect == SMTPD_PROX_WANT_NONE)
744 	return (0);
745 
746     /*
747      * Censor out non-printable characters in server responses and save
748      * complete multi-line responses if possible.
749      *
750      * We can't parse or store input that exceeds var_line_limit, so we just
751      * skip over it to simplify the remainder of the code below.
752      */
753     VSTRING_RESET(proxy->reply);
754     if (buffer == 0)
755 	buffer = vstring_alloc(10);
756     for (;;) {
757 	last_char = smtp_get(buffer, proxy->service_stream, var_line_limit,
758 			     SMTP_GET_FLAG_SKIP);
759 	printable(STR(buffer), '?');
760 	if (last_char != '\n')
761 	    msg_warn("%s: response longer than %d: %.30s...",
762 		     proxy->service_name, var_line_limit,
763 		     STR(buffer));
764 	if (msg_verbose)
765 	    msg_info("< %s: %.100s", proxy->service_name, STR(buffer));
766 
767 	/*
768 	 * Defend against a denial of service attack by limiting the amount
769 	 * of multi-line text that we are willing to store.
770 	 */
771 	if (LEN(proxy->reply) < var_line_limit) {
772 	    if (VSTRING_LEN(proxy->reply))
773 		vstring_strcat(proxy->reply, "\r\n");
774 	    vstring_strcat(proxy->reply, STR(buffer));
775 	}
776 
777 	/*
778 	 * Parse the response into code and text. Ignore unrecognized
779 	 * garbage. This means that any character except space (or end of
780 	 * line) will have the same effect as the '-' line continuation
781 	 * character.
782 	 */
783 	for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++)
784 	     /* void */ ;
785 	if (cp - STR(buffer) == 3) {
786 	    if (*cp == '-')
787 		continue;
788 	    if (*cp == ' ' || *cp == 0)
789 		break;
790 	}
791 	msg_warn("received garbage from proxy %s: %.100s",
792 		 proxy->service_name, STR(buffer));
793     }
794 
795     /*
796      * Log a warning in case the proxy does not send the expected response.
797      * Silently accept any response when the client expressed no expectation.
798      *
799      * Starting with Postfix 2.6 we don't pass through unexpected 2xx or 3xx
800      * proxy replies. They are a source of support problems, so we replace
801      * them by generic server error replies.
802      */
803     if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(proxy->reply)) {
804 	msg_warn("proxy %s rejected \"%s\": \"%s\"",
805 		 proxy->service_name, LEN(proxy->request) == 0 ?
806 		 "connection request" : STR(proxy->request),
807 		 STR(proxy->reply));
808 	if (*STR(proxy->reply) == SMTPD_PROX_WANT_OK
809 	    || *STR(proxy->reply) == SMTPD_PROX_WANT_MORE) {
810 	    smtpd_proxy_rdwr_error(state, 0);
811 	}
812 	return (-1);
813     } else {
814 	return (0);
815     }
816 }
817 
818 /* smtpd_proxy_save_rec_put - save message content to replay log */
819 
smtpd_proxy_save_rec_put(VSTREAM * stream,int rec_type,const char * data,ssize_t len)820 static int smtpd_proxy_save_rec_put(VSTREAM *stream, int rec_type,
821 				            const char *data, ssize_t len)
822 {
823     const char *myname = "smtpd_proxy_save_rec_put";
824     int     ret;
825 
826 #define VSTREAM_TO_SMTPD_STATE(s) ((SMTPD_STATE *) vstream_context(s))
827 
828     /*
829      * Sanity check.
830      */
831     if (stream == 0)
832 	msg_panic("%s: attempt to use closed stream", myname);
833 
834     /*
835      * Send one content record. Errors and results must be as with rec_put().
836      */
837     if (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT)
838 	ret = rec_put(stream, rec_type, data, len);
839     else
840 	msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname);
841 
842     /*
843      * Errors last.
844      */
845     if (ret != rec_type) {
846 	(void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream));
847 	return (REC_TYPE_ERROR);
848     }
849     return (rec_type);
850 }
851 
852 /* smtpd_proxy_rec_put - send message content, rec_put() clone */
853 
smtpd_proxy_rec_put(VSTREAM * stream,int rec_type,const char * data,ssize_t len)854 static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type,
855 			               const char *data, ssize_t len)
856 {
857     const char *myname = "smtpd_proxy_rec_put";
858     int     err = 0;
859 
860     /*
861      * Errors first.
862      */
863     if (vstream_ferror(stream) || vstream_feof(stream)
864 	|| (err = vstream_setjmp(stream)) != 0) {
865 	(void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err);
866 	return (REC_TYPE_ERROR);
867     }
868 
869     /*
870      * Send one content record. Errors and results must be as with rec_put().
871      */
872     if (rec_type == REC_TYPE_NORM)
873 	smtp_fputs(data, len, stream);
874     else if (rec_type == REC_TYPE_CONT)
875 	smtp_fwrite(data, len, stream);
876     else
877 	msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname);
878     return (rec_type);
879 }
880 
881 /* smtpd_proxy_save_rec_fprintf - save message content to replay log */
882 
smtpd_proxy_save_rec_fprintf(VSTREAM * stream,int rec_type,const char * fmt,...)883 static int smtpd_proxy_save_rec_fprintf(VSTREAM *stream, int rec_type,
884 					        const char *fmt,...)
885 {
886     const char *myname = "smtpd_proxy_save_rec_fprintf";
887     va_list ap;
888     int     ret;
889 
890     /*
891      * Sanity check.
892      */
893     if (stream == 0)
894 	msg_panic("%s: attempt to use closed stream", myname);
895 
896     /*
897      * Save one content record. Errors and results must be as with
898      * rec_fprintf().
899      */
900     va_start(ap, fmt);
901     if (rec_type == REC_TYPE_NORM)
902 	ret = rec_vfprintf(stream, rec_type, fmt, ap);
903     else
904 	msg_panic("%s: need REC_TYPE_NORM", myname);
905     va_end(ap);
906 
907     /*
908      * Errors last.
909      */
910     if (ret != rec_type) {
911 	(void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream));
912 	return (REC_TYPE_ERROR);
913     }
914     return (rec_type);
915 }
916 
917 /* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */
918 
smtpd_proxy_rec_fprintf(VSTREAM * stream,int rec_type,const char * fmt,...)919 static int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type,
920 				           const char *fmt,...)
921 {
922     const char *myname = "smtpd_proxy_rec_fprintf";
923     va_list ap;
924     int     err = 0;
925 
926     /*
927      * Errors first.
928      */
929     if (vstream_ferror(stream) || vstream_feof(stream)
930 	|| (err = vstream_setjmp(stream)) != 0) {
931 	(void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err);
932 	return (REC_TYPE_ERROR);
933     }
934 
935     /*
936      * Send one content record. Errors and results must be as with
937      * rec_fprintf().
938      */
939     va_start(ap, fmt);
940     if (rec_type == REC_TYPE_NORM)
941 	smtp_vprintf(stream, fmt, ap);
942     else
943 	msg_panic("%s: need REC_TYPE_NORM", myname);
944     va_end(ap);
945     return (rec_type);
946 }
947 
948 #ifndef NO_TRUNCATE
949 
950 /* smtpd_proxy_replay_setup - prepare the replay logfile */
951 
smtpd_proxy_replay_setup(SMTPD_STATE * state)952 static int smtpd_proxy_replay_setup(SMTPD_STATE *state)
953 {
954     const char *myname = "smtpd_proxy_replay_setup";
955     off_t   file_offs;
956 
957     /*
958      * Where possible reuse an existing replay logfile, because creating a
959      * file is expensive compared to reading or writing. For security reasons
960      * we must truncate the file before reuse. For performance reasons we
961      * should truncate the file immediately after the end of a mail
962      * transaction. We enforce the security guarantee upon reuse, by
963      * requiring that no I/O happened since the file was truncated. This is
964      * less expensive than truncating the file redundantly.
965      */
966     if (smtpd_proxy_replay_stream != 0) {
967 	/* vstream_ftell() won't invoke the kernel, so all errors are mine. */
968 	if ((file_offs = vstream_ftell(smtpd_proxy_replay_stream)) != 0)
969 	    msg_panic("%s: bad before-queue filter speed-adjust log offset %lu",
970 		      myname, (unsigned long) file_offs);
971 	vstream_clearerr(smtpd_proxy_replay_stream);
972 	if (msg_verbose)
973 	    msg_info("%s: reuse speed-adjust stream fd=%d", myname,
974 		     vstream_fileno(smtpd_proxy_replay_stream));
975 	/* Here, smtpd_proxy_replay_stream != 0 */
976     }
977 
978     /*
979      * Create a new replay logfile.
980      */
981     if (smtpd_proxy_replay_stream == 0) {
982 	smtpd_proxy_replay_stream = mail_queue_enter(MAIL_QUEUE_INCOMING, 0,
983 						     (struct timeval *) 0);
984 	if (smtpd_proxy_replay_stream == 0)
985 	    return (smtpd_proxy_replay_rdwr_error(state));
986 	if (unlink(VSTREAM_PATH(smtpd_proxy_replay_stream)) < 0)
987 	    msg_warn("remove before-queue filter speed-adjust log %s: %m",
988 		     VSTREAM_PATH(smtpd_proxy_replay_stream));
989 	if (msg_verbose)
990 	    msg_info("%s: new speed-adjust stream fd=%d", myname,
991 		     vstream_fileno(smtpd_proxy_replay_stream));
992     }
993 
994     /*
995      * Needed by our DATA-phase record emulation routines.
996      */
997     vstream_control(smtpd_proxy_replay_stream,
998 		    CA_VSTREAM_CTL_CONTEXT((void *) state),
999 		    CA_VSTREAM_CTL_END);
1000     return (0);
1001 }
1002 
1003 #endif
1004 
1005 /* smtpd_proxy_create - set up smtpd proxy handle */
1006 
smtpd_proxy_create(SMTPD_STATE * state,int flags,const char * service,int timeout,const char * ehlo_name,const char * mail_from)1007 int     smtpd_proxy_create(SMTPD_STATE *state, int flags, const char *service,
1008 			           int timeout, const char *ehlo_name,
1009 			           const char *mail_from)
1010 {
1011     SMTPD_PROXY *proxy;
1012 
1013     /*
1014      * When an operation has many arguments it is safer to use named
1015      * parameters, and have the compiler enforce the argument count.
1016      */
1017 #define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) \
1018 	((p) = (SMTPD_PROXY *) mymalloc(sizeof(*(p))), (p)->a1, (p)->a2, \
1019 	 (p)->a3, (p)->a4, (p)->a5, (p)->a6, (p)->a7, (p)->a8, (p)->a9, \
1020 	 (p)->a10, (p)->a11, (p)->a12, (p))
1021 
1022     /*
1023      * Sanity check.
1024      */
1025     if (state->proxy != 0)
1026 	msg_panic("smtpd_proxy_create: handle still exists");
1027 
1028     /*
1029      * Connect to the before-queue filter immediately.
1030      */
1031     if ((flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) == 0) {
1032 	state->proxy =
1033 	    SMTPD_PROXY_ALLOC(proxy, stream = 0, request = vstring_alloc(10),
1034 			      reply = vstring_alloc(10),
1035 			      cmd = smtpd_proxy_cmd,
1036 			      rec_fprintf = smtpd_proxy_rec_fprintf,
1037 			      rec_put = smtpd_proxy_rec_put,
1038 			      flags = flags, service_stream = 0,
1039 			      service_name = service, timeout = timeout,
1040 			      ehlo_name = ehlo_name, mail_from = mail_from);
1041 	if (smtpd_proxy_connect(state) < 0) {
1042 	    /* NOT: smtpd_proxy_free(state); we still need proxy->reply. */
1043 	    return (-1);
1044 	}
1045 	proxy->stream = proxy->service_stream;
1046 	return (0);
1047     }
1048 
1049     /*
1050      * Connect to the before-queue filter after we receive the entire
1051      * message. Open the replay logfile early to simplify code. The file is
1052      * reused for multiple mail transactions, so there is no need to minimize
1053      * its life time.
1054      */
1055     else {
1056 #ifdef NO_TRUNCATE
1057 	msg_panic("smtpd_proxy_create: speed-adjust support is not available");
1058 #else
1059 	if (smtpd_proxy_replay_setup(state) < 0)
1060 	    return (-1);
1061 	state->proxy =
1062 	    SMTPD_PROXY_ALLOC(proxy, stream = smtpd_proxy_replay_stream,
1063 			      request = vstring_alloc(10),
1064 			      reply = vstring_alloc(10),
1065 			      cmd = smtpd_proxy_save_cmd,
1066 			      rec_fprintf = smtpd_proxy_save_rec_fprintf,
1067 			      rec_put = smtpd_proxy_save_rec_put,
1068 			      flags = flags, service_stream = 0,
1069 			      service_name = service, timeout = timeout,
1070 			      ehlo_name = ehlo_name, mail_from = mail_from);
1071 	return (0);
1072 #endif
1073     }
1074 }
1075 
1076 /* smtpd_proxy_close - close proxy connection without destroying handle */
1077 
smtpd_proxy_close(SMTPD_STATE * state)1078 void    smtpd_proxy_close(SMTPD_STATE *state)
1079 {
1080     SMTPD_PROXY *proxy = state->proxy;
1081 
1082     /*
1083      * Specify SMTPD_PROX_WANT_NONE so that the server reply will not clobber
1084      * the END-OF-DATA reply.
1085      */
1086     if (proxy->service_stream != 0) {
1087 	if (vstream_feof(proxy->service_stream) == 0
1088 	    && vstream_ferror(proxy->service_stream) == 0)
1089 	    (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE,
1090 				   SMTPD_CMD_QUIT);
1091 	(void) vstream_fclose(proxy->service_stream);
1092 	if (proxy->stream == proxy->service_stream)
1093 	    proxy->stream = 0;
1094 	proxy->service_stream = 0;
1095     }
1096 }
1097 
1098 /* smtpd_proxy_free - destroy smtpd proxy handle */
1099 
smtpd_proxy_free(SMTPD_STATE * state)1100 void    smtpd_proxy_free(SMTPD_STATE *state)
1101 {
1102     SMTPD_PROXY *proxy = state->proxy;
1103 
1104     /*
1105      * Clean up.
1106      */
1107     if (proxy->service_stream != 0)
1108 	(void) smtpd_proxy_close(state);
1109     if (proxy->request != 0)
1110 	vstring_free(proxy->request);
1111     if (proxy->reply != 0)
1112 	vstring_free(proxy->reply);
1113     myfree((void *) proxy);
1114     state->proxy = 0;
1115 
1116     /*
1117      * Reuse the replay logfile if possible. For security reasons we must
1118      * truncate the replay logfile before reuse. For performance reasons we
1119      * should truncate the replay logfile immediately after the end of a mail
1120      * transaction. We truncate the file here, and enforce the security
1121      * guarantee by requiring that no I/O happens before the file is reused.
1122      */
1123     if (smtpd_proxy_replay_stream == 0)
1124 	return;
1125     if (vstream_ferror(smtpd_proxy_replay_stream)) {
1126 	/* Errors are already reported. */
1127 	(void) vstream_fclose(smtpd_proxy_replay_stream);
1128 	smtpd_proxy_replay_stream = 0;
1129 	return;
1130     }
1131     /* Flush output from aborted transaction before truncating the file!! */
1132     if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) {
1133 	msg_warn("seek before-queue filter speed-adjust log: %m");
1134 	(void) vstream_fclose(smtpd_proxy_replay_stream);
1135 	smtpd_proxy_replay_stream = 0;
1136 	return;
1137     }
1138     if (ftruncate(vstream_fileno(smtpd_proxy_replay_stream), (off_t) 0) < 0) {
1139 	msg_warn("truncate before-queue filter speed-adjust log: %m");
1140 	(void) vstream_fclose(smtpd_proxy_replay_stream);
1141 	smtpd_proxy_replay_stream = 0;
1142 	return;
1143     }
1144 }
1145 
1146 /* smtpd_proxy_parse_opts - parse main.cf options */
1147 
smtpd_proxy_parse_opts(const char * param_name,const char * param_val)1148 int     smtpd_proxy_parse_opts(const char *param_name, const char *param_val)
1149 {
1150     static const NAME_MASK proxy_opts_table[] = {
1151 	SMTPD_PROXY_NAME_SPEED_ADJUST, SMTPD_PROXY_FLAG_SPEED_ADJUST,
1152 	0, 0,
1153     };
1154     int     flags;
1155 
1156     /*
1157      * The optional before-filter speed-adjust buffers use disk space.
1158      * However, we don't know if they compete for storage space with the
1159      * after-filter queue, so we can't simply bump up the free space
1160      * requirement to 2.5 * message_size_limit.
1161      */
1162     flags = name_mask(param_name, proxy_opts_table, param_val);
1163     if (flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) {
1164 #ifdef NO_TRUNCATE
1165 	msg_warn("smtpd_proxy %s support is not available",
1166 		 SMTPD_PROXY_NAME_SPEED_ADJUST);
1167 	flags &= ~SMTPD_PROXY_FLAG_SPEED_ADJUST;
1168 #endif
1169     }
1170     return (flags);
1171 }
1172