mindor/area/
mindor/data/
mindor/data/bank/
mindor/data/hometown/
mindor/log/
mindor/player/
/* MUDftp module
 * (c) Copyright 1997, 1998 Erwin S. Andreasen and Oliver Jowett
 * This code may be freely redistributable, as long as this header is left
 * intact.
 *
 * Thanks to:
 * - Jessica Boyd for the ROM version
 * - Dominic J. Eidson for the ROT version
 * - George Greer for the Circle version
 */


/**************************************************************************
*      Mindor 1.0 is copyright 2002-2004 by Shaun Mcbride                *
*      Portions of the code were inspired by other works                 *
*      found around the internet.                                        *
*                                                                        *
*      Please follow all previous copyrights and licenses.               *
*      THanks to Maji for getting this to work on mindor.                *
**************************************************************************/

#include <stdlib.h> 
#include <unistd.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdarg.h>

/* Define one of the below
 MERC: Will work for MERC, Envy, ROM, ROT
 CMUD: Will work for CircleMUD 3.x, bpl14 tested.
 */
#define MERC
/* #define CMUD 1 */

#if defined(MERC)

#include <string.h>
#include "merc.h"

#define CLOSE_DESCRIPTOR(desc,explanation) close_socket(desc)
#define WRITE(desc,text) write_to_descriptor(desc->descriptor, text, 0)
bool    write_to_descriptor     args( ( int desc, char *txt, int length ) );
#define GET_NAME(ch)		((ch)->name)
#define GET_PASSWD(ch)		((ch)->pcdata->pwd)
#define CRYPT(x,y)		crypt(x,y)

#elif defined(CMUD)

#include "conf.h"
#include "sysdep.h"
#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "interpreter.h"

#define CLOSE_DESCRIPTOR(d,txt)	do { close_socket(d); log("ftp close socket: %s", txt); } while(0)
#define WRITE(d, text)		(write_to_output(text, d), 1)
typedef struct char_data	CHAR_DATA;
typedef struct descriptor_data	DESCRIPTOR_DATA;
#define pString			str
#define free_string(x)		free(x)
#define args(x)			x
void mudftp_string_add(struct char_data *ch, char *txt) { string_add(ch->desc, txt); }
#define string_add(ch, txt)	mudftp_string_add(ch, txt)
#endif

#if !defined(NUL)
#define NUL '\0'
#endif

#if !defined(MSL)
#define MSL MAX_STRING_LENGTH
#define MIL MAX_INPUT_LENGTH
#endif

#if !defined(MAX_PWD_LENGTH)
#define MAX_PWD_LENGTH 12
#endif

static void mudftp_notify(const char *fmt, ... ) {
    va_list va;
    char buf[MSL];
    
    va_start(va, fmt);
    vsprintf(buf, fmt, va);
    va_end(va);

    /* Then do something with "buf", appropriate to the current MUD base */
    /* E.g. send it over "Wiznet" or log it (not recommended) */
    /* log_string (buf); */
}

/* Called this because of conflict with ROT */
static void mudftp_str_replace(char *buf, const char *s, const char *repl) {
    char out_buf[MSL];
    char *pc, *out;
    int len = strlen(s);
    bool found = FALSE;
    
    for (pc = buf, out = out_buf; *pc && (out-out_buf) < (MSL-len-4); )
        if (!strncasecmp(pc, s, len))  {
            out += sprintf(out, repl);
            pc += len;
            found = TRUE;
        }
        else
            *out++ = *pc++;
    
    if (found) { /* don't bother copying if we did not change anything */
        *out = NUL;
        strcpy(buf, out_buf);
    }
}


int line_count (const char *s) {
    int count = 0;
    
    for (; *s; s++)
        if (*s == '\n')
            count++;
    
    return count;
}

void greet_ftp (DESCRIPTOR_DATA *d, const char *argument) {
    d->connected = CON_FTP_AUTH;
	mudftp_notify("FTP connect from %s", d->host);
}

