/src/freeradius-server/src/protocols/tacacs/encode.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: ae626531476fc4cb7a740d10db086373a9f01a48 $ |
19 | | * |
20 | | * @file protocols/tacacs/encode.c |
21 | | * @brief Low-Level TACACS+ encode functions |
22 | | * |
23 | | * @copyright 2017 The FreeRADIUS server project |
24 | | * @copyright 2017 Network RADIUS SAS (legal@networkradius.com) |
25 | | */ |
26 | | |
27 | | #include <freeradius-devel/io/test_point.h> |
28 | | #include <freeradius-devel/protocol/tacacs/tacacs.h> |
29 | | #include <freeradius-devel/util/dbuff.h> |
30 | | #include <freeradius-devel/util/rand.h> |
31 | | #include <freeradius-devel/util/struct.h> |
32 | | |
33 | | #include "tacacs.h" |
34 | | #include "attrs.h" |
35 | | |
36 | | int fr_tacacs_code_to_packet(fr_tacacs_packet_t *pkt, uint32_t code) |
37 | 0 | { |
38 | 0 | switch (code) { |
39 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_START: |
40 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
41 | 0 | pkt->hdr.seq_no = 1; |
42 | 0 | break; |
43 | | |
44 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_PASS: |
45 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
46 | 0 | pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_PASS; |
47 | 0 | break; |
48 | | |
49 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_FAIL: |
50 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
51 | 0 | pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_FAIL; |
52 | 0 | break; |
53 | | |
54 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_GETDATA: |
55 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
56 | 0 | pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_GETDATA; |
57 | 0 | break; |
58 | | |
59 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_GETUSER: |
60 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
61 | 0 | pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_GETUSER; |
62 | 0 | break; |
63 | | |
64 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_GETPASS: |
65 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
66 | 0 | pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_GETPASS; |
67 | 0 | break; |
68 | | |
69 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_RESTART: |
70 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
71 | 0 | pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_RESTART; |
72 | 0 | break; |
73 | | |
74 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_ERROR: |
75 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
76 | 0 | pkt->authen_reply.status = FR_TAC_PLUS_AUTHEN_STATUS_ERROR; |
77 | 0 | break; |
78 | | |
79 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE: |
80 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
81 | 0 | pkt->authen_cont.flags = FR_TAC_PLUS_CONTINUE_FLAG_UNSET; |
82 | 0 | break; |
83 | | |
84 | 0 | case FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE_ABORT: |
85 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHEN; |
86 | 0 | pkt->authen_cont.flags = FR_TAC_PLUS_CONTINUE_FLAG_ABORT; |
87 | 0 | break; |
88 | | |
89 | 0 | case FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST: |
90 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHOR; |
91 | 0 | break; |
92 | | |
93 | 0 | case FR_PACKET_TYPE_VALUE_AUTHORIZATION_PASS_ADD: |
94 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHOR; |
95 | 0 | pkt->author_reply.status = FR_TAC_PLUS_AUTHOR_STATUS_PASS_ADD; |
96 | 0 | break; |
97 | | |
98 | 0 | case FR_PACKET_TYPE_VALUE_AUTHORIZATION_PASS_REPLACE: |
99 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHOR; |
100 | 0 | pkt->author_reply.status = FR_TAC_PLUS_AUTHOR_STATUS_PASS_REPL; |
101 | 0 | break; |
102 | | |
103 | 0 | case FR_PACKET_TYPE_VALUE_AUTHORIZATION_FAIL: |
104 | 0 | pkt->hdr.type = FR_TAC_PLUS_AUTHOR; |
105 | 0 | pkt->author_reply.status = FR_TAC_PLUS_AUTHOR_STATUS_FAIL; |
106 | 0 | break; |
107 | | |
108 | 0 | case FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST: |
109 | 0 | pkt->hdr.type = FR_TAC_PLUS_ACCT; |
110 | 0 | break; |
111 | | |
112 | 0 | case FR_PACKET_TYPE_VALUE_ACCOUNTING_SUCCESS: |
113 | 0 | pkt->hdr.type = FR_TAC_PLUS_ACCT; |
114 | 0 | pkt->acct_reply.status = FR_TAC_PLUS_ACCT_STATUS_SUCCESS; |
115 | 0 | break; |
116 | | |
117 | 0 | case FR_PACKET_TYPE_VALUE_ACCOUNTING_ERROR: |
118 | 0 | pkt->hdr.type = FR_TAC_PLUS_ACCT; |
119 | 0 | pkt->acct_reply.status = FR_TAC_PLUS_ACCT_STATUS_ERROR; |
120 | 0 | break; |
121 | | |
122 | 0 | default: |
123 | 0 | fr_strerror_const("Invalid TACACS+ packet type"); |
124 | 0 | return -1; |
125 | 0 | } |
126 | | |
127 | 0 | return 0; |
128 | 0 | } |
129 | | |
130 | | /** |
131 | | * Encode a TACACS+ 'arg_N' fields. |
132 | | */ |
133 | | static uint8_t tacacs_encode_body_arg_cnt(fr_pair_list_t *vps, fr_dict_attr_t const *da) |
134 | 0 | { |
135 | 0 | int arg_cnt = 0; |
136 | 0 | fr_pair_t *vp; |
137 | |
|
138 | 0 | for (vp = fr_pair_list_head(vps); |
139 | 0 | vp; |
140 | 0 | vp = fr_pair_list_next(vps, vp)) { |
141 | 0 | if (arg_cnt == 255) break; |
142 | | |
143 | 0 | if (vp->da->flags.internal) continue; |
144 | | |
145 | 0 | if (vp->da == attr_tacacs_packet) continue; |
146 | | |
147 | | /* |
148 | | * Argument-List = "foo=bar" |
149 | | */ |
150 | 0 | if (vp->da == da) { |
151 | 0 | if (vp->vp_length > 0xff) continue; |
152 | 0 | arg_cnt++; |
153 | 0 | continue; |
154 | 0 | } |
155 | | |
156 | 0 | fr_assert(fr_dict_by_da(vp->da) == dict_tacacs); |
157 | | |
158 | | /* |
159 | | * Recurse into children. |
160 | | */ |
161 | 0 | if (vp->vp_type == FR_TYPE_VENDOR) { |
162 | 0 | arg_cnt += tacacs_encode_body_arg_cnt(&vp->vp_group, NULL); |
163 | 0 | continue; |
164 | 0 | } |
165 | | |
166 | | /* |
167 | | * RFC 8907 attributes. |
168 | | */ |
169 | 0 | if (vp->da->parent->flags.is_root) { |
170 | 0 | arg_cnt++; |
171 | 0 | continue; |
172 | 0 | } |
173 | | |
174 | 0 | if (vp->da->parent->type != FR_TYPE_VENDOR) continue; |
175 | | |
176 | 0 | arg_cnt++; |
177 | 0 | } |
178 | |
|
179 | 0 | return arg_cnt; |
180 | 0 | } |
181 | | |
182 | | static ssize_t tacacs_encode_body_arg_n(fr_dbuff_t *dbuff, uint8_t arg_cnt, uint8_t *arg_len, fr_pair_list_t *vps, fr_dict_attr_t const *da) |
183 | 0 | { |
184 | 0 | fr_pair_t *vp; |
185 | 0 | uint8_t i = 0; |
186 | 0 | fr_dbuff_t work_dbuff = FR_DBUFF(dbuff); |
187 | |
|
188 | 0 | for (vp = fr_pair_list_head(vps); |
189 | 0 | vp; |
190 | 0 | vp = fr_pair_list_next(vps, vp)) { |
191 | 0 | int len; |
192 | |
|
193 | 0 | if (i == 255) break; |
194 | 0 | if (i > arg_cnt) break; |
195 | | |
196 | 0 | if (vp->da->flags.internal) continue; |
197 | | |
198 | 0 | if (vp->da == attr_tacacs_packet) continue; |
199 | | |
200 | | /* |
201 | | * Argument-List = "foo=bar" |
202 | | */ |
203 | 0 | if (vp->da == da) { |
204 | 0 | if (vp->vp_length > 0xff) continue; |
205 | | |
206 | | /* Append the <arg_N> field */ |
207 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, vp->vp_length); |
208 | | |
209 | 0 | FR_PROTO_TRACE("arg[%d] --> %s", i, vp->vp_strvalue); |
210 | 0 | len = vp->vp_length; |
211 | |
|
212 | 0 | } else if (vp->vp_type == FR_TYPE_VENDOR) { |
213 | 0 | ssize_t slen; |
214 | 0 | uint8_t child_argc; |
215 | | |
216 | | /* |
217 | | * Nested attribute: just recurse. |
218 | | */ |
219 | 0 | child_argc = fr_pair_list_num_elements(&vp->vp_group); |
220 | 0 | if (child_argc > (arg_cnt - i)) child_argc = arg_cnt - i; |
221 | |
|
222 | 0 | slen = tacacs_encode_body_arg_n(&work_dbuff, child_argc, &arg_len[i], &vp->vp_group, vp->da); |
223 | 0 | if (slen < 0) return FR_DBUFF_ERROR_OFFSET(slen, fr_dbuff_used(&work_dbuff)); |
224 | | |
225 | 0 | i += child_argc; |
226 | 0 | continue; |
227 | |
|
228 | 0 | } else if (!vp->da->parent || (!vp->da->parent->flags.is_root && (vp->da->parent->type != FR_TYPE_VENDOR))) { |
229 | 0 | continue; |
230 | |
|
231 | 0 | } else { |
232 | 0 | ssize_t slen; |
233 | 0 | fr_sbuff_t sbuff; |
234 | 0 | fr_dbuff_t arg_dbuff = FR_DBUFF_MAX(&work_dbuff, 255); |
235 | 0 | fr_value_box_t box; |
236 | 0 | char buffer[256]; |
237 | | |
238 | | /* |
239 | | * Print it as "name=value" |
240 | | */ |
241 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&arg_dbuff, vp->da->name, strlen(vp->da->name)); |
242 | 0 | FR_DBUFF_IN_BYTES_RETURN(&arg_dbuff, (uint8_t) '='); |
243 | | |
244 | 0 | sbuff = FR_SBUFF_OUT(buffer, sizeof(buffer)); |
245 | |
|
246 | 0 | switch (vp->vp_type) { |
247 | | /* |
248 | | * For now, we always print time deltas and dates as integers. |
249 | | * |
250 | | * Because everyone else's date formats are insane. |
251 | | */ |
252 | 0 | case FR_TYPE_DATE: |
253 | 0 | case FR_TYPE_TIME_DELTA: |
254 | 0 | fr_value_box_init(&box, FR_TYPE_UINT64, vp->data.enumv, vp->vp_tainted); |
255 | 0 | if (fr_value_box_cast(NULL, &box, FR_TYPE_UINT64, NULL, &vp->data) < 0) { |
256 | 0 | buffer[0] = '\0'; |
257 | 0 | slen = 0; |
258 | 0 | break; |
259 | 0 | } |
260 | | |
261 | 0 | slen = fr_sbuff_in_sprintf(&sbuff, "%lu", box.vb_uint64); |
262 | 0 | if (slen <= 0) return -1; |
263 | 0 | break; |
264 | | |
265 | 0 | default: |
266 | 0 | slen = fr_pair_print_value_quoted(&sbuff, vp, T_BARE_WORD); |
267 | 0 | if (slen <= 0) return -1; |
268 | 0 | } |
269 | | |
270 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&arg_dbuff, buffer, (size_t) slen); |
271 | | |
272 | 0 | len = fr_dbuff_used(&arg_dbuff); |
273 | |
|
274 | 0 | FR_PROTO_TRACE("arg[%d] --> %.*s", i, len, fr_dbuff_start(&arg_dbuff)); |
275 | |
|
276 | 0 | fr_dbuff_set(&work_dbuff, &arg_dbuff); |
277 | 0 | } |
278 | | |
279 | 0 | fr_assert(len <= UINT8_MAX); |
280 | |
|
281 | 0 | FR_PROTO_TRACE("len(arg[%d]) = %d", i, len); |
282 | 0 | arg_len[i++] = len; |
283 | 0 | } |
284 | | |
285 | 0 | return fr_dbuff_set(dbuff, &work_dbuff); |
286 | 0 | } |
287 | | |
288 | | /* |
289 | | * Encode a TACACS+ field. |
290 | | */ |
291 | | static ssize_t tacacs_encode_field(fr_dbuff_t *dbuff, fr_pair_list_t *vps, fr_dict_attr_t const *da, size_t max_len) |
292 | 0 | { |
293 | 0 | fr_pair_t *vp; |
294 | 0 | fr_dbuff_t work_dbuff = FR_DBUFF(dbuff); |
295 | |
|
296 | 0 | vp = fr_pair_find_by_da(vps, NULL, da); |
297 | 0 | if (!vp || !vp->vp_length || (vp->vp_length > max_len)) return 0; |
298 | | |
299 | 0 | if (da->type == FR_TYPE_STRING) { |
300 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, vp->vp_length); |
301 | 0 | } else { |
302 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_octets, vp->vp_length); |
303 | 0 | } |
304 | | |
305 | 0 | FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), da->name); |
306 | |
|
307 | 0 | return fr_dbuff_set(dbuff, &work_dbuff); |
308 | 0 | } |
309 | | |
310 | | static ssize_t tacacs_encode_chap(fr_dbuff_t *dbuff, fr_tacacs_packet_t *packet, fr_pair_list_t *vps, fr_dict_attr_t const *da_chap, fr_dict_attr_t const *da_challenge) |
311 | 0 | { |
312 | 0 | fr_pair_t *chap, *challenge; |
313 | 0 | fr_dbuff_t work_dbuff = FR_DBUFF(dbuff); |
314 | |
|
315 | 0 | chap = fr_pair_find_by_da(vps, NULL, da_chap); |
316 | 0 | if (!chap) { |
317 | 0 | packet->authen_start.data_len = 0; |
318 | 0 | return 0; |
319 | 0 | } |
320 | | |
321 | 0 | challenge = fr_pair_find_by_da(vps, NULL, da_challenge); |
322 | 0 | if (!challenge) { |
323 | 0 | fr_strerror_printf("Packet contains %s but no %s", da_chap->name, da_challenge->name); |
324 | 0 | return -1; |
325 | 0 | } |
326 | | |
327 | 0 | if (!challenge->vp_length) { |
328 | 0 | fr_strerror_printf("%s is empty", da_challenge->name); |
329 | 0 | return -1; |
330 | 0 | } |
331 | | |
332 | 0 | if (!chap->vp_length) { |
333 | 0 | fr_strerror_printf("%s is empty", da_chap->name); |
334 | 0 | return -1; |
335 | 0 | } |
336 | | |
337 | 0 | if ((chap->vp_length + challenge->vp_length) > 255) { |
338 | 0 | fr_strerror_printf("%s and %s are longer than 255 octets", da_chap->name, da_challenge->name); |
339 | 0 | return -1; |
340 | 0 | } |
341 | | |
342 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, chap->vp_octets, 1); |
343 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, challenge->vp_octets, challenge->vp_length); |
344 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, chap->vp_octets + 1, chap->vp_length - 1); |
345 | | |
346 | 0 | packet->authen_start.data_len = chap->vp_length + challenge->vp_length; |
347 | |
|
348 | 0 | return fr_dbuff_set(dbuff, &work_dbuff); |
349 | 0 | } |
350 | | |
351 | | /* |
352 | | * Magic macros to keep things happy. |
353 | | * |
354 | | * Note that the various fields are optional. If the caller |
355 | | * doesn't specify them, then they don't get encoded. |
356 | | */ |
357 | 0 | #define ENCODE_FIELD_UINT8(_field, _da) do { \ |
358 | 0 | vp = fr_pair_find_by_da(vps, NULL, _da); \ |
359 | 0 | _field = (vp) ? vp->vp_uint8 : 0; \ |
360 | 0 | } while (0) |
361 | | |
362 | 0 | #define ENCODE_FIELD_STRING8(_field, _da) _field = tacacs_encode_field(&work_dbuff, vps, _da, 0xff) |
363 | 0 | #define ENCODE_FIELD_STRING16(_field, _da) _field = htons(tacacs_encode_field(&work_dbuff, vps, _da, 0xffff)) |
364 | | |
365 | | /** |
366 | | * Encode VPS into a raw TACACS packet. |
367 | | */ |
368 | | ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original_packet, char const *secret, size_t secret_len, |
369 | | unsigned int code, fr_pair_list_t *vps) |
370 | 0 | { |
371 | 0 | fr_pair_t *vp; |
372 | 0 | fr_tacacs_packet_t *packet; |
373 | 0 | fr_dcursor_t cursor; |
374 | 0 | fr_da_stack_t da_stack; |
375 | 0 | ssize_t len = 0; |
376 | 0 | size_t body_len, packet_len; |
377 | 0 | fr_dbuff_t work_dbuff = FR_DBUFF(dbuff); |
378 | 0 | fr_dbuff_marker_t hdr, body, hdr_io; |
379 | 0 | uint8_t version_byte = 0; |
380 | |
|
381 | 0 | fr_tacacs_packet_hdr_t const *original = (fr_tacacs_packet_hdr_t const *) original_packet; |
382 | |
|
383 | 0 | if (!vps) { |
384 | 0 | error: |
385 | 0 | fr_strerror_const("Cannot encode empty packet"); |
386 | 0 | return -1; |
387 | 0 | } |
388 | | |
389 | | /* |
390 | | * Verify space for the packet... |
391 | | */ |
392 | 0 | FR_DBUFF_REMAINING_RETURN(&work_dbuff, sizeof(fr_tacacs_packet_t)); |
393 | | |
394 | | /* |
395 | | * 3.4. The TACACS+ Packet Header |
396 | | * |
397 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
398 | | * +----------------+----------------+----------------+----------------+ |
399 | | * |major | minor | | | | |
400 | | * |version| version| type | seq_no | flags | |
401 | | * +----------------+----------------+----------------+----------------+ |
402 | | * | | |
403 | | * | session_id | |
404 | | * +----------------+----------------+----------------+----------------+ |
405 | | * | | |
406 | | * | length | |
407 | | * +----------------+----------------+----------------+----------------+ |
408 | | */ |
409 | | |
410 | | /* |
411 | | * Let's keep reference for packet header. |
412 | | */ |
413 | 0 | fr_dbuff_marker(&hdr, &work_dbuff); |
414 | | /* |
415 | | * Add marker letting us read/write header bytes without moving hdr. |
416 | | */ |
417 | 0 | fr_dbuff_marker(&hdr_io, &work_dbuff); |
418 | | |
419 | | /* |
420 | | * Handle the fields in-place. |
421 | | */ |
422 | 0 | packet = (fr_tacacs_packet_t *)fr_dbuff_start(&work_dbuff); |
423 | | |
424 | | /* |
425 | | * Find the first attribute which is parented by TACACS-Packet. |
426 | | */ |
427 | 0 | for (vp = fr_pair_dcursor_init(&cursor, vps); |
428 | 0 | vp; |
429 | 0 | vp = fr_dcursor_next(&cursor)) { |
430 | 0 | if (vp->da == attr_tacacs_packet) break; |
431 | 0 | if (vp->da->parent == attr_tacacs_packet) break; |
432 | 0 | } |
433 | | |
434 | | /* |
435 | | * No "Packet" struct to encode. We MUST have an original packet to copy the various fields |
436 | | * from. |
437 | | */ |
438 | 0 | if (!vp) { |
439 | 0 | if (!original) { |
440 | 0 | fr_strerror_printf("%s: No TACACS+ %s in the attribute list", |
441 | 0 | __FUNCTION__, attr_tacacs_packet->name); |
442 | 0 | return -1; |
443 | 0 | } |
444 | | |
445 | | /* |
446 | | * Initialize the buffer avoiding invalid values. |
447 | | */ |
448 | 0 | memset(packet, 0, sizeof(fr_tacacs_packet_t)); |
449 | | |
450 | | /* |
451 | | * Initialize the reply from the request. |
452 | | * |
453 | | * Make room and fill up the original header. We shouldn't just copy the original packet, |
454 | | * because the fields 'seq_no' and 'length' are not the same. |
455 | | */ |
456 | 0 | FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(fr_tacacs_packet_hdr_t)); |
457 | |
|
458 | 0 | } else if (vp->da == attr_tacacs_packet) { |
459 | 0 | fr_dcursor_t child_cursor; |
460 | |
|
461 | 0 | fr_proto_da_stack_build(&da_stack, attr_tacacs_packet); |
462 | 0 | FR_PROTO_STACK_PRINT(&da_stack, 0); |
463 | |
|
464 | 0 | fr_pair_dcursor_init(&child_cursor, &vp->vp_group); |
465 | | |
466 | | /* |
467 | | * Call the struct encoder to do the actual work, |
468 | | * which fills the struct fields with zero if the member VP is not used. |
469 | | */ |
470 | 0 | len = fr_struct_to_network(&work_dbuff, &da_stack, 0, &child_cursor, NULL, NULL, NULL); |
471 | 0 | if (len != sizeof(fr_tacacs_packet_hdr_t)) { |
472 | 0 | fr_strerror_printf("%s: Failed encoding %s using fr_struct_to_network()", |
473 | 0 | __FUNCTION__, attr_tacacs_packet->name); |
474 | 0 | return -1; |
475 | 0 | } |
476 | 0 | fr_dcursor_next(&cursor); |
477 | |
|
478 | 0 | } else { |
479 | 0 | fr_proto_da_stack_build(&da_stack, attr_tacacs_packet); |
480 | 0 | FR_PROTO_STACK_PRINT(&da_stack, 0); |
481 | | |
482 | | /* |
483 | | * Call the struct encoder to do the actual work, |
484 | | * which fills the struct fields with zero if the member VP is not used. |
485 | | */ |
486 | 0 | len = fr_struct_to_network(&work_dbuff, &da_stack, 0, &cursor, NULL, NULL, NULL); |
487 | 0 | if (len != sizeof(fr_tacacs_packet_hdr_t)) { |
488 | 0 | fr_strerror_printf("%s: Failed encoding %s using fr_struct_to_network()", |
489 | 0 | __FUNCTION__, attr_tacacs_packet->name); |
490 | 0 | return -1; |
491 | 0 | } |
492 | 0 | } |
493 | | |
494 | | /* |
495 | | * Ensure that we send a sane reply to a request. |
496 | | */ |
497 | 0 | if (original) { |
498 | 0 | packet->hdr.version = original->version; |
499 | 0 | packet->hdr.type = original->type; |
500 | 0 | packet->hdr.flags = original->flags; /* encrypted && single connection */ |
501 | 0 | packet->hdr.session_id = original->session_id; |
502 | | |
503 | | /* |
504 | | * The client may not set SINGLE_CONNECT flag. So if the administrator has set it in the reply, |
505 | | * we allow setting the flag. This lets the server tell the client that it supports "single |
506 | | * connection" mode. |
507 | | */ |
508 | 0 | vp = fr_pair_find_by_da_nested(vps, NULL, attr_tacacs_flags); |
509 | 0 | if (vp) packet->hdr.flags |= (vp->vp_uint8 & FR_TAC_PLUS_SINGLE_CONNECT_FLAG); |
510 | 0 | } |
511 | | |
512 | | /* |
513 | | * Starting here is a 'body' that may require encryption. |
514 | | */ |
515 | 0 | fr_dbuff_marker(&body, &work_dbuff); |
516 | | |
517 | | /* |
518 | | * Encode 8 octets of various fields not members of STRUCT |
519 | | */ |
520 | 0 | switch (packet->hdr.type) { |
521 | 0 | case FR_TAC_PLUS_AUTHEN: |
522 | | /* |
523 | | * seq_no |
524 | | * |
525 | | * This is the sequence number of the current packet for the current session. |
526 | | * The first packet in a session MUST have the sequence number 1 and each |
527 | | * subsequent packet will increment the sequence number by one. Thus clients |
528 | | * only send packets containing odd sequence numbers, and TACACS+ servers only |
529 | | * send packets containing even sequence numbers. |
530 | | */ |
531 | 0 | if (packet_is_authen_start_request(packet)) { /* Start */ |
532 | | /** |
533 | | * 4.1. The Authentication START Packet Body |
534 | | * |
535 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
536 | | * +----------------+----------------+----------------+----------------+ |
537 | | * | action | priv_lvl | authen_type | authen_service | |
538 | | * +----------------+----------------+----------------+----------------+ |
539 | | * | user_len | port_len | rem_addr_len | data_len | |
540 | | * +----------------+----------------+----------------+----------------+ |
541 | | * | user ... |
542 | | * +----------------+----------------+----------------+----------------+ |
543 | | * | port ... |
544 | | * +----------------+----------------+----------------+----------------+ |
545 | | * | rem_addr ... |
546 | | * +----------------+----------------+----------------+----------------+ |
547 | | * | data... |
548 | | * +----------------+----------------+----------------+----------------+ |
549 | | */ |
550 | | |
551 | | /* |
552 | | * Make room for such body request. |
553 | | */ |
554 | 0 | FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(packet->authen_start)); |
555 | | |
556 | | /* |
557 | | * Encode 4 octets of various flags. |
558 | | */ |
559 | 0 | ENCODE_FIELD_UINT8(packet->authen_start.action, attr_tacacs_action); |
560 | 0 | ENCODE_FIELD_UINT8(packet->authen_start.priv_lvl, attr_tacacs_privilege_level); |
561 | 0 | ENCODE_FIELD_UINT8(packet->authen_start.authen_type, attr_tacacs_authentication_type); |
562 | 0 | ENCODE_FIELD_UINT8(packet->authen_start.authen_service, attr_tacacs_authentication_service); |
563 | | |
564 | | /* |
565 | | * Encode 4 mandatory fields. |
566 | | */ |
567 | 0 | ENCODE_FIELD_STRING8(packet->authen_start.user_len, attr_tacacs_user_name); |
568 | 0 | ENCODE_FIELD_STRING8(packet->authen_start.port_len, attr_tacacs_client_port); |
569 | 0 | ENCODE_FIELD_STRING8(packet->authen_start.rem_addr_len, attr_tacacs_remote_address); |
570 | | |
571 | | /* |
572 | | * No explicit "Data" attribute, try to automatically determine what to do. |
573 | | */ |
574 | 0 | if (fr_pair_find_by_da_nested(vps, NULL, attr_tacacs_data)) { |
575 | 0 | ENCODE_FIELD_STRING8(packet->authen_start.data_len, attr_tacacs_data); |
576 | |
|
577 | 0 | } else switch (packet->authen_start.authen_type) { |
578 | 0 | default: |
579 | 0 | break; |
580 | | |
581 | 0 | case FR_AUTHENTICATION_TYPE_VALUE_PAP: |
582 | 0 | ENCODE_FIELD_STRING8(packet->authen_start.data_len, attr_tacacs_user_password); |
583 | 0 | break; |
584 | | |
585 | 0 | case FR_AUTHENTICATION_TYPE_VALUE_CHAP: |
586 | 0 | if (tacacs_encode_chap(&work_dbuff, packet, vps, attr_tacacs_chap_password, attr_tacacs_chap_challenge) < 0) return -1; |
587 | 0 | break; |
588 | | |
589 | 0 | case FR_AUTHENTICATION_TYPE_VALUE_MSCHAP: { |
590 | 0 | int rcode; |
591 | |
|
592 | 0 | rcode = tacacs_encode_chap(&work_dbuff, packet, vps, attr_tacacs_mschap_response, attr_tacacs_mschap_challenge); |
593 | 0 | if (rcode < 0) return rcode; |
594 | | |
595 | 0 | if (rcode > 0) break; |
596 | | |
597 | 0 | if (tacacs_encode_chap(&work_dbuff, packet, vps, attr_tacacs_mschap2_response, attr_tacacs_mschap_challenge) < 0) return -1; |
598 | 0 | } |
599 | 0 | break; |
600 | 0 | } |
601 | | |
602 | 0 | goto check_request; |
603 | |
|
604 | 0 | } else if (packet_is_authen_continue(packet)) { |
605 | | /* |
606 | | * 4.3. The Authentication CONTINUE Packet Body |
607 | | * |
608 | | * This packet is sent from the client to the server following the receipt of |
609 | | * a REPLY packet. |
610 | | * |
611 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
612 | | * +----------------+----------------+----------------+----------------+ |
613 | | * | user_msg len | data_len | |
614 | | * +----------------+----------------+----------------+----------------+ |
615 | | * | flags | user_msg ... |
616 | | * +----------------+----------------+----------------+----------------+ |
617 | | * | data ... |
618 | | * +----------------+ |
619 | | */ |
620 | | |
621 | | /* |
622 | | * Make room for such body request. |
623 | | */ |
624 | 0 | FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(packet->authen_cont)); |
625 | | |
626 | | /* |
627 | | * Encode 2 mandatory fields. |
628 | | */ |
629 | 0 | ENCODE_FIELD_STRING16(packet->authen_cont.user_msg_len, attr_tacacs_user_message); |
630 | 0 | ENCODE_FIELD_STRING16(packet->authen_cont.data_len, attr_tacacs_data); |
631 | | |
632 | | /* |
633 | | * Look at the abort flag after encoding the fields. |
634 | | */ |
635 | 0 | ENCODE_FIELD_UINT8(packet->authen_cont.flags, attr_tacacs_authentication_continue_flags); |
636 | |
|
637 | 0 | goto check_request; |
638 | |
|
639 | 0 | } else if (packet_is_authen_reply(packet)) { |
640 | | /* |
641 | | * 4.2. The Authentication REPLY Packet Body |
642 | | * |
643 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
644 | | * +----------------+----------------+----------------+----------------+ |
645 | | * | status | flags | server_msg_len | |
646 | | * +----------------+----------------+----------------+----------------+ |
647 | | * | data_len | server_msg ... |
648 | | * +----------------+----------------+----------------+----------------+ |
649 | | * | data ... |
650 | | * +----------------+----------------+ |
651 | | */ |
652 | | |
653 | | /* |
654 | | * Make room for such body request. |
655 | | */ |
656 | 0 | FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(packet->authen_reply)); |
657 | | |
658 | 0 | ENCODE_FIELD_UINT8(packet->authen_reply.status, attr_tacacs_authentication_status); |
659 | 0 | ENCODE_FIELD_UINT8(packet->authen_reply.flags, attr_tacacs_authentication_flags); |
660 | | |
661 | | /* |
662 | | * Encode 2 mandatory fields. |
663 | | */ |
664 | 0 | ENCODE_FIELD_STRING16(packet->authen_reply.server_msg_len, attr_tacacs_server_message); |
665 | 0 | ENCODE_FIELD_STRING16(packet->authen_reply.data_len, attr_tacacs_data); |
666 | |
|
667 | 0 | goto check_reply; |
668 | 0 | } |
669 | | |
670 | 0 | fr_strerror_const("encode: Unknown authentication packet type"); |
671 | 0 | return -1; |
672 | | |
673 | 0 | case FR_TAC_PLUS_AUTHOR: |
674 | 0 | if (packet_is_author_request(packet)) { |
675 | | /* |
676 | | * 5.1. The Authorization REQUEST Packet Body |
677 | | * |
678 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
679 | | * +----------------+----------------+----------------+----------------+ |
680 | | * | authen_method | priv_lvl | authen_type | authen_service | |
681 | | * +----------------+----------------+----------------+----------------+ |
682 | | * | user_len | port_len | rem_addr_len | arg_cnt | |
683 | | * +----------------+----------------+----------------+----------------+ |
684 | | * | arg_1_len | arg_2_len | ... | arg_N_len | |
685 | | * +----------------+----------------+----------------+----------------+ |
686 | | * | user ... |
687 | | * +----------------+----------------+----------------+----------------+ |
688 | | * | port ... |
689 | | * +----------------+----------------+----------------+----------------+ |
690 | | * | rem_addr ... |
691 | | * +----------------+----------------+----------------+----------------+ |
692 | | * | arg_1 ... |
693 | | * +----------------+----------------+----------------+----------------+ |
694 | | * | arg_2 ... |
695 | | * +----------------+----------------+----------------+----------------+ |
696 | | * | ... |
697 | | * +----------------+----------------+----------------+----------------+ |
698 | | * | arg_N ... |
699 | | * +----------------+----------------+----------------+----------------+ |
700 | | */ |
701 | | |
702 | | /* |
703 | | * Make room for such body request. |
704 | | */ |
705 | 0 | FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(packet->author_req)); |
706 | | |
707 | | /* |
708 | | * Encode 4 octets of various flags. |
709 | | */ |
710 | 0 | ENCODE_FIELD_UINT8(packet->author_req.authen_method, attr_tacacs_authentication_method); |
711 | 0 | ENCODE_FIELD_UINT8(packet->author_req.priv_lvl, attr_tacacs_privilege_level); |
712 | 0 | ENCODE_FIELD_UINT8(packet->author_req.authen_type, attr_tacacs_authentication_type); |
713 | 0 | ENCODE_FIELD_UINT8(packet->author_req.authen_service, attr_tacacs_authentication_service); |
714 | | |
715 | | /* |
716 | | * Encode 'arg_N' arguments (horrible format) |
717 | | */ |
718 | 0 | packet->author_req.arg_cnt = tacacs_encode_body_arg_cnt(vps, attr_tacacs_argument_list); |
719 | 0 | if (packet->author_req.arg_cnt) FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, packet->author_req.arg_cnt); |
720 | | |
721 | | /* |
722 | | * Encode 3 mandatory fields. |
723 | | */ |
724 | 0 | ENCODE_FIELD_STRING8(packet->author_req.user_len, attr_tacacs_user_name); |
725 | 0 | ENCODE_FIELD_STRING8(packet->author_req.port_len, attr_tacacs_client_port); |
726 | 0 | ENCODE_FIELD_STRING8(packet->author_req.rem_addr_len, attr_tacacs_remote_address); |
727 | | |
728 | | /* |
729 | | * Append 'args_body' to the end of buffer |
730 | | */ |
731 | 0 | if (packet->author_req.arg_cnt > 0) { |
732 | 0 | if (tacacs_encode_body_arg_n(&work_dbuff, packet->author_req.arg_cnt, &packet->author_req.arg_len[0], vps, attr_tacacs_argument_list) < 0) goto error; |
733 | 0 | } |
734 | | |
735 | 0 | goto check_request; |
736 | 0 | } else if (packet_is_author_reply(packet)) { |
737 | | /* |
738 | | * 5.2. The Authorization RESPONSE Packet Body |
739 | | * |
740 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
741 | | * +----------------+----------------+----------------+----------------+ |
742 | | * | status | arg_cnt | server_msg len | |
743 | | * +----------------+----------------+----------------+----------------+ |
744 | | * + data_len | arg_1_len | arg_2_len | |
745 | | * +----------------+----------------+----------------+----------------+ |
746 | | * | ... | arg_N_len | server_msg ... |
747 | | * +----------------+----------------+----------------+----------------+ |
748 | | * | data ... |
749 | | * +----------------+----------------+----------------+----------------+ |
750 | | * | arg_1 ... |
751 | | * +----------------+----------------+----------------+----------------+ |
752 | | * | arg_2 ... |
753 | | * +----------------+----------------+----------------+----------------+ |
754 | | * | ... |
755 | | * +----------------+----------------+----------------+----------------+ |
756 | | * | arg_N ... |
757 | | * +----------------+----------------+----------------+----------------+ |
758 | | */ |
759 | | |
760 | | /* |
761 | | * Make room for such body request. |
762 | | */ |
763 | 0 | FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(packet->author_reply)); |
764 | | |
765 | | /* |
766 | | * Encode 1 mandatory field. |
767 | | */ |
768 | 0 | ENCODE_FIELD_UINT8(packet->author_reply.status, attr_tacacs_authorization_status); |
769 | | |
770 | | /* |
771 | | * Encode 'arg_N' arguments (horrible format) |
772 | | * |
773 | | * For ERRORs, we don't encode arguments. |
774 | | * |
775 | | * 5.2 |
776 | | * |
777 | | * A status of TAC_PLUS_AUTHOR_STATUS_ERROR indicates an error occurred |
778 | | * on the server. For the differences between ERROR and FAIL, refer to |
779 | | * section Session Completion (Section 3.4) . None of the arg values |
780 | | * have any relevance if an ERROR is set, and must be ignored. |
781 | | * |
782 | | * When the status equals TAC_PLUS_AUTHOR_STATUS_FOLLOW, then the |
783 | | * arg_cnt MUST be 0. |
784 | | */ |
785 | 0 | if (!((packet->author_reply.status == FR_AUTHORIZATION_STATUS_VALUE_ERROR) || |
786 | 0 | (packet->author_reply.status == FR_AUTHORIZATION_STATUS_VALUE_FOLLOW))) { |
787 | 0 | packet->author_reply.arg_cnt = tacacs_encode_body_arg_cnt(vps, attr_tacacs_argument_list); |
788 | 0 | if (packet->author_reply.arg_cnt) FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, packet->author_reply.arg_cnt); |
789 | 0 | } else { |
790 | 0 | packet->author_reply.arg_cnt = 0; |
791 | 0 | } |
792 | | |
793 | | /* |
794 | | * Encode 2 mandatory fields. |
795 | | */ |
796 | 0 | ENCODE_FIELD_STRING16(packet->author_reply.server_msg_len, attr_tacacs_server_message); |
797 | 0 | ENCODE_FIELD_STRING16(packet->author_reply.data_len, attr_tacacs_data); |
798 | | |
799 | | /* |
800 | | * Append 'args_body' to the end of buffer |
801 | | */ |
802 | 0 | if (packet->author_reply.arg_cnt > 0) { |
803 | 0 | if (tacacs_encode_body_arg_n(&work_dbuff, packet->author_reply.arg_cnt, &packet->author_reply.arg_len[0], vps, attr_tacacs_argument_list) < 0) goto error; |
804 | 0 | } |
805 | | |
806 | 0 | goto check_reply; |
807 | |
|
808 | 0 | } |
809 | | |
810 | 0 | fr_strerror_const("encode: Unknown authorization packet type"); |
811 | 0 | return -1; |
812 | | |
813 | 0 | case FR_TAC_PLUS_ACCT: |
814 | 0 | if (packet_is_acct_request(packet)) { |
815 | | /** |
816 | | * 6.1. The Account REQUEST Packet Body |
817 | | * |
818 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
819 | | * +----------------+----------------+----------------+----------------+ |
820 | | * | flags | authen_method | priv_lvl | authen_type | |
821 | | * +----------------+----------------+----------------+----------------+ |
822 | | * | authen_service | user_len | port_len | rem_addr_len | |
823 | | * +----------------+----------------+----------------+----------------+ |
824 | | * | arg_cnt | arg_1_len | arg_2_len | ... | |
825 | | * +----------------+----------------+----------------+----------------+ |
826 | | * | arg_N_len | user ... |
827 | | * +----------------+----------------+----------------+----------------+ |
828 | | * | port ... |
829 | | * +----------------+----------------+----------------+----------------+ |
830 | | * | rem_addr ... |
831 | | * +----------------+----------------+----------------+----------------+ |
832 | | * | arg_1 ... |
833 | | * +----------------+----------------+----------------+----------------+ |
834 | | * | arg_2 ... |
835 | | * +----------------+----------------+----------------+----------------+ |
836 | | * | ... |
837 | | * +----------------+----------------+----------------+----------------+ |
838 | | * | arg_N ... |
839 | | * +----------------+----------------+----------------+----------------+ |
840 | | */ |
841 | | |
842 | | /* |
843 | | * Make room for such body request. |
844 | | */ |
845 | 0 | FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(packet->acct_req)); |
846 | | |
847 | | /* |
848 | | * Encode 5 octets of various flags. |
849 | | */ |
850 | 0 | ENCODE_FIELD_UINT8(packet->acct_req.flags, attr_tacacs_accounting_flags); |
851 | 0 | ENCODE_FIELD_UINT8(packet->acct_req.authen_method, attr_tacacs_authentication_method); |
852 | 0 | ENCODE_FIELD_UINT8(packet->acct_req.priv_lvl, attr_tacacs_privilege_level); |
853 | 0 | ENCODE_FIELD_UINT8(packet->acct_req.authen_type, attr_tacacs_authentication_type); |
854 | 0 | ENCODE_FIELD_UINT8(packet->acct_req.authen_service, attr_tacacs_authentication_service); |
855 | | |
856 | | /* |
857 | | * Encode 'arg_N' arguments (horrible format) |
858 | | */ |
859 | 0 | packet->acct_req.arg_cnt = tacacs_encode_body_arg_cnt(vps, attr_tacacs_argument_list); |
860 | 0 | if (packet->acct_req.arg_cnt) FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, packet->acct_req.arg_cnt); |
861 | | |
862 | | /* |
863 | | * Encode 3 mandatory fields. |
864 | | */ |
865 | 0 | ENCODE_FIELD_STRING8(packet->acct_req.user_len, attr_tacacs_user_name); |
866 | 0 | ENCODE_FIELD_STRING8(packet->acct_req.port_len, attr_tacacs_client_port); |
867 | 0 | ENCODE_FIELD_STRING8(packet->acct_req.rem_addr_len, attr_tacacs_remote_address); |
868 | | |
869 | | /* |
870 | | * Append 'args_body' to the end of buffer |
871 | | */ |
872 | 0 | if (packet->acct_req.arg_cnt > 0) { |
873 | 0 | if (tacacs_encode_body_arg_n(&work_dbuff, packet->acct_req.arg_cnt, &packet->acct_req.arg_len[0], vps, attr_tacacs_argument_list) < 0) goto error; |
874 | 0 | } |
875 | | |
876 | 0 | check_request: |
877 | | /* |
878 | | * Just to avoid malformed packet. |
879 | | */ |
880 | 0 | fr_dbuff_set(&hdr_io, &hdr); |
881 | 0 | fr_dbuff_out(&version_byte, &hdr_io); |
882 | 0 | if (!version_byte) { |
883 | 0 | version_byte = 0xc1; /* version 12.1 */ |
884 | 0 | fr_dbuff_set(&hdr_io, &hdr); |
885 | 0 | FR_DBUFF_IN_RETURN(&hdr_io, version_byte); |
886 | 0 | } |
887 | | /* |
888 | | * If the caller didn't set a session ID, use a random one. |
889 | | */ |
890 | 0 | if (!fr_pair_find_by_da_nested(vps, NULL, attr_tacacs_session_id)) { |
891 | 0 | packet->hdr.session_id = fr_rand(); |
892 | 0 | } |
893 | | |
894 | | /* |
895 | | * Requests have odd sequence numbers. |
896 | | */ |
897 | 0 | packet->hdr.seq_no |= 0x01; |
898 | 0 | break; |
899 | |
|
900 | 0 | } else if (packet_is_acct_reply(packet)) { |
901 | | /** |
902 | | * 6.2. The Accounting REPLY Packet Body |
903 | | * |
904 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
905 | | * +----------------+----------------+----------------+----------------+ |
906 | | * | server_msg len | data_len | |
907 | | * +----------------+----------------+----------------+----------------+ |
908 | | * | status | server_msg ... |
909 | | * +----------------+----------------+----------------+----------------+ |
910 | | * | data ... |
911 | | * +----------------+ |
912 | | */ |
913 | | |
914 | | /* |
915 | | * Make room for such body request. |
916 | | */ |
917 | 0 | FR_DBUFF_ADVANCE_RETURN(&work_dbuff, sizeof(packet->acct_reply)); |
918 | | |
919 | | /* |
920 | | * Encode 2 mandatory fields. |
921 | | */ |
922 | 0 | ENCODE_FIELD_STRING16(packet->acct_reply.server_msg_len, attr_tacacs_server_message); |
923 | 0 | ENCODE_FIELD_STRING16(packet->acct_reply.data_len, attr_tacacs_data); |
924 | | |
925 | | /* |
926 | | * And also, the status field. |
927 | | */ |
928 | 0 | ENCODE_FIELD_UINT8(packet->acct_reply.status, attr_tacacs_accounting_status); |
929 | |
|
930 | 0 | check_reply: |
931 | | /* |
932 | | * fr_struct_to_network() fills the struct fields with 0 |
933 | | * if there is no matching VP. In the interest of making |
934 | | * things easier for the user, we don't require them to |
935 | | * copy all of the fields from the request to the reply. |
936 | | * |
937 | | * Instead, we copy the fields manually, and ensure that |
938 | | * they have the correct values. |
939 | | */ |
940 | 0 | if (original) { |
941 | 0 | fr_dbuff_set(&hdr_io, &hdr); |
942 | 0 | fr_dbuff_out(&version_byte, &hdr_io); |
943 | 0 | if (!version_byte) { |
944 | 0 | packet->hdr.version = original->version; |
945 | 0 | } |
946 | |
|
947 | 0 | if (!packet->hdr.seq_no) { |
948 | 0 | packet->hdr.seq_no = original->seq_no + 1; /* uint8_t */ |
949 | 0 | } |
950 | |
|
951 | 0 | if (!packet->hdr.session_id) { |
952 | 0 | packet->hdr.session_id = original->session_id; |
953 | 0 | } |
954 | 0 | } |
955 | | |
956 | | /* |
957 | | * Replies have even sequence numbers. |
958 | | */ |
959 | 0 | packet->hdr.seq_no &= 0xfe; |
960 | 0 | break; |
961 | 0 | } |
962 | | |
963 | 0 | fr_strerror_const("encode: Unknown accounting packet type"); |
964 | 0 | return -1; |
965 | | |
966 | 0 | default: |
967 | 0 | fr_strerror_printf("encode: unknown packet type %d", packet->hdr.type); |
968 | 0 | return -1; |
969 | 0 | } |
970 | | |
971 | | /* |
972 | | * Force the correct header type, and randomly-placed |
973 | | * status fields. But only if there's no code field. |
974 | | * Only the unit tests pass a zero code field, as that's |
975 | | * normally invalid. The unit tests ensure that all of |
976 | | * the VPs are passed to encode a packet, and they all |
977 | | * must be correct |
978 | | */ |
979 | 0 | if (code && (fr_tacacs_code_to_packet(packet, code) < 0)) return -1; |
980 | | |
981 | | /* |
982 | | * The packet length we store in the header doesn't |
983 | | * include the size of the header. But we tell the |
984 | | * caller about the total length of the packet. |
985 | | */ |
986 | 0 | packet_len = fr_dbuff_used(&work_dbuff); |
987 | 0 | fr_assert(packet_len >= sizeof(fr_tacacs_packet_hdr_t)); |
988 | |
|
989 | 0 | body_len = (packet_len - sizeof(fr_tacacs_packet_hdr_t)); |
990 | 0 | fr_assert(packet_len < FR_MAX_PACKET_SIZE); |
991 | 0 | packet->hdr.length = htonl(body_len); |
992 | | |
993 | | /* |
994 | | * If the original packet is encrypted, then the reply |
995 | | * MUST be encrypted too. |
996 | | * |
997 | | * On the other hand, if the request is unencrypted, |
998 | | * we're OK with sending an encrypted reply. Because, |
999 | | * whatever. |
1000 | | */ |
1001 | 0 | if (original && |
1002 | 0 | ((original->flags & FR_TAC_PLUS_UNENCRYPTED_FLAG) == 0)) { |
1003 | 0 | packet->hdr.flags &= ~FR_TAC_PLUS_UNENCRYPTED_FLAG; |
1004 | 0 | } |
1005 | |
|
1006 | 0 | #ifndef NDEBUG |
1007 | 0 | if (fr_debug_lvl >= L_DBG_LVL_4) { |
1008 | 0 | uint8_t flags = packet->hdr.flags; |
1009 | |
|
1010 | 0 | packet->hdr.flags |= FR_TAC_PLUS_UNENCRYPTED_FLAG; |
1011 | 0 | fr_tacacs_packet_log_hex(&default_log, packet, packet_len); |
1012 | 0 | packet->hdr.flags = flags; |
1013 | 0 | } |
1014 | 0 | #endif |
1015 | | |
1016 | | /* |
1017 | | * 3.6. Encryption |
1018 | | * |
1019 | | * Packets are encrypted if the unencrypted flag is clear. |
1020 | | */ |
1021 | 0 | if (secret) { |
1022 | 0 | if (!secret_len) { |
1023 | 0 | fr_strerror_const("Packet should be decrypted, but the secret has zero length"); |
1024 | 0 | return -1; |
1025 | 0 | } |
1026 | | |
1027 | 0 | FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), packet_len, "fr_tacacs_packet_t (unencrypted)"); |
1028 | |
|
1029 | 0 | if (fr_tacacs_body_xor(packet, fr_dbuff_current(&body), body_len, secret, secret_len) != 0) return -1; |
1030 | | |
1031 | 0 | packet->hdr.flags &= ~FR_TAC_PLUS_UNENCRYPTED_FLAG; |
1032 | 0 | } else { |
1033 | | /* |
1034 | | * Packets which have no secret cannot be encrypted. |
1035 | | */ |
1036 | 0 | packet->hdr.flags |= FR_TAC_PLUS_UNENCRYPTED_FLAG; |
1037 | |
|
1038 | 0 | } |
1039 | | |
1040 | 0 | FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), packet_len, "fr_tacacs_packet_t (encoded)"); |
1041 | |
|
1042 | 0 | return fr_dbuff_set(dbuff, &work_dbuff); |
1043 | 0 | } |
1044 | | |
1045 | | /* |
1046 | | * Test points for protocol encode |
1047 | | */ |
1048 | | static ssize_t fr_tacacs_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vps, |
1049 | | uint8_t *data, size_t data_len, void *proto_ctx) |
1050 | 0 | { |
1051 | 0 | fr_tacacs_ctx_t *test_ctx = talloc_get_type_abort(proto_ctx, fr_tacacs_ctx_t); |
1052 | |
|
1053 | 0 | return fr_tacacs_encode(&FR_DBUFF_TMP(data, data_len), NULL, test_ctx->secret, |
1054 | 0 | test_ctx->secret ? talloc_strlen(test_ctx->secret) : 0, 0, vps); |
1055 | 0 | } |
1056 | | |
1057 | | static int _encode_test_ctx(fr_tacacs_ctx_t *test_ctx) |
1058 | 5.74k | { |
1059 | 5.74k | talloc_const_free(test_ctx->secret); |
1060 | | |
1061 | 5.74k | fr_tacacs_global_free(); |
1062 | | |
1063 | 5.74k | return 0; |
1064 | 5.74k | } |
1065 | | |
1066 | | static int encode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict, |
1067 | | UNUSED fr_dict_attr_t const *root_da) |
1068 | 5.74k | { |
1069 | 5.74k | fr_tacacs_ctx_t *test_ctx; |
1070 | | |
1071 | 5.74k | if (fr_tacacs_global_init() < 0) return -1; |
1072 | | |
1073 | 5.74k | test_ctx = talloc_zero(ctx, fr_tacacs_ctx_t); |
1074 | 5.74k | if (!test_ctx) return -1; |
1075 | | |
1076 | 5.74k | test_ctx->root = fr_dict_root(dict_tacacs); |
1077 | 5.74k | talloc_set_destructor(test_ctx, _encode_test_ctx); |
1078 | | |
1079 | 5.74k | *out = test_ctx; |
1080 | | |
1081 | 5.74k | return 0; |
1082 | 5.74k | } |
1083 | | |
1084 | | /* |
1085 | | * Test points |
1086 | | */ |
1087 | | extern fr_test_point_proto_encode_t tacacs_tp_encode_proto; |
1088 | | fr_test_point_proto_encode_t tacacs_tp_encode_proto = { |
1089 | | .test_ctx = encode_test_ctx, |
1090 | | .func = fr_tacacs_encode_proto |
1091 | | }; |