]> git.decadent.org.uk Git - nfs-utils.git/blob - support/export/client.c
c74961e56f0e6eb0bcb1eefbafc53689ce29dfd7
[nfs-utils.git] / support / export / client.c
1 /*
2  * support/export/client.c
3  *
4  * Maintain list of nfsd clients.
5  *
6  * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
7  */
8
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <sys/types.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <ctype.h>
19 #include <netdb.h>
20 #include <errno.h>
21
22 #include "sockaddr.h"
23 #include "misc.h"
24 #include "nfslib.h"
25 #include "exportfs.h"
26
27 /* netgroup stuff never seems to be defined in any header file. Linux is
28  * not alone in this.
29  */
30 #if !defined(__GLIBC__) || __GLIBC__ < 2
31 extern int      innetgr(char *netgr, char *host, char *, char *);
32 #endif
33
34 static char     *add_name(char *old, const char *add);
35
36 nfs_client      *clientlist[MCL_MAXTYPES] = { NULL, };
37
38
39 static void
40 init_addrlist(nfs_client *clp, const struct addrinfo *ai)
41 {
42         int i;
43
44         if (ai == NULL)
45                 return;
46
47         for (i = 0; (ai != NULL) && (i < NFSCLNT_ADDRMAX); i++) {
48                 set_addrlist(clp, i, ai->ai_addr);
49                 ai = ai->ai_next;
50         }
51
52         clp->m_naddr = i;
53 }
54
55 static void
56 client_free(nfs_client *clp)
57 {
58         free(clp->m_hostname);
59         free(clp);
60 }
61
62 static int
63 init_netmask(nfs_client *clp, const char *slash, const sa_family_t family)
64 {
65         struct sockaddr_in sin = {
66                 .sin_family             = AF_INET,
67         };
68         unsigned long prefixlen;
69         uint32_t shift;
70 #ifdef IPV6_SUPPORTED
71         struct sockaddr_in6 sin6 = {
72                 .sin6_family            = AF_INET6,
73         };
74         int i;
75 #endif
76
77         /* No slash present; assume netmask is all ones */
78         if (slash == NULL) {
79                 switch (family) {
80                 case AF_INET:
81                         prefixlen = 32;
82                         break;
83 #ifdef IPV6_SUPPORTED
84                 case AF_INET6:
85                         prefixlen = 128;
86                         break;
87 #endif
88                 default:
89                         goto out_badfamily;
90                 }
91         } else {
92                 char *endptr;
93
94                 /* A spelled out netmask address, perhaps? */
95                 if (strchr(slash + 1, '.') != NULL) {
96                         if (inet_pton(AF_INET, slash + 1,
97                                                 &sin.sin_addr.s_addr) == 0)
98                                 goto out_badmask;
99                         set_addrlist_in(clp, 1, &sin);
100                         return 1;
101                 }
102 #ifdef IPV6_SUPPORTED
103                 if (strchr(slash + 1, ':')) {
104                         if (!inet_pton(AF_INET6, slash + 1, &sin6.sin6_addr))
105                                 goto out_badmask;
106                         set_addrlist_in6(clp, 1, &sin6);
107                         return 1;
108                 }
109 #endif
110
111                 /* A prefixlen was given */
112                 prefixlen = strtoul(slash + 1, &endptr, 10);
113                 if (*endptr != '\0' && prefixlen != ULONG_MAX && errno != ERANGE)
114                         goto out_badprefix;
115         }
116
117         switch (family) {
118         case AF_INET:
119                 if (prefixlen > 32)
120                         goto out_badprefix;
121                 shift = 32 - (uint32_t)prefixlen;
122                 sin.sin_addr.s_addr = htonl((uint32_t)~0 << shift);
123                 set_addrlist_in(clp, 1, &sin);
124                 return 1;
125 #ifdef IPV6_SUPPORTED
126         case AF_INET6:
127                 if (prefixlen > 128)
128                         goto out_badprefix;
129                 for (i = 0; prefixlen > 32; i++) {
130                         sin6.sin6_addr.s6_addr32[i] = 0xffffffff;
131                         prefixlen -= 32;
132                 }
133                 shift = 32 - (uint32_t)prefixlen;
134                 sin6.sin6_addr.s6_addr32[i] = htonl((uint32_t)~0 << shift);
135                 set_addrlist_in6(clp, 1, &sin6);
136                 return 1;
137 #endif
138         }
139
140 out_badfamily:
141         xlog(L_ERROR, "Unsupported address family for %s", clp->m_hostname);
142         return 0;
143
144 out_badmask:
145         xlog(L_ERROR, "Invalid netmask `%s' for %s", slash + 1, clp->m_hostname);
146         return 0;
147
148 out_badprefix:
149         xlog(L_ERROR, "Invalid prefix `%s' for %s", slash + 1, clp->m_hostname);
150         return 0;
151 }
152
153 static int
154 init_subnetwork(nfs_client *clp)
155 {
156         struct addrinfo *ai;
157         sa_family_t family;
158         char *slash;
159
160         slash = strchr(clp->m_hostname, '/');
161         if (slash != NULL) {
162                 *slash = '\0';
163                 ai = host_pton(clp->m_hostname);
164                 *slash = '/';
165         } else
166                 ai = host_pton(clp->m_hostname);
167         if (ai == NULL) {
168                 xlog(L_ERROR, "Invalid IP address %s", clp->m_hostname);
169                 return false;
170         }
171
172         set_addrlist(clp, 0, ai->ai_addr);
173         family = ai->ai_addr->sa_family;
174
175         freeaddrinfo(ai);
176
177         return init_netmask(clp, slash, family);
178 }
179
180 static int
181 client_init(nfs_client *clp, const char *hname, const struct addrinfo *ai)
182 {
183         clp->m_hostname = strdup(hname);
184         if (clp->m_hostname == NULL)
185                 return 0;
186
187         clp->m_exported = 0;
188         clp->m_count = 0;
189         clp->m_naddr = 0;
190
191         if (clp->m_type == MCL_SUBNETWORK)
192                 return init_subnetwork(clp);
193
194         init_addrlist(clp, ai);
195         return 1;
196 }
197
198 static void
199 client_add(nfs_client *clp)
200 {
201         nfs_client **cpp;
202
203         cpp = &clientlist[clp->m_type];
204         while (*cpp != NULL)
205                 cpp = &((*cpp)->m_next);
206         clp->m_next = NULL;
207         *cpp = clp;
208 }
209
210 /**
211  * client_lookup - look for @hname in our list of cached nfs_clients
212  * @hname: '\0'-terminated ASCII string containing hostname to look for
213  * @canonical: if set, @hname is known to be canonical DNS name
214  *
215  * Returns pointer to a matching or freshly created nfs_client.  NULL
216  * is returned if some problem occurs.
217  */
218 nfs_client *
219 client_lookup(char *hname, int canonical)
220 {
221         nfs_client      *clp = NULL;
222         int             htype;
223         struct addrinfo *ai = NULL;
224
225         htype = client_gettype(hname);
226
227         if (htype == MCL_FQDN && !canonical) {
228                 ai = host_addrinfo(hname);
229                 if (!ai) {
230                         xlog(L_ERROR, "Failed to resolve %s", hname);
231                         goto out;
232                 }
233                 hname = ai->ai_canonname;
234
235                 for (clp = clientlist[htype]; clp; clp = clp->m_next)
236                         if (client_check(clp, ai))
237                                 break;
238         } else {
239                 for (clp = clientlist[htype]; clp; clp = clp->m_next) {
240                         if (strcasecmp(hname, clp->m_hostname)==0)
241                                 break;
242                 }
243         }
244
245         if (clp == NULL) {
246                 clp = calloc(1, sizeof(*clp));
247                 if (clp == NULL)
248                         goto out;
249                 clp->m_type = htype;
250                 if (!client_init(clp, hname, NULL)) {
251                         client_free(clp);
252                         clp = NULL;
253                         goto out;
254                 }
255                 client_add(clp);
256         }
257
258         if (htype == MCL_FQDN && clp->m_naddr == 0)
259                 init_addrlist(clp, ai);
260
261 out:
262         freeaddrinfo(ai);
263         return clp;
264 }
265
266 /**
267  * client_dup - create a copy of an nfs_client
268  * @clp: pointer to nfs_client to copy
269  * @ai: pointer to addrinfo used to initialize the new client's addrlist
270  *
271  * Returns a dynamically allocated nfs_client if successful, or
272  * NULL if some problem occurs.  Caller must free the returned
273  * nfs_client with free(3).
274  */
275 nfs_client *
276 client_dup(const nfs_client *clp, const struct addrinfo *ai)
277 {
278         nfs_client              *new;
279
280         new = (nfs_client *)malloc(sizeof(*new));
281         if (new == NULL)
282                 return NULL;
283         memcpy(new, clp, sizeof(*new));
284         new->m_type = MCL_FQDN;
285         new->m_hostname = NULL;
286
287         if (!client_init(new, ai->ai_canonname, ai)) {
288                 client_free(new);
289                 return NULL;
290         }
291         client_add(new);
292         return new;
293 }
294
295 /**
296  * client_release - drop a reference to an nfs_client record
297  *
298  */
299 void
300 client_release(nfs_client *clp)
301 {
302         if (clp->m_count <= 0)
303                 xlog(L_FATAL, "client_free: m_count <= 0!");
304         clp->m_count--;
305 }
306
307 /**
308  * client_freeall - deallocate all nfs_client records
309  *
310  */
311 void
312 client_freeall(void)
313 {
314         nfs_client      *clp, **head;
315         int             i;
316
317         for (i = 0; i < MCL_MAXTYPES; i++) {
318                 head = clientlist + i;
319                 while (*head) {
320                         *head = (clp = *head)->m_next;
321                         client_free(clp);
322                 }
323         }
324 }
325
326 /**
327  * client_resolve - look up an IP address
328  * @sap: pointer to socket address to resolve
329  *
330  * Returns an addrinfo structure, or NULL if some problem occurred.
331  * Caller must free the result with freeaddrinfo(3).
332  */
333 struct addrinfo *
334 client_resolve(const struct sockaddr *sap)
335 {
336         struct addrinfo *ai = NULL;
337
338         if (clientlist[MCL_WILDCARD] || clientlist[MCL_NETGROUP])
339                 ai = host_reliable_addrinfo(sap);
340         if (ai == NULL)
341                 ai = host_numeric_addrinfo(sap);
342
343         return ai;
344 }
345
346 /**
347  * client_compose - Make a list of cached hostnames that match an IP address
348  * @ai: pointer to addrinfo containing IP address information to match
349  *
350  * Gather all known client hostnames that match the IP address, and sort
351  * the result into a comma-separated list.
352  *
353  * Returns a '\0'-terminated ASCII string containing a comma-separated
354  * sorted list of client hostnames, or NULL if no client records matched
355  * the IP address or memory could not be allocated.  Caller must free the
356  * returned string with free(3).
357  */
358 char *
359 client_compose(const struct addrinfo *ai)
360 {
361         char *name = NULL;
362         int i;
363
364         for (i = 0 ; i < MCL_MAXTYPES; i++) {
365                 nfs_client      *clp;
366                 for (clp = clientlist[i]; clp ; clp = clp->m_next) {
367                         if (!client_check(clp, ai))
368                                 continue;
369                         name = add_name(name, clp->m_hostname);
370                 }
371         }
372         return name;
373 }
374
375 /**
376  * client_member - check if @name is contained in the list @client
377  * @client: '\0'-terminated ASCII string containing
378  *              comma-separated list of hostnames
379  * @name: '\0'-terminated ASCII string containing hostname to look for
380  *
381  * Returns 1 if @name was found in @client, otherwise zero is returned.
382  */
383 int
384 client_member(const char *client, const char *name)
385 {
386         size_t l = strlen(name);
387
388         while (*client) {
389                 if (strncmp(client, name, l) == 0 &&
390                     (client[l] == ',' || client[l] == '\0'))
391                         return 1;
392                 client = strchr(client, ',');
393                 if (client == NULL)
394                         return 0;
395                 client++;
396         }
397         return 0;
398 }
399
400 static int
401 name_cmp(const char *a, const char *b)
402 {
403         /* compare strings a and b, but only upto ',' in a */
404         while (*a && *b && *a != ',' && *a == *b)
405                 a++, b++;
406         if (!*b && (!*a || *a == ','))
407                 return 0;
408         if (!*b) return 1;
409         if (!*a || *a == ',') return -1;
410         return *a - *b;
411 }
412
413 static char *
414 add_name(char *old, const char *add)
415 {
416         size_t len = strlen(add) + 2;
417         char *new;
418         char *cp;
419         if (old) len += strlen(old);
420         
421         new = malloc(len);
422         if (!new) {
423                 free(old);
424                 return NULL;
425         }
426         cp = old;
427         while (cp && *cp && name_cmp(cp, add) < 0) {
428                 /* step cp forward over a name */
429                 char *e = strchr(cp, ',');
430                 if (e)
431                         cp = e+1;
432                 else
433                         cp = cp + strlen(cp);
434         }
435         strncpy(new, old, cp-old);
436         new[cp-old] = 0;
437         if (cp != old && !*cp)
438                 strcat(new, ",");
439         strcat(new, add);
440         if (cp && *cp) {
441                 strcat(new, ",");
442                 strcat(new, cp);
443         }
444         free(old);
445         return new;
446 }
447
448 /*
449  * Check each address listed in @ai against each address
450  * stored in @clp.  Return 1 if a match is found, otherwise
451  * zero.
452  */
453 static int
454 check_fqdn(const nfs_client *clp, const struct addrinfo *ai)
455 {
456         int i;
457
458         for (; ai; ai = ai->ai_next)
459                 for (i = 0; i < clp->m_naddr; i++)
460                         if (nfs_compare_sockaddr(ai->ai_addr,
461                                                         get_addrlist(clp, i)))
462                                 return 1;
463
464         return 0;
465 }
466
467 static _Bool
468 mask_match(const uint32_t a, const uint32_t b, const uint32_t m)
469 {
470         return ((a ^ b) & m) == 0;
471 }
472
473 static int
474 check_subnet_v4(const struct sockaddr_in *address,
475                 const struct sockaddr_in *mask, const struct addrinfo *ai)
476 {
477         for (; ai; ai = ai->ai_next) {
478                 struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr;
479
480                 if (sin->sin_family != AF_INET)
481                         continue;
482
483                 if (mask_match(address->sin_addr.s_addr,
484                                 sin->sin_addr.s_addr,
485                                 mask->sin_addr.s_addr))
486                         return 1;
487         }
488         return 0;
489 }
490
491 #ifdef IPV6_SUPPORTED
492 static int
493 check_subnet_v6(const struct sockaddr_in6 *address,
494                 const struct sockaddr_in6 *mask, const struct addrinfo *ai)
495 {
496         for (; ai; ai = ai->ai_next) {
497                 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr;
498
499                 if (sin6->sin6_family != AF_INET6)
500                         continue;
501
502                 if (mask_match(address->sin6_addr.s6_addr32[0],
503                                 sin6->sin6_addr.s6_addr32[0],
504                                 mask->sin6_addr.s6_addr32[0]) &&
505                     mask_match(address->sin6_addr.s6_addr32[1],
506                                 sin6->sin6_addr.s6_addr32[1],
507                                 mask->sin6_addr.s6_addr32[1]) &&
508                     mask_match(address->sin6_addr.s6_addr32[2],
509                                 sin6->sin6_addr.s6_addr32[2],
510                                 mask->sin6_addr.s6_addr32[2]) &&
511                     mask_match(address->sin6_addr.s6_addr32[3],
512                                 sin6->sin6_addr.s6_addr32[3],
513                                 mask->sin6_addr.s6_addr32[3]))
514                         return 1;
515         }
516         return 0;
517 }
518 #else   /* !IPV6_SUPPORTED */
519 static int
520 check_subnet_v6(const struct sockaddr_in6 *UNUSED(address),
521                 const struct sockaddr_in6 *UNUSED(mask),
522                 const struct addrinfo *UNUSED(ai))
523 {
524         return 0;
525 }
526 #endif  /* !IPV6_SUPPORTED */
527
528 /*
529  * Check each address listed in @ai against the subnetwork or
530  * host address stored in @clp.  Return 1 if an address in @hp
531  * matches the host address stored in @clp, otherwise zero.
532  */
533 static int
534 check_subnetwork(const nfs_client *clp, const struct addrinfo *ai)
535 {
536         switch (get_addrlist(clp, 0)->sa_family) {
537         case AF_INET:
538                 return check_subnet_v4(get_addrlist_in(clp, 0),
539                                 get_addrlist_in(clp, 1), ai);
540         case AF_INET6:
541                 return check_subnet_v6(get_addrlist_in6(clp, 0),
542                                 get_addrlist_in6(clp, 1), ai);
543         }
544
545         return 0;
546 }
547
548 /*
549  * Check if a wildcard nfs_client record matches the canonical name
550  * or the aliases of a host.  Return 1 if a match is found, otherwise
551  * zero.
552  */
553 static int
554 check_wildcard(const nfs_client *clp, const struct addrinfo *ai)
555 {
556         char *cname = clp->m_hostname;
557         char *hname = ai->ai_canonname;
558         struct hostent *hp;
559         char **ap;
560
561         if (wildmat(hname, cname))
562                 return 1;
563
564         /* See if hname aliases listed in /etc/hosts or nis[+]
565          * match the requested wildcard */
566         hp = gethostbyname(hname);
567         if (hp != NULL) {
568                 for (ap = hp->h_aliases; *ap; ap++)
569                         if (wildmat(*ap, cname))
570                                 return 1;
571         }
572
573         return 0;
574 }
575
576 /*
577  * Check if @ai's hostname or aliases fall in a given netgroup.
578  * Return 1 if @ai represents a host in the netgroup, otherwise
579  * zero.
580  */
581 #ifdef HAVE_INNETGR
582 static int
583 check_netgroup(const nfs_client *clp, const struct addrinfo *ai)
584 {
585         const char *netgroup = clp->m_hostname + 1;
586         struct addrinfo *tmp = NULL;
587         struct hostent *hp;
588         char *dot, *hname;
589         int i, match;
590
591         match = 0;
592
593         hname = strdup(ai->ai_canonname);
594         if (hname == NULL) {
595                 xlog(D_GENERAL, "%s: no memory for strdup", __func__);
596                 goto out;
597         }
598
599         /* First, try to match the hostname without
600          * splitting off the domain */
601         if (innetgr(netgroup, hname, NULL, NULL)) {
602                 match = 1;
603                 goto out;
604         }
605
606         /* See if hname aliases listed in /etc/hosts or nis[+]
607          * match the requested netgroup */
608         hp = gethostbyname(hname);
609         if (hp != NULL) {
610                 for (i = 0; hp->h_aliases[i]; i++)
611                         if (innetgr(netgroup, hp->h_aliases[i], NULL, NULL)) {
612                                 match = 1;
613                                 goto out;
614                         }
615         }
616
617         /* If hname happens to be an IP address, convert it
618          * to a the canonical DNS name bound to this address. */
619         tmp = host_pton(hname);
620         if (tmp != NULL) {
621                 char *cname = host_canonname(tmp->ai_addr);
622                 freeaddrinfo(tmp);
623
624                 /* The resulting FQDN may be in our netgroup. */
625                 if (cname != NULL) {
626                         free(hname);
627                         hname = cname;
628                         if (innetgr(netgroup, hname, NULL, NULL)) {
629                                 match = 1;
630                                 goto out;
631                         }
632                 }
633         }
634
635         /* Okay, strip off the domain (if we have one) */
636         dot = strchr(hname, '.');
637         if (dot == NULL)
638                 goto out;
639
640         *dot = '\0';
641         match = innetgr(netgroup, hname, NULL, NULL);
642
643 out:
644         free(hname);
645         return match;
646 }
647 #else   /* !HAVE_INNETGR */
648 static int
649 check_netgroup(__attribute__((unused)) const nfs_client *clp,
650                 __attribute__((unused)) const struct addrinfo *ai)
651 {
652         return 0;
653 }
654 #endif  /* !HAVE_INNETGR */
655
656 /**
657  * client_check - check if IP address information matches a cached nfs_client
658  * @clp: pointer to a cached nfs_client record
659  * @ai: pointer to addrinfo to compare it with
660  *
661  * Returns 1 if the address information matches the cached nfs_client,
662  * otherwise zero.
663  */
664 int
665 client_check(const nfs_client *clp, const struct addrinfo *ai)
666 {
667         switch (clp->m_type) {
668         case MCL_FQDN:
669                 return check_fqdn(clp, ai);
670         case MCL_SUBNETWORK:
671                 return check_subnetwork(clp, ai);
672         case MCL_WILDCARD:
673                 return check_wildcard(clp, ai);
674         case MCL_NETGROUP:
675                 return check_netgroup(clp, ai);
676         case MCL_ANONYMOUS:
677                 return 1;
678         case MCL_GSS:
679                 return 0;
680         default:
681                 xlog(D_GENERAL, "%s: unrecognized client type: %d",
682                                 __func__, clp->m_type);
683         }
684
685         return 0;
686 }
687
688 /**
689  * client_gettype - determine type of nfs_client given an identifier
690  * @ident: '\0'-terminated ASCII string containing a client identifier
691  *
692  * Returns the type of nfs_client record that would be used for
693  * this client.
694  */
695 int
696 client_gettype(char *ident)
697 {
698         struct addrinfo *ai;
699         char *sp;
700
701         if (ident[0] == '\0' || strcmp(ident, "*")==0)
702                 return MCL_ANONYMOUS;
703         if (strncmp(ident, "gss/", 4) == 0)
704                 return MCL_GSS;
705         if (ident[0] == '@') {
706 #ifndef HAVE_INNETGR
707                 xlog(L_WARNING, "netgroup support not compiled in");
708 #endif
709                 return MCL_NETGROUP;
710         }
711         for (sp = ident; *sp; sp++) {
712                 if (*sp == '*' || *sp == '?' || *sp == '[')
713                         return MCL_WILDCARD;
714                 if (*sp == '/')
715                         return MCL_SUBNETWORK;
716                 if (*sp == '\\' && sp[1])
717                         sp++;
718         }
719
720         /*
721          * Treat unadorned IP addresses as MCL_SUBNETWORK.
722          * Everything else is MCL_FQDN.
723          */
724         ai = host_pton(ident);
725         if (ai != NULL) {
726                 freeaddrinfo(ai);
727                 return MCL_SUBNETWORK;
728         }
729
730         return MCL_FQDN;
731 }