/*
 * Copyright (C) 2006 Martin Koegler
 * Copyright (C) 2010 TigerVNC Team
 * Copyright (C) 2014-2023 m-privacy GmbH
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <security/pam_appl.h>

#include <rfb/pam.h>

#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <ctype.h>
#include <stdio.h>

#if !defined(WIN32) && !defined(WIN64)
#include <syslog.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/stat.h>
#if !defined(__APPLE__)
#include <sys/capability.h>
#endif
#endif

#define mplog_error(...) syslog(LOG_ERR, __VA_ARGS__);
#define mplog_info(...) syslog(LOG_INFO, __VA_ARGS__);
// #define mplog_debug(...) syslog(LOG_DEBUG, __VA_ARGS__);
#define mplog_debug(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n");
#define mplog_verbose(...) mplog_debug(__VA_ARGS__);

#include <activedirectory-dev.h>

typedef struct
{
  const char *username;
  const char *password;
} AuthData;

#if defined(__sun)
static int pam_callback(int count, struct pam_message **in,
                        struct pam_response **out, void *ptr)
#else
static int pam_callback(int count, const struct pam_message **in,
                        struct pam_response **out, void *ptr)
#endif
{
  int i;
  AuthData *auth = (AuthData *) ptr;
  struct pam_response *resp =
    (struct pam_response *) malloc (sizeof (struct pam_response) * count);

  if (!resp && count)
    return PAM_CONV_ERR;

  for (i = 0; i < count; i++) {
    resp[i].resp_retcode = PAM_SUCCESS;
    switch (in[i]->msg_style) {
    case PAM_TEXT_INFO:
    case PAM_ERROR_MSG:
      resp[i].resp = 0;
      break;
    case PAM_PROMPT_ECHO_ON:	/* Send Username */
      resp[i].resp = strdup(auth->username);
      break;
    case PAM_PROMPT_ECHO_OFF:	/* Send Password */
      resp[i].resp = strdup(auth->password);
      break;
    default:
      free(resp);
      return PAM_CONV_ERR;
    }
  }

  *out = resp;
  return PAM_SUCCESS;
}


