Skip to content
Snippets Groups Projects
pam_authclient.c 5.07 KiB

#include "config.h"
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif

#include "auth_client.h"

/* These #defines must be present according to PAM documentation. */
#define PAM_SM_AUTH

#ifdef HAVE_SECURITY_PAM_APPL_H
#include <security/pam_appl.h>
#endif
#ifdef HAVE_SECURITY_PAM_MODULES_H
#include <security/pam_modules.h>
#endif
#ifdef HAVE_PAM_PAM_APPL_H
#include <pam/pam_appl.h>
#endif
#ifdef HAVE_PAM_PAM_MODULES_H
#include <pam/pam_modules.h>
#endif

#ifndef PAM_EXTERN
#ifdef PAM_STATIC
#define PAM_EXTERN static
#else
#define PAM_EXTERN extern
#endif
#endif

static const char *kPasswordPrompt = "Password: ";
static const char *kOtpPrompt = "OTP Token: ";

struct cfg {
  int debug;
  int use_first_pass;
  int try_first_pass;
  char *auth_server;
  char *ssl_crt;
  char *ssl_key;
  char *ca_file;
};

static void parse_cfg(int argc, const char **argv, struct cfg *cfg) {
  int i;

  memset(cfg, 0, sizeof(struct cfg));
  for (i = 0; i < argc; i++) {
    if (!strcmp(argv[i], "debug")) {
      cfg->debug = 1;
    } else if (!strcmp(argv[i], "try_first_pass")) {
      cfg->try_first_pass = 1;
    } else if (!strcmp(argv[i], "use_first_pass")) {
      cfg->use_first_pass = 1;
    } else if (!strncmp(argv[i], "auth_server=", 12)) {
      cfg->auth_server = (char *)(argv[i] + 12);
    } else if (!strncmp(argv[i], "ssl_crt=", 8)) {
      cfg->ssl_crt = (char *)(argv[i] + 8);
    } else if (!strncmp(argv[i], "ssl_key=", 8)) {
      cfg->ssl_key = (char *)(argv[i] + 8);
    } else if (!strncmp(argv[i], "ca=", 3)) {
      cfg->ca_file = (char *)(argv[i] + 3);
    }
  }
}

PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,
                                   int flags, int argc, const char **argv) {
  int i, retval, err;
  const char *service = NULL;
  const char *username = NULL;
  const char *password = NULL;
  const char *otp_token = NULL;
  const char *source_ip = NULL;
  struct pam_conv *conv = NULL;
  struct cfg cfg;
  auth_client_t ac;

  parse_cfg(argc, argv, &cfg);

  err = pam_get_user(pamh, &username, NULL);
  if (err != PAM_SUCCESS) {
    D(("pam_get_user() error: %s", pam_strerror(pamh, err)));
    return PAM_AUTH_ERR;
  }

  err = pam_get_item(pamh, PAM_SERVICE, (PAM_CONST void **)&service);
  if (err != PAM_SUCCESS) {
    D(("pam_get_item(service) error: %s", pam_strerror(pamh, err)));
    return PAM_AUTH_ERR;
  }

  pam_get_item(pamh, PAM_RHOST, (PAM_CONST void **)&source_ip);

  if (cfg.try_first_pass || cfg.use_first_pass) {
    if (pam_get_item(pamh, PAM_AUTHTOK, (PAM_CONST void **)&password) != PAM_SUCCESS) {
      return PAM_AUTH_ERR;
    }
  }

  if (cfg.use_first_pass && password == NULL) {
    return PAM_AUTH_ERR;
  }

  if (password == NULL) {
    // Ask for the password interactively.
    struct pam_message *pmsg[1], msg[1];
    struct pam_response *resp;

    err = pam_get_item(pamh, PAM_CONV, (PAM_CONST void **)&conv);
    if (err != PAM_SUCCESS) {
      D(("pam_get_item(conv) error: %s", pam_strerror(pamh, err)));
      return PAM_AUTH_ERR;
    }
    pmsg[0] = &msg[0];
    msg[0].msg = (char *)kPasswordPrompt;
    msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
    err = conv->conv(1, (const struct pam_message **)pmsg,
                     &resp, conv->appdata_ptr);
    if (err != PAM_SUCCESS) {
      D(("conv() error: %s", pam_strerror(pamh, err)));
      return PAM_AUTH_ERR;
    }
    password = resp->resp;
  }

  // Create the auth client request.
  ac = auth_client_new(service, cfg.auth_server);
  if (cfg.ssl_crt && cfg.ssl_key && cfg.ca_file) {
    auth_client_set_certificate(ac, cfg.ca_file, cfg.ssl_crt, cfg.ssl_key);
  }

  retval = PAM_AUTH_ERR;
  // Allow two authentication attempts in case we receive an
  // OTP_REQUIRED response from the server.
  for (i = 0; i < 2; i++) {
    int ac_err = auth_client_authenticate(ac, username, password, otp_token, source_ip);
    if (ac_err == AC_OK) {
      retval = PAM_SUCCESS;
    } else if (ac_err == AC_ERR_OTP_REQUIRED) {
      struct pam_message *pmsg[1], msg[1];
      struct pam_response *resp;

      // Ask for the OTP token interactively.
      if (conv == NULL) {
        err = pam_get_item(pamh, PAM_CONV, (PAM_CONST void **)&conv);
        if (err != PAM_SUCCESS) {
          D(("pam_get_item(conv) error: %s", pam_strerror(pamh, err)));
          break;
        }
      }
      pmsg[0] = &msg[0];
      msg[0].msg = (char *)kOtpPrompt;
      msg[0].msg_style = PAM_PROMPT_ECHO_ON;
      err = conv->conv(1, (const struct pam_message **)pmsg,
                       &resp, conv->appdata_ptr);
      if (err != PAM_SUCCESS) {
        D(("conv() error: %s", pam_strerror(pamh, err)));
        break;
      }
      otp_token = resp->resp;
      continue;
    } else {
      D(("auth_client error: %s", auth_client_strerror(ac_err)));
    }
    break;
  }

  auth_client_free(ac);
  return retval;
}

PAM_EXTERN int pam_sm_setcred(pam_handle_t *pam, int flags, int argc, const char **argv) {
  return PAM_SUCCESS;
}

#ifdef PAM_STATIC

struct pam_module _pam_authclient_modstruct = {
  "pam_authclient",
  pam_sm_authenticate,
  pam_sm_setcred,
  NULL,
  NULL,
  NULL,
  NULL
};

#endif