/*
 * Copyright (c) 1996 Regents of the University of Michigan.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of Michigan at Ann Arbor. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided ``as is'' without express or implied warranty.
 */


/*
 * config.c - configuration file handling routines
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <lber.h>
#include <ldap.h>

#include "slurp.h"
#include "globals.h"

#define MAXARGS	100

/* Forward declarations */
#ifdef NEEDPROTOS
static void	add_replica( char **, int );
static int	parse_replica_line( char **, int, Ri *);
static void	parse_line( char *, int *, char ** );
static char	*getline( FILE * );
static char	*strtok_quote( char *, char * );
#else /* NEEDPROTOS */
static void	add_replica();
static int	parse_replica_line();
static void	parse_line();
static char	*getline();
static char	*strtok_quote();
#endif /* NEEDPROTOS */

/* current config file line # */
static int	lineno;



/*
 * Read the slapd config file, looking only for config options we're
 * interested in.  Since we haven't detached from the controlling
 * terminal yet, we just perror() and fprintf here.
 */
int
slurpd_read_config(
    char	*fname
)
{
    FILE	*fp;
    char	buf[BUFSIZ];
    char	*line, *p;
    int		cargc;
    char	*cargv[MAXARGS];

    Debug( LDAP_DEBUG_CONFIG, "Config: opening config file \"%s\"\n",
	    fname, 0, 0 );

    if ( (fp = fopen( fname, "r" )) == NULL ) {
	perror( fname );
	exit( 1 );
    }

    lineno = 0;
    while ( (line = getline( fp )) != NULL ) {
	/* skip comments and blank lines */
	if ( line[0] == '#' || line[0] == '\0' ) {
	    continue;
	}

	Debug( LDAP_DEBUG_CONFIG, "Config: (%s)\n", line, 0, 0 );

	parse_line( line, &cargc, cargv );

	if ( cargc < 1 ) {
	    fprintf( stderr, "line %d: bad config line (ignored)\n", lineno );
	    continue;
	}

	/* replication log file to which changes are appended */
	if ( strcasecmp( cargv[0], "replogfile" ) == 0 ) {
	    /* 
	     * if slapd_replogfile has a value, the -r option was given,
	     * so use that value.  If slapd_replogfile has length == 0,
	     * then we should use the value in the config file we're reading.
	     */
	    if ( sglob->slapd_replogfile[ 0 ] == '\0' ) {
		if ( cargc < 2 ) {
		    fprintf( stderr,
			"line %d: missing filename in \"replogfile ",
			lineno );
		    fprintf( stderr, "<filename>\" line\n" );
		    exit( 1 );
		} else if ( cargc > 2 && *cargv[2] != '#' ) {
		    fprintf( stderr,
			"line %d: extra cruft at the end of \"replogfile %s\"",
			lineno, cargv[1] );
		    fprintf( stderr, "line (ignored)\n" );
		}
		sprintf( sglob->slapd_replogfile, cargv[1] );
	    }
	} else if ( strcasecmp( cargv[0], "replica" ) == 0 ) {
	    add_replica( cargv, cargc );
	}
    }
    fclose( fp );
    Debug( LDAP_DEBUG_CONFIG,
	    "Config: ** configuration file successfully read and parsed\n",
	    0, 0, 0 );
    return 0;
}




/*
 * Parse one line of input.
 */
static void
parse_line(
    char	*line,
    int		*argcp,
    char	**argv
)
{
    char *	token;

    *argcp = 0;
    for ( token = strtok_quote( line, " \t" ); token != NULL;
	token = strtok_quote( NULL, " \t" ) ) {
	argv[(*argcp)++] = token;
    }
    argv[*argcp] = NULL;
}




