/*
 * qstat 2.5c
 * by Steve Jankowski
 * steve@qstat.org
 * http://www.qstat.org
 *
 * Copyright 1996,1997,1998,1999 by Steve Jankowski
 *
 * Licensed under the Artistic License, see LICENSE.txt for license terms
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "config.h"

#ifdef __hpux
#define STATIC static
#else
#define STATIC
#endif

#ifdef _WIN32
#define strcasecmp      stricmp
#endif


#ifdef _WIN32
#define HOME_CONFIG_FILE "qstat.cfg"
#else
#define HOME_CONFIG_FILE ".qstatrc"
#endif


static server_type ** config_types;
static int n_config_types;
static int max_config_types;
static int next_gametype_id= LAST_BUILTIN_SERVER+1;

static int load_config_file( char *filename);
static int try_load_config_file( char *filename, int show_error);
static int pf_top_level( char *text, void *context);
static int pf_gametype_new( char *text, void *context);
static int pf_gametype_modify( char *text, void *context);

static int set_game_type_value( server_type *gametype, int key, char *value);
static int modify_game_type_value( server_type *gametype, int key, char *value);
static int set_packet_value( server_type *gametype, char *value,
	char *packet_name, char **packet, int *len);

static char *first_token( char *text);
static char *next_token();
static char *next_token_dup();
static char *next_value();
static char * parse_value( char *source, int len);
static unsigned int parse_hex( char *hex, int len, int *error);
static unsigned int parse_oct( char *oct, int len, int *error);
static char *force_lower_case( char *string);
static char *force_upper_case( char *string);
static char *get_token();
static void * memdup( void *mem, unsigned int len);

static int (*parse_func)( char *, void *);
static void *parse_context;
static int line;
static char *current_file_name;

static char *parse_text;
static char *parse_end;
static char *lex;
static char *token_end;
static char token_buf[1024];
static int value_len;

static int debug= 0;

#define REPORT_ERROR(a)		print_location(); fprintf a; fprintf(stderr, "\n")

#define CK_MASTER_PROTOCOL	1
#define CK_MASTER_QUERY		2
#define CK_MASTER_PACKET	3
#define CK_NAME			4
#define CK_DEFAULT_PORT		5
#define CK_GAME_RULE		6
#define CK_TEMPLATE_VAR		7
#define CK_MASTER_TYPE		8
#define CK_STATUS_PACKET	9
#define CK_STATUS2_PACKET	10
#define CK_PLAYER_PACKET	11
#define CK_RULE_PACKET		12
#define CK_PORT_OFFSET		13


typedef struct _config_key {
    int key;
    char *key_name;
} ConfigKey;

static ConfigKey new_keys[] = {
{ CK_MASTER_PROTOCOL, "master protocol" },
{ CK_MASTER_QUERY, "master query" },
{ CK_MASTER_PACKET, "master packet" },
{ CK_NAME, "name" },
{ CK_DEFAULT_PORT, "default port" },
{ CK_GAME_RULE, "game rule" },
{ CK_TEMPLATE_VAR, "template var" },
{ CK_MASTER_TYPE, "master for gametype" },
{ CK_STATUS_PACKET, "status packet" },
{ CK_STATUS2_PACKET, "status2 packet" },
{ CK_PLAYER_PACKET, "player packet" },
{ CK_RULE_PACKET, "rule packet" },
{ CK_PORT_OFFSET, "status port offset" },
{ 0, NULL },
};

static ConfigKey modify_keys[] = {
{ CK_MASTER_PROTOCOL, "master protocol" },
{ CK_MASTER_QUERY, "master query" },
{ CK_MASTER_PACKET, "master packet" },
{ 0, NULL },
};

static int get_config_key( char *first_token, ConfigKey *keys);
static void add_config_type( server_type *gametype);
static server_type * get_config_type( char *game_type);
static void copy_server_type( server_type *dest, server_type *source);
static server_type * get_server_type( char *game_type);

typedef struct _gametype_context {
    char *type;
    char *extend_type;
    server_type *gametype;
} GameTypeContext;


static void
print_location()
{
    fprintf( stderr, "%s line %d: ", current_file_name, line);
}

int
qsc_load_default_config_files()
{
    int rc= 0;
    char *filename= NULL, *var;
    char path[1024];

    var= getenv( "QSTAT_CONFIG");
    if ( var != NULL && var[0] != '\0')  {
	rc= try_load_config_file( var, 1);
	if ( rc == 0 || rc == -1)
	    return rc;
    }

    var= getenv( "HOME");
    if ( var != NULL && var[0] != '\0')  {
	int len= strlen(var);
	if ( len > 900)
	    len= 900;
	strncpy( path, var, len);
	path[len]= '\0';
	strcat( path, "/");
	strcat( path, HOME_CONFIG_FILE);
/*	sprintf( path, "%s/%s", var, HOME_CONFIG_FILE); */
	rc= try_load_config_file( path, 0);
	if ( rc == 0 || rc == -1)
	    return rc;
    }

