/ circle3.1 / src / mail.c
mail.c
  1  /* ************************************************************************
  2  *   File: mail.c                                        Part of CircleMUD *
  3  *  Usage: Internal funcs and player spec-procs of mud-mail system         *
  4  *                                                                         *
  5  *  All rights reserved.  See license.doc for complete information.        *
  6  *                                                                         *
  7  *  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
  8  *  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
  9  ************************************************************************ */
 10  
 11  /******* MUD MAIL SYSTEM MAIN FILE ***************************************
 12  
 13  Written by Jeremy Elson (jelson@circlemud.org)
 14  
 15  *************************************************************************/
 16  
 17  #include "conf.h"
 18  #include "sysdep.h"
 19  
 20  #include "structs.h"
 21  #include "utils.h"
 22  #include "comm.h"
 23  #include "db.h"
 24  #include "interpreter.h"
 25  #include "handler.h"
 26  #include "mail.h"
 27  
 28  
 29  /* external variables */
 30  extern int no_mail;
 31  
 32  /* external functions */
 33  SPECIAL(postmaster);
 34  
 35  /* local globals */
 36  mail_index_type *mail_index = NULL;	/* list of recs in the mail file  */
 37  position_list_type *free_list = NULL;	/* list of free positions in file */
 38  long file_end_pos = 0;			/* length of file */
 39  
 40  /* local functions */
 41  void postmaster_send_mail(struct char_data *ch, struct char_data *mailman, int cmd, char *arg);
 42  void postmaster_check_mail(struct char_data *ch, struct char_data *mailman, int cmd, char *arg);
 43  void postmaster_receive_mail(struct char_data *ch, struct char_data *mailman, int cmd, char *arg);
 44  void push_free_list(long pos);
 45  long pop_free_list(void);
 46  void clear_free_list(void);
 47  mail_index_type *find_char_in_index(long searchee);
 48  void write_to_file(void *buf, int size, long filepos);
 49  void read_from_file(void *buf, int size, long filepos);
 50  void index_mail(long id_to_index, long pos);
 51  int mail_recip_ok(const char *name);
 52  
 53  /* -------------------------------------------------------------------------- */
 54  
 55  int mail_recip_ok(const char *name)
 56  {
 57    struct char_file_u tmp_store;
 58    struct char_data *victim;
 59    int ret = FALSE;
 60  
 61    CREATE(victim, struct char_data, 1);
 62    clear_char(victim);
 63    if (load_char(name, &tmp_store) >= 0) {
 64      store_to_char(&tmp_store, victim);
 65      char_to_room(victim, 0);
 66      if (!PLR_FLAGGED(victim, PLR_DELETED))
 67        ret = TRUE;
 68      extract_char_final(victim);
 69    } else 
 70      free(victim);
 71    return ret;
 72  }
 73  
 74  /*
 75   * void push_free_list(long #1)
 76   * #1 - What byte offset into the file the block resides.
 77   *
 78   * Net effect is to store a list of free blocks in the mail file in a linked
 79   * list.  This is called when people receive their messages and at startup
 80   * when the list is created.
 81   */
 82  void push_free_list(long pos)
 83  {
 84    position_list_type *new_pos;
 85  
 86    CREATE(new_pos, position_list_type, 1);
 87    new_pos->position = pos;
 88    new_pos->next = free_list;
 89    free_list = new_pos;
 90  }
 91  
 92  
 93  /*
 94   * long pop_free_list(none)
 95   * Returns the offset of a free block in the mail file.
 96   *
 97   * Typically used whenever a person mails a message.  The blocks are not
 98   * guaranteed to be sequential or in any order at all.
 99   */
