/* 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 <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <signal.h>
#include "screen.h"
#include "extern.h"

#include <pwd.h>

static int WriteMessage __P((int, struct msg *));
static sigret_t AttacherSigInt __P(SIGPROTOARG);
#if defined(SIGWINCH) && defined(TIOCGWINSZ)
static sigret_t AttacherWinch __P(SIGPROTOARG);
#endif
#ifdef LOCK
static sigret_t DoLock __P(SIGPROTOARG);
static void  LockTerminal __P((void));
static sigret_t LockHup __P(SIGPROTOARG);
static void  screen_builtin_lck __P((void));
#endif
#ifdef DEBUG
static sigret_t AttacherChld __P(SIGPROTOARG);
#endif
static sigret_t AttachSigCont __P(SIGPROTOARG);

extern int real_uid, real_gid, eff_uid, eff_gid;
extern int ServerSocket;
extern struct display *displays;
extern char *SockName, *SockMatch, SockPath[];
extern char HostName[];
extern struct passwd *ppp;
extern char *attach_tty, *attach_term, *LoginName, *preselect;
/* Indicator whether the current tty exists in another namespace. */
extern bool attach_tty_is_in_new_ns;
/* Content of the tty symlink when attach_tty_is_in_new_ns == true. */
extern char attach_tty_name_in_ns[];
extern int xflag, dflag, rflag, quietflag, adaptflag;
extern struct mode attach_Mode;
extern struct NewWindow nwin_options;
extern int MasterPid, attach_fd;

#ifdef MULTIUSER
extern char *multi;
extern int multiattach, multi_uid, own_uid;
extern int tty_mode, tty_oldmode;
# ifndef USE_SETEUID
static int multipipe[2];
# endif
#endif


static int ContinuePlease;

static sigret_t
AttachSigCont SIGDEFARG
{
  debug("SigCont()\n");
  ContinuePlease = 1;
  SIGRETURN;
}

static int QueryResult;

static sigret_t
QueryResultSuccess SIGDEFARG
{
  QueryResult = 1;
  SIGRETURN;
}

static sigret_t
QueryResultFail SIGDEFARG
{
  QueryResult = 2;
  SIGRETURN;
}

/*
 *  Send message to a screen backend.
 *  returns 1 if we could attach one, or 0 if none.
 *  Understands  MSG_ATTACH, MSG_DETACH, MSG_POW_DETACH
 *               MSG_CONT, MSG_WINCH and nothing else!
 *
 *  if type == MSG_ATTACH and sockets are used, attaches
 *  tty file descriptor.
 */

static int
WriteMessage(s, m)
int s;
struct msg *m;
{
  int r, l = sizeof(*m);
  bool is_socket;

  is_socket = IsSocket(SockPath);
  if (is_socket && m->type == MSG_ATTACH)
    return SendAttachMsg(s, m, attach_fd);

  while(l > 0)
    {
      r = write(s, (char *)m + (sizeof(*m) - l), l);
      if (r == -1 && errno == EINTR)
	continue;
      if (r == -1 || r == 0)
	return -1;
      l -= r;
    }
  return 0;
}


