1 /*
2  * Copyright (C) 2015 Red Hat, Inc.
3  *
4  * This file is part of ocserv.
5  *
6  * ocserv is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * ocserv is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 
22 #include <sys/types.h>
23 #include <netinet/in.h>
24 #include <sys/socket.h>
25 #include <netinet/tcp.h>
26 #include <arpa/inet.h>
27 
28 #include <worker.h>
29 
30 /* This file implements the Proxy Protocol v2, as described in:
31  * http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt
32  *
33  * That allows one to obtain the detailed peer information even when
34  * the session is received by a proxy.
35  */
36 
37 #define PROXY_HEADER_V2 "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
38 #define PROXY_HEADER_V2_SIZE (sizeof(PROXY_HEADER_V2)-1)
39 
40 #define AVAIL_HEADER_SIZE(hsize, want) { \
41 	if (hsize < want) { \
42 		oclog(ws, LOG_ERR, "proxy-hdr: invalid TLV header"); \
43 		return; \
44 	} \
45 	hsize -= want; \
46 	}
47 
48 typedef struct proxy_hdr_v2 {
49 	uint8_t sig[PROXY_HEADER_V2_SIZE];
50 	uint8_t ver_cmd;
51 	uint8_t family;
52 	uint16_t len;
53 	uint8_t data[520];
54 } _ATTR_PACKED proxy_hdr_v2;
55 
56 #define PP2_TYPE_SSL           0x20
57 #define PP2_TYPE_SSL_CN        0x22
58 
59 #define PP2_CLIENT_SSL           0x01
60 #define PP2_CLIENT_CERT_CONN     0x02
61 #define PP2_CLIENT_CERT_SESS     0x04
62 
63 typedef struct pp2_tlv {
64 	uint8_t type;
65 	uint16_t length;
66 } _ATTR_PACKED pp2_tlv;
67 
68 typedef struct pp2_tlv_ssl {
69 	uint8_t  client;
70 	uint32_t verify;
71 } _ATTR_PACKED pp2_tlv_ssl;
72 
parse_ssl_tlvs(struct worker_st * ws,uint8_t * data,size_t data_size)73 static void parse_ssl_tlvs(struct worker_st *ws, uint8_t *data, size_t data_size)
74 {
75 	pp2_tlv tlv;
76 
77 	while(data_size > 0) {
78 		AVAIL_HEADER_SIZE(data_size, sizeof(pp2_tlv));
79 		memcpy(&tlv, data, sizeof(pp2_tlv));
80 
81 		/* that seems to be in little endian */
82 		tlv.length = htons(tlv.length);
83 
84 		data += sizeof(pp2_tlv);
85 
86 		oclog(ws, LOG_INFO, "proxy-hdr: TLV type %x", (unsigned)tlv.type);
87 		if (tlv.type == PP2_TYPE_SSL) {
88 			pp2_tlv_ssl tssl;
89 			if (tlv.length < sizeof(pp2_tlv_ssl)) {
90 				oclog(ws, LOG_ERR, "proxy-hdr: TLV SSL header size is invalid");
91 				continue;
92 			}
93 			tlv.length = sizeof(pp2_tlv_ssl);
94 			AVAIL_HEADER_SIZE(data_size, tlv.length);
95 
96 			memcpy(&tssl, data, sizeof(pp2_tlv_ssl));
97 
98 			if ((tssl.client & PP2_CLIENT_SSL) &&
99 			    (tssl.client & PP2_CLIENT_CERT_SESS) &&
100 			    (tssl.verify == 0)) {
101 				oclog(ws, LOG_INFO, "proxy-hdr: user has presented valid certificate");
102 			    	ws->cert_auth_ok = 1;
103 
104 			}
105 		} else if (tlv.type == PP2_TYPE_SSL_CN && ws->cert_auth_ok) {
106 			if (tlv.length > sizeof(ws->cert_username)-1) {
107 				oclog(ws, LOG_ERR, "proxy-hdr: TLV SSL CN header size is too long");
108 				continue;
109 			}
110 
111 			AVAIL_HEADER_SIZE(data_size, tlv.length);
112 
113 			memcpy(ws->cert_username, data, tlv.length);
114 			ws->cert_username[tlv.length] = 0;
115 
116 			oclog(ws, LOG_INFO, "proxy-hdr: user's name is '%s'", ws->cert_username);
117 		} else {
118 			AVAIL_HEADER_SIZE(data_size, tlv.length);
119 		}
120 
121 		data += tlv.length;
122 	}
123 
124 }
125 
126 /* A null-terminated string of the form:
127  * TCP4 192.168.0.1 192.168.0.11 56324 443
128  *        src           dst       src  dst
129  */
parse_proxy_proto_header_v1(struct worker_st * ws,char * line)130 static int parse_proxy_proto_header_v1(struct worker_st *ws, char *line)
131 {
132 	int ret;
133 	char *next;
134 
135 	memset(&ws->remote_addr, 0, sizeof(ws->remote_addr));
136 	memset(&ws->our_addr, 0, sizeof(ws->our_addr));
137 
138 	if (strncmp(line, "TCP4 ", 5) == 0) {
139 		struct sockaddr_in *sa = (void*)&ws->remote_addr;
140 
141 		ws->our_addr_len = sizeof(struct sockaddr_in);
142 		ws->remote_addr_len = sizeof(struct sockaddr_in);
143 		sa->sin_family = AF_INET;
144 
145 		line += 5;
146 
147 		next = strchr(line, ' ');
148 		if (next == NULL) {
149 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header %s", line);
150 			return -1;
151 		}
152 
153 		*next = 0;
154 		ret = inet_pton(AF_INET, line, &sa->sin_addr);
155 		if (ret != 1) {
156 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header: %s", line);
157 			return -1;
158 		}
159 
160 
161 		sa = (void*)&ws->our_addr;
162 		sa->sin_family = AF_INET;
163 
164 		line = next+1;
165 		next = strchr(line, ' ');
166 		if (next == NULL) {
167 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header %s", line);
168 			return -1;
169 		}
170 
171 		*next = 0;
172 
173 		ret = inet_pton(AF_INET, line, &sa->sin_addr);
174 		if (ret != 1) {
175 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header %s", line);
176 			return -1;
177 		}
178 
179 		line = next+1;
180 
181 		sa = (void*)&ws->remote_addr;
182 		sa->sin_port = htons(atoi(line));
183 
184 		next = strchr(line, ' ');
185 		if (next == NULL) {
186 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header %s", line);
187 			return -1;
188 		}
189 
190 		line = next+1;
191 
192 		sa = (void*)&ws->our_addr;
193 		sa->sin_port = htons(atoi(line));
194 	} else if (strncmp(line, "TCP6 ", 5) == 0) {
195 		struct sockaddr_in6 *sa = (void*)&ws->remote_addr;
196 
197 		ws->our_addr_len = sizeof(struct sockaddr_in6);
198 		ws->remote_addr_len = sizeof(struct sockaddr_in6);
199 		sa->sin6_family = AF_INET6;
200 
201 		line += 5;
202 
203 		next = strchr(line, ' ');
204 		if (next == NULL) {
205 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header %s", line);
206 			return -1;
207 		}
208 
209 		*next = 0;
210 
211 		ret = inet_pton(AF_INET6, line, &sa->sin6_addr);
212 		if (ret != 1) {
213 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header %s", line);
214 			return -1;
215 		}
216 
217 		line = next+1;
218 		next = strchr(line, ' ');
219 		if (next == NULL) {
220 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header %s", line);
221 			return -1;
222 		}
223 
224 		*next = 0;
225 
226 		sa = (void*)&ws->our_addr;
227 		sa->sin6_family = AF_INET6;
228 
229 		ret = inet_pton(AF_INET6, line, &sa->sin6_addr);
230 		if (ret != 1) {
231 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header %s", line);
232 			return -1;
233 		}
234 
235 		line = next+1;
236 
237 		sa = (void*)&ws->remote_addr;
238 		sa->sin6_port = htons(atoi(line));
239 
240 		next = strchr(line, ' ');
241 		if (next == NULL) {
242 			oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header %s", line);
243 			return -1;
244 		}
245 
246 		line = next+1;
247 
248 		sa = (void*)&ws->our_addr;
249 		sa->sin6_port = htons(atoi(line));
250 	} else {
251 		oclog(ws, LOG_ERR, "proxy-hdr: unknown protocol: %s", line);
252 		return -1;
253 	}
254 
255 	return 0;
256 }
257 
258 #define PROXY_HEADER_V1 "PROXY "
259 #define PROXY_HEADER_V1_SIZE (sizeof(PROXY_HEADER_V1)-1)
260 #define MAX_PROXY_PROTO_V1_SIZE 108
261 
262 /* This parses a version 2 Proxy protocol header (from haproxy).
263  *
264  * When called from a UNIX socket (where we don't have any SSL
265  * info), we additionally read information about the SSL session.
266  * We expect to receive the peer's certificate verification status,
267  * and CN. That corresponds to send-proxy-v2-ssl-cn and send-proxy-v2-ssl
268  * haproxy config options.
269  *
270  * Returns -1 on error and zero on success.
271  */
parse_proxy_proto_header(struct worker_st * ws,int fd)272 int parse_proxy_proto_header(struct worker_st *ws, int fd)
273 {
274 	proxy_hdr_v2 hdr;
275 	size_t data_size;
276 	uint8_t cmd, family, proto;
277 	uint8_t ver;
278 	uint8_t *p;
279 	int ret;
280 
281 	ret = force_read_timeout(fd, &hdr, 16, DEFAULT_SOCKET_TIMEOUT);
282 	if (ret < 0) {
283 		oclog(ws, LOG_ERR,
284 		      "proxy-hdr: recv timed out");
285 		return -1;
286 	}
287 
288 	if (ret < 16) {
289 		oclog(ws, LOG_ERR, "proxy-hdr: header size less than 16");
290 		return -1;
291 	}
292 
293 	if (memcmp(hdr.sig, PROXY_HEADER_V1, PROXY_HEADER_V1_SIZE) == 0) {
294 		unsigned i;
295 
296 		/* recv all */
297 		oclog(ws, LOG_DEBUG, "proxy-hdr: detected v1 header");
298 		memcpy(hdr.data, &hdr, 16);
299 		for (i=0;i<MAX_PROXY_PROTO_V1_SIZE-16;i++) {
300 			ret = recv(fd, &hdr.data[16+i], 1, 0);
301 			if (ret != 1) {
302 				oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header");
303 				return -1;
304 			}
305 			if (hdr.data[16+i] == '\r') {
306 				hdr.data[16+i] = 0;
307 			} else if (hdr.data[16+i] == '\n') {
308 				if (hdr.data[16+i-1] == 0) {
309 					return parse_proxy_proto_header_v1(ws, (char*)hdr.data+PROXY_HEADER_V1_SIZE);
310 				} else {
311 					oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header: no carriage return");
312 					return -1;
313 				}
314 			}
315 		}
316 		oclog(ws, LOG_ERR, "proxy-hdr: error parsing v1 header");
317 		return -1;
318 	}
319 
320 	if (memcmp(hdr.sig, PROXY_HEADER_V2, PROXY_HEADER_V2_SIZE) != 0) {
321 		oclog(ws, LOG_ERR, "proxy-hdr: invalid v2 header");
322 		return -1;
323 	}
324 
325 	data_size = ntohs(hdr.len);
326 
327 	if (data_size > sizeof(hdr.data)) {
328 		oclog(ws, LOG_ERR, "proxy-hdr: too long v2 header size");
329 		return -1;
330 	}
331 
332 	ret = force_read_timeout(fd, hdr.data, data_size, DEFAULT_SOCKET_TIMEOUT);
333 	if (ret < 0) {
334 		oclog(ws, LOG_ERR,
335 		      "proxy-hdr: recv data timed out");
336 		return -1;
337 	}
338 
339 	cmd = hdr.ver_cmd & 0x0f;
340 	ver = (hdr.ver_cmd & 0xf0) >> 4;
341 	if (ver != 0x02) {
342 		oclog(ws, LOG_ERR, "proxy-hdr: unsupported version (%x), skipping message", (unsigned)ver);
343 		return 0;
344 	}
345 
346 	if (cmd != 0x01) {
347 		if (cmd == 0) {
348 			oclog(ws, LOG_DEBUG, "proxy-hdr: received health check command");
349 		} else {
350 			oclog(ws, LOG_ERR, "proxy-hdr: received unsupported command %x", (unsigned)cmd);
351 			return -1;
352 		}
353 	}
354 
355 	family = (hdr.family & 0xf0) >> 4;
356 	proto = hdr.family & 0x0f;
357 
358 	if (family != 0x1 && family != 0x2) {
359 		oclog(ws, LOG_ERR, "proxy-hdr: received unsupported family %x; skipping header", (unsigned)family);
360 		return 0;
361 	}
362 
363 	if ((proto != 0x1 && proto != 0x0)) {
364 		oclog(ws, LOG_ERR, "proxy-hdr: received unsupported protocol %x; skipping header", (unsigned)proto);
365 		return 0;
366 	}
367 
368 	p = hdr.data;
369 
370 	if (family == 0x01) { /* AF_INET */
371 		struct sockaddr_in *sa = (void*)&ws->remote_addr;
372 
373 		if (data_size < 12) {
374 			oclog(ws, LOG_INFO, "proxy-hdr: received not enough IPv4 data");
375 			return 0;
376 		}
377 
378 		memset(&ws->remote_addr, 0, sizeof(ws->remote_addr));
379 		sa->sin_family = AF_INET;
380 		memcpy(&sa->sin_port, p+8, 2);
381 		memcpy(&sa->sin_addr, p, 4);
382 		ws->remote_addr_len = sizeof(struct sockaddr_in);
383 
384 		memset(&ws->our_addr, 0, sizeof(ws->our_addr));
385 		sa = (void*)&ws->our_addr;
386 		sa->sin_family = AF_INET;
387 		memcpy(&sa->sin_addr, p+4, 4);
388 		memcpy(&sa->sin_port, p+10, 2);
389 		ws->our_addr_len = sizeof(struct sockaddr_in);
390 
391 		p += 12;
392 		data_size -= 12;
393 	} else if (family == 0x02) { /* AF_INET6 */
394 		struct sockaddr_in6 *sa = (void*)&ws->remote_addr;
395 
396 		if (data_size < 36) {
397 			oclog(ws, LOG_INFO, "proxy-hdr: did not receive enough IPv6 data");
398 			return 0;
399 		}
400 
401 		memset(&ws->remote_addr, 0, sizeof(ws->remote_addr));
402 		sa->sin6_family = AF_INET6;
403 		sa->sin6_port = 0;
404 		memcpy(&sa->sin6_addr, p, 16);
405 		memcpy(&sa->sin6_port, p+32, 2);
406 		ws->remote_addr_len = sizeof(struct sockaddr_in6);
407 
408 		memset(&ws->our_addr, 0, sizeof(ws->our_addr));
409 		sa->sin6_family = AF_INET6;
410 		sa = (void*)&ws->our_addr;
411 		memcpy(&sa->sin6_addr, p+16, 16);
412 		memcpy(&sa->sin6_port, p+34, 2);
413 		ws->our_addr_len = sizeof(struct sockaddr_in);
414 
415 		p += 36;
416 		data_size -= 36;
417 	}
418 
419 	/* Find CN if needed */
420 	if (ws->conn_type == SOCK_TYPE_UNIX && data_size > 0) {
421 		parse_ssl_tlvs(ws, p, data_size);
422 	}
423 
424 	return 0;
425 }
426