diff --git a/servers/telnet_pit.c b/servers/telnet_pit.c index ebd2013..4a228d1 100644 --- a/servers/telnet_pit.c +++ b/servers/telnet_pit.c @@ -13,10 +13,6 @@ #include #include "../shared/structs.h" -// #define PORT 23 -// #define DELAY_MS 100 -// #define HEARTBEAT_INTERVAL_MS 600000 // 10 minutes -// #define FD_LIMIT 4096 #define SERVER_ID "Telnet" #define IAC 255 @@ -29,7 +25,6 @@ int port; int delay; int maxNoClients; -// Telnet negotiation options unsigned char negotiations[][3] = { {IAC, WILL, 1}, {IAC, DO, 3}, @@ -40,133 +35,213 @@ unsigned char negotiations[][3] = { }; int num_options = sizeof(negotiations) / sizeof(negotiations[0]); -// void heartbeatLog() { -// syslog(LOG_INFO, "Server is running with %d connected clients. Number of most concurrent connected clients is %d", clientQueueTelnet.length, statsTelnet.mostConcurrentConnections); -// syslog(LOG_INFO, "Current statistics: wasted time: %lld ms. Total connected clients: %ld", statsTelnet.totalWastedTime, statsTelnet.totalConnects); -// } - void initializeStats(){ statsTelnet.totalConnects = 0; statsTelnet.totalWastedTime = 0; statsTelnet.mostConcurrentConnections = 0; } +// ── Buffered read helper ───────────────────────────────────────────────────── +// Reads available bytes into c->inputBuf, echoes printable chars back. +// Returns 1 when Enter is pressed and buffer has content, 0 otherwise. +static int readUntilEnter(struct telnetAndUpnpClient *c) { + char chunk[256]; + ssize_t n = read(c->fd, chunk, sizeof(chunk) - 1); + + if (n <= 0) return 0; + + for (int i = 0; i < n; i++) { + unsigned char ch = chunk[i]; + + // Skip IAC negotiation sequences (3 bytes: IAC + cmd + option) + if (ch == IAC) { i += 2; continue; } + + // Skip non-printable, non-CR/LF bytes + if (ch < 32 && ch != '\r' && ch != '\n') continue; + + // Echo printable chars back so attacker sees what they type + if (ch >= 32 && ch <= 126) { + write(c->fd, &ch, 1); + } + + if (ch == '\r' || ch == '\n') { + if (c->inputBufLen > 0) { + c->inputBuf[c->inputBufLen] = '\0'; + c->inputBufLen = 0; + return 1; // Enter pressed with content + } + } else { + if (c->inputBufLen < (int)sizeof(c->inputBuf) - 1) { + c->inputBuf[c->inputBufLen++] = ch; + } + } + } + return 0; +} + +// ── Log credentials to file ────────────────────────────────────────────────── +static void logCredentials(const char *ip, const char *username, const char *password) { + FILE *f = fopen("credentials.log", "a"); + if (!f) return; + time_t t = time(NULL); + char timeStr[64]; + strncpy(timeStr, ctime(&t), sizeof(timeStr) - 1); + timeStr[strcspn(timeStr, "\n")] = '\0'; // strip trailing newline + fprintf(f, "[%s] ip=%s user=%s pass=%s\n", timeStr, ip, username, password); + fclose(f); +} + int main(int argc, char *argv[]) { setbuf(stdout, NULL); - - // testing - // char msg[256]; - // snprintf(msg, sizeof(msg), "%s connect %s\n", - // SERVER_ID, "82.211.213.247"); - // fprintf(stderr, "%s", msg); - // sendMetric(msg); + (void)argc; - port = atoi(argv[1]); - delay = atoi(argv[2]); - maxNoClients = atoi(argv[3]); + port = atoi(argv[1]); + delay = atoi(argv[2]); + maxNoClients = atoi(argv[3]); initializeStats(); setFdLimit(maxNoClients); - signal(SIGPIPE, SIG_IGN); // Ignore + signal(SIGPIPE, SIG_IGN); queue_init(&clientQueueTelnet); - + int serverSock = createServer(port); if (serverSock < 0) { fprintf(stderr, "Invalid server socket fd: %d", serverSock); exit(EXIT_FAILURE); } - + struct sockaddr_in clientAddr; socklen_t addrLen = sizeof(clientAddr); - + struct pollfd fds; memset(&fds, 0, sizeof(fds)); - fds.fd = serverSock; + fds.fd = serverSock; fds.events = POLLIN; - - // long long lastHeartbeat = currentTimeMs(); - while (1) { - long long now = currentTimeMs(); - int timeout = -1; - // if (now - lastHeartbeat >= HEARTBEAT_INTERVAL_MS) { - // heartbeatLog(); - // lastHeartbeat = now; - // } + while (1) { + long long now = currentTimeMs(); + int timeout = -1; - // Process clients in queue + // ── Process clients in queue ───────────────────────────────────────── while (clientQueueTelnet.head) { - if(clientQueueTelnet.head->sendNext <= now){ - struct baseClient *bc = queue_pop(&clientQueueTelnet); - struct telnetAndUpnpClient *c = (struct telnetAndUpnpClient *)bc; - - int optionIndex = rand() % num_options; - ssize_t out = write(c->fd, negotiations[optionIndex], sizeof(negotiations[optionIndex])); - - if (out == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { // Avoid blocking - c->base.sendNext = now + delay; - c->base.timeConnected += delay; - statsTelnet.totalWastedTime += delay; - queue_append(&clientQueueTelnet, (struct baseClient *)c); - } else { - long long timeTrapped = c->base.timeConnected; - char msg[256]; - snprintf(msg, sizeof(msg), "%s disconnect %s %lld\n", - SERVER_ID, c->base.ipaddr, timeTrapped); - printf("%s", msg); - sendMetric(msg); - close(c->fd); - free(c); - } - } else { - c->base.sendNext = now + delay; + if (clientQueueTelnet.head->sendNext > now) { + timeout = clientQueueTelnet.head->sendNext - now; + break; + } + + struct baseClient *bc = queue_pop(&clientQueueTelnet); + struct telnetAndUpnpClient *c = (struct telnetAndUpnpClient *)bc; + + ssize_t out = 0; + + // ── Step 1: Send "login: " prompt ──────────────────────────────── + if (c->loginPromptSent == 0) { + out = write(c->fd, "login: ", 7); + c->loginPromptSent = 1; + } + + // ── Step 2: Read username ──────────────────────────────────────── + else if (c->inputCaptured == 0) { + if (readUntilEnter(c)) { + strncpy(c->username, c->inputBuf, sizeof(c->username) - 1); + printf("Username from %s: %s\n", c->base.ipaddr, c->username); + c->inputCaptured = 1; + + // Send password prompt immediately + write(c->fd, "\r\nPassword: ", 12); + c->passwordPromptSent = 1; + } + out = write(c->fd, negotiations[rand() % num_options], 3); + } + + // ── Step 3: Read password ──────────────────────────────────────── + else if (c->passwordPromptSent && c->passwordCaptured == 0) { + if (readUntilEnter(c)) { + printf("Credentials from %s — user: %s pass: %s\n", + c->base.ipaddr, c->username, c->inputBuf); + logCredentials(c->base.ipaddr, c->username, c->inputBuf); + c->passwordCaptured = 1; + + // Fake "Login incorrect" and restart to catch more attempts + write(c->fd, "\r\nLogin incorrect\r\n\r\nlogin: ", 28); + c->inputCaptured = 0; + c->passwordPromptSent = 0; + c->passwordCaptured = 0; + memset(c->username, 0, sizeof(c->username)); + } + out = write(c->fd, negotiations[rand() % num_options], 3); + } + + // ── Step 4: Hold connection with negotiations ──────────────────── + else { + out = write(c->fd, negotiations[rand() % num_options], 3); + } + + // ── Error / requeue handling (unchanged) ──────────────────────── + if (out == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + c->base.sendNext = now + delay; c->base.timeConnected += delay; statsTelnet.totalWastedTime += delay; queue_append(&clientQueueTelnet, (struct baseClient *)c); + } else { + long long timeTrapped = c->base.timeConnected; + char msg[256]; + snprintf(msg, sizeof(msg), "%s disconnect %s %lld\n", + SERVER_ID, c->base.ipaddr, timeTrapped); + printf("%s", msg); + sendMetric(msg); + close(c->fd); + free(c); } } else { - timeout = clientQueueTelnet.head->sendNext - now; - break; + c->base.sendNext = now + delay; + c->base.timeConnected += delay; + statsTelnet.totalWastedTime += delay; + queue_append(&clientQueueTelnet, (struct baseClient *)c); } } - + + // ── Poll for new connections ───────────────────────────────────────── int pollResult = poll(&fds, 1, timeout); - now = currentTimeMs(); // Poll will cause old value to be misrepresenting + now = currentTimeMs(); if (pollResult < 0) { fprintf(stderr, "Poll error with error %s", strerror(errno)); continue; } - // Accept new connections if (fds.revents & POLLIN) { int clientFd = accept(serverSock, (struct sockaddr *)&clientAddr, &addrLen); - if(clientFd == -1) { + if (clientFd == -1) { fprintf(stderr, "Failed accepting new client with error %s", strerror(errno)); continue; } - fcntl(clientFd, F_SETFL, O_NONBLOCK); // Set non-blocking mode - struct telnetAndUpnpClient* newClient = malloc(sizeof(struct telnetAndUpnpClient)); + fcntl(clientFd, F_SETFL, O_NONBLOCK); + + struct telnetAndUpnpClient *newClient = malloc(sizeof(struct telnetAndUpnpClient)); if (!newClient) { fprintf(stderr, "Out of memory"); close(clientFd); continue; } - statsTelnet.totalConnects += 1; - newClient->fd = clientFd; - newClient->base.sendNext = now + delay; + // Initialise all new fields + memset(newClient, 0, sizeof(struct telnetAndUpnpClient)); + newClient->fd = clientFd; + newClient->base.sendNext = now + delay; newClient->base.timeConnected = 0; - snprintf(newClient->base.ipaddr, INET_ADDRSTRLEN, "%s", inet_ntoa(clientAddr.sin_addr)); - queue_append(&clientQueueTelnet, (struct baseClient*)newClient); + snprintf(newClient->base.ipaddr, INET_ADDRSTRLEN, "%s", + inet_ntoa(clientAddr.sin_addr)); + + queue_append(&clientQueueTelnet, (struct baseClient *)newClient); - if(statsTelnet.mostConcurrentConnections < clientQueueTelnet.length) { + statsTelnet.totalConnects += 1; + if (statsTelnet.mostConcurrentConnections < clientQueueTelnet.length) statsTelnet.mostConcurrentConnections = clientQueueTelnet.length; - } char msg[256]; snprintf(msg, sizeof(msg), "%s connect %s\n", - SERVER_ID, newClient->base.ipaddr); + SERVER_ID, newClient->base.ipaddr); printf("%s", msg); sendMetric(msg); } @@ -174,4 +249,4 @@ int main(int argc, char *argv[]) { close(serverSock); return 0; -} +} \ No newline at end of file diff --git a/shared/structs.h b/shared/structs.h index 3c1d2df..a7523ee 100644 --- a/shared/structs.h +++ b/shared/structs.h @@ -17,10 +17,17 @@ struct baseClient { char ipaddr[INET_ADDRSTRLEN]; }; -struct telnetAndUpnpClient { - struct baseClient base; - int fd; -}; + struct telnetAndUpnpClient { + struct baseClient base; + int fd; + int loginPromptSent; + int inputCaptured; + int passwordPromptSent; + int passwordCaptured; + char username[256]; + char inputBuf[256]; + int inputBufLen; + }; struct coapClient { struct baseClient base;