/** * nsm_update_kernel_state - attempt to post new NSM state to kernel * @state: NSM state number * */ void nsm_update_kernel_state(const int state) { ssize_t result; char buf[20]; int fd, len; fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY); if (fd == -1) { xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m"); return; } len = snprintf(buf, sizeof(buf), "%d", state); if (error_check(len, sizeof(buf))) { xlog_warn("Failed to form NSM state number string"); return; } result = write(fd, buf, strlen(buf)); if (exact_error_check(result, strlen(buf))) xlog_warn("Failed to write NSM state number: %m"); if (close(fd) == -1) xlog(L_ERROR, "Failed to close NSM state file " NSM_KERNEL_STATE_FILE ": %m"); }
/** * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/" * * Returns the count of host records that were moved. * * Note that if any error occurs during this process, some monitor * records may be left in the "sm" directory. */ unsigned int nsm_retire_monitored_hosts(void) { unsigned int count = 0; struct dirent *de; char *path; DIR *dir; path = nsm_make_pathname(NSM_MONITOR_DIR); if (path == NULL) { xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR); return count; } dir = opendir(path); free(path); if (dir == NULL) { xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m"); return count; } while ((de = readdir(dir)) != NULL) { char *src, *dst; if (de->d_type != (unsigned char)DT_REG) continue; if (de->d_name[0] == '.') continue; src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name); if (src == NULL) { xlog_warn("Bad monitor file name, skipping"); continue; } dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name); if (dst == NULL) { free(src); xlog_warn("Bad notify file name, skipping"); continue; } if (rename(src, dst) == -1) xlog_warn("Failed to rename %s -> %s: %m", src, dst); else { xlog(D_GENERAL, "Retired record for mon_name %s", de->d_name); count++; } free(dst); free(src); } (void)closedir(dir); return count; }
/* find_local - find all IP addresses for this host */ static int find_local(void) { struct ifconf ifc; struct ifreq ifreq; struct ifreq *ifr; struct ifreq *the_end; int sock; char buf[BUFSIZ]; /* * Get list of network interfaces. We use a huge buffer to allow for the * presence of non-IP interfaces. */ if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { xlog_warn("%s: socket(2): %m", __func__); return (0); } ifc.ifc_len = sizeof(buf); ifc.ifc_buf = buf; if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) { xlog_warn("%s: ioctl(SIOCGIFCONF): %m", __func__); (void) close(sock); return (0); } /* Get IP address of each active IP network interface. */ the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len); num_local = 0; for (ifr = ifc.ifc_req; ifr < the_end; ifr++) { if (ifr->ifr_addr.sa_family == AF_INET) { /* IP net interface */ ifreq = *ifr; if (ioctl(sock, SIOCGIFFLAGS, (char *) &ifreq) < 0) { xlog_warn("%s: ioctl(SIOCGIFFLAGS): %m", __func__); } else if (ifreq.ifr_flags & IFF_UP) { /* active interface */ if (ioctl(sock, SIOCGIFADDR, (char *) &ifreq) < 0) { xlog_warn("%s: ioctl(SIOCGIFADDR): %m", __func__); } else { if (num_local >= num_addrs) if (grow_addrs() == 0) break; addrs[num_local++] = ((struct sockaddr_in *) & ifreq.ifr_addr)->sin_addr; } } } /* Support for variable-length addresses. */ #ifdef HAS_SA_LEN ifr = (struct ifreq *) ((caddr_t) ifr + ifr->ifr_addr.sa_len - sizeof(struct sockaddr)); #endif } (void) close(sock); return (num_local); }
static notify_list * recv_rply(u_long *portp) { char msgbuf[NSM_MAXMSGSIZE]; ssize_t msglen; notify_list *lp = NULL; XDR xdr; struct sockaddr_in sin; socklen_t alen = (socklen_t)sizeof(sin); uint32_t xid; memset(msgbuf, 0, sizeof(msgbuf)); msglen = recvfrom(sockfd, msgbuf, sizeof(msgbuf), 0, (struct sockaddr *)(char *)&sin, &alen); if (msglen == (ssize_t)-1) { xlog_warn("%s: recvfrom failed: %m", __func__); return NULL; } memset(&xdr, 0, sizeof(xdr)); xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE); xid = nsm_parse_reply(&xdr); if (xid == 0) goto done; if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { struct in_addr addr = sin.sin_addr; char buf[INET_ADDRSTRLEN]; xlog_warn("%s: Unrecognized reply from %s", __func__, inet_ntop(AF_INET, &addr, buf, (socklen_t)sizeof(buf))); goto done; } for (lp = notify; lp != NULL; lp = lp->next) { /* LH - this was a bug... it should have been checking * the xid from the response message from the client, * not the static, internal xid */ if (lp->xid != xid) continue; if (lp->port == 0) *portp = nsm_recv_getport(&xdr); break; } done: xdr_destroy(&xdr); return lp; }
static int grow_addrs(void) { struct in_addr *new_addrs; int new_num; /* * Keep the previous result if we run out of memory. The system would * really get hosed if we simply give up. */ new_num = (addrs == 0) ? 1 : num_addrs + num_addrs; new_addrs = (struct in_addr *) malloc(sizeof(*addrs) * new_num); if (new_addrs == 0) { xlog_warn("%s: out of memory", __func__); return (0); } else { if (addrs != 0) { memcpy((char *) new_addrs, (char *) addrs, sizeof(*addrs) * num_addrs); free((char *) addrs); } num_addrs = new_num; addrs = new_addrs; return (1); } }
/* * Process a datagram received on the notify socket */ int process_reply(FD_SET_TYPE *rfds) { notify_list *lp; u_long port; if (sockfd == -1 || !FD_ISSET(sockfd, rfds)) return 0; if (!(lp = recv_rply(&port))) return 1; if (lp->port == 0) { if (port != 0) { lp->port = htons((unsigned short) port); process_entry(lp); NL_WHEN(lp) = time(NULL) + NOTIFY_TIMEOUT; nlist_remove(¬ify, lp); nlist_insert_timer(¬ify, lp); return 1; } xlog_warn("%s: service %d not registered on localhost", __func__, NL_MY_PROG(lp)); } else { xlog(D_GENERAL, "%s: Callback to %s (for %d) succeeded", __func__, NL_MY_NAME(lp), NL_MON_NAME(lp)); } nlist_free(¬ify, lp); return 1; }
/** * nsm_insert_monitored_host - write callback data for one host to disk * @hostname: C string containing a hostname * @sap: sockaddr containing NLM callback address * @mon: SM_MON arguments to save * * Returns true if successful, otherwise false if some error occurs. */ _Bool nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap, const struct mon *m) { static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; char *path; _Bool result = false; ssize_t len; size_t size; int fd; path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname); if (path == NULL) { xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'", hostname); return false; } size = nsm_create_monitor_record(buf, sizeof(buf), sap, m); if (size == 0) { xlog(L_ERROR, "Failed to insert: record too long"); goto out; } /* * If exclusive create fails, we're adding a new line to an * existing file. */ fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_SYNC, S_IRUSR | S_IWUSR); if (fd == -1) { if (errno != EEXIST) { xlog(L_ERROR, "Failed to insert: creating %s: %m", path); goto out; } result = nsm_append_monitored_host(path, buf); goto out; } result = true; len = write(fd, buf, size); if (exact_error_check(len, size)) { xlog_warn("Failed to insert: writing %s: %m", path); (void)unlink(path); result = false; } if (close(fd) == -1) { xlog(L_ERROR, "Failed to insert: closing %s: %m", path); (void)unlink(path); result = false; } out: free(path); return result; }
struct sm_stat * sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) { short int count = 0; static sm_stat result; notify_list *clnt; char *my_name = argp->my_name; xlog(D_CALL, "Received SM_UNMON_ALL for %s", my_name); if (!caller_is_localhost(rqstp)) goto failure; result.state = MY_STATE; if (rtnl == NULL) { xlog_warn("Received SM_UNMON_ALL request from %s " "while not monitoring any hosts", my_name); return (&result); } clnt = rtnl; while ((clnt = nlist_gethost(clnt, my_name, 1))) { if (NL_MY_PROC(clnt) == argp->my_proc && NL_MY_PROG(clnt) == argp->my_prog && NL_MY_VERS(clnt) == argp->my_vers) { /* Watch stack! */ char mon_name[SM_MAXSTRLEN + 1]; notify_list *temp; xlog(D_GENERAL, "UNMONITORING (SM_UNMON_ALL) %s for %s", NL_MON_NAME(clnt), NL_MY_NAME(clnt)); strncpy(mon_name, NL_MON_NAME(clnt), sizeof (mon_name) - 1); mon_name[sizeof (mon_name) - 1] = '\0'; temp = NL_NEXT(clnt); /* PRC: do the HA callout: */ ha_callout("del-client", mon_name, my_name, -1); nsm_delete_monitored_host(clnt->dns_name, mon_name, my_name); nlist_free(&rtnl, clnt); ++count; clnt = temp; } else clnt = NL_NEXT(clnt); } if (!count) { xlog(D_GENERAL, "SM_UNMON_ALL request from %s with no " "SM_MON requests from it", my_name); } failure: return (&result); }
/** * nsm_get_state - retrieve on-disk NSM state number * * Returns an odd NSM state number read from disk, or an initial * state number. Zero is returned if some error occurs. */ int nsm_get_state(_Bool update) { int fd, state = 0; ssize_t result; char *path = NULL; path = nsm_make_pathname(NSM_STATE_FILE); if (path == NULL) { xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE); goto out; } fd = open(path, O_RDONLY); if (fd == -1) { if (errno != ENOENT) { xlog(L_ERROR, "Failed to open %s: %m", path); goto out; } xlog(L_NOTICE, "Initializing NSM state"); state = 1; update = true; goto update; } result = read(fd, &state, sizeof(state)); if (exact_error_check(result, sizeof(state))) { xlog_warn("Failed to read %s: %m", path); xlog(L_NOTICE, "Initializing NSM state"); state = 1; update = true; goto update; } if ((state & 1) == 0) state++; update: if(fd >= 0) (void)close(fd); if (update) { state += 2; if (!nsm_atomic_write(path, &state, sizeof(state))) state = 0; } out: free(path); return state; }
/* * 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); }
/* * Reject requests from non-loopback addresses in order * to prevent attack described in CERT CA-99.05. * * Although the kernel contacts the statd service via only IPv4 * transports, the statd service can receive other requests, such * as SM_NOTIFY, from remote peers via IPv6. */ static _Bool caller_is_localhost(struct svc_req *rqstp) { struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt); char buf[INET6_ADDRSTRLEN]; if (!nfs_is_v4_loopback(sap)) goto out_nonlocal; return true; out_nonlocal: if (!statd_present_address(sap, buf, sizeof(buf))) buf[0] = '\0'; xlog_warn("SM_MON/SM_UNMON call from non-local host %s", buf); return false; }
/* * Notify operation for a single list entry */ static int process_entry(notify_list *lp) { struct sockaddr_in sin; if (NL_TIMES(lp) == 0) { xlog(D_GENERAL, "%s: Cannot notify localhost, giving up", __func__); return 0; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = lp->port; /* LH - moved address into switch */ /* __FORCE__ loopback for callbacks to lockd ... */ /* Just in case we somehow ignored it thus far */ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (sin.sin_port == 0) lp->xid = nsm_xmit_getport(sockfd, &sin, (rpcprog_t)NL_MY_PROG(lp), (rpcvers_t)NL_MY_VERS(lp)); else { struct mon m; memcpy(m.priv, NL_PRIV(lp), SM_PRIV_SIZE); m.mon_id.mon_name = NL_MON_NAME(lp); m.mon_id.my_id.my_name = NULL; m.mon_id.my_id.my_prog = NL_MY_PROG(lp); m.mon_id.my_id.my_vers = NL_MY_VERS(lp); m.mon_id.my_id.my_proc = NL_MY_PROC(lp); lp->xid = nsm_xmit_nlmcall(sockfd, (struct sockaddr *)(char *)&sin, (socklen_t)sizeof(sin), &m, NL_STATE(lp)); } if (lp->xid == 0) { xlog_warn("%s: failed to notify port %d", __func__, ntohs(lp->port)); } NL_TIMES(lp) -= 1; return 1; }
/* * Clear all the keys on the given keyring */ static int keyring_clear(const char *keyring) { key_serial_t key; key = find_key_by_type_and_desc("keyring", keyring, 0); if (key == -1) { xlog_err("'%s' keyring was not found.", keyring); return EXIT_FAILURE; } if (keyctl_clear(key) < 0) { xlog_err("keyctl_clear(0x%x) failed: %m", (unsigned int)key); return EXIT_FAILURE; } if (verbose) xlog_warn("'%s' cleared", keyring); return EXIT_SUCCESS; }
/* * Clear all the keys on the given keyring */ static int keyring_clear(char *keyring) { FILE *fp; char buf[BUFSIZ]; key_serial_t key; if (keyring == NULL) keyring = DEFAULT_KEYRING; if ((fp = fopen(PROCKEYS, "r")) == NULL) { xlog_err("fopen(%s) failed: %m", PROCKEYS); return 1; } while(fgets(buf, BUFSIZ, fp) != NULL) { if (strstr(buf, "keyring") == NULL) continue; if (strstr(buf, keyring) == NULL) continue; if (verbose) { *(strchr(buf, '\n')) = '\0'; xlog_warn("clearing '%s'", buf); } /* * The key is the first arugment in the string */ *(strchr(buf, ' ')) = '\0'; sscanf(buf, "%x", &key); if (keyctl_clear(key) < 0) { xlog_err("keyctl_clear(0x%x) failed: %m", key); fclose(fp); return 1; } fclose(fp); return 0; } xlog_err("'%s' keyring was not found.", keyring); fclose(fp); return 1; }
/** * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/" * * Returns the count of host records that were moved. * * Note that if any error occurs during this process, some monitor * records may be left in the "sm" directory. */ unsigned int nsm_retire_monitored_hosts(void) { unsigned int count = 0; struct dirent *de; char *path; DIR *dir; path = nsm_make_pathname(NSM_MONITOR_DIR); if (path == NULL) { xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR); return count; } dir = opendir(path); free(path); if (dir == NULL) { xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m"); return count; } while ((de = readdir(dir)) != NULL) { char *src, *dst; struct stat stb; if (de->d_name[0] == '.') continue; src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name); if (src == NULL) { xlog_warn("Bad monitor file name, skipping"); continue; } /* NB: not all file systems fill in d_type correctly */ if (lstat(src, &stb) == -1) { xlog_warn("Bad monitor file %s, skipping: %m", de->d_name); free(src); continue; } if (!S_ISREG(stb.st_mode)) { xlog(D_GENERAL, "Skipping non-regular file %s", de->d_name); free(src); continue; } dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name); if (dst == NULL) { free(src); xlog_warn("Bad notify file name, skipping"); continue; } if (rename(src, dst) == -1) xlog_warn("Failed to rename %s -> %s: %m", src, dst); else { xlog(D_GENERAL, "Retired record for mon_name %s", de->d_name); count++; } free(dst); free(src); } (void)closedir(dir); return count; }
/* * Revoke a key */ static int key_invalidate(char *keystr, int keymask) { FILE *fp; char buf[BUFSIZ], *ptr; key_serial_t key; int mask; xlog_syslog(0); if ((fp = fopen(PROCKEYS, "r")) == NULL) { xlog_err("fopen(%s) failed: %m", PROCKEYS); return EXIT_FAILURE; } while(fgets(buf, BUFSIZ, fp) != NULL) { if (strstr(buf, "keyring") != NULL) continue; mask = 0; if ((ptr = strstr(buf, "uid:")) != NULL) mask = UIDKEYS; else if ((ptr = strstr(buf, "gid:")) != NULL) mask = GIDKEYS; else continue; if ((keymask & mask) == 0) continue; if (strncmp(ptr+4, keystr, strlen(keystr)) != 0) continue; if (verbose) { *(strchr(buf, '\n')) = '\0'; xlog_warn("invalidating '%s'", buf); } /* * The key is the first arugment in the string */ *(strchr(buf, ' ')) = '\0'; sscanf(buf, "%x", &key); /* older libkeyutils compatibility */ #ifndef KEYCTL_INVALIDATE #define KEYCTL_INVALIDATE 21 /* invalidate a key */ #endif if (keyctl(KEYCTL_INVALIDATE, key) < 0) { if (errno != EOPNOTSUPP) { xlog_err("keyctl_invalidate(0x%x) failed: %m", key); fclose(fp); return EXIT_FAILURE; } else { /* older kernel compatibility attempt: */ if (keyctl_revoke(key) < 0) { xlog_err("keyctl_revoke(0x%x) failed: %m", key); fclose(fp); return EXIT_FAILURE; } } } keymask &= ~mask; if (keymask == 0) { fclose(fp); return EXIT_SUCCESS; } } xlog_err("'%s' key was not found.", keystr); fclose(fp); return EXIT_FAILURE; }
/** * nsm_drop_privileges - drop root privileges * @pidfd: file descriptor of a pid file * * Returns true if successful, or false if some error occurred. * * Set our effective UID and GID to that of our on-disk database. */ _Bool nsm_drop_privileges(const int pidfd) { struct stat st; (void)umask(S_IRWXO); /* * XXX: If we can't stat dirname, or if dirname is owned by * root, we should use "statduser" instead, which is set up * by configure.ac. Nothing in nfs-utils seems to use * "statduser," though. */ if (lstat(nsm_base_dirname, &st) == -1) { xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname); return false; } if (chdir(nsm_base_dirname) == -1) { xlog(L_ERROR, "Failed to change working directory to %s: %m", nsm_base_dirname); return false; } if (!prune_bounding_set()) return false; if (st.st_uid == 0) { xlog_warn("Running as root. " "chown %s to choose different user", nsm_base_dirname); return true; } /* * If the pidfile happens to reside on NFS, dropping privileges * will probably cause us to lose access, even though we are * holding it open. Chown it to prevent this. */ if (pidfd >= 0) if (fchown(pidfd, st.st_uid, st.st_gid) == -1) xlog_warn("Failed to change owner of pidfile: %m"); /* * Don't clear capabilities when dropping root. */ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { xlog(L_ERROR, "prctl(PR_SET_KEEPCAPS) failed: %m"); return false; } if (setgroups(0, NULL) == -1) { xlog(L_ERROR, "Failed to drop supplementary groups: %m"); return false; } /* * ORDER * * setgid(2) first, as setuid(2) may remove privileges needed * to set the group id. */ if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) { xlog(L_ERROR, "Failed to drop privileges: %m"); return false; } xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid); return nsm_clear_capabilities(); }
/* * 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_UNMON requests. * * There is no statement in the X/Open spec's about returning an error * for requests to unmonitor a host that we're *not* monitoring. I just * return the state of the NSM when I get such foolish requests for lack * of any better ideas. (I also log the "offense.") */ struct sm_stat * sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) { static sm_stat result; notify_list *clnt; char *mon_name = argp->mon_name, *my_name = argp->my_id.my_name; struct my_id *id = &argp->my_id; char *cp; xlog(D_CALL, "Received SM_UNMON for %s from %s", mon_name, my_name); result.state = MY_STATE; if (!caller_is_localhost(rqstp)) goto failure; /* my_name must not have white space */ for (cp=my_name ; *cp ; cp++) if (*cp == ' ' || *cp == '\t' || *cp == '\r' || *cp == '\n') *cp = '_'; /* Check if we're monitoring anyone. */ if (rtnl == NULL) { xlog_warn("Received SM_UNMON request from %s for %s while not " "monitoring any hosts", my_name, argp->mon_name); return (&result); } clnt = rtnl; /* * OK, we are. Now look for appropriate entry in run-time list. * There should only be *one* match on this, since I block "duplicate" * SM_MON calls. (Actually, duplicate calls are allowed, but only one * entry winds up in the list the way I'm currently handling them.) */ 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) { /* Match! */ xlog(D_GENERAL, "UNMONITORING %s for %s", mon_name, my_name); /* PRC: do the HA callout: */ ha_callout("del-client", mon_name, my_name, -1); nsm_delete_monitored_host(clnt->dns_name, mon_name, my_name); nlist_free(&rtnl, clnt); return (&result); } else clnt = NL_NEXT(clnt); } failure: xlog_warn("Received erroneous SM_UNMON request from %s for %s", my_name, mon_name); return (&result); }
int main(int argc, char **argv) { char *arg; char *value; char *type; int rc = 1, opt; int timeout = 600; key_serial_t key; char *progname, *keystr = NULL; int clearing = 0, keymask = 0, display = 0, list = 0; /* Set the basename */ if ((progname = strrchr(argv[0], '/')) != NULL) progname++; else progname = argv[0]; xlog_open(progname); while ((opt = getopt(argc, argv, "du:g:r:ct:vl")) != -1) { switch (opt) { case 'd': display++; break; case 'l': list++; break; case 'u': keymask = UIDKEYS; keystr = strdup(optarg); break; case 'g': keymask = GIDKEYS; keystr = strdup(optarg); break; case 'r': keymask = GIDKEYS|UIDKEYS; keystr = strdup(optarg); break; case 'c': clearing++; break; case 'v': verbose++; break; case 't': timeout = atoi(optarg); break; default: xlog_warn(usage, progname); break; } } if ((rc = nfs4_init_name_mapping(PATH_IDMAPDCONF))) { xlog_errno(rc, "Unable to create name to user id mappings."); return EXIT_FAILURE; } if (!verbose) verbose = conf_get_num("General", "Verbosity", 0); if (display) return display_default_domain(); if (list) return list_keyring(DEFAULT_KEYRING); if (keystr) { return key_invalidate(keystr, keymask); } if (clearing) { xlog_syslog(0); return keyring_clear(DEFAULT_KEYRING); } xlog_stderr(0); if ((argc - optind) != 2) { xlog_err("Bad arg count. Check /etc/request-key.conf"); xlog_warn(usage, progname); return EXIT_FAILURE; } if (verbose) nfs4_set_debug(verbose, NULL); key = strtol(argv[optind++], NULL, 10); arg = strdup(argv[optind]); if (arg == NULL) { xlog_err("strdup failed: %m"); return EXIT_FAILURE; } type = strtok(arg, ":"); value = strtok(NULL, ":"); if (value == NULL) { free(arg); xlog_err("Error: Null uid/gid value."); return EXIT_FAILURE; } if (verbose) { xlog_warn("key: 0x%lx type: %s value: %s timeout %ld", key, type, value, timeout); } /* Become a possesor of the to-be-instantiated key to set the key's timeout */ request_key("keyring", DEFAULT_KEYRING, NULL, KEY_SPEC_THREAD_KEYRING); if (strcmp(type, "uid") == 0) rc = id_lookup(value, key, USER); else if (strcmp(type, "gid") == 0) rc = id_lookup(value, key, GROUP); else if (strcmp(type, "user") == 0) rc = name_lookup(value, key, USER); else if (strcmp(type, "group") == 0) rc = name_lookup(value, key, GROUP); /* Set timeout to 10 (600 seconds) minutes */ if (rc == EXIT_SUCCESS) keyctl_set_timeout(key, timeout); free(arg); return rc; }
/* * Revoke a key */ static int key_invalidate(char *keystr, int keymask) { FILE *fp; char buf[BUFSIZ], *ptr; key_serial_t key; int mask; xlog_syslog(0); if ((fp = fopen(PROCKEYS, "r")) == NULL) { xlog_err("fopen(%s) failed: %m", PROCKEYS); return 1; } while(fgets(buf, BUFSIZ, fp) != NULL) { if (strstr(buf, "keyring") != NULL) continue; mask = 0; if ((ptr = strstr(buf, "uid:")) != NULL) mask = UIDKEYS; else if ((ptr = strstr(buf, "gid:")) != NULL) mask = GIDKEYS; else continue; if ((keymask & mask) == 0) continue; if (strncmp(ptr+4, keystr, strlen(keystr)) != 0) continue; if (verbose) { *(strchr(buf, '\n')) = '\0'; xlog_warn("invalidating '%s'", buf); } /* * The key is the first arugment in the string */ *(strchr(buf, ' ')) = '\0'; sscanf(buf, "%x", &key); #ifdef HAVE_KEYCTL_INVALIDATE #warning Using keyctl-invalidate (yay!). if (keyctl_invalidate(key) < 0) { xlog_err("keyctl_invalidate(0x%x) failed: %m", key); fclose(fp); return 1; } #else #ifdef HAVE_KEYCTL_REVOKE #warning Using keyctl-revoke because keyctl-invalidate is not available. if (keyctl_revoke(key) < 0) { xlog_err("keyctl_invalidate(0x%x) failed: %m", key); fclose(fp); return 1; } #else #error "Need keyctl_revoke or keyctl_invalidate." #endif #endif keymask &= ~mask; if (keymask == 0) { fclose(fp); return 0; } } xlog_err("'%s' key was not found.", keystr); fclose(fp); return 1; }