├── Makefile ├── include ├── chat_room.h ├── commands.h ├── constants.h ├── global_server_cmds.h ├── list.h ├── misc.h ├── my_funcs.h ├── server_mutexes.h ├── start_server.h └── users.h ├── modules ├── .users.c.swp ├── chat_room.c ├── commands.c ├── constants.c ├── global_server_cmds.c ├── global_server_cmds.h ├── list.c ├── misc.c ├── my_funcs.c ├── server_mutexes.c ├── start_server.c └── users.c ├── run_server ├── run_server.c └── tags /Makefile: -------------------------------------------------------------------------------- 1 | FILES = run_server.c modules/*.c include/*.h 2 | INPUT = run_server.c modules/*.c 3 | FLAGS = -Wall -g -pthread 4 | OUTPUT = run_server 5 | 6 | run_server: $(FILES) 7 | gcc -o $(OUTPUT) $(FLAGS) $(INPUT) 8 | 9 | clean: 10 | rm -f run_server 11 | -------------------------------------------------------------------------------- /include/chat_room.h: -------------------------------------------------------------------------------- 1 | #ifndef CHAT_ROOM_H 2 | #define CHAT_ROOM_H 3 | 4 | #include "users.h" 5 | #include "list.h" 6 | #include "commands.h" 7 | #include "constants.h" 8 | #include "misc.h" 9 | #include "global_server_cmds.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | /* 20 | * The chat_room module manages the list of chat rooms. It also has functions for the loop to listen for 21 | * commands from the client, and how to respond to these commands. 22 | * 23 | * Forward declaration here so that gcc will compile properly. Could have also done 2 stages generating .o files 24 | * then linking, but this method always works. 25 | */ 26 | 27 | typedef struct user_struct user; //Forward-declare typedef 28 | 29 | struct chat_room_struct { 30 | 31 | list* users_in_room; //linked list of users in this chat room 32 | char* room_name; //Name of room 33 | char* current_msg; //Unused currently. History of msg previously sent to user 34 | pthread_mutex_t chat_room_mutex; //To avoid 2 threads editing user's room list simultaneously 35 | 36 | }; 37 | 38 | typedef struct chat_room_struct chat_room; 39 | 40 | //Allocate LOBBY chat room and chat_room_list (see chat_room.c) 41 | void init_chat_rooms(); 42 | 43 | //Allocate a single chat room. Initialize mutex, etc. 44 | chat_room* new_chat_room(char* name); 45 | 46 | //Free a chat room. Destroys mutex, frees heap data, etc. 47 | void free_chat_room(chat_room* cr); 48 | 49 | //Add user to the special LOBBY room 50 | void add_user_to_lobby(user* u); 51 | 52 | //Adds user to room if it exists, otherwise creates new room and adds user 53 | void add_user_to_room(user* u, char* room_name); 54 | 55 | //Receive-act on commands from client 56 | void chat_room_loop(user* u); 57 | 58 | //Act on a particular command received 59 | void act_on_chat_cmd(char* msg, user* u); 60 | 61 | //Send message to all users in a chat room. Default behaviour of +MSG command 62 | void msg_all_users(char* msg, chat_room* room, char* sender); 63 | 64 | //Send list of all users in a room to user u. +LIST command 65 | void list_users_in_room(user* u); 66 | 67 | //Send list of all rooms on server to user u. +ROOMS command 68 | void list_all_rooms(user* u); 69 | 70 | //Send list of all uesrs on server to user u. +LIST_ALL command. 71 | void list_server_users(user* u); 72 | 73 | //Send a message to a single user. Prepends +MSG command 74 | void print_to_user(user* u, char* msg); 75 | 76 | //Sends string msg to user. For sending commands other than +MSG 77 | void print_raw_to_user(user* u, char* msg); 78 | 79 | //Returns chat room if it's in list, otherwise returns NULL 80 | chat_room* chat_room_exists(char* chat_room_name); 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /include/commands.h: -------------------------------------------------------------------------------- 1 | #ifndef _IRC_COMMANDS 2 | #define _IRC_COMMANDS 3 | 4 | /* 5 | * These constants are defined in commands.c 6 | */ 7 | 8 | //Commands from client 9 | const char* CONNECT_CMD; 10 | const char* QUIT_CMD; 11 | const char* MSG_CMD; //User doesn't have to type +MSG, appended by program when in a valid chat room 12 | const char* LIST_CMD; //list names of ppl in channel 13 | const char* LIST_ALL_CMD; //list names of all ppl on server 14 | const char* ROOMS_CMD; 15 | const char* HELP_CMD; 16 | const char* JOIN_CMD; 17 | 18 | //Commands to client 19 | const char* PRINT_CMD; 20 | const char* SET_PROMPT_CMD; 21 | const char* FAIL_CMD; 22 | const char* JOIN_SUCCESS_CMD; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef IRC_CONSTANTS 2 | #define IRC_CONSTANTS 3 | 4 | //Defined in constants.c 5 | // 6 | const int BUFFER_SIZE; //Max buffer determined by SCoT protocol 7 | const int SLEEP_TIME_US; //Not used at the moment--for debugging 8 | const int MAX_PROMPT_SIZE; 9 | 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/global_server_cmds.h: -------------------------------------------------------------------------------- 1 | #ifndef _GLOBAL_SERVER_CMDS 2 | #define _GLOBAL_SERVER_CMDS 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "constants.h" 12 | 13 | //Check if a nick or room name uses only allowed chars 14 | bool valid_charset(char* test); 15 | 16 | //Checks an individual char 17 | bool char_in_charset(char c); 18 | 19 | //Reads from socket to get the next command from the user 20 | //Caller must have a buffer with partial command 21 | //Returns new command (dynamically allocated) if we got a full cmd 22 | //Otherwise, returns NULL indicating only received partial cmd 23 | char* get_next_cmd(int socket, char* partial, bool* err); 24 | 25 | //Just a helper function to check if the data received had a null char 26 | //Must use this since all std. library functions assume the string 27 | //is already null-terminated 28 | int contains_null_char(char* str, int num_bytes); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /include/list.h: -------------------------------------------------------------------------------- 1 | #ifndef _LIST 2 | #define _LIST 3 | 4 | #include 5 | #include 6 | 7 | typedef struct node_struct node; //Forward declaration so a list can have a next pointer 8 | 9 | typedef struct node_struct { //Node type. Holds arbitrary data 10 | 11 | void* data; 12 | node* next; 13 | 14 | } node_str; 15 | 16 | typedef struct { //List type 17 | 18 | node* head; 19 | 20 | void (*free_data)(void*); //Function that frees the data in node. 21 | 22 | } list; 23 | 24 | node* new_node(void* data); //Allocate a new list node 25 | 26 | list* new_list(void (*free_fnc)(void*)); //Allocate a new empty list. Pass in function pointer for freeing data in list 27 | 28 | void add_to_list(list* l, void* data); //Adds node to list and returns the new head 29 | 30 | void remove_front(list* l); //Removes a node from the front and frees the node. Doesn't free data, because 31 | //for this project we may need to remove a user from multiple lists, then free the actual data. 32 | 33 | int list_size(list* l); //Count number of nodes in a list 34 | 35 | void remove_node(list* l, void* data); //Removes node from list and frees node, without freeing data 36 | 37 | bool is_empty(list* l); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /include/misc.h: -------------------------------------------------------------------------------- 1 | #include "string.h" 2 | 3 | //Removes whitespace from end of string 4 | void trim_str(char* str); 5 | -------------------------------------------------------------------------------- /include/my_funcs.h: -------------------------------------------------------------------------------- 1 | #include "string.h" 2 | 3 | void trim_str(char* str); 4 | -------------------------------------------------------------------------------- /include/server_mutexes.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_MUTEX 2 | #define SERVER_MUTEX 3 | 4 | #include 5 | 6 | pthread_mutex_t sock_mutex; //Guarantee 2 threads don't simultaneously write to client socket 7 | pthread_mutex_t users_mutex; //Guarantee 2 threads don't both modify user list 8 | pthread_mutex_t chat_room_mutex; //Guarantee 2 threads don't both modify chat room list 9 | 10 | //Must be called by server code before command loop 11 | void init_mutexes(); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /include/start_server.h: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | #include "stdlib.h" 3 | #include "unistd.h" 4 | #include "string.h" 5 | #include "sys/socket.h" 6 | #include "sys/types.h" 7 | #include "netdb.h" 8 | #include "errno.h" 9 | 10 | /* 11 | *Function to open a socket and bind it. 12 | *Returns the file descriptor for the socket. 13 | */ 14 | int start_server(char* socket_str); 15 | 16 | //Start listening on socket that accepts new clients 17 | int start_listening(int socket_fd); 18 | -------------------------------------------------------------------------------- /include/users.h: -------------------------------------------------------------------------------- 1 | #ifndef USERS_H 2 | #define USERS_H 3 | 4 | #include "chat_room.h" 5 | #include "list.h" 6 | #include "commands.h" 7 | #include "server_mutexes.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /* 15 | * The users module manages creating new users, freeing users, removing users from all lists, manages 16 | * the global user list all_users. 17 | * 18 | * Also processes some commands such as set_user_prompt (for +PRINT) and checking if a user is in the global list. 19 | */ 20 | 21 | //Forward-declare this struct so that linking works. 22 | typedef struct chat_room_struct chat_room; 23 | 24 | struct user_struct { 25 | char* nick; 26 | pthread_t user_thread; //Each user has its own thread 27 | int user_socket; 28 | chat_room* current_room; 29 | pthread_mutex_t user_sock_mutex; 30 | }; 31 | 32 | typedef struct user_struct user; 33 | 34 | list* all_users; //List of all users. 35 | 36 | void init_user_list(); //create empty list of users 37 | 38 | void init_user(char* nick, int sock, pthread_t user_thread); //Creates a new user object and adds it to the list all_users 39 | //Also adds user to LOBBY room 40 | 41 | void set_user_prompt(user* u, char* new_prompt); //+PROMPT command 42 | 43 | void free_user(user* u); //Free data associated with a user u 44 | 45 | void remove_user(user* u); //removes user from all_users list, current chat room, then frees user 46 | 47 | bool user_exists(char* nick); //Checks if user is in global list of all users 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /modules/.users.c.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlescapps/Simple-IRC-server/e4b58e81d65aa9f608396a4be71db21da9176551/modules/.users.c.swp -------------------------------------------------------------------------------- /modules/chat_room.c: -------------------------------------------------------------------------------- 1 | #include "../include/chat_room.h" 2 | /* 3 | * See chat_room.h for comments on what these functions do. 4 | */ 5 | 6 | static const char* LOBBY_NAME = "LOBBY"; //Name of lobby room 7 | static list* all_chat_rooms = NULL; //List of all chat rooms. Static for C-style data encapsulation 8 | static chat_room* LOBBY = NULL; //LOBBY chat_room object 9 | 10 | void init_chat_rooms() { 11 | 12 | if (all_chat_rooms == NULL) { 13 | all_chat_rooms = new_list((void(*)(void*))free_chat_room); 14 | } 15 | 16 | if (LOBBY == NULL) { 17 | LOBBY = new_chat_room((char*)LOBBY_NAME); 18 | add_to_list(all_chat_rooms, (void*)LOBBY); 19 | } 20 | } 21 | 22 | chat_room* new_chat_room(char* name) { //Creates new chat room called 'name'. Assumes name is dynamically allocated already. 23 | 24 | chat_room* new_room = (chat_room*)malloc(sizeof(chat_room)); 25 | new_room -> users_in_room = new_list((void(*)(void*)) free_user ); 26 | new_room -> room_name = name; 27 | new_room -> current_msg = NULL; 28 | pthread_mutex_init(&(new_room->chat_room_mutex), NULL); 29 | 30 | return new_room; 31 | } 32 | 33 | void free_chat_room(chat_room* c) { 34 | pthread_mutex_destroy(&(c->chat_room_mutex)); 35 | free(c->room_name); 36 | free(c); 37 | } 38 | 39 | void add_user_to_lobby(user* u) { 40 | 41 | char new_prompt[MAX_PROMPT_SIZE]; 42 | char msg_buffer[BUFFER_SIZE]; 43 | 44 | sprintf(new_prompt, "%s @ %s>", u->nick, LOBBY_NAME); 45 | set_user_prompt(u, new_prompt); //Sends +PROMPT command to user to force change of prompt 46 | 47 | add_to_list(LOBBY -> users_in_room, (void*)u); 48 | u->current_room = LOBBY; 49 | 50 | print_raw_to_user(u, (char*)JOIN_SUCCESS_CMD); 51 | sprintf(msg_buffer, "+PRINT [Server]: Connected as user '%s'.\n[Server]: Now entering lobby.\n \n", u->nick); 52 | print_raw_to_user(u, msg_buffer); 53 | 54 | chat_room_loop(u); 55 | } 56 | 57 | void chat_room_loop(user* u) { 58 | char partial_cmd[BUFFER_SIZE]; //Required by get_next_cmd. Calling function must have buffer of partial command. 59 | char* next_cmd = NULL; 60 | bool err = false; 61 | 62 | partial_cmd[0] = '\0'; 63 | 64 | while (true) { 65 | 66 | if (next_cmd) { 67 | free(next_cmd); 68 | next_cmd = NULL; 69 | } 70 | next_cmd = get_next_cmd(u->user_socket, partial_cmd, &err); 71 | 72 | if (err) { 73 | printf("Error reading message from client.\nClosing socket #%d to client '%s'\nError: %s\n", u->user_socket, u->nick, strerror(errno)); 74 | remove_user(u); 75 | if (next_cmd) { 76 | free(next_cmd); 77 | } 78 | pthread_exit(NULL); 79 | } 80 | else if (next_cmd == NULL) { //Haven't received full command 81 | continue; 82 | } 83 | else { 84 | trim_str(next_cmd); 85 | printf("Message from client %s: %s\n", u->nick, next_cmd); 86 | act_on_chat_cmd(next_cmd, u); 87 | } 88 | 89 | } 90 | } 91 | 92 | void act_on_chat_cmd(char* cmd, user* u) { 93 | char* tok1 = NULL; 94 | char* tok2 = NULL; 95 | char* msg = NULL; 96 | 97 | //Get the first token to determine which command we received. 98 | //Of course, strtok adds null char after each toekn is read, so keep this in mind 99 | tok1 = strtok(cmd, " "); 100 | 101 | if (strcmp(tok1, MSG_CMD) == 0) { //+MSG 102 | msg = (char*)(&cmd[strlen(MSG_CMD)+1]); //Send the message as everything after +MSG 103 | trim_str(msg); //Trim since chat messages can't have endlines 104 | msg_all_users(msg, u->current_room, u->nick); 105 | } 106 | else if (strcmp(tok1, JOIN_CMD)==0) { //+JOIN 107 | tok2 = strtok(NULL, " "); //Room name is the second token 108 | add_user_to_room(u, tok2); 109 | } 110 | else if (strcmp(tok1, LIST_CMD) == 0) { 111 | list_users_in_room(u); 112 | } 113 | else if (strcmp(tok1, LIST_ALL_CMD) == 0) { 114 | list_server_users(u); 115 | } 116 | else if (strcmp(tok1, ROOMS_CMD) == 0) { 117 | list_all_rooms(u); 118 | } 119 | else { 120 | printf("Received bad command from user '%s', sending fail msg\n", u->nick); 121 | print_raw_to_user(u, (char*)FAIL_CMD); 122 | } 123 | } 124 | 125 | void add_user_to_room(user* u, char* room_name) { //Adds user to room if it exists, otherwise creates new room and adds user 126 | 127 | if (room_name == NULL || strlen(room_name) <= 0) { 128 | printf("Error: Attempt to add user to NULL or empty room name.\n"); 129 | return; 130 | } 131 | 132 | if (strcasecmp(room_name, u->current_room->room_name) == 0) { 133 | print_to_user(u, "[Server]: You are already in that chat room!"); 134 | return; 135 | } 136 | 137 | if (!valid_charset(room_name)) { 138 | print_to_user(u, "[Server]: Invalid room name!\n" 139 | "[Server]: Alphanumeric or '_', '-', '*', '&' required!\n "); 140 | return; 141 | } 142 | chat_room* room_to_enter; 143 | char* new_room_name; //Will dynamically allocate to pass to new_room(char*) 144 | char msg_buffer[BUFFER_SIZE]; 145 | char new_prompt[MAX_PROMPT_SIZE]; 146 | 147 | sprintf(new_prompt, "%s @ %s>", u->nick, room_name); 148 | set_user_prompt(u, new_prompt); //Set user's prompt 149 | 150 | if ( (room_to_enter = chat_room_exists(room_name))) { //Check if room exists. 151 | remove_node(u->current_room->users_in_room, u); //Remove user from old chat room 152 | u->current_room = room_to_enter; //Set user's current room to the new room 153 | add_to_list(room_to_enter->users_in_room, (void*)u); //Add user to new chat room's list of users 154 | 155 | //If we successfully added user to room, send success command 156 | print_raw_to_user(u, (char*)JOIN_SUCCESS_CMD); 157 | //Build string to send user. Different if joining room vs. creating new room 158 | sprintf(msg_buffer, "[Server]: Joining room '%s'\n \n", room_to_enter->room_name); 159 | print_to_user(u, msg_buffer); //Send message to user 160 | printf("User %s joined existing room %s.\n", u->nick, room_to_enter->room_name); 161 | } 162 | else { 163 | //Need to dynamically allocate name to store in chat_room object if a new room is created 164 | new_room_name = (char*)malloc(strlen(room_name) + 1); 165 | strcpy(new_room_name, room_name); 166 | 167 | room_to_enter = new_chat_room(new_room_name); //Create new chat room 168 | add_to_list(all_chat_rooms, room_to_enter); //Add new room to universal list of chat rooms 169 | add_to_list(room_to_enter->users_in_room, (void*)u); //Add user to new chat room's list of users 170 | 171 | remove_node(u->current_room->users_in_room, u); //Remove user from old chat room 172 | u->current_room = room_to_enter; //Set user's current room to the new room 173 | 174 | //If we successfully added user to room, send success command 175 | print_raw_to_user(u, (char*)JOIN_SUCCESS_CMD); 176 | //Build string to send user. Different if joining room vs. creating new room 177 | sprintf(msg_buffer, "[Server]: Creating new room '%s'\n \n", new_room_name); 178 | print_to_user(u, msg_buffer); 179 | printf("User %s joined new room %s.\n", u->nick, new_room_name); 180 | } 181 | } 182 | 183 | void msg_all_users(char* msg, chat_room* room, char* sender) { 184 | char buffer[BUFFER_SIZE]; 185 | int tmp_socket = -1; 186 | node* tmp_node = room -> users_in_room -> head; 187 | user* tmp_user = NULL; 188 | int err = -1; 189 | 190 | while (tmp_node != NULL) { 191 | tmp_user = (user*)(tmp_node -> data); 192 | tmp_socket = tmp_user -> user_socket; 193 | 194 | sprintf(buffer, "%s %s says: %s\n", PRINT_CMD, sender, msg); 195 | pthread_mutex_lock(&(tmp_user->user_sock_mutex)); 196 | err = send(tmp_socket, buffer, strlen(buffer) + 1, 0); 197 | pthread_mutex_unlock(&(tmp_user->user_sock_mutex)); 198 | 199 | if (err < 0) { 200 | printf("Error sending msg %s to user %s\n", buffer, tmp_user -> nick); 201 | } 202 | 203 | tmp_node = tmp_node -> next; 204 | } 205 | } 206 | 207 | void list_users_in_room(user* u) { //Print out all the users in a user's room to the user 208 | 209 | node* tmp = u->current_room->users_in_room->head; 210 | user* tmp_user = NULL; 211 | int user_no = 0; 212 | char strbuffer[BUFFER_SIZE]; 213 | 214 | print_to_user(u, "All users in room:"); 215 | 216 | while (tmp != NULL) { 217 | tmp_user = (user*)(tmp->data); 218 | sprintf(strbuffer, ">User %d: %s", ++user_no, tmp_user->nick); 219 | print_to_user(u, strbuffer); 220 | tmp = tmp->next; 221 | } 222 | 223 | print_to_user(u, " \n"); //Endline after list of users for pretty formatting. 224 | 225 | } 226 | 227 | void list_server_users(user* u) { 228 | node* tmp = all_users->head; 229 | user* tmp_user = NULL; 230 | int user_no = 0; 231 | char strbuffer[BUFFER_SIZE]; 232 | 233 | print_to_user(u, "All users on server:"); 234 | 235 | while (tmp != NULL) { 236 | tmp_user = (user*)(tmp->data); 237 | sprintf(strbuffer, ">User %d: %s in room %s", ++user_no, tmp_user->nick, tmp_user->current_room->room_name); 238 | print_to_user(u, strbuffer); 239 | tmp = tmp->next; 240 | } 241 | 242 | print_to_user(u, " \n"); //Endline after list of users for pretty formatting. 243 | } 244 | 245 | void list_all_rooms(user* u) { 246 | node* tmp = all_chat_rooms->head; 247 | chat_room* tmp_room = NULL; 248 | int room_no = 0; 249 | char strbuffer[BUFFER_SIZE]; 250 | 251 | print_to_user(u, "All chat rooms on server:"); 252 | 253 | while (tmp != NULL) { 254 | tmp_room = (chat_room*)tmp->data; 255 | sprintf(strbuffer, ">Room %d: %s", ++room_no, tmp_room->room_name); 256 | print_to_user(u, strbuffer); 257 | tmp = tmp->next; 258 | } 259 | 260 | print_to_user(u, " \n"); 261 | } 262 | 263 | 264 | void print_to_user(user* u, char* msg) { 265 | 266 | char buffer[BUFFER_SIZE]; 267 | sprintf(buffer, "%s %s", PRINT_CMD, msg); 268 | int err = -1; 269 | 270 | pthread_mutex_lock(&(u->user_sock_mutex)); 271 | err = send(u->user_socket, buffer, strlen(buffer) + 1, 0); 272 | pthread_mutex_unlock(&(u->user_sock_mutex)); 273 | 274 | if (err < 0) 275 | printf("Error in 'print_to_user' function.\nError: %s\n", strerror(errno)); 276 | else if (err == 0) { 277 | printf("Error in 'print_to_user': 0 bytes written.\nError: %s\n", strerror(errno)); 278 | } 279 | } 280 | 281 | void print_raw_to_user(user* u, char* msg) { 282 | 283 | if (msg == NULL || strlen(msg) <=0) { 284 | printf("Error: NULL or 0-length message in print_raw_to_user()\n"); 285 | return; 286 | } 287 | 288 | int err = -1; 289 | 290 | pthread_mutex_lock(&(u->user_sock_mutex)); 291 | err = send(u->user_socket, msg, strlen(msg) + 1, 0); 292 | pthread_mutex_unlock(&(u->user_sock_mutex)); 293 | 294 | if (err < 0) 295 | printf("Error sending in 'print_raw_to_user' function.\nError: %s\n", strerror(errno)); 296 | else if (err == 0) 297 | printf("Error sending in 'print_raw_to_user': 0 bytes written.\nError: %s\n", strerror(errno)); 298 | 299 | } 300 | 301 | chat_room* chat_room_exists(char* chat_room_name) { 302 | node* tmp = all_chat_rooms->head; 303 | 304 | while (tmp != NULL) { 305 | if (strcasecmp(((chat_room*)(tmp->data))->room_name, chat_room_name)==0) 306 | return (chat_room*)(tmp->data); 307 | tmp = tmp->next ; 308 | } 309 | return NULL; 310 | } 311 | -------------------------------------------------------------------------------- /modules/commands.c: -------------------------------------------------------------------------------- 1 | /* 2 | * commands.c 3 | * 4 | * Created on: Oct 18, 2011 5 | * Author: charles 6 | */ 7 | 8 | //Commands from client 9 | const char* CONNECT_CMD = "+CONNECT"; 10 | const char* QUIT_CMD = "+QUIT"; 11 | const char* MSG_CMD = "+MSG"; 12 | const char* LIST_CMD = "+LIST"; //list names of ppl in channel 13 | const char* LIST_ALL_CMD = "+LIST_ALL"; //list names of all ppl on server 14 | const char* ROOMS_CMD = "+ROOMS"; 15 | const char* HELP_CMD = "+HELP"; 16 | const char* JOIN_CMD = "+JOIN"; 17 | 18 | //Commands to client 19 | const char* PRINT_CMD = "+PRINT"; 20 | const char* SET_PROMPT_CMD = "+PROMPT"; 21 | const char* FAIL_CMD = "+FAIL"; 22 | const char* JOIN_SUCCESS_CMD = "+JOIN_SUCCESS"; 23 | 24 | -------------------------------------------------------------------------------- /modules/constants.c: -------------------------------------------------------------------------------- 1 | #include "../include/constants.h" 2 | 3 | const int BUFFER_SIZE = 512; //Universal buffer size 4 | const int SLEEP_TIME_US = 10000; 5 | const int MAX_PROMPT_SIZE = 100; 6 | -------------------------------------------------------------------------------- /modules/global_server_cmds.c: -------------------------------------------------------------------------------- 1 | #include "../include/global_server_cmds.h" 2 | 3 | 4 | bool valid_charset(char* test) { 5 | 6 | if (test == NULL) { 7 | printf("In valid_charse: NULL string received.\n"); 8 | return false; 9 | } 10 | int len = strlen(test), i = 0; 11 | 12 | for (; i < len; i++) { 13 | if (!char_in_charset(test[i])) 14 | return false; 15 | } 16 | return true; 17 | } 18 | 19 | 20 | bool char_in_charset(char c) { 21 | 22 | if ('a' <= c && c <= 'z') 23 | return true; 24 | 25 | if ('A' <= c && c <= 'Z') 26 | return true; 27 | 28 | if ('0' <= c && c <= '9') 29 | return true; 30 | 31 | if (c == '-' || c == '_' || c == '*' || c == '&' || c == '#') 32 | return true; 33 | 34 | return false; 35 | 36 | } 37 | 38 | char* get_next_cmd(int socket, char* partial_cmd, bool* err) { 39 | int num_bytes = -1, null_char_loc = -1; 40 | char buffer[BUFFER_SIZE]; 41 | char* next_cmd; 42 | *err = false; 43 | 44 | if ((num_bytes = recv(socket, buffer, BUFFER_SIZE, 0)) <= 0) { 45 | *err = true; 46 | return NULL; 47 | } 48 | 49 | null_char_loc = contains_null_char(buffer, num_bytes); 50 | //We didn't find the end of a command 51 | if (null_char_loc < 0) { 52 | strncat(partial_cmd, buffer, num_bytes); //concatenate buffer onto current partial command 53 | return NULL; //NULL means no new command 54 | } 55 | else { //We found a null char, hence end of a command 56 | int len = strlen(buffer); 57 | strncat(partial_cmd, buffer, len); 58 | next_cmd = (char*)malloc(sizeof(char)*(strlen(partial_cmd)+1)); 59 | strcpy(next_cmd, partial_cmd); 60 | partial_cmd[0]='\0'; //Reset partial command 61 | 62 | //If we received 2 commands simultaneously 63 | if (len + 1 < num_bytes) { 64 | strncpy(partial_cmd, buffer + (len + 1), num_bytes - (len + 1)); 65 | } 66 | return next_cmd; 67 | } 68 | } 69 | 70 | int contains_null_char(char* str, int num_bytes) { 71 | int i = 0, pos = -1; 72 | for (; idata = data; 6 | n->next = NULL; 7 | return n; 8 | } 9 | 10 | list* new_list(void (*free_fnc)(void*)) {//Allocate a new list; free_fnc is a function pointer to a fucntion for freeing the data in the list 11 | list* l = (list*) malloc(sizeof(list)); 12 | l->free_data = free_fnc; 13 | l->head = NULL; 14 | return l; 15 | } 16 | 17 | void add_to_list(list* l, void* data) { //Adds node to list and returns the new head 18 | node* new_head = new_node(data); 19 | new_head -> next = l->head; 20 | l->head = new_head; 21 | } 22 | 23 | void remove_front(list* l) { //Removes a node from the front. Doesn't free data, because I need to manage it for this project 24 | if (l->head == NULL) { 25 | return; 26 | } 27 | 28 | node* old_head = l->head; 29 | l->head = old_head->next; 30 | free(old_head); //Call function to free the old head 31 | } 32 | 33 | void remove_node(list* l, void* data){ //Removes node from list without freeing data 34 | node* iter = l->head; 35 | node* to_delete; 36 | if (!iter) //empty list 37 | return; 38 | 39 | if (iter->data == data) { //Data in first node, just remove the front 40 | remove_front(l); 41 | return; 42 | } 43 | 44 | while (iter->next && iter->next->data != data) { 45 | iter = iter->next; 46 | } 47 | 48 | //Never found node to remove 49 | if (iter->next==NULL) 50 | return; 51 | 52 | //Found node to remove; remove it and free the node 53 | to_delete = iter->next; 54 | iter->next = iter->next->next; 55 | 56 | free(to_delete); 57 | 58 | } 59 | 60 | int list_size(list* l) { //Count number of nodes in a list 61 | 62 | if (l == NULL) { 63 | return 0; 64 | } 65 | 66 | node* tmp = l->head; 67 | int cnt = 0; 68 | 69 | while (tmp != NULL) { 70 | cnt++; 71 | tmp = tmp->next; 72 | } 73 | 74 | return cnt; 75 | } 76 | 77 | bool is_empty(list* l) { 78 | if (l->head == NULL) 79 | return true; 80 | return false; 81 | } 82 | -------------------------------------------------------------------------------- /modules/misc.c: -------------------------------------------------------------------------------- 1 | #include "../include/misc.h" 2 | 3 | void trim_str(char* str) { 4 | int len = strlen(str); 5 | int i = len - 1; 6 | 7 | while (i >=0 && (str[i] == '\n' || str[i] == ' ' || str[i] == '\t')) { 8 | --i; 9 | } 10 | 11 | str[i+1] = '\0'; 12 | } 13 | -------------------------------------------------------------------------------- /modules/my_funcs.c: -------------------------------------------------------------------------------- 1 | #include "../include/my_funcs.h" 2 | 3 | void trim_str(char* str) { 4 | int len = strlen(str); 5 | int i = len - 1; 6 | 7 | while (i >=0 && (str[i] == '\n' || str[i] == ' ' || str[i] == '\t')) { 8 | --i; 9 | } 10 | 11 | str[i+1] = '\0'; 12 | } 13 | -------------------------------------------------------------------------------- /modules/server_mutexes.c: -------------------------------------------------------------------------------- 1 | #include "../include/server_mutexes.h" 2 | 3 | void init_mutexes() { 4 | 5 | pthread_mutex_init(&sock_mutex, NULL); //Guarantee 2 threads don't simultaneously write to client socket 6 | pthread_mutex_init(&users_mutex, NULL); //Guarantee 2 threads don't both modify user list 7 | pthread_mutex_init(&chat_room_mutex, NULL); //Guarantee 2 threads don't both modify chat room list 8 | } 9 | -------------------------------------------------------------------------------- /modules/start_server.c: -------------------------------------------------------------------------------- 1 | #include "../include/start_server.h" 2 | 3 | static const int MAX_PENDING_CONNECTIONS = 256; 4 | 5 | int start_server(char* socket_str) { 6 | struct addrinfo hints; 7 | struct addrinfo *result, *rp; //result stores addresses returned by getaddrinfo; rp is just a pointer to iterate 8 | int socket_fd, error_code; 9 | 10 | memset(&hints, 0, sizeof(struct addrinfo)); //Set hints to default 11 | hints.ai_family = AF_INET; //IPv4 12 | hints.ai_socktype = SOCK_STREAM; //Two-way reliable communication 13 | hints.ai_flags = AI_PASSIVE; //Flag for server 14 | hints.ai_protocol = 0; //Any protocol. Will choose IPv4 due to SOCK_STREAM choice 15 | hints.ai_canonname = NULL; //According to docs, these should be NULL for binding an accept socket 16 | hints.ai_addr = NULL; 17 | hints.ai_next = NULL; 18 | 19 | error_code = getaddrinfo(NULL, socket_str, &hints, &result); //Get an address that can be used in bind to host server 20 | 21 | if (error_code != 0) { 22 | fprintf(stderr, "Error calling getaddrinfo: %s\nExiting...\n", gai_strerror(error_code)); 23 | exit(EXIT_FAILURE); 24 | } 25 | 26 | for (rp = result; rp!=NULL; rp = rp->ai_next) { //Try all sockets returned in linked list 27 | socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 28 | //printf("Socket FD obtained: %d\n", socket_fd); 29 | 30 | if (socket_fd < 0) //Failed to open socket, try next one 31 | continue; 32 | 33 | if (bind(socket_fd, rp->ai_addr, rp->ai_addrlen) == 0) //Succeeded, use this socket 34 | break; 35 | 36 | fprintf(stderr, "Error calling bind: %s\n", strerror(errno)); 37 | 38 | close(socket_fd); //If we got a socket, but failed to bind, close it an try the next one 39 | } 40 | 41 | freeaddrinfo(result); //Result already parsed, free memory 42 | 43 | if (rp == NULL) { 44 | fprintf(stderr, "Error: Failed to bind a socket!\nExiting...\n"); 45 | exit(EXIT_FAILURE); 46 | } 47 | 48 | printf("Accepting clients on socket %s. File descriptor: %d\n", socket_str, socket_fd); 49 | 50 | return socket_fd; 51 | } 52 | 53 | int start_listening(int socket_fd) { 54 | 55 | return listen(socket_fd, MAX_PENDING_CONNECTIONS); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /modules/users.c: -------------------------------------------------------------------------------- 1 | #include "../include/users.h" 2 | 3 | pthread_mutex_t sock_mutex; //guarantees 2 threads don't write to 4 | 5 | void init_user_list() {//Create empty list of users if it hasn't been initialized 6 | all_users = new_list((void(*)(void*))free_user); 7 | } 8 | 9 | void init_user(char* nick, int sock, pthread_t user_thread) { 10 | 11 | user* new_user = (user*)malloc(sizeof(user)); 12 | new_user->user_socket = sock; 13 | new_user->current_room = NULL; 14 | new_user->nick = nick; 15 | new_user->user_thread = user_thread; 16 | pthread_mutex_init(&(new_user->user_sock_mutex), NULL); 17 | 18 | pthread_mutex_lock(&users_mutex); //Atomically add user to list 19 | add_to_list(all_users, new_user); 20 | pthread_mutex_unlock(&users_mutex); 21 | 22 | add_user_to_lobby(new_user); 23 | } 24 | 25 | void free_user(user* u) { 26 | close(u->user_socket); 27 | pthread_mutex_destroy(&(u->user_sock_mutex)); 28 | free(u->nick); 29 | free(u); 30 | } 31 | 32 | void remove_user(user* u) { //removes user from all_users list, current chat room, then frees user 33 | if (u->current_room != NULL) {//Remove user from its current chat room 34 | pthread_mutex_lock(&(u->current_room->chat_room_mutex)); 35 | remove_node(u->current_room->users_in_room, (void*)u); 36 | pthread_mutex_unlock(&(u->current_room->chat_room_mutex)); 37 | } 38 | 39 | pthread_mutex_lock(&users_mutex); 40 | remove_node(all_users, (void*)u); //Remove user from global list of users 41 | pthread_mutex_unlock(&users_mutex); 42 | 43 | free_user(u); //Finally, close user socket, destroy thread, and free memory 44 | } 45 | 46 | void set_user_prompt(user* u, char* new_prompt) { 47 | 48 | char msg_to_client[BUFFER_SIZE]; 49 | int bytes_written; 50 | 51 | sprintf(msg_to_client, "%s %s", SET_PROMPT_CMD, new_prompt); 52 | 53 | pthread_mutex_lock(&(u->user_sock_mutex)); //atomically write to user's socket using user's mutex 54 | bytes_written = send(u->user_socket, msg_to_client, strlen(msg_to_client) + 1, 0); 55 | pthread_mutex_unlock(&(u->user_sock_mutex)); 56 | 57 | if (bytes_written < 0) { 58 | printf("Error sending new prompt.\nError: %s\n", strerror(errno)); 59 | } 60 | else if (bytes_written == 0) { 61 | printf("Error sending new prompt: 0 bytes written.\nError: %s\n", strerror(errno)); 62 | } 63 | } 64 | 65 | bool user_exists(char* nick) { 66 | 67 | user* tmp_u; 68 | bool exists = false; 69 | node* iter = all_users->head; 70 | 71 | while (iter != NULL) { 72 | tmp_u = (user*)iter->data; 73 | if (strcasecmp(tmp_u->nick, nick)==0) { 74 | exists = true; 75 | break; 76 | } 77 | iter=iter->next; 78 | } 79 | return exists; 80 | } 81 | -------------------------------------------------------------------------------- /run_server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlescapps/Simple-IRC-server/e4b58e81d65aa9f608396a4be71db21da9176551/run_server -------------------------------------------------------------------------------- /run_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "include/users.h" 13 | #include "include/start_server.h" 14 | #include "include/misc.h" 15 | #include "include/commands.h" 16 | #include "include/constants.h" 17 | #include "include/server_mutexes.h" 18 | #include "include/global_server_cmds.h" 19 | 20 | //As soon as we accept a client, we start a new thread so we can continue accepting clients. 21 | //We need this struct with the barebones information about a client so we can just call pthread_create with one arument 22 | typedef struct user_info_s { 23 | pthread_t my_thread; 24 | int my_socket; 25 | } user_info; 26 | 27 | void accept_loop(int welcome_socket); 28 | char* parse_connect_cmd(char* cmd, bool* quit); //Returns the nick for connect cmd 29 | void wait_for_connect_cmd(user_info* client_socket); //A new thread just for the user is created as soon as the socket connects 30 | void catch_broken_pipe(int signum); 31 | 32 | int main(int argc, char** argv) { 33 | 34 | int error_number; 35 | 36 | if (argc != 2) { 37 | fprintf(stderr, "Error- need exactly 1 argument. Usage: run_server \n"); 38 | exit(EXIT_FAILURE); 39 | } 40 | 41 | 42 | //Get socket from args 43 | char* chosen_socket = argv[1]; 44 | 45 | //Call method from start_server.h to get socket that accepts clients 46 | int socket_fd = start_server(chosen_socket); 47 | 48 | //Call another function to start listening on this socket. 49 | if ( (error_number = start_listening(socket_fd)) < 0) { 50 | fprintf(stderr, "Error listening on socket: %s\nExiting...\n", strerror(errno)); 51 | exit(EXIT_FAILURE); 52 | } 53 | 54 | //register function to deal with a broken pipe, e.g. when client exits program and server tries to write to stream 55 | //signal(SIGPIPE, catch_broken_pipe); 56 | 57 | //Init mutexes used 58 | init_mutexes(); 59 | 60 | //Init list of users to empty list 61 | init_user_list(); 62 | 63 | //Init list of chat rooms to empty list, instantiate lobby chat room 64 | init_chat_rooms(); 65 | 66 | //Start loop to accept clients 67 | accept_loop(socket_fd); 68 | 69 | return EXIT_SUCCESS; 70 | } 71 | 72 | void accept_loop(int server_socket) { 73 | 74 | int new_socket_fd = -1; //File descriptor of connecting client 75 | socklen_t client_length = sizeof(struct sockaddr); 76 | 77 | struct sockaddr client_addr; 78 | user_info* ui; 79 | 80 | //reset client address object 81 | memset(&client_addr, 0, sizeof(struct sockaddr)); 82 | 83 | while (true) { 84 | //Wait for client to connect 85 | new_socket_fd = accept(server_socket, &client_addr, &client_length); 86 | 87 | if (new_socket_fd < 0) { 88 | printf("Error accepting client connection: %s", strerror(errno)); 89 | continue; 90 | } 91 | 92 | ui = (user_info*)malloc(sizeof(user_info)); 93 | ui -> my_socket = new_socket_fd; 94 | 95 | pthread_create(&(ui->my_thread), NULL, (void*(*)(void*))wait_for_connect_cmd, (void*)(ui)); 96 | } 97 | } 98 | 99 | 100 | 101 | //Wait until user types +CONNECT to enter the lobby 102 | void wait_for_connect_cmd(user_info* ui) { 103 | char partial_cmd[BUFFER_SIZE]; char to_send[BUFFER_SIZE]; 104 | char* next_cmd = NULL; char* nick = NULL; 105 | bool err = false; bool quit = false; 106 | int user_thread = ui->my_thread; 107 | int new_socket_fd = ui->my_socket; 108 | 109 | partial_cmd[0]='\0'; 110 | 111 | //While user inputs invalid connect command, keep reading commands from user 112 | while (true) { 113 | quit = false; 114 | 115 | if (next_cmd) 116 | free(next_cmd); 117 | 118 | next_cmd = get_next_cmd(new_socket_fd, partial_cmd, &err); 119 | 120 | if (err) { 121 | printf("Error reading message from client. Terminating client socket %d\nError: %s\n", new_socket_fd, strerror(errno)); 122 | close(new_socket_fd); 123 | free(ui); 124 | if (next_cmd) 125 | free(next_cmd); 126 | pthread_exit(NULL); 127 | } 128 | else if (next_cmd == NULL) { //Havne't yet received a full command. 129 | continue; 130 | } 131 | else { 132 | printf("Received msg from client: '%s'\n", next_cmd); 133 | trim_str(next_cmd); 134 | nick = parse_connect_cmd(next_cmd, &quit); 135 | } 136 | 137 | if (quit==true) { 138 | printf("Received +QUIT command from client. Terminating client socket %d.\n", new_socket_fd); 139 | close(new_socket_fd); //close socket 140 | free(ui); 141 | free(next_cmd); 142 | pthread_exit(NULL); //free user 143 | } 144 | else if (nick == NULL) { 145 | printf("NULL nick received from client\n"); 146 | write(new_socket_fd, FAIL_CMD, strlen(FAIL_CMD) + 1); 147 | } 148 | else if (user_exists(nick)) { 149 | printf("Nick %s already exists.\n", nick); 150 | sprintf(to_send, "%s Nick already exists. Please try again with a different nick.\n", PRINT_CMD); 151 | write(new_socket_fd, to_send, strlen(to_send) + 1); 152 | free(nick); 153 | } 154 | else if (!valid_charset(nick)) { 155 | printf("Nick %s not in valid charset.\n", nick); 156 | sprintf(to_send, "%s Nick contains invalid chars. \n" 157 | "Must be alphanumeric or '-', '_', '&', '*'.\n ", PRINT_CMD); 158 | write(new_socket_fd, to_send, strlen(to_send) + 1); 159 | free(nick); 160 | } 161 | else { 162 | printf("New user '%s' entering lobby.\n", nick); 163 | free(next_cmd); 164 | init_user(nick, new_socket_fd, user_thread); 165 | } 166 | } 167 | } 168 | 169 | char* parse_connect_cmd(char* cmd, bool* quit) { 170 | 171 | *quit = false; 172 | char* tok1 = strtok(cmd, " "); 173 | char* tok2 = strtok(NULL, " "); 174 | 175 | if (tok1 == NULL) { 176 | printf("Empty msg received from client.\n"); 177 | return NULL; 178 | } 179 | 180 | if (strcmp(tok1, QUIT_CMD) == 0) { 181 | *quit = true; 182 | return NULL; 183 | } 184 | 185 | if (strcmp(tok1, CONNECT_CMD) != 0 || tok2 == NULL) { 186 | return NULL; 187 | } 188 | 189 | else { 190 | char* nick = (char*)malloc(sizeof(char)*(strlen(tok2) + 1)); 191 | strcpy(nick, tok2); 192 | return nick; 193 | } 194 | 195 | } 196 | 197 | void catch_broken_pipe(int signum) { 198 | printf("Pipe broken trying to write to user's socket. Terminating user's thread.\n"); 199 | pthread_exit(NULL); //Quit the thread for this user if a broken pipe occurs 200 | } 201 | -------------------------------------------------------------------------------- /tags: -------------------------------------------------------------------------------- 1 | !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ 2 | !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ 3 | !_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ 4 | !_TAG_PROGRAM_NAME Exuberant Ctags // 5 | !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ 6 | !_TAG_PROGRAM_VERSION 5.9~svn20110310 // 7 | BUFFER_SIZE run_server.c /^const int BUFFER_SIZE = 256; $/;" v 8 | FILES Makefile /^FILES = run_server.c modules\/*.c include\/*.h$/;" m 9 | FLAGS Makefile /^FLAGS = -Wall$/;" m 10 | INPUT Makefile /^INPUT = run_server.c modules\/*.c$/;" m 11 | OUTPUT Makefile /^OUTPUT = run_server$/;" m 12 | main run_server.c /^int main(int argc, char** argv) {$/;" f 13 | --------------------------------------------------------------------------------