Netty | Implement Telnet Automated Authentication

주싱·2022년 2월 23일
0

Network Programming

목록 보기
7/21

Get started

I had to use Telnet to control the sub-equipment while developing the space ground station software. Except for the authentication, Telnet is quite similar to regular TCP server communication. So, I implemented a Handler that automatically handles Telnet authentication to communicate with the Telnet server. When connecting to the Telnet server, the following introductory message, “Username: “, “Passwrod: “ messages are displayed in sequence, and user authentication is requested. Handler automatically handles the authentication process as if a human would input account information. Below is a brief description of the implementation.

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

> 

Handler

TelnetAuthenticator Handler simply works as follows.

  1. If the message contains the string “Username: “, send the username.
  2. If the message contains the string “Password: “, the password is sent.
  3. If the message contains the string “>” waiting for input, delete the authentication handler from the Pipeline. After authentication, TelnetAuthenticator Handler is unnecessary.

If the account is not registered on the Telnet server or the password does not match, the string “Username: “ or “Password: “ is repeatedly received. The authentication failure error is unrecoverable, notifying the user of a failed authentication process and forcing them to disconnect.

@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();
    }
}

Initialize ChannelPipeline

A ChannelPipeline configuration with a TelnetAuthenticator Handler can be: First, register InboundHandlers as follows.

  1. First, add DelimiterBasedFrameDecoder with “Username: “, “Password: “, “>” strings as delimiters. The stripDelimiter option is set to false because all delimiters must be received to recognize the authentication process.
  2. Add StringDecoder.
  3. Add the implemented TelnetAuthenticator Handler.
  4. Add other necessary business logic.

Simply add StringEncoder to Outbound. You can add other Handlers as needed.

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

ChannelSpec defines specifications required for communication with Telnet server. Manage server IP, port, account information, separator, etc.

@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;
}
profile
소프트웨어 엔지니어, 일상

0개의 댓글