背景:
阅读文章

MUD FTP服务器的修缮

[日期:2007-05-06] 来源:交通大学思源BBS  作者: [字体: ]

    写这篇文章的目的一是介绍修改 MUD 的 FTP 功能使 CuteFTP 能正常显示
FTP 服务器的目录的方法(不一定是最好的喔),二是希望这种方法能为有
兴趣者发现问题和解决问题提供一个参照。

    一切都是因为要对运行中的 MUD 的部分程序进行修改而起的。
    当运行中的 MUD 需要作比较小的改动,小到不值得将 MUD 服务器重新


启动一次时,有两种方法可以实现:一是使用 MUDOS 的 ed 命令(MUDOS
最先是在 UNIX 系统下开发出来的,因此行编辑的命令叫 ed 而不是 edit,
当然我们不用管这个,只管敲 edit 就行了,edit motd、edit here......
随叫随到,但不大好用,尤其是编辑大文件 :-( );另一个方法就是通过
MUD 的 FTP 功能下载——编辑——上传,先载入 MUD 的 FTP 服务程序,
通常是 /adm/daemons/ftpd.c ,在 /adm/etc/preload 文件中增加一行:
/adm/daemons/ftpd
就可以在 MUD 启动的时候连带启动 FTP 服务器,也可以在 MUD 运行的过程
中让有权限的巫师使 update /adm/daemons/ftpd.c 来加载 FTP 服务器。接
着使用 CuteFTP 之类的软件连接到 MUD 的 FTP 服务器,下载源程序到自己
的机器上,用 UltraEdit 等功能较强、使用较方便的编辑器来修改,随你怎
么改都行,但别忘了存盘 :-) 改好了再用 CuteFTP 传回 MUD 服务器,最后
update 使改动生效。
    CuteFTP 是很优秀的软件,但不是万能的,它的作者没有专门为 MUD 的
FTP 服务器考虑过(其实应该是 MUD 的 FTP 程序的作者没有认真考虑过啦),
便导致了用 CuteFTP 连接 MUD 的 FTP 服务器时,默认的目录显示方式不能
正常的显示服务器端的目录结构,以 FTPD V5.8 为例,一个目录都显示不出
来,真……把 CuteFTP的目录显示方式设为简单方式(编辑连接的 Advanced
属性,把 Simple directory listing 勾上)只显示文件、目录名,忽略文件
大小、创建日期等)稍微好些,但切换目录还是很不方便,习惯的鼠标双击在
这里不能用了。
    CuteFTP 不能正常显示目录,是因为 FTP 服务器传回的数据格式不正确,
解决之道就是使 FTP 服务器传给 CuteFTP 的数据格式正确化,别无他法。别
把这个格式想的太难太深奥,只要细心些,它其实是很简单的——无非就是些
字符串罢了,说到字符串的处理,有信心了吧!

    接下来就得弄到这些字符串的一个样本。从哪里弄呢?从 CuteFTP 是不
行的,得依靠一个比较原始的 FTP 软件——就是吻都死自带的 ftp.exe
打开一个 DOS 命令窗口,敲入 ftp ,回车,就会出现提示符:ftp>
接着敲 open   连接到 MUD 的 FTP 服务器,输入帐号
和密码(如果 MUD 的管理员为你开了个使用权限,FTP 的帐号和密码就是你
在 MUD 里的帐号和密码)登录。比如:
ftp> open localhost 8900
Connected to User.ChinaTone.com.
220- 西游记 FTP server (Version 5.8 (MudOS/LPC)) ready.
220  Please login as yourself.
User (User.ChinaTone.com:(none)):fof
331 Password required for fof.
Password:********
230 No directory! Logging in with home=/
ftp>

