updated Copyright info
[strongswan.git] / src / libcharon / plugins / tnccs_20 / batch / pb_tnc_batch.c
1 /*
2 * Copyright (C) 2010 Sansar Choinyanbuu
3 * Copyright (C) 2010-2012 Andreas Steffen
4 * HSR Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #include "pb_tnc_batch.h"
18 #include "messages/pb_error_msg.h"
19 #include "state_machine/pb_tnc_state_machine.h"
20
21 #include <tnc/tnccs/tnccs.h>
22
23 #include <utils/linked_list.h>
24 #include <bio/bio_writer.h>
25 #include <bio/bio_reader.h>
26 #include <pen/pen.h>
27 #include <debug.h>
28
29 ENUM(pb_tnc_batch_type_names, PB_BATCH_CDATA, PB_BATCH_CLOSE,
30 "CDATA",
31 "SDATA",
32 "RESULT",
33 "CRETRY",
34 "SRETRY",
35 "CLOSE"
36 );
37
38 typedef struct private_pb_tnc_batch_t private_pb_tnc_batch_t;
39
40 /**
41 * PB-Batch Header (see section 4.1 of RFC 5793)
42 *
43 * 0 1 2 3
44 * 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
45 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
46 * | Version |D| Reserved | B-Type|
47 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48 * | Batch Length |
49 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
50 */
51
52 #define PB_TNC_BATCH_FLAG_NONE 0x00
53 #define PB_TNC_BATCH_FLAG_D (1<<7)
54 #define PB_TNC_BATCH_HEADER_SIZE 8
55
56 /**
57 * PB-TNC Message (see section 4.2 of RFC 5793)
58 *
59 * 0 1 2 3
60 * 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
61 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
62 * | Flags | PB-TNC Vendor ID |
63 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
64 * | PB-TNC Message Type |
65 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
66 * | PB-TNC Message Length |
67 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
68 * | PB-TNC Message Value (Variable Length) |
69 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
70 */
71
72 #define PB_TNC_FLAG_NONE 0x00
73 #define PB_TNC_FLAG_NOSKIP (1<<7)
74 #define PB_TNC_HEADER_SIZE 12
75
76 #define PB_TNC_RESERVED_MSG_TYPE 0xffffffff
77
78 /**
79 * Private data of a pb_tnc_batch_t object.
80 *
81 */
82 struct private_pb_tnc_batch_t {
83 /**
84 * Public pb_pa_msg_t interface.
85 */
86 pb_tnc_batch_t public;
87
88 /**
89 * TNCC if TRUE, TNCS if FALSE
90 */
91 bool is_server;
92
93 /**
94 * PB-TNC Batch type
95 */
96 pb_tnc_batch_type_t type;
97
98 /**
99 * Current PB-TNC Batch size
100 */
101 size_t batch_len;
102
103 /**
104 * Maximum PB-TNC Batch size
105 */
106 size_t max_batch_len;
107
108 /**
109 * linked list of PB-TNC messages
110 */
111 linked_list_t *messages;
112
113 /**
114 * linked list of PB-TNC error messages
115 */
116 linked_list_t *errors;
117
118 /**
119 * Encoded message
120 */
121 chunk_t encoding;
122
123 /**
124 * Offset into encoding (used for error reporting)
125 */
126 u_int32_t offset;
127 };
128
129 METHOD(pb_tnc_batch_t, get_type, pb_tnc_batch_type_t,
130 private_pb_tnc_batch_t *this)
131 {
132 return this->type;
133 }
134
135 METHOD(pb_tnc_batch_t, get_encoding, chunk_t,
136 private_pb_tnc_batch_t *this)
137 {
138 return this->encoding;
139 }
140
141 METHOD(pb_tnc_batch_t, add_msg, bool,
142 private_pb_tnc_batch_t *this, pb_tnc_msg_t* msg)
143 {
144 chunk_t msg_value;
145 size_t msg_len;
146
147 msg->build(msg);
148 msg_value = msg->get_encoding(msg);
149 msg_len = PB_TNC_HEADER_SIZE + msg_value.len;
150
151 if (this->batch_len + msg_len > this->max_batch_len)
152 {
153 /* message just does not fit into this batch */
154 return FALSE;
155 }
156 this->batch_len += msg_len;
157
158 DBG2(DBG_TNC, "adding %N message", pb_tnc_msg_type_names,
159 msg->get_type(msg));
160 this->messages->insert_last(this->messages, msg);
161 return TRUE;
162 }
163
164 METHOD(pb_tnc_batch_t, build, void,
165 private_pb_tnc_batch_t *this)
166 {
167 u_int32_t msg_len;
168 chunk_t msg_value;
169 enumerator_t *enumerator;
170 pb_tnc_msg_type_t msg_type;
171 pb_tnc_msg_t *msg;
172 bio_writer_t *writer;
173
174 /* build PB-TNC batch header */
175 writer = bio_writer_create(this->batch_len);
176 writer->write_uint8 (writer, PB_TNC_VERSION);
177 writer->write_uint8 (writer, this->is_server ?
178 PB_TNC_BATCH_FLAG_D : PB_TNC_BATCH_FLAG_NONE);
179 writer->write_uint16(writer, this->type);
180 writer->write_uint32(writer, this->batch_len);
181
182 /* build PB-TNC messages */
183 enumerator = this->messages->create_enumerator(this->messages);
184 while (enumerator->enumerate(enumerator, &msg))
185 {
186 u_int8_t flags = PB_TNC_FLAG_NONE;
187
188 /* build PB-TNC message */
189 msg_value = msg->get_encoding(msg);
190 msg_len = PB_TNC_HEADER_SIZE + msg_value.len;
191 msg_type = msg->get_type(msg);
192 if (pb_tnc_msg_infos[msg_type].has_noskip_flag)
193 {
194 flags |= PB_TNC_FLAG_NOSKIP;
195 }
196 writer->write_uint8 (writer, flags);
197 writer->write_uint24(writer, PEN_IETF);
198 writer->write_uint32(writer, msg_type);
199 writer->write_uint32(writer, msg_len);
200 writer->write_data (writer, msg_value);
201 }
202 enumerator->destroy(enumerator);
203
204 this->encoding = chunk_clone(writer->get_buf(writer));
205 writer->destroy(writer);
206 }
207
208 static status_t process_batch_header(private_pb_tnc_batch_t *this,
209 pb_tnc_state_machine_t *state_machine)
210 {
211 bio_reader_t *reader;
212 pb_tnc_msg_t *msg;
213 pb_error_msg_t *err_msg;
214 u_int8_t version, flags, reserved, type;
215 u_int32_t batch_len;
216 bool directionality;
217
218 if (this->encoding.len < PB_TNC_BATCH_HEADER_SIZE)
219 {
220 DBG1(DBG_TNC, "%u bytes insufficient to parse PB-TNC batch header",
221 this->encoding.len);
222 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
223 PB_ERROR_INVALID_PARAMETER, 0);
224 goto fatal;
225 }
226
227 reader = bio_reader_create(this->encoding);
228 reader->read_uint8 (reader, &version);
229 reader->read_uint8 (reader, &flags);
230 reader->read_uint8 (reader, &reserved);
231 reader->read_uint8 (reader, &type);
232 reader->read_uint32(reader, &batch_len);
233 reader->destroy(reader);
234
235 /* Version */
236 if (version != PB_TNC_VERSION)
237 {
238 DBG1(DBG_TNC, "unsupported TNCCS batch version 0x%02x", version);
239 msg = pb_error_msg_create(TRUE, PEN_IETF,
240 PB_ERROR_VERSION_NOT_SUPPORTED);
241 err_msg = (pb_error_msg_t*)msg;
242 err_msg->set_bad_version(err_msg, version);
243 goto fatal;
244 }
245
246 /* Directionality */
247 directionality = (flags & PB_TNC_BATCH_FLAG_D) != PB_TNC_BATCH_FLAG_NONE;
248 if (directionality == this->is_server)
249 {
250 DBG1(DBG_TNC, "wrong Directionality: batch is from a PB %s",
251 directionality ? "server" : "client");
252 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
253 PB_ERROR_INVALID_PARAMETER, 1);
254 goto fatal;
255 }
256
257 /* Batch Type */
258 this->type = type & 0x0F;
259 if (this->type > PB_BATCH_ROOF)
260 {
261 DBG1(DBG_TNC, "unknown PB-TNC batch type: %d", this->type);
262 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
263 PB_ERROR_INVALID_PARAMETER, 3);
264 goto fatal;
265 }
266
267 if (!state_machine->receive_batch(state_machine, this->type))
268 {
269 DBG1(DBG_TNC, "unexpected PB-TNC batch type: %N",
270 pb_tnc_batch_type_names, this->type);
271 msg = pb_error_msg_create(TRUE, PEN_IETF,
272 PB_ERROR_UNEXPECTED_BATCH_TYPE);
273 goto fatal;
274 }
275 DBG1(DBG_TNC, "processing PB-TNC %N batch", pb_tnc_batch_type_names,
276 this->type);
277
278 /* Batch Length */
279 if (this->encoding.len != batch_len)
280 {
281 DBG1(DBG_TNC, "%u bytes of data is not equal to batch length of %u bytes",
282 this->encoding.len, batch_len);
283 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
284 PB_ERROR_INVALID_PARAMETER, 4);
285 goto fatal;
286 }
287
288 this->offset = PB_TNC_BATCH_HEADER_SIZE;
289
290 /* Register an empty CDATA batch with the state machine */
291 if (this->type == PB_BATCH_CDATA)
292 {
293 state_machine->set_empty_cdata(state_machine,
294 this->offset == this->encoding.len);
295 }
296 return SUCCESS;
297
298 fatal:
299 this->errors->insert_last(this->errors, msg);
300 return FAILED;
301 }
302
303 static status_t process_tnc_msg(private_pb_tnc_batch_t *this)
304 {
305 bio_reader_t *reader;
306 pb_tnc_msg_t *pb_tnc_msg, *msg;
307 u_int8_t flags;
308 u_int32_t vendor_id, msg_type, msg_len, offset;
309 chunk_t data, msg_value;
310 bool noskip_flag;
311 status_t status;
312
313 data = chunk_skip(this->encoding, this->offset);
314
315 if (data.len < PB_TNC_HEADER_SIZE)
316 {
317 DBG1(DBG_TNC, "%u bytes insufficient to parse PB-TNC message header",
318 data.len);
319 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
320 PB_ERROR_INVALID_PARAMETER, this->offset);
321 goto fatal;
322 }
323
324 reader = bio_reader_create(data);
325 reader->read_uint8 (reader, &flags);
326 reader->read_uint24(reader, &vendor_id);
327 reader->read_uint32(reader, &msg_type);
328 reader->read_uint32(reader, &msg_len);
329 reader->destroy(reader);
330
331 noskip_flag = (flags & PB_TNC_FLAG_NOSKIP) != PB_TNC_FLAG_NONE;
332
333 if (msg_len > data.len)
334 {
335 DBG1(DBG_TNC, "%u bytes insufficient to parse PB-TNC message", data.len);
336 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
337 PB_ERROR_INVALID_PARAMETER, this->offset + 8);
338 goto fatal;
339 }
340
341 if (vendor_id == PEN_RESERVED)
342 {
343 DBG1(DBG_TNC, "Vendor ID 0x%06x is reserved", PEN_RESERVED);
344 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
345 PB_ERROR_INVALID_PARAMETER, this->offset + 1);
346 goto fatal;
347
348 }
349
350 if (msg_type == PB_TNC_RESERVED_MSG_TYPE)
351 {
352 DBG1(DBG_TNC, "PB-TNC message Type 0x%08x is reserved",
353 PB_TNC_RESERVED_MSG_TYPE);
354 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
355 PB_ERROR_INVALID_PARAMETER, this->offset + 4);
356 goto fatal;
357 }
358
359
360 if (vendor_id != PEN_IETF || msg_type > PB_MSG_ROOF)
361 {
362 if (msg_len < PB_TNC_HEADER_SIZE)
363 {
364 DBG1(DBG_TNC, "%u bytes too small for PB-TNC message length",
365 msg_len);
366 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
367 PB_ERROR_INVALID_PARAMETER, this->offset + 8);
368 goto fatal;
369 }
370
371 if (noskip_flag)
372 {
373 DBG1(DBG_TNC, "reject PB-TNC message (Vendor ID 0x%06x / "
374 "Type 0x%08x)", vendor_id, msg_type);
375 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
376 PB_ERROR_UNSUPPORTED_MANDATORY_MSG, this->offset);
377 goto fatal;
378 }
379 else
380 {
381 DBG1(DBG_TNC, "ignore PB-TNC message (Vendor ID 0x%06x / "
382 "Type 0x%08x)", vendor_id, msg_type);
383 this->offset += msg_len;
384 return SUCCESS;
385 }
386 }
387 else
388 {
389 if (pb_tnc_msg_infos[msg_type].has_noskip_flag != TRUE_OR_FALSE &&
390 pb_tnc_msg_infos[msg_type].has_noskip_flag != noskip_flag)
391 {
392 DBG1(DBG_TNC, "%N message must%s have NOSKIP flag set",
393 pb_tnc_msg_type_names, msg_type,
394 pb_tnc_msg_infos[msg_type].has_noskip_flag ? "" : " not");
395 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
396 PB_ERROR_INVALID_PARAMETER, this->offset);
397 goto fatal;
398 }
399
400 if (msg_len < pb_tnc_msg_infos[msg_type].min_size ||
401 (pb_tnc_msg_infos[msg_type].exact_size &&
402 msg_len != pb_tnc_msg_infos[msg_type].min_size))
403 {
404 DBG1(DBG_TNC, "%N message length must be %s %u bytes but is %u bytes",
405 pb_tnc_msg_type_names, msg_type,
406 pb_tnc_msg_infos[msg_type].exact_size ? "exactly" : "at least",
407 pb_tnc_msg_infos[msg_type].min_size, msg_len);
408 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
409 PB_ERROR_INVALID_PARAMETER, this->offset);
410 goto fatal;
411 }
412 }
413
414 if (pb_tnc_msg_infos[msg_type].in_result_batch &&
415 this->type != PB_BATCH_RESULT)
416 {
417 if (this->is_server)
418 {
419 DBG1(DBG_TNC,"reject %N message received from a PB-TNC client",
420 pb_tnc_msg_type_names, msg_type);
421 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
422 PB_ERROR_INVALID_PARAMETER, this->offset);
423 goto fatal;
424 }
425 else
426 {
427 DBG1(DBG_TNC,"ignore %N message not received within RESULT batch",
428 pb_tnc_msg_type_names, msg_type);
429 this->offset += msg_len;
430 return SUCCESS;
431 }
432 }
433
434 DBG2(DBG_TNC, "processing %N message (%u bytes)", pb_tnc_msg_type_names,
435 msg_type, msg_len);
436 data.len = msg_len;
437 msg_value = chunk_skip(data, PB_TNC_HEADER_SIZE);
438 pb_tnc_msg = pb_tnc_msg_create_from_data(msg_type, msg_value);
439
440 status = pb_tnc_msg->process(pb_tnc_msg, &offset);
441 if (status == FAILED || status == VERIFY_ERROR)
442 {
443 msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
444 PB_ERROR_INVALID_PARAMETER, this->offset + offset);
445 this->errors->insert_last(this->errors, msg);
446 }
447 if (status == FAILED)
448 {
449 pb_tnc_msg->destroy(pb_tnc_msg);
450 return FAILED;
451 }
452 this->messages->insert_last(this->messages, pb_tnc_msg);
453 this->offset += msg_len;
454 return status;
455
456 fatal:
457 this->errors->insert_last(this->errors, msg);
458 return FAILED;
459 }
460
461 METHOD(pb_tnc_batch_t, process, status_t,
462 private_pb_tnc_batch_t *this, pb_tnc_state_machine_t *state_machine)
463 {
464 status_t status;
465
466 status = process_batch_header(this, state_machine);
467 if (status != SUCCESS)
468 {
469 return FAILED;
470 }
471
472 while (this->offset < this->encoding.len)
473 {
474 switch (process_tnc_msg(this))
475 {
476 case FAILED:
477 return FAILED;
478 case VERIFY_ERROR:
479 status = VERIFY_ERROR;
480 break;
481 case SUCCESS:
482 default:
483 break;
484 }
485 }
486 return status;
487 }
488
489 METHOD(pb_tnc_batch_t, create_msg_enumerator, enumerator_t*,
490 private_pb_tnc_batch_t *this)
491 {
492 return this->messages->create_enumerator(this->messages);
493 }
494
495 METHOD(pb_tnc_batch_t, create_error_enumerator, enumerator_t*,
496 private_pb_tnc_batch_t *this)
497 {
498 return this->errors->create_enumerator(this->errors);
499 }
500
501 METHOD(pb_tnc_batch_t, destroy, void,
502 private_pb_tnc_batch_t *this)
503 {
504 this->messages->destroy_offset(this->messages,
505 offsetof(pb_tnc_msg_t, destroy));
506 this->errors->destroy_offset(this->errors,
507 offsetof(pb_tnc_msg_t, destroy));
508 free(this->encoding.ptr);
509 free(this);
510 }
511
512 /**
513 * See header
514 */
515 pb_tnc_batch_t* pb_tnc_batch_create(bool is_server, pb_tnc_batch_type_t type,
516 size_t max_batch_len)
517 {
518 private_pb_tnc_batch_t *this;
519
520 INIT(this,
521 .public = {
522 .get_type = _get_type,
523 .get_encoding = _get_encoding,
524 .add_msg = _add_msg,
525 .build = _build,
526 .process = _process,
527 .create_msg_enumerator = _create_msg_enumerator,
528 .create_error_enumerator = _create_error_enumerator,
529 .destroy = _destroy,
530 },
531 .is_server = is_server,
532 .type = type,
533 .max_batch_len = max_batch_len,
534 .batch_len = PB_TNC_BATCH_HEADER_SIZE,
535 .messages = linked_list_create(),
536 .errors = linked_list_create(),
537 );
538
539 DBG2(DBG_TNC, "creating PB-TNC %N batch", pb_tnc_batch_type_names, type);
540
541 return &this->public;
542 }
543
544 /**
545 * See header
546 */
547 pb_tnc_batch_t* pb_tnc_batch_create_from_data(bool is_server, chunk_t data)
548 {
549 private_pb_tnc_batch_t *this;
550
551 INIT(this,
552 .public = {
553 .get_type = _get_type,
554 .get_encoding = _get_encoding,
555 .add_msg = _add_msg,
556 .build = _build,
557 .process = _process,
558 .create_msg_enumerator = _create_msg_enumerator,
559 .create_error_enumerator = _create_error_enumerator,
560 .destroy = _destroy,
561 },
562 .is_server = is_server,
563 .messages = linked_list_create(),
564 .errors = linked_list_create(),
565 .encoding = chunk_clone(data),
566 );
567
568 return &this->public;
569 }
570