dontHaveName dontHaveName - 22 days ago 4
C Question

c custom netcat for simple webserver

I'm trying to make copy of netcat for webserver purposes using c and bash. This is my code in bash:

#!/bin/bash

rm -f out
mkfifo out
trap "rm -f out" EXIT

cat out | nc -l 1500 > >(
while read line
do
echo $line
# parse http request from line
# some other things that doesnt matter

printf "HTTP/1.1 200 OK\nContent-Type:text/html\n\ntest" > out
done


)


When I visit localhost:1500 it prints "test". I want to replace netcat(nc) with my own.

cat out | customnc -l 1500 > >(
#
)


This is customnc.c

#include<netinet/in.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>

int main() {
int create_socket, new_socket;
socklen_t addrlen;
int bufsize = 1024;
char *buffer = malloc(bufsize);
struct sockaddr_in address;

if ((create_socket = socket(AF_INET, SOCK_STREAM, 0)) > 0){
printf("The socket was created\n");
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(1600);

if (bind(create_socket, (struct sockaddr *) &address, sizeof(address)) == 0){
printf("Binding Socket\n");
}


if (listen(create_socket, 10) < 0) {
perror("server: listen");
exit(1);
}

if ((new_socket = accept(create_socket, (struct sockaddr *) &address, &addrlen)) < 0) {
perror("server: accept");
exit(1);
}

if (new_socket > 0){
printf("The Client is connected...\n");
}

recv(new_socket, buffer, bufsize, 0);
printf("%s", buffer);
close(new_socket);

close(create_socket);
return 0;
}


What's wrong with my code? When I hit localhost:1600 (customnc) it just throws an error Unable to connect.

The problem is with sending data from bash. If I send response in customnc.c using
write
, browser will display the message, but I have to send it from bash.


Edit: The real problem is that I want to parse http request in bash and send file using bash to c. So it goes like this:
1. Bash starts c server
2. User opens localhost in browser
3. C will get request and sent it back to bash
4. Bash will parse request and send response (text) to c
5. C will receive response and send it through socket to browser
6. C will close socket

I'm trying to implement this but I'm not able to make this bidirectional communication. Can I do it with named pipes (2)?

Tim Tim
Answer

You are closing the socket before writing anything to it. You need to read the data from stdin and write it to the socket before closing. Try adding this just before your call to close(new_socket);:

char c;
while ((c = getchar()) != EOF) {
    write(new_socket, &c, 1);
}

Here's my full code, I also added more error checks, removed the printf calls so that they don't get parsed by the bash script, removed the unnecessary call to malloc, and set SO_REUSEADDR on the socket so it can be reused quickly (see this answer for more info).

#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define BUFSIZE 1024
int main()
{
    int create_socket, new_socket;
    socklen_t addrlen;
    char buffer[BUFSIZE];
    struct sockaddr_in address;
    char c;
    int true = 1;

    if ((create_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("server: socket");
        exit(1);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(1600);

    setsockopt(create_socket,SOL_SOCKET,SO_REUSEADDR,&true,sizeof(int));

    if (bind(create_socket, (struct sockaddr *)&address, sizeof(address)) < 0)
    {
        perror("server: bind");
        exit(1);
    }
    if (listen(create_socket, 10) < 0)
    {
        perror("server: listen");
        exit(1);
    }
    if ((new_socket = accept(create_socket, (struct sockaddr *)&address, &addrlen)) < 0)
    {
        perror("server: accept");
        exit(1);
    }
    if (recv(new_socket, buffer, BUFSIZE, 0) < 0)
    {
        perror("server: recv");
        exit(1);
    }
    printf("%s", buffer);
    fflush(stdout);

    while ((c = getchar()) != EOF)
    {
        if (write(new_socket, &c, 1) < 0)
        {
            perror("server: write");
            exit(1);
        }
    }
    close(new_socket);
    close(create_socket);
    return 0;
}

This works with the following bash script. Note that I added a check for a blank line, which indicates the end of the GET request.

#!/bin/bash
rm -f out
mkfifo out
trap "rm -f out" EXIT

cat out | ./a.out > >(
    while read line
    do
       # strip carriage return
       line=$(echo $line | tr -d '\r')
       echo $line
       if [[ $line == "" ]]; then
           # reached end of GET request
           break
       fi
    done

    printf "HTTP/1.1 200 OK\nContent-Type:text/html\n\ntest" > out
)

If you want to fully replace the nc command, you'll have to do some argument parsing to get the port number (-l) instead of hard coding it to 1600.

Comments