Client-Server-Assignment / server.c
server.c
Raw
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <netdb.h>
#include <unistd.h>
#include <stdbool.h>
#include <signal.h>

struct Client {
    char* name;
    struct Client* nextItem;
    FILE* to;
    FILE* from;
    int say;
    int kick;
    int list;
};

typedef struct {
    int num;
    sem_t* lock;
    struct Client* firstItem;
    struct Client* lastItem;
    int auth;
    int name;
    int say;
    int kick;
    int list;
    int leave;
} SafeClients;

typedef struct {
    SafeClients* clientList;
    char* auth;
    int fd;
} ThreadInfo;

//Opens a listening port on a given port
//If the given port is zero, open a ephemeral port
int open_listen(const char* port){
    struct addrinfo* ai = 0;
    struct addrinfo hints;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET; //IPv4
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; //Listens on all IP addresses

    int err;
    if ((err = getaddrinfo(NULL, port, &hints, &ai))) {
        freeaddrinfo(ai);
        fprintf(stderr, "Communications error\n");
        exit(2);
    }

    int fd = socket(AF_INET, SOCK_STREAM, 0);

    //Open Listen
    int optVal = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optVal, sizeof(int));
    bind(fd, (struct sockaddr*)ai->ai_addr, sizeof(struct sockaddr));
    listen(fd, 10);
    
    //Find port
    struct sockaddr_in ad;
    memset(&ad, 0, sizeof(struct sockaddr_in));
    socklen_t len = sizeof(struct sockaddr_in);
    getsockname(fd, (struct sockaddr*)&ad, &len);

    //Print socket
    fprintf(stderr, "%u\n", ntohs(ad.sin_port));

    //fd is now connected, return fd
    return fd;
}

//Checks the auth code with the client
//Returns true if passed, false if not
bool auth_check(FILE* to, FILE* from, char* auth){
    char buffer[1024];
    char command[60];
    char authCode[800];
    fprintf(to, "AUTH:\n");
    fflush(to);
    while (fgets(buffer, 1024, from)) {
        sscanf(buffer, "%[^:]:%[^\n]", command, authCode);
        if (!strcmp(command, "AUTH")) {
            if (!strcmp(authCode, auth)) {
                //Auth passed
                return true;
            } else {
                //Auth failed
                return false;
            }
        }

    }
    //Error or closed pipe?
    return false;
}

//Removes a client from the threadsafe client list
//Frees the memory related to that client
void remove_client(SafeClients* clientList, struct Client* client){
    if (clientList->num == 1) {
        //No more clients in the list
        clientList->firstItem = NULL;
        clientList->lastItem = NULL;
    } else {
        //Loop through the clients to remove the client
        struct Client* clientCurr = clientList->firstItem;
        struct Client* clientPrev;
        while (1) {
            if (client == clientCurr) {
                //New client name is more than the current client
                //Store it in front of the current client
                if (clientList->firstItem == clientCurr) {
                    //First item
                    clientList->firstItem = clientCurr->nextItem;
                } else {
                    //Somewhere in the middle
                    clientPrev->nextItem = clientCurr->nextItem;
                }
                break;
            } else if (clientCurr->nextItem == NULL) {
                //Last item
                clientList->lastItem = clientPrev;
                clientPrev->nextItem = NULL;
                break;
            }
            //Otherwise, advance the clients
            clientPrev = clientCurr;
            clientCurr = clientCurr->nextItem;
        }
    }
    fclose(client->to);
    fclose(client->from);
    free(client->name);
    free(client);
    clientList->num--;

}

