Skip to content

Instantly share code, notes, and snippets.

@SeungheonOh
Created March 19, 2020 02:25
Show Gist options
  • Save SeungheonOh/fe7f34e394b23064a64b20226729519a to your computer and use it in GitHub Desktop.
Save SeungheonOh/fe7f34e394b23064a64b20226729519a to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#define LISTENQ 1 // maximum length of queue of pending connection
#define MAXEVENTS 5 // maximum event at a time
#define MAXLINE 4069 //maximum characters for each line
#define MAXCONNECTIONS 5
#define UNNAMED_USER "unnamed_user"
#define UNNAMED_CHANNEL "*"
#define UNNAMED_HOST "unknown_host"
// util.c
void string_foreach(char *s, int (*func)(int)) {
do
*s = (*func)(*s);
while(*s++);
}
void str_append(char *s, char *s1) {
s = realloc(s, sizeof(char) * (strlen(s) + strlen(s1)));
strcat(s, s1);
}
void str_set(char *d, char *s) {
d = realloc(d, sizeof(char) * strlen(s));
strcpy(d, s);
}
// user.c
typedef struct user_t {
char *nick;
char *hostname;
char *channel;
int id;
int socket_fd;
} user_t;
typedef struct user_list_t {
int size;
user_t *list;
} user_list_t;
void user_list_init(user_list_t *l, int size) {
l->list = malloc(size * sizeof(user_t));
memset(l->list, 0, sizeof(*l->list));
l->size = size;
}
void user_list_free(user_list_t *l) {
free(l->list);
}
int user_append_user(user_list_t *l, user_t user) {
user_t *lp = l->list;
while(lp->socket_fd) ++lp;
if(lp - l->list >= l->size) return -1; // if it goes over the limit
*lp = user;
lp->id = lp - l->list;
return 0;
}
int user_add(user_list_t *l, char *nick, char *hostname, char *channel, int socket_fd) {
user_t user;
user.nick = malloc(1),
user.hostname = malloc(1),
user.channel = malloc(1),
user.socket_fd = socket_fd;
str_set(user.nick, nick);
str_set(user.hostname, hostname);
str_set(user.channel, channel);
return user_append_user(l, user);
}
int user_remove(user_list_t *l, int id) {
if(id >= l->size) return -1;
l->list[id] = (user_t){0};
return 0;
}
user_t *get_user_by_id(user_list_t *l, int id) {
if(id >= l->size) return NULL;
return &l->list[id];
}
user_t *get_user_by_nick(user_list_t *l, char* nick) {
for(int i = 0; i < l->size; i++){
if(l->list[i].nick != NULL && strcmp(l->list[i].nick, nick) == 0)
return &l->list[i];
}
return NULL;
}
user_t *get_user_by_socket_fd(user_list_t *l, int sfd) {
for(int i = 0; i < l->size; i++)
if(l->list[i].socket_fd == sfd)
return &l->list[i];
return NULL;
}
int make_nonblock(int fd) {
int flags;
if((flags = fcntl(fd, F_GETFL, 0)) == -1) return -1;
flags |= O_NONBLOCK;
if(fcntl(fd, F_SETFL, flags) == -1) return -1;
return 0;
}
void send_to_channel(user_list_t *l, char *channel, char *message) {
for(int i = 0; i < l->size; i++)
if(l->list[i].channel != NULL && strcmp(l->list[i].channel, channel) == 0)
write(l->list[i].socket_fd, message, strlen(message));
}
void send_to_channel_excluding(user_list_t *l, user_t *exclusion, char *channel, char *message) {
for(int i = 0; i < l->size; i++){
if(l->list[i].channel != NULL && strcmp(l->list[i].channel, channel) == 0
&& exclusion->id != i){
printf("::::::sending to %s\n", l->list[i].nick);
write(l->list[i].socket_fd, message, strlen(message));
}
}
}
// Server
int init_server(int port) {
int server_socket; // server fd
struct sockaddr_in server_address;
// Open socket
if((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // -1 on failure
fprintf(stderr, "Failed to open socket\n");
return -1;
}
// Configure socket address to bind
bzero(&server_address, sizeof(server_address)); // clear struct first
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(port);
if(bind(server_socket, (struct sockaddr *) &server_address, sizeof(server_address)) == -1) { // Bind address to socket
fprintf(stderr, "Failed to bind socket\n");
return -1;
}
// Start listen from socket
if(listen(server_socket, LISTENQ) == -1) {
fprintf(stderr, "Failed to listen\n");
return -1;
}
make_nonblock(server_socket);
return server_socket;
}
int main() {
// server init
int server_socket = init_server(6667);
if(server_socket == -1) {
printf("failed to init");
exit(2);
}
// epoll init
int epoll_fd;
struct epoll_event event;
struct epoll_event *events;
if((epoll_fd = epoll_create1(0)) == -1) { // create epoll
fprintf(stderr, "Failed to create epoll\n");
exit(2);
}
event.data.fd = server_socket;
event.events = EPOLLIN | EPOLLET;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1) {
fprintf(stderr, "Failed to add server socket to epoll\n");
exit(2);
}
events = calloc(MAXEVENTS, sizeof(event));
// user list init
user_list_t users;
user_list_init(&users, MAXCONNECTIONS);
while(1) {
int events_number = epoll_wait(epoll_fd, events, MAXEVENTS, -1);
for(int i = 0; i < events_number; i++) {
if((events[i].events & EPOLLERR) ||
(events[i].events & EPOLLHUP) //||
/*(!(events[i].events & EPOLLIN))*/){ // Unknown event or error
fprintf(stderr, "epoll error\n");
user_remove(&users, get_user_by_socket_fd(&users, events[i].data.fd)->id);
close(events[i].data.fd);
continue;
} else if (events[i].data.fd == server_socket) { // if the events if from the server socket
struct sockaddr_in client_address;
socklen_t client_socket_length = sizeof(client_address);
int client_socket;
if(( client_socket
= accept(server_socket, (struct sockaddr *)&client_address, &client_socket_length)) == -1) { // accept pending connection request
fprintf(stderr, "Failed to accept request\n");
exit(2);
}
make_nonblock(client_socket);
event.data.fd = client_socket;
event.events = EPOLLIN | EPOLLOUT | EPOLLET;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) == -1) {
fprintf(stderr, "Failed to add client socket to epoll");
continue;
}
printf("New Client added\n");
printf("Hostname: %s\n", inet_ntoa(client_address.sin_addr));
user_add(&users, UNNAMED_USER, inet_ntoa(client_address.sin_addr), UNNAMED_CHANNEL, client_socket);
for(int i = 0; i < users.size; i++) {
printf(":%s \n", users.list[i].nick);
}
fflush(stdout);
} else {
user_t *user = get_user_by_socket_fd(&users, events[i].data.fd);
char lines[MAXLINE+1] = "\0";
if(read(events[i].data.fd, lines, MAXLINE) <= 0) continue;
char *rest = lines; //rest of lines
char *line;
while((line = strtok_r(rest , "\n", &rest))) {
printf("=====Command recived=====\n");
char message[MAXLINE] = "\0";
char *command = strtok(line, " ");
string_foreach(command, &toupper);
printf("COMAND: %s\n", command);
if(strcmp(command, "NICK") == 0) {
char *desired_nick = strtok(NULL, " \r\n");
if(get_user_by_nick(&users, desired_nick) != NULL) {
// When its taken by other user
sprintf(message, ":%s!%s %s %s %s\n", user->nick, user->hostname, "433", desired_nick, desired_nick);
} else if (desired_nick == NULL) {
// When no nickname is given
sprintf(message, ":%s!%s %s%s\n", user->nick, user->hostname, "431", " :No nickname given\n");
} else {
// Setting new nickname
sprintf(message, ":%s!%s NICK :%s\n", user->nick, user->hostname, desired_nick);
str_set(user->nick, desired_nick);
send_to_channel(&users, user->channel, message);
printf("Success: changed the nickname\n");
continue;
}
write(events[i].data.fd, message, strlen(message));
if(strcmp(user->channel, UNNAMED_CHANNEL) != 0) {
}
} else if(strcmp(command, "USER") == 0) {
sprintf(message, ":server NOTICE * :*** WELCOME!\n:server NOTICE * :*** This is test!\n:server NOTICE * :*** You are %s at %s\n",
user->nick, user->hostname);
write(events[i].data.fd, message, strlen(message));
} else if(strcmp(command, "JOIN") == 0) {
printf("JOIN\n");
char *channel = strtok(NULL, " \r\n");
sprintf(message, ":%s!%s JOIN #%s\n", user->nick, user->hostname, channel);
str_set(user->channel, channel);
printf("channel: %s\n", channel);
write(events[i].data.fd, message, strlen(message));
} else if(strcmp(command, "PRIVMSG") == 0) {
char *m = strtok(NULL, ":");
char *message_sum = malloc(1);
m = strtok(NULL, ":");
while(m != NULL){
str_append(message_sum, m);
m = strtok(NULL, ":");
}
sprintf(message, ":%s!%s PRIVMSG #%s :%s\n", user->nick, user->hostname, user->channel, message_sum);
send_to_channel_excluding(&users, user, user->channel, message);
for(int i = 0; i < users.size; i++) {
printf("nick; %s channel: %s\n", users.list[i].nick, users.list[i].channel);
}
free(message_sum);
} else if (strcmp(command, "PING") == 0) {
sprintf(message, "%s%s\n", ":server PONG server :", strtok(NULL, " \n\r"));
printf("%s\n", message);
write(user->socket_fd, message, strlen(message));
} else {
printf("unknown command: %s\n", command);
}
printf("=====Command Ended=====\n");
fflush(stdout);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment