#include #include #include // usleep() #include #include #include #include #include #include // handle Ctrl-C/SIGINT #include // strcasecmp #include // usleep(), nanonsleep() ? #include #include // random() #include // LOGGING with file output #include "zf_log.h" #include #include 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 void 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; } atexit(file_output_close); zf_log_set_output_v(ZF_LOG_PUT_STD, 0, file_output_callback); } // END LOGGING /* 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 128 /* // Don't need this, zf_log does date/time stamps on output. const char * it_is_now(void) { static char buffer[100]; time_t timer; struct tm* tm_info; timer = time(NULL); tm_info = localtime(&timer); strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_info); return buffer; } void slow_write(int fd, int speed, char * buffer, int len) { int x; for( x = 0; x < len; x++) { usleep(speed); write( fd, &buffer[x], 1); } } */ /** * Display a repr of the given string. * * This converts most \n\r\v\f\t codes, * defaults to \xHH (hex value). */ const char *repr(const char *data) { static char buffer[4096]; char *cp; strcpy(buffer, data); cp = buffer; while (*cp != 0) { char c = *cp; if (isspace(c)) { if (c == ' ') { cp++; continue; }; /* Ok, it's form-feed ('\f'), newline ('\n'), carriage return ('\r'), * horizontal tab ('\t'), and vertical tab ('\v') */ memmove(cp + 1, cp, strlen(cp) + 1); *cp = '\\'; cp++; switch (c) { case '\f': *cp = 'f'; cp++; break; case '\n': *cp = 'n'; cp++; break; case '\r': *cp = 'r'; cp++; break; case '\t': *cp = 't'; cp++; break; case '\v': *cp = 'v'; cp++; break; default: *cp = '?'; cp++; break; } continue; } if (isprint(c)) { cp++; continue; }; // Ok, default to \xHH output. memmove(cp + 3, cp, strlen(cp) + 1); *cp = '\\'; cp++; *cp = 'x'; cp++; char buffer[3]; sprintf(buffer, "%02x", (int)c & 0xff); *cp = buffer[0]; cp++; *cp = buffer[1]; cp++; continue; } return buffer; } struct render { int speed; int effect; } current_render; int render_overlimit = 0; void reset_render(void) { current_render.speed = 0; current_render.effect = 0; render_overlimit = 0; } #define TRIGGER "^" // Max limit we'll sleep before ignoring effects/speed. #define SLEEP_LIMIT 30 int ms_sleep(unsigned int ms) { int result = 0; struct timespec ts = {ms / 1000, (ms % 1000) * 1000000L}; do { struct timespec ts_sleep = ts; result = nanosleep(&ts_sleep, &ts); } while ((-1 == result)); return result; } void render_sleep(void) { if (render_overlimit) return; if (current_render.speed) { // * 100 still too slow. ms_sleep(current_render.speed * 10); } } // This needs work. void write_color(int fd, int color) { char buffer[10]; sprintf(buffer, "\x1b[%dm", color); write(fd, buffer, strlen(buffer)); } /** * process_trigger( fd, *cp ) * * This process a command trigger. * It has seen TRIGGER, and now it is * processing whatever comes after it. * It will perform the process, and * return the char * of whatever is next. */ const char *process_trigger(int fd, const char *cp) { char ch; int i, x, y; ch = toupper(*cp); cp++; switch (ch) { case 'D': i = 0; if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if (isdigit(*cp)) { i *= 10; i += (*cp) - '0'; cp++; }; if ((i > 0) && (i < 80)) { ZF_LOGI("DEL %02d", i); for (x = 0; x < i; x++) { write(fd, "\b \b", 3); } }; break; case 'C': i = 0; if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if (isdigit(*cp)) { i *= 10; i += (*cp) - '0'; cp++; }; write_color(fd, i); break; case 'G': x = 0; if (isdigit(*cp)) { x = (*cp) - '0'; cp++; }; if (isdigit(*cp)) { x *= 10; x += (*cp) - '0'; cp++; }; y = 0; if (isdigit(*cp)) { y = (*cp) - '0'; cp++; }; if (isdigit(*cp)) { y *= 10; y += (*cp) - '0'; cp++; }; break; case 'R': i = 0; if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if ((i > 0) && (i < 10)) { ZF_LOGI("RENDER %d", i); current_render.effect = i; } else { current_render.effect = 0; } break; case 'S': i = 0; if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if ((i > 0) && (i < 10)) { ZF_LOGI("SPEED %d", i); current_render.speed = i; } else { current_render.speed = 0; } break; case 'P': i = 0; if (isdigit(*cp)) { i = (*cp) - '0'; cp++; }; if ((i > 0) && (i < 10)) { ZF_LOGI("PAWS %d", i); // sleep(i); if (!render_overlimit) { sleep(i); }; } break; } return cp; } /** * render_effect( fd, ch ) * * Displays the given character with whatever * rendering effect is currently active. * (If any). */ void render_effect(int fd, char ch) { int effect = current_render.effect; int l; char space = ' '; char bs = '\b'; switch (effect) { case 1: // CHAR + SPC + BS render_sleep(); write(fd, &ch, 1); render_sleep(); write(fd, &space, 1); render_sleep(); render_sleep(); write(fd, &bs, 1); break; case 2: // CHAR + 8 spaces + 8 BS render_sleep(); write(fd, &ch, 1); for (l = 0; l < 8; l++) { render_sleep(); write(fd, &space, 1); } for (l = 0; l < 8; l++) { render_sleep(); write(fd, &bs, 1); } break; case 0: default: // NORMAL render_sleep(); write(fd, &ch, 1); break; } } /** * render( fd, string_out ) * * Render an entire string. * Handles TRIGGER. * Renders with effects. */ void render(int fd, const char *string_out) { const char *cp = string_out; const char *trigger = cp; time_t start = time(NULL); int elapsed; int over = 0; reset_render(); ZF_LOGD("render(%d, %s)", fd, repr(string_out)); // Check our time from time to time. // If we start running long, disable sleeps. while ((trigger = strstr(cp, TRIGGER)) != NULL) { // There is special things to handle in here. while (cp != trigger) { elapsed = time(NULL) - start; if (elapsed > SLEEP_LIMIT) { render_overlimit = 1; current_render.speed = 0; }; // write(fd, cp, 1 ); render_effect(fd, *cp); cp++; }; // ZF_LOGI( "at trigger: (%s)", cp); cp += strlen(TRIGGER); // Ok, we're pointing at the trigger -- do something. cp = process_trigger(fd, cp); // ZF_LOGI( "after trigger: (%s)", cp); }; // We still might be under a rendering effect. while (*cp != 0) { elapsed = time(NULL) - start; if (elapsed > SLEEP_LIMIT) { render_overlimit = 1; current_render.speed = 0; }; // write(fd, cp, 1); render_effect(fd, *cp); cp++; } } // Beanzilla'a no repeats /** * have_seen( list, len, item ) * * Returns 1 (true) if item is in the list. * Rotates the list [x] = [x+1], and * list[len-1] = item, return 0 (false) */ int have_seen(int *list, int len, int item) { int x; for (x = 0; x < len; x++) { if (list[x] == item) { return 1; } }; // Ok, it is something different for (x = 0; x < len - 1; x++) { list[x] = list[x + 1]; }; list[x] = item; return 0; } /** * init_have_seen( list, len ) * * Initialize the have_seen list with -1. * (-1 isn't a valid index, so we start * out with all invalid.) */ void init_have_seen(int *list, int len) { int x; for (x = 0; x < len; x++) { list[x] = -1; } } /* These are harry "timeout" events. These happen when we've been sitting around awhile. */ #define MAX_HARRY_EVENT_DUPS 2 int last_seen_harry_event[MAX_HARRY_EVENT_DUPS]; void harry_event(int fd) { // Make something happen char buffer[100]; int r; // This is no where near finished, BUT! const char *phrases[] = {"Hahaha", "Snicker, snicker", "Boo!", "MeOW", "I see U"}; const char *cp; // Remember the last phrase used, // and don't repeat (the last two)! do { r = random() % 5; } while (have_seen(last_seen_harry_event, MAX_HARRY_EVENT_DUPS, r)); ZF_LOGD("%d => %d %d", r, last_seen_harry_event[0], last_seen_harry_event[1]); cp = phrases[r]; sprintf(buffer, "^S2%s^P2^D%02d", cp, (int)strlen(cp)); ZF_LOGD("harry_event: render(%d, \"%s\")", fd, buffer); render(fd, buffer); } void init_harry() { init_have_seen(last_seen_harry_event, MAX_HARRY_EVENT_DUPS); ZF_LOGD("init => %d %d", last_seen_harry_event[0], last_seen_harry_event[1]); } /* NOTE: Logging is single file, don't use in production! It won't handle multiple writers. */ char *username = NULL; char *fullname = NULL; /* Pascal String Copy. Copy from pascal string, to C String. First char is pascal string length. (Max 255). */ void pcopy(char *pstring, char *str) { int len = (int)*pstring; strncpy(str, pstring + 1, len); str[len] = 0; } /* 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; } // Buffers are BSIZE + 1, so a buffer that size can strcpy safely. regex_t ANSI; regex_t WORDS; regex_t WORD; int init_regex(void) { int ret; char ansi[] = "\x1b\[[0-9]+(;[0-9]+)*?[a-zA-Z]"; char words[] = "[a-zA-Z]+( [a-zA-Z]+)+"; char word[] = "[a-zA-Z]+"; char errorbuf[100]; if (ret = regcomp(&ANSI, ansi, REG_EXTENDED | REG_NEWLINE)) { regerror(ret, &ANSI, errorbuf, sizeof(errorbuf)); ZF_LOGW("Regex %s failed to compile: %s", ansi, errorbuf); return 0; }; if (ret = regcomp(&WORDS, words, REG_EXTENDED | REG_NEWLINE)) { regerror(ret, &WORDS, errorbuf, sizeof(errorbuf)); ZF_LOGW("Regex %s failed to compile: %s", words, errorbuf); return 0; }; if (ret = regcomp(&WORD, word, REG_EXTENDED | REG_NEWLINE)) { regerror(ret, &WORD, errorbuf, sizeof(errorbuf)); ZF_LOGW("Regex %s failed to compile: %s", word, errorbuf); return 0; }; return 1; } int regmatch(regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags) { // returns number of matches found. (Max nmatch) int matches = 0; int offset = 0; int ret; while (matches < nmatch) { ret = regexec(preg, string + offset, nmatch - matches, pmatch + matches, eflags); if (!ret) { int current = offset; offset += pmatch[matches].rm_eo; pmatch[matches].rm_so += current; pmatch[matches].rm_eo += current; matches++; } else if (ret == REG_NOMATCH) { break; } else { break; } } return matches; } #define MAX_MATCH 32 regmatch_t rxmatch[MAX_MATCH]; int rx_match(regex_t *regex, const char *buffer) { int ret; ret = regmatch(regex, buffer, MAX_MATCH, rxmatch, 0); if (0) { for (int i = 0; i < ret; i++) { ZF_LOGI("%d : (%d-%d)", i, rxmatch[i].rm_so, rxmatch[i].rm_eo); } } return ret; } /* Terminal processing section. Monitor lines, position, color. What screen size do I want to emulate? ANSI codes. Do I need this?? */ int random_activate(int w) { int r = random() % 100; if (r <= (w * 10)) { return 1; }; return 0; } /* * The buffer that we've been given is much larger now. * */ int mangle(int fd, char *buffer) { int x, i; int need_render = 0; // changing word case around doesn't need the render int mangled = 0; char *cp; // Ok, we want to look for words to: // MaNGlE , or transpose (both?!) // or possibly transpose words char work[(BSIZE * 4) + 1]; ZF_LOGI("mangle(%s)", repr(buffer)); strcpy(work, buffer); /* NOTE: We copy the buffer, so we can clear out ANSI codes, etc. Otherwise we might mess some ANSI up in the manglying process. */ /* (random) Look for ANSI CLS and: display random spooky texts around, with delays ... then CLS. display ANSI graphic file, with delays ... then CLS */ const char *ANSI_CLS = "\x1b[2J"; cp = strstr(buffer, ANSI_CLS); if (cp != NULL) { ZF_LOGI("seen: ANSI_CLS"); if (random_activate(9)) { char display[100] = ""; ZF_LOGI("mangle(ANSI_CLS)"); // sprintf( display, "^P2..."); // This string actually screws up ANSI detection (takes too long) // strcpy(display, "^P2^S501234567890^P1abcdef^P2g^P3h^P4i^S0^P2"); strcpy(display, "^P2^S301234^P15^S0^P2"); // Move the buffer so there's room for the display string. memmove(cp + strlen(display), cp, strlen(cp) + 1); strncpy(cp, display, strlen(display)); ZF_LOGI("mangle(ANSI_CLS): (%d) %s", (int)strlen(buffer), repr(buffer)); need_render = 1; /* Copy the new buffer over, but hide our "render" code from the remaining mangler steps. */ strcpy(work, buffer); i = cp - buffer; // find offset into "buffer" // apply to work. memset(work + i, ' ', strlen(display)); }; } /* work -- clear out ANSI so we don't mangle ANSI codes. */ x = rx_match(&ANSI, work); char replace_with = ' '; if (x > 0) { ZF_LOGD("found %d ANSI", x); for (i = 0; i < x; i++) { memset(work + rxmatch[i].rm_so, replace_with, rxmatch[i].rm_eo - rxmatch[i].rm_so); }; ZF_LOGD("Work Now : (%d) %s", (int)strlen(work), repr(work)); } // ZF_LOGI("mangle: %s", repr(work)); /* (random) Locate words (in work), and possibly flip them around. Transpose words. Transpose case. Transpose letters. */ x = rx_match(&WORDS, work); ZF_LOGD("found %d WORDS", x); if (x > 0) { for (i = 0; i < x; i++) { // Do things here. if (i % 3 == 0) { for (int p = rxmatch[i].rm_so; p < rxmatch[i].rm_eo; p++) { buffer[p] = tolower(buffer[p]); mangled++; } } else { if (i % 3 == 1) { for (int p = rxmatch[i].rm_so; p < rxmatch[i].rm_eo; p++) { buffer[p] = toupper(buffer[p]); mangled++; } } } } } /* (random) Locate single words, and transpose them. Transpose case. Transpose letters. */ /* (random) Display up to certain point. Delay. Print some characters slowly. Delay. */ if (need_render) { ZF_LOGD("HH %d : (%d) %s", need_render, (int)strlen(buffer), repr(buffer)); } else { if (mangled) { ZF_LOGD("Mangled %d : %s", mangled, repr(buffer)); } } if (need_render) { render(fd, buffer); } else { write(fd, buffer, strlen(buffer)); }; return need_render && mangled; } int harry_happens(time_t *last_event, int wakeup) { time_t now = time(NULL); int elapsed = now - *last_event; if (elapsed > wakeup) { // Ok! It's been too long since we've done something. *last_event = now; return 1; } return 0; } int main(int argc, char *argv[]) { int master; pid_t pid; int node = -1; file_output_open("horrible_harry.log"); 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 // -PUP2LAT3 // ./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 // -PUP2LAT3 // ./mystic -TID9 -IP192.168.0.1 -HOSTUnknown -ML1 -SL1 -ST2 -CUnknown -Ubugz // -PUP2LAT3 // 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 = argv[x][3] - '0' + 1; ZF_LOGI("Node: %d", node); } } if (username != NULL) { locate_user(username); ZF_LOGD("Username: [%s] A.K.A. [%s]", username, fullname); } if (!init_regex()) return 2; // With IGNBRK I don't think I need this anymore. (Nope!) // signal(SIGINT, SIG_IGN); 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; args[0] = TARGET; for (x = 1; x < argc; x++) { args[x] = argv[x]; }; args[x] = NULL; // run Mystic, run! execvp(TARGET, args); } // parent else { // remove the echo // ICANON - raw mode. No more line buffering! 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); tcsetattr(master, TCSAFLUSH, &tios); tcgetattr(1, &orig1); tios = orig1; tios.c_iflag &= ~(ICRNL | IXON); // Disable software flow control tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html // ISIG should be Ctrl-C and Ctrl-Z IGNBRK + tcsetattr(1, TCSAFLUSH, &tios); for (;;) { // 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. */ // we're in luck! The last parameter is time interval/timeout. :D timeout.tv_sec = 10; timeout.tv_usec = 0; // select(master+1, &read_fd, &write_fd, &except_fd, NULL); if (select(master + 1, &read_fd, &write_fd, &except_fd, &timeout) == 0) { // This means timeout! ZF_LOGI("TIMEOUT"); harry_event(STDOUT_FILENO); } char input[BSIZE + 1]; static char output[(BSIZE * 4) + 1]; int total; // read_fd esta atribuido com read_fd? if (FD_ISSET(master, &read_fd)) { // leia o que bc esta mandando if ((total = read(master, &output, BSIZE)) != -1) { // e escreva isso na saida padrao output[total] = 0; // if ( harry_happens( &last_event, 5)) { if (1) { ZF_LOGI("harry_happens"); if (mangle(STDOUT_FILENO, output) == 0) { // failed, so. Try again. last_event = 0; } } else { write(STDOUT_FILENO, &output, total); // This is OUTPUT from the BBS // ZF_LOGI( ">> %s", repr(output)); ZF_LOGI(">> %d chars", (int)strlen(output)); // I think repr is flipping out here. :( // ZF_LOGI( ">> %d", (int)strlen(repr(output))); } } else break; } // read_fd esta atribuido com a entrada padrao? if (FD_ISSET(STDIN_FILENO, &read_fd)) { // leia a entrada padrao total = read(STDIN_FILENO, &input, BSIZE); input[total] = 0; // e escreva no bc ZF_LOGI("<< %s", repr(input)); write(master, &input, total); // This is INPUT from the USER // ZF_LOGI_MEM( input, strlen(input), "<< "); } } // Restore terminal tcsetattr(1, TCSAFLUSH, &orig1); ZF_LOGD("exit"); } return 0; }