/***************************************************************************
 * LPRng - An Extended Print Spooler System
 *
 * Copyright 1988-2003, Patrick Powell, San Diego, CA
 *     papowell@lprng.com
 * See LICENSE for conditions of use.
 *
 ***************************************************************************/

 static char *const _id =
"$Id: permission.c,v 1.74 2004/09/24 20:19:58 papowell Exp $";


#include "lp.h"
#include "fileopen.h"
#include "globmatch.h"
#include "gethostinfo.h"
#include "getqueue.h"
#include "permission.h"
#include "linksupport.h"

/**** ENDINCLUDE ****/

 struct keywords permwords[] = {

{"ACCEPT", 0, P_ACCEPT,0,0,0,0},
{"AUTH", 0, P_AUTH,0,0,0,0},
{"AUTHFROM", 0, P_AUTHFROM,0,0,0,0},
{"AUTHJOB", 0, P_AUTHJOB,0,0,0,0},
{"AUTHSAMEUSER", 0, P_AUTHSAMEUSER,0,0,0,0},
{"AUTHTYPE", 0, P_AUTHTYPE,0,0,0,0},
{"AUTHUSER", 0, P_AUTHUSER,0,0,0,0},
{"CONTROLLINE", 0, P_CONTROLLINE,0,0,0,0},
{"DEFAULT", 0, P_DEFAULT,0,0,0,0},
{"FORWARD", 0, P_FORWARD,0,0,0,0},
{"GROUP", 0, P_GROUP,0,0,0,0},
{"HOST", 0, P_HOST,0,0,0,0},
{"IFIP", 0, P_IFIP,0,0,0,0},
{"IP", 0, P_IP,0,0,0,0},
{"LPC", 0, P_LPC,0,0,0,0},
{"NOMATCHFOUND", 0, 0,0,0,0,0},
{"NOT", 0, P_NOT,0,0,0,0},
{"PORT", 0, P_PORT,0,0,0,0},
{"PRINTER", 0, P_PRINTER,0,0,0,0},
{"REJECT", 0, P_REJECT,0,0,0,0},
{"REMOTEGROUP", 0, P_REMOTEGROUP,0,0,0,0},
{"REMOTEHOST", 0, P_REMOTEHOST,0,0,0,0},
{"REMOTEIP", 0, P_REMOTEIP,0,0,0,0},
{"REMOTEPORT", 0, P_REMOTEPORT,0,0,0,0},
{"REMOTEUSER", 0, P_REMOTEUSER,0,0,0,0},
{"SAMEHOST", 0, P_SAMEHOST,0,0,0,0},
{"SAMEUSER", 0, P_SAMEUSER,0,0,0,0},
{"SERVER", 0, P_SERVER,0,0,0,0},
{"SERVICE", 0, P_SERVICE,0,0,0,0},
{"USER", 0, P_USER,0,0,0,0},
{"UNIXSOCKET", 0, P_UNIXSOCKET,0,0,0,0},
{"AUTHCA", 0, P_AUTHCA,0,0,0,0},

{0,0,0,0,0,0,0}
};

char *perm_str( int n )
{
	return(Get_keystr(n,permwords));
}
int perm_val( char *s )
{
	if( !s )return(0);
	if( safestrlen(s) == 1 && isupper(cval(s)) ){
		return( P_CONTROLLINE );
	}
	return(Get_keyval(s,permwords));
}

/***************************************************************************
 * Perms_check( struct line_list *perms, struct perm_check );
 * - run down the list of permissions
 * - do the check on each of them
 * - if you get a distinct fail or success, return
 * 1. the P_NOT field inverts the result of the next test
 * 2. if one test fails,  then we go to the next line
 * 3. The entire set of tests is accepted if all pass, i.e. none fail
 ***************************************************************************/

int Perms_check( struct line_list *perms, struct perm_check *check,
	struct job *job, int job_check )
{
	int j, c, linecount, valuecount, key;
	int invert = 0;
	int result = 0, m = 0;
	struct line_list values, args;
	char *s, *t;					/* string */
	int last_default_perm;
	char buffer[4];

