#include #include #include #include #include #include #include #include #include using namespace std; // #include // handle Ctrl-C/SIGINT #include // strcasecmp #include #include #include // random() /* Log level guideline: * - ZF_LOG_FATAL - happened something impossible and absolutely unexpected. * Process can't continue and must be terminated. * Example: division by zero, unexpected modifications from other thread. * - ZF_LOG_ERROR - happened something possible, but highly unexpected. The * process is able to recover and continue execution. * Example: out of memory (could also be FATAL if not handled properly). * - ZF_LOG_WARN - happened something that *usually* should not happen and * significantly changes application behavior for some period of time. * Example: configuration file not found, auth error. * - ZF_LOG_INFO - happened significant life cycle event or major state * transition. * Example: app started, user logged in. * - ZF_LOG_DEBUG - minimal set of events that could help to reconstruct the * execution path. Usually disabled in release builds. * - ZF_LOG_VERBOSE - all other events. Usually disabled in release builds. * * *Ideally*, log file of debugged, well tested, production ready application * should be empty or very small. Choosing a right log level is as important as * providing short and self descriptive log message. */ /* #define ZF_LOG_VERBOSE 1 #define ZF_LOG_DEBUG 2 #define ZF_LOG_INFO 3 #define ZF_LOG_WARN 4 #define ZF_LOG_ERROR 5 #define ZF_LOG_FATAL 6 */ // When debugging low-level, use this: // #define ZF_LOG_LEVEL ZF_LOG_VERBOSE // Except this doesn't work. It needs to be anywere the // zf_log.h is included. // LOGGING with file output #include "zf_log.h" FILE *g_log_file; static void file_output_callback(const zf_log_message *msg, void *arg) { (void)arg; *msg->p = '\n'; fwrite(msg->buf, msg->p - msg->buf + 1, 1, g_log_file); fflush(g_log_file); } static void file_output_close(void) { fclose(g_log_file); } static int file_output_open(const char *const log_path) { g_log_file = fopen(log_path, "a"); if (!g_log_file) { ZF_LOGW("Failed to open log file %s", log_path); return 0; } atexit(file_output_close); zf_log_set_output_v(ZF_LOG_PUT_STD, 0, file_output_callback); return 1; } void log_flush(void) { fflush(g_log_file); } // END LOGGING #include "terminal.h" #include "utils.h" struct console_details console; #include "wordplay.h" /* What is the name of the actual, real Mystic executable that we'll be executing and mangling? */ #define TARGET "./mySTIC" // Size of our input and output buffers. #define BSIZE 1024 /* These are harry "timeout" events. These happen when we've been sitting around awhile. */ /* The code to get the username and fullname is useless on telnet connections. */ const char *username = NULL; const char *fullname = NULL; /* This only works for those few idiots that use the horribly broken SSH crap that Mystic uses. */ int locate_user(const char *alias) { FILE *user; char buffer[0x600]; char temp[100]; user = fopen("data/users.dat", "rb"); if (user == NULL) return 0; // Carry on! while (fread(buffer, 0x600, 1, user) == 1) { pcopy(buffer + 0x6d, temp); if (strcasecmp(temp, username) == 0) { pcopy(buffer + 0x8c, temp); fullname = strdup(temp); break; } /* printf("Alias: %s\n", temp); pcopy(buffer + 0x8c, temp ); printf("Full Name: %s\n", temp ); */ } fclose(user); return 1; } /* This is done. :D My buffering system works with stack'em. TO FIX: Stop using c strings, must use char * buffer + int length. MAY CONTAIN NULL VALUES. Rework some things here. Here's the "plan": if buffer is EMPTY: time_idle = 1; // setup for "random timeout value mess" // we're in luck! The last parameter is time interval/timeout. :D timeout.tv_sec = 10; // randrange(10-25) timeout.tv_usec = 0; NOT EMPTY: // we're in luck! The last parameter is time interval/timeout. :D timeout.tv_sec = 0; timeout.tv_usec = 10; // Wild Guess Here? Maybe higher, maybe lower? time_idle = 0; ON READ: read/append to current buffer. We can't use nulls -- what if they are using ZModem, there's nulls in the file! Look for trailing / the very last "\r\n". (I could mangle/chunk it line by line. But I'm not sure I'd need to do that.) Optional "mangle" buffer up to that very point -- and send up to that point. Option #2: Maybe we send everything if program has been running for under 20 seconds. This would allow the ANSI detect to not get screwed up by this new idea. ON TIMEOUT: if time_idle: Activate funny harry timeout events. else: Ok, we *STILL* haven't received any more characters into the buffer -- even after waiting. (Maybe we haven't waited long enough?) send the pending information in the buffer and clear it out. Maybe this is a prompt, and there won't be a \r\n. This allows for cleaner process of "lines" of buffer. We shouldn't break in the midDLE OF A WORD. Downside is that we sit on buffer contents a little while / some amount of time -- which will add some lag to prompts showing up. (LAG? Are you kidding?) ZModem: start: "rz^M**"... 05-12 18:12:15.916 >> rz^M**^XB00000000000000^M<8A>^Q 05-12 18:12:15.928 << **\x18B0100000023be50\r\n\x11 05-12 18:12:15.928 >> *^XC^D 05-12 18:12:15.939 << **\x18B0900000000a87c\r\n\x11 05-12 18:12:15.940 >> *^XC # Start of PK zipfile. 05-12 18:12:15.941 >> PK^C^D^T end: 05-12 18:26:38.700 << **\x18B0100000023be50\r\n\x11 05-12 18:26:38.700 >> **^XB0823a77600344c^M<8A> 05-12 18:26:38.711 << **\x18B0800000000022d\r\n 05-12 18:26:38.712 >> OO^MESC[0m */ // TODO: Get everything above this -- into another file. int main(int argc, char *argv[]) { int master; pid_t pid; int node = -1; if (!file_output_open("horrible_harry.log")) return 2; init_harry(); srandom(time(NULL)); // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML1 -SL0 -ST2 -CUnknown // -Ubugz -PUWISHPASSWORD // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML0 -SL0 -ST0 -CUnknown // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML1 -SL0 -ST2 -CUnknown // -Ubugz -PUWISH // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML0 -SL0 -ST0 -CUnknown // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML0 -SL0 -ST0 -CUnknown // ./mystic -TID9 -IP192.168.0.1 -HOSTUnknown -ML0 -SL1 -ST0 -CUnknown // ./mystic -TID7 -IP192.168.0.1 -HOSTUnknown -ML1 -SL0 -ST2 -CUnknown // -Ubugz -PDUMBWAYTODOTHIS // ./mystic -TID9 -IP192.168.0.1 -HOSTUnknown -ML1 -SL1 -ST2 -CUnknown // -Ubugz -PIDONTUSEPASCAL // SSH: -ML1 -ST2 // Telnet: -ML0 -ST0 // Locate username (if given) in the command line // -U for (int x = 0; x < argc; x++) { if (strncmp("-U", argv[x], 2) == 0) { username = argv[x] + 2; ZF_LOGI("Username: [%s]", username); }; if (strncmp("-SL", argv[x], 3) == 0) { node = atoi(argv[x] + 3) + 1; ZF_LOGI("Node: %d", node); } } if (username != NULL) { locate_user(username); ZF_LOGD("Username: [%s] A.K.A. [%s]", username, fullname); } pid = forkpty(&master, NULL, NULL, NULL); // impossible to fork if (pid < 0) { return 1; } // child else if (pid == 0) { char *args[20]; // max 20 args int x; char new_exec[] = TARGET; // build new args list args[0] = new_exec; for (x = 1; x < argc; x++) { args[x] = argv[x]; }; // null term the list args[x] = NULL; // run Mystic, run! execvp(TARGET, args); } // parent else { struct termios tios, orig1; struct timeval timeout; time_t last_event = 0; // time(NULL); ZF_LOGD("starting"); tcgetattr(master, &tios); tios.c_lflag &= ~(ECHO | ECHONL | ICANON); /* tios.c_iflag &= ~(ICRNL | IXON | BRKINT); tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); tios.c_oflag &= ~(OPOST); */ tcsetattr(master, TCSAFLUSH, &tios); tcgetattr(1, &orig1); tios = orig1; tios.c_iflag &= ~(ICRNL | IXON | BRKINT); tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); tios.c_oflag &= ~(OPOST); // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html tcsetattr(1, TCSAFLUSH, &tios); /* This doesn't need to be static -- because it is part of main. Once main ends, we're done. */ string buffer; buffer.reserve(BSIZE * 2); string play; play.reserve(4096); // int size = 0; // use buffer.size() instead for (;;) { int time_idle; // define estruturas para o select, que serve para verificar qual // se tornou "pronto pra uso" fd_set read_fd; fd_set write_fd; fd_set except_fd; // inicializa as estruturas FD_ZERO(&read_fd); FD_ZERO(&write_fd); FD_ZERO(&except_fd); // atribui o descritor master, obtido pelo forkpty, ao read_fd FD_SET(master, &read_fd); // atribui o stdin ao read_fd FD_SET(STDIN_FILENO, &read_fd); // o descritor tem que ser unico para o programa, a documentacao // recomenda um calculo entre os descritores sendo usados + 1 /* TODO: Figure out how this would work. I'm thinking something like timeouts 30-50 seconds? And as we get closer, 15-25 seconds. */ if (buffer.size() == 0) { // buffer is empty timeout.tv_sec = randrange(10, 20); timeout.tv_usec = 0; time_idle = 1; } else { // buffer is not empty timeout.tv_sec = 0; timeout.tv_usec = 1; time_idle = 0; } if (select(master + 1, &read_fd, &write_fd, &except_fd, &timeout) == 0) { ZF_LOGI("TIMEOUT"); // This means timeout! if (time_idle) { harry_idle_event(STDOUT_FILENO); } else { ZF_LOGI_MEM(buffer.data(), buffer.size(), "TIMEOUT buffer size=%lu", buffer.size()); play.assign(buffer); mangle(STDOUT_FILENO, play); /* ZF_LOGI("console_receive"); console_receive(&console, buffer); ZF_LOGI("write buffer"); write(STDOUT_FILENO, buffer.data(), buffer.size()); */ ZF_LOGI("buffer clear"); buffer.clear(); // size = 0; // buffer is empty now } } // read_fd esta atribuido com read_fd? if (FD_ISSET(master, &read_fd)) { // leia o que bc esta mandando // ZF_LOGD("read (%d) %d bytes", size, BSIZE - size); char read_buffer[BSIZE + 1]; int total; // We may adjust this later on (adjusting read length). if ((total = read(master, read_buffer, BSIZE)) != -1) { // Ok, we've read more into the buffer. ZF_LOGV("Read %d bytes", total); buffer.append(read_buffer, total); // ZF_LOGV_MEM(buffer + size, total, "Read %d bytes:", total); // size += total; // ZF_LOGV_MEM(buffer, size, "Buffer now:"); int pos = buffer.rfind("\r\n"); // rstrnstr(buffer, size, "\r\n"); // >= 0) { if (pos != string::npos) { // found something! pos += 2; // play = buffer.substr() wipes out play's reserve. // play = buffer.substr(0, pos); play.assign(buffer, 0, pos); ZF_LOGI("play %lu size, %lu cap", play.size(), play.capacity()); // play.copy(buffer.data(), pos); //) = buffer.substr(0, pos); buffer.erase(0, pos); mangle(STDOUT_FILENO, play); // ZF_LOGD_MEM(buffer, pos, "mangle buffer %d bytes:", pos); // mangle(STDOUT_FILENO, buffer, pos); // memmove(buffer, buffer + pos, size - pos); // size -= pos; // } else { // ZF_LOGV("position of /r/n not found."); } // Ok, we failed to find CR+NL. What's the buffer size at? if (buffer.size() > BSIZE) { // Ok, there's something going on, and it doesn't look good // unsure if I want to feed this into the console // my guess at this point would be zmodem xfer ZF_LOGI("Buffer %lu bytes, write only...", buffer.size()); // console_receive(&console, buffer); write(STDOUT_FILENO, buffer.data(), buffer.size()); buffer.clear(); } } else break; } // read_fd esta atribuido com a entrada padrao? if (FD_ISSET(STDIN_FILENO, &read_fd)) { // leia a entrada padrao char input[BSIZE]; int r = read(STDIN_FILENO, &input, BSIZE); input[r] = 0; // e escreva no bc if (r > 50) { ZF_LOGI("<< %d bytes", r); } else { ZF_LOGI("<< %s", repr(input)); }; write(master, &input, r); // This is INPUT from the USER // ZF_LOGI_MEM( input, strlen(input), "<< "); } } // Restore terminal tcsetattr(1, TCSAFLUSH, &orig1); ZF_LOGD("exit"); } return 0; }