1/*	$NetBSD: bootparamd.c,v 1.45 2009/04/18 12:57:10 lukem Exp $	*/
2
3/*
4 * This code is not copyright, and is placed in the public domain.
5 * Feel free to use and modify. Please send modifications and/or
6 * suggestions + bug fixes to Klas Heggemann <klas@nada.kth.se>
7 *
8 * Various small changes by Theo de Raadt <deraadt@fsa.ca>
9 * Parser rewritten (adding YP support) by Roland McGrath <roland@frob.com>
10 */
11
12#include <sys/cdefs.h>
13#ifndef lint
14__RCSID("$NetBSD: bootparamd.c,v 1.45 2009/04/18 12:57:10 lukem Exp $");
15#endif
16
17#include <sys/types.h>
18#include <sys/ioctl.h>
19#include <sys/stat.h>
20#include <sys/socket.h>
21
22#include <assert.h>
23#include <ctype.h>
24#include <errno.h>
25#include <err.h>
26#include <fnmatch.h>
27#include <netdb.h>
28#include <stdlib.h>
29#include <stdio.h>
30#include <string.h>
31#include <syslog.h>
32#include <unistd.h>
33#include <util.h>
34#include <ifaddrs.h>
35
36#include <net/if.h>
37
38#include <netinet/in.h>
39#include <arpa/inet.h>
40
41#include <rpc/rpc.h>
42#include <rpc/pmap_clnt.h>
43#include <rpcsvc/bootparam_prot.h>
44#ifdef YP
45#include <rpcsvc/ypclnt.h>
46#endif
47
48#include "pathnames.h"
49
50#define MAXLEN 800
51
52static char hostname[MAX_MACHINE_NAME];
53static char askname[MAX_MACHINE_NAME];
54static char domain_name[MAX_MACHINE_NAME];
55
56extern void bootparamprog_1(struct svc_req *, SVCXPRT *);
57
58int	_rpcsvcdirty = 0;
59int	_rpcpmstart = 0;
60int     debug = 0;
61int     dolog = 0;
62struct in_addr route_addr;
63struct sockaddr_in my_addr;
64const char *bootpfile = _PATH_BOOTPARAMS;
65char   *iface = NULL;
66
67static int	lookup_bootparam(char *, char *, char *, char **, char **);
68__dead static void	usage(void);
69static int get_localaddr(const char *, struct sockaddr_in *);
70
71
72/*
73 * ever familiar
74 */
75int
76main(int argc, char  *argv[])
77{
78	SVCXPRT *transp;
79	struct hostent *he;
80	struct stat buf;
81	int    c;
82
83	while ((c = getopt(argc, argv, "di:sr:f:")) != -1)
84		switch (c) {
85		case 'd':
86			debug = 1;
87			break;
88		case 'i':
89			iface = optarg;
90			break;
91		case 'r':
92			if (isdigit((unsigned char)*optarg)) {
93				if (inet_aton(optarg, &route_addr) != 0)
94					break;
95			}
96			he = gethostbyname(optarg);
97			if (he == 0) {
98				warnx("no such host: %s", optarg);
99				usage();
100			}
101			memmove(&route_addr.s_addr, he->h_addr, he->h_length);
102			break;
103		case 'f':
104			bootpfile = optarg;
105			break;
106		case 's':
107			dolog = 1;
108#ifndef LOG_DAEMON
109			openlog("rpc.bootparamd", 0, 0);
110#else
111			openlog("rpc.bootparamd", 0, LOG_DAEMON);
112			setlogmask(LOG_UPTO(LOG_NOTICE));
113#endif
114			break;
115		default:
116			usage();
117		}
118
119	if (stat(bootpfile, &buf))
120		err(1, "%s", bootpfile);
121
122	if (route_addr.s_addr == 0) {
123		if (get_localaddr(NULL, &my_addr) != 0)
124			errx(1, "router address not found");
125		route_addr.s_addr = my_addr.sin_addr.s_addr;
126	}
127	if (!debug) {
128		if (daemon(0, 0))
129			err(1, "can't detach from terminal");
130	}
131	pidfile(NULL);
132
133	(void) pmap_unset(BOOTPARAMPROG, BOOTPARAMVERS);
134
135	transp = svcudp_create(RPC_ANYSOCK);
136	if (transp == NULL)
137		errx(1, "can't create udp service");
138
139	if (!svc_register(transp, BOOTPARAMPROG, BOOTPARAMVERS, bootparamprog_1,
140	    IPPROTO_UDP))
141/*
142 * Do NOT change the "%u" in the format string below to "%lu". If your
143 * build fails update the "rpcgen" program and use "make cleandir" and
144 * "make includes" in "src/lib/librpcsvc" afterwards.
145 */
146		errx(1, "unable to register BOOTPARAMPROG version %u, udp",
147		    BOOTPARAMVERS);
148
149	svc_run();
150	errx(1, "svc_run returned");
151}
152
153bp_whoami_res *
154bootparamproc_whoami_1_svc(bp_whoami_arg *whoami, struct svc_req *rqstp)
155{
156	static bp_whoami_res res;
157	struct hostent *he;
158	struct in_addr haddr;
159	int e;
160
161	if (debug)
162		warnx("whoami got question for %d.%d.%d.%d",
163		    255 & whoami->client_address.bp_address_u.ip_addr.net,
164		    255 & whoami->client_address.bp_address_u.ip_addr.host,
165		    255 & whoami->client_address.bp_address_u.ip_addr.lh,
166		    255 & whoami->client_address.bp_address_u.ip_addr.impno);
167	if (dolog)
168		syslog(LOG_NOTICE, "whoami got question for %d.%d.%d.%d",
169		    255 & whoami->client_address.bp_address_u.ip_addr.net,
170		    255 & whoami->client_address.bp_address_u.ip_addr.host,
171		    255 & whoami->client_address.bp_address_u.ip_addr.lh,
172		    255 & whoami->client_address.bp_address_u.ip_addr.impno);
173
174	memmove((char *) &haddr,
175	    (char *) &whoami->client_address.bp_address_u.ip_addr,
176	    sizeof(haddr));
177	he = gethostbyaddr((char *) &haddr, sizeof(haddr), AF_INET);
178	if (he) {
179		(void)strlcpy(askname, he->h_name, sizeof(askname));
180	} else {
181		(void)strlcpy(askname, inet_ntoa(haddr), sizeof(askname));
182	}
183
184	if (debug)
185		warnx("This is host %s", askname);
186	if (dolog)
187		syslog(LOG_NOTICE, "This is host %s", askname);
188
189	if ((e = lookup_bootparam(askname, hostname, NULL, NULL, NULL)) == 0) {
190		res.client_name = hostname;
191		getdomainname(domain_name, MAX_MACHINE_NAME);
192		res.domain_name = domain_name;
193
194		if (res.router_address.address_type != IP_ADDR_TYPE) {
195			res.router_address.address_type = IP_ADDR_TYPE;
196			memmove(&res.router_address.bp_address_u.ip_addr,
197			    &route_addr.s_addr,4);
198		}
199		if (debug)
200			warnx("Returning %s   %s    %d.%d.%d.%d",
201			    res.client_name, res.domain_name,
202			    255 & res.router_address.bp_address_u.ip_addr.net,
203			    255 & res.router_address.bp_address_u.ip_addr.host,
204			    255 & res.router_address.bp_address_u.ip_addr.lh,
205			    255 &res.router_address.bp_address_u.ip_addr.impno);
206		if (dolog)
207			syslog(LOG_NOTICE, "Returning %s   %s    %d.%d.%d.%d",
208			    res.client_name, res.domain_name,
209			    255 & res.router_address.bp_address_u.ip_addr.net,
210			    255 & res.router_address.bp_address_u.ip_addr.host,
211			    255 & res.router_address.bp_address_u.ip_addr.lh,
212			    255 &res.router_address.bp_address_u.ip_addr.impno);
213
214		return (&res);
215	}
216	errno = e;
217	if (debug)
218		warn("whoami failed");
219	if (dolog)
220		syslog(LOG_NOTICE, "whoami failed %m");
221	return (NULL);
222}
223
224
225bp_getfile_res *
226bootparamproc_getfile_1_svc(bp_getfile_arg *getfile, struct svc_req *rqstp)
227{
228	static bp_getfile_res res;
229	struct hostent *he;
230	int     error;
231
232	if (debug)
233		warnx("getfile got question for \"%s\" and file \"%s\"",
234		    getfile->client_name, getfile->file_id);
235
236	if (dolog)
237		syslog(LOG_NOTICE,
238		    "getfile got question for \"%s\" and file \"%s\"",
239		    getfile->client_name, getfile->file_id);
240
241	he = NULL;
242	he = gethostbyname(getfile->client_name);
243	if (!he) {
244		if (debug)
245			warnx("getfile can't resolve client %s",
246			    getfile->client_name);
247		if (dolog)
248			syslog(LOG_NOTICE, "getfile can't resolve client %s",
249			    getfile->client_name);
250		return (NULL);
251	}
252
253	(void)strlcpy(askname, he->h_name, sizeof(askname));
254	error = lookup_bootparam(askname, NULL, getfile->file_id,
255	    &res.server_name, &res.server_path);
256	if (error == 0) {
257		he = gethostbyname(res.server_name);
258		if (!he) {
259			if (debug)
260				warnx("getfile can't resolve server %s for %s",
261			    res.server_name, getfile->client_name);
262		if (dolog)
263			syslog(LOG_NOTICE,
264			    "getfile can't resolve server %s for %s",
265			    res.server_name, getfile->client_name);
266		return (NULL);
267
268		}
269		memmove(&res.server_address.bp_address_u.ip_addr,
270		    he->h_addr, 4);
271		res.server_address.address_type = IP_ADDR_TYPE;
272	} else if (error == ENOENT && !strcmp(getfile->file_id, "dump")) {
273		/* Special for dump, answer with null strings. */
274		res.server_name[0] = '\0';
275		res.server_path[0] = '\0';
276		memset(&res.server_address.bp_address_u.ip_addr, 0, 4);
277	} else {
278		if (debug)
279			warnx("getfile lookup failed for %s",
280			    getfile->client_name);
281		if (dolog)
282			syslog(LOG_NOTICE,
283			    "getfile lookup failed for %s",
284			    getfile->client_name);
285		return (NULL);
286	}
287
288	if (debug)
289		warnx(
290		    "returning server:%s path:%s address: %d.%d.%d.%d",
291		    res.server_name, res.server_path,
292		    255 & res.server_address.bp_address_u.ip_addr.net,
293		    255 & res.server_address.bp_address_u.ip_addr.host,
294		    255 & res.server_address.bp_address_u.ip_addr.lh,
295		    255 & res.server_address.bp_address_u.ip_addr.impno);
296	if (dolog)
297		syslog(LOG_NOTICE,
298		    "returning server:%s path:%s address: %d.%d.%d.%d",
299		    res.server_name, res.server_path,
300		    255 & res.server_address.bp_address_u.ip_addr.net,
301		    255 & res.server_address.bp_address_u.ip_addr.host,
302		    255 & res.server_address.bp_address_u.ip_addr.lh,
303		    255 & res.server_address.bp_address_u.ip_addr.impno);
304	return (&res);
305}
306
307
308static int
309lookup_bootparam(char *client, char *client_canonical, char *id,
310    char **server, char **path)
311{
312	FILE   *f = fopen(bootpfile, "r");
313#ifdef YP
314	static char *ypbuf = NULL;
315	static int ypbuflen = 0;
316#endif
317	static char buf[BUFSIZ];
318	char   *canon = NULL, *bp, *word = NULL;
319	size_t  idlen = id == NULL ? 0 : strlen(id);
320	int     contin = 0;
321	int     found = 0;
322
323	if (f == NULL)
324		return EINVAL;	/* ? */
325
326	while (fgets(buf, sizeof buf, f)) {
327		int     wascontin = contin;
328		contin = buf[strlen(buf) - 2] == '\\';
329		bp = buf + strspn(buf, " \t\n");
330
331		switch (wascontin) {
332		case -1:
333			/* Continuation of uninteresting line */
334			contin *= -1;
335			continue;
336		case 0:
337			/* New line */
338			contin *= -1;
339			if (*bp == '#')
340				continue;
341			if ((word = strsep(&bp, " \t\n")) == NULL)
342				continue;
343#ifdef YP
344			/* A + in the file means try YP now */
345			if (!strcmp(word, "+")) {
346				char   *ypdom;
347
348				if (yp_get_default_domain(&ypdom) ||
349				    yp_match(ypdom, "bootparams", client,
350					strlen(client), &ypbuf, &ypbuflen))
351					continue;
352				bp = ypbuf;
353				word = client;
354				contin *= -1;
355				break;
356			}
357#endif
358			if (debug)
359				warnx("match %s with %s", word, client);
360
361#define	HASGLOB(str) \
362	(strchr(str, '*') != NULL || \
363	 strchr(str, '?') != NULL || \
364	 strchr(str, '[') != NULL || \
365	 strchr(str, ']') != NULL)
366
367			/* See if this line's client is the one we are
368			 * looking for */
369			if (fnmatch(word, client, FNM_CASEFOLD) == 0) {
370				/*
371				 * Match.  The token may be globbed, we
372				 * can't just return that as the canonical
373				 * name.  Check to see if the token has any
374				 * globbing characters in it (*, ?, [, ]).
375				 * If so, just return the name we already
376				 * have.  Otherwise, return the token.
377				 */
378				if (HASGLOB(word))
379					canon = client;
380				else
381					canon = word;
382			} else {
383				struct hostent *hp;
384				/*
385				 * If it didn't match, try getting the
386				 * canonical host name of the client
387				 * on this line, if it's not a glob,
388				 * and comparing it to the client we
389				 * are looking up.
390				 */
391				if (HASGLOB(word)) {
392					if (debug)
393						warnx("Skipping non-match: %s",
394						    word);
395					continue;
396				}
397				if ((hp = gethostbyname(word)) == NULL) {
398					if (debug)
399						warnx(
400					    "Unknown bootparams host %s",
401					    word);
402					if (dolog)
403						syslog(LOG_NOTICE,
404					    "Unknown bootparams host %s",
405					    word);
406					continue;
407				}
408				if (strcasecmp(hp->h_name, client) != 0)
409					continue;
410				canon = hp->h_name;
411			}
412
413#undef HASGLOB
414
415			contin *= -1;
416			break;
417		case 1:
418			/* Continued line we want to parse below */
419			break;
420		}
421
422		assert(canon != NULL);
423		if (client_canonical)
424			strncpy(client_canonical, canon, MAX_MACHINE_NAME);
425
426		/* We have found a line for CLIENT */
427		if (id == NULL) {
428			(void) fclose(f);
429			return 0;
430		}
431
432		/* Look for a value for the parameter named by ID */
433		while ((word = strsep(&bp, " \t\n")) != NULL) {
434			if (!strncmp(word, id, idlen) && word[idlen] == '=') {
435				/* We have found the entry we want */
436				*server = &word[idlen + 1];
437				*path = strchr(*server, ':');
438				if (*path == NULL)
439					/* Malformed entry */
440					continue;
441				*(*path)++ = '\0';
442				(void) fclose(f);
443				return 0;
444			}
445		}
446
447		found = 1;
448	}
449
450	(void) fclose(f);
451	return found ? ENOENT : EPERM;
452}
453
454static void
455usage(void)
456{
457	fprintf(stderr,
458	    "usage: %s [-ds] [-i interface] [-r router] [-f bootparamsfile]\n",
459	    getprogname());
460	exit(1);
461}
462
463static int
464get_localaddr(const char *ifname, struct sockaddr_in *sin)
465{
466	struct ifaddrs *ifap, *ifa;
467
468	if (getifaddrs(&ifap) != 0)
469		return -1;
470
471	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
472		if (ifname && strcmp(ifname, ifa->ifa_name) != 0)
473			continue;
474		if (ifa->ifa_addr->sa_family != AF_INET)
475			continue;
476		if (ifa->ifa_addr->sa_len != sizeof(*sin))
477			continue;
478
479		/* no loopback please */
480#ifdef IFF_LOOPBACK
481		if (ifa->ifa_flags & IFF_LOOPBACK)
482			continue;
483#else
484		if (strncmp(ifa->ifa_name, "lo", 2) == 0 &&
485		    (isdigit(ifa->ifa_name[2]) || ifa->ifa_name[2] == '\0'))
486			continue;
487#endif
488
489		if (!iface || strcmp(ifa->ifa_name, iface) == 0)
490			;
491		else
492			continue;
493
494		/* candidate found */
495		memcpy(sin, ifa->ifa_addr, ifa->ifa_addr->sa_len);
496		freeifaddrs(ifap);
497		return 0;
498	}
499
500	/* no candidate */
501	freeifaddrs(ifap);
502	return -1;
503}
504