#!/usr/bin/perl -w -T
#
# Project    : ipv6calc/ip6calcweb
# File       : ipv6calcweb.cgi
# Version    : $Id: ipv6calcweb.cgi.in,v 1.21 2010/09/21 19:01:16 peter Exp $
# Copyright  : 2002-2009 by Peter Bieringer <pb (at) bieringer.de>
# License    : GPL, but copyright always has to be displayed in output
#
# Simple Perl web interface and security wrapper
#  It's too dangerous to call the binary 'ipv6calc' directly...
#
# Todo: more functions by query string...

### Uses environment variables:
## Created by http server before invoking CGI:
#  REMOTE_ADDR    : remote client address
#  REMOTE_HOST    : remote client name (DNS resolved)
#  HTTP_USER_AGENT: user agent string
#  SERVER_ADDR    : local server address
#  SERVER_NAME    : local server name (by http server config)
#  SERVER_PROTOCOL: check for INCLUDED (called by SSI)
#  QUERY_STRING   : for language setting
#    Currently supported:
#	"lang=$lang" with $lang in @supported_languages
#	"format=$format" with $format in @supported_formats

use strict;

## Defines
# Program information
my $program_name = "ipv6calcweb.cgi";
my $program_copyright = "(P) & (C) 2002-@COPYRIGHT_YEAR@ by Peter Bieringer";
my $program_version = "@PACKAGE_VERSION@";

# required output version of ipv6calc (introduced in 0.60.0)
my $program_required_ipv6calc_output_version = 2;


# Debug value
my $debug = 0;
#$debug |= 0x02;
#$debug |= 0x10;
#$debug = 0xffff;
#$debug = 0x01;

# debug | 0x10: print raw ipv6calc output
# debug | 0x02: print result of find_file to stderr


# List of deefault location of files (first existing one would be choosen)
my @list_bin_ipv6calc = (
	"../ipv6calc/ipv6calc",
	"/usr/bin/ipv6calc",
	"/bin/ipv6calc",
);

my @list_database_ip2location_ipv4 = (
	"/usr/share/IP2Location/IP-COUNTRY-SAMPLE.BIN",
);

my @list_database_ip2location_ipv6 = (
	"/var/local/share/IP2Location/IPV6-COUNTRY.BIN",
	"/usr/share/IP2Location/IPV6-COUNTRY.BIN",
);

my @list_database_geoip = (
	"/var/local/share/GeoIP/GeoLiteCity.dat",
	"/var/local/share/GeoIP/GeoIP.dat",
	"/usr/share/GeoIP/GeoLiteCity.dat",
	"/usr/share/GeoIP/GeoIP.dat",
);

sub find_file(@) {
	foreach my $file (@_) {
		if (-e $file) {
			print STDERR "find_file selected: $file\n" if ($debug & 0x02);
			return ($file);
		};
	};
	return undef;
};

# Location of binary
my $bin_ipv6calc = find_file(@list_bin_ipv6calc);
my $options_ipv6calc = "-m -i -q";

my $database_ip2location_ipv4 = find_file(@list_database_ip2location_ipv4);
my $database_ip2location_ipv6 = find_file(@list_database_ip2location_ipv6);
my $flag_ip2location_used_ipv4 = 0;
my $flag_ip2location_used_ipv6 = 0;
my %info_ip2location;
my $info_ip2location_string;
my $info_ip2location_string_ipv4;
my $info_ip2location_string_ipv6;

my $database_geoip = find_file(@list_database_geoip);
my $flag_geoip_used = 0;
my $info_geoip_string;

if (defined $database_ip2location_ipv4) {
	$options_ipv6calc .= " --db-ip2location-ipv4 $database_ip2location_ipv4";
};

if (defined $database_ip2location_ipv6) {
	$options_ipv6calc .= " --db-ip2location-ipv6 $database_ip2location_ipv6";
};

if (defined $database_geoip) {
	$options_ipv6calc .= " --db-geoip $database_geoip";
};


