/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*- * vim:expandtab:shiftwidth=8:tabstop=8: * * Copyright (c) 2005 Cluster File Systems, Inc. * * This file is part of Lustre, http://www.lustre.org. * * Lustre is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * Lustre is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Lustre; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETHOSTBYNAME # include #endif #ifdef _NEW_BUILD_ # include "lgss_utils.h" #else # include "err_util.h" # include "gssd.h" #endif #include "lsupport.h" /**************************************** * exclusive startup * ****************************************/ static struct __sem_s { char *name; key_t sem_key; int sem_id; } sems[2] = { [GSSD_CLI] = { "client", 0x3a92d473, 0 }, [GSSD_SVC] = { "server", 0x3b92d473, 0 }, }; void gssd_init_unique(int type) { struct __sem_s *sem = &sems[type]; struct sembuf sembuf; assert(type == GSSD_CLI || type == GSSD_SVC); again: sem->sem_id = semget(sem->sem_key, 1, IPC_CREAT | IPC_EXCL | 0700); if (sem->sem_id == -1) { if (errno != EEXIST) { printerr(0, "Create sem: %s\n", strerror(errno)); exit(-1); } /* already exist. Note there's still a small window racing * with other processes, due to the stupid semaphore semantics. */ sem->sem_id = semget(sem->sem_key, 0, 0700); if (sem->sem_id == -1) { if (errno == ENOENT) { printerr(0, "another instance just exit, " "try again\n"); goto again; } printerr(0, "Obtain sem: %s\n", strerror(errno)); exit(-1); } } else { int val = 1; if (semctl(sem->sem_id, 0, SETVAL, val) == -1) { printerr(0, "Initialize sem: %s\n", strerror(errno)); exit(-1); } } sembuf.sem_num = 0; sembuf.sem_op = -1; sembuf.sem_flg = IPC_NOWAIT | SEM_UNDO; if (semop(sem->sem_id, &sembuf, 1) != 0) { if (errno == EAGAIN) { printerr(0, "Another instance is running, exit\n"); exit(0); } printerr(0, "Grab sem: %s\n", strerror(errno)); exit(0); } printerr(2, "Successfully created %s global identity\n", sem->name); } void gssd_exit_unique(int type) { assert(type == GSSD_CLI || type == GSSD_SVC); /* * do nothing. we can't remove the sem here, otherwise the race * window would be much bigger. So it's sad we have to leave the * sem in the system forever. */ } /**************************************** * client side resolvation: * * lnd/netid/nid => hostname * ****************************************/ char gethostname_ex[PATH_MAX] = GSSD_DEFAULT_GETHOSTNAME_EX; typedef int lnd_nid2hostname_t(char *lnd, uint32_t net, uint32_t addr, char *buf, int buflen); /* FIXME what about IPv6? */ static int ipv4_nid2hostname(char *lnd, uint32_t net, uint32_t addr, char *buf, int buflen) { struct hostent *ent; addr = htonl(addr); ent = gethostbyaddr(&addr, sizeof(addr), AF_INET); if (!ent) { printerr(0, "%s: can't resolve 0x%x\n", lnd, addr); return -1; } if (strlen(ent->h_name) >= buflen) { printerr(0, "%s: name too long: %s\n", lnd, ent->h_name); return -1; } strcpy(buf, ent->h_name); printerr(2, "%s: net 0x%x, addr 0x%x => %s\n", lnd, net, addr, buf); return 0; } static int lolnd_nid2hostname(char *lnd, uint32_t net, uint32_t addr, char *buf, int buflen) { struct utsname uts; struct hostent *ent; if (addr) { printerr(0, "%s: addr is 0x%x, we expect 0\n", lnd, addr); return -1; } if (uname(&uts)) { printerr(0, "%s: failed obtain local machine name\n", lnd); return -1; } ent = gethostbyname(uts.nodename); if (!ent) { printerr(0, "%s: failed obtain canonical name of %s\n", lnd, uts.nodename); return -1; } if (strlen(ent->h_name) >= buflen) { printerr(0, "%s: name too long: %s\n", lnd, ent->h_name); return -1; } strcpy(buf, ent->h_name); printerr(3, "%s: addr 0x%x => %s\n", lnd, addr, buf); return 0; } static int is_space(char c) { return (c == ' ' || c == '\t' || c == '\n'); } static int external_nid2hostname(char *lnd, uint32_t net, uint32_t addr, char *namebuf, int namebuflen) { const int bufsize = PATH_MAX + 256; char buf[bufsize], *head, *tail; FILE *fghn; sprintf(buf, "%s %s 0x%x 0x%x", gethostname_ex, lnd, net, addr); printerr(2, "cmd: %s\n", buf); fghn = popen(buf, "r"); if (fghn == NULL) { printerr(0, "failed to call %s\n", gethostname_ex); return -1; } head = fgets(buf, bufsize, fghn); if (head == NULL) { printerr(0, "can't read from %s\n", gethostname_ex); return -1; } if (pclose(fghn) == -1) printerr(1, "pclose failed, continue\n"); /* trim head/tail space */ while (is_space(*head)) head++; tail = head + strlen(head); if (tail <= head) { printerr(0, "no output from %s\n", gethostname_ex); return -1; } while (is_space(*(tail - 1))) tail--; if (tail <= head) { printerr(0, "output are all space from %s\n", gethostname_ex); return -1; } *tail = '\0'; /* start with '@' means error msg */ if (head[0] == '@') { printerr(0, "error from %s: %s\n", gethostname_ex, &head[1]); return -1; } if (tail - head > namebuflen) { printerr(0, "external hostname too long: %s\n", head); return -1; } printerr(2, "%s: net 0x%x, addr 0x%x => %s\n", lnd, net, addr, head); strcpy(namebuf, head); return 0; } static struct { char *name; lnd_nid2hostname_t *nid2name; } converter[LND_ENUM_END_MARKER] = { {"UNUSED0", NULL}, [QSWLND] = { "QSWLND", external_nid2hostname}, [SOCKLND] = { "SOCKLND", ipv4_nid2hostname }, [GMLND] = { "GMLND", external_nid2hostname}, [PTLLND] = { "PTLLND", external_nid2hostname }, [O2IBLND] = { "O2IBLND", ipv4_nid2hostname }, [CIBLND] = { "CIBLND", external_nid2hostname }, [OPENIBLND] = { "OPENIBLND",external_nid2hostname }, [IIBLND] = { "IIBLND", external_nid2hostname }, [LOLND] = { "LOLND", lolnd_nid2hostname }, [RALND] = { "RALND", external_nid2hostname }, [VIBLND] = { "VIBLND", external_nid2hostname }, }; int lnet_nid2hostname(lnet_nid_t nid, char *buf, int buflen) { uint32_t lnd, net, addr; addr = LNET_NIDADDR(nid); net = LNET_NIDNET(nid); lnd = LNET_NETTYP(net); if (lnd >= LND_ENUM_END_MARKER) { printerr(0, "ERROR: Unrecognized LND %u\n", lnd); return -1; } if (converter[lnd].nid2name == NULL) { printerr(0, "ERROR: %s converter not ready\n", converter[lnd].name); return -1; } return converter[lnd].nid2name(converter[lnd].name, net, addr, buf, buflen); } /**************************************** * lnet support routine * * (from lnet/libcfs/nidstrings.c * ****************************************/ #define LNET_NIDSTR_SIZE 32 /* size of each one (see below for usage) */ static int libcfs_lo_str2addr(char *str, int nob, uint32_t *addr); static void libcfs_ip_addr2str(uint32_t addr, char *str); static int libcfs_ip_str2addr(char *str, int nob, uint32_t *addr); static void libcfs_decnum_addr2str(uint32_t addr, char *str); static void libcfs_hexnum_addr2str(uint32_t addr, char *str); static int libcfs_num_str2addr(char *str, int nob, uint32_t *addr); struct netstrfns { int nf_type; char *nf_name; char *nf_modname; void (*nf_addr2str)(uint32_t addr, char *str); int (*nf_str2addr)(char *str, int nob, uint32_t *addr); }; static struct netstrfns libcfs_netstrfns[] = { {/* .nf_type */ LOLND, /* .nf_name */ "lo", /* .nf_modname */ "klolnd", /* .nf_addr2str */ libcfs_decnum_addr2str, /* .nf_str2addr */ libcfs_lo_str2addr}, {/* .nf_type */ SOCKLND, /* .nf_name */ "tcp", /* .nf_modname */ "ksocklnd", /* .nf_addr2str */ libcfs_ip_addr2str, /* .nf_str2addr */ libcfs_ip_str2addr}, {/* .nf_type */ O2IBLND, /* .nf_name */ "o2ib", /* .nf_modname */ "ko2iblnd", /* .nf_addr2str */ libcfs_ip_addr2str, /* .nf_str2addr */ libcfs_ip_str2addr}, {/* .nf_type */ CIBLND, /* .nf_name */ "cib", /* .nf_modname */ "kciblnd", /* .nf_addr2str */ libcfs_ip_addr2str, /* .nf_str2addr */ libcfs_ip_str2addr}, {/* .nf_type */ OPENIBLND, /* .nf_name */ "openib", /* .nf_modname */ "kopeniblnd", /* .nf_addr2str */ libcfs_ip_addr2str, /* .nf_str2addr */ libcfs_ip_str2addr}, {/* .nf_type */ IIBLND, /* .nf_name */ "iib", /* .nf_modname */ "kiiblnd", /* .nf_addr2str */ libcfs_ip_addr2str, /* .nf_str2addr */ libcfs_ip_str2addr}, {/* .nf_type */ VIBLND, /* .nf_name */ "vib", /* .nf_modname */ "kviblnd", /* .nf_addr2str */ libcfs_ip_addr2str, /* .nf_str2addr */ libcfs_ip_str2addr}, {/* .nf_type */ RALND, /* .nf_name */ "ra", /* .nf_modname */ "kralnd", /* .nf_addr2str */ libcfs_ip_addr2str, /* .nf_str2addr */ libcfs_ip_str2addr}, {/* .nf_type */ QSWLND, /* .nf_name */ "elan", /* .nf_modname */ "kqswlnd", /* .nf_addr2str */ libcfs_decnum_addr2str, /* .nf_str2addr */ libcfs_num_str2addr}, {/* .nf_type */ GMLND, /* .nf_name */ "gm", /* .nf_modname */ "kgmlnd", /* .nf_addr2str */ libcfs_hexnum_addr2str, /* .nf_str2addr */ libcfs_num_str2addr}, {/* .nf_type */ PTLLND, /* .nf_name */ "ptl", /* .nf_modname */ "kptllnd", /* .nf_addr2str */ libcfs_decnum_addr2str, /* .nf_str2addr */ libcfs_num_str2addr}, /* placeholder for net0 alias. It MUST BE THE LAST ENTRY */ {/* .nf_type */ -1}, }; const int libcfs_nnetstrfns = sizeof(libcfs_netstrfns)/sizeof(libcfs_netstrfns[0]); static int libcfs_lo_str2addr(char *str, int nob, uint32_t *addr) { *addr = 0; return 1; } static void libcfs_ip_addr2str(uint32_t addr, char *str) { snprintf(str, LNET_NIDSTR_SIZE, "%u.%u.%u.%u", (addr >> 24) & 0xff, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff); } /* CAVEAT EMPTOR XscanfX * I use "%n" at the end of a sscanf format to detect trailing junk. However * sscanf may return immediately if it sees the terminating '0' in a string, so * I initialise the %n variable to the expected length. If sscanf sets it; * fine, if it doesn't, then the scan ended at the end of the string, which is * fine too :) */ static int libcfs_ip_str2addr(char *str, int nob, uint32_t *addr) { int a; int b; int c; int d; int n = nob; /* XscanfX */ /* numeric IP? */ if (sscanf(str, "%u.%u.%u.%u%n", &a, &b, &c, &d, &n) >= 4 && n == nob && (a & ~0xff) == 0 && (b & ~0xff) == 0 && (c & ~0xff) == 0 && (d & ~0xff) == 0) { *addr = ((a<<24)|(b<<16)|(c<<8)|d); return 1; } #ifdef HAVE_GETHOSTBYNAME /* known hostname? */ if (('a' <= str[0] && str[0] <= 'z') || ('A' <= str[0] && str[0] <= 'Z')) { char *tmp; tmp = malloc(nob + 1); if (tmp != NULL) { struct hostent *he; memcpy(tmp, str, nob); tmp[nob] = 0; he = gethostbyname(tmp); free(tmp); tmp = NULL; if (he != NULL) { uint32_t ip = *(uint32_t *)he->h_addr; *addr = ntohl(ip); return 1; } } } #endif return 0; } static void libcfs_decnum_addr2str(uint32_t addr, char *str) { snprintf(str, LNET_NIDSTR_SIZE, "%u", addr); } static void libcfs_hexnum_addr2str(uint32_t addr, char *str) { snprintf(str, LNET_NIDSTR_SIZE, "0x%x", addr); } static int libcfs_num_str2addr(char *str, int nob, uint32_t *addr) { int n; n = nob; if (sscanf(str, "0x%x%n", addr, &n) >= 1 && n == nob) return 1; n = nob; if (sscanf(str, "0X%x%n", addr, &n) >= 1 && n == nob) return 1; n = nob; if (sscanf(str, "%u%n", addr, &n) >= 1 && n == nob) return 1; return 0; } static struct netstrfns * libcfs_lnd2netstrfns(int lnd) { int i; if (lnd >= 0) for (i = 0; i < libcfs_nnetstrfns; i++) if (lnd == libcfs_netstrfns[i].nf_type) return &libcfs_netstrfns[i]; return NULL; } static struct netstrfns * libcfs_str2net_internal(char *str, uint32_t *net) { struct netstrfns *nf; int nob; int netnum; int i; for (i = 0; i < libcfs_nnetstrfns; i++) { nf = &libcfs_netstrfns[i]; if (nf->nf_type >= 0 && !strncmp(str, nf->nf_name, strlen(nf->nf_name))) break; } if (i == libcfs_nnetstrfns) return NULL; nob = strlen(nf->nf_name); if (strlen(str) == (unsigned int)nob) { netnum = 0; } else { if (nf->nf_type == LOLND) /* net number not allowed */ return NULL; str += nob; i = strlen(str); if (sscanf(str, "%u%n", &netnum, &i) < 1 || i != (int)strlen(str)) return NULL; } *net = LNET_MKNET(nf->nf_type, netnum); return nf; } lnet_nid_t libcfs_str2nid(char *str) { char *sep = strchr(str, '@'); struct netstrfns *nf; uint32_t net; uint32_t addr; if (sep != NULL) { nf = libcfs_str2net_internal(sep + 1, &net); if (nf == NULL) return LNET_NID_ANY; } else { sep = str + strlen(str); net = LNET_MKNET(SOCKLND, 0); nf = libcfs_lnd2netstrfns(SOCKLND); if (!nf) return LNET_NID_ANY; } if (!nf->nf_str2addr(str, sep - str, &addr)) return LNET_NID_ANY; return LNET_MKNID(net, addr); } /**************************************** * user mapping database handling * * (very rudiment) * ****************************************/ #define MAPPING_GROW_SIZE 512 #define MAX_LINE_LEN 256 struct user_map_item { char *principal; /* NULL means match all, will cause multi->single mapped, FORBID */ lnet_nid_t nid; uid_t uid; }; struct user_mapping { int nitems; struct user_map_item *items; }; static struct user_mapping mapping = {0, NULL}; /* FIXME to be finished: monitor change of mapping database */ static int mapping_mtime = 0; void cleanup_mapping(void) { if (mapping.items) { for (; mapping.nitems > 0; mapping.nitems--) free(mapping.items[mapping.nitems - 1].principal); free(mapping.items); mapping.items = NULL; } } static int grow_mapping(int nitems) { struct user_map_item *new; int oldsize, newsize; oldsize = (mapping.nitems * sizeof(struct user_map_item) + MAPPING_GROW_SIZE - 1) / MAPPING_GROW_SIZE; newsize = (nitems * sizeof(struct user_map_item) + MAPPING_GROW_SIZE - 1) / MAPPING_GROW_SIZE; while (newsize <= oldsize) return 0; newsize *= MAPPING_GROW_SIZE; new = malloc(newsize); if (!new) { printerr(0, "can't alloc mapping size %d\n", newsize); return -1; } if (mapping.items) { memcpy(new, mapping.items, mapping.nitems * sizeof(struct user_map_item)); free(mapping.items); } mapping.items = new; return 0; } uid_t parse_uid(char *uidstr) { struct passwd *pw; char *p = NULL; long uid; pw = getpwnam(uidstr); if (pw) return pw->pw_uid; uid = strtol(uidstr, &p, 0); if (*p == '\0') return (uid_t) uid; return -1; } static int read_mapping_db(void) { char princ[MAX_LINE_LEN]; char nid_str[MAX_LINE_LEN]; char dest[MAX_LINE_LEN]; char linebuf[MAX_LINE_LEN]; char *line; lnet_nid_t nid; uid_t dest_uid; FILE *f; /* cleanup old mappings */ cleanup_mapping(); f = fopen(MAPPING_DATABASE_FILE, "r"); if (!f) { printerr(0, "can't open mapping database: %s\n", MAPPING_DATABASE_FILE); return -1; } while ((line = fgets(linebuf, MAX_LINE_LEN, f)) != NULL) { char *name; if (strlen(line) >= MAX_LINE_LEN) { printerr(0, "invalid mapping db: line too long (%d)\n", strlen(line)); continue; } if (sscanf(line, "%s %s %s", princ, nid_str, dest) != 3) { printerr(0, "mapping db: syntax error\n"); continue; } if (!strcmp(princ, "*")) { printerr(0, "NOT permit \"*\" princ, it will cause multi->single mapped\n"); continue; } else { name = strdup(princ); if (!name) { printerr(0, "fail to dup str %s\n", princ); continue; } } if (!strcmp(nid_str, "*")) { nid = LNET_NID_ANY; } else { nid = libcfs_str2nid(nid_str); if (nid == LNET_NID_ANY) { printerr(0, "fail to parse nid %s\n", nid_str); free(name); continue; } } dest_uid = parse_uid(dest); if (dest_uid == -1) { printerr(0, "no valid user: %s\n", dest); free(name); continue; } if (grow_mapping(mapping.nitems + 1)) { printerr(0, "fail to grow mapping to %d\n", mapping.nitems + 1); free(name); fclose(f); return -1; } mapping.items[mapping.nitems].principal = name; mapping.items[mapping.nitems].nid = nid; mapping.items[mapping.nitems].uid = dest_uid; mapping.nitems++; printerr(1, "add mapping: %s(%s/0x%llx) ==> %d\n", name, nid_str, nid, dest_uid); } fclose(f); return 0; } static inline int mapping_changed(void) { struct stat st; if (stat(MAPPING_DATABASE_FILE, &st) == -1) { /* stat failed, treat it like doesn't exist or be removed */ if (mapping_mtime == 0) { return 0; } else { printerr(0, "Warning: stat %s failed: %s\n", MAPPING_DATABASE_FILE, strerror(errno)); mapping_mtime = 0; return 1; } } if (st.st_mtime != mapping_mtime) { mapping_mtime = st.st_mtime; return 1; } return 0; } int lookup_mapping(char *princ, lnet_nid_t nid, uid_t *uid) { int n; *uid = -1; /* FIXME race condition here */ if (mapping_changed()) { if (read_mapping_db()) printerr(0, "all remote users will be denied\n"); } for (n = 0; n < mapping.nitems; n++) { struct user_map_item *entry = &mapping.items[n]; if (entry->nid != LNET_NID_ANY && entry->nid != nid) continue; if (!strcasecmp(entry->principal, princ)) { printerr(1, "found mapping: %s ==> %d\n", princ, entry->uid); *uid = entry->uid; return 0; } } printerr(2, "no mapping for %s/%#Lx\n", princ, nid); return -1; }