1/*++
2/* NAME
3/*	postscreen_haproxy 3
4/* SUMMARY
5/*	haproxy protocol adapter
6/* SYNOPSIS
7/*	#include <postscreen_haproxy.h>
8/*
9/*	void	psc_endpt_haproxy_lookup(smtp_client_stream, lookup_done)
10/*	VSTRING	*smtp_client_stream;
11/*	void	(*lookup_done)(status, smtp_client_stream,
12/*				smtp_client_addr, smtp_client_port,
13/*				smtp_server_addr, smtp_server_port)
14/*	int	status;
15/*	MAI_HOSTADDR_STR *smtp_client_addr;
16/*	MAI_SERVPORT_STR *smtp_client_port;
17/*	MAI_HOSTADDR_STR *smtp_server_addr;
18/*	MAI_SERVPORT_STR *smtp_server_port;
19/* DESCRIPTION
20/*	psc_endpt_haproxy_lookup() looks up connection endpoint
21/*	information via the haproxy protocol.  Arguments and results
22/*	conform to the postscreen_endpt(3) API.
23/* LICENSE
24/* .ad
25/* .fi
26/*	The Secure Mailer license must be distributed with this software.
27/* AUTHOR(S)
28/*	Wietse Venema
29/*	IBM T.J. Watson Research
30/*	P.O. Box 704
31/*	Yorktown Heights, NY 10598, USA
32/*--*/
33
34/* System library. */
35
36#include <sys_defs.h>
37#include <stdio.h>
38#include <stdarg.h>
39#include <stdlib.h>
40
41/* Utility library. */
42
43#include <msg.h>
44#include <mymalloc.h>
45#include <events.h>
46#include <myaddrinfo.h>
47#include <vstream.h>
48#include <vstring.h>
49#include <stringops.h>
50
51/* Global library. */
52
53#include <haproxy_srvr.h>
54#include <mail_params.h>
55
56/* Application-specific. */
57
58#include <postscreen.h>
59#include <postscreen_haproxy.h>
60
61 /*
62  * Per-session state.
63  */
64typedef struct {
65    VSTREAM *stream;
66    PSC_ENDPT_LOOKUP_FN notify;
67    VSTRING *buffer;
68} PSC_HAPROXY_STATE;
69
70/* psc_endpt_haproxy_event - read or time event */
71
72static void psc_endpt_haproxy_event(int event, char *context)
73{
74    const char *myname = "psc_endpt_haproxy_event";
75    PSC_HAPROXY_STATE *state = (PSC_HAPROXY_STATE *) context;
76    int     status = 0;
77    MAI_HOSTADDR_STR smtp_client_addr;
78    MAI_SERVPORT_STR smtp_client_port;
79    MAI_HOSTADDR_STR smtp_server_addr;
80    MAI_SERVPORT_STR smtp_server_port;
81    int     last_char = 0;
82    const char *err;
83    VSTRING *escape_buf;
84    char    read_buf[HAPROXY_MAX_LEN];
85    ssize_t read_len;
86    char   *cp;
87
88    /*
89     * We must not read(2) past the <CR><LF> that terminates the haproxy
90     * line. For efficiency reasons we read the entire haproxy line in one
91     * read(2) call when we know that the line is unfragmented. In the rare
92     * case that the line is fragmented, we fall back and read(2) it one
93     * character at a time.
94     */
95    switch (event) {
96    case EVENT_TIME:
97	msg_warn("haproxy read: time limit exceeded");
98	status = -1;
99	break;
100    case EVENT_READ:
101	/* Determine the initial VSTREAM read(2) buffer size. */
102	if (VSTRING_LEN(state->buffer) == 0) {
103	    if ((read_len = recv(vstream_fileno(state->stream),
104			      read_buf, sizeof(read_buf) - 1, MSG_PEEK)) > 0
105		&& ((cp = memchr(read_buf, '\n', read_len)) != 0)) {
106		read_len = cp - read_buf + 1;
107	    } else {
108		read_len = 1;
109	    }
110	    vstream_control(state->stream, VSTREAM_CTL_BUFSIZE, read_len,
111			    VSTREAM_CTL_END);
112	}
113	/* Drain the VSTREAM buffer, otherwise this pseudo-thread will hang. */
114	do {
115	    if ((last_char = VSTREAM_GETC(state->stream)) == VSTREAM_EOF) {
116		if (vstream_ferror(state->stream))
117		    msg_warn("haproxy read: %m");
118		else
119		    msg_warn("haproxy read: lost connection");
120		status = -1;
121		break;
122	    }
123	    if (VSTRING_LEN(state->buffer) >= HAPROXY_MAX_LEN) {
124		msg_warn("haproxy read: line too long");
125		status = -1;
126		break;
127	    }
128	    VSTRING_ADDCH(state->buffer, last_char);
129	} while (vstream_peek(state->stream) > 0);
130	break;
131    }
132
133    /*
134     * Parse the haproxy line. Note: the haproxy_srvr_parse() routine
135     * performs address protocol checks, address and port syntax checks, and
136     * converts IPv4-in-IPv6 address string syntax (:ffff::1.2.3.4) to IPv4
137     * syntax where permitted by the main.cf:inet_protocols setting.
138     */
139    if (status == 0 && last_char == '\n') {
140	VSTRING_TERMINATE(state->buffer);
141	if ((err = haproxy_srvr_parse(vstring_str(state->buffer),
142				      &smtp_client_addr, &smtp_client_port,
143			      &smtp_server_addr, &smtp_server_port)) != 0) {
144	    escape_buf = vstring_alloc(HAPROXY_MAX_LEN + 2);
145	    escape(escape_buf, vstring_str(state->buffer),
146		   VSTRING_LEN(state->buffer));
147	    msg_warn("haproxy read: %s: %s", err, vstring_str(escape_buf));
148	    status = -1;
149	    vstring_free(escape_buf);
150	}
151    }
152
153    /*
154     * Are we done yet?
155     */
156    if (status < 0 || last_char == '\n') {
157	PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->stream),
158				psc_endpt_haproxy_event, context);
159	vstream_control(state->stream,
160			VSTREAM_CTL_BUFSIZE, (ssize_t) VSTREAM_BUFSIZE,
161			VSTREAM_CTL_END);
162	state->notify(status, state->stream,
163		      &smtp_client_addr, &smtp_client_port,
164		      &smtp_server_addr, &smtp_server_port);
165	/* Note: the stream may be closed at this point. */
166	vstring_free(state->buffer);
167	myfree((char *) state);
168    }
169}
170
171/* psc_endpt_haproxy_lookup - event-driven haproxy client */
172
173void    psc_endpt_haproxy_lookup(VSTREAM *stream,
174				         PSC_ENDPT_LOOKUP_FN notify)
175{
176    const char *myname = "psc_endpt_haproxy_lookup";
177    PSC_HAPROXY_STATE *state;
178
179    /*
180     * Prepare the per-session state. XXX To improve overload behavior,
181     * maintain a pool of these so that we can reduce memory allocator
182     * activity.
183     */
184    state = (PSC_HAPROXY_STATE *) mymalloc(sizeof(*state));
185    state->stream = stream;
186    state->notify = notify;
187    state->buffer = vstring_alloc(100);
188
189    /*
190     * Read the haproxy line.
191     */
192    PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_endpt_haproxy_event,
193			   (char *) state, var_psc_uproxy_tmout);
194}
195