//Make changes to the threadsafe linked list to insert a new client in
void sort_list(SafeClients* clientList, struct Client* newClient){
    struct Client* clientCurr = clientList->firstItem;
    struct Client* clientPrev;
    while (clientCurr != NULL) {
        if (strcmp(newClient->name, clientCurr->name) < 0) {
            //New client name is more than the current client
            //Store it in front of the current client
            if (clientList->firstItem == clientCurr) {
                //First item
                clientList->firstItem = newClient;
                newClient->nextItem = clientCurr;
            } else {
                //Somewhere in the middle
                clientPrev->nextItem = newClient;
                newClient->nextItem = clientCurr;
            }
            break;
        } else if (clientCurr->nextItem == NULL) {
            //Must insert after the last item
            clientList->lastItem = newClient;
            clientCurr->nextItem = newClient;
            newClient->nextItem = NULL;
            break;
        }
        //Otherwise, advance the clients
        clientPrev = clientCurr;
        clientCurr = clientCurr->nextItem;
    }
}

//Adds a new client to the threadsafe client list in lexographical order
struct Client* addsort_new_client(char* name, SafeClients* clientList, 
        FILE* to, FILE* from){
    struct Client* newClient = malloc(sizeof(struct Client));
    newClient->name = malloc(sizeof(name));
    strcpy(newClient->name, name);
    newClient->nextItem = NULL;
    newClient->to = to;
    newClient->from = from;
    newClient->say = 0;
    newClient->kick = 0;
    newClient->list = 0;
    if (clientList->num == 0) {
        //This is the first client we are adding
        clientList->firstItem = newClient;
        clientList->lastItem = NULL;
        newClient->nextItem = NULL;
    } else {
        sort_list(clientList, newClient);
    }
    //Message the client ok
    fprintf(to, "OK:\n");
    fflush(to);
    clientList->num++;
    //Message all clients
    struct Client* currClient = clientList->firstItem;
    while (currClient != NULL) {
        fprintf(currClient->to, "ENTER:%s\n", name);
        fflush(currClient->to);
        currClient = currClient->nextItem;
    }
    return newClient;

}

//Performs name negotiation with the client
struct Client* negotiate_name(FILE* to, FILE* from, SafeClients* clientList){
    //Lock the client list while this is happening
    struct Client* returnClient;
    char buffer[1024];
    char command[30];
    char name[200];
    bool named = false;
    while (!named) {
        fprintf(to, "WHO:\n");
        fflush(to);
        while (fgets(buffer, 1024, from)) {
            sscanf(buffer, "%[^:]:%[^\n]", command, name);
            sem_wait(clientList->lock);
            clientList->name++;
            if (!strcmp(command, "NAME")) {
              bool exists = false;
              if (!strcmp(name, "")) { //If the name is empty, send name taken
                  fprintf(to, "NAME_TAKEN:\n");
                  fflush(to);
                  break;
              }
              struct Client* currClient = clientList->firstItem;
              while(currClient != NULL) { //Check if name exists
                 if (!strcmp(currClient->name, name)) {
                    exists = true;
                    break;
                 }
                currClient = currClient->nextItem;
              }
              if (!exists) {
                  //Name doesnt exist, add client
                  returnClient = addsort_new_client(name, clientList, to, 
                          from);
                  printf("(%s has entered the chat)\n", name);
                  fflush(stdout);
                  named = true;
              } else {
                  //Name is taken, send name taken and loop again
                  fprintf(to, "NAME_TAKEN:\n");
                  fflush(to);
              }
              sem_post(clientList->lock);
              break;
            }
        }
        if (!strcmp(buffer, "")) {
            break;
        }
    }
    return returnClient;
}

//Sends a list of currently connected clients to a client that requests it
void client_list(SafeClients* clientList, FILE* to, 
        struct Client* threadedClient){
    sem_wait(clientList->lock);
    fprintf(to, "LIST:");
    fflush(to);
    struct Client* currClient = clientList->firstItem;
    while (currClient != NULL) {
        if (currClient->nextItem != NULL) {
            fprintf(to, "%s,", currClient->name);
        } else {
            fprintf(to, "%s\n", currClient->name);
        }
        fflush(to);
        currClient = currClient->nextItem;
    }
    threadedClient->list++;
    clientList->list++;
    sem_post(clientList->lock);
}