至此已登录成功。
敲一个 ls (列目录)命令:
ftp> ls
200 PORT command successful.
150 Opening ASCII mode data connection for ls (220 bytes).
adm/
cmds/
config.xiyou
d/
daemon/
data/
doc/
feature/
include/
log/
mudos.exe
mudos.log
obj/
std/
226 Transfer complete.
ftp: 237 bytes received in 0.00Seconds 237000.00Kbytes/sec.
ftp>
看啦,MUD 的根目录就是上面这些东东。带"/"尾巴的是目录,不带的是文件。
这只是简单列表而已,就相当于 CuteFTP 里把 Simple directory listing
那个选项勾上。看详细列表,得加个 -l 参数或者使 dir 命令:
ftp> ls -l (或 dir )
200 PORT command successful.
150 Opening ASCII mode data connection for ls (1316 bytes).
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:11  ./
drwxrwsr-x         MudOS          Root    < DIR >  Jan 01  1980  ../
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:11  adm/
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:11  cmds/
-rw-rw-r--         MudOS          Root      5504  Nov 15 00:10  config
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:11  d/
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:12  daemon/
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:12  data/
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:12  doc/
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:13  feature
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:13  include
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:13  log/
-rw-rw-r--         MudOS          Root    700416  Jun 29 13:09  mudos.
-rw-rw-r--         MudOS          Root      5185  Nov 15 12:06  mudos.
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:13  obj/
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:13  std/
226 Transfer complete.
ftp: 1335 bytes received in 0.22Seconds 6.07Kbytes/sec.
ftp>

以上看到的是 v5.8 版的 MUD FTP 服务器,下面来看看标准的 FTP 服务器:
(就拿北大的 FTP 服务器做例子,这个服务器应该是蛮标准的吧)
ftp> open ftp.pku.edu.cn
……
ftp> ls -l
200 PORT command successful.
150 Opening ASCII mode data connection for /bin/ls.
total 55880
-rw-r--r--   1 ftpadmin ftpadmin     984 Nov 19 1999  .message
----------   1 ftpadmin ftpadmin       0 Sep  6 1999  .notar
-rw-r--r--   1 ftpadmin ftpadmin     534 Nov 19 1999  WELCOME
d--x--x--x   2 ftpadmin ftpadmin     512 Nov 11 1999  bin
drwxrwxr-x   2 ftpadmin ftpadmin     512 Jul  5 1999  dev
d--x--x--x   2 ftpadmin ftpadmin     512 Nov 15 1999  etc
drwxrws-wt   2 ftpadmin ftpadmin   40960 Nov 15 10:10 incoming
-rw-r--r--   1 ftpadmin ftpadmin 25941142 Nov 10 00:06 ls-lR
-rw-r--r--   1 ftpadmin ftpadmin 2593008 Nov 10 00:06 ls-lR.gz
-rw-r--r--   1 ftpadmin ftpadmin       0 Nov 15 00:00 ls-lR.new
drwxr-xr-x  25 ftpadmin ftpadmin    1024 Oct 12 18:28 pub
drwxr-xr-x   3 ftpadmin ftpadmin     512 Jul  5 1999  usr
226 Transfer complete.
ftp: 752 bytes received in 0.00Seconds 752000.00Kbytes/sec.
ftp>

OK!标准的目录列表格式已经拿到了。接下来,
将 MUD FTP 和标准 FTP 各抽出一个文件项来比较:
-rw-rw-r--         MudOS          Root    700416  Jun 29 13:09  mudos.
-rw-r--r--   1 ftpadmin ftpadmin     534 Nov 19 1999  WELCOME

将 MUD FTP 和标准 FTP 各抽出一个目录项来比较:
drwxrwsr-x         MudOS          Root    < DIR >  Oct 29 22:13  std/
drwxr-xr-x   3 ftpadmin ftpadmin     512 Jul  5 1999  usr

