freebsd-dev/sys/netncp/ncp_conn.c
Don Lewis 47934cef8f Split the mlock() kernel code into two parts, mlock(), which unpacks
the syscall arguments and does the suser() permission check, and
kern_mlock(), which does the resource limit checking and calls
vm_map_wire().  Split munlock() in a similar way.

Enable the RLIMIT_MEMLOCK checking code in kern_mlock().

Replace calls to vslock() and vsunlock() in the sysctl code with
calls to kern_mlock() and kern_munlock() so that the sysctl code
will obey the wired memory limits.

Nuke the vslock() and vsunlock() implementations, which are no
longer used.

Add a member to struct sysctl_req to track the amount of memory
that is wired to handle the request.

Modify sysctl_wire_old_buffer() to return an error if its call to
kern_mlock() fails.  Only wire the minimum of the length specified
in the sysctl request and the length specified in its argument list.
It is recommended that sysctl handlers that use sysctl_wire_old_buffer()
should specify reasonable estimates for the amount of data they
want to return so that only the minimum amount of memory is wired
no matter what length has been specified by the request.

Modify the callers of sysctl_wire_old_buffer() to look for the
error return.

Modify sysctl_old_user to obey the wired buffer length and clean up
its implementation.

Reviewed by:	bms
2004-02-26 00:27:04 +00:00

672 lines
16 KiB
C

