ftpd.c

来自「C实现的MUD,对大家基本入门网络游戏很有帮助!」· C语言 代码 · 共 1,581 行 · 第 1/3 页

C
1,581
字号
#define FTPD_VERSION "5.8"

inherit F_DBASE;

mapping socket_info;
int our_socket;
mapping temp_map;
int number_of_users;
int rest_pos;

/*
 * edit ftpdconf.h & ftpdsupp.h to configure ftpd for your mudlib
 */
#include <net/ftpd.h>
#include <net/ftpdconf.h>
#include <net/ftpdsupp.h>

void setup_ftp( int port );
void finish_lookup( string host, string number );
void resolve_callback( string address, string resolved, int key );
string get_path( int fd, string str );
int strncmp(string str, string sub, int i);

void lose_connection( int fd )
{
	socket_close( fd );
	map_delete( socket_info, fd );
}

void lose_user( int fd )
{
	socket_close( fd );
	map_delete( socket_info, fd );
	number_of_users--;
}

int strncmp(string str, string sub, int i)
{
	int j;

	j = strlen(str);
	return (i > j ? strcmp(str, sub) : strcmp(str[0..i-1], sub));
}

int check_valid_read( string fname, int fd )
{
	int res;

	seteuid( socket_info[ fd ][ USER_NAME ] );
	res = (int) master() -> valid_read(fname, this_object(), "read_file");
	seteuid( getuid() );

	/*
	 * This rather complicated looking section of code handles the
	 * various read levels available.  See ftpdconf.h for READ_LEVEL
	 * definitions of these flags.
	 */
#ifdef READ_LEVEL
#if READ_LEVEL == VALID_READ
	return res;
#elif READ_LEVEL == RESTRICTED_READ
	if (strncmp(fname, HOME_DIR( UNAME ), strlen(HOME_DIR( UNAME ))) == 0
#ifdef PUB_DIR
		  || strncmp(fname, PUB_DIR, strlen(PUB_DIR)) == 0
#endif
#ifdef FTP_DIR
		  || strncmp(fname, FTP_DIR, strlen(FTP_DIR)) == 0
#endif
#ifdef TMP_DIR
		  || strncmp(fname, TMP_DIR, strlen(TMP_DIR)) == 0
#endif
		  )
		return res;
	else
		return 0;
#elif READ_LEVEL == WRITE_LIMIT_READ
	return check_valid_write( fname, fd );
#else
	// default
	return res;
#endif
#else
	// default
	return res;
#endif
}

int check_valid_write( string fname, int fd )
{
	int res;

#ifdef MESSAGE_FILES
	// Prevent removing .message file from ftp connection.
	if( fname[<8..<0] == "/.message") return 0;
#endif

	seteuid( socket_info[ fd ][ USER_NAME ] );
	res = (int) master() -> valid_write(fname, this_object(), "write_file");
	seteuid( getuid() );

	/*
	 * This rather complicated looking section of code handles the
	 * various write levels available.  See ftpdconf.h for WRITE_LEVEL
	 * definitions of these flags.
	 */
#ifdef WRITE_LEVEL
#if WRITE_LEVEL == VALID_WRITE
	return res;
#elif WRITE_LEVEL == RESTRICTED_WRITE
	if (strncmp(fname, HOME_DIR( UNAME ), strlen(HOME_DIR( UNAME ))) == 0
#ifdef PUB_DIR
		  || strncmp(fname, PUB_DIR, strlen(PUB_DIR)) == 0
#endif
#ifdef FTP_DIR
		  || strncmp(fname, FTP_DIR, strlen(FTP_DIR)) == 0
#endif
#ifdef TMP_DIR
		  || strncmp(fname, TMP_DIR, strlen(TMP_DIR)) == 0
#endif
		  )
		return res;
	else
		return 0;
#elif WRITE_LEVEL == READ_ONLY
	return 0;
#else
	// default
	return res;
#endif
#else
	// default
	return res;
#endif
}

mapping query_sinfo() { return socket_info; }

/* query_connections returns an array of users connected to ftpd */
mapping *query_connections()
{
	mixed *vals;
	mapping *list;
	int count, i;

	return values(socket_info);
	vals = values( socket_info );
	list = allocate( sizeof( vals ) );
	count = sizeof( vals );

	while ( count-- )
		if (!undefinedp( (vals[count])[USER_NAME] ))
			list[i++] = ([
						   USER_NAME :
							 capitalize(( string )((vals[count])[USER_NAME])),
						   USER_SITE :
							 (string)((vals[count])[USER_SITE])
						]);

	return i ? list[0..i-1] : ({ });
}

void create()
{
	set("channel_id", "传输精灵");
	seteuid(ROOT_UID);
	if ( !socket_info ) {
		socket_info = ([  ]);
		temp_map = ([ ]);
		number_of_users = 0;
		call_out( "setup_ftp", 2, FTPD_PORT );
		call_out( "check_connections", 3 * 60 );
	}

	rest_pos = 0;
	CHANNEL_D->do_channel(this_object(), "sys", "FTP服务已经启动。");
}

