/* ***************************************************************************** * * Copyright 1993 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 Digital * Equipment Corporation's Ultrix (tested on version 4.2v0). * Based upon @(#)telnetd.c 4.26 (Berkeley) 83/08/06 * * Original Author: James Carlson Created on: 21JUN93 * * Module Reviewers: * carlson * * Revision Control Information: * $Id: machdep.ultrix,v 1.1 1993/06/21 14:18:44 carlson Rel $ * * This file created by RCS from * $Source: /annex/common/src/./newrtelnet/RCS/machdep.ultrix,v $ * * Revision History: * $Log: machdep.ultrix,v $ * Revision 1.1 1993/06/21 14:18:44 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 "rtelnet.h" #include "../inc/erpc/netadmp.h" char machrev[] = "$Revision: 1.1 $"; char machsrc[] = "$Source: /annex/common/src/./newrtelnet/RCS/machdep.ultrix,v $"; /* * Berkeley-style ptys, 11 banks of 16 units * master="ptyBU" slave="ttyBU" banks=[pqrstuvwxyz] units=[0-9a-f] */ #define BANKS "zyxwvutsrqp" #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) static char master[64],slave[64],alias[64]; int process_id; extern int hangup, symbolic, tcp_port, hold_open, never_open, onthefly, transparent, show_pid, cbreakmode, port_num, renametarget, alternate_ptys; #ifndef NO_DEBUG extern int so_debug,debug,force_fork; #endif extern char *myname; extern int errno; extern char *sprintf(); 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 */ static int pty_was_selected = 0,net_was_selected = 0; static int user_id = 0; /* -u user for log & slave pty */ static int file_mode = 0666; /* -M file mode for slave pty */ static struct sockaddr_in sin = { AF_INET }; static struct sockaddr_in sin2 = { AF_INET }; /* Bits defined in "progress" state variable. */ #define LINKED_TTY 1 #define RENAMED_PTY 2 #define FORKED_DAEMON 4 extern void cleanup(), show_rtelnet_statistics(); extern int telnet_halt_network(); void pty_close(); int flag_check() { if (alternate_ptys) { (void)fprintf(stderr, "%s: -a flag is not available on this system.\n", myname); return 1; } if (cbreakmode && (never_open || onthefly)) { (void)fprintf(stderr, "%s: -c flag is incompatible with -fn flags.\n", myname); return 1; } return 0; } 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 } 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; } fd = open(name,O_RDWR|O_APPEND|O_CREAT|O_NOCTTY,0666); if (fd < 0) { perror(name); return; } using_log_file = fd; } void start_using_log() { process_id = 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); } } 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; } 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 != -1) sin.sin_family = AF_INET; else { host = gethostbyname(name); if (host) { sin.sin_family = host->h_addrtype; bcopy(host->h_addr, (caddr_t)&sin.sin_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; } void startup_cleaning() { struct stat stb; char *bank,*unit; int pty; /* * Clean up after previous incarnations of rtelnet */ for (bank = BANKS; *bank; bank++) { unit = UNITS; ALIAS(*bank,*unit); MASTER(*bank,*unit); if (stat(master,&stb) < 0) continue; /* Skip missing bank. */ /* Skip unit 0 -- we don't touch this one. */ for (unit++; *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); } } } } static void control_c() { DBG((1,D_INFO,"control_c interrupt")); cleanup(); } static void increase_debugging() { #ifndef NO_DEBUG if (debug < 5) debug++; DBG((debug,D_INFO,"Setting debug to level %d.",debug)); show_rtelnet_statistics(debug); #endif } static void stop_debugging() { #ifndef NO_DEBUG DBG((0,D_INFO,"Turning off debugging.")); debug = 0; #endif } 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); } progress |= FORKED_DAEMON; fd = getpid(); DBG((1,D_INIT,"Forked off child process %d",fd)); process_id = fd; /* Remove association with control terminal */ fd = open("/dev/tty",O_RDWR); if (fd >= 0) { (void)ioctl(fd,(int)TIOCNOTTY,(char *)0); (void)close(fd); } /* So we don't keep this file system busy, go to root. */ #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)setpgrp(0,getpid()); } (void)signal(SIGINT,control_c); (void)signal(SIGTERM,control_c); (void)signal(SIGUSR1,increase_debugging); (void)signal(SIGUSR2,stop_debugging); (void)signal(SIGXFSZ,stop_debugging); (void)signal(SIGHUP,SIG_IGN); (void)signal(SIGPIPE,SIG_IGN); (void)signal(SIGTTIN,SIG_IGN); (void)signal(SIGTTOU,SIG_IGN); (void)signal(SIGIO,SIG_IGN); (void)signal(SIGTSTP,SIG_IGN); } int set_io_block(s,flag) int s,flag; { int on; on = flag ? 0 : 1; return ioctl(s,(int)FIONBIO,(char *)&on); } int make_connection() { int s,on=1; struct linger linger; s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { i_perror("socket"); return -1; } #ifndef NO_DEBUG if (so_debug && setsockopt(s,SOL_SOCKET,SO_DEBUG,(int)&on,sizeof(on)) < 0) i_perror("setsockopt SO_DEBUG"); #endif if (setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,(int)&on,sizeof(on))<0) i_perror("setsockopt SO_KEEPALIVE"); if (setsockopt(s,SOL_SOCKET,SO_OOBINLINE,(int)&on,sizeof(on))<0) i_perror("setsockopt SO_OOBINLINE"); linger.l_onoff = 1; linger.l_linger = 120; if (setsockopt(s,SOL_SOCKET,SO_LINGER,(int)&linger, sizeof(linger)) < 0) i_perror("setsockopt SO_LINGER"); if (bind(s,(struct sockaddr *)&sin2,sizeof(struct sockaddr))<0) i_perror("bind"); sin.sin_port = htons((u_short)tcp_port); if (connect(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) { i_perror("connect"); (void)close(s); return -1; } /* Make I/O non-blocking */ if (set_io_block(s,0) < 0) i_perror("set_io_block"); return s; } /* * timet is in milliseconds. */ int wait_for_io(from,dev_pty,dev_net,timet) int from,dev_pty,dev_net,timet; { int n_found,towait; fd_set ibits,obits; struct timeval timev,*timevp; #ifndef NO_DEBUG char temp[64]; /* 58 bytes used */ #endif towait = dev_pty+1; if (dev_net >= towait) towait = dev_net+1; pty_was_selected = 0; net_was_selected = 0; for (;;) { FD_ZERO(&ibits); FD_ZERO(&obits); if (dev_pty >= 0) { if (from & FROM_PTY) FD_SET(dev_pty, &ibits); if (from & TO_PTY) { FD_SET(dev_pty, &obits); pty_was_selected = 1; } } if (dev_net >= 0) { if (from & FROM_NET) FD_SET(dev_net, &ibits); if (from & TO_NET) { FD_SET(dev_net, &obits); net_was_selected = 1; } } if (timet > 0) { timevp = &timev; timev.tv_usec = (timet%1000)*1000; timev.tv_sec = timet/1000; } else timevp = (struct timeval *)NULL; #define FDB(x) ((x)->fds_bits[0]) #define IOB FDB(&ibits),FDB(&obits) DBG((3,D_INFO,"selecting: i %02X o %02X.",IOB)); errno = 0; n_found = select(towait,&ibits,&obits,(fd_set *)NULL, timevp); if (n_found < 0) { if (errno == EINTR) { DBG((3,D_INFO,"interrupted -- trying again.")); continue; } i_perror("select"); cleanup(); } else break; } DBG((3,D_INFO,"%d found: i %02X o %02X.",n_found,IOB)); 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 (FD_ISSET(dev_pty, &ibits)) { from = FROM_PTY; ADDS(" input"); } if (FD_ISSET(dev_pty, &obits)) { from |= TO_PTY; ADDS(" output"); } ADDS((from&ALL_PTY) ? ")" : " none)"); } if (dev_net >= 0) { ADDS("(net"); if (FD_ISSET(dev_net, &ibits)) { from |= FROM_NET; ADDS(" input"); } if (FD_ISSET(dev_net, &obits)) { from |= TO_NET; ADDS(" output"); } ADDS((from&ALL_NET) ? ")" : " none)"); } DBG((3,D_INFO,temp)); return from; } static void set_pty_flags(newpty) int newpty; { struct termio b; int on; if (!never_open && !onthefly && (hold_open || cbreakmode || transparent)) { slave_pty = open(slave,O_RDONLY | O_NOCTTY); if (slave_pty < 0) i_perror(slave); else if (ioctl(slave_pty,(int)TCGETA,(char *)&b) < 0) i_perror("pty geta"); else { if (transparent) { b.c_iflag = 0; b.c_oflag = 0; b.c_cflag = B9600 | CS8 | CREAD; b.c_lflag = 0; } else if (cbreakmode) { b.c_iflag = ICRNL | IXON | IXOFF | IGNPAR; b.c_oflag = OPOST | ONLCR | TAB3; b.c_cflag = B9600 | CS8 | CREAD; b.c_lflag = 0; } else { b.c_iflag = ICRNL | IXON | IXOFF | IGNPAR; b.c_oflag = OPOST | ONLCR | TAB3; b.c_cflag = B9600 | CS8 | CREAD; b.c_lflag = ISIG | ICANON; } if (ioctl(slave_pty,(int)TCSETA,(char *)&b) < 0) i_perror("pty seta"); } } on = 1; if (ioctl(newpty,(int)TIOCPKT,(char *)&on) < 0) { i_perror("pty pkt"); return; } if (set_io_block(newpty,0)) i_perror("pty nbio"); } static void unlink_pty() { pty_close(-1); 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)); } if (progress & RENAMED_PTY) { (void)rename(alias, master); progress &= ~RENAMED_PTY; } } /* * Berkeley style ptys. * Open the master pty. Search backwards, so that the pty we take * permanently doesn't slow down other pty users. We may reserve * the pty by renaming it, so don't use /dev/pty?0, which can't * ever disappear. */ int openmaster(name) char *name; { int newpty,i; char *bank, *unit; struct stat stb; unlink_pty(); DBG((2,D_INFO,"Attempting to find pty for \"%s\".",name)); 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); DBG((0,D_ERR,"Unable to link slave pty.")); return -1; } new_node = name; progress |= LINKED_TTY; ALIAS(*bank,*unit); if (chmod(name,file_mode) < 0) i_perror("chmod"); if (user_id != 0 && chown(name,user_id,-1) < 0) i_perror("chown"); DBG((1,D_INFO,"Using master %s (slave %s).",master,slave)); DBG((1,D_INFO,"\talias for master is %s.",alias)); set_pty_flags(newpty); return newpty; } } return -1; } void machdep_cleanup() { unlink_pty(); if (!(progress&FORKED_DAEMON)) return; #ifndef NO_DEBUG /* * If we're debugging, and we're not forked, then void out the * controlling terminal so that we can claim the slave as the the * controlling terminal and hang it up. We're on the way out, anyway. */ if (debug && !force_fork && setpgrp(0,0) < 0) { i_perror("setpgrp"); return; } #endif if (open(slave,O_RDWR|O_NDELAY) < 0) { i_perror(slave); return; } (void)vhangup(); } int reopen_pty(dev_pty) int dev_pty; { int saved_errno,try; /* * 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"); 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); } 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; return -1; } progress &= ~RENAMED_PTY; set_pty_flags(dev_pty); return dev_pty; } /* * This routine is called when the first real data packet is read from * the pty. We can now close our end of the pty. */ void first_pty_data() { if (!hold_open && slave_pty >= 0) { DBG((3,D_INFO,"Closing slave pty.")); (void)close(slave_pty); slave_pty = -1; } } /* * If we're not in RAW or CBREAK mode, then we can experience trouble * with the pty if we give it more than 191 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; { struct termio b; if (columns == 191) { if (!transparent && !cbreakmode) { if (slave_pty < 0) return 1; if (ioctl(slave_pty,(int)TCGETA,(char *)&b) < 0) return 1; if (b.c_lflag & ICANON) return 1; } } return 0; } /*ARGSUSED*/ int get_interrupt_char(s) int s; { struct termio b; if (slave_pty < 0 || ioctl(slave_pty,(int)TCGETA,(char *)&b) < 0) return (int)'\177'; if (!(b.c_oflag & OPOST)) return (int)'\0'; return (int)b.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; { struct termio b; if (slave_pty<0 || ioctl(slave_pty,(int)TCGETA,(char *)&b) < 0) return (int)'\b'; return (int)b.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; if (slave_pty < 0) return 0; if (ioctl(slave_pty,(int)TCGETA,(char *)&b) < 0) { #ifndef NO_DEBUG if (debug > 3) #endif i_perror("mode: TCGETA"); return 1; } if (option == MODEF_RAW) { oopt = OPOST | TAB3; iopt = 0; lopt = 0; } 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 (ioctl(slave_pty,(int)TCSETA,(char *)&b)<0){ i_perror("mode: 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 (ioctl(slave_pty,(int)TCSETA,(char *)&b)<0){ i_perror("mode: TCSETA 2"); return 1; } } } return 0; } 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); } int force_send(fd,buff,len,flag) int fd,flag,len; char *buff; { errno = 0; net_was_selected = 0; return send(fd,buff,len,flag ? MSG_OOB : 0); } int network_read(fd,buffp,siz) int fd,siz; char **buffp; { int cc; errno = 0; cc = read(fd,*buffp,siz); if (cc > 0) return cc; if (cc == 0) return MDIO_CLOSED; if (errno == EWOULDBLOCK || errno == EINTR) return MDIO_DEFER; return MDIO_ERROR; } int network_write(fd,buff,siz) int fd,siz; char *buff; { int cc,nws = net_was_selected; net_was_selected = 0; errno = 0; cc = write(fd,buff,siz); if (cc >= 0) return cc; if (errno == EWOULDBLOCK || errno == EINTR) if (nws) return MDIO_UNSELECT; else return MDIO_DEFER; return MDIO_ERROR; } void network_close(fd) int fd; { if (fd >= 0) { DBG((3,D_INFO,"Closing network fd %d.",fd)); if (close(fd) < 0) i_perror("net close"); } } int pty_read(fd,buffp,siz) int fd,siz; char **buffp; { int done,cc; char *cp = *buffp; static int flag = 0; if (flag == 0) { errno = 0; cc = read(fd,cp,siz); if (cc == 0) return MDIO_CLOSED; if (cc < 0) if (errno == EWOULDBLOCK || errno == EINTR) return MDIO_DEFER; else return MDIO_ERROR; if (*cp == 0 && cc > 1) { *buffp = cp+1; return cc - 1; } flag = *cp & (TIOCPKT_FLUSHREAD | TIOCPKT_FLUSHWRITE | TIOCPKT_STOP | TIOCPKT_START | TIOCPKT_DOSTOP | TIOCPKT_NOSTOP); } DBG((flag!=0?2:3,D_INFO,"Processing pty packet flags %02X",flag)); done = 0; /* * The order of flush write/read is important, due to the way the * telnet protocol handles it. */ if (flag & (TIOCPKT_STOP | TIOCPKT_START)) { done |= flag & (TIOCPKT_STOP | TIOCPKT_START); telnet_halt_network((flag&TIOCPKT_STOP)?1:0); } if (flag & TIOCPKT_FLUSHWRITE) { if (cancel_network_output()) done |= TIOCPKT_FLUSHWRITE; telnet_halt_network(0); } if (flag & TIOCPKT_FLUSHREAD) { if (cancel_network_input()) done |= TIOCPKT_FLUSHREAD; } if (flag & TIOCPKT_DOSTOP) { if (telnet_ship_lflow(1)) done |= TIOCPKT_DOSTOP; } else if (flag & TIOCPKT_NOSTOP) { if (telnet_ship_lflow(0)) done |= TIOCPKT_NOSTOP; } if (done != 0) DBG((3,D_INFO,"Processed pty packet flags %02X.",done)); flag &= ~done; return MDIO_DEFER; } int pty_write(fd,buff,siz) int fd,siz; char *buff; { int cc,pws = pty_was_selected; pty_was_selected = 0; errno = 0; cc = write(fd,buff,siz); if (cc >= 0) return cc; if (errno == EWOULDBLOCK || errno == EINTR) if (pws) return MDIO_UNSELECT; else return MDIO_DEFER; return MDIO_ERROR; } void pty_close(fd) int fd; { if (slave_pty >= 0) { (void)close(slave_pty); slave_pty = -1; } if (fd >= 0) { (void)set_io_block(fd,1); DBG((3,D_INFO,"Closing master pty fd %d.",fd)); (void)close(fd); } }