/* pam_authsrv module */

#define CONF_FILE "/etc/authsrv"
#define _BSD_SOURCE

#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <ctype.h>


/*
 * here, we make a definition for the externally accessible function
 * in this file (this definition is required for static a module
 * but strongly encouraged generally) it is used to instruct the
 * modules include file to define the function prototypes.
 */

#define PAM_SM_AUTH

#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#include <security/_pam_types.h>
/* some syslogging */

static void _pam_log(int err, const char *format, ...)
{
    va_list args;

    va_start(args, format);
    openlog("PAM-authsrv", LOG_CONS|LOG_PID, LOG_AUTH);
    vsyslog(err, format, args);
    va_end(args);
    closelog();
}


/* The converse function. */
static int converse(pam_handle_t * pamh, int nargs,
	     struct pam_message ** message,
	     struct pam_response ** response)
{
   int retval;
   struct pam_conv * conv;
   
   retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
   
   if (retval == PAM_SUCCESS) {
      retval = conv->conv(nargs, (const struct pam_message **) message, 
			  response, conv->appdata_ptr);
      
      if (retval != PAM_SUCCESS) {
	 _pam_log(LOG_ERR, "Conversation failure [%s]", 
		  pam_strerror(pamh, retval));
      }
   } 
   else {
      _pam_log(LOG_ERR, "could not obtain conversation function [%s] ",
	       pam_strerror(pamh, retval));
   }
   
   return retval;
}


static int pam_conversate(pam_handle_t * pamh,
		   char *challenge,
		   char *response)
{
   struct pam_message msg[1], *mesg[1];
   struct pam_response * resp = NULL;
   char * prompt = NULL;
   int rcode;
   
   /* Fill the necesary thingies */	
   mesg[0] = &msg[0];
   msg[0].msg_style= PAM_TEXT_INFO;	/* We give some info, no response yet */
   msg[0].msg = challenge;
   
   rcode = converse(pamh, 1, mesg, &resp);	/* We don't want a response here */
   
   _pam_overwrite(prompt);
   _pam_drop(prompt);
   
   /* OK, now ask for the response */
   msg[0].msg_style= PAM_PROMPT_ECHO_OFF;
   msg[0].msg = response;
   
   rcode = converse(pamh, 1, mesg, &resp); 	/* Finally */
   _pam_overwrite(prompt);
   _pam_drop(prompt);
   
   /* Check if the conversation succeeded */
   if (rcode != PAM_SUCCESS) {
      if (resp != NULL)
	_pam_drop_reply(resp,1);
      return PAM_AUTHINFO_UNAVAIL;
   }
   
   /* Copy the response */
   strncpy(response, resp->resp, PAM_MAX_RESP_SIZE);
   
   /* Free the prompt.. */
   free(prompt);
   
   return PAM_SUCCESS;
}



/* --- authentication management functions (only) --- */

