nis_ib_proc.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1990-2001 by Sun Microsystems, Inc.
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Ported from
* "@(#)nis_ib_proc.c 1.37 91/03/21 Copyr 1990 Sun Micro";
*
*
* This module contains information base function of NIS Version 3
* NB : It provides the routines that the dispatch function in nis_svc.c
* call. That file, nis_svc.c, is automatically generated and reflects the
* interface definition that is described in the nis.x file. When the
* nis.x file changes, you must make sure that any parameters that change
* get reflected in these routines.
*
*/
#include <stdio.h>
#include <syslog.h>
#include <stdlib.h>
#include <rpc/auth_des.h>
#include <rpcsvc/nis_callback.h>
#include "nis_proc.h"
#include "nisdb_mt.h"
#include <string.h>
#include <netdir.h>
#include <netconfig.h>
extern bool_t xdr_nis_result();
extern bool_t xdr_nis_error();
extern bool_t xdr_nis_object();
extern void (*_svc_getreqset_proc)();
#define NO_ENTRY_OBJS 96
/*
* nis_censor_object[_attr]
*
* This function enforces the access rights policies for objects.
* It is called with a candidate ENTRY object and a principal name.
* The result is either NULL (object not readable) or a pointer to
* an object that has been "censored" by this code to remove sensitive
* columns or encrypt encrypted columns.
* nis_censor_object_attr adds a fix which ensures that
* users cannot infer information from the database by using special
* search criteria.
*/
nis_object *o; /* The object to censor */
nis_name p; /* The principal in question */
{
int mc; /* Number of columns */
int i, j,
*ec; /* Some temporary buffer */
/* copy the object */
zo = *o;
/* get some temporay space. */
if (! ec)
return (NULL);
/* Use our static version of the columns */
/* Get a pointer to the original columns */
somedata = 0;
for (j = 0; j < mc; j++) {
/* Transfer the flags values */
#ifdef NIS_CRYPT_SUPPORT
#endif
/* Check to see if we can read this column */
/* close inference holes */
for (i = 0; i < zn; i++)
return (NULL);
/* Replace with No Permission value */
} else {
somedata++;
#ifdef NIS_CRYPT_SUPPORT
/* Encrypt the data using the session key */
/* XXX */
}
#endif
}
}
return (NULL);
return (nis_clone_object(&zo, 0));
}
/*
* The original function (nis_censor_object) takes
* the old arguments, and passes them on with new placeholder arguments to
* the new function (nis_censor_object_attr). Only YP compat functions
* still call nis_censor_object.
*/
*nis_censor_object(o, tc, p)
nis_object *o; /* The object to censor */
nis_name p; /* The principal in question */
{
}
/*
* This is the free function for the list results that are returned "enmasse"
* without a callback.
*/
static void
{
}
/*
* nis_return_list()
*
* This function converts a full list of entries returned by the database
* into a list that can be returned to the user. It has two main functions,
* #1) Remove those entries from the list which the client does not
* have read access too.
* #2) If the client has read access to the entry, censor those
* columns which the client does not have read access to.
*/
int num; /* Size of the array */
nis_name p; /* principal making reqst */
int *got; /* Number actually returned */
int ar; /* All readable flag */
{
int i, /* Temporary counters */
cur_entry; /* The current entry */
*got = 0;
/* Get some parameters from the table object */
else
/* This may allocate more than we need but it is faster this way */
if (! entries)
return (NULL);
return (NULL);
if (! list[i].o)
continue;
if (ar ||
cur_entry++;
} else {
if (eo) {
"nis_censor_object_attr result");
cur_entry++;
}
}
}
return (entries);
}
/*
* __search_ok()
*
* This function does a sanity check on the request (which has attribute
* value pairs) and the table which defines the names of searchable columns.
* if an attribute is passed that doesn't match up to a column the request
* is rejected.
*/
static int
{
int i, j, mc;
} else {
/* Can't specify anything for directories */
return (zn == 0);
}
for (i = 0; i < zn; i++) {
for (j = 0; j < mc; j++) {
== 0)) {
if (acm[i] == -1)
acm[i] = j;
else
acm[i] = -2;
break;
}
}
/*
* If we didn't find a searchable column with the name,
* check to see if it is a multival column. If it is,
* then continue on to the other attributes. We don't
* update acm because multival columns can be specified
* multiple times.
*/
if (acm[i] == -1) {
continue;
}
/* Didn't match or matched twice */
if (acm[i] < 0) {
return (0);
}
}
return (1);
}
/*
* __nis_listinit()
*
* If it is successful, it returns a pointer to the object to be
* listed (either a table or directory object), if it is unsuccessful
* it returns NULL and fills in the result structure appropriately.
*/
static nis_object *
{
struct ticks t;
enum name_pos p;
NIS_RES_NUMOBJ(res) = 0;
if ((p != SAME_NAME) && (p != LOWER_NAME)) {
return (NULL);
}
/* Make sure we serve this directory. */
/*
* Hmm, we didn't find it. Maybe it _is_ our directory
* object.
*/
/* They really blew it */
return (NULL);
} else {
}
}
/* Now get the information base object from the database */
if (! ib_obj) {
/* This means table of directory does not exist. */
/* for sure, argp->ibr_name will not be there */
}
else
return (NULL);
}
} else {
/*
* If its a directory, make sure we've got a database
* for it. (if not create one and synchronize)
*/
switch (status) {
case NIS_SUCCESS:
break;
case NIS_NOSUCHTABLE:
return (NULL);
}
break;
default:
return (NULL);
}
}
case NIS_TABLE_OBJ:
case NIS_DIRECTORY_OBJ:
break;
case NIS_LINK_OBJ:
if (verbose)
/*
* we return a partial result indicating that the LINK was
* found and the client can follow it if they choose to.
*/
return (NULL);
default:
/*
* XXX this hard codes the "kinds" of objects that can be
* information bases. This is not nice if we want to support
* user designed objects.
*/
if (verbose)
"__nis_listinit: Nonsearchable object (type = %d).",
return (NULL);
}
/* Check to see is we have a valid search criteria */
if (verbose)
"__nis_listinit: Unparsable attribute failure.");
return (NULL);
}
return (ib_obj);
}
/*
* Does the callback part of iblist
*/
static nis_result *
char *pname; /* caller's name */
int all_read; /* all readable flag */
{
int i, queued;
/* An array of object pointers */
char *table;
nis_attr *a;
int na; /* number of regular attributes */
int nm; /* number of multival attributes */
/*
* Used to create the callback handle here. We now delay
* until we know that we actually intend to perform the callback.
*/
if (! table) {
return (res);
}
if (err != NIS_SUCCESS) {
return (res);
}
/*
* This will force the database to be loaded.
*/
} else {
}
return (res);
}
if (verbose)
else
/* If we couldn't create a client handle we're hosed */
if (! cback) {
return (res);
}
/* Create a new thread to perform the callback */
{
int stat;
"nis_iblist_callback: memory allocation failed for %d bytes",
sizeof (*cbtarg));
return (res);
}
} else {
"nis_iblist_callback: too many attributes; max %d expected, %d found",
#ifdef NIS_MT_DEBUG
abort();
#endif /* NIS_MT_DEBUG */
}
sizeof (cbtarg->cbhostname));
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr,
cbtarg)) == 0) {
if (verbose)
"nis_iblist_callback: created callback thread %d", tid);
} else {
if (verbose)
"nis_iblist_callback: callback thread create failed: %d",
stat);
}
(void) pthread_attr_destroy(&attr);
"Couldn't allocate memory for callback id");
/* what error to return in case of no memory */
} else {
add_cleanup((void (*)())XFREE,
sizeof (anonid));
if (verbose)
"returning, callback id = %d",
}
return (res);
}
}
/*
* __nis_alt_callback_server()
*
* Return a pointer to a new chunk of memory containing a copy of the
* org_endpoint argument, with the rpc_origin address substituted (merged,
* since the port number is retained) for the first endpoint whose family
* matches an NC_TPI_COTS netconfig entry.
*
* If (*uaddr != 0), it is assumed to point to a piece of memory of length
* uaddrlen, where the caller wants a copy of the uaddr.
*
* It is the responsibility of the caller to free() the returned endpoint
* structure, as well as the uaddr if (*uaddr == 0).
*/
endpoint *
struct netbuf *rpc_origin,
char **uaddr,
int uaddrlen)
{
void *newaddr;
void *rpcaddr;
int addrlen;
char *tmpuaddr;
/* Sanity check arguments */
return (0);
}
/* We only know how to deal with IP */
return (0);
}
/* Retrieve a netconfig entry for "inet" that matches an endpoint */
for (i = 0; i < count; i++) {
} else if (strcasecmp(
} else {
continue;
}
ei = i;
break;
}
}
/* Did we find one ? */
if (match == 0) {
return (0);
}
/*
* Allocate memory for the alternate endpoint array.
* Must be freed by caller, if we return successfully.
*/
return (0);
}
for (j = 0; j < count; j++) {
alt_endpoint[j] = org_endpoint[j];
}
/* Get the address suggested by the remote caller */
return (0);
}
/*
* The ugly part: merge port number and RPC source address.
* This requires knowledge about the internals of a netbuf.
*/
newaddr =
rpcaddr =
} else {
newaddr =
rpcaddr =
}
/*
* If addresses are the same, there is no need to try an alternate
* address, so just pretend that we failed.
*/
return (0);
}
/* Alternate address differs; worth trying */
return (0);
}
if (*uaddr == 0) {
} else {
return (0);
}
/* Clean up */
return (alt_endpoint);
}
/*
* nis_iblist, this function will take the search criteria passed,
* and pass it on to the database. If the callback structure is
* null then the results are simply returned, otherwise the server
* forks and and begins returning results one by one. In a multithreaded
* environment this is handled by a thread. The library will check
* a resource limit variable before forking.
*/
{
*list;
int got;
char *s;
int all_read; /* all readable flag */
char pname[1024];
int zn;
__start_clock(0);
if (verbose)
/* If reqstp == NULL then we're recursing */
if (reqstp)
else
pname[0] = '\0';
return (res);
}
/* If we have read access on the table, we can read all of it */
/*
* Now we actually do the list operation. If there is a callback,
* one at a time.
*/
/* Process the callback request. */
/*
* The client supplied an address for the callback service.
* However, we may not be able to reach that address, so
* try the source address of the RPC request first.
*
* Note: The alt_endpoint array (which usually contains just
* a single element) is a copy of of org_endpoint, including
* pointers, except for the uaddr of one element. This uaddr
* contains a merged version of the RPC source address and the
* port number specified by the client in the callback data.
*/
{
struct netbuf *rpc_origin;
char *uaddr = 0;
&uaddr, 0)) != 0) {
return (res);
}
}
}
}
/*
* If the client didn't ask for a callback, we process the
* entire list at once and return it. (implicit else clause)
*/
} else {
s = "table";
} else
s = "directory";
NIS_RES_NUMOBJ(res) = 0;
}
if (verbose)
return (res);
}
/* Now process the list of objects we've got to return */
/* Construct the result */
if (got) {
} else {
NIS_RES_NUMOBJ(res) = 0;
}
return (res);
}
/*
* This is an internal Information Base operations function. It is collected
* here for clarity. It makes the actual database calls to manipulate the
* namespace.
*/
static void
int zn; /* Number of attributes */
*tbl; /* Table we're adding to */
int *xid; /* XID for transaction */
{
/*
* Check to see if we can actually create an entry in this
* table.
*/
if (auth_verbose) {
"add_entry: creation is %s by the table.",
}
if (*xid == 0) {
return;
}
} else if ((flags & ADD_OVERWRITE) == 0) {
} else if (! mod_ok &&
} else {
/* Act like a modify on the OID */
/*
* Tell the DB that we're doing a modify, so that
* update.
*/
tsd = __nisdb_get_tsd();
/* This updates the log */
/* As does this, a virtual modify operation */
tsd->doingModify = 0;
}
/* Ok, it doesn't exist so lets add it to the table */
if (! add_ok) {
return;
}
}
} else
}
/*
* This is an internal Information Base operations function that implements
* the remove operation. It is collected here for clarity.
*/
static void
int zn; /* Number of attributes */
*tbl; /* Table we're adding to */
int *xid; /* XID for transaction */
{
int i, rem_ok;
/*
* Access control, if we have remove access granted at the
* table level then we can remove all entries.
*/
if (auth_verbose) {
"remove_entry: removal is %s by the table.",
}
/*
* Check for errors, first to see if we actually found an
* object to remove.
*/
return;
}
/*
* next to see if more than one object may be removed.
*/
return;
}
/*
* Third to see if we need the same object to remove
*/
return;
}
/*
* Fourth check to see if we have the right to remove
* these entrie(s)
*/
if (! rem_ok) {
if (! __can_do(NIS_DESTROY_ACC,
break;
return;
}
}
if (*xid == 0) {
return;
}
/*
* Finally, remove the object in question.
*/
}
/*
* This is the internal Information base function that implements the modify
* operation. It is put here for clarity.
*/
static void
int zn; /* Number of attributes */
*tbl; /* Table we're adding to */
int *xid; /* XID for transaction */
{
int i, mc;
*n_ec; /* New entry columns */
int clone; /* True if new object is a clone */
int mod_ok;
/*
* Set the global modify access bit for the objects by checking
* the table's access rights.
*/
if (auth_verbose) {
"modify_entry: modification is %s by the table.",
}
if (! mec) {
return;
}
/* If we didn't find the original just return the error */
return;
}
/* If the object isn't unique we can't modify it. */
return;
}
return;
}
/*
* Now check the permissions on the object itself.
* If we don't have modify access to the table we check
* to see if we have modify access to the object itself,
* if that fails we check to see if we have modify access
* to the column we are modifying. If that fails we return
* a permission error.
*/
if (! mod_ok)
if (! mod_ok) {
for (i = 0; i < mc; i++)
return;
}
}
/*
* Check the MOD_EXCLUSIVE flag. If a searchable column has been
* modified, the key index will be changed, which may conflict with
* an existing entry.
*/
if (flags & MOD_EXCLUSIVE) {
/* Check for a searchable column that has been modified */
for (i = 0; i < mc; i++) {
break;
}
}
/*
* If key modified, then create new attr list and see if
* it matches an existing entry
*/
if (mod_attrs) {
return;
}
0) {
} else {
}
na++;
}
}
return;
}
}
}
/* Now check to see if the attributes need to change */
/*
* XXX this can fail silently when attributes change
* but mod_ok isn't set, we should figure out how to
* return a permission error. XXX
*/
}
for (i = 0; i < mc; i++) {
else
}
if (*xid == 0) {
return;
}
/*
* If changes are written through to LDAP, we want to make
* just one LDAP update, not one remove and one add, which
* causes problems when more than one NIS+ table maps to
* one and the same LDAP container. Hence, inform the DB
* that this removal is part of a modify operation.
*/
/* Remove the old version of the entry */
name);
/*
* Now do the modify by overwriting the existing
* object.
*/
}
/* Reset modification indication to the DB */
tsd->doingModify = 0;
}
/*
* __nis_ibops()
* This is the common information base ops routine. It implements the
* various operations that can be done on an information base. It is
* analagous to the nameops() routine above.
*/
static nis_result *
int op;
{
int zn;
struct ticks t;
char optxt[32];
if (readonly) {
return (res);
}
if (auth_verbose) {
switch (op) {
case ADD_OP :
break;
case REM_OP :
break;
case MOD_OP :
break;
}
}
/*
* Check to see if we serve the directory this table is in.
* NOTE: this could do a NIS+ lookup to the parent.
*/
&d_obj);
return (res);
}
/*
* Get the last update time. If the current time is earlier
* someone has set the clock back which is very bad as far
* as NIS+ is concerned. This will catch almost all possible
* situations except the single second when time has finally
* caught up with the last entry in the log. Fixing this
* would require more radical changes to the whole update
* mechanism which is potentially risky and may even require
* protocol changes.
*/
"earlier than most recent log entry");
return (res);
}
/*
* POLICY : Should we have to read the directory to
* read the table ?
* ANSWER : No, if we _know_ the name of the table we
* should be able to access it.
*/
/* quick pointer to the "new" object */
else
/* Get the table that we will be manipulating. */
return (res);
}
return (res);
}
return (res);
}
if (! n_obj) {
return (res);
}
}
/*
* For all operations, if there is an object, check that
* it matches the table.
*/
if (n_obj) {
return (res);
}
/* Make sure the number of columns are the same */
return (res);
}
}
/*
* Now we check the attribute list, doing some sanity checking
* and then go to the database.
*/
if (n_obj) /* The Entry columns */
else
} else if (ec) {
/* build a search criteria from the passed entry */
mc);
zn++;
}
}
} else {
zn = 0;
}
xid = 0;
switch (op) {
case ADD_OP :
res);
break;
case REM_OP :
res);
break;
case MOD_OP :
res);
break;
}
if (verbose)
}
} else if (xid != 0)
if (verbose)
return (res);
}
/*
* nis_fnops()
* This is routine is common to the first and next functions below. Quite
* a bit of code is shared so this cuts down on the chance for errors.
* The first part is identical to the ibops function above but this
* routine needs to return different data so I didn't want to overload
* the return value of ibobs() above.
*/
static nis_result *
int op;
{
int all_read = 0;
int zn;
if (! res) {
return (NULL);
}
if (! ib_obj) {
return (res);
}
}
return (res);
}
/*
* If we have read access to everything or the object then we're done.
*/
if (all_read ||
"fnops descript");
return (res);
}
else
do {
else {
}
if (t_obj) {
"fnops obj");
add_cleanup((void (*)())XFREE,
"fnops descript");
return (res);
}
return (res);
}
/*
* nis_addib, this function adds an entry into an Information Base.
* the entry is not "visible" in the namespace, only as a component
* of the information base that contains it.
*/
{
char pname[1024];
__start_clock(0);
if (verbose)
/* A quick and easy check... */
return (nis_make_error(NIS_INVALIDOBJ, 0, 0, 0,
__stop_clock(0)));
}
if (verbose)
return (res);
}
/*
* nis_ibmodify, this function modifys an entry in the information
* base by changing the columns that are marked as modified in the
* passed entry. Permission checking is done here as well.
*/
{
char pname[1024];
__start_clock(0);
if (verbose)
/* A quick and easy check... */
return (nis_make_error(NIS_INVALIDOBJ, 0, 0, 0,
__stop_clock(0)));
}
if (verbose)
return (res);
}
/*
* nis_remove, delete an entry from the indicated information base.
*/
{
char pname[1024];
__start_clock(0);
if (verbose)
/* A quick and easy check... */
return (nis_make_error(NIS_INVALIDOBJ, 0, 0, 0,
__stop_clock(0)));
}
if (verbose)
return (res);
}
/*
* nis_ibfirst()
* This function will return the first entry in an information base.
* It is provided primarily for backward compatibility with YP and its
* use is not encouraged.
*/
{
char pname[1024];
__start_clock(0);
if (verbose)
if (verbose)
return (res);
}
/*
* nis_ibnext, return a subsequent entry in the information base.
*/
{
char pname[1024];
__start_clock(0);
if (verbose)
if (verbose)
return (res);
}