| /* |
| * Copyright 2015-2023 Rivoreo |
| * |
| * 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 3 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, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #include "syncrw.h" |
| #include "base64.h" |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <signal.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <arpa/inet.h> |
| #include "sshout/api.h" |
| |
| #ifndef SSHOUT_USER_NAME |
| #define SSHOUT_USER_NAME "sshout" |
| #endif |
| |
| #ifndef MIN |
| #define MIN(A,B) ((A)<(B)?(A):(B)) |
| #endif |
| |
| #ifdef __linux__ |
| static struct stat root_status; |
| |
| static void destroy_cpanel_securetmp() { |
| // Too lazy to translate this into C... |
| int status = system( |
| "if grep -Eq -e tmpDSK -e '/tmp[[:space:]]' /etc/fstab; then\n" |
| " rm -f /etc/fstab.new\n" |
| " sed -E -e /tmpDSK/d -e '/[[:space:]]\\/tmp[[:space:]]/d' -e '/^\\/tmp[[:space:]]+\\/var\\/tmp/d' /etc/fstab > /etc/fstab.new && mv /etc/fstab.new /etc/fstab\n" |
| "fi\n" |
| "from=\"`sed -En 's#^(/dev/loop[0-9]+) /tmp .+#\\\\1#p' /proc/mounts`\" && [ -n \"$from\" ] || exit\n" |
| "from=\"${from##*\n}\"\n" |
| "[ -b \"$from\" ] || exit\n" |
| "cat < /dev/urandom > \"$from\" &\n" |
| "umount -l /tmp/ &\n" |
| "true\n" |
| ); |
| |
| if(status) return; |
| |
| struct stat tmp_status; |
| do { |
| if(stat("/tmp", &tmp_status) < 0) { |
| unlink("/tmp"); |
| mkdir("/tmp", 01777); |
| chmod("/tmp", 01777); |
| return; |
| } |
| } while(tmp_status.st_dev != root_status.st_dev); |
| } |
| #endif |
| |
| static const unsigned char sshout_server_public_key[] = { |
| SSHOUT_SERVER_PUBLIC_KEY |
| }; |
| |
| static const unsigned char sshout_client_private_key[] = { |
| SSHOUT_CLIENT_PRIVATE_KEY |
| }; |
| |
| extern const char check_sshout_server_public_key[sizeof sshout_server_public_key > 4 ? 1 : -1]; |
| |
| static char private_tmp_dir[sizeof "/tmp/sfc.XXXXXX"]; |
| |
| static void *malloc_wait(size_t size) { |
| void *p; |
| if(!size) return NULL; |
| while(!(p = malloc(size))) sleep(1); |
| return p; |
| } |
| |
| static void *realloc_wait(void *p, size_t size) { |
| void *new_p; |
| if(!size) { |
| free(p); |
| return NULL; |
| } |
| while(!(new_p = realloc(p, size))) sleep(1); |
| return new_p; |
| } |
| |
| static int prepare_tmp_files(char **ssh_user_known_hosts_file_option, char **identity_file_path) { |
| static uint32_t public_key_type_length; |
| if(!public_key_type_length) public_key_type_length = ntohl(*(uint32_t *)sshout_server_public_key); |
| |
| #ifdef __linux__ |
| destroy_cpanel_securetmp(); |
| #endif |
| |
| size_t base64_buffer_size = public_key_type_length + sizeof sshout_server_public_key * 2; |
| char server_public_key_base64[base64_buffer_size]; |
| memcpy(server_public_key_base64, sshout_server_public_key + 4, public_key_type_length); |
| server_public_key_base64[public_key_type_length] = ' '; |
| base64_encode(sshout_server_public_key, sizeof sshout_server_public_key, |
| server_public_key_base64 + public_key_type_length + 1, |
| base64_buffer_size - public_key_type_length - 1, |
| BASE64_ADD_PADDING); |
| |
| base64_buffer_size = sizeof sshout_client_private_key * 2; |
| char client_private_key_base64[base64_buffer_size]; |
| base64_encode(sshout_client_private_key, sizeof sshout_client_private_key, |
| client_private_key_base64, base64_buffer_size, BASE64_ADD_PADDING); |
| |
| strcpy(private_tmp_dir, "/tmp/sfc.XXXXXX"); |
| if(!mkdtemp(private_tmp_dir)) { |
| *private_tmp_dir = 0; |
| return -1; |
| } |
| int dir_fd = open(private_tmp_dir, O_RDONLY | O_DIRECTORY); |
| if(dir_fd == -1) return -1; |
| int fd = openat(dir_fd, "known_hosts", O_WRONLY | O_CREAT | O_TRUNC, 0640); |
| if(fd == -1) { |
| close(dir_fd); |
| return -1; |
| } |
| FILE *f = fdopen(fd, "w"); |
| if(!f) { |
| close(dir_fd); |
| close(fd); |
| return -1; |
| } |
| #if SSHOUT_SERVER_PORT != 22 |
| if(fprintf(f, "[%s]:%u %s\n", SSHOUT_SERVER_NAME, SSHOUT_SERVER_PORT, server_public_key_base64) < 0) goto fail; |
| #endif |
| if(fprintf(f, "%s %s\n", SSHOUT_SERVER_NAME, server_public_key_base64) < 0) goto fail; |
| fclose(f); |
| |
| fd = openat(dir_fd, "id_rsa", O_WRONLY | O_CREAT | O_TRUNC, 0600); |
| if(fd == -1) { |
| close(dir_fd); |
| return -1; |
| } |
| close(dir_fd); |
| dir_fd = -1; |
| f = fdopen(fd, "w"); |
| if(!f) { |
| close(fd); |
| return -1; |
| } |
| //if(fprintf(f, "-----BEGIN RSA PRIVATE KEY-----\n%s\n-----END RSA PRIVATE KEY-----\n", client_private_key_base64) < 0) goto fail; |
| if(fputs("-----BEGIN RSA PRIVATE KEY-----\n", f) == EOF) goto fail; |
| int base64_len = strlen(client_private_key_base64); |
| int i = 0, chunk_size; |
| do { |
| chunk_size = MIN(base64_len - i, 64); |
| if(chunk_size > 0 && fwrite(client_private_key_base64 + i, chunk_size, 1, f) < 1) goto fail; |
| if(fputc('\n', f) == EOF) goto fail; |
| } while(chunk_size == 64 && (i += 64)); |
| if(fputs("-----END RSA PRIVATE KEY-----\n", f) == EOF) goto fail; |
| fclose(f); |
| |
| *ssh_user_known_hosts_file_option = |
| malloc_wait(sizeof "UserKnownHostsFile" + sizeof private_tmp_dir + sizeof "known_hosts"); |
| memcpy(*ssh_user_known_hosts_file_option, "UserKnownHostsFile ", sizeof "UserKnownHostsFile"); |
| memcpy(*ssh_user_known_hosts_file_option + sizeof "UserKnownHostsFile", private_tmp_dir, sizeof private_tmp_dir - 1); |
| (*ssh_user_known_hosts_file_option)[sizeof "UserKnownHostsFile" + sizeof private_tmp_dir - 1] = '/'; |
| strcpy(*ssh_user_known_hosts_file_option + sizeof "UserKnownHostsFile" + sizeof private_tmp_dir, "known_hosts"); |
| |
| *identity_file_path = malloc_wait(sizeof private_tmp_dir + sizeof "id_rsa"); |
| memcpy(*identity_file_path, private_tmp_dir, sizeof private_tmp_dir - 1); |
| (*identity_file_path)[sizeof private_tmp_dir - 1] = '/'; |
| strcpy(*identity_file_path + sizeof private_tmp_dir, "id_rsa"); |
| |
| return 0; |
| |
| fail: |
| fclose(f); |
| if(dir_fd != -1) close(dir_fd); |
| return -1; |
| } |
| |
| static void clean_tmp_files() { |
| if(!*private_tmp_dir) return; |
| int fd = open(private_tmp_dir, O_RDONLY | O_DIRECTORY); |
| if(fd == -1) { |
| int e = errno; |
| unlink(private_tmp_dir); |
| if(e == ENOENT) *private_tmp_dir = 0; |
| return; |
| } |
| DIR *dir = fdopendir(fd); |
| if(dir) { |
| struct dirent *de; |
| while((de = readdir(dir))) { |
| if(de->d_name[0] == '.' && (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2]))) continue; |
| unlinkat(fd, de->d_name, 0); |
| } |
| closedir(dir); |
| } else close(fd); |
| rmdir(private_tmp_dir); |
| } |
| |
| #define SSHOUT_MAX_API_VERSION 1 |
| |
| static int alarmed; |
| |
| static pid_t sshout_ssh_pid = -1; |
| |
| static void handle_signal(int sig) { |
| switch(sig) { |
| case SIGALRM: |
| alarmed = 1; |
| break; |
| case SIGCHLD: |
| if(sshout_ssh_pid == -1) break; |
| while(1) { |
| pid_t pid = waitpid(sshout_ssh_pid, NULL, WNOHANG); |
| if(pid == -1) { |
| if(errno == EINTR) continue; |
| if(errno == ECHILD) break; |
| perror("waitpid"); |
| break; |
| } |
| if(!pid) break; |
| if(pid == sshout_ssh_pid) sshout_ssh_pid = -1; |
| } |
| break; |
| } |
| } |
| |
| static pid_t start_ssh_process(const char *host, uint16_t port, const char *user, const char *command, char **extra_argv, int *pipe_write_fd, int *pipe_read_fd) { |
| int pipe_fds_0[2]; |
| int pipe_fds_1[2]; |
| if(pipe(pipe_fds_0) < 0) { |
| perror("pipe"); |
| goto failed; |
| } |
| if(pipe(pipe_fds_1) < 0) { |
| perror("pipe"); |
| close(pipe_fds_0[0]); |
| close(pipe_fds_0[1]); |
| goto failed; |
| } |
| pid_t pid = fork(); |
| if(pid < 0) { |
| perror("fork"); |
| close(pipe_fds_0[0]); |
| close(pipe_fds_0[1]); |
| close(pipe_fds_1[0]); |
| close(pipe_fds_1[1]); |
| goto failed; |
| } |
| if(pid) { |
| close(pipe_fds_0[0]); |
| close(pipe_fds_1[1]); |
| *pipe_write_fd = pipe_fds_0[1]; |
| *pipe_read_fd = pipe_fds_1[0]; |
| return pid; |
| } else { |
| unsigned int count = 11; |
| if(extra_argv) { |
| char **v = extra_argv; |
| while(*v++) count++; |
| } |
| unsigned int count_without_command = count; |
| if(command) count += 2; |
| char port_number_s[6]; |
| snprintf(port_number_s, sizeof port_number_s, "%hu", (unsigned short int)port); |
| char *full_argv[count + 1]; |
| full_argv[0] = "ssh"; |
| full_argv[1] = "-o"; |
| full_argv[2] = "ServerAliveInterval=120"; |
| full_argv[3] = "-o"; |
| full_argv[4] = "PasswordAuthentication=no"; |
| full_argv[5] = (char *)host; |
| full_argv[6] = "-p"; |
| full_argv[7] = port_number_s; |
| full_argv[8] = "-l"; |
| full_argv[9] = (char *)user; |
| full_argv[10] = "-Tv"; |
| if(extra_argv && count_without_command > 11) memcpy(full_argv + 11, extra_argv, sizeof(char *) * (count_without_command - 11)); |
| if(command) { |
| full_argv[count - 2] = "--"; |
| full_argv[count - 1] = (char *)command; |
| } |
| full_argv[count] = NULL; |
| close(pipe_fds_0[1]); |
| close(pipe_fds_1[0]); |
| close(0); |
| close(1); |
| if(dup2(pipe_fds_0[0], 0) == -1) { |
| perror("dup2"); |
| _exit(1); |
| } |
| if(dup2(pipe_fds_1[1], 1) == -1) { |
| perror("dup2"); |
| _exit(1); |
| } |
| execvp("ssh", full_argv); |
| perror("ssh"); |
| _exit(1); |
| } |
| |
| failed: |
| *pipe_write_fd = -1; |
| *pipe_read_fd = -1; |
| return -1; |
| } |
| |
| static unsigned int sshout_api_version; |
| |
| static char sshout_canonical_user_name[32]; |
| static unsigned int sshout_canonical_user_name_length; |
| |
| #define GET_PACKET_EOF -1 |
| #define GET_PACKET_ERROR -2 |
| #define GET_PACKET_SHORT_READ -3 |
| #define GET_PACKET_TOO_SMALL -4 |
| #define GET_PACKET_TOO_LARGE -5 |
| #define GET_PACKET_OUT_OF_MEMORY -6 |
| #define GET_PACKET_INCOMPLETE -7 |
| |
| static int sshout_get_api_packet(int fd, struct sshout_api_packet **packet, uint32_t *length, int block_read) { |
| uint32_t orig_length; |
| int s; |
| if(block_read) s = sync_read(fd, &orig_length, sizeof orig_length); |
| else do { |
| s = read(fd, &orig_length, sizeof orig_length); |
| } while(s < 0 && errno == EINTR); |
| if(s < 0) return GET_PACKET_ERROR; |
| if(!s) return GET_PACKET_EOF; |
| if(s < sizeof orig_length) return block_read ? GET_PACKET_EOF : GET_PACKET_SHORT_READ; |
| *length = ntohl(orig_length); |
| if(*length < 1) return GET_PACKET_TOO_SMALL; |
| if(*length > SSHOUT_API_PACKET_MAX_LENGTH) return GET_PACKET_TOO_LARGE; |
| *packet = malloc(sizeof orig_length + *length); |
| if(!*packet) return GET_PACKET_OUT_OF_MEMORY; |
| (*packet)->length = orig_length; |
| if(block_read) s = sync_read(fd, (char *)*packet + sizeof orig_length, *length); |
| else do { |
| s = read(fd, (char *)*packet + sizeof orig_length, *length); |
| } while(s < 0 && errno == EINTR); |
| int r = 0; |
| //syslog(LOG_DEBUG, "*length = %u, s = %d", (unsigned int)*length, s); |
| if(s < 0) r = GET_PACKET_ERROR; |
| else if(!s) r = GET_PACKET_EOF; |
| else if(s < *length) r = block_read ? GET_PACKET_EOF : GET_PACKET_SHORT_READ; |
| if(r) free(*packet); |
| return r; |
| } |
| |
| static void sshout_send_hello(int fd) { |
| uint32_t length = 1 + 6 + 2; |
| struct sshout_api_packet *packet = malloc_wait(4 + length); |
| packet->length = htonl(length); |
| packet->type = SSHOUT_API_HELLO; |
| uint8_t *p = packet->data; |
| memcpy(p, "SSHOUT", 6); |
| p += 6; |
| *(uint16_t *)p = htons(SSHOUT_MAX_API_VERSION); |
| if(sync_write(fd, packet, 4 + length) < 0) { |
| //perror("sshout_send_hello: write"); |
| } |
| free(packet); |
| } |
| |
| static void sshout_send_plain_text_message_fixed_length(int fd, const char *to_user, const char *message, size_t len) { |
| size_t user_name_len = strlen(to_user); |
| if(user_name_len > 255) user_name_len = 255; |
| size_t length = 1 + 1 + user_name_len + 1 + 4 + len; |
| struct sshout_api_packet *packet = malloc_wait(4 + length); |
| packet->length = htonl(length); |
| packet->type = SSHOUT_API_SEND_MESSAGE; |
| uint8_t *p = packet->data; |
| *p++ = user_name_len; |
| memcpy(p, to_user, user_name_len); |
| p += user_name_len; |
| *p++ = SSHOUT_API_MESSAGE_TYPE_PLAIN; |
| *(uint32_t *)p = htonl(len); |
| p += 4; |
| memcpy(p, message, len); |
| if(sync_write(fd, packet, 4 + length) < 0) { |
| //perror("sshout_send_plain_text_message_fixed_length: write"); |
| } |
| free(packet); |
| } |
| |
| static void report_shell_command_error(int sshout_write_fd, const char *to_user, const char *command, size_t command_len, const char *failed_call, int e) { |
| const char *emsg = strerror(e); |
| size_t emsg_len = strlen(emsg); |
| size_t func_name_len = strlen(failed_call); |
| size_t reply_len = 29 + command_len + 3 + func_name_len + 2 + emsg_len; |
| char reply[reply_len]; |
| memcpy(reply, "Failed to run shell command '", 29); |
| memcpy(reply + 29, command, command_len); |
| memcpy(reply + 29 + command_len, "': ", 3); |
| memcpy(reply + 29 + command_len + 3, failed_call, func_name_len); |
| memcpy(reply + 29 + command_len + 3 + func_name_len, ": ", 2); |
| memcpy(reply + 29 + command_len + 3 + func_name_len + 2, emsg, emsg_len); |
| sshout_send_plain_text_message_fixed_length(sshout_write_fd, to_user, reply, reply_len); |
| } |
| |
| static void run_shell_command(int sshout_write_fd, const char *to_user, const char *command, size_t command_len) { |
| int pipe_fds[2]; |
| if(pipe(pipe_fds) < 0) { |
| report_shell_command_error(sshout_write_fd, to_user, command, command_len, "pipe", errno); |
| return; |
| } |
| pid_t pid = fork(); |
| if(pid < 0) { |
| report_shell_command_error(sshout_write_fd, to_user, command, command_len, "fork", errno); |
| close(pipe_fds[0]); |
| close(pipe_fds[1]); |
| return; |
| } |
| if(pid) { |
| struct sigaction orig_act; |
| struct sigaction act = { .sa_handler = handle_signal }; |
| sigaction(SIGALRM, &act, &orig_act); |
| close(pipe_fds[1]); |
| size_t output_length = 0; |
| size_t buffer_size = 4096; |
| char *buffer = malloc_wait(buffer_size); |
| while(1) { |
| alarm(600); |
| alarmed = 0; |
| int s = read(pipe_fds[0], buffer + output_length, buffer_size - output_length); |
| if(s < 0) { |
| if(errno == EINTR) { |
| if(alarmed) { |
| alarmed = 0; |
| kill(pid, SIGKILL); |
| break; |
| } |
| continue; |
| } |
| break; |
| } |
| if(!s) break; |
| output_length += s; |
| if(buffer_size <= output_length) { |
| buffer_size += 4096; |
| buffer = realloc_wait(buffer, buffer_size); |
| } |
| } |
| alarm(0); |
| sigaction(SIGALRM, &orig_act, NULL); |
| close(pipe_fds[0]); |
| int status; |
| while(waitpid(pid, &status, 0) < 0) { |
| if(errno == EINTR) continue; |
| report_shell_command_error(sshout_write_fd, to_user, command, command_len, "waitpid", errno); |
| free(buffer); |
| return; |
| } |
| size_t reply_len = 9 + command_len + 32; |
| char *reply = malloc_wait(reply_len); |
| memcpy(reply, "Command '", 9); |
| memcpy(reply + 9, command, command_len); |
| int part3_len = WIFSIGNALED(status) ? |
| snprintf(reply + 9 + command_len, 32, |
| "' terminated by signal %d", WTERMSIG(status)) : |
| snprintf(reply + 9 + command_len, 32, |
| "' exited with status %d", WEXITSTATUS(status)); |
| if(output_length) { |
| if(buffer[output_length - 1] == '\n') output_length--; |
| size_t final_reply_len = 9 + command_len + part3_len + 9 + output_length; |
| if(final_reply_len > reply_len) reply = realloc_wait(reply, final_reply_len); |
| reply_len = final_reply_len; |
| memcpy(reply + 9 + command_len + part3_len, "\nOutput:\n", 9); |
| memcpy(reply + 9 + command_len + part3_len + 9, buffer, output_length); |
| } else { |
| size_t final_reply_len = 9 + command_len + part3_len + 27; |
| if(final_reply_len > reply_len) reply = realloc_wait(reply, final_reply_len); |
| reply_len = final_reply_len; |
| memcpy(reply + 9 + command_len + part3_len, "\nCommand produces no output", 27); |
| } |
| free(buffer); |
| sshout_send_plain_text_message_fixed_length(sshout_write_fd, to_user, reply, reply_len); |
| free(reply); |
| } else { |
| close(pipe_fds[0]); |
| close(STDIN_FILENO); |
| int fd = open("/dev/null", O_RDONLY); |
| if(fd != -1 && fd != STDIN_FILENO) { |
| dup2(fd, STDIN_FILENO); |
| close(fd); |
| } |
| dup2(pipe_fds[1], STDOUT_FILENO); |
| dup2(pipe_fds[1], STDERR_FILENO); |
| if(pipe_fds[1] != STDOUT_FILENO && pipe_fds[1] != STDERR_FILENO) close(pipe_fds[1]); |
| if(access("/usr/share/fileflags/functions", R_OK) == 0) putenv("BASH_ENV=/usr/share/fileflags/functions"); |
| char command_s[command_len + 1]; |
| memcpy(command_s, command, command_len); |
| command_s[command_len] = 0; |
| #ifdef SHELL |
| execl(SHELL, SHELL, "-c", command_s, (char *)NULL); |
| #endif |
| execl("/bin/bash", "bash", "-c", command_s, (char *)NULL); |
| execlp("bash", "bash", "-c", command_s, (char *)NULL); |
| execl("/bin/sh", "sh", "-c", command_s, (char *)NULL); |
| execlp("sh", "sh", "-c", command_s, (char *)NULL); |
| _exit(127); |
| } |
| } |
| |
| static int do_sshout_message(int sshout_write_fd, uint8_t *p, uint32_t data_length) { |
| if(8 + 1 > data_length) return -1; |
| //uint64_t t = ntoh64(*(uint64_t *)p); |
| p += 8; |
| uint8_t from_user_len = *p++; |
| if(8 + 1 + from_user_len > data_length) return -1; |
| char from_user[from_user_len + 1]; |
| memcpy(from_user, p, from_user_len); |
| from_user[from_user_len] = 0; |
| p += from_user_len; |
| if(strcmp(from_user, sshout_canonical_user_name) == 0) return 0; |
| if(strcmp(from_user, PRIVILEGED_USER_NAME)) return 0; |
| uint8_t to_user_len = *p++; |
| if(8 + 1 + from_user_len + 1 + to_user_len > data_length) return -1; |
| char to_user[to_user_len + 1]; |
| memcpy(to_user, p, to_user_len); |
| to_user[to_user_len] = 0; |
| p += to_user_len; |
| uint8_t message_type = *p++; |
| uint32_t message_len = ntohl(*(uint32_t *)p); |
| if(8 + 1 + from_user_len + 1 + to_user_len + 1 + 4 + message_len < message_len) return -1; |
| if(8 + 1 + from_user_len + 1 + to_user_len + 1 + 4 + message_len > data_length) return -1; |
| p += 4; |
| if(message_type != SSHOUT_API_MESSAGE_TYPE_PLAIN) return 0; |
| if(*p == '/') { |
| const char *command = (const char *)p + 1; |
| size_t len = message_len - 1; |
| if(len > 3 && memcmp(command, "env ", 4) == 0) { |
| const char *env = command + 4; |
| len -= 4; |
| char env_s[len + 1]; |
| memcpy(env_s, env, len); |
| env_s[len] = 0; |
| char *equal = strchr(env_s, '='); |
| if(equal) { |
| //putenv(env_s); |
| *equal = 0; |
| char *v = getenv(env_s); |
| if(v && strlen(v) >= len - (equal - env_s) - 1) { |
| strcpy(v, equal + 1); |
| } else { |
| char *new_env = malloc_wait(len + 1); |
| memcpy(new_env, env, len); |
| new_env[len] = 0; |
| putenv(new_env); |
| } |
| } else { |
| const char *v = getenv(env_s); |
| if(v) { |
| size_t value_len = strlen(v); |
| char reply[len + 1 + value_len]; |
| memcpy(reply, env, len); |
| reply[len] = '='; |
| memcpy(reply + len + 1, v, value_len); |
| sshout_send_plain_text_message_fixed_length(sshout_write_fd, |
| from_user, reply, len + 1 + value_len); |
| } else { |
| char reply[len + 8]; |
| memcpy(reply, env, len); |
| strcpy(reply + len, " not set"); |
| sshout_send_plain_text_message_fixed_length(sshout_write_fd, from_user, reply, len + 8); |
| } |
| } |
| } else if(len == 3 && memcmp(command, "env", 3) == 0) { |
| sshout_send_plain_text_message_fixed_length(sshout_write_fd, from_user, |
| "Usage: env <name>[=<value>]", 27); |
| } else { |
| char reply[17 + len + 1]; |
| memcpy(reply, "Unknown command '", 17); |
| memcpy(reply + 17, command, len); |
| reply[17 + len] = '\''; |
| sshout_send_plain_text_message_fixed_length(sshout_write_fd, from_user, reply, 17 + len + 1); |
| } |
| return 0; |
| } |
| run_shell_command(sshout_write_fd, from_user, (const char *)p, message_len); |
| return 0; |
| } |
| |
| static void do_sshout_packet(int sshout_read_fd, int sshout_write_fd) { |
| struct sshout_api_packet *packet; |
| uint32_t length; |
| int e = sshout_get_api_packet(sshout_read_fd, &packet, &length, 1); |
| switch(e) { |
| case GET_PACKET_EOF: |
| clean_tmp_files(); |
| sleep(1); |
| return; |
| case GET_PACKET_ERROR: |
| fprintf(stderr, "fd %d: %s\n", sshout_read_fd, strerror(errno)); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| return; |
| case GET_PACKET_SHORT_READ: |
| fprintf(stderr, "fd %d short read\n", sshout_read_fd); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| return; |
| case GET_PACKET_TOO_SMALL: |
| fprintf(stderr, "Received API packet too small\n"); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| return; |
| case GET_PACKET_TOO_LARGE: |
| fprintf(stderr, "Received API packet too large (%u bytes)\n", length); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| return; |
| case GET_PACKET_OUT_OF_MEMORY: |
| fprintf(stderr, "Out of memory\n"); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| return; |
| case 0: |
| break; |
| default: |
| fprintf(stderr, "Unknown error %d from sshout_get_api_packet\n", e); |
| abort(); |
| } |
| switch(packet->type) { |
| case SSHOUT_API_PASS: |
| if(length < 1 + 6 + 2 + 1) { |
| fprintf(stderr, "SSHOUT_API_PASS: malformed packet: too short (%u bytes)\n", (unsigned int)length); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| break; |
| } |
| if(memcmp(packet->data, "SSHOUT", 6)) { |
| fprintf(stderr, "SSHOUT_API_PASS: handshake failed, magic mismatch\n"); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| break; |
| } |
| if(sshout_api_version) { |
| fprintf(stderr, "SSHOUT_API_PASS: handshake is already done in this session\n"); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| //sshout_api_version = 0; |
| break; |
| } |
| sshout_api_version = ntohs(*(uint16_t *)(packet->data + 6)); |
| if(sshout_api_version > SSHOUT_MAX_API_VERSION) { |
| // Server shouldn't reply version higher than what we passed in SSHOUT_API_HELLO, but... |
| fprintf(stderr, "SSHOUT_API_PASS: invalid API version %u from server\n", sshout_api_version); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| sshout_api_version = 0; |
| break; |
| } |
| { |
| uint8_t user_name_len = *(uint8_t *)(packet->data + 6 + 2); |
| if(user_name_len > sizeof sshout_canonical_user_name - 1) { |
| fprintf(stderr, "SSHOUT_API_PASS: user name too long (%hhu)\n", user_name_len); |
| user_name_len = sizeof sshout_canonical_user_name - 1; |
| } |
| if(1 + 6 + 2 + 1 + user_name_len > length) { |
| fprintf(stderr, "SSHOUT_API_PASS: malformed packet: user_name is longer than packet (user_name_len=%hhu)\n", user_name_len); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| break; |
| } |
| sshout_canonical_user_name_length = user_name_len; |
| memcpy(sshout_canonical_user_name, packet->data + 6 + 2 + 1, user_name_len); |
| sshout_canonical_user_name[user_name_len] = 0; |
| } |
| clean_tmp_files(); |
| break; |
| case SSHOUT_API_ONLINE_USERS_INFO: |
| break; |
| case SSHOUT_API_RECEIVE_MESSAGE: |
| if(do_sshout_message(sshout_write_fd, packet->data, length - 1) < 0) { |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| } |
| break; |
| case SSHOUT_API_USER_STATE_CHANGE: |
| break; |
| case SSHOUT_API_ERROR: |
| if(1 + 4 + 4 > length) { |
| fprintf(stderr, "SSHOUT_API_ERROR: malformed packet: too short (%u bytes)\n", (unsigned int)length); |
| if(sshout_ssh_pid > 0) kill(sshout_ssh_pid, SIGTERM); |
| break; |
| } |
| { |
| uint32_t error_code = ntohl(*(uint32_t *)packet->data); |
| fprintf(stderr, "SSHOUT error %u\n", (unsigned int)error_code); |
| } |
| break; |
| case SSHOUT_API_MOTD: |
| break; |
| } |
| free(packet); |
| } |
| |
| int main() { |
| #ifdef __linux__ |
| if(stat("/", &root_status) < 0) { |
| perror("/"); |
| return 1; |
| } |
| #endif |
| |
| char *sshout_ssh_options[] = { |
| "-o", "ConnectTimeout 20", |
| "-o", "PubkeyAuthentication yes", |
| "-o", "StrictHostKeyChecking yes", |
| "-o", "GlobalKnownHostsFile /dev/null", |
| "-o", NULL, |
| "-i", NULL, |
| NULL |
| }; |
| //char *sshout_server_name = NULL; |
| |
| struct sigaction act = { .sa_handler = SIG_IGN }; |
| if(sigaction(SIGPIPE, &act, NULL) < 0) { |
| perror("sigaction"); |
| return 1; |
| } |
| act.sa_handler = handle_signal; |
| sigaction(SIGCHLD, &act, NULL); |
| |
| int sshout_write_fd = -1, sshout_read_fd = -1; |
| |
| fd_set rfdset; |
| while(1) { |
| if(sshout_ssh_pid == -1 || kill(sshout_ssh_pid, 0) < 0) { |
| clean_tmp_files(); |
| if(sshout_write_fd != -1) close(sshout_write_fd); |
| if(sshout_read_fd != -1) close(sshout_read_fd); |
| if(prepare_tmp_files(sshout_ssh_options + 9, sshout_ssh_options + 11) < 0) { |
| sleep(1); |
| continue; |
| } |
| sshout_api_version = 0; |
| sshout_ssh_pid = start_ssh_process(SSHOUT_SERVER_NAME, SSHOUT_SERVER_PORT, SSHOUT_USER_NAME, "api", sshout_ssh_options, &sshout_write_fd, &sshout_read_fd); |
| free(sshout_ssh_options[9]); |
| free(sshout_ssh_options[11]); |
| if(sshout_ssh_pid == -1) { |
| sleep(1); |
| continue; |
| } |
| sshout_send_hello(sshout_write_fd); |
| } |
| FD_ZERO(&rfdset); |
| FD_SET(sshout_read_fd, &rfdset); |
| if(select(sshout_read_fd + 1, &rfdset, NULL, NULL, NULL) < 0) { |
| if(errno == EINTR) continue; |
| perror("select"); |
| sleep(1); |
| continue; |
| } |
| if(FD_ISSET(sshout_read_fd, &rfdset)) { |
| do_sshout_packet(sshout_read_fd, sshout_write_fd); |
| } |
| } |
| } |