/* ***************************************************************************** * * Copyright 1992 by Xylogics, Inc. ALL RIGHTS RESERVED. * * ALL RIGHTS RESERVED. Licensed Material - Property of Xylogics, Inc. * This software is made available solely pursuant to the terms of a * software license agreement which governs its use. * Unauthorized duplication, distribution or sale are strictly prohibited. * * Module Description: * * Annex reverse-telnet daemon machine-dependent code for SunOS * version 5.1 (a/k/a Solaris 2.0). Based upon @(#)telnetd.c 4.26 * (Berkeley) 83/08/06 * * Original Author: James Carlson Created on: 17DEC92 * * Module Reviewers: * lint, carlson * * Revision Control Information: * $Id: $ * * This file created by RCS from * $Source: $ * * Revision History: * $Log: machdep.sol,v $ * Revision 1.7 1995/07/26 11:17:05 carlson * Fixed a flush problem. * * Revision 1.6 1994/09/28 14:48:54 carlson * SPR 3573 -- only open streams administrative driver when needed. * SPR 3569 -- incorrect kind of flush implemented. * * Revision 1.5 1994/01/11 14:12:01 carlson * Added ability to detect break ioctl and send across network. * * Revision 1.4 93/07/29 11:28:46 carlson * Added "config.h" to work around configuration problems, moved chmod * to set_slave_flags to fix "cu" problems, forced close with -nf flags * and fixed t_snd argument usage. * * Revision 1.3 93/02/11 08:44:23 carlson * Removed separate process hack, added autopush clearing on master * start-up and cleaned up a little. * * Revision 1.2 93/02/04 16:59:03 carlson * Added support for -auM flags, added autopush set-up, fixed controlling * tty bug, added work-arounds for NDELAY and poll problems, and decoded * more "normal" error codes. * * Revision 1.1 92/12/17 17:51:43 carlson * Initial revision * * * This file is currently under revision by: $Locker: $ * ***************************************************************************** */ #include "../inc/config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rtelnet.h" #include "../inc/erpc/netadmp.h" char machrev[] = "$Revision: 1.7 $"; char machsrc[] = "$Source: $"; /* * Uncomment this macro to enable ioctl debug messages. (These will * be used to implement full ioctl control of the Annex slave port * some day.) */ /* #define TEST_IOCTL */ static char slave[64]; /* saved device name */ int process_id; /* Main rtelnet process number */ extern int onthefly, hangup, symbolic, tcp_port, never_open, hold_open, transparent, show_pid, cbreakmode, port_num, renametarget, binary, alternate_ptys; #ifndef NO_DEBUG extern int so_debug,debug,force_fork; #endif extern int errno,t_errno; extern char *ptsname(); extern char *myname; static char *new_node; /* User's name for pty */ static int progress = 0; /* Internal flags (for cleanup) */ static int erpc_port = 121; /* Port for na communication */ static int using_log_file = 0; /* Flag and file descriptor */ static int slave_pty = -1, /* File descriptor for holding slave */ master_pty = -1; static int net_was_selected = 0,pty_was_selected = 0; static int pty_is_closed = 0; /* Close message has arrived in master */ static int first_dummy = 0; /* Ignore first zero-length message */ static int pipe_break = 0; /* Set if a SIGPIPE arrives */ static int file_mode = 0666; /* Default file mode (set by -M) */ static int user_id = -1; static struct termio slaveconf; static struct sockaddr_in sin = { AF_INET }; /* Annex address */ #define LINKED_TTY 1 #define RENAMED_PTY 2 static char master[64],alias[64]; #define BANKS "srqp" #define UNITS "0123456789abcdef" #define MASTER(b,u) (void)sprintf(master,"/dev/pty%c%c",b,u) #define SLAVE(b,u) (void)sprintf(slave,"/dev/tty%c%c",b,u) #define ALIAS(b,u) (void)sprintf(alias,"/dev/tty%c%c.rtelnet",b,u) extern void cleanup(), show_rtelnet_statistics(); extern int telnet_halt_network(); void pty_close(), i_perror(); static int set_slave_flags(); #define BADPOLL (POLLERR | POLLHUP | POLLNVAL) /* * Examine command-line option flags and reject combinations that won't * work (or just don't make sense) on this system. */ int flag_check() { if (cbreakmode && (never_open || onthefly)) { (void)fprintf(stderr, "%s: -c flag is incompatible with -fn flags.\n", myname); return 1; } return 0; } /* * Open debugging log file as named by command line argument. */ void use_log_file(name) char *name; { int fd; if (using_log_file) { (void)fprintf(stderr, "Duplicate log file name \"%s\" ignored.\n", name); return; } if ((fd = open(name,O_RDWR|O_APPEND|O_CREAT|O_NOCTTY,0666)) < 0) { perror(name); return; } using_log_file = fd; } /* * Change standard output to point to log file and prepare for * debugging. */ void start_using_log() { process_id = (int)getpid(); if (using_log_file == 0) return; if (dup2(using_log_file,2) < 0) { using_log_file = 0; perror("dup2 log file"); return; } (void)close(using_log_file); using_log_file = -1; if (user_id < 0) return; if (fchown(2,user_id,-1) < 0) { i_perror("fchown"); exit(1); } } /* * Convert port number string to TCP port number. */ int name_to_unit(port) char *port; { int num; struct servent *servp; if (tcp_port) { /* User requested interpretation of port number as TCP port */ if (isdigit(*port)) num = atoi(port); else if (servp = getservbyname(port,"tcp")) num = ntohs((u_short)servp->s_port); else { (void)fprintf(stderr, "%s: tcp/%s: unknown service.\n", myname,port); return -1; } if (num <= 0 || num >= 65536) { (void)fprintf(stderr, "%s: Illegal TCP port number -- %d.\n", myname,num); return -1; } } else { /* Interpret port number as Annex serial port number */ num = atoi(port); if (num <= 0 || num > MAX_PORT ) { (void)fprintf(stderr, "%s: Illegal serial port specifier -- \"%s\".\n", myname,port); return -1; } num += PORT_MAP_BASE; } return num; } /* * Attempt to locate Annex named by given string, and initialize * erpc port information if necessary. */ void resolve_annex(name) char *name; { register struct hostent *host; struct servent *servp; sin.sin_addr.s_addr = inet_addr(name); if (sin.sin_addr.s_addr != (ulong)-1) sin.sin_family = AF_INET; else { host = gethostbyname(name); if (host) { sin.sin_family = host->h_addrtype; memcpy((caddr_t)&sin.sin_addr, host->h_addr, host->h_length); } else { (void)fprintf(stderr, "%s: %s: unknown host\n", myname,name); exit(1); } } if (hangup) { servp = getservbyname("erpc", "udp"); if (servp == 0) (void)fprintf(stderr, "%s: udp/erpc: unknown service, using %d.\n", myname,erpc_port); else erpc_port = ntohs((u_short)servp->s_port); } } void set_file_mode(fmode) char *fmode; { int i; i = (int)strtol(fmode,&fmode,8); if (*fmode == '\0') file_mode = i; else { (void)fprintf(stderr,"%s: Illegal data in file mode: %s\n", myname,fmode); exit(1); } } void set_user_name(uname) char *uname; { struct passwd *pd; if ((pd = getpwnam(uname)) == NULL) { perror(uname); exit(1); } if (getuid() == pd->pw_uid) return; user_id = pd->pw_uid; } /* * Clean up anything left behind from crashed rtelnets. */ void startup_cleaning() { struct stat stb; char *bank,*unit; int pty; if (!alternate_ptys) return; /* * Clean up after previous incarnations of rtelnet */ for (bank = BANKS; *bank; bank++) for (unit = UNITS; *unit; unit++) { ALIAS(*bank,*unit); MASTER(*bank,*unit); if (stat(alias, &stb) < 0 || stat(master,&stb) == 0) continue; if ((pty = open(alias, O_RDWR|O_NOCTTY)) < 0) continue; else { (void)rename(alias, master); (void)close(pty); } } } /* * Internal version of perror -- send string through debugging * interface so that it gets formatted as expected. */ void i_perror(str) char *str; { #ifdef NO_DEBUG perror(str); #else extern int sys_nerr; extern char *sys_errlist[]; if (errno < 0 || errno >= sys_nerr) DBG((0,D_ERR,"%s: error %d",str,errno)); else DBG((0,D_ERR,"%s: %s",str,sys_errlist[errno])); #endif } /* * Internal version of t_error -- send string through debugging * interface so that it gets formatted as expected. */ int i_t_error(str) char *str; { extern int t_nerr; extern char *t_errlist[]; if (t_errno == TSYSERR) { int myerrno = errno; i_perror(str); return myerrno; } #ifdef NO_DEBUG t_error(str); #else if (t_errno < 0 || t_errno >= t_nerr) DBG((0,D_ERR,"%s: TLI error %d",str,t_errno)); else DBG((0,D_ERR,"%s: %s",str,t_errlist[t_errno])); #endif return EIO; } /*ARGSUSED*/ static void control_c(dummy) int dummy; { DBG((1,D_INFO,"control_c interrupt")); cleanup(); } /*ARGSUSED*/ static void increase_debugging(dummy) int dummy; { #ifndef NO_DEBUG if (debug < 5) debug++; DBG((debug,D_INFO,"Setting debug to level %d.",debug)); show_rtelnet_statistics(debug); #endif } /*ARGSUSED*/ static void stop_debugging(dummy) int dummy; { #ifndef NO_DEBUG DBG((0,D_INFO,"Turning off debugging.")); debug = 0; #endif } /*ARGSUSED*/ static void bad_connection(dummy) int dummy; { pipe_break = 1; } /* * Fork off daemon task and make appropriate system calls so that * we appear as a normal daemon -- removing control tty, et cetera. */ void become_daemon() { int fd; #ifndef NO_DEBUG if (!debug || force_fork) #endif { if (fd = fork()) { if (fd < 0) { perror("fork"); exit(1); } if (show_pid) (void)printf("%d\n",fd); exit(0); } fd = getpid(); DBG((1,D_INIT,"Forked off child process %d",fd)); process_id = fd; /* * Don't keep the current file system busy. If we're debugging, then * we should stay put so that any core files aren't dropped in the root * directory. */ #ifndef NO_DEBUG if (!debug) #endif if (chdir("/") < 0) DBG((1,D_WARN,"chdir / failed -- %d",errno)); (void)close(0); (void)close(1); if (!using_log_file) (void)close(2); (void)setsid(); } (void)sigset(SIGINT,control_c); (void)sigset(SIGTERM,control_c); (void)sigset(SIGPWR,control_c); (void)sigset(SIGUSR1,increase_debugging); (void)sigset(SIGUSR2,stop_debugging); (void)sigset(SIGHUP,SIG_IGN); (void)sigset(SIGTSTP,SIG_IGN); (void)sigset(SIGPIPE,bad_connection); } /* * Set given file descriptor to blocking or non-blocking I/O. */ int set_io_block(s,flag) int s,flag; { return fcntl(s,F_SETFL,flag ? 0 : O_NONBLOCK); } static int set_tli_option(s,opt) int s,opt; { struct t_optmgmt request,reply; u_long reqopts[10],repopts[10]; struct opthdr *iop; iop = (struct opthdr *)reqopts; iop->level = IPPROTO_TCP; iop->name = opt; iop->len = sizeof(u_long); *(u_long *)(iop+1) = 1; request.flags = T_NEGOTIATE; request.opt.len = sizeof(*iop)+sizeof(u_long); request.opt.buf = (char *)iop; reply.flags = 0; reply.opt.len = 0; reply.opt.maxlen = sizeof(repopts); reply.opt.buf = (char *)repopts; return t_optmgmt(s,&request,&reply); } static int set_tli_linger(s) int s; { struct t_optmgmt request,reply; u_long reqopts[10],repopts[10]; struct opthdr *iop; struct linger *lp; iop = (struct opthdr *)reqopts; iop->level = IPPROTO_TCP; iop->name = SO_LINGER; iop->len = sizeof(struct linger); lp = (struct linger *)(iop+1); lp->l_onoff = 1; lp->l_linger = 120; request.flags = T_NEGOTIATE; request.opt.len = sizeof(*iop)+sizeof(struct linger); request.opt.buf = (char *)iop; reply.flags = 0; reply.opt.len = 0; reply.opt.maxlen = sizeof(repopts); reply.opt.buf = (char *)repopts; return t_optmgmt(s,&request,&reply); } /* * Open a TLI connection to the previously determined Annex address. */ int make_connection() { int s,myerrno = 0; struct t_call tcall; s = t_open("/dev/tcp",O_RDWR,(struct t_info *)NULL); if (s < 0) { errno = i_t_error("t_open"); return -1; } if (t_bind(s,(struct t_bind *)NULL,(struct t_bind *)NULL) < 0) { myerrno = i_t_error("t_bind"); goto general_error; } #if 0 if (set_tli_option(s,SO_NEWPORT) < 0) { myerrno = i_t_error("t_optmgmt SO_NEWPORT"); goto general_error; } if (set_tli_option(s,SO_REUSEADDR) < 0) { myerrno = i_t_error("t_optmgmt SO_REUSEADDR"); goto general_error; } if (set_tli_linger(s) < 0) { myerrno = i_t_error("t_optmgmt SO_LINGER"); goto general_error; } if (set_tli_option(s,SO_KEEPALIVE) < 0) { myerrno = i_t_error("t_optmgmt SO_KEEPALIVE"); goto general_error; } #endif if (set_tli_option(s,TCP_NODELAY) < 0) { myerrno = i_t_error("t_optmgmt TCP_NODELAY"); goto general_error; } #if 0 if (set_tli_option(s,SO_OOBINLINE) < 0) { myerrno = i_t_error("t_optmgmt SO_OOBINLINE"); goto general_error; } #ifndef NO_DEBUG if (so_debug && set_tli_option(s,SO_DEBUG) < 0) { myerrno = i_t_error("t_optmgmt SO_DEBUG"); goto general_error; } #endif #endif sin.sin_port = htons((u_short)tcp_port); tcall.addr.len = sizeof(sin); tcall.addr.buf = (char *)&sin; tcall.opt.len = tcall.udata.len = 0; tcall.opt.buf = tcall.udata.buf = NULL; tcall.sequence = 0; pipe_break = 0; if (t_connect(s,&tcall,(struct t_call *)NULL) < 0) { int i; if (pipe_break) myerrno = ECONNREFUSED; else if (t_errno == TLOOK) { i = t_look(s); if (i < 0) myerrno = i_t_error("t_connect/t_look"); /* * Connect returns asynchronous "DISCONNECT" if another connection is * in use. */ else if (i == T_DISCONNECT) myerrno = ECONNREFUSED; else { DBG((0,D_ERR,"Unexpected t_look %d.",i)); myerrno = EIO; } } else myerrno = i_t_error("t_connect"); goto general_error; } /* Make I/O non-blocking */ if (set_io_block(s,0) < 0) i_perror("set_io_block"); /* * New host connection now established -- set up default slave configuration * data. */ memset((caddr_t)&slaveconf,0,sizeof(slaveconf)); if (transparent) { slaveconf.c_iflag = IMAXBEL; slaveconf.c_oflag = 0; slaveconf.c_cflag = B9600 | CS8 | CREAD; slaveconf.c_lflag = 0; } else if (cbreakmode) { slaveconf.c_iflag = ICRNL | IXON | IXOFF | IGNPAR | IMAXBEL; slaveconf.c_oflag = OPOST | ONLCR | TAB3; slaveconf.c_cflag = B9600 | CS8 | CREAD; slaveconf.c_lflag = 0; } else { slaveconf.c_iflag = ICRNL | IXON | IXOFF | IGNPAR | IMAXBEL; slaveconf.c_oflag = OPOST | ONLCR | TAB3; slaveconf.c_cflag = B9600 | CS8 | CREAD; slaveconf.c_lflag = ISIG | ICANON; } if (binary) { slaveconf.c_iflag = IGNPAR | IMAXBEL; slaveconf.c_oflag &= ~ONLCR & ~TAB3; } slaveconf.c_cc[VINTR] = 0x7F; slaveconf.c_cc[VQUIT] = 0x1C; slaveconf.c_cc[VERASE] = '#'; slaveconf.c_cc[VKILL] = '@'; slaveconf.c_cc[VEOF] = 0x04; if (slave_pty >= 0 && ioctl(slave_pty,(int)TCSETA,(char *)&slaveconf) < 0 && errno != ETIME) i_perror("TCSETA"); return s; general_error: (void)t_close(s); errno = myerrno; return -1; } /* * Wait until something interesting happens. * timet is in milliseconds. */ int wait_for_io(from,dev_pty,dev_net,timet) int from,dev_pty,dev_net,timet; { int n_found; struct pollfd pollfds[2]; unsigned long towait; #ifndef NO_DEBUG char temp[64]; /* 58 bytes used */ #endif #if 0 /* This is a fix for a poll() bug in Solaris BSD ptys. */ if (alternate_ptys && timet <= 0 && dev_net >= 0) timet = 500; #endif /* Zero time means wait forever */ if (timet == 0) timet = -1; net_was_selected = pty_was_selected = 0; for (;;) { towait = 0; if (dev_pty >= 0) { if (pty_is_closed) return ERR_PTY; pollfds[towait].fd = dev_pty; pollfds[towait].events = 0; if (from & FROM_PTY) { pollfds[towait].events |= POLLIN | POLLPRI | POLLRDNORM; if (alternate_ptys) pollfds[towait].events |= POLLRDBAND; } if (from & TO_PTY) { pollfds[towait].events |= POLLOUT; pty_was_selected = 1; } towait++; } else pty_is_closed = 0; if (dev_net >= 0) { pollfds[towait].fd = dev_net; pollfds[towait].events = 0; if (from & FROM_NET) pollfds[towait].events |= POLLIN | POLLPRI; if (from & TO_NET) { pollfds[towait].events |= POLLOUT; net_was_selected = 1; } towait++; } #ifndef NO_DEBUG #define FDB(x,e) pollfds[x].fd,pollfds[x].e #define IOB(e) FDB(0,e),FDB(1,e) if (towait == 1) DBG((3,D_INFO,"polling: %d - %X,%X (%d).",towait,FDB(0,events),timet)); else DBG((3,D_INFO,"polling: %d - %X,%X %X,%X (%d).",towait,IOB(events),timet)); #endif n_found = poll(pollfds,towait,timet); if (n_found < 0) { if (errno == EINTR) { DBG((3,D_INFO,"interrupted -- trying again.")); continue; } i_perror("poll"); cleanup(); } else break; } #ifndef NO_DEBUG if (towait == 1) DBG((3,D_INFO,"polled: %d - %X,%X.",towait,FDB(0,revents))); else DBG((3,D_INFO,"polled: %d - %X,%X %X,%X.",towait,IOB(revents))); #endif if (n_found == 0) from = WFIO_TIMEOUT; else from = 0; #ifndef NO_DEBUG (void)strcpy(temp,"\t"); #define ADDS(s) (void)strcat(temp,s) #else #define ADDS(s) #endif if (dev_pty >= 0) { ADDS("(pty"); if (pollfds[0].revents & (POLLIN | POLLPRI)) { from = FROM_PTY; ADDS(" input"); } if (pollfds[0].revents & POLLOUT) { from |= TO_PTY; ADDS(" output"); } if (pollfds[0].revents & BADPOLL) { from |= ERR_PTY; ADDS(" exception"); } ADDS((from&ALL_PTY) ? ")" : " none)"); } if (dev_net >= 0) { ADDS("(net"); if (pollfds[towait-1].revents & (POLLIN | POLLPRI)) { from |= FROM_NET; ADDS(" input"); } if (pollfds[towait-1].revents & POLLOUT) { from |= TO_NET; ADDS(" output"); } if (pollfds[towait-1].revents & BADPOLL) { from |= ERR_NET; ADDS(" exception"); } ADDS((from&ALL_NET) ? ")" : " none)"); } DBG((3,D_INFO,temp)); return from; } /* * Set flags for slave pty. */ static int set_slave_flags() { int retval = 0; struct termio b; char *name; name = renametarget?new_node:slave; if (chmod(name,file_mode) < 0) i_perror("chmod"); if (!onthefly && slave_pty < 0 && (hold_open || cbreakmode || transparent)) { if (never_open) { DBG((1,D_WARN,"-n flag set -- cannot set modes.")); return 0; } retval = 1; DBG((3,D_INFO,"Opening slave end of pseudo terminal.")); slave_pty = open(name,O_RDONLY|O_NDELAY|O_NOCTTY); if (slave_pty < 0) i_perror(name); else if (ioctl(slave_pty,(int)TCSETA,(char *)&slaveconf) < 0) { if (errno == ETIME) retval = 2; i_perror("TCSETA"); } else { DBG((3,D_INFO,"Successfully set modes on slave.")); retval = 0; } } return retval; } void unlink_pty() { if (progress & LINKED_TTY) { if (renametarget) (void)rename(new_node,slave); else (void)unlink(new_node); progress &= ~LINKED_TTY; DBG((2,D_INFO,"Removed link between %s and pty %s.",new_node,slave)); } } /* * Clean up before exiting -- remove user's pty node. */ void machdep_cleanup() { pty_close(master_pty); unlink_pty(); if (progress & RENAMED_PTY) { (void)rename(alias, master); progress &= ~RENAMED_PTY; } } /* * Turn autopush on or off for the current slave device. * Flag is zero to clear autopush, non-zero to set it, negative to * silently clear it. */ static void call_autopush(flag) int flag; { struct strapush sadpush; struct stat sbuf; int sad_fd; if (alternate_ptys) return; /* Grab the device number for configuration. */ if (stat(slave,&sbuf) < 0) { i_perror("stat slave"); return; } while (errno = 0, (sad_fd=open(ADMINDEV,O_RDWR)) < 0) { if (errno != ENXIO) { i_perror("sad admin"); DBG((1,D_ERR,"Cannot open sad device.")); return; } sleep(1); DBG((3,D_INFO,"Sad device was busy; trying again.")); } /* Set up autopush modules, if possible. */ bzero(&sadpush,sizeof(sadpush)); sadpush.sap_major = major(sbuf.st_rdev); sadpush.sap_minor = minor(sbuf.st_rdev); if (flag > 0) { sadpush.sap_cmd = SAP_ONE; sadpush.sap_npush = 3; strcpy(sadpush.sap_list[0],"ptem"); strcpy(sadpush.sap_list[1],"ldterm"); strcpy(sadpush.sap_list[2],"ttcompat"); } else sadpush.sap_cmd = SAP_CLEAR; if (ioctl(sad_fd,SAD_SAP,(char *)&sadpush) < 0 && flag >= 0) i_perror("ioctl SAD_SAP"); else DBG((3,D_INFO,"Configured autopush sucessfully.")); (void)close(sad_fd); } /* * Open the multiplexed master device and get a pty pair. Then, link up * the user's name for this device. */ int openmaster(name) char *name; { int newpty,i; struct stat stb; char *bank,*unit; static int first_time=1; if (first_time) { if (user_id > 0 && setuid(user_id) < 0) { i_perror("setuid"); exit(1); } first_time = 0; } pty_is_closed = 0; machdep_cleanup(); DBG((2,D_INFO,"Attempting to find pty for \"%s\".",name)); if (alternate_ptys) { for (bank = BANKS; *bank; bank++) { unit = UNITS; MASTER(*bank,*unit); if (stat(master, &stb) < 0) { DBG((1,D_WARN,"%s: missing pty bank %c.",myname,*bank)); continue; } /* * Skip unit 0 -- this master is the key for the bank, and other * programs will think the entire bank is missing if we rename it. */ unit++; for (; *unit; unit++) { MASTER(*bank,*unit); if ((newpty = open(master, O_RDWR|O_NOCTTY))< 0) continue; SLAVE(*bank,*unit); if (symbolic) i = symlink(slave,name); else if (renametarget) i = rename(slave,name); else i = link(slave,name); if (i < 0) { i_perror(name); (void)close(newpty); continue; } goto got_the_master; } } return -1; } if ((newpty = open("/dev/ptmx",O_RDWR|O_NOCTTY)) < 0) { DBG((0,D_ERR,"No pseudo terminals left.")); return -1; } if (grantpt(newpty) < 0) i_perror("grantpt"); else if (unlockpt(newpty) < 0) i_perror("unlockpt"); else if ((unit = ptsname(newpty)) == NULL) i_perror("ptsname"); else if (ioctl(newpty,I_PUSH,"pckt") < 0) i_perror("I_PUSH pckt"); else { (void)strcpy(slave,unit); call_autopush(-1); /* remove any trash quietly */ call_autopush(1); if (symbolic) i = symlink(slave,name); else if (renametarget) i = rename(slave,name); else i = link(slave,name); if (i < 0) i_perror(name); goto got_the_master; } (void)close(newpty); return -1; got_the_master: master_pty = newpty; new_node = name; progress |= LINKED_TTY; if (set_slave_flags()) { (void)close(newpty); return -1; } if (alternate_ptys) { DBG((1,D_INFO,"Using master %s, slave %s.",master,slave)); ALIAS(*bank,*unit); DBG((1,D_INFO,"\talias for master is %s.",alias)); } DBG((1,D_INFO,"Slave %s linked to %s.",slave,name)); first_dummy = 1; if (set_io_block(newpty,0)) i_perror("pty nbio"); return newpty; } /* * Reopening the master pty isn't necessary on System V.4 -- it stays * open. All that happens is that we get a zero-length message. We can * just reopen the slave, push everything back on and start over. */ int reopen_pty(dev_pty) int dev_pty; { int saved_errno,try; if (onthefly) { pty_close(dev_pty); DBG((3,D_WARN,"Using -f switch -- forced to close master.")); return -1; } if (alternate_ptys) { /* * Berkeley style ptys. While the master pty is closed, someone could * open it and effectively steal the remote device from us. * (Yecch) rename the master pty while it's closed. */ if (rename(master, alias) < 0) { i_perror("rename to alias"); unlink_pty(); return -1; } progress |= RENAMED_PTY; pty_close(dev_pty); /* Try a few times to reopen the alias */ try = 0; while ((dev_pty = open(alias,O_RDWR|O_NOCTTY)) < 0) { saved_errno = errno; if (++try > 2) break; (void)sleep(1); } master_pty = dev_pty; if (rename(alias, master) < 0 || dev_pty < 0) { if (dev_pty < 0) { errno = saved_errno; i_perror("reopen of alias"); } else { i_perror("rename to normal"); (void)close(dev_pty); } progress &= ~RENAMED_PTY; unlink_pty(); return -1; } progress &= ~RENAMED_PTY; set_slave_flags(); if (set_io_block(dev_pty,0)) i_perror("pty nbio"); return dev_pty; } pty_is_closed = 0; if (ioctl(dev_pty,I_NREAD,&try) < 0) { pty_close(dev_pty); DBG((3,D_WARN,"Unable to reuse master pty on multiplexed device.")); return -1; } if (!never_open) { pty_close(dev_pty); if (!onthefly) DBG((3,D_WARN,"Not using -n switch -- forced to close master.")); return -1; } DBG((3,D_INFO,"Reusing master pty fd %d in reopen_pty.",dev_pty)); if (hold_open) return dev_pty; pty_close(-1); /* Close the slave if it's still open */ if (set_slave_flags()) { /* Reopen the slave */ pty_close(dev_pty); /* Darn -- someone grabbed the slave */ return -1; /* while we were fooling around */ } return dev_pty; } /* * This routine is called when the first real data packet is read from * the pty. We can now close our slave pty so that we'll see the * application's last close as a zero length message on the master. */ void first_pty_data() { first_dummy = 0; if (!hold_open && slave_pty >= 0) { DBG((3,D_INFO,"Closing slave pty.")); (void)close(slave_pty); slave_pty = -1; } } /* * If we're in canonical input mode, then we can experience trouble * with the pty if we give it more than 255 bytes between line feeds. * (I have no idea why this is!) In any event, we have to compensate * for this bit of weirdness. */ /*ARGSUSED*/ int fix_cooked_mode_bug(columns,pty) int columns,pty; { if (columns == 255) { if (slaveconf.c_lflag & ICANON) return 1; } return 0; } /* * Get the proper representation of an interrupt character for the * current pty mode. */ /*ARGSUSED*/ int get_interrupt_char(s) int s; { if (!(slaveconf.c_oflag & OPOST)) return (int)'\0'; return (int)slaveconf.c_cc[VQUIT]; } /* * If flag is zero, return character-erase, if non-zero return line- * erase. */ /*ARGSUSED*/ int get_erase_char(s,flag) int s,flag; { return (int)slaveconf.c_cc[flag ? VKILL : VERASE]; } /* * flag is 0 to clear, 1 to set, and option is 0 for raw mode, 1 for * echo/crmod. */ /*ARGSUSED*/ int mode(s,flag,option) int s,flag,option; { struct termio b; int oopt,iopt,lopt; b = slaveconf; if (option == MODEF_RAW) { oopt = OPOST | TAB3; iopt = 0; lopt = 0; flag = !flag; } else { oopt = ECHO | ONLCR; iopt = ICRNL; lopt = ICANON; } if (flag) { if ((b.c_oflag&oopt) != oopt || (b.c_iflag&iopt) != iopt || (b.c_lflag&lopt) != lopt) { b.c_oflag |= oopt; b.c_iflag |= iopt; b.c_lflag |= lopt; if (slave_pty < 0) slaveconf = b; else if (ioctl(slave_pty,(int)TCSETA,(char *)&b) < 0) { i_perror("TCSETA"); return 1; } } } else { if ((b.c_oflag&oopt) != 0 || (b.c_iflag&iopt) != 0 || (b.c_lflag&lopt) != 0) { b.c_oflag &= ~oopt; b.c_iflag &= ~iopt; b.c_lflag &= ~lopt; if (slave_pty < 0) slaveconf = b; else if (ioctl(slave_pty,(int)TCSETA,(char *)&b) < 0) { i_perror("TCSETA"); return 1; } } } return 0; } /* * Call netadm (na) routine to forcibly reset the Annex's serial line. */ void reset_serial_line() { sin.sin_port = htons((u_short)erpc_port); (void)reset_line((struct sockaddr_in *)&sin,(u_short)SERIAL_DEV, (u_short)port_num); } /* * Convert the TLI error code into one of rtelnet's internal error codes, * and handle any special processing along the way. */ static int convert_error(fd,module) int fd; char *module; { int i,myerrno = errno; switch (t_errno) { case TSYSERR: if (errno != EWOULDBLOCK && errno != EAGAIN) { i_perror(module); break; } case TNODATA: case TFLOW: myerrno = EAGAIN; return MDIO_DEFER; case TLOOK: i = t_look(fd); if (i < 0) { myerrno = i_t_error("t_look"); break; } switch (i) { case T_CONNECT: DBG((3,D_INFO,"t_look returns T_CONNECT")); break; case T_DATA: DBG((3,D_INFO,"t_look returns T_DATA")); break; case T_LISTEN: DBG((3,D_INFO,"t_look returns T_LISTEN")); break; case T_UDERR: DBG((3,D_INFO,"t_look returns T_UDERR")); break; case T_EXDATA: DBG((3,D_INFO,"t_look returns T_EXDATA")); break; case T_DISCONNECT: DBG((3,D_INFO,"t_look returns T_DISCONNECT")); errno = ECONNABORTED; return MDIO_ERROR; case T_ORDREL: DBG((3,D_INFO,"t_look returns T_ORDREL")); errno = ECONNRESET; return MDIO_ERROR; case T_ERROR: DBG((3,D_INFO,"t_look returns T_ERROR")); errno = EIO; return MDIO_ERROR; default: DBG((3,D_INFO,"Unknown t_look value -- %d.",i)); break; } errno = EINTR; return MDIO_DEFER; default: myerrno = i_t_error(module); } errno = myerrno; return MDIO_ERROR; } /* * Bypass normal I/O to send a message to the Annex. */ int force_send(fd,buff,len,flag) int fd,flag,len; char *buff; { int cc; t_errno = errno = 0; cc = t_snd(fd,buff,len,flag ? T_EXPEDITED : 0); if (cc > 0 || t_errno == 0) return cc; return convert_error(fd,"force_send"); } /* * Read from network interface. */ int network_read(fd,buffp,siz) int fd,siz; char **buffp; { int cc,flags = 0; t_errno = errno = 0; cc = t_rcv(fd,*buffp,siz,&flags); if (cc > 0 || t_errno == 0) return cc; return convert_error(fd,"network_read"); } /* * Write to network interface. */ int network_write(fd,buff,siz) int fd,siz; char *buff; { int cc,nws = net_was_selected; net_was_selected = 0; t_errno = errno = 0; cc = t_snd(fd,buff,siz,0); if (cc > 0 || t_errno == 0) return cc; cc = convert_error(fd,"network_write"); if (cc == MDIO_DEFER && nws) cc = MDIO_UNSELECT; return cc; } /* * Shut down network connection and close interface. */ void network_close(fd) int fd; { (void)t_sndrel(fd); (void)sleep(1); (void)t_close(fd); } #ifdef TEST_IOCTL static void dump_strbuf(name,buf) char *name; struct strbuf *buf; { int i; fprintf(stderr,"%s length %d:\n",name,buf->len); for (i = 0;i < buf->len;i++) fprintf(stderr,"%02X ",((unsigned char *)buf->buf)[i]); fprintf(stderr,"\n"); } struct flag_table { char *name; int flag; }; static void show_flags(flags,tp) int flags; struct flag_table *tp; { while (tp->name) { if (tp->flag & flags) { fprintf(stderr,"%s ",tp->name); flags &= ~tp->flag; } tp++; } if (flags) fprintf(stderr,"%06o",flags); fprintf(stderr,"\n"); } static void display_terminal_flags(iflag,oflag,cflag,lflag) int iflag,oflag,cflag,lflag; { static struct flag_table input[] = { { "IMAXBEL", IMAXBEL }, { "IXOFF", IXOFF }, { "IXANY", IXANY }, { "IXON", IXON }, { "IUCLC", IUCLC }, { "ICRNL", ICRNL }, { "IGNCR", IGNCR }, { "INLCR", INLCR }, { "ISTRIP", ISTRIP }, { "INPCK", INPCK }, { "PARMRK", PARMRK }, { "IGNPAR", IGNPAR }, { "BRKINT", BRKINT }, { "IGNBRK", IGNBRK }, { NULL, 0 } }; static struct flag_table output[] = { { "WRAP", WRAP }, { "PAGEOUT", PAGEOUT }, { "FFDLY", FFDLY }, { "VTDLY", VTDLY }, { "BSDLY", BSDLY }, { "NLDLY", NLDLY }, { "OFDEL", OFDEL }, { "OFILL", OFILL }, { "ONLRET", ONLRET }, { "ONOCR", ONOCR }, { "OCRNL", OCRNL }, { "ONLCR", ONLCR }, { "OPOST", OPOST }, { NULL, 0 } }; static struct flag_table control[] = { { "PAREXT", PAREXT }, { "XCLUDE", XCLUDE }, { "LOBLK", LOBLK }, { "XMT1EN", XMT1EN }, { "RCV1EN", RCV1EN }, { "CLOCAL", CLOCAL }, { "HUPCL", HUPCL }, { "PARODD", PARODD }, { "PARENB", PARENB }, { "CREAD", CREAD }, { "CSTOPB", CSTOPB }, { NULL, 0 } }; static struct flag_table line[] = { { "IEXTEN", IEXTEN }, { "PENDIN", PENDIN }, { "FLUSHO", FLUSHO }, { "DEFECHO", DEFECHO }, { "ECHOKE", ECHOKE }, { "ECHOPRT", ECHOPRT }, { "ECHOCTL", ECHOCTL }, { "TOSTOP", TOSTOP }, { "NOFLSH", NOFLSH }, { "ECHONL", ECHONL }, { "ECHOK", ECHOK }, { "ECHOE", ECHOE }, { "ECHO", ECHO }, { "XCASE", XCASE }, { "ICANON", ICANON }, { "ISIG", ISIG }, { NULL, 0 } }; static int baud_table[16] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400 }; fprintf(stderr,"Input flags: "); show_flags(iflag,input); fprintf(stderr,"Output flags: "); if ((oflag & TABDLY) != 0) fprintf(stderr,"TAB%d ",(oflag&TABDLY)>>11); if ((oflag & CRDLY) != 0) fprintf(stderr,"CR%d ",(oflag&CRDLY)>>9); oflag &= ~TABDLY & ~CRDLY; show_flags(oflag,output); fprintf(stderr,"Control flags: CS%d ",((cflag&CSIZE)>>4)+5); fprintf(stderr,"B%d ",baud_table[cflag&CBAUD]); fprintf(stderr,"IB%d ",baud_table[(cflag&CIBAUD)>>16]); cflag &= ~CSIZE & ~CBAUD & ~CIBAUD; show_flags(cflag,control); fprintf(stderr,"Line flags: "); show_flags(lflag,line); } static void termio_dump(tp) struct termio *tp; { display_terminal_flags(tp->c_iflag,tp->c_oflag,tp->c_cflag,tp->c_lflag); fprintf(stderr,"Line discipline %d.\n",tp->c_line); fprintf(stderr,"INTR %02X, QUIT %02X, ERASE %02X, KILL %02X,\n", tp->c_cc[VINTR],tp->c_cc[VQUIT],tp->c_cc[VERASE], tp->c_cc[VKILL]); fprintf(stderr,"EOF %02X, EOL %02X, EOL2 %02X, SWTCH %02X,\n", tp->c_cc[VEOF],tp->c_cc[VEOL],tp->c_cc[VEOL2], tp->c_cc[VSWTCH]); } static void termios_dump(tp) struct termios *tp; { display_terminal_flags(tp->c_iflag,tp->c_oflag,tp->c_cflag,tp->c_lflag); fprintf(stderr,"INTR %02X, QUIT %02X, ERASE %02X, KILL %02X,\n", tp->c_cc[VINTR],tp->c_cc[VQUIT],tp->c_cc[VERASE], tp->c_cc[VKILL]); fprintf(stderr,"EOF %02X, EOL %02X, EOL2 %02X, SWTCH %02X,\n", tp->c_cc[VEOF],tp->c_cc[VEOL],tp->c_cc[VEOL2], tp->c_cc[VSWTCH]); fprintf(stderr,"START %02X, STOP %02X, SUSP %02X, DSUSP %02X,\n", tp->c_cc[VSTART],tp->c_cc[VSTOP],tp->c_cc[VSUSP], tp->c_cc[VDSUSP]); fprintf(stderr,"REPRINT %02X, DISCARD %02X, WERASE %02X, LNEXT %02X,\n", tp->c_cc[VREPRINT],tp->c_cc[VDISCARD],tp->c_cc[VWERASE], tp->c_cc[VLNEXT]); } #endif /* TEST_IOCTL */ static void termios_configure(tp) struct termios *tp; { int i; slaveconf.c_iflag = tp->c_iflag; slaveconf.c_oflag = tp->c_oflag; slaveconf.c_cflag = tp->c_cflag; slaveconf.c_lflag = tp->c_lflag; slaveconf.c_line = 0; for (i=0;ic_cc);i++) slaveconf.c_cc[i] = tp->c_cc[i]; } static void termio_configure(tp) struct termio *tp; { slaveconf = *tp; } static void handle_tioc(cmd,data) int cmd; char *data; { switch (cmd) { #ifdef TEST_IOCTL case TCGETA: fprintf(stderr,"ioctl TCGETA\n"); termio_dump((struct termio *)data); break; #endif /* TEST_IOCTL */ case TCSETA: #ifdef TEST_IOCTL fprintf(stderr,"ioctl TCSETA\n"); termio_dump((struct termio *)data); #endif /* TEST_IOCTL */ termio_configure((struct termio *)data); break; case TCSETAW: #ifdef TEST_IOCTL fprintf(stderr,"ioctl TCSETAW\n"); termio_dump((struct termio *)data); #endif /* TEST_IOCTL */ termio_configure((struct termio *)data); break; case TCSETAF: #ifdef TEST_IOCTL fprintf(stderr,"ioctl TCSETAF\n"); termio_dump((struct termio *)data); #endif /* TEST_IOCTL */ termio_configure((struct termio *)data); break; case TCSBRK: #ifdef TEST_IOCTL fprintf(stderr,"ioctl TCSBRK\n"); #endif /* TEST_IOCTL */ DBG((4,D_INFO,"handle_tioc:About to do timemark.\n")); do_timing_mark(); DBG((4,D_INFO,"handle_tioc: data after timeing mark %d\n",*data)); if (!*(unsigned long *)data) tn_send_break(); break; #ifdef TEST_IOCTL case TCXONC: fprintf(stderr,"ioctl TCXONC\n"); break; case TCFLSH: fprintf(stderr,"ioctl TCFLSH\n"); break; case TCGETS: fprintf(stderr,"ioctl TCGETS\n"); termios_dump((struct termios *)data); break; #endif /* TEST_IOCTL */ case TCSETS: #ifdef TEST_IOCTL fprintf(stderr,"ioctl TCSETS\n"); termios_dump((struct termios *)data); #endif /* TEST_IOCTL */ termios_configure((struct termios *)data); break; case TCSETSW: #ifdef TEST_IOCTL fprintf(stderr,"ioctl TCSETSW\n"); termios_dump((struct termios *)data); #endif /* TEST_IOCTL */ termios_configure((struct termios *)data); break; case TCSETSF: #ifdef TEST_IOCTL fprintf(stderr,"ioctl TCSETSF\n"); termios_dump((struct termios *)data); #endif /* TEST_IOCTL */ termios_configure((struct termios *)data); break; #ifdef TEST_IOCTL default: fprintf(stderr,"Unknown ioctl TIOC %d.\n",cmd&0xFF); #endif /* TEST_IOCTL */ } } static void handle_ioctl(buf,dlen) char *buf; int dlen; { struct iocblk *ioc; char *data; ioc = (struct iocblk *)buf; data = (char *)(ioc+1); #ifdef TEST_IOCTL { int i; fprintf(stderr,"ioctl data length %d:\n",ioc->ioc_count); for (i = 0; i < ioc->ioc_count; i++) fprintf(stderr,"%02X ",data[i]&0xFF); fprintf(stderr,"\n"); } #endif /* TEST_IOCTL */ if (dlen != ioc->ioc_count + sizeof(struct iocblk)) { DBG((1,D_WARN,"Strange IOCTL data -- length %d/%d",dlen,ioc->ioc_count+sizeof(struct iocblk))); #ifdef TEST_IOCTL { int i; fprintf(stderr,"ioctl header:\n"); for (i = 0 ; i < sizeof(struct iocblk); i++) fprintf(stderr,"%02X ",buf[i]&0xFF); fprintf(stderr,"\n"); cleanup(); } #else return; #endif } switch (ioc->ioc_cmd & 0xFF00) { case TIOC: handle_tioc(ioc->ioc_cmd&0xFFFF,data); break; #ifdef TEST_IOCTL case tIOC: fprintf(stderr,"Old BSD ioctl %d.\n",ioc->ioc_cmd&0xFF); break; case LDIOC: fprintf(stderr,"LDIOC %d.\n",ioc->ioc_cmd&0xFF); break; case DIOC: fprintf(stderr,"DIOC %d.\n",ioc->ioc_cmd&0xFF); break; default: fprintf(stderr,"Unknown ioctl type %02X '%c'.\n", (ioc->ioc_cmd>>8)&0xFF,(ioc->ioc_cmd>>8)&0xFF); #endif /* TEST_IOCTL */ } } static int handle_telnet_changes(flagp) int *flagp; { if (*flagp & 4) { if (telnet_halt_network(1)) *flagp &= ~4; else return 1; } if (*flagp & 8) { if (telnet_halt_network(0)) *flagp &= ~8; else return 1; } if (*flagp & 2) { if (cancel_network_output()) *flagp &= ~2; else return 1; telnet_halt_network(0); } if (*flagp & 1) { if (cancel_network_input()) *flagp &= ~1; else return 1; } return 0; } /* * Read from master pty interface. */ int pty_read(fd,buffp,siz) int fd,siz; char **buffp; { int cc,flags; struct strbuf ctl,data; char ctlbuf[256]; static int savedtelnet = 0; if (alternate_ptys) { errno = 0; cc = read(fd,*buffp,siz); if (cc == 0) return MDIO_CLOSED; if (cc < 0) if (errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) return MDIO_DEFER; else if (errno == EIO) return MDIO_CLOSED; else return MDIO_ERROR; return cc; } if (savedtelnet && handle_telnet_changes(&savedtelnet)) { DBG((3,D_INFO,"Pending telnet protocol flags %X",savedtelnet)); return MDIO_UNSELECT; } flags = errno = 0; ctl.maxlen = sizeof(ctlbuf); ctl.len = 0; ctl.buf = ctlbuf; data.maxlen = siz; data.len = 0; data.buf = *buffp; cc = getmsg(fd,&ctl,&data,&flags); if (cc < 0) if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) return MDIO_DEFER; else return MDIO_ERROR; if (ctl.len == -1) { ctl.len = 1; ctlbuf[0] = M_DATA; } if (ctl.len != 1) { DBG((1,D_WARN,"Strange control message -- length %d?",ctl.len)); #ifdef TEST_IOCTL dump_strbuf("Control",&ctl); dump_strbuf("Data",&data); cleanup(); #else return MDIO_DEFER; #endif /* TEST_IOCTL */ } switch (ctlbuf[0]&0xFF) { case M_DATA: cc = data.len; if (cc == 0) if (first_dummy) { DBG((3,D_INFO,"First dummy zero-length message.")); first_dummy = 0; cc = MDIO_DEFER; } else { DBG((3,D_INFO,"Received close message.")); pty_is_closed = 1; cc = MDIO_CLOSED; } else DBG((3,D_INFO,"Received %d bytes in M_DATA.",cc)); break; case M_IOCTL: if (data.len < sizeof(struct iocblk)) { DBG((1,D_WARN,"Strange IOCTL message -- length %d?",data.len)); #ifdef TEST_IOCTL dump_strbuf("Data",&data); cleanup(); #else return MDIO_DEFER; #endif /* TEST_IOCTL */ } DBG((3,D_INFO,"Received M_IOCTL.")); handle_ioctl(data.buf,data.len); cc = MDIO_DEFER; break; case M_FLUSH: DBG((3,D_INFO,"Received M_FLUSH.")); savedtelnet |= data.buf[0] & 3; handle_telnet_changes(&savedtelnet); cc = MDIO_DEFER; break; case M_STOP: DBG((3,D_INFO,"Received M_STOP.")); savedtelnet = (savedtelnet & ~8) | 4; handle_telnet_changes(&savedtelnet); cc = MDIO_DEFER; break; case M_START: DBG((3,D_INFO,"Received M_START.")); savedtelnet = (savedtelnet & ~4) | 8; handle_telnet_changes(&savedtelnet); cc = MDIO_DEFER; break; default: DBG((1,D_WARN,"Strange control message -- type %02X?",ctlbuf[0]&0xFF)); #ifdef TEST_IOCTL dump_strbuf("Control",&ctl); dump_strbuf("Data",&data); cleanup(); #else cc = MDIO_DEFER; #endif /* TEST_IOCTL */ } return cc; } /* * Write to master pty interface. */ int pty_write(fd,buff,siz) int fd,siz; char *buff; { int cc,pws = pty_was_selected; struct strbuf ctl,data; pty_was_selected = errno = 0; if (alternate_ptys) { cc = write(fd,buff,siz); if (cc >= 0) return cc; if (errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) if (pws) return MDIO_UNSELECT; else return MDIO_DEFER; else if (errno == EIO) return MDIO_CLOSED; return MDIO_ERROR; } ctl.len = -1; ctl.buf = NULL; data.len = siz; data.buf = buff; cc = putmsg(fd,&ctl,&data,0); if (cc >= 0) return siz; if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) if (pws) return MDIO_UNSELECT; else return MDIO_DEFER; return MDIO_ERROR; } /* * Close master and slave ptys. */ void pty_close(fd) int fd; { if (slave_pty >= 0) { DBG((3,D_INFO,"Closing held slave pty fd %d in pty_close.",slave_pty)); (void)close(slave_pty); slave_pty = -1; } if (fd >= 0) { call_autopush(0); DBG((3,D_INFO,"Closing master pty fd %d in pty_close.",fd)); (void)close(fd); master_pty = -1; } }