initial commit
[udhcp] / options.c
1 /*
2  * options.c -- DHCP server option packet tools
3  * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
4  */
5
6 #include <stdlib.h>
7 #include <string.h>
8
9 #include "dhcpd.h"
10 #include "files.h"
11 #include "options.h"
12 #include "common.h"
13
14
15 /* supported options are easily added here */
16 struct dhcp_option dhcp_options[] = {
17         /* name[10]     flags                                   code */
18         {"subnet",      OPTION_IP | OPTION_REQ,                 0x01},
19         {"timezone",    OPTION_S32,                             0x02},
20         {"router",      OPTION_IP | OPTION_LIST | OPTION_REQ,   0x03},
21         {"timesvr",     OPTION_IP | OPTION_LIST,                0x04},
22         {"namesvr",     OPTION_IP | OPTION_LIST,                0x05},
23         {"dns",         OPTION_IP | OPTION_LIST | OPTION_REQ,   0x06},
24         {"logsvr",      OPTION_IP | OPTION_LIST,                0x07},
25         {"cookiesvr",   OPTION_IP | OPTION_LIST,                0x08},
26         {"lprsvr",      OPTION_IP | OPTION_LIST,                0x09},
27         {"hostname",    OPTION_STRING | OPTION_REQ,             0x0c},
28         {"bootsize",    OPTION_U16,                             0x0d},
29         {"domain",      OPTION_STRING | OPTION_REQ,             0x0f},
30         {"swapsvr",     OPTION_IP,                              0x10},
31         //We had the possibility for the client to ask for a rootpath
32         //closes http://bugs.debian.org/279110 (ericvb@debian.org)
33         //{"rootpath",  OPTION_STRING,                          0x11},
34         {"rootpath",    OPTION_STRING | OPTION_REQ,             0x11},
35         {"ipttl",       OPTION_U8,                              0x17},
36         {"mtu",         OPTION_U16,                             0x1a},
37         {"broadcast",   OPTION_IP | OPTION_REQ,                 0x1c},
38         {"nisdomain",   OPTION_STRING | OPTION_REQ,             0x28},
39         {"nissrv",      OPTION_IP | OPTION_LIST | OPTION_REQ,   0x29},
40         {"ntpsrv",      OPTION_IP | OPTION_LIST | OPTION_REQ,   0x2a},
41         {"wins",        OPTION_IP | OPTION_LIST,                0x2c},
42         {"requestip",   OPTION_IP,                              0x32},
43         {"lease",       OPTION_U32,                             0x33},
44         {"dhcptype",    OPTION_U8,                              0x35},
45         {"serverid",    OPTION_IP,                              0x36},
46         {"message",     OPTION_STRING,                          0x38},
47         {"tftp",        OPTION_STRING,                          0x42},
48         {"bootfile",    OPTION_STRING,                          0x43},
49         {"wpad",        OPTION_STRING,                          0xfc},
50         {"",            0x00,                           0x00}
51 };
52
53 /* Lengths of the different option types */
54 int option_lengths[] = {
55         [OPTION_IP] =           4,
56         [OPTION_IP_PAIR] =      8,
57         [OPTION_BOOLEAN] =      1,
58         [OPTION_STRING] =       1,
59         [OPTION_U8] =           1,
60         [OPTION_U16] =          2,
61         [OPTION_S16] =          2,
62         [OPTION_U32] =          4,
63         [OPTION_S32] =          4
64 };
65
66
67 /* get an option with bounds checking (warning, not aligned). */
68 uint8_t *get_option(struct dhcpMessage *packet, int code)
69 {
70         int i, length;
71         uint8_t *optionptr;
72         int over = 0, done = 0, curr = OPTION_FIELD;
73
74         optionptr = packet->options;
75         i = 0;
76         length = 308;
77         while (!done) {
78                 if (i >= length) {
79                         LOG(LOG_WARNING, "bogus packet, option fields too long.");
80                         return NULL;
81                 }
82                 if (optionptr[i + OPT_CODE] == code) {
83                         if (i + 1 + optionptr[i + OPT_LEN] >= length) {
84                                 LOG(LOG_WARNING, "bogus packet, option fields too long.");
85                                 return NULL;
86                         }
87                         return optionptr + i + 2;
88                 }
89                 switch (optionptr[i + OPT_CODE]) {
90                 case DHCP_PADDING:
91                         i++;
92                         break;
93                 case DHCP_OPTION_OVER:
94                         if (i + 1 + optionptr[i + OPT_LEN] >= length) {
95                                 LOG(LOG_WARNING, "bogus packet, option fields too long.");
96                                 return NULL;
97                         }
98                         over = optionptr[i + 3];
99                         i += optionptr[OPT_LEN] + 2;
100                         break;
101                 case DHCP_END:
102                         if (curr == OPTION_FIELD && over & FILE_FIELD) {
103                                 optionptr = packet->file;
104                                 i = 0;
105                                 length = 128;
106                                 curr = FILE_FIELD;
107                         } else if (curr == FILE_FIELD && over & SNAME_FIELD) {
108                                 optionptr = packet->sname;
109                                 i = 0;
110                                 length = 64;
111                                 curr = SNAME_FIELD;
112                         } else done = 1;
113                         break;
114                 default:
115                         i += optionptr[OPT_LEN + i] + 2;
116                 }
117         }
118         return NULL;
119 }
120
121
122 /* return the position of the 'end' option (no bounds checking) */
123 int end_option(uint8_t *optionptr)
124 {
125         int i = 0;
126
127         while (optionptr[i] != DHCP_END) {
128                 if (optionptr[i] == DHCP_PADDING) i++;
129                 else i += optionptr[i + OPT_LEN] + 2;
130         }
131         return i;
132 }
133
134
135 /* add an option string to the options (an option string contains an option code,
136  * length, then data) */
137 int add_option_string(uint8_t *optionptr, uint8_t *string)
138 {
139         int end = end_option(optionptr);
140
141         /* end position + string length + option code/length + end option */
142         if (end + string[OPT_LEN] + 2 + 1 >= 308) {
143                 LOG(LOG_ERR, "Option 0x%02x did not fit into the packet!", string[OPT_CODE]);
144                 return 0;
145         }
146         DEBUG(LOG_INFO, "adding option 0x%02x", string[OPT_CODE]);
147         memcpy(optionptr + end, string, string[OPT_LEN] + 2);
148         optionptr[end + string[OPT_LEN] + 2] = DHCP_END;
149         return string[OPT_LEN] + 2;
150 }
151
152
153 /* add a one to four byte option to a packet */
154 int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data)
155 {
156         char length = 0;
157         int i;
158         uint8_t option[2 + 4];
159         uint8_t *u8;
160         uint16_t *u16;
161         uint32_t *u32;
162         uint32_t aligned;
163         u8 = (uint8_t *) &aligned;
164         u16 = (uint16_t *) &aligned;
165         u32 = &aligned;
166
167         for (i = 0; dhcp_options[i].code; i++)
168                 if (dhcp_options[i].code == code) {
169                         length = option_lengths[dhcp_options[i].flags & TYPE_MASK];
170                 }
171
172         if (!length) {
173                 DEBUG(LOG_ERR, "Could not add option 0x%02x", code);
174                 return 0;
175         }
176
177         option[OPT_CODE] = code;
178         option[OPT_LEN] = length;
179
180         switch (length) {
181                 case 1: *u8 =  data; break;
182                 case 2: *u16 = data; break;
183                 case 4: *u32 = data; break;
184         }
185         memcpy(option + 2, &aligned, length);
186         return add_option_string(optionptr, option);
187 }
188
189
190 /* find option 'code' in opt_list */
191 struct option_set *find_option(struct option_set *opt_list, char code)
192 {
193         while (opt_list && opt_list->data[OPT_CODE] < code)
194                 opt_list = opt_list->next;
195
196         if (opt_list && opt_list->data[OPT_CODE] == code) return opt_list;
197         else return NULL;
198 }
199
200
201 /* add an option to the opt_list */
202 void attach_option(struct option_set **opt_list, struct dhcp_option *option, char *buffer, int length)
203 {
204         struct option_set *existing, *new, **curr;
205
206         /* add it to an existing option */
207         if ((existing = find_option(*opt_list, option->code))) {
208                 DEBUG(LOG_INFO, "Attaching option %s to existing member of list", option->name);
209                 if (option->flags & OPTION_LIST) {
210                         if (existing->data[OPT_LEN] + length <= 255) {
211                                 existing->data = realloc(existing->data,
212                                                 existing->data[OPT_LEN] + length + 2);
213                                 memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, length);
214                                 existing->data[OPT_LEN] += length;
215                         } /* else, ignore the data, we could put this in a second option in the future */
216                 } /* else, ignore the new data */
217         } else {
218                 DEBUG(LOG_INFO, "Attaching option %s to list", option->name);
219
220                 /* make a new option */
221                 new = xmalloc(sizeof(struct option_set));
222                 new->data = xmalloc(length + 2);
223                 new->data[OPT_CODE] = option->code;
224                 new->data[OPT_LEN] = length;
225                 memcpy(new->data + 2, buffer, length);
226
227                 curr = opt_list;
228                 while (*curr && (*curr)->data[OPT_CODE] < option->code)
229                         curr = &(*curr)->next;
230
231                 new->next = *curr;
232                 *curr = new;
233         }
234 }