# Whois server urls
my %url_whoisservers = (
	'RIPENCC' => {
		'ipv4'	=> "http://www.ripe.net/perl/whois?searchtext=",
		'ipv6'	=> "http://www.ripe.net/perl/whois?searchtext=",
	},
	'ARIN'	=> {
		'ipv4'	=> "http://ws.arin.net/whois?queryinput=",
		'ipv6'	=> "http://ws.arin.net/whois?queryinput=",
	},
	'APNIC'	=> {
		'ipv4'	=> "http://www.apnic.net/apnic-bin/whois.pl?searchtext=",
		'ipv6'	=> "http://www.apnic.net/apnic-bin/whois.pl?searchtext=",
	},
	'LACNIC'	=> {
		'ipv4'	=> "http://lacnic.net/cgi-bin/lacnic/whois?query=",
		'ipv6'	=> "http://lacnic.net/cgi-bin/lacnic/whois?query=",
	},
	'AFRINIC'	=> {
		'ipv4'	=> "http://www.afrinic.net/cgi-bin/whois?searchtext=",
		'ipv6'	=> "http://www.afrinic.net/cgi-bin/whois?searchtext=",
	},
	'IANA'	=> {
		'ipv4'	=> "",
		'ipv6'	=> "",
	},
	'unknown'	=> {
		'ipv4'	=> "",
		'ipv6'	=> "",
	}
);

#my $lang_default = "de";
my $lang_default = "en";

my $lang = $lang_default;

## Output format: text, html, htmlfull
#my $outputformat = "text";
#my $outputformat = "html";
my $outputformat = "htmlfull"; # switched to "html", if called by SSI

## Output type
# full = with description
# simple = without description
my $outputtype = "full";
#my $outputtype = "simple";

## Select output
# skip server = 1
my $skip_server = 1;

## Text
# Language
my @supported_languages = ( "de", "en");

# Format
my @supported_formats = ( "text", "html");

# Tokens to be formatted using <TT>
my @format_tt = ( "EUI48", "EUI64", "IPV6", "IPV4", "SLA", "IID", "IPV4_6TO4" );

