/* ***************************************************************************** * * 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 MIPS * RISC/os (UMIPS) 4.51. * Based upon @(#)telnetd.c 4.26 (Berkeley) 83/08/06 * * Original Author: James Carlson Created on: 12JUN92 * * Module Reviewers: * carlson * * Revision Control Information: * $Id: machdep.mips,v 1.1 1993/04/23 10:00:40 carlson Rel $ * * This file created by RCS from * $Source: /annex/common/src/./newrtelnet/RCS/machdep.mips,v $ * * Revision History: * $Log: machdep.mips,v $ * Revision 1.1 1993/04/23 10:00:40 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 "rtelnet.h" #include "../inc/erpc/netadmp.h" char machrev[] = "$Revision: 1.1 $"; char machsrc[] = "$Source: /annex/common/src/./newrtelnet/RCS/machdep.mips,v $"; static char slave[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 struct passwd *getpwnam(); 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 int slave_is_open = 0; 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 FORKED_DAEMON 4 extern void cleanup(), show_rtelnet_statistics(); extern int telnet_halt_network(); void pty_close(); 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; } 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|FNOCTTY,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 (geteuid() == pd->pw_uid) return; user_id = pd->pw_uid; } void startup_cleaning() { } 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,ebits; 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; for (;;) { FD_ZERO(&ibits); FD_ZERO(&obits); FD_ZERO(&ebits); if (dev_pty >= 0) { if ((from == FROM_PTY && dev_net < 0) || (!slave_is_open && never_open)) from = TO_PTY; if (from & FROM_PTY) FD_SET(dev_pty,&ibits); if (from & TO_PTY) FD_SET(dev_pty,&obits); if (slave_is_open) FD_SET(dev_pty,&ebits); } if (dev_net >= 0) { if (from & FROM_NET) FD_SET(dev_net,&ibits); if (from & TO_NET) FD_SET(dev_net,&obits); FD_SET(dev_pty,&ebits); } 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),FDB(&ebits) DBG((3,D_INFO,"selecting: i %02X o %02X e %02X.",IOB)); errno = 0; n_found = select(towait,&ibits,&obits,&ebits,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 e %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; slave_is_open = 1; ADDS(" input"); } if (FD_ISSET(dev_pty,&obits)) { from |= TO_PTY; slave_is_open = 1; ADDS(" output"); } if (FD_ISSET(dev_pty,&ebits)) { from |= ERR_PTY; ADDS(" exception"); } 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"); } if (FD_ISSET(dev_net,&ebits)) { from |= ERR_NET; ADDS(" exception"); } 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 | FNOCTTY); if (slave_pty < 0) i_perror(slave); else { DBG((3,D_INFO,"Opened slave %s as fd %d.",slave,slave_pty)); 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 | TAB0; b.c_cflag = B9600 | CS8 | CREAD; b.c_lflag = 0; } else { b.c_iflag = ICRNL | IXON | IXOFF | IGNPAR; b.c_oflag = OPOST | ONLCR | TAB0; 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)); } } /* * Open the "big" multiplexed master pty -- /dev/ptcm -- and try to * get a pty pair from it. (These are an optional feature.) */ static int try_big_master() { int mpty,mjr,mnr,ord; struct stat sb; mpty = open("/dev/ptcm",O_RDWR|FNOCTTY); if (mpty >= 0) { if (!isatty(mpty)) DBG((2,D_ERR,"/dev/ptcm isn't a tty?")); else if (fstat(mpty,&sb) >= 0) { mjr = major(sb.st_rdev); mnr = minor(sb.st_rdev); /* ord == 0 is equivalent to /dev/ptc */ for (ord = 1; ord < 10; ord++) { (void)sprintf(slave,"/dev/ptmc%d",ord); if (stat(slave,&sb) < 0) i_perror(slave); else if (major(sb.st_rdev) == mjr) break; } if (ord < 10) { (void)sprintf(slave,"/dev/ttyq%d", mnr + ord*256); return mpty; } } (void)close(mpty); } return -1; } /* * Open the "small" multiplexed master pty -- /dev/ptc -- and try to * get a pty pair from it. */ static int try_small_master() { int mpty; struct stat sb; mpty = open("/dev/ptc",O_RDWR|FNOCTTY); if (mpty >= 0) { if (!isatty(mpty)) DBG((2,D_ERR,"/dev/ptc isn't a tty?")); else if (fstat(mpty,&sb) >= 0) { (void)sprintf(slave,"/dev/ttyq%d", minor(sb.st_rdev)); return mpty; } (void)close(mpty); } return -1; } /* * MIPS style ptys. * Open the one of the multiplexed master ptys, get the name of the * associated slave and set it up. */ int openmaster(name) char *name; { int newpty,i; unlink_pty(); DBG((2,D_INFO,"Attempting to find pty for \"%s\".",name)); if (alternate_ptys) { newpty = try_big_master(); if (newpty < 0) newpty = try_small_master(); } else { newpty = try_small_master(); if (newpty < 0) newpty = try_big_master(); } if (newpty < 0) { DBG((1,D_ERR,"Unable to allocate master pty.")); return newpty; } 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; 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 slave pty %s.",slave)); set_pty_flags(newpty); return newpty; } void machdep_cleanup() { unlink_pty(); #if 0 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(); #endif } int reopen_pty(dev_pty) int dev_pty; { pty_close(dev_pty); return -1; } /* * 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 fd %d on first data.",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) { (void)shutdown(fd,2); 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) { DBG((3,D_INFO,"Closing slave pty fd %d.",slave_pty)); (void)close(slave_pty); slave_pty = -1; } if (fd >= 0) (void)close(fd); slave_is_open = 0; }