/* $OpenLDAP: pkg/ldap/servers/slurpd/config.c,v 1.47.2.1 2007/02/13 20:22:32 kurt Exp $ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 1998-2007 The OpenLDAP Foundation.
 * Portions Copyright 2003 Mark Benson.
 * Portions Copyright 2002 John Morrissey.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
 */
/* Portions 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.
 */
/* ACKNOWLEDGEMENTS:
 * This work was originally developed by the University of Michigan
 * (as part of U-MICH LDAP).  Additional signficant contributors
 * include:
 *    John Morrissey
 *    Mark Benson
 */


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

#include "portable.h"

#include <stdio.h>

#include <ac/stdlib.h>
#include <ac/string.h>
#include <ac/socket.h>
#include <ac/ctype.h>

#include <ldap.h>
#include <lutil.h>

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

#define ARGS_STEP	512

/* Forward declarations */
static void	add_replica LDAP_P(( char **, int ));
static int	parse_replica_line LDAP_P(( char **, int, Ri *));
static void	parse_line LDAP_P(( char * ));
static char	*slurpd_getline LDAP_P(( FILE * ));
static char	*strtok_quote LDAP_P(( char *, char * ));

int	cargc = 0, cargv_size = 0;
char	**cargv;
/* current config file line # */
static int	lineno;

char *slurpd_pid_file = NULL;
char *slurpd_args_file = NULL;

