#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; }