my %text = (
	'EUI48' => {
		'de' => "EUI-48 Identifizierungsnummer (MAC Adresse)",
		'en' => "EUI-48 identifier (MAC address)",
	},
	'EUI48_SCOPE' => {
		'de' => "EUI-48 Art",
		'en' => "EUI-48 scope",
	},
	'EUI48_TYPE' => {
		'de' => "EUI-48 Adresstyp",
		'en' => "EUI-48 address type",
	},
	'EUI64' => {
		'de' => "EUI-64 Identifizierungsnummer",
		'en' => "EUI-64 identifier",
	},
	'EUI64_SCOPE' => {
		'de' => "EUI-64 Art",
		'en' => "EUI-64 scope",
	},
	'IPV6' => {
		'de' => "IPv6 Adresse",
		'en' => "IPv6 address",
	},
	'IPV4' => {
		'de' => "IPv4 Adresse",
		'en' => "IPv4 address",
	},
	'IPV4_REGISTRY' => {
		'de' => "Registry der IPv4 Adresse",
		'en' => "Registry of IPv4 address",
	},
	'IPV6_REGISTRY' => {
		'de' => "Registry der IPv6 Adresse",
		'en' => "Registry of IPv6 address",
	},
	'IPV4_SOURCE' => {
		'de' => "Quelle der IPv4 Adresse",
		'en' => "Source of IPv4 address",
	},
	'TEREDO_PORT_CLIENT' => {
		'de' => "UDP-Port des Teredo Clients (nach NAT)",
		'en' => "UDP port of Teredo client (outside NAT)",
	},
	'OUI' => {
		'de' => "Hersteller-Identifizierung der Netzwerkarte",
		'en' => "Vendor identification of network interface card",
	},
	'REMOTE' => {
		'de' => "Ihr Client",
		'en' => "Your client",
	},
	'SERVER' => {
		'de' => "Dieser Server",
		'en' => "This server",
	},
	'SLA' => {
		'de' => "Subnetz ID",
		'en' => "Subnet ID",
	},
	'IIDSCOPE' => {
		'de' => "Art der Interface-Identifierungsnummer",
		'en' => "Scope of interface identifier",
	},
	'IID' => {
		'de' => "Interface-Identifierungsnummer",
		'en' => "Interface identifier",
	},
	'TYPE' => {
		'de' => "Adresstyp",
		'en' => "Address type",
	},
	'NAME' => {
		'de' => "Reverse DNS Auflösung",
		'en' => "Reverse DNS resolution",
	},
	'title' => {
		'de' => "Adresstyp Information",
		'en' => "Addresstype information",
	},
	'nodata' => {
		'de' => "Keine Daten verfügbar",
		'en' => "No data availabe",
	},
	'generated' => {
		'de' => "Generiert durch",
		'en' => "Generated by",
	},
	'powered' => {
		'de' => "Unterstützt durch",
		'en' => "Powered by",
	},
	'entries' => {
		'de' => "Einträge",
		'en' => "entries",
	},
	'database' => {
		'de' => "Datenbank",
		'en' => "database",
	},
	'version' => {
		'de' => "Version",
		'en' => "version",
	},
	'USERAGENT' => {
		'de' => "Browseridentifikation",
		'en' => "User agent identification",
	},
	'IP2LOCATION_COUNTRY_SHORT' => {
		'de' => "IP2Location Länderkennung",
		'en' => "IP2Location country code",
	},
	'IP2LOCATION_COUNTRY_LONG' => {
		'de' => "IP2Location Land",
		'en' => "IP2Location country",
	},
	'IP2LOCATION_REGION' => {
		'de' => "IP2Location Region",
		'en' => "IP2Location region",
	},
	'IP2LOCATION_CITY' => {
		'de' => "IP2Location Stadt",
		'en' => "IP2Location city",
	},
	'IP2LOCATION_ISP' => {
		'de' => "IP2Location ISP",
		'en' => "IP2Location ISP",
	},
	'IP2LOCATION_LATITUDE' => {
		'de' => "IP2Location Breitengrad",
		'en' => "IP2Location latitude",
	},
	'IP2LOCATION_LONGITUDE' => {
		'de' => "IP2Location Längengrad",
		'en' => "IP2Location longitude",
	},
	'IP2LOCATION_DOMAIN' => {
		'de' => "IP2Location Domain",
		'en' => "IP2Location domain",
	},
	'IP2LOCATION_ZIPCODE' => {
		'de' => "IP2Location Postleitzahl",
		'en' => "IP2Location ZIP code",
	},
	'IP2LOCATION_DATABASE_INFO' => {
		'de' => "IP2Location Datenbank-Information",
		'en' => "IP2Location database information",
	},
	'IP2LOCATION_DATABASE_INFO_IPV4' => {
		'de' => "IP2Location IPv4 Datenbank-Information",
		'en' => "IP2Location IPv4 database information",
	},
	'IP2LOCATION_DATABASE_INFO_IPV6' => {
		'de' => "IP2Location IPv6 Datenbank-Information",
		'en' => "IP2Location IPv6 database information",
	},
	'GEOIP_COUNTRY_SHORT' => {
		'de' => "GeoIP Länderkennung",
		'en' => "GeoIP country code",
	},
	'GEOIP_COUNTRY_LONG' => {
		'de' => "GeoIP Land",
		'en' => "GeoIP country",
	},
	'GEOIP_REGION' => {
		'de' => "GeoIP Region",
		'en' => "GeoIP region",
	},
	'GEOIP_CITY' => {
		'de' => "GeoIP Stadt",
		'en' => "GeoIP city",
	},
	'GEOIP_LATITUDE' => {
		'de' => "GeoIP Breitengrad",
		'en' => "GeoIP latitude",
	},
	'GEOIP_LONGITUDE' => {
		'de' => "GeoIP Längengrad",
		'en' => "GeoIP longitude",
	},
	'GEOIP_ZIPCODE' => {
		'de' => "GeoIP Postleitzahl",
		'en' => "GeoIP ZIP code",
	},
	'GEOIP_DMACODE' => {
		'de' => "GeoIP DMACODE",
		'en' => "GeoIP DMACODE",
	},
	'GEOIP_AREACODE' => {
		'de' => "GeoIP AREACODE",
		'en' => "GeoIP AREACODE",
	},
	'GEOIP_DATABASE_INFO' => {
		'de' => "GeoIP Datenbank-Information",
		'en' => "GeoIP database information",
	},
);