int
Attach(how)
int how;
{
  int n, lasts;
  struct msg m;
  struct stat st;
  char *s;
  bool is_socket;

  debug2("Attach: how=%d, tty=%s\n", how, attach_tty);
#ifdef MULTIUSER
# ifndef USE_SETEUID
  while ((how == MSG_ATTACH || how == MSG_CONT) && multiattach)
    {
      int ret;

      if (pipe(multipipe))
	Panic(errno, "pipe");
      if (chmod(attach_tty, 0666))
	Panic(errno, "chmod %s", attach_tty);
      tty_oldmode = tty_mode;
      eff_uid = -1;	/* make UserContext fork */
      real_uid = multi_uid;
      if ((ret = UserContext()) <= 0)
	{
	  char dummy;
          eff_uid = 0;
	  real_uid = own_uid;
	  if (ret < 0)
	    Panic(errno, "UserContext");
	  close(multipipe[1]);
	  read(multipipe[0], &dummy, 1);
	  if (tty_oldmode >= 0)
	    {
	      chmod(attach_tty, tty_oldmode);
	      tty_oldmode = -1;
	    }
	  ret = UserStatus();
#ifdef LOCK
	  if (ret == SIG_LOCK)
	    LockTerminal();
	  else
#endif
#ifdef SIGTSTP
	  if (ret == SIG_STOP)
	    kill(getpid(), SIGTSTP);
	  else
#endif
	  if (ret == SIG_POWER_BYE)
	    {
	      int ppid;
	      if (setgid(real_gid) || setuid(real_uid))
		Panic(errno, "setuid/gid");
	      if ((ppid = getppid()) > 1)
		Kill(ppid, SIGHUP);
	      exit(0);
	    }
	  else
            exit(ret);
	  dflag = 0;
#ifdef MULTI
	  xflag = 1;
#endif
	  how = MSG_ATTACH;
	  continue;
	}
      close(multipipe[0]);
      eff_uid  = real_uid;
      break;
    }
# else /* USE_SETEUID */
  if ((how == MSG_ATTACH || how == MSG_CONT) && multiattach)
    {
      real_uid = multi_uid;
      eff_uid  = own_uid;
#ifdef HAVE_SETRESUID
      if (setresuid(multi_uid, own_uid, multi_uid))
	Panic(errno, "setresuid");
#else
      xseteuid(multi_uid);
      xseteuid(own_uid);
#endif
      if (chmod(attach_tty, 0666))
	Panic(errno, "chmod %s", attach_tty);
      tty_oldmode = tty_mode;
    }
# endif /* USE_SETEUID */
#endif /* MULTIUSER */

  bzero((char *) &m, sizeof(m));
  m.type = how;
  m.protocol_revision = MSG_REVISION;
  strncpy(m.m_tty, attach_tty_is_in_new_ns ? attach_tty_name_in_ns : attach_tty, sizeof(m.m_tty) - 1);
  m.m_tty[sizeof(m.m_tty) - 1] = 0;

  is_socket = IsSocket(SockPath);
  if (how == MSG_WINCH)
    {
      if ((lasts = MakeClientSocket(0, is_socket)) >= 0)
	{
	  WriteMessage(lasts, &m);
          close(lasts);
	}
      return 0;
    }

  if (how == MSG_CONT)
    {
      if ((lasts = MakeClientSocket(0, is_socket)) < 0)
        {
          Panic(0, "Sorry, cannot contact session \"%s\" again.\r\n",
                 SockName);
        }
    }
  else
    {
      n = FindSocket(&lasts, (int *)0, (int *)0, SockMatch, &is_socket);
      switch (n)
	{
	case 0:
	  if (rflag && (rflag & 1) == 0)
	    return 0;
	  if (quietflag)
	    eexit(10);
	  if (SockMatch && *SockMatch) {
	    Panic(0, "There is no screen to be %sed matching %s.",
		xflag ? "attach" :
		dflag ? "detach" :
                        "resum", SockMatch);
          } else {
            Panic(0, "There is no screen to be %sed.",
	        xflag ? "attach" :
		dflag ? "detach" :
                        "resum");
          }
	  /* NOTREACHED */
	case 1:
	  break;
	default:
	  if (rflag < 3)
	    {
	      if (quietflag)
		eexit(10 + n);
	      Panic(0, "Type \"screen [-d] -r [pid.]tty.host\" to resume one of them.");
	    }
	  /* NOTREACHED */
	}
    }
  /*
   * Go in UserContext. Advantage is, you can kill your attacher
   * when things go wrong. Any disadvantages? jw.
   * Do this before the attach to prevent races!
   */
#ifdef MULTIUSER
  if (!multiattach)
#endif
    {
      if (setuid(real_uid))
        Panic(errno, "setuid");
    }
#if defined(MULTIUSER) && defined(USE_SETEUID)
  else
    {
      /* This call to xsetuid should also set the saved uid */
      xseteuid(real_uid); /* multi_uid, allow backend to send signals */
    }
#endif
  eff_uid = real_uid;
  if (setgid(real_gid))
    Panic(errno, "setgid");
  eff_gid = real_gid;

  debug2("Attach: uid %d euid %d\n", (int)getuid(), (int)geteuid());
  MasterPid = 0;
  for (s = SockName; *s; s++)
    {
      if (*s > '9' || *s < '0')
	break;
      MasterPid = 10 * MasterPid + (*s - '0');
    }
  debug1("Attach decided, it is '%s'\n", SockPath);
  debug1("Attach found MasterPid == %d\n", MasterPid);
  if (stat(SockPath, &st) == -1)
    Panic(errno, "stat %s", SockPath);
  if ((st.st_mode & 0600) != 0600)
    Panic(0, "Socket is in wrong mode (%03o)", (int)st.st_mode);

  /*
   * Change: if -x or -r ignore failing -d
   */
  if ((xflag || rflag) && dflag && (st.st_mode & 0700) == 0600)
    dflag = 0;

  /*
   * Without -x, the mode must match. 
   * With -x the mode is irrelevant unless -d.
   */
  if ((dflag || !xflag) && (st.st_mode & 0700) != (dflag ? 0700 : 0600))
    Panic(0, "That screen is %sdetached.", dflag ? "already " : "not ");
#ifdef REMOTE_DETACH
  if (dflag &&
      (how == MSG_DETACH || how == MSG_POW_DETACH))
    {
      m.m.detach.dpid = getpid();
      strncpy(m.m.detach.duser, LoginName, sizeof(m.m.detach.duser) - 1); 
      m.m.detach.duser[sizeof(m.m.detach.duser) - 1] = 0;
# ifdef POW_DETACH
      if (dflag == 2)
	m.type = MSG_POW_DETACH;
      else
# endif
	m.type = MSG_DETACH;
      /* If there is no password for the session, or the user enters the correct
       * password, then we get a SIGCONT. Otherwise we get a SIG_BYE */
      signal(SIGCONT, AttachSigCont);
      if (WriteMessage(lasts, &m))
	Panic(errno, "WriteMessage");
      close(lasts);
      while (!ContinuePlease)
        pause();	/* wait for SIGCONT */
      signal(SIGCONT, SIG_DFL);
      ContinuePlease = 0;
      if (how != MSG_ATTACH)
	return 0;	/* we detached it. jw. */
      sleep(1);	/* we dont want to overrun our poor backend. jw. */
      if ((lasts = MakeClientSocket(0, is_socket)) == -1)
	Panic(0, "Cannot contact screen again. Sigh.");
      m.type = how;
    }
#endif
  ASSERT(how == MSG_ATTACH || how == MSG_CONT);
  strncpy(m.m.attach.envterm, attach_term, MAXTERMLEN);
  m.m.attach.envterm[MAXTERMLEN] = 0;
  debug1("attach: sending %d bytes... ", (int)sizeof(m));

  strncpy(m.m.attach.auser, LoginName, sizeof(m.m.attach.auser) - 1); 
  m.m.attach.auser[sizeof(m.m.attach.auser) - 1] = 0;
  m.m.attach.esc = DefaultEsc;
  m.m.attach.meta_esc = DefaultMetaEsc;
  strncpy(m.m.attach.preselect, preselect ? preselect : "", sizeof(m.m.attach.preselect) - 1);
  m.m.attach.preselect[sizeof(m.m.attach.preselect) - 1] = 0;
  m.m.attach.apid = getpid();
  m.m.attach.adaptflag = adaptflag;
  m.m.attach.lines = m.m.attach.columns = 0;
  if ((s = getenv("LINES")))
    m.m.attach.lines = atoi(s);
  if ((s = getenv("COLUMNS")))
    m.m.attach.columns = atoi(s);
  m.m.attach.encoding = nwin_options.encoding > 0 ? nwin_options.encoding + 1 : 0;

#ifdef REMOTE_DETACH
#ifdef POW_DETACH
  if (dflag == 2)
    m.m.attach.detachfirst = MSG_POW_DETACH;
  else
#endif
  if (dflag)
    m.m.attach.detachfirst = MSG_DETACH;
  else
#endif
    m.m.attach.detachfirst = MSG_ATTACH;

#ifdef MULTIUSER
  /* setup CONT signal handler to repair the terminal mode */
  if (multi && (how == MSG_ATTACH || how == MSG_CONT))
    signal(SIGCONT, AttachSigCont);
#endif

  if (WriteMessage(lasts, &m))
    Panic(errno, "WriteMessage");
  close(lasts);
  debug1("Attach(%d): sent\n", m.type);
#ifdef MULTIUSER
  if (multi && (how == MSG_ATTACH || how == MSG_CONT))
    {
      while (!ContinuePlease)
        pause();	/* wait for SIGCONT */
      signal(SIGCONT, SIG_DFL);
      ContinuePlease = 0;
# ifndef USE_SETEUID
      close(multipipe[1]);
# else
      xseteuid(own_uid);
      if (tty_oldmode >= 0)
        if (chmod(attach_tty, tty_oldmode))
          Panic(errno, "chmod %s", attach_tty);
      tty_oldmode = -1;
      xseteuid(real_uid);
# endif
    }
#endif
  rflag = 0;
  return 1;
}


