1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * SNMP PDU and packet transport related routines
31  */
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include "asn1.h"
38 #include "pdu.h"
39 #include "debug.h"
40 
41 /*
42  * Static declarations
43  */
44 static int	snmp_add_null_vars(snmp_pdu_t *, char *, int, int);
45 static oid	*snmp_oidstr_to_oid(int, char *, int, size_t *);
46 static uchar_t	*snmp_build_pdu(snmp_pdu_t *, uchar_t *, size_t *);
47 static uchar_t	*snmp_build_variable(uchar_t *, size_t *, oid *, size_t,
48 		    uchar_t, void *, size_t);
49 static uchar_t	*snmp_parse_pdu(int, uchar_t *, size_t *, snmp_pdu_t *);
50 static uchar_t	*snmp_parse_variable(uchar_t *, size_t *, pdu_varlist_t *);
51 
52 /*
53  * Allocates and creates a PDU for the specified SNMP command. Currently
54  * only SNMP_MSG_GET, SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK are supported
55  */
56 snmp_pdu_t *
57 snmp_create_pdu(int cmd, int max_reps, char *oidstrs, int n_oids, int row)
58 {
59 	snmp_pdu_t	*pdu;
60 
61 	if ((cmd != SNMP_MSG_GET) && (cmd != SNMP_MSG_GETNEXT) &&
62 	    (cmd != SNMP_MSG_GETBULK)) {
63 		return (NULL);
64 	}
65 
66 	pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t));
67 	if (pdu == NULL)
68 		return (NULL);
69 
70 	if (cmd == SNMP_MSG_GET || cmd == SNMP_MSG_GETNEXT) {
71 		pdu->version = SNMP_VERSION_1;
72 		pdu->errstat = 0;
73 		pdu->errindex = 0;
74 	} else if (cmd == SNMP_MSG_GETBULK) {
75 		pdu->version = SNMP_VERSION_2c;
76 		pdu->non_repeaters = 0;
77 		pdu->max_repetitions = max_reps ?
78 		    max_reps : SNMP_DEF_MAX_REPETITIONS;
79 	}
80 
81 	pdu->command = cmd;
82 	pdu->reqid = snmp_get_reqid();
83 	pdu->community = (uchar_t *)SNMP_DEF_COMMUNITY;
84 	pdu->community_len = SNMP_DEF_COMMUNITY_LEN;
85 
86 	if (snmp_add_null_vars(pdu, oidstrs, n_oids, row) < 0) {
87 		free((void *) pdu);
88 		return (NULL);
89 	}
90 
91 	pdu->req_pkt = NULL;
92 	pdu->req_pktsz = 0;
93 	pdu->reply_pkt = NULL;
94 	pdu->reply_pktsz = 0;
95 
96 	return (pdu);
97 }
98 
99 /*
100  * Builds a complete ASN.1 encoded snmp message packet out of the PDU.
101  * Currently the maximum request packet is limited to SNMP_DEF_PKTBUF_SZ.
102  * Since we only send SNMP_MSG_GET, SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK,
103  * as long as the number of bulk oids are not *too* many, we're safe with
104  * this limit (the typical packet size of a bulk request of 10 vars is
105  * around 250 bytes).
106  */
107 int
108 snmp_make_packet(snmp_pdu_t *pdu)
109 {
110 	uchar_t	*buf, *p;
111 	uchar_t	*msg_seq_end;
112 	uchar_t id;
113 	size_t	bufsz = SNMP_DEF_PKTBUF_SZ;
114 	size_t	seqlen;
115 
116 	if ((buf = (uchar_t *)calloc(1, SNMP_DEF_PKTBUF_SZ)) == NULL)
117 		return (-1);
118 
119 	/*
120 	 * Let's start with the ASN sequence tag. Set the length
121 	 * to 0 initially and fill it up once the message packetizing
122 	 * is complete.
123 	 */
124 	id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
125 	if ((p = asn_build_sequence(buf, &bufsz, id, 0)) == NULL) {
126 		free((void *) buf);
127 		return (-1);
128 	}
129 	msg_seq_end = p;
130 
131 	/*
132 	 * Store the version
133 	 */
134 	id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
135 	if ((p = asn_build_int(p, &bufsz, id, pdu->version)) == NULL) {
136 		free((void *) buf);
137 		return (-1);
138 	}
139 
140 	/*
141 	 * Store the community string
142 	 */
143 	id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR;
144 	p = asn_build_string(p, &bufsz, id, pdu->community, pdu->community_len);
145 	if (p == NULL) {
146 		free((void *) buf);
147 		return (-1);
148 	}
149 
150 	/*
151 	 * Build the PDU
152 	 */
153 	if ((p = snmp_build_pdu(pdu, p, &bufsz)) == NULL) {
154 		free((void *) buf);
155 		return (-1);
156 	}
157 
158 	/*
159 	 * Complete the message pkt by updating the message sequence length
160 	 */
161 	seqlen = p - msg_seq_end;
162 	id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
163 	(void) asn_build_sequence(buf, NULL, id, seqlen);
164 
165 	/*
166 	 * Calculate packet size and return
167 	 */
168 	pdu->req_pkt = buf;
169 	pdu->req_pktsz = p - buf;
170 
171 	return (0);
172 }
173 
174 /*
175  * Makes a PDU out of a reply packet. The reply message is parsed
176  * and if the reqid of the incoming packet does not match the reqid
177  * we're waiting for, an error is returned. The PDU is allocated
178  * inside this routine and must be freed by the caller once it is no
179  * longer needed.
180  */
181 snmp_pdu_t *
182 snmp_parse_reply(int reqid, uchar_t *reply_pkt, size_t reply_pktsz)
183 {
184 	snmp_pdu_t	*reply_pdu;
185 	uchar_t		*p;
186 	size_t		msgsz = reply_pktsz;
187 	uchar_t		exp_id;
188 
189 	reply_pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t));
190 	if (reply_pdu == NULL)
191 		return (NULL);
192 
193 	/*
194 	 * Try to parse the ASN sequence out of the beginning of the reply
195 	 * packet. If we don't find a sequence at the beginning, something's
196 	 * wrong.
197 	 */
198 	exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
199 	if ((p = asn_parse_sequence(reply_pkt, &msgsz, exp_id)) == NULL) {
200 		snmp_free_pdu(reply_pdu);
201 		return (NULL);
202 	}
203 
204 	/*
205 	 * Now try to parse the version out of the packet
206 	 */
207 	if ((p = asn_parse_int(p, &msgsz, &reply_pdu->version)) == NULL) {
208 		snmp_free_pdu(reply_pdu);
209 		return (NULL);
210 	}
211 	if ((reply_pdu->version != SNMP_VERSION_1) &&
212 	    (reply_pdu->version != SNMP_VERSION_2c)) {
213 		snmp_free_pdu(reply_pdu);
214 		return (NULL);
215 	}
216 
217 	/*
218 	 * Parse the community string (space allocated by asn_parse_string)
219 	 */
220 	p = asn_parse_string(p, &msgsz, &reply_pdu->community,
221 	    &reply_pdu->community_len);
222 	if (p == NULL) {
223 		snmp_free_pdu(reply_pdu);
224 		return (NULL);
225 	}
226 
227 	/*
228 	 * Parse the PDU part of the message
229 	 */
230 	if ((p = snmp_parse_pdu(reqid, p, &msgsz, reply_pdu)) == NULL) {
231 		snmp_free_pdu(reply_pdu);
232 		return (NULL);
233 	}
234 
235 	return (reply_pdu);
236 }
237 
238 
239 /*
240  * Convert the OID strings into the standard PDU oid form (sequence of
241  * integer subids) and add them to the PDU's variable list. Note that
242  * this is used only for preparing the request messages (GET, GETNEXT
243  * and GETBULK), so the values of the variables are always null.
244  */
245 static int
246 snmp_add_null_vars(snmp_pdu_t *pdu, char *oidstrs, int n_oids, int row)
247 {
248 	pdu_varlist_t	*vp, *prev;
249 	pdu_varlist_t	*varblock_p;
250 	char	*p;
251 	int	i;
252 
253 	/*
254 	 * It's much easier to allocate for all variables in one go,
255 	 * so we can release it quickly if there's any failure.
256 	 */
257 	varblock_p = (pdu_varlist_t *)calloc(n_oids, sizeof (pdu_varlist_t));
258 	if (varblock_p == NULL)
259 		return (-1);
260 
261 	prev = NULL;
262 	p = oidstrs;
263 	vp = varblock_p;
264 	for (i = 0; i < n_oids; i++) {
265 		vp->name = snmp_oidstr_to_oid(pdu->command,
266 		    p, row, &vp->name_len);
267 		if (vp->name == NULL) {
268 			free((void *) varblock_p);
269 			return (-1);
270 		}
271 		vp->val.str = NULL;
272 		vp->val_len = 0;
273 		vp->type = ASN_NULL;
274 		vp->nextvar = vp + 1;
275 
276 		LOGVAR(TAG_NULL_VAR, vp);
277 
278 		prev = vp;
279 		p += strlen(p) + 1;
280 		vp++;
281 	}
282 	prev->nextvar = NULL;
283 
284 	/*
285 	 * append the varlist to the PDU
286 	 */
287 	if (pdu->vars == NULL)
288 		pdu->vars = varblock_p;
289 	else {
290 		for (vp = pdu->vars; vp->nextvar; vp = vp->nextvar)
291 			;
292 		vp->nextvar = varblock_p;
293 	}
294 
295 	return (0);
296 }
297 /*
298  * Some assumptions are in place here to eliminate unnecessary complexity.
299  * All OID strings passed are assumed to be in the numeric string form, have
300  * no leading/trailing '.' or spaces. Since PICL plugin is currently the
301  * only customer, this is quite reasonable.
302  */
303 static oid *
304 snmp_oidstr_to_oid(int cmd, char *oidstr, int row, size_t *n_subids)
305 {
306 	int	i, count;
307 	char	*p, *q;
308 	char	*oidstr_dup;
309 	oid	*objid;
310 
311 	if ((oidstr == NULL) || (n_subids == NULL))
312 		return (NULL);
313 
314 	for (count = 1, p = oidstr; p; count++, p++) {
315 		if ((p = strchr(p, '.')) == NULL)
316 			break;
317 	}
318 
319 	/*
320 	 * Add one more to count for 'row'. Need special processing
321 	 * for SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK requests; see
322 	 * comment below.
323 	 */
324 	if ((cmd == SNMP_MSG_GET) || (cmd == SNMP_MSG_GETBULK && row > 0) ||
325 	    (cmd == SNMP_MSG_GETNEXT && row >= 0)) {
326 		count++;
327 	}
328 
329 	if ((oidstr_dup = strdup(oidstr)) == NULL)
330 		return (NULL);
331 
332 	objid = (oid *) calloc(count, sizeof (oid));
333 	if (objid == NULL) {
334 		free((void *) p);
335 		return (NULL);
336 	}
337 
338 	p = oidstr_dup;
339 	for (i = 0; i < count - 1; i++) {
340 		if (q = strchr(p, '.'))
341 			*q = 0;
342 		objid[i] = (oid) strtoul(p, NULL, 10);
343 		p = q + 1;
344 	}
345 
346 	/*
347 	 * For SNMP_MSG_GET, the leaf subid will simply be the row#.
348 	 *
349 	 * For SNMP_MSG_GETBULK, if the row# passed is greater than 0,
350 	 * we pass 'row-1' as the leaf subid, to include the item that
351 	 * is of interest to us. If the row# is less than or equal to 0,
352 	 * we will simply ignore it and pass only the prefix part of the
353 	 * oidstr. For this case, our count would have been 1 less than
354 	 * usual, and we are yet to save the last subid.
355 	 *
356 	 * For SNMP_MSG_GETNEXT, if the row# passed is less than 0,
357 	 * we'll simply ignore it and pass only the prefix part of the
358 	 * oidstr. For this case, our count would have been 1 less than
359 	 * usual, and we are yet to save the last subid. If the row#
360 	 * passed is greater than or equal to 0, we'll simply pass it
361 	 * verbatim, as the leaf subid.
362 	 */
363 	switch (cmd) {
364 	case SNMP_MSG_GET:
365 		objid[i] = (oid) row;
366 		break;
367 
368 	case SNMP_MSG_GETBULK:
369 		if (row > 0)
370 			objid[i] = (oid) (row - 1);
371 		else
372 			objid[i] = (oid) strtoul(p, NULL, 10);
373 		break;
374 
375 	case SNMP_MSG_GETNEXT:
376 		if (row < 0)
377 			objid[i] = (oid) strtoul(p, NULL, 10);
378 		else
379 			objid[i] = (oid) row;
380 		break;
381 	}
382 
383 	*n_subids = count;
384 
385 	free((void *) oidstr_dup);
386 
387 	return (objid);
388 }
389 
390 /*
391  * Builds the PDU part of the snmp message packet.
392  */
393 static uchar_t *
394 snmp_build_pdu(snmp_pdu_t *pdu, uchar_t *buf, size_t *bufsz_p)
395 {
396 	uchar_t	*p;
397 	uchar_t	*pdu_seq_begin, *pdu_seq_end;
398 	uchar_t	*varlist_seq_begin, *varlist_seq_end;
399 	uchar_t	id;
400 	size_t	seqlen;
401 	pdu_varlist_t	*vp;
402 
403 	/*
404 	 * Build ASN sequence for the PDU command (length will be
405 	 * updated later once the entire command is completely formed)
406 	 */
407 	pdu_seq_begin = buf;
408 	p = asn_build_sequence(buf, bufsz_p, (uchar_t)pdu->command, 0);
409 	if (p == NULL)
410 		return (NULL);
411 	pdu_seq_end = p;
412 
413 	/*
414 	 * Build the request id
415 	 */
416 	id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
417 	if ((p = asn_build_int(p, bufsz_p, id, pdu->reqid)) == NULL)
418 		return (NULL);
419 
420 	/*
421 	 * Build the non-repeaters and max-repetitions for SNMP_MSG_GETBULK
422 	 * (same as error status and error index for other message types)
423 	 */
424 	id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
425 	if ((p = asn_build_int(p, bufsz_p, id, pdu->non_repeaters)) == NULL)
426 		return (NULL);
427 
428 	id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
429 	if ((p = asn_build_int(p, bufsz_p, id, pdu->max_repetitions)) == NULL)
430 		return (NULL);
431 
432 	/*
433 	 * Build ASN sequence for the variables list (update length
434 	 * after building the varlist)
435 	 */
436 	varlist_seq_begin = p;
437 	id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
438 	if ((p = asn_build_sequence(p, bufsz_p, id, 0)) == NULL)
439 		return (NULL);
440 	varlist_seq_end = p;
441 
442 	/*
443 	 * Build the variables list
444 	 */
445 	for (vp = pdu->vars; vp; vp = vp->nextvar) {
446 		p = snmp_build_variable(p, bufsz_p, vp->name, vp->name_len,
447 		    vp->type, vp->val.str, vp->val_len);
448 		if (p == NULL)
449 			return (NULL);
450 	}
451 
452 	/*
453 	 * Now update the varlist sequence length
454 	 */
455 	seqlen = p - varlist_seq_end;
456 	id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
457 	(void) asn_build_sequence(varlist_seq_begin, NULL, id, seqlen);
458 
459 	/*
460 	 * And finally, update the length for the PDU sequence
461 	 */
462 	seqlen = p - pdu_seq_end;
463 	(void) asn_build_sequence(pdu_seq_begin, NULL, (uchar_t)pdu->command,
464 	    seqlen);
465 
466 	return (p);
467 }
468 
469 /*
470  * Builds an object variable into the snmp message packet. Although the
471  * code is here to build variables of basic types such as integer, object id
472  * and strings, the only type of variable we ever send via snmp request
473  * messages is the ASN_NULL type.
474  */
475 static uchar_t *
476 snmp_build_variable(uchar_t *buf, size_t *bufsz_p, oid *name, size_t name_len,
477     uchar_t val_type, void *val, size_t val_len)
478 {
479 	uchar_t	*p, *varseq_end;
480 	size_t	seqlen;
481 	uchar_t	id;
482 
483 	/*
484 	 * Each variable binding is in turn defined as a 'SEQUENCE of' by
485 	 * the SNMP PDU format, so we'll prepare the sequence and fill up
486 	 * the length later. Sigh!
487 	 */
488 	id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
489 	if ((p = asn_build_sequence(buf, bufsz_p, id, 0)) == NULL)
490 		return (NULL);
491 	varseq_end = p;
492 
493 	/*
494 	 * Build the object id
495 	 */
496 	id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID;
497 	if ((p = asn_build_objid(p, bufsz_p, id, name, name_len)) == NULL)
498 		return (NULL);
499 
500 	/*
501 	 * Currently we only ever build ASN_NULL vars while sending requests,
502 	 * since we support only SNMP_MSG_GET, SNMP_MSG_GETNEXT and
503 	 * SNMP_MSG_GETBULK.
504 	 */
505 	id = ASN_UNIVERSAL | ASN_PRIMITIVE | val_type;
506 	switch (val_type) {
507 	case ASN_INTEGER:
508 		p = asn_build_int(p, bufsz_p, id, *((int *)val));
509 		if (p == NULL)
510 			return (NULL);
511 		break;
512 
513 	case ASN_OBJECT_ID:
514 		p = asn_build_objid(p, bufsz_p, id, val,
515 		    val_len / sizeof (oid));
516 		if (p == NULL)
517 			return (NULL);
518 		break;
519 
520 	case ASN_OCTET_STR:
521 		p = asn_build_string(p, bufsz_p, id, (uchar_t *)val, val_len);
522 		if (p == NULL)
523 			return (NULL);
524 		break;
525 
526 	case ASN_NULL:
527 		if ((p = asn_build_null(p, bufsz_p, id)) == NULL)
528 			return (NULL);
529 		break;
530 
531 	default:
532 		return (NULL);
533 	}
534 
535 	/*
536 	 * Rebuild the variable sequence length
537 	 */
538 	seqlen = p - varseq_end;
539 	id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
540 	(void) asn_build_sequence(buf, NULL, id, seqlen);
541 
542 	return (p);
543 }
544 
545 /*
546  * Parse the PDU portion of the incoming snmp message into the reply_pdu.
547  * Space for all structure members are allocated as needed and must be freed
548  * by the caller when these are no longer needed.
549  */
550 static uchar_t *
551 snmp_parse_pdu(int reqid, uchar_t *msg, size_t *msgsz_p, snmp_pdu_t *reply_pdu)
552 {
553 	uchar_t	*p;
554 	uchar_t	id, exp_id;
555 	pdu_varlist_t	*newvp, *vp = NULL;
556 
557 	/*
558 	 * Parse the PDU header out of the message
559 	 */
560 	if ((p = asn_parse_header(msg, msgsz_p, &id)) == NULL)
561 		return (NULL);
562 	if (id != SNMP_MSG_RESPONSE && id != SNMP_MSG_REPORT)
563 		return (NULL);
564 	reply_pdu->command = (int)id;
565 
566 	/*
567 	 * Parse the request id and verify that this is the response
568 	 * we're expecting.
569 	 */
570 	if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->reqid)) == NULL)
571 		return (NULL);
572 	if (reply_pdu->reqid != reqid)
573 		return (NULL);
574 
575 	/*
576 	 * Parse the error-status and error-index values
577 	 */
578 	if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->errstat)) == NULL)
579 		return (NULL);
580 	if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->errindex)) == NULL)
581 		return (NULL);
582 
583 	/*
584 	 * Parse the header for the variables list sequence.
585 	 */
586 	exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
587 	if ((p = asn_parse_sequence(p, msgsz_p, exp_id)) == NULL)
588 		return (NULL);
589 
590 	while (((int)*msgsz_p) > 0) {
591 		if ((newvp = calloc(1, sizeof (pdu_varlist_t))) == NULL)
592 			return (NULL);
593 
594 		if (vp == NULL)
595 			reply_pdu->vars = newvp;
596 		else
597 			vp->nextvar = newvp;
598 
599 		vp = newvp;
600 		if ((p = snmp_parse_variable(p, msgsz_p, vp)) == NULL)
601 			return (NULL);
602 
603 		LOGVAR(TAG_RESPONSE_VAR, vp);
604 	}
605 
606 	return (p);
607 }
608 
609 /*
610  * Allocate and parse the next variable into the varlist
611  */
612 static uchar_t *
613 snmp_parse_variable(uchar_t *msg, size_t *msgsz_p, pdu_varlist_t *vp)
614 {
615 	uchar_t	*p;
616 	uchar_t	exp_id;
617 
618 	/*
619 	 * Parse this variable's sequence
620 	 */
621 	exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
622 	if ((p = asn_parse_sequence(msg, msgsz_p, exp_id)) == NULL)
623 		return (NULL);
624 
625 	/*
626 	 * Parse the variable's object identifier
627 	 */
628 	p = asn_parse_objid(p, msgsz_p, &vp->name, &vp->name_len);
629 	if (p == NULL)
630 		return (NULL);
631 
632 	/*
633 	 * Parse the object's value
634 	 */
635 	if ((p = asn_parse_objval(p, msgsz_p, vp)) == NULL)
636 		return (NULL);
637 
638 	return (p);
639 }
640 
641 void
642 snmp_free_pdu(snmp_pdu_t *pdu)
643 {
644 	pdu_varlist_t *vp, *nxt;
645 
646 	if (pdu) {
647 		if (pdu->community)
648 			free((void *) pdu->community);
649 
650 		for (vp = pdu->vars; vp; vp = nxt) {
651 			nxt = vp->nextvar;
652 
653 			if (vp->name)
654 				free((void *) vp->name);
655 			if (vp->val.str)
656 				free((void *) vp->val.str);
657 			free((void *) vp);
658 		}
659 
660 		if (pdu->req_pkt)
661 			free((void *) pdu->req_pkt);
662 
663 		if (pdu->reply_pkt)
664 			free((void *) pdu->reply_pkt);
665 
666 		free((void *) pdu);
667 	}
668 }
669