I hark back to the stuff that mountd does because I know I should be sharing code with it when I am done with the prototype. The problem facing me now is how do I parse (and verify) a network address and also do a comparison. Instead of stating what I think is happening, I'm going to walk down some code - look in usr/src/cmd/fs.d/nfs/mountd/mountd.c for the latest version of it.
First off, we know networks start with a '@', so let's look for that:
1380 /*
1381 * If the list name begins with an at
1382 * sign then do a network comparison.
1383 */
1384 if (*gr == '@') {
1385 if (netmatch(nb, gr + 1))
1386 return (response);
Okay, before we head down netmatch(), what is the context?
1312 /*
1313 * Determine whether an access list grants rights to a particular host.
1314 * We match on aliases of the hostname as well as on the canonical name.
1315 * Names in the access list may be either hosts or netgroups; they're
1316 * not distinguished syntactically. We check for hosts first because
1317 * it's cheaper (just M*N strcmp()s), then try netgroups.
1318 */
1319 int
1320 in_access_list(struct netbuf *nb, struct nd_hostservlist *clnames,
1321 char *access_list) /* N.B. we clobber this "input" parameter */
1322 {
...
1331
1332 /*
1333 * If no access list - then it's unrestricted
1334 */
1335 if (access_list == NULL || *access_list == '\0')
1336 return (1);
1337
1338 nentries = 0;
1339
1340 for (gr = strtok_r(access_list, ":", &lasts);
1341 gr != NULL; gr = strtok_r(NULL, ":", &lasts)) {
So we are walking down a ':' list of access list entries trying to determine whether or not a given client is in that list. Okay, right away I want to know if any verification has already occurred on that list. We can find one caller of in_access_list() here:
1552 /*
1553 * Given an export and the clients hostname(s)
1554 * determine the security flavors that this
1555 * client is permitted to use.
1556 *
1557 * This routine is called only for "old" syntax, i.e.
1558 * only one security flavor is allowed. So we need
1559 * to determine two things: the particular flavor,
1560 * and whether the client is allowed to use this
1561 * flavor, i.e. is in the access list.
1562 *
1563 * Note that if there is no access list, then the
1564 * default is that access is granted.
1565 */
1566 static int
1567 getclientsflavors_old(struct share *sh, struct netbuf *nb,
1568 struct nd_hostservlist *clnames, int *flavors)
1569 {
...
1573
1574 opts = strdup(sh->sh_opts);
...
1581 p = opts;
1582
1583 while (*p) {
1584
1585 switch (getsubopt(&p, optlist, &val)) {
...
1590 case OPT_RO:
1591 case OPT_RW:
1592 defaultaccess = 0;
1593 if (in_access_list(nb, clnames, val))
1594 ok++;
All of the instances seem to work on making a local copy of struct share *sh's sh_opts. I'm going to claim that this is a performance issue. I may back that up later. What I'm guessing is that we keep a string copy to make it easy to dump out the share and we claim that doing mount checks is rare. Which it may be, but remember that any NFS request may result in a door call from the kernel to do such a check. See nfsauth_svc() in this file.
So back to the question about whether the entry is validated when loaded into sh_opts? We can see that this code loads an entry:
729 static int
730 mount_enoent_error(char *path, char *rpath, struct nd_hostservlist *clnames,
731 struct netbuf *nb, int *flavor_list)
732 {
733 char *checkpath, *dp;
734 struct share *sh = NULL;
...
744 /* CONSTCOND */
745 while (1) {
746 if (sh) {
747 sharefree(sh);
748 sh = NULL;
749 }
750 if ((sh = findentry(rpath)) == NULL &&
751 (sh = find_lofsentry(rpath, &lofs_tried)) == NULL) {
Which in turn leads us to:
1074 struct share *
1075 findentry(char *path)
1076 {
1077 struct share *sh = NULL;
...
1083 check_sharetab();
1084
1085 (void) rw_rdlock(&sharetab_lock);
1086
1087 for (shp = share_list; shp; shp = shp->shl_next) {
1088 sh = shp->shl_sh;
And then:
1986 void
1987 check_sharetab()
1988 {
...
2030 /*
2031 * Note that since the sharetab is now in memory
2032 * and a snapshot is taken, we no longer have to
2033 * lock the file.
2034 */
2035 f = fopen(SHARETAB, "r");
...
2041
2042 /*
2043 * Once we are sure /etc/dfs/sharetab has been
2044 * modified, flush netgroup cache entries.
2045 */
2046 netgrp_cache_flush();
2047
2048 sh_free(share_list); /* free old list */
2049 share_list = NULL;
2050
2051 while ((res = getshare(f, &sh)) > 0) {
So, this can cause the sharetab(4) to be loaded (I've elided out the part that stat's it and returns if no change). And we have another lead down into getshare(), we are getting close. This rountine is actually over in usr/src/cmd/fs.d/nfs/lib/sharetab.c :
44 /*
45 * Get an entry from the share table.
46 * There should be at least 4 fields:
47 *
48 * pathname resource fstype options [ description ]
49 *
50 * A fifth field (description) is optional.
51 *
52 * Returns:
53 * > 1 valid entry
54 * = 0 end of file
55 * < 0 error
56 */
57 int
58 getshare(FILE *fd, share_t **shp)
...
91 sh->sh_opts = (char *)strtok_r(NULL, w, &lasts);
92 if (sh->sh_opts == NULL)
93 return (-3);
...
99 return (1);
100 }
So, no checking is done on the access list when it is loaded. By the way, I can see two "bugs" in the above code. On line 53, the comment says that "> 1" is returned if valid, yet we can see that '1' is returned. And on line 93, what does '-3' correspond to? I.e., it is a magic number.
Back to netmatch() and the comparison:
1406 int
1407 netmatch(struct netbuf *nb, char *name)
1408 {
1409 uint_t claddr;
...
1416 /*
1417 * Check if it's an IPv4 addr
1418 */
1419 if (nb->len != sizeof (struct sockaddr_in))
1420 return (0);
1421
1422 (void) memcpy(&claddr,
1423 /* LINTED pointer alignment */
1424 &((struct sockaddr_in *)nb->buf)->sin_addr.s_addr,
1425 sizeof (struct in_addr));
1426 claddr = ntohl(claddr);
So the netbuf is really a IPv4 sockaddr and the code states we do not handle mountd (or NFS requests which need verification) with IPv6. We also convert it to an uint_t claddr. Note we do that conversion each time in here, so if we had a huge list of networks, we would redo this many times.
And we do the conversion of the address here:
1432 if (isdigit(*name)) {
1433 /*
1434 * Convert a dotted IP address
1435 * to an IP address. The conversion
1436 * is not the same as that in inet_addr().
1437 */
1438 p = name;
1439 addr = 0;
1440 for (i = 0; i < 4; i++) {
1441 addr |= atoi(p) << ((3-i) * 8);
1442 p = strchr(p, '.');
1443 if (p == NULL)
1444 break;
1445 p++;
1446 }
So there is no checking to see that the address is valid. And we can see that here:
# share -F nfs -o rw=256.1.2.-13 /foo # share - /foo sec=sys,rw=256.1.2.-13 ""
And a simple program shows this in action:
% cat a.c #include#include #include int main (int argc, char *argv[]) { char *p = argv[1]; uint_t addr = 0; int i; for (i = 0; i < 4; i++) { addr |= atoi(p) << ((3-i) * 8); p = strchr(p, '.'); if (p == NULL) break; p++; } printf("%s yields %u\n", argv[1], addr); return (0); } % ./a.out 127.1.1.1 127.1.1.1 yields 2130772225 % ./a.out 192.168.2.1 192.168.2.1 yields 3232236033 % ./a.out 192.168.2.2 192.168.2.2 yields 3232236034 % ./a.out 256.1.2.-13 256.1.2.-13 yields 4294967283 % ./a.out 192.168.1.255 192.168.1.255 yields 3232236031 % ./a.out 192.168.1.256 192.168.1.256 yields 3232235776 % ./a.out 192.168..256 192.168..256 yields 3232235776 % ./a.out 192.168.1.-1 192.168.1.-1 yields 4294967295 % ./a.out 192.168.1.happy 192.168.1.happy yields 3232235776 % ./a.out 192.168.1.2happy 192.168.1.2happy yields 3232235778 % ./a.out 192helpme.168.1.2 192helpme.168.1.2 yields 3232235778 % ./a.out 192.168.1.2 192.168.1.2 yields 3232235778
The assumptions made in netmatch() would be okay if the share was verified correctly in getshare(). Note that I am not advocating keeping the share options as a ':' separated string. I think that sh_opts is to flat and causes too much parsing to be duplicated.
Also, I've mentioned in the past that OpenSolaris does not support single machine addresses (see [Open]Solaris and sharing subnets and single machines). We can finally see the reason here in netmatch():
1412 uint_t addr, mask;
...
1460 /*
1461 * If the mask is specified explicitly then
1462 * use that value, e.g.
1463 *
1464 * @109.104.56/28
1465 *
1466 * otherwise assume a mask from the zero octets
1467 * in the least significant bits of the address, e.g.
1468 *
1469 * @109.104 or @109.104.0.0
1470 */
1471 if (mp) {
1472 bits = atoi(mp);
1473 mask = bits ? ~0 << ((sizeof (struct in_addr) * NBBY) - bits)
1474 : 0;
1475 addr &= mask;
1476 } else {
1477 if ((addr & 0x00ffffff) == 0)
1478 mask = 0xff000000;
1479 else if ((addr & 0x0000ffff) == 0)
1480 mask = 0xffff0000;
1481 else if ((addr & 0x000000ff) == 0)
1482 mask = 0xffffff00;
1483 }
If we added this code after 1482:
1483 else 1484 mask = 0xffffffff;
then single host addresses would be handled correctly.
Okay, I happen to like the way that mountd() converts the network address and netmask to unsigned integers. I'm going to steal the code and modify it. I need the error checking to occur for me and I do not want to be parsing a string every time I do a check.