/*
 * 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	*line;

#define GOT_REPLOG_NO		(0)
#define GOT_REPLOG_ONE		(1)
#define GOT_REPLOG_YES		(2)	
#define GOT_REPLOG_DONE		(3)
#define	GOT_REPLOG_MASK		(0xF)
#define	GOT_REPLOG(i)		((i) & GOT_REPLOG_MASK)
#define	GOT_REPLOG_SET(i,v)	((i) = ((i) & ~GOT_REPLOG_MASK) | ((v) & GOT_REPLOG_MASK))

#define GOT_REPLOG_PID		(0x10)
#define GOT_REPLOG_ARGS		(0x20)
#define GOT_REPLOG_INTERVAL	(0x40)
	int	got_replog =	GOT_REPLOG_NO;

	/*
	 * replica-pidfile and replica-argsfile can appear before any replog;
	 * in this case they're global (legacy behavior); otherwise, since
	 * each replog needs a slurpd, they can appear after a replogfile line;
	 * in that case, the replog specific values are used.
	 */

	if ( cargv == NULL ) {
		cargv = ch_calloc( ARGS_STEP + 1, sizeof(*cargv) );
		cargv_size = ARGS_STEP + 1;
	}

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

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

	lineno = 0;
	while ( (line = slurpd_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 );

		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 ( cargc < 2 ) {
				fprintf( stderr,
					"line %d: missing filename in \"replogfile ",
					lineno );
				fprintf( stderr, "<filename>\" line\n" );
				exit( EXIT_FAILURE );

			} 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" );
			}

			LUTIL_SLASHPATH( cargv[1] );
			if ( sglob->slapd_replogfile[0] == '\0' ) {
				strcpy( sglob->slapd_replogfile, cargv[1] );
				GOT_REPLOG_SET(got_replog, GOT_REPLOG_YES);

			} else {
				if ( strcmp( sglob->slapd_replogfile, cargv[1] ) == 0 ) {
					GOT_REPLOG_SET(got_replog, GOT_REPLOG_YES);

				} else if ( GOT_REPLOG(got_replog) == GOT_REPLOG_YES ) {
					GOT_REPLOG_SET(got_replog, GOT_REPLOG_DONE);

				} else {
					GOT_REPLOG_SET(got_replog, GOT_REPLOG_ONE);
				}
			}

		} else if ( strcasecmp( cargv[0], "replica" ) == 0 ) {
			add_replica( cargv, cargc );
	    
		/* include another config file */
		} else if ( strcasecmp( cargv[0], "include" ) == 0 ) {
			char *savefname;
			int savelineno;

			if ( cargc < 2 ) {
				Debug( LDAP_DEBUG_ANY,
					"%s: line %d: missing filename in \"include <filename>\" line\n",
					fname, lineno, 0 );
		
				fclose( fp );
				return( 1 );
			}
			LUTIL_SLASHPATH( cargv[1] );
			savefname = strdup( cargv[1] );
			savelineno = lineno;
	    
			if ( slurpd_read_config( savefname ) != 0 ) {
				fclose( fp );
				return( 1 );
			}
		
			free( savefname );
			lineno = savelineno - 1;

		} else if ( strcasecmp( cargv[0], "replica-pidfile" ) == 0 ) {
			if ( cargc < 2 ) {
				Debug( LDAP_DEBUG_ANY,
					"%s: line %d: missing file name in \"replica-pidfile <file>\" line\n",
					fname, lineno, 0 );

				fclose( fp );
				return( 1 );
			}

			switch ( GOT_REPLOG(got_replog) ) {
			case GOT_REPLOG_YES:
				Debug( LDAP_DEBUG_CONFIG, "%s: line %d: "
					"got replog specific replica-pidfile \"%s\".\n",
					fname, lineno, cargv[1] );
			case GOT_REPLOG_NO:
				LUTIL_SLASHPATH( cargv[1] );
				if ( slurpd_pid_file != NULL ) {
					ch_free( slurpd_pid_file );
				}
				slurpd_pid_file = ch_strdup( cargv[1] );
				got_replog |= GOT_REPLOG_PID;
				break;

			default:
				Debug( LDAP_DEBUG_CONFIG, "%s: line %d: "
					"replica-pidfile \"%s\" not mine.\n",
					fname, lineno, cargv[1] );
				break;
			}

		} else if ( strcasecmp( cargv[0], "replica-argsfile" ) == 0 ) {
			if ( cargc < 2 ) {
				Debug( LDAP_DEBUG_ANY,
					"%s: line %d: missing file name in \"argsfile <file>\" line\n",
					fname, lineno, 0 );

				fclose( fp );
				return( 1 );
			}

			switch ( GOT_REPLOG(got_replog) ) {
			case GOT_REPLOG_YES:
				Debug( LDAP_DEBUG_CONFIG, "%s: line %d: "
					"got replog specific replica-argsfile \"%s\".\n",
					fname, lineno, cargv[1] );
			case GOT_REPLOG_NO:
				LUTIL_SLASHPATH( cargv[1] );
				if ( slurpd_args_file != NULL ) {
					ch_free( slurpd_args_file );
				}
				slurpd_args_file = ch_strdup( cargv[1] );
				got_replog |= GOT_REPLOG_ARGS;
				break;

			default:
				Debug( LDAP_DEBUG_CONFIG, "%s: line %d: "
					"replica-argsfile \"%s\" not mine.\n",
					fname, lineno, cargv[1] );
				break;
			}

		} else if ( strcasecmp( cargv[0], "replicationinterval" ) == 0 ) {
			int c;

			if ( cargc < 2 ) {
				Debug( LDAP_DEBUG_ANY, "%s: line %d: missing interval in "
					"\"replicationinterval <seconds>\" line\n",
					fname, lineno, 0 );
				fclose( fp );
				return( 1 );
			}

			if ( lutil_atoi( &c, cargv[1] ) != 0 || c < 1 ) {
				Debug( LDAP_DEBUG_ANY, "%s: line %d: invalid interval "
					"(%d) in \"replicationinterval <seconds>\" line\n",
					fname, lineno, c );

				fclose( fp );
				return( 1 );
			}

			switch ( GOT_REPLOG(got_replog) ) {
			case GOT_REPLOG_YES:
				Debug( LDAP_DEBUG_CONFIG, "%s: line %d: "
					"got replog specific replicationinterval \"%s\".\n",
					fname, lineno, cargv[1] );
			case GOT_REPLOG_NO:
				sglob->no_work_interval = c;
				got_replog |= GOT_REPLOG_INTERVAL;
				break;

			default:
				Debug( LDAP_DEBUG_CONFIG, "%s: line %d: "
					"replicationinterval \"%s\" not mine.\n",
					fname, lineno, cargv[1] );
				break;
			}
		}
	}

	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
)
{
    char *	token;

    cargc = 0;
    for ( token = strtok_quote( line, " \t" ); token != NULL;
	token = strtok_quote( NULL, " \t" ) )
    {
        if ( cargc == cargv_size - 1 ) {
	    char **tmp;
            tmp = ch_realloc( cargv, (cargv_size + ARGS_STEP) *
                               sizeof(*cargv) );
	    if (tmp == NULL) {
		cargc = 0;
		return;
	    }
	    cargv = tmp;
            cargv_size += ARGS_STEP;
        }

	cargv[cargc++] = token;
    }
    cargv[cargc] = 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;
	    }
	    AC_MEMCPY( next, next + 1, strlen( next + 1 ) + 1 );
	    break;

	case '\\':
	    if ( next[1] )
		AC_MEMCPY( next, next + 1, strlen( next + 1 ) + 1 );
	    next++;		/* dont parse the escaped character */
	    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 *
