Initial commit
This commit is contained in:
976
package/network/services/ead/src/ead.c
Normal file
976
package/network/services/ead/src/ead.c
Normal file
@@ -0,0 +1,976 @@
|
||||
/*
|
||||
* 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/time.h>
|
||||
#include <sys/select.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <pcap.h>
|
||||
#include <pcap-bpf.h>
|
||||
#include <t_pwd.h>
|
||||
#include <t_read.h>
|
||||
#include <t_sha.h>
|
||||
#include <t_defines.h>
|
||||
#include <t_server.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include "list.h"
|
||||
#include "ead.h"
|
||||
#include "ead-pcap.h"
|
||||
#include "ead-crypt.h"
|
||||
#include "libbridge.h"
|
||||
|
||||
#include "filter.c"
|
||||
|
||||
#ifdef linux
|
||||
#include <linux/if_packet.h>
|
||||
#endif
|
||||
|
||||
#define PASSWD_FILE "/etc/passwd"
|
||||
|
||||
#ifndef DEFAULT_IFNAME
|
||||
#define DEFAULT_IFNAME "eth0"
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_DEVNAME
|
||||
#define DEFAULT_DEVNAME "Unknown"
|
||||
#endif
|
||||
|
||||
#define PCAP_MRU 1600
|
||||
#define PCAP_TIMEOUT 200
|
||||
|
||||
#if EAD_DEBUGLEVEL >= 1
|
||||
#define DEBUG(n, format, ...) do { \
|
||||
if (EAD_DEBUGLEVEL >= n) \
|
||||
fprintf(stderr, format, ##__VA_ARGS__); \
|
||||
} while (0);
|
||||
|
||||
#else
|
||||
#define DEBUG(n, format, ...) do {} while(0)
|
||||
#endif
|
||||
|
||||
struct ead_instance {
|
||||
struct list_head list;
|
||||
char ifname[16];
|
||||
int pid;
|
||||
char id;
|
||||
char bridge[16];
|
||||
bool br_check;
|
||||
};
|
||||
|
||||
static char ethmac[6] = "\x00\x13\x37\x00\x00\x00"; /* last 3 bytes will be randomized */
|
||||
static pcap_t *pcap_fp = NULL;
|
||||
static pcap_t *pcap_fp_rx = NULL;
|
||||
static char pktbuf_b[PCAP_MRU];
|
||||
static struct ead_packet *pktbuf = (struct ead_packet *)pktbuf_b;
|
||||
static u16_t nid = 0xffff; /* node id */
|
||||
static char username[32] = "";
|
||||
static int state = EAD_TYPE_SET_USERNAME;
|
||||
static const char *passwd_file = PASSWD_FILE;
|
||||
static const char password[MAXPARAMLEN];
|
||||
static bool child_pending = false;
|
||||
|
||||
static unsigned char abuf[MAXPARAMLEN + 1];
|
||||
static unsigned char pwbuf[MAXPARAMLEN];
|
||||
static unsigned char saltbuf[MAXSALTLEN];
|
||||
static unsigned char pw_saltbuf[MAXSALTLEN];
|
||||
static struct list_head instances;
|
||||
static const char *dev_name = DEFAULT_DEVNAME;
|
||||
static bool nonfork = false;
|
||||
static struct ead_instance *instance = NULL;
|
||||
|
||||
static struct t_pwent tpe = {
|
||||
.name = username,
|
||||
.index = 1,
|
||||
.password.data = pwbuf,
|
||||
.password.len = 0,
|
||||
.salt.data = saltbuf,
|
||||
.salt.len = 0,
|
||||
};
|
||||
struct t_confent *tce = NULL;
|
||||
static struct t_server *ts = NULL;
|
||||
static struct t_num A, *B = NULL;
|
||||
unsigned char *skey;
|
||||
|
||||
static void
|
||||
set_recv_type(pcap_t *p, bool rx)
|
||||
{
|
||||
#ifdef PACKET_RECV_TYPE
|
||||
struct sockaddr_ll sll;
|
||||
struct ifreq ifr;
|
||||
int mask;
|
||||
int fd;
|
||||
|
||||
fd = pcap_get_selectable_fd(p);
|
||||
if (fd < 0)
|
||||
return;
|
||||
|
||||
if (rx)
|
||||
mask = 1 << PACKET_BROADCAST;
|
||||
else
|
||||
mask = 0;
|
||||
|
||||
setsockopt(fd, SOL_PACKET, PACKET_RECV_TYPE, &mask, sizeof(mask));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static pcap_t *
|
||||
ead_open_pcap(const char *ifname, char *errbuf, bool rx)
|
||||
{
|
||||
pcap_t *p;
|
||||
|
||||
p = pcap_create(ifname, errbuf);
|
||||
if (p == NULL)
|
||||
goto out;
|
||||
|
||||
pcap_set_snaplen(p, PCAP_MRU);
|
||||
pcap_set_promisc(p, rx);
|
||||
pcap_set_timeout(p, PCAP_TIMEOUT);
|
||||
#ifdef HAS_PROTO_EXTENSION
|
||||
pcap_set_protocol(p, (rx ? htons(ETH_P_IP) : 0));
|
||||
#endif
|
||||
pcap_set_buffer_size(p, (rx ? 10 : 1) * PCAP_MRU);
|
||||
pcap_activate(p);
|
||||
set_recv_type(p, rx);
|
||||
out:
|
||||
return p;
|
||||
}
|
||||
|
||||
static void
|
||||
get_random_bytes(void *ptr, int len)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
exit(1);
|
||||
}
|
||||
read(fd, ptr, len);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static bool
|
||||
prepare_password(void)
|
||||
{
|
||||
static char lbuf[1024];
|
||||
unsigned char dig[SHA_DIGESTSIZE];
|
||||
BigInteger x, v, n, g;
|
||||
SHA1_CTX ctxt;
|
||||
int ulen = strlen(username);
|
||||
FILE *f;
|
||||
|
||||
lbuf[sizeof(lbuf) - 1] = 0;
|
||||
|
||||
f = fopen(passwd_file, "r");
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
while (fgets(lbuf, sizeof(lbuf) - 1, f) != NULL) {
|
||||
char *str, *s2;
|
||||
|
||||
if (strncmp(lbuf, username, ulen) != 0)
|
||||
continue;
|
||||
|
||||
if (lbuf[ulen] != ':')
|
||||
continue;
|
||||
|
||||
str = &lbuf[ulen + 1];
|
||||
|
||||
if (strncmp(str, "$1$", 3) != 0)
|
||||
continue;
|
||||
|
||||
s2 = strchr(str + 3, '$');
|
||||
if (!s2)
|
||||
continue;
|
||||
|
||||
if (s2 - str >= MAXSALTLEN)
|
||||
continue;
|
||||
|
||||
strncpy((char *) pw_saltbuf, str, s2 - str);
|
||||
pw_saltbuf[s2 - str] = 0;
|
||||
|
||||
s2 = strchr(s2, ':');
|
||||
if (!s2)
|
||||
continue;
|
||||
|
||||
*s2 = 0;
|
||||
if (s2 - str >= MAXPARAMLEN)
|
||||
continue;
|
||||
|
||||
strncpy((char *)password, str, MAXPARAMLEN);
|
||||
fclose(f);
|
||||
goto hash_password;
|
||||
}
|
||||
|
||||
/* not found */
|
||||
fclose(f);
|
||||
return false;
|
||||
|
||||
hash_password:
|
||||
tce = gettcid(tpe.index);
|
||||
do {
|
||||
t_random(tpe.password.data, SALTLEN);
|
||||
} while (memcmp(saltbuf, (char *)dig, sizeof(saltbuf)) == 0);
|
||||
if (saltbuf[0] == 0)
|
||||
saltbuf[0] = 0xff;
|
||||
|
||||
n = BigIntegerFromBytes(tce->modulus.data, tce->modulus.len);
|
||||
g = BigIntegerFromBytes(tce->generator.data, tce->generator.len);
|
||||
v = BigIntegerFromInt(0);
|
||||
|
||||
SHA1Init(&ctxt);
|
||||
SHA1Update(&ctxt, (unsigned char *) username, strlen(username));
|
||||
SHA1Update(&ctxt, (unsigned char *) ":", 1);
|
||||
SHA1Update(&ctxt, (unsigned char *) password, strlen(password));
|
||||
SHA1Final(dig, &ctxt);
|
||||
|
||||
SHA1Init(&ctxt);
|
||||
SHA1Update(&ctxt, saltbuf, tpe.salt.len);
|
||||
SHA1Update(&ctxt, dig, sizeof(dig));
|
||||
SHA1Final(dig, &ctxt);
|
||||
|
||||
/* x = H(s, H(u, ':', p)) */
|
||||
x = BigIntegerFromBytes(dig, sizeof(dig));
|
||||
|
||||
BigIntegerModExp(v, g, x, n);
|
||||
tpe.password.len = BigIntegerToBytes(v, (unsigned char *)pwbuf);
|
||||
|
||||
BigIntegerFree(v);
|
||||
BigIntegerFree(x);
|
||||
BigIntegerFree(g);
|
||||
BigIntegerFree(n);
|
||||
return true;
|
||||
}
|
||||
|
||||
static u16_t
|
||||
chksum(u16_t sum, const u8_t *data, u16_t len)
|
||||
{
|
||||
u16_t t;
|
||||
const u8_t *dataptr;
|
||||
const u8_t *last_byte;
|
||||
|
||||
dataptr = data;
|
||||
last_byte = data + len - 1;
|
||||
|
||||
while(dataptr < last_byte) { /* At least two more bytes */
|
||||
t = (dataptr[0] << 8) + dataptr[1];
|
||||
sum += t;
|
||||
if(sum < t) {
|
||||
sum++; /* carry */
|
||||
}
|
||||
dataptr += 2;
|
||||
}
|
||||
|
||||
if(dataptr == last_byte) {
|
||||
t = (dataptr[0] << 8) + 0;
|
||||
sum += t;
|
||||
if(sum < t) {
|
||||
sum++; /* carry */
|
||||
}
|
||||
}
|
||||
|
||||
/* Return sum in host byte order. */
|
||||
return sum;
|
||||
}
|
||||
|
||||
static void
|
||||
ead_send_packet_clone(struct ead_packet *pkt)
|
||||
{
|
||||
u16_t len, sum;
|
||||
|
||||
memcpy(pktbuf, pkt, offsetof(struct ead_packet, msg));
|
||||
memcpy(pktbuf->eh.ether_shost, ethmac, 6);
|
||||
memcpy(pktbuf->eh.ether_dhost, pkt->eh.ether_shost, 6);
|
||||
|
||||
/* ip header */
|
||||
len = sizeof(struct ead_packet) - sizeof(struct ether_header) + ntohl(pktbuf->msg.len);
|
||||
pktbuf->len[0] = len >> 8;
|
||||
pktbuf->len[1] = len & 0xff;
|
||||
memcpy(pktbuf->srcipaddr, &pkt->msg.ip, 4);
|
||||
memcpy(pktbuf->destipaddr, pkt->srcipaddr, 4);
|
||||
|
||||
/* ip checksum */
|
||||
pktbuf->ipchksum = 0;
|
||||
sum = chksum(0, (void *) &pktbuf->vhl, UIP_IPH_LEN);
|
||||
if (sum == 0)
|
||||
sum = 0xffff;
|
||||
pktbuf->ipchksum = htons(~sum);
|
||||
|
||||
/* udp header */
|
||||
pktbuf->srcport = pkt->destport;
|
||||
pktbuf->destport = pkt->srcport;
|
||||
|
||||
/* udp checksum */
|
||||
len -= UIP_IPH_LEN;
|
||||
pktbuf->udplen = htons(len);
|
||||
pktbuf->udpchksum = 0;
|
||||
sum = len + UIP_PROTO_UDP;
|
||||
sum = chksum(sum, (void *) &pktbuf->srcipaddr[0], 8); /* src, dest ip */
|
||||
sum = chksum(sum, (void *) &pktbuf->srcport, len);
|
||||
if (sum == 0)
|
||||
sum = 0xffff;
|
||||
pktbuf->udpchksum = htons(~sum);
|
||||
pcap_sendpacket(pcap_fp, (void *) pktbuf, sizeof(struct ead_packet) + ntohl(pktbuf->msg.len));
|
||||
}
|
||||
|
||||
static void
|
||||
set_state(int nstate)
|
||||
{
|
||||
if (state == nstate)
|
||||
return;
|
||||
|
||||
if (nstate < state) {
|
||||
if ((nstate < EAD_TYPE_GET_PRIME) &&
|
||||
(state >= EAD_TYPE_GET_PRIME)) {
|
||||
t_serverclose(ts);
|
||||
ts = NULL;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
switch(state) {
|
||||
case EAD_TYPE_SET_USERNAME:
|
||||
if (!prepare_password())
|
||||
goto error;
|
||||
ts = t_serveropenraw(&tpe, tce);
|
||||
if (!ts)
|
||||
goto error;
|
||||
break;
|
||||
case EAD_TYPE_GET_PRIME:
|
||||
B = t_servergenexp(ts);
|
||||
break;
|
||||
case EAD_TYPE_SEND_A:
|
||||
skey = t_servergetkey(ts, &A);
|
||||
if (!skey)
|
||||
goto error;
|
||||
|
||||
ead_set_key(skey);
|
||||
break;
|
||||
}
|
||||
done:
|
||||
state = nstate;
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_ping(struct ead_packet *pkt, int len, int *nstate)
|
||||
{
|
||||
struct ead_msg *msg = &pktbuf->msg;
|
||||
struct ead_msg_pong *pong = EAD_DATA(msg, pong);
|
||||
int slen;
|
||||
|
||||
slen = strlen(dev_name);
|
||||
if (slen > 1024)
|
||||
slen = 1024;
|
||||
|
||||
msg->len = htonl(sizeof(struct ead_msg_pong) + slen);
|
||||
strncpy(pong->name, dev_name, slen);
|
||||
pong->name[slen] = 0;
|
||||
pong->auth_type = htons(EAD_AUTH_MD5);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_set_username(struct ead_packet *pkt, int len, int *nstate)
|
||||
{
|
||||
struct ead_msg *msg = &pkt->msg;
|
||||
struct ead_msg_user *user = EAD_DATA(msg, user);
|
||||
|
||||
set_state(EAD_TYPE_SET_USERNAME); /* clear old state */
|
||||
strncpy(username, user->username, sizeof(username));
|
||||
username[sizeof(username) - 1] = 0;
|
||||
|
||||
msg = &pktbuf->msg;
|
||||
msg->len = 0;
|
||||
|
||||
*nstate = EAD_TYPE_GET_PRIME;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_get_prime(struct ead_packet *pkt, int len, int *nstate)
|
||||
{
|
||||
struct ead_msg *msg = &pktbuf->msg;
|
||||
struct ead_msg_salt *salt = EAD_DATA(msg, salt);
|
||||
|
||||
msg->len = htonl(sizeof(struct ead_msg_salt));
|
||||
salt->prime = tce->index - 1;
|
||||
salt->len = ts->s.len;
|
||||
memcpy(salt->salt, ts->s.data, ts->s.len);
|
||||
memcpy(salt->ext_salt, pw_saltbuf, MAXSALTLEN);
|
||||
|
||||
*nstate = EAD_TYPE_SEND_A;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_send_a(struct ead_packet *pkt, int len, int *nstate)
|
||||
{
|
||||
struct ead_msg *msg = &pkt->msg;
|
||||
struct ead_msg_number *number = EAD_DATA(msg, number);
|
||||
len = ntohl(msg->len) - sizeof(struct ead_msg_number);
|
||||
|
||||
if (len > MAXPARAMLEN + 1)
|
||||
return false;
|
||||
|
||||
A.len = len;
|
||||
A.data = abuf;
|
||||
memcpy(A.data, number->data, len);
|
||||
|
||||
msg = &pktbuf->msg;
|
||||
number = EAD_DATA(msg, number);
|
||||
msg->len = htonl(sizeof(struct ead_msg_number) + B->len);
|
||||
memcpy(number->data, B->data, B->len);
|
||||
|
||||
*nstate = EAD_TYPE_SEND_AUTH;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_send_auth(struct ead_packet *pkt, int len, int *nstate)
|
||||
{
|
||||
struct ead_msg *msg = &pkt->msg;
|
||||
struct ead_msg_auth *auth = EAD_DATA(msg, auth);
|
||||
|
||||
if (t_serververify(ts, auth->data) != 0) {
|
||||
DEBUG(2, "Client authentication failed\n");
|
||||
*nstate = EAD_TYPE_SET_USERNAME;
|
||||
return false;
|
||||
}
|
||||
|
||||
msg = &pktbuf->msg;
|
||||
auth = EAD_DATA(msg, auth);
|
||||
msg->len = htonl(sizeof(struct ead_msg_auth));
|
||||
|
||||
DEBUG(2, "Client authentication successful\n");
|
||||
memcpy(auth->data, t_serverresponse(ts), sizeof(auth->data));
|
||||
|
||||
*nstate = EAD_TYPE_SEND_CMD;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_send_cmd(struct ead_packet *pkt, int len, int *nstate)
|
||||
{
|
||||
struct ead_msg *msg = &pkt->msg;
|
||||
struct ead_msg_cmd *cmd = EAD_ENC_DATA(msg, cmd);
|
||||
struct ead_msg_cmd_data *cmddata;
|
||||
struct timeval tv, to, tn;
|
||||
int pfd[2], fd;
|
||||
fd_set fds;
|
||||
pid_t pid;
|
||||
bool stream = false;
|
||||
int timeout;
|
||||
int type;
|
||||
int datalen;
|
||||
|
||||
datalen = ead_decrypt_message(msg) - sizeof(struct ead_msg_cmd);
|
||||
if (datalen <= 0)
|
||||
return false;
|
||||
|
||||
type = ntohs(cmd->type);
|
||||
timeout = ntohs(cmd->timeout);
|
||||
|
||||
FD_ZERO(&fds);
|
||||
cmd->data[datalen] = 0;
|
||||
switch(type) {
|
||||
case EAD_CMD_NORMAL:
|
||||
if (pipe(pfd) < 0)
|
||||
return false;
|
||||
|
||||
fcntl(pfd[0], F_SETFL, O_NONBLOCK | fcntl(pfd[0], F_GETFL));
|
||||
child_pending = true;
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
close(pfd[0]);
|
||||
fd = open("/dev/null", O_RDWR);
|
||||
if (fd > 0) {
|
||||
dup2(fd, 0);
|
||||
dup2(pfd[1], 1);
|
||||
dup2(pfd[1], 2);
|
||||
}
|
||||
system((char *)cmd->data);
|
||||
exit(0);
|
||||
} else if (pid > 0) {
|
||||
close(pfd[1]);
|
||||
if (!timeout)
|
||||
timeout = EAD_CMD_TIMEOUT;
|
||||
|
||||
stream = true;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
case EAD_CMD_BACKGROUND:
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
/* close stdin, stdout, stderr, replace with fd to /dev/null */
|
||||
fd = open("/dev/null", O_RDWR);
|
||||
if (fd > 0) {
|
||||
dup2(fd, 0);
|
||||
dup2(fd, 1);
|
||||
dup2(fd, 2);
|
||||
}
|
||||
system((char *)cmd->data);
|
||||
exit(0);
|
||||
} else if (pid > 0) {
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
msg = &pktbuf->msg;
|
||||
cmddata = EAD_ENC_DATA(msg, cmd_data);
|
||||
|
||||
if (stream) {
|
||||
int nfds, bytes;
|
||||
|
||||
/* send keepalive packets every 200 ms so that the client doesn't timeout */
|
||||
gettimeofday(&to, NULL);
|
||||
memcpy(&tn, &to, sizeof(tn));
|
||||
tv.tv_usec = PCAP_TIMEOUT * 1000;
|
||||
tv.tv_sec = 0;
|
||||
do {
|
||||
cmddata->done = 0;
|
||||
FD_SET(pfd[0], &fds);
|
||||
nfds = select(pfd[0] + 1, &fds, NULL, NULL, &tv);
|
||||
bytes = 0;
|
||||
if (nfds > 0) {
|
||||
bytes = read(pfd[0], cmddata->data, 1024);
|
||||
if (bytes < 0)
|
||||
bytes = 0;
|
||||
}
|
||||
if (!bytes && !child_pending)
|
||||
break;
|
||||
DEBUG(3, "Sending %d bytes of console data, type=%d, timeout=%d\n", bytes, ntohl(msg->type), timeout);
|
||||
ead_encrypt_message(msg, sizeof(struct ead_msg_cmd_data) + bytes);
|
||||
ead_send_packet_clone(pkt);
|
||||
gettimeofday(&tn, NULL);
|
||||
} while (tn.tv_sec < to.tv_sec + timeout);
|
||||
if (child_pending) {
|
||||
kill(pid, SIGKILL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
cmddata->done = 1;
|
||||
ead_encrypt_message(msg, sizeof(struct ead_msg_cmd_data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
parse_message(struct ead_packet *pkt, int len)
|
||||
{
|
||||
bool (*handler)(struct ead_packet *pkt, int len, int *nstate);
|
||||
int min_len = sizeof(struct ead_packet);
|
||||
int nstate = state;
|
||||
int type = ntohl(pkt->msg.type);
|
||||
|
||||
if ((type >= EAD_TYPE_GET_PRIME) &&
|
||||
(state != type))
|
||||
return;
|
||||
|
||||
if ((type != EAD_TYPE_PING) &&
|
||||
((ntohs(pkt->msg.sid) & EAD_INSTANCE_MASK) >>
|
||||
EAD_INSTANCE_SHIFT) != instance->id)
|
||||
return;
|
||||
|
||||
switch(type) {
|
||||
case EAD_TYPE_PING:
|
||||
handler = handle_ping;
|
||||
break;
|
||||
case EAD_TYPE_SET_USERNAME:
|
||||
handler = handle_set_username;
|
||||
min_len += sizeof(struct ead_msg_user);
|
||||
break;
|
||||
case EAD_TYPE_GET_PRIME:
|
||||
handler = handle_get_prime;
|
||||
break;
|
||||
case EAD_TYPE_SEND_A:
|
||||
handler = handle_send_a;
|
||||
min_len += sizeof(struct ead_msg_number);
|
||||
break;
|
||||
case EAD_TYPE_SEND_AUTH:
|
||||
handler = handle_send_auth;
|
||||
min_len += sizeof(struct ead_msg_auth);
|
||||
break;
|
||||
case EAD_TYPE_SEND_CMD:
|
||||
handler = handle_send_cmd;
|
||||
min_len += sizeof(struct ead_msg_cmd) + sizeof(struct ead_msg_encrypted);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (len < min_len) {
|
||||
DEBUG(2, "discarding packet: message too small\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pktbuf->msg.magic = htonl(EAD_MAGIC);
|
||||
pktbuf->msg.type = htonl(type + 1);
|
||||
pktbuf->msg.nid = htons(nid);
|
||||
pktbuf->msg.sid = pkt->msg.sid;
|
||||
pktbuf->msg.len = 0;
|
||||
|
||||
if (handler(pkt, len, &nstate)) {
|
||||
DEBUG(2, "sending response to packet type %d: %d\n", type + 1, ntohl(pktbuf->msg.len));
|
||||
/* format response packet */
|
||||
ead_send_packet_clone(pkt);
|
||||
}
|
||||
set_state(nstate);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
|
||||
{
|
||||
struct ead_packet *pkt = (struct ead_packet *) bytes;
|
||||
|
||||
if (h->len < sizeof(struct ead_packet))
|
||||
return;
|
||||
|
||||
if (pkt->eh.ether_type != htons(ETHERTYPE_IP))
|
||||
return;
|
||||
|
||||
if (memcmp(pkt->eh.ether_dhost, "\xff\xff\xff\xff\xff\xff", 6) != 0)
|
||||
return;
|
||||
|
||||
if (pkt->proto != UIP_PROTO_UDP)
|
||||
return;
|
||||
|
||||
if (pkt->destport != htons(EAD_PORT))
|
||||
return;
|
||||
|
||||
if (pkt->msg.magic != htonl(EAD_MAGIC))
|
||||
return;
|
||||
|
||||
if (h->len < sizeof(struct ead_packet) + ntohl(pkt->msg.len))
|
||||
return;
|
||||
|
||||
if ((pkt->msg.nid != 0xffff) &&
|
||||
(pkt->msg.nid != htons(nid)))
|
||||
return;
|
||||
|
||||
parse_message(pkt, h->len);
|
||||
}
|
||||
|
||||
static void
|
||||
ead_pcap_reopen(bool first)
|
||||
{
|
||||
static char errbuf[PCAP_ERRBUF_SIZE] = "";
|
||||
|
||||
if (pcap_fp_rx && (pcap_fp_rx != pcap_fp))
|
||||
pcap_close(pcap_fp_rx);
|
||||
|
||||
if (pcap_fp)
|
||||
pcap_close(pcap_fp);
|
||||
|
||||
pcap_fp_rx = NULL;
|
||||
do {
|
||||
if (instance->bridge[0]) {
|
||||
pcap_fp_rx = ead_open_pcap(instance->bridge, errbuf, 1);
|
||||
pcap_fp = ead_open_pcap(instance->ifname, errbuf, 0);
|
||||
} else {
|
||||
pcap_fp = ead_open_pcap(instance->ifname, errbuf, 1);
|
||||
}
|
||||
|
||||
if (!pcap_fp_rx)
|
||||
pcap_fp_rx = pcap_fp;
|
||||
if (first && !pcap_fp) {
|
||||
DEBUG(1, "WARNING: unable to open interface '%s'\n", instance->ifname);
|
||||
first = false;
|
||||
}
|
||||
if (!pcap_fp)
|
||||
sleep(1);
|
||||
} while (!pcap_fp);
|
||||
pcap_setfilter(pcap_fp_rx, &pktfilter);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ead_pktloop(void)
|
||||
{
|
||||
while (1) {
|
||||
if (pcap_dispatch(pcap_fp_rx, 1, handle_packet, NULL) < 0) {
|
||||
ead_pcap_reopen(false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
usage(const char *prog)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [<options>]\n"
|
||||
"Options:\n"
|
||||
"\t-B Run in background mode\n"
|
||||
"\t-d <device> Set the device to listen on\n"
|
||||
"\t-D <name> Set the name of the device visible to clients\n"
|
||||
"\t-p <file> Set the password file for authenticating\n"
|
||||
"\t-P <file> Write a pidfile\n"
|
||||
"\n", prog);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
server_handle_sigchld(int sig)
|
||||
{
|
||||
struct ead_instance *in;
|
||||
struct list_head *p;
|
||||
int pid = 0;
|
||||
wait(&pid);
|
||||
|
||||
list_for_each(p, &instances) {
|
||||
in = list_entry(p, struct ead_instance, list);
|
||||
if (pid != in->pid)
|
||||
continue;
|
||||
|
||||
in->pid = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
instance_handle_sigchld(int sig)
|
||||
{
|
||||
int pid = 0;
|
||||
wait(&pid);
|
||||
child_pending = false;
|
||||
}
|
||||
|
||||
static void
|
||||
start_server(struct ead_instance *i)
|
||||
{
|
||||
if (!nonfork) {
|
||||
i->pid = fork();
|
||||
if (i->pid != 0) {
|
||||
if (i->pid < 0)
|
||||
i->pid = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
instance = i;
|
||||
signal(SIGCHLD, instance_handle_sigchld);
|
||||
ead_pcap_reopen(true);
|
||||
ead_pktloop();
|
||||
pcap_close(pcap_fp);
|
||||
if (pcap_fp_rx != pcap_fp)
|
||||
pcap_close(pcap_fp_rx);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
start_servers(bool restart)
|
||||
{
|
||||
struct ead_instance *in;
|
||||
struct list_head *p;
|
||||
|
||||
list_for_each(p, &instances) {
|
||||
in = list_entry(p, struct ead_instance, list);
|
||||
if (in->pid > 0)
|
||||
continue;
|
||||
|
||||
sleep(1);
|
||||
start_server(in);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stop_server(struct ead_instance *in, bool do_free)
|
||||
{
|
||||
if (in->pid > 0)
|
||||
kill(in->pid, SIGKILL);
|
||||
in->pid = 0;
|
||||
if (do_free) {
|
||||
list_del(&in->list);
|
||||
free(in);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
server_handle_sigint(int sig)
|
||||
{
|
||||
struct ead_instance *in;
|
||||
struct list_head *p, *tmp;
|
||||
|
||||
list_for_each_safe(p, tmp, &instances) {
|
||||
in = list_entry(p, struct ead_instance, list);
|
||||
stop_server(in, true);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static int
|
||||
check_bridge_port(const char *br, const char *port, void *arg)
|
||||
{
|
||||
struct ead_instance *in;
|
||||
struct list_head *p;
|
||||
|
||||
list_for_each(p, &instances) {
|
||||
in = list_entry(p, struct ead_instance, list);
|
||||
|
||||
if (strcmp(in->ifname, port) != 0)
|
||||
continue;
|
||||
|
||||
in->br_check = true;
|
||||
if (strcmp(in->bridge, br) == 0)
|
||||
break;
|
||||
|
||||
strncpy(in->bridge, br, sizeof(in->bridge));
|
||||
DEBUG(2, "assigning port %s to bridge %s\n", in->ifname, in->bridge);
|
||||
stop_server(in, false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
check_bridge(const char *name, void *arg)
|
||||
{
|
||||
br_foreach_port(name, check_bridge_port, arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
check_all_interfaces(void)
|
||||
{
|
||||
struct ead_instance *in;
|
||||
struct list_head *p;
|
||||
|
||||
br_foreach_bridge(check_bridge, NULL);
|
||||
|
||||
/* look for interfaces that are no longer part of a bridge */
|
||||
list_for_each(p, &instances) {
|
||||
in = list_entry(p, struct ead_instance, list);
|
||||
|
||||
if (in->br_check) {
|
||||
in->br_check = false;
|
||||
} else if (in->bridge[0]) {
|
||||
DEBUG(2, "removing port %s from bridge %s\n", in->ifname, in->bridge);
|
||||
in->bridge[0] = 0;
|
||||
stop_server(in, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct ead_instance *in;
|
||||
struct timeval tv;
|
||||
const char *pidfile = NULL;
|
||||
bool background = false;
|
||||
int n_iface = 0;
|
||||
int fd, ch;
|
||||
|
||||
if (argc == 1)
|
||||
return usage(argv[0]);
|
||||
|
||||
INIT_LIST_HEAD(&instances);
|
||||
while ((ch = getopt(argc, argv, "Bd:D:fhp:P:")) != -1) {
|
||||
switch(ch) {
|
||||
case 'B':
|
||||
background = true;
|
||||
break;
|
||||
case 'f':
|
||||
nonfork = true;
|
||||
break;
|
||||
case 'h':
|
||||
return usage(argv[0]);
|
||||
case 'd':
|
||||
in = malloc(sizeof(struct ead_instance));
|
||||
memset(in, 0, sizeof(struct ead_instance));
|
||||
INIT_LIST_HEAD(&in->list);
|
||||
strncpy(in->ifname, optarg, sizeof(in->ifname) - 1);
|
||||
list_add(&in->list, &instances);
|
||||
in->id = n_iface++;
|
||||
break;
|
||||
case 'D':
|
||||
dev_name = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
passwd_file = optarg;
|
||||
break;
|
||||
case 'P':
|
||||
pidfile = optarg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
signal(SIGCHLD, server_handle_sigchld);
|
||||
signal(SIGINT, server_handle_sigint);
|
||||
signal(SIGTERM, server_handle_sigint);
|
||||
signal(SIGKILL, server_handle_sigint);
|
||||
|
||||
if (!n_iface) {
|
||||
fprintf(stderr, "Error: ead needs at least one interface\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (background) {
|
||||
if (fork() > 0)
|
||||
exit(0);
|
||||
|
||||
fd = open("/dev/null", O_RDWR);
|
||||
dup2(fd, 0);
|
||||
dup2(fd, 1);
|
||||
dup2(fd, 2);
|
||||
}
|
||||
|
||||
if (pidfile) {
|
||||
char pid[8];
|
||||
int len;
|
||||
|
||||
unlink(pidfile);
|
||||
fd = open(pidfile, O_CREAT|O_WRONLY|O_EXCL, 0644);
|
||||
if (fd > 0) {
|
||||
len = sprintf(pid, "%d\n", getpid());
|
||||
write(fd, pid, len);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/* randomize the mac address */
|
||||
get_random_bytes(ethmac + 3, 3);
|
||||
nid = *(((u16_t *) ethmac) + 2);
|
||||
|
||||
start_servers(false);
|
||||
br_init();
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
while (1) {
|
||||
check_all_interfaces();
|
||||
start_servers(true);
|
||||
sleep(1);
|
||||
}
|
||||
br_shutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user