#include #include #include #include #include #include #include #include #define BUFFER_LENGTH 50 #define OTPCHALL_LENGTH 10 #define OTPRESP_LENGTH 30 #define OTPCOMM_LENGTH 70 #define NULL_CHAR_SIZE 1 /* number of file descriptors to poll */ #define POLL_FDS 2 #define RESTORE_TERMIOS 1 #define DONT_RESTORE_TERMIOS 0 struct termios termios_stdin_initial, termios_stdin_new; void restore_termios_stdin(void) { if (tcsetattr(STDIN_FILENO, TCSANOW, &termios_stdin_initial) != 0) perror("Error: restore termios - type 'stty echo'\n"); } void exit_failure(char *mess, char restore) { perror(mess); if (restore) restore_termios_stdin(); exit(EXIT_FAILURE); } void printhelp(void) { printf("antikos 0.1b, 14 September 2002\n" "This program comes with ABSOLUTELY NO WARRANTY.\n" "This is free software, and you are welcome to redistribute " "it\nunder the GNU General Public Licence, version 2.\n" "See the http://www.gnu.org for licence details.\n\n" "Antikos attempts to login to the remote host via telnet " "periodically.\n" "It uses the otp-md5 authentication via the Java OTP Calculator.\n\n" "Project homepage: http://verunek.host.sk\n" "Author: Jiri VERUNEK " "\n\n"); printf("Usage: antikos [-j path] [-m e-mail] [-l loginname] " "-p password host\n" "Options:\n" " -j path Path to the Java OTP Calculator. You can " "download that\n" " from http://www.cs.umd.edu/users/harry/jotp/\n" " This option overrides CLASSPATH environment " "variable.\n" " -l loginname Login using this username.\n" " -p password Login using this password.\n" " -h Print this help message.\n" " host Login to this host by telnet.\n"); } int main(int argc, char *argv[]) { int opt, pid_stat; char *jpath, *loginname, *pass, *host; char buffer[BUFFER_LENGTH]; /* the main buffer */ char *ptrchar; /* general purpose char pointer */ pid_t pid; char otpchall[OTPCHALL_LENGTH + 1]; /* OTP challenge buffer */ char *otpchall_ptr; char chars_to_otpchall; /* number of chars to be still written to the otpchall[] buffer */ char otpresp[OTPRESP_LENGTH]; /* buffer for the jOTP output */ FILE *otpresp_fd; char otpcomm[OTPCOMM_LENGTH]; /* buffer for the command to execute the Java OTP calculator. */ int child_to_parent[2], parent_to_child[2]; /* pipes */ struct pollfd mypoll[POLL_FDS]; char poll_break, find_challs; ssize_t bytesread, otpbytesread; char *challenges[] = {"login:", "OTP Challenge: otp-md5", \ "Response:", "Stisknete klavesu ", NULL}; int chall_index; /* index of the current string in challenges[] */ char *chall_ptr, *buffer_ptr; /* pointers to the compared chars */ jpath = loginname = pass = host = (char *)0; while ((opt = getopt(argc, argv, "j:l:p:h")) != -1) { switch (opt) { case 'j': if ((jpath = malloc(strlen(optarg) + 1)) == NULL) exit_failure("Error: allocating memory", DONT_RESTORE_TERMIOS); strcpy(jpath, optarg); break; case 'l': if ((loginname = malloc(strlen(optarg) + 1)) == NULL) exit_failure("Error: allocating memory", DONT_RESTORE_TERMIOS); strcpy(loginname, optarg); break; case 'p': if ((pass = malloc(strlen(optarg) + 1)) == NULL) exit_failure("Error: allocating memory", DONT_RESTORE_TERMIOS); strcpy(pass, optarg); break; case ':': fprintf(stderr, "Option %d needs argument\n", opt); case '?': case 'h': printhelp(); exit(EXIT_FAILURE); } } if ((jpath == NULL) && (getenv("CLASSPATH") == NULL)) { fprintf(stderr, "Please specify path to the Java OTP Calculator " "by -j option.\n"); exit(EXIT_FAILURE); } if ((argc - optind) > 1) { fprintf(stderr, "Too many arguments.\n"); printhelp(); exit(EXIT_FAILURE); } if (optind < argc) { if ((host = malloc(strlen(argv[optind]) + 1)) == NULL) exit_failure("Error: allocating memory", DONT_RESTORE_TERMIOS); strcpy(host, argv[optind]); } else { /* scanf() has/had a lot of bugs */ printf("Enter hostname: "); fgets(buffer, BUFFER_LENGTH, stdin); ptrchar = strchr(buffer, '\n'); if ((ptrchar - buffer) > BUFFER_LENGTH) { fprintf(stderr, "Error: Hostname is too long. Please specify that as " "command line parameter.\n"); printhelp(); exit(EXIT_FAILURE); } if ((host = malloc(ptrchar - buffer + 1)) == NULL) exit_failure("Error: allocating memory", DONT_RESTORE_TERMIOS); strncpy(host, buffer, ptrchar - buffer); *(ptrchar - buffer + host) = '\0'; } if (loginname == NULL) { if ((loginname = getenv("LOGNAME")) == NULL) { /* scanf() has/had a lot of bugs */ printf("Enter loginname: "); fgets(buffer, BUFFER_LENGTH, stdin); ptrchar = strchr(buffer, '\n'); if ((ptrchar - buffer) > BUFFER_LENGTH) { fprintf(stderr, "Error: Loginname is too long. Please use the -l " "option.\n"); printhelp(); exit(EXIT_FAILURE); } if ((loginname = malloc(ptrchar - buffer + 1)) == NULL) exit_failure("Error: allocating memory", DONT_RESTORE_TERMIOS); strncpy(loginname, buffer, ptrchar - buffer); *(ptrchar - buffer + loginname) = '\0'; } } if (tcgetattr(STDIN_FILENO, &termios_stdin_initial) != 0) perror("Error: tcgetattr(stdin)\n"); if (pass == NULL) { /* Suppress printing chars of the pressed keys. */ termios_stdin_new = termios_stdin_initial; termios_stdin_new.c_lflag &= ~ECHO; /* read() will freeze the process until he received at least VMIN chars or until the VTIME elapses since the last character was received. */ termios_stdin_new.c_cc[VMIN] = 1; termios_stdin_new.c_cc[VTIME] = 0; if (tcsetattr(STDIN_FILENO, TCSANOW,&termios_stdin_new) != 0) perror("Error: tcsetattr(stdin)\n"); /* scanf() has/had a lot of bugs */ printf("Enter password: "); fgets(buffer, BUFFER_LENGTH, stdin); ptrchar = strchr(buffer, '\n'); if ((ptrchar - buffer) > BUFFER_LENGTH) { fprintf(stderr, "Password is too long. Please use the -p option.\n"); printhelp(); restore_termios_stdin(); exit(EXIT_FAILURE); } if ((pass = malloc(ptrchar - buffer + 1)) == NULL) exit_failure("Error: allocating memory", DONT_RESTORE_TERMIOS); strncpy(pass, buffer, ptrchar - buffer); *(ptrchar - buffer + pass) = '\0'; restore_termios_stdin(); } while(1) { /* Working with pipes is similar like with file descriptors except that you have to use pipe_fd[1] as the file descriptor for writing and pipe_fd[0] for reading. Everything you wrote to pipe_fd[1] you can read from pipe_fd[0] in the same order. We will use two pipes - child_to_parent and parent_to_child. So the child (telnet) will read from parent_to_child[0] and write to child_to_parent[1] and the parent will read from child_to_parent[0] and write to parent_to_child[1]. This works due to the fork() creates child process that inherites the parent file descriptors. */ pipe(child_to_parent); pipe(parent_to_child); switch (pid = fork()) { case (pid_t)-1: exit_failure("Error: forking process", DONT_RESTORE_TERMIOS); case (pid_t)0: /* Redirect stdin to the output of the parent_to_child pipe. */ if (dup2(parent_to_child[0], STDIN_FILENO) == -1) exit_failure("Error: dup2()", DONT_RESTORE_TERMIOS); close(parent_to_child[0]); /* Redirect the stdout to the input of the child_to_parent pipe. */ dup2(child_to_parent[1], STDOUT_FILENO); close(child_to_parent[1]); /* Close the remaining child pipes descriptors. */ close(parent_to_child[1]); close(child_to_parent[0]); execlp("telnet", "telnet", host, (char *)0); break; default: close(parent_to_child[0]); close(child_to_parent[1]); /* Suppress printing chars of the pressed keys, turn off canonical mode. */ /* Terminal is in one of the two modes. In CANONICAL mode the input is served by the shell and the application receives the input after the user pressed Enter. So you can use the backspace in this mode. In NON-CANONICAL mode the application served input by itself. */ termios_stdin_new = termios_stdin_initial; termios_stdin_new.c_lflag &= ~ICANON; termios_stdin_new.c_lflag &= ~ECHO; /* read(stdin) will not freeze when having nothing in stdin */ termios_stdin_new.c_cc[VMIN] = 0; termios_stdin_new.c_cc[VTIME] = 0; if (tcsetattr(STDIN_FILENO, TCSANOW, &termios_stdin_new) != 0) exit_failure("Error: tcsetattr(stdin)\n", DONT_RESTORE_TERMIOS); /* We will freeze this process until the user will press any key or the telnet want to write something to "stdout". This will be done by poll() function. */ mypoll[0].fd = STDIN_FILENO; mypoll[0].events = POLLIN; /* Waiting for some data to read. */ mypoll[1].fd = child_to_parent[0]; mypoll[1].events = POLLIN; chall_ptr = challenges[0]; /* Move to the first expectated telnet output string. */ chall_index = 0; otpchall_ptr = otpchall; chars_to_otpchall = 0; /* We are not reading the OTP Challenge now. */ find_challs = 1; /* We need to watch the telnet output. */ poll_break = 0; /* No error still occured. */ do { /* wait for data to read from the stdin or the child_to_parent[0]. */ if (poll(mypoll, POLL_FDS, -1) > 0) { /* No error occured. */ /* Has the child_to_parent pipe changed? */ if (mypoll[1].revents > 0) { /* Is some data in the child_to_parent pipe? Did telnet write something to stdout? */ if (mypoll[1].revents != POLLIN) { /* An error occured. */ poll_break = 1; break; } else { /* Yes, there is some data waiting to be written to the stdout. Well, print them. */ bytesread = read(child_to_parent[0], &buffer, BUFFER_LENGTH); write(STDOUT_FILENO, &buffer, bytesread); /* Do we still need to watch the telnet output? */ if (find_challs) { /* Compare the telnet output in buffer[] with the expectated output in challenges[] char by char. */ buffer_ptr = buffer; for (; (((buffer_ptr - buffer) < bytesread) && (find_challs)) ; ) { /* We are still comparing the valid data. */ /* If we are reading OTP Challenge now we still need to read chars_to_otpchall chars. */ if (chars_to_otpchall) { *otpchall_ptr++ = *buffer_ptr; chars_to_otpchall--; } if (*chall_ptr == '\0') { /* We have finished reading of some expectated string. */ if (chall_index == 0) { /* We have read "login: ". */ /* Child has written the challenge, but it is still not reading. */ /* Send me e-mail if you know the better sollution. */ usleep(200000); /* Send loginname to the telnet. */ write(parent_to_child[1], loginname, strlen(loginname)); write(parent_to_child[1], "\n", strlen("\n")); } else if (chall_index == 1) { /* We have read "OTP Challenge: otp-md5". */ /* The next OTPCHALL_LENGTH chars is the OTP challenge */ chars_to_otpchall = OTPCHALL_LENGTH; otpchall[OTPCHALL_LENGTH] = '\0'; buffer_ptr++; } else if (chall_index == 2) { /* We have read "Response:" */ /* Call the JavaOTP Calculator. */ strcpy(otpcomm, "java"); if (jpath != NULL) { strcat(otpcomm, " -cp "); strcat(otpcomm, jpath); } strcat(otpcomm, " jotp "); strcat(otpcomm, otpchall); strcat(otpcomm, " "); strcat(otpcomm, pass); strcat(otpcomm, " md5 |tail -1"); if ((otpresp_fd = popen(otpcomm, "r")) == NULL) exit_failure("Error: popen()", RESTORE_TERMIOS); otpbytesread = fread(otpresp, sizeof(char), OTPRESP_LENGTH, otpresp_fd); pclose(otpresp_fd); /* usleep(200000); Java is slow enough. ;-) */ /* Write the output of the JavaOTP Calculator to the telnet. */ write(parent_to_child[1], otpresp, otpbytesread); } else if (chall_index == 3) { /* We have read "Stisknete klavesu " (press Enter key). */ /* Child has written the challenge, but it is still not reading */ /* Send me e-mail if you know the better sollution. */ usleep(200000); /* Send pressing Enter to telnet */ write(parent_to_child[1], "\n", strlen("\n")); } /* Move to the next expectated string. */ chall_index++; chall_ptr = challenges[chall_index]; if (chall_ptr == NULL) { /* We need not to watch the telnet output. */ find_challs = 0; break; } } else if (*chall_ptr == *buffer_ptr) { /* The compared chars are the same so move to the next two. */ chall_ptr++; buffer_ptr++; } else { /* The compared chars are different. Move to the next char in telnet output in buffer[] and to the begin of the current expectated string in challenges[]. */ chall_ptr = challenges[chall_index]; buffer_ptr++; } } } } } /* Has the stdin changed? */ else if (mypoll[0].revents > 0) { /* Is some data in the stdin? Has the user something write? */ if (mypoll[0].revents != POLLIN) { exit_failure("Error: polling stdin", RESTORE_TERMIOS); } /* There is some data waiting to be written to the telnet. Well, do that. */ bytesread = read(STDIN_FILENO, &buffer, BUFFER_LENGTH); write(parent_to_child[1], &buffer, bytesread); } } else { exit_failure("Error: poll()", RESTORE_TERMIOS); } } while (! poll_break); wait(&pid_stat); } close(parent_to_child[1]); close(child_to_parent[0]); } /* These lines are not executed in this version. */ restore_termios_stdin(); return(EXIT_SUCCESS); }