configure.c revision 4c5a5fb53bccceff331bae70f748bf9b4609fe0a
1/*
2 * dhcpcd - DHCP client daemon
3 * Copyright 2006-2008 Roy Marples <roy@marples.name>
4 * All rights reserved
5
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/stat.h>
29#include <sys/wait.h>
30
31#include <netinet/in.h>
32#include <arpa/inet.h>
33
34#include <ctype.h>
35#include <errno.h>
36#include <signal.h>
37#include <stdlib.h>
38#include <unistd.h>
39
40#include "config.h"
41#include "common.h"
42#include "configure.h"
43#include "dhcp.h"
44#include "dhcpcd.h"
45#include "logger.h"
46#include "net.h"
47#include "signals.h"
48
49#define DEFAULT_PATH	"PATH=/usr/bin:/usr/sbin:/bin:/sbin"
50
51
52static int
53exec_script(char *const *argv, char *const *env)
54{
55	pid_t pid;
56	sigset_t full;
57	sigset_t old;
58
59	/* OK, we need to block signals */
60	sigfillset(&full);
61	sigprocmask(SIG_SETMASK, &full, &old);
62	signal_reset();
63
64	switch (pid = vfork()) {
65	case -1:
66		logger(LOG_ERR, "vfork: %s", strerror(errno));
67		break;
68	case 0:
69		sigprocmask(SIG_SETMASK, &old, NULL);
70		execve(argv[0], argv, env);
71		logger(LOG_ERR, "%s: %s", argv[0], strerror(errno));
72		_exit(127);
73		/* NOTREACHED */
74	}
75
76	/* Restore our signals */
77	signal_setup();
78	sigprocmask(SIG_SETMASK, &old, NULL);
79	return pid;
80}
81
82int
83run_script(const struct options *options, const char *iface,
84           const char *reason,
85           const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo)
86{
87	char *const argv[2] = { UNCONST(options->script), NULL };
88	char **env = NULL, **ep;
89	char *path;
90	ssize_t e, elen;
91	pid_t pid;
92	int status = 0;
93
94	logger(LOG_DEBUG, "executing `%s', reason %s", options->script, reason);
95
96	/* Make our env */
97	elen = 5;
98	env = xmalloc(sizeof(char *) * (elen + 1));
99	path = getenv("PATH");
100	if (path) {
101		e = strlen("PATH") + strlen(path) + 2;
102		env[0] = xmalloc(e);
103		snprintf(env[0], e, "PATH=%s", path);
104	} else
105		env[0] = xstrdup(DEFAULT_PATH);
106	e = strlen("interface") + strlen(iface) + 2;
107	env[1] = xmalloc(e);
108	snprintf(env[1], e, "interface=%s", iface);
109	e = strlen("reason") + strlen(reason) + 2;
110	env[2] = xmalloc(e);
111	snprintf(env[2], e, "reason=%s", reason);
112	e = 20;
113	env[3] = xmalloc(e);
114	snprintf(env[3], e, "pid=%d", getpid());
115	env[4] = xmalloc(e);
116	snprintf(env[4], e, "metric=%d", options->metric);
117	if (dhcpo) {
118		e = configure_env(NULL, NULL, dhcpo, options);
119		if (e > 0) {
120			env = xrealloc(env, sizeof(char *) * (elen + e + 1));
121			elen += configure_env(env + elen, "old", dhcpo, options);
122		}
123	}
124	if (dhcpn) {
125		e = configure_env(NULL, NULL, dhcpn, options);
126		if (e > 0) {
127			env = xrealloc(env, sizeof(char *) * (elen + e + 1));
128			elen += configure_env(env + elen, "new", dhcpn, options);
129		}
130	}
131	/* Add our base environment */
132	if (options->environ) {
133		e = 0;
134		while (options->environ[e++])
135			;
136		env = xrealloc(env, sizeof(char *) * (elen + e + 1));
137		e = 0;
138		while (options->environ[e]) {
139			env[elen + e] = xstrdup(options->environ[e]);
140			e++;
141		}
142		elen += e;
143	}
144	env[elen] = '\0';
145
146	pid = exec_script(argv, env);
147	if (pid == -1)
148		status = -1;
149	else if (pid != 0) {
150		/* Wait for the script to finish */
151		while (waitpid(pid, &status, 0) == -1) {
152			if (errno != EINTR) {
153				logger(LOG_ERR, "waitpid: %s", strerror(errno));
154				status = -1;
155				break;
156			}
157		}
158	}
159
160	/* Cleanup */
161	ep = env;
162	while (*ep)
163		free(*ep++);
164	free(env);
165	return status;
166}
167
168static struct rt *
169reverse_routes(struct rt *routes)
170{
171	struct rt *rt;
172	struct rt *rtn = NULL;
173
174	while (routes) {
175		rt = routes->next;
176		routes->next = rtn;
177		rtn = routes;
178		routes = rt;
179	}
180	return rtn;
181}
182
183static int
184delete_route(const char *iface, struct rt *rt, int metric)
185{
186	char *addr;
187	int retval;
188
189	addr = xstrdup(inet_ntoa(rt->dest));
190	logger(LOG_DEBUG, "deleting route %s/%d via %s",
191	       addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate));
192	free(addr);
193	retval = del_route(iface, &rt->dest, &rt->net, &rt->gate, metric);
194	if (retval != 0 && errno != ENOENT && errno != ESRCH)
195		logger(LOG_ERR," del_route: %s", strerror(errno));
196	return retval;
197}
198
199static int
200delete_routes(struct interface *iface, int metric)
201{
202	struct rt *rt;
203	struct rt *rtn;
204	int retval = 0;
205
206	rt = reverse_routes(iface->routes);
207	while (rt) {
208		rtn = rt->next;
209		retval += delete_route(iface->name, rt, metric);
210		free(rt);
211		rt = rtn;
212	}
213	iface->routes = NULL;
214
215	return retval;
216}
217
218static int
219in_routes(const struct rt *routes, const struct rt *rt)
220{
221	while (routes) {
222		if (routes->dest.s_addr == rt->dest.s_addr &&
223				routes->net.s_addr == rt->net.s_addr &&
224				routes->gate.s_addr == rt->gate.s_addr)
225			return 0;
226		routes = routes->next;
227	}
228	return -1;
229}
230
231static int
232configure_routes(struct interface *iface, const struct dhcp_message *dhcp,
233		 const struct options *options)
234{
235	struct rt *rt, *ort;
236	struct rt *rtn = NULL, *nr = NULL;
237	int remember;
238	int retval = 0;
239	char *addr;
240
241	ort = get_option_routes(dhcp);
242
243#ifdef IPV4LL_ALWAYSROUTE
244	if (options->options & DHCPCD_IPV4LL &&
245	    IN_PRIVATE(ntohl(dhcp->yiaddr)))
246	{
247		for (rt = ort; rt; rt = rt->next) {
248			/* Check if we have already got a link locale route
249			 * dished out by the DHCP server */
250			if (rt->dest.s_addr == htonl(LINKLOCAL_ADDR) &&
251			    rt->net.s_addr == htonl(LINKLOCAL_MASK))
252				break;
253			rtn = rt;
254		}
255
256		if (!rt) {
257			rt = xmalloc(sizeof(*rt));
258			rt->dest.s_addr = htonl(LINKLOCAL_ADDR);
259			rt->net.s_addr = htonl(LINKLOCAL_MASK);
260			rt->gate.s_addr = 0;
261			rt->next = NULL;
262			if (rtn)
263				rtn->next = rt;
264			else
265				ort = rt;
266		}
267	}
268#endif
269
270	/* Now remove old routes we no longer use.
271 	 * We should do this in reverse order. */
272	iface->routes = reverse_routes(iface->routes);
273	for (rt = iface->routes; rt; rt = rt->next)
274		if (in_routes(ort, rt) != 0)
275			delete_route(iface->name, rt, options->metric);
276
277	for (rt = ort; rt; rt = rt->next) {
278		/* Don't set default routes if not asked to */
279		if (rt->dest.s_addr == 0 &&
280		    rt->net.s_addr == 0 &&
281		    !(options->options & DHCPCD_GATEWAY))
282			continue;
283
284		addr = xstrdup(inet_ntoa(rt->dest));
285		logger(LOG_DEBUG, "adding route to %s/%d via %s",
286			addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate));
287		free(addr);
288		remember = add_route(iface->name, &rt->dest,
289				     &rt->net, &rt->gate,
290				     options->metric);
291		retval += remember;
292
293		/* If we failed to add the route, we may have already added it
294		   ourselves. If so, remember it again. */
295		if (remember < 0) {
296			if (errno != EEXIST)
297				logger(LOG_ERR, "add_route: %s",
298				       strerror(errno));
299			if (in_routes(iface->routes, rt) == 0)
300				remember = 1;
301		}
302
303		/* This login is split from above due to the #ifdef below */
304		if (remember >= 0) {
305			if (nr) {
306				rtn->next = xmalloc(sizeof(*rtn));
307				rtn = rtn->next;
308			} else {
309				nr = rtn = xmalloc(sizeof(*rtn));
310			}
311			rtn->dest.s_addr = rt->dest.s_addr;
312			rtn->net.s_addr = rt->net.s_addr;
313			rtn->gate.s_addr = rt->gate.s_addr;
314			rtn->next = NULL;
315		}
316	}
317	free_routes(ort);
318	free_routes(iface->routes);
319	iface->routes = nr;
320	return retval;
321}
322
323static int
324delete_address(struct interface *iface)
325{
326	int retval;
327	logger(LOG_DEBUG, "deleting IP address %s/%d",
328	       inet_ntoa(iface->addr),
329	       inet_ntocidr(iface->net));
330	retval = del_address(iface->name, &iface->addr, &iface->net);
331	if (retval == -1 && errno != EADDRNOTAVAIL)
332		logger(LOG_ERR, "del_address: %s", strerror(errno));
333	iface->addr.s_addr = 0;
334	iface->net.s_addr = 0;
335	return retval;
336}
337
338int
339configure(struct interface *iface, const char *reason,
340	  const struct dhcp_message *dhcp, const struct dhcp_message *old,
341	  const struct dhcp_lease *lease, const struct options *options,
342	  int up)
343{
344	struct in_addr addr;
345	struct in_addr net;
346	struct in_addr brd;
347#ifdef __linux__
348	struct in_addr dest;
349	struct in_addr gate;
350#endif
351
352	/* Grab our IP config */
353	if (dhcp == NULL)
354		up = 0;
355	else {
356		addr.s_addr = dhcp->yiaddr;
357		if (addr.s_addr == 0)
358			addr.s_addr = lease->addr.s_addr;
359		/* Ensure we have all the needed values */
360		if (get_option_addr(&net.s_addr, dhcp, DHO_SUBNETMASK) == -1)
361			net.s_addr = get_netmask(addr.s_addr);
362		if (get_option_addr(&brd.s_addr, dhcp, DHO_BROADCAST) == -1)
363			brd.s_addr = addr.s_addr | ~net.s_addr;
364	}
365
366	/* If we aren't up, then reset the interface as much as we can */
367	if (!up) {
368		/* Only reset things if we had set them before */
369		if (iface->addr.s_addr != 0) {
370			delete_routes(iface, options->metric);
371			delete_address(iface);
372		}
373
374		run_script(options, iface->name, reason, NULL, old);
375		return 0;
376	}
377
378	/* This also changes netmask */
379	if (!(options->options & DHCPCD_INFORM) ||
380	    !has_address(iface->name, &addr, &net)) {
381		logger(LOG_DEBUG, "adding IP address %s/%d",
382		       inet_ntoa(addr), inet_ntocidr(net));
383		if (add_address(iface->name, &addr, &net, &brd) == -1 &&
384		    errno != EEXIST)
385		{
386			logger(LOG_ERR, "add_address: %s", strerror(errno));
387			return -1;
388		}
389	}
390
391	/* Now delete the old address if different */
392	if (iface->addr.s_addr != addr.s_addr &&
393	    iface->addr.s_addr != 0)
394		delete_address(iface);
395
396#ifdef __linux__
397	/* On linux, we need to change the subnet route to have our metric. */
398	if (iface->addr.s_addr != lease->addr.s_addr &&
399	    options->metric > 0 && net.s_addr != INADDR_BROADCAST)
400	{
401		dest.s_addr = addr.s_addr & net.s_addr;
402		gate.s_addr = 0;
403		add_route(iface->name, &dest, &net, &gate, options->metric);
404		del_route(iface->name, &dest, &net, &gate, 0);
405	}
406#endif
407
408	configure_routes(iface, dhcp, options);
409	up = (iface->addr.s_addr != addr.s_addr ||
410	      iface->net.s_addr != net.s_addr);
411	iface->addr.s_addr = addr.s_addr;
412	iface->net.s_addr = net.s_addr;
413
414	if (!lease->frominfo)
415		if (write_lease(iface, dhcp) == -1)
416			logger(LOG_ERR, "write_lease: %s", strerror(errno));
417
418	run_script(options, iface->name, reason, dhcp, old);
419	return 0;
420}
421