Added --with-vmps to configure options, so that you can build
[freeradius.git] / src / lib / vqp.c
1 /*
2  * vqp.c        Functions to send/receive VQP packets.
3  *
4  * Version:     $Id$
5  *
6  *   This library is free software; you can redistribute it and/or
7  *   modify it under the terms of the GNU Lesser General Public
8  *   License as published by the Free Software Foundation; either
9  *   version 2.1 of the License, or (at your option) any later version.
10  *
11  *   This library is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  *   Lesser General Public License for more details.
15  *
16  *   You should have received a copy of the GNU Lesser General Public
17  *   License along with this library; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2007 Alan DeKok <aland@deployingradius.com>
21  */
22
23 #include        <freeradius-devel/ident.h>
24 RCSID("$Id$");
25
26 #include        <freeradius-devel/libradius.h>
27 #include        <freeradius-devel/vqp.h>
28
29 #ifdef WITH_VMPS
30
31 /*
32  *  http://www.openbsd.org/cgi-bin/cvsweb/src/usr.sbin/tcpdump/print-vqp.c
33  *
34  *  Some of how it works:
35  *
36  *  http://www.hackingciscoexposed.com/pdf/chapter12.pdf
37  *
38  * VLAN Query Protocol (VQP)
39  *
40  *    0                   1                   2                   3
41  *    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
42  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
43  *   |    Version    |    Opcode     | Response Code |  Data Count   |
44  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
45  *   |                         Transaction ID                        |
46  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
47  *   |                            Type (1)                           |
48  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
49  *   |             Length            |            Data               /
50  *   /                                                               /
51  *   /                                                               /
52  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
53  *   |                            Type (n)                           |
54  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55  *   |             Length            |            Data               /
56  *   /                                                               /
57  *   /                                                               /
58  *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59  *
60  * VQP is layered over UDP.  The default destination port is 1589.
61  *
62  */
63 #define VQP_HDR_LEN (8)
64 #define VQP_VERSION (1)
65 #define VQP_MAX_ATTRIBUTES (12)
66
67
68 /*
69  *      Wrapper for sendto which handles sendfromto, IPv6, and all
70  *      possible combinations.
71  *
72  *      FIXME:  This is just a copy of rad_sendto().
73  *      Duplicate code is bad.
74  */
75 static int vqp_sendto(int sockfd, void *data, size_t data_len, int flags,
76                       lrad_ipaddr_t *src_ipaddr, lrad_ipaddr_t *dst_ipaddr,
77                       int dst_port)
78 {
79         struct sockaddr_storage dst;
80         socklen_t               sizeof_dst = sizeof(dst);
81
82 #ifdef WITH_UDPFROMTO
83         struct sockaddr_storage src;
84         socklen_t               sizeof_src = sizeof(src);
85
86         memset(&src, 0, sizeof(src));
87 #endif
88         memset(&dst, 0, sizeof(dst));
89
90         /*
91          *      IPv4 is supported.
92          */
93         if (dst_ipaddr->af == AF_INET) {
94                 struct sockaddr_in      *s4;
95
96                 s4 = (struct sockaddr_in *)&dst;
97                 sizeof_dst = sizeof(struct sockaddr_in);
98
99                 s4->sin_family = AF_INET;
100                 s4->sin_addr = dst_ipaddr->ipaddr.ip4addr;
101                 s4->sin_port = htons(dst_port);
102
103 #ifdef WITH_UDPFROMTO
104                 s4 = (struct sockaddr_in *)&src;
105                 sizeof_src = sizeof(struct sockaddr_in);
106
107                 s4->sin_family = AF_INET;
108                 s4->sin_addr = src_ipaddr->ipaddr.ip4addr;
109 #endif
110
111         /*
112          *      IPv6 MAY be supported.
113          */
114 #ifdef HAVE_STRUCT_SOCKADDR_IN6
115         } else if (dst_ipaddr->af == AF_INET6) {
116                 struct sockaddr_in6     *s6;
117
118                 s6 = (struct sockaddr_in6 *)&dst;
119                 sizeof_dst = sizeof(struct sockaddr_in6);
120                 
121                 s6->sin6_family = AF_INET6;
122                 s6->sin6_addr = dst_ipaddr->ipaddr.ip6addr;
123                 s6->sin6_port = htons(dst_port);
124
125 #ifdef WITH_UDPFROMTO
126                 return -1;      /* UDPFROMTO && IPv6 are not supported */
127 #if 0
128                 s6 = (struct sockaddr_in6 *)&src;
129                 sizeof_src = sizeof(struct sockaddr_in6);
130
131                 s6->sin6_family = AF_INET6;
132                 s6->sin6_addr = src_ipaddr->ipaddr.ip6addr;
133 #endif /* #if 0 */
134 #endif /* WITH_UDPFROMTO */
135 #endif /* HAVE_STRUCT_SOCKADDR_IN6 */
136         } else return -1;   /* Unknown address family, Die Die Die! */
137
138 #ifdef WITH_UDPFROMTO
139         /*
140          *      Only IPv4 is supported for udpfromto.
141          *
142          *      And if they don't specify a source IP address, don't
143          *      use udpfromto.
144          */
145         if ((dst_ipaddr->af == AF_INET) ||
146             (src_ipaddr->af != AF_UNSPEC)) {
147                 return sendfromto(sockfd, data, data_len, flags,
148                                   (struct sockaddr *)&src, sizeof_src, 
149                                   (struct sockaddr *)&dst, sizeof_dst);
150         }
151 #else
152         src_ipaddr = src_ipaddr; /* -Wunused */
153 #endif
154
155         /*
156          *      No udpfromto, OR an IPv6 socket, fail gracefully.
157          */
158         return sendto(sockfd, data, data_len, flags, 
159                       (struct sockaddr *)&dst, sizeof_dst);
160 }
161
162 /*
163  *      Wrapper for recvfrom, which handles recvfromto, IPv6, and all
164  *      possible combinations.
165  *
166  *      FIXME:  This is copied from rad_recvfrom, with minor edits.
167  */
168 static ssize_t vqp_recvfrom(int sockfd, uint8_t **pbuf, int flags,
169                             lrad_ipaddr_t *src_ipaddr, uint16_t *src_port,
170                             lrad_ipaddr_t *dst_ipaddr, uint16_t *dst_port)
171 {
172         struct sockaddr_storage src;
173         struct sockaddr_storage dst;
174         socklen_t               sizeof_src = sizeof(src);
175         socklen_t               sizeof_dst = sizeof(dst);
176         ssize_t                 data_len;
177         uint8_t                 header[4];
178         void                    *buf;
179         size_t                  len;
180
181         memset(&src, 0, sizeof_src);
182         memset(&dst, 0, sizeof_dst);
183
184         /*
185          *      Get address family, etc. first, so we know if we
186          *      need to do udpfromto.
187          *
188          *      FIXME: udpfromto also does this, but it's not
189          *      a critical problem.
190          */
191         if (getsockname(sockfd, (struct sockaddr *)&dst,
192                         &sizeof_dst) < 0) return -1;
193
194         /*
195          *      Read the length of the packet, from the packet.
196          *      This lets us allocate the buffer to use for
197          *      reading the rest of the packet.
198          */
199         data_len = recvfrom(sockfd, header, sizeof(header), MSG_PEEK,
200                             (struct sockaddr *)&src, &sizeof_src);
201         if (data_len < 0) return -1;
202
203         /*
204          *      Too little data is available, discard the packet.
205          */
206         if (data_len < 4) {
207                 recvfrom(sockfd, header, sizeof(header), flags, 
208                          (struct sockaddr *)&src, &sizeof_src);
209                 return 0;
210
211                 /*
212                  *      Invalid version, packet type, or too many
213                  *      attributes.  Die.
214                  */
215         } else if ((header[0] != VQP_VERSION) ||
216                    (header[1] < 1) ||
217                    (header[1] > 4) ||
218                    (header[3] > VQP_MAX_ATTRIBUTES)) {
219                 recvfrom(sockfd, header, sizeof(header), flags,
220                          (struct sockaddr *)&src, &sizeof_src);
221                 return 0;
222
223         } else {                /* we got 4 bytes of data. */
224                 /*
225                  *      We don't care about the contents for now...
226                  */
227 #if 0
228                 /*
229                  *      How many attributes are in the packet.
230                  */
231                 len = header[3];
232
233                 if ((header[1] == 1) || (header[1] == 3)) {
234                         if (len != VQP_MAX_ATTRIBUTES) {
235                                 recvfrom(sockfd, header, sizeof(header), 0,
236                                          (struct sockaddr *)&src, &sizeof_src);
237                                 return 0;
238                         }
239                         /*
240                          *      Maximum length we support.
241                          */
242                         len = (12 * (4 + 4 + 253));
243
244                 } else {
245                         if (len != 2) {
246                                 recvfrom(sockfd, header, sizeof(header), 0, 
247                                  (struct sockaddr *)&src, &sizeof_src);
248                                 return 0;
249                         }
250                         /*
251                          *      Maximum length we support.
252                          */
253                         len = (12 * (4 + 4 + 253));
254                 }
255 #endif
256         }
257
258         /*
259          *      For now, be generous.
260          */
261         len = (12 * (4 + 4 + 253));
262
263         buf = malloc(len);
264         if (!buf) return -1;
265
266         /*
267          *      Receive the packet.  The OS will discard any data in the
268          *      packet after "len" bytes.
269          */
270 #ifdef WITH_UDPFROMTO
271         if (dst.ss_family == AF_INET) {
272                 data_len = recvfromto(sockfd, buf, len, flags,
273                                       (struct sockaddr *)&src, &sizeof_src, 
274                                       (struct sockaddr *)&dst, &sizeof_dst);
275         } else
276 #endif
277                 /*
278                  *      No udpfromto, OR an IPv6 socket.  Fail gracefully.
279                  */
280                 data_len = recvfrom(sockfd, buf, len, flags, 
281                                     (struct sockaddr *)&src, &sizeof_src);
282         if (data_len < 0) {
283                 free(buf);
284                 return data_len;
285         }
286
287         /*
288          *      Check address families, and update src/dst ports, etc.
289          */
290         if (src.ss_family == AF_INET) {
291                 struct sockaddr_in      *s4;
292
293                 s4 = (struct sockaddr_in *)&src;
294                 src_ipaddr->af = AF_INET;
295                 src_ipaddr->ipaddr.ip4addr = s4->sin_addr;
296                 *src_port = ntohs(s4->sin_port);
297
298                 s4 = (struct sockaddr_in *)&dst;
299                 dst_ipaddr->af = AF_INET;
300                 dst_ipaddr->ipaddr.ip4addr = s4->sin_addr;
301                 *dst_port = ntohs(s4->sin_port);
302
303 #ifdef HAVE_STRUCT_SOCKADDR_IN6
304         } else if (src.ss_family == AF_INET6) {
305                 struct sockaddr_in6     *s6;
306
307                 s6 = (struct sockaddr_in6 *)&src;
308                 src_ipaddr->af = AF_INET6;
309                 src_ipaddr->ipaddr.ip6addr = s6->sin6_addr;
310                 *src_port = ntohs(s6->sin6_port);
311
312                 s6 = (struct sockaddr_in6 *)&dst;
313                 dst_ipaddr->af = AF_INET6;
314                 dst_ipaddr->ipaddr.ip6addr = s6->sin6_addr;
315                 *dst_port = ntohs(s6->sin6_port);
316 #endif
317         } else {
318                 free(buf);
319                 return -1;      /* Unknown address family, Die Die Die! */
320         }
321         
322         /*
323          *      Different address families should never happen.
324          */
325         if (src.ss_family != dst.ss_family) {
326                 free(buf);
327                 return -1;
328         }
329
330         /*
331          *      Tell the caller about the data
332          */
333         *pbuf = buf;
334
335         return data_len;
336 }
337
338 RADIUS_PACKET *vqp_recv(int sockfd)
339 {
340         uint8_t *ptr;
341         ssize_t length;
342         uint32_t id;
343         RADIUS_PACKET *packet;
344
345         /*
346          *      Allocate the new request data structure
347          */
348         if ((packet = malloc(sizeof(*packet))) == NULL) {
349                 librad_log("out of memory");
350                 return NULL;
351         }
352         memset(packet, 0, sizeof(*packet));
353
354         packet->data_len = vqp_recvfrom(sockfd, &packet->data, 0,
355                                         &packet->src_ipaddr, &packet->src_port,
356                                         &packet->dst_ipaddr, &packet->dst_port);
357
358         /*
359          *      Check for socket errors.
360          */
361         if (packet->data_len < 0) {
362                 librad_log("Error receiving packet: %s", strerror(errno));
363                 /* packet->data is NULL */
364                 free(packet);
365                 return NULL;
366         }
367
368
369         /*
370          *      We can only receive packets formatted in a way we
371          *      expect.  However, we accept MORE attributes in a
372          *      packet than normal implementations may send.
373          */
374         if (packet->data_len < VQP_HDR_LEN) {
375                 librad_log("VQP packet is too short");
376                 rad_free(&packet);
377                 return NULL;
378         }
379
380         ptr = packet->data;
381
382         if (0) {
383                 int i;
384                 for (i = 0; i < packet->data_len; i++) {
385                         if ((i & 0x0f) == 0) fprintf(stderr, "%02x: ", i);
386                         fprintf(stderr, "%02x ", ptr[i]);
387                         if ((i & 0x0f) == 0x0f) fprintf(stderr, "\n");
388                 }
389           
390         }
391
392         if (ptr[3] > VQP_MAX_ATTRIBUTES) {
393                 librad_log("Too many VQP attributes");
394                 rad_free(&packet);
395                 return NULL;
396         }
397
398         if (packet->data_len > VQP_HDR_LEN) {
399                 int attrlen;
400
401                 /*
402                  *      Skip the header.
403                  */
404                 ptr += VQP_HDR_LEN;
405                 length = packet->data_len - VQP_HDR_LEN;
406
407                 while (length > 0) {
408                         if (length < 7) {
409                                 librad_log("Packet contains malformed attribute");
410                                 rad_free(&packet);
411                                 return NULL;
412                         }
413
414                         /*
415                          *      Attributes are 4 bytes
416                          *      0x00000c01 ... 0x00000c08
417                          */
418                         if ((ptr[0] != 0) || (ptr[1] != 0) ||
419                             (ptr[2] != 0x0c) || (ptr[3] < 1) || (ptr[3] > 8)) {
420                                 librad_log("Packet contains invalid attribute");
421                                 rad_free(&packet);
422                                 return NULL;
423                         }
424                         
425                         /*
426                          *      Length is 2 bytes
427                          *
428                          *      We support lengths 1..253, for internal
429                          *      server reasons.  Also, there's no reason
430                          *      for bigger lengths to exist... admins
431                          *      won't be typing in a 32K vlan name.
432                          */
433                         if ((ptr[4] != 0) || (ptr[5] > 253)) {
434                                 librad_log("Packet contains attribute with invalid length %02x %02x", ptr[4], ptr[5]);
435                                 rad_free(&packet);
436                                 return NULL;
437                         }
438                         attrlen = ptr[5];
439                         ptr += 6 + attrlen;
440                         length -= (6 + attrlen);
441                 }
442         }
443
444         packet->sockfd = sockfd;
445         packet->vps = NULL;
446
447         /*
448          *      This is more than a bit of a hack.
449          */
450         packet->code = PW_AUTHENTICATION_REQUEST;
451
452         memcpy(&id, packet->data + 4, 4);
453         packet->id = ntohl(id);
454
455         /*
456          *      FIXME: Create a fake "request authenticator", to
457          *      avoid duplicates?  Or is the VQP sequence number
458          *      adequate for this purpose?
459          */
460
461         return packet;
462 }
463
464 /*
465  *      We do NOT  mirror the old-style RADIUS code  that does encode,
466  *      sign && send in one function.  For VQP, the caller MUST perform
467  *      each task manually, and separately.
468  */
469 int vqp_send(RADIUS_PACKET *packet)
470 {
471         if (!packet || !packet->data || (packet->data_len < 8)) return -1;
472
473         /*
474          *      Don't print out the attributes, they were printed out
475          *      when it was encoded.
476          */
477
478         /*
479          *      And send it on it's way.
480          */
481         return vqp_sendto(packet->sockfd, packet->data, packet->data_len, 0,
482                           &packet->src_ipaddr, &packet->dst_ipaddr,
483                           packet->dst_port);
484 }
485
486
487 int vqp_decode(RADIUS_PACKET *packet)
488 {
489         uint8_t *ptr, *end;
490         int attribute, length;
491         VALUE_PAIR *vp, **tail;
492
493         if (!packet || !packet->data) return -1;
494
495         if (packet->data_len < VQP_HDR_LEN) return -1;
496
497         tail = &packet->vps;
498
499         vp = paircreate(PW_VQP_PACKET_TYPE, PW_TYPE_OCTETS);
500         if (!vp) {
501                 librad_log("No memory");
502                 return -1;
503         }
504         vp->lvalue = packet->data[1];
505         debug_pair(vp);
506
507         *tail = vp;
508         tail = &(vp->next);
509
510         vp = paircreate(PW_VQP_ERROR_CODE, PW_TYPE_OCTETS);
511         if (!vp) {
512                 librad_log("No memory");
513                 return -1;
514         }
515         vp->lvalue = packet->data[2];
516         debug_pair(vp);
517
518         *tail = vp;
519         tail = &(vp->next);
520
521         vp = paircreate(PW_VQP_SEQUENCE_NUMBER, PW_TYPE_OCTETS);
522         if (!vp) {
523                 librad_log("No memory");
524                 return -1;
525         }
526         vp->lvalue = packet->id; /* already set by vqp_recv */
527         debug_pair(vp);
528
529         *tail = vp;
530         tail = &(vp->next);
531
532         ptr = packet->data + VQP_HDR_LEN;
533         end = packet->data + packet->data_len;
534
535         /*
536          *      Note that vqp_recv() MUST ensure that the packet is
537          *      formatted in a way we expect, and that vqp_recv() MUST
538          *      be called before vqp_decode().
539          */
540         while (ptr < end) {
541                 attribute = (ptr[2] << 8) | ptr[3];
542                 length = ptr[5];
543                 ptr += 6;
544
545                 /*
546                  *      Hack to get the dictionaries to work correctly.
547                  */
548                 attribute |= 0x2000;
549                 vp = paircreate(attribute, PW_TYPE_OCTETS);
550                 if (!vp) {
551                         pairfree(&packet->vps);
552
553                         librad_log("No memory");
554                         return -1;
555                 }
556
557                 switch (vp->type) {
558                 case PW_TYPE_IPADDR:
559                         if (length == 4) {
560                                 memcpy(&vp->vp_ipaddr, ptr, 4);
561                                 vp->length = 4;
562                                 break;
563                         }
564                         vp->type = PW_TYPE_OCTETS;
565                         /* FALL-THROUGH */
566
567                 default:
568                 case PW_TYPE_STRING:
569                 case PW_TYPE_OCTETS:
570                         memcpy(vp->vp_octets, ptr, length);
571                         vp->length = length;
572                         break;
573                 }
574                 ptr += length;
575                 debug_pair(vp);
576
577                 *tail = vp;
578                 tail = &(vp->next);
579         }
580
581         /*
582          *      FIXME: Map attributes to Calling-Station-Id, etc...
583          */
584
585         return 0;
586 }
587
588 /*
589  *      These are the MUST HAVE contents for a VQP packet.
590  *
591  *      We don't allow the caller to give less than these, because
592  *      it won't work.  We don't encode more than these, because the
593  *      clients will ignore it.
594  *
595  *      FIXME: Be more generous?  Look for CISCO + VQP attributes?
596  */
597 static int contents[5][VQP_MAX_ATTRIBUTES] = {
598         { 0,      0,      0,      0,      0,      0 },
599         { 0x0c01, 0x0c02, 0x0c03, 0x0c04, 0x0c07, 0x0c05 }, /* Join request */
600         { 0x0c03, 0x0c08, 0,      0,      0,      0 },  /* Join Response */
601         { 0x0c01, 0x0c02, 0x0c03, 0x0c04, 0x0c07, 0x0c08 }, /* Reconfirm */
602         { 0x0c03, 0x0c08, 0,      0,      0,      0 }
603 };
604
605 int vqp_encode(RADIUS_PACKET *packet, RADIUS_PACKET *original)
606 {
607         int i, code, length;
608         VALUE_PAIR *vp;
609         uint8_t *ptr;
610         VALUE_PAIR      *vps[VQP_MAX_ATTRIBUTES];
611
612         if (!packet) {
613                 librad_log("Failed encoding VQP");
614                 return -1;
615         }
616
617         if (packet->data) return 0;
618
619         vp = pairfind(packet->vps, PW_VQP_PACKET_TYPE);
620         if (!vp) {
621                 librad_log("Failed to find VQP-Packet-Type in response packet");
622                 return -1;
623         }
624
625         code = vp->lvalue;
626         if ((code < 1) || (code > 4)) {
627                 librad_log("Invalid value %d for VQP-Packet-Type", code);
628                 return -1;
629         }
630
631         length = VQP_HDR_LEN;
632         memset(vps, 0, sizeof(vps));
633
634         vp = pairfind(packet->vps, PW_VQP_ERROR_CODE);
635
636         /*
637          *      FIXME: Map attributes from calling-station-Id, etc.
638          *
639          *      Maybe do this via rlm_vqp?  That's probably the
640          *      best place to add the code...
641          */
642
643         /*
644          *      No error: encode attributes.
645          */
646         if (!vp) for (i = 0; i < VQP_MAX_ATTRIBUTES; i++) {
647                 if (!contents[code][i]) break;
648
649                 vps[i] = pairfind(packet->vps, contents[code][i] | 0x2000);
650
651                 /*
652                  *      FIXME: Print the name...
653                  */
654                 if (!vps[i]) {
655                         librad_log("Failed to find VQP attribute %02x",
656                                    contents[code][i]);
657                         return -1;
658                 }
659
660                 length += 6;
661                 length += vps[i]->length;
662         }
663
664         packet->data = malloc(length);
665         if (!packet->data) {
666                 librad_log("No memory");
667                 return -1;
668         }
669         packet->data_len = length;
670
671         ptr = packet->data;
672
673         ptr[0] = VQP_VERSION;
674         ptr[1] = code;
675
676         if (!vp) {
677                 ptr[2] = 0;
678         } else {
679                 ptr[2] = vp->lvalue & 0xff;
680                 return 0;
681         }
682
683         /*
684          *      The number of attributes is hard-coded.
685          */
686         if ((code == 1) || (code == 3)) {
687                 uint32_t sequence;
688
689                 ptr[3] = VQP_MAX_ATTRIBUTES;
690
691                 sequence = htonl(packet->id);
692                 memcpy(ptr + 4, &sequence, 4);
693         } else {
694                 if (!original) {
695                         librad_log("Cannot send VQP response without request");
696                         return -1;
697                 }
698
699                 /*
700                  *      Packet Sequence Number
701                  */
702                 memcpy(ptr + 4, original->data + 4, 4);
703
704                 ptr[3] = 2;
705         }
706
707         ptr += 8;
708
709         /*
710          *      Encode the VP's.
711          */
712         for (i = 0; i < VQP_MAX_ATTRIBUTES; i++) {
713                 if (!vps[i]) break;
714                 vp = vps[i];
715
716                 debug_pair(vp);
717
718                 /*
719                  *      Type.  Note that we look at only the lower 8
720                  *      bits, as the upper 8 bits have been hacked.
721                  *      See also dictionary.vqp
722                  */
723                 ptr[0] = 0;
724                 ptr[1] = 0;
725                 ptr[2] = 0x0c;
726                 ptr[3] = vp->attribute & 0xff;
727
728                 /* Length */
729                 ptr[4] = 0;
730                 ptr[5] = vp->length & 0xff;
731
732                 ptr += 6;
733
734                 /* Data */
735                 switch (vp->type) {
736                 case PW_TYPE_IPADDR:
737                         memcpy(ptr, &vp->vp_ipaddr, 4);
738                         break;
739
740                 default:
741                 case PW_TYPE_OCTETS:
742                 case PW_TYPE_STRING:
743                         memcpy(ptr, vp->vp_octets, vp->length);
744                         break;
745                 }
746                 ptr += vp->length;
747         }
748
749         return 0;
750 }
751 #endif