###### Normally nothing to change here

## Cleanup environment
# Please report, if more cleanup is needed on other systems

# Hardwire path to well known
if ( defined $ENV{'PATH'} ) { $ENV{'PATH'}="/bin:/usr/bin:/usr/local/bin"; };
# Clear shell environment
if ( defined $ENV{'BASH_ENV'} ) { $ENV{'BASH_ENV'}=""; };

## Fallbacks
if (! defined $outputformat) { $outputformat = "text" };
if (! defined $outputtype) { $outputtype = "simple" };
if (! defined $lang_default) { $lang_default = "en"};
if (! defined $lang) { $lang = $lang_default};

## Variables
my $addr_remote;
my $name_remote;
my $user_agent;
my $addr_server;
my $name_server;
my @info_remote;
my @info_server;
my %infoh_remote;
my %infoh_server;
my @sort_remote;
my @sort_server;
my $length_max_key = 0;
my $length_max_description = 0;
my $query_string;

my $maxenvlength = 256;

my $ipv6calc_version = "";
my $ipv6calc_copyright = "(P) & (C) by Peter Bieringer";
my $ipv6calc_name = "ipv6calc";


############### Functions

## Error message
sub print_error ($) {
	my $message = shift;
	if ( defined $message ) {
		printf STDERR $message . "\n";
	};
	exit 1;
};

## Print conditional html
sub print_tagoutput ($) {
	my $text = shift;
	if ( defined $text ) {
		if ($outputformat eq "html" || $outputformat eq "htmlfull") {
			print $text;
		};
	};
};

sub print_textonly ($) {
	my $text = shift;
	if ( defined $text ) {
		if ($outputformat eq "text") {
			print $text;
		};
	};
};

## split IP2Location data
sub split_ip2location($) {
	for my $stringlet (split / /, $_[0]) {
		my ($key, $value) = split /=/, $stringlet;
		$info_ip2location{$key} = $value;
	};
	if (! defined $info_ip2location{'proto'}) {
		$info_ip2location{'proto'} = "";
	};
};

