0

My goal is to use the C/C++ readline library on the telnet server-side as it delivers all necessary terminal functionality out of the box. Readline is already connected to the telnet socket, read and write to the socket works fine. I'm using libtelnet by Sean Middleditch.

The problem is that the readline outputs '\n' instead of '\n\r' which is a telnet standard to go to a new line = New Line + Carriage Return.

Current output on the client-side:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
> example
         > another example
                          > 3rd example
                                       > 

Expected output:

Trying 127.0.0.1...
Connected to localhost.
The escape character is '^]'.
> example
> another example
> 3rd example
> 

I read documentation trying to find a simple variable with a default newline character, but there's no such thing, or I must've missed it. There's also a chance that this behavior might be changed with the telnet terminal type - I not this far yet.

Do you have any idea on how to change this '\n' to '\r\n'?

Edit:

Telnet initiated & Readline connected to socket:

void startLibTelnet(int connfd)
{
    char buffer[512];
    int rs;

    telnet_t *telnet;
        telnet = telnet_init(telopts, _event_handler, 0, &connfd);

    // telnet options
    telnet_negotiate(telnet, TELNET_DO,
            TELNET_TELOPT_LINEMODE);

    telnet_negotiate(telnet, TELNET_WILL,
            TELNET_TELOPT_ECHO);

    telnet_negotiate(telnet, TELNET_WILL,
            TELNET_TELOPT_TTYPE);

    // redirecting readline input/output to TCP socket
    rl_instream = fdopen(connfd, "r");
    rl_outstream = fdopen(connfd, "w");

again:
    // forever loop
    while ( (rs = recv(connfd, buffer, sizeof(buffer), 0)) > 0)
    {
        telnet_recv(telnet, buffer, rs);
    }
    if (rs  < 0 && errno == EINTR)
        goto again;
    else if (rs  < 0)
        perror("str_echo: read error");
}

Part of the event handler:

static void _event_handler(telnet_t *telnet, telnet_event_t *ev,
                void *user_data)
{
    int sock = *(int*)user_data;

    switch (ev->type) {
    /* data received */
    case TELNET_EV_DATA:
        rl_gets("> ");
        break;
    /* data must be sent */
    case TELNET_EV_SEND:
        _send(sock, ev->data.buffer, ev->data.size);
        break;
    /* request to enable remote feature (or receipt) */
    case TELNET_EV_WILL:
        break;
    /* notification of disabling remote feature (or receipt) */
    case TELNET_EV_WONT:
        break;
    . // here's also 
    . // case DO
    . // case DONT
    default:
        break;
    }
}

I think I should leave the readline module as is. The best solution is probably telling the client side to interpret '\n' or Line Feed as '\n\r' or Line Feed and Carriege Return. I'm having problems finding this kind of telnet option.

Antoni
  • 176
  • 10
  • `\r\n` is not a standard line break. The C++ standard only knows about `\n`. How are you outputting the lines? On platforms that use such line breaks (ie, Windows), `cin` normalizes `\r\n` into `\n` on input, and `cout` converts `\n` to `\r\n` on output. Please provide a [mcve] – Remy Lebeau Mar 11 '21 at 18:05
  • 1
    The open mode of a stream will determine whether newlines are translated or not. A stream in `binary` mode will treat `\r\n` as two characters. When opened as `text` mode, the `\r\n` will be treated as a *newline*; the `\r` usually ignored (as whitespace). – Thomas Matthews Mar 11 '21 at 18:26
  • @RemyLebeau The question is updated. I think I need a telnet option to tell the client side to interpret '\n' as '\n\r', am I right? – Antoni Mar 12 '21 at 09:59
  • I think libtelnet is responsible for making the C-standard output of readline compliant with RFC854 - you just need to instruct it to do this. – Useless Mar 12 '21 at 10:10

1 Answers1

1

It's not readline that should be changing its behaviour IMO, but libtelnet that should be performing the appropriate translation.

The documentation for libtelnet mentions

  • void telnet_send_text(telnet_t *telnet, const char *buffer, size_t size);

Sends text characters with translation of C newlines (\n) into CR LF and C carriage returns (\r) into CR NUL, as required by RFC854, unless transmission in BINARY mode has been negotiated.

so it should be able to handle this. You'll have to show how readline is talking to libtelnet if you need more detailed help.


Edit - it looks like you're using libtelnet to handle input from the telnet client, but allowing readline to put characters directly on the socket back to the client. This is a mistake. Per the libtelnet documentation:

Note: it is very important that ALL data sent to the remote end of the connection be passed through libtelnet. All user input or process output that you wish to send over the wire should be given to one of the following functions. Do NOT send or buffer unprocessed output data directly!

That is, you need

client <-> libtelnet <-> readline

not

client  -> libtelnet -> readline
   |                       /
    ----------<----------- 

which I think is what you're doing now. I can't tell for certain though, since I don't know how conn and sock are set up outside the code you've shown.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • Thank for this answer! Readline talks not exactly with telnet, but directly with TCP socket. It actually omits telnet. Take a look at my edited question, there's function call ```rl_gets()``` in ```_event_handler()```. I'm not sure how could I pass output from readline to telnet. I'm thinking about using a pipe between readline output and read from it in the forever loop, than send data with telnet_send_text you mentioned. – Antoni Mar 12 '21 at 13:25
  • 1
    Readline is writing to a socket. It should be writing to a socket _you_ control, so you can pass it back through libtelnet on the way to the client. – Useless Mar 12 '21 at 16:39
  • That's exactly what I'm trying to achieve right now, good point. However nothing I tried works. Tried pipes, memfd, sockets to communicate libelnet and readline like below `libtelnet <-> readline`, can't make it work. Do you have any suggestions on how to exchange data between these two? – Antoni Mar 15 '21 at 14:01
  • Pardon my ignorance. I didn't try UNIX sockets, that's probably the solution. Thank again @Useless, not so useless :) – Antoni Mar 15 '21 at 14:26
  • You could use `socketpair()` to set up a bidirectional link (`<->`), or you could use one `pipe()` for `libtelnet -> readline` and another `pipe()` for the reverse path. A pair of sockets is more straightforward in this case. – Useless Mar 15 '21 at 21:56
  • Pipes seemed to be the easier way, but even with non blocking reads readline freezes up. Well, I hope I'll find a way to make this work. – Antoni Mar 15 '21 at 22:40
  • My next guess is `rl_callback_read_char()` for non-blocking readline reads. I'll try this tomorrow. – Antoni Mar 15 '21 at 22:42