/* pam_authuser module */

/*
 * Copyright (c) 2004 The University of New Mexico (UNM)
 *
 * Created by The Center for High Performance Computing at The University
 * of New Mexico.
 *
 * Authors: Shawn Sustaita, Jim Prewett <download@hpcerc.unm.edu>
 * Version: 0.1.1
 *
 * 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 2 of the License, 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 */

#include <string.h>
#include <syslog.h>
#include <security/_pam_macros.h>

#define PAM_SM_ACCOUNT
#include <security/pam_modules.h>
#include <stdio.h>

#define NETGROUP_STR "netgroup"
#define AUTHUSER_STR "authuser"
#define NOAUTHUSER_STR "noauthuser"

/* external functions */
extern ssize_t 
getline(char **lineptr, size_t *n, FILE *stream);

extern int
innetgr(const char *netgroup, const char *host, const char *user, const char *domain);


PAM_EXTERN int 
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv);

char **
build_netgroups(char **argv, int argc, int *count);

char **
list_authuser_files(char **argv, int argc, int *count);

char **
list_authusers(char **files, int file_count, int *count);

char **
list_noauthuser_files(char **argv, int argc, int *count);



/* --- session management functions --- */

PAM_EXTERN int 
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv){

	/* the name of the user and the service that user is connecting to */
	const char * user, * service, * ruser;

	int i; /* cannonical counter int */
	int retval = PAM_SESSION_ERR; /* the value to return; default is error */

	char **netgroups = NULL;	/* to hold the list of netgroups */
	int netgroup_count = 0;

	char **authuser_file_list = NULL; /* to hold the list of authuser files*/
	int authuser_file_count = 0;
	
/*	char **noauthuser_file_list = NULL;*/ /* to hold the list of noauthuser files*/
/*	int noauthuser_file_count = 0;*/

	char **authusers = NULL;	  /* to hold the list of authorized users */
	int authuser_count = 0;

	pam_get_item(pamh, PAM_SERVICE, (const void **) &service); /* what service is being requested? */
	openlog(service, LOG_PID, LOG_AUTHPRIV);

	/* find out who is asking for the session */
	if ( pam_get_item(pamh, PAM_USER, (const void **) &user) != PAM_SUCCESS ) {
		syslog(LOG_NOTICE, "%s", "pam_get_item failed (PAM_USER)\n");
		closelog();
		return retval;
	}

	/* we always allow root */
	if(!strcmp(user,"root")){
		retval = PAM_SUCCESS;
		closelog();
		return retval;
	}

	/* root can do whatever he likes... really */
	/* root had better have already authenticated properly */
	if(pam_get_item(pamh, PAM_RUSER, (const void **) &ruser) == PAM_SUCCESS){
		if(ruser){
			if(!strcmp(ruser,"root")){
				retval = PAM_SUCCESS;
				closelog();
				return retval;
			}
		}
	}

	/* build the list of netgroups to always allow */
	netgroups = build_netgroups((char **)argv,argc,&netgroup_count);

	/* see if the user is in an allowed netgroup */
	for(i = 0; i < netgroup_count; i++){
		if(innetgr(netgroups[i], NULL, user, NULL)){
			retval = PAM_SUCCESS;
			free(netgroups);
			closelog();
			return retval;
		}
	}
	free(netgroups);

	/* build the list of files containing the authorized users */
	authuser_file_list = list_authuser_files((char **)argv,argc,&authuser_file_count);
	
	/* build the list of files containing the explicitly unauthorized users */
/*	noauthuser_file_list = list_noauthuser_files((char **)argv,argc,&noauthuser_file_count);*/

	/* build the list of authorized users from authuser_file_list */
	authusers = list_authusers(authuser_file_list,authuser_file_count, &authuser_count);

	if (authuser_count) { /*Some gcc have problem when authuser_count==0*/
	    for(i = 0; i<authuser_count; i++) {
		    if(!strcmp(user,authusers[i])){
			retval = PAM_SUCCESS;
			break;
		    }
	    }
	    for(i = 0; i < authuser_count; i++)
	    	free(authusers[i]); /* Have to be freed due to strdup! */
	}

	/* free our malloc()'d memory */
	free(authusers);

	free(authuser_file_list);

/*	free(noauthuser_file_list);*/

	closelog();
	return retval;
}

/* return an array of allowed netgroups, or NULL upon failure. Also set count to
 * zero in that case */
char **
build_netgroups(char **argv, int argc, int *count){
	int i = 0;
	char **oldgroups = NULL, **netgroups = NULL;
	for(i = 0; i < argc; i++){
		if(!strncmp(NETGROUP_STR,argv[i],strlen(NETGROUP_STR))){
			(*count)++;
			if ((netgroups = realloc(oldgroups, (sizeof(char *) * *count)))==NULL)   {
			/* netgroups is NULL */
				syslog(LOG_CRIT,"not enough memory!\n");
				free(oldgroups);
				(*count)=0;
				break;
				}
			oldgroups=netgroups;
			netgroups[(*count) - 1] = (argv[i] + strlen(NETGROUP_STR) + 1);
		}
	}
	return(netgroups);
}

/* return an array of authusers */
char **
list_authusers(char **files, int file_count, int *count){
	int i = 0;
	char **oldallowed_users = NULL,**allowed_users = NULL;
	char *potential_authuser = NULL;
	char *position = NULL;  /* where to stop looking for an authusername */
	int n = 0;
	FILE *file = NULL;

	/* XXX this loop could be cleaned up a bit */
	for(i = 0; i < file_count ; i++){
		if((file = fopen(files[i],"r"))){
		/* download hacking -- mostly ripped from my authuser patch */
		/* see if they are in the authuser file, if so allow them, else deny them */
			while(getline(&potential_authuser,&n,file) != -1){
	  	 /* allow for extra whitespace, 
		 		* and optional comments after first whitespace
		 		*
		 		* Throw away all of that nonsense... 
		 		*/
	  		position = strpbrk(potential_authuser,"\n\t ");
				if(position){
								/* put a null there and ignore the rest of the string */
								*position = '\0';
				}

				(*count)++;
				if ((allowed_users = realloc(oldallowed_users, (sizeof(char *) * (*count))))==NULL)	{
				/* allowed_users is NULL -> revert to previous
				 * situation and exit*/
				    syslog(LOG_CRIT,"not enough memory!\n");
				    fclose(file);
				    (*count)--;
				    allowed_users=oldallowed_users;
				    return(allowed_users);
				}
				oldallowed_users=allowed_users;
				allowed_users[((*count) - 1)] = strdup(potential_authuser);
			}
			fclose(file);
		}
		else{
			syslog(LOG_ERR,"can't open file: %s\n",files[i]);
		}
	}
	return(allowed_users);
}

/* return an array of authuser files or NULL on failure. Also set count to
 * zero in that case */
char** list_authuser_files(char **argv, int argc, int *count){
  int i = 0;
  char **oldauthusers = NULL, **authusers = NULL;
  for(i = 0; i < argc; i++){
    if(!strncmp(AUTHUSER_STR,argv[i],strlen(AUTHUSER_STR))){
      (*count)++;
      if ((authusers = realloc(oldauthusers, (sizeof(char *) * *count)))==NULL) {
      /* realloc returns NULL -> authuser is NULL */
	syslog(LOG_CRIT,"not enough memory!\n");
	free(oldauthusers);
	(*count)=0;
	return(authusers);
      }
      oldauthusers=authusers;
      authusers[(*count) - 1] = (argv[i] + strlen(AUTHUSER_STR) + 1);
    }
  }
  return(authusers);
}

/* return an array of noauthuser files */
char **
list_noauthuser_files(char **argv, int argc, int *count){
	int i = 0;
	char **oldnoauthusers = NULL, **noauthusers = NULL;
	for(i = 0; i < argc; i++){
		if(!strncmp(NOAUTHUSER_STR,argv[i],strlen(NOAUTHUSER_STR))){
			(*count)++;
			if ((noauthusers = realloc(oldnoauthusers, (sizeof(char *) * *count)))==NULL)   {
				syslog(LOG_CRIT,"not enough memory!\n");
				free(oldnoauthusers);
				(*count)=0;
				return(noauthusers);
			}
			oldnoauthusers=noauthusers;
			noauthusers[(*count) - 1] = (argv[i] + strlen(NOAUTHUSER_STR) + 1);
			}
			}
	return(noauthusers);
}

#ifdef PAM_STATIC

/* static module data */
/* XXX does this work?  Haven't tested a static build yet */

struct pam_module _pam_authuser_modstruct = {
     "pam_authuser",
     NULL,
     NULL,
     pam_sm_acct_mgmt,
     NULL,
     NULL,
     NULL,
};

#endif
