]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp-option.c
Merge pull request #2040 from keszybz/randomized-delay
[thirdparty/systemd.git] / src / libsystemd-network / dhcp-option.c
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
22 #include <errno.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "alloc-util.h"
28 #include "utf8.h"
29
30 #include "dhcp-internal.h"
31
32 static int option_append(uint8_t options[], size_t size, size_t *offset,
33 uint8_t code, size_t optlen, const void *optval) {
34 assert(options);
35 assert(offset);
36
37 if (code != DHCP_OPTION_END)
38 /* always make sure there is space for an END option */
39 size --;
40
41 switch (code) {
42
43 case DHCP_OPTION_PAD:
44 case DHCP_OPTION_END:
45 if (size < *offset + 1)
46 return -ENOBUFS;
47
48 options[*offset] = code;
49 *offset += 1;
50 break;
51
52 default:
53 if (size < *offset + optlen + 2)
54 return -ENOBUFS;
55
56 options[*offset] = code;
57 options[*offset + 1] = optlen;
58
59 if (optlen) {
60 assert(optval);
61
62 memcpy(&options[*offset + 2], optval, optlen);
63 }
64
65 *offset += optlen + 2;
66
67 break;
68 }
69
70 return 0;
71 }
72
73 int 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 */
94 r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL);
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 */
115 r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL);
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
144 static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
145 uint8_t *message_type, char **error_message, dhcp_option_cb_t cb,
146 void *userdata) {
147 uint8_t code, len;
148 const uint8_t *option;
149 size_t offset = 0;
150
151 while (offset < buflen) {
152 code = options[offset ++];
153
154 switch (code) {
155 case DHCP_OPTION_PAD:
156 continue;
157
158 case DHCP_OPTION_END:
159 return 0;
160 }
161
162 if (buflen < offset + 1)
163 return -ENOBUFS;
164
165 len = options[offset ++];
166
167 if (buflen < offset + len)
168 return -EINVAL;
169
170 option = &options[offset];
171
172 switch (code) {
173 case DHCP_OPTION_MESSAGE_TYPE:
174 if (len != 1)
175 return -EINVAL;
176
177 if (message_type)
178 *message_type = *option;
179
180 break;
181
182 case DHCP_OPTION_ERROR_MESSAGE:
183 if (len == 0)
184 return -EINVAL;
185
186 if (error_message) {
187 _cleanup_free_ char *string = NULL;
188
189 /* Accept a trailing NUL byte */
190 if (memchr(option, 0, len - 1))
191 return -EINVAL;
192
193 string = strndup((const char *) option, len);
194 if (!string)
195 return -ENOMEM;
196
197 if (!ascii_is_valid(string))
198 return -EINVAL;
199
200 free(*error_message);
201 *error_message = string;
202 string = NULL;
203 }
204
205 break;
206 case DHCP_OPTION_OVERLOAD:
207 if (len != 1)
208 return -EINVAL;
209
210 if (overload)
211 *overload = *option;
212
213 break;
214
215 default:
216 if (cb)
217 cb(code, len, option, userdata);
218
219 break;
220 }
221
222 offset += len;
223 }
224
225 if (offset < buflen)
226 return -EINVAL;
227
228 return 0;
229 }
230
231 int 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;
233 uint8_t overload = 0;
234 uint8_t message_type = 0;
235 int r;
236
237 if (!message)
238 return -EINVAL;
239
240 if (len < sizeof(DHCPMessage))
241 return -EINVAL;
242
243 len -= sizeof(DHCPMessage);
244
245 r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata);
246 if (r < 0)
247 return r;
248
249 if (overload & DHCP_OVERLOAD_FILE) {
250 r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata);
251 if (r < 0)
252 return r;
253 }
254
255 if (overload & DHCP_OVERLOAD_SNAME) {
256 r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata);
257 if (r < 0)
258 return r;
259 }
260
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 }
268
269 return message_type;
270 }