]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp-option.c
dhcp: make DHCP_OPTION_* enum public
[thirdparty/systemd.git] / src / libsystemd-network / dhcp-option.c
CommitLineData
524cf458
PF
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright (C) 2013 Intel Corporation. All rights reserved.
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
524cf458 22#include <errno.h>
cf0fbc49 23#include <stdint.h>
524cf458 24#include <stdio.h>
cf0fbc49 25#include <string.h>
524cf458 26
f693e9b3
TG
27#include "alloc-util.h"
28#include "utf8.h"
29
524cf458
PF
30#include "dhcp-internal.h"
31
04b28be1
TG
32static int option_append(uint8_t options[], size_t size, size_t *offset,
33 uint8_t code, size_t optlen, const void *optval) {
20b958bf
TG
34 assert(options);
35 assert(offset);
524cf458 36
22805d92 37 if (code != SD_DHCP_OPTION_END)
2688ef60
TG
38 /* always make sure there is space for an END option */
39 size --;
40
524cf458
PF
41 switch (code) {
42
22805d92
BG
43 case SD_DHCP_OPTION_PAD:
44 case SD_DHCP_OPTION_END:
04b28be1 45 if (size < *offset + 1)
524cf458
PF
46 return -ENOBUFS;
47
20b958bf
TG
48 options[*offset] = code;
49 *offset += 1;
524cf458
PF
50 break;
51
52 default:
04b28be1 53 if (size < *offset + optlen + 2)
524cf458
PF
54 return -ENOBUFS;
55
20b958bf
TG
56 options[*offset] = code;
57 options[*offset + 1] = optlen;
04b28be1
TG
58
59 if (optlen) {
60 assert(optval);
61
62 memcpy(&options[*offset + 2], optval, optlen);
63 }
524cf458 64
20b958bf 65 *offset += optlen + 2;
524cf458
PF
66
67 break;
68 }
69
70 return 0;
71}
72
04b28be1
TG
73int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
74 uint8_t overload,
75 uint8_t code, size_t optlen, const void *optval) {
76 size_t file_offset = 0, sname_offset =0;
77 bool file, sname;
78 int r;
79
80 assert(message);
81 assert(offset);
82
83 file = overload & DHCP_OVERLOAD_FILE;
84 sname = overload & DHCP_OVERLOAD_SNAME;
85
86 if (*offset < size) {
87 /* still space in the options array */
88 r = option_append(message->options, size, offset, code, optlen, optval);
89 if (r >= 0)
90 return 0;
91 else if (r == -ENOBUFS && (file || sname)) {
92 /* did not fit, but we have more buffers to try
93 close the options array and move the offset to its end */
22805d92 94 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
04b28be1
TG
95 if (r < 0)
96 return r;
97
98 *offset = size;
99 } else
100 return r;
101 }
102
103 if (overload & DHCP_OVERLOAD_FILE) {
104 file_offset = *offset - size;
105
106 if (file_offset < sizeof(message->file)) {
107 /* still space in the 'file' array */
108 r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
109 if (r >= 0) {
110 *offset = size + file_offset;
111 return 0;
112 } else if (r == -ENOBUFS && sname) {
113 /* did not fit, but we have more buffers to try
114 close the file array and move the offset to its end */
22805d92 115 r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL);
04b28be1
TG
116 if (r < 0)
117 return r;
118
119 *offset = size + sizeof(message->file);
120 } else
121 return r;
122 }
123 }
124
125 if (overload & DHCP_OVERLOAD_SNAME) {
126 sname_offset = *offset - size - (file ? sizeof(message->file) : 0);
127
128 if (sname_offset < sizeof(message->sname)) {
129 /* still space in the 'sname' array */
130 r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
131 if (r >= 0) {
132 *offset = size + (file ? sizeof(message->file) : 0) + sname_offset;
133 return 0;
134 } else {
135 /* no space, or other error, give up */
136 return r;
137 }
138 }
139 }
140
141 return -ENOBUFS;
142}
143
32008a96 144static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
f693e9b3 145 uint8_t *message_type, char **error_message, dhcp_option_cb_t cb,
89ca10c6 146 void *userdata) {
32008a96 147 uint8_t code, len;
f693e9b3 148 const uint8_t *option;
32008a96 149 size_t offset = 0;
524cf458 150
32008a96 151 while (offset < buflen) {
f693e9b3 152 code = options[offset ++];
32008a96 153
f693e9b3 154 switch (code) {
22805d92 155 case SD_DHCP_OPTION_PAD:
f693e9b3 156 continue;
524cf458 157
22805d92 158 case SD_DHCP_OPTION_END:
524cf458 159 return 0;
f693e9b3 160 }
524cf458 161
f693e9b3
TG
162 if (buflen < offset + 1)
163 return -ENOBUFS;
164
165 len = options[offset ++];
524cf458 166
f693e9b3
TG
167 if (buflen < offset + len)
168 return -EINVAL;
169
170 option = &options[offset];
171
172 switch (code) {
22805d92 173 case SD_DHCP_OPTION_MESSAGE_TYPE:
32008a96 174 if (len != 1)
524cf458
PF
175 return -EINVAL;
176
177 if (message_type)
f693e9b3 178 *message_type = *option;
524cf458
PF
179
180 break;
181
22805d92 182 case SD_DHCP_OPTION_ERROR_MESSAGE:
f693e9b3 183 if (len == 0)
524cf458
PF
184 return -EINVAL;
185
f693e9b3
TG
186 if (error_message) {
187 _cleanup_free_ char *string = NULL;
524cf458 188
f693e9b3
TG
189 /* Accept a trailing NUL byte */
190 if (memchr(option, 0, len - 1))
191 return -EINVAL;
524cf458 192
f693e9b3
TG
193 string = strndup((const char *) option, len);
194 if (!string)
195 return -ENOMEM;
524cf458 196
f693e9b3
TG
197 if (!ascii_is_valid(string))
198 return -EINVAL;
524cf458 199
f693e9b3
TG
200 free(*error_message);
201 *error_message = string;
202 string = NULL;
203 }
524cf458 204
f693e9b3 205 break;
22805d92 206 case SD_DHCP_OPTION_OVERLOAD:
f693e9b3 207 if (len != 1)
524cf458 208 return -EINVAL;
524cf458 209
f693e9b3
TG
210 if (overload)
211 *overload = *option;
524cf458 212
f693e9b3
TG
213 break;
214
215 default:
216 if (cb)
217 cb(code, len, option, userdata);
524cf458
PF
218
219 break;
220 }
f693e9b3
TG
221
222 offset += len;
524cf458
PF
223 }
224
32008a96 225 if (offset < buflen)
524cf458
PF
226 return -EINVAL;
227
228 return 0;
229}
230
f693e9b3
TG
231int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_cb_t cb, void *userdata, char **_error_message) {
232 _cleanup_free_ char *error_message = NULL;
524cf458
PF
233 uint8_t overload = 0;
234 uint8_t message_type = 0;
32008a96 235 int r;
524cf458
PF
236
237 if (!message)
238 return -EINVAL;
239
3b7ca119 240 if (len < sizeof(DHCPMessage))
524cf458
PF
241 return -EINVAL;
242
3b7ca119 243 len -= sizeof(DHCPMessage);
524cf458 244
f693e9b3 245 r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
32008a96
TG
246 if (r < 0)
247 return r;
524cf458
PF
248
249 if (overload & DHCP_OVERLOAD_FILE) {
f693e9b3 250 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
32008a96
TG
251 if (r < 0)
252 return r;
524cf458
PF
253 }
254
255 if (overload & DHCP_OVERLOAD_SNAME) {
f693e9b3 256 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
32008a96
TG
257 if (r < 0)
258 return r;
524cf458
PF
259 }
260
f693e9b3
TG
261 if (message_type == 0)
262 return -ENOMSG;
263
264 if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) {
265 *_error_message = error_message;
266 error_message = NULL;
267 }
524cf458 268
f693e9b3 269 return message_type;
524cf458 270}