Use stdio.h instead of vsprintf.h
[people/mcb30/gpxe.git] / src / net / dhcpopts.c
1 /*
2  * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <byteswap.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <assert.h>
26 #include <gpxe/list.h>
27 #include <gpxe/in.h>
28 #include <gpxe/dhcp.h>
29
30 /** @file
31  *
32  * DHCP options
33  *
34  */
35
36 /** List of registered DHCP option blocks */
37 static LIST_HEAD ( option_blocks );
38
39 /**
40  * Obtain printable version of a DHCP option tag
41  *
42  * @v tag               DHCP option tag
43  * @ret name            String representation of the tag
44  *
45  */
46 static inline char * dhcp_tag_name ( unsigned int tag ) {
47         static char name[8];
48
49         if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
50                 snprintf ( name, sizeof ( name ), "%d.%d",
51                            DHCP_ENCAPSULATOR ( tag ),
52                            DHCP_ENCAPSULATED ( tag ) );
53         } else {
54                 snprintf ( name, sizeof ( name ), "%d", tag );
55         }
56         return name;
57 }
58
59 /**
60  * Obtain value of a numerical DHCP option
61  *
62  * @v option            DHCP option, or NULL
63  * @ret value           Numerical value of the option, or 0
64  *
65  * Parses the numerical value from a DHCP option, if present.  It is
66  * permitted to call dhcp_num_option() with @c option set to NULL; in
67  * this case 0 will be returned.
68  *
69  * The caller does not specify the size of the DHCP option data; this
70  * is implied by the length field stored within the DHCP option
71  * itself.
72  */
73 unsigned long dhcp_num_option ( struct dhcp_option *option ) {
74         unsigned long value = 0;
75         uint8_t *data;
76
77         if ( option ) {
78                 /* This is actually smaller code than using htons()
79                  * etc., and will also cope well with malformed
80                  * options (such as zero-length options).
81                  */
82                 for ( data = option->data.bytes ;
83                       data < ( option->data.bytes + option->len ) ; data++ )
84                         value = ( ( value << 8 ) | *data );
85         }
86         return value;
87 }
88
89 /**
90  * Obtain value of an IPv4-address DHCP option
91  *
92  * @v option            DHCP option, or NULL
93  * @v inp               IPv4 address to fill in
94  *
95  * Parses the IPv4 address value from a DHCP option, if present.  It
96  * is permitted to call dhcp_ipv4_option() with @c option set to NULL;
97  * in this case the address will be set to 0.0.0.0.
98  */
99 void dhcp_ipv4_option ( struct dhcp_option *option, struct in_addr *inp ) {
100         if ( option )
101                 *inp = option->data.in;
102 }
103
104 /**
105  * Print DHCP string option value into buffer
106  *
107  * @v buf               Buffer into which to write the string
108  * @v size              Size of buffer
109  * @v option            DHCP option, or NULL
110  * @ret len             Length of formatted string
111  *
112  * DHCP option strings are stored without a NUL terminator.  This
113  * function provides a convenient way to extract these DHCP strings
114  * into standard C strings.  It is permitted to call dhcp_snprintf()
115  * with @c option set to NULL; in this case the buffer will be filled
116  * with an empty string.
117  *
118  * The usual snprintf() semantics apply with regard to buffer size,
119  * return value when the buffer is too small, etc.
120  */
121 int dhcp_snprintf ( char *buf, size_t size, struct dhcp_option *option ) {
122         size_t len;
123         char *content = "";
124
125         if ( option ) {
126                 /* Shrink buffer size so that it is only just large
127                  * enough to contain the option data.  snprintf() will
128                  * take care of everything else (inserting the NUL etc.)
129                  */
130                 len = ( option->len + 1 );
131                 if ( len < size )
132                         size = len;
133                 content = option->data.string;
134         }
135         return snprintf ( buf, size, "%s", content );
136 }
137
138 /**
139  * Calculate length of a normal DHCP option
140  *
141  * @v option            DHCP option
142  * @ret len             Length (including tag and length field)
143  *
144  * @c option may not be a @c DHCP_PAD or @c DHCP_END option.
145  */
146 static inline unsigned int dhcp_option_len ( struct dhcp_option *option ) {
147         assert ( option->tag != DHCP_PAD );
148         assert ( option->tag != DHCP_END );
149         return ( option->len + DHCP_OPTION_HEADER_LEN );
150 }
151
152 /**
153  * Calculate length of any DHCP option
154  *
155  * @v option            DHCP option
156  * @ret len             Length (including tag and length field)
157  */
158 static inline unsigned int dhcp_any_option_len ( struct dhcp_option *option ) {
159         if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
160                 return 1;
161         } else {
162                 return dhcp_option_len ( option );
163         }
164 }
165
166 /**
167  * Find DHCP option within DHCP options block, and its encapsulator (if any)
168  *
169  * @v options           DHCP options block
170  * @v tag               DHCP option tag to search for
171  * @ret encapsulator    Encapsulating DHCP option
172  * @ret option          DHCP option, or NULL if not found
173  *
174  * Searches for the DHCP option matching the specified tag within the
175  * DHCP option block.  Encapsulated options may be searched for by
176  * using DHCP_ENCAP_OPT() to construct the tag value.
177  *
178  * If the option is encapsulated, and @c encapsulator is non-NULL, it
179  * will be filled in with a pointer to the encapsulating option.
180  *
181  * This routine is designed to be paranoid.  It does not assume that
182  * the option data is well-formatted, and so must guard against flaws
183  * such as options missing a @c DHCP_END terminator, or options whose
184  * length would take them beyond the end of the data block.
185  */
186 static struct dhcp_option *
187 find_dhcp_option_with_encap ( struct dhcp_option_block *options,
188                               unsigned int tag,
189                               struct dhcp_option **encapsulator ) {
190         unsigned int original_tag __attribute__ (( unused )) = tag;
191         struct dhcp_option *option = options->data;
192         ssize_t remaining = options->len;
193         unsigned int option_len;
194
195         while ( remaining ) {
196                 /* Calculate length of this option.  Abort processing
197                  * if the length is malformed (i.e. takes us beyond
198                  * the end of the data block).
199                  */
200                 option_len = dhcp_any_option_len ( option );
201                 remaining -= option_len;
202                 if ( remaining < 0 )
203                         break;
204                 /* Check for matching tag */
205                 if ( option->tag == tag ) {
206                         DBG ( "Found DHCP option %s (length %d) in block %p\n",
207                               dhcp_tag_name ( original_tag ), option->len,
208                               options );
209                         return option;
210                 }
211                 /* Check for explicit end marker */
212                 if ( option->tag == DHCP_END )
213                         break;
214                 /* Check for start of matching encapsulation block */
215                 if ( DHCP_IS_ENCAP_OPT ( tag ) &&
216                      ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
217                         if ( encapsulator )
218                                 *encapsulator = option;
219                         /* Continue search within encapsulated option block */
220                         tag = DHCP_ENCAPSULATED ( tag );
221                         remaining = option->len;
222                         option = ( void * ) &option->data;
223                         continue;
224                 }
225                 option = ( ( ( void * ) option ) + option_len );
226         }
227         return NULL;
228 }
229
230 /**
231  * Find DHCP option within DHCP options block
232  *
233  * @v options           DHCP options block, or NULL
234  * @v tag               DHCP option tag to search for
235  * @ret option          DHCP option, or NULL if not found
236  *
237  * Searches for the DHCP option matching the specified tag within the
238  * DHCP option block.  Encapsulated options may be searched for by
239  * using DHCP_ENCAP_OPT() to construct the tag value.
240  *
241  * If @c options is NULL, all registered option blocks will be
242  * searched.  Where multiple option blocks contain the same DHCP
243  * option, the option from the highest-priority block will be
244  * returned.  (Priority of an options block is determined by the value
245  * of the @c DHCP_EB_PRIORITY option within the block, if present; the
246  * default priority is zero).
247  */
248 struct dhcp_option * find_dhcp_option ( struct dhcp_option_block *options,
249                                         unsigned int tag ) {
250         struct dhcp_option *option;
251
252         if ( options ) {
253                 return find_dhcp_option_with_encap ( options, tag, NULL );
254         } else {
255                 list_for_each_entry ( options, &option_blocks, list ) {
256                         if ( ( option = find_dhcp_option ( options, tag ) ) )
257                                 return option;
258                 }
259                 return NULL;
260         }
261 }
262
263 /**
264  * Register DHCP option block
265  *
266  * @v options           DHCP option block
267  *
268  * Register a block of DHCP options.
269  */
270 void register_dhcp_options ( struct dhcp_option_block *options ) {
271         struct dhcp_option_block *existing;
272
273         /* Determine priority of new block */
274         options->priority = find_dhcp_num_option ( options, DHCP_EB_PRIORITY );
275         DBG ( "Registering DHCP options block %p with priority %d\n",
276               options, options->priority );
277
278         /* Insert after any existing blocks which have a higher priority */
279         list_for_each_entry ( existing, &option_blocks, list ) {
280                 if ( options->priority > existing->priority )
281                         break;
282         }
283         list_add_tail ( &options->list, &existing->list );
284 }
285
286 /**
287  * Unregister DHCP option block
288  *
289  * @v options           DHCP option block
290  */
291 void unregister_dhcp_options ( struct dhcp_option_block *options ) {
292         list_del ( &options->list );
293 }
294
295 /**
296  * Initialise empty block of DHCP options
297  *
298  * @v options           Uninitialised DHCP option block
299  * @v data              Memory for DHCP option data
300  * @v max_len           Length of memory for DHCP option data
301  *
302  * Populates the DHCP option data with a single @c DHCP_END option and
303  * fills in the fields of the @c dhcp_option_block structure.
304  */
305 void init_dhcp_options ( struct dhcp_option_block *options,
306                          void *data, size_t max_len ) {
307         struct dhcp_option *option;
308
309         options->data = data;
310         options->max_len = max_len;
311         option = options->data;
312         option->tag = DHCP_END;
313         options->len = 1;
314
315         DBG ( "DHCP options block %p initialised (data %p max_len %#zx)\n",
316               options, options->data, options->max_len );
317 }
318
319 /**
320  * Allocate space for a block of DHCP options
321  *
322  * @v max_len           Maximum length of option block
323  * @ret options         DHCP option block, or NULL
324  *
325  * Creates a new DHCP option block and populates it with an empty
326  * options list.  This call does not register the options block.
327  */
328 struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ) {
329         struct dhcp_option_block *options;
330
331         options = malloc ( sizeof ( *options ) + max_len );
332         if ( options ) {
333                 init_dhcp_options ( options, 
334                                     ( (void *) options + sizeof ( *options ) ),
335                                     max_len );
336         }
337         return options;
338 }
339
340 /**
341  * Free DHCP options block
342  *
343  * @v options           DHCP option block
344  */
345 void free_dhcp_options ( struct dhcp_option_block *options ) {
346         free ( options );
347 }
348
349 /**
350  * Resize a DHCP option
351  *
352  * @v options           DHCP option block
353  * @v option            DHCP option to resize
354  * @v encapsulator      Encapsulating option (or NULL)
355  * @v old_len           Old length (including header)
356  * @v new_len           New length (including header)
357  * @ret rc              Return status code
358  */
359 static int resize_dhcp_option ( struct dhcp_option_block *options,
360                                 struct dhcp_option *option,
361                                 struct dhcp_option *encapsulator,
362                                 size_t old_len, size_t new_len ) {
363         void *source = ( ( ( void * ) option ) + old_len );
364         void *dest = ( ( ( void * ) option ) + new_len );
365         void *end = ( options->data + options->max_len );
366         ssize_t delta = ( new_len - old_len );
367         size_t new_options_len;
368         size_t new_encapsulator_len;
369
370         /* Check for sufficient space, and update length fields */
371         if ( new_len > DHCP_MAX_LEN )
372                 return -ENOMEM;
373         new_options_len = ( options->len + delta );
374         if ( new_options_len > options->max_len )
375                 return -ENOMEM;
376         if ( encapsulator ) {
377                 new_encapsulator_len = ( encapsulator->len + delta );
378                 if ( new_encapsulator_len > DHCP_MAX_LEN )
379                         return -ENOMEM;
380                 encapsulator->len = new_encapsulator_len;
381         }
382         options->len = new_options_len;
383
384         /* Move remainder of option data */
385         memmove ( dest, source, ( end - dest ) );
386
387         return 0;
388 }
389
390 /**
391  * Set value of DHCP option
392  *
393  * @v options           DHCP option block
394  * @v tag               DHCP option tag
395  * @v data              New value for DHCP option
396  * @v len               Length of value, in bytes
397  * @ret option          DHCP option, or NULL
398  *
399  * Sets the value of a DHCP option within the options block.  The
400  * option may or may not already exist.  Encapsulators will be created
401  * (and deleted) as necessary.
402  *
403  * This call may fail due to insufficient space in the options block.
404  * If it does fail, and the option existed previously, the option will
405  * be left with its original value.
406  */
407 struct dhcp_option * set_dhcp_option ( struct dhcp_option_block *options,
408                                        unsigned int tag,
409                                        const void *data, size_t len ) {
410         static const uint8_t empty_encapsulator[] = { DHCP_END };
411         struct dhcp_option *option;
412         void *insertion_point;
413         struct dhcp_option *encapsulator = NULL;
414         unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
415         size_t old_len = 0;
416         size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
417
418         /* Return NULL if no options block specified */
419         if ( ! options )
420                 return NULL;
421
422         /* Find old instance of this option, if any */
423         option = find_dhcp_option_with_encap ( options, tag, &encapsulator );
424         if ( option ) {
425                 old_len = dhcp_option_len ( option );
426                 DBG ( "Resizing DHCP option %s from length %d to %d in block "
427                       "%p\n", dhcp_tag_name (tag), option->len, len, options );
428         } else {
429                 old_len = 0;
430                 DBG ( "Creating DHCP option %s (length %d) in block %p\n",
431                       dhcp_tag_name ( tag ), len, options );
432         }
433         
434         /* Ensure that encapsulator exists, if required */
435         insertion_point = options->data;
436         if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
437                 if ( ! encapsulator )
438                         encapsulator = set_dhcp_option ( options, encap_tag,
439                                                          empty_encapsulator,
440                                                 sizeof ( empty_encapsulator) );
441                 if ( ! encapsulator )
442                         return NULL;
443                 insertion_point = &encapsulator->data;
444         }
445
446         /* Create new option if necessary */
447         if ( ! option )
448                 option = insertion_point;
449         
450         /* Resize option to fit new data */
451         if ( resize_dhcp_option ( options, option, encapsulator,
452                                   old_len, new_len ) != 0 )
453                 return NULL;
454
455         /* Copy new data into option, if applicable */
456         if ( len ) {
457                 option->tag = tag;
458                 option->len = len;
459                 memcpy ( &option->data, data, len );
460         }
461
462         /* Delete encapsulator if there's nothing else left in it */
463         if ( encapsulator && ( encapsulator->len <= 1 ) )
464                 set_dhcp_option ( options, encap_tag, NULL, 0 );
465
466         return option;
467 }
468
469 /**
470  * Find DHCP option within all registered DHCP options blocks
471  *
472  * @v tag               DHCP option tag to search for
473  * @ret option          DHCP option, or NULL if not found
474  *
475  * This function exists merely as a notational shorthand for
476  * find_dhcp_option() with @c options set to NULL.
477  */
478 struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
479         return find_dhcp_option ( NULL, tag );
480 }
481
482 /**
483  * Find DHCP numerical option, and return its value
484  *
485  * @v options           DHCP options block
486  * @v tag               DHCP option tag to search for
487  * @ret value           Numerical value of the option, or 0 if not found
488  *
489  * This function exists merely as a notational shorthand for a call to
490  * find_dhcp_option() followed by a call to dhcp_num_option().  It is
491  * not possible to distinguish between the cases "option not found"
492  * and "option has a value of zero" using this function; if this
493  * matters to you then issue the two constituent calls directly and
494  * check that find_dhcp_option() returns a non-NULL value.
495  */
496 unsigned long find_dhcp_num_option ( struct dhcp_option_block *options,
497                                      unsigned int tag ) {
498         return dhcp_num_option ( find_dhcp_option ( options, tag ) );
499 }
500
501 /**
502  * Find DHCP numerical option, and return its value
503  *
504  * @v tag               DHCP option tag to search for
505  * @ret value           Numerical value of the option, or 0 if not found
506  *
507  * This function exists merely as a notational shorthand for a call to
508  * find_global_dhcp_option() followed by a call to dhcp_num_option().
509  * It is not possible to distinguish between the cases "option not
510  * found" and "option has a value of zero" using this function; if
511  * this matters to you then issue the two constituent calls directly
512  * and check that find_global_dhcp_option() returns a non-NULL value.
513  */
514 unsigned long find_global_dhcp_num_option ( unsigned int tag ) {
515         return dhcp_num_option ( find_global_dhcp_option ( tag ) );
516 }
517
518 /**
519  * Find DHCP IPv4-address option, and return its value
520  *
521  * @v options           DHCP options block
522  * @v tag               DHCP option tag to search for
523  * @v inp               IPv4 address to fill in
524  * @ret value           Numerical value of the option, or 0 if not found
525  *
526  * This function exists merely as a notational shorthand for a call to
527  * find_dhcp_option() followed by a call to dhcp_ipv4_option().  It is
528  * not possible to distinguish between the cases "option not found"
529  * and "option has a value of 0.0.0.0" using this function; if this
530  * matters to you then issue the two constituent calls directly and
531  * check that find_dhcp_option() returns a non-NULL value.
532  */
533 void find_dhcp_ipv4_option ( struct dhcp_option_block *options,
534                              unsigned int tag, struct in_addr *inp ) {
535         dhcp_ipv4_option ( find_dhcp_option ( options, tag ), inp );
536 }
537
538 /**
539  * Find DHCP IPv4-address option, and return its value
540  *
541  * @v options           DHCP options block
542  * @v tag               DHCP option tag to search for
543  * @v inp               IPv4 address to fill in
544  * @ret value           Numerical value of the option, or 0 if not found
545  *
546  * This function exists merely as a notational shorthand for a call to
547  * find_dhcp_option() followed by a call to dhcp_ipv4_option().  It is
548  * not possible to distinguish between the cases "option not found"
549  * and "option has a value of 0.0.0.0" using this function; if this
550  * matters to you then issue the two constituent calls directly and
551  * check that find_dhcp_option() returns a non-NULL value.
552  */
553 void find_global_dhcp_ipv4_option ( unsigned int tag, struct in_addr *inp ) {
554         dhcp_ipv4_option ( find_global_dhcp_option ( tag ), inp );
555 }
556
557 /**
558  * Delete DHCP option
559  *
560  * @v options           DHCP options block
561  * @v tag               DHCP option tag
562  *
563  * This function exists merely as a notational shorthand for a call to
564  * set_dhcp_option() with @c len set to zero.
565  */
566 void delete_dhcp_option ( struct dhcp_option_block *options,
567                           unsigned int tag ) {
568         set_dhcp_option ( options, tag, NULL, 0 );
569 }