/*

    VALIDATECONF - Part of the tsocks package
		   This utility can be used to validate the tsocks.conf
		   configuration file

    Copyright (C) 2000 Shaun Clowes 

    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; either version 2 of the License, or
    (at your option) any later version.

    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, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

/* Global configuration variables */ 
char *progname = "validateconf";	      /* Name for error msgs      */

/* Header Files */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <common.h>
#include <parser.h>

void show_server(struct parsedfile *, struct serverent *, int);
void show_conf(struct parsedfile *config);
void test_host(struct parsedfile *config, char *);

int main(int argc, char *argv[]) {
	char *usage = "Usage: [-f conf file] [-t hostname/ip[:port]]"; 
	char *filename = NULL;
	char *testhost = NULL;
   struct parsedfile config;
	int i;

	if ((argc > 5) || (((argc - 1) % 2) != 0)) {
		show_msg(MSGERR, "Invalid number of arguments\n");
		show_msg(MSGERR, "%s\n", usage);
		exit(1);
	}

	for (i = 1; i < argc; i = i + 2) {
		if (!strcmp(argv[i], "-f")) {
			filename = argv[(i + 1)];
		} else if (!strcmp(argv[i], "-t")) {
			testhost = argv[(i + 1)];
		} else {
			show_msg(MSGERR, "Unknown option %s\n", argv[i]);
			show_msg(MSGERR, "%s\n", usage);
			exit(1);
		}
	}

	if (!filename) 
		filename = strdup(CONF_FILE);

	printf("Reading configuration file %s...\n", filename);
	if (read_config(filename, &config) == 0)
		printf("... Read complete\n\n");
	else 
		exit(1);

	/* If they specified a test host, test it, otherwise */
	/* dump the configuration                            */
	if (!testhost)
		show_conf(&config);
	else
		test_host(&config, testhost);
	
	return(0);  
}

void test_host(struct parsedfile *config, char *host) { 
	struct in_addr hostaddr;
	struct serverent *path;
   char *hostname, *port;
   char separator;
   unsigned long portno = 0;

   /* See if a port has been specified */
   hostname = strsplit(&separator, &host, ": \t\n");
   if (separator == ':') {
      port = strsplit(NULL, &host, " \t\n");
      if (port) 
         portno = strtol(port, NULL, 0);
   }

	/* First resolve the host to an ip */
	if ((hostaddr.s_addr = resolve_ip(hostname, 0, 1)) == -1) {
		fprintf(stderr, "Error: Cannot resolve %s\n", host);
		return;
	} else {
		printf("Finding path for %s...\n", inet_ntoa(hostaddr));
      if (!(is_local(config, &(hostaddr)))) {
         printf("Path is local\n");
      } else {
         pick_server(config, &path, &hostaddr, portno);
         if (path == &(config->defaultserver)) {
            printf("Path is via default server:\n");
            show_server(config, path, 1);
         } else {
            printf("Host is reached via this path:\n");
            show_server(config, path, 0);
         }
      }
	}

	return;
}

void show_conf(struct parsedfile *config) {
	struct netent *net;
	struct serverent *server;

	/* Show the local networks */
	printf("=== Local networks (no socks server needed) ===\n");
	net = (config->localnets);
	while (net != NULL) {
		printf("Network: %15s ",
		       inet_ntoa(net->localip));
		printf("NetMask: %15s\n", 
		       inet_ntoa(net->localnet));
		net = net->next;
	}
	printf("\n");

	/* If we have a default server configuration show it */
	printf("=== Default Server Configuration ===\n");
	if ((config->defaultserver).address != NULL) {
		show_server(config, &(config->defaultserver), 1);
	} else {
		printf("No default server specified, this is rarely a "
		       "good idea\n");
	}
	printf("\n");

	/* Now show paths */
	if ((config->paths) != NULL) {
		server = (config->paths);
		while (server != NULL) {
			printf("=== Path (line no %d in configuration file)"
			       " ===\n", server->lineno);
			show_server(config, server, 0);
			printf("\n");
			server = server->next;
		}	
	} 

	return;
}

void show_server(struct parsedfile *config, struct serverent *server, int def) {
	struct in_addr res;
	struct netent *net;

	/* Show address */
	if (server->address != NULL) 
		printf("Server:       %s (%s)\n", server->address, 
	       		((res.s_addr = resolve_ip(server->address, 0, 
						  HOSTNAMES)) == -1 
			 ? "Invalid!" : inet_ntoa(res)));
	else
		printf("Server:       ERROR! None specified\n");

	/* Check the server is on a local net */
	if ((server->address != NULL) && (res.s_addr != -1) && 
	    (is_local(config, &res))) 
		fprintf(stderr, "Error: Server is not on a network "
				"specified as local\n");

	/* Show port */
	printf("Port:         %d\n", server->port);

	/* Show SOCKS type */
	printf("SOCKS type:   %d\n", server->type);

	/* Show default username and password info */
	if (server->type == 5) {
		/* Show the default user info */
		printf("Default user: %s\n", 
		       (server->defuser == NULL) ? 
		       "Not Specified" : server->defuser);
		printf("Default pass: %s\n", 
		       (server->defpass == NULL) ? 
		       "Not Specified" : "******** (Hidden)");
		if ((server->defuser == NULL) && 
		    (server->defpass != NULL)) 
			fprintf(stderr, "Error: Default user must be specified "
				   "if default pass is specified\n");
	} else {
		if (server->defuser) printf("Default user: %s\n", 
					    server->defuser);
		if (server->defpass) printf("Default pass: %s\n", 
					    server->defpass);
		if ((server->defuser != NULL) || (server->defpass != NULL))
			fprintf(stderr, "Error: Default user and password "
				   "may only be specified for version 5 "
				   "servers\n");
	}

	/* If this is the default servers and it has reachnets, thats stupid */
	if (def) {
		if (server->reachnets != NULL) { 
			fprintf(stderr, "Error: The default server has "
			       "specified networks it can reach (reach statements), "
			       "these statements are ignored since the "
			       "default server will be tried for any network "
			       "which is not specified in a reach statement "
			       "for other servers\n");
		}
	} else if (server->reachnets == NULL) {
		fprintf(stderr, "Error: No reach statements specified for "
		       "server, this server will never be used\n");
	} else {
		printf("This server can be used to reach:\n");
		net = server->reachnets;
		while (net != NULL) {
			printf("Network: %15s ",
			       inet_ntoa(net->localip));
			printf("NetMask: %15s ", 
			       inet_ntoa(net->localnet));
         if (net->startport)
            printf("Ports: %5lu - %5lu",
                   net->startport, net->endport);
         printf("\n");
			net = net->next;
		}
	}
}
