1 /* Copyright 2008 Bernhard R. Fischer, Daniel Haslinger.
2  *
3  * This file is part of OnionCat.
4  *
5  * OnionCat is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, version 3 of the License.
8  *
9  * OnionCat is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with OnionCat. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*! ocateth.c
19  *  Contains Ethernet (for TAP) and ICMPv6 (for NDP) code.
20  *
21  *  @author Bernhard Fischer <rahra _at_ cypherpunk at>
22  *  @version 2008/10/10
23  */
24 
25 
26 #include "ocat.h"
27 
28 
29 static MACTable_t mac_tbl_[MAX_MAC_ENTRY];
30 static int mac_cnt_ = 0;
31 static pthread_mutex_t mac_mutex_ = PTHREAD_MUTEX_INITIALIZER;
32 
33 
34 /*! Pseudo header for IPv6 checksum calculation.
35  *  RFC2460 8.1, (RFC1885 2.3) RFC2463, RFC1071. */
36 /* RFC2461, rfc2462, RFC2464 ipv6 ethernet enc.
37  * RFC2373 is obsoleted by RFC3513 addressing ipv6
38  * RFC2461 is obsoleted by RFC4861
39  * RFC4862 IPv6 Stateless Address Autoconfiguration
40  * RFC4443 ICMP6 (updates 2780, obsoletes 2463) (20101230)
41  */
42 
43 /* IPv6 Ethernet Multicast: (MAC) 33:33:xx:xx:xx:xx, xx -> 4 lowest order bytes of IPv6 destination
44  * Solicited-Node address: (IPv6) FF02:0:0:0:0:1:ffxx:xxxx, -> xx -> 3 lowest order bytes of IPv6 destination (RFC4291)
45  * IPv4 Ethernet Multicast: 01:00:5e:0xx:xx:xx, */
46 
47 
print_mac_tbl(FILE * f)48 void print_mac_tbl(FILE *f)
49 {
50    int i;
51    char buf[INET6_ADDRSTRLEN];
52 
53    //fprintf(f, "  # age MAC               C   address\n");
54    pthread_mutex_lock(&mac_mutex_);
55 
56    for (i = 0; i < mac_cnt_; i++)
57    {
58       fprintf(f, "%3d %3d %s ", i, (int) (time(NULL) - mac_tbl_[i].age), ether_ntoa_r((struct ether_addr*) mac_tbl_[i].hwaddr, buf));
59       fprintf(f, "%s ", mac_tbl_[i].family == AF_INET6 ? "IN6" : "IN ");
60       inet_ntop(mac_tbl_[i].family, &mac_tbl_[i].in6addr, buf, INET6_ADDRSTRLEN);
61       fprintf(f, "%s\n", buf);
62    }
63 
64    pthread_mutex_unlock(&mac_mutex_);
65 }
66 
67 
68 /*! Scan MAC table for outages (age > MAX_MAC_AGE) and remove entries.
69  */
mac_cleanup(void)70 void mac_cleanup(void)
71 {
72    int i;
73 #ifdef DEBUG
74    char hw[20];
75 #endif
76 
77    pthread_mutex_lock(&mac_mutex_);
78 
79    for (i = 0; i < mac_cnt_; i++)
80       if (mac_tbl_[i].age + MAX_MAC_AGE < time(NULL))
81       {
82          log_debug("mac table entry %d (%s) timed out", i, ether_ntoa_r((struct ether_addr*) mac_tbl_[i].hwaddr, hw));
83          memmove(&mac_tbl_[i], &mac_tbl_[i + 1], sizeof(MACTable_t) * (MAX_MAC_ENTRY - i));
84          mac_cnt_--;
85          i--;
86       }
87 
88    pthread_mutex_unlock(&mac_mutex_);
89 }
90 
91 
92 /*! Lookup an entry in the MAC-table by IP, update age.
93  *  If hwaddr != NULL and MAC eq 00:00:00:00:00:00 then copy MAC entry
94  *  from MAC table to hwaddr, otherwise copy hwaddr to MAC table.
95  *  @return -1 if no entry available, otherwise index of entry in table starting with 0.
96  */
mac_set(const struct in6_addr * in6,uint8_t * hwaddr)97 int mac_set(const struct in6_addr *in6, uint8_t *hwaddr)
98 {
99    int i;
100 
101    pthread_mutex_lock(&mac_mutex_);
102 
103    for (i = mac_cnt_ - 1; i >= 0; i--)
104       if (IN6_ARE_ADDR_EQUAL(in6, &mac_tbl_[i].in6addr))
105       {
106          if (hwaddr)
107          {
108             if (!hwaddr[0] && !hwaddr[1] && !hwaddr[2] && !hwaddr[3] && !hwaddr[4] && !hwaddr[5])
109                memcpy(hwaddr, &mac_tbl_[i].hwaddr, ETHER_ADDR_LEN);
110             else
111                memcpy(&mac_tbl_[i].hwaddr, hwaddr, ETHER_ADDR_LEN);
112          }
113          mac_tbl_[i].age = time(NULL);
114          break;
115       }
116 
117    pthread_mutex_unlock(&mac_mutex_);
118 
119    return i;
120 }
121 
122 
123 /*! Wrapper function for mac_set() (see above) to keep valid pointer alignment. */
mac_set_s(struct in6_addr in6,uint8_t * hwaddr)124 int mac_set_s(struct in6_addr in6, uint8_t *hwaddr)
125 {
126    return mac_set(&in6, hwaddr);
127 }
128 
129 
130 /*! Add MAC/IPv6-pair to MAC table.
131  *  @param hwaddr MAC address.
132  *  @param in6 IPv6 address.
133  *  @return Index of entry (starting with 0) or -1 if MAC table is full (MAX_MAC_ENTRY)
134  */
mac_add_entry(const uint8_t * hwaddr,struct in6_addr in6)135 int mac_add_entry(const uint8_t *hwaddr, struct in6_addr in6)
136 {
137    int e = -1;
138 
139    pthread_mutex_lock(&mac_mutex_);
140 
141    if (mac_cnt_ < MAX_MAC_ENTRY)
142    {
143       log_debug("adding entry to MAC table %d", mac_cnt_);
144       memcpy(&mac_tbl_[mac_cnt_].hwaddr, hwaddr, ETHER_ADDR_LEN);
145       IN6_ADDR_COPY(&mac_tbl_[mac_cnt_].in6addr, &in6);
146       mac_tbl_[mac_cnt_].age = time(NULL);
147       mac_tbl_[mac_cnt_].family = AF_INET6;
148       e = mac_cnt_++;
149    }
150 
151    pthread_mutex_unlock(&mac_mutex_);
152 
153    return e;
154 }
155 
156 
157 /*! Lookup entry by MAC address in MAC-table. It returns the first
158  *  occurence and updates the age.
159  *  @param hwaddr MAC address to search for.
160  *  @param in6 If not NULL, this buffer is filled with the IPv6 address.
161  *  @return Index of entry or -1 if no entry exists.
162  */
mac_get_ip(const uint8_t * hwaddr,struct in6_addr * in6)163 int mac_get_ip(const uint8_t *hwaddr, struct in6_addr *in6)
164 {
165    int i;
166 
167    pthread_mutex_lock(&mac_mutex_);
168 
169    for (i = mac_cnt_ - 1; i >= 0; i--)
170       if (!memcmp(hwaddr, &mac_tbl_[i].hwaddr, ETHER_ADDR_LEN))
171       {
172          if (in6)
173             memcpy(in6, &mac_tbl_[i].in6addr, sizeof(struct in6_addr));
174          mac_tbl_[i].age = time(NULL);
175          break;
176       }
177 
178    pthread_mutex_unlock(&mac_mutex_);
179 
180    return i;
181 }
182 
183 
184 /*! Calculate 16 bit one's complement checksum (RFC1071) suitable for ICMPv6.
185  *  @param buf Pointer to buffer.
186  *  @param len Number of bytes in buffer.
187  *  @return Checksum of buffer.
188  */
checksum(const uint16_t * buf,int len)189 uint16_t checksum(const uint16_t *buf, int len)
190 {
191    uint32_t sum = 0;
192 
193    // sum up all 16 bit words
194    // (Note that it's endiness independent)
195    for (; len > 1 ; len -= 2, buf++)
196       sum += *buf;
197 
198    // add last byte if buffer has odd length
199    if (len)
200       sum += *((uint8_t*) buf);
201 
202    // add carries
203    while (sum >> 16)
204       sum = (sum & 0xffff) + (sum >> 16);
205 
206    // return complement
207    return ~sum;
208 }
209 
210 
211 /*! Free checksum buffer.
212  */
free_ckbuf(uint16_t * buf)213 void free_ckbuf(uint16_t *buf)
214 {
215    free(buf);
216 }
217 
218 
219 /*! Malloc and fill buffer suitable for ICMPv6 checksum calculation.
220  */
malloc_ckbuf(struct in6_addr src,struct in6_addr dst,uint16_t plen,uint8_t proto,const void * payload)221 uint16_t *malloc_ckbuf(struct in6_addr src, struct in6_addr dst, uint16_t plen, uint8_t proto, const void *payload)
222 {
223    struct ip6_psh *psh;
224    uint16_t *psh_u16;
225 
226    if (!(psh_u16 = calloc(1, sizeof(struct ip6_psh) + plen)))
227    {
228       log_msg(LOG_EMERG, "error creating checksum buffer: %s", strerror(errno));
229       //return NULL;
230       exit(1);
231    }
232 
233    psh = (struct ip6_psh*) psh_u16;
234    psh->src = src;
235    psh->dst = dst;
236    psh->len = htons(plen);
237    psh->nxt = proto;
238    memcpy(psh + 1, payload, plen);
239 
240    return psh_u16;
241 }
242 
243 
244 /*! Send NDP solicitation for dst to appropriate IPv6 multicast address.
245  *  @param src Source address.
246  *  @param dst Solicited target address.
247  *  @return Returns always 0.
248  */
ndp_solicit(const struct in6_addr * src,const struct in6_addr * dst)249 int ndp_solicit(const struct in6_addr *src, const struct in6_addr *dst)
250 {
251    char buf[sizeof(ndp6_t) + sizeof(struct nd_opt_hdr) + 4 + ETHER_ADDR_LEN];
252    ndp6_t *ndp6 = (ndp6_t*) (buf + 4);
253    struct nd_opt_hdr *ohd = (struct nd_opt_hdr*) (ndp6 + 1);
254    uint16_t *ckb;
255    struct in6_addr mcastd = {{{0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0xff, 0, 0, 0}}};
256 
257    // clear buffer and setup ipv6 multicast destination
258    memset(buf, 0, sizeof(buf));
259    memcpy(((char*) &mcastd) + 13, ((char*) dst) + 13, 3);
260 
261    // tunnel header
262    set_tunheader(buf, htonl(CNF(fhd_key[IPV6_KEY])));
263 
264    // ethernet header
265    ndp6->eth.ether_dst[0] = 0x33;
266    ndp6->eth.ether_dst[1] = 0x33;
267    memcpy(&ndp6->eth.ether_dst[2], ((char*) &mcastd) + 12, 4);
268    memcpy(ndp6->eth.ether_src, CNF(ocat_hwaddr), ETHER_ADDR_LEN);
269    ndp6->eth.ether_type = htons(ETHERTYPE_IPV6);
270 
271    // ipv6 header
272    ndp6->ip6.ip6_vfc = 0x60;
273    ndp6->ip6.ip6_plen = htons(sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + ETHER_ADDR_LEN);
274    ndp6->ip6.ip6_nxt = IPPROTO_ICMPV6;
275    ndp6->ip6.ip6_hlim = 255;
276    memcpy(&ndp6->ip6.ip6_src, src, sizeof(struct in6_addr));
277    memcpy(&ndp6->ip6.ip6_dst, &mcastd, sizeof(struct in6_addr));
278 
279    // icmpv6 header (partially)
280    ndp6->icmp6.icmp6_type = ND_NEIGHBOR_SOLICIT;
281 
282    // ndp solicit header
283    memcpy(&ndp6->ndp_sol.nd_ns_target, dst, sizeof(struct in6_addr));
284 
285    // icmpv6 ndp option
286    ohd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
287    ohd->nd_opt_len = 1;
288    memcpy(ohd + 1, ndp6->eth.ether_src, ETHER_ADDR_LEN);
289 
290    // calculate checksum
291    ckb = malloc_ckbuf(ndp6->ip6.ip6_src, ndp6->ip6.ip6_dst, ntohs(ndp6->ip6.ip6_plen), IPPROTO_ICMPV6, &ndp6->icmp6);
292    ndp6->icmp6.icmp6_cksum = checksum(ckb, ntohs(ndp6->ip6.ip6_plen) + sizeof(struct ip6_psh));
293    free_ckbuf(ckb);
294 
295 #ifdef __CYGWIN__
296    log_debug("writing %d bytes ndp solicitation to TAP driver", sizeof(buf) - 4);
297    // FIXME: there's no error checking
298    win_write_tun(buf + 4, sizeof(buf) - 4);
299 #else
300    log_debug("writing %d bytes ndp solicitation to tunfd %d", sizeof(buf), CNF(tunfd[1]));
301    if (write(CNF(tunfd[1]), buf, sizeof(buf)) < sizeof(buf))
302       log_msg(LOG_ERR, "short write to tun fd %d", CNF(tunfd[1]));
303 #endif
304 
305    return 0;
306 }
307 
308 
309 /*! Wrapper function for macro IN6_IS_ADDR_MULTICAST to keep valid pointer alignment. */
IN6_IS_ADDR_MULTICAST_S(struct in6_addr a)310 static int IN6_IS_ADDR_MULTICAST_S(struct in6_addr a)
311 {
312    return IN6_IS_ADDR_MULTICAST(&a);
313 }
314 
315 
316 /*! Wrapper function for macro IN6_IS_ADDR_MC_LINKLOCAL to keep valid pointer alignment. */
IN6_IS_ADDR_MC_LINKLOCAL_S(struct in6_addr a)317 static int IN6_IS_ADDR_MC_LINKLOCAL_S(struct in6_addr a)
318 {
319    return IN6_IS_ADDR_MC_LINKLOCAL(&a);
320 }
321 
322 
323 /*! Wrapper function for macro IN6_IS_ADDR_UNSPECIFIED_S to keep valid pointer alignment. */
IN6_IS_ADDR_UNSPECIFIED_S(struct in6_addr a)324 static int IN6_IS_ADDR_UNSPECIFIED_S(struct in6_addr a)
325 {
326    return IN6_IS_ADDR_UNSPECIFIED(&a);
327 }
328 
329 
330 /*! Check neighbor solicitation and generate advertisement.
331  *  @param buf pointer to frame buffer.
332  *  @param rlen buffer length, must be at least sizeof(ICMPv6 header) + 4.
333  *  @return 0 if everything gone write, -1 on failure.
334  */
ndp_soladv(char * buf,int rlen)335 int ndp_soladv(char *buf, int rlen)
336 {
337    ndp6_t *ndp6 = (ndp6_t*) (buf + 4);
338    struct nd_opt_hdr *ohd = (struct nd_opt_hdr*) (ndp6 + 1);
339    uint16_t *ckb, cksum;
340 #ifdef DEBUG
341    char hw[20];
342 #endif
343 
344    if (ndp6->eth.ether_dst[0] & 1)
345    {
346       // check for right multicast destination on ethernet
347       if (ndp6->eth.ether_dst[2] != 0xff)
348       {
349          log_debug("ethernet multicast destination %s cannot be solicited node address", ether_ntoa_r((struct ether_addr*) ndp6->eth.ether_dst, hw));
350          return -1;
351       }
352 
353       // check for right multicast destination in IPv6
354       if (!IN6_IS_ADDR_MULTICAST_S(ndp6->ip6.ip6_dst) || !IN6_IS_ADDR_MC_LINKLOCAL_S(ndp6->ip6.ip6_dst))
355       {
356          log_debug("IPv6 multicast destination not solicited node address");
357          return -1;
358       }
359    }
360 
361    ckb = malloc_ckbuf(ndp6->ip6.ip6_src, ndp6->ip6.ip6_dst, ntohs(ndp6->ip6.ip6_plen), IPPROTO_ICMPV6, &ndp6->icmp6);
362    cksum = checksum(ckb, ntohs(ndp6->ip6.ip6_plen) + sizeof(struct ip6_psh));
363    free_ckbuf(ckb);
364 
365    if (cksum)
366    {
367       log_msg(LOG_ERR, "icmpv6 checksum wrong");
368       return -1;
369    }
370 
371    // check for duplicate address detection
372    if (IN6_IS_ADDR_UNSPECIFIED_S(ndp6->ip6.ip6_src))
373    {
374       log_debug("duplicate address detection in progress");
375       //FIXME: we should check something more here. See RFC2462
376       return -1;
377    }
378 
379    struct in6_addr _nst;
380    IN6_ADDR_COPY(&_nst, &ndp6->ndp_sol.nd_ns_target);
381    if (!has_tor_prefix(&_nst))
382    //if (!IN6_HAS_TOR_PREFIX(&ndp6->ndp_sol.nd_ns_target))
383    {
384       log_debug("solicit target is not TOR IPv6");
385       return -1;
386    }
387 
388    log_debug("generating response");
389    // add source MAC to table
390    if (mac_set_s(ndp6->ip6.ip6_src, ndp6->eth.ether_src) == -1)
391       if (mac_add_entry(ndp6->eth.ether_src, ndp6->ip6.ip6_src) == -1)
392       {
393          log_msg(LOG_ERR, "MAC table full");
394          return -1;
395       }
396 
397    // set MAC addresses for response
398    memcpy(ndp6->eth.ether_dst, ndp6->eth.ether_src, ETHER_ADDR_LEN);
399    memcpy(ndp6->eth.ether_src, CNF(ocat_hwaddr), ETHER_ADDR_LEN);
400 
401    // init ip6 header
402    memcpy(&ndp6->ip6.ip6_dst, &ndp6->ip6.ip6_src, sizeof(struct in6_addr));
403    memcpy(&ndp6->ip6.ip6_src, &ndp6->ndp_sol.nd_ns_target, sizeof(struct in6_addr));
404 
405    // init nda icmp6 header
406    ndp6->ndp_adv.nd_na_hdr.icmp6_type = ND_NEIGHBOR_ADVERT;
407    ndp6->ndp_adv.nd_na_hdr.icmp6_code = 0;
408    ndp6->ndp_adv.nd_na_hdr.icmp6_cksum = 0;
409    ndp6->ndp_adv.nd_na_flags_reserved = ND_NA_FLAG_SOLICITED;
410 
411    //FIXME: setting target option does not check total frame length!
412    ohd->nd_opt_type = ND_OPT_TARGET_LINKADDR;
413    memcpy(ohd + 1, CNF(ocat_hwaddr), ETHER_ADDR_LEN);
414 
415    ckb = malloc_ckbuf(ndp6->ip6.ip6_src, ndp6->ip6.ip6_dst, ntohs(ndp6->ip6.ip6_plen), IPPROTO_ICMPV6, &ndp6->icmp6);
416    ndp6->ndp_adv.nd_na_hdr.icmp6_cksum = checksum(ckb, ntohs(ndp6->ip6.ip6_plen) + sizeof(struct ip6_psh));
417    free_ckbuf(ckb);
418 
419 #ifdef __CYGWIN__
420    log_debug("writing %d bytes to TAP driver", rlen);
421    // FIXME: there's no error checking
422    win_write_tun(buf + 4, rlen - 4);
423 #else
424    log_debug("writing %d bytes to tunfd %d", rlen, CNF(tunfd[1]));
425    if (write(CNF(tunfd[1]), buf, rlen) < rlen)
426       log_msg(LOG_ERR, "short write");
427 #endif
428 
429    return 0;
430 }
431 
432 
433 /*! Extract source ipv6 and MAC address and add/update MAC table.
434  *  FIXME: there should be some additional checks!
435  */
ndp_recadv(char * buf,int len)436 int ndp_recadv(char *buf, int len)
437 {
438    ndp6_t *ndp6 = (ndp6_t*) (buf + 4);
439 
440    // add source MAC to table
441    if (mac_set_s(ndp6->ip6.ip6_src, ndp6->eth.ether_src) == -1)
442       if (mac_add_entry(ndp6->eth.ether_src, ndp6->ip6.ip6_src) == -1)
443       {
444          log_msg(LOG_ERR, "MAC table full");
445          return -1;
446       }
447    return 0;
448 }
449 
450 
eth_ndp(char * buf,int len,int ndp_type)451 int eth_ndp(char *buf, int len, int ndp_type)
452 {
453    switch (ndp_type)
454    {
455       case ND_NEIGHBOR_SOLICIT:
456          log_debug("ND_NEIGHBOR_SOLICIT received");
457          (void) ndp_soladv(buf, len);
458          return 0;
459 
460       case ND_NEIGHBOR_ADVERT:
461          log_debug("ND_NEIGHBOR_ADVERT received");
462          (void) ndp_recadv(buf, len);
463          return 0;
464    }
465    return -1;
466 }
467 
468 
469 /*! Check if destination MAC is designated for OnionCat and
470  *  L4-Protocol is ICMPv6.
471  *  @return 0 if packet does not match criteria, -1 else.
472  */
eth_check(char * buf,int len)473 int eth_check(char *buf, int len)
474 {
475    ndp6_t *ndp6= (ndp6_t*) (buf + 4);
476 
477    // check minimum frame length
478    if (len < sizeof(struct ether_header) + 4)
479    {
480       log_msg(LOG_ERR, "frame too short, len = %d < 4 + %d", len, sizeof(struct ether_header));
481       return E_ETH_TRUNC;
482    }
483 
484    // check ethernet destination
485    if ((ndp6->eth.ether_dst[0] != 0x33) && (ndp6->eth.ether_dst[1] != 0x33) && memcmp(ndp6->eth.ether_dst, CNF(ocat_hwaddr), ETHER_ADDR_LEN))
486    {
487       log_debug("unknown destination MAC");
488       return E_ETH_ILLDEST;
489    }
490 
491    // check L3 protocol
492    if (ndp6->eth.ether_type != htons(ETHERTYPE_IPV6))
493    {
494       log_msg(LOG_ERR, "L3 protocol not implemented 0x%04x", ntohs(ndp6->eth.ether_type));
495       return E_ETH_ILLPROTO;
496    }
497 
498    // check for ndp
499    if ((len >= sizeof(ndp6_t) + 4) && (ndp6->ip6.ip6_nxt == IPPROTO_ICMPV6))
500    {
501       log_debug("ICMPv6 frame intercepted, icmp6_type = %d", ndp6->icmp6.icmp6_type);
502       if (eth_ndp(buf, len, ndp6->icmp6.icmp6_type) != -1)
503          return E_ETH_INTERCEPT;
504    }
505 
506    // else forward as usual
507    return 0;
508 }
509 
510 
511 #ifndef HAVE_ETHER_NTOA
512 
513 #define ETHER_ADDR_BUF_SIZE 18
514 static char ether_addr_buf_[ETHER_ADDR_BUF_SIZE];
515 
ether_ntoa(const struct ether_addr * addr)516 char *ether_ntoa(const struct ether_addr *addr)
517 {
518    snprintf(ether_addr_buf_, ETHER_ADDR_BUF_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x",
519          addr->ether_addr_octet[0], addr->ether_addr_octet[1], addr->ether_addr_octet[2],
520          addr->ether_addr_octet[3], addr->ether_addr_octet[4], addr->ether_addr_octet[5]);
521    return ether_addr_buf_;
522 }
523 
524 #endif
525 
526 
527 #ifndef HAVE_ETHER_NTOA_R
528 
529 static pthread_mutex_t ether_ntoa_mutex_ = PTHREAD_MUTEX_INITIALIZER;
530 
ether_ntoa_r(const struct ether_addr * addr,char * buf)531 char *ether_ntoa_r(const struct ether_addr *addr, char *buf)
532 {
533    if (!buf)
534       return NULL;
535 
536    pthread_mutex_lock(&ether_ntoa_mutex_);
537    strlcpy(buf, ether_ntoa((struct ether_addr*) addr), 18);
538    pthread_mutex_unlock(&ether_ntoa_mutex_);
539    return buf;
540 }
541 
542 #endif
543 
544