cdesktopenv/cde/lib/tt/bin/ttdbserverd/dm_server.C

2329 lines
62 KiB
C

/*
* CDE - Common Desktop Environment
*
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
*
* These libraries and programs are free software; you can
* redistribute them and/or modify them under the terms of the GNU
* Lesser General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* These libraries and programs are distributed in the hope that
* they will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with these librararies and programs; if not, write
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
//%% (c) Copyright 1993, 1994 Hewlett-Packard Company
//%% (c) Copyright 1993, 1994 International Business Machines Corp.
//%% (c) Copyright 1993, 1994 Sun Microsystems, Inc.
//%% (c) Copyright 1993, 1994 Novell, Inc.
//%% $TOG: dm_server.C /main/4 1999/10/08 12:01:17 mgreess $
/*
* Tool Talk Database Server - dm_server.C
*
* Copyright (c) 1989 Sun Microsystems, Inc.
*
*
* This file contains the db server wrappers for the following NetISAM
* functions:
*
* isaddindex, isbuild, isclose, iscntl, isdelrec, iserase, isopen, isread,
* isrewrec, isstart, iswrite.
*
* Each wrapper is an rpc procedure that calls the actual NetISAM function
* that it wraps on behalf of the client.
*
* The file also contains functions for implementing access control and
* cacheing isopens.
*/
#include "tt_options.h"
#include "dm_access_cache.h"
#include "tt_db_server_consts.h"
#include "tt_db_msg_q_lock.h"
#include "tt_isam_file_utils.h"
#include "tt_isam_record_utils.h"
#include "tt_db_server_db.h"
#include "db_server_globals.h"
#include "db/tt_db_key.h"
#include "db/db_server.h"
#include "db/tt_db_key.h"
#include "db/tt_db_key_utils.h"
#include "util/tt_string.h"
#include "util/tt_path.h"
#include "util/tt_log.h"
#include "util/tt_port.h"
#include "util/tt_gettext.h"
#include "util/tt_file_system.h"
#include "util/tt_file_system_entry.h"
#include "dm/dm_enums.h"
#include "dm/dm_recfmts.h"
#include <errno.h>
#include <memory.h>
#include <string.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <isam.h>
#if defined(ultrix)
# include <sys/time.h>
# include <sys/mount.h>
# include <sys/fs_types.h>
# define KERNEL
# include </usr/sys/h/fs_types.h>
# undef KERNEL
# define MNTTAB "/etc/fstab"
const char *TYPE_NFS = "nfs";
const char *TYPE_LOCAL = "ufs";
extern "C"
{
int getmnt(int *,struct fs_data*, int, int, char*);
time_t time();
}
#else
# include <time.h>
#endif
extern "C" { int isaddindex(int, struct keydesc*); }
extern "C" { int isbuild(const char*, int, struct keydesc*, int); }
extern "C" { int isclose(int); }
extern "C" { int iscntl(int, int, ...); }
extern "C" { int isdelcurr(int); }
extern "C" { int isdelrec(int, long); }
extern "C" { int iserase(const char*); }
extern "C" { int isfsync(int); }
extern "C" { int isopen(const char*, int); }
extern "C" { int isread(int, char*, int); }
extern "C" { int isrename(char*, char*); }
extern "C" { int isrewcurr(int, char*); }
extern "C" { int isrewrec(int, long, char*); }
extern "C" { int isstart(int, struct keydesc*, int, char*, int); }
extern "C" { int iswrite(int, char*); }
extern "C" { int isunlock(int); }
extern int _tt_auth_level;
extern int _tt_gidlen;
extern gid_t _tt_gid, _tt_gidlist[NGROUPS];
extern uid_t _tt_uid;
extern keydesc _tt_oid_keydesc;
extern _Tt_oid_access_queue_ptr _tt_oa_cache;
extern _Tt_link_access_queue_ptr _tt_la_cache;
extern char _tt_log_file[];
extern _Tt_db_info _tt_db_table[_TT_MAX_ISFD]; /* table of NetISAM db opened */
extern int _tt_refclock;
extern time_t _tt_mtab_last_mtime;
extern int access_checking;
extern void _tt_process_transaction();
extern FILE *errstr;
extern char *_tt_get_realpath(char *, char *);
int find_endstring(const char *string, const char *end_string);
bool_t msg_q_lock(int isfd,
const char *record,
int length,
SVCXPRT *transp);
_Tt_string map_old_db_to_new_db(const char *old_db);
_Tt_string get_new_db(_Tt_string old_db,
int index,
_Tt_string new_file);
// HACK??? Why do some of these have slashes and some not?
const char *_TT_DOCOID_PATH = "/docoid_path";
const char *_TT_LINK_ACCESS = "/link_access";
const char *_TT_LINK_PROP = "link_prop";
const char *_TT_LINK_ENDS = "link_ends";
const char *_TT_OID_ACCESS = "/oid_access";
const char *_TT_OID_CONTAINER = "/oid_container";
const char *_TT_OID_PROP = "oid_prop";
const char *_TT_LOG_FILE = "/log_file";
const int _TT_ROOT_UID = 0;
static _Tt_isam_results res;
static char _tt_record[ISMAXRECLEN];
enum _Tt_dm_access_category { DM_USER, DM_GROUP, DM_OTHER};
#define DM_TEST_CRASH 0
// XXX We should derive a Tt_path_string class from Tt_string that
// has path manipulation member functions built in.
void
set_real_path (char *path, char *real_path)
{ if (path[0] != '/') {
// a relative path, make it absolute
char wd[MAXPATHLEN+1];
if (getcwd(wd,sizeof(wd)) != 0) {
path = _Tt_string(wd).cat("/").cat(path);
}
}
_Tt_string right, left, dir, base;
left = path;
char *p = _tt_get_realpath((char *) path, real_path);
while ((p == 0) && (left.len() > 0)) {
// realpath failed; drop right components until it
// works.
base = left.rsplit('/', dir);
// Takes care of special case of "/file" where
// file does not exist.
if ((dir.len() <= 0) && (left[0]=='/')) {
dir = "/";
}
left = dir;
if (right.len() > 0) {
right = base.cat("/").cat(right);
} else {
right = base;
}
p = _tt_get_realpath((char *)left, real_path);
}
if (p == 0) {
// Give up, use original path.
strcpy(real_path, (char *)path);
} else if (right.len() > 0) {
strcat(real_path, "/");
strcat(real_path, (char *)right);
}
}
/*
* _tt_get_record - read a record from the database with the given directory
* prefix and db_name. The input record 'rec' contains the key for searching.
* The result of the read is returned in _tt_record. If succeeds returns 1.
* If fails returns 0.
*/
int
_tt_get_record(int prefix_len, const char *prefix, const char *db_name, char *rec)
{
static const char *here = "_tt_get_record()";
if (!rec) { /* rec must contain key, even when reading by recnum */
return 0;
}
char *db = (char *)malloc(prefix_len + strlen(db_name) + 2);
memcpy(db, prefix, prefix_len+1);
strcpy(db + prefix_len +1, db_name);
/*
* HACK: Somehow, isopening non-existent ISAM data bases seems
* to burn isfds. I can't see in the NetISAM code where this happens,
* but it sure is happening. Bypass this by statting the .rec
* file before trying to do isopen.
*/
char *dblong = (char *)malloc(strlen(db)+1+4);
int isfd;
struct stat statbuf;
strcpy(dblong,db);
strcat(dblong,".rec");
isfd = stat(dblong,&statbuf);
if (-1==isfd) {
_tt_syslog(errstr, LOG_ERR, "%s: %m", dblong);
free(dblong);
return 0;
}
free(dblong);
isfd = cached_isopen(db, ISINOUT+ISFIXLEN+ISMANULOCK);
free(db);
if (isfd == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isopen(): %d", here, iserrno);
return 0;
}
if (isstart(isfd, &_tt_oid_keydesc, OID_KEY_LENGTH, rec, ISEQUAL) == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isstart(): %d", here, iserrno);
cached_isclose(isfd);
return 0;
}
memcpy(_tt_record, rec, OID_KEY_LENGTH);
if (isread(isfd, _tt_record, ISNEXT) == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isread(): %d", here, iserrno);
cached_isclose(isfd);
return 0;
}
if (cached_isclose(isfd) == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isclose(): %d", here, iserrno);
return 0;
}
return 1;
}
/*
* _tt_write_record - write a record to the database with the given directory
* prefix and db_name. The input record 'rec' contains record for writing.
* If succeeds returns 1. If fails returns 0.
*/
int
_tt_write_record(int prefix_len, const char *prefix, const char *db_name,
char *rec, int reclen)
{
static const char *here = "_tt_write_record()";
if (!rec) {
return 0;
}
char *db = (char *)malloc(prefix_len + strlen(db_name) + 2);
memcpy(db, prefix, prefix_len+1);
strcpy(db + prefix_len +1, db_name);
/*
* HACK: Somehow, isopening non-existent ISAM data bases seems
* to burn isfds. I can't see in the NetISAM code where this happens,
* but it sure is happening. Bypass this by statting the .rec
* file before trying to do isopen.
*/
char *dblong = (char *)malloc(strlen(db)+1+4);
int isfd;
struct stat statbuf;
strcpy(dblong,db);
strcat(dblong,".rec");
isfd = stat(dblong,&statbuf);
if (-1==isfd) {
_tt_syslog(errstr, LOG_ERR, "%s: %m", dblong);
free(dblong);
return 0;
}
free(dblong);
isfd = cached_isopen(db, ISINOUT+ISFIXLEN+ISMANULOCK);
free(db);
if (isfd == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isopen(): %d", here, iserrno);
return 0;
}
memcpy(_tt_record, rec, OID_KEY_LENGTH);
isreclen = reclen;
if (iswrite(isfd, rec) == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: iswrite(): %d", here, iserrno);
cached_isclose(isfd);
return 0;
}
if (cached_isclose(isfd) == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isclose(): %d", here, iserrno);
return 0;
}
return 1;
}
/*
* _tt_get_oid_access - get the input oid's owner, group, and mode
*/
int
_tt_get_oid_access(char *key, int prefix_len, char *db_path,
uid_t &uid, gid_t &group, mode_t &mode)
{
/* lookup in LRU cache */
_Tt_oid_access_ptr oa = _tt_oa_cache->lookup(key);
if (oa.is_null()) {
/* if not in cache, get from OID-ACCESS database */
if (_tt_get_record(prefix_len, db_path, _TT_OID_ACCESS, key)) {
oa = new _Tt_oid_access(_tt_record);
if (!oa.is_null()) {
_tt_oa_cache->enqueue(oa); /* put in LRU cache */
}
} else {
// no oid record in access table
return 1;
}
} else {
_tt_oa_cache->promote(oa); /* update the LRU cache */
}
uid = oa->user();
group = oa->group();
mode = oa->mode();
return 0;
}
/*
* _tt_get_link_permissions - get the link's permissions, user, and groups.
* Returns 0 if the link has no access info, 1 if succeeds.
*/
int
_tt_get_link_permissions(char *key, int prefix_len, char *db_path,
uid_t &uid, gid_t &group, mode_t &mode)
{
/* lookup in LRU cache */
_Tt_link_access_ptr la = _tt_la_cache->lookup(key);
if (la.is_null()) {
/* if not in cache, get from LINK-ACCESS database */
if (_tt_get_record(prefix_len, db_path,
_TT_LINK_ACCESS, key)) {
la = new _Tt_link_access(_tt_record);
if (!la.is_null()) {
_tt_la_cache->enqueue(la); /* put in LRU cache */
}
} else {
return 1;
}
} else {
_tt_la_cache->promote(la); /* update the LRU cache */
}
uid = la->user();
group = la->group();
mode = la->mode();
return 0;
}
/*
* Check for stale NetISAM file descriptor. Returns 1 if client's uid matches
* database opener's uid. Otherwise returns 0.
*/
int
_tt_check_stale_isfd(int isfd)
{
if (_tt_uid != _tt_db_table[isfd].opener_uid) {
return 0;
}
return 1;
}
/*
* _tt_oid_accessp - this function determines whether the client with the
* extracted uid and gids have read or write permissions as specified by the
* input mode to the OID-PROP or LINK-PROP records. If no access property
* is found, an error has occurred and no permission is given.
* If the client has the access permission, returns 1. Otherwise, returns 0.
*/
int
_tt_oid_accessp(int _isfd, char *rec, int mode)
{
static const char *here = "_tt_oid_accessp()";
int prefix_len;
mode_t request_mask = 0;
_Tt_dm_access_category category = DM_OTHER;
uid_t oid_uid, file_uid;
gid_t oid_group, file_group;
mode_t oid_mode, file_mode;
if (! access_checking) {
// access checking turned off
return 1;
}
if (_tt_uid == _TT_ROOT_UID) {
// The superuser always has access
return 1;
}
if (!_tt_check_stale_isfd(_isfd)) {
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
return 0;
}
char *db_path = _tt_db_table[_isfd].db_path;
if (!db_path) {
_tt_syslog(errstr, LOG_ERR, "%s: db_path == 0", here);
return 0;
}
prefix_len = _Tt_dirname(db_path);
if (prefix_len < 0) {
_tt_syslog(errstr, LOG_ERR, "%s: _Tt_dirname(db_path) < 0",
here);
return 0;
}
int basename_len = _Tt_basename(db_path);
char *db_basename = db_path + basename_len;
if (strcmp(db_basename, _TT_OID_PROP) == 0) {
/* get the OID-ACCESS info */
if (_tt_get_oid_access(rec, prefix_len, db_path,
oid_uid, oid_group, oid_mode)) {
// no record, error
_tt_syslog(errstr, LOG_ERR, "_tt_get_oid_access() != 0");
return 0;
}
/* get the document oid */
if (!_tt_get_record(prefix_len, db_path, _TT_OID_CONTAINER,
rec)) {
// only a doc oid has no mapping, allow access
return 1;
}
/* get the document path */
if (!_tt_get_record(prefix_len, db_path, _TT_DOCOID_PATH,
_tt_record + OID_KEY_LENGTH)) {
_tt_syslog(errstr, LOG_ERR,
"_tt_get_record(,,_TT_DOCOID_PATH,) == 0");
return 0;
}
char *path = _tt_record + OID_KEY_LENGTH;
char *p = strchr(path, ':');
if (!p) {
_tt_syslog(errstr, LOG_ERR, "strchr(path, ':') != 0");
return 0;
}
/* get the document's permissions */
struct stat buf;
if (stat(p+1, &buf) != -1) {
file_uid = buf.st_uid;
file_group = buf.st_gid;
file_mode = buf.st_mode;
} else {
file_uid = oid_uid;
file_group = oid_group;
file_mode = oid_mode;
}
if (_tt_uid == file_uid || _tt_uid == oid_uid) {
category = DM_USER;
} else {
for (int i = 0; i < _tt_gidlen; i++) {
if (_tt_gidlist[i] == file_group) {
category = DM_GROUP;
break;
}
}
}
if (file_mode == (mode_t)-1) {
// no mode, assume OK
return 1;
}
/* check access request against permissions */
switch (category) {
case DM_USER:
// The owner is allowed all priviledges
return 1;
/* this left here in case we want to check someday
* switch (mode) {
* case 'r':
* request_mask = S_IRUSR;
* break;
* case 'w':
* request_mask = S_IWUSR;
* break;
* case ('r' + 'w'):
* request_mask = S_IRUSR + S_IWUSR;
* break;
* }
* break;
*/
case DM_GROUP:
switch (mode) {
case 'r':
request_mask = S_IRGRP;
break;
case 'w':
request_mask = S_IWGRP;
break;
case ('r' + 'w'):
request_mask = S_IRGRP + S_IWGRP;
break;
}
break;
case DM_OTHER:
switch (mode) {
case 'r':
request_mask = S_IROTH;
break;
case 'w':
request_mask = S_IWOTH;
break;
case ('r' + 'w'):
request_mask = S_IROTH + S_IWOTH;
break;
}
break;
}
if ((request_mask & file_mode) == 0 &&
(oid_mode == (mode_t)-1 || (request_mask & oid_mode) == 0)) {
return 0;
}
} else if (strcmp(db_basename, _TT_LINK_PROP) == 0 ||
strcmp(db_basename, _TT_LINK_ENDS) == 0) {
/* get the LINK access permissions */
if (_tt_get_link_permissions(rec, prefix_len, db_path,
oid_uid, oid_group, oid_mode)) {
// no link permissions, error
_tt_syslog(errstr, LOG_ERR,
"_tt_get_link_permissions() != 0");
return 0;
}
if (_tt_uid == oid_uid) {
category = DM_USER;
} else {
for (int i = 0; i < _tt_gidlen; i++) {
if (_tt_gidlist[i] == oid_group) {
category = DM_GROUP;
break;
}
}
}
if (oid_mode == (mode_t)-1) {
// no mode, assume OK
return 1;
}
/* check LINK access request against permissions */
switch (category) {
case DM_USER:
switch (mode) {
case 'r':
// everything fails if the owner can't read
// the link, so just let him.
return 1;
// request_mask = S_IRUSR;
// break;
case 'w':
request_mask = S_IWUSR;
break;
case ('r' + 'w'):
request_mask = S_IRUSR + S_IWUSR;
break;
}
break;
case DM_GROUP:
switch (mode) {
case 'r':
request_mask = S_IRGRP;
break;
case 'w':
request_mask = S_IWGRP;
break;
case ('r' + 'w'):
request_mask = S_IRGRP + S_IWGRP;
break;
}
break;
case DM_OTHER:
switch (mode) {
case 'r':
request_mask = S_IROTH;
break;
case 'w':
request_mask = S_IWOTH;
break;
case ('r' + 'w'):
request_mask = S_IROTH + S_IWOTH;
break;
}
break;
}
if ((request_mask & oid_mode) == 0) {
return 0;
}
}
return 1;
}
/*
* Read the oid owner as a uid.
*/
_Tt_dm_status
_tt_read_oid_user(int isfd, const char *key, uid_t &uid,
gid_t &group, mode_t &mode)
{
static const char *here = "_tt_read_oid_user()";
memcpy(_tt_record, key, OID_KEY_LENGTH);
if (isstart(isfd, &_tt_oid_keydesc, OID_KEY_LENGTH, _tt_record,
ISEQUAL) == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isstart(): %d", here, iserrno);
if (iserrno == ENOREC) {
return DM_NO_RECORD;
} else {
return DM_ERROR;
}
}
if (isread(isfd, _tt_record, ISNEXT) == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isread(): %d", here, iserrno);
return DM_ERROR;
}
_Tt_oid_access_ptr oa = new _Tt_oid_access(_tt_record);
uid = oa->user();
group = oa->group();
mode = oa->mode();
return DM_OK;
}
/*
* Write out the oid access fields.
*
* The uid can only be written if there does not already exist one.
* The group can only be set by the owner and must be a group of the user.
* The mode can only be changed by the owner.
*
* The superuser can change anything.
*/
_Tt_dm_status
_tt_write_oid_access(int isfd, const char *key, uid_t uid,
gid_t group, mode_t mode)
{
static const char *here = "_tt_write_oid_access()";
_Tt_oid_access_ptr oa;
memcpy(_tt_record, key, OID_KEY_LENGTH);
if (isstart(isfd, &_tt_oid_keydesc, OID_KEY_LENGTH, _tt_record,
ISEQUAL) == -1) {
if (iserrno != ENOREC) {
_tt_syslog(errstr, LOG_ERR, "%s: isstart(): %d",
here, iserrno);
return DM_ERROR;
}
// no record found so we are writing for the first time
if (uid == (uid_t)-1) {
// no record and we are not writing the uid
return DM_ACCESS_DENIED;
}
if (_tt_uid == _TT_ROOT_UID) {
// superuser can do anything
} else if (_tt_uid != uid) {
// Can only write yourself as owner
return DM_ACCESS_DENIED;
}
oa = new _Tt_oid_access(key, uid, group, mode);
isreclen = oa->reclen();
if (iswrite(isfd, oa->rec()) == -1) {
return DM_ERROR;
}
} else {
if (isread(isfd, _tt_record, ISNEXT) == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isread(): %d",
here, iserrno);
return DM_ERROR;
}
oa = new _Tt_oid_access(_tt_record);
uid_t owner = oa->user();
int valid_group = 0;
for (int i = 0; i < _tt_gidlen; i++) {
if (group == _tt_gidlist[i]) {
valid_group = 1;
break;
}
}
if (_tt_uid == _TT_ROOT_UID) {
// superuser can do anything
} else if (uid != (uid_t)-1) {
// can't change uid
return DM_ACCESS_DENIED;
} else if (group != (gid_t)-1 &&
(_tt_uid != owner || !valid_group)) {
// only owner can change group and group must be valid
return DM_ACCESS_DENIED;
} else if (mode != (mode_t)-1 && _tt_uid != owner) {
// only owner can change mode
return DM_ACCESS_DENIED;
}
if (uid != (uid_t)-1) {
oa->set_user(uid);
}
if (group != (gid_t)-1) {
oa->set_group(group);
}
if (mode != (mode_t)-1) {
oa->set_mode(mode);
}
isreclen = oa->reclen();
if (isrewrec(isfd, isrecnum, oa->rec()) == -1) {
return DM_ERROR;
}
// Flush any cached access values
_Tt_oid_access_ptr oa = _tt_oa_cache->lookup(key);
if (! oa.is_null()) {
_tt_oa_cache->remove(oa);
}
_Tt_link_access_ptr la = _tt_la_cache->lookup(key);
if (! la.is_null()) {
_tt_la_cache->remove(la);
}
}
return DM_OK;
}
void
_tt_update_modtime(int isfd,
keydesc * key,
int key_len,
_Tt_isam_results & res)
{
static const char *here = "_tt_update_modtime()";
memset(_tt_record, 0, sizeof(_tt_record));
memcpy(_tt_record, key, key_len);
res.result = isstart(isfd, key, key_len, _tt_record, ISEQUAL);
if (res.result == -1) {
if (iserrno != ENOREC && iserrno != EENDFILE) {
_tt_syslog(errstr, LOG_ERR, "%s: isstart(): %d",
here, iserrno);
res.iserrno = iserrno;
return;
}
// no mod time?
res.result = 0;
res.iserrno = 0;
return;
}
res.result = isread(isfd, _tt_record, ISNEXT);
if (res.result == -1) {
if (iserrno != ENOREC && iserrno != EENDFILE) {
_tt_syslog(errstr, LOG_ERR, "%s: isread(): %d",
here, iserrno);
res.iserrno = iserrno;
return;
}
// no mod time?
res.result = 0;
res.iserrno = 0;
return;
}
_Tt_string last_mod_time_string((const unsigned char *)
&_tt_record[OID_KEY_LENGTH +
MAX_PROP_LENGTH],
isreclen -
MAX_PROP_LENGTH - OID_KEY_LENGTH);
long last_mod_time = atol((char *)last_mod_time_string);
if (last_mod_time < 0) {
last_mod_time = 0;
}
else {
last_mod_time++;
}
char mod_string[16];
sprintf(&_tt_record[OID_KEY_LENGTH + MAX_PROP_LENGTH],
"%ld", (long)last_mod_time);
res.result = isrewcurr(isfd, _tt_record);
res.iserrno = iserrno;
return;
}
/*
* _tt_min_auth_level_1 - returns the minimally required authentication level.
*/
int *
_tt_min_auth_level_1(char ** /* file */, SVCXPRT * /* transp */)
{
return &_tt_auth_level;
}
/*
* _tt_isaddindex_1 - wrapper for NetISAM isaddindex
*/
_Tt_isam_results *
_tt_isaddindex_1(_Tt_isaddindex_args *args, SVCXPRT * /* transp */)
{
/* check for stale NetISAM file descriptor */
if (!_tt_check_stale_isfd(args->isfd)) {
res.result = -1;
res.iserrno = ERPC;
} else {
res.result = isaddindex(args->isfd, args->key);
res.iserrno = iserrno;
}
return (&res);
}
/*
* _tt_isbuild_1 - wrapper for NetISAM isbuild. The input path is in the form
* <host>::<local path>
*/
_Tt_isam_results *
_tt_isbuild_1(_Tt_isbuild_args *args, SVCXPRT * /* transp */)
{
static const char *here = "_tt_isbuild_1()";
_Tt_string db_path = (char *)NULL;
mode_t prev_umask;
isreclen = args->isreclen;
prev_umask = umask(~(S_IRUSR+S_IWUSR)); // Only I can read and write
db_path = map_old_db_to_new_db(args->path);
res.result = isbuild(db_path, args->reclen, args->key, args->mode);
umask(prev_umask);
if (res.result == -1) {
char *root_dir = (char *)db_path;
int dir_len;
if (root_dir) {
dir_len = _Tt_dirname(root_dir);
} else {
dir_len = 0;
}
if (dir_len == 0 ||
((iserrno != ENOENT) && (iserrno != EFNAME))) {
// no root dir to build or strange error
_tt_syslog(errstr, LOG_ERR, "%s: isbuild(): %d",
here, iserrno);
res.iserrno = iserrno;
return (&res);
}
// failed because dir doesn't exist - create it
char *dir = (char *)malloc(dir_len+2);
memcpy(dir, root_dir, dir_len+1);
dir[dir_len+1] = '\0';
/*
* Create the TT_DIR directory with read and search (x)
* permissions for everyone. Can't think of any harm that
* could be done by allowing people to see the file names,
* and it would be irritating to somebody poking around
* looking for a problem not to be able to cd into the
* directory. Setting the group search (x) permission is
* particularly important, else if set-gid is set for the
* directory (to get BSD semantics) ls shows the perms as
* rwx--S---, and the capital S looks like a bug to most
* people.
*/
if (mkdir(dir, S_IRWXU+S_IRGRP+S_IXGRP+S_IROTH+S_IXOTH)
== -1) {
_tt_syslog(errstr, LOG_ERR, "dir: %m", dir);
free(dir);
res.iserrno = iserrno;
return (&res);
}
free(dir);
if (db_path.is_null()) {
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "db_path.is_null()");
return (&res);
}
prev_umask = umask(~(S_IRUSR+S_IWUSR));
res.result = isbuild((char *)db_path, args->reclen,
args->key, args->mode);
umask(prev_umask);
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isbuild(): %d",
here, iserrno);
res.iserrno = iserrno;
return (&res);
}
}
_tt_db_table[res.result].db_path = db_path;
_tt_db_table[res.result].opener_uid = _tt_uid;
res.iserrno = iserrno;
return (&res);
}
/*
* _tt_isclose_1 - wrapper for NetISAM isclose
*/
_Tt_isam_results *
_tt_isclose_1(int *isfd, SVCXPRT * /* transp */)
{
static const char *here = "_tt_isclose_1()";
if (!_tt_check_stale_isfd(*isfd)) {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
} else {
// JET - 06/12/2002
// VU#975403 - CERT TT vulnerability. By passing an invalid isfd
// a local or remote attacker can zero out 4 bytes at any location,
// thereby allowing other exploits (items 2 & 3 - delete or
// overwrite any file on the system.)
// Here, we will just check to make sure: 0 >= isfd < _TT_MAX_ISFD
if (*isfd < 0 || *isfd >= _TT_MAX_ISFD)
{ // some trickery going on?
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_isclose_1: Invalid file descriptor. This may be an attempted exploit.",
here );
}
else
{
res.result = cached_isclose(*isfd);
if (res.result != -1) {
_tt_db_table[*isfd].db_path = 0;
_tt_db_table[*isfd].opener_uid = (uid_t)-1;
} else {
_tt_syslog(errstr, LOG_ERR, "%s: isclose(): %d",
here, iserrno);
}
res.iserrno = iserrno;
}
}
return (&res);
}
/*
* _tt_iscntl_1 - wrapper for NetISAM iscntl
*/
_Tt_iscntl_results *
_tt_iscntl_1(_Tt_iscntl_args *args, SVCXPRT * /* transp */)
{
static const char *here = "_tt_iscntl_1()";
static _Tt_iscntl_results res;
if (!_tt_check_stale_isfd(args->isfd)) {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
} else {
/*
res.result = iscntl(args->isfd, args->func, args->arg.arg_val);
res.iserrno = iserrno;
res.arg.arg_val = args->arg.arg_val;
res.arg.arg_len = args->arg.arg_len;
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: iscntl(): %d",
here, iserrno);
}
*/
res.iserrno = 0;
res.arg.arg_val = " 1";
res.arg.arg_len = 4;
res.result = 0;
}
return (&res);
}
/*
* _tt_isdelrec_1 - wrapper for NetISAM isdelrec
*/
_Tt_isam_results *
_tt_isdelrec_1(_Tt_isdelrec_args *args, SVCXPRT *transp)
{
static const char *here = "_tt_isdelrec_1()";
if (!_tt_check_stale_isfd(args->isfd)) {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
} else {
if (args->rec.rec_len > 0) {
if (_tt_oid_accessp(args->isfd, args->rec.rec_val, 'w')) {
if (msg_q_lock(args->isfd,
args->rec.rec_val,
args->rec.rec_len,
transp)) {
res.result = isdelrec(args->isfd, args->recnum);
res.iserrno = iserrno;
}
else {
res.result = -1;
res.iserrno = ERPC;
}
} else {
res.result = -1;
res.iserrno = DM_ACCESS_DENIED;
}
} else {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: rec_len <= 0", here);
}
}
return (&res);
}
/*
* _tt_iserase_1 - wrapper for NetISAM iserase
*/
_Tt_isam_results *
_tt_iserase_1(char **path, SVCXPRT * /* transp */)
{
res.result = iserase(*path);
res.iserrno = iserrno;
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "iserase(): %d", iserrno);
}
return (&res);
}
/*
* _tt_isopen_1 - wrapper for NetISAM isopen
*/
_Tt_isam_results *
_tt_isopen_1(_Tt_isopen_args *args, SVCXPRT * /* transp */)
{
static const char *here = "_tt_isopen_1()";
_Tt_string db_path = args->path;
if (db_path.is_null()) {
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: args->path.is_null()", here);
return (&res);
}
int prefix_len = _Tt_dirname((char *)db_path) + 1;
if (prefix_len < 1) {
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _Tt_dirname(db_path) < 0",
here);
return (&res);
}
// bugid 1049947: someone could use our dbserver to get at
// other isam files on the same machine. Protect (lightly)
// against this by making sure "TT_DB" is in the path.
// Someone could still bypass this by making symlinks to the
// file he wants to get at, so the "right way" to fix this
// would be to write a distinctive signature into the APPL_MAGIC
// bucket, and check for that signature on open. However,
// that's a more widespread change than we want to try to
// squeeze in only hours from Beta... RFM 1/28/90
int l = db_path.len();
int tl = strlen("TT_DB");
int j;
for (j = 0; j<l; j++) {
if (0==strncmp((char *)db_path+j,"TT_DB",tl)) {
break;
}
}
if (j==l) {
// Might consider logging this one to the console, since
// it's evidence of an attempted security violation.
res.result = -1;
res.iserrno = DM_ACCESS_DENIED;
_tt_syslog(errstr, LOG_ERR,
catgets(_ttcatd, 5, 3, "Security violation: "
"RPC call wanted me to open a file that "
"is not a ToolTalk database"));
return(&res);
}
memcpy(_tt_log_file, (char *)db_path, prefix_len);
strcpy(_tt_log_file+prefix_len, _TT_LOG_FILE);
if (access(_tt_log_file, F_OK) == 0) {
_tt_process_transaction();
}
db_path = map_old_db_to_new_db(args->path);
if (db_path.is_null()) {
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: map_old_db_to_new_db() == 0",
here);
return (&res);
}
res.result = cached_isopen((char *)db_path, args->mode);
if (res.result != -1) { /* record database name and isfd */
_tt_db_table[res.result].db_path = db_path;
_tt_db_table[res.result].opener_uid = _tt_uid;
} else {
//
// Try to create the file.
// The constructor create the files.
// Then call isopen again,
// the files will then exist. bug# 1179660
//
_Tt_db_server_db * db;
_tt_file_partition_results *partitionRes;
char *file = (char *)db_path;
partitionRes = _tt_get_file_partition_1(&file, NULL);
db = new _Tt_db_server_db(partitionRes->partition);
delete db;
res.result = cached_isopen((char *)db_path, args->mode);
if (res.result != -1) { /* record database name and isfd */
_tt_db_table[res.result].db_path = db_path;
_tt_db_table[res.result].opener_uid = _tt_uid;
} else {
_tt_syslog(errstr, LOG_ERR, "%s: isopen(): %d", here, iserrno);
}
}
res.iserrno = iserrno;
return (&res);
}
/*
* _tt_isread_1 - wrapper for NetISAM isread
*/
_Tt_isread_results *
_tt_isread_1(_Tt_isread_args *args, SVCXPRT *transp)
{
static const char *here = "_tt_isread_1()";
static _Tt_isread_results res;
if (!_tt_check_stale_isfd(args->isfd)) {
res.isresult.result = -1;
res.isresult.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
return (&res);
}
if (_tt_oid_accessp(args->isfd, args->rec.rec_val, 'r')) {
isrecnum = args->isrecnum;
memcpy(_tt_record, args->rec.rec_val, args->rec.rec_len);
if (msg_q_lock(args->isfd,
args->rec.rec_val,
args->rec.rec_len,
transp)) {
res.isresult.result = isread(args->isfd,
_tt_record,
args->mode);
#ifdef Foo
//
// Do not return the _MODIFICATIN_DATE information
// to 4.x client systems (the only ones calling
// this function). That is only in the file:
//
// TT_DB_PROPERTY_TABLE_FILE (table_fn below)
//
// NOTE:
//
//_MODIFICATION_DATE==TT_DB_PROPS_CACHE_LEVEL_PROPERTY.
//
// The record format is:
//
// ------------------------------
// | OID-KEY | PROPERTY | VALUE |
// ------------------------------
//
// and OID_KEY_LENGTH is the size of OID-KEY.
//
const char *table_fn = TT_DB_PROPERTY_TABLE_FILE;
const char *mod_name=TT_DB_PROPS_CACHE_LEVEL_PROPERTY;
//
// Find the last slash in the full path name so
// we can compare the file name (not dir names).
//
char *file = strrchr(_tt_db_table[args->isfd].db_path,
'/');
if (file == NULL) {
// Hack to avoid core dump if no '/' in name.
file = _tt_db_table[args->isfd].db_path;
} else {
file++; // Point past '/' to file name.
}
if ((strncmp(table_fn, file, strlen(table_fn)) == 0)
&& (strncmp(mod_name,
&_tt_record[OID_KEY_LENGTH],
strlen(mod_name)) ==0)) {
//
// If you find a _MODIFICATION_DATE then,
// Just return the next record or error.
//
return _tt_isread_1(args, transp);
} else {
#endif // Foo
res.isresult.iserrno = iserrno;
res.rec.rec_len = isreclen;
res.rec.rec_val = _tt_record;
#ifdef Foo
}
#endif
} else {
res.isresult.result = -1;
res.isresult.iserrno = ERPC;
}
if (res.isresult.result == -1 && iserrno != EENDFILE) {
_tt_syslog(errstr, LOG_ERR, "%s: isread(): %d",
here, iserrno);
}
} else {
res.isresult.result = -1;
res.isresult.iserrno = DM_ACCESS_DENIED;
res.rec.rec_len = 0;
res.rec.rec_val = 0;
}
res.isreclen = isreclen;
res.isrecnum = isrecnum;
return (&res);
}
/*
* _tt_isrewrec_1 - wrapper for NetISAM isrewrec
*/
_Tt_isam_results *
_tt_isrewrec_1(_Tt_isrewrec_args *args, SVCXPRT *transp)
{
static const char *here = "_tt_isrewrec_1()";
if (!_tt_check_stale_isfd(args->isfd)) {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
} else {
if (args->rec.rec_len > 0) {
if (_tt_oid_accessp(args->isfd, args->rec.rec_val, 'w')) {
if (msg_q_lock(args->isfd,
args->rec.rec_val,
args->rec.rec_len,
transp)) {
isreclen = args->rec.rec_len;
res.result = isrewrec(args->isfd, args->recnum,
args->rec.rec_val);
res.iserrno = iserrno;
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isrewrec"
"(): %d", here, iserrno);
}
}
else {
res.result = -1;
res.iserrno = ERPC;
}
} else {
res.result = -1;
res.iserrno = DM_ACCESS_DENIED;
}
} else {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: rec_len <= 0", here);
}
}
return (&res);
}
/*
* _tt_isstart_1 - wrapper for NetISAM isstart
*/
_Tt_isam_results *
_tt_isstart_1(_Tt_isstart_args *args, SVCXPRT *transp)
{
static const char *here = "_tt_isstart_1";
if (!_tt_check_stale_isfd(args->isfd)) {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
} else if (args->rec.rec_val==0 && args->mode!=ISFIRST) {
res.result = -1;
res.iserrno = EBADKEY;
_tt_syslog(errstr, LOG_ERR, "rec_val == 0 && mode != ISFIRST");
} else if (msg_q_lock(args->isfd,
args->rec.rec_val,
args->rec.rec_len,
transp)) {
res.result = isstart(args->isfd, args->key, args->key_len,
args->rec.rec_val, args->mode);
res.iserrno = iserrno;
if (res.result == -1) {
// ENOREC happens a lot even in normal operation.
if (iserrno!=ENOREC) {
_tt_syslog(errstr, LOG_ERR, "%s: isstart(): %d",
here, iserrno);
}
}
}
else {
res.result = -1;
res.iserrno = ERPC;
}
return (&res);
}
/*
* _tt_iswrite_1 - wrapper for NetISAM iswrite
*/
_Tt_isam_results *
_tt_iswrite_1(_Tt_iswrite_args *args, SVCXPRT *transp)
{
static const char *here = "_tt_iswrite_1()";
if (!_tt_check_stale_isfd(args->isfd)) {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
} else {
if (args->rec.rec_len > 0) {
if (_tt_oid_accessp(args->isfd,
args->rec.rec_val, 'w')) {
if (msg_q_lock(args->isfd,
args->rec.rec_val,
args->rec.rec_len,
transp)) {
isreclen = args->rec.rec_len;
res.result = iswrite(args->isfd, args->rec.rec_val);
res.iserrno = iserrno;
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: iswrite"
"(): %d", here, iserrno);
}
}
else {
res.result = -1;
res.iserrno = ERPC;
}
} else {
res.result = -1;
res.iserrno = DM_ACCESS_DENIED;
}
} else {
res.result = -1;
res.iserrno = ERPC;
}
}
return (&res);
}
/*
* _tt_test_and_set_1 - reads (test) the record with the given input
* (key + prop name). If the record already existed, returns it and 0.
* Otherwise, writes (set) the given record out and returns 1. If any error,
* returns -1.
*/
_Tt_test_and_set_results *
_tt_test_and_set_1(_Tt_test_and_set_args *args, SVCXPRT *transp)
{
static const char *here = "_tt_test_and_set_1()";
static _Tt_test_and_set_results res;
if (!_tt_check_stale_isfd(args->isfd)) {
res.isresult.result = -1;
res.isresult.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
return (&res);
}
if (!_tt_oid_accessp(args->isfd, args->rec.rec_val, 'r' + 'w')) {
res.isresult.result = -1;
res.isresult.iserrno = DM_ACCESS_DENIED;
res.rec.rec_len = 0;
res.rec.rec_val = 0;
return (&res);
}
if (!msg_q_lock(args->isfd,
args->rec.rec_val,
args->rec.rec_len,
transp)) {
res.isresult.result = -1;
res.isresult.iserrno = ERPC;
res.rec.rec_len = 0;
res.rec.rec_val = 0;
return (&res);
}
memcpy(_tt_record, args->rec.rec_val, args->rec.rec_len);
// look for this record
if (isstart(args->isfd, args->key, args->key_len,
_tt_record, ISEQUAL) == -1) {
if (iserrno == ENOREC) {
// not found, we can write ours
isreclen = args->rec.rec_len;
if (iswrite(args->isfd, args->rec.rec_val)
!= -1) {
res.isresult.result = 1;
res.rec.rec_len = 0;
res.rec.rec_val = 0;
res.isrecnum = isrecnum;
return (&res);
}
_tt_syslog(errstr, LOG_ERR, "%s: iswrite(): %d",
here, iserrno);
res.isresult.iserrno = iserrno;
res.isresult.result = -1;
res.rec.rec_len = 0;
res.rec.rec_val = 0;
return (&res);
} else {
_tt_syslog(errstr, LOG_ERR, "%s: isstart(): %d",
here, iserrno);
res.isresult.iserrno = iserrno;
res.isresult.result = -1;
res.rec.rec_len = 0;
res.rec.rec_val = 0;
return (&res);
}
}
// we found a record, read it and return it
if (isread(args->isfd, _tt_record, ISNEXT) != -1) {
res.isresult.result = 0;
res.isresult.iserrno = iserrno;
res.rec.rec_len = isreclen;
res.rec.rec_val = _tt_record;
res.isrecnum = isrecnum;
return (&res);
}
// EENDFILE happens a lot even in normal operation.
if (iserrno && iserrno!=EENDFILE) {
_tt_syslog(errstr, LOG_ERR, "%s: isread(): %d",
here, iserrno);
}
res.isresult.iserrno = iserrno;
res.isresult.result = -1;
res.rec.rec_len = 0;
res.rec.rec_val = 0;
return (&res);
}
/*
* _tt_transaction_1 - given a list of records, writes them out to the log file
* associated with the given database. If the log file writes succeed returns
* 0, otherwise returns -1. The actual updating of database records is done
* by procedure _tt_process_transaction and is invoked by _tt_dbserver_prog_1
* after a reply is sent to the client. In case where there's a crash, the
* server recovers by checking for the existents of log files every time it
* opens a database and invoking _tt_process_transaction. The log file is in
* the same directory as that of the database. The log file's name is
* "log_file".
*
* The log file has the following format:
* <transaction begin/commit flag>
* <path of the target database of the transaction>
* <the set of records to be written/updated>,
* where each element of the set is
* <record number, record length, record data>
*/
_Tt_isam_results *
_tt_transaction_error(int fd)
{
if (fd != -1) {
close(fd);
unlink(_tt_log_file);
_tt_log_file[0] = '\0';
}
res.result = -1;
return (&res);
}
_Tt_isam_results *
_tt_transaction_1(_Tt_transaction_args* args, SVCXPRT * /* transp */)
{
struct stat buf; // JET - VU#975403/VU#299816
static const char *here = "_tt_transaction_1()";
/* check for stale NetISAM file descriptor */
if (!_tt_check_stale_isfd(args->isfd)) {
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
return _tt_transaction_error(args->isfd);
}
_Tt_trans_record *trec = args->recs;
if (!trec) {
res.iserrno = ERPC;
return _tt_transaction_error(-1);
}
int fd = -1;
_tt_log_file[0] = '\0';
/* Must have write permission to start transaction */
if (_tt_oid_accessp(args->isfd, trec->rec.rec_val, 'w')) {
char *db_path = _tt_db_table[args->isfd].db_path;
if (!db_path) {
res.iserrno = ERPC;
return _tt_transaction_error(fd);
}
int prefix_len = _Tt_dirname(db_path) + 1;
if (prefix_len < 1) {
res.iserrno = ERPC;
return _tt_transaction_error(fd);
}
memcpy(_tt_log_file, db_path, prefix_len);
strcpy(_tt_log_file+prefix_len, _TT_LOG_FILE);
if (access(_tt_log_file, F_OK) == 0) {
_tt_process_transaction();
}
// JET - 06/24/2002 VU#975403/VU#299816 - CERT TT
// vulnerability. check for the presence of a
// symlink. Abort (nicely) if there.
if(lstat(_tt_log_file, &buf) != -1)
{ // present
if (S_ISLNK(buf.st_mode))
{ // it's a symlink. Oops.
_tt_syslog(errstr, LOG_ERR,
"%s: _tt_log_file is a symlink. Aborting.",
here );
res.result = -1;
res.iserrno = DM_ACCESS_DENIED;
return(&res);
}
}
if ((fd = open(_tt_log_file, O_RDWR | O_CREAT, S_IREAD + S_IWRITE))
== -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
/* Turn on close-on-exec */
fcntl(fd, F_SETFD, 1);
/* reset to beginning of file */
off_t offset;
if ((offset = lseek(fd, 0, SEEK_SET)) == -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
/* set the transaction flag to signify begin of transaction */
int flag = 1;
if (write(fd, (char *)&flag, sizeof(int)) == -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
/* write the target database path out to the log file */
if (write(fd, db_path, strlen(db_path)+1) == -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
/* write the records out to the log file */
while (trec) {
/* write the record's new flag out to the log file */
if (write(fd, (char *)&trec->newp, sizeof(int)) == -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
/* write the record number out to the log file */
if (write(fd, (char *)&trec->recnum, sizeof(long))
== -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
/* write the record's length out to the log file */
if (write(fd, (char *)&trec->rec.rec_len, sizeof(u_int))
== -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
/* write the record's content out to the log file */
if (write(fd, (char *)trec->rec.rec_val,
trec->rec.rec_len) == -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
trec = trec->next;
}
if (DM_TEST_CRASH) {
/* Test crash recovery */
fprintf(stderr,"_tt_transaction_1: simulating server crash to test crash recovery . . . exiting\n");
exit(1);
}
/* clear the transaction flag to signify transaction commit */
if (lseek(fd, 0, SEEK_SET) == -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
flag = 0;
if (write(fd, (char *)&flag, sizeof(int)) == -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
/* close the log file */
if (close(fd) == -1) {
res.iserrno = DM_WRITE_FAILED;
return _tt_transaction_error(fd);
}
res.result = 0;
} else {
res.result = -1;
res.iserrno = DM_ACCESS_DENIED;
}
return (&res);
}
/*
* _tt_mfs_1 - find a mount entry whose mounted directory/file system is the
* longest match of the given directory/absolute path. For example, the mount
* entry "/home3" matches "/home3/dynamo" better than the mount entry "/".
* If msfp == 1, then match using directory. Otherwise, match using absolute
* path.
*/
char **
_tt_mfs_1(char **path, SVCXPRT * /* transp */)
{
static char *res;
if (!*path) {
res = "";
} else {
_Tt_file_system fs;
_Tt_file_system_entry_ptr fsep = fs.bestMatchToPath(*path);
res = fsep->getMountPoint();
}
return (&res);
}
/*
* _tt_getoidaccess_1 - get the uid/group/mode for an oid
*/
_Tt_oidaccess_results *
_tt_getoidaccess_1(_Tt_oidaccess_args *args, SVCXPRT * /* transp */)
{
static _Tt_oidaccess_results res;
res.result = _tt_read_oid_user(args->isfd, args->key.key_val,
res.uid, res.group, res.mode);
res.iserrno = iserrno;
return (&res);
}
/*
* _tt_setoiduser_1 - set the user for an oid
*/
_Tt_isam_results *
_tt_setoiduser_1(_Tt_oidaccess_args *args, SVCXPRT * /* transp */)
{
res.result = _tt_write_oid_access(args->isfd, args->key.key_val,
(uid_t)args->value, (gid_t)-1,
(mode_t)-1);
res.iserrno = iserrno;
return (&res);
}
/*
* _tt_setoidgroup_1 - set the user for an oid
*/
_Tt_isam_results *
_tt_setoidgroup_1(_Tt_oidaccess_args *args, SVCXPRT * /* transp */)
{
res.result = _tt_write_oid_access(args->isfd, args->key.key_val,
(uid_t)-1, (gid_t)args->value,
(mode_t)-1);
res.iserrno = iserrno;
return (&res);
}
/*
* _tt_setoidmode_1 - set the user for an oid
*/
_Tt_isam_results *
_tt_setoidmode_1(_Tt_oidaccess_args *args, SVCXPRT * /* transp */)
{
res.result = _tt_write_oid_access(args->isfd, args->key.key_val,
(uid_t)-1, (gid_t)-1,
(mode_t)args->value);
res.iserrno = iserrno;
return (&res);
}
_Tt_spec_props *
_tt_readspec_1(_Tt_spec_props * /* argp */, SVCXPRT * /* transp */)
{
static _Tt_spec_props res;
res.iserrno = iserrno;
return (&res);
}
_Tt_isam_results *
_tt_writespec_1(_Tt_spec_props * /* argp */, SVCXPRT * /* transp */)
{
res.iserrno = iserrno;
return (&res);
}
_Tt_isam_results *
_tt_addsession_1(_Tt_session_args *argp, SVCXPRT * /* transp */)
{
static const char *here = "_tt_addsession_1()";
Table_oid_prop* tp = (Table_oid_prop *) _tt_record;
_Tt_string sessionid((unsigned char *)argp->session.session_val,
argp->session.session_len);
int isfd = argp->isfd;
_Tt_string filejoin_prop(_TT_FILEJOIN_PROPNAME);
if (!_tt_check_stale_isfd(isfd)) {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
}
// Start reading session props
memset(_tt_record, 0, sizeof(_tt_record));
memcpy(_tt_record, argp->key, argp->key_len);
res.result = isstart(isfd, argp->key, argp->key_len,
_tt_record, ISEQUAL);
res.iserrno = iserrno;
if (res.result == -1) {
if (iserrno != ENOREC && iserrno != EENDFILE) {
_tt_syslog(errstr, LOG_ERR, "%s: isstart(): %d",
here, iserrno);
return (&res);
}
// no record found, no sessions on this doc node
} else {
// check to make sure this session is not already registered
for (;;) {
res.result = isread(isfd, _tt_record, ISNEXT);
if (res.result == -1) {
if (iserrno != ENOREC && iserrno != EENDFILE) {
_tt_syslog(errstr, LOG_ERR, "%s: isread"
"(): %d", here, iserrno);
return (&res);
}
break;
}
if (strcmp((char *) filejoin_prop, tp->propname)) {
// ran past the session prop
break;
}
if (memcmp(argp->oidkey.oidkey_val,
tp->objkey, argp->oidkey.oidkey_len)) {
// ran past our key
break;
}
if (memcmp((char *) sessionid, tp->propval,
isreclen-MAX_PROP_LENGTH-OID_KEY_LENGTH)) {
// already here so we're done
res.result = 0;
return (&res);
}
}
}
// need to add this session to the doc node
// zero out _tt_record, which is where tp points
memset(_tt_record, 0, sizeof(_tt_record));
memcpy(tp->objkey, argp->oidkey.oidkey_val, argp->oidkey.oidkey_len);
strcpy(tp->propname, filejoin_prop);
memcpy(tp->propval, (char *) sessionid, sessionid.len());
isreclen = OID_KEY_LENGTH+MAX_PROP_LENGTH+sessionid.len();
res.result = iswrite(isfd, _tt_record);
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: iswrite(): %d", here, iserrno);
res.iserrno = iserrno;
return (&res);
}
_tt_update_modtime(isfd, argp->key,
argp->key_len,
res);
return (&res);
}
_Tt_isam_results *
_tt_delsession_1(_Tt_session_args *argp, SVCXPRT * /* transp */)
{
static const char *here = "_tt_delsession_1()";
Table_oid_prop* tp = (Table_oid_prop *) _tt_record;
_Tt_string sessionid((unsigned char *)argp->session.session_val,
argp->session.session_len);
int isfd = argp->isfd;
_Tt_string filejoin_prop(_TT_FILEJOIN_PROPNAME);
if (!_tt_check_stale_isfd(isfd)) {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
}
// Start reading session props
memset(_tt_record, 0, sizeof(_tt_record));
memcpy(_tt_record, argp->key, argp->key_len);
res.result = isstart(isfd, argp->key, argp->key_len,
_tt_record, ISEQUAL);
res.iserrno = iserrno;
if (res.result == -1) {
//
//
// _tt_syslog(errstr, LOG_ERR, "%s: isstart(): %d", here, iserrno);
return (&res);
}
// look for this session
for (;;) {
res.result = isread(isfd, _tt_record, ISNEXT);
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isread(): %d",
here, iserrno);
break;
}
if (strcmp((char *) filejoin_prop, tp->propname)) {
// ran past the session prop
break;
}
if (memcmp(argp->oidkey.oidkey_val,
tp->objkey, argp->oidkey.oidkey_len)) {
// ran past our key
break;
}
if (memcmp((char *) sessionid, tp->propval,
isreclen-MAX_PROP_LENGTH-OID_KEY_LENGTH)) {
// we found it, now delete it
res.result = isdelcurr(isfd);
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isdelcurr"
"(): %d", here, iserrno);
res.iserrno = iserrno;
return (&res);
}
break;
}
}
_tt_update_modtime(isfd, argp->key,
argp->key_len,
res);
return (&res);
}
_Tt_spec_props *
_tt_gettype_1(_Tt_spec_props *argp, SVCXPRT * /* transp */)
{
static const char *here = "_tt_gettype_1()";
static _Tt_spec_props res;
static _Tt_prop props;
_Tt_string propname((unsigned char *)argp->props.props_val[0].propname.propname_val,
argp->props.props_val[0].propname.propname_len);
int isfd = argp->isfd;
if (!_tt_check_stale_isfd(isfd)) {
res.result = -1;
res.iserrno = ERPC;
_tt_syslog(errstr, LOG_ERR, "%s: _tt_check_stale_isfd() == 0",
here );
}
memset(_tt_record, 0, sizeof(_tt_record));
memcpy(_tt_record, argp->key, argp->key_len);
res.result = isstart(isfd, argp->key, argp->key_len,
_tt_record, ISEQUAL);
res.iserrno = iserrno;
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isstart(): %d", here, iserrno);
return (&res);
}
res.result = isread(isfd, _tt_record, ISNEXT);
if (res.result == -1) {
_tt_syslog(errstr, LOG_ERR, "%s: isread(): %d", here, iserrno);
return (&res);
}
res.props.props_len = 1;
res.props.props_val = &props;
props.value.value_val = &_tt_record[OID_KEY_LENGTH+MAX_PROP_LENGTH];
props.value.value_len = isreclen-MAX_PROP_LENGTH-OID_KEY_LENGTH;
props.recnum = 0;
props.propname.propname_val = 0;
props.propname.propname_len = 0;
res.iserrno = iserrno;
return (&res);
}
//
// Go through the _tt_db_table and check on each file.
// Then compresse the files (empty records deleted).
//
void
isgarbage_collect()
{
register int offset;
_Tt_string dbName;
for (offset = 0; offset < _TT_MAX_ISFD; offset++) {
#ifdef OPT_GARBAGE_THREADS
//
// Allow an RPC process to run until it exits.
//
UNLOCK_RPC();
thr_yield();
LOCK_RPC();
#endif
// Don't mess with open files!
if (_tt_db_table[offset].db_path.len() > 0) {
//
// Save name as cached_isclose() removes the
// name from the table.
//
dbName = _tt_db_table[offset].db_path;
cached_isclose(offset);
isgarbage(_tt_db_table[offset].db_path);
}
}
return;
}
int
cached_isopen(const char *filepath, int mode)
{
// First search for an available open isfd for this file,
// reuse it if found.
int i, isfd, lru_fd;
_Tt_string fp(filepath);
_Tt_db_info *p;
for (i=0; i<_TT_MAX_ISFD; i++) {
p = _tt_db_table+i;
if (!p->client_has_open &&
p->db_path == fp &&
p->open_mode == mode) {
p->client_has_open = 1;
p->reftime = _tt_refclock++;
return i;
}
}
// No suitable reusable isfd was found. Try opening a new one.
// If we can't open one because they're all in use (ETOOMANY),
// close the least-recently-used one and try opening again.
for (;;) {
isfd = isopen(filepath, mode);
if (isfd!=-1) {
// Opened fine.
p = _tt_db_table+isfd;
p->db_path = fp;
p->open_mode = mode;
p->client_has_open = 1;
p->server_has_open = 1;
p->reftime = _tt_refclock++;
return isfd;
} else if (iserrno!=ETOOMANY) {
// some REAL problem..
//
return isfd;
}
// find the least-recently-used isfd and close it.
// Note that any fd a client has open is considered
// more recently used than any fd a client doesn't have
// open; this prevents us from giving away fd's a client
// thinks it has open, unless we're really in desparate
// straits. It would be nice to completely decouple the
// "fd" numbers we hand back from the isfd's, so that
// stale fd's could be more reliably distinguished.
lru_fd = -1;
for (i = 0; i<_TT_MAX_ISFD; i++) {
if (!_tt_db_table[i].server_has_open) {
// we never opened this one, so we better
// not close it.
continue;
}
if (lru_fd < 0) {
lru_fd = i;
}
if (_tt_db_table[i].reftime<
_tt_db_table[lru_fd].reftime) {
// this entry is older, check open status
if (_tt_db_table[lru_fd].client_has_open ||
!_tt_db_table[i].client_has_open) {
lru_fd = i;
}
} else {
// this entry is newer, but still check
// open status
if (_tt_db_table[lru_fd].client_has_open &&
!_tt_db_table[i].client_has_open) {
lru_fd = i;
}
}
}
isclose(lru_fd);
// mark cache entry invalid in case open fails.
_tt_db_table[lru_fd].client_has_open = 0;
_tt_db_table[lru_fd].server_has_open = 0;
_tt_db_table[lru_fd].db_path = "";
}
}
int
cached_isclose(int isfd)
{
int isrc;
if (-1 == isfd) return ISERROR;
if (!_tt_db_table[isfd].server_has_open) {
// The open was not cached (perhaps because it was an
// isbuild instead of an isopen.) So do a real
// close.
isrc = isclose(isfd);
// mark cache entry invalid (it probably was already.)
_tt_db_table[isfd].client_has_open = 0;
_tt_db_table[isfd].server_has_open = 0;
_tt_db_table[isfd].db_path = "";
return isrc;
}
// Ensure everything gets written out.
isrc = isfsync(isfd);
// Mark cache entry as reusable
_tt_db_table[isfd].client_has_open = 0;
return isrc;
}
int find_endstring(const char *string, const char *end_string)
{
int sl = strlen(string);
int esl = strlen(end_string);
if (sl >= esl) {
if (!strncmp(string+sl-esl, end_string, esl)) {
return sl-esl;
}
}
return -1;
}
// See if the message queue is locked or should be locked...
//
// Args: file descriptor, record buffer, record length, RPC client info
//
bool_t msg_q_lock(int isfd, const char *record, int length, SVCXPRT *transp)
{
_Tt_string db_file = _tt_db_table [isfd].db_path;
// If this is not the property table then we don't care about it
if (!db_file.sh_match("*property_table")) {
return TRUE;
}
// See if this is a _TT_QUEUED_MSGS property access...
// (with a 16 byte key, the rec length must be at least 31)
if ((length > 30) && !memcmp(record+16, "_TT_QUEUED_MSGS", 15)) {
// Get the address of the client
#if defined(OPT_TLI)
netbuf *client_address = svc_getrpccaller(transp);
_Tt_string client_id(client_address->len);
memcpy((char *)client_id, client_address->buf, client_address->len);
#else
struct sockaddr_in *client_address = svc_getcaller(transp);
_Tt_string client_id(sizeof(struct in_addr));
memcpy( (char *)client_id, &client_address->sin_addr,
sizeof(struct in_addr) );
#endif
// Get the file key
_Tt_string file_key(16);
(void)memcpy((char *)file_key, record, 16);
// Set a lock for this client and file key and lock the message queue
// for new DB clients
_Tt_db_msg_q_lock lock;
return lock.testAndSetLock(client_id, file_key);
}
// Else, see if this is a _TT_MSG_<ID#>_<PART#> property access
// (with a 16 byte key, the rec length must be at least 27)
else if ((length > 26) && !memcmp(record+16, "_TT_MSG_", 8)) {
// Get the address of the client
#if defined(OPT_TLI)
netbuf *client_address = svc_getrpccaller(transp);
_Tt_string client_id(client_address->len);
(void)memcpy((char *)client_id, client_address->buf, client_address->len);
#else
struct sockaddr_in *client_address = svc_getcaller(transp);
_Tt_string client_id(sizeof(struct in_addr));
memcpy( (char *)client_id, &client_address->sin_addr,
sizeof(struct in_addr) );
#endif
// Get the file key
_Tt_string file_key(16);
(void)memcpy((char *)file_key, record, 16);
// Unlock the message queue for new DB clients
_Tt_db_msg_q_lock lock;
lock.unsetLock(client_id, file_key);
}
return TRUE;
}
_Tt_string map_old_db_to_new_db(const char *old_db)
{
_Tt_string new_db(old_db);
int index;
if ((index = find_endstring(old_db, _TT_DOCOID_PATH)) > 0) {
new_db = get_new_db(new_db, index, TT_DB_FILE_TABLE_FILE);
}
else if ((index = find_endstring(old_db, _TT_OID_CONTAINER)) > 0) {
new_db = get_new_db(new_db, index, TT_DB_FILE_OBJECT_MAP_FILE);
}
else if ((index = find_endstring(old_db, _TT_OID_PROP)) > 0) {
new_db = get_new_db(new_db, index, TT_DB_PROPERTY_TABLE_FILE);
}
else if ((index = find_endstring(old_db, _TT_OID_ACCESS)) > 0) {
new_db = get_new_db(new_db, index, TT_DB_ACCESS_TABLE_FILE);
}
else {
new_db = (char *)NULL;
}
return new_db;
}
// Takes the old DB path and table name, the index of where the table name
// starts in the path and the new table name to replace the old name
// with. It returns the path with the new table name. This function also
// handles the renaming of the old DB table to the new name and the change
// of format in the access table.
_Tt_string get_new_db(_Tt_string old_db, int index, _Tt_string new_file)
{
static bool_t old_db_diagnostic = FALSE;
_Tt_string new_db(old_db);
new_db = new_db.left(index);
if (new_db [new_db.len()-1] != '/') {
new_db = new_db.cat("/");
}
new_db = new_db.cat(new_file);
_Tt_string new_db_index(new_db);
new_db_index = new_db_index.cat(".ind");
_Tt_string old_db_index(old_db);
old_db_index = old_db_index.cat(".ind");
struct stat stat_buf;
int temp_errno = 0;
// If there is no new DB and there is an old DB...
if (stat((char *)new_db_index, &stat_buf) &&
((temp_errno = errno) == ENOENT) &&
!stat((char *)old_db_index, &stat_buf)) {
// If the file being accessed is the access_table...
if (new_file == TT_DB_ACCESS_TABLE_FILE) {
_Tt_isam_file_ptr old_db_table =
new _Tt_isam_file(old_db, ISFIXLEN+ISINOUT+ISEXCLLOCK);
_Tt_isam_file_ptr access_table;
int results = old_db_table->getErrorStatus();
if (!results) {
_Tt_isam_key_descriptor_ptr access_key = new _Tt_isam_key_descriptor;
access_key->addKeyPart(0, TT_DB_KEY_LENGTH, BINTYPE);
access_table =
new _Tt_isam_file(new_db,
TT_DB_KEY_LENGTH+3*TT_DB_LONG_SIZE,
TT_DB_KEY_LENGTH+3*TT_DB_LONG_SIZE,
access_key,
ISFIXLEN+ISINOUT+ISEXCLLOCK);
(void)access_table->writeMagicString(_Tt_string(TT_DB_VERSION));
results = access_table->getErrorStatus();
}
if (!results) {
_Tt_isam_record_ptr new_record = access_table->getEmptyRecord();
_Tt_isam_record_ptr record;
while (!results) {
record = old_db_table->readRecord(ISNEXT);
results = old_db_table->getErrorStatus();
if (!results) {
memset((char *)new_record->getRecord(),
'\0',
new_record->getLength());
memcpy((char *)new_record->getRecord(),
(char *)record->getRecord(),
TT_DB_KEY_LENGTH);
short n_user = *(short *)
((char *)record->getRecord()+TT_DB_KEY_LENGTH);
long user = (long)ntohs(n_user);
u_long nl_user = htonl(user);
memcpy((char *)new_record->getRecord()+TT_DB_ACCESS_USER_OFFSET,
(char *)&nl_user,
TT_DB_LONG_SIZE);
short n_group = *(short *)
((char *)record->getRecord()+
TT_DB_KEY_LENGTH+TT_DB_SHORT_SIZE);
long group = (long)ntohs(n_group);
u_long nl_group = htonl(group);
memcpy((char *)new_record->getRecord()+TT_DB_ACCESS_GROUP_OFFSET,
(char *)&nl_group,
TT_DB_LONG_SIZE);
short n_mode = *(short *)
((char *)record->getRecord()+
TT_DB_KEY_LENGTH+2*TT_DB_SHORT_SIZE);
long mode = (long)ntohs(n_mode);
u_long nl_mode = htonl(mode);
memcpy((char *)new_record->getRecord()+TT_DB_ACCESS_MODE_OFFSET,
(char *)&nl_mode,
TT_DB_LONG_SIZE);
results = access_table->writeRecord(new_record);
}
}
if (results == EENDFILE) {
results = 0;
}
}
if (!results) {
(void)iserase((char *)old_db);
}
if (results) {
new_db = (char *)NULL;
}
}
// Else, everything other than the access table is just renamed...
else {
if (isrename((char *)old_db, (char *)new_db)) {
new_db = (char *)NULL;
}
if (!new_db.is_null()) {
_Tt_isam_file_ptr new_db_table =
new _Tt_isam_file(old_db, ISFIXLEN+ISINOUT+ISEXCLLOCK);
int results = new_db_table->getErrorStatus();
if (results) {
new_db_table = new _Tt_isam_file(old_db, ISVARLEN+ISINOUT+ISEXCLLOCK);
results = new_db_table->getErrorStatus();
}
if (!results) {
(void)new_db_table->writeMagicString(TT_DB_VERSION);
}
}
}
}
// The stat on the new DB generated a nasty error...
else if (temp_errno && (temp_errno != ENOENT)) {
new_db = (char *)NULL;
}
// Old and new DB files exist...
else if (!stat((char *)old_db_index, &stat_buf)) {
if (old_db_diagnostic == FALSE) {
_tt_syslog(errstr, LOG_ERR,
catgets(_ttcatd, 5, 4,
"Any data written using an old (<= 1.0.2) "
"rpc.ttdbserverd after using a new (>= 1.1) "
"rpc.ttdbserverd will be ignored"));
old_db_diagnostic = TRUE;
}
}
return new_db;
}