/* /secure/ftpd... */ /* * Originaly written by Pinkfish@Discworld. * * 93-08-22 Bannor@newmoon * - Added DEBUG and LOGGING flags. * - Fixed a problem with sending files larger then 1k. * - Added support for events. * - Added support for finger ftp. * - Fixed security problems that allowed anyone to read/write * to any directory. * - Improved readability (well, I think so anyway) * * 13-07-94 Pinkfish@Discworld * - Fixed up the handling of new lines. * * 28-11-94 Pinkfish@Discworld * - Added in binary mode communications. * * 26-6-95 Turrican@Discworld * - Fixed binary mode communications. * - Fixed ABOR handling (control characters), and now the data socket * is closed instead of the control socket ;-) * - Fixed the "CWD ~" bug. * - Fixed the STAT and the SIZE command. * - Added "long" listing of ls. * - Made NLST and LIST take arguments like -C, -l. * - Fixed timeout checking. * - Added commands MKD, RMD, RNFR, RNTO, MDTM, DELE, REST, * HELP, and some SITE commands. * * 19-8-95 Turrican@Discworld * - Fixed login timeout checking. * - Fixed data_write_callback function, it was quite broken. * Sending of double packets should now belong to the past. * - Fixed data_conn function, it violated the flow control model. * 15-02-97 Baldrick@Final Realms. * - How the heck do I make this multi-mud aware.. */ #include <ftp.h> #include <socket.h> #include <socket_errors.h> #include <localtime.h> /* ----------------------------------------------- debugging defines. ----------------------------------------------- */ #define TP_CRE "baldrick" #undef DEBUG #ifdef DEBUG #define TPX(STR) if (find_player(TP_CRE)) \ tell_object(find_player(TP_CRE), STR) #define DEBUG_SEND /* define to debug data_write_callback() */ #else #define TPX(STR) #undef DEBUG_SEND #endif /* ----------------------------------------------- log file defines. ----------------------------------------------- */ #define LOGGING #ifdef LOGGING #define LOG_CONNECT /* define to log all connections */ #define LOG_FILE /* define to log all file xfers */ #undef LOG_CD_SIZE /* define to log cd's and file size commands */ #else #undef LOG_CONNECT #undef LOG_FILE #undef LOG_CD_SIZE #endif #define BLOCK_SIZE 4096 /* ----------------------------------------------- driver version defines. ----------------------------------------------- */ #define NEWDRIVER #ifdef __VERSION__ #define VERSION __VERSION__ #else #ifndef VERSION #define VERSION version() #endif #endif #define CHECK_LOGIN() if (!socket_info[fd][LOGGED_IN]) \ { \ socket_write(fd, "530 Please login with USER and PASS.\r\n"); \ break; \ } #define CHECK_CMD(INT) if (sizeof(bits) < INT+1) \ { \ socket_write(fd, sprintf("500 '%s': command not understood.\r\n", \ str)); \ break; \ } #define CHECK_PLAYER() if (socket_info[fd][LOGGED_IN] == 2) \ { \ socket_write(fd, "553 Permission denied (you are not a creator)\r\n"); \ break; \ } #define CHECK_STRING socket_info[pfd][TYPE] == STRING #define UNAME socket_info[fd][USER_NAME] #define HOME_DIR(NAME) "/w/"+NAME /* ----------------------------------------------- Variables ----------------------------------------------- */ mapping socket_info; int our_socket, offset; string rnfr; /* ----------------------------------------------- Prototypes ----------------------------------------------- */ void setup_ftp(int port); void finish_lookup(string host, string number); string get_path(int fd, string str); static void do_update(string name, int fd); mixed *query_connections() /* returns an array of users connected to ftpd */ { mixed *vals; string *list; int count; list = ({ }); vals = values(socket_info); for (count = 0; count < sizeof(vals); count++) { if (vals[count][USER_NAME]) { if (vals[count][LOGGED_IN]) { list += ({ capitalize((string)((vals[count])[USER_NAME])) }); } else { list += ({ "login" }); } } } return list; } /* query_connections() */ void create() { seteuid("Root"); socket_info = ([]); rnfr = ""; offset = 0; call_out("setup_ftp", 2, FR_FTP_PORT); call_out("check_connections", 5 * 60); } /* create() */ void setup_ftp(int port) { our_socket = socket_create(STREAM, "in_read_callback", "in_close_callback"); if (our_socket < 0) { TPX("Failed to create socket.\n"); return; } if (socket_bind(our_socket, port) < 0) { TPX("Failed to bind socket.\n"); socket_close(our_socket); return; } if (socket_listen(our_socket, "in_listen_callback") < 0) { TPX("Failed to listen to socket.\n"); return; } } /* setup_ftp() */ void in_listen_callback(int fd) { int new_fd; if ((new_fd = socket_accept(fd, "in_read_callback", "in_write_callback")) < 0) { return; } socket_info[new_fd] = ([USER_NAME : "Login", IDLE : 900, LAST_DATA : time()]); socket_write(new_fd, sprintf("220 %s FTP server ready. " + "Please login as yourself.\r\n", mud_name())); } /* in_listen_callback() */ string ls( string path, int mask) { string *files; int i, j, s; mixed *xfiles; mixed *stats; string tmp, tmp2, creator, domain; /* if path is a directory get contents */ if ( file_size( path ) == -2) { if ( path[ strlen( path ) - 1 ] == '/' ) path += "*"; else path += "/*"; } /* begin narrow columnar "nlst" */ if (!(MASK_L & mask)) { files = get_dir( path ); /* can only happen if permissions are messed up at account level */ if (!files) return ""; if (!(MASK_A & mask)) files -= ({ ".", ".." }); if (!(i = sizeof( files ))) return ""; /* no wild cards...must have been the exact pathname to a file */ if (strsrch(path, '*') == -1 && strsrch(path, '?') == -1) { return files[0] + "\n"; } /* remove globber at end of path, leave a trailing slash */ j = strsrch(path, '/', -1); path = path[0..j]; while ( i-- ) { /* scan next level down for files */ tmp = sprintf("%s%s/", path, files[i]); if (MASK_F & mask) { if (strsrch(tmp, "/./") != -1 || strsrch(tmp, "/../") != -1) { files[i] += "/"; continue; } if (file_size(tmp) == -2) files[i] += "/"; // else if (stat(tmp[0..-2])[2]) else if (virtual_find_object(tmp[0..(strlen(tmp)-2)])) files[i] += "*"; } } if (MASK_C & mask) return sprintf("%-#70s\n", implode(files, "\n")); else return implode( files, "\n" ) + "\n"; } /* begin long "list" */ xfiles = get_dir( path, -1 ); if (!(mask & MASK_A)) xfiles = filter_array(xfiles, "check_dots", this_object()); if (!xfiles || !(s = sizeof( xfiles ))) return ""; files = allocate(s); // the Unix-like file permissions are mainly for effect...hopefully it // isn't too much, since anything more would likely be too cpu intensive // and cause it to max eval... creator = (string)MASTER->creator_file(path); if (!creator) creator = "Root"; domain = (string)MASTER->domain_file(path); if (!domain) domain = "Root"; i = strsrch(path, '/', -1); if (i >= 0) path = path[0..i]; for (i = 0; i < s; i++) { /* process timestamp */ tmp2 = ctime((xfiles[i])[2]); /* get last modified timestamp */ if ((xfiles[i])[2] + (365 * 24 * 60 * 60) < time()) { /* MMM DD YYYY */ tmp = sprintf("%s %s", tmp2[4..9], tmp2[20..23]); } else { /* MMM DD hh:mm */ tmp = tmp2[4..15]; } j = (xfiles[i])[1]; /* get filesize */ if (j == -2) { /* directory */ files[i] = sprintf("drwxrwsr-x %3d %-12s %-12s 0 %12s %s", sizeof(get_dir(sprintf((path == "/"?"%s%s":"%s/%s/"),path, xfiles[i][0]))),creator, domain, tmp, (xfiles[i])[0]+((MASK_F & mask)?"/":"")); } else { /* file */ stats = stat(path + (xfiles[i])[0]); files[i] = sprintf("-rw%crw-r-- 1 %-12s %-12s %6d %12s %s", sizeof(stats) > 1 && stats[2] ? 'x' : '-', creator, domain, j, tmp, (xfiles[i])[0]+ (sizeof(stats) > 1 && stats[2] && (MASK_F & mask)?"*":"")); } } return sprintf( "%-#70s\n", implode( files, "\n" ) ); } void data_write_callback(int fd) { int pfd, pos, length, ret_val; mixed tmp; if (socket_info[fd][TYPE] == DOWNLOAD) return; pos = socket_info[fd][POS]; pfd = socket_info[fd][PARENT_FD]; if (undefinedp(pfd) || undefinedp(socket_info[pfd])) return; // not a data connection, or was orphaned socket_info[pfd][LAST_DATA] = time(); if (pos > socket_info[fd][LEN]) { TPX("pos > len\n"); socket_write(pfd, "226 Transfer complete.\r\n"); socket_close(fd); map_delete(socket_info[pfd], DATA_FD); map_delete(socket_info[pfd], DATA_ADDR); offset = 0; #ifdef DEBUG_SEND TPX("dwc() complete, exiting.\n"); #endif return; } length = ((pos+BLOCK_SIZE) >= socket_info[fd][LEN]) ? (socket_info[fd][LEN] - pos) : BLOCK_SIZE; #ifdef DEBUG_SEND TPX("Entering dwc(), pos: " + pos + " length should be: " + length + ".\n") ; #endif if (socket_info[fd][TYPE] == STRING) { #ifdef DEBUG_SEND TPX("type == STRING\n"); #endif while ((ret_val = socket_write(fd, socket_info[fd][DATA][pos..(pos+BLOCK_SIZE-1)])) == EESUCCESS) { pos += BLOCK_SIZE; socket_info[fd][POS] = pos; if (pos > socket_info[fd][LEN]) { socket_write(pfd, "226 Transfer complete.\r\n"); socket_close(fd); map_delete(socket_info[pfd], DATA_FD); map_delete(socket_info[pfd], DATA_ADDR); offset = 0; #ifdef DEBUG_SEND TPX("dwc() complete, exiting.\n"); #endif return; } } } else { #ifdef DEBUG_SEND TPX("type is other then STRING\n"); #endif if (CHECK_STRING) { tmp = ""; if (catch(tmp = read_bytes(socket_info[fd][DATA], pos, length))) socket_write(pfd, "551 Error on input file.\r\n"); tmp = replace_string(tmp, "\n", "\r\n"); } else { tmp = allocate_buffer(0); if (catch(tmp = read_buffer(socket_info[fd][DATA], pos, length))) socket_write(pfd, "551 Error on input file.\r\n"); } while ((ret_val = socket_write(fd, tmp)) == EESUCCESS) { #ifdef DEBUG_SEND TPX("sent from " + pos + " to " + (pos + length) + ".\n"); TPX("ret_val was: " + ret_val + ".\n"); #endif pos += BLOCK_SIZE; socket_info[fd][POS] = pos; if (pos >= socket_info[fd][LEN]) { socket_write(pfd, "226 Transfer complete.\r\n"); socket_close(fd); map_delete(socket_info[pfd], DATA_FD); map_delete(socket_info[pfd], DATA_ADDR); offset = 0; #ifdef DEBUG_SEND TPX("dwc() complete, exiting.\n"); #endif return; } if (CHECK_STRING) { tmp = ""; tmp = read_bytes(socket_info[fd][DATA], pos, BLOCK_SIZE); tmp = replace_string(tmp, "\n", "\r\n"); } else { tmp = allocate_buffer(0); tmp = read_buffer(socket_info[fd][DATA], pos, BLOCK_SIZE); } } } #ifdef DEBUG_SEND TPX("ret_val was: " + ret_val + ".\n"); TPX("leaving dwc(), pos: " + pos + ".\n"); #endif if (ret_val == EEWOULDBLOCK) { /* it would block, so it's up to us to try again */ #ifdef DEBUG_SEND TPX("Adding call_out\n"); #endif call_out("data_write_callback", 2, fd); } else if (ret_val == EECALLBACK) { /* Buffer full, wait untill we are called back again. Do increase the * position, since the previous block WAS sent. * We are now flow controlled. */ socket_info[fd][POS] += BLOCK_SIZE; } else { /* not going to be called again by driver */ while (remove_call_out("data_write_callback") != -1) { #ifdef DEBUG_SEND TPX("Killing callout.\n"); #endif } } } /* data_write_callback() */ static void data_conn(int fd, string mess, string name, int type) { int new_fd, ret, data_mode; string data_mode_name; if (type == STRING || (type == FILE && socket_info[fd][TYPE] == STRING)) { data_mode_name = "ASCII"; data_mode = STREAM; } else { data_mode_name = "BINARY"; data_mode = STREAM_BINARY; } if (socket_info[fd][DATA_FD]) { socket_write(fd, "425 Can't open data connection.\r\n"); return; } new_fd = socket_create(data_mode, "data_read_callback", "data_close_callback"); if (new_fd < 0) { socket_write(fd, "425 Can't create data socket.\r\n"); return; } if (!stringp(socket_info[fd][DATA_ADDR])) { socket_write(fd, "425 Can't open data connection.\r\n"); return; } socket_info[new_fd] = ([ DATA : (type == STRING ? replace_string(mess, "\n", "\r\n") : mess), POS : offset, PARENT_FD : fd, TYPE : type, LEN : (type == STRING ? strlen(mess) : file_size(mess)) ]); socket_info[fd][DATA_FD] = new_fd; if ((ret = socket_connect(new_fd, sprintf("%s %d", socket_info[fd][DATA_ADDR], socket_info[fd][DATA_PORT]), "data_read_callback", "data_write_callback")) < 0) { TPX("Error: " + socket_info[fd][DATA_ADDR] + " " + socket_info[fd][DATA_PORT] + "\n"); TPX(socket_error(ret) + "\n"); socket_write(fd, "425 Can't build data connection.\r\n"); socket_close(new_fd); return; } socket_write(fd, sprintf("150 Opening %s mode data connection for %s " "(%d bytes).\r\n", data_mode_name, name, socket_info[new_fd][LEN])); } /* data_conn() */ static void read_connection(int fd, string path, int append) { int new_fd, ret, data_mode; string data_mode_name; if (socket_info[fd][TYPE] == BINARY) { data_mode_name = "BINARY"; data_mode = STREAM_BINARY; } else { data_mode_name = "ASCII"; data_mode = STREAM; } if (socket_info[fd][DATA_FD]) { socket_write(fd, "425 Can't open data connection.\r\n"); return; } new_fd = socket_create(data_mode, "data_read_callback", "data_close_callback"); if (new_fd < 0) { socket_write(fd, "425 Can't create data socket.\r\n"); return; } socket_info[new_fd] = ([ PATH : path, PARENT_FD : fd, POS : (!append?0:(file_size(path)==-1?0:file_size(path))), TYPE : DOWNLOAD, APPEND : append ]); if ((ret = socket_connect(new_fd, sprintf("%s %d", socket_info[fd][DATA_ADDR], socket_info[fd][DATA_PORT]), "data_read_callback", "data_write_callback")) < 0) { TPX("Error: " + socket_info[fd][DATA_ADDR] + " " + socket_info[fd][DATA_PORT] + "\n"); TPX(socket_error(ret) + "\n"); socket_write(fd, "425 Can't build data connection.\r\n"); socket_close(new_fd); return; } if (append != 1) catch(rm(path)); socket_info[fd][DATA_FD] = new_fd; socket_write(fd, sprintf("150 Opening %s mode data connection for %s.\r\n", data_mode_name, path)); } /* read_connection() */ void data_read_callback(int fd, mixed mess) { int pfd; if (socket_info[fd][TYPE] != DOWNLOAD) return; pfd = socket_info[fd][PARENT_FD]; if (undefinedp(pfd) || undefinedp(socket_info[pfd])) return; // not a data connection, or was orphaned socket_info[pfd][LAST_DATA] = time(); if (stringp(mess)) mess = replace_string(mess, "\r", ""); write_buffer(socket_info[fd][PATH], socket_info[fd][POS], mess); socket_info[fd][POS] += (stringp(mess)?strlen(mess):sizeof(mess)); } /* data_read_callback() */ void data_close_callback(int fd) { int pfd; pfd = socket_info[fd][PARENT_FD]; if (socket_info[fd][TYPE] == DOWNLOAD) { if (socket_info[fd][APPEND] == -1) socket_write(pfd, sprintf("226 Transfer complete (unique file name:%s).\r\n", socket_info[fd][PATH])); else socket_write(pfd, "226 Transfer complete.\r\n"); } /* * only close data connections here */ if (!undefinedp(pfd)) { socket_close(fd); map_delete(socket_info, fd); map_delete(socket_info[pfd], DATA_FD); map_delete(socket_info[pfd], DATA_ADDR); } offset = 0; } /* data_close_callback() */ void logout(int fd) { string name; name = socket_info[fd][USER_NAME]; event(users(), "inform", sprintf("%s logged out of ftpd", "name"), "ftp"); #ifdef LOG_CONNECT log_file("ftpd.log", sprintf("%s logged out at %s.\n", name, ctime(time()))); #endif map_delete(socket_info[fd], USER_NAME); map_delete(socket_info[fd], LOGGED_IN); map_delete(socket_info[fd], CWD); } /* logout() */ void parse_comm(int fd, string str) { string *bits, tmp; mixed *misc; int port, i, mask; if (strsrch(str, "PASS") == -1) TPX("Parseing " + str + ".\n"); bits = explode(str, " "); socket_info[fd][LAST_DATA] = time(); switch (lower_case(bits[0])) { case "port": bits = explode(implode(bits[1..1000], " "), ","); if (sizeof(bits) < 6) socket_write(fd, "550 Failed command.\r\n"); else { socket_info[fd][DATA_ADDR] = implode(bits[0..3], "."); sscanf(bits[4], "%d", i); port = i << 8; sscanf(bits[5], "%d", i); port += i; socket_info[fd][DATA_PORT] = port; socket_write(fd, "200 PORT command successful.\r\n"); } break; case "user": CHECK_CMD(1); if ((bits[1] == "mabel") && socket_info[fd][LOGGED_IN]) if (MASTER->query_lord(socket_info[fd][USER_NAME])) { "/obj/shut"->shut(10); if (find_object("/obj/shut")) socket_write(fd, "530 Mabelrode loaded.\r\n"); else socket_write(fd, "530 Mabelrode failed to load.\r\n"); break; } if (socket_info[fd][LOGGED_IN]) logout(fd); if (!LOGIN_OB->test_user(bits[1])) socket_write(fd, sprintf("530 User %s access denied...\r\n", bits[1])); else if (!LOGIN_OB->test_creator(bits[1])) socket_write(fd, sprintf("530 User %s access denied...\r\n", bits[1])); else { socket_write(fd, sprintf("331 Password required for %s.\r\n", bits[1])); socket_info[fd][USER_NAME] = bits[1]; } break; case "pass": if (socket_info[fd][LOGGED_IN] || !socket_info[fd][USER_NAME]) { socket_write(fd, "503 Login with USER first.\r\n"); break; } if (!LOGIN_OB->test_password(socket_info[fd][USER_NAME], bits[1])) { socket_write(fd, "530 Login incorrect.\r\n"); map_delete(socket_info[fd], USER_NAME); } else if (!LOGIN_OB->test_creator(socket_info[fd][USER_NAME])) { socket_info[fd][LOGGED_IN] = 2; socket_info[fd][CWD] = "/open"; socket_info[fd][TYPE] = STRING; event(users(), "inform", sprintf("%s(player) connected to ftpd", socket_info[fd][USER_NAME]), "ftp"); #ifdef LOG_CONNECT log_file("ftpd.log", sprintf("%s(player) (%s) connected at %s.\n", UNAME, explode(socket_address(fd)," ")[0], ctime(time()))); #endif } else { socket_info[fd][LOGGED_IN] = 1; socket_info[fd][CWD] = HOME_DIR(socket_info[fd][USER_NAME]); socket_info[fd][TYPE] = STRING; event(users(), "inform", sprintf("%s connected to ftpd", socket_info[fd][USER_NAME]), "ftp"); #ifdef LOG_CONNECT log_file("ftpd.log", sprintf("%s (%s) connected at %s.\n", UNAME, explode(socket_address(fd)," ")[0], ctime(time()))); #endif } if (file_size(socket_info[fd][CWD]) != -2) { socket_write(fd, "230 Cannot cd to home. Logging in with dir=/\r\n"); socket_info[fd][CWD] = "/"; } else socket_write(fd, sprintf("230 User %s logged in.\r\n", socket_info[fd][USER_NAME])); break; case "allo": socket_write(fd, "201 ALLO command ignored.\r\n"); break; case "noop": socket_write(fd, "200 NOOP operation successful.\r\n"); break; case "rnfr": CHECK_LOGIN(); CHECK_CMD(1); CHECK_PLAYER(); tmp = get_path(fd, bits[1]); if (MASTER->valid_read(tmp, socket_info[fd][USER_NAME], "file_size")) { socket_write(fd, sprintf("550 Permission denied reading %s.\r\n", tmp)); break; } if (file_size(tmp) != -1) { rnfr = tmp; socket_write(fd, "350 File exists, ready for destination name\r\n"); } else socket_write(fd, sprintf("550 %s: No such file or directory.\r\n", bits[1])); break; case "rnto": CHECK_LOGIN(); CHECK_CMD(1); CHECK_PLAYER(); if (rnfr == "") { socket_write(fd, "503 Bad sequence of commands.\r\n"); break; } tmp = get_path(fd, bits[1]); if (MASTER->valid_write(rnfr, socket_info[fd][USER_NAME], "rename") && MASTER->valid_write(tmp, socket_info[fd][USER_NAME], "rename")) { if (!catch(rename(rnfr, tmp))) socket_write(fd, "250 RNTO command successful.\r\n"); else socket_write(fd, "550 rename: No such file or directory.\r\n"); } else socket_write(fd, "550 rename: Operation not permitted.\r\n"); rnfr = ""; break; case "rest": CHECK_LOGIN(); CHECK_CMD(1); sscanf(bits[1], "%d", offset); socket_write(fd, sprintf("350 Restarting at %d. %s\r\n", offset, "Send STORE or RETRIEVE to initiate transfer.")); break; case "retr": CHECK_LOGIN(); CHECK_CMD(1); tmp = get_path(fd, bits[1]); switch(file_size(tmp)) { case -2: socket_write(fd, sprintf("550 %s: Not a plain file.\r\n", tmp)); break; case -1: socket_write(fd, sprintf("550 %s: No such file or directory.\r\n", tmp)); break; default: if (!MASTER->valid_read(tmp, socket_info[fd][USER_NAME], "read_file")) socket_write(fd, sprintf("550 Permison denied reading %s.\r\n", tmp)); else { #ifdef LOG_FILE log_file("ftpd.log", sprintf("%s RETR %s at %s.\n", UNAME, tmp, ctime(time()))); #endif data_conn(fd, tmp, bits[1], FILE); } break; } break; case "stor": CHECK_LOGIN(); CHECK_CMD(1); tmp = get_path(fd, bits[1]); if (MASTER->valid_write(tmp, socket_info[fd][USER_NAME], "write_file")) { #ifdef LOG_FILE log_file("ftpd.log", sprintf("%s STOR %s at %s.\n", UNAME, tmp, ctime(time()))); #endif if (offset) read_connection(fd, tmp, 1); else read_connection(fd, tmp, 0); } else socket_write(fd, sprintf("553 Permision denied to %s.\r\n", tmp)); break; case "dele": /* delete a file */ CHECK_LOGIN(); CHECK_CMD(1); CHECK_PLAYER(); tmp = get_path(fd, bits[1]); if (MASTER->valid_write(tmp, socket_info[fd][USER_NAME], "rm")) { #ifdef LOG_FILE log_file("ftpd.log", sprintf("%s DELE %s at %s.\n", UNAME, tmp, ctime(time()))); #endif if (!rm(tmp)) socket_write(fd, sprintf("550 %s: Directory not empty.\r\n", tmp)); else socket_write(fd, "250 DELE command successful.\r\n"); } else socket_write(fd, sprintf("550 Permission denied to %s.\r\n", tmp)); break; case "mkd": case "xmkd": /* make a dir */ CHECK_LOGIN(); CHECK_CMD(1); CHECK_PLAYER(); tmp = get_path(fd, bits[1]); if (MASTER->valid_write(tmp, socket_info[fd][USER_NAME], "mkdir")) { #ifdef LOG_FILE log_file("ftpd.log", sprintf("%s MKD %s at %s.\n", UNAME, tmp, ctime(time()))); #endif if (!mkdir(tmp)) socket_write(fd, sprintf("550 %s: File exits.\r\n", tmp)); else socket_write(fd, "257 MKD command successful.\r\n"); } else socket_write(fd, sprintf("550 Permission denied to %s.\r\n", tmp)); break; case "rmd": case "xrmd": /* remove a dir */ CHECK_LOGIN(); CHECK_CMD(1); CHECK_PLAYER(); tmp = get_path(fd, bits[1]); if (MASTER->valid_write(tmp, socket_info[fd][USER_NAME], "rmdir")) { if (file_size(tmp) == -1) { socket_write(fd, sprintf("550 %s: No such file or directory.\r\n", tmp)); break; } if (file_size(tmp) != -2) { socket_write(fd, sprintf("550 %s: Not a directory.\r\n", tmp)); break; } if (!rmdir(tmp)) socket_write(fd, sprintf("550 %s: Directory not empty.\r\n", tmp)); else { #ifdef LOG_FILE log_file("ftpd.log", sprintf("%s RMD %s at %s.\n", UNAME, tmp, ctime(time()))); #endif socket_write(fd, "250 RMD command successful.\r\n"); } } else socket_write(fd, sprintf("550 Permission denied to %s.\r\n", tmp)); break; case "appe": /* append... */ CHECK_LOGIN(); CHECK_CMD(1); CHECK_PLAYER(); tmp = get_path(fd, bits[1]); if (MASTER->valid_write(tmp, socket_info[fd][USER_NAME], "write_file")) { #ifdef LOG_FILE log_file("ftpd.log", sprintf("%s APPE %s at %s.\n", UNAME, tmp, ctime(time()))); #endif read_connection(fd, tmp, 1); } else socket_write(fd, sprintf("553 Permision denied to %s.\r\n", tmp)); break; case "help": // give help information if (sizeof(bits) > 1) { tmp = lower_case(bits[1]); if (bits[1] == "site") bits[1] = "HELP"; if (!undefinedp(cmdtab[ tmp ]) && tmp != "site") { misc = cmdtab[ tmp ]; if (misc[1]) socket_write(fd, sprintf("214 Syntax: %s %s.\r\n", misc[0], misc[2]) ); else socket_write(fd, sprintf("214 %s %s; unimplemented.\r\n", misc[0], misc[2]) ); break; } else if (bits[1] != "HELP") { socket_write(fd, sprintf("502 Unknown command %s.\r\n", bits[ 1 ]) ); break; } } else { int s; socket_write(fd, "214-The following commands are recognized " "(* =>'s unimplemented).\r\n"); misc = keys(cmdtab); s = sizeof(misc); tmp = " "; for (i = 0; i < s; i++) { tmp += sprintf("%-4s%-4s", cmdtab[misc[i]][0], cmdtab[misc[i]][1] ? " " : "*"); if (i % 8 == 7) { socket_write(fd, tmp + "\r\n"); tmp = " "; } } if (i % 8) socket_write(fd, tmp + "\r\n"); socket_write(fd, sprintf("214 Direct comments to %s.\r\n", "Dyraen@RotD")); break; } case "site": // non-standard commands CHECK_LOGIN(); switch (lower_case(bits[ 1 ])) { case "idle": if (sizeof(bits) < 3) { socket_write(fd, sprintf("200 Current IDLE time limit is %d seconds; max 7200\r\n", socket_info[fd][IDLE])); break; } if (!sscanf(bits[2], "%d", i)) { socket_write(fd, "550 SITE IDLE command failed.\r\n"); break; } i = (i<300?300:(i>7200?7200:i)); socket_info[fd][IDLE] = i; socket_write(fd, sprintf("200 Maximum IDLE time set to %d seconds\r\n", i)); break; case "time": socket_write(fd, sprintf("200 Local TIME is %s.\r\n", ctime(time())[4..15] ) ); break; case "upd": /* remote updating of files */ CHECK_CMD(2); tmp = get_path(fd, bits[2]); do_update(tmp, fd); #ifdef LOG_FILE log_file("ftpd.log", sprintf("%s UPD %s at %s.\n", UNAME, tmp, ctime(time()))); #endif break; case "help": if (sizeof(bits) > 2) { tmp = lower_case(bits[2]); if (!undefinedp(sitecmdtab[ tmp ])) { misc = sitecmdtab[ tmp ]; if (misc[1]) socket_write(fd, sprintf("214 Syntax: SITE %s %s.\r\n", misc[0], misc[2]) ); else socket_write(fd, sprintf("214 SITE %s %s; unimplemented.\r\n", misc[0], misc[2]) ); } else { socket_write(fd, sprintf("502 Unknown command %s.\r\n", bits[ 2 ]) ); } } else { int s; socket_write(fd, "214-The following SITE commands are recognized " "(* =>'s unimplemented).\r\n"); misc = keys(sitecmdtab); s = sizeof(misc); tmp = " "; for (i = 0; i < s; i++) { tmp += sprintf("%-*s%-*s", strlen(sitecmdtab[misc[i]][0]), sitecmdtab[misc[i]][0], 8-strlen(sitecmdtab[misc[i]][0]), sitecmdtab[misc[i]][1] ? " " : "*"); if (i % 8 == 7) { socket_write(fd, tmp + "\r\n"); tmp = " "; } } if (i % 8) socket_write(fd, tmp + "\r\n"); socket_write(fd, sprintf("214 Direct comments to %s.\r\n", "Dyraen@RotD")); } break; case "newer": case "minfo": socket_write( fd, sprintf("502 %s command not implemented.\r\n", bits[ 0 ]) ); break; default: socket_write( fd, sprintf("500 '%s %s': command " "not understood.\r\n", bits[ 0 ], bits[ 1 ]) ); break; } break; case "mdtm": /* Supposed to return modified time in the format: YYYYMMDDHHMMSS */ CHECK_LOGIN(); CHECK_CMD(1); tmp = get_path(fd, bits[1]); if (MASTER->valid_read(tmp, socket_info[fd][USER_NAME], "file_size")) { if (file_size(tmp) == -2) socket_write(fd, sprintf("550 %s not a plain file.\r\n", tmp)); else if (file_size(tmp) == -1) socket_write(fd, sprintf("550 %s does not exist.\r\n", tmp)); else { mixed *tm; tm = localtime(stat(tmp)[1]+localtime(0)[LT_GMTOFF]); socket_write(fd, sprintf("213 %d%02d%02d%02d%02d%02d\r\n", tm[LT_YEAR], tm[LT_MON]+1, tm[LT_MDAY], tm[LT_HOUR], tm[LT_MIN], tm[LT_SEC])); } } else socket_write(fd, sprintf("550 Permission denied to %s.\r\n", tmp)); break; case "size": CHECK_LOGIN(); CHECK_CMD(1); tmp = get_path(fd, bits[1]) ; if (MASTER->valid_read(tmp, socket_info[fd][USER_NAME], "file_size")) { if (file_size(tmp) == -2) socket_write(fd, sprintf("550 %s not a plain file.\r\n", tmp)); else if (file_size(tmp) == -1) socket_write(fd, sprintf("550 %s does not exist.\r\n", tmp)); else { #ifdef LOG_CD_SIZE log_file("ftpd.log", sprintf("%s SIZE %s at %s.\n", UNAME, tmp, ctime(time()))); #endif socket_write(fd, sprintf("213 %d\r\n", file_size(tmp))); } } else socket_write(fd, sprintf("550 Permission denied to %s.\r\n", tmp)); break; case "stat": if (sizeof(bits) > 1) { CHECK_LOGIN(); tmp = get_path(fd, bits[1]); if (MASTER->valid_read(tmp, socket_info[fd][USER_NAME], "get_dir")) { if (file_size(tmp) != -1) socket_write(fd, sprintf("211-status of %s:\r\n%s" "211 End of status\r\n", bits[1], ls(tmp, MASK_L))); else socket_write(fd, sprintf("211 %s: No such file or directory.\r\n", tmp)); } else socket_write(fd, sprintf("211 Permission denied to %s.\r\n", tmp)); break; } else { socket_write(fd, sprintf("211-%s FTP server status:\r\n", mud_name())); socket_write(fd, sprintf(" %s %s\r\n", FTP_VERSION, ctime(stat(file_name(this_object())+".c")[1]))); sscanf(socket_address(fd), "%s %*d", tmp); socket_write(fd, sprintf(" Connected to %s\r\n", tmp)); if (socket_info[fd][LOGGED_IN]) socket_write(fd, " Logged in as " + socket_info[fd][USER_NAME] + "\r\n"); else if (socket_info[fd][USER_NAME]) socket_write(fd, " Waiting for password\r\n"); else socket_write(fd, " Waiting for user name\r\n"); socket_write(fd, sprintf(" TYPE: %s", (socket_info[fd][TYPE] == STRING?"ASCII":"BINARY"))+ ", FORM: Nonprint; STRUcture: " + "File; transfer MODE: Stream\r\n"); if (socket_info[fd][DATA_FD]) socket_write(fd, " Data connection open\r\n"); else if (socket_info[fd][DATA_ADDR]) socket_write(fd, sprintf(" PORT (%s,%d,%d)\r\n", replace_string(socket_info[fd][DATA_ADDR], ".", ","), socket_info[fd][DATA_PORT]>>8, socket_info[fd][DATA_PORT] & 0xff)); else socket_write(fd, " No data connection\r\n"); socket_write(fd, "211 End of status\r\n"); break; } case "list": if ((sizeof(bits) > 1 && bits[1][0] != '-') || sizeof(bits) == 1) { mask |= MASK_L; mask |= MASK_A; } case "nlst": CHECK_LOGIN(); /* Send name list. */ if ((i = sizeof(bits)) > 1 && bits[1][0] == '-') { int j = strlen(bits[1]); while (j--) { if (bits[1][j] == '-') continue; switch(bits[1][j]) { case 'l': mask |= MASK_L; break; case 'C': mask |= MASK_C; break; case 'F': mask |= MASK_F; break; case 'a': mask |= MASK_A; } } if (i == 2) bits[1] = "."; else bits = ({ bits[0] }) + bits[2..i-1]; } if (sizeof(bits) > 1) tmp = get_path(fd, bits[1]); else tmp = socket_info[fd][CWD]; if (MASTER->valid_read(tmp, socket_info[fd][USER_NAME], "read_file")) data_conn(fd, ls(tmp, mask), "ls", STRING); else socket_write(fd, sprintf("550 Permision denied to %s.\r\n", tmp)); break; case "pwd": case "xpwd": CHECK_LOGIN(); socket_write(fd, sprintf("257 \"%s\" is the current directory.\r\n", socket_info[fd][CWD])); break; case "cdup": case "xcup": bits[1] = ".."; case "cwd": case "xcwd": CHECK_LOGIN(); CHECK_PLAYER(); if (sizeof(bits) > 1) tmp = get_path(fd, bits[1]); else tmp = socket_info[fd][CWD]; if (MASTER->valid_read(tmp, socket_info[fd][USER_NAME], "cwd") || tmp == "/") { switch(file_size(tmp)) { case -2: #ifdef LOG_CD_SIZE log_file("ftpd.log", sprintf("%s CWD %s at %s.\n", UNAME, tmp, ctime(time()))); #endif socket_info[fd][CWD] = get_path(fd, tmp); socket_write(fd, "250 CWD command successful.\r\n"); break; case -1: socket_write(fd, sprintf("550 %s: No such file or directory.\r\n", tmp)); break; default: socket_write(fd, sprintf("550 %s: Not a directory.\r\n", tmp)); break; } } else socket_write(fd, sprintf("550 Permission denied to %s.\r\n", tmp)); break; case "quit": socket_write(fd, "221 Goodbye.\r\n"); event(users(), "inform", sprintf("%s quit ftpd", socket_info[fd][USER_NAME]), "ftp"); #ifdef LOG_CONNECT log_file("ftpd.log", sprintf("%s logged out at %s.\n", UNAME, ctime(time()))); #endif socket_close(fd); map_delete(socket_info, fd); break; case "type": CHECK_LOGIN(); CHECK_CMD(1); if (bits[1] == "I" || bits [1] == "B") { socket_info[fd][TYPE] = BINARY; socket_write(fd, "200 Type set to I.\r\n"); } else if (bits[1] == "A") { socket_info[fd][TYPE] = STRING; socket_write(fd, "200 Type set to A.\r\n"); } else socket_write(fd, sprintf("504 Type %s not implemented.\r\n", bits[1])); break; case "abor": /* Abort... Handle this with blue stuff, * stops recvs and stors. I guess thats * what it is supposed to do. */ if (socket_info[fd][DATA_FD]) { socket_close(socket_info[fd][DATA_FD]); map_delete(socket_info, socket_info[fd][DATA_FD]); map_delete(socket_info[fd], DATA_FD); map_delete(socket_info[fd], DATA_ADDR); offset = 0; } socket_write(fd, "225 ABOR command successful.\r\n"); break; case "syst": socket_write(fd, "215 MUD Type: Realms of the Dragon\r\n"); break; case "acct": case "smnt": case "rein": case "pasv": case "stru": case "mode": case "mlfl": case "mail": case "msnd": case "msom": case "msam": case "mrsq": case "mrcp": case "stou": socket_write(fd, sprintf("502 %s command not implemented.\r\n", bits[0])); break; default: socket_write(fd, sprintf("500 '%s': command not understood.\r\n", str)); break; } } /* parse_comm */ void in_read_callback(int fd, string str) { string *bits; int i; str = replace(str, ({sprintf("%c", 242), "", "\r", "", sprintf("%c", 255), "", sprintf("%c", 244), ""})); bits = explode(str, "\n"); for (i = 0; i < sizeof(bits); i++) parse_comm(fd, bits[i]); } /* in_read_callback() */ void in_close_callback(int fd) { map_delete(socket_info, fd); socket_close(fd); } /* in_close_callback() */ string get_path(int fd, string str) { string *arry, *arry1, temp; int i; if (!str || str == "") { /* no change of dir */ return socket_info[fd][CWD]; } if (str == "~") { /* change to home dir */ return HOME_DIR(socket_info[fd][USER_NAME]); } else { if (str[0] == '~') { if (str[1] == '/') { sscanf(str, "~%s", temp); str = HOME_DIR(socket_info[fd][USER_NAME]) + temp; } else { string name; if (sscanf(str, "~%s/%s", name, str) != 2) { name = extract(str, 1); str = HOME_DIR(name); } else { /* cheat at this point and just assume they are a wizard. sigh * i know i know */ str = HOME_DIR(name) + "/" + str; } } } else if (str[0] != '/') str = socket_info[fd][CWD] + "/" + str + "/"; } if (str == "/") return "/"; arry = explode(str, "/") - ({ "" }); for (i = 0; i < sizeof(arry); i++) { if (arry[i] == "..") { if (i < 1) return "/"; if (i == 1) arry1 = ({ "." }); else arry1 = arry[0..i - 2]; if (i + 1 <= sizeof(arry) - 1) arry1 += arry[i + 1..sizeof(arry) - 1]; arry = arry1; i -= 2; } else if (arry[i] == ".") arry[i] = 0; } if (arry) str = implode(arry, "/"); else str = ""; return "/" + str; } /* get_path() */ string desc_object(mixed o){ string str; if (!o) return "** Null-space **"; if (!catch(str = (string)o->short()) && str) return str; if (!catch(str = (string)o->query_name()) && str) return str; return file_name(o); } /* desc_object() */ string desc_f_object(object o) { string str, tmp; str = desc_object(o); if (o && str != file_name(o)) { if (tmp) str += " (" + tmp + ")"; else str += " (" + file_name(o) + ")"; } return str; } /* desc_f_object() */ string get_cfile(string str) { if (sscanf(str, "%*s.%*s") != 2) str += ".c"; return str; } /* get_cfile() */ static void do_update(string name, int fd) { string pname, err; int i, j; object *invent, rsv, env, dup, loaded, ov; mixed static_arg, dynamic_arg; "room/void"->bingle_bingle(); rsv = find_object("room/void"); /* RSV = Room Slash Void */ if (!rsv) { /* Die in horror */ socket_write(fd, "530 The void is lost!\r\n"); return; } name = get_cfile(name); ov = find_object(name); if (!ov) { if(file_size(name) >= 0) { if (!(err = catch(name->bing_with_me()))) socket_write(fd, sprintf("530 Loaded %s.\r\n", name)); else socket_write(fd, sprintf("530 Failed to load %s, error: %s\r\n", name, err)); } else socket_write(fd, sprintf("530 File %s does not exist.\r\n", name)); return; } env = environment(ov); invent = all_inventory(ov); for (j = 0; j < sizeof(invent); j++) invent[j]->move(rsv); pname = file_name(ov); if (sscanf(pname, "%s#%*d", pname) != 2) { /* a room ? */ ov -> dest_me(); if (ov) ov->dwep(); if (ov) destruct(ov); file_size("/secure/master"); if (!ov) ov = find_object(pname); call_other(pname, "??"); ov = find_object(pname); } else { loaded = find_object(pname); static_arg = ov->query_static_auto_load(); dynamic_arg = ov->query_dynamic_auto_load(); if (loaded) loaded->dest_me(); if (loaded) loaded->dwep(); if (loaded) destruct(loaded); dup = clone_object(pname); if (dup && ov) { ov -> dest_me(); if (ov) ov->dwep(); if (ov) destruct(ov); ov = dup; if (static_arg) ov->init_static_arg(static_arg); if (dynamic_arg) ov->init_dynamic_arg(dynamic_arg); } } if (!ov) { socket_write(fd, "530 I seem to have lost your object.\r\n"); return; } for (j = 0; j < sizeof(invent); j++) if (invent[j]) invent[j]->move(ov); if (env) ov->move(env); socket_write(fd, sprintf("530 Updated %s.\r\n", desc_f_object(ov))); } /* do_update() */ void check_connections() { int *bits, i; bits = keys(socket_info); i = sizeof(bits); while (i--) if ((socket_info[bits[i]][LAST_DATA]+socket_info[bits[i]][IDLE]) <= time()) { socket_write(bits[i], sprintf("421 Timeout (%d seconds): closing control connection.\r\n", socket_info[bits[i]][IDLE])); socket_close(bits[i]); map_delete(socket_info, bits[i]); } call_out("check_connections", 5 * 60); } /* check_connections() */ int check_dots(mixed arg) { return (arg[0] != ".." && arg[0] != "."); }