dawn/notes/
dawn/src/
dawn/src/docs/
/* 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
 */
/* 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 */
#ifdef WIN32
#include <string.h>
#include <stdio.h>
#define strcasecmp stricmp
#define strncasecmp strnicmp
#endif

#if defined(MERC)
#include "include.h"
#include "nanny.h"

#define CLOSE_DESCRIPTOR(desc,explanation) connection_close(desc)
#define WRITE(desc,text)		desc->write(text, 0)
#define GET_NAME(ch)		((ch)->name)
#define GET_PASSWD(ch)		((ch)->pcdata->pwd)

#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 { connection_close(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 connection_data	connection_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

/**************************************************************************/
#define mudftp_notify logf
/*static void mudftp_notify(const char *fmt, ... ) {
    va_list va;
    char buf[MSL];
    
    va_start(va, fmt);
    vsnprintf(buf, MSL, 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 = str_len(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(connection_data *c) 
{
    c->connected_state = CON_FTP_AUTH;
	mudftp_notify("FTP connect from %s", c->remote_tcp_pair);
}

/**************************************************************************/
// Authorization line: <username> <password> 
void handle_ftp_auth (connection_data *d, const char *argument) {
    char name[MIL], pass[MIL];
    connection_data *dftp, *dftp_next;
    char_data *ch = NULL;

    mudftp_str_replace((char*)argument, "\r", "");
    
    argument = first_arg((char*)argument, name, false);
    
    // Find the descriptor of the connected character 
    for (dftp = connection_list; dftp; dftp=dftp->next) {
        if (dftp != d &&
            dftp->character &&
            !IS_NPC(dftp->character) &&
            dftp->connected_state >= CON_PLAYING &&
            !str_cmp(GET_NAME(dftp->character), name)) {
            ch = dftp->character;
            break;
        }
    }
    argument = first_arg((char*)argument, pass, false);
    mudftp_str_replace(pass, "\r", "");
    
    if (!ch || (!is_valid_password(pass, GET_PASSWD(ch), NULL) 
		&& strcmp(GET_PASSWD(ch),"none"))) {
		WRITE(d,"FAILED\n");
		CLOSE_DESCRIPTOR(d, "FTP authorization failure");
		mudftp_notify("FTP authorization for %s [%d] failed",
			name, d->connected_socket);
		return;
    }
    
    // Search for old ftp connections 
    for (dftp = connection_list; dftp; dftp=dftp_next) {
        dftp_next = dftp->next;
        
        if (dftp != d &&
            (dftp->connected_state == CON_FTP_COMMAND ||
             dftp->connected_state == 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_state = CON_FTP_COMMAND;
    mudftp_notify("FTP authorization for %s@%s", name, d->remote_hostname);
}

/**************************************************************************/
// 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 = str_len(string);
    unsigned hash = 0;
    
    while(i--)
        hash = hash * 33U + *string++;
    
    sprintf(buf, "%08x", hash);
    return buf;
}

/**************************************************************************/
static char_data *findFTPChar(connection_data *d) {
    connection_data *dftp;
    
    for (dftp = connection_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;
}

#ifdef WIN32
#pragma warning( disable : 4311 ) // disable pointer truncation warning in VS.NET
#endif
/**************************************************************************/
static void finish_file(connection_data *d) {
    unsigned long temp_file;
    
    mudftp_notify("Transfer of %s done from %s@%s", 
		d->ftp.filename, d->username, d->remote_hostname);
    
    d->connected_state = 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 
			ch->println("done.");
            return;
        }
    }
    
    WRITE(d,"FAILED Something went wrong\n");
    
    free_string(d->ftp.data);
    free_string(d->ftp.filename);
	d->ftp.inuse=false;
}

/**************************************************************************/
void handle_ftp_command (connection_data *d, const char *argument) {
    char arg[MIL];
    mudftp_str_replace((char*)argument, "\r", "");

    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->remote_hostname);
    
    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_state = 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", "");

				if(buf2[0] && buf2[str_len(buf2)-1]!='\n'){
					strcat(buf2, "\n"); // everything sent to mudftp must end with a \n
				}
                
                sprintf(buf, "SENDING tmp/%lu %d %s\n", temp_file, line_count(buf2),
                        ftp_checksum(buf2));
                if (WRITE(d,buf)<0 || WRITE(d,buf2)<0) {
                    CLOSE_DESCRIPTOR(d, "FTP write failure");
                    return;
                }
				d->ftp.inuse=true;
            }
        }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 (connection_data *d, const char *argument) {
    int len_data, len_argument;

    mudftp_str_replace((char*)argument, "\r", "");

    len_data = str_len(d->ftp.data);
    len_argument = str_len(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, "\r\n");
        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(connection_data *d) {
    connection_data *m;
    for (m = connection_list; m; m=m->next)  {
        if (m->connected_state == 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 
            
			if(buf2[0] && buf2[str_len(buf2)-1]!='\n'){
				strcat(buf2, "\n"); // everything sent to mudftp must end with a \n
			}
            
            sprintf(buf, "SENDING tmp/%lu %d %s\n", (unsigned long)d->pString, line_count(buf2),
                    ftp_checksum(buf2));
            
            if (WRITE(m,buf)<0 || WRITE(m,buf2)<0) {
                CLOSE_DESCRIPTOR(m, "FTP write failure");
                return false;
            }
            
            m->ftp.mode = FTP_PUSH_WAIT;
            
            return true;
        }
    }
    
    return false;
}
#ifdef WIN32
#pragma warning( default : 4311 ) // reenable pointer truncation warning in VS.NET
#endif

/**************************************************************************/
// Tell the dawnftp client to reconnect - requires dawnftp... 
// used for hotreboots because I don't feel motivated to write the code 
// to transfer the mudftp descriptor accross the hotreboot
// - Kal, May 02
bool ftp_reconnect(char *name) 
{
    connection_data *m;
    for (m = connection_list; m; m=m->next)  {
        if (m->connected_state == CON_FTP_COMMAND &&
            m->ftp.mode == FTP_PUSH &&
            !str_cmp(m->username, name)) {
            
            if (WRITE(m,"RECONNECT\n")<0) {
                CLOSE_DESCRIPTOR(m, "FTP write failure");
                return false;
            }          
            return true;
        }
    }    
    return false;
}
/**************************************************************************/
/**************************************************************************/