/* Authorization line: <username> <password> */
void handle_ftp_auth (DESCRIPTOR_DATA *d, const char *argument) {
    char name[MIL];
    DESCRIPTOR_DATA *dftp, *dftp_next;
    CHAR_DATA *ch = NULL;
    
    argument = one_argument((char*)argument, name);
    
    /* Find the descriptor of the connected character */
    for (dftp = descriptor_list; dftp; dftp=dftp->next) {
        if (dftp != d &&
            dftp->character &&
            !IS_NPC(dftp->character) &&
            dftp->connected >= CON_PLAYING &&
            !str_cmp(GET_NAME(dftp->character), name)) {
            ch = dftp->character;
            break;
        }
    }
    
    if (!ch || (strncmp(CRYPT(argument, GET_PASSWD(ch)), GET_PASSWD(ch), MAX_PWD_LENGTH))) {
        WRITE(d,"FAILED\n");
        CLOSE_DESCRIPTOR(d, "FTP authorization failure");
        mudftp_notify("FTP authorization for %s failed",name);
        return;
    }
    
    /* Search for old ftp connections */
    for (dftp = descriptor_list; dftp; dftp=dftp_next) {
        dftp_next = dftp->next;
        
        if (dftp != d &&
            (dftp->connected == CON_FTP_COMMAND ||
             dftp->connected == CON_FTP_DATA) &&
            !str_cmp(dftp->username, name))
            CLOSE_DESCRIPTOR(dftp, "Old mudftp connection");
    }
    
    d->username = str_dup(name);
    WRITE(d, "OK mudFTP 2.0 ready\n");
    d->connected = CON_FTP_COMMAND;
    mudftp_notify("FTP authorization for %s@%s", name, d->host);
}

/* This algorithm is derived from the one supposedly used in Perl */
static const char *ftp_checksum(const char *string) {
    static char buf[10];
    int i = strlen(string);
    unsigned hash = 0;
    
    while(i--)
        hash = hash * 33U + *string++;
    
    sprintf(buf, "%08x", hash);
    return buf;
}

static CHAR_DATA *findFTPChar(DESCRIPTOR_DATA *d) {
    DESCRIPTOR_DATA *dftp;
    
    for (dftp = descriptor_list; dftp; dftp=dftp->next)
    {
        if (dftp != d &&
            dftp->character &&
            !str_cmp(GET_NAME(dftp->character), d->username) &&
            dftp->pString)
            return dftp->character;
    }
    
    return NULL;
}

static void finish_file(DESCRIPTOR_DATA *d) {
    unsigned long temp_file;
    
    mudftp_notify("Transfer of %s done from %s@%s", d->ftp.filename, d->username, d->host);
    
    d->connected = CON_FTP_COMMAND;
    /* Put the file in its rightful spot */
    
    if (1 == sscanf(d->ftp.filename, "tmp/%lu", &temp_file))
    {
        CHAR_DATA *ch = findFTPChar(d);
        
        if (ch && ((unsigned long) ch->desc->pString) == temp_file)
        {
            char temp[MSL];
            char buf[MSL];
            
            strcpy(temp, d->ftp.data);
            smash_tilde(temp);
            
            sprintf(buf, "OK %s\n", ftp_checksum(temp));
            WRITE(d,buf);
            
            free_string(*ch->desc->pString);
            *ch->desc->pString = str_dup(temp);
            free_string(d->ftp.data);
            d->ftp.data = NULL;
            
            strcpy(buf, "@");
            string_add(ch, buf); /* Finish editing */
            
            return;
        }
    }
    
    WRITE(d,"FAILED Something went wrong\n");
    
    free_string(d->ftp.data);
    free_string(d->ftp.filename);
    
}

