]> Pileus Git - ~andy/fetchmail/blob - interface.c
Before trying to fix the no-reset bug.
[~andy/fetchmail] / interface.c
1 /*
2  * interface.c -- implements fetchmail 'interface' and 'monitor' commands
3  *
4  * This module was implemented by George M. Sipe <gsipe@pobox.com>
5  * or <gsipe@acm.org> and is:
6  *
7  *      Copyright (c) 1996,1997 by George M. Sipe
8  *
9  *      FreeBSD specific portions written by and Copyright (c) 1999 
10  *      Andy Doran <ad@psn.ie>.
11  *
12  * This is free software; you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free
14  * Software Foundation; version 2, or (at your option) any later version.
15  */
16 #include <sys/types.h>
17 #include <sys/param.h>
18
19 #if (defined(linux) && !defined(INET6_ENABLE)) || defined(__FreeBSD__)
20
21 #include "config.h"
22 #include <stdio.h>
23 #include <string.h>
24 #if defined(STDC_HEADERS)
25 #include <stdlib.h>
26 #endif
27 #if defined(HAVE_UNISTD_H)
28 #include <unistd.h>
29 #endif
30 #include <sys/ioctl.h>
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34 #include <net/if.h>
35 #if defined(__FreeBSD__)
36 #if defined __FreeBSD_USE_KVM
37     #if __FreeBSD_version >= 300001
38         #include <net/if_var.h>
39     #endif
40     #include <kvm.h>
41     #include <nlist.h>
42     #include <sys/fcntl.h>
43 #else
44     #include <sys/sysctl.h>
45     #include <net/route.h>
46     #include <net/if_dl.h>
47 #endif
48 #endif
49 #include "config.h"
50 #include "fetchmail.h"
51 #include "socket.h"
52 #include "i18n.h"
53 #include "tunable.h"
54
55 typedef struct {
56         struct in_addr addr, dstaddr, netmask;
57         int rx_packets, tx_packets;
58 } ifinfo_t;
59
60 struct interface_pair_s {
61         struct in_addr interface_address;
62         struct in_addr interface_mask;
63 } *interface_pair;
64
65 static char *netdevfmt;
66
67 /*
68  * Count of packets to see on an interface before monitor considers it up.
69  * Needed because when pppd shuts down the link, the packet counts go up
70  * by two (one rx and one tx?, maybe).  A value of 2 seems to do the trick,
71  * but we'll give it some extra.
72  */
73 #define MONITOR_SLOP            5
74
75 #if defined(linux)
76
77 void interface_init(void)
78 /* figure out which /proc/dev/net format to use */
79 {
80     FILE *fp = popen("uname -r", "r");  /* still wins if /proc is out */
81
82     /* pre-linux-2.2 format -- transmit packet count in 8th field */
83     netdevfmt = "%d %d %*d %*d %*d %d %*d %d %*d %*d %*d %*d %d";
84
85     if (!fp)
86         return;
87     else
88     {
89         int major, minor;
90
91         if (fscanf(fp, "%d.%d.%*d", &major, &minor) >= 2
92                                         && major >= 2 && minor >= 2)
93             /* Linux 2.2 -- transmit packet count in 10th field */
94             netdevfmt = "%d %d %*d %*d %*d %d %*d %*d %*d %d %*d %*d %d";
95         pclose(fp);
96     }
97 }
98
99 static int _get_ifinfo_(int socket_fd, FILE *stats_file, const char *ifname,
100                 ifinfo_t *ifinfo)
101 /* get active network interface information - return non-zero upon success */
102 {
103         int namelen = strlen(ifname);
104         struct ifreq request;
105         char *cp, buffer[256];
106         int found = 0;
107         int counts[4];
108
109         /* initialize result */
110         memset((char *) ifinfo, 0, sizeof(ifinfo_t));
111
112         /* get the packet I/O counts */
113         while (fgets(buffer, sizeof(buffer) - 1, stats_file)) {
114                 for (cp = buffer; *cp && *cp == ' '; ++cp);
115                 if (!strncmp(cp, ifname, namelen) &&
116                                 cp[namelen] == ':') {
117                         cp += namelen + 1;
118                         if (sscanf(cp, netdevfmt,
119                                    counts, counts+1, counts+2, 
120                                    counts+3,&found)>4) { /* found = dummy */
121                                 /* newer kernel with byte counts */
122                                 ifinfo->rx_packets=counts[1];
123                                 ifinfo->tx_packets=counts[3];
124                         } else {
125                                 /* older kernel, no byte counts */
126                                 ifinfo->rx_packets=counts[0];
127                                 ifinfo->tx_packets=counts[2];
128                         }
129                         found = 1;
130                 }
131         }
132         if (!found) return (FALSE);
133
134         /* see if the interface is up */
135         strcpy(request.ifr_name, ifname);
136         if (ioctl(socket_fd, SIOCGIFFLAGS, &request) < 0)
137                 return(FALSE);
138         if (!(request.ifr_flags & IFF_RUNNING))
139                 return(FALSE);
140
141         /* get the (local) IP address */
142         strcpy(request.ifr_name, ifname);
143         if (ioctl(socket_fd, SIOCGIFADDR, &request) < 0)
144                 return(FALSE);
145         ifinfo->addr = ((struct sockaddr_in *) (&request.ifr_addr))->sin_addr;
146
147         /* get the PPP destination (remote) IP address */
148         ifinfo->dstaddr.s_addr = 0;
149         strcpy(request.ifr_name, ifname);
150         if (ioctl(socket_fd, SIOCGIFDSTADDR, &request) >= 0)
151                 ifinfo->dstaddr = ((struct sockaddr_in *)
152                                         (&request.ifr_dstaddr))->sin_addr;
153
154         /* get the netmask */
155         strcpy(request.ifr_name, ifname);
156         if (ioctl(socket_fd, SIOCGIFNETMASK, &request) >= 0) {
157           ifinfo->netmask = ((struct sockaddr_in *)
158                              (&request.ifr_netmask))->sin_addr;
159           return (TRUE);
160         }
161
162         return(FALSE);
163 }
164
165 static int get_ifinfo(const char *ifname, ifinfo_t *ifinfo)
166 {
167         int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
168         FILE *stats_file = fopen("/proc/net/dev", "r");
169         int result;
170
171         if (socket_fd < 0 || !stats_file)
172                 result = FALSE;
173         else
174         {
175             char        *sp = strchr(ifname, '/');
176
177             if (sp)
178                 *sp = '\0';
179             result = _get_ifinfo_(socket_fd, stats_file, ifname, ifinfo);
180             if (sp)
181                 *sp = '/';
182         }
183         if (socket_fd >= 0)
184             SockClose(socket_fd);
185         if (stats_file)
186             fclose(stats_file); /* not checking should be safe, mode was "r" */
187         return(result);
188 }
189
190 #elif defined __FreeBSD__
191
192 #if defined __FreeBSD_USE_KVM
193
194 static kvm_t *kvmfd;
195 static struct nlist symbols[] = 
196 {
197         {"_ifnet"},
198         {NULL}
199 };
200 static u_long   ifnet_savedaddr;
201 static gid_t    if_rgid;
202 static gid_t    if_egid;
203
204 void 
205 interface_set_gids(gid_t egid, gid_t rgid)
206 {
207         if_rgid = rgid;
208         if_egid = egid;
209 }
210
211 static int 
212 openkvm(void)
213 {
214         if ((kvmfd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) == NULL)
215                 return FALSE;
216         
217         if (kvm_nlist(kvmfd, symbols) < 0)
218                 return FALSE;
219            
220         if (kvm_read(kvmfd, (unsigned long) symbols[0].n_value, &ifnet_savedaddr, sizeof(unsigned long)) == -1)
221                 return FALSE;
222                 
223         return TRUE;
224 }
225
226 static int 
227 get_ifinfo(const char *ifname, ifinfo_t *ifinfo)
228 {
229         char                    tname[16];
230         char                    iname[16];
231         struct ifnet            ifnet;
232         unsigned long           ifnet_addr = ifnet_savedaddr;
233 #if __FreeBSD_version >= 300001
234         struct ifnethead        ifnethead;
235         struct ifaddrhead       ifaddrhead;
236 #endif
237         struct ifaddr           ifaddr;
238         unsigned long           ifaddr_addr;
239         struct sockaddr         sa;
240         unsigned long           sa_addr;
241         uint                    i;
242         
243         if (if_egid)
244                 setegid(if_egid);
245         
246         for (i = 0; ifname[i] && ifname[i] != '/'; i++)
247                 iname[i] = ifname[i];
248                 
249         iname[i] = '\0';
250         
251         if (!kvmfd)
252         {
253                 if (!openkvm())
254                 {
255                         report(stderr, 0, _("Unable to open kvm interface. Make sure fetchmail is SGID kmem."));
256                         if (if_egid)
257                                 setegid(if_rgid);
258                         exit(1);
259                 }
260         }
261
262 #if __FreeBSD_version >= 300001
263         kvm_read(kvmfd, ifnet_savedaddr, (char *) &ifnethead, sizeof ifnethead);
264         ifnet_addr = (u_long) ifnethead.tqh_first;
265 #else
266         ifnet_addr = ifnet_savedaddr;
267 #endif
268
269         while (ifnet_addr)
270         {
271                 kvm_read(kvmfd, ifnet_addr, &ifnet, sizeof(ifnet));
272                 kvm_read(kvmfd, (unsigned long) ifnet.if_name, tname, sizeof tname);
273                 snprintf(tname, sizeof tname - 1, "%s%d", tname, ifnet.if_unit);
274
275                 if (!strcmp(tname, iname))
276                 {
277                         if (!(ifnet.if_flags & IFF_UP))
278                         {
279                                 if (if_egid)
280                                         setegid(if_rgid);
281                                 return 0;
282                         }
283                                 
284                         ifinfo->rx_packets = ifnet.if_ipackets;
285                         ifinfo->tx_packets = ifnet.if_opackets;
286
287 #if __FreeBSD_version >= 300001
288                         ifaddr_addr = (u_long) ifnet.if_addrhead.tqh_first;
289 #else
290                         ifaddr_addr = (u_long) ifnet.if_addrlist;
291 #endif
292                         
293                         while(ifaddr_addr)
294                         {
295                                 kvm_read(kvmfd, ifaddr_addr, &ifaddr, sizeof(ifaddr));
296                                 kvm_read(kvmfd, (u_long)ifaddr.ifa_addr, &sa, sizeof(sa));
297                                 
298                                 if (sa.sa_family != AF_INET)
299                                 {
300 #if __FreeBSD_version >= 300001
301                                         ifaddr_addr = (u_long) ifaddr.ifa_link.tqe_next;
302 #else
303                                         ifaddr_addr = (u_long) ifaddr.ifa_next;
304 #endif
305                                         continue;
306                                 }
307                         
308                                 ifinfo->addr.s_addr = *(u_long *)(sa.sa_data + 2);
309                                 kvm_read(kvmfd, (u_long)ifaddr.ifa_dstaddr, &sa, sizeof(sa));
310                                 ifinfo->dstaddr.s_addr = *(u_long *)(sa.sa_data + 2);
311                                 kvm_read(kvmfd, (u_long)ifaddr.ifa_netmask, &sa, sizeof(sa));
312                                 ifinfo->netmask.s_addr = *(u_long *)(sa.sa_data + 2);
313
314                                 if (if_egid)
315                                         setegid(if_rgid);
316
317                                 return 1;
318                         }
319                         
320                         if (if_egid)
321                                 setegid(if_rgid);
322                         
323                         return 0;
324                 }
325
326 #if __FreeBSD_version >= 300001
327                 ifnet_addr = (u_long) ifnet.if_link.tqe_next;
328 #else
329                 ifnet_addr = (unsigned long) ifnet.if_next;
330 #endif
331         }
332
333         if (if_egid)
334                 setegid(if_rgid);
335         
336         return 0;
337 }
338
339 #else /* Do not use KVM on FreeBSD */
340
341 /*
342  * Expand the compacted form of addresses as returned via the
343  * configuration read via sysctl().
344  */
345
346 static void
347 rt_xaddrs(caddr_t cp, caddr_t cplim, struct rt_addrinfo *rtinfo)
348 {
349     struct sockaddr *sa;
350     int i;
351
352 #define ROUNDUP(a) \
353         ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
354 #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
355
356     memset(rtinfo->rti_info, 0, sizeof(rtinfo->rti_info));
357     for (i = 0; (i < RTAX_MAX) && (cp < cplim); i++) {
358         if ((rtinfo->rti_addrs & (1 << i)) == 0)
359             continue;
360         rtinfo->rti_info[i] = sa = (struct sockaddr *)cp;
361         ADVANCE(cp, sa);
362     }
363
364 #undef ROUNDUP
365 #undef ADVANCE
366 }
367
368 static int
369 get_ifinfo(const char *ifname, ifinfo_t *ifinfo)
370 {
371     uint                i;
372     int                 rc = 0;
373     int                 ifindex = -1;
374     size_t              needed;
375     char                *buf = NULL;
376     char                *lim = NULL;
377     char                *next = NULL;
378     struct if_msghdr    *ifm;
379     struct ifa_msghdr   *ifam;
380     struct sockaddr_in  *sin;
381     struct sockaddr_dl  *sdl;
382     struct rt_addrinfo  info;
383     char                iname[16];
384     int                 mib[6];
385
386     memset(ifinfo, 0, sizeof(ifinfo));
387
388     /* trim interface name */
389
390     for (i = 0; i < sizeof(iname) && ifname[i] && ifname[i] != '/'; i++)
391         iname[i] = ifname[i];
392         
393     if (i == 0 || i == sizeof(iname))
394     {
395         report(stderr, _("Unable to parse interface name from %s"), ifname);
396         return 0;
397     }
398
399     iname[i] = 0;
400
401
402     /* get list of existing interfaces */
403
404     mib[0] = CTL_NET;
405     mib[1] = PF_ROUTE;
406     mib[2] = 0;
407     mib[3] = AF_INET;           /* Only IP addresses please. */
408     mib[4] = NET_RT_IFLIST;
409     mib[5] = 0;                 /* List all interfaces. */
410
411
412     /* Get interface data. */
413
414     if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1)
415     {
416         report(stderr, 
417             _("get_ifinfo: sysctl (iflist estimate) failed"));
418         exit(1);
419     }
420     if ((buf = malloc(needed)) == NULL)
421     {
422         report(stderr, 
423             _("get_ifinfo: malloc failed"));
424         exit(1);
425     }
426     if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1)
427     {
428         report(stderr, 
429             _("get_ifinfo: sysctl (iflist) failed"));
430         exit(1);
431     }
432
433     lim = buf+needed;
434
435
436     /* first look for the interface information */
437
438     next = buf;
439     while (next < lim)
440     {
441         ifm = (struct if_msghdr *)next;
442         next += ifm->ifm_msglen;
443
444         if (ifm->ifm_version != RTM_VERSION) 
445         {
446             report(stderr, 
447                 _("Routing message version %d not understood."),
448                 ifm->ifm_version);
449             exit(1);
450         }
451
452         if (ifm->ifm_type == RTM_IFINFO)
453         {
454             sdl = (struct sockaddr_dl *)(ifm + 1);
455
456             if (!(strlen(iname) == sdl->sdl_nlen 
457                 && strncmp(iname, sdl->sdl_data, sdl->sdl_nlen) == 0))
458             {
459                 continue;
460             }
461
462             if ( !(ifm->ifm_flags & IFF_UP) )
463             {
464                 /* the interface is down */
465                 goto get_ifinfo_end;
466             }
467
468             ifindex = ifm->ifm_index;
469             ifinfo->rx_packets = ifm->ifm_data.ifi_ipackets;
470             ifinfo->tx_packets = ifm->ifm_data.ifi_opackets;
471
472             break;
473         }
474     }
475
476     if (ifindex < 0)
477     {
478         /* we did not find an interface with a matching name */
479         report(stderr, _("No interface found with name %s"), iname);
480         goto get_ifinfo_end;
481     }
482
483     /* now look for the interface's IP address */
484
485     next = buf;
486     while (next < lim)
487     {
488         ifam = (struct ifa_msghdr *)next;
489         next += ifam->ifam_msglen;
490
491         if (ifindex > 0
492             && ifam->ifam_type == RTM_NEWADDR
493             && ifam->ifam_index == ifindex)
494         {
495             /* Expand the compacted addresses */
496             info.rti_addrs = ifam->ifam_addrs;
497             rt_xaddrs((char *)(ifam + 1), 
498                         ifam->ifam_msglen + (char *)ifam,
499                         &info);
500
501             /* Check for IPv4 address information only */
502             if (info.rti_info[RTAX_IFA]->sa_family != AF_INET)
503             {
504                 continue;
505             }
506
507             rc = 1;
508
509             sin = (struct sockaddr_in *)info.rti_info[RTAX_IFA];
510             if (sin)
511             {
512                 ifinfo->addr = sin->sin_addr;
513             }
514
515             sin = (struct sockaddr_in *)info.rti_info[RTAX_NETMASK];
516             if (!sin)
517             {
518                 ifinfo->netmask = sin->sin_addr;
519             }
520
521             /* note: RTAX_BRD contains the address at the other
522              * end of a point-to-point link or the broadcast address
523              * of non point-to-point link
524              */
525             sin = (struct sockaddr_in *)info.rti_info[RTAX_BRD];
526             if (!sin)
527             {
528                 ifinfo->dstaddr = sin->sin_addr;
529             }
530
531             break;
532         }
533     }
534
535     if (rc == 0)
536     {
537         report(stderr, _("No IP address found for %s"), iname);
538     }
539
540 get_ifinfo_end:
541     free(buf);
542     return rc;
543 }
544
545 #endif /* __FREEBSD_USE_SYSCTL_GET_IFFINFO */
546
547 #endif /* defined __FreeBSD__ */
548
549
550 #ifndef HAVE_INET_ATON
551 /*
552  * Note: This is not a true replacement for inet_aton(), as it won't
553  * do the right thing on "255.255.255.255" (which translates to -1 on
554  * most machines).  Fortunately this code will be used only if you're
555  * on an older Linux that lacks a real implementation.
556  */
557 #ifdef HAVE_NETINET_IN_SYSTM_H
558 # include <sys/types.h>
559 # include <netinet/in_systm.h>
560 #endif
561
562 #include <netinet/in.h>
563 #include <netinet/ip.h>
564 #include <arpa/inet.h>
565 #include <string.h>
566
567 static int inet_aton(const char *cp, struct in_addr *inp) {
568     long addr;
569
570     addr = inet_addr(cp);
571     if (addr == ((long) -1)) return 0;
572
573     memcpy(inp, &addr, sizeof(addr));
574     return 1;
575 }
576 #endif /* HAVE_INET_ATON */
577
578 void interface_parse(char *buf, struct hostdata *hp)
579 /* parse 'interface' specification */
580 {
581         char *cp1, *cp2;
582
583         hp->interface = xstrdup(buf);
584
585         /* find and isolate just the IP address */
586         if (!(cp1 = strchr(buf, '/')))
587         {
588                 (void) report(stderr,
589                               _("missing IP interface address\n"));
590                 exit(PS_SYNTAX);
591         }
592         *cp1++ = '\000';
593
594         /* find and isolate just the netmask */
595         if (!(cp2 = strchr(cp1, '/')))
596                 cp2 = "255.255.255.255";
597         else
598                 *cp2++ = '\000';
599
600         /* convert IP address and netmask */
601         hp->interface_pair = (struct interface_pair_s *)xmalloc(sizeof(struct interface_pair_s));
602         if (!inet_aton(cp1, &hp->interface_pair->interface_address))
603         {
604                 (void) report(stderr,
605                               _("invalid IP interface address\n"));
606                 exit(PS_SYNTAX);
607         }
608         if (!inet_aton(cp2, &hp->interface_pair->interface_mask))
609         {
610                 (void) report(stderr,
611                               _("invalid IP interface mask\n"));
612                 exit(PS_SYNTAX);
613         }
614         /* apply the mask now to the IP address (range) required */
615         hp->interface_pair->interface_address.s_addr &=
616                 hp->interface_pair->interface_mask.s_addr;
617
618         /* restore original interface string (for configuration dumper) */
619         *--cp1 = '/';
620         return;
621 }
622
623 void interface_note_activity(struct hostdata *hp)
624 /* save interface I/O counts */
625 {
626         ifinfo_t ifinfo;
627         struct query *ctl;
628
629         /* if not monitoring link, all done */
630         if (!hp->monitor)
631                 return;
632
633         /* get the current I/O stats for the monitored link */
634         if (get_ifinfo(hp->monitor, &ifinfo))
635                 /* update this and preceeding host entries using the link
636                    (they were already set during this pass but the I/O
637                    count has now changed and they need to be re-updated)
638                 */
639                 for (ctl = querylist; ctl; ctl = ctl->next) {
640                         if (ctl->server.monitor && !strcmp(hp->monitor, ctl->server.monitor))
641                                 ctl->server.monitor_io =
642                                         ifinfo.rx_packets + ifinfo.tx_packets;
643                         /* do NOT update host entries following this one */
644                         if (&ctl->server == hp)
645                                 break;
646                 }
647
648 #ifdef  ACTIVITY_DEBUG
649         (void) report(stdout, 
650                       _("activity on %s -noted- as %d\n"), 
651                       hp->monitor, hp->monitor_io);
652 #endif
653 }
654
655 int interface_approve(struct hostdata *hp, flag domonitor)
656 /* return TRUE if OK to poll, FALSE otherwise */
657 {
658         ifinfo_t ifinfo;
659
660         /* check interface IP address (range), if specified */
661         if (hp->interface) {
662                 /* get interface info */
663                 if (!get_ifinfo(hp->interface, &ifinfo)) {
664                         (void) report(stdout, 
665                                       _("skipping poll of %s, %s down\n"),
666                                       hp->pollname, hp->interface);
667                         return(FALSE);
668                 }
669                 /* check the IP addresses (range) */
670                 if      (!(
671                                 /* check remote IP address */
672                                 ((ifinfo.dstaddr.s_addr != 0) &&
673                                 (ifinfo.dstaddr.s_addr &
674                                 hp->interface_pair->interface_mask.s_addr) ==
675                                 hp->interface_pair->interface_address.s_addr)
676                                 ||
677                                 /* check local IP address */
678                                 ((ifinfo.addr.s_addr &
679                                 hp->interface_pair->interface_mask.s_addr) ==
680                                 hp->interface_pair->interface_address.s_addr)
681                         ) )
682                 {
683                         (void) report(stdout,
684                                 _("skipping poll of %s, %s IP address excluded\n"),
685                                 hp->pollname, hp->interface);
686                         return(FALSE);
687                 }
688         }
689
690         /* if not monitoring link, all done */
691         if (!domonitor || !hp->monitor)
692                 return(TRUE);
693
694 #ifdef  ACTIVITY_DEBUG
695         (void) report(stdout, 
696                       _("activity on %s checked as %d\n"), 
697                       hp->monitor, hp->monitor_io);
698 #endif
699         /* if monitoring, check link for activity if it is up */
700         if (get_ifinfo(hp->monitor, &ifinfo))
701         {
702             int diff = (ifinfo.rx_packets + ifinfo.tx_packets)
703                                                         - hp->monitor_io;
704
705             /*
706              * There are three cases here:
707              *
708              * (a) If the new packet count is less than the recorded one,
709              * probably pppd was restarted while fetchmail was running.
710              * Don't skip.
711              *
712              * (b) newpacket count is greater than the old packet count,
713              * but the difference is small and may just reflect the overhead
714              * of a link shutdown.  Skip.
715              *
716              * (c) newpacket count is greater than the old packet count,
717              * and the difference is large. Connection is live.  Don't skip.
718              */
719             if (diff >= 0 && diff <= MONITOR_SLOP)
720             {
721                 (void) report(stdout, 
722                               _("skipping poll of %s, %s inactive\n"),
723                               hp->pollname, hp->monitor);
724                 return(FALSE);
725             }
726         }
727
728 #ifdef ACTIVITY_DEBUG
729        report(stdout, _("activity on %s was %d, is %d\n"),
730              hp->monitor, hp->monitor_io,
731              ifinfo.rx_packets + ifinfo.tx_packets);
732 #endif
733
734         return(TRUE);
735 }
736 #endif /* (defined(linux) && !defined(INET6_ENABLE)) || defined(__FreeBSD__) */