beardeadclown
beardeadclown

Reputation: 377

IRC client does not print/receive full response

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <unistd.h>

static char *host = "irc.libera.chat";
static char *port = "6667";
static char *chan = "#libera";
static char *nick = "nick";
static char *pass = NULL;

static int sock  = 0;

void
message(char *fmt, ...) {
    va_list ap;
    /* determine size */
    va_start(ap, fmt);
    int n = vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);
    if (n < 0) {
        fputs("vsnprintf() failed", stderr);
        exit(EXIT_FAILURE);
    }
    size_t size = n + 1;
    /* construct */
    char *msg = malloc(size);
    if (msg == NULL) {
        perror("malloc() failed");
        exit(EXIT_FAILURE);
    }
    va_start(ap, fmt);
    n = vsnprintf(msg, size, fmt, ap);
    va_end(ap);
    if (n < 0) {
        fputs("vsnprintf() failed\n", stderr);
        free(msg);
        exit(EXIT_FAILURE);
    }
    /* send */
    ssize_t nsent = send(sock, msg, size, 0);
    free(msg);
    if (nsent == -1) {
        perror("send() failed");
        exit(EXIT_FAILURE);
    } else if ((size_t)nsent != size) {
        fprintf(stderr,
                "send() failed: expected to send %lu bytes, sent %ld instead\n",
                size, nsent);
        exit(EXIT_FAILURE);
    }
}

int
main(void) {
    /* initialize connection */
    struct addrinfo hints = {
        .ai_flags     = 0,
        .ai_family    = AF_UNSPEC,
        .ai_socktype  = SOCK_STREAM,
        .ai_protocol  = 0,
        .ai_addrlen   = 0,
        .ai_addr      = NULL,
        .ai_canonname = NULL,
        .ai_next      = NULL
    };
    struct addrinfo *res;
    int ret = getaddrinfo(host, port, &hints, &res);
    if (ret != 0) {
        fprintf(stderr, "getaddrinfo() failed: %s\n", gai_strerror(ret));
        return EXIT_FAILURE;
    }
    struct addrinfo *rp;
    for (rp = res; rp != NULL; rp = rp->ai_next) {
        sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sock == -1) {
            perror("socket() failed");
            continue;
        }
        if (connect(sock, rp->ai_addr, rp->ai_addrlen) == -1) {
            perror("connect() failed");
            close(sock);
            continue;
        }
        break;
    }
    freeaddrinfo(res);
    if (rp == NULL) {
        fprintf(stderr, "could not connect to %s:%s\n", host, port);
        return EXIT_FAILURE;
    }
    /* log in */
    if (pass)
        message("PASS %s\n", pass);
    message("NICK %s\n", nick);
    message("USER %s - - :%s\n", nick, nick);
    /* join channel */
    if (chan != NULL)
        message("JOIN %s\n", chan);
    /* print response */
    char buffer[4096];
    ssize_t nbyte;
loop:
    nbyte = recv(sock, buffer, 4095, 0);
    if (nbyte < 0) {
        fputs("recv() failed", stderr);
        return 1;
    } else if (nbyte == 0) {
        fputs("recv() failed: connection closed prematurely", stderr);
        return 1;
    }
    buffer[nbyte] = '\0';
    printf("%s", buffer);
    goto loop;
    /* unreachable */
}

outputs

:calcium.libera.chat NOTICE * :*** Checking Ident
:calcium.libera.chat NOTICE * :*** Looking up your hostname...
:calcium.libera.chat NOTICE * :*** Couldn't look up your hostname
:calcium.libera.chat NOTICE * :*** No Ident response
ERROR :Closing Link: 127.0.0.1 (Connection timed out)
recv() failed: connection closed prematurely

Other irc clients further output

:calcium.libera.chat 001 nick :Welcome to the Libera.Chat Internet Relay Chat Network nick
...

Could the issue be in error handling?

For example, according to send(2)

On success, these calls return the number of bytes sent. On error, -1 is returned, and errno is set to indicate the error.

so

} else if ((size_t)nsent != size) {
    fprintf(stderr,
            "send() failed: expected to send %lu bytes, sent %ld instead\n",
            size, nsent);
    exit(EXIT_FAILURE);
}

seems redundant, as well as its recv counterpart. Am I handling vsnprintf and malloc correctly?

Upvotes: 1

Views: 267

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 598134

You are handling vsnprintf() and malloc() fine. It is send() that you are not handling correctly. There are two problems with your usage:

  1. you are including the formatted string's null-terminator in the transmission. Don't do that, that is not part of the IRC protocol.

  2. you are not accounting for partial transmissions, as send() can return fewer bytes than requested, thus requiring send() to be called again to send any unsent bytes. So you need to call send() in a loop. A return value that is greater than 0 but less than the number of requested bytes is not an error condition. The only error condition is a return value that is less than 0.

Try this instead:

void
message(char *fmt, ...) {
    va_list ap;
    /* determine size */
    va_start(ap, fmt);
    int n = vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);
    if (n < 0) {
        fputs("vsnprintf() failed", stderr);
        exit(EXIT_FAILURE);
    }
    size_t size = n + 1;
    /* construct */
    char *msg = malloc(size);
    if (msg == NULL) {
        perror("malloc() failed");
        exit(EXIT_FAILURE);
    }
    va_start(ap, fmt);
    n = vsnprintf(msg, size, fmt, ap);
    va_end(ap);
    if (n < 0) {
        fputs("vsnprintf() failed\n", stderr);
        free(msg);
        exit(EXIT_FAILURE);
    }
    /* send */
    char *curr = msg;
    --size; // don't sent null terminator!
    while (size > 0) {
        ssize_t nsent = send(sock, curr, size, 0);
        if (nsent < 0) {
            perror("send() failed");
            free(msg);
            exit(EXIT_FAILURE);
        }
        curr += nsent;
        size -= nsent;
    }
    free(msg);
}

That said, you really shouldn't be using a goto loop in main(), either. Use a while or do..while loop instead, eg:

int
main(void) {
    ...
    /* print response */
    char buffer[4096];
    int exitCode = 0;

    do {
        ssize_t nbyte = recv(sock, buffer, sizeof buffer, 0);
        if (nbyte < 0) { 
            perror("recv() failed");
            exitCode = 1;
        } else if (nbyte == 0) {
            fputs("connection closed by peer", stderr);
            exitCode = 1;
        } else {
            printf("%.*s", nbyte, buffer);
        }
    }
    while (exitCode == 0);

    close(sock);
    return exitCode;
}

Upvotes: 1

amo-ej1
amo-ej1

Reputation: 3307

When you trace the application (E.g. using strace) you will see the following calls:

connect(3, {sa_family=AF_INET, sin_port=htons(6667), sin_addr=inet_addr("172.106.11.86")}, 16) = 0
sendto(3, "NICK nick\n\0", 11, 0, NULL, 0) = 11
sendto(3, "USER nick - - :nick\n\0", 21, 0, NULL, 0) = 21
sendto(3, "JOIN #libera\n\0", 14, 0, NULL, 0) = 14

Meaning when sending the NICK, USER and JOIN, those strings are begin transmitted with an additional null byte at the end and the server on the other side doesn't like that.

This implies that in your code the message() method is wrong, more specifically the calculation of the size variable. If I compile your code with size decremented before the send() call, the connection to the irc server succeeds.

Upvotes: 2

Related Questions