//Sends a message to all clients that a client is leaving and removes the
//client
void client_leave(SafeClients* clientList, struct Client* threadedClient){
    sem_wait(clientList->lock);
    struct Client* currClient = clientList->firstItem;
    while (currClient != NULL) {
        if (currClient != threadedClient) {
            fprintf(currClient->to, "LEAVE:%s\n", threadedClient->name);
            fflush(currClient->to);
        }
        currClient = currClient->nextItem;
    }
    printf("(%s has left the chat)\n", threadedClient->name);
    fflush(stdout);
    remove_client(clientList, threadedClient);
    sem_post(clientList->lock);
}

//Sends a kick message to a client based on the name given by another
//client
void client_kick(SafeClients* clientList, char* arg1, 
        struct Client* threadedClient){
    sem_wait(clientList->lock);
    struct Client* currClient = clientList->firstItem;
    while (currClient != NULL) {
        if (!strcmp(currClient->name, arg1)) {
            fprintf(currClient->to, "KICK:\n");
            fflush(currClient->to);
            break;
        }
        currClient = currClient->nextItem;
    }
    threadedClient->kick++;
    clientList->kick++;
    sem_post(clientList->lock);
    client_leave(clientList, currClient);
}

//Sends a message from one client to all clients
void client_say(SafeClients* clientList, char* arg1, 
        struct Client* threadedClient){
    //Format message and turn any non-printable characters to ?
    int i = 0;
    char c = arg1[i];
    while (c != '\0') {
        if (c < 32) {
            arg1[i] = '?';
        }
        i++;
        c = arg1[i];
    }
    //Send a message to all clients
    sem_wait(clientList->lock);
    struct Client* currClient = clientList->firstItem;
    while (currClient != NULL) {
        fprintf(currClient->to, "MSG:%s:%s\n", threadedClient->name, arg1);
        fflush(currClient->to);
        currClient = currClient->nextItem;
    }
    printf("%s: %s\n", threadedClient->name, arg1);
    fflush(stdout);
    threadedClient->say++;
    clientList->say++;
    sem_post(clientList->lock);
    usleep(100000);
}

//Increments the counter for the auth statistic
void increment_auth(SafeClients* clientList){
    sem_wait(clientList->lock);
    clientList->auth++;
    sem_post(clientList->lock);
}

//Increments the counter for the leave statistic
void increment_leave(SafeClients* clientList){
    sem_wait(clientList->lock);
    clientList->leave++;
    sem_post(clientList->lock);
}

//The thread that handles clients
//Performs auth and name procedures and then listens for messages from the 
//client
void* client_thread(void* arg){
    ThreadInfo* threadInfo = (ThreadInfo*) arg; //Get info from struct
    char* auth = malloc(strlen(threadInfo->auth) * sizeof(char) + 1);
    strcpy(auth, threadInfo->auth);
    int fd = threadInfo->fd;
    SafeClients* clientList = threadInfo->clientList;
    free(threadInfo);
    int fd2 = dup(fd);
    FILE* to = fdopen(fd, "w");
    FILE* from = fdopen(fd2, "r");
    char buffer[10040];
    char command[40];
    char arg1[10000];  
    increment_auth(clientList);
    if (auth[0] != '\n') {
        if (!auth_check(to, from, auth)) { //Auth failed, terminate connection
            fclose(to);
            fclose(from);
            close(fd);
            return NULL;
        } else { //Auth passed, send ok
            fprintf(to, "OK:\n");
            fflush(to);
        }
    }
    struct Client* threadedClient = negotiate_name(to, from, clientList);
    while (fgets(buffer, 10040, from)) { //Wait for messages from the client
        if (ferror(from)) {
            return NULL;
        }
        sscanf(buffer, "%[^:]:%[^\n]", command, arg1);

        if (!strcmp(command, "SAY")) {
            client_say(clientList, arg1, threadedClient);
        }
        if (!strcmp(command, "LIST")) {
            client_list(clientList, to, threadedClient);
        }
        if (!strcmp(command, "KICK")) {
            client_kick(clientList, arg1, threadedClient);
        }
        if (!strcmp(command, "LEAVE")) {
            increment_leave(clientList);
            break;
        }
        
    }
    client_leave(clientList, threadedClient); //Error reading from client/left
    return NULL;
}

