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