aboutsummaryrefslogtreecommitdiffstats
path: root/src/server.cpp
diff options
context:
space:
mode:
authorMatt Strapp <matt@mattstrapp.net>2022-04-25 17:48:52 -0500
committerMatt Strapp <matt@mattstrapp.net>2022-04-25 17:49:31 -0500
commit6889e2d66b710c241b3884fc28610a9e6be4e610 (patch)
tree30aabe5e28a4306c41d7d73a248ed174bd36f0a5 /src/server.cpp
downloadcsci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.gz
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.bz2
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.lz
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.xz
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.zst
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.zip
A
Diffstat (limited to 'src/server.cpp')
-rw-r--r--src/server.cpp796
1 files changed, 796 insertions, 0 deletions
diff --git a/src/server.cpp b/src/server.cpp
new file mode 100644
index 0000000..270a350
--- /dev/null
+++ b/src/server.cpp
@@ -0,0 +1,796 @@
+#include "server.hpp"
+
+#define MAX_CONCURRENCY_LIMIT 40000 // overkill
+#define POLL_TIMEOUT 1000 // Milliseconds
+#define S2C_SERVICE_FREQ 1.0 // Seconds
+#define USER_DATABASE "user_database.txt"
+#define FILE_DATABASE "file_database.txt"
+#define FILES_DIR "files"
+
+int nConns; // total # of data sockets
+struct pollfd
+ peers[MAX_CONCURRENCY_LIMIT + 1]; // sockets to be monitored by poll()
+struct CONN_STAT
+ connStat[MAX_CONCURRENCY_LIMIT + 1]; // app-layer stats of the sockets
+BYTE *buf[MAX_CONCURRENCY_LIMIT + 1];
+
+// Duplicate exists in server and client
+void RemoveConnection(int i) {
+ close(peers[i].fd);
+ if (i < nConns) {
+ memmove(peers + i, peers + i + 1, (nConns - i) * sizeof(struct pollfd));
+ memmove(connStat + i, connStat + i + 1,
+ (nConns - i) * sizeof(struct CONN_STAT));
+ free(buf[i]);
+ memmove(buf + i, buf + i + 1, (nConns - i) * sizeof(BYTE *));
+ }
+ nConns--;
+ Log("[%s] %d active connections after removal", NAME, nConns);
+}
+
+void DoServer(int svrPort) {
+ int maxConcurrency = 20; // TODO: Change this to be a #define
+ int listenFD = socket(AF_INET, SOCK_STREAM, 0);
+ if (listenFD < 0) {
+ Error("[%s] Cannot create listening socket.", NAME);
+ }
+ SetNonBlockIO(listenFD);
+
+ struct sockaddr_in serverAddr;
+ memset(&serverAddr, 0, sizeof(struct sockaddr_in));
+ serverAddr.sin_family = AF_INET;
+ serverAddr.sin_port = htons((unsigned short)svrPort);
+ serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ int optval = 1;
+ int r =
+ setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
+ if (r != 0) {
+ Error("[%s] Cannot enable SO_REUSEADDR option.", NAME);
+ }
+
+ signal(SIGPIPE, SIG_IGN);
+
+ if (bind(listenFD, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0) {
+ Error("[%s] Cannot bind to port %d.", NAME, svrPort);
+ }
+
+ if (listen(listenFD, 16) != 0) {
+ Error("[%s] Cannot listen to port %d.", NAME, svrPort);
+ }
+
+ nConns = 0;
+ memset(peers, 0, sizeof(peers));
+ peers[0].fd = listenFD;
+ peers[0].events = POLLRDNORM;
+ memset(connStat, 0, sizeof(connStat));
+
+ int connID = 0;
+ while (1) {
+ // monitor the listening sock and data socks, nConn+1 in total
+ int nReady = poll(peers, nConns + 1, POLL_TIMEOUT);
+ if (nReady < 0) {
+ Error("[%s] Invalid poll() return value.", NAME);
+ }
+
+ struct sockaddr_in clientAddr;
+ socklen_t clientAddrLen = sizeof(clientAddr);
+
+ struct timeb currentTime;
+ ftime(&currentTime);
+
+ // new incoming connection
+ if ((peers[0].revents & POLLRDNORM) && (nConns < maxConcurrency)) {
+ int fd = accept(listenFD, (struct sockaddr *)&clientAddr, &clientAddrLen);
+ if (fd != -1) {
+ SetNonBlockIO(fd);
+ nConns++;
+ Log("[%s] Accepted connection %d", NAME, nConns);
+ peers[nConns].fd = fd;
+ peers[nConns].events = POLLRDNORM;
+ peers[nConns].revents = 0;
+
+ memset(&connStat[nConns], 0, sizeof(struct CONN_STAT));
+ ftime(&connStat[nConns].lastTime);
+ connStat[nConns].recInitHeader =
+ false; // waiting to recieve initial header
+ connStat[nConns].expectingHeader =
+ true; // message to be read is a header
+ connStat[nConns].nToDo = HEADER_LEN;
+
+ buf[nConns] = (BYTE *)malloc(BUF_LEN);
+ memset(buf[nConns], 0, BUF_LEN);
+ }
+
+ if (--nReady <= 0)
+ continue;
+ }
+
+ for (int i = 1; i <= nConns; i++) {
+ // Log("nConns: %d peers[%d].revents: %02X (POLLRDNORM %02X, POLLWRNORM
+ // %02X, POLLERR %02X, POLLHUP %02X)", nConns, i, peers[i].revents,
+ // POLLRDNORM, POLLWRNORM, POLLERR, POLLHUP);
+ double timeDiff =
+ (currentTime.time + currentTime.millitm / (double)1000.0f) -
+ (connStat[i].lastTime.time +
+ connStat[i].lastTime.millitm / (double)1000.0f);
+
+ if (peers[i].revents &
+ (POLLRDNORM | POLLERR |
+ POLLHUP)) { // TODO: break out POLLERR and POLLHUP?
+ // Log("Servicing connection %d, POLLHUP: %d", i, peers[i].revents &
+ // POLLHUP);
+ // Process initial communication
+ if (connStat[i].recInitHeader == false) {
+ if (Recv_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) <
+ 0) {
+ Log("[%s] Line %d: Removing connection %d", NAME, __LINE__,
+ i);
+ RemoveConnection(i);
+ goto NEXT_CONNECTION;
+ }
+
+ if (connStat[i].nToDo == 0) {
+ Header header;
+ header.decode(buf[i]);
+ // Log("Initial communication for connection %d:", i);
+
+ if (!((header.m_command == CONNECT) | (header.m_command == SENDF) |
+ (header.m_command == SENDF2) | (header.m_command == GETF))) {
+ Log("[%s] WARNING: Unexpected initial connection command %s",
+ NAME, com2str(header.m_command));
+ }
+
+ connStat[i].recInitHeader = true;
+ connStat[i].expectingHeader =
+ true; // flag needed for processing below
+ if (processReception(i) != 0) {
+ Log("[%s] Line %d: Removing connection %d", NAME, __LINE__,
+ i);
+ RemoveConnection(i);
+ goto NEXT_CONNECTION;
+ }
+ }
+ } // End initial communication
+ else { // Standard polling recieve
+ // Log("Standard polling recieve/send: connStat[i].direction: %02X",
+ // connStat[i].direction);
+ if (connStat[i].direction == C2S) {
+ if (Recv_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) <
+ 0) {
+ Log("[%s] Line %d: Removing connection %d", NAME, __LINE__,
+ i);
+ RemoveConnection(i);
+ goto NEXT_CONNECTION;
+ }
+
+ if (connStat[i].nToDo == 0) {
+ if (processReception(i) != 0) {
+ Log("[%s] Line %d: Removing connection %d", NAME,
+ __LINE__, i);
+ RemoveConnection(i);
+ goto NEXT_CONNECTION;
+ }
+ }
+ } // end C2S
+ } // End standard recieve
+ } // if POLLRDNORM
+
+ // a data socket is writable
+ if (peers[i].revents & POLLWRNORM) {
+ // Log("Sending nonblocking!");
+ if (Send_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) <
+ 0) {
+ Log("[%s] Line %d: Removing connection %d", NAME, __LINE__, i);
+ RemoveConnection(i);
+ goto NEXT_CONNECTION;
+ }
+ }
+
+ // a s2c connection hasn't been used in a while? Check that the client is
+ // still there with a ping
+ if ((connStat[i].direction == S2C) && (timeDiff > S2C_SERVICE_FREQ) &&
+ (connStat[i].nToDo == 0) && (connStat[i].messageLen == 0) &&
+ (connStat[i].nBuffered == 0)) {
+ // Log("[%s] Pinging [%s] on connection %d due to timeout", SERVER_NAME,
+ // connStat[i].name, i);
+ Header header;
+ header.setFlags(S2C, SUCCESS, PRIV, SIGN);
+ memcpy(header.m_name, NAME, MAX_USERNAME_LEN);
+ header.m_command = PING;
+ header.m_size = 0;
+
+ header.encode(&buf[i][connStat[i].messageLen]);
+ connStat[i].nBuffered = HEADER_LEN + header.m_size;
+ connStat[i].messageLen = HEADER_LEN + header.m_size;
+ connStat[i].nToDo = HEADER_LEN + header.m_size;
+
+ peers[i].events |= POLLWRNORM;
+ }
+
+ NEXT_CONNECTION:
+ asm("nop");
+ // if (--nReady <= 0) break;
+ } // for
+ } // while
+} // do_server
+
+// buf has all buffers
+// Stat has all stat structs
+// i is the index of the link being processed
+int processReception(int i) {
+ Header header;
+ header.decode(buf[i]);
+ // header.displayContents(true);
+
+ if (connStat[i]
+ .expectingHeader) { // Expecting a header read sequence was completed
+ // Log("\nProcessing header reception. header.m_size: %d", header.m_size);
+ if (header.m_size > 0) {
+ connStat[i].expectingHeader = false;
+ connStat[i].nToDo = header.m_size;
+ } else if (header.m_size == 0) {
+ // printServerCommand(i);
+ doServerCommand(i);
+ connStat[i].expectingHeader = true;
+ connStat[i].nBuffered = 0;
+ connStat[i].nToDo = HEADER_LEN;
+ memset(buf[i], 0, BUF_LEN);
+ } else {
+ return -1; // Error, signal to caller to end connection
+ }
+ } else { // expecting a data read sequence was completed
+ // Log("\nProcessing data reception.");
+ // printServerCommand(i);
+ doServerCommand(i);
+ if (connStat[i].direction == C2S) { // CONNECT events for S2C can get here
+ connStat[i].expectingHeader = true;
+ connStat[i].nBuffered = 0;
+ connStat[i].nToDo = HEADER_LEN;
+ memset(buf[i], 0, BUF_LEN);
+ }
+ }
+
+ if (connStat[i].shouldClose) {
+ Log("[%s] Line %d: Removing connection %d due to shouldClose", NAME,
+ __LINE__, i);
+ RemoveConnection(i);
+ }
+
+ return 0;
+}
+
+void doServerCommand(int i) {
+ // Assume the buf[i] has a message at the beginning of the buffers
+ Header header;
+ header.decode(buf[i]);
+
+ struct timeb currentTime;
+ ftime(&currentTime);
+
+ char message[5000] = {0};
+ char username[20] = {0};
+ char password[20] = {0};
+ Header outHeader;
+ int numLoggedIn = 0;
+ bool foundActiveUser = false;
+ int s2cConnNumb = -1;
+ char server_filename[5000] = {0};
+ char filename[5000] = {0};
+ int filenameLen, fileLen, offset, nextFileId, fileId;
+ BYTE intbuf[5] = {0};
+ FILE *fp;
+
+ switch (header.m_command) {
+ case CONNECT:
+ buf2i(&buf[i][HEADER_LEN], connStat[i].id);
+ connStat[i].direction = header.m_direction;
+ connStat[i].linkType = MESSAGE_LINK;
+ connStat[i].lastTime = currentTime;
+ if (header.m_direction == C2S) {
+ Log("[---- : %s] id: %d, direction: C2S, connection number: %d.",
+ com2str(header.m_command), connStat[i].id, i);
+ connStat[i].expectingHeader = true;
+ connStat[i].nBuffered = 0;
+ connStat[i].nToDo = HEADER_LEN;
+ } else {
+ Log("[---- : %s] id: %d, direction: S2C, connection number: %d.",
+ com2str(header.m_command), connStat[i].id, i);
+ connStat[i].nBuffered = 0;
+ connStat[i].messageLen = 0;
+ connStat[i].nToDo = 0;
+ peers[i].events &= ~POLLRDNORM;
+ peers[i].events &= ~POLLWRNORM;
+ }
+ memset(buf[i], 0, BUF_LEN);
+ break;
+ case REGISTER:
+ memcpy(username, header.m_name, MAX_USERNAME_LEN);
+ memcpy(password, &buf[i][HEADER_LEN], header.m_size);
+ if (usernameQuery(USER_DATABASE, username) == true) {
+ Log("[---- : %s] Registration failed. Username [%s] is already "
+ "registered.",
+ com2str(header.m_command), username);
+ outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+ outHeader.m_command = REGISTER;
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ strcat(message, "Cannot register username [");
+ strcat(message, username);
+ strcat(message, "]. Username already registered.\n");
+ strcat(message,
+ "\tUsernames may only contain characters a-z, A-Z, or 0-9.\n");
+ strcat(message, "\tUsernames may be 4 to 8 characters long.");
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, false, outHeader, message);
+ } else {
+ Log("[---- : %s] Successful registration (usr, pswd): %s %s",
+ com2str(header.m_command), username, password);
+ recordEntry(USER_DATABASE, username, password);
+ outHeader.setFlags(S2C, SUCCESS, PUB, SIGN);
+ outHeader.m_command = REGISTER;
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ strcat(message, "Successfully registered ");
+ strcat(message, username);
+ strcat(message, ".");
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, false, outHeader, message);
+ }
+ break;
+ case LOGIN:
+ memcpy(username, header.m_name, MAX_USERNAME_LEN);
+ memcpy(password, &buf[i][HEADER_LEN], header.m_size);
+ if (loginQuery(USER_DATABASE, username, password) == true) {
+ // Check that user is not currently logged in
+ for (int x = 1; x <= nConns; x++) {
+ if ((connStat[x].isLoggedIn == true) &&
+ (strcmp(username, connStat[x].name) == 0)) {
+ Log("[%s : %s] Login failed. User is already logged in.",
+ header.m_name, com2str(header.m_command));
+ foundActiveUser = true;
+ outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+ outHeader.m_command = LOGIN;
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ strcat(message, "Login failed. ");
+ strcat(message, username);
+ strcat(message, " is logged in on another device.");
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, false, outHeader, message);
+ break;
+ }
+ }
+
+ if (!foundActiveUser) {
+ for (int x = 1; x <= nConns; x++) {
+ if (connStat[i].id == connStat[x].id) {
+ memcpy(connStat[x].name, username, MAX_USERNAME_LEN);
+ // Log("Connection %d (%s) is loggin in.", x, connStat[x].name);
+ connStat[x].isLoggedIn = true;
+ }
+ }
+ Log("[%s : %s] Successful login (usr, pswd): %s %s", connStat[i].name,
+ com2str(header.m_command), username, password);
+ outHeader.setFlags(S2C, SUCCESS, PUB, SIGN);
+ outHeader.m_command = LOGIN;
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ strcat(message, "Welcome ");
+ strcat(message, username);
+ strcat(message, ".");
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, true, outHeader, message);
+ }
+ } else {
+ Log("[---- : %s] Login failed (usr, pswd): %s %s",
+ com2str(header.m_command), username, password);
+ outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+ outHeader.m_command = LOGIN;
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ strcat(message, "Login failed: Invalid username or password.");
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, false, outHeader, message);
+ }
+ break;
+ case LOGOUT:
+ if (connStat[i].isLoggedIn) {
+ for (int x = 1; x <= nConns; x++) {
+ if (connStat[i].id == connStat[x].id) {
+ // Log("Connection %d (user %s) is logging off.", x,
+ // connStat[x].name);
+ connStat[x].isLoggedIn = false;
+ foundActiveUser = true;
+ }
+ }
+ Log("[%s : %s] Successful logout.", connStat[i].name,
+ com2str(header.m_command));
+ outHeader.setFlags(S2C, SUCCESS, PUB, SIGN);
+ outHeader.m_command = LOGOUT;
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ strcat(message, "Goodbye ");
+ strcat(message, connStat[i].name);
+ strcat(message, ". Come back soon.");
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, false, outHeader, message);
+ } else {
+ Log("[---- : %s] Failed: Connection %d is not logged in.",
+ com2str(header.m_command), i);
+ outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+ outHeader.m_command = LOGOUT;
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ strcat(message, "Logout failed. You are not logged in.");
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, false, outHeader, message);
+ }
+ break;
+ case SEND:
+ case SENDA:
+ case SEND2:
+ case SENDA2:
+ if (connStat[i].isLoggedIn) {
+ outHeader.setFlags(S2C, SUCCESS, PUB, header.m_trace);
+ outHeader.m_command = header.m_command;
+ if (header.m_trace == SIGN)
+ memcpy(outHeader.m_name, connStat[i].name, strlen(connStat[i].name));
+ else
+ memcpy(outHeader.m_name, ANON, strlen(ANON));
+ memcpy(message, &buf[i][HEADER_LEN], header.m_size);
+ outHeader.m_size = strlen(message);
+ if ((header.m_command == SEND) || (header.m_command == SENDA)) {
+ Log("[%s : %s] %s", connStat[i].name, com2str(header.m_command),
+ message);
+ sendMessageToAllLoggedIn(outHeader, message);
+ } else {
+ memcpy(username, header.m_name, MAX_USERNAME_LEN);
+ Log("[%s : %s %s] %s", connStat[i].name, com2str(header.m_command),
+ username, message);
+ sendMessageToName(username, outHeader, message);
+ }
+ } else {
+ Log("[%s : %s] Failed: Connection %d is not logged in.", connStat[i].name,
+ com2str(header.m_command), i);
+ outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+ outHeader.m_command = header.m_command;
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ strcat(message, "Users must be logged in to '");
+ strcat(message, com2str(header.m_command));
+ strcat(message, "'. Login with '");
+ strcat(message, com2str(LOGIN));
+ strcat(message, " [username] [password]'.");
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, false, outHeader, message);
+ }
+ break;
+ case SENDF: // Initial connection
+ case SENDF2: // Initial connection
+ buf2i(&buf[i][HEADER_LEN], connStat[i].id);
+ // Log("This connection's id: %d", connStat[i].id);
+ connStat[i].direction = header.m_direction;
+ connStat[i].linkType = FILE_LINK;
+ connStat[i].lastTime = currentTime;
+ connStat[i].expectingHeader = true;
+ connStat[i].nBuffered = 0;
+ connStat[i].nToDo = HEADER_LEN;
+ connStat[i].shouldClose =
+ true; // flag to close this connection once it's processed
+
+ for (int x = 1; x <= nConns; x++) {
+ if ((connStat[x].direction == C2S) &&
+ (connStat[x].linkType == MESSAGE_LINK) &&
+ (connStat[x].id == connStat[i].id)) {
+ strcat(connStat[i].name, connStat[x].name);
+ connStat[i].isLoggedIn = connStat[x].isLoggedIn;
+ foundActiveUser = true;
+ s2cConnNumb = x;
+ break;
+ }
+ }
+
+ if (s2cConnNumb == -1) {
+ Log("Enexpected state. Recieved %s on new connection without S2C "
+ "connection for messages.",
+ com2str(header.m_command));
+ } else if (!connStat[i].isLoggedIn) {
+ Log("[%s : %s] Failed: Connection %d is not logged in.", connStat[i].name,
+ com2str(header.m_command), i);
+ outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+ outHeader.m_command = SEND; // avoid file transfer logic on client side
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ strcat(message, "Users must be logged in to '");
+ strcat(message, com2str(header.m_command));
+ strcat(message, "'. Login with '");
+ strcat(message, com2str(LOGIN));
+ strcat(message, " [username] [password]'.");
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, false, outHeader, message);
+ } else {
+ // Save the file
+ nextFileId = getNumEntries(FILE_DATABASE);
+ if (nextFileId == -1) {
+ Log("Enexpected state. %s next File Id of -1.", FILE_DATABASE);
+ } else {
+ snprintf(server_filename, sizeof(server_filename), "%s/%d_", FILES_DIR,
+ nextFileId);
+ offset = HEADER_LEN + 4;
+ buf2i(&buf[i][offset], filenameLen); // first 4 bytes is client id
+ offset += 4;
+ memcpy(&server_filename[strlen(server_filename)], &buf[i][offset],
+ filenameLen);
+ memcpy(filename, &buf[i][offset], filenameLen);
+ offset += filenameLen;
+ buf2i(&buf[i][offset], fileLen);
+ offset += 4;
+ // Log("[%s : %s] Saving file: %s", server_filename);
+ fp = fopen(FILE_DATABASE, "a");
+ fputs(server_filename, fp);
+ fputs("\n", fp);
+ fclose(fp);
+ buf2file(&buf[i][offset], fileLen, server_filename);
+
+ // Send SENDF/SENDF2 message to interested parties
+ outHeader.setFlags(S2C, SUCCESS, header.m_recipient,
+ SIGN); // will update recipient as necessary
+ outHeader.m_command = header.m_command;
+ memcpy(outHeader.m_name, connStat[i].name, strlen(connStat[i].name));
+ i2buf(nextFileId, intbuf);
+ memcpy(message, intbuf, 4);
+ offset = 4;
+ memcpy(&message[offset], &buf[i][HEADER_LEN + 8], filenameLen);
+ offset += filenameLen;
+ outHeader.m_size = offset;
+
+ if (header.m_command == SENDF) {
+ Log("[%s : %s] %s", connStat[i].name, com2str(header.m_command),
+ filename);
+ sendMessageToAllLoggedInExceptSender(connStat[i].id, outHeader,
+ message);
+ } else {
+ memcpy(username, header.m_name, MAX_USERNAME_LEN);
+ Log("[%s : %s %s] %s", connStat[i].name, com2str(header.m_command),
+ username, filename);
+ sendMessageToName(username, outHeader, message);
+ }
+ }
+ }
+ break;
+ case GETF:
+ buf2i(&buf[i][HEADER_LEN], connStat[i].id);
+ // header name is origional sender. Find client's name with client id
+ for (int x = 1; x <= nConns; x++) {
+ if ((connStat[i].id == connStat[x].id) &&
+ (connStat[x].direction == C2S) &&
+ (connStat[x].linkType == MESSAGE_LINK)) {
+ memcpy(connStat[i].name, connStat[x].name, MAX_USERNAME_LEN);
+ break;
+ }
+ }
+ buf2i(&buf[i][HEADER_LEN + 4], fileId);
+ connStat[i].direction = S2C;
+ connStat[i].linkType = FILE_LINK;
+ connStat[i].lastTime = currentTime;
+ // no need for shouldClose. Client will close connection and periodic ping will
+ // detect closure
+
+ snprintf(server_filename, sizeof(server_filename), "%s/%d_", FILES_DIR,
+ fileId);
+ memcpy(&server_filename[strlen(server_filename)], &buf[i][HEADER_LEN + 8],
+ header.m_size - 8);
+
+ memcpy(filename, &buf[i][HEADER_LEN + 8], header.m_size - 8);
+ filenameLen = strlen(filename);
+
+ // Log("Getting file %s", server_filename);
+ memset(buf[i], 0, BUF_LEN);
+
+ // Check that the file exists
+ fp = fopen(server_filename, "r");
+ if (fp) {
+ fclose(fp);
+ outHeader.setFlags(S2C, SUCCESS, header.m_recipient,
+ SIGN); // will update recipient as necessary
+ if (header.m_command == SENDF2)
+ outHeader.m_recipient = PRIV;
+ outHeader.m_command = header.m_command;
+ memcpy(outHeader.m_name, header.m_name,
+ MAX_USERNAME_LEN); // pass along origional sender
+ offset = HEADER_LEN;
+ i2buf(filenameLen, &buf[i][offset]); // encode filename length
+ offset += 4;
+ memcpy(&buf[i][offset], filename,
+ strlen(filename)); // encode the filename
+ offset += strlen(filename);
+ fileLen = file2buf(server_filename, &buf[i][offset + 4]);
+ i2buf(fileLen, &buf[i][offset]);
+ offset += 4 + fileLen;
+ outHeader.m_size = offset - HEADER_LEN;
+ outHeader.encode(buf[i]);
+
+ connStat[i].nBuffered = offset;
+ connStat[i].messageLen = offset;
+ connStat[i].nToDo = offset;
+ peers[i].events &= ~POLLRDNORM;
+ peers[i].events |= POLLWRNORM;
+
+ Log("[%s : %s] %s", connStat[i].name, com2str(header.m_command),
+ filename);
+ } else {
+ Log("File not found: %s", filename);
+ // TODO: Send message if file is not found
+ }
+ break;
+ case LIST:
+ Log("[%s : %s]", connStat[i].name, com2str(header.m_command));
+ outHeader.setFlags(S2C, SUCCESS, PUB, SIGN);
+ outHeader.m_command = LIST;
+ memcpy(outHeader.m_name, NAME, strlen(NAME));
+ for (int x = 1; x <= nConns; x++) {
+ if ((connStat[x].direction == C2S) &&
+ (connStat[x].linkType == MESSAGE_LINK) && (connStat[x].isLoggedIn)) {
+ numLoggedIn++;
+ }
+ }
+ snprintf(message, sizeof(message), "%d", numLoggedIn);
+ strcat(message, " user(s) currently logged in:");
+ for (int x = 1; x <= nConns; x++) {
+ if ((connStat[x].direction == C2S) &&
+ (connStat[x].linkType == MESSAGE_LINK) && (connStat[x].isLoggedIn)) {
+ strcat(message, "\n\t");
+ strcat(message, connStat[x].name);
+ }
+ }
+ outHeader.m_size = strlen(message);
+ sendMessageToId(connStat[i].id, false, outHeader, message);
+ break;
+ case PING:
+ // Log("Ping from %s", header.m_name);
+ asm("nop"); // do nothing
+ break;
+ default:
+ Log("No doServerCommand() for command %s", com2str(header.m_command));
+ }
+}
+
+// Send the message to every S2C with the specified id. If the reqLoggedIn
+// flag is set, require that the connection is logged in.
+void sendMessageToId(int id, bool reqLoggedIn, Header header, char *message) {
+ for (int i = 1; i <= nConns; i++) {
+ if ((connStat[i].direction == S2C) && (id == connStat[i].id)) {
+ if (reqLoggedIn) {
+ if (connStat[i].isLoggedIn) {
+ prepareMessage(i, header, message);
+ }
+ } else {
+ prepareMessage(i, header, message);
+ }
+ }
+ }
+}
+
+// Send the message to every S2C which is logged in.
+void sendMessageToAllLoggedIn(Header header, char *message) {
+ for (int i = 1; i <= nConns; i++) {
+ if ((connStat[i].direction == S2C) && connStat[i].isLoggedIn) {
+ prepareMessage(i, header, message);
+ }
+ }
+}
+
+// Send the message to every s2cConnNumb
+void sendMessageToAllLoggedInExceptSender(int senderID, Header header,
+ char *message) {
+ for (int i = 1; i <= nConns; i++) {
+ if ((connStat[i].direction == S2C) && connStat[i].isLoggedIn &&
+ (connStat[i].id != senderID)) {
+ prepareMessage(i, header, message);
+ }
+ }
+}
+
+// Send the message to all loged in users with the specified name.
+void sendMessageToName(char *name, Header header, char *message) {
+ for (int i = 1; i <= nConns; i++) {
+ if ((connStat[i].direction == S2C) && connStat[i].isLoggedIn &&
+ (strcmp(name, connStat[i].name) == 0)) {
+ prepareMessage(i, header, message);
+ }
+ }
+}
+
+// Load a message into a connection
+void prepareMessage(int i, Header header, char *message) {
+ if (connStat[i].direction == S2C) {
+ int offset = connStat[i].nBuffered;
+
+ // Log("Preparing message for connection %d", i);
+ // header.displayContents(true);
+ // Log("Message: |%s|", message);
+ // Log("offset: %d, nBuffered: %d, nToDo: %d: messageLen: %d", offset,
+ // connStat[i].nBuffered, connStat[i].nToDo, connStat[i].messageLen);
+
+ header.encode(&buf[i][offset]);
+ memcpy(&buf[i][offset + HEADER_LEN], message, header.m_size);
+
+ connStat[i].nBuffered += HEADER_LEN + header.m_size;
+ if (connStat[i].nToDo == 0) {
+ connStat[i].messageLen = HEADER_LEN + header.m_size;
+ connStat[i].nToDo = HEADER_LEN + header.m_size;
+ }
+
+ peers[i].events |= POLLWRNORM;
+ } else {
+ Log("[%s] WARNING: attempted to write to C2S connection %d", NAME,
+ i);
+ }
+}
+
+void printServerCommand(int i) {
+ Header h;
+ h.decode(buf[i]);
+
+ Log("[%s]: [%s, %s, %s, %s] [%s] [%s] [%d]", connStat[i].name,
+ !h.m_direction ? "C2S" : "S2C", !h.m_flag ? "SUCCESS" : "FAIL",
+ !h.m_recipient ? "PUB" : "PRIV", !h.m_trace ? "SIGN" : "ANNON",
+ com2str(h.m_command), h.m_name, h.m_size);
+
+ if (h.m_size > 0)
+ Log("\tData: [%s]", buf[i] + HEADER_LEN);
+}
+
+int main(int argc, char **argv) {
+ if (argc != 2) {
+ Log("Usage: %s reset", argv[0]);
+ Log("Usage: %s [server Port]", argv[0]);
+ return -1;
+ }
+
+ if (strcmp("reset", argv[1]) == 0) {
+ Log("Resetting server databases");
+ clearDatabase(USER_DATABASE);
+ clearDatabase(FILE_DATABASE);
+
+ int status;
+ pid_t cpid;
+
+ if ((cpid = fork()) == -1) {
+ perror("fork");
+ return 1;
+ }
+
+ if (cpid == 0) { // Child process executes "rm -rf users"
+ char command[40] = "rm -rf files"; // only way this worked
+ char *args[5] = {NULL};
+ args[0] = strtok(command, " ");
+ for (int i = 1; i < 3; i++)
+ args[i] = strtok(NULL, " ");
+ execvp(args[0], args);
+ return 0;
+ } else { // Parent process waits for "rm -rf users" to finish
+ pid_t rpid;
+ int status;
+ if ((rpid = wait(NULL)) == -1)
+ perror("wait");
+ else {
+ Log("Clearing file storage");
+ if (mkdir(FILES_DIR, S_IRWXU | S_IRWXG | S_IRWXO) != 0) {
+ perror("mkdir");
+ return -1;
+ }
+ }
+ return 0;
+ }
+ }
+
+ // Make files directory if it doesn't exist
+ DIR *pDir;
+ struct stat s;
+
+ if (stat("files", &s) != 0) {
+ // The files directory doesn't exist
+ if (mkdir("files", S_IRWXU | S_IRWXG | S_IRWXO) != 0) {
+ perror("mkdir");
+ return -1;
+ }
+ }
+
+ int port = atoi(argv[1]);
+ DoServer(port);
+
+ return 0;
+}