// Copyright (c) 2002 FireBrick (Andrews & Arnold Ltd / Watchfront Ltd) - GPL licenced
// vim: sw=8 ts=8
-char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.81 2005/02/08 01:20:38 bodea Exp $";
+char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.85 2005/03/10 03:08:08 bodea Exp $";
#include <arpa/inet.h>
#include <assert.h>
CONFIG("l2tp_secret", l2tpsecret, STRING),
CONFIG("primary_dns", default_dns1, IPv4),
CONFIG("secondary_dns", default_dns2, IPv4),
- CONFIG("save_state", save_state, BOOL),
CONFIG("primary_radius", radiusserver[0], IPv4),
CONFIG("secondary_radius", radiusserver[1], IPv4),
CONFIG("primary_radius_port", radiusport[0], SHORT),
static void sigterm_handler(int sig);
static void sigquit_handler(int sig);
static void sigchild_handler(int sig);
-static void read_state(void);
-static void dump_state(void);
static void build_chap_response(char *challenge, uint8_t id, uint16_t challenge_length, char **challenge_response);
static void update_config(void);
static void read_config_file(void);
static void plugins_done(void);
static void processcontrol(uint8_t *buf, int len, struct sockaddr_in *addr, int alen);
static tunnelidt new_tunnel(void);
-static int unhide_avp(uint8_t *avp, tunnelidt t, sessionidt s, uint16_t length);
+static void unhide_value(uint8_t *value, size_t len, uint16_t type, uint8_t *vector, size_t vec_len);
// return internal time (10ths since process startup)
static clockt now(void)
int s = lookup_ipmap(ip);
CSTAT(sessionbyip);
- if (s > 0 && s < MAXSESSION && session[s].tunnel)
+ if (s > 0 && s < MAXSESSION && session[s].opened)
return (sessionidt) s;
return 0;
s = lookup_ipv6map(ip);
}
- if (s > 0 && s < MAXSESSION && session[s].tunnel)
+ if (s > 0 && s < MAXSESSION && session[s].opened)
return s;
return 0;
int s;
CSTAT(sessionbyuser);
- for (s = 1; s < MAXSESSION ; ++s)
+ for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
{
+ if (!session[s].opened)
+ continue;
+
if (session[s].walled_garden)
continue; // Skip walled garden users.
sendarp(ifr.ifr_ifindex, mac, ip);
}
-// Find session by username, 0 for not found
static sessiont *sessiontbysessionidt(sessionidt s)
{
- if (!s || s > MAXSESSION) return NULL;
+ if (!s || s >= MAXSESSION) return NULL;
return &session[s];
}
static sessionidt sessionidtbysessiont(sessiont *s)
{
sessionidt val = s-session;
- if (s < session || val > MAXSESSION) return 0;
+ if (s < session || val >= MAXSESSION) return 0;
return val;
}
//
void throttle_session(sessionidt s, int rate_in, int rate_out)
{
- if (!session[s].tunnel)
+ if (!session[s].opened)
return; // No-one home.
if (!*session[s].user)
// add/remove filters from session (-1 = no change)
static void filter_session(sessionidt s, int filter_in, int filter_out)
{
- if (!session[s].tunnel)
+ if (!session[s].opened)
return; // No-one home.
if (!*session[s].user)
CSTAT(sessionshutdown);
- if (!session[s].tunnel)
+ if (!session[s].opened)
{
- LOG(3, s, session[s].tunnel, "Called sessionshutdown on a session with no tunnel.\n");
+ LOG(3, s, session[s].tunnel, "Called sessionshutdown on an unopened session.\n");
return; // not a live session
}
run_plugins(PLUGIN_KILL_SESSION, &data);
}
- if (session[s].opened && !walled_garden && !session[s].die)
+ if (!walled_garden && !session[s].die)
{
// RADIUS Stop message
uint16_t r = session[s].radius;
}
if (!session[s].die)
- session[s].die = now() + 150; // Clean up in 15 seconds
+ session[s].die = TIME + 150; // Clean up in 15 seconds
// update filter refcounts
if (session[s].filter_in) ip_filters[session[s].filter_in - 1].used--;
}
// kill a session now
-static void sessionkill(sessionidt s, char *reason)
+void sessionkill(sessionidt s, char *reason)
{
CSTAT(sessionkill);
- session[s].die = now();
+ if (!session[s].opened) // not alive
+ return;
+
+ if (session[s].next)
+ {
+ LOG(0, s, session[s].tunnel, "Tried to kill a session with next pointer set (%d)\n", session[s].next);
+ return;
+ }
+
+ session[s].die = TIME;
sessionshutdown(s, reason); // close radius/routes, etc.
if (session[s].radius)
radiusclear(session[s].radius, s); // cant send clean accounting data, session is killed
controlfree = c;
}
// kill sessions
- for (s = 1; s < MAXSESSION; s++)
+ for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
if (session[s].tunnel == t)
sessionkill(s, reason);
LOG(1, 0, t, "Shutting down tunnel %d (%s)\n", t, reason);
// close session
- for (s = 1; s < MAXSESSION; s++)
+ for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
if (session[s].tunnel == t)
sessionshutdown(s, reason);
tunnel[t].state = TUNNELDIE;
- tunnel[t].die = now() + 700; // Clean up in 70 seconds
+ tunnel[t].die = TIME + 700; // Clean up in 70 seconds
cluster_send_tunnel(t);
// TBA - should we wait for sessions to stop?
{ // Send StopCCN
{ // control
uint16_t message = 0xFFFF; // message type
uint8_t fatal = 0;
- uint8_t mandatorymessage = 0;
+ uint8_t mandatory = 0;
uint8_t chap = 0; // if CHAP being used
uint16_t asession = 0; // assigned session
uint32_t amagic = 0; // magic number
return;
}
- if ((*buf & 0xCA) != 0xC8)
+ // control messages must have bits 0x80|0x40|0x08
+ // (type, length and sequence) set, and bits 0x02|0x01
+ // (offset and priority) clear
+ if ((*buf & 0xCB) != 0xC8)
{
LOG(1, s, t, "Bad control header %02X\n", *buf);
STAT(tunnel_rx_errors);
if (l)
{ // if not a null message
// process AVPs
- while (l && !(fatal & 0x80))
+ while (l && !(fatal & 0x80)) // 0x80 = mandatory AVP
{
uint16_t n = (ntohs(*(uint16_t *) p) & 0x3FF);
uint8_t *b = p;
uint8_t flags = *p;
uint16_t mtype;
- p += n; // next
- if (l < n)
+ if (n > l)
{
LOG(1, s, t, "Invalid length in AVP\n");
STAT(tunnel_rx_errors);
- fatal = flags;
return;
}
+ p += n; // next
l -= n;
+ if (flags & 0x3C) // reserved bits, should be clear
+ {
+ LOG(1, s, t, "Unrecognised AVP flags %02X\n", *b);
+ fatal = flags;
+ continue; // next
+ }
+ b += 2;
+ if (*(uint16_t *) (b))
+ {
+ LOG(2, s, t, "Unknown AVP vendor %d\n", ntohs(*(uint16_t *) (b)));
+ fatal = flags;
+ continue; // next
+ }
+ b += 2;
+ mtype = ntohs(*(uint16_t *) (b));
+ b += 2;
+ n -= 6;
+
if (flags & 0x40)
{
+ uint16_t orig_len;
+
// handle hidden AVPs
if (!*config->l2tpsecret)
{
fatal = flags;
continue;
}
+ if (n < 8)
+ {
+ LOG(2, s, t, "Short hidden AVP.\n");
+ fatal = flags;
+ continue;
+ }
+
LOG(4, s, t, "Hidden AVP\n");
+
// Unhide the AVP
- n = unhide_avp(b, t, s, n);
- if (n == 0)
+ unhide_value(b, n, mtype, session[s].random_vector, session[s].random_vector_length);
+
+ orig_len = ntohs(*(uint16_t *) b);
+ if (orig_len > n + 2)
{
fatal = flags;
continue;
}
+
+ b += 2;
+ n = orig_len;
}
- if (*b & 0x3C)
- {
- LOG(1, s, t, "Unrecognised AVP flags %02X\n", *b);
- fatal = flags;
- continue; // next
- }
- b += 2;
- if (*(uint16_t *) (b))
- {
- LOG(2, s, t, "Unknown AVP vendor %d\n", ntohs(*(uint16_t *) (b)));
- fatal = flags;
- continue; // next
- }
- b += 2;
- mtype = ntohs(*(uint16_t *) (b));
- b += 2;
- n -= 6;
LOG(4, s, t, " AVP %d (%s) len %d\n", mtype, avp_name(mtype), n);
switch (mtype)
{
case 0: // message type
message = ntohs(*(uint16_t *) b);
+ mandatory = flags & 0x80;
LOG(4, s, t, " Message type = %d (%s)\n", *b, l2tp_message_type(message));
- mandatorymessage = flags;
break;
case 1: // result code
{
if (!sessionfree)
{
STAT(session_overflow);
- tunnelshutdown(t, "No free sessions");
+ LOG(1, 0, t, "No free sessions");
+ return;
}
else
{
c = controlnew(11); // sending ICRP
session[s].id = sessionid++;
- session[s].opened = time(NULL);
+ session[s].opened = time_now;
session[s].tunnel = t;
session[s].far = asession;
session[s].last_packet = time_now;
break;
default:
STAT(tunnel_rx_errors);
- if (mandatorymessage & 0x80)
- tunnelshutdown(t, "Unknown message");
+ if (mandatory)
+ tunnelshutdown(t, "Unknown message type");
else
LOG(1, s, t, "Unknown message type %d\n", message);
break;
l -= 2;
}
- if (s && !session[s].tunnel) // Is something wrong??
+ if (s && !session[s].opened) // Is something wrong??
{
if (!config->cluster_iam_master)
{
}
- LOG(1, s, t, "UDP packet contains session %d but no session[%d].tunnel "
- "exists (LAC said tunnel = %d). Dropping packet.\n", s, s, t);
-
+ LOG(1, s, t, "UDP packet contains session which is not opened. Dropping packet.\n");
STAT(tunnel_rx_errors);
return;
}
if (s > config->cluster_highest_sessionid)
s = 1;
- if (!session[s].tunnel) // Session isn't in use
+ if (!session[s].opened) // Session isn't in use
continue;
if (!session[s].die && session[s].ip && !(session[s].flags & SF_IPCP_ACKED))
memset(ip_address_pool, 0, sizeof(ippoolt) * MAXIPPOOL);
// Put all the sessions on the free list marked as undefined.
- for (i = 1; i < MAXSESSION - 1; i++)
+ for (i = 1; i < MAXSESSION; i++)
{
session[i].next = i + 1;
session[i].tunnel = T_UNDEF; // mark it as not filled in.
sessionfree = 1;
// Mark all the tunnels as undefined (waiting to be filled in by a download).
- for (i = 1; i < MAXTUNNEL- 1; i++)
+ for (i = 1; i < MAXTUNNEL; i++)
tunnel[i].state = TUNNELUNDEF; // mark it as not filled in.
if (!*hostname)
for (i = 0; i < MAXSESSION; ++i)
{
int ipid;
- if (!session[i].ip || !session[i].tunnel)
+ if (!(session[i].opened && session[i].ip))
continue;
+
ipid = - lookup_ipmap(htonl(session[i].ip));
if (session[i].ip_pool_index < 0)
initrad();
initippool();
- read_state();
-
signal(SIGHUP, sighup_handler);
signal(SIGTERM, sigterm_handler);
signal(SIGINT, sigterm_handler);
static void sigterm_handler(int sig)
{
LOG(1, 0, 0, "Shutting down cleanly\n");
- if (config->save_state)
- dump_state();
-
main_quit++;
}
;
}
-static void read_state()
-{
- struct stat sb;
- int i;
- ippoolt itmp;
- FILE *f;
- char magic[sizeof(DUMP_MAGIC) - 1];
- uint32_t buf[2];
-
- if (!config->save_state)
- {
- unlink(STATEFILE);
- return ;
- }
-
- if (stat(STATEFILE, &sb) < 0)
- {
- unlink(STATEFILE);
- return ;
- }
-
- if (sb.st_mtime < (time(NULL) - 60))
- {
- LOG(0, 0, 0, "State file is too old to read, ignoring\n");
- unlink(STATEFILE);
- return ;
- }
-
- f = fopen(STATEFILE, "r");
- unlink(STATEFILE);
-
- if (!f)
- {
- LOG(0, 0, 0, "Can't read state file: %s\n", strerror(errno));
- exit(1);
- }
-
- if (fread(magic, sizeof(magic), 1, f) != 1 || strncmp(magic, DUMP_MAGIC, sizeof(magic)))
- {
- LOG(0, 0, 0, "Bad state file magic\n");
- exit(1);
- }
-
- LOG(1, 0, 0, "Reading state information\n");
- if (fread(buf, sizeof(buf), 1, f) != 1 || buf[0] > MAXIPPOOL || buf[1] != sizeof(ippoolt))
- {
- LOG(0, 0, 0, "Error/mismatch reading ip pool header from state file\n");
- exit(1);
- }
-
- if (buf[0] > ip_pool_size)
- {
- LOG(0, 0, 0, "ip pool has shrunk! state = %d, current = %d\n", buf[0], ip_pool_size);
- exit(1);
- }
-
- LOG(2, 0, 0, "Loading %u ip addresses\n", buf[0]);
- for (i = 0; i < buf[0]; i++)
- {
- if (fread(&itmp, sizeof(itmp), 1, f) != 1)
- {
- LOG(0, 0, 0, "Error reading ip %d from state file: %s\n", i, strerror(errno));
- exit(1);
- }
-
- if (itmp.address != ip_address_pool[i].address)
- {
- LOG(0, 0, 0, "Mismatched ip %d from state file: pool may only be extended\n", i);
- exit(1);
- }
-
- memcpy(&ip_address_pool[i], &itmp, sizeof(itmp));
- }
-
- if (fread(buf, sizeof(buf), 1, f) != 1 || buf[0] != MAXTUNNEL || buf[1] != sizeof(tunnelt))
- {
- LOG(0, 0, 0, "Error/mismatch reading tunnel header from state file\n");
- exit(1);
- }
-
- LOG(2, 0, 0, "Loading %u tunnels\n", MAXTUNNEL);
- if (fread(tunnel, sizeof(tunnelt), MAXTUNNEL, f) != MAXTUNNEL)
- {
- LOG(0, 0, 0, "Error reading tunnel data from state file\n");
- exit(1);
- }
-
- for (i = 0; i < MAXTUNNEL; i++)
- {
- tunnel[i].controlc = 0;
- tunnel[i].controls = NULL;
- tunnel[i].controle = NULL;
- if (*tunnel[i].hostname)
- LOG(3, 0, 0, "Created tunnel for %s\n", tunnel[i].hostname);
- }
-
- if (fread(buf, sizeof(buf), 1, f) != 1 || buf[0] != MAXSESSION || buf[1] != sizeof(sessiont))
- {
- LOG(0, 0, 0, "Error/mismatch reading session header from state file\n");
- exit(1);
- }
-
- LOG(2, 0, 0, "Loading %u sessions\n", MAXSESSION);
- if (fread(session, sizeof(sessiont), MAXSESSION, f) != MAXSESSION)
- {
- LOG(0, 0, 0, "Error reading session data from state file\n");
- exit(1);
- }
-
- for (i = 0; i < MAXSESSION; i++)
- {
- session[i].tbf_in = 0;
- session[i].tbf_out = 0;
- if (session[i].opened)
- {
- LOG(2, i, 0, "Loaded active session for user %s\n", session[i].user);
- if (session[i].ip)
- sessionsetup(session[i].tunnel, i);
- }
- }
-
- fclose(f);
- LOG(0, 0, 0, "Loaded saved state information\n");
-}
-
-static void dump_state()
-{
- FILE *f;
- uint32_t buf[2];
-
- if (!config->save_state)
- return;
-
- do
- {
- if (!(f = fopen(STATEFILE, "w")))
- break;
-
- LOG(1, 0, 0, "Dumping state information\n");
-
- if (fwrite(DUMP_MAGIC, sizeof(DUMP_MAGIC) - 1, 1, f) != 1)
- break;
-
- LOG(2, 0, 0, "Dumping %u ip addresses\n", ip_pool_size);
- buf[0] = ip_pool_size;
- buf[1] = sizeof(ippoolt);
- if (fwrite(buf, sizeof(buf), 1, f) != 1)
- break;
- if (fwrite(ip_address_pool, sizeof(ippoolt), ip_pool_size, f) != ip_pool_size)
- break;
-
- LOG(2, 0, 0, "Dumping %u tunnels\n", MAXTUNNEL);
- buf[0] = MAXTUNNEL;
- buf[1] = sizeof(tunnelt);
- if (fwrite(buf, sizeof(buf), 1, f) != 1)
- break;
- if (fwrite(tunnel, sizeof(tunnelt), MAXTUNNEL, f) != MAXTUNNEL)
- break;
-
- LOG(2, 0, 0, "Dumping %u sessions\n", MAXSESSION);
- buf[0] = MAXSESSION;
- buf[1] = sizeof(sessiont);
- if (fwrite(buf, sizeof(buf), 1, f) != 1)
- break;
- if (fwrite(session, sizeof(sessiont), MAXSESSION, f) != MAXSESSION)
- break;
-
- if (fclose(f) == 0)
- return ; // OK
- }
- while (0);
-
- LOG(0, 0, 0, "Can't write state information: %s\n", strerror(errno));
- unlink(STATEFILE);
-}
-
static void build_chap_response(char *challenge, uint8_t id, uint16_t challenge_length, char **challenge_response)
{
MD5_CTX ctx;
LOG(3, s, t, "Doing session setup for session\n");
- if (!session[s].ip || session[s].ip == 0xFFFFFFFE)
+ if (!session[s].ip)
{
assign_ip_address(s);
if (!session[s].ip)
{
for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
{
- if (!session[s].tunnel) // Not an in-use session.
+ if (!session[s].opened) // Not an in-use session.
continue;
run_plugins(PLUGIN_NEW_SESSION_MASTER, &session[s]);
for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
{
int idle;
- if (!session[s].tunnel)
+ if (!session[s].opened)
continue;
idle = time_now - session[s].last_packet;
for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
{
int open = 0, d;
- if (!session[s].tunnel)
+ if (!session[s].opened)
continue;
d = time_now - session[s].opened;
/* Unhide an avp.
*
- * This unencodes the AVP using the L2TP CHAP secret and the
- * previously stored random vector. It replaces the hidden data with
- * the cleartext data and returns the length of the cleartext data
- * (including the AVP "header" of 6 bytes).
- *
- * Based on code from rp-l2tpd by Roaring Penguin Software Inc.
+ * This unencodes the AVP using the L2TP secret and the previously
+ * stored random vector. It overwrites the hidden data with the
+ * unhidden AVP subformat.
*/
-static int unhide_avp(uint8_t *avp, tunnelidt t, sessionidt s, uint16_t length)
+static void unhide_value(uint8_t *value, size_t len, uint16_t type, uint8_t *vector, size_t vec_len)
{
MD5_CTX ctx;
- uint8_t *cursor;
uint8_t digest[16];
- uint8_t working_vector[16];
- uint16_t hidden_length;
- uint8_t type[2];
- size_t done, todo;
- uint8_t *output;
-
- // Find the AVP type.
- type[0] = *(avp + 4);
- type[1] = *(avp + 5);
-
- // Line up with the hidden data
- cursor = output = avp + 6;
+ uint8_t *last;
+ size_t d = 0;
// Compute initial pad
MD5Init(&ctx);
- MD5Update(&ctx, type, 2);
+ MD5Update(&ctx, (uint8_t) (type >> 8) & 0xff, 1);
+ MD5Update(&ctx, (uint8_t) type & 0xff, 1);
MD5Update(&ctx, config->l2tpsecret, strlen(config->l2tpsecret));
- MD5Update(&ctx, session[s].random_vector, session[s].random_vector_length);
+ MD5Update(&ctx, vector, vec_len);
MD5Final(digest, &ctx);
- // Get hidden length
- hidden_length = ((uint16_t) (digest[0] ^ cursor[0])) * 256 + (uint16_t) (digest[1] ^ cursor[1]);
-
- // Keep these for later use
- working_vector[0] = *cursor;
- working_vector[1] = *(cursor + 1);
- cursor += 2;
-
- if (hidden_length > length - 8)
- {
- LOG(1, s, t, "Hidden length %d too long in AVP of length %d\n", (int) hidden_length, (int) length);
- return 0;
- }
+ // pointer to last decoded 16 octets
+ last = value;
- /* Decrypt remainder */
- done = 2;
- todo = hidden_length;
- while (todo)
+ while (len > 0)
{
- working_vector[done] = *cursor;
- *output = digest[done] ^ *cursor;
- ++output;
- ++cursor;
- --todo;
- ++done;
- if (done == 16 && todo)
+ // calculate a new pad based on the last decoded block
+ if (d >= sizeof(digest))
{
- // Compute new digest
- done = 0;
MD5Init(&ctx);
MD5Update(&ctx, config->l2tpsecret, strlen(config->l2tpsecret));
- MD5Update(&ctx, &working_vector, 16);
+ MD5Update(&ctx, last, sizeof(digest));
MD5Final(digest, &ctx);
+
+ d = 0;
+ last = value;
}
- }
- return hidden_length + 6;
+ *value++ ^= digest[d++];
+ len--;
+ }
}
static int ip_filter_port(ip_filter_portt *p, uint16_t port)