Mercurial > dropbear
diff svr-main.c @ 391:00fcf5045160
propagate from branch 'au.asn.ucc.matt.ltc.dropbear' (head c1db4398d56c56c6d06ae1e20c1e0d04dbb598ed)
to branch 'au.asn.ucc.matt.dropbear' (head d26d5eb2837f46b56a33fb0e7573aa0201abd4d5)
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Thu, 11 Jan 2007 04:29:08 +0000 |
parents | b66a00272a90 |
children | 1afa503e33f5 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svr-main.c Thu Jan 11 04:29:08 2007 +0000 @@ -0,0 +1,420 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002-2006 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "session.h" +#include "buffer.h" +#include "signkey.h" +#include "runopts.h" +#include "random.h" + +static size_t listensockets(int *sock, size_t sockcount, int *maxfd); +static void sigchld_handler(int dummy); +static void sigsegv_handler(int); +static void sigintterm_handler(int fish); +#ifdef INETD_MODE +static void main_inetd(); +#endif +#ifdef NON_INETD_MODE +static void main_noinetd(); +#endif +static void commonsetup(); + +#if defined(DBMULTI_dropbear) || !defined(DROPBEAR_MULTI) +#if defined(DBMULTI_dropbear) && defined(DROPBEAR_MULTI) +int dropbear_main(int argc, char ** argv) +#else +int main(int argc, char ** argv) +#endif +{ + _dropbear_exit = svr_dropbear_exit; + _dropbear_log = svr_dropbear_log; + + /* get commandline options */ + svr_getopts(argc, argv); + +#ifdef INETD_MODE + /* service program mode */ + if (svr_opts.inetdmode) { + main_inetd(); + /* notreached */ + } +#endif + +#ifdef NON_INETD_MODE + main_noinetd(); + /* notreached */ +#endif + + dropbear_exit("Compiled without normal mode, can't run without -i\n"); + return -1; +} +#endif + +#ifdef INETD_MODE +static void main_inetd() { + + struct sockaddr_storage remoteaddr; + socklen_t remoteaddrlen; + char * addrstring = NULL; + + /* Set up handlers, syslog, seed random */ + commonsetup(); + + remoteaddrlen = sizeof(remoteaddr); + if (getpeername(0, (struct sockaddr*)&remoteaddr, &remoteaddrlen) < 0) { + dropbear_exit("Unable to getpeername: %s", strerror(errno)); + } + + /* In case our inetd was lax in logging source addresses */ + addrstring = getaddrstring(&remoteaddr, 1); + dropbear_log(LOG_INFO, "Child connection from %s", addrstring); + + /* Don't check the return value - it may just fail since inetd has + * already done setsid() after forking (xinetd on Darwin appears to do + * this */ + setsid(); + + /* Start service program + * -1 is a dummy childpipe, just something we can close() without + * mattering. */ + svr_session(0, -1, getaddrhostname(&remoteaddr), addrstring); + + /* notreached */ +} +#endif /* INETD_MODE */ + +#ifdef NON_INETD_MODE +void main_noinetd() { + fd_set fds; + struct timeval seltimeout; + unsigned int i, j; + int val; + int maxsock = -1; + int listensocks[MAX_LISTEN_ADDR]; + size_t listensockcount = 0; + FILE *pidfile = NULL; + + int childpipes[MAX_UNAUTH_CLIENTS]; + char * preauth_addrs[MAX_UNAUTH_CLIENTS]; + + int childsock; + int childpipe[2]; + + // Note: commonsetup() must happen before we daemon()ise. Otherwise + // daemon() will chdir("/"), and we won't be able to find local-dir hostkeys. + commonsetup(); + + /* fork */ + if (svr_opts.forkbg) { + int closefds = 0; +#ifndef DEBUG_TRACE + if (!svr_opts.usingsyslog) { + closefds = 1; + } +#endif + if (daemon(0, closefds) < 0) { + dropbear_exit("Failed to daemonize: %s", strerror(errno)); + } + } + + /* should be done after syslog is working */ + if (svr_opts.forkbg) { + dropbear_log(LOG_INFO, "Running in background"); + } else { + dropbear_log(LOG_INFO, "Not forking"); + } + + /* create a PID file so that we can be killed easily */ + pidfile = fopen(svr_opts.pidfile, "w"); + if (pidfile) { + fprintf(pidfile, "%d\n", getpid()); + fclose(pidfile); + } + + /* sockets to identify pre-authenticated clients */ + for (i = 0; i < MAX_UNAUTH_CLIENTS; i++) { + childpipes[i] = -1; + } + bzero(preauth_addrs, sizeof(preauth_addrs)); + + /* Set up the listening sockets */ + listensockcount = listensockets(listensocks, MAX_LISTEN_ADDR, &maxsock); + if (listensockcount == 0) + { + dropbear_exit("No listening ports available."); + } + + /* incoming connection select loop */ + for(;;) { + + FD_ZERO(&fds); + + seltimeout.tv_sec = 60; + seltimeout.tv_usec = 0; + + /* listening sockets */ + for (i = 0; i < listensockcount; i++) { + FD_SET(listensocks[i], &fds); + } + + /* pre-authentication clients */ + for (i = 0; i < MAX_UNAUTH_CLIENTS; i++) { + if (childpipes[i] >= 0) { + FD_SET(childpipes[i], &fds); + maxsock = MAX(maxsock, childpipes[i]); + } + } + + val = select(maxsock+1, &fds, NULL, NULL, &seltimeout); + + if (exitflag) { + unlink(svr_opts.pidfile); + dropbear_exit("Terminated by signal"); + } + + if (val == 0) { + /* timeout reached */ + continue; + } + + if (val < 0) { + if (errno == EINTR) { + continue; + } + dropbear_exit("Listening socket error"); + } + + /* close fds which have been authed or closed - svr-auth.c handles + * closing the auth sockets on success */ + for (i = 0; i < MAX_UNAUTH_CLIENTS; i++) { + if (childpipes[i] >= 0 && FD_ISSET(childpipes[i], &fds)) { + m_close(childpipes[i]); + childpipes[i] = -1; + m_free(preauth_addrs[i]); + } + } + + /* handle each socket which has something to say */ + for (i = 0; i < listensockcount; i++) { + + struct sockaddr_storage remoteaddr; + socklen_t remoteaddrlen = 0; + size_t num_unauthed_for_addr = 0; + size_t num_unauthed_total = 0; + char * remote_addr_str = NULL; + pid_t fork_ret = 0; + size_t conn_idx = 0; + + if (!FD_ISSET(listensocks[i], &fds)) + continue; + + remoteaddrlen = sizeof(remoteaddr); + childsock = accept(listensocks[i], + (struct sockaddr*)&remoteaddr, &remoteaddrlen); + + if (childsock < 0) { + /* accept failed */ + continue; + } + + /* Limit the number of unauthenticated connections per IP */ + remote_addr_str = getaddrstring(&remoteaddr, 0); + + num_unauthed_for_addr = 0; + num_unauthed_total = 0; + for (j = 0; j < MAX_UNAUTH_CLIENTS; j++) { + if (childpipes[j] >= 0) { + num_unauthed_total++; + if (strcmp(remote_addr_str, preauth_addrs[j]) == 0) { + num_unauthed_for_addr++; + } + } else { + /* a free slot */ + conn_idx = j; + } + } + + if (num_unauthed_total >= MAX_UNAUTH_CLIENTS + || num_unauthed_for_addr >= MAX_UNAUTH_PER_IP) { + goto out; + } + + if (pipe(childpipe) < 0) { + TRACE(("error creating child pipe")) + goto out; + } + + fork_ret = fork(); + if (fork_ret < 0) { + dropbear_log(LOG_WARNING, "error forking: %s", strerror(errno)); + goto out; + + } else if (fork_ret > 0) { + + /* parent */ + childpipes[conn_idx] = childpipe[0]; + m_close(childpipe[1]); + preauth_addrs[conn_idx] = remote_addr_str; + remote_addr_str = NULL; + + } else { + + /* child */ + char * addrstring = NULL; +#ifdef DEBUG_FORKGPROF + extern void _start(void), etext(void); + monstartup((u_long)&_start, (u_long)&etext); +#endif /* DEBUG_FORKGPROF */ + + m_free(remote_addr_str); + addrstring = getaddrstring(&remoteaddr, 1); + dropbear_log(LOG_INFO, "Child connection from %s", addrstring); + + if (setsid() < 0) { + dropbear_exit("setsid: %s", strerror(errno)); + } + + /* make sure we close sockets */ + for (i = 0; i < listensockcount; i++) { + m_close(listensocks[i]); + } + + m_close(childpipe[0]); + + /* start the session */ + svr_session(childsock, childpipe[1], + getaddrhostname(&remoteaddr), + addrstring); + /* don't return */ + dropbear_assert(0); + } + +out: + /* This section is important for the parent too */ + m_close(childsock); + if (remote_addr_str) { + m_free(remote_addr_str); + } + } + } /* for(;;) loop */ + + /* don't reach here */ +} +#endif /* NON_INETD_MODE */ + + +/* catch + reap zombie children */ +static void sigchld_handler(int UNUSED(unused)) { + struct sigaction sa_chld; + + while(waitpid(-1, NULL, WNOHANG) > 0); + + sa_chld.sa_handler = sigchld_handler; + sa_chld.sa_flags = SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) { + dropbear_exit("signal() error"); + } +} + +/* catch any segvs */ +static void sigsegv_handler(int UNUSED(unused)) { + fprintf(stderr, "Aiee, segfault! You should probably report " + "this as a bug to the developer\n"); + exit(EXIT_FAILURE); +} + +/* catch ctrl-c or sigterm */ +static void sigintterm_handler(int UNUSED(unused)) { + + exitflag = 1; +} + +/* Things used by inetd and non-inetd modes */ +static void commonsetup() { + + struct sigaction sa_chld; +#ifndef DISABLE_SYSLOG + if (svr_opts.usingsyslog) { + startsyslog(); + } +#endif + + /* set up cleanup handler */ + if (signal(SIGINT, sigintterm_handler) == SIG_ERR || +#ifndef DEBUG_VALGRIND + signal(SIGTERM, sigintterm_handler) == SIG_ERR || +#endif + signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + dropbear_exit("signal() error"); + } + + /* catch and reap zombie children */ + sa_chld.sa_handler = sigchld_handler; + sa_chld.sa_flags = SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) { + dropbear_exit("signal() error"); + } + if (signal(SIGSEGV, sigsegv_handler) == SIG_ERR) { + dropbear_exit("signal() error"); + } + + /* Now we can setup the hostkeys - needs to be after logging is on, + * otherwise we might end up blatting error messages to the socket */ + loadhostkeys(); + + seedrandom(); +} + +/* Set up listening sockets for all the requested ports */ +static size_t listensockets(int *sock, size_t sockcount, int *maxfd) { + + unsigned int i; + char* errstring = NULL; + size_t sockpos = 0; + int nsock; + + TRACE(("listensockets: %d to try\n", svr_opts.portcount)) + + for (i = 0; i < svr_opts.portcount; i++) { + + TRACE(("listening on '%s'", svr_opts.ports[i])) + + nsock = dropbear_listen("", svr_opts.ports[i], &sock[sockpos], + sockcount - sockpos, + &errstring, maxfd); + + if (nsock < 0) { + dropbear_log(LOG_WARNING, "Failed listening on '%s': %s", + svr_opts.ports[i], errstring); + m_free(errstring); + continue; + } + + sockpos += nsock; + + } + return sockpos; +}