From 12f16f60c19b470a3a4e5ee145485095dc674dee Mon Sep 17 00:00:00 2001 From: bodea Date: Sat, 27 Nov 2004 05:19:53 +0000 Subject: [PATCH] - Revise CCP, send ConfigReq once only. - Check control serial before clearing window, prevents looping tunnel setup in some instances. - Add configuration syntax for adding named access lists (work in progress). --- Changes | 5 +- Docs/manual.html | 100 +++++-- Docs/startup-config.5 | 108 +++++++- cli.c | 631 ++++++++++++++++++++++++++++++++++++++++-- l2tpns.c | 84 +++--- l2tpns.h | 76 ++++- l2tpns.spec | 2 +- ppp.c | 4 +- radius.c | 4 +- 9 files changed, 920 insertions(+), 94 deletions(-) diff --git a/Changes b/Changes index 59dde80..1d2bdcd 100644 --- a/Changes +++ b/Changes @@ -1,8 +1,11 @@ -* Thu Nov 25 2004 Brendan O'Dea 2.0.9 +* Sat Nov 27 2004 Brendan O'Dea 2.0.9 - Revise CCP, send ConfigReq once only. - Don't copy the old buffer into Config{Nak,Rej} LCP responses (oops); add length checks when appending. - Have makeppp print a backtrace on overflow. +- Check control serial before clearing window, prevents looping tunnel + setup in some instances. +- Add configuration syntax for adding named access lists (work in progress). * Sat Nov 20 2004 Brendan O'Dea 2.0.8 - Ignore gateway address in Framed-Route (from Jonathan McDowell). diff --git a/Docs/manual.html b/Docs/manual.html index 08d9408..13748a9 100644 --- a/Docs/manual.html +++ b/Docs/manual.html @@ -52,7 +52,8 @@ H3 {
  • Interception
  • Authentication
  • Plugins
  • -
  • Walled Garden
  • +
  • Walled Garden
  • +
  • Filtering
  • Clustering
  • Routing
  • Performance
  • @@ -200,29 +201,34 @@ software upgrade.
  • primary_radius (ip address)
  • secondary_radius (ip address)
    -Sets the radius servers used for both authentication and accounting. -If the primary server does not respond, then the secondary radius -server will be tried. +Sets the RADIUS servers used for both authentication and accounting. +If the primary server does not respond, then the secondary RADIUS +server will be tried.
    +Note: in addition to the source IP address and +identifier, the RADIUS server must include the source +port when detecting duplicates to supress (in order to cope with a +large number of sessions comming on-line simultaneously l2tpns uses a +set of udp sockets, each with a seperate identifier).
  • primary_radius_port (short)
  • secondary_radius_port (short)
    -Sets the authentication ports for the primary and secondary radius +Sets the authentication ports for the primary and secondary RADIUS servers. The accounting port is one more than the authentication -port. If no radius ports are given, the authentication port defaults +port. If no RADIUS ports are given, the authentication port defaults to 1645, and the accounting port to 1646.
  • radius_accounting (boolean)
    -If set to true, then radius accounting packets will be sent. This +If set to true, then RADIUS accounting packets will be sent. This means that a Start record will be sent when the session is successfully authenticated, and a Stop record will be sent when the session is closed.
  • radius_secret (string)
    -This secret will be used in all radius queries. If this is not set then -radius queries will fail. +This secret will be used in all RADIUS queries. If this is not set then +RADIUS queries will fail.
  • bind_address (ip address)
    @@ -338,6 +344,40 @@ Where peer specifies the BGP neighbour as either a hostname or IP address, as is the remote AS number and keepalive, hold are the timer values in seconds. +

    Named access-lists are configured using one of the commands: +

    +
    ip access-list standard name +
    ip access-list extended name +
    + +

    Subsequent lines prefixed with permit or deny +define the body of the access-list. Standard access-list syntax: +

    +
    {permit|deny} + {host|source source-wildcard|any} + [{host|destination destination-wildcard|any}] +
    + +Extended access-lists: + +
    +
    {permit|deny} ip + {host|source source-wildcard|any} + {host|destination destination-wildcard|any} +
    {permit|deny} udp + {host|source source-wildcard|any} + [{eq|neq|gt|lt} port|range from to] + {host|destination destination-wildcard|any} + [{eq|neq|gt|lt} port|range from to] +
    {permit|deny} tcp + {host|source source-wildcard|any} + [{eq|neq|gt|lt} port|range from to] + {host|destination destination-wildcard|any} + [{eq|neq|gt|lt} port|range from to] + [{established|{match-any|match-all} + {+|-}{fin|syn|rst|psh|ack|urg} ...] +
    +

    users

    Usernames and passwords for the command-line interface are stored in @@ -500,19 +540,19 @@ IP Address Used Session User
  • show radius
    -Show a summary of the in-use radius sessions. This list should not be very -long, as radius sessions should be cleaned up as soon as they are used. The +Show a summary of the in-use RADIUS sessions. This list should not be very +long, as RADIUS sessions should be cleaned up as soon as they are used. The columns listed are: - + - -
    RadiusThe ID of the radius request. This is - sent in the packet to the radius server for identification.
    RadiusThe ID of the RADIUS request. This is + sent in the packet to the RADIUS server for identification.
    StateThe state of the request - WAIT, CHAP, AUTH, IPCP, START, STOP, NULL.
    SessionThe session ID that this radius +
    SessionThe session ID that this RADIUS request is associated with
    RetryIf a response does not appear to the request, it will retry at this time. This is a unix timestamp.
    TryRetry count. The radius request is +
    TryRetry count. The RADIUS request is discarded after 3 retries.

    @@ -553,7 +593,7 @@ current session for that username will be forwarded to the given host/port. Specify no snoop username to disable interception for the session.

    -If you want interception to be permanent, you will have to modify the radius +If you want interception to be permanent, you will have to modify the RADIUS response for the user. See Interception.

  • @@ -564,7 +604,7 @@ session. Specify no throttle username to disable throttling for the current session.

    If you want throttling to be permanent, you will have to modify the -radius response for the user. See Throttling. +RADIUS response for the user. See Throttling.

    @@ -660,7 +700,7 @@ desire. You must first enable the global setting throttle_speed before this will be activated.

    If you wish a session to be throttled permanently, you should set the -Vendor-Specific radius value Cisco-Avpair="throttle=yes", which +Vendor-Specific RADIUS value Cisco-Avpair="throttle=yes", which will be handled by the autothrottle module.

    Otherwise, you can enable and disable throttling an active session using @@ -684,7 +724,7 @@ and no snoop username CLI commands. These will enable interception immediately.

    If you wish the user to be intercepted whenever they reconnect, you will -need to modify the radius response to include the Vendor-Specific value +need to modify the RADIUS response to include the Vendor-Specific value Cisco-Avpair="intercept=yes". For this feature to be enabled, you need to have the autosnoop module loaded.

    @@ -694,11 +734,11 @@ Whenever a session connects, it is not fully set up until authentication is completed. The remote end must send a PPP CHAP or PPP PAP authentication request to l2tpns.

    -This request is sent to the radius server, which will hopefully respond with +This request is sent to the RADIUS server, which will hopefully respond with Auth-Accept or Auth-Reject.

    If Auth-Accept is received, the session is set up and an IP address is -assigned. The radius server can include a Framed-IP-Address field in the +assigned. The RADIUS server can include a Framed-IP-Address field in the reply, and that address will be assigned to the client. It can also include specific DNS servers, and a Framed-Route if that is required.

    @@ -708,7 +748,7 @@ walled garden module is loaded, in which case the user still receives the PPP AUTHACK, but their session is flagged as being a garden'd user, and they should not receive any service.

    -The radius reply can also contain a Vendor-Specific attribute called +The RADIUS reply can also contain a Vendor-Specific attribute called Cisco-Avpair. This field is a freeform text field that most Cisco devices understand to contain configuration instructions for the session. In the case of l2tpns it is expected to be of the form @@ -758,7 +798,7 @@ supplied structure: - - -
    EventDescriptionParameters
    pre_authThis is called after a radius response has been + This is called after a RADIUS response has been received, but before it has been processed by the code. This will allow you to modify the response in some way. @@ -775,7 +815,7 @@ supplied structure:
    post_authThis is called after a radius response has been + This is called after a RADIUS response has been received, and the basic checks have been performed. This is what the garden module uses to force authentication to be accepted. @@ -855,7 +895,7 @@ supplied structure:
    radius_responseThis is called whenever a radius response includes a + This is called whenever a RADIUS response includes a Cisco-Avpair value. The value is split up into key=value pairs, and each is processed through all modules. @@ -901,7 +941,7 @@ Walled Garden is implemented so that you can provide perhaps limited service to sessions that incorrectly authenticate.

    Whenever a session provides incorrect authentication, and the -radius server responds with Auth-Reject, the walled garden module +RADIUS server responds with Auth-Reject, the walled garden module (if loaded) will force authentication to succeed, but set the flag garden in the session structure, and adds an iptables rule to the garden_users chain to force all packets for the session's IP @@ -926,6 +966,14 @@ command: iptables -t nat -L garden -nvx +

    Filtering

    + +Sessions may be filtered by specifying Filter-Id attributes in +the RADIUS reply. filter.in specifies that the named +access-list filter should be applied to traffic from the +customer, filter.out specifies a list for traffic to the +customer. +

    Clustering

    An l2tpns cluster consists of of one* or more servers configured with diff --git a/Docs/startup-config.5 b/Docs/startup-config.5 index 35527f3..0c13c8a 100644 --- a/Docs/startup-config.5 +++ b/Docs/startup-config.5 @@ -2,7 +2,7 @@ .de Id .ds Dt \\$4 \\$5 .. -.Id $Id: startup-config.5,v 1.1 2004/11/17 15:08:19 bodea Exp $ +.Id $Id: startup-config.5,v 1.2 2004/11/27 05:19:54 bodea Exp $ .TH STARTUP-CONFIG 5 "\*(Dt" L2TPNS "File Formats and Conventions" .SH NAME startup\-config \- configuration file for l2tpns @@ -199,5 +199,111 @@ is the remote AS number and .IR keepalive , .I hold are the timer values in seconds. +.SS NAMED ACCESS LISTS +Named access lists may be defined with either of +.IP +.BI "ip access\-list standard " name +.br +.BI "ip access\-list extended " name +.PP +Subsequent lines starting with +.B permit +or +.B deny +define the body of the access\-list. +.PP +.B Standard Access Lists +.RS 4n +Standard access lists are defined with: +.IP +.RB { permit | deny } +.IR source " [" dest ] +.PP +Where +.I source +and +.I dest +specify IP matches using one of: +.IP +.I address +.I wildard +.br +.B host +.I address +.br +.B any +.PP +.I address +and +.I wildard +are in dotted-quad notation, bits in the +.I wildard +indicate which address bits in +.I address +are relevant to the match (0 = exact match; 1 = don't care). +.PP +The shorthand +.RB ' host +.IR address ' +is equivalent to +.RI ' address +.BR 0.0.0.0 '; +.RB ' any ' +to +.RB ' 0.0.0.0 +.BR 255.255.255.255 '. +.RE +.PP +.B Extended Access Lists +.RS 4n +Extended access lists are defined with: +.IP +.RB { permit | deny } +.I proto +.IR source " [" ports "] " dest " [" ports "] [" flags ] +.PP +Where +.I proto +is one of +.BR ip , +.B tcp +or +.BR udp , +and +.I source +and +.I dest +are as described above for standard lists. +.PP +For +.B tcp +and +.B udp +matches, source and destination may be optionally followed by a +.I ports +specification: +.IP +.RB { eq | neq | gt | lt } +.I port +.br +.B +range +.I from to +.PP +.B tcp +matches may also specify +.I flags +to match against tcp header flags: +.IP +.RB { match\-any | match\-all } +.RB { + | - }{ fin | syn | rst | psh | ack | urg } +\&... +.br +.B established +.PP +.RB ' established ' +is shorthand for +.RB ' "match-any +ack +rst -syn" '. +.RE .SH SEE ALSO .BR l2tpns (8) diff --git a/cli.c b/cli.c index 435c4cf..0fca55f 100644 --- a/cli.c +++ b/cli.c @@ -2,7 +2,7 @@ // vim: sw=8 ts=8 char const *cvs_name = "$Name: $"; -char const *cvs_id_cli = "$Id: cli.c,v 1.28 2004/11/16 07:54:32 bodea Exp $"; +char const *cvs_id_cli = "$Id: cli.c,v 1.29 2004/11/27 05:19:53 bodea Exp $"; #include #include @@ -38,14 +38,15 @@ extern radiust *radius; extern ippoolt *ip_address_pool; extern struct Tstats *_statistics; static struct cli_def *cli = NULL; -extern struct configt *config; -extern struct config_descriptt config_values[]; +extern configt *config; +extern config_descriptt config_values[]; #ifdef RINGBUFFER extern struct Tringbuffer *ringbuffer; #endif extern struct cli_session_actions *cli_session_actions; extern struct cli_tunnel_actions *cli_tunnel_actions; extern tbft *filter_list; +extern ip_filtert *ip_filters; static char *debug_levels[] = { "CRIT", @@ -110,6 +111,14 @@ static int cmd_no_suspend_bgp(struct cli_def *cli, char *command, char **argv, i static int cmd_restart_bgp(struct cli_def *cli, char *command, char **argv, int argc); #endif /* BGP */ +#define MODE_CONFIG_NACL 9 +static int cmd_ip_access_list(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_no_ip_access_list(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_ip_access_list_rule(struct cli_def *cli, char *command, char **argv, int argc); + +/* match if b is a substr of a */ +#define MATCH(a,b) (!strncmp((a), (b), strlen(b))) + void init_cli(char *hostname) { FILE *f; @@ -203,6 +212,16 @@ void init_cli(char *hostname) cli_register_command(cli, NULL, "set", cmd_set, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Set a configuration variable"); + c = cli_register_command(cli, NULL, "ip", NULL, PRIVILEGE_PRIVILEGED, MODE_CONFIG, NULL); + cli_register_command(cli, c, "access-list", cmd_ip_access_list, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Add named access-list"); + + cli_register_command(cli, NULL, "permit", cmd_ip_access_list_rule, PRIVILEGE_PRIVILEGED, MODE_CONFIG_NACL, "Permit rule"); + cli_register_command(cli, NULL, "deny", cmd_ip_access_list_rule, PRIVILEGE_PRIVILEGED, MODE_CONFIG_NACL, "Deny rule"); + + c = cli_register_command(cli, NULL, "no", NULL, PRIVILEGE_UNPRIVILEGED, MODE_CONFIG, NULL); + c2 = cli_register_command(cli, c, "ip", NULL, PRIVILEGE_PRIVILEGED, MODE_CONFIG, NULL); + cli_register_command(cli, c2, "access-list", cmd_no_ip_access_list, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Remove named access-list"); + // Enable regular processing cli_regular(cli, regular_stuff); @@ -267,7 +286,7 @@ void cli_do(int sockfd) require_auth = addr.sin_addr.s_addr != inet_addr("127.0.0.1"); } else - LOG(0, 0, 0, 0, "getpeername() failed on cli socket. Requiring authentication: %s\n", strerror(errno)); + LOG(0, 0, 0, 0, "getpeername() failed on cli socket. Requiring authentication: %s\n", strerror(errno)); if (require_auth) { @@ -852,6 +871,8 @@ static int cmd_write_memory(struct cli_def *cli, char *command, char **argv, int return CLI_OK; } +static char const *show_access_list_rule(int extended, ip_filter_rulet *rule); + static int cmd_show_run(struct cli_def *cli, char *command, char **argv, int argc) { int i; @@ -898,17 +919,17 @@ static int cmd_show_run(struct cli_def *cli, char *command, char **argv, int arg #ifdef BGP if (config->as_number) { - int k; + int k; int h; - cli_print(cli, "# BGP"); + cli_print(cli, "# BGP"); cli_print(cli, "router bgp %u", config->as_number); for (i = 0; i < BGP_NUM_PEERS; i++) { if (!config->neighbour[i].name[0]) continue; - cli_print(cli, " neighbour %s remote-as %u", config->neighbour[i].name, config->neighbour[i].as); + cli_print(cli, " neighbour %s remote-as %u", config->neighbour[i].name, config->neighbour[i].as); k = config->neighbour[i].keepalive; h = config->neighbour[i].hold; @@ -929,6 +950,22 @@ static int cmd_show_run(struct cli_def *cli, char *command, char **argv, int arg } #endif + cli_print(cli, "# Filters"); + for (i = 0; i < MAXFILTER; i++) + { + ip_filter_rulet *rules; + if (!*ip_filters[i].name) + continue; + + cli_print(cli, "ip access-list %s %s", + ip_filters[i].extended ? "extended" : "standard", + ip_filters[i].name); + + rules = ip_filters[i].rules; + while (rules->action) + cli_print(cli, "%s", show_access_list_rule(ip_filters[i].extended, rules++)); + } + cli_print(cli, "# end"); return CLI_OK; } @@ -1877,7 +1914,7 @@ static int cmd_router_bgp(struct cli_def *cli, char *command, char **argv, int a return CLI_OK; } -static int find_bgp_neighbour(char *name) +static int find_bgp_neighbour(char const *name) { int i; int new = -1; @@ -1893,7 +1930,7 @@ static int find_bgp_neighbour(char *name) for (i = 0; i < BGP_NUM_PEERS; i++) { - if (!config->neighbour[i].name[0]) + if (!config->neighbour[i].name[0]) { if (new == -1) new = i; continue; @@ -1940,12 +1977,12 @@ static int cmd_router_bgp_neighbour(struct cli_def *cli, char *command, char **a NULL); default: - if (!strncmp("remote-as", argv[1], strlen(argv[1]))) - return cli_arg_help(cli, argv[2][1], "<1-65535>", "Autonomous system number", NULL); + if (MATCH("remote-as", argv[1])) + return cli_arg_help(cli, argv[2][1], "<1-65535>", "Autonomous system number", NULL); - if (!strncmp("timers", argv[1], strlen(argv[1]))) + if (MATCH("timers", argv[1])) { - if (argc == 3) + if (argc == 3) return cli_arg_help(cli, 0, "<1-65535>", "Keepalive time", NULL); if (argc == 4) @@ -1977,9 +2014,9 @@ static int cmd_router_bgp_neighbour(struct cli_def *cli, char *command, char **a return CLI_OK; } - if (!strncmp("remote-as", argv[1], strlen(argv[1]))) + if (MATCH("remote-as", argv[1])) { - int as = atoi(argv[2]); + int as = atoi(argv[2]); if (as < 0 || as > 65535) { cli_print(cli, "Invalid autonomous system number"); @@ -1997,7 +2034,7 @@ static int cmd_router_bgp_neighbour(struct cli_def *cli, char *command, char **a return CLI_OK; } - if (argc != 4 || strncmp("timers", argv[1], strlen(argv[1]))) + if (argc != 4 || !MATCH("timers", argv[1])) { cli_print(cli, "Invalid arguments"); return CLI_OK; @@ -2222,6 +2259,568 @@ static int cmd_restart_bgp(struct cli_def *cli, char *command, char **argv, int } #endif /* BGP*/ +static int filt; +static int find_access_list(char const *name) +{ + int i; + + for (i = 0; i < MAXFILTER; i++) + if (!(*ip_filters[i].name && strcmp(ip_filters[i].name, name))) + return i; + + return -1; +} + +static int access_list(struct cli_def *cli, char **argv, int argc, int add) +{ + int extended; + + if (CLI_HELP_REQUESTED) + { + switch (argc) + { + case 1: + return cli_arg_help(cli, 0, + "standard", "Standard syntax", + "extended", "Extended syntax", + NULL); + + case 2: + return cli_arg_help(cli, argv[1][1], + "NAME", "Access-list name", + NULL); + + default: + if (argc == 3 && !argv[2][1]) + return cli_arg_help(cli, 1, NULL); + + return CLI_OK; + } + } + + if (argc != 2) + { + cli_print(cli, "Specify access-list type and name"); + return CLI_OK; + } + + if (MATCH("standard", argv[0])) + extended = 0; + else if (MATCH("extended", argv[0])) + extended = 1; + else + { + cli_print(cli, "Invalid access-list type"); + return CLI_OK; + } + + if (strlen(argv[1]) > sizeof(ip_filters[0].name) - 1 || + strspn(argv[1], "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_") != strlen(argv[1])) + { + cli_print(cli, "Invalid access-list name"); + return CLI_OK; + } + + filt = find_access_list(argv[1]); + if (add) + { + if (filt < 0) + { + cli_print(cli, "Too many access-lists"); + return CLI_OK; + } + + // racy + if (!*ip_filters[filt].name) + { + memset(&ip_filters[filt], 0, sizeof(ip_filters[filt])); + strcpy(ip_filters[filt].name, argv[1]); + ip_filters[filt].extended = extended; + } + else if (ip_filters[filt].extended != extended) + { + cli_print(cli, "Access-list is %s", + ip_filters[filt].extended ? "extended" : "standard"); + + return CLI_OK; + } + + cli_set_configmode(cli, MODE_CONFIG_NACL, extended ? "ext-nacl" : "std-nacl"); + return CLI_OK; + } + + if (filt < 0 || !*ip_filters[filt].name) + { + cli_print(cli, "Access-list not defined"); + return CLI_OK; + } + + // racy + if (ip_filters[filt].used) + { + cli_print(cli, "Access-list in use"); + return CLI_OK; + } + + memset(&ip_filters[filt], 0, sizeof(ip_filters[filt])); + return CLI_OK; +} + +static int cmd_ip_access_list(struct cli_def *cli, char *command, char **argv, int argc) +{ + return access_list(cli, argv, argc, 1); +} + +static int cmd_no_ip_access_list(struct cli_def *cli, char *command, char **argv, int argc) +{ + return access_list(cli, argv, argc, 0); +} + +static int show_ip_wild(char *buf, ipt ip, ipt wild) +{ + int i; + if (ip == INADDR_ANY && wild == INADDR_BROADCAST) + return sprintf(buf, " any"); + + if (wild == INADDR_ANY) + return sprintf(buf, " host %s", inet_toa(ip)); + + i = sprintf(buf, " %s", inet_toa(ip)); + return i + sprintf(buf + i, " %s", inet_toa(wild)); +} + +static int show_ports(char *buf, ip_filter_portt *ports) +{ + switch (ports->op) + { + case FILTER_PORT_OP_EQ: return sprintf(buf, " eq %u", ports->port); + case FILTER_PORT_OP_NEQ: return sprintf(buf, " neq %u", ports->port); + case FILTER_PORT_OP_GT: return sprintf(buf, " gt %u", ports->port); + case FILTER_PORT_OP_LT: return sprintf(buf, " lt %u", ports->port); + case FILTER_PORT_OP_RANGE: return sprintf(buf, " range %u %u", ports->port, ports->port2); + } + + return 0; +} + +static char const *show_access_list_rule(int extended, ip_filter_rulet *rule) +{ + static char buf[256]; + char *p = buf; + + p += sprintf(p, " %s", rule->action == FILTER_ACTION_PERMIT ? "permit" : "deny"); + if (extended) + { + struct protoent *proto = getprotobynumber(rule->proto); + p += sprintf(p, " %s", proto ? proto->p_name : "ERR"); + } + + p += show_ip_wild(p, rule->src_ip, rule->src_wild); + if (!extended) + return buf; + + if (rule->proto == IPPROTO_TCP || rule->proto == IPPROTO_UDP) + p += show_ports(p, &rule->src_ports); + + p += show_ip_wild(p, rule->dst_ip, rule->dst_wild); + if (rule->proto == IPPROTO_TCP || rule->proto == IPPROTO_UDP) + p += show_ports(p, &rule->dst_ports); + + if (rule->proto == IPPROTO_TCP && (rule->tcp_sflags || rule->tcp_cflags)) + { + if (rule->tcp_flag_op == FILTER_FLAG_OP_ANY && + rule->tcp_sflags == (TCP_FLAG_ACK|TCP_FLAG_FIN) && + rule->tcp_cflags == TCP_FLAG_SYN) + { + p += sprintf(p, " established"); + } + else + { + p += sprintf(p, " match-%s", rule->tcp_flag_op == FILTER_FLAG_OP_ALL ? "all" : "any"); + if (rule->tcp_sflags & TCP_FLAG_FIN) p += sprintf(p, " +fin"); + if (rule->tcp_cflags & TCP_FLAG_FIN) p += sprintf(p, " -fin"); + if (rule->tcp_sflags & TCP_FLAG_SYN) p += sprintf(p, " +syn"); + if (rule->tcp_cflags & TCP_FLAG_SYN) p += sprintf(p, " -syn"); + if (rule->tcp_sflags & TCP_FLAG_RST) p += sprintf(p, " +rst"); + if (rule->tcp_cflags & TCP_FLAG_RST) p += sprintf(p, " -rst"); + if (rule->tcp_sflags & TCP_FLAG_PSH) p += sprintf(p, " +psh"); + if (rule->tcp_cflags & TCP_FLAG_PSH) p += sprintf(p, " -psh"); + if (rule->tcp_sflags & TCP_FLAG_ACK) p += sprintf(p, " +ack"); + if (rule->tcp_cflags & TCP_FLAG_ACK) p += sprintf(p, " -ack"); + if (rule->tcp_sflags & TCP_FLAG_URG) p += sprintf(p, " +urg"); + if (rule->tcp_cflags & TCP_FLAG_URG) p += sprintf(p, " -urg"); + } + } + + return buf; +} + +ip_filter_rulet *access_list_rule_ext(struct cli_def *cli, char *command, char **argv, int argc) +{ + static ip_filter_rulet rule; + struct in_addr addr; + int i; + int a; + + if (CLI_HELP_REQUESTED) + { + if (argc == 1) + { + cli_arg_help(cli, 0, + "ip", "Match IP packets", + "tcp", "Match TCP packets", + "udp", "Match UDP packets", + NULL); + + return NULL; + } + + // *sigh*, too darned complex + cli_arg_help(cli, 0, "RULE", "SOURCE [PORTS] DEST [PORTS] FLAGS", NULL); + return NULL; + } + + if (argc < 3) + { + cli_print(cli, "Specify rule details"); + return NULL; + } + + memset(&rule, 0, sizeof(rule)); + rule.action = (command[0] == 'p') + ? FILTER_ACTION_PERMIT + : FILTER_ACTION_DENY; + + if (MATCH("ip", argv[0])) + rule.proto = IPPROTO_IP; + else if (MATCH("udp", argv[0])) + rule.proto = IPPROTO_UDP; + else if (MATCH("tcp", argv[0])) + rule.proto = IPPROTO_TCP; + else + { + cli_print(cli, "Invalid protocol \"%s\"", argv[0]); + return NULL; + } + + for (a = 1, i = 0; i < 2; i++) + { + ipt *ip; + ipt *wild; + ip_filter_portt *port; + + if (i == 0) + { + ip = &rule.src_ip; + wild = &rule.src_wild; + port = &rule.src_ports; + } + else + { + ip = &rule.dst_ip; + wild = &rule.dst_wild; + port = &rule.dst_ports; + if (a >= argc) + { + cli_print(cli, "Specify destination"); + return NULL; + } + } + + if (MATCH("any", argv[a])) + { + *ip = INADDR_ANY; + *wild = INADDR_BROADCAST; + a++; + } + else if (MATCH("host", argv[a])) + { + if (++a >= argc) + { + cli_print(cli, "Specify host ip address"); + return NULL; + } + + if (!inet_aton(argv[a], &addr)) + { + cli_print(cli, "Cannot parse IP \"%s\"", argv[a]); + return NULL; + } + + *ip = addr.s_addr; + *wild = INADDR_ANY; + a++; + } + else + { + if (++a >= argc) + { + cli_print(cli, "Specify %s ip address and wildcard", i ? "destination" : "source"); + return NULL; + } + + if (!inet_aton(argv[a], &addr)) + { + cli_print(cli, "Cannot parse IP \"%s\"", argv[a]); + return NULL; + } + + *ip = addr.s_addr; + + if (!inet_aton(argv[++a], &addr)) + { + cli_print(cli, "Cannot parse IP \"%s\"", argv[a]); + return NULL; + } + + *wild = addr.s_addr; + } + + if (rule.proto == IPPROTO_IP || a >= argc) + continue; + + port->op = 0; + if (MATCH("eq", argv[a])) + port->op = FILTER_PORT_OP_EQ; + else if (MATCH("neq", argv[a])) + port->op = FILTER_PORT_OP_NEQ; + else if (MATCH("gt", argv[a])) + port->op = FILTER_PORT_OP_GT; + else if (MATCH("lt", argv[a])) + port->op = FILTER_PORT_OP_LT; + else if (MATCH("range", argv[a])) + port->op = FILTER_PORT_OP_RANGE; + + if (!port->op) + continue; + + if (++a >= argc) + { + cli_print(cli, "Specify port"); + return NULL; + } + + if (!(port->port = atoi(argv[a]))) + { + cli_print(cli, "Invalid port \"%s\"", argv[a]); + return NULL; + } + + a++; + if (port->op != FILTER_PORT_OP_RANGE) + continue; + + if (a >= argc) + { + cli_print(cli, "Specify port"); + return NULL; + } + + if (!(port->port2 = atoi(argv[a])) || port->port2 < port->port) + { + cli_print(cli, "Invalid port \"%s\"", argv[a]); + return NULL; + } + + a++; + } + + if (rule.proto == IPPROTO_TCP && a < argc) + { + if (MATCH("established", argv[a])) + { + rule.tcp_flag_op = FILTER_FLAG_OP_ANY; + rule.tcp_sflags = (TCP_FLAG_ACK|TCP_FLAG_FIN); + rule.tcp_cflags = TCP_FLAG_SYN; + a++; + } + else if (!strcmp(argv[a], "match-any") || !strcmp(argv[a], "match-an") || + !strcmp(argv[a], "match-all") || !strcmp(argv[a], "match-al")) + { + rule.tcp_flag_op = argv[a][7] == 'n' + ? FILTER_FLAG_OP_ANY + : FILTER_FLAG_OP_ALL; + + if (++a >= argc) + { + cli_print(cli, "Specify tcp flags"); + return NULL; + } + + while (a < argc && (argv[a][0] == '+' || argv[a][0] == '-')) + { + u8 *f; + + f = (argv[a][0] == '+') ? &rule.tcp_sflags : &rule.tcp_cflags; + + if (MATCH("fin", &argv[a][1])) *f |= TCP_FLAG_FIN; + else if (MATCH("syn", &argv[a][1])) *f |= TCP_FLAG_SYN; + else if (MATCH("rst", &argv[a][1])) *f |= TCP_FLAG_RST; + else if (MATCH("psh", &argv[a][1])) *f |= TCP_FLAG_PSH; + else if (MATCH("ack", &argv[a][1])) *f |= TCP_FLAG_ACK; + else if (MATCH("urg", &argv[a][1])) *f |= TCP_FLAG_URG; + else + { + cli_print(cli, "Invalid tcp flag \"%s\"", argv[a]); + return NULL; + } + + a++; + } + } + } + + if (a < argc) + { + cli_print(cli, "Invalid flag \"%s\"", argv[a]); + return NULL; + } + + return &rule; +} + +ip_filter_rulet *access_list_rule_std(struct cli_def *cli, char *command, char **argv, int argc) +{ + static ip_filter_rulet rule; + struct in_addr addr; + + if (CLI_HELP_REQUESTED) + { + if (argc == 1) + { + cli_arg_help(cli, argv[0][1], + "A.B.C.D", "Source address", + "any", "Any source address", + "host", "Source host", + NULL); + + return NULL; + } + + if (MATCH("any", argv[0])) + { + if (argc == 2 && !argv[1][1]) + cli_arg_help(cli, 1, NULL); + } + else if (MATCH("host", argv[0])) + { + if (argc == 2) + { + cli_arg_help(cli, argv[1][1], + "A.B.C.D", "Host address", + NULL); + } + else if (argc == 3 && !argv[2][1]) + cli_arg_help(cli, 1, NULL); + } + else + { + if (argc == 2) + { + cli_arg_help(cli, 1, + "A.B.C.D", "Wildcard bits", + NULL); + } + else if (argc == 3 && !argv[2][1]) + cli_arg_help(cli, 1, NULL); + } + + return NULL; + } + + if (argc < 1) + { + cli_print(cli, "Specify rule details"); + return NULL; + } + + memset(&rule, 0, sizeof(rule)); + rule.action = (command[0] == 'p') + ? FILTER_ACTION_PERMIT + : FILTER_ACTION_DENY; + + rule.proto = IPPROTO_IP; + if (MATCH("any", argv[0])) + { + rule.src_ip = INADDR_ANY; + rule.src_wild = INADDR_BROADCAST; + } + else if (MATCH("host", argv[0])) + { + if (argc != 2) + { + cli_print(cli, "Specify host ip address"); + return NULL; + } + + if (!inet_aton(argv[1], &addr)) + { + cli_print(cli, "Cannot parse IP \"%s\"", argv[1]); + return NULL; + } + + rule.src_ip = addr.s_addr; + rule.src_wild = INADDR_ANY; + } + else + { + if (argc > 2) + { + cli_print(cli, "Specify source ip address and wildcard"); + return NULL; + } + + if (!inet_aton(argv[0], &addr)) + { + cli_print(cli, "Cannot parse IP \"%s\"", argv[0]); + return NULL; + } + + rule.src_ip = addr.s_addr; + + if (argc > 1) + { + if (!inet_aton(argv[1], &addr)) + { + cli_print(cli, "Cannot parse IP \"%s\"", argv[1]); + return NULL; + } + + rule.src_wild = addr.s_addr; + } + else + rule.src_wild = INADDR_ANY; + } + + return &rule; +} + +static int cmd_ip_access_list_rule(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + ip_filter_rulet *rule = ip_filters[filt].extended + ? access_list_rule_ext(cli, command, argv, argc) + : access_list_rule_std(cli, command, argv, argc); + + if (!rule) + return CLI_OK; + + for (i = 0; i < MAXFILTER_RULES - 1; i++) // -1: list always terminated by empty rule + { + if (!ip_filters[filt].rules[i].action) + { + memcpy(&ip_filters[filt].rules[i], rule, sizeof(*rule)); + return CLI_OK; + } + + if (!memcmp(&ip_filters[filt].rules[i], rule, sizeof(*rule))) + return CLI_OK; + } + + cli_print(cli, "Too many rules"); + return CLI_OK; +} + // Convert a string in the form of abcd.ef12.3456 into char[6] void parsemac(char *string, char mac[6]) { diff --git a/l2tpns.c b/l2tpns.c index de3929d..54b4667 100644 --- a/l2tpns.c +++ b/l2tpns.c @@ -4,7 +4,7 @@ // 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.56 2004/11/25 02:49:18 bodea Exp $"; +char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.57 2004/11/27 05:19:53 bodea Exp $"; #include #include @@ -54,7 +54,7 @@ char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.56 2004/11/25 02:49:18 bodea Exp #endif /* BGP */ // Globals -struct configt *config = NULL; // all configuration +configt *config = NULL; // all configuration int tunfd = -1; // tun interface file handle. (network device) int udpfd = -1; // UDP file handle int controlfd = -1; // Control signal handle @@ -88,9 +88,9 @@ linked_list *loaded_plugins; linked_list *plugins[MAX_PLUGIN_TYPES]; #define membersize(STRUCT, MEMBER) sizeof(((STRUCT *)0)->MEMBER) -#define CONFIG(NAME, MEMBER, TYPE) { NAME, offsetof(struct configt, MEMBER), membersize(struct configt, MEMBER), TYPE } +#define CONFIG(NAME, MEMBER, TYPE) { NAME, offsetof(configt, MEMBER), membersize(configt, MEMBER), TYPE } -struct config_descriptt config_values[] = { +config_descriptt config_values[] = { CONFIG("debug", debug, INT), CONFIG("log_file", log_filename, STRING), CONFIG("pid_file", pid_file, STRING), @@ -146,6 +146,7 @@ sessiont *session = NULL; // Array of session structures. sessioncountt *sess_count = NULL; // Array of partial per-session traffic counters. radiust *radius = NULL; // Array of radius structures. ippoolt *ip_address_pool = NULL; // Array of dynamic IP addresses. +ip_filtert *ip_filters = NULL; // Array of named filters. static controlt *controlfree = 0; struct Tstats *_statistics = NULL; #ifdef RINGBUFFER @@ -942,8 +943,10 @@ static void controladd(controlt * c, tunnelidt t, sessionidt s) tunnel[t].controle->next = c; else tunnel[t].controls = c; + tunnel[t].controle = c; tunnel[t].controlc++; + // send now if space in window if (tunnel[t].controlc <= tunnel[t].window) { @@ -1306,10 +1309,9 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr) STAT(tunnel_rx_errors); return; } - LOG(3, ntohl(addr->sin_addr.s_addr), s, t, "Control message (%d bytes): (unacked %d) l-ns %d l-nr %d r-ns %d r-nr %d\n", - l, tunnel[t].controlc, tunnel[t].ns, tunnel[t].nr, ns, nr); - // if no tunnel specified, assign one - if (!t) + + // check for duplicate tunnel open message + if (!t && ns == 0) { int i; @@ -1323,10 +1325,15 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr) tunnel[i].port != ntohs(addr->sin_port) ) continue; t = i; + LOG(3, ntohl(addr->sin_addr.s_addr), s, t, "Duplicate SCCRQ?\n"); break; } } + LOG(3, ntohl(addr->sin_addr.s_addr), s, t, "Control message (%d bytes): (unacked %d) l-ns %d l-nr %d r-ns %d r-nr %d\n", + l, tunnel[t].controlc, tunnel[t].ns, tunnel[t].nr, ns, nr); + + // if no tunnel specified, assign one if (!t) { if (!(t = new_tunnel())) @@ -1339,8 +1346,28 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr) tunnel[t].ip = ntohl(*(ipt *) & addr->sin_addr); tunnel[t].port = ntohs(addr->sin_port); tunnel[t].window = 4; // default window - LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, " New tunnel from %u.%u.%u.%u/%u ID %d\n", tunnel[t].ip >> 24, tunnel[t].ip >> 16 & 255, tunnel[t].ip >> 8 & 255, tunnel[t].ip & 255, tunnel[t].port, t); STAT(tunnel_created); + LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, " New tunnel from %u.%u.%u.%u/%u ID %d\n", + tunnel[t].ip >> 24, tunnel[t].ip >> 16 & 255, + tunnel[t].ip >> 8 & 255, tunnel[t].ip & 255, tunnel[t].port, t); + } + + // If the 'ns' just received is not the 'nr' we're + // expecting, just send an ack and drop it. + // + // if 'ns' is less, then we got a retransmitted packet. + // if 'ns' is greater than missed a packet. Either way + // we should ignore it. + if (ns != tunnel[t].nr) + { + // is this the sequence we were expecting? + STAT(tunnel_rx_errors); + LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, " Out of sequence tunnel %d, (%d is not the expected %d)\n", + t, ns, tunnel[t].nr); + + if (l) // Is this not a ZLB? + controlnull(t); + return; } // This is used to time out old tunnels @@ -1350,7 +1377,7 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr) { int skip = tunnel[t].window; // track how many in-window packets are still in queue // some to clear maybe? - while (tunnel[t].controlc && (((tunnel[t].ns - tunnel[t].controlc) - nr) & 0x8000)) + while (tunnel[t].controlc > 0 && (((tunnel[t].ns - tunnel[t].controlc) - nr) & 0x8000)) { controlt *c = tunnel[t].controls; tunnel[t].controls = c->next; @@ -1361,22 +1388,6 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr) tunnel[t].try = 0; // we have progress } - // If the 'ns' just received is not the 'nr' we're - // expecting, just send an ack and drop it. - // - // if 'ns' is less, then we got a retransmitted packet. - // if 'ns' is greater than missed a packet. Either way - // we should ignore it. - if (ns != tunnel[t].nr) - { - // is this the sequence we were expecting? - LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, " Out of sequence tunnel %d, (%d is not the expected %d)\n", t, ns, tunnel[t].nr); - STAT(tunnel_rx_errors); - - if (l) // Is this not a ZLB? - controlnull(t); - return; - } // receiver advance (do here so quoted correctly in any sends below) if (l) tunnel[t].nr = (ns + 1); if (skip < 0) skip = 0; @@ -2480,12 +2491,12 @@ static void initdata(int optdebug, char *optconfig) LOG(0, 0, 0, 0, "Error doing malloc for _statistics: %s\n", strerror(errno)); exit(1); } - if (!(config = shared_malloc(sizeof(struct configt)))) + if (!(config = shared_malloc(sizeof(configt)))) { LOG(0, 0, 0, 0, "Error doing malloc for configuration: %s\n", strerror(errno)); exit(1); } - memset(config, 0, sizeof(struct configt)); + memset(config, 0, sizeof(configt)); time(&config->start_time); strncpy(config->config_file, optconfig, strlen(optconfig)); config->debug = optdebug; @@ -2521,6 +2532,13 @@ static void initdata(int optdebug, char *optconfig) exit(1); } +if (!(ip_filters = shared_malloc(sizeof(ip_filtert) * MAXFILTER))) +{ + LOG(0, 0, 0, 0, "Error doing malloc for ip_filters: %s\n", strerror(errno)); + exit(1); +} +memset(ip_filters, 0, sizeof(ip_filtert) * MAXFILTER); + #ifdef RINGBUFFER if (!(ringbuffer = shared_malloc(sizeof(struct Tringbuffer)))) { @@ -2572,11 +2590,11 @@ static void initdata(int optdebug, char *optconfig) _statistics->start_time = _statistics->last_reset = time(NULL); #ifdef BGP - if (!(bgp_peers = shared_malloc(sizeof(struct bgp_peer) * BGP_NUM_PEERS))) - { - LOG(0, 0, 0, 0, "Error doing malloc for bgp: %s\n", strerror(errno)); - exit(1); - } + if (!(bgp_peers = shared_malloc(sizeof(struct bgp_peer) * BGP_NUM_PEERS))) + { + LOG(0, 0, 0, 0, "Error doing malloc for bgp: %s\n", strerror(errno)); + exit(1); + } #endif /* BGP */ } diff --git a/l2tpns.h b/l2tpns.h index ccb55ef..eb575a3 100644 --- a/l2tpns.h +++ b/l2tpns.h @@ -1,5 +1,5 @@ // L2TPNS Global Stuff -// $Id: l2tpns.h,v 1.37 2004/11/25 12:41:35 bodea Exp $ +// $Id: l2tpns.h,v 1.38 2004/11/27 05:19:53 bodea Exp $ #ifndef __L2TPNS_H__ #define __L2TPNS_H__ @@ -142,7 +142,7 @@ struct cli_tunnel_actions { #define DUMP_MAGIC "L2TPNS#" VERSION "#" // structures -typedef struct routes // route +typedef struct // route { ipt ip; ipt mask; @@ -157,7 +157,7 @@ typedef struct controls // control message } controlt; -typedef struct sessions +typedef struct { sessionidt next; // next session in linked list sessionidt far; // far end session ID @@ -199,7 +199,9 @@ typedef struct sessions ipt snoop_ip; // Interception destination IP u16 snoop_port; // Interception destination port u16 sid; // near end session id. - char reserved[20]; // Space to expand structure without changing HB_VERSION + u8 filter_in; // input filter index (to ip_filters[N-1]; 0 if none) + u8 filter_out; // output filter index + char reserved[18]; // Space to expand structure without changing HB_VERSION } sessiont; @@ -207,7 +209,8 @@ sessiont; #define SF_LCP_ACKED 2 // LCP negotiated #define SF_CCP_ACKED 4 // CCP negotiated -typedef struct { +typedef struct +{ u32 cin; u32 cout; } sessioncountt; @@ -216,7 +219,7 @@ typedef struct { #define SESSIONACFC 2 // ACFC negotiated flags // 168 bytes per tunnel -typedef struct tunnels +typedef struct { tunnelidt far; // far end tunnel ID ipt ip; // Ip for far end @@ -239,7 +242,7 @@ typedef struct tunnels tunnelt; // 180 bytes per radius session -typedef struct radiuss // outstanding RADIUS requests +typedef struct // outstanding RADIUS requests { sessionidt session; // which session this applies to hasht auth; // request authenticator @@ -393,7 +396,7 @@ struct Tstats #define SET_STAT(x, y) #endif -struct configt +typedef struct { int debug; // debugging level time_t start_time; // time when l2tpns was started @@ -470,16 +473,65 @@ struct configt int hold; } neighbour[BGP_NUM_PEERS]; #endif -}; +} configt; enum config_typet { INT, STRING, UNSIGNED_LONG, SHORT, BOOL, IP, MAC }; -struct config_descriptt +typedef struct { char *key; int offset; int size; enum config_typet type; -}; +} config_descriptt; + +typedef struct +{ + u8 op; // operation +#define FILTER_PORT_OP_NONE 0 // all ports match +#define FILTER_PORT_OP_EQ 1 +#define FILTER_PORT_OP_NEQ 2 +#define FILTER_PORT_OP_GT 3 +#define FILTER_PORT_OP_LT 4 +#define FILTER_PORT_OP_RANGE 5 + portt port; + portt port2; // for range +} ip_filter_portt; + +typedef struct +{ + int action; // permit/deny +#define FILTER_ACTION_DENY 1 +#define FILTER_ACTION_PERMIT 2 + int proto; // protocol: IPPROTO_* (netinet/in.h) + ipt src_ip; // source ip + ipt src_wild; + ip_filter_portt src_ports; + ipt dst_ip; // dest ip + ipt dst_wild; + ip_filter_portt dst_ports; + u8 tcp_flag_op; // match type: any, all +#define FILTER_FLAG_OP_ANY 0 +#define FILTER_FLAG_OP_ALL 1 + u8 tcp_sflags; // flags set + u8 tcp_cflags; // flags clear +} ip_filter_rulet; + +#define TCP_FLAG_FIN 0x01 +#define TCP_FLAG_SYN 0x02 +#define TCP_FLAG_RST 0x04 +#define TCP_FLAG_PSH 0x08 +#define TCP_FLAG_ACK 0x10 +#define TCP_FLAG_URG 0x20 + +#define MAXFILTER 32 +#define MAXFILTER_RULES 32 +typedef struct +{ + char name[32]; // ACL name + int extended; // type: 0 = standard, 1 = extended + ip_filter_rulet rules[MAXFILTER_RULES]; + int used; // session ref count +} ip_filtert; // arp.c void sendarp(int ifr_idx, const unsigned char* mac, ipt ip); @@ -570,7 +622,7 @@ if (count++ < max) { \ } -extern struct configt *config; +extern configt *config; extern time_t basetime; // Time when this process started. extern time_t time_now; // Seconds since EPOCH. extern u32 last_id; diff --git a/l2tpns.spec b/l2tpns.spec index be9b52a..1a39885 100644 --- a/l2tpns.spec +++ b/l2tpns.spec @@ -43,5 +43,5 @@ rm -rf %{buildroot} %attr(644,root,root) /usr/share/man/man[58]/* %changelog -* Thu Nov 25 2004 Brendan O'Dea 2.0.9-1 +* Sat Nov 27 2004 Brendan O'Dea 2.0.9-1 - 2.0.9 release, see /usr/share/doc/l2tpns-2.0.9/Changes diff --git a/ppp.c b/ppp.c index d6db8d8..9cd9948 100644 --- a/ppp.c +++ b/ppp.c @@ -1,6 +1,6 @@ // L2TPNS PPP Stuff -char const *cvs_id_ppp = "$Id: ppp.c,v 1.30 2004/11/25 12:46:48 bodea Exp $"; +char const *cvs_id_ppp = "$Id: ppp.c,v 1.31 2004/11/27 05:19:53 bodea Exp $"; #include #include @@ -21,7 +21,7 @@ extern int tunfd; extern char hostname[]; extern u32 eth_tx; extern time_t time_now; -extern struct configt *config; +extern configt *config; static void initccp(tunnelidt t, sessionidt s); diff --git a/radius.c b/radius.c index ed8bc54..33f8bcb 100644 --- a/radius.c +++ b/radius.c @@ -1,6 +1,6 @@ // L2TPNS Radius Stuff -char const *cvs_id_radius = "$Id: radius.c,v 1.13 2004/11/25 02:45:27 bodea Exp $"; +char const *cvs_id_radius = "$Id: radius.c,v 1.14 2004/11/27 05:19:53 bodea Exp $"; #include #include @@ -22,7 +22,7 @@ extern radiust *radius; extern sessiont *session; extern tunnelt *tunnel; extern u32 sessionid; -extern struct configt *config; +extern configt *config; extern int *radfds; static const char *radius_state(int state) -- 2.20.1