## Print one table part
sub print_infohash ($$) {
	my $phash = $_[0];
	my $parray = $_[1];
	if (! defined $phash) { return; };

	my ($flag_tt, $flag_whoisurl, $whois_registry, $whois_type);
	my $last_key_embedded = "";
	my $count_key_embedded = 0;

	if ( ! defined \$phash ) {
		&print_tagoutput ( "      <tr>\n" );
		&print_tagoutput ( "        <td colspan=\"3\">" );
		print $text{'nodata'}->{$lang};
		&print_textonly ("\n");
		&print_tagoutput ( "</td>\n" );
		&print_tagoutput ( "</tr>\n" );
		return;
	};

	for my $key (@$parray) {
		if ($key eq "IP2LOCATION_DATABASE_INFO") {
			$info_ip2location_string = $$phash{$key};
			# skipped, will be shown in footer
			next;
		};

		if ($key eq "IP2LOCATION_DATABASE_INFO_IPV4") {
			$info_ip2location_string_ipv4 = $$phash{$key} . " proto=IPv4";
			# skipped, will be shown in footer
			next;
		};

		if ($key eq "IP2LOCATION_DATABASE_INFO_IPV6") {
			$info_ip2location_string_ipv6 = $$phash{$key} . " proto=IPv6";
			# skipped, will be shown in footer
			next;
		};

		if ($key eq "GEOIP_DATABASE_INFO") {
			$info_geoip_string = $$phash{$key};
			# strip non-ascii chars
			$info_geoip_string =~ s/[\200-\377]//g;
			# skipped, will be shown in footer
			next;
		};

		if ( $key =~ /^IP2LOCATION_/ ) {
			#$flag_ip2location_used = 1;
		};

		if ( $key =~ /^GEOIP_/ ) {
			$flag_geoip_used = 1;
		};

		# catch internal keys
		if ( $key =~ /^IPV6CALC_/ ) {
			# skipped, will be shown in footer
			next;
		};

		$flag_tt = 0;
		$flag_whoisurl = 0;

		# extract lookup key
		$key =~ /^([^[]+)(\[[^]]*\])?$/;;
		my $key_lookup = $1;
		my $key_embedded = "";
		if (defined $2) {
			$key_embedded = $2;
			if ($last_key_embedded eq "") {
				$last_key_embedded = $key_embedded;
				$count_key_embedded++;
			} elsif ( $last_key_embedded ne $key_embedded) {
				$last_key_embedded = $key_embedded;
				$count_key_embedded++;
			};
		} else {
			$count_key_embedded = 0;
			$last_key_embedded = "";
		};

		if (grep(/^$key_lookup$/, @format_tt)) {
			$flag_tt = 1;
		};

		# print key
		if ($count_key_embedded > 0) {
			if ($count_key_embedded & 1) {
				&print_tagoutput ( "      <tr style=\"background-color: rgb(240, 240, 240);\">\n" );
			} else {
				&print_tagoutput ( "      <tr style=\"background-color: rgb(224, 224, 224);\">\n" );
			};
		} else {
			&print_tagoutput ( "      <tr>\n" );
		};
		&print_tagoutput ( "        <td><b>" );
		print $key;
		&print_textonly (' ' x ($length_max_key - length($key)) );
		&print_textonly (" | ");
		&print_tagoutput ( "</b></td>\n" );

		# print description
		if ($outputtype ne "simple") {
			&print_tagoutput ( "        <td>" );
			if (defined $text{$key_lookup}->{$lang}) {
				print $text{$key_lookup}->{$lang};
				&print_textonly (' ' x ($length_max_description - length($text{$key_lookup}->{$lang})) );
			} else {
				&print_textonly (' ' x ($length_max_description) );
			};
			&print_textonly (" | ");
			&print_tagoutput ( "</td>\n" );
		};

		# print data
		&print_tagoutput ( "        <td>" );

		if ($flag_tt) {
			&print_tagoutput ( "<tt>" );
		};

		if ( $key_lookup eq "IPV4" ) {
			if ( defined $$phash{'IPV4_REGISTRY' . $key_embedded} ) {
				$whois_registry = $$phash{'IPV4_REGISTRY' . $key_embedded};
				$whois_type = "ipv4";
				$flag_whoisurl = 1;
			} else {
				# Temporary workaround
				$whois_registry = 'unknown';
				$whois_type = "ipv4";
				$flag_whoisurl = 1;
			};
		} elsif ( $key_lookup eq "IPV6" ) {
			if ( defined $$phash{'IPV6_REGISTRY' . $key_embedded} ) {
				$whois_registry = $$phash{'IPV6_REGISTRY' . $key_embedded};
				$whois_type = "ipv6";
				$flag_whoisurl = 1;
			};
		};

		if ( $flag_whoisurl == 1 ) {
			if ( defined $url_whoisservers{$whois_registry}->{$whois_type} ) {
				if ( $url_whoisservers{$whois_registry}->{$whois_type} ne "" ) {
					&print_tagoutput ( "<a target=\"_blank\" href=\"" . $url_whoisservers{$whois_registry}->{$whois_type} . $$phash{$key} . "\">" );
				} else {
					$flag_whoisurl = 0;
				};
			} else {
				if ($debug & 0x08) {
					print STDERR "whoisserver is not defined\n";
				};
				$flag_whoisurl = 0;
			};
		};
		
		print $$phash{$key};

		if ( $flag_whoisurl == 1 ) {
			&print_tagoutput ( "</a>" );
		};

		if ($flag_tt) {
			&print_tagoutput ( "</tt>" );
		};
		&print_tagoutput ( "</td>\n" );
		&print_tagoutput ( "      </tr>\n" );
		&print_textonly ("\n");
	};
};

############### Main

