/* * Copyright (c) 1995, 1996 Gunther Schadow. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "socket.h" #include #include #include #ifdef _AIX # include #endif #ifndef sun # include #endif #include #include #include #include #include #include #include #include #ifdef hpux #include #else int open_slave(const char *slave); #endif #ifndef PTY_REOPEN_DELAY #define PTY_REOPEN_DELAY 2 /* seconds */ #endif result plisten(int fd); static char *pty_master[NOFILE]; #ifdef hpux static pid_t pty_peer[NOFILE]; #else static char *pty_slave[NOFILE]; #endif static result split_address(const char *address, char **master, char **slave) { char *addr = strdup(address); if((*master = strtok(addr,"%")) == NULL) goto syntax_error; else *slave = strtok(NULL,"%"); #ifndef hpux if(*slave == NULL) goto syntax_error; #endif return SUCCESS; syntax_error: syslog(LOG_ERR, "pty address must be '[%%]'"); return FAIL; } int bind_pty(const char *address) { char *master; char *slave; int fd; DBG(syslog(LOG_DEBUG, "opening pty '%s'", address)); if(split_address(address, &master, &slave) == FAIL) goto fail; DBG(syslog(LOG_DEBUG, "master: `%s', slave: `%s'", master, slave)); if((fd = open(master, O_RDWR)) == FAIL) { syslog(LOG_ERR, "open %s: %m", master); goto fail; } /* Always put an exclusive lock on the master. If flock() would block * we there is an other master already running */ pty_master[fd]=master; #ifndef hpux pty_slave[fd]=strdup(slave); #endif return fd; fail: return FAIL; } int reset_pty(int mfd) { int fd; #ifdef hpux int kill_sig = SIGINT; #endif /* Send a break in order to tell the remote station to hangup */ if(tcsendbreak(mfd, 1) == FAIL) syslog(LOG_ERR, __FUNCTION__ ":TCSBRK: %m"); /* The mfd file desciptor must be the last one that referes to the * pty. Otherwise the client will never notice that we just closed * the file. */ close(mfd); /* A short delay seems to be necessary on some systems, at least * does no harm. */ sleep(PTY_REOPEN_DELAY); #ifdef hpux open_again: #endif if((fd = open(pty_master[mfd], O_RDWR)) == FAIL) { syslog(LOG_ERR, "reset_pty %s: %m", pty_master[mfd]); if(errno == EPTYREOPEN) /* see socket.h */ { syslog(LOG_ERR, __FUNCTION__ ": the pty master can not be reopened"); #ifdef hpux if(kill_sig != 0) { /* Try to push the hanging process from the slave */ syslog(LOG_INFO, __FUNCTION__ ": signal %d `%s' to process %d", kill_sig, sys_siglist[kill_sig], (int)pty_peer[mfd]); if(kill(pty_peer[mfd], kill_sig) == FAIL) { syslog(LOG_ERR, __FUNCTION__ ": %m"); if(errno == ESRCH) kill_sig = SIGKILL; else return FAIL; } switch(kill_sig) { case SIGINT: kill_sig = SIGTERM; break; case SIGTERM: kill_sig = SIGKILL; break; case SIGKILL: kill_sig = -1; break; } sleep(2 /* seconds */); goto open_again; } #endif return FAIL; } else return FAIL; } /* Rearrange the stored address if necessary */ if(fd != mfd) { pty_master[fd]=pty_master[mfd]; #ifndef hpux pty_slave[fd]=pty_slave[mfd]; #endif } return fd; } int accept_pty(int mfd) { int rfd; /* return descriptor */ #ifndef hpux int sfd; /* slave descriptor */ #endif /* * Here are two branches: HPUX can trap open/close/ioctl requests * while traditional BSD has rather poor means. (I'll implement the * TIOCTRAP functionality for FreeBSD's pty pseudo device). */ #ifdef hpux /* * Disable for the slave: * - any termios ioctl * - troughput processing * Trap: * - open requests * - close requests */ { int on = 1; int off = 0; if (ioctl(mfd, TIOCTTY, &off) == FAIL) { syslog(LOG_ERR, __FUNCTION__ ":TIOCTTY: %m"); goto fail_master; } if (ioctl(mfd, TIOCTRAP, &on) == FAIL) { syslog(LOG_ERR, __FUNCTION__ ":TIOCTRAP: %m"); goto fail_master; } } #else /* ! hpux */ /* * The slave terminal must be set to raw mode in order to * prevent unexpected modification of data (like LF->CRLF). The * slave terminal has to stay open until an other process opens * it otherwise the parameters are reset immediately */ sfd = open_slave(pty_slave[mfd]); #endif /* ! hpux */ /* * Listen at the master side of the pty for any open requests */ if( plisten(mfd) == FAIL ) goto fail_master; /* * Plisten returned which implies that a process opened the * slave side. */ #ifndef hpux /* * We have to close the slave here. Note, that we should * not waist time (e.g. by a call to syslog) before the close * system call is issued or else a deadlock may occur if the file * sent to the slave is short (only about 6 characters.) */ if ( close(sfd) == FAIL ) { syslog(LOG_ERR, __FUNCTION__ ":close slave: %m"); goto fail_slave; } DBG(syslog(LOG_DEBUG, __FUNCTION__ ": slave closed")); #endif /* ! hpux */ /* Just like Berkeley's accept, return a duplicate of the file * descriptor. */ rfd = dup(mfd); pty_master[rfd] = pty_master[mfd]; #ifdef hpux pty_peer[rfd] = pty_peer[mfd]; #else pty_slave[rfd] = pty_slave[mfd]; #endif return rfd; fail_master: syslog(LOG_ERR, "unable to continue with error on master"); abort(); fail_slave: return FAIL; } /* * Open slave (non-hpux only) */ #ifndef hpux int open_slave(const char *slave) { int sfd; struct termios stio; /* slave tty settings */ /* * The slave terminal must be set to raw mode in order to * prevent unexpected modification of data (like LF->CRLF). The * slave terminal has to stay open until an other process opens * it otherwise the parameters are reset immediately */ DBG(syslog(LOG_DEBUG, "opening %s", slave)); sfd = open(slave, O_RDWR); if (sfd < 0) { syslog(LOG_ERR, __FUNCTION__ ":open %s: %m", slave); return FAIL; } if(tcgetattr(sfd, &stio) == FAIL) { syslog(LOG_ERR, __FUNCTION__ "tcgetattr: %m", slave); goto fail; } cfmakeraw(&stio); if(tcsetattr(sfd, TCSANOW, &stio) == FAIL) { syslog(LOG_ERR, __FUNCTION__ ":TIOCSETA: %m", slave); goto fail; } return sfd; fail: /* * reclose slave */ DBG(syslog(LOG_DEBUG, "closing %s", slave)); if (close(sfd) == FAIL) { syslog(LOG_ERR, __FUNCTION__ ":close %s: %m", slave); exit(1); } return FAIL; } #endif #ifdef hpux /* Enable (state == 1) or disable (state == 0) termios(7) ioctl * requests. Must be (temporarily) enabled even for the master side. */ int tioctty(int fd, int state) { if (ioctl(fd, TIOCTTY, &state) == FAIL) { syslog(LOG_ERR, __FUNCTION__ ": %m"); return FAIL; } return SUCCESS; } #endif /******************************************************************* * * Listen at the master side of the pty for any open requests * */ #ifdef hpux /* HPUX declares select in */ #include /* * this is the first time that I experience HPUX to be really better * than FreeBSD: HPUX allows IOCTL requests to be trapped, analyzed * and responded to by the master side of the pty. Especially I get * the pid of the requesting process. */ result plisten(int mfd) { fd_set ecfds, rdfds; int nfds; struct request_info rqinf; DBG(syslog(LOG_DEBUG, __FUNCTION__ ": listening ...")); FD_ZERO(&ecfds); FD_SET(mfd, &ecfds); FD_ZERO(&rdfds); FD_SET(mfd, &rdfds); again: nfds = select(FD_SETSIZE, &rdfds, NULL, &ecfds, NULL); if (nfds < 0) { syslog(LOG_ERR, __FUNCTION__ ":select: %m"); if(errno == EINTR) goto again; else return FAIL; } /* * I just don't know exactly how to handle the condition when * select returns 0. Simply issue a warning and fail. */ if (nfds == 0) { syslog(LOG_WARNING, __FUNCTION__ ":select: timeout??"); return FAIL; } /* * Select returned with either an open/close/ioctl request or * a read request. At this time we expect open requests; however, * it is possible that the pty was left open by the last transaction. */ if(FD_ISSET(mfd, &ecfds)) { if(ioctl(mfd, TIOCREQCHECK, &rqinf) == FAIL) { if(errno == EINVAL) { syslog(LOG_WARNING, __FUNCTION__ ":TIOCREQCHECK: client died"); return FAIL; } else { syslog(LOG_ERR, __FUNCTION__ ":TIOCREQCHECK: %m"); return FAIL; } } if (rqinf.request != TIOCOPEN) { syslog(LOG_WARNING, __FUNCTION__ "TIOCOPEN expected"); logioctl(LOG_WARNING, "was", rqinf.request); return FAIL; } DBG(syslog(LOG_DEBUG, __FUNCTION__ ": requesting process id is %d", rqinf.pid)); pty_peer[mfd] = rqinf.pid; if (ioctl(mfd, TIOCREQSET, &rqinf) == FAIL) if(errno == EINVAL) { syslog(LOG_WARNING, __FUNCTION__ ":TIOCREQCHECK: client died"); return FAIL; } else { syslog(LOG_ERR, __FUNCTION__ ":TIOREQSET: %m"); return FAIL; } return pty_peer[mfd]; } if(FD_ISSET(mfd, &rdfds)) return pty_peer[mfd]; return SUCCESS; } #else /* ! hpux */ result plisten(int mfd) { fd_set rcfds; int nfds; syslog(LOG_INFO, __FUNCTION__ ": listening ..."); FD_ZERO(&rcfds); FD_SET(mfd, &rcfds); nfds = select(FD_SETSIZE, &rcfds, NULL, NULL, NULL); /* * Select returned which implies that a process opened the * slave side. Thus we can savely close the slave. Note, * that we should not waist time (e.g. by a call to syslog) * before the close system call is issued or else a deadlock * may occur if the file sent to the slave is short (only * about 6 characters.) */ if (nfds < 0) { syslog(LOG_ERR, __FUNCTION__ ":select: %m"); return FAIL; } /* * I just don't know exactly how to handle the condition when * select returns 0. Simply issue a warning and fail. */ if (nfds == 0) { syslog(LOG_WARNING, __FUNCTION__ ":select: timeout??"); return FAIL; } /* * Select returned with data ready. At this time we will not read * anything, since our job was only to listen if something happens. */ return SUCCESS; } #endif /* !hpux */ result shutdown_pty(int mfd) { #ifndef hpux free(pty_slave[mfd]); #endif if( close(mfd) == FAIL ) { syslog(LOG_ERR, __FUNCTION__ ":close master: %m"); return FAIL; } DBG(syslog(LOG_DEBUG, __FUNCTION__ ": master closed")); return SUCCESS; } result peername_pty(int s, peername_t peer) { char host[MAXHOSTNAMELEN]; if(gethostname(host, MAXHOSTNAMELEN) == FAIL) { syslog(LOG_ERR, "gethostname: %m"); return FAIL; } DBG(syslog(LOG_DEBUG, "hostname: `%s'", host)); strncpy(peer, gethostbyname(host)->h_name, PEERNAME_SIZE); return SUCCESS; }