/* * Services SM_NOTIFY requests. * * When NLM uses an SM_MON request to tell statd to monitor a remote, * the request contains a "mon_name" argument. This is usually the * "caller_name" argument of an NLMPROC_LOCK request. On Linux, the * NLM can send statd the remote's IP address instead of its * caller_name. The NSM protocol does not allow both the remote's * caller_name and it's IP address to be sent in the same SM_MON * request. * * The remote's caller_name is useful because it makes it simple * to identify rebooting remotes by matching the "mon_name" argument * they sent via an SM_NOTIFY request. * * The caller_name string may not be a fully qualified domain name, * or even registered in the DNS database, however. Having the * remote's IP address is useful because then there is no ambiguity * about where to send an SM_NOTIFY after the local system reboots. * * Without the actual caller_name, however, statd must use an * heuristic to match an incoming SM_NOTIFY request to one of the * hosts it is currently monitoring. The incoming mon_name in an * SM_NOTIFY address is converted to a list of IP addresses using * DNS. Each mon_name on statd's monitor list is also converted to * an address list, and the two lists are checked to see if there is * a matching address. * * There are some risks to this strategy: * * 1. The external DNS database is not reliable. It can change * over time, or the forward and reverse mappings could be * inconsistent. * * 2. If statd's monitor list becomes substantial, finding a match * can generate a not inconsequential amount of DNS traffic. * * 3. statd is a single-threaded service. When DNS becomes slow or * unresponsive, statd also becomes slow or unresponsive. * * 4. If the remote does not have a DNS entry at all (or if the * remote can resolve itself, but the local host can't resolve * the remote's hostname), the remote cannot be monitored, and * therefore NLM locking cannot be provided for that host. * * 5. Local DNS resolution can produce different results for the * mon_name than the results the remote might see for the same * query, especially if the remote did not send a caller_name * or mon_name that is a fully qualified domain name. * * Note that a caller_name is passed from NFS client to server, * but the client never knows what mon_name the server might use * to notify it of a reboot. On Linux, the client extracts the * server's name from the devname it was passed by the mount * command. This is often not a fully-qualified domain name. */ void * sm_notify_1_svc(struct stat_chge *argp, struct svc_req *rqstp) { notify_list *lp, *call; static char *result = NULL; struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt); char ip_addr[INET6_ADDRSTRLEN]; xlog(D_CALL, "Received SM_NOTIFY from %s, state: %d", argp->mon_name, argp->state); /* quick check - don't bother if we're not monitoring anyone */ if (rtnl == NULL) { xlog_warn("SM_NOTIFY from %s while not monitoring any hosts", argp->mon_name); return ((void *) &result); } if (!statd_present_address(sap, ip_addr, sizeof(ip_addr))) { xlog_warn("Unrecognized sender address"); return ((void *) &result); } /* okir change: statd doesn't remove the remote host from its * internal monitor list when receiving an SM_NOTIFY call from * it. Lockd will want to continue monitoring the remote host * until it issues an SM_UNMON call. */ for (lp = rtnl ; lp ; lp = lp->next) if (NL_STATE(lp) != argp->state && (statd_matchhostname(argp->mon_name, lp->dns_name) || statd_matchhostname(ip_addr, lp->dns_name))) { NL_STATE(lp) = argp->state; call = nlist_clone(lp); nlist_insert(¬ify, call); } return ((void *) &result); }
/* * Services SM_MON requests. */ struct sm_stat_res * sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) { static sm_stat_res result; char *mon_name = argp->mon_id.mon_name, *my_name = argp->mon_id.my_id.my_name; struct my_id *id = &argp->mon_id.my_id; char *cp; notify_list *clnt; struct sockaddr_in my_addr = { .sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK), }; char *dnsname = NULL; xlog(D_CALL, "Received SM_MON for %s from %s", mon_name, my_name); /* Assume that we'll fail. */ result.res_stat = STAT_FAIL; result.state = -1; /* State is undefined for STAT_FAIL. */ /* 1. Reject any remote callers. * Ignore the my_name specified by the caller, and * use "127.0.0.1" instead. */ if (!caller_is_localhost(rqstp)) goto failure; /* 2. Reject any registrations for non-lockd services. * * This is specific to the linux kernel lockd, which * makes the callback procedure part of the lockd interface. * It is also prone to break when lockd changes its callback * procedure number -- which, in fact, has now happened once. * There must be a better way.... XXX FIXME */ if (id->my_prog != 100021 || (id->my_proc != 16 && id->my_proc != 24)) { xlog_warn("Attempt to register callback to %d/%d", id->my_prog, id->my_proc); goto failure; } /* * Check hostnames. If I can't look them up, I won't monitor. This * might not be legal, but it adds a little bit of safety and sanity. */ /* must check for /'s in hostname! See CERT's CA-96.09 for details. */ if (strchr(mon_name, '/') || mon_name[0] == '.') { xlog(L_ERROR, "SM_MON request for hostname containing '/' " "or starting '.': %s", mon_name); xlog(L_ERROR, "POSSIBLE SPOOF/ATTACK ATTEMPT!"); goto failure; } /* my_name must not have white space */ for (cp=my_name ; *cp ; cp++) if (*cp == ' ' || *cp == '\t' || *cp == '\r' || *cp == '\n') *cp = '_'; /* * Hostnames checked OK. * Now choose a hostname to use for matching. We cannot * really trust much in the incoming NOTIFY, so to make * sure that multi-homed hosts work nicely, we get an * FQDN now, and use that for matching. */ dnsname = statd_canonical_name(mon_name); if (dnsname == NULL) { xlog(L_WARNING, "No canonical hostname found for %s", mon_name); goto failure; } /* Now check to see if this is a duplicate, and warn if so. * I will also return STAT_FAIL. (I *think* this is how I should * handle it.) * * Olaf requests that I allow duplicate SM_MON requests for * hosts due to the way he is coding lockd. No problem, * I'll just do a quickie success return and things should * be happy. */ clnt = rtnl; while ((clnt = nlist_gethost(clnt, mon_name, 0))) { if (statd_matchhostname(NL_MY_NAME(clnt), my_name) && NL_MY_PROC(clnt) == id->my_proc && NL_MY_PROG(clnt) == id->my_prog && NL_MY_VERS(clnt) == id->my_vers && memcmp(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE) == 0) { /* Hey! We already know you guys! */ xlog(D_GENERAL, "Duplicate SM_MON request for %s " "from procedure on %s", mon_name, my_name); /* But we'll let you pass anyway. */ free(dnsname); goto success; } clnt = NL_NEXT(clnt); } /* * We're committed...ignoring errors. Let's hope that a malloc() * doesn't fail. (I should probably fix this assumption.) */ if (!(clnt = nlist_new(my_name, mon_name, 0))) { free(dnsname); xlog_warn("out of memory"); goto failure; } NL_MY_PROG(clnt) = id->my_prog; NL_MY_VERS(clnt) = id->my_vers; NL_MY_PROC(clnt) = id->my_proc; memcpy(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE); clnt->dns_name = dnsname; /* * Now, Create file on stable storage for host. */ if (!nsm_insert_monitored_host(dnsname, (struct sockaddr *)(char *)&my_addr, argp)) { nlist_free(NULL, clnt); goto failure; } /* PRC: do the HA callout: */ ha_callout("add-client", mon_name, my_name, -1); nlist_insert(&rtnl, clnt); xlog(D_GENERAL, "MONITORING %s for %s", mon_name, my_name); success: result.res_stat = STAT_SUCC; /* SUN's sm_inter.x says this should be "state number of local site". * X/Open says '"state" will be contain the state of the remote NSM.' * href=http://www.opengroup.org/onlinepubs/9629799/SM_MON.htm * Linux lockd currently (2.6.21 and prior) ignores whatever is * returned, and given the above contraction, it probably always will.. * So we just return what we always returned. If possible, we * have already told lockd about our state number via a sysctl. * If lockd wants the remote state, it will need to * use SM_STAT (and prayer). */ result.state = MY_STATE; return (&result); failure: xlog_warn("STAT_FAIL to %s for SM_MON of %s", my_name, mon_name); return (&result); } static unsigned int load_one_host(const char *hostname, __attribute__ ((unused)) const struct sockaddr *sap, const struct mon *m, __attribute__ ((unused)) const time_t timestamp) { notify_list *clnt; clnt = nlist_new(m->mon_id.my_id.my_name, m->mon_id.mon_name, 0); if (clnt == NULL) return 0; clnt->dns_name = strdup(hostname); if (clnt->dns_name == NULL) { nlist_free(NULL, clnt); return 0; } xlog(D_GENERAL, "Adding record for %s to the monitor list...", hostname); NL_MY_PROG(clnt) = m->mon_id.my_id.my_prog; NL_MY_VERS(clnt) = m->mon_id.my_id.my_vers; NL_MY_PROC(clnt) = m->mon_id.my_id.my_proc; memcpy(NL_PRIV(clnt), m->priv, SM_PRIV_SIZE); nlist_insert(&rtnl, clnt); return 1; } void load_state(void) { unsigned int count; count = nsm_load_monitor_list(load_one_host); if (count) xlog(D_GENERAL, "Loaded %u previously monitored hosts"); }
/* * Services SM_MON requests. */ struct sm_stat_res * sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) { static sm_stat_res result; char *mon_name = argp->mon_id.mon_name, *my_name = argp->mon_id.my_id.my_name; struct my_id *id = &argp->mon_id.my_id; char *path; char *cp; int fd; notify_list *clnt; struct in_addr my_addr; char *dnsname; struct hostent *hostinfo = NULL; /* Assume that we'll fail. */ result.res_stat = STAT_FAIL; result.state = -1; /* State is undefined for STAT_FAIL. */ /* 1. Reject any remote callers. * Ignore the my_name specified by the caller, and * use "127.0.0.1" instead. */ if (!caller_is_localhost(rqstp)) goto failure; my_addr.s_addr = htonl(INADDR_LOOPBACK); /* 2. Reject any registrations for non-lockd services. * * This is specific to the linux kernel lockd, which * makes the callback procedure part of the lockd interface. * It is also prone to break when lockd changes its callback * procedure number -- which, in fact, has now happened once. * There must be a better way.... XXX FIXME */ if (id->my_prog != 100021 || (id->my_proc != 16 && id->my_proc != 24)) { note(N_WARNING, "Attempt to register callback to %d/%d", id->my_prog, id->my_proc); goto failure; } /* * Check hostnames. If I can't look them up, I won't monitor. This * might not be legal, but it adds a little bit of safety and sanity. */ /* must check for /'s in hostname! See CERT's CA-96.09 for details. */ if (strchr(mon_name, '/') || mon_name[0] == '.') { note(N_CRIT, "SM_MON request for hostname containing '/' " "or starting '.': %s", mon_name); note(N_CRIT, "POSSIBLE SPOOF/ATTACK ATTEMPT!"); goto failure; } else if ((hostinfo = gethostbyname(mon_name)) == NULL) { note(N_WARNING, "gethostbyname error for %s", mon_name); goto failure; } /* my_name must not have white space */ for (cp=my_name ; *cp ; cp++) if (*cp == ' ' || *cp == '\t' || *cp == '\r' || *cp == '\n') *cp = '_'; /* * Hostnames checked OK. * Now choose a hostname to use for matching. We cannot * really trust much in the incoming NOTIFY, so to make * sure that multi-homed hosts work nicely, we get an * FQDN now, and use that for matching */ hostinfo = gethostbyaddr(hostinfo->h_addr, hostinfo->h_length, hostinfo->h_addrtype); if (hostinfo) dnsname = xstrdup(hostinfo->h_name); else dnsname = xstrdup(my_name); /* Now check to see if this is a duplicate, and warn if so. * I will also return STAT_FAIL. (I *think* this is how I should * handle it.) * * Olaf requests that I allow duplicate SM_MON requests for * hosts due to the way he is coding lockd. No problem, * I'll just do a quickie success return and things should * be happy. */ clnt = rtnl; while ((clnt = nlist_gethost(clnt, mon_name, 0))) { if (matchhostname(NL_MY_NAME(clnt), my_name) && NL_MY_PROC(clnt) == id->my_proc && NL_MY_PROG(clnt) == id->my_prog && NL_MY_VERS(clnt) == id->my_vers && memcmp(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE) == 0) { /* Hey! We already know you guys! */ dprintf(N_DEBUG, "Duplicate SM_MON request for %s " "from procedure on %s", mon_name, my_name); /* But we'll let you pass anyway. */ goto success; } clnt = NL_NEXT(clnt); } /* * We're committed...ignoring errors. Let's hope that a malloc() * doesn't fail. (I should probably fix this assumption.) */ if (!(clnt = nlist_new(my_name, mon_name, 0))) { note(N_WARNING, "out of memory"); goto failure; } NL_ADDR(clnt) = my_addr; NL_MY_PROG(clnt) = id->my_prog; NL_MY_VERS(clnt) = id->my_vers; NL_MY_PROC(clnt) = id->my_proc; memcpy(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE); clnt->dns_name = dnsname; /* * Now, Create file on stable storage for host. */ path=xmalloc(strlen(SM_DIR)+strlen(dnsname)+2); sprintf(path, "%s/%s", SM_DIR, dnsname); if ((fd = open(path, O_WRONLY|O_SYNC|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR)) < 0) { /* Didn't fly. We won't monitor. */ note(N_ERROR, "creat(%s) failed: %s", path, strerror (errno)); nlist_free(NULL, clnt); free(path); goto failure; } { char buf[LINELEN + 1 + SM_MAXSTRLEN*2 + 4]; char *e; int i; e = buf + sprintf(buf, "%08x %08x %08x %08x ", my_addr.s_addr, id->my_prog, id->my_vers, id->my_proc); for (i=0; i<SM_PRIV_SIZE; i++) e += sprintf(e, "%02x", 0xff & (argp->priv[i])); if (e+1-buf != LINELEN) abort(); e += sprintf(e, " %s %s\n", mon_name, my_name); if (write(fd, buf, e-buf) != (e-buf)) { note(N_WARNING, "writing to %s failed: errno %d (%s)", path, errno, strerror(errno)); } } free(path); /* PRC: do the HA callout: */ ha_callout("add-client", mon_name, my_name, -1); nlist_insert(&rtnl, clnt); close(fd); dprintf(N_DEBUG, "MONITORING %s for %s", mon_name, my_name); success: result.res_stat = STAT_SUCC; /* SUN's sm_inter.x says this should be "state number of local site". * X/Open says '"state" will be contain the state of the remote NSM.' * href=http://www.opengroup.org/onlinepubs/9629799/SM_MON.htm * Linux lockd currently (2.6.21 and prior) ignores whatever is * returned, and given the above contraction, it probably always will.. * So we just return what we always returned. If possible, we * have already told lockd about our state number via a sysctl. * If lockd wants the remote state, it will need to * use SM_STAT (and prayer). */ result.state = MY_STATE; return (&result); failure: note(N_WARNING, "STAT_FAIL to %s for SM_MON of %s", my_name, mon_name); return (&result); }
void load_state(void) { DIR *d; struct dirent *de; char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; d = opendir(SM_DIR); if (!d) return; while ((de = readdir(d))) { char *path; FILE *f; int p; if (de->d_name[0] == '.') continue; path = xmalloc(strlen(SM_DIR)+strlen(de->d_name)+2); sprintf(path, "%s/%s", SM_DIR, de->d_name); f = fopen(path, "r"); free(path); if (f == NULL) continue; while (fgets(buf, sizeof(buf), f) != NULL) { int addr, proc, prog, vers; char priv[SM_PRIV_SIZE]; char *monname, *myname; char *b; int i; notify_list *clnt; buf[sizeof(buf)-1] = 0; b = strchr(buf, '\n'); if (b) *b = 0; sscanf(buf, "%x %x %x %x ", &addr, &prog, &vers, &proc); b = buf+36; for (i=0; i<SM_PRIV_SIZE; i++) { sscanf(b, "%2x", &p); priv[i] = p; b += 2; } b++; monname = b; while (*b && *b != ' ') b++; if (*b) *b++ = '\0'; while (*b == ' ') b++; myname = b; clnt = nlist_new(myname, monname, 0); if (!clnt) break; NL_ADDR(clnt).s_addr = addr; NL_MY_PROG(clnt) = prog; NL_MY_VERS(clnt) = vers; NL_MY_PROC(clnt) = proc; clnt->dns_name = xstrdup(de->d_name); memcpy(NL_PRIV(clnt), priv, SM_PRIV_SIZE); nlist_insert(&rtnl, clnt); } fclose(f); } closedir(d); }