void handle_ftp_command (DESCRIPTOR_DATA *d, const char *argument) {
    char arg[MIL];
    const char *orig_argument = argument;
    
    argument = one_argument((char*)argument, arg);
    
    
    if (!str_cmp(arg, "noop")) {
        WRITE(d,"OK\n");
        return;
    }
    
    mudftp_notify("FTP command: '%s' from %s@%s", orig_argument, d->username, d->host);
    
    if (!str_cmp(arg, "push")) {
        if (d->ftp.mode != FTP_NORMAL)
        {
            WRITE(d, "ERROR Already in push mode\n");
            return;
        }
        
        d->ftp.mode = FTP_PUSH;
        WRITE(d, "OK Pushing you data as it arrives\n");
        return;
    }
    
    if (!str_cmp(arg, "stop"))	{
        CHAR_DATA *ch = findFTPChar(d);
        
        if (!ch)
            WRITE(d,"FAILED\n");
        else {
            free_string(d->ftp.data);
            d->ftp.data = NULL;
            
            string_add(ch,"@"); /* Finish editing */
            WRITE(d,"OK\n");
        }
        
        if (d->ftp.mode == FTP_PUSH_WAIT)
            d->ftp.mode = FTP_PUSH;
        
        return;
    }
    
    if (!str_cmp(arg, "put")) {
        argument = one_argument((char*)argument, arg);
        if (!argument[0] || !is_number((char*)argument) || atoi(argument) < 0)
            WRITE(d, "ERROR Missing filename or number of lines\n");
        else
        {
            d->ftp.filename = str_dup(arg);
            d->ftp.lines_left = atoi(argument);
            d->ftp.data = str_dup("");
            
            if (d->ftp.lines_left)
                d->connected = CON_FTP_DATA;
            else
                finish_file(d);
        }
        
        if (d->ftp.mode == FTP_PUSH_WAIT)
            d->ftp.mode = FTP_PUSH;
    }
    else if (!str_cmp(arg, "get")) {
        unsigned long temp_file;
        
        if (d->ftp.mode == FTP_PUSH_WAIT)
        {
            WRITE(d, "FAILED Expected STOP or PUT");
            return;
        }
        
        /* Send buffer being edited */
        if (1 == sscanf(argument, "tmp/%lu", &temp_file)) {
            CHAR_DATA *ch = findFTPChar(d);
            
            if (!ch || ((unsigned long) ch->desc->pString) != temp_file)
                WRITE(d,"FAILED\n");
            else { /* Write the string */
                char buf[MSL];
                char buf2[MSL];
                
                if (*ch->desc->pString)
                    strcpy(buf2, *ch->desc->pString);
                else
                    buf2[0] = '\0';
                
                mudftp_str_replace(buf2, "\r", "");
                
                sprintf(buf, "SENDING tmp/%lu %d %s\n", temp_file, line_count(buf2),
                        ftp_checksum(buf2));
                if (!WRITE(d,buf) || !WRITE(d,buf2)) {
                    CLOSE_DESCRIPTOR(d, "FTP write failure");
                    return;
                }
            }
        }
        else
            WRITE(d, "FAILED Currently only tmp/file is supported\n");
    }
    else if (!str_cmp(arg, "quit"))
        CLOSE_DESCRIPTOR(d, "Quitting");
    else
        WRITE(d, "ERROR unknown command\n");
}


void handle_ftp_data (DESCRIPTOR_DATA *d, const char *argument) {
    int len_data, len_argument;
    
    len_data = strlen(d->ftp.data);
    len_argument = strlen(argument);
    
    /* Lines that overflow the buffer are silently lost */
    if (len_data + len_argument < MSL-16) {
        char buf[MSL];
        strcpy(buf, d->ftp.data);
        strcpy(buf+len_data, argument);
        
        /* All strings are \n internally */
        strcpy(buf+len_data+len_argument, "\n\r");
        free_string(d->ftp.data);
        d->ftp.data = str_dup(buf);
    }
    
    /* All of the file received? */
    if (--d->ftp.lines_left == 0)
        finish_file(d);
}

/* Try to push a string to this desc. false if we can't */
bool ftp_push(DESCRIPTOR_DATA *d) {
    DESCRIPTOR_DATA *m;
    for (m = descriptor_list; m; m=m->next)  {
        if (m->connected == CON_FTP_COMMAND &&
            m->ftp.mode == FTP_PUSH &&
            !str_cmp(m->username, GET_NAME(d->character))) {
            
            char buf[MSL];
            char buf2[MSL];
            
            if (*d->pString)
                strcpy(buf2, *d->pString);
            else
                buf2[0] = '\0';
            
            mudftp_str_replace(buf2, "\r", ""); /* Never send \r to clients */
            
            sprintf(buf, "SENDING tmp/%lu %d %s\n", (unsigned long)d->pString, line_count(buf2),
                    ftp_checksum(buf2));
            
            if (!WRITE(m,buf) || !WRITE(m,buf2)) {
                CLOSE_DESCRIPTOR(m, "FTP write failure");
                return FALSE;
            }
            
            m->ftp.mode = FTP_PUSH_WAIT;
            
            return TRUE;
        }
    }
    
    return FALSE;
}