/** * \file player.c * * \brief Player creation and connection for PennMUSH. * * */ #include "copyrite.h" #include "config.h" #include <stdio.h> #ifdef I_UNISTD #include <unistd.h> #endif #include <stdlib.h> #include <string.h> #ifdef I_SYS_TIME #include <sys/time.h> #else #include <time.h> #endif #ifdef I_SYS_TYPES #include <sys/types.h> #endif #include <fcntl.h> #include "conf.h" #include "externs.h" #include "mushdb.h" #include "attrib.h" #include "access.h" #include "mymalloc.h" #include "log.h" #include "dbdefs.h" #include "flags.h" #include "lock.h" #include "parse.h" #ifdef HAS_CRYPT #ifdef I_CRYPT #include <crypt.h> #else extern char *crypt(const char *, const char *); #endif #endif #include "extmail.h" #include "confmagic.h" dbref email_register_player (const char *name, const char *email, const char *host, const char *ip); static dbref make_player (const char *name, const char *password, const char *host, const char *ip); void do_password (dbref player, dbref cause, const char *old, const char *newobj); static const char pword_attr[] = "XYXXY"; extern struct db_stat_info current_state; /** Check a player's password against a given string. * \param player dbref of player. * \param password plaintext password string to check. * \retval 1 password matches (or player has no password). * \retval 0 password fails to match. */ int password_check(dbref player, const char *password) { ATTR *a; char *saved; char *passwd; /* read the password and compare it */ if (!(a = atr_get_noparent(player, pword_attr))) return 1; /* No password attribute */ saved = strdup(atr_value(a)); if (!saved) return 0; passwd = mush_crypt(password); if (strcmp(passwd, saved) != 0) { /* Nope. Try non-SHS. */ #ifdef HAS_CRYPT /* shs encryption didn't match. Try crypt(3) */ if (strcmp(crypt(password, "XX"), saved) != 0) /* Nope */ #endif /* HAS_CRYPT */ /* crypt() didn't work. Try plaintext, being sure to not * allow unencrypted entry of encrypted password */ if ((strcmp(saved, password) != 0) || (strlen(password) < 4) || ((password[0] == 'X') && (password[1] == 'X'))) { /* Nothing worked. You lose. */ free(saved); return 0; } /* Something worked. Change password to SHS-encrypted */ (void) atr_add(player, pword_attr, passwd, GOD, NOTHING); } free(saved); return 1; } /** Check to see if someone can connect to a player. * \param name name of player to connect to. * \param password password of player to connect to. * \param host host from which connection is being attempted. * \param ip ip address from which connection is being attempted. * \param errbuf buffer to return connection errors. * \return dbref of connected player object or NOTHING for failure * (with reason for failure returned in errbuf). */ dbref connect_player(const char *name, const char *password, const char *host, const char *ip, char *errbuf) { dbref player; /* Default error */ strcpy(errbuf, T("Either that player does not exist, or has a different password.")); if (!name || !*name) return NOTHING; /* validate name */ if ((player = lookup_player(name)) == NOTHING) return NOTHING; /* See if player is allowed to connect like this */ if (Going(player) || Going_Twice(player)) { do_log(LT_CONN, 0, 0, T("Connection to GOING player %s not allowed from %s (%s)"), name, host, ip); return NOTHING; } if (Guest(player) && (!Site_Can_Guest(host) || !Site_Can_Guest(ip))) { if (!Deny_Silent_Site(host, AMBIGUOUS) && !Deny_Silent_Site(ip, AMBIGUOUS)) { do_log(LT_CONN, 0, 0, T("Connection to %s (GUEST) not allowed from %s (%s)"), name, host, ip); strcpy(errbuf, T("Guest connections not allowed.")); } return NOTHING; } else if (!Guest(player) && (!Site_Can_Connect(host, player) || !Site_Can_Connect(ip, player))) { if (!Deny_Silent_Site(host, player) && !Deny_Silent_Site(ip, player)) { do_log(LT_CONN, 0, 0, T("Connection to %s (Non-GUEST) not allowed from %s (%s)"), name, host, ip); strcpy(errbuf, T("Player connections not allowed.")); } return NOTHING; } /* validate password */ if (!Guest(player)) if (!password_check(player, password)) { /* Increment count of login failures */ ModTime(player)++; check_lastfailed(player, host); return NOTHING; } /* If it's a Guest player, and already connected, search the * db for another Guest player to connect them to. */ if (Guest(player)) { /* Enforce guest limit */ player = guest_to_connect(player); if (!GoodObject(player)) { do_log(LT_CONN, 0, 0, T("Can't connect to a guest (too many connected)")); strcpy(errbuf, T("Too many guests are connected now.")); return NOTHING; } } if (Suspect_Site(host, player) || Suspect_Site(ip, player)) { do_log(LT_CONN, 0, 0, T("Connection from Suspect site. Setting %s(#%d) suspect."), Name(player), player); set_flag_internal(player, "SUSPECT"); } return player; } /** Attempt to create a new player object. * \param name name of player to create. * \param password initial password of created player. * \param host host from which creation is attempted. * \param ip ip address from which creation is attempted. * \return dbref of created player, NOTHING if bad name, AMBIGUOUS if bad * password. */ dbref create_player(const char *name, const char *password, const char *host, const char *ip) { if (!ok_player_name(name, NOTHING)) { do_log(LT_CONN, 0, 0, T("Failed creation (bad name) from %s"), host); return NOTHING; } if (!ok_password(password)) { do_log(LT_CONN, 0, 0, T("Failed creation (bad password) from %s"), host); return AMBIGUOUS; } if (DBTOP_MAX && (db_top >= DBTOP_MAX + 1) && (first_free == NOTHING)) { /* Oops, out of db space! */ do_log(LT_CONN, 0, 0, T("Failed creation (no db space) from %s"), host); return NOTHING; } /* else he doesn't already exist, create him */ return make_player(name, password, host, ip); } /* The HAS_SENDMAIL ifdef is kept here as a hint to metaconfig */ #ifdef MAILER #undef HAS_SENDMAIL #define HAS_SENDMAIL 1 #undef SENDMAIL #define SENDMAIL MAILER #endif #ifdef HAS_SENDMAIL /** Size of the elems array */ #define NELEMS (sizeof(elems)-1) /** Attempt to register a new player at the connect screen. * If registration is allowed, a new player object is created with * a random password which is emailed to the registering player. * \param name name of player to register. * \param email email address to send registration details. * \param host host from which registration is being attempted. * \param ip ip address from which registration is being attempted. * \return dbref of created player or NOTHING if creation failed. */ dbref email_register_player(const char *name, const char *email, const char *host, const char *ip) { char *p; char passwd[BUFFER_LEN]; static char elems[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; int i, len; dbref player; FILE *fp; if (!ok_player_name(name, NOTHING)) { do_log(LT_CONN, 0, 0, T("Failed registration (bad name) from %s"), host); return NOTHING; } /* Make sure that the email address is valid. A valid address must * contain either an @ or a ! * Also, to prevent someone from using the MUSH to mailbomb another site, * let's make sure that the site to which the user wants the email * sent is also allowed to use the register command. * If there's an @, we check whatever's after the last @ * (since @foo.bar:user@host is a valid email) * If not, we check whatever comes before the first ! */ if ((p = strrchr(email, '@'))) { p++; if (!Site_Can_Register(p)) { if (!Deny_Silent_Site(p, AMBIGUOUS)) { do_log(LT_CONN, 0, 0, T("Failed registration (bad site in email: %s) from %s"), email, host); } return NOTHING; } } else if ((p = strchr(email, '!'))) { *p = '\0'; if (!Site_Can_Register(email)) { *p = '!'; if (!Deny_Silent_Site(email, AMBIGUOUS)) { do_log(LT_CONN, 0, 0, T("Failed registration (bad site in email: %s) from %s"), email, host); } return NOTHING; } else *p = '!'; } else { if (!Deny_Silent_Site(host, AMBIGUOUS)) { do_log(LT_CONN, 0, 0, T("Failed registration (bad email: %s) from %s"), email, host); } return NOTHING; } if (DBTOP_MAX && (db_top >= DBTOP_MAX + 1) && (first_free == NOTHING)) { /* Oops, out of db space! */ do_log(LT_CONN, 0, 0, T("Failed registration (no db space) from %s"), host); return NOTHING; } /* Come up with a random password of length 7-12 chars */ len = get_random_long(7, 12); for (i = 0; i < len; i++) passwd[i] = elems[get_random_long(0, NELEMS - 1)]; passwd[len] = '\0'; /* If we've made it here, we can send the email and create the * character. Email first, since that's more likely to go bad. * Some security precautions we'll take: * 1) We'll use sendmail -t, so we don't pass user-given values to a shell. */ release_fd(); if ((fp = #ifdef __LCC__ (FILE *) #endif popen(tprintf("%s -t", SENDMAIL), "w")) == NULL) { do_log(LT_CONN, 0, 0, T("Failed registration of %s by %s: unable to open sendmail"), name, email); reserve_fd(); return NOTHING; } fprintf(fp, T("Subject: [%s] Registration of %s\n"), MUDNAME, name); fprintf(fp, "To: %s\n", email); fprintf(fp, "Precedence: junk\n"); fprintf(fp, "\n"); fprintf(fp, T("This is an automated message.\n")); fprintf(fp, "\n"); fprintf(fp, T("Your requested player, %s, has been created.\n"), name); fprintf(fp, T("The password is %s\n"), passwd); fprintf(fp, "\n"); fprintf(fp, T("To access this character, connect to %s and type:\n"), MUDNAME); fprintf(fp, "\tconnect \"%s\" %s\n", name, passwd); fprintf(fp, "\n"); pclose(fp); reserve_fd(); /* Ok, all's well, make a player */ player = make_player(name, passwd, host, ip); (void) atr_add(player, "REGISTERED_EMAIL", email, GOD, NOTHING); return player; } #else dbref email_register_player(const char *name, const char *email, const char *host, const char *ip) { do_log(LT_CONN, 0, 0, T("Failed registration (no sendmail) from %s"), host); return NOTHING; } #endif static dbref make_player(const char *name, const char *password, const char *host, const char *ip) { dbref player; char temp[SBUF_LEN]; object_flag_type flags; player = new_object(); /* initialize everything */ set_name(player, name); Location(player) = PLAYER_START; Home(player) = PLAYER_START; Owner(player) = player; Parent(player) = NOTHING; Type(player) = TYPE_PLAYER; flags = string_to_bits("FLAG", options.player_flags); copy_flag_bitmask("FLAG", Flags(player), flags); destroy_flag_bitmask(flags); if (Suspect_Site(host, player) || Suspect_Site(ip, player)) set_flag_internal(player, "SUSPECT"); set_initial_warnings(player); /* Modtime tracks login failures */ ModTime(player) = (time_t) 0; (void) atr_add(player, "XYXXY", mush_crypt(password), GOD, NOTHING); giveto(player, START_BONUS); /* starting bonus */ (void) atr_add(player, "LAST", show_time(mudtime, 0), GOD, NOTHING); (void) atr_add(player, "LASTSITE", host, GOD, NOTHING); (void) atr_add(player, "LASTIP", ip, GOD, NOTHING); (void) atr_add(player, "LASTFAILED", " ", GOD, NOTHING); sprintf(temp, "%d", START_QUOTA); (void) atr_add(player, "RQUOTA", temp, GOD, NOTHING); (void) atr_add(player, "ICLOC", EMPTY_ATTRS ? "" : " ", GOD, AF_MDARK | AF_PRIVATE | AF_WIZARD | AF_NOCOPY); (void) atr_add(player, "MAILCURF", "0", GOD, AF_LOCKED | AF_NOPROG | AF_WIZARD); add_folder_name(player, 0, "inbox"); /* link him to PLAYER_START */ PUSH(player, Contents(PLAYER_START)); add_player(player, NULL); add_lock(GOD, player, Basic_Lock, parse_boolexp(player, "=me", Basic_Lock), -1); add_lock(GOD, player, Enter_Lock, parse_boolexp(player, "=me", Basic_Lock), -1); add_lock(GOD, player, Use_Lock, parse_boolexp(player, "=me", Basic_Lock), -1); current_state.players++; local_data_create(player); return player; } /** Change a player's password. * \verbatim * This function implements @password. * \endverbatim * \param player the executor. * \param cause the enactor. * \param old player's current password. * \param newobj player's desired new password. */ void do_password(dbref player, dbref cause, const char *old, const char *newobj) { if (!global_eval_context.process_command_port) { char old_eval[BUFFER_LEN]; char new_eval[BUFFER_LEN]; char const *sp; char *bp; sp = old; bp = old_eval; process_expression(old_eval, &bp, &sp, player, player, cause, PE_DEFAULT, PT_DEFAULT, NULL); *bp = '\0'; old = old_eval; sp = newobj; bp = new_eval; process_expression(new_eval, &bp, &sp, player, player, cause, PE_DEFAULT, PT_DEFAULT, NULL); *bp = '\0'; newobj = new_eval; } if (!password_check(player, old)) { notify(player, T("The old password that you entered was incorrect.")); } else if (!ok_password(newobj)) { notify(player, T("Bad new password.")); } else { (void) atr_add(player, "XYXXY", mush_crypt(newobj), GOD, NOTHING); notify(player, T("You have changed your password.")); } } /** Processing related to players' last connections. * Here we check to see if a player gets a paycheck, tell them their * last connection site, and update all their LAST* attributes. * \param player dbref of player. * \param host hostname of player's current connection. * \param ip ip address of player's current connection. */ void check_last(dbref player, const char *host, const char *ip) { char *s; ATTR *a; ATTR *h; char last_time[MAX_COMMAND_LEN / 8]; char last_place[MAX_COMMAND_LEN]; /* compare to last connect see if player gets salary */ s = show_time(mudtime, 0); a = atr_get_noparent(player, "LAST"); if (a && (strncmp(atr_value(a), s, 10) != 0)) giveto(player, Paycheck(player)); /* tell the player where he last connected from */ if (!Guest(player)) { h = atr_get_noparent(player, "LASTSITE"); if (h && a) { strcpy(last_place, atr_value(h)); strcpy(last_time, atr_value(a)); notify_format(player, T("Last connect was from %s on %s."), last_place, last_time); } /* How about last failed connection */ h = atr_get_noparent(player, "LASTFAILED"); if (h && a) { strcpy(last_place, atr_value(h)); if (strlen(last_place) > 2) notify_format(player, T("Last FAILED connect was from %s."), last_place); } } /* if there is no Lastsite, then the player is newly created. * the extra variables are a kludge to work around some weird * behavior involving uncompress. */ /* set the new attributes */ (void) atr_add(player, "LAST", s, GOD, NOTHING); (void) atr_add(player, "LASTSITE", host, GOD, NOTHING); (void) atr_add(player, "LASTIP", ip, GOD, NOTHING); (void) atr_add(player, "LASTFAILED", " ", GOD, NOTHING); } /** Update the LASTFAILED attribute on a failed connection. * \param player dbref of player. * \param host host from which connection attempt failed. */ void check_lastfailed(dbref player, const char *host) { char last_place[BUFFER_LEN], *bp; bp = last_place; safe_format(last_place, &bp, T("%s on %s"), host, show_time(mudtime, 0)); *bp = '\0'; (void) atr_add(player, "LASTFAILED", last_place, GOD, NOTHING); }