summaryrefslogtreecommitdiff
path: root/mactelnetd.c
diff options
context:
space:
mode:
Diffstat (limited to 'mactelnetd.c')
-rw-r--r--mactelnetd.c545
1 files changed, 545 insertions, 0 deletions
diff --git a/mactelnetd.c b/mactelnetd.c
new file mode 100644
index 0000000..4bc576e
--- /dev/null
+++ b/mactelnetd.c
@@ -0,0 +1,545 @@
+/*
+ Mac-Telnet - Connect to RouterOS routers via MAC address
+ Copyright (C) 2010, Håkon Nessjøen <haakon.nessjoen@gmail.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#define _XOPEN_SOURCE 600
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/ether.h>
+#include <sys/time.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <linux/if_ether.h>
+#include <openssl/md5.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include "mactelnet.h"
+#include "udp.h"
+#include "console.h"
+#include "devices.h"
+#include "users.h"
+#include "config.h"
+
+int sockfd;
+int insockfd;
+int deviceIndex;
+
+struct in_addr sourceip;
+struct in_addr destip;
+int sourceport;
+
+unsigned char trypassword[17];
+
+/* Protocol data direction */
+unsigned char mt_direction_fromserver = 1;
+
+/* Anti-timeout is every 10 seconds. Give up after 15. */
+#define MT_CONNECTION_TIMEOUT 15
+
+/* Connection states */
+#define STATE_AUTH 1
+#define STATE_CLOSED 2
+#define STATE_ACTIVE 3
+
+struct mt_connection {
+ unsigned short seskey;
+ unsigned short incounter;
+ unsigned short outcounter;
+ time_t lastdata;
+ int terminalMode;
+ unsigned char username[30];
+ unsigned char srcip[4];
+ unsigned char srcmac[6];
+ unsigned short srcport;
+ unsigned char dstmac[6];
+ unsigned char enckey[16];
+ int state;
+ int ptsfd;
+ int slavefd;
+ int pid;
+ unsigned short terminal_width;
+ unsigned short terminal_height;
+ unsigned char terminal_type[30];
+
+ struct mt_connection *next;
+};
+
+struct mt_connection *connections_head = NULL;
+
+void addConnection(struct mt_connection *conn) {
+ struct mt_connection *p;
+ struct mt_connection *last;
+ if (connections_head == NULL) {
+ connections_head = conn;
+ connections_head->next = NULL;
+ return;
+ }
+ for (p = connections_head; p != NULL; p = p->next) {last = p;}
+ last->next = conn;
+ conn->next = NULL;
+}
+
+void removeConnection(struct mt_connection *conn) {
+ struct mt_connection *p;
+ struct mt_connection *last;
+ if (connections_head == NULL)
+ return;
+
+ if (conn->state == STATE_ACTIVE && conn->ptsfd > 0) {
+ close(conn->ptsfd);
+ }
+ if (conn->state == STATE_ACTIVE && conn->slavefd > 0) {
+ close(conn->slavefd);
+ }
+
+
+ if (connections_head == conn) {
+ connections_head = conn->next;
+ free(conn);
+ return;
+ }
+
+ for (p = connections_head; p != NULL; p = p->next) {
+ if (p == conn) {
+ last->next = p->next;
+ free(p);
+ return;
+ }
+ last = p;
+ }
+}
+
+struct mt_connection *findConnection(unsigned short seskey, unsigned char *srcmac) {
+ struct mt_connection *p;
+
+ if (connections_head == NULL)
+ return NULL;
+
+ for (p = connections_head; p != NULL; p = p->next) {
+ if (p->seskey == seskey && memcmp(srcmac, p->srcmac, 6) == 0) {
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+int sendUDP(const struct mt_connection *conn, const struct mt_packet *data) {
+ return sendCustomUDP(sockfd, 2, conn->dstmac, conn->srcmac, &sourceip, sourceport, &destip, conn->srcport, data->data, data->size);
+}
+
+void handlePacket(unsigned char *data, int data_len, const struct sockaddr_in *address) {
+ struct mt_mactelnet_hdr pkthdr;
+ struct mt_connection *curconn;
+ struct mt_packet pdata;
+
+ parsePacket(data, &pkthdr);
+
+ switch (pkthdr.ptype) {
+
+ case MT_PTYPE_SESSIONSTART:
+ printf("Adding connection with sessionid %d\n", pkthdr.seskey);
+ curconn = calloc(1, sizeof(struct mt_connection));
+ curconn->seskey = pkthdr.seskey;
+ curconn->lastdata = time(NULL);
+ curconn->state = STATE_AUTH;
+ memcpy(curconn->srcmac, pkthdr.srcaddr, 6);
+ memcpy(curconn->srcip, &(address->sin_addr), 4);
+ curconn->srcport = htons(address->sin_port);
+ memcpy(curconn->dstmac, pkthdr.dstaddr, 6);
+
+ addConnection(curconn);
+
+ initPacket(&pdata, MT_PTYPE_ACK, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, pkthdr.counter);
+ sendUDP(curconn, &pdata);
+ break;
+
+ case MT_PTYPE_END:
+ curconn = findConnection(pkthdr.seskey, (unsigned char *)&(pkthdr.srcaddr));
+ if (curconn != NULL) {
+ if (curconn->state != STATE_CLOSED) {
+ initPacket(&pdata, MT_PTYPE_END, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, pkthdr.counter);
+ sendUDP(curconn, &pdata);
+ }
+ printf("Connection with sessionid %d closed.\n", curconn->seskey);
+ removeConnection(curconn);
+ return;
+ }
+ break;
+
+ case MT_PTYPE_ACK:
+ curconn = findConnection(pkthdr.seskey, (unsigned char *)&(pkthdr.srcaddr));
+ if (curconn != NULL) {
+ curconn->lastdata = time(NULL);
+ if (pkthdr.counter == curconn->outcounter) {
+ // Answer to anti-timeout packet
+ initPacket(&pdata, MT_PTYPE_ACK, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, pkthdr.counter);
+ sendUDP(curconn, &pdata);
+ }
+ return;
+ }
+ break;
+
+ case MT_PTYPE_DATA:
+ curconn = findConnection(pkthdr.seskey, (unsigned char *)&(pkthdr.srcaddr));
+ if (curconn != NULL) {
+ unsigned char *p = data;
+ int rest;
+ char doLogin = 0;
+
+ curconn->lastdata = time(NULL);
+
+ /* ack the data packet */
+ initPacket(&pdata, MT_PTYPE_ACK, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, pkthdr.counter + (data_len - MT_HEADER_LEN));
+ sendUDP(curconn, &pdata);
+
+ /* Calculate how much more there is in the packet */
+ rest = data_len - MT_HEADER_LEN;
+ p += MT_HEADER_LEN;
+
+ while (rest > 0) {
+ int read;
+ struct mt_mactelnet_control_hdr cpkt;
+
+ /* Parse controlpacket data */
+ read = parseControlPacket(p, rest, &cpkt);
+ p += read;
+ rest -= read;
+
+ //read = parseControlPacket(data+22, data_len-22, &cpkt);
+
+ if (cpkt.cptype == MT_CPTYPE_BEGINAUTH) {
+ int plen,i;
+ for (i = 0; i < 16; ++i) {
+ curconn->enckey[i] = rand() % 256;
+ }
+ initPacket(&pdata, MT_PTYPE_DATA, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, curconn->outcounter);
+ plen = addControlPacket(&pdata, MT_CPTYPE_ENCRYPTIONKEY, (curconn->enckey), 16);
+ curconn->outcounter = plen;
+ sendUDP(curconn, &pdata);
+ memset(trypassword, 0, sizeof(trypassword));
+
+ } else if (cpkt.cptype == MT_CPTYPE_USERNAME) {
+
+ memcpy(curconn->username, cpkt.data, cpkt.length > 29 ? 29 : cpkt.length);
+ curconn->username[cpkt.length > 29 ? 29 : cpkt.length] = 0;
+
+ } else if (cpkt.cptype == MT_CPTYPE_TERM_WIDTH) {
+
+ curconn->terminal_width = cpkt.data[0] | (cpkt.data[1]<<8);
+ if (curconn->state == STATE_ACTIVE) {
+ setTerminalSize(curconn->ptsfd, curconn->terminal_width, curconn->terminal_height);
+ }
+
+ } else if (cpkt.cptype == MT_CPTYPE_TERM_HEIGHT) {
+
+ curconn->terminal_height = cpkt.data[0] | (cpkt.data[1]<<8);
+ if (curconn->state == STATE_ACTIVE) {
+ setTerminalSize(curconn->ptsfd, curconn->terminal_width, curconn->terminal_height);
+ }
+
+ } else if (cpkt.cptype == MT_CPTYPE_TERM_TYPE) {
+
+ memcpy(curconn->terminal_type, cpkt.data, cpkt.length > 29 ? 29 : cpkt.length);
+ curconn->terminal_type[cpkt.length > 29 ? 29 : cpkt.length] = 0;
+
+ } else if (cpkt.cptype == MT_CPTYPE_PASSWORD) {
+
+ memcpy(trypassword, cpkt.data, 17);
+ doLogin = 1;
+
+ } else if (cpkt.cptype == MT_CPTYPE_PLAINDATA) {
+ if (curconn->state == STATE_ACTIVE && curconn->ptsfd != -1) {
+ write(curconn->ptsfd, cpkt.data, cpkt.length);
+ }
+ } else {
+ printf("Unhandeled CPTYPE: %d\n", cpkt.cptype);
+ }
+ }
+ if (doLogin) {
+ int plen = 0;
+ unsigned char md5sum[17];
+ unsigned char md5data[100];
+ struct mt_credentials *user;
+
+ readUserfile();
+
+ if ((user = findUser(curconn->username)) != NULL) {
+ MD5_CTX c;
+ /* Concat string of 0 + password + encryptionkey */
+ md5data[0] = 0;
+ strncpy(md5data + 1, user->password, 82);
+ memcpy(md5data + 1 + strlen(user->password), curconn->enckey, 16);
+
+ /* Generate md5 sum of md5data with a leading 0 */
+ MD5_Init(&c);
+ MD5_Update(&c, md5data, strlen(user->password) + 17);
+ MD5_Final(md5sum + 1, &c);
+ md5sum[0] = 0;
+
+ initPacket(&pdata, MT_PTYPE_DATA, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, curconn->outcounter);
+ plen = addControlPacket(&pdata, MT_CPTYPE_END_AUTH, NULL, 0);
+ curconn->outcounter += plen;
+ sendUDP(curconn, &pdata);
+
+ if (curconn->state == STATE_ACTIVE)
+ return;
+ } else {
+ doLogin = 0;
+ }
+
+ if (doLogin == 1 && memcmp(md5sum, trypassword, 17) == 0) {
+ initPacket(&pdata, MT_PTYPE_DATA, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, curconn->outcounter);
+ plen = addControlPacket(&pdata, MT_CPTYPE_PLAINDATA, "Login OK!\n", 10);
+ sendUDP(curconn, &pdata);
+ curconn->outcounter += plen;
+ curconn->state = STATE_ACTIVE;
+ curconn->terminalMode = 1;
+ } else {
+ initPacket(&pdata, MT_PTYPE_DATA, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, curconn->outcounter);
+ plen = addControlPacket(&pdata, MT_CPTYPE_PLAINDATA, "Login FAILED!\n", 14);
+ sendUDP(curconn, &pdata);
+ curconn->outcounter += plen;
+ curconn->state = STATE_CLOSED;
+ initPacket(&pdata, MT_PTYPE_END, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, curconn->outcounter);
+ sendUDP(curconn, &pdata);
+ /* TODO: should wait some time (not with sleep) before returning, to minimalize brute force attacks */
+ return;
+ }
+
+ char *slavename;
+ curconn->ptsfd = posix_openpt(O_RDWR);
+ if (curconn->ptsfd == -1 || grantpt(curconn->ptsfd) == -1 || unlockpt(curconn->ptsfd) == -1) {
+ perror("openpt");
+ initPacket(&pdata, MT_PTYPE_DATA, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, curconn->outcounter);
+ addControlPacket(&pdata, MT_CPTYPE_PLAINDATA, "Terminal error\n", 14);
+ sendUDP(curconn, &pdata);
+ curconn->state = STATE_CLOSED;
+ initPacket(&pdata, MT_PTYPE_END, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, curconn->outcounter);
+ sendUDP(curconn, &pdata);
+ return;
+ }
+ slavename = ptsname(curconn->ptsfd);
+ if (slavename != NULL) {
+ int pid;
+ curconn->slavefd = open(slavename, O_RDWR);
+ if (curconn->slavefd == -1) {
+ perror ("Error opening the slave");
+ removeConnection(curconn);
+ return;
+ }
+ fcntl (curconn->slavefd, F_SETFD, FD_CLOEXEC);
+ if ((pid = fork()) == 0) {
+ struct passwd *user = (struct passwd *)getpwnam(curconn->username);
+ if (user == NULL) {
+ initPacket(&pdata, MT_PTYPE_DATA, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, curconn->outcounter);
+ addControlPacket(&pdata, MT_CPTYPE_PLAINDATA, "User not found\n", 15);
+ sendUDP(curconn, &pdata);
+ curconn->state = STATE_CLOSED;
+ initPacket(&pdata, MT_PTYPE_END, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, curconn->outcounter);
+ sendUDP(curconn, &pdata);
+ return;
+ }
+ setuid(user->pw_uid);
+ setgid(user->pw_gid);
+ setenv("USER", user->pw_name,1);
+ setenv("HOME", user->pw_dir, 1);
+ setenv("SHELL", user->pw_shell, 1);
+ setenv("TERM", curconn->terminal_type, 1);
+ close(sockfd);
+ close(insockfd);
+ setsid();
+ close(0);
+ dup(curconn->slavefd);
+ close(1);
+ dup(curconn->slavefd);
+ close(2);
+ dup(curconn->slavefd);
+ close(curconn->ptsfd);
+ /* Set controlling terminal */
+ ioctl (0, TIOCSCTTY, 1);
+ close(curconn->slavefd);
+ chdir(user->pw_dir);
+ /* Spawn shell */
+ execl (user->pw_shell, user->pw_shell, (char *) 0);
+ //exit(0);
+ }
+ close(curconn->slavefd);
+ curconn->pid = pid;
+ setTerminalSize(curconn->ptsfd, curconn->terminal_width, curconn->terminal_height);
+ }
+ }
+ }
+ break;
+
+ default:
+ printf("Unhandeled packet type: %d\n", pkthdr.ptype);
+ initPacket(&pdata, MT_PTYPE_ACK, pkthdr.dstaddr, pkthdr.srcaddr, pkthdr.seskey, pkthdr.counter);
+ sendUDP(curconn, &pdata);
+ }
+}
+
+/*
+ * TODO: Rewrite main() when all sub-functionality is tested
+ */
+int main (int argc, char **argv) {
+ int result;
+ struct sockaddr_in si_me;
+ struct timeval timeout;
+ int keepalive_counter = 0;
+ struct mt_packet pdata;
+ fd_set read_fds;
+
+ /* Try to read user file */
+ readUserfile();
+
+ /* Seed randomizer */
+ srand(time(NULL));
+
+ if (geteuid() != 0) {
+ fprintf(stderr, "You need to have root privileges to use %s.\n", argv[0]);
+ return 1;
+ }
+
+ /* Transmit raw packets with this socket */
+ sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+ if (sockfd < 0) {
+ perror("sockfd");
+ return 1;
+ }
+
+ /* Receive regular udp packets with this socket */
+ insockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (insockfd < 0) {
+ perror("insockfd");
+ return 1;
+ }
+
+ /* Set random source port */
+ sourceport = MT_MACTELNET_PORT;
+
+ /* Listen address*/
+ inet_pton(AF_INET, (char *)"0.0.0.0", &sourceip);
+
+ /* Set up global info about the connection */
+ inet_pton(AF_INET, (char *)"255.255.255.255", &destip);
+
+ /* Initialize receiving socket on the device chosen */
+ memset((char *) &si_me, 0, sizeof(si_me));
+ si_me.sin_family = AF_INET;
+ si_me.sin_port = htons(MT_MACTELNET_PORT);
+ memcpy(&(si_me.sin_addr), &sourceip, 4);
+
+ /* Bind to udp port */
+ if (bind(insockfd, (struct sockaddr *)&si_me, sizeof(si_me))==-1) {
+ fprintf(stderr, "Error binding to %s:%d, %s\n", inet_ntoa(si_me.sin_addr), sourceport, strerror(errno));
+ return 1;
+ }
+
+ printf("Bound to %s:%d\n", inet_ntoa(si_me.sin_addr), sourceport);
+
+ while (1) {
+ int reads;
+ struct mt_connection *p;
+ int maxfd=0;
+
+ /* Init select */
+ FD_ZERO(&read_fds);
+ FD_SET(insockfd, &read_fds);
+ maxfd = sockfd > insockfd ? sockfd : insockfd;
+
+ /* Add active connections to select queue */
+ for (p = connections_head; p != NULL; p = p->next) {
+ if (p->state == STATE_ACTIVE && p->ptsfd > 0) {
+ FD_SET(p->ptsfd, &read_fds);
+ if (p->ptsfd > maxfd)
+ maxfd = p->ptsfd;
+ }
+ }
+
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+
+ /* Wait for data or timeout */
+ reads = select(maxfd+1, &read_fds, NULL, NULL, &timeout);
+ if (reads > 0) {
+ /* Handle data from clients */
+ if (FD_ISSET(insockfd, &read_fds)) {
+ unsigned char buff[1500];
+ struct sockaddr_in saddress;
+ int slen = sizeof(saddress);
+ result = recvfrom(insockfd, buff, 1500, 0, (struct sockaddr *)&saddress, &slen);
+ handlePacket(buff, result, &saddress);
+ }
+
+ for (p = connections_head; p != NULL; p = p->next) {
+ /* Check if we have data ready in the pty buffer for the active session */
+ if (p->state == STATE_ACTIVE && p->ptsfd > 0 && FD_ISSET(p->ptsfd, &read_fds)) {
+ unsigned char keydata[100];
+ int datalen,plen;
+
+ /* Read it */
+ datalen = read(p->ptsfd, &keydata, 100);
+ if (datalen != -1) {
+ /* Send it */
+ initPacket(&pdata, MT_PTYPE_DATA, p->dstmac, p->srcmac, p->seskey, p->outcounter);
+ plen = addControlPacket(&pdata, MT_CPTYPE_PLAINDATA, &keydata, datalen);
+ p->outcounter += plen;
+ result = sendUDP(p, &pdata);
+ } else {
+ /* Bash exited */
+ initPacket(&pdata, MT_PTYPE_END, p->dstmac, p->srcmac, p->seskey, p->outcounter);
+ sendUDP(p, &pdata);
+ printf("Connection with sessionid %d closed.\n", p->seskey);
+ removeConnection(p);
+ }
+ }
+ }
+ /* Handle select() timeout */
+ } else {
+ /* TODO: Kill timed out sessions */
+ if (connections_head != NULL) {
+ struct mt_connection *p;
+ for (p = connections_head; p != NULL; p = p->next) {
+ if (time(NULL) - p->lastdata >= MT_CONNECTION_TIMEOUT) {
+ printf("Sessionid %d timed out\n", p->seskey);
+ initPacket(&pdata, MT_PTYPE_DATA, p->dstmac, p->srcmac, p->seskey, p->outcounter);
+ addControlPacket(&pdata, MT_CPTYPE_PLAINDATA, "Timeout\r\n", 9);
+ sendUDP(p, &pdata);
+ initPacket(&pdata, MT_PTYPE_END, p->dstmac, p->srcmac, p->seskey, p->outcounter);
+ sendUDP(p, &pdata);
+ removeConnection(p);
+ //break;
+ }
+ }
+ }
+ }
+ }
+
+ close(sockfd);
+ close(insockfd);
+
+ return 0;
+}