#ifdef sysconfdir
    strcpy( path, sysconfdir "/qstat.cfg");
    filename= path;
#elif defined(_WIN32)
    if ( filename == NULL && _pgmptr && strchr( _pgmptr, '\\'))  {
	    char *slash= strrchr( _pgmptr, '\\');
	    strncpy( path, _pgmptr, slash - _pgmptr);
	    path[slash - _pgmptr]= '\0';
	    strcat( path, "\\qstat.cfg");
	    filename= path;
    }
#endif

    if ( filename != NULL)  {
	rc= try_load_config_file( filename, 0);
	if ( rc == 0 || rc == -1)
	    return rc;
    }
    return rc;
/*
    if ( rc == -2 && show_error)  {
	perror( filename);
	fprintf( stderr, "Error: Could not open config file \"%s\"\n",
		filename);
    }
    else if ( rc == -1 && show_error)
	fprintf( stderr, "Error: Error loading $QSTAT_CONFIG file\n");
    return rc;
*/

#ifdef foo
    filename= getenv( "HOME");
    if ( filename != NULL && filename[0] != '\0')  {
	char path[1024];
	sprintf( path, "%s/%s", filename, HOME_CONFIG_FILE);
    }
/* 1. $QSTAT_CONFIG
   2. UNIX: $HOME/.qstatrc         WIN: $HOME/qstat.cfg
   3. UNIX: sysconfdir/qstat.cfg   WIN: qstat.exe-dir/qstat.cfg
*/

    rc= load_config_file( "qstat.cfg");
    if ( rc == -1)
	fprintf( stderr, "Warning: Error loading default qstat.cfg\n");
    return 0;
#endif
}

int
qsc_load_config_file( char *filename)
{
    int rc= load_config_file( filename);
    if ( rc == -2)  {
	perror( filename);
	fprintf( stderr, "Could not open config file \"%s\"\n",
		filename);
	return -1;
    }
    return rc;
}

server_type **
qsc_get_config_server_types( int *n_config_types_ref)
{
    if ( n_config_types_ref)
	*n_config_types_ref= n_config_types;
    return config_types;
}

STATIC int
try_load_config_file( char *filename, int show_error)
{
    int rc= load_config_file( filename);
    if ( rc == -2 && show_error)  {
	perror( filename);
	fprintf( stderr, "Error: Could not open config file \"%s\"\n",
		filename);
    }
    else if ( rc == -1)
	fprintf( stderr, "Error: Could not load config file \"%s\"\n",
		filename);
    return rc;
}

STATIC int
load_config_file( char *filename)
{
    FILE *file;
    int rc;

    file= fopen( filename, "r");
    if ( file == NULL)
	return -2;

    current_file_name= filename;
    rc= parse_config_file( file);
    fclose(file);
    return rc;
}

int
parse_config_file( FILE *file)
{
    char buf[4096];
    int rc;

    line= 0;
    parse_func= pf_top_level;

    while ( fgets( buf, sizeof(buf), file) != NULL)  {
	int len= strlen(buf);
	while ( len && (buf[len-1] == '\n' || buf[len-1] == '\r'))
	   len--;
	buf[len]= '\0';
	line++;
	rc= parse_func( buf, parse_context);
	if ( rc != 0)
	    return rc;
    }
    return 0;
}


/* Top level
 *   Keywords: gametype
 */
