Files: ae008179f033c8409c69b13787a539351bace626 / sslh-select.c
11312 bytesRaw
1 | /* |
2 | sslh: a SSL/SSH multiplexer |
3 | |
4 | # Copyright (C) 2007-2010 Yves Rutschle |
5 | # |
6 | # This program is free software; you can redistribute it |
7 | # and/or modify it under the terms of the GNU General Public |
8 | # License as published by the Free Software Foundation; either |
9 | # version 2 of the License, or (at your option) any later |
10 | # version. |
11 | # |
12 | # This program is distributed in the hope that it will be |
13 | # useful, but WITHOUT ANY WARRANTY; without even the implied |
14 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
15 | # PURPOSE. See the GNU General Public License for more |
16 | # details. |
17 | # |
18 | # The full text for the General Public License is here: |
19 | # http://www.gnu.org/licenses/gpl.html |
20 | |
21 | */ |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | const char* server_type = "sslh-select"; |
28 | |
29 | /* cnx_num_alloc is the number of connection to allocate at once (at start-up, |
30 | * and then every time we get too many simultaneous connections: e.g. start |
31 | * with 100 slots, then if we get more than 100 connections allocate another |
32 | * 100 slots, and so on). We never free up connection structures. We try to |
33 | * allocate as many structures at once as will fit in one page (which is 102 |
34 | * in sslh 1.9 on Linux on x86) |
35 | */ |
36 | static long cnx_num_alloc; |
37 | |
38 | /* Make the file descriptor non-block */ |
39 | int set_nonblock(int fd) |
40 | { |
41 | int flags; |
42 | |
43 | flags = fcntl(fd, F_GETFL); |
44 | CHECK_RES_RETURN(flags, "fcntl"); |
45 | |
46 | flags |= O_NONBLOCK; |
47 | |
48 | flags = fcntl(fd, F_SETFL, flags); |
49 | CHECK_RES_RETURN(flags, "fcntl"); |
50 | |
51 | return flags; |
52 | } |
53 | |
54 | int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2) |
55 | { |
56 | int i; |
57 | |
58 | for (i = 0; i < 2; i++) { |
59 | if (cnx->q[i].fd != -1) { |
60 | if (verbose) |
61 | fprintf(stderr, "closing fd %d\n", cnx->q[i].fd); |
62 | |
63 | close(cnx->q[i].fd); |
64 | FD_CLR(cnx->q[i].fd, fds); |
65 | FD_CLR(cnx->q[i].fd, fds2); |
66 | if (cnx->q[i].defered_data) |
67 | free(cnx->q[i].defered_data); |
68 | } |
69 | } |
70 | init_cnx(cnx); |
71 | return 0; |
72 | } |
73 | |
74 | /* Accepts a connection from the main socket and assigns it to an empty slot. |
75 | * If no slots are available, allocate another few. If that fails, drop the |
76 | * connexion */ |
77 | int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_size) |
78 | { |
79 | int in_socket, free, i, res; |
80 | struct connection *new; |
81 | |
82 | in_socket = accept(listen_socket, 0, 0); |
83 | CHECK_RES_RETURN(in_socket, "accept"); |
84 | |
85 | res = set_nonblock(in_socket); |
86 | if (res == -1) return -1; |
87 | |
88 | /* Find an empty slot */ |
89 | for (free = 0; (free < *cnx_size) && ((*cnx)[free].q[0].fd != -1); free++) { |
90 | /* nothing */ |
91 | } |
92 | if (free >= *cnx_size) { |
93 | if (verbose) |
94 | fprintf(stderr, "buying more slots from the slot machine.\n"); |
95 | new = realloc(*cnx, (*cnx_size + cnx_num_alloc) * sizeof((*cnx)[0])); |
96 | if (!new) { |
97 | log_message(LOG_ERR, "unable to realloc -- dropping connection\n"); |
98 | return -1; |
99 | } |
100 | *cnx = new; |
101 | *cnx_size += cnx_num_alloc; |
102 | for (i = free; i < *cnx_size; i++) { |
103 | init_cnx(&(*cnx)[i]); |
104 | } |
105 | } |
106 | (*cnx)[free].q[0].fd = in_socket; |
107 | (*cnx)[free].state = ST_PROBING; |
108 | (*cnx)[free].probe_timeout = time(NULL) + probing_timeout; |
109 | |
110 | if (verbose) |
111 | fprintf(stderr, "accepted fd %d on slot %d\n", in_socket, free); |
112 | |
113 | return in_socket; |
114 | } |
115 | |
116 | |
117 | /* Connect queue 1 of connection to SSL; returns new file descriptor */ |
118 | int connect_queue(struct connection *cnx, struct addrinfo *addr, |
119 | char* cnx_name, |
120 | fd_set *fds_r, fd_set *fds_w) |
121 | { |
122 | struct queue *q = &cnx->q[1]; |
123 | |
124 | q->fd = connect_addr(addr, cnx_name); |
125 | if (q->fd != -1) { |
126 | log_connection(cnx); |
127 | set_nonblock(q->fd); |
128 | flush_defered(q); |
129 | if (q->defered_data) { |
130 | FD_SET(q->fd, fds_w); |
131 | } else { |
132 | FD_SET(q->fd, fds_r); |
133 | } |
134 | return q->fd; |
135 | } else { |
136 | tidy_connection(cnx, fds_r, fds_w); |
137 | return -1; |
138 | } |
139 | } |
140 | |
141 | /* shovels data from active fd to the other |
142 | returns after one socket closed or operation would block |
143 | */ |
144 | void shovel(struct connection *cnx, int active_fd, |
145 | fd_set *fds_r, fd_set *fds_w) |
146 | { |
147 | struct queue *read_q, *write_q; |
148 | |
149 | read_q = &cnx->q[active_fd]; |
150 | write_q = &cnx->q[1-active_fd]; |
151 | |
152 | if (verbose) |
153 | fprintf(stderr, "activity on fd%d\n", read_q->fd); |
154 | |
155 | switch(fd2fd(write_q, read_q)) { |
156 | case -1: |
157 | case FD_CNXCLOSED: |
158 | tidy_connection(cnx, fds_r, fds_w); |
159 | break; |
160 | |
161 | case FD_STALLED: |
162 | FD_SET(write_q->fd, fds_w); |
163 | FD_CLR(read_q->fd, fds_r); |
164 | break; |
165 | |
166 | default: /* Nothing */ |
167 | break; |
168 | } |
169 | } |
170 | |
171 | /* returns true if specified fd is initialised and present in fd_set */ |
172 | int is_fd_active(int fd, fd_set* set) |
173 | { |
174 | if (fd == -1) return 0; |
175 | return FD_ISSET(fd, set); |
176 | } |
177 | |
178 | /* Main loop: the idea is as follow: |
179 | * - fds_r and fds_w contain the file descritors to monitor in read and write |
180 | * - When a file descriptor goes off, process it: read from it, write the data |
181 | * to its corresponding pair. |
182 | * - When a file descriptor blocks when writing, remove the read fd from fds_r, |
183 | * move the data to a defered buffer, and add the write fd to fds_w. Defered |
184 | * buffer is allocated dynamically. |
185 | * - When we can write to a file descriptor that has defered data, we try to |
186 | * write as much as we can. Once all data is written, remove the fd from fds_w |
187 | * and add its corresponding pair to fds_r, free the buffer. |
188 | * |
189 | * That way, each pair of file descriptor (read from one, write to the other) |
190 | * is monitored either for read or for write, but never for both. |
191 | */ |
192 | void main_loop(int listen_sockets[], int num_addr_listen) |
193 | { |
194 | fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */ |
195 | fd_set readfds, writefds; /* working read and write fd sets */ |
196 | struct timeval tv; |
197 | int max_fd, in_socket, i, j, res; |
198 | struct connection *cnx; |
199 | T_PROTO_ID prot; |
200 | int num_cnx; /* Number of connections in *cnx */ |
201 | int num_probing = 0; /* Number of connections currently probing |
202 | * We use this to know if we need to time out of |
203 | * select() */ |
204 | |
205 | FD_ZERO(&fds_r); |
206 | FD_ZERO(&fds_w); |
207 | |
208 | for (i = 0; i < num_addr_listen; i++) { |
209 | FD_SET(listen_sockets[i], &fds_r); |
210 | set_nonblock(listen_sockets[i]); |
211 | } |
212 | max_fd = listen_sockets[num_addr_listen-1] + 1; |
213 | |
214 | cnx_num_alloc = getpagesize() / sizeof(struct connection); |
215 | |
216 | num_cnx = cnx_num_alloc; /* Start with a set pool of slots */ |
217 | cnx = malloc(num_cnx * sizeof(struct connection)); |
218 | for (i = 0; i < num_cnx; i++) |
219 | init_cnx(&cnx[i]); |
220 | |
221 | while (1) |
222 | { |
223 | memset(&tv, 0, sizeof(tv)); |
224 | tv.tv_sec = probing_timeout; |
225 | |
226 | memcpy(&readfds, &fds_r, sizeof(readfds)); |
227 | memcpy(&writefds, &fds_w, sizeof(writefds)); |
228 | |
229 | if (verbose) |
230 | fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n", max_fd, num_probing); |
231 | res = select(max_fd, &readfds, &writefds, NULL, num_probing ? &tv : NULL); |
232 | if (res < 0) |
233 | perror("select"); |
234 | |
235 | |
236 | /* Check main socket for new connections */ |
237 | for (i = 0; i < num_addr_listen; i++) { |
238 | if (FD_ISSET(listen_sockets[i], &readfds)) { |
239 | in_socket = accept_new_connection(listen_sockets[i], &cnx, &num_cnx); |
240 | num_probing++; |
241 | |
242 | if (in_socket > 0) { |
243 | FD_SET(in_socket, &fds_r); |
244 | if (in_socket >= max_fd) |
245 | max_fd = in_socket + 1;; |
246 | } |
247 | FD_CLR(listen_sockets[i], &readfds); |
248 | } |
249 | } |
250 | |
251 | /* Check all sockets for write activity */ |
252 | for (i = 0; i < num_cnx; i++) { |
253 | if (cnx[i].q[0].fd != -1) { |
254 | for (j = 0; j < 2; j++) { |
255 | if (is_fd_active(cnx[i].q[j].fd, &writefds)) { |
256 | res = flush_defered(&cnx[i].q[j]); |
257 | if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) { |
258 | if (cnx[i].state == ST_PROBING) num_probing--; |
259 | tidy_connection(&cnx[i], &fds_r, &fds_w); |
260 | if (verbose) |
261 | fprintf(stderr, "closed slot %d\n", i); |
262 | } |
263 | /* If no defered data is left, stop monitoring the fd |
264 | * for write, and restart monitoring the other one for reads*/ |
265 | if (!cnx[i].q[j].defered_data_size) { |
266 | FD_CLR(cnx[i].q[j].fd, &fds_w); |
267 | FD_SET(cnx[i].q[1-j].fd, &fds_r); |
268 | } |
269 | } |
270 | } |
271 | } |
272 | } |
273 | |
274 | /* Check all sockets for read activity */ |
275 | for (i = 0; i < num_cnx; i++) { |
276 | for (j = 0; j < 2; j++) { |
277 | if (is_fd_active(cnx[i].q[j].fd, &readfds) || |
278 | ((cnx[i].state == ST_PROBING) && (cnx[i].probe_timeout < time(NULL)))) { |
279 | if (verbose) |
280 | fprintf(stderr, "processing fd%d slot %d\n", j, i); |
281 | |
282 | switch (cnx[i].state) { |
283 | |
284 | case ST_PROBING: |
285 | if (j == 1) { |
286 | fprintf(stderr, "Activity on fd2 while probing, impossible\n"); |
287 | dump_connection(&cnx[i]); |
288 | exit(1); |
289 | } |
290 | num_probing--; |
291 | cnx[i].state = ST_SHOVELING; |
292 | |
293 | /* If timed out it's SSH, otherwise the client sent |
294 | * data so probe the protocol */ |
295 | if ((cnx[i].probe_timeout < time(NULL))) { |
296 | prot = 0; |
297 | } else { |
298 | prot = probe_client_protocol(&cnx[i]); |
299 | } |
300 | |
301 | /* libwrap check if required for this protocol */ |
302 | if (protocols[prot].service && |
303 | check_access_rights(in_socket, protocols[prot].service)) { |
304 | tidy_connection(&cnx[i], &fds_r, &fds_w); |
305 | res = -1; |
306 | } else { |
307 | res = connect_queue(&cnx[i], |
308 | &protocols[prot].saddr, |
309 | protocols[prot].description, |
310 | &fds_r, &fds_w); |
311 | } |
312 | |
313 | if (res >= max_fd) |
314 | max_fd = res + 1;; |
315 | break; |
316 | |
317 | case ST_SHOVELING: |
318 | shovel(&cnx[i], j, &fds_r, &fds_w); |
319 | break; |
320 | |
321 | default: /* illegal */ |
322 | log_message(LOG_ERR, "Illegal connection state %d\n", cnx[i].state); |
323 | exit(1); |
324 | } |
325 | } |
326 | } |
327 | } |
328 | } |
329 | } |
330 | |
331 | |
332 | void start_shoveler(int listen_socket) { |
333 | fprintf(stderr, "inetd mode is not supported in select mode\n"); |
334 | exit(1); |
335 | } |
336 | |
337 | |
338 | /* The actual main is in common.c: it's the same for both version of |
339 | * the server |
340 | */ |
341 | |
342 | |
343 |
Built with git-ssb-web