blob: d910b41a545fba0f00f622add6430dc0a0b154de [file] [log] [blame]
// MorgenGrauen MUDlib
//
// thing/properties.c -- most general class (property handling)
//
// $Id: properties.c 6951 2008-08-09 23:08:31Z Zesstra $
// Properties.c -- Propertyverwaltung
// (c) 1993 Hate@MorgenGrauen, Mateese@NightFall
// Idea and Code Flames and Destructions
// -> *grin* thats the point actually :)
//
// Ueberarbeitet von Jof am 12.06.1994
// Ueberarbeitet von Mandragon am 11.05.2003
#pragma strict_types
#pragma save_types
#pragma range_check
#pragma no_clone
#include <driver_info.h>
#define NEED_PROTOTYPES
#include "/sys/thing/properties.h"
#include "/secure/wizlevels.h"
// the mapping where the actual properties are stored. Direct initialization.
// Indexed with F_VALUE, F_MODE, F_SET_METHOD, F_QUERY_METHOD, F_VALIDATOR
// F_MODE, F_SET_METHOD, F_QUERY_METHOD and F_VALIDATOR are usually 'sparse'
// (i.e. there is no entry for most properties), therefore it is more
// memory-efficient to store them like this than in one mapping, although it
// requires more mapping lookups.
private nosave mapping *prop = allocate(F_PROP_ENTRIES, ([]));
// the mapping that is used for saving. During save_object/restore_object it
// contains the properties with SAVE flag.
// This is empty outside of a save_object() or restore_object() call!
private mapping properties;
// Z.Zt. nur Abschalten des Resets noetig.
protected void create() {
// Blueprints in /std benoetigenkeinen Reset ....
if (object_name()=="/std/thing/properties")
set_next_reset(-1);
}
protected void create_super() {
set_next_reset(-1);
}
// Prueft, ob ein Zugriff auf eine Property erlaubt ist.
// Properties, die SECURED oder PROTECTED sind, duerfen nur vom Objekt
// selber, EM+ oder ROOT veraendert werden.
#define SAVE_OBJECT 1
#define PROP_SET 2
#define PROTECTED_SET 3
#define PROTECTED_DELETE 4
#define SECURED_SET 5
#define SECURED_DELETE 6
#define RAW_ACCESS 7
nomask private int prop_check_privilege(int drop_priv, int mode,
int op=PROP_SET)
{
// Interne Calls oder eigenes Objekt (als letzter 'externer' Aufrufer via
// Callother) darf alles
if (!drop_priv || previous_object() == this_object())
return 1;
// Ab hier nur noch fremde Objekte
switch(op)
{
case SAVE_OBJECT:
// Eigentlich nur vom jeweils aktiven simul_efun-Objekt gerufen, aber
// der einfacheit halber sind alle ROOT-Objekte erlaubt. Aber nur
// direkten Caller pruefen, *nicht* ROOT_SECURITY!
if (geteuid(previous_object()) == ROOTID)
return 1;
case SECURED_DELETE:
// Das SECURED-Flag darf bei Properties nicht mehr geloescht werden
return 0;
case SECURED_SET:
if (geteuid(previous_object()) == ROOTID || ARCH_SECURITY)
return 1;
case RAW_ACCESS:
// RAW_ACCESS grants access to all properties and their raw, uncopied
// data. It is only for very specific objects and they *MUST NOT* change
// it or give user-code (or any other) access to it.
if (load_name(previous_object()) == "/obj/tools/MGtool")
return 1;
return 0;
case PROTECTED_DELETE:
case PROTECTED_SET:
case PROP_SET:
// Properties, die schon SECURED oder PROTECTED sind, duerfen
// nur vom Objekt selber manipuliert werden
if (!(mode & (PROTECTED|SECURED)) // nicht geschuetzt
|| (geteuid(previous_object()) == ROOTID || ARCH_SECURITY))
return 1;
}
return 0;
}
// Set() -- provides direct access to a property, no filters
// Type=F_VALUE und drop_priv=extern_call() by default
public mixed Set(string name, mixed Value, int Type, int drop_priv)
{
int mode = prop[F_MODE][name];
// Es ist verfuehrerisch, das 'drop_priv||extern_call()' durch 'drop_priv' zu
// ersetzen, weil extern_call() das default-argument fuer <drop_priv> ist.
// Das ist keine gute Idee, weil <drop_priv> unter der Kontrolle des Aufrufers
// ist und dieser 0 uebergeben kann. Sprich: wenn es 0 ist, muessen wir
// dennoch selber pruefen. Wir glauben aber immer, wenn es 1 ist, der
// Aufrufer hat nichts davon und das eigene Objekt darf Privilegien abgeben.
drop_priv = drop_priv||extern_call();
// Erstmal pruefen, ob Aenderung der Prop erlaubt ist.
if (!prop_check_privilege(drop_priv, mode, PROP_SET))
return -1;
// Bemerkung: aktuell koennen alle Objekte PROTECTED setzen. Loeschen
// koennen es ROOT, EM+ und ME ueber den Check oben.
// Soll SECURED geloescht werden und ist das erlaubt?
if ( (Type==F_MODE||Type==F_MODE_AD) && (Value & SECURED)
&& (prop[F_MODE][name] & SECURED)
&& !prop_check_privilege(drop_priv, mode, SECURED_DELETE))
return -2;
// Soll SECURED gesetzt werden und ist das erlaubt?
if ( (Type==F_MODE||Type==F_MODE_AS) && (Value&SECURED)
&& !(prop[F_MODE][name] & SECURED)
&& !prop_check_privilege(drop_priv, mode, SECURED_SET) )
return -3;
switch(Type)
{
// Je nach Modus Flags veraendern
case F_MODE_AS:
prop[F_MODE][name]|= Value;
return prop[F_MODE][name];
case F_MODE_AD:
prop[F_MODE][name]&= ~Value;
if (!prop[F_MODE][name]) prop[F_MODE]-=([name]);
return prop[F_MODE][name];
case F_MODE:
prop[F_MODE][name]^= Value;
if (!prop[F_MODE][name]) prop[F_MODE]-=([name]);
return prop[F_MODE][name];
case F_SET_METHOD:
if (!Value)
{
prop[Type]-=([name]);
break;
}
// Ungebundene Lambda_Closure? Heutzutage ein Fehler.
if (closurep(Value) && !query_closure_object(Value))
{
raise_error("Ungebundene Lambdas sind als Setmethoden "
"nicht mehr unterstuetzt.\n");
}
// -1 als Setz-Methode: Stattdessen NOSETMETHOD setzen (deprecated!)
if (Value == -1)
{
prop[F_SET_METHOD]-=([name]);
prop[F_MODE][name] |= NOSETMETHOD;
return 0;
}
// Zur Sicherheit SETMAPPED loeschen
prop[F_MODE][name] &= ~SETMAPPED;
prop[Type][name] = Value;
break;
case F_SET_MAPPER:
// Als Type F_SET_METHOD speichern/eintragen.
Type = F_SET_METHOD;
if (!Value)
{
prop[F_MODE][name] &= ~SETMAPPED;
prop[Type]-=([name]);
break;
}
// Da wir hier keine Ruecksicht auf historischen Kram nehmen muessen,
// werden nur Closures akzeptiert. Und auch keine -1 als Alias fuer
// NOSETMETHOD.
if (!closurep(Value))
{
raise_error("Fuer F_SET_MAPPER werden nur Closures unterstuetzt.\n");
}
// Und keine ungebundenen Lambda-Closures.
if (!query_closure_object(Value))
{
raise_error("Ungebundene Lambdas sind als Mapper "
"nicht unterstuetzt.\n");
}
// SETMAPPED setzen
prop[F_MODE][name] |= SETMAPPED;
prop[Type][name] = Value;
break;
case F_QUERY_METHOD:
if (!Value)
{
prop[Type]-=([name]);
break;
}
// Jetzt wirklich mal nen Fehler werfen und den Zopf abschneiden.
if (!closurep(Value))
{
raise_error("Fuer F_QUERY_METHOD werden nur noch Closures "
"unterstuetzt.\n");
}
// Ungebundene Lambda-Closure? Heutzutage ein Fehler.
if (!query_closure_object(Value))
{
raise_error("Ungebundene Lambdas sind als Querymethoden "
"nicht mehr unterstuetzt.\n");
}
prop[Type][name] = Value;
break;
default:
if (!Value) prop[Type]-=([name]);
else prop[Type][name] = Value;
}
// Gesamtwert zurueckgeben
return prop[Type][name];
}
// Direktes Auslesen der Property ohne Filter ...
// Type = F_VALUE by default
public mixed Query( string name, int Type )
{
if (pointerp(prop)) return prop[Type][name];
return 0;
}
// Property setzen unter Verwendung evtl. vorhandener Zugriffsfunktionen
public mixed SetProp( string name, mixed Value, int drop_priv)
{
mixed result;
int mode = prop[F_MODE][name];
// NOSETMETHOD: Darf nicht gesetzt werden
if (mode & NOSETMETHOD ) return -1;
// Set-Method abfragen, so vorhanden
// TODO: nachdem alle moeglichen Werte als Set-Methode illegal sind, auf
// closure aendern.
mixed func = prop[F_SET_METHOD][name];
if (func)
{
// Wert als Set-Method? gleich zurueckgeben
if (!closurep(func)) return func;
// An dieser Stelle muss func eine Closure sein. Da Set() ungebundene
// Lambdas bindet, kann es auch nur eine gebundene Closure sein und das
// Objekt existiert auch noch (sonst waere func == 0).
// Dann mal die Closure aufrufen. Bei Fehler selbige loeschen
if (catch(result=funcall(func, Value, name);publish))
{
prop[F_SET_METHOD]-=([name]);
}
else // Erfolgreicher call
{
if (mode & SETMAPPED) // SM hat noch nicht selber gesetzt
result = Set( name, result, F_VALUE, drop_priv||extern_call() );
}
// Und zurueckgeben
return result;
}
// _set_*-Methode vorhanden? falls ja, aufrufen.
if (call_resolved(&result,this_object(),"_set_"+name,Value ))
return result;
// Letzte Moeglichkeit: Muss eine 'normale' Property sein
// Es ist verfuehrerisch, das 'drop_priv||extern_call()' durch 'drop_priv'
// zu ersetzen, weil extern_call() das default-argument fuer <drop_priv>
// ist. Das ist keine gute Idee, weil <drop_priv> unter der Kontrolle des
// Aufrufers ist und dieser 0 uebergeben kann. Sprich: wenn es 0 ist,
// muessen wir dennoch selber pruefen. Wir glauben aber immer, wenn es 1
// ist und der Aufrufer Privilegien abgeben will.
return Set( name, Value, F_VALUE, drop_priv||extern_call() );
}
// Property auslesen unter Verwendung evtl. vorhandener Zugriffsfunktionen
public mixed QueryProp( string name )
{
mixed result;
// Query-Methode vorhanden?
mixed func = prop[F_QUERY_METHOD][name];
if (func)
{
// Wert als Query-Method? Gleich zurueckgeben ...
if (!closurep(func)) return func;
// An dieser Stelle muss func eine Closure sein. Da Set() ungebundene
// Lambdas bindet, kann es auch nur eine gebundene Closure sein und das
// Objekt existiert auch noch (sonst waere func == 0).
// Dann Mal die Closure aufrufen. Bei Fehler selbige loeschen
if (catch(result=funcall(func, prop[F_VALUE][name]);publish))
{
prop[F_QUERY_METHOD]-=([name]);
}
// Und zurueckgeben
return result;
}
// _query_*-Methode vorhanden? falls ja, aufrufen.
else if (call_resolved(&result,this_object(),"_query_"+name))
return result;
// Hilft alles nichts. Es ist eine 'normale' Property ...
return prop[F_VALUE][name];
}
// Addiert einen Wert zu einer Prop. Eigentlich ist es nur ein Short-Cut fuer
// QueryProp und += und SetProp. Dementsprechend gehen auch hier nur
// Typ-Kombinationen, die += auch kann.
public mixed AddToProp(string pname,
<int|float|string|mixed*|mapping|bytes> add_value)
{
mixed value = QueryProp(pname);
string err = catch(value += add_value; nolog);
if (err)
raise_error(sprintf("Nicht unterstuetzter Typ des Summanden: %s\n",
err[strstr(err,":")+2..]));
return SetProp(pname, value, extern_call());
}
// "subtrahiert" einen Wert von einer Prop. Eigentlich ist es nur ein
// Short-Cut fuer QueryProp und -= und SetProp. Dementsprechend gehen auch
// hier nur Typ-Kombinationen, die -= auch kann.
public mixed SubFromProp(string pname,
<int|float|string|mixed*|mapping|bytes> sub_value)
{
mixed value = QueryProp(pname);
string err = catch(value -= sub_value; nolog);
if (err)
raise_error(sprintf("Nicht unterstuetzter Typ des Subtrahenden: %s\n",
err[strstr(err,":")+2..]));
return SetProp(pname, value, extern_call());
}
// Viele Properties auf einen Schlag setzen.
// Wurde genutzt von simul_efun zum Setzen aller Properties, welche im
// restore_object() eingelesen wurden.
// Nutzer in Lib: Magiershell (upd), Spellbooks Zaubis, Klerus, Lupe
// Andere objekt-externe Nutzung ausdruecklich **NICHT** supportet!
// Problem: ggf. Verlust von SECURED, wenn Props kopiert werden (im alten
// SECURED; im neuen nicht und nicht-privilegiertes Objekt
// Problem 2: beim Kopieren von einem alten in ein neues Objekt gehen alle
// internen QM/SM auf das eigene Objekt ersatzlos kaputt.
// Problem 3: beim "Clonen" von Objekten haben beide Clones dann geteilte
// Referenztypen wie Mappings/Structs/Arrays
public deprecated void SetProperties( mapping props )
{
int i, j;
// Kein Mapping? Schlecht ...
if(!mappingp(props)) return;
string *names = m_indices(props);
// Das SECURED-Flag darf nur durch das Objekt selber gesetzt werden:
// mode ist hier egal. Antwort cachen fuer den Loop
int no_secure = !prop_check_privilege(extern_call(), 0, SECURED_SET);
j=sizeof(names);
while(j--)
{
if (prop_check_privilege(extern_call(), prop[F_MODE][names[j]],
PROP_SET))
{
// Prop-Setzen erlaubt, aber ggf. SECURED flag loeschen, wenn nicht
// das nicht erlaubt ist (s.o.).
if (no_secure)
props[names[j], F_MODE] &= ~SECURED;
// jetzt die einzelnen Teile der Prop seztzen.
i=F_PROP_ENTRIES;
while(i--)
{
if(props[names[j],i])
prop[i][names[j]] = props[names[j], i];
else
prop[i]-=([names[j]]);
}
}
}
}
// Ein Mapping mit allen Properties zurueckgeben
// Wurde genutzt von simul_efun im save_object() zur Abfrage aller Properties,
// um hieraus die gespeicherten zu bestimmen.
// Nutzer in Lib: Magiershell (upd), Spellbooks Zaubis, Klerus, Lupe,
// Listmaster-Zaubis, Fingerdaemon, Kaempfergilde (Karteikarten)
// Andere objekt-externe Nutzung ausdruecklich **NICHT** supportet!
// TODO fuer zukuenftige Features (wie private Properties) auf die simul_efun
// beschraenken.
// Problem: QueryProperties: die Werte entsprechen Query, nicht QueryProp und
// Abfragende werten die QMs in der Regel nicht aus (und waere auch nicht
// dieselbe Abfragesituation und wuerde daher nicht funktionieren).
// Problem 2: Herausgabe interner Daten wie Closures, Referenztypen wie
// Mappings/Arrays/Structs etc. Diese koennen dann geaendert werden oder im
// Falle von Closures sind ggf. private lfuns rufbar (stimmt: momentan koennen
// die auch per Query abgefragt werden).
public deprecated mapping QueryProperties()
{
mapping props;
int i, j;
string *names;
props = m_allocate( sizeof(prop[F_VALUE]), F_PROP_ENTRIES );
if (pointerp(prop))
{
i=F_PROP_ENTRIES;
while(i--)
{
names = m_indices(prop[i]);
j=sizeof(names);
while(j--) props[names[j], i] = prop[i][names[j]];
}
}
return props;
}
// Die Properties als urspruengliches Array zurueckgeben, wird fuers MG-Tool
// benoetigt.
public mixed *__query_properties()
{
// RAW_ACCESS grants access to all properties and their raw, uncopied data.
// It is only for very specific objects.
if (!prop_check_privilege(extern_call(), 0, RAW_ACCESS))
return 0;
if ( pointerp(prop) )
return prop;
else
return allocate(F_PROP_ENTRIES, ([]));
}
// Wird aus simul_efun (und nur von dort) gerufen, sollte die Properties aufs
// Speichern vorbereiten und alle *gespeicherten* Props in passender Form in
// der gespeicherten Variable <properties> ablegen.
public void _prop_prepare_save()
{
// 2 Werte pro Prop (Wert + Modusflags) speichern
// F_SET_METHOD, F_QUERY_METHOD werden nicht gespeichert.
// Anzahl gespeicherter Props raten, 20% der Props mit Modusflags
properties = m_allocate(sizeof(prop[F_MODE])/5, 2);
foreach(string pname, int mode: prop[F_MODE])
{
if (mode & SAVE)
{
// Uralte, ungenutzte Flags loeschen sowie SETMAPPED (F_SET_METHOD wird
// nicht gespeichert)
properties[pname, F_MODE] = mode &
(~(SETMNOTFOUND|QUERYMNOTFOUND|QUERYCACHED|SETCACHED|SETMAPPED));
properties[pname, F_VALUE] = prop[F_VALUE][pname];
}
}
}
// Wird nach dem Speichern gerufen und kann ggf. aufraeumen. Vor allem wird
// der Inhalt von <properties> geloescht, da dies nur beim Speichern relevant
// ist (und auch nicht gecached werden kann!).
public void _prop_finalize_save()
{
properties = 0;
}
// Konvertiert die Daten in <properties> wieder in die Laufzeitform und mergt
// sie mit den bereits existierenden Props.
public void _prop_finalize_restore()
{
// Da wir das eigene Savefile oder Savestring restaurieren, koennen wir
// alle Rechte ignorieren. Wer das Savefile schreiben kann, hat gewonnen...
// Und Savestrings muss das Objekt selber in seinen restore_object()-Aufruf
// schreiben.
foreach(string pname, mixed value, int mode: properties)
{
prop[F_VALUE][pname] = value;
// mode aus dem Savefile wird uebernommen, ausser SETMAPPED, was vom
// bisher gueltigem mode uebernommen werden muss.
if (prop[F_MODE][pname] & SETMAPPED)
mode &= SETMAPPED;
prop[F_MODE][pname] = mode;
// F_SET_METHOD und F_QUERY_METHOD werden beibehalten, sollten sie vor dem
// restore bereits gesetzt sein. Aus dem Savefile kommen beide nie.
}
properties = 0;
}