/*
 *
 *   Authors:
 *    Lars Fenneberg		<lf@elemental.net>
 *    Marko Myllynen		<myllynen@lut.fi>
 *
 *   This software is Copyright 1996-2000 by the above mentioned author(s),
 *   All Rights Reserved.
 *
 *   The license which is distributed with this software in the file COPYRIGHT
 *   applies to this software. If your distribution is missing this file, you
 *   may request it from https://github.com/radvd-project/radvd/issues
 *
 */

#include "radvd.h"
#include "config.h"
#include "includes.h"

#if __BYTE_ORDER == __LITTLE_ENDIAN
#define bswap64(y) (((uint64_t)ntohl(y)) << 32 | ntohl(y>>32))
#else
#define bswap64(y) (y)
#endif

static char usage_str[] = "[-vhfe] [-d level]";

#ifdef HAVE_GETOPT_LONG
struct option prog_opt[] = {
	{"debug", 1, 0, 'd'},
	{"username", 1, 0, 'u'},
	{"file-format", 0, 0, 'f'},
	{"exclude-defaults", 0, 0, 'e'},
	{"version", 0, 0, 'v'},
	{"help", 0, 0, 'h'},
	{NULL, 0, 0, 0}
};
#endif

int sock = -1;

static void version(void);
static void usage(char const *pname);
static void print_ff(unsigned char *, int, struct sockaddr_in6 *, int, unsigned int, int);
static void print_preferences(int);

int main(int argc, char *argv[])
{
	int c;
	int edefs = 0;
#ifdef HAVE_GETOPT_LONG
	int opt_idx;
#endif
	char const *pname = ((pname = strrchr(argv[0], '/')) != NULL) ? pname + 1 : argv[0];
	char username[256];
	strncpy(username, "nobody", 255);

/* parse args */
#ifdef HAVE_GETOPT_LONG
	while ((c = getopt_long(argc, argv, "d:fehvu:", prog_opt, &opt_idx)) > 0)
#else
	while ((c = getopt(argc, argv, "d:fehvu:")) > 0)
#endif
	{
		switch (c) {
		case 'd':
			set_debuglevel(atoi(optarg));
			break;
		case 'f':
			break;
		case 'e':
			edefs = 1;
			break;
		case 'v':
			version();
			break;
		case 'u':
			strncpy(username, optarg, 255);
			break;
		case 'h':
			usage(pname);
#ifdef HAVE_GETOPT_LONG
		case ':':
			fprintf(stderr, "%s: option %s: parameter expected\n", pname, prog_opt[opt_idx].name);
			exit(1);
#endif
		case '?':
			exit(1);
		}
	}

	if (log_open(L_STDERR, pname, NULL, 0) < 0) {
		perror("log_open");
		exit(1);
	}

	/* get a raw socket for sending and receiving ICMPv6 messages */
	int sock = open_icmpv6_socket();
	if (sock < 0) {
		perror("open_icmpv6_socket");
		exit(1);
	}

	if(strncmp(username, "", 255) == 0 || strncmp(username, "-", 255) == 0) {
		// Must opt out of security
	} else {
		if (drop_root_privileges(username) < 0) {
			perror("drop_root_privileges");
			flog(LOG_ERR, "unable to drop root privileges");
			exit(1);
		}
		dlog(LOG_DEBUG, 5, "running as user: %s/%d", username, geteuid());
	}

#define _MSG_SIZE MSG_SIZE_RECV
#define _CHDR_SIZE CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))
	for (;;) {
		unsigned char msg[_MSG_SIZE];
		int hoplimit = 0;
		struct in6_pktinfo *pkt_info = NULL;
		struct sockaddr_in6 rcv_addr;
		unsigned char chdr[_CHDR_SIZE];
		// safety; completely zero buffers for each receive
		memset(&msg, 0, _MSG_SIZE);
		memset(&chdr, 0, _CHDR_SIZE);

		int len = recv_rs_ra(sock, msg, &rcv_addr, &pkt_info, &hoplimit, chdr);
		if (len > 0) {
			struct icmp6_hdr *icmph;

			/*
			 * can this happen?
			 */

			if (len < sizeof(struct icmp6_hdr)) {
				flog(LOG_WARNING, "received icmpv6 packet with invalid length: %d", len);
				exit(1);
			}

			icmph = (struct icmp6_hdr *)msg;

			if (icmph->icmp6_type != ND_ROUTER_SOLICIT && icmph->icmp6_type != ND_ROUTER_ADVERT) {
				/*
				 *      We just want to listen to RSs and RAs
				 */

				flog(LOG_ERR, "icmpv6 filter failed");
				exit(1);
			}

			dlog(LOG_DEBUG, 4, "receiver if_index: %u", pkt_info->ipi6_ifindex);

			if (icmph->icmp6_type == ND_ROUTER_SOLICIT) {
				/* not yet */
			} else if (icmph->icmp6_type == ND_ROUTER_ADVERT)
				print_ff(msg, len, &rcv_addr, hoplimit, (unsigned int)pkt_info->ipi6_ifindex, edefs);
		} else if (len == 0) {
			flog(LOG_ERR, "received zero length packet");
			exit(1);
		} else {
			flog(LOG_ERR, "recv_rs_ra: %s", strerror(errno));
			exit(1);
		}
	}

	exit(0);
}