100  long pop_free_list(void)
101  {
102    position_list_type *old_pos;
103    long return_value;
104  
105    /*
106     * If we don't have any free blocks, we append to the file.
107     */
108    if ((old_pos = free_list) == NULL)
109      return (file_end_pos);
110  
111    /* Save the offset of the free block. */
112    return_value = free_list->position;
113    /* Remove this block from the free list. */
114    free_list = old_pos->next;
115    /* Get rid of the memory the node took. */
116    free(old_pos);
117    /* Give back the free offset. */
118    return (return_value);
119  }
120  
121  
122  void clear_free_list(void)
123  {
124    while (free_list)
125      pop_free_list();
126  }
127  
128  
129  /*
130   * main_index_type *find_char_in_index(long #1)
131   * #1 - The idnum of the person to look for.
132   * Returns a pointer to the mail block found.
133   *
134   * Finds the first mail block for a specific person based on id number.
135   */
136  mail_index_type *find_char_in_index(long searchee)
137  {
138    mail_index_type *tmp;
139  
140    if (searchee < 0) {
141      log("SYSERR: Mail system -- non fatal error #1 (searchee == %ld).", searchee);
142      return (NULL);
143    }
144    for (tmp = mail_index; (tmp && tmp->recipient != searchee); tmp = tmp->next);
145  
146    return (tmp);
147  }
148  
149  
150  /*
151   * void write_to_file(void * #1, int #2, long #3)
152   * #1 - A pointer to the data to write, usually the 'block' record.
153   * #2 - How much to write (because we'll write NUL terminated strings.)
154   * #3 - What offset (block position) in the file to write to.
155   *
156   * Writes a mail block back into the database at the given location.
157   */
158  void write_to_file(void *buf, int size, long filepos)
159  {
160    FILE *mail_file;
161  
162    if (filepos % BLOCK_SIZE) {
163      log("SYSERR: Mail system -- fatal error #2!!! (invalid file position %ld)", filepos);
164      no_mail = TRUE;
165      return;
166    }
167    if (!(mail_file = fopen(MAIL_FILE, "r+b"))) {
168      log("SYSERR: Unable to open mail file '%s'.", MAIL_FILE);
169      no_mail = TRUE;
170      return;
171    }
172    fseek(mail_file, filepos, SEEK_SET);
173    fwrite(buf, size, 1, mail_file);
174  
175    /* find end of file */
176    fseek(mail_file, 0L, SEEK_END);
177    file_end_pos = ftell(mail_file);
178    fclose(mail_file);
179    return;
180  }
181  
182  
183  /*
184   * void read_from_file(void * #1, int #2, long #3)
185   * #1 - A pointer to where we should store the data read.
186   * #2 - How large the block we're reading is.
187   * #3 - What position in the file to read.
188   *
189   * This reads a block from the mail database file.
190   */
191  void read_from_file(void *buf, int size, long filepos)
192  {
193    FILE *mail_file;
194  
195    if (filepos % BLOCK_SIZE) {
196      log("SYSERR: Mail system -- fatal error #3!!! (invalid filepos read %ld)", filepos);
197      no_mail = TRUE;
198      return;
199    }
200    if (!(mail_file = fopen(MAIL_FILE, "r+b"))) {
201      log("SYSERR: Unable to open mail file '%s'.", MAIL_FILE);
202      no_mail = TRUE;
203      return;
204    }
205  
206    fseek(mail_file, filepos, SEEK_SET);
207    fread(buf, size, 1, mail_file);
208    fclose(mail_file);
209    return;
210  }
211  
212  
213  void index_mail(long id_to_index, long pos)
214  {
215    mail_index_type *new_index;
216    position_list_type *new_position;
217  
218    if (id_to_index < 0) {
219      log("SYSERR: Mail system -- non-fatal error #4. (id_to_index == %ld)", id_to_index);
220      return;
221    }
222    if (!(new_index = find_char_in_index(id_to_index))) {
223      /* name not already in index.. add it */
224      CREATE(new_index, mail_index_type, 1);
225      new_index->recipient = id_to_index;
226      new_index->list_start = NULL;
227  
228      /* add to front of list */
229      new_index->next = mail_index;
230      mail_index = new_index;
231    }
232    /* now, add this position to front of position list */
233    CREATE(new_position, position_list_type, 1);
234    new_position->position = pos;
235    new_position->next = new_index->list_start;
236    new_index->list_start = new_position;
237  }
238  
239  
240  /*
241   * int scan_file(none)
242   * Returns false if mail file is corrupted or true if everything correct.
243   *
244   * This is called once during boot-up.  It scans through the mail file
245   * and indexes all entries currently in the mail file.
246   */
247  int scan_file(void)
248  {
249    FILE *mail_file;
250    header_block_type next_block;
251    int total_messages = 0, block_num = 0;
252  
253    if (!(mail_file = fopen(MAIL_FILE, "rb"))) {
254      log("   Mail file non-existant... creating new file.");
255      touch(MAIL_FILE);
256      return (1);
257    }
258    while (fread(&next_block, sizeof(header_block_type), 1, mail_file)) {
259      if (next_block.block_type == HEADER_BLOCK) {
260        index_mail(next_block.header_data.to, block_num * BLOCK_SIZE);
261        total_messages++;
262      } else if (next_block.block_type == DELETED_BLOCK)
263        push_free_list(block_num * BLOCK_SIZE);
264      block_num++;
265    }
266  
267    file_end_pos = ftell(mail_file);
268    fclose(mail_file);
269    log("   %ld bytes read.", file_end_pos);
270    if (file_end_pos % BLOCK_SIZE) {
271      log("SYSERR: Error booting mail system -- Mail file corrupt!");
272      log("SYSERR: Mail disabled!");
273      return (0);
274    }
275    log("   Mail file read -- %d messages.", total_messages);
276    return (1);
277  }				/* end of scan_file */
278  
279  
280  /*
281   * int has_mail(long #1)
282   * #1 - id number of the person to check for mail.
283   * Returns true or false.
284   *
285   * A simple little function which tells you if the guy has mail or not.
286   */
287  int has_mail(long recipient)
288  {
289    return (find_char_in_index(recipient) != NULL);
290  }
291  
292  
293  /*
294   * void store_mail(long #1, long #2, char * #3)
295   * #1 - id number of the person to mail to.
296   * #2 - id number of the person the mail is from.
297   * #3 - The actual message to send.
298   *
299   * call store_mail to store mail.  (hard, huh? :-) )  Pass 3 arguments:
300   * who the mail is to (long), who it's from (long), and a pointer to the
301   * actual message text (char *).
302   */
303  void store_mail(long to, long from, char *message_pointer)
304  {
305    header_block_type header;
306    data_block_type data;
307    long last_address, target_address;
308    char *msg_txt = message_pointer;
309    int bytes_written, total_length = strlen(message_pointer);
310  
311    if ((sizeof(header_block_type) != sizeof(data_block_type)) ||
312        (sizeof(header_block_type) != BLOCK_SIZE)) {
313      core_dump();
314      return;
315    }
316  
317    if (from < 0 || to < 0 || !*message_pointer) {
318      log("SYSERR: Mail system -- non-fatal error #5. (from == %ld, to == %ld)", from, to);
319      return;
320    }
321    memset((char *) &header, 0, sizeof(header));	/* clear the record */
322    header.block_type = HEADER_BLOCK;
323    header.header_data.next_block = LAST_BLOCK;
324    header.header_data.from = from;
325    header.header_data.to = to;
326    header.header_data.mail_time = time(0);
327    strncpy(header.txt, msg_txt, HEADER_BLOCK_DATASIZE);	/* strncpy: OK (h.txt:HEADER_BLOCK_DATASIZE+1) */
328    header.txt[HEADER_BLOCK_DATASIZE] = '\0';
329  
330    target_address = pop_free_list();	/* find next free block */
331    index_mail(to, target_address);	/* add it to mail index in memory */
332    write_to_file(&header, BLOCK_SIZE, target_address);
333  
334    if (strlen(msg_txt) <= HEADER_BLOCK_DATASIZE)
335      return;			/* that was the whole message */
336  
337    bytes_written = HEADER_BLOCK_DATASIZE;
338    msg_txt += HEADER_BLOCK_DATASIZE;	/* move pointer to next bit of text */
339  
340    /*
341     * find the next block address, then rewrite the header to reflect where
342     * the next block is.
343     */
344    last_address = target_address;
345    target_address = pop_free_list();
346    header.header_data.next_block = target_address;
347    write_to_file(&header, BLOCK_SIZE, last_address);
348  
349    /* now write the current data block */
350    memset((char *) &data, 0, sizeof(data));	/* clear the record */
351    data.block_type = LAST_BLOCK;
352    strncpy(data.txt, msg_txt, DATA_BLOCK_DATASIZE);	/* strncpy: OK (d.txt:DATA_BLOCK_DATASIZE+1) */
353    data.txt[DATA_BLOCK_DATASIZE] = '\0';
354    write_to_file(&data, BLOCK_SIZE, target_address);
355    bytes_written += strlen(data.txt);
356    msg_txt += strlen(data.txt);
357  
358    /*
359     * if, after 1 header block and 1 data block there is STILL part of the
360     * message left to write to the file, keep writing the new data blocks and
361     * rewriting the old data blocks to reflect where the next block is.  Yes,
362     * this is kind of a hack, but if the block size is big enough it won't
363     * matter anyway.  Hopefully, MUD players won't pour their life stories out
364     * into the Mud Mail System anyway.
365     * 
366     * Note that the block_type data field in data blocks is either a number >=0,
367     * meaning a link to the next block, or LAST_BLOCK flag (-2) meaning the
368     * last block in the current message.  This works much like DOS' FAT.
369     */
370    while (bytes_written < total_length) {
371      last_address = target_address;
372      target_address = pop_free_list();
373  
374      /* rewrite the previous block to link it to the next */
375      data.block_type = target_address;
376      write_to_file(&data, BLOCK_SIZE, last_address);
377  
378      /* now write the next block, assuming it's the last.  */
379      data.block_type = LAST_BLOCK;
380      strncpy(data.txt, msg_txt, DATA_BLOCK_DATASIZE);	/* strncpy: OK (d.txt:DATA_BLOCK_DATASIZE+1) */
381      data.txt[DATA_BLOCK_DATASIZE] = '\0';
382      write_to_file(&data, BLOCK_SIZE, target_address);
383  
384      bytes_written += strlen(data.txt);
385      msg_txt += strlen(data.txt);
386    }
387  }				/* store mail */
388  
389  
390  /*
391   * char *read_delete(long #1)
392   * #1 - The id number of the person we're checking mail for.
393   * Returns the message text of the mail received.
394   *
395   * Retrieves one messsage for a player. The mail is then discarded from
396   * the file and the mail index.
397   */
398  char *read_delete(long recipient)
399  {
400    header_block_type header;
401    data_block_type data;
402    mail_index_type *mail_pointer, *prev_mail;
403    position_list_type *position_pointer;
404    long mail_address, following_block;
405    char *tmstr, buf[MAX_MAIL_SIZE + 256];	/* + header */
406    char *from, *to;
407  
408    if (recipient < 0) {
409      log("SYSERR: Mail system -- non-fatal error #6. (recipient: %ld)", recipient);
410      return (NULL);
411    }
412    if (!(mail_pointer = find_char_in_index(recipient))) {
413      log("SYSERR: Mail system -- post office spec_proc error?  Error #7. (invalid character in index)");
414      return (NULL);
415    }
416    if (!(position_pointer = mail_pointer->list_start)) {
417      log("SYSERR: Mail system -- non-fatal error #8. (invalid position pointer %p)", position_pointer);
418      return (NULL);
419    }
420    if (!(position_pointer->next)) {	/* just 1 entry in list. */
421      mail_address = position_pointer->position;
422      free(position_pointer);
423  
424      /* now free up the actual name entry */
425      if (mail_index == mail_pointer) {	/* name is 1st in list */
426        mail_index = mail_pointer->next;
427        free(mail_pointer);
428      } else {
429        /* find entry before the one we're going to del */
430        for (prev_mail = mail_index;
431  	   prev_mail->next != mail_pointer;
432  	   prev_mail = prev_mail->next);
433        prev_mail->next = mail_pointer->next;
434        free(mail_pointer);
435      }
436    } else {
437      /* move to next-to-last record */
438      while (position_pointer->next->next)
439        position_pointer = position_pointer->next;
440      mail_address = position_pointer->next->position;
441      free(position_pointer->next);
442      position_pointer->next = NULL;
443    }
444  
445    /* ok, now lets do some readin'! */
446    read_from_file(&header, BLOCK_SIZE, mail_address);
447  
448    if (header.block_type != HEADER_BLOCK) {
449      log("SYSERR: Oh dear. (Header block %ld != %d)", header.block_type, HEADER_BLOCK);
450      no_mail = TRUE;
451      log("SYSERR: Mail system disabled!  -- Error #9. (Invalid header block.)");
452      return (NULL);
453    }
454    tmstr = asctime(localtime(&header.header_data.mail_time));
455    *(tmstr + strlen(tmstr) - 1) = '\0';
456  
457    from = get_name_by_id(header.header_data.from);
458    to = get_name_by_id(recipient);
459  
460    snprintf(buf, sizeof(buf),
461  	" * * * * Midgaard Mail System * * * *\r\n"
462  	"Date: %s\r\n"
463  	"  To: %s\r\n"
464  	"From: %s\r\n"
465  	"\r\n"
466  	"%s",
467  
468  	tmstr,
469  	to ? to : "Unknown",
470  	from ? from : "Unknown",
471  	header.txt
472  	);
473    following_block = header.header_data.next_block;
474  
475    /* mark the block as deleted */
476    header.block_type = DELETED_BLOCK;
477    write_to_file(&header, BLOCK_SIZE, mail_address);
478    push_free_list(mail_address);
479  
480    while (following_block != LAST_BLOCK) {
481      read_from_file(&data, BLOCK_SIZE, following_block);
482  
483      strcat(buf, data.txt);	/* strcat: OK (data.txt:DATA_BLOCK_DATASIZE < buf:MAX_MAIL_SIZE) */
484      mail_address = following_block;
485      following_block = data.block_type;
486      data.block_type = DELETED_BLOCK;
487      write_to_file(&data, BLOCK_SIZE, mail_address);
488      push_free_list(mail_address);
489    }
490  
491    return strdup(buf);
492  }
493  
494  
495  /****************************************************************
496  * Below is the spec_proc for a postmaster using the above       *
497  * routines.  Written by Jeremy Elson (jelson@circlemud.org) *
498  ****************************************************************/
499  
500  SPECIAL(postmaster)
501  {
502    if (!ch->desc || IS_NPC(ch))
503      return (0);			/* so mobs don't get caught here */
504  
505    if (!(CMD_IS("mail") || CMD_IS("check") || CMD_IS("receive")))
506      return (0);
507  
508    if (no_mail) {
509      send_to_char(ch, "Sorry, the mail system is having technical difficulties.\r\n");
510      return (0);
511    }
512  
513    if (CMD_IS("mail")) {
514      postmaster_send_mail(ch, (struct char_data *)me, cmd, argument);
515      return (1);
516    } else if (CMD_IS("check")) {
517      postmaster_check_mail(ch, (struct char_data *)me, cmd, argument);
518      return (1);
519    } else if (CMD_IS("receive")) {
520      postmaster_receive_mail(ch, (struct char_data *)me, cmd, argument);
521      return (1);
522    } else
523      return (0);
524  }
525  
526  
527  void postmaster_send_mail(struct char_data *ch, struct char_data *mailman,
528  			  int cmd, char *arg)
529  {
530    long recipient;
531    char buf[MAX_INPUT_LENGTH], **mailwrite;
532  
533    if (GET_LEVEL(ch) < MIN_MAIL_LEVEL) {
534      snprintf(buf, sizeof(buf), "$n tells you, 'Sorry, you have to be level %d to send mail!'", MIN_MAIL_LEVEL);
535      act(buf, FALSE, mailman, 0, ch, TO_VICT);
536      return;
537    }
538    one_argument(arg, buf);
539  
540    if (!*buf) {			/* you'll get no argument from me! */
541      act("$n tells you, 'You need to specify an addressee!'",
542  	FALSE, mailman, 0, ch, TO_VICT);
543      return;
544    }
545    if (GET_GOLD(ch) < STAMP_PRICE) {
546      snprintf(buf, sizeof(buf), "$n tells you, 'A stamp costs %d coin%s.'\r\n"
547  	    "$n tells you, '...which I see you can't afford.'", STAMP_PRICE,
548              STAMP_PRICE == 1 ? "" : "s");
549      act(buf, FALSE, mailman, 0, ch, TO_VICT);
550      return;
551    }
552    if ((recipient = get_id_by_name(buf)) < 0 || !mail_recip_ok(buf)) {
553      act("$n tells you, 'No one by that name is registered here!'",
554  	FALSE, mailman, 0, ch, TO_VICT);
555      return;
556    }
557    act("$n starts to write some mail.", TRUE, ch, 0, 0, TO_ROOM);
558    snprintf(buf, sizeof(buf), "$n tells you, 'I'll take %d coins for the stamp.'\r\n"
559         "$n tells you, 'Write your message, use @ on a new line when done.'",
560  	  STAMP_PRICE);
561  
562    act(buf, FALSE, mailman, 0, ch, TO_VICT);
563    GET_GOLD(ch) -= STAMP_PRICE;
564    SET_BIT(PLR_FLAGS(ch), PLR_MAILING);	/* string_write() sets writing. */
565  
566    /* Start writing! */
567    CREATE(mailwrite, char *, 1);
568    string_write(ch->desc, mailwrite, MAX_MAIL_SIZE, recipient, NULL);
569  }
570  
571  
572  void postmaster_check_mail(struct char_data *ch, struct char_data *mailman,
573  			  int cmd, char *arg)
574  {
575    if (has_mail(GET_IDNUM(ch)))
576      act("$n tells you, 'You have mail waiting.'", FALSE, mailman, 0, ch, TO_VICT);
577    else
578      act("$n tells you, 'Sorry, you don't have any mail waiting.'", FALSE, mailman, 0, ch, TO_VICT);
579  }
580  
581  
582  void postmaster_receive_mail(struct char_data *ch, struct char_data *mailman,
583  			  int cmd, char *arg)
584  {
585    char buf[256];
586    struct obj_data *obj;
587  
588    if (!has_mail(GET_IDNUM(ch))) {
589      snprintf(buf, sizeof(buf), "$n tells you, 'Sorry, you don't have any mail waiting.'");
590      act(buf, FALSE, mailman, 0, ch, TO_VICT);
591      return;
592    }
593    while (has_mail(GET_IDNUM(ch))) {
594      obj = create_obj();
595      obj->item_number = NOTHING;
596      obj->name = strdup("mail paper letter");
597      obj->short_description = strdup("a piece of mail");
598      obj->description = strdup("Someone has left a piece of mail here.");
599  
600      GET_OBJ_TYPE(obj) = ITEM_NOTE;
601      GET_OBJ_WEAR(obj) = ITEM_WEAR_TAKE | ITEM_WEAR_HOLD;
602      GET_OBJ_WEIGHT(obj) = 1;
603      GET_OBJ_COST(obj) = 30;
604      GET_OBJ_RENT(obj) = 10;
605      obj->action_description = read_delete(GET_IDNUM(ch));
606  
607      if (obj->action_description == NULL)
608        obj->action_description =
609  	strdup("Mail system error - please report.  Error #11.\r\n");
610  
611      obj_to_char(obj, ch);
612  
613      act("$n gives you a piece of mail.", FALSE, mailman, 0, ch, TO_VICT);
614      act("$N gives $n a piece of mail.", FALSE, ch, 0, mailman, TO_ROOM);
615    }
616  }