# Parse query string first 
if ( defined $ENV{'QUERY_STRING'} ) {
	# split query string
	foreach my $query_stringlet (split /\?/, $ENV{'QUERY_STRING'}) {
		if ($query_stringlet !~ /^([[:alnum:]]+)=([[:alnum:]]+)$/ ) {
			&print_error("Error: problem with data");
			next;
		};

		my ($name, $value) = ($1, $2);

		if ($name eq "lang") {
			for my $langtest (@supported_languages) {
				if ($value eq $langtest) {
					$lang = $langtest;
					last;
				};
			};
		} elsif ($name eq "format") {
			for my $formattest (@supported_formats) {
				if ($value eq $formattest) {
					$outputformat = $formattest;
					last;
				};
			};
		};
	};
};

## Check for binary ipv6calc exists and is executable
if (! defined $bin_ipv6calc) {
	&print_error("Error: missing needed program (E0)");
};
if ( length($bin_ipv6calc) == 0) {
	&print_error("Error: missing needed program (E1)");
};
if ( ! -f $bin_ipv6calc ) {
	&print_error("Error: missing needed program (E2)");
};
if ( ! -x $bin_ipv6calc ) {
	&print_error("Error: missing needed program (E3)");
};


## Get variables
if ( defined $ENV{'REMOTE_ADDR'} ) {
	$ENV{'REMOTE_ADDR'} =~ /^([[:xdigit:]\.\:]*)$/;
	if ( ! defined $1 || (length($1) > $maxenvlength)) {
		&print_error("Error: problem with data");
	};
	$addr_remote = $1;
};

if ( defined $ENV{'REMOTE_HOST'} ) {
	$ENV{'REMOTE_HOST'} =~ /^([[:alnum:]\.\-_]*)$/;
	if ( ! defined $1 || (length($1) > $maxenvlength)) {
		&print_error("Error: problem with data");
	};
	$name_remote = $1;
};

if ( defined $ENV{'HTTP_USER_AGENT'} ) {
	$ENV{'HTTP_USER_AGENT'} =~ /^([[:alnum:]\[\]\/\(\)\\\.\-+\;\: ]*)$/;
	if ( ! defined $1 || (length($1) > $maxenvlength)) {
		# not a problem, skip it
	} else {
		$user_agent = $1;
	};
};

if ( defined $ENV{'SERVER_ADDR'} ) {
	$ENV{'SERVER_ADDR'} =~ /^([[:xdigit:]\.\:]*)$/;
	if ( ! defined $1 || (length($1) > $maxenvlength)) {
		&print_error("Error: problem with data");
	};
	$addr_server = $1;
};

if ( defined $ENV{'SERVER_NAME'} ) {
	$ENV{'SERVER_NAME'} =~ /^([[:alnum:]\.\-\:_]*)$/;
	if ( ! defined $1 || (length($1) > $maxenvlength)) {
		&print_error("Error: problem with data");
	};
	$name_server = $1;
};


## Check type
if ( defined $ENV{'SERVER_PROTOCOL'} ) {
	if ( $ENV{'SERVER_PROTOCOL'} eq "INCLUDED" ) {
		if ( $outputformat eq "htmlfull" ) {
			# Switch back to included html
			$outputformat = "html";
		};
	};
};


## HTML header
&print_tagoutput ("Content-type: text/html\n\n");
&print_textonly  ("Content-type: text/plain\n\n");

## Get and fill information

