/* 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 <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pwd.h>

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

#include "config.h" 
#include "screen.h"
#include "extern.h"

extern struct display *display, *displays;
extern struct win *fore;
extern struct layer *flayer;
extern int ServerSocket;
extern int real_uid, eff_uid;
extern int real_gid, eff_gid;
extern char *extra_incap, *extra_outcap;
extern char *home, *RcFileName;
extern char SockPath[], *SockName;
#ifdef COPY_PASTE
extern char *BufferFile;
#endif
extern int hardcopy_append;
extern char *hardcopydir;

static char *CatExtra __P((char *, char *));
static char *findrcfile __P((char *));


char *rc_name = "";
int rc_recursion = 0;

static char * CatExtra(register char *str1, register char *str2) {
  register char *cp;
  register int len1, len2, add_colon;

  len1 = strlen(str1);
  if (len1 == 0)
    return str2;
  add_colon = (str1[len1 - 1] != ':');
  if (str2) {
    len2 = strlen(str2);
    if ((cp = realloc(str2, (unsigned) len1 + len2 + add_colon + 1)) == NULL)
      Panic(0, "%s", strnomem);
    bcopy(cp, cp + len1 + add_colon, len2 + 1);
  }
  else {
    if ((cp = malloc((unsigned) len1 + add_colon + 1)) == NULL)
      Panic(0, "%s", strnomem);
    cp[len1 + add_colon] = '\0';
  }
  bcopy(str1, cp, len1);
  if (add_colon)
    cp[len1] = ':';
  return cp;
}

static char *findrcfile(char *rcfile) {
  char buf[256];
  char *p;

  /* Tilde prefix support courtesy <hesso@pool.math.tu-berlin.de>,
   * taken from a Debian patch. */
  if (rcfile && *rcfile == '~') {
    static char rcfilename_tilde_exp[MAXPATHLEN+1];
    char *slash_position = strchr(rcfile, '/');

    if (slash_position == rcfile+1) {
      char *home = getenv("HOME");
      if (!home) {
        Msg(0, "%s: source: tilde expansion failed", rc_name);
        return NULL;
      }
      snprintf(rcfilename_tilde_exp, MAXPATHLEN, "%s/%s", home, rcfile+2);
    }
    else if (slash_position) {
      struct passwd *p;
      *slash_position = 0;
      p = getpwnam(rcfile+1);
      if (!p){
        Msg(0, "%s: source: tilde expansion failed for user %s", rc_name, rcfile+1);
        return NULL;
      }
      snprintf(rcfilename_tilde_exp, MAXPATHLEN, "%s/%s", p->pw_dir, slash_position+1);
    }
    else {
      Msg(0, "%s: source: illegal tilde expression.", rc_name);
      return NULL;
    }
    rcfile = rcfilename_tilde_exp;
  }

  if (rcfile) {
    char *rcend = rindex(rc_name, '/');
    if (*rcfile != '/' && rcend && (rcend - rc_name) + strlen(rcfile) + 2 < sizeof(buf)) {
      strncpy(buf, rc_name, rcend - rc_name + 1);
      strcpy(buf + (rcend - rc_name) + 1, rcfile);
      if (access(buf, R_OK) == 0)
        return SaveStr(buf);
    }
    debug1("findrcfile: you specified '%s'\n", rcfile);
    return SaveStr(rcfile);
  }

  debug("findrcfile: you specified nothing...\n");
  if ((p = getenv("SCREENRC")) != NULL && *p != '\0') {
    debug1("  $SCREENRC has: '%s'\n", p);
    return SaveStr(p);
  }
  else {
    debug("  ...nothing in $SCREENRC, defaulting $HOME/.screenrc\n");
    if (strlen(home) > sizeof(buf) - 12)
      Panic(0, "Rc: home too large");
    sprintf(buf, "%s/.screenrc", home);
    return SaveStr(buf);
  }
}

