4cfb9bdd0ede81d62f19467422b7c92d5d91fc0e
[strongswan.git] / src / libimcv / pa_tnc / pa_tnc_msg.c
1 /*
2 * Copyright (C) 2011-2014 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "imcv.h"
17 #include "pa_tnc_msg.h"
18 #include "ietf/ietf_attr_pa_tnc_error.h"
19
20 #include <bio/bio_writer.h>
21 #include <bio/bio_reader.h>
22 #include <collections/linked_list.h>
23 #include <pen/pen.h>
24 #include <utils/debug.h>
25
26 typedef struct private_pa_tnc_msg_t private_pa_tnc_msg_t;
27
28 /**
29 * PA-TNC message header
30 *
31 * 1 2 3
32 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
33 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
34 * | Version | Reserved |
35 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36 * | Message Identifier |
37 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38 */
39
40 #define PA_TNC_RESERVED 0x000000
41
42 /**
43 * PA-TNC attribute
44 *
45 * 1 2 3
46 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
47 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48 * | Flags | PA-TNC Attribute Vendor ID |
49 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
50 * | PA-TNC Attribute Type |
51 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
52 * | PA-TNC Attribute Length |
53 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
54 * | Attribute Value (Variable Length) |
55 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
56 */
57
58 #define PA_TNC_ATTR_FLAG_NONE 0x00
59 #define PA_TNC_ATTR_FLAG_NOSKIP (1<<7)
60 #define PA_TNC_ATTR_INFO_SIZE 8
61
62 /**
63 * Private data of a pa_tnc_msg_t object.
64 *
65 */
66 struct private_pa_tnc_msg_t {
67
68 /**
69 * Public pa_tnc_msg_t interface.
70 */
71 pa_tnc_msg_t public;
72
73 /**
74 * List of PA-TNC attributes
75 */
76 linked_list_t *attributes;
77
78 /**
79 * linked list of PA-TNC error messages
80 */
81 linked_list_t *errors;
82
83 /**
84 * Message identifier
85 */
86 uint32_t identifier;
87
88 /**
89 * Current PA-TNC Message size
90 */
91 size_t msg_len;
92
93 /**
94 * Maximum PA-TNC Message size
95 */
96 size_t max_msg_len;
97
98 /**
99 * Encoded message
100 */
101 chunk_t encoding;
102 };
103
104 METHOD(pa_tnc_msg_t, get_encoding, chunk_t,
105 private_pa_tnc_msg_t *this)
106 {
107 return this->encoding;
108 }
109
110 METHOD(pa_tnc_msg_t, add_attribute, bool,
111 private_pa_tnc_msg_t *this, pa_tnc_attr_t *attr)
112 {
113 chunk_t attr_value;
114 size_t attr_len;
115
116 attr->build(attr);
117 attr_value = attr->get_value(attr);
118 attr_len = PA_TNC_ATTR_HEADER_SIZE + attr_value.len;
119
120 if (this->max_msg_len && this->msg_len + attr_len > this->max_msg_len)
121 {
122 /* attribute just does not fit into this message */
123 return FALSE;
124 }
125 this->msg_len += attr_len;
126
127 this->attributes->insert_last(this->attributes, attr);
128 return TRUE;
129 }
130
131 METHOD(pa_tnc_msg_t, build, bool,
132 private_pa_tnc_msg_t *this)
133 {
134 bio_writer_t *writer;
135 enumerator_t *enumerator;
136 pa_tnc_attr_t *attr;
137 enum_name_t *pa_attr_names;
138 pen_type_t type;
139 uint8_t flags;
140 chunk_t value;
141 nonce_gen_t *ng;
142
143 /* generate a nonce as a message identifier */
144 ng = lib->crypto->create_nonce_gen(lib->crypto);
145 if (!ng || !ng->get_nonce(ng, 4, (uint8_t*)&this->identifier))
146 {
147 DBG1(DBG_TNC, "failed to generate random PA-TNC message identifier");
148 DESTROY_IF(ng);
149 return FALSE;
150 }
151 ng->destroy(ng);
152 DBG1(DBG_TNC, "creating PA-TNC message with ID 0x%08x", this->identifier);
153
154 /* build message header */
155 writer = bio_writer_create(this->msg_len);
156 writer->write_uint8 (writer, PA_TNC_VERSION);
157 writer->write_uint24(writer, PA_TNC_RESERVED);
158 writer->write_uint32(writer, this->identifier);
159
160 /* append encoded value of PA-TNC attributes */
161 enumerator = this->attributes->create_enumerator(this->attributes);
162 while (enumerator->enumerate(enumerator, &attr))
163 {
164 type = attr->get_type(attr);
165 value = attr->get_value(attr);
166 flags = attr->get_noskip_flag(attr) ? PA_TNC_ATTR_FLAG_NOSKIP :
167 PA_TNC_ATTR_FLAG_NONE;
168
169 pa_attr_names = imcv_pa_tnc_attributes->get_names(imcv_pa_tnc_attributes,
170 type.vendor_id);
171 if (pa_attr_names)
172 {
173 DBG2(DBG_TNC, "creating PA-TNC attribute type '%N/%N' "
174 "0x%06x/0x%08x", pen_names, type.vendor_id,
175 pa_attr_names, type.type, type.vendor_id, type.type);
176 }
177 else
178 {
179 DBG2(DBG_TNC, "creating PA-TNC attribute type '%N' "
180 "0x%06x/0x%08x", pen_names, type.vendor_id,
181 type.vendor_id, type.type);
182 }
183 DBG3(DBG_TNC, "%B", &value);
184
185 writer->write_uint8 (writer, flags);
186 writer->write_uint24(writer, type.vendor_id);
187 writer->write_uint32(writer, type.type);
188 writer->write_uint32(writer, PA_TNC_ATTR_HEADER_SIZE + value.len);
189 writer->write_data (writer, value);
190 }
191 enumerator->destroy(enumerator);
192
193 free(this->encoding.ptr);
194 this->encoding = writer->extract_buf(writer);
195 writer->destroy(writer);
196
197 return TRUE;
198 }
199
200 METHOD(pa_tnc_msg_t, process, status_t,
201 private_pa_tnc_msg_t *this)
202 {
203 bio_reader_t *reader;
204 pa_tnc_attr_t *error;
205 uint8_t version;
206 uint32_t reserved, offset, attr_offset;
207 pen_type_t error_code = { PEN_IETF, PA_ERROR_INVALID_PARAMETER };
208
209 /* process message header */
210 if (this->encoding.len < PA_TNC_HEADER_SIZE)
211 {
212 DBG1(DBG_TNC, "%u bytes insufficient to parse PA-TNC message header",
213 this->encoding.len);
214 return FAILED;
215 }
216 reader = bio_reader_create(this->encoding);
217 reader->read_uint8 (reader, &version);
218 reader->read_uint24(reader, &reserved);
219 reader->read_uint32(reader, &this->identifier);
220 DBG1(DBG_TNC, "processing PA-TNC message with ID 0x%08x", this->identifier);
221
222 if (version != PA_TNC_VERSION)
223 {
224 DBG1(DBG_TNC, "PA-TNC version %u not supported", version);
225 error_code = pen_type_create(PEN_IETF, PA_ERROR_VERSION_NOT_SUPPORTED);
226 error = ietf_attr_pa_tnc_error_create(error_code, this->encoding);
227 goto err;
228 }
229
230 /* offset of the first PA-TNC attribute in the PA-TNC message */
231 offset = PA_TNC_HEADER_SIZE;
232
233 /* pre-process PA-TNC attributes */
234 while (reader->remaining(reader) >= PA_TNC_ATTR_HEADER_SIZE)
235 {
236 pen_t vendor_id;
237 uint8_t flags;
238 uint32_t type, length;
239 chunk_t value;
240 pa_tnc_attr_t *attr;
241 enum_name_t *pa_attr_names;
242 ietf_attr_pa_tnc_error_t *error_attr;
243 pen_type_t unsupported_type;
244
245 reader->read_uint8 (reader, &flags);
246 reader->read_uint24(reader, &vendor_id);
247 reader->read_uint32(reader, &type);
248 reader->read_uint32(reader, &length);
249
250 pa_attr_names = imcv_pa_tnc_attributes->get_names(imcv_pa_tnc_attributes,
251 vendor_id);
252 if (pa_attr_names)
253 {
254 DBG2(DBG_TNC, "processing PA-TNC attribute type '%N/%N' "
255 "0x%06x/0x%08x", pen_names, vendor_id,
256 pa_attr_names, type, vendor_id, type);
257 }
258 else
259 {
260 DBG2(DBG_TNC, "processing PA-TNC attribute type '%N' "
261 "0x%06x/0x%08x", pen_names, vendor_id,
262 vendor_id, type);
263 }
264
265 if (length < PA_TNC_ATTR_HEADER_SIZE)
266 {
267 DBG1(DBG_TNC, "%u bytes too small for PA-TNC attribute length",
268 length);
269 error = ietf_attr_pa_tnc_error_create_with_offset(error_code,
270 this->encoding, offset + PA_TNC_ATTR_INFO_SIZE);
271 goto err;
272 }
273
274 if (!reader->read_data(reader, length - PA_TNC_ATTR_HEADER_SIZE, &value))
275 {
276 DBG1(DBG_TNC, "insufficient bytes for PA-TNC attribute value");
277 error = ietf_attr_pa_tnc_error_create_with_offset(error_code,
278 this->encoding, offset + PA_TNC_ATTR_INFO_SIZE);
279 goto err;
280 }
281 DBG3(DBG_TNC, "%B", &value);
282
283 if (vendor_id == PEN_RESERVED)
284 {
285 error = ietf_attr_pa_tnc_error_create_with_offset(error_code,
286 this->encoding, offset + 1);
287 goto err;
288 }
289 if (type == IETF_ATTR_RESERVED)
290 {
291 error = ietf_attr_pa_tnc_error_create_with_offset(error_code,
292 this->encoding, offset + 4);
293 goto err;
294 }
295 attr = imcv_pa_tnc_attributes->create(imcv_pa_tnc_attributes,
296 vendor_id, type, value);
297 if (!attr)
298 {
299 if (!(flags & PA_TNC_ATTR_FLAG_NOSKIP))
300 {
301 DBG1(DBG_TNC, "skipping unsupported PA-TNC attribute");
302 offset += length;
303 continue;
304 }
305
306 DBG1(DBG_TNC, "unsupported PA-TNC attribute with NOSKIP flag");
307 unsupported_type = pen_type_create(vendor_id, type);
308 error_code = pen_type_create(PEN_IETF,
309 PA_ERROR_ATTR_TYPE_NOT_SUPPORTED);
310 error = ietf_attr_pa_tnc_error_create(error_code, this->encoding);
311 error_attr = (ietf_attr_pa_tnc_error_t*)error;
312 error_attr->set_unsupported_attr(error_attr, flags, unsupported_type);
313 goto err;
314 }
315
316 if (attr->process(attr, &attr_offset) != SUCCESS)
317 {
318 attr->destroy(attr);
319 if (vendor_id == PEN_IETF && type == IETF_ATTR_PA_TNC_ERROR)
320 {
321 /* error while processing a PA-TNC error attribute - abort */
322 reader->destroy(reader);
323 return FAILED;
324 }
325 error_code = pen_type_create(PEN_IETF,
326 PA_ERROR_INVALID_PARAMETER);
327 error = ietf_attr_pa_tnc_error_create_with_offset(error_code,
328 this->encoding,
329 offset + PA_TNC_ATTR_HEADER_SIZE + attr_offset);
330 goto err;
331 }
332 this->attributes->insert_last(this->attributes, attr);
333 offset += length;
334 }
335
336 if (reader->remaining(reader) == 0)
337 {
338 reader->destroy(reader);
339 return SUCCESS;
340 }
341 DBG1(DBG_TNC, "insufficient bytes for PA-TNC attribute header");
342 error = ietf_attr_pa_tnc_error_create_with_offset(error_code,
343 this->encoding, offset);
344
345 err:
346 reader->destroy(reader);
347 this->errors->insert_last(this->errors, error);
348 return VERIFY_ERROR;
349 }
350
351 METHOD(pa_tnc_msg_t, process_ietf_std_errors, bool,
352 private_pa_tnc_msg_t *this, linked_list_t *non_fatal_types)
353 {
354 enumerator_t *e1, *e2;
355 enum_name_t *pa_attr_names;
356 pa_tnc_attr_t *attr;
357 pen_type_t type, unsupported_type;
358 uint8_t flags;
359 bool fatal_error = FALSE;
360
361 e1 = this->attributes->create_enumerator(this->attributes);
362 while (e1->enumerate(e1, &attr))
363 {
364 type = attr->get_type(attr);
365
366 if (type.vendor_id == PEN_IETF && type.type == IETF_ATTR_PA_TNC_ERROR)
367 {
368 ietf_attr_pa_tnc_error_t *error_attr;
369 pen_type_t error_code, *non_fatal_type;
370 chunk_t msg_info;
371 uint32_t offset;
372 bool fatal_current_error = TRUE;
373
374 error_attr = (ietf_attr_pa_tnc_error_t*)attr;
375 error_code = error_attr->get_error_code(error_attr);
376 msg_info = error_attr->get_msg_info(error_attr);
377
378 /* skip errors from non-IETF namespaces */
379 if (error_code.vendor_id != PEN_IETF)
380 {
381 continue;
382 }
383 DBG1(DBG_TNC, "received PA-TNC error '%N' concerning message "
384 "0x%08x/0x%08x", pa_tnc_error_code_names, error_code.type,
385 untoh32(msg_info.ptr), untoh32(msg_info.ptr + 4));
386
387 switch (error_code.type)
388 {
389 case PA_ERROR_INVALID_PARAMETER:
390 offset = error_attr->get_offset(error_attr);
391 DBG1(DBG_TNC, " occurred at offset of %u bytes", offset);
392 break;
393 case PA_ERROR_ATTR_TYPE_NOT_SUPPORTED:
394 unsupported_type =
395 error_attr->get_unsupported_attr(error_attr, &flags);
396 pa_attr_names =
397 imcv_pa_tnc_attributes->get_names(imcv_pa_tnc_attributes,
398 unsupported_type.vendor_id);
399 if (pa_attr_names)
400 {
401 DBG1(DBG_TNC, " unsupported attribute type '%N/%N' "
402 "0x%06x/0x%08x, flags 0x%02x",
403 pen_names, unsupported_type.vendor_id,
404 pa_attr_names, unsupported_type.type,
405 unsupported_type.vendor_id, unsupported_type.type,
406 flags);
407 }
408 else
409 {
410 DBG1(DBG_TNC, " unsupported attribute type '%N' "
411 "0x%06x/0x%08x, flags 0x%02x",
412 pen_names, unsupported_type.vendor_id,
413 unsupported_type.vendor_id, unsupported_type.type,
414 flags);
415 }
416 e2 = non_fatal_types->create_enumerator(non_fatal_types);
417 while (e2->enumerate(e2, &non_fatal_type))
418 {
419 if (pen_type_equals(unsupported_type, *non_fatal_type))
420 {
421 fatal_current_error = FALSE;
422 break;
423 }
424 }
425 e2->destroy(e2);
426 break;
427 default:
428 break;
429 }
430 if (fatal_current_error)
431 {
432 fatal_error = TRUE;
433 }
434 }
435 }
436 e1->destroy(e1);
437
438 return fatal_error;
439 }
440
441 METHOD(pa_tnc_msg_t, create_attribute_enumerator, enumerator_t*,
442 private_pa_tnc_msg_t *this)
443 {
444 return this->attributes->create_enumerator(this->attributes);
445 }
446
447 METHOD(pa_tnc_msg_t, create_error_enumerator, enumerator_t*,
448 private_pa_tnc_msg_t *this)
449 {
450 return this->errors->create_enumerator(this->errors);
451 }
452
453 METHOD(pa_tnc_msg_t, destroy, void,
454 private_pa_tnc_msg_t *this)
455 {
456 this->attributes->destroy_offset(this->attributes,
457 offsetof(pa_tnc_attr_t, destroy));
458 this->errors->destroy_offset(this->errors,
459 offsetof(pa_tnc_attr_t, destroy));
460 free(this->encoding.ptr);
461 free(this);
462 }
463
464 /**
465 * See header
466 */
467 pa_tnc_msg_t *pa_tnc_msg_create(size_t max_msg_len)
468 {
469 private_pa_tnc_msg_t *this;
470
471 INIT(this,
472 .public = {
473 .get_encoding = _get_encoding,
474 .add_attribute = _add_attribute,
475 .build = _build,
476 .process = _process,
477 .process_ietf_std_errors = _process_ietf_std_errors,
478 .create_attribute_enumerator = _create_attribute_enumerator,
479 .create_error_enumerator = _create_error_enumerator,
480 .destroy = _destroy,
481 },
482 .attributes = linked_list_create(),
483 .errors = linked_list_create(),
484 .msg_len = PA_TNC_HEADER_SIZE,
485 .max_msg_len = max_msg_len,
486 );
487
488 return &this->public;
489 }
490
491 /**
492 * See header
493 */
494 pa_tnc_msg_t *pa_tnc_msg_create_from_data(chunk_t data)
495 {
496 private_pa_tnc_msg_t *this;
497
498 INIT(this,
499 .public = {
500 .get_encoding = _get_encoding,
501 .add_attribute = _add_attribute,
502 .build = _build,
503 .process = _process,
504 .process_ietf_std_errors = _process_ietf_std_errors,
505 .create_attribute_enumerator = _create_attribute_enumerator,
506 .create_error_enumerator = _create_error_enumerator,
507 .destroy = _destroy,
508 },
509 .encoding = chunk_clone(data),
510 .attributes = linked_list_create(),
511 .errors = linked_list_create(),
512 );
513
514 return &this->public;
515 }
516