	DEBUGFC(DDB1)Dump_perm_check( "Perms_check - checking", check );
	DEBUGFC(DDB1)Dump_line_list( "Perms_check - permissions", perms );
	Init_line_list(&values);
	Init_line_list(&args);
	last_default_perm = perm_val( Default_permission_DYN );
	DEBUGF(DDB1)("Perms_check: last_default_perm '%s', Default_perm '%s'",
		perm_str( last_default_perm ), Default_permission_DYN );
	if( check == 0 || perms == 0 ){
		return( last_default_perm );
	}
	for( linecount = 0; result == 0 && linecount < perms->count; ++linecount ){
		DEBUGF(DDB2)("Perms_check: line [%d]='%s'", linecount,
			perms->list[linecount]);
		Free_line_list(&values);
		Split(&values,perms->list[linecount],Whitespace,0,0,0,0,0,0);
		if( values.count == 0 ) continue;
		result = 0; m = 0; invert = 0;
		for( valuecount = 0; m == 0 && valuecount < values.count;
				++valuecount ){
			DEBUGF(DDB2)("Perms_check: [%d]='%s'",valuecount,
				values.list[valuecount] );
			Free_line_list(&args);
			Split(&args,values.list[valuecount],Perm_sep,0,0,0,0,0,0);
			if( args.count == 0 ) continue;
			if( invert > 0 ){
				invert = -1;
			} else {
				invert = 0;
			}
			key = perm_val( args.list[0] );
			if( key == 0 ){
				m = 1;
				break;
			}
			/* we remove the key entry */
			Remove_line_list( &args, 0 );
			DEBUGF(DDB2)("Perms_check: before doing %s, result %d, %s",
				perm_str(key), result, perm_str(result) );
			switch( key ){
			case P_NOT:
				invert = 1;
				continue;
			case P_REJECT: result = P_REJECT; m = 0; break;
			case P_ACCEPT: result = P_ACCEPT; m = 0; break;
			case P_USER:
				m = 1;
				if( !job_check ){ m = 0; }
				else switch (check->service){
				case 'X': break;
				default:
					m = match( &args, check->user, invert );
					break;
				}
				break;
			case P_LPC:
				m = 1;
				switch (check->service){
				case 'X': break;
				case 'C':
					m = match( &args, check->lpc, invert );
					break;
				}
				break;

			case P_IP:
			case P_HOST:
				m = 1;
				if( !job_check ){ m = 0; }
				else switch (check->service){
				case 'X': break;
				default:
					m = match_host( &args, check->host, invert );
					break;
				}
				break;

			case P_GROUP:
				m = 1;
				if( !job_check ){ m = 0; }
				else switch (check->service){
				case 'X': break;
				default:
					m = match_group( &args, check->user, invert );
					break;
				}
				break;

			case P_REMOTEPORT:
			case P_PORT:
				m = 1;
				switch (check->service){
				case 'X': case 'M': case 'C':
					m = match_range( &args, check->port, invert );
					break;
				}
				break;
			case P_REMOTEUSER:
				m = 1;
				switch (check->service){
				case 'X': break;
				default:
					m = match( &args, check->remoteuser, invert );
					break;
				}
				break;
			case P_REMOTEGROUP:
				m = 1;
				switch (check->service){
				case 'X': break;
				default:
					m = match_group( &args, check->remoteuser, invert );
					break;
				}
				break;

			case P_IFIP:
			case P_REMOTEHOST:
			case P_REMOTEIP:
				m = match_host( &args, check->remotehost, invert );
				break;

			case P_AUTH:
				m = 1;
				switch (check->service){
				case 'X': break;
				default:
					DEBUGF(DDB3)(
						"Perms_check: P_AUTH authuser '%s'", check->authuser );
					m = !check->authuser;
					if( invert ) m = !m;
				}
				break;

			case P_AUTHTYPE:
				m = 1;
				switch (check->service){
				case 'X': break;
				default:
					DEBUGF(DDB3)(
						"Perms_check: P_AUTHTYPE authtype '%s'", check->authtype );
					m = match( &args, check->authtype, invert );
				}
				break;

			case P_AUTHFROM:
				m = 1;
				switch (check->service){
				case 'X': break;
				default:
					m = match( &args, check->authfrom, invert );
				}
				break;

			case P_AUTHCA:
				m = 1;
				switch (check->service){
				case 'X': break;
				default:
					m = match( &args, check->authca, invert );
				}
				break;

			case P_AUTHUSER:
				m = 1;
				switch (check->service){
				case 'X': break;
				default:
					m = match( &args, check->authuser, invert );
				}
				break;

			case P_CONTROLLINE:
				/* check to see if we have control line */
				m = 1;
				if( !job_check ){ m = 0; }
				for( j = 0; m && j < args.count; ++j ){
					if( !(t = args.list[j]) ) continue;
					c = cval(t);
					buffer[1] = 0; buffer[0] = c;
					if( isupper(c) && (s = Find_str_value(&job->info,buffer))){
						/* we do a glob match against line */
						m = Globmatch( t+1, s );
					}
				}
				if( invert ) m = !m;
				break;
			case P_PRINTER:
				m = 1;
				switch (check->service){
				case 'X': break;
				default:
					m = match( &args, check->printer, invert );
					break;
				}
				break;
			case P_SERVICE:
				m = match_char( &args, check->service, invert );
				break;
			case P_FORWARD:
			case P_SAMEHOST:
				m = 1;
				if( !job_check ){ m = 0; }
				else switch (check->service){
				default:
					/* P_SAMEHOST check succeeds if P_REMOTEIP == P_IP */
					m = Same_host(check->host, check->remotehost);
					if( m ){
					/* check to see if both remote and local are server */
					int r, h;
					r = Same_host(check->remotehost,&Host_IP);
					if( r ) r = Same_host(check->remotehost,&Localhost_IP);
					h = Same_host(check->host,&Host_IP);
					if( h ) h = Same_host(check->host,&Localhost_IP);
					DEBUGF(DDB3)(
						"Perms_check: P_SAMEHOST server name check r=%d,h=%d",
						r, h );
					if( h == 0 && r == 0 ){
						m = 0;
					}
					}
					if( invert ) m = !m;
					break;
				}
				if( key == P_FORWARD ) m = !m;
				break;
			case P_SAMEUSER:
				m = 1;
				if( !job_check ){ m = 0; }
				else switch (check->service){
				default: break;
				case 'Q': case 'M': case 'C':
					/* check succeeds if remoteuser == user */
					m = (safestrcmp( check->user, check->remoteuser ) != 0);
					if( invert ) m = !m;
					DEBUGF(DDB3)(
					"Perms_check: P_SAMEUSER '%s' == remote '%s', rslt %d",
					check->user, check->remoteuser, m );
					break;
				}
				break;

			case P_AUTHSAMEUSER:
				m = 1;
				if( !job_check ){ m = 0; }
				else switch (check->service){
				default: break;
				case 'Q': case 'M': case 'C':
					/* check succeeds if remoteuser == user */
					t = Find_str_value(&job->info,AUTHUSER);
					m = (safestrcmp( check->authuser, t ) != 0);
					if( invert ) m = !m;
					DEBUGF(DDB3)(
					"Perms_check: P_AUTHSAMEUSER job authinfo '%s' == auth_id '%s', rslt %d",
					t, check->authuser, m );
					break;
				}
				break;

			case P_AUTHJOB:
				m = 1;
				switch (check->service){
				default: break;
				case 'Q': case 'M': case 'C':
					/* check succeeds if authinfo present */
					t = Find_str_value(&job->info,AUTHUSER);
					m = !t;
					if( invert ) m = !m;
					DEBUGF(DDB3)(
					"Perms_check: P_AUTHJOB job authinfo '%s', rslt %d", t, m );
					break;
				}
				break;

			case P_SERVER:
				m = 1;
				/* check succeeds if remote P_IP and server P_IP == P_IP */
				m = Same_host(check->remotehost,&Host_IP);
				if( m ) m = Same_host(check->remotehost,&Localhost_IP);
				if( invert ) m = !m;
				break;

			case P_UNIXSOCKET:
				m = 1;
				/* check succeeds if connection via unix socket */
				m = !check->unix_socket;
				if( invert ) m = !m;
				break;

			case P_DEFAULT:
				
				DEBUGF(DDB3)("Perms_check: DEFAULT - %d, values.count %d",
					valuecount, values.count );
				m = 1;
				if( values.count == 2 ){
					switch( perm_val( values.list[1]) ){
					case P_REJECT: last_default_perm =  P_REJECT; break;
					case P_ACCEPT: last_default_perm =  P_ACCEPT; break;
					}
				}
			}
			DEBUGF(DDB2)("Perms_check: match %d, result '%s' default now '%s'",
				m, perm_str(result), perm_str(last_default_perm) );
		}
		if( m ){
			result = 0;
		} else if( result == 0 ){
			result = last_default_perm;
		}
		DEBUGF(DDB1)("Perms_check: '%s' - match %d, result '%s' default now '%s'",
			perms->list[linecount],
			m, perm_str(result), perm_str(last_default_perm) );
	}
	if( result == 0 ){
		result = last_default_perm;
	}
	DEBUGF(DDB1)("Perms_check: final result %d '%s'",
				result, perm_str( result ) );
	Free_line_list(&values);
	Free_line_list(&args);
	return( result );
}