slurpd_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 ) {
		if( p > buf && p[-1] == '\r' ) --p;       
		*p = '\0';
	}
	lineno++;
	if ( ! isspace( (unsigned char) buf[0] ) ) {
	    return( line );
	}

	/* change leading whitespace to space */
	buf[0] = ' ';

	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( EXIT_FAILURE );
    }
    sglob->replicas[ nr ] = NULL; 

    if ( Ri_init( &(sglob->replicas[ nr - 1 ])) < 0 ) {
	fprintf( stderr, "out of memory, Ri_init\n" );
	exit( EXIT_FAILURE );
    }
    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( EXIT_FAILURE );
	}
    }
}



/* 
 * Parse a "replica" line from the config file.  replica lines should be
 * in the following format:
 * replica    host=<hostname:portnumber> binddn=<binddn>
 *            bindmethod="simple" 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 "simple", and
 *
 * <creds> are the credentials (e.g. password) for binddn.  <creds> are
 * only used for bindmethod=simple.  
 *
 * 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 )
#define	GOT_MECH	8

static int
parse_replica_line( 
    char	**cargv,
    int		cargc,
    Ri		*ri
)
{
    int		gots = 0;
    int		i;
    char	*hp, *val;
    LDAPURLDesc *ludp;

    for ( i = 1; i < cargc; i++ ) {
	if ( !strncasecmp( cargv[ i ], HOSTSTR, sizeof( HOSTSTR ) - 1 ) ) {
		if ( gots & GOT_HOST ) {
			fprintf( stderr, "Error: Malformed \"replica\" line in slapd config " );
			fprintf( stderr, "file, too many host or uri names specified, line %d\n",
				lineno );
			return -1;
		}	
	    val = cargv[ i ] + sizeof( HOSTSTR ); /* '\0' string terminator accounts for '=' */
	    if (( hp = strchr( val, ':' )) != NULL ) {
		*hp = '\0';
		hp++;
		if ( lutil_atoi( &ri->ri_port, hp ) != 0 ) {
		    fprintf( stderr, "unable to parse port \"%s\", line %d\n",
			    hp, lineno );
		    return -1;
		}
	    }
	    if ( ri->ri_port <= 0 ) {
		ri->ri_port = LDAP_PORT;
	    }
	    ri->ri_hostname = strdup( val );
	    gots |= GOT_HOST;
	} else if ( !strncasecmp( cargv[ i ], URISTR, sizeof( URISTR ) - 1 ) ) {
		if ( gots & GOT_HOST ) {
			fprintf( stderr, "Error: Malformed \"replica\" line in slapd config " );
			fprintf( stderr, "file, too many host or uri names specified, line %d\n",
				lineno );
			return -1;
		}		
		if ( ldap_url_parse( cargv[ i ] + sizeof( URISTR ), &ludp ) != LDAP_SUCCESS ) {
			fprintf( stderr, "Error: Malformed \"replica\" line in slapd config " );
			fprintf( stderr, "file, bad uri format specified, line %d\n",
				lineno );
			return -1;
		}
		if (ludp->lud_host == NULL) {
			fprintf( stderr, "Error: Malformed \"replica\" line in slapd config " );
			fprintf( stderr, "file, missing uri hostname, line %d\n",
				lineno );
			ldap_free_urldesc( ludp );				
			return -1;
		}
		ri->ri_hostname = strdup ( ludp->lud_host );
		ri->ri_port = ludp->lud_port;
		ri->ri_uri = strdup ( cargv[ i ] + sizeof( URISTR ) );		
		ldap_free_urldesc( ludp );				
	    gots |= GOT_HOST;
	} else if ( !strncasecmp( cargv[ i ], 
			ATTRSTR, sizeof( ATTRSTR ) - 1 ) ) {
	    /* ignore it */ ;
	} else if ( !strncasecmp( cargv[ i ], 
			SUFFIXSTR, sizeof( SUFFIXSTR ) - 1 ) ) {
	    /* ignore it */ ;
	} else if ( !strncasecmp( cargv[i], STARTTLSSTR, sizeof(STARTTLSSTR)-1 )) {
	    val = cargv[ i ] + sizeof( STARTTLSSTR );
		if( !strcasecmp( val, CRITICALSTR ) ) {
			ri->ri_tls = TLS_CRITICAL;
		} else {
			ri->ri_tls = TLS_ON;
		}
	} else if ( !strncasecmp( cargv[ i ], TLSSTR, sizeof( TLSSTR ) - 1 ) ) {
	    val = cargv[ i ] + sizeof( TLSSTR );
		if( !strcasecmp( val, CRITICALSTR ) ) {
			ri->ri_tls = TLS_CRITICAL;
		} else {
			ri->ri_tls = TLS_ON;
		}
	} else if ( !strncasecmp( cargv[ i ],
			BINDDNSTR, sizeof( BINDDNSTR ) - 1 ) ) { 
	    val = cargv[ i ] + sizeof( BINDDNSTR );
	    ri->ri_bind_dn = strdup( val );
	    gots |= GOT_DN;
	} else if ( !strncasecmp( cargv[ i ], BINDMETHSTR,
		sizeof( BINDMETHSTR ) - 1 ) ) {
	    val = cargv[ i ] + sizeof( BINDMETHSTR );
	    if ( !strcasecmp( val, SIMPLESTR )) {
		ri->ri_bind_method = LDAP_AUTH_SIMPLE;
		gots |= GOT_METHOD;
	    } else if ( !strcasecmp( val, SASLSTR )) {
		ri->ri_bind_method = LDAP_AUTH_SASL;
		gots |= GOT_METHOD;
	    } else {
		ri->ri_bind_method = -1;
	    }
	} else if ( !strncasecmp( cargv[ i ], 
			SASLMECHSTR, sizeof( SASLMECHSTR ) - 1 ) ) {
	    val = cargv[ i ] + sizeof( SASLMECHSTR );
	    gots |= GOT_MECH;
	    ri->ri_saslmech = strdup( val );
	} else if ( !strncasecmp( cargv[ i ], 
			CREDSTR, sizeof( CREDSTR ) - 1 ) ) {
	    val = cargv[ i ] + sizeof( CREDSTR );
	    ri->ri_password = strdup( val );
	} else if ( !strncasecmp( cargv[ i ], 
			SECPROPSSTR, sizeof( SECPROPSSTR ) - 1 ) ) {
	    val = cargv[ i ] + sizeof( SECPROPSSTR );
	    ri->ri_secprops = strdup( val );
	} else if ( !strncasecmp( cargv[ i ], 
			REALMSTR, sizeof( REALMSTR ) - 1 ) ) {
	    val = cargv[ i ] + sizeof( REALMSTR );
	    ri->ri_realm = strdup( val );
	} else if ( !strncasecmp( cargv[ i ], 
			AUTHCSTR, sizeof( AUTHCSTR ) - 1 ) ) {
	    val = cargv[ i ] + sizeof( AUTHCSTR );
	    ri->ri_authcId = strdup( val );
	} else if ( !strncasecmp( cargv[ i ], 
			OLDAUTHCSTR, sizeof( OLDAUTHCSTR ) - 1 ) ) {
	    /* Old authcID is provided for some backwards compatibility */
	    val = cargv[ i ] + sizeof( OLDAUTHCSTR );
	    ri->ri_authcId = strdup( val );
	} else if ( !strncasecmp( cargv[ i ], 
			AUTHZSTR, sizeof( AUTHZSTR ) - 1 ) ) {
	    val = cargv[ i ] + sizeof( AUTHZSTR );
	    ri->ri_authzId = strdup( val );
	} else {
	    fprintf( stderr, 
		    "Error: parse_replica_line: unknown keyword \"%s\"\n",
		    cargv[ i ] );
	}
    }
    
	if ( ri->ri_bind_method == LDAP_AUTH_SASL) {
		if ((gots & GOT_MECH) == 0) {
			fprintf( stderr, "Error: \"replica\" line needs SASLmech flag in " );
			fprintf( stderr, "slapd config file, line %d\n", lineno );
			return -1;
		}
	} else if ( gots != GOT_ALL ) {
		fprintf( stderr, "Error: Malformed \"replica\" line in slapd " );
		fprintf( stderr, "config file, line %d\n", lineno );
		return -1;
	}
    return 0;
}

