/* $OpenBSD: parse.y,v 1.27 2023/04/19 13:33:37 jsg Exp $ */ /* * Copyright (c) 2002, 2003, 2004 Henning Brauer * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. * Copyright (c) 2001 Theo de Raadt. All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ %{ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "npppd_auth.h" #include "npppd.h" #ifdef USE_NPPPD_RADIUS #include "radius_req.h" #endif #include "privsep.h" #include "log.h" TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); static struct file { TAILQ_ENTRY(file) entry; FILE *stream; char *name; int lineno; int errors; } *file, *topfile; struct file *pushfile(const char *); int popfile(void); int yyparse(void); int yylex(void); int yyerror(const char *, ...) __attribute__((__format__ (printf, 1, 2))) __attribute__((__nonnull__ (1))); int kw_cmp(const void *, const void *); int lookup(char *); int lgetc(int); int lungetc(int); int findeol(void); static void tunnconf_init (struct tunnconf *, int); static void tunnconf_fini (struct tunnconf *); static struct tunnconf *tunnconf_find (const char *); static void authconf_init (struct authconf *); static void authconf_fini (struct authconf *); static void radconf_fini (struct radconf *); static struct authconf *authconf_find (const char *); static void ipcpconf_init (struct ipcpconf *); static void ipcpconf_fini (struct ipcpconf *); static struct ipcpconf *ipcpconf_find (const char *); static struct iface *iface_find (const char *); static void sa_set_in_addr_any(struct sockaddr *); struct npppd_conf *conf; struct ipcpconf *curr_ipcpconf; struct tunnconf *curr_tunnconf; struct authconf *curr_authconf; struct radconf *curr_radconf; typedef struct { union { int64_t number; char *string; struct sockaddr_storage address; struct in_addr in4_addr; bool yesno; } v; int lineno; } YYSTYPE; %} %token SET MAX_SESSION USER_MAX_SESSION %token TUNNEL LISTEN ON PROTOCOL %token MRU %token IP LCP PAP CHAP EAP MPPE CCP MSCHAPV2 STATEFUL STATELESS REQUIRED %token YES NO %token L2TP PPTP PPPOE L2TP_HOSTNAME L2TP_VENDOR_NAME L2TP_DATA_USE_SEQ %token L2TP_REQUIRE_IPSEC L2TP_LCP_RENEGOTIATION L2TP_FORCE_LCP_RENEGOTIATION %token L2TP_CTRL_IN_PKTDUMP L2TP_CTRL_OUT_PKTDUMP L2TP_DATA_IN_PKTDUMP %token L2TP_DATA_OUT_PKTDUMP PPTP_HOSTNAME %token PPTP_VENDOR_NAME PPTP_ECHO_INTERVAL PPTP_ECHO_TIMEOUT %token PPTP_CTRL_IN_PKTDUMP PPTP_CTRL_OUT_PKTDUMP PPTP_DATA_IN_PKTDUMP %token PPTP_DATA_OUT_PKTDUMP %token PPPOE_SERVICE_NAME PPPOE_ACCEPT_ANY_SERVICE PPPOE_AC_NAME %token PPPOE_DESC_IN_PKTDUMP PPPOE_DESC_OUT_PKTDUMP PPPOE_SESSION_IN_PKTDUMP %token PPPOE_SESSION_OUT_PKTDUMP %token LCP_TIMEOUT LCP_MAX_CONFIGURE LCP_MAX_TERMINATE LCP_MAX_NAK_LOOP %token LCP_KEEPALIVE LCP_KEEPALIVE_INTERVAL LCP_KEEPALIVE_RETRY_INTERVAL %token LCP_KEEPALIVE_MAX_RETRIES AUTHENTICATION_METHOD CHAP_NAME %token IPCP_TIMEOUT IPCP_MAX_CONFIGURE IPCP_MAX_TERMINATE IPCP_MAX_NAK_LOOP %token CCP_TIMEOUT CCP_MAX_CONFIGURE CCP_MAX_TERMINATE CCP_MAX_NAK_LOOP %token L2TP_HELLO_INTERVAL L2TP_HELLO_TIMEOUT L2TP_ACCEPT_DIALIN %token MPPE MPPE_KEY_LENGTH MPPE_KEY_STATE %token IDLE_TIMEOUT TCP_MSS_ADJUST INGRESS_FILTER CALLNUM_CHECK %token PIPEX DEBUG_DUMP_PKTIN DEBUG_DUMP_PKTOUT %token AUTHENTICATION TYPE LOCAL USERNAME_SUFFIX USERNAME_PREFIX EAP_CAPABLE %token STRIP_NT_DOMAIN STRIP_ATMARK_REALM USERS_FILE %token RADIUS AUTHENTICATION_SERVER ACCOUNTING_SERVER PORT %token X_TIMEOUT MAX_TRIES MAX_FAILOVERS SECRET %token POOL_ADDRESS DNS_SERVERS NBNS_SERVERS FOR STATIC DYNAMIC %token RESOLVER ALLOW_USER_SELECTED_ADDRESS %token INTERFACE ADDRESS IPCP %token BIND FROM AUTHENTICATED BY TO %token ERROR %token STRING %token NUMBER %type yesno %type address %type addressport %type optport %type in4_addr %type tunnelproto %type mppeyesno %type mppekeylen %type mppekeylen_l %type mppekeystate %type mppekeystate_l %type protobit %type protobit_l %type authtype %type authmethod %type authmethod_l %type ipcppooltype %% grammar : /* empty */ | grammar '\n' | grammar set '\n' | grammar tunnel '\n' | grammar authentication '\n' | grammar ipcp '\n' | grammar interface '\n' | grammar bind '\n' | grammar error '\n' { file->errors++; } ; set : SET MAX_SESSION NUMBER { conf->max_session = $3; } | SET USER_MAX_SESSION NUMBER { conf->user_max_session = $3; } ; /* * tunnel { } */ tunnel : TUNNEL STRING PROTOCOL tunnelproto { struct tunnconf *n; if (tunnconf_find($2) != NULL) { yyerror("tunnel name = %s is already in use.", $2); free($2); YYERROR; } if ((n = malloc(sizeof(struct tunnconf))) == NULL) { yyerror("out of memory"); free($2); YYERROR; } tunnconf_init(n, $4); switch ($4) { case NPPPD_TUNNEL_L2TP: strlcpy(n->proto.l2tp.name, $2, sizeof(n->proto.l2tp.name)); n->name = n->proto.l2tp.name; break; case NPPPD_TUNNEL_PPTP: strlcpy(n->proto.pptp.name, $2, sizeof(n->proto.pptp.name)); n->name = n->proto.pptp.name; break; case NPPPD_TUNNEL_PPPOE: strlcpy(n->proto.pppoe.name, $2, sizeof(n->proto.pppoe.name)); n->name = n->proto.pppoe.name; break; } free($2); n->protocol = $4; curr_tunnconf = n; } tunnopts { TAILQ_INSERT_TAIL(&conf->tunnconfs, curr_tunnconf, entry); switch (curr_tunnconf->protocol) { #ifdef USE_NPPPD_L2TP case NPPPD_TUNNEL_L2TP: if (TAILQ_EMPTY( &curr_tunnconf->proto.l2tp.listen)) { struct l2tp_listen_addr *addr; if ((addr = malloc(sizeof(struct l2tp_listen_addr))) == NULL) { free(curr_tunnconf); yyerror("out of memory"); YYERROR; } sa_set_in_addr_any( (struct sockaddr *)&addr->addr); TAILQ_INSERT_TAIL(&curr_tunnconf->proto. l2tp.listen, addr, entry); } TAILQ_INSERT_TAIL(&conf->l2tp_confs, &curr_tunnconf->proto.l2tp, entry); break; #endif #ifdef USE_NPPPD_PPTP case NPPPD_TUNNEL_PPTP: if (TAILQ_EMPTY( &curr_tunnconf->proto.pptp.listen)) { struct pptp_listen_addr *addr; if ((addr = malloc(sizeof(struct pptp_listen_addr))) == NULL) { free(curr_tunnconf); yyerror("out of memory"); YYERROR; } sa_set_in_addr_any( (struct sockaddr *)&addr->addr); TAILQ_INSERT_TAIL(&curr_tunnconf->proto. pptp.listen, addr, entry); } TAILQ_INSERT_TAIL(&conf->pptp_confs, &curr_tunnconf->proto.pptp, entry); break; #endif #ifdef USE_NPPPD_PPPOE case NPPPD_TUNNEL_PPPOE: TAILQ_INSERT_TAIL(&conf->pppoe_confs, &curr_tunnconf->proto.pppoe, entry); break; #endif default: yyerror("%s is not enabled.", npppd_tunnel_protocol_name( curr_tunnconf->protocol)); tunnconf_fini(curr_tunnconf); free(curr_tunnconf); YYERROR; } curr_tunnconf = NULL; } ; tunnopts : | '{' optnl tunnopt_l '}' ; tunnopt_l : /* empty */ | tunnopt_l tunnopt nl | tunnopt optnl ; tunnopt : LISTEN ON addressport { switch (curr_tunnconf->protocol) { case NPPPD_TUNNEL_L2TP: { struct l2tp_listen_addr *l_listen; if ((l_listen = malloc(sizeof( struct l2tp_listen_addr))) == NULL) { yyerror("out of memory"); YYERROR; } l_listen->addr = $3; TAILQ_INSERT_TAIL(&curr_tunnconf->proto .l2tp.listen, l_listen, entry); break; } case NPPPD_TUNNEL_PPTP: if ($3.ss_family == AF_INET6) { yyerror("listen on IPv6 address is not " "supported by pptp tunnel"); YYERROR; } { struct pptp_listen_addr *p_listen; if ((p_listen = malloc(sizeof( struct pptp_listen_addr))) == NULL) { yyerror("out of memory"); YYERROR; } p_listen->addr = $3; TAILQ_INSERT_TAIL(&curr_tunnconf->proto .pptp.listen, p_listen, entry); break; } default: yyerror("listen on address is not supported " "for specified protocol.\n"); YYERROR; } } | LISTEN ON INTERFACE STRING { switch (curr_tunnconf->protocol) { case NPPPD_TUNNEL_PPPOE: strlcpy(curr_tunnconf->proto.pppoe.if_name, $4, sizeof(curr_tunnconf->proto.pppoe.if_name)); free($4); break; default: free($4); yyerror("listen on interface is not supported " "for specified protocol.\n"); YYERROR; } } | LCP_TIMEOUT NUMBER { curr_tunnconf->lcp_timeout = $2; } | LCP_MAX_CONFIGURE NUMBER { curr_tunnconf->lcp_max_configure = $2; } | LCP_MAX_TERMINATE NUMBER { curr_tunnconf->lcp_max_terminate = $2; } | LCP_MAX_NAK_LOOP NUMBER { curr_tunnconf->lcp_max_nak_loop = $2; } | MRU NUMBER { curr_tunnconf->mru = $2; } | LCP_KEEPALIVE yesno { curr_tunnconf->lcp_keepalive = $2; } | LCP_KEEPALIVE_INTERVAL NUMBER { curr_tunnconf->lcp_keepalive_interval = $2; } | LCP_KEEPALIVE_RETRY_INTERVAL NUMBER { curr_tunnconf->lcp_keepalive_retry_interval = $2; } | LCP_KEEPALIVE_MAX_RETRIES NUMBER { curr_tunnconf->lcp_keepalive_max_retries = $2; } | AUTHENTICATION_METHOD authmethod_l { curr_tunnconf->auth_methods = $2; } | CHAP_NAME STRING { curr_tunnconf->chap_name = $2; } | IPCP_TIMEOUT NUMBER { curr_tunnconf->ipcp_timeout = $2; } | IPCP_MAX_CONFIGURE NUMBER { curr_tunnconf->ipcp_max_configure = $2; } | IPCP_MAX_TERMINATE NUMBER { curr_tunnconf->ipcp_max_terminate = $2; } | IPCP_MAX_NAK_LOOP NUMBER { curr_tunnconf->ipcp_max_nak_loop = $2; } | CCP_TIMEOUT NUMBER { curr_tunnconf->ccp_timeout = $2; } | CCP_MAX_CONFIGURE NUMBER { curr_tunnconf->ccp_max_configure = $2; } | CCP_MAX_TERMINATE NUMBER { curr_tunnconf->ccp_max_terminate = $2; } | CCP_MAX_NAK_LOOP NUMBER { curr_tunnconf->ccp_max_nak_loop = $2; } | L2TP_HOSTNAME STRING { curr_tunnconf->proto.l2tp.hostname = $2; } | L2TP_VENDOR_NAME STRING { curr_tunnconf->proto.l2tp.vendor_name = $2; } | L2TP_HELLO_INTERVAL NUMBER { curr_tunnconf->proto.l2tp.hello_interval = $2; } | L2TP_HELLO_TIMEOUT NUMBER { curr_tunnconf->proto.l2tp.hello_timeout = $2; } | L2TP_ACCEPT_DIALIN yesno { curr_tunnconf->proto.l2tp.accept_dialin = $2; } | L2TP_DATA_USE_SEQ yesno { curr_tunnconf->proto.l2tp.data_use_seq = $2; } | L2TP_REQUIRE_IPSEC yesno { curr_tunnconf->proto.l2tp.require_ipsec = $2; } | L2TP_LCP_RENEGOTIATION yesno { curr_tunnconf->proto.l2tp.lcp_renegotiation = $2; } | L2TP_FORCE_LCP_RENEGOTIATION yesno { curr_tunnconf->proto.l2tp.force_lcp_renegotiation = $2; } | L2TP_CTRL_IN_PKTDUMP yesno { curr_tunnconf->proto.l2tp.ctrl_in_pktdump = $2; } | L2TP_CTRL_OUT_PKTDUMP yesno { curr_tunnconf->proto.l2tp.ctrl_out_pktdump = $2; } | L2TP_DATA_IN_PKTDUMP yesno { curr_tunnconf->proto.l2tp.data_in_pktdump = $2; } | L2TP_DATA_OUT_PKTDUMP yesno { curr_tunnconf->proto.l2tp.data_out_pktdump = $2; } | PPTP_HOSTNAME STRING { curr_tunnconf->proto.pptp.hostname = $2; } | PPTP_VENDOR_NAME STRING { curr_tunnconf->proto.pptp.vendor_name = $2; } | PPTP_ECHO_INTERVAL NUMBER { curr_tunnconf->proto.pptp.echo_interval = $2; } | PPTP_ECHO_TIMEOUT NUMBER { curr_tunnconf->proto.pptp.echo_timeout = $2; } | PPTP_CTRL_IN_PKTDUMP yesno { curr_tunnconf->proto.pptp.ctrl_in_pktdump = $2; } | PPTP_CTRL_OUT_PKTDUMP yesno { curr_tunnconf->proto.pptp.ctrl_out_pktdump = $2; } | PPTP_DATA_IN_PKTDUMP yesno { curr_tunnconf->proto.pptp.data_in_pktdump = $2; } | PPTP_DATA_OUT_PKTDUMP yesno { curr_tunnconf->proto.pptp.data_out_pktdump = $2; } | PPPOE_SERVICE_NAME STRING { curr_tunnconf->proto.pppoe.service_name = $2; } | PPPOE_ACCEPT_ANY_SERVICE yesno { curr_tunnconf->proto.pppoe.accept_any_service = $2; } | PPPOE_AC_NAME STRING { curr_tunnconf->proto.pppoe.ac_name = $2; } | PPPOE_DESC_IN_PKTDUMP yesno { curr_tunnconf->proto.pppoe.desc_in_pktdump = $2; } | PPPOE_DESC_OUT_PKTDUMP yesno { curr_tunnconf->proto.pppoe.desc_out_pktdump = $2; } | PPPOE_SESSION_IN_PKTDUMP yesno { curr_tunnconf->proto.pppoe.session_in_pktdump = $2; } | PPPOE_SESSION_OUT_PKTDUMP yesno { curr_tunnconf->proto.pppoe.session_out_pktdump = $2; } | MPPE mppeyesno { curr_tunnconf->mppe_yesno = $2; } | MPPE_KEY_LENGTH mppekeylen_l { curr_tunnconf->mppe_keylen = $2; } | MPPE_KEY_STATE mppekeystate_l { curr_tunnconf->mppe_keystate = $2; } | TCP_MSS_ADJUST yesno { curr_tunnconf->tcp_mss_adjust = $2; } | IDLE_TIMEOUT NUMBER { curr_tunnconf->idle_timeout = $2; } | INGRESS_FILTER yesno { curr_tunnconf->ingress_filter = $2; } | CALLNUM_CHECK yesno { curr_tunnconf->callnum_check = $2; } | PIPEX yesno { curr_tunnconf->pipex = $2; } | DEBUG_DUMP_PKTIN protobit_l { curr_tunnconf->debug_dump_pktin = $2; } | DEBUG_DUMP_PKTOUT protobit_l { curr_tunnconf->debug_dump_pktout = $2; } ; tunnelproto : L2TP { $$ = NPPPD_TUNNEL_L2TP; } | PPTP { $$ = NPPPD_TUNNEL_PPTP; } | PPPOE { $$ = NPPPD_TUNNEL_PPPOE; } ; mppeyesno : YES { $$ = NPPPD_MPPE_ENABLED; } | NO { $$ = NPPPD_MPPE_DISABLED; } | REQUIRED { $$ = NPPPD_MPPE_REQUIRED; } ; address : STRING { int retval; struct addrinfo hint, *res; memset(&hint, 0, sizeof(hint)); hint.ai_family = PF_UNSPEC; hint.ai_socktype = SOCK_DGRAM; /* dummy */ hint.ai_flags = AI_NUMERICHOST; if ((retval = getaddrinfo($1, NULL, &hint, &res)) != 0) { yyerror("could not parse the address %s: %s", $1, gai_strerror(retval)); free($1); YYERROR; } free($1); if (res->ai_family != AF_INET && res->ai_family != AF_INET6) { yyerror("address family(%d) is not supported", res->ai_family); freeaddrinfo(res); YYERROR; } memcpy(&($$), res->ai_addr, res->ai_addrlen); freeaddrinfo(res); } ; addressport : address optport { $$ = $1; ((struct sockaddr_in *)&($$))->sin_port = htons($2); } ; in4_addr : STRING { if (inet_aton($1, &($$)) != 1) { yyerror("could not parse the address %s", $1); free($1); YYERROR; } } ; authmethod_l : authmethod { $$ = $1; } | authmethod_l authmethod { $$ |= $2; } ; authmethod : PAP { $$ = NPPPD_AUTH_METHODS_PAP; } | CHAP { $$ = NPPPD_AUTH_METHODS_CHAP; } | MSCHAPV2 { $$ = NPPPD_AUTH_METHODS_MSCHAPV2; } ; mppekeylen_l : mppekeylen { $$ = $1; } | mppekeylen_l mppekeylen { $$ |= $2; } ; mppekeylen : NUMBER { if ($1 == 40) $$ = NPPPD_MPPE_40BIT; else if ($1 == 56) $$ = NPPPD_MPPE_56BIT; else if ($1 == 128) $$ = NPPPD_MPPE_128BIT; else { yyerror("%"PRId64": unknown mppe key length", $$); YYERROR; } } ; mppekeystate_l : mppekeystate { $$ = $1; } | mppekeystate_l mppekeystate { $$ |= $2; } ; mppekeystate : STATEFUL { $$ = NPPPD_MPPE_STATEFUL; } | STATELESS { $$ = NPPPD_MPPE_STATELESS; } ; protobit_l : protobit { $$ = $1; } | protobit_l protobit { $$ |= $2; } ; protobit : IP { $$ = NPPPD_PROTO_BIT_IP; } | LCP { $$ = NPPPD_PROTO_BIT_LCP; } | PAP { $$ = NPPPD_PROTO_BIT_PAP; } | CHAP { $$ = NPPPD_PROTO_BIT_CHAP; } | EAP { $$ = NPPPD_PROTO_BIT_EAP; } | MPPE { $$ = NPPPD_PROTO_BIT_MPPE; } | CCP { $$ = NPPPD_PROTO_BIT_CCP; } | IPCP { $$ = NPPPD_PROTO_BIT_IPCP; } ; /* * authentication { } */ authentication : AUTHENTICATION STRING TYPE authtype { struct authconf *n; if (authconf_find($2) != NULL) { yyerror("authentication name %s is already in " "use.", $2); free($2); YYERROR; } if ((n = malloc(sizeof(struct authconf))) == NULL) { yyerror("out of memory"); free($2); YYERROR; } authconf_init(n); strlcpy(n->name, $2, sizeof(n->name)); free($2); n->auth_type = $4; if ($4 == NPPPD_AUTH_TYPE_RADIUS) { TAILQ_INIT(&n->data.radius.auth.servers); TAILQ_INIT(&n->data.radius.acct.servers); } curr_authconf = n; } '{' optnl authopt_l '}' { TAILQ_INSERT_TAIL(&conf->authconfs, curr_authconf, entry); curr_authconf = NULL; } ; authopt_l : /* empty */ | authopt_l authopt nl | authopt optnl ; authopt : USERNAME_SUFFIX STRING { curr_authconf->username_suffix = $2; } | EAP_CAPABLE yesno { curr_authconf->eap_capable = $2; } | STRIP_NT_DOMAIN yesno { curr_authconf->strip_nt_domain = $2; } | STRIP_ATMARK_REALM yesno { curr_authconf->strip_atmark_realm = $2; } | USERS_FILE STRING { strlcpy(curr_authconf->users_file_path, $2, sizeof(curr_authconf->users_file_path)); free($2); } | USER_MAX_SESSION NUMBER { curr_authconf->user_max_session = $2; } | AUTHENTICATION_SERVER { if (curr_authconf->auth_type != NPPPD_AUTH_TYPE_RADIUS){ yyerror("`authentication-server' can not be " "used for this type."); YYERROR; } curr_radconf = &curr_authconf->data.radius.auth; } '{' optnl radopt_l '}' | ACCOUNTING_SERVER { if (curr_authconf->auth_type != NPPPD_AUTH_TYPE_RADIUS){ yyerror("`accounting-server' can not be used " "for this type."); YYERROR; } curr_radconf = &curr_authconf->data.radius.acct; } '{' optnl radopt_l '}' ; optport : /* empty */ { $$ = 0; } | PORT NUMBER { $$ = $2; } ; authtype : LOCAL { $$ = NPPPD_AUTH_TYPE_LOCAL; } | RADIUS { $$ = NPPPD_AUTH_TYPE_RADIUS; } ; radopt_l : | radopt_l radopt nl | radopt optnl ; radopt : ADDRESS address optport SECRET STRING { int cnt; struct radserver *n; if (strlen($5) > MAX_RADIUS_SECRET - 1) { yyerror("`secret' is too long. " "use less than %d chars.", MAX_RADIUS_SECRET - 1); YYERROR; } cnt = 0; TAILQ_FOREACH(n, &curr_radconf->servers, entry) { cnt++; } if (cnt >= MAX_RADIUS_SERVERS) { yyerror("too many radius servers. use less " "than or equal to %d servers.", MAX_RADIUS_SERVERS); YYERROR; } if ((n = malloc(sizeof(struct radserver))) == NULL) { yyerror("out of memory"); YYERROR; } n->address = $2; ((struct sockaddr_in *)&n->address)->sin_port = htons($3); n->secret = $5; TAILQ_INSERT_TAIL(&curr_radconf->servers, n, entry); } | X_TIMEOUT NUMBER { curr_radconf->timeout = $2; } | MAX_TRIES NUMBER { curr_radconf->max_tries = $2; } | MAX_FAILOVERS NUMBER { curr_radconf->max_failovers = $2; } ; /* * ipcp { } */ ipcp : IPCP STRING { int cnt; struct ipcpconf *n; cnt = 0; /* TAILQ_FOREACH(n, &conf->ipcpconfs, entry) { cnt++; } if (cnt >= NPPPD_MAX_POOL) { yyerror("too many `ipcp' settings. it must be " "less than or euals to %d.", NPPPD_MAX_POOL); YYERROR; } */ if (ipcpconf_find($2) != NULL) { yyerror("ipcp name %s is already in use.", $2); free($2); YYERROR; } if ((n = malloc(sizeof(struct ipcpconf))) == NULL) { yyerror("out of memory"); free($2); YYERROR; } ipcpconf_init(n); strlcpy(n->name, $2, sizeof(n->name)); free($2); curr_ipcpconf = n; } '{' optnl ipcpopt_l '}' { TAILQ_INSERT_TAIL(&conf->ipcpconfs, curr_ipcpconf, entry); curr_ipcpconf = NULL; } ; ipcpopt_l : /* empty */ | ipcpopt_l ipcpopt nl | ipcpopt optnl ; ipcpopt : POOL_ADDRESS STRING ipcppooltype { if ($3 != 1) { if (in_addr_range_list_add( &curr_ipcpconf->dynamic_pool, $2) != 0) { yyerror("out of memory"); free($2); YYERROR; } } if (in_addr_range_list_add( &curr_ipcpconf->static_pool, $2) != 0) { yyerror("out of memory"); free($2); YYERROR; } free($2); } | DNS_SERVERS RESOLVER { curr_ipcpconf->dns_use_resolver = true; curr_ipcpconf->dns_servers[0].s_addr = 0; curr_ipcpconf->dns_servers[1].s_addr = 0; } | DNS_SERVERS in4_addr in4_addr { curr_ipcpconf->dns_use_resolver = false; curr_ipcpconf->dns_servers[0] = $2; curr_ipcpconf->dns_servers[1] = $3; } | DNS_SERVERS in4_addr { curr_ipcpconf->dns_use_resolver = false; curr_ipcpconf->dns_servers[0] = $2; curr_ipcpconf->dns_servers[1].s_addr = 0; } | NBNS_SERVERS in4_addr in4_addr { curr_ipcpconf->nbns_servers[0] = $2; curr_ipcpconf->nbns_servers[1] = $3; } | NBNS_SERVERS in4_addr { curr_ipcpconf->nbns_servers[0] = $2; curr_ipcpconf->nbns_servers[1].s_addr = 0; } | ALLOW_USER_SELECTED_ADDRESS yesno { curr_ipcpconf->allow_user_select = $2; } | MAX_SESSION NUMBER { curr_ipcpconf->max_session = $2; } ; ipcppooltype : /* empty */ { $$ = 0; } | FOR DYNAMIC { $$ = 0; } | FOR STATIC { $$ = 1; } ; /* * interface */ interface : INTERFACE STRING ADDRESS in4_addr IPCP STRING { int cnt; struct iface *n; struct ipcpconf *ipcp; cnt = 0; TAILQ_FOREACH(n, &conf->ifaces, entry) { cnt++; } if (cnt >= NPPPD_MAX_IFACE) { yyerror("too many interfaces. use less than " "or equal to %d", NPPPD_MAX_IFACE); YYERROR; } if ((ipcp = ipcpconf_find($6)) == NULL) { yyerror("ipcp %s is not found", $6); free($2); YYERROR; } if (iface_find($2) != NULL) { yyerror("interface %s is already in used.", $2); free($2); YYERROR; } if ((n = calloc(1, sizeof(struct iface))) == NULL) { yyerror("out of memory"); free($2); YYERROR; } strlcpy(n->name, $2, sizeof(n->name)); free($2); n->ip4addr = $4; if (strncmp(n->name, "pppx", 4) == 0) n->is_pppx = true; n->ipcpconf = ipcp; TAILQ_INSERT_TAIL(&conf->ifaces, n, entry); } ; /* * bind */ bind : BIND TUNNEL FROM STRING AUTHENTICATED BY STRING TO STRING { struct authconf *auth; struct tunnconf *tunn; struct iface *iface; struct confbind *n; if ((tunn = tunnconf_find($4)) == NULL) { yyerror("tunnel %s is not found", $4); free($4); free($7); free($9); YYERROR; } if ((auth = authconf_find($7)) == NULL) { yyerror("authentication %s is not found", $7); free($4); free($7); free($9); YYERROR; } if ((iface = iface_find($9)) == NULL) { yyerror("interface %s is not found", $9); free($4); free($7); free($9); YYERROR; } if (tunn->pipex == 0 && iface->is_pppx) { yyerror("pipex should be enabled for" " interface %s", $9); free($4); free($7); free($9); YYERROR; } if ((n = malloc(sizeof(struct confbind))) == NULL) { yyerror("out of memory"); free($4); free($7); free($9); YYERROR; } n->tunnconf = tunn; n->authconf = auth; n->iface = iface; TAILQ_INSERT_TAIL(&conf->confbinds, n, entry); } ; yesno : YES { $$ = true; } | NO { $$ = false; } ; optnl : '\n' optnl | ; nl : '\n' optnl ; %% struct keywords { const char *k_name; int k_val; }; int yyerror(const char *fmt, ...) { va_list ap; char *msg; file->errors++; va_start(ap, fmt); if (vasprintf(&msg, fmt, ap) == -1) fatalx("yyerror vasprintf"); va_end(ap); logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); free(msg); return (0); } int kw_cmp(const void *k, const void *e) { return (strcmp(k, ((const struct keywords *)e)->k_name)); } int lookup(char *s) { /* this has to be sorted always */ static const struct keywords keywords[] = { { "accounting-server", ACCOUNTING_SERVER}, { "address", ADDRESS}, { "allow-user-selected-address", ALLOW_USER_SELECTED_ADDRESS}, { "authenticated", AUTHENTICATED}, { "authentication", AUTHENTICATION}, { "authentication-method", AUTHENTICATION_METHOD}, { "authentication-server", AUTHENTICATION_SERVER}, { "bind", BIND}, { "by", BY}, { "callnum-check", CALLNUM_CHECK}, { "ccp", CCP}, { "ccp-max-configure", CCP_MAX_CONFIGURE}, { "ccp-max-nak-loop", CCP_MAX_NAK_LOOP}, { "ccp-max-terminate", CCP_MAX_TERMINATE}, { "ccp-timeout", CCP_TIMEOUT}, { "chap", CHAP}, { "chap-name", CHAP_NAME}, { "debug-dump-pktin", DEBUG_DUMP_PKTIN}, { "debug-dump-pktout", DEBUG_DUMP_PKTOUT}, { "dns-servers", DNS_SERVERS}, { "dynamic", DYNAMIC}, { "eap", EAP}, { "eap-capable", EAP_CAPABLE}, { "for", FOR}, { "from", FROM}, { "idle-timeout", IDLE_TIMEOUT}, { "ingress-filter", INGRESS_FILTER}, { "interface", INTERFACE}, { "ip", IP}, { "ipcp", IPCP}, { "ipcp-max-configure", IPCP_MAX_CONFIGURE}, { "ipcp-max-nak-loop", IPCP_MAX_NAK_LOOP}, { "ipcp-max-terminate", IPCP_MAX_TERMINATE}, { "ipcp-timeout", IPCP_TIMEOUT}, { "l2tp", L2TP}, { "l2tp-accept-dialin", L2TP_ACCEPT_DIALIN}, { "l2tp-ctrl-in-pktdump", L2TP_CTRL_IN_PKTDUMP}, { "l2tp-ctrl-out-pktdump", L2TP_CTRL_OUT_PKTDUMP}, { "l2tp-data-in-pktdump", L2TP_DATA_IN_PKTDUMP}, { "l2tp-data-out-pktdump", L2TP_DATA_OUT_PKTDUMP}, { "l2tp-data-use-seq", L2TP_DATA_USE_SEQ}, { "l2tp-force-lcp-renegotiation", L2TP_FORCE_LCP_RENEGOTIATION}, { "l2tp-hello-interval", L2TP_HELLO_INTERVAL}, { "l2tp-hello-timeout", L2TP_HELLO_TIMEOUT}, { "l2tp-hostname", L2TP_HOSTNAME}, { "l2tp-lcp-renegotiation", L2TP_LCP_RENEGOTIATION}, { "l2tp-require-ipsec", L2TP_REQUIRE_IPSEC}, { "l2tp-vendor-name", L2TP_VENDOR_NAME}, { "lcp", LCP}, { "lcp-keepalive", LCP_KEEPALIVE}, { "lcp-keepalive-interval", LCP_KEEPALIVE_INTERVAL}, { "lcp-keepalive-max-retries", LCP_KEEPALIVE_MAX_RETRIES }, { "lcp-keepalive-retry-interval", LCP_KEEPALIVE_RETRY_INTERVAL}, { "lcp-max-configure", LCP_MAX_CONFIGURE}, { "lcp-max-nak-loop", LCP_MAX_NAK_LOOP}, { "lcp-max-terminate", LCP_MAX_TERMINATE}, { "lcp-timeout", LCP_TIMEOUT}, { "listen", LISTEN}, { "local", LOCAL}, { "max-failovers", MAX_FAILOVERS}, { "max-session", MAX_SESSION}, { "max-tries", MAX_TRIES}, { "mppe", MPPE}, { "mppe-key-length", MPPE_KEY_LENGTH}, { "mppe-key-state", MPPE_KEY_STATE}, { "mru", MRU}, { "mschapv2", MSCHAPV2}, { "nbns-servers", NBNS_SERVERS}, { "no", NO}, { "on", ON}, { "pap", PAP}, { "pipex", PIPEX}, { "pool-address", POOL_ADDRESS}, { "port", PORT}, { "pppoe", PPPOE}, { "pppoe-ac-name", PPPOE_AC_NAME}, { "pppoe-accept-any-service", PPPOE_ACCEPT_ANY_SERVICE}, { "pppoe-desc-in-pktdump", PPPOE_DESC_IN_PKTDUMP}, { "pppoe-desc-out-pktdump", PPPOE_DESC_OUT_PKTDUMP}, { "pppoe-service-name", PPPOE_SERVICE_NAME}, { "pppoe-session-in-pktdump", PPPOE_SESSION_IN_PKTDUMP}, { "pppoe-session-out-pktdump", PPPOE_SESSION_OUT_PKTDUMP}, { "pptp", PPTP}, { "pptp-ctrl-in-pktdump", PPTP_CTRL_IN_PKTDUMP}, { "pptp-ctrl-out-pktdump", PPTP_CTRL_OUT_PKTDUMP}, { "pptp-data-in-pktdump", PPTP_DATA_IN_PKTDUMP}, { "pptp-data-out-pktdump", PPTP_DATA_OUT_PKTDUMP}, { "pptp-echo-interval", PPTP_ECHO_INTERVAL}, { "pptp-echo-timeout", PPTP_ECHO_TIMEOUT}, { "pptp-hostname", PPTP_HOSTNAME}, { "pptp-vendor-name", PPTP_VENDOR_NAME}, { "protocol", PROTOCOL}, { "radius", RADIUS}, { "required", REQUIRED}, { "resolver", RESOLVER}, { "secret", SECRET}, { "set", SET}, { "stateful", STATEFUL}, { "stateless", STATELESS}, { "static", STATIC}, { "strip-atmark-realm", STRIP_ATMARK_REALM}, { "strip-nt-domain", STRIP_NT_DOMAIN}, { "tcp-mss-adjust", TCP_MSS_ADJUST}, { "timeout", X_TIMEOUT}, { "to", TO}, { "tunnel", TUNNEL}, { "type", TYPE}, { "user-max-session", USER_MAX_SESSION}, { "username-suffix", USERNAME_SUFFIX}, { "users-file", USERS_FILE}, { "yes", YES} }; const struct keywords *p; p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), sizeof(keywords[0]), kw_cmp); if (p) return (p->k_val); else return (STRING); } #define MAXPUSHBACK 128 char *parsebuf; int parseindex; char pushback_buffer[MAXPUSHBACK]; int pushback_index = 0; int lgetc(int quotec) { int c, next; if (parsebuf) { /* Read character from the parsebuffer instead of input. */ if (parseindex >= 0) { c = (unsigned char)parsebuf[parseindex++]; if (c != '\0') return (c); parsebuf = NULL; } else parseindex++; } if (pushback_index) return ((unsigned char)pushback_buffer[--pushback_index]); if (quotec) { if ((c = getc(file->stream)) == EOF) { yyerror("reached end of file while parsing " "quoted string"); if (file == topfile || popfile() == EOF) return (EOF); return (quotec); } return (c); } while ((c = getc(file->stream)) == '\\') { next = getc(file->stream); if (next != '\n') { c = next; break; } yylval.lineno = file->lineno; file->lineno++; } while (c == EOF) { if (file == topfile || popfile() == EOF) return (EOF); c = getc(file->stream); } return (c); } int lungetc(int c) { if (c == EOF) return (EOF); if (parsebuf) { parseindex--; if (parseindex >= 0) return (c); } if (pushback_index + 1 >= MAXPUSHBACK) return (EOF); pushback_buffer[pushback_index++] = c; return (c); } int findeol(void) { int c; parsebuf = NULL; /* skip to either EOF or the first real EOL */ while (1) { if (pushback_index) c = (unsigned char)pushback_buffer[--pushback_index]; else c = lgetc(0); if (c == '\n') { file->lineno++; break; } if (c == EOF) break; } return (ERROR); } int yylex(void) { char buf[8096]; char *p; int quotec, next, c; int token; p = buf; while ((c = lgetc(0)) == ' ' || c == '\t') ; /* nothing */ yylval.lineno = file->lineno; if (c == '#') while ((c = lgetc(0)) != '\n' && c != EOF) ; /* nothing */ switch (c) { case '\'': case '"': quotec = c; while (1) { if ((c = lgetc(quotec)) == EOF) return (0); if (c == '\n') { file->lineno++; continue; } else if (c == '\\') { if ((next = lgetc(quotec)) == EOF) return (0); if (next == quotec || next == ' ' || next == '\t') c = next; else if (next == '\n') { file->lineno++; continue; } else lungetc(next); } else if (c == quotec) { *p = '\0'; break; } else if (c == '\0') { yyerror("syntax error"); return (findeol()); } if (p + 1 >= buf + sizeof(buf) - 1) { yyerror("string too long"); return (findeol()); } *p++ = c; } yylval.v.string = strdup(buf); if (yylval.v.string == NULL) fatal("yylex: strdup"); return (STRING); } #define allowed_to_end_number(x) \ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') if (c == '-' || isdigit(c)) { do { *p++ = c; if ((size_t)(p-buf) >= sizeof(buf)) { yyerror("string too long"); return (findeol()); } } while ((c = lgetc(0)) != EOF && isdigit(c)); lungetc(c); if (p == buf + 1 && buf[0] == '-') goto nodigits; if (c == EOF || allowed_to_end_number(c)) { const char *errstr = NULL; *p = '\0'; yylval.v.number = strtonum(buf, LLONG_MIN, LLONG_MAX, &errstr); if (errstr) { yyerror("\"%s\" invalid number: %s", buf, errstr); return (findeol()); } return (NUMBER); } else { nodigits: while (p > buf + 1) lungetc((unsigned char)*--p); c = (unsigned char)*--p; if (c == '-') return (c); } } #define allowed_in_string(x) \ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ x != '{' && x != '}' && x != '<' && x != '>' && \ x != '!' && x != '=' && x != '/' && x != '#' && \ x != ',')) if (isalnum(c) || c == ':' || c == '_' || c == '*') { do { *p++ = c; if ((size_t)(p-buf) >= sizeof(buf)) { yyerror("string too long"); return (findeol()); } } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); lungetc(c); *p = '\0'; if ((token = lookup(buf)) == STRING) if ((yylval.v.string = strdup(buf)) == NULL) fatal("yylex: strdup"); return (token); } if (c == '\n') { yylval.lineno = file->lineno; file->lineno++; } if (c == EOF) return (0); return (c); } struct file * pushfile(const char *name) { struct file *nfile; if ((nfile = calloc(1, sizeof(struct file))) == NULL) { log_warn("%s", __func__); return (NULL); } if ((nfile->name = strdup(name)) == NULL) { log_warn("%s", __func__); free(nfile); return (NULL); } #ifdef NO_PRIVSEP if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { #else if ((nfile->stream = priv_fopen(nfile->name)) == NULL) { #endif log_warn("%s: %s", __func__, nfile->name); free(nfile->name); free(nfile); return (NULL); } nfile->lineno = 1; TAILQ_INSERT_TAIL(&files, nfile, entry); return (nfile); } int popfile(void) { struct file *prev; if ((prev = TAILQ_PREV(file, files, entry)) != NULL) prev->errors += file->errors; TAILQ_REMOVE(&files, file, entry); fclose(file->stream); free(file->name); free(file); file = prev; return (file ? 0 : EOF); } int npppd_conf_parse(struct npppd_conf *xconf, const char *filename) { int errors = 0; conf = xconf; if ((file = pushfile(filename)) == NULL) { return (-1); } topfile = file; yyparse(); errors = file->errors; popfile(); if (curr_tunnconf != NULL) { tunnconf_fini(curr_tunnconf); free(curr_tunnconf); } curr_tunnconf = NULL; if (curr_authconf != NULL) { authconf_fini(curr_authconf); free(curr_authconf); } curr_authconf = NULL; if (curr_ipcpconf != NULL) { ipcpconf_fini(curr_ipcpconf); free(curr_ipcpconf); } curr_ipcpconf = NULL; if (errors) npppd_conf_fini(xconf); return (errors ? -1 : 0); } void npppd_conf_init(struct npppd_conf *xconf) { memset(xconf, 0, sizeof(struct npppd_conf)); TAILQ_INIT(&xconf->tunnconfs); TAILQ_INIT(&xconf->authconfs); TAILQ_INIT(&xconf->ipcpconfs); TAILQ_INIT(&xconf->ifaces); TAILQ_INIT(&xconf->confbinds); TAILQ_INIT(&xconf->l2tp_confs); TAILQ_INIT(&xconf->pptp_confs); TAILQ_INIT(&xconf->pppoe_confs); } void npppd_conf_fini(struct npppd_conf *xconf) { struct tunnconf *tunn, *tunn0; struct authconf *auth, *auth0; struct ipcpconf *ipcp, *ipcp0; struct iface *iface, *iface0; struct confbind *confbind, *confbind0; TAILQ_FOREACH_SAFE(tunn, &xconf->tunnconfs, entry, tunn0) { tunnconf_fini(tunn); } TAILQ_FOREACH_SAFE(auth, &xconf->authconfs, entry, auth0) { authconf_fini(auth); } TAILQ_FOREACH_SAFE(ipcp, &xconf->ipcpconfs, entry, ipcp0) { ipcpconf_fini(ipcp); } TAILQ_FOREACH_SAFE(iface, &xconf->ifaces, entry, iface0) { free(iface); } TAILQ_FOREACH_SAFE(confbind, &xconf->confbinds, entry, confbind0) { free(confbind); } TAILQ_INIT(&xconf->l2tp_confs); TAILQ_INIT(&xconf->pptp_confs); TAILQ_INIT(&xconf->pppoe_confs); } void tunnconf_fini(struct tunnconf *tun) { if (tun->chap_name != NULL) free(tun->chap_name); tun->chap_name = NULL; switch (tun->protocol) { case NPPPD_TUNNEL_L2TP: { struct l2tp_listen_addr *l_addr, *l_tmp; if (tun->proto.l2tp.hostname != NULL) free(tun->proto.l2tp.hostname); tun->proto.l2tp.hostname = NULL; if (tun->proto.l2tp.vendor_name != NULL) free(tun->proto.l2tp.vendor_name); tun->proto.l2tp.vendor_name = NULL; TAILQ_FOREACH_SAFE(l_addr, &tun->proto.l2tp.listen, entry, l_tmp) { TAILQ_REMOVE(&tun->proto.l2tp.listen, l_addr, entry); free(l_addr); } break; } case NPPPD_TUNNEL_PPTP: { struct pptp_listen_addr *p_addr, *p_tmp; if (tun->proto.pptp.hostname != NULL) free(tun->proto.pptp.hostname); tun->proto.pptp.hostname = NULL; if (tun->proto.pptp.vendor_name != NULL) free(tun->proto.pptp.vendor_name); tun->proto.pptp.vendor_name = NULL; TAILQ_FOREACH_SAFE(p_addr, &tun->proto.pptp.listen, entry, p_tmp) { TAILQ_REMOVE(&tun->proto.pptp.listen, p_addr, entry); free(p_addr); } break; } case NPPPD_TUNNEL_PPPOE: if (tun->proto.pppoe.service_name != NULL) free(tun->proto.pppoe.service_name); tun->proto.pppoe.service_name = NULL; if (tun->proto.pppoe.ac_name != NULL) free(tun->proto.pppoe.ac_name); tun->proto.pppoe.ac_name = NULL; break; } } void tunnconf_init(struct tunnconf *tun, int protocol) { extern struct tunnconf tunnconf_default_l2tp, tunnconf_default_pptp; extern struct tunnconf tunnconf_default_pppoe; switch (protocol) { case NPPPD_TUNNEL_L2TP: memcpy(tun, &tunnconf_default_l2tp, sizeof(struct tunnconf)); TAILQ_INIT(&tun->proto.l2tp.listen); break; case NPPPD_TUNNEL_PPTP: memcpy(tun, &tunnconf_default_pptp, sizeof(struct tunnconf)); TAILQ_INIT(&tun->proto.pptp.listen); break; case NPPPD_TUNNEL_PPPOE: memcpy(tun, &tunnconf_default_pppoe, sizeof(struct tunnconf)); break; } } struct tunnconf * tunnconf_find(const char *name) { struct tunnconf *tunn; TAILQ_FOREACH(tunn, &conf->tunnconfs, entry) { if (strcmp(tunn->name, name) == 0) return tunn; } return NULL; } void authconf_init(struct authconf *auth) { memset(auth, 0, sizeof(struct authconf)); auth->eap_capable = true; auth->strip_nt_domain = true; auth->strip_atmark_realm = false; } void authconf_fini(struct authconf *auth) { if (auth->username_suffix != NULL) free(auth->username_suffix); auth->username_suffix = NULL; switch (auth->auth_type) { case NPPPD_AUTH_TYPE_RADIUS: radconf_fini(&auth->data.radius.auth); radconf_fini(&auth->data.radius.acct); break; } } void radconf_fini(struct radconf *radconf) { struct radserver *server, *server0; TAILQ_FOREACH_SAFE(server, &radconf->servers, entry, server0) { if (server->secret != NULL) free(server->secret); server->secret = NULL; } } struct authconf * authconf_find(const char *name) { struct authconf *auth; TAILQ_FOREACH(auth, &conf->authconfs, entry) { if (strcmp(auth->name, name) == 0) return auth; } return NULL; } void ipcpconf_init(struct ipcpconf *ipcp) { memset(ipcp, 0, sizeof(struct ipcpconf)); } void ipcpconf_fini(struct ipcpconf *ipcp) { if (ipcp->dynamic_pool != NULL) in_addr_range_list_remove_all(&ipcp->dynamic_pool); if (ipcp->static_pool != NULL) in_addr_range_list_remove_all(&ipcp->static_pool); } struct ipcpconf * ipcpconf_find(const char *name) { struct ipcpconf *ipcp; TAILQ_FOREACH(ipcp, &conf->ipcpconfs, entry) { if (strcmp(ipcp->name, name) == 0) return ipcp; } return NULL; } struct iface * iface_find(const char *name) { struct iface *iface; TAILQ_FOREACH(iface, &conf->ifaces, entry) { if (strcmp(iface->name, name) == 0) return iface; } return NULL; } void sa_set_in_addr_any(struct sockaddr *sa) { memset(sa, 0, sizeof(struct sockaddr_in)); sa->sa_family = AF_INET, sa->sa_len = sizeof(struct sockaddr_in); ((struct sockaddr_in *)sa)->sin_addr.s_addr = htonl(INADDR_ANY); }