21ad2724dab64cecb400cedf275b0b35332e19cf
[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
21 #include "misc.h"
22 #include "nfslib.h"
23 #include "exportfs.h"
24
25 /* netgroup stuff never seems to be defined in any header file. Linux is
26  * not alone in this.
27  */
28 #if !defined(__GLIBC__) || __GLIBC__ < 2
29 extern int      innetgr(char *netgr, char *host, char *, char *);
30 #endif
31
32 static char     *add_name(char *old, const char *add);
33
34 nfs_client      *clientlist[MCL_MAXTYPES] = { NULL, };
35
36
37 static void
38 init_addrlist(nfs_client *clp, const struct hostent *hp)
39 {
40         struct sockaddr_in sin = {
41                 .sin_family             = AF_INET,
42         };
43         char **ap;
44         int i;
45
46         if (hp == NULL)
47                 return;
48
49         ap = hp->h_addr_list;
50         for (i = 0; *ap != NULL && i < NFSCLNT_ADDRMAX; i++, ap++) {
51                 sin.sin_addr = *(struct in_addr *)*ap;
52                 set_addrlist_in(clp, i, &sin);
53         }
54         clp->m_naddr = i;
55 }
56
57 static void
58 client_free(nfs_client *clp)
59 {
60         free(clp->m_hostname);
61         free(clp);
62 }
63
64 static int
65 init_netmask(nfs_client *clp, const char *slash)
66 {
67         struct sockaddr_in sin = {
68                 .sin_family             = AF_INET,
69         };
70
71         if (strchr(slash + 1, '.') != NULL)
72                 sin.sin_addr.s_addr = inet_addr(slash + 1);
73         else {
74                 int prefixlen = atoi(slash + 1);
75                 if (0 < prefixlen && prefixlen <= 32)
76                         sin.sin_addr.s_addr =
77                                         htonl((uint32_t)~0 << (32 - prefixlen));
78                 else
79                         goto out_badprefix;
80         }
81
82         set_addrlist_in(clp, 1, &sin);
83         return 1;
84
85 out_badprefix:
86         xlog(L_ERROR, "Invalid prefix `%s' for %s", slash + 1, clp->m_hostname);
87         return 0;
88 }
89
90 static int
91 init_subnetwork(nfs_client *clp)
92 {
93         struct sockaddr_in sin = {
94                 .sin_family             = AF_INET,
95         };
96         static char slash32[] = "/32";
97         char *cp;
98
99         cp = strchr(clp->m_hostname, '/');
100         if (cp == NULL)
101                 cp = slash32;
102
103         *cp = '\0';
104         sin.sin_addr.s_addr = inet_addr(clp->m_hostname);
105         set_addrlist_in(clp, 0, &sin);
106         *cp = '/';
107
108         return init_netmask(clp, cp);
109 }
110
111 static int
112 client_init(nfs_client *clp, const char *hname, const struct hostent *hp)
113 {
114         clp->m_hostname = strdup(hname);
115         if (clp->m_hostname == NULL)
116                 return 0;
117
118         clp->m_exported = 0;
119         clp->m_count = 0;
120         clp->m_naddr = 0;
121
122         if (clp->m_type == MCL_SUBNETWORK)
123                 return init_subnetwork(clp);
124
125         init_addrlist(clp, hp);
126         return 1;
127 }
128
129 static void
130 client_add(nfs_client *clp)
131 {
132         nfs_client **cpp;
133
134         cpp = &clientlist[clp->m_type];
135         while (*cpp != NULL)
136                 cpp = &((*cpp)->m_next);
137         clp->m_next = NULL;
138         *cpp = clp;
139 }
140
141 /* if canonical is set, then we *know* this is already a canonical name
142  * so hostname lookup is avoided.
143  * This is used when reading /proc/fs/nfs/exports
144  */
145 nfs_client *
146 client_lookup(char *hname, int canonical)
147 {
148         nfs_client      *clp = NULL;
149         int             htype;
150         struct hostent  *hp = NULL;
151
152         htype = client_gettype(hname);
153
154         if (htype == MCL_FQDN && !canonical) {
155                 struct hostent *hp2;
156                 hp = gethostbyname(hname);
157                 if (hp == NULL || hp->h_addrtype != AF_INET) {
158                         xlog(L_ERROR, "%s has non-inet addr", hname);
159                         return NULL;
160                 }
161                 /* make sure we have canonical name */
162                 hp2 = hostent_dup(hp);
163                 hp = gethostbyaddr(hp2->h_addr, hp2->h_length,
164                                    hp2->h_addrtype);
165                 if (hp) {
166                         hp = hostent_dup(hp);
167                         /* but now we might not have all addresses... */
168                         if (hp2->h_addr_list[1]) {
169                                 struct hostent *hp3 =
170                                         gethostbyname(hp->h_name);
171                                 if (hp3) {
172                                         free(hp);
173                                         hp = hostent_dup(hp3);
174                                 }
175                         }
176                         free(hp2);
177                 } else
178                         hp = hp2;
179
180                 hname = (char *) hp->h_name;
181
182                 for (clp = clientlist[htype]; clp; clp = clp->m_next) {
183                         if (client_check(clp, hp))
184                                 break;
185                 }
186         } else {
187                 for (clp = clientlist[htype]; clp; clp = clp->m_next) {
188                         if (strcasecmp(hname, clp->m_hostname)==0)
189                                 break;
190                 }
191         }
192
193         if (clp == NULL) {
194                 clp = calloc(1, sizeof(*clp));
195                 if (clp == NULL)
196                         goto out;
197                 clp->m_type = htype;
198                 if (!client_init(clp, hname, NULL)) {
199                         client_free(clp);
200                         clp = NULL;
201                         goto out;
202                 }
203                 client_add(clp);
204         }
205
206         if (htype == MCL_FQDN && clp->m_naddr == 0)
207                 init_addrlist(clp, hp);
208
209 out:
210         if (hp)
211                 free (hp);
212
213         return clp;
214 }
215
216 nfs_client *
217 client_dup(nfs_client *clp, struct hostent *hp)
218 {
219         nfs_client              *new;
220
221         new = (nfs_client *)malloc(sizeof(*new));
222         if (new == NULL)
223                 return NULL;
224         memcpy(new, clp, sizeof(*new));
225         new->m_type = MCL_FQDN;
226         new->m_hostname = NULL;
227
228         if (!client_init(new, hp->h_name, hp)) {
229                 client_free(new);
230                 return NULL;
231         }
232         client_add(new);
233         return new;
234 }
235
236 void
237 client_release(nfs_client *clp)
238 {
239         if (clp->m_count <= 0)
240                 xlog(L_FATAL, "client_free: m_count <= 0!");
241         clp->m_count--;
242 }
243
244 void
245 client_freeall(void)
246 {
247         nfs_client      *clp, **head;
248         int             i;
249
250         for (i = 0; i < MCL_MAXTYPES; i++) {
251                 head = clientlist + i;
252                 while (*head) {
253                         *head = (clp = *head)->m_next;
254                         client_free(clp);
255                 }
256         }
257 }
258
259 struct hostent *
260 client_resolve(struct in_addr addr)
261 {
262         struct hostent *he = NULL;
263
264         if (clientlist[MCL_WILDCARD] || clientlist[MCL_NETGROUP])
265                 he = get_reliable_hostbyaddr((const char*)&addr, sizeof(addr), AF_INET);
266         if (he == NULL)
267                 he = get_hostent((const char*)&addr, sizeof(addr), AF_INET);
268
269         return he;
270 }
271
272 /**
273  * client_compose - Make a list of cached hostnames that match an IP address
274  * @he: pointer to hostent containing IP address information to match
275  *
276  * Gather all known client hostnames that match the IP address, and sort
277  * the result into a comma-separated list.
278  *
279  * Returns a '\0'-terminated ASCII string containing a comma-separated
280  * sorted list of client hostnames, or NULL if no client records matched
281  * the IP address or memory could not be allocated.  Caller must free the
282  * returned string with free(3).
283  */
284 char *
285 client_compose(struct hostent *he)
286 {
287         char *name = NULL;
288         int i;
289
290         for (i = 0 ; i < MCL_MAXTYPES; i++) {
291                 nfs_client      *clp;
292                 for (clp = clientlist[i]; clp ; clp = clp->m_next) {
293                         if (!client_check(clp, he))
294                                 continue;
295                         name = add_name(name, clp->m_hostname);
296                 }
297         }
298         return name;
299 }
300
301 /**
302  * client_member - check if @name is contained in the list @client
303  * @client: '\0'-terminated ASCII string containing
304  *              comma-separated list of hostnames
305  * @name: '\0'-terminated ASCII string containing hostname to look for
306  *
307  * Returns 1 if @name was found in @client, otherwise zero is returned.
308  */
309 int
310 client_member(const char *client, const char *name)
311 {
312         size_t l = strlen(name);
313
314         while (*client) {
315                 if (strncmp(client, name, l) == 0 &&
316                     (client[l] == ',' || client[l] == '\0'))
317                         return 1;
318                 client = strchr(client, ',');
319                 if (client == NULL)
320                         return 0;
321                 client++;
322         }
323         return 0;
324 }
325
326 static int
327 name_cmp(const char *a, const char *b)
328 {
329         /* compare strings a and b, but only upto ',' in a */
330         while (*a && *b && *a != ',' && *a == *b)
331                 a++, b++;
332         if (!*b && (!*a || *a == ','))
333                 return 0;
334         if (!*b) return 1;
335         if (!*a || *a == ',') return -1;
336         return *a - *b;
337 }
338
339 static char *
340 add_name(char *old, const char *add)
341 {
342         size_t len = strlen(add) + 2;
343         char *new;
344         char *cp;
345         if (old) len += strlen(old);
346         
347         new = malloc(len);
348         if (!new) {
349                 free(old);
350                 return NULL;
351         }
352         cp = old;
353         while (cp && *cp && name_cmp(cp, add) < 0) {
354                 /* step cp forward over a name */
355                 char *e = strchr(cp, ',');
356                 if (e)
357                         cp = e+1;
358                 else
359                         cp = cp + strlen(cp);
360         }
361         strncpy(new, old, cp-old);
362         new[cp-old] = 0;
363         if (cp != old && !*cp)
364                 strcat(new, ",");
365         strcat(new, add);
366         if (cp && *cp) {
367                 strcat(new, ",");
368                 strcat(new, cp);
369         }
370         free(old);
371         return new;
372 }
373
374 /*
375  * Check each address listed in @hp against each address
376  * stored in @clp.  Return 1 if a match is found, otherwise
377  * zero.
378  */
379 static int
380 check_fqdn(const nfs_client *clp, const struct hostent *hp)
381 {
382         const struct sockaddr_in *sin;
383         struct in_addr addr;
384         char **ap;
385         int i;
386
387         for (ap = hp->h_addr_list; *ap; ap++) {
388                 addr = *(struct in_addr *)*ap;
389
390                 for (i = 0; i < clp->m_naddr; i++) {
391                         sin = get_addrlist_in(clp, i);
392                         if (sin->sin_addr.s_addr == addr.s_addr)
393                                 return 1;
394                 }
395         }
396         return 0;
397 }
398
399 /*
400  * Check each address listed in @hp against the subnetwork or
401  * host address stored in @clp.  Return 1 if an address in @hp
402  * matches the host address stored in @clp, otherwise zero.
403  */
404 static int
405 check_subnetwork(const nfs_client *clp, const struct hostent *hp)
406 {
407         const struct sockaddr_in *address, *mask;
408         struct in_addr addr;
409         char **ap;
410
411         for (ap = hp->h_addr_list; *ap; ap++) {
412                 address = get_addrlist_in(clp, 0);
413                 mask = get_addrlist_in(clp, 1);
414                 addr = *(struct in_addr *)*ap;
415
416                 if (!((address->sin_addr.s_addr ^ addr.s_addr) &
417                       mask->sin_addr.s_addr))
418                         return 1;
419         }
420         return 0;
421 }
422
423 /*
424  * Check if a wildcard nfs_client record matches the canonical name
425  * or the aliases of a host.  Return 1 if a match is found, otherwise
426  * zero.
427  */
428 static int
429 check_wildcard(const nfs_client *clp, const struct hostent *hp)
430 {
431         char *cname = clp->m_hostname;
432         char *hname = hp->h_name;
433         char **ap;
434
435         if (wildmat(hname, cname))
436                 return 1;
437
438         /* See if hname aliases listed in /etc/hosts or nis[+]
439          * match the requested wildcard */
440         for (ap = hp->h_aliases; *ap; ap++) {
441                 if (wildmat(*ap, cname))
442                         return 1;
443         }
444
445         return 0;
446 }
447
448 /*
449  * Check if @hp's hostname or aliases fall in a given netgroup.
450  * Return 1 if @hp represents a host in the netgroup, otherwise zero.
451  */
452 #ifdef HAVE_INNETGR
453 static int
454 check_netgroup(const nfs_client *clp, const struct hostent *hp)
455 {
456         const char *netgroup = clp->m_hostname + 1;
457         const char *hname = hp->h_name;
458         struct hostent *nhp = NULL;
459         struct sockaddr_in addr;
460         int match, i;
461         char *dot;
462
463         /* First, try to match the hostname without
464          * splitting off the domain */
465         if (innetgr(netgroup, hname, NULL, NULL))
466                 return 1;
467
468         /* See if hname aliases listed in /etc/hosts or nis[+]
469          * match the requested netgroup */
470         for (i = 0; hp->h_aliases[i]; i++) {
471                 if (innetgr(netgroup, hp->h_aliases[i], NULL, NULL))
472                         return 1;
473         }
474
475         /* If hname is ip address convert to FQDN */
476         if (inet_aton(hname, &addr.sin_addr) &&
477            (nhp = gethostbyaddr((const char *)&(addr.sin_addr),
478             sizeof(addr.sin_addr), AF_INET))) {
479                 hname = nhp->h_name;
480                 if (innetgr(netgroup, hname, NULL, NULL))
481                         return 1;
482         }
483
484         /* Okay, strip off the domain (if we have one) */
485         dot = strchr(hname, '.');
486         if (dot == NULL)
487                 return 0;
488
489         *dot = '\0';
490         match = innetgr(netgroup, hname, NULL, NULL);
491         *dot = '.';
492
493         return match;
494 }
495 #else   /* !HAVE_INNETGR */
496 static int
497 check_netgroup(__attribute__((unused)) const nfs_client *clp,
498                 __attribute__((unused)) const struct hostent *hp)
499 {
500         return 0;
501 }
502 #endif  /* !HAVE_INNETGR */
503
504 /**
505  * client_check - check if IP address information matches a cached nfs_client
506  * @clp: pointer to a cached nfs_client record
507  * @hp: pointer to hostent containing host IP information
508  *
509  * Returns 1 if the address information matches the cached nfs_client,
510  * otherwise zero.
511  */
512 int
513 client_check(nfs_client *clp, struct hostent *hp)
514 {
515         switch (clp->m_type) {
516         case MCL_FQDN:
517                 return check_fqdn(clp, hp);
518         case MCL_SUBNETWORK:
519                 return check_subnetwork(clp, hp);
520         case MCL_WILDCARD:
521                 return check_wildcard(clp, hp);
522         case MCL_NETGROUP:
523                 return check_netgroup(clp, hp);
524         case MCL_ANONYMOUS:
525                 return 1;
526         case MCL_GSS:
527                 return 0;
528         default:
529                 xlog(D_GENERAL, "%s: unrecognized client type: %d",
530                                 __func__, clp->m_type);
531         }
532
533         return 0;
534 }
535
536 int
537 client_gettype(char *ident)
538 {
539         char    *sp;
540
541         if (ident[0] == '\0' || strcmp(ident, "*")==0)
542                 return MCL_ANONYMOUS;
543         if (strncmp(ident, "gss/", 4) == 0)
544                 return MCL_GSS;
545         if (ident[0] == '@') {
546 #ifndef HAVE_INNETGR
547                 xlog(L_WARNING, "netgroup support not compiled in");
548 #endif
549                 return MCL_NETGROUP;
550         }
551         for (sp = ident; *sp; sp++) {
552                 if (*sp == '*' || *sp == '?' || *sp == '[')
553                         return MCL_WILDCARD;
554                 if (*sp == '/')
555                         return MCL_SUBNETWORK;
556                 if (*sp == '\\' && sp[1])
557                         sp++;
558         }
559         /* check for N.N.N.N */
560         sp = ident;
561         if(!isdigit(*sp) || strtoul(sp, &sp, 10) > 255 || *sp != '.') return MCL_FQDN;
562         sp++; if(!isdigit(*sp) || strtoul(sp, &sp, 10) > 255 || *sp != '.') return MCL_FQDN;
563         sp++; if(!isdigit(*sp) || strtoul(sp, &sp, 10) > 255 || *sp != '.') return MCL_FQDN;
564         sp++; if(!isdigit(*sp) || strtoul(sp, &sp, 10) > 255 || *sp != '\0') return MCL_FQDN;
565         /* we lie here a bit. but technically N.N.N.N == N.N.N.N/32 :) */
566         return MCL_SUBNETWORK;
567 }