;; IPv6 patch for spegla-1.1p4
;; Jan 29, 2002 by Hajimu UMEMOTO <ume@mahoroba.org>
;;
;; On FreeBSD 4.X or later, IPv6 support is enabled automatically.
;; For other systems, please specify -DINET6 in Makefile to enable
;; IPv6 support.
;;
Index: Makefile
===================================================================
RCS file: /usr/home/ume/ncvs/src/spegla/Makefile,v
retrieving revision 1.1.1.1
retrieving revision 1.4
diff -u -r1.1.1.1 -r1.4
--- Makefile	28 Jan 2002 11:36:51 -0000	1.1.1.1
+++ Makefile	29 Jan 2002 11:55:58 -0000	1.4
@@ -47,6 +47,9 @@
 LDADD		+= -L/usr/local/lib -lmemdb
 .endif
 .endif
+.if defined(INET6)
+CFLAGS		+= -DINET6
+.endif
 
 .include <bsd.prog.mk>
 
Index: jftp.c
===================================================================
RCS file: /usr/home/ume/ncvs/src/spegla/jftp.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 jftp.c
--- jftp.c	28 Jan 2002 11:36:51 -0000	1.1.1.1
+++ jftp.c	29 Jan 2002 11:17:39 -0000
@@ -131,6 +131,32 @@
 #define E_LOG_4(level, fmt, a1, a2, a3, a4) \
 	e_log(level, "ftp: %s: " fmt, __FUNCTION__, a1, a2, a3, a4);
 