//Repeatedly accepts connections from clients and creates a thread to handle
//that client
void process_connections(int fdServer, char* auth, SafeClients* clientList){
    int fd;
    struct sockaddr_in fromAddr;
    socklen_t fromAddrSize;

    //Repeatedly accept connections from clients and send threads to deal 
    //with them
    while (1) {
        fromAddrSize = sizeof(struct sockaddr_in);
        //Wait for a new connection (fromaddr will be populated with a the
        //address of a new client)
        fd = accept(fdServer, (struct sockaddr*)&fromAddr, &fromAddrSize);
        if (fd < 0) {
            fprintf(stderr, "Communications error\n");
            exit(2);
        }
        //Create a new client thread

        ThreadInfo* threadInfo = malloc(sizeof(ThreadInfo*));
        threadInfo->auth = malloc(strlen(auth) * sizeof(char) + 1);
        strcpy(threadInfo->auth, auth);
        threadInfo->clientList = clientList;
        threadInfo->fd = fd;

        pthread_t threadId;
        pthread_create(&threadId, NULL, client_thread, (void*) threadInfo);
        pthread_detach(threadId);
    }
}

//Handles signlas sent to the server
//If SIGHUP is sent, list all the client and server statistics
void* handle_signals(void* arg){
    SafeClients* clientList = (SafeClients*) arg;
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGHUP);
    int errno;
    while (1) {
        sigwait(&set, &errno);
        if (errno == SIGHUP) {
            fprintf(stderr, "@CLIENTS@\n");
            sem_wait(clientList->lock);
            struct Client* currClient = clientList->firstItem;
            while (currClient != NULL) {
                fprintf(stderr, "%s:SAY:%d:KICK:%d:LIST:%d\n", 
                        currClient->name, currClient->say, currClient->kick, 
                        currClient->list);
                fflush(currClient->to);
                currClient = currClient->nextItem;
            }
            fprintf(stderr, "@SERVER@\n");
            fprintf(stderr, "server:AUTH:%d:NAME:%d:SAY:%d:KICK:%d"
                    ":LIST:%d:LEAVE:%d\n", clientList->auth, clientList->name, 
                    clientList->say, clientList->kick, clientList->list, 
                    clientList->leave);
            sem_post(clientList->lock);
        }
    }
    return NULL;
}

//Creates the signal handler thread
void create_signal_handler(SafeClients* clientList){
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGPIPE);
    sigaddset(&set, SIGHUP);
    pthread_sigmask(SIG_BLOCK, &set, NULL);

    pthread_t threadId;
    pthread_create(&threadId, NULL, handle_signals, clientList);
    pthread_detach(threadId);
}

int main(int argc, char* argv[]){
    int fdServer;
    if (argc < 2 || argc > 3) { //Check for correct num of args
        fprintf(stderr, "Usage: server authfile [port]\n");
        return 1;
    }
    const char* port; //Get the port
    if (argc == 3) {
        port = argv[2];
    } else {
        port = "0";
    }
    FILE* file = fopen(argv[1], "r"); //Open file
    if (file == NULL) {
        fprintf(stderr, "Usage: server authfile [port]\n");
        return 1;
    }
    char authBuffer[700]; //Get the auth string
    fgets(authBuffer, 700, file);
    if (authBuffer[0] != '\n') {
        strtok(authBuffer, "\n");
    }
    fclose(file);
    
    //Create client list
    SafeClients cl;
    cl.num = 0;
    cl.firstItem = NULL;
    cl.lastItem = NULL;
    SafeClients* clientList = &cl;
    //Create the lock
    sem_t l;
    sem_init(&l, 0, 1);
    cl.lock = &l;
    //Set all server stats to 0
    cl.auth = 0;
    cl.name = 0;
    cl.say = 0;
    cl.kick = 0;
    cl.list = 0;
    cl.leave = 0;

    fdServer = open_listen(port);

    create_signal_handler(clientList);

    process_connections(fdServer, authBuffer, clientList);

    return 0;

}