【问题标题】:Netty: How to implement a telnet client handler which needs authenticationNetty:如何实现需要身份验证的 telnet 客户端处理程序
【发布时间】:2016-06-03 16:04:37
【问题描述】:

这是我第一次通过这个平台提问。我很抱歉。我英语不好。我会尽力让你理解我的问题。 我完全是 Netty 的初学者。我想实现一个程序来向 telnet 服务器发送命令并接收响应消息。我修改了示例 telnet 程序以在没有服务身份验证时连接并从服务获取响应。 问题是 在服务器中设置身份验证过程时。 (需要登录名和密码) 如何实现客户端程序? 如何接收服务登录请求并响应? 我应该实现另一个处理程序来处理身份验证吗?

下面显示了我如何将命令发送到服务器

EventLoopGroup group = new NioEventLoopGroup();
    try {
        Bootstrap b = new Bootstrap();
        b.group(group)
                .channel(NioSocketChannel.class)
                .handler(new TelnetClientInitializer(sslCtx));

        // Start the connection attempt.
        ChannelFuture lastWriteFuture = null;
        lastWriteFuture = b.connect(HOST, PORT).sync();

        Channel ch = lastWriteFuture.channel();

        lastWriteFuture = ch.writeAndFlush("ls" + "\r\n", ch.newPromise());

        lastWriteFuture = ch.writeAndFlush("status" + "\r\n");

        lastWriteFuture = ch.writeAndFlush("ls" + "\r\n");

        lastWriteFuture = ch.writeAndFlush("exit" + "\r\n");

        // Wait until the connection is closed.
        lastWriteFuture.channel().closeFuture().sync();

    } finally {
        // Shut down the event loop to terminate all threads.
        group.shutdownGracefully();
    }

但是在发送上述命令登录服务之前我应该​​做什么?

The following picture shows what i want to do in the program

非常感谢!!!

