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