protected void setup_ftp( int port )
{
	our_socket = socket_create( STREAM, "in_read_callback",
								"in_close_callback" );
	if ( our_socket < 0 ) {
		TP( "Failed to create socket.\n" );
		return;
	}
	if ( socket_bind( our_socket, port ) <  0 ) {
		TP( "Failed to bind socket.\n" );
		socket_close( our_socket );
		return;
	}
	if ( socket_listen( our_socket, "in_listen_callback" ) <  0 ) {
		TP( "Failed to listen to socket.\n" );
		return;
	}
} /* setup_ftp() */

void in_write_callback( int fd )
{
	string address;
	int res;

	if ( undefinedp( socket_info[fd][CONNECTED] ) ) {
		if ( number_of_users > FTPD_MAX_USERS ) {
			res = socket_write( fd,
				  "530 Too many ftp connections active.  Try again later.\n");
			/*
			 * In these cases there isn't much more that can be done
			 */
			if (res != EECALLBACK)
				lose_user( fd );

			/*
			 * The following blocks the code in_read_callback &
			 * in_write_callback on this connection; we let the driver
			 * complete the send and let the daemon timeout the connection
			 */
			if (res == EECALLBACK) {
				socket_info[fd][CONNECTED] = 0;
			}
			return;
		}

		res = socket_write( fd,
			  sprintf( "220- %s FTP server (Version %s (MudOS/LPC)) ready.\n"
#ifdef ANONYMOUS_FTP
					   "220  Please login as yourself or 'anonymous'.\n",
#else
					   "220  Please login as yourself.\n",
#endif
					   THE_MUD_NAME, FTPD_VERSION ) );

		/*
		 * In these cases, assume user will receive above message
		 */
		if (res == EESUCCESS || res == EECALLBACK) {
			socket_info[fd][CONNECTED] = 1;
			if ((address = socket_address(fd)) &&
				  sscanf( address, "%s %*s", address )) {
				socket_info[fd][USER_SITE] = address;
				socket_info[fd][IDLE_SETTING] = FTPD_TIMEOUT;
				temp_map += ([ resolve( address, "resolve_callback" ) : fd ]);
			} else {
				socket_write( fd,
						"451 Error in server: socket_address() failed.\n"
						"221 Closing connection due to server error.\n");
				lose_user( fd );
			}
		} else if (res == EEWOULDBLOCK) {
			call_out( "in_write_callback", 2, fd );
		} else {
			lose_user( fd );
		}
	}
}

/*
 * called for each new ftp login connection
 */
void in_listen_callback( int fd )
{
	int new_fd;

	if ( ( new_fd =
		socket_accept( fd, "in_read_callback", "in_write_callback" ) ) < 0 )
		return;

	number_of_users++;
	socket_info[new_fd] = ([  ]);
	socket_info[new_fd][LAST_DATA] = time();
	in_write_callback( new_fd );
}

string ls( string path, int column, int fd )
{
	string *files;
	int i, j, s;
	mixed *xfiles;
	mixed *stats;
	string tmp, tmp2, creator, domain;

	/* if path is a directory get contents */
	if ( directory_exists( path ) ) {
		if ( path[ strlen( path ) - 1 ] == '/' )
			path += "*";
		else
			path += "/*";
	}

	/* begin narrow columnar "nlst" */
	if (column) {
		files = get_dir( path );

		/* can only happen if permissions are messed up at account level */
		if (!files) return "";

		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 ( directory_exists( tmp ) ) {
				files[i] += "/";
			}
		}
		return implode( files, "\n" ) + "\n";
	}

	/* begin long "list" */
	xfiles = get_dir( path, -1 );
	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_UID;
	creator = creator[0..11];

	domain = (string)master()->domain_file(path);
	if (!domain)  domain = ROOT_UID;
	domain = domain[0..11];

	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] + SECS_IN_YEAR < 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 %-8s %-8s     1024 %12s %s",
				  2, creator, domain, tmp, (xfiles[i])[0]);
		} else {
			/* file */
			stats = stat(path + (xfiles[i])[0]);
			files[i] = sprintf("-rw%crw-r--   1 %-8s %-8s %8d %12s %s",
				  stats[2] ? 'x' : '-', /* 'x' if loaded, else ' ' */
				  creator, domain, j, tmp, (xfiles[i])[0]);
		}
	}

	return sprintf( "%-#70s\n", implode( files, "\n" ) );
}

buffer to_stream(buffer buf)
{
        string s;
	buffer ret;

        if (! sizeof(buf) )
                return buf;

        s = read_buffer(buf, 0, sizeof(buf));
        s = replace_string(s, "\n", "\r\n");
	ret = allocate_buffer(strlen(s));
	write_buffer(ret, 0, s);
        return ret;
}

