LCOV - code coverage report
Current view: top level - libsystemd-network - dhcp-option.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 98 151 64.9 %
Date: 2019-08-22 15:41:25 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /* SPDX-License-Identifier: LGPL-2.1+ */
       2             : /***
       3             :   Copyright © 2013 Intel Corporation. All rights reserved.
       4             : ***/
       5             : 
       6             : #include <errno.h>
       7             : #include <stdint.h>
       8             : #include <stdio.h>
       9             : #include <string.h>
      10             : 
      11             : #include "alloc-util.h"
      12             : #include "dhcp-internal.h"
      13             : #include "memory-util.h"
      14             : #include "strv.h"
      15             : #include "utf8.h"
      16             : 
      17          18 : static int option_append(uint8_t options[], size_t size, size_t *offset,
      18             :                          uint8_t code, size_t optlen, const void *optval) {
      19          18 :         assert(options);
      20          18 :         assert(offset);
      21             : 
      22          18 :         if (code != SD_DHCP_OPTION_END)
      23             :                 /* always make sure there is space for an END option */
      24          15 :                 size--;
      25             : 
      26          18 :         switch (code) {
      27             : 
      28           6 :         case SD_DHCP_OPTION_PAD:
      29             :         case SD_DHCP_OPTION_END:
      30           6 :                 if (*offset + 1 > size)
      31           1 :                         return -ENOBUFS;
      32             : 
      33           5 :                 options[*offset] = code;
      34           5 :                 *offset += 1;
      35           5 :                 break;
      36             : 
      37           0 :         case SD_DHCP_OPTION_USER_CLASS: {
      38           0 :                 size_t total = 0;
      39             :                 char **s;
      40             : 
      41           0 :                 STRV_FOREACH(s, (char **) optval) {
      42           0 :                         size_t len = strlen(*s);
      43             : 
      44           0 :                         if (len > 255)
      45           0 :                                 return -ENAMETOOLONG;
      46             : 
      47           0 :                         total += 1 + len;
      48             :                 }
      49             : 
      50           0 :                 if (*offset + 2 + total > size)
      51           0 :                         return -ENOBUFS;
      52             : 
      53           0 :                 options[*offset] = code;
      54           0 :                 options[*offset + 1] =  total;
      55           0 :                 *offset += 2;
      56             : 
      57           0 :                 STRV_FOREACH(s, (char **) optval) {
      58           0 :                         size_t len = strlen(*s);
      59             : 
      60           0 :                         options[*offset] = len;
      61             : 
      62           0 :                         memcpy(&options[*offset + 1], *s, len);
      63           0 :                         *offset += 1 + len;
      64             :                 }
      65             : 
      66           0 :                 break;
      67             :         }
      68          12 :         default:
      69          12 :                 if (*offset + 2 + optlen > size)
      70           1 :                         return -ENOBUFS;
      71             : 
      72          11 :                 options[*offset] = code;
      73          11 :                 options[*offset + 1] = optlen;
      74             : 
      75          11 :                 memcpy_safe(&options[*offset + 2], optval, optlen);
      76          11 :                 *offset += 2 + optlen;
      77             : 
      78          11 :                 break;
      79             :         }
      80             : 
      81          16 :         return 0;
      82             : }
      83             : 
      84          17 : int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
      85             :                        uint8_t overload,
      86             :                        uint8_t code, size_t optlen, const void *optval) {
      87          17 :         const bool use_file = overload & DHCP_OVERLOAD_FILE;
      88          17 :         const bool use_sname = overload & DHCP_OVERLOAD_SNAME;
      89             :         int r;
      90             : 
      91          17 :         assert(message);
      92          17 :         assert(offset);
      93             : 
      94             :         /* If *offset is in range [0, size), we are writing to ->options,
      95             :          * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file,
      96             :          * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname))
      97             :          * and use_sname, we are writing to ->sname.
      98             :          */
      99             : 
     100          17 :         if (*offset < size) {
     101             :                 /* still space in the options array */
     102          16 :                 r = option_append(message->options, size, offset, code, optlen, optval);
     103          16 :                 if (r >= 0)
     104          14 :                         return 0;
     105           2 :                 else if (r == -ENOBUFS && (use_file || use_sname)) {
     106             :                         /* did not fit, but we have more buffers to try
     107             :                            close the options array and move the offset to its end */
     108           1 :                         r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
     109           1 :                         if (r < 0)
     110           0 :                                 return r;
     111             : 
     112           1 :                         *offset = size;
     113             :                 } else
     114           1 :                         return r;
     115             :         }
     116             : 
     117           2 :         if (use_file) {
     118           0 :                 size_t file_offset = *offset - size;
     119             : 
     120           0 :                 if (file_offset < sizeof(message->file)) {
     121             :                         /* still space in the 'file' array */
     122           0 :                         r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
     123           0 :                         if (r >= 0) {
     124           0 :                                 *offset = size + file_offset;
     125           0 :                                 return 0;
     126           0 :                         } else if (r == -ENOBUFS && use_sname) {
     127             :                                 /* did not fit, but we have more buffers to try
     128             :                                    close the file array and move the offset to its end */
     129           0 :                                 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
     130           0 :                                 if (r < 0)
     131           0 :                                         return r;
     132             : 
     133           0 :                                 *offset = size + sizeof(message->file);
     134             :                         } else
     135           0 :                                 return r;
     136             :                 }
     137             :         }
     138             : 
     139           2 :         if (use_sname) {
     140           1 :                 size_t sname_offset = *offset - size - use_file*sizeof(message->file);
     141             : 
     142           1 :                 if (sname_offset < sizeof(message->sname)) {
     143             :                         /* still space in the 'sname' array */
     144           1 :                         r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
     145           1 :                         if (r >= 0) {
     146           1 :                                 *offset = size + use_file*sizeof(message->file) + sname_offset;
     147           1 :                                 return 0;
     148             :                         } else
     149             :                                 /* no space, or other error, give up */
     150           0 :                                 return r;
     151             :                 }
     152             :         }
     153             : 
     154           1 :         return -ENOBUFS;
     155             : }
     156             : 
     157          16 : static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
     158             :                          uint8_t *message_type, char **error_message, dhcp_option_callback_t cb,
     159             :                          void *userdata) {
     160             :         uint8_t code, len;
     161             :         const uint8_t *option;
     162          16 :         size_t offset = 0;
     163             : 
     164         359 :         while (offset < buflen) {
     165         348 :                 code = options[offset ++];
     166             : 
     167         348 :                 switch (code) {
     168         308 :                 case SD_DHCP_OPTION_PAD:
     169         308 :                         continue;
     170             : 
     171           3 :                 case SD_DHCP_OPTION_END:
     172           3 :                         return 0;
     173             :                 }
     174             : 
     175          37 :                 if (buflen < offset + 1)
     176           1 :                         return -ENOBUFS;
     177             : 
     178          36 :                 len = options[offset ++];
     179             : 
     180          36 :                 if (buflen < offset + len)
     181           1 :                         return -EINVAL;
     182             : 
     183          35 :                 option = &options[offset];
     184             : 
     185          35 :                 switch (code) {
     186           9 :                 case SD_DHCP_OPTION_MESSAGE_TYPE:
     187           9 :                         if (len != 1)
     188           0 :                                 return -EINVAL;
     189             : 
     190           9 :                         if (message_type)
     191           9 :                                 *message_type = *option;
     192             : 
     193           9 :                         break;
     194             : 
     195           0 :                 case SD_DHCP_OPTION_ERROR_MESSAGE:
     196           0 :                         if (len == 0)
     197           0 :                                 return -EINVAL;
     198             : 
     199           0 :                         if (error_message) {
     200           0 :                                 _cleanup_free_ char *string = NULL;
     201             : 
     202             :                                 /* Accept a trailing NUL byte */
     203           0 :                                 if (memchr(option, 0, len - 1))
     204           0 :                                         return -EINVAL;
     205             : 
     206           0 :                                 string = memdup_suffix0((const char *) option, len);
     207           0 :                                 if (!string)
     208           0 :                                         return -ENOMEM;
     209             : 
     210           0 :                                 if (!ascii_is_valid(string))
     211           0 :                                         return -EINVAL;
     212             : 
     213           0 :                                 free_and_replace(*error_message, string);
     214             :                         }
     215             : 
     216           0 :                         break;
     217           2 :                 case SD_DHCP_OPTION_OVERLOAD:
     218           2 :                         if (len != 1)
     219           0 :                                 return -EINVAL;
     220             : 
     221           2 :                         if (overload)
     222           2 :                                 *overload = *option;
     223             : 
     224           2 :                         break;
     225             : 
     226          24 :                 default:
     227          24 :                         if (cb)
     228          24 :                                 cb(code, len, option, userdata);
     229             : 
     230          24 :                         break;
     231             :                 }
     232             : 
     233          35 :                 offset += len;
     234             :         }
     235             : 
     236          11 :         if (offset < buflen)
     237           0 :                 return -EINVAL;
     238             : 
     239          11 :         return 0;
     240             : }
     241             : 
     242          15 : int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) {
     243          15 :         _cleanup_free_ char *error_message = NULL;
     244          15 :         uint8_t overload = 0;
     245          15 :         uint8_t message_type = 0;
     246             :         int r;
     247             : 
     248          15 :         if (!message)
     249           0 :                 return -EINVAL;
     250             : 
     251          15 :         if (len < sizeof(DHCPMessage))
     252           2 :                 return -EINVAL;
     253             : 
     254          13 :         len -= sizeof(DHCPMessage);
     255             : 
     256          13 :         r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
     257          13 :         if (r < 0)
     258           2 :                 return r;
     259             : 
     260          11 :         if (overload & DHCP_OVERLOAD_FILE) {
     261           2 :                 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
     262           2 :                 if (r < 0)
     263           0 :                         return r;
     264             :         }
     265             : 
     266          11 :         if (overload & DHCP_OVERLOAD_SNAME) {
     267           1 :                 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
     268           1 :                 if (r < 0)
     269           0 :                         return r;
     270             :         }
     271             : 
     272          11 :         if (message_type == 0)
     273           2 :                 return -ENOMSG;
     274             : 
     275           9 :         if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
     276           0 :                 *_error_message = TAKE_PTR(error_message);
     277             : 
     278           9 :         return message_type;
     279             : }

Generated by: LCOV version 1.14