/* * slush SSL remote shell * Copyright (c) 1999 Damien Miller * All Rights Reserved * * Based on stunnel-2.1: * Copyright (c) 1998 Michal Trojnara * All Rights Reserved * Author: Michal Trojnara * SSL support: Adam Hernik * Pawel Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #define BUFFSIZE 8192 /* I/O buffer size */ #define HOSTNAME_SIZE 256 #include "config.h" /* General headers */ #include #include /* errno */ #include /* stat */ #include /* signal */ #include /* openlog, syslog */ #include /* strerror */ #include #include #include #include #if __FreeBSD_version >= 500000 #include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include /* fork, execvp, exit */ #endif /* Networking headers */ #include /* struct sockaddr_in */ #include /* getpeername */ #include /* inet_ntoa */ #include /* select */ #ifdef HAVE_SYS_SELECT_H #include /* for aix */ #endif #ifndef _PATH_WTMPLOCK #define _PATH_WTMPLOCK "/etc/wtmplock" #endif /* _PATH_WTMPLOCK */ #include "common.h" /* SSL headers */ #include #include #include #include #include /* libwrap header */ #if HAVE_TCPD_H && HAVE_LIBWRAP #include #define USE_LIBWRAP 1 #endif /* Prototypes */ static char *find_certificate(char *certfile); void server(struct sockaddr_in *addr, SSL_CTX *ctx); void transfer(SSL *, int); void ioerror(char *); void sslerror(char *); void signal_handler(int); void read_control_strings(SSL *ssl); int process_control_word(const char *token, const char *value); void parse_control_string(char *buffer); void child(int slave, struct passwd *pw); void log_uwtmp(struct passwd *pw, struct in_addr *i, char *tty, int is_logout); void authenticate(SSL *ssl, char *username); int check_auth(char *username, char *subject_name, char *issuer_name); int lookup_cert(char *authfile, char *oneline_name); #ifdef USE_LIBWRAP int allow_severity = LOG_INFO; int deny_severity = LOG_WARNING; #endif /* USE_LIBWRAP */ static int child_alive; static struct winsize *w = NULL; static char *username = NULL; static char *hostname; #ifdef HAVE_GETOPT_LONG static struct option long_options[] = { {"certificate", 1, NULL, 'C'}, {"verify", 1, NULL, 'v'}, {NULL, 0, NULL, 0} }; #endif /* HAVE_GETOPT_LONG */ int main(int argc, char* argv[]) { struct sockaddr_in addr; int addrlen; SSL_CTX *ctx; char error[256]; char certfile[128]; char *certpath; int c; int verify = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; struct hostent *h; #ifdef USE_LIBWRAP struct request_info request; #endif /* USE_LIBWRAP */ /* Blank certificate file */ certfile[0] = '\0'; /* Start up logging */ openlog("slushd", LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); /* Parse command-line options */ while(1) { #ifdef HAVE_GETOPT_LONG c = getopt_long (argc, argv, "C:V:", long_options, NULL); #else /* HAVE_GETOPT_LONG */ c = getopt(argc, argv, "C:V:"); #endif /* HAVE_GETOPT_LONG */ if (c == -1) break; switch(c) { case 'C': strncpy(certfile, optarg, sizeof(certfile) - 1); certfile[sizeof(certfile) - 1] = '\0'; break; case 'v': verify = atoi(optarg); if (verify == 0) { syslog(LOG_ERR, "Invalid verification level."); exit(1); } break; default: syslog(LOG_ERR, "Invalid commandline options"); exit(1); } } signal(SIGPIPE, SIG_IGN); /* avoid 'broken pipe' signal */ signal(SIGTERM, signal_handler); signal(SIGQUIT, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGCHLD, signal_handler); /* avoid zombie */ /* check if started from inetd */ addrlen = sizeof(addr); memset(&addr, 0, addrlen); if(getpeername(0, (struct sockaddr *)&addr, &addrlen)) { fprintf(stderr, "Error: must be run from inetd or similar\n"); exit(1); } h = gethostbyaddr((char*)&(addr.sin_addr), sizeof(addr.sin_addr), AF_INET); if (h == NULL) hostname = NULL; else hostname = strdup(h->h_name); #ifdef USE_LIBWRAP /* Check host access */ request_init(&request, RQ_DAEMON, "slushd", RQ_FILE, STDIN_FILENO, 0); fromhost(&request); if (!hosts_access(&request)) { syslog(LOG_ERR, "Connection refused from %s:%d", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); exit(2); } #endif init_ssl(); certpath = find_certificate(certfile); ctx = setup_ssl_context(certpath, certpath, verify, SERVER, error, sizeof(error)); if (ctx == NULL) { syslog(LOG_ERR, "%s", error); exit(1); } server(&addr, ctx); /* close SSL */ SSL_CTX_free(ctx); return(0); } /* Finds the servers certificate */ static char *find_certificate(char *certfile) { static char certpath[256]; struct stat st; char *name; /* If certificate file not specified, use default */ if (certfile[0] == '\0') strcpy(certfile, DEFAULT_SERVER_CERT); /* If the user has specified a filename only, prepend default cert path */ name = strrchr(certfile, '/'); if (name != NULL) strncpy(certpath, certfile, sizeof(certpath) - 1); else snprintf(certpath, sizeof(certpath), "%s/%s", X509_get_default_cert_dir(), certfile); certpath[sizeof(certpath) - 1] = '\0'; /* Check existance and perms on certificate file */ if(stat(certpath, &st)) ioerror("stat"); if(st.st_mode & 7) syslog(LOG_WARNING, "WARNING: Wrong permissions on %s", certpath); return(certpath); } /* Prepares SSL connection, forks child and prepares for transfer */ void server(struct sockaddr_in *addr, SSL_CTX *ctx) { SSL *ssl; int master; int slave; char *pty_slave; char error[256]; struct passwd *pw; /* Initialise SSL */ ssl = SSL_new(ctx); SSL_set_fd(ssl, STDIN_FILENO); if (SSL_accept(ssl) <= 0) sslerror("SSL_accept"); if (hostname == NULL) { syslog(LOG_NOTICE, "Connected from %s:%d using %s (%s)", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), SSL_get_version(ssl), SSL_get_cipher(ssl)); } else { syslog(LOG_NOTICE, "Connected from %s(%s):%d using %s (%s)", hostname, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), SSL_get_version(ssl), SSL_get_cipher(ssl)); } /* Read and set control parameters */ read_control_strings(ssl); if ((username == NULL) || (username[0] == '\0')) { syslog(LOG_ERR, "Username not specified"); exit(3); } pw = getpwnam(username); if (pw == NULL) { syslog(LOG_ERR, "Could not find user %s: %m", username); exit(3); } /* Check authentication */ authenticate(ssl, username); /* Find and open master pty */ master = get_pty_master(&pty_slave, error, sizeof(error)); if (master == -1) { syslog(LOG_ERR, "%s", error); exit(3); } /* Find and open slave pty */ slave = get_pty_slave(pty_slave, pw->pw_uid, error, sizeof(error)); if (slave == -1) { syslog(LOG_ERR, "%s", error); exit(3); } /* Record the login event */ log_uwtmp(pw, &(addr->sin_addr), pty_slave, 0); child_alive = 1; switch(fork()) { case -1: /* error */ ioerror("fork"); case 0: /* child */ syslog(LOG_NOTICE, "LOGIN BY %s ON %s", pw->pw_name, pty_slave + 5); close(master); child(slave, pw); } close(slave); transfer(ssl, master); /* Record the logout event */ log_uwtmp(pw, &(addr->sin_addr), pty_slave, 1); SSL_free(ssl); } /* Processing performed by child */ void child(int slave, struct passwd *pw) { char *p; char shell[256]; char mailpath[1024]; /* Assume privs of the requested user */ if (initgroups(pw->pw_name, pw->pw_gid) == -1) ioerror("initgroups"); if (setgid(pw->pw_gid) == -1) ioerror("setgid"); if (setuid(pw->pw_uid) == -1) ioerror("setuid"); /* Set up environment */ snprintf(mailpath, sizeof(mailpath), "%s/%s", _PATH_MAILDIR, pw->pw_name); setenv("MAIL", mailpath, 1); setenv("HOME", pw->pw_dir, 1); setenv("SHELL", pw->pw_shell, 1); if(pw->pw_uid) setenv("PATH", _PATH_DEFPATH, 0); else setenv("PATH", _PATH_STDPATH, 0); /* If requested, set the terminal window size */ if (w != NULL) { if (ioctl(slave, TIOCSWINSZ, w) == -1) ioerror("ioctl"); } /* Reset signal handlers */ signal(SIGPIPE, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGSEGV, SIG_DFL); signal(SIGCHLD, SIG_DFL); if (setsid() < 0) ioerror("setsid"); #if defined(TIOCSCTTY) && !defined(CIBAUD) if (ioctl(slave, TIOCSCTTY, NULL) < 0) ioerror("TIOCSCTTY"); #endif dup2(slave, STDIN_FILENO); dup2(slave, STDOUT_FILENO); dup2(slave, STDERR_FILENO); close(slave); if (chdir(pw->pw_dir) == -1) { syslog(LOG_WARNING, "Couldn't change directory to %s: %m", pw->pw_dir); chdir("/"); } p = strrchr(pw->pw_shell, '/'); if (p == NULL) p = pw->pw_shell; snprintf(shell, sizeof(shell), "-%s", p); execl(pw->pw_shell, shell, "-", NULL); ioerror("execvp"); /* execvp failed */ } /* Main transfer loop - transfers bytes between child and SSL peer */ /* until one or both exit */ void transfer(SSL *ssl, int tunnel) /* transfer data */ { fd_set rin, rout; int num, fdno, fd_ssl, bytes_in=0, bytes_out=0; char buffer[BUFFSIZE]; fd_ssl = SSL_get_fd(ssl); FD_ZERO(&rin); FD_SET(fd_ssl, &rin); FD_SET(tunnel, &rin); fdno = (fd_ssl>tunnel ? fd_ssl : tunnel) + 1; while(child_alive) { rout = rin; if(select(fdno, &rout, NULL, NULL, NULL)<0) ioerror("select"); if(FD_ISSET(tunnel, &rout)) { num = read(tunnel, buffer, BUFFSIZE); if(num == 0) break; /* close */ if (num < 0) { switch(errno) { case ECONNRESET: syslog(LOG_INFO, "IPC reset (child died)"); child_alive = 0; break; case EIO: child_alive = 0; break; default: ioerror("read"); break; } } else { if(SSL_write(ssl, buffer, num)!=num) sslerror("SSL_write"); bytes_out += num; } } if(FD_ISSET(fd_ssl, &rout)) { num = SSL_read(ssl, buffer, BUFFSIZE); if(num < 0) sslerror("SSL_read"); if(num == 0) break; /* close */ if(write(tunnel, buffer, num)!=num) ioerror("write"); bytes_in+=num; } } syslog(LOG_INFO, "Connection closed: %d bytes in, %d bytes out", bytes_in, bytes_out); } /* Print system errors */ void ioerror(char *fun) { syslog(LOG_ERR, "%s: %m", fun); exit(1); } /* Print ssl errors and exit */ void sslerror(char *fun) { char string[120]; ERR_error_string(ERR_get_error(), string); syslog(LOG_ERR, "%s: %s", fun, string); exit(2); } /* Signal handler :) */ void signal_handler(int sig) /* Signal handler */ { if (sig == SIGCHLD) { child_alive = 0; signal(sig, signal_handler); } else { syslog(LOG_ERR, "Received signal %d; terminating.", sig); exit(3); } } /* Reads and acts upon control strings received over SSL connection */ /* Exits on error */ void read_control_strings(SSL *ssl) { char buffer[BUFFSIZE]; int bytes_read; char *p; int offset; /* Read all the control strings up to "END\n" */ p = buffer; memset(buffer, 0, sizeof(buffer)); offset = 0; while (1) { /* Read upto buffsize - 1 bytes */ bytes_read = SSL_read(ssl, buffer + offset, BUFFSIZE - offset - 1); if (bytes_read < 0) sslerror("SSL_read"); if (bytes_read == 0) break; offset += bytes_read; if (offset >= (BUFFSIZE - 1)) { syslog(LOG_ERR, "Control strings too long."); exit(3); } buffer[offset] = '\0'; p = strstr(buffer, "END\n"); if (p != NULL) break; } parse_control_string(buffer); } /* Splits the control string into token/value pairs, validates them */ /* and performs the appropriate action */ /* exits on error */ void parse_control_string(char *token) { char *value; char *p; int offset; /* Process each string */ while (1) { /* Find end of this control word */ p = strchr(token, '\n'); if (p == NULL) { syslog(LOG_ERR, "Bad control word: %s", token); exit(3); } /* nul terminate word */ *p = '\0'; /* Advance p to start of next token */ p++; offset = strspn(token, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); value = strdup(token + offset); *(token + offset) = '\0'; if (process_control_word(token, value)) break; token = p; } } /* Processes a single control token/value and performs appropriate */ /* action. */ /* Returns 1 if control word was 'END', 0 otherwise */ int process_control_word(const char *token, const char *value) { if ((token[0] == '\0') || (strcmp(token, "END") == 0)) return(1); switch (value[0]) { case '=': /* Env var */ setenv(token, value + 1, 1); return(0); case ':': /* Control string */ break; default: syslog(LOG_ERR, "Unsupported control string: %s%s", token, value); exit(3); } if (strcmp(token, "WINSIZE") == 0) { if (w == NULL) { w = malloc(sizeof(*w)); if (w == NULL) { syslog(LOG_ERR, "Out of memory."); exit(3); } } if (sscanf(value + 1, "%hi %hi %hi %hi", &(w->ws_row), &(w->ws_col), &(w->ws_xpixel), &(w->ws_ypixel)) != 4) { syslog(LOG_ERR, "Invalid window size: %s%s", token, value); exit(3); } return(0); } if (strcmp(token, "USER") == 0) { username = strdup(value + 1); return(0); } syslog(LOG_ERR, "Unrecognised control word: %s%s", token, value); exit(3); } /* Records login in utmp file */ /* exits on error */ void log_uwtmp(struct passwd *pw, struct in_addr *i, char *tty, int is_logout) { struct utmp ut; tty = strrchr(tty, '/'); if (tty == NULL) { syslog(LOG_ERR, "Can't determine basename of tty"); return; } tty++; if(is_logout) { logout(tty); return; } strncpy(ut.ut_line, tty, sizeof(ut.ut_line) - 1); strncpy(ut.ut_name, pw->pw_name, sizeof(ut.ut_name)-1); strncpy(ut.ut_host, inet_ntoa(*i), sizeof(ut.ut_host) - 1); ut.ut_time = time(0); login(&ut); return; } /* Retrieve client X509 certificate and test authentication */ /* exits on auth failure */ void authenticate(SSL *ssl, char *username) { X509 *client_cert; X509_NAME *subject; X509_NAME *issuer; char subject_name[1024]; char issuer_name[1024]; /* Retrieve the certificate that the client presented */ client_cert = SSL_get_peer_certificate(ssl); if (client_cert == NULL) sslerror("SSL_get_certificate"); /* Get the names of the client and the client's CA */ issuer = X509_get_issuer_name(client_cert); subject = X509_get_subject_name(client_cert); if (subject == NULL) sslerror("X509_get_issuer_name(subject)"); if (issuer == NULL) sslerror("X509_get_issuer_name(issuer)"); /* Convert them to text */ X509_NAME_oneline(subject, subject_name, sizeof(subject_name)); X509_NAME_oneline(issuer, issuer_name, sizeof(issuer_name)); if (!check_auth(username, subject_name, issuer_name)) exit(4); } /* Check a user's authentication against certificate names */ /* returns 1 on good, 0 on bad or error */ int check_auth(char *username, char *subject_name, char *issuer_name) { char authpath[1024]; struct passwd *pw; pw = getpwnam(username); if (pw == NULL) { syslog(LOG_ERR, "Authentication failed: Cannot find user %s: %m", username); return(0); } if ((subject_name[0] == '\0') || (issuer_name[0] == '\0')) { syslog(LOG_WARNING, "Authentication failed: Empty subject and/or issuer name"); return(0); } /* Check denied subjects list */ if (lookup_cert(AUTH_DENIED_SUBJECTS, subject_name)) { syslog(LOG_WARNING, "Authentication failed: Forbidden subject"); syslog(LOG_WARNING, "Client: %s", subject_name); syslog(LOG_WARNING, "Issuer: %s", issuer_name); return(0); } /* Check denied issuers list */ if (lookup_cert(AUTH_DENIED_ISSUERS, issuer_name)) { syslog(LOG_WARNING, "Authentication failed: Forbidden issuer"); syslog(LOG_WARNING, "Client: %s", subject_name); syslog(LOG_WARNING, "Issuer: %s", issuer_name); return(0); } /* Check that issuer is trusted */ snprintf(authpath, sizeof(authpath), "%s/%s/%s", pw->pw_dir, USER_DIRECTORY, AUTH_TRUSTED_ISSUERS); if (!lookup_cert(authpath, issuer_name)) { syslog(LOG_WARNING, "Authentication failed: Untrusted issuer"); syslog(LOG_WARNING, "Client: %s", subject_name); syslog(LOG_WARNING, "Issuer: %s", issuer_name); return(0); } /* Check if issuer is allowed */ snprintf(authpath, sizeof(authpath), "%s/%s/%s", pw->pw_dir, USER_DIRECTORY, AUTH_ALLOWED_ISSUERS); if (lookup_cert(authpath, issuer_name)) { syslog(LOG_INFO, "Authentication succeeded for certificate:"); syslog(LOG_INFO, "Client: %s", subject_name); syslog(LOG_INFO, "Issuer: %s", issuer_name); return(1); } /* Check if subject is allowed */ snprintf(authpath, sizeof(authpath), "%s/%s/%s", pw->pw_dir, USER_DIRECTORY, AUTH_ALLOWED_SUBJECTS); if (lookup_cert(authpath, subject_name)) { syslog(LOG_INFO, "Authentication succeeded for certificate:"); syslog(LOG_INFO, "Client: %s", subject_name); syslog(LOG_INFO, "Issuer: %s", issuer_name); return(1); } syslog(LOG_WARNING, "Authentication failed: Not authorised"); syslog(LOG_WARNING, "Client: %s", subject_name); syslog(LOG_WARNING, "Issuer: %s", issuer_name); return(0); } /* Lookup a certificate online name in a textfile */ /* returns 1 on find, 0 on not found or error */ int lookup_cert(char *authfile, char *oneline_name) { FILE *f; char buffer[1024]; char *p; f = fopen(authfile, "r"); if (f == NULL) return(0); while(!feof(f) && (fgets(buffer, sizeof(buffer) - 1, f) != NULL)) { buffer[sizeof(buffer) - 1] = '\0'; p = strrchr(buffer, '\n'); if (p) *p = '\0'; if (strcmp(buffer, oneline_name) == 0) { fclose(f); return(1); } } fclose(f); return(0); } /* End of slushd.c */