/* * nibindd remote procedure implementation * Copyright 1989-94, NeXT Computer Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nibind_globals.h" #ifdef _OS_NEXT_ #define nibind_ping_1_svc nibind_ping_1 #define nibind_register_1_svc nibind_register_1 #define nibind_unregister_1_svc nibind_unregister_1 #define nibind_getregister_1_svc nibind_getregister_1 #define nibind_listreg_1_svc nibind_listreg_1 #define nibind_createmaster_1_svc nibind_createmaster_1 #define nibind_createclone_1_svc nibind_createclone_1 #define nibind_destroydomain_1_svc nibind_destroydomain_1 #define nibind_bind_1_svc nibind_bind_1 #endif #ifndef __SLICK__ /* This is needed until nibind_prot.h in System.framework has been * generated by the new rpcgen. */ extern ni_status * nibind_unregister_1_svc(ni_name *, struct svc_req *); #endif /* __SLICK__ */ #define BIND_TIMEOUT 2 #define BIND_RETRIES 0 #define MAX_WAIT 3 #define NI_TIMEOUT 5 #define NI_PING_TIMEOUT 1 #define NI_ALIAS_NAME "alias_name" #define NI_ALIAS_ADDRS "alias_addrs" /* the list of server aliases */ typedef struct ni_aliasinfo { char *name; char *alias; unsigned long address; unsigned long mask; } ni_aliasinfo; int serverCount = 0; /* num servers that have been checked */ int aliasCount = 0; /* num aliases in the list */ ni_aliasinfo *aliasList; /* the list of aliases */ char *find_ni_alias(char *server_tag, unsigned long client_addr); void get_ni_aliases(); static void clnt_destroy_lock(CLIENT *cl, int sock); extern const char NETINFO_PROG[]; extern const char NIBINDD_PROG[]; int waitreg; static const char NI_SUFFIX_CUR[] = ".nidb"; static const char NI_SUFFIX_MOVED[] = ".move"; static const char NI_SUFFIX_TMP[] = ".temp"; /* * The list of registered servers */ struct { unsigned regs_len; nibind_registration *regs_val; } regs; typedef struct ni_serverinfo { int pid; ni_name name; } ni_serverinfo; ni_serverinfo *servers; unsigned nservers; void destroydir(ni_name); void storepid(int pid, ni_name name) { MM_GROW_ARRAY(servers, nservers); servers[nservers].pid = pid; servers[nservers].name = ni_name_dup(name); nservers++; } void killservers(void) { int i; system_log(LOG_ERR, "Shutting down NetInfo servers"); signal(SIGCHLD, SIG_IGN); for (i = 0; i < nservers; i++) kill(servers[i].pid, SIGUSR1); } void killchildren(void) { killservers(); exit(1); } /* * Note that respawn doesn't preserve debug flags. */ void respawn(void) { int i; killservers(); system_log(LOG_ERR, "Restarting NetInfo"); for (i = getdtablesize() - 1; i >= 0; i--) close(i); open("/dev/null", O_RDWR, 0); dup(0); dup(0); execl(NIBINDD_PROG, NIBINDD_PROG, 0); exit(1); } ni_status validate(struct svc_req *req) { struct authunix_parms *ap; char *p; struct passwd *pwent; struct sockaddr_in *sin = svc_getcaller(req->rq_xprt); int auth_ok = 0; if (sys_is_my_address(&sin->sin_addr) && ntohs(sin->sin_port) < IPPORT_RESERVED) { return (NI_OK); } if (req->rq_cred.oa_flavor != AUTH_UNIX) { return(NI_PERM); } /* validate requests if root password supplied - MM 1994.02.19 */ /* this used the unix auth to pass a vaguely-encrypted */ /* root password in the machine name string */ ap = (struct authunix_parms *)req->rq_clntcred; /* our stunning encryption scheme */ for (p = ap->aup_machname; *p != '\0'; p++) *p = ~(*p); /* look up root */ pwent = getpwuid(0); if (pwent == NULL) { system_log(LOG_ERR, "Can't find user account data for root!"); return(NI_PERM); } /* check for no password */ if (pwent->pw_passwd[0] == '\0') auth_ok = 1; else { /* check for password match */ if (!strcmp(pwent->pw_passwd, crypt(ap->aup_machname, pwent->pw_passwd))) auth_ok = 1; } if (auth_ok == 1) { system_log(LOG_NOTICE, "Connection (sender = %s) authenticated as root", inet_ntoa(sin->sin_addr)); return(NI_OK); } system_log(LOG_NOTICE, "Connection (sender = %s) failed authenticated as root", inet_ntoa(sin->sin_addr)); return(NI_PERM); } void deletepid(int pid) { int i; for (i = 0; i < nservers; i++) { if (servers[i].pid == pid) { nibind_unregister_1_svc(&servers[i].name, NULL); ni_name_free(&servers[i].name); for (i += 1; i < nservers; i++) { servers[i - 1] = servers[i]; } MM_SHRINK_ARRAY(servers, nservers); nservers--; waitreg--; return; } } } void register_done(ni_name tag) { int i; for (i = 0; i < nservers; i++) { if (ni_name_match(servers[i].name, tag)) { waitreg--; return; } } } /* * Signal handler: must save and restore errno */ void catchchild(void) { int i, pid; union wait status; int save_errno; char *tag = "UNKNOWN"; save_errno = errno; pid = wait3((_WAIT_TYPE_ *)&status, WNOHANG, NULL); if (pid > 0) { if ((status.w_termsig != 0) || (status.w_retcode != 0)) { /* Find the child with this PID */ for (i = 0; i < nservers; i++) { if (servers[i].pid == pid) { tag = servers[i].name; break; } } if (status.w_termsig == 0) { system_log(LOG_WARNING, "netinfod %s [%d] exited %d", tag, pid, status.w_retcode); } else if (status.w_retcode != 0) { system_log(LOG_WARNING, "netinfod %s [%d] exited %d, " "signal %d", tag, pid, status.w_retcode, status.w_termsig); } else { system_log(LOG_WARNING, "netinfod %s [%d] terminated with " "signal %d", tag, pid, status.w_termsig); } } deletepid(pid); } errno = save_errno; } void *nibind_ping_1_svc(void *arg, struct svc_req *req) { struct sockaddr_in *sin = svc_getcaller(req->rq_xprt); system_log(LOG_DEBUG, "ping (sender = %s)", inet_ntoa(sin->sin_addr)); return((void *)~0); } ni_status *nibind_register_1_svc(nibind_registration *arg, struct svc_req *req) { static ni_status status; int i; system_log(LOG_DEBUG, "register %s tcp %u udp %u", arg->tag, arg->addrs.tcp_port, arg->addrs.udp_port); status = validate(req); if (status != NI_OK) return(&status); for (i = 0; i < regs.regs_len; i++) { if (ni_name_match(arg->tag, regs.regs_val[i].tag)) { register_done(arg->tag); ni_name_free(®s.regs_val[i].tag); regs.regs_val[i] = *arg; bzero(arg, sizeof(*arg)); status = NI_OK; return(&status); } } register_done(arg->tag); MM_GROW_ARRAY(regs.regs_val, regs.regs_len); regs.regs_val[regs.regs_len++] = *arg; bzero(arg, sizeof(*arg)); status = NI_OK; return(&status); } ni_status * nibind_unregister_1_svc(ni_name *tag, struct svc_req *req) { static ni_status status; ni_index i; system_log(LOG_DEBUG, "unregister %s", *tag); if (req != NULL) { status = validate(req); if (status != NI_OK) return(&status); } for (i = 0; i < regs.regs_len; i++) { if (ni_name_match(*tag, regs.regs_val[i].tag)) { ni_name_free(®s.regs_val[i].tag); regs.regs_val[i] = regs.regs_val[regs.regs_len - 1]; MM_SHRINK_ARRAY(regs.regs_val, regs.regs_len); regs.regs_len--; break; } } /* remove the entries from the alias list as well */ for (i = 0; i < aliasCount; i++) { if (strcmp((char *) *tag, aliasList[i].name)) { free(aliasList[i].name); free(aliasList[i].alias); aliasList[i] = aliasList[aliasCount - 1]; MM_SHRINK_ARRAY(aliasList, aliasCount); aliasCount--; } } status = NI_OK; return(&status); } nibind_getregister_res *nibind_getregister_1_svc(ni_name *tag, struct svc_req *req) { ni_index i; static nibind_getregister_res res; char *new_tag; struct sockaddr_in loopback; int sock; CLIENT *cl; enum clnt_stat clstat; struct timeval tv; res.status = NI_NOTAG; /* Check to see if we should substitute an alias here */ new_tag = find_ni_alias(*tag, (req->rq_xprt->xp_raddr).sin_addr.s_addr); for (i = 0; i < regs.regs_len; i++) { if (ni_name_match(new_tag, regs.regs_val[i].tag)) { res.nibind_getregister_res_u.addrs = regs.regs_val[i].addrs; res.status = NI_OK; break; } } /* * If we have a tag to use, let's check to make sure that netinfod is * alive. We have to use low-level RPC here, since the ni library * routines might try and talk to nibindd (which won't work!) * */ if ((NI_PING_TIMEOUT != 0) && (res.status == NI_OK)) { tv.tv_sec = NI_PING_TIMEOUT; tv.tv_usec = 0; loopback.sin_addr.s_addr = htonl(INADDR_LOOPBACK); loopback.sin_family = AF_INET; loopback.sin_port = htons(res.nibind_getregister_res_u.addrs.udp_port); sock = socket_open(&loopback, NI_PROG, NI_VERS); if (sock < 0) { res.status = NI_NORESPONSE; return (&res); } cl = clntudp_create(&loopback, NI_PROG, NI_VERS, tv, &sock); if (cl == NULL) { socket_close(sock); res.status = NI_NORESPONSE; return (&res); } clstat = clnt_call(cl, _NI_PING, xdr_void, NULL, xdr_void, NULL, tv); if (clstat != RPC_SUCCESS) res.status = NI_NORESPONSE; clnt_destroy_lock(cl, sock); } return(&res); } nibind_listreg_res *nibind_listreg_1_svc(void *arg, struct svc_req *req) { static nibind_listreg_res res; struct sockaddr_in *sin = svc_getcaller(req->rq_xprt); system_log(LOG_DEBUG, "listreg (sender = %s)", inet_ntoa(sin->sin_addr)); res.status = NI_OK; res.nibind_listreg_res_u.regs.regs_len = regs.regs_len; res.nibind_listreg_res_u.regs.regs_val = regs.regs_val; return (&res); } ni_status *nibind_createmaster_1_svc(ni_name *tag, struct svc_req *req) { static ni_status status; ni_index i; int pid; struct sockaddr_in *sin = svc_getcaller(req->rq_xprt); system_log(LOG_DEBUG, "createmaster %s (sender = %s)", *tag, inet_ntoa(sin->sin_addr)); status = validate(req); if (status != NI_OK) return(&status); for (i = 0; i < regs.regs_len; i++) { if (ni_name_match(*tag, regs.regs_val[i].tag)) { status = NI_DUPTAG; return(&status); } } pid = sys_spawn(NETINFO_PROG, "-m", *tag, 0); if (pid < 0) { status = NI_SYSTEMERR; } else { storepid(pid, *tag); status = NI_OK; } return(&status); } ni_status * nibind_createclone_1_svc(nibind_clone_args *args, struct svc_req *req) { struct in_addr addr; static ni_status status; ni_index i; int pid; struct sockaddr_in *sin = svc_getcaller(req->rq_xprt); system_log(LOG_DEBUG, "createclone %s of %s/%s (sender = %s)", args->tag, args->master_name, args->master_tag, inet_ntoa(sin->sin_addr)); status = validate(req); if (status != NI_OK) return(&status); /* XDR will have byte-swapped the master address if this is a */ /* little-endian system, since it gets passed as an unsigned long */ addr.s_addr = htonl(args->master_addr); for (i = 0; i < regs.regs_len; i++) { if (ni_name_match(args->tag, regs.regs_val[i].tag)) { status = NI_DUPTAG; return(&status); } } pid = sys_spawn(NETINFO_PROG, "-c", args->master_name, inet_ntoa(addr), args->master_tag, args->tag, 0); if (pid < 0) { status = NI_SYSTEMERR; } else { status = NI_OK; storepid(pid, args->tag); } return(&status); } ni_status *nibind_destroydomain_1_svc(ni_name *tag, struct svc_req *req) { static ni_status status; int i; status = validate(req); if (status != NI_OK) return (&status); /* * Unregister it */ status = NI_NOTAG; for (i = 0; i < regs.regs_len; i++) { if (ni_name_match(*tag, regs.regs_val[i].tag)) { regs.regs_val[i] = regs.regs_val[regs.regs_len - 1]; MM_SHRINK_ARRAY(regs.regs_val, regs.regs_len); regs.regs_len--; status = NI_OK; break; } } if (status != NI_OK) return(&status); /* remove the entries from the alias list as well */ for (i = 0; i < aliasCount; i++) { if (strcmp((char *) *tag, aliasList[i].name)) { free(aliasList[i].name); free(aliasList[i].alias); aliasList[i] = aliasList[aliasCount - 1]; MM_SHRINK_ARRAY(aliasList, aliasCount); aliasCount--; } } /* Then kill it */ for (i = 0; i < nservers; i++) { if (ni_name_match(servers[i].name, *tag)) { kill(servers[i].pid, SIGKILL); ni_name_free(&servers[i].name); servers[i] = servers[nservers - 1]; MM_SHRINK_ARRAY(servers, nservers); nservers--; break; } } destroydir(*tag); return(&status); } void *nibind_bind_1_svc(nibind_bind_args *args, struct svc_req *req) { unsigned port = 0; ni_index i; ni_binding binding; struct timeval tv; CLIENT *cl; struct sockaddr_in sin; int sock; char *new_srvr_tag; /* Check to see if we should substitute an alias here */ new_srvr_tag = find_ni_alias(args->server_tag,htonl(args->client_addr)); for (i = 0; i < regs.regs_len; i++) { if (ni_name_match(new_srvr_tag, regs.regs_val[i].tag)) { port = regs.regs_val[i].addrs.udp_port; break; } } if (port == 0) { /* Report an RPC error so the caller doesn't need to time out */ svcerr_systemerr(req->rq_xprt); return(NULL); } sin.sin_port = htons(port); sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sin.sin_family = AF_INET; MM_ZERO(&sin.sin_zero); sock = RPC_ANYSOCK; tv.tv_sec = BIND_TIMEOUT; tv.tv_usec = 0; cl = clntudp_create(&sin, NI_PROG, NI_VERS, tv, &sock); if (cl == NULL) { svcerr_systemerr(req->rq_xprt); return (NULL); } binding.tag = args->client_tag; binding.addr = args->client_addr; /* GS - in host byte order */ tv.tv_sec /= (BIND_RETRIES + 1); if (clnt_call(cl, _NI_BIND, xdr_ni_binding, &binding, xdr_void, NULL, tv) != RPC_SUCCESS) { clnt_destroy(cl); svcerr_systemerr(req->rq_xprt); return (NULL); } clnt_destroy(cl); return((void *)~0); } /* * Destroy a database directory */ static int dir_destroy(char *dir) { char path1[MAXPATHLEN + 1]; char path2[MAXPATHLEN + 1]; DIR *dp; struct direct *d; dp = opendir(dir); if (dp == NULL) return(0); while (NULL != (d = readdir(dp))) { sprintf(path1, "%s/%.*s", dir, d->d_namlen, d->d_name); sprintf(path2, "./%.*s.tmp", d->d_namlen, d->d_name); /* * rename, then unlink in case NFS leaves tmp files behind * (.nfs* files, that is). */ if ((rename(path1, path2) != 0) || (unlink(path2) != 0)) { /* ignore error: rmdir will catch ENOTEMPTY */ } } closedir(dp); return(rmdir(dir)); } void dir_getnames(ni_name orig, ni_name *target, ni_name *moved, ni_name *tmp) { if (target != NULL) { *target = malloc(strlen(orig) + strlen(NI_SUFFIX_CUR) + 1); sprintf(*target, "%s%s", orig, NI_SUFFIX_CUR); } if (moved != NULL) { *moved = malloc(strlen(orig) + strlen(NI_SUFFIX_MOVED) + 1); sprintf(*moved, "%s%s", orig, NI_SUFFIX_MOVED); } if (tmp != NULL) { *tmp = malloc(strlen(orig) + strlen(NI_SUFFIX_TMP) + 1); sprintf(*tmp, "%s%s", orig, NI_SUFFIX_TMP); } } void destroydir(ni_name tag) { ni_name target = NULL; ni_name moved = NULL; ni_name tmp = NULL; dir_getnames(tag, &target, &moved, &tmp); dir_destroy(target); dir_destroy(moved); dir_destroy(tmp); ni_name_free(&target); ni_name_free(&moved); ni_name_free(&tmp); } /* * Look in the alias list to see if we have a match for the requested tag * that applies to the given client source address. If so, return the * real tag. */ char * find_ni_alias(char *server_tag, unsigned long client_addr) { int i; if (serverCount != regs.regs_len) get_ni_aliases(); if (aliasCount == 0) return server_tag; /* look for first match in the table */ for (i = 0; i < aliasCount; i++) { if (((client_addr & aliasList[i].mask) == aliasList[i].address) && (strcmp(server_tag, aliasList[i].alias) == 0)) { system_log(LOG_INFO, "substituting alias %s for %s (client %s)", aliasList[i].name, server_tag, inet_ntoa(inet_makeaddr((htonl(client_addr) >> 16), htonl(client_addr)))); return aliasList[i].name; } } return server_tag; } void get_ni_aliases() { int db, val, index; ni_id niID; ni_id_res niID_res; ni_proplist niPropList; ni_proplist_res niPropList_res; ni_namelist niNameList; char tmpAliasName[MAXPATHLEN + 1], tmpAddress[16], tmpMask[16]; ni_aliasinfo aliasRecord; char *info, *ptr; struct sockaddr_in loopback; int sock; CLIENT *cl; enum clnt_stat clstat; struct timeval tv; tv.tv_sec = NI_TIMEOUT; tv.tv_usec = 0; loopback.sin_addr.s_addr = htonl(INADDR_LOOPBACK); loopback.sin_family = AF_INET; /* * Go through the list of registered netinfod processes, and find out what * aliases each has registered for. Start with the next database that * hasn't been checked yet. */ for (db = serverCount; db < regs.regs_len; db++) { serverCount++; loopback.sin_port = htons(regs.regs_val[db].addrs.tcp_port); sock = socket_connect(&loopback, NI_PROG, NI_VERS); if (sock < 0) continue; cl = clnttcp_create(&loopback, NI_PROG, NI_VERS, &sock, 0, 0); if (cl == NULL) { socket_close(sock); continue; } /* * Get the object ID for the root directory (where the data * are stored). */ NI_INIT(&niID_res); clstat = clnt_call(cl, _NI_ROOT, xdr_void, NULL, xdr_ni_id_res, &niID_res, tv); if ((clstat != RPC_SUCCESS) || (niID_res.status != NI_OK)) { clnt_destroy_lock(cl, sock); continue; } niID = niID_res.ni_id_res_u.id; /* read the property list for the root directory */ NI_INIT(&niPropList_res); clstat = clnt_call(cl, _NI_READ, xdr_ni_id, &niID, xdr_ni_proplist_res, &niPropList_res, tv); if ((clstat != RPC_SUCCESS) || (niPropList_res.status != NI_OK)) { clnt_destroy_lock(cl, sock); continue; } niPropList = niPropList_res.ni_proplist_res_u.stuff.props; /* find the property for the alias name */ for (index = 0; index < niPropList.ni_proplist_len; index++) { if (strcmp(niPropList.ni_proplist_val[index].nip_name, NI_ALIAS_NAME) == 0) { strcpy(tmpAliasName, niPropList.ni_proplist_val[index].nip_val.ni_namelist_val[0]); break; } } if (index == niPropList.ni_proplist_len) { /* didn't find a match */ clnt_destroy_lock(cl, sock); ni_proplist_free(&niPropList); continue; } /* find the property for the address list */ for (index = 0; index < niPropList.ni_proplist_len; index++) { if (strcmp(niPropList.ni_proplist_val[index].nip_name, NI_ALIAS_ADDRS) == 0) { break; } } if (index == niPropList.ni_proplist_len) { /* didn't find a match */ clnt_destroy_lock(cl, sock); ni_proplist_free(&niPropList); continue; } else { niNameList = niPropList.ni_proplist_val[index].nip_val; for (val = 0; val < niNameList.ni_namelist_len; val++) { aliasRecord.name = malloc(strlen(regs.regs_val[db].tag) + 1); strcpy(aliasRecord.name, regs.regs_val[db].tag); aliasRecord.alias = malloc(strlen(tmpAliasName) + 1); strcpy(aliasRecord.alias, tmpAliasName); info = niNameList.ni_namelist_val[val]; ptr = strchr(info, '/'); if (ptr == NULL) ptr = strchr(info, '&'); if (ptr == NULL) { system_log(LOG_ERR, "Error adding alias record, malformed " "NetInfo property: %s", info); free(aliasRecord.name); free(aliasRecord.alias); } else { strncpy(tmpAddress, info, ptr - info); tmpAddress[ptr - info] = 0; strcpy(tmpMask, ptr + 1); aliasRecord.address = inet_addr(tmpAddress); aliasRecord.mask = inet_addr(tmpMask); system_log(LOG_INFO, "Adding alias record: %s %s %s %s", aliasRecord.name, aliasRecord.alias, tmpAddress, tmpMask); MM_GROW_ARRAY(aliasList, aliasCount); aliasList[aliasCount++] = aliasRecord; } } } clnt_destroy_lock(cl, sock); ni_proplist_free(&niPropList); } } /* * Destroys a client handle with locks */ static void clnt_destroy_lock(CLIENT *cl, int sock) { socket_lock(); clnt_destroy(cl); close(sock); socket_unlock(); }