/* $Id$ */
/* Copyright (c) 2011 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Servers inetd */
/* 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, version 3 of the License.
 *
 * 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, see <http://www.gnu.org/licenses/>. */



#include <sys/types.h>
#include <errno.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include "scanner.h"
#include "service.h"
#include "inetd.h"
#include "parser.h"


/* types */
typedef struct _State
{
	char const * filename;
	FILE * fp;
	Token * token;
	unsigned int line;
	Config * config;
	Service service;
} State;


/* functions */
/* parser */
static void _config(State * state);

Config * parser(char const * filename)
{
	State state;

	if((state.config = config_new()) == NULL)
		return NULL;
	if((state.fp = fopen(filename, "r")) == NULL)
	{
		inetd_error(filename, 0);
		config_delete(state.config);
		return NULL;
	}
	if((state.token = scan(state.fp)) == NULL)
	{
		fclose(state.fp);
		config_delete(state.config);
		return NULL;
	}
	state.filename = filename;
	state.line = 1;
	_config(&state);
	token_delete(state.token);
	fclose(state.fp);
	return state.config;
}

static void _parser_error(State * state, char const * message)
{
	fprintf(stderr, "%s%s%d%s%s\n", state->filename, ", line ", state->line,
			": ", message);
}

static void _parser_scan(State * state)
{
	token_delete(state->token);
	state->token = scan(state->fp);
}

static int _parser_check(State * state, TokenCode code)
{
	int ret = 0;

	if(state->token == NULL || state->token->code != code)
		_parser_error(state, "parse error"); /* FIXME tell expected */
	else
		ret = 1;
	_parser_scan(state);
	return ret;
}

static void _newline(State * state);
static void _service(State * state);
static void _config(State * state)
	/* { newline | service } */
{
	for(;;)
	{
		if(token_in_set(state->token, TS_NEWLINE))
			_newline(state);
		else if(token_in_set(state->token, TS_SERVICE))
			_service(state);
		else if(state->token->code == TC_EOF)
			break;
		else
		{
			/* FIXME be verbose */
			break;
		}
	}
}

static void _space(State * state);
static void _newline(State * state)
	/* [ space ] NEWLINE */
{
	if(token_in_set(state->token, TS_SPACE))
		_space(state);
	if(_parser_check(state, TC_NEWLINE))
		state->line++;
}

static void _space(State * state)
	/* SPACE { SPACE } */
{
	_parser_check(state, TC_SPACE);
	while(state->token->code == TC_SPACE)
		_parser_scan(state);
}

static void _service_name(State * state);
static void _socket(State * state);
static void _protocol(State * state);
static void _wait(State * state);
static void _id(State * state);
static void _program(State * state);
static void _service(State * state)
	/* service_name space socket space protocol space wait space id space
	 * program newline */
{
	_service_name(state);
	_space(state);
	_socket(state);
	_space(state);
	_protocol(state);
	_space(state);
	_wait(state);
	_space(state);
	_id(state);
	_space(state);
	_program(state);
	_newline(state);
	/* FIXME not so elegant */
	if(state->service.name != NULL)
	{
		config_service_add(state->config,
				service_new(state->service.name,
				state->service.socket, state->service.proto,
				state->service.wait, state->service.id,
				state->service.program));
		free(state->service.name);
	}
}

static void _service_name(State * state)
	/* WORD */
{
	state->service.name = strdup(state->token->string);
	_parser_check(state, TC_WORD);
}

static void _socket(State * state)
	/* "stream" | "dgram" */
{
	if(strcmp("stream", state->token->string) == 0)
		state->service.socket = SS_STREAM;
	else if(strcmp("dgram", state->token->string) == 0)
		state->service.socket = SS_DGRAM;
	else
		_parser_error(state, "stream or dgram expected");
	_parser_check(state, TC_WORD); /* FIXME use test result */
}

static void _protocol(State * state)
	/* "tcp" | "udp" */
{
	if(strcmp("tcp", state->token->string) == 0)
		state->service.proto = SP_TCP;
	else if(strcmp("udp", state->token->string) == 0)
		state->service.proto = SP_UDP;
	else
		_parser_error(state, "Expected \"tcp\" or \"udp\"");
	_parser_check(state, TC_WORD); /* FIXME use test result */
}

static void _wait(State * state)
	/* "wait" | "nowait" */
{
	if(strcmp("wait", state->token->string) == 0)
		state->service.wait = SW_WAIT;
	else if(strcmp("nowait", state->token->string) == 0)
		state->service.wait = SW_NOWAIT;
	else
		_parser_error(state, "Expected \"wait\" or \"nowait\"");
	_parser_check(state, TC_WORD); /* FIXME use test result */
}

static void _id(State * state)
	/* user [ "." group ] */
{
	struct passwd * pwd;

	/* FIXME */
	errno = 0;
	if((pwd = getpwnam(state->token->string)) != NULL)
		state->service.id.uid = pwd->pw_uid;
	else
	{
		if(errno == 0)
			fprintf(stderr, "%s%s%s", "inetd: ",
					state->token->string,
					": No such user\n");
		else
			inetd_error(state->token->string, 0);
		state->service.id.uid = 0;
	}
	state->service.id.gid = 0;
	_parser_check(state, TC_WORD);
}

static void _program_argument(State * state);
static void _program_name(State * state);
static void _program(State * state)
	/* program_name { [ space [ program_argument ] ] } */
{
	state->service.program = NULL;
	_program_name(state);
	while(token_in_set(state->token, TS_SPACE))
	{
		_space(state);
		if(token_in_set(state->token, TS_PROGRAM_ARGUMENT))
			_program_argument(state);
	}
}

static void _program_name(State * state)
	/* WORD */
{
	if((state->service.program = malloc(2 * sizeof(char*))) != NULL)
	{
		state->service.program[0] = strdup(state->token->string);
		state->service.program[1] = NULL;
	}
	_parser_check(state, TC_WORD); /* FIXME use test result */
}

static void _program_argument(State * state)
	/* WORD */
{
	unsigned int i;
	char ** p;

	if(state->service.program == NULL)
	{
		_parser_check(state, TC_WORD);
		return; /* FIXME */
	}
	for(i = 0; state->service.program[i] != NULL; i++);
	if((p = realloc(state->service.program, sizeof(char*) * (i+2))) == NULL)
	{
		_parser_check(state, TC_WORD);
		return; /* FIXME */
	}
	state->service.program = p;
	state->service.program[i] = strdup(state->token->string);
	state->service.program[i+1] = NULL;
	_parser_check(state, TC_WORD);
}
