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