static void print_ff(unsigned char *msg, int len, struct sockaddr_in6 *addr, int hoplimit, unsigned int if_index, int edefs)
{
	/* XXX: hoplimit not being used for anything here.. */
	int orig_len = len;

	char addr_str[INET6_ADDRSTRLEN];
	addrtostr(&addr->sin6_addr, addr_str, sizeof(addr_str));
	printf("#\n# radvd configuration generated by radvdump %s\n", VERSION);
	printf("# based on Router Advertisement from %s\n", addr_str);
	char if_name[IFNAMSIZ] = {""};
	if_indextoname(if_index, if_name);
	printf("# received by interface %s\n", if_name);
	printf("#\n\ninterface %s\n{\n\tAdvSendAdvert on;\n", if_name);

	printf("\t# Note: {Min,Max}RtrAdvInterval cannot be obtained with radvdump\n");

	struct nd_router_advert *radvert = (struct nd_router_advert *)msg;

	if (!edefs || DFLT_AdvManagedFlag != (ND_RA_FLAG_MANAGED == (radvert->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)))
		printf("\tAdvManagedFlag %s;\n", (radvert->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) ? "on" : "off");

	if (!edefs || DFLT_AdvOtherConfigFlag != (ND_RA_FLAG_OTHER == (radvert->nd_ra_flags_reserved & ND_RA_FLAG_OTHER)))
		printf("\tAdvOtherConfigFlag %s;\n", (radvert->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) ? "on" : "off");

	if (!edefs || DFLT_AdvReachableTime != ntohl(radvert->nd_ra_reachable))
		printf("\tAdvReachableTime %u;\n", ntohl(radvert->nd_ra_reachable));

	if (!edefs || DFLT_AdvRetransTimer != ntohl(radvert->nd_ra_retransmit))
		printf("\tAdvRetransTimer %u;\n", ntohl(radvert->nd_ra_retransmit));

	if (!edefs || DFLT_AdvCurHopLimit != radvert->nd_ra_curhoplimit)
		printf("\tAdvCurHopLimit %u;\n", radvert->nd_ra_curhoplimit);

	if (!edefs || (3 * DFLT_MaxRtrAdvInterval) != ntohs(radvert->nd_ra_router_lifetime))
		printf("\tAdvDefaultLifetime %hu;\n", ntohs(radvert->nd_ra_router_lifetime));

	/* Mobile IPv6 ext */
	if (!edefs || DFLT_AdvHomeAgentFlag != (ND_RA_FLAG_HOME_AGENT == (radvert->nd_ra_flags_reserved & ND_RA_FLAG_HOME_AGENT)))
		printf("\tAdvHomeAgentFlag %s;\n", (radvert->nd_ra_flags_reserved & ND_RA_FLAG_HOME_AGENT) ? "on" : "off");

	/* Route Preferences and more specific routes */
	/* XXX two middlemost bits from 8 bit field */
	if (!edefs || (((radvert->nd_ra_flags_reserved & 0x18) >> 3) & 0xff) != DFLT_AdvDefaultPreference) {
		printf("\tAdvDefaultPreference ");
		print_preferences(((radvert->nd_ra_flags_reserved & 0x18) >> 3) & 0xff);
		printf(";\n");
	}

	len -= sizeof(struct nd_router_advert);

	if (len == 0)
		return;

	uint8_t *opt_str = (uint8_t *)(msg + sizeof(struct nd_router_advert));

	while (len > 0) {

		if (len < 2) {
			flog(LOG_ERR, "trailing garbage in RA from %s", addr_str);
			break;
		}

		int optlen = (opt_str[1] << 3);

		if (optlen == 0) {
			flog(LOG_ERR, "zero length option in RA");
			break;
		} else if (optlen > len) {
			flog(LOG_ERR, "option length greater than total"
				      " length in RA (type %d, optlen %d, len %d)",
			     (int)*opt_str, optlen, len);
			// Do not process anything further in this RA.
			goto end_of_interface;
		}

		switch (*opt_str) {
		case ND_OPT_MTU: {
			struct nd_opt_mtu *mtu = (struct nd_opt_mtu *)opt_str;

			if (!edefs || DFLT_AdvLinkMTU != ntohl(mtu->nd_opt_mtu_mtu))
				printf("\tAdvLinkMTU %u;\n", ntohl(mtu->nd_opt_mtu_mtu));
			break;
		}
		case ND_OPT_SOURCE_LINKADDR:
			/* XXX: !DFLT depends on current DFLT_ value */
			if (!edefs || !DFLT_AdvSourceLLAddress)
				printf("\tAdvSourceLLAddress on;\n");
			break;
		/* Mobile IPv6 ext */
		case ND_OPT_RTR_ADV_INTERVAL:
			/* XXX: !DFLT depends on current DFLT_ value */
			if (!edefs || !DFLT_AdvIntervalOpt)
				printf("\tAdvIntervalOpt on;\n");
			break;
		/* Mobile IPv6 ext */
		case ND_OPT_HOME_AGENT_INFO: {
			struct HomeAgentInfo *ha_info = (struct HomeAgentInfo *)opt_str;

			/* XXX: we check DFLT_HomeAgentInfo by interface, and it's outside
			   of context here, so we always need to print it out.. */
			printf("\tAdvHomeAgentInfo on;\n");

			/* NEMO ext */
			if (!edefs || DFLT_AdvMobRtrSupportFlag != (ha_info->flags_reserved & ND_OPT_HAI_FLAG_SUPPORT_MR))
				printf("\tAdvMobRtrSupportFlag %s;\n",
				       (ha_info->flags_reserved & ND_OPT_HAI_FLAG_SUPPORT_MR) ? "on" : "off");

			if (!edefs || DFLT_HomeAgentPreference != ntohs(ha_info->preference))
				printf("\tHomeAgentPreference %hu;\n", ntohs(ha_info->preference));
			/* Hum.. */
			if (!edefs || (3 * DFLT_MaxRtrAdvInterval) != ntohs(ha_info->lifetime))
				printf("\tHomeAgentLifetime %hu;\n", ntohs(ha_info->lifetime));
			break;
		}
		case ND_OPT_CAPTIVE_PORTAL: {
			char *opt_captive_portal = (char *)opt_str+2;
			char *captive_portal = strndup(opt_captive_portal, optlen-2);

			printf("\tAdvCaptivePortalAPI \"%s\";\n", captive_portal);

			free(captive_portal);
			break;
		}
		case ND_OPT_TIMESTAMP: {
			char *opt_timestamp = (char *)opt_str+8; //tl + 6 bytes of resv
			unsigned short fracs;
			uint64_t secs;
			uint64_t ts;

			memcpy(&ts, opt_timestamp, sizeof(ts));
			ts = bswap64(ts);
			secs = ts >> 16;
			fracs = ts & 0xffff;

			printf("\tAdvTimestamp \"%lu secs, %hu fracs\";\n", secs, fracs);
			break;
		}
		case ND_OPT_TARGET_LINKADDR:
		case ND_OPT_REDIRECTED_HEADER:
			flog(LOG_ERR, "invalid option %d in RA", (int)*opt_str);
			break;
		case ND_OPT_PREFIX_INFORMATION:
			break;
		case ND_OPT_ROUTE_INFORMATION:
			break;
		case ND_OPT_RDNSS_INFORMATION:
			break;
		case ND_OPT_DNSSL_INFORMATION:
			break;
		case ND_OPT_PREF64:
			break;
		default:
			dlog(LOG_DEBUG, 1, "unknown option %d in RA", (int)*opt_str);
			break;
		}

		len -= optlen;
		opt_str += optlen;
	}

	orig_len -= sizeof(struct nd_router_advert);

	if (orig_len == 0)
		return;

	opt_str = (uint8_t *)(msg + sizeof(struct nd_router_advert));

	while (orig_len > 0) {
		char prefix_str[INET6_ADDRSTRLEN];

		if (orig_len < 2) {
			flog(LOG_ERR, "trailing garbage in RA from %s", addr_str);
			break;
		}

		int optlen = (opt_str[1] << 3);

		if (optlen == 0) {
			flog(LOG_ERR, "zero length option in RA");
			break;
		} else if (optlen > orig_len) {
			flog(LOG_ERR, "option length greater than total"
				      " length in RA (type %d, optlen %d, len %d)",
			     (int)*opt_str, optlen, orig_len);
			// Do not process anything further in this RA.
			goto end_of_interface;
		}

		switch (*opt_str) {
		case ND_OPT_PREFIX_INFORMATION: {
			struct nd_opt_prefix_info *pinfo = (struct nd_opt_prefix_info *)opt_str;

			addrtostr(&pinfo->nd_opt_pi_prefix, prefix_str, sizeof(prefix_str));

			printf("\n\tprefix %s/%d\n\t{\n", prefix_str, pinfo->nd_opt_pi_prefix_len);

			if (ntohl(pinfo->nd_opt_pi_valid_time) == 0xffffffff) {
				if (!edefs || DFLT_AdvValidLifetime != 0xffffffff)
					printf("\t\tAdvValidLifetime infinity; # (0xffffffff)\n");
			} else {
				if (!edefs || DFLT_AdvValidLifetime != ntohl(pinfo->nd_opt_pi_valid_time))
					printf("\t\tAdvValidLifetime %u;\n", ntohl(pinfo->nd_opt_pi_valid_time));
			}
			if (ntohl(pinfo->nd_opt_pi_preferred_time) == 0xffffffff) {
				if (!edefs || DFLT_AdvPreferredLifetime != 0xffffffff)
					printf("\t\tAdvPreferredLifetime infinity; # (0xffffffff)\n");
			} else {
				if (!edefs || DFLT_AdvPreferredLifetime != ntohl(pinfo->nd_opt_pi_preferred_time))
					printf("\t\tAdvPreferredLifetime %u;\n", ntohl(pinfo->nd_opt_pi_preferred_time));
			}

			if (!edefs ||
			    DFLT_AdvOnLinkFlag !=
				(ND_OPT_PI_FLAG_ONLINK == (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK)))
				printf("\t\tAdvOnLink %s;\n",
				       (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) ? "on" : "off");

			if (!edefs ||
			    DFLT_AdvAutonomousFlag !=
				(ND_OPT_PI_FLAG_AUTO == (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO)))
				printf("\t\tAdvAutonomous %s;\n",
				       (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) ? "on" : "off");

			/* Mobile IPv6 ext */
			if (!edefs ||
			    DFLT_AdvRouterAddr !=
				(ND_OPT_PI_FLAG_RADDR == (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_RADDR)))
				printf("\t\tAdvRouterAddr %s;\n",
				       (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_RADDR) ? "on" : "off");
			
			if (!edefs ||
			    DFLT_AdvDHCPv6PDPreferredFlag !=
				(ND_OPT_PI_FLAG_DHCPv6_PD_PREF == (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_DHCPv6_PD_PREF)))
				printf("\t\tAdvDHCPv6PDPreferred %s;\n",
				       (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_DHCPv6_PD_PREF) ? "on" : "off");

			printf("\t}; # End of prefix definition\n\n");
			break;
		}
		case ND_OPT_ROUTE_INFORMATION: {
			struct nd_opt_route_info_local *rinfo = (struct nd_opt_route_info_local *)opt_str;

			if (optlen == 8) {
				printf("\n\troute ::/0\n\t{\n");
			} else {
				struct in6_addr addr;
				memset(&addr, 0, sizeof(addr));
				/* RFC4191 section 2.3 (reformatted)
				   Length      8-bit unsigned integer.

				   The length of the option (including the Type and Length
				   fields) in units of 8 octets.

				   The Length field is 1, 2, or 3 depending on the Prefix
				   Length.

				   If Prefix Length is greater than 64, then Length must be 3.

				   If Prefix Length is greater than 0, then Length must be 2 or
				   3.

				   If Prefix Length is zero, then Length must be 1, 2, or 3.
				*/
				if (
					(rinfo->nd_opt_ri_len > 3)
					|| (rinfo->nd_opt_ri_len == 0)
					|| (rinfo->nd_opt_ri_prefix_len > 64 && rinfo->nd_opt_ri_len < 3)
					|| (rinfo->nd_opt_ri_prefix_len > 0  && rinfo->nd_opt_ri_len < 1)
					) {
					flog(LOG_ERR, "Invalid Route Information option length %d from %s", rinfo->nd_opt_ri_len, addr_str);
					printf("# Invalid Route Information option length %d, per RFC4191 section 2.3 ", rinfo->nd_opt_ri_len);
					break;
				}
				if (rinfo->nd_opt_ri_len > 1)
					memcpy(&addr, &rinfo->nd_opt_ri_prefix, (rinfo->nd_opt_ri_len - 1) * 8);
				addrtostr(&addr, prefix_str, sizeof(prefix_str));
				printf("\n\troute %s/%d\n\t{\n", prefix_str, rinfo->nd_opt_ri_prefix_len);
			}

			if (!edefs || (((radvert->nd_ra_flags_reserved & 0x18) >> 3) & 0xff) != DFLT_AdvRoutePreference) {
				printf("\t\tAdvRoutePreference ");
				print_preferences(((rinfo->nd_opt_ri_flags_reserved & 0x18) >> 3) & 0xff);
				printf(";\n");
			}

			/* XXX: we check DFLT_AdvRouteLifetime by interface, and it's outside of context here */
			if (ntohl(rinfo->nd_opt_ri_lifetime) == 0xffffffff)
				printf("\t\tAdvRouteLifetime infinity; # (0xffffffff)\n");
			else
				printf("\t\tAdvRouteLifetime %u;\n", ntohl(rinfo->nd_opt_ri_lifetime));

			printf("\t}; # End of route definition\n\n");
			break;
		}
		case ND_OPT_RDNSS_INFORMATION: {
			struct nd_opt_rdnss_info_local *rdnss_info = (struct nd_opt_rdnss_info_local *)opt_str;


			if (rdnss_info->nd_opt_rdnssi_len >= 3 && rdnss_info->nd_opt_rdnssi_len % 2 == 1) {
				printf("\n\tRDNSS");
				for (int i = 0; i < (rdnss_info->nd_opt_rdnssi_len - 1) / 2; i++) {
					addrtostr(&rdnss_info->nd_opt_rdnssi_addr[i], prefix_str, sizeof(prefix_str));
					printf(" %s", prefix_str);
				}
				printf("\n\t{\n");
				/* as AdvRDNSSLifetime may depend on MaxRtrAdvInterval, it could change */
				if (ntohl(rdnss_info->nd_opt_rdnssi_lifetime) == 0xffffffff)
					printf("\t\tAdvRDNSSLifetime infinity; # (0xffffffff)\n");
				else
					printf("\t\tAdvRDNSSLifetime %u;\n", ntohl(rdnss_info->nd_opt_rdnssi_lifetime));
				printf("\t}; # End of RDNSS definition\n\n");
			} else {
				flog(LOG_ERR, "Invalid RDNSS option length %d from %s", rdnss_info->nd_opt_rdnssi_len, addr_str);
				printf("# Invalid RDNSS length %d, per RFC8106 section 5.1 ", rdnss_info->nd_opt_rdnssi_len);
			}

			break;
		}
		case ND_OPT_DNSSL_INFORMATION: {
			struct nd_opt_dnssl_info_local *dnssl_info = (struct nd_opt_dnssl_info_local *)opt_str;

			printf("\n\tDNSSL");

			char suffix[256] = {""};
			for (int offset = 0; offset < (dnssl_info->nd_opt_dnssli_len - 1) * 8;) {
				int label_len = dnssl_info->nd_opt_dnssli_suffixes[offset++];

				if (label_len == 0) {
					/*
					 * Ignore empty suffixes. They're
					 * probably just padding...
					 */
					if (suffix[0] == '\0')
						continue;

					printf(" %s", suffix);

					suffix[0] = '\0';
					continue;
				}

				if ((sizeof(suffix) - strlen(suffix)) < (label_len + 2)) {
					flog(LOG_ERR, "oversized suffix in DNSSL option from %s", addr_str);
					break;
				}

				if (suffix[0] != '\0')
					strcat(suffix, ".");
				strncat(suffix, (char *)&dnssl_info->nd_opt_dnssli_suffixes[offset], label_len);
				offset += label_len;
			}

			printf("\n\t{\n");
			/* as AdvDNSSLLifetime may depend on MaxRtrAdvInterval, it could change */
			if (ntohl(dnssl_info->nd_opt_dnssli_lifetime) == 0xffffffff)
				printf("\t\tAdvDNSSLLifetime infinity; # (0xffffffff)\n");
			else
				printf("\t\tAdvDNSSLLifetime %u;\n", ntohl(dnssl_info->nd_opt_dnssli_lifetime));

			printf("\t}; # End of DNSSL definition\n\n");
			break;
		}
		case ND_OPT_PREF64: {
			if (optlen != sizeof(struct nd_opt_nat64prefix_info)) {
				flog(LOG_ERR, "incorrect pref64 option length in RA from %s, skipping", addr_str);
				break;
			}

			struct nd_opt_nat64prefix_info *pinfo = (struct nd_opt_nat64prefix_info *)opt_str;
			uint16_t lifetime_preflen = ntohs(pinfo->nd_opt_pi_lifetime_preflen);
			uint8_t prefix_length_code = lifetime_preflen & 7;
			uint16_t prefix_lifetime = lifetime_preflen & 0xFFF8;
			int prefix_size = -1;
			struct in6_addr nat64prefix;

			/* The option only contains the first 96 bits of the prefix */
			memset(&nat64prefix, 0, sizeof(nat64prefix));
			memcpy(&nat64prefix, &pinfo->nd_opt_pi_nat64prefix, 12);
			addrtostr(&nat64prefix, prefix_str, sizeof(prefix_str));

			switch (prefix_length_code) {
			case 0:
				prefix_size = 96;
				break;
			case 1:
				prefix_size = 64;
				break;
			case 2:
				prefix_size = 56;
				break;
			case 3:
				prefix_size = 48;
				break;
			case 4:
				prefix_size = 40;
				break;
			case 5:
				prefix_size = 32;
				break;
			default:
				flog(LOG_ERR, "Invalid (reserved) nat64prefix length code %d received", prefix_length_code);
			}

			printf("\n\tnat64prefix %s/%d\n\t{\n", prefix_str, prefix_size);
			printf("\t\tAdvValidLifetime %u;\n", prefix_lifetime);
			printf("\t}; # End of nat64prefix definition\n\n");

			break;
		}
		default:
			break;
		}
		orig_len -= optlen;
		opt_str += optlen;
	}

end_of_interface:
	printf("}; # End of interface definition\n");

	fflush(stdout);
}

static void print_preferences(int p)
{
	switch (p) {
	case 0:
		printf("medium");
		break;
	case 1:
		printf("high");
		break;
	case 2:
		/* reserved, ignore */
		break;
	case 3:
		printf("low");
		break;
	}
}

static void version(void)
{
	fprintf(stderr, "Version: %s\n\n", VERSION);
	fprintf(stderr, "Please send bug reports and suggestions to %s\n", CONTACT_EMAIL);
	exit(-1);
}

static void usage(char const *pname)
{
	fprintf(stderr, "usage: %s %s\n", pname, usage_str);
	exit(-1);
}
