From 0a97fd92483349178a6750ec4a232fea4f9864df Mon Sep 17 00:00:00 2001 From: Johnson Li Date: Fri, 8 Sep 2017 17:09:58 +0800 Subject: [PATCH] Integrate FreeRADIUS Client for vBNG Signed-off-by: Johnson Li diff --git a/src/configure.ac b/src/configure.ac index fb2ead6d..ef5537da 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -152,6 +152,7 @@ PLUGIN_ENABLED(ioam) PLUGIN_ENABLED(ixge) PLUGIN_ENABLED(lb) PLUGIN_ENABLED(memif) +PLUGIN_ENABLED(vbng) PLUGIN_ENABLED(sixrd) PLUGIN_ENABLED(snat) diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 623892e7..d6f607fc 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -61,6 +61,10 @@ if ENABLE_MEMIF_PLUGIN include memif.am endif +if ENABLE_VBNG_PLUGIN +include vbng.am +endif + if ENABLE_SIXRD_PLUGIN include sixrd.am endif diff --git a/src/plugins/vbng.am b/src/plugins/vbng.am new file mode 100644 index 00000000..99398f49 --- /dev/null +++ b/src/plugins/vbng.am @@ -0,0 +1,32 @@ +# Copyright (c) 2017 Intel and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +vppplugins_LTLIBRARIES += vbng_plugin.la + +vbng_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lfreeradiusclient + +vbng_plugin_la_SOURCES = vbng/vbng_dhcp4_node.c \ + vbng/vbng_dhcp4.c \ + vbng/vbng_api.c \ + vbng/vbng_aaa.c + +BUILT_SOURCES += vbng/vbng.api.h \ + vbng/vbng.api.json + +API_FILES += vbng/vbng.api + +nobase_apiinclude_HEADERS += vbng/vbng_all_api_h.h \ + vbng/vbng_msg_enum.h \ + vbng/vbng.api.h + +# vi:syntax=automake diff --git a/src/plugins/vbng/etc/dictionary b/src/plugins/vbng/etc/dictionary new file mode 100644 index 00000000..45f4189c --- /dev/null +++ b/src/plugins/vbng/etc/dictionary @@ -0,0 +1,274 @@ +# +# Updated 97/06/13 to livingston-radius-2.01 miquels@cistron.nl +# +# This file contains dictionary translations for parsing +# requests and generating responses. All transactions are +# composed of Attribute/Value Pairs. The value of each attribute +# is specified as one of 4 data types. Valid data types are: +# +# string - 0-253 octets +# ipaddr - 4 octets in network byte order +# integer - 32 bit value in big endian order (high byte first) +# date - 32 bit value in big endian order - seconds since +# 00:00:00 GMT, Jan. 1, 1970 +# +# Enumerated values are stored in the user file with dictionary +# VALUE translations for easy administration. +# +# Example: +# +# ATTRIBUTE VALUE +# --------------- ----- +# Framed-Protocol = PPP +# 7 = 1 (integer encoding) +# + +# +# Following are the proper new names. Use these. +# +ATTRIBUTE User-Name 1 string +ATTRIBUTE Password 2 string +ATTRIBUTE CHAP-Password 3 string +ATTRIBUTE NAS-IP-Address 4 ipaddr +ATTRIBUTE NAS-Port-Id 5 integer +ATTRIBUTE Service-Type 6 integer +ATTRIBUTE Framed-Protocol 7 integer +ATTRIBUTE Framed-IP-Address 8 ipaddr +ATTRIBUTE Framed-IP-Netmask 9 ipaddr +ATTRIBUTE Framed-Routing 10 integer +ATTRIBUTE Filter-Id 11 string +ATTRIBUTE Framed-MTU 12 integer +ATTRIBUTE Framed-Compression 13 integer +ATTRIBUTE Login-IP-Host 14 ipaddr +ATTRIBUTE Login-Service 15 integer +ATTRIBUTE Login-TCP-Port 16 integer +ATTRIBUTE Reply-Message 18 string +ATTRIBUTE Callback-Number 19 string +ATTRIBUTE Callback-Id 20 string +ATTRIBUTE Framed-Route 22 string +ATTRIBUTE Framed-IPX-Network 23 ipaddr +ATTRIBUTE State 24 string +ATTRIBUTE Class 25 string +ATTRIBUTE Vendor-Specific 26 string +ATTRIBUTE Session-Timeout 27 integer +ATTRIBUTE Idle-Timeout 28 integer +ATTRIBUTE Termination-Action 29 integer +ATTRIBUTE Called-Station-Id 30 string +ATTRIBUTE Calling-Station-Id 31 string +ATTRIBUTE NAS-Identifier 32 string +ATTRIBUTE Proxy-State 33 string +ATTRIBUTE Login-LAT-Service 34 string +ATTRIBUTE Login-LAT-Node 35 string +ATTRIBUTE Login-LAT-Group 36 string +ATTRIBUTE Framed-AppleTalk-Link 37 integer +ATTRIBUTE Framed-AppleTalk-Network 38 integer +ATTRIBUTE Framed-AppleTalk-Zone 39 string +ATTRIBUTE Acct-Status-Type 40 integer +ATTRIBUTE Acct-Delay-Time 41 integer +ATTRIBUTE Acct-Input-Octets 42 integer +ATTRIBUTE Acct-Output-Octets 43 integer +ATTRIBUTE Acct-Session-Id 44 string +ATTRIBUTE Acct-Authentic 45 integer +ATTRIBUTE Acct-Session-Time 46 integer +ATTRIBUTE Acct-Input-Packets 47 integer +ATTRIBUTE Acct-Output-Packets 48 integer +ATTRIBUTE Acct-Terminate-Cause 49 integer +ATTRIBUTE Acct-Multi-Session-Id 50 string +ATTRIBUTE Acct-Link-Count 51 integer +ATTRIBUTE Acct-Input-Gigawords 52 integer +ATTRIBUTE Acct-Output-Gigawords 53 integer +ATTRIBUTE Event-Timestamp 55 integer +ATTRIBUTE CHAP-Challenge 60 string +ATTRIBUTE NAS-Port-Type 61 integer +ATTRIBUTE Port-Limit 62 integer +ATTRIBUTE Login-LAT-Port 63 integer +ATTRIBUTE Connect-Info 77 string + +# +# RFC3162 IPv6 attributes +# +ATTRIBUTE NAS-IPv6-Address 95 string +ATTRIBUTE Framed-Interface-Id 96 string +ATTRIBUTE Framed-IPv6-Prefix 97 ipv6prefix +ATTRIBUTE Login-IPv6-Host 98 string +ATTRIBUTE Framed-IPv6-Route 99 string +ATTRIBUTE Framed-IPv6-Pool 100 string + +# +# RFC6911 IPv6 attributes +# +ATTRIBUTE Framed-IPv6-Address 168 ipv6addr +ATTRIBUTE DNS-Server-IPv6-Address 169 ipv6addr +ATTRIBUTE Route-IPv6-Information 170 ipv6prefix + +# +# Experimental Non Protocol Attributes used by Cistron-Radiusd +# +ATTRIBUTE Huntgroup-Name 221 string +ATTRIBUTE User-Category 1029 string +ATTRIBUTE Group-Name 1030 string +ATTRIBUTE Simultaneous-Use 1034 integer +ATTRIBUTE Strip-User-Name 1035 integer +ATTRIBUTE Fall-Through 1036 integer +ATTRIBUTE Add-Port-To-IP-Address 1037 integer +ATTRIBUTE Exec-Program 1038 string +ATTRIBUTE Exec-Program-Wait 1039 string +ATTRIBUTE Hint 1040 string + +# +# Non-Protocol Attributes +# These attributes are used internally by the server +# +ATTRIBUTE Expiration 21 date +ATTRIBUTE Auth-Type 1000 integer +ATTRIBUTE Menu 1001 string +ATTRIBUTE Termination-Menu 1002 string +ATTRIBUTE Prefix 1003 string +ATTRIBUTE Suffix 1004 string +ATTRIBUTE Group 1005 string +ATTRIBUTE Crypt-Password 1006 string +ATTRIBUTE Connect-Rate 1007 integer + +# +# Integer Translations +# + +# User Types + +VALUE Service-Type Login-User 1 +VALUE Service-Type Framed-User 2 +VALUE Service-Type Callback-Login-User 3 +VALUE Service-Type Callback-Framed-User 4 +VALUE Service-Type Outbound-User 5 +VALUE Service-Type Administrative-User 6 +VALUE Service-Type NAS-Prompt-User 7 +VALUE Service-Type Authenticate-Only 8 +VALUE Service-Type Callback-NAS-Prompt 9 +VALUE Service-Type Call-Check 10 +VALUE Service-Type Callback-Administrative 11 + +# Framed Protocols + +VALUE Framed-Protocol PPP 1 +VALUE Framed-Protocol SLIP 2 +VALUE Framed-Protocol ARAP 3 +VALUE Framed-Protocol GANDALF-SLMLP 4 +VALUE Framed-Protocol XYLOGICS-IPX-SLIP 5 +VALUE Framed-Protocol X75 6 + +# Framed Routing Values + +VALUE Framed-Routing None 0 +VALUE Framed-Routing Broadcast 1 +VALUE Framed-Routing Listen 2 +VALUE Framed-Routing Broadcast-Listen 3 + +# Framed Compression Types + +VALUE Framed-Compression None 0 +VALUE Framed-Compression Van-Jacobson-TCP-IP 1 +VALUE Framed-Compression IPX-Header 2 +VALUE Framed-Compression Stac-LZS 3 + +# Login Services + +VALUE Login-Service Telnet 0 +VALUE Login-Service Rlogin 1 +VALUE Login-Service TCP-Clear 2 +VALUE Login-Service PortMaster 3 +VALUE Login-Service LAT 4 +VALUE Login-Service X.25-PAD 5 +VALUE Login-Service X.25-T3POS 6 +VALUE Login-Service TCP-Clear-Quiet 8 + +# Status Types + +VALUE Acct-Status-Type Start 1 +VALUE Acct-Status-Type Stop 2 +VALUE Acct-Status-Type Alive 3 +VALUE Acct-Status-Type Accounting-On 7 +VALUE Acct-Status-Type Accounting-Off 8 + +# Authentication Types + +VALUE Acct-Authentic RADIUS 1 +VALUE Acct-Authentic Local 2 +VALUE Acct-Authentic Remote 3 + +# Termination Options + +VALUE Termination-Action Default 0 +VALUE Termination-Action RADIUS-Request 1 + +# NAS Port Types, available in 3.3.1 and later + +VALUE NAS-Port-Type Async 0 +VALUE NAS-Port-Type Sync 1 +VALUE NAS-Port-Type ISDN 2 +VALUE NAS-Port-Type ISDN-V120 3 +VALUE NAS-Port-Type ISDN-V110 4 +VALUE NAS-Port-Type Virtual 5 +VALUE NAS-Port-Type PIAFS 6 +VALUE NAS-Port-Type HDLC-Clear-Channel 7 +VALUE NAS-Port-Type X.25 8 +VALUE NAS-Port-Type X.75 9 +VALUE NAS-Port-Type G.3-Fax 10 +VALUE NAS-Port-Type SDSL 11 +VALUE NAS-Port-Type ADSL-CAP 12 +VALUE NAS-Port-Type ADSL-DMT 13 +VALUE NAS-Port-Type IDSL 14 +VALUE NAS-Port-Type Ethernet 15 + +# Acct Terminate Causes, available in 3.3.2 and later + +VALUE Acct-Terminate-Cause User-Request 1 +VALUE Acct-Terminate-Cause Lost-Carrier 2 +VALUE Acct-Terminate-Cause Lost-Service 3 +VALUE Acct-Terminate-Cause Idle-Timeout 4 +VALUE Acct-Terminate-Cause Session-Timeout 5 +VALUE Acct-Terminate-Cause Admin-Reset 6 +VALUE Acct-Terminate-Cause Admin-Reboot 7 +VALUE Acct-Terminate-Cause Port-Error 8 +VALUE Acct-Terminate-Cause NAS-Error 9 +VALUE Acct-Terminate-Cause NAS-Request 10 +VALUE Acct-Terminate-Cause NAS-Reboot 11 +VALUE Acct-Terminate-Cause Port-Unneeded 12 +VALUE Acct-Terminate-Cause Port-Preempted 13 +VALUE Acct-Terminate-Cause Port-Suspended 14 +VALUE Acct-Terminate-Cause Service-Unavailable 15 +VALUE Acct-Terminate-Cause Callback 16 +VALUE Acct-Terminate-Cause User-Error 17 +VALUE Acct-Terminate-Cause Host-Request 18 + +# +# Non-Protocol Integer Translations +# + +VALUE Auth-Type Local 0 +VALUE Auth-Type System 1 +VALUE Auth-Type SecurID 2 +VALUE Auth-Type Crypt-Local 3 +VALUE Auth-Type Reject 4 + +# +# Cistron extensions +# +VALUE Auth-Type Pam 253 +VALUE Auth-Type Accept 254 + +# +# Experimental Non-Protocol Integer Translations for Cistron-Radiusd +# +VALUE Fall-Through No 0 +VALUE Fall-Through Yes 1 +VALUE Add-Port-To-IP-Address No 0 +VALUE Add-Port-To-IP-Address Yes 1 + +# +# Configuration Values +# uncomment these two lines to turn account expiration on +# + +#VALUE Server-Config Password-Expiration 30 +#VALUE Server-Config Password-Warning 5 + diff --git a/src/plugins/vbng/etc/dictionary.ascend b/src/plugins/vbng/etc/dictionary.ascend new file mode 100644 index 00000000..a02c207d --- /dev/null +++ b/src/plugins/vbng/etc/dictionary.ascend @@ -0,0 +1,297 @@ +# +# Ascend dictionary. +# +# Enable by putting the line "$INCLUDE dictionary.ascend" into +# the main dictionary file. +# +# Version: 1.00 21-Jul-1997 Jens Glaser +# + + +# +# Ascend specific extensions +# Used by ASCEND MAX/Pipeline products +# +ATTRIBUTE Ascend-FCP-Parameter 119 string +ATTRIBUTE Ascend-Modem-PortNo 120 integer +ATTRIBUTE Ascend-Modem-SlotNo 121 integer +ATTRIBUTE Ascend-Modem-ShelfNo 122 integer +ATTRIBUTE Ascend-Call-Attempt-Limit 123 integer +ATTRIBUTE Ascend-Call-Block-Duration 124 integer +ATTRIBUTE Ascend-Maximum-Call-Duration 125 integer +ATTRIBUTE Ascend-Temporary-Rtes 126 integer +ATTRIBUTE Tunneling-Protocol 127 integer +ATTRIBUTE Ascend-Shared-Profile-Enable 128 integer +ATTRIBUTE Ascend-Primary-Home-Agent 129 string +ATTRIBUTE Ascend-Secondary-Home-Agent 130 string +ATTRIBUTE Ascend-Dialout-Allowed 131 integer +ATTRIBUTE Ascend-Client-Gateway 132 ipaddr +ATTRIBUTE Ascend-BACP-Enable 133 integer +ATTRIBUTE Ascend-DHCP-Maximum-Leases 134 integer +ATTRIBUTE Ascend-Client-Primary-DNS 135 ipaddr +ATTRIBUTE Ascend-Client-Secondary-DNS 136 ipaddr +ATTRIBUTE Ascend-Client-Assign-DNS 137 integer +ATTRIBUTE Ascend-User-Acct-Type 138 integer +ATTRIBUTE Ascend-User-Acct-Host 139 ipaddr +ATTRIBUTE Ascend-User-Acct-Port 140 integer +ATTRIBUTE Ascend-User-Acct-Key 141 string +ATTRIBUTE Ascend-User-Acct-Base 142 integer +ATTRIBUTE Ascend-User-Acct-Time 143 integer +ATTRIBUTE Ascend-Assign-IP-Client 144 ipaddr +ATTRIBUTE Ascend-Assign-IP-Server 145 ipaddr +ATTRIBUTE Ascend-Assign-IP-Global-Pool 146 string +ATTRIBUTE Ascend-DHCP-Reply 147 integer +ATTRIBUTE Ascend-DHCP-Pool-Number 148 integer +ATTRIBUTE Ascend-Expect-Callback 149 integer +ATTRIBUTE Ascend-Event-Type 150 integer +ATTRIBUTE Ascend-Session-Svr-Key 151 string +ATTRIBUTE Ascend-Multicast-Rate-Limit 152 integer +ATTRIBUTE Ascend-IF-Netmask 153 ipaddr +ATTRIBUTE Ascend-Remote-Addr 154 ipaddr +ATTRIBUTE Ascend-Multicast-Client 155 integer +ATTRIBUTE Ascend-FR-Circuit-Name 156 string +ATTRIBUTE Ascend-FR-LinkUp 157 integer +ATTRIBUTE Ascend-FR-Nailed-Grp 158 integer +ATTRIBUTE Ascend-FR-Type 159 integer +ATTRIBUTE Ascend-FR-Link-Mgt 160 integer +ATTRIBUTE Ascend-FR-N391 161 integer +ATTRIBUTE Ascend-FR-DCE-N392 162 integer +ATTRIBUTE Ascend-FR-DTE-N392 163 integer +ATTRIBUTE Ascend-FR-DCE-N393 164 integer +ATTRIBUTE Ascend-FR-DTE-N393 165 integer +ATTRIBUTE Ascend-FR-T391 166 integer +ATTRIBUTE Ascend-FR-T392 167 integer +ATTRIBUTE Ascend-Bridge-Address 168 string +ATTRIBUTE Ascend-TS-Idle-Limit 169 integer +ATTRIBUTE Ascend-TS-Idle-Mode 170 integer +ATTRIBUTE Ascend-DBA-Monitor 171 integer +ATTRIBUTE Ascend-Base-Channel-Count 172 integer +ATTRIBUTE Ascend-Minimum-Channels 173 integer +ATTRIBUTE Ascend-IPX-Route 174 string +ATTRIBUTE Ascend-FT1-Caller 175 integer +ATTRIBUTE Ascend-Backup 176 string +ATTRIBUTE Ascend-Call-Type 177 integer +ATTRIBUTE Ascend-Group 178 string +ATTRIBUTE Ascend-FR-DLCI 179 integer +ATTRIBUTE Ascend-FR-Profile-Name 180 string +ATTRIBUTE Ascend-Ara-PW 181 string +ATTRIBUTE Ascend-IPX-Node-Addr 182 string +ATTRIBUTE Ascend-Home-Agent-IP-Addr 183 ipaddr +ATTRIBUTE Ascend-Home-Agent-Password 184 string +ATTRIBUTE Ascend-Home-Network-Name 185 string +ATTRIBUTE Ascend-Home-Agent-UDP-Port 186 integer +ATTRIBUTE Ascend-Multilink-ID 187 integer +ATTRIBUTE Ascend-Num-In-Multilink 188 integer +ATTRIBUTE Ascend-First-Dest 189 ipaddr +ATTRIBUTE Ascend-Pre-Input-Octets 190 integer +ATTRIBUTE Ascend-Pre-Output-Octets 191 integer +ATTRIBUTE Ascend-Pre-Input-Packets 192 integer +ATTRIBUTE Ascend-Pre-Output-Packets 193 integer +ATTRIBUTE Ascend-Maximum-Time 194 integer +ATTRIBUTE Ascend-Disconnect-Cause 195 integer +ATTRIBUTE Ascend-Connect-Progress 196 integer +ATTRIBUTE Ascend-Data-Rate 197 integer +ATTRIBUTE Ascend-PreSession-Time 198 integer +ATTRIBUTE Ascend-Token-Idle 199 integer +ATTRIBUTE Ascend-Token-Immediate 200 integer +ATTRIBUTE Ascend-Require-Auth 201 integer +ATTRIBUTE Ascend-Number-Sessions 202 string +ATTRIBUTE Ascend-Authen-Alias 203 string +ATTRIBUTE Ascend-Token-Expiry 204 integer +ATTRIBUTE Ascend-Menu-Selector 205 string +ATTRIBUTE Ascend-Menu-Item 206 string +ATTRIBUTE Ascend-PW-Warntime 207 integer +ATTRIBUTE Ascend-PW-Lifetime 208 integer +ATTRIBUTE Ascend-IP-Direct 209 ipaddr +ATTRIBUTE Ascend-PPP-VJ-Slot-Comp 210 integer +ATTRIBUTE Ascend-PPP-VJ-1172 211 integer +ATTRIBUTE Ascend-PPP-Async-Map 212 integer +ATTRIBUTE Ascend-Third-Prompt 213 string +ATTRIBUTE Ascend-Send-Secret 214 string +ATTRIBUTE Ascend-Receive-Secret 215 string +ATTRIBUTE Ascend-IPX-Peer-Mode 216 integer +ATTRIBUTE Ascend-IP-Pool-Definition 217 string +ATTRIBUTE Ascend-Assign-IP-Pool 218 integer +ATTRIBUTE Ascend-FR-Direct 219 integer +ATTRIBUTE Ascend-FR-Direct-Profile 220 string +ATTRIBUTE Ascend-FR-Direct-DLCI 221 integer +ATTRIBUTE Ascend-Handle-IPX 222 integer +ATTRIBUTE Ascend-Netware-timeout 223 integer +ATTRIBUTE Ascend-IPX-Alias 224 integer +ATTRIBUTE Ascend-Metric 225 integer +ATTRIBUTE Ascend-PRI-Number-Type 226 integer +ATTRIBUTE Ascend-Dial-Number 227 string +ATTRIBUTE Ascend-Route-IP 228 integer +ATTRIBUTE Ascend-Route-IPX 229 integer +ATTRIBUTE Ascend-Bridge 230 integer +ATTRIBUTE Ascend-Send-Auth 231 integer +ATTRIBUTE Ascend-Send-Passwd 232 string +ATTRIBUTE Ascend-Link-Compression 233 integer +ATTRIBUTE Ascend-Target-Util 234 integer +ATTRIBUTE Ascend-Maximum-Channels 235 integer +ATTRIBUTE Ascend-Inc-Channel-Count 236 integer +ATTRIBUTE Ascend-Dec-Channel-Count 237 integer +ATTRIBUTE Ascend-Seconds-Of-History 238 integer +ATTRIBUTE Ascend-History-Weigh-Type 239 integer +ATTRIBUTE Ascend-Add-Seconds 240 integer +ATTRIBUTE Ascend-Remove-Seconds 241 integer +ATTRIBUTE Ascend-Idle-Limit 244 integer +ATTRIBUTE Ascend-Preempt-Limit 245 integer +ATTRIBUTE Ascend-Callback 246 integer +ATTRIBUTE Ascend-Data-Svc 247 integer +ATTRIBUTE Ascend-Force-56 248 integer +ATTRIBUTE Ascend-Billing-Number 249 string +ATTRIBUTE Ascend-Call-By-Call 250 integer +ATTRIBUTE Ascend-Transit-Number 251 string +ATTRIBUTE Ascend-Host-Info 252 string +ATTRIBUTE Ascend-PPP-Address 253 ipaddr +ATTRIBUTE Ascend-MPP-Idle-Percent 254 integer +ATTRIBUTE Ascend-Xmit-Rate 255 integer + + + +# Ascend protocols +VALUE Service-Type Dialout-Framed-User 5 +VALUE Framed-Protocol ARA 255 +VALUE Framed-Protocol MPP 256 +VALUE Framed-Protocol EURAW 257 +VALUE Framed-Protocol EUUI 258 +VALUE Framed-Protocol X25 259 +VALUE Framed-Protocol COMB 260 +VALUE Framed-Protocol FR 261 +VALUE Framed-Protocol MP 262 +VALUE Framed-Protocol FR-CIR 263 + + +# +# Ascend specific extensions +# Used by ASCEND MAX/Pipeline products (see above) +# + +VALUE Ascend-FR-Direct FR-Direct-No 0 +VALUE Ascend-FR-Direct FR-Direct-Yes 1 +VALUE Ascend-Handle-IPX Handle-IPX-None 0 +VALUE Ascend-Handle-IPX Handle-IPX-Client 1 +VALUE Ascend-Handle-IPX Handle-IPX-Server 2 +VALUE Ascend-IPX-Peer-Mode IPX-Peer-Router 0 +VALUE Ascend-IPX-Peer-Mode IPX-Peer-Dialin 1 +VALUE Ascend-Call-Type Nailed 1 +VALUE Ascend-Call-Type Nailed/Mpp 2 +VALUE Ascend-Call-Type Perm/Switched 3 +VALUE Ascend-FT1-Caller FT1-No 0 +VALUE Ascend-FT1-Caller FT1-Yes 1 +VALUE Ascend-PRI-Number-Type Unknown-Number 0 +VALUE Ascend-PRI-Number-Type Intl-Number 1 +VALUE Ascend-PRI-Number-Type National-Number 2 +VALUE Ascend-PRI-Number-Type Local-Number 4 +VALUE Ascend-PRI-Number-Type Abbrev-Number 5 +VALUE Ascend-Route-IPX Route-IPX-No 0 +VALUE Ascend-Route-IPX Route-IPX-Yes 1 +VALUE Ascend-Bridge Bridge-No 0 +VALUE Ascend-Bridge Bridge-Yes 1 +VALUE Ascend-TS-Idle-Mode TS-Idle-None 0 +VALUE Ascend-TS-Idle-Mode TS-Idle-Input 1 +VALUE Ascend-TS-Idle-Mode TS-Idle-Input-Output 2 +VALUE Ascend-Send-Auth Send-Auth-None 0 +VALUE Ascend-Send-Auth Send-Auth-PAP 1 +VALUE Ascend-Send-Auth Send-Auth-CHAP 2 +VALUE Ascend-Send-Auth Send-Auth-MS-CHAP 3 +VALUE Ascend-Link-Compression Link-Comp-None 0 +VALUE Ascend-Link-Compression Link-Comp-Stac 1 +VALUE Ascend-Link-Compression Link-Comp-Stac-Draft-9 2 +VALUE Ascend-Link-Compression Link-Comp-MS-Stac 3 +VALUE Ascend-History-Weigh-Type History-Constant 0 +VALUE Ascend-History-Weigh-Type History-Linear 1 +VALUE Ascend-History-Weigh-Type History-Quadratic 2 +VALUE Ascend-Callback Callback-No 0 +VALUE Ascend-Callback Callback-Yes 1 +VALUE Ascend-Expect-Callback Expect-Callback-No 0 +VALUE Ascend-Expect-Callback Expect-Callback-Yes 1 +VALUE Ascend-Data-Svc Switched-Voice-Bearer 0 +VALUE Ascend-Data-Svc Switched-56KR 1 +VALUE Ascend-Data-Svc Switched-64K 2 +VALUE Ascend-Data-Svc Switched-64KR 3 +VALUE Ascend-Data-Svc Switched-56K 4 +VALUE Ascend-Data-Svc Switched-384KR 5 +VALUE Ascend-Data-Svc Switched-384K 6 +VALUE Ascend-Data-Svc Switched-1536K 7 +VALUE Ascend-Data-Svc Switched-1536KR 8 +VALUE Ascend-Data-Svc Switched-128K 9 +VALUE Ascend-Data-Svc Switched-192K 10 +VALUE Ascend-Data-Svc Switched-256K 11 +VALUE Ascend-Data-Svc Switched-320K 12 +VALUE Ascend-Data-Svc Switched-384K-MR 13 +VALUE Ascend-Data-Svc Switched-448K 14 +VALUE Ascend-Data-Svc Switched-512K 15 +VALUE Ascend-Data-Svc Switched-576K 16 +VALUE Ascend-Data-Svc Switched-640K 17 +VALUE Ascend-Data-Svc Switched-704K 18 +VALUE Ascend-Data-Svc Switched-768K 19 +VALUE Ascend-Data-Svc Switched-832K 20 +VALUE Ascend-Data-Svc Switched-896K 21 +VALUE Ascend-Data-Svc Switched-960K 22 +VALUE Ascend-Data-Svc Switched-1024K 23 +VALUE Ascend-Data-Svc Switched-1088K 24 +VALUE Ascend-Data-Svc Switched-1152K 25 +VALUE Ascend-Data-Svc Switched-1216K 26 +VALUE Ascend-Data-Svc Switched-1280K 27 +VALUE Ascend-Data-Svc Switched-1344K 28 +VALUE Ascend-Data-Svc Switched-1408K 29 +VALUE Ascend-Data-Svc Switched-1472K 30 +VALUE Ascend-Data-Svc Switched-1600K 31 +VALUE Ascend-Data-Svc Switched-1664K 32 +VALUE Ascend-Data-Svc Switched-1728K 33 +VALUE Ascend-Data-Svc Switched-1792K 34 +VALUE Ascend-Data-Svc Switched-1856K 35 +VALUE Ascend-Data-Svc Switched-1920K 36 +VALUE Ascend-Data-Svc Switched-inherited 37 +VALUE Ascend-Data-Svc Switched-restricted-bearer-x30 38 +VALUE Ascend-Data-Svc Switched-clear-bearer-v110 39 +VALUE Ascend-Data-Svc Switched-restricted-64-x30 40 +VALUE Ascend-Data-Svc Switched-clear-56-v110 41 +VALUE Ascend-Data-Svc Switched-modem 42 +VALUE Ascend-Data-Svc Switched-atmodem 43 +VALUE Ascend-Data-Svc Nailed-56KR 1 +VALUE Ascend-Data-Svc Nailed-64K 2 +VALUE Ascend-Force-56 Force-56-No 0 +VALUE Ascend-Force-56 Force-56-Yes 1 +VALUE Ascend-PW-Lifetime Lifetime-In-Days 0 +VALUE Ascend-PW-Warntime Days-Of-Warning 0 +VALUE Ascend-PPP-VJ-1172 PPP-VJ-1172 1 +VALUE Ascend-PPP-VJ-Slot-Comp VJ-Slot-Comp-No 1 +VALUE Ascend-Require-Auth Not-Require-Auth 0 +VALUE Ascend-Require-Auth Require-Auth 1 +VALUE Ascend-Token-Immediate Tok-Imm-No 0 +VALUE Ascend-Token-Immediate Tok-Imm-Yes 1 +VALUE Ascend-DBA-Monitor DBA-Transmit 0 +VALUE Ascend-DBA-Monitor DBA-Transmit-Recv 1 +VALUE Ascend-DBA-Monitor DBA-None 2 +VALUE Ascend-FR-Type Ascend-FR-DTE 0 +VALUE Ascend-FR-Type Ascend-FR-DCE 1 +VALUE Ascend-FR-Type Ascend-FR-NNI 2 +VALUE Ascend-FR-Link-Mgt Ascend-FR-No-Link-Mgt 0 +VALUE Ascend-FR-Link-Mgt Ascend-FR-T1-617D 1 +VALUE Ascend-FR-Link-Mgt Ascend-FR-Q-933A 2 +VALUE Ascend-FR-LinkUp Ascend-LinkUp-Default 0 +VALUE Ascend-FR-LinkUp Ascend-LinkUp-AlwaysUp 1 +VALUE Ascend-Multicast-Client Multicast-No 0 +VALUE Ascend-Multicast-Client Multicast-Yes 1 +VALUE Ascend-User-Acct-Type Ascend-User-Acct-None 0 +VALUE Ascend-User-Acct-Type Ascend-User-Acct-User 1 +VALUE Ascend-User-Acct-Type Ascend-User-Acct-User-Default 2 +VALUE Ascend-User-Acct-Base Base-10 0 +VALUE Ascend-User-Acct-Base Base-16 1 +VALUE Ascend-DHCP-Reply DHCP-Reply-No 0 +VALUE Ascend-DHCP-Reply DHCP-Reply-Yes 1 +VALUE Ascend-Client-Assign-DNS DNS-Assign-No 0 +VALUE Ascend-Client-Assign-DNS DNS-Assign-Yes 1 +VALUE Ascend-Event-Type Ascend-ColdStart 1 +VALUE Ascend-Event-Type Ascend-Session-Event 2 +VALUE Ascend-BACP-Enable BACP-No 0 +VALUE Ascend-BACP-Enable BACP-Yes 1 +VALUE Ascend-Dialout-Allowed Dialout-Not-Allowed 0 +VALUE Ascend-Dialout-Allowed Dialout-Allowed 1 +VALUE Ascend-Shared-Profile-Enable Shared-Profile-No 0 +VALUE Ascend-Shared-Profile-Enable Shared-Profile-Yes 1 +VALUE Ascend-Temporary-Rtes Temp-Rtes-No 0 +VALUE Ascend-Temporary-Rtes Temp-Rtes-Yes 1 diff --git a/src/plugins/vbng/etc/dictionary.compat b/src/plugins/vbng/etc/dictionary.compat new file mode 100644 index 00000000..4c85ea87 --- /dev/null +++ b/src/plugins/vbng/etc/dictionary.compat @@ -0,0 +1,47 @@ +# +# Obsolete names for backwards compatibility with older users files. +# Move the $INCLUDE in the main dictionary file to the end if you want +# these names to be used in the "details" logfile. +# +ATTRIBUTE Client-Id 4 ipaddr +ATTRIBUTE Client-Port-Id 5 integer +ATTRIBUTE User-Service-Type 6 integer +ATTRIBUTE Framed-Address 8 ipaddr +ATTRIBUTE Framed-Netmask 9 ipaddr +ATTRIBUTE Framed-Filter-Id 11 string +ATTRIBUTE Login-Host 14 ipaddr +ATTRIBUTE Login-Port 16 integer +ATTRIBUTE Old-Password 17 string +ATTRIBUTE Port-Message 18 string +ATTRIBUTE Dialback-No 19 string +ATTRIBUTE Dialback-Name 20 string +ATTRIBUTE Challenge-State 24 string +VALUE Framed-Compression Van-Jacobsen-TCP-IP 1 +VALUE Framed-Compression VJ-TCP-IP 1 +VALUE Service-Type Shell-User 6 +VALUE Auth-Type Unix 1 +VALUE Service-Type Dialback-Login-User 3 +VALUE Service-Type Dialback-Framed-User 4 + +# +# For compatibility with MERIT users files. +# +ATTRIBUTE NAS-Port 5 integer +ATTRIBUTE Login-Host 14 ipaddr +ATTRIBUTE Login-Callback-Number 19 string +ATTRIBUTE Framed-Callback-Id 20 string +ATTRIBUTE Client-Port-DNIS 30 string +ATTRIBUTE Caller-ID 31 string +VALUE Service-Type Login 1 +VALUE Service-Type Framed 2 +VALUE Service-Type Callback-Login 3 +VALUE Service-Type Callback-Framed 4 +VALUE Service-Type Exec-User 7 + +# +# For compatibility with ESVA RADIUS, Old Cistron RADIUS +# +ATTRIBUTE Session 1034 integer +ATTRIBUTE User-Name-Is-Star 1035 integer +VALUE User-Name-Is-Star No 0 +VALUE User-Name-Is-Star Yes 1 diff --git a/src/plugins/vbng/etc/dictionary.dhcp b/src/plugins/vbng/etc/dictionary.dhcp new file mode 100644 index 00000000..cf329348 --- /dev/null +++ b/src/plugins/vbng/etc/dictionary.dhcp @@ -0,0 +1,440 @@ +# -*- text -*- +# Copyright (C) 2011 The FreeRADIUS Server project and contributors +############################################################################## +# +# DHCP to RADUS gateway dictionary. +# +# http://www.iana.org/assignments/bootp-dhcp-parameters +# +# Also http://www.networksorcery.com/enp/protocol/bootp/options.htm +# +# http://www.bind9.net/rfc-dhcp +# +# $Id: 73632c57d3860bb30749a1df4dad2320d5f79f31 $ +# +############################################################################## + +# + +# This is really Apollo's number, but since they're out of business, +# I don't think they'll be needing this. +# +# HP owns the Apollo assets, but let's not worry about that. +# +# The vendor codes are 2 octets, because we need 256 numbers +# for the base DHCP options, PLUS a few for the DHCP headers, +# which aren't in option format. +# +# On top of that, a number of options are really TLV's. +# We need to be able to understand them, too. +# +VENDOR DHCP 54 format=2,1 + +BEGIN-VENDOR DHCP + +ATTRIBUTE DHCP-Opcode 256 byte +ATTRIBUTE DHCP-Hardware-Type 257 byte +ATTRIBUTE DHCP-Hardware-Address-Length 258 byte +ATTRIBUTE DHCP-Hop-Count 259 byte +ATTRIBUTE DHCP-Transaction-Id 260 integer +ATTRIBUTE DHCP-Number-of-Seconds 261 short +ATTRIBUTE DHCP-Flags 262 short +ATTRIBUTE DHCP-Client-IP-Address 263 ipaddr +ATTRIBUTE DHCP-Your-IP-Address 264 ipaddr +ATTRIBUTE DHCP-Server-IP-Address 265 ipaddr +ATTRIBUTE DHCP-Gateway-IP-Address 266 ipaddr +ATTRIBUTE DHCP-Client-Hardware-Address 267 ether # 16 octets +ATTRIBUTE DHCP-Server-Host-Name 268 string # 64 octets +ATTRIBUTE DHCP-Boot-Filename 269 string # 128 octets + +ATTRIBUTE DHCP-Relay-To-IP-Address 270 ipaddr +ATTRIBUTE DHCP-Relay-Max-Hop-Count 271 integer + +# This is copied from the request packet, giaddr, and +# added to the reply packet by the server core. +ATTRIBUTE DHCP-Relay-IP-Address 272 ipaddr + +VALUE DHCP-Flags Broadcast 0x8000 + +VALUE DHCP-Hardware-Type Ethernet 1 +VALUE DHCP-Hardware-Type Experiemental-Ethernet 2 +VALUE DHCP-Hardware-Type AX.25 3 +VALUE DHCP-Hardware-Type Proteon-Token-Ring 4 +VALUE DHCP-Hardware-Type Chaos 5 +VALUE DHCP-Hardware-Type IEEE-802 6 +VALUE DHCP-Hardware-Type Arcnet 7 +VALUE DHCP-Hardware-Type Hyperchannel 8 +VALUE DHCP-Hardware-Type Lanstar 9 +VALUE DHCP-Hardware-Type Autonet-Short-Address 10 +VALUE DHCP-Hardware-Type LocalTalk 11 +VALUE DHCP-Hardware-Type LocalNet 12 +VALUE DHCP-Hardware-Type Ultra-Link 13 +VALUE DHCP-Hardware-Type SMDS 14 +VALUE DHCP-Hardware-Type Frame-Relay 15 +VALUE DHCP-Hardware-Type ATM-16 16 +VALUE DHCP-Hardware-Type HDLC 17 +VALUE DHCP-Hardware-Type Fibre-Channel 18 +VALUE DHCP-Hardware-Type ATM-19 19 +VALUE DHCP-Hardware-Type Serial-Line 20 +VALUE DHCP-Hardware-Type ATM-21 21 +VALUE DHCP-Hardware-Type MIL-STD-188-220 22 +VALUE DHCP-Hardware-Type Metricom 23 +VALUE DHCP-Hardware-Type IEEE-1394 24 +VALUE DHCP-Hardware-Type MAPOS 25 +VALUE DHCP-Hardware-Type Twinaxial 26 +VALUE DHCP-Hardware-Type EUI-64 27 +VALUE DHCP-Hardware-Type HIPARP 28 +VALUE DHCP-Hardware-Type IP-Over-ISO-7816-3 29 +VALUE DHCP-Hardware-Type ARPSec 30 +VALUE DHCP-Hardware-Type IPSec-Tunnel 31 +VALUE DHCP-Hardware-Type Infiniband 32 +VALUE DHCP-Hardware-Type CAI-TIA-102 33 + +############################################################################## +# +# DHCP Options, with comments. For now, many are "octets", +# as FreeRADIUS doesn't handle complex data structures. +# +############################################################################## + +#ATTRIBUTE DHCP-Pad 0 octets +ATTRIBUTE DHCP-Subnet-Mask 1 ipaddr +# Time Offset in twos-complement notation. +ATTRIBUTE DHCP-Time-Offset 2 integer +ATTRIBUTE DHCP-Router-Address 3 ipaddr array +ATTRIBUTE DHCP-Time-Server 4 ipaddr array +ATTRIBUTE DHCP-IEN-116-Name-Server 5 ipaddr array +ATTRIBUTE DHCP-Domain-Name-Server 6 ipaddr array +# Logging-Server addresses +ATTRIBUTE DHCP-Log-Server 7 ipaddr array +ATTRIBUTE DHCP-Quotes-Server 8 ipaddr array +ATTRIBUTE DHCP-LPR-Server 9 ipaddr array +ATTRIBUTE DHCP-Impress-Server 10 ipaddr array +ATTRIBUTE DHCP-RLP-Server 11 ipaddr array +# Hostname string +ATTRIBUTE DHCP-Hostname 12 string +# Size of boot file in 512 byte +ATTRIBUTE DHCP-Boot-File-Size 13 short +# Client to dump and name +ATTRIBUTE DHCP-Merit-Dump-File 14 octets +ATTRIBUTE DHCP-Domain-Name 15 string +ATTRIBUTE DHCP-Swap-Server 16 ipaddr +# Path name for root disk +ATTRIBUTE DHCP-Root-Path 17 string +ATTRIBUTE DHCP-Bootp-Extensions-Path 18 string +ATTRIBUTE DHCP-IP-Forward-Enable 19 byte +ATTRIBUTE DHCP-Source-Route-Enable 20 byte +# Routing Policy Filters +ATTRIBUTE DHCP-Policy-Filter 21 octets +ATTRIBUTE DHCP-Max-Datagram-Reassembly-Sz 22 short +ATTRIBUTE DHCP-Default-IP-TTL 23 octets +ATTRIBUTE DHCP-Path-MTU-Aging-Timeout 24 integer +ATTRIBUTE DHCP-Path-MTU-Plateau-Table 25 short array +ATTRIBUTE DHCP-Interface-MTU-Size 26 short +ATTRIBUTE DHCP-All-Subnets-Are-Local 27 byte +ATTRIBUTE DHCP-Broadcast-Address 28 ipaddr +ATTRIBUTE DHCP-Perform-Mask-Discovery 29 byte +ATTRIBUTE DHCP-Provide-Mask-To-Others 30 byte +ATTRIBUTE DHCP-Perform-Router-Discovery 31 byte +ATTRIBUTE DHCP-Router-Solicitation-Address 32 ipaddr +# first is destination address, second is router. +ATTRIBUTE DHCP-Static-Routes 33 ipaddr array +ATTRIBUTE DHCP-Trailer-Encapsulation 34 byte +ATTRIBUTE DHCP-ARP-Cache-Timeout 35 integer +ATTRIBUTE DHCP-Ethernet-Encapsulation 36 byte +ATTRIBUTE DHCP-Default-TCP-TTL 37 byte +ATTRIBUTE DHCP-Keep-Alive-Interval 38 integer +ATTRIBUTE DHCP-Keep-Alive-Garbage 39 byte +ATTRIBUTE DHCP-NIS-Domain-Name 40 string +ATTRIBUTE DHCP-NIS-Servers 41 ipaddr array +ATTRIBUTE DHCP-NTP-Servers 42 ipaddr array +# N Vendor Specific Information +ATTRIBUTE DHCP-Vendor 43 octets # tlv +ATTRIBUTE DHCP-NETBIOS-Name-Servers 44 ipaddr array +ATTRIBUTE DHCP-NETBIOS-Dgm-Dist-Servers 45 ipaddr array +ATTRIBUTE DHCP-NETBIOS-Node-Type 46 byte +# N NETBIOS Scope +ATTRIBUTE DHCP-NETBIOS 47 octets +ATTRIBUTE DHCP-X-Window-Font-Server 48 ipaddr array +ATTRIBUTE DHCP-X-Window-Display-Mgr 49 ipaddr array +ATTRIBUTE DHCP-Requested-IP-Address 50 ipaddr +ATTRIBUTE DHCP-IP-Address-Lease-Time 51 integer +# Overload "sname" or "file" +ATTRIBUTE DHCP-Overload 52 byte +ATTRIBUTE DHCP-Message-Type 53 byte +ATTRIBUTE DHCP-DHCP-Server-Identifier 54 ipaddr + +# Array of 1-byte numbers indicating which options the client +# would like to see in the response. +ATTRIBUTE DHCP-Parameter-Request-List 55 byte array +ATTRIBUTE DHCP-DHCP-Error-Message 56 octets +ATTRIBUTE DHCP-DHCP-Maximum-Msg-Size 57 short +ATTRIBUTE DHCP-Renewal-Time 58 integer +ATTRIBUTE DHCP-Rebinding-Time 59 integer +ATTRIBUTE DHCP-Vendor-Class-Identifier 60 string + +# Client Identifier +# First octets is DHCP-Hardware-Type, rest are type-specific data, +# e.g. MAC address. +ATTRIBUTE DHCP-Client-Identifier 61 ether +ATTRIBUTE DHCP-Netware-Domain-Name 62 octets +ATTRIBUTE DHCP-Netware-Sub-Options 63 octets +ATTRIBUTE DHCP-NIS-Client-Domain-Name 64 octets +ATTRIBUTE DHCP-NIS-Server-Address 65 ipaddr +ATTRIBUTE DHCP-TFTP-Server-Name 66 string +ATTRIBUTE DHCP-Boot-File-Name 67 string +# Home Agent Addresses +ATTRIBUTE DHCP-Home-Agent-Address 68 octets +ATTRIBUTE DHCP-SMTP-Server-Address 69 ipaddr array +ATTRIBUTE DHCP-POP3-Server-Address 70 ipaddr array +ATTRIBUTE DHCP-NNTP-Server-Address 71 ipaddr array +ATTRIBUTE DHCP-WWW-Server-Address 72 ipaddr array +ATTRIBUTE DHCP-Finger-Server-Address 73 ipaddr array +ATTRIBUTE DHCP-IRC-Server-Address 74 ipaddr array +ATTRIBUTE DHCP-StreetTalk-Server-Address 75 ipaddr array +ATTRIBUTE DHCP-STDA-Server-Address 76 ipaddr array +# User Class Information +ATTRIBUTE DHCP-User-Class 77 octets +# directory agent information +ATTRIBUTE DHCP-Directory-Agent 78 octets +# service location agent scope +ATTRIBUTE DHCP-Service-Scope 79 octets +# Rapid Commit +ATTRIBUTE DHCP-Rapid-Commit 80 octets +# Fully Qualified Domain Name +ATTRIBUTE DHCP-Client-FQDN 81 string +# Relay Agent Information +ATTRIBUTE DHCP-Relay-Agent-Information 82 tlv + +ATTRIBUTE DHCP-Agent-Circuit-Id 82.1 octets +ATTRIBUTE DHCP-Agent-Remote-Id 82.2 octets + +ATTRIBUTE DHCP-Relay-Circuit-Id 82.1 octets +ATTRIBUTE DHCP-Relay-Remote-Id 82.2 octets + +# 3 is reserved and shouldn't be used for anything +ATTRIBUTE DHCP-Docsis-Device-Class 82.4 integer +ATTRIBUTE DHCP-Relay-Link-Selection 82.5 ipaddr +ATTRIBUTE DHCP-Subscriber-Id 82.6 string + +# AGH! RADIUS inside of DHCP! +ATTRIBUTE DHCP-RADIUS-Attributes 82.7 octets + +# Horribly complicated +ATTRIBUTE DHCP-Authentication-Information 82.8 octets +ATTRIBUTE DHCP-Vendor-Specific-Information 82.9 octets +ATTRIBUTE DHCP-Relay-Agent-Flags 82.10 byte +ATTRIBUTE DHCP-Server-Identifier-Override 82.11 ipaddr + +# Internet Storage Name Service +ATTRIBUTE DHCP-iSNS 83 octets +# Novell Directory Services +ATTRIBUTE DHCP-NDS-Servers 85 octets +# Novell Directory Services +ATTRIBUTE DHCP-NDS-Tree-Name 86 octets +# Novell Directory Services +ATTRIBUTE DHCP-NDS-Context 87 octets +# Authentication +ATTRIBUTE DHCP-Authentication 90 octets + +ATTRIBUTE DHCP-Client-Last-Txn-Time 91 octets + +ATTRIBUTE DHCP-associated-ip 92 octets +# Client System Architecture +ATTRIBUTE DHCP-Client-System 93 octets +# Client Network Device Interface +ATTRIBUTE DHCP-Client-NDI 94 octets +# Lightweight Directory Access Protocol +ATTRIBUTE DHCP-LDAP 95 octets +# UUID/GUID-based Client Identifier +ATTRIBUTE DHCP-UUID/GUID 97 octets +# Open Group's User Authentication +ATTRIBUTE DHCP-User-Auth 98 octets +# NetInfo Parent-Server Address +ATTRIBUTE DHCP-Netinfo-Address 112 octets +# NetInfo Parent-Server Tag +ATTRIBUTE DHCP-Netinfo-Tag 113 octets +# URL +ATTRIBUTE DHCP-URL 114 octets +# DHCP Auto-Configuration +ATTRIBUTE DHCP-Auto-Config 116 byte +# Name Service Search +ATTRIBUTE DHCP-Name-Service-Search 117 octets +# Subnet Selection Option +ATTRIBUTE DHCP-Subnet-Selection-Option 118 octets +# DNS domain serach list +ATTRIBUTE DHCP-Domain-Search 119 octets +# SIP-Servers DHCP Option +ATTRIBUTE DHCP-SIP-Servers-DHCP-Option 120 octets +# Classless Static Route Option +ATTRIBUTE DHCP-Classless-Static-Route 121 octets +# CableLabs Client Configuration +ATTRIBUTE DHCP-CCC 122 octets +# 16 GeoConf Option +ATTRIBUTE DHCP-GeoConf-Option 123 octets + +# Vendor Class +# +# String name that defines the vendor space used for the TLV's +# in option 125. +# +ATTRIBUTE DHCP-V-I-Vendor-Class 124 octets +# Vendor-Specific +ATTRIBUTE DHCP-V-I-Vendor-Specific 125 octets # tlv +ATTRIBUTE DHCP-Etherboot 128 ether +# (for IP Phone software load) +ATTRIBUTE DHCP-TFTP-Server-IP-Address 128 octets + +ATTRIBUTE DHCP-Call-Server-IP-address 129 octets + +ATTRIBUTE DHCP-Ethernet-Interface 130 octets + +ATTRIBUTE DHCP-Vendor-Discrimination-Str 130 octets + +ATTRIBUTE DHCP-Remote-Stats-Svr-IP-Address 131 octets + +ATTRIBUTE DHCP-IEEE-802.1Q-L2-Priority 132 octets + +ATTRIBUTE DHCP-IEEE-802.1P-VLAN-ID 133 octets + +ATTRIBUTE DHCP-Diffserv-Code-Point 134 octets + +ATTRIBUTE DHCP-HTTP-Proxy 135 octets + +ATTRIBUTE DHCP-Cisco-TFTP-Server-IP-Addresses 150 ipaddr array + +ATTRIBUTE DHCP-End-Of-Options 255 byte + +VALUE DHCP-Opcode Client-Message 1 +VALUE DHCP-Opcode Server-Message 2 + +VALUE DHCP-Message-Type DHCP-Do-Not-Respond 0 +VALUE DHCP-Message-Type DHCP-Discover 1 +VALUE DHCP-Message-Type DHCP-Offer 2 +VALUE DHCP-Message-Type DHCP-Request 3 +VALUE DHCP-Message-Type DHCP-Decline 4 +VALUE DHCP-Message-Type DHCP-Ack 5 +VALUE DHCP-Message-Type DHCP-NAK 6 +VALUE DHCP-Message-Type DHCP-Release 7 +VALUE DHCP-Message-Type DHCP-Inform 8 +VALUE DHCP-Message-Type DHCP-Force-Renew 9 + +VALUE DHCP-Parameter-Request-List DHCP-Subnet-Mask 1 +VALUE DHCP-Parameter-Request-List DHCP-Time-Offset 2 +VALUE DHCP-Parameter-Request-List DHCP-Router-Address 3 +VALUE DHCP-Parameter-Request-List DHCP-Time-Server 4 +VALUE DHCP-Parameter-Request-List DHCP-IEN-116-Name-Server 5 +VALUE DHCP-Parameter-Request-List DHCP-Domain-Name-Server 6 +VALUE DHCP-Parameter-Request-List DHCP-Log-Server 7 +VALUE DHCP-Parameter-Request-List DHCP-Quotes-Server 8 +VALUE DHCP-Parameter-Request-List DHCP-LPR-Server 9 +VALUE DHCP-Parameter-Request-List DHCP-Impress-Server 10 +VALUE DHCP-Parameter-Request-List DHCP-RLP-Server 11 +VALUE DHCP-Parameter-Request-List DHCP-Hostname 12 +VALUE DHCP-Parameter-Request-List DHCP-Boot-File-Size 13 +VALUE DHCP-Parameter-Request-List DHCP-Merit-Dump-File 14 +VALUE DHCP-Parameter-Request-List DHCP-Domain-Name 15 +VALUE DHCP-Parameter-Request-List DHCP-Swap-Server 16 +VALUE DHCP-Parameter-Request-List DHCP-Root-Path 17 +VALUE DHCP-Parameter-Request-List DHCP-Bootp-Extensions-Path 18 +VALUE DHCP-Parameter-Request-List DHCP-IP-Forward-Enable 19 +VALUE DHCP-Parameter-Request-List DHCP-Source-Route-Enable 20 +VALUE DHCP-Parameter-Request-List DHCP-Policy-Filter 21 +VALUE DHCP-Parameter-Request-List DHCP-Max-Datagram-Reassembly-Sz 22 +VALUE DHCP-Parameter-Request-List DHCP-Default-IP-TTL 23 +VALUE DHCP-Parameter-Request-List DHCP-Path-MTU-Aging-Timeout 24 +VALUE DHCP-Parameter-Request-List DHCP-Path-MTU-Plateau-Table 25 +VALUE DHCP-Parameter-Request-List DHCP-Interface-MTU-Size 26 +VALUE DHCP-Parameter-Request-List DHCP-All-Subnets-Are-Local 27 +VALUE DHCP-Parameter-Request-List DHCP-Broadcast-Address 28 +VALUE DHCP-Parameter-Request-List DHCP-Perform-Mask-Discovery 29 +VALUE DHCP-Parameter-Request-List DHCP-Provide-Mask-To-Others 30 +VALUE DHCP-Parameter-Request-List DHCP-Perform-Router-Discovery 31 +VALUE DHCP-Parameter-Request-List DHCP-Router-Solicitation-Address 32 +VALUE DHCP-Parameter-Request-List DHCP-Static-Routes 33 +VALUE DHCP-Parameter-Request-List DHCP-Trailer-Encapsulation 34 +VALUE DHCP-Parameter-Request-List DHCP-ARP-Cache-Timeout 35 +VALUE DHCP-Parameter-Request-List DHCP-Ethernet-Encapsulation 36 +VALUE DHCP-Parameter-Request-List DHCP-Default-TCP-TTL 37 +VALUE DHCP-Parameter-Request-List DHCP-Keep-Alive-Interval 38 +VALUE DHCP-Parameter-Request-List DHCP-Keep-Alive-Garbage 39 +VALUE DHCP-Parameter-Request-List DHCP-NIS-Domain-Name 40 +VALUE DHCP-Parameter-Request-List DHCP-NIS-Servers 41 +VALUE DHCP-Parameter-Request-List DHCP-NTP-Servers 42 +VALUE DHCP-Parameter-Request-List DHCP-Vendor 43 +VALUE DHCP-Parameter-Request-List DHCP-NETBIOS-Name-Servers 44 +VALUE DHCP-Parameter-Request-List DHCP-NETBIOS-Dgm-Dist-Servers 45 +VALUE DHCP-Parameter-Request-List DHCP-NETBIOS-Node-Type 46 +VALUE DHCP-Parameter-Request-List DHCP-NETBIOS 47 +VALUE DHCP-Parameter-Request-List DHCP-X-Window-Font-Server 48 +VALUE DHCP-Parameter-Request-List DHCP-X-Window-Display-Mgr 49 +VALUE DHCP-Parameter-Request-List DHCP-Requested-IP-Address 50 +VALUE DHCP-Parameter-Request-List DHCP-IP-Address-Lease-Time 51 +VALUE DHCP-Parameter-Request-List DHCP-Overload 52 +VALUE DHCP-Parameter-Request-List DHCP-Message-Type 53 +VALUE DHCP-Parameter-Request-List DHCP-DHCP-Server-Identifier 54 +VALUE DHCP-Parameter-Request-List DHCP-Parameter-Request-List 55 +VALUE DHCP-Parameter-Request-List DHCP-DHCP-Error-Message 56 +VALUE DHCP-Parameter-Request-List DHCP-DHCP-Maximum-Msg-Size 57 +VALUE DHCP-Parameter-Request-List DHCP-Renewal-Time 58 +VALUE DHCP-Parameter-Request-List DHCP-Rebinding-Time 59 +VALUE DHCP-Parameter-Request-List DHCP-Class-Identifier 60 +VALUE DHCP-Parameter-Request-List DHCP-Client-Identifier 61 +VALUE DHCP-Parameter-Request-List DHCP-Netware-Domain-Name 62 +VALUE DHCP-Parameter-Request-List DHCP-Netware-Sub-Options 63 +VALUE DHCP-Parameter-Request-List DHCP-NIS-Client-Domain-Name 64 +VALUE DHCP-Parameter-Request-List DHCP-NIS-Server-Address 65 +VALUE DHCP-Parameter-Request-List DHCP-TFTP-Server-Name 66 +VALUE DHCP-Parameter-Request-List DHCP-Boot-File-Name 67 +VALUE DHCP-Parameter-Request-List DHCP-Home-Agent-Address 68 +VALUE DHCP-Parameter-Request-List DHCP-SMTP-Server-Address 69 +VALUE DHCP-Parameter-Request-List DHCP-POP3-Server-Address 70 +VALUE DHCP-Parameter-Request-List DHCP-NNTP-Server-Address 71 +VALUE DHCP-Parameter-Request-List DHCP-WWW-Server-Address 72 +VALUE DHCP-Parameter-Request-List DHCP-Finger-Server-Address 73 +VALUE DHCP-Parameter-Request-List DHCP-IRC-Server-Address 74 +VALUE DHCP-Parameter-Request-List DHCP-StreetTalk-Server-Address 75 +VALUE DHCP-Parameter-Request-List DHCP-STDA-Server-Address 76 +VALUE DHCP-Parameter-Request-List DHCP-User-Class 77 +VALUE DHCP-Parameter-Request-List DHCP-Directory-Agent 78 +VALUE DHCP-Parameter-Request-List DHCP-Service-Scope 79 +VALUE DHCP-Parameter-Request-List DHCP-Rapid-Commit 80 +VALUE DHCP-Parameter-Request-List DHCP-Client-FQDN 81 +VALUE DHCP-Parameter-Request-List DHCP-Relay-Agent-Information 82 +VALUE DHCP-Parameter-Request-List DHCP-iSNS 83 +VALUE DHCP-Parameter-Request-List DHCP-NDS-Servers 85 +VALUE DHCP-Parameter-Request-List DHCP-NDS-Tree-Name 86 +VALUE DHCP-Parameter-Request-List DHCP-NDS-Context 87 +VALUE DHCP-Parameter-Request-List DHCP-Authentication 90 +VALUE DHCP-Parameter-Request-List DHCP-Client-Last-Txn-Time 91 +VALUE DHCP-Parameter-Request-List DHCP-associated-ip 92 +VALUE DHCP-Parameter-Request-List DHCP-Client-System 93 +VALUE DHCP-Parameter-Request-List DHCP-Client-NDI 94 +VALUE DHCP-Parameter-Request-List DHCP-LDAP 95 +VALUE DHCP-Parameter-Request-List DHCP-UUID/GUID 97 +VALUE DHCP-Parameter-Request-List DHCP-User-Auth 98 +VALUE DHCP-Parameter-Request-List DHCP-Netinfo-Address 112 +VALUE DHCP-Parameter-Request-List DHCP-Netinfo-Tag 113 +VALUE DHCP-Parameter-Request-List DHCP-URL 114 +VALUE DHCP-Parameter-Request-List DHCP-Auto-Config 116 +VALUE DHCP-Parameter-Request-List DHCP-Name-Service-Search 117 +VALUE DHCP-Parameter-Request-List DHCP-Subnet-Selection-Option 118 +VALUE DHCP-Parameter-Request-List DHCP-Domain-Search 119 +VALUE DHCP-Parameter-Request-List DHCP-SIP-Servers-DHCP-Option 120 +VALUE DHCP-Parameter-Request-List DHCP-Classless-Static-Route 121 +VALUE DHCP-Parameter-Request-List DHCP-CCC 122 +VALUE DHCP-Parameter-Request-List DHCP-GeoConf-Option 123 +VALUE DHCP-Parameter-Request-List DHCP-V-I-Vendor-Class 124 +VALUE DHCP-Parameter-Request-List DHCP-V-I-Vendor-Specific 125 +VALUE DHCP-Parameter-Request-List DHCP-Etherboot 128 +VALUE DHCP-Parameter-Request-List DHCP-TFTP-Server-IP-Address 128 +VALUE DHCP-Parameter-Request-List DHCP-Call-Server-IP-address 129 +VALUE DHCP-Parameter-Request-List DHCP-Ethernet-Interface 130 +VALUE DHCP-Parameter-Request-List DHCP-Vendor-Discrimination-Str 130 +VALUE DHCP-Parameter-Request-List DHCP-Remote-Stats-Svr-IP-Address 131 +VALUE DHCP-Parameter-Request-List DHCP-IEEE-802.1P-VLAN-ID 132 +VALUE DHCP-Parameter-Request-List DHCP-IEEE-802.1Q-L2-Priority 133 +VALUE DHCP-Parameter-Request-List DHCP-Diffserv-Code-Point 134 +VALUE DHCP-Parameter-Request-List DHCP-HTTP-Proxy 135 + +END-VENDOR DHCP diff --git a/src/plugins/vbng/etc/dictionary.merit b/src/plugins/vbng/etc/dictionary.merit new file mode 100644 index 00000000..7d675e50 --- /dev/null +++ b/src/plugins/vbng/etc/dictionary.merit @@ -0,0 +1,17 @@ +# +# Experimental extensions, configuration only (for check-items) +# Names/numbers as per the MERIT extensions (if possible). +# +ATTRIBUTE NAS-Identifier 32 string +ATTRIBUTE Proxy-State 33 string +ATTRIBUTE Login-LAT-Service 34 string +ATTRIBUTE Login-LAT-Node 35 string +ATTRIBUTE Login-LAT-Group 36 string +ATTRIBUTE Framed-AppleTalk-Link 37 integer +ATTRIBUTE Framed-AppleTalk-Network 38 integer +ATTRIBUTE Framed-AppleTalk-Zone 39 string +ATTRIBUTE Acct-Input-Packets 47 integer +ATTRIBUTE Acct-Output-Packets 48 integer +# 8 is a MERIT extension. +VALUE Service-Type Authenticate-Only 8 + diff --git a/src/plugins/vbng/etc/dictionary.sip b/src/plugins/vbng/etc/dictionary.sip new file mode 100644 index 00000000..149fa4cb --- /dev/null +++ b/src/plugins/vbng/etc/dictionary.sip @@ -0,0 +1,77 @@ +# +# Updated 97/06/13 to livingston-radius-2.01 miquels@cistron.nl +# +# This file contains dictionary translations for parsing +# requests and generating responses. All transactions are +# composed of Attribute/Value Pairs. The value of each attribute +# is specified as one of 4 data types. Valid data types are: +# +# string - 0-253 octets +# ipaddr - 4 octets in network byte order +# integer - 32 bit value in big endian order (high byte first) +# date - 32 bit value in big endian order - seconds since +# 00:00:00 GMT, Jan. 1, 1970 +# +# Enumerated values are stored in the user file with dictionary +# VALUE translations for easy administration. +# +# Example: +# +# ATTRIBUTE VALUE +# --------------- ----- +# Framed-Protocol = PPP +# 7 = 1 (integer encoding) +# + +# +# Experimental SIP Attributes/Values (draft-sterman-aaa-sip-00.txt etc) +# +ATTRIBUTE Sip-Method 101 integer +ATTRIBUTE Sip-Response-Code 102 integer +ATTRIBUTE Sip-CSeq 103 string +ATTRIBUTE Sip-To-Tag 104 string +ATTRIBUTE Sip-From-Tag 105 string +ATTRIBUTE Sip-Branch-ID 106 string +ATTRIBUTE Sip-Translated-Request-URI 107 string +ATTRIBUTE Sip-Source-IP-Address 108 ipaddr +ATTRIBUTE Sip-Source-Port 109 integer +ATTRIBUTE Sip-User-ID 110 string +ATTRIBUTE Sip-User-Realm 111 string +ATTRIBUTE Sip-User-Nonce 112 string +ATTRIBUTE Sip-User-Method 113 string +ATTRIBUTE Sip-User-Digest-URI 114 string +ATTRIBUTE Sip-User-Nonce-Count 115 string +ATTRIBUTE Sip-User-QOP 116 string +ATTRIBUTE Sip-User-Opaque 117 string +ATTRIBUTE Sip-User-Response 118 string +ATTRIBUTE Sip-User-CNonce 119 string +ATTRIBUTE Sip-URI-User 208 string +ATTRIBUTE Sip-Req-URI 210 string +ATTRIBUTE Sip-CC 212 string +ATTRIBUTE Sip-RPId 213 string +ATTRIBUTE Digest-Response 206 string +ATTRIBUTE Digest-Attributes 207 string +ATTRIBUTE Digest-Realm 1063 string +ATTRIBUTE Digest-Nonce 1064 string +ATTRIBUTE Digest-Method 1065 string +ATTRIBUTE Digest-URI 1066 string +ATTRIBUTE Digest-QOP 1067 string +ATTRIBUTE Digest-Algorithm 1068 string +ATTRIBUTE Digest-Body-Digest 1069 string +ATTRIBUTE Digest-CNonce 1070 string +ATTRIBUTE Digest-Nonce-Count 1071 string +ATTRIBUTE Digest-User-Name 1072 string + +VALUE Service-Type SIP 15 + +VALUE Sip-Method Other 0 +VALUE Sip-Method Invite 1 +VALUE Sip-Method Cancel 2 +VALUE Sip-Method Ack 3 +VALUE Sip-Method Bye 4 + +VALUE Sip-Response-Code Other 0 +VALUE Sip-Response-Code Invite 1 +VALUE Sip-Response-Code Cancel 2 +VALUE Sip-Response-Code Ack 3 +VALUE Sip-Response-Code Bye 4 diff --git a/src/plugins/vbng/etc/issue b/src/plugins/vbng/etc/issue new file mode 100644 index 00000000..62544873 --- /dev/null +++ b/src/plugins/vbng/etc/issue @@ -0,0 +1,5 @@ +(\I) +----------------------------------------------------- +\S \R (\N) (port \L) +----------------------------------------------------- + diff --git a/src/plugins/vbng/etc/port-id-map b/src/plugins/vbng/etc/port-id-map new file mode 100644 index 00000000..9088a0b9 --- /dev/null +++ b/src/plugins/vbng/etc/port-id-map @@ -0,0 +1,24 @@ +# +# port-id-map +# +# This file describes the ttyname to port id mapping. The port id +# is reported as part of a RADIUS authentication or accouting request. +# +#ttyname (as returned by ttyname(3)) port-id +/dev/tty1 1 +/dev/tty2 2 +/dev/tty3 3 +/dev/tty4 4 +/dev/tty5 5 +/dev/tty6 6 +/dev/tty7 7 +/dev/tty8 8 +/dev/ttyS0 9 +/dev/ttyS1 10 +/dev/ttyS2 11 +/dev/ttyS3 12 +/dev/ttyS4 13 +/dev/ttyS5 14 +/dev/ttyS6 15 +/dev/ttyS7 16 + \ No newline at end of file diff --git a/src/plugins/vbng/etc/radiusclient.conf b/src/plugins/vbng/etc/radiusclient.conf new file mode 100644 index 00000000..3a315b46 --- /dev/null +++ b/src/plugins/vbng/etc/radiusclient.conf @@ -0,0 +1,92 @@ +# General settings + +# specify which authentication comes first respectively which +# authentication is used. possible values are: "radius" and "local". +# if you specify "radius,local" then the RADIUS server is asked +# first then the local one. if only one keyword is specified only +# this server is asked. +auth_order radius,local + +# maximum login tries a user has +login_tries 4 + +# timeout for all login tries +# if this time is exceeded the user is kicked out +login_timeout 60 + +# name of the nologin file which when it exists disables logins. +# it may be extended by the ttyname which will result in +# a terminal specific lock (e.g. /etc/nologin.ttyS2 will disable +# logins on /dev/ttyS2) +nologin /etc/nologin + +# name of the issue file. it's only display when no username is passed +# on the radlogin command line +issue /usr/local/etc/radiusclient/issue + +# RADIUS settings + +# RADIUS server to use for authentication requests. this config +# item can appear more then one time. if multiple servers are +# defined they are tried in a round robin fashion if one +# server is not answering. +# optionally you can specify a the port number on which is remote +# RADIUS listens separated by a colon from the hostname. if +# no port is specified /etc/services is consulted of the radius +# service. if this fails also a compiled in default is used. +authserver localhost + +# RADIUS server to use for accouting requests. All that I +# said for authserver applies, too. +# +acctserver localhost + +# file holding shared secrets used for the communication +# between the RADIUS client and server +servers /usr/local/etc/radiusclient/servers + +# dictionary of allowed attributes and values +# just like in the normal RADIUS distributions +dictionary /usr/local/etc/radiusclient/dictionary + +# program to call for a RADIUS authenticated login +login_radius /usr/local/sbin/login.radius + +# file which holds sequence number for communication with the +# RADIUS server +seqfile /var/run/radius.seq + +# file which specifies mapping between ttyname and NAS-Port attribute +mapfile /usr/local/etc/radiusclient/port-id-map + +# default authentication realm to append to all usernames if no +# realm was explicitly specified by the user +# the radiusd directly form Livingston doesnt use any realms, so leave +# it blank then +default_realm + +# time to wait for a reply from the RADIUS server +radius_timeout 10 + +# resend request this many times before trying the next server +radius_retries 3 + +# The length of time in seconds that we skip a nonresponsive RADIUS +# server for transaction requests. Server(s) being in the "dead" state +# are tried only after all other non-dead servers have been tried and +# failed or timeouted. The deadtime interval starts when the server +# does not respond to an authentication/accounting request transmissions. +# When the interval expires, the "dead" server would be re-tried again, +# and if it's still down then it will be considered "dead" for another +# such interval and so on. This option is no-op if there is only one +# server in the list. Set to 0 in order to disable the feature. +radius_deadtime 0 + +# local address from which radius packets have to be sent +bindaddr * + +# LOCAL settings + +# program to execute for local login +# it must support the -f flag for preauthenticated login +login_local /bin/login diff --git a/src/plugins/vbng/etc/radiusclient.conf.in b/src/plugins/vbng/etc/radiusclient.conf.in new file mode 100644 index 00000000..fdf62e6d --- /dev/null +++ b/src/plugins/vbng/etc/radiusclient.conf.in @@ -0,0 +1,92 @@ +# General settings + +# specify which authentication comes first respectively which +# authentication is used. possible values are: "radius" and "local". +# if you specify "radius,local" then the RADIUS server is asked +# first then the local one. if only one keyword is specified only +# this server is asked. +auth_order radius,local + +# maximum login tries a user has +login_tries 4 + +# timeout for all login tries +# if this time is exceeded the user is kicked out +login_timeout 60 + +# name of the nologin file which when it exists disables logins. +# it may be extended by the ttyname which will result in +# a terminal specific lock (e.g. /etc/nologin.ttyS2 will disable +# logins on /dev/ttyS2) +nologin /etc/nologin + +# name of the issue file. it's only display when no username is passed +# on the radlogin command line +issue @pkgsysconfdir@/issue + +# RADIUS settings + +# RADIUS server to use for authentication requests. this config +# item can appear more then one time. if multiple servers are +# defined they are tried in a round robin fashion if one +# server is not answering. +# optionally you can specify a the port number on which is remote +# RADIUS listens separated by a colon from the hostname. if +# no port is specified /etc/services is consulted of the radius +# service. if this fails also a compiled in default is used. +authserver localhost + +# RADIUS server to use for accouting requests. All that I +# said for authserver applies, too. +# +acctserver localhost + +# file holding shared secrets used for the communication +# between the RADIUS client and server +servers @pkgsysconfdir@/servers + +# dictionary of allowed attributes and values +# just like in the normal RADIUS distributions +dictionary @pkgsysconfdir@/dictionary + +# program to call for a RADIUS authenticated login +login_radius @sbindir@/login.radius + +# file which holds sequence number for communication with the +# RADIUS server +seqfile /var/run/radius.seq + +# file which specifies mapping between ttyname and NAS-Port attribute +mapfile @pkgsysconfdir@/port-id-map + +# default authentication realm to append to all usernames if no +# realm was explicitly specified by the user +# the radiusd directly form Livingston doesnt use any realms, so leave +# it blank then +default_realm + +# time to wait for a reply from the RADIUS server +radius_timeout 10 + +# resend request this many times before trying the next server +radius_retries 3 + +# The length of time in seconds that we skip a nonresponsive RADIUS +# server for transaction requests. Server(s) being in the "dead" state +# are tried only after all other non-dead servers have been tried and +# failed or timeouted. The deadtime interval starts when the server +# does not respond to an authentication/accounting request transmissions. +# When the interval expires, the "dead" server would be re-tried again, +# and if it's still down then it will be considered "dead" for another +# such interval and so on. This option is no-op if there is only one +# server in the list. Set to 0 in order to disable the feature. +radius_deadtime 0 + +# local address from which radius packets have to be sent +bindaddr * + +# LOCAL settings + +# program to execute for local login +# it must support the -f flag for preauthenticated login +login_local /bin/login diff --git a/src/plugins/vbng/etc/servers b/src/plugins/vbng/etc/servers new file mode 100644 index 00000000..50eddd39 --- /dev/null +++ b/src/plugins/vbng/etc/servers @@ -0,0 +1,10 @@ +## Server Name or Client/Server pair Key +## ---------------- --------------- +# +#portmaster.elemental.net hardlyasecret +#portmaster2.elemental.net donttellanyone +# +## uncomment the following line for simple testing of radlogin +## with freeradius-server +# +#localhost/localhost testing123 diff --git a/src/plugins/vbng/include/freeradius-client.h b/src/plugins/vbng/include/freeradius-client.h new file mode 100644 index 00000000..96c75460 --- /dev/null +++ b/src/plugins/vbng/include/freeradius-client.h @@ -0,0 +1,528 @@ +/* + * $Id: freeradius-client.h,v 1.18 2010/06/15 09:22:51 aland Exp $ + * + * Copyright (C) 1995,1996,1997,1998 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#ifndef FREERADIUS_CLIENT_H +#define FREERADIUS_CLIENT_H + +#ifdef CP_DEBUG +#define DEBUG(args, ...) rc_log(## args) +#else +#define DEBUG(args, ...) ; +#endif + +#include +/* + * Include for C99 uintX_t defines is stdint.h on most systems. Solaris uses + * inttypes.h instead. Comment out the stdint include if you get an error, + * and uncomment the inttypes.h include. + */ +#include +/* #include */ +#include +#include + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +#define AUTH_VECTOR_LEN 16 +#define AUTH_PASS_LEN (3 * 16) /* multiple of 16 */ +#define AUTH_ID_LEN 64 +#define AUTH_STRING_LEN 253 /* maximum of 253 */ + +#define BUFFER_LEN 8192 + +#define NAME_LENGTH 32 +#define GETSTR_LENGTH 128 /* must be bigger than AUTH_PASS_LEN */ + +#define MAX_SECRET_LENGTH (3 * 16) /* MUST be multiple of 16 */ + +#define VENDOR(x) (((x) >> 16) & 0xffff) +#define ATTRID(x) ((x) & 0xffff) + +#define PW_MAX_MSG_SIZE 4096 + +/* codes for radius_buildreq, radius_getport, etc. */ +#define AUTH 0 +#define ACCT 1 + +/* defines for config.c */ + +#define SERVER_MAX 8 + +#define AUTH_LOCAL_FST (1<<0) +#define AUTH_RADIUS_FST (1<<1) +#define AUTH_LOCAL_SND (1<<2) +#define AUTH_RADIUS_SND (1<<3) + +typedef struct server { + int max; + char *name[SERVER_MAX]; + uint16_t port[SERVER_MAX]; + char *secret[SERVER_MAX]; + double deadtime_ends[SERVER_MAX]; +} SERVER; + +typedef struct pw_auth_hdr +{ + uint8_t code; + uint8_t id; + uint16_t length; + uint8_t vector[AUTH_VECTOR_LEN]; + uint8_t data[2]; +} AUTH_HDR; + +struct rc_conf +{ + struct _option *config_options; + uint32_t this_host_ipaddr; + uint32_t *this_host_bind_ipaddr; + struct map2id_s *map2id_list; + struct dict_attr *dictionary_attributes; + struct dict_value *dictionary_values; + struct dict_vendor *dictionary_vendors; + char buf[GETSTR_LENGTH]; + char buf1[14]; + char ifname[512]; +}; + +typedef struct rc_conf rc_handle; + +#define AUTH_HDR_LEN 20 +#define CHAP_VALUE_LENGTH 16 + +#define PW_AUTH_UDP_PORT 1645 +#define PW_ACCT_UDP_PORT 1646 + +#define PW_TYPE_STRING 0 +#define PW_TYPE_INTEGER 1 +#define PW_TYPE_IPADDR 2 +#define PW_TYPE_DATE 3 +#define PW_TYPE_IPV6ADDR 4 +#define PW_TYPE_IPV6PREFIX 5 + +/* standard RADIUS codes */ + +#define PW_ACCESS_REQUEST 1 +#define PW_ACCESS_ACCEPT 2 +#define PW_ACCESS_REJECT 3 +#define PW_ACCOUNTING_REQUEST 4 +#define PW_ACCOUNTING_RESPONSE 5 +#define PW_ACCOUNTING_STATUS 6 +#define PW_PASSWORD_REQUEST 7 +#define PW_PASSWORD_ACK 8 +#define PW_PASSWORD_REJECT 9 +#define PW_ACCOUNTING_MESSAGE 10 +#define PW_ACCESS_CHALLENGE 11 +#define PW_STATUS_SERVER 12 +#define PW_STATUS_CLIENT 13 + + +/* standard RADIUS attribute-value pairs */ + +#define PW_USER_NAME 1 /* string */ +#define PW_USER_PASSWORD 2 /* string */ +#define PW_CHAP_PASSWORD 3 /* string */ +#define PW_NAS_IP_ADDRESS 4 /* ipaddr */ +#define PW_NAS_PORT 5 /* integer */ +#define PW_SERVICE_TYPE 6 /* integer */ +#define PW_FRAMED_PROTOCOL 7 /* integer */ +#define PW_FRAMED_IP_ADDRESS 8 /* ipaddr */ +#define PW_FRAMED_IP_NETMASK 9 /* ipaddr */ +#define PW_FRAMED_ROUTING 10 /* integer */ +#define PW_FILTER_ID 11 /* string */ +#define PW_FRAMED_MTU 12 /* integer */ +#define PW_FRAMED_COMPRESSION 13 /* integer */ +#define PW_LOGIN_IP_HOST 14 /* ipaddr */ +#define PW_LOGIN_SERVICE 15 /* integer */ +#define PW_LOGIN_PORT 16 /* integer */ +#define PW_OLD_PASSWORD 17 /* string */ /* deprecated */ +#define PW_REPLY_MESSAGE 18 /* string */ +#define PW_LOGIN_CALLBACK_NUMBER 19 /* string */ +#define PW_FRAMED_CALLBACK_ID 20 /* string */ +#define PW_EXPIRATION 21 /* date */ /* deprecated */ +#define PW_FRAMED_ROUTE 22 /* string */ +#define PW_FRAMED_IPX_NETWORK 23 /* integer */ +#define PW_STATE 24 /* string */ +#define PW_CLASS 25 /* string */ +#define PW_VENDOR_SPECIFIC 26 /* string */ +#define PW_SESSION_TIMEOUT 27 /* integer */ +#define PW_IDLE_TIMEOUT 28 /* integer */ +#define PW_TERMINATION_ACTION 29 /* integer */ +#define PW_CALLED_STATION_ID 30 /* string */ +#define PW_CALLING_STATION_ID 31 /* string */ +#define PW_NAS_IDENTIFIER 32 /* string */ +#define PW_PROXY_STATE 33 /* string */ +#define PW_LOGIN_LAT_SERVICE 34 /* string */ +#define PW_LOGIN_LAT_NODE 35 /* string */ +#define PW_LOGIN_LAT_GROUP 36 /* string */ +#define PW_FRAMED_APPLETALK_LINK 37 /* integer */ +#define PW_FRAMED_APPLETALK_NETWORK 38 /* integer */ +#define PW_FRAMED_APPLETALK_ZONE 39 /* string */ +#define PW_EVENT_TIMESTAMP 55 /* integer */ +#define PW_CHAP_CHALLENGE 60 /* string */ +#define PW_NAS_PORT_TYPE 61 /* integer */ +#define PW_PORT_LIMIT 62 /* integer */ +#define PW_LOGIN_LAT_PORT 63 /* string */ +#define PW_CONNECT_INFO 77 /* string */ +#define PW_MESSAGE_AUTHENTICATOR 80 /* string */ + +/* RFC3162 IPv6 attributes */ + +#define PW_NAS_IPV6_ADDRESS 95 /* string */ +#define PW_FRAMED_INTERFACE_ID 96 /* string */ +#define PW_FRAMED_IPV6_PREFIX 97 /* string */ +#define PW_LOGIN_IPV6_HOST 98 /* string */ +#define PW_FRAMED_IPV6_ROUTE 99 /* string */ +#define PW_FRAMED_IPV6_POOL 100 /* string */ + +/* RFC6911 IPv6 attributes */ +#define PW_FRAMED_IPV6_ADDRESS 168 /* ipaddr6 */ +#define PW_DNS_SERVER_IPV6_ADDRESS 169 /* ipaddr6 */ +#define PW_ROUTE_IPV6_INFORMATION 170 /* ipv6prefix */ + +/* Accounting */ + +#define PW_ACCT_STATUS_TYPE 40 /* integer */ +#define PW_ACCT_DELAY_TIME 41 /* integer */ +#define PW_ACCT_INPUT_OCTETS 42 /* integer */ +#define PW_ACCT_OUTPUT_OCTETS 43 /* integer */ +#define PW_ACCT_SESSION_ID 44 /* string */ +#define PW_ACCT_AUTHENTIC 45 /* integer */ +#define PW_ACCT_SESSION_TIME 46 /* integer */ +#define PW_ACCT_INPUT_PACKETS 47 /* integer */ +#define PW_ACCT_OUTPUT_PACKETS 48 /* integer */ +#define PW_ACCT_TERMINATE_CAUSE 49 /* integer */ +#define PW_ACCT_MULTI_SESSION_ID 50 /* string */ +#define PW_ACCT_LINK_COUNT 51 /* integer */ +#define PW_ACCT_INPUT_GIGAWORDS 52 /* integer */ +#define PW_ACCT_OUTPUT_GIGAWORDS 53 /* integer */ + +/* Experimental SIP-specific attributes (draft-sterman-aaa-sip-00.txt etc) */ + +#define PW_DIGEST_RESPONSE 206 /* string */ +#define PW_DIGEST_ATTRIBUTES 207 /* string */ +#define PW_DIGEST_REALM 1063 /* string */ +#define PW_DIGEST_NONCE 1064 /* string */ +#define PW_DIGEST_METHOD 1065 /* string */ +#define PW_DIGEST_URI 1066 /* string */ +#define PW_DIGEST_QOP 1067 /* string */ +#define PW_DIGEST_ALGORITHM 1068 /* string */ +#define PW_DIGEST_BODY_DIGEST 1069 /* string */ +#define PW_DIGEST_CNONCE 1070 /* string */ +#define PW_DIGEST_NONCE_COUNT 1071 /* string */ +#define PW_DIGEST_USER_NAME 1072 /* string */ + +/* Merit Experimental Extensions */ + +#define PW_USER_ID 222 /* string */ +#define PW_USER_REALM 223 /* string */ + +/* Integer Translations */ + +/* SERVICE TYPES */ + +#define PW_LOGIN 1 +#define PW_FRAMED 2 +#define PW_CALLBACK_LOGIN 3 +#define PW_CALLBACK_FRAMED 4 +#define PW_OUTBOUND 5 +#define PW_ADMINISTRATIVE 6 +#define PW_NAS_PROMPT 7 +#define PW_AUTHENTICATE_ONLY 8 +#define PW_CALLBACK_NAS_PROMPT 9 + +/* FRAMED PROTOCOLS */ + +#define PW_PPP 1 +#define PW_SLIP 2 +#define PW_ARA 3 +#define PW_GANDALF 4 +#define PW_XYLOGICS 5 + +/* FRAMED ROUTING VALUES */ + +#define PW_NONE 0 +#define PW_BROADCAST 1 +#define PW_LISTEN 2 +#define PW_BROADCAST_LISTEN 3 + +/* FRAMED COMPRESSION TYPES */ + +#define PW_VAN_JACOBSON_TCP_IP 1 +#define PW_IPX_HEADER_COMPRESSION 2 + +/* LOGIN SERVICES */ + +#define PW_TELNET 0 +#define PW_RLOGIN 1 +#define PW_TCP_CLEAR 2 +#define PW_PORTMASTER 3 +#define PW_LAT 4 +#define PW_X25_PAD 5 +#define PW_X25_T3POS 6 + +/* TERMINATION ACTIONS */ + +#define PW_DEFAULT 0 +#define PW_RADIUS_REQUEST 1 + +/* PROHIBIT PROTOCOL */ + +#define PW_DUMB 0 /* 1 and 2 are defined in FRAMED PROTOCOLS */ +#define PW_AUTH_ONLY 3 +#define PW_ALL 255 + +/* ACCOUNTING STATUS TYPES */ + +#define PW_STATUS_START 1 +#define PW_STATUS_STOP 2 +#define PW_STATUS_ALIVE 3 +#define PW_STATUS_MODEM_START 4 +#define PW_STATUS_MODEM_STOP 5 +#define PW_STATUS_CANCEL 6 +#define PW_ACCOUNTING_ON 7 +#define PW_ACCOUNTING_OFF 8 + +/* ACCOUNTING TERMINATION CAUSES */ + +#define PW_USER_REQUEST 1 +#define PW_LOST_CARRIER 2 +#define PW_LOST_SERVICE 3 +#define PW_ACCT_IDLE_TIMEOUT 4 +#define PW_ACCT_SESSION_TIMEOUT 5 +#define PW_ADMIN_RESET 6 +#define PW_ADMIN_REBOOT 7 +#define PW_PORT_ERROR 8 +#define PW_NAS_ERROR 9 +#define PW_NAS_REQUEST 10 +#define PW_NAS_REBOOT 11 +#define PW_PORT_UNNEEDED 12 +#define PW_PORT_PREEMPTED 13 +#define PW_PORT_SUSPENDED 14 +#define PW_SERVICE_UNAVAILABLE 15 +#define PW_CALLBACK 16 +#define PW_USER_ERROR 17 +#define PW_HOST_REQUEST 18 + +/* NAS PORT TYPES */ + +#define PW_ASYNC 0 +#define PW_SYNC 1 +#define PW_ISDN_SYNC 2 +#define PW_ISDN_SYNC_V120 3 +#define PW_ISDN_SYNC_V110 4 +#define PW_VIRTUAL 5 + +/* AUTHENTIC TYPES */ +#define PW_RADIUS 1 +#define PW_LOCAL 2 +#define PW_REMOTE 3 + +/* Server data structures */ + +typedef struct dict_attr +{ + char name[NAME_LENGTH + 1]; /* attribute name */ + int value; /* attribute index */ + int type; /* string, int, etc. */ + struct dict_attr *next; +} DICT_ATTR; + +typedef struct dict_value +{ + char attrname[NAME_LENGTH +1]; + char name[NAME_LENGTH + 1]; + int value; + struct dict_value *next; +} DICT_VALUE; + +typedef struct dict_vendor +{ + char vendorname[NAME_LENGTH +1]; + int vendorpec; + struct dict_vendor *next; +} DICT_VENDOR; + +typedef struct value_pair +{ + char name[NAME_LENGTH + 1]; + int attribute; + int type; + uint32_t lvalue; + char strvalue[AUTH_STRING_LEN + 1]; + struct value_pair *next; +} VALUE_PAIR; + +/* don't change this, as it has to be the same as in the Merit radiusd code */ +#define MGMT_POLL_SECRET "Hardlyasecret" + +/* Define return codes from "SendServer" utility */ + +#define BADRESP_RC -2 +#define ERROR_RC -1 +#define OK_RC 0 +#define TIMEOUT_RC 1 +#define REJECT_RC 2 + +typedef struct send_data /* Used to pass information to sendserver() function */ +{ + uint8_t code; /* RADIUS packet code */ + uint8_t seq_nbr; /* Packet sequence number */ + char *server; /* Name/addrress of RADIUS server */ + int svc_port; /* RADIUS protocol destination port */ + char *secret; /* Shared secret of RADIUS server */ + int timeout; /* Session timeout in seconds */ + int retries; + VALUE_PAIR *send_pairs; /* More a/v pairs to send */ + VALUE_PAIR *receive_pairs; /* Where to place received a/v pairs */ +} SEND_DATA; + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +typedef struct env +{ + int maxsize, size; + char **env; +} ENV; + +#define ENV_SIZE 128 + +__BEGIN_DECLS + +/* Function prototypes */ + +/* avpair.c */ + +VALUE_PAIR *rc_avpair_add(rc_handle const *, VALUE_PAIR **, int, void const *, int, int); +int rc_avpair_assign(VALUE_PAIR *, void const *, int); +VALUE_PAIR *rc_avpair_new(rc_handle const *, int, void const *, int, int); +VALUE_PAIR *rc_avpair_gen(rc_handle const *, VALUE_PAIR *, unsigned char const *, int, int); +VALUE_PAIR *rc_avpair_get(VALUE_PAIR *, int, int); +void rc_avpair_insert(VALUE_PAIR **, VALUE_PAIR *, VALUE_PAIR *); +void rc_avpair_free(VALUE_PAIR *); +int rc_avpair_parse(rc_handle const *, char const *, VALUE_PAIR **); +int rc_avpair_tostr(rc_handle const *, VALUE_PAIR *, char *, int, char *, int); +char *rc_avpair_log(rc_handle const *, VALUE_PAIR *, char *buf, size_t buf_len); +VALUE_PAIR *rc_avpair_readin(rc_handle const *, FILE *); + +/* buildreq.c */ + +void rc_buildreq(rc_handle const *, SEND_DATA *, int, char *, unsigned short, char *, int, int); +unsigned char rc_get_id(); +int rc_auth(rc_handle *, uint32_t, VALUE_PAIR *, VALUE_PAIR **, char *); +int rc_auth_proxy(rc_handle *, VALUE_PAIR *, VALUE_PAIR **, char *); +int rc_acct(rc_handle *, uint32_t, VALUE_PAIR *); +int rc_acct_proxy(rc_handle *, VALUE_PAIR *); +int rc_check(rc_handle *, char *, char *, unsigned short, char *); + +int rc_aaa(rc_handle *rh, uint32_t client_port, VALUE_PAIR *send, VALUE_PAIR **received, + char *msg, int add_nas_port, int request_type); + +/* clientid.c */ + +int rc_read_mapfile(rc_handle *, char const *); +uint32_t rc_map2id(rc_handle const *, char const *); +void rc_map2id_free(rc_handle *); + +/* config.c */ + +rc_handle *rc_read_config(char const *); +char *rc_conf_str(rc_handle const *, char const *); +int rc_conf_int(rc_handle const *, char const *); +SERVER *rc_conf_srv(rc_handle const *, char const *); +int rc_find_server(rc_handle const *, char const *, uint32_t *, char *); +void rc_config_free(rc_handle *); +int rc_add_config(rc_handle *, char const *, char const *, char const *, int); +rc_handle *rc_config_init(rc_handle *); +int test_config(rc_handle const *, char const *); + +/* dict.c */ + +int rc_read_dictionary(rc_handle *, char const *); +DICT_ATTR *rc_dict_getattr(rc_handle const *, int); +DICT_ATTR *rc_dict_findattr(rc_handle const *, char const *); +DICT_VALUE *rc_dict_findval(rc_handle const *, char const *); +DICT_VENDOR *rc_dict_findvend(rc_handle const *, char const *); +DICT_VENDOR *rc_dict_getvend(rc_handle const *, int); +DICT_VALUE * rc_dict_getval(rc_handle const *, uint32_t, char const *); +void rc_dict_free(rc_handle *); + +/* ip_util.c */ + +struct hostent *rc_gethostbyname(char const *); +struct hostent *rc_gethostbyaddr(char const *, size_t, int); +uint32_t rc_get_ipaddr(char const *); +int rc_good_ipaddr(char const *); +char const *rc_ip_hostname(uint32_t); +unsigned short rc_getport(int); +int rc_own_hostname(char *, int); +uint32_t rc_own_ipaddress(rc_handle *); +uint32_t rc_own_bind_ipaddress(rc_handle *); +struct sockaddr; +int rc_get_srcaddr(struct sockaddr *, struct sockaddr *); + + +/* log.c */ + +void rc_openlog(char const *); +void rc_log(int, char const *, ...); + +/* sendserver.c */ + +int rc_send_server(rc_handle *, SEND_DATA *, char *); + +/* util.c */ + +void rc_str2tm(char const *, struct tm *); +char *rc_getifname(rc_handle *, char const *); +char *rc_getstr(rc_handle *, char const *, int); +void rc_mdelay(int); +char *rc_mksid(rc_handle *); +rc_handle *rc_new(void); +void rc_destroy(rc_handle *); +char *rc_fgetln(FILE *, size_t *); +double rc_getctime(void); + +/* env.c */ + +struct env *rc_new_env(int); +void rc_free_env(struct env *); +int rc_add_env(struct env *, char const *, char const *); +int rc_import_env(struct env *, char const **); + +/* md5.c */ + +void rc_md5_calc(unsigned char *, unsigned char const *, unsigned int); + +__END_DECLS + +#endif /* FREERADIUS_CLIENT_H */ diff --git a/src/plugins/vbng/include/includes.h b/src/plugins/vbng/include/includes.h new file mode 100644 index 00000000..908f0e74 --- /dev/null +++ b/src/plugins/vbng/include/includes.h @@ -0,0 +1,182 @@ +/* + * $Id: includes.h,v 1.6 2007/06/21 18:07:22 cparker Exp $ + * + * Copyright (C) 1997 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#include "config.h" + +/* AIX requires this to be the first thing in the file. */ +#ifndef __GNUC__ +# if HAVE_ALLOCA_H +# include +# else +# ifdef _AIX +# pragma alloca +# else +# ifndef alloca /* predefined by HP cc +Olibcalls */ + char *alloca (); +# endif +# endif +# endif +#endif + +#include + +#include +#include +#include + +#ifdef HAVE_NETDB_H +#include +#endif + +#ifdef HAVE_SYSLOG_H +#include +#endif + +#ifdef STDC_HEADERS +# include +# include +# include +#else +# include +# ifndef HAVE_STRCHR +# define strchr index +# define strrchr rindex +# endif +#endif + +/* I realize that this is ugly and unsafe.. :( */ +#ifndef HAVE_SNPRINTF +# define snprintf(buf, len, format, ...) sprintf(buf, format, __VA_ARGS__) +#endif +#ifndef HAVE_VSNPRINTF +# define vsnprintf(buf, len, format, ap) vsprintf(buf, format, ap) +#endif + +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ + +#ifdef HAVE_FCNTL_H +# include +#endif + +#ifdef HAVE_SYS_FCNTL_H +# include +#endif + +#ifdef HAVE_SYS_FILE_H +# include +#endif + +#ifdef HAVE_SYS_STAT_H +# include +#endif + +#ifdef HAVE_SYS_UTSNAME_H +# include +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include +#endif + +#ifdef HAVE_CRYPT_H +# include +#endif + +#ifdef HAVE_LIMITS_H +# include +#endif + +#ifdef HAVE_TERMIOS_H +# include +#endif + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +#ifndef UCHAR_MAX +# ifdef __STDC__ +# define UCHAR_MAX 255U +# else +# define UCHAR_MAX 255 +# endif +#endif + +#ifdef HAVE_PWD_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#if defined(HAVE_SIGNAL_H) +# include +#endif +#if defined(HAVE_SYS_SIGNAL_H) +# include +#endif + +#ifdef NEED_SIG_PROTOTYPES +int sigemptyset(sigset_t *); +int sigaddset(sigset_t *, int); +int sigprocmask (int, sigset_t *, sigset_t *); +#endif + +#if HAVE_GETOPT_H +# include +#endif + +#if defined(HAVE_SHADOW_H) && defined(HAVE_SHADOW_PASSWORDS) +# include +#endif + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +/* + * prefer srandom/random over srand/rand as there generator has a + * better distribution of the numbers on certain systems. + * on Linux both generators are identical. + */ +#ifndef HAVE_RANDOM +# ifdef HAVE_RAND +# define srandom srand +# define random rand +# endif +#endif + +/* rlib/lock.c */ +int do_lock_exclusive(FILE *); +int do_unlock(FILE *); diff --git a/src/plugins/vbng/include/messages.h b/src/plugins/vbng/include/messages.h new file mode 100644 index 00000000..9a5f0e81 --- /dev/null +++ b/src/plugins/vbng/include/messages.h @@ -0,0 +1,53 @@ +/* + * $Id: messages.h,v 1.2 2004/02/23 20:10:39 sobomax Exp $ + * + * Copyright (C) 1995,1996 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +/* + * Only messages that the user gets under normal use are in here. + * Error messages and such are still in the source code. + */ + +#ifndef MESSAGES_H +#define MESSAGES_H + +/* radlogin.c */ + +#define SC_LOGIN "login: " +#define SC_PASSWORD "Password: " + +#define SC_TIMEOUT "\r\nlogin timed out after %d seconds. Bye.\r\n" +#define SC_EXCEEDED "Maximum login tries exceeded. Go away!\r\n" + +#define SC_RADIUS_OK "RADIUS: Authentication OK\r\n" +#define SC_RADIUS_FAILED "RADIUS: Authentication failure\r\n" + +#define SC_LOCAL_OK "local: Authentication OK\r\n" +#define SC_LOCAL_FAILED "local: Authentication failure\r\n" +#define SC_NOLOGIN "\r\nSystem closed for maintenance. Try again later...\r\n" + +#define SC_SERVER_REPLY "RADIUS: %s" + +#define SC_DEFAULT_ISSUE "(\\I)\r\n\r\n\\S \\R (\\N) (port \\L)\r\n\r\n" + +/* radacct.c */ + +#define SC_ACCT_OK "RADIUS accounting OK\r\n" +#define SC_ACCT_FAILED "RADIUS accounting failed (RC=%i)\r\n" + +/* radstatus.c */ + +#define SC_STATUS_FAILED "RADIUS: Status failure\r\n" + +#endif /* MESSAGES_H */ diff --git a/src/plugins/vbng/include/pathnames.h b/src/plugins/vbng/include/pathnames.h new file mode 100644 index 00000000..0256d473 --- /dev/null +++ b/src/plugins/vbng/include/pathnames.h @@ -0,0 +1,28 @@ +/* + * $Id: pathnames.h,v 1.2 2004/02/23 20:10:39 sobomax Exp $ + * + * Copyright (C) 1995,1996 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#ifndef PATHNAMES_H +#define PATHNAMES_H + +#define _PATH_DEV_URANDOM "/dev/urandom" /* Linux only */ +#define _PATH_ETC_ISSUE "/etc/issue" + +/* normally defined in the Makefile */ +#ifndef _PATH_ETC_RADIUSCLIENT_CONF +#define _PATH_ETC_RADIUSCLIENT_CONF "/etc/radiusclient.conf" +#endif + +#endif /* PATHNAMES_H */ diff --git a/src/plugins/vbng/lib/avpair.c b/src/plugins/vbng/lib/avpair.c new file mode 100644 index 00000000..8ce2a8ec --- /dev/null +++ b/src/plugins/vbng/lib/avpair.c @@ -0,0 +1,874 @@ +/* + * $Id: avpair.c,v 1.26 2010/06/15 09:22:52 aland Exp $ + * + * Copyright (C) 1995 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#include +#include +#include + +/* + * Function: rc_avpair_add + * + * Purpose: add an attribute-value pair to the given list. + * + * Returns: pointer to added a/v pair upon success, NULL pointer upon failure. + * + * Remarks: Always appends the new pair to the end of the list. + * + */ + +VALUE_PAIR *rc_avpair_add (rc_handle const *rh, VALUE_PAIR **list, int attrid, void const *pval, int len, int vendorpec) +{ + VALUE_PAIR *vp; + + vp = rc_avpair_new (rh, attrid, pval, len, vendorpec); + + if (vp != NULL) + { + rc_avpair_insert (list, NULL, vp); + } + + return vp; + +} + +/* + * Function: rc_avpair_assign + * + * Purpose: assign the given value to an attribute-value pair. + * + * Returns: 0 on success, + * -1 on failure. + * + */ + +int rc_avpair_assign (VALUE_PAIR *vp, void const *pval, int len) +{ + + switch (vp->type) + { + case PW_TYPE_STRING: + if (len == -1) + len = (uint32_t)strlen((char const *)pval); + if (len > AUTH_STRING_LEN) { + rc_log(LOG_ERR, "rc_avpair_assign: bad attribute length"); + return -1; + } + memcpy(vp->strvalue, (char const *)pval, len); + vp->strvalue[len] = '\0'; + vp->lvalue = len; + break; + + case PW_TYPE_DATE: + case PW_TYPE_INTEGER: + case PW_TYPE_IPADDR: + vp->lvalue = * (uint32_t *) pval; + break; + case PW_TYPE_IPV6ADDR: + if (len != 16) { + rc_log(LOG_ERR, "rc_avpair_assign: bad IPv6 length"); + return -1; + } + memcpy(vp->strvalue, (char const *)pval, len); + vp->lvalue = len; + break; + + case PW_TYPE_IPV6PREFIX: + if (len < 2 || len > 18) { + rc_log(LOG_ERR, "rc_avpair_assign: bad IPv6 prefix length"); + return -1; + } + memcpy(vp->strvalue, (char const *)pval, len); + vp->lvalue = len; + break; + + default: + rc_log(LOG_ERR, "rc_avpair_assign: unknown attribute %d", vp->type); + return -1; + } + return 0; +} + +/* + * Function: rc_avpair_new + * + * Purpose: make a new attribute-value pair with given parameters. + * + * Returns: pointer to generated a/v pair when successful, NULL when failure. + * + */ + +VALUE_PAIR *rc_avpair_new (rc_handle const *rh, int attrid, void const *pval, int len, int vendorpec) +{ + VALUE_PAIR *vp = NULL; + DICT_ATTR *pda; + + attrid = attrid | (vendorpec << 16); + if ((pda = rc_dict_getattr (rh, attrid)) == NULL) + { + rc_log(LOG_ERR,"rc_avpair_new: unknown attribute %d", attrid); + return NULL; + } + if (vendorpec != 0 && rc_dict_getvend(rh, vendorpec) == NULL) + { + rc_log(LOG_ERR,"rc_avpair_new: unknown Vendor-Id %d", vendorpec); + return NULL; + } + if ((vp = malloc (sizeof (VALUE_PAIR))) != NULL) + { + strncpy (vp->name, pda->name, sizeof (vp->name)); + vp->attribute = attrid; + vp->next = NULL; + vp->type = pda->type; + if (rc_avpair_assign (vp, pval, len) == 0) + { + /* XXX: Fix up Digest-Attributes */ + switch (vp->attribute) { + case PW_DIGEST_REALM: + case PW_DIGEST_NONCE: + case PW_DIGEST_METHOD: + case PW_DIGEST_URI: + case PW_DIGEST_QOP: + case PW_DIGEST_ALGORITHM: + case PW_DIGEST_BODY_DIGEST: + case PW_DIGEST_CNONCE: + case PW_DIGEST_NONCE_COUNT: + case PW_DIGEST_USER_NAME: + /* overlapping! */ + if (vp->lvalue > AUTH_STRING_LEN - 2) + vp->lvalue = AUTH_STRING_LEN - 2; + memmove(&vp->strvalue[2], &vp->strvalue[0], vp->lvalue); + vp->strvalue[0] = vp->attribute - PW_DIGEST_REALM + 1; + vp->lvalue += 2; + vp->strvalue[1] = vp->lvalue; + vp->strvalue[vp->lvalue] = '\0'; + vp->attribute = PW_DIGEST_ATTRIBUTES; + default: + break; + } + return vp; + } + free (vp); + vp = NULL; + } + else + { + rc_log(LOG_CRIT,"rc_avpair_new: out of memory"); + } + + return vp; +} + +/* + * + * Function: rc_avpair_gen + * + * Purpose: takes attribute/value pairs from buffer and builds a + * value_pair list using allocated memory. Uses recursion. + * + * Returns: value_pair list or NULL on failure + */ + +VALUE_PAIR * +rc_avpair_gen(rc_handle const *rh, VALUE_PAIR *pair, unsigned char const *ptr, + int length, int vendorpec) +{ + int attribute, attrlen, x_len; + unsigned char const *x_ptr; + uint32_t lvalue; + DICT_ATTR *attr; + VALUE_PAIR *rpair; + char buffer[(AUTH_STRING_LEN * 2) + 1]; + /* For hex string conversion. */ + char hex[3]; + + if (length < 2) { + rc_log(LOG_ERR, "rc_avpair_gen: received attribute with " + "invalid length"); + goto shithappens; + } + attrlen = ptr[1]; + if (length < attrlen || attrlen < 2) { + rc_log(LOG_ERR, "rc_avpair_gen: received attribute with " + "invalid length"); + goto shithappens; + } + + /* Advance to the next attribute and process recursively */ + if (length != attrlen) { + pair = rc_avpair_gen(rh, pair, ptr + attrlen, length - attrlen, + vendorpec); + if (pair == NULL) + return NULL; + } + + /* Actual processing */ + attribute = ptr[0] | (vendorpec << 16); + ptr += 2; + attrlen -= 2; + + /* VSA */ + if (attribute == PW_VENDOR_SPECIFIC) { + if (attrlen < 4) { + rc_log(LOG_ERR, "rc_avpair_gen: received VSA " + "attribute with invalid length"); + goto shithappens; + } + memcpy(&lvalue, ptr, 4); + vendorpec = ntohl(lvalue); + if (rc_dict_getvend(rh, vendorpec) == NULL) { + /* Warn and skip over the unknown VSA */ + rc_log(LOG_WARNING, "rc_avpair_gen: received VSA " + "attribute with unknown Vendor-Id %d", vendorpec); + return pair; + } + /* Process recursively */ + return rc_avpair_gen(rh, pair, ptr + 4, attrlen - 4, + vendorpec); + } + + /* Normal */ + attr = rc_dict_getattr(rh, attribute); + if (attr == NULL) { + buffer[0] = '\0'; /* Initial length. */ + x_ptr = ptr; + for (x_len = attrlen; x_len > 0; x_len--, x_ptr++) { + snprintf(hex, sizeof(hex), "%2.2X", x_ptr[0]); + strcat(buffer, hex); + } + if (vendorpec == 0) { + rc_log(LOG_WARNING, "rc_avpair_gen: received " + "unknown attribute %d of length %d: 0x%s", + attribute, attrlen + 2, buffer); + } else { + rc_log(LOG_WARNING, "rc_avpair_gen: received " + "unknown VSA attribute %d, vendor %d of " + "length %d: 0x%s", attribute & 0xffff, + VENDOR(attribute), attrlen + 2, buffer); + } + goto shithappens; + } + + rpair = malloc(sizeof(*rpair)); + if (rpair == NULL) { + rc_log(LOG_CRIT, "rc_avpair_gen: out of memory"); + goto shithappens; + } + memset(rpair, '\0', sizeof(*rpair)); + + /* Insert this new pair at the beginning of the list */ + rpair->next = pair; + pair = rpair; + strcpy(pair->name, attr->name); + pair->attribute = attr->value; + pair->type = attr->type; + + switch (attr->type) { + case PW_TYPE_STRING: + memcpy(pair->strvalue, (char *)ptr, (size_t)attrlen); + pair->strvalue[attrlen] = '\0'; + pair->lvalue = attrlen; + break; + + case PW_TYPE_INTEGER: + if (attrlen != 4) { + rc_log(LOG_ERR, "rc_avpair_gen: received INT " + "attribute with invalid length"); + goto shithappens; + } + case PW_TYPE_IPADDR: + if (attrlen != 4) { + rc_log(LOG_ERR, "rc_avpair_gen: received IPADDR" + " attribute with invalid length"); + goto shithappens; + } + memcpy((char *)&lvalue, (char *)ptr, 4); + pair->lvalue = ntohl(lvalue); + break; + case PW_TYPE_IPV6ADDR: + if (attrlen != 16) { + rc_log(LOG_ERR, "rc_avpair_gen: received IPV6ADDR" + " attribute with invalid length"); + goto shithappens; + } + memcpy(pair->strvalue, (char *)ptr, 16); + pair->lvalue = attrlen; + break; + case PW_TYPE_IPV6PREFIX: + if (attrlen > 18 || attrlen < 2) { + rc_log(LOG_ERR, "rc_avpair_gen: received IPV6PREFIX" + " attribute with invalid length: %d", attrlen); + goto shithappens; + } + memcpy(pair->strvalue, (char *)ptr, attrlen); + pair->lvalue = attrlen; + break; + case PW_TYPE_DATE: + if (attrlen != 4) { + rc_log(LOG_ERR, "rc_avpair_gen: received DATE " + "attribute with invalid length"); + goto shithappens; + } + + default: + rc_log(LOG_WARNING, "rc_avpair_gen: %s has unknown type", + attr->name); + goto shithappens; + } + return pair; + +shithappens: + while (pair != NULL) { + rpair = pair->next; + free(pair); + pair = rpair; + } + return NULL; +} + +/* + * Function: rc_avpair_get + * + * Purpose: Find the first attribute value-pair (which matches the given + * attribute) from the specified value-pair list. + * + * Returns: found value_pair + * + */ + +VALUE_PAIR *rc_avpair_get (VALUE_PAIR *vp, int attrid, int vendorpec) +{ + for (; vp != NULL && !(ATTRID(vp->attribute) == ATTRID(attrid) && + VENDOR(vp->attribute) == vendorpec); vp = vp->next) + { + continue; + } + return vp; +} + +/* + * Function: rc_avpair_insert + * + * Purpose: Given the address of an existing list "a" and a pointer + * to an entry "p" in that list, add the value pair "b" to + * the "a" list after the "p" entry. If "p" is NULL, add + * the value pair "b" to the end of "a". + * + */ + +void rc_avpair_insert (VALUE_PAIR **a, VALUE_PAIR *p, VALUE_PAIR *b) +{ + VALUE_PAIR *this_node = NULL; + VALUE_PAIR *vp; + + if (b->next != NULL) + { + rc_log(LOG_CRIT, "rc_avpair_insert: value pair (0x%p) next ptr. (0x%p) not NULL", b, b->next); + abort (); + } + + if (*a == NULL) + { + *a = b; + return; + } + + vp = *a; + + if ( p == NULL) /* run to end of "a" list */ + { + while (vp != NULL) + { + this_node = vp; + vp = vp->next; + } + } + else /* look for the "p" entry in the "a" list */ + { + this_node = *a; + while (this_node != NULL) + { + if (this_node == p) + { + break; + } + this_node = this_node->next; + } + } + + b->next = this_node->next; + this_node->next = b; + + return; +} + +/* + * Function: rc_avpair_free + * + * Purpose: frees all value_pairs in the list + * + */ + +void rc_avpair_free (VALUE_PAIR *pair) +{ + VALUE_PAIR *next; + + while (pair != NULL) + { + next = pair->next; + free (pair); + pair = next; + } +} + +/* + * Function: rc_fieldcpy + * + * Purpose: Copy a data field from the buffer. Advance the buffer + * past the data field. Ensure that no more than len - 1 + * bytes are copied and that resulting string is terminated + * with '\0'. + * + */ + +static void +rc_fieldcpy(char *string, char const **uptr, char const *stopat, size_t len) +{ + char const *ptr, *estring; + + ptr = *uptr; + estring = string + len - 1; + if (*ptr == '"') + { + ptr++; + while (*ptr != '"' && *ptr != '\0' && *ptr != '\n') + { + if (string < estring) + *string++ = *ptr; + ptr++; + } + if (*ptr == '"') + { + ptr++; + } + *string = '\0'; + *uptr = ptr; + return; + } + + while (*ptr != '\0' && strchr(stopat, *ptr) == NULL) + { + if (string < estring) + *string++ = *ptr; + ptr++; + } + *string = '\0'; + *uptr = ptr; + return; +} + + +/* + * Function: rc_avpair_parse + * + * Purpose: parses the buffer to extract the attribute-value pairs. + * + * Returns: 0 = successful parse of attribute-value pair, + * -1 = syntax (or other) error detected. + * + */ + +#define PARSE_MODE_NAME 0 +#define PARSE_MODE_EQUAL 1 +#define PARSE_MODE_VALUE 2 +#define PARSE_MODE_INVALID 3 + +int rc_avpair_parse (rc_handle const *rh, char const *buffer, VALUE_PAIR **first_pair) +{ + int mode; + char attrstr[AUTH_ID_LEN]; + char valstr[AUTH_STRING_LEN + 1], *p; + DICT_ATTR *attr = NULL; + DICT_VALUE *dval; + VALUE_PAIR *pair; + VALUE_PAIR *link; + struct tm *tm; + time_t timeval; + + mode = PARSE_MODE_NAME; + while (*buffer != '\n' && *buffer != '\0') + { + if (*buffer == ' ' || *buffer == '\t') + { + buffer++; + continue; + } + + switch (mode) + { + case PARSE_MODE_NAME: /* Attribute Name */ + rc_fieldcpy (attrstr, &buffer, " \t\n=,", sizeof(attrstr)); + if ((attr = + rc_dict_findattr (rh, attrstr)) == NULL) + { + rc_log(LOG_ERR, "rc_avpair_parse: unknown attribute"); + if (*first_pair) { + rc_avpair_free(*first_pair); + *first_pair = NULL; + } + return -1; + } + mode = PARSE_MODE_EQUAL; + break; + + case PARSE_MODE_EQUAL: /* Equal sign */ + if (*buffer == '=') + { + mode = PARSE_MODE_VALUE; + buffer++; + } + else + { + rc_log(LOG_ERR, "rc_avpair_parse: missing or misplaced equal sign"); + if (*first_pair) { + rc_avpair_free(*first_pair); + *first_pair = NULL; + } + return -1; + } + break; + + case PARSE_MODE_VALUE: /* Value */ + rc_fieldcpy (valstr, &buffer, " \t\n,", sizeof(valstr)); + + if ((pair = malloc (sizeof (VALUE_PAIR))) == NULL) + { + rc_log(LOG_CRIT, "rc_avpair_parse: out of memory"); + if (*first_pair) { + rc_avpair_free(*first_pair); + *first_pair = NULL; + } + return -1; + } + strcpy (pair->name, attr->name); + pair->attribute = attr->value; + pair->type = attr->type; + + switch (pair->type) + { + + case PW_TYPE_STRING: + strcpy (pair->strvalue, valstr); + pair->lvalue = (uint32_t)strlen(valstr); + break; + + case PW_TYPE_INTEGER: + if (isdigit (*valstr)) + { + pair->lvalue = atoi (valstr); + } + else + { + if ((dval = rc_dict_findval (rh, valstr)) + == NULL) + { + rc_log(LOG_ERR, "rc_avpair_parse: unknown attribute value: %s", valstr); + if (*first_pair) { + rc_avpair_free(*first_pair); + *first_pair = NULL; + } + free (pair); + return -1; + } + else + { + pair->lvalue = dval->value; + } + } + break; + + case PW_TYPE_IPADDR: + pair->lvalue = rc_get_ipaddr(valstr); + break; + + case PW_TYPE_IPV6ADDR: + if (inet_pton(AF_INET6, valstr, pair->strvalue) == 0) { + rc_log(LOG_ERR, "rc_avpair_parse: invalid IPv6 address %s", valstr); + free(pair); + return -1; + } + pair->lvalue = 16; + break; + + case PW_TYPE_IPV6PREFIX: + p = strchr(valstr, '/'); + if (p == NULL) { + rc_log(LOG_ERR, "rc_avpair_parse: invalid IPv6 prefix %s", valstr); + free(pair); + return -1; + } + *p = 0; + p++; + pair->strvalue[0] = 0; + pair->strvalue[1] = atoi(p); + + if (inet_pton(AF_INET6, valstr, pair->strvalue+2) == 0) { + rc_log(LOG_ERR, "rc_avpair_parse: invalid IPv6 prefix %s", valstr); + free(pair); + return -1; + } + pair->lvalue = 2+16; + break; + + case PW_TYPE_DATE: + timeval = time (0); + tm = localtime (&timeval); + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + rc_str2tm (valstr, tm); +#ifdef TIMELOCAL + pair->lvalue = (uint32_t) timelocal (tm); +#else /* TIMELOCAL */ + pair->lvalue = (uint32_t) mktime (tm); +#endif /* TIMELOCAL */ + break; + + default: + rc_log(LOG_ERR, "rc_avpair_parse: unknown attribute type %d", pair->type); + if (*first_pair) { + rc_avpair_free(*first_pair); + *first_pair = NULL; + } + free (pair); + return -1; + } + + /* XXX: Fix up Digest-Attributes */ + switch (pair->attribute) { + case PW_DIGEST_REALM: + case PW_DIGEST_NONCE: + case PW_DIGEST_METHOD: + case PW_DIGEST_URI: + case PW_DIGEST_QOP: + case PW_DIGEST_ALGORITHM: + case PW_DIGEST_BODY_DIGEST: + case PW_DIGEST_CNONCE: + case PW_DIGEST_NONCE_COUNT: + case PW_DIGEST_USER_NAME: + /* overlapping! */ + if (pair->lvalue > AUTH_STRING_LEN - 2) + pair->lvalue = AUTH_STRING_LEN - 2; + memmove(&pair->strvalue[2], &pair->strvalue[0], pair->lvalue); + pair->strvalue[0] = pair->attribute - PW_DIGEST_REALM + 1; + pair->lvalue += 2; + pair->strvalue[1] = pair->lvalue; + pair->strvalue[pair->lvalue] = '\0'; + pair->attribute = PW_DIGEST_ATTRIBUTES; + } + + pair->next = NULL; + + if (*first_pair == NULL) + { + *first_pair = pair; + } + else + { + link = *first_pair; + while (link->next != NULL) + { + link = link->next; + } + link->next = pair; + } + + mode = PARSE_MODE_NAME; + break; + + default: + mode = PARSE_MODE_NAME; + break; + } + } + return 0; +} + +/* + * Function: rc_avpair_tostr + * + * Purpose: Translate an av_pair into two strings + * + * Returns: 0 on success, -1 on failure + * + */ + +int rc_avpair_tostr (rc_handle const *rh, VALUE_PAIR *pair, char *name, int ln, char *value, int lv) +{ + DICT_VALUE *dval; + char buffer[32]; + struct in_addr inad; + unsigned char *ptr; + + *name = *value = '\0'; + + if (!pair || pair->name[0] == '\0') { + rc_log(LOG_ERR, "rc_avpair_tostr: pair is NULL or empty"); + return -1; + } + + strncpy(name, pair->name, (size_t) ln); + + switch (pair->type) + { + case PW_TYPE_STRING: + lv--; + ptr = (unsigned char *) pair->strvalue; + if (pair->attribute == PW_DIGEST_ATTRIBUTES) { + pair->strvalue[*(ptr + 1)] = '\0'; + ptr += 2; + } + while (*ptr != '\0') + { + if (!(isprint (*ptr))) + { + snprintf (buffer, sizeof(buffer), "\\%03o", *ptr); + strncat(value, buffer, (size_t) lv); + lv -= 4; + if (lv < 0) break; + } + else + { + strncat(value, (char *)ptr, 1); + lv--; + if (lv <= 0) break; + } + ptr++; + } + break; + + case PW_TYPE_INTEGER: + dval = rc_dict_getval (rh, pair->lvalue, pair->name); + if (dval != NULL) + { + strncpy(value, dval->name, (size_t) lv-1); + } + else + { + snprintf(buffer, sizeof(buffer), "%ld", (long int)pair->lvalue); + strncpy(value, buffer, (size_t) lv); + } + break; + + case PW_TYPE_IPADDR: + inad.s_addr = htonl(pair->lvalue); + strncpy (value, inet_ntoa (inad), (size_t) lv-1); + break; + + case PW_TYPE_IPV6ADDR: + if (inet_ntop(AF_INET6, pair->strvalue, value, lv-1) == NULL) + return -1; + break; + + case PW_TYPE_IPV6PREFIX: { + uint8_t ip[16]; + uint8_t txt[48]; + if (pair->lvalue < 2) + return -1; + + memset(ip, 0, sizeof(ip)); + memcpy(ip, pair->strvalue+2, pair->lvalue-2); + + if (inet_ntop(AF_INET6, ip, txt, sizeof(txt)) == NULL) + return -1; + snprintf(value, lv-1, "%s/%u", txt, (unsigned)pair->strvalue[1]); + + break; + } + case PW_TYPE_DATE: + strftime (buffer, sizeof (buffer), "%m/%d/%y %H:%M:%S", + gmtime ((time_t *) & pair->lvalue)); + strncpy(value, buffer, lv-1); + break; + + default: + rc_log(LOG_ERR, "rc_avpair_tostr: unknown attribute type %d", pair->type); + return -1; + break; + } + + return 0; +} + +/* + * Function: rc_avpair_log + * + * Purpose: format sequence of attribute value pairs into printable + * string. The caller should provide a storage buffer and the buffer length. + * Returns pointer to provided storage buffer. + * + */ +char * +rc_avpair_log(rc_handle const *rh, VALUE_PAIR *pair, char *buf, size_t buf_len) +{ + size_t len, nlen; + VALUE_PAIR *vp; + char name[33], value[256]; + + len = 0; + for (vp = pair; vp != NULL; vp = vp->next) { + if (rc_avpair_tostr(rh, vp, name, sizeof(name), value, + sizeof(value)) == -1) + return NULL; + nlen = len + 32 + 3 + strlen(value) + 2 + 2; + if(nlen +#include +#include + +unsigned char rc_get_id(); + +/* + * Function: rc_buildreq + * + * Purpose: builds a skeleton RADIUS request using information from the + * config file. + * + */ + +void rc_buildreq(rc_handle const *rh, SEND_DATA *data, int code, char *server, unsigned short port, + char *secret, int timeout, int retries) +{ + data->server = server; + data->secret = secret; + data->svc_port = port; + data->seq_nbr = rc_get_id(); + data->timeout = timeout; + data->retries = retries; + data->code = code; +} + +/* + * Function: rc_get_id + * + * Purpose: generate random id + * + */ + +unsigned char rc_get_id() +{ + return (unsigned char)(random() & UCHAR_MAX); +} + +/* + * Function: rc_aaa + * + * Purpose: Builds an authentication/accounting request for port id client_port + * with the value_pairs send and submits it to a server + * + * Returns: received value_pairs in received, messages from the server in msg + * and 0 on success, negative on failure as return value + * + */ + +int rc_aaa(rc_handle *rh, uint32_t client_port, VALUE_PAIR *send, VALUE_PAIR **received, + char *msg, int add_nas_port, int request_type) +{ + SEND_DATA data; + VALUE_PAIR *adt_vp = NULL; + int result; + int i, skip_count; + SERVER *aaaserver; + int timeout = rc_conf_int(rh, "radius_timeout"); + int retries = rc_conf_int(rh, "radius_retries"); + int radius_deadtime = rc_conf_int(rh, "radius_deadtime"); + double start_time = 0; + double now = 0; + time_t dtime; + + if (request_type != PW_ACCOUNTING_REQUEST) { + aaaserver = rc_conf_srv(rh, "authserver"); + } else { + aaaserver = rc_conf_srv(rh, "acctserver"); + } + if (aaaserver == NULL) + return ERROR_RC; + + data.send_pairs = send; + data.receive_pairs = NULL; + + if (add_nas_port != 0) { + /* + * Fill in NAS-Port + */ + if (rc_avpair_add(rh, &(data.send_pairs), PW_NAS_PORT, + &client_port, 0, 0) == NULL) + return ERROR_RC; + } + + if (request_type == PW_ACCOUNTING_REQUEST) { + /* + * Fill in Acct-Delay-Time + */ + dtime = 0; + now = rc_getctime(); + adt_vp = rc_avpair_get(data.send_pairs, PW_ACCT_DELAY_TIME, 0); + if (adt_vp == NULL) { + adt_vp = rc_avpair_add(rh, &(data.send_pairs), + PW_ACCT_DELAY_TIME, &dtime, 0, 0); + if (adt_vp == NULL) + return ERROR_RC; + start_time = now; + } else { + start_time = now - adt_vp->lvalue; + } + } + + skip_count = 0; + result = ERROR_RC; + for (i=0; (i < aaaserver->max) && (result != OK_RC) && (result != BADRESP_RC) + ; i++, now = rc_getctime()) + { + if (aaaserver->deadtime_ends[i] != -1 && + aaaserver->deadtime_ends[i] > start_time) { + skip_count++; + continue; + } + if (data.receive_pairs != NULL) { + rc_avpair_free(data.receive_pairs); + data.receive_pairs = NULL; + } + rc_buildreq(rh, &data, request_type, aaaserver->name[i], + aaaserver->port[i], aaaserver->secret[i], timeout, retries); + + if (request_type == PW_ACCOUNTING_REQUEST) { + dtime = now - start_time; + rc_avpair_assign(adt_vp, &dtime, 0); + } + + result = rc_send_server (rh, &data, msg); + if (result == TIMEOUT_RC && radius_deadtime > 0) + aaaserver->deadtime_ends[i] = start_time + (double)radius_deadtime; + } + if (result == OK_RC || result == BADRESP_RC || skip_count == 0) + goto exit; + + result = ERROR_RC; + for (i=0; (i < aaaserver->max) && (result != OK_RC) && (result != BADRESP_RC) + ; i++) + { + if (aaaserver->deadtime_ends[i] == -1 || + aaaserver->deadtime_ends[i] <= start_time) { + continue; + } + if (data.receive_pairs != NULL) { + rc_avpair_free(data.receive_pairs); + data.receive_pairs = NULL; + } + rc_buildreq(rh, &data, request_type, aaaserver->name[i], + aaaserver->port[i], aaaserver->secret[i], timeout, retries); + + if (request_type == PW_ACCOUNTING_REQUEST) { + dtime = rc_getctime() - start_time; + rc_avpair_assign(adt_vp, &dtime, 0); + } + + result = rc_send_server (rh, &data, msg); + if (result != TIMEOUT_RC) + aaaserver->deadtime_ends[i] = -1; + } + +exit: + if (request_type != PW_ACCOUNTING_REQUEST) { + *received = data.receive_pairs; + } else { + rc_avpair_free(data.receive_pairs); + } + + return result; +} + +/* + * Function: rc_auth + * + * Purpose: Builds an authentication request for port id client_port + * with the value_pairs send and submits it to a server + * + * Returns: received value_pairs in received, messages from the server in msg (if non-NULL), + * and 0 on success, negative on failure as return value + * + */ + +int rc_auth(rc_handle *rh, uint32_t client_port, VALUE_PAIR *send, VALUE_PAIR **received, + char *msg) +{ + + return rc_aaa(rh, client_port, send, received, msg, 1, PW_ACCESS_REQUEST); +} + +/* + * Function: rc_auth_proxy + * + * Purpose: Builds an authentication request + * with the value_pairs send and submits it to a server. + * Works for a proxy; does not add IP address, and does + * does not rely on config file. + * + * Returns: received value_pairs in received, messages from the server in msg (if non-NULL) + * and 0 on success, negative on failure as return value + * + */ + +int rc_auth_proxy(rc_handle *rh, VALUE_PAIR *send, VALUE_PAIR **received, char *msg) +{ + + return rc_aaa(rh, 0, send, received, msg, 0, PW_ACCESS_REQUEST); +} + + +/* + * Function: rc_acct + * + * Purpose: Builds an accounting request for port id client_port + * with the value_pairs send + * + * Remarks: NAS-IP-Address, NAS-Port and Acct-Delay-Time get filled + * in by this function, the rest has to be supplied. + */ + +int rc_acct(rc_handle *rh, uint32_t client_port, VALUE_PAIR *send) +{ + + return rc_aaa(rh, client_port, send, NULL, NULL, 1, PW_ACCOUNTING_REQUEST); +} + +/* + * Function: rc_acct_proxy + * + * Purpose: Builds an accounting request with the value_pairs send + * + */ + +int rc_acct_proxy(rc_handle *rh, VALUE_PAIR *send) +{ + + return rc_aaa(rh, 0, send, NULL, NULL, 0, PW_ACCOUNTING_REQUEST); +} + +/* + * Function: rc_check + * + * Purpose: ask the server hostname on the specified port for a + * status message + * + */ + +int rc_check(rc_handle *rh, char *host, char *secret, unsigned short port, char *msg) +{ + SEND_DATA data; + int result; + uint32_t service_type; + int timeout = rc_conf_int(rh, "radius_timeout"); + int retries = rc_conf_int(rh, "radius_retries"); + + data.send_pairs = data.receive_pairs = NULL; + + /* + * Fill in Service-Type + */ + + service_type = PW_ADMINISTRATIVE; + rc_avpair_add(rh, &(data.send_pairs), PW_SERVICE_TYPE, &service_type, 0, 0); + + rc_buildreq(rh, &data, PW_STATUS_SERVER, host, port, secret, timeout, retries); + result = rc_send_server (rh, &data, msg); + + rc_avpair_free(data.receive_pairs); + + return result; +} diff --git a/src/plugins/vbng/lib/clientid.c b/src/plugins/vbng/lib/clientid.c new file mode 100644 index 00000000..6901a04b --- /dev/null +++ b/src/plugins/vbng/lib/clientid.c @@ -0,0 +1,146 @@ +/* + * $Id: clientid.c,v 1.7 2007/07/11 17:29:29 cparker Exp $ + * + * Copyright (C) 1995,1996,1997 Lars Fenneberg + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#include +#include +#include + +struct map2id_s { + char *name; + uint32_t id; + + struct map2id_s *next; +}; + +/* + * Function: rc_read_mapfile + * + * Purpose: Read in the ttyname to port id map file + * + * Arguments: the file name of the map file + * + * Returns: zero on success, negative integer on failure + */ + +int rc_read_mapfile(rc_handle *rh, char const *filename) +{ + char buffer[1024]; + FILE *mapfd; + char *c, *name, *id, *q; + struct map2id_s *p; + int lnr = 0; + + if ((mapfd = fopen(filename,"r")) == NULL) + { + rc_log(LOG_ERR,"rc_read_mapfile: can't read %s: %s", filename, strerror(errno)); + return -1; + } + +#define SKIP(p) while(*p && isspace(*p)) p++; + + while (fgets(buffer, sizeof(buffer), mapfd) != NULL) + { + lnr++; + + q = buffer; + + SKIP(q); + + if ((*q == '\n') || (*q == '#') || (*q == '\0')) + continue; + + if (( c = strchr(q, ' ')) || (c = strchr(q,'\t'))) { + + *c = '\0'; c++; + SKIP(c); + + name = q; + id = c; + + if ((p = (struct map2id_s *)malloc(sizeof(*p))) == NULL) { + rc_log(LOG_CRIT,"rc_read_mapfile: out of memory"); + fclose(mapfd); + return -1; + } + + p->name = strdup(name); + p->id = atoi(id); + p->next = rh->map2id_list; + rh->map2id_list = p; + + } else { + + rc_log(LOG_ERR, "rc_read_mapfile: malformed line in %s, line %d", filename, lnr); + fclose(mapfd); + return -1; + + } + } + +#undef SKIP + + fclose(mapfd); + + return 0; +} + +/* + * Function: rc_map2id + * + * Purpose: Map ttyname to port id + * + * Arguments: full pathname of the tty + * + * Returns: port id, zero if no entry found + */ + +uint32_t rc_map2id(rc_handle const *rh, char const *name) +{ + struct map2id_s *p; + char ttyname[PATH_MAX]; + + *ttyname = '\0'; + if (*name != '/') + strcpy(ttyname, "/dev/"); + + strncat(ttyname, name, sizeof(ttyname)-strlen(ttyname)-1); + + for(p = rh->map2id_list; p; p = p->next) + if (!strcmp(ttyname, p->name)) return p->id; + + rc_log(LOG_WARNING,"rc_map2id: can't find tty %s in map database", ttyname); + + return 0; +} + +/* + * Function: rc_map2id_free + * + * Purpose: Free allocated map2id list + * + * Arguments: Radius Client handle + */ + +void +rc_map2id_free(rc_handle *rh) +{ + struct map2id_s *p, *np; + + if (rh->map2id_list == NULL) + return; + + for(p = rh->map2id_list; p != NULL; p = np) { + np = p->next; + free(p->name); + free(p); + } + rh->map2id_list = NULL; +} diff --git a/src/plugins/vbng/lib/config.c b/src/plugins/vbng/lib/config.c new file mode 100644 index 00000000..1db78608 --- /dev/null +++ b/src/plugins/vbng/lib/config.c @@ -0,0 +1,925 @@ +/* + * $Id: config.c,v 1.23 2010/04/28 14:26:15 aland Exp $ + * + * Copyright (C) 1995,1996,1997 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#include +#include +#include +#include + +/* + * Function: find_option + * + * Purpose: find an option in the option list + * + * Returns: pointer to option on success, NULL otherwise + */ + +static OPTION *find_option(rc_handle const *rh, char const *optname, unsigned int type) +{ + int i; + + /* there're so few options that a binary search seems not necessary */ + for (i = 0; i < NUM_OPTIONS; i++) { + if (!strcmp(rh->config_options[i].name, optname) && + (rh->config_options[i].type & type)) + { + return &rh->config_options[i]; + } + } + + return NULL; +} + +/* + * Function: set_option_... + * + * Purpose: set a specific option doing type conversions + * + * Returns: 0 on success, -1 on failure + */ + +static int set_option_str(char const *filename, int line, OPTION *option, char const *p) +{ + if (p) { + option->val = (void *) strdup(p); + if (option->val == NULL) { + rc_log(LOG_CRIT, "read_config: out of memory"); + return -1; + } + } else { + option->val = NULL; + } + + return 0; +} + +static int set_option_int(char const *filename, int line, OPTION *option, char const *p) +{ + int *iptr; + + if (p == NULL) { + rc_log(LOG_ERR, "%s: line %d: bogus option value", filename, line); + return -1; + } + + if ((iptr = malloc(sizeof(*iptr))) == NULL) { + rc_log(LOG_CRIT, "read_config: out of memory"); + return -1; + } + + *iptr = atoi(p); + option->val = (void *) iptr; + + return 0; +} + +static int set_option_srv(char const *filename, int line, OPTION *option, char const *p) +{ + SERVER *serv; + char *p_pointer; + char *p_dupe; + char *p_save; + char *q; + char *s; + struct servent *svp; + + p_dupe = strdup(p); + + if (p_dupe == NULL) { + rc_log(LOG_ERR, "%s: line %d: Invalid option or memory failure", filename, line); + return -1; + } + + serv = (SERVER *) option->val; + if (serv == NULL) { + DEBUG(LOG_ERR, "option->val / server is NULL, allocating memory"); + serv = malloc(sizeof(*serv)); + if (serv == NULL) { + rc_log(LOG_CRIT, "read_config: out of memory"); + free(p_dupe); + return -1; + } + memset(serv, 0, sizeof(*serv)); + serv->max = 0; + } + + p_pointer = strtok_r(p_dupe, ", \t", &p_save); + + /* Check to see if we have 'servername:port' syntax */ + if ((q = strchr(p_pointer,':')) != NULL) { + *q = '\0'; + q++; + + /* Check to see if we have 'servername:port:secret' syntax */ + if((s = strchr(q,':')) != NULL) { + *s = '\0'; + s++; + serv->secret[serv->max] = strdup(s); + if (serv->secret[serv->max] == NULL) { + rc_log(LOG_CRIT, "read_config: out of memory"); + if (option->val == NULL) { + free(p_dupe); + free(serv); + } + return -1; + } + } + } + if(q && strlen(q) > 0) { + serv->port[serv->max] = atoi(q); + } else { + if (!strcmp(option->name,"authserver")) + if ((svp = getservbyname ("radius", "udp")) == NULL) + serv->port[serv->max] = PW_AUTH_UDP_PORT; + else + serv->port[serv->max] = ntohs ((unsigned int) svp->s_port); + else if (!strcmp(option->name, "acctserver")) + if ((svp = getservbyname ("radacct", "udp")) == NULL) + serv->port[serv->max] = PW_ACCT_UDP_PORT; + else + serv->port[serv->max] = ntohs ((unsigned int) svp->s_port); + else { + rc_log(LOG_ERR, "%s: line %d: no default port for %s", filename, line, option->name); + if (option->val == NULL) { + free(p_dupe); + free(serv); + } + return -1; + } + } + + serv->name[serv->max] = strdup(p_pointer); + if (serv->name[serv->max] == NULL) { + rc_log(LOG_CRIT, "read_config: out of memory"); + if (option->val == NULL) { + free(p_dupe); + free(serv); + } + return -1; + } + free(p_dupe); + + serv->deadtime_ends[serv->max] = -1; + serv->max++; + + if (option->val == NULL) + option->val = (void *)serv; + + return 0; +} + +static int set_option_auo(char const *filename, int line, OPTION *option, char const *p) +{ + int *iptr; + char *p_dupe = NULL; + char *p_pointer = NULL; + char *p_save = NULL; + + p_dupe = strdup(p); + + if (p_dupe == NULL) { + rc_log(LOG_WARNING, "%s: line %d: bogus option value", filename, line); + return -1; + } + + if ((iptr = malloc(sizeof(iptr))) == NULL) { + rc_log(LOG_CRIT, "read_config: out of memory"); + free(p_dupe); + return -1; + } + + *iptr = 0; + /*if(strstr(p_dupe,", \t") != NULL) {*/ + p_pointer = strtok_r(p_dupe, ", \t", &p_save); + /*}*/ + + if (!strncmp(p_pointer, "local", 5)) + *iptr = AUTH_LOCAL_FST; + else if (!strncmp(p_pointer, "radius", 6)) + *iptr = AUTH_RADIUS_FST; + else { + rc_log(LOG_ERR,"%s: auth_order: unknown keyword: %s", filename, p); + free(iptr); + free(p_dupe); + return -1; + } + + p_pointer = strtok_r(NULL, ", \t", &p_save); + + if (p_pointer && (*p_pointer != '\0')) { + if ((*iptr & AUTH_RADIUS_FST) && !strcmp(p_pointer, "local")) + *iptr = (*iptr) | AUTH_LOCAL_SND; + else if ((*iptr & AUTH_LOCAL_FST) && !strcmp(p_pointer, "radius")) + *iptr = (*iptr) | AUTH_RADIUS_SND; + else { + rc_log(LOG_ERR,"%s: auth_order: unknown or unexpected keyword: %s", filename, p); + free(iptr); + free(p_dupe); + return -1; + } + } + + option->val = (void *) iptr; + + free(p_dupe); + return 0; +} + + +/* Function: rc_add_config + * + * Purpose: allow a config option to be added to rc_handle from inside a program + * + * Returns: 0 on success, -1 on failure + */ + +int rc_add_config(rc_handle *rh, char const *option_name, char const *option_val, char const *source, int line) +{ + OPTION *option; + + if ((option = find_option(rh, option_name, OT_ANY)) == NULL) + { + rc_log(LOG_ERR, "ERROR: unrecognized option: %s", option_name); + return -1; + } + + if (option->status != ST_UNDEF) + { + rc_log(LOG_ERR, "ERROR: duplicate option: %s", option_name); + return -1; + } + + switch (option->type) { + case OT_STR: + if (set_option_str(source, line, option, option_val) < 0) { + return -1; + } + break; + case OT_INT: + if (set_option_int(source, line, option, option_val) < 0) { + return -1; + } + break; + case OT_SRV: + if (set_option_srv(source, line, option, option_val) < 0) { + return -1; + } + break; + case OT_AUO: + if (set_option_auo(source, line, option, option_val) < 0) { + return -1; + } + break; + default: + rc_log(LOG_CRIT, "rc_add_config: impossible case branch!"); + abort(); + } + return 0; +} + +/* + * Function: rc_config_init + * + * Purpose: initialize the configuration structure from an external program. For use when not + * running a standalone client that reads from a config file. + * + * Returns: rc_handle on success, NULL on failure + */ + +rc_handle * +rc_config_init(rc_handle *rh) +{ + int i; + SERVER *authservers; + SERVER *acctservers; + OPTION *acct; + OPTION *auth; + + rh->config_options = malloc(sizeof(config_options_default)); + if (rh->config_options == NULL) + { + rc_log(LOG_CRIT, "rc_config_init: out of memory"); + rc_destroy(rh); + return NULL; + } + memcpy(rh->config_options, &config_options_default, sizeof(config_options_default)); + + acct = find_option(rh, "acctserver", OT_ANY); + auth = find_option(rh, "authserver", OT_ANY); + authservers = malloc(sizeof(SERVER)); + acctservers = malloc(sizeof(SERVER)); + + if(authservers == NULL || acctservers == NULL) + { + rc_log(LOG_CRIT, "rc_config_init: error initializing server structs"); + rc_destroy(rh); + if(authservers) free(authservers); + if(acctservers) free(acctservers); + return NULL; + } + + + authservers->max = 0; + acctservers->max = 0; + + for(i=0; i < SERVER_MAX; i++) + { + authservers->name[i] = NULL; + authservers->secret[i] = NULL; + acctservers->name[i] = NULL; + acctservers->secret[i] = NULL; + } + acct->val = acctservers; + auth->val = authservers; + return rh; +} + + +/* + * Function: rc_read_config + * + * Purpose: read the global config file + * + * Returns: new rc_handle on success, NULL when failure + */ + +rc_handle * +rc_read_config(char const *filename) +{ + FILE *configfd; + char buffer[512], *p; + OPTION *option; + int line; + size_t pos; + rc_handle *rh; + + srandom((unsigned int)(time(NULL)+getpid())); + + rh = rc_new(); + if (rh == NULL) + return NULL; + + rh->config_options = malloc(sizeof(config_options_default)); + if (rh->config_options == NULL) { + rc_log(LOG_CRIT, "rc_read_config: out of memory"); + rc_destroy(rh); + return NULL; + } + memcpy(rh->config_options, &config_options_default, sizeof(config_options_default)); + + if ((configfd = fopen(filename,"r")) == NULL) + { + rc_log(LOG_ERR,"rc_read_config: can't open %s: %s", filename, strerror(errno)); + rc_destroy(rh); + return NULL; + } + + line = 0; + while ((fgets(buffer, sizeof(buffer), configfd) != NULL)) + { + line++; + p = buffer; + + if ((*p == '\n') || (*p == '#') || (*p == '\0')) + continue; + + p[strlen(p)-1] = '\0'; + + + if ((pos = strcspn(p, "\t ")) == 0) { + rc_log(LOG_ERR, "%s: line %d: bogus format: %s", filename, line, p); + fclose(configfd); + rc_destroy(rh); + return NULL; + } + + p[pos] = '\0'; + + if ((option = find_option(rh, p, OT_ANY)) == NULL) { + rc_log(LOG_ERR, "%s: line %d: unrecognized keyword: %s", filename, line, p); + fclose(configfd); + rc_destroy(rh); + return NULL; + } + + if (option->status != ST_UNDEF) { + rc_log(LOG_ERR, "%s: line %d: duplicate option line: %s", filename, line, p); + fclose(configfd); + rc_destroy(rh); + return NULL; + } + + p += pos+1; + while (isspace(*p)) + p++; + pos = strlen(p) - 1; + while(pos != 0 && isspace(p[pos])) + pos--; + p[pos + 1] = '\0'; + + switch (option->type) { + case OT_STR: + if (set_option_str(filename, line, option, p) < 0) { + fclose(configfd); + rc_destroy(rh); + return NULL; + } + break; + case OT_INT: + if (set_option_int(filename, line, option, p) < 0) { + fclose(configfd); + rc_destroy(rh); + return NULL; + } + break; + case OT_SRV: + if (set_option_srv(filename, line, option, p) < 0) { + fclose(configfd); + rc_destroy(rh); + return NULL; + } + break; + case OT_AUO: + if (set_option_auo(filename, line, option, p) < 0) { + fclose(configfd); + rc_destroy(rh); + return NULL; + } + break; + default: + rc_log(LOG_CRIT, "rc_read_config: impossible case branch!"); + abort(); + } + } + fclose(configfd); + + if (test_config(rh, filename) == -1) { + rc_destroy(rh); + return NULL; + } + return rh; +} + +/* + * Function: rc_conf_str, rc_conf_int, rc_conf_src + * + * Purpose: get the value of a config option + * + * Returns: config option value + */ + +char *rc_conf_str(rc_handle const *rh, char const *optname) +{ + OPTION *option; + + option = find_option(rh, optname, OT_STR); + + if (option != NULL) { + return (char *)option->val; + } else { + rc_log(LOG_CRIT, "rc_conf_str: unkown config option requested: %s", optname); + abort(); + return NULL; + } +} + +int rc_conf_int(rc_handle const *rh, char const *optname) +{ + OPTION *option; + + option = find_option(rh, optname, OT_INT|OT_AUO); + + if (option != NULL) { + if (option->val) { + return *((int *)option->val); + } else { + rc_log(LOG_ERR, "rc_conf_int: config option %s was not set", optname); + return 0; + } + } else { + rc_log(LOG_CRIT, "rc_conf_int: unkown config option requested: %s", optname); + abort(); + return 0; + } +} + +SERVER *rc_conf_srv(rc_handle const *rh, char const *optname) +{ + OPTION *option; + + option = find_option(rh, optname, OT_SRV); + + if (option != NULL) { + return (SERVER *)option->val; + } else { + rc_log(LOG_CRIT, "rc_conf_srv: unkown config option requested: %s", optname); + abort(); + return NULL; + } +} + +/* + * Function: test_config + * + * Purpose: test the configuration the user supplied + * + * Returns: 0 on success, -1 when failure + */ + +int test_config(rc_handle const *rh, char const *filename) +{ +#if 0 + struct stat st; + char *file; +#endif + + if (!(rc_conf_srv(rh, "authserver")->max)) + { + rc_log(LOG_ERR,"%s: no authserver specified", filename); + return -1; + } + if (!(rc_conf_srv(rh, "acctserver")->max)) + { + rc_log(LOG_ERR,"%s: no acctserver specified", filename); + return -1; + } + if (!rc_conf_str(rh, "servers")) + { + rc_log(LOG_ERR,"%s: no servers file specified", filename); + return -1; + } + if (!rc_conf_str(rh, "dictionary")) + { + rc_log(LOG_ERR,"%s: no dictionary specified", filename); + return -1; + } + + if (rc_conf_int(rh, "radius_timeout") <= 0) + { + rc_log(LOG_ERR,"%s: radius_timeout <= 0 is illegal", filename); + return -1; + } + if (rc_conf_int(rh, "radius_retries") <= 0) + { + rc_log(LOG_ERR,"%s: radius_retries <= 0 is illegal", filename); + return -1; + } + if (rc_conf_int(rh, "radius_deadtime") < 0) + { + rc_log(LOG_ERR,"%s: radius_deadtime is illegal", filename); + return -1; + } +#if 0 + file = rc_conf_str(rh, "login_local"); + if (stat(file, &st) == 0) + { + if (!S_ISREG(st.st_mode)) { + rc_log(LOG_ERR,"%s: not a regular file: %s", filename, file); + return -1; + } + } else { + rc_log(LOG_ERR,"%s: file not found: %s", filename, file); + return -1; + } + file = rc_conf_str(rh, "login_radius"); + if (stat(file, &st) == 0) + { + if (!S_ISREG(st.st_mode)) { + rc_log(LOG_ERR,"%s: not a regular file: %s", filename, file); + return -1; + } + } else { + rc_log(LOG_ERR,"%s: file not found: %s", filename, file); + return -1; + } +#endif + + if (rc_conf_int(rh, "login_tries") <= 0) + { + rc_log(LOG_ERR,"%s: login_tries <= 0 is illegal", filename); + return -1; + } + if (rc_conf_str(rh, "seqfile") == NULL) + { + rc_log(LOG_ERR,"%s: seqfile not specified", filename); + return -1; + } + if (rc_conf_int(rh, "login_timeout") <= 0) + { + rc_log(LOG_ERR,"%s: login_timeout <= 0 is illegal", filename); + return -1; + } + if (rc_conf_str(rh, "mapfile") == NULL) + { + rc_log(LOG_ERR,"%s: mapfile not specified", filename); + return -1; + } + if (rc_conf_str(rh, "nologin") == NULL) + { + rc_log(LOG_ERR,"%s: nologin not specified", filename); + return -1; + } + + return 0; +} + +/* + * Function: rc_find_match + * + * Purpose: see if ip_addr is one of the ip addresses of hostname + * + * Returns: 0 on success, -1 when failure + * + */ + +static int find_match (uint32_t *ip_addr, char const *hostname) +{ + + uint32_t addr; + char **paddr; + struct hostent *hp; + + if (rc_good_ipaddr (hostname) == 0) + { + if (*ip_addr == ntohl(inet_addr (hostname))) + { + return 0; + } + return -1; + } + + if ((hp = rc_gethostbyname(hostname)) == NULL) + { + return -1; + } + + for (paddr = hp->h_addr_list; *paddr; paddr++) + { + addr = ** (uint32_t **) paddr; + if (ntohl(addr) == *ip_addr) + { + return 0; + } + } + return -1; +} + +/* + * Function: rc_ipaddr_local + * + * Purpose: checks if provided address is local address + * + * Returns: 0 if local, 1 if not local, -1 on failure + * + */ + +static int +rc_ipaddr_local(uint32_t ip_addr) +{ + int temp_sock, res, serrno; + struct sockaddr_in sin; + + temp_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (temp_sock == -1) + return -1; + memset(&sin, '\0', sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(ip_addr); + sin.sin_port = htons(0); + res = bind(temp_sock, (struct sockaddr *)&sin, sizeof(sin)); + serrno = errno; + close(temp_sock); + if (res == 0) + return 0; + if (serrno == EADDRNOTAVAIL) + return 1; + return -1; +} + +/* + * Function: rc_is_myname + * + * Purpose: check if provided name refers to ourselves + * + * Returns: 0 if yes, 1 if no and -1 on failure + * + */ + +static int +rc_is_myname(char const *hostname) +{ + uint32_t addr; + char **paddr; + struct hostent *hp; + int res; + + if (rc_good_ipaddr(hostname) == 0) + return rc_ipaddr_local(ntohl(inet_addr(hostname))); + + if ((hp = rc_gethostbyname(hostname)) == NULL) + return -1; + for (paddr = hp->h_addr_list; *paddr; paddr++) { + addr = **(uint32_t **)paddr; + res = rc_ipaddr_local(ntohl(addr)); + if (res == 0 || res == -1) + return res; + } + return 1; +} + +/* + * Function: rc_find_server + * + * Purpose: locate a server in the rh config or if not found, check for a servers file + * + * Returns: 0 on success, -1 on failure + * + */ + +int rc_find_server (rc_handle const *rh, char const *server_name, uint32_t *ip_addr, char *secret) +{ + int i; + size_t len; + int result = 0; + FILE *clientfd; + char *h; + char *s; + char buffer[128]; + char hostnm[AUTH_ID_LEN + 1]; + char *buffer_save; + char *hostnm_save; + SERVER *authservers; + SERVER *acctservers; + + /* Lookup the IP address of the radius server */ + if ((*ip_addr = rc_get_ipaddr (server_name)) == (uint32_t) 0) + return -1; + + /* Check to see if the server secret is defined in the rh config */ + if( (authservers = rc_conf_srv(rh, "authserver")) != NULL ) + { + for( i = 0; i < authservers->max; i++ ) + { + if( (strncmp(server_name, authservers->name[i], strlen(server_name)) == 0) && + (authservers->secret[i] != NULL) ) + { + memset (secret, '\0', MAX_SECRET_LENGTH); + len = strlen (authservers->secret[i]); + if (len > MAX_SECRET_LENGTH) + { + len = MAX_SECRET_LENGTH; + } + strncpy (secret, authservers->secret[i], (size_t) len); + secret[MAX_SECRET_LENGTH] = '\0'; + return 0; + } + } + } + + if( (acctservers = rc_conf_srv(rh, "acctserver")) != NULL ) + { + for( i = 0; i < acctservers->max; i++ ) + { + if( (strncmp(server_name, acctservers->name[i], strlen(server_name)) == 0) && + (acctservers->secret[i] != NULL) ) + { + memset (secret, '\0', MAX_SECRET_LENGTH); + len = strlen (acctservers->secret[i]); + if (len > MAX_SECRET_LENGTH) + { + len = MAX_SECRET_LENGTH; + } + strncpy (secret, acctservers->secret[i], (size_t) len); + secret[MAX_SECRET_LENGTH] = '\0'; + return 0; + } + } + } + + /* We didn't find it in the rh_config or the servername is too long so look for a + * servers file to define the secret(s) + */ + + if ((clientfd = fopen (rc_conf_str(rh, "servers"), "r")) == NULL) + { + rc_log(LOG_ERR, "rc_find_server: couldn't open file: %s: %s", strerror(errno), rc_conf_str(rh, "servers")); + return -1; + } + + while (fgets (buffer, sizeof (buffer), clientfd) != NULL) + { + if (*buffer == '#') + continue; + + if ((h = strtok_r(buffer, " \t\n", &buffer_save)) == NULL) /* first hostname */ + continue; + + memset (hostnm, '\0', AUTH_ID_LEN); + len = strlen (h); + if (len > AUTH_ID_LEN) + { + len = AUTH_ID_LEN; + } + strncpy (hostnm, h, (size_t) len); + hostnm[AUTH_ID_LEN] = '\0'; + + if ((s = strtok_r (NULL, " \t\n", &buffer_save)) == NULL) /* and secret field */ + continue; + + memset (secret, '\0', MAX_SECRET_LENGTH); + len = strlen (s); + if (len > MAX_SECRET_LENGTH) + { + len = MAX_SECRET_LENGTH; + } + strncpy (secret, s, (size_t) len); + secret[MAX_SECRET_LENGTH] = '\0'; + + if (!strchr (hostnm, '/')) /* If single name form */ + { + if (find_match (ip_addr, hostnm) == 0) + { + result++; + break; + } + } + else /* / "paired" form */ + { + strtok_r(hostnm, "/", &hostnm_save); + if (rc_is_myname(hostnm) == 0) + { /* If we're the 1st name, target is 2nd */ + if (find_match (ip_addr, hostnm_save) == 0) + { + result++; + break; + } + } + else /* If we were 2nd name, target is 1st name */ + { + if (find_match (ip_addr, hostnm) == 0) + { + result++; + break; + } + } + } + } + fclose (clientfd); + if (result == 0) + { + memset (buffer, '\0', sizeof (buffer)); + memset (secret, '\0', MAX_SECRET_LENGTH); + rc_log(LOG_ERR, "rc_find_server: couldn't find RADIUS server %s in %s", + server_name, rc_conf_str(rh, "servers")); + return -1; + } + return 0; +} + +/* + * Function: rc_config_free + * + * Purpose: Free allocated config values + * + * Arguments: Radius Client handle + */ + +void +rc_config_free(rc_handle *rh) +{ + int i, j; + SERVER *serv; + + if (rh->config_options == NULL) + return; + + for (i = 0; i < NUM_OPTIONS; i++) { + if (rh->config_options[i].val == NULL) + continue; + if (rh->config_options[i].type == OT_SRV) { + serv = (SERVER *)rh->config_options[i].val; + for (j = 0; j < serv->max; j++){ + free(serv->name[j]); + if(serv->secret[j]) free(serv->secret[j]); + } + free(serv); + } else { + free(rh->config_options[i].val); + } + } + free(rh->config_options); + rh->config_options = NULL; +} diff --git a/src/plugins/vbng/lib/dict.c b/src/plugins/vbng/lib/dict.c new file mode 100644 index 00000000..84dbba0e --- /dev/null +++ b/src/plugins/vbng/lib/dict.c @@ -0,0 +1,519 @@ +/* + * $Id: dict.c,v 1.10 2007/07/11 17:29:29 cparker Exp $ + * + * Copyright (C) 1995,1996,1997 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#include +#include +#include + +/* + * Function: rc_read_dictionary + * + * Purpose: Initialize the dictionary. Read all ATTRIBUTES into + * the dictionary_attributes list. Read all VALUES into + * the dictionary_values list. + * + */ + +int rc_read_dictionary (rc_handle *rh, char const *filename) +{ + FILE *dictfd; + char dummystr[AUTH_ID_LEN]; + char namestr[AUTH_ID_LEN]; + char valstr[AUTH_ID_LEN]; + char attrstr[AUTH_ID_LEN]; + char typestr[AUTH_ID_LEN]; + char optstr[AUTH_ID_LEN]; + char *cp, *ifilename; + int line_no; + DICT_ATTR *attr; + DICT_VALUE *dval; + DICT_VENDOR *dvend; + char buffer[256]; + int value; + int type; + unsigned attr_vendorspec = 0; + + if ((dictfd = fopen (filename, "r")) == NULL) + { + rc_log(LOG_ERR, "rc_read_dictionary: couldn't open dictionary %s: %s", + filename, strerror(errno)); + return -1; + } + + line_no = 0; + while (fgets (buffer, sizeof (buffer), dictfd) != NULL) + { + line_no++; + + /* Skip empty space */ + if (*buffer == '#' || *buffer == '\0' || *buffer == '\n' || \ + *buffer == '\r') + { + continue; + } + + /* Strip out comments */ + cp = strchr(buffer, '#'); + if (cp != NULL) + { + *cp = '\0'; + } + + if (strncmp (buffer, "ATTRIBUTE", 9) == 0) + { + optstr[0] = '\0'; + /* Read the ATTRIBUTE line */ + if (sscanf (buffer, "%63s%63s%63s%63s%63s", dummystr, namestr, + valstr, typestr, optstr) < 4) + { + rc_log(LOG_ERR, "rc_read_dictionary: invalid attribute on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + + /* + * Validate all entries + */ + if (strlen (namestr) > NAME_LENGTH) + { + rc_log(LOG_ERR, "rc_read_dictionary: invalid name length on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + + if (!isdigit (*valstr)) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid value on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + value = atoi (valstr); + + if (strcmp (typestr, "string") == 0) + { + type = PW_TYPE_STRING; + } + else if (strcmp (typestr, "integer") == 0) + { + type = PW_TYPE_INTEGER; + } + else if (strcmp (typestr, "ipaddr") == 0) + { + type = PW_TYPE_IPADDR; + } + else if (strcmp (typestr, "ipv6addr") == 0) + { + type = PW_TYPE_IPV6ADDR; + } + else if (strcmp (typestr, "ipv6prefix") == 0) + { + type = PW_TYPE_IPV6PREFIX; + } + else if (strcmp (typestr, "date") == 0) + { + type = PW_TYPE_DATE; + } + else + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid type on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + + dvend = NULL; + if (optstr[0] != '\0') { + char *cp1; + for (cp1 = optstr; cp1 != NULL; cp1 = cp) { + cp = strchr(cp1, ','); + if (cp != NULL) { + *cp = '\0'; + cp++; + } + if (strncmp(cp1, "vendor=", 7) == 0) + cp1 += 7; + dvend = rc_dict_findvend(rh, cp1); + if (dvend == NULL) { + rc_log(LOG_ERR, + "rc_read_dictionary: unknown Vendor-Id %s on line %d of dictionary %s", + cp1, line_no, filename); + fclose(dictfd); + return -1; + } + } + } + + /* Create a new attribute for the list */ + if ((attr = malloc (sizeof (DICT_ATTR))) == NULL) + { + rc_log(LOG_CRIT, "rc_read_dictionary: out of memory"); + fclose(dictfd); + return -1; + } + strcpy (attr->name, namestr); + attr->value = value | (attr_vendorspec << 16); + attr->type = type; + + if (dvend != NULL) { + attr->value = value | (dvend->vendorpec << 16); + } else { + attr->value = value | (attr_vendorspec << 16); + } + + /* Insert it into the list */ + attr->next = rh->dictionary_attributes; + rh->dictionary_attributes = attr; + } + else if (strncmp (buffer, "VALUE", 5) == 0) + { + /* Read the VALUE line */ + if (sscanf (buffer, "%63s%63s%63s%63s", dummystr, attrstr, + namestr, valstr) != 4) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid value entry on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + + /* + * Validate all entries + */ + if (strlen (attrstr) > NAME_LENGTH) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid attribute length on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + + if (strlen (namestr) > NAME_LENGTH) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid name length on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + + if (!isdigit (*valstr)) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid value on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + value = atoi (valstr); + + /* Create a new VALUE entry for the list */ + if ((dval = malloc (sizeof (DICT_VALUE))) == NULL) + { + rc_log(LOG_CRIT, "rc_read_dictionary: out of memory"); + fclose(dictfd); + return -1; + } + strcpy (dval->attrname, attrstr); + strcpy (dval->name, namestr); + dval->value = value; + + /* Insert it into the list */ + dval->next = rh->dictionary_values; + rh->dictionary_values = dval; + } + else if (strncmp (buffer, "$INCLUDE", 8) == 0) + { + /* Read the $INCLUDE line */ + if (sscanf (buffer, "%63s%63s", dummystr, namestr) != 2) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid include entry on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + ifilename = namestr; + /* Append directory if necessary */ + if (namestr[0] != '/') { + cp = strrchr(filename, '/'); + if (cp != NULL) { + ifilename = alloca(AUTH_ID_LEN); + *cp = '\0'; + snprintf(ifilename, AUTH_ID_LEN, "%s/%s", filename, namestr); + *cp = '/'; + } + } + if (rc_read_dictionary(rh, ifilename) < 0) + { + fclose(dictfd); + return -1; + } + } + else if (strncmp (buffer, "END-VENDOR", 10) == 0) + { + attr_vendorspec = 0; + } + else if (strncmp (buffer, "BEGIN-VENDOR", 12) == 0) + { + DICT_VENDOR *v; + /* Read the vendor name */ + if (sscanf (buffer+12, "%63s", dummystr) != 1) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid Vendor-Id on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + + v = rc_dict_findvend(rh, dummystr); + if (v == NULL) { + rc_log(LOG_ERR, + "rc_read_dictionary: unknown Vendor %s on line %d of dictionary %s", + dummystr, line_no, filename); + fclose(dictfd); + return -1; + } + + attr_vendorspec = v->vendorpec; + } + else if (strncmp (buffer, "VENDOR", 6) == 0) + { + /* Read the VALUE line */ + if (sscanf (buffer, "%63s%63s%63s", dummystr, attrstr, valstr) != 3) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid Vendor-Id on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + + /* Validate all entries */ + if (strlen (attrstr) > NAME_LENGTH) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid attribute length on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + + if (!isdigit (*valstr)) + { + rc_log(LOG_ERR, + "rc_read_dictionary: invalid Vendor-Id on line %d of dictionary %s", + line_no, filename); + fclose(dictfd); + return -1; + } + value = atoi (valstr); + + /* Create a new VENDOR entry for the list */ + dvend = malloc(sizeof(DICT_VENDOR)); + if (dvend == NULL) + { + rc_log(LOG_CRIT, "rc_read_dictionary: out of memory"); + fclose(dictfd); + return -1; + } + strcpy (dvend->vendorname, attrstr); + dvend->vendorpec = value; + + /* Insert it into the list */ + dvend->next = rh->dictionary_vendors; + rh->dictionary_vendors = dvend; + } + } + fclose (dictfd); + return 0; +} + +/* + * Function: rc_dict_getattr + * + * Purpose: Return the full attribute structure based on the + * attribute id number. + * + */ + +DICT_ATTR *rc_dict_getattr (rc_handle const *rh, int attribute) +{ + DICT_ATTR *attr; + + attr = rh->dictionary_attributes; + while (attr != NULL) + { + if (attr->value == attribute) + { + return attr; + } + attr = attr->next; + } + return NULL; +} + +/* + * Function: rc_dict_findattr + * + * Purpose: Return the full attribute structure based on the + * attribute name. + * + */ + +DICT_ATTR *rc_dict_findattr (rc_handle const *rh, char const *attrname) +{ + DICT_ATTR *attr; + + attr = rh->dictionary_attributes; + while (attr != NULL) + { + if (strcasecmp (attr->name, attrname) == 0) + { + return attr; + } + attr = attr->next; + } + return NULL; +} + + +/* + * Function: rc_dict_findval + * + * Purpose: Return the full value structure based on the + * value name. + * + */ + +DICT_VALUE *rc_dict_findval (rc_handle const *rh, char const *valname) +{ + DICT_VALUE *val; + + val = rh->dictionary_values; + while (val != NULL) + { + if (strcasecmp (val->name, valname) == 0) + { + return val; + } + val = val->next; + } + return NULL; +} + +/* + * Function: rc_dict_findvend + * + * Purpose: Return the full vendor structure based on the + * vendor name. + * + */ + +DICT_VENDOR * +rc_dict_findvend(rc_handle const *rh, char const *vendorname) +{ + DICT_VENDOR *vend; + + for (vend = rh->dictionary_vendors; vend != NULL; vend = vend->next) + if (strcasecmp(vend->vendorname, vendorname) == 0) + return vend; + return NULL; +} + +/* + * Function: rc_dict_getvend + * + * Purpose: Return the full vendor structure based on the + * vendor id number. + * + */ + +DICT_VENDOR * +rc_dict_getvend (rc_handle const *rh, int vendorpec) +{ + DICT_VENDOR *vend; + + for (vend = rh->dictionary_vendors; vend != NULL; vend = vend->next) + if (vend->vendorpec == vendorpec) + return vend; + return NULL; +} + +/* + * Function: dict_getval + * + * Purpose: Return the full value structure based on the + * actual value and the associated attribute name. + * + */ + +DICT_VALUE * +rc_dict_getval (rc_handle const *rh, uint32_t value, char const *attrname) +{ + DICT_VALUE *val; + + val = rh->dictionary_values; + while (val != NULL) + { + if (strcmp (val->attrname, attrname) == 0 && + val->value == value) + { + return val; + } + val = val->next; + } + return NULL; +} + +/* + * Function: rc_dict_free + * + * Purpose: Free allocated av lists + * + * Arguments: Radius Client handle + */ + +void +rc_dict_free(rc_handle *rh) +{ + DICT_ATTR *attr, *nattr; + DICT_VALUE *val, *nval; + DICT_VENDOR *vend, *nvend; + + for (attr = rh->dictionary_attributes; attr != NULL; attr = nattr) { + nattr = attr->next; + free(attr); + } + for (val = rh->dictionary_values; val != NULL; val = nval) { + nval = val->next; + free(val); + } + for (vend = rh->dictionary_vendors; vend != NULL; vend = nvend) { + nvend = vend->next; + free(vend); + } + rh->dictionary_attributes = NULL; + rh->dictionary_values = NULL; + rh->dictionary_vendors = NULL; +} diff --git a/src/plugins/vbng/lib/env.c b/src/plugins/vbng/lib/env.c new file mode 100644 index 00000000..03dccf91 --- /dev/null +++ b/src/plugins/vbng/lib/env.c @@ -0,0 +1,147 @@ +/* + * $Id: env.c,v 1.6 2007/06/21 18:07:23 cparker Exp $ + * + * Copyright (C) 1995,1996,1997 Lars Fenneberg + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#include +#include +#include + +/* + * Function: rc_new_env + * + * Purpose: allocate space for a new environment + * + */ + +ENV *rc_new_env(int size) +{ + ENV *p; + + if (size < 1) + return NULL; + + if ((p = malloc(sizeof(*p))) == NULL) + return NULL; + + if ((p->env = malloc(size * sizeof(char *))) == NULL) + { + rc_log(LOG_CRIT, "rc_new_env: out of memory"); + free(p); + return NULL; + } + + p->env[0] = NULL; + + p->size = 0; + p->maxsize = size; + + return p; +} + +/* + * Function: rc_free_env + * + * Purpose: free the space used by an env structure + * + */ + +void rc_free_env(ENV *env) +{ + free(env->env); + free(env); +} + +/* + * Function: rc_add_env + * + * Purpose: add an environment entry + * + */ + +int rc_add_env(ENV *env, char const *name, char const *value) +{ + int i; + size_t len; + char *new_env; + + for (i = 0; env->env[i] != NULL; i++) + { + if (strncmp(env->env[i], name, MAX(strchr(env->env[i], '=') - env->env[i], (int)strlen(name))) == 0) + break; + } + + if (env->env[i]) + { + len = strlen(name)+strlen(value)+2; + if ((new_env = realloc(env->env[i], len)) == NULL) + return -1; + + env->env[i] = new_env; + + snprintf(env->env[i], len, "%s=%s", name, value); + } else { + if (env->size == (env->maxsize-1)) { + rc_log(LOG_CRIT, "rc_add_env: not enough space for environment (increase ENV_SIZE)"); + return -1; + } + + len = strlen(name)+strlen(value)+2; + if ((env->env[env->size] = malloc(len)) == NULL) { + rc_log(LOG_CRIT, "rc_add_env: out of memory"); + return -1; + } + + snprintf(env->env[env->size], len, "%s=%s", name, value); + + env->size++; + + env->env[env->size] = NULL; + } + + return 0; +} + +/* + * Function: rc_import_env + * + * Purpose: imports an array of null-terminated strings + * + */ + +int rc_import_env(ENV *env, char const **import) +{ + char *es; + + while (*import) + { + es = strchr(*import, '='); + + if (!es) + { + import++; + continue; + } + + /* ok, i grant thats not very clean... */ + *es = '\0'; + + if (rc_add_env(env, *import, es+1) < 0) + { + *es = '='; + return -1; + } + + *es = '='; + + import++; + } + + return 0; +} diff --git a/src/plugins/vbng/lib/ip_util.c b/src/plugins/vbng/lib/ip_util.c new file mode 100644 index 00000000..d686a59e --- /dev/null +++ b/src/plugins/vbng/lib/ip_util.c @@ -0,0 +1,390 @@ +/* + * $Id: ip_util.c,v 1.14 2010/03/17 18:57:01 aland Exp $ + * + * Copyright (C) 1995,1996,1997 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#include +#include +#include + +#define HOSTBUF_SIZE 1024 + +#if !defined(SA_LEN) +#define SA_LEN(sa) \ + (((sa)->sa_family == AF_INET) ? \ + sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) +#endif + + +static __thread size_t hostbuflen=HOSTBUF_SIZE; +static __thread char *tmphostbuf=NULL; + +/* + * Function: rc_gethostbyname + * + * Purpose: threadsafe replacement for gethostbyname. + * + * Returns: NULL on failure, hostent pointer on success + */ + +struct hostent *rc_gethostbyname(char const *hostname) +{ + struct hostent *hp; +#ifdef GETHOSTBYNAME_R +#if defined (GETHOSTBYNAMERSTYLE_SYSV) || defined (GETHOSTBYNAMERSTYLE_GNU) + struct hostent hostbuf; + int res; + int herr; + + if(!tmphostbuf) tmphostbuf = malloc(hostbuflen); +#endif +#endif + +#ifdef GETHOSTBYNAME_R +#if defined (GETHOSTBYNAMERSTYLE_GNU) + while ((res = gethostbyname_r(hostname, &hostbuf, tmphostbuf, hostbuflen, &hp, &herr)) == ERANGE) + { + /* Enlarge the buffer */ + hostbuflen *= 2; + tmphostbuf = realloc(tmphostbuf, hostbuflen); + } + if(res) return NULL; +#elif defined (GETHOSTBYNAMERSTYLE_SYSV) + hp = gethostbyname_r(hostname, &hostbuf, tmphostbuf, hostbuflen, &herr); +#else + hp = gethostbyname(hostname); +#endif +#else + hp = gethostbyname(hostname); +#endif + + if (hp == NULL) { + return NULL; + } + return hp; +} + +/* + * Function: rc_gethostbyname + * + * Purpose: threadsafe replacement for gethostbyname. + * + * Returns: NULL on failure, hostent pointer on success + */ + +struct hostent *rc_gethostbyaddr(char const *addr, size_t length, int format) +{ + struct hostent *hp; +#ifdef GETHOSTBYADDR_R +#if defined (GETHOSTBYADDRRSTYLE_SYSV) || defined (GETHOSTBYADDRRSTYLE_GNU) + struct hostent hostbuf; + int res; + int herr; + + if(!tmphostbuf) tmphostbuf = malloc(hostbuflen); +#endif +#endif + +#ifdef GETHOSTBYADDR_R +#if defined (GETHOSTBYADDRRSTYLE_GNU) + while ((res = gethostbyaddr_r(addr, length, format, &hostbuf, tmphostbuf, hostbuflen, + &hp, &herr)) == ERANGE) + { + /* Enlarge the buffer */ + hostbuflen *= 2; + tmphostbuf = realloc(tmphostbuf, hostbuflen); + } + if(res) return NULL; +#elif GETHOSTBYADDRSTYLE_SYSV + hp = gethostbyaddr_r(addr, length, format, &hostbuf, tmphostbuf, hostbuflen, &herr); +#else + hp = gethostbyaddr((char *)&addr, sizeof(struct in_addr), AF_INET); +#endif +#else + hp = gethostbyaddr((char *)&addr, sizeof(struct in_addr), AF_INET); +#endif + + if (hp == NULL) { + return NULL; + } + return hp; +} + +/* + * Function: rc_get_ipaddr + * + * Purpose: return an IP address in host long notation from a host + * name or address in dot notation. + * + * Returns: 0 on failure + */ + +uint32_t rc_get_ipaddr (char const *host) +{ + struct hostent *hp; + + if (rc_good_ipaddr (host) == 0) + { + return ntohl(inet_addr (host)); + } + else if ((hp = rc_gethostbyname(host)) == NULL) + { + rc_log(LOG_ERR,"rc_get_ipaddr: couldn't resolve hostname: %s", host); + return (uint32_t)0; + } + return ntohl((*(uint32_t *) hp->h_addr)); +} + +/* + * Function: rc_good_ipaddr + * + * Purpose: check for valid IP address in standard dot notation. + * + * Returns: 0 on success, -1 when failure + * + */ + +int rc_good_ipaddr (char const *addr) +{ + int dot_count; + int digit_count; + + if (addr == NULL) + return -1; + + dot_count = 0; + digit_count = 0; + while (*addr != '\0' && *addr != ' ') + { + if (*addr == '.') + { + dot_count++; + digit_count = 0; + } + else if (!isdigit (*addr)) + { + dot_count = 5; + } + else + { + digit_count++; + if (digit_count > 3) + { + dot_count = 5; + } + } + addr++; + } + if (dot_count != 3) + { + return -1; + } + else + { + return 0; + } +} + +/* + * Function: rc_ip_hostname + * + * Purpose: Return a printable host name (or IP address in dot notation) + * for the supplied IP address. + * + */ + +char const *rc_ip_hostname (uint32_t h_ipaddr) +{ + struct hostent *hp; + uint32_t n_ipaddr = htonl (h_ipaddr); + + if ((hp = rc_gethostbyaddr ((char *) &n_ipaddr, sizeof (struct in_addr), + AF_INET)) == NULL) { + rc_log(LOG_ERR,"rc_ip_hostname: couldn't look up host by addr: %08lX", h_ipaddr); + } + + return (hp == NULL) ? "unknown" : hp->h_name; +} + +/* + * Function: rc_getport + * + * Purpose: get the port number for the supplied request type + * + */ + +unsigned short rc_getport(int type) +{ + struct servent *svp; + + if ((svp = getservbyname ((type==AUTH)?"radius":"radacct", "udp")) == NULL) + { + return (type==AUTH) ? PW_AUTH_UDP_PORT : PW_ACCT_UDP_PORT; + } else { + return ntohs ((unsigned short) svp->s_port); + } +} + +/* + * Function: rc_own_hostname + * + * Purpose: get the hostname of this machine + * + * Returns -1 on failure, 0 on success + * + */ + +int +rc_own_hostname(char *hostname, int len) +{ +#ifdef HAVE_UNAME + struct utsname uts; +#endif + +#if defined(HAVE_UNAME) + if (uname(&uts) < 0) + { + rc_log(LOG_ERR,"rc_own_hostname: couldn't get own hostname"); + return -1; + } + strncpy(hostname, uts.nodename, len); +#elif defined(HAVE_GETHOSTNAME) + if (gethostname(hostname, len) < 0) + { + rc_log(LOG_ERR,"rc_own_hostname: couldn't get own hostname"); + return -1; + } +#elif defined(HAVE_SYSINFO) + if (sysinfo(SI_HOSTNAME, hostname, len) < 0) + { + rc_log(LOG_ERR,"rc_own_hostname: couldn't get own hostname"); + return -1; + } +#else + return -1; +#endif + + return 0; +} + +/* + * Function: rc_own_ipaddress + * + * Purpose: get the IP address of this host in host order + * + * Returns: IP address on success, 0 on failure + * + */ + +uint32_t rc_own_ipaddress(rc_handle *rh) +{ + char hostname[256]; + + if (!rh->this_host_ipaddr) { + if (rc_conf_str(rh, "bindaddr") == NULL || + strcmp(rc_conf_str(rh, "bindaddr"), "*") == 0) { + if (rc_own_hostname(hostname, sizeof(hostname)) < 0) + return 0; + } else { + strncpy(hostname, rc_conf_str(rh, "bindaddr"), sizeof(hostname)); + hostname[sizeof(hostname) - 1] = '\0'; + } + if ((rh->this_host_ipaddr = rc_get_ipaddr (hostname)) == 0) { + rc_log(LOG_ERR, "rc_own_ipaddress: couldn't get own IP address"); + return 0; + } + } + + return rh->this_host_ipaddr; +} + +/* + * Function: rc_own_bind_ipaddress + * + * Purpose: get the IP address to be used as a source address + * for sending requests in host order + * + * Returns: IP address + * + */ + +uint32_t rc_own_bind_ipaddress(rc_handle *rh) +{ + char hostname[256]; + uint32_t rval; + + if (rh->this_host_bind_ipaddr != NULL) + return *rh->this_host_bind_ipaddr; + + rh->this_host_bind_ipaddr = malloc(sizeof(*rh->this_host_bind_ipaddr)); + if (rh->this_host_bind_ipaddr == NULL) + rc_log(LOG_CRIT, "rc_own_bind_ipaddress: out of memory"); + if (rc_conf_str(rh, "bindaddr") == NULL || + strcmp(rc_conf_str(rh, "bindaddr"), "*") == 0) { + rval = INADDR_ANY; + } else { + strncpy(hostname, rc_conf_str(rh, "bindaddr"), sizeof(hostname)); + hostname[sizeof(hostname) - 1] = '\0'; + if ((rval = rc_get_ipaddr (hostname)) == 0) { + rc_log(LOG_ERR, "rc_own_ipaddress: couldn't get IP address from bindaddr"); + rval = INADDR_ANY; + } + } + if (rh->this_host_bind_ipaddr != NULL) + *rh->this_host_bind_ipaddr = rval; + + return rval; +} + +/* + * Function: rc_get_srcaddr + * + * Purpose: given remote address find local address which the + * system will use as a source address for sending + * datagrams to that remote address + * + * Returns: 0 in success, -1 on failure, address is filled into + * the first argument. + * + */ +int +rc_get_srcaddr(struct sockaddr *lia, struct sockaddr *ria) +{ + int temp_sock; + socklen_t namelen; + + temp_sock = socket(ria->sa_family, SOCK_DGRAM, 0); + if (temp_sock == -1) { + rc_log(LOG_ERR, "rc_get_srcaddr: socket: %s", strerror(errno)); + return -1; + } + + if (connect(temp_sock, ria, SA_LEN(ria)) != 0) { + rc_log(LOG_ERR, "rc_get_srcaddr: connect: %s", + strerror(errno)); + close(temp_sock); + return -1; + } + + namelen = SA_LEN(ria); + if (getsockname(temp_sock, lia, &namelen) != 0) { + rc_log(LOG_ERR, "rc_get_srcaddr: getsockname: %s", + strerror(errno)); + close(temp_sock); + return -1; + } + + close(temp_sock); + return 0; +} diff --git a/src/plugins/vbng/lib/libfreeradiusclient.a b/src/plugins/vbng/lib/libfreeradiusclient.a new file mode 100644 index 0000000000000000000000000000000000000000..56c91fd880e9d435b62149ded2a0a9067cf8cbd6 GIT binary patch literal 367122 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 VI+nMQ;@;Fm^CRi(C#c__rjCD}rq>HJ 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 D0lJ|_ literal 0 HcmV?d00001 diff --git a/src/plugins/vbng/lib/libfreeradiusclient.so b/src/plugins/vbng/lib/libfreeradiusclient.so new file mode 100644 index 0000000000000000000000000000000000000000..2603f0181c7c43542e59d2907d5bf6c4b0730916 GIT binary patch literal 216320 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%0DhV@0HB5ucRTyXxDm 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 +#include +#include + +/* + * Function: rc_openlog + * + * Purpose: open log + * + * Arguments: identification string + * + * Returns: nothing + * + */ + +void rc_openlog(char const *ident) +{ +#ifndef _MSC_VER /* TODO: Fix me */ + openlog(ident, LOG_PID, RC_LOG_FACILITY); +#endif +} + +/* + * Function: rc_log + * + * Purpose: log information + * + * Arguments: priority (just like syslog), rest like printf + * + * Returns: nothing + * + */ + +void rc_log(int prio, char const *format, ...) +{ + char buff[1024]; + va_list ap; + + va_start(ap,format); + vsnprintf(buff, sizeof(buff), format, ap); + va_end(ap); + +#ifndef _MSC_VER /* TODO: Fix me */ + syslog(prio, "%s", buff); +#endif +} diff --git a/src/plugins/vbng/lib/md5.c b/src/plugins/vbng/lib/md5.c new file mode 100644 index 00000000..2344fb9c --- /dev/null +++ b/src/plugins/vbng/lib/md5.c @@ -0,0 +1,251 @@ +#include "md5.h" + +/* The below was retrieved from + * http://www.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/crypto/md5.c?rev=1.1 + * with the following changes: + * #includes commented out. + * Support context->count as uint32_t[2] instead of uint64_t + * u_int* to uint* + */ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/*#include */ +/*#include */ +/*#include */ + +#define PUT_64BIT_LE(cp, value) do { \ + (cp)[7] = (value)[1] >> 24; \ + (cp)[6] = (value)[1] >> 16; \ + (cp)[5] = (value)[1] >> 8; \ + (cp)[4] = (value)[1]; \ + (cp)[3] = (value)[0] >> 24; \ + (cp)[2] = (value)[0] >> 16; \ + (cp)[1] = (value)[0] >> 8; \ + (cp)[0] = (value)[0]; } while (0) + +#define PUT_32BIT_LE(cp, value) do { \ + (cp)[3] = (value) >> 24; \ + (cp)[2] = (value) >> 16; \ + (cp)[1] = (value) >> 8; \ + (cp)[0] = (value); } while (0) + +static uint8_t PADDING[MD5_BLOCK_LENGTH] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(MD5_CTX *ctx) +{ + ctx->count[0] = 0; + ctx->count[1] = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(MD5_CTX *ctx, uint8_t const *input, size_t len) +{ + size_t have, need; + + /* Check how many bytes we already have and how many more we need. */ + have = (size_t)((ctx->count[0] >> 3) & (MD5_BLOCK_LENGTH - 1)); + need = MD5_BLOCK_LENGTH - have; + + /* Update bitcount */ +/* ctx->count += (uint64_t)len << 3;*/ + if ((ctx->count[0] += ((uint32_t)len << 3)) < (uint32_t)len) { + /* Overflowed ctx->count[0] */ + ctx->count[1]++; + } + ctx->count[1] += ((uint32_t)len >> 29); + + + + if (len >= need) { + if (have != 0) { + memcpy(ctx->buffer + have, input, need); + MD5Transform(ctx->state, ctx->buffer); + input += need; + len -= need; + have = 0; + } + + /* Process data in MD5_BLOCK_LENGTH-byte chunks. */ + while (len >= MD5_BLOCK_LENGTH) { + MD5Transform(ctx->state, input); + input += MD5_BLOCK_LENGTH; + len -= MD5_BLOCK_LENGTH; + } + } + + /* Handle any remaining bytes of data. */ + if (len != 0) + memcpy(ctx->buffer + have, input, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx) +{ + uint8_t count[8]; + size_t padlen; + int i; + + /* Convert count to 8 bytes in little endian order. */ + PUT_64BIT_LE(count, ctx->count); + + /* Pad out to 56 mod 64. */ + padlen = MD5_BLOCK_LENGTH - + ((ctx->count[0] >> 3) & (MD5_BLOCK_LENGTH - 1)); + if (padlen < 1 + 8) + padlen += MD5_BLOCK_LENGTH; + MD5Update(ctx, PADDING, padlen - 8); /* padlen - 8 <= 64 */ + MD5Update(ctx, count, 8); + + if (digest != NULL) { + for (i = 0; i < 4; i++) + PUT_32BIT_LE(digest + i * 4, ctx->state[i]); + } + memset(ctx, 0, sizeof(*ctx)); /* in case it's sensitive */ +} + + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32_t state[4], uint8_t const block[MD5_BLOCK_LENGTH]) +{ + uint32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4]; + + for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) { + in[a] = (uint32_t)( + (uint32_t)(block[a * 4 + 0]) | + (uint32_t)(block[a * 4 + 1]) << 8 | + (uint32_t)(block[a * 4 + 2]) << 16 | + (uint32_t)(block[a * 4 + 3]) << 24); + } + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} diff --git a/src/plugins/vbng/lib/md5.h b/src/plugins/vbng/lib/md5.h new file mode 100644 index 00000000..fcbaf91f --- /dev/null +++ b/src/plugins/vbng/lib/md5.h @@ -0,0 +1,86 @@ +/* + * md5.h Structures and prototypes for md5. + * + * Version: $Id: md5.h,v 1.2 2007/06/21 18:07:24 cparker Exp $ + * License: BSD, but largely derived from a public domain source. + * + */ + +#ifndef _RCRAD_MD5_H +#define _RCRAD_MD5_H + +#include "config.h" + +#ifdef HAVE_NETTLE + +#include + +#else + +#ifdef HAVE_INTTYPES_H +#include +#endif + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_STDINT_H +#include +#endif + +#include +/* + * FreeRADIUS Client defines to ensure globally unique MD5 function names, + * so that we don't pick up vendor-specific broken MD5 libraries. + */ +#define MD5_CTX librad_MD5_CTX +#define MD5Init librad_MD5Init +#define MD5Update librad_MD5Update +#define MD5Final librad_MD5Final +#define MD5Transform librad_MD5Transform + +/* The below was retrieved from + * http://www.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/crypto/md5.h?rev=1.1 + * With the following changes: uint64_t => uint32_t[2] + * Commented out #include + * Commented out the __BEGIN and __END _DECLS, and the __attributes. + */ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + */ + +#define MD5_BLOCK_LENGTH 64 +#define MD5_DIGEST_LENGTH 16 + +typedef struct MD5Context { + uint32_t state[4]; /* state */ + uint32_t count[2]; /* number of bits, mod 2^64 */ + uint8_t buffer[MD5_BLOCK_LENGTH]; /* input buffer */ +} MD5_CTX; + +/* include */ + +/* __BEGIN_DECLS */ +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, uint8_t const *, size_t) +/* __attribute__((__bounded__(__string__,2,3)))*/; +void MD5Final(uint8_t [MD5_DIGEST_LENGTH], MD5_CTX *) +/* __attribute__((__bounded__(__minbytes__,1,MD5_DIGEST_LENGTH)))*/; +void MD5Transform(uint32_t [4], uint8_t const [MD5_BLOCK_LENGTH]) +/* __attribute__((__bounded__(__minbytes__,1,4)))*/ +/* __attribute__((__bounded__(__minbytes__,2,MD5_BLOCK_LENGTH)))*/; +/* __END_DECLS */ + +#endif /* HAVE_NETTLE */ + +#endif /* _RCRAD_MD5_H */ diff --git a/src/plugins/vbng/lib/options.h b/src/plugins/vbng/lib/options.h new file mode 100644 index 00000000..05b66dfd --- /dev/null +++ b/src/plugins/vbng/lib/options.h @@ -0,0 +1,57 @@ +/* + * $Id: options.h,v 1.6 2008/03/05 16:35:20 cparker Exp $ + * + * Copyright (C) 1996 Lars Fenneberg + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#define OPTION_LEN 64 + +/* ids for different option types */ +#define OT_STR (1<<0) /* string */ +#define OT_INT (1<<1) /* integer */ +#define OT_SRV (1<<2) /* server list */ +#define OT_AUO (1<<3) /* authentication order */ + +#define OT_ANY ((unsigned int)~0) /* used internally */ + +/* status types */ +#define ST_UNDEF (1<<0) /* option is undefined */ + +typedef struct _option { + char name[OPTION_LEN]; /* name of the option */ + int type, status; /* type and status */ + void *val; /* pointer to option value */ +} OPTION; + +static OPTION config_options_default[] = { +/* internally used options */ +{"config_file", OT_STR, ST_UNDEF, NULL}, +/* General options */ +{"auth_order", OT_AUO, ST_UNDEF, NULL}, +{"login_tries", OT_INT, ST_UNDEF, NULL}, +{"login_timeout", OT_INT, ST_UNDEF, NULL}, +{"nologin", OT_STR, ST_UNDEF, NULL}, +{"issue", OT_STR, ST_UNDEF, NULL}, +/* RADIUS specific options */ +{"authserver", OT_SRV, ST_UNDEF, NULL}, +{"acctserver", OT_SRV, ST_UNDEF, NULL}, +{"servers", OT_STR, ST_UNDEF, NULL}, +{"dictionary", OT_STR, ST_UNDEF, NULL}, +{"login_radius", OT_STR, ST_UNDEF, NULL}, +{"seqfile", OT_STR, ST_UNDEF, NULL}, +{"mapfile", OT_STR, ST_UNDEF, NULL}, +{"default_realm", OT_STR, ST_UNDEF, NULL}, +{"radius_timeout", OT_INT, ST_UNDEF, NULL}, +{"radius_retries", OT_INT, ST_UNDEF, NULL}, +{"radius_deadtime", OT_INT, ST_UNDEF, NULL}, +{"bindaddr", OT_STR, ST_UNDEF, NULL}, +/* local options */ +{"login_local", OT_STR, ST_UNDEF, NULL}, +}; + +#define NUM_OPTIONS ((sizeof(config_options_default))/(sizeof(config_options_default[0]))) diff --git a/src/plugins/vbng/lib/rc-md5.c b/src/plugins/vbng/lib/rc-md5.c new file mode 100644 index 00000000..be9fbcbd --- /dev/null +++ b/src/plugins/vbng/lib/rc-md5.c @@ -0,0 +1,24 @@ +/* MD5 message-digest algorithm */ + +/* This file is licensed under the BSD License, but is largely derived from + * public domain source code + */ + +/* + * FORCE MD5 TO USE OUR MD5 HEADER FILE! + * + * If we don't do this, it might pick up the systems broken MD5. + * - Alan DeKok + */ +#include "rc-md5.h" + +void rc_md5_calc(unsigned char *output, unsigned char const *input, + size_t inlen) +{ + MD5_CTX context; + + MD5Init(&context); + MD5Update(&context, input, inlen); + MD5Final(output, &context); +} + diff --git a/src/plugins/vbng/lib/rc-md5.h b/src/plugins/vbng/lib/rc-md5.h new file mode 100644 index 00000000..a30f16d3 --- /dev/null +++ b/src/plugins/vbng/lib/rc-md5.h @@ -0,0 +1,27 @@ +/* + * md5.h Structures and prototypes for md5. + * + * Version: $Id: md5.h,v 1.2 2007/06/21 18:07:24 cparker Exp $ + * License: BSD + * + */ + +#ifndef _RC_MD5_H +#define _RC_MD5_H + +#include "config.h" + +#ifdef HAVE_NETTLE + +#include + +#else + +#include "md5.h" + +#endif /* HAVE_NETTLE */ + +void rc_md5_calc(unsigned char *output, unsigned char const *input, + size_t inputlen); + +#endif /* _RC_MD5_H */ diff --git a/src/plugins/vbng/lib/sendserver.c b/src/plugins/vbng/lib/sendserver.c new file mode 100644 index 00000000..bfdd9a26 --- /dev/null +++ b/src/plugins/vbng/lib/sendserver.c @@ -0,0 +1,631 @@ +/* + * $Id: sendserver.c,v 1.30 2010/06/15 09:22:52 aland Exp $ + * + * Copyright (C) 1995,1996,1997 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#include + +#include +#include +#include +#include + +#define SA(p) ((struct sockaddr *)(p)) + +static void rc_random_vector (unsigned char *); +static int rc_check_reply (AUTH_HDR *, int, char const *, unsigned char const *, unsigned char); + +/* + * Function: rc_pack_list + * + * Purpose: Packs an attribute value pair list into a buffer. + * + * Returns: Number of octets packed. + * + */ + +static int rc_pack_list (VALUE_PAIR *vp, char *secret, AUTH_HDR *auth) +{ + int length, i, pc, padded_length; + int total_length = 0; + size_t secretlen; + uint32_t lvalue, vendor; + unsigned char passbuf[MAX(AUTH_PASS_LEN, CHAP_VALUE_LENGTH)]; + unsigned char md5buf[256]; + unsigned char *buf, *vector, *vsa_length_ptr; + + buf = auth->data; + + while (vp != NULL) + { + vsa_length_ptr = NULL; + if (VENDOR(vp->attribute) != 0) { + *buf++ = PW_VENDOR_SPECIFIC; + vsa_length_ptr = buf; + *buf++ = 6; + vendor = htonl(VENDOR(vp->attribute)); + memcpy(buf, &vendor, sizeof(uint32_t)); + buf += 4; + total_length += 6; + } + *buf++ = (vp->attribute & 0xff); + + switch (vp->attribute) + { + case PW_USER_PASSWORD: + + /* Encrypt the password */ + + /* Chop off password at AUTH_PASS_LEN */ + length = vp->lvalue; + if (length > AUTH_PASS_LEN) + length = AUTH_PASS_LEN; + + /* Calculate the padded length */ + padded_length = (length+(AUTH_VECTOR_LEN-1)) & ~(AUTH_VECTOR_LEN-1); + + /* Record the attribute length */ + *buf++ = padded_length + 2; + if (vsa_length_ptr != NULL) *vsa_length_ptr += padded_length + 2; + + /* Pad the password with zeros */ + memset ((char *) passbuf, '\0', AUTH_PASS_LEN); + memcpy ((char *) passbuf, vp->strvalue, (size_t) length); + + secretlen = strlen (secret); + vector = (unsigned char *)auth->vector; + for(i = 0; i < padded_length; i += AUTH_VECTOR_LEN) + { + /* Calculate the MD5 digest*/ + strcpy ((char *) md5buf, secret); + memcpy ((char *) md5buf + secretlen, vector, + AUTH_VECTOR_LEN); + rc_md5_calc (buf, md5buf, secretlen + AUTH_VECTOR_LEN); + + /* Remeber the start of the digest */ + vector = buf; + + /* Xor the password into the MD5 digest */ + for (pc = i; pc < (i + AUTH_VECTOR_LEN); pc++) + { + *buf++ ^= passbuf[pc]; + } + } + + total_length += padded_length + 2; + + break; +#if 0 + case PW_CHAP_PASSWORD: + + *buf++ = CHAP_VALUE_LENGTH + 2; + if (vsa_length_ptr != NULL) *vsa_length_ptr += CHAP_VALUE_LENGTH + 2; + + /* Encrypt the Password */ + length = vp->lvalue; + if (length > CHAP_VALUE_LENGTH) + { + length = CHAP_VALUE_LENGTH; + } + memset ((char *) passbuf, '\0', CHAP_VALUE_LENGTH); + memcpy ((char *) passbuf, vp->strvalue, (size_t) length); + + /* Calculate the MD5 Digest */ + secretlen = strlen (secret); + strcpy ((char *) md5buf, secret); + memcpy ((char *) md5buf + secretlen, (char *) auth->vector, + AUTH_VECTOR_LEN); + rc_md5_calc (buf, md5buf, secretlen + AUTH_VECTOR_LEN); + + /* Xor the password into the MD5 digest */ + for (i = 0; i < CHAP_VALUE_LENGTH; i++) + { + *buf++ ^= passbuf[i]; + } + total_length += CHAP_VALUE_LENGTH + 2; + + break; +#endif + default: + switch (vp->type) + { + case PW_TYPE_STRING: + length = vp->lvalue; + *buf++ = length + 2; + if (vsa_length_ptr != NULL) *vsa_length_ptr += length + 2; + memcpy (buf, vp->strvalue, (size_t) length); + buf += length; + total_length += length + 2; + break; + + case PW_TYPE_IPV6ADDR: + length = 16; + if (vsa_length_ptr != NULL) *vsa_length_ptr += length + 2; + memcpy (buf, vp->strvalue, (size_t) length); + buf += length; + total_length += length + 2; + break; + + case PW_TYPE_IPV6PREFIX: + length = vp->lvalue; + if (vsa_length_ptr != NULL) *vsa_length_ptr += length + 2; + memcpy (buf, vp->strvalue, (size_t) length); + buf += length; + total_length += length + 2; + break; + + case PW_TYPE_INTEGER: + case PW_TYPE_IPADDR: + case PW_TYPE_DATE: + *buf++ = sizeof (uint32_t) + 2; + if (vsa_length_ptr != NULL) *vsa_length_ptr += sizeof(uint32_t) + 2; + lvalue = htonl (vp->lvalue); + memcpy (buf, (char *) &lvalue, sizeof (uint32_t)); + buf += sizeof (uint32_t); + total_length += sizeof (uint32_t) + 2; + break; + + default: + break; + } + break; + } + vp = vp->next; + } + return total_length; +} + +/* Function strappend + * + * Purpose: appends a string to the provided buffer + */ +static void strappend(char *dest, unsigned max_size, int *pos, const char *src) +{ + unsigned len = strlen(src) + 1; + + if (*pos == -1) + return; + + if (len + *pos > max_size) { + *pos = -1; + return; + } + + memcpy(&dest[*pos], src, len); + *pos += len-1; + return; +} + +/* + * Function: rc_send_server + * + * Purpose: send a request to a RADIUS server and wait for the reply + * + */ + +int rc_send_server (rc_handle *rh, SEND_DATA *data, char *msg) +{ + int sockfd; + struct sockaddr_in sinlocal; + struct sockaddr_in sinremote; + AUTH_HDR *auth, *recv_auth; + uint32_t auth_ipaddr, nas_ipaddr; + char *server_name; /* Name of server to query */ + socklen_t salen; + int result = 0; + int total_length; + int length, pos; + int retry_max; + size_t secretlen; + char secret[MAX_SECRET_LENGTH + 1]; + unsigned char vector[AUTH_VECTOR_LEN]; + char recv_buffer[BUFFER_LEN]; + char send_buffer[BUFFER_LEN]; + int retries; + VALUE_PAIR *vp; + struct pollfd pfd; + double start_time, timeout; + + server_name = data->server; + if (server_name == NULL || server_name[0] == '\0') + return ERROR_RC; + + if ((vp = rc_avpair_get(data->send_pairs, PW_SERVICE_TYPE, 0)) && \ + (vp->lvalue == PW_ADMINISTRATIVE)) + { + strcpy(secret, MGMT_POLL_SECRET); + if ((auth_ipaddr = rc_get_ipaddr(server_name)) == 0) + return ERROR_RC; + } + else + { + if(data->secret != NULL) + { + strncpy(secret, data->secret, MAX_SECRET_LENGTH); + } + /* + else + { + */ + if (rc_find_server (rh, server_name, &auth_ipaddr, secret) != 0) + { + rc_log(LOG_ERR, "rc_send_server: unable to find server: %s", server_name); + return ERROR_RC; + } + /*}*/ + } + + DEBUG(LOG_ERR, "DEBUG: rc_send_server: creating socket to: %s", server_name); + + sockfd = socket (AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + memset (secret, '\0', sizeof (secret)); + rc_log(LOG_ERR, "rc_send_server: socket: %s", strerror(errno)); + return ERROR_RC; + } + + memset((char *)&sinlocal, '\0', sizeof(sinlocal)); + sinlocal.sin_family = AF_INET; + sinlocal.sin_addr.s_addr = htonl(rc_own_bind_ipaddress(rh)); + sinlocal.sin_port = htons((unsigned short) 0); + if (bind(sockfd, SA(&sinlocal), sizeof(sinlocal)) < 0) + { + close (sockfd); + memset (secret, '\0', sizeof (secret)); + rc_log(LOG_ERR, "rc_send_server: bind: %s: %s", server_name, strerror(errno)); + return ERROR_RC; + } + + retry_max = data->retries; /* Max. numbers to try for reply */ + retries = 0; /* Init retry cnt for blocking call */ + + memset ((char *)&sinremote, '\0', sizeof(sinremote)); + sinremote.sin_family = AF_INET; + sinremote.sin_addr.s_addr = htonl (auth_ipaddr); + sinremote.sin_port = htons ((unsigned short) data->svc_port); + + /* + * Fill in NAS-IP-Address (if needed) + */ + if (rc_avpair_get(data->send_pairs, PW_NAS_IP_ADDRESS, 0) == NULL) { + if (sinlocal.sin_addr.s_addr == htonl(INADDR_ANY)) { + if (rc_get_srcaddr(SA(&sinlocal), SA(&sinremote)) != 0) { + close (sockfd); + memset (secret, '\0', sizeof (secret)); + return ERROR_RC; + } + } + nas_ipaddr = ntohl(sinlocal.sin_addr.s_addr); + rc_avpair_add(rh, &(data->send_pairs), PW_NAS_IP_ADDRESS, + &nas_ipaddr, 0, 0); + } + + /* Build a request */ + auth = (AUTH_HDR *) send_buffer; + auth->code = data->code; + auth->id = data->seq_nbr; + + if (data->code == PW_ACCOUNTING_REQUEST) + { + total_length = rc_pack_list(data->send_pairs, secret, auth) + AUTH_HDR_LEN; + + auth->length = htons ((unsigned short) total_length); + + memset((char *) auth->vector, 0, AUTH_VECTOR_LEN); + secretlen = strlen (secret); + memcpy ((char *) auth + total_length, secret, secretlen); + rc_md5_calc (vector, (unsigned char *) auth, total_length + secretlen); + memcpy ((char *) auth->vector, (char *) vector, AUTH_VECTOR_LEN); + } + else + { + rc_random_vector (vector); + memcpy ((char *) auth->vector, (char *) vector, AUTH_VECTOR_LEN); + + total_length = rc_pack_list(data->send_pairs, secret, auth) + AUTH_HDR_LEN; + + auth->length = htons ((unsigned short) total_length); + } + + DEBUG(LOG_ERR, "DEBUG: local %s : 0, remote %s : %u\n", + inet_ntoa(sinlocal.sin_addr), + inet_ntoa(sinremote.sin_addr), data->svc_port); + + for (;;) + { + sendto (sockfd, (char *) auth, (unsigned int) total_length, (int) 0, + SA(&sinremote), sizeof (struct sockaddr_in)); + + pfd.fd = sockfd; + pfd.events = POLLIN; + pfd.revents = 0; + start_time = rc_getctime(); + for (timeout = data->timeout; timeout > 0; + timeout -= rc_getctime() - start_time) { + result = poll(&pfd, 1, timeout * 1000); + if (result != -1 || errno != EINTR) + break; + } + if (result == -1) + { + rc_log(LOG_ERR, "rc_send_server: poll: %s", strerror(errno)); + memset (secret, '\0', sizeof (secret)); + close (sockfd); + return ERROR_RC; + } + if (result == 1 && (pfd.revents & POLLIN) != 0) + break; + + /* + * Timed out waiting for response. Retry "retry_max" times + * before giving up. If retry_max = 0, don't retry at all. + */ + if (retries++ >= retry_max) + { + rc_log(LOG_ERR, + "rc_send_server: no reply from RADIUS server %s:%u, %s", + rc_ip_hostname (auth_ipaddr), data->svc_port, inet_ntoa(sinremote.sin_addr)); + close (sockfd); + memset (secret, '\0', sizeof (secret)); + return TIMEOUT_RC; + } + } + salen = sizeof(sinremote); + length = recvfrom (sockfd, (char *) recv_buffer, + (int) sizeof (recv_buffer), + (int) 0, SA(&sinremote), &salen); + + if (length <= 0) + { + rc_log(LOG_ERR, "rc_send_server: recvfrom: %s:%d: %s", server_name,\ + data->svc_port, strerror(errno)); + close (sockfd); + memset (secret, '\0', sizeof (secret)); + return ERROR_RC; + } + + recv_auth = (AUTH_HDR *)recv_buffer; + + if (length < AUTH_HDR_LEN || length < ntohs(recv_auth->length)) { + rc_log(LOG_ERR, "rc_send_server: recvfrom: %s:%d: reply is too short", + server_name, data->svc_port); + close(sockfd); + memset(secret, '\0', sizeof(secret)); + return ERROR_RC; + } + + result = rc_check_reply (recv_auth, BUFFER_LEN, secret, vector, data->seq_nbr); + + length = ntohs(recv_auth->length) - AUTH_HDR_LEN; + if (length > 0) { + data->receive_pairs = rc_avpair_gen(rh, NULL, recv_auth->data, + length, 0); + } else { + data->receive_pairs = NULL; + } + + close (sockfd); + memset (secret, '\0', sizeof (secret)); + + if (result != OK_RC) return result; + + if (msg) { + *msg = '\0'; + pos = 0; + vp = data->receive_pairs; + while (vp) + { + if ((vp = rc_avpair_get(vp, PW_REPLY_MESSAGE, 0))) + { + strappend(msg, PW_MAX_MSG_SIZE, &pos, vp->strvalue); + strappend(msg, PW_MAX_MSG_SIZE, &pos, "\n"); + vp = vp->next; + } + } + } + + if ((recv_auth->code == PW_ACCESS_ACCEPT) || + (recv_auth->code == PW_PASSWORD_ACK) || + (recv_auth->code == PW_ACCOUNTING_RESPONSE)) + { + result = OK_RC; + } + else if ((recv_auth->code == PW_ACCESS_REJECT) || + (recv_auth->code == PW_PASSWORD_REJECT)) + { + result = REJECT_RC; + } + else + { + result = BADRESP_RC; + } + + return result; +} + +/* + * Function: rc_check_reply + * + * Purpose: verify items in returned packet. + * + * Returns: OK_RC -- upon success, + * BADRESP_RC -- if anything looks funny. + * + */ + +static int rc_check_reply (AUTH_HDR *auth, int bufferlen, char const *secret, unsigned char const *vector, uint8_t seq_nbr) +{ + int secretlen; + int totallen; + unsigned char calc_digest[AUTH_VECTOR_LEN]; + unsigned char reply_digest[AUTH_VECTOR_LEN]; +#ifdef DIGEST_DEBUG + uint8_t *ptr; +#endif + + totallen = ntohs (auth->length); + secretlen = (int)strlen (secret); + + /* Do sanity checks on packet length */ + if ((totallen < 20) || (totallen > 4096)) + { + rc_log(LOG_ERR, "rc_check_reply: received RADIUS server response with invalid length"); + return BADRESP_RC; + } + + /* Verify buffer space, should never trigger with current buffer size and check above */ + if ((totallen + secretlen) > bufferlen) + { + rc_log(LOG_ERR, "rc_check_reply: not enough buffer space to verify RADIUS server response"); + return BADRESP_RC; + } + + /* Verify that id (seq. number) matches what we sent */ + if (auth->id != seq_nbr) + { + rc_log(LOG_ERR, "rc_check_reply: received non-matching id in RADIUS server response"); + return BADRESP_RC; + } + + /* Verify the reply digest */ + memcpy ((char *) reply_digest, (char *) auth->vector, AUTH_VECTOR_LEN); + memcpy ((char *) auth->vector, (char *) vector, AUTH_VECTOR_LEN); + memcpy ((char *) auth + totallen, secret, secretlen); +#ifdef DIGEST_DEBUG + rc_log(LOG_ERR, "Calculating digest on:"); + for (ptr = (u_char *)auth; ptr < ((u_char *)auth) + totallen + secretlen; ptr += 32) { + char buf[65]; + int i; + + buf[0] = '\0'; + for (i = 0; i < 32; i++) { + if (ptr + i >= ((u_char *)auth) + totallen + secretlen) + break; + sprintf(buf + i * 2, "%.2X", ptr[i]); + } + rc_log(LOG_ERR, " %s", buf); + } +#endif + rc_md5_calc (calc_digest, (unsigned char *) auth, totallen + secretlen); +#ifdef DIGEST_DEBUG + rc_log(LOG_ERR, "Calculated digest is:"); + for (ptr = (u_char *)calc_digest; ptr < ((u_char *)calc_digest) + 16; ptr += 32) { + char buf[65]; + int i; + + buf[0] = '\0'; + for (i = 0; i < 32; i++) { + if (ptr + i >= ((u_char *)calc_digest) + 16) + break; + sprintf(buf + i * 2, "%.2X", ptr[i]); + } + rc_log(LOG_ERR, " %s", buf); + } + rc_log(LOG_ERR, "Reply digest is:"); + for (ptr = (u_char *)reply_digest; ptr < ((u_char *)reply_digest) + 16; ptr += 32) { + char buf[65]; + int i; + + buf[0] = '\0'; + for (i = 0; i < 32; i++) { + if (ptr + i >= ((u_char *)reply_digest) + 16) + break; + sprintf(buf + i * 2, "%.2X", ptr[i]); + } + rc_log(LOG_ERR, " %s", buf); + } +#endif + + if (memcmp ((char *) reply_digest, (char *) calc_digest, + AUTH_VECTOR_LEN) != 0) + { +#ifdef RADIUS_116 + /* the original Livingston radiusd v1.16 seems to have + a bug in digest calculation with accounting requests, + authentication request are ok. i looked at the code + but couldn't find any bugs. any help to get this + kludge out are welcome. preferably i want to + reproduce the calculation bug here to be compatible + to stock Livingston radiusd v1.16. -lf, 03/14/96 + */ + if (auth->code == PW_ACCOUNTING_RESPONSE) + return OK_RC; +#endif + rc_log(LOG_ERR, "rc_check_reply: received invalid reply digest from RADIUS server"); + return BADRESP_RC; + } + + return OK_RC; + +} + +/* + * Function: rc_random_vector + * + * Purpose: generates a random vector of AUTH_VECTOR_LEN octets. + * + * Returns: the vector (call by reference) + * + */ + +static void rc_random_vector (unsigned char *vector) +{ + int randno; + int i; +#if defined(HAVE_GETENTROPY) + if (getentropy(vector, AUTH_VECTOR_LEN) >= 0) { + return; + } /* else fall through */ +#elif defined(HAVE_DEV_URANDOM) + int fd; + +/* well, I added this to increase the security for user passwords. + we use /dev/urandom here, as /dev/random might block and we don't + need that much randomness. BTW, great idea, Ted! -lf, 03/18/95 */ + + if ((fd = open(_PATH_DEV_URANDOM, O_RDONLY)) >= 0) + { + unsigned char *pos; + int readcount; + + i = AUTH_VECTOR_LEN; + pos = vector; + while (i > 0) + { + readcount = read(fd, (char *)pos, i); + if (readcount >= 0) { + pos += readcount; + i -= readcount; + } else { + if (errno != EINTR && errno != EAGAIN) + goto fallback; + } + } + + close(fd); + return; + } /* else fall through */ +#endif + fallback: + for (i = 0; i < AUTH_VECTOR_LEN;) + { + randno = random (); + memcpy ((char *) vector, (char *) &randno, sizeof (int)); + vector += sizeof (int); + i += sizeof (int); + } + + return; +} diff --git a/src/plugins/vbng/lib/util.c b/src/plugins/vbng/lib/util.c new file mode 100644 index 00000000..aa7c057d --- /dev/null +++ b/src/plugins/vbng/lib/util.c @@ -0,0 +1,347 @@ +/* + * $Id: util.c,v 1.10 2010/02/04 10:31:41 aland Exp $ + * + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * + * Copyright (C) 1995,1996,1997 Lars Fenneberg + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan + * and Merit Network, Inc. All Rights Reserved + * + * See the file COPYRIGHT for the respective terms and conditions. + * If the file is missing contact me at lf@elemental.net + * and I'll send you a copy. + * + */ + +#include + +#include +#include +#include + +#define RC_BUFSIZ 1024 + +/* + * Function: rc_str2tm + * + * Purpose: Turns printable string into correct tm struct entries. + * + */ + +static char const * months[] = + { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + +void rc_str2tm (char const *valstr, struct tm *tm) +{ + int i; + + /* Get the month */ + for (i = 0; i < 12; i++) + { + if (strncmp (months[i], valstr, 3) == 0) + { + tm->tm_mon = i; + i = 13; + } + } + + /* Get the Day */ + tm->tm_mday = atoi (&valstr[4]); + + /* Now the year */ + tm->tm_year = atoi (&valstr[7]) - 1900; +} + +/* + * Function: rc_getifname + * + * Purpose: get the network interface name associated with this tty + * + */ + +char *rc_getifname(rc_handle *rh, char const *tty) +{ +#if defined(BSD4_4) || defined(linux) + int fd; + + if ((fd = open(tty, O_RDWR|O_NDELAY)) < 0) { + rc_log(LOG_ERR, "rc_getifname: can't open %s: %s", tty, strerror(errno)); + return NULL; + } +#endif + +#ifdef BSD4_4 + strcpy(rh->ifname,ttyname(fd)); + if (strlen(rh->ifname) < 1) { + rc_log(LOG_ERR, "rc_getifname: can't get attached interface of %s: %s", tty, strerror(errno)); + close(fd); + return NULL; + } +#elif linux + if (ioctl(fd, SIOCGIFNAME, rh->ifname) < 0) { + rc_log(LOG_ERR, "rc_getifname: can't ioctl %s: %s", tty, strerror(errno)); + close(fd); + return NULL; + } +#else + return NULL; +#endif + +#if defined(BSD4_4) || defined(linux) + close(fd); + return rh->ifname; +#endif +} + +/* + * Function: rc_getstr + * + * Purpose: Reads in a string from the user (with or witout echo) + * + */ +#ifndef _MSC_VER +char *rc_getstr (rc_handle *rh, char const *prompt, int do_echo) +{ + int in, out; + char *p; + struct termios term_old, term_new; + int is_term, flags, old_flags; + char c; + int flushed = 0; + sigset_t newset; + sigset_t oldset; + + in = fileno(stdin); + out = fileno(stdout); + + (void) sigemptyset (&newset); + (void) sigaddset (&newset, SIGINT); + (void) sigaddset (&newset, SIGTSTP); + (void) sigaddset (&newset, SIGQUIT); + + (void) sigprocmask (SIG_BLOCK, &newset, &oldset); + + if ((is_term = isatty(in))) + { + + (void) tcgetattr (in, &term_old); + term_new = term_old; + if (do_echo) + term_new.c_lflag |= ECHO; + else + term_new.c_lflag &= ~ECHO; + + if (tcsetattr (in, TCSAFLUSH, &term_new) == 0) + flushed = 1; + + } + else + { + is_term = 0; + if ((flags = fcntl(in, F_GETFL, 0)) >= 0) { + old_flags = flags; + flags |= O_NONBLOCK; + + fcntl(in, F_SETFL, flags); + + while (read(in, &c, 1) > 0) + /* nothing */; + + fcntl(in, F_SETFL, old_flags); + + flushed = 1; + } + } + + (void)write(out, prompt, strlen(prompt)); + + /* well, this looks ugly, but it handles the following end of line + markers: \r \r\0 \r\n \n \n\r, at least at a second pass */ + + p = rh->buf; + for (;;) + { + if (read(in, &c, 1) <= 0) + return NULL; + + if (!flushed && ((c == '\0') || (c == '\r') || (c == '\n'))) { + flushed = 1; + continue; + } + + if ((c == '\r') || (c == '\n')) + break; + + flushed = 1; + + if (p < rh->buf + GETSTR_LENGTH) + { + if (do_echo && !is_term) + (void)write(out, &c, 1); + *p++ = c; + } + } + + *p = '\0'; + + if (!do_echo || !is_term) (void)write(out, "\r\n", 2); + + if (is_term) + tcsetattr (in, TCSAFLUSH, &term_old); + else { + if ((flags = fcntl(in, F_GETFL, 0)) >= 0) { + old_flags = flags; + flags |= O_NONBLOCK; + + fcntl(in, F_SETFL, flags); + + while (read(in, &c, 1) > 0) + /* nothing */; + + fcntl(in, F_SETFL, old_flags); + } + } + + (void) sigprocmask (SIG_SETMASK, &oldset, NULL); + + return rh->buf; +} +#endif +void rc_mdelay(int msecs) +{ + struct timeval tv; + + tv.tv_sec = (int) msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; + + select(0, NULL, NULL, NULL, &tv); +} + +/* + * Function: rc_mksid + * + * Purpose: generate a quite unique string + * + * Remarks: not that unique at all... + * + */ + +char * +rc_mksid (rc_handle *rh) +{ + snprintf (rh->buf1, sizeof(rh->buf1), "%08lX%04X", (unsigned long int) time (NULL), (unsigned int) getpid ()); + return rh->buf1; +} + +/* + * Function: rc_new + * + * Purpose: Initialises new Radius Client handle + * + */ + +rc_handle * +rc_new(void) +{ + rc_handle *rh; + + rh = malloc(sizeof(*rh)); + if (rh == NULL) { + rc_log(LOG_CRIT, "rc_new: out of memory"); + return NULL; + } + memset(rh, 0, sizeof(*rh)); + return rh; +} + +/* + * Function: rc_destroy + * + * Purpose: Destroys Radius Client handle reclaiming all memory + * + */ + +void +rc_destroy(rc_handle *rh) +{ + + rc_map2id_free(rh); + rc_dict_free(rh); + rc_config_free(rh); + if (rh->this_host_bind_ipaddr != NULL) + free(rh->this_host_bind_ipaddr); + free(rh); +} + +/* + * Function: rc_fgetln + * + * Purpose: Get next line from the stream. + * + */ + +char * +rc_fgetln(FILE *fp, size_t *len) +{ + static char *buf = NULL; + static size_t bufsiz = 0; + char *ptr; + + if (buf == NULL) { + bufsiz = RC_BUFSIZ; + if ((buf = malloc(bufsiz)) == NULL) + return NULL; + } + + if (fgets(buf, (int)bufsiz, fp) == NULL) + return NULL; + *len = 0; + + while ((ptr = strchr(&buf[*len], '\n')) == NULL) { + size_t nbufsiz = bufsiz + RC_BUFSIZ; + char *nbuf = realloc(buf, nbufsiz); + + if (nbuf == NULL) { + int oerrno = errno; + free(buf); + errno = oerrno; + buf = NULL; + return NULL; + } else + buf = nbuf; + + *len = bufsiz; + if (fgets(&buf[bufsiz], RC_BUFSIZ, fp) == NULL) + return buf; + + bufsiz = nbufsiz; + } + + *len = (ptr - buf) + 1; + return buf; +} + +/* + * Function: rc_getctime + * + * Purpose: Get current time (seconds since epoch) expressed as + * double-precision floating point number. + * + */ + +double +rc_getctime(void) +{ + struct timeval timev; + + if (gettimeofday(&timev, NULL) == -1) + return -1; + + return timev.tv_sec + ((double)timev.tv_usec) / 1000000.0; +} diff --git a/src/plugins/vbng/vbng.api b/src/plugins/vbng/vbng.api new file mode 100644 index 00000000..eba9a10f --- /dev/null +++ b/src/plugins/vbng/vbng.api @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017 Intel Corp and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \brief vBNG DHCP config add / del request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param rx_vrf_id - Rx/interface vrf id + @param server_vrf_id - server vrf id + @param is_add - add the config if non-zero, else delete + @param remote_addr[] - DHCP server address + @param local_addr[] - Local Address which could reach DHCP server +*/ +define vbng_dhcp4_config +{ + u32 client_index; + u32 context; + u32 rx_vrf_id; + u32 server_vrf_id; + u8 is_add; + u8 remote_addr[16]; + u8 local_addr[16]; +}; + +/** \brief vBNG DHCP config response + @param context - sender context, to match reply w/ request + @param retval - return code for the request +*/ +define vbng_dhcp4_config_reply +{ + u32 context; + i32 retval; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/vbng/vbng_aaa.c b/src/plugins/vbng/vbng_aaa.c new file mode 100644 index 00000000..5e8861f7 --- /dev/null +++ b/src/plugins/vbng/vbng_aaa.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 Intel and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include + +int +process(void *rh, VALUE_PAIR *send, int nas_port) +{ + VALUE_PAIR *received = NULL; + char msg[PW_MAX_MSG_SIZE]; + int retval; + + retval = rc_auth(rh, nas_port, send, &received, msg); + if (retval == OK_RC && received != NULL) { + rc_avpair_free(received); + } + + return retval; +} + +int +vbng_auth(vbng_dhcp4_main_t *dm, int argc, char **argv) +{ + int i, nas_port = dm->config.nas_port; + char *rc_conf = (char *)dm->config.config_file; + VALUE_PAIR *send, **vp; + void *rh; + + if ((rh = rc_read_config(rc_conf)) == NULL) { + fprintf(stderr, "error opening radius configuration file\n"); + return (1); + } + + if (rc_read_dictionary(rh, rc_conf_str(rh, "dictionary")) != 0) { + fprintf(stderr, "error reading radius dictionary\n"); + return (2); + } + + send = NULL; + vp = &send; + for (i = 0; i < argc; i++) { + if (rc_avpair_parse(rh, argv[i], vp) < 0) { + fprintf(stderr, "%s: can't parse AV pair\n", argv[i]); + return (3); + } + vp = &send->next; + } + + return process(rh, send, nas_port); +} + diff --git a/src/plugins/vbng/vbng_aaa.h b/src/plugins/vbng/vbng_aaa.h new file mode 100644 index 00000000..411a7533 --- /dev/null +++ b/src/plugins/vbng/vbng_aaa.h @@ -0,0 +1,34 @@ +/* + * vbng_aaa.h - vBNG FreeRADIUS client commons. + * + * Copyright (c) 2017 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _VBNG_AAA_H_ +#define _VBNG_AAA_H_ + +#include + +/* Common configuration for RADIUS client */ +#define AAA_DEFAULT_NAS_PORT 5060 +#define AAA_DEFAULT_CONFIG_FILE "/etc/vpp/vbng-aaa.cfg" + +#define BUF_LEN 4096 + +/* String template for the vAAA attributes */ +#define STR_TPL_ATTR_DHCP_AGENT_CIRCUIT_ID "DHCP-Agent-Circuit-Id=%c" +#define STR_TPL_ATTR_DHCP_AGENT_REMOTE_ID "DHCP-Agent-Remote-Id=%c" + +int vbng_auth(vbng_dhcp4_main_t *dm, int argc, char **argv); + +#endif /* _VBNG_AAA_H_ */ diff --git a/src/plugins/vbng/vbng_all_api_h.h b/src/plugins/vbng/vbng_all_api_h.h new file mode 100644 index 00000000..3f744275 --- /dev/null +++ b/src/plugins/vbng/vbng_all_api_h.h @@ -0,0 +1,18 @@ +/* + * vbng_all_api_h.h - skeleton vpp engine plug-in api #include file + * + * Copyright (c) 2017 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include diff --git a/src/plugins/vbng/vbng_api.c b/src/plugins/vbng/vbng_api.c new file mode 100644 index 00000000..4080f775 --- /dev/null +++ b/src/plugins/vbng/vbng_api.c @@ -0,0 +1,123 @@ +/* + *------------------------------------------------------------------ + * vbng_api.c - vbng api + * + * Copyright (c) 2017 Intel Corp and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +#define vl_endianfun /* define message structures */ +#include +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define vl_printfun +#include +#undef vl_printfun + +#include + +#define foreach_vpe_api_msg \ +_(VBNG_DHCP4_CONFIG,vbng_dhcp4_config) + +static void vl_api_vbng_dhcp4_config_t_handler + (vl_api_vbng_dhcp4_config_t * mp) +{ + vl_api_vbng_dhcp4_config_reply_t *rmp; + ip46_address_t src, server; + int rv = -1; + + ip46_address_reset (&src); + ip46_address_reset (&server); + + clib_memcpy (&src.ip4, mp->local_addr, sizeof (src.ip4)); + clib_memcpy (&server.ip4, mp->remote_addr, sizeof (server.ip4)); + + rv = dhcp4_proxy_set_server (&server, + &src, + (u32) ntohl (mp->rx_vrf_id), + (u32) ntohl (mp->server_vrf_id), + (int) (mp->is_add == 0)); + + + REPLY_MACRO (VL_API_VBNG_DHCP4_CONFIG_REPLY); +} + +/* + * vbng_api_hookup + * Add vpe's API message handlers to the table. + * vlib has alread mapped shared memory and + * added the client registration handlers. + * See .../vlib-api/vlibmemory/memclnt_vlib.c:memclnt_process() + */ +#define vl_msg_name_crc_list +#include +#undef vl_msg_name_crc_list + +static void +setup_message_id_table (api_main_t * am) +{ +#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id); + foreach_vl_msg_name_crc_vbng; +#undef _ +} + +static clib_error_t * +vbng_api_hookup (vlib_main_t * vm) +{ + api_main_t *am = &api_main; + +#define _(N,n) \ + vl_msg_api_set_handlers(VL_API_##N, #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_api_##n##_t_endian, \ + vl_api_##n##_t_print, \ + sizeof(vl_api_##n##_t), 1); + foreach_vpe_api_msg; +#undef _ + + /* + * Set up the (msg_name, crc, message-id) table + */ + setup_message_id_table (am); + + return 0; +} + +VLIB_API_INIT_FUNCTION (vbng_api_hookup); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/vbng/vbng_dhcp4.c b/src/plugins/vbng/vbng_dhcp4.c new file mode 100644 index 00000000..ed79df42 --- /dev/null +++ b/src/plugins/vbng/vbng_dhcp4.c @@ -0,0 +1,160 @@ +/* + * vbng_dhcp4.c: common dhcp v4 processing + * + * Copyright (c) 2017 Intel Corp and/or its affiliates and others. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +/** + * @brief Shard 4/6 instance of DHCP main + */ +vbng_dhcp4_main_t vbng_dhcp4_main; + +void +vbng_dhcp4_walk (vbng_dhcp4_walk_fn_t fn, + void *ctx) +{ + vbng_dhcp4_main_t *vdm = &vbng_dhcp4_main; + dhcp_proxy_t * server; + u32 server_index, i; + + vec_foreach_index (i, vdm->dhcp_server_index_by_rx_fib_index) + { + server_index = vdm->dhcp_server_index_by_rx_fib_index[i]; + if (~0 == server_index) + continue; + + server = pool_elt_at_index (vdm->dhcp4_servers, server_index); + + if (!fn(server, ctx)) + break; + } +} + +static u32 +dhcp_proxy_server_find (dhcp_proxy_t *proxy, + fib_protocol_t proto, + ip46_address_t *addr, + u32 server_table_id) +{ + dhcp_server_t *server; + u32 ii, fib_index; + + vec_foreach_index(ii, proxy->dhcp_servers) + { + server = &proxy->dhcp_servers[ii]; + fib_index = fib_table_find(proto, server_table_id); + + if (ip46_address_is_equal(&server->dhcp_server, + addr) && + (server->server_fib_index == fib_index)) + { + return (ii); + } + } + return (~0); +} + +int +vbng_dhcp4_server_del (fib_protocol_t proto, + u32 rx_fib_index, + ip46_address_t *addr, + u32 server_table_id) +{ + vbng_dhcp4_main_t *vdm = &vbng_dhcp4_main; + dhcp_proxy_t *proxy = 0; + + proxy = vbng_dhcp4_get_server(vdm, rx_fib_index); + + if (NULL != proxy) + { + dhcp_server_t *server; + u32 index; + + index = dhcp_proxy_server_find(proxy, proto, addr, server_table_id); + + if (~0 != index) + { + server = &proxy->dhcp_servers[index]; + fib_table_unlock (server->server_fib_index, proto); + + vec_del1(proxy->dhcp_servers, index); + + if (0 == vec_len(proxy->dhcp_servers)) + { + /* no servers left, delete the proxy config */ + vdm->dhcp_server_index_by_rx_fib_index[rx_fib_index] = ~0; + vec_free(proxy->dhcp_servers); + pool_put (vdm->dhcp4_servers, proxy); + return (1); + } + } + } + + /* the proxy still exists */ + return (0); +} + +int +vbng_dhcp4_server_add (fib_protocol_t proto, + ip46_address_t *addr, + ip46_address_t *src_address, + u32 rx_fib_index, + u32 server_table_id) +{ + vbng_dhcp4_main_t *vdm = &vbng_dhcp4_main; + dhcp_proxy_t * proxy = 0; + int new = 0; + + proxy = vbng_dhcp4_get_server(vdm, rx_fib_index); + + if (NULL == proxy) + { + vec_validate_init_empty(vdm->dhcp_server_index_by_rx_fib_index, + rx_fib_index, + ~0); + + pool_get (vdm->dhcp4_servers, proxy); + memset (proxy, 0, sizeof (*proxy)); + new = 1; + + vdm->dhcp_server_index_by_rx_fib_index[rx_fib_index] = + proxy - vdm->dhcp4_servers; + + proxy->dhcp_src_address = *src_address; + proxy->rx_fib_index = rx_fib_index; + } + else + { + if (~0 != dhcp_proxy_server_find(proxy, proto, addr, server_table_id)) + { + return (new); + } + } + + dhcp_server_t server = { + .dhcp_server = *addr, + .server_fib_index = fib_table_find_or_create_and_lock(proto, + server_table_id), + }; + + vec_add1(proxy->dhcp_servers, server); + + return (new); +} + diff --git a/src/plugins/vbng/vbng_dhcp4.h b/src/plugins/vbng/vbng_dhcp4.h new file mode 100644 index 00000000..2f41575f --- /dev/null +++ b/src/plugins/vbng/vbng_dhcp4.h @@ -0,0 +1,139 @@ +/* + * vbng_dhcp4.h: DHCP v4 common functions/types + * + * Copyright (c) 2017 Intel Corp and/or its affiliates and others. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _VBNG_DHCP4_H_ +#define _VBNG_DHCP4_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum { +#define vbng_dhcp4_error(n,s) VBNG_DHCP4_ERROR_##n, +#include +#undef vbng_dhcp4_error + VBNG_DHCP4_N_ERROR, +} vbng_dhcp4_error_t; + +#define VBNG_AAA_DISABLED 0 +#define VBNG_AAA_ENABLED 1 + +typedef struct { + int is_enabled; + u32 nas_port; /* AAA server port */ + u8 *config_file; /* Radius Client config file path */ +} vbng_aaa_config_t; + +/** + * @brief Global configuration for the vBNG plugin. + */ +typedef struct { + /* Pool of DHCP servers */ + dhcp_proxy_t *dhcp4_servers; + + /* Pool of selected DHCP server. Zero is the default server */ + u32 * dhcp_server_index_by_rx_fib_index; + + /* to drop pkts in server-to-client direction */ + u32 error_drop_node_index; + + /* Configuration for the AAA client */ + vbng_aaa_config_t config; + + /* convenience */ + vlib_main_t * vlib_main; + vnet_main_t * vnet_main; +} vbng_dhcp4_main_t; + +extern vbng_dhcp4_main_t vbng_dhcp4_main; + +/** + * @brief Add a new DHCP proxy server configuration. + * @return 1 is the config is new, + * 0 otherwise (implying a modify of an existing) + */ +int vbng_dhcp4_server_add(fib_protocol_t proto, + ip46_address_t *addr, + ip46_address_t *src_address, + u32 rx_fib_iindex, + u32 server_table_id); + +/** + * @brief Delete a DHCP proxy config + * @return 1 if the proxy is deleted, 0 otherwise + */ +int vbng_dhcp4_server_del(fib_protocol_t proto, + u32 rx_fib_index, + ip46_address_t *addr, + u32 server_table_id); + +u32 +dhcp_proxy_rx_table_get_table_id (fib_protocol_t proto, + u32 fib_index); + +/** + * @brief Callback function invoked for each DHCP proxy entry + * return 0 to break the walk, non-zero otherwise. + */ +typedef int (*vbng_dhcp4_walk_fn_t)(dhcp_proxy_t *server, + void *ctx); + +/** + * @brief Walk/Visit each vBNG DHCP server configurations + */ +void vbng_dhcp4_walk(vbng_dhcp4_walk_fn_t fn, + void *ctx); + +/** + * @brief Get the DHCP proxy server data for the FIB index + */ +static inline dhcp_proxy_t * +vbng_dhcp4_get_server(vbng_dhcp4_main_t *vm, + u32 rx_fib_index) +{ + dhcp_proxy_t *s = NULL; + + if (vec_len(vm->dhcp_server_index_by_rx_fib_index) > rx_fib_index && + vm->dhcp_server_index_by_rx_fib_index[rx_fib_index] != ~0) + { + s = pool_elt_at_index ( + vm->dhcp4_servers, + vm->dhcp_server_index_by_rx_fib_index[rx_fib_index]); + } + + return (s); +} + +int vbng_dhcp4_set_server(ip46_address_t *addr, + ip46_address_t *src_addr, + u32 rx_table_id, + u32 server_table_id, + int is_del); + +#define DHCP_PACKET_OPTION_82 82 +#define DHCP_PACKET_OPTION82_SUB1 1 +#define DHCP_PACKET_OPTION82_SUB2 2 +#define DHCP_PACKET_OPTION82_SUB5 5 + +#endif /* _VBNG_DHCP4_H_ */ diff --git a/src/plugins/vbng/vbng_dhcp4_err.def b/src/plugins/vbng/vbng_dhcp4_err.def new file mode 100644 index 00000000..23f2d0d2 --- /dev/null +++ b/src/plugins/vbng/vbng_dhcp4_err.def @@ -0,0 +1,29 @@ +/* + * vbng_dhcp4_err.def: VBNG DHCP4 Errors + * + * Copyright (c) 2017 Intel Corp and/or its affiliates and others. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +vbng_dhcp4_error (NONE, "no error") +vbng_dhcp4_error (NO_SERVER, "no dhcp server configured") +vbng_dhcp4_error (RELAY_TO_SERVER, "DHCP packets relayed to the server") +vbng_dhcp4_error (RELAY_TO_CLIENT, "DHCP packets relayed to clients") +vbng_dhcp4_error (NO_INTERFACE_ADDRESS, "DHCP no interface address") +vbng_dhcp4_error (BAD_YIADDR, "DHCP packets with bad your_ip_address fields") +vbng_dhcp4_error (BAD_SVR_FIB_OR_ADDRESS, "DHCP packets not from DHCP server or server FIB.") +vbng_dhcp4_error (PKT_TOO_BIG, "DHCP packets which are too big.") +vbng_dhcp4_error (AAA_FAILURE, "DHCP packets failed to pass the AAA check.") +vbng_dhcp4_error (NO_OPTION_82, "DHCP option 82 missing") +vbng_dhcp4_error (BAD_OPTION_82_ITF, "Bad DHCP option 82 interface value") +vbng_dhcp4_error (BAD_OPTION_82_ADDR, "Bad DHCP option 82 address value") diff --git a/src/plugins/vbng/vbng_dhcp4_node.c b/src/plugins/vbng/vbng_dhcp4_node.c new file mode 100644 index 00000000..205959bf --- /dev/null +++ b/src/plugins/vbng/vbng_dhcp4_node.c @@ -0,0 +1,1024 @@ +/* + * proxy_node.c: dhcp proxy node processing + * + * Copyright (c) 2013 Cisco and/or its affiliates and others. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +static char * vbng_dhcp4_error_strings[] = { +#define vbng_dhcp4_error(n,s) s, +#include +#undef vbng_dhcp4_error +}; + +#define foreach_vbng_dhcp4_to_server_input_next \ + _ (DROP, "error-drop") \ + _ (LOOKUP, "ip4-lookup") \ + _ (SEND_TO_CLIENT, "vbng-dhcp-to-client") + +typedef enum { +#define _(s,n) VBNG_DHCP4_TO_SERVER_INPUT_NEXT_##s, + foreach_vbng_dhcp4_to_server_input_next +#undef _ + VBNG_DHCP4_TO_SERVER_INPUT_N_NEXT, +} vbng_dhcp4_to_server_input_next_t; + +typedef struct { + /* 0 => to server, 1 => to client */ + int which; + ip4_address_t trace_ip4_address; + u32 error; + u32 sw_if_index; + u32 original_sw_if_index; +} dhcp_proxy_trace_t; + +#define VPP_DHCP_OPTION82_SUB1_SIZE 6 +#define VPP_DHCP_OPTION82_SUB5_SIZE 6 +#define VPP_DHCP_OPTION82_VSS_SIZE 12 +#define VPP_DHCP_OPTION82_SIZE (VPP_DHCP_OPTION82_SUB1_SIZE + \ + VPP_DHCP_OPTION82_SUB5_SIZE + \ + VPP_DHCP_OPTION82_VSS_SIZE +3) + +static vlib_node_registration_t vbng_dhcp4_to_server_node; +static vlib_node_registration_t vbng_dhcp4_to_client_node; + +static u8 * +format_dhcp_proxy_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + dhcp_proxy_trace_t * t = va_arg (*args, dhcp_proxy_trace_t *); + + if (t->which == 0) + s = format (s, "DHCP proxy: sent to server %U\n", + format_ip4_address, &t->trace_ip4_address, t->error); + else + s = format (s, "DHCP proxy: broadcast to client from %U\n", + format_ip4_address, &t->trace_ip4_address); + + if (t->error != (u32)~0) + s = format (s, " error: %s\n", vbng_dhcp4_error_strings[t->error]); + + s = format (s, " original_sw_if_index: %d, sw_if_index: %d\n", + t->original_sw_if_index, t->sw_if_index); + + return s; +} + +static u8 * +format_dhcp_proxy_header_with_length (u8 * s, va_list * args) +{ + dhcp_header_t * h = va_arg (*args, dhcp_header_t *); + u32 max_header_bytes = va_arg (*args, u32); + u32 header_bytes; + + header_bytes = sizeof (h[0]); + if (max_header_bytes != 0 && header_bytes > max_header_bytes) + return format (s, "dhcp header truncated"); + + s = format (s, "DHCP Proxy"); + + return s; +} + +static uword +vbng_dhcp_to_server_input (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + u32 n_left_from, next_index, * from, * to_next; + vbng_dhcp4_main_t *dm = &vbng_dhcp4_main; + from = vlib_frame_vector_args (from_frame); + n_left_from = from_frame->n_vectors; + u32 pkts_to_server=0, pkts_to_client=0, pkts_no_server=0; + u32 pkts_no_interface_address=0; + u32 pkts_too_big=0, pkts_aaa_fail = 0; + ip4_main_t * im = &ip4_main; + + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t * b0; + udp_header_t * u0; + dhcp_header_t * h0; + ip4_header_t * ip0; + u32 next0; + u32 old0, new0; + ip_csum_t sum0; + u32 error0 = (u32) ~0; + u32 sw_if_index = 0; + u32 original_sw_if_index = 0; + u8 *end = NULL; + u32 fib_index; + dhcp_proxy_t *proxy; + dhcp_server_t *server; + u32 rx_sw_if_index; + dhcp_option_t *o; + u32 len = 0; + vlib_buffer_free_list_t *fl; + u8 is_discover = 0; + + bi0 = from[0]; + from += 1; + n_left_from -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + h0 = vlib_buffer_get_current (b0); + + /* + * udp_local hands us the DHCP header, need udp hdr, + * ip hdr to relay to server + */ + vlib_buffer_advance (b0, -(sizeof(*u0))); + u0 = vlib_buffer_get_current (b0); + + /* This blows. Return traffic has src_port = 67, dst_port = 67 */ + if (u0->src_port == clib_net_to_host_u16(UDP_DST_PORT_dhcp_to_server)) + { + vlib_buffer_advance (b0, sizeof(*u0)); + next0 = VBNG_DHCP4_TO_SERVER_INPUT_NEXT_SEND_TO_CLIENT; + error0 = 0; + pkts_to_client++; + goto do_enqueue; + } + + rx_sw_if_index = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + + fib_index = im->fib_index_by_sw_if_index [rx_sw_if_index]; + proxy = vbng_dhcp4_get_server(dm, fib_index); + + if (PREDICT_FALSE (NULL == proxy)) + { + error0 = VBNG_DHCP4_ERROR_NO_SERVER; + next0 = VBNG_DHCP4_TO_SERVER_INPUT_NEXT_DROP; + pkts_no_server++; + goto do_trace; + } + + server = &proxy->dhcp_servers[0]; + vlib_buffer_advance (b0, -(sizeof(*ip0))); + ip0 = vlib_buffer_get_current (b0); + + /* disable UDP checksum */ + u0->checksum = 0; + sum0 = ip0->checksum; + old0 = ip0->dst_address.as_u32; + new0 = server->dhcp_server.ip4.as_u32; + ip0->dst_address.as_u32 = server->dhcp_server.ip4.as_u32; + sum0 = ip_csum_update (sum0, old0, new0, + ip4_header_t /* structure */, + dst_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + sum0 = ip0->checksum; + old0 = ip0->src_address.as_u32; + new0 = proxy->dhcp_src_address.ip4.as_u32; + ip0->src_address.as_u32 = new0; + sum0 = ip_csum_update (sum0, old0, new0, + ip4_header_t /* structure */, + src_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + /* Send to DHCP server via the configured FIB */ + vnet_buffer(b0)->sw_if_index[VLIB_TX] = + server->server_fib_index; + + h0->gateway_ip_address.as_u32 = proxy->dhcp_src_address.ip4.as_u32; + pkts_to_server++; + + o = (dhcp_option_t *) h0->options; + + fib_index = im->fib_index_by_sw_if_index + [vnet_buffer(b0)->sw_if_index[VLIB_RX]]; + + end = b0->data + b0->current_data + b0->current_length; + /* TLVs are not performance-friendly... */ + while (o->option != 0xFF /* end of options */ && (u8 *)o < end) + { + if (DHCP_PACKET_OPTION_MSG_TYPE == o->option) + { + if (DHCP_PACKET_DISCOVER == o->data[0]) + { + is_discover = 1; + } + } + + if (DHCP_PACKET_OPTION_82 == o->option) { + /* For Demo purpose only */ + if (dm->config.is_enabled) { + int i = 0, num_kvs = 0, retval = 0; + char *kv_pairs[1]; + char key_string[32]; + + if (DHCP_PACKET_OPTION82_SUB1 == o->data[0]) { + sprintf(key_string, STR_TPL_ATTR_DHCP_AGENT_CIRCUIT_ID, o->data[2]); + for (i = 1; i < o->data[1]; i++) { + sprintf(key_string, "%s%c", key_string, o->data[2 + i]); + } + } + + if (DHCP_PACKET_OPTION82_SUB2 == o->data[0]) { + sprintf(key_string, STR_TPL_ATTR_DHCP_AGENT_REMOTE_ID, o->data[2]); + for (i = 1; i < o->data[1]; i++) { + sprintf(key_string, "%s%c", key_string, o->data[2 + i]); + } + } + + kv_pairs[num_kvs] = key_string; + num_kvs++; + + retval = vbng_auth(dm, num_kvs, kv_pairs); + if (retval) { + if (retval == 1 /* TIMEOUT_RC */) { + dm->config.is_enabled = VBNG_AAA_DISABLED; + } + error0 = VBNG_DHCP4_ERROR_AAA_FAILURE; + next0 = VBNG_DHCP4_TO_SERVER_INPUT_NEXT_DROP; + pkts_aaa_fail++; + goto do_trace; + } + } + + fl = vlib_buffer_get_free_list (vm, b0->free_list_index); + if (((u8 *)o - (u8 *)b0->data + (VPP_DHCP_OPTION82_SUB1_SIZE + VPP_DHCP_OPTION82_SUB5_SIZE)) + > fl->n_data_bytes) + { + next0 = VBNG_DHCP4_TO_SERVER_INPUT_NEXT_DROP; + pkts_too_big++; + goto do_trace; + } + + /* Begin to appending new sub-options */ + { + vnet_main_t *vnm = vnet_get_main(); + u16 old_l0, new_l0, orig_len = 0; + ip4_address_t _ia0, * ia0 = &_ia0; + vnet_sw_interface_t *swif; + sw_if_index = 0; + original_sw_if_index = 0; + + original_sw_if_index = sw_if_index = + vnet_buffer(b0)->sw_if_index[VLIB_RX]; + swif = vnet_get_sw_interface (vnm, sw_if_index); + if (swif->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED) + sw_if_index = swif->unnumbered_sw_if_index; + + /* + * Get the first ip4 address on the [client-side] + * RX interface, if not unnumbered. otherwise use + * the loopback interface's ip address. + */ + ia0 = ip4_interface_first_address(&ip4_main, sw_if_index, 0); + + if (ia0 == 0) + { + error0 = VBNG_DHCP4_ERROR_NO_INTERFACE_ADDRESS; + next0 = VBNG_DHCP4_TO_SERVER_INPUT_NEXT_DROP; + pkts_no_interface_address++; + goto do_trace; + } + + orig_len = o->length; + o->data[orig_len + 0] = 1; /* suboption 1, circuit ID (=FIB id) */ + o->data[orig_len + 1] = 4; /* length of suboption */ + o->data[orig_len + 2] = (original_sw_if_index >> 24) & 0xFF; + o->data[orig_len + 3] = (original_sw_if_index >> 16) & 0xFF; + o->data[orig_len + 4] = (original_sw_if_index >> 8) & 0xFF; + o->data[orig_len + 5] = (original_sw_if_index >> 0) & 0xFF; + o->data[orig_len + 6] = 5; /* suboption 5 (client RX intfc address) */ + o->data[orig_len + 7] = 4; /* length 4 */ + o->data[orig_len + 8] = ia0->as_u8[0]; + o->data[orig_len + 9] = ia0->as_u8[1]; + o->data[orig_len + 10] = ia0->as_u8[2]; + o->data[orig_len + 11] = ia0->as_u8[3]; + o->data[orig_len + 12] = 0xFF; + o->length += 12; /* 12 octets appended*/ + + len = o->length + 3; + b0->current_length += len; + /* Fix IP header length and checksum */ + old_l0 = ip0->length; + new_l0 = clib_net_to_host_u16 (old_l0); + new_l0 += len; + new_l0 = clib_host_to_net_u16 (new_l0); + ip0->length = new_l0; + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_l0, new_l0, ip4_header_t, + length /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + /* Fix UDP length */ + new_l0 = clib_net_to_host_u16 (u0->length); + new_l0 += len; + u0->length = clib_host_to_net_u16 (new_l0); + } + } + + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + } + + next0 = VBNG_DHCP4_TO_SERVER_INPUT_NEXT_LOOKUP; + + /* + * If we have multiple servers configured and this is the + * client's discover message, then send copies to each of + * those servers + */ + if (is_discover && vec_len(proxy->dhcp_servers) > 1) + { + u32 ii; + + for (ii = 1; ii < vec_len(proxy->dhcp_servers); ii++) + { + vlib_buffer_t *c0; + u32 ci0; + + c0 = vlib_buffer_copy(vm, b0); + ci0 = vlib_get_buffer_index(vm, c0); + server = &proxy->dhcp_servers[ii]; + + ip0 = vlib_buffer_get_current (c0); + + sum0 = ip0->checksum; + old0 = ip0->dst_address.as_u32; + new0 = server->dhcp_server.ip4.as_u32; + ip0->dst_address.as_u32 = server->dhcp_server.ip4.as_u32; + sum0 = ip_csum_update (sum0, old0, new0, + ip4_header_t /* structure */, + dst_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + to_next[0] = ci0; + to_next += 1; + n_left_to_next -= 1; + + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + ci0, next0); + + if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_proxy_trace_t *tr; + + tr = vlib_add_trace (vm, node, c0, sizeof (*tr)); + tr->which = 0; /* to server */ + tr->error = error0; + tr->original_sw_if_index = original_sw_if_index; + tr->sw_if_index = sw_if_index; + if (next0 == VBNG_DHCP4_TO_SERVER_INPUT_NEXT_LOOKUP) + tr->trace_ip4_address.as_u32 = server->dhcp_server.ip4.as_u32; + } + + if (PREDICT_FALSE(0 == n_left_to_next)) + { + vlib_put_next_frame (vm, node, next_index, + n_left_to_next); + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + } + } + } + do_trace: + if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_proxy_trace_t *tr = vlib_add_trace (vm, node, + b0, sizeof (*tr)); + tr->which = 0; /* to server */ + tr->error = error0; + tr->original_sw_if_index = original_sw_if_index; + tr->sw_if_index = sw_if_index; + if (next0 == VBNG_DHCP4_TO_SERVER_INPUT_NEXT_LOOKUP) + tr->trace_ip4_address.as_u32 = + proxy->dhcp_servers[0].dhcp_server.ip4.as_u32; + } + + do_enqueue: + to_next[0] = bi0; + to_next += 1; + n_left_to_next -= 1; + + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_node_increment_counter (vm, vbng_dhcp4_to_server_node.index, + VBNG_DHCP4_ERROR_RELAY_TO_CLIENT, + pkts_to_client); + vlib_node_increment_counter (vm, vbng_dhcp4_to_server_node.index, + VBNG_DHCP4_ERROR_RELAY_TO_SERVER, + pkts_to_server); + vlib_node_increment_counter (vm, vbng_dhcp4_to_server_node.index, + VBNG_DHCP4_ERROR_NO_SERVER, + pkts_no_server); + vlib_node_increment_counter (vm, vbng_dhcp4_to_server_node.index, + VBNG_DHCP4_ERROR_NO_INTERFACE_ADDRESS, + pkts_no_interface_address); + vlib_node_increment_counter (vm, vbng_dhcp4_to_server_node.index, + VBNG_DHCP4_ERROR_PKT_TOO_BIG, + pkts_too_big); + vlib_node_increment_counter (vm, vbng_dhcp4_to_server_node.index, + VBNG_DHCP4_ERROR_AAA_FAILURE, + pkts_aaa_fail); + return from_frame->n_vectors; +} + +VLIB_REGISTER_NODE (vbng_dhcp4_to_server_node, static) = { + .function = vbng_dhcp_to_server_input, + .name = "vbng-dhcp-to-server", + /* Takes a vector of packets. */ + .vector_size = sizeof (u32), + + .n_errors = VBNG_DHCP4_N_ERROR, + .error_strings = vbng_dhcp4_error_strings, + + .n_next_nodes = VBNG_DHCP4_TO_SERVER_INPUT_N_NEXT, + .next_nodes = { +#define _(s,n) [VBNG_DHCP4_TO_SERVER_INPUT_NEXT_##s] = n, + foreach_vbng_dhcp4_to_server_input_next +#undef _ + }, + + .format_buffer = format_dhcp_proxy_header_with_length, + .format_trace = format_dhcp_proxy_trace, +}; + +static uword +vbng_dhcp_to_client_input (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + u32 n_left_from, * from; + ethernet_main_t *em = ethernet_get_main (vm); + vbng_dhcp4_main_t *dm = &vbng_dhcp4_main; + vnet_main_t * vnm = vnet_get_main(); + ip4_main_t * im = &ip4_main; + + from = vlib_frame_vector_args (from_frame); + n_left_from = from_frame->n_vectors; + + while (n_left_from > 0) + { + u32 bi0; + vlib_buffer_t * b0; + udp_header_t * u0; + dhcp_header_t * h0; + ip4_header_t * ip0 = 0; + ip4_address_t * ia0 = 0; + u32 old0, new0; + ip_csum_t sum0; + ethernet_interface_t *ei0; + ethernet_header_t *mac0; + vnet_hw_interface_t *hi0; + vlib_frame_t *f0; + u32 * to_next0; + u32 sw_if_index = ~0; + vnet_sw_interface_t *si0; + u32 error0 = (u32)~0; + vnet_sw_interface_t *swif; + u32 fib_index; + dhcp_proxy_t *proxy; + dhcp_server_t *server; + u32 original_sw_if_index = (u32) ~0; + ip4_address_t relay_addr = { + .as_u32 = 0, + }; + + bi0 = from[0]; + from += 1; + n_left_from -= 1; + + b0 = vlib_get_buffer (vm, bi0); + h0 = vlib_buffer_get_current (b0); + + /* + * udp_local hands us the DHCP header, need udp hdr, + * ip hdr to relay to client + */ + vlib_buffer_advance (b0, -(sizeof(*u0))); + u0 = vlib_buffer_get_current (b0); + + vlib_buffer_advance (b0, -(sizeof(*ip0))); + ip0 = vlib_buffer_get_current (b0); + + { + dhcp_option_t *o = (dhcp_option_t *) h0->options; + dhcp_option_t *sub; + + /* Parse through TLVs looking for option 82. + The circuit-ID is the FIB number we need + to track down the client-facing interface */ + + while (o->option != 0xFF /* end of options */ && + (u8 *) o < (b0->data + b0->current_data + b0->current_length)) + { + if (o->option == 82) + { + sub = (dhcp_option_t *) &o->data[0]; + while (sub->option != 0xFF /* end of options */ && + (u8 *) sub < (u8 *)(o + o->length)) { + /* If this is one of ours, it will have + total length 12, circuit-id suboption type, + and the sw_if_index */ + if (sub->option == 1 && sub->length == 4) + { + sw_if_index = ((sub->data[0] << 24) | + (sub->data[1] << 16) | + (sub->data[2] << 8) | + (sub->data[3])); + } + else if (sub->option == 5 && sub->length == 4) + { + relay_addr.as_u8[0] = sub->data[0]; + relay_addr.as_u8[1] = sub->data[1]; + relay_addr.as_u8[2] = sub->data[2]; + relay_addr.as_u8[3] = sub->data[3]; + } + sub = (dhcp_option_t *) + (((uword) sub) + (sub->length + 2)); + } + + } + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + } + } + + if (sw_if_index == (u32)~0) + { + error0 = VBNG_DHCP4_ERROR_NO_OPTION_82; + + drop_packet: + vlib_node_increment_counter (vm, vbng_dhcp4_to_client_node.index, + error0, 1); + f0 = vlib_get_frame_to_node (vm, dm->error_drop_node_index); + to_next0 = vlib_frame_vector_args (f0); + to_next0[0] = bi0; + f0->n_vectors = 1; + vlib_put_frame_to_node (vm, dm->error_drop_node_index, f0); + goto do_trace; + } + + if (relay_addr.as_u32 == 0) + { + error0 = VBNG_DHCP4_ERROR_BAD_OPTION_82_ADDR; + goto drop_packet; + } + + if (sw_if_index >= vec_len (im->fib_index_by_sw_if_index)) + { + error0 = VBNG_DHCP4_ERROR_BAD_OPTION_82_ITF; + goto drop_packet; + } + + fib_index = im->fib_index_by_sw_if_index [sw_if_index]; + proxy = vbng_dhcp4_get_server(dm, fib_index); + + if (PREDICT_FALSE (NULL == proxy)) + { + error0 = VBNG_DHCP4_ERROR_NO_SERVER; + goto drop_packet; + } + + vec_foreach(server, proxy->dhcp_servers) + { + if (ip0->src_address.as_u32 == server->dhcp_server.ip4.as_u32) + { + goto server_found; + } + } + + error0 = VBNG_DHCP4_ERROR_BAD_SVR_FIB_OR_ADDRESS; + goto drop_packet; + + server_found: + vnet_buffer (b0)->sw_if_index[VLIB_TX] = sw_if_index; + + swif = vnet_get_sw_interface (vnm, sw_if_index); + original_sw_if_index = sw_if_index; + if (swif->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED) + sw_if_index = swif->unnumbered_sw_if_index; + + ia0 = ip4_interface_first_address (&ip4_main, sw_if_index, 0); + if (ia0 == 0) + { + error0 = VBNG_DHCP4_ERROR_NO_INTERFACE_ADDRESS; + goto drop_packet; + } + + if (relay_addr.as_u32 != ia0->as_u32) + { + error0 = VBNG_DHCP4_ERROR_BAD_YIADDR; + goto drop_packet; + } + + u0->checksum = 0; + u0->dst_port = clib_net_to_host_u16 (UDP_DST_PORT_dhcp_to_client); + sum0 = ip0->checksum; + old0 = ip0->dst_address.as_u32; + new0 = 0xFFFFFFFF; + ip0->dst_address.as_u32 = new0; + sum0 = ip_csum_update (sum0, old0, new0, + ip4_header_t /* structure */, + dst_address /* offset of changed member */); + ip0->checksum = ip_csum_fold (sum0); + + sum0 = ip0->checksum; + old0 = ip0->src_address.as_u32; + new0 = ia0->as_u32; + ip0->src_address.as_u32 = new0; + sum0 = ip_csum_update (sum0, old0, new0, + ip4_header_t /* structure */, + src_address /* offset of changed member */); + ip0->checksum = ip_csum_fold (sum0); + + vlib_buffer_advance (b0, -(sizeof(ethernet_header_t))); + si0 = vnet_get_sw_interface (vnm, original_sw_if_index); + if (si0->type == VNET_SW_INTERFACE_TYPE_SUB) + vlib_buffer_advance (b0, -4 /* space for VLAN tag */); + + mac0 = vlib_buffer_get_current (b0); + + hi0 = vnet_get_sup_hw_interface (vnm, original_sw_if_index); + ei0 = pool_elt_at_index (em->interfaces, hi0->hw_instance); + clib_memcpy (mac0->src_address, ei0->address, sizeof (ei0->address)); + memset (mac0->dst_address, 0xff, sizeof (mac0->dst_address)); + mac0->type = (si0->type == VNET_SW_INTERFACE_TYPE_SUB) ? + clib_net_to_host_u16(0x8100) : clib_net_to_host_u16 (0x0800); + + if (si0->type == VNET_SW_INTERFACE_TYPE_SUB) + { + u32 * vlan_tag = (u32 *)(mac0+1); + u32 tmp; + tmp = (si0->sub.id << 16) | 0x0800; + *vlan_tag = clib_host_to_net_u32 (tmp); + } + + /* $$$ This needs to be rewritten, for sure */ + f0 = vlib_get_frame_to_node (vm, hi0->output_node_index); + to_next0 = vlib_frame_vector_args (f0); + to_next0[0] = bi0; + f0->n_vectors = 1; + vlib_put_frame_to_node (vm, hi0->output_node_index, f0); + + do_trace: + if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_proxy_trace_t *tr = vlib_add_trace (vm, node, + b0, sizeof (*tr)); + tr->which = 1; /* to client */ + tr->trace_ip4_address.as_u32 = ia0 ? ia0->as_u32 : 0; + tr->error = error0; + tr->original_sw_if_index = original_sw_if_index; + tr->sw_if_index = sw_if_index; + } + } + return from_frame->n_vectors; +} + +VLIB_REGISTER_NODE (vbng_dhcp4_to_client_node, static) = { + .function = vbng_dhcp_to_client_input, + .name = "vbng-dhcp-to-client", + /* Takes a vector of packets. */ + .vector_size = sizeof (u32), + + .n_errors = VBNG_DHCP4_N_ERROR, + .error_strings = vbng_dhcp4_error_strings, + .format_buffer = format_dhcp_proxy_header_with_length, + .format_trace = format_dhcp_proxy_trace, +}; + +static clib_error_t * +vbng_dhcp4_proxy_init (vlib_main_t * vm) +{ + vbng_dhcp4_main_t *dm = &vbng_dhcp4_main; + vlib_node_t * error_drop_node; + + error_drop_node = vlib_get_node_by_name (vm, (u8 *) "error-drop"); + dm->error_drop_node_index = error_drop_node->index; + + udp_register_dst_port (vm, UDP_DST_PORT_dhcp_to_client, + vbng_dhcp4_to_client_node.index, 1 /* is_ip4 */); + + udp_register_dst_port (vm, UDP_DST_PORT_dhcp_to_server, + vbng_dhcp4_to_server_node.index, 1 /* is_ip4 */); + + dm->vlib_main = vm; + dm->vnet_main = vnet_get_main(); + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_INIT_FUNCTION (vbng_dhcp4_proxy_init); +/* *INDENT-ON* */ + +int +vbng_dhcp4_set_server (ip46_address_t *addr, + ip46_address_t *src_addr, + u32 rx_table_id, + u32 server_table_id, + int is_del) +{ + u32 rx_fib_index = 0; + int rc = 0; + + const fib_prefix_t all_1s = + { + .fp_len = 32, + .fp_addr.ip4.as_u32 = 0xffffffff, + .fp_proto = FIB_PROTOCOL_IP4, + }; + + if (ip46_address_is_zero(addr)) + return VNET_API_ERROR_INVALID_DST_ADDRESS; + + if (ip46_address_is_zero(src_addr)) + return VNET_API_ERROR_INVALID_SRC_ADDRESS; + + rx_fib_index = fib_table_find_or_create_and_lock(FIB_PROTOCOL_IP4, + rx_table_id); + + if (is_del) + { + if (vbng_dhcp4_server_del (FIB_PROTOCOL_IP4, rx_fib_index, + addr, server_table_id)) + { + fib_table_entry_special_remove(rx_fib_index, + &all_1s, + FIB_SOURCE_DHCP); + fib_table_unlock (rx_fib_index, FIB_PROTOCOL_IP4); + } + } + else + { + if (vbng_dhcp4_server_add (FIB_PROTOCOL_IP4, + addr, src_addr, + rx_fib_index, server_table_id)) + { + fib_table_entry_special_add(rx_fib_index, + &all_1s, + FIB_SOURCE_DHCP, + FIB_ENTRY_FLAG_LOCAL); + fib_table_lock (rx_fib_index, FIB_PROTOCOL_IP4); + } + } + fib_table_unlock (rx_fib_index, FIB_PROTOCOL_IP4); + + return (rc); +} + +static clib_error_t * +dhcp4_proxy_set_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + ip46_address_t server_addr, src_addr; + u32 server_table_id = 0, rx_table_id = 0; + int is_del = 0; + int set_src = 0, set_server = 0; + + memset(&server_addr, 0, sizeof(server_addr)); + memset(&src_addr, 0, sizeof(src_addr)); + + while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "remote %U", + unformat_ip4_address, &server_addr.ip4)) + set_server = 1; + else if (unformat (input, "server-fib-id %d", &server_table_id)) + ; + else if (unformat (input, "rx-fib-id %d", &rx_table_id)) + ; + else if (unformat(input, "local %U", + unformat_ip4_address, &src_addr.ip4)) + set_src = 1; + else if (unformat (input, "delete") || + unformat (input, "del")) + is_del = 1; + else + break; + } + + if (is_del || (set_server && set_src)) + { + int rv; + + rv = vbng_dhcp4_set_server (&server_addr, &src_addr, rx_table_id, + server_table_id, is_del); + switch (rv) + { + case 0: + return 0; + + case VNET_API_ERROR_INVALID_DST_ADDRESS: + return clib_error_return (0, "Invalid remote address"); + + case VNET_API_ERROR_INVALID_SRC_ADDRESS: + return clib_error_return (0, "Invalid local address"); + + case VNET_API_ERROR_NO_SUCH_ENTRY: + return clib_error_return + (0, "Fib id %d: no per-fib DHCP server configured", rx_table_id); + + default: + return clib_error_return (0, "BUG: rv %d", rv); + } + } + else + return clib_error_return (0, "parse error`%U'", + format_unformat_error, input); +} + +VLIB_CLI_COMMAND (vbng_dhcp4_set_command, static) = { + .path = "set vbng dhcp4", + .short_help = "set vbng dhcp4 [del] remote local [server-fib-id ] [rx-fib-id ]", + .function = dhcp4_proxy_set_command_fn, +}; + +static u8 * +format_dhcp4_proxy_server (u8 * s, va_list * args) +{ + dhcp_proxy_t *proxy = va_arg (*args, dhcp_proxy_t *); + ip4_fib_t * rx_fib, * server_fib; + dhcp_server_t *server; + + if (proxy == 0) + { + s = format (s, "%=14s%=16s%s", "RX FIB", "Src Address", + "Servers FIB,Address"); + return s; + } + + rx_fib = ip4_fib_get(proxy->rx_fib_index); + + s = format (s, "%=14u%=16U", + rx_fib->table_id, + format_ip46_address, &proxy->dhcp_src_address, IP46_TYPE_ANY); + + vec_foreach(server, proxy->dhcp_servers) + { + server_fib = ip4_fib_get(server->server_fib_index); + s = format (s, "%u,%U ", + server_fib->table_id, + format_ip46_address, &server->dhcp_server, IP46_TYPE_ANY); + } + return s; +} + +static int +dhcp4_proxy_show_walk (dhcp_proxy_t *server, + void *ctx) +{ + vlib_main_t * vm = ctx; + + vlib_cli_output (vm, "%U", format_dhcp4_proxy_server, server); + + return (1); +} + +static clib_error_t * +vbng_dhcp4_show_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + vlib_cli_output (vm, "%U", format_dhcp4_proxy_server, NULL /* header line */); + + vbng_dhcp4_walk(dhcp4_proxy_show_walk, vm); + + return (NULL); +} + +VLIB_CLI_COMMAND (vbng_dhcp4_show_command, static) = { + .path = "show vbng dhcp4", + .short_help = "Display vbng DHCP4 configuration info", + .function = vbng_dhcp4_show_command_fn, +}; + +static clib_error_t * +vbng_aaa_set_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + vbng_dhcp4_main_t *dm = &vbng_dhcp4_main; + u8 *config_file = NULL; + u32 nas_port = 0; + int set_config = 0, is_del = 0; + + while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT) { + if (unformat (input, "nas-port %d", &nas_port)) + ; + else if (unformat (input, "config %v", &config_file)) + set_config = 1; + else if (unformat (input, "delete") || + unformat (input, "del")) + is_del = 1; + else + break; + } + + if (!is_del && set_config) { + if (dm->config.is_enabled == VBNG_AAA_ENABLED) { + return 0; + } + + if (nas_port) { + dm->config.nas_port = nas_port; + } else { + dm->config.nas_port = AAA_DEFAULT_NAS_PORT; + } + dm->config.config_file = config_file; + dm->config.is_enabled = VBNG_AAA_ENABLED; + } else if (is_del) { + if (dm->config.is_enabled == VBNG_AAA_DISABLED) { + return 0; + } + + vec_free (dm->config.config_file); + dm->config.config_file = format(0, "%s", AAA_DEFAULT_CONFIG_FILE); + dm->config.nas_port = AAA_DEFAULT_NAS_PORT; + dm->config.is_enabled = VBNG_AAA_DISABLED; + } else { + return clib_error_return (0, "parse error`%U'", + format_unformat_error, input); + } + + return 0; +} + +VLIB_CLI_COMMAND (vbng_aaa_set_command, static) = { + .path = "set vbng aaa", + .short_help = "set vbng aaa [del] config [nas-port ]", + .function = vbng_aaa_set_command_fn, +}; + +static u8 * +format_vbng_aaa_config(u8 *s, va_list *args) +{ + vbng_dhcp4_main_t *dm = &vbng_dhcp4_main; + + s = format(s, "%=8s %=8s %s\n", "Enabled", + "NAS Port", "Config File"); + + s = format(s, "%=8s %=8d %v\n", + dm->config.is_enabled ? "True" : "False", + dm->config.nas_port, + dm->config.config_file); + + return s; +} + +static clib_error_t * +vbng_aaa_show_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + vlib_cli_output (vm, "%U", format_vbng_aaa_config, NULL); + + return (NULL); +} + +VLIB_CLI_COMMAND (vbng_aaa_show_command, static) = { + .path = "show vbng aaa", + .short_help = "Display vbng AAA configuration info", + .function = vbng_aaa_show_command_fn, +}; + +/* *INDENT-OFF* */ +VLIB_PLUGIN_REGISTER () = { + .version = VPP_BUILD_VER, + .description = "DHCP V4 Proxy With Radius Client", +}; +/* *INDENT-ON* */ diff --git a/src/plugins/vbng/vbng_msg_enum.h b/src/plugins/vbng/vbng_msg_enum.h new file mode 100644 index 00000000..1dc1357f --- /dev/null +++ b/src/plugins/vbng/vbng_msg_enum.h @@ -0,0 +1,31 @@ +/* + * vbng_msg_enum.h - vpp engine plug-in message enumeration + * + * Copyright (c) 2017 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _VBNG_MSG_ENUM_H_ +#define _VBNG_MSG_ENUM_H_ + +#include + +#define vl_msg_id(n,h) n, +typedef enum +{ +#include + /* We'll want to know how many messages IDs we need... */ + VL_MSG_FIRST_AVAILABLE, +} vl_msg_id_t; +#undef vl_msg_id + +#endif /* _VBNG_MSG_ENUM_H_ */ diff --git a/src/vnet.am b/src/vnet.am index 9e099f33..7c107f0f 100644 --- a/src/vnet.am +++ b/src/vnet.am @@ -682,31 +682,31 @@ endif ######################################## # DHCP client ######################################## -libvnet_la_SOURCES += \ - vnet/dhcp/client.c \ - vnet/dhcp/client.h \ - vnet/dhcp/dhcp_api.c - -nobase_include_HEADERS += \ - vnet/dhcp/client.h \ - vnet/dhcp/dhcp.api.h - -API_FILES += vnet/dhcp/dhcp.api +#libvnet_la_SOURCES += \ +# vnet/dhcp/client.c \ +# vnet/dhcp/client.h \ +# vnet/dhcp/dhcp_api.c +# +#nobase_include_HEADERS += \ +# vnet/dhcp/client.h \ +# vnet/dhcp/dhcp.api.h +# +#API_FILES += vnet/dhcp/dhcp.api ######################################## # DHCP proxy ######################################## -libvnet_la_SOURCES += \ - vnet/dhcp/dhcp6_proxy_node.c \ - vnet/dhcp/dhcp4_proxy_node.c \ - vnet/dhcp/dhcp_proxy.c - -nobase_include_HEADERS += \ - vnet/dhcp/dhcp4_packet.h \ - vnet/dhcp/dhcp6_packet.h \ - vnet/dhcp/dhcp_proxy.h \ - vnet/dhcp/dhcp6_proxy_error.def \ - vnet/dhcp/dhcp4_proxy_error.def +#libvnet_la_SOURCES += \ +# vnet/dhcp/dhcp6_proxy_node.c \ +# vnet/dhcp/dhcp4_proxy_node.c \ +# vnet/dhcp/dhcp_proxy.c +# +#nobase_include_HEADERS += \ +# vnet/dhcp/dhcp4_packet.h \ +# vnet/dhcp/dhcp6_packet.h \ +# vnet/dhcp/dhcp_proxy.h \ +# vnet/dhcp/dhcp6_proxy_error.def \ +# vnet/dhcp/dhcp4_proxy_error.def ######################################## # ipv6 segment routing diff --git a/src/vpp-api/java/Makefile.am b/src/vpp-api/java/Makefile.am index f18e0c24..cadaa8d9 100644 --- a/src/vpp-api/java/Makefile.am +++ b/src/vpp-api/java/Makefile.am @@ -149,6 +149,26 @@ jvpp-snat/io_fd_vpp_jvpp_snat_JVppSnatImpl.h: $(jvpp_registry_ok) $(jvpp_snat_js endif # +# VBNG Plugin +# +if ENABLE_VBNG_PLUGIN +noinst_LTLIBRARIES += libjvpp_vbng.la +libjvpp_vbng_la_SOURCES = jvpp-vbng/jvpp_vbng.c +libjvpp_vbng_la_CPPFLAGS = -Ijvpp-vbng +libjvpp_vbng_la_LIBADD = $(JVPP_LIBS) +libjvpp_vbng_la_DEPENDENCIES = libjvpp_common.la + +BUILT_SOURCES += jvpp-vbng/io_fd_vpp_jvpp_vbng_JVppVbngImpl.h +JAR_FILES += jvpp-vbng-$(PACKAGE_VERSION).jar +CLEANDIRS += jvpp-vbng/target + +jvpp_vbng_json_files = @top_builddir@/plugins/vbng/vbng.api.json + +jvpp-vbng/io_fd_vpp_jvpp_vbng_JVppVbngImpl.h: $(jvpp_registry_ok) $(jvpp_vbng_json_files) + $(call japigen,vbng,JVppVbngImpl) +endif + +# # iOAM Trace Plugin # if ENABLE_IOAM_PLUGIN diff --git a/src/vpp-api/java/jvpp-vbng/jvpp_vbng.c b/src/vpp-api/java/jvpp-vbng/jvpp_vbng.c new file mode 100644 index 00000000..b722a500 --- /dev/null +++ b/src/vpp-api/java/jvpp-vbng/jvpp_vbng.c @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2017 Intel Corp and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +#include +#include +#include + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include + +#include "jvpp-vbng/io_fd_vpp_jvpp_vbng_JVppVbngImpl.h" +#include "jvpp_vbng.h" +#include "jvpp-vbng/jvpp_vbng_gen.h" + +/* + * Class: io_fd_vpp_jvpp_vbng_JVppVbngImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_vbng_JVppVbngImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + vbng_main_t * plugin_main = &vbng_main; + clib_warning ("Java_io_fd_vpp_jvpp_vbng_JVppVbngImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = (unix_shared_memory_queue_t *)queue_address; + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + get_message_id(env, #N); + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_vbng_JVppVbngImpl_close0 +(JNIEnv *env, jclass clazz) { + vbng_main_t * plugin_main = &vbng_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP VES */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP VES */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/src/vpp-api/java/jvpp-vbng/jvpp_vbng.h b/src/vpp-api/java/jvpp-vbng/jvpp_vbng.h new file mode 100644 index 00000000..62b4cda5 --- /dev/null +++ b/src/vpp-api/java/jvpp-vbng/jvpp_vbng.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 Intel Corp and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __included_jvpp_vbng_h__ +#define __included_jvpp_vbng_h__ + +#include +#include +#include +#include +#include +#include + +/* Global state for JVPP-VES */ +typedef struct { + /* Pointer to shared memory queue */ + unix_shared_memory_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} vbng_main_t; + +vbng_main_t vbng_main __attribute__((aligned (64))); + +#endif /* __included_jvpp_vbng_h__ */ -- 2.12.2.windows.2