Line data Source code
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 <stdint.h>
23 : #include <string.h>
24 : #include <errno.h>
25 : #include <stdio.h>
26 :
27 : #include "dhcp-internal.h"
28 :
29 25 : static int option_append(uint8_t options[], size_t size, size_t *offset,
30 : uint8_t code, size_t optlen, const void *optval) {
31 25 : assert(options);
32 25 : assert(offset);
33 :
34 25 : if (code != DHCP_OPTION_END)
35 : /* always make sure there is space for an END option */
36 21 : size --;
37 :
38 25 : switch (code) {
39 :
40 : case DHCP_OPTION_PAD:
41 : case DHCP_OPTION_END:
42 7 : if (size < *offset + 1)
43 1 : return -ENOBUFS;
44 :
45 6 : options[*offset] = code;
46 6 : *offset += 1;
47 6 : break;
48 :
49 : default:
50 18 : if (size < *offset + optlen + 2)
51 1 : return -ENOBUFS;
52 :
53 17 : options[*offset] = code;
54 17 : options[*offset + 1] = optlen;
55 :
56 17 : if (optlen) {
57 17 : assert(optval);
58 :
59 17 : memcpy(&options[*offset + 2], optval, optlen);
60 : }
61 :
62 17 : *offset += optlen + 2;
63 :
64 17 : break;
65 : }
66 :
67 23 : return 0;
68 : }
69 :
70 24 : int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
71 : uint8_t overload,
72 : uint8_t code, size_t optlen, const void *optval) {
73 24 : size_t file_offset = 0, sname_offset =0;
74 : bool file, sname;
75 : int r;
76 :
77 24 : assert(message);
78 24 : assert(offset);
79 :
80 24 : file = overload & DHCP_OVERLOAD_FILE;
81 24 : sname = overload & DHCP_OVERLOAD_SNAME;
82 :
83 24 : if (*offset < size) {
84 : /* still space in the options array */
85 23 : r = option_append(message->options, size, offset, code, optlen, optval);
86 23 : if (r >= 0)
87 21 : return 0;
88 2 : else if (r == -ENOBUFS && (file || sname)) {
89 : /* did not fit, but we have more buffers to try
90 : close the options array and move the offset to its end */
91 1 : r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL);
92 1 : if (r < 0)
93 0 : return r;
94 :
95 1 : *offset = size;
96 : } else
97 1 : return r;
98 : }
99 :
100 2 : if (overload & DHCP_OVERLOAD_FILE) {
101 0 : file_offset = *offset - size;
102 :
103 0 : if (file_offset < sizeof(message->file)) {
104 : /* still space in the 'file' array */
105 0 : r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
106 0 : if (r >= 0) {
107 0 : *offset = size + file_offset;
108 0 : return 0;
109 0 : } else if (r == -ENOBUFS && sname) {
110 : /* did not fit, but we have more buffers to try
111 : close the file array and move the offset to its end */
112 0 : r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL);
113 0 : if (r < 0)
114 0 : return r;
115 :
116 0 : *offset = size + sizeof(message->file);
117 : } else
118 0 : return r;
119 : }
120 : }
121 :
122 2 : if (overload & DHCP_OVERLOAD_SNAME) {
123 1 : sname_offset = *offset - size - (file ? sizeof(message->file) : 0);
124 :
125 1 : if (sname_offset < sizeof(message->sname)) {
126 : /* still space in the 'sname' array */
127 1 : r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
128 1 : if (r >= 0) {
129 1 : *offset = size + (file ? sizeof(message->file) : 0) + sname_offset;
130 1 : return 0;
131 : } else {
132 : /* no space, or other error, give up */
133 0 : return r;
134 : }
135 : }
136 : }
137 :
138 1 : return -ENOBUFS;
139 : }
140 :
141 18 : static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
142 : uint8_t *message_type, dhcp_option_cb_t cb,
143 : void *user_data) {
144 : uint8_t code, len;
145 18 : size_t offset = 0;
146 :
147 392 : while (offset < buflen) {
148 363 : switch (options[offset]) {
149 : case DHCP_OPTION_PAD:
150 308 : offset++;
151 :
152 308 : break;
153 :
154 : case DHCP_OPTION_END:
155 5 : return 0;
156 :
157 : case DHCP_OPTION_MESSAGE_TYPE:
158 11 : if (buflen < offset + 3)
159 0 : return -ENOBUFS;
160 :
161 11 : len = options[++offset];
162 11 : if (len != 1)
163 0 : return -EINVAL;
164 :
165 11 : if (message_type)
166 11 : *message_type = options[++offset];
167 : else
168 0 : offset++;
169 :
170 11 : offset++;
171 :
172 11 : break;
173 :
174 : case DHCP_OPTION_OVERLOAD:
175 2 : if (buflen < offset + 3)
176 0 : return -ENOBUFS;
177 :
178 2 : len = options[++offset];
179 2 : if (len != 1)
180 0 : return -EINVAL;
181 :
182 2 : if (overload)
183 2 : *overload = options[++offset];
184 : else
185 0 : offset++;
186 :
187 2 : offset++;
188 :
189 2 : break;
190 :
191 : default:
192 37 : if (buflen < offset + 3)
193 1 : return -ENOBUFS;
194 :
195 36 : code = options[offset];
196 36 : len = options[++offset];
197 :
198 36 : if (buflen < ++offset + len)
199 1 : return -EINVAL;
200 :
201 35 : if (cb)
202 35 : cb(code, len, &options[offset], user_data);
203 :
204 35 : offset += len;
205 :
206 35 : break;
207 : }
208 : }
209 :
210 11 : if (offset < buflen)
211 0 : return -EINVAL;
212 :
213 11 : return 0;
214 : }
215 :
216 17 : int dhcp_option_parse(DHCPMessage *message, size_t len,
217 : dhcp_option_cb_t cb, void *user_data) {
218 17 : uint8_t overload = 0;
219 17 : uint8_t message_type = 0;
220 : int r;
221 :
222 17 : if (!message)
223 0 : return -EINVAL;
224 :
225 17 : if (len < sizeof(DHCPMessage))
226 2 : return -EINVAL;
227 :
228 15 : len -= sizeof(DHCPMessage);
229 :
230 15 : r = parse_options(message->options, len, &overload, &message_type,
231 : cb, user_data);
232 15 : if (r < 0)
233 2 : return r;
234 :
235 13 : if (overload & DHCP_OVERLOAD_FILE) {
236 2 : r = parse_options(message->file, sizeof(message->file),
237 : NULL, &message_type, cb, user_data);
238 2 : if (r < 0)
239 0 : return r;
240 : }
241 :
242 13 : if (overload & DHCP_OVERLOAD_SNAME) {
243 1 : r = parse_options(message->sname, sizeof(message->sname),
244 : NULL, &message_type, cb, user_data);
245 1 : if (r < 0)
246 0 : return r;
247 : }
248 :
249 13 : if (message_type)
250 11 : return message_type;
251 :
252 2 : return -ENOMSG;
253 : }
|