#ifdef HAVE_CONFIG_H # include "config.h" #endif #include "shell.h" #include "basic-array.h" #include "basic-list.h" #include "xwrap.h" #include "shell-builtin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RAS_SHELL_BUILTIN(x) ((RasShellBuiltin*)(x)) #define RAS_SHELL_SAVED(x) ((RasShellSaved*)(x)) #define RAS_SHELL_CMD_COMP(x) ((RasShellCmdComp*)(x)) #define RAS_SHELL_CMD(x) ((RasShellCmd*)(x)) #define RAS_SHELL(x) ((RasShell*)(x)) #define STRV(x) ((char**)(x)) #define STR(x) ((char*)(x)) typedef struct { Array* arg; /* NULL-terminated string array */ Array* redir_stdin; /* string */ Array* redir_stdout; /* string */ Array* redir_stderr; /* string */ Array* tapipe; /* string containing decimal number */ } RasShellCmdComp; typedef struct { List* argrp; /* A List of RasShellCmdComp */ pid_t pgid; /* process group ID */ } RasShellCmd; #define PARSE_QUOTE_REGULAR '\"' #define PARSE_QUOTE_SUPER '\'' #define PARSE_ESCAPE '\\' typedef enum { APPEND_STR, APPEND_STDIN, APPEND_STDOUT, APPEND_STDERR, APPEND_TAPIPE, APPEND_LAST } RasShellCmdMode; static void ras_shell_cmd_comp_init (RasShellCmdComp* comp) { comp->arg = array_create (sizeof (char*), 0); comp->redir_stdin = array_create (sizeof (char), 0); comp->redir_stdout = array_create (sizeof (char), 0); comp->redir_stderr = array_create (sizeof (char), 0); comp->tapipe = array_create (sizeof (char), 0); } static void ras_shell_cmd_comp_destroy (RasShellCmdComp* comp) { for (int i = 0; i < array_getlen (comp->arg); i++) { free (STRV (array_data (comp->arg))[i]); } array_free (comp->arg); array_free (comp->redir_stdin); array_free (comp->redir_stdout); array_free (comp->redir_stderr); array_free (comp->tapipe); } static void complete_string (Array*** ws, int* append, bool* committed, RasShellCmdComp* comp, bool allow_empty, bool reinit) { if (!allow_empty && array_getmax (*ws[*append]) <= 0) { return; } char zero = '\0'; array_pushback (*ws[*append], &zero); if (*append == APPEND_STR) { char* str = array_drop_struct (*ws[*append]); array_pushback (comp->arg, &str); if (reinit) { *ws[*append] = array_create (sizeof (char), 0); *committed = false; } else { *committed = true; } } *append = APPEND_STR; } static void complete_component (Array*** ws, int* append, bool* committed, RasShellCmdComp* comp, RasShellCmd* cmd, bool allow_empty, bool reinit) { complete_string (ws, append, committed, comp, allow_empty, reinit); char* null = NULL; array_pushback (comp->arg, &null); list_pushback (cmd->argrp, comp, sizeof (RasShellCmdComp)); if (reinit) { ras_shell_cmd_comp_init (comp); } } static int ras_shell_cmd_parse ( RasShellCmd* cmd, const char* cmdstr, RasShellRead read_cb, void* data) { bool is_quoted = false; bool is_escaped = false; bool ignore_esc = false; bool allow_empty = false; bool committed = false; char quote_char = '\0'; int append = APPEND_STR; /* current working string */ cmd->argrp = list_create (); cmd->pgid = 0; RasShellCmdComp comp; Array* str; str = array_create (sizeof (char), 0); ras_shell_cmd_comp_init (&comp); Array** ws[APPEND_LAST]; /* working string table */ ws[APPEND_STR] = &str; ws[APPEND_STDIN] = &comp.redir_stdin; ws[APPEND_STDOUT] = &comp.redir_stdout; ws[APPEND_STDERR] = &comp.redir_stderr; ws[APPEND_TAPIPE] = &comp.tapipe; const char* p = cmdstr; for (; *p != '\0'; p++) { if (is_escaped) { array_pushback (*ws[append], p); is_escaped = false; continue; } if (is_quoted) { if (*p == quote_char) { is_quoted = false; is_escaped = false; ignore_esc = false; } else { if (!ignore_esc && *p == PARSE_ESCAPE) { is_escaped = true; } else { array_pushback (str, p); } } continue; } if (isspace (*p)) { /* complete a string */ complete_string (ws, &append, &committed, &comp, allow_empty, true); allow_empty = false; continue; } switch (*p) { case PARSE_ESCAPE: is_escaped = true; continue; case PARSE_QUOTE_REGULAR: is_quoted = true; allow_empty = true; quote_char = *p; continue; case PARSE_QUOTE_SUPER: is_quoted = true; allow_empty = true; ignore_esc = true; quote_char = *p; continue; case '<': append = APPEND_STDIN; continue; case '>': append = APPEND_STDOUT; continue; case '|': if (isdigit (*(p + 1))) { append = APPEND_TAPIPE; } else { /* complete a component */ complete_component (ws, &append, &committed, &comp, cmd, allow_empty, true); } continue; default: array_pushback (*ws[append], p); } } complete_component (ws, &append, &committed, &comp, cmd, allow_empty, false); if (!committed) { array_free (str); } return 0; } static void ras_shell_cmd_print (RasShellCmd* cmd) { ListNode* iter = list_node_front (cmd->argrp); printf ("==> Command component count: %d\n", list_len (cmd->argrp)); for (int i = 1; iter != NULL; iter = list_next (iter), i++) { RasShellCmdComp* comp = list_data (iter); printf ("==> List component %d\n", i); if (array_getlen (comp->arg)) { char** arg = array_data (comp->arg); for (int j = 0; arg[j] != NULL; j++) { printf ("---> arg[%d] = %s\n", j, arg[j]); } } if (array_getlen (comp->redir_stdin)) { printf ("---> redir_stdin = %s\n", STR (array_data (comp->redir_stdin))); } if (array_getlen (comp->redir_stdout)) { printf ("---> redir_stdout = %s\n", STR (array_data (comp->redir_stdout))); } if (array_getlen (comp->redir_stderr)) { printf ("---> redir_stderr = %s\n", STR (array_data (comp->redir_stderr))); } if (array_getlen (comp->tapipe)) { printf ("---> tapipe = %s\n", STR (array_data (comp->tapipe))); } } } static int ras_shell_cmd_save ( RasShellCmd* cmd, RasShell* shell, const char* cmdstr, int num) { char* outfile = xsprintf ("%s/%" PRIu64, shell->saved_tmpdir, shell->count); int outfd = open (outfile, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (outfd < 0 && errno == ENOENT) { mkdir (shell->saved_tmpdir, S_IRWXU); outfd = open (outfile, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); } if (outfd < 0) { free (outfile); return outfd; } free (outfile); RasShellSaved saved = { .from = shell->count, .to = shell->count + num, .cmdorg = xstrdup (cmdstr) }; bool inserted = false; ListNode* iter = list_node_front (shell->saved_list); for (; iter != NULL; iter = list_next (iter)) { if (RAS_SHELL_SAVED (list_data (iter))->to > saved.to) { list_insert_prev (shell->saved_list, iter, &saved, sizeof (saved)); inserted = true; } } if (!inserted) { list_pushback (shell->saved_list, &saved, sizeof (saved)); } return outfd; } static int ras_shell_cmd_run ( RasShellCmd* cmd, RasShell* shell, const char* cmdstr) { ListNode* iter_saved = list_node_front (shell->saved_list); RasShellCmdComp tacomp; bool tamatched = false; while (iter_saved != NULL) { RasShellSaved* saved = list_data (iter_saved); if (saved->to == shell->count) { if (!tamatched) { char* myname = xstrdup ("tacat"); ras_shell_cmd_comp_init (&tacomp); array_pushback (tacomp.arg, &myname); tamatched = true; } char* myto = xsprintf ("%" PRIu64, saved->from); array_pushback (tacomp.arg, &myto); ListNode* iternext = list_next (iter_saved); free (saved->cmdorg); list_remove (shell->saved_list, iter_saved); iter_saved = iternext; } else { break; } } if (tamatched) { char* null = NULL; array_pushback (tacomp.arg, &null); list_pushfront (cmd->argrp, &tacomp, sizeof (tacomp)); } ListNode* iter = list_node_front (cmd->argrp); pid_t child; int pipefd[2], infile, outfile; int wait_status, status; cmd->pgid = 0; infile = STDIN_FILENO; outfile = STDOUT_FILENO; /* Prevent using uninitialized vaules */ pipefd[0] = STDIN_FILENO; pipefd[1] = STDOUT_FILENO; for (int i = 1; iter != NULL; iter = list_next (iter), i++) { RasShellCmdComp* comp = list_data (iter); RasShellFunc builtin_func = NULL; for (int j = 0; ras_shell_builtins[j].cmd != NULL; j++) { char* cmd = STRV (array_data (comp->arg))[0]; if (cmd == NULL) { continue; } if (strcmp (ras_shell_builtins[j].cmd, cmd) == 0) { builtin_func = ras_shell_builtins[j].func; break; } } if (list_next (iter) != NULL) { if (pipe (pipefd) < 0) { fprintf (stderr, "%s: pipe: %s\n", shell->name, strerror (errno)); break; } outfile = pipefd[1]; } else { if (array_getlen (comp->redir_stdout)) { outfile = open ( STR (array_data (comp->redir_stdout)), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (outfile < 0) { fprintf (stderr, "%s: %s: %s\n", shell->name, STR (array_data (comp->redir_stdout)), strerror (errno)); break; } } else { outfile = STDOUT_FILENO; } if (array_getlen (comp->tapipe)) { int tpnum; if (sscanf (STR (array_data (comp->tapipe)), "%d", &tpnum) < 1) { fprintf (stderr, "%s: invalid TA pipe number `%s\'\n", shell->name, STR (array_data (comp->tapipe))); break; } outfile = ras_shell_cmd_save (cmd, shell, cmdstr, tpnum); if (outfile < 0) { fprintf (stderr, "%s: cannot setup TA pipe: %s\n", shell->name, strerror (errno)); break; } } } if (list_len (cmd->argrp) <= 1 && builtin_func != NULL) { int inbackup, outbackup; status = 1; if (infile != STDIN_FILENO) { inbackup = dup (STDIN_FILENO); if (inbackup < 0) { fprintf (stderr, "%s: cannot backup stdin fd\n", shell->name); goto redir_cleanup; } dup2 (infile, STDIN_FILENO); close (infile); } if (outfile != STDOUT_FILENO) { outbackup = dup (STDOUT_FILENO); if (outbackup < 0) { fprintf (stderr, "%s: cannot backup stdout fd\n", shell->name); goto redir_cleanup; } dup2 (outfile, STDOUT_FILENO); close (outfile); } fflush (stdout); status = (*builtin_func)( array_getlen (comp->arg) - 1, STRV (array_data (comp->arg)), shell); if (infile != STDIN_FILENO) { dup2 (inbackup, STDIN_FILENO); close (inbackup); } if (outfile != STDOUT_FILENO) { dup2 (outbackup, STDOUT_FILENO); close (outbackup); } goto redir_cleanup; } child = fork (); if (child < 0) { fprintf (stderr, "%s: fork: %s\n", shell->name, strerror (errno)); continue; } else if (child > 0) { /* parent */ if (cmd->pgid == 0) { cmd->pgid = child; } setpgid (child, cmd->pgid); } else { /* child */ struct sigaction sa = { .sa_handler = SIG_DFL, .sa_flags = 0 }; sigemptyset (&sa.sa_mask); sigaction (SIGINT, &sa, NULL); sigaction (SIGQUIT, &sa, NULL); sigaction (SIGTSTP, &sa, NULL); sigaction (SIGTTOU, &sa, NULL); sigaction (SIGPIPE, &sa, NULL); sigaction (SIGHUP, &sa, NULL); pid_t pid = getpid (); if (cmd->pgid == 0) { cmd->pgid = pid; } setpgid (pid, cmd->pgid); if (shell->attr_isterm) { tcsetpgrp (shell->attr_isterm, cmd->pgid); } if (infile != STDIN_FILENO) { dup2 (infile, STDIN_FILENO); close (infile); } if (outfile != STDOUT_FILENO) { dup2 (outfile, STDOUT_FILENO); close (outfile); } if (builtin_func == NULL) { char* execname = STRV (array_data (comp->arg))[0]; char** execargv = STRV (array_data (comp->arg)); execvp (execname, execargv); fprintf (stderr, "%s: %s: %s\n", shell->name, execname, strerror (errno)); _exit (1); } else { _exit ((*builtin_func)(array_getlen (comp->arg) - 1, STRV (array_data (comp->arg)), shell)); } } if (infile != STDIN_FILENO) { close (infile); } if (outfile != STDOUT_FILENO) { close (outfile); } infile = pipefd[0]; } if (shell->attr_isterm) { tcsetpgrp (shell->term_fd, cmd->pgid); } while (waitpid (- cmd->pgid, &wait_status, 0) > 0); status = WEXITSTATUS (wait_status); if (shell->attr_isterm) { tcsetpgrp (shell->term_fd, shell->pgid); tcsetattr (shell->term_fd, TCSADRAIN, &shell->tmode); } redir_cleanup: if (infile != STDIN_FILENO) { close (infile); } if (outfile != STDOUT_FILENO) { close (outfile); } return status; } static void ras_shell_cmd_destroy (RasShellCmd* cmd) { if (cmd == NULL) { return; } ListNode* iter; for (iter = list_node_front (cmd->argrp); iter != NULL; iter = list_next (iter)) { if (list_data (iter) == NULL) { continue; } RasShellCmdComp* comp = RAS_SHELL_CMD_COMP (list_data (iter)); ras_shell_cmd_comp_destroy (comp); } list_free (cmd->argrp); } static char* ras_shell_cmd_read (void* data) { Array* cmdstr = array_readline (); char* cmd; if (cmdstr == NULL) { return NULL; } cmd = array_drop_struct (cmdstr); return cmd; } volatile sig_atomic_t hangup_req; static void hangup_req_setter (int signo) { hangup_req = true; } static int ras_shell_init (RasShell* shell, int argc, char* argv[]) { shell->last_rval = 0; shell->pid = getpid (); shell->pgid = getpgid (shell->pid); shell->attr_login = argv[0][0] == '-'; shell->attr_rshell = false; shell->attr_debug = false; shell->attr_isterm = isatty (STDIN_FILENO) == 1; shell->req_exit = false; shell->req_exit_status = 0; for (int i = 1; i < argc; i++) { if (argv[i][0] != '-') { fprintf (stderr, "%s: %s: unknown argument\n", argv[0], argv[i]); return -1; } for (int j = 1; argv[i][j] != '\0'; j++) { switch (argv[i][j]) { case 'r': shell->attr_rshell = true; break; case 'l': shell->attr_login = true; break; case 'v': case 'd': shell->attr_debug = true; break; case 'q': case 's': shell->attr_debug = false; break; case 'h': case '?': case '-': printf ( "Usage: %s [-hlr]\n" " -h View this help message\n" " -l Make ras-shell be a login shell\n" " -r Make ras-shell be a restricted shell\n" " -v Verbose mode\n", argv[0]); return -1; break; default: fprintf (stderr, "%s: -%c: unknown option\n", argv[0], argv[i][j]); return -1; } } } char* tmpdir = xstrcat ("/tmp/", argv[0], "-XXXXXX", NULL); if (mkdtemp (tmpdir) < 0) { fprintf (stderr, "%s: mkdtemp: %s\n", argv[0], strerror (errno)); free (tmpdir); return -1; } shell->name = strrchr (argv[0], '/'); shell->name = shell->name == NULL ? argv[0] : shell->name + 1; shell->count = 1; shell->saved_list = list_create (); shell->saved_tmpdir = tmpdir; struct passwd* pwname = getpwuid (getuid ()); shell->user_name = pwname == NULL ? NULL : xstrdup (pwname->pw_name); shell->user_ps = geteuid () ? '$' : '#'; if (shell->attr_isterm) { shell->term_fd = STDIN_FILENO; if (tcgetattr (shell->term_fd, &shell->tmode) < 0) { shell->attr_isterm = false; } } if (shell->attr_rshell) { setenv ("PATH", "bin:.", 1); } struct sigaction sa = { .sa_handler = SIG_IGN, .sa_flags = 0 }; sigemptyset (&sa.sa_mask); sigaction (SIGINT, &sa, NULL); sigaction (SIGQUIT, &sa, NULL); sigaction (SIGTSTP, &sa, NULL); sigaction (SIGTTOU, &sa, NULL); sigaction (SIGPIPE, &sa, NULL); sa.sa_handler = hangup_req_setter; sigaction (SIGHUP, &sa, NULL); hangup_req = false; return 0; } static void ras_shell_destroy (RasShell* shell) { for (uint64_t i = 1; i <= shell->count; i++) { char* f = xsprintf ("%s/%" PRIu64, shell->saved_tmpdir, i); unlink (f); free (f); } rmdir (shell->saved_tmpdir); ListNode* iter = list_node_front (shell->saved_list); for (; iter != NULL; iter = list_next (iter)) { free (RAS_SHELL_SAVED (list_data (iter))->cmdorg); } list_free (shell->saved_list); free (shell->saved_tmpdir); free (shell->user_name); } int ras_shell_main (int argc, char* argv[]) { setlocale (LC_ALL, ""); tzset (); RasShell shell; if (ras_shell_init (&shell, argc, argv) < 0) { return 1; } if (shell.attr_login) { FILE* motd_fp; int c; puts ("Welcome! This is remote access system (RAS) login shell."); motd_fp = fopen (ETC_MOTD, "r"); if (motd_fp == NULL) { fprintf (stderr, "%s: fail to open " ETC_MOTD ": %s", argv[0], strerror (errno)); } else { while ((c = getc (motd_fp)) != EOF) { putchar (c); } fclose (motd_fp); } } if (shell.attr_rshell) { puts ("Note: This is a restricted shell. Some command may be disabled."); } for (; !shell.req_exit && !hangup_req; shell.count++) { char* user = shell.user_name == NULL ? "I have no name!" : shell.user_name; char* cwd = xgetcwd (); printf ("%" PRIu64 ":%s[%s]%c ", shell.count, user, cwd, shell.user_ps); fflush (stdout); free (cwd); char* cmdstr = NULL; size_t cmdsize = 0; RasShellCmd cmd; if (getline (&cmdstr, &cmdsize, stdin) < 0) { if (feof (stdin)) { shell.req_exit = true; shell.req_exit_status = 0; free (cmdstr); } continue; } bool empty = true; for (char* p = cmdstr; *p != '\0'; p++) { if (!isspace (*p)) { empty = false; break; } } if (empty) { free (cmdstr); shell.count--; continue; } if (shell.attr_rshell) { if (strchr (cmdstr, '/') != NULL) { fprintf (stderr, "%s: you are not permitted to use `/\' in command\n", shell.name); free (cmdstr); continue; } } if (ras_shell_cmd_parse ( &cmd, cmdstr, ras_shell_cmd_read, NULL) < 0) { fprintf (stderr, "%s: cannot parse command `%s\'\n", shell.name, cmdstr); free (cmdstr); continue; } if (shell.attr_debug) { ras_shell_cmd_print (&cmd); } shell.last_rval = ras_shell_cmd_run (&cmd, &shell, cmdstr); free (cmdstr); ras_shell_cmd_destroy (&cmd); } if (hangup_req) { shell.req_exit_status = 0; } if (feof (stdin)) { if (shell.attr_login) { puts ("logout"); } else { puts ("exit"); } } int exit_status = shell.req_exit_status; ras_shell_destroy (&shell); return exit_status; }