static int AttacherPanic = 0;

#ifdef DEBUG
static sigret_t
AttacherChld SIGDEFARG
{
  AttacherPanic = 1;
  SIGRETURN;
}
#endif

static sigret_t 
AttacherSigAlarm SIGDEFARG
{
#ifdef DEBUG
  static int tick_cnt = 0;
  if ((tick_cnt = (tick_cnt + 1) % 4) == 0)
    debug("tick\n");
#endif
  SIGRETURN;
}

/*
 * the frontend's Interrupt handler
 * we forward SIGINT to the poor backend
 */
static sigret_t 
AttacherSigInt SIGDEFARG
{
  signal(SIGINT, AttacherSigInt);
  Kill(MasterPid, SIGINT);
  SIGRETURN;
}

/*
 * Unfortunately this is also the SIGHUP handler, so we have to
 * check if the backend is already detached.
 */

sigret_t
AttacherFinit SIGDEFARG
{
  struct stat statb;
  struct msg m;
  int s;
  bool is_socket;

  debug("AttacherFinit();\n");
  signal(SIGHUP, SIG_IGN);
  /* Check if signal comes from backend */
  if (stat(SockPath, &statb) == 0 && (statb.st_mode & 0777) != 0600)
    {
      debug("Detaching backend!\n");
      bzero((char *) &m, sizeof(m));
      strncpy(m.m_tty, attach_tty_is_in_new_ns ? attach_tty_name_in_ns : attach_tty, sizeof(m.m_tty) - 1);
      m.m_tty[sizeof(m.m_tty) - 1] = 0;
      debug1("attach_tty is %s\n", attach_tty);
      m.m.detach.dpid = getpid();
      m.type = MSG_HANGUP;
      m.protocol_revision = MSG_REVISION;
      is_socket = IsSocket(SockPath);
      if ((s = MakeClientSocket(0, is_socket)) >= 0)
	{
          WriteMessage(s, &m);
	  close(s);
	}
    }
#ifdef MULTIUSER
  if (tty_oldmode >= 0)
    {
      if (setuid(own_uid))
        Panic(errno, "setuid");
      chmod(attach_tty, tty_oldmode);
    }
#endif
  exit(0);
  SIGRETURN;
}