【问题讨论】:

    标签: authentication netty handler telnet


    【解决方案1】:

    Telnet 没有密码包的真正概念,密码提示就像任何正常的文本输出一样。这意味着您可以在连接时将用户名和密码作为单独的行发送,并且telnet服务器将正确使用它们。

    ch.writeAndFlush("administrator" + "\r\n");
    ch.writeAndFlush("LetMeIn4!!" + "\r\n");
    

    如果您需要连接到并不总是需要密码的服务器,那么您应该读取服务器的输出,检查它是否包含“用户名”,发送用户名,然后继续阅读它是否包含“密码”和发送密码。这很容易被破坏,因为服务器不需要发送这些字符串,合法的输出也可能包含这些。这是 telnet 协议的缺点。

    【讨论】:

    • 首先感谢您的帮助!!!我试过了。它不起作用。几分钟后服务器没有响应,频道未注册
    • 你把代码放在哪里了?它应该介于 Channel ch = lastWriteFuture.channel();lastWriteFuture = ch.writeAndFlush("ls" + "\r\n", ch.newPromise()); 之间
    • 是的。我对你的描述做了同样的事情。在我的程序中,处理程序和 telnetClientInitializer 没有变化。它们与示例相同。
    【解决方案2】:

    如果我们将 TELNET 作为一种协议来讨论,您应该知道 Netty 示例中的 Telnet 客户端不支持 TELNET 协议。他的名字令人困惑,您无法连接到任何标准的 telnet 服务器。您可以在此处阅读有关 TELNET 协议的更多信息 - THE TELNET PROTOCOL

    我看到了两种方式:

    1. 在 Netty 上编写 TELNET 实现
    2. 为示例 Apache Commons Net 使用另一种实现

    第一种方式的示例 - modified netty client,我在 Linux 服务器上测试了他。他有几个肮脏的技巧,比如计时器,但他工作。

    第二个例子 - Java - Writing An Automated Telnet Client:

    import org.apache.commons.net.telnet.*;
    
    import java.io.InputStream;
    import java.io.PrintStream;
    
    public class AutomatedTelnetClient {
        private TelnetClient telnet = new TelnetClient();
        private InputStream in;
        private PrintStream out;
        private String prompt = "~>";
    
        public AutomatedTelnetClient(String server) {
            try {
                // Connect to the specified server
                telnet.connect(server, 8023);
                TerminalTypeOptionHandler ttopt = new TerminalTypeOptionHandler("VT100", false, false, true, false);
                EchoOptionHandler echoopt = new EchoOptionHandler(true, false, true, false);
                SuppressGAOptionHandler gaopt = new SuppressGAOptionHandler(true, true, true, true);
                try {
                    telnet.addOptionHandler(ttopt);
                    telnet.addOptionHandler(echoopt);
                    telnet.addOptionHandler(gaopt);
                } catch (InvalidTelnetOptionException e) {
                    System.err.println("Error registering option handlers: " + e.getMessage());
                }
                // Get input and output stream references
                in = telnet.getInputStream();
                out = new PrintStream(telnet.getOutputStream());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    //    public void su(String password) {
    //        try {
    //            write(“su”);
    //            readUntil(“Password: “);
    //            write(password);
    //            prompt = “#”;
    //            readUntil(prompt + ” “);
    //        } catch (Exception e) {
    //            e.printStackTrace();
    //        }
    //    }
    
        public String readUntil(String pattern) {
            try {
                char lastChar = pattern.charAt(pattern.length() - 1);
                StringBuffer sb = new StringBuffer();
                boolean found = false;
                char ch = (char) in.read();
                while (true) {
                    System.out.print(ch);
                    sb.append(ch);
                    if (ch == lastChar) {
                        if (sb.toString().endsWith(pattern)) {
                            return sb.toString();
                        }
                    }
                    ch = (char) in.read();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public void write(String value) {
            try {
                out.println(value);
                out.flush();
                System.out.println(value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public String sendCommand(String command) {
            try {
                write(command);
                return readUntil(prompt + " ");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public void disconnect() {
            try {
                telnet.disconnect();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            String user = "test";
            String password = "test";
            AutomatedTelnetClient telnet = new AutomatedTelnetClient("localhost");
            // Log the user on
            telnet.readUntil("login:");
            telnet.write(user);
            telnet.readUntil("Password:");
            telnet.write(password);
            // Advance to a prompt
            telnet.readUntil(telnet.prompt + " ");
    
            telnet.sendCommand("ps -ef");
            telnet.sendCommand("ls");
            telnet.sendCommand("w");
            telnet.disconnect();
        }
    }
    

    【讨论】:

      【解决方案3】:

      希望我的这篇文章对某人有所帮助。
      Netty | Implement Telnet Automated Authentication

      在开发空间地面站软件时,我不得不使用 Telnet 来控制子设备。除了身份验证之外,Telnet 与常规 TCP 服务器通信非常相似。因此,我实现了一个自动处理 Telnet 身份验证以与 Telnet 服务器通信的 Handler。连接 Telnet 服务器时,依次显示以下介绍性消息“用户名:”、“密码:”消息,并请求用户验证。 Handler 自动处理身份验证过程,就像人类输入帐户信息一样。下面是实现的简要说明。

      c:\> telnet 192.168.0.1 12345
      
      Power On Self Test (POST) Passed.
      Integrated Control Unit (ICU) Build xxx (Build:xxxxxx) - Feb  7 2022, 17:57:16 (Network/TCP)
      Date and Time: 2022-02-16 20:01:19 (GMT)
      MAC Address  : [00:xx:xx:xx:C6:8F]
      
      Username: User
      Password: 1234
      
      > 
      

      处理程序

      TelnetAuthenticator Handler 的工作原理如下。

      1. 如果消息包含字符串“用户名:”,则发送用户名。
      2. 如果消息中包含字符串“Password:”,则发送密码。
      3. 如果消息包含等待输入的字符串“>”,则从管道中删除身份验证处理程序。认证后不需要 TelnetAuthenticator Handler。

      如果账号未在Telnet服务器上注册或密码不匹配,则重复接收字符串“用户名:”或“密码:”。身份验证失败错误不可恢复,通知用户身份验证过程失败并强制他们断开连接。

      @Slf4j
      @RequiredArgsConstructor
      public class TelnetAuthenticator extends SimpleChannelInboundHandler<String> {
          private final ChannelSpec channelSpec;
          private boolean alreadyUserTried = false;
          private boolean alreadyPasswordTried = false;
      
          @Override
          protected void channelRead0(ChannelHandlerContext ctx, String msg) {
              // If the message contains the string “Username: “, send the username.
              if (msg.contains(channelSpec.getReqUserTag())) {
                  if (alreadyUserTried) {
                      processFail(ctx);
                  }
                  ctx.channel().writeAndFlush(channelSpec.getAccount().getUser() + channelSpec.getEndLine());
                  alreadyUserTried = true;
                  return;
              }
      
              // If the message contains the string “Password: “, the password is sent.
              if (msg.contains(channelSpec.getReqPasswordTag())) {
                  if (alreadyPasswordTried) {
                      processFail(ctx);
                  }
                  ctx.channel().writeAndFlush(channelSpec.getAccount().getPassword() + channelSpec.getEndLine());
                  alreadyPasswordTried = true;
                  return;
              }
      
              // If the incoming message contains an input waiting message, the Pipeline deletes the current handler.
              if (msg.contains(channelSpec.getStandByTag())) {
                  ctx.pipeline().remove(this.getClass());
              }
          }
      
          private void processFail(ChannelHandlerContext ctx) {
              ctx.fireUserEventTriggered(ErrorMessage.AUTHENTICATE_FAIL);
              ctx.close();
          }
      }
      

      初始化 ChannelPipeline

      带有 TelnetAuthenticator Handler 的 ChannelPipeline 配置可以是:首先,如下注册 InboundHandlers。

      1. 首先,添加带有“用户名:”、“密码:”、“>”字符串的 DelimiterBasedFrameDecoder 作为分隔符。 stripDelimiter 选项设置为 false,因为必须接收所有分隔符才能识别身份验证过程。
      2. 添加字符串解码器。
      3. 添加实现的 TelnetAuthenticator Handler。
      4. 添加其他必要的业务逻辑。

      只需将 StringEncoder 添加到 Outbound。您可以根据需要添加其他 Handler。

      public class PipelineInitializer extends ChannelInitializer<SocketChannel> {
          private ChannelSpec channelSpec;
      
          public void init(ChannelSpec channelSpec) {
              this.channelSpec = channelSpec;
          }
      
          @Override
          protected void initChannel(SocketChannel ch) throws Exception {
              ch.pipeline()
                      // Inbound
                      .addLast(new DelimiterBasedFrameDecoder(1024, false,
                              channelSpec.getDelimiter().reqUserTag(),
                              channelSpec.getDelimiter().reqPasswordTag(),
                              channelSpec.getDelimiter().standByTag()))
                      .addLast(new StringDecoder())
                      .addLast(new TelnetAuthenticator(channelSpec))
                      .addLast(new BusinessLogic())
      
                      // Outbound
                      .addLast(new StringEncoder());
          }
      }
      

      频道规格

      ChannelSpec 定义了与 Telnet 服务器通信所需的规范。管理服务器IP、端口、账号信息、分隔符等。

      @Getter
      public class ChannelSpec {
          private final String serverIp = "192.168.0.1";
          private final int serverPort = 12345;
          private final String endLine = "\r\n";
          private final String standByTag = ">";
          private final String reqUserTag = "Username: ";
          private final String reqPasswordTag = "Password: ";
          private final Account account = new Account("User", "1234");
          private final Delimiter delimiter = new Delimiter();
      
          public class Delimiter {
              public ByteBuf standByTag() {
                  return toByteBuf(standByTag);
              }
      
              public ByteBuf reqUserTag() {
                  return toByteBuf(reqUserTag);
              }
      
              public ByteBuf reqPasswordTag() {
                  return toByteBuf(reqPasswordTag);
              }
      
              private ByteBuf toByteBuf(String input) {
                  ByteBuf delimiterBuf = Unpooled.buffer();
                  delimiterBuf.writeCharSequence(input, StandardCharsets.UTF_8);
                  return delimiterBuf;
              }
          }
      }
      
      @RequiredArgsConstructor
      @Getter
      public class Account {
          private final String user;
          private final String password;
      }
      

      【讨论】:

      • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-13
      相关资源
      最近更新 更多