STATIC int
pf_top_level( char *text, void *_context)
{
    GameTypeContext *context;
    server_type *extend;
    char *token, *game_type, *extend_type;
    token= first_token( text);
    if ( token == NULL)
	return 0;

    if ( strcmp( token, "gametype") != 0)  {
	REPORT_ERROR(( stderr, "Unknown config command \"%s\"", token));
	return -1;
    }

    game_type= next_token_dup();
    if ( game_type == NULL)  {
	REPORT_ERROR(( stderr, "Missing game type"));
	return -1;
    }

    force_lower_case( game_type);

    token= next_token();
    if ( token == NULL)  {
	REPORT_ERROR(( stderr, "Expecting \"new\" or \"modify\""));
	return -1;
    }

    if ( strcmp( token, "new") == 0)  {
	parse_func= pf_gametype_new;
    }
    else if ( strcmp( token, "modify") == 0)  {
	parse_func= pf_gametype_modify;
    }
    else  {
	REPORT_ERROR(( stderr, "Expecting \"new\" or \"modify\""));
	return -1;
    }

    context= (GameTypeContext*) malloc( sizeof(GameTypeContext));
    context->type= game_type;
    context->extend_type= NULL;
    context->gametype= NULL;

    token= next_token();

    if ( parse_func == pf_gametype_modify)  {
	if ( token != NULL)  {
	    REPORT_ERROR(( stderr, "Extra text after gametype modify"));
	    return -1;
	}
	context->gametype= get_server_type( game_type);
	if ( context->gametype == NULL)  {
	    REPORT_ERROR(( stderr, "Unknown game type \"%s\"", game_type));
	    free(context);
	    return -1;
	}
	parse_context= context;
	return 0;
    }

    if ( token == NULL || strcmp( token, "extend") != 0)  {
	REPORT_ERROR(( stderr, "Expecting \"extend\""));
	return -1;
    }

    extend_type= next_token();

    if ( extend_type == NULL)  {
	REPORT_ERROR(( stderr, "Missing extend game type"));
	return -1;
    }

    force_lower_case( extend_type);

    if ( strcasecmp( extend_type, game_type) == 0)  {
	REPORT_ERROR(( stderr, "Identical game type and extend type"));
	return -1;
    }

    context->extend_type= extend_type;
    extend= get_server_type( extend_type);
    if ( extend == NULL)  {
	REPORT_ERROR(( stderr, "Unknown extend game type \"%s\"", extend_type));
	return -1;
    }

    /* Over-write a previous gametype new */
    context->gametype= get_config_type( game_type);
    if ( context->gametype == NULL)
	context->gametype= (server_type*) malloc( sizeof(server_type));
    copy_server_type( context->gametype, extend);

    /* Set flag for new type-id if not re-defining previous config type */
    if ( get_config_type( game_type) == NULL)
	context->gametype->id= 0;

    context->gametype->type_string= game_type;
    context->gametype->type_prefix= strdup(game_type);
    force_upper_case( context->gametype->type_prefix);
    context->gametype->type_option= (char*)malloc( strlen(game_type)+2);
    context->gametype->type_option[0]= '-';
    strcpy( &context->gametype->type_option[1], game_type);

    parse_context= context;

    return 0;
}

STATIC int
pf_gametype_modify( char *text, void *_context)
{
    GameTypeContext *context= (GameTypeContext*) _context;
    char *token;
    int key;

    token= first_token( text);
    if ( token == NULL)
	return 0;

    if ( strcmp( token, "end") == 0)  {
	parse_func= pf_top_level;
	parse_context= NULL;
	return 0;
    }

    key= get_config_key( token, modify_keys);

    token= next_token();

    if ( strcmp( token, "=") != 0)  {
	REPORT_ERROR(( stderr, "Expecting \"=\", found \"%s\"", token));
	return -1;
    }

    token= next_value();
    if ( token == NULL)  {
	REPORT_ERROR(( stderr, "Missing value after \"=\""));
	return -1;
    }

    if ( debug)
	printf( "%d %s = <%s>\n", key, modify_keys[key-1].key_name,
		token?token:"");

    return modify_game_type_value( context->gametype, key, token);
}

