/** * int main() * * Invokes the backend, waits until it has successfully detached from the I/O ports, * and then quits. */ int main() { /** Block SIGUSR1, SIGCHLD until we can handle them. */ sigset_t waitset; sigemptyset(&waitset); sigaddset(&waitset, SIGUSR1); sigaddset(&waitset, SIGCHLD); sigprocmask(SIG_BLOCK, &waitset, NULL); /** Install signal handlers for those signals. */ struct sigaction actions; sigemptyset(&actions.sa_mask); actions.sa_flags = SA_SIGINFO; actions.sa_sigaction = dummy_signal_action; sigaction(SIGUSR1, &actions, NULL); sigaction(SIGCHLD, &actions, NULL); /** Fork the child */ pid_t childpid = fork(); if(childpid < 0) { perror("Could not fork() child:"); return 1; } if(childpid == 0) { /* Detach session, setup file descriptors, spawn Racket process. */ if(setsid() < 0) { perror("Could not detach session:"); return 1; } #ifndef NDEBUG putenv("PLTSTDERR=debug"); #endif // Prefer build directory if debug build. if(!IS_INSTALLED() && access(SEASHELL_DEBUG_MAIN, F_OK) != -1) { char * argv2[] = {SEASHELL_DEBUG_MAIN, "-s", NULL}; execv(SEASHELL_DEBUG_MAIN, argv2); perror("Could not execv() the Seashell backend:"); return 1; } // Main case if not in debug mode (fall through if) char * argv[] = {SEASHELL_MAIN, "-s", NULL}; execv(SEASHELL_MAIN, argv); perror("Could not execv() the Seashell backend:"); return 1; } else { /* Setup file descriptors, pass key and port, exit. */ while (true) { siginfo_t info; int result = sigwaitinfo(&waitset, &info); if (result < 0) { if (errno == EINTR) { continue; } else { perror("sigwaitinfo failed with:"); return 1; } } else { /** Wait for the process to quit (successfully) or detach. */ if (info.si_signo == SIGCHLD) { int status = 0; result = waitpid(childpid, &status, WCONTINUED | WUNTRACED); if (result < 0) { perror("wait failed with:"); return 1; } else if (WIFEXITED(status)) { return WEXITSTATUS(status); } } else if (info.si_signo == SIGUSR1) { return 0; } } } } }
/** * seashell_tunnel_connect_password (const char* host, const char* user, const char* password, int* error) * Connects to the host via SSH on port 22, and launches a Seashell backend instance for that user * on the host. * * Consults /etc/seashell_hosts for host's SSH public keys. If this file does not exist, * this function will fail for security reasons. /etc/seashell_hosts is a standard * OpenSSH known_hosts file. * * Arguments: * host - Host to connect to. * user - User to run as. * password - User's password. * error - [optional] denotes error on failure. * remote_addr - Address to which the remote IP address will * be written. Reserve 128 bytes. * family - Address family. * target - Target to execute. * * Returns: * Handle to connection object on success, NULL otherwise. * If error is NOT null, error will hold more detailed error information. */ struct seashell_connection* seashell_tunnel_connect_password (const char* host, const char* user, const char* password, int* error, char * remote_addr, int* family, char* target) { struct addrinfo hints; struct addrinfo *results, *rp; int sockfd; int i, e; struct seashell_connection* result = NULL; /* Resolve the host's address. * See getaddrinfo(3) for how this works. */ memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = 0; hints.ai_protocol = 0; e = getaddrinfo(host, "22", &hints, &results); if (e != 0) { SET_ERROR(TUNNEL_ERROR_RESOLV); return NULL; } for (rp = results; rp != NULL; rp = rp->ai_next) { sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sockfd == -1) continue; if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) break; close(sockfd); } /* Write address that we're connecting to into * remote_addr. */ if(rp != NULL) { *family = rp->ai_family; switch(rp->ai_family) { case AF_INET: if(inet_ntop(rp->ai_family, &((struct sockaddr_in *)rp->ai_addr)->sin_addr, remote_addr, 128) == NULL) { SET_ERROR(TUNNEL_ERROR_RESOLV); return NULL; } break; case AF_INET6: if(inet_ntop(rp->ai_family, &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, remote_addr, 128) == NULL) { SET_ERROR(TUNNEL_ERROR_RESOLV); return NULL; } break; default: SET_ERROR(TUNNEL_ERROR_RESOLV); return NULL; } } freeaddrinfo(results); /* Either rp == NULL, in which case we failed at connecting, * or sockfd holds our socket. */ if (rp == NULL) { SET_ERROR(TUNNEL_ERROR_CONNECT); return NULL; } /** Set up the session */ LIBSSH2_SESSION* session; LIBSSH2_CHANNEL* channel; LIBSSH2_KNOWNHOSTS* hosts; size_t len; int type; session = libssh2_session_init(); if (!session) { SET_ERROR(TUNNEL_ERROR_SESSION_START); goto session_teardown; } e = libssh2_session_handshake(session, sockfd); if (e) { SET_ERROR(TUNNEL_ERROR_SESSION_HANDSHAKE); goto session_teardown; } hosts = libssh2_knownhost_init(session); if (!hosts) { SET_ERROR(TUNNEL_ERROR_HOSTS_FILE); goto session_teardown; } if (!IS_INSTALLED() && access(DEBUG_HOSTS_FILE, F_OK) != -1) { libssh2_knownhost_readfile(hosts, DEBUG_HOSTS_FILE, LIBSSH2_KNOWNHOST_FILE_OPENSSH); } else { libssh2_knownhost_readfile(hosts, HOSTS_FILE, LIBSSH2_KNOWNHOST_FILE_OPENSSH); } const char* fingerprint = libssh2_session_hostkey(session, &len, &type); if (!fingerprint || type == LIBSSH2_HOSTKEY_TYPE_UNKNOWN) { libssh2_knownhost_free(hosts); SET_ERROR(TUNNEL_ERROR_HOST); goto session_teardown; } struct libssh2_knownhost *hostkey; /** NOTE: Documentation is buggy. hostkey MUST be passed. */ int check = libssh2_knownhost_check(hosts, host, fingerprint, len, LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW, &hostkey); if (check != LIBSSH2_KNOWNHOST_CHECK_MATCH) { int keytype = 0; switch (type) { case LIBSSH2_HOSTKEY_TYPE_RSA: keytype = LIBSSH2_KNOWNHOST_KEY_SSHRSA; break; case LIBSSH2_HOSTKEY_TYPE_DSS: keytype = LIBSSH2_KNOWNHOST_KEY_SSHRSA; break; } if (keytype) { libssh2_knownhost_addc(hosts, host, NULL, fingerprint, len, "Generated from Seashell Tunnel", strlen("Generated from Seashell Tunnel"), LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | (type == LIBSSH2_HOSTKEY_TYPE_RSA ? LIBSSH2_KNOWNHOST_KEY_SSHRSA : LIBSSH2_KNOWNHOST_KEY_SSHDSS), NULL); libssh2_knownhost_writefile(hosts, DUMP_FILE, LIBSSH2_KNOWNHOST_FILE_OPENSSH); fprintf(stderr, "%s: Check SSH key for %s! Keys written to %s\n", user, host, DUMP_FILE); } else { fprintf(stderr, "%s: Check SSH key for %s!\n", user, host, DUMP_FILE); fprintf(stderr, "%s: Keys not written to file - contact Seashell Maintainers to add support for the LibSSH2 key format %d\n", user, type); } libssh2_knownhost_free(hosts); SET_ERROR(TUNNEL_ERROR_HOST); goto session_teardown; } libssh2_knownhost_free(hosts); FPRINTF_IF_DEBUG(stderr, "%s: Host check passed for %s (fingerprint type %d) - ", user, host, type); for(i = 0; i < 20; i++) { FPRINTF_IF_DEBUG(stderr, "%02X ", (unsigned char)fingerprint[i]); } FPRINTF_IF_DEBUG(stderr, "\n"); e = libssh2_userauth_password(session, user, password); if (e) { FPRINTF_IF_DEBUG(stderr, "%s: Error authenticating: %d\n", user, e); SET_ERROR(TUNNEL_ERROR_CREDS); goto session_teardown; } channel = libssh2_channel_open_session(session); if (!channel) { SET_ERROR(TUNNEL_ERROR_CHANNEL_OPEN); goto session_teardown; } /** * Ideally we'd have a subsystem configured, * as I don't see a good way of pulling out of ssh2 * if the target does not exist. */ e = libssh2_channel_exec(channel, target); if (e) { SET_ERROR(TUNNEL_ERROR_LAUNCH_SEASHELL); goto channel_teardown; } result = malloc(sizeof(struct seashell_connection)); if (!result) { SET_ERROR(TUNNEL_ERROR_SESSION_START); goto channel_teardown; } result->sockfd = sockfd; result->session = session; result->channel = channel; goto end; channel_teardown: libssh2_channel_free(channel); session_teardown: libssh2_session_free(session); close(sockfd); end: return result; }