void data_write_callback( int fd )
{
	int pos, length, ret_val;
	buffer tmp;
	string s;

	if ( socket_info[fd][TYPE] == DOWNLOAD ) return;

	pos = socket_info[fd][POS];
	length = ( (pos+BLOCK_SIZE) >= socket_info[fd][LEN] ) ?
			 ( socket_info[fd][LEN] - pos ) : BLOCK_SIZE;

	socket_info[ socket_info[fd][PARENT_FD] ][LAST_DATA] = time();

	if ( socket_info[fd][TYPE] == STRING ) {
		while ( ((ret_val = socket_write( fd, socket_info[fd][DATA][pos..(pos+BLOCK_SIZE-1)] )) >= 0)
		||	(ret_val==EECALLBACK) ) {
			pos += BLOCK_SIZE;
			if ( pos > socket_info[ fd ][ LEN ] ) {
				socket_write( socket_info[ fd ][ PARENT_FD ], "226 Transfer complete.\n" );
				socket_close( fd );
				map_delete( socket_info[ socket_info[ fd ][ PARENT_FD ] ], DATA_FD );
				return;
			}
			if( ret_val==EECALLBACK ) break;
		}
	} else {
		if (catch( tmp = read_buffer( socket_info[ fd ][ DATA ], pos, length ) )) {
			socket_write( socket_info[ fd ][ PARENT_FD ], "551 Error on input file.\n" );
			socket_close( fd );
			map_delete( socket_info[ socket_info[ fd ][ PARENT_FD ] ], DATA_FD );
			return;
		}

                if ( socket_info[ fd ][ TRANSFER_MODE ] == STREAM )
                        tmp = to_stream(tmp);

		while ( ((ret_val = socket_write( fd, tmp )) >= 0)
				|| (ret_val==EECALLBACK) ) {
			pos += length;
			if ( pos >= socket_info[ fd ][ LEN ] ) {
				socket_write( socket_info[ fd ][ PARENT_FD ], "226 Transfer complete.\n" );
				socket_close( fd );
				map_delete( socket_info[ socket_info[ fd ][ PARENT_FD ] ], DATA_FD );
				return;
			}
			if( ret_val==EECALLBACK ) break;
			length = ( (pos+BLOCK_SIZE) >= socket_info[ fd ][ LEN ] ) ?
				  ( socket_info[ fd ][ LEN ] - pos ) : BLOCK_SIZE;
			tmp = read_buffer( socket_info[ fd ][ DATA ], pos, length );

                        if ( socket_info[ fd ][ TRANSFER_MODE ] == STREAM )
                                tmp = to_stream(tmp);
		}
	}

	// Advance the position we're sending next time.
	socket_info[ fd ][ POS ] = pos;

	switch(ret_val) {
		case EEWOULDBLOCK:	// data transfer busy (MudOS -> OS)
			call_out("data_write_callback", 2, fd);
		case EECALLBACK:	// data transfer busy (LPC -> MudOS)
			return;
		default:		
#ifdef DEBUG_REPORT
			CHANNEL_D->do_channel(this_object(), "sys",
				"data_write_callback: error " + ret_val + ", wait until next callback.");
#endif
			while ( remove_call_out( "data_write_callback" ) != -1 );
	}
}

protected void data_conn( int fd, string mess, string name, int type )
{
	int new_fd, ret, data_mode;
	string data_mode_name;
	mixed tmp;

	if(	type == STRING
	||	( type == FILE && UTYPE == STRING ) ) {
		data_mode_name = "ASCII";
		data_mode = STREAM;
		tmp = "";
                rest_pos = 0;
	} else {
		data_mode_name = "BINARY";
		data_mode = STREAM_BINARY;
		tmp = allocate_buffer( 0 );
	}

	if ( socket_info[ fd ][ DATA_FD ] ) {
		socket_write( fd, "425 Can't open data connection.\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.\n" );
		return;
	}

	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 ) {
		socket_write( fd, "425 Can't build data connection.\n" );
		socket_close( new_fd );
		return;
	}

	socket_info[ new_fd ] = ([
		DATA	  : (type == STRING ? replace_string(mess, "\n", "\r\n") : mess),
		POS	  : rest_pos,
		PARENT_FD : fd,
		TYPE	  : type,
                LEN       : (type == STRING ? strlen(mess) : file_size(mess)),
                TRANSFER_MODE : data_mode,
	]);
        rest_pos = 0;

	socket_info[ fd ][ DATA_FD ] = new_fd;

	if ( type == STRING )
	{
	        socket_write( fd, sprintf("150 Opening %s mode data "
        	          "connection for %s (%d bytes).\n",
	                  data_mode_name, name,
	                  socket_info[ new_fd ][ LEN ]));
	} else
	{
	        socket_write( fd, sprintf("150 Opening %s mode data "
        	          "connection for %s start at %d (%d bytes).\n",
	                  data_mode_name, name,
			  socket_info[ new_fd ][ POS ],
	                  socket_info[ new_fd ][ LEN ]));
	}
}

protected void read_connection( int fd, string path, int append )
{
	int new_fd, ret, data_mode;
	string data_mode_name;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?