#include #include #include // usleep() #include #include #include #include #include #include // handle Ctrl-C/SIGINT #include // usleep(), nanonsleep() ? #include // strcasecmp #include // random() #include #define TARGET "./mySTIC" #define BSIZE 1024 #define LOGGING #ifdef LOGGING FILE * fp; #endif 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); } } 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 "^" #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) { ms_sleep(current_render.speed * 100); } } 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)) { #ifdef LOGGING fprintf(fp, "DEL %02d\n", i); #endif 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++; }; 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) ) { #ifdef LOGGING fprintf(fp, "RENDER %d\n", i); #endif 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) ) { #ifdef LOGGING fprintf(fp, "SPEED %d\n", i); #endif 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) ) { #ifdef LOGGING fprintf(fp, "PAWS %d\n", i); #endif // sleep(i); if (!render_overlimit) { sleep(i); }; } break; } return cp; } void render_effect(int fd, char ch) { int effect = current_render.effect; render_sleep(); int x = write(fd, &ch, 1); return; switch (effect) { case 0: default: write(fd, &ch, 1); break; } } 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(); #ifdef LOGGING fprintf(fp, "render(%d, %s)\n", fd, string_out); fflush(fp); #endif // Check our time from time to time. // If we start running long, kill 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; }; #ifdef LOGGING fprintf(fp, "re(%c)\n", *cp); fflush(fp); #endif // write(fd, cp, 1 ); render_effect(fd, *cp); cp++; }; #ifdef LOGGING fprintf(fp, "at trigger: (%s)\n", cp); fflush(fp); #endif cp += strlen(TRIGGER); // Ok, we're pointing at the trigger -- do something. cp = process_trigger(fd, cp); #ifdef LOGGING fprintf(fp, "after trigger: (%s)\n", cp); fflush(fp); #endif }; // We still might be under a rendering effect. while (*cp != 0) { // write(fd, cp, 1); render_effect(fd, *cp); cp++; } } void harry_event(int fd) { // Make something happen char buffer[100]; int r = random() % 5; const char * phrases[] = { "Hahaha", "Snicker, snicker", "Boo!", "MeOW", "I see U" }; const char * cp; #ifdef LOGGING fprintf(fp, "harry_event(%d)\n", fd); fflush(fp); #endif cp = phrases[r]; sprintf(buffer, "^S2%s^P2^D%02d", cp, (int)strlen(cp)); // write(fd, ) is working just fine here! // write(fd, buffer, strlen(buffer)); #ifdef LOGGING fprintf(fp, "harry_event: render(%d, \"%s\")\n", fd, buffer); fflush(fp); #endif render(fd, buffer); } /* NOTE: Logging is single file, don't use in production! It won't handle multiple writers. */ char * username = NULL; char * fullname = NULL; void pcopy(char * pstring, char * str) { int len = (int)*pstring; strncpy( str, pstring+1, len ); str[len] = 0; } 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. void mangle( char * buffer ) { // Ok, we want to look for words to: // MaNGlE , or transpose (both?!) // or possibly transpose words } int main(int argc, char * argv[]) { int master; pid_t pid; #ifdef LOGGING // FILE * fp; fp = fopen("mystic_pipe.data", "wb"); if ( fp == NULL ) { return 2; } #endif 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 // // -U for (int x = 0; x < argc; x++) { if (strncmp("-U", argv[x], 2) == 0) { username = argv[x] + 2; #ifdef LOGGING fprintf(fp, "Username: [%s]\n", username); #endif break; } } if (username != NULL) { locate_user(username); #ifdef LOGGING fprintf(fp, "Username: [%s] A.K.A. [%s]\n", username, fullname); #endif } // With IGNBRK I don't think I need this anymore. // 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; // char *args[] = { "vim", "test.txt", NULL }; // argv; //{ 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; int last_event; 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 // 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! harry_event(STDOUT_FILENO); #ifdef LOGGING fprintf(fp, "%s : TICK\n", it_is_now()); fprintf(fp, "STDOUT is %d\n", STDOUT_FILENO); #endif } char input[BSIZE + 1]; char output[BSIZE + 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 (random() % 20 < 3) { mangle( output ); } write(STDOUT_FILENO, &output, total); // This is OUTPUT from the BBS #ifdef LOGGING fprintf(fp, ">> [%s]\n", output); #endif } 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 write(master, &input, total); // This is INPUT from the USER #ifdef LOGGING fprintf(fp, "<< [%s]\n", input); #endif } } tcsetattr(1, TCSAFLUSH, &orig1); #ifdef LOGGING fclose(fp); #endif } return 0; }