/***************************************************************************
 * static int match( char **val, char *str );
 *  returns 1 on failure, 0 on success
 *  - match the string against the list of options
 *    options are glob type regular expressions;  we implement this
 *    currently using the most crude of pattern matching
 *  - if string is null or pattern list is null, then match fails
 *    if both are null, then match succeeds
 ***************************************************************************/

int match( struct line_list *list, const char *str, int invert )
{
 	int result = 1, i, c;
	char *s;
 	DEBUGF(DDB3)("match: str '%s', invert %d", str, invert );
 	if(str)for( i = 0; result && i < list->count; ++i ){
		if( !(s = list->list[i])) continue;
		DEBUGF(DDB3)("match: str '%s' to '%s'", str, s );
 		/* now do the match */
		c = cval(s);
		if( c == '@' ) {	/* look up host in netgroup */
#ifdef HAVE_INNETGR
			result = !innetgr( s+1, (char *)str, 0, 0 );
#else /* HAVE_INNETGR */
			DEBUGF(DDB3)("match: no innetgr() call, netgroups not permitted");
#endif /* HAVE_INNETGR */
		} else if( c == '<' && cval(s+1) == '/' ){
			struct line_list users;
			Init_line_list(&users);
			Get_file_image_and_split(s+1,0,0,&users,Whitespace,
				0,0,0,0,0,0);
			DEBUGFC(DDB3)Dump_line_list("match- file contents'", &users );
			result = match( &users,str,0);
			Free_line_list(&users);
		} else {
	 		result = Globmatch( s, str );
		}
		DEBUGF(DDB3)("match: list[%d]='%s', result %d", i, s,  result );
	}
	if( invert ) result = !result;
 	DEBUGF(DDB3)("match: str '%s' final result %d", str, result );
	return( result );
}

