root/1.8.3/tags/p3/src/extchat.c

Revision 919, 99.4 KB (checked in by shawnw, 19 months ago)

1.8.3p3

Line 
1/**
2 * \file extchat.c
3 *
4 * \brief The PennMUSH chat system
5 *
6 *
7 */
8#include "copyrite.h"
9#include "config.h"
10#include <ctype.h>
11#include <stdlib.h>
12#include <string.h>
13#include <stddef.h>
14#ifdef I_SYS_TYPES
15#include <sys/types.h>
16#endif
17#include <stdarg.h>
18#include "conf.h"
19#include "externs.h"
20#include "attrib.h"
21#include "mushdb.h"
22#include "match.h"
23#include "flags.h"
24#include "extchat.h"
25#include "ansi.h"
26#include "privtab.h"
27#include "mymalloc.h"
28#include "pueblo.h"
29#include "parse.h"
30#include "lock.h"
31#include "log.h"
32#include "game.h"
33#include "dbdefs.h"
34#include "function.h"
35#include "command.h"
36#include "dbio.h"
37#include "confmagic.h"
38
39
40static CHAN *new_channel(void);
41static CHANLIST *new_chanlist(void);
42static CHANUSER *new_user(dbref who);
43static void free_channel(CHAN *c);
44static void free_chanlist(CHANLIST *cl);
45static void free_user(CHANUSER *u);
46static int load_chatdb_oldstyle(FILE * fp);
47static int load_channel(FILE * fp, CHAN *ch);
48static int load_chanusers(FILE * fp, CHAN *ch);
49static int load_labeled_channel(FILE * fp, CHAN *ch);
50static int load_labeled_chanusers(FILE * fp, CHAN *ch);
51static void insert_channel(CHAN **ch);
52static void remove_channel(CHAN *ch);
53static void insert_obj_chan(dbref who, CHAN **ch);
54static void remove_obj_chan(dbref who, CHAN *ch);
55void remove_all_obj_chan(dbref thing);
56static void chan_chown(CHAN *c, dbref victim);
57void chan_chownall(dbref old, dbref new);
58static int insert_user(CHANUSER *user, CHAN *ch);
59static int remove_user(CHANUSER *u, CHAN *ch);
60static int save_channel(FILE * fp, CHAN *ch);
61static int save_chanuser(FILE * fp, CHANUSER *user);
62static void channel_wipe(dbref player, CHAN *chan);
63static int yesno(const char *str);
64static int canstilladd(dbref player);
65static enum cmatch_type find_channel_partial_on(const char *name, CHAN **chan,
66                                                dbref player);
67static enum cmatch_type find_channel_partial_off(const char *name, CHAN **chan,
68                                                 dbref player);
69static char *list_cuflags(CHANUSER *u, int verbose);
70static void channel_join_self(dbref player, const char *name);
71static void channel_leave_self(dbref player, const char *name);
72static void do_channel_who(dbref player, CHAN *chan);
73void chat_player_announce(dbref player, char *msg, int ungag);
74static int ok_channel_name(const char *n);
75static void format_channel_broadcast(CHAN *chan, CHANUSER *u, dbref victim,
76                                     int flags, const char *msg,
77                                     const char *extra);
78static void list_partial_matches(dbref player, const char *name,
79                                 enum chan_match_type type);
80
81const char *chan_speak_lock = "ChanSpeakLock";  /**< Name of speak lock */
82const char *chan_join_lock = "ChanJoinLock";    /**< Name of join lock */
83const char *chan_mod_lock = "ChanModLock";      /**< Name of modify lock */
84const char *chan_see_lock = "ChanSeeLock";      /**< Name of see lock */
85const char *chan_hide_lock = "ChanHideLock";    /**< Name of hide lock */
86
87#define YES 1     /**< An affirmative. */
88#define NO 0      /**< A negative. */
89#define ERR -1    /**< An error. Clever, eh? */
90
91/** Wrapper for insert_user() that generates a new CHANUSER and inserts it */
92#define insert_user_by_dbref(who,chan) \
93        insert_user(new_user(who),chan)
94/** Wrapper for remove_user() that searches for the CHANUSER to remove */
95#define remove_user_by_dbref(who,chan) \
96        remove_user(onchannel(who,chan),chan)
97
98int num_channels;  /**< Number of channels defined */
99
100CHAN *channels;    /**< Pointer to channel list */
101
102static PRIV priv_table[] = {
103  {"Disabled", 'D', CHANNEL_DISABLED, CHANNEL_DISABLED},
104  {"Admin", 'A', CHANNEL_ADMIN | CHANNEL_PLAYER, CHANNEL_ADMIN},
105  {"Wizard", 'W', CHANNEL_WIZARD | CHANNEL_PLAYER, CHANNEL_WIZARD},
106  {"Player", 'P', CHANNEL_PLAYER, CHANNEL_PLAYER},
107  {"Object", 'O', CHANNEL_OBJECT, CHANNEL_OBJECT},
108  {"Quiet", 'Q', CHANNEL_QUIET, CHANNEL_QUIET},
109  {"Open", 'o', CHANNEL_OPEN, CHANNEL_OPEN},
110  {"Hide_Ok", 'H', CHANNEL_CANHIDE, CHANNEL_CANHIDE},
111  {"NoTitles", 'T', CHANNEL_NOTITLES, CHANNEL_NOTITLES},
112  {"NoNames", 'N', CHANNEL_NONAMES, CHANNEL_NONAMES},
113  {"NoCemit", 'C', CHANNEL_NOCEMIT, CHANNEL_NOCEMIT},
114  {"Interact", 'I', CHANNEL_INTERACT, CHANNEL_INTERACT},
115  {NULL, '\0', 0, 0}
116};
117
118static PRIV chanuser_priv[] = {
119  {"Quiet", 'Q', CU_QUIET, CU_QUIET},
120  {"Hide", 'H', CU_HIDE, CU_HIDE},
121  {"Gag", 'G', CU_GAG, CU_GAG},
122  {NULL, '\0', 0, 0}
123};
124
125
126/** Get a player's CHANUSER entry if they're on a channel.
127 * This function checks to see if a given player is on a given channel.
128 * If so, it returns a pointer to their CHANUSER structure. If not,
129 * returns NULL.
130 * \param who player to test channel membership of.
131 * \param ch pointer to channel to test membership on.
132 * \return player's CHANUSER entry on the channel, or NULL.
133 */
134CHANUSER *
135onchannel(dbref who, CHAN *ch)
136{
137  static CHANUSER *u;
138  for (u = ChanUsers(ch); u; u = u->next) {
139    if (CUdbref(u) == who) {
140      return u;
141    }
142  }
143  return NULL;
144}
145
146/** A macro to test if a channel exists and, if not, to notify. */
147#define test_channel(player,name,chan) \
148   do { \
149    chan = NULL; \
150    switch (find_channel(name,&chan,player)) { \
151    case CMATCH_NONE: \
152      notify(player, T ("CHAT: I don't recognize that channel.")); \
153      return; \
154    case CMATCH_AMBIG: \
155      notify(player, T("CHAT: I don't know which channel you mean.")); \
156      list_partial_matches(player, name, PMATCH_ALL); \
157      return; \
158    case CMATCH_EXACT: \
159    case CMATCH_PARTIAL: \
160    default: \
161      break; \
162     } \
163    } while (0)
164
165/** A macro to test if a channel exists and player's on it, and,
166 * if not, to notify. */
167#define test_channel_on(player,name,chan) \
168   do { \
169    chan = NULL; \
170    switch (find_channel_partial_on(name,&chan,player)) { \
171    case CMATCH_NONE: \
172      notify(player, T ("CHAT: I don't recognize that channel.")); \
173      return; \
174    case CMATCH_AMBIG: \
175      notify(player, T("CHAT: I don't know which channel you mean.")); \
176      list_partial_matches(player, name, PMATCH_ALL); \
177      return; \
178    case CMATCH_EXACT: \
179    case CMATCH_PARTIAL: \
180    default: \
181      break; \
182     } \
183    } while (0)
184
185/*----------------------------------------------------------
186 * Loading and saving the chatdb
187 * The chatdb's format is pretty straightforward
188 * Return 1 on success, 0 on failure
189 */
190
191/** Initialize the chat database .*/
192void
193init_chatdb(void)
194{
195  num_channels = 0;
196  channels = NULL;
197}
198
199/** Load the chat database from a file.
200 * \param fp pointer to file to read from.
201 * \retval 1 success
202 * \retval 0 failure
203 */
204static int
205load_chatdb_oldstyle(FILE * fp)
206{
207  int i;
208  CHAN *ch;
209  char buff[20];
210
211  /* How many channels? */
212  num_channels = getref(fp);
213  if (num_channels > MAX_CHANNELS)
214    return 0;
215
216  /* Load all channels */
217  for (i = 0; i < num_channels; i++) {
218    if (feof(fp))
219      break;
220    ch = new_channel();
221    if (!ch)
222      return 0;
223    if (!load_channel(fp, ch)) {
224      do_rawlog(LT_ERR, T("Unable to load channel %d."), i);
225      free_channel(ch);
226      return 0;
227    }
228    insert_channel(&ch);
229  }
230  num_channels = i;
231
232  /* Check for **END OF DUMP*** */
233  fgets(buff, sizeof buff, fp);
234  if (!buff)
235    do_rawlog(LT_ERR, T("CHAT: No end-of-dump marker in the chat database."));
236  else if (strcmp(buff, EOD) != 0)
237    do_rawlog(LT_ERR, T("CHAT: Trailing garbage in the chat database."));
238
239  return 1;
240}
241
242extern char db_timestamp[];
243
244/** Load the chat database from a file.
245 * \param fp pointer to file to read from.
246 * \retval 1 success
247 * \retval 0 failure
248 */
249int
250load_chatdb(FILE * fp)
251{
252  int i, flags;
253  CHAN *ch;
254  char buff[20];
255  char *chat_timestamp;
256
257  i = fgetc(fp);
258  if (i == EOF) {
259    do_rawlog(LT_ERR, T("CHAT: Invalid database format!"));
260    longjmp(db_err, 1);
261  } else if (i != '+') {
262    ungetc(i, fp);
263    return load_chatdb_oldstyle(fp);
264  }
265
266  i = fgetc(fp);
267
268  if (i != 'V') {
269    do_rawlog(LT_ERR, T("CHAT: Invalid database format!"));
270    longjmp(db_err, 1);
271  }
272
273  flags = getref(fp);
274
275  db_read_this_labeled_string(fp, "savedtime", &chat_timestamp);
276
277  if (strcmp(chat_timestamp, db_timestamp))
278    do_rawlog(LT_ERR,
279              T
280              ("CHAT: warning: chatdb and game db were saved at different times!"));
281
282  /* How many channels? */
283  db_read_this_labeled_int(fp, "channels", &num_channels);
284  if (num_channels > MAX_CHANNELS)
285    return 0;
286
287  /* Load all channels */
288  for (i = 0; i < num_channels; i++) {
289    ch = new_channel();
290    if (!ch)
291      return 0;
292    if (!load_labeled_channel(fp, ch)) {
293      do_rawlog(LT_ERR, T("Unable to load channel %d."), i);
294      free_channel(ch);
295      return 0;
296    }
297    insert_channel(&ch);
298  }
299  num_channels = i;
300
301  /* Check for **END OF DUMP*** */
302  fgets(buff, sizeof buff, fp);
303  if (!buff)
304    do_rawlog(LT_ERR, T("CHAT: No end-of-dump marker in the chat database."));
305  else if (strcmp(buff, EOD) != 0)
306    do_rawlog(LT_ERR, T("CHAT: Trailing garbage in the chat database."));
307
308  return 1;
309}
310
311
312/* Malloc memory for a new channel, and initialize it */
313static CHAN *
314new_channel(void)
315{
316  CHAN *ch;
317
318  ch = (CHAN *) mush_malloc(sizeof(CHAN), "CHAN");
319  if (!ch)
320    return NULL;
321  ch->name[0] = '\0';
322  ch->title[0] = '\0';
323  ChanType(ch) = CHANNEL_DEFAULT_FLAGS;
324  ChanCreator(ch) = NOTHING;
325  ChanCost(ch) = CHANNEL_COST;
326  ChanNext(ch) = NULL;
327  ChanNumMsgs(ch) = 0;
328  /* By default channels are public but mod-lock'd to the creator */
329  ChanJoinLock(ch) = TRUE_BOOLEXP;
330  ChanSpeakLock(ch) = TRUE_BOOLEXP;
331  ChanSeeLock(ch) = TRUE_BOOLEXP;
332  ChanHideLock(ch) = TRUE_BOOLEXP;
333  ChanModLock(ch) = TRUE_BOOLEXP;
334  ChanNumUsers(ch) = 0;
335  ChanMaxUsers(ch) = 0;
336  ChanUsers(ch) = NULL;
337  ChanBufferQ(ch) = NULL;
338  return ch;
339}
340
341
342
343/* Malloc memory for a new user, and initialize it */
344static CHANUSER *
345new_user(dbref who)
346{
347  CHANUSER *u;
348  u = (CHANUSER *) mush_malloc(sizeof(CHANUSER), "CHANUSER");
349  if (!u)
350    mush_panic("Couldn't allocate memory in new_user in extchat.c");
351  CUdbref(u) = who;
352  CUtype(u) = CU_DEFAULT_FLAGS;
353  u->title[0] = '\0';
354  CUnext(u) = NULL;
355  return u;
356}
357
358/* Free memory from a channel */
359static void
360free_channel(CHAN *c)
361{
362  CHANUSER *u, *unext;
363  if (!c)
364    return;
365  free_boolexp(ChanJoinLock(c));
366  free_boolexp(ChanSpeakLock(c));
367  free_boolexp(ChanHideLock(c));
368  free_boolexp(ChanSeeLock(c));
369  free_boolexp(ChanModLock(c));
370  u = ChanUsers(c);
371  while (u) {
372    unext = u->next;
373    free_user(u);
374    u = unext;
375  }
376  return;
377}
378
379/* Free memory from a channel user */
380static void
381free_user(CHANUSER *u)
382{
383  if (u)
384    mush_free(u, "CHANUSER");
385}
386
387/* Load in a single channel into position i. Return 1 if
388 * successful, 0 otherwise.
389 */
390static int
391load_channel(FILE * fp, CHAN *ch)
392{
393  strcpy(ChanName(ch), getstring_noalloc(fp));
394  if (feof(fp))
395    return 0;
396  strcpy(ChanTitle(ch), getstring_noalloc(fp));
397  ChanType(ch) = (privbits) getref(fp);
398  ChanCreator(ch) = getref(fp);
399  ChanCost(ch) = getref(fp);
400  ChanNumMsgs(ch) = 0;
401  ChanJoinLock(ch) = getboolexp(fp, chan_join_lock);
402  ChanSpeakLock(ch) = getboolexp(fp, chan_speak_lock);
403  ChanModLock(ch) = getboolexp(fp, chan_mod_lock);
404  ChanSeeLock(ch) = getboolexp(fp, chan_see_lock);
405  ChanHideLock(ch) = getboolexp(fp, chan_hide_lock);
406  ChanNumUsers(ch) = getref(fp);
407  ChanMaxUsers(ch) = ChanNumUsers(ch);
408  ChanUsers(ch) = NULL;
409  if (ChanNumUsers(ch) > 0)
410    ChanNumUsers(ch) = load_chanusers(fp, ch);
411  return 1;
412}
413
414/* Load in a single channel into position i. Return 1 if
415 * successful, 0 otherwise.
416 */
417static int
418load_labeled_channel(FILE * fp, CHAN *ch)
419{
420  char *tmp;
421  int i;
422  dbref d;
423  char *label, *value;
424
425  db_read_this_labeled_string(fp, "name", &tmp);
426  strcpy(ChanName(ch), tmp);
427  db_read_this_labeled_string(fp, "description", &tmp);
428  strcpy(ChanTitle(ch), tmp);
429  db_read_this_labeled_int(fp, "flags", &i);
430  ChanType(ch) = (privbits) i;
431  db_read_this_labeled_dbref(fp, "creator", &d);
432  ChanCreator(ch) = d;
433  db_read_this_labeled_int(fp, "cost", &i);
434  ChanCost(ch) = i;
435  ChanNumMsgs(ch) = 0;
436  while (1) {
437    db_read_labeled_string(fp, &label, &value);
438    if (strcmp(label, "lock"))
439      break;
440    else if (strcmp(value, "join") == 0)
441      ChanJoinLock(ch) = getboolexp(fp, chan_join_lock);
442    else if (strcmp(value, "speak") == 0)
443      ChanSpeakLock(ch) = getboolexp(fp, chan_speak_lock);
444    else if (strcmp(value, "modify") == 0)
445      ChanModLock(ch) = getboolexp(fp, chan_mod_lock);
446    else if (strcmp(value, "see") == 0)
447      ChanSeeLock(ch) = getboolexp(fp, chan_see_lock);
448    else if (strcmp(value, "hide") == 0)
449      ChanHideLock(ch) = getboolexp(fp, chan_hide_lock);
450  }
451  ChanNumUsers(ch) = parse_integer(value);
452  ChanMaxUsers(ch) = ChanNumUsers(ch);
453  ChanUsers(ch) = NULL;
454  if (ChanNumUsers(ch) > 0)
455    ChanNumUsers(ch) = load_labeled_chanusers(fp, ch);
456  return 1;
457}
458
459
460/* Load the *channel's user list. Return number of users on success, or 0 */
461static int
462load_chanusers(FILE * fp, CHAN *ch)
463{
464  int i, num = 0;
465  CHANUSER *user;
466  dbref player;
467  for (i = 0; i < ChanNumUsers(ch); i++) {
468    player = getref(fp);
469    /* Don't bother if the player isn't a valid dbref or the wrong type */
470    if (GoodObject(player) && Chan_Ok_Type(ch, player)) {
471      user = new_user(player);
472      CUtype(user) = getref(fp);
473      strcpy(CUtitle(user), getstring_noalloc(fp));
474      CUnext(user) = NULL;
475      if (insert_user(user, ch))
476        num++;
477    } else {
478      /* But be sure to read (and discard) the player's info */
479      do_log(LT_ERR, 0, 0, T("Bad object #%d removed from channel %s"),
480             player, ChanName(ch));
481      (void) getref(fp);
482      (void) getstring_noalloc(fp);
483    }
484  }
485  return num;
486}
487
488/* Load the *channel's user list. Return number of users on success, or 0 */
489static int
490load_labeled_chanusers(FILE * fp, CHAN *ch)
491{
492  int i, num = 0, n;
493  char *tmp;
494  CHANUSER *user;
495  dbref player;
496  for (i = 0; i < ChanNumUsers(ch); i++) {
497    db_read_this_labeled_dbref(fp, "dbref", &player);
498    /* Don't bother if the player isn't a valid dbref or the wrong type */
499    if (GoodObject(player) && Chan_Ok_Type(ch, player)) {
500      user = new_user(player);
501      db_read_this_labeled_int(fp, "flags", &n);
502      CUtype(user) = n;
503      db_read_this_labeled_string(fp, "title", &tmp);
504      strcpy(CUtitle(user), tmp);
505      CUnext(user) = NULL;
506      if (insert_user(user, ch))
507        num++;
508    } else {
509      /* But be sure to read (and discard) the player's info */
510      do_log(LT_ERR, 0, 0, T("Bad object #%d removed from channel %s"),
511             player, ChanName(ch));
512      db_read_this_labeled_int(fp, "type", &n);
513      db_read_this_labeled_string(fp, "title", &tmp);
514    }
515  }
516  return num;
517}
518
519
520/* Insert the channel onto the list of channels, sorted by name */
521static void
522insert_channel(CHAN **ch)
523{
524  CHAN *p;
525  char cleanname[CHAN_NAME_LEN];
526  char cleanp[CHAN_NAME_LEN];
527
528  if (!ch || !*ch)
529    return;
530
531  /* If there's no users on the list, or if the first user is already
532   * alphabetically greater, user should be the first entry on the list */
533  /* No channels? */
534  if (!channels) {
535    channels = *ch;
536    channels->next = NULL;
537    return;
538  }
539  p = channels;
540  /* First channel? */
541  strcpy(cleanp, remove_markup(ChanName(p), NULL));
542  strcpy(cleanname, remove_markup(ChanName(*ch), NULL));
543  if (strcasecoll(cleanp, cleanname) > 0) {
544    channels = *ch;
545    channels->next = p;
546    return;
547  }
548  /* Otherwise, find which user this user should be inserted after */
549  while (p->next) {
550    strcpy(cleanp, remove_markup(ChanName(p->next), NULL));
551    if (strcasecoll(cleanp, cleanname) > 0)
552      break;
553    p = p->next;
554  }
555  (*ch)->next = p->next;
556  p->next = *ch;
557  return;
558}
559
560/* Remove a channel from the list, but don't free it */
561static void
562remove_channel(CHAN *ch)
563{
564  CHAN *p;
565
566  if (!ch)
567    return;
568  if (!channels)
569    return;
570  if (channels == ch) {
571    /* First channel */
572    channels = ch->next;
573    return;
574  }
575  /* Otherwise, find the channel before this one */
576  for (p = channels; p->next && (p->next != ch); p = p->next) ;
577
578  if (p->next) {
579    p->next = ch->next;
580  }
581  return;
582}
583
584/* Insert the channel onto the list of channels on a given object,
585 * sorted by name
586 */
587static void
588insert_obj_chan(dbref who, CHAN **ch)
589{
590  CHANLIST *p;
591  CHANLIST *tmp;
592
593  if (!ch || !*ch)
594    return;
595
596  tmp = new_chanlist();
597  if (!tmp)
598    return;
599  tmp->chan = *ch;
600  /* If there's no channels on the list, or if the first channel is already
601   * alphabetically greater, chan should be the first entry on the list */
602  /* No channels? */
603  if (!Chanlist(who)) {
604    tmp->next = NULL;
605    s_Chanlist(who, tmp);
606    return;
607  }
608  p = Chanlist(who);
609  /* First channel? */
610  if (strcasecoll(ChanName(p->chan), ChanName(*ch)) > 0) {
611    tmp->next = p;
612    s_Chanlist(who, tmp);
613    return;
614  } else if (!strcasecmp(ChanName(p->chan), ChanName(*ch))) {
615    /* Don't add the same channel twice! */
616    free_chanlist(tmp);
617  } else {
618    /* Otherwise, find which channel this channel should be inserted after */
619    for (;
620         p->next
621         && (strcasecoll(ChanName(p->next->chan), ChanName(*ch)) < 0);
622         p = p->next) ;
623    if (p->next && !strcasecmp(ChanName(p->next->chan), ChanName(*ch))) {
624      /* Don't add the same channel twice! */
625      free_chanlist(tmp);
626    } else {
627      tmp->next = p->next;
628      p->next = tmp;
629    }
630  }
631  return;
632}
633
634/* Remove a channel from the obj's chanlist, and free the chanlist ptr */
635static void
636remove_obj_chan(dbref who, CHAN *ch)
637{
638  CHANLIST *p, *q;
639
640  if (!ch)
641    return;
642  p = Chanlist(who);
643  if (!p)
644    return;
645  if (p->chan == ch) {
646    /* First channel */
647    s_Chanlist(who, p->next);
648    free_chanlist(p);
649    return;
650  }
651  /* Otherwise, find the channel before this one */
652  for (; p->next && (p->next->chan != ch); p = p->next) ;
653
654  if (p->next) {
655    q = p->next;
656    p->next = p->next->next;
657    free_chanlist(q);
658  }
659  return;
660}
661
662/** Remove all channels from the obj's chanlist, freeing them.
663 * \param thing object to have channels removed from.
664 */
665void
666remove_all_obj_chan(dbref thing)
667{
668  CHANLIST *p, *nextp;
669  for (p = Chanlist(thing); p; p = nextp) {
670    nextp = p->next;
671    remove_user_by_dbref(thing, p->chan);
672  }
673  return;
674}
675
676
677static CHANLIST *
678new_chanlist(void)
679{
680  CHANLIST *c;
681  c = (CHANLIST *) mush_malloc(sizeof(CHANLIST), "CHANLIST");
682  if (!c)
683    return NULL;
684  c->chan = NULL;
685  c->next = NULL;
686  return c;
687}
688
689static void
690free_chanlist(CHANLIST *cl)
691{
692  mush_free(cl, "CHANLIST");
693}
694
695
696/* Insert the user onto the channel's list, sorted by the user's name */
697static int
698insert_user(CHANUSER *user, CHAN *ch)
699{
700  CHANUSER *p;
701
702  if (!user || !ch)
703    return 0;
704
705  /* If there's no users on the list, or if the first user is already
706   * alphabetically greater, user should be the first entry on the list */
707  p = ChanUsers(ch);
708  if (!p || (strcasecoll(Name(CUdbref(p)), Name(CUdbref(user))) > 0)) {
709    user->next = ChanUsers(ch);
710    ChanUsers(ch) = user;
711  } else {
712    /* Otherwise, find which user this user should be inserted after */
713    for (;
714         p->next
715         && (strcasecoll(Name(CUdbref(p->next)), Name(CUdbref(user))) <= 0);
716         p = p->next) ;
717    if (CUdbref(p) == CUdbref(user)) {
718      /* Don't add the same user twice! */
719      mush_free((Malloc_t) user, "CHANUSER");
720      return 0;
721    } else {
722      user->next = p->next;
723      p->next = user;
724    }
725  }
726  insert_obj_chan(CUdbref(user), &ch);
727  return 1;
728}
729
730/* Remove a user from a channel list, and free it */
731static int
732remove_user(CHANUSER *u, CHAN *ch)
733{
734  CHANUSER *p;
735  dbref who;
736
737  if (!ch || !u)
738    return 0;
739  p = ChanUsers(ch);
740  if (!p)
741    return 0;
742  who = CUdbref(u);
743  if (p == u) {
744    /* First user */
745    ChanUsers(ch) = p->next;
746    free_user(u);
747  } else {
748    /* Otherwise, find the user before this one */
749    for (; p->next && (p->next != u); p = p->next) ;
750
751    if (p->next) {
752      p->next = u->next;
753      free_user(u);
754    } else
755      return 0;
756  }
757
758  /* Now remove the channel from the user's chanlist */
759  remove_obj_chan(who, ch);
760  ChanNumUsers(ch)--;
761  return 1;
762}
763
764
765/** Write the chat database to disk.
766 * \param fp pointer to file to write to.
767 * \retval 1 success
768 * \retval 0 failure
769 */
770int
771save_chatdb(FILE * fp)
772{
773  CHAN *ch;
774  int default_flags = 0;
775
776  /* How many channels? */
777  OUTPUT(fprintf(fp, "+V%d\n", default_flags));
778  db_write_labeled_string(fp, "savedtime", show_time(mudtime, 1));
779  db_write_labeled_int(fp, "channels", num_channels);
780  for (ch = channels; ch; ch = ch->next) {
781    save_channel(fp, ch);
782  }
783  OUTPUT(fputs(EOD, fp));
784  return 1;
785}
786
787/* Save a single channel. Return 1 if  successful, 0 otherwise.
788 */
789static int
790save_channel(FILE * fp, CHAN *ch)
791{
792  CHANUSER *cu;
793
794  db_write_labeled_string(fp, " name", ChanName(ch));
795  db_write_labeled_string(fp,