PAM_EXTERN
  int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc
			  ,const char **argv)
{
   int retval = PAM_AUTH_ERR;
   const char *userName;
   char *p;
   int  port = 0;
   FILE *confFile;
   char line[PAM_MAX_RESP_SIZE + 1];
   struct passwd * pw;
   struct stat sb;
   FILE *fp;
   struct in_addr defaddr;
   struct hostent *hp, def;
   struct sockaddr_in sn;
   int s;
   char *alist[1];
   
   
   retval = pam_get_user(pamh,&userName,NULL);
   if(retval != PAM_SUCCESS) 
     return PAM_SERVICE_ERR;
   
   if(!userName || (strlen(userName) <= 0)) {
      /* Don't let them use a NULL username... */
      pam_get_user(pamh,&userName,"Login name:");
      if (retval != PAM_SUCCESS)
	return PAM_SERVICE_ERR;
   }

   pw = getpwnam(userName);
   if (!pw)
     return PAM_AUTH_ERR;		/* user doesn't exist */
   
   if(stat(CONF_FILE,&sb)) {
      _pam_log(LOG_ERR, CONF_FILE " cannot be stat'd (it probably does "
	       "not exist)");
      return PAM_AUTH_ERR;
   }
   
   if((sb.st_mode & S_IWOTH) || !S_ISREG(sb.st_mode)) {
      _pam_log(LOG_ERR,
	       CONF_FILE " is either world writable or not a normal file");
      return PAM_AUTH_ERR;
   }
      
   confFile = fopen(CONF_FILE,"r");
   if(confFile == NULL) { /* Check that we opened it successfully */
      _pam_log(LOG_ERR,
	       "Error opening " CONF_FILE);
      fclose(confFile);
      return PAM_SERVICE_ERR;
   }
   
   while(fgets(line,PAM_MAX_RESP_SIZE,confFile) != NULL) {
      if (line[strlen(line) - 1] == '\n')
	line[strlen(line) - 1] = '\0';
      p = strchr(line, ':');
      if (p == NULL) {
	 _pam_log(LOG_ERR,
		  "Bad line in config file");
	 fclose(confFile);
	 return PAM_SERVICE_ERR;
      }
      *p++ = '\0';
      port = atol(p);
      if (port < 1024) {
	 _pam_log(LOG_ERR,
		  "Bad port number in config file");
	 fclose(confFile);
	 return PAM_SERVICE_ERR;
      }
      hp = gethostbyname(line);
      if (!hp) {
	 if (!inet_aton(line, &defaddr)) {
	    _pam_log(LOG_ERR,
		     "Unknown server. Trying the next server if available");
	    continue;
	 }   
	 def.h_name = line;
	 def.h_addr_list = alist;
	 def.h_addr = (char *)&defaddr;
	 def.h_length = sizeof(struct in_addr);
	 def.h_addrtype = AF_INET;
	 def.h_aliases = 0;
	 hp = &def;
      }

      memset(&sn, 0, sizeof(sn));
      sn.sin_family = hp->h_addrtype;
      if (hp->h_length > (int)sizeof(sn.sin_addr)) {
	 hp->h_length = sizeof(sn.sin_addr);
      }
      memcpy(&sn.sin_addr, hp->h_addr, hp->h_length);
      sn.sin_port = htons(port);
      if ((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
	 _pam_log(LOG_ERR,
		  "socket error. Trying the next server if available");
	 continue;
      }
      /* have network connection; identify the host connected with */
      if (connect(s, (struct sockaddr *)&sn, sizeof(sn)) < 0) {
	 _pam_log(LOG_ERR,
		  "Connect error. Trying the next server if available");
	 close(s);
	 continue;	
      }
      fp = fdopen(s, "r+");
      if (!fp) {
	 _pam_log(LOG_ERR,
		  "Fdopen error.");
	 close(s);
	 fclose(confFile);
	 return PAM_SERVICE_ERR;
      }
      /* Begining of the TIS protokoll */
      if (fgets(line, PAM_MAX_RESP_SIZE, fp) == NULL) {
	 _pam_log(LOG_ERR,
		  "No response from authsrv. Trying the next server if available");
	 fclose(fp);
	 continue;
      }
      
      if (strncmp(line, "Authsrv ready.", strlen("Authsrv ready."))) {
	 _pam_log(LOG_ERR,
		  "Authsrv not ready. Trying the next server if available");
	 fclose(fp);
	 continue;
      }
      
      if (!fprintf(fp, "authenticate %s\n", userName)) {
	 _pam_log(LOG_ERR,
		  "Error on writing to authserver. Trying the next server if available");
	 fclose(fp);
	 continue;
      }
      
      if (fgets(line, PAM_MAX_RESP_SIZE, fp) == NULL) {
	 _pam_log(LOG_ERR,
		  "No response from authsrv. Trying the next server if available");
	 fclose(fp);
	 continue;
      }

      if (strncmp(line, "challenge Challenge ", strlen("challenge Challenge "))) {
	 _pam_log(LOG_ERR,
		  "Authsrv doesn't send a challenge. Trying the next server if available");
	 fclose(fp);
	 continue;
      }
 
      p = strchr(line, ' ');
      p++;
      if (p[strlen(p) -1] == '\n')
	p[strlen(p) -1] = 0;
      memcpy(line, p, strlen(p) + 1);
      
      retval = pam_conversate(pamh, NULL, line);
      if (retval != PAM_SUCCESS) { 
	 _pam_log(LOG_ERR,
		  "Getting response to the challenge failed.");
	 fclose(fp);
	 fclose(confFile);
	 return PAM_SERVICE_ERR;
      }
      
      if (!fprintf(fp, "response %s\n", line)) {
	 _pam_log(LOG_ERR,
		  "Error on writing to authserver. Trying the next server if available");
	 fclose(fp);
	 continue;
      }
      
      if (fgets(line, PAM_MAX_RESP_SIZE, fp) == NULL) {
	 _pam_log(LOG_ERR,
		  "No response from authsrv. Trying the next server if available");
	 fclose(fp);
	 continue;
      }
      
      fclose(fp);
      fclose(confFile);
      if (strncmp(line, "ok", strlen("ok"))) {
	 return PAM_AUTH_ERR;
      } else {
	 return PAM_SUCCESS;	 
      }
   }
   fclose(confFile);
   return PAM_SERVICE_ERR;
}

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


#ifdef PAM_STATIC

/* static module data */

struct pam_module _pam_authsrv_modstruct = {
     "pam_authsrv",
     pam_sm_authenticate,
     pam_sm_setcred,
     NULL,
     NULL,
     NULL,
     NULL,
};

#endif

/* end of module definition */
