Add support for L2TP secrets in mtpd.
diff --git a/l2tp.c b/l2tp.c
index 367ca28..11b587a 100644
--- a/l2tp.c
+++ b/l2tp.c
@@ -27,6 +27,7 @@
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/if_pppolac.h>
+#include <openssl/md5.h>
#include "mtpd.h"
@@ -67,10 +68,13 @@
#define HOST_NAME htons(7)
#define ASSIGNED_TUNNEL htons(9)
#define WINDOW_SIZE htons(10)
+#define CHALLENGE htons(11)
+#define CHALLENGE_RESPONSE htons(13)
#define ASSIGNED_SESSION htons(14)
#define CALL_SERIAL_NUMBER htons(15)
#define FRAMING_TYPE htons(19)
#define CONNECT_SPEED htons(24)
+#define RANDOM_VECTOR htons(36)
#define MESSAGE_FLAG 0xC802
#define MESSAGE_MASK 0xCB0F
@@ -81,6 +85,7 @@
#define ACK_SIZE 12
#define MESSAGE_HEADER_SIZE 20
#define ATTRIBUTE_HEADER_SIZE 6
+#define MAX_ATTRIBUTE_SIZE 1024
static uint16_t local_tunnel;
static uint16_t local_session;
@@ -92,6 +97,12 @@
static uint16_t state;
static int acknowledged;
+#define CHALLENGE_SIZE 32
+
+static char *secret;
+static int secret_length;
+static uint8_t challenge[CHALLENGE_SIZE];
+
/* According to RFC 2661 page 46, an exponential backoff strategy is required
* for retransmission. However, it might waste too much time waiting for IPsec
* negotiation. Here we use the same interval to keep things simple. */
@@ -219,6 +230,9 @@
static int get_attribute_raw(uint16_t type, void *value, int size)
{
int offset = MESSAGE_HEADER_SIZE;
+ uint8_t *vector = NULL;
+ int vector_length = 0;
+
while (incoming.length >= offset + ATTRIBUTE_HEADER_SIZE) {
struct attribute *p = (struct attribute *)&incoming.buffer[offset];
uint16_t flag = ntohs(p->flag);
@@ -229,15 +243,61 @@
if (length < 0 || offset > incoming.length) {
break;
}
+ if (p->vendor) {
+ continue;
+ }
+ if (p->type != type) {
+ if (p->type == RANDOM_VECTOR && !ATTRIBUTE_HIDDEN(flag)) {
+ vector = p->value;
+ vector_length = length;
+ }
+ continue;
+ }
- /* Currently we do not support hidden attributes. */
- if (!ATTRIBUTE_HIDDEN(flag) && !p->vendor && p->type == type) {
+ if (!ATTRIBUTE_HIDDEN(flag)) {
if (size > length) {
size = length;
}
memcpy(value, p->value, size);
return size;
}
+
+ if (!secret || !vector) {
+ return 0;
+ } else {
+ uint8_t buffer[MAX_ATTRIBUTE_SIZE];
+ uint8_t hash[MD5_DIGEST_LENGTH];
+ MD5_CTX ctx;
+ int i = 0;
+
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, &type, sizeof(uint16_t));
+ MD5_Update(&ctx, secret, secret_length);
+ MD5_Update(&ctx, vector, vector_length);
+ MD5_Final(hash, &ctx);
+
+ while (i + MD5_DIGEST_LENGTH <= length) {
+ int j;
+ for (j = 0; j < MD5_DIGEST_LENGTH; ++j) {
+ buffer[i + j] = p->value[i + j] ^ hash[j];
+ }
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, secret, secret_length);
+ MD5_Update(&ctx, &buffer[i], MD5_DIGEST_LENGTH);
+ MD5_Final(hash, &ctx);
+ i += MD5_DIGEST_LENGTH;
+ }
+
+ length = buffer[0] << 8 | buffer[1];
+ if (i == 0 || length > i - 2) {
+ return 0;
+ }
+ if (size > length) {
+ size = length;
+ }
+ memcpy(value, &buffer[2], size);
+ return size;
+ }
}
return 0;
}
@@ -266,6 +326,17 @@
add_attribute_u32(FRAMING_CAPABILITIES, htonl(3));
add_attribute_u16(ASSIGNED_TUNNEL, local_tunnel);
add_attribute_u16(WINDOW_SIZE, htons(1));
+
+ if (argc >= 3) {
+ int i;
+ for (i = 0; i < CHALLENGE_SIZE; ++i) {
+ challenge[i] = random();
+ }
+ add_attribute_raw(CHALLENGE, challenge, CHALLENGE_SIZE);
+ secret = argv[2];
+ secret_length = strlen(argv[2]);
+ }
+
send_packet();
return TIMEOUT_INTERVAL;
}
@@ -295,11 +366,49 @@
return pppox;
}
+static uint8_t *compute_response(uint8_t type, void *challenge, int size)
+{
+ static uint8_t response[MD5_DIGEST_LENGTH];
+ MD5_CTX ctx;
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, &type, sizeof(uint8_t));
+ MD5_Update(&ctx, secret, secret_length);
+ MD5_Update(&ctx, challenge, size);
+ MD5_Final(response, &ctx);
+ return response;
+}
+
+static int verify_challenge()
+{
+ if (secret) {
+ uint8_t response[MD5_DIGEST_LENGTH];
+ if (get_attribute_raw(CHALLENGE_RESPONSE, response, MD5_DIGEST_LENGTH)
+ != MD5_DIGEST_LENGTH) {
+ return 0;
+ }
+ return !memcmp(compute_response(SCCRP, challenge, CHALLENGE_SIZE),
+ response, MD5_DIGEST_LENGTH);
+ }
+ return 1;
+}
+
+static void answer_challenge()
+{
+ if (secret) {
+ uint8_t challenge[MAX_ATTRIBUTE_SIZE];
+ int size = get_attribute_raw(CHALLENGE, challenge, MAX_ATTRIBUTE_SIZE);
+ if (size > 0) {
+ uint8_t *response = compute_response(SCCCN, challenge, size);
+ add_attribute_raw(CHALLENGE_RESPONSE, response, MD5_DIGEST_LENGTH);
+ }
+ }
+}
+
static int l2tp_process()
{
uint16_t sequence = local_sequence;
- uint16_t tunnel;
- uint16_t session;
+ uint16_t tunnel = 0;
+ uint16_t session = 0;
if (!recv_packet(&session)) {
return acknowledged ? 0 : TIMEOUT_INTERVAL;
@@ -310,15 +419,18 @@
switch(incoming.message) {
case SCCRP:
if (state == SCCRQ) {
- if (get_attribute_u16(ASSIGNED_TUNNEL, &tunnel) && tunnel) {
+ if (get_attribute_u16(ASSIGNED_TUNNEL, &tunnel) && tunnel
+ && verify_challenge()) {
remote_tunnel = tunnel;
log_print(DEBUG, "Received SCCRP (remote_tunnel = %d) -> "
"Sending SCCCN", remote_tunnel);
state = SCCCN;
+ answer_challenge();
set_message(0, SCCCN);
break;
}
- log_print(DEBUG, "Received SCCRP without assigned tunnel");
+ log_print(DEBUG, "Received SCCRP without %s", tunnel ?
+ "valid challenge response" : "assigned tunnel");
log_print(ERROR, "Protocol error");
return -PROTOCOL_ERROR;
}
@@ -445,7 +557,7 @@
struct protocol l2tp = {
.name = "l2tp",
- .usage = "<server> <port>",
+ .usage = "<server> <port> [secret]",
.connect = l2tp_connect,
.process = l2tp_process,
.timeout = l2tp_timeout,