if ( defined $addr_remote ) {
	@info_remote = `$bin_ipv6calc $options_ipv6calc $addr_remote`;
	if ( $? != 0 ) {
		&print_error("Error: problem retrieving data");
	};

	print "***remote***\n" if ($debug & 0x10);

	for my $line (@info_remote) {
		print $line if ($debug & 0x10);

		my ( $key, $content ) = split /=/, $line, 2;
		if ( (! defined $key) || (! defined $content) ) {
			&print_error("Error: problem parsing data");
		};
		chomp $content;

		if ( $key =~ /^IP2LOCATION_/ ) {
			#$flag_ip2location_used = 1;
			if ( $content =~ /^This (parameter|record) is unavailable/ ) {
				$content = "n/a";
			};
		};

		if ( $key =~ /^GEOIP_/ ) {
			$flag_geoip_used = 1;
		};

		# catch internal keys
		if ( $key =~ /^IPV6CALC_/ ) {
			if ( $key eq "IPV6CALC_COPYRIGHT" ) {
				$ipv6calc_copyright = $content;
				$ipv6calc_copyright =~ s/^\"//;
				$ipv6calc_copyright =~ s/\"$//;
			};
			if ( $key eq "IPV6CALC_VERSION" ) {
				$ipv6calc_version = $content;
				$ipv6calc_version =~ s/^\"//;
				$ipv6calc_version =~ s/\"$//;
			};
			if ( $key eq "IPV6CALC_NAME" ) {
				$ipv6calc_name = $content;
				$ipv6calc_name =~ s/^\"//;
				$ipv6calc_name =~ s/\"$//;
			};
		};

		$infoh_remote{$key} = $content;
		push @sort_remote, $key;
	};
	if (defined $name_remote) {
		if ($name_remote ne $addr_remote) {
			$infoh_remote{'NAME'} = $name_remote;
		};
	};
	if (defined $user_agent) {
		$infoh_remote{'USERAGENT'} = $user_agent;
	};
};

if ((defined $addr_server) && ($skip_server == 0)) {
	@info_server = `$bin_ipv6calc $options_ipv6calc  $addr_server`;
	if ( $? != 0 ) {
		&print_error("Error: problem retrieving data");
	};

	print "***server***\n" if ($debug & 0x10);

	for my $line (@info_server) {
		print $line if ($debug & 0x10);
		my ( $key, $content ) = split /=/, $line, 2;
		if ( (! defined $key) || (! defined $content) ) {
			&print_error("Error: problem parsing data");
		};
		chomp $content;

		if ( $key =~ /^IP2LOCATION_/ ) {
			if ( $content =~ /^This (parameter|record) is unavailable/ ) {
				$content = "n/a";
			};
		};

		if ( $key =~ /^GEOIP_/ ) {
			$flag_geoip_used = 1;
		};

		# catch internal keys
		if ( $key =~ /^IPV6CALC_/ ) {
			if ( $key eq "IPV6CALC_COPYRIGHT" ) {
				$ipv6calc_copyright = $content;
				$ipv6calc_copyright =~ s/^\"//;
				$ipv6calc_copyright =~ s/\"$//;
			};
			if ( $key eq "IPV6CALC_VERSION" ) {
				$ipv6calc_version = $content;
				$ipv6calc_version =~ s/^\"//;
				$ipv6calc_version =~ s/\"$//;
			};
			if ( $key eq "IPV6CALC_NAME" ) {
				$ipv6calc_name = $content;
				$ipv6calc_name =~ s/^\"//;
				$ipv6calc_name =~ s/\"$//;
			};
		};

		$infoh_server{$key} = $content;
		push @sort_server, $key;
	};
	if (defined $name_server) {
		if ($name_server ne $addr_server) {
			$infoh_server{'NAME'} = $name_server;
		};
	};
};


## Print content
if ($debug & 0x01) {
	print STDERR "REMOTE\n";
	for my $key (keys %infoh_remote) {
		print STDERR " ". $key . "=" .  $infoh_remote{$key} . "\n";
	};
	print STDERR "SERVER\n";
	for my $key (keys %infoh_server) {
		print STDERR " " . $key . "=" .  $infoh_server{$key} . "\n";
	};
};


## Calculate max lengths
for my $key (keys %text) {
	if (length($key) + 17 > $length_max_key) {
		$length_max_key = length($key) + 17;
	};

	if (length($text{$key}->{$lang}) > $length_max_description) {
		$length_max_description = length($text{$key}->{$lang});

	};
};
	

## Print output
if ($outputformat eq "htmlfull") {
	&print_tagoutput ( "<html>\n" );
	&print_tagoutput ( "  <head>\n" );
	&print_tagoutput ( "    <meta name=\"Author\" content=\"Peter Bieringer\">\n" );
	&print_tagoutput ( "    <title>" );
	print $text{'title'}->{$lang};
	&print_tagoutput ( "</title>\n" );
	&print_tagoutput ( "  </head>\n" );
	&print_tagoutput ( "  <body>\n" );

};

&print_tagoutput ( "    <table border CELLSPACING=0>\n" );

