/*
  $Source: /local/data/cvs/yellowbank/postgres/src/y_pam/y_pg_pamc.c,v $
  $Revision: 1.3 $
  $State: Exp $
  $Date: 2007/03/25 12:35:54 $
  $Author: yrp001 $
  $Locker:  $

  Copyright (c) 2006
  Ronald Peterson
  (Y) Yellowbank
  All rights reserved.  Applicable BSD license terms can be found in
  the associated LICENSE file.
*/

/* PostgreSQL includes */
#include "postgres.h"
#include "fmgr.h"
#include "utils/datetime.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

#include <security/pam_appl.h>

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

#define PAM_SERVICE_NAME "y_pg_pamc"
#define AUTH_FAIL 0
#define AUTH_SUCCESS 1

typedef struct pam_conv pam_pass_conv;
typedef struct pam_response pam_resp;
typedef char pg_bool;

// This code largely borrowed from PostgreSQL's auth.c

// forward declarations
static char *tp2cp_prealloc( char *, const text * );
static int pam_passwd_conv_proc( int num_msg,
                                 const struct pam_message **msg,
                                 pam_resp **resp,
                                 void *appdata_ptr );
static int authenticate_user( const char *username,
                              char *password );


static
char
*tp2cp_prealloc( char *cp, const text *textp ) {
   int len;
   len = VARSIZE(textp) - VARHDRSZ;
   if( cp ) {
      pfree( cp );
   }
   cp = (char*)palloc( len + 1 );
   if( ! memcpy( cp, VARDATA(textp), len ) ) { return NULL; }
   if( ! memset( cp + len, '\0', 1 ) ) { return NULL; }
   return( cp );
}

// This is basically a no-op, that simply checks that we have
// not passed in an empty password.  There is no actual
// conversation.
static
int
pam_passwd_conv_proc( int num_msg,
                      const struct pam_message **msg,
                      pam_resp **resp,
                      void *appdata_ptr )
{
  if( num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF ) {
    return PAM_CONV_ERR;
  }
  
  if( ! appdata_ptr ) {
    // Apparently Solaris 2.6 is broken, and needs a workaround
    // here, but I don't care.
    return PAM_CONV_ERR;
  }

  if( strlen( (char *)appdata_ptr ) == 0 ) {
    return PAM_CONV_ERR;
  }

  // pam will take of freeing this
  *resp = (pam_resp *)calloc( num_msg, sizeof( pam_resp ) );
  if( !(*resp) ) {
    return PAM_CONV_ERR;
  }
  
  (*resp)[0].resp = strdup( (char*)appdata_ptr );
  (*resp)[0].resp_retcode = 0;

  return ( (*resp)[0].resp ? PAM_SUCCESS : PAM_CONV_ERR );
}

static
int
authenticate_user( const char *pam_username,
                   char *pam_password )
{
  int pam_status;
  int password_length;
  pam_handle_t *pamh = NULL;
  pam_pass_conv pconv = { &pam_passwd_conv_proc, NULL };

  password_length = strlen( pam_password );
  pconv.appdata_ptr = (void *)pam_password;

  pam_status = pam_start( PAM_SERVICE_NAME,
                          pam_username,
                          &pconv,
                          &pamh );
  if( pam_status != PAM_SUCCESS ) {
    bzero( pam_password, password_length );
    pam_password = NULL;
    return AUTH_FAIL;
  }

  pam_status = pam_authenticate( pamh, PAM_DISALLOW_NULL_AUTHTOK );
  if( pam_status != PAM_SUCCESS ) {
    if( pam_status == PAM_AUTH_ERR ) {
      // cerr << "error: PAM_AUTH_ERR" << endl;
    }
    bzero( pam_password, password_length );
    pam_password = NULL;
    return AUTH_FAIL;
  }

  pam_status = pam_acct_mgmt( pamh, 0 );
  if( pam_status != PAM_SUCCESS ) {
    bzero( pam_password, password_length );
    pam_password = NULL;
    return AUTH_FAIL;
  }

  pam_status = pam_end( pamh, pam_status );
  if( pam_status != PAM_SUCCESS ) {
    bzero( pam_password, password_length );
    pam_password = NULL;
    return AUTH_FAIL;
  }

  bzero( pam_password, password_length );
  pam_password = NULL;

  if( pam_status == PAM_SUCCESS ) {
    return AUTH_SUCCESS;
  }
  
  return AUTH_FAIL;
}

Datum y_pam_auth( PG_FUNCTION_ARGS );
PG_FUNCTION_INFO_V1( y_pam_auth );
Datum
y_pam_auth( PG_FUNCTION_ARGS )
{
   text *username_t;
   text *password_t;
   char *username = NULL;
   char *password = NULL;
   int passlen;
   pg_bool authOK;

   if( PG_ARGISNULL(0) ||
       PG_ARGISNULL(1) )
   {
      PG_RETURN_NULL();
   }
   username_t = PG_GETARG_TEXT_P(0);
   password_t = PG_GETARG_TEXT_P(1);

   passlen = VARSIZE( password_t ) - VARHDRSZ;

   username = tp2cp_prealloc( username, username_t );
   password = tp2cp_prealloc( password, password_t );
   bzero( VARDATA(password_t), passlen );

   authOK = authenticate_user( username, password ) == AUTH_SUCCESS;

   bzero( password, passlen );
   pfree( username );
   pfree( password );

   PG_RETURN_BOOL( authOK );
}

