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