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 }