Home | History | Annotate | Download | only in rpc.nisd
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License, Version 1.0 only
      6  * (the "License").  You may not use this file except in compliance
      7  * with the License.
      8  *
      9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10  * or http://www.opensolaris.org/os/licensing.
     11  * See the License for the specific language governing permissions
     12  * and limitations under the License.
     13  *
     14  * When distributing Covered Code, include this CDDL HEADER in each
     15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16  * If applicable, add the following below this CDDL HEADER, with the
     17  * fields enclosed by brackets "[]" replaced with your own identifying
     18  * information: Portions Copyright [yyyy] [name of copyright owner]
     19  *
     20  * CDDL HEADER END
     21  */
     22 /*
     23  * Copyright (c) 1990-2001 by Sun Microsystems, Inc.
     24  * All rights reserved.
     25  */
     26 
     27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     28 
     29 /*
     30  * Ported from :
     31  *	"@(#)nis_ns_proc.c 1.15 91/03/01 Copyr 1990 Sun Micro";
     32  *
     33  *	nis_ns_proc.c
     34  *
     35  * This module contains the actual implementation of NIS version 3.
     36  * NB : It provides the routines that the dispatch function in nis_svc.c
     37  * call. That file, nis_svc.c, is automatically generated and reflects the
     38  * interface definition that is described in the nis.x file. When the
     39  * nis.x file changes, you must make sure that any parameters that change
     40  * get reflected in these routines.
     41  *
     42  * This module contains the Namespace manipulation procedures.
     43  */
     44 
     45 #include <stdio.h>
     46 #include <syslog.h>
     47 #include <stdlib.h>
     48 #include <rpc/rpc.h>
     49 #include <rpc/clnt.h>
     50 #include <rpc/svc.h>
     51 #include <rpc/auth.h>
     52 #include <rpc/auth_des.h>
     53 #include <rpcsvc/nis.h>
     54 #include "nis_proc.h"
     55 
     56 extern bool_t	xdr_nis_result();
     57 
     58 extern nis_object* get_root_object();
     59 extern int update_root_object(nis_name, nis_object *);
     60 extern void add_pingitem_with_name(char *buf, nis_object *dir,
     61 				    u_long ptime, NIS_HASH_TABLE *tbl);
     62 extern nis_name __nis_local_root();
     63 extern char *relative_name();
     64 
     65 /*
     66  * Handle the case in which we have the directory object but not its table.
     67  * It is called when we try to lookup an item and get back a NIS_NOSUCHTABLE.
     68  * On master, table should always exist; missing due to a mkdir failure
     69  *	master: create table & return (item) not found
     70  *	master & readonly: return system error
     71  * On replica: indicates we have not seen directory's update yet.
     72  *	replica: add to pinglist and return NIS_NOT_ME
     73  *	replica & readonly: return NIS_NOT_ME
     74  */
     75 nis_error
     76 recover_from_no_table(nis_object* d_obj, nis_name dirname)
     77 {
     78 	if (we_serve(&(d_obj->DI_data), MASTER_ONLY)) {
     79 		if (readonly)
     80 			return (NIS_TRYAGAIN);
     81 		else {
     82 			/* create table */
     83 			nis_error status;
     84 
     85 			if ((status = db_create(dirname, &tbl_prototype)) !=
     86 			    NIS_SUCCESS) {
     87 				syslog(LOG_ERR,
     88 					"Unable to create table %s: %s.",
     89 					dirname, nis_sperrno(status));
     90 				return (status);
     91 			} else
     92 				return (NIS_SUCCESS);
     93 		}
     94 	} else {
     95 		/* replica */
     96 		if (!readonly) /* force update */
     97 			add_pingitem(d_obj, time(0), &upd_list);
     98 		return (NIS_NOT_ME);
     99 	}
    100 }
    101 
    102 /*
    103  * nis_lookup is the basic function used by clients of the name service.
    104  * This function translates the request into an access of the local
    105  * database.
    106  */
    107 nis_result *
    108 nis_lookup_svc(argp, reqstp)
    109 	ns_request *argp;
    110 	struct svc_req *reqstp;
    111 {
    112 	nis_result	*res;
    113 	nis_db_result	*dbres;
    114 	nis_object	*d_obj = 0;
    115 	char		pname[1024];
    116 	struct ticks	t;
    117 	int		read_dir,	/* read access on directory */
    118 			read_obj;	/* read access on an object */
    119 	nis_error	xx;
    120 	char		*p;
    121 
    122 	if (verbose)
    123 		syslog(LOG_INFO, "LOOKUP_SVC : '%s'", argp->ns_name);
    124 	__start_clock(0);
    125 	res = (nis_result *)XCALLOC(1, sizeof (nis_result));
    126 	add_cleanup((void (*)()) XFREE, (char *)(res), "ns_lookup result");
    127 
    128 	if (reqstp)
    129 		nis_getprincipal(pname, reqstp);
    130 	else
    131 		pname[0] = '\0';
    132 
    133 	if ((p = relative_name(argp->ns_name)) == NULL) {
    134 		int want_root;
    135 		nis_name root_dir = __nis_local_root();
    136 
    137 		want_root = root_dir &&
    138 			(nis_dir_cmp(argp->ns_name, root_dir) == SAME_NAME);
    139 		if (!want_root)
    140 			res->status = NIS_NOT_ME;
    141 		else {
    142 			if (! root_server) {
    143 				/* maybe we have been added */
    144 				xx = __directory_object(argp->ns_name,
    145 							&t, 0, &d_obj);
    146 				res->aticks = t.aticks;
    147 				res->dticks = t.dticks;
    148 				res->zticks = t.zticks;
    149 				res->cticks = t.cticks;
    150 				if (d_obj == NULL)
    151 					res->status = NIS_NOT_ME;
    152 				else {
    153 					/* make copy to be returned */
    154 					nis_object* clone;
    155 
    156 					clone = nis_clone_object(d_obj, 0);
    157 					d_obj = clone;
    158 				}
    159 			} else {
    160 				d_obj = get_root_object();
    161 				if (d_obj == NULL)
    162 					res->status = NIS_NAMEUNREACHABLE;
    163 			}
    164 
    165 			if (d_obj) {
    166 				read_dir = __can_do(NIS_READ_ACC,
    167 						    d_obj->zo_access,
    168 						    d_obj, pname);
    169 				if (! read_dir) {
    170 					res->status = NIS_PERMISSION;
    171 					nis_destroy_object(d_obj);
    172 				} else {
    173 					NIS_RES_OBJECT(res) = d_obj;
    174 					NIS_RES_NUMOBJ(res) = 1;
    175 					res->status = NIS_SUCCESS;
    176 					add_cleanup(nis_destroy_object,
    177 						(char *)(d_obj),
    178 						"ns_lookup objects");
    179 				}
    180 			}
    181 		}
    182 		res->zticks = __stop_clock(0);
    183 		return (res);
    184 	}
    185 	free((void *)p);
    186 
    187 	/*
    188 	 * Make sure we are allowed to READ this directory.
    189 	 *
    190 	 * POLICY: servers _must_ be able to read their own directory
    191 	 * objects.
    192 	 */
    193 	xx = __directory_object(nis_domain_of(argp->ns_name), &t, 0, &d_obj);
    194 	res->aticks = t.aticks;
    195 	res->dticks = t.dticks;
    196 	res->zticks = t.zticks;
    197 	res->cticks = t.cticks;
    198 	if (d_obj == NULL) {
    199 		res->status = xx;  /* status from __directory_object call */
    200 		res->zticks = __stop_clock(0);
    201 		return (res);
    202 	}
    203 
    204 	read_dir = __can_do(NIS_READ_ACC, d_obj->zo_access, d_obj, pname);
    205 
    206 	/* Look it up in the data base */
    207 	dbres = db_lookup(argp->ns_name);
    208 	if (dbres->status != NIS_SUCCESS) {
    209 		if (dbres->status == NIS_NOSUCHTABLE) {
    210 			/* This means table of domain does not exist */
    211 			dbres->status =
    212 			recover_from_no_table(d_obj,
    213 					    nis_domain_of(argp->ns_name));
    214 			/* for sure, argp->ns_name will not be there */
    215 			if (dbres->status == NIS_SUCCESS)
    216 				dbres->status = NIS_NOTFOUND;
    217 		}
    218 		if (read_dir) {
    219 			res->status = dbres->status;
    220 			res->dticks = dbres->ticks;
    221 		} else {
    222 			res->status = NIS_PERMISSION;
    223 			res->dticks = 0;
    224 		}
    225 		res->zticks = __stop_clock(0);
    226 		if (verbose)
    227 			syslog(LOG_INFO, "nis_lookup : exit status %s",
    228 						nis_sperrno(res->status));
    229 		return (res);
    230 	}
    231 
    232 	read_obj = __can_do(NIS_READ_ACC, dbres->obj->zo_access,
    233 						dbres->obj, pname);
    234 
    235 	if (! read_dir && ! read_obj) {
    236 		res->status = NIS_PERMISSION;
    237 		res->zticks = __stop_clock(0);
    238 		if (verbose)
    239 			syslog(LOG_INFO,
    240 				"nis_lookup : exit status PERMISSION DENIED");
    241 		return (res);
    242 	}
    243 
    244 	res->status = NIS_SUCCESS;
    245 	res->objects.objects_len = 1;
    246 	res->objects.objects_val = dbres->obj;
    247 	res->dticks = dbres->ticks;
    248 	res->zticks = __stop_clock(0);
    249 
    250 	if (verbose)
    251 		syslog(LOG_INFO, "nis_lookup : exit status %s",
    252 						nis_sperrno(res->status));
    253 
    254 	return (res);
    255 }
    256 
    257 /*
    258  * Internal function that adds an object into the namespace.
    259  */
    260 static void
    261 add_object(name, obj, dobj, princp, res)
    262 	nis_name	name;
    263 	nis_object	*obj,
    264 			*dobj;
    265 	nis_name	princp;
    266 	nis_result	*res;
    267 {
    268 	directory_obj	*d;
    269 	nis_db_result	*dbres;
    270 	u_long		xid, ttime;
    271 	int		i;
    272 	int		add_ok = 0;
    273 
    274 	/*
    275 	 * d points at the directory specific information
    276 	 */
    277 	d = &(dobj->DI_data);
    278 
    279 	dbres = db_lookup(name);
    280 	res->dticks += dbres->ticks;
    281 	if (dbres->status != NIS_NOTFOUND) {
    282 		if (dbres->status == NIS_SUCCESS)
    283 			res->status = NIS_NAMEEXISTS;
    284 		else
    285 			res->status = dbres->status;
    286 		return;
    287 	}
    288 
    289 	/*
    290 	 * If we are adding a directory, make sure that it is a legitimate
    291 	 * name, i.e. the new directory must be a descendant of the domain
    292 	 * whose server is making this request.  If we are in the root domain,
    293 	 * then we can add a new directory object in the same domain.
    294 	 */
    295 	if (__type_of(obj) == NIS_DIRECTORY_OBJ) {
    296 	    switch (nis_dir_cmp(obj->DI_data.do_name, dobj->DI_data.do_name)) {
    297 		case LOWER_NAME:
    298 		    break;
    299 		case SAME_NAME:
    300 		    if (root_server) break;
    301 		default:
    302 		    res->status = NIS_BADNAME;
    303 		    return;
    304 	    }
    305 	}
    306 
    307 	/*
    308 	 * Verify access rights. Check first to see if the directory object
    309 	 * grants the right. If not, then check to see if the object access
    310 	 * rights for that type of object grant the right. And if that
    311 	 * doesn't then abort with PERMISSION denied.
    312 	 */
    313 	add_ok = __can_do(NIS_CREATE_ACC, dobj->zo_access, dobj, princp);
    314 	if (! add_ok) {
    315 		if (auth_verbose) {
    316 			syslog(LOG_INFO,
    317 			    "add_object : create DENIED by directory %s.%s",
    318 						dobj->zo_name, dobj->zo_domain);
    319 		}
    320 		for (i = 0; i < d->do_armask.do_armask_len; i++) {
    321 			if (obj->zo_data.zo_type == OATYPE(d, i)) {
    322 				add_ok = __can_do(NIS_CREATE_ACC,
    323 						OARIGHTS(d, i), dobj, princp);
    324 				if (auth_verbose) {
    325 					syslog(LOG_INFO,
    326 				    "add_object : create %s by object type.",
    327 					(add_ok) ? "ALLOWED" : "DENIED");
    328 				}
    329 				break;
    330 			}
    331 		}
    332 	} else if (auth_verbose) {
    333 		syslog(LOG_INFO,
    334 			"add_object : create is ALLOWED by the directory.");
    335 	}
    336 
    337 	if (! add_ok) {
    338 		res->status = NIS_PERMISSION;
    339 		return;
    340 	}
    341 
    342 	/*
    343 	 * Start the transaction, if we can't get an XID abort
    344 	 */
    345 	xid = begin_transaction(princp);
    346 	if (! xid) {
    347 		res->status = NIS_TRYAGAIN;
    348 		return;
    349 	}
    350 
    351 	/* Do the add operation */
    352 	obj->zo_oid.ctime = (unsigned long)time(0);
    353 	obj->zo_oid.mtime = obj->zo_oid.ctime;
    354 	dbres = db_add(name, obj, 0);
    355 	res->dticks += dbres->ticks;
    356 	res->status = dbres->status;
    357 	if (res->status != NIS_SUCCESS)
    358 		abort_transaction(xid);
    359 	else {
    360 		ttime = (u_long)(time(0));
    361 		end_transaction(xid); /* COMMIT */
    362 		/* Notify replicates of the change. */
    363 		if (dobj->DI_data.do_servers.do_servers_len > 1)
    364 			add_pingitem(dobj, ttime, &ping_list);
    365 	}
    366 	return;
    367 
    368 }
    369 
    370 /*
    371  * Internal function that removes an object from the namespace.
    372  */
    373 static void
    374 remove_object(name, obj, dobj, princp, res)
    375 	nis_name	name;
    376 	nis_object	*obj,
    377 			*dobj;
    378 	nis_name	princp;
    379 	nis_result	*res;
    380 {
    381 	directory_obj	*d;
    382 	nis_object	*old;
    383 	nis_db_result	*dbres;
    384 	u_long		xid, ttime;
    385 	int		i, rem_ok;
    386 
    387 	/*
    388 	 * d points at the directory specific information
    389 	 */
    390 	d = &(dobj->DI_data);
    391 
    392 	dbres = db_lookup(name);
    393 	res->dticks += dbres->ticks;
    394 	if (dbres->status != NIS_SUCCESS) {
    395 		res->status = dbres->status;
    396 		return;
    397 	}
    398 
    399 	/* This is the existing object in the database */
    400 	old = dbres->obj;
    401 
    402 	if (obj && ! same_oid(obj, old)) {
    403 		res->status = NIS_NOTSAMEOBJ;
    404 		return;
    405 	}
    406 
    407 	/*
    408 	 * Verify delete access rights. Check first to see if the directory
    409 	 * object grants the right. If not, then check to see if the object
    410 	 * itself grants the right, then finally check to see of we have
    411 	 * this access right for this type of object.
    412 	 */
    413 	rem_ok = __can_do(NIS_DESTROY_ACC, dobj->zo_access, dobj, princp);
    414 	if (auth_verbose) {
    415 		syslog(LOG_INFO,
    416 			    "remove_object : destroy %s by directory %s.%s",
    417 					(rem_ok) ? "ALLOWED" : "DENIED",
    418 					dobj->zo_name, dobj->zo_domain);
    419 	}
    420 	if (! rem_ok) {
    421 		rem_ok = __can_do(NIS_DESTROY_ACC, old->zo_access, old, princp);
    422 		if (auth_verbose) {
    423 			syslog(LOG_INFO,
    424 			    "remove_object : destroy %s by object %s.%s",
    425 					(rem_ok) ? "ALLOWED" : "DENIED",
    426 					old->zo_name, old->zo_domain);
    427 		}
    428 	}
    429 
    430 	if (! rem_ok) {
    431 		for (i = 0; i < d->do_armask.do_armask_len; i++) {
    432 			if (old->zo_data.zo_type == OATYPE(d, i)) {
    433 				rem_ok =  __can_do(NIS_DESTROY_ACC,
    434 						OARIGHTS(d, i), dobj, princp);
    435 				break;
    436 			}
    437 		}
    438 	}
    439 	if (! rem_ok) {
    440 		res->status = NIS_PERMISSION;
    441 		if (auth_verbose) {
    442 			syslog(LOG_INFO,
    443 			    "remove_object : destroy DENIED by object type.");
    444 		}
    445 		return;
    446 	} else if (auth_verbose) {
    447 			syslog(LOG_INFO,
    448 			    "remove_object : destroy ALLOWED by object type.");
    449 		}
    450 
    451 	/*
    452 	 * Start the transaction, if we can't get an XID abort
    453 	 */
    454 	xid = begin_transaction(princp);
    455 	if (! xid) {
    456 		res->status = NIS_TRYAGAIN;
    457 		return;
    458 	}
    459 
    460 	/* Do the remove operation */
    461 	dbres = db_remove(name, old, time(0));
    462 	res->dticks += dbres->ticks;
    463 	res->status = dbres->status;
    464 	if (res->status != NIS_SUCCESS)
    465 		abort_transaction(xid);
    466 	else {
    467 		ttime = (u_long)(time(0));
    468 		end_transaction(xid); /* COMMIT */
    469 		/* Notify replicates of the change. */
    470 		if (dobj->DI_data.do_servers.do_servers_len > 1)
    471 			add_pingitem(dobj, ttime, &ping_list);
    472 		/*
    473 		 * No need to flush the caches - they have been flushed
    474 		 * inside db_remove
    475 		 */
    476 	}
    477 }
    478 
    479 /* macro to get to the table column structures */
    480 #define	TABLE_COL(o, n) o->TA_data.ta_cols.ta_cols_val[n]
    481 
    482 /*
    483  * Internal function that modifies an object in the namespace.
    484  */
    485 static void
    486 modify_object(name, obj, dobj, princp, res)
    487 	nis_name	name;
    488 	nis_object	*obj,
    489 			*dobj;
    490 	nis_name	princp;
    491 	nis_result	*res;
    492 {
    493 	directory_obj	*d;
    494 	nis_object	*old;
    495 	nis_db_result	*dbres;
    496 	u_long		xid, ttime;
    497 	int		i, mod_ok;
    498 	log_entry	le;
    499 	nis_object	mod_obj;
    500 
    501 	/*
    502 	 * d points at the directory specific information
    503 	 */
    504 	d = &(dobj->DI_data);
    505 
    506 	dbres = db_lookup(name);
    507 	res->dticks += dbres->ticks;
    508 	if (dbres->status != NIS_SUCCESS) {
    509 		res->status = dbres->status;
    510 		return;
    511 	}
    512 
    513 	/* This is the existing object in the database */
    514 	old = dbres->obj;
    515 
    516 	/*
    517 	 * Verify modify access rights. Check first to see if the directory
    518 	 * object grants the right. If not, then check to see if the object
    519 	 * itself grants the right, then finally check to see if we have
    520 	 * this access right for this type of object.
    521 	 */
    522 	mod_ok = __can_do(NIS_MODIFY_ACC, dobj->zo_access, dobj, princp);
    523 	if (auth_verbose) {
    524 		syslog(LOG_INFO,
    525 			    "modify_object : modify %s by directory %s.%s",
    526 					(mod_ok) ? "ALLOWED" : "DENIED",
    527 					dobj->zo_name, dobj->zo_domain);
    528 	}
    529 	if (! mod_ok) {
    530 		mod_ok = __can_do(NIS_MODIFY_ACC, old->zo_access, old, princp);
    531 		if (auth_verbose) {
    532 			syslog(LOG_INFO,
    533 			    "remove_object : modify %s by object %s.%s",
    534 					(mod_ok) ? "ALLOWED" : "DENIED",
    535 					old->zo_name, old->zo_domain);
    536 		}
    537 	}
    538 
    539 	if (! mod_ok) {
    540 		for (i = 0; i < d->do_armask.do_armask_len; i++) {
    541 			if (old->zo_data.zo_type == OATYPE(d, i)) {
    542 				if (! __can_do(NIS_MODIFY_ACC, OARIGHTS(d, i),
    543 							dobj, princp)) {
    544 					res->status = NIS_PERMISSION;
    545 					if (auth_verbose) {
    546 						syslog(LOG_INFO,
    547 			    "modify_object : modify DENIED by object type.");
    548 					}
    549 					return;
    550 				} else {
    551 					if (auth_verbose) {
    552 						syslog(LOG_INFO,
    553 			    "modify_object : modify ALLOWED by object type.");
    554 					}
    555 					mod_ok = 1;
    556 					break;
    557 				}
    558 			}
    559 		}
    560 	}
    561 	/*
    562 	 * if no one allows modify then fail.
    563 	 */
    564 	if (! mod_ok) {
    565 		res->status = NIS_PERMISSION;
    566 		return;
    567 	}
    568 
    569 	/*
    570 	 * POLICY : Allow changing type ?
    571 	 *
    572 	 * ANSWER : No, only changing the meta data
    573 	 * such as access etc is allowed.
    574 	 */
    575 	if (__type_of(old) != __type_of(obj)) {
    576 		res->status = NIS_BADOBJECT;
    577 		return;
    578 	}
    579 
    580 	if (nis_dir_cmp(old->zo_domain, obj->zo_domain) != SAME_NAME) {
    581 		res->status = NIS_BADNAME;
    582 		return;
    583 	}
    584 
    585 	/*
    586 	 * Start the transaction, if we can't get an XID abort
    587 	 */
    588 	xid = begin_transaction(princp);
    589 	if (! xid) {
    590 		res->status = NIS_TRYAGAIN;
    591 		return;
    592 	}
    593 
    594 	/*
    595 	 * Now we generate a log entry with the old value so that if
    596 	 * we crash, the transaction system can restore the object
    597 	 * to its pre-modified state.
    598 	 */
    599 	memset((char *)&le, 0, sizeof (le));
    600 	le.le_type = MOD_NAME_OLD;
    601 	le.le_time = (u_long) (time(0));
    602 	le.le_name = name;
    603 	le.le_attrs.le_attrs_len = 0;
    604 	le.le_object = *old;
    605 	add_update(&le);
    606 
    607 	mod_obj = *old;
    608 	if (same_oid(old, obj)) {
    609 		mod_obj.zo_owner  = obj->zo_owner;
    610 		mod_obj.zo_group  = obj->zo_group;
    611 		mod_obj.zo_access = obj->zo_access;
    612 		mod_obj.zo_ttl    = obj->zo_ttl;
    613 	} else if (obj->zo_oid.mtime || obj->zo_oid.ctime) {
    614 		res->status = NIS_NOTSAMEOBJ;
    615 		abort_transaction(xid);
    616 		return;
    617 	}
    618 
    619 	/*
    620 	 * Data replacement of the variant part.
    621 	 * NOTE: We play games with TABLE objects because we can't
    622 	 *	 allow their schema to change on the fly. But there are
    623 	 *	 fields in the table data that _can_ change such as the
    624 	 *	 path of the table, separator character, and access rights.
    625 	 *
    626 	 * As in the case of ENTRY objects, we let the 'same oid' test be
    627 	 * the signal that it is ok to overwrite these variables. This
    628 	 * is overloading the OID value but I can't think of any other
    629 	 * way to signal this operation without changing the protocol at
    630 	 * this point.
    631 	 */
    632 	if (__type_of(old) != NIS_TABLE_OBJ)
    633 		mod_obj.zo_data = obj->zo_data;
    634 	else if (same_oid(old, obj)) {
    635 		mod_obj.TA_data.ta_path = obj->TA_data.ta_path;
    636 		mod_obj.TA_data.ta_sep = obj->TA_data.ta_sep;
    637 		mod_obj.TA_data.ta_type = obj->TA_data.ta_type;
    638 		for (i = 0; i < mod_obj.TA_data.ta_maxcol; i++) {
    639 			if (TABLE_COL(obj, i).tc_flags & TA_MODIFIED)
    640 				TABLE_COL((&mod_obj), i).tc_rights =
    641 					TABLE_COL(obj, i).tc_rights;
    642 		}
    643 	}
    644 
    645 	mod_obj.zo_oid.mtime = (unsigned long)time(0);
    646 	/*
    647 	 * Now we add the object over the previous
    648 	 * one. This instructs the database to
    649 	 * discard the current one and replace it
    650 	 * with our modified one. (And it's atomic)
    651 	 */
    652 	dbres = db_add(name, &mod_obj, 1);
    653 	res->dticks += dbres->ticks;
    654 	res->status = dbres->status;
    655 	if (res->status != NIS_SUCCESS)
    656 		abort_transaction(xid);
    657 	else {
    658 		nis_taglist	taglist;
    659 		nis_tag		tags;
    660 
    661 		ttime = (u_long)(time(0));
    662 		end_transaction(xid); /* COMMIT */
    663 
    664 		/* Notify replicates of the change. */
    665 		if (dobj->DI_data.do_servers.do_servers_len > 1)
    666 			add_pingitem(dobj, ttime, &ping_list);
    667 
    668 		/*
    669 		 * No need to flush the caches - they have been flushed
    670 		 * inside db_remove
    671 		 */
    672 		if (__type_of(old) == NIS_DIRECTORY_OBJ) {
    673 			/*
    674 			 * Send all the servers that serve this particular
    675 			 * directory object a flush_cache message.
    676 			 * We dont do this for group and table caches because
    677 			 * they are served by those servers; and we can
    678 			 * just hope that they get the updates real fast.
    679 			 * For Directory objects, since we dont want the servers
    680 			 * to throw out the cached directory objects and then go
    681 			 * and refresh it with the stale copies, we will use
    682 			 * TAG_DCACHE_ONE_REFRESH which will
    683 			 * get a fresh copy of the object from the master.
    684 			 *
    685 			 * XXX: Since we are sending this message to all
    686 			 * servers (including MASTER), this will lead to
    687 			 * multiple calls to the master.  One could have
    688 			 * perhaps passed a modified directory object
    689 			 * without the master.
    690 			 */
    691 			tags.tag_type = TAG_DCACHE_ONE_REFRESH;
    692 			tags.tag_val = old->DI_data.do_name;
    693 			taglist.tags.tags_len = 1;
    694 			taglist.tags.tags_val = &tags;
    695 			(void) nis_mcast_tags(&old->DI_data, &taglist);
    696 		}
    697 	}
    698 }
    699 
    700 /*
    701  * If prim and sec contain the same servers, return 0.
    702  * If sec contains different servers than prim, result is set to:
    703  *	<all prim servers> <sec servers that are not in prim's list>
    704  */
    705 static int
    706 merge_srv_list(directory_obj* prim, directory_obj* sec, nis_server**result)
    707 {
    708 	nis_server* answer = 0;
    709 	int psize = prim->do_servers.do_servers_len;
    710 	int ssize = sec->do_servers.do_servers_len;
    711 	nis_server* prim_server = prim->do_servers.do_servers_val;
    712 	nis_server* sec_server = sec->do_servers.do_servers_val;
    713 	int i, j, diff = 0, newsize = 0, newadd;
    714 
    715 	/* First count how many names in sec are different */
    716 	for (i = 0; i < ssize; i++) {
    717 		for (j = 0; j < psize; j++) {
    718 			if (nis_dir_cmp(prim_server[j].name,
    719 					sec_server[i].name) == SAME_NAME)
    720 				break;
    721 		}
    722 		if (j == psize)
    723 			++diff;
    724 	}
    725 
    726 	if (diff == 0)
    727 		return (0);
    728 
    729 	/* different names were found on sec_server list */
    730 	newsize = psize+diff;
    731 	answer = (nis_server*)malloc(newsize*sizeof (nis_server));
    732 
    733 	if (answer == 0)
    734 		return (0);
    735 
    736 	/* Copy prim first */
    737 	for (i = 0; i < psize; i++)
    738 		answer[i] = prim_server[i];
    739 
    740 	newadd = i;
    741 	for (i = 0; i < ssize; i++) {
    742 		for (j = 0; j < psize; j++) {
    743 			if (nis_dir_cmp(prim_server[j].name,
    744 					sec_server[i].name) == SAME_NAME)
    745 				break;
    746 		}
    747 		if (j == psize)
    748 			answer[newadd++] = sec_server[i];
    749 	}
    750 
    751 	*result = answer;
    752 	return (newsize);
    753 }
    754 
    755 static void
    756 modify_root_object(nis_name	name,
    757 		nis_object	*obj,
    758 		nis_name	princp,
    759 		nis_result	*res)
    760 {
    761 	nis_object* oldroot = get_root_object();
    762 	nis_object newroot;
    763 	u_long ttime;
    764 
    765 	if (! oldroot) {
    766 		syslog(LOG_ERR, "Cannot read %s!", ROOT_OBJ);
    767 		res->status = NIS_SYSTEMERROR;
    768 		return;
    769 	}
    770 
    771 	if (__can_do(NIS_MODIFY_ACC, oldroot->zo_access, oldroot, princp)) {
    772 		newroot = *oldroot;    /* copy all fields from old object */
    773 
    774 		if (same_oid(oldroot, obj)) {
    775 			/* updating non-data portion as well. */
    776 			newroot.zo_owner  = obj->zo_owner;
    777 			newroot.zo_group  = obj->zo_group;
    778 			newroot.zo_access = obj->zo_access;
    779 			newroot.zo_ttl    = obj->zo_ttl;
    780 		} else if (obj->zo_oid.mtime || obj->zo_oid.ctime) {
    781 			/* somehow got an outdated copy of object. */
    782 			res->status = NIS_NOTSAMEOBJ;
    783 			nis_destroy_object(oldroot);
    784 			return;
    785 		} /* else, only interested in changing data portion. */
    786 
    787 		newroot.DI_data = obj->DI_data;  /* always update data */
    788 		ttime = (u_long)(time(0));
    789 		newroot.zo_oid.mtime = ttime;
    790 		if (update_root_object(name, &newroot)) {
    791 			nis_server* merged_srvs = 0;
    792 			int howmany;
    793 
    794 			howmany = merge_srv_list(&(newroot.DI_data),
    795 						    &(oldroot->DI_data),
    796 						    &merged_srvs);
    797 			/* howmany is 0 if new and old list are the same */
    798 			if (howmany > 0) {
    799 				directory_obj *dobj = &(newroot.DI_data);
    800 				dobj->do_servers.do_servers_val = merged_srvs;
    801 				dobj->do_servers.do_servers_len = howmany;
    802 			}
    803 			if (newroot.DI_data.do_servers.do_servers_len > 1)
    804 				add_pingitem_with_name(ROOT_OBJ,
    805 							&newroot,
    806 							ttime,
    807 							&ping_list);
    808 			if (merged_srvs)
    809 				free(merged_srvs);
    810 			res->status = NIS_SUCCESS;
    811 		} else
    812 			res->status = NIS_MODFAIL;
    813 	} else
    814 		res->status = NIS_PERMISSION;
    815 
    816 	/* newroot just copies contents of other objects; need not be freed */
    817 	nis_destroy_object(oldroot);
    818 }
    819 
    820 /*
    821  * __nis_nameops()
    822  *
    823  * This code actually implements the operation that is requested.
    824  * It is collected here in one place.
    825  */
    826 static nis_result *
    827 nis_nameops(op, name, obj, princp)
    828 	int		op;	/* operation to perform		*/
    829 	nis_name	name;	/* Object's name		*/
    830 	nis_object	*obj;	/* Object to use (NULL for rem)	*/
    831 	nis_name	princp;	/* Principal making the request */
    832 {
    833 	nis_result		*res;
    834 	nis_object		*d_obj;
    835 	struct ticks		t;
    836 	nis_error		xx;
    837 	char			optxt[32];
    838 
    839 	res = (nis_result *)XCALLOC(1, sizeof (nis_result));
    840 	add_cleanup((void (*)())XFREE, (char *)res, "nameops result");
    841 	if (readonly) {
    842 		res->status = NIS_TRYAGAIN;
    843 		return (res);
    844 	}
    845 
    846 	if (auth_verbose) {
    847 		switch (op) {
    848 			case ADD_OP :
    849 				strcpy(optxt, "ADD");
    850 				break;
    851 			case REM_OP :
    852 				strcpy(optxt, "REMOVE");
    853 				break;
    854 			case MOD_OP :
    855 				strcpy(optxt, "MODIFY");
    856 				break;
    857 		}
    858 		syslog(LOG_INFO, "Object operation '%s' for principal %s",
    859 				optxt, princp);
    860 	}
    861 
    862 	if (((op == ADD_OP) || (op == MOD_OP)) && (obj == NULL)) {
    863 		res->status = NIS_BADOBJECT;
    864 		return (res);
    865 	}
    866 
    867 	/*
    868 	 * don't allow an object to be added without an owner.
    869 	 */
    870 	if ((op == ADD_OP) && (strlen(obj->zo_owner) < 2)) {
    871 		res->status = NIS_BADOBJECT;
    872 		return (res);
    873 	}
    874 
    875 	/*
    876 	 * We have to handle the root object specially if we want to
    877 	 * manipulate it using the NIS code.
    878 	 */
    879 	if (root_server &&
    880 	    (nis_dir_cmp(name, __nis_rpc_domain()) == SAME_NAME)) {
    881 
    882 		/* All we allow is a modify */
    883 		if (op != MOD_OP) {
    884 			res->status = NIS_BADREQUEST;
    885 			return (res);
    886 		}
    887 		modify_root_object(name, obj, princp, res);
    888 		return (res);
    889 	}
    890 
    891 	/*
    892 	 * Fetch the directory object for this domain.
    893 	 */
    894 	xx = __directory_object(nis_domain_of(name), &t, TRUE, &d_obj);
    895 	res->aticks = t.aticks;
    896 	res->dticks = t.dticks;
    897 	res->zticks = t.zticks;
    898 	res->cticks = t.cticks;
    899 	if ((xx != NIS_SUCCESS) && (xx != NIS_S_SUCCESS)) {
    900 		res->status = xx;
    901 		return (res);
    902 	}
    903 
    904 	/*
    905 	 * Get the last update time. If the current time is earlier
    906 	 * someone has set the clock back which is very bad as far
    907 	 * as NIS+ is concerned. This will catch almost all possible
    908 	 * situations except the single second when time has finally
    909 	 * caught up with the last entry in the log. Fixing this
    910 	 * would require more radical changes to the whole update
    911 	 * mechanism which is potentially risky and may even require
    912 	 * protocol changes.
    913 	 */
    914 	if (last_update(d_obj->DI_data.do_name) > time(0)) {
    915 		syslog(LOG_ERR, "Update rejected because system time is "
    916 		    "earlier than most recent log entry");
    917 		res->status = NIS_SYSTEMERROR;
    918 		return (res);
    919 	}
    920 
    921 	switch (op) {
    922 	case ADD_OP :
    923 		add_object(name, obj, d_obj, princp, res);
    924 		break;
    925 	case REM_OP :
    926 		remove_object(name, obj, d_obj, princp, res);
    927 		break;
    928 	case MOD_OP :
    929 		modify_object(name, obj, d_obj, princp, res);
    930 		break;
    931 	}
    932 
    933 	return (res);
    934 }
    935 
    936 /*
    937  * nis_add, this function will add a name to the name space if the
    938  * permissions are allowed. The object that is about to be added to
    939  * must allow "CREATE" access to the principal making the request.
    940  */
    941 nis_result *
    942 nis_add_svc(argp, reqstp)
    943 	ns_request *argp;
    944 	struct svc_req *reqstp;
    945 {
    946 	nis_result 		*res;
    947 	char			pname[1024];
    948 	char			*p;
    949 
    950 	__start_clock(0);	/* Timing information */
    951 	if (verbose)
    952 		syslog(LOG_INFO, "ADD_SVC : '%s'", argp->ns_name);
    953 	if ((p = relative_name(argp->ns_name)) == NULL)
    954 		return (nis_make_error(NIS_BADNAME, 0, 0, 0, __stop_clock(0)));
    955 	free((void *)p);
    956 
    957 	nis_getprincipal(pname, reqstp);
    958 	res = nis_nameops(ADD_OP, argp->ns_name,
    959 					argp->ns_object.ns_object_val, pname);
    960 	if (verbose)
    961 		syslog(LOG_INFO, "nis_add : exit status %s",
    962 			nis_sperrno(res->status));
    963 
    964 	res->zticks += __stop_clock(0);
    965 	return (res);
    966 }
    967 
    968 /*
    969  * nis_modify, this function modifies the contents of the object that
    970  * are named. It checks the MODIFY access right in the existing object
    971  * and then does the operation.
    972  */
    973 nis_result *
    974 nis_modify_svc(argp, reqstp)
    975 	ns_request *argp;
    976 	struct svc_req *reqstp;
    977 {
    978 	nis_result 		*res;
    979 	char			pname[1024];
    980 	char			*p = NULL;
    981 
    982 	__start_clock(0);
    983 	if (verbose)
    984 		syslog(LOG_INFO, "MODIFY_SVC : '%s'", argp->ns_name);
    985 	if (! root_server && ((p = relative_name(argp->ns_name)) == NULL))
    986 		return (nis_make_error(NIS_BADNAME, 0, 0, 0, __stop_clock(0)));
    987 	if (p)
    988 		free((void *)p);
    989 
    990 	nis_getprincipal(pname, reqstp);
    991 	res = nis_nameops(MOD_OP, argp->ns_name,
    992 					argp->ns_object.ns_object_val, pname);
    993 	if (verbose)
    994 		syslog(LOG_INFO, "nis_modify : exit status %s",
    995 			nis_sperrno(res->status));
    996 	res->zticks += __stop_clock(0);
    997 	return (res);
    998 }
    999 
   1000 /*
   1001  * nis_remove removes and object from the name space. You will notice it
   1002  * shares a lot of code with the Add and remove functions.
   1003  */
   1004 nis_result *
   1005 nis_remove_svc(argp, reqstp)
   1006 	ns_request *argp;
   1007 	struct svc_req *reqstp;
   1008 {
   1009 	nis_result		*res;
   1010 	char			*principal, pname[1024];
   1011 	char			*p;
   1012 
   1013 	__start_clock(0);
   1014 	if (verbose)
   1015 		syslog(LOG_INFO, "REMOVE_SVC : '%s'", argp->ns_name);
   1016 	if ((p = relative_name(argp->ns_name)) == NULL)
   1017 		return (nis_make_error(NIS_BADNAME, 0, 0, 0, __stop_clock(0)));
   1018 	free((void *)p);
   1019 
   1020 	/*
   1021 	 * If 'reqstp' is NULL, we're being called internally, and supply
   1022 	 * the local principal.
   1023 	 */
   1024 	if (reqstp != 0) {
   1025 		nis_getprincipal(pname, reqstp);
   1026 		principal = pname;
   1027 	} else {
   1028 		principal = nis_local_principal();
   1029 	}
   1030 	res = nis_nameops(REM_OP, argp->ns_name,
   1031 					argp->ns_object.ns_object_val,
   1032 					principal);
   1033 	if (verbose)
   1034 		syslog(LOG_INFO, "nis_remove : exit status %s",
   1035 			nis_sperrno(res->status));
   1036 	res->zticks += __stop_clock(0);
   1037 	return (res);
   1038 }
   1039