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