#ifdef POW_DETACH
static sigret_t
AttacherFinitBye SIGDEFARG
{
  int ppid;
  debug("AttacherFintBye()\n");
#if defined(MULTIUSER) && !defined(USE_SETEUID)
  if (multiattach)
    exit(SIG_POWER_BYE);
#endif
  if (setgid(real_gid))
    Panic(errno, "setgid");
#ifdef MULTIUSER
  if (setuid(own_uid))
    Panic(errno, "setuid");
#else
  if (setuid(real_uid))
    Panic(errno, "setuid");
#endif
  /* we don't want to disturb init (even if we were root), eh? jw */
  if ((ppid = getppid()) > 1)
    Kill(ppid, SIGHUP);		/* carefully say good bye. jw. */
  exit(0);
  SIGRETURN;
}
#endif

#if defined(DEBUG) && defined(SIG_NODEBUG)
static sigret_t
AttacherNoDebug SIGDEFARG
{
  debug("AttacherNoDebug()\n");
  signal(SIG_NODEBUG, AttacherNoDebug);
  if (dfp)
    { 
      debug("debug: closing debug file.\n");
      fflush(dfp);
      fclose(dfp);
      dfp = NULL;
    }
  SIGRETURN;
}
#endif /* SIG_NODEBUG */

static int SuspendPlease;