/***************************************************************************
 * static int match_host( char **list, char *host );
 *  returns 1 on failure, 0 on success
 *  - match the hostname/printer strings against the list of options
 *    options are glob type regular expressions;  we implement this
 *    currently using the most crude of pattern matching
 *  - if string is null or pattern list is null, then match fails
 *    if both are null, then match succeeds
 ***************************************************************************/

int match_host( struct line_list *list, struct host_information *host,
	int invert )
{
 	int result = Match_ipaddr_value(list,host);
	if( invert ) result = !result;
 	DEBUGF(DDB3)("match_host: host '%s' final result %d", host?host->fqdn:0,
		result );
	return( result );
}
/***************************************************************************
 * static int match_range( char **list, int port );
 * check the port number and/or range
 * entry has the format:  number     number-number
 ***************************************************************************/

int portmatch( char *val, int port )
{
	int low, high, err;
	char *end;
	int result = 1;
	char *s, *t, *tend;

	err = 0;
	s = safestrchr( val, '-' );
	if( s ){
		*s = 0;
	}
	end = val;
	low = strtol( val, &end, 10 );
	if( end == val || *end ) err = 1;

	high = low;
	if( s ){
		tend = t = s+1;
		high = strtol( t, &tend, 10 );
		if( t == tend || *tend ) err = 1;
		*s = '-';
	}
	if( err ){
		LOGMSG( LOG_ERR) "portmatch: bad port range '%s'", val );
	}
	if( high < low ){
		err = high;
		high = low;
		low = err;
	}
	result = !( port >= low && port <= high );
	DEBUGF(DDB3)("portmatch: low %d, high %d, port %d, result %d",
		low, high, port, result );
	return( result );
}

