Path: utzoo!attcan!uunet!lll-winken!ncis.tis.llnl.gov!helios.ee.lbl.gov!pasteur!ucbvax!decwrl!hplabs!hp-pcd!hplsla!hpubvwa!mechp10!klein From: klein@mechp10.UUCP (Greg Klein) Newsgroups: comp.sources.d Subject: callback source Message-ID: <180004@mechp10.UUCP> Date: 9 May 89 20:01:22 GMT Organization: Microcomputer Electronics Corp,Kirkland,WA Lines: 931 About a month ago I posted a request for getty and login source code that I could use to write a callback program. Here's what I came up with. You get a login and password prompt as usual and then the system disconnects your line and calls you back at your remote phone number (in your .cbtelno file). Your modem answers the call and you are then prompted for a normal login. There are two programs, gettycb and logincb. gettycb is started by init from an /etc/inittab entry just like getty and gettycb invokes logincb after a user name is entered. Since gettycb is functionally equivalent to getty except that it invokes logincb instead of login, I could have simply not bothered with gettycb at all and just replaced /bin/login with the callback version. However, I needed to retain login for regular non-callback) logins. The man pages should be adequate documentation. I'm running this on a HP 350 under HP-UX (an AT&T/Berkley UNIX merge). I haven't tried it on any other configuration. I WAS able to get PD login and getty source code, but not until after I finished writing the callback program, so I didn't use the source. It's a bit dirty but it works for me. Feel free to email me any improvements. ========================================================================= Greg Klein | Microcomputer Electronics Corp. voice: (206)821-2800 x702 | 12421 Willows Rd, NE uucp: ...!hplabs!hpubvwa!mechp!klein | Kirkland WA, USA 98034 ========================================================================= # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by Greg Klein on Tue May 9 12:52:55 1989 # # This archive contains: # mk.gettycb mk.logincb gettycb.1 logincb.1 # gettycb.c logincb.c # LANG=""; export LANG echo x - mk.gettycb cat >mk.gettycb <<'@EOF' CFLAGS = -O gettycb: @EOF chmod 644 mk.gettycb echo x - mk.logincb cat >mk.logincb <<'@EOF' CFLAGS = -O logincb: @EOF chmod 644 mk.logincb rm -f /tmp/uud$$ (echo "begin 777 /tmp/uud$$\n \nend" | uudecode) >/dev/null 2>&1 if [ -f /tmp/uud$$ ] then rm -f /tmp/uud$$ unpacker=uudecode else echo Compiling unpacker for non-ascii files pwd=`pwd`; cd /tmp cat >unpack$$.c <<-'EOF' #include #define DEC(c) (((c) - ' ') & 077) main() { int n; char dest[128], a,b,c,d; scanf("begin %o ", &n); gets(dest); if (freopen(dest, "w", stdout) == NULL) { perror(dest); exit(1); } while ((n=getchar()) != EOF && (n=DEC(n))!=0) { while (n>0) { a = DEC(getchar()); b = DEC(getchar()); c = DEC(getchar()); d = DEC(getchar()); if (n-- > 0) putchar(a << 2 | b >> 4); if (n-- > 0) putchar(b << 4 | c >> 2); if (n-- > 0) putchar(c << 6 | d); } n=getchar(); } exit(0); } EOF cc -o unpack$$ unpack$$.c rm unpack$$.c cd $pwd unpacker=/tmp/unpack$$ fi echo x - gettycb.1 '[non-ascii]' $unpacker <<'@eof' begin 644 gettycb.1 M+F1E?48@(" @(" @(" @(" @(" @(" @(" @(" @(" @("!<(B!R961E9FEN M92!F;V]T97(@;6%C2!O;B!T:&%T(&QI;F4@2!T:&4@=7-E7-T96TN M"BY0"DYO2!S=6-H(&%S.@HB9S(Z,CIR97-P87=N.B]U&%M<&QE('1T>3 T(&ES M(&%L2!R97-P87=N:6YG(&=E='1Y8V(L M(&%D9&ET:6]N86P@8V%L;',*8V%N(&)E(&%N3 T*0IU2P@:68@+V5T8R]I&ES=',L(&=E='1Y8V(@<')I;G1S(&ET2!T:&4@<')O;7!T(")L;V=I;CH@ M(BX@($=E='1Y8V(@2!A='1E;7!T2!I0IO=71P=70L(&-AF5D(&%S(&-H87)A8W1E2X*+E-(($9)3$53"B]U2P@;VYE('=I;&P@9FEN9 IA(&YU;6)E6-B(&%N9 IG971T>2!A2!C97)T86EN('!R;V=R86UM86)L92 *8VAA2!A6-B(&%R92!N;W0@&-E<'0@=&AA="!I;G-T96%D(&]F(&5X M96,G:6YG"BAE>&5C*#(I*2!A(&-O;6UA;F0@:6YT97)P2!I;B *+V5T8R]P87-S=V0@*'5S M=6%L;'D@82!S:&5L;"!S=6-H(&%S('-H*#$I*2P@;&]G:6YC8B!S<&%W;G,@ M8W0H,2D@=&\*8V%L;"!B86-K('1H92!U6-B"F%N9"!L;V=I M;F-B('1H=7,@<')O=FED92!A('-E8W5R92!R96UO=&4@;&]G:6X@=&\@=&AE M('-Y6]UF5D('5S97(@=&\@ M;&]G(&EN(&]N('1H92!S>7-T96T*8GD@=')I86P@86YD(&5R6]U(&1O(&YO="!C;VUP;&5T M92!T:&4@;&]G:6X@2!D:7-C;VYN96-T960N"BY0"D]N8V4@82!V M86QI9"!L;V=I;B!A;F0@<&%S7,@8F4@ M82!C86QL;W5T(&QI;F4*879A:6QA8FQE*2X@($-T*#$I(&AA;F=S('5P('1H M92!L:6YE(&%N9"!S<&%W;G,@82!G971T>2@Q*0IO;B!T:&%T(&QI;F4@2X*+E10"BY"4B B8V%N;F]T(&-A;&P@8F%C:R M+2!I;G9A;&ED('1E M;'!H;VYE(&YU;6)Egettycb.c <<'@EOF' /* * gettycb.c * * gettycb is like getty(1) (set terminal type, modes, speed, and line * discipline) but used with logincb to call back the terminal. This * provides a secure remote access to the system. * * Normally, there should be only one instance of gettycb running on a * system. That is, only one line is used to answer calls. The * /etc/inittab file would then contain the single gettycb entry such as: * "g2:2:respawn:/etc/gettycb -t 30 tty04 1200." Once gettycb * answers a call, it exec's logincb which spawns the program ct(1) to do * the callback. Ct uses the first available callout device it finds in * the L-Devices file (or Devices file for HDB uucp). If the call-in line * (example tty04) is also used as a call-out line (example cul04) then * that call-out line should be the last one listed in the L-Devices file * so that the one call-in line will remain available to answer calls until * it is also finally chosen to call back on. * */ #include #include #include #include #include #include #include #include struct baud { int bin; char ascii[5]; } baud[] = { {B300,"300"}, {B1200,"1200"}, {B2400,"2400"}, }; int baud_count = sizeof(baud)/sizeof(struct baud); char usage[] = "usage: gettycb [-l] [-d] [-t timeout] line [300|1200|2400]\n"; main(argc,argv,envp) int argc; char **argv; char **envp; { void login_timeout(); long nap(); void setbuf(); unsigned long alarm(); void exit(); extern char *optarg; extern int optind; int cfd, i, timeout, c, log, debug; struct termio tty; char ttyname[64]; char logname[64], *p; FILE *ifp,*dfp; int baud_index; #ifndef DEBUG /* compile with -DDEBUG to use current terminal for i/o */ /* Close stdin, stdout, stderr. This is actually only necessary when * this program is not started by init. */ close(0); close(1); close(2); /* Write error messages to the console since this program is usually * started by init. */ cfd = open("/dev/console",O_WRONLY); /* stdin */ dup(cfd); /* stdout */ dup(cfd); /* stder */ #endif timeout = -1; /* default is no timeout (negative values are invalid)*/ log = 0; /* default is don't log callback */ debug = 0; /* default is no gettycb debug logging */ while ( (c = getopt(argc,argv,"ldt:")) != EOF ) switch(c) { case 'd': debug = 1; break; case 'l': log = 1; break; case 't': for ( i = 0; i < strlen(optarg); i++) { /* only non-negative timeouts allowed */ if ( !isdigit(optarg[i]) ) { fprintf(stderr,"%s: invalid timeout \"%s\".\n",argv[0],optarg); uexit(1); } } timeout = atoi(optarg); break; case '?': uexit(1); break; } if ( optind == argc ) { fprintf(stderr,"%s: no terminal line specified.\n",argv[0]); uexit(1); } strcpy(ttyname,"/dev/"); strcat(ttyname,argv[optind]); /* get speed argument if any */ baud_index = 0; /* default to 300 baud */ if ( ++optind < argc ) { for ( ; baud_index < baud_count; baud_index++ ) { if ( strcmp(argv[optind],baud[baud_index].ascii) == 0 ) break; } if ( baud_index > baud_count ) { fprintf(stderr,"%s: invalid speed \"%s\".\n",argv[0],argv[optind]); uexit(1); } } #ifndef DEBUG /* compile with -DDEBUG to use current terminal for i/o */ /* Close console so that tty will become stdin on open */ close(0); close(1); close(2); /* Make the tty to open the controlling terminal */ if ( setpgrp() < 0 ) { cfd = open("/dev/console",O_WRONLY);dup(cfd);dup(cfd); fprintf(stderr,"%s: setpgrp failed. errno: %d\n",argv[0],errno); } if( open(ttyname,O_RDWR) != 0) { /* open stdin */ cfd = open("/dev/console",O_WRONLY);dup(cfd);dup(cfd); fprintf(stderr,"%s: cannot open \"%s\". errno: %d\n",argv[0],ttyname,errno); uexit(1); } if ( dup(0) < 0 || dup(0) < 0 ) { /* create stdout, stderr */ close(0); cfd = open("/dev/console",O_WRONLY);dup(cfd);dup(cfd); fprintf(stderr,"%s: cannot create stdout and stderr.\n",argv[0]); exit(1); } #endif if ( debug ) { if ( (dfp = fopen("/tmp/gettycb.dbg","a")) == NULL ) debug = 0; else fprintf(dfp,"line \"%s\" opened, initial speed is %s\n",ttyname,baud[baud_index].ascii ); } /* Initially, set terminal to raw mode (read awakens on every * character). A BREAK will not generate an interrupt and will * will be received as a NULL character (all zero bits). * Output parity (even) is generated but input parity is not checked * (INPCK is not set). */ tty.c_iflag = ICRNL; tty.c_oflag = OPOST | ONLCR; tty.c_cflag = PARENB | CS7 | HUPCL | CREAD; tty.c_lflag = 0; tty.c_line = 0; tty.c_cc[VINTR] = CDEL; tty.c_cc[VQUIT] = CQUIT; tty.c_cc[VERASE] = CERASE; tty.c_cc[VKILL] = CKILL; tty.c_cc[VMIN] = 1; tty.c_cc[VTIME] = 0; setline(0,&tty,baud[baud_index].bin); /* Print contents of /etc/issue to user's terminal */ if ( (ifp = fopen("/etc/issue","r")) != NULL ) { char cn; while( (c=getc(ifp)) != EOF ) { cn = c; if ( write(1,&cn,1) != 1 ) exit(1); } fclose(ifp); } /* If 'timeout' was specified, hangup if nothing is entered at the * login prompt within 'timeout' seconds. */ if ( timeout != -1 ) { if ( signal(SIGALRM,login_timeout) < 0 ) { cfd = open("/dev/console",O_WRONLY);dup(cfd);dup(cfd); fprintf(stderr,"%s: signal() failed. errno: %d\n",argv[0],errno); exit(1); } alarm((unsigned long)timeout); } setbuf(stdout,NULL); /* no buffering */ /* Prompt for login and read response one character at a time. A * BREAK (received as a NULL) is entered by the user to cause * sequencing to the next line speed. */ while (1) { printf("login: "); /* prompt */ if ( debug ) fprintf(dfp,"login prompt sent, rcvd chars:\n"); p = logname; if ( read(0,p,1) != 1 ) exit(1); /* Turn off timer since something was entered (no effect * if timer is not running). */ alarm(0); while ( *p != NULL && *p != '\n' ) { if ( debug ) putc(*p,dfp); if ( write(1,p,1) != 1 ) exit(1); if ( read(0,++p,1) != 1 ) exit(1); } if ( *p == '\n' ) { /* re-prompt user if just carriage return was entered */ if ( p > logname ) { /* logname entered, exit loop */ putchar('\n'); break; } } else { /* A break was sent, wait time for break to complete * before setting to new speed. Otherwise the same * break may be misinterpreted as a new break at * the new speed. */ nap(500L); /* want baud_index to reflect to last tried speed */ baud_index = (baud_index + 1) % baud_count; setline(0,&tty,baud[baud_index].bin); if ( debug ) fprintf(dfp,"\nbreak received, sequenced to %s\n",baud[baud_index].ascii); } putchar('\n'); } *p = 0; /* terminate the logname string (which could be empty) */ if ( debug ) { fprintf(dfp,"\nlogin of \"%s\" received, exec logincb at %s\n",logname,baud[baud_index].ascii); fclose(dfp); } /* Set terminal to cooked mode. Output parity (even) is generated but * input parity is not checked (INPCK is not set). Receiving VINTR * VQUIT characters will generate signals, as will receiving a BREAK. * Turn on XON/XOFF flow control. */ tty.c_iflag = BRKINT | IGNPAR | ISTRIP | ICRNL | IXON | IXANY; tty.c_oflag = OPOST | ONLCR | TAB3; tty.c_cflag = PARENB | CS7 | HUPCL | CREAD; tty.c_lflag = ISIG | ICANON | ECHO | ECHOK; tty.c_line = 0; tty.c_cc[VINTR] = CDEL; tty.c_cc[VQUIT] = CQUIT; tty.c_cc[VERASE] = CERASE; tty.c_cc[VKILL] = CKILL; tty.c_cc[VEOF] = CEOF; tty.c_cc[VEOL] = CNUL; setline(0,&tty,baud[baud_index].bin); if ( log ) execl("/usr/local/bin/logincb","/usr/local/bin/logincb","-l",baud[baud_index].ascii,logname,(char *)0); else execl("/usr/local/bin/logincb","/usr/local/bin/logincb",baud[baud_index].ascii,logname,(char *)0); /* only get here if execl failed */ close(0);close(1);close(2); cfd = open("/dev/console",O_WRONLY);dup(cfd);dup(cfd); fprintf(stderr,"%s: cannot exec logincb\n",argv[0]); exit(1); } /* * Exit and print usage message. */ uexit(earg) int earg; { fprintf(stderr,"%s",usage); exit(earg); } /* * Time expired for login entry. */ void login_timeout() { exit(1); } /* * Set the line 'fd' to the characteristics specified by 'ttyp' augmented * by the speed in 'cbaud'. */ setline(fd,ttyp,cbaud) int fd; struct termio *ttyp; int cbaud; { ttyp->c_cflag &= ~CBAUD; ttyp->c_cflag |= cbaud; if ( ioctl(fd,TCSETA,(char *)ttyp) == -1 ) exit(1); if ( ioctl(fd,TCFLSH,2) == -1 ) exit(1); } /* * Delay program. Used instead of speep() when more granularity is needed. */ long nap(msec) long msec; { struct timeval tp; struct timezone tzp; unsigned long start_sec; long start_usec; long target_usec; long now_usec; gettimeofday(&tp,&tzp); start_sec = tp.tv_sec; start_usec = tp.tv_usec; target_usec = start_usec + (msec * 1000L); now_usec = start_usec; while(now_usec < target_usec) { gettimeofday(&tp,&tzp); now_usec = ((tp.tv_sec - start_sec) * 1000000L) + tp.tv_usec; } return((now_usec - start_usec) / 1000L); } @EOF chmod 644 gettycb.c echo x - logincb.c cat >logincb.c <<'@EOF' /* * logincb.c * * logincb is like login(1) but instead of exec'ing the program specified * in the last field of the users line in /etc/passwd (normally a shell), * the user is called back at a phone number found in the file .cbtelno * in his/her home directory. This program is exec'ed by gettycb. Together * they provide a secure remote access to the system. * */ #include #include #include #include #include #include #include #include #include #define MAXSPNM 5 #define MAXLGNM 64 #define MAXTN 256 /* max length telephone number */ char usage[] = "usage: logincb [ -l ] speed [ name ]\n"; main(argc,argv,envp) int argc; char **argv; char **envp; { extern int optind; void login_timeout(); char *getlog(); struct passwd *pw, *getpwnam(); char *pass, *crypt_pass, *getpass(), *crypt(); char logname[MAXLGNM+1], speed[MAXSPNM+1]; int i, c, log; log = 0; /* logging off by default */ while ( (c = getopt(argc,argv,"l")) != EOF ) switch(c) { case 'l': log = 1; break; case '?': uexit(1); break; } if ( optind == argc ) { fprintf(stderr,"%s: no terminal speed specified.\n",argv[0]); uexit(1); } /* Process arguments */ if ( argc == optind || argc > optind+2) uexit(1); /* copy speed argument */ if ( strlen(argv[optind]) > MAXSPNM ) { fprintf(stderr,"%s: invalid speed \"%s\".\n",argv[0],argv[optind]); uexit(1); } /* One minute to complete login, else hangup */ if ( signal(SIGALRM,login_timeout) < 0 ) { fprintf(stderr,"%s: signal() failed. errno: %d\n",argv[0],errno); exit(1); } alarm((unsigned long)60); strcpy(speed,argv[optind]); /* copy logname argument if supplied */ if ( argc == ++optind + 1 ) { if ( strlen(argv[2]) > MAXLGNM ) { fprintf(stderr,"%s: name too long \"%s\".\n",argv[0],argv[optind]); uexit(1); } strcpy(logname,argv[optind]); } else getlog(logname); /* Prompt for valid login name and password and then call the * phone number associated with that user. Exit after three invalid * login attempts. */ for ( i=0; i < 3; i++ ) { if ( i != 0 ) { printf("Login incorrect\n"); getlog(logname); } /* Get password entry associated with logname */ pw = getpwnam(logname); /* pw = NULL implies logname invalid */ if ( pw != NULL && strlen(pw->pw_passwd) == 0 ) { /* valid login name with no password */ printf("Sorry, login has no password -- not allowed here!\n"); continue; } /* Prompt for password (echo turned off). This is done even * though the login name may be invalid, makes it more * difficult to guess a valid logname-password. */ pass = getpass("Password: "); /* Callback if logname and password are valid */ if(pw != NULL && strcmp(crypt(pass,pw->pw_passwd),pw->pw_passwd) == 0) { alarm(0); /* stop timeout */ /* does not return */ callback(logname,pw->pw_dir,speed,log); } } exit(1); /* 3 failed attempts */ } char * getlog(log) char *log; { char *gets(); log[0] = 0; /* initialize to empty string */ do { printf("login: "); gets(log); } while ( log[0] == 0 ); return( log ); } /* * exit and print usage message. */ uexit(earg) int earg; { fprintf(stderr,"%s",usage); exit(earg); } /* * Time expired for login entry. */ void login_timeout() { exit(1); } static char nstdin[] = "/tmp/logincb.in"; static char nstdout[] = "/tmp/logincb.out"; static char nstder[] = "/tmp/logincb.er"; /* * Callback the terminal of user 'usrnm' on a callback line with speed * 'speed'. The user's home directory is 'usrdir' which must contain the file * .cbtelno which contains the users callback telephone number. Log the * callback if 'log' is nonzero. */ callback(usrnm,usrdir,speed,log) char *usrnm; char *usrdir; char *speed; int log; { FILE *fp; char cbfname[MAXPATHLEN]; char telno[MAXTN]; int forkret; char *s; strcpy(cbfname,usrdir); strcat(cbfname,"/.cbtelno"); if ( (fp=fopen(cbfname,"r")) == NULL ) { fprintf(stderr,"cannot call back -- cannot open %s for reading.\n",cbfname); exit(1); } if ( fscanf(fp,"%s",telno) == EOF ) { fprintf(stderr,"cannot call back -- %s is empty.\n",cbfname); exit(1); } /* Check for valid phone number (wanted to use strcspn() here but it * didn't seem to work right). */ for ( s = telno; *s != '\0'; s++ ) { if ( !isdigit(*s) && *s != '-' && *s != '=' && *s != '*' && *s != '#' ) { fprintf(stderr,"cannot call back -- %s contains an invalid telephone number \"%s\".\n",cbfname,telno); exit(1); } } /* Log the callback */ if (log) { long ltime; struct timeval t; struct timezone tz; int fd; /* Create log file (with proper permissions) if it doesn't * already exist. */ if ( (fd=open("/usr/adm/cblog",O_WRONLY|O_CREAT,0644)) >= 0 ) { /* Get file pointer so can use fprintf */ fp = fdopen(fd,"a"); /* Can't just use ctime() here to format the date * because it uses TZ which isn't defined when this * program runs from init. */ gettimeofday(&t,&tz); ltime = t.tv_sec - (tz.tz_minuteswest * 60); fprintf(fp,"%s %s %s",usrnm,telno,asctime(gmtime(<ime))); fclose(fp); } } printf("The line will now be disconnected and you will be called\n"); printf("back at the telephone number \"%s\". Your modem\n",telno); printf("must be able to answer the call. This may take up to 3\n"); printf("minutes, a longer delay means the callback was unsuccessful.\n"); fflush(stdout); /* Spawn ct(1) to call back the terminal that has called in. Ct will * search the L-Devices file (or Devices, for HDB uucp) for an * available callout line. If found, it will callback on that line * and then spawn a getty on the line for the user to log in on. * We need to close the current line because it may be a valid * call back line in the L-Devices file but ct will not be able * to open it if this process already has it open. (For example, if * the current line is tty04 (call-in line) and cul04 (call-out line) * is the line ct picks to call back on, it will not be able to * open cul04 with tty04 still open. This is because cul04 is of * higher priority than tty04 and tty04 already has a successful * open on it (refer to modem(7).) * * However, ct(1) seems to need stdin, stdout, and stderr to work * properly (the callback hangs up immediately after the connection * is made otherwise). Re-create them here and associate tham with * dummy files. */ close(0); close(1); close(2); /* Don't want the call-in line to be the controlling terminal */ setpgrp(); /* Start from scratch to avoid potential open permission problems */ unlink(nstdin);unlink(nstdout);unlink(nstder); if ( open(nstdin,O_RDWR|O_CREAT,0644) != 0 || open(nstdout,O_WRONLY|O_CREAT,0644) != 1 || open(nstder,O_RDWR|O_CREAT,0644) != 2 ) exit(1); /* Ct asks if it's ok to hangup the line so put a 'y' in the * stdin file. */ if ( write(0,"y\n",2) != 2 ) exit(1); /* Instead of just exec'ing ct here, spawn it and have the parent * exit. Ct will be 'adopted' by init (pid=1). This way, when * logincb is exec'ed by gettycb and gettycb is marked for "respawn" * in /etc/inittab, an new gettycb is started to answer new calls * on the current line. Otherwise, the current line would not be * availabe to answer calls until the current callback session * (potentially called back on a different line) is over. * * Note that it is important that the line specified in a gettycb * inittab entry (ex. tty04) be made (with mknod(1)) as a 'call-in' * device and the call-out devices in L-Devices (ex. cul04 * and cua04) be make as 'call-out' devices. This is so that when * a callout occurs on the same line as a (the) line with gettycb * running on it (respawned), the open of the line (tty04) by gettycb * will lose out to the open of the line (cul04) by logincb when * the call back is established. That is, the open of cul04 by * logincb will complete but the open of tty04 will become blocked. * This is because a call-out device is a higher priority device than * a call-in device. Refer to modem(7). */ forkret = fork(); if ( forkret == -1 ) { /* error msg goes to file */ fprintf(stderr,"fork() failed, errno: %d\n",errno); exit(1); } if ( forkret != 0 ) exit(0); /* parent -- exit */ /* spawn ct with debugging (-x9) so any call-back problems can * be analyzed in nstderr */ execl("/usr/bin/ct","/usr/bin/ct","-x9","-s",speed,telno,(char *)0); fprintf(stderr,"execl() of ct failed, errno: %d\n",errno); exit(1); } @EOF chmod 644 logincb.c rm -f /tmp/unpack$$ exit 0