+#ifdef INET6
+/* translate IPv4 mapped IPv6 address to IPv4 address */
+static void
+unmappedaddr(struct sockaddr_in6 *sin6, int *len)
+{
+	struct sockaddr_in *sin4;
+	u_int32_t addr;
+	int port;
+
+	if (sin6->sin6_family != AF_INET6 ||
+	    !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
+		return;
+	sin4 = (struct sockaddr_in *)sin6;
+	addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
+	port = sin6->sin6_port;
+	memset(sin4, 0, sizeof(struct sockaddr_in));
+	sin4->sin_addr.s_addr = addr;
+	sin4->sin_port = port;
+	sin4->sin_family = AF_INET;
+#ifdef SIN6_LEN
+	sin4->sin_len = sizeof(struct sockaddr_in);
+#endif
+	*len = sizeof(struct sockaddr_in);
+}
+#endif
+
 void
 ftp_set_timeout_val(struct ftp_con *c, int n)
 {
@@ -304,11 +330,10 @@
 }
 
 struct ftp_con *
-ftp_login(char *host, int port, char *username,
+ftp_login(char *host, int port, int family, char *username,
 	char *password, FILE * logfile, int verbose)
 {
 	struct	ftp_con *c;
-	struct	hostent *hp;
 
 	c = calloc((size_t)1, sizeof(*c));
 	if ((c->ftp_remote_host = strdup(host)) == NULL)
@@ -324,40 +349,13 @@
 	c->ftp_listen = -1;
 	c->ftp_data = -1;
 	c->ftp_port = port;
+	c->ftp_family = family;
 	c->ftp_retries = 20;
 	c->ftp_timeout = JFTP_TIMEOUT_VAL;
 	c->ftp_relogins = -1;
 	if ((c->ftp_tempdir = strdup(JFTP_DIR)) == NULL)
 		goto ret_bad;
 
-	/* init ftp_my_ip_comma used by ftp_port */
-	(void) gethostname(c->ftp_buf, sizeof(c->ftp_buf));
-	hp = gethostbyname(c->ftp_buf); /* XXX this isn't the best way */
-	if (hp == NULL) {
-		E_LOGX_1(0, "can't resolve my name: %s", c->ftp_buf);
-		c->ftp_resp = JFTP_CONFUSED;
-		goto ret_bad;
-	}
-	/* make sure we like the address */
-	if (hp->h_addrtype != AF_INET) {
-		E_LOGX_1(0, "wrong address type: %d", hp->h_addrtype);
-		goto ret_bad;
-	}
-	if (hp->h_length != 4) {
-		E_LOGX_1(0, "wrong address length: %d", hp->h_length);
-		goto ret_bad;
-	}
-	/* ip address with commas */
-	c->ftp_my_ip_comma = malloc((size_t)(3 * 4 + 3 + 1));
-	if (c->ftp_my_ip_comma == NULL) {
-		E_LOG(0, "malloc");
-		goto ret_bad;
-	}
-	(void) sprintf(c->ftp_my_ip_comma, "%d,%d,%d,%d",
-		(u_int8_t) hp->h_addr[0], (u_int8_t) hp->h_addr[1],
-		(u_int8_t) hp->h_addr[2], (u_int8_t) hp->h_addr[3]);
-
-
 	if (ftp_relogin(c) < 0)
 		goto ret_bad;
 
@@ -378,7 +376,6 @@
 	FC_FREE(ftp_remote_host);
 	FC_FREE(ftp_user_name);
 	FC_FREE(ftp_password);
-	FC_FREE(ftp_my_ip_comma);
 	FC_FREE(ftp_tempdir);
 	free(c);
 }
@@ -386,13 +383,47 @@
 int
 ftp_relogin(struct ftp_con *c)
 {
+#ifdef INET6
+	struct	addrinfo hints, *res0 = NULL, *res;
+	char	str_port[NI_MAXSERV];
+#else
 	struct	sockaddr_in server;
 	struct	hostent *hp;
+#endif
 
 	c->ftp_relogins++;
 	FD_CLOSE(c->ftp_com);
 	FD_CLOSE(c->ftp_listen);
 	FD_CLOSE(c->ftp_data);
+#ifdef INET6
+	snprintf(str_port, sizeof(str_port), "%d", c->ftp_port);
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = c->ftp_family;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_CANONNAME;
+	if (getaddrinfo(c->ftp_remote_host, str_port, &hints, &res0) != 0) {
+		E_LOGX_1(0, "getaddrinfo(%s): failed", c->ftp_remote_host);
+		c->ftp_resp = JFTP_ERR;
+		return -1;
+	}
+	c->ftp_com = -1;
+	for (res = res0; res; res = res->ai_next) {
+		c->ftp_com = socket(res->ai_family, res->ai_socktype,
+				    res->ai_protocol);
+		if (c->ftp_com < 0)
+			continue;
+		if (connect(c->ftp_com, res->ai_addr, res->ai_addrlen) >= 0)
+			break;
+		FD_CLOSE(c->ftp_com);
+		c->ftp_com = -1;
+	}
+	freeaddrinfo(res0);
+	if (c->ftp_com < 0) {
+		E_LOG(0, "connect");
+		c->ftp_resp = JFTP_ERR;
+		return -1;
+	}
+#else
 	c->ftp_com = socket(AF_INET, SOCK_STREAM, 0);
 	if (c->ftp_com < 0) {
 		E_LOG(0, "socket");
@@ -417,6 +448,7 @@
 		c->ftp_resp = JFTP_ERR;
 		return -1;
 	}
+#endif
 	if (ftp_req(c, JFTP_RESPONSE) < 0 || c->ftp_resp != 220) {
 		E_LOGX_1(0, "unexpected greeting from server: %s", c->ftp_buf);
 		FD_CLOSE(c->ftp_com);
@@ -509,12 +541,44 @@
 ftp_port(struct ftp_con *c)
 {
 	char	*p, *a;
+#ifdef INET6
+	struct	sockaddr_storage sin2;
+	struct	sockaddr_in6 *sin6;
+	char	hname[INET6_ADDRSTRLEN];
+	u_char	addr[4];
+	int	port;
+#else
 	struct	sockaddr_in sin2;
+#endif
+	struct	sockaddr_in *sin4;
+	int	arg;
 	int     len, res;
 	int		a0, a1, a2, a3, p0, p1;
 
 	c->ftp_downloads++;
 
+	len = sizeof(sin2);
+	if (getsockname(c->ftp_com, (struct sockaddr *)&sin2, &len) == -1) {
+		E_LOG(0, "getsockname");
+		FD_CLOSE(c->ftp_com);
+		c->ftp_resp = JFTP_BROKEN;
+		return -1;
+	}
+	switch (((struct sockaddr *)&sin2)->sa_family) {
+	case AF_INET:
+		break;
+#ifdef INET6
+	case AF_INET6:
+		unmappedaddr((struct sockaddr_in6 *)&sin2, &len);
+		break;
+#endif
+	default:
+		E_LOG(0, "getsockname");
+		FD_CLOSE(c->ftp_com);
+		c->ftp_resp = JFTP_BROKEN;
+		return -1;
+	}
+
 	/* doing active ftp */
 	if (!c->ftp_passive) {
 		/* sanity check */
@@ -525,7 +589,8 @@
 		}
 
 		/* create listen socket */
-		c->ftp_listen = socket(AF_INET, SOCK_STREAM, 0);
+		c->ftp_listen = socket(((struct sockaddr *)&sin2)->sa_family,
+				       SOCK_STREAM, 0);
 		if (c->ftp_listen < 0) {
 			E_LOGX(0, "socket");
 			c->ftp_listen = -1;
@@ -534,12 +599,27 @@
 		}
 
 		/* bind socket */
-		(void) memset((void *) &sin2, 0, sizeof(sin2));
-		sin2.sin_family = AF_INET;
-		sin2.sin_addr.s_addr = htonl(INADDR_ANY);
-		sin2.sin_port = 0;
-		/* NOSTRICT sin2 */
-		if (bind(c->ftp_listen, (struct sockaddr *) &sin2, sizeof(sin2)) < 0) {
+		switch (((struct sockaddr *)&sin2)->sa_family) {
+		case AF_INET:
+			((struct sockaddr_in *)&sin2)->sin_port = 0;
+#ifdef IP_PORTRANGE
+			arg = IP_PORTRANGE_HIGH;
+			setsockopt(c->ftp_listen, IPPROTO_IP, IP_PORTRANGE,
+				   (char *)&arg, sizeof(arg));
+#endif
+			break;
+#ifdef INET6
+		case AF_INET6:
+			((struct sockaddr_in6 *)&sin2)->sin6_port = 0;
+#ifdef IPV6_PORTRANGE
+			arg = IPV6_PORTRANGE_HIGH;
+			setsockopt(c->ftp_listen, IPPROTO_IPV6, IPV6_PORTRANGE,
+				   (char *)&arg, sizeof(arg));
+#endif
+			break;
+#endif
+		}
+		if (bind(c->ftp_listen, (struct sockaddr *) &sin2, len) < 0) {
 			E_LOG(0, "bind");
 			c->ftp_resp = JFTP_BROKEN;
 			FD_CLOSE(c->ftp_listen);
@@ -568,9 +648,33 @@
 		}
 	
 		/* do the port command */
-		res = ftp_req(c, "PORT %s,%d,%d", c->ftp_my_ip_comma,
-	    	((unsigned) ntohs(sin2.sin_port) >> 8) & 0xff,
-	    	ntohs(sin2.sin_port) & 0xff);
+		switch (((struct sockaddr *)&sin2)->sa_family) {
+		case AF_INET:
+			sin4 = (struct sockaddr_in *)&sin2;
+			a = (char *)&sin4->sin_addr.s_addr;
+			p = (char *)&sin4->sin_port;
+			res = ftp_req(c, "PORT %d,%d,%d,%d,%d,%d",
+				      a[0] & 0xff, a[1] & 0xff,
+				      a[2] & 0xff, a[3] & 0xff,
+				      p[0] & 0xff, p[1] & 0xff);
+			break;
+#ifdef INET6
+		case AF_INET6:
+			sin6 = (struct sockaddr_in6 *)&sin2;
+			if (getnameinfo((struct sockaddr *)&sin2, sin2.ss_len,
+					hname, sizeof(hname),
+					NULL, 0, NI_NUMERICHOST) != 0) {
+				E_LOG(0, "getnameinfo");
+				FD_CLOSE(c->ftp_listen);
+				FD_CLOSE(c->ftp_com);
+				c->ftp_resp = JFTP_BROKEN;
+				return -1;
+			}
+			res = ftp_req(c, "EPRT |%d|%s|%d|", 2, hname,
+				      htons(sin6->sin6_port));
+			break;
+#endif
+		}
 		if (res < 0 || c->ftp_resp != 200) {
 			if (res >= 0)
 				c->ftp_resp = JFTP_ERR;
@@ -579,29 +683,68 @@
 		}
 		return 0;
 	} else { /* passive ftp */
-		res = ftp_req(c, "PASV");
-		if (res < 0 || c->ftp_resp != 227) {
-			if (res >= 0)
+		switch (((struct sockaddr *)&sin2)->sa_family) {
+		case AF_INET:
+			res = ftp_req(c, "PASV");
+			if (res < 0 || c->ftp_resp != 227) {
+				if (res >= 0)
+					c->ftp_resp = JFTP_ERR;
+				return -1;
+			}
+			for (p = c->ftp_buf; *p != '\0' && *p != '('; p++);
+			res = sscanf(p, "(%d,%d,%d,%d,%d,%d",
+				     &a0, &a1, &a2, &a3, &p0, &p1);
+			if (res != 6) {
 				c->ftp_resp = JFTP_ERR;
-			return -1;
-		}
-		for (p = c->ftp_buf; *p != '\0' && *p != '('; p++);
-		res = sscanf(p, "(%d,%d,%d,%d,%d,%d", &a0, &a1, &a2, &a3, &p0, &p1);
-		if (res != 6) {
-			c->ftp_resp = JFTP_ERR;
-			return -1;
+				return -1;
+			}
+			sin4 = (struct sockaddr_in *)&sin2;
+			/* NOSTRICT a */
+			a = (char *)&sin4->sin_addr.s_addr;
+			a[0] = a0 & 0xff;
+			a[1] = a1 & 0xff;
+			a[2] = a2 & 0xff;
+			a[3] = a3 & 0xff;
+			/* NOSTRICT p */
+			p = (char *)&sin4->sin_port;
+			p[0] = p0 & 0xff;
+			p[1] = p1 & 0xff;
+			sin4->sin_family = AF_INET;
+			break;
+#ifdef INET6
+		case AF_INET6:
+			res = ftp_req(c, "EPSV");
+			if (res < 0 || c->ftp_resp != 229) {
+				if (res >= 0)
+					c->ftp_resp = JFTP_ERR;
+				return -1;
+			}
+			for (p = c->ftp_buf; *p != '\0' && *p != '('; p++);
+			if (*p == '\0') {
+				c->ftp_resp = JFTP_ERR;
+				return -1;
+			}
+			++p;
+			res = sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1],
+				     &addr[2], &port, &addr[3]);
+			if (res != 5 || addr[0] != addr[1] ||
+			    addr[0] != addr[2] || addr[0] != addr[3]) {
+				c->ftp_resp = JFTP_ERR;
+				return -1;
+			}
+			len = sizeof(sin2);
+			res = getpeername(c->ftp_com,
+					  (struct sockaddr *)&sin2, &len);
+			if (res == -1 || sin2.ss_family != AF_INET6) {
+				E_LOG(0, "getpeername");
+				c->ftp_resp = JFTP_BROKEN;
+				return -1;
+			}
+			sin6 = (struct sockaddr_in6 *)&sin2;
+			sin6->sin6_port = htons(port);
+			break;
+#endif
 		}
-		/* NOSTRICT a */
-		a = (char *)&sin2.sin_addr.s_addr;
-		a[0] = a0 & 0xff;
-		a[1] = a1 & 0xff;
-		a[2] = a2 & 0xff;
-		a[3] = a3 & 0xff;
-		/* NOSTRICT p */
-		p = (char *)&sin2.sin_port;
-		p[0] = p0 & 0xff;
-		p[1] = p1 & 0xff;
-		sin2.sin_family = AF_INET;
 
 		/* sanity check */
 		if (c->ftp_data != -1) {
@@ -610,7 +753,8 @@
 				"data connection wasn't closed");
 		}
 		/* connect to server */
-		c->ftp_data = socket(AF_INET, SOCK_STREAM, 0);
+		c->ftp_data = socket(((struct sockaddr *)&sin2)->sa_family,
+				     SOCK_STREAM, 0);
 		if (c->ftp_data < 0) {
 			E_LOG(0, "socket");
 			c->ftp_resp = JFTP_ERR;
@@ -619,7 +763,7 @@
 		(void)ftp_set_sock_opts(c, c->ftp_data);
 		if (connect(c->ftp_data, 
 				/* NOSTRICT sin2 */
-				(struct sockaddr *)&sin2, sizeof(sin2)) < 0) {
+				(struct sockaddr *)&sin2, len) < 0) {
 			E_LOG(0, "connect");
 			FD_CLOSE(c->ftp_data);
 			c->ftp_resp = JFTP_ERR;
Index: jftp.h
===================================================================
RCS file: /usr/home/ume/ncvs/src/spegla/jftp.h,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 jftp.h
--- jftp.h	28 Jan 2002 11:36:51 -0000	1.1.1.1
+++ jftp.h	29 Jan 2002 11:17:02 -0000
@@ -49,11 +49,11 @@
 	int		ftp_com;
 	int		ftp_data;
 	int		ftp_listen;
+	int		ftp_family;
 	char	*ftp_remote_host;
 	char	*ftp_user_name;
 	char	*ftp_password;
 	char	*ftp_remote_dir;
-	char	*ftp_my_ip_comma;
 	FILE	*ftp_logfile;
 	char	ftp_buf[JFTP_BUF];
 	char	*ftp_tempdir;
@@ -84,7 +84,7 @@
 
 
 /* Login on server, returns NULL on failure */
-struct ftp_con * ftp_login(char *host, int port, char *username,
+struct ftp_con * ftp_login(char *host, int port, int family, char *username,
 			char *password, FILE * logfile, int verbose);
 
 void ftp_unalloc(struct ftp_con *);
@@ -126,5 +126,11 @@
 
 /* specify where temp files should go */
 void	ftp_set_tempdir(struct ftp_con * c, char *tempdir);
+
+#ifndef INET6
+#if defined(__FreeBSD__) && __FreeBSD__ >= 4
+#define INET6
+#endif
+#endif
 
 #endif
Index: spegla.1
===================================================================
RCS file: /usr/home/ume/ncvs/src/spegla/spegla.1,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 spegla.1
--- spegla.1	28 Jan 2002 11:36:51 -0000	1.1.1.1
+++ spegla.1	29 Jan 2002 12:16:32 -0000
@@ -146,6 +146,10 @@
 Port to connect to.
 .br
 Defaults to 21.
+.It Cm family No or Fl a Aq "unspec | inet | inet6"
+Address family to be used to connect.
+.br
+Defaults to unspec.
 .It Cm passive Aq "yes | no"
 Whether to use passive mode or not.
 .br
Index: spegla.c
===================================================================
RCS file: /usr/home/ume/ncvs/src/spegla/spegla.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 spegla.c
--- spegla.c	28 Jan 2002 11:36:51 -0000	1.1.1.1
+++ spegla.c	29 Jan 2002 12:21:43 -0000
@@ -42,6 +42,7 @@
 #include <sys/stat.h>
 #include <sys/param.h>
 #include <sys/mount.h>
+#include <sys/socket.h>
 
 #if defined(SunOS) || defined(Solaris) || defined(OSF1) || defined(ULTRIX)
 #	include <sys/time.h>
@@ -118,6 +119,7 @@
 #define GO_MIRRORUSER		26
 #define GO_HELP				27
 #define GO_URL				28
+#define GO_FAMILY			29
 
 static struct get_opt go[] = {
 { GO_CONFIGFILE,		"configfile",		'f',	REQ_ARG },
@@ -151,6 +153,7 @@
 { GO_MIRRORUSER,		"mirroruser",		'?',	REQ_ARG },
 { GO_HELP,				"help",				'?',	NO_ARG },
 { GO_URL,				"url",				'?',	REQ_ARG },
+{ GO_FAMILY,			"family",	'a',	REQ_ARG },
 { -1,NULL,0,NO_ARG}
 };
 
@@ -868,6 +871,54 @@
 	}
 }
 
+static void
+set_param_family(int option, const char *arg, int **var)
+{
+	int	family;
+	char	*f;
+
+	family = PF_UNSPEC;	/* Silent gcc */
+	if (strlen(arg) == 0 || strcmp(arg, "unspec") == 0)
+		family = PF_UNSPEC;
+	else if (strcmp(arg, "inet") == 0)
+		family = PF_INET;
+#ifdef INET6
+	else if (strcmp(arg, "inet6") == 0)
+		family = PF_INET6;
+#endif
+	else
+		e_errx(1, "%s's argument '%s' invalid", go[option].go_name, arg);
+
+	if (*var != NULL) {
+		if (!nowarn) {
+			switch (**var) {
+			case PF_UNSPEC:
+				f = "unspec";
+				break;
+			case PF_INET:
+				f = "inet";
+				break;
+#ifdef INET6
+			case PF_INET6:
+				f = "inet6";
+				break;
+#endif
+			default:
+				f = "unknown";
+				break;
+			}
+			e_warnx("overriding %s's value '%s' with '%s'",
+				go[option].go_name, f, arg);
+		}
+	} else {
+		*var = malloc(sizeof(family));
+		if (*var == NULL)
+			e_err(1, "malloc");
+	}
+		
+	**var = family;
+}
+
 /* ARGSUSED */
 static void
 add_param_sps(int option, const char *arg, struct cl_sps_que **q)
@@ -973,6 +1024,38 @@
 }
 
 static void
+default_family(const char *name, int **val, int num, int show)
+{
+	char *f;
+
+	if (*val == NULL) {
+		*val = malloc(sizeof(int));
+		if (*val == NULL)
+			e_err(1, "malloc");
+		**val = num;
+	}
+	if (show) {
+		switch (**val) {
+		case PF_UNSPEC:
+			f = "unspec";
+			break;
+		case PF_INET:
+			f = "inet";
+			break;
+#ifdef INET6
+		case PF_INET6:
+			f = "inet6";
+			break;
+#endif
+		default:
+			f = "unknown";
+			break;
+		}
+		e_warnx("%s = %s", name, f);
+	}
+}
+
+static void
 show_skip_map_fun(struct sp_skip *sps)
 {
 	e_warnx("skip = '%s'", sps->sps_name);
@@ -1023,6 +1106,7 @@
 		int		*timeout;
 		int		*retries;
 		int		*port;
+		int		*family;
 		int		*passive;
 		int		*retrytime;
 		int		*showconf;
@@ -1080,6 +1164,9 @@
 		}
 
 		switch(go[option].go_opt_num) {
+		case GO_FAMILY:
+			set_param_family(option, arg, &param.family);
+			break;
 		case GO_LOCALDIR:
 			set_param_str(option, arg, &param.localdir);
 			break;
@@ -1229,6 +1316,7 @@
 	default_num("timeout",	&param.timeout,		150,*param.showconf);
 	default_num("retries",	&param.retries,		20,	*param.showconf);
 	default_num("port",		&param.port,		21,	*param.showconf);
+	default_family("family",	&param.family,		PF_UNSPEC,	*param.showconf);
 	default_num("passive",	&param.passive,		1,	*param.showconf);
 	default_num("retrytime",&param.retrytime,	150,*param.showconf);
 	default_num("fastsync",	&param.fastsync,	0,	*param.showconf);
@@ -1303,8 +1391,9 @@
 	e_warnx("Spegla started at %s", ctime(&start_time));
 	e_warnx("Logging in to %s", param.host);
 	do {
-		c = ftp_login(param.host, *param.port, param.username,
-				param.password, fp_log_file, *param.loglevel);
+		c = ftp_login(param.host, *param.port, *param.family,
+				param.username, param.password,
+				fp_log_file, *param.loglevel);
 		if (c == NULL) {
 			if ((*param.retries)-- < 0)
 				e_errx(1, "I've retried enough now, quit");
@@ -1345,6 +1434,7 @@
 	SP_FREE(timeout);
 	SP_FREE(retries);
 	SP_FREE(port);
+	SP_FREE(family);
 	SP_FREE(passive);
 	SP_FREE(retrytime);
 	SP_FREE(showconf);