archie/prospero/lib/psrv/dsrobject.c
2024-05-27 16:13:40 +02:00

556 lines
21 KiB
C

/*
* Copyright (c) 1993 by the University of Southern California
*
* For copying and distribution information, please see the file
* <usc-license.h>.
*/
#include <usc-license.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pmachine.h>
#ifndef SOLARIS /* if not posix, need MAXPATHLEN still */
#include <sys/param.h>
#endif
#include <pserver.h> /* For DIRECTORYCACHING and for PLOG
overrides. */
#include <pfs.h>
#include <perrno.h>
#include <psrv.h>
#include <plog.h>
#ifdef DSROBJECT_SPEEDUP_IS_EXPERIMENTAL
int dsrobject_speedup = 1; /* speed up DSROBJECT. This can be set to 0 to
use the old code. These changes made by SWA
during the week of 5/9/94 */
#endif
static PATTRIB newamat(void);
#ifdef DIRECTORYCACHING
/* One day */
#define SECONDSPERDAY (60*60*24)
#define MAXDIRCACHEAGE (1*SECONDSPERDAY)
int cache_attempt = 0; int cache_can = 0; int cache_yes = 0;
/* Used to see how many times dsrobject() failed in a retrieval request. */
int dsrobject_fail = 0;
int
vdir_outofdate(VDIR dir, char *hsoname)
{
char native_dirname[MAXPATHLEN];
char vfs_dirname[MAXPATHLEN];
nativize_prefix(hsoname, native_dirname, sizeof native_dirname);
strcpy(vfs_dirname,shadow);
strcat(vfs_dirname,native_dirname);
strcat(vfs_dirname,"/");
strcat(vfs_dirname,dircont);
return (stat_age(vfs_dirname) > MAXDIRCACHEAGE);
}
#endif /*DIRECTORYCACHING */
/* Union links are expanded at a higher level.
* The only message dsrobject() sends to the client is an ardp_rwait().
* dsrobject() logs NOT-AUTHORIZED failures via plog, but doesn't send messages
to the client; the caller does that.
* Checks to make sure hsoname is within the space we're allowed to discuss.
* Does check to make sure you have r, G, or g rights on a database before it
lets you scan that database for information.
*/
/* flags honored::
DRO_VERIFY_DIR
*/
/*
Returns:
DSRFINFO_FORWARDED
DIRSRV_NOT_AUTHORIZED
DIRSRV_NOT_FOUND
PFAILURE
PSUCCESS
*/
/* If called with the DRO_VERIFY flag, will set the P_OBJECT_FILE and
P_OBJECT_DIRECTORY flags in the ob structure, but will not *necessarily* put
any other information into the ob structure. Caller is still responsible
for freeing any links pointed to by the ob structure. */
/* If called with the DRO_VERIFY_DIR flag, will return PSUCCESS only if object
is a directory. Will not necessarily put any other information into the OB
structure. Caller is still responsible for freeing any links pointed to by
the ob structure. */
/* dsrobject(), if it returns anything other than DSRFINFO_FORWARDED or
PSUCCESS, may leave the ob structure with arbitrary data in it. The caller
should not expect the ob structure to remain untouched. */
/* At the moment, the only option we look at in dsrobject_list_options is the
requested_attributes one, and that only to test for the CONTENTS
attribute. */
/* The database ACL itself may be obtained by using an HSONAME that is just the
root prefix of the database. */
int requested_contents(struct dsrobject_list_options *listopts);
PATTRIB read_contents(char hsoname[]);
int
dsrobject(RREQ req, char hsoname_type[], char hsoname[], long version,
long magic_no,
int flags, struct dsrobject_list_options *listopts, P_OBJECT ob)
{
VDIR_ST dir_st;
register VDIR dir = &dir_st;
register PFILE fi = NULL; /* Only set if it needs to be. */
int retval; /* Integer return value from subfunctions */
int dirretval = PFAILURE; /* Save initial dsrdir result*/
#ifdef DIRECTORYCACHING
int cancache = TRUE;
#endif
#ifdef DSROBJECT_SPEEDUP_IS_EXPERIMENTAL
if (!dsrobject_speedup)
flags &= ~DRO_VERIFY; /* if not speedup, turn off DRO_VERIFY flag. */
#endif
VLDEBUGBEGIN;
vdir_init(dir); /* empty directory. */
set_client_addr(req->peer_addr.s_addr); /* temporary hack. */
/* Check whether HSONAME-TYPE and HSONAME are valid. */
if (!strequal(hsoname_type, "ASCII")) {
plog(L_DIR_ERR, req, "dsrobject(): got invalid hsoname-type: %s",
hsoname_type, 0);
vdir_freelinks(dir);
RETURNPFAILURE;
}
if (check_handle(hsoname) == FALSE) {
plog(L_AUTH_ERR, req, "Got an HSONAME outside the part of the \
filesystem that this server is authorized to publish information about: %s",
hsoname);
/* Free the directory links */
vdir_freelinks(dir);
return DIRSRV_NOT_AUTHORIZED;
}
if ( *hsoname != '/') {
/* Database or special prefix in use */
register int i;
#ifdef DIRECTORYCACHING /* mitracode */
cache_attempt++;
/* SWA bug fix: this assumes listopts is always set, which it need not
be. */
/* mitra: Can not cache if specifying a component (other than"*") */
/* swa added: Can not cache if #ALL attributes not set. This prevents
us from blowing it badly. */
if (!listopts
|| (listopts->thiscompp && strcmp(*(listopts->thiscompp),"*"))
|| !listopts->req_link_ats.all)
cancache = FALSE;
if (cancache) {
cache_can++;
VLDEBUGBEGIN;
if (!(dirretval
= dsrdir(hsoname, magic_no, dir, NULL, DSRD_ATTRIBUTES)))
cache_yes++;
VLDEBUGDIR(dir);
VLDEBUGEND;
}
#endif
if (dirretval != PSUCCESS
#ifdef DIRECTORYCACHING
|| vdir_outofdate(dir,hsoname)
#endif
|| !(dir->links) ) {
for (i = 0; i < db_num_ents; i++) {
if (strnequal(hsoname, db_prefixes[i].prefix,
strlen(db_prefixes[i].prefix))
&& strequal(db_prefixes[i].hsoname_type, hsoname_type)) {
ACL dbacl;
VLDEBUGBEGIN;
get_named_acl(db_prefixes[i].named_acl, &dbacl);
VLDEBUGEND;
if (dbacl
&& !srv_check_acl(dbacl, NULL, req, "r",
SCA_LINKDIR,db_prefixes[i].prefix,NULL)
&& !srv_check_acl(dbacl, NULL, req, "g",
SCA_LINKDIR,db_prefixes[i].prefix,NULL)
&& !srv_check_acl(dbacl, NULL, req, "G",
SCA_LINKDIR,db_prefixes[i].prefix,NULL)) {
plog(L_AUTH_ERR, req,
"Unauthorized database request: %s %s",
hsoname, listopts? *(listopts->thiscompp) : "");
vdir_freelinks(dir);
return DIRSRV_NOT_AUTHORIZED;
}
/* This could take a while, tell client not to retry.
This should be customized on a per-database basis. */
ardp_rwait(req, 180, 0, 0);
VLDEBUGBEGIN;
retval= db_prefixes[i].read_function(req, hsoname,
version, magic_no, flags, listopts, ob);
VLDEBUGOB(ob);
VLDEBUGEND;
#ifdef DIRECTORYCACHING
if (retval == PSUCCESS && ob->links) {
if (cancache) {
VLDEBUGBEGIN;
dswobject(hsoname_type,hsoname,ob);
VLDEBUGEND;
}
vdir_freelinks(dir);
return(retval);
} else if (dirretval == PSUCCESS) {
/* Must just have been old*/
/*drop through and reread dir and file*/
break;
} else { /* Neither old nor new version available */
/* Or directory is really empty */
vdir_freelinks(dir);
return(retval);
}
#else
vdir_freelinks(dir);
return retval;
#endif /*DIRECTORYCACHING*/
}
} /*for*/
} /*PSUCCESS*/
/* Here we have a prefix that is not a database, */
/* but not a normal filename either */
/* If normal file names need not begin with / */
/* fall through and try a normal query */
}
/* Local directories can have associated finfo too. */
if (dirretval == PSUCCESS) { /* No need to reread if did already */
retval = dirretval;
} else {
VLDEBUGBEGIN;
retval = dsrdir(hsoname, magic_no, dir, NULL,
/* Set DSRD_VERIFY_DIR iff DRO_VERIFY_DIR is set. */
((flags & DRO_VERIFY_DIR) ? DSRD_VERIFY_DIR : 0)
/* Note that dsrdir() curently always returns all
object attributes. This costs us some potentially
unnecessary MALLOC()s. */
| ((listopts && listopts->requested_attrs) ?
DSRD_ATTRIBUTES : 0));
VLDEBUGDIR(dir);
VLDEBUGEND;
}
if (retval == PSUCCESS) ob->flags |= P_OBJECT_DIRECTORY;
if (retval == PSUCCESS || retval == DSRDIR_NOT_A_DIRECTORY ||
retval == DIRSRV_NOT_DIRECTORY || retval == DIRSRV_NOT_FOUND) {
int dsrfinfo_retval;
#ifdef SERVER_DO_NOT_SUPPORT_FORWARDING
/* Don't bother with the dsrfinfo() unless a specific attribute or set
of attributes was requested, or unless a directory was not found. */
if (listopts->req_obj_ats.all
|| listopts->req_obj_ats.interesting
|| listopts->req_obj_ats.specific) {
fi = pfalloc();
dsrfinfo_retval = dsrfinfo_with_attribs(hsoname, magic_no, fi,
&listopts->req_obj_ats);
} else {
dsrfinfo_retval = PFAILURE;
}
#else
VLDEBUGBEGIN;
dsrfinfo_retval = dsrfinfo(hsoname,magic_no,fi);
VLDEBUGFI(fi);
VLDEBUGEND;
if (dsrfinfo_retval == DSRFINFO_FORWARDED) {
ob->inc_native = VDIN_MUNGED;
ob->forward = fi->forward; fi->forward = NULL;
pffree(fi);
vdir_freelinks(dir);
return dsrfinfo_retval;
}
#endif
if (dsrfinfo_retval < 0) { /* dsrfinfo returns <0 to mean
directory */
ob->flags |= P_OBJECT_DIRECTORY;
retval = PSUCCESS;
} else if (dsrfinfo_retval == PSUCCESS) {
ob->flags |= P_OBJECT_FILE;
if (requested_contents(listopts)) {
PATTRIB at = read_contents(hsoname);
/* Append it to fi->attributes since ob->attributes will get
set from that. */
if (at) APPEND_ITEM(at, fi->attributes);
}
retval = PSUCCESS;
}
}
#ifndef SERVER_DO_NOT_SUPPORT_FORWARDING
if (retval == DSRFINFO_FORWARDED) {
/* Only get here if dsrdir() or dsdb() returned DSRFINFO_FORWARDED */
ob->inc_native = VDIN_MUNGED;
ob->forward = dir->f_info->forward; dir->f_info->forward = NULL;
vdir_freelinks(dir);
return retval;
}
#endif
if (retval) {
vdir_freelinks(dir);
if (fi) pffree(fi);
if (retval == DIRSRV_NOT_DIRECTORY || retval == DSRDIR_NOT_A_DIRECTORY
|| retval == DSRFINFO_NOT_A_FILE)
return DIRSRV_NOT_FOUND;
return retval;
}
/* Now for some manly calisthenics!
Merge f_info and directory info. */
/* Note this is the model for the VERSION5 version dswobject
so, if you change this, probably need to change that - Mitra */
/* Only potential conflict here: ACL. Handle it by appending the two
ACLs if both are set. (DIRECTORY first; why not). If one is unset,
use the default (DEFAULT SYSTEM for DIRECTORY, CONTAINER for OBJECT).
If directory and object both have magic numbers set use one from
directory.
*/
if (ob->flags & P_OBJECT_DIRECTORY) {
ob->version = dir->version; /* always 0 */
ob->inc_native = dir->inc_native;
ob->magic_no = dir->magic_no;
ob->acl = dir->dacl; dir->dacl = NULL;
assert(!dir->f_info); /* should have already been handled */
ob->links = dir->links; dir->links = NULL;
ob->ulinks = dir->ulinks; dir->ulinks = NULL;
ob->native_mtime = dir->native_mtime;
} else {
ob->inc_native = VDIN_NOTDIR;
}
/* If FINFO present (will not always need to be present), merge it in. */
if (fi) {
/* Magic # on directory, if present, supersedes magic # on finfo. */
if (!ob->magic_no) ob->magic_no = fi->f_magic_no;
/* Append object ACLs. */
if (fi->oacl) {
CONCATENATE_LISTS(ob->acl, fi->oacl); fi->oacl = NULL;
}
ob->exp = fi->exp;
ob->ttl = fi->ttl;
ob->last_ref = fi->last_ref;
ob->forward = fi->forward; fi->forward = NULL;
ob->backlinks = fi->backlinks; fi->backlinks = NULL;
ob->attributes = fi->attributes; fi->attributes = NULL;
/* Done merging in attributes from FINFO structure. */
}
/* Clean up and return. */
vdir_freelinks(dir);
if (fi) pffree(fi); fi = NULL;
VLDEBUGOB(ob);
VLDEBUGEND;
return retval;
}
/* Return a list of OBJECT INTRINSIC ACCESS-METHOD attributes for the local
real file FILENAME. These attributes should be usable by the client coming
from the address client_addr. One day this function will no longer be
necessary when we have the Prospero access method working.
Allocates pattribs and puts them into RETVAL.
Note that FILENAME has already been expanded from any HSONAME.
*/
/* Called by dsrfinfo at the moment. */
void
get_access_method(char filename[], long client_addr, PATTRIB *retval)
{
PATTRIB at; /* temporary working attribute. */
TOKEN nfs_am; /* Access method for NFS (if any) */
*retval = NULL; /* return list starts empty. */
/* Check for PROSPERO-CONTENTS access method */
/* Always true for any local file. */
at = newamat();
at->value.sequence = tkappend("PROSPERO-CONTENTS", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
APPEND_ITEM(at, *retval);
/* Check for LOCAL access method. */
if (myaddress() == client_addr ||
/* Check for loopback net. */
#if BYTE_ORDER == BIG_ENDIAN
(client_addr & 0xff000000) == (127 << 24)
#else
(client_addr & 0x000000ff) == 127
#endif
) {
/* This may not return information for multi-homed hosts.
But it will never return incorrect information. */
at = newamat();
at->value.sequence = tkappend("LOCAL", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
APPEND_ITEM(at, *retval);
} else {
#ifdef SHARED_PREFIXES
char *cp; /* this memory doesn't need to be freed. */
if (cp = check_localpath(filename, client_addr)){
at = newamat();
at->value.sequence = tkappend("LOCAL", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend(cp, at->value.sequence);
APPEND_ITEM(at, *retval);
}
#endif
}
/* Check for NFS access method. */
#ifdef NFS_EXPORT
if(nfs_am = check_nfs(filename,client_addr)) {
at = newamat();
at->value.sequence = nfs_am;
APPEND_ITEM(at, *retval);
}
#endif NFS_EXPORT
/* Check for AFS access method. Note that the hostname is irrelevant. */
if(*afsdir && strnequal(filename, afsdir, strlen(afsdir))) {
char *suffix = filename + strlen(afsdir);
at = newamat();
at->value.sequence = tkappend("AFS", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("", at->value.sequence);
at->value.sequence = tkappend("ASCII", at->value.sequence);
at->value.sequence = tkappend(suffix, at->value.sequence);
APPEND_ITEM(at, *retval);
#ifdef AFS_AFTP_GATEWAY /* provide additional access method for sites
that don't run AFS. */
at = newamat();
at->value.sequence = tkappend("AFTP", at->value.sequence);
at->value.sequence = tkappend("INTERNET-D", at->value.sequence);
at->value.sequence = tkappend(hostname, at->value.sequence);
at->value.sequence = tkappend("ASCII", at->value.sequence);
at->value.sequence =
tkappend(qsprintf_stcopyr((char *) NULL,
"%s%s", AFS_AFTP_GATEWAY, suffix),
at->value.sequence);
at->value.sequence = tkappend("BINARY", at->value.sequence);
APPEND_ITEM(at, *retval);
#endif
}
/* Check for AFTP access method. */
if(*aftpdir && strnequal(filename,aftpdir, strlen(aftpdir))) {
char *suffix = filename + strlen(aftpdir);
at = newamat();
at->value.sequence = tkappend("AFTP", at->value.sequence);
at->value.sequence = tkappend("INTERNET-D", at->value.sequence);
at->value.sequence = tkappend(hostname, at->value.sequence);
at->value.sequence = tkappend("ASCII", at->value.sequence);
at->value.sequence = tkappend(suffix, at->value.sequence);
at->value.sequence = tkappend("BINARY", at->value.sequence);
APPEND_ITEM(at, *retval);
}
}
/* Allocate a new ACCESS-METHOD attribute. */
static
PATTRIB
newamat(void)
{
PATTRIB retval = atalloc();
retval->aname = stcopyr("ACCESS-METHOD", retval->aname);
retval->precedence = ATR_PREC_OBJECT;
retval->nature = ATR_NATURE_INTRINSIC;
retval->avtype = ATR_SEQUENCE;
return retval;
}
/* Was the CONTENTS attribute requested on the object itself? CONTENTS is an
INTRINSIC- attribute. Return non-zero (C language "true") if true, zero (C
language "false") if false. */
int
requested_contents(struct dsrobject_list_options *listopts)
{
return listopts
&& was_attribute_requested("CONTENTS", &listopts->req_obj_ats);
}
/* Return the CONTENTS attribute. */
PATTRIB
read_contents(char hsoname[])
{
char *filename;
PATTRIB retval;
int fd; /* file descriptor to read from. */
struct stat file_stat_st;
struct stat * file_stat = &file_stat_st;
TOKEN tk; /* working token */
/* Code modified from dsrfinfo() */
/* Expand special file names */
if((*hsoname != '/') && *aftpdir && strnequal(hsoname,"AFTP",4))
filename = qsprintf_stcopyr("%s%s", aftpdir, hsoname + 4);
else
filename = stcopy(hsoname);
fd = open(filename, 0); /* open for reading */
if (fd < 0) {
stfree(filename);
return NULL;
}
if (stat(filename,file_stat) != 0) {
close(fd);
stfree(filename);
return NULL;
}
retval = atalloc();
retval->aname = stcopyr("CONTENTS", retval->aname);
retval->precedence = ATR_PREC_OBJECT;
retval->nature = ATR_NATURE_INTRINSIC;
retval->avtype = ATR_SEQUENCE;
/* Build a two-element sequence, DATA and a byte stream of the data. */
retval->value.sequence = tkappend("DATA", retval->value.sequence);
tk = tkalloc(NULL);
/* allocate one extra space for the trailing null. */
tk->token = stalloc(file_stat->st_size + 1);
/* tk->token is the buffer we fill in. It remains a valid reference even
after the APPEND_ITEM. We call APPEND_ITEM() first so that it's easy
to abort in case of an error (one less freeing operation to call). */
APPEND_ITEM(tk, retval->value.sequence);
/* Read whole contents and close the file. If either fails, don't set the
CONTENTS attribute. */
/* This might fail IF the file size changes between the stat() and the
read. Oh well. */
if(read(fd, tk->token, file_stat->st_size) != file_stat->st_size
|| close(fd)) {
atfree(retval);
close(fd);
return NULL;
}
p_bst_set_buffer_length_nullterm(tk->token, file_stat->st_size);
/* Automatically null terminates it for us too. */
return retval;
}