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