STATIC int
pf_gametype_new( char *text, void *_context)
{
    GameTypeContext *context= (GameTypeContext*) _context;
    char *token;
    int key;

    token= first_token( text);
    if ( token == NULL)
	return 0;

    if ( strcmp( token, "end") == 0)  {
	add_config_type( context->gametype);
	parse_func= pf_top_level;
	parse_context= NULL;
	return 0;
    }

    key= get_config_key( token, new_keys);

    if ( key <= 0)
	return key;

    token= next_token();
    if ( strcmp( token, "=") != 0)  {
	REPORT_ERROR(( stderr, "Expecting \"=\", found \"%s\"", token));
	return -1;
    }

    token= next_value();
    if ( token == NULL && (key != CK_MASTER_PROTOCOL && key != CK_MASTER_QUERY))  {
	REPORT_ERROR(( stderr, "Missing value after \"=\""));
	return -1;
    }

    if ( debug)
	printf("%d %s = <%s>\n", key, new_keys[key-1].key_name, token?token:"");

    return set_game_type_value( context->gametype, key, token);
}

STATIC int
get_config_key( char *first_token, ConfigKey *keys)
{
    char key_name[1024], *token;
    int key= 0;

    strcpy( key_name, first_token);
    do  {
	int k;
	for ( k= 0; keys[k].key_name; k++)  {
	    if ( strcmp( keys[k].key_name, key_name) == 0)  {
		key= keys[k].key;
		break;
	    }
	}
	if ( key)
	    break;
	token= next_token();
	if ( token == NULL || strcmp( token, "=") == 0)
	    break;
	if ( strlen(key_name) + strlen(token) > sizeof(key_name)-2)  {
	    REPORT_ERROR(( stderr, "Key name too long"));
	    return -1;
	}
	strcat( key_name, " ");
	strcat( key_name, token);
    } while ( 1);

    if ( key == 0)  {
	REPORT_ERROR(( stderr, "Unknown config key \"%s\"", key_name));
	return -1;
    }

    return key;
}

STATIC int
set_game_type_value( server_type *gametype, int key, char *value)
{
    switch ( key)  {
    case CK_NAME:
	gametype->game_name= strdup(value);
	break;
    case CK_DEFAULT_PORT: {
	unsigned short port;
	if ( sscanf( value, "%hu", &port) != 1)  {
	    REPORT_ERROR(( stderr, "Syntax error on port.  Should be a number between 1 and 65535."));
	    return -1;
	}
	gametype->default_port= port;
	break;
	}
    case CK_GAME_RULE:
	gametype->game_rule= strdup(value);
	break;
    case CK_TEMPLATE_VAR:
	gametype->template_var= strdup(value);
	force_upper_case( gametype->template_var);
	break;
    case CK_MASTER_PROTOCOL:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master protocol on non-master game type\n"));
	    return -1;
	}
	if ( value)
	    gametype->master_protocol= strdup(value);
	else
	    gametype->master_protocol= NULL;
	break;
    case CK_MASTER_QUERY:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master query on non-master game type\n"));
	    return -1;
	}
	if ( value)
	    gametype->master_query= strdup(value);
	else
	    gametype->master_query= NULL;
	break;
    case CK_MASTER_TYPE:
	{
	server_type *type;
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master type on non-master game type\n"));
	    return -1;
	}
	force_lower_case( value);
	type= get_server_type( value);
	if ( type == NULL)  {
	    REPORT_ERROR((stderr, "Unknown server type \"%s\"\n", value));
	    return -1;
	}
	gametype->master= type->id;
	}
	break;
    case CK_MASTER_PACKET:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master packet on non-master game type"));
	    return -1;
	}
	if ( value == NULL || value[0] == '\0')  {
	    REPORT_ERROR((stderr, "Empty master packet"));
	    return -1;
	}
	gametype->master_packet= memdup( value, value_len);
	gametype->master_len= value_len;
	break;
    case CK_STATUS_PACKET:
	return set_packet_value( gametype, value, "status",
		&gametype->status_packet, &gametype->status_len);
    case CK_STATUS2_PACKET:
	return set_packet_value( gametype, value, "status2",
		&gametype->rule_packet, &gametype->rule_len);
    case CK_PLAYER_PACKET:
	return set_packet_value( gametype, value, "player",
		&gametype->player_packet, &gametype->player_len);
    case CK_RULE_PACKET:
	return set_packet_value( gametype, value, "rule",
		&gametype->rule_packet, &gametype->rule_len);
    case CK_PORT_OFFSET:  {
	short port;
	if ( sscanf( value, "%hd", &port) != 1)  {
	    REPORT_ERROR(( stderr, "Syntax error on port.  Should be a number between 32767 and -32768."));
	    return -1;
	}
	gametype->port_offset= port;
	break;
	}
    }

    return 0;
}