static sigret_t
SigStop SIGDEFARG
{
  debug("SigStop()\n");
  SuspendPlease = 1;
  SIGRETURN;
}

#ifdef LOCK
static int LockPlease;

static sigret_t
DoLock SIGDEFARG
{
# ifdef SYSVSIGS
  signal(SIG_LOCK, DoLock);
# endif
  debug("DoLock()\n");
  LockPlease = 1;
  SIGRETURN;
}
#endif

#if defined(SIGWINCH) && defined(TIOCGWINSZ)
static int SigWinchPlease;

static sigret_t
AttacherWinch SIGDEFARG
{
  debug("AttacherWinch()\n");
  SigWinchPlease = 1;
  SIGRETURN;
}
#endif


/*
 *  Attacher loop - no return
 */

void
Attacher()
{
  signal(SIGHUP, AttacherFinit);
  signal(SIG_BYE, AttacherFinit);
#ifdef POW_DETACH
  signal(SIG_POWER_BYE, AttacherFinitBye);
#endif
#if defined(DEBUG) && defined(SIG_NODEBUG)
  signal(SIG_NODEBUG, AttacherNoDebug);
#endif
#ifdef LOCK
  signal(SIG_LOCK, DoLock);
#endif
  signal(SIGINT, AttacherSigInt);
#ifdef BSDJOBS
  signal(SIG_STOP, SigStop);
#endif
#if defined(SIGWINCH) && defined(TIOCGWINSZ)
  signal(SIGWINCH, AttacherWinch);
#endif
#ifdef DEBUG
  signal(SIGCHLD, AttacherChld);
#endif
  debug("attacher: going for a nap.\n");
  dflag = 0;
#ifdef MULTI
  xflag = 1;
#endif
  for (;;)
    {
      signal(SIGALRM, AttacherSigAlarm);
      alarm(15);
      pause();
      alarm(0);
      if (kill(MasterPid, 0) < 0 && errno != EPERM)
        {
	  debug1("attacher: Panic! MasterPid %d does not exist.\n", MasterPid);
	  AttacherPanic++;
	}
      if (AttacherPanic)
        {
	  fcntl(0, F_SETFL, 0);
	  SetTTY(0, &attach_Mode);
	  printf("\nError: Cannot find master process to attach to!\n");
	  eexit(1);
        }
#ifdef BSDJOBS
      if (SuspendPlease)
	{
	  SuspendPlease = 0;
#if defined(MULTIUSER) && !defined(USE_SETEUID)
	  if (multiattach)
	    exit(SIG_STOP);
#endif
	  signal(SIGTSTP, SIG_DFL);
	  debug("attacher: killing myself SIGTSTP\n");
	  kill(getpid(), SIGTSTP);
	  debug("attacher: continuing from stop\n");
	  signal(SIG_STOP, SigStop);
	  (void) Attach(MSG_CONT);
	}
#endif
#ifdef LOCK
      if (LockPlease)
	{
	  LockPlease = 0;
#if defined(MULTIUSER) && !defined(USE_SETEUID)
	  if (multiattach)
	    exit(SIG_LOCK);
#endif
	  LockTerminal();
# ifdef SYSVSIGS
	  signal(SIG_LOCK, DoLock);
# endif
	  (void) Attach(MSG_CONT);
	}
#endif	/* LOCK */
#if defined(SIGWINCH) && defined(TIOCGWINSZ)
      if (SigWinchPlease)
	{
	  SigWinchPlease = 0;
# ifdef SYSVSIGS
	  signal(SIGWINCH, AttacherWinch);
# endif
	  (void) Attach(MSG_WINCH);
	}
#endif	/* SIGWINCH */
    }
}

#ifdef LOCK

/* ADDED by Rainer Pruy 10/15/87 */
/* POLISHED by mls. 03/10/91 */

