int main(int argc, char **argv) { const int exit_sig[] = {SIGINT, SIGTERM, SIGHUP}; char *fqdn = NULL; struct passwd *passwd; struct tlog_writer *writer = NULL; clockid_t clock_id; struct timespec timestamp; struct tlog_sink *sink = NULL; tlog_grc grc; ssize_t rc; int master_fd; pid_t child_pid; unsigned int session_id; sig_atomic_t last_sigwinch_caught = 0; sig_atomic_t new_sigwinch_caught; sig_atomic_t last_alarm_caught = 0; sig_atomic_t new_alarm_caught; bool alarm_set = false; bool io_pending = false; bool term_attrs_set = false; struct termios orig_termios; struct termios raw_termios; struct winsize winsize; struct winsize new_winsize; size_t i, j; struct sigaction sa; struct pollfd fds[FD_IDX_NUM]; uint8_t input_buf[BUF_SIZE]; size_t input_pos = 0; size_t input_len = 0; uint8_t output_buf[BUF_SIZE]; size_t output_pos = 0; size_t output_len = 0; struct tlog_pkt pkt = TLOG_PKT_VOID; int status = 1; (void)argv; if (argc > 1) { fprintf(stderr, "Arguments are not accepted\n"); goto cleanup; } /* Get host FQDN */ grc = get_fqdn(&fqdn); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed retrieving host FQDN: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Get session ID */ grc = get_session_id(&session_id); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed retrieving session ID: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Open syslog */ openlog("tlog", LOG_NDELAY, LOG_LOCAL0); /* Create the syslog writer */ grc = tlog_syslog_writer_create(&writer, LOG_INFO); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating syslog writer: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Get effective user entry */ errno = 0; passwd = getpwuid(geteuid()); if (passwd == NULL) { if (errno == 0) fprintf(stderr, "User entry not found\n"); else fprintf(stderr, "Failed retrieving user entry: %s\n", strerror(errno)); goto cleanup; } /* * Choose the clock: try to use coarse monotonic clock (which is faster), * if it provides resolution of at least one millisecond. */ if (clock_getres(CLOCK_MONOTONIC_COARSE, ×tamp) == 0 && timestamp.tv_sec == 0 && timestamp.tv_nsec < 1000000) { clock_id = CLOCK_MONOTONIC_COARSE; } else if (clock_getres(CLOCK_MONOTONIC, NULL) == 0) { clock_id = CLOCK_MONOTONIC; } else { fprintf(stderr, "No clock to use\n"); goto cleanup; } /* Create the log sink */ grc = tlog_sink_create(&sink, writer, fqdn, passwd->pw_name, session_id, BUF_SIZE); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating log sink: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Get terminal attributes */ rc = tcgetattr(STDOUT_FILENO, &orig_termios); if (rc < 0) { fprintf(stderr, "Failed retrieving tty attributes: %s\n", strerror(errno)); goto cleanup; } /* Get terminal window size */ rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize); if (rc < 0) { fprintf(stderr, "Failed retrieving tty window size: %s\n", strerror(errno)); goto cleanup; } /* Fork a child under a slave pty */ child_pid = forkpty(&master_fd, NULL, &orig_termios, &winsize); if (child_pid < 0) { fprintf(stderr, "Failed forking a pty: %s\n", strerror(errno)); goto cleanup; } else if (child_pid == 0) { /* * Child */ execl("/bin/bash", "/bin/bash", NULL); fprintf(stderr, "Failed to execute the shell: %s", strerror(errno)); goto cleanup; } /* * Parent */ /* Log initial window size */ clock_gettime(clock_id, ×tamp); tlog_pkt_init_window(&pkt, ×tamp, winsize.ws_col, winsize.ws_row); grc = tlog_sink_write(sink, &pkt); tlog_pkt_cleanup(&pkt); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed logging window size: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Setup signal handlers to terminate gracefully */ for (i = 0; i < ARRAY_SIZE(exit_sig); i++) { sigaction(exit_sig[i], NULL, &sa); if (sa.sa_handler != SIG_IGN) { sa.sa_handler = exit_sighandler; sigemptyset(&sa.sa_mask); for (j = 0; j < ARRAY_SIZE(exit_sig); j++) sigaddset(&sa.sa_mask, exit_sig[j]); /* NOTE: no SA_RESTART on purpose */ sa.sa_flags = 0; sigaction(exit_sig[i], &sa, NULL); } } /* Setup WINCH signal handler */ sa.sa_handler = winch_sighandler; sigemptyset(&sa.sa_mask); /* NOTE: no SA_RESTART on purpose */ sa.sa_flags = 0; sigaction(SIGWINCH, &sa, NULL); /* Setup ALRM signal handler */ sa.sa_handler = alarm_sighandler; sigemptyset(&sa.sa_mask); /* NOTE: no SA_RESTART on purpose */ sa.sa_flags = 0; sigaction(SIGALRM, &sa, NULL); /* Switch the terminal to raw mode */ raw_termios = orig_termios; raw_termios.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO); raw_termios.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK); raw_termios.c_oflag &= ~OPOST; raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0; rc = tcsetattr(STDOUT_FILENO, TCSAFLUSH, &raw_termios); if (rc < 0) { fprintf(stderr, "Failed setting tty attributes: %s\n", strerror(errno)); goto cleanup; } term_attrs_set = true; /* * Transfer I/O and window changes */ fds[FD_IDX_STDIN].fd = STDIN_FILENO; fds[FD_IDX_STDIN].events = POLLIN; fds[FD_IDX_MASTER].fd = master_fd; fds[FD_IDX_MASTER].events = POLLIN; while (exit_signum == 0) { /* * Handle SIGWINCH */ new_sigwinch_caught = sigwinch_caught; if (new_sigwinch_caught != last_sigwinch_caught) { /* Retrieve window size */ rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &new_winsize); if (rc < 0) { if (errno == EBADF) status = 0; else fprintf(stderr, "Failed retrieving window size: %s\n", strerror(errno)); break; } /* If window size has changed */ if (new_winsize.ws_row != winsize.ws_row || new_winsize.ws_col != winsize.ws_col) { /* Log window size */ clock_gettime(clock_id, ×tamp); tlog_pkt_init_window(&pkt, ×tamp, new_winsize.ws_col, new_winsize.ws_row); grc = tlog_sink_write(sink, &pkt); tlog_pkt_cleanup(&pkt); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed logging window size: %s\n", tlog_grc_strerror(grc)); break; } /* Propagate window size */ rc = ioctl(master_fd, TIOCSWINSZ, &new_winsize); if (rc < 0) { if (errno == EBADF) status = 0; else fprintf(stderr, "Failed setting window size: %s\n", strerror(errno)); break; } winsize = new_winsize; } /* Mark SIGWINCH processed */ last_sigwinch_caught = new_sigwinch_caught; } /* * Deliver I/O */ clock_gettime(clock_id, ×tamp); if (input_pos < input_len) { rc = write(master_fd, input_buf + input_pos, input_len - input_pos); if (rc >= 0) { if (rc > 0) { /* Log delivered input */ tlog_pkt_init_io(&pkt, ×tamp, false, input_buf + input_pos, false, (size_t)rc); grc = tlog_sink_write(sink, &pkt); tlog_pkt_cleanup(&pkt); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed logging input: %s\n", tlog_grc_strerror(grc)); break; } io_pending = true; input_pos += rc; } /* If interrupted by a signal handler */ if (input_pos < input_len) continue; /* Exhausted */ input_pos = input_len = 0; } else { if (errno == EINTR) continue; else if (errno == EBADF || errno == EINVAL) status = 0; else fprintf(stderr, "Failed to write to master: %s\n", strerror(errno)); break; }; } if (output_pos < output_len) { rc = write(STDOUT_FILENO, output_buf + output_pos, output_len - output_pos); if (rc >= 0) { if (rc > 0) { /* Log delivered output */ tlog_pkt_init_io(&pkt, ×tamp, true, output_buf + output_pos, false, (size_t)rc); grc = tlog_sink_write(sink, &pkt); tlog_pkt_cleanup(&pkt); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed logging output: %s\n", tlog_grc_strerror(grc)); break; } io_pending = true; output_pos += rc; } /* If interrupted by a signal handler */ if (output_pos < output_len) continue; /* Exhausted */ output_pos = output_len = 0; } else { if (errno == EINTR) continue; else if (errno == EBADF || errno == EINVAL) status = 0; else fprintf(stderr, "Failed to write to stdout: %s\n", strerror(errno)); break; }; } /* * Handle I/O latency limit */ new_alarm_caught = alarm_caught; if (new_alarm_caught != last_alarm_caught) { alarm_set = false; grc = tlog_sink_flush(sink); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed flushing I/O log: %s\n", tlog_grc_strerror(grc)); goto cleanup; } last_alarm_caught = new_alarm_caught; io_pending = false; } else if (io_pending && !alarm_set) { alarm(IO_LATENCY); alarm_set = true; } /* * Wait for I/O */ rc = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (rc < 0) { if (errno == EINTR) continue; else fprintf(stderr, "Failed waiting for I/O: %s\n", strerror(errno)); break; } /* * Retrieve I/O * * NOTE: Reading master first in case child went away. * Otherwise writing to it can block */ if (fds[FD_IDX_MASTER].revents & (POLLIN | POLLHUP | POLLERR)) { rc = read(master_fd, output_buf, sizeof(output_buf)); if (rc <= 0) { if (rc < 0 && errno == EINTR) continue; status = 0; break; } output_len = rc; } if (fds[FD_IDX_STDIN].revents & (POLLIN | POLLHUP | POLLERR)) { rc = read(STDIN_FILENO, input_buf, sizeof(input_buf)); if (rc <= 0) { if (rc < 0 && errno == EINTR) continue; status = 0; break; } input_len = rc; } } /* Cut I/O log (write incomplete characters as binary) */ grc = tlog_sink_cut(sink); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed cutting-off I/O log: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Flush I/O log */ grc = tlog_sink_flush(sink); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed flushing I/O log: %s\n", tlog_grc_strerror(grc)); goto cleanup; } cleanup: tlog_sink_destroy(sink); tlog_writer_destroy(writer); free(fqdn); /* Restore signal handlers */ for (i = 0; i < ARRAY_SIZE(exit_sig); i++) { sigaction(exit_sig[i], NULL, &sa); if (sa.sa_handler != SIG_IGN) signal(exit_sig[i], SIG_DFL); } /* Restore terminal attributes */ if (term_attrs_set) { rc = tcsetattr(STDOUT_FILENO, TCSAFLUSH, &orig_termios); if (rc < 0 && errno != EBADF) { fprintf(stderr, "Failed restoring tty attributes: %s\n", strerror(errno)); return 1; } } /* Reproduce the exit signal to get proper exit status */ if (exit_signum != 0) raise(exit_signum); return status; }
tlog_grc tlog_json_msg_read(struct tlog_json_msg *msg, struct tlog_pkt *pkt, uint8_t *io_buf, size_t io_size) { const char *timing_ptr; size_t io_len = 0; bool io_full = false; bool pkt_output; assert(tlog_json_msg_is_valid(msg)); assert(!tlog_json_msg_is_void(msg)); assert(tlog_pkt_is_valid(pkt)); assert(tlog_pkt_is_void(pkt)); assert(io_buf != NULL); assert(io_size >= TLOG_JSON_MSG_IO_SIZE_MIN); /* Until the I/O buffer (io_buf/io_size) is full */ do { /* * Read next timing record if the current one is spent */ if (msg->rem == 0) { char type_buf[2]; char type; int read; uint64_t first_val; uint64_t second_val; struct timespec delay; /* Skip leading whitespace */ while (true) { switch (*msg->timing_ptr) { case ' ': case '\f': case '\n': case '\r': case '\t': case '\v': msg->timing_ptr++; continue; } break; } /* Modify timing pointer on the side to be able to rollback */ timing_ptr = msg->timing_ptr; /* If reached the end of timing and so the end of data */ if (*timing_ptr == 0) { /* Return whatever we have */ break; } if (sscanf(timing_ptr, "%1[][><+=]%" SCNu64 "%n", type_buf, &first_val, &read) < 2) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } type = *type_buf; timing_ptr += read; if (type == '[' || type == ']') { if (sscanf(timing_ptr, "/%" SCNu64 "%n", &second_val, &read) < 1) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } timing_ptr += read; } else if (type == '=') { if (sscanf(timing_ptr, "x%" SCNu64 "%n", &second_val, &read) < 1) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } timing_ptr += read; } else { second_val = 0; } /* If it is a delay record */ if (type == '+') { if (first_val != 0) { if (first_val > TLOG_DELAY_MAX_MS_NUM) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } /* If there was I/O already */ if (io_len > 0) { /* * We gotta return the old pos packet and re-read * delay record next time */ break; } delay.tv_sec = first_val / 1000; delay.tv_nsec = first_val % 1000 * 1000000; tlog_timespec_add(&msg->pos, &delay, &msg->pos); } /* Timing record consumed */ msg->timing_ptr = timing_ptr; /* Read next timing record - no I/O from this one */ continue; /* If it is a window record */ } else if (type == '=') { /* If there was I/O already */ if (io_len > 0) { /* * We gotta return the I/O packet and re-read * window record next time */ break; } /* Check extents */ if (first_val > USHRT_MAX || second_val > USHRT_MAX) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } /* Return window packet */ tlog_pkt_init_window(pkt, &msg->pos, (unsigned short int)first_val, (unsigned short int)second_val); /* Timing record consumed */ msg->timing_ptr = timing_ptr; return TLOG_RC_OK; /* If it is a text input record */ } else if (type == '<') { if (first_val > SIZE_MAX) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } msg->output = false; msg->binary = false; msg->rem = first_val; msg->ptxt_ptr = &msg->in_txt_ptr; msg->ptxt_len = &msg->in_txt_len; /* If it is a binary input record */ } else if (type == '[') { if (first_val > SIZE_MAX || second_val > SIZE_MAX) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } msg->output = false; msg->binary = true; msg->rem = second_val; msg->ptxt_ptr = &msg->in_txt_ptr; msg->ptxt_len = &msg->in_txt_len; msg->bin_obj = msg->in_bin_obj; msg->pbin_pos = &msg->in_bin_pos; /* If it is a text output record */ } else if (type == '>') { if (first_val > SIZE_MAX) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } msg->output = true; msg->binary = false; msg->rem = first_val; msg->ptxt_ptr = &msg->out_txt_ptr; msg->ptxt_len = &msg->out_txt_len; /* If it is a binary output record */ } else if (type == ']') { if (first_val > SIZE_MAX || second_val > SIZE_MAX) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } msg->output = true; msg->binary = true; msg->rem = second_val; msg->ptxt_ptr = &msg->out_txt_ptr; msg->ptxt_len = &msg->out_txt_len; msg->bin_obj = msg->out_bin_obj; msg->pbin_pos = &msg->out_bin_pos; } else { assert(false); return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING; } /* Timing record consumed */ msg->timing_ptr = timing_ptr; if (msg->binary) { size_t l; /* Skip replacement characters */ for (; first_val > 0; first_val--) { /* If not enough text */ if (*msg->ptxt_len == 0) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT; } l = tlog_json_msg_utf8_len(*(uint8_t *)*msg->ptxt_ptr); /* If found invalid UTF-8 character in text */ if (l == 0) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT; } /* If character crosses text boundary */ if (l > *msg->ptxt_len) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT; } *msg->ptxt_len -= l; *msg->ptxt_ptr += l; } } /* Ignore zero I/O */ if (msg->rem == 0) { continue; } } /* Stop if the packet I/O is in different direction */ if (io_len == 0) { pkt_output = msg->output; } else if (pkt_output != msg->output) { /* Return whatever we have */ break; } /* * Append (a piece of) I/O to the output buffer */ if (msg->binary) { struct json_object *o; int32_t n; for (; msg->rem > 0; msg->rem--, io_len++, (*msg->pbin_pos)++) { o = json_object_array_get_idx(msg->bin_obj, *msg->pbin_pos); /* If not enough bytes */ if (o == NULL) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_BIN; } /* If supposed byte is not an int */ if (json_object_get_type(o) != json_type_int) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_BIN; } n = json_object_get_int(o); /* If supposed byte value is out of range */ if (n < 0 || n > UINT8_MAX) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_BIN; } io_buf[io_len] = (uint8_t)n; /* If the I/O buffer is full */ if (io_len >= io_size) { io_full = true; break; } } } else { uint8_t b; size_t l; while (msg->rem > 0) { b = *(uint8_t *)*msg->ptxt_ptr; l = tlog_json_msg_utf8_len(b); /* If found invalid UTF-8 character in text */ if (l == 0) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT; } /* If character crosses text boundary */ if (l > *msg->ptxt_len) { return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT; } /* If character crosses the I/O buffer boundary */ if (io_len + l > io_size) { io_full = true; break; } while (true) { io_buf[io_len] = b; io_len++; (*msg->ptxt_len)--; (*msg->ptxt_ptr)++; l--; if (l == 0) { break; } b = *(uint8_t *)*msg->ptxt_ptr; } msg->rem--; } } } while (!io_full); if (io_len > 0) { tlog_pkt_init_io(pkt, &msg->pos, pkt_output, io_buf, false, io_len); } return TLOG_RC_OK; }