static Npuser * _alloc_user (Npsrv *srv, struct passwd *pwd) { Npuser *u; if (!(u = malloc (sizeof (*u)))) { np_uerror (ENOMEM); np_logerr (srv, "_alloc_user: %s", pwd->pw_name); goto error; } u->sg = NULL; u->nsg = 0; if (!(u->uname = strdup (pwd->pw_name))) { np_uerror (ENOMEM); np_logerr (srv, "_alloc_user: %s", pwd->pw_name); goto error; } u->uid = pwd->pw_uid; u->gid = pwd->pw_gid; if (u->uid != 0 && _getgrouplist(srv, u) < 0) goto error; pthread_mutex_init (&u->lock, NULL); u->refcount = 0; u->t = time (NULL); u->next = NULL; if (srv->flags & SRV_FLAGS_DEBUG_USER) np_logmsg (srv, "user lookup: %d", u->uid); return u; error: if (u) _free_user (u); return NULL; }
/* This needs to be called with the usercache lock held. * I don't think it's thread safe. -jg */ static int _getgrouplist (Npsrv *srv, Npuser *u) { int i, ret = -1; gid_t *sgcpy; u->nsg = sysconf(_SC_NGROUPS_MAX); if (u->nsg < 65536) u->nsg = 65536; if (!(u->sg = malloc (u->nsg * sizeof (gid_t)))) { np_uerror (ENOMEM); np_logerr (srv, "_alloc_user: %s", u->uname); goto done; } if (getgrouplist(u->uname, u->gid, u->sg, &u->nsg) == -1) { np_logerr (srv, "_alloc_user: %s: getgrouplist", u->uname); if (np_rerror () == 0) np_uerror (EPERM); goto done; } if ((sgcpy = malloc (u->nsg * sizeof (gid_t)))) { for (i = 0; i < u->nsg; i++) sgcpy[i] = u->sg[i]; free (u->sg); u->sg = sgcpy; } ret = 0; done: return ret; }
Npfcall * np_auth(Npreq *req, Npfcall *tc) { Npconn *conn = req->conn; Npsrv *srv = conn->srv; Npfid *afid = req->fid; Npfcall *rc = NULL; Npqid aqid; char a[128]; int auth_required = _authrequired(srv, &tc->u.tauth.uname, tc->u.tauth.n_uname, &tc->u.tauth.aname); if (tc->u.tauth.n_uname != P9_NONUNAME) { snprintf (a, sizeof(a), "auth(%d@%s:%.*s)", tc->u.tauth.n_uname, np_conn_get_client_id (conn), tc->u.tauth.aname.len, tc->u.tauth.aname.str); } else { snprintf (a, sizeof(a), "auth(%.*s@%s:%.*s)", tc->u.tauth.uname.len, tc->u.tauth.uname.str, np_conn_get_client_id (conn), tc->u.tauth.aname.len, tc->u.tauth.aname.str); } if (!auth_required) { if (!(rc = np_create_rlerror(0))) { np_uerror(ENOMEM); np_logerr (srv, "%s: creating response", a); } goto error; } if (!afid) { np_uerror (EIO); np_logerr (srv, "%s: invalid afid (%d)", a, tc->u.tauth.afid); goto error; } np_fid_incref(afid); if (!(afid->user = np_attach2user (srv, &tc->u.tauth.uname, tc->u.tauth.n_uname))) { np_logerr (srv, "%s: user lookup", a); goto error; } afid->type = P9_QTAUTH; if (!srv->auth->startauth(afid, afid->aname, &aqid)) { np_logerr (srv, "%s: startauth", a); goto error; } assert((aqid.type & P9_QTAUTH)); if (!(rc = np_create_rauth(&aqid))) { np_uerror(ENOMEM); np_logerr (srv, "%s: creating response", a); goto error; } error: return rc; }
static void np_tpool_destroy(Nptpool *tp) { Npsrv *srv = tp->srv; Npwthread *wt, *next; void *retval; int err, i; for(wt = tp->wthreads; wt != NULL; wt = wt->next) { wt->shutdown = 1; } xpthread_cond_broadcast(&tp->reqcond); for (i = 0, wt = tp->wthreads; wt != NULL; wt = next, i++) { next = wt->next; if ((err = pthread_join (wt->thread, &retval))) { np_uerror (err); np_logerr(srv, "%s: join thread %d", tp->name, i); } else if (retval == PTHREAD_CANCELED) { np_logmsg(srv, "%s: join thread %d: cancelled", tp->name, i); } else if (retval != NULL) { np_logmsg(srv, "%s: join thread %d: non-NULL return", tp->name, i); } free (wt); } pthread_cond_destroy (&tp->reqcond); pthread_mutex_destroy (&tp->lock); if (tp->name) free (tp->name); free (tp); }
void np_tpool_select (Npreq *req) { Npsrv *srv = req->conn->srv; Nptpool *tp; if ((srv->flags & SRV_FLAGS_TPOOL_SINGLE)) return; if (!req->fid || !req->fid->aname || *req->fid->aname != '/') return; if (req->fid->tpool) return; xpthread_mutex_lock (&srv->lock); for (tp = srv->tpool; tp != NULL; tp = tp->next) { if (!strcmp (req->fid->aname, tp->name)) break; } if (!tp) { tp = np_tpool_create(srv, req->fid->aname); if (tp) { assert (srv->tpool); /* default tpool */ tp->next = srv->tpool->next; srv->tpool->next = tp; } else np_logerr (srv, "np_tpool_create %s", req->fid->aname); } if (tp) { np_tpool_incref (tp); req->fid->tpool = tp; } xpthread_mutex_unlock (&srv->lock); }
/* Create an Npuser struct for a user, without requiring * that user to be in the password/group file. * N.B. gid is assumed to be same as uid. */ static Npuser * _alloc_nouserdb (Npsrv *srv, uid_t uid, char *name) { Npuser *u = NULL; char ustr[32] = "root"; if (name) { if (strcmp (name, "root") != 0) { np_uerror (EPERM); goto error; } uid = 0; } if (uid != 0) snprintf (ustr, sizeof (ustr), "%d", uid); if (!(u = malloc (sizeof (*u)))) { np_uerror (ENOMEM); np_logerr (srv, "_alloc_nouserdb: %s", ustr); goto error; } u->sg = NULL; if (!(u->uname = strdup (ustr))) { np_uerror (ENOMEM); np_logerr (srv, "_alloc_nouserdb: %s", ustr); goto error; } u->uid = uid; u->gid = (gid_t)uid; u->nsg = 1; if (!(u->sg = malloc (sizeof (gid_t) * u->nsg))) { np_uerror (ENOMEM); np_logerr (srv, "_alloc_nouserdb: %s", ustr); goto error; } u->sg[0] = u->gid; pthread_mutex_init (&u->lock, NULL); if (srv->flags & SRV_FLAGS_DEBUG_USER) np_logmsg (srv, "user lookup: %d", u->uid); u->refcount = 0; u->t = time (NULL); u->next = NULL; return u; error: if (u) _free_user (u); return NULL; }
/* When handling requests on connections authenticated as root, we consider * it safe to disable DAC checks on the server and presume the client is * doing it. This is only done if the server sets SRV_FLAGS_DAC_BYPASS. */ static int _chg_privcap (Npsrv *srv, cap_flag_value_t val) { cap_t cap; cap_flag_value_t cur; cap_value_t cf[] = { CAP_DAC_OVERRIDE, CAP_CHOWN, CAP_FOWNER }; int need_set = 0; int i, ret = -1; if (!(cap = cap_get_proc ())) { np_uerror (errno); np_logerr (srv, "cap_get_proc failed"); goto done; } for (i = 0; i < sizeof(cf) / sizeof(cf[0]); i++) { if (cap_get_flag (cap, cf[i], CAP_EFFECTIVE, &cur) < 0) { np_uerror (errno); np_logerr (srv, "cap_get_flag failed"); goto done; } if (cur == val) continue; need_set = 1; if (cap_set_flag (cap, CAP_EFFECTIVE, 1, &cf[i], val) < 0) { np_uerror (errno); np_logerr (srv, "cap_set_flag failed"); goto done; } } if (need_set && cap_set_proc (cap) < 0) { np_uerror (errno); np_logerr (srv, "cap_set_proc failed"); goto done; } ret = 0; done: if (cap != NULL && cap_free (cap) < 0) { np_uerror (errno); np_logerr (srv, "cap_free failed"); } return ret; }
static Npuser * _real_lookup_byname (Npsrv *srv, char *uname) { Npuser *u; int err, len; struct passwd pw, *pwd = NULL; char *buf = NULL; if (srv->flags & SRV_FLAGS_NOUSERDB) { if (!(u = _alloc_nouserdb (srv, P9_NONUNAME, uname))) goto error; } else { len= sysconf(_SC_GETPW_R_SIZE_MAX); if (len < 4096) len = 4096; if (!(buf = malloc (len))) { np_uerror (ENOMEM); np_logerr (srv, "uname2user: %s", uname); goto error; } if ((err = getpwnam_r (uname, &pw, buf, len, &pwd)) != 0) { np_uerror (err); np_logerr (srv, "uname2user: %s: getpwnam_r", uname); goto error; } if (!pwd) { np_logmsg (srv, "uname2user: %s lookup failure", uname); np_uerror (EPERM); goto error; } if (!(u = _alloc_user (srv, pwd))) goto error; free (buf); } return u; error: if (buf) free (buf); return NULL; }
static Npuser * _real_lookup_byuid (Npsrv *srv, uid_t uid) { Npuser *u; int err, len; struct passwd pw, *pwd; char *buf = NULL; if (srv->flags & SRV_FLAGS_NOUSERDB) { if (!(u = _alloc_nouserdb (srv, uid, NULL))) goto error; } else { len = sysconf(_SC_GETPW_R_SIZE_MAX); if (len < 4096) len = 4096; if (!(buf = malloc (len))) { np_uerror (ENOMEM); np_logerr (srv, "uid2user"); goto error; } if ((err = getpwuid_r (uid, &pw, buf, len, &pwd)) != 0) { np_uerror (err); np_logerr (srv, "uid2user: unable to lookup %d", uid); goto error; } if (!pwd) { np_logmsg (srv, "uid2user: unable to lookup %d", uid); np_uerror (EPERM); goto error; } if (!(u = _alloc_user (srv, pwd))) goto error; free (buf); } return u; error: if (buf) free (buf); return NULL; }
void np_conn_respond(Npreq *req) { int n; Npconn *conn = req->conn; Npsrv *srv = conn->srv; Npfcall *rc = req->rcall; _debug_trace (srv, rc); xpthread_mutex_lock(&conn->wlock); n = np_trans_send(conn->trans, rc); xpthread_mutex_unlock(&conn->wlock); if (n < 0) np_logerr (srv, "send to '%s'", conn->client_id); }
/* Per-connection read thread. */ static void * np_conn_read_proc(void *a) { int i, n, size; Npsrv *srv; Npconn *conn = (Npconn *)a; Nptrans *trans; Npreq *req; Npfcall *fc, *fc1; pthread_detach(pthread_self()); np_conn_incref(conn); srv = conn->srv; fc = _alloc_npfcall(conn->msize); n = 0; while (fc && conn->trans && (i = np_trans_read(conn->trans, fc->pkt + n, conn->msize - n)) > 0) { n += i; again: size = np_peek_size (fc->pkt, n); if (size == 0 || n < size) continue; /* Corruption on the transport, unhandled op, etc. * is fatal to the connection. We could consider returning * an error to the client here. However, various kernels * may not handle that well, depending on where it happens. */ if (!np_deserialize(fc, fc->pkt)) { _debug_trace (srv, fc); np_logerr (srv, "protocol error - " "dropping connection to '%s'", conn->client_id); break; } if ((srv->flags & SRV_FLAGS_DEBUG_9PTRACE)) _debug_trace (srv, fc); /* Replace fc, and copy any data past the current packet * to the replacement. */ fc1 = _alloc_npfcall(conn->msize); if (!fc1) { np_logerr (srv, "out of memory in receive path - " "dropping connection to '%s'", conn->client_id); break; } if (n > size) memmove(fc1->pkt, fc->pkt + size, n - size); n -= size; /* Encapsulate fc in a request and hand to srv worker threads. * In np_req_alloc, req->fid is looked up/initialized. */ req = np_req_alloc(conn, fc); if (!req) { np_logerr (srv, "out of memory in receive path - " "dropping connection to '%s'", conn->client_id); break; } np_srv_add_req(srv, req); xpthread_mutex_lock(&conn->lock); conn->reqs_in++; xpthread_mutex_unlock(&conn->lock); fc = fc1; if (n > 0) goto again; } /* Just got EOF on read, or some other fatal error for the * connection like out of memory. */ xpthread_mutex_lock(&conn->lock); trans = conn->trans; conn->trans = NULL; if (fc) _free_npfcall(fc); xpthread_mutex_unlock(&conn->lock); np_srv_remove_conn(conn->srv, conn); np_conn_reset(conn); if (trans) np_trans_destroy(trans); np_conn_decref(conn); return NULL; }
/* Note: it is possible for setfsuid/setfsgid to fail silently, * e.g. if user doesn't have CAP_SETUID/CAP_SETGID. * That should be checked at server startup. */ int np_setfsid (Npreq *req, Npuser *u, u32 gid_override) { Npwthread *wt = req->wthread; Npsrv *srv = req->conn->srv; int i, n, ret = -1; u32 gid; uid_t authuid; int dumpable = prctl (PR_GET_DUMPABLE, 0, 0, 0, 0); int dumpclrd = 0; if (np_conn_get_authuser(req->conn, &authuid) < 0) authuid = P9_NONUNAME; if ((srv->flags & SRV_FLAGS_SETFSID)) { /* gid_override must be one of user's suppl. groups unless * connection was originally authed as root (trusted). */ if (gid_override != -1 && u->uid != 0 && authuid != 0 && !(srv->flags & SRV_FLAGS_NOUSERDB)) { for (i = 0; i < u->nsg; i++) { if (u->sg[i] == gid_override) break; } if (i == u->nsg) { np_uerror (EPERM); np_logerr (srv, "np_setfsid(%s): gid_override " "%d not in user's sg list", u->uname, gid_override); goto done; } } gid = (gid_override == -1 ? u->gid : gid_override); if (wt->fsgid != gid) { dumpclrd = 1; if ((n = setfsgid (gid)) < 0) { np_uerror (errno); np_logerr (srv, "setfsgid(%s) gid=%d failed", u->uname, gid); wt->fsgid = P9_NONUNAME; goto done; } if (n != wt->fsgid) { np_uerror (errno); np_logerr (srv, "setfsgid(%s) gid=%d failed" "returned %d, expected %d", u->uname, gid, n, wt->fsgid); wt->fsgid = P9_NONUNAME; goto done; } wt->fsgid = gid; } if (wt->fsuid != u->uid) { dumpclrd = 1; if ((n = setfsuid (u->uid)) < 0) { np_uerror (errno); np_logerr (srv, "setfsuid(%s) uid=%d failed", u->uname, u->uid); wt->fsuid = P9_NONUNAME; goto done; } if (n != wt->fsuid) { np_uerror (EPERM); np_logerr (srv, "setfsuid(%s) uid=%d failed: " "returned %d, expected %d", u->uname, u->uid, n, wt->fsuid); wt->fsuid = P9_NONUNAME; goto done; } /* Track CAP side effects of setfsuid. */ if (u->uid == 0) wt->privcap = 1; /* transiton to 0 sets caps */ else if (wt->fsuid == 0) wt->privcap = 0; /* trans from 0 clears caps */ /* Suppl groups need to be part of cred for NFS * forwarding even with DAC_BYPASS. However only * do it if kernel treats sg's per-thread not process. * Addendum: late model glibc attempts to make this * per-process, so for now bypass glibc. See issue 53. */ if ((srv->flags & SRV_FLAGS_SETGROUPS)) { if (syscall(SYS_setgroups, u->nsg, u->sg) < 0) { np_uerror (errno); np_logerr (srv, "setgroups(%s) nsg=%d failed", u->uname, u->nsg); wt->fsuid = P9_NONUNAME; goto done; } } wt->fsuid = u->uid; } } #if HAVE_LIBCAP if ((srv->flags & SRV_FLAGS_DAC_BYPASS) && wt->fsuid != 0) { if (!wt->privcap && authuid == 0) { if (_chg_privcap (srv, CAP_SET) < 0) goto done; wt->privcap = 1; dumpclrd = 1; } else if (wt->privcap && authuid != 0) { if (_chg_privcap (srv, CAP_CLEAR) < 0) goto done; wt->privcap = 0; dumpclrd = 1; } } #endif ret = 0; done: if (dumpable && dumpclrd && prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) np_logerr (srv, "prctl PR_SET_DUMPABLE failed"); return ret; }
/* Per-connection read thread. */ static void * np_conn_read_proc(void *a) { Npconn *conn = (Npconn *)a; Npsrv *srv = conn->srv; Npreq *req; Npfcall *fc; pthread_detach(pthread_self()); for (;;) { if (np_trans_recv(conn->trans, &fc, conn->msize) < 0) { np_logerr (srv, "recv error - " "dropping connection to '%s'", conn->client_id); break; } if (!fc) /* EOF */ break; _debug_trace (srv, fc); /* Encapsulate fc in a request and hand to srv worker threads. * In np_req_alloc, req->fid is looked up/initialized. */ req = np_req_alloc(conn, fc); if (!req) { np_logmsg (srv, "out of memory in receive path - " "dropping connection to '%s'", conn->client_id); free (fc); break; } /* Enqueue request for processing by next available worker * thread, except P9_TFLUSH which is handled immediately. */ if (fc->type == P9_TFLUSH) { if (np_flush (req, fc)) { np_req_respond_flush (req); np_req_unref(req); } xpthread_mutex_lock (&srv->lock); srv->tpool->stats.nreqs[P9_TFLUSH]++; xpthread_mutex_unlock (&srv->lock); } else { xpthread_mutex_lock(&srv->lock); np_srv_add_req(srv, req); xpthread_mutex_unlock(&srv->lock); } } /* Just got EOF on read, or some other fatal error for the * connection like out of memory. */ np_conn_flush (conn); xpthread_mutex_lock(&conn->lock); while (conn->refcount > 0) xpthread_cond_wait(&conn->refcond, &conn->lock); xpthread_mutex_unlock(&conn->lock); np_conn_destroy(conn); return NULL; }
Npfcall * np_attach(Npreq *req, Npfcall *tc) { Npconn *conn = req->conn; Npsrv *srv = conn->srv; Npfid *fid = req->fid; Npfid *afid = NULL; Npfcall *rc = NULL; char a[128]; int auth_required = _authrequired(srv, &tc->u.tattach.uname, tc->u.tattach.n_uname, &tc->u.tattach.aname); if (tc->u.tattach.n_uname != P9_NONUNAME) { snprintf (a, sizeof(a), "attach(%d@%s:%.*s)", tc->u.tattach.n_uname, np_conn_get_client_id (conn), tc->u.tattach.aname.len, tc->u.tattach.aname.str); } else { snprintf (a, sizeof(a), "attach(%.*s@%s:%.*s)", tc->u.tattach.uname.len, tc->u.tattach.uname.str, np_conn_get_client_id (conn), tc->u.tattach.aname.len, tc->u.tattach.aname.str); } if (!fid) { np_uerror (EIO); np_logerr (srv, "%s: invalid fid (%d)", a, tc->u.tattach.fid); goto error; } if (tc->u.tattach.afid != P9_NOFID) { if (!(afid = np_fid_find(conn, tc->u.tattach.afid))) { np_uerror(EPERM); np_logerr (srv, "%s: invalid afid (%d)", a, tc->u.tattach.afid); goto error; } np_fid_incref(afid); if (!(afid->type & P9_QTAUTH)) { np_uerror(EPERM); np_logerr (srv, "%s: invalid afid type", a); goto error; } } if (auth_required) { if (afid) { fid->user = np_afid2user (afid, &tc->u.tattach.uname, tc->u.tattach.n_uname); if (!fid->user) { np_logerr (srv, "%s: invalid afid user", a); goto error; } if (srv->auth->checkauth(fid, afid, fid->aname) == 0) { np_logerr (srv, "%s: checkauth", a); goto error; } np_conn_set_authuser(conn, fid->user->uid); } else { u32 uid; fid->user = np_attach2user (srv, &tc->u.tattach.uname, tc->u.tattach.n_uname); if (!fid->user) { np_logerr (srv, "%s: user lookup", a); goto error; } if (!(srv->flags & SRV_FLAGS_AUTHCONN)) { np_uerror(EPERM); np_logerr (srv, "%s: auth required", a); goto error; } if (np_conn_get_authuser(conn, &uid) < 0) { np_uerror(EPERM); np_logerr (srv, "%s: prior auth required", a); goto error; } if (uid != 0 && uid != fid->user->uid) { np_uerror(EPERM); np_logerr (srv, "%s: insufficient auth", a); goto error; } } } if (srv->remapuser) { /* squash user handling */ if (srv->remapuser(fid, &tc->u.tattach.uname, tc->u.tattach.n_uname, &tc->u.tattach.aname) < 0) { np_logerr (srv, "%s: error remapping user", a); goto error; } } if (!fid->user) { fid->user = np_attach2user (srv, &tc->u.tattach.uname, tc->u.tattach.n_uname); if (!fid->user) { np_logerr (srv, "%s: user lookup", a); goto error; } } if (!strcmp (fid->aname, "ctl")) { rc = np_ctl_attach (fid, afid, fid->aname); } else { if (!srv->attach) { np_uerror (EIO); goto error; } rc = (*srv->attach)(fid, afid, &tc->u.tattach.aname); } error: if (afid) np_fid_decref(afid); return rc; }