static char LockEnd[] = "Welcome back to screen !!\n";

static sigret_t
LockHup SIGDEFARG
{
  int ppid = getppid();
  if (setgid(real_gid))
    Panic(errno, "setgid");
#ifdef MULTIUSER
  if (setuid(own_uid))
    Panic(errno, "setuid");
#else
  if (setuid(real_uid))
    Panic(errno, "setuid");
#endif
  if (ppid > 1)
    Kill(ppid, SIGHUP);
  exit(0);
}

static void
LockTerminal()
{
  char *prg;
  int sig, pid;
  sigret_t (*sigs[NSIG])__P(SIGPROTOARG);

  for (sig = 1; sig < NSIG; sig++)
    sigs[sig] = signal(sig, sig == SIGCHLD ? SIG_DFL : SIG_IGN);
  signal(SIGHUP, LockHup);
  printf("\n");

  prg = getenv("LOCKPRG");
  if (prg && strcmp(prg, "builtin") && !access(prg, X_OK))
    {
      signal(SIGCHLD, SIG_DFL);
      debug1("lockterminal: '%s' seems executable, execl it!\n", prg);
      if ((pid = fork()) == 0)
        {
          /* Child */
          displays = 0;		/* beware of Panic() */
          ServerSocket = -1;
          if (setgid(real_gid))
            Panic(errno, "setgid");
#ifdef MULTIUSER
          if (setuid(own_uid))
            Panic(errno, "setuid");
#else
          if (setuid(real_uid))   /* this should be done already */
            Panic(errno, "setuid");
#endif
          closeallfiles(0);	/* important: /etc/shadow may be open */
          execl(prg, "SCREEN-LOCK", NULL);
          exit(errno);
        }
      if (pid == -1)
        Msg(errno, "Cannot lock terminal - fork failed");
      else
        {
#ifdef BSDWAIT
          union wait wstat;
#else
          int wstat;
#endif
          int wret;

#ifdef hpux
          signal(SIGCHLD, SIG_DFL);
#endif
          errno = 0;
          while (((wret = wait(&wstat)) != pid) ||
	         ((wret == -1) && (errno == EINTR))
	         )
	    errno = 0;
    
          if (errno)
	    {
	      Msg(errno, "Lock");
	      sleep(2);
	    }
	  else if (WTERMSIG(wstat) != 0)
	    {
	      fprintf(stderr, "Lock: %s: Killed by signal: %d%s\n", prg,
		      WTERMSIG(wstat), WIFCORESIG(wstat) ? " (Core dumped)" : "");
	      sleep(2);
	    }
	  else if (WEXITSTATUS(wstat))
	    {
	      debug2("Lock: %s: return code %d\n", prg, WEXITSTATUS(wstat));
	    }
          else
	    printf("%s", LockEnd);
        }
    }
  else
    {
      if (prg)
	{
          debug1("lockterminal: '%s' seems NOT executable, we use our builtin\n", prg);
	}
      else
	{
	  debug("lockterminal: using buitin.\n");
	}
      screen_builtin_lck();
    }
  /* reset signals */
  for (sig = 1; sig < NSIG; sig++)
    {
      if (sigs[sig] != (sigret_t(*)__P(SIGPROTOARG)) -1)
	signal(sig, sigs[sig]);
    }
}				/* LockTerminal */

#ifdef USE_PAM

/*
 *  PAM support by Pablo Averbuj <pablo@averbuj.com>
 */

#include <security/pam_appl.h>

static int PAM_conv __P((int, const struct pam_message **, struct pam_response **, void *));

