/* Copyright (c) 2008, 2009 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de) * Micah Cowan (micah@cowan.name) * Sadrul Habib Chowdhury (sadrul@users.sourceforge.net) * Copyright (c) 1993-2002, 2003, 2005, 2006, 2007 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de) * Copyright (c) 1987 Oliver Laumann * * 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 3, 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 (see the file COPYING); if not, see * https://www.gnu.org/licenses/, or contact Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * **************************************************************** */ #include "config.h" #include #include #include #include #include #include # include # ifdef _OpenBSD_ # include # endif # include #ifndef SIGINT # include #endif #include "screen.h" #ifdef HAVE_DIRENT_H # include #else # include # define dirent direct #endif #ifndef CMSG_LEN #define CMSG_LEN(length) ((_CMSG_DATA_ALIGN(sizeof(struct cmsghdr))) + (length)) #endif #ifndef CMSG_SPACE #define CMSG_SPACE(length) ((_CMSG_DATA_ALIGN(sizeof(struct cmsghdr))) + (_CMSG_DATA_ALIGN(length))) #endif #include "extern.h" #include "list_generic.h" static int CheckPid __P((int)); static void ExecCreate __P((struct msg *)); static void DoCommandMsg __P((struct msg *)); #if defined(_SEQUENT_) # define connect sconnect /* _SEQUENT_ has braindamaged connect */ static int sconnect __P((int, struct sockaddr *, int)); #endif static void FinishAttach __P((struct msg *)); static void FinishDetach __P((struct msg *)); static void AskPassword __P((struct msg *)); extern char *RcFileName, *extra_incap, *extra_outcap; extern int ServerSocket, real_uid, real_gid, eff_uid, eff_gid; extern int dflag, iflag, rflag, lsflag, quietflag, wipeflag, xflag; extern int queryflag; extern char *attach_tty, *LoginName, HostName[]; extern struct display *display, *displays; extern struct win *fore, **wtab, *console_window, *windows; extern struct layer *flayer; extern struct layout *layout_attach, *layout_last, layout_last_marker; extern struct NewWindow nwin_undef; #ifdef MULTIUSER extern char *multi; #endif extern int maxwin; extern char *getenv(); extern char SockPath[]; extern struct event serv_read; extern char *rc_name; extern struct comm comms[]; #ifdef MULTIUSER # define SOCKMODE (S_IWRITE | S_IREAD | (displays ? S_IEXEC : 0) | (multi ? 1 : 0)) #else # define SOCKMODE (S_IWRITE | S_IREAD | (displays ? S_IEXEC : 0)) #endif /* * Socket directory manager * * fdp: pointer to store the first good socket. * nfoundp: pointer to store the number of sockets found matching. * notherp: pointer to store the number of sockets not matching. * match: string to match socket name. * * The socket directory must be in SockPath! * The global variables LoginName, multi, rflag, xflag, dflag, * quietflag, SockPath are used. * * The first good socket is stored in fdp and its name is * appended to SockPath. * If none exists or fdp is NULL SockPath is not changed. * * Returns: number of good sockets. * */ int FindSocket(fdp, nfoundp, notherp, match, is_sock) int *fdp; int *nfoundp, *notherp; char *match; bool *is_sock; { DIR *dirp; struct dirent *dp; struct stat st; int mode; int sdirlen; int matchlen = 0; char *name, *n; int firsts = -1, sockfd; char *firstn = NULL; int nfound = 0, ngood = 0, ndead = 0, nwipe = 0, npriv = 0; int nperfect = 0; struct sent { struct sent *next; int mode; char *name; } *slist, **slisttail, *sent, *nsent; if (match) { matchlen = strlen(match); #ifdef NAME_MAX if (matchlen > NAME_MAX) matchlen = NAME_MAX; #endif } /* * SockPath contains the socket directory. * At the end of FindSocket the socket name will be appended to it. * Thus FindSocket() can only be called once! */ sdirlen = strlen(SockPath); #ifdef USE_SETEUID xseteuid(real_uid); xsetegid(real_gid); #endif if ((dirp = opendir(SockPath)) == 0) Panic(errno, "Cannot opendir %s", SockPath); slist = 0; slisttail = &slist; while ((dp = readdir(dirp))) { int cmatch = 0; name = dp->d_name; debug1("- %s\n", name); if (*name == 0 || *name == '.' || strlen(name) > 2*MAXSTR) continue; if (matchlen) { n = name; /* if we don't want to match digits. Skip them */ if ((*match <= '0' || *match > '9') && (*n > '0' && *n <= '9')) { while (*n >= '0' && *n <= '9') n++; if (*n == '.') n++; } /* the tty prefix is optional */ if (strncmp(match, "tty", 3) && strncmp(n, "tty", 3) == 0) n += 3; if (strncmp(match, n, matchlen)) { if (n == name && *match > '0' && *match <= '9') { while (*n >= '0' && *n <= '9') n++; if (*n == '.') n++; if (strncmp(match, n, matchlen)) continue; } else continue; } else cmatch = (*(n + matchlen) == 0); debug1(" -> matched %s\n", match); } sprintf(SockPath + sdirlen, "/%s", name); debug1("stat %s\n", SockPath); errno = 0; debug2("uid = %d, gid = %d\n", getuid(), getgid()); debug2("euid = %d, egid = %d\n", geteuid(), getegid()); if (stat(SockPath, &st)) { debug1("errno = %d\n", errno); continue; } *is_sock = S_ISSOCK(st.st_mode); if (!(*is_sock) && !S_ISFIFO(st.st_mode)) continue; debug2("st.st_uid = %d, real_uid = %d\n", st.st_uid, real_uid); #ifdef SOCKDIR /* if SOCKDIR is not defined, the socket is in $HOME. in that case it does not make sense to compare uids. */ if ((int)st.st_uid != real_uid) continue; #endif mode = (int)st.st_mode & 0777; debug1(" has mode 0%03o\n", mode); #ifdef MULTIUSER if (multi && ((mode & 0677) != 0601)) { debug(" is not a MULTI-USER session"); if (strcmp(multi, LoginName)) { debug(" and we are in a foreign directory.\n"); mode = -4; } else { debug(", but it is our own session.\n"); } } #endif debug(" store it.\n"); if ((sent = (struct sent *)malloc(sizeof(struct sent))) == 0) continue; sent->next = 0; sent->name = SaveStr(name); sent->mode = mode; *slisttail = sent; slisttail = &sent->next; nfound++; sockfd = MakeClientSocket(0, *is_sock); #ifdef USE_SETEUID /* MakeClientSocket sets ids back to eff */ xseteuid(real_uid); xsetegid(real_gid); #endif if (sockfd == -1) { debug2(" MakeClientSocket failed, unreachable? %d %d\n", matchlen, wipeflag); sent->mode = -3; #ifndef SOCKDIR_IS_LOCAL_TO_HOST /* Unreachable - it is dead if we detect that it's local * or we specified a match */ n = name + strlen(name) - 1; while (n != name && *n != '.') n--; if (matchlen == 0 && !(*n == '.' && n[1] && strncmp(HostName, n + 1, strlen(n + 1)) == 0)) { npriv++; /* a good socket that was not for us */ continue; } #endif ndead++; sent->mode = -1; if (wipeflag) { if (unlink(SockPath) == 0) { sent->mode = -2; nwipe++; } } continue; } mode &= 0776; /* Shall we connect ? */ debug2(" connecting: mode=%03o, rflag=%d, ", mode, rflag); debug2("xflag=%d, dflag=%d ?\n", xflag, dflag); /* * mode 600: socket is detached. * mode 700: socket is attached. * xflag implies rflag here. * * fail, when socket mode mode is not 600 or 700 * fail, when we want to detach w/o reattach, but it already is detached. * fail, when we only want to attach, but mode 700 and not xflag. * fail, if none of dflag, rflag, xflag is set. */ if ((mode != 0700 && mode != 0600) || (dflag && !rflag && !xflag && mode == 0600) || (!dflag && rflag && mode == 0700 && !xflag) || (!dflag && !rflag && !xflag)) { close(sockfd); debug(" no!\n"); npriv++; /* a good socket that was not for us */ continue; } ngood++; if (cmatch) nperfect++; if (fdp && (firsts == -1 || (cmatch && nperfect == 1))) { if (firsts != -1) close(firsts); firsts = sockfd; firstn = sent->name; debug(" taken.\n"); } else { debug(" discarded.\n"); close(sockfd); } } (void)closedir(dirp); if (!lsflag && nperfect == 1) ngood = nperfect; if (nfound && (lsflag || ngood != 1) && !quietflag) { switch(ngood) { case 0: Msg(0, nfound > 1 ? "There are screens on:" : "There is a screen on:"); break; case 1: Msg(0, nfound > 1 ? "There are several screens on:" : "There is a suitable screen on:"); break; default: Msg(0, "There are several suitable screens on:"); break; } for (sent = slist; sent; sent = sent->next) { switch (sent->mode) { case 0700: printf("\t%s\t(Attached)\n", sent->name); break; case 0600: printf("\t%s\t(Detached)\n", sent->name); break; #ifdef MULTIUSER case 0701: printf("\t%s\t(Multi, attached)\n", sent->name); break; case 0601: printf("\t%s\t(Multi, detached)\n", sent->name); break; #endif case -1: /* No trigraphs here! */ printf("\t%s\t(Dead ?%c?)\n", sent->name, '?'); break; case -2: printf("\t%s\t(Removed)\n", sent->name); break; case -3: printf("\t%s\t(Remote or dead)\n", sent->name); break; case -4: printf("\t%s\t(Private)\n", sent->name); break; } } } if (ndead && !quietflag) { char *m = "Remove dead screens with 'screen -wipe'."; if (wipeflag) Msg(0, "%d socket%s wiped out.", nwipe, nwipe > 1 ? "s" : ""); else Msg(0, m, ndead > 1 ? "s" : "", ndead > 1 ? "" : "es"); /* other args for nethack */ } if (firsts != -1) { sprintf(SockPath + sdirlen, "/%s", firstn); *fdp = firsts; } else SockPath[sdirlen] = 0; for (sent = slist; sent; sent = nsent) { nsent = sent->next; free(sent->name); free((char *)sent); } #ifdef USE_SETEUID xseteuid(eff_uid); xsetegid(eff_gid); #endif if (notherp) *notherp = npriv; if (nfoundp) *nfoundp = nfound - nwipe; return ngood; } /* FIFO (legacy mode) */ static int MakeServerFifo() { register int s; struct stat st; #ifdef USE_SETEUID xseteuid(real_uid); xsetegid(real_gid); #endif s = open(SockPath, O_WRONLY | O_NONBLOCK); if (s >= 0) { debug("huii, my fifo already exists??\n"); if (quietflag) { Kill(D_userpid, SIG_BYE); eexit(11); } Msg(0, "There is already a screen running on %s.", Filename(SockPath)); if (stat(SockPath, &st) < 0) Panic(errno, "stat"); #ifdef SOCKDIR /* if SOCKDIR is not defined, the socket is in $HOME. \ in that case it does not make sense to compare uids. */ if ((int)st.st_uid != real_uid) Panic(0, "Unfortunately you are not its owner."); #endif if ((st.st_mode & 0700) == 0600) Panic(0, "To resume it, use \"screen -r\""); else Panic(0, "It is not detached."); /* NOTREACHED */ } # ifdef USE_SETEUID (void) unlink(SockPath); if (mkfifo(SockPath, SOCKMODE) < 0) Panic(0, "mkfifo %s failed", SockPath); # ifdef BROKEN_PIPE s = open(SockPath, O_RDWR | O_NONBLOCK, 0); # else s = open(SockPath, O_RDONLY | O_NONBLOCK, 0); # endif if (s < 0) Panic(errno, "open fifo %s", SockPath); xseteuid(eff_uid); xsetegid(eff_gid); return s; # else /* !USE_SETEUID */ if (UserContext() > 0) { (void) unlink(SockPath); UserReturn(mkfifo(SockPath, SOCKMODE)); } if (UserStatus()) Panic(0, "mkfifo %s failed", SockPath); # ifdef BROKEN_PIPE s = secopen(SockPath, O_RDWR | O_NONBLOCK, 0); # else s = secopen(SockPath, O_RDONLY | O_NONBLOCK, 0); # endif if (s < 0) Panic(errno, "open fifo %s", SockPath); return s; # endif /* !USE_SETEUID */ } static int MakeClientFifo(err) int err; { register int s = 0; s = secopen(SockPath, O_WRONLY | O_NONBLOCK, 0); if (s >= 0) { (void) fcntl(s, F_SETFL, 0); return s; } if (err) Msg(errno, "%s", SockPath); debug2("MakeClientSocket() open %s failed (%d)\n", SockPath, errno); return -1; } /* Unix Domain Sockets */ static int MakeServerUnixSocket() { register int s; struct sockaddr_un a; struct stat st; s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) Panic(errno, "socket"); a.sun_family = AF_UNIX; strncpy(a.sun_path, SockPath, sizeof(a.sun_path)); a.sun_path[sizeof(a.sun_path) - 1] = 0; # ifdef USE_SETEUID xseteuid(real_uid); xsetegid(real_gid); # endif if (connect(s, (struct sockaddr *) &a, strlen(SockPath) + 2) != -1) { debug("oooooh! socket already is alive!\n"); if (quietflag) { Kill(D_userpid, SIG_BYE); /* * oh, well. nobody receives that return code. papa * dies by signal. */ eexit(11); } Msg(0, "There is already a screen running on %s.", Filename(SockPath)); if (stat(SockPath, &st) < 0) Panic(errno, "stat"); #ifdef SOCKDIR /* if SOCKDIR is not defined, the socket is in $HOME. \ in that case it does not make sense to compare uids. */ if (st.st_uid != real_uid) Panic(0, "Unfortunately you are not its owner."); #endif if ((st.st_mode & 0700) == 0600) Panic(0, "To resume it, use \"screen -r\""); else Panic(0, "It is not detached."); /* NOTREACHED */ } #if defined(m88k) || defined(sysV68) close(s); /* we get bind: Invalid argument if this is not done */ s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) Panic(errno, "reopen socket"); #endif (void) unlink(SockPath); if (bind(s, (struct sockaddr *) & a, strlen(SockPath) + 2) == -1) Panic(errno, "bind (%s)", SockPath); #ifdef SOCK_NOT_IN_FS { int f = secopen(SockPath, O_RDWR | O_CREAT, SOCKMODE); if (f < 0) Panic(errno, "shadow socket open"); close(f); } #else chmod(SockPath, SOCKMODE); #ifndef USE_SETEUID chown(SockPath, real_uid, real_gid); #endif #endif /* SOCK_NOT_IN_FS */ if (listen(s, 5) == -1) Panic(errno, "listen"); #ifdef F_SETOWN fcntl(s, F_SETOWN, getpid()); debug1("Serversocket owned by %d\n", fcntl(s, F_GETOWN, 0)); #endif /* F_SETOWN */ #ifdef USE_SETEUID xseteuid(eff_uid); xsetegid(eff_gid); #endif return s; } static int MakeClientUnixSocket(err) int err; { register int s; struct sockaddr_un a; s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) Panic(errno, "socket"); a.sun_family = AF_UNIX; strncpy(a.sun_path, SockPath, sizeof(a.sun_path)); a.sun_path[sizeof(a.sun_path) - 1] = 0; #ifdef USE_SETEUID xseteuid(real_uid); xsetegid(real_gid); #else if (access(SockPath, W_OK)) { if (err) Msg(errno, "%s", SockPath); debug2("MakeClientSocket: access(%s): %d.\n", SockPath, errno); close(s); return -1; } #endif if (connect(s, (struct sockaddr *)&a, strlen(SockPath) + 2) == -1) { if (err) Msg(errno, "%s: connect", SockPath); debug("MakeClientSocket: connect failed.\n"); close(s); s = -1; } #ifdef USE_SETEUID xseteuid(eff_uid); xsetegid(eff_gid); #endif return s; } int MakeServerSocket(socket) bool socket; { if (socket) return MakeServerUnixSocket(); return MakeServerFifo(); } int MakeClientSocket(err, socket) int err; bool socket; { if (socket) return MakeClientUnixSocket(err); return MakeClientFifo(err); } /* ** ** Message send and receive routines ** */ void SendCreateMsg(sty, nwin) char *sty; struct NewWindow *nwin; { int s; struct msg m; register char *p; register int len, n; char **av; bool is_socket; #ifdef NAME_MAX if (strlen(sty) > NAME_MAX) sty[NAME_MAX] = 0; #endif if (strlen(sty) > 2 * MAXSTR - 1) sty[2 * MAXSTR - 1] = 0; sprintf(SockPath + strlen(SockPath), "/%s", sty); is_socket = IsSocket(SockPath); if ((s = MakeClientSocket(1, is_socket)) == -1) exit(1); debug1("SendCreateMsg() to '%s'\n", SockPath); bzero((char *)&m, sizeof(m)); m.type = MSG_CREATE; strncpy(m.m_tty, attach_tty, sizeof(m.m_tty) - 1); m.m_tty[sizeof(m.m_tty) - 1] = 0; p = m.m.create.line; n = 0; if (nwin->args != nwin_undef.args) for (av = nwin->args; *av && n < MAXARGS - 1; ++av, ++n) { len = strlen(*av) + 1; if (p + len >= m.m.create.line + sizeof(m.m.create.line) - 1) break; strcpy(p, *av); p += len; } if (nwin->aka != nwin_undef.aka && p + strlen(nwin->aka) + 1 < m.m.create.line + sizeof(m.m.create.line)) strcpy(p, nwin->aka); else *p = '\0'; m.m.create.nargs = n; m.m.create.aflag = nwin->aflag; m.m.create.flowflag = nwin->flowflag; m.m.create.lflag = nwin->lflag; m.m.create.hheight = nwin->histheight; if (getcwd(m.m.create.dir, sizeof(m.m.create.dir)) == 0) { Msg(errno, "getcwd"); goto end; } if (nwin->term != nwin_undef.term) strncpy(m.m.create.screenterm, nwin->term, MAXTERMLEN); m.m.create.screenterm[MAXTERMLEN] = '\0'; m.protocol_revision = MSG_REVISION; debug1("SendCreateMsg writing '%s'\n", m.m.create.line); if (write(s, (char *) &m, sizeof m) != sizeof m) Msg(errno, "write"); end: close(s); } int SendErrorMsg(tty, buf) char *tty, *buf; { int s; struct msg m; bool is_socket; debug2("SendErrorMsg: %s %s\n", tty, buf); strncpy(m.m.message, buf, sizeof(m.m.message) - 1); m.m.message[sizeof(m.m.message) - 1] = 0; is_socket = IsSocket(SockPath); s = MakeClientSocket(0, is_socket); if (s < 0) return -1; m.type = MSG_ERROR; strncpy(m.m_tty, tty, sizeof(m.m_tty) - 1); m.m_tty[sizeof(m.m_tty) - 1] = 0; m.protocol_revision = MSG_REVISION; debug1("SendErrorMsg(): writing to '%s'\n", SockPath); (void) write(s, (char *) &m, sizeof m); close(s); return 0; } static void ExecCreate(mp) struct msg *mp; { struct NewWindow nwin; char *args[MAXARGS]; register int n; register char **pp = args, *p = mp->m.create.line; char buf[20]; nwin = nwin_undef; n = mp->m.create.nargs; if (n > MAXARGS - 1) n = MAXARGS - 1; /* ugly hack alert... should be done by the frontend! */ if (n) { int l, num; l = strlen(p); if (IsNumColon(p, 10, buf, sizeof(buf))) { if (*buf) nwin.aka = buf; num = atoi(p); if (num < 0 || num > maxwin - 1) num = 0; nwin.StartAt = num; p += l + 1; n--; } } for (; n > 0; n--) { *pp++ = p; p += strlen(p) + 1; } *pp = 0; if (*p) nwin.aka = p; if (*args) nwin.args = args; nwin.aflag = mp->m.create.aflag; nwin.flowflag = mp->m.create.flowflag; if (*mp->m.create.dir) nwin.dir = mp->m.create.dir; nwin.lflag = mp->m.create.lflag; nwin.histheight = mp->m.create.hheight; if (*mp->m.create.screenterm) nwin.term = mp->m.create.screenterm; MakeWindow(&nwin); } static int CheckPid(pid) int pid; { debug1("Checking pid %d\n", pid); if (pid < 2) return -1; if (eff_uid == real_uid) return kill(pid, 0); if (UserContext() > 0) UserReturn(kill(pid, 0)); return UserStatus(); } #ifdef hpux /* * From: "F. K. Bruner" * From: "Dan Egnor" Tue Aug 10 06:56:45 1993 * The problem is that under HPUX (and possibly other systems too) there are * two equivalent device files for each pty/tty device: * /dev/ttyxx == /dev/pty/ttyxx * /dev/ptyxx == /dev/ptym/ptyxx * I didn't look into the exact specifics, but I've run across this problem * before: Even if you open /dev/ttyxx as fds 0 1 & 2 for a process, if that * process calls the system to determine its tty, it'll get /dev/pty/ttyxx. * * Earlier versions seemed to work -- wonder what they did. */ static int ttycmp(s1, s2) char *s1, *s2; { if (strlen(s1) > 5) s1 += strlen(s1) - 5; if (strlen(s2) > 5) s2 += strlen(s2) - 5; return strcmp(s1, s2); } # define TTYCMP(a, b) ttycmp(a, b) #else # define TTYCMP(a, b) strcmp(a, b) #endif static int CreateTempDisplay(m, recvfd, wi) struct msg *m; int recvfd; struct win *wi; { int pid; int attach; char *user; int i; struct mode Mode; struct display *olddisplays = displays; switch (m->type) { case MSG_CONT: case MSG_ATTACH: pid = m->m.attach.apid; user = m->m.attach.auser; attach = 1; break; #ifdef REMOTE_DETACH case MSG_DETACH: # ifdef POW_DETACH case MSG_POW_DETACH: # endif /* POW_DETACH */ pid = m->m.detach.dpid; user = m->m.detach.duser; attach = 0; break; #endif default: return -1; } if (CheckPid(pid)) { Msg(0, "Attach attempt with bad pid(%d)!", pid); return -1; } if (recvfd != -1) { int ret; char ttyname_in_ns[MAXPATHLEN]; char *myttyname; i = recvfd; recvfd = -1; memset(&ttyname_in_ns, 0, sizeof(ttyname_in_ns)); errno = 0; myttyname = GetPtsPathOrSymlink(i); if (myttyname && errno == ENODEV) { ret = readlink(myttyname, ttyname_in_ns, sizeof(ttyname_in_ns)); if (ret < 0 || (size_t)ret >= sizeof(ttyname_in_ns)) { Msg(errno, "Could not perform necessary sanity checks on pts device."); close(i); Kill(pid, SIG_BYE); return -1; } if (strcmp(ttyname_in_ns, m->m_tty)) { Msg(errno, "Attach: passed fd does not match tty: %s - %s!", ttyname_in_ns, m->m_tty[0] != '\0' ? m->m_tty : "(null)"); close(i); Kill(pid, SIG_BYE); return -1; } /* m->m_tty so far contains the actual name of the pts device in the * its (e.g. /dev/pts/0). This name however is not valid in the * current namespace. So after we verified that the symlink returned * by GetPtsPathOrSymlink() refers to the same pts device in this * namespace we need to update m->m_tty to use that symlink for all * future operations. */ strncpy(m->m_tty, myttyname, sizeof(m->m_tty) - 1); m->m_tty[sizeof(m->m_tty) - 1] = 0; } else if (myttyname == 0 || strcmp(myttyname, m->m_tty)) { Msg(errno, "Attach: passed fd does not match tty: %s - %s!", m->m_tty, myttyname ? myttyname : "NULL"); close(i); Kill(pid, SIG_BYE); return -1; } } else if ((i = secopen(m->m_tty, O_RDWR | O_NONBLOCK, 0)) < 0) { Msg(errno, "Attach: Could not open %s!", m->m_tty); Kill(pid, SIG_BYE); return -1; } #ifdef MULTIUSER if (attach) Kill(pid, SIGCONT); #endif #if defined(ultrix) || defined(pyr) || defined(NeXT) brktty(i); /* for some strange reason this must be done */ #endif if (attach) { if (display || wi) { write(i, "Attaching from inside of screen?\n", 33); close(i); Kill(pid, SIG_BYE); Msg(0, "Attach msg ignored: coming from inside."); return -1; } #ifdef MULTIUSER if (strcmp(user, LoginName)) if (*FindUserPtr(user) == 0) { write(i, "Access to session denied.\n", 26); close(i); Kill(pid, SIG_BYE); Msg(0, "Attach: access denied for user %s.", user); return -1; } #endif debug2("RecMsg: apid %d is o.k. and we just opened '%s'\n", pid, m->m_tty); #ifndef MULTI if (displays) { write(i, "Screen session in use.\n", 23); close(i); Kill(pid, SIG_BYE); return -1; } #endif } /* create new display */ GetTTY(i, &Mode); if (MakeDisplay(user, m->m_tty, attach ? m->m.attach.envterm : "", i, pid, &Mode) == 0) { write(i, "Could not make display.\n", 24); close(i); Msg(0, "Attach: could not make display for user %s", user); Kill(pid, SIG_BYE); return -1; } #ifdef ENCODINGS if (attach) { # ifdef UTF8 D_encoding = m->m.attach.encoding == 1 ? UTF8 : m->m.attach.encoding ? m->m.attach.encoding - 1 : 0; # else D_encoding = m->m.attach.encoding ? m->m.attach.encoding - 1 : 0; # endif if (D_encoding < 0 || !EncodingName(D_encoding)) D_encoding = 0; } #endif if (iflag && olddisplays) { iflag = 0; #if defined(TERMIO) || defined(POSIX) olddisplays->d_NewMode.tio.c_cc[VINTR] = VDISABLE; olddisplays->d_NewMode.tio.c_lflag &= ~ISIG; #else /* TERMIO || POSIX */ olddisplays->d_NewMode.m_tchars.t_intrc = -1; #endif /* TERMIO || POSIX */ SetTTY(olddisplays->d_userfd, &olddisplays->d_NewMode); } SetMode(&D_OldMode, &D_NewMode, D_flow, iflag); SetTTY(D_userfd, &D_NewMode); if (fcntl(D_userfd, F_SETFL, FNBLOCK)) Msg(errno, "Warning: NBLOCK fcntl failed"); return 0; } void ReceiveMsg() { int left, len; static struct msg m; char *p; int ns = ServerSocket; struct win *wi; int recvfd = -1; struct acluser *user; bool is_socket; /* Socket specific variables. */ struct sockaddr_un a; struct msghdr msg; struct iovec iov; char control[1024]; is_socket = IsSocket(SockPath); if (!is_socket) { debug("Ha, there was someone knocking on my fifo??\n"); if (fcntl(ServerSocket, F_SETFL, 0) == -1) Panic(errno, "BLOCK fcntl"); p = (char *)&m; left = sizeof(m); } else { len = sizeof(a); debug("Ha, there was someone knocking on my socket??\n"); if ((ns = accept(ns, (struct sockaddr *)&a, (void *)&len)) < 0) { Msg(errno, "accept"); return; } p = (char *)&m; left = sizeof(m); bzero(&msg, sizeof(msg)); iov.iov_base = &m; iov.iov_len = left; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_controllen = sizeof(control); msg.msg_control = &control; while (left > 0) { len = recvmsg(ns, &msg, 0); if (len < 0 && errno == EINTR) continue; if (len < 0) { close(ns); Msg(errno, "read"); return; } if (msg.msg_controllen) { struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { int cl; char *cp; if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) continue; cp = (char *)CMSG_DATA(cmsg); cl = cmsg->cmsg_len; while (cl >= CMSG_LEN(sizeof(int))) { int passedfd; bcopy(cp, &passedfd, sizeof(int)); if (recvfd >= 0 && passedfd != recvfd) close(recvfd); recvfd = passedfd; cl -= CMSG_LEN(sizeof(int)); } } } p += len; left -= len; break; } } while (left > 0) { len = read(ns, p, left); if (len < 0 && errno == EINTR) continue; if (len <= 0) break; p += len; left -= len; } if (!is_socket) { #ifndef BROKEN_PIPE /* Reopen pipe to prevent EOFs at the select() call */ close(ServerSocket); if ((ServerSocket = secopen(SockPath, O_RDONLY | O_NONBLOCK, 0)) < 0) Panic(errno, "reopen fifo %s", SockPath); evdeq(&serv_read); serv_read.fd = ServerSocket; evenq(&serv_read); #endif } else { close(ns); } if (len < 0) { Msg(errno, "read"); if (recvfd != -1) close(recvfd); return; } if (left > 0) { if (left != sizeof(m)) Msg(0, "Message %d of %d bytes too small", left, (int)sizeof(m)); else debug("No data on socket.\n"); return; } if (m.protocol_revision != MSG_REVISION) { if (recvfd != -1) close(recvfd); Msg(0, "Invalid message (magic 0x%08x).", m.protocol_revision); return; } debug2("*** RecMsg: type %d tty %s\n", m.type, m.m_tty); if (m.type != MSG_ATTACH && recvfd != -1) { close(recvfd); recvfd = -1; } for (display = displays; display; display = display->d_next) if (TTYCMP(D_usertty, m.m_tty) == 0) break; debug2("display: %s display %sfound\n", m.m_tty, display ? "" : "not "); wi = 0; if (!display) { for (wi = windows; wi; wi = wi->w_next) if (!TTYCMP(m.m_tty, wi->w_tty)) { /* XXX: hmmm, rework this? */ display = wi->w_layer.l_cvlist ? wi->w_layer.l_cvlist->c_display : 0; debug2("but window %s %sfound.\n", m.m_tty, display ? "" : "(backfacing)"); break; } } /* Remove the status to prevent garbage on the screen */ if (display && D_status) RemoveStatus(); if (display && !D_tcinited && m.type != MSG_HANGUP) { if (recvfd != -1) close(recvfd); return; /* ignore messages for bad displays */ } switch (m.type) { case MSG_WINCH: if (display) CheckScreenSize(1); /* Change fore */ break; case MSG_CREATE: /* * the window that issued the create message need not be an * active * window. Then we create the window without having a display. * Resulting in another inactive window. */ ExecCreate(&m); break; case MSG_CONT: if (display && D_userpid != 0 && kill(D_userpid, 0) == 0) break; /* Intruder Alert */ debug2("RecMsg: apid=%d,was %d\n", m.m.attach.apid, display ? D_userpid : 0); /* FALLTHROUGH */ case MSG_ATTACH: if (CreateTempDisplay(&m, recvfd, wi)) break; #ifdef PASSWORD if (D_user->u_password && *D_user->u_password) AskPassword(&m); else #endif FinishAttach(&m); break; case MSG_ERROR: { int blocked=D_blocked; if(D_blocked == 4) /* allow error messages while in blanker mode */ D_blocked=0; /* likely they're from failed blanker */ Msg(0, "%s", m.m.message); D_blocked=blocked; } break; case MSG_HANGUP: if (!wi) /* ignore hangups from inside */ Hangup(); break; #ifdef REMOTE_DETACH case MSG_DETACH: #ifdef POW_DETACH case MSG_POW_DETACH: #endif /* POW_DETACH */ #ifdef PASSWORD user = *FindUserPtr(m.m.detach.duser); if (user && user->u_password && *user->u_password) { if (CreateTempDisplay(&m, recvfd, 0)) break; AskPassword(&m); } else #endif /* PASSWORD */ FinishDetach(&m); break; #endif case MSG_QUERY: { char *oldSockPath = SaveStr(SockPath); strcpy(SockPath, m.m.command.writeback); bool is_socket = IsSocket(SockPath); int s = MakeClientSocket(0, is_socket); strcpy(SockPath, oldSockPath); Free(oldSockPath); if (s >= 0) { queryflag = s; DoCommandMsg(&m); close(s); } else queryflag = -1; Kill(m.m.command.apid, (queryflag >= 0) ? SIGCONT : SIG_BYE); /* Send SIG_BYE if an error happened */ queryflag = -1; } break; case MSG_COMMAND: DoCommandMsg(&m); break; default: Msg(0, "Invalid message (type %d).", m.type); } } void ReceiveRaw(s) int s; { char rd[256]; int len = 0; struct sockaddr_un a; bool is_socket; is_socket = IsSocket(SockPath); if (!is_socket) { if (fcntl(s, F_SETFL, 0) < 0) Panic(errno, "BLOCK fcntl"); } else { len = sizeof(a); s = accept(s, (struct sockaddr *)&a, (void *)&len); if (s < 0) { Msg(errno, "accept"); return; } } while ((len = read(s, rd, 255)) > 0) { rd[len] = 0; printf("%s", rd); } close(s); } #if defined(_SEQUENT_) #undef connect /* * sequent_ptx socket emulation must have mode 000 on the socket! */ static int sconnect(s, sapp, len) int s, len; struct sockaddr *sapp; { register struct sockaddr_un *sap; struct stat st; int x; sap = (struct sockaddr_un *)sapp; if (stat(sap->sun_path, &st)) return -1; chmod(sap->sun_path, 0); x = connect(s, (struct sockaddr *) sap, len); chmod(sap->sun_path, st.st_mode); return x; } #endif /* * Set the mode bits of the socket to the current status */ int chsock() { int r, euid = geteuid(); if (euid != real_uid) { if (UserContext() <= 0) return UserStatus(); } r = chmod(SockPath, SOCKMODE); /* * Sockets usually reside in the /tmp/ area, where sysadmin scripts * may be happy to remove old files. We manually prevent the socket * from becoming old. (chmod does not touch mtime). */ (void)utimes(SockPath, NULL); if (euid != real_uid) UserReturn(r); return r; } /* * Try to recreate the socket/pipe */ int RecoverSocket() { bool is_socket; close(ServerSocket); if ((int)geteuid() != real_uid) { if (UserContext() > 0) UserReturn(unlink(SockPath)); (void)UserStatus(); } else (void) unlink(SockPath); is_socket = IsSocket(SockPath); if ((ServerSocket = MakeServerSocket(is_socket)) < 0) return 0; evdeq(&serv_read); serv_read.fd = ServerSocket; evenq(&serv_read); return 1; } static void FinishAttach(m) struct msg *m; { char *p; int pid; int noshowwin; struct win *wi; ASSERT(display); pid = D_userpid; #ifdef REMOTE_DETACH if (m->m.attach.detachfirst == MSG_DETACH # ifdef POW_DETACH || m->m.attach.detachfirst == MSG_POW_DETACH # endif ) FinishDetach(m); #endif #if defined(pyr) || defined(xelos) || defined(sequent) /* * Kludge for systems with braindamaged termcap routines, * which evaluate $TERMCAP, regardless whether it describes * the correct terminal type or not. */ debug("unsetenv(TERMCAP) in case of a different terminal"); unsetenv("TERMCAP"); #endif /* * We reboot our Terminal Emulator. Forget all we knew about * the old terminal, reread the termcap entries in .screenrc * (and nothing more from .screenrc is read. Mainly because * I did not check, whether a full reinit is safe. jw) * and /etc/screenrc, and initialise anew. */ if (extra_outcap) free(extra_outcap); if (extra_incap) free(extra_incap); extra_incap = extra_outcap = 0; debug2("Message says size (%dx%d)\n", m->m.attach.columns, m->m.attach.lines); #ifdef ETCSCREENRC # ifdef ALLOW_SYSSCREENRC if ((p = getenv("SYSSCREENRC"))) StartRc(p, 1); else # endif StartRc(ETCSCREENRC, 1); #endif StartRc(RcFileName, 1); if (InitTermcap(m->m.attach.columns, m->m.attach.lines)) { FreeDisplay(); Kill(pid, SIG_BYE); return; } MakeDefaultCanvas(); InitTerm(m->m.attach.adaptflag); /* write init string on fd */ if (displays->d_next == 0) (void) chsock(); signal(SIGHUP, SigHup); if (m->m.attach.esc != -1 && m->m.attach.meta_esc != -1) { D_user->u_Esc = m->m.attach.esc; D_user->u_MetaEsc = m->m.attach.meta_esc; } #ifdef UTMPOK /* * we set the Utmp slots again, if we were detached normally * and if we were detached by ^Z. * don't log zomies back in! */ RemoveLoginSlot(); if (displays->d_next == 0) for (wi = windows; wi; wi = wi->w_next) if (wi->w_ptyfd >= 0 && wi->w_slot != (slot_t) -1) SetUtmp(wi); #endif D_fore = NULL; if (layout_attach) { struct layout *lay = layout_attach; if (lay == &layout_last_marker) lay = layout_last; if (lay) { LoadLayout(lay, &D_canvas); SetCanvasWindow(D_forecv, 0); } } /* * there may be a window that we remember from last detach: */ debug1("D_user->u_detachwin = %d\n", D_user->u_detachwin); if (D_user->u_detachwin >= 0) fore = wtab[D_user->u_detachwin]; else fore = 0; /* Wayne wants us to restore the other window too. */ if (D_user->u_detachotherwin >= 0) D_other = wtab[D_user->u_detachotherwin]; noshowwin = 0; if (*m->m.attach.preselect) { if (!strcmp(m->m.attach.preselect, "=")) fore = 0; else if (!strcmp(m->m.attach.preselect, "-")) { fore = 0; noshowwin = 1; } else if (!strcmp(m->m.attach.preselect, "+")) { struct action newscreen; char *na = 0; newscreen.nr = RC_SCREEN; newscreen.args = &na; newscreen.quiet = 0; DoAction(&newscreen, -1); } else fore = FindNiceWindow(fore, m->m.attach.preselect); } else fore = FindNiceWindow(fore, 0); if (fore) SetForeWindow(fore); else if (!noshowwin) { #ifdef MULTIUSER if (!AclCheckPermCmd(D_user, ACL_EXEC, &comms[RC_WINDOWLIST])) #endif { struct display *olddisplay = display; flayer = D_forecv->c_layer; display_windows(1, WLIST_NUM, (struct win *)0); noshowwin = 1; display = olddisplay; /* display_windows can change display */ } } Activate(0); ResetIdle(); if (!D_fore && !noshowwin) ShowWindows(-1); if (displays->d_next == 0 && console_window) { if (TtyGrabConsole(console_window->w_ptyfd, 1, "reattach") == 0) Msg(0, "console %s is on window %d", HostName, console_window->w_number); } debug("activated...\n"); # if defined(DEBUG) && defined(SIG_NODEBUG) if (!dfp) { sleep(1); debug1("Attacher %d must not debug, as we have debug off.\n", pid); kill(pid, SIG_NODEBUG); } # endif /* SIG_NODEBUG */ } static void FinishDetach(m) struct msg *m; { struct display *next, **d, *det; int pid; if (m->type == MSG_ATTACH) pid = D_userpid; else pid = m->m.detach.dpid; /* Remove the temporary display prompting for the password from the list */ for (d = &displays; (det = *d); d = &det->d_next) { if (det->d_userpid == pid) break; } if (det) { *d = det->d_next; det->d_next = 0; } for (display = displays; display; display = next) { next = display->d_next; # ifdef POW_DETACH if (m->type == MSG_POW_DETACH) Detach(D_REMOTE_POWER); else # endif /* POW_DETACH */ if (m->type == MSG_DETACH) Detach(D_REMOTE); else if (m->type == MSG_ATTACH) { #ifdef POW_DETACH if (m->m.attach.detachfirst == MSG_POW_DETACH) Detach(D_REMOTE_POWER); else #endif if (m->m.attach.detachfirst == MSG_DETACH) Detach(D_REMOTE); } } display = displays = det; if (m->type != MSG_ATTACH) { if (display) FreeDisplay(); Kill(pid, SIGCONT); } } #ifdef PASSWORD static void PasswordProcessInput __P((char *, int)); struct pwdata { int l; char buf[MAXLOGINLEN + 1]; struct msg m; }; static void AskPassword(m) struct msg *m; { struct pwdata *pwdata; ASSERT(display); pwdata = (struct pwdata *)malloc(sizeof(struct pwdata)); if (!pwdata) Panic(0, "%s", strnomem); pwdata->l = 0; pwdata->m = *m; D_processinputdata = (char *)pwdata; D_processinput = PasswordProcessInput; AddStr("Screen password: "); } static void PasswordProcessInput(ibuf, ilen) char *ibuf; int ilen; { struct pwdata *pwdata; int c, l; char *up; int pid = D_userpid; pwdata = (struct pwdata *)D_processinputdata; l = pwdata->l; while (ilen-- > 0) { c = *(unsigned char *)ibuf++; if (c == '\r' || c == '\n') { char *buf = NULL; up = D_user->u_password; pwdata->buf[l] = 0; buf = crypt(pwdata->buf, up); if (!buf || strncmp(buf, up, strlen(up))) { /* uh oh, user failed */ bzero(pwdata->buf, sizeof(pwdata->buf)); if (!buf) AddStr("\r\ncrypt() failed.\r\n"); else AddStr("\r\nPassword incorrect.\r\n"); D_processinputdata = 0; /* otherwise freed by FreeDis */ FreeDisplay(); Msg(0, "Illegal reattach attempt from terminal %s.", pwdata->m.m_tty); free(pwdata); Kill(pid, SIG_BYE); return; } /* great, pw matched, all is fine */ bzero(pwdata->buf, sizeof(pwdata->buf)); AddStr("\r\n"); D_processinputdata = 0; D_processinput = ProcessInput; #ifdef REMOTE_DETACH if (pwdata->m.type == MSG_DETACH # ifdef POW_DETACH || pwdata->m.type == MSG_POW_DETACH # endif ) FinishDetach(&pwdata->m); else #endif FinishAttach(&pwdata->m); free(pwdata); return; } if (c == Ctrl('c')) { AddStr("\r\n"); FreeDisplay(); Kill(pid, SIG_BYE); return; } if (c == '\b' || c == 0177) { if (l > 0) l--; continue; } if (c == Ctrl('u')) { l = 0; continue; } if (l < (int)sizeof(pwdata->buf) - 1) pwdata->buf[l++] = c; } pwdata->l = l; } #endif /* 'end' is exclusive, i.e. you should *not* write in *end */ static char * strncpy_escape_quote(dst, src, end) char *dst; const char *src, *end; { while (*src && dst < end) { if (*src == '"') { if (dst + 2 < end) /* \\ \" \0 */ *dst++ = '\\'; else return NULL; } *dst++ = *src++; } if (dst >= end) return NULL; *dst = '\0'; return dst; } static void DoCommandMsg(mp) struct msg *mp; { char *args[MAXARGS]; int argl[MAXARGS]; char fullcmd[MAXSTR]; register char *fc; int n; register char *p = mp->m.command.cmd; struct acluser *user; #ifdef MULTIUSER extern struct acluser *EffectiveAclUser; /* acls.c */ #else extern struct acluser *users; /* acls.c */ #endif n = mp->m.command.nargs; if (n > MAXARGS - 1) n = MAXARGS - 1; for (fc = fullcmd; n > 0; n--) { int len = strlen(p); *fc++ = '"'; if (!(fc = strncpy_escape_quote(fc, p, fullcmd + sizeof(fullcmd) - 2))) /* '"' ' ' */ { Msg(0, "Remote command too long."); queryflag = -1; return; } p += len + 1; *fc++ = '"'; *fc++ = ' '; } if (fc != fullcmd) *--fc = 0; if (Parse(fullcmd, sizeof fullcmd, args, argl) <= 0) { queryflag = -1; return; } #ifdef MULTIUSER user = *FindUserPtr(mp->m.attach.auser); if (user == 0) { Msg(0, "Unknown user %s tried to send a command!", mp->m.attach.auser); queryflag = -1; return; } #else user = users; #endif #ifdef PASSWORD if (user->u_password && *user->u_password) { Msg(0, "User %s has a password, cannot use remote commands (using -Q or -X option).", mp->m.attach.auser); queryflag = -1; return; } #endif if (!display) for (display = displays; display; display = display->d_next) if (D_user == user) break; for (fore = windows; fore; fore = fore->w_next) if (!TTYCMP(mp->m_tty, fore->w_tty)) { if (!display) display = fore->w_layer.l_cvlist ? fore->w_layer.l_cvlist->c_display : 0; /* If the window is not visibile in any display, then do not use the originating window as * the foreground window for the command. This way, if there is an existing display, then * the command will execute from the foreground window of that display. This is necessary so * that commands that are relative to the window (e.g. 'next' etc.) do the right thing. */ if (!fore->w_layer.l_cvlist || !fore->w_layer.l_cvlist->c_display) fore = NULL; break; } if (!display) display = displays; /* sigh */ if (*mp->m.command.preselect) { int i = -1; if (strcmp(mp->m.command.preselect, "-")) { i = WindowByNoN(mp->m.command.preselect); if (i < 0 || !wtab[i]) { Msg(0, "Could not find pre-select window."); queryflag = -1; return; } } fore = i >= 0 ? wtab[i] : 0; } else if (!fore) { if (display && D_user == user) fore = Layer2Window(display->d_forecv->c_layer); if (!fore) { fore = user->u_detachwin >= 0 ? wtab[user->u_detachwin] : 0; fore = FindNiceWindow(fore, 0); } } if (!fore) fore = windows; /* sigh */ #ifdef MULTIUSER EffectiveAclUser = user; #endif if (*args) { char *oldrcname = rc_name; rc_name = "-X"; debug3("Running command on display %x window %x (%d)\n", display, fore, fore ? fore->w_number : -1); flayer = fore ? &fore->w_layer : 0; if (fore && fore->w_savelayer && (fore->w_blocked || fore->w_savelayer->l_cvlist == 0)) flayer = fore->w_savelayer; DoCommand(args, argl); rc_name = oldrcname; } #ifdef MULTIUSER EffectiveAclUser = 0; #endif } int SendAttachMsg(s, m, fd) int s; struct msg *m; int fd; { int r; struct msghdr msg; struct iovec iov; char buf[CMSG_SPACE(sizeof(int))]; struct cmsghdr *cmsg; iov.iov_base = (char *)m; iov.iov_len = sizeof(*m); bzero(&msg, sizeof(msg)); msg.msg_name = 0; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = buf; msg.msg_controllen = sizeof(buf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); bcopy(&fd, CMSG_DATA(cmsg), sizeof(int)); msg.msg_controllen = cmsg->cmsg_len; while(1) { r = sendmsg(s, &msg, 0); if (r == -1 && errno == EINTR) continue; if (r == -1) return -1; return 0; } } bool IsSocket(path) const char *path; { struct stat st; if (stat(path, &st) < 0) return false; return S_ISSOCK(st.st_mode); }