int do_pam_auth(const char *service, const char *username, const char *password)
{
  int ret;

  char * pamusername;
  char importname[256];

  const char * numeric_scanner = username;
  while (*numeric_scanner >= '0' && *numeric_scanner <= '9')
    ++numeric_scanner;
  if (!*numeric_scanner) {
    size_t len = strlen(username);
    pamusername = (char *)malloc(len + 2);
    strcpy(pamusername, username);
    pamusername[len] = '#';
    pamusername[len+1] = '\0';
  } else
    pamusername = strdup(username);

  if(!access("/usr/local/bin/importsingleuser", X_OK)) {
    snprintf(importname, 255, "/usr/local/bin/importsingleuser \"%s\"", pamusername);
    importname[255] = 0;
    system(importname);
  }

  AuthData auth = { username, password };
  struct pam_conv conv = {
    pam_callback,
    &auth
  };
  pam_handle_t *h = 0;
  if (!*numeric_scanner) {
    ret = pam_start(service, username, &conv, &h);
    if (ret == PAM_SUCCESS) {
      ret = pam_authenticate(h, PAM_SILENT);
      pam_set_item(h, PAM_USER, pamusername);
      if ((ret != PAM_SUCCESS) && getpwnam(pamusername)) {
        ret = pam_authenticate(h, PAM_SILENT);
      }
    }
  } else {
    ret = pam_start(service, pamusername, &conv, &h);
    if (ret == PAM_SUCCESS)
      ret = pam_authenticate(h, PAM_SILENT);
  }

  /* m-privacy: convert to lowercase and prepare account */
  char * tmpusername = strdup(username);
  if (ret == PAM_SUCCESS) {
    char * tmp;
    int systemerr;

    if(!access("/etc/cu/usertolower", F_OK)) {
      tmp = tmpusername;

      while(*tmp) {
        *tmp = tolower(*tmp);
        tmp++;
      }
      pam_set_item(h, PAM_USER, tmpusername);
    }
    tmp = tmpusername;
    while(*tmp >= '0' && *tmp <= '9') {
      tmp++;
    }
    if (!*tmp) { /* numeric */
      size_t len = strlen(tmpusername);

      tmp = tmpusername;
      tmpusername = (char *) malloc(len + 2);
      strcpy(tmpusername, tmp);
      tmpusername[len] = '#';
      tmpusername[len+1] = '\0';
      free(tmp);
      pam_set_item(h, PAM_USER, tmpusername);
    }

    openlog("Xtightgatevnc", LOG_PID, LOG_AUTH); // For the calls in get_tg_groups_and_set_env_if_necessary
    get_tg_groups_and_set_env_if_necessary(username); // Distinguishes between authentication methods and only reads groups from AD or LDAP server if the config settings say so

    setenv("USER", tmpusername, 1);
    if ((systemerr = system("/usr/local/bin/prepareuser &>/dev/null"))) {
      free(tmpusername);
#if !defined(WIN32) && !defined(WIN64)
      systemerr = WEXITSTATUS(systemerr);
      openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
      syslog(LOG_DEBUG, "do_pam_auth(): prepareuser failed with error %i: %s", systemerr, prepareuser_error(systemerr));
#endif
      return 0;
    }
  } else {
      fprintf(stderr, "do_pam_auth(): pam_start() or pam_authenticate() failed with error %s\n", pam_strerror(h, ret));
#if !defined(WIN32) && !defined(WIN64)
      openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
      syslog(LOG_DEBUG, "do_pam_auth(): pam_start() or pam_authenticate() failed with error %s",
        pam_strerror(h, ret));
#endif
  }

  if (ret == PAM_SUCCESS) {
    ret = pam_acct_mgmt(h, 0);
    if (ret != PAM_SUCCESS) {
      fprintf(stderr, "do_pam_auth(): pam_acct_mgmt() failed with error %s\n", pam_strerror(h, ret));
#if !defined(WIN32) && !defined(WIN64)
      openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
      syslog(LOG_DEBUG, "do_pam_auth(): pam_acct_mgmt() failed with error %s",
        pam_strerror(h, ret));
#endif
    }
  }
  pam_end(h, ret);

  free(pamusername);

  /* setuid to user and setup environment */
  if (ret == PAM_SUCCESS) {
    struct passwd * pw;

    pw = getpwnam(tmpusername);
    if (pw) {
      char * UserProgram = getenv("VNCUserProgram");

      /* if we are not called from inetd, we should not setup environment
       * or subsequent connections will probably fail */
      if (getenv("inetd") == NULL) {
        free(tmpusername);
        return 1;
      }

      if (setuid(getuid())) {
#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
        cap_t proccap;
#endif
        free(tmpusername);
#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
        proccap = cap_get_proc();
        if (!proccap) {
          openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
          syslog(LOG_DEBUG, "do_pam_auth(): failed cap_get_proc() as user %u, error: %s", getuid(), strerror(errno));
        } else {
          char * capstext;
          capstext = cap_to_text(proccap, NULL);
          if (capstext) {
            openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
            syslog(LOG_DEBUG, "do_pam_auth(): failed setuid(%u) as user %u, caps %s, error: %s", getuid(), getuid(), capstext, strerror(errno));
            cap_free(capstext);
          }
          cap_free(proccap);
        }
#endif
        return 0;
      }
      if (setgid(pw->pw_gid)) {
        free(tmpusername);
#if !defined(WIN32) && !defined(WIN64)
        openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
        syslog(LOG_DEBUG, "do_pam_auth(): failed setgid(), error: %s", strerror(errno));
#endif
        return 0;
      }

      char * displaynum = getenv("DISPLAYNUM");
#if !defined(WIN32) && !defined(WIN64)
      if (displaynum) {
        char tmp[256];

        snprintf(tmp, 255, "/tmp/.X11-unix/X%s", displaynum);
        tmp[255] = 0;
        chown(tmp, pw->pw_uid, pw->pw_gid);
        chmod(tmp, 0600);
      }
#endif

      if (setuid(pw->pw_uid)) {
#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
        cap_t proccap;
#endif
        free(tmpusername);
#if !defined(WIN32) && !defined(WIN64) && !defined(__APPLE__)
        proccap = cap_get_proc();
        if (!proccap) {
          openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
          syslog(LOG_DEBUG, "do_pam_auth(): failed cap_get_proc() as user %u, error: %s", getuid(), strerror(errno));
        } else {
          char * capstext;
          capstext = cap_to_text(proccap, NULL);
          if (capstext) {
            openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
            syslog(LOG_DEBUG, "do_pam_auth(): failed setuid(%u) as user %u, caps %s, error: %s", pw->pw_uid, getuid(), capstext, strerror(errno));
            cap_free(capstext);
          }
          cap_free(proccap);
        }
#endif
        return 0;
      }

      if (chdir(pw->pw_dir)) {
        free(tmpusername);
#if !defined(WIN32) && !defined(WIN64)
        openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
        syslog(LOG_DEBUG, "do_pam_auth(): failed chdir(), error: %s", strerror(errno));
#endif
        return 0;
      }

      setenv("USER", tmpusername, 1);
      setenv("LOGNAME", tmpusername, 1);
      setenv("HOME", pw->pw_dir, 1);
      setenv("SHELL", pw->pw_shell, 1);
      free(tmpusername);

      if (!getenv("DISPLAY")) {
        if (displaynum) {
          char displaystring[20];

          snprintf(displaystring, 19, "unix:%s.0", displaynum);
          displaystring[19] = 0;
          setenv("DISPLAY", displaystring, 1);
        }
      }

      if (UserProgram != NULL) {
        pid_t childpid;

        childpid = fork();
        if (childpid < 0) {
          fprintf(stderr, "could not fork for user program: %s\n", UserProgram);
          ret = PAM_ABORT;
        } else
          if (childpid == 0) {
            /* the child */
            char * execargs[2];
            int fd;

            /* close potentially open FDs */
            for (fd = 0; fd < 20; fd++)
              close(fd);
            execargs[0] = UserProgram;
            execargs[1] = NULL;
            execv(UserProgram, execargs);
            fprintf(stderr, "could not execute user program: %s\n", UserProgram);
            exit(1);
          }
      }
    } else {
      free(tmpusername);
#if !defined(WIN32) && !defined(WIN64)
      openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
      syslog(LOG_DEBUG, "do_pam_auth(): pam failed");
#endif
      return 0;
    }
  } else {
    free(tmpusername);
  }

  return ret == PAM_SUCCESS ? 1 : 0;
}