static int
PAM_conv(num_msg, msg, resp, appdata_ptr)
int num_msg;
const struct pam_message **msg;
struct pam_response **resp;
void *appdata_ptr;
{
  int replies = 0;
  struct pam_response *reply = NULL;

  reply = malloc(sizeof(struct pam_response)*num_msg);
  if (!reply)
    return PAM_CONV_ERR;
  #define COPY_STRING(s) (s) ? strdup(s) : NULL

  for (replies = 0; replies < num_msg; replies++)
    {
      switch (msg[replies]->msg_style)
        {
	case PAM_PROMPT_ECHO_OFF:
	  /* wants password */
	  reply[replies].resp_retcode = PAM_SUCCESS;
	  reply[replies].resp = appdata_ptr ? strdup((char *)appdata_ptr) : 0;
	  break;
	case PAM_TEXT_INFO:
	  /* ignore the informational mesage */
	  /* but first clear out any drek left by malloc */
	  reply[replies].resp = NULL;
	  break;
	case PAM_PROMPT_ECHO_ON:
	  /* user name given to PAM already */
	  /* fall through */
	default:
	  /* unknown or PAM_ERROR_MSG */
	  free(reply);
	  return PAM_CONV_ERR;
	}
    }
  *resp = reply;
  return PAM_SUCCESS;
}

static struct pam_conv PAM_conversation = {
    &PAM_conv,
    NULL
};


#endif

/* -- original copyright by Luigi Cannelloni 1985 (luigi@faui70.UUCP) -- */
static void
screen_builtin_lck()
{
  char fullname[100], *cp1, message[100 + 100];
#ifdef USE_PAM
  pam_handle_t *pamh = 0;
  int pam_error;
  char *tty_name;
#endif
  char *pass = 0, mypass[16 + 1], salt[3];
  int using_pam = 1;

#ifdef USE_PAM
  if (!ppp->pw_uid)
    {
#endif
  using_pam = 0;
  pass = ppp->pw_passwd;
  if (pass == 0 || *pass == 0)
    {
      if ((pass = getpass("Key:   ")))
        {
          strncpy(mypass, pass, sizeof(mypass) - 1);
          mypass[sizeof(mypass) - 1] = 0;
          if (*mypass == 0)
            return;
          if ((pass = getpass("Again: ")))
            {
              if (strcmp(mypass, pass))
                {
                  fprintf(stderr, "Passwords don't match.\007\n");
                  sleep(2);
                  return;
                }
            }
        }
      if (pass == 0)
        {
          fprintf(stderr, "Getpass error.\007\n");
          sleep(2);
          return;
        }

      salt[0] = 'A' + (int)(time(0) % 26);
      salt[1] = 'A' + (int)((time(0) >> 6) % 26);
      salt[2] = 0;
      pass = crypt(mypass, salt);
      if (!pass)
	{
          fprintf(stderr, "crypt() error.\007\n");
          sleep(2);
          return;
	}
      pass = ppp->pw_passwd = SaveStr(pass);
    }
#ifdef USE_PAM
    }
#endif

  debug("screen_builtin_lck looking in gcos field\n");
  strncpy(fullname, ppp->pw_gecos, sizeof(fullname) - 9);
  fullname[sizeof(fullname) - 9] = 0;

  if ((cp1 = index(fullname, ',')) != NULL)
    *cp1 = '\0';
  if ((cp1 = index(fullname, '&')) != NULL)
    {
      strncpy(cp1, ppp->pw_name, 8);
      cp1[8] = 0;
      if (*cp1 >= 'a' && *cp1 <= 'z')
	*cp1 -= 'a' - 'A';
    }

  sprintf(message, "Screen used by %s%s<%s> on %s.\nPassword:\007",
          fullname, fullname[0] ? " " : "", ppp->pw_name, HostName);

  /* loop here to wait for correct password */
  for (;;)
    {
      debug("screen_builtin_lck awaiting password\n");
      errno = 0;
      if ((cp1 = getpass(message)) == NULL)
        {
          AttacherFinit(SIGARG);
          /* NOTREACHED */
        }
      if (using_pam)
        {
#ifdef USE_PAM
      PAM_conversation.appdata_ptr = cp1;
      pam_error = pam_start("screen", ppp->pw_name, &PAM_conversation, &pamh);
      if (pam_error != PAM_SUCCESS)
	AttacherFinit(SIGARG);		/* goodbye */

      if (strncmp(attach_tty, "/dev/", 5) == 0)
	tty_name = attach_tty + 5;
      else
	tty_name = attach_tty;
      pam_error = pam_set_item(pamh, PAM_TTY, tty_name);
      if (pam_error != PAM_SUCCESS)
	AttacherFinit(SIGARG);		/* goodbye */

      pam_error = pam_authenticate(pamh, 0);
      pam_end(pamh, pam_error);
      PAM_conversation.appdata_ptr = 0;
      if (pam_error == PAM_SUCCESS)
	break;
#endif
        }
      else
	{
          char *buf = crypt(cp1, pass);
          if (buf && !strncmp(buf, pass, strlen(pass)))
	    break;
	}
      debug("screen_builtin_lck: NO!!!!!\n");
      bzero(cp1, strlen(cp1));
    }
  bzero(cp1, strlen(cp1));
  debug("password ok.\n");
}