# Client
&print_textonly ("\n");
&print_tagoutput ( "      <tr>\n" );
&print_tagoutput ( "        <th colspan=\"3\">" );
print $text{'REMOTE'}->{$lang};
&print_textonly ("\n");
&print_tagoutput ( "</th>\n" );
&print_tagoutput ( "      </tr>\n" );
&print_infohash (\%infoh_remote, \@sort_remote);


if ( $skip_server == 0 ) {
	# Server
	&print_textonly ("\n");
	&print_tagoutput ( "      <tr>\n" );
	&print_tagoutput ( "        <th colspan=\"3\">" );
	print $text{'SERVER'}->{$lang};
	&print_textonly ("\n");
	&print_tagoutput ( "</th>\n" );
	&print_tagoutput ( "      </tr>\n" );
	&print_infohash (\%infoh_server, \@sort_server);
};

# Footer
&print_textonly ("\n");
&print_tagoutput ( "      <tr>\n" );
&print_tagoutput ( "        <td colspan=\"3\">\n" );

if ($outputformat eq "html" || $outputformat eq "htmlfull") {
	print "          <font size=-2>" . $text{'generated'}->{$lang} . " " . $program_name . " " . $program_version . ", " . $program_copyright . "</font><br>\n";
	print "          <font size=-2>" . $text{'powered'}->{$lang} . " <a target=\"_blank\" href=\"http://www.deepspace6.net/projects/ipv6calc.html\">" . $ipv6calc_name . "</a> " . $ipv6calc_version . ", " . $ipv6calc_copyright . "</font>\n";

	foreach my $string ($info_ip2location_string, $info_ip2location_string_ipv4, $info_ip2location_string_ipv6) {
		if (! defined $string) { next; };
		split_ip2location($string);
		print "<br>\n";
		print "          <font size=-2>" . $text{'powered'}->{$lang} . " <a target=\"_blank\" href=\"" . $info_ip2location{'url'} . "\">IP2Location</a> " . $info_ip2location{'proto'} . " " . $text{'database'}->{$lang} . " " . $text{'version'}->{$lang} . " " . $info_ip2location{'date'} . " (" . $info_ip2location{'entries'} . " " . $text{'entries'}->{$lang} . ")</font>\n";
	};

	if ($flag_geoip_used == 1) {
		print "<br>\n";
		print "          <font size=-2>" . $text{'powered'}->{$lang} . " <a target=\"_blank\" href=\"http://www.maxmind.com\">MaxMind</a> " . $text{'database'}->{$lang} . " " . $info_geoip_string . "</font>\n";
	};

} else {
	print $text{'generated'}->{$lang} . " " . $program_name . " " . $program_version . ", " . $program_copyright . "\n";
	print $text{'powered'}->{$lang} . " " . $ipv6calc_name . " " . $ipv6calc_version . ", " . $ipv6calc_copyright . " (http://www.deepspace6.net/projects/ipv6calc.html)" . "\n";

	foreach my $string ($info_ip2location_string, $info_ip2location_string_ipv4, $info_ip2location_string_ipv6) {
		if (! defined $string) { next; };
		split_ip2location($string);
		print $text{'powered'}->{$lang} . " IP2Location " . $info_ip2location{'proto'} . " " . $text{'database'}->{$lang} . " " . $text{'version'}->{$lang} . " " . $info_ip2location{'date'} ." (" . $info_ip2location{'entries'} . " " . $text{'entries'}->{$lang} . ")" . " (" . $info_ip2location{'url'} . ")" . "\n";

	};

	if ($flag_geoip_used == 1) {
		print $text{'powered'}->{$lang} . " MaxMind " . $text{'database'}->{$lang} . " " . $info_geoip_string . "(http://www.maxmind.com)" . "\n";
	};
};

&print_tagoutput ( "        </td>\n" );
&print_tagoutput ( "      </tr>\n" );
&print_tagoutput ( "    </table>\n" );

if ($outputformat eq "htmlfull") {
	&print_tagoutput ( "  </body>\n" );
	&print_tagoutput ( "</html>\n" );
};

exit (0);
