// main [c]
int main (int argc, char **argv) {
	int result;
	struct mt_packet data;
	struct sockaddr_in si_me;
	unsigned char buff[1500];
	unsigned char print_help = 0, have_username = 0, have_password = 0;
	unsigned char drop_priv = 0;
	int c;
	int optval = 1;

	setlocale(LC_ALL, "");
	bindtextdomain("mactelnet","/usr/share/locale");
	textdomain("mactelnet");

	/* Set default for ssh_path. */
	strncpy(ssh_path, SSH_PATH, sizeof(ssh_path) - 1);
	ssh_path[sizeof(ssh_path) - 1] = '\0';

    /* Ignore args after -- for MAC-Telnet client. */
	int mactelnet_argc = argc;
	int i;
	for (i=0; i < argc; i++) {
		if (strlen(argv[i]) == 2 && strncmp(argv[i], "--", 2) == 0) {
			mactelnet_argc = i;
			break;
		}
	}

	while (1) {
		c = getopt(mactelnet_argc, argv, "nqlt:u:p:vh?SFP:c:U:");

		if (c == -1) {
			break;
		}

		switch (c) {

			case 'n':
				use_raw_socket = 1;
				break;

			case 'S':
				tunnel_conn = 1;
				launch_ssh = 1;
				break;

			case 'F':
				tunnel_conn = 1;
				break;

			case 'P':
				fwdport = atoi(optarg);
				break;

			case 'u':
				/* Save username */
				strncpy(username, optarg, sizeof(username) - 1);
				username[sizeof(username) - 1] = '\0';
				have_username = 1;
				break;

			case 'p':
				/* Save password */
				strncpy(password, optarg, sizeof(password) - 1);
				password[sizeof(password) - 1] = '\0';
				have_password = 1;
				break;

			case 'U':
				/* Save nonpriv_username */
				strncpy(nonpriv_username, optarg, sizeof(nonpriv_username) - 1);
				nonpriv_username[sizeof(nonpriv_username) - 1] = '\0';
				drop_priv = 1;
				break;

			case 'c':
				/* Save ssh executable path */
				strncpy(ssh_path, optarg, sizeof(ssh_path) - 1);
				ssh_path[sizeof(ssh_path) - 1] = '\0';
				break;

			case 't':
				connect_timeout = atoi(optarg);
				break;

			case 'l':
				return mndp();
				break;

			case 'v':
				print_version();
				exit(0);
				break;

			case 'q':
				quiet_mode = 1;
				break;

			case 'h':
			case '?':
				print_help = 1;
				break;

		}
	}
	if (argc - optind < 1 || print_help) {
		print_version();
		fprintf(stderr, _("Usage: %s <MAC|identity> [-v] [-h] [-q] [-n] [-l] [-S] [-P <port>]\n"
				          "       [-t <timeout>] [-u <user>] [-p <pass>] [-c <path>] [-U <user>]\n"), argv[0]);

		if (print_help) {
			fprintf(stderr, _("\nParameters:\n"
			"  MAC           MAC-Address of the RouterOS/mactelnetd device. Use mndp to \n"
            "                discover it.\n"
			"  identity      The identity/name of your destination device. Uses MNDP \n"
			"                protocol to find it.\n"
			"  -l            List/Search for routers nearby. (using MNDP)\n"
			"  -n            Do not use broadcast packets. Less insecure but requires root \n"
		    "                privileges.\n"
			"  -t <timeout>  Amount of seconds to wait for a response on each interface.\n"
			"  -u <user>     Specify username on command line.\n"
			"  -p <pass>     Specify password on command line.\n"
			"  -U <user>     Drop privileges by switching to user, when the command is\n"
			"                run as a privileged user in conjunction with the -n option.\n"
			"  -S            Use MAC-SSH instead of MAC-Telnet. (Implies -F)\n"
		    "                Forward SSH connection through MAC-Telnet and launch SSH client.\n"
			"  -F            Forward connection through of MAC-Telnet without launching the \n"
		    "                SSH Client.\n"
			"  -P <port>     Local TCP port for forwarding SSH connection.\n"
			"                (If not specified, port 2222 by default.)\n"
			"  -c <path>     Path for ssh client executable. (Default: /usr/bin/ssh)\n"
			"  -q            Quiet mode.\n"
			"  -v            Print version and exit.\n"
			"  -h            Print help and exit.\n"
			"\n"
			"All arguments after '--' will be passed to the ssh client command.\n"
			"\n"));
		}
		return 1;
	}

	/* Setup command line for ssh client */
	if (launch_ssh) {
		int ssh_argc;
		int add_argc;
		ssh_argc = argc - mactelnet_argc;
		add_argc = ssh_argc;
		ssh_argc += 3; /* Port option and hostname: -p <port> <host>*/
		if (have_username) {
			ssh_argc += 2;  /* Login name option: -l <user> */
		}
		ssh_argv = (char **) calloc(sizeof(char *), ssh_argc + 1);
		char *ssh_path_c = strndup(ssh_path, sizeof(ssh_path) - 1);
		char *ssh_filename = basename(ssh_path_c);
		int idx = 0;
		ssh_argv[idx++] = ssh_filename;
		int i;
		for (i = 1; i < add_argc; i++) {
			ssh_argv[idx++] = argv[mactelnet_argc + i];
		}
		char portstr[8];
		snprintf(portstr, 8, "%d", fwdport);
		ssh_argv[idx++] = strdup("-p");
		ssh_argv[idx++] = strndup(portstr, sizeof(portstr) - 1);
		if (have_username) {
			ssh_argv[idx++] = strdup("-l");
			ssh_argv[idx++] = username;
		}
		ssh_argv[idx++] = strdup("127.0.0.1");
		ssh_argv[idx++] = (char*) 0;
	}

	is_a_tty = isatty(fileno(stdout)) && isatty(fileno(stdin));
	if (!is_a_tty) {
		quiet_mode = 1;
	}

	/* Seed randomizer */
	srand(time(NULL));

	if (use_raw_socket) {
		if (geteuid() != 0) {
			fprintf(stderr, _("You need to have root privileges to use the -n parameter.\n"));
			return 1;
		}

		sockfd = net_init_raw_socket();
	}

	if (drop_priv) {
		drop_privileges(nonpriv_username);
	}

	/* Receive regular udp packets with this socket */
	insockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (insockfd < 0) {
		perror("insockfd");
		return 1;
	}

	if (!use_raw_socket) {
		if (setsockopt(insockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof (optval))==-1) {
			perror("SO_BROADCAST");
			return 1;
		}
	}

	/* Need to use, to be able to autodetect which interface to use */
	setsockopt(insockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (optval));

	/* Get mac-address from string, or check for hostname via mndp */
	if (!query_mndp_or_mac(argv[optind], dstmac, !quiet_mode)) {
		/* No valid mac address found, abort */
		return 1;
	}

	if (!tunnel_conn && !have_username) {
		if (!quiet_mode) {
			printf(_("Login: "));
		}
		scanf("%254s", username);
	}

	if (!tunnel_conn && !have_password) {
		char *tmp;
		tmp = getpass(quiet_mode ? "" : _("Password: "));
		strncpy(password, tmp, sizeof(password) - 1);
		password[sizeof(password) - 1] = '\0';
		/* security */
		memset(tmp, 0, strlen(tmp));
#ifdef __GNUC__
		free(tmp);
#endif
	}

	if (tunnel_conn) {
		/* Setup signal handler for broken tunnels. */
		signal(SIGPIPE,SIG_IGN);

		/* Setup Server socket for receiving connection from local SSH Client. */
		int fwdsrvfd;
		fwdsrvfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (fwdsrvfd < 0) {
			perror("fwdsrvfd");
			return 1;
		}
		if(setsockopt(fwdsrvfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (optval)) < 0) {
			perror("SO_REUSEADDR");
			return 1;
		}

		/* Bind to server socket for receiving terminal client connection. */
		struct sockaddr_in srv_socket;
		memset(&srv_socket, 0, sizeof(srv_socket));
		srv_socket.sin_family = AF_INET;
		srv_socket.sin_port = htons(fwdport);
		srv_socket.sin_addr.s_addr = inet_addr("127.0.0.1");
		if (bind(fwdsrvfd, (struct sockaddr *) &srv_socket, sizeof(srv_socket)) < 0) {
			fprintf(stderr, _("Error binding to %s:%d, %s\n"), "127.0.0.1", fwdport, strerror(errno));
			return 1;
		}
		if (listen(fwdsrvfd, 1) < 0) {
			fprintf(stderr, _("Failed listen on server socket %s:%d, %s\n"), "127.0.0.1", fwdport, strerror(errno));
			return 1;
		}

		/* Fork child to execute SSH Client locally and connect to parent
		 * waiting for connection from child if launch_ssh is requested.
		 */
		int pid;
		if (launch_ssh) {
			pid = fork();
		}

		if (!launch_ssh || pid > 0) {
			/* Parent code. Waits for connection to local end of tunnel */

			/* Close stdin and stdout, leave stderr active for error messages.
			 * The terminal will be handled by client connecting to local end of tunnel. */
			close(0);
			close(1);

			/* Wait for remote terminal client connection on server port. */
			fprintf(stderr, _("Waiting for tunnel connection on port: %d\n"), fwdport);
			struct sockaddr_in cli_socket;
			unsigned int cli_socket_len = sizeof(cli_socket);
			memset(&cli_socket, 0, sizeof(cli_socket));
			if ((fwdfd = accept(fwdsrvfd, (struct sockaddr *) &cli_socket, &cli_socket_len)) < 0) {
				perror("fwdfd");
			}
			if(setsockopt(fwdfd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) < 0) {
				perror("SO_KEEPALIVE");
				return 1;
			}
			fprintf(stderr, _("Client connected to tunnel from port: %d\n"), ntohs(cli_socket.sin_port));
		}
		else if (launch_ssh && pid == 0) {
			/* Child Code. Executes SSH Client and connects to parent to tunnel
			 * connection through MAC-Telnet protocol. */
			if (use_raw_socket) {
				close(sockfd);
			}
			close(insockfd);
			close(fwdsrvfd);

			/* Give time to parent to initialize listening port. */
			sleep(2);

			/* Execute SSH Client. */
			execvp(ssh_path, ssh_argv);
			perror("Execution of terminal client failed.");
			exit(1);
		}
		/* Fork failure. */
		else {
			fprintf(stderr, _("Execution of terminal client failed.\n"));
			if (use_raw_socket) {
				close(sockfd);
			}
			close(insockfd);
			return 1;
		}
	}

	/* Set random source port */
	sourceport = 1024 + (rand() % 1024);

	/* Set up global info about the connection */
	inet_pton(AF_INET, (char *)"255.255.255.255", &destip);
	memcpy(&sourceip, &(si_me.sin_addr), IPV4_ALEN);

	/* Session key */
	sessionkey = rand() % 65535;

	/* stop output buffering */
	setvbuf(stdout, (char*)NULL, _IONBF, 0);

	if (!quiet_mode) {
		printf(_("Connecting to %s..."), ether_ntoa((struct ether_addr *)dstmac));
	}

	/* 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(sourceport);

	/* 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;
	}

	if (!find_interface() || (result = recvfrom(insockfd, buff, 1400, 0, 0, 0)) < 1) {
		fprintf(stderr, _("Connection failed.\n"));
		return 1;
	}
	if (!quiet_mode) {
		printf(_("done\n"));
	}

	/* Handle first received packet */
	handle_packet(buff, result);

	init_packet(&data, MT_PTYPE_DATA, srcmac, dstmac, sessionkey, 0);
	outcounter +=  add_control_packet(&data, MT_CPTYPE_BEGINAUTH, NULL, 0);

	/* TODO: handle result of send_udp */
	result = send_udp(&data, 1);

	while (running) {
		fd_set read_fds;
		int reads;
		static int terminal_gone = 0;
		struct timeval timeout;

		int maxfd = 0;
		maxfd = insockfd > fwdfd ? insockfd : fwdfd;

		/* Init select */
		FD_ZERO(&read_fds);
		if (!tunnel_conn && !terminal_gone) {
			/* Setup fd to read input from terminal. */
			FD_SET(0, &read_fds);
		}
		else if (tunnel_conn) {
			/* Setup fd to read input from local SSH Client. */
			FD_SET(fwdfd, &read_fds);
		}
		FD_SET(insockfd, &read_fds);

		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 server */
			if (FD_ISSET(insockfd, &read_fds)) {
				bzero(buff, 1500);
				result = recvfrom(insockfd, buff, 1500, 0, 0, 0);
				handle_packet(buff, result);
			}
			unsigned char keydata[512];
			int datalen = 0;
			/* Handle data from keyboard/local terminal */
			if (!tunnel_conn && FD_ISSET(0, &read_fds) && terminal_mode) {
				datalen = read(STDIN_FILENO, &keydata, 512);
			}
			/* Handle data from local SSH client */
			if (tunnel_conn && FD_ISSET(fwdfd, &read_fds)) {
				datalen = read(fwdfd, &keydata, 512);
			}
			if (datalen > 0) {
				/* Data received, transmit to server */
				init_packet(&data, MT_PTYPE_DATA, srcmac, dstmac, sessionkey, outcounter);
				add_control_packet(&data, MT_CPTYPE_PLAINDATA, &keydata, datalen);
				outcounter += datalen;
				send_udp(&data, 1);
			}
			else if (datalen < 0) {
				terminal_gone = 1;
			}
		/* Handle select() timeout */
		} else {
			/* handle keepalive counter, transmit keepalive packet every 10 seconds
			   of inactivity  */
			if (keepalive_counter++ == 10) {
				struct mt_packet odata;
				init_packet(&odata, MT_PTYPE_ACK, srcmac, dstmac, sessionkey, outcounter);
				send_udp(&odata, 0);
			}
		}
	}

	if (!tunnel_conn && is_a_tty && terminal_mode) {
		/* Reset terminal back to old settings */
		reset_term();
	}

	close(sockfd);
	close(insockfd);
	if (tunnel_conn && fwdfd > 0) {
		close(fwdfd);
	}

	return 0;
}