怎么样,很不相同吧?文件和目录都各有千秋,MUD FTP 的文件名不完整,目录
名后面的"/"竟成了多余的 :-( ,"< DIR >"也是多余的!


毛病找到了,开始着手解决吧!先查找这些错误的格式是在哪里生成的。查找什么
呢?想想:我们给 FTP 服务器发送了 ls 命令,那么 FTP 服务器必定有接收这
个命令的程序。这里需要一点 FTP 方面的知识,就是当我们对着服务器敲 ls 命
令时,我们的程序向服务器发送的实际是 nlst 命令,ls -l 实际就是 nlst -l
了,而敲 dir 命令实际发送的是 list 命令,所以,我们应该在 FTP 服务器的
源程序 /adm/daemons/ftpd.c 中查找字符串 "nlst" 或字符串 "list",找到接
收 ls 和 dir 命令的入口。
在 UltraEdit 编辑器里一找,就能找到如下的程序段:
……
     case "size": // return size of file
……
     case "nlst": // give name list of files in directory
           ^^^^对 ls 命令的处理就在这里了
        CHECK_LOGIN();
        /* Send name list */
        if ((i = sizeof(command)) > 1 && command[1] == "-l") {
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -l 参数就在这里了^^
          if (i == 2)
             command[1] = ".";
          else
             command = ({ command[0] }) + command[2..s-1];
          // and fall through to "list"
        } else {
          /* Used by commands like "dir", "mget", and "mput" */
          if ( i > 1 )
             tmp = get_path( fd, command[ 1 ] );
          else
             tmp = socket_info[ fd ][ CWD ];

          if ( check_valid_read( tmp, fd ) ) {
             tmp2 = ls( tmp, 1, fd );
             ^^^^^^^^^^^^^^^^^^^^^^^注意这一行,调用了 ls() 函数,没错,
             ^^^^^^ ls() 函数就是用来从磁盘读取目录列表的,跟进去看看吧
             if (tmp2 == "")
               socket_write( fd, "550 No files found.\n" );
             else
               data_conn( fd, tmp2, "ls", STRING );
          } else
             PERMISSION_DENIED550(command[ 1 ]);
          break;
        }
     case "list": // give list files in a directory
           ^^^^对 dir 命令的处理就在这里了
        CHECK_LOGIN();
        /* Send directory file list (like exec'ing "ls") */
        if ( sizeof( command ) > 1 )
          tmp = get_path( fd, command[ 1 ] );
        else
          tmp = socket_info[ fd ][ CWD ];
        if ( check_valid_read( tmp, fd ) ) {
          tmp2 = ls( tmp, 0, fd );
          ^^^^^^^^^^^^^^^^^^^^^^这一行也调用了 ls() 函数,与前面不同之处
          ^^^^^^^^^^^^ 是第二个参数,前面是 1 ,这里是 0 ,等会儿再研究它
          if (tmp2 == "")
             socket_write( fd, "550 No files found.\n");
          else
             data_conn( fd, tmp2, "ls", STRING );
        } else
          PERMISSION_DENIED550(command[ 1 ]);
        break;
     case "xpwd": // print the current working directory (deprecated)
……

看看 ls() 函数的内容:
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 += "/*";
   } // 这个判断的意思是说如果 ls 的目标是一个目录,就在它的尾巴
     // 加上个记号,确保它以 "/*" 结尾,在后面的程序中好跟文件区别

   /* begin narrow columnar "nlst" */
   if (column) { // 前面不是说了么,调用 ls() 函数的两个地方参数
                 // 不同,这里是处理参数为 1 的地方
     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";
   }