/*
 * this will be called twice:
 * 1) rcfilename = "/etc/screenrc"
 * 2) rcfilename = RcFileName
 */
int StartRc(char *rcfilename, int nopanic) {
  register int argc, len;
  register char *p, *cp;
  char buf[2048];
  char *args[MAXARGS];
  int argl[MAXARGS];
  FILE *fp;
  char *oldrc_name = rc_name;

  /* always fix termcap/info capabilities */
  extra_incap = CatExtra("TF", extra_incap);

  /* Special settings for vt100 and others */
  if (display && (!strncmp(D_termname, "vt", 2) || !strncmp(D_termname, "xterm", 5)))
    extra_incap = CatExtra("xn:f0=\033Op:f1=\033Oq:f2=\033Or:f3=\033Os:f4=\033Ot:f5=\033Ou:f6=\033Ov:f7=\033Ow:f8=\033Ox:f9=\033Oy:f.=\033On:f,=\033Ol:fe=\033OM:f+=\033Ok:f-=\033Om:f*=\033Oj:f/=\033Oo:fq=\033OX", extra_incap);

  rc_name = findrcfile(rcfilename);
  if (rc_name == NULL || (fp = secfopen(rc_name, "r")) == NULL) {
    const char *rc_nonnull = rc_name ? rc_name : rcfilename;
    if (!rc_recursion && RcFileName && !strcmp(RcFileName, rc_nonnull)) {
      /*
       * User explicitly gave us that name,
       * this is the only case, where we get angry, if we can't read
       * the file.
       */
      debug3("StartRc: '%s','%s', '%s'\n", RcFileName, rc_name ? rc_name : "(null)", rcfilename);
      if (!nopanic) Panic(0, "Unable to open \"%s\".", rc_nonnull);
      /* possibly NOTREACHED */
    }

    debug1("StartRc: '%s' no good. ignored\n", rc_nonnull);
    if (rc_name)
      Free(rc_name);
    rc_name = oldrc_name;
    return 1;
  }
  while (fgets(buf, sizeof buf, fp) != NULL) {
    if ((p = rindex(buf, '\n')) != NULL)
      *p = '\0';

    if ((argc = Parse(buf, sizeof buf, args, argl)) == 0)
      continue;

    if (strcmp(args[0], "echo") == 0) {
      if (!display)
        continue;
      if (argc < 2 || (argc == 3 && strcmp(args[1], "-n")) || argc > 3) {
        Msg(0, "%s: 'echo [-n] \"string\"' expected.", rc_name);
        continue;
      }
      AddStr(args[argc - 1]);
      if (argc != 3) {
        AddStr("\r\n");
        Flush(0);
      }
    }

    else if (strcmp(args[0], "sleep") == 0) {
      if (!display)
        continue;
      debug("sleeeeeeep\n");
      if (argc != 2) {
        Msg(0, "%s: sleep: one numeric argument expected.", rc_name);
        continue;
      }
      DisplaySleep1000(1000 * atoi(args[1]), 1);
    }
#ifdef TERMINFO
    else if (!strcmp(args[0], "termcapinfo") || !strcmp(args[0], "terminfo")) {
#else
    else if (!strcmp(args[0], "termcapinfo") || !strcmp(args[0], "termcap")) {
#endif
      if (!display)
        continue;
      if (argc < 3 || argc > 4) {
        Msg(0, "%s: %s: incorrect number of arguments.", rc_name, args[0]);
        continue;
      }

      for (p = args[1]; p && *p; p = cp) {
        if ((cp = index(p, '|')) != 0)
          *cp++ = '\0';
        len = strlen(p);
        if (p[len - 1] == '*') {
          if (!(len - 1) || !strncmp(p, D_termname, len - 1))
            break;
        }
        else if (!strcmp(p, D_termname))
          break;
      }
      if (!(p && *p))
        continue;
      extra_incap = CatExtra(args[2], extra_incap);
      if (argc == 4)
        extra_outcap = CatExtra(args[3], extra_outcap);
    }
    else if (!strcmp(args[0], "source")) {
      if (rc_recursion <= 10) {
        rc_recursion++;
        (void)StartRc(args[1], 0);
        rc_recursion--;
      }
    }
  }
  fclose(fp);
  Free(rc_name);
  rc_name = oldrc_name;
  return 0;
}

void FinishRc(char *rcfilename) {
  char buf[2048];
  FILE *fp;
  char *oldrc_name = rc_name;

  rc_name = findrcfile(rcfilename);

  if (rc_name == NULL || (fp = secfopen(rc_name, "r")) == NULL) {
    const char *rc_nonnull = rc_name ? rc_name : rcfilename;
    if (rc_recursion)
      Msg(errno, "%s: source %s", oldrc_name, rc_nonnull);
    else if (RcFileName && !strcmp(RcFileName, rc_nonnull)) {
      /*
       * User explicitly gave us that name,
       * this is the only case, where we get angry, if we can't read
       * the file.
       */
       debug3("FinishRc:'%s','%s','%s'\n", RcFileName, rc_name ? rc_name : "(null)", rcfilename);
       Panic(0, "Unable to open \"%s\".", rc_nonnull);
       /* NOTREACHED */
    }
    debug1("FinishRc: '%s' no good. ignored\n", rc_nonnull);
    if (rc_name)
      Free(rc_name);
    rc_name = oldrc_name;
    return;
  }

  debug("finishrc is going...\n");
  while (fgets(buf, sizeof buf, fp) != NULL)
    RcLine(buf, sizeof buf);
  (void) fclose(fp);
  Free(rc_name);
  rc_name = oldrc_name;
}

void do_source(char *rcfilename) {
  if (rc_recursion > 10) {
    Msg(0, "%s: source: recursion limit reached", rc_name);
    return;
  }
  rc_recursion++;
  FinishRc(rcfilename);
  rc_recursion--;
}


/*
 * Running a Command Line in the environment determined by the display.
 * The fore window is taken from the display as well as the user.
 * This is bad when we run detached.
 */
void RcLine(char *ubuf, int ubufl) {
  char *args[MAXARGS];
  int argl[MAXARGS];
#ifdef MULTIUSER
  extern struct acluser *EffectiveAclUser;      /* acl.c */
  extern struct acluser *users;                 /* acl.c */
#endif

  if (display) {
    fore = D_fore;
    flayer = D_forecv->c_layer;
  }
  else
    flayer = fore ? fore->w_savelayer : 0;
  if (Parse(ubuf, ubufl, args, argl) <= 0)
    return;

#ifdef MULTIUSER
  if (!display) {
      /* the session owner does it, when there is no display here */
      EffectiveAclUser = users;        
      debug("RcLine: WARNING, no display no user! Session owner executes command\n");
  }
#endif

  DoCommand(args, argl);

#ifdef MULTIUSER
  EffectiveAclUser = 0;
#endif
}

/*  needs display for copybuffer access and termcap dumping  */
void WriteFile(struct acluser *user, char *fn, int dump) {
  /* dump==0:	create .termcap,
   * dump==1:	hardcopy,
   * #ifdef COPY_PASTE
   * dump==2:	BUFFERFILE
   * #endif COPY_PASTE 
   * dump==1:	scrollback,
   */
  register int i, j, k;
  register char *p;
  register FILE *f;
  char fnbuf[1024];
  char *mode = "w";

#ifdef COPY_PASTE
  int public = 0;
# ifdef HAVE_LSTAT
  struct stat stb, stb2;
  int fd, exists = 0;
# endif
#endif

  switch (dump) {
    case DUMP_TERMCAP:
      if (fn == 0) {
        i = SockName - SockPath;
        if (i > (int)sizeof(fnbuf) - 9)
          i = 0;
        strncpy(fnbuf, SockPath, i);
        strcpy(fnbuf + i, ".termcap");
        fn = fnbuf;
      }
      break;

    case DUMP_HARDCOPY:
    case DUMP_SCROLLBACK:
      if (fn == 0) {
        if (fore == 0)
          return;
        if (hardcopydir && *hardcopydir && strlen(hardcopydir) < sizeof(fnbuf) - 21)
          sprintf(fnbuf, "%s/hardcopy.%d", hardcopydir, fore->w_number);
        else
          sprintf(fnbuf, "hardcopy.%d", fore->w_number);
        fn = fnbuf;
      }
      if (hardcopy_append && !access(fn, W_OK))
        mode = "a";
      break;

#ifdef COPY_PASTE
    case DUMP_EXCHANGE:
      if (fn == 0) {
        strncpy(fnbuf, BufferFile, sizeof(fnbuf) - 1);
        fnbuf[sizeof(fnbuf) - 1] = 0;
        fn = fnbuf;
      }
      public = !strcmp(fn, DEFAULT_BUFFERFILE);
# ifdef HAVE_LSTAT
      exists = !lstat(fn, &stb);
      if (public && exists && (S_ISLNK(stb.st_mode) || stb.st_nlink > 1)) {
        Msg(0, "No write to links, please.");
        return;
      }
# endif
      break;
#endif
  }

  debug2("WriteFile(%d) %s\n", dump, fn);
  if (UserContext() > 0) {
    debug("Writefile: usercontext\n");
#ifdef COPY_PASTE
    if (dump == DUMP_EXCHANGE && public) {
# ifdef HAVE_LSTAT
      if (exists) {
        if ((fd = open(fn, O_WRONLY, 0666)) >= 0) {
          if (fstat(fd, &stb2) == 0 && stb.st_dev == stb2.st_dev && stb.st_ino == stb2.st_ino)
            ftruncate(fd, 0);
          else {
            close(fd);
            fd = -1;
          }
        }
      }
      else
        fd = open(fn, O_WRONLY|O_CREAT|O_EXCL, 0666);
      f = fd >= 0 ? fdopen(fd, mode) : 0;
# else
      f = fopen(fn, mode);
# endif
    }
    else
#endif /* COPY_PASTE */
      f = fopen(fn, mode);
        if (f == NULL) {
          debug2("WriteFile: fopen(%s,\"%s\") failed\n", fn, mode);
          UserReturn(0);
        }
        else {
          switch (dump) {
            case DUMP_HARDCOPY:
            case DUMP_SCROLLBACK:
              if (!fore)
                break;
              if (*mode == 'a') {
                putc('>', f);
                for (j = fore->w_width - 2; j > 0; j--)
                  putc('=', f);
                  fputs("<\n", f);
              }
              if (dump == DUMP_SCROLLBACK) {
#ifdef COPY_PASTE
                for (i = fore->w_histheight - fore->w_scrollback_height; i < fore->w_histheight; i++) {
                  p = (char *)(WIN(i)->image);
                    for (k = fore->w_width - 1; k >= 0 && p[k] == ' '; k--)
                      ;
                    for (j = 0; j <= k; j++)
                      putc(p[j], f);
                      putc('\n', f);
                }
#endif
              }
              for (i = 0; i < fore->w_height; i++) {
                p = (char *)fore->w_mlines[i].image;
                for (k = fore->w_width - 1; k >= 0 && p[k] == ' '; k--)
                  ;
                for (j = 0; j <= k; j++)
                  putc(p[j], f);
                  putc('\n', f);
                }
                break;

            case DUMP_TERMCAP:
              DumpTermcap(fore->w_aflag, f);
	      break;

#ifdef COPY_PASTE
            case DUMP_EXCHANGE:
              p = user->u_plop.buf;
              for (i = user->u_plop.len; i-- > 0; p++)
                if (*p == '\r' && (i == 0 || p[1] != '\n'))
                  putc('\n', f);
                else
                  putc(*p, f);
              break;
#endif
        }
        (void) fclose(f);
        UserReturn(1);
      }
    }
  if (UserStatus() <= 0)
    Msg(0, "Cannot open \"%s\"", fn);
  else if (display && !*rc_name) {
    switch (dump) {
      case DUMP_TERMCAP:
        Msg(0, "Termcap entry written to \"%s\".", fn);
        break;
      case DUMP_HARDCOPY:
      case DUMP_SCROLLBACK:
        Msg(0, "Screen image %s to \"%s\".", (*mode == 'a') ? "appended" : "written", fn);
        break;
#ifdef COPY_PASTE
      case DUMP_EXCHANGE:
        Msg(0, "Copybuffer written to \"%s\".", fn);
#endif
    }
  }
}

#ifdef COPY_PASTE

/*
 * returns an allocated buffer which holds a copy of the file named fn.
 * lenp (if nonzero) points to a location, where the buffer size should be 
 * stored.
 */
char *ReadFile(char *fn, int *lenp) {
  int i, l, size;
  char c, *bp, *buf;
  struct stat stb;

  ASSERT(lenp);
  debug1("ReadFile(%s)\n", fn);

  if ((i = secopen(fn, O_RDONLY, 0)) < 0) {
    Msg(errno, "no %s -- no slurp", fn);
    return NULL;
  }

  if (fstat(i, &stb)) {
    Msg(errno, "no good %s -- no slurp", fn);
    close(i);
    return NULL;
  }
  size = stb.st_size;

  if ((buf = malloc(size)) == NULL) {
    close(i);
    Msg(0, "%s", strnomem);
    return NULL;
  }
  errno = 0;

  if ((l = read(i, buf, size)) != size) {
    if (l < 0)
      l = 0;
    Msg(errno, "Got only %d bytes from %s", l, fn);
  }
  else {
    if (read(i, &c, 1) > 0)
      Msg(0, "Slurped only %d characters (of %d) into buffer - try again", l, size);
    else
      Msg(0, "Slurped %d characters into buffer", l);
  }
  close(i);
  *lenp = l;
  for (bp = buf; l-- > 0; bp++)
    if (*bp == '\n' && (bp == buf || bp[-1] != '\r'))
      *bp = '\r';
  return buf;
}

void KillBuffers() {
  if (UserContext() > 0)
    UserReturn(unlink(BufferFile) ? errno : 0);
  errno = UserStatus();
  Msg(errno, "%s %sremoved", BufferFile, errno ? "not " : "");
}
#endif	/* COPY_PASTE */


/* (Almost) secure open and fopen...  */

FILE *secfopen(char *name, char *mode) {
  FILE *fi;
#ifndef USE_SETEUID
  int flags, fd;
#endif

  debug2("secfopen(%s, %s)\n", name, mode);
#ifdef USE_SETEUID
  xseteuid(real_uid);
  xsetegid(real_gid);
  fi = fopen(name, mode);
  xseteuid(eff_uid);
  xsetegid(eff_gid);
  return fi;

#else
  if (eff_uid == real_uid)
    return fopen(name, mode);
  if (mode[0] && mode[1] == '+')
    flags = O_RDWR;
  else
    flags = (mode[0] == 'r') ? O_RDONLY : O_WRONLY;
  if (mode[0] == 'w')
    flags |= O_CREAT | O_TRUNC;
  else if (mode[0] == 'a')
    flags |= O_CREAT | O_APPEND;
  else if (mode[0] != 'r') {
    errno = EINVAL;
    return 0;
  }
  if ((fd = secopen(name, flags, 0666)) < 0)
    return 0;
  if ((fi = fdopen(fd, mode)) == 0) {
    close(fd);
    return 0;
  }
  return fi;
#endif
}


int secopen(char *name, int flags, int mode) {
  int fd;
#ifndef USE_SETEUID
  int q;
  struct stat stb;
#endif

  debug3("secopen(%s, 0x%x, 0%03o)\n", name, flags, mode);
#ifdef USE_SETEUID
  xseteuid(real_uid);
  xsetegid(real_gid);
  fd = open(name, flags, mode);
  xseteuid(eff_uid);
  xsetegid(eff_gid);
  return fd;
#else
  if (eff_uid == real_uid)
    return open(name, flags, mode);
  /* Truncation/creation is done in UserContext */
  if ((flags & O_TRUNC) || ((flags & O_CREAT) && access(name, F_OK))) {
    if (UserContext() > 0) {
      if ((fd = open(name, flags, mode)) >= 0) {
        close(fd);
        UserReturn(0);
      }
      if (errno == 0)
        errno = EACCES;
      UserReturn(errno);
    }
    if ((q = UserStatus())) {
      if (q > 0)
        errno = q;
      return -1;
    }
  }
  if (access(name, F_OK))
    return -1;
  if ((fd = open(name, flags & ~(O_TRUNC | O_CREAT), 0)) < 0)
    return -1;
  debug("open successful\n");
  if (fstat(fd, &stb)) {
    close(fd);
    return -1;
  }
  debug("fstat successful\n");
  if (stb.st_uid != real_uid) {
    switch (flags & (O_RDONLY | O_WRONLY | O_RDWR)) {
      case O_RDONLY:
        q = 0004;
        break;
      case O_WRONLY:
        q = 0002;
        break;
      default:
        q = 0006;
        break;
    }
    if ((stb.st_mode & q) != q) {
      debug1("secopen: permission denied (%03o)\n", stb.st_mode & 07777);
      close(fd);
      errno = EACCES;
      return -1;
    }
  }
  debug1("secopen ok - returning %d\n", fd);
  return fd;
#endif
}


int printpipe(struct win *p, char *cmd) {
  int pi[2];
  if (pipe(pi)) {
    WMsg(p, errno, "printing pipe");
    return -1;
  }
  switch (fork()) {
    case -1:
      WMsg(p, errno, "printing fork");
      return -1;
    case 0:
      display = p->w_pdisplay;
      displays = 0;
      ServerSocket = -1;
#ifdef DEBUG
      if (dfp && dfp != stderr)
        fclose(dfp);
#endif
      close(0);
      dup(pi[0]);
      closeallfiles(0);
      if (setgid(real_gid) || setuid(real_uid))
        Panic(errno, "printpipe setuid");
      eff_uid = real_uid;
      eff_gid = real_gid;

#ifdef SIGPIPE
      signal(SIGPIPE, SIG_DFL);
#endif
      execl("/bin/sh", "sh", "-c", cmd, (char *)0);
      Panic(errno, "/bin/sh");
    default:
      break;
  }
  close(pi[0]);
  return pi[1];
}

int readpipe(char **cmdv) {
  int pi[2];

  if (pipe(pi)) {
    Msg(errno, "pipe");
    return -1;
  }

  switch (fork()) {
  case -1:
    Msg(errno, "fork");
    return -1;
  case 0:
    displays = 0;
    ServerSocket = -1;
#ifdef DEBUG
    if (dfp && dfp != stderr)
      fclose(dfp);
#endif
    close(1);
    if (dup(pi[1]) != 1) {
      close(pi[1]);
      Panic(0, "dup");
    }
    closeallfiles(1);

    if (setgid(real_gid) || setuid(real_uid)) {
      close(1);
      Panic(errno, "setuid/setgid");
    }
    eff_uid = real_uid;
    eff_gid = real_gid;
#ifdef SIGPIPE
    signal(SIGPIPE, SIG_DFL);
#endif
    execvp(*cmdv, cmdv);
    close(1);
    Panic(errno, "%s", *cmdv);
  default:
    break;
  }
  close(pi[1]);
  return pi[0];
}
