xref: /dragonfly/sbin/dhclient/options.c (revision dafcba8560ef1964310caf10c2e6e9edfbff0d12)
1 /*        $OpenBSD: src/sbin/dhclient/options.c,v 1.42 2012/10/27 23:08:53 krw Exp $      */
2 
3 /* DHCP options parsing and reassembly. */
4 
5 /*
6  * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of The Internet Software Consortium nor the names
19  *    of its contributors may be used to endorse or promote products derived
20  *    from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
23  * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
24  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26  * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
30  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
31  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  *
36  * This software has been written for the Internet Software Consortium
37  * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
38  * Enterprises.  To learn more about the Internet Software Consortium,
39  * see ``http://www.vix.com/isc''.  To learn more about Vixie
40  * Enterprises, see ``http://www.vix.com''.
41  */
42 
43 #include <ctype.h>
44 
45 #include "dhcpd.h"
46 
47 int parse_option_buffer(struct option_data *, unsigned char *, int);
48 
49 /*
50  * Parse options out of the specified buffer, storing addresses of
51  * option values in options and setting client->options_valid if
52  * no errors are encountered.
53  */
54 int
parse_option_buffer(struct option_data * options,unsigned char * buffer,int length)55 parse_option_buffer(struct option_data *options, unsigned char *buffer,
56     int length)
57 {
58           unsigned char *s, *t, *end = buffer + length;
59           int len, code;
60 
61           for (s = buffer; *s != DHO_END && s < end; ) {
62                     code = s[0];
63 
64                     /* Pad options don't have a length - just skip them. */
65                     if (code == DHO_PAD) {
66                               s++;
67                               continue;
68                     }
69 
70                     /*
71                      * All options other than DHO_PAD and DHO_END have a one-byte
72                      * length field. It could be 0! Make sure that the length byte
73                      * is present, and all the data is available.
74                      */
75                     if (s + 1 < end) {
76                               len = s[1];
77                               if (s + 1 + len < end) {
78                                         ; /* option data is all there. */
79                               } else {
80                                         warning("option %s (%d) larger than buffer.",
81                                             dhcp_options[code].name, len);
82                                         warning("rejecting bogus offer.");
83                                         return (0);
84                               }
85                     } else {
86                               warning("option %s has no length field.",
87                                   dhcp_options[code].name);
88                               warning("rejecting bogus offer.");
89                               return (0);
90                     }
91 
92                     /*
93                      * Strip trailing NULs from ascii ('t') options. They
94                      * will be treated as DHO_PAD options. i.e. ignored. RFC 2132
95                      * says "Options containing NVT ASCII data SHOULD NOT include
96                      * a trailing NULL; however, the receiver of such options
97                      * MUST be prepared to delete trailing nulls if they exist."
98                      */
99                     if (dhcp_options[code].format[0] == 't') {
100                               while (len > 0 && s[len + 1] == '\0')
101                                         len--;
102                     }
103 
104                     /*
105                      * If we haven't seen this option before, just make
106                      * space for it and copy it there.
107                      */
108                     if (!options[code].data) {
109                               if (!(t = calloc(1, len + 1)))
110                                         error("Can't allocate storage for option %s.",
111                                             dhcp_options[code].name);
112                               /*
113                                * Copy and NUL-terminate the option (in case
114                                * it's an ASCII string).
115                                */
116                               memcpy(t, &s[2], len);
117                               t[len] = 0;
118                               options[code].len = len;
119                               options[code].data = t;
120                     } else {
121                               /*
122                                * If it's a repeat, concatenate it to whatever
123                                * we last saw.   This is really only required
124                                * for clients, but what the heck...
125                                */
126                               t = calloc(1, len + options[code].len + 1);
127                               if (!t)
128                                         error("Can't expand storage for option %s.",
129                                             dhcp_options[code].name);
130                               memcpy(t, options[code].data, options[code].len);
131                               memcpy(t + options[code].len, &s[2], len);
132                               options[code].len += len;
133                               t[options[code].len] = 0;
134                               free(options[code].data);
135                               options[code].data = t;
136                     }
137                     s += len + 2;
138           }
139 
140           return (1);
141 }
142 
143 /*
144  * Copy as many options as fit in buflen bytes of buf. Return the
145  * offset of the start of the last option copied. A caller can check
146  * to see if it's DHO_END to decide if all the options were copied.
147  */
148 int
cons_options(struct option_data * options)149 cons_options(struct option_data *options)
150 {
151           unsigned char *buf = client->packet.options;
152           int buflen = 576 - DHCP_FIXED_LEN;
153           int ix, incr, length, bufix, code, lastopt = -1;
154 
155           bzero(buf, buflen);
156 
157           memcpy(buf, DHCP_OPTIONS_COOKIE, 4);
158           if (options[DHO_DHCP_MESSAGE_TYPE].data) {
159                     memcpy(&buf[4], DHCP_OPTIONS_MESSAGE_TYPE, 3);
160                     buf[6] = options[DHO_DHCP_MESSAGE_TYPE].data[0];
161                     bufix = 7;
162           } else
163                     bufix = 4;
164 
165           for (code = DHO_SUBNET_MASK; code < DHO_END; code++) {
166                     if (!options[code].data || code == DHO_DHCP_MESSAGE_TYPE)
167                               continue;
168 
169                     length = options[code].len;
170                     if (bufix + length + 2*((length+254)/255) >= buflen)
171                               return (lastopt);
172 
173                     lastopt = bufix;
174                     ix = 0;
175 
176                     while (length) {
177                               incr = length > 255 ? 255 : length;
178 
179                               buf[bufix++] = code;
180                               buf[bufix++] = incr;
181                               memcpy(buf + bufix, options[code].data + ix, incr);
182 
183                               length -= incr;
184                               ix += incr;
185                               bufix += incr;
186                     }
187           }
188 
189           if (bufix < buflen) {
190                     buf[bufix] = DHO_END;
191                     lastopt = bufix;
192           }
193 
194           return (lastopt);
195 }
196 
197 /*
198  * Format the specified option so that a human can easily read it.
199  */
200 char *
pretty_print_option(unsigned int code,struct option_data * option,int emit_punct)201 pretty_print_option(unsigned int code, struct option_data *option,
202     int emit_punct)
203 {
204           static char optbuf[32768]; /* XXX */
205           int hunksize = 0, numhunk = -1, numelem = 0;
206           char fmtbuf[32], *op = optbuf;
207           int i, j, k, opleft = sizeof(optbuf);
208           unsigned char *data = option->data;
209           unsigned char *dp = data;
210           int len = option->len;
211           struct in_addr foo;
212           char comma;
213 
214           /* Code should be between 0 and 255. */
215           if (code > 255)
216                     error("pretty_print_option: bad code %d", code);
217 
218           if (emit_punct)
219                     comma = ',';
220           else
221                     comma = ' ';
222 
223           /* Figure out the size of the data. */
224           for (i = 0; dhcp_options[code].format[i]; i++) {
225                     if (!numhunk) {
226                               warning("%s: Excess information in format string: %s",
227                                   dhcp_options[code].name,
228                                   &(dhcp_options[code].format[i]));
229                               break;
230                     }
231                     numelem++;
232                     fmtbuf[i] = dhcp_options[code].format[i];
233                     switch (dhcp_options[code].format[i]) {
234                     case 'A':
235                               --numelem;
236                               fmtbuf[i] = 0;
237                               numhunk = 0;
238                               if (hunksize == 0) {
239                                         warning("%s: no size indicator before A"
240                                             " in format string: %s",
241                                             dhcp_options[code].name,
242                                             dhcp_options[code].format);
243                                         return ("<fmt error>");
244                               }
245                               break;
246                     case 'X':
247                               for (k = 0; k < len; k++)
248                                         if (!isascii(data[k]) ||
249                                             !isprint(data[k]))
250                                                   break;
251                               if (k == len) {
252                                         fmtbuf[i] = 't';
253                                         numhunk = -2;
254                               } else {
255                                         fmtbuf[i] = 'x';
256                                         hunksize++;
257                                         comma = ':';
258                                         numhunk = 0;
259                               }
260                               fmtbuf[i + 1] = 0;
261                               break;
262                     case 't':
263                               fmtbuf[i] = 't';
264                               fmtbuf[i + 1] = 0;
265                               numhunk = -2;
266                               break;
267                     case 'I':
268                     case 'l':
269                     case 'L':
270                               hunksize += 4;
271                               break;
272                     case 's':
273                     case 'S':
274                               hunksize += 2;
275                               break;
276                     case 'b':
277                     case 'B':
278                     case 'f':
279                               hunksize++;
280                               break;
281                     case 'e':
282                               break;
283                     default:
284                               warning("%s: garbage in format string: %s",
285                                   dhcp_options[code].name,
286                                   &(dhcp_options[code].format[i]));
287                               break;
288                     }
289           }
290 
291           /* Check for too few bytes... */
292           if (hunksize > len) {
293                     warning("%s: expecting at least %d bytes; got %d",
294                         dhcp_options[code].name, hunksize, len);
295                     return ("<error>");
296           }
297           /* Check for too many bytes... */
298           if (numhunk == -1 && hunksize < len)
299                     warning("%s: %d extra bytes",
300                         dhcp_options[code].name, len - hunksize);
301 
302           /* If this is an array, compute its size. */
303           if (!numhunk)
304                     numhunk = len / hunksize;
305           /* See if we got an exact number of hunks. */
306           if (numhunk > 0 && numhunk * hunksize < len)
307                     warning("%s: %d extra bytes at end of array",
308                         dhcp_options[code].name, len - numhunk * hunksize);
309 
310           /* A one-hunk array prints the same as a single hunk. */
311           if (numhunk < 0)
312                     numhunk = 1;
313 
314           /* Cycle through the array (or hunk) printing the data. */
315           for (i = 0; i < numhunk; i++) {
316                     for (j = 0; j < numelem; j++) {
317                               int opcount;
318                               size_t oplen;
319                               switch (fmtbuf[j]) {
320                               case 't':
321                                         if (emit_punct) {
322                                                   *op++ = '"';
323                                                   opleft--;
324                                         }
325                                         for (; dp < data + len; dp++) {
326                                                   if (!isascii(*dp) ||
327                                                       !isprint(*dp)) {
328                                                             if (dp + 1 != data + len ||
329                                                                 *dp != 0) {
330                                                                       size_t oplen;
331                                                                       snprintf(op, opleft,
332                                                                           "\\%03o", *dp);
333                                                                       oplen = strlen(op);
334                                                                       op += oplen;
335                                                                       opleft -= oplen;
336                                                             }
337                                                   } else if (*dp == '"' ||
338                                                       *dp == '\'' ||
339                                                       *dp == '$' ||
340                                                       *dp == '`' ||
341                                                       *dp == '\\') {
342                                                             *op++ = '\\';
343                                                             *op++ = *dp;
344                                                             opleft -= 2;
345                                                   } else {
346                                                             *op++ = *dp;
347                                                             opleft--;
348                                                   }
349                                         }
350                                         if (emit_punct) {
351                                                   *op++ = '"';
352                                                   opleft--;
353                                         }
354 
355                                         *op = 0;
356                                         break;
357                               case 'I':
358                                         foo.s_addr = htonl(getULong(dp));
359                                         opcount = strlcpy(op, inet_ntoa(foo), opleft);
360                                         if (opcount >= opleft)
361                                                   goto toobig;
362                                         opleft -= opcount;
363                                         dp += 4;
364                                         break;
365                               case 'l':
366                                         opcount = snprintf(op, opleft, "%ld",
367                                             (long)getLong(dp));
368                                         if (opcount >= opleft || opcount == -1)
369                                                   goto toobig;
370                                         opleft -= opcount;
371                                         dp += 4;
372                                         break;
373                               case 'L':
374                                         opcount = snprintf(op, opleft, "%ld",
375                                             (unsigned long)getULong(dp));
376                                         if (opcount >= opleft || opcount == -1)
377                                                   goto toobig;
378                                         opleft -= opcount;
379                                         dp += 4;
380                                         break;
381                               case 's':
382                                         opcount = snprintf(op, opleft, "%d",
383                                             getShort(dp));
384                                         if (opcount >= opleft || opcount == -1)
385                                                   goto toobig;
386                                         opleft -= opcount;
387                                         dp += 2;
388                                         break;
389                               case 'S':
390                                         opcount = snprintf(op, opleft, "%d",
391                                             getUShort(dp));
392                                         if (opcount >= opleft || opcount == -1)
393                                                   goto toobig;
394                                         opleft -= opcount;
395                                         dp += 2;
396                                         break;
397                               case 'b':
398                                         opcount = snprintf(op, opleft, "%d",
399                                             *(char *)dp++);
400                                         if (opcount >= opleft || opcount == -1)
401                                                   goto toobig;
402                                         opleft -= opcount;
403                                         break;
404                               case 'B':
405                                         opcount = snprintf(op, opleft, "%d", *dp++);
406                                         if (opcount >= opleft || opcount == -1)
407                                                   goto toobig;
408                                         opleft -= opcount;
409                                         break;
410                               case 'x':
411                                         opcount = snprintf(op, opleft, "%x", *dp++);
412                                         if (opcount >= opleft || opcount == -1)
413                                                   goto toobig;
414                                         opleft -= opcount;
415                                         break;
416                               case 'f':
417                                         opcount = strlcpy(op,
418                                             *dp++ ? "true" : "false", opleft);
419                                         if (opcount >= opleft)
420                                                   goto toobig;
421                                         opleft -= opcount;
422                                         break;
423                               default:
424                                         warning("Unexpected format code %c", fmtbuf[j]);
425                               }
426                               oplen = strlen(op);
427                               op += oplen;
428                               opleft -= oplen;
429                               if (opleft < 1)
430                                         goto toobig;
431                               if (j + 1 < numelem && comma != ':') {
432                                         *op++ = ' ';
433                                         opleft--;
434                               }
435                     }
436                     if (i + 1 < numhunk) {
437                               *op++ = comma;
438                               opleft--;
439                     }
440                     if (opleft < 1)
441                               goto toobig;
442 
443           }
444           return (optbuf);
445  toobig:
446           warning("dhcp option too large");
447           return ("<error>");
448 }
449 
450 void
do_packet(int len,unsigned int from_port,struct iaddr from,struct hardware * hfrom)451 do_packet(int len, unsigned int from_port, struct iaddr from,
452     struct hardware *hfrom)
453 {
454           struct dhcp_packet *packet = &client->packet;
455           struct option_data options[256];
456           struct iaddrlist *ap;
457           void (*handler)(struct iaddr, struct option_data *);
458           char *type;
459           int i, options_valid = 1;
460 
461           if (packet->hlen > sizeof(packet->chaddr)) {
462                     note("Discarding packet with invalid hlen.");
463                     return;
464           }
465 
466           /*
467            * Silently drop the packet if the client hardware address in the
468            * packet is not the hardware address of the interface being managed.
469            */
470           if ((ifi->hw_address.hlen != packet->hlen) ||
471               (memcmp(ifi->hw_address.haddr, packet->chaddr, packet->hlen)))
472                     return;
473 
474           memset(options, 0, sizeof(options));
475 
476           if (memcmp(&packet->options, DHCP_OPTIONS_COOKIE, 4) == 0) {
477                     /* Parse the BOOTP/DHCP options field. */
478                     options_valid = parse_option_buffer(options,
479                         &packet->options[4], sizeof(packet->options) - 4);
480 
481                     /* Only DHCP packets have overload areas for options. */
482                     if (options_valid &&
483                         options[DHO_DHCP_MESSAGE_TYPE].data &&
484                         options[DHO_DHCP_OPTION_OVERLOAD].data) {
485                               if (options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1)
486                                         options_valid = parse_option_buffer(options,
487                                             (unsigned char *)packet->file,
488                                             sizeof(packet->file));
489                               if (options_valid &&
490                                   options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2)
491                                         options_valid = parse_option_buffer(options,
492                                             (unsigned char *)packet->sname,
493                                             sizeof(packet->sname));
494                     }
495           }
496 
497           type = "";
498           handler = NULL;
499 
500           if (options[DHO_DHCP_MESSAGE_TYPE].data) {
501                     /* Always try a DHCP packet, even if a bad option was seen. */
502                     switch (options[DHO_DHCP_MESSAGE_TYPE].data[0]) {
503                     case DHCPOFFER:
504                               handler = dhcpoffer;
505                               type = "DHCPOFFER";
506                               break;
507                     case DHCPNAK:
508                               handler = dhcpnak;
509                               type = "DHCPNACK";
510                               break;
511                     case DHCPACK:
512                               handler = dhcpack;
513                               type = "DHCPACK";
514                               break;
515                     default:
516                               break;
517                     }
518           } else if (options_valid && packet->op == BOOTREPLY) {
519                     handler = dhcpoffer;
520                     type = "BOOTREPLY";
521           }
522 
523           if (handler && client->xid == client->packet.xid) {
524                     if (hfrom->hlen == 6)
525                               note("%s from %s (%s)", type, piaddr(from),
526                                   ether_ntoa((struct ether_addr *)hfrom->haddr));
527                     else
528                               note("%s from %s", type, piaddr(from));
529           } else
530                     handler = NULL;
531 
532           for (ap = config->reject_list; ap && handler; ap = ap->next)
533                     if (addr_eq(from, ap->addr)) {
534                               note("%s from %s rejected.", type, piaddr(from));
535                               handler = NULL;
536                     }
537 
538           if (handler)
539                     (*handler)(from, options);
540 
541           for (i = 0; i < 256; i++)
542                     if (options[i].len && options[i].data)
543                               free(options[i].data);
544 }
545