1 /*
2  * Copyright 2001-2003 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /****************************************************************************
9   Copyright (c) 1999,2000 WU-FTPD Development Group.
10   All rights reserved.
11 
12   Portions Copyright (c) 1980, 1985, 1988, 1989, 1990, 1991, 1993, 1994
13     The Regents of the University of California.
14   Portions Copyright (c) 1993, 1994 Washington University in Saint Louis.
15   Portions Copyright (c) 1996, 1998 Berkeley Software Design, Inc.
16   Portions Copyright (c) 1989 Massachusetts Institute of Technology.
17   Portions Copyright (c) 1998 Sendmail, Inc.
18   Portions Copyright (c) 1983, 1995, 1996, 1997 Eric P.  Allman.
19   Portions Copyright (c) 1997 by Stan Barber.
20   Portions Copyright (c) 1997 by Kent Landfield.
21   Portions Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997
22     Free Software Foundation, Inc.
23 
24   Use and distribution of this software and its source code are governed
25   by the terms and conditions of the WU-FTPD Software License ("LICENSE").
26 
27   If you did not receive a copy of the license, it may be obtained online
28   at http://www.wu-ftpd.org/license.html.
29 
30   $Id: routevector.c,v 1.13 2000/07/01 18:17:39 wuftpd Exp $
31 
32 ****************************************************************************/
33 /*
34  * Parse the entire ftpaccess file looking for:
35  *
36  * passive address <externalip> <address/CIDR>
37  * passive ports <address/CIDR> <min> <max>
38  *
39  * vect_addr, passive_port_min and passive_port_max store the external IP
40  * address, min and max ports found whose associated address is the most
41  * specific match of the address the client connected from.
42  *
43  * The optional CIDR denotes the number of significant bits in the address,
44  * the higher the CIDR the more specific the address. If no CIDR is specified,
45  * the whole address is significant.
46  *
47  * When a passive data connection is requested the server listens on a port
48  * randomly selected between passive_port_min and passive_port_max
49  * (inclusive), if vect_addr is set its address is reported (if not the
50  * local address of the control connection is reported). Note this does not
51  * change the address the server actually listens on, only the address
52  * reported to the client.
53  *
54  * For example if the ftpaccess file includes:
55  * passive address 194.80.17.14  0.0.0.0/0
56  * passive address 10.0.1.15     10.0.0.0/8
57  *
58  * Clients connecting from the class-A network 10 will be told the passive
59  * connection is listening on IP address 10.0.1.15, while clients connecting
60  * from all other addresses will be told the connection is listening on
61  * 194.80.17.14 (a CIDR of /0 matches all addresses of the same address
62  * family, if IPv6 support is enabled then IPv4 and IPv6 addresses are
63  * supported).
64  */
65 
66 #include "config.h"
67 #include <sys/socket.h>
68 #include <netinet/in.h>
69 #include <arpa/inet.h>
70 #include <string.h>
71 #ifdef HAVE_SYS_SYSLOG_H
72 #include <sys/syslog.h>
73 #endif
74 #if defined(HAVE_SYSLOG_H) || (!defined(AUTOCONF) && !defined(HAVE_SYS_SYSLOG_H))
75 #include <syslog.h>
76 #endif
77 #include "extensions.h"
78 #include "proto.h"
79 
80 extern struct SOCKSTORAGE his_addr;
81 extern struct SOCKSTORAGE vect_addr; /* best matching external IP address */
82 extern int passive_port_min;
83 extern int passive_port_max;
84 
85 /* significance of the external IP address and port entries */
86 static int vect_sig = -1;
87 static int port_sig = -1;
88 
89 #ifdef INET6
90 static int his_addr_family = AF_INET;
91 static int his_v4mapped = 0;
92 #endif
93 
94 /*
95  * Compares the address the client connected from (in his_addr) with the
96  * supplied address, with the specified number of bits being significant
97  * in the comparison. Returns 0 if the addresses match, non-zero otherwise.
98  */
addr_cmp(void * addr,int sig)99 static int addr_cmp(void *addr, int sig)
100 {
101     uint32_t addr32[4], rem32[4];
102     int bitstozero, i, start = 0, len = sizeof(uint32_t);
103     char *ptr;
104 
105 #ifdef INET6
106     if (his_addr_family == AF_INET) {
107 	if (his_v4mapped) {
108 	    ptr = (char *)&((struct sockaddr_in6 *)&his_addr)->sin6_addr;
109 	    /* move to the IPv4 part of an IPv4-mapped IPv6 address */
110 	    ptr += 12;
111 	}
112 	else
113 #endif
114 	    ptr = (char *)&((struct sockaddr_in *)&his_addr)->sin_addr;
115 
116 	/* IPv4 addresses are 32-bits long */
117 	bitstozero = 32 - sig;
118 	memcpy(addr32, addr, sizeof(uint32_t));
119 	memcpy(rem32, ptr, sizeof(uint32_t));
120 #ifdef INET6
121     }
122     else {
123 	/* IPv6 addresses are 128-bits long */
124 	bitstozero = 128 - sig;
125 	start = 3;
126 	len = sizeof(addr32);
127 	memcpy(addr32, addr, sizeof(addr32));
128 	memcpy(rem32, &((struct sockaddr_in6 *)&his_addr)->sin6_addr, sizeof(rem32));
129     }
130 #endif
131 
132     /* zero bits starting with the least significant */
133     for (i = start; (bitstozero > 0) && (i >= 0); i--, bitstozero -= 32) {
134 	if (bitstozero >= 32)
135 	    addr32[i] = rem32[i] = 0;
136 	else {
137 	    addr32[i] = (ntohl(addr32[i]) >> bitstozero) << bitstozero;
138 	    rem32[i] = (ntohl(rem32[i]) >> bitstozero) << bitstozero;
139 	}
140     }
141 
142     /* compare the IP addresses */
143     return memcmp(addr32, rem32, len);
144 }
145 
146 /*
147  * Matches a supplied IP address string against the address the client
148  * connected from (in his_addr). Returns 1 and updates sig if the addresses
149  * match and there hasn't already been a more specific match, zero otherwise.
150  */
better_match(char * addrstr,int * sig)151 static int better_match(char *addrstr, int *sig)
152 {
153     int addr_sig, max_sig = 32;
154     char *ptr;
155     void *addr;
156 #ifdef INET6
157     int rval;
158     struct in6_addr in6;
159 #else
160     struct in_addr in;
161 #endif
162 
163     /* look for the optional significance (/CIDR) */
164     if ((ptr = strstr(addrstr, "/")))
165 	*ptr = '\0';
166 
167 #ifdef INET6
168     if (his_addr_family == AF_INET6)
169 	max_sig = 128;
170 #endif
171 
172     if (ptr) {
173 	addr_sig = atoi(++ptr);
174 	if (addr_sig < 0)
175 	    addr_sig = 0;
176 	else if (addr_sig > max_sig)
177 	    addr_sig = max_sig;
178     }
179     else
180 	addr_sig = max_sig;
181 
182     /* return if we already have a more specific match */
183     if (addr_sig < *sig) {
184 	if (ptr)
185 	    *--ptr = '/';
186 	return 0;
187     }
188 
189 #ifdef INET6
190     rval = inet_pton6(addrstr, &in6);
191     if (ptr)
192 	*--ptr = '/';
193     if (rval != 1)
194 	return 0;
195 
196     if (his_addr_family == AF_INET) {
197 	/* convert IPv4-mapped IPv6 addresses to IPv4 addresses */
198 	if (IN6_IS_ADDR_V4MAPPED(&in6))
199 	    addr = &in6.s6_addr[12];
200 	else
201 	    return 0;
202     }
203     else
204 	addr = &in6.s6_addr;
205 #else
206     in.s_addr = inet_addr(addrstr);
207     if (ptr)
208 	*--ptr = '/';
209     if ((int)in.s_addr == -1)
210 	return 0;
211     addr = &in.s_addr;
212 #endif
213 
214     if (addr_cmp(addr, addr_sig) == 0) {
215 	*sig = addr_sig;
216 	return 1;
217     }
218     return 0;
219 }
220 
update_address(char * externalip,char * addrstr)221 static void update_address(char *externalip, char *addrstr)
222 {
223     struct SOCKSTORAGE ext_addr;
224 #ifndef INET6
225     struct in_addr in;
226 #endif
227 
228     /* validate the external IP address string */
229 #ifdef INET6
230     SET_SOCK_FAMILY(ext_addr, AF_INET6);
231     if (inet_pton6(externalip, SOCK_ADDR(ext_addr)) != 1)
232 	return;
233     if ((his_addr_family == AF_INET) &&
234 	!IN6_IS_ADDR_V4MAPPED((struct in6_addr *)SOCK_ADDR(ext_addr)))
235 	return;
236 #else
237     if ((int)(in.s_addr = inet_addr(externalip)) == -1)
238 	return;
239     SET_SOCK_FAMILY(ext_addr, AF_INET);
240     SET_SOCK_ADDR4(ext_addr, in);
241 #endif
242 
243     if (better_match(addrstr, &vect_sig))
244 	vect_addr = ext_addr;
245 }
246 
update_ports(char * addrstr,char * minport,char * maxport)247 static void update_ports(char *addrstr, char *minport, char *maxport)
248 {
249     int min, max;
250 
251     min = atoi(minport);
252     max = atoi(maxport);
253 
254     /* validate the ports supplied */
255     if ((min > max) || (min < 0) || (max > 65535) || (min == 0 && max != 0)) {
256 	syslog(LOG_WARNING, "ftpaccess passive ports entry invalid: %s %s %s", addrstr, minport, maxport);
257 	return;
258     }
259 
260     if (better_match(addrstr, &port_sig)) {
261 	passive_port_min = min;
262 	passive_port_max = max;
263     }
264 }
265 
routevector(void)266 int routevector(void)
267 {
268     struct aclmember *entry = NULL;
269 
270 #ifdef INET6
271     if (SOCK_FAMILY(his_addr) == AF_INET6) {
272 	if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)&(his_addr))->sin6_addr))
273 	    his_v4mapped = 1;
274 	else
275 	    his_addr_family = AF_INET6;
276     }
277 #endif
278 
279     while (getaclentry("passive", &entry)) {
280 	if (!strcasecmp(ARG0, "address")) {
281 	    if (!ARG1 || !ARG2)
282 		continue;
283 	    update_address(ARG1, ARG2);
284 	}
285 	if (!strcasecmp(ARG0, "ports")) {
286 	    if (!ARG1 || !ARG2 || !ARG3)
287 		continue;
288 	    update_ports(ARG1, ARG2, ARG3);
289 	}
290     }
291     return vect_sig != -1;
292 }
293