]> Pileus Git - ~andy/fetchmail/blob - interface.c
Changed George's email address.
[~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)) || 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 __FreeBSD_version >= 300001
37 #include <net/if_var.h>
38 #endif
39 #include <kvm.h>
40 #include <nlist.h>
41 #include <sys/fcntl.h>
42 #endif
43 #include "config.h"
44 #include "fetchmail.h"
45 #include "i18n.h"
46
47 typedef struct {
48         struct in_addr addr, dstaddr, netmask;
49         int rx_packets, tx_packets;
50 } ifinfo_t;
51
52 struct interface_pair_s {
53         struct in_addr interface_address;
54         struct in_addr interface_mask;
55 } *interface_pair;
56
57 static char *netdevfmt;
58
59 #if defined(linux)
60
61 void interface_init(void)
62 /* figure out which /roc/dev/net format to use */
63 {
64     FILE *fp = popen("uname -r", "r");  /* still wins if /proc is out */
65
66     /* pre-linux-2.2 format -- transmit packet count in 8th field */
67     netdevfmt = "%d %d %*d %*d %*d %d %*d %d %*d %*d %*d %*d %d";
68
69     if (!fp)
70         return;
71     else
72     {
73         int major, minor;
74
75         if (fscanf(fp, "%d.%d.%*d", &major, &minor) != 2)
76             return;
77
78         if (major >= 2 && minor >= 2)
79             /* Linux 2.2 -- transmit packet count in 10th field */
80             netdevfmt = "%d %d %*d %*d %*d %d %*d %*d %*d %*d %d %*d %d";
81     }
82 }
83
84 static int _get_ifinfo_(int socket_fd, FILE *stats_file, const char *ifname,
85                 ifinfo_t *ifinfo)
86 /* get active network interface information - return non-zero upon success */
87 {
88         int namelen = strlen(ifname);
89         struct ifreq request;
90         char *cp, buffer[256];
91         int found = 0;
92         int counts[4];
93
94         /* initialize result */
95         memset((char *) ifinfo, 0, sizeof(ifinfo_t));
96
97         /* get the packet I/O counts */
98         while (fgets(buffer, sizeof(buffer) - 1, stats_file)) {
99                 for (cp = buffer; *cp && *cp == ' '; ++cp);
100                 if (!strncmp(cp, ifname, namelen) &&
101                                 cp[namelen] == ':') {
102                         cp += namelen + 1;
103                         if (sscanf(cp, netdevfmt,
104                                    counts, counts+1, counts+2, 
105                                    counts+3,&found)>4) { /* found = dummy */
106                                 /* newer kernel with byte counts */
107                                 ifinfo->rx_packets=counts[1];
108                                 ifinfo->tx_packets=counts[3];
109                         } else {
110                                 /* older kernel, no byte counts */
111                                 ifinfo->rx_packets=counts[0];
112                                 ifinfo->tx_packets=counts[2];
113                         }
114                         found = 1;
115                 }
116         }
117         if (!found) return (FALSE);
118
119         /* see if the interface is up */
120         strcpy(request.ifr_name, ifname);
121         if (ioctl(socket_fd, SIOCGIFFLAGS, &request) < 0)
122                 return(FALSE);
123         if (!(request.ifr_flags & IFF_RUNNING))
124                 return(FALSE);
125
126         /* get the IP address */
127         strcpy(request.ifr_name, ifname);
128         if (ioctl(socket_fd, SIOCGIFADDR, &request) < 0)
129                 return(FALSE);
130         ifinfo->addr = ((struct sockaddr_in *) (&request.ifr_addr))->sin_addr;
131
132         /* get the PPP destination IP address */
133         strcpy(request.ifr_name, ifname);
134         if (ioctl(socket_fd, SIOCGIFDSTADDR, &request) >= 0)
135                 ifinfo->dstaddr = ((struct sockaddr_in *)
136                                         (&request.ifr_dstaddr))->sin_addr;
137
138         /* get the netmask */
139         strcpy(request.ifr_name, ifname);
140         if (ioctl(socket_fd, SIOCGIFNETMASK, &request) >= 0) {
141           ifinfo->netmask = ((struct sockaddr_in *)
142                              (&request.ifr_netmask))->sin_addr;
143           return (TRUE);
144         }
145
146         return(FALSE);
147 }
148
149 static int get_ifinfo(const char *ifname, ifinfo_t *ifinfo)
150 {
151         int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
152         FILE *stats_file = fopen("/proc/net/dev", "r");
153         int result;
154
155         if (socket_fd < 0 || !stats_file)
156                 result = FALSE;
157         else
158         {
159             char        *sp = strchr(ifname, '/');
160
161             if (sp)
162                 *sp = '\0';
163             result = _get_ifinfo_(socket_fd, stats_file, ifname, ifinfo);
164             if (sp)
165                 *sp = '/';
166         }
167         if (socket_fd >= 0)
168                 close(socket_fd);
169         if (stats_file)
170                 fclose(stats_file);
171         return(result);
172 }
173
174 #elif defined __FreeBSD__
175
176 static kvm_t *kvmfd;
177 static struct nlist symbols[] = 
178 {
179         {"_ifnet"},
180         {NULL}
181 };
182 static u_long   ifnet_savedaddr;
183 static gid_t    if_rgid;
184 static gid_t    if_egid;
185
186 void 
187 interface_set_gids(gid_t egid, gid_t rgid)
188 {
189         if_rgid = rgid;
190         if_egid = egid;
191 }
192
193 static int 
194 openkvm(void)
195 {
196         if ((kvmfd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) == NULL)
197                 return FALSE;
198         
199         if (kvm_nlist(kvmfd, symbols) < 0)
200                 return FALSE;
201            
202         if (kvm_read(kvmfd, (unsigned long) symbols[0].n_value, &ifnet_savedaddr, sizeof(unsigned long)) == -1)
203                 return FALSE;
204                 
205         return TRUE;
206 }
207
208 static int 
209 get_ifinfo(const char *ifname, ifinfo_t *ifinfo)
210 {
211         char                    tname[16];
212         char                    iname[16];
213         struct ifnet            ifnet;
214         unsigned long           ifnet_addr = ifnet_savedaddr;
215 #if __FreeBSD_version >= 300001
216         struct ifnethead        ifnethead;
217         struct ifaddrhead       ifaddrhead;
218 #endif
219         struct ifaddr           ifaddr;
220         unsigned long           ifaddr_addr;
221         struct sockaddr         sa;
222         unsigned long           sa_addr;
223         uint                    i;
224         
225         if (if_egid)
226                 setegid(if_egid);
227         
228         for (i = 0; ifname[i] && ifname[i] != '/'; i++)
229                 iname[i] = ifname[i];
230                 
231         iname[i] = '\0';
232         
233         if (!kvmfd)
234         {
235                 if (!openkvm())
236                 {
237                         report(stderr, 0, _("Unable to open kvm interface. Make sure fetchmail is SGID kmem."));
238                         if (if_egid)
239                                 setegid(if_rgid);
240                         exit(1);
241                 }
242         }
243
244 #if __FreeBSD_version >= 300001
245         kvm_read(kvmfd, ifnet_savedaddr, (char *) &ifnethead, sizeof ifnethead);
246         ifnet_addr = (u_long) ifnethead.tqh_first;
247 #else
248         ifnet_addr = ifnet_savedaddr;
249 #endif
250
251         while (ifnet_addr)
252         {
253                 kvm_read(kvmfd, ifnet_addr, &ifnet, sizeof(ifnet));
254                 kvm_read(kvmfd, (unsigned long) ifnet.if_name, tname, sizeof tname);
255                 snprintf(tname, sizeof tname - 1, "%s%d", tname, ifnet.if_unit);
256
257                 if (!strcmp(tname, iname))
258                 {
259                         if (!(ifnet.if_flags & IFF_UP))
260                         {
261                                 if (if_egid)
262                                         setegid(if_rgid);
263                                 return 0;
264                         }
265                                 
266                         ifinfo->rx_packets = ifnet.if_ipackets;
267                         ifinfo->tx_packets = ifnet.if_opackets;
268
269 #if __FreeBSD_version >= 300001
270                         ifaddr_addr = (u_long) ifnet.if_addrhead.tqh_first;
271 #else
272                         ifaddr_addr = (u_long) ifnet.if_addrlist;
273 #endif
274                         
275                         while(ifaddr_addr)
276                         {
277                                 kvm_read(kvmfd, ifaddr_addr, &ifaddr, sizeof(ifaddr));
278                                 kvm_read(kvmfd, (u_long)ifaddr.ifa_addr, &sa, sizeof(sa));
279                                 
280                                 if (sa.sa_family != AF_INET)
281                                 {
282 #if __FreeBSD_version >= 300001
283                                         ifaddr_addr = (u_long) ifaddr.ifa_link.tqe_next;
284 #else
285                                         ifaddr_addr = (u_long) ifaddr.ifa_next;
286 #endif
287                                         continue;
288                                 }
289                         
290                                 ifinfo->addr.s_addr = *(u_long *)(sa.sa_data + 2);
291                                 kvm_read(kvmfd, (u_long)ifaddr.ifa_dstaddr, &sa, sizeof(sa));
292                                 ifinfo->dstaddr.s_addr = *(u_long *)(sa.sa_data + 2);
293                                 kvm_read(kvmfd, (u_long)ifaddr.ifa_netmask, &sa, sizeof(sa));
294                                 ifinfo->netmask.s_addr = *(u_long *)(sa.sa_data + 2);
295
296                                 if (if_egid)
297                                         setegid(if_rgid);
298
299                                 return 1;
300                         }
301                         
302                         if (if_egid)
303                                 setegid(if_rgid);
304                         
305                         return 0;
306                 }
307
308 #if __FreeBSD_version >= 300001
309                 ifnet_addr = (u_long) ifnet.if_link.tqe_next;
310 #else
311                 ifnet_addr = (unsigned long) ifnet.if_next;
312 #endif
313         }
314
315         if (if_egid)
316                 setegid(if_rgid);
317         
318         return 0;
319 }
320 #endif /* defined __FreeBSD__ */
321
322
323 #ifndef HAVE_INET_ATON
324 /*
325  * Note: This is not a true replacement for inet_aton(), as it won't
326  * do the right thing on "255.255.255.255" (which translates to -1 on
327  * most machines).  Fortunately this code will be used only if you're
328  * on an older Linux that lacks a real implementation.
329  */
330 #ifdef HAVE_NETINET_IN_SYSTM_H
331 # include <sys/types.h>
332 # include <netinet/in_systm.h>
333 #endif
334
335 #include <netinet/in.h>
336 #include <netinet/ip.h>
337 #include <arpa/inet.h>
338 #include <string.h>
339
340 static int inet_aton(const char *cp, struct in_addr *inp) {
341     long addr;
342
343     addr = inet_addr(cp);
344     if (addr == ((long) -1)) return 0;
345
346     memcpy(inp, &addr, sizeof(addr));
347     return 1;
348 }
349 #endif /* HAVE_INET_ATON */
350
351 void interface_parse(char *buf, struct hostdata *hp)
352 /* parse 'interface' specification */
353 {
354         char *cp1, *cp2;
355
356         hp->interface = xstrdup(buf);
357
358         /* find and isolate just the IP address */
359         if (!(cp1 = strchr(buf, '/')))
360         {
361                 (void) report(stderr,
362                               _("missing IP interface address\n"));
363                 exit(PS_SYNTAX);
364         }
365         *cp1++ = '\000';
366
367         /* find and isolate just the netmask */
368         if (!(cp2 = strchr(cp1, '/')))
369                 cp2 = "255.255.255.255";
370         else
371                 *cp2++ = '\000';
372
373         /* convert IP address and netmask */
374         hp->interface_pair = (struct interface_pair_s *)xmalloc(sizeof(struct interface_pair_s));
375         if (!inet_aton(cp1, &hp->interface_pair->interface_address))
376         {
377                 (void) report(stderr,
378                               _("invalid IP interface address\n"));
379                 exit(PS_SYNTAX);
380         }
381         if (!inet_aton(cp2, &hp->interface_pair->interface_mask))
382         {
383                 (void) report(stderr,
384                               _("invalid IP interface mask\n"));
385                 exit(PS_SYNTAX);
386         }
387         /* apply the mask now to the IP address (range) required */
388         hp->interface_pair->interface_address.s_addr &=
389                 hp->interface_pair->interface_mask.s_addr;
390
391         /* restore original interface string (for configuration dumper) */
392         *--cp1 = '/';
393         return;
394 }
395
396 void interface_note_activity(struct hostdata *hp)
397 /* save interface I/O counts */
398 {
399         ifinfo_t ifinfo;
400         struct query *ctl;
401
402         /* if not monitoring link, all done */
403         if (!hp->monitor)
404                 return;
405
406         /* get the current I/O stats for the monitored link */
407         if (get_ifinfo(hp->monitor, &ifinfo))
408                 /* update this and preceeding host entries using the link
409                    (they were already set during this pass but the I/O
410                    count has now changed and they need to be re-updated)
411                 */
412                 for (ctl = querylist; ctl; ctl = ctl->next) {
413                         if (ctl->server.monitor && !strcmp(hp->monitor, ctl->server.monitor))
414                                 ctl->server.monitor_io =
415                                         ifinfo.rx_packets + ifinfo.tx_packets;
416                         /* do NOT update host entries following this one */
417                         if (&ctl->server == hp)
418                                 break;
419                 }
420
421 #ifdef  ACTIVITY_DEBUG
422         (void) report(stdout, 
423                       _("activity on %s -noted- as %d\n"), 
424                       hp->monitor, hp->monitor_io);
425 #endif
426 }
427
428 int interface_approve(struct hostdata *hp)
429 /* return TRUE if OK to poll, FALSE otherwise */
430 {
431         ifinfo_t ifinfo;
432
433         /* check interface IP address (range), if specified */
434         if (hp->interface) {
435                 /* get interface info */
436                 if (!get_ifinfo(hp->interface, &ifinfo)) {
437                         (void) report(stdout, 
438                                       _("skipping poll of %s, %s down\n"),
439                                       hp->pollname, hp->interface);
440                         return(FALSE);
441                 }
442                 /* check the IP address (range) */
443                 if ((ifinfo.addr.s_addr &
444                                 hp->interface_pair->interface_mask.s_addr) !=
445                                 hp->interface_pair->interface_address.s_addr) {
446                         (void) report(stdout,
447                                 _("skipping poll of %s, %s IP address excluded\n"),
448                                 hp->pollname, hp->interface);
449                         return(FALSE);
450                 }
451         }
452
453         /* if not monitoring link, all done */
454         if (!hp->monitor)
455                 return(TRUE);
456
457 #ifdef  ACTIVITY_DEBUG
458         (void) report(stdout, 
459                       _("activity on %s checked as %d\n"), 
460                       hp->monitor, hp->monitor_io);
461 #endif
462         /* if monitoring, check link for activity if it is up */
463         if (get_ifinfo(hp->monitor, &ifinfo) &&
464                         hp->monitor_io == ifinfo.rx_packets +
465                                 ifinfo.tx_packets) {
466                 (void) report(stdout, 
467                               _("skipping poll of %s, %s inactive\n"),
468                               hp->pollname, hp->monitor);
469                 return(FALSE);
470         }
471
472 #ifdef ACTIVITY_DEBUG
473        error(0, 0, _("activity on %s was %d, is %d"),
474              hp->monitor, hp->monitor_io,
475              ifinfo.rx_packets + ifinfo.tx_packets);
476 #endif
477
478         return(TRUE);
479 }
480 #endif /* (defined(linux) && !defined(INET6)) || defined(__FreeBSD__) */