Files: 44f02ddf39a9d4c338dae207cfd1fb6bc6bfd1e0 / sslh.c
12275 bytesRaw
1 | /* |
2 | Reimplementation of sslh in C |
3 | |
4 | # Copyright (C) 2007-2008 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 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
42 | int allow_severity =0, deny_severity = 0; |
43 | |
44 | |
45 | |
46 | |
47 | |
48 | |
49 | |
50 | (res == -1) { \ |
51 | perror(str); \ |
52 | exit(1); \ |
53 | } |
54 | |
55 | |
56 | VERSION \ |
57 | \ |
58 | \ |
59 | \ |
60 | \ |
61 | \ |
62 | \ |
63 | \ |
64 | \ |
65 | \ |
66 | \ |
67 | |
68 | |
69 | int verbose = 0; /* That's really quite global */ |
70 | |
71 | /* Starts a listening socket on specified address. |
72 | Returns file descriptor |
73 | */ |
74 | int start_listen_socket(struct sockaddr *addr) |
75 | { |
76 | struct sockaddr_in *saddr = (struct sockaddr_in*)addr; |
77 | int sockfd, res, reuse; |
78 | |
79 | sockfd = socket(AF_INET, SOCK_STREAM, 0); |
80 | CHECK_RES_DIE(sockfd, "socket"); |
81 | |
82 | reuse = 1; |
83 | res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); |
84 | CHECK_RES_DIE(res, "setsockopt"); |
85 | |
86 | res = bind (sockfd, (struct sockaddr*)saddr, sizeof(*saddr)); |
87 | CHECK_RES_DIE(res, "bind"); |
88 | |
89 | res = listen (sockfd, 5); |
90 | CHECK_RES_DIE(res, "listen"); |
91 | |
92 | return sockfd; |
93 | } |
94 | |
95 | |
96 | /* |
97 | * moves data from one fd to other |
98 | * returns 0 if incoming socket closed, size moved otherwise |
99 | */ |
100 | int fd2fd(int target, int from) |
101 | { |
102 | char buffer[BUFSIZ]; |
103 | int size; |
104 | |
105 | size = read(from, buffer, sizeof(buffer)); |
106 | CHECK_RES_DIE(size, "read"); |
107 | |
108 | if (size == 0) |
109 | return 0; |
110 | |
111 | size = write(target, buffer, size); |
112 | CHECK_RES_DIE(size, "write"); |
113 | |
114 | return size; |
115 | } |
116 | |
117 | /* shovels data from one fd to the other and vice-versa |
118 | returns after one socket closed |
119 | */ |
120 | int shovel(int fd1, int fd2) |
121 | { |
122 | fd_set fds; |
123 | int res; |
124 | |
125 | FD_ZERO(&fds); |
126 | while (1) { |
127 | FD_SET(fd1, &fds); |
128 | FD_SET(fd2, &fds); |
129 | |
130 | res = select( |
131 | (fd1 > fd2 ? fd1 : fd2 ) + 1, |
132 | &fds, |
133 | NULL, |
134 | NULL, |
135 | NULL |
136 | ); |
137 | CHECK_RES_DIE(res, "select"); |
138 | |
139 | if (FD_ISSET(fd1, &fds)) { |
140 | res = fd2fd(fd2, fd1); |
141 | if (!res) { |
142 | if (verbose) fprintf(stderr, "client socket closed\n"); |
143 | return res; |
144 | } |
145 | } |
146 | |
147 | if (FD_ISSET(fd2, &fds)) { |
148 | res = fd2fd(fd1, fd2); |
149 | if (!res) { |
150 | if (verbose) fprintf(stderr, "server socket closed\n"); |
151 | return res; |
152 | } |
153 | } |
154 | } |
155 | } |
156 | |
157 | /* returns a string that prints the IP and port of the sockaddr */ |
158 | char* sprintaddr(char* buf, size_t size, struct sockaddr* s) |
159 | { |
160 | char addr_str[1024]; |
161 | |
162 | inet_ntop(AF_INET, &((struct sockaddr_in*)s)->sin_addr, addr_str, sizeof(addr_str)); |
163 | snprintf(buf, size, "%s:%d", addr_str, ntohs(((struct sockaddr_in*)s)->sin_port)); |
164 | return buf; |
165 | } |
166 | |
167 | /* turns a "hostname:port" string into a struct sockaddr; |
168 | sock: socket address to which to copy the addr |
169 | fullname: input string -- it gets clobbered |
170 | */ |
171 | void resolve_name(struct sockaddr *sock, char* fullname) { |
172 | struct addrinfo *addr, hint; |
173 | char *serv, *host; |
174 | int res; |
175 | |
176 | char *sep = strchr(fullname, ':'); |
177 | |
178 | if (!sep) /* No separator: parameter is just a port */ |
179 | { |
180 | serv = fullname; |
181 | fprintf(stderr, "names must be fully specified as hostname:port\n"); |
182 | exit(1); |
183 | } |
184 | else { |
185 | host = fullname; |
186 | serv = sep+1; |
187 | *sep = 0; |
188 | } |
189 | |
190 | memset(&hint, 0, sizeof(hint)); |
191 | hint.ai_family = PF_INET; |
192 | hint.ai_socktype = SOCK_STREAM; |
193 | |
194 | res = getaddrinfo(host, serv, &hint, &addr); |
195 | if (res) { |
196 | fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname); |
197 | if (res == EAI_SERVICE) |
198 | fprintf(stderr, "(Check you have specified all ports)\n"); |
199 | exit(1); |
200 | } |
201 | |
202 | memcpy(sock, addr->ai_addr, sizeof(*sock)); |
203 | } |
204 | |
205 | /* syslogs who connected to where */ |
206 | void log_connection(int socket, char* target) |
207 | { |
208 | struct sockaddr peeraddr; |
209 | socklen_t size = sizeof(peeraddr); |
210 | char buf[64]; |
211 | int res; |
212 | |
213 | res = getpeername(socket, &peeraddr, &size); |
214 | CHECK_RES_DIE(res, "getpeername"); |
215 | |
216 | syslog(LOG_INFO, "connection from %s forwarded to %s\n", |
217 | sprintaddr(buf, sizeof(buf), &peeraddr), target); |
218 | |
219 | } |
220 | |
221 | /* |
222 | * Settings that depend on the command line. That's less global than verbose * :-) |
223 | * They're set in main(), but also used in start_shoveler(), and it'd be heavy-handed |
224 | * to pass it all as parameters |
225 | */ |
226 | int timeout = 2; |
227 | struct sockaddr addr_listen; |
228 | struct sockaddr addr_ssl, addr_ssh; |
229 | |
230 | /* libwrap (tcpd): check the ssh connection is legal. This is necessary because |
231 | * the actual sshd will only see a connection coming from localhost and can't |
232 | * apply the rules itself. |
233 | */ |
234 | void check_access_rights(int in_socket) |
235 | { |
236 | |
237 | struct sockaddr peeraddr; |
238 | socklen_t size = sizeof(peeraddr); |
239 | char addr_str[1024]; |
240 | struct hostent *host; |
241 | struct in_addr addr; |
242 | int res; |
243 | |
244 | res = getpeername(in_socket, &peeraddr, &size); |
245 | CHECK_RES_DIE(res, "getpeername"); |
246 | inet_ntop(AF_INET, &((struct sockaddr_in*)&peeraddr)->sin_addr, addr_str, sizeof(addr_str)); |
247 | |
248 | addr.s_addr = inet_addr(addr_str); |
249 | host = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET); |
250 | |
251 | if (!hosts_ctl("sshd", (host ? host->h_name : STRING_UNKNOWN), addr_str, STRING_UNKNOWN)) { |
252 | if (verbose) |
253 | fprintf(stderr, "access denied\n"); |
254 | log_connection(in_socket, "access denied"); |
255 | close(in_socket); |
256 | exit(0); |
257 | } |
258 | |
259 | } |
260 | |
261 | /* Child process that finds out what to connect to and proxies |
262 | */ |
263 | void start_shoveler(int in_socket) |
264 | { |
265 | fd_set fds; |
266 | struct timeval tv; |
267 | struct sockaddr *saddr; |
268 | int res; |
269 | int out_socket; |
270 | char *target; |
271 | |
272 | FD_ZERO(&fds); |
273 | FD_SET(in_socket, &fds); |
274 | memset(&tv, 0, sizeof(tv)); |
275 | tv.tv_sec = timeout; |
276 | res = select(in_socket + 1, &fds, NULL, NULL, &tv); |
277 | if (res == -1) |
278 | perror("select"); |
279 | |
280 | /* Pick the target address depending on whether we timed out or not */ |
281 | if (FD_ISSET(in_socket, &fds)) { |
282 | /* The client wrote something to the socket: it's an SSL connection */ |
283 | saddr = &addr_ssl; |
284 | target = "SSL"; |
285 | } else { |
286 | /* The client hasn't written anything and we timed out: connect to SSH */ |
287 | saddr = &addr_ssh; |
288 | target = "SSH"; |
289 | |
290 | /* do hosts_access check if built with libwrap support */ |
291 | check_access_rights(in_socket); |
292 | } |
293 | |
294 | log_connection(in_socket, target); |
295 | |
296 | /* Connect the target socket */ |
297 | out_socket = socket(AF_INET, SOCK_STREAM, 0); |
298 | res = connect(out_socket, saddr, sizeof(addr_ssl)); |
299 | CHECK_RES_DIE(res, "connect"); |
300 | if (verbose) |
301 | fprintf(stderr, "connected to something\n"); |
302 | |
303 | shovel(in_socket, out_socket); |
304 | |
305 | close(in_socket); |
306 | close(out_socket); |
307 | |
308 | if (verbose) |
309 | fprintf(stderr, "connection closed down\n"); |
310 | |
311 | exit(0); |
312 | } |
313 | |
314 | void setup_signals(void) |
315 | { |
316 | int res; |
317 | struct sigaction action; |
318 | |
319 | /* Request no SIGCHLD is sent upon termination of |
320 | * the children */ |
321 | memset(&action, 0, sizeof(action)); |
322 | action.sa_handler = NULL; |
323 | action.sa_flags = SA_NOCLDWAIT; |
324 | res = sigaction(SIGCHLD, &action, NULL); |
325 | CHECK_RES_DIE(res, "sigaction"); |
326 | } |
327 | |
328 | /* Open syslog connection with appropriate banner; |
329 | * banner is made up of basename(bin_name)+"[pid]" */ |
330 | void setup_syslog(char* bin_name) { |
331 | char *name1, *name2; |
332 | |
333 | name1 = strdup(bin_name); |
334 | asprintf(&name2, "%s[%d]", basename(name1), getpid()); |
335 | openlog(name2, LOG_CONS, LOG_AUTH); |
336 | free(name1); |
337 | /* Don't free name2, as openlog(3) uses it (at least in glibc) */ |
338 | } |
339 | |
340 | /* We don't want to run as root -- drop priviledges if required */ |
341 | void drop_privileges(char* user_name) |
342 | { |
343 | int res; |
344 | struct passwd *pw = getpwnam(user_name); |
345 | if (!pw) { |
346 | fprintf(stderr, "%s: not found\n", user_name); |
347 | exit(1); |
348 | } |
349 | if (verbose) |
350 | fprintf(stderr, "turning into %s\n", user_name); |
351 | |
352 | res = setgid(pw->pw_gid); |
353 | CHECK_RES_DIE(res, "setgid"); |
354 | setuid(pw->pw_uid); |
355 | CHECK_RES_DIE(res, "setuid"); |
356 | } |
357 | |
358 | /* Writes my PID */ |
359 | void write_pid_file(char* pidfile) |
360 | { |
361 | FILE *f; |
362 | |
363 | f = fopen(pidfile, "w"); |
364 | if (!f) { |
365 | perror(pidfile); |
366 | exit(1); |
367 | } |
368 | |
369 | fprintf(f, "%d\n", getpid()); |
370 | fclose(f); |
371 | } |
372 | |
373 | void printsettings(void) |
374 | { |
375 | char buf[64]; |
376 | |
377 | fprintf( |
378 | stderr, |
379 | "SSL addr: %s (after timeout %ds)\n", |
380 | sprintaddr(buf, sizeof(buf), &addr_ssl), |
381 | timeout |
382 | ); |
383 | fprintf(stderr, "SSH addr: %s\n", sprintaddr(buf, sizeof(buf), &addr_ssh)); |
384 | fprintf(stderr, "listening on %s\n", sprintaddr(buf, sizeof(buf), &addr_listen)); |
385 | } |
386 | |
387 | int main(int argc, char *argv[]) |
388 | { |
389 | |
390 | extern char *optarg; |
391 | extern int optind; |
392 | int c, res; |
393 | |
394 | int in_socket, listen_socket; |
395 | |
396 | /* Init defaults */ |
397 | char *user_name = "nobody"; |
398 | char listen_str[] = "0.0.0.0:443"; |
399 | char ssl_str[] = "localhost:443"; |
400 | char ssh_str[] = "localhost:22"; |
401 | char *pid_file = "/var/run/sslh.pid"; |
402 | char inetd = 0; |
403 | |
404 | resolve_name(&addr_listen, listen_str); |
405 | resolve_name(&addr_ssl, ssl_str); |
406 | resolve_name(&addr_ssh, ssh_str); |
407 | |
408 | while ((c = getopt(argc, argv, "t:l:s:p:P:ivVu:")) != EOF) { |
409 | switch (c) { |
410 | |
411 | case 't': |
412 | timeout = atoi(optarg); |
413 | break; |
414 | |
415 | case 'p': |
416 | resolve_name(&addr_listen, optarg); |
417 | break; |
418 | |
419 | case 'l': |
420 | resolve_name(&addr_ssl, optarg); |
421 | break; |
422 | |
423 | case 's': |
424 | resolve_name(&addr_ssh, optarg); |
425 | break; |
426 | |
427 | case 'i': |
428 | inetd = 1; |
429 | break; |
430 | |
431 | case 'v': |
432 | verbose += 1; |
433 | break; |
434 | |
435 | case 'V': |
436 | printf("sslh %s\n", VERSION); |
437 | exit(0); |
438 | |
439 | case 'u': |
440 | user_name = optarg; |
441 | break; |
442 | |
443 | case 'P': |
444 | pid_file = optarg; |
445 | break; |
446 | |
447 | default: |
448 | fprintf(stderr, USAGE_STRING); |
449 | exit(2); |
450 | } |
451 | } |
452 | |
453 | if(inetd) |
454 | { |
455 | verbose = 0; |
456 | start_shoveler(0); |
457 | exit(0); |
458 | } |
459 | |
460 | if (verbose) |
461 | printsettings(); |
462 | |
463 | setup_signals(); |
464 | |
465 | listen_socket = start_listen_socket(&addr_listen); |
466 | |
467 | if (fork() > 0) exit(0); /* Detach */ |
468 | |
469 | write_pid_file(pid_file); |
470 | |
471 | drop_privileges(user_name); |
472 | |
473 | /* New session -- become group leader */ |
474 | res = setsid(); |
475 | CHECK_RES_DIE(res, "setsid: already process leader"); |
476 | |
477 | /* Open syslog connection */ |
478 | setup_syslog(argv[0]); |
479 | |
480 | /* Main server loop: accept connections, find what they are, fork shovelers */ |
481 | while (1) |
482 | { |
483 | in_socket = accept(listen_socket, 0, 0); |
484 | if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket); |
485 | |
486 | if (!fork()) |
487 | { |
488 | close(listen_socket); |
489 | start_shoveler(in_socket); |
490 | exit(0); |
491 | } |
492 | close(in_socket); |
493 | } |
494 | |
495 | return 0; |
496 | } |
497 | |
498 | |
499 |
Built with git-ssb-web