int match_range( struct line_list *list, int port, int invert )
{
	int result = 1;
	int i;
	char *s;

	DEBUGF(DDB3)("match_range: port '0x%x'", port );
	for( i = 0; result && i < list->count; ++i ){
		/* now do the match */
		if( !(s = list->list[i]) ) continue;
		result = portmatch( s, port );
	}
	if( invert ) result = !result;
	DEBUGF(DDB3)("match_range: port '%d' result %d", port, result );
	return( result );
}

/***************************************************************************
 * static int match_char( char **list, int value );
 * check for the character value in one of the option strings
 * entry has the format:  string
 ***************************************************************************/

int match_char( struct line_list *list, int value, int invert )
{
	int result = 1;
	int i;
	char *s;

	DEBUGF(DDB3)("match_char: value '0x%x' '%c'", value, value );
	DEBUGFC(DDB3)Dump_line_list("match_char - lines", list );
	for( i = 0; result && i < list->count; ++i ){
		if( !(s = list->list[i]) ) continue;
		result = (safestrchr( s, value ) == 0) && (safestrchr(s,'*') == 0) ;
		DEBUGF(DDB3)("match_char: val %c, str '%s', match %d",
			value, s, result);
	}
	if( invert ) result = !result;
	DEBUGF(DDB3)("match_char: value '%c' result %d", value, result );
	return( result );
}


/***************************************************************************
 * static int match_group( char **list, char *str );
 *  returns 1 on failure, 0 on success
 *  - get the UID for the named user
 *  - scan the listed groups to see if there is a group
 *    check to see if user is in group
 ***************************************************************************/

int match_group( struct line_list *list, const char *str, int invert )
{
 	int result = 1;
 	int i;
	char *s;

 	DEBUGF(DDB3)("match_group: str '%s'", str );
 	for( i = 0; str && result && i < list->count; ++i ){
 		/* now do the match */
		if( !(s = list->list[i]) ) continue;
 		result = ingroup( s, str );
	}
	if( invert ) result = !result;
 	DEBUGF(DDB3)("match: str '%s' value %d", str, result );
	return( result );
}

/***************************************************************************
 * static int ingroup( char* *group, char *user );
 *  returns 1 on failure, 0 on success
 *  scan group for user name
 * Note: we first check for the group.  If there is none, we check for
 *  wildcard (*) in group name, and then scan only if we need to
 ***************************************************************************/