static char *
strtok_quote(
    char *line,
    char *sep
)
{
    int		inquote;
    char	*tmp;
    static char	*next;

    if ( line != NULL ) {
	next = line;
    }
    while ( *next && strchr( sep, *next ) ) {
	next++;
    }

    if ( *next == '\0' ) {
	next = NULL;
	return( NULL );
    }
    tmp = next;

    for ( inquote = 0; *next; ) {
	switch ( *next ) {
	case '"':
	    if ( inquote ) {
		inquote = 0;
	    } else {
		inquote = 1;
	    }
	    strcpy( next, next + 1 );
	    break;

	case '\\':
	    strcpy( next, next + 1 );
	    break;

	default:
	    if ( ! inquote ) {
		if ( strchr( sep, *next ) != NULL ) {
		    *next++ = '\0';
		    return( tmp );
		}
	    }
	    next++;
	    break;
	}
    }

    return( tmp );
}

#define CATLINE( buf )	{ \
    int	len; \
    len = strlen( buf ); \
    while ( lcur + len + 1 > lmax ) { \
	lmax += BUFSIZ; \
	line = (char *) ch_realloc( line, lmax ); \
    } \
    strcpy( line + lcur, buf ); \
    lcur += len; \
}



/*
 * Get a line of input.
 */
static char *
getline(
    FILE *fp
)
{
    char	*p;
    static char	buf[BUFSIZ];
    static char	*line;
    static int	lmax, lcur;

    lcur = 0;
    CATLINE( buf );
    while ( fgets( buf, sizeof(buf), fp ) != NULL ) {
	if ( (p = strchr( buf, '\n' )) != NULL ) {
	    *p = '\0';
	}
	lineno++;
	if ( ! isspace( buf[0] ) ) {
	    return( line );
	}

	CATLINE( buf );
    }
    buf[0] = '\0';

    return( line[0] ? line : NULL );
}


/*
 * Add a node to the array of replicas.
 */
static void
add_replica(
    char	**cargv,
    int		cargc
)
{
    int	nr;

    nr = ++sglob->num_replicas;
    sglob->replicas = (Ri **) ch_realloc( sglob->replicas,
	    ( nr + 1 )  * sizeof( Re * ));
    if ( sglob->replicas == NULL ) {
	fprintf( stderr, "out of memory, add_replica\n" );
	exit( 1 );
    }
    sglob->replicas[ nr ] = NULL; 

    if ( Ri_init( &(sglob->replicas[ nr - 1 ])) < 0 ) {
	fprintf( stderr, "out of memory, Ri_init\n" );
	exit( 1 );
    }
    if ( parse_replica_line( cargv, cargc,
	    sglob->replicas[ nr - 1] ) < 0 ) {
	/* Something bad happened - back out */
	fprintf( stderr,
	    "Warning: failed to add replica \"%s:%d - ignoring replica\n",
	    sglob->replicas[ nr - 1 ]->ri_hostname == NULL ?
	    "(null)" : sglob->replicas[ nr - 1 ]->ri_hostname,
	    sglob->replicas[ nr - 1 ]->ri_port );
	sglob->replicas[ nr - 1] = NULL;
	sglob->num_replicas--;
    } else {
	Debug( LDAP_DEBUG_CONFIG,
		"Config: ** successfully added replica \"%s:%d\"\n",
		sglob->replicas[ nr - 1 ]->ri_hostname == NULL ?
		"(null)" : sglob->replicas[ nr - 1 ]->ri_hostname,
		sglob->replicas[ nr - 1 ]->ri_port, 0 );
	sglob->replicas[ nr - 1]->ri_stel =
		sglob->st->st_add( sglob->st,
		sglob->replicas[ nr - 1 ] );
	if ( sglob->replicas[ nr - 1]->ri_stel == NULL ) {
	    fprintf( stderr, "Failed to add status element structure\n" );
	    exit( 1 );
	}
    }
}



/* 
 * Parse a "replica" line from the config file.  replica lines should be
 * in the following format:
 * replica    host=<hostname:portnumber> binddn=<binddn>
 *            bindmethod="simple|kerberos" credentials=<creds>
 *
 * where:
 * <hostname:portnumber> describes the host name and port number where the
 * replica is running,
 *
 * <binddn> is the DN to bind to the replica slapd as,
 *
 * bindmethod is either "simple" or "kerberos", and
 *
 * <creds> are the credentials (e.g. password) for binddn.  <creds> are
 * only used for bindmethod=simple.  For bindmethod=kerberos, the
 * credentials= option should be omitted.  Credentials for kerberos
 * authentication are in the system srvtab file.
 *
 * The "replica" config file line may be split across multiple lines.  If
 * a line begins with whitespace, it is considered a continuation of the
 * previous line.
 */
