| /* |
| * Client for the Emergency Access Daemon |
| * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 |
| * as published by the Free Software Foundation |
| * |
| * 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. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <stdio.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <t_pwd.h> |
| #include <t_read.h> |
| #include <t_sha.h> |
| #include <t_defines.h> |
| #include <t_client.h> |
| #include "ead.h" |
| #include "ead-crypt.h" |
| |
| #include "pw_encrypt_md5.c" |
| |
| #define EAD_TIMEOUT 400 |
| #define EAD_TIMEOUT_LONG 2000 |
| |
| static char msgbuf[1500]; |
| static struct ead_msg *msg = (struct ead_msg *) msgbuf; |
| static uint16_t nid = 0xffff; |
| struct sockaddr_in local, remote; |
| static int s = 0; |
| static int sockflags; |
| static struct in_addr serverip = { |
| .s_addr = 0x01010101 /* dummy */ |
| }; |
| |
| static unsigned char *skey = NULL; |
| static unsigned char bbuf[MAXPARAMLEN]; |
| static unsigned char saltbuf[MAXSALTLEN]; |
| static char *username = NULL; |
| static char password[MAXPARAMLEN] = ""; |
| static char pw_md5[MD5_OUT_BUFSIZE]; |
| static char pw_salt[MAXSALTLEN]; |
| |
| static struct t_client *tc = NULL; |
| static struct t_num salt = { .data = saltbuf }; |
| static struct t_num *A, B; |
| static struct t_preconf *tcp; |
| static int auth_type = EAD_AUTH_DEFAULT; |
| static int timeout = EAD_TIMEOUT; |
| static uint16_t sid = 0; |
| |
| static void |
| set_nonblock(int enable) |
| { |
| if (enable == !!(sockflags & O_NONBLOCK)) |
| return; |
| |
| sockflags ^= O_NONBLOCK; |
| fcntl(s, F_SETFL, sockflags); |
| } |
| |
| static int |
| send_packet(int type, bool (*handler)(void), unsigned int max) |
| { |
| struct timeval tv; |
| fd_set fds; |
| int nfds; |
| int len; |
| int res = 0; |
| |
| type = htonl(type); |
| memcpy(&msg->ip, &serverip.s_addr, sizeof(msg->ip)); |
| set_nonblock(0); |
| sendto(s, msgbuf, sizeof(struct ead_msg) + ntohl(msg->len), 0, (struct sockaddr *) &remote, sizeof(remote)); |
| set_nonblock(1); |
| |
| tv.tv_sec = timeout / 1000; |
| tv.tv_usec = (timeout % 1000) * 1000; |
| |
| FD_ZERO(&fds); |
| do { |
| FD_SET(s, &fds); |
| nfds = select(s + 1, &fds, NULL, NULL, &tv); |
| |
| if (nfds <= 0) |
| break; |
| |
| if (!FD_ISSET(s, &fds)) |
| break; |
| |
| len = read(s, msgbuf, sizeof(msgbuf)); |
| if (len < 0) |
| break; |
| |
| if (len < sizeof(struct ead_msg)) |
| continue; |
| |
| if (len < sizeof(struct ead_msg) + ntohl(msg->len)) |
| continue; |
| |
| if (msg->magic != htonl(EAD_MAGIC)) |
| continue; |
| |
| if ((nid != 0xffff) && (ntohs(msg->nid) != nid)) |
| continue; |
| |
| if (msg->type != type) |
| continue; |
| |
| if (handler()) |
| res++; |
| |
| if ((max > 0) && (res >= max)) |
| break; |
| } while (1); |
| |
| return res; |
| } |
| |
| static void |
| prepare_password(void) |
| { |
| switch(auth_type) { |
| case EAD_AUTH_DEFAULT: |
| break; |
| case EAD_AUTH_MD5: |
| md5_crypt(pw_md5, (unsigned char *) password, (unsigned char *) pw_salt); |
| strncpy(password, pw_md5, sizeof(password)); |
| break; |
| } |
| } |
| |
| static bool |
| handle_pong(void) |
| { |
| struct ead_msg_pong *pong = EAD_DATA(msg, pong); |
| int len = ntohl(msg->len) - sizeof(struct ead_msg_pong); |
| |
| if (len <= 0) |
| return false; |
| |
| pong->name[len] = 0; |
| auth_type = ntohs(pong->auth_type); |
| if (nid == 0xffff) |
| printf("%04x: %s\n", ntohs(msg->nid), pong->name); |
| sid = msg->sid; |
| return true; |
| } |
| |
| static bool |
| handle_prime(void) |
| { |
| struct ead_msg_salt *sb = EAD_DATA(msg, salt); |
| |
| salt.len = sb->len; |
| memcpy(salt.data, sb->salt, salt.len); |
| |
| if (auth_type == EAD_AUTH_MD5) { |
| memcpy(pw_salt, sb->ext_salt, MAXSALTLEN); |
| pw_salt[MAXSALTLEN - 1] = 0; |
| } |
| |
| tcp = t_getpreparam(sb->prime); |
| tc = t_clientopen(username, &tcp->modulus, &tcp->generator, &salt); |
| if (!tc) { |
| fprintf(stderr, "Client open failed\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| handle_b(void) |
| { |
| struct ead_msg_number *num = EAD_DATA(msg, number); |
| int len = ntohl(msg->len) - sizeof(struct ead_msg_number); |
| |
| B.data = bbuf; |
| B.len = len; |
| memcpy(bbuf, num->data, len); |
| return true; |
| } |
| |
| static bool |
| handle_none(void) |
| { |
| return true; |
| } |
| |
| static bool |
| handle_done_auth(void) |
| { |
| struct ead_msg_auth *auth = EAD_DATA(msg, auth); |
| if (t_clientverify(tc, auth->data) != 0) { |
| fprintf(stderr, "Client auth verify failed\n"); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool |
| handle_cmd_data(void) |
| { |
| struct ead_msg_cmd_data *cmd = EAD_ENC_DATA(msg, cmd_data); |
| int datalen = ead_decrypt_message(msg) - sizeof(struct ead_msg_cmd_data); |
| |
| if (datalen < 0) |
| return false; |
| |
| if (datalen > 0) { |
| write(1, cmd->data, datalen); |
| } |
| |
| return !!cmd->done; |
| } |
| static int |
| send_ping(void) |
| { |
| msg->type = htonl(EAD_TYPE_PING); |
| msg->len = 0; |
| return send_packet(EAD_TYPE_PONG, handle_pong, (nid == 0xffff ? 0 : 1)); |
| } |
| |
| static int |
| send_username(void) |
| { |
| msg->type = htonl(EAD_TYPE_SET_USERNAME); |
| msg->len = htonl(sizeof(struct ead_msg_user)); |
| strcpy(EAD_DATA(msg, user)->username, username); |
| return send_packet(EAD_TYPE_ACK_USERNAME, handle_none, 1); |
| } |
| |
| static int |
| get_prime(void) |
| { |
| msg->type = htonl(EAD_TYPE_GET_PRIME); |
| msg->len = 0; |
| return send_packet(EAD_TYPE_PRIME, handle_prime, 1); |
| } |
| |
| static int |
| send_a(void) |
| { |
| struct ead_msg_number *num = EAD_DATA(msg, number); |
| A = t_clientgenexp(tc); |
| msg->type = htonl(EAD_TYPE_SEND_A); |
| msg->len = htonl(sizeof(struct ead_msg_number) + A->len); |
| memcpy(num->data, A->data, A->len); |
| return send_packet(EAD_TYPE_SEND_B, handle_b, 1); |
| } |
| |
| static int |
| send_auth(void) |
| { |
| struct ead_msg_auth *auth = EAD_DATA(msg, auth); |
| |
| prepare_password(); |
| t_clientpasswd(tc, password); |
| skey = t_clientgetkey(tc, &B); |
| if (!skey) |
| return 0; |
| |
| ead_set_key(skey); |
| msg->type = htonl(EAD_TYPE_SEND_AUTH); |
| msg->len = htonl(sizeof(struct ead_msg_auth)); |
| memcpy(auth->data, t_clientresponse(tc), sizeof(auth->data)); |
| return send_packet(EAD_TYPE_DONE_AUTH, handle_done_auth, 1); |
| } |
| |
| static int |
| send_command(const char *command) |
| { |
| struct ead_msg_cmd *cmd = EAD_ENC_DATA(msg, cmd); |
| |
| msg->type = htonl(EAD_TYPE_SEND_CMD); |
| cmd->type = htons(EAD_CMD_NORMAL); |
| cmd->timeout = htons(10); |
| strncpy((char *)cmd->data, command, 1024); |
| ead_encrypt_message(msg, sizeof(struct ead_msg_cmd) + strlen(command) + 1); |
| return send_packet(EAD_TYPE_RESULT_CMD, handle_cmd_data, 1); |
| } |
| |
| |
| static int |
| usage(const char *prog) |
| { |
| fprintf(stderr, "Usage: %s [-s <addr>] [-b <addr>] <node> <username>[:<password>] <command>\n" |
| "\n" |
| "\t-s <addr>: Set the server's source address to <addr>\n" |
| "\t-b <addr>: Set the broadcast address to <addr>\n" |
| "\t<node>: Node ID (4 digits hex)\n" |
| "\t<username>: Username to authenticate with\n" |
| "\n" |
| "\tPassing no arguments shows a list of active nodes on the network\n" |
| "\n", prog); |
| return -1; |
| } |
| |
| |
| int main(int argc, char **argv) |
| { |
| int val = 1; |
| char *st = NULL; |
| const char *command = NULL; |
| const char *prog = argv[0]; |
| int ch; |
| |
| msg->magic = htonl(EAD_MAGIC); |
| msg->sid = 0; |
| |
| memset(&local, 0, sizeof(local)); |
| memset(&remote, 0, sizeof(remote)); |
| |
| remote.sin_family = AF_INET; |
| remote.sin_addr.s_addr = 0xffffffff; |
| remote.sin_port = htons(EAD_PORT); |
| |
| local.sin_family = AF_INET; |
| local.sin_addr.s_addr = INADDR_ANY; |
| local.sin_port = 0; |
| |
| while ((ch = getopt(argc, argv, "b:s:h")) != -1) { |
| switch(ch) { |
| case 's': |
| inet_aton(optarg, &serverip); |
| break; |
| case 'b': |
| inet_aton(optarg, &remote.sin_addr); |
| break; |
| case 'h': |
| return usage(prog); |
| } |
| } |
| argv += optind; |
| argc -= optind; |
| |
| switch(argc) { |
| case 3: |
| command = argv[2]; |
| /* fall through */ |
| case 2: |
| username = argv[1]; |
| st = strchr(username, ':'); |
| if (st) { |
| *st = 0; |
| st++; |
| strncpy(password, st, sizeof(password)); |
| password[sizeof(password) - 1] = 0; |
| /* hide command line password */ |
| memset(st, 0, strlen(st)); |
| } |
| /* fall through */ |
| case 1: |
| nid = strtoul(argv[0], &st, 16); |
| if (st && st[0] != 0) |
| return usage(prog); |
| /* fall through */ |
| case 0: |
| break; |
| default: |
| return usage(prog); |
| } |
| |
| msg->nid = htons(nid); |
| s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
| if (s < 0) { |
| perror("socket"); |
| return -1; |
| } |
| |
| setsockopt(s, SOL_SOCKET, SO_BROADCAST, &val, sizeof(val)); |
| |
| if (bind(s, (struct sockaddr *)&local, sizeof(local)) < 0) { |
| perror("bind"); |
| return -1; |
| } |
| sockflags = fcntl(s, F_GETFL); |
| |
| if (!send_ping()) { |
| fprintf(stderr, "No devices found\n"); |
| return 1; |
| } |
| |
| if (nid == 0xffff) |
| return 0; |
| |
| if (!username || !password[0]) |
| return 0; |
| |
| if (!send_username()) { |
| fprintf(stderr, "Device did not accept user name\n"); |
| return 1; |
| } |
| timeout = EAD_TIMEOUT_LONG; |
| if (!get_prime()) { |
| fprintf(stderr, "Failed to get user password info\n"); |
| return 1; |
| } |
| if (!send_a()) { |
| fprintf(stderr, "Failed to send local authentication data\n"); |
| return 1; |
| } |
| if (!send_auth()) { |
| fprintf(stderr, "Authentication failed\n"); |
| return 1; |
| } |
| if (!command) { |
| fprintf(stderr, "Authentication succesful\n"); |
| return 0; |
| } |
| if (!send_command(command)) { |
| fprintf(stderr, "Command failed\n"); |
| return 1; |
| } |
| |
| return 0; |
| } |