#endif	/* LOCK */


void
SendCmdMessage(sty, match, av, query)
char *sty;
char *match;
char **av;
int query;
{
  int i, s;
  struct msg m;
  char *p;
  int len, n;
  bool is_socket;

  if (sty == 0)
    {
      i = FindSocket(&s, (int *)0, (int *)0, match, &is_socket);
      if (i == 0)
	Panic(0, "No screen session found.");
      if (i != 1)
	Panic(0, "Use -S to specify a session.");
    }
  else
    {
#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);
    }
  bzero((char *)&m, sizeof(m));
  m.type = query ? MSG_QUERY : MSG_COMMAND;
  if (attach_tty)
    {
      strncpy(m.m_tty, attach_tty_is_in_new_ns ? attach_tty_name_in_ns : attach_tty, sizeof(m.m_tty) - 1);
      m.m_tty[sizeof(m.m_tty) - 1] = 0;
    }
  p = m.m.command.cmd;
  n = 0;
  for (; *av && n < MAXARGS - 1; ++av, ++n)
    {
      len = strlen(*av) + 1;
      if (p + len >= m.m.command.cmd + sizeof(m.m.command.cmd) - 1)
	break;
      strcpy(p, *av);
      p += len;
    }
  *p = 0;
  m.m.command.nargs = n;
  strncpy(m.m.attach.auser, LoginName, sizeof(m.m.attach.auser) - 1);
  m.m.command.auser[sizeof(m.m.command.auser) - 1] = 0;
  m.protocol_revision = MSG_REVISION;
  strncpy(m.m.command.preselect, preselect ? preselect : "", sizeof(m.m.command.preselect) - 1);
  m.m.command.preselect[sizeof(m.m.command.preselect) - 1] = 0;
  m.m.command.apid = getpid();
  debug1("SendCommandMsg writing '%s'\n", m.m.command.cmd);
  if (query)
    {
      /* Create a server socket so we can get back the result */
      char *sp = SockPath + strlen(SockPath);
      char query[] = "-queryX";
      char c;
      int r = -1;
      for (c = 'A'; c <= 'Z'; c++)
	{
	  query[6] = c;
	  strcpy(sp, query);	/* XXX: strncpy? */
	  if ((r = MakeServerSocket(is_socket)) >= 0)
	    break;
	}
      if (r < 0)
	{
	  for (c = '0'; c <= '9'; c++)
	    {
	      query[6] = c;
	      strcpy(sp, query);
	      if ((r = MakeServerSocket(is_socket)) >= 0)
		break;
	    }
	}

      if (r < 0)
	Panic(0, "Could not create a listening socket to read the results.");

      strncpy(m.m.command.writeback, SockPath, sizeof(m.m.command.writeback) - 1);
      m.m.command.writeback[sizeof(m.m.command.writeback) - 1] = '\0';

      /* Send the message, then wait for a response */
      signal(SIGCONT, QueryResultSuccess);
      signal(SIG_BYE, QueryResultFail);
      if (WriteMessage(s, &m))
	Msg(errno, "write");
      close(s);
      while (!QueryResult)
	pause();
      signal(SIGCONT, SIG_DFL);
      signal(SIG_BYE, SIG_DFL);

      /* Read the result and spit it out to stdout */
      ReceiveRaw(r);
      unlink(SockPath);
      if (QueryResult == 2)	/* An error happened */
	exit(1);
    }
  else
    {
      if (WriteMessage(s, &m))
	Msg(errno, "write");
      close(s);
    }
}

