/* 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 <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
# include <sys/socket.h>
# ifdef _OpenBSD_
#  include <sys/uio.h>
# endif
# include <sys/un.h>

#ifndef SIGINT
# include <signal.h>
#endif

#include "screen.h"

#ifdef HAVE_DIRENT_H
# include <dirent.h>
#else
# include <sys/dir.h>
# 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" <napalm@ugcs.caltech.edu>
 * From: "Dan Egnor" <egnor@oracorp.com> 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);
}