1/* vi: set sw=4 ts=4: */
2/* script.c
3 *
4 * Functions to call the DHCP client notification scripts
5 *
6 * Russ Dill <Russ.Dill@asu.edu> July 2001
7 *
8 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
9 */
10
11#include "common.h"
12#include "dhcpc.h"
13#include "options.h"
14
15
16/* get a rough idea of how long an option will be (rounding up...) */
17static const uint8_t max_option_length[] = {
18	[OPTION_IP] =		sizeof("255.255.255.255 "),
19	[OPTION_IP_PAIR] =	sizeof("255.255.255.255 ") * 2,
20	[OPTION_STRING] =	1,
21#if ENABLE_FEATURE_RFC3397
22	[OPTION_STR1035] =	1,
23#endif
24	[OPTION_BOOLEAN] =	sizeof("yes "),
25	[OPTION_U8] =		sizeof("255 "),
26	[OPTION_U16] =		sizeof("65535 "),
27	[OPTION_S16] =		sizeof("-32768 "),
28	[OPTION_U32] =		sizeof("4294967295 "),
29	[OPTION_S32] =		sizeof("-2147483684 "),
30};
31
32
33static inline int upper_length(int length, int opt_index)
34{
35	return max_option_length[opt_index] *
36		(length / option_lengths[opt_index]);
37}
38
39
40static int sprintip(char *dest, const char *pre, const uint8_t *ip)
41{
42	return sprintf(dest, "%s%d.%d.%d.%d", pre, ip[0], ip[1], ip[2], ip[3]);
43}
44
45
46/* really simple implementation, just count the bits */
47static int mton(struct in_addr *mask)
48{
49	int i;
50	unsigned long bits = ntohl(mask->s_addr);
51	/* too bad one can't check the carry bit, etc in c bit
52	 * shifting */
53	for (i = 0; i < 32 && !((bits >> i) & 1); i++);
54	return 32 - i;
55}
56
57
58/* Allocate and fill with the text of option 'option'. */
59static char *alloc_fill_opts(uint8_t *option, const struct dhcp_option *type_p)
60{
61	int len, type, optlen;
62	uint16_t val_u16;
63	int16_t val_s16;
64	uint32_t val_u32;
65	int32_t val_s32;
66	char *dest, *ret;
67
68	len = option[OPT_LEN - 2];
69	type = type_p->flags & TYPE_MASK;
70	optlen = option_lengths[type];
71
72	dest = ret = xmalloc(upper_length(len, type) + strlen(type_p->name) + 2);
73	dest += sprintf(ret, "%s=", type_p->name);
74
75	for (;;) {
76		switch (type) {
77		case OPTION_IP_PAIR:
78			dest += sprintip(dest, "", option);
79			*dest++ = '/';
80			option += 4;
81			optlen = 4;
82		case OPTION_IP:	/* Works regardless of host byte order. */
83			dest += sprintip(dest, "", option);
84			break;
85		case OPTION_BOOLEAN:
86			dest += sprintf(dest, *option ? "yes" : "no");
87			break;
88		case OPTION_U8:
89			dest += sprintf(dest, "%u", *option);
90			break;
91		case OPTION_U16:
92			memcpy(&val_u16, option, 2);
93			dest += sprintf(dest, "%u", ntohs(val_u16));
94			break;
95		case OPTION_S16:
96			memcpy(&val_s16, option, 2);
97			dest += sprintf(dest, "%d", ntohs(val_s16));
98			break;
99		case OPTION_U32:
100			memcpy(&val_u32, option, 4);
101			dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32));
102			break;
103		case OPTION_S32:
104			memcpy(&val_s32, option, 4);
105			dest += sprintf(dest, "%ld", (long) ntohl(val_s32));
106			break;
107		case OPTION_STRING:
108			memcpy(dest, option, len);
109			dest[len] = '\0';
110			return ret;	 /* Short circuit this case */
111#if ENABLE_FEATURE_RFC3397
112		case OPTION_STR1035:
113			/* unpack option into dest; use ret for prefix (i.e., "optname=") */
114			dest = dname_dec(option, len, ret);
115			free(ret);
116			return dest;
117#endif
118		}
119		option += optlen;
120		len -= optlen;
121		if (len <= 0) break;
122		dest += sprintf(dest, " ");
123	}
124	return ret;
125}
126
127
128/* put all the parameters into an environment */
129static char **fill_envp(struct dhcpMessage *packet)
130{
131	int num_options = 0;
132	int i, j;
133	char **envp;
134	char *var;
135	uint8_t *temp;
136	struct in_addr subnet;
137	char over = 0;
138
139	if (packet) {
140		for (i = 0; dhcp_options[i].code; i++) {
141			if (get_option(packet, dhcp_options[i].code)) {
142				num_options++;
143				if (dhcp_options[i].code == DHCP_SUBNET)
144					num_options++; /* for mton */
145			}
146		}
147		if (packet->siaddr)
148			num_options++;
149		temp = get_option(packet, DHCP_OPTION_OVER);
150		if (temp)
151			over = *temp;
152		if (!(over & FILE_FIELD) && packet->file[0])
153			num_options++;
154		if (!(over & SNAME_FIELD) && packet->sname[0])
155			num_options++;
156	}
157
158	envp = xzalloc(sizeof(char *) * (num_options + 5));
159	j = 0;
160	envp[j++] = xasprintf("interface=%s", client_config.interface);
161	var = getenv("PATH");
162	if (var)
163		envp[j++] = xasprintf("PATH=%s", var);
164	var = getenv("HOME");
165	if (var)
166		envp[j++] = xasprintf("HOME=%s", var);
167
168	if (packet == NULL)
169		return envp;
170
171	envp[j] = xmalloc(sizeof("ip=255.255.255.255"));
172	sprintip(envp[j++], "ip=", (uint8_t *) &packet->yiaddr);
173
174	for (i = 0; dhcp_options[i].code; i++) {
175		temp = get_option(packet, dhcp_options[i].code);
176		if (!temp)
177			continue;
178		envp[j++] = alloc_fill_opts(temp, &dhcp_options[i]);
179
180		/* Fill in a subnet bits option for things like /24 */
181		if (dhcp_options[i].code == DHCP_SUBNET) {
182			memcpy(&subnet, temp, 4);
183			envp[j++] = xasprintf("mask=%d", mton(&subnet));
184		}
185	}
186	if (packet->siaddr) {
187		envp[j] = xmalloc(sizeof("siaddr=255.255.255.255"));
188		sprintip(envp[j++], "siaddr=", (uint8_t *) &packet->siaddr);
189	}
190	if (!(over & FILE_FIELD) && packet->file[0]) {
191		/* watch out for invalid packets */
192		packet->file[sizeof(packet->file) - 1] = '\0';
193		envp[j++] = xasprintf("boot_file=%s", packet->file);
194	}
195	if (!(over & SNAME_FIELD) && packet->sname[0]) {
196		/* watch out for invalid packets */
197		packet->sname[sizeof(packet->sname) - 1] = '\0';
198		envp[j++] = xasprintf("sname=%s", packet->sname);
199	}
200	return envp;
201}
202
203
204/* Call a script with a par file and env vars */
205void udhcp_run_script(struct dhcpMessage *packet, const char *name)
206{
207	int pid;
208	char **envp, **curr;
209
210	if (client_config.script == NULL)
211		return;
212
213	DEBUG("vfork'ing and execle'ing %s", client_config.script);
214
215	envp = fill_envp(packet);
216
217	/* call script */
218// can we use wait4pid(spawn(...)) here?
219	pid = vfork();
220	if (pid < 0) return;
221	if (pid == 0) {
222		/* close fd's? */
223		/* exec script */
224		execle(client_config.script, client_config.script,
225		       name, NULL, envp);
226		bb_perror_msg_and_die("script %s failed", client_config.script);
227	}
228	waitpid(pid, NULL, 0);
229	for (curr = envp; *curr; curr++)
230		free(*curr);
231	free(envp);
232}
233