STATIC int
set_packet_value( server_type *gametype, char *value, char *packet_name,
	char **packet, int *len)
{
    if ( gametype->master)  {
	REPORT_ERROR((stderr, "Cannot set info packet on master game type"));
	return -1;
    }
	if ( value == NULL || value[0] == '\0')  {
	    REPORT_ERROR((stderr, "Empty %s packet", packet_name));
	    return -1;
	}
	if ( *packet == NULL)  {
	    REPORT_ERROR((stderr, "Cannot set %s packet; extend game type does not define a %s packet", packet_name, packet_name));
	    return -1;
	}
	*packet= memdup( value, value_len);
	*len= value_len;
    return 0;
}

STATIC int
modify_game_type_value( server_type *gametype, int key, char *value)
{
    switch ( key)  {
    case CK_MASTER_PROTOCOL:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master protocol on non-master game type"));
	    return -1;
	}
	if ( value)
	    gametype->master_protocol= strdup(value);
	else
	    gametype->master_protocol= NULL;
	break;
    case CK_MASTER_QUERY:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master query on non-master game type"));
	    return -1;
	}
	if ( value)
	    gametype->master_query= strdup(value);
	else
	    gametype->master_query= NULL;
	break;
    case CK_MASTER_PACKET:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master packet on non-master game type"));
	    return -1;
	}
	if ( value == NULL || value[0] == '\0')  {
	    REPORT_ERROR((stderr, "Empty master packet"));
	    return -1;
	}
	gametype->master_packet= memdup( value, value_len);
	break;
    }
    return 0;
}

STATIC server_type *
get_server_type( char *game_type)
{
    server_type *result;
    result= find_server_type_string( game_type);
    if ( result != NULL)
	return result;

    return get_config_type( game_type);
}

STATIC void
add_config_type( server_type *gametype)
{
    if ( gametype->id == 0)  {
	if ( next_gametype_id >= MASTER_SERVER)  {
	    REPORT_ERROR(( stderr, "Exceeded game type limit, ignoring \"%s\"",
		gametype->type_string));
	    return;
	}
	gametype->id= next_gametype_id;
	if ( gametype->master)
	    gametype->id |= MASTER_SERVER;
	next_gametype_id++;
    }

    if ( max_config_types == 0)  {
	max_config_types= 4;
	config_types= (server_type**) malloc(sizeof(server_type*) * (max_config_types + 1));
    }
    else if ( n_config_types >= max_config_types)  {
	max_config_types*= 2;
	config_types= (server_type**) realloc( config_types,
		sizeof(server_type*) * (max_config_types + 1));
    }
    config_types[ n_config_types]= gametype;
    n_config_types++;
}

STATIC server_type *
get_config_type( char *game_type)
{
    server_type **search= config_types;
    int i;
    for ( i= 0; i < n_config_types; i++)
	if ( strcmp( config_types[i]->type_string, game_type) == 0)
	    return config_types[i];

    return NULL;
}

STATIC void
copy_server_type( server_type *dest, server_type *source)
{
    *dest= *source;
}

STATIC void *
memdup( void *mem, unsigned int len)
{
    void *result= malloc( len);
    memcpy( result, mem, len);
    return result;
}

/* Parsing primitives
 */

STATIC char *
first_token( char *text)
{
    parse_text= text;
    parse_end= parse_text + strlen( parse_text);
    lex= text;
    token_end= text;

    if ( *lex == '#')
	return NULL;

    return get_token();
}

STATIC char *
next_token()
{
    lex= token_end;
    return get_token();
}

STATIC char *
next_token_dup()
{
    return strdup( next_token());
}