/*
* Copyright (c) 1999, Boris Popov
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Boris Popov.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Connection tables
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/sysctl.h>
#include <netncp/ncp.h>
#include <netncp/nwerror.h>
#include <netncp/ncp_subr.h>
#include <netncp/ncp_conn.h>
#include <netncp/ncp_sock.h>
#include <netncp/ncp_ncp.h>
SLIST_HEAD(ncp_handle_head,ncp_handle);
int ncp_burst_enabled = 1;
struct ncp_conn_head conn_list={NULL};
static int ncp_conn_cnt = 0;
static int ncp_next_ref = 1;
static struct lock listlock;
struct ncp_handle_head lhlist={NULL};
static int ncp_next_handle = 1;
static struct lock lhlock;
static int ncp_sysctl_connstat(SYSCTL_HANDLER_ARGS);
static int ncp_conn_lock_any(struct ncp_conn *conn, struct thread *td,
struct ucred *cred);
SYSCTL_DECL(_net_ncp);
SYSCTL_INT (_net_ncp, OID_AUTO, burst_enabled, CTLFLAG_RD, &ncp_burst_enabled, 0, "");
SYSCTL_INT (_net_ncp, OID_AUTO, conn_cnt, CTLFLAG_RD, &ncp_conn_cnt, 0, "");
SYSCTL_PROC(_net_ncp, OID_AUTO, conn_stat, CTLFLAG_RD|CTLTYPE_OPAQUE,
NULL, 0, ncp_sysctl_connstat, "S,connstat", "Connections list");
MALLOC_DEFINE(M_NCPDATA, "NCP data", "NCP private data");
int
ncp_conn_init(void)
{
lockinit(&listlock, PSOCK, "ncpll", 0, 0);
lockinit(&lhlock, PSOCK, "ncplh", 0, 0);
return 0;
}
int
ncp_conn_destroy(void)
{
if (ncp_conn_cnt) {
NCPERROR("There are %d connections active\n", ncp_conn_cnt);
return EBUSY;
}
lockdestroy(&listlock);
lockdestroy(&lhlock);
return 0;
}
int
ncp_conn_locklist(int flags, struct thread *td)
{
return lockmgr(&listlock, flags | LK_CANRECURSE, 0, td);
}
void
ncp_conn_unlocklist(struct thread *td)
{
lockmgr(&listlock, LK_RELEASE, 0, td);
}
int
ncp_conn_access(struct ncp_conn *conn, struct ucred *cred, mode_t mode)
{
int error;
if (cred == NOCRED || ncp_suser(cred) == 0 ||
cred->cr_uid == conn->nc_owner->cr_uid)
return 0;
mode >>= 3;
if (!groupmember(conn->nc_group, cred))
mode >>= 3;
error = (conn->li.access_mode & mode) == mode ? 0 : EACCES;
return error;
}
int
ncp_conn_lock_any(struct ncp_conn *conn, struct thread *td, struct ucred *cred)
{
int error;
if (conn->nc_id == 0) return EACCES;
error = lockmgr(&conn->nc_lock, LK_EXCLUSIVE | LK_CANRECURSE, 0, td);
if (error == ERESTART)
return EINTR;
error = ncp_chkintr(conn, td);
if (error) {
lockmgr(&conn->nc_lock, LK_RELEASE, 0, td);
return error;
}
if (conn->nc_id == 0) {
lockmgr(&conn->nc_lock, LK_RELEASE, 0, td);
return EACCES;
}
conn->td = td; /* who currently operates */
conn->ucred = cred;
return 0;
}
int
ncp_conn_lock(struct ncp_conn *conn, struct thread *td, struct ucred *cred, int mode)
{
int error;
error = ncp_conn_access(conn, cred, mode);
if (error) return error;
return ncp_conn_lock_any(conn, td, cred);
}
/*
* Lock conn but unlock connlist
*/
static int
ncp_conn_lock2(struct ncp_conn *conn, struct thread *td, struct ucred *cred, int mode)
{
int error;
error = ncp_conn_access(conn, cred, mode);
if (error) {
ncp_conn_unlocklist(td);
return error;
}
conn->nc_lwant++;
ncp_conn_unlocklist(td);
error = ncp_conn_lock_any(conn, td, cred);
conn->nc_lwant--;
if (conn->nc_lwant == 0) {
wakeup(&conn->nc_lwant);
}
return error;
}
void
ncp_conn_unlock(struct ncp_conn *conn, struct thread *td)
{
/*
* note, that LK_RELASE will do wakeup() instead of wakeup_one().
* this will do a little overhead
*/
lockmgr(&conn->nc_lock, LK_RELEASE, 0, td);
}
int
ncp_conn_assert_locked(struct ncp_conn *conn, const char *checker, struct thread *td)
{
if (conn->nc_lock.lk_flags & LK_HAVE_EXCL) return 0;
printf("%s: connection isn't locked!\n", checker);
return EIO;
}
void
ncp_conn_invalidate(struct ncp_conn *ncp)
{
ncp->flags &= ~(NCPFL_ATTACHED | NCPFL_LOGGED | NCPFL_INVALID);
}
int
ncp_conn_invalid(struct ncp_conn *ncp)
{
return ncp->flags & NCPFL_INVALID;
}
/*
* create, fill with defaults and return in locked state
*/
int
ncp_conn_alloc(struct ncp_conn_args *cap, struct thread *td, struct ucred *cred,
struct ncp_conn **conn)
{
struct ncp_conn *ncp;
struct ucred *owner;
int error, isroot;
if (cap->saddr.sa_family != AF_INET && cap->saddr.sa_family != AF_IPX)
return EPROTONOSUPPORT;
isroot = ncp_suser(cred) == 0;
/*
* Only root can change ownership
*/
if (cap->owner != NCP_DEFAULT_OWNER && !isroot)
return EPERM;
if (cap->group != NCP_DEFAULT_GROUP &&
!groupmember(cap->group, cred) && !isroot)
return EPERM;
if (cap->owner != NCP_DEFAULT_OWNER) {
owner = crget();
owner->cr_uid = cap->owner;
} else
owner = crhold(cred);
MALLOC(ncp, struct ncp_conn *, sizeof(struct ncp_conn),
M_NCPDATA, M_WAITOK | M_ZERO);
error = 0;
lockinit(&ncp->nc_lock, PZERO, "ncplck", 0, 0);
ncp_conn_cnt++;
ncp->nc_id = ncp_next_ref++;
ncp->nc_owner = cred;
ncp->seq = 0;
ncp->connid = 0xFFFF;
ncp->li = *cap;
ncp->nc_group = (cap->group != NCP_DEFAULT_GROUP) ?
cap->group : cred->cr_groups[0];
if (cap->retry_count == 0)
ncp->li.retry_count = NCP_RETRY_COUNT;
if (cap->timeout == 0)
ncp->li.timeout = NCP_RETRY_TIMEOUT;
ncp_conn_lock_any(ncp, td, ncp->nc_owner);
*conn = ncp;
ncp_conn_locklist(LK_EXCLUSIVE, td);
SLIST_INSERT_HEAD(&conn_list,ncp,nc_next);
ncp_conn_unlocklist(td);
return (error);
}
/*
* Remove the connection, on entry it must be locked
*/
int
ncp_conn_free(struct ncp_conn *ncp)
{
struct thread *td;
int error;
if (ncp == NULL) {
NCPFATAL("ncp == NULL\n");
return 0;
}
if (ncp->nc_id == 0) {
NCPERROR("nc_id == 0\n");
return EACCES;
}
td = ncp->td;
error = ncp_conn_assert_locked(ncp, __func__, td);
if (error)
return error;
if (ncp->ref_cnt != 0 || (ncp->flags & NCPFL_PERMANENT))
return EBUSY;
if (ncp_conn_access(ncp, ncp->ucred, NCPM_WRITE))
return EACCES;
if (ncp->flags & NCPFL_ATTACHED)
ncp_ncp_disconnect(ncp);
ncp_sock_disconnect(ncp);
/*
* Mark conn as dead and wait for other process
*/
ncp->nc_id = 0;
ncp_conn_unlock(ncp, td);
/*
* if signal is raised - how I do react ?
*/
lockmgr(&ncp->nc_lock, LK_DRAIN, 0, td);
lockdestroy(&ncp->nc_lock);
while (ncp->nc_lwant) {
printf("lwant = %d\n", ncp->nc_lwant);
tsleep(&ncp->nc_lwant, PZERO,"ncpdr",2*hz);
}
ncp_conn_locklist(LK_EXCLUSIVE, td);
SLIST_REMOVE(&conn_list, ncp, ncp_conn, nc_next);
ncp_conn_cnt--;
ncp_conn_unlocklist(td);
if (ncp->li.user)
free(ncp->li.user, M_NCPDATA);
if (ncp->li.password)
free(ncp->li.password, M_NCPDATA);
crfree(ncp->nc_owner);
FREE(ncp, M_NCPDATA);
return (0);
}
int
ncp_conn_reconnect(struct ncp_conn *ncp)
{
int error;
/*
* Close opened sockets if any
*/
ncp_sock_disconnect(ncp);
error = ncp_sock_connect(ncp);
if (error)
return error;
error = ncp_ncp_connect(ncp);
if (error)
return error;
error = ncp_renegotiate_connparam(ncp, NCP_DEFAULT_BUFSIZE, 0);
if (error == NWE_SIGNATURE_LEVEL_CONFLICT) {
printf("Unable to negotiate requested security level\n");
error = EOPNOTSUPP;
}
if (error) {
ncp_ncp_disconnect(ncp);
return error;
}
#ifdef NCPBURST
error = ncp_burst_connect(ncp);
if (error) {
ncp_ncp_disconnect(ncp);
return error;
}
#endif
return 0;
}
int
ncp_conn_login(struct ncp_conn *conn, struct thread *td, struct ucred *cred)
{
struct ncp_bindery_object user;
u_char ncp_key[8];
int error;
error = ncp_get_encryption_key(conn, ncp_key);
if (error) {
printf("%s: Warning: use unencrypted login\n", __func__);
error = ncp_login_unencrypted(conn, conn->li.objtype,
conn->li.user, conn->li.password, td, cred);
} else {
error = ncp_get_bindery_object_id(conn, conn->li.objtype,
conn->li.user, &user, td, cred);
if (error)
return error;
error = ncp_login_encrypted(conn, &user, ncp_key,
conn->li.password, td, cred);
}
if (!error)
conn->flags |= NCPFL_LOGGED | NCPFL_WASLOGGED;
return error;
}
/*
* Lookup connection by handle, return a locked conn descriptor
*/
int
ncp_conn_getbyref(int ref, struct thread *td, struct ucred *cred, int mode,
struct ncp_conn **connpp)
{
struct ncp_conn *ncp;
int error = 0;
ncp_conn_locklist(LK_SHARED, td);
SLIST_FOREACH(ncp, &conn_list, nc_next)
if (ncp->nc_id == ref) break;
if (ncp == NULL) {
ncp_conn_unlocklist(td);
return(EBADF);
}
error = ncp_conn_lock2(ncp, td, cred, mode);
if (!error)
*connpp = ncp;
return (error);
}
/*
* find attached, but not logged in connection to specified server
*/
int
ncp_conn_getattached(struct ncp_conn_args *li, struct thread *td,
struct ucred *cred, int mode, struct ncp_conn **connpp)
{
struct ncp_conn *ncp, *ncp2 = NULL;
int error = 0;
ncp_conn_locklist(LK_SHARED, td);
SLIST_FOREACH(ncp, &conn_list, nc_next) {
if ((ncp->flags & NCPFL_LOGGED) != 0 ||
strcmp(ncp->li.server,li->server) != 0 ||
ncp->li.saddr.sa_len != li->saddr.sa_len ||
bcmp(&ncp->li.saddr,&ncp->li.saddr,li->saddr.sa_len) != 0)
continue;
if (ncp_suser(cred) == 0 ||
cred->cr_uid == ncp->nc_owner->cr_uid)
break;
error = ncp_conn_access(ncp,cred,mode);
if (!error && ncp2 == NULL)
ncp2 = ncp;
}
if (ncp == NULL) ncp = ncp2;
if (ncp == NULL) {
ncp_conn_unlocklist(td);
return(EBADF);
}
error = ncp_conn_lock2(ncp, td, cred, mode);
if (!error)
*connpp=ncp;
return (error);
}
/*
* Lookup connection by server/user pair, return a locked conn descriptor.
* if li is NULL or server/user pair incomplete, try to select best connection
* based on owner.
* Connection selected in next order:
* 1. Try to search conn with ucred owner, if li is NULL also find a primary
* 2. If 1. fails try to get first suitable shared connection
* 3. If 2. fails then nothing can help to poor ucred owner
*/
int
ncp_conn_getbyli(struct ncp_conn_args *li, struct thread *td,
struct ucred *cred, int mode, struct ncp_conn **connpp)
{
struct ncp_conn *ncp, *ncp2 = NULL;
int error = 0, partial, haveserv;
partial = (li == NULL || li->server[0] == 0 || li->user == NULL);
haveserv = (li && li->server[0]);
ncp_conn_locklist(LK_SHARED, td);
SLIST_FOREACH(ncp, &conn_list, nc_next) {
if (partial) {
if (cred->cr_uid == ncp->nc_owner->cr_uid) {
if (haveserv) {
if (strcmp(ncp->li.server,li->server) == 0)
break;
} else {
if (ncp->flags & NCPFL_PRIMARY)
break;
ncp2 = ncp;
}
continue;
}
} else {
if (strcmp(ncp->li.server,li->server) != 0 ||
ncp->li.user == NULL ||
strcmp(ncp->li.user,li->user) != 0)
continue;
if (cred->cr_uid == ncp->nc_owner->cr_uid)
break;
if (ncp_suser(cred) == 0)
ncp2 = ncp;
}
error = ncp_conn_access(ncp,cred,mode);
if (!error && ncp2 == NULL)
ncp2 = ncp;
}
if (ncp == NULL) ncp = ncp2;
if (ncp == NULL) {
ncp_conn_unlocklist(td);
return(EBADF);
}
error = ncp_conn_lock2(ncp, td, cred,mode);
if (!error)
*connpp=ncp;
return (error);
}
/*
* Set primary connection flag, since it have sence only for an owner,
* only owner can modify this flag.
* connection expected to be locked.
*/
int
ncp_conn_setprimary(struct ncp_conn *conn, int on)
{
struct ncp_conn *ncp=NULL;
if (conn->ucred->cr_uid != conn->nc_owner->cr_uid)
return EACCES;
ncp_conn_locklist(LK_SHARED, conn->td);
SLIST_FOREACH(ncp, &conn_list, nc_next) {
if (conn->ucred->cr_uid == ncp->nc_owner->cr_uid)
ncp->flags &= ~NCPFL_PRIMARY;
}
ncp_conn_unlocklist(conn->td);
if (on)
conn->flags |= NCPFL_PRIMARY;
return 0;
}
/*
* Lease conn to given proc, returning unique handle
* problem: how locks should be applied ?
*/
int
ncp_conn_gethandle(struct ncp_conn *conn, struct thread *td, struct ncp_handle **handle)
{
struct ncp_handle *refp;
lockmgr(&lhlock, LK_EXCLUSIVE, 0, td);
SLIST_FOREACH(refp, &lhlist, nh_next)
if (refp->nh_conn == conn && td == refp->nh_td) break;
if (refp) {
conn->ref_cnt++;
refp->nh_ref++;
*handle = refp;
lockmgr(&lhlock, LK_RELEASE, 0, td);
return 0;
}
MALLOC(refp,struct ncp_handle *,sizeof(struct ncp_handle),M_NCPDATA,
M_WAITOK | M_ZERO);
SLIST_INSERT_HEAD(&lhlist,refp,nh_next);
refp->nh_ref++;
refp->nh_td = td;
refp->nh_conn = conn;
refp->nh_id = ncp_next_handle++;
*handle = refp;
conn->ref_cnt++;
lockmgr(&lhlock, LK_RELEASE, 0, td);
return 0;
}
/*
* release reference, if force - ignore refcount
*/
int
ncp_conn_puthandle(struct ncp_handle *handle, struct thread *td, int force)
{
struct ncp_handle *refp = handle;
lockmgr(&lhlock, LK_EXCLUSIVE, 0, td);
refp->nh_ref--;
refp->nh_conn->ref_cnt--;
if (force) {
refp->nh_conn->ref_cnt -= refp->nh_ref;
refp->nh_ref = 0;
}
if (refp->nh_ref == 0) {
SLIST_REMOVE(&lhlist, refp, ncp_handle, nh_next);
FREE(refp, M_NCPDATA);
}
lockmgr(&lhlock, LK_RELEASE, 0, td);
return 0;
}
/*
* find a connHandle
*/
int
ncp_conn_findhandle(int connHandle, struct thread *td, struct ncp_handle **handle) {
struct ncp_handle *refp;
lockmgr(&lhlock, LK_SHARED, 0, td);
SLIST_FOREACH(refp, &lhlist, nh_next)
if (refp->nh_td == td && refp->nh_id == connHandle) break;
lockmgr(&lhlock, LK_RELEASE, 0, td);
if (refp == NULL) {
return EBADF;
}
*handle = refp;
return 0;
}
/*
* Clear handles associated with specified process
*/
int
ncp_conn_putprochandles(struct thread *td)
{
struct ncp_handle *hp, *nhp;
int haveone = 0;
lockmgr(&lhlock, LK_EXCLUSIVE, 0, td);
for (hp = SLIST_FIRST(&lhlist); hp; hp = nhp) {
nhp = SLIST_NEXT(hp, nh_next);
if (hp->nh_td != td) continue;
haveone = 1;
hp->nh_conn->ref_cnt -= hp->nh_ref;
SLIST_REMOVE(&lhlist, hp, ncp_handle, nh_next);
FREE(hp, M_NCPDATA);
}
lockmgr(&lhlock, LK_RELEASE, 0, td);
return haveone;
}
/*
* remove references in all possible connections,
* XXX - possible problem is a locked list.
*/
/*void
ncp_conn_list_rm_ref(pid_t pid) {
struct ncp_conn *ncp;
ncp_conn_locklist(LK_SHARED, NULL);
SLIST_FOREACH(ncp, &conn_list, nc_next) {
ncp_conn_rm_ref(ncp,pid,1);
}
ncp_conn_unlocklist(NULL);
return;
}
*/
int
ncp_conn_getinfo(struct ncp_conn *ncp, struct ncp_conn_stat *ncs) {
bzero(ncs,sizeof(*ncs));
ncs->li = ncp->li;
ncs->li.user = ncs->user;
if (ncp->li.user)
strcpy(ncs->user, ncp->li.user);
ncs->li.password = NULL;
ncs->connRef = ncp->nc_id;
ncs->ref_cnt = ncp->ref_cnt;
ncs->connid = ncp->connid;
ncs->owner = ncp->nc_owner->cr_uid;
ncs->group = ncp->nc_group;
ncs->flags = ncp->flags;
ncs->buffer_size = ncp->buffer_size;
return 0;
}
static int
ncp_sysctl_connstat(SYSCTL_HANDLER_ARGS)
{
int error;
struct ncp_conn_stat ncs;
struct ncp_conn *ncp;
/* struct ucred *cred = req->td->td_ucred;*/
error = sysctl_wire_old_buffer(req, 0);
if (error != 0)
return (error);
ncp_conn_locklist(LK_SHARED, req->td);
error = SYSCTL_OUT(req, &ncp_conn_cnt, sizeof(ncp_conn_cnt));
SLIST_FOREACH(ncp, &conn_list, nc_next) {
if (error) break;
/* I can't do conn_lock while list is locked */
ncp->nc_lwant++;
if (!error) {
ncp_conn_getinfo(ncp, &ncs);
} else {
bzero(&ncs,sizeof(ncs));
ncs.connRef = ncp->nc_id;
strcpy(ncs.li.server,"***");
}
ncp->nc_lwant--;
error = SYSCTL_OUT(req, &ncs, sizeof(ncs));
}
ncp_conn_unlocklist(req->td);
return(error);
}