#define	GOT_HOST	1
#define	GOT_DN		2
#define	GOT_METHOD	4
#define	GOT_ALL		( GOT_HOST | GOT_DN | GOT_METHOD )
static int
parse_replica_line( 
    char	**cargv,
    int		cargc,
    Ri		*ri
)
{
    int		gots = 0;
    int		i;
    char	*hp, *val;

    for ( i = 1; i < cargc; i++ ) {
	if ( !strncasecmp( cargv[ i ], HOSTSTR, strlen( HOSTSTR ))) {
	    val = cargv[ i ] + strlen( HOSTSTR ) + 1;
	    if (( hp = strchr( val, ':' )) != NULL ) {
		*hp = '\0';
		hp++;
		ri->ri_port = atoi( hp );
	    }
	    if ( ri->ri_port <= 0 ) {
		ri->ri_port = LDAP_PORT;
	    }
	    ri->ri_hostname = strdup( val );
	    gots |= GOT_HOST;
	} else if ( !strncasecmp( cargv[ i ],
		BINDDNSTR, strlen( BINDDNSTR ))) { 
	    val = cargv[ i ] + strlen( BINDDNSTR ) + 1;
	    ri->ri_bind_dn = strdup( val );
	    gots |= GOT_DN;
	} else if ( !strncasecmp( cargv[ i ], BINDMETHSTR,
		strlen( BINDMETHSTR ))) {
	    val = cargv[ i ] + strlen( BINDMETHSTR ) + 1;
	    if ( !strcasecmp( val, KERBEROSSTR )) {
#ifdef KERBEROS
		ri->ri_bind_method = AUTH_KERBEROS;
		if ( ri->ri_srvtab == NULL ) {
		    ri->ri_srvtab = strdup( sglob->default_srvtab );
		}
		gots |= GOT_METHOD;
#else /* KERBEROS */
	    fprintf( stderr, "Error: a bind method of \"kerberos\" was\n" );
	    fprintf( stderr, "specified in the slapd configuration file,\n" );
	    fprintf( stderr, "but slurpd was not built with kerberos.\n" );
	    fprintf( stderr, "You must rebuild the LDAP release with\n" );
	    fprintf( stderr, "kerberos support if you wish to use\n" );
	    fprintf( stderr, "bindmethod=kerberos\n" );
	    exit( 1 );
#endif /* KERBEROS */
	    } else if ( !strcasecmp( val, SIMPLESTR )) {
		ri->ri_bind_method = AUTH_SIMPLE;
		gots |= GOT_METHOD;
	    } else {
		ri->ri_bind_method = -1;
	    }
	} else if ( !strncasecmp( cargv[ i ], CREDSTR, strlen( CREDSTR ))) {
	    val = cargv[ i ] + strlen( CREDSTR ) + 1;
	    ri->ri_password = strdup( val );
	} else if ( !strncasecmp( cargv[ i ], BINDPSTR, strlen( BINDPSTR ))) {
	    val = cargv[ i ] + strlen( BINDPSTR ) + 1;
	    ri->ri_principal = strdup( val );
	} else if ( !strncasecmp( cargv[ i ], SRVTABSTR, strlen( SRVTABSTR ))) {
	    val = cargv[ i ] + strlen( SRVTABSTR ) + 1;
	    if ( ri->ri_srvtab != NULL ) {
		free( ri->ri_srvtab );
	    }
	    ri->ri_srvtab = strdup( val );
	} else {
	    fprintf( stderr, 
		    "Error: parse_replica_line: unknown keyword \"%s\"\n",
		    cargv[ i ] );
	}
    }
    if ( gots != GOT_ALL ) {
	    fprintf( stderr, "Error: Malformed \"replica\" line in slapd " );
	    fprintf( stderr, "config file, line %d\n", lineno );
	return -1;
    }
    return 0;
}