以下就是处理 ls() 的第二个参数为 0 的地方了,也就是要返回详细目录列表的地方。
   /* 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_OB->creator_file(path);
   if (!creator)  creator = ROOT_UID;
   domain = (string)MASTER_OB->domain_file(path);
   if (!domain)  domain = ROOT_UID;
上面这一小段程序涉及 creater 和 domain ,指的是文件的所有权问题

   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) { // 字节数为 -2 ,不是文件是目录
        files[i] = sprintf("drwxrwsr-x  %12s  %12s    < DIR >  %12s  %s/",
            creator, domain, tmp, (xfiles[i])[0]);
        !!^^^^^^^看看这一串目录格式定义,就是我们千辛万苦要找的东东了
     } else { // 除了目录就是文件了
        stats = stat(path + (xfiles[i])[0]);
        files[i] = sprintf("-rw%crw-r--  %12s  %12s  %8d  %12s  %s",
            stats[2] ? 'x' : '-', /* 'x' if loaded, else ' ' */
            creator, domain, j, tmp, (xfiles[i])[0]);
        !!^^^^^^^这一串文件格式定义也不合标准
     }
   }
   return sprintf( "%-#70s\n", implode( files, "\n" ) );
   ^^^^最后,把“自定义格式”的目录项传回调用它的地方,最终传回客户端
}
至此可以告一段落了,MUD FTP 服务器传回的目录项格式不正确的根源已经找到
了,将这些格式稍作修改,使之“基本”符合标准就OK了(说“基本”是因为
我没有翻过关于 FTP 协议一系列标准的典籍,自己改的是否真的符合标准我也
不清楚,:-) 另外,CuteFTP 喜欢发 list -a -l 命令,多了个 -a 参数,也
得考虑进去的喔!

有兴趣的你大可一试,有什么新发现请务必告诉我,先谢过了!


我修改过的那两段程序如下:
……
     case "size": // return size of file
……
     case "nlst": // give name list of files in directory
        CHECK_LOGIN();
        /* Send name list */
        if ((i = sizeof(command)) > 1 && command[1] == "-l") {
          if (i == 2)
             command[1] = ".";
          else
             command = ({ command[0] }) + command[2..s-1];
          // and fall through to "list"
        } else {
          /* Used by commands like "dir", "mget", and "mput" */
          if ( i > 1 )
             tmp = get_path( fd, command[ 1 ] );
          else
             tmp = socket_info[ fd ][ CWD ];

          if ( check_valid_read( tmp, fd ) ) {
             tmp2 = ls( tmp, 1, fd );
             if (tmp2 == "")
               socket_write( fd, "550 No files found.\n" );
             else
               data_conn( fd, tmp2, "ls", STRING );
          } else
             PERMISSION_DENIED550(command[ 1 ]);
          break;
        }
     case "list": // give list files in a directory
        CHECK_LOGIN();
        /* Send directory file list (like exec'ing "ls") */
        if ((i = sizeof(command)) > 1 &&
           (command[1] == "-L" || command[1] == "-a" &&
            command[2] == "-L")) {
           if (i == 2)
             command[1] = ".";
           else
             command = ({ command[0] }) + command[2..s-1];
        }
        if ( sizeof( command ) > 1 )
          tmp = get_path( fd, command[ 1 ] );
        else
          tmp = socket_info[ fd ][ CWD ];
        if ( check_valid_read( tmp, fd ) ) {
          tmp2 = ls( tmp, 0, fd );
          if (tmp2 == "")
             socket_write( fd, "550 No files found.\n");
          else
             data_conn( fd, tmp2, "ls", STRING );
        } else
          PERMISSION_DENIED550(command[ 1 ]);
        break;
     case "xpwd": // print the current working directory (deprecated)
……

修改后的 ls() 函数:
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_OB->creator_file(path);
   if (!creator)  creator = ROOT_UID;

   domain = (string)MASTER_OB->domain_file(path);
   if (!domain)  domain = ROOT_UID;

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

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

Well, are you clear ?

后记:
在西游记2000版中,西游记巫师组对 FTP 服务器程序作了一些修缮,不过因为
没有去掉 "< DIR >" 这个不合标准的东东,CuteFTP 还是不能识别它传回的目录。
把 "< DIR >" 换成目录的字节 "0" 就行了,整个 ftpd.c 就改这一个地方就可以
适应 CuteFTP 了,弄一个试试吧!

(完) 
 
 
尊重作者 转载请注明出处52mud.com

收藏 推荐 打印 | 录入:sbso | 阅读:
相关内容      
本文评论   [发表评论]   全部评论 (0)
内容推送
52mud提供
一起回忆泥巴游戏QQ群68186072
52mud官方微信公众平台
热门评论