1/*
2 * Copyright (c) 1988, 1990, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include <sys/types.h>
35#include <sys/socket.h>
36
37#include <netdb.h>
38#include <netinet/in.h>
39#include <netinet/ip.h>
40
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <errno.h>
45
46/*
47 * 5/10/12: Imported from telnet project
48 *
49 * Source route is handed in as
50 *	[!]@hop1@hop2...[@|:]dst
51 * If the leading ! is present, it is a
52 * strict source route, otherwise it is
53 * assmed to be a loose source route.
54 *
55 * We fill in the source route option as
56 *	hop1,hop2,hop3...dest
57 * and return a pointer to hop1, which will
58 * be the address to connect() to.
59 *
60 * Arguments:
61 *
62 *	res:	ponter to addrinfo structure which contains sockaddr to
63 *		the host to connect to.
64 *
65 *	arg:	pointer to route list to decipher
66 *
67 *	cpp:	If *cpp is not equal to NULL, this is a
68 *		pointer to a pointer to a character array
69 *		that should be filled in with the option.
70 *
71 *	lenp:	pointer to an integer that contains the
72 *		length of *cpp if *cpp != NULL.
73 *
74 *	protop: pointer to an integer that should be filled in with
75 *		appropriate protocol for setsockopt, as socket
76 *		protocol family.
77 *
78 *	optp:	pointer to an integer that should be filled in with
79 *		appropriate option for setsockopt, as socket protocol
80 *		family.
81 *
82 * Return values:
83 *
84 *	If the return value is 1, then all operations are
85 *	successful. If the
86 *	return value is -1, there was a syntax error in the
87 *	option, either unknown characters, or too many hosts.
88 *	If the return value is 0, one of the hostnames in the
89 *	path is unknown, and *cpp is set to point to the bad
90 *	hostname.
91 *
92 *	*cpp:	If *cpp was equal to NULL, it will be filled
93 *		in with a pointer to our static area that has
94 *		the option filled in.  This will be 32bit aligned.
95 *
96 *	*lenp:	This will be filled in with how long the option
97 *		pointed to by *cpp is.
98 *
99 *	*protop: This will be filled in with appropriate protocol for
100 *		 setsockopt, as socket protocol family.
101 *
102 *	*optp:	This will be filled in with appropriate option for
103 *		setsockopt, as socket protocol family.
104 */
105int
106sourceroute(struct addrinfo *ai, char *arg, char **cpp, int *lenp, int *protop, int *optp)
107{
108	static char buf[1024 + ALIGNBYTES];	/*XXX*/
109	char *cp, *cp2, *lsrp, *ep;
110	struct sockaddr_in *_sin;
111#ifdef INET6
112	struct sockaddr_in6 *sin6;
113	struct cmsghdr *cmsg = NULL;
114#endif
115	struct addrinfo hints, *res;
116	int error;
117	char c;
118
119	/*
120	 * Verify the arguments, and make sure we have
121	 * at least 7 bytes for the option.
122	 */
123	if (cpp == NULL || lenp == NULL)
124		return -1;
125	if (*cpp != NULL) {
126		switch (ai->ai_family) {
127			case AF_INET:
128				if (*lenp < 7)
129					return -1;
130				break;
131#ifdef INET6
132			case AF_INET6:
133				if (*lenp < (int)CMSG_SPACE(sizeof(struct ip6_rthdr) +
134											sizeof(struct in6_addr)))
135					return -1;
136				break;
137#endif
138		}
139	}
140	/*
141	 * Decide whether we have a buffer passed to us,
142	 * or if we need to use our own static buffer.
143	 */
144	if (*cpp) {
145		lsrp = *cpp;
146		ep = lsrp + *lenp;
147	} else {
148		*cpp = lsrp = (char *)ALIGN(buf);
149		ep = lsrp + 1024;
150	}
151
152	cp = arg;
153
154#ifdef INET6
155	if (ai->ai_family == AF_INET6) {
156		cmsg = inet6_rthdr_init(*cpp, IPV6_RTHDR_TYPE_0);
157		if (*cp != '@')
158			return -1;
159		*protop = IPPROTO_IPV6;
160		*optp = IPV6_PKTOPTIONS;
161	} else
162#endif
163	{
164		/*
165		 * Next, decide whether we have a loose source
166		 * route or a strict source route, and fill in
167		 * the begining of the option.
168		 */
169		if (*cp == '!') {
170			cp++;
171			*lsrp++ = IPOPT_SSRR;
172		} else
173			*lsrp++ = IPOPT_LSRR;
174
175		if (*cp != '@')
176			return -1;
177
178		lsrp++;		/* skip over length, we'll fill it in later */
179		*lsrp++ = 4;
180		*protop = IPPROTO_IP;
181		*optp = IP_OPTIONS;
182	}
183
184	cp++;
185	memset(&hints, 0, sizeof(hints));
186	hints.ai_family = ai->ai_family;
187	hints.ai_socktype = SOCK_STREAM;
188	for (c = 0;;) {
189		if (
190#ifdef INET6
191			ai->ai_family != AF_INET6 &&
192#endif
193			c == ':')
194			cp2 = 0;
195		else for (cp2 = cp; (c = *cp2); cp2++) {
196			if (c == ',') {
197				*cp2++ = '\0';
198				if (*cp2 == '@')
199					cp2++;
200			} else if (c == '@') {
201				*cp2++ = '\0';
202			} else if (
203#ifdef INET6
204					   ai->ai_family != AF_INET6 &&
205#endif
206					   c == ':') {
207				*cp2++ = '\0';
208			} else
209				continue;
210			break;
211		}
212		if (!c)
213			cp2 = 0;
214
215		hints.ai_flags = AI_NUMERICHOST;
216		error = getaddrinfo(cp, NULL, &hints, &res);
217#ifdef EAI_NODATA
218		if ((error == EAI_NODATA) || (error == EAI_NONAME))
219#else
220			if (error == EAI_NONAME)
221#endif
222			{
223				hints.ai_flags = 0;
224				error = getaddrinfo(cp, NULL, &hints, &res);
225			}
226		if (error != 0) {
227			fprintf(stderr, "%s: %s\n", cp, gai_strerror(error));
228			if (error == EAI_SYSTEM)
229				fprintf(stderr, "%s: %s\n", cp,
230						strerror(errno));
231			*cpp = cp;
232			return(0);
233		}
234#ifdef INET6
235		if (res->ai_family == AF_INET6) {
236			sin6 = (struct sockaddr_in6 *)res->ai_addr;
237			inet6_rthdr_add(cmsg, &sin6->sin6_addr, IPV6_RTHDR_LOOSE);
238		} else
239#endif
240		{
241			_sin = (struct sockaddr_in *)res->ai_addr;
242			memcpy(lsrp, (char *)&_sin->sin_addr, 4);
243			lsrp += 4;
244		}
245		if (cp2)
246			cp = cp2;
247		else
248			break;
249		/*
250		 * Check to make sure there is space for next address
251		 */
252#ifdef INET6
253		if (res->ai_family == AF_INET6) {
254			if (((char *)CMSG_DATA(cmsg) +
255				 sizeof(struct ip6_rthdr) +
256				 ((inet6_rthdr_segments(cmsg) + 1) *
257				  sizeof(struct in6_addr))) > ep)
258				return -1;
259		} else
260#endif
261			if (lsrp + 4 > ep)
262				return -1;
263		freeaddrinfo(res);
264	}
265#ifdef INET6
266	if (res->ai_family == AF_INET6) {
267		inet6_rthdr_lasthop(cmsg, IPV6_RTHDR_LOOSE);
268		*lenp = cmsg->cmsg_len;
269	} else
270#endif
271	{
272		if ((*(*cpp+IPOPT_OLEN) = lsrp - *cpp) <= 7) {
273			*cpp = 0;
274			*lenp = 0;
275			return -1;
276		}
277		*lsrp++ = IPOPT_NOP; /* 32 bit word align it */
278		*lenp = lsrp - *cpp;
279	}
280	freeaddrinfo(res);
281	return 1;
282}