STATIC char *
get_token()
{
    char *token= &token_buf[0];
    while ( isspace(*token_end))
	token_end++;

    if ( token_end == parse_end)
	return NULL;

    while ( (isalnum(*token_end) || *token_end == '.' || *token_end == '_' || *token_end == '-')
		&& token < &token_buf[0] + sizeof(token_buf))
	*token++= *token_end++;

    if ( token == &token_buf[0])
	*token++= *token_end++;
    *token= '\0';
    return &token_buf[0];
}

extern void print_packet( struct qserver *server, char *buf, int buflen);

STATIC char *
next_value()
{
    char *token= &token_buf[0];
    while ( isspace(*token_end))
	token_end++;

    if ( token_end == parse_end)
	return NULL;

    while ( token_end < parse_end && token < &token_buf[0] + sizeof(token_buf))
	*token++= *token_end++;

    *token--= '\0';

    if ( strchr( token_buf, '\\'))
	return parse_value(token_buf, (token - token_buf)+1);

    while ( isspace(*token))
	*token--= '\0';
 
    value_len= token - token_buf + 1;

    return &token_buf[0];
}

STATIC char *
parse_value( char *source, int len)
{
    char *value, *v, *end;
    int error= 0, rc;
    unsigned int n;

    value= (char*)malloc( len + 1);
    v= value;

/*
printf( "parse_value <%.*s>\n", len, source);
*/

    for ( ; len; len--, source++)  {
	if ( *source != '\\')  {
	    *v++= *source;
	    if ( *source != ' ')
		end= v;
	    continue;
	}
	source++; len--;
	if ( len == 0)
	    break;
	if ( *source == '\\')
	    *v++= *source;
	else if ( *source == 'n')
	    *v++= '\n';
	else if ( *source == 'r')
	    *v++= '\r';
	else if ( *source == ' ')
	    *v++= ' ';
	else if ( *source == 'x')  {
	    source++; len--;
	    if ( len < 2)
		break;
	    *v++= parse_hex(source, 2, &error);
	    if ( error)
		break;
	    source++; len--;
	}
	else if ( isdigit( *source))  {
	    if ( len < 3)
		break;
	    *v++= parse_oct(source, 3, &error);
	    if ( error)
		break;
	    source++; len--;
	    source++; len--;
	}
	else  {
	    error= 1;
	    REPORT_ERROR(( stderr, "Invalid character escape \"%.*s\"", 2,
		source-1));
	    break;
	}
	end= v;
    }

    if ( error)  {
	free(value);
	return NULL;
    }

    value_len= end - value;
    memcpy( token_buf, value, value_len);
    token_buf[value_len]= '\0';

/*
print_packet(NULL, token_buf, value_len);
*/

    free(value);
    return &token_buf[0];
}

STATIC unsigned int
parse_hex( char *hex, int len, int *error)
{
    char *save_hex= hex;
    int save_len= len;
    unsigned int result= 0;
    *error= 0;
    for ( ; len; len--, hex++)  {
	result <<= 4;
	if ( ! isxdigit( *hex))  {
	    *error= 1;
	    REPORT_ERROR(( stderr, "Invalid hex \"%.*s\"", save_len+2,
		save_hex-2));
	    return 0;
	}
	if ( *hex >= '0' && *hex <= '9')
	    result|= *hex - '0';
	else if ( *hex >= 'A' && *hex <= 'F')
	    result|= ((*hex - 'A') + 10);
	else if ( *hex >= 'a' && *hex <= 'f')
	    result|= ((*hex - 'a') + 10);
    }
    return result;
}

STATIC unsigned int
parse_oct( char *oct, int len, int *error)
{
    char *save_oct= oct;
    int save_len= len;
    unsigned int result= 0;
    *error= 0;
    for ( ; len; len--, oct++)  {
	result <<= 3;
	if ( *oct >= '0' && *oct <= '7')
	    result|= *oct - '0';
	else  {
	    *error= 1;
	    REPORT_ERROR(( stderr, "Invalid octal \"%.*s\"", save_len+1,
		save_oct-1));
	    return 0;
	}
    }
    return result;
}

STATIC char *
force_lower_case( char *string)
{
    char *s= string;
    for ( ; *s; s++)
	*s= tolower(*s);
    return string;
}

STATIC char *
force_upper_case( char *string)
{
    char *s= string;
    for ( ; *s; s++)
	*s= toupper(*s);
    return string;
}

