2013-12-10 01:43:11 +04:00
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd .
Copyright ( C ) 2013 Intel Corporation . All rights reserved .
systemd is free software ; you can redistribute it and / or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation ; either version 2.1 of the License , or
( at your option ) any later version .
systemd is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public License
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
# include <stdint.h>
# include <string.h>
# include <errno.h>
# include <stdio.h>
# include "dhcp-internal.h"
2014-05-21 17:55:02 +04:00
static int option_append ( uint8_t options [ ] , size_t size , size_t * offset ,
uint8_t code , size_t optlen , const void * optval ) {
2014-05-20 13:04:50 +04:00
assert ( options ) ;
assert ( offset ) ;
2013-12-10 01:43:11 +04:00
2014-05-21 17:27:53 +04:00
if ( code ! = DHCP_OPTION_END )
/* always make sure there is space for an END option */
size - - ;
2013-12-10 01:43:11 +04:00
switch ( code ) {
case DHCP_OPTION_PAD :
case DHCP_OPTION_END :
2014-05-21 17:55:02 +04:00
if ( size < * offset + 1 )
2013-12-10 01:43:11 +04:00
return - ENOBUFS ;
2014-05-20 13:04:50 +04:00
options [ * offset ] = code ;
* offset + = 1 ;
2013-12-10 01:43:11 +04:00
break ;
default :
2014-05-21 17:55:02 +04:00
if ( size < * offset + optlen + 2 )
2013-12-10 01:43:11 +04:00
return - ENOBUFS ;
2014-05-20 13:04:50 +04:00
options [ * offset ] = code ;
options [ * offset + 1 ] = optlen ;
2014-05-21 17:55:02 +04:00
if ( optlen ) {
assert ( optval ) ;
memcpy ( & options [ * offset + 2 ] , optval , optlen ) ;
}
2013-12-10 01:43:11 +04:00
2014-05-20 13:04:50 +04:00
* offset + = optlen + 2 ;
2013-12-10 01:43:11 +04:00
break ;
}
return 0 ;
}
2014-05-21 17:55:02 +04:00
int dhcp_option_append ( DHCPMessage * message , size_t size , size_t * offset ,
uint8_t overload ,
uint8_t code , size_t optlen , const void * optval ) {
size_t file_offset = 0 , sname_offset = 0 ;
bool file , sname ;
int r ;
assert ( message ) ;
assert ( offset ) ;
file = overload & DHCP_OVERLOAD_FILE ;
sname = overload & DHCP_OVERLOAD_SNAME ;
if ( * offset < size ) {
/* still space in the options array */
r = option_append ( message - > options , size , offset , code , optlen , optval ) ;
if ( r > = 0 )
return 0 ;
else if ( r = = - ENOBUFS & & ( file | | sname ) ) {
/* did not fit, but we have more buffers to try
close the options array and move the offset to its end */
r = option_append ( message - > options , size , offset , DHCP_OPTION_END , 0 , NULL ) ;
if ( r < 0 )
return r ;
* offset = size ;
} else
return r ;
}
if ( overload & DHCP_OVERLOAD_FILE ) {
file_offset = * offset - size ;
if ( file_offset < sizeof ( message - > file ) ) {
/* still space in the 'file' array */
r = option_append ( message - > file , sizeof ( message - > file ) , & file_offset , code , optlen , optval ) ;
if ( r > = 0 ) {
* offset = size + file_offset ;
return 0 ;
} else if ( r = = - ENOBUFS & & sname ) {
/* did not fit, but we have more buffers to try
close the file array and move the offset to its end */
r = option_append ( message - > options , size , offset , DHCP_OPTION_END , 0 , NULL ) ;
if ( r < 0 )
return r ;
* offset = size + sizeof ( message - > file ) ;
} else
return r ;
}
}
if ( overload & DHCP_OVERLOAD_SNAME ) {
sname_offset = * offset - size - ( file ? sizeof ( message - > file ) : 0 ) ;
if ( sname_offset < sizeof ( message - > sname ) ) {
/* still space in the 'sname' array */
r = option_append ( message - > sname , sizeof ( message - > sname ) , & sname_offset , code , optlen , optval ) ;
if ( r > = 0 ) {
* offset = size + ( file ? sizeof ( message - > file ) : 0 ) + sname_offset ;
return 0 ;
} else {
/* no space, or other error, give up */
return r ;
}
}
}
return - ENOBUFS ;
}
2014-05-20 15:07:19 +04:00
static int parse_options ( const uint8_t options [ ] , size_t buflen , uint8_t * overload ,
2013-12-10 01:43:11 +04:00
uint8_t * message_type , dhcp_option_cb_t cb ,
2014-05-20 15:07:19 +04:00
void * user_data ) {
uint8_t code , len ;
size_t offset = 0 ;
2013-12-10 01:43:11 +04:00
2014-05-20 15:07:19 +04:00
while ( offset < buflen ) {
switch ( options [ offset ] ) {
2013-12-10 01:43:11 +04:00
case DHCP_OPTION_PAD :
2014-05-20 15:07:19 +04:00
offset + + ;
2013-12-10 01:43:11 +04:00
break ;
case DHCP_OPTION_END :
return 0 ;
case DHCP_OPTION_MESSAGE_TYPE :
2014-05-20 15:07:19 +04:00
if ( buflen < offset + 3 )
2013-12-10 01:43:11 +04:00
return - ENOBUFS ;
2014-05-20 15:07:19 +04:00
len = options [ + + offset ] ;
if ( len ! = 1 )
2013-12-10 01:43:11 +04:00
return - EINVAL ;
if ( message_type )
2014-05-20 15:07:19 +04:00
* message_type = options [ + + offset ] ;
else
offset + + ;
2013-12-10 01:43:11 +04:00
2014-05-20 15:07:19 +04:00
offset + + ;
2013-12-10 01:43:11 +04:00
break ;
case DHCP_OPTION_OVERLOAD :
2014-05-20 15:07:19 +04:00
if ( buflen < offset + 3 )
2013-12-10 01:43:11 +04:00
return - ENOBUFS ;
2014-05-20 15:07:19 +04:00
len = options [ + + offset ] ;
if ( len ! = 1 )
2013-12-10 01:43:11 +04:00
return - EINVAL ;
if ( overload )
2014-05-20 15:07:19 +04:00
* overload = options [ + + offset ] ;
else
offset + + ;
2013-12-10 01:43:11 +04:00
2014-05-20 15:07:19 +04:00
offset + + ;
2013-12-10 01:43:11 +04:00
break ;
default :
2014-05-20 15:07:19 +04:00
if ( buflen < offset + 3 )
2013-12-10 01:43:11 +04:00
return - ENOBUFS ;
2014-05-20 15:07:19 +04:00
code = options [ offset ] ;
len = options [ + + offset ] ;
2013-12-10 01:43:11 +04:00
2014-05-20 15:07:19 +04:00
if ( buflen < + + offset + len )
2013-12-10 01:43:11 +04:00
return - EINVAL ;
if ( cb )
2014-05-20 15:07:19 +04:00
cb ( code , len , & options [ offset ] , user_data ) ;
2013-12-10 01:43:11 +04:00
2014-05-20 15:07:19 +04:00
offset + = len ;
2013-12-10 01:43:11 +04:00
break ;
}
}
2014-05-20 15:07:19 +04:00
if ( offset < buflen )
2013-12-10 01:43:11 +04:00
return - EINVAL ;
return 0 ;
}
int dhcp_option_parse ( DHCPMessage * message , size_t len ,
2014-05-20 15:07:19 +04:00
dhcp_option_cb_t cb , void * user_data ) {
2013-12-10 01:43:11 +04:00
uint8_t overload = 0 ;
uint8_t message_type = 0 ;
2014-05-20 15:07:19 +04:00
int r ;
2013-12-10 01:43:11 +04:00
if ( ! message )
return - EINVAL ;
2014-04-06 16:05:32 +04:00
if ( len < sizeof ( DHCPMessage ) )
2013-12-10 01:43:11 +04:00
return - EINVAL ;
2014-04-06 16:05:32 +04:00
len - = sizeof ( DHCPMessage ) ;
2013-12-10 01:43:11 +04:00
2014-05-20 15:07:19 +04:00
r = parse_options ( message - > options , len , & overload , & message_type ,
cb , user_data ) ;
if ( r < 0 )
return r ;
2013-12-10 01:43:11 +04:00
if ( overload & DHCP_OVERLOAD_FILE ) {
2014-05-20 15:07:19 +04:00
r = parse_options ( message - > file , sizeof ( message - > file ) ,
2013-12-10 01:43:11 +04:00
NULL , & message_type , cb , user_data ) ;
2014-05-20 15:07:19 +04:00
if ( r < 0 )
return r ;
2013-12-10 01:43:11 +04:00
}
if ( overload & DHCP_OVERLOAD_SNAME ) {
2014-05-20 15:07:19 +04:00
r = parse_options ( message - > sname , sizeof ( message - > sname ) ,
2013-12-10 01:43:11 +04:00
NULL , & message_type , cb , user_data ) ;
2014-05-20 15:07:19 +04:00
if ( r < 0 )
return r ;
2013-12-10 01:43:11 +04:00
}
if ( message_type )
return message_type ;
return - ENOMSG ;
}