int ingroup( char *group, const char *user )
{
	struct group *grent;
	struct passwd *pwent;
	char **members;
	int result = 1;

	DEBUGF(DDB3)("ingroup: checking '%s' for membership in group '%s'", user, group);
	if( group == 0 || user == 0 ){
		return( result );
	}
	/* first try getgrnam, see if it is a group */
	pwent = getpwnam(user);
	if( group[0] == '@' ) {	/* look up user in netgroup */
#ifdef HAVE_INNETGR
		if( !innetgr( group+1, 0, (char *)user, 0 ) ) {
			DEBUGF(DDB3)( "ingroup: user %s P_NOT in netgroup %s", user, group+1 );
		} else {
			DEBUGF(DDB3)( "ingroup: user %s in netgroup %s", user, group+1 );
			result = 0;
		}
#else /* HAVE_INNETGR */
		DEBUGF(DDB3)( "ingroup: no innetgr() call, netgroups not permitted" );
#endif /* HAVE_INNETGR */
	} else if( group[0] == '<' && group[1] == '/' ){
		struct line_list users;
		Init_line_list(&users);
		Get_file_image_and_split(group+1,0,0,&users,Whitespace,
			0,0,0,0,0,0);
		DEBUGFC(DDB3)Dump_line_list("match- file contents'", &users );
		result = match_group( &users,user,0);
		Free_line_list(&users);
	} else if( (grent = getgrnam( group )) ){
		DEBUGF(DDB3)("ingroup: group id: %d\n", grent->gr_gid);
		if( pwent && ((int)pwent->pw_gid == (int)grent->gr_gid) ){
			DEBUGF(DDB3)("ingroup: user default group id: %d\n", pwent->pw_gid);
			result = 0;
		} else for( members = grent->gr_mem; result && *members; ++members ){
			DEBUGF(DDB3)("ingroup: member '%s'", *members);
			result = (safestrcmp( user, *members ) != 0);
		}
	} else if( safestrpbrk( group, "*[]") ){
		/* wildcard in group name, scan through all groups */
		setgrent();
		while( result && (grent = getgrent()) ){
			DEBUGF(DDB3)("ingroup: group name '%s'", grent->gr_name);
			/* now do match against group */
			if( Globmatch( group, grent->gr_name ) == 0 ){
				if( pwent && ((int)pwent->pw_gid == (int)grent->gr_gid) ){
					DEBUGF(DDB3)("ingroup: user default group id: %d\n",
					pwent->pw_gid);
					result = 0;
				} else {
					DEBUGF(DDB3)("ingroup: found '%s'", grent->gr_name);
					for( members = grent->gr_mem; result && *members; ++members ){
						DEBUGF(DDB3)("ingroup: member '%s'", *members);
						result = (safestrcmp( user, *members ) != 0);
					}
				}
			}
		}
		endgrent();
	}
	DEBUGF(DDB3)("ingroup: result: %d", result );
	return( result );
}

/***************************************************************************
 * Dump_perm_check( char *title, struct perm_check *check )
 * Dump perm_check information
 ***************************************************************************/

void Dump_perm_check( char *title,  struct perm_check *check )
{
	char buffer[SMALLBUFFER];
	if( title ) LOGDEBUG( "*** perm_check %s ***", title );
	buffer[0] = 0;
	if( check ){
		LOGDEBUG(
		"  user '%s', rmtuser '%s', printer '%s', service '%c', lpc '%s'",
		check->user, check->remoteuser, check->printer, check->service, check->lpc );
		Dump_host_information( "  host", check->host );
		Dump_host_information( "  remotehost", check->remotehost );
/*
		LOGDEBUG( "  ip '%s' port %d, unix_socket %d",
			inet_ntop_sockaddr( &check->addr, buffer, sizeof(buffer)),
			check->port, check->unix_socket );
*/
		LOGDEBUG( "  port %d, unix_socket %d",
			check->port, check->unix_socket );
		LOGDEBUG( " authtype '%s', authfrom '%s', authuser '%s', authca '%s'",
			check->authtype, check->authfrom, check->authuser, check->authca );
	}
}

/***************************************************************************
 * Perm_check_to_list( struct line_list *list, struct perm_check *check )
 *  Put perm_check information in list
 ***************************************************************************/

void Perm_check_to_list( struct line_list *list, struct perm_check *check )
{
	char buffer[SMALLBUFFER];
	Set_str_value( list, USER, check->user );
	Set_str_value( list, REMOTEUSER, check->remoteuser );
	Set_str_value( list, PRINTER, check->printer );
	SNPRINTF(buffer,sizeof(buffer))"%c",check->service);
	Set_str_value( list, SERVICE, buffer );
	Set_str_value( list, LPC, check->lpc );
	if( check->host ){
		Set_str_value( list, HOST, check->host->fqdn );
	}
	if( check->remotehost ){
		Set_str_value( list, HOST, check->remotehost->fqdn );
	}
	Set_decimal_value( list, PORT, check->port );
	Set_str_value( list, AUTHTYPE, check->authtype );
	Set_str_value( list, AUTHFROM, check->authfrom );
	Set_str_value( list, AUTHUSER, check->authuser );
	Set_str_value( list, AUTHCA, check->authca );
}
