blob: 5d8f4ffbb6b436d260910639579c9b35b9d09ffb [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// master/userinfo.c -- Cache mit Spielerinfos
4//
5// $Id: userinfo.c 9467 2016-02-19 19:48:24Z Zesstra $
6
7#pragma strict_types
8
9#include "/secure/master.h"
10#include "/sys/files.h"
11#include "/sys/object_info.h"
12
13// Makro aus wizlevels.h ersetzen, da secure_level ne sefun ist. *seufz*
14#undef ARCH_SECURITY
15#define ARCH_SECURITY (call_sefun("secure_level") >= ARCH_LVL)
16
17private string* ExpandUIDAlias(string alias, int rec);
18
19nosave mapping userlist;
20string name, password;
21string ektips;
22string fptips;
23int level;
24string shell;
25int creation_date;
26string ep, ek, mq;
27string *domains,*guilds, *uidstotakecare;
28nosave string* lateplayers;
29// hier wird ein Mapping von UID-zu-Magier-Zuordnungen gespeichert. Als Keys
30// werden UIDs verwendet, der Value ist jeweils ein Array mit den magiern, die
31// dafuer zustaendig sind.
32nosave private mapping userids = ([]);
33// Ein Cache fuer UID-Aliase, damit ExpandUIDAlias() nicht staendig ganze
34// Verzeichnisse einlesen muss, sondern der Master das nur im Reset machen
35// muss.
36nosave private mapping uidaliase = ([]);
37
38#ifdef _PUREFTPD_
39// Liste von FTP-berechtigten Usern
40nosave private mapping ftpuser = ([]);
41
42public void read_ftp_users() {
43 string str = read_file("/"SECUREDIR"/ARCH/pureftpd.passwd") || "";
44 string *data = explode(str, "\n");
45 if (!sizeof(data)) return;
46 ftpuser = m_allocate(sizeof(data), 1);
47 foreach(str : data) {
48 string *tmp=explode(str, ":");
49 if(sizeof(tmp) >= 2)
50 m_add(ftpuser, tmp[0], tmp[1]);
51 }
52}
53
54public int write_ftp_users() {
55 string *record = allocate(18,"");
56 mapping tmp = m_allocate(sizeof(ftpuser));
57 // set UID/GID of system user
58 record[2] = "1000";
59 record[3] = "1000";
60 foreach(string u, string pwhash : ftpuser) {
61 record[0] = u; // UID
62 record[1] = pwhash;
63 record[5] = "/home/mud/mudlib/"WIZARDDIR"/" + u + "/./";
64 m_add(tmp, implode(record, ":"));
65 }
66 write_file("/"SECUREDIR"/ARCH/pureftpd.passwd", implode(m_indices(tmp),"\n"),1);
67 return sizeof(tmp);
68}
69
70// geloeschte Magier oder Magier, die 2 Wochen nicht da waren, expiren
71public void expire_ftp_user() {
72 foreach(string u : ftpuser) {
73 mixed uinfo = get_full_userinfo(u);
74 if (!uinfo) {
75 m_delete(ftpuser,u);
76 continue;
77 }
78 int zeit = call_sefun("file_time",
79 "/"SECURESAVEPATH + u[0..0] + "/" + u + ".o");
80 if (zeit < time()-1209600) {
81 m_delete(ftpuser,u);
82 continue;
83 }
84 }
85 call_out(#'write_ftp_users, 2);
86}
87
88#endif // _PUREFTPD_
89
90// ********** oeffentliche Funktionen ***********************
91
92//Verantwortliche Magier fuer eine UID ausgeben
93public varargs string* QueryWizardsForUID(string uid, int recursive) {
94
95 if (!stringp(uid) || !sizeof(uid))
96 return(({}));
97
98 string *tolookup=({uid}); //diese spaeter in userids nachgucken.
99 string *wizards=({});
100 // Schauen, was automatisch ermittelt werden kann.
101 string *parts=explode(uid,".");
102 switch(sizeof(parts)) {
103 case 3:
104 // z.B. d.region.magier und p.service.magier
105 if (find_userinfo(parts[2]))
106 //Magier existiert, reinschreiben.
107 wizards+=({parts[2]});
108 if (parts[0]=="d")
109 // d.region noch nachgucken (RMs)
110 tolookup=({implode(parts[0..1],".")});
111 break;
112 //case 2:
113 // GUILD.gilde, p.project, d.region
114 // koennen nur in userids nachgeguckt werden (s.u. tolookup)
115 // muessen da als GUILD.*, d.* und p.* drinstehen!
116 case 1:
117 // kein Punkt drin. Entweder Magier-ID oder spezielle ID
118 // (letztere wird unten noch per tolookup nachgeguckt)
119 if (find_userinfo(parts[0]))
120 wizards+=({parts[0]});
121 break;
122 }
123 // jetzt in userids nachschlagen
124 foreach(uid: tolookup) {
125 if (member(userids,uid))
126 wizards+=userids[uid];
127 }
128 // so. Nun kann es aber noch sein, dass in userids sowas wie
129 // "d.wald.atamur":({atamur}) und "atamur":({"rumata"}) drinsteht, also
130 // ein Magier sozusagen fuer Kram eines anderen verantwortlich ist. D.h.
131 // nochmal durch QueryWizardsForUID() schicken (das ist dann allerdings nicht
132 // weiter rekursiv).
133 if (!recursive) {
134 foreach(uid: wizards) {
135 //es ist moeglich, in der Schleife wizards zu vergroessern, ohne dass
136 //es Einfluss auf die Schleife hat.
137 wizards += QueryWizardsForUID(uid, 1) - ({uid});
138 }
139 }
140 return(m_indices(mkmapping(wizards)));
141}
142
143//das Define ist nicht sonderlich elegant, aber ich kann hier nicht das
144//newskills.h hier includen (1. zu viel, 2. ists nen Sicherheitsproblem)
145#define P_GUILD_DEFAULT_SPELLBOOK "guild_sb"
146// dito fuer den Gildenmaster
147#define GUILDMASTER "/secure/gildenmaster"
148
149// Den Alias-Cache loeschen (normalerweise einmal am Tag)
150public void ResetUIDAliase() {
151 // RM+ duerfen den Cache loeschen (wenn sie z.B. nen neues Verzeichnis
152 // angelegt haben.)
153 if (extern_call()
154 && call_sefun("secure_level") < LORD_LVL)
155 return;
156
157 uidaliase=([]);
158}
159
160// expandiert einige 'Aliase' in ein Array von allen UIDs, fuer die sie
161// stehen. Bsp: "region" -> d.region.* + region + d.region,
162// "zauberer" -> GUILD.zauberer, "p.service" -> p.service.*
163// Nutzt Eintrag aus dem uidalias-Cache, sofern vorhanden.
164public varargs string* QueryUIDAlias(string alias, int rec) {
165 string *uids;
166 if (!stringp(alias) || !sizeof(alias))
167 return ({});
168 // Wen im cache, gehts schnell.
169 if (member(uidaliase, alias))
170 uids = uidaliase[alias];
171 else
172 uids = ExpandUIDAlias(alias, rec);
173
174 if (extern_call())
175 return copy(uids);
176
177 return(uids);
178}
179
180// Fuer welche UIDs ist ein Magier verantwortlich? (Ist er RM,
181// Gildenmagier, in welchen Regionen hat er ein Verzeichnis, etc.)
182// recursive != 0 bedeutet, dass der Aufruf indirekt aus einem laufenden
183// QueryUIDsForWizard() erfolgt. In dem Fall soll QueryUIDAlias() keine
184// weitere Rekursion durchfuehren. Wird einfach durchgereicht.
185public varargs string* QueryUIDsForWizard(string wizuid, int recursive) {
186 string *uids, *tmp, *uidstoadd;
187 int i;
188
189 if (!stringp(wizuid) || !sizeof(wizuid) || !IS_LEARNER(wizuid))
190 return(({}));
191
192 if (!find_userinfo(wizuid))
193 return(({}));
194
195 uidstoadd=({}); //diese werden hinterher in userids gespeichert.
196
197 // als erstes die ratebaren UIDs. ;-)
198 uids=({wizuid});
199 // Regionen ermitteln, wo wizuid ein Verzeichnis hat und entsprechende
200 // UIDs anhaengen:
201 foreach(string region: get_domain_homes(wizuid)) {
202 uids+=({ sprintf(DOMAINDIR".%s.%s",region,wizuid) });
203 }
204 // Verzeichnis in /p/service?
205 if (file_size(PROJECTDIR"/service/"+wizuid) == FSIZE_DIR)
206 uids+=({PROJECTDIR".service."+wizuid});
207
208 // Gildenchef?
209 if (pointerp(userlist[wizuid,USER_GUILD])) {
210 foreach(string gilde: userlist[wizuid,USER_GUILD]) {
211 uidstoadd += QueryUIDAlias(gilde);
212 }
213 }
214 // Regionsmagier?
215 if (pointerp(userlist[wizuid,USER_DOMAIN])) {
216 foreach(string domain: userlist[wizuid,USER_DOMAIN]) {
217 //generelle Pseudo-UID 'd.region' und 'region' fuer geloeschte
218 //Magier dieser Region vormerken, um sie hinterher fuers
219 //Reverse-Lookup ins uid-Mapping zu schreiben.
220 string *pseudo=({DOMAINDIR"."+domain, domain});
221 uidstoadd += pseudo;
222 // Rest macht QueryUIDAlias, dabei aber die von der Funktion
223 // ebenfalls gelieferten Pseudo-UIDs wieder abziehen.
224 uids += QueryUIDAlias(domain) - pseudo;
225 }
226 }
227 // jetzt noch nachgucken, fuer welche UIDs dieser Magier explizit noch
228 // zustaendig ist.
229 if (pointerp(userlist[wizuid,USER_UIDS_TO_TAKE_CARE])) {
230 // dies koennte etwas a la "region" oder "anderermagier" sein, d.h. dieser
231 // Magier ist fuer alle UIDs von 'andermagier' auch zustaendig. Daher muss
232 // jedes davon durch QueryUIDAlias() (was im Falle von Spielern wiederum
233 // QueryUIDsForWizard() ruft, aber die Rekursion im Falle von Spielern ist
234 // auf 1 begrenzt).
235 foreach(string uid2: userlist[wizuid,USER_UIDS_TO_TAKE_CARE]) {
236 uidstoadd += QueryUIDAlias(uid2, recursive);
237 }
238 }
239
240 // so, in uidstoadd stehen UIDs drin, die nicht Magiername selber,
241 // d.region.magier oder p.service.magier sind und bei welchen folglich das
242 // Mapping UIDs-nach-Magier nur mit einer Liste moeglich ist. In die
243 // werden die uids nun eingetragen. (z.B. d.region)
244 if (sizeof(uidstoadd)) {
245 // genug Platz in userids? Sonst welche rauswerfen. :-/
246 // (besser als bug) TODO: Auf 10k begrenzen -> max Arraygroesse!
247 if ( sizeof(userids)+(i=sizeof(uidstoadd))
248 >= __MAX_MAPPING_KEYS__) {
249 foreach(string tmpuid: m_indices(userids)[0..i])
250 m_delete(userids,tmpuid);
251 }
252 foreach(string tmpuid: uidstoadd) {
253 if (member(userids,tmpuid)) {
254 //User dem Array hinzufuegen, wenn noch nicht drin.
255 if (member(userids[tmpuid],wizuid)==-1)
256 userids[tmpuid]=userids[tmpuid]+({wizuid});
257 }
258 //sonst neuen Eintragen hinzufuegen
259 else
260 m_add(userids,tmpuid,({wizuid}));
261 }
262 } // Ende spez. uids speichern
263
264 return(uids+uidstoadd);
265}
266
267//Einen Magier als verantwortlich fuer eine bestimmte UID eintragen
268public string* AddWizardForUID(string uid, string wizuid) {
269 if (!stringp(wizuid) || !sizeof(wizuid)
270 || !IS_LEARNER(wizuid))
271 return(({}));
272
273 //Zugriff nur als EM oder jemand, der fuer die UID zustaendig ist.
274 if ( !ARCH_SECURITY
275 && member(
276 QueryUIDsForWizard(call_sefun("secure_euid")),
277 uid) == -1)
278 return(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
279
280 if (!pointerp(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]))
281 //Array neu anlegen
282 userlist[wizuid,USER_UIDS_TO_TAKE_CARE]=({uid});
283 else {
284 //Ein Array schon vorhanden
285 if (member(userlist[wizuid,USER_UIDS_TO_TAKE_CARE],uid)==-1)
286 //uid nicht drin
287 userlist[wizuid,USER_UIDS_TO_TAKE_CARE]=
288 userlist[wizuid,USER_UIDS_TO_TAKE_CARE]+({uid});
289 }
290 save_userinfo(wizuid);
291 // aus dem UID-Alias-Cache werfen
292 m_delete(uidaliase, wizuid);
293 // Aufruf, um userids und uidaliase zu aktualisieren
294 QueryUIDsForWizard(wizuid);
295 return(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
296}
297
298// Einen Magier wieder austragen, wenn er nicht mehr zustaendig ist.
299public string* RemoveWizardFromUID(string uid, string wizuid) {
300 if (!stringp(wizuid) || !sizeof(wizuid)
301 || !find_userinfo(wizuid))
302 return(({}));
303
304 //Zugriff nur als EM oder jemand, der fuer die UID zustaendig ist.
305 if ( !ARCH_SECURITY
306 && member(
307 QueryUIDsForWizard(call_sefun("secure_euid")),
308 uid)==-1)
309 return copy(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
310
311 // jetzt muss diese wizuid aus allen UIDs in userids geloescht werden, die
312 // sie bisher enthalten. Hierzu sollte QueryUIDAlias die potentiell
313 // drinstehenden UIDs liefern.
314 foreach(string tuid: QueryUIDAlias(wizuid,0)) {
315 if (member(userids, tuid) &&
316 member(userids[tuid],wizuid)!=-1 )
317 userids[tuid] -= ({wizuid});
318 }
319 // wenn es eine UID war, fuer die der Magier explizit zustaendig war,
320 // entsprechend loeschen. Sonst ist hier Ende.
321 if (!pointerp(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]))
322 return ({});
323 if (member(userlist[wizuid,USER_UIDS_TO_TAKE_CARE],uid)==-1)
324 return copy(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
325
326 // Jetzt aus userlist loeschen.
327 userlist[wizuid,USER_UIDS_TO_TAKE_CARE] -= ({uid});
328 save_userinfo(wizuid);
329 // und userids/uidaliase aktualisieren.
330 QueryUIDsForWizard(wizuid);
331 return copy(userlist[wizuid,USER_UIDS_TO_TAKE_CARE]);
332}
333
334//entfernt einen user aus dem usercache
335void RemoveFromCache(string user) {
336 m_delete(userlist,user);
337}
338
339//loescht den gesamten Usercache
340int clear_cache() {
341 userlist=m_allocate(0,widthof(userlist));
342 update_late_players();
343 return 1;
344}
345
346// Loescht UID->Magier Lookuptabelle. Sollte nicht gerufen werden, wenn man
347// nicht genau weiss, was man damit kaputtmacht.
348// als Nebeneffekt wird clear_cache() gerufen.
349int ResetUIDCache() {
350 // da diese Funktion auch das UID<->Magier-Lookup loescht, darf das nicht
351 // jeder rufen.
352 if (extern_call() &&
353 call_sefun("secure_level") < ELDER_LVL)
354 return -1;
355 userids=([]);
356 clear_cache();
357 return 1;
358}
359
360//verstorbene Spieler auflisten
361string list_late_players()
362{
363 string ret;
364 int size,i;
365
366 ret= "************************************\n";
367 ret+="* Verstorbene Spieler *\n";
368 ret+="************************************\n";
369
370 if(!lateplayers || (size=sizeof(lateplayers))==0)
371 {
372 return ret;
373 }
374
375 for(i=0;i<size;i++)
376 {
377 ret+=lateplayers[i]+"\n";
378 }
379
380 return ret;
381}
382
383// ist der Spieler gestorben?
384int check_late_player(string str)
385{
386 if(!lateplayers || !str || str=="")
387 {
388 return 0;
389 }
390
391 if(member(lateplayers,str)!=-1)
392 {
393 return 1;
394 }
395
396 return 0;
397}
398
399// alle Eintraege im usercache ausgeben
400public mixed *show_cache() {
401 return m_indices(userlist);
402}
403
404// gibt es so einen User? Anfrage ausserdem immer in den Cache eintragen
405public int find_userinfo(string user) {
406 string file;
407 int i;
408 if (!stringp(user) || !sizeof(user)
409 || member(user,' ')!=-1 || member(user,':')!=-1)
410 return 0;
411 if (!member(userlist,user)) {
412 //erstmal schauen, ob wir mit einem neuen Eintrag nicht die max.
413 //Mapping-Groessen ueberschreiten, wenn ja, einen Eintrag loeschen
414 //BTW: widthof()+1 ist richtig so.
415 //BTW2: Ich hoffe, die max. Arraygroesse ist immer gross genug, sollte
416 //sie aber sein bei ner Mappingbreite von 10.
417 // BTW3: Dieses Rausloeschen von einem Eintrag bei Erreichen der Grenze
418 // erhoeht die benoetigten Ticks fuer diese Funktion um das 5-6fache und
419 // die noetige Zeit um das 70fache. Daher wird der Cache bei Erreichen
420 // der Grenze jetzt einfach geloescht.
421 if ( ( (i=sizeof(userlist)+1) >= __MAX_MAPPING_KEYS__)
422 || (( i * (widthof(userlist)+1)) >
423 __MAX_MAPPING_SIZE__))
424 //m_delete(userlist,m_indices(userlist)[0]);
425 userlist=m_allocate(1,widthof(userlist));
426
427 // Usersavefile finden
428 if ((file=secure_savefile(user))=="") {
429 // User gibt es nicht, aber Anfrage cachen
430 userlist+=([user: "NP"; -1; ({}); "LOCKED"; -1; time(); ""; ""; "";
431 ({});"";""; 0 ]);
432 return 0;
433 }
434 password="";
435 ep="";
436 ek="";
437 mq="";
438 guilds=({});
439 ektips="";
440 fptips="";
441 uidstotakecare=0;
442 if (!restore_object(file)) return 0;
443 userlist+=([user: password; level; domains; shell; creation_date;
444 time();ep; ek; mq; guilds; ektips; fptips; uidstotakecare]);
445 // die speziellen UIDs, fuer die dieser Magier zustaendig ist, ermitten
446 // und gleichzeitig im entsprechenden Mapping fuers Reverse-Loopup
447 // speichern.
448 if (level >= LEARNER_LVL)
449 QueryUIDsForWizard(user);
450 }
451 userlist[user,USER_TOUCH]=time();
452 if (userlist[user,USER_LEVEL]==-1) return 0;
453 return 1;
454}
455
456// Daten aus der Userlist fuer diesen User liefern. Macht ggf. find_userinfo()
457public mixed *get_userinfo(string user) {
458 if(!user||user=="")
459 return 0;
460 user=explode(user,".")[0];
461 if (!member(userlist,user) && !find_userinfo(user))
462 return 0;
463
464 if (userlist[user,USER_LEVEL]==-1) return 0;
465
466 return({user,"##"+user,userlist[user,USER_LEVEL],
467 userlist[user,USER_DOMAIN], userlist[user,USER_OBJECT],
468 userlist[user,USER_CREATION_DATE], 0, 0,
469 userlist[user,USER_GUILD],
470 userlist[user,USER_EKTIPS],userlist[user,USER_FPTIPS],
471 userlist[user,USER_UIDS_TO_TAKE_CARE]});
472}
473
474// liefert das Objekt des users aus der Userlist zurueck.
475public string query_player_object( string name )
476{
477 mixed *userentry;
478 if( !find_userinfo(name) ) return "";
479 return userlist[name,USER_OBJECT];
480}
481
482// Passwort ok?
483public int good_password( string str, string name )
484{
485 string rts;
486 int i, n;
487
488 if ( str[0] == '!' ){
489 tell_object( this_player() || this_object(), "Ein Ausrufungszeichen "
490 "('!') als erster Buchstabe des Passwortes wuerde zu\n"
491 "Problemen fuehren. Such Dir bitte ein anderes Passwort "
492 "aus.\n" );
493 return 0;
494 }
495
496 if ( sizeof(str) < 6 ){
497 tell_object( this_player() || this_object(),
498 "Das Passwort muss wenigstens 6 Zeichen lang sein.\n" );
499 return 0;
500 }
501
502 str = lower_case(str);
503 rts = "";
504
505 // Zahlen/Sonderzeichen am Anfang oder Ende des Passwortes
506 // (z.B. "merlin99") sind nicht wirklich einfallsreich.
507 while ( sizeof(str) && (str[0] > 'z' || str[0] < 'a') ){
508 rts += str[0..0];
509 str = str[1..];
510 }
511
512 while ( sizeof(str) && (str[<1] > 'z' || str[<1] < 'a') ){
513 rts += str[<1..];
514 str = str[0..<2];
515 }
516
517 // Anzahl unterschiedlicher Zeichen merken, die herausgeschnitten wurden
518 n = sizeof( mkmapping(efun::explode( rts, "" )) );
519 rts = "";
520
521 for ( i = sizeof(str); i--; )
522 rts += str[i..i];
523
524 // Eigener Name als Passwort bzw. eigener Name rueckwaerts
525 if ( str == lower_case(name) || rts == lower_case(name) ||
526 // Name eines anderen Mudders (Erstie?)
527 secure_savefile(str) != "" || secure_savefile(rts) != "" ||
528 // Name von der Banish-Liste
529 QueryBanished(str) || QueryBanished(rts) ||
530 // Name eines vorhandenen NPC o.ae.
531 call_sefun("find_living", str ) ||
532 call_sefun("find_living", rts ) ||
533 // Zuwenig verschiedene Zeichen
534 sizeof( mkmapping(efun::explode( str, "" )) ) + n < 4 ){
535 tell_object( this_player() || this_object(),
536 "Das Passwort waere zu einfach zu erraten. Nimm bitte "
537 "ein anderes.\n" );
538 return 0;
539 }
540
541 return 1;
542}
543
544// User-ID fuer ein File ermitteln.
545public string get_wiz_name(string file) {
546 return creator_file(file);
547}
548
549// Wizlevel aus dem Userinfo-Cache holen
550public int get_wiz_level(string user) {
551 if (user && find_userinfo(user))
552 return userlist[user,USER_LEVEL];
553 //return 0 if no user given (return type needed)
554 return(0);
555}
556
557// Wizlevel fuer eine UID ermitteln.
558public int query_wiz_level( mixed player )
559{
560 if ( objectp(player) && efun::object_info(player, OI_ONCE_INTERACTIVE) )
561 return get_wiz_level( getuid(player) );
562 else {
563 // erstmal UID ermitteln, falls Objekt
564 //if (objectp(player))
565 // player=getuid(player);
566 if ( stringp(player) ) {
567 if( player[0..1]==DOMAINDIR"." ) return 25;
568 if( player[0..5]==GUILDID"." )
569 return WIZLVLS[GUILDID];
570 if( player[0..1]==PROJECTDIR"." ) return 21;
571 // die alte Loesung mit || verhaelt sich falsch, wenn ein UID ne
572 // spezielle ist, der Level 0 zugeordnet wurde und es einen
573 // Spieler mit diesem namen gibt.
574 if (member(WIZLVLS,player))
575 return(WIZLVLS[player]);
576 return get_wiz_level(player);
577 }
578 }
579 return 0;
580}
581
582// Savefile aus /secure/save* zurueckliefern
583public string secure_savefile(string name)
584{
585 if(!name||name=="")
586 return "";
587 name=explode(name,".")[0];
588 if (file_size(SECURESAVEPATH+name[0..0]+"/"+name+".o")>=0)
589 return SECURESAVEPATH+name[0..0]+"/"+name;
590
591 return "";
592}
593
594// *************** 'halb-oeffentliche Funktionen *********************
595#ifdef _PUREFTPD_
596// FTP-Berechtigung fuer Magier
597int update_ftp_access(string user, int state) {
598 // wenn nicht EM+ oder ROOT, darf nur fuer this_interactive geaendert
599 // werden.
600 if (getuid(PO) != ROOTID && extern_call()
601 && !ARCH_SECURITY) {
602 if (this_interactive())
603 user = getuid(this_interactive());
604 else
605 user = 0;
606 }
607 if (!user || !sizeof(user))
608 return -1;
609
610 if (!find_userinfo(user))
611 return 0;
612
613 // Passwort muss manuell vom Magier neu gesetzt werden, da es keine
614 // Moeglichkeit gibt, an das aktuelle im Klartext heranzukommen.
615 if (state) {
616 if (!member(ftpuser,user))
617 m_add(ftpuser, user, "*");
618 }
619 else
620 m_delete(ftpuser, user);
621
622 call_out(#'write_ftp_users, 4);
623 return state;
624}
625#endif
626
627// Spieler ein neues Wizlevel verpassen.
628int update_wiz_level(string user,int lev) {
629 object ob;
630
631 if (getuid(PO) != ROOTID && extern_call()) return 0;
632 if (!find_userinfo(user)) return 0;
633 userlist[user,USER_LEVEL] = lev;
634 save_userinfo(user);
635 return 1;
636}
637
638// neue Forscherpunkte fuer den User abspeichern.
639int update_ep(string user,string ep_neu) {
640 if (getuid(PO) != ROOTID) return 0;
641 if (!find_userinfo(user)) return 0;
642 userlist[user,USER_EP] = ep_neu;
643 save_userinfo(user);
644 return 1;
645}
646
647// neue Erstkills fuer den User abspeichern.
648int update_ek(string user,string ek_neu) {
649 if (getuid(PO) != ROOTID) return 0;
650 if (!find_userinfo(user)) return 0;
651 userlist[user,USER_EK] = ek_neu;
652 save_userinfo(user);
653 return 1;
654}
655
656// Miniquestdaten speichern.
657int update_mq(string user,string mq_neu) {
658 if (getuid(PO) != ROOTID) return 0;
659 if (!find_userinfo(user)) return 0;
660 userlist[user,USER_MQ] = mq_neu;
661 save_userinfo(user);
662 return 1;
663}
664
665// Erstkillpunkt-Tips speichern.
666int update_ektips(string user,string ek_neu) {
667 if (getuid(PO) != ROOTID) return 0;
668 if (!find_userinfo(user)) return 0;
669 userlist[user,USER_EKTIPS] = ek_neu;
670 save_userinfo(user);
671 return 1;
672}
673
674// Forscherpunkttips abspeichern.
675int update_fptips(string user,string fp_neu) {
676 if (getuid(PO) != ROOTID) return 0;
677 if (!find_userinfo(user)) return 0;
678 userlist[user,USER_FPTIPS] = fp_neu;
679 save_userinfo(user);
680 return 1;
681}
682
683// forscherpunkte abfragen.
684string query_ep(string user) {
685 if (getuid(PO) != ROOTID) return 0;
686 if (!find_userinfo(user)) return 0;
687 return userlist[user,USER_EP];
688}
689
690// Erstkills abfragen
691string query_ek(string user) {
692 if (getuid(PO) != ROOTID) return 0;
693 if (!find_userinfo(user)) return 0;
694 return userlist[user,USER_EK];
695}
696
697// Miniquests abfragen.
698string query_mq(string user) {
699 if (getuid(PO) != ROOTID) return 0;
700 if (!find_userinfo(user)) return 0;
701 return userlist[user,USER_MQ];
702}
703
704// EK-Tips abfragen.
705string query_ektips(string user) {
706 if (getuid(PO) != ROOTID) return 0;
707 if (!find_userinfo(user)) return 0;
708 return userlist[user,USER_EKTIPS];
709}
710
711// FP-Tips abfragen.
712string query_fptips(string user) {
713 if (getuid(PO) != ROOTID) return 0;
714 if (!find_userinfo(user)) return 0;
715 return userlist[user,USER_FPTIPS];
716}
717
718#define PLAYERSHELLS ({"/std/shells/darkelf", "/std/shells/dwarf", \
719 "/std/shells/elf", "/std/shells/feline", "/std/shells/hobbit", \
720 "/std/shells/human" })
721
722// Aendert die Shells eines Users.
723int set_player_object( string user, string objectname )
724{
725 mixed *path;
726 string prev;
727
728 // nur EM und ROOT duerfen die Shell eines Charakters aendern
729 if ( !ARCH_SECURITY &&
730 (!previous_object() || getuid(previous_object()) != ROOTID) ) {
731 return -1;
732 }
733
734 if ( objectname == "" )
735 objectname = 0;
736
737 if ( !stringp(user) || user == "" )
738 return -6;
739
740 if ( !stringp(objectname) ){
741 if ( !find_userinfo(user) )
742 return -4;
743
744 userlist[user, USER_OBJECT] = 0;
745 save_userinfo(user);
746 return 1;
747 }
748
749 if ( catch(load_object(objectname);publish) ) {
750 write( "Fehler in " + objectname + "!\n" );
751 return -2;
752 }
753
754 objectname = _get_path( objectname, 0 );
755 path = (efun::explode( objectname, "/" ) - ({ "", 0 }));
756
757 if ( sizeof(path) < 3 || path[0] != "std" || path[1] != "shells" )
758 return -3;
759
760 if ( !find_userinfo(user) )
761 return -4;
762
763 prev = userlist[user, USER_OBJECT];
764 userlist[user, USER_OBJECT] = objectname;
765 save_userinfo(user);
766
767 // Loggen, falls die Aenderung nicht von Login beim Anlegen des Chars
768 // erfolgt.
769 if (load_name(this_interactive()) != "/secure/login"
770 || prev != "") {
771 if (prev == "") prev ="<keine>";
772 call_sefun("log_file", "ARCH/SHELL_AENDERUNGEN",
773 sprintf( "%s: %O aendert die Shell von %s von %s auf %s (PO: %O)\n",
774 strftime("%Y%m%d-%H%M%S"),
775 this_interactive(), capitalize(user), prev, objectname,
776 previous_object()) );
777 }
778
779 return 1;
780}
781
782// Passwort aktualisieren.
783int update_password( string old, string new )
784{
785 string user;
786
787 // nanu, kein user?
788 if ( !find_userinfo(user = getuid(PO)) )
789 return 0;
790
791 // wenn das neue PW unterschiedlich ist, schauen, ob das neue PW ok ist.
792 if ( old != new && !good_password( new, user ) )
793 return 0;
794
795 string pwhash = userlist[user, USER_PASSWORD];
796 string oldpwhash;
797 if (sizeof(pwhash) > 13) {
798 // MD5-Hash
799 oldpwhash = md5_crypt(old, pwhash);
800 }
801 else if (sizeof(pwhash) > 2) {
802 // Crypt-Hash
803 oldpwhash = crypt(old, pwhash[0..1]);
804 }
805
806 // wenn es einen PW-hash gibt, also ein PW gesetzt wird, muss der Hash von
807 // old mit dem pwhash uebereinstimmen. Leerer Hash oder gar kein Hash
808 // erlaubt jedes beliebige PW.
809 if ( stringp(pwhash) && sizeof(pwhash) && pwhash != oldpwhash)
810 return 0;
811 // an dieser Stelle stimmt 'old' wohl mit dem gesetzten Passwort ueberein.
812 // Wenn allerdings die Funktion mit old==new aufgerufen wurde, wird hier
813 // nur 1 zurueckgeben und sonst an sich nix gemacht. Kann nicht weiter
814 // oben schon gemacht werden, die Shells dies als PW-Pruefung nutzen.
815 // *seufz*
816 if (old == new) return 1;
817
818 // dann mal neu setzen
819 userlist[ user, USER_PASSWORD ] = md5_crypt( new, 0 );
820 save_userinfo(user);
821#ifdef _PUREFTPD_
822 // Bedauerlicherweise versteht pureftpd unser md5_crypt nicht. :-(
823 if (member(ftpuser,user)) {
824 ftpuser[user] = crypt(new);
825 if (find_call_out(#'write_ftp_users) == -1)
826 call_out(#'write_ftp_users,4);
827 }
828#endif
829 return 1;
830}
831
832// Spieler loeschen.
833int delete_player(string passwd, string real_name)
834{
835 int wlevel;
836 string part_filename;
837
838 if (!PO || PO!=TP || PO!=TI || real_name != getuid(PO) ||
839 !find_userinfo(real_name))
840 return 0;
841 mixed erstie=(mixed)this_interactive()->QueryProp(P_SECOND);
842 password = userlist[real_name,USER_PASSWORD];
843 wlevel = get_wiz_level(real_name);
844 if (!update_password(passwd, passwd)) return 0;
845
846 // Spielpausen aufheben (sonst kann man als Spieler nen Namen sperren).
847 TBanishName(real_name, 0);
848
849 part_filename="/"+real_name[0..0]+"/"+real_name+".o";
850 rm("/"SECUREDIR"/save"+part_filename);
851 rm("/"LIBSAVEDIR"/"+part_filename);
852 rm("/"MAILDIR"/"+part_filename);
853
854 m_delete(userlist,real_name);
855
856 if (wlevel >= LEARNER_LVL)
857 TO->BanishName(real_name, "So hiess mal ein Magier hier");
858 else if (wlevel >= SEER_LVL)
859 TO->BanishName(real_name, "So hiess mal ein Seher hier");
860
861#ifdef _PUREFTPD_
862 if (member(ftpuser,real_name)) {
863 m_delete(ftpuser,real_name);
864 call_out(#'write_ftp_users,4);
865 }
866#endif
867
868 call_sefun("log_file", "USERDELETE",
869 sprintf("%s: %s %s(%s)\n",
870 ctime(time()),real_name,
871 (stringp(erstie)?sprintf("[Erstie: %s] ",erstie):""),
872 call_sefun("query_ip_number",TI)));
873
874 return 1;
875}
876
877// ermittelt alle existierenden Spieler, die ein Savefile in /secure/save
878// haben.
879// ([ "a": ({...namen...}), "b": ({...namen...}), ... ])
880public mapping get_all_players() {
881 string *dirs=({"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o",
882 "p","q","r","s","t","u","v","w","x","y","z"});
883 mapping allplayer=([]);
884 string *tmp;
885 foreach(string dir: dirs) {
886 tmp=get_dir("/secure/save/"+dir+"/*")
887 - ({".","..",".KEEP",".svn"});
888 allplayer[dir] = map(tmp,function string (string fn)
889 { return explode(fn,".")[0]; } );
890 }
891 return allplayer;
892}
893
894// *************** interne Funktionen ********************************
895protected void create()
896{
897 userlist=m_allocate(0,12);
898 update_late_players();
899#ifdef _PUREFTPD_
900 read_ftp_users();
901#endif
902}
903
904//verstorbene Spieler einlesen
905void update_late_players() {
906 string read;
907 string *tmp;
908
909 lateplayers=0;
910
911 read=read_file("/"SECUREDIR"/LATE_PLAYERS");
912 if(!read || read=="")
913 {
914 return;
915 }
916
917 tmp=explode(read,"\n");
918 if(!sizeof(tmp))
919 {
920 return;
921 }
922
923 lateplayers=tmp;
924}
925
926
927// Daten aus der Userlist inkl. Passworthash zurueckliefern. Macht ggf.
928// find_userinfo(). Nur masterintern aufrufbar.
929protected mixed *get_full_userinfo(string user) {
930 if(!user||user=="")
931 return 0;
932 user=explode(user,".")[0];
933 if (!member(userlist,user) && !find_userinfo(user))
934 return 0;
935
936 return({user,userlist[user,USER_PASSWORD],userlist[user,USER_LEVEL],
937 userlist[user,USER_DOMAIN], userlist[user,USER_OBJECT],
938 userlist[user,USER_CREATION_DATE], 0, 0, userlist[user,USER_GUILD],
939 userlist[user,USER_EKTIPS],userlist[user,USER_FPTIPS],
940 userlist[user,USER_UIDS_TO_TAKE_CARE]});
941}
942
943// Userdaten aus der Userlist im Savefile in /secure/save/ speichern.
944protected void save_userinfo(string user) {
945 if(!user||user=="")
946 return;
947 user=explode(user,".")[0];
948 if (!member(userlist,user)) return;
949 name = user;
950 level = userlist[name,USER_LEVEL];
951 domains = userlist[name,USER_DOMAIN];
952 shell = userlist[name,USER_OBJECT];
953 password = userlist[name,USER_PASSWORD];
954 creation_date = userlist[name,USER_CREATION_DATE];
955 if (!creation_date) creation_date = -1;
956 ep = userlist[name,USER_EP];
957 if (!ep) ep="";
958 ek = userlist[name,USER_EK];
959 if (!ek) ek="";
960 mq = userlist[name,USER_MQ];
961 if (!mq) mq="";
962 guilds = userlist[name,USER_GUILD];
963 ektips=userlist[name,USER_EKTIPS];
964 if(!ektips) ektips="";
965 fptips=userlist[name,USER_FPTIPS];
966 if(!fptips) fptips="";
967 uidstotakecare=userlist[name,USER_UIDS_TO_TAKE_CARE];
968
969 if (save_object(SECURESAVEPATH+name[0..0]+"/"+name) != 0) {
970 // autsch. Buggen damit dieser moeglichst schnell auffaellt, dass hier
971 // Savefiles in /secure/save/ nicht geschrieben wurden.
972 raise_error(sprintf(
973 "Savefile %O konnte nicht erstellt werden!\n",
974 SECURESAVEPATH+name[0..0]+"/"+name));
975 }
976}
977
978// In welchen Regionen ist der Spieler Regionsmagier?
979protected void set_domains(string player, string *domains)
980{
981 // wenn der Magier jetzt Domains nicht mehr hat, muessen die aus 'userids'
982 // entfernt werden, das uebernimmt RemoveWizardFromUID().
983 if (pointerp(userlist[player, USER_DOMAIN])) {
984 string *removeduids=
985 ((string*)userlist[player, USER_DOMAIN] | domains) - domains;
986 foreach(string uid: removeduids)
987 RemoveWizardFromUID(uid, player);
988 }
989 // gecachtes Alias fuer player loeschen
990 m_delete(uidaliase,player);
991 userlist[player, USER_DOMAIN]=domains;
992 save_userinfo(player);
993 // UID-zu-Magier-Mapping aktualisieren
994 QueryUIDsForWizard(player);
995}
996
997// In welche Gilden ist der Spieler Gildenmagier?
998protected void set_guilds(string player, string *guilds)
999{
1000 // wenn der Magier jetzt Gilden nicht mehr hat, muessen die aus 'userids'
1001 // entfernt werden, das uebernimmt RemoveWizardFromUID().
1002 if (pointerp(userlist[player, USER_GUILD])) {
1003 string *removeduids=
1004 ((string*)userlist[player, USER_GUILD] | guilds) - guilds;
1005 foreach(string uid: removeduids)
1006 RemoveWizardFromUID(uid, player);
1007 }
1008 // gecachtes Alias fuer player loeschen
1009 m_delete(uidaliase,player);
1010 userlist[player, USER_GUILD]=guilds;
1011 save_userinfo(player);
1012 // UID-zu-Magier-Mapping aktualisieren
1013 QueryUIDsForWizard(player);
1014}
1015
1016// Userinfo-Cache expiren...
1017protected void _cleanup_uinfo()
1018{
1019 foreach(string user: userlist) {
1020 if ((time() - userlist[user,USER_TOUCH]) > 1800
1021 && !call_sefun("find_player",user))
1022 m_delete(userlist,user);
1023 }
1024}
1025
1026// expandiert einige 'Aliase' in ein Array von allen UIDs, fuer die sie
1027// stehen. Bsp: "region" -> d.region.* + region + d.region,
1028// "zauberer" -> GUILD.zauberer, "p.service" -> p.service.* oder auch
1029// "magier" -> QueryUIDsForWizard().
1030// Das erfolgt ueber Lookup der entsprechenden Verzeichnisse auf der PLatte.
1031// Da zusaetzlich auch noch jede Menge find_userinfo() dazu kommen, ist das
1032// recht aufwaendig. Damit das ganze nicht jedesmal erfolgen muss, wird das
1033// Ergebnis gecacht und QueryUIDAlias() nutzt den Cache.
1034private string* ExpandUIDAlias(string alias, int rec) {
1035
1036 string *uids=({});
1037
1038 // Regionsname?
1039 if (file_size("/"DOMAINDIR"/"+alias) == FSIZE_DIR) {
1040 //generelle Pseudo-UID 'd.region' und 'region' fuer geloeschte
1041 //Magier dieser Region
1042 uids += ({DOMAINDIR"."+alias, alias});
1043 //alle Magier-Verzeichnisse ermitteln:
1044 string tmpdir="/"DOMAINDIR"/"+alias+"/";
1045 foreach(string dir: (get_dir(tmpdir+"*") || ({}))
1046 - ({".","..",".svn"})) {
1047 // schauen, obs nen (sichtbares) Verzeichnis ist und ob der Magier
1048 // existiert. Letzteres aber nur, falls die Rekursionstiefe min. 100
1049 // ist, da beim Nachschauen eines Magiers mit (mehreren)
1050 // RM-Posten, der in der Region RMs aus anderen Regionen hat, leicht
1051 // Rekursionstiefen von 20 (*4) auftreten koennen, wenn noch gar
1052 // keine UIDs im Cache sind (find_userinfo() ruft indirekt diese
1053 // Funktion).
1054 if (dir[0]!='.'
1055 && file_size(tmpdir+dir) == FSIZE_DIR
1056#if __MAX_RECURSION__ > 99
1057 && find_userinfo(dir)
1058#endif
1059 )
1060 uids += ({DOMAINDIR"."+alias+"."+dir});
1061 }
1062 }
1063 // Gildenname?
1064 else if (GUILDMASTER->ValidGuild(alias)) {
1065 uids += ({GUILDID"."+alias});
1066 //hat die Gilde ein Projektverzeichnis?
1067 if (file_size("/"PROJECTDIR"/"+alias) == FSIZE_DIR) {
1068 uids += ({PROJECTDIR"."+alias});
1069 }
1070 // jetzt haben dummerweise die Spellbooks meist nicht den gleichen
1071 // Namen wie die Gilde. D.h. die Gilde muss nun noch nach dem
1072 // Namen des Spellbooks gefragt werden, weil dessen UID nicht
1073 // unbedingt gleich dem der Gilde ist. *seufz*
1074 string spbook;
1075 object guild;
1076 catch(guild=load_object(GUILDDIR"/"+alias));
1077 if (objectp(guild)
1078 && (sizeof(spbook=(string)
1079 guild->QueryProp(P_GUILD_DEFAULT_SPELLBOOK)))
1080 && spbook!=alias)
1081 uids += ({GUILDID"."+spbook});
1082 }
1083 // Spieler/Magier-UID?
1084 else if (find_userinfo(alias)) {
1085 // wenn rec > 0, wird eine Spieler-UID als Alias aufgefasst und zu seinen
1086 // UIDs expandiert. Hierbei erfolgt aber nur eine Rekursion.
1087 if (!rec) uids = QueryUIDsForWizard(alias, 1);
1088 else uids = ({alias});
1089 }
1090 // Projektkrams? -> alle Subdirs von /p/ ausser /p/service selber.
1091 else if (alias==PROJECTDIR) {
1092 foreach(string dir: (get_dir("/"PROJECTDIR"/*") || ({}))
1093 - ({".","..",".svn","service"})) {
1094 if (dir[0]!='.' &&
1095 file_size("/"PROJECTDIR"/"+dir) == FSIZE_DIR)
1096 uids += ({PROJECTDIR"."+dir});
1097 }
1098 }
1099 // p.service? -> Alle Subdirs von /p/service.
1100 else if (alias==PROJECTDIR".service") {
1101 foreach(string dir: (get_dir("/"PROJECTDIR"/service/*") || ({}))
1102 - ({".","..",".svn"})) {
1103 if (dir[0]!='.' &&
1104 file_size("/"PROJECTDIR"/service/"+dir) == FSIZE_DIR)
1105 uids += ({PROJECTDIR".service."+dir});
1106 }
1107 }
1108 // wenn nix zutrifft -> unexpandiert zurueckgeben
1109 else
1110 uids = ({alias});
1111
1112 // im Cache vermerken
1113 if (sizeof(uidaliase) >= __MAX_MAPPING_KEYS__)
1114 uidaliase=m_allocate(1);
1115 // auch vermerken, wenn keine UIDs ermittelt wurden.
1116 uidaliase += ([alias: uids]);
1117 return uids;
1118}
1119