diff options
author | Renato Botelho <renato@netgate.com> | 2015-08-25 08:08:24 -0300 |
---|---|---|
committer | Renato Botelho <renato@netgate.com> | 2015-08-25 14:49:54 -0300 |
commit | 46bc6e545a17e77202aaf01ec0cd8d5a46567525 (patch) | |
tree | 32d18dda436ec739c67c489ceb771e8629cd926f /src/etc | |
parent | 4d9801c2dbd2b3e54a39578ee62b93af66607227 (diff) | |
download | pfsense-46bc6e545a17e77202aaf01ec0cd8d5a46567525.zip pfsense-46bc6e545a17e77202aaf01ec0cd8d5a46567525.tar.gz |
Move main pfSense content to src/
Diffstat (limited to 'src/etc')
204 files changed, 95589 insertions, 0 deletions
diff --git a/src/etc/ascii-art/pfsense-logo-small.txt b/src/etc/ascii-art/pfsense-logo-small.txt new file mode 100644 index 0000000..01d8bc5 --- /dev/null +++ b/src/etc/ascii-art/pfsense-logo-small.txt @@ -0,0 +1,5 @@ + ___ + ___/ f \ +/ p \___/ Sense +\___/ \ + \___/
\ No newline at end of file diff --git a/src/etc/bogons b/src/etc/bogons new file mode 100644 index 0000000..9be218d --- /dev/null +++ b/src/etc/bogons @@ -0,0 +1,10 @@ +0.0.0.0/8 +127.0.0.0/8 +169.254.0.0/16 +192.0.0.0/24 +192.0.2.0/24 +198.18.0.0/15 +198.51.100.0/24 +203.0.113.0/24 +224.0.0.0/4 +240.0.0.0/4 diff --git a/src/etc/bogonsv6 b/src/etc/bogonsv6 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/etc/bogonsv6 diff --git a/src/etc/ca_countries b/src/etc/ca_countries new file mode 100644 index 0000000..1a9ca9e --- /dev/null +++ b/src/etc/ca_countries @@ -0,0 +1,237 @@ +US United States of America +CA Canada +AX Aland Islands +AD Andorra +AE United Arab Emirates +AF Afghanistan +AG Antigua and Barbuda +AI Anguilla +AL Albania +AM Armenia +AN Netherlands Antilles +AO Angola +AQ Antarctica +AR Argentina +AS American Samoa +AT Austria +AU Australia +AW Aruba +AZ Azerbaijan +BA Bosnia and Herzegovina +BB Barbados +BD Bangladesh +BE Belgium +BF Burkina Faso +BG Bulgaria +BH Bahrain +BI Burundi +BJ Benin +BM Bermuda +BN Brunei Darussalam +BO Bolivia +BR Brazil +BS Bahamas +BT Bhutan +BV Bouvet Island +BW Botswana +BZ Belize +CA Canada +CC Cocos (Keeling) Islands +CF Central African Republic +CH Switzerland +CI Cote D'Ivoire (Ivory Coast) +CK Cook Islands +CL Chile +CM Cameroon +CN China +CO Colombia +CR Costa Rica +CS Czechoslovakia (former) +CV Cape Verde +CX Christmas Island +CY Cyprus +CZ Czech Republic +DE Germany +DJ Djibouti +DK Denmark +DM Dominica +DO Dominican Republic +DZ Algeria +EC Ecuador +EE Estonia +EG Egypt +EH Western Sahara +ER Eritrea +ES Spain +ET Ethiopia +FI Finland +FJ Fiji +FK Falkland Islands (Malvinas) +FM Micronesia +FO Faroe Islands +FR France +FX France, Metropolitan +GA Gabon +GB Great Britain (UK) +GD Grenada +GE Georgia +GF French Guiana +GG Guernsey +GH Ghana +GI Gibraltar +GL Greenland +GM Gambia +GN Guinea +GP Guadeloupe +GQ Equatorial Guinea +GR Greece +GS S. Georgia and S. Sandwich Isls. +GT Guatemala +GU Guam +GW Guinea-Bissau +GY Guyana +HK Hong Kong +HM Heard and McDonald Islands +HN Honduras +HR Croatia (Hrvatska) +HT Haiti +HU Hungary +ID Indonesia +IE Ireland +IL Israel +IM Isle of Man +IN India +IO British Indian Ocean Territory +IS Iceland +IT Italy +JE Jersey +JM Jamaica +JO Jordan +JP Japan +KE Kenya +KG Kyrgyzstan +KH Cambodia +KI Kiribati +KM Comoros +KN Saint Kitts and Nevis +KR Korea (South) +KW Kuwait +KY Cayman Islands +KZ Kazakhstan +LA Laos +LC Saint Lucia +LI Liechtenstein +LK Sri Lanka +LS Lesotho +LT Lithuania +LU Luxembourg +LV Latvia +LY Libya +MA Morocco +MC Monaco +MD Moldova +ME Montenegro +MG Madagascar +MH Marshall Islands +MK Macedonia +ML Mali +MM Myanmar +MN Mongolia +MO Macau +MP Northern Mariana Islands +MQ Martinique +MR Mauritania +MS Montserrat +MT Malta +MU Mauritius +MV Maldives +MW Malawi +MX Mexico +MY Malaysia +MZ Mozambique +NA Namibia +NC New Caledonia +NE Niger +NF Norfolk Island +NG Nigeria +NI Nicaragua +NL Netherlands +NO Norway +NP Nepal +NR Nauru +NT Neutral Zone +NU Niue +NZ New Zealand (Aotearoa) +OM Oman +PA Panama +PE Peru +PF French Polynesia +PG Papua New Guinea +PH Philippines +PK Pakistan +PL Poland +PM St. Pierre and Miquelon +PN Pitcairn +PR Puerto Rico +PS Palestinian Territory +PT Portugal +PW Palau +PY Paraguay +QA Qatar +RE Reunion +RO Romania +RS Serbia +RU Russian Federation +RW Rwanda +SA Saudi Arabia +SB Solomon Islands +SC Seychelles +SE Sweden +SG Singapore +SH St. Helena +SI Slovenia +SJ Svalbard and Jan Mayen Islands +SK Slovak Republic +SL Sierra Leone +SM San Marino +SN Senegal +SR Suriname +ST Sao Tome and Principe +SU USSR (former) +SV El Salvador +SZ Swaziland +TC Turks and Caicos Islands +TD Chad +TF French Southern Territories +TG Togo +TH Thailand +TJ Tajikistan +TK Tokelau +TM Turkmenistan +TN Tunisia +TO Tonga +TP East Timor +TR Turkey +TT Trinidad and Tobago +TV Tuvalu +TW Taiwan +TZ Tanzania +UA Ukraine +UG Uganda +UM US Minor Outlying Islands +US United States +UY Uruguay +UZ Uzbekistan +VA Vatican City State (Holy See) +VC Saint Vincent and the Grenadines +VE Venezuela +VG Virgin Islands (British) +VI Virgin Islands (U.S.) +VN Viet Nam +VU Vanuatu +WF Wallis and Futuna Islands +WS Samoa +YE Yemen +YT Mayotte +ZA South Africa +ZM Zambia
\ No newline at end of file diff --git a/src/etc/crontab b/src/etc/crontab new file mode 100644 index 0000000..8be8494 --- /dev/null +++ b/src/etc/crontab @@ -0,0 +1,5 @@ +SHELL=/bin/sh +PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin +HOME=/var/log +#minute hour mday month wday who command +#
\ No newline at end of file diff --git a/src/etc/ddb.conf b/src/etc/ddb.conf new file mode 100644 index 0000000..65f49c2 --- /dev/null +++ b/src/etc/ddb.conf @@ -0,0 +1,3 @@ +script lockinfo=show locks; show alllocks; show lockedvnods +script kdb.enter.default=textdump set; capture on; run lockinfo; show pcpu; bt; ps; alltrace; capture off; call doadump; reset +script kdb.enter.witness=run lockinfo diff --git a/src/etc/devd.conf b/src/etc/devd.conf new file mode 100644 index 0000000..ea67ba6 --- /dev/null +++ b/src/etc/devd.conf @@ -0,0 +1,80 @@ +# $Id$ +# $FreeBSD: src/etc/devd.conf,v 1.26.2.1 2005/09/03 22:49:22 sam Exp $ + +options { + directory "/etc/devd"; + directory "/usr/local/etc/devd"; + pid-file "/var/run/devd.pid"; + set scsi-controller-regex + "(aac|adv|adw|aha|ahb|ahc|ahd|aic|amd|amr|asr|bt|ciss|ct|dpt|\ + esp|ida|iir|ips|isp|mlx|mly|mpt|ncr|ncv|nsp|stg|sym|trm|wds)\ + [0-9]+"; +}; + +# CARP notify hooks. This will call carpup/carpdown with the +# interface (carp0, carp1) as the first parameter. +notify 100 { + match "system" "CARP"; + match "type" "MASTER"; + action "/usr/local/sbin/pfSctl -c 'interface carpmaster $subsystem'"; +}; + +notify 100 { + match "system" "CARP"; + match "type" "BACKUP"; + action "/usr/local/sbin/pfSctl -c 'interface carpbackup $subsystem'"; +}; + +notify 100 { + match "system" "CARP"; + match "type" "INIT"; + action "/usr/local/sbin/pfSctl -c 'interface carpbackup $subsystem'"; +}; + +# When a USB keyboard arrives, attach it as the console keyboard. +attach 100 { + device-name "ukbd0"; + action "kbdcontrol -k /dev/ukbd0 < /dev/console 2>/dev/null"; +}; + +detach 100 { + device-name "ukbd0"; + action "kbdcontrol -k /dev/kbd0 < /dev/console 2>/dev/null"; +}; + +# +# Signal upper levels that an event happened on ethernet class interface +# +notify 0 { + match "system" "IFNET"; + match "type" "LINK_UP"; + media-type "ethernet"; + action "/usr/local/sbin/pfSctl -c 'interface linkup start $subsystem'"; +}; + +notify 0 { + match "system" "IFNET"; + match "type" "LINK_DOWN"; + media-type "ethernet"; + action "/usr/local/sbin/pfSctl -c 'interface linkup stop $subsystem'"; +}; + +# +# Signal upper levels that an event happened on 802.11 class interface +# +notify 0 { + match "system" "IFNET"; + match "type" "LINK_UP"; + match "subsystem" "[a-z]+[0-9]+_wlan[0-9]+"; + action "/usr/local/sbin/pfSctl -c 'interface linkup start $subsystem'"; +}; + +# Notify all users before beginning emergency shutdown when we get +# a _CRT or _HOT thermal event and we're going to power down the system +# very soon. +notify 10 { + match "system" "ACPI"; + match "subsystem" "Thermal"; + match "notify" "0xcc"; + action "logger -p kern.emerg 'WARNING: system temperature too high, shutting down soon!'"; +}; diff --git a/src/etc/dh-parameters.1024 b/src/etc/dh-parameters.1024 new file mode 100644 index 0000000..3148f4c --- /dev/null +++ b/src/etc/dh-parameters.1024 @@ -0,0 +1,5 @@ +-----BEGIN DH PARAMETERS----- +MIGHAoGBAINPWm4z+KHppuzSZFjreaLrKdI/wkP0ojutrSlkiszXsGkbU6++GB1C +7ZH2ZVpSIo4z31XyQnlraIkyY2pAItxqN8ozWaz84QLSHcwVcWKDEU7ZP0ISyTep +alnFPGG8nJBSzxch+7H3HOfM68y6kfMtFDWuZtYj/9Zw4W42fVDLAgEC +-----END DH PARAMETERS----- diff --git a/src/etc/dh-parameters.2048 b/src/etc/dh-parameters.2048 new file mode 100644 index 0000000..f0e1a5d --- /dev/null +++ b/src/etc/dh-parameters.2048 @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEAmWwXhRjeqPYl1TvXeKZt5W8MHe0keJK7wC+uPMxpGFVXlvPnWdN+ +W/GyimtD2rHYWF1gyr5IbhiEkXSAuTCnwokwz9XiNQ3hKY/iwTPDo0Go8beB5Ezr +wz8DibSIv93Va5C+fHzwosuwTAqaOgpOzPqSmVS/UmUATssxOuCK6Crv7YyA5knW +v0JsJK3VfloeXq/p4skn/KRgL2twO5puJvZWGycMd3cv9+afsWjES/ItwzEHNSEG +sPen/kNDB4nH+WFKdXnP3fUAqPZCxiqaBC+UnuHngm7Se4smc7DeJkUsed7NLIeg +zDZ0a3bKZ3UB0lcLGbqXIhh74TtFQ1egmwIBAg== +-----END DH PARAMETERS----- diff --git a/src/etc/dh-parameters.4096 b/src/etc/dh-parameters.4096 new file mode 100644 index 0000000..30058a1 --- /dev/null +++ b/src/etc/dh-parameters.4096 @@ -0,0 +1,13 @@ +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEA1G0VaCFVkFFPB0pL1Y6NtAlysfvZaAXXmmJ89Xy5wrNLEZfTdmqT +NmABAhr0DD6+1rcI5d4LriRLhTFf77COjW/+FelEA5BZBsoQDL6QsxWt4VoLT6uK +bKVkbtwKycz0uOU1areS5gWHF71KRmKgooOuY2yl7a75uLn4QYCS7hKLXsAIB8eC +63nl81T5gXOAc3hMiKrk8hKLUA6zkMfqWIpG06wvicaPlg8GyQavwGxONDNl/Y2r +XyRoh/4ja7Moz0tUCmZV+iKtGgq5wekJ1fCN3zhXPX6h6WujoYqzcCmPLFCuIuEa +kxRy9XaDTe8V40p1RDc4yMYQrl2hxrO8YPRBewigILYxEfe+51qE5Sb//UZszwNL +kIhW9ObfAkotXoH81xke4EN0RX+rVK1ZYbeBIDCn62ZqNsUVkMh5Otsh0TiK7SP9 +O14IflklQqpyYc+aHMNknhsN30MFV3aD/785QS8zcWUdSdQeZlbjjFgJ4Xpt+r3p +X6Vv8cwEh8qDHn2CaOfZtyTx2V3B2LU1sJZQ9ynVzlxy2clQcVboXPM1xNgzHSsd +bFgPMJUAq9VjLGrbN6a3NqWwXnQPMuczX1G3T690fKF55e/boIAXZD1hEZqKt1f0 +DuCwyf/D4CEGyHhHIdVm7f1kTaErWzSgqcc2wGsjFi3ABTG2byxTnSsCAQI= +-----END DH PARAMETERS----- diff --git a/src/etc/disktab b/src/etc/disktab new file mode 100644 index 0000000..5726c4d --- /dev/null +++ b/src/etc/disktab @@ -0,0 +1,204 @@ +# $FreeBSD: stable/10/etc/disktab 242462 2012-11-02 00:17:30Z eadler $ +# +# Disk geometry and partition layout tables. +# See disktab(5) for format of this file. +# + +# +# Floppy formats: +# +# To make a filesystem on a floppy: +# fdformat [-f <size>] fd<drive>[.<size>] +# disklabel -B -r -w fd<drive>[.<size>] fd<size> +# newfs <opts> fd<drive>[.<size>] +# +# with <opts>: +# -t 2 - two heads +# -u 9|15|18 - sectors per track +# (using the default value of 1/4096 is not much useful for floppies) +# -l 1 - interleave 1 (for most floppies) +# -i 65536 - bytes of data per i-node +# (the default -i value will render you with a floppy wasting way +# too much space in i-node areas) +# + +fd360:\ + :ty=floppy:se#512:nt#2:rm#300:ns#9:nc#40:\ + :pa#720:oa#0:ba#4096:fa#512:\ + :pc#720:oc#0:bc#4096:fc#512: + +fd720:\ + :ty=floppy:se#512:nt#2:rm#300:ns#9:nc#80:\ + :pa#1440:oa#0:ba#4096:fa#512:\ + :pc#1440:oc#0:bc#4096:fc#512: + +fd1200|floppy5|5in|5.25in High Density Floppy:\ + :ty=floppy:se#512:nt#2:rm#360:ns#15:nc#80:\ + :pa#2400:oa#0:ba#4096:fa#512:\ + :pc#2400:oc#0:bc#4096:fc#512: + +fd1440|floppy|floppy3|3in|3.5in High Density Floppy:\ + :ty=floppy:se#512:nt#2:rm#300:ns#18:nc#80:\ + :pa#2880:oa#0:ba#4096:fa#512:\ + :pc#2880:oc#0:bc#4096:fc#512: + +fd2880|2.88MB 3.5in Extra High Density Floppy:\ + :ty=floppy:se#512:nt#2:rm#300:ns#36:nc#80:\ + :pa#5760:oa#0:ba#4096:fa#512:\ + :pb#5760:ob#0:bb#4096:fa#512:\ + :pc#5760:oc#0:bb#4096:fa#512: + +# +# Stressed floppy-formats. No guarantees given. +# + +fd800:\ + :ty=floppy:se#512:nt#2:rm#300:ns#10:nc#80:\ + :pa#1600:oa#0:ba#4096:fa#512:\ + :pc#1600:oc#0:bc#4096:fc#512: + +fd820:\ + :ty=floppy:se#512:nt#2:rm#300:ns#10:nc#82:\ + :pa#1640:oa#0:ba#4096:fa#512:\ + :pc#1640:oc#0:bc#4096:fc#512: + +fd1480:\ + :ty=floppy:se#512:nt#2:rm#300:ns#18:nc#82:\ + :pa#2952:oa#0:ba#4096:fa#512:\ + :pc#2952:oc#0:bc#4096:fc#512: + +fd1720:\ + :ty=floppy:se#512:nt#2:rm#300:ns#21:nc#82:\ + :pa#3444:oa#0:ba#4096:fa#512:\ + :pc#3444:oc#0:bc#4096:fc#512: + +# +# LS-120 floppy-format. +# +fd120m|floppy120|floppy120m|3.5in LS-120 Floppy:\ + :ty=floppy:se#512:nt#8:rm#300:ns#32:nc#963:\ + :pa#246528:oa#0:ba#4096:fa#512:\ + :pc#246528:oc#0:bc#4096:fc#512: + +# +# Harddisk formats +# +qp120at|Quantum Peripherals 120MB IDE:\ + :dt=ESDI:ty=winchester:se#512:nt#9:ns#32:nc#813:sf: \ + :pa#13824:oa#0:ta=4.2BSD:ba#4096:fa#512: \ + :pb#13824:ob#13824:tb=swap: \ + :pc#234144:oc#0: \ + :ph#206496:oh#27648:th=4.2BSD:bh#4096:fh#512: + +pan60|Panasonic Laptop's 60MB IDE:\ + :dt=ST506:ty=winchester:se#512:nt#13:ns#17:nc#565:\ + :pa#13260:oa#0:ta=4.2BSD:ba#4096:fa#512:\ + :pb#13260:ob#13260:tb=swap: \ + :pc#124865:oc#0: \ + :ph#97682:oh#26520:th=4.2BSD:bh#4096:fh#512: + +mk156|toshiba156|Toshiba MK156 156Mb:\ + :dt=SCSI:ty=winchester:se#512:nt#10:ns#35:nc#825:\ + :pa#15748:oa#0:ba#4096:fa#512:ta=4.2BSD:\ + :pb#15748:ob#15748:tb=swap:\ + :pc#288750:oc#0:\ + :ph#257250:oh#31500:bh#4096:fh#512:th=4.2BSD: + +cp3100|Connor Peripherals 100MB IDE:\ + :dt=ST506:ty=winchester:se#512:nt#8:ns#33:nc#766: \ + :pa#12144:oa#0:ta=4.2BSD:ba#4096:fa#512: \ + :pb#12144:ob#12144:tb=swap: \ + :pc#202224:oc#0: \ + :ph#177936:oh#24288:th=4.2BSD:bh#4096:fh#512: + +# a == root +# b == swap +# c == d == whole disk +# e == /var +# f == scratch +# h == /usr + +cp3100new|Connor Peripherals 100MB IDE, with a different configuration:\ + :dt=ST506:ty=winchester:se#512:nt#8:ns#33:nc#766: \ + :pa#15840:oa#0:ta=4.2BSD:ba#4096:fa#512: \ + :pb#24288:ob#15840:tb=swap: \ + :pc#202224:oc#0: \ + :pd#202224:od#0: \ + :pe#15840:oe#40128:te=4.2BSD:be#4096:fe#512: \ + :pg#15840:og#55968:tg=4.2BSD:bg#4096:fg#512: \ + :ph#130416:oh#71808:th=4.2BSD:bh#4096:fh#512: + +maxtor4380|Maxtor XT4380E ESDI :\ + :dt=ESDI:ty=winchester:se#512:nt#15:ns#36:nc#1222:sf: \ + :pa#21600:oa#0:ta=4.2BSD:ba#4096:fa#512:\ + :pb#21600:ob#21600:tb=swap: \ + :pc#659880:oc#0: \ + :pd#216000:od#53200:td=4.2BSD:bd#4096:fd#512: \ + :ph#398520:oh#269200:th=4.2BSD:bh#4096:fh#512: + +miniscribe9380|compaq38|Miniscribe 9380 ESDI :\ + :ty=winchester:dt=ESDI:se#512:nt#15:ns#35:nc#1223:rm#3600:sf: \ + :pa#21000:oa#0:ba#8192:fa#1024:ta=4.2BSD: \ + :pb#42000:ob#21000:tb=swap: \ + :pc#642075:oc#0: \ + :pd#21000:od#63000:bd#8192:fd#1024:td=4.2BSD: \ + :ph#556500:oh#84000:bh#8192:fh#1024:th=4.2BSD: + +ida4|compaq88|Compaq IDA (4 drives) :\ + :ty=winchester:dt=IDA:se#512:nt#16:ns#63:nc#1644:rm#3600:\ + :pa#20160:oa#0:ba#8192:fa#1024:ta=4.2BSD: \ + :pb#80640:ob#20160:tb=swap: \ + :pc#1659168:oc#0: \ + :pd#201600:od#100800:bd#8192:fd#1024:td=4.2BSD: \ + :pe#20160:oe#1310400:be#8192:fe#1024:te=4.2BSD: \ + :ph#1008000:oh#302400:bh#8192:fh#1024:th=4.2BSD: \ + :pg#302400:og#1330560:bg#4096:fg#512:tg=4.2BSD: + +fuji513|Fujitsu M22XXXX: \ + :ty=winchester:dt=ESDI:se#512:nt#16:ns#63:nc#954:rm#3600:\ + :pa#20160:oa#82656:ba#4096:fa#512:ta=4.2BSD: \ + :pb#40320:ob#102816:tb=swap: \ + :pc#961632:oc#0: \ + :ph#656208:oh#143136:bh#4096:fh#512:th=4.2BSD: + +sony650|Sony 650 MB MOD|\ + :ty=removable:dt=SCSI:se#512:nt#1:ns#31:nc#18600:ts#1:rm#4800:\ + :pc#576600:oc#0:\ + :pa#576600:oa#0:ta=4.2BSD:ba#8192:fa#1024: + +mta3230|mo230|IBM MTA-3230 230 Meg 3.5inch Magneto-Optical:\ + :ty=removeable:dt=SCSI:rm#3600:\ + :se#512:nt#64:ns#32:nc#216:sc#2048:su#444384:\ + :pa#444384:oa#0:ba#4096:fa#0:ta=4.2BSD:\ + :pc#444384:oc#0: + +minimum:ty=mfs:se#512:nt#1:rm#300:\ + :ns#2880:nc#1:\ + :pa#2880:oa#0:ba#4096:fa#512:\ + :pc#2880:oc#0:bc#4096:fc#512: + +minimum2:ty=mfs:se#512:nt#1:rm#300:\ + :ns#5760:nc#1:\ + :pa#5760:oa#0:ba#4096:fa#512:\ + :pc#5760:oc#0:bc#4096:fc#512: + +minimum3:ty=mfs:se#512:nt#1:rm#300:\ + :ns#8640:nc#1:\ + :pa#8640:oa#0:ba#4096:fa#512:\ + :pc#8640:oc#0:bc#4096:fc#512: + +zip100|zip 100:\ + :ty=removable:se#512:nc#96:nt#64:ns#32:\ + :pa#196608:oa#0:ba#4096:fa#512:\ + :pc#196608:oc#0:bc#4096:fc#512: + +zip250|zip 250:\ + :ty=removable:se#512:nc#239:nt#64:ns#32:\ + :pa#489472:oa#0:ba#4096:fa#512:\ + :pc#489472:oc#0:bc#4096:fc#512: + +orb2200|orb22|orb:\ + :ty=removable:ns#63:nt#128:nc#4273:sc#1008:su#4307184:se#512:\ + :pa#4307184:oa#0:ba#8192:fa#1024:\ + :pc#4307184:oc#0:bc#8192:fc#1024: + diff --git a/src/etc/ecl.php b/src/etc/ecl.php new file mode 100755 index 0000000..15205f1 --- /dev/null +++ b/src/etc/ecl.php @@ -0,0 +1,189 @@ +<?php +/* + external config loader + Copyright (C) 2010 Scott Ullrich + Copyright (C) 2013-2015 Electric Sheep Fencing, LP + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Currently supported file system types: MS-Dos, FreeBSD UFS + +*/ + +require_once("globals.inc"); +require_once("functions.inc"); +require_once("config.lib.inc"); +require_once("config.inc"); + +$debug = false; + +function get_boot_disk() { + global $g, $debug; + $disk = exec("/sbin/mount | /usr/bin/grep \"on / \" | /usr/bin/cut -d'/' -f3 | /usr/bin/cut -d' ' -f1"); + return $disk; +} + +function get_swap_disks() { + exec("/usr/sbin/swapinfo | /usr/bin/sed '/^\/dev/!d; s,^/dev/,,; s, .*\$,,'", $disks); + return $disks; +} + +function get_disk_slices($disk) { + global $g, $debug; + $slices_array = array(); + $slices = trim(exec("/bin/ls " . escapeshellarg("/dev/" . $disk . "s*") . " 2>/dev/null")); + $slices = str_replace("/dev/", "", $slices); + if ($slices == "ls: No match.") { + return; + } + $slices_array = explode(" ", $slices); + return $slices_array; +} + +function get_disks() { + global $g, $debug; + $disks_array = array(); + $disks_s = explode(" ", get_single_sysctl("kern.disks")); + foreach ($disks_s as $disk) { + if (trim($disk)) { + $disks_array[] = $disk; + } + } + return $disks_array; +} + +function discover_config($mountpoint) { + global $g, $debug; + $locations_to_check = array("/", "/config"); + foreach ($locations_to_check as $ltc) { + $tocheck = "/tmp/mnt/cf{$ltc}config.xml"; + if ($debug) { + echo "\nChecking for $tocheck"; + if (file_exists($tocheck)) { + echo " -> found!"; + } + } + if (file_exists($tocheck)) { + return $tocheck; + } + } + return ""; +} + +function test_config($file_location) { + global $g, $debug; + if (!$file_location) { + return; + } + // config.xml was found. ensure it is sound. + $root_obj = trim("<{$g['xml_rootobj']}>"); + $xml_file_head = exec("/usr/bin/head -2 " . escapeshellarg($file_location) . " | /usr/bin/tail -n1"); + if ($debug) { + echo "\nroot obj = $root_obj"; + echo "\nfile head = $xml_file_head"; + } + if ($xml_file_head == $root_obj) { + // Now parse config to make sure + $config_status = config_validate($file_location); + if ($config_status) { + return true; + } + } + return false; +} + +// Probes all disks looking for config.xml +function find_config_xml() { + global $g, $debug; + $disks = get_disks(); + // Safety check. + if (!is_array($disks)) { + return; + } + $boot_disk = get_boot_disk(); + $swap_disks = get_swap_disks(); + exec("/bin/mkdir -p /tmp/mnt/cf"); + foreach ($disks as $disk) { + $slices = get_disk_slices($disk); + if (is_array($slices)) { + foreach ($slices as $slice) { + if ($slice == "") { + continue; + } + if (stristr($slice, $boot_disk)) { + if ($debug) { + echo "\nSkipping boot device slice $slice"; + } + continue; + } + if (in_array($slice, $swap_disks)) { + if ($debug) { + echo "\nSkipping swap device slice $slice"; + } + continue; + } + echo " $slice"; + // First try msdos fs + if ($debug) { + echo "\n/sbin/mount -t msdosfs /dev/{$slice} /tmp/mnt/cf 2>/dev/null \n"; + } + $result = exec("/sbin/mount -t msdosfs /dev/{$slice} /tmp/mnt/cf 2>/dev/null"); + // Next try regular fs (ufs) + if (!$result) { + if ($debug) { + echo "\n/sbin/mount /dev/{$slice} /tmp/mnt/cf 2>/dev/null \n"; + } + $result = exec("/sbin/mount /dev/{$slice} /tmp/mnt/cf 2>/dev/null"); + } + $mounted = trim(exec("/sbin/mount | /usr/bin/grep -v grep | /usr/bin/grep '/tmp/mnt/cf' | /usr/bin/wc -l")); + if ($debug) { + echo "\nmounted: $mounted "; + } + if (intval($mounted) > 0) { + // Item was mounted - look for config.xml file + $config_location = discover_config($slice); + if ($config_location) { + if (test_config($config_location)) { + // We have a valid configuration. Install it. + echo " -> found config.xml\n"; + echo "Backing up old configuration...\n"; + backup_config(); + echo "Restoring [{$slice}] {$config_location}...\n"; + restore_backup($config_location); + echo "Cleaning up...\n"; + exec("/sbin/umount /tmp/mnt/cf"); + exit; + } + } + exec("/sbin/umount /tmp/mnt/cf"); + } + } + } + } +} + +echo "External config loader 1.0 is now starting..."; +find_config_xml(); +echo "\n"; + +?> diff --git a/src/etc/fbtab b/src/etc/fbtab new file mode 100644 index 0000000..06d2d61 --- /dev/null +++ b/src/etc/fbtab @@ -0,0 +1,4 @@ +# $FreeBSD: src/etc/fbtab,v 1.3 1999/09/13 17:09:07 peter Exp $ +# +#/dev/ttyv0 0600 /dev/console +#/dev/ttyv0 0600 /dev/pcaudio:/dev/pcaudioctl diff --git a/src/etc/gettytab b/src/etc/gettytab new file mode 100644 index 0000000..5af5aae --- /dev/null +++ b/src/etc/gettytab @@ -0,0 +1,235 @@ +# $FreeBSD: stable/10/etc/gettytab 241708 2012-10-18 22:20:02Z peterj $ +# from: @(#)gettytab 5.14 (Berkeley) 3/27/91 +# +# Most of the table entries here are just copies of the old getty table, +# it is by no means certain, or even likely, that any of them are optimal +# for any purpose whatever. Nor is it likely that more than a couple are +# even correct. +# +# The default gettytab entry, used to set defaults for all other +# entries, and in cases where getty is called with no table name. +# +# cb, ce and ck are desirable on most crt's. The non-crt entries need to +# be changed to turn them off (:cb@:ce@:ck@:). +# +# lc should always be on; it's a remainder of some stone age when there +# have been terminals around not being able of handling lower-case +# characters. Those terminals aren't supported any longer, but getty is +# `smart' about them by default. +# +# Parity defaults to even, but the Pc entry and all the `std' entries +# specify no parity. The different parities are: +# (none): same as ep for getty. login will use terminal as is. +# ep: getty will use raw mode (cs8 -parenb) (unless rw is set) and +# fake parity. login will use even parity (cs7 parenb -parodd). +# op: same as ep except odd parity (cs7 parenb parodd) for login. +# getty will fake odd parity as well. +# ap: same as ep except -inpck instead of inpck for login. +# ap overrides op and ep. +# np: 1. don't fake parity in getty. The fake parity garbles +# characters on non-terminals (like pccons) that don't +# support parity. It would probably better for getty not to +# try to fake parity. It could just use cbreak mode so as +# not to force cs8 and let the hardware handle the parity. +# login has to be rely on the hardware anyway. +# 2. set cs8 -parenb -istrip -inpck. +# ep:op: same as ap. +# +default:\ + :cb:ce:ck:lc:fd#1000:im=\r\n%s/%m (%h) (%t)\r\n\r\n:sp#1200:\ + :if=/etc/issue: + +# +# Fixed speed entries +# +# The "std.NNN" names are known to the special case +# portselector code in getty, however they can +# be assigned to any table desired. +# The "NNN-baud" names are known to the special case +# autobaud code in getty, and likewise can +# be assigned to any table desired (hopefully the same speed). +# +a|std.110|110-baud:\ + :np:nd#1:cd#1:uc:sp#110: +b|std.134|134.5-baud:\ + :np:nd#1:cd#2:ff#1:td#1:sp#134:ht:nl: +1|std.150|150-baud:\ + :np:nd#1:cd#2:td#1:fd#1:sp#150:ht:nl:lm=\E\72\6\6\17login\72 : +c|std.300|300-baud:\ + :np:nd#1:cd#1:sp#300: +d|std.600|600-baud:\ + :np:nd#1:cd#1:sp#600: +f|std.1200|1200-baud:\ + :np:fd#1:sp#1200: +6|std.2400|2400-baud:\ + :np:sp#2400: +7|std.4800|4800-baud:\ + :np:sp#4800: +2|std.9600|9600-baud:\ + :np:sp#9600: +g|std.19200|19200-baud:\ + :np:sp#19200: +std.38400|38400-baud:\ + :np:sp#38400: +std.57600|57600-baud:\ + :np:sp#57600: +std.115200|115200-baud:\ + :np:sp#115200: +std.230400|230400-baud:\ + :np:sp#230400: + +# +# Entry specifying explicit device settings. See termios(4) and +# /usr/include/termios.h, too. The entry forces the tty into +# CLOCAL mode (so no DCD is required), and uses Xon/Xoff flow control. +# +# cflags: CLOCAL | HUPCL | CREAD | CS8 +# oflags: OPOST | ONLCR | OXTABS +# iflags: IXOFF | IXON | ICRNL | IGNPAR +# lflags: IEXTEN | ICANON | ISIG | ECHOCTL | ECHO | ECHOK | ECHOE | ECHOKE +# +# The `0' flags don't have input enabled. The `1' flags don't echo. +# (Echoing is done inside getty itself.) +# +local.9600|CLOCAL tty @ 9600 Bd:\ + :c0#0x0000c300:c1#0x0000cb00:c2#0x0000cb00:\ + :o0#0x00000007:o1#0x00000002:o2#0x00000007:\ + :i0#0x00000704:i1#0x00000000:i2#0x00000704:\ + :l0#0x000005cf:l1#0x00000000:l2#0x000005cf:\ + :sp#9600:np: + +# +# Dial in rotary tables, speed selection via 'break' +# +0|d300|Dial-300:\ + :nx=d1200:cd#2:sp#300: +d1200|Dial-1200:\ + :nx=d150:fd#1:sp#1200: +d150|Dial-150:\ + :nx=d110:lm@:tc=150-baud: +d110|Dial-110:\ + :nx=d300:tc=300-baud: + +# +# Fast dialup terminals, 2400/1200/300 rotary (can start either way) +# +D2400|d2400|Fast-Dial-2400:\ + :nx=D1200:tc=2400-baud: +3|D1200|Fast-Dial-1200:\ + :nx=D300:tc=1200-baud: +5|D300|Fast-Dial-300:\ + :nx=D2400:tc=300-baud: + +# +#telebit (19200) +# +t19200:\ + :nx=t2400:tc=19200-baud: +t2400:\ + :nx=t1200:tc=2400-baud: +t1200:\ + :nx=t19200:tc=1200-baud: + +# +#telebit (9600) +# +t9600:\ + :nx=t2400a:tc=9600-baud: +t2400a:\ + :nx=t1200a:tc=2400-baud: +t1200a:\ + :nx=t9600:tc=1200-baud: + +# +# Odd special case terminals +# +-|tty33|asr33|Pity the poor user of this beast:\ + :tc=110-baud: + +4|Console|Console Decwriter II:\ + :nd@:cd@:rw:tc=300-baud: + +e|Console-1200|Console Decwriter III:\ + :fd@:nd@:cd@:rw:tc=1200-baud: + +i|Interdata console:\ + :uc:sp#0: + +l|lsi chess terminal:\ + :sp#300: + +X|Xwindow|X window system:\ + :fd@:nd@:cd@:rw:sp#9600: + +P|Pc|Pc console:\ + :ht:np:sp#9600: + +al.Pc:\ + :ht:np:sp#9600:al=root: + +# Weirdo special case for fast crt's with hardcopy devices +# +8|T9600|CRT with hardcopy:\ + :nx=T300:tc=9600-baud: +9|T300|CRT with hardcopy (300):\ + :nx=T9600:tc=300-baud: + +# +# Plugboard, and misc other terminals +# +plug-9600|Plugboard-9600:\ + :pf#1:tc=9600-baud: +p|P9600|Plugboard-9600-rotary:\ + :pf#1:nx=P300:tc=9600-baud: +q|P300|Plugboard-300:\ + :pf#1:nx=P1200:tc=300-baud: +r|P1200|Plugboard-1200:\ + :pf#1:nx=P9600:tc=1200-baud: + +# +# XXXX Port selector +# +s|DSW|Port Selector:\ + :ps:sp#2400: + +# +# Auto-baud speed detect entry for Micom 600. +# Special code in getty will switch this out +# to one of the NNN-baud entries. +# +A|Auto-baud:\ + :ab:sp#2400:f0#040: + +# +# autologin - automatically log in as root +# + +autologin|al.9600:\ + :al=root:tc=std.9600: +al.19200:\ + :al=root:tc=std.19200: +al.38400:\ + :al=root:tc=std.38400: +al.57600:\ + :al=root:tc=std.57600: +al.115200:\ + :al=root:tc=std.115200: +al.230400:\ + :al=root:tc=std.230400: + +# +# Entries for 3-wire serial terminals. These don't supply carrier, so +# clocal needs to be set, and crtscts needs to be unset. +# +3wire.9600|9600-3wire:\ + :np:nc:sp#9600: +3wire.19200|19200-3wire:\ + :np:nc:sp#19200: +3wire.38400|38400-3wire:\ + :np:nc:sp#38400: +3wire.57600|57600-3wire:\ + :np:nc:sp#57600: +3wire.115200|115200-3wire:\ + :np:nc:sp#115200: +3wire.230400|230400-3wire:\ + :np:nc:sp#230400: diff --git a/src/etc/group b/src/etc/group new file mode 100644 index 0000000..a0ca8ce --- /dev/null +++ b/src/etc/group @@ -0,0 +1,31 @@ +wheel:*:0:root,admin +daemon:*:1:daemon +kmem:*:2:root +sys:*:3:root +tty:*:4:root +operator:*:5:root +mail:*:6: +bin:*:7: +news:*:8: +man:*:9: +games:*:13: +staff:*:20:root +sshd:*:22: +smmsp:*:25: +mailnull:*:26: +guest:*:31:root +bind:*:53: +unbound:*:59: +proxy:*:62: +_pflogd:*:64: +_dhcp:*:65: +authpf:*:63: +uucp:*:66: +dialer:*:68: +network:*:69: +www:*:80: +nogroup:*:65533: +nobody:*:65534: +audit:*:77: +_ntp:*:123: +_relayd:*:913: diff --git a/src/etc/host.conf b/src/etc/host.conf new file mode 100644 index 0000000..6643c7f --- /dev/null +++ b/src/etc/host.conf @@ -0,0 +1,7 @@ +# $FreeBSD: src/etc/host.conf,v 1.6 1999/08/27 23:23:41 peter Exp $ +# First try the /etc/hosts file +hosts +# Now try the nameserver next. +bind +# If you have YP/NIS configured, uncomment the next line +# nis diff --git a/src/etc/hosts.allow b/src/etc/hosts.allow new file mode 100644 index 0000000..ab11cc0 --- /dev/null +++ b/src/etc/hosts.allow @@ -0,0 +1,5 @@ +# +# hosts.allow access control file for "tcp wrapped" applications. +# +ALL : ALL : allow + diff --git a/src/etc/inc/CHAP.inc b/src/etc/inc/CHAP.inc new file mode 100644 index 0000000..6eb22f7 --- /dev/null +++ b/src/etc/inc/CHAP.inc @@ -0,0 +1,463 @@ +<?php +/* +Copyright (c) 2002-2010, Michael Bretterklieber <michael@bretterklieber.com> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +This code cannot simply be copied and put under the GNU Public License or +any other GPL-like (LGPL, GPL2) License. + + $Id: CHAP.php 302857 2010-08-28 21:12:59Z mbretter $ +*/ + +require_once 'PEAR.inc'; + +/** +* Classes for generating packets for various CHAP Protocols: +* CHAP-MD5: RFC1994 +* MS-CHAPv1: RFC2433 +* MS-CHAPv2: RFC2759 +* +* @package Crypt_CHAP +* @author Michael Bretterklieber <michael@bretterklieber.com> +* @access public +* @version $Revision: 302857 $ +*/ + +/** + * class Crypt_CHAP + * + * Abstract base class for CHAP + * + * @package Crypt_CHAP + */ +class Crypt_CHAP extends PEAR +{ + /** + * Random binary challenge + * @var string + */ + var $challenge = null; + + /** + * Binary response + * @var string + */ + var $response = null; + + /** + * User password + * @var string + */ + var $password = null; + + /** + * Id of the authentication request. Should incremented after every request. + * @var integer + */ + var $chapid = 1; + + /** + * Constructor + * + * Generates a random challenge + * @return void + */ + function Crypt_CHAP() + { + $this->PEAR(); + $this->generateChallenge(); + } + + /** + * Generates a random binary challenge + * + * @param string $varname Name of the property + * @param integer $size Size of the challenge in Bytes + * @return void + */ + function generateChallenge($varname = 'challenge', $size = 8) + { + $this->$varname = ''; + for ($i = 0; $i < $size; $i++) { + $this->$varname .= pack('C', 1 + mt_rand() % 255); + } + return $this->$varname; + } + + /** + * Generates the response. Overwrite this. + * + * @return void + */ + function challengeResponse() + { + } + +} + +/** + * class Crypt_CHAP_MD5 + * + * Generate CHAP-MD5 Packets + * + * @package Crypt_CHAP + */ +class Crypt_CHAP_MD5 extends Crypt_CHAP +{ + + /** + * Generates the response. + * + * CHAP-MD5 uses MD5-Hash for generating the response. The Hash consists + * of the chapid, the plaintext password and the challenge. + * + * @return string + */ + function challengeResponse() + { + return pack('H*', md5(pack('C', $this->chapid) . $this->password . $this->challenge)); + } +} + +/** + * class Crypt_CHAP_MSv1 + * + * Generate MS-CHAPv1 Packets. MS-CHAP doesen't use the plaintext password, it uses the + * NT-HASH wich is stored in the SAM-Database or in the smbpasswd, if you are using samba. + * The NT-HASH is MD4(str2unicode(plaintextpass)). + * You need the hash extension for this class. + * + * @package Crypt_CHAP + */ +class Crypt_CHAP_MSv1 extends Crypt_CHAP +{ + /** + * Wether using deprecated LM-Responses or not. + * 0 = use LM-Response, 1 = use NT-Response + * @var bool + */ + var $flags = 1; + + /** + * Constructor + * + * Loads the hash extension + * @return void + */ + function Crypt_CHAP_MSv1() + { + $this->Crypt_CHAP(); + $this->loadExtension('hash'); + } + + /** + * Generates the NT-HASH from the given plaintext password. + * + * @access public + * @return string + */ + function ntPasswordHash($password = null) + { + if (isset($password)) { + return pack('H*',hash('md4', $this->str2unicode($password))); + } else { + return pack('H*',hash('md4', $this->str2unicode($this->password))); + } + } + + /** + * Converts ascii to unicode. + * + * @access public + * @return string + */ + function str2unicode($str) + { + $uni = ''; + $str = (string) $str; + for ($i = 0; $i < strlen($str); $i++) { + $a = ord($str{$i}) << 8; + $uni .= sprintf("%X", $a); + } + return pack('H*', $uni); + } + + /** + * Generates the NT-Response. + * + * @access public + * @return string + */ + function challengeResponse() + { + return $this->_challengeResponse(); + } + + /** + * Generates the NT-Response. + * + * @access public + * @return string + */ + function ntChallengeResponse() + { + return $this->_challengeResponse(false); + } + + /** + * Generates the LAN-Manager-Response. + * + * @access public + * @return string + */ + function lmChallengeResponse() + { + return $this->_challengeResponse(true); + } + + /** + * Generates the response. + * + * Generates the response using DES. + * + * @param bool $lm wether generating LAN-Manager-Response + * @access private + * @return string + */ + function _challengeResponse($lm = false) + { + if ($lm) { + $hash = $this->lmPasswordHash(); + } else { + $hash = $this->ntPasswordHash(); + } + + while (strlen($hash) < 21) { + $hash .= "\0"; + } + + $td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, ''); + $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND); + $key = $this->_desAddParity(substr($hash, 0, 7)); + mcrypt_generic_init($td, $key, $iv); + $resp1 = mcrypt_generic($td, $this->challenge); + mcrypt_generic_deinit($td); + + $key = $this->_desAddParity(substr($hash, 7, 7)); + mcrypt_generic_init($td, $key, $iv); + $resp2 = mcrypt_generic($td, $this->challenge); + mcrypt_generic_deinit($td); + + $key = $this->_desAddParity(substr($hash, 14, 7)); + mcrypt_generic_init($td, $key, $iv); + $resp3 = mcrypt_generic($td, $this->challenge); + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + return $resp1 . $resp2 . $resp3; + } + + /** + * Generates the LAN-Manager-HASH from the given plaintext password. + * + * @access public + * @return string + */ + function lmPasswordHash($password = null) + { + $plain = isset($password) ? $password : $this->password; + + $plain = substr(strtoupper($plain), 0, 14); + while (strlen($plain) < 14) { + $plain .= "\0"; + } + + return $this->_desHash(substr($plain, 0, 7)) . $this->_desHash(substr($plain, 7, 7)); + } + + /** + * Generates an irreversible HASH. + * + * @access private + * @return string + */ + function _desHash($plain) + { + $key = $this->_desAddParity($plain); + $td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, ''); + $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND); + mcrypt_generic_init($td, $key, $iv); + $hash = mcrypt_generic($td, 'KGS!@#$%'); + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + return $hash; + } + + /** + * Adds the parity bit to the given DES key. + * + * @access private + * @param string $key 7-Bytes Key without parity + * @return string + */ + function _desAddParity($key) + { + static $odd_parity = array( + 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, + 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, + 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, + 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62, + 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, + 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, + 97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110, + 112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127, + 128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143, + 145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158, + 161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174, + 176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191, + 193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206, + 208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223, + 224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239, + 241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254); + + $bin = ''; + for ($i = 0; $i < strlen($key); $i++) { + $bin .= sprintf('%08s', decbin(ord($key{$i}))); + } + + $str1 = explode('-', substr(chunk_split($bin, 7, '-'), 0, -1)); + $x = ''; + foreach($str1 as $s) { + $x .= sprintf('%02s', dechex($odd_parity[bindec($s . '0')])); + } + + return pack('H*', $x); + + } + + /** + * Generates the response-packet. + * + * @param bool $lm wether including LAN-Manager-Response + * @access private + * @return string + */ + function response($lm = false) + { + $ntresp = $this->ntChallengeResponse(); + if ($lm) { + $lmresp = $this->lmChallengeResponse(); + } else { + $lmresp = str_repeat ("\0", 24); + } + + // Response: LM Response, NT Response, flags (0 = use LM Response, 1 = use NT Response) + return $lmresp . $ntresp . pack('C', !$lm); + } +} + +/** + * class Crypt_CHAP_MSv2 + * + * Generate MS-CHAPv2 Packets. This version of MS-CHAP uses a 16 Bytes authenticator + * challenge and a 16 Bytes peer Challenge. LAN-Manager responses no longer exists + * in this version. The challenge is already a SHA1 challenge hash of both challenges + * and of the username. + * + * @package Crypt_CHAP + */ +class Crypt_CHAP_MSv2 extends Crypt_CHAP_MSv1 +{ + /** + * The username + * @var string + */ + var $username = null; + + /** + * The 16 Bytes random binary peer challenge + * @var string + */ + var $peerChallenge = null; + + /** + * The 16 Bytes random binary authenticator challenge + * @var string + */ + var $authChallenge = null; + + /** + * Constructor + * + * Generates the 16 Bytes peer and authentication challenge + * @return void + */ + function Crypt_CHAP_MSv2() + { + $this->Crypt_CHAP_MSv1(); + $this->generateChallenge('peerChallenge', 16); + $this->generateChallenge('authChallenge', 16); + } + + /** + * Generates a hash from the NT-HASH. + * + * @access public + * @param string $nthash The NT-HASH + * @return string + */ + function ntPasswordHashHash($nthash) + { + return pack('H*',hash('md4', $nthash)); + } + + /** + * Generates the challenge hash from the peer and the authenticator challenge and + * the username. SHA1 is used for this, but only the first 8 Bytes are used. + * + * @access public + * @return string + */ + function challengeHash() + { + return substr(pack('H*',hash('sha1', $this->peerChallenge . $this->authChallenge . $this->username)), 0, 8); + } + + /** + * Generates the response. + * + * @access public + * @return string + */ + function challengeResponse() + { + $this->challenge = $this->challengeHash(); + return $this->_challengeResponse(); + } +} + + +?> diff --git a/src/etc/inc/IPv6.inc b/src/etc/inc/IPv6.inc new file mode 100644 index 0000000..faacb8d --- /dev/null +++ b/src/etc/inc/IPv6.inc @@ -0,0 +1,1110 @@ +<?php + +/* + pfSense_MODULE: utils +*/ + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * This file contains the implementation of the Net_IPv6 class + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to the New BSD license, that is + * available through the world-wide-web at + * http://www.opensource.org/licenses/bsd-license.php + * If you did not receive a copy of the new BSDlicense and are unable + * to obtain it through the world-wide-web, please send a note to + * license@php.net so we can mail you a copy immediately + * + * @category Net + * @package Net_IPv6 + * @author Alexander Merz <alexander.merz@web.de> + * @copyright 2003-2005 The PHP Group + * @license BSD License http://www.opensource.org/licenses/bsd-license.php + * @version CVS: $Id$ + * @link http://pear.php.net/package/Net_IPv6 + */ + +// {{{ constants + +/** + * Error message if netmask bits was not found + * @see isInNetmask + */ +define("NET_IPV6_NO_NETMASK_MSG", "Netmask length not found"); + +/** + * Error code if netmask bits was not found + * @see isInNetmask + */ +define("NET_IPV6_NO_NETMASK", 10); + +/** + * Address Type: Unassigned (RFC 1884, Section 2.3) + * @see getAddressType() + */ +define("NET_IPV6_UNASSIGNED", 1); + +/** + * Address Type: Reserved (RFC 1884, Section 2.3) + * @see getAddressType() + */ +define("NET_IPV6_RESERVED", 11); + +/** + * Address Type: Reserved for NSAP Allocation (RFC 1884, Section 2.3) + * @see getAddressType() + */ +define("NET_IPV6_RESERVED_NSAP", 12); + +/** + * Address Type: Reserved for IPX Allocation (RFC 1884, Section 2.3) + * @see getAddressType() + */ +define("NET_IPV6_RESERVED_IPX", 13); + +/** + * Address Type: Reserved for Geographic-Based Unicast Addresses + * (RFC 1884, Section 2.3) + * @see getAddressType() + */ +define("NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC", 14); + +/** + * Address Type: Provider-Based Unicast Address (RFC 1884, Section 2.3) + * @see getAddressType() + */ +define("NET_IPV6_UNICAST_PROVIDER", 22); + +/** + * Address Type: Multicast Addresses (RFC 1884, Section 2.3) + * @see getAddressType() + */ +define("NET_IPV6_MULTICAST", 31); + +/** + * Address Type: Link Local Use Addresses (RFC 1884, Section 2.3) + * @see getAddressType() + */ +define("NET_IPV6_LOCAL_LINK", 42); + +/** + * Address Type: Link Local Use Addresses (RFC 1884, Section 2.3) + * @see getAddressType() + */ +define("NET_IPV6_LOCAL_SITE", 43); + +/** + * Address Type: Address range to embedded IPv4 ip in an IPv6 address (RFC 4291, Section 2.5.5) + * @see getAddressType() + */ +define("NET_IPV6_IPV4MAPPING", 51); + +/** + * Address Type: Unspecified (RFC 4291, Section 2.5.2) + * @see getAddressType() + */ +define("NET_IPV6_UNSPECIFIED", 52); + +/** + * Address Type: Unspecified (RFC 4291, Section 2.5.3) + * @see getAddressType() + */ +define("NET_IPV6_LOOPBACK", 53); + +/** + * Address Type: address can not assigned to a specific type + * @see getAddressType() + */ +define("NET_IPV6_UNKNOWN_TYPE", 1001); + +// }}} +// {{{ Net_IPv6 + +/** + * Class to validate and to work with IPv6 addresses. + * + * @category Net + * @package Net_IPv6 + * @author Alexander Merz <alexander.merz@web.de> + * @author <elfrink at introweb dot nl> + * @author Josh Peck <jmp at joshpeck dot org> + * @copyright 2003-2010 The PHP Group + * @license BSD License http://www.opensource.org/licenses/bsd-license.php + * @version Release: 1.1.0RC5 + * @link http://pear.php.net/package/Net_IPv6 + */ +class Net_IPv6 +{ + + // {{{ separate() + /** + * Separates an IPv6 address into the address and a prefix length part + * + * @param String $ip the (compressed) IP as Hex representation + * + * @return Array the first element is the IP, the second the prefix length + * @since 1.2.0 + * @access public + * @static + */ + static function separate($ip) + { + + $addr = $ip; + $spec = ''; + + if(false === strrpos($ip, '/')) { + + return array($addr, $spec); + + } + + $elements = explode('/', $ip); + + if(2 == count($elements)) { + + $addr = $elements[0]; + $spec = $elements[1]; + + } + + return array($addr, $spec); + + } + // }}} + + // {{{ removeNetmaskSpec() + + /** + * Removes a possible existing prefix length/ netmask specification at an IP address. + * + * @param String $ip the (compressed) IP as Hex representation + * + * @return String the IP without netmask length + * @since 1.1.0 + * @access public + * @static + */ + static function removeNetmaskSpec($ip) + { + + $elements = Net_IPv6::separate($ip); + + return $elements[0]; + + } + // }}} + // {{{ removePrefixLength() + + /** + * Tests for a prefix length specification in the address + * and removes the prefix length, if exists + * + * The method is technically identical to removeNetmaskSpec() and + * will be dropped in a future release. + * + * @param String $ip a valid ipv6 address + * + * @return String the address without a prefix length + * @access public + * @static + * @see removeNetmaskSpec() + * @deprecated + */ + static function removePrefixLength($ip) + { + $pos = strrpos($ip, '/'); + + if (false !== $pos) { + + return substr($ip, 0, $pos); + + } + + return $ip; + } + + // }}} + // {{{ getNetmaskSpec() + + /** + * Returns a possible existing prefix length/netmask specification on an IP address. + * + * @param String $ip the (compressed) IP as Hex representation + * + * @return String the netmask spec + * @since 1.1.0 + * @access public + * @static + */ + static function getNetmaskSpec($ip) + { + + $elements = Net_IPv6::separate($ip); + + return $elements[1]; + + } + + // }}} + // {{{ getPrefixLength() + + /** + * Tests for a prefix length specification in the address + * and returns the prefix length, if exists + * + * The method is technically identical to getNetmaskSpec() and + * will be dropped in a future release. + * + * @param String $ip a valid ipv6 address + * + * @return Mixed the prefix as String or false, if no prefix was found + * @access public + * @static + * @deprecated + */ + static function getPrefixLength($ip) + { + if (preg_match("/^([0-9a-fA-F:]{2,39})\/(\d{1,3})*$/", + $ip, $matches)) { + + return $matches[2]; + + } else { + + return false; + + } + + } + + // }}} + // {{{ getNetmask() + + /** + * Calculates the network prefix based on the netmask bits. + * + * @param String $ip the (compressed) IP in Hex format + * @param int $bits if the number of netmask bits is not part of the IP + * you must provide the number of bits + * + * @return String the network prefix + * @since 1.1.0 + * @access public + * @static + */ + static function getNetmask($ip, $bits = null) + { + if (null==$bits) { + + $elements = explode('/', $ip); + + if (2 == count($elements)) { + + $addr = $elements[0]; + $bits = $elements[1]; + + } else { + + include_once 'PEAR.inc'; + + return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG, + NET_IPV6_NO_NETMASK); + } + + } else { + + $addr = $ip; + + } + + $addr = Net_IPv6::uncompress($addr); + $binNetmask = str_repeat('1', $bits).str_repeat('0', 128 - $bits); + + return Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($addr) & $binNetmask); + } + + // }}} + // {{{ isInNetmask() + + /** + * Checks if an (compressed) IP is in a specific address space. + * + * If the IP does not contain the number of netmask bits (F8000::FFFF/16) + * then you have to use the $bits parameter. + * + * @param String $ip the IP to check (eg. F800::FFFF) + * @param String $netmask the netmask (eg F800::) + * @param int $bits the number of netmask bits to compare, + * if not given in $ip + * + * @return boolean true if $ip is in the netmask + * @since 1.1.0 + * @access public + * @static + */ + static function isInNetmask($ip, $netmask, $bits=null) + { + // try to get the bit count + + if (null == $bits) { + + $elements = explode('/', $ip); + + if (2 == count($elements)) { + + $ip = $elements[0]; + $bits = $elements[1]; + + } else if (null == $bits) { + + $elements = explode('/', $netmask); + + if (2 == count($elements)) { + + $netmask = $elements[0]; + $bits = $elements[1]; + + } + + if (null == $bits) { + + include_once 'PEAR.inc'; + return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG, + NET_IPV6_NO_NETMASK); + + } + + } + + } + + $binIp = Net_IPv6::_ip2Bin(Net_IPv6::removeNetmaskSpec($ip)); + $binNetmask = Net_IPv6::_ip2Bin(Net_IPv6::removeNetmaskSpec($netmask)); + + if (null != $bits + && "" != $bits + && 0 == strncmp($binNetmask, $binIp, $bits)) { + + return true; + + } + + return false; + } + + // }}} + // {{{ getAddressType() + + /** + * Returns the type of an IPv6 address. + * + * RFC 2373, Section 2.3 describes several types of addresses in + * the IPv6 address space. + * Several address types are markers for reserved spaces and as + * a consequence are subject to change. + * + * @param String $ip the IP address in Hex format, + * compressed IPs are allowed + * + * @return int one of the address type constants + * @access public + * @since 1.1.0 + * @static + * + * @see NET_IPV6_UNASSIGNED + * @see NET_IPV6_RESERVED + * @see NET_IPV6_RESERVED_NSAP + * @see NET_IPV6_RESERVED_IPX + * @see NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC + * @see NET_IPV6_UNICAST_PROVIDER + * @see NET_IPV6_MULTICAST + * @see NET_IPV6_LOCAL_LINK + * @see NET_IPV6_LOCAL_SITE + * @see NET_IPV6_IPV4MAPPING + * @see NET_IPV6_UNSPECIFIED + * @see NET_IPV6_LOOPBACK + * @see NET_IPV6_UNKNOWN_TYPE + */ + static function getAddressType($ip) + { + $ip = Net_IPv6::removeNetmaskSpec($ip); + $binip = Net_IPv6::_ip2Bin($ip); + + if(0 == strncmp(str_repeat('0', 128), $binip, 128)) { // ::/128 + + return NET_IPV6_UNSPECIFIED; + + } else if(0 == strncmp(str_repeat('0', 127).'1', $binip, 128)) { // ::/128 + + return NET_IPV6_LOOPBACK; + + } else if (0 == strncmp(str_repeat('0', 80).str_repeat('1', 16), $binip, 96)) { // ::ffff/96 + + return NET_IPV6_IPV4MAPPING; + + } else if (0 == strncmp('1111111010', $binip, 10)) { + + return NET_IPV6_LOCAL_LINK; + + } else if (0 == strncmp('1111111011', $binip, 10)) { + + return NET_IPV6_LOCAL_SITE; + + } else if (0 == strncmp('111111100', $binip, 9)) { + + return NET_IPV6_UNASSIGNED; + + } else if (0 == strncmp('11111111', $binip, 8)) { + + return NET_IPV6_MULTICAST; + + } else if (0 == strncmp('00000000', $binip, 8)) { + + return NET_IPV6_RESERVED; + + } else if (0 == strncmp('00000001', $binip, 8) + || 0 == strncmp('1111110', $binip, 7)) { + + return NET_IPV6_UNASSIGNED; + + } else if (0 == strncmp('0000001', $binip, 7)) { + + return NET_IPV6_RESERVED_NSAP; + + } else if (0 == strncmp('0000010', $binip, 7)) { + + return NET_IPV6_RESERVED_IPX; + + } else if (0 == strncmp('0000011', $binip, 7) || + 0 == strncmp('111110', $binip, 6) || + 0 == strncmp('11110', $binip, 5) || + 0 == strncmp('00001', $binip, 5) || + 0 == strncmp('1110', $binip, 4) || + 0 == strncmp('0001', $binip, 4) || + 0 == strncmp('001', $binip, 3) || + 0 == strncmp('011', $binip, 3) || + 0 == strncmp('101', $binip, 3) || + 0 == strncmp('110', $binip, 3)) { + + return NET_IPV6_UNASSIGNED; + + } else if (0 == strncmp('010', $binip, 3)) { + + return NET_IPV6_UNICAST_PROVIDER; + + } else if (0 == strncmp('100', $binip, 3)) { + + return NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC; + + } + + return NET_IPV6_UNKNOWN_TYPE; + } + + // }}} + // {{{ Uncompress() + + /** + * Uncompresses an IPv6 address + * + * RFC 2373 allows you to compress zeros in an address to '::'. This + * function expects a valid IPv6 address and expands the '::' to + * the required zeros. + * + * Example: FF01::101 -> FF01:0:0:0:0:0:0:101 + * ::1 -> 0:0:0:0:0:0:0:1 + * + * Note: You can also pass an invalid IPv6 address (usually as part of the process + * of validation by checkIPv6, which will validate the return string). + * This function will insert the "0" between "::" even if this results in too many + * numbers in total. It is NOT the purpose of this function to validate your input. + * + * Example of calling with invalid input: 1::2:3:4:5:6:7:8:9 -> 1:0:2:3:4:5:6:7:8:9 + * + * @param String $ip a (possibly) valid IPv6-address (hex format) + * @param Boolean $leadingZeros if true, leading zeros are added to each + * block of the address + * (FF01::101 -> + * FF01:0000:0000:0000:0000:0000:0000:0101) + * + * @return String the uncompressed IPv6-address (hex format) + * @access public + * @see Compress() + * @static + * @author Pascal Uhlmann + */ + static function uncompress($ip, $leadingZeros = false) + { + + $prefix = Net_IPv6::getPrefixLength($ip); + + if (false === $prefix) { + + $prefix = ''; + + } else { + + $ip = Net_IPv6::removePrefixLength($ip); + $prefix = '/'.$prefix; + + } + + $netmask = Net_IPv6::getNetmaskSpec($ip); + $uip = Net_IPv6::removeNetmaskSpec($ip); + + $c1 = -1; + $c2 = -1; + + if (false !== strpos($uip, '::') ) { + + list($ip1, $ip2) = explode('::', $uip); + + if ("" == $ip1) { + + $c1 = -1; + + } else { + + $pos = 0; + + if (0 < ($pos = substr_count($ip1, ':'))) { + + $c1 = $pos; + + } else { + + $c1 = 0; + + } + } + if ("" == $ip2) { + + $c2 = -1; + + } else { + + $pos = 0; + + if (0 < ($pos = substr_count($ip2, ':'))) { + + $c2 = $pos; + + } else { + + $c2 = 0; + + } + + } + + if (strstr($ip2, '.')) { + + $c2++; + + } + if (-1 == $c1 && -1 == $c2) { // :: + + $uip = "0:0:0:0:0:0:0:0"; + + } else if (-1 == $c1) { // ::xxx + + $fill = str_repeat('0:', 7-$c2); + $uip = str_replace('::', $fill, $uip); + + } else if (-1 == $c2) { // xxx:: + + $fill = str_repeat(':0', 7-$c1); + $uip = str_replace('::', $fill, $uip); + + } else { // xxx::xxx + + $fill = str_repeat(':0:', max(1, 6-$c2-$c1)); + $uip = str_replace('::', $fill, $uip); + $uip = str_replace('::', ':', $uip); + + } + } + + if(true == $leadingZeros) { + + $uipT = array(); + $uiparts = explode(':', $uip); + + foreach($uiparts as $p) { + + $uipT[] = sprintf('%04s', $p); + + } + + $uip = implode(':', $uipT); + } + + if ('' != $netmask) { + + $uip = $uip.'/'.$netmask; + + } + + return $uip.$prefix; + } + + // }}} + // {{{ Compress() + + /** + * Compresses an IPv6 address + * + * RFC 2373 allows you to compress zeros in an address to '::'. This + * function expects a valid IPv6 address and compresses successive zeros + * to '::' + * + * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 + * 0:0:0:0:0:0:0:1 -> ::1 + * + * When $ip is an already compressed address and $force is false, the method returns + * the value as is, even if the address can be compressed further. + * + * Example: FF01::0:1 -> FF01::0:1 + * + * To enforce maximum compression, you can set the second argument $force to true. + * + * Example: FF01::0:1 -> FF01::1 + * + * @param String $ip a valid IPv6-address (hex format) + * @param boolean $force if true the address will be compressed as best as possible (since 1.2.0) + * + * @return String the compressed IPv6-address (hex format) + * @access public + * @see Uncompress() + * @static + * @author elfrink at introweb dot nl + */ + static function compress($ip, $force = false) + { + + if(false !== strpos($ip, '::')) { // its already compressed + + if(true == $force) { + + $ip = Net_IPv6::uncompress($ip); + + } else { + + return $ip; + + } + + } + + $prefix = Net_IPv6::getPrefixLength($ip); + + if (false === $prefix) { + + $prefix = ''; + + } else { + + $ip = Net_IPv6::removePrefixLength($ip); + $prefix = '/'.$prefix; + + } + + $netmask = Net_IPv6::getNetmaskSpec($ip); + $ip = Net_IPv6::removeNetmaskSpec($ip); + + $ipp = explode(':', $ip); + + for ($i = 0; $i < count($ipp); $i++) { + + $ipp[$i] = dechex(hexdec($ipp[$i])); + + } + + $cip = ':' . join(':', $ipp) . ':'; + + preg_match_all("/(:0)(:0)+/", $cip, $zeros); + + if (count($zeros[0]) > 0) { + + $match = ''; + + foreach ($zeros[0] as $zero) { + + if (strlen($zero) > strlen($match)) { + + $match = $zero; + + } + } + + $cip = preg_replace('/' . $match . '/', ':', $cip, 1); + + } + + $cip = preg_replace('/((^:)|(:$))/', '', $cip); + $cip = preg_replace('/((^:)|(:$))/', '::', $cip); + + if (empty($cip)) { + + $cip = "::"; + + } + + if ('' != $netmask) { + + $cip = $cip.'/'.$netmask; + + } + + return $cip.$prefix; + + } + + // }}} + // {{{ recommendedFormat() + /** + * Represent IPv6 address in RFC5952 format. + * + * @param String $ip a valid IPv6-address (hex format) + * + * @return String the recommended representation of IPv6-address (hex format) + * @access public + * @see compress() + * @static + * @author koyama at hoge dot org + * @todo This method may become a part of compress() in a further releases + */ + static function recommendedFormat($ip) + { + $compressed = self::compress($ip, true); + // RFC5952 4.2.2 + // The symbol "::" MUST NOT be used to shorten just one + // 16-bit 0 field. + if ((substr_count($compressed, ':') == 7) && + (strpos($compressed, '::') !== false)) { + $compressed = str_replace('::', ':0:', $compressed); + } + return $compressed; + } + // }}} + + // {{{ isCompressible() + + /** + * Checks, if an IPv6 address can be compressed + * + * @param String $ip a valid IPv6 address + * + * @return Boolean true, if address can be compressed + * + * @access public + * @since 1.2.0b + * @static + * @author Manuel Schmitt + */ + static function isCompressible($ip) + { + + return (bool)($ip != Net_IPv6::compress($address)); + + } + + // }}} + // {{{ SplitV64() + + /** + * Splits an IPv6 address into the IPv6 and a possible IPv4 part + * + * RFC 2373 allows you to note the last two parts of an IPv6 address as + * an IPv4 compatible address + * + * Example: 0:0:0:0:0:0:13.1.68.3 + * 0:0:0:0:0:FFFF:129.144.52.38 + * + * @param String $ip a valid IPv6-address (hex format) + * @param Boolean $uncompress if true, the address will be uncompressed + * before processing + * + * @return Array [0] contains the IPv6 part, + * [1] the IPv4 part (hex format) + * @access public + * @static + */ + static function SplitV64($ip, $uncompress = true) + { + $ip = Net_IPv6::removeNetmaskSpec($ip); + + if ($uncompress) { + + $ip = Net_IPv6::Uncompress($ip); + + } + + if (strstr($ip, '.')) { + + $pos = strrpos($ip, ':'); + $ip{$pos} = '_'; + $ipPart = explode('_', $ip); + + return $ipPart; + + } else { + + return array($ip, ""); + + } + } + + // }}} + // {{{ checkIPv6() + + /** + * Checks an IPv6 address + * + * Checks if the given IP is IPv6-compatible + * + * @param String $ip a valid IPv6-address + * + * @return Boolean true if $ip is an IPv6 address + * @access public + * @static + */ + static function checkIPv6($ip) + { + + $elements = Net_IPv6::separate($ip); + + $ip = $elements[0]; + + if('' != $elements[1] && ( !is_numeric($elements[1]) || 0 > $elements[1] || 128 < $elements[1])) { + + return false; + + } + + $ipPart = Net_IPv6::SplitV64($ip); + $count = 0; + + if (!empty($ipPart[0])) { + $ipv6 = explode(':', $ipPart[0]); + + foreach($ipv6 as $element) { // made a validate precheck + if(!preg_match('/[0-9a-fA-F]*/', $element)) { + return false; + } + } + + for ($i = 0; $i < count($ipv6); $i++) { + + if(4 < strlen($ipv6[$i])) { + + return false; + + } + + $dec = hexdec($ipv6[$i]); + $hex = strtoupper(preg_replace("/^[0]{1,3}(.*[0-9a-fA-F])$/", + "\\1", + $ipv6[$i])); + + if ($ipv6[$i] >= 0 && $dec <= 65535 + && $hex == strtoupper(dechex($dec))) { + + $count++; + + } + + } + + if (8 == $count) { + + return true; + + } else if (6 == $count and !empty($ipPart[1])) { + + $ipv4 = explode('.', $ipPart[1]); + $count = 0; + + for ($i = 0; $i < count($ipv4); $i++) { + + if ($ipv4[$i] >= 0 && (integer)$ipv4[$i] <= 255 + && preg_match("/^\d{1,3}$/", $ipv4[$i])) { + + $count++; + + } + + } + + if (4 == $count) { + + return true; + + } + + } else { + + return false; + + } + + } else { + + return false; + + } + + } + + // }}} + + // {{{ _parseAddress() + + /** + * Returns the lowest and highest IPv6 address + * for a given IP and netmask specification + * + * The netmask may be a part of the $ip or + * the number of netmask bits is provided via $bits + * + * The result is an indexed array. The key 'start' + * contains the lowest possible IP address. The key + * 'end' the highest address. + * + * @param String $ipToParse the IPv6 address + * @param String $bits the optional count of netmask bits + * + * @return Array ['start', 'end'] the lowest and highest IPv6 address + * @access public + * @static + * @author Nicholas Williams + */ + + static function parseAddress($ipToParse, $bits = null) + { + + $ip = null; + $bitmask = null; + + if ( null == $bits ) { + + $elements = explode('/', $ipToParse); + + if ( 2 == count($elements) ) { + + $ip = Net_IPv6::uncompress($elements[0]); + $bitmask = $elements[1]; + + } else { + + include_once 'PEAR.inc'; + + return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG, + NET_IPV6_NO_NETMASK); + } + } else { + + $ip = Net_IPv6::uncompress($ipToParse); + $bitmask = $bits; + + } + + $binNetmask = str_repeat('1', $bitmask). + str_repeat('0', 128 - $bitmask); + $maxNetmask = str_repeat('1', 128); + $netmask = Net_IPv6::_bin2Ip($binNetmask); + + $startAddress = Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($ip) + & $binNetmask); + $endAddress = Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($ip) + | ($binNetmask ^ $maxNetmask)); + + return array('start' => $startAddress, 'end' => $endAddress); + } + + // }}} + + // {{{ _ip2Bin() + + /** + * Converts an IPv6 address from Hex into Binary representation. + * + * @param String $ip the IP to convert (a:b:c:d:e:f:g:h), + * compressed IPs are allowed + * + * @return String the binary representation + * @access private + @ @since 1.1.0 + */ + static function _ip2Bin($ip) + { + $binstr = ''; + + $ip = Net_IPv6::removeNetmaskSpec($ip); + $ip = Net_IPv6::Uncompress($ip); + + $parts = explode(':', $ip); + + foreach ( $parts as $v ) { + + $str = base_convert($v, 16, 2); + $binstr .= str_pad($str, 16, '0', STR_PAD_LEFT); + + } + + return $binstr; + } + + // }}} + // {{{ _bin2Ip() + + /** + * Converts an IPv6 address from Binary into Hex representation. + * + * @param String $bin the IP address as binary + * + * @return String the uncompressed Hex representation + * @access private + @ @since 1.1.0 + */ + static function _bin2Ip($bin) + { + $ip = ""; + + if (strlen($bin) < 128) { + + $bin = str_pad($bin, 128, '0', STR_PAD_LEFT); + + } + + $parts = str_split($bin, "16"); + + foreach ( $parts as $v ) { + + $str = base_convert($v, 2, 16); + $ip .= $str.":"; + + } + + $ip = substr($ip, 0, -1); + + return $ip; + } + + // }}} +} +// }}} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ + +?> diff --git a/src/etc/inc/PEAR.inc b/src/etc/inc/PEAR.inc new file mode 100644 index 0000000..a280602 --- /dev/null +++ b/src/etc/inc/PEAR.inc @@ -0,0 +1,1103 @@ +<?php +/** + * PEAR, the PHP Extension and Application Repository + * + * PEAR class and PEAR_Error class + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Sterling Hughes <sterling@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2010 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Greg Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + + if ($error_class !== null) { + $this->_error_class = $error_class; + } + + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class description about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + + return $properties[$class][$var]; + } + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + function registerShutdownFunc($func, $args = array()) + { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + function isError($data, $code = null) + { + if (!is_object($data)) { + return false; + } + if (!is_a($data, 'PEAR_Error')) { + return false; + } + + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } + + return $data->getCode() == $code; + } + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return count($this->_expected_errors); + } + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + foreach ($this->_expected_errors as $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + + return $deleted; + } + + /** + * This method deletes all occurrences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; we walk through it trying + // to unset all values + foreach ($error_code as $key => $error) { + $deleted = $this->_checkDelExpect($error) ? true : false; + } + + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } + + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + function &raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + + // Make sure right data gets passed. + $r = new ReflectionClass($error_class); + $c = $r->getConstructor(); + $p = array_shift($c->getParameters()); + $skipmsg = ($p->getName() != 'message'); + } + + if ( + isset($this) && + isset($this->_expected_errors) && + count($this->_expected_errors) > 0 && + count($exp = end($this->_expected_errors)) + ) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp)) + ) { + $mode = PEAR_ERROR_RETURN; + } + } + + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + + if (intval(PHP_VERSION) < 5) { + // little non-eval hack to fix bug #12147 + include 'PEAR/FixPHP5PEARWarnings.php'; + return $a; + } + + if ($skipmsg) { + $a = new $ec($code, $mode, $options, $userinfo); + } else { + $a = new $ec($message, $code, $mode, $options, $userinfo); + } + + return $a; + } + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @access public + * @return object a PEAR error object + * @see PEAR::raiseError + */ + function &throwError($message = null, $code = null, $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } + + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + + function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + /** + * OS independent PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + static function loadExtension($ext) + { + if (extension_loaded($ext)) { + return true; + } + + // if either returns true dl() will produce a FATAL error, stop that + if ( + function_exists('dl') === false || + ini_get('enable_dl') != 1 || + ini_get('safe_mode') == 1 + ) { + return false; + } + + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } +} + +if (PEAR_ZE2) { + /** + * This is only meant for PHP 5 to get rid of certain strict warning + * that doesn't get hidden since it's in the shutdown function + */ + class PEAR5 + { + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR5::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + static function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + + return $properties[$class][$var]; + } + } +} + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (PEAR_ZE2) { + $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo'); + } else { + $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); + } + + if ($destructLifoExists) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if ( + isset($GLOBALS['_PEAR_shutdown_funcs']) && + is_array($GLOBALS['_PEAR_shutdown_funcs']) && + !empty($GLOBALS['_PEAR_shutdown_funcs']) + ) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +/** + * Standard PEAR error class for PHP 4 + * + * This class is superseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Gregory Beaver <cellog@php.net> + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + + if (PEAR_ZE2) { + $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace'); + } else { + $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); + } + + if (!$skiptrace) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); + } + } + + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + + $this->level = $options; + $this->callback = null; + } + + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + + printf($format, $this->getMessage()); + } + + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + + if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() + { + return $this->mode; + } + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() + { + return $this->callback; + } + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + function __toString() + { + return $this->getMessage(); + } + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() + { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ diff --git a/src/etc/inc/auth.inc b/src/etc/inc/auth.inc new file mode 100644 index 0000000..3c0acaa --- /dev/null +++ b/src/etc/inc/auth.inc @@ -0,0 +1,1627 @@ +<?php +/* $Id$ */ +/* + Copyright (C) 2010 Ermal Luçi + All rights reserved. + + Copyright (C) 2007, 2008 Scott Ullrich <sullrich@gmail.com> + All rights reserved. + + Copyright (C) 2005-2006 Bill Marquette <bill.marquette@gmail.com> + All rights reserved. + + Copyright (C) 2006 Paul Taylor <paultaylor@winn-dixie.com>. + All rights reserved. + + Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + pfSense_BUILDER_BINARIES: /usr/sbin/pw /bin/cp + pfSense_MODULE: auth +*/ + +/* + * NOTE : Portions of the mschapv2 support was based on the BSD licensed CHAP.php + * file courtesy of Michael Retterklieber. + */ +if (!$do_not_include_config_gui_inc) { + require_once("config.gui.inc"); +} + +// Will be changed to false if security checks fail +$security_passed = true; + +/* If this function doesn't exist, we're being called from Captive Portal or + another internal subsystem which does not include authgui.inc */ +if (function_exists("display_error_form") && !isset($config['system']['webgui']['nodnsrebindcheck'])) { + /* DNS ReBinding attack prevention. https://redmine.pfsense.org/issues/708 */ + $found_host = false; + + /* Either a IPv6 address with or without a alternate port */ + if (strstr($_SERVER['HTTP_HOST'], "]")) { + $http_host_port = explode("]", $_SERVER['HTTP_HOST']); + /* v6 address has more parts, drop the last part */ + if (count($http_host_port) > 1) { + array_pop($http_host_port); + $http_host = str_replace(array("[", "]"), "", implode(":", $http_host_port)); + } else { + $http_host = str_replace(array("[", "]"), "", implode(":", $http_host_port)); + } + } else { + $http_host = explode(":", $_SERVER['HTTP_HOST']); + $http_host = $http_host[0]; + } + if (is_ipaddr($http_host) or $_SERVER['SERVER_ADDR'] == "127.0.0.1" or + strcasecmp($http_host, "localhost") == 0 or $_SERVER['SERVER_ADDR'] == "::1") { + $found_host = true; + } + if (strcasecmp($http_host, $config['system']['hostname'] . "." . $config['system']['domain']) == 0 or + strcasecmp($http_host, $config['system']['hostname']) == 0) { + $found_host = true; + } + + if (is_array($config['dyndnses']['dyndns']) && !$found_host) { + foreach ($config['dyndnses']['dyndns'] as $dyndns) { + if (strcasecmp($dyndns['host'], $http_host) == 0) { + $found_host = true; + break; + } + } + } + + if (is_array($config['dnsupdates']['dnsupdate']) && !$found_host) { + foreach ($config['dnsupdates']['dnsupdate'] as $rfc2136) { + if (strcasecmp($rfc2136['host'], $http_host) == 0) { + $found_host = true; + break; + } + } + } + + if (!empty($config['system']['webgui']['althostnames']) && !$found_host) { + $althosts = explode(" ", $config['system']['webgui']['althostnames']); + foreach ($althosts as $ah) { + if (strcasecmp($ah, $http_host) == 0 or strcasecmp($ah, $_SERVER['SERVER_ADDR']) == 0) { + $found_host = true; + break; + } + } + } + + if ($found_host == false) { + if (!security_checks_disabled()) { + display_error_form("501", gettext("Potential DNS Rebind attack detected, see http://en.wikipedia.org/wiki/DNS_rebinding<br />Try accessing the router by IP address instead of by hostname.")); + exit; + } + $security_passed = false; + } +} + +// If the HTTP_REFERER is something other than ourselves then disallow. +if (function_exists("display_error_form") && !isset($config['system']['webgui']['nohttpreferercheck'])) { + if ($_SERVER['HTTP_REFERER']) { + if (file_exists("{$g['tmp_path']}/setupwizard_lastreferrer")) { + if ($_SERVER['HTTP_REFERER'] == file_get_contents("{$g['tmp_path']}/setupwizard_lastreferrer")) { + unlink("{$g['tmp_path']}/setupwizard_lastreferrer"); + header("Refresh: 1; url=index.php"); + echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"; + echo "<html><head><title>" . gettext("Redirecting...") . "</title></head><body>" . gettext("Redirecting to the dashboard...") . "</body></html>"; + exit; + } + } + $found_host = false; + $referrer_host = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST); + $referrer_host = str_replace(array("[", "]"), "", $referrer_host); + if ($referrer_host) { + if (strcasecmp($referrer_host, $config['system']['hostname'] . "." . $config['system']['domain']) == 0 || + strcasecmp($referrer_host, $config['system']['hostname']) == 0) { + $found_host = true; + } + + if (!empty($config['system']['webgui']['althostnames']) && !$found_host) { + $althosts = explode(" ", $config['system']['webgui']['althostnames']); + foreach ($althosts as $ah) { + if (strcasecmp($referrer_host, $ah) == 0) { + $found_host = true; + break; + } + } + } + + if (is_array($config['dyndnses']['dyndns']) && !$found_host) { + foreach ($config['dyndnses']['dyndns'] as $dyndns) { + if (strcasecmp($dyndns['host'], $referrer_host) == 0) { + $found_host = true; + break; + } + } + } + + if (is_array($config['dnsupdates']['dnsupdate']) && !$found_host) { + foreach ($config['dnsupdates']['dnsupdate'] as $rfc2136) { + if (strcasecmp($rfc2136['host'], $referrer_host) == 0) { + $found_host = true; + break; + } + } + } + + if (!$found_host) { + $interface_list_ips = get_configured_ip_addresses(); + foreach ($interface_list_ips as $ilips) { + if (strcasecmp($referrer_host, $ilips) == 0) { + $found_host = true; + break; + } + } + $interface_list_ipv6s = get_configured_ipv6_addresses(); + foreach ($interface_list_ipv6s as $ilipv6s) { + if (strcasecmp($referrer_host, $ilipv6s) == 0) { + $found_host = true; + break; + } + } + if ($referrer_host == "127.0.0.1" || $referrer_host == "localhost") { + // allow SSH port forwarded connections and links from localhost + $found_host = true; + } + } + } + if ($found_host == false) { + if (!security_checks_disabled()) { + display_error_form("501", "An HTTP_REFERER was detected other than what is defined in System -> Advanced (" . htmlspecialchars($_SERVER['HTTP_REFERER']) . "). You can disable this check if needed in System -> Advanced -> Admin."); + exit; + } + $security_passed = false; + } + } else { + $security_passed = false; + } +} + +if (function_exists("display_error_form") && $security_passed) { + /* Security checks passed, so it should be OK to turn them back on */ + restore_security_checks(); +} +unset($security_passed); + +$groupindex = index_groups(); +$userindex = index_users(); + +function index_groups() { + global $g, $debug, $config, $groupindex; + + $groupindex = array(); + + if (is_array($config['system']['group'])) { + $i = 0; + foreach ($config['system']['group'] as $groupent) { + $groupindex[$groupent['name']] = $i; + $i++; + } + } + + return ($groupindex); +} + +function index_users() { + global $g, $debug, $config; + + if (is_array($config['system']['user'])) { + $i = 0; + foreach ($config['system']['user'] as $userent) { + $userindex[$userent['name']] = $i; + $i++; + } + } + + return ($userindex); +} + +function & getUserEntry($name) { + global $debug, $config, $userindex; + if (isset($userindex[$name])) { + return $config['system']['user'][$userindex[$name]]; + } +} + +function & getUserEntryByUID($uid) { + global $debug, $config; + + if (is_array($config['system']['user'])) { + foreach ($config['system']['user'] as & $user) { + if ($user['uid'] == $uid) { + return $user; + } + } + } + + return false; +} + +function & getGroupEntry($name) { + global $debug, $config, $groupindex; + if (isset($groupindex[$name])) { + return $config['system']['group'][$groupindex[$name]]; + } +} + +function & getGroupEntryByGID($gid) { + global $debug, $config; + + if (is_array($config['system']['group'])) { + foreach ($config['system']['group'] as & $group) { + if ($group['gid'] == $gid) { + return $group; + } + } + } + + return false; +} + +function get_user_privileges(& $user) { + + $privs = $user['priv']; + if (!is_array($privs)) { + $privs = array(); + } + + $names = local_user_get_groups($user, true); + + foreach ($names as $name) { + $group = getGroupEntry($name); + if (is_array($group['priv'])) { + $privs = array_merge($privs, $group['priv']); + } + } + + return $privs; +} + +function userHasPrivilege($userent, $privid = false) { + + if (!$privid || !is_array($userent)) { + return false; + } + + $privs = get_user_privileges($userent); + + if (!is_array($privs)) { + return false; + } + + if (!in_array($privid, $privs)) { + return false; + } + + return true; +} + +function local_backed($username, $passwd) { + + $user = getUserEntry($username); + if (!$user) { + return false; + } + + if (is_account_disabled($username) || is_account_expired($username)) { + return false; + } + + if ($user['password']) { + if (crypt($passwd, $user['password']) == $user['password']) { + return true; + } + } + + if ($user['md5-hash']) { + if (md5($passwd) == $user['md5-hash']) { + return true; + } + } + + return false; +} + +function local_sync_accounts() { + global $debug, $config; + conf_mount_rw(); + + /* remove local users to avoid uid conflicts */ + $fd = popen("/usr/sbin/pw usershow -a", "r"); + if ($fd) { + while (!feof($fd)) { + $line = explode(":", fgets($fd)); + if (((!strncmp($line[0], "_", 1)) || ($line[2] < 2000) || ($line[2] > 65000)) && ($line[0] != "admin")) { + continue; + } + /* + * If a crontab was created to user, pw userdel will be interactive and + * can cause issues. Just remove crontab before run it when necessary + */ + unlink_if_exists("/var/cron/tabs/{$line[0]}"); + $cmd = "/usr/sbin/pw userdel -n '{$line[0]}'"; + if ($debug) { + log_error(sprintf(gettext("Running: %s"), $cmd)); + } + mwexec($cmd); + } + pclose($fd); + } + + /* remove local groups to avoid gid conflicts */ + $gids = array(); + $fd = popen("/usr/sbin/pw groupshow -a", "r"); + if ($fd) { + while (!feof($fd)) { + $line = explode(":", fgets($fd)); + if (!strncmp($line[0], "_", 1)) { + continue; + } + if ($line[2] < 2000) { + continue; + } + if ($line[2] > 65000) { + continue; + } + $cmd = "/usr/sbin/pw groupdel {$line[2]}"; + if ($debug) { + log_error(sprintf(gettext("Running: %s"), $cmd)); + } + mwexec($cmd); + } + pclose($fd); + } + + /* make sure the all group exists */ + $allgrp = getGroupEntryByGID(1998); + local_group_set($allgrp, true); + + /* sync all local users */ + if (is_array($config['system']['user'])) { + foreach ($config['system']['user'] as $user) { + local_user_set($user); + } + } + + /* sync all local groups */ + if (is_array($config['system']['group'])) { + foreach ($config['system']['group'] as $group) { + local_group_set($group); + } + } + + conf_mount_ro(); + +} + +function local_user_set(& $user) { + global $g, $debug; + + if (empty($user['password'])) { + log_error("There is something wrong in your config because user {$user['name']} password is missing!"); + return; + } + + conf_mount_rw(); + + $home_base = "/home/"; + $user_uid = $user['uid']; + $user_name = $user['name']; + $user_home = "{$home_base}{$user_name}"; + $user_shell = "/etc/rc.initial"; + $user_group = "nobody"; + + // Ensure $home_base exists and is writable + if (!is_dir($home_base)) { + mkdir($home_base, 0755); + } + + $lock_account = false; + /* configure shell type */ + /* Cases here should be ordered by most privileged to least privileged. */ + if (userHasPrivilege($user, "user-shell-access") || userHasPrivilege($user, "page-all")) { + $user_shell = "/bin/tcsh"; + } elseif (userHasPrivilege($user, "user-copy-files")) { + $user_shell = "/usr/local/bin/scponly"; + } elseif (userHasPrivilege($user, "user-ssh-tunnel")) { + $user_shell = "/usr/local/sbin/ssh_tunnel_shell"; + } elseif (userHasPrivilege($user, "user-ipsec-xauth-dialin")) { + $user_shell = "/sbin/nologin"; + } else { + $user_shell = "/sbin/nologin"; + $lock_account = true; + } + + /* Lock out disabled or expired users, unless it's root/admin. */ + if ((is_account_disabled($user_name) || is_account_expired($user_name)) && ($user_uid != 0)) { + $user_shell = "/sbin/nologin"; + $lock_account = true; + } + + /* root user special handling */ + if ($user_uid == 0) { + $cmd = "/usr/sbin/pw usermod -q -n root -s /bin/sh -H 0"; + if ($debug) { + log_error(sprintf(gettext("Running: %s"), $cmd)); + } + $fd = popen($cmd, "w"); + fwrite($fd, $user['password']); + pclose($fd); + $user_group = "wheel"; + $user_home = "/root"; + $user_shell = "/etc/rc.initial"; + } + + /* read from pw db */ + $fd = popen("/usr/sbin/pw usershow -n {$user_name} 2>&1", "r"); + $pwread = fgets($fd); + pclose($fd); + $userattrs = explode(":", trim($pwread)); + + /* determine add or mod */ + if (($userattrs[0] != $user['name']) || (!strncmp($pwread, "pw:", 3))) { + $user_op = "useradd -m -k /etc/skel -o"; + } else { + $user_op = "usermod"; + } + + $comment = str_replace(array(":", "!", "@"), " ", $user['descr']); + /* add or mod pw db */ + $cmd = "/usr/sbin/pw {$user_op} -q -u {$user_uid} -n {$user_name}". + " -g {$user_group} -s {$user_shell} -d {$user_home}". + " -c ".escapeshellarg($comment)." -H 0 2>&1"; + + if ($debug) { + log_error(sprintf(gettext("Running: %s"), $cmd)); + } + $fd = popen($cmd, "w"); + fwrite($fd, $user['password']); + pclose($fd); + + /* create user directory if required */ + if (!is_dir($user_home)) { + mkdir($user_home, 0700); + } + @chown($user_home, $user_name); + @chgrp($user_home, $user_group); + + /* write out ssh authorized key file */ + if ($user['authorizedkeys']) { + if (!is_dir("{$user_home}/.ssh")) { + @mkdir("{$user_home}/.ssh", 0700); + @chown("{$user_home}/.ssh", $user_name); + } + $keys = base64_decode($user['authorizedkeys']); + @file_put_contents("{$user_home}/.ssh/authorized_keys", $keys); + @chown("{$user_home}/.ssh/authorized_keys", $user_name); + } else { + unlink_if_exists("{$user_home}/.ssh/authorized_keys"); + } + + $un = $lock_account ? "" : "un"; + exec("/usr/sbin/pw {$un}lock {$user_name} -q"); + + conf_mount_ro(); +} + +function local_user_del($user) { + global $debug; + + /* remove all memberships */ + local_user_set_groups($user); + + /* Don't remove /root */ + if ($user['uid'] != 0) { + $rmhome = "-r"; + } + + /* read from pw db */ + $fd = popen("/usr/sbin/pw usershow -n {$user['name']} 2>&1", "r"); + $pwread = fgets($fd); + pclose($fd); + $userattrs = explode(":", trim($pwread)); + + if ($userattrs[0] != $user['name']) { + log_error("Tried to remove user {$user['name']} but got user {$userattrs[0]} instead. Bailing."); + return; + } + + /* delete from pw db */ + $cmd = "/usr/sbin/pw userdel -n {$user['name']} {$rmhome}"; + + if ($debug) { + log_error(sprintf(gettext("Running: %s"), $cmd)); + } + mwexec($cmd); + + /* Delete user from groups needs a call to write_config() */ + local_group_del_user($user); +} + +function local_user_set_password(&$user, $password) { + + $user['password'] = crypt($password); + $user['md5-hash'] = md5($password); + + // Converts ascii to unicode. + $astr = (string) $password; + $ustr = ''; + for ($i = 0; $i < strlen($astr); $i++) { + $a = ord($astr{$i}) << 8; + $ustr .= sprintf("%X", $a); + } + +} + +function local_user_get_groups($user, $all = false) { + global $debug, $config; + + $groups = array(); + if (!is_array($config['system']['group'])) { + return $groups; + } + + foreach ($config['system']['group'] as $group) { + if ($all || (!$all && ($group['name'] != "all"))) { + if (is_array($group['member'])) { + if (in_array($user['uid'], $group['member'])) { + $groups[] = $group['name']; + } + } + } + } + + if ($all) { + $groups[] = "all"; + } + + sort($groups); + + return $groups; + +} + +function local_user_set_groups($user, $new_groups = NULL) { + global $debug, $config, $groupindex; + + if (!is_array($config['system']['group'])) { + return; + } + + $cur_groups = local_user_get_groups($user, true); + $mod_groups = array(); + + if (!is_array($new_groups)) { + $new_groups = array(); + } + + if (!is_array($cur_groups)) { + $cur_groups = array(); + } + + /* determine which memberships to add */ + foreach ($new_groups as $groupname) { + if ($groupname == '' || in_array($groupname, $cur_groups)) { + continue; + } + $group = & $config['system']['group'][$groupindex[$groupname]]; + $group['member'][] = $user['uid']; + $mod_groups[] = $group; + } + unset($group); + + /* determine which memberships to remove */ + foreach ($cur_groups as $groupname) { + if (in_array($groupname, $new_groups)) { + continue; + } + if (!isset($config['system']['group'][$groupindex[$groupname]])) { + continue; + } + $group = & $config['system']['group'][$groupindex[$groupname]]; + if (is_array($group['member'])) { + $index = array_search($user['uid'], $group['member']); + array_splice($group['member'], $index, 1); + $mod_groups[] = $group; + } + } + unset($group); + + /* sync all modified groups */ + foreach ($mod_groups as $group) { + local_group_set($group); + } +} + +function local_group_del_user($user) { + global $config; + + if (!is_array($config['system']['group'])) { + return; + } + + foreach ($config['system']['group'] as $group) { + if (is_array($group['member'])) { + foreach ($group['member'] as $idx => $uid) { + if ($user['uid'] == $uid) { + unset($config['system']['group']['member'][$idx]); + } + } + } + } +} + +function local_group_set($group, $reset = false) { + global $debug; + + $group_name = $group['name']; + $group_gid = $group['gid']; + $group_members = ''; + if (!$reset && !empty($group['member']) && count($group['member']) > 0) { + $group_members = implode(",", $group['member']); + } + + if (empty($group_name)) { + return; + } + + /* read from group db */ + $fd = popen("/usr/sbin/pw groupshow {$group_name} 2>&1", "r"); + $pwread = fgets($fd); + pclose($fd); + + /* determine add or mod */ + if (!strncmp($pwread, "pw:", 3)) { + $group_op = "groupadd"; + } else { + $group_op = "groupmod"; + } + + /* add or mod group db */ + $cmd = "/usr/sbin/pw {$group_op} {$group_name} -g {$group_gid} -M '{$group_members}' 2>&1"; + + if ($debug) { + log_error(sprintf(gettext("Running: %s"), $cmd)); + } + mwexec($cmd); + +} + +function local_group_del($group) { + global $debug; + + /* delete from group db */ + $cmd = "/usr/sbin/pw groupdel {$group['name']}"; + + if ($debug) { + log_error(sprintf(gettext("Running: %s"), $cmd)); + } + mwexec($cmd); +} + +function ldap_test_connection($authcfg) { + global $debug, $config, $g; + + if ($authcfg) { + if (strstr($authcfg['ldap_urltype'], "Standard")) { + $ldapproto = "ldap"; + } else { + $ldapproto = "ldaps"; + } + $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']); + $ldapport = $authcfg['ldap_port']; + if (!empty($ldapport)) { + $ldapserver .= ":{$ldapport}"; + } + $ldapbasedn = $authcfg['ldap_basedn']; + $ldapbindun = $authcfg['ldap_binddn']; + $ldapbindpw = $authcfg['ldap_bindpw']; + } else { + return false; + } + + /* first check if there is even an LDAP server populated */ + if (!$ldapserver) { + return false; + } + + /* Setup CA environment if needed. */ + ldap_setup_caenv($authcfg); + + /* connect and see if server is up */ + $error = false; + if (!($ldap = ldap_connect($ldapserver))) { + $error = true; + } + + if ($error == true) { + log_error(sprintf(gettext("ERROR! Could not connect to server %s."), $ldapname)); + return false; + } + + return true; +} + +function ldap_setup_caenv($authcfg) { + global $g; + require_once("certs.inc"); + + unset($caref); + if (empty($authcfg['ldap_caref']) || !strstr($authcfg['ldap_urltype'], "SSL")) { + putenv('LDAPTLS_REQCERT=never'); + return; + } else { + $caref = lookup_ca($authcfg['ldap_caref']); + if (!$caref) { + log_error(sprintf(gettext("LDAP: Could not lookup CA by reference for host %s."), $authcfg['ldap_caref'])); + /* XXX: Prevent for credential leaking since we cannot setup the CA env. Better way? */ + putenv('LDAPTLS_REQCERT=hard'); + return; + } + if (!is_dir("{$g['varrun_path']}/certs")) { + @mkdir("{$g['varrun_path']}/certs"); + } + if (file_exists("{$g['varrun_path']}/certs/{$caref['refid']}.ca")) { + @unlink("{$g['varrun_path']}/certs/{$caref['refid']}.ca"); + } + file_put_contents("{$g['varrun_path']}/certs/{$caref['refid']}.ca", base64_decode($caref['crt'])); + @chmod("{$g['varrun_path']}/certs/{$caref['refid']}.ca", 0600); + putenv('LDAPTLS_REQCERT=hard'); + /* XXX: Probably even the hashed link should be created for this? */ + putenv("LDAPTLS_CACERTDIR={$g['varrun_path']}/certs"); + putenv("LDAPTLS_CACERT={$g['varrun_path']}/certs/{$caref['refid']}.ca"); + } +} + +function ldap_test_bind($authcfg) { + global $debug, $config, $g; + + if ($authcfg) { + if (strstr($authcfg['ldap_urltype'], "Standard")) { + $ldapproto = "ldap"; + } else { + $ldapproto = "ldaps"; + } + $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']); + $ldapport = $authcfg['ldap_port']; + if (!empty($ldapport)) { + $ldapserver .= ":{$ldapport}"; + } + $ldapbasedn = $authcfg['ldap_basedn']; + $ldapbindun = $authcfg['ldap_binddn']; + $ldapbindpw = $authcfg['ldap_bindpw']; + $ldapver = $authcfg['ldap_protver']; + if (empty($ldapbndun) || empty($ldapbindpw)) { + $ldapanon = true; + } else { + $ldapanon = false; + } + } else { + return false; + } + + /* first check if there is even an LDAP server populated */ + if (!$ldapserver) { + return false; + } + + /* Setup CA environment if needed. */ + ldap_setup_caenv($authcfg); + + /* connect and see if server is up */ + $error = false; + if (!($ldap = ldap_connect($ldapserver))) { + $error = true; + } + + if ($error == true) { + log_error(sprintf(gettext("ERROR! Could not connect to server %s."), $ldapname)); + return false; + } + + ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + ldap_set_option($ldap, LDAP_OPT_DEREF, LDAP_DEREF_SEARCHING); + ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, (int)$ldapver); + + $ldapbindun = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindun) : $ldapbindun; + $ldapbindpw = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindpw) : $ldapbindpw; + if ($ldapanon == true) { + if (!($res = @ldap_bind($ldap))) { + @ldap_close($ldap); + return false; + } + } else if (!($res = @ldap_bind($ldap, $ldapbindun, $ldapbindpw))) { + @ldap_close($ldap); + return false; + } + + @ldap_unbind($ldap); + + return true; +} + +function ldap_get_user_ous($show_complete_ou=true, $authcfg) { + global $debug, $config, $g; + + if (!function_exists("ldap_connect")) { + return; + } + + $ous = array(); + + if ($authcfg) { + if (strstr($authcfg['ldap_urltype'], "Standard")) { + $ldapproto = "ldap"; + } else { + $ldapproto = "ldaps"; + } + $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']); + $ldapport = $authcfg['ldap_port']; + if (!empty($ldapport)) { + $ldapserver .= ":{$ldapport}"; + } + $ldapbasedn = $authcfg['ldap_basedn']; + $ldapbindun = $authcfg['ldap_binddn']; + $ldapbindpw = $authcfg['ldap_bindpw']; + $ldapver = $authcfg['ldap_protver']; + if (empty($ldapbindun) || empty($ldapbindpw)) { + $ldapanon = true; + } else { + $ldapanon = false; + } + $ldapname = $authcfg['name']; + $ldapfallback = false; + $ldapscope = $authcfg['ldap_scope']; + } else { + return false; + } + + /* first check if there is even an LDAP server populated */ + if (!$ldapserver) { + log_error(gettext("ERROR! ldap_get_user_ous() backed selected with no LDAP authentication server defined.")); + return $ous; + } + + /* Setup CA environment if needed. */ + ldap_setup_caenv($authcfg); + + /* connect and see if server is up */ + $error = false; + if (!($ldap = ldap_connect($ldapserver))) { + $error = true; + } + + if ($error == true) { + log_error(sprintf(gettext("ERROR! Could not connect to server %s."), $ldapname)); + return $ous; + } + + $ldapfilter = "(|(ou=*)(cn=Users))"; + + ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + ldap_set_option($ldap, LDAP_OPT_DEREF, LDAP_DEREF_SEARCHING); + ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, (int)$ldapver); + + $ldapbindun = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindun) : $ldapbindun; + $ldapbindpw = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindpw) : $ldapbindpw; + if ($ldapanon == true) { + if (!($res = @ldap_bind($ldap))) { + log_error(sprintf(gettext("ERROR! ldap_get_user_ous() could not bind anonymously to server %s."), $ldapname)); + @ldap_close($ldap); + return $ous; + } + } else if (!($res = @ldap_bind($ldap, $ldapbindun, $ldapbindpw))) { + log_error(sprintf(gettext("ERROR! ldap_get_user_ous() could not bind to server %s."), $ldapname)); + @ldap_close($ldap); + return $ous; + } + + if ($ldapscope == "one") { + $ldapfunc = "ldap_list"; + } else { + $ldapfunc = "ldap_search"; + } + + $search = @$ldapfunc($ldap, $ldapbasedn, $ldapfilter); + $info = @ldap_get_entries($ldap, $search); + + if (is_array($info)) { + foreach ($info as $inf) { + if (!$show_complete_ou) { + $inf_split = explode(",", $inf['dn']); + $ou = $inf_split[0]; + $ou = str_replace("OU=", "", $ou); + $ou = str_replace("CN=", "", $ou); + } else { + if ($inf['dn']) { + $ou = $inf['dn']; + } + } + if ($ou) { + $ous[] = $ou; + } + } + } + + @ldap_unbind($ldap); + + return $ous; +} + +function ldap_get_groups($username, $authcfg) { + global $debug, $config; + + if (!function_exists("ldap_connect")) { + return; + } + + if (!$username) { + return false; + } + + if (!isset($authcfg['ldap_nostrip_at']) && stristr($username, "@")) { + $username_split = explode("@", $username); + $username = $username_split[0]; + } + + if (stristr($username, "\\")) { + $username_split = explode("\\", $username); + $username = $username_split[0]; + } + + //log_error("Getting LDAP groups for {$username}."); + if ($authcfg) { + if (strstr($authcfg['ldap_urltype'], "Standard")) { + $ldapproto = "ldap"; + } else { + $ldapproto = "ldaps"; + } + $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']); + $ldapport = $authcfg['ldap_port']; + if (!empty($ldapport)) { + $ldapserver .= ":{$ldapport}"; + } + $ldapbasedn = $authcfg['ldap_basedn']; + $ldapbindun = $authcfg['ldap_binddn']; + $ldapbindpw = $authcfg['ldap_bindpw']; + $ldapauthcont = $authcfg['ldap_authcn']; + $ldapnameattribute = strtolower($authcfg['ldap_attr_user']); + $ldapgroupattribute = strtolower($authcfg['ldap_attr_member']); + $ldapfilter = "({$ldapnameattribute}={$username})"; + $ldaptype = ""; + $ldapver = $authcfg['ldap_protver']; + if (empty($ldapbindun) || empty($ldapbindpw)) { + $ldapanon = true; + } else { + $ldapanon = false; + } + $ldapname = $authcfg['name']; + $ldapfallback = false; + $ldapscope = $authcfg['ldap_scope']; + } else { + return false; + } + + $ldapdn = $_SESSION['ldapdn']; + + /*Convert attribute to lowercase. php ldap arrays put everything in lowercase */ + $ldapgroupattribute = strtolower($ldapgroupattribute); + $memberof = array(); + + /* Setup CA environment if needed. */ + ldap_setup_caenv($authcfg); + + /* connect and see if server is up */ + $error = false; + if (!($ldap = ldap_connect($ldapserver))) { + $error = true; + } + + if ($error == true) { + log_error(sprintf(gettext("ERROR! ldap_get_groups() Could not connect to server %s."), $ldapname)); + return memberof; + } + + ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + ldap_set_option($ldap, LDAP_OPT_DEREF, LDAP_DEREF_SEARCHING); + ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, (int)$ldapver); + + /* bind as user that has rights to read group attributes */ + $ldapbindun = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindun) : $ldapbindun; + $ldapbindpw = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindpw) : $ldapbindpw; + if ($ldapanon == true) { + if (!($res = @ldap_bind($ldap))) { + log_error(sprintf(gettext("ERROR! ldap_get_groups() could not bind anonymously to server %s."), $ldapname)); + @ldap_close($ldap); + return false; + } + } else if (!($res = @ldap_bind($ldap, $ldapbindun, $ldapbindpw))) { + log_error(sprintf(gettext("ERROR! ldap_get_groups() could not bind to server %s."), $ldapname)); + @ldap_close($ldap); + return memberof; + } + + /* get groups from DN found */ + /* use ldap_read instead of search so we don't have to do a bunch of extra work */ + /* since we know the DN is in $_SESSION['ldapdn'] */ + //$search = ldap_read($ldap, $ldapdn, "(objectclass=*)", array($ldapgroupattribute)); + if ($ldapscope == "one") { + $ldapfunc = "ldap_list"; + } else { + $ldapfunc = "ldap_search"; + } + + $search = @$ldapfunc($ldap, $ldapdn, $ldapfilter, array($ldapgroupattribute)); + $info = @ldap_get_entries($ldap, $search); + + $countem = $info["count"]; + + if (is_array($info[0][$ldapgroupattribute])) { + /* Iterate through the groups and throw them into an array */ + foreach ($info[0][$ldapgroupattribute] as $member) { + if (stristr($member, "CN=") !== false) { + $membersplit = explode(",", $member); + $memberof[] = preg_replace("/CN=/i", "", $membersplit[0]); + } + } + } + + /* Time to close LDAP connection */ + @ldap_unbind($ldap); + + $groups = print_r($memberof, true); + + //log_error("Returning groups ".$groups." for user $username"); + + return $memberof; +} + +function ldap_format_host($host) { + return is_ipaddrv6($host) ? "[$host]" : $host ; +} + +function ldap_backed($username, $passwd, $authcfg) { + global $debug, $config; + + if (!$username) { + return; + } + + if (!function_exists("ldap_connect")) { + return; + } + + if (!isset($authcfg['ldap_nostrip_at']) && stristr($username, "@")) { + $username_split = explode("@", $username); + $username = $username_split[0]; + } + if (stristr($username, "\\")) { + $username_split = explode("\\", $username); + $username = $username_split[0]; + } + + if ($authcfg) { + if (strstr($authcfg['ldap_urltype'], "Standard")) { + $ldapproto = "ldap"; + } else { + $ldapproto = "ldaps"; + } + $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']); + $ldapport = $authcfg['ldap_port']; + if (!empty($ldapport)) { + $ldapserver .= ":{$ldapport}"; + } + $ldapbasedn = $authcfg['ldap_basedn']; + $ldapbindun = $authcfg['ldap_binddn']; + $ldapbindpw = $authcfg['ldap_bindpw']; + if (empty($ldapbindun) || empty($ldapbindpw)) { + $ldapanon = true; + } else { + $ldapanon = false; + } + $ldapauthcont = $authcfg['ldap_authcn']; + $ldapnameattribute = strtolower($authcfg['ldap_attr_user']); + $ldapextendedqueryenabled = $authcfg['ldap_extended_enabled']; + $ldapextendedquery = $authcfg['ldap_extended_query']; + $ldapfilter = ""; + if (!$ldapextendedqueryenabled) { + $ldapfilter = "({$ldapnameattribute}={$username})"; + } else { + $ldapfilter = "(&({$ldapnameattribute}={$username})({$ldapextendedquery}))"; + } + $ldaptype = ""; + $ldapver = $authcfg['ldap_protver']; + $ldapname = $authcfg['name']; + $ldapscope = $authcfg['ldap_scope']; + } else { + return false; + } + + /* first check if there is even an LDAP server populated */ + if (!$ldapserver) { + if ($ldapfallback) { + log_error(gettext("ERROR! ldap_backed() called with no LDAP authentication server defined. Defaulting to local user database. Visit System -> User Manager.")); + return local_backed($username, $passwd); + } else { + log_error(gettext("ERROR! ldap_backed() called with no LDAP authentication server defined.")); + } + + return false; + } + + /* Setup CA environment if needed. */ + ldap_setup_caenv($authcfg); + + ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + ldap_set_option($ldap, LDAP_OPT_DEREF, LDAP_DEREF_SEARCHING); + ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, (int)$ldapver); + + /* Make sure we can connect to LDAP */ + $error = false; + if (!($ldap = ldap_connect($ldapserver))) { + $error = true; + } + + if ($error == true) { + log_error(sprintf(gettext("ERROR! Could not connect to server %s."), $ldapname)); + return false; + } + + /* ok, its up. now, lets bind as the bind user so we can search it */ + $error = false; + $ldapbindun = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindun) : $ldapbindun; + $ldapbindpw = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindpw) : $ldapbindpw; + if ($ldapanon == true) { + if (!($res = @ldap_bind($ldap))) { + $error = true; + } + } else if (!($res = @ldap_bind($ldap, $ldapbindun, $ldapbindpw))) { + $error = true; + } + + if ($error == true) { + @ldap_close($ldap); + log_error(sprintf(gettext("ERROR! Could not bind to server %s."), $ldapname)); + return false; + } + + /* Get LDAP Authcontainers and split em up. */ + $ldac_splits = explode(";", $ldapauthcont); + + /* setup the usercount so we think we haven't found anyone yet */ + $usercount = 0; + + /*****************************************************************/ + /* We First find the user based on username and filter */ + /* Then, once we find the first occurrence of that person */ + /* We set session variables to point to the OU and DN of the */ + /* Person. To later be used by ldap_get_groups. */ + /* that way we don't have to search twice. */ + /*****************************************************************/ + if ($debug) { + log_auth(sprintf(gettext("Now Searching for %s in directory."), $username)); + } + /* Iterate through the user containers for search */ + foreach ($ldac_splits as $i => $ldac_split) { + $ldac_split = isset($authcfg['ldap_utf8']) ? utf8_encode($ldac_split) : $ldac_split; + $ldapfilter = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapfilter) : $ldapfilter; + $ldapsearchbasedn = isset($authcfg['ldap_utf8']) ? utf8_encode("{$ldac_split},{$ldapbasedn}") : "{$ldac_split},{$ldapbasedn}"; + /* Make sure we just use the first user we find */ + if ($debug) { + log_auth(sprintf(gettext('Now Searching in server %1$s, container %2$s with filter %3$s.'), $ldapname, utf8_decode($ldac_split), utf8_decode($ldapfilter))); + } + if ($ldapscope == "one") { + $ldapfunc = "ldap_list"; + } else { + $ldapfunc = "ldap_search"; + } + /* Support legacy auth container specification. */ + if (stristr($ldac_split, "DC=") || empty($ldapbasedn)) { + $search = @$ldapfunc($ldap,$ldac_split,$ldapfilter); + } else { + $search = @$ldapfunc($ldap,$ldapsearchbasedn,$ldapfilter); + } + if (!$search) { + log_error(sprintf(gettext("Search resulted in error: %s"), ldap_error($ldap))); + continue; + } + $info = ldap_get_entries($ldap, $search); + $matches = $info['count']; + if ($matches == 1) { + $userdn = $_SESSION['ldapdn'] = $info[0]['dn']; + $_SESSION['ldapou'] = $ldac_split[$i]; + $_SESSION['ldapon'] = "true"; + $usercount = 1; + break; + } + } + + if ($usercount != 1) { + @ldap_unbind($ldap); + log_error(gettext("ERROR! Either LDAP search failed, or multiple users were found.")); + return false; + } + + /* Now lets bind as the user we found */ + $passwd = isset($authcfg['ldap_utf8']) ? utf8_encode($passwd) : $passwd; + if (!($res = @ldap_bind($ldap, $userdn, $passwd))) { + log_error(sprintf(gettext('ERROR! Could not login to server %1$s as user %2$s: %3$s'), $ldapname, $username, ldap_error($ldap))); + @ldap_unbind($ldap); + return false; + } + + if ($debug) { + $userdn = isset($authcfg['ldap_utf8']) ? utf8_decode($userdn) : $userdn; + log_auth(sprintf(gettext('Logged in successfully as %1$s via LDAP server %2$s with DN = %3$s.'), $username, $ldapname, $userdn)); + } + + /* At this point we are bound to LDAP so the user was auth'd okay. Close connection. */ + @ldap_unbind($ldap); + + return true; +} + +function radius_backed($username, $passwd, $authcfg, &$attributes = array()) { + global $debug, $config; + $ret = false; + + require_once("radius.inc"); + + $rauth = new Auth_RADIUS_PAP($username, $passwd); + if ($authcfg) { + $radiusservers = array(); + $radiusservers[0]['ipaddr'] = $authcfg['host']; + $radiusservers[0]['port'] = $authcfg['radius_auth_port']; + $radiusservers[0]['sharedsecret'] = $authcfg['radius_secret']; + $radiusservers[0]['timeout'] = $authcfg['radius_timeout']; + } else { + return false; + } + + /* Add new servers to our instance */ + foreach ($radiusservers as $radsrv) { + $timeout = (is_numeric($radsrv['timeout'])) ? $radsrv['timeout'] : 5; + $rauth->addServer($radsrv['ipaddr'], $radsrv['port'], $radsrv['sharedsecret'], $timeout); + } + + if (PEAR::isError($rauth->start())) { + $retvalue['auth_val'] = 1; + $retvalue['error'] = $rauth->getError(); + if ($debug) { + printf(gettext("Radius start: %s<br />\n"), $retvalue['error']); + } + } + + // XXX - billm - somewhere in here we need to handle securid challenge/response + + /* Send request */ + $result = $rauth->send(); + if (PEAR::isError($result)) { + $retvalue['auth_val'] = 1; + $retvalue['error'] = $result->getMessage(); + if ($debug) { + printf(gettext("Radius send failed: %s<br />\n"), $retvalue['error']); + } + } else if ($result === true) { + if ($rauth->getAttributes()) { + $attributes = $rauth->listAttributes(); + } + $retvalue['auth_val'] = 2; + if ($debug) { + printf(gettext("Radius Auth succeeded")."<br />\n"); + } + $ret = true; + } else { + $retvalue['auth_val'] = 3; + if ($debug) { + printf(gettext("Radius Auth rejected")."<br />\n"); + } + } + + // close OO RADIUS_AUTHENTICATION + $rauth->close(); + + return $ret; +} + +/* + $attributes must contain a "class" key containing the groups and local + groups must exist to match. +*/ +function radius_get_groups($attributes) { + $groups = array(); + if (!empty($attributes) && is_array($attributes) && !empty($attributes['class'])) { + $groups = explode(";", $attributes['class']); + foreach ($groups as & $grp) { + $grp = trim($grp); + if (strtolower(substr($grp, 0, 3)) == "ou=") { + $grp = substr($grp, 3); + } + } + } + return $groups; +} + +function get_user_expiration_date($username) { + $user = getUserEntry($username); + if ($user['expires']) { + return $user['expires']; + } +} + +function is_account_expired($username) { + $expirydate = get_user_expiration_date($username); + if ($expirydate) { + if (strtotime("-1 day") > strtotime(date("m/d/Y", strtotime($expirydate)))) { + return true; + } + } + + return false; +} + +function is_account_disabled($username) { + $user = getUserEntry($username); + if (isset($user['disabled'])) { + return true; + } + + return false; +} + +function auth_get_authserver($name) { + global $config; + + if (is_array($config['system']['authserver'])) { + foreach ($config['system']['authserver'] as $authcfg) { + if ($authcfg['name'] == $name) { + return $authcfg; + } + } + } + if ($name == "Local Database") { + return array("name" => gettext("Local Database"), "type" => "Local Auth", "host" => $config['system']['hostname']); + } +} + +function auth_get_authserver_list() { + global $config; + + $list = array(); + + if (is_array($config['system']['authserver'])) { + foreach ($config['system']['authserver'] as $authcfg) { + /* Add support for disabled entries? */ + $list[$authcfg['name']] = $authcfg; + } + } + + $list["Local Database"] = array("name" => gettext("Local Database"), "type" => "Local Auth", "host" => $config['system']['hostname']); + return $list; +} + +function getUserGroups($username, $authcfg, &$attributes = array()) { + global $config; + + $allowed_groups = array(); + + switch ($authcfg['type']) { + case 'ldap': + $allowed_groups = @ldap_get_groups($username, $authcfg); + break; + case 'radius': + $allowed_groups = @radius_get_groups($attributes); + break; + default: + $user = getUserEntry($username); + $allowed_groups = @local_user_get_groups($user, true); + break; + } + + $member_groups = array(); + if (is_array($config['system']['group'])) { + foreach ($config['system']['group'] as $group) { + if (in_array($group['name'], $allowed_groups)) { + $member_groups[] = $group['name']; + } + } + } + + return $member_groups; +} + +function authenticate_user($username, $password, $authcfg = NULL, &$attributes = array()) { + + if (!$authcfg) { + return local_backed($username, $password); + } + + $authenticated = false; + switch ($authcfg['type']) { + case 'ldap': + if (ldap_backed($username, $password, $authcfg)) { + $authenticated = true; + } + break; + case 'radius': + if (radius_backed($username, $password, $authcfg, $attributes)) { + $authenticated = true; + } + break; + default: + /* lookup user object by name */ + if (local_backed($username, $password)) { + $authenticated = true; + } + break; + } + + return $authenticated; +} + +function session_auth() { + global $config, $_SESSION, $page; + + // Handle HTTPS httponly and secure flags + $currentCookieParams = session_get_cookie_params(); + session_set_cookie_params( + $currentCookieParams["lifetime"], + $currentCookieParams["path"], + NULL, + ($config['system']['webgui']['protocol'] == "https"), + true + ); + + if (!session_id()) { + session_start(); + } + + // Detect protocol change + if (!isset($_POST['login']) && !empty($_SESSION['Logged_In']) && $_SESSION['protocol'] != $config['system']['webgui']['protocol']) { + return false; + } + + /* Validate incoming login request */ + $attributes = array(); + if (isset($_POST['login']) && !empty($_POST['usernamefld']) && !empty($_POST['passwordfld'])) { + $authcfg = auth_get_authserver($config['system']['webgui']['authmode']); + if (authenticate_user($_POST['usernamefld'], $_POST['passwordfld'], $authcfg, $attributes) || + authenticate_user($_POST['usernamefld'], $_POST['passwordfld'])) { + // Generate a new id to avoid session fixation + session_regenerate_id(); + $_SESSION['Logged_In'] = "True"; + $_SESSION['Username'] = $_POST['usernamefld']; + $_SESSION['user_radius_attributes'] = $attributes; + $_SESSION['last_access'] = time(); + $_SESSION['protocol'] = $config['system']['webgui']['protocol']; + if (!isset($config['system']['webgui']['quietlogin'])) { + log_auth(sprintf(gettext("Successful login for user '%1\$s' from: %2\$s"), $_POST['usernamefld'], $_SERVER['REMOTE_ADDR'])); + } + if (isset($_POST['postafterlogin'])) { + return true; + } else { + if (empty($page)) { + $page = "/"; + } + header("Location: {$page}"); + } + exit; + } else { + /* give the user an error message */ + $_SESSION['Login_Error'] = "Username or Password incorrect"; + log_auth("webConfigurator authentication error for '{$_POST['usernamefld']}' from {$_SERVER['REMOTE_ADDR']}"); + if (isAjax()) { + echo "showajaxmessage('{$_SESSION['Login_Error']}');"; + return; + } + } + } + + /* Show login page if they aren't logged in */ + if (empty($_SESSION['Logged_In'])) { + return false; + } + + /* If session timeout isn't set, we don't mark sessions stale */ + if (!isset($config['system']['webgui']['session_timeout'])) { + /* Default to 4 hour timeout if one is not set */ + if ($_SESSION['last_access'] < (time() - 14400)) { + $_GET['logout'] = true; + $_SESSION['Logout'] = true; + } else { + $_SESSION['last_access'] = time(); + } + } else if (intval($config['system']['webgui']['session_timeout']) == 0) { + /* only update if it wasn't ajax */ + if (!isAjax()) { + $_SESSION['last_access'] = time(); + } + } else { + /* Check for stale session */ + if ($_SESSION['last_access'] < (time() - ($config['system']['webgui']['session_timeout'] * 60))) { + $_GET['logout'] = true; + $_SESSION['Logout'] = true; + } else { + /* only update if it wasn't ajax */ + if (!isAjax()) { + $_SESSION['last_access'] = time(); + } + } + } + + /* user hit the logout button */ + if (isset($_GET['logout'])) { + + if ($_SESSION['Logout']) { + log_error(sprintf(gettext("Session timed out for user '%1\$s' from: %2\$s"), $_SESSION['Username'], $_SERVER['REMOTE_ADDR'])); + } else { + log_error(sprintf(gettext("User logged out for user '%1\$s' from: %2\$s"), $_SESSION['Username'], $_SERVER['REMOTE_ADDR'])); + } + + /* wipe out $_SESSION */ + $_SESSION = array(); + + if (isset($_COOKIE[session_name()])) { + setcookie(session_name(), '', time()-42000, '/'); + } + + /* and destroy it */ + session_destroy(); + + $scriptName = explode("/", $_SERVER["SCRIPT_FILENAME"]); + $scriptElms = count($scriptName); + $scriptName = $scriptName[$scriptElms-1]; + + if (isAjax()) { + return false; + } + + /* redirect to page the user is on, it'll prompt them to login again */ + header("Location: {$scriptName}"); + + return false; + } + + /* + * this is for debugging purpose if you do not want to use Ajax + * to submit a HTML form. It basically disables the observation + * of the submit event and hence does not trigger Ajax. + */ + if ($_GET['disable_ajax']) { + $_SESSION['NO_AJAX'] = "True"; + } + + /* + * Same to re-enable Ajax. + */ + if ($_GET['enable_ajax']) { + unset($_SESSION['NO_AJAX']); + } + + return true; +} + +?> diff --git a/src/etc/inc/authgui.inc b/src/etc/inc/authgui.inc new file mode 100644 index 0000000..07cf9a9 --- /dev/null +++ b/src/etc/inc/authgui.inc @@ -0,0 +1,335 @@ +<?php +/* $Id$ */ +/* + Copyright (C) 2007, 2008 Scott Ullrich <sullrich@gmail.com> + All rights reserved. + + Copyright (C) 2005-2006 Bill Marquette <bill.marquette@gmail.com> + All rights reserved. + + Copyright (C) 2006 Paul Taylor <paultaylor@winn-dixie.com>. + All rights reserved. + + Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + pfSense_MODULE: authgui +*/ + +include_once("auth.inc"); +include_once("priv.inc"); +if (!function_exists('platform_booting')) { + require_once('globals.inc'); +} + +/* Authenticate user - exit if failed */ +if (!session_auth()) { + display_login_form(); + exit; +} + +/* + * Once here, the user has authenticated with the web server. + * We give them access only to the appropriate pages based on + * the user or group privileges. + */ +$allowedpages = getAllowedPages($_SESSION['Username'], $_SESSION['user_radius_attributes']); + +/* + * redirect to first allowed page if requesting a wrong url + */ +if (!isAllowedPage($_SERVER['REQUEST_URI'])) { + if (count($allowedpages) > 0) { + $page = str_replace('*', '', $allowedpages[0]); + $_SESSION['Post_Login'] = true; + require_once("functions.inc"); + pfSenseHeader("/{$page}"); + + $username = empty($_SESSION["Username"]) ? "(system)" : $_SESSION['Username']; + if (!empty($_SERVER['REMOTE_ADDR'])) { + $username .= '@' . $_SERVER['REMOTE_ADDR']; + } + log_error("{$username} attempted to access {$_SERVER['SCRIPT_NAME']} but does not have access to that page. Redirecting to {$page}."); + + exit; + } else { + display_error_form("201", gettext("No page assigned to this user! Click here to logout.")); + exit; + } +} else { + $_SESSION['Post_Login'] = true; +} + +/* + * redirect browsers post-login to avoid pages + * taking action in response to a POST request + */ +if (!$_SESSION['Post_Login']) { + $_SESSION['Post_Login'] = true; + require_once("functions.inc"); + pfSenseHeader($_SERVER['REQUEST_URI']); + exit; +} + +/* + * Close session data to allow other scripts from same host to come in. + * A session can be reactivated from calling session_start again + */ +session_commit(); + +/* + * determine if the user is allowed access to the requested page + */ +function display_error_form($http_code, $desc) { + global $config, $g; + $g['theme'] = get_current_theme(); + if (isAjax()) { + printf(gettext('Error: %1$s Description: %2$s'), $http_code, $desc); + return; + } + +?> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> + <head> + <script type="text/javascript" src="/javascript/jquery-1.11.1.min.js"></script> + <script type="text/javascript" src="/javascript/jquery-migrate-1.2.1.min.js"></script> + <title><?=$http_code?></title> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <link rel="shortcut icon" href="/themes/<?= $g['theme'] ?>/images/icons/favicon.ico" /> + <?php if (file_exists("{$g['www_path']}/themes/{$g['theme']}/login.css")): ?> + <link rel="stylesheet" type="text/css" href="/themes/<?= $g['theme'] ?>/login.css" media="all" /> + <?php else: ?> + <link rel="stylesheet" type="text/css" href="/themes/<?= $g['theme'] ?>/all.css" media="all" /> + <?php endif; ?> + <script type="text/javascript"> + //<![CDATA[ + function page_load() {} + function clearError() { + if ($('#inputerrors')) { + $('#inputerrors').html(''); + } + } + <?php + require("headjs.php"); + echo getHeadJS(); + ?> + //]]> + </script> + <script type="text/javascript" src="/themes/<?= $g['theme'] ?>/javascript/niftyjsCode.js"></script> + </head> + <body onload="page_load();"> + <div id="errordesc"> + <h1> </h1> + <a href="/index.php?logout"> + <p id="errortext" style="vertical-align: middle; text-align: center;"> + <span style="color: #000000; font-weight: bold;"> + <?=$desc;?> + </span> + </p> + </div> + </body> +</html> + +<?php + +} // end function + + +function display_login_form() { + require_once("globals.inc"); + global $config, $g; + $g['theme'] = get_current_theme(); + + unset($input_errors); + + if (isAjax()) { + if (isset($_POST['login'])) { + if ($_SESSION['Logged_In'] <> "True") { + isset($_SESSION['Login_Error']) ? $login_error = $_SESSION['Login_Error'] : $login_error = gettext("unknown reason"); + printf("showajaxmessage('" . gettext("Invalid login (%s).") . "')", $login_error); + } + if (file_exists("{$g['tmp_path']}/webconfigurator.lock")) { + // TODO: add the IP from the user who did lock the device + $whom = file_get_contents("{$g['tmp_path']}/webconfigurator.lock"); + printf("showajaxmessage('" . gettext("This device is currently being maintained by: %s.") . "');", $whom); + } + } + exit; + } + +/* Check against locally configured IP addresses, which will catch when someone + port forwards WebGUI access from WAN to an internal IP on the router. */ +global $FilterIflist, $nifty_background; +$local_ip = false; +if (strpos($_SERVER['HTTP_HOST'], ":") === FALSE) { + $http_host_port = explode(":", $_SERVER['HTTP_HOST']); + $http_host = $http_host_port[0]; +} else { + $http_host = $_SERVER['HTTP_HOST']; +} +if (empty($FilterIflist)) { + require_once('filter.inc'); + require_once('shaper.inc'); + filter_generate_optcfg_array(); +} +foreach ($FilterIflist as $iflist) { + if ($iflist['ip'] == $http_host) { + $local_ip = true; + } else if ($iflist['ipv6'] == $http_host) { + $local_ip = true; + } else if (is_array($iflist['vips'])) { + foreach ($iflist['vips'] as $vip) { + if ($vip['ip'] == $http_host) { + $local_ip = true; + break; + } + } + unset($vip); + } + if ($local_ip == true) { + break; + } +} +unset($FilterIflist); +unset($iflist); + +if ($local_ip == false) { + if (is_array($config['openvpn']['openvpn-server'])) { + foreach ($config['openvpn']['openvpn-server'] as $ovpns) { + if (is_ipaddrv4($http_host) && !empty($ovpns['tunnel_network']) && ip_in_subnet($http_host, $ovpns['tunnel_network'])) { + $local_ip = true; + } else if (is_ipaddrv6($http_host) && !empty($ovpns['tunnel_networkv6']) && ip_in_subnet($http_host, $ovpns['tunnel_networkv6'])) { + $local_ip = true; + } + if ($local_ip == true) { + break; + } + } + } +} + +?> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> + <head> + <script type="text/javascript" src="/javascript/jquery-1.11.1.min.js"></script> + <script type="text/javascript" src="/javascript/jquery-migrate-1.2.1.min.js"></script> + <script type="text/javascript"> + //<![CDATA[ + $(document).ready(function() { jQuery('#usernamefld').focus(); }); + //]]> + </script> + + <title><?=gettext("Login"); ?></title> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <link rel="shortcut icon" href="/themes/<?= $g['theme'] ?>/images/icons/favicon.ico" /> + <?php if (file_exists("{$g['www_path']}/themes/{$g['theme']}/login.css")): ?> + <link rel="stylesheet" type="text/css" href="/themes/<?= $g['theme'] ?>/login.css" media="all" /> + <?php else: ?> + <link rel="stylesheet" type="text/css" href="/themes/<?= $g['theme'] ?>/all.css" media="all" /> + <?php endif; ?> + <script type="text/javascript"> + //<![CDATA[ + function page_load() {} + function clearError() { + if ($('#inputerrors')) { + $('#inputerrors').html(''); + } + } + <?php + require("headjs.php"); + echo getHeadJS(); + ?> + //]]> + </script> + <script type="text/javascript" src="/themes/<?= $g['theme'] ?>/javascript/niftyjsCode.js"></script> + </head> + <body onload="page_load()"> + <div id="login"> + <?php + if (is_ipaddr($http_host) && !$local_ip && !isset($config['system']['webgui']['nohttpreferercheck'])) { + $nifty_background = "#999"; + print_info_box(gettext("You are accessing this router by an IP address not configured locally, which may be forwarded by NAT or other means. <br /><br />If you did not setup this forwarding, you may be the target of a man-in-the-middle attack.")); + } + $loginautocomplete = isset($config['system']['webgui']['loginautocomplete']) ? '' : 'autocomplete="off"'; + ?> + <form id="iform" name="iform" method="post" <?= $loginautocomplete ?> action="<?=$_SERVER['SCRIPT_NAME'];?>"> + <h1> </h1> + <div id="inputerrors"><?=$_SESSION['Login_Error'];?></div> + <p> + <span style="text-align:left"> + <?=gettext("Username:"); ?><br /> + <input onclick="clearError();" onchange="clearError();" id="usernamefld" type="text" name="usernamefld" class="formfld user" tabindex="1" /> + </span> + </p> + <p> + <br /> + <span style="text-align:left"> + <?=gettext("Password:"); ?> <br /> + <input onclick="clearError();" onchange="clearError();" id="passwordfld" type="password" name="passwordfld" class="formfld pwd" tabindex="2" /> + </span> + </p> + <p> + <br /> + <span style="text-align:center; font-weight: normal ; font-style: italic"> + <?=gettext("Enter username and password to login."); ?> + </span> + + <span style="text-align:center; font-weight: normal ; font-style: italic; color: #ff0000; display:none" id="no_cookies"> + <br /><br /> + <?= gettext("Your browser must support cookies to login."); ?> + </span> + </p> + <p> + <span style="text-align:center"> + <input type="submit" name="login" class="formbtn" value="<?=gettext("Login"); ?>" tabindex="3" /> + </span> + </p> + </form> + </div> + <script type="text/javascript"> + //<![CDATA[ + document.cookie= + "cookie_test=1" + + "<?php echo $config['system']['webgui']['protocol'] == 'https' ? '; secure' : '';?>"; + + if (document.cookie.indexOf("cookie_test") == -1) { + document.getElementById("no_cookies").style.display=""; + } + + // Delete it + document.cookie = "cookie_test=1; expires=Thu, 01-Jan-1970 00:00:01 GMT"; + //]]> + </script> + </body> +</html> +<?php +} // end function + +?> diff --git a/src/etc/inc/basic_sasl_client.inc b/src/etc/inc/basic_sasl_client.inc new file mode 100644 index 0000000..c817664 --- /dev/null +++ b/src/etc/inc/basic_sasl_client.inc @@ -0,0 +1,63 @@ +<?php +/* + * basic_sasl_client.php + * + * @(#) $Id: basic_sasl_client.php,v 1.1 2004/11/17 08:01:23 mlemos Exp $ + * + */ + +define("SASL_BASIC_STATE_START", 0); +define("SASL_BASIC_STATE_DONE", 1); + +class basic_sasl_client_class +{ + var $credentials=array(); + var $state=SASL_BASIC_STATE_START; + + Function Initialize(&$client) + { + return(1); + } + + Function Start(&$client, &$message, &$interactions) + { + if ($this->state!=SASL_BASIC_STATE_START) + { + $client->error="Basic authentication state is not at the start"; + return(SASL_FAIL); + } + $this->credentials=array( + "user"=>"", + "password"=>"" + ); + $defaults=array( + ); + $status=$client->GetCredentials($this->credentials,$defaults,$interactions); + if ($status==SASL_CONTINUE) + { + $message=$this->credentials["user"].":".$this->credentials["password"]; + $this->state=SASL_BASIC_STATE_DONE; + } + else + { + Unset($message); + } + return($status); + } + + Function Step(&$client, $response, &$message, &$interactions) + { + switch ($this->state) + { + case SASL_BASIC_STATE_DONE: + $client->error="Basic authentication was finished without success"; + return(SASL_FAIL); + default: + $client->error="invalid Basic authentication step state"; + return(SASL_FAIL); + } + return(SASL_CONTINUE); + } +}; + +?>
\ No newline at end of file diff --git a/src/etc/inc/captiveportal.inc b/src/etc/inc/captiveportal.inc new file mode 100644 index 0000000..bd294e4 --- /dev/null +++ b/src/etc/inc/captiveportal.inc @@ -0,0 +1,2409 @@ +<?php +/* + captiveportal.inc + part of pfSense (https://www.pfsense.org) + Copyright (C) 2004-2011 Scott Ullrich <sullrich@gmail.com> + Copyright (C) 2009-2012 Ermal Luçi <eri@pfsense.org> + Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>. + + originally part of m0n0wall (http://m0n0.ch/wall) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + This version of captiveportal.inc has been modified by Rob Parker + <rob.parker@keycom.co.uk> to include changes for per-user bandwidth management + via returned RADIUS attributes. This page has been modified to delete any + added rules which may have been created by other per-user code (index.php, etc). + These changes are (c) 2004 Keycom PLC. + + pfSense_BUILDER_BINARIES: /sbin/ipfw /sbin/route + pfSense_BUILDER_BINARIES: /usr/local/sbin/lighttpd /usr/local/bin/minicron /sbin/pfctl + pfSense_BUILDER_BINARIES: /bin/hostname /bin/cp + pfSense_MODULE: captiveportal +*/ + +/* include all configuration functions */ +require_once("config.inc"); +require_once("functions.inc"); +require_once("filter.inc"); +require_once("radius.inc"); +require_once("voucher.inc"); + +function get_default_captive_portal_html() { + global $config, $g, $cpzone; + + $htmltext = <<<EOD +<html> +<body> +<form method="post" action="\$PORTAL_ACTION\$"> + <input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$"> + <input name="zone" type="hidden" value="\$PORTAL_ZONE\$"> + <center> + <table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000"> + <tr height="10" bgcolor="#990000"> + <td style="border-bottom:1px solid #000000"> + <font color='white'> + <b> + {$g['product_name']} captive portal + </b> + </font> + </td> + </tr> + <tr> + <td> + <div id="mainlevel"> + <center> + <table width="100%" border="0" cellpadding="5" cellspacing="0"> + <tr> + <td> + <center> + <div id="mainarea"> + <center> + <table width="100%" border="0" cellpadding="5" cellspacing="5"> + <tr> + <td> + <div id="maindivarea"> + <center> + <div id='statusbox'> + <font color='red' face='arial' size='+1'> + <b> + \$PORTAL_MESSAGE\$ + </b> + </font> + </div> + <br /> + <div id='loginbox'> + <table> + <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr> + <tr><td> </td></tr> + <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr> + <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr> + <tr><td> </td></tr> + +EOD; + + if (isset($config['voucher'][$cpzone]['enable'])) { + $htmltext .= <<<EOD + <tr> + <td align="right">Enter Voucher Code: </td> + <td><input name="auth_voucher" type="text" style="border:1px dashed;" size="22"></td> + </tr> + +EOD; + } + + $htmltext .= <<<EOD + <tr> + <td colspan="2"><center><input name="accept" type="submit" value="Continue"></center></td> + </tr> + </table> + </div> + </center> + </div> + </td> + </tr> + </table> + </center> + </div> + </center> + </td> + </tr> + </table> + </center> + </div> + </td> + </tr> + </table> + </center> +</form> +</body> +</html> + +EOD; + + return $htmltext; +} + +function captiveportal_load_modules() { + global $config; + + mute_kernel_msgs(); + if (!is_module_loaded("ipfw.ko")) { + mwexec("/sbin/kldload ipfw"); + /* make sure ipfw is not on pfil hooks */ + set_sysctl(array( + "net.inet.ip.pfil.inbound" => "pf", "net.inet6.ip6.pfil.inbound" => "pf", + "net.inet.ip.pfil.outbound" => "pf", "net.inet6.ip6.pfil.outbound" => "pf") + ); + } + /* Activate layer2 filtering */ + set_sysctl(array("net.link.ether.ipfw" => "1", "net.inet.ip.fw.one_pass" => "1")); + + /* Always load dummynet now that even allowed ip and mac passthrough use it. */ + if (!is_module_loaded("dummynet.ko")) { + mwexec("/sbin/kldload dummynet"); + set_sysctl(array("net.inet.ip.dummynet.io_fast" => "1", "net.inet.ip.dummynet.hash_size" => "256")); + } + unmute_kernel_msgs(); +} + +function captiveportal_configure() { + global $config, $cpzone, $cpzoneid; + + if (is_array($config['captiveportal'])) { + foreach ($config['captiveportal'] as $cpkey => $cp) { + $cpzone = $cpkey; + $cpzoneid = $cp['zoneid']; + captiveportal_configure_zone($cp); + } + } +} + +function captiveportal_configure_zone($cpcfg) { + global $config, $g, $cpzone, $cpzoneid; + + $captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX); + + if (isset($cpcfg['enable'])) { + + if (platform_booting()) { + echo "Starting captive portal({$cpcfg['zone']})... "; + + /* remove old information */ + unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"); + } else { + captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']})."); + } + + /* init ipfw rules */ + captiveportal_init_rules(true); + + /* kill any running minicron */ + killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"); + + /* initialize minicron interval value */ + $croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60; + + /* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */ + if ((!is_numeric($croninterval)) || ($croninterval < 10)) { + $croninterval = 60; + } + + /* write portal page */ + if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext']) { + $htmltext = base64_decode($cpcfg['page']['htmltext']); + } else { + /* example/template page */ + $htmltext = get_default_captive_portal_html(); + } + + $fd = @fopen("{$g['varetc_path']}/captiveportal_{$cpzone}.html", "w"); + if ($fd) { + // Special case handling. Convert so that we can pass this page + // through the PHP interpreter later without clobbering the vars. + $htmltext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $htmltext); + $htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext); + $htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext); + $htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext); + $htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext); + $htmltext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $htmltext); + $htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext); + if ($cpcfg['preauthurl']) { + $htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext); + $htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext); + } + fwrite($fd, $htmltext); + fclose($fd); + } + unset($htmltext); + + /* write error page */ + if (is_array($cpcfg['page']) && $cpcfg['page']['errtext']) { + $errtext = base64_decode($cpcfg['page']['errtext']); + } else { + /* example page */ + $errtext = get_default_captive_portal_html(); + } + + $fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html", "w"); + if ($fd) { + // Special case handling. Convert so that we can pass this page + // through the PHP interpreter later without clobbering the vars. + $errtext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $errtext); + $errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext); + $errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext); + $errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext); + $errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext); + $errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext); + $errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext); + if ($cpcfg['preauthurl']) { + $errtext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $errtext); + $errtext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $errtext); + } + fwrite($fd, $errtext); + fclose($fd); + } + unset($errtext); + + /* write logout page */ + if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext']) { + $logouttext = base64_decode($cpcfg['page']['logouttext']); + } else { + /* example page */ + $logouttext = <<<EOD +<html> +<head><title>Redirecting...</title></head> +<body> +<span style="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;"> +<b>Redirecting to <a href="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></a>...</b> +</span> +<script type="text/javascript"> +//<![CDATA[ +LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64'); +if (LogoutWin) { + LogoutWin.document.write('<html>'); + LogoutWin.document.write('<head><title>Logout</title></head>') ; + LogoutWin.document.write('<body bgcolor="#435370">'); + LogoutWin.document.write('<div align="center" style="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ; + LogoutWin.document.write('<b>Click the button below to disconnect</b><p />'); + LogoutWin.document.write('<form method="POST" action="<?=\$logouturl;?>">'); + LogoutWin.document.write('<input name="logout_id" type="hidden" value="<?=\$sessionid;?>" />'); + LogoutWin.document.write('<input name="zone" type="hidden" value="<?=\$cpzone;?>" />'); + LogoutWin.document.write('<input name="logout" type="submit" value="Logout" />'); + LogoutWin.document.write('</form>'); + LogoutWin.document.write('</div></body>'); + LogoutWin.document.write('</html>'); + LogoutWin.document.close(); +} + +document.location.href="<?=\$my_redirurl;?>"; +//]]> +</script> +</body> +</html> + +EOD; + } + + $fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w"); + if ($fd) { + fwrite($fd, $logouttext); + fclose($fd); + } + unset($logouttext); + + /* write elements */ + captiveportal_write_elements(); + + /* kill any running mini_httpd */ + killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid"); + killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid"); + + /* start up the webserving daemon */ + captiveportal_init_webgui_zone($cpcfg); + + /* Kill any existing prunecaptiveportal processes */ + if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid")) { + killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"); + } + + /* start pruning process (interval defaults to 60 seconds) */ + mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/cp_prunedb_{$cpzone}.pid " . + "/etc/rc.prunecaptiveportal {$cpzone}"); + + /* generate radius server database */ + unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db"); + captiveportal_init_radius_servers(); + + if (platform_booting()) { + /* send Accounting-On to server */ + captiveportal_send_server_accounting(); + echo "done\n"; + } + + } else { + killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid"); + killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid"); + killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"); + @unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html"); + @unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html"); + @unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html"); + + captiveportal_radius_stop_all(); + + /* send Accounting-Off to server */ + if (!platform_booting()) { + captiveportal_send_server_accounting(true); + } + + /* remove old information */ + unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"); + unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db"); + unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"); + /* Release allocated pipes for this zone */ + captiveportal_free_dnrules(); + + mwexec("/sbin/ipfw zone {$cpzoneid} destroy", true); + + if (empty($config['captiveportal'])) { + set_single_sysctl("net.link.ether.ipfw", "0"); + } else { + /* Deactivate ipfw(4) if not needed */ + $cpactive = false; + if (is_array($config['captiveportal'])) { + foreach ($config['captiveportal'] as $cpkey => $cp) { + if (isset($cp['enable'])) { + $cpactive = true; + break; + } + } + } + if ($cpactive === false) { + set_single_sysctl("net.link.ether.ipfw", "0"); + } + } + } + + unlock($captiveportallck); + + return 0; +} + +function captiveportal_init_webgui() { + global $config, $cpzone; + + if (is_array($config['captiveportal'])) { + foreach ($config['captiveportal'] as $cpkey => $cp) { + $cpzone = $cpkey; + captiveportal_init_webgui_zone($cp); + } + } +} + +function captiveportal_init_webgui_zonename($zone) { + global $config, $cpzone; + + if (isset($config['captiveportal'][$zone])) { + $cpzone = $zone; + captiveportal_init_webgui_zone($config['captiveportal'][$zone]); + } +} + +function captiveportal_init_webgui_zone($cpcfg) { + global $g, $config, $cpzone; + + if (!isset($cpcfg['enable'])) { + return; + } + + if (isset($cpcfg['httpslogin'])) { + $cert = lookup_cert($cpcfg['certref']); + $crt = base64_decode($cert['crt']); + $key = base64_decode($cert['prv']); + $ca = ca_chain($cert); + + /* generate lighttpd configuration */ + if (!empty($cpcfg['listenporthttps'])) { + $listenporthttps = $cpcfg['listenporthttps']; + } else { + $listenporthttps = 8001 + $cpcfg['zoneid']; + } + system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf", + $crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal", + "cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone); + } + + /* generate lighttpd configuration */ + if (!empty($cpcfg['listenporthttp'])) { + $listenporthttp = $cpcfg['listenporthttp']; + } else { + $listenporthttp = 8000 + $cpcfg['zoneid']; + } + system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf", + "", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal", + "", "", $cpzone); + + @unlink("{$g['varrun']}/lighty-{$cpzone}-CaptivePortal.pid"); + /* attempt to start lighttpd */ + $res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf"); + + /* fire up https instance */ + if (isset($cpcfg['httpslogin'])) { + @unlink("{$g['varrun']}/lighty-{$cpzone}-CaptivePortal-SSL.pid"); + $res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf"); + } +} + +function captiveportal_init_rules_byinterface($interface) { + global $cpzone, $cpzoneid, $config; + + if (!is_array($config['captiveportal'])) { + return; + } + + foreach ($config['captiveportal'] as $cpkey => $cp) { + $cpzone = $cpkey; + $cpzoneid = $cp['zoneid']; + $cpinterfaces = explode(",", $cp['interface']); + if (in_array($interface, $cpinterfaces)) { + captiveportal_init_rules(); + break; + } + } +} + +/* reinit will disconnect all users, be careful! */ +function captiveportal_init_rules($reinit = false) { + global $config, $g, $cpzone, $cpzoneid; + + if (!isset($config['captiveportal'][$cpzone]['enable'])) { + return; + } + + captiveportal_load_modules(); + mwexec("/sbin/ipfw zone {$cpzoneid} create", true); + + /* Cleanup so nothing is leaked */ + captiveportal_free_dnrules(); + unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"); + + $cpips = array(); + $ifaces = get_configured_interface_list(); + $cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']); + $firsttime = 0; + foreach ($cpinterfaces as $cpifgrp) { + if (!isset($ifaces[$cpifgrp])) { + continue; + } + $tmpif = get_real_interface($cpifgrp); + if (!empty($tmpif)) { + $cpipm = get_interface_ip($cpifgrp); + if (is_ipaddr($cpipm)) { + $cpips[] = $cpipm; + if (!is_array($config['virtualip']) || !is_array($config['virtualip']['vip'])) { + continue; + } + foreach ($config['virtualip']['vip'] as $vip) { + if (($vip['interface'] == $cpifgrp) && (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) { + $cpips[] = $vip['subnet']; + } + } + } + mwexec("/sbin/ipfw zone {$cpzoneid} madd {$tmpif}", true); + } + } + if (count($cpips) > 0) { + $cpactive = true; + } else { + return false; + } + + if ($reinit == false) { + $captiveportallck = lock("captiveportal{$cpzone}"); + } + + $cprules = <<<EOD + +flush +add 65291 allow pfsync from any to any +add 65292 allow carp from any to any + +# layer 2: pass ARP +add 65301 pass layer2 mac-type arp,rarp +# pfsense requires for WPA +add 65302 pass layer2 mac-type 0x888e,0x88c7 +# PPP Over Ethernet Session Stage/Discovery Stage +add 65303 pass layer2 mac-type 0x8863,0x8864 + +# layer 2: block anything else non-IP(v4/v6) +add 65307 deny layer2 not mac-type ip,ipv6 + +EOD; + + $rulenum = 65310; + /* These tables contain host ips */ + $cprules .= "add {$rulenum} pass ip from any to table(100) in\n"; + $rulenum++; + $cprules .= "add {$rulenum} pass ip from table(100) to any out\n"; + $rulenum++; + $ips = ""; + foreach ($cpips as $cpip) { + $cprules .= "table 100 add {$cpip}\n"; + } + $cprules .= "table 100 add 255.255.255.255\n"; + $cprules .= "add {$rulenum} pass ip from any to {$ips} in\n"; + $rulenum++; + $cprules .= "add {$rulenum} pass ip from {$ips} to any out\n"; + $rulenum++; + $cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n"; + $rulenum++; + $cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n"; + $rulenum++; + /* Allowed ips */ + $cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n"; + $rulenum++; + $cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n"; + $rulenum++; + $cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n"; + $rulenum++; + $cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n"; + $rulenum++; + + /* Authenticated users rules. */ + $cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n"; + $rulenum++; + $cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n"; + $rulenum++; + + if (!empty($config['captiveportal'][$cpzone]['listenporthttp'])) { + $listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp']; + } else { + $listenporthttp = 8000 + $cpzoneid; + } + + if (isset($config['captiveportal'][$cpzone]['httpslogin'])) { + if (!empty($config['captiveportal'][$cpzone]['listenporthttps'])) { + $listenporthttps = $config['captiveportal'][$cpzone]['listenporthttps']; + } else { + $listenporthttps = 8001 + $cpzoneid; + } + if (!isset($config['captiveportal'][$cpzone]['nohttpsforwards'])) { + $cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n"; + } + } + + $cprules .= <<<EOD + +# redirect non-authenticated clients to captive portal +add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in +# let the responses from the captive portal web server back out +add 65533 pass tcp from any to any out +# block everything else +add 65534 deny all from any to any + +EOD; + + /* generate passthru mac database */ + $cprules .= captiveportal_passthrumac_configure(true); + $cprules .= "\n"; + + /* allowed ipfw rules to make allowed ip work */ + $cprules .= captiveportal_allowedip_configure(); + + /* allowed ipfw rules to make allowed hostnames work */ + $cprules .= captiveportal_allowedhostname_configure(); + + /* load rules */ + file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules); + mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true); + //@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules"); + unset($cprules); + + if ($reinit == false) { + unlock($captiveportallck); + } +} + +/* + * Remove clients that have been around for longer than the specified amount of time + * db file structure: + * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval + * (password is in Base64 and only saved when reauthentication is enabled) + */ +function captiveportal_prune_old() { + global $g, $config, $cpzone, $cpzoneid; + + if (empty($cpzone)) { + return; + } + + $cpcfg = $config['captiveportal'][$cpzone]; + $vcpcfg = $config['voucher'][$cpzone]; + + /* check for expired entries */ + $idletimeout = 0; + $timeout = 0; + if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout'])) { + $timeout = $cpcfg['timeout'] * 60; + } + + if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout'])) { + $idletimeout = $cpcfg['idletimeout'] * 60; + } + + /* Is there any job to do? */ + if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) && + !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable'])) { + return; + } + + $radiussrvs = captiveportal_get_radius_servers(); + + /* Read database */ + /* NOTE: while this can be simplified in non radius case keep as is for now */ + $cpdb = captiveportal_read_db(); + + $unsetindexes = array(); + $voucher_needs_sync = false; + /* + * Snapshot the time here to use for calculation to speed up the process. + * If something is missed next run will catch it! + */ + $pruning_time = time(); + $stop_time = $pruning_time; + foreach ($cpdb as $cpentry) { + + $timedout = false; + $term_cause = 1; + if (empty($cpentry[11])) { + $cpentry[11] = 'first'; + } + $radiusservers = $radiussrvs[$cpentry[11]]; + + /* hard timeout? */ + if ($timeout) { + if (($pruning_time - $cpentry[0]) >= $timeout) { + $timedout = true; + $term_cause = 5; // Session-Timeout + } + } + + /* Session-Terminate-Time */ + if (!$timedout && !empty($cpentry[9])) { + if ($pruning_time >= $cpentry[9]) { + $timedout = true; + $term_cause = 5; // Session-Timeout + } + } + + /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */ + $uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout; + /* if an idle timeout is specified, get last activity timestamp from ipfw */ + if (!$timedout && $uidletimeout > 0) { + $lastact = captiveportal_get_last_activity($cpentry[2], $cpentry[3]); + /* If the user has logged on but not sent any traffic they will never be logged out. + * We "fix" this by setting lastact to the login timestamp. + */ + $lastact = $lastact ? $lastact : $cpentry[0]; + if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) { + $timedout = true; + $term_cause = 4; // Idle-Timeout + $stop_time = $lastact; // Entry added to comply with WISPr + } + } + + /* if vouchers are configured, activate session timeouts */ + if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) { + if ($pruning_time >= ($cpentry[0] + $cpentry[7])) { + $timedout = true; + $term_cause = 5; // Session-Timeout + $voucher_needs_sync = true; + } + } + + /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */ + if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) { + if ($pruning_time >= ($cpentry[0] + $cpentry[7])) { + $timedout = true; + $term_cause = 5; // Session-Timeout + } + } + + if ($timedout) { + captiveportal_disconnect($cpentry, $radiusservers, $term_cause, $stop_time); + captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT"); + $unsetindexes[] = $cpentry[5]; + } + + /* do periodic RADIUS reauthentication? */ + if (!$timedout && !empty($radiusservers)) { + if (isset($cpcfg['radacct_enable'])) { + if ($cpcfg['reauthenticateacct'] == "stopstart") { + /* stop and restart accounting */ + RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno + $cpentry[4], // username + $cpentry[5], // sessionid + $cpentry[0], // start time + $radiusservers, + $cpentry[2], // clientip + $cpentry[3], // clientmac + 10); // NAS Request + $clientsn = (is_ipaddrv6($cpentry[2])) ? 128 : 32; + $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 1, $cpentry[2], $clientsn, $cpentry[3]); + $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 2, $cpentry[2], $clientsn, $cpentry[3]); + RADIUS_ACCOUNTING_START($cpentry[1], // ruleno + $cpentry[4], // username + $cpentry[5], // sessionid + $radiusservers, + $cpentry[2], // clientip + $cpentry[3]); // clientmac + } else if ($cpcfg['reauthenticateacct'] == "interimupdate") { + $session_time = $pruning_time - $cpentry[0]; + if (!empty($cpentry[10]) && $cpentry[10] > 60) { + $interval = $cpentry[10]; + } else { + $interval = 0; + } + $past_interval_min = ($session_time > $interval); + if ($interval != 0) { + $within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59); + } + if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) { + RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno + $cpentry[4], // username + $cpentry[5], // sessionid + $cpentry[0], // start time + $radiusservers, + $cpentry[2], // clientip + $cpentry[3], // clientmac + 10, // NAS Request + true); // Interim Updates + } + } + } + + /* check this user against RADIUS again */ + if (isset($cpcfg['reauthenticate'])) { + $auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username + base64_decode($cpentry[6]), // password + $radiusservers, + $cpentry[2], // clientip + $cpentry[3], // clientmac + $cpentry[1]); // ruleno + if ($auth_list['auth_val'] == 3) { + captiveportal_disconnect($cpentry, $radiusservers, 17); + captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']); + $unsetindexes[] = $cpentry[5]; + } else if ($auth_list['auth_val'] == 2) { + captiveportal_reapply_attributes($cpentry, $auth_list); + } + } + } + } + unset($cpdb); + + captiveportal_prune_old_automac(); + + if ($voucher_needs_sync == true) { + /* Trigger a sync of the vouchers on config */ + send_event("service sync vouchers"); + } + + /* write database */ + if (!empty($unsetindexes)) { + captiveportal_remove_entries($unsetindexes); + } +} + +function captiveportal_prune_old_automac() { + global $g, $config, $cpzone, $cpzoneid; + + if (is_array($config['captiveportal'][$cpzone]['passthrumac']) && isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) { + $tmpvoucherdb = array(); + $macrules = ""; + $writecfg = false; + foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) { + if ($emac['logintype'] == "voucher") { + if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) { + if (isset($tmpvoucherdb[$emac['username']])) { + $temac = $config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]; + $ruleno = captiveportal_get_ipfw_passthru_ruleno($temac['mac']); + $pipeno = captiveportal_get_dn_passthru_ruleno($temac['mac']); + if ($ruleno) { + captiveportal_free_ipfw_ruleno($ruleno); + $macrules .= "delete {$ruleno}"; + ++$ruleno; + $macrules .= "delete {$ruleno}"; + } + if ($pipeno) { + captiveportal_free_dn_ruleno($pipeno); + $macrules .= "pipe delete {$pipeno}\n"; + ++$pipeno; + $macrules .= "pipe delete {$pipeno}\n"; + } + $writecfg = true; + captiveportal_logportalauth($temac['username'], $temac['mac'], $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION"); + unset($config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]); + } + $tmpvoucherdb[$emac['username']] = $eid; + } + if (voucher_auth($emac['username']) <= 0) { + $ruleno = captiveportal_get_ipfw_passthru_ruleno($emac['mac']); + $pipeno = captiveportal_get_dn_passthru_ruleno($emac['mac']); + if ($ruleno) { + captiveportal_free_ipfw_ruleno($ruleno); + $macrules .= "delete {$ruleno}"; + ++$ruleno; + $macrules .= "delete {$ruleno}"; + } + if ($pipeno) { + captiveportal_free_dn_ruleno($pipeno); + $macrules .= "pipe delete {$pipeno}\n"; + ++$pipeno; + $macrules .= "pipe delete {$pipeno}\n"; + } + $writecfg = true; + captiveportal_logportalauth($emac['username'], $emac['mac'], $emac['ip'], "EXPIRED {$emac['username']} LOGIN - TERMINATING SESSION"); + unset($config['captiveportal'][$cpzone]['passthrumac'][$eid]); + } + } + } + unset($tmpvoucherdb); + if (!empty($macrules)) { + @file_put_contents("{$g['tmp_path']}/macentry.prunerules.tmp", $macrules); + unset($macrules); + mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry.prunerules.tmp"); + } + if ($writecfg === true) { + write_config("Prune session for auto-added macs"); + } + } +} + +/* remove a single client according to the DB entry */ +function captiveportal_disconnect($dbent, $radiusservers, $term_cause = 1, $stop_time = null) { + global $g, $config, $cpzone, $cpzoneid; + + $stop_time = (empty($stop_time)) ? time() : $stop_time; + + /* this client needs to be deleted - remove ipfw rules */ + if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) { + RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno + $dbent[4], // username + $dbent[5], // sessionid + $dbent[0], // start time + $radiusservers, + $dbent[2], // clientip + $dbent[3], // clientmac + $term_cause, // Acct-Terminate-Cause + false, + $stop_time); + } + + if (is_ipaddr($dbent[2])) { + /* Delete client's ip entry from tables 1 and 2. */ + $clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32; + pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 1, $dbent[2], $clientsn, $dbent[3]); + pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 2, $dbent[2], $clientsn, $dbent[3]); + /* XXX: Redundant?! Ensure all pf(4) states are killed. */ + $_gb = @pfSense_kill_states($dbent[2]); + $_gb = @pfSense_kill_srcstates($dbent[2]); + } + + /* + * These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal + * We could get an error if the pipe doesn't exist but everything should still be fine + */ + if (!empty($dbent[1])) { + $_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}"); + $_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1)); + + /* Release the ruleno so it can be reallocated to new clients. */ + captiveportal_free_dn_ruleno($dbent[1]); + } + + // XMLRPC Call over to the master Voucher node + if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) { + $syncip = $config['voucher'][$cpzone]['vouchersyncdbip']; + $syncport = $config['voucher'][$cpzone]['vouchersyncport']; + $syncpass = $config['voucher'][$cpzone]['vouchersyncpass']; + $vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername']; + $remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time); + } + +} + +/* remove a single client by sessionid */ +function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") { + global $g, $config; + + $radiusservers = captiveportal_get_radius_servers(); + + /* read database */ + $result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'"); + + /* find entry */ + if (!empty($result)) { + captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'"); + + foreach ($result as $cpentry) { + if (empty($cpentry[11])) { + $cpentry[11] = 'first'; + } + captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause); + captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT"); + } + unset($result); + } +} + +/* send RADIUS acct stop for all current clients */ +function captiveportal_radius_stop_all() { + global $config, $cpzone; + + if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) { + return; + } + + $radiusservers = captiveportal_get_radius_servers(); + if (!empty($radiusservers)) { + $cpdb = captiveportal_read_db(); + foreach ($cpdb as $cpentry) { + if (empty($cpentry[11])) { + $cpentry[11] = 'first'; + } + if (!empty($radiusservers[$cpentry[11]])) { + RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno + $cpentry[4], // username + $cpentry[5], // sessionid + $cpentry[0], // start time + $radiusservers[$cpentry[11]], + $cpentry[2], // clientip + $cpentry[3], // clientmac + 7); // Admin Reboot + } + } + } +} + +function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) { + global $config, $g, $cpzone; + + $bwUp = 0; + if (!empty($macent['bw_up'])) { + $bwUp = $macent['bw_up']; + } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) { + $bwUp = $config['captiveportal'][$cpzone]['bwdefaultup']; + } + $bwDown = 0; + if (!empty($macent['bw_down'])) { + $bwDown = $macent['bw_down']; + } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) { + $bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn']; + } + + $ruleno = captiveportal_get_next_ipfw_ruleno(); + + if ($macent['action'] == 'pass') { + $rules = ""; + $pipeno = captiveportal_get_next_dn_ruleno(); + + $pipeup = $pipeno; + if ($pipeinrule == true) { + $_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16"); + } else { + $rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n"; + } + + $pipedown = $pipeno + 1; + if ($pipeinrule == true) { + $_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16"); + } else { + $rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n"; + } + + $rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n"; + $ruleno++; + $rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n"; + } + + return $rules; +} + +function captiveportal_passthrumac_delete_entry($macent) { + $rules = ""; + + if ($macent['action'] == 'pass') { + $ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']); + + if (!$ruleno) { + return $rules; + } + + captiveportal_free_ipfw_ruleno($ruleno); + + $rules .= "delete {$ruleno}\n"; + $rules .= "delete " . ++$ruleno . "\n"; + + $pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']); + + if (!empty($pipeno)) { + captiveportal_free_dn_ruleno($pipeno); + $rules .= "pipe delete " . $pipeno . "\n"; + $rules .= "pipe delete " . ++$pipeno . "\n"; + } + } + + return $rules; +} + +function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) { + global $config, $g, $cpzone; + + $rules = ""; + + if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) { + if ($stopindex > 0) { + $fd = fopen($filename, "w"); + for ($idx = $startindex; $idx <= $stopindex; $idx++) { + if (isset($config['captiveportal'][$cpzone]['passthrumac'][$idx])) { + $rules = captiveportal_passthrumac_configure_entry($config['captiveportal'][$cpzone]['passthrumac'][$idx]); + fwrite($fd, $rules); + } + } + fclose($fd); + + return; + } else { + $nentries = count($config['captiveportal'][$cpzone]['passthrumac']); + if ($nentries > 2000) { + $nloops = $nentries / 1000; + $remainder= $nentries % 1000; + for ($i = 0; $i < $nloops; $i++) { + mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . ((($i+1) * 1000) - 1) . "\""); + } + if ($remainder > 0) { + mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . (($i* 1000) + $remainder) ."\""); + } + } else { + foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) { + $rules .= captiveportal_passthrumac_configure_entry($macent, true); + } + } + } + } + + return $rules; +} + +function captiveportal_passthrumac_findbyname($username) { + global $config, $cpzone; + + if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) { + foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) { + if ($macent['username'] == $username) { + return $macent; + } + } + } + return NULL; +} + +/* + * table (3=IN)/(4=OUT) hold allowed ip's without bw limits + */ +function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) { + global $g; + + /* Instead of copying this entire function for something + * easy such as hostname vs ip address add this check + */ + if ($ishostname === true) { + if (!platform_booting()) { + $ipaddress = gethostbyname($ipent['hostname']); + if (!is_ipaddr($ipaddress)) { + return; + } + } else { + $ipaddress = ""; + } + } else { + $ipaddress = $ipent['ip']; + } + + $rules = ""; + $cp_filterdns_conf = ""; + $enBwup = 0; + if (!empty($ipent['bw_up'])) { + $enBwup = intval($ipent['bw_up']); + } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) { + $enBwup = $config['captiveportal'][$cpzone]['bwdefaultup']; + } + $enBwdown = 0; + if (!empty($ipent['bw_down'])) { + $enBwdown = intval($ipent['bw_down']); + } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) { + $enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn']; + } + + $pipeno = captiveportal_get_next_dn_ruleno(); + $_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16"); + $pipedown = $pipeno + 1; + $_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16"); + if ($ishostname === true) { + $cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n"; + $cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n"; + if (!is_ipaddr($ipaddress)) { + return array("", $cp_filterdns_conf); + } + } + $subnet = ""; + if (!empty($ipent['sn'])) { + $subnet = "/{$ipent['sn']}"; + } + $rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n"; + $rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n"; + + if ($ishostname === true) { + return array($rules, $cp_filterdns_conf); + } else { + return $rules; + } +} + +function captiveportal_allowedhostname_configure() { + global $config, $g, $cpzone, $cpzoneid; + + $rules = ""; + if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) { + $rules = "\n# captiveportal_allowedhostname_configure()\n"; + $cp_filterdns_conf = ""; + foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) { + $tmprules = captiveportal_allowedip_configure_entry($hostnameent, true); + $rules .= $tmprules[0]; + $cp_filterdns_conf .= $tmprules[1]; + } + $cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf"; + @file_put_contents($cp_filterdns_filename, $cp_filterdns_conf); + unset($cp_filterdns_conf); + if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid")) { + sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP"); + } else { + mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzoneid} -d 1"); + } + } else { + killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"); + @unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"); + } + + return $rules; +} + +function captiveportal_allowedip_configure() { + global $config, $g, $cpzone; + + $rules = ""; + if (is_array($config['captiveportal'][$cpzone]['allowedip'])) { + foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) { + $rules .= captiveportal_allowedip_configure_entry($ipent); + } + } + + return $rules; +} + +/* get last activity timestamp given client IP address */ +function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) { + global $cpzoneid; + + $ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, $table, $ip, $mac); + /* Reading only from one of the tables is enough of approximation. */ + if (is_array($ipfwoutput)) { + /* Workaround for #46652 */ + if ($ipfwoutput['packets'] > 0) { + return $ipfwoutput['timestamp']; + } else { + return 0; + } + } + + return 0; +} + +function captiveportal_init_radius_servers() { + global $config, $g, $cpzone; + + /* generate radius server database */ + if ($config['captiveportal'][$cpzone]['radiusip'] && + (!isset($config['captiveportal'][$cpzone]['auth_method']) || $config['captiveportal'][$cpzone]['auth_method'] == "radius")) { + $radiusip = $config['captiveportal'][$cpzone]['radiusip']; + $radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null; + $radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null; + $radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null; + + if ($config['captiveportal'][$cpzone]['radiusport']) { + $radiusport = $config['captiveportal'][$cpzone]['radiusport']; + } else { + $radiusport = 1812; + } + if ($config['captiveportal'][$cpzone]['radiusacctport']) { + $radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport']; + } else { + $radiusacctport = 1813; + } + if ($config['captiveportal'][$cpzone]['radiusport2']) { + $radiusport2 = $config['captiveportal'][$cpzone]['radiusport2']; + } else { + $radiusport2 = 1812; + } + if ($config['captiveportal'][$cpzone]['radiusport3']) { + $radiusport3 = $config['captiveportal'][$cpzone]['radiusport3']; + } else { + $radiusport3 = 1812; + } + if ($config['captiveportal'][$cpzone]['radiusport4']) { + $radiusport4 = $config['captiveportal'][$cpzone]['radiusport4']; + } else { + $radiusport4 = 1812; + } + + $radiuskey = $config['captiveportal'][$cpzone]['radiuskey']; + $radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2']; + $radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3']; + $radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4']; + + $cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX); + $fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w"); + if (!$fd) { + captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n"); + unlock($cprdsrvlck); + return 1; + } + if (isset($radiusip)) { + fwrite($fd, $radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first"); + } + if (isset($radiusip2)) { + fwrite($fd, "\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first"); + } + if (isset($radiusip3)) { + fwrite($fd, "\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second"); + } + if (isset($radiusip4)) { + fwrite($fd, "\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second"); + } + + fclose($fd); + unlock($cprdsrvlck); + } +} + +/* read RADIUS servers into array */ +function captiveportal_get_radius_servers() { + global $g, $cpzone; + + $cprdsrvlck = lock("captiveportalradius{$cpzone}"); + if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) { + $radiusservers = array(); + $cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", + FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if ($cpradiusdb) { + foreach ($cpradiusdb as $cpradiusentry) { + $line = trim($cpradiusentry); + if ($line) { + $radsrv = array(); + list($radsrv['ipaddr'], $radsrv['port'], $radsrv['acctport'], $radsrv['key'], $context) = explode(",", $line); + } + if (empty($context)) { + if (!is_array($radiusservers['first'])) { + $radiusservers['first'] = array(); + } + $radiusservers['first'] = $radsrv; + } else { + if (!is_array($radiusservers[$context])) { + $radiusservers[$context] = array(); + } + $radiusservers[$context][] = $radsrv; + } + } + } + unlock($cprdsrvlck); + return $radiusservers; + } + + unlock($cprdsrvlck); + return false; +} + +/* log successful captive portal authentication to syslog */ +/* part of this code from php.net */ +function captiveportal_logportalauth($user, $mac, $ip, $status, $message = null) { + // Log it + if (!$message) { + $message = "{$status}: {$user}, {$mac}, {$ip}"; + } else { + $message = trim($message); + $message = "{$status}: {$user}, {$mac}, {$ip}, {$message}"; + } + captiveportal_syslog($message); +} + +/* log simple messages to syslog */ +function captiveportal_syslog($message) { + global $cpzone; + + $message = trim($message); + $message = "Zone: {$cpzone} - {$message}"; + openlog("logportalauth", LOG_PID, LOG_LOCAL4); + // Log it + syslog(LOG_INFO, $message); + closelog(); +} + +function radius($username, $password, $clientip, $clientmac, $type, $radiusctx = null) { + global $g, $config, $cpzoneid; + + $pipeno = captiveportal_get_next_dn_ruleno(); + + /* If the pool is empty, return appropriate message and fail authentication */ + if (empty($pipeno)) { + $auth_list = array(); + $auth_list['auth_val'] = 1; + $auth_list['error'] = "System reached maximum login capacity"; + return $auth_list; + } + + $radiusservers = captiveportal_get_radius_servers(); + + if (is_null($radiusctx)) { + $radiusctx = 'first'; + } + + $auth_list = RADIUS_AUTHENTICATION($username, + $password, + $radiusservers[$radiusctx], + $clientip, + $clientmac, + $pipeno); + + if ($auth_list['auth_val'] == 2) { + captiveportal_logportalauth($username, $clientmac, $clientip, $type); + $sessionid = portal_allow($clientip, + $clientmac, + $username, + $password, + $auth_list, + $pipeno, + $radiusctx); + } else { + captiveportal_free_dn_ruleno($pipeno); + } + + return $auth_list; +} + +function captiveportal_opendb() { + global $g, $cpzone; + + $db_path = "{$g['vardb_path']}/captiveportal{$cpzone}.db"; + $createquery = "CREATE TABLE IF NOT EXISTS captiveportal (" . + "allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " . + "sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " . + "session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " . + "CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " . + "CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " . + "CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " . + "CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)"; + + try { + $DB = new SQLite3($db_path); + } catch (Exception $e) { + captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Trying again."); + unlink_if_exists($db_path); + try { + $DB = new SQLite3($db_path); + } catch (Exception $e) { + captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Remove the database file manually and ensure there is enough free space."); + return; + } + } + + if (!$DB) { + captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Trying again."); + unlink_if_exists($db_path); + $DB = new SQLite3($db_path); + if (!$DB) { + captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and ensure there is enough free space."); + return; + } + } + + if (! $DB->exec($createquery)) { + captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}. Resetting and trying again."); + + /* If unable to initialize the database, reset and try again. */ + $DB->close(); + unset($DB); + unlink_if_exists($db_path); + $DB = new SQLite3($db_path); + if ($DB->exec($createquery)) { + captiveportal_syslog("Successfully reinitialized tables for {$cpzone} -- database has been reset."); + } else { + captiveportal_syslog("Still unable to create tables for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and try again."); + } + } + + return $DB; +} + +/* read captive portal DB into array */ +function captiveportal_read_db($query = "") { + $cpdb = array(); + + $DB = captiveportal_opendb(); + if ($DB) { + $response = $DB->query("SELECT * FROM captiveportal {$query}"); + if ($response != FALSE) { + while ($row = $response->fetchArray()) { + $cpdb[] = $row; + } + } + $DB->close(); + } + + return $cpdb; +} + +function captiveportal_remove_entries($remove) { + + if (!is_array($remove) || empty($remove)) { + return; + } + + $query = "DELETE FROM captiveportal WHERE sessionid in ("; + foreach ($remove as $idx => $unindex) { + $query .= "'{$unindex}'"; + if ($idx < (count($remove) - 1)) { + $query .= ","; + } + } + $query .= ")"; + captiveportal_write_db($query); +} + +/* write captive portal DB */ +function captiveportal_write_db($queries) { + global $g; + + if (is_array($queries)) { + $query = implode(";", $queries); + } else { + $query = $queries; + } + + $DB = captiveportal_opendb(); + if ($DB) { + $DB->exec("BEGIN TRANSACTION"); + $result = $DB->exec($query); + if (!$result) { + captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}"); + } else { + $DB->exec("END TRANSACTION"); + } + $DB->close(); + return $result; + } else { + return true; + } +} + +function captiveportal_write_elements() { + global $g, $config, $cpzone; + + $cpcfg = $config['captiveportal'][$cpzone]; + + if (!is_dir($g['captiveportal_element_path'])) { + @mkdir($g['captiveportal_element_path']); + } + + if (is_array($cpcfg['element'])) { + conf_mount_rw(); + foreach ($cpcfg['element'] as $data) { + if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) { + printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n"); + return 1; + } + if (!file_exists("{$g['captiveportal_path']}/{$data['name']}")) { + @symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}"); + } + } + conf_mount_ro(); + } + + return 0; +} + +function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) { + global $cpzone; + + $cpruleslck = lock("captiveportalrulesdn", LOCK_EX); + if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) { + $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules")); + $ridx = $rulenos_start; + while ($ridx < $rulenos_range_max) { + if ($rules[$ridx] == $cpzone) { + $rules[$ridx] = false; + $ridx++; + $rules[$ridx] = false; + $ridx++; + } else { + $ridx += 2; + } + } + file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules)); + unset($rules); + } + unlock($cpruleslck); +} + +function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) { + global $config, $g, $cpzone; + + $cpruleslck = lock("captiveportalrulesdn", LOCK_EX); + $ruleno = 0; + if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) { + $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules")); + $ridx = $rulenos_start; + while ($ridx < $rulenos_range_max) { + if (empty($rules[$ridx])) { + $ruleno = $ridx; + $rules[$ridx] = $cpzone; + $ridx++; + $rules[$ridx] = $cpzone; + break; + } else { + $ridx += 2; + } + } + } else { + $rules = array_pad(array(), $rulenos_range_max, false); + $ruleno = $rulenos_start; + $rules[$rulenos_start] = $cpzone; + $rulenos_start++; + $rules[$rulenos_start] = $cpzone; + } + file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules)); + unlock($cpruleslck); + unset($rules); + + return $ruleno; +} + +function captiveportal_free_dn_ruleno($ruleno) { + global $config, $g; + + $cpruleslck = lock("captiveportalrulesdn", LOCK_EX); + if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) { + $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules")); + $rules[$ruleno] = false; + $ruleno++; + $rules[$ruleno] = false; + file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules)); + unset($rules); + } + unlock($cpruleslck); +} + +function captiveportal_get_dn_passthru_ruleno($value) { + global $config, $g, $cpzone, $cpzoneid; + + $cpcfg = $config['captiveportal'][$cpzone]; + if (!isset($cpcfg['enable'])) { + return NULL; + } + + $cpruleslck = lock("captiveportalrulesdn", LOCK_EX); + $ruleno = NULL; + if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) { + $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules")); + unset($output); + $_gb = exec("/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep " . escapeshellarg($value) . " | /usr/bin/grep -v grep | /usr/bin/awk '{print $5}' | /usr/bin/head -n 1", $output); + $ruleno = intval($output[0]); + if (!$rules[$ruleno]) { + $ruleno = NULL; + } + unset($rules); + } + unlock($cpruleslck); + + return $ruleno; +} + +/* + * This function will calculate the lowest free firewall ruleno + * within the range specified based on the actual logged on users + * + */ +function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) { + global $config, $g, $cpzone; + + $cpcfg = $config['captiveportal'][$cpzone]; + if (!isset($cpcfg['enable'])) { + return NULL; + } + + $cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX); + $ruleno = 0; + if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) { + $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")); + $ridx = $rulenos_start; + while ($ridx < $rulenos_range_max) { + if (empty($rules[$ridx])) { + $ruleno = $ridx; + $rules[$ridx] = $cpzone; + $ridx++; + $rules[$ridx] = $cpzone; + break; + } else { + /* + * This allows our traffic shaping pipes to be the in pipe the same as ruleno + * and the out pipe ruleno + 1. + */ + $ridx += 2; + } + } + } else { + $rules = array_pad(array(), $rulenos_range_max, false); + $ruleno = $rulenos_start; + $rules[$rulenos_start] = $cpzone; + $rulenos_start++; + $rules[$rulenos_start] = $cpzone; + } + file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules)); + unlock($cpruleslck); + unset($rules); + + return $ruleno; +} + +function captiveportal_free_ipfw_ruleno($ruleno) { + global $config, $g, $cpzone; + + $cpcfg = $config['captiveportal'][$cpzone]; + if (!isset($cpcfg['enable'])) { + return NULL; + } + + $cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX); + if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) { + $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")); + $rules[$ruleno] = false; + $ruleno++; + $rules[$ruleno] = false; + file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules)); + unset($rules); + } + unlock($cpruleslck); +} + +function captiveportal_get_ipfw_passthru_ruleno($value) { + global $config, $g, $cpzone, $cpzoneid; + + $cpcfg = $config['captiveportal'][$cpzone]; + if (!isset($cpcfg['enable'])) { + return NULL; + } + + $cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX); + $ruleno = NULL; + if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) { + $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")); + unset($output); + $_gb = exec("/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep " . escapeshellarg($value) . " | /usr/bin/grep -v grep | /usr/bin/awk '{print $1}' | /usr/bin/head -n 1", $output); + $ruleno = intval($output[0]); + if (!$rules[$ruleno]) { + $ruleno = NULL; + } + unset($rules); + } + unlock($cpruleslck); + + return $ruleno; +} + +/** + * This function will calculate the traffic produced by a client + * based on its firewall rule + * + * Point of view: NAS + * + * Input means: from the client + * Output means: to the client + * + */ + +function getVolume($ip, $mac = NULL) { + global $config, $cpzone, $cpzoneid; + + $reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true; + $volume = array(); + // Initialize vars properly, since we don't want NULL vars + $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ; + + $ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac); + if (is_array($ipfw)) { + if ($reverse) { + $volume['output_pkts'] = $ipfw['packets']; + $volume['output_bytes'] = $ipfw['bytes']; + } + else { + $volume['input_pkts'] = $ipfw['packets']; + $volume['input_bytes'] = $ipfw['bytes']; + } + } + + $ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac); + if (is_array($ipfw)) { + if ($reverse) { + $volume['input_pkts'] = $ipfw['packets']; + $volume['input_bytes'] = $ipfw['bytes']; + } + else { + $volume['output_pkts'] = $ipfw['packets']; + $volume['output_bytes'] = $ipfw['bytes']; + } + } + + return $volume; +} + +/** + * Get the NAS-IP-Address based on the current wan address + * + * Use functions in interfaces.inc to find this out + * + */ + +function getNasIP() { + global $config, $cpzone; + + if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) { + $nasIp = get_interface_ip(); + } else { + if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) { + $nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute']; + } else { + $nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']); + } + } + + if (!is_ipaddr($nasIp)) { + $nasIp = "0.0.0.0"; + } + + return $nasIp; +} + +function portal_ip_from_client_ip($cliip) { + global $config, $cpzone; + + $isipv6 = is_ipaddrv6($cliip); + $interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']); + foreach ($interfaces as $cpif) { + if ($isipv6) { + $ip = get_interface_ipv6($cpif); + $sn = get_interface_subnetv6($cpif); + } else { + $ip = get_interface_ip($cpif); + $sn = get_interface_subnet($cpif); + } + if (ip_in_subnet($cliip, "{$ip}/{$sn}")) { + return $ip; + } + } + + $inet = ($isipv6) ? '-inet6' : '-inet'; + $iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'"); + $iface = trim($iface, "\n"); + if (!empty($iface)) { + $ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface); + if (is_ipaddr($ip)) { + return $ip; + } + } + + // doesn't match up to any particular interface + // so let's set the portal IP to what PHP says + // the server IP issuing the request is. + // allows same behavior as 1.2.x where IP isn't + // in the subnet of any CP interface (static routes, etc.) + // rather than forcing to DNS hostname resolution + $ip = $_SERVER['SERVER_ADDR']; + if (is_ipaddr($ip)) { + return $ip; + } + + return false; +} + +function portal_hostname_from_client_ip($cliip) { + global $config, $cpzone; + + $cpcfg = $config['captiveportal'][$cpzone]; + + if (isset($cpcfg['httpslogin'])) { + $listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001); + $ourhostname = $cpcfg['httpsname']; + + if ($listenporthttps != 443) { + $ourhostname .= ":" . $listenporthttps; + } + } else { + $listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : ($cpcfg['zoneid'] + 8000); + $ifip = portal_ip_from_client_ip($cliip); + if (!$ifip) { + $ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}"; + } else { + $ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}"; + } + + if ($listenporthttp != 80) { + $ourhostname .= ":" . $listenporthttp; + } + } + + return $ourhostname; +} + +/* functions move from index.php */ + +function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) { + global $g, $config, $cpzone; + + /* Get captive portal layout */ + if ($type == "redir") { + header("Location: {$redirurl}"); + return; + } else if ($type == "login") { + $htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html"); + } else { + $htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html"); + } + + $cpcfg = $config['captiveportal'][$cpzone]; + + /* substitute the PORTAL_REDIRURL variable */ + if ($cpcfg['preauthurl']) { + $htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext); + $htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext); + } + + /* substitute other variables */ + $ourhostname = portal_hostname_from_client_ip($clientip); + $protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://'; + $htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext); + $htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext); + + $htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext); + $htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext); + $htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext); + $htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext); + $htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext); + + // Special handling case for captive portal master page so that it can be ran + // through the PHP interpreter using the include method above. We convert the + // $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out. + $htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext); + $htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext); + $htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext); + $htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext); + $htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext); + $htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext); + $htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext); + + echo $htmltext; +} + +function portal_mac_radius($clientmac, $clientip) { + global $config, $cpzone; + + $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret']; + + /* authentication against the radius server */ + $username = mac_format($clientmac); + $auth_list = radius($username, $radmac_secret, $clientip, $clientmac, "MACHINE LOGIN"); + if ($auth_list['auth_val'] == 2) { + return TRUE; + } + + if (!empty($auth_list['url_redirection'])) { + portal_reply_page($auth_list['url_redirection'], "redir"); + } + + return FALSE; +} + +function captiveportal_reapply_attributes($cpentry, $attributes) { + global $config, $cpzone, $g; + + if (isset($config['captiveportal'][$cpzone]['peruserbw'])) { + $dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0; + $dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0; + } else { + $dwfaultbw_up = $dwfaultbw_down = 0; + } + $bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up; + $bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down; + $bw_up_pipeno = $cpentry[1]; + $bw_down_pipeno = $cpentry[1]+1; + + $_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16"); + $_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16"); + //captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}"); + + unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down); +} + +function portal_allow($clientip, $clientmac, $username, $password = null, $attributes = null, $pipeno = null, $radiusctx = null) { + global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone, $cpzoneid; + + // Ensure we create an array if we are missing attributes + if (!is_array($attributes)) { + $attributes = array(); + } + + unset($sessionid); + + /* Do not allow concurrent login execution. */ + $cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX); + + if ($attributes['voucher']) { + $remaining_time = $attributes['session_timeout']; + } + + $writecfg = false; + /* Find an existing session */ + if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) { + if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) { + $mac = captiveportal_passthrumac_findbyname($username); + if (!empty($mac)) { + if ($_POST['replacemacpassthru']) { + foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) { + if ($macent['mac'] == $mac['mac']) { + $macrules = ""; + $ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']); + $pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']); + if ($ruleno) { + captiveportal_free_ipfw_ruleno($ruleno); + $macrules .= "delete {$ruleno}\n"; + ++$ruleno; + $macrules .= "delete {$ruleno}\n"; + } + if ($pipeno) { + captiveportal_free_dn_ruleno($pipeno); + $macrules .= "pipe delete {$pipeno}\n"; + ++$pipeno; + $macrules .= "pipe delete {$pipeno}\n"; + } + unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]); + $mac['action'] = 'pass'; + $mac['mac'] = $clientmac; + $config['captiveportal'][$cpzone]['passthrumac'][] = $mac; + $macrules .= captiveportal_passthrumac_configure_entry($mac); + file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules); + mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp"); + $writecfg = true; + $sessionid = true; + break; + } + } + } else { + portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.", + $clientmac, $clientip, $username, $password); + unlock($cpdblck); + return; + } + } + } + } + + /* read in client database */ + $query = "WHERE ip = '{$clientip}'"; + $tmpusername = strtolower($username); + if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) { + $query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')"; + } + $cpdb = captiveportal_read_db($query); + + /* Snapshot the timestamp */ + $allow_time = time(); + $radiusservers = captiveportal_get_radius_servers(); + $unsetindexes = array(); + if (is_null($radiusctx)) { + $radiusctx = 'first'; + } + + foreach ($cpdb as $cpentry) { + if (empty($cpentry[11])) { + $cpentry[11] = 'first'; + } + /* on the same ip */ + if ($cpentry[2] == $clientip) { + if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac) { + captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING OLD SESSION"); + } else { + captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}"); + } + $sessionid = $cpentry[5]; + break; + } elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) { + // user logged in with an active voucher. Check for how long and calculate + // how much time we can give him (voucher credit - used time) + $remaining_time = $cpentry[0] + $cpentry[7] - $allow_time; + if ($remaining_time < 0) { // just in case. + $remaining_time = 0; + } + + /* This user was already logged in so we disconnect the old one */ + captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13); + captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION"); + $unsetindexes[] = $cpentry[5]; + break; + } elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) { + /* on the same username */ + if (strcasecmp($cpentry[4], $username) == 0) { + /* This user was already logged in so we disconnect the old one */ + captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13); + captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION"); + $unsetindexes[] = $cpentry[5]; + break; + } + } + } + unset($cpdb); + + if (!empty($unsetindexes)) { + captiveportal_remove_entries($unsetindexes); + } + + if ($attributes['voucher'] && $remaining_time <= 0) { + return 0; // voucher already used and no time left + } + + if (!isset($sessionid)) { + /* generate unique session ID */ + $tod = gettimeofday(); + $sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16); + + if ($passthrumac) { + $mac = array(); + $mac['action'] = 'pass'; + $mac['mac'] = $clientmac; + $mac['ip'] = $clientip; /* Used only for logging */ + if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) { + $mac['username'] = $username; + if ($attributes['voucher']) { + $mac['logintype'] = "voucher"; + } + } + if ($username == "unauthenticated") { + $mac['descr'] = "Auto-added"; + } else { + $mac['descr'] = "Auto-added for user {$username}"; + } + if (!empty($bw_up)) { + $mac['bw_up'] = $bw_up; + } + if (!empty($bw_down)) { + $mac['bw_down'] = $bw_down; + } + if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) { + $config['captiveportal'][$cpzone]['passthrumac'] = array(); + } + $config['captiveportal'][$cpzone]['passthrumac'][] = $mac; + unlock($cpdblck); + $macrules = captiveportal_passthrumac_configure_entry($mac); + file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules); + mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp"); + $writecfg = true; + } else { + /* See if a pipeno is passed, if not start sessions because this means there isn't one atm */ + if (is_null($pipeno)) { + $pipeno = captiveportal_get_next_dn_ruleno(); + } + + /* if the pool is empty, return appropriate message and exit */ + if (is_null($pipeno)) { + portal_reply_page($redirurl, "error", "System reached maximum login capacity"); + log_error("Zone: {$cpzone} - WARNING! Captive portal has reached maximum login capacity"); + unlock($cpdblck); + return; + } + + if (isset($config['captiveportal'][$cpzone]['peruserbw'])) { + $dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0; + $dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0; + } else { + $dwfaultbw_up = $dwfaultbw_down = 0; + } + $bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up; + $bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down; + + $bw_up_pipeno = $pipeno; + $bw_down_pipeno = $pipeno + 1; + //$bw_up /= 1000; // Scale to Kbit/s + $_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16"); + $_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16"); + + $clientsn = (is_ipaddrv6($clientip)) ? 128 : 32; + if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) { + $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno); + } else { + $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno); + } + + if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) { + $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno); + } else { + $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno); + } + + if ($attributes['voucher']) { + $attributes['session_timeout'] = $remaining_time; + } + + /* handle empty attributes */ + $session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL'; + $idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL'; + $session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL'; + $interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL'; + + /* escape username */ + $safe_username = SQLite3::escapeString($username); + + /* encode password in Base64 just in case it contains commas */ + $bpassword = base64_encode($password); + $insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) "; + $insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', "; + $insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')"; + + /* store information to database */ + captiveportal_write_db($insertquery); + unlock($cpdblck); + unset($insertquery, $bpassword); + + if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) { + $acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac); + if ($acct_val == 1) { + captiveportal_logportalauth($username, $clientmac, $clientip, $type, "RADIUS ACCOUNTING FAILED"); + } + } + } + } else { + /* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */ + if (!is_null($pipeno)) { + captiveportal_free_dn_ruleno($pipeno); + } + + unlock($cpdblck); + } + + if ($writecfg == true) { + write_config(); + } + + /* redirect user to desired destination */ + if (!empty($attributes['url_redirection'])) { + $my_redirurl = $attributes['url_redirection']; + } else if (!empty($redirurl)) { + $my_redirurl = $redirurl; + } else if (!empty($config['captiveportal'][$cpzone]['redirurl'])) { + $my_redirurl = $config['captiveportal'][$cpzone]['redirurl']; + } + + if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) { + $ourhostname = portal_hostname_from_client_ip($clientip); + $protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://'; + $logouturl = "{$protocol}{$ourhostname}/"; + + if (isset($attributes['reply_message'])) { + $message = $attributes['reply_message']; + } else { + $message = 0; + } + + include("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html"); + + } else { + portal_reply_page($my_redirurl, "redir", "Just redirect the user."); + } + + return $sessionid; +} + + +/* + * Used for when pass-through credits are enabled. + * Returns true when there was at least one free login to deduct for the MAC. + * Expired entries are removed as they are seen. + * Active entries are updated according to the configuration. + */ +function portal_consume_passthrough_credit($clientmac) { + global $config, $cpzone; + + if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) { + $freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count']; + } else { + return false; + } + + if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) { + $resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout']; + } else { + return false; + } + + if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) { + return false; + } + + $updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']); + + /* + * Read database of used MACs. Lines are a comma-separated list + * of the time, MAC, then the count of pass-through credits remaining. + */ + $usedmacs = captiveportal_read_usedmacs_db(); + + $currenttime = time(); + $found = false; + foreach ($usedmacs as $key => $usedmac) { + $usedmac = explode(",", $usedmac); + + if ($usedmac[1] == $clientmac) { + if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) { + if ($usedmac[2] < 1) { + if ($updatetimeouts) { + $usedmac[0] = $currenttime; + unset($usedmacs[$key]); + $usedmacs[] = implode(",", $usedmac); + captiveportal_write_usedmacs_db($usedmacs); + } + + return false; + } else { + $usedmac[2] -= 1; + $usedmacs[$key] = implode(",", $usedmac); + } + + $found = true; + } else { + unset($usedmacs[$key]); + } + + break; + } else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) { + unset($usedmacs[$key]); + } + } + + if (!$found) { + $usedmac = array($currenttime, $clientmac, $freeloginscount - 1); + $usedmacs[] = implode(",", $usedmac); + } + + captiveportal_write_usedmacs_db($usedmacs); + return true; +} + +function captiveportal_read_usedmacs_db() { + global $g, $cpzone; + + $cpumaclck = lock("captiveusedmacs{$cpzone}"); + if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) { + $usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!$usedmacs) { + $usedmacs = array(); + } + } else { + $usedmacs = array(); + } + + unlock($cpumaclck); + return $usedmacs; +} + +function captiveportal_write_usedmacs_db($usedmacs) { + global $g, $cpzone; + + $cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX); + @file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs)); + unlock($cpumaclck); +} + +function captiveportal_blocked_mac($mac) { + global $config, $g, $cpzone; + + if (empty($mac) || !is_macaddr($mac)) { + return false; + } + + if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) { + return false; + } + + foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) { + if (($passthrumac['action'] == 'block') && + ($passthrumac['mac'] == strtolower($mac))) { + return true; + } + } + + return false; + +} + +function captiveportal_send_server_accounting($off = false) { + global $cpzone, $config; + + if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) { + return; + } + if ($off) { + $racct = new Auth_RADIUS_Acct_Off; + } else { + $racct = new Auth_RADIUS_Acct_On; + } + $radiusservers = captiveportal_get_radius_servers(); + if (empty($radiusservers)) { + return; + } + foreach ($radiusservers['first'] as $radsrv) { + // Add a new server to our instance + $racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']); + } + if (PEAR::isError($racct->start())) { + $retvalue['acct_val'] = 1; + $retvalue['error'] = $racct->getMessage(); + + // If we encounter an error immediately stop this function and go back + $racct->close(); + return $retvalue; + } + // Send request + $result = $racct->send(); + // Evaluation of the response + // 5 -> Accounting-Response + // See RFC2866 for this. + if (PEAR::isError($result)) { + $retvalue['acct_val'] = 1; + $retvalue['error'] = $result->getMessage(); + } else if ($result === true) { + $retvalue['acct_val'] = 5 ; + } else { + $retvalue['acct_val'] = 1 ; + } + + $racct->close(); + return $retvalue; +} +?> diff --git a/src/etc/inc/certs.inc b/src/etc/inc/certs.inc new file mode 100644 index 0000000..9c99952 --- /dev/null +++ b/src/etc/inc/certs.inc @@ -0,0 +1,867 @@ +<?php +/* $Id$ */ +/* + certs.inc + Copyright (C) 2008 Shrew Soft Inc + Copyright (C) 2010 Jim Pingle <jimp@pfsense.org> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + pfSense_MODULE: certificate_manager +*/ + +define("OPEN_SSL_CONF_PATH", "/etc/ssl/openssl.cnf"); + +require_once("functions.inc"); + +global $openssl_digest_algs; +$openssl_digest_algs = array("sha1", "sha224", "sha256", "sha384", "sha512"); + +global $openssl_crl_status; +$openssl_crl_status = array( + OCSP_REVOKED_STATUS_NOSTATUS => "No Status (default)", + OCSP_REVOKED_STATUS_UNSPECIFIED => "Unspecified", + OCSP_REVOKED_STATUS_KEYCOMPROMISE => "Key Compromise", + OCSP_REVOKED_STATUS_CACOMPROMISE => "CA Compromise", + OCSP_REVOKED_STATUS_AFFILIATIONCHANGED => "Affiliation Changed", + OCSP_REVOKED_STATUS_SUPERSEDED => "Superseded", + OCSP_REVOKED_STATUS_CESSATIONOFOPERATION => "Cessation of Operation", + OCSP_REVOKED_STATUS_CERTIFICATEHOLD => "Certificate Hold" +); + +function & lookup_ca($refid) { + global $config; + + if (is_array($config['ca'])) { + foreach ($config['ca'] as & $ca) { + if ($ca['refid'] == $refid) { + return $ca; + } + } + } + + return false; +} + +function & lookup_ca_by_subject($subject) { + global $config; + + if (is_array($config['ca'])) { + foreach ($config['ca'] as & $ca) { + $ca_subject = cert_get_subject($ca['crt']); + if ($ca_subject == $subject) { + return $ca; + } + } + } + + return false; +} + +function & lookup_cert($refid) { + global $config; + + if (is_array($config['cert'])) { + foreach ($config['cert'] as & $cert) { + if ($cert['refid'] == $refid) { + return $cert; + } + } + } + + return false; +} + +function & lookup_cert_by_name($name) { + global $config; + if (is_array($config['cert'])) { + foreach ($config['cert'] as & $cert) { + if ($cert['descr'] == $name) { + return $cert; + } + } + } +} + +function & lookup_crl($refid) { + global $config; + + if (is_array($config['crl'])) { + foreach ($config['crl'] as & $crl) { + if ($crl['refid'] == $refid) { + return $crl; + } + } + } + + return false; +} + +function ca_chain_array(& $cert) { + if ($cert['caref']) { + $chain = array(); + $crt = lookup_ca($cert['caref']); + $chain[] = $crt; + while ($crt) { + $caref = $crt['caref']; + if ($caref) { + $crt = lookup_ca($caref); + } else { + $crt = false; + } + if ($crt) { + $chain[] = $crt; + } + } + return $chain; + } + return false; +} + +function ca_chain(& $cert) { + if ($cert['caref']) { + $ca = ""; + $cas = ca_chain_array($cert); + if (is_array($cas)) { + foreach ($cas as & $ca_cert) { + $ca .= base64_decode($ca_cert['crt']); + $ca .= "\n"; + } + } + return $ca; + } + return ""; +} + +function ca_import(& $ca, $str, $key = "", $serial = 0) { + global $config; + + $ca['crt'] = base64_encode($str); + if (!empty($key)) { + $ca['prv'] = base64_encode($key); + } + if (!empty($serial)) { + $ca['serial'] = $serial; + } + $subject = cert_get_subject($str, false); + $issuer = cert_get_issuer($str, false); + + // Find my issuer unless self-signed + if ($issuer <> $subject) { + $issuer_crt =& lookup_ca_by_subject($issuer); + if ($issuer_crt) { + $ca['caref'] = $issuer_crt['refid']; + } + } + + /* Correct if child certificate was loaded first */ + if (is_array($config['ca'])) { + foreach ($config['ca'] as & $oca) { + $issuer = cert_get_issuer($oca['crt']); + if ($ca['refid'] <> $oca['refid'] && $issuer == $subject) { + $oca['caref'] = $ca['refid']; + } + } + } + if (is_array($config['cert'])) { + foreach ($config['cert'] as & $cert) { + $issuer = cert_get_issuer($cert['crt']); + if ($issuer == $subject) { + $cert['caref'] = $ca['refid']; + } + } + } + return true; +} + +function ca_create(& $ca, $keylen, $lifetime, $dn, $digest_alg = "sha256") { + + $args = array( + "x509_extensions" => "v3_ca", + "digest_alg" => $digest_alg, + "private_key_bits" => (int)$keylen, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + "encrypt_key" => false); + + // generate a new key pair + $res_key = openssl_pkey_new($args); + if (!$res_key) { + return false; + } + + // generate a certificate signing request + $res_csr = openssl_csr_new($dn, $res_key, $args); + if (!$res_csr) { + return false; + } + + // self sign the certificate + $res_crt = openssl_csr_sign($res_csr, null, $res_key, $lifetime, $args); + if (!$res_crt) { + return false; + } + + // export our certificate data + if (!openssl_pkey_export($res_key, $str_key) || + !openssl_x509_export($res_crt, $str_crt)) { + return false; + } + + // return our ca information + $ca['crt'] = base64_encode($str_crt); + $ca['prv'] = base64_encode($str_key); + $ca['serial'] = 0; + + return true; +} + +function ca_inter_create(& $ca, $keylen, $lifetime, $dn, $caref, $digest_alg = "sha256") { + // Create Intermediate Certificate Authority + $signing_ca =& lookup_ca($caref); + if (!$signing_ca) { + return false; + } + + $signing_ca_res_crt = openssl_x509_read(base64_decode($signing_ca['crt'])); + $signing_ca_res_key = openssl_pkey_get_private(array(0 => base64_decode($signing_ca['prv']) , 1 => "")); + if (!$signing_ca_res_crt || !$signing_ca_res_key) { + return false; + } + $signing_ca_serial = ++$signing_ca['serial']; + + $args = array( + "x509_extensions" => "v3_ca", + "digest_alg" => $digest_alg, + "private_key_bits" => (int)$keylen, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + "encrypt_key" => false); + + // generate a new key pair + $res_key = openssl_pkey_new($args); + if (!$res_key) { + return false; + } + + // generate a certificate signing request + $res_csr = openssl_csr_new($dn, $res_key, $args); + if (!$res_csr) { + return false; + } + + // Sign the certificate + $res_crt = openssl_csr_sign($res_csr, $signing_ca_res_crt, $signing_ca_res_key, $lifetime, $args, $signing_ca_serial); + if (!$res_crt) { + return false; + } + + // export our certificate data + if (!openssl_pkey_export($res_key, $str_key) || + !openssl_x509_export($res_crt, $str_crt)) { + return false; + } + + // return our ca information + $ca['crt'] = base64_encode($str_crt); + $ca['prv'] = base64_encode($str_key); + $ca['serial'] = 0; + + return true; +} + +function cert_import(& $cert, $crt_str, $key_str) { + + $cert['crt'] = base64_encode($crt_str); + $cert['prv'] = base64_encode($key_str); + + $subject = cert_get_subject($crt_str, false); + $issuer = cert_get_issuer($crt_str, false); + + // Find my issuer unless self-signed + if ($issuer <> $subject) { + $issuer_crt =& lookup_ca_by_subject($issuer); + if ($issuer_crt) { + $cert['caref'] = $issuer_crt['refid']; + } + } + return true; +} + +function cert_create(& $cert, $caref, $keylen, $lifetime, $dn, $type = "user", $digest_alg = "sha256") { + + $cert['type'] = $type; + + if ($type != "self-signed") { + $cert['caref'] = $caref; + $ca =& lookup_ca($caref); + if (!$ca) { + return false; + } + + $ca_str_crt = base64_decode($ca['crt']); + $ca_str_key = base64_decode($ca['prv']); + $ca_res_crt = openssl_x509_read($ca_str_crt); + $ca_res_key = openssl_pkey_get_private(array(0 => $ca_str_key, 1 => "")); + if (!$ca_res_key) { + return false; + } + $ca_serial = ++$ca['serial']; + } + + switch ($type) { + case "ca": + $cert_type = "v3_ca"; + break; + case "server": + case "self-signed": + $cert_type = "server"; + break; + default: + $cert_type = "usr_cert"; + break; + } + + // in case of using Subject Alternative Names use other sections (with postfix '_san') + // pass subjectAltName over environment variable 'SAN' + if ($dn['subjectAltName']) { + putenv("SAN={$dn['subjectAltName']}"); // subjectAltName can be set _only_ via configuration file + $cert_type .= '_san'; + unset($dn['subjectAltName']); + } + + $args = array( + "x509_extensions" => $cert_type, + "digest_alg" => $digest_alg, + "private_key_bits" => (int)$keylen, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + "encrypt_key" => false); + + // generate a new key pair + $res_key = openssl_pkey_new($args); + if (!$res_key) { + return false; + } + + // If this is a self-signed cert, blank out the CA and sign with the cert's key + if ($type == "self-signed") { + $ca = null; + $ca_res_crt = null; + $ca_res_key = $res_key; + $ca_serial = 0; + $cert['type'] = "server"; + } + + // generate a certificate signing request + $res_csr = openssl_csr_new($dn, $res_key, $args); + if (!$res_csr) { + return false; + } + + // sign the certificate using an internal CA + $res_crt = openssl_csr_sign($res_csr, $ca_res_crt, $ca_res_key, $lifetime, + $args, $ca_serial); + if (!$res_crt) { + return false; + } + + // export our certificate data + if (!openssl_pkey_export($res_key, $str_key) || + !openssl_x509_export($res_crt, $str_crt)) { + return false; + } + + // return our certificate information + $cert['crt'] = base64_encode($str_crt); + $cert['prv'] = base64_encode($str_key); + + return true; +} + +function csr_generate(& $cert, $keylen, $dn, $digest_alg = "sha256") { + + $args = array( + "x509_extensions" => "v3_req", + "digest_alg" => $digest_alg, + "private_key_bits" => (int)$keylen, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + "encrypt_key" => false); + + // generate a new key pair + $res_key = openssl_pkey_new($args); + if (!$res_key) { + return false; + } + + // generate a certificate signing request + $res_csr = openssl_csr_new($dn, $res_key, $args); + if (!$res_csr) { + return false; + } + + // export our request data + if (!openssl_pkey_export($res_key, $str_key) || + !openssl_csr_export($res_csr, $str_csr)) { + return false; + } + + // return our request information + $cert['csr'] = base64_encode($str_csr); + $cert['prv'] = base64_encode($str_key); + + return true; +} + +function csr_complete(& $cert, $str_crt) { + + // return our request information + $cert['crt'] = base64_encode($str_crt); + unset($cert['csr']); + + return true; +} + +function csr_get_subject($str_crt, $decode = true) { + + if ($decode) { + $str_crt = base64_decode($str_crt); + } + + $components = openssl_csr_get_subject($str_crt); + + if (empty($components) || !is_array($components)) { + return "unknown"; + } + + ksort($components); + foreach ($components as $a => $v) { + if (!strlen($subject)) { + $subject = "{$a}={$v}"; + } else { + $subject = "{$a}={$v}, {$subject}"; + } + } + + return $subject; +} + +function cert_get_subject($str_crt, $decode = true) { + + if ($decode) { + $str_crt = base64_decode($str_crt); + } + + $inf_crt = openssl_x509_parse($str_crt); + $components = $inf_crt['subject']; + + if (empty($components) || !is_array($components)) { + return "unknown"; + } + + ksort($components); + foreach ($components as $a => $v) { + if (is_array($v)) { + ksort($v); + foreach ($v as $w) { + $asubject = "{$a}={$w}"; + $subject = (strlen($subject)) ? "{$asubject}, {$subject}" : $asubject; + } + } else { + $asubject = "{$a}={$v}"; + $subject = (strlen($subject)) ? "{$asubject}, {$subject}" : $asubject; + } + } + + return $subject; +} + +function cert_get_subject_array($crt) { + $str_crt = base64_decode($crt); + $inf_crt = openssl_x509_parse($str_crt); + $components = $inf_crt['subject']; + + if (!is_array($components)) { + return; + } + + $subject_array = array(); + + foreach ($components as $a => $v) { + $subject_array[] = array('a' => $a, 'v' => $v); + } + + return $subject_array; +} + +function cert_get_subject_hash($crt) { + $str_crt = base64_decode($crt); + $inf_crt = openssl_x509_parse($str_crt); + return $inf_crt['subject']; +} + +function cert_get_issuer($str_crt, $decode = true) { + + if ($decode) { + $str_crt = base64_decode($str_crt); + } + + $inf_crt = openssl_x509_parse($str_crt); + $components = $inf_crt['issuer']; + + if (empty($components) || !is_array($components)) { + return "unknown"; + } + + ksort($components); + foreach ($components as $a => $v) { + if (is_array($v)) { + ksort($v); + foreach ($v as $w) { + $aissuer = "{$a}={$w}"; + $issuer = (strlen($issuer)) ? "{$aissuer}, {$issuer}" : $aissuer; + } + } else { + $aissuer = "{$a}={$v}"; + $issuer = (strlen($issuer)) ? "{$aissuer}, {$issuer}" : $aissuer; + } + } + + return $issuer; +} + +/* this function works on x509 (crt), rsa key (prv), and req(csr) */ +function cert_get_modulus($str_crt, $decode = true, $type = "crt") { + if ($decode) { + $str_crt = base64_decode($str_crt); + } + + $modulus = ""; + if (in_array($type, array("crt", "prv", "csr"))) { + $type = str_replace(array("crt", "prv", "csr"), array("x509", "rsa", "req"), $type); + $modulus = exec("echo \"{$str_crt}\" | openssl {$type} -noout -modulus"); + } + return $modulus; +} +function csr_get_modulus($str_crt, $decode = true) { + return cert_get_modulus($str_crt, $decode, "csr"); +} + +function cert_get_purpose($str_crt, $decode = true) { + if ($decode) { + $str_crt = base64_decode($str_crt); + } + $crt_details = openssl_x509_parse($str_crt); + $purpose = array(); + $purpose['ca'] = (stristr($crt_details['extensions']['basicConstraints'], 'CA:TRUE') === false) ? 'No': 'Yes'; + $purpose['server'] = ($crt_details['extensions']['nsCertType'] == "SSL Server") ? 'Yes': 'No'; + return $purpose; +} + +function cert_get_dates($str_crt, $decode = true) { + if ($decode) { + $str_crt = base64_decode($str_crt); + } + $crt_details = openssl_x509_parse($str_crt); + if ($crt_details['validFrom_time_t'] > 0) { + $start = date('r', $crt_details['validFrom_time_t']); + } + if ($crt_details['validTo_time_t'] > 0) { + $end = date('r', $crt_details['validTo_time_t']); + } + return array($start, $end); +} + +function cert_get_serial($str_crt, $decode = true) { + if ($decode) { + $str_crt = base64_decode($str_crt); + } + $crt_details = openssl_x509_parse($str_crt); + if (isset($crt_details['serialNumber']) && !empty($crt_details['serialNumber'])) { + return $crt_details['serialNumber']; + } else { + return NULL; + } +} + +function prv_get_modulus($str_crt, $decode = true) { + return cert_get_modulus($str_crt, $decode, "prv"); +} + +function is_user_cert($certref) { + global $config; + if (!is_array($config['system']['user'])) { + return; + } + foreach ($config['system']['user'] as $user) { + if (!is_array($user['cert'])) { + continue; + } + foreach ($user['cert'] as $cert) { + if ($certref == $cert) { + return true; + } + } + } + return false; +} + +function is_openvpn_server_cert($certref) { + global $config; + if (!is_array($config['openvpn']['openvpn-server'])) { + return; + } + foreach ($config['openvpn']['openvpn-server'] as $ovpns) { + if ($ovpns['certref'] == $certref) { + return true; + } + } + return false; +} + +function is_openvpn_client_cert($certref) { + global $config; + if (!is_array($config['openvpn']['openvpn-client'])) { + return; + } + foreach ($config['openvpn']['openvpn-client'] as $ovpnc) { + if ($ovpnc['certref'] == $certref) { + return true; + } + } + return false; +} + +function is_ipsec_cert($certref) { + global $config; + if (!is_array($config['ipsec']['phase1'])) { + return; + } + foreach ($config['ipsec']['phase1'] as $ipsec) { + if ($ipsec['certref'] == $certref) { + return true; + } + } + return false; +} + +function is_webgui_cert($certref) { + global $config; + if (($config['system']['webgui']['ssl-certref'] == $certref) && + ($config['system']['webgui']['protocol'] != "http")) { + return true; + } +} + +function is_captiveportal_cert($certref) { + global $config; + if (!is_array($config['captiveportal'])) { + return; + } + foreach ($config['captiveportal'] as $portal) { + if (isset($portal['enable']) && isset($portal['httpslogin']) && ($portal['certref'] == $certref)) { + return true; + } + } + return false; +} + +function cert_in_use($certref) { + return (is_webgui_cert($certref) || + is_user_cert($certref) || + is_openvpn_server_cert($certref) || + is_openvpn_client_cert($certref) || + is_ipsec_cert($certref) || + is_captiveportal_cert($certref)); +} + +function crl_create(& $crl, $caref, $name, $serial = 0, $lifetime = 9999) { + global $config; + $ca =& lookup_ca($caref); + if (!$ca) { + return false; + } + $crl['descr'] = $name; + $crl['caref'] = $caref; + $crl['serial'] = $serial; + $crl['lifetime'] = $lifetime; + $crl['cert'] = array(); + $crl_res = crl_update($crl); + $config['crl'][] = $crl; + return $crl_res; +} + +function crl_update(& $crl) { + global $config; + $ca =& lookup_ca($crl['caref']); + if (!$ca) { + return false; + } + // If we have text but no certs, it was imported and cannot be updated. + if (($crl["method"] != "internal") && (!empty($crl['text']) && empty($crl['cert']))) { + return false; + } + $crl['serial']++; + $ca_str_crt = base64_decode($ca['crt']); + $ca_str_key = base64_decode($ca['prv']); + $crl_res = openssl_crl_new($ca_str_crt, $crl['serial'], $crl['lifetime']); + if (is_array($crl['cert']) && (count($crl['cert']) > 0)) { + foreach ($crl['cert'] as $cert) { + openssl_crl_revoke_cert($crl_res, base64_decode($cert["crt"]), $cert["revoke_time"], $cert["reason"]); + } + } + openssl_crl_export($crl_res, $crl_text, $ca_str_key); + $crl['text'] = base64_encode($crl_text); + return $crl_res; +} + +function cert_revoke($cert, & $crl, $reason = OCSP_REVOKED_STATUS_UNSPECIFIED) { + global $config; + if (is_cert_revoked($cert, $crl['refid'])) { + return true; + } + // If we have text but no certs, it was imported and cannot be updated. + if (!is_crl_internal($crl)) { + return false; + } + $cert["reason"] = $reason; + $cert["revoke_time"] = time(); + $crl["cert"][] = $cert; + crl_update($crl); + return true; +} + +function cert_unrevoke($cert, & $crl) { + global $config; + if (!is_crl_internal($crl)) { + return false; + } + foreach ($crl['cert'] as $id => $rcert) { + if (($rcert['refid'] == $cert['refid']) || ($rcert['descr'] == $cert['descr'])) { + unset($crl['cert'][$id]); + if (count($crl['cert']) == 0) { + // Protect against accidentally switching the type to imported, for older CRLs + if (!isset($crl['method'])) { + $crl['method'] = "internal"; + } + crl_update($crl); + } else { + crl_update($crl); + } + return true; + } + } + return false; +} + +/* Compare two certificates to see if they match. */ +function cert_compare($cert1, $cert2) { + /* Ensure two certs are identical by first checking that their issuers match, then + subjects, then serial numbers, and finally the moduli. Anything less strict + could accidentally count two similar, but different, certificates as + being identical. */ + $c1 = base64_decode($cert1['crt']); + $c2 = base64_decode($cert2['crt']); + if ((cert_get_issuer($c1, false) == cert_get_issuer($c2, false)) && + (cert_get_subject($c1, false) == cert_get_subject($c2, false)) && + (cert_get_serial($c1, false) == cert_get_serial($c2, false)) && + (cert_get_modulus($c1, false) == cert_get_modulus($c2, false))) { + return true; + } + return false; +} + +function is_cert_revoked($cert, $crlref = "") { + global $config; + if (!is_array($config['crl'])) { + return false; + } + + if (!empty($crlref)) { + $crl = lookup_crl($crlref); + if (!is_array($crl['cert'])) { + return false; + } + foreach ($crl['cert'] as $rcert) { + if (cert_compare($rcert, $cert)) { + return true; + } + } + } else { + foreach ($config['crl'] as $crl) { + if (!is_array($crl['cert'])) { + continue; + } + foreach ($crl['cert'] as $rcert) { + if (cert_compare($rcert, $cert)) { + return true; + } + } + } + } + return false; +} + +function is_openvpn_server_crl($crlref) { + global $config; + if (!is_array($config['openvpn']['openvpn-server'])) { + return; + } + foreach ($config['openvpn']['openvpn-server'] as $ovpns) { + if (!empty($ovpns['crlref']) && ($ovpns['crlref'] == $crlref)) { + return true; + } + } + return false; +} + +// Keep this general to allow for future expansion. See cert_in_use() above. +function crl_in_use($crlref) { + return (is_openvpn_server_crl($crlref)); +} + +function is_crl_internal($crl) { + return (!(!empty($crl['text']) && empty($crl['cert'])) || ($crl["method"] == "internal")); +} + +function cert_get_cn($crt, $isref = false) { + /* If this is a certref, not an actual cert, look up the cert first */ + if ($isref) { + $cert = lookup_cert($crt); + /* If it's not a valid cert, bail. */ + if (!(is_array($cert) && !empty($cert['crt']))) { + return ""; + } + $cert = $cert['crt']; + } else { + $cert = $crt; + } + $sub = cert_get_subject_array($cert); + if (is_array($sub)) { + foreach ($sub as $s) { + if (strtoupper($s['a']) == "CN") { + return $s['v']; + } + } + } + return ""; +} + +?> diff --git a/src/etc/inc/config.console.inc b/src/etc/inc/config.console.inc new file mode 100644 index 0000000..df3fa6f --- /dev/null +++ b/src/etc/inc/config.console.inc @@ -0,0 +1,560 @@ +<?php +/****h* pfSense/config + * NAME + * config.inc - Functions to manipulate config.xml + * DESCRIPTION + * This include contains various config.xml specific functions. + * HISTORY + * $Id$ + ****** + + config.console.inc + Copyright (C) 2004-2010 Scott Ullrich + All rights reserved. + + originally part of m0n0wall (http://m0n0.ch/wall) + Copyright (C) 2003-2004 Manuel Kasper <mk@neon1.net>. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + pfSense_BUILDER_BINARIES: /sbin/mount /sbin/umount /sbin/halt /sbin/fsck + pfSense_MODULE: config +*/ + +function set_networking_interfaces_ports() { + global $noreboot; + global $config; + global $g; + global $fp; + + $fp = fopen('php://stdin', 'r'); + + $memory = get_memory(); + $physmem = $memory[0]; + $realmem = $memory[1]; + + if ($physmem < $g['minimum_ram_warning']) { + echo "\n\n\n"; + echo gettext("DANGER! WARNING! ACHTUNG!") . "\n\n"; + printf(gettext("%s requires *AT LEAST* %s RAM to function correctly.%s"), $g['product_name'], $g['minimum_ram_warning_text'], "\n"); + printf(gettext("Only (%s) MB RAM has been detected, with (%s) available to %s.%s"), $realmem, $physmem, $g['product_name'], "\n"); + echo "\n" . gettext("Press ENTER to continue.") . " "; + fgets($fp); + echo "\n"; + } + + $iflist = get_interface_list(); + + /* Function flow is based on $key and $auto_assign or the lack thereof */ + $key = null; + + /* Only present auto interface option if running from LiveCD and interface mismatch*/ + if ((preg_match("/cdrom/", $g['platform'])) && is_interface_mismatch()) { + $auto_assign = false; + } + + echo <<<EOD + +Valid interfaces are: + + +EOD; + + if (!is_array($iflist)) { + echo gettext("No interfaces found!") . "\n"; + $iflist = array(); + } else { + foreach ($iflist as $iface => $ifa) { + echo sprintf("% -7s%s %s %s\n", $iface, $ifa['mac'], + $ifa['up'] ? " (up)" : "(down)", $ifa['dmesg']); + } + } + + if ($auto_assign) { + echo <<<EOD + + !!! LiveCD Detected: Auto Interface Option !!!! +BEGIN MANUAL CONFIGURATION OR WE WILL PROCEED WITH AUTO CONFIGURATION. + +EOD; + } + + echo <<<EOD + +Do you want to set up VLANs first? + +If you are not going to use VLANs, or only for optional interfaces, you should +say no here and use the webConfigurator to configure VLANs later, if required. + +Do you want to set up VLANs now [y|n]? +EOD; + + if ($auto_assign) { + $key = timeout(); + } else { + $key = chop(fgets($fp)); + } + + if (!isset($key) and $auto_assign) { // Auto Assign Interfaces + do { + echo <<<EOD + + !!! Auto Assigning Interfaces !!! + +For installation purposes, you must plug in at least one NIC +for the LAN connection. If you plug in a second NIC it will be +assigned to WAN. Otherwise, we'll temporarily assign WAN to the +next available NIC found regardless of activity. You should +assign and configure the WAN interface according to your requirements + +If you haven't plugged in any network cables yet, +now is the time to do so. +We'll keep trying until you do. + +Searching for active interfaces... + +EOD; + unset($wanif, $lanif); + + $media_iflist = $plugged_in = array(); + $media_iflist = get_interface_list("media"); + foreach ($media_iflist as $iface => $ifa) { + if ($ifa['up']) { + $plugged_in[] = $iface; + } + } + + $lanif = array_shift($plugged_in); + $wanif = array_shift($plugged_in); + + if (isset($lanif) && !isset($wanif)) { + foreach ($iflist as $iface => $ifa) { + if ($iface != $lanif) { + $wanif = $iface; + break; + } + } + } + + echo <<<EOD + +Assigned WAN to : $wanif +Assigned LAN to : $lanif + +If you don't like this assignment, +press any key to go back to manual configuration. + +EOD; + $key = timeout(20); + if (isset($key)) { + return; + } + } while (!isset($wanif)); + + $config['system']['enablesshd'] = 'enabled'; + $key = 'y'; + + } else { + //Manually assign interfaces + if (in_array($key, array('y', 'Y'))) { + vlan_setup(); + } + + if (is_array($config['vlans']['vlan']) && count($config['vlans']['vlan'])) { + + echo "\n\n" . gettext("VLAN interfaces:") . "\n\n"; + foreach ($config['vlans']['vlan'] as $vlan) { + + echo sprintf("% -16s%s\n", "{$vlan['if']}_vlan{$vlan['tag']}", + "VLAN tag {$vlan['tag']}, parent interface {$vlan['if']}"); + + $iflist[$vlan['if'] . '_vlan' . $vlan['tag']] = array(); + } + } + + echo <<<EOD + +If you do not know the names of your interfaces, you may choose to use +auto-detection. In that case, disconnect all interfaces now before +hitting 'a' to initiate auto detection. + +EOD; + + do { + echo "\n" . gettext("Enter the WAN interface name or 'a' for auto-detection:") . " "; + $wanif = chop(fgets($fp)); + if ($wanif === "") { + return; + } + if ($wanif === "a") { + $wanif = autodetect_interface("WAN", $fp); + } else if (!array_key_exists($wanif, $iflist)) { + printf(gettext("%sInvalid interface name '%s'%s"), "\n", $wanif, "\n"); + unset($wanif); + continue; + } + } while (!$wanif); + + do { + printf(gettext("%sEnter the LAN interface name or 'a' for auto-detection %s" . + "NOTE: this enables full Firewalling/NAT mode.%s" . + "(or nothing if finished):%s"), "\n", "\n", "\n", " "); + + $lanif = chop(fgets($fp)); + + if ($lanif == "exit") { + exit; + } + + if ($lanif == "") { + /* It is OK to have just a WAN, without a LAN so break if the user does not want LAN. */ + break; + } + + if ($lanif === "a") { + $lanif = autodetect_interface("LAN", $fp); + } else if (!array_key_exists($lanif, $iflist)) { + printf(gettext("%sInvalid interface name '%s'%s"), "\n", $lanif, "\n"); + unset($lanif); + continue; + } + } while (!$lanif); + + /* optional interfaces */ + $i = 0; + $optif = array(); + + if ($lanif <> "") { + while (1) { + if ($optif[$i]) { + $i++; + } + $io = $i + 1; + + if ($config['interfaces']['opt' . $io]['descr']) { + printf(gettext("%sOptional interface %s description found: %s"), "\n", $io, $config['interfaces']['opt' . $io]['descr']); + } + + printf(gettext("%sEnter the Optional %s interface name or 'a' for auto-detection%s" . + "(or nothing if finished):%s"), "\n", $io, "\n", " "); + + $optif[$i] = chop(fgets($fp)); + + if ($optif[$i]) { + if ($optif[$i] === "a") { + $ad = autodetect_interface(gettext("Optional") . " " . $io, $fp); + if ($ad) { + $optif[$i] = $ad; + } else { + unset($optif[$i]); + } + } else if (!array_key_exists($optif[$i], $iflist)) { + printf(gettext("%sInvalid interface name '%s'%s"), "\n", $optif[$i], "\n"); + unset($optif[$i]); + continue; + } + } else { + unset($optif[$i]); + break; + } + } + } + + /* check for double assignments */ + $ifarr = array_merge(array($lanif, $wanif), $optif); + + for ($i = 0; $i < (count($ifarr)-1); $i++) { + for ($j = ($i+1); $j < count($ifarr); $j++) { + if ($ifarr[$i] == $ifarr[$j]) { + echo <<<EOD + +Error: you cannot assign the same interface name twice! + +EOD; + fclose($fp); + return; + } + } + } + + echo "\n" . gettext("The interfaces will be assigned as follows:") . "\n\n"; + + echo "WAN -> " . $wanif . "\n"; + if ($lanif != "") { + echo "LAN -> " . $lanif . "\n"; + } + for ($i = 0; $i < count($optif); $i++) { + echo "OPT" . ($i+1) . " -> " . $optif[$i] . "\n"; + } + + echo <<<EOD + +Do you want to proceed [y|n]? +EOD; + $key = chop(fgets($fp)); + } + + if (in_array($key, array('y', 'Y'))) { + if ($lanif) { + if (!is_array($config['interfaces']['lan'])) { + $config['interfaces']['lan'] = array(); + } + $config['interfaces']['lan']['if'] = $lanif; + $config['interfaces']['lan']['enable'] = true; + } elseif (!platform_booting() && !$auto_assign) { + +echo <<<EODD + +You have chosen to remove the LAN interface. + +Would you like to remove the LAN IP address and +unload the interface now? [y|n]? +EODD; + + if (strcasecmp(chop(fgets($fp)), "y") == 0) { + if (isset($config['interfaces']['lan']) && $config['interfaces']['lan']['if']) { + mwexec("/sbin/ifconfig " . $config['interfaces']['lan']['if'] . " delete"); + } + } + if (isset($config['interfaces']['lan'])) { + unset($config['interfaces']['lan']); + } + if (isset($config['dhcpd']['lan'])) { + unset($config['dhcpd']['lan']); + } + if (isset($config['interfaces']['lan']['if'])) { + unset($config['interfaces']['lan']['if']); + } + if (isset($config['interfaces']['wan']['blockpriv'])) { + unset($config['interfaces']['wan']['blockpriv']); + } + if (isset($config['shaper'])) { + unset($config['shaper']); + } + if (isset($config['ezshaper'])) { + unset($config['ezshaper']); + } + if (isset($config['nat'])) { + unset($config['nat']); + } + } else { + if (isset($config['interfaces']['lan']['if'])) { + mwexec("/sbin/ifconfig " . $config['interfaces']['lan']['if'] . " delete"); + } + if (isset($config['interfaces']['lan'])) { + unset($config['interfaces']['lan']); + } + if (isset($config['dhcpd']['lan'])) { + unset($config['dhcpd']['lan']); + } + if (isset($config['interfaces']['lan']['if'])) { + unset($config['interfaces']['lan']['if']); + } + if (isset($config['interfaces']['wan']['blockpriv'])) { + unset($config['interfaces']['wan']['blockpriv']); + } + if (isset($config['shaper'])) { + unset($config['shaper']); + } + if (isset($config['ezshaper'])) { + unset($config['ezshaper']); + } + if (isset($config['nat'])) { + unset($config['nat']); + } + } + if (preg_match($g['wireless_regex'], $lanif)) { + if (is_array($config['interfaces']['lan']) && + !is_array($config['interfaces']['lan']['wireless'])) { + $config['interfaces']['lan']['wireless'] = array(); + } + } else { + if (isset($config['interfaces']['lan'])) { + unset($config['interfaces']['lan']['wireless']); + } + } + + if (!is_array($config['interfaces']['wan'])) { + $config['interfaces']['wan'] = array(); + } + $config['interfaces']['wan']['if'] = $wanif; + $config['interfaces']['wan']['enable'] = true; + if (preg_match($g['wireless_regex'], $wanif)) { + if (is_array($config['interfaces']['wan']) && + !is_array($config['interfaces']['wan']['wireless'])) { + $config['interfaces']['wan']['wireless'] = array(); + } + } else { + if (isset($config['interfaces']['wan'])) { + unset($config['interfaces']['wan']['wireless']); + } + } + + for ($i = 0; $i < count($optif); $i++) { + if (!is_array($config['interfaces']['opt' . ($i+1)])) { + $config['interfaces']['opt' . ($i+1)] = array(); + } + + $config['interfaces']['opt' . ($i+1)]['if'] = $optif[$i]; + + /* wireless interface? */ + if (preg_match($g['wireless_regex'], $optif[$i])) { + if (!is_array($config['interfaces']['opt' . ($i+1)]['wireless'])) { + $config['interfaces']['opt' . ($i+1)]['wireless'] = array(); + } + } else { + unset($config['interfaces']['opt' . ($i+1)]['wireless']); + } + + if (empty($config['interfaces']['opt' . ($i+1)]['descr'])) { + $config['interfaces']['opt' . ($i+1)]['descr'] = "OPT" . ($i+1); + unset($config['interfaces']['opt' . ($i+1)]['enable']); + } + } + + /* remove all other (old) optional interfaces */ + for (; isset($config['interfaces']['opt' . ($i+1)]); $i++) { + unset($config['interfaces']['opt' . ($i+1)]); + } + + printf(gettext("%sWriting configuration..."), "\n"); + write_config("Console assignment of interfaces"); + printf(gettext("done.%s"), "\n"); + + fclose($fp); + + if (platform_booting()) { + return; + } + + echo gettext("One moment while we reload the settings..."); + echo gettext(" done!") . "\n"; + + touch("{$g['tmp_path']}/assign_complete"); + + } +} + +function autodetect_interface($ifname, $fp) { + $iflist_prev = get_interface_list("media"); + echo <<<EOD + +Connect the {$ifname} interface now and make sure that the link is up. +Then press ENTER to continue. + +EOD; + fgets($fp); + $iflist = get_interface_list("media"); + + foreach ($iflist_prev as $ifn => $ifa) { + if (!$ifa['up'] && $iflist[$ifn]['up']) { + printf(gettext("Detected link-up on interface %s.%s"), $ifn, "\n"); + return $ifn; + } + } + + printf(gettext("No link-up detected.%s"), "\n"); + + return null; +} + +function interfaces_setup() { + global $iflist, $config, $g, $fp; + + $iflist = get_interface_list(); +} + +function vlan_setup() { + global $iflist, $config, $g, $fp; + + $iflist = get_interface_list(); + + if (is_array($config['vlans']['vlan']) && count($config['vlans']['vlan'])) { + + echo <<<EOD + +WARNING: all existing VLANs will be cleared if you proceed! + +Do you want to proceed [y|n]? +EOD; + + if (strcasecmp(chop(fgets($fp)), "y") != 0) { + return; + } + } + + $config['vlans']['vlan'] = array(); + echo "\n"; + + $vlanif = 0; + + while (1) { + $vlan = array(); + + echo "\n\n" . gettext("VLAN Capable interfaces:") . "\n\n"; + if (!is_array($iflist)) { + echo gettext("No interfaces found!") . "\n"; + } else { + $vlan_capable = 0; + foreach ($iflist as $iface => $ifa) { + if (is_jumbo_capable($iface)) { + echo sprintf("% -8s%s%s\n", $iface, $ifa['mac'], + $ifa['up'] ? " (up)" : ""); + $vlan_capable++; + } + } + } + + if ($vlan_capable == 0) { + echo gettext("No VLAN capable interfaces detected.") . "\n"; + return; + } + + echo "\n" . gettext("Enter the parent interface name for the new VLAN (or nothing if finished):") . " "; + $vlan['if'] = chop(fgets($fp)); + + if ($vlan['if']) { + if (!array_key_exists($vlan['if'], $iflist) or + !is_jumbo_capable($vlan['if'])) { + printf(gettext("%sInvalid interface name '%s'%s"), "\n", $vlan['if'], "\n"); + continue; + } + } else { + break; + } + + echo gettext("Enter the VLAN tag (1-4094):") . " "; + $vlan['tag'] = chop(fgets($fp)); + $vlan['vlanif'] = "{$vlan['if']}_vlan{$vlan['tag']}"; + if (!is_numericint($vlan['tag']) || ($vlan['tag'] < 1) || ($vlan['tag'] > 4094)) { + printf(gettext("%sInvalid VLAN tag '%s'%s"), "\n", $vlan['tag'], "\n"); + continue; + } + + $config['vlans']['vlan'][] = $vlan; + $vlanif++; + } +} + +?> diff --git a/src/etc/inc/config.gui.inc b/src/etc/inc/config.gui.inc new file mode 100644 index 0000000..56b5555 --- /dev/null +++ b/src/etc/inc/config.gui.inc @@ -0,0 +1,96 @@ +<?php +/****h* pfSense/config + * NAME + * config.gui.inc - Functions to manipulate config.xml + * DESCRIPTION + * This include contains various config.xml specific functions. + * HISTORY + * $Id$ + ****** + + config.gui.inc + Copyright (C) 2004-2010 Scott Ullrich + All rights reserved. + + originally part of m0n0wall (http://m0n0.ch/wall) + Copyright (C) 2003-2004 Manuel Kasper <mk@neon1.net>. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + pfSense_BUILDER_BINARIES: /sbin/mount /sbin/umount /sbin/halt /sbin/fsck + pfSense_MODULE: config +*/ + +require_once("globals.inc"); + +/* do not load this file twice. */ +if ($config_parsed == true) { + return; +} else { + $config_parsed = true; +} + +/* include globals from notices.inc /utility/XML parser files */ +require_once('config.lib.inc'); +require_once("notices.inc"); +require_once("util.inc"); +require_once("IPv6.inc"); +if (file_exists("/cf/conf/use_xmlreader")) { + require_once("xmlreader.inc"); +} else { + require_once("xmlparse.inc"); +} +require_once("crypt.inc"); + +/* read platform */ +if (file_exists("{$g['etc_path']}/platform")) { + $g['platform'] = chop(file_get_contents("{$g['etc_path']}/platform")); +} else { + $g['platform'] = "unknown"; +} + +/* if /debugging exists, lets set $debugging + so we can output more information */ +if (file_exists("/debugging")) { + $debugging = true; + $g['debug'] = true; +} + +$config = parse_config(); + +/* set timezone */ +$timezone = $config['system']['timezone']; +if (!$timezone) { + $timezone = "Etc/UTC"; +} +date_default_timezone_set("$timezone"); + +if ($config_parsed == true) { + /* process packager manager custom rules */ + if (is_dir("/usr/local/pkg/parse_config")) { + run_plugins("/usr/local/pkg/parse_config/"); + } +} + +?> diff --git a/src/etc/inc/config.inc b/src/etc/inc/config.inc new file mode 100644 index 0000000..4792ac3 --- /dev/null +++ b/src/etc/inc/config.inc @@ -0,0 +1,226 @@ +<?php +/****h* pfSense/config + * NAME + * config.inc - Functions to manipulate config.xml + * DESCRIPTION + * This include contains various config.xml specific functions. + * HISTORY + * $Id$ + ****** + + config.inc + Copyright (C) 2004-2010 Scott Ullrich + All rights reserved. + + originally part of m0n0wall (http://m0n0.ch/wall) + Copyright (C) 2003-2004 Manuel Kasper <mk@neon1.net>. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + pfSense_BUILDER_BINARIES: /sbin/mount /sbin/umount /sbin/halt /sbin/fsck + pfSense_MODULE: config +*/ + +if (!function_exists('platform_booting')) { + require_once('globals.inc'); +} + +/* do not load this file twice. */ +//if (in_array("/etc/inc/config.inc", get_included_files())) +// return; + +// Set the memory limit to 128M on i386. When someone has something like 500+ tunnels +// the parser needs quite a bit of ram. Do not remove this line unless you +// know what you are doing. If in doubt, check with dev@ _/FIRST/_! +if (!$ARCH) { + $ARCH = php_uname("m"); +} + +// Set memory limit to 256M on amd64. +if ($ARCH == "amd64") { + ini_set("memory_limit", "256M"); +} else { + ini_set("memory_limit", "128M"); +} + +/* include globals from notices.inc /utility/XML parser files */ +require_once("notices.inc"); +require_once("util.inc"); +require_once("IPv6.inc"); +require_once('config.lib.inc'); +if (file_exists("/cf/conf/use_xmlreader")) { + require_once("xmlreader.inc"); +} else { + require_once("xmlparse.inc"); +} +require_once("crypt.inc"); + +/* read platform */ +if (file_exists("{$g['etc_path']}/platform")) { + $g['platform'] = chop(file_get_contents("{$g['etc_path']}/platform")); +} else { + $g['platform'] = "unknown"; +} + +/* if /debugging exists, lets set $debugging + so we can output more information */ +if (file_exists("/debugging")) { + $debugging = true; + $g['debug'] = true; +} + +if (platform_booting(true)) { + echo "."; + if (file_exists("/cf/conf/config.xml")) { + $config_contents = file_get_contents("/cf/conf/config.xml"); + if (stristr($config_contents, "<m0n0wall>") == true) { + echo "."; + /* user has just upgraded from m0n0wall, replace root xml tags */ + log_error(gettext("Upgrading m0n0wall configuration to pfSense... ")); + $config_contents = str_replace("m0n0wall", "pfsense", $config_contents); + if (!config_validate("{$g['conf_path']}/config.xml")) { + log_error(gettext("ERROR! Could not convert m0n0wall -> pfsense in config.xml")); + } + conf_mount_rw(); + file_put_contents("/cf/conf/config.xml", $config_contents); + conf_mount_ro(); + } + unset($config_contents); + } + /* if our config file exists bail out, we're already set. */ + else if (!file_exists($g['cf_conf_path'] . "/config.xml")) { + echo "."; + /* find the device where config.xml resides and write out an fstab */ + unset($cfgdevice); + echo "."; + /* check if there's already an fstab (NFS booting?) */ + if (!file_exists("{$g['etc_path']}/fstab")) { + echo "."; + if (strstr($g['platform'], "cdrom")) { + /* config is on floppy disk for CD-ROM version */ + $cfgdevice = $cfgpartition = "fd0"; + $_gb = exec('/sbin/dmesg -a', $dmesg); + if (preg_match("/da0/", $dmesg) == true) { + $cfgdevice = $cfgpartition = "da0" ; + if (mwexec("/sbin/mount -r /dev/{$cfgdevice} /cf")) { + /* could not mount, fallback to floppy */ + $cfgdevice = $cfgpartition = "fd0"; + } + } + unset($dmesg); + $cfgfstype = "msdosfs"; + echo gettext("CDROM build") . "\n"; + echo " " . gettext("CFG:") . " {$cfgpartition}\n"; + echo " " . gettext("CFG:") . " {$cfgpartition}\n"; + echo " " . gettext("TYPE:") . " {$cfgfstype}\n"; + } else { + echo "."; + /* probe kernel known disks until we find one with config.xml */ + $disks = explode(" ", get_single_sysctl("kern.disks")); + foreach ($disks as $mountdisk) { + /* skip mfs mounted filesystems */ + if (strstr($mountdisk, "md")) { + continue; + } + if (mwexec("/sbin/mount -r /dev/{$mountdisk}a {$g['cf_path']}") == 0) { + if (file_exists("{$g['cf_conf_path']}/config.xml")) { + /* found it */ + $cfgdevice = $mountdisk; + $cfgpartition = $cfgdevice . "a"; + $cfgfstype = "ufs"; + printf(gettext("Found configuration on %s.%s"), $cfgdevice, "\n"); + } + + mwexec("/sbin/umount -f {$g['cf_path']}"); + + if ($cfgdevice) { + break; + } + } + if (mwexec("/sbin/mount -r /dev/{$mountdisk}d {$g['cf_path']}") == 0) { + if (platform_booting()) { + echo "."; + } + if (file_exists("{$g['cf_conf_path']}/config.xml")) { + /* found it */ + $cfgdevice = $mountdisk; + $cfgpartition = $cfgdevice . "d"; + $cfgfstype = "ufs"; + printf(gettext("Found configuration on %s.%s"), $cfgdevice, "\n"); + } + + mwexec("/sbin/umount -f {$g['cf_path']}"); + + if ($cfgdevice) { + break; + } + } + } + } + echo "."; + if (!$cfgdevice) { + $last_backup = discover_last_backup(); + if ($last_backup) { + log_error(gettext("No config.xml found, attempting last known config restore.")); + file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", ""); + restore_backup("/cf/conf/backup/{$last_backup}"); + } else { + log_error(gettext("No config.xml or config backups found, resetting to factory defaults.")); + restore_backup('/conf.default/config.xml'); + } + } + + /* write device name to a file for rc.firmware */ + file_put_contents("{$g['varetc_path']}/cfdevice", $cfgdevice . "\n"); + + /* write out an fstab */ + + $fstab = "/dev/{$cfgpartition} {$g['cf_path']} {$cfgfstype} ro,noatime 1 1\n"; + $fstab .= "proc /proc procfs rw 0 0\n"; + file_put_contents("{$g['etc_path']}/fstab", $fstab); + } + echo "."; + /* mount all filesystems */ + mwexec("/sbin/mount -a"); + } + echo "."; +} + +$config = parse_config(); + +/* set timezone */ +$timezone = $config['system']['timezone']; +if (!$timezone) { + $timezone = "Etc/UTC"; +} +date_default_timezone_set("$timezone"); + +if ($config_parsed == true) { + /* process packager manager custom rules */ + if (is_dir("/usr/local/pkg/parse_config")) { + run_plugins("/usr/local/pkg/parse_config/"); + } +} + +?> diff --git a/src/etc/inc/config.lib.inc b/src/etc/inc/config.lib.inc new file mode 100644 index 0000000..222d9d8 --- /dev/null +++ b/src/etc/inc/config.lib.inc @@ -0,0 +1,1020 @@ +<?php +/****h* pfSense/config + * NAME + * config.lib.inc - Functions to manipulate config.xml + * DESCRIPTION + * This include contains various config.xml specific functions. + * HISTORY + * $Id$ + ****** + + config.lib.inc + Ported from config.inc by Erik Kristensen + Copyright (C) 2004-2010 Scott Ullrich + All rights reserved. + + originally part of m0n0wall (http://m0n0.ch/wall) + Copyright (C) 2003-2004 Manuel Kasper <mk@neon1.net>. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + pfSense_BUILDER_BINARIES: /sbin/mount /sbin/umount /sbin/halt + pfSense_MODULE: config +*/ + +/****f* config/encrypted_configxml + * NAME + * encrypted_configxml - Checks to see if config.xml is encrypted and if so, prompts to unlock. + * INPUTS + * None + * RESULT + * $config - rewrites config.xml without encryption + ******/ +function encrypted_configxml() { + global $g, $config; + + if (!file_exists($g['conf_path'] . "/config.xml")) { + return; + } + + if (!platform_booting()) { + return; + } + + $configtxt = file_get_contents($g['conf_path'] . "/config.xml"); + if (tagfile_deformat($configtxt, $configtxt, "config.xml")) { + $fp = fopen('php://stdin', 'r'); + $data = ""; + echo "\n\n*** Encrypted config.xml detected ***\n"; + while ($data == "") { + echo "\nEnter the password to decrypt config.xml: "; + $decrypt_password = chop(fgets($fp)); + $data = decrypt_data($configtxt, $decrypt_password); + if (!strstr($data, "<pfsense>")) { + $data = ""; + } + if ($data) { + $fd = fopen($g['conf_path'] . "/config.xml.tmp", "w"); + fwrite($fd, $data); + fclose($fd); + exec("/bin/mv {$g['conf_path']}/config.xml.tmp {$g['conf_path']}/config.xml"); + echo "\n" . gettext("Config.xml unlocked.") . "\n"; + fclose($fp); + pfSense_fsync("{$g['conf_path']}/config.xml"); + } else { + echo "\n" . gettext("Invalid password entered. Please try again.") . "\n"; + } + } + } +} + +/****f* config/parse_config + * NAME + * parse_config - Read in config.cache or config.xml if needed and return $config array + * INPUTS + * $parse - boolean to force parse_config() to read config.xml and generate config.cache + * RESULT + * $config - array containing all configuration variables + ******/ +function parse_config($parse = false) { + global $g, $config_parsed, $config_extra; + + $lockkey = lock('config'); + $config_parsed = false; + + if (!file_exists("{$g['conf_path']}/config.xml") || filesize("{$g['conf_path']}/config.xml") == 0) { + $last_backup = discover_last_backup(); + if ($last_backup) { + log_error(gettext("No config.xml found, attempting last known config restore.")); + file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", ""); + restore_backup("{$g['conf_path']}/backup/{$last_backup}"); + } else { + unlock($lockkey); + die(gettext("Config.xml is corrupted and is 0 bytes. Could not restore a previous backup.")); + } + } + + if (platform_booting(true)) { + echo "."; + } + + // Check for encrypted config.xml + encrypted_configxml(); + + if (!$parse) { + if (file_exists($g['tmp_path'] . '/config.cache')) { + $config = unserialize(file_get_contents($g['tmp_path'] . '/config.cache')); + if (is_null($config)) { + $parse = true; + } + } else { + $parse = true; + } + } + if ($parse == true) { + if (!file_exists($g['conf_path'] . "/config.xml")) { + if (platform_booting(true)) { + echo "."; + } + log_error("No config.xml found, attempting last known config restore."); + file_notice("config.xml", "No config.xml found, attempting last known config restore.", "pfSenseConfigurator", ""); + $last_backup = discover_last_backup(); + if ($last_backup) { + restore_backup("/cf/conf/backup/{$last_backup}"); + } else { + log_error(gettext("Could not restore config.xml.")); + unlock($lockkey); + die(gettext("Config.xml is corrupted and is 0 bytes. Could not restore a previous backup.")); + } + } + $config = parse_xml_config($g['conf_path'] . '/config.xml', array($g['xml_rootobj'], 'pfsense')); + if ($config == -1) { + $last_backup = discover_last_backup(); + if ($last_backup) { + restore_backup("/cf/conf/backup/{$last_backup}"); + } else { + log_error(gettext("Could not restore config.xml.")); + unlock($lockkey); + die("Config.xml is corrupted and is 0 bytes. Could not restore a previous backup."); + } + } + generate_config_cache($config); + } + + if (platform_booting(true)) { + echo "."; + } + + $config_parsed = true; + unlock($lockkey); + + alias_make_table($config); + + return $config; +} + +/****f* config/generate_config_cache + * NAME + * generate_config_cache - Write serialized configuration to cache. + * INPUTS + * $config - array containing current firewall configuration + * RESULT + * boolean - true on completion + ******/ +function generate_config_cache($config) { + global $g, $config_extra; + + $configcache = fopen($g['tmp_path'] . '/config.cache', "w"); + fwrite($configcache, serialize($config)); + fclose($configcache); + pfSense_fsync("{$g['tmp_path']}/config.cache"); + + unset($configcache); + /* Used for config.extra.xml */ + if (file_exists($g['tmp_path'] . '/config.extra.cache') && $config_extra) { + $configcacheextra = fopen($g['tmp_path'] . '/config.extra.cache', "w"); + fwrite($configcacheextra, serialize($config_extra)); + fclose($configcacheextra); + pfSense_fsync("{$g['tmp_path']}/config.extra.cache"); + unset($configcacheextra); + } +} + +function discover_last_backup() { + $backups = glob('/cf/conf/backup/*.xml'); + $last_backup = ""; + $last_mtime = 0; + foreach ($backups as $backup) { + if (filemtime($backup) > $last_mtime) { + $last_mtime = filemtime($backup); + $last_backup = $backup; + } + } + + return basename($last_backup); +} + +function restore_backup($file) { + global $g; + + if (file_exists($file)) { + conf_mount_rw(); + unlink_if_exists("{$g['tmp_path']}/config.cache"); + copy("$file", "/cf/conf/config.xml"); + pfSense_fsync("/cf/conf/config.xml"); + pfSense_fsync($g['conf_path']); + disable_security_checks(); + log_error(sprintf(gettext('%1$s is restoring the configuration %2$s'), $g['product_name'], $file)); + file_notice("config.xml", sprintf(gettext('%1$s is restoring the configuration %2$s'), $g['product_name'], $file), "pfSenseConfigurator", ""); + conf_mount_ro(); + } +} + +/****f* config/parse_config_bootup + * NAME + * parse_config_bootup - Bootup-specific configuration checks. + * RESULT + * null + ******/ +function parse_config_bootup() { + global $config, $g; + + if (platform_booting()) { + echo "."; + } + + $lockkey = lock('config'); + if (!file_exists("{$g['conf_path']}/config.xml")) { + if (platform_booting()) { + if (strstr($g['platform'], "cdrom")) { + /* try copying the default config. to the floppy */ + echo gettext("Resetting factory defaults...") . "\n"; + reset_factory_defaults(true); + if (!file_exists("{$g['conf_path']}/config.xml")) { + echo gettext("No XML configuration file found - using factory defaults.\n" . + "Make sure that the configuration floppy disk with the conf/config.xml\n" . + "file is inserted. If it isn't, your configuration changes will be lost\n" . + "on reboot.\n"); + } + } else { + $last_backup = discover_last_backup(); + if ($last_backup) { + log_error("No config.xml found, attempting last known config restore."); + file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", ""); + restore_backup("/cf/conf/backup/{$last_backup}"); + } + if (!file_exists("{$g['conf_path']}/config.xml")) { + echo sprintf(gettext("XML configuration file not found. %s cannot continue booting."), $g['product_name']) . "\n"; + unlock($lockkey); + mwexec("/sbin/halt"); + exit; + } + log_error("Last known config found and restored. Please double check your configuration file for accuracy."); + file_notice("config.xml", gettext("Last known config found and restored. Please double check your configuration file for accuracy."), "pfSenseConfigurator", ""); + } + } else { + unlock($lockkey); + log_error(gettext("Could not find a usable configuration file! Exiting....")); + exit(0); + } + } + + if (filesize("{$g['conf_path']}/config.xml") == 0) { + $last_backup = discover_last_backup(); + if ($last_backup) { + log_error(gettext("No config.xml found, attempting last known config restore.")); + file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", ""); + restore_backup("{$g['conf_path']}/backup/{$last_backup}"); + } else { + unlock($lockkey); + die(gettext("Config.xml is corrupted and is 0 bytes. Could not restore a previous backup.")); + } + } + unlock($lockkey); + + $config = parse_config(true); + + if ((float)$config['version'] > (float)$g['latest_config']) { + echo <<<EOD + + +******************************************************************************* +* WARNING! * +* The current configuration has been created with a newer version of {$g['product_name']} * +* than this one! This can lead to serious misbehavior and even security * +* holes! You are urged to either upgrade to a newer version of {$g['product_name']} or * +* revert to the default configuration immediately! * +******************************************************************************* + + +EOD; + } + + /* make alias table (for faster lookups) */ + alias_make_table($config); +} + +/****f* config/conf_mount_rw + * NAME + * conf_mount_rw - Mount filesystems read/write. + * RESULT + * null + ******/ +/* mount flash card read/write */ +function conf_mount_rw() { + global $g, $config; + + /* do not mount on cdrom platform */ + if ($g['platform'] == "cdrom" or $g['platform'] == "pfSense") { + return; + } + + if ((refcount_reference(1000) > 1) && is_writable("/")) { + return; + } + + $status = mwexec("/sbin/mount -u -w -o sync,noatime {$g['cf_path']}"); + if ($status <> 0) { + if (platform_booting()) { + echo gettext("/cf Filesystem is dirty.") . "\n"; + } + $status = mwexec("/sbin/mount -u -w -o sync,noatime {$g['cf_path']}"); + } + + /* if the platform is soekris or wrap or pfSense, lets mount the + * compact flash cards root. + */ + $status = mwexec("/sbin/mount -u -w -o sync,noatime /"); + /* we could not mount this correctly. */ + if ($status <> 0) { + log_error(gettext("/ File system is dirty.")); + $status = mwexec("/sbin/mount -u -w -o sync,noatime /"); + } + + mark_subsystem_dirty('mount'); +} + +/****f* config/conf_mount_ro + * NAME + * conf_mount_ro - Mount filesystems readonly. + * RESULT + * null + ******/ +function conf_mount_ro() { + global $g, $config; + + /* Do not trust $g['platform'] since this can be clobbered during factory reset. */ + $platform = trim(file_get_contents("/etc/platform")); + /* do not umount on cdrom or pfSense platforms */ + if ($platform == "cdrom" or $platform == "pfSense") { + return; + } + + if (refcount_unreference(1000) > 0) { + return; + } + + if (isset($config['system']['nanobsd_force_rw'])) { + return; + } + + if (platform_booting()) { + return; + } + + clear_subsystem_dirty('mount'); + /* sync data, then force a remount of /cf */ + pfSense_fsync($g['cf_path']); + mwexec("/sbin/mount -u -r -f -o sync,noatime {$g['cf_path']}"); + mwexec("/sbin/mount -u -r -f -o sync,noatime /"); +} + +/****f* config/convert_config + * NAME + * convert_config - Attempt to update config.xml. + * DESCRIPTION + * convert_config() reads the current global configuration + * and attempts to convert it to conform to the latest + * config.xml version. This allows major formatting changes + * to be made with a minimum of breakage. + * RESULT + * null + ******/ +/* convert configuration, if necessary */ +function convert_config() { + global $config, $g; + $now = date("H:i:s"); + log_error(sprintf(gettext("Start Configuration upgrade at %s, set execution timeout to 15 minutes"), $now)); + //ini_set("max_execution_time", "900"); + + /* special case upgrades */ + /* fix every minute crontab bogons entry */ + if (is_array($config['cron'])) { + $cron_item_count = count($config['cron']['item']); + for ($x = 0; $x < $cron_item_count; $x++) { + if (stristr($config['cron']['item'][$x]['command'], "rc.update_bogons.sh")) { + if ($config['cron']['item'][$x]['hour'] == "*") { + $config['cron']['item'][$x]['hour'] = "3"; + write_config(gettext("Updated bogon update frequency to 3am")); + log_error(gettext("Updated bogon update frequency to 3am")); + } + } + } + } + if ($config['version'] == $g['latest_config']) { + return; /* already at latest version */ + } + + // Save off config version + $prev_version = $config['version']; + + include_once('auth.inc'); + include_once('upgrade_config.inc'); + if (file_exists("/etc/inc/upgrade_config_custom.inc")) { + include_once("upgrade_config_custom.inc"); + } + /* Loop and run upgrade_VER_to_VER() until we're at current version */ + while ($config['version'] < $g['latest_config']) { + $cur = $config['version'] * 10; + $next = $cur + 1; + $migration_function = sprintf('upgrade_%03d_to_%03d', $cur, $next); + if (function_exists($migration_function)) { + $migration_function(); + } + $migration_function = "{$migration_function}_custom"; + if (function_exists($migration_function)) { + $migration_function(); + } + $config['version'] = sprintf('%.1f', $next / 10); + if (platform_booting()) { + echo "."; + } + } + + $now = date("H:i:s"); + log_error(sprintf(gettext("Ended Configuration upgrade at %s"), $now)); + + if ($prev_version != $config['version']) { + write_config(sprintf(gettext('Upgraded config version level from %1$s to %2$s'), $prev_version, $config['version'])); + } +} + +/****f* config/safe_write_file + * NAME + * safe_write_file - Write a file out atomically + * DESCRIPTION + * safe_write_file() Writes a file out atomically by first writing to a + * temporary file of the same name but ending with the pid of the current + * process, them renaming the temporary file over the original. + * INPUTS + * $filename - string containing the filename of the file to write + * $content - string containing the file content to write to file + * $force_binary - boolean denoting whether we should force binary + * mode writing. + * RESULT + * boolean - true if successful, false if not + ******/ +function safe_write_file($file, $content, $force_binary) { + $tmp_file = $file . "." . getmypid(); + $write_mode = $force_binary ? "wb" : "w"; + + $fd = fopen($tmp_file, $write_mode); + if (!$fd) { + // Unable to open temporary file for writing + return false; + } + if (!fwrite($fd, $content)) { + // Unable to write to temporary file + fclose($fd); + return false; + } + fflush($fd); + fclose($fd); + + if (!pfSense_fsync($tmp_file) || !rename($tmp_file, $file)) { + // Unable to move temporary file to original + @unlink($tmp_file); + return false; + } + + // Sync file before returning + return pfSense_fsync($file); +} + +/****f* config/write_config + * NAME + * write_config - Backup and write the firewall configuration. + * DESCRIPTION + * write_config() handles backing up the current configuration, + * applying changes, and regenerating the configuration cache. + * INPUTS + * $desc - string containing the a description of configuration changes + * $backup - boolean: do not back up current configuration if false. + * RESULT + * null + ******/ +/* save the system configuration */ +function write_config($desc="Unknown", $backup = true) { + global $config, $g; + + if (!empty($_SERVER['REMOTE_ADDR'])) { + if (!session_id()) { + @session_start(); + } + if (!empty($_SESSION['Username']) && ($_SESSION['Username'] != "admin")) { + $user = getUserEntry($_SESSION['Username']); + if (is_array($user) && userHasPrivilege($user, "user-config-readonly")) { + session_commit(); + return false; + } + } + } + + if (!isset($argc)) { + session_commit(); + } + + if ($backup) { + backup_config(); + } + + $config['revision'] = make_config_revision_entry($desc); + + conf_mount_rw(); + $lockkey = lock('config', LOCK_EX); + + /* generate configuration XML */ + $xmlconfig = dump_xml_config($config, $g['xml_rootobj']); + + /* write new configuration */ + if (!safe_write_file("{$g['cf_conf_path']}/config.xml", $xmlconfig, false)) { + log_error(gettext("WARNING: Config contents could not be saved. Could not open file!")); + unlock($lockkey); + file_notice("config.xml", sprintf(gettext("Unable to open %s/config.xml for writing in write_config()%s"), $g['cf_conf_path'], "\n")); + return -1; + } + + cleanup_backupcache(true); + + /* re-read configuration */ + /* NOTE: We assume that the file can be parsed since we wrote it. */ + $config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']); + if ($config == -1) { + copy("{$g['conf_path']}/config.xml", "{$g['conf_path']}/config.xml.bad"); + $last_backup = discover_last_backup(); + if ($last_backup) { + restore_backup("/cf/conf/backup/{$last_backup}"); + $config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']); + if (platform_booting()) { + echo "\n\n ************** WARNING **************"; + echo "\n\n Configuration could not be validated. A previous configuration was restored. \n"; + echo "\n The failed configuration file has been saved as {$g['conf_path']}/config.xml.bad \n\n"; + } + } else { + log_error(gettext("Could not restore config.xml.")); + } + } else { + generate_config_cache($config); + } + + unlock($lockkey); + + unlink_if_exists("/usr/local/pkg/pf/carp_sync_client.php"); + + /* tell kernel to sync fs data */ + conf_mount_ro(); + + /* sync carp entries to other firewalls */ + carp_sync_client(); + + if (is_dir("/usr/local/pkg/write_config")) { + /* process packager manager custom rules */ + run_plugins("/usr/local/pkg/write_config/"); + } + + return $config; +} + +/****f* config/reset_factory_defaults + * NAME + * reset_factory_defaults - Reset the system to its default configuration. + * RESULT + * integer - indicates completion + ******/ +function reset_factory_defaults($lock = false) { + global $g; + + conf_mount_rw(); + if (!$lock) { + $lockkey = lock('config', LOCK_EX); + } + + /* create conf directory, if necessary */ + safe_mkdir("{$g['cf_conf_path']}"); + + /* clear out /conf */ + $dh = opendir($g['conf_path']); + while ($filename = readdir($dh)) { + if (($filename != ".") && ($filename != "..")) { + unlink_if_exists($g['conf_path'] . "/" . $filename); + } + } + closedir($dh); + unlink_if_exists($g['tmp_path'] . "/config.cache"); + + /* copy default configuration */ + copy("{$g['conf_default_path']}/config.xml", "{$g['conf_path']}/config.xml"); + + disable_security_checks(); + + /* call the wizard */ + touch("/conf/trigger_initial_wizard"); + if (!$lock) { + unlock($lockkey); + } + conf_mount_ro(); + setup_serial_port(); + return 0; +} + +function config_restore($conffile) { + global $config, $g; + + if (!file_exists($conffile)) { + return 1; + } + + backup_config(); + + conf_mount_rw(); + + $lockkey = lock('config', LOCK_EX); + + unlink_if_exists("{$g['tmp_path']}/config.cache"); + copy($conffile, "{$g['cf_conf_path']}/config.xml"); + + disable_security_checks(); + + unlock($lockkey); + + $config = parse_config(true); + + conf_mount_ro(); + + write_config(gettext("Reverted to") . " " . array_pop(explode("/", $conffile)) . ".", false); + + return 0; +} + +function config_install($conffile) { + global $config, $g; + + if (!file_exists($conffile)) { + return 1; + } + + if (!config_validate("{$conffile}")) { + return 1; + } + + if (platform_booting()) { + echo gettext("Installing configuration...") . "\n"; + } else { + log_error(gettext("Installing configuration ....")); + } + + conf_mount_rw(); + $lockkey = lock('config', LOCK_EX); + + copy($conffile, "{$g['conf_path']}/config.xml"); + + disable_security_checks(); + + /* unlink cache file if it exists */ + if (file_exists("{$g['tmp_path']}/config.cache")) { + unlink("{$g['tmp_path']}/config.cache"); + } + + unlock($lockkey); + conf_mount_ro(); + + return 0; +} + +/* + * Disable security checks for DNS rebind and HTTP referrer until next time + * they pass (or reboot), to aid in preventing accidental lockout when + * restoring settings like hostname, domain, IP addresses, and settings + * related to the DNS rebind and HTTP referrer checks. + * Intended for use when restoring a configuration or directly + * modifying config.xml without an unconditional reboot. + */ +function disable_security_checks() { + global $g; + touch("{$g['tmp_path']}/disable_security_checks"); +} + +/* Restores security checks. Should be called after all succeed. */ +function restore_security_checks() { + global $g; + unlink_if_exists("{$g['tmp_path']}/disable_security_checks"); +} + +/* Returns status of security check temporary disable. */ +function security_checks_disabled() { + global $g; + return file_exists("{$g['tmp_path']}/disable_security_checks"); +} + +function config_validate($conffile) { + + global $g, $xmlerr; + + $xml_parser = xml_parser_create(); + + if (!($fp = fopen($conffile, "r"))) { + $xmlerr = gettext("XML error: unable to open file"); + return false; + } + + while ($data = fread($fp, 4096)) { + if (!xml_parse($xml_parser, $data, feof($fp))) { + $xmlerr = sprintf(gettext('%1$s at line %2$d'), + xml_error_string(xml_get_error_code($xml_parser)), + xml_get_current_line_number($xml_parser)); + return false; + } + } + xml_parser_free($xml_parser); + + fclose($fp); + + return true; +} + +function cleanup_backupcache($lock = false) { + global $g; + $i = false; + + $revisions = get_config_backup_count(); + + if (!$lock) { + $lockkey = lock('config'); + } + + conf_mount_rw(); + + $backups = get_backups(); + if ($backups) { + $baktimes = $backups['versions']; + unset($backups['versions']); + } else { + $backups = array(); + $baktimes = array(); + } + $newbaks = array(); + $bakfiles = glob($g['cf_conf_path'] . "/backup/config-*"); + $tocache = array(); + + foreach ($bakfiles as $backup) { // Check for backups in the directory not represented in the cache. + $backupsize = filesize($backup); + if ($backupsize == 0) { + unlink($backup); + continue; + } + $backupexp = explode('-', $backup); + $backupexp = explode('.', array_pop($backupexp)); + $tocheck = array_shift($backupexp); + unset($backupexp); + if (!in_array($tocheck, $baktimes)) { + $i = true; + if (platform_booting()) { + echo "."; + } + $newxml = parse_xml_config($backup, array($g['xml_rootobj'], 'pfsense')); + if ($newxml == "-1") { + log_error(sprintf(gettext("The backup cache file %s is corrupted. Unlinking."), $backup)); + unlink($backup); + log_error(sprintf(gettext("The backup cache file %s is corrupted. Unlinking."), $backup)); + continue; + } + if ($newxml['revision']['description'] == "") { + $newxml['revision']['description'] = "Unknown"; + } + if ($newxml['version'] == "") { + $newxml['version'] = "?"; + } + $tocache[$tocheck] = array('description' => $newxml['revision']['description'], 'version' => $newxml['version'], 'filesize' => $backupsize); + } + } + foreach ($backups as $checkbak) { + if (count(preg_grep('/' . $checkbak['time'] . '/i', $bakfiles)) != 0) { + $newbaks[] = $checkbak; + } else { + $i = true; + if (platform_booting()) print " " . $tocheck . "r"; + } + } + foreach ($newbaks as $todo) { + $tocache[$todo['time']] = array('description' => $todo['description'], 'version' => $todo['version'], 'filesize' => $todo['filesize']); + } + if (is_int($revisions) and (count($tocache) > $revisions)) { + $toslice = array_slice(array_keys($tocache), 0, $revisions); + foreach ($toslice as $sliced) { + $newcache[$sliced] = $tocache[$sliced]; + } + foreach ($tocache as $version => $versioninfo) { + if (!in_array($version, array_keys($newcache))) { + unlink_if_exists($g['conf_path'] . '/backup/config-' . $version . '.xml'); + } + } + $tocache = $newcache; + } + $bakout = fopen($g['cf_conf_path'] . '/backup/backup.cache', "w"); + fwrite($bakout, serialize($tocache)); + fclose($bakout); + pfSense_fsync("{$g['cf_conf_path']}/backup/backup.cache"); + conf_mount_ro(); + + if (!$lock) { + unlock($lockkey); + } +} + +function get_backups() { + global $g; + if (file_exists("{$g['cf_conf_path']}/backup/backup.cache")) { + $confvers = unserialize(file_get_contents("{$g['cf_conf_path']}/backup/backup.cache")); + $bakvers = array_keys($confvers); + $toreturn = array(); + sort($bakvers); + // $bakvers = array_reverse($bakvers); + foreach (array_reverse($bakvers) as $bakver) { + $toreturn[] = array('time' => $bakver, 'description' => $confvers[$bakver]['description'], 'version' => $confvers[$bakver]['version'], 'filesize' => $confvers[$bakver]['filesize']); + } + } else { + return false; + } + $toreturn['versions'] = $bakvers; + return $toreturn; +} + +function backup_config() { + global $config, $g; + + if ($g['platform'] == "cdrom") { + return; + } + + conf_mount_rw(); + + /* Create backup directory if needed */ + safe_mkdir("{$g['cf_conf_path']}/backup"); + if ($config['revision']['time'] == "") { + $baktime = 0; + } else { + $baktime = $config['revision']['time']; + } + + if ($config['revision']['description'] == "") { + $bakdesc = "Unknown"; + } else { + $bakdesc = $config['revision']['description']; + } + + $bakver = ($config['version'] == "") ? "?" : $config['version']; + $bakfilename = $g['cf_conf_path'] . '/backup/config-' . $baktime . '.xml'; + copy($g['cf_conf_path'] . '/config.xml', $bakfilename); + + if (file_exists($g['cf_conf_path'] . '/backup/backup.cache')) { + $backupcache = unserialize(file_get_contents($g['cf_conf_path'] . '/backup/backup.cache')); + } else { + $backupcache = array(); + } + $backupcache[$baktime] = array('description' => $bakdesc, 'version' => $bakver, 'filesize' => filesize($bakfilename)); + $bakout = fopen($g['cf_conf_path'] . '/backup/backup.cache', "w"); + fwrite($bakout, serialize($backupcache)); + fclose($bakout); + pfSense_fsync("{$g['cf_conf_path']}/backup/backup.cache"); + + conf_mount_ro(); + + return true; +} + +function set_device_perms() { + $devices = array( + 'pf' => array( + 'user' => 'root', + 'group' => 'proxy', + 'mode' => 0660), + ); + + foreach ($devices as $name => $attr) { + $path = "/dev/$name"; + if (file_exists($path)) { + chown($path, $attr['user']); + chgrp($path, $attr['group']); + chmod($path, $attr['mode']); + } + } +} + +function get_config_user() { + if (empty($_SESSION["Username"])) { + $username = getenv("USER"); + if (empty($conuser) || $conuser == "root") { + $username = "(system)"; + } + } else { + $username = $_SESSION["Username"]; + } + + if (!empty($_SERVER['REMOTE_ADDR'])) { + $username .= '@' . $_SERVER['REMOTE_ADDR']; + } + + return $username; +} + +function make_config_revision_entry($desc = null, $override_user = null) { + if (empty($override_user)) { + $username = get_config_user(); + } else { + $username = $override_user; + } + + $revision = array(); + + if (time() > mktime(0, 0, 0, 9, 1, 2004)) { /* make sure the clock settings are plausible */ + $revision['time'] = time(); + } + + /* Log the running script so it's not entirely unlogged what changed */ + if ($desc == "Unknown") { + $desc = sprintf(gettext("%s made unknown change"), $_SERVER['SCRIPT_NAME']); + } + if (!empty($desc)) { + $revision['description'] = "{$username}: " . $desc; + } + $revision['username'] = $username; + return $revision; +} + +function get_config_backup_count() { + global $config, $g; + if (isset($config['system']['backupcount']) && is_numeric($config['system']['backupcount']) && ($config['system']['backupcount'] >= 0)) { + return intval($config['system']['backupcount']); + } elseif ($g['platform'] == "nanobsd") { + return 5; + } else { + return 30; + } +} + +function pfSense_clear_globals() { + global $config, $FilterIfList, $GatewaysList, $filterdns, $aliases, $aliastable; + + $error = error_get_last(); + + if ($error !== NULL) { + if ($error['type'] == E_ERROR) { + $errorstr = "PHP ERROR: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}"; + print($errorstr); + log_error($errorstr); + } else if ($error['type'] != E_NOTICE) { + $errorstr = "PHP WARNING: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}"; + // XXX: comment out for now, should re-enable post-2.2 + //print($errorstr); + //log_error($errorstr); + } + } + + if (isset($FilterIfList)) { + unset($FilterIfList); + } + + if (isset($GatewaysList)) { + unset($GatewaysList); + } + + /* Used for the hostname dns resolver */ + if (isset($filterdns)) { + unset($filterdns); + } + + /* Used for aliases and interface macros */ + if (isset($aliases)) { + unset($aliases); + } + if (isset($aliastable)) { + unset($aliastable); + } + + unset($config); +} + +register_shutdown_function('pfSense_clear_globals'); + +?> diff --git a/src/etc/inc/cram_md5_sasl_client.inc b/src/etc/inc/cram_md5_sasl_client.inc new file mode 100644 index 0000000..69bd625 --- /dev/null +++ b/src/etc/inc/cram_md5_sasl_client.inc @@ -0,0 +1,67 @@ +<?php +/* + * cram_md5_sasl_client.php + * + * @(#) $Id: cram_md5_sasl_client.php,v 1.3 2004/11/17 08:00:37 mlemos Exp $ + * + */ + +define("SASL_CRAM_MD5_STATE_START", 0); +define("SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE", 1); +define("SASL_CRAM_MD5_STATE_DONE", 2); + +class cram_md5_sasl_client_class +{ + var $credentials=array(); + var $state=SASL_CRAM_MD5_STATE_START; + + Function Initialize(&$client) + { + return(1); + } + + Function HMACMD5($key,$text) + { + $key=(strlen($key)<64 ? str_pad($key,64,"\0") : substr($key,0,64)); + return(md5((str_repeat("\x5c", 64)^$key).pack("H32", md5((str_repeat("\x36", 64)^$key).$text)))); + } + + Function Start(&$client, &$message, &$interactions) + { + if($this->state!=SASL_CRAM_MD5_STATE_START) + { + $client->error="CRAM-MD5 authentication state is not at the start"; + return(SASL_FAIL); + } + $this->credentials=array( + "user"=>"", + "password"=>"" + ); + $defaults=array(); + $status=$client->GetCredentials($this->credentials,$defaults,$interactions); + if($status==SASL_CONTINUE) + $this->state=SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE; + Unset($message); + return($status); + } + + Function Step(&$client, $response, &$message, &$interactions) + { + switch($this->state) + { + case SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE: + $message=$this->credentials["user"]." ".$this->HMACMD5($this->credentials["password"], $response); + $this->state=SASL_CRAM_MD5_STATE_DONE; + break; + case SASL_CRAM_MD5_STATE_DONE: + $client->error="CRAM-MD5 authentication was finished without success"; + return(SASL_FAIL); + default: + $client->error="invalid CRAM-MD5 authentication step state"; + return(SASL_FAIL); + } + return(SASL_CONTINUE); + } +}; + +?>
\ No newline at end of file diff --git a/src/etc/inc/crypt.inc b/src/etc/inc/crypt.inc new file mode 100644 index 0000000..8d96b26 --- /dev/null +++ b/src/etc/inc/crypt.inc @@ -0,0 +1,101 @@ +<?php + +/* $Id$ */ +/* + Copyright (C) 2008 Shrew Soft Inc + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + pfSense_BUILDER_BINARIES: /usr/bin/openssl + pfSense_MODULE: crypto + +*/ + + function crypt_data($val, $pass, $opt) { + $file = tempnam("/tmp", "php-encrypt"); + file_put_contents("{$file}.dec", $val); + exec("/usr/bin/openssl enc {$opt} -aes-256-cbc -in {$file}.dec -out {$file}.enc -k " . escapeshellarg($pass)); + if (file_exists("{$file}.enc")) { + $result = file_get_contents("{$file}.enc"); + } else { + $result = ""; + log_error("Failed to encrypt/decrypt data!"); + } + @unlink($file); + @unlink("{$file}.dec"); + @unlink("{$file}.enc"); + return $result; + } + + function encrypt_data(& $data, $pass) { + return base64_encode(crypt_data($data, $pass, "-e")); + } + + function decrypt_data(& $data, $pass) { + return crypt_data(base64_decode($data), $pass, "-d"); + } + + function tagfile_reformat($in, & $out, $tag) { + + $out = "---- BEGIN {$tag} ----\n"; + + $size = 80; + $oset = 0; + while ($size >= 64) { + $line = substr($in, $oset, 64); + $out .= $line."\n"; + $size = strlen($line); + $oset += $size; + } + + $out .= "---- END {$tag} ----\n"; + + return true; + } + + function tagfile_deformat($in, & $out, $tag) { + + $btag_val = "---- BEGIN {$tag} ----"; + $etag_val = "---- END {$tag} ----"; + + $btag_len = strlen($btag_val); + $etag_len = strlen($etag_val); + + $btag_pos = stripos($in, $btag_val); + $etag_pos = stripos($in, $etag_val); + + if (($btag_pos === false) || ($etag_pos === false)) { + return false; + } + + $body_pos = $btag_pos + $btag_len; + $body_len = strlen($in); + $body_len -= $btag_len; + $body_len -= $etag_len + 1; + + $out = substr($in, $body_pos, $body_len); + + return true; + } + +?> diff --git a/src/etc/inc/digest_sasl_client.inc b/src/etc/inc/digest_sasl_client.inc new file mode 100644 index 0000000..924887d --- /dev/null +++ b/src/etc/inc/digest_sasl_client.inc @@ -0,0 +1,135 @@ +<?php +/* + * digest_sasl_client.php + * + * @(#) $Id: digest_sasl_client.php,v 1.1 2005/10/27 05:24:15 mlemos Exp $ + * + */ + +define('SASL_DIGEST_STATE_START', 0); +define('SASL_DIGEST_STATE_RESPOND_CHALLENGE', 1); +define('SASL_DIGEST_STATE_DONE', 2); + +class digest_sasl_client_class +{ + var $credentials=array(); + var $state=SASL_DIGEST_STATE_START; + + Function unq($string) + { + return(($string[0]=='"' && $string[strlen($string)-1]=='"') ? substr($string, 1, strlen($string)-2) : $string); + } + + Function H($data) + { + return md5($data); + } + + Function KD($secret, $data) + { + return $this->H($secret.':'.$data); + } + + Function Initialize(&$client) + { + return(1); + } + + Function Start(&$client, &$message, &$interactions) + { + if($this->state!=SASL_DIGEST_STATE_START) + { + $client->error='Digest authentication state is not at the start'; + return(SASL_FAIL); + } + $this->credentials=array( + 'user'=>'', + 'password'=>'', + 'uri'=>'', + 'method'=>'', + 'session'=>'' + ); + $defaults=array(); + $status=$client->GetCredentials($this->credentials,$defaults,$interactions); + if($status==SASL_CONTINUE) + $this->state=SASL_DIGEST_STATE_RESPOND_CHALLENGE; + Unset($message); + return($status); + } + + Function Step(&$client, $response, &$message, &$interactions) + { + switch($this->state) + { + case SASL_DIGEST_STATE_RESPOND_CHALLENGE: + $values=explode(',',$response); + $parameters=array(); + for($v=0; $v<count($values); $v++) + $parameters[strtok(trim($values[$v]), '=')]=strtok(''); + + $message='username="'.$this->credentials['user'].'"'; + if(!IsSet($parameters[$p='realm']) + && !IsSet($parameters[$p='nonce'])) + { + $client->error='Digest authentication parameter '.$p.' is missing from the server response'; + return(SASL_FAIL); + } + $message.=', realm='.$parameters['realm']; + $message.=', nonce='.$parameters['nonce']; + $message.=', uri="'.$this->credentials['uri'].'"'; + if(IsSet($parameters['algorithm'])) + { + $algorithm=$this->unq($parameters['algorithm']); + $message.=', algorithm='.$parameters['algorithm']; + } + else + $algorithm=''; + + $realm=$this->unq($parameters['realm']); + $nonce=$this->unq($parameters['nonce']); + if(IsSet($parameters['qop'])) + { + switch($qop=$this->unq($parameters['qop'])) + { + case "auth": + $cnonce=$this->credentials['session']; + break; + default: + $client->error='Digest authentication quality of protection '.$qop.' is not yet supported'; + return(SASL_FAIL); + } + } + $nc_value='00000001'; + if(IsSet($parameters['qop']) + && !strcmp($algorithm, 'MD5-sess')) + $A1=$this->H($this->credentials['user'].':'. $realm.':'. $this->credentials['password']).':'.$nonce.':'.$cnonce; + else + $A1=$this->credentials['user'].':'. $realm.':'. $this->credentials['password']; + $A2=$this->credentials['method'].':'.$this->credentials['uri']; + if(IsSet($parameters['qop'])) + $response=$this->KD($this->H($A1), $nonce.':'. $nc_value.':'. $cnonce.':'. $qop.':'. $this->H($A2)); + else + $response=$this->KD($this->H($A1), $nonce.':'. $this->H($A2)); + $message.=', response="'.$response.'"'; + if(IsSet($parameters['opaque'])) + $message.=', opaque='.$parameters['opaque']; + if(IsSet($parameters['qop'])) + $message.=', qop="'.$qop.'"'; + $message.=', nc='.$nc_value; + if(IsSet($parameters['qop'])) + $message.=', cnonce="'.$cnonce.'"'; + $client->encode_response=0; + $this->state=SASL_DIGEST_STATE_DONE; + break; + case SASL_DIGEST_STATE_DONE: + $client->error='Digest authentication was finished without success'; + return(SASL_FAIL); + default: + $client->error='invalid Digest authentication step state'; + return(SASL_FAIL); + } + return(SASL_CONTINUE); + } +}; + +?>
\ No newline at end of file diff --git a/src/etc/inc/dot.hushlogin b/src/etc/inc/dot.hushlogin new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/etc/inc/dot.hushlogin diff --git a/src/etc/inc/dyndns.class b/src/etc/inc/dyndns.class new file mode 100644 index 0000000..cb21fb5 --- /dev/null +++ b/src/etc/inc/dyndns.class @@ -0,0 +1,1634 @@ +<?php + /* + * PHP.updateDNS (pfSense version) + * + * +====================================================+ + * Services Supported: + * - DynDns (dyndns.org) [dynamic, static, custom] + * - DHSDns (dhs.org) + * - No-IP (no-ip.com) + * - EasyDNS (easydns.com) + * - DHS (www.dhs.org) + * - HN (hn.org) -- incomplete checking! + * - DynS (dyns.org) + * - ZoneEdit (zoneedit.com) + * - FreeDNS (freedns.afraid.org) + * - Loopia (loopia.se) + * - StaticCling (staticcling.org) + * - DNSexit (dnsexit.com) + * - OpenDNS (opendns.com) + * - Namecheap (namecheap.com) + * - HE.net (dns.he.net) + * - HE.net IPv6 (dns.he.net) + * - HE.net Tunnelbroker IP update (ipv4.tunnelbroker.net) + * - SelfHost (selfhost.de) + * - Amazon Route 53 (aws.amazon.com) + * - DNS-O-Matic (dnsomatic.com) + * - Custom DDNS (any URL) + * - Custom DDNS IPv6 (any URL) + * - CloudFlare (www.cloudflare.com) + * - Eurodns (eurodns.com) + * - GratisDNS (gratisdns.dk) + * - City Network (citynetwork.se) + * - GleSYS (glesys.com) + * - DNSimple (dnsimple.com) + * - Google Domains (domains.google.com) + * - DNS Made Easy (www.dnsmadeeasy.com) + * +----------------------------------------------------+ + * Requirements: + * - PHP version 4.0.2 or higher with the CURL Library and the PCRE Library + * +----------------------------------------------------+ + * Public Functions + * - updatedns() + * + * Private Functions + * - _update() + * - _checkStatus() + * - _error() + * - _detectChange() + * - _debug() + * - _checkIP() + * +----------------------------------------------------+ + * DynDNS Dynamic - Last Tested: 12 July 2005 + * DynDNS Static - Last Tested: NEVER + * DynDNS Custom - Last Tested: NEVER + * No-IP - Last Tested: 20 July 2008 + * HN.org - Last Tested: 12 July 2005 + * EasyDNS - Last Tested: 20 July 2008 + * DHS - Last Tested: 12 July 2005 + * ZoneEdit - Last Tested: NEVER + * Dyns - Last Tested: NEVER + * ODS - Last Tested: 02 August 2005 + * FreeDNS - Last Tested: 23 Feb 2011 + * Loopia - Last Tested: NEVER + * StaticCling - Last Tested: 27 April 2006 + * DNSexit - Last Tested: 20 July 2008 + * OpenDNS - Last Tested: 4 August 2008 + * Namecheap - Last Tested: 31 August 2010 + * HE.net - Last Tested: 7 July 2013 + * HE.net IPv6 - Last Tested: 7 July 2013 + * HE.net Tunnel - Last Tested: 28 June 2011 + * SelfHost - Last Tested: 26 December 2011 + * Amazon Route 53 - Last tested: 01 April 2012 + * DNS-O-Matic - Last Tested: 9 September 2010 + * CloudFlare - Last Tested: 30 May 2013 + * Eurodns - Last Tested: 27 June 2013 + * GratisDNS - Last Tested: 15 August 2012 + * OVH DynHOST - Last Tested: NEVER + * City Network - Last Tested: 13 November 2013 + * GleSYS - Last Tested: 3 February 2015 + * DNSimple - Last Tested: 09 February 2015 + * Google Domains - Last Tested: 27 April 2015 + * DNS Made Easy - Last Tested: 27 April 2015 + * +====================================================+ + * + * @author E.Kristensen + * @link http://www.idylldesigns.com/projects/phpdns/ + * @version 0.8 + * @updated 13 October 05 at 21:02:42 GMT + * + * DNSexit/OpenDNS support and multiwan extension for pfSense by Ermal Luçi + * Custom DNS support by Matt Corallo + * + */ + + class updatedns { + var $_cacheFile; + var $_cacheFile_v6; + var $_debugFile; + var $_UserAgent = 'User-Agent: phpDynDNS/0.7'; + var $_errorVerbosity = 0; + var $_dnsService; + var $_dnsUser; + var $_dnsPass; + var $_dnsHost; + var $_dnsIP; + var $_dnsWildcard; + var $_dnsMX; + var $_dnsBackMX; + var $_dnsServer; + var $_dnsPort; + var $_dnsUpdateURL; + var $_dnsZoneID; + var $_dnsTTL; + var $status; + var $_debugID; + var $_if; + var $_dnsResultMatch; + var $_dnsRequestIf; + var $_dnsRequestIfIP; + var $_dnsVerboseLog; + var $_curlIpresolveV4; + var $_curlSslVerifypeer; + var $_dnsMaxCacheAgeDays; + var $_dnsDummyUpdateDone; + var $_forceUpdateNeeded; + var $_useIPv6; + + /* + * Public Constructor Function (added 12 July 05) [beta] + * - Gets the dice rolling for the update. + * - $dnsResultMatch should only be used with $dnsService = 'custom' + * - $dnsResultMatch is parsed for '%IP%', which is the IP the provider was updated to, + * - it is otherwise expected to be exactly identical to what is returned by the Provider. + * - $dnsUser, and $dnsPass indicate HTTP Auth for custom DNS, if they are needed in the URL (GET Variables), include them in $dnsUpdateURL. + * - $For custom requests, $dnsUpdateURL is parsed for '%IP%', which is replaced with the new IP. + */ + function updatedns ($dnsService = '', $dnsHost = '', $dnsUser = '', $dnsPass = '', + $dnsWildcard = 'OFF', $dnsMX = '', $dnsIf = '', $dnsBackMX = '', + $dnsServer = '', $dnsPort = '', $dnsUpdateURL = '', $forceUpdate = false, + $dnsZoneID ='', $dnsTTL='', $dnsResultMatch = '', $dnsRequestIf = '', + $dnsID = '', $dnsVerboseLog = false, $curlIpresolveV4 = false, $curlSslVerifypeer = true) { + + global $config, $g; + + $this->_cacheFile = "{$g['conf_path']}/dyndns_{$dnsIf}{$dnsService}" . escapeshellarg($dnsHost) . "{$dnsID}.cache"; + $this->_cacheFile_v6 = "{$g['conf_path']}/dyndns_{$dnsIf}{$dnsService}" . escapeshellarg($dnsHost) . "{$dnsID}_v6.cache"; + $this->_debugFile = "{$g['varetc_path']}/dyndns_{$dnsIf}{$dnsService}" . escapeshellarg($dnsHost) . "{$dnsID}.debug"; + + $this->_curlIpresolveV4 = $curlIpresolveV4; + $this->_curlSslVerifypeer = $curlSslVerifypeer; + $this->_dnsVerboseLog = $dnsVerboseLog; + if ($this->_dnsVerboseLog) { + log_error("DynDns: updatedns() starting"); + } + + $dyndnslck = lock("DDNS".$dnsID, LOCK_EX); + + if (!$dnsService) $this->_error(2); + switch ($dnsService) { + case 'freedns': + if (!$dnsHost) $this->_error(5); + break; + case 'namecheap': + if (!$dnsPass) $this->_error(4); + if (!$dnsHost) $this->_error(5); + break; + case 'route53': + if (!$dnsZoneID) $this->_error(8); + if (!$dnsTTL) $this->_error(9); + break; + case 'custom': + if (!$dnsUpdateURL) $this->_error(7); + break; + default: + if (!$dnsUser) $this->_error(3); + if (!$dnsPass) $this->_error(4); + if (!$dnsHost) $this->_error(5); + } + + switch ($dnsService) { + case 'he-net-v6': + case 'custom-v6': + $this->_useIPv6 = true; + break; + default: + $this->_useIPv6 = false; + } + $this->_dnsService = strtolower($dnsService); + $this->_dnsUser = $dnsUser; + $this->_dnsPass = $dnsPass; + $this->_dnsHost = $dnsHost; + $this->_dnsServer = $dnsServer; + $this->_dnsPort = $dnsPort; + $this->_dnsWildcard = $dnsWildcard; + $this->_dnsMX = $dnsMX; + $this->_dnsZoneID = $dnsZoneID; + $this->_dnsTTL = $dnsTTL; + $this->_if = get_failover_interface($dnsIf); + $this->_checkIP(); + $this->_dnsUpdateURL = $dnsUpdateURL; + $this->_dnsResultMatch = $dnsResultMatch; + $this->_dnsRequestIf = get_failover_interface($dnsRequestIf); + if ($this->_dnsVerboseLog) { + log_error("DynDNS ({$this->_dnsHost}): running get_failover_interface for {$dnsRequestIf}. found {$this->_dnsRequestIf}"); + } + $this->_dnsRequestIfIP = get_interface_ip($dnsRequestIf); + $this->_dnsMaxCacheAgeDays = 25; + $this->_dnsDummyUpdateDone = false; + $this->_forceUpdateNeeded = $forceUpdate; + + // Ensure that we were able to lookup the IP + if (!is_ipaddr($this->_dnsIP)) { + log_error("DynDNS ({$this->_dnsHost}) There was an error trying to determine the public IP for interface - {$dnsIf}({$this->_if}). Probably interface is not a WAN interface."); + unlock($dyndnslck); + return; + } + + $this->_debugID = rand(1000000, 9999999); + + if ($forceUpdate == false && $this->_detectChange() == false) { + $this->_error(10); + } else { + switch ($this->_dnsService) { + case 'glesys': + case 'dnsomatic': + case 'dyndns': + case 'dyndns-static': + case 'dyndns-custom': + case 'dhs': + case 'noip': + case 'noip-free': + case 'easydns': + case 'hn': + case 'zoneedit': + case 'dyns': + case 'ods': + case 'freedns': + case 'loopia': + case 'staticcling': + case 'dnsexit': + case 'custom': + case 'custom-v6': + case 'opendns': + case 'namecheap': + case 'he-net': + case 'he-net-v6': + case 'selfhost': + case 'he-net-tunnelbroker': + case 'route53': + case 'cloudflare': + case 'eurodns': + case 'gratisdns': + case 'ovh-dynhost': + case 'citynetwork': + case 'dnsimple': + case 'googledomains': + case 'dnsmadeeasy': + $this->_update(); + if ($this->_dnsDummyUpdateDone == true) { + // If a dummy update was needed, then sleep a while and do the update again to put the proper address back. + // Some providers (e.g. No-IP free accounts) need to have at least 1 address change every month. + // If the address has not changed recently, or the user did "Force Update", then the code does + // a dummy address change for providers like this. + sleep(10); + $this->_update(); + } + break; + default: + $this->_error(6); + break; + } + } + + unlock($dyndnslck); + } + + /* + * Private Function (added 12 July 05) [beta] + * Send Update To Selected Service. + */ + function _update() { + + if ($this->_dnsVerboseLog) { + log_error("DynDNS ({$this->_dnsHost}): DynDns _update() starting."); + } + + if ($this->_dnsService != 'ods' and $this->_dnsService != 'route53 ') { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($ch, CURLOPT_INTERFACE, 'if!' . $this->_dnsRequestIf); + curl_setopt($ch, CURLOPT_TIMEOUT, 120); // Completely empirical + } + + switch ($this->_dnsService) { + case 'glesys': + $needsIP = TRUE; + if ($this->_dnsVerboseLog) { + log_error("DynDNS: ({$this->_dnsHost}) DNS update() starting."); + } + $server = 'https://api.glesys.com/domain/updaterecord/format/json'; + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + $post_data['recordid'] = $this->_dnsHost; + $post_data['data'] = $this->_dnsIP; + curl_setopt($ch, CURLOPT_URL, $server); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); + break; + case 'dyndns': + case 'dyndns-static': + case 'dyndns-custom': + $needsIP = FALSE; + if ($this->_dnsVerboseLog) { + log_error("DynDNS: ({$this->_dnsHost}) DNS update() starting."); + } + if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") { + $this->_dnsWildcard = "ON"; + } + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + $server = "https://members.dyndns.org/nic/update"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server .$port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard='.$this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO'); + break; + case 'dhs': + $needsIP = TRUE; + $post_data['hostscmd'] = 'edit'; + $post_data['hostscmdstage'] = '2'; + $post_data['type'] = '4'; + $post_data['updatetype'] = 'Online'; + $post_data['mx'] = $this->_dnsMX; + $post_data['mx2'] = ''; + $post_data['txt'] = ''; + $post_data['offline_url'] = ''; + $post_data['cloak'] = 'Y'; + $post_data['cloak_title'] = ''; + $post_data['ip'] = $this->_dnsIP; + $post_data['domain'] = 'dyn.dhs.org'; + $post_data['hostname'] = $this->_dnsHost; + $post_data['submit'] = 'Update'; + $server = "https://members.dhs.org/nic/hosts"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, '{$server}{$port}'); + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); + break; + case 'noip': + case 'noip-free': + $needsIP = TRUE; + $server = "https://dynupdate.no-ip.com/ducupdate.php"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + if (($this->_dnsService == "noip-free") && + ($this->_forceUpdateNeeded == true) && + ($this->_dnsDummyUpdateDone == false)) { + // Update the IP to a dummy value to force No-IP free accounts to see a change. + $iptoset = "192.168.1.1"; + $this->_dnsDummyUpdateDone = true; + log_error("DynDNS ({$this->_dnsHost}): Processing dummy update on No-IP free account. IP temporarily set to " . $iptoset); + } else { + $iptoset = $this->_dnsIP; + } + curl_setopt($ch, CURLOPT_URL, $server . $port . '?username=' . urlencode($this->_dnsUser) . '&pass=' . urlencode($this->_dnsPass) . '&hostname=' . $this->_dnsHost.'&ip=' . $iptoset); + break; + case 'easydns': + $needsIP = TRUE; + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + $server = "https://members.easydns.com/dyn/dyndns.php"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=' . $this->_dnsBackMX); + break; + case 'hn': + $needsIP = TRUE; + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + $server = "http://dup.hn.org/vanity/update"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server . $port . '?ver=1&IP=' . $this->_dnsIP); + break; + case 'zoneedit': + $needsIP = FALSE; + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + + $server = "https://dynamic.zoneedit.com/auth/dynamic.html"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, "{$server}{$port}?host=" .$this->_dnsHost); + break; + case 'dyns': + $needsIP = FALSE; + $server = "https://www.dyns.cx/postscript011.php"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server . $port . '?username=' . urlencode($this->_dnsUser) . '&password=' . $this->_dnsPass . '&host=' . $this->_dnsHost); + break; + case 'ods': + $needsIP = FALSE; + $misc_errno = 0; + $misc_error = ""; + $server = "ods.org"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + $this->con['socket'] = fsockopen("{$server}{$port}", "7070", $misc_errno, $misc_error, 30); + /* Check that we have connected */ + if (!$this->con['socket']) { + print "error! could not connect."; + break; + } + /* Here is the loop. Read the incoming data (from the socket connection) */ + while (!feof($this->con['socket'])) { + $this->con['buffer']['all'] = trim(fgets($this->con['socket'], 4096)); + $code = substr($this->con['buffer']['all'], 0, 3); + sleep(1); + switch ($code) { + case 100: + fputs($this->con['socket'], "LOGIN ".$this->_dnsUser." ".$this->_dnsPass."\n"); + break; + case 225: + fputs($this->con['socket'], "DELRR ".$this->_dnsHost." A\n"); + break; + case 901: + fputs($this->con['socket'], "ADDRR ".$this->_dnsHost." A ".$this->_dnsIP."\n"); + break; + case 795: + fputs($this->con['socket'], "QUIT\n"); + break; + } + } + $this->_checkStatus(0, $code); + break; + case 'freedns': + $needIP = FALSE; + curl_setopt($ch, CURLOPT_URL, 'https://freedns.afraid.org/dynamic/update.php?' . $this->_dnsPass); + break; + case 'dnsexit': + $needsIP = TRUE; + curl_setopt($ch, CURLOPT_URL, 'https://www.dnsexit.com/RemoteUpdate.sv?login='.$this->_dnsUser. '&password='.$this->_dnsPass.'&host='.$this->_dnsHost.'&myip='.$this->_dnsIP); + break; + case 'loopia': + $needsIP = TRUE; + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + curl_setopt($ch, CURLOPT_URL, 'https://dns.loopia.se/XDynDNSServer/XDynDNS.php?hostname='.$this->_dnsHost.'&myip='.$this->_dnsIP); + break; + case 'opendns': + $needsIP = FALSE; + if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") $this->_dnsWildcard = "ON"; + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + $server = "https://updates.opendns.com/nic/update?hostname=". $this->_dnsHost; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server .$port); + break; + + case 'staticcling': + $needsIP = FALSE; + curl_setopt($ch, CURLOPT_URL, 'https://www.staticcling.org/update.html?login='.$this->_dnsUser.'&pass='.$this->_dnsPass); + break; + case 'dnsomatic': + /* Example syntax + https://username:password@updates.dnsomatic.com/nic/update?hostname=yourhostname&myip=ipaddress&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG + */ + $needsIP = FALSE; + if ($this->_dnsVerboseLog) { + log_error("DNS-O-Matic: DNS update() starting."); + } + if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") { + $this->_dnsWildcard = "ON"; + } + /* + Reference: https://www.dnsomatic.com/wiki/api + DNS-O-Matic usernames are 3-25 characters. + DNS-O-Matic passwords are 6-20 characters. + All ASCII letters and numbers accepted. + Dots, dashes, and underscores allowed, but not at the beginning or end of the string. + Required: "rawurlencode" http://www.php.net/manual/en/function.rawurlencode.php + Encodes the given string according to RFC 3986. + */ + $server = "https://" . rawurlencode($this->_dnsUser) . ":" . rawurlencode($this->_dnsPass) . "@updates.dnsomatic.com/nic/update?hostname="; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard='.$this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NOCHG'); + break; + case 'namecheap': + /* Example: + https://dynamicdns.park-your-domain.com/update?host=[host_name]&domain=[domain.com]&password=[domain_password]&ip=[your_ip] + */ + $needsIP = FALSE; + if ($this->_dnsVerboseLog) { + log_error("Namecheap ({$this->_dnsHost}): DNS update() starting."); + } + $dparts = explode(".", trim($this->_dnsHost)); + $domain_part_count = ($dparts[count($dparts)-1] == "uk") ? 3 : 2; + $domain_offset = count($dparts) - $domain_part_count; + $hostname = implode(".", array_slice($dparts, 0, $domain_offset)); + $domain = implode(".", array_slice($dparts, $domain_offset)); + $dnspass = trim($this->_dnsPass); + $server = "https://dynamicdns.park-your-domain.com/update?host={$hostname}&domain={$domain}&password={$dnspass}&ip={$this->_dnsIP}"; + curl_setopt($ch, CURLOPT_URL, $server); + break; + case 'he-net': + case 'he-net-v6': + $needsIP = FALSE; + if ($this->_dnsVerboseLog) { + log_error("HE.net ({$this->_dnsHost}): DNS update() starting."); + } + $server = "https://dyn.dns.he.net/nic/update?"; + curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_URL, $server . 'hostname=' . $this->_dnsHost . '&password=' . $this->_dnsPass . '&myip=' . $this->_dnsIP); + break; + case 'he-net-tunnelbroker': + $needsIP = FALSE; + if ($this->_dnsVerboseLog) { + log_error("HE.net Tunnelbroker: DNS update() starting."); + } + $server = "https://ipv4.tunnelbroker.net/ipv4_end.php?"; + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass); + curl_setopt($ch, CURLOPT_URL, $server . 'tid=' . $this->_dnsHost); + break; + case 'selfhost': + $needsIP = FALSE; + if ($this->_dnsVerboseLog) { + log_error("SelfHost: DNS update() starting."); + } + if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") { + $this->_dnsWildcard = "ON"; + } + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + $server = "https://carol.selfhost.de/nic/update"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server .$port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard='.$this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO'); + break; + case 'route53': + if ($this->_dnsVerboseLog) { + log_error("Route53 ({$this->_dnsHost}): DNS update() starting."); + } + + /* Setting Variables */ + $hostname = "{$this->_dnsHost}."; + $ZoneID = $this->_dnsZoneID; + $AccessKeyId = $this->_dnsUser; + $SecretAccessKey = $this->_dnsPass; + $NewIP = $this->_dnsIP; + $NewTTL = $this->_dnsTTL; + + /* Include Route 53 Library Class */ + require_once('/etc/inc/r53.class'); + + /* Set Amazon AWS Credentials for this record */ + $r53 = new Route53($AccessKeyId, $SecretAccessKey); + + /* Function to find old values of records in Route 53 */ + if (!function_exists('Searchrecords')) { + function SearchRecords($records, $name) { + $result = array(); + foreach ($records as $record) { + if (strtolower($record['Name']) == strtolower($name)) { + $result [] = $record; + } + } + return ($result) ? $result : false; + } + } + + $records = $r53->listResourceRecordSets("/hostedzone/$ZoneID"); + + /* Get IP for your hostname in Route 53 */ + if (false !== ($a_result = SearchRecords($records['ResourceRecordSets'], "$hostname"))) { + $OldTTL = $a_result[0][TTL]; + $OldIP = $a_result[0][ResourceRecords][0]; + } else { + $OldIP = ""; + } + + /* Check if we need to update DNS Record */ + if ($OldIP !== $NewIP) { + if (!empty($OldIP)) { + /* Your Hostname already exists, deleting and creating it again */ + $changes = array(); + $changes[] = $r53->prepareChange(DELETE, $hostname, A, $OldTTL, $OldIP); + $changes[] = $r53->prepareChange(CREATE, $hostname, A, $NewTTL, $NewIP); + $result = $r53->changeResourceRecordSets("/hostedzone/$ZoneID", $changes); + } else { + /* Your Hostname does not exist yet, creating it */ + $changes = $r53->prepareChange(CREATE, $hostname, A, $NewTTL, $NewIP); + $result = $r53->changeResourceRecordSets("/hostedzone/$ZoneID", $changes); + } + } + $this->_checkStatus(0, $result); + break; + case 'custom': + case 'custom-v6': + if ($this->_dnsVerboseLog) { + log_error("Custom DDNS ({$this->_dnsHost}): DNS update() starting."); + } + if (strstr($this->dnsUpdateURL, "%IP%")) {$needsIP = TRUE;} else {$needsIP = FALSE;} + if ($this->_dnsUser != '') { + if ($this->_curlIpresolveV4) { + curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + } + if ($this->_curlSslVerifypeer) { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE); + } else { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + } + curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + curl_setopt($ch, CURLOPT_USERPWD, "{$this->_dnsUser}:{$this->_dnsPass}"); + } + $server = str_replace("%IP%", $this->_dnsIP, $this->_dnsUpdateURL); + if ($this->_dnsVerboseLog) { + log_error("Sending request to: ".$server); + } + curl_setopt($ch, CURLOPT_URL, $server); + break; + case 'cloudflare': + $needsIP = TRUE; + $dnsServer ='www.cloudflare.com'; + $dnsHost = str_replace(' ', '', $this->_dnsHost); + $URL = "https://{$dnsServer}/api.html?a=DIUP&email={$this->_dnsUser}&tkn={$this->_dnsPass}&ip={$this->_dnsIP}&hosts={$dnsHost}"; + curl_setopt($ch, CURLOPT_URL, $URL); + break; + case 'eurodns': + $needsIP = TRUE; + if ($this->_dnsVerboseLog) { + log_error("EuroDynDns ({$this->_dnsHost}) DNS update() starting."); + } + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + $server = "https://update.eurodyndns.org/update/"; + $port = ""; + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server .$port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP); + break; + case 'gratisdns': + $needsIP = TRUE; + if ($this->_dnsVerboseLog) { + log_error("GratisDNS.dk ({$this->_dnsHost}): DNS update() starting."); + } + $server = "https://ssl.gratisdns.dk/ddns.phtml"; + list($hostname, $domain) = explode(".", $this->_dnsHost, 2); + curl_setopt($ch, CURLOPT_URL, $server . '?u=' . $this->_dnsUser . '&p=' . $this->_dnsPass . '&h=' . $this->_dnsHost . '&d=' . $domain . '&i=' . $this->_dnsIP); + break; + case 'ovh-dynhost': + $needsIP = FALSE; + if ($this->_dnsVerboseLog) { + log_error("OVH DynHOST: ({$this->_dnsHost}) DNS update() starting."); + } + if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") $this->_dnsWildcard = "ON"; + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + $server = "https://www.ovh.com/nic/update"; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server .$port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard='.$this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO'); + break; + case 'citynetwork': + $needsIP = TRUE; + if ($this->_dnsVerboseLog) { + log_error("City Network: ({$this->_dnsHost}) DNS update() starting."); + } + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + $server = 'https://dyndns.citynetwork.se/nic/update'; + $port = ""; + if ($this->_dnsServer) { + $server = $this->_dnsServer; + } + if ($this->_dnsPort) { + $port = ":" . $this->_dnsPort; + } + curl_setopt($ch, CURLOPT_URL, $server .$port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP); + break; + case 'dnsimple': + /* Uses DNSimple's REST API + Requires username and Account API token passed in header + Piggybacks on Route 53's ZoneID field for DNSimple record ID + Data sent as JSON */ + $needsIP = TRUE; + $server = 'https://api.dnsimple.com/v1/domains/'; + $token = $this->_dnsUser . ':' . $this->_dnsPass; + $jsondata = '{"record":{"content":"' . $this->_dnsIP . '","ttl":"' . $this->_dnsTTL . '"}}'; + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json', 'Content-Type: application/json', 'X-DNSimple-Token: ' . $token)); + curl_setopt($ch, CURLOPT_URL, $server . $this->_dnsHost . '/records/' . $this->_dnsZoneID); + curl_setopt($ch, CURLOPT_POSTFIELDS, $jsondata); + break; + case 'googledomains': + $needsIP = FALSE; + if ($this->_dnsVerboseLog) { + log_error("Google Domains: ({$this->_dnsHost}) DNS update() starting."); + } + $post_data['username:password'] = $this->_dnsUser . ':' . $this->_dnsPass; + $post_data['hostname'] = $this->_dnsHost; + $post_data['myip'] = $this->_dnsIP; + $post_data['offline'] = 'no'; + $server = "https://domains.google.com/nic/update"; + $port = ""; + curl_setopt($ch, CURLOPT_URL, 'https://domains.google.com/nic/update'); + curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); + break; + case 'dnsmadeeasy': + $needsIP = TRUE; + if ($this->_dnsVerboseLog) { + log_error("DNS Made Easy ({$this->_dnsHost}): DNS update() starting."); + } + $server = "https://cp.dnsmadeeasy.com/servlet/updateip"; + curl_setopt($ch, CURLOPT_URL, $server . '?username=' . $this->_dnsUser . '&password=' . $this->_dnsPass . '&id=' . $this->_dnsHost . '&ip=' . $this->_dnsIP); + break; + default: + break; + } + if ($this->_dnsService != 'ods' and $this->_dnsService != 'route53') { + $data = curl_exec($ch); + $this->_checkStatus($ch, $data); + @curl_close($ch); + } + } + + /* + * Private Function (added 12 July 2005) [beta] + * Retrieve Update Status + */ + function _checkStatus($ch, $data) { + if ($this->_dnsVerboseLog) { + log_error("DynDNS ({$this->_dnsHost}): DynDns _checkStatus() starting."); + log_error("DynDNS ({$this->_dnsHost}): Current Service: {$this->_dnsService}"); + } + $successful_update = false; + if ($this->_dnsService != 'ods' and $this->_dnsService != 'route53' && @curl_error($ch)) { + $status = "Curl error occurred: " . curl_error($ch); + log_error($status); + $this->status = $status; + return; + } + switch ($this->_dnsService) { + case 'glesys': + if (preg_match('/Record updated/i', $data)) { + $status = "GleSYS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")"; + $successful_update = true; + } else { + $status = "GleSYS ({$this->_dnsHost}): (Unknown Response)"; + log_error("GleSYS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'dnsomatic': + if (preg_match('/badauth/i', $data)) { + $status = "DNS-O-Matic ({$this->_dnsHost}): The DNS-O-Matic username or password specified are incorrect. No updates will be distributed to services until this is resolved."; + } else if (preg_match('/notfqdn /i', $data)) { + $status = "DNS-O-Matic ({$this->_dnsHost}): The hostname specified is not a fully-qualified domain name. If no hostnames included, notfqdn will be returned once."; + } else if (preg_match('/nohost/i', $data)) { + $status = "DNS-O-Matic ({$this->_dnsHost}): The hostname passed could not be matched to any services configured. The service field will be blank in the return code."; + } else if (preg_match('/numhost/i', $data)) { + $status = "DNS-O-Matic ({$this->_dnsHost}): You may update up to 20 hosts. numhost is returned if you try to update more than 20 or update a round-robin."; + } else if (preg_match('/abuse/i', $data)) { + $status = "DNS-O-Matic ({$this->_dnsHost}): The hostname is blocked for update abuse."; + } else if (preg_match('/good/i', $data)) { + $status = "DNS-O-Matic ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")"; + $successful_update = true; + } else if (preg_match('/dnserr/i', $data)) { + $status = "DNS-O-Matic ({$this->_dnsHost}): DNS error encountered. Stop updating for 30 minutes."; + } else { + $status = "DNS-O-Matic ({$this->_dnsHost}): (Unknown Response)"; + log_error("DNS-O-Matic ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'citynetwork': + if (preg_match('/notfqdn/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN!"; + } else if (preg_match('/nohost/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) No such host"; + } else if (preg_match('/nochg/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match('/good/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")"; + $successful_update = true; + } else if (preg_match('/badauth/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed"; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'ovh-dynhost': + case 'dyndns': + if (preg_match('/notfqdn/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN!"; + } else if (preg_match('/nochg/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match('/good/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")"; + $successful_update = true; + } else if (preg_match('/noauth/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed"; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'dyndns-static': + if (preg_match('/notfqdn/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN!"; + } else if (preg_match('/nochg/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match('/good/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!"; + $successful_update = true; + } else if (preg_match('/noauth/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed"; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'dyndns-custom': + if (preg_match('/notfqdn/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN!"; + } else if (preg_match('/nochg/i', $data)) { + $status = "phpDynDNS: (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match('/good/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!"; + $successful_update = true; + } else if (preg_match('/noauth/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed"; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'dhs': + break; + case 'noip': + case 'noip-free': + list($ip, $code) = explode(":", $data); + switch ($code) { + case 0: + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP address is current, no update performed."; + $successful_update = true; + break; + case 1: + $status = "phpDynDNS ({$this->_dnsHost}): (Success) DNS hostname update successful."; + $successful_update = true; + break; + case 2: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname supplied does not exist."; + break; + case 3: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid Username."; + break; + case 4: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid Password."; + break; + case 5: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) To many updates sent."; + break; + case 6: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Account disabled due to violation of No-IP terms of service."; + break; + case 7: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid IP. IP Address submitted is improperly formatted or is a private IP address or is on a blacklist."; + break; + case 8: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Disabled / Locked Hostname."; + break; + case 9: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Host updated is configured as a web redirect and no update was performed."; + break; + case 10: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Group supplied does not exist."; + break; + case 11: + $status = "phpDynDNS ({$this->_dnsHost}): (Success) DNS group update is successful."; + $successful_update = true; + break; + case 12: + $status = "phpDynDNS ({$this->_dnsHost}): (Success) DNS group is current, no update performed."; + $successful_update = true; + break; + case 13: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Update client support not available for supplied hostname or group."; + break; + case 14: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname supplied does not have offline settings configured."; + break; + case 99: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Client disabled. Client should exit and not perform any more updates without user intervention."; + break; + case 100: + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Client disabled. Client should exit and not perform any more updates without user intervention."; + break; + default: + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + $this->_debug("Unknown Response: ".$data); + break; + } + break; + case 'easydns': + if (preg_match('/NOACCESS/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Authentication Failed: Username and/or Password was Incorrect."; + } else if (preg_match('/NOSERVICE/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) No Service: Dynamic DNS Service has been disabled for this domain."; + } else if (preg_match('/ILLEGAL INPUT/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Illegal Input: Self-Explanatory"; + } else if (preg_match('/TOOSOON/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Too Soon: Not Enough Time Has Elapsed Since Last Update"; + } else if (preg_match('/NOERROR/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Updated Successfully!"; + $successful_update = true; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'hn': + /* FIXME: add checks */ + break; + case 'zoneedit': + if (preg_match('/799/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error 799) Update Failed!"; + } else if (preg_match('/700/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error 700) Update Failed!"; + } else if (preg_match('/200/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else if (preg_match('/201/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'dyns': + if (preg_match("/400/i", $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad Request - The URL was malformed. Required parameters were not provided."; + } else if (preg_match('/402/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Update Too Soon - You have tried updating to quickly since last change."; + } else if (preg_match('/403/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Database Error - There was a server-sided database error."; + } else if (preg_match('/405/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname Error - The hostname (".$this->_dnsHost.") doesn't belong to you."; + } else if (preg_match('/200/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'ods': + if (preg_match("/299/i", $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'freedns': + if (preg_match("/has not changed./i", $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match("/Updated/i", $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!"; + $successful_update = true; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'dnsexit': + if (preg_match("/is the same/i", $data)) { + $status = "phpDynDns ({$this->_dnsHost}): (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match("/Success/i", $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!"; + $successful_update = true; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'loopia': + if (preg_match("/nochg/i", $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match("/good/i", $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!"; + $successful_update = true; + } else if (preg_match('/badauth/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed"; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'opendns': + if (preg_match('/badauth/i', $data)) { + $status = "phpDynDNS({$this->_dnsHost}): (Error) Not a valid username or password!"; + } else if (preg_match('/nohost/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname you are trying to update does not exist."; + $successful_update = true; + } else if (preg_match('/good/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")"; + $successful_update = true; + } else if (preg_match('/yours/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) hostname specified exists, but not under the username specified."; + } else if (preg_match('/abuse/i', $data)) { + $status = "phpDynDns ({$this->_dnsHost}): (Error) Updating too frequently, considered abuse."; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'staticcling': + if (preg_match("/invalid ip/i", $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad Request - The IP provided was invalid."; + } else if (preg_match('/required info missing/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad Request - Required parameters were not provided."; + } else if (preg_match('/invalid characters/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad Request - Illegal characters in either the username or the password."; + } else if (preg_match('/bad password/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid password."; + } else if (preg_match('/account locked/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) This account has been administratively locked."; + } else if (preg_match('/update too frequent/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Updating too frequently."; + } else if (preg_match('/DB error/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Server side error."; + } else if (preg_match('/success/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'namecheap': + $tmp = str_replace("^M", "", $data); + $ncresponse = @xml2array($tmp); + if (preg_match("/internal server error/i", $data)) { + $status = "phpDynDNS: (Error) Server side error."; + } else if (preg_match("/request is badly formed/i", $data)) { + $status = "phpDynDNS: (Error) Badly Formed Request (check your settings)."; + } else if ($ncresponse['interface-response']['ErrCount'] === "0") { + $status = "phpDynDNS: (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else if (is_numeric($ncresponse['interface-response']['ErrCount']) && ($ncresponse['interface-response']['ErrCount'] > 0)) { + $status = "phpDynDNS: (Error) " . implode(", ", $ncresponse["interface-response"]["errors"]); + $successful_update = true; + } else { + $status = "phpDynDNS: (Unknown Response)"; + log_error("phpDynDNS: PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + + case 'he-net': + case 'he-net-v6': + if (preg_match("/badip/i", $data)) { + $status = "phpDynDNS: (Error) Bad Request - The IP provided was invalid."; + } else if (preg_match('/nohost/i', $data)) { + $status = "phpDynDNS: (Error) Bad Request - A hostname was not provided."; + } else if (preg_match('/badauth/i', $data)) { + $status = "phpDynDNS: (Error) Invalid username or password."; + } else if (preg_match('/good/i', $data)) { + $status = "phpDynDNS: (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else if (preg_match('/nochg/i', $data)) { + $status = "phpDynDNS: (Success) No Change In IP Address."; + $successful_update = true; + } else { + $status = "phpDynDNS: (Unknown Response)"; + log_error("phpDynDNS: PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'he-net-tunnelbroker': + /* + -ERROR: Missing parameter(s). + -ERROR: Invalid API key or password + -ERROR: Tunnel not found + -ERROR: Another tunnel exists for this IP. + -ERROR: This tunnel is already associated with this IP address + +OK: Tunnel endpoint updated to: x.x.x.x + */ + if (preg_match("/Missing parameter/i", $data)) { + $status = "phpDynDNS: (Error) Bad Request - Missing/Invalid Parameters."; + } else if (preg_match('/Tunnel not found/i', $data)) { + $status = "phpDynDNS: (Error) Bad Request - Invalid Tunnel ID."; + } else if (preg_match('/Invalid API key or password/i', $data)) { + $status = "phpDynDNS: (Error) Invalid username or password."; + } else if (preg_match('/OK:/i', $data)) { + $status = "phpDynDNS: (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else if (preg_match('/This tunnel is already associated with this IP address/i', $data)) { + $status = "phpDynDNS: (Success) No Change In IP Address."; + $successful_update = true; + } else { + $status = "phpDynDNS: (Unknown Response)"; + log_error("phpDynDNS: PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'selfhost': + if (preg_match('/notfqdn/i', $data)) { + $status = "phpDynDNS: (Error) Not A FQDN!"; + } else if (preg_match('/nochg/i', $data)) { + $status = "phpDynDNS: (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match('/good/i', $data)) { + $status = "phpDynDNS: (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")"; + $successful_update = true; + } else if (preg_match('/noauth/i', $data)) { + $status = "phpDynDNS: (Error) User Authorization Failed"; + } else { + $status = "phpDynDNS: (Unknown Response)"; + log_error("phpDynDNS: PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'route53': + $successful_update = true; + break; + case 'custom': + case 'custom-v6': + $successful_update = false; + if ($this->_dnsResultMatch == "") { + $successful_update = true; + } else { + $this->_dnsResultMatch = str_replace("%IP%", $this->_dnsIP, $this->_dnsResultMatch); + $matches = preg_split("/(?<!\\\\)\\|/", $this->_dnsResultMatch); + foreach ($matches as $match) { + $match= str_replace("\\|", "|", $match); + if (strcmp($match, trim($data, "\t\n\r")) == 0) { + $successful_update = true; + } + } + unset ($matches); + } + if ($successful_update == true) { + $status = "phpDynDNS: (Success) IP Address Updated Successfully!"; + } else { + $status = "phpDynDNS: (Error) Result did not match."; + } + break; + case 'cloudflare': + // receive multiple results + $data = explode("\n", $data); + $lines = count($data)-1; + + // loop over the lines + for ($pos = 0; ($successful_update || $pos == 0) && $pos < $lines; $pos++) { + $resp = $data[$pos]; + if (preg_match('/UAUTH/i', $resp)) { + $status = "DynDNS: The username specified is not authorized to update this hostname and domain."; + } else if (preg_match('/NOHOST/i', $resp)) { + $status = "DynDNS: No valid FQDN (fully qualified domain name) was specified"; + } else if (preg_match('/INVLDHST/i', $resp)) { + $status = "DynDNS: An invalid hostname was specified. This may be due to the fact the hostname has not been created in the system. Creating new host names via clients is not supported."; + } else if (preg_match('/INVLDIP/i', $resp)) { + $status = "DynDNS: The IP address given is not valid."; + } else if (preg_match('/DUPHST/i', $resp)) { + $status = "DynDNS: Duplicate values exist for a record. Only single values for records are supported currently."; + } else if (preg_match('/NOUPDATE/i', $resp)) { + $status = "DynDNS: No changes made to the hostname (" . strtok($resp, ' ') . "). Continual updates with no changes lead to blocked clients."; + $successful_update = true; //success if it is the same so that it saves + } else if (preg_match('/OK/i', $resp)) { + $status = "DynDNS: (Success) (" . strtok($resp, ' ') . ") IP Address for Changed Successfully!"; + $successful_update = true; + } else { + $status = "DynDNS: (Unknown Response)"; + log_error("DynDNS: PAYLOAD: {$resp}"); + $this->_debug($resp); + } + log_error($status); + } + break; + case 'eurodns': + if (preg_match('/notfqdn/i', $data)) { + $status = "phpDynDNS: (Error) Not A FQDN!"; + } else if (preg_match('/nochg/i', $data)) { + $status = "phpDynDNS: (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match('/good/i', $data)) { + $status = "phpDynDNS: (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")"; + $successful_update = true; + } else if (preg_match('/badauth/i', $data)) { + $status = "phpDynDNS: (Error) User Authorization Failed"; + } else { + $status = "phpDynDNS: (Unknown Response)"; + log_error("phpDynDNS: PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'gratisdns': + if (preg_match('/Forkerte værdier/i', $data)) { + $status = "phpDynDNS: (Error) Wrong values - Update could not be completed."; + } else if (preg_match('/Bruger login: Bruger eksistere ikke/i', $data)) { + $status = "phpDynDNS: (Error) Unknown username - User does not exist."; + } else if (preg_match('/Bruger login: 1Fejl i kodeord/i', $data)) { + $status = "phpDynDNS: (Error) Wrong password - Remember password is case sensitive."; + } else if (preg_match('/Domæne kan IKKE administreres af bruger/i', $data)) { + $status = "phpDynDNS: (Error) User unable to administer the selected domain."; + } else if (preg_match('/OK/i', $data)) { + $status = "phpDynDNS: (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else { + $status = "phpDynDNS: (Unknown Response)"; + log_error("phpDynDNS: PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'dnsimple': + /* Responds with HTTP 200 on success. + Responds with HTTP 4xx on error. + Returns JSON data as body */ + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($data, 0, $header_size); + $body = substr($data, $header_size); + if (preg_match("/Status: 200\s/i", $header)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!"; + $successful_update = true; + } else if (preg_match("/Status: 4\d\d\s/i", $header)) { + $arrbody = json_decode($body, true); + $message = $arrbody['message'] . "."; + if (isset($arrbody['errors']['content'])) { + foreach ($arrbody['errors']['content'] as $key => $content) { + $message .= " " . $content . "."; + } + } + $status = "phpDynDNS ({$this->_dnsHost}): (Error) " . $message; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$body}"); + $this->_debug($body); + } + break; + case 'googledomains': + if (preg_match('/notfqdn/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN"; + } else if (preg_match('/nochg/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address"; + $successful_update = true; + } else if (preg_match('/good/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")"; + $successful_update = true; + } else if (preg_match('/badauth/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed"; + } else if (preg_match('/nohost/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname does not exist or DynDNS not enabled"; + } else if (preg_match('/badagent/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad request"; + } else if (preg_match('/abuse/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Dynamic DNS access has been blocked!"; + } else if (preg_match('/911/i', $data)) { + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Error on Google's end, retry in 5 minutes"; + } else { + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + } + break; + case 'dnsmadeeasy': + switch ($data) { + case 'success': + $status = "phpDynDNS({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")"; + $successful_update = true; + break; + case 'error-auth': + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid username or password"; + break; + case 'error-auth-suspend': + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Account suspended"; + break; + case 'error-auth-voided': + $status = "phpDynDNS ({$this->_dnsHost}): (Error) Account revoked"; + break; + case 'error-record-invalid': + $status = "phpDynDns ({$this->_dnsHost}): (Error) Record does not exist in the system. Unable to update record"; + break; + case 'error-record-auth': + $status = "phpDynDns ({$this->_dnsHost}): (Error) User does not have access to this record"; + break; + case 'error-record-ip-same': + $status = "phpDynDns ({$this->_dnsHost}): (Success) No change in IP Address"; + $successful_update = true; + break; + case 'error-system': + $status = "phpDynDns ({$this->_dnsHost}): (Error) General system error recognized by the system"; + break; + case 'error': + $status = "phpDynDns ({$this->_dnsHost}): (Error) General system error unrecognized by the system"; + break; + default: + $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)"; + log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug($data); + break; + } + break; + } + + if ($successful_update == true) { + /* Write WAN IP to cache file */ + $wan_ip = $this->_checkIP(); + conf_mount_rw(); + if ($this->_useIPv6 == false && $wan_ip > 0) { + $currentTime = time(); + notify_all_remote(sprintf(gettext("DynDNS updated IP Address on %s (%s) to %s"), convert_real_interface_to_friendly_descr($this->_if), $this->_if, $wan_ip)); + log_error("phpDynDNS: updating cache file {$this->_cacheFile}: {$wan_ip}"); + @file_put_contents($this->_cacheFile, "{$wan_ip}:{$currentTime}"); + } else { + @unlink($this->_cacheFile); + } + if ($this->_useIPv6 == true && $wan_ip > 0) { + $currentTime = time(); + notify_all_remote(sprintf(gettext("DynDNS updated IPv6 Address on %s (%s) to %s"), convert_real_interface_to_friendly_descr($this->_if), $this->_if, $wan_ip)); + log_error("phpDynDNS: updating cache file {$this->_cacheFile_v6}: {$wan_ip}"); + @file_put_contents($this->_cacheFile_v6, "{$wan_ip}|{$currentTime}"); + } else { + @unlink($this->_cacheFile_v6); + } + conf_mount_ro(); + } + $this->status = $status; + log_error($status); + } + + /* + * Private Function (added 12 July 05) [beta] + * Return Error, Set Last Error, and Die. + */ + function _error($errorNumber = '1') { + switch ($errorNumber) { + case 0: + break; + case 2: + $error = 'phpDynDNS: (ERROR!) No Dynamic DNS Service provider was selected.'; + break; + case 3: + $error = 'phpDynDNS: (ERROR!) No Username Provided.'; + break; + case 4: + $error = 'phpDynDNS: (ERROR!) No Password Provided.'; + break; + case 5: + $error = 'phpDynDNS: (ERROR!) No Hostname Provided.'; + break; + case 6: + $error = 'phpDynDNS: (ERROR!) The Dynamic DNS Service provided is not yet supported.'; + break; + case 7: + $error = 'phpDynDNS: (ERROR!) No Update URL Provided.'; + break; + case 8: + $status = "Route 53: (Error) Invalid ZoneID"; + break; + case 9: + $status = "Route 53: (Error) Invalid TTL"; + break; + case 10: + $error = "phpDynDNS ({$this->_dnsHost}): No change in my IP address and/or " . $this->_dnsMaxCacheAgeDays . " days has not passed. Not updating dynamic DNS entry."; + break; + default: + $error = "phpDynDNS: (ERROR!) Unknown Response."; + /* FIXME: $data isn't in scope here */ + /* $this->_debug($data); */ + break; + } + $this->lastError = $error; + log_error($error); + } + + /* + * Private Function (added 12 July 05) [beta] + * - Detect whether or not IP needs to be updated. + * | Written Specifically for pfSense (https://www.pfsense.org) may + * | work with other systems. pfSense base is FreeBSD. + */ + function _detectChange() { + global $debug; + + if ($debug) { + log_error("DynDns ({$this->_dnsHost}): _detectChange() starting."); + } + + $currentTime = time(); + + $wan_ip = $this->_checkIP(); + if ($wan_ip == 0) { + log_error("DynDns ({$this->_dnsHost}): Current WAN IP could not be determined, skipping update process."); + return false; + } + $log_error = "DynDns ({$this->_dnsHost}): Current WAN IP: {$wan_ip} "; + + if ($this->_useIPv6 == true) { + if (file_exists($this->_cacheFile_v6)) { + $contents = file_get_contents($this->_cacheFile_v6); + list($cacheIP, $cacheTime) = explode('|', $contents); + $this->_debug($cacheIP.'/'.$cacheTime); + $initial = false; + $log_error .= "Cached IPv6: {$cacheIP} "; + } else { + conf_mount_rw(); + $cacheIP = '::'; + @file_put_contents($this->_cacheFile, "::|{$currentTime}"); + conf_mount_ro(); + $cacheTime = $currentTime; + $initial = true; + $log_error .= "No Cached IPv6 found."; + } + } else { + if (file_exists($this->_cacheFile)) { + $contents = file_get_contents($this->_cacheFile); + list($cacheIP, $cacheTime) = explode(':', $contents); + $this->_debug($cacheIP.'/'.$cacheTime); + $initial = false; + $log_error .= "Cached IP: {$cacheIP} "; + } else { + conf_mount_rw(); + $cacheIP = '0.0.0.0'; + @file_put_contents($this->_cacheFile, "0.0.0.0:{$currentTime}"); + conf_mount_ro(); + $cacheTime = $currentTime; + $initial = true; + $log_error .= "No Cached IP found."; + } + } + if ($this->_dnsVerboseLog) { + log_error($log_error); + } + + // Convert seconds = days * hr/day * min/hr * sec/min + $maxCacheAgeSecs = $this->_dnsMaxCacheAgeDays * 24 * 60 * 60; + + $needs_updating = FALSE; + /* lets determine if the item needs updating */ + if ($cacheIP != $wan_ip) { + $needs_updating = true; + $update_reason = "DynDns: cacheIP != wan_ip. Updating. "; + $update_reason .= "Cached IP: {$cacheIP} WAN IP: {$wan_ip} "; + } + if (($currentTime - $cacheTime) > $maxCacheAgeSecs) { + $needs_updating = true; + $this->_forceUpdateNeeded = true; + $update_reason = "DynDns: More than " . $this->_dnsMaxCacheAgeDays . " days. Updating. "; + $update_reason .= "{$currentTime} - {$cacheTime} > {$maxCacheAgeSecs} "; + } + if ($initial == true) { + $needs_updating = true; + $update_reason .= "Initial update. "; + } + + /* finally if we need updating then store the + * new cache value and return true + */ + if ($needs_updating == true) { + if ($this->_dnsVerboseLog) { + log_error("DynDns ({$this->_dnsHost}): {$update_reason}"); + } + return true; + } + + return false; + } + + /* + * Private Function (added 16 July 05) [beta] + * - Writes debug information to a file. + * - This function is only called when a unknown response + * - status is returned from a DynDNS service provider. + */ + function _debug($data) { + global $g; + + if (!$g['debug']) { + return; + } + $string = date('m-d-y h:i:s').' - ('.$this->_debugID.') - ['.$this->_dnsService.'] - '.$data."\n"; + conf_mount_rw(); + $file = fopen($this->_debugFile, 'a'); + fwrite($file, $string); + fclose($file); + conf_mount_ro(); + } + function _checkIP() { + global $debug; + + if ($debug) { + log_error("DynDns ({$this->_dnsHost}): _checkIP() starting."); + } + + if ($this->_useIPv6 == true) { + $ip_address = find_interface_ipv6($this->_if); + if (!is_ipaddrv6($ip_address)) { + return 0; + } + } else { + $ip_address = find_interface_ip($this->_if); + if (!is_ipaddr($ip_address)) { + return 0; + } + } + if ($this->_useIPv6 == false && is_private_ip($ip_address)) { + $hosttocheck = "checkip.dyndns.org"; + $try = 0; + while ($try < 3) { + $checkip = gethostbyname($hosttocheck); + if (is_ipaddr($checkip)) { + break; + } + $try++; + } + if ($try >= 3) { + log_error("Dyndns debug information ({$this->_dnsHost}): Could not resolve {$hosttocheck} to IP using interface IP {$ip_address}."); + return 0; + } + $ip_ch = curl_init("http://{$checkip}"); + curl_setopt($ip_ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ip_ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ip_ch, CURLOPT_INTERFACE, 'host!' . $ip_address); + curl_setopt($ip_ch, CURLOPT_CONNECTTIMEOUT, '30'); + curl_setopt($ip_ch, CURLOPT_TIMEOUT, 120); + if ($this->_useIPv6 == false) { + curl_setopt($ip_ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + } + $ip_result_page = curl_exec($ip_ch); + curl_close($ip_ch); + $ip_result_decoded = urldecode($ip_result_page); + preg_match('/Current IP Address: (.*)<\/body>/', $ip_result_decoded, $matches); + $ip_address = trim($matches[1]); + if (is_ipaddr($ip_address)) { + if ($this->_dnsVerboseLog) { + log_error("DynDns ({$this->_dnsHost}): {$ip_address} extracted from {$hosttocheck}"); + } + } else { + log_error("DynDns ({$this->_dnsHost}): IP address could not be extracted from {$hosttocheck}"); + return 0; + } + } else { + if ($this->_dnsVerboseLog) { + log_error("DynDns ({$this->_dnsHost}): {$ip_address} extracted from local system."); + } + } + $this->_dnsIP = $ip_address; + + return $ip_address; + } + + } + +?> diff --git a/src/etc/inc/easyrule.inc b/src/etc/inc/easyrule.inc new file mode 100644 index 0000000..c46e84d --- /dev/null +++ b/src/etc/inc/easyrule.inc @@ -0,0 +1,495 @@ +<?php +/* + easyrule.inc + + Copyright (C) 2009-2010 Jim Pingle (jpingle@gmail.com) + Originally Sponsored By Anathematic @ pfSense Forums + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ +/* + pfSense_BUILDER_BINARIES: + pfSense_MODULE: filter +*/ + +$blockaliasname = 'EasyRuleBlockHosts'; +$protocols_with_ports = array('tcp', 'udp'); +require_once("functions.inc"); +require_once("util.inc"); +require_once("config.inc"); + +function easyrule_find_rule_interface($int) { + global $config; + /* Borrowed from firewall_rules.php */ + $iflist = get_configured_interface_with_descr(false, true); + + if ($config['pptpd']['mode'] == "server") { + $iflist['pptp'] = "PPTP VPN"; + } + + if ($config['pppoe']['mode'] == "server") { + $iflist['pppoe'] = "PPPoE Server"; + } + + if ($config['l2tp']['mode'] == "server") { + $iflist['l2tp'] = "L2TP VPN"; + } + + /* add ipsec interfaces */ + if (isset($config['ipsec']['enable']) || isset($config['ipsec']['client']['enable'])) { + $iflist["enc0"] = "IPSEC"; + } + + if (isset($iflist[$int])) { + return $int; + } + + foreach ($iflist as $if => $ifd) { + if (strtolower($int) == strtolower($ifd)) { + return $if; + } + } + + if (substr($int, 0, 4) == "ovpn") { + return "openvpn"; + } + + return false; +} + +function easyrule_block_rule_exists($int = 'wan', $ipproto = "inet") { + global $blockaliasname, $config; + /* No rules, we we know it doesn't exist */ + if (!is_array($config['filter']['rule'])) { + return false; + } + + /* Search through the rules for one referencing our alias */ + foreach ($config['filter']['rule'] as $rule) { + if (!is_array($rule) || !is_array($rule['source'])) { + continue; + } + $checkproto = isset($rule['ipprotocol']) ? $rule['ipprotocol'] : "inet"; + if ($rule['source']['address'] == $blockaliasname . strtoupper($int) && ($rule['interface'] == $int) && ($checkproto == $ipproto)) { + return true; + } + } + return false; +} + +function easyrule_block_rule_create($int = 'wan', $ipproto = "inet") { + global $blockaliasname, $config; + /* If the alias doesn't exist, exit. + * Can't create an empty alias, and we don't know a host */ + if (easyrule_block_alias_getid($int) === false) { + return false; + } + + /* If the rule already exists, no need to do it again */ + if (easyrule_block_rule_exists($int, $ipproto)) { + return true; + } + + /* No rules, start a new array */ + if (!is_array($config['filter']['rule'])) { + $config['filter']['rule'] = array(); + } + + filter_rules_sort(); + $a_filter = &$config['filter']['rule']; + + /* Make up a new rule */ + $filterent = array(); + $filterent['type'] = 'block'; + $filterent['interface'] = $int; + $filterent['ipprotocol'] = $ipproto; + $filterent['source']['address'] = $blockaliasname . strtoupper($int); + $filterent['destination']['any'] = ''; + $filterent['descr'] = gettext("Easy Rule: Blocked from Firewall Log View"); + $filterent['created'] = make_config_revision_entry(null, gettext("Easy Rule")); + + array_splice($a_filter, 0, 0, array($filterent)); + + return true; +} + +function easyrule_block_alias_getid($int = 'wan') { + global $blockaliasname, $config; + if (!is_array($config['aliases'])) { + return false; + } + + /* Hunt down an alias with the name we want, return its id */ + foreach ($config['aliases']['alias'] as $aliasid => $alias) { + if ($alias['name'] == $blockaliasname . strtoupper($int)) { + return $aliasid; + } + } + + return false; +} + +function easyrule_block_alias_add($host, $int = 'wan') { + global $blockaliasname, $config; + /* If the host isn't a valid IP address, bail */ + $host = trim($host, "[]"); + if (!is_ipaddr($host) && !is_subnet($host)) { + return false; + } + + /* If there are no aliases, start an array */ + if (!is_array($config['aliases']['alias'])) { + $config['aliases']['alias'] = array(); + } + + $a_aliases = &$config['aliases']['alias']; + + /* Try to get the ID if the alias already exists */ + $id = easyrule_block_alias_getid($int); + if ($id === false) { + unset($id); + } + + $alias = array(); + + if (is_subnet($host)) { + list($host, $mask) = explode("/", $host); + } elseif (is_specialnet($host)) { + $mask = 0; + } elseif (is_ipaddrv6($host)) { + $mask = 128; + } else { + $mask = 32; + } + + if (isset($id) && $a_aliases[$id]) { + + // Catch case when the list is empty + if (empty($a_aliases[$id]['address'])) { + $a_address = array(); + $a_detail = array(); + } else { + $a_address = explode(" ", $a_aliases[$id]['address']); + + /* Make sure this IP isn't already in the list. */ + if (in_array($host.'/'.$mask, $a_address)) { + return true; + } + $a_detail = explode("||", $a_aliases[$id]['detail']); + } + + /* Since the alias already exists, just add to it. */ + $alias['name'] = $a_aliases[$id]['name']; + $alias['type'] = $a_aliases[$id]['type']; + $alias['descr'] = $a_aliases[$id]['descr']; + + $a_address[] = $host.'/'.$mask; + $a_detail[] = gettext('Entry added') . ' ' . date('r'); + + $alias['address'] = join(" ", $a_address); + $alias['detail'] = join("||", $a_detail); + + } else { + /* Create a new alias with all the proper information */ + $alias['name'] = $blockaliasname . strtoupper($int); + $alias['type'] = 'network'; + $alias['descr'] = gettext("Hosts blocked from Firewall Log view"); + + $alias['address'] = $host . '/' . $mask; + $alias['detail'] = gettext('Entry added') . ' ' . date('r') . '||'; + } + + /* Replace the old alias if needed, otherwise tack it on the end */ + if (isset($id) && $a_aliases[$id]) { + $a_aliases[$id] = $alias; + } else { + $a_aliases[] = $alias; + } + + // Sort list + $a_aliases = msort($a_aliases, "name"); + + return true; +} + +function easyrule_block_host_add($host, $int = 'wan', $ipproto = "inet") { + global $retval; + /* Bail if the supplied host is not a valid IP address */ + $host = trim($host, "[]"); + if (!is_ipaddr($host) && !is_subnet($host)) { + return false; + } + + /* Flag whether or not we need to reload the filter */ + $dirty = false; + + /* Attempt to add this host to the alias */ + if (easyrule_block_alias_add($host, $int)) { + $dirty = true; + } else { + /* Couldn't add the alias, or adding the host failed. */ + return false; + } + + /* Attempt to add the firewall rule if it doesn't exist. + * Failing to add the rule isn't necessarily an error, it may + * have been modified by the user in some way. Adding to the + * Alias is what's important. + */ + if (!easyrule_block_rule_exists($int, $ipproto)) { + if (easyrule_block_rule_create($int, $ipproto)) { + $dirty = true; + } else { + return false; + } + } + + /* If needed, write the config and reload the filter */ + if ($dirty) { + write_config(); + $retval = filter_configure(); + if (!empty($_SERVER['DOCUMENT_ROOT'])) { + header("Location: firewall_aliases.php"); + exit; + } else { + return true; + } + } else { + return false; + } +} + +function easyrule_pass_rule_add($int, $proto, $srchost, $dsthost, $dstport, $ipproto) { + global $config; + + /* No rules, start a new array */ + if (!is_array($config['filter']['rule'])) { + $config['filter']['rule'] = array(); + } + + filter_rules_sort(); + $a_filter = &$config['filter']['rule']; + + /* Make up a new rule */ + $filterent = array(); + $filterent['type'] = 'pass'; + $filterent['interface'] = $int; + $filterent['ipprotocol'] = $ipproto; + $filterent['descr'] = gettext("Easy Rule: Passed from Firewall Log View"); + + if ($proto != "any") { + $filterent['protocol'] = $proto; + } else { + unset($filterent['protocol']); + } + + /* Default to only allow echo requests, since that's what most people want and + * it should be a safe choice. */ + if ($proto == "icmp") { + $filterent['icmptype'] = 'echoreq'; + } + + if ((strtolower($proto) == "icmp6") || (strtolower($proto) == "icmpv6")) { + $filterent['protocol'] = "icmp"; + } + + if (is_subnet($srchost)) { + list($srchost, $srcmask) = explode("/", $srchost); + } elseif (is_specialnet($srchost)) { + $srcmask = 0; + } elseif (is_ipaddrv6($srchost)) { + $srcmask = 128; + } else { + $srcmask = 32; + } + + if (is_subnet($dsthost)) { + list($dsthost, $dstmask) = explode("/", $dsthost); + } elseif (is_specialnet($dsthost)) { + $dstmask = 0; + } elseif (is_ipaddrv6($dsthost)) { + $dstmask = 128; + } else { + $dstmask = 32; + } + + pconfig_to_address($filterent['source'], $srchost, $srcmask); + pconfig_to_address($filterent['destination'], $dsthost, $dstmask, '', $dstport, $dstport); + + $filterent['created'] = make_config_revision_entry(null, gettext("Easy Rule")); + $a_filter[] = $filterent; + + write_config($filterent['descr']); + $retval = filter_configure(); + if (!empty($_SERVER['DOCUMENT_ROOT'])) { + header("Location: firewall_rules.php?if={$int}"); + exit; + } else { + return true; + } +} + +function easyrule_parse_block($int, $src, $ipproto = "inet") { + if (!empty($src) && !empty($int)) { + $src = trim($src, "[]"); + if (!is_ipaddr($src) && !is_subnet($src)) { + return gettext("Tried to block invalid IP:") . ' ' . htmlspecialchars($src); + } + $int = easyrule_find_rule_interface($int); + if ($int === false) { + return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int); + } + if (easyrule_block_host_add($src, $int, $ipproto)) { + return gettext("Host added successfully"); + } else { + return gettext("Failed to create block rule, alias, or add host."); + } + } else { + return gettext("Tried to block but had no host IP or interface"); + } + return gettext("Unknown block error."); +} + +function easyrule_parse_unblock($int, $host, $ipproto = "inet") { + global $blockaliasname, $config; + + if (!empty($host) && !empty($int)) { + $host = trim($host, "[]"); + if (!is_ipaddr($host) && !is_subnet($host)) { + return gettext("Tried to unblock invalid IP:") . ' ' . htmlspecialchars($host); + } + $real_int = easyrule_find_rule_interface($int); + if ($real_int === false) { + return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int); + } + + /* Try to get the ID - will fail if there are no rules/alias on this interface */ + $id = easyrule_block_alias_getid($real_int); + if ($id === false || !$config['aliases']['alias'][$id]) { + return gettext("No block rules set on interface:") . ' ' . htmlspecialchars($int); + } + + $alias = &$config['aliases']['alias'][$id]; + + if (is_subnet($host)) { + list($host, $mask) = explode("/", $host); + } elseif (is_specialnet($host)) { + $mask = 0; + } elseif (is_ipaddrv6($host)) { + $mask = 128; + } else { + $mask = 32; + } + + // Create the expected string representation + $unblock = $host.'/'.$mask; + + $a_address = explode(" ", $config['aliases']['alias'][$id]['address']); + $a_detail = explode("||", $config['aliases']['alias'][$id]['detail']); + + if (($key = array_search($unblock, $a_address)) !== false) { + unset($a_address[$key]); + unset($a_detail[$key]); + // Write back the result to the config array + $config['aliases']['alias'][$id]['address'] = join(" ", $a_address); + $config['aliases']['alias'][$id]['detail'] = join("||", $a_detail); + + // Update config + write_config(); + $retval = filter_configure(); + if (!empty($_SERVER['DOCUMENT_ROOT'])) { + header("Location: firewall_aliases.php"); + exit; + } else { + return gettext("Host unblocked successfully"); + } + } else { + return gettext("Host ist not on block list: " . $host); + } + } + + return gettext("Tried to unblock but had no host IP or interface"); + +} + +function easyrule_parse_getblock($int = 'wan', $sep = "\n") { + global $blockaliasname, $config; + + $real_int = easyrule_find_rule_interface($int); + if ($real_int === false) { + return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int); + } + + /* Try to get the ID - will fail if there are no rules/alias on this interface */ + $id = easyrule_block_alias_getid($real_int); + + if ($id === false || !$config['aliases']['alias'][$id] || empty($config['aliases']['alias'][$id]['address'])) { + return gettext("No block rules set on interface:") . ' ' . htmlspecialchars($int); + } + return join($sep, explode(" ", $config['aliases']['alias'][$id]['address'])); + +} + +function easyrule_parse_pass($int, $proto, $src, $dst, $dstport = 0, $ipproto = "inet") { + /* Check for valid int, srchost, dsthost, dstport, and proto */ + global $protocols_with_ports; + $src = trim($src, "[]"); + $dst = trim($dst, "[]"); + + if (!empty($int) && !empty($proto) && !empty($src) && !empty($dst)) { + $int = easyrule_find_rule_interface($int); + if ($int === false) { + return gettext("Invalid interface for pass rule:") . ' ' . htmlspecialchars($int); + } + if (getprotobyname($proto) == -1) { + return gettext("Invalid protocol for pass rule:") . ' ' . htmlspecialchars($proto); + } + if (!is_ipaddr($src) && !is_subnet($src) && !is_ipaddroralias($src) && !is_specialnet($src)) { + return gettext("Tried to pass invalid source IP:") . ' ' . htmlspecialchars($src); + } + if (!is_ipaddr($dst) && !is_subnet($dst) && !is_ipaddroralias($dst) && !is_specialnet($dst)) { + return gettext("Tried to pass invalid destination IP:") . ' ' . htmlspecialchars($dst); + } + if (in_array($proto, $protocols_with_ports)) { + if (empty($dstport)) { + return gettext("Missing destination port:") . ' ' . htmlspecialchars($dstport); + } + if (!is_port($dstport) && ($dstport != "any")) { + return gettext("Tried to pass invalid destination port:") . ' ' . htmlspecialchars($dstport); + } + } else { + $dstport = 0; + } + /* Should have valid input... */ + if (easyrule_pass_rule_add($int, $proto, $src, $dst, $dstport, $ipproto)) { + return gettext("Successfully added pass rule!"); + } else { + return gettext("Failed to add pass rule."); + } + } else { + return gettext("Missing parameters for pass rule."); + } + return gettext("Unknown pass error."); +} + +?> diff --git a/src/etc/inc/filter.inc b/src/etc/inc/filter.inc new file mode 100644 index 0000000..36bbe2b --- /dev/null +++ b/src/etc/inc/filter.inc @@ -0,0 +1,4228 @@ +<?php +/* $Id$ */ +/* + filter.inc + Copyright (C) 2004-2006 Scott Ullrich + Copyright (C) 2005 Bill Marquette + Copyright (C) 2006 Peter Allgeyer + Copyright (C) 2008-2010 Ermal Luçi + All rights reserved. + + originally part of m0n0wall (http://m0n0.ch/wall) + Copyright (C) 2003-2004 Manuel Kasper <mk@neon1.net>. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + pfSense_BUILDER_BINARIES: /sbin/kldload /usr/sbin/tcpdump /sbin/pfctl /bin/rm + pfSense_BUILDER_BINARIES: /usr/sbin/inetd + pfSense_MODULE: filter +*/ + + +/* holds the items that will be executed *AFTER* the filter is fully loaded */ +$after_filter_configure_run = array(); + +/* For installing cron job of schedules */ +$time_based_rules = false; + +/* Used to hold the interface list that will be used on ruleset creation. */ +$FilterIflist = array(); + +/* Create a global array to avoid errors on rulesets. */ +$GatewaysList = array(); + +/* Used for the hostname dns resolver */ +$filterdns = array(); + +/* Used for aliases and interface macros */ +$aliases = ""; + +/* ICMP v4 types */ +$icmptypes = array( + "" => gettext("any"), + "echoreq" => gettext("Echo request"), + "echorep" => gettext("Echo reply"), + "unreach" => gettext("Destination unreachable"), + "squench" => gettext("Source quench"), + "redir" => gettext("Redirect"), + "althost" => gettext("Alternate Host"), + "routeradv" => gettext("Router advertisement"), + "routersol" => gettext("Router solicitation"), + "timex" => gettext("Time exceeded"), + "paramprob" => gettext("Invalid IP header"), + "timereq" => gettext("Timestamp"), + "timerep" => gettext("Timestamp reply"), + "inforeq" => gettext("Information request"), + "inforep" => gettext("Information reply"), + "maskreq" => gettext("Address mask request"), + "maskrep" => gettext("Address mask reply"), + "trace" => gettext("Traceroute"), + "dataconv" => gettext("Datagram conversion error"), + "mobredir" => gettext("Mobile host redirect"), + "ipv6-where" => gettext("IPv6 where-are-you"), + "ipv6-here" => gettext("IPv6 I-am-here"), + "mobregreq" => gettext("Mobile registration request"), + "mobregrep" => gettext("Mobile registration reply"), + "skip" => gettext("SKIP"), + "photuris" => gettext("Photuris") +); + +/* ICMP v6 types */ +$icmp6types = array( + "" => gettext("any"), + "unreach" => gettext("Destination unreachable"), + "toobig" => gettext("Packet too big"), + "timex" => gettext("Time exceeded"), + "paramprob" => gettext("Parameter problem"), + "echoreq" => gettext("Echo request"), + "echorep" => gettext("Echo reply"), + "groupqry" => gettext("Group membership query"), + "listqry" => gettext("Multicast listener query"), + "grouprep" => gettext("Group membership report"), + "listenrep" => gettext("Multicast listener report"), + "groupterm" => gettext("Group membership termination"), + "listendone" => gettext("Multicast listener done"), + "routersol" => gettext("Router solicitation"), + "routeradv" => gettext("Router advertisement"), + "neighbrsol" => gettext("Neighbor solicitation"), + "neighbradv" => gettext("Neighbor advertisement"), + "redir" => gettext("Redirect"), + "routrrenum" => gettext("Router renumbering"), + "wrureq" => gettext("Who are you request"), + "wrurep" => gettext("Who are you reply"), + "fqdnreq" => gettext("FQDN query"), + "fqdnrep" => gettext("FQDN reply"), + "niqry" => gettext("Node information request"), + "nirep" => gettext("Node information reply"), + "mtraceresp" => gettext("mtrace resp"), + "mtrace" => gettext("mtrace messages") +); + +global $tracker; +global $negate_tracker; +$tracker = 1000000000; +$negate_tracker = 10000000; + +function filter_rule_tracker($tracker) { + global $tracker; + + return (++$tracker); +} + +function filter_negaterule_tracker() { + global $negate_tracker; + + ++$negate_tracker; + return "tracker {$negate_tracker} "; +} + +function fix_rule_label($descr) { + $descr = str_replace('"', '', $descr); + if (strlen($descr) > 63) { + return substr($descr, 0, 60) . "..."; + } else { + return $descr; + } +} + +function is_bogonsv6_used() { + global $config, $g; + # Only use bogonsv6 table if IPv6 Allow is on, and at least 1 enabled interface also has "blockbogons" enabled. + $usebogonsv6 = false; + if (isset($config['system']['ipv6allow'])) { + foreach ($config['interfaces'] as $ifacedata) { + if (isset($ifacedata['enable']) && isset($ifacedata['blockbogons'])) { + $usebogonsv6 = true; + break; + } + } + } + return $usebogonsv6; +} + +function filter_pflog_start($kill_first = false) { + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "filter_pflog_start() being called $mt\n"; + } + if ((!file_exists("{$g['varrun_path']}/filterlog.pid")) || + (!isvalidpid("{$g['varrun_path']}/filterlog.pid"))) { + mwexec("/usr/local/sbin/filterlog -i pflog0 -p {$g['varrun_path']}/filterlog.pid"); + } +} + +/* reload filter async */ +function filter_configure() { + global $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "filter_configure() being called $mt\n"; + } + + /* + * NOTE: Check here for bootup status since this should not be triggered during bootup. + * The reason is that rc.bootup calls filter_configure_sync directly which does this too. + */ + if (!platform_booting()) { + send_event("filter reload"); + } +} + +function filter_delete_states_for_down_gateways() { + global $config, $GatewaysList; + + if (isset($config['system']['kill_states'])) { + return; + } + + $any_gateway_down = false; + $a_gateways = return_gateways_status(); + if (is_array($GatewaysList)) { + foreach ($GatewaysList as $gwname => $gateway) { + if (empty($gateway['monitor'])) { + continue; + } + if (!is_ipaddr($gateway['monitor'])) { + continue; + } + if (strstr($gateway['monitor'], "127.0.0.")) { + continue; + } + if (empty($a_gateways[$gateway['monitor']])) { + continue; + } + $gwstatus =& $a_gateways[$gateway['monitor']]; + if (strstr($gwstatus['status'], "down")) { + $any_gateway_down = true; + break; + } + } + } + if ($any_gateway_down == true) { + mwexec("/sbin/pfctl -Fs"); + } +} + +/* reload filter sync */ +function filter_configure_sync($delete_states_if_needed = true) { + global $config, $g, $after_filter_configure_run, $FilterIflist; + global $time_based_rules, $filterdns, $aliases, $dummynet_name_list; + + /* Use filter lock to not allow concurrent filter reloads during this run. */ + $filterlck = lock('filter', LOCK_EX); + + filter_pflog_start(); + update_filter_reload_status(gettext("Initializing")); + + /* invalidate interface cache */ + get_interface_arr(true); + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "filter_configure_sync() being called $mt\n"; + } + /* Get interface list to work with. */ + filter_generate_optcfg_array(); + if (platform_booting() == true) { + echo gettext("Configuring firewall"); + } + + /* generate aliases */ + if (platform_booting() == true) { + echo "."; + } + update_filter_reload_status(gettext("Creating aliases")); + $aliases = filter_generate_aliases(); + $gateways = filter_generate_gateways(); + if (platform_booting() == true) { + echo "."; + } + update_filter_reload_status(gettext("Generating Limiter rules")); + $dummynet_rules = filter_generate_dummynet_rules(); + $dummynet_name_list = get_unique_dnqueue_list(); + update_filter_reload_status(gettext("Generating NAT rules")); + /* generate nat rules */ + $natrules = filter_nat_rules_generate(); + if (platform_booting() == true) { + echo "."; + } + update_filter_reload_status(gettext("Generating filter rules")); + /* generate pfctl rules */ + $pfrules = filter_rules_generate(); + /* generate altq, limiter */ + if (platform_booting() == true) { + echo "."; + } + update_filter_reload_status(gettext("Generating ALTQ queues")); + $altq_queues = filter_generate_altq_queues(); + update_filter_reload_status(gettext("Generating Layer7 rules")); + generate_layer7_files(); + if (platform_booting() == true) { + echo "."; + } + update_filter_reload_status(gettext("Loading filter rules")); + /* enable pf if we need to, otherwise disable */ + if (!isset ($config['system']['disablefilter'])) { + mwexec("/sbin/pfctl -e", true); + } else { + mwexec("/sbin/pfctl -d", true); + unlink_if_exists("{$g['tmp_path']}/filter_loading"); + update_filter_reload_status(gettext("Filter is disabled. Not loading rules.")); + if (platform_booting() == true) { + echo gettext("done.") . "\n"; + } + unlock($filterlck); + return; + } + + $limitrules = ""; + /* User defined maximum table entries in Advanced menu. */ + if ($config['system']['maximumtableentries'] <> "" && is_numeric($config['system']['maximumtableentries'])) { + $limitrules .= "set limit table-entries {$config['system']['maximumtableentries']}\n"; + } + + if ($config['system']['optimization'] <> "") { + $limitrules .= "set optimization {$config['system']['optimization']}\n"; + if ($config['system']['optimization'] == "conservative") { + $limitrules .= "set timeout { udp.first 300, udp.single 150, udp.multiple 900 }\n"; + } + } else { + $limitrules .= "set optimization normal\n"; + } + + $timeoutlist = ""; + if (isset($config['system']['tcpfirsttimeout']) && is_numericint($config['system']['tcpfirsttimeout'])) { + $timeoutlist .= " tcp.first {$config['system']['tcpfirsttimeout']} "; + } + if (isset($config['system']['tcpopeningtimeout']) && is_numericint($config['system']['tcpopeningtimeout'])) { + $timeoutlist .= " tcp.opening {$config['system']['tcpopeningtimeout']} "; + } + if (isset($config['system']['tcpestablishedtimeout']) && is_numericint($config['system']['tcpestablishedtimeout'])) { + $timeoutlist .= " tcp.established {$config['system']['tcpestablishedtimeout']} "; + } + if (isset($config['system']['tcpclosingtimeout']) && is_numericint($config['system']['tcpclosingtimeout'])) { + $timeoutlist .= " tcp.closing {$config['system']['tcpclosingtimeout']} "; + } + if (isset($config['system']['tcpfinwaittimeout']) && is_numericint($config['system']['tcpfinwaittimeout'])) { + $timeoutlist .= " tcp.finwait {$config['system']['tcpfinwaittimeout']} "; + } + if (isset($config['system']['tcpclosedtimeout']) && is_numericint($config['system']['tcpclosedtimeout'])) { + $timeoutlist .= " tcp.closed {$config['system']['tcpclosedtimeout']} "; + } + if (isset($config['system']['udpfirsttimeout']) && is_numericint($config['system']['udpfirsttimeout'])) { + $timeoutlist .= " udp.first {$config['system']['udpfirsttimeout']} "; + } + if (isset($config['system']['udpsingletimeout']) && is_numericint($config['system']['udpsingletimeout'])) { + $timeoutlist .= " udp.single {$config['system']['udpsingletimeout']} "; + } + if (isset($config['system']['udpmultipletimeout']) && is_numericint($config['system']['udpmultipletimeout'])) { + $timeoutlist .= " udp.multiple {$config['system']['udpmultipletimeout']} "; + } + if (isset($config['system']['icmpfirsttimeout']) && is_numericint($config['system']['icmpfirsttimeout'])) { + $timeoutlist .= " icmp.first {$config['system']['icmpfirsttimeout']} "; + } + if (isset($config['system']['icmperrortimeout']) && is_numericint($config['system']['icmperrortimeout'])) { + $timeoutlist .= " icmp.error {$config['system']['icmperrortimeout']} "; + } + if (isset($config['system']['otherfirsttimeout']) && is_numericint($config['system']['otherfirsttimeout'])) { + $timeoutlist .= " other.first {$config['system']['otherfirsttimeout']} "; + } + if (isset($config['system']['othersingletimeout']) && is_numericint($config['system']['othersingletimeout'])) { + $timeoutlist .= " other.single {$config['system']['othersingletimeout']} "; + } + if (isset($config['system']['othermultipletimeout']) && is_numericint($config['system']['othermultipletimeout'])) { + $timeoutlist .= " other.multiple {$config['system']['othermultipletimeout']} "; + } + + if ($timeoutlist <> "") { + $limitrules .= "set timeout { $timeoutlist }\n"; + } + + if (!empty($config['system']['adaptivestart']) && !empty($config['system']['adaptiveend'])) { + $limitrules .= "set timeout { adaptive.start {$config['system']['adaptivestart']}, adaptive.end {$config['system']['adaptiveend']} }\n"; + } + + if ($config['system']['maximumstates'] <> "" && is_numeric($config['system']['maximumstates'])) { + /* User defined maximum states in Advanced menu. */ + $limitrules .= "set limit states {$config['system']['maximumstates']}\n"; + $limitrules .= "set limit src-nodes {$config['system']['maximumstates']}\n"; + } else { + $max_states = pfsense_default_state_size(); + $limitrules .= "set limit states {$max_states}\n"; + $limitrules .= "set limit src-nodes {$max_states}\n"; + } + + /* Frag limit. pf default is 5000 */ + if ($config['system']['maximumfrags'] <> "" && is_numeric($config['system']['maximumfrags'])) { + $limitrules .= "set limit frags {$config['system']['maximumfrags']}\n"; + } + + if (isset($config['system']['lb_use_sticky']) && is_numeric($config['system']['srctrack']) && ($config['system']['srctrack'] > 0)) { + $limitrules .= "set timeout src.track {$config['system']['srctrack']}\n"; + } + + $rules = ""; + $rules = "{$limitrules}\n"; + $rules .= "{$aliases} \n"; + $rules .= "{$gateways} \n"; + update_filter_reload_status(gettext("Setting up logging information")); + $rules .= filter_setup_logging_interfaces(); + $rules .= "\n"; + $rules .= "set skip on pfsync0\n"; + $rules .= "\n"; + update_filter_reload_status(gettext("Setting up SCRUB information")); + $rules .= filter_generate_scrubing(); + $rules .= "\n"; + $rules .= "{$altq_queues}\n"; + $rules .= "{$natrules}\n"; + $rules .= "{$pfrules}\n"; + $rules .= discover_pkg_rules("filter"); + + unset($aliases, $gateways, $altq_queues, $natrules, $pfrules); + + // Copy rules.debug to rules.debug.old + if (file_exists("{$g['tmp_path']}/rules.debug")) { + @copy("{$g['tmp_path']}/rules.debug", "{$g['tmp_path']}/rules.debug.old"); + } + + if (!@file_put_contents("{$g['tmp_path']}/rules.debug", $rules, LOCK_EX)) { + log_error("WARNING: Could not write new rules!"); + unlock($filterlck); + return; + } + + @file_put_contents("{$g['tmp_path']}/rules.limits", $limitrules); + mwexec("/sbin/pfctl -Of {$g['tmp_path']}/rules.limits"); + unset($rules, $limitrules); + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "pfctl being called at $mt\n"; + } + unset($rules_loading, $rules_error); + $_grbg = exec("/sbin/pfctl -o basic -f {$g['tmp_path']}/rules.debug 2>&1", $rules_error, $rules_loading); + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "pfctl done at $mt\n"; + } + /* + * check for a error while loading the rules file. if an error has occurred + * then output the contents of the error to the caller + */ + if ($rules_loading <> 0) { + $saved_line_error = $rules_error[0]; + $line_error = explode(":", $rules_error[0]); + $line_number = $line_error[1]; + $line_split = file("{$g['tmp_path']}/rules.debug"); + if (is_array($line_split)) { + $line_error = sprintf(gettext('The line in question reads [%1$d]: %2$s'), $line_number, $line_split[$line_number-1]); + } + unset($line_split); + + /* Brutal ugly hack but required -- PF is stuck, unwedge */ + if (strstr("$rules_error[0]", "busy")) { + exec("/sbin/pfctl -d; /sbin/pfctl -e; /sbin/pfctl -f {$g['tmp_path']}/rules.debug"); + $error_msg = gettext("PF was wedged/busy and has been reset."); + file_notice("pf_busy", $error_msg, "pf_busy", ""); + } else { + $_grbg = exec("/sbin/pfctl -o basic -f {$g['tmp_path']}/rules.debug.old 2>&1"); + } + unset($rules_loading, $rules_error); + + if ($line_error and $line_number) { + file_notice("filter_load", sprintf(gettext('There were error(s) loading the rules: %1$s - %2$s'), $saved_line_error, $line_error), "Filter Reload", ""); + update_filter_reload_status(sprintf(gettext('There were error(s) loading the rules: %1$s - %2$s'), $saved_line_error, $line_error)); + unlock($filterlck); + return; + } + } + + # If we are not using bogonsv6 then we can remove any bogonsv6 table from the running pf (if the table is not there, the kill is still fine). + if (!is_bogonsv6_used()) { + $_grbg = exec("/sbin/pfctl -t bogonsv6 -T kill 2>/dev/null"); + } + + update_filter_reload_status(gettext("Starting up layer7 daemon")); + layer7_start_l7daemon(); + + if (!platform_booting()) { + if (!empty($filterdns)) { + @file_put_contents("{$g['varetc_path']}/filterdns.conf", implode("", $filterdns)); + unset($filterdns); + if (isvalidpid("{$g['varrun_path']}/filterdns.pid")) { + sigkillbypid("{$g['varrun_path']}/filterdns.pid", "HUP"); + } else { + /* + * FilterDNS has three debugging levels. The default chosen is 1. + * Available are level 2 and greater then 2. + */ + if (isset($config['system']['aliasesresolveinterval']) && is_numeric($config['system']['aliasesresolveinterval'])) { + $resolve_interval = $config['system']['aliasesresolveinterval']; + } else { + $resolve_interval = 300; + } + mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns.pid -i {$resolve_interval} -c {$g['varetc_path']}/filterdns.conf -d 1"); + } + } else { + killbypid("{$g['varrun_path']}/filterdns.pid"); + @unlink("{$g['varrun_path']}/filterdns.pid"); + } + } + + /* run items scheduled for after filter configure run */ + $fda = fopen("{$g['tmp_path']}/commands.txt", "w"); + if ($fda) { + if ($after_filter_configure_run) { + foreach ($after_filter_configure_run as $afcr) { + fwrite($fda, $afcr . "\n"); + } + unset($after_filter_configure_run); + } + + /* + * we need a way to let a user run a shell cmd after each + * filter_configure() call. run this xml command after + * each change. + */ + if ($config['system']['afterfilterchangeshellcmd'] <> "") { + fwrite($fda, $config['system']['afterfilterchangeshellcmd'] . "\n"); + } + + fclose($fda); + } + + if (file_exists("{$g['tmp_path']}/commands.txt")) { + mwexec("sh {$g['tmp_path']}/commands.txt &"); + unlink("{$g['tmp_path']}/commands.txt"); + } + + /* if time based rules are enabled then swap in the set */ + if ($time_based_rules == true) { + filter_tdr_install_cron(true); + } else { + filter_tdr_install_cron(false); + } + + if (platform_booting() == true) { + echo "."; + } + + if ($delete_states_if_needed) { + update_filter_reload_status(gettext("Processing down interface states")); + filter_delete_states_for_down_gateways(); + } + + update_filter_reload_status(gettext("Running plugins")); + + if (is_dir("/usr/local/pkg/pf/")) { + /* process packager manager custom rules */ + update_filter_reload_status(gettext("Running plugins (pf)")); + run_plugins("/usr/local/pkg/pf/"); + update_filter_reload_status(gettext("Plugins completed.")); + } + + update_filter_reload_status(gettext("Done")); + if (platform_booting() == true) { + echo gettext("done.") . "\n"; + } + + unlock($filterlck); + return 0; +} + +function filter_generate_scrubing() { + global $config, $FilterIflist; + $scrubrules = ""; + + if (isset($config['system']['maxmss_enable'])) { + $maxmss = 1400; + if (!empty($config['system']['maxmss'])) { + $maxmss = $config['system']['maxmss']; + } + + $scrubrules .= "scrub from any to <vpn_networks> max-mss {$maxmss}\n"; + $scrubrules .= "scrub from <vpn_networks> to any max-mss {$maxmss}\n"; + } + /* disable scrub option */ + foreach ($FilterIflist as $scrubif => $scrubcfg) { + if (isset($scrubcfg['virtual']) || empty($scrubcfg['descr'])) { + continue; + } + /* set up MSS clamping */ + if (($scrubcfg['mss'] <> "") && + (is_numeric($scrubcfg['mss'])) && + ($scrubcfg['if'] != "pppoe") && + ($scrubcfg['if'] != "pptp") && + ($scrubif['if'] != "l2tp")) { + $mssclamp = "max-mss " . (intval($scrubcfg['mss'] - 40)); + } else { + $mssclamp = ""; + } + /* configure no-df for linux nfs and others */ + if ($config['system']['scrubnodf']) { + $scrubnodf = "no-df"; + } else { + $scrubnodf = ""; + } + if ($config['system']['scrubrnid']) { + $scrubrnid = "random-id"; + } else { + $scrubrnid = ""; + } + if (!isset($config['system']['disablescrub'])) { + $scrubrules .= "scrub on \${$scrubcfg['descr']} all {$scrubnodf} {$scrubrnid} {$mssclamp} fragment reassemble\n"; // reassemble all directions + } else if (!empty($mssclamp)) { + $scrubrules .= "scrub on \${$scrubcfg['descr']} {$mssclamp}\n"; + } + } + return $scrubrules; +} + +function filter_generate_nested_alias($name, $alias, &$aliasnesting, &$aliasaddrnesting) { + global $aliastable, $filterdns; + + $addresses = explode(" ", $alias); + $use_filterdns = false; + $finallist = ""; + $builtlist = ""; + $urltable_nesting = ""; + $aliasnesting[$name] = $name; + $alias_type = alias_get_type($name); + foreach ($addresses as $address) { + if (empty($address)) { + continue; + } + $linelength = strlen($builtlist); + $tmpline = ""; + if (is_alias($address)) { + if (alias_get_type($address) == 'urltable') { + // Feature#1603. For this type of alias we do not need to recursively call filter_generate_nested_alias. Just load IPs from the file. + $urltable_nesting = alias_expand_urltable($address); + if (!empty($urltable_nesting)) { + $urlfile_as_arr = file($urltable_nesting); + foreach ($urlfile_as_arr as $line) { + $address= rtrim($line); + if ((strlen($tmpline) + $linelength) > 4036) { + $finallist .= "{$tmpline} \\\n"; + $tmpline = ""; + } + $tmpline .= " {$address}"; + } + } + } + /* We already expanded this alias so there is no necessity to do it again. */ + else if (!isset($aliasnesting[$address])) { + $tmpline = filter_generate_nested_alias($name, $aliastable[$address], $aliasnesting, $aliasaddrnesting); + } + } else if (!isset($aliasaddrnesting[$address])) { + if (!is_ipaddr($address) && !is_subnet($address) && !((($alias_type == 'port') || ($alias_type == 'url_ports')) && (is_port($address) || is_portrange($address))) && is_hostname($address)) { + if (!isset($filterdns["{$address}{$name}"])) { + $use_filterdns = true; + $filterdns["{$address}{$name}"] = "pf {$address} {$name}\n"; + } + continue; + } + $aliasaddrnesting[$address] = $address; + $tmpline = " {$address}"; + } + if ((strlen($tmpline)+ $linelength) > 4036) { + $finallist .= "{$builtlist} \\\n"; + $builtlist = ""; + } + if (!empty($tmpline)) { + $builtlist .= " {$tmpline}"; + } + } + $finallist .= $builtlist; + + if ($use_filterdns === true && !empty($finallist)) { + foreach (explode(" ", $finallist) as $address) { + if (empty($address)) { + continue; + } + if ((is_ipaddr($address) || is_subnet($address)) && !isset($filterdns["{$address}{$name}"])) { + $filterdns["{$address}{$name}"] = "pf {$address} {$name}\n"; + } + } + $finallist = ''; + } + + return $finallist; +} + +function filter_expand_alias($alias_name) { + global $config; + + if (isset($config['aliases']['alias'])) { + foreach ($config['aliases']['alias'] as $aliased) { + if ($aliased['name'] == $alias_name) { + $aliasnesting = array(); + $aliasaddrnesting = array(); + return filter_generate_nested_alias($aliased['name'], $aliased['address'], $aliasnesting, $aliasaddrnesting); + } + } + } +} + +function filter_expand_alias_array($alias_name) { + $expansion = filter_expand_alias($alias_name); + return explode(" ", preg_replace('/\s+/', ' ', trim($expansion))); +} + +function filter_generate_aliases() { + global $config, $FilterIflist, $after_filter_configure_run; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "filter_generate_aliases() being called $mt\n"; + } + + $alias = "#System aliases\n "; + $aliases = "loopback = \"{ lo0 }\"\n"; + + foreach ($FilterIflist as $if => $ifcfg) { + if (is_array($ifcfg[0])) { + if ($ifcfg[0]['if'] == 'pppoe') { + $aliases .= "{$ifcfg[0]['descr']} = \"{ {$ifcfg[0]['if']}"; + $aliases .= " }\"\n"; + } + } elseif (!empty($ifcfg['descr']) && !empty($ifcfg['if'])) { + if ($ifcfg['type6'] == '6rd') { + $aliases .= "{$ifcfg['descr']} = \"{ {$ifcfg['if']} {$if}_stf"; + } else if ($ifcfg['type6'] == '6to4') { + $aliases .= "{$ifcfg['descr']} = \"{ {$ifcfg['if']} {$if}_stf"; + } else { + $aliases .= "{$ifcfg['descr']} = \"{ {$ifcfg['if']}"; + + if ($ifcfg['type'] == 'pptp') { + foreach (get_parent_interface($ifcfg['if']) as $parent_if) { + if ($parent_if != $ifcfg['if']) { + $aliases .= " {$parent_if}"; + } + } + } + } + $aliases .= " }\"\n"; + } + } + + $aliases .= "\n#SSH Lockout Table\n"; + $aliases .= "table <sshlockout> persist\n"; + $aliases .= "table <webConfiguratorlockout> persist\n"; + + $aliases .= "#Snort tables\n"; + $aliases .= "table <snort2c>\n"; + $aliases .= "table <virusprot>\n"; + if (!file_exists("/etc/bogons") || !file_exists("/etc/bogonsv6")) { + conf_mount_rw(); + if (!file_exists("/etc/bogons")) { + @file_put_contents("/etc/bogons", ""); + } + if (!file_exists("/etc/bogonsv6")) { + @file_put_contents("/etc/bogonsv6", ""); + } + conf_mount_ro(); + } + $aliases .= "table <bogons> persist file \"/etc/bogons\"\n"; + if (is_bogonsv6_used()) { + $aliases .= "table <bogonsv6> persist file \"/etc/bogonsv6\"\n"; + } + + $vpns_list = filter_get_vpns_list(); + if ($vpns_list) { + $aliases .= "table <vpn_networks> { $vpns_list }\n"; + } + + /* add a Negate_networks table */ + $aliases .= "table <negate_networks> "; + if ($vpns_list) { + $aliases .= "{ $vpns_list }"; + } + $aliases .= "\n"; + + $aliases .= "\n# User Aliases \n"; + /* Setup pf groups */ + if (isset($config['aliases']['alias'])) { + foreach ($config['aliases']['alias'] as $aliased) { + $extralias = ""; + $aliasnesting = array(); + $aliasaddrnesting = array(); + if (is_numericint($aliased['name'])) { + // skip aliases with numeric-only names. redmine #4289 + file_notice("Filter_Reload", "Aliases with numeric-only names are not valid. Skipping alias " . $aliased['name']); + continue; + } + $addrlist = filter_generate_nested_alias($aliased['name'], $aliased['address'], $aliasnesting, $aliasaddrnesting); + switch ($aliased['type']) { + case "host": + case "network": + case "url": + $tableaddrs = "{$addrlist}{$extralias}"; + if (empty($tableaddrs)) { + $aliases .= "table <{$aliased['name']}> persist\n"; + if (empty($aliased['address'])) { + $after_filter_configure_run[] = "/sbin/pfctl -T flush -t " . escapeshellarg($aliased['name']); + } + } else { + $aliases .= "table <{$aliased['name']}> { {$addrlist}{$extralias} } \n"; + } + + $aliases .= "{$aliased['name']} = \"<{$aliased['name']}>\"\n"; + break; + case "openvpn": + $openvpncfg = array(); + if ($config['openvpn']['user']) { + /* XXX: Check if we have a correct ip? */ + foreach ($config['openvpn']['user'] as $openvpn) { + $openvpncfg[$openvpn['name']] = $openvpn['ip']; + } + } + $vpn_lines = explode("\n", $addrlist); + foreach ($vpn_lines as $vpn_line) { + $vpn_address_split = explode(" ", $vpn_line); + foreach ($vpn_address_split as $vpnsplit) { + if (isset($openvpncfg[$vpnsplit])) { + $newaddress .= " "; + $newaddress .= $openvpn[$vpnsplit]; + break; + } + } + } + $aliases .= "table <{$aliased['name']}> { {$newaddress}{$extralias} } \n"; + $aliases .= "{$aliased['name']} = \"<{$aliased['name']}>\"\n"; + break; + case "urltable": + $urlfn = alias_expand_urltable($aliased['name']); + if ($urlfn) { + $aliases .= "table <{$aliased['name']}> persist file \"{$urlfn}\"\n"; + $aliases .= "{$aliased['name']} = \"<{$aliased['name']}>\"\n"; + } + break; + case "urltable_ports": + // TODO: Change it when pf supports tables with ports + $urlfn = alias_expand_urltable($aliased['name']); + if ($urlfn) { + $aliases .= "{$aliased['name']} = \"{ " . preg_replace("/\n/", " ", file_get_contents($urlfn)) . " }\"\n"; + } + break; + case "port": + case "url_ports": + $aliases .= "{$aliased['name']} = \"{ {$addrlist} }\"\n"; + break; + default: + $aliases .= "{$aliased['name']} = \"{ {$aliased['address']}{$extralias} }\"\n"; + break; + } + } + } + $result = "{$alias} \n"; + $result .= "{$aliases}"; + + return $result; +} + +function filter_generate_gateways() { + global $config, $g, $GatewaysList; + + $rules = "# Gateways\n"; + + update_filter_reload_status(gettext("Creating gateway group item...")); + + /* Lookup Gateways to be used in filter rules once */ + $GatewaysList = return_gateways_array(); + $GatewayGroupsList = return_gateway_groups_array(); + + if (is_array($GatewaysList)) { + foreach ($GatewaysList as $gwname => $gateway) { + $int = $gateway['interface']; + $gwip = $gateway['gateway']; + $route = ""; + if (!is_ipaddr($gwip)) { + $gwip = get_interface_gateway($gateway['friendlyiface']); + } + if (is_ipaddr($gwip) && !empty($int) && !isset($gateway['force_down'])) { + $route = "route-to ( {$int} {$gwip} )"; + } + if (($route === "") && isset($config['system']['skip_rules_gw_down'])) { + unset($GatewaysList[$gwname]); + } else { + $rules .= "GW{$gwname} = \" {$route} \"\n"; + } + } + } + + if (is_array($GatewayGroupsList)) { + foreach ($GatewayGroupsList as $gateway => $members) { + $route = ""; + /* hey, that's not a group member! */ + unset($members['ipprotocol']); + if (count($members) > 0) { + $foundlb = 0; + $routeto = ""; + foreach ($members as $idx => $member) { + $int = $member['int']; + $gatewayip = $member['gwip']; + if (($int <> "") && is_ipaddr($gatewayip)) { + if ($g['debug']) { + log_error(sprintf(gettext('Setting up route with %1$s on %2$s'), $gatewayip, $int)); + } + if ($member['weight'] > 1) { + $routeto .= str_repeat("( {$int} {$gatewayip} ) ", $member['weight']); + } else { + $routeto .= "( {$int} {$gatewayip} ) "; + } + $foundlb++; + } else { + log_error(sprintf(gettext("An error occurred while trying to find the interface got %s . The rule has not been added."), $gatewayip)); + } + } + $route = ""; + if ($foundlb > 0) { + $route = " route-to { {$routeto} } "; + if ($foundlb > 1) { + $route .= " round-robin "; + if (isset($config['system']['lb_use_sticky'])) { + $route .= " sticky-address "; + } + } + } + } + if (($route === "") && isset($config['system']['skip_rules_gw_down'])) { + unset($GatewayGroupsList[$gateway]); + } else { + $rules .= "GW{$gateway} = \" {$route} \"\n"; + } + } + } + + /* Create a global array to avoid errors on rulesets. */ + $GatewaysList = $GatewaysList + $GatewayGroupsList; + + $rules .= "\n"; + + return $rules; +} + +/* returns space separated list of vpn subnets */ +function filter_get_vpns_list() { + global $config; + + $vpns = ""; + $vpns_arr = array(); + + /* ipsec */ + if (isset($config['ipsec']['enable'])) { + if (is_array($config['ipsec']['phase2'])) { + foreach ($config['ipsec']['phase2'] as $ph2ent) { + if ((!$ph2ent['mobile']) && ($ph2ent['mode'] != 'transport')) { + if (!function_exists('ipsec_idinfo_to_cidr')) { + require_once("ipsec.inc"); + } + if (!is_array($ph2ent['remoteid'])) { + continue; + } + $ph2ent['remoteid']['mode'] = $ph2ent['mode']; + $vpns_subnet = ipsec_idinfo_to_cidr($ph2ent['remoteid']); + if (!is_subnet($vpns_subnet) || $vpns_subnet == "0.0.0.0/0") { + continue; + } + $vpns_arr[] = $vpns_subnet; + } + } + } + } + + /* openvpn */ + foreach (array('client', 'server') as $type) { + if (is_array($config['openvpn']["openvpn-$type"])) { + foreach ($config['openvpn']["openvpn-$type"] as $settings) { + if (is_array($settings)) { + if (!isset($settings['disable'])) { + $remote_networks = explode(',', $settings['remote_network']); + foreach ($remote_networks as $remote_network) { + if (is_subnet($remote_network) && ($remote_network <> "0.0.0.0/0")) { + $vpns_arr[] = $remote_network; + } + } + if (is_subnet($settings['tunnel_network']) && $settings['tunnel_network'] <> "0.0.0.0/0") { + $vpns_arr[] = $settings['tunnel_network']; + } + } + } + } + } + } + /* pppoe */ + if (is_array($config['pppoes']['pppoe'])) { + foreach ($config['pppoes']['pppoe'] as $pppoe) { + if ($pppoe['mode'] == "server") { + if (is_ipaddr($pppoe['remoteip'])) { + $pppoesub = gen_subnet($pppoe['remoteip'], $pppoe['pppoe_subnet']); + if (is_subnet($pppoesub)) { + $vpns_arr[] = $pppoesub; + } + } + } + } + } + + if (!empty($vpns_arr)) { + $vpns = implode(" ", $vpns_arr); + } + + return $vpns; +} + +/* returns space separated list of directly connected networks + * optionally returns an array instead, including friendly interface and gateway (if applicable) + */ +function filter_get_direct_networks_list($returnsubnetsonly = true) { + global $config, $FilterIflist, $GatewaysList; + /* build list of directly connected interfaces and networks */ + $networks = ""; + $networks_arr = array(); + if (empty($FilterIflist)) { + filter_generate_optcfg_array(); + } + foreach ($FilterIflist as $ifent => $ifcfg) { + $subnet = "{$ifcfg['sa']}/{$ifcfg['sn']}"; + if (is_subnet($subnet)) { + if ($returnsubnetsonly) { + $networks_arr[] = $subnet; + } else { + $networks_arr[] = array( + 'subnet' => $subnet, + 'if' => $ifent, + 'ip' => $ifcfg['ip']); + } + } + } + foreach (get_configured_ip_aliases_list(true) as $vip) { + $subnet = "{$vip['subnet']}/{$vip['subnet_bits']}"; + if (is_subnet($subnet) && !(is_subnetv4($subnet) && $vip['subnet_bits'] == 32) && !(is_subnetv6($subnet) && $vip['subnet_bits'] == 128)) { + if (is_subnetv4($subnet)) { + $subnet = gen_subnet($vip['subnet'], $vip['subnet_bits']) . "/{$vip['subnet_bits']}"; + } else if (is_subnetv6($subnet)) { + $subnet = gen_subnetv6($vip['subnet'], $vip['subnet_bits']) . "/{$vip['subnet_bits']}"; + } + if ($returnsubnetsonly) { + $networks_arr[] = $subnet; + } else { + $networks_arr[] = array( + 'subnet' => $subnet, + 'if' => $vip['interface'], + 'ip' => $vip['subnet']); + } + } + } + foreach (get_staticroutes() as $netent) { + if (is_subnet($netent['network'])) { + if ($returnsubnetsonly) { + $networks_arr[] = $netent['network']; + } else if (isset($GatewaysList[$netent['gateway']])) { + $networks_arr[] = array( + 'subnet' => $netent['network'], + 'if' => $GatewaysList[$netent['gateway']]['friendlyiface'], + 'gateway' => $GatewaysList[$netent['gateway']]['gateway']); + } + } + } + if ($returnsubnetsonly) { + if (!empty($networks_arr)) { + $networks = implode(" ", $networks_arr); + } + return $networks; + } else { + return $networks_arr; + } +} + +function filter_generate_optcfg_array() { + global $config, $FilterIflist; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "filter_generate_optcfg_array() being called $mt\n"; + } + + read_layer7_config(); + /* if list */ + $iflist = get_configured_interface_with_descr(); + foreach ($iflist as $if => $ifdetail) { + $oc = $config['interfaces'][$if]; + $oic = array(); + $oic['if'] = get_real_interface($if); + if (!does_interface_exist($oic['if'])) { + continue; + } + $oic['ifv6'] = get_real_interface($if, "inet6"); + $oic['ip'] = get_interface_ip($if); + $oic['ipv6'] = get_interface_ipv6($if); + if (!is_ipaddrv4($oc['ipaddr']) && !empty($oc['ipaddr'])) { + $oic['type'] = $oc['ipaddr']; + } + if (!is_ipaddrv6($oc['ipaddrv6']) && !empty($oc['ipaddrv6'])) { + $oic['type6'] = $oc['ipaddrv6']; + } + if (!empty($oc['track6-interface'])) { + $oic['track6-interface'] = $oc['track6-interface']; + } + $oic['sn'] = get_interface_subnet($if); + $oic['snv6'] = get_interface_subnetv6($if); + $oic['mtu'] = empty($oc['mtu']) ? 1500 : $oc['mtu']; + $oic['mss'] = empty($oc['mss']) ? '' : $oc['mss']; + $oic['descr'] = $ifdetail; + $oic['sa'] = gen_subnet($oic['ip'], $oic['sn']); + $oic['sav6'] = gen_subnetv6($oic['ipv6'], $oic['snv6']); + $oic['nonat'] = $oc['nonat']; + $oic['alias-address'] = $oc['alias-address']; + $oic['alias-subnet'] = $oc['alias-subnet']; + $oic['gateway'] = $oc['gateway']; + $oic['gatewayv6'] = $oc['gatewayv6']; + $oic['spoofcheck'] = "yes"; + $oic['bridge'] = link_interface_to_bridge($if); + $vips = link_interface_to_vips($if); + if (!empty($vips)) { + foreach ($vips as $vipidx => $vip) { + if (is_ipaddrv4($vip['subnet'])) { + if (!is_array($oic['vips'])) { + $oic['vips'] = array(); + } + $oic['vips'][$vipidx]['ip'] = $vip['subnet']; + if (empty($vip['subnet_bits'])) { + $oic['vips'][$vipidx]['sn'] = 32; + } else { + $oic['vips'][$vipidx]['sn'] = $vip['subnet_bits']; + } + } else if (is_ipaddrv6($vip['subnet'])) { + if (!is_array($oic['vips6'])) { + $oic['vips6'] = array(); + } + $oic['vips6'][$vipidx]['ip'] = $vip['subnet']; + if (empty($vip['subnet_bits'])) { + $oic['vips6'][$vipidx]['sn'] = 128; + } else { + $oic['vips6'][$vipidx]['sn'] = $vip['subnet_bits']; + } + } + } + } + unset($vips); + $FilterIflist[$if] = $oic; + } + + if ($config['pptpd']['mode'] == "server" || $config['pptpd']['mode'] == "redir") { + $oic = array(); + $oic['if'] = 'pptp'; + $oic['descr'] = 'pptp'; + $oic['ip'] = $config['pptpd']['localip']; + $oic['sa'] = $config['pptpd']['remoteip']; + $oic['mode'] = $config['pptpd']['mode']; + $oic['virtual'] = true; + if ($config['pptpd']['pptp_subnet'] <> "") { + $oic['sn'] = $config['pptpd']['pptp_subnet']; + } else { + $oic['sn'] = "32"; + } + $FilterIflist['pptp'] = $oic; + } + if ($config['l2tp']['mode'] == "server") { + $oic = array(); + $oic['if'] = 'l2tp'; + $oic['descr'] = 'L2TP'; + $oic['ip'] = $config['l2tp']['localip']; + $oic['sa'] = $config['l2tp']['remoteip']; + if ($config['l2tp']['l2tp_subnet'] <> "") { + $oic['sn'] = $config['l2tp']['l2tp_subnet']; + } else { + $oic['sn'] = "32"; + } + $oic['mode'] = $config['l2tp']['mode']; + $oic['virtual'] = true; + $FilterIflist['l2tp'] = $oic; + } + if (is_array($config['pppoes']['pppoe']) && (count($config['pppoes']['pppoe']) > 0)) { + $pppoeifs = array(); + foreach ($config['pppoes']['pppoe'] as $pppoe) { + if ($pppoe['mode'] == "server") { + $oic = array(); + $oic['if'] = 'pppoe'; + $oic['descr'] = 'pppoe'; + $oic['ip'] = $pppoe['localip']; + $oic['sa'] = $pppoe['remoteip']; + $oic['mode'] = $pppoe['mode']; + $oic['virtual'] = true; + if ($pppoe['pppoe_subnet'] <> "") { + $oic['sn'] = $pppoe['pppoe_subnet']; + } else { + $oic['sn'] = "32"; + } + $pppoeifs[] = $oic; + } + } + if (count($pppoeifs)) { + $FilterIflist['pppoe'] = $pppoeifs; + } + } + /* add ipsec interfaces */ + if (isset($config['ipsec']['enable']) || isset($config['ipsec']['client']['enable'])) { + $oic = array(); + $oic['if'] = 'enc0'; + $oic['descr'] = 'IPsec'; + $oic['type'] = "none"; + $oic['virtual'] = true; + $FilterIflist['enc0'] = $oic; + } + /* add openvpn interfaces */ + if ($config['openvpn']['openvpn-server'] || $config['openvpn']['openvpn-client']) { + $oic = array(); + $oic['if'] = "openvpn"; + $oic['descr'] = 'OpenVPN'; + $oic['type'] = "none"; + $oic['virtual'] = true; + $FilterIflist['openvpn'] = $oic; + } + /* add interface groups */ + if (is_array($config['ifgroups']['ifgroupentry'])) { + foreach ($config['ifgroups']['ifgroupentry'] as $ifgen) { + $oc = array(); + $oc['if'] = $ifgen['ifname']; + $oc['descr'] = $ifgen['ifname']; + $oc['virtual'] = true; + $FilterIflist[$ifgen['ifname']] = $oc; + } + } +} + +function filter_flush_nat_table() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "filter_flush_nat_table() being called $mt\n"; + } + return mwexec("/sbin/pfctl -F nat"); +} + +function filter_flush_state_table() { + return mwexec("/sbin/pfctl -F state"); +} + +function filter_get_reflection_interfaces($natif = "") { + global $FilterIflist; + + $nat_if_list = array(); + + foreach ($FilterIflist as $ifent => $ifname) { + if ($ifname['if'] == $natif) { + continue; + } + + /* Do not add reflection redirects for interfaces with gateways */ + if (interface_has_gateway($ifent)) { + continue; + } + + $nat_if_list[] = $ifname['if']; + } + + return $nat_if_list; +} + +function filter_generate_reflection_nat($rule, &$route_table, $nat_ifs, $protocol, $target, $target_ip, $target_subnet = "") { + global $config, $FilterIflist; + + if (!isset($config['system']['enablenatreflectionhelper'])) { + return ""; + } + + // Initialize natrules holder string + $natrules = ""; + + update_filter_reload_status(sprintf(gettext("Creating reflection NAT rule for %s..."), $rule['descr'])); + + /* TODO: Add this option to port forwards page. */ + if (isset($rule['staticnatport'])) { + $static_port = " static-port"; + } else { + $static_port = " port 1024:65535"; + } + + if (!empty($protocol)) { + $protocol_text = " proto {$protocol}"; + } else { + $protocol_text = ""; + } + + if (empty($target_subnet) || !is_numeric($target_subnet)) { + $target_subnet = 32; + } + + if (!is_array($route_table)) { + /* get a simulated IPv4-only route table based on the config */ + $route_table = filter_get_direct_networks_list(false); + foreach ($route_table as $rt_key => $rt_ent) { + if (!is_subnetv4($rt_ent['subnet'])) { + unset($route_table[$rt_key]); + } + if (isset($route_table[$rt_key]) && isset($FilterIflist[$rt_ent['if']]['if'])) { + $route_table[$rt_key]['if'] = $FilterIflist[$rt_ent['if']]['if']; + } + } + } + + /* Check if the target is accessed through a static route */ + foreach ($route_table as $route) { + if (isset($route['gateway']) && is_ipaddr($route['gateway'])) { + $subnet_split = explode("/", $route['subnet']); + if (in_array($route['if'], $nat_ifs) && check_subnets_overlap($target_ip, $target_subnet, $subnet_split[0], $subnet_split[1])) { + $target_ip = $route['gateway']; + $target_subnet = 32; + break; + } + } + } + + /* Search for matching subnets in the routing table */ + foreach ($route_table as $route) { + $subnet = $route['subnet']; + $subnet_split = explode("/", $subnet); + $subnet_if = $route['if']; + /* Blacklist invalid "from" sources since they can be picked up accidentally and cause rule errors. */ + $no_reflect_from = array("l2tp"); + if (in_array($subnet_if, $nat_ifs) && check_subnets_overlap($target_ip, $target_subnet, $subnet_split[0], $subnet_split[1])) { + $ifsubnet_ip = ""; + /* Find interface IP to use for NAT */ + foreach ($route_table as $ifnetwork) { + if (isset($ifnetwork['ip']) && is_ipaddr($ifnetwork['ip']) && $ifnetwork['if'] == $subnet_if && ip_in_subnet($ifnetwork['ip'], $subnet)) { + $ifsubnet_ip = $ifnetwork['ip']; + break; + } + } + if(!empty($ifsubnet_ip) && !in_array($subnet, $no_reflect_from)) { + $subnets = array($subnet); + /* Find static routes that also need to be referenced in the NAT rule */ + foreach ($route_table as $rtentry) { + if (isset($rtentry['gateway']) && is_ipaddr($rtentry['gateway']) && $rtentry['if'] == $subnet_if && ip_in_subnet($rtentry['gateway'], $subnet)) { + $subnets[] = $rtentry['subnet']; + } + } + if (count($subnets) > 1) { + $subnet = "{ " . implode(" ", $subnets) . " }"; + } + $natrules .= "no nat on {$subnet_if}{$protocol_text} from {$subnet_if} to {$target}\n"; + $natrules .= "nat on {$subnet_if}{$protocol_text} from {$subnet} to {$target} -> {$ifsubnet_ip}{$static_port}\n"; + } + } + } + + if (!empty($natrules)) { + $natrules .= "\n"; + } + + return $natrules; +} + +function filter_generate_reflection_proxy($rule, $nordr, $rdr_ifs, $srcaddr, $dstaddr_port, &$starting_localhost_port, &$reflection_txt) { + global $FilterIflist, $config; + + // Initialize natrules holder string + $natrules = ""; + $reflection_txt = array(); + + if (!empty($rdr_ifs)) { + if ($config['system']['reflectiontimeout']) { + $reflectiontimeout = $config['system']['reflectiontimeout']; + } else { + $reflectiontimeout = "2000"; + } + + update_filter_reload_status(sprintf(gettext("Creating reflection rule for %s..."), $rule['descr'])); + + $rdr_if_list = implode(" ", $rdr_ifs); + if (count($rdr_ifs) > 1) { + $rdr_if_list = "{ {$rdr_if_list} }"; + } + + $natrules .= "\n# Reflection redirects\n"; + + $localport = $rule['local-port']; + if (!empty($localport) && is_alias($localport)) { + $localport = filter_expand_alias($localport); + $localport = explode(" ", trim($localport)); + // The translation port for rdr, when specified, does not support more than one port or range. + // Emulating for behavior consistent with the original port forward. + $localport = $localport[0]; + } + + if (is_alias($rule['destination']['port'])) { + if (empty($localport) || $rule['destination']['port'] == $rule['local-port']) { + $dstport = filter_expand_alias($rule['destination']['port']); + $dstport = array_filter(explode(" ", trim($dstport))); + $localport = ""; + } else if (!empty($localport)) { + $dstport = array($localport); + } + } else { + $dstport = array(str_replace("-", ":", $rule['destination']['port'])); + $dstport_split = explode(":", $dstport[0]); + + if (!empty($localport) && $dstport_split[0] != $rule['local-port']) { + if (!is_alias($rule['local-port']) && $dstport_split[1] && $dstport_split[0] != $dstport_split[1]) { + $localendport = $localport + ($dstport_split[1] - $dstport_split[0]); + $localport .= ":$localendport"; + } + + $dstport = array($localport); + } else { + $localport = ""; + } + } + + $dstaddr = explode(" ", $dstaddr_port); + if ($dstaddr[2]) { + $rflctintrange = array_pop($dstaddr); + array_pop($dstaddr); + } else { + return ""; + } + $dstaddr = implode(" ", $dstaddr); + if (empty($dstaddr) || trim($dstaddr) == "0.0.0.0" || strtolower(trim($dstaddr)) == "port") { + return ""; + } + + if (isset($rule['destination']['any'])) { + if (!$rule['interface']) { + $natif = "wan"; + } else { + $natif = $rule['interface']; + } + + if (!isset($FilterIflist[$natif])) { + return ""; + } + if (is_ipaddr($FilterIflist[$natif]['ip'])) { + $dstaddr = $FilterIflist[$natif]['ip']; + } else { + return ""; + } + + if (!empty($FilterIflist[$natif]['sn'])) { + $dstaddr = gen_subnet($dstaddr, $FilterIflist[$natif]['sn']) . '/' . $FilterIflist[$natif]['sn']; + } + } + + switch ($rule['protocol']) { + case "tcp/udp": + $protocol = "{ tcp udp }"; + $reflect_protos = array('tcp', 'udp'); + break; + case "tcp": + case "udp": + $protocol = $rule['protocol']; + $reflect_protos = array($rule['protocol']); + break; + default: + return ""; + break; + } + + if (!empty($nordr)) { + $natrules .= "no rdr on {$rdr_if_list} proto {$protocol} from {$srcaddr} to {$dstaddr} port {$rflctintrange}\n"; + return $natrules; + } + + if (is_alias($rule['target'])) { + $target = filter_expand_alias($rule['target']); + } else if (is_ipaddr($rule['target'])) { + $target = $rule['target']; + } else if (is_ipaddr($FilterIflist[$rule['target']]['ip'])) { + $target = $FilterIflist[$rule['target']]['ip']; + } else { + return ""; + } + $starting_localhost_port_tmp = $starting_localhost_port; + $toomanyports = false; + /* only install reflection rules for < 19991 items */ + foreach ($dstport as $loc_pt) { + if ($starting_localhost_port < 19991) { + $toadd_array = array(); + $inetdport = $starting_localhost_port; + $rflctrange = $starting_localhost_port; + + $loc_pt = explode(":", $loc_pt); + if ($loc_pt[1] && $loc_pt[1] > $loc_pt[0]) { + $delta = $loc_pt[1] - $loc_pt[0]; + } else { + $delta = 0; + } + + if (($inetdport + $delta + 1) - $starting_localhost_port_tmp > 500) { + log_error("Not installing NAT reflection rules for a port range > 500"); + $inetdport = $starting_localhost_port; + $toadd_array = array(); + $toomanyports = true; + break; + } else if (($inetdport + $delta) > 19990) { + log_error("Installing partial NAT reflection rules. Maximum 1,000 reached."); + $delta = 19990 - $inetdport; + $loc_pt[1] = $loc_pt[0] + $delta; + if ($delta == 0) { + unset($loc_pt[1]); + } + $toomanyports = true; + + if (!empty($localport)) { + if (is_alias($rule['destination']['port'])) { + $rflctintrange = alias_expand($rule['destination']['port']); + } else { + if ($dstport_split[1]) { + $dstport_split[1] = $dstport_split[0] + $inetdport + $delta - $starting_localhost_port; + } + $rflctintrange = implode(":", $dstport_split); + } + } + } + + if (empty($localport)) { + $rflctintrange = implode(":", $loc_pt); + } + if ($inetdport + $delta > $starting_localhost_port) { + $rflctrange .= ":" . ($inetdport + $delta); + } + $starting_localhost_port = $inetdport + $delta + 1; + $toadd_array = array_merge($toadd_array, range($loc_pt[0], $loc_pt[0] + $delta)); + + if (!empty($toadd_array)) { + $rtarget = explode(" ", trim($target)); + foreach ($toadd_array as $tda) { + if (empty($tda)) { + continue; + } + foreach ($reflect_protos as $reflect_proto) { + if ($reflect_proto == "udp") { + $socktype = "dgram"; + $dash_u = "-u "; + $wait = "wait\t"; + } else { + $socktype = "stream"; + $dash_u = ""; + $wait = "nowait/0"; + } + foreach ($rtarget as $targip) { + if (empty($targip)) { + continue; + } + $reflection_txt[] = "{$inetdport}\t{$socktype}\t{$reflect_proto}\t{$wait}\tnobody\t/usr/bin/nc\tnc {$dash_u}-w {$reflectiontimeout} {$targip} {$tda}\n"; + } + } + $inetdport++; + } + $natrules .= "rdr on {$rdr_if_list} proto {$protocol} from {$srcaddr} to {$dstaddr} port {$rflctintrange} tag PFREFLECT -> 127.0.0.1 port {$rflctrange}\n"; + } + } + + if ($toomanyports) { + break; + } + } + + $reflection_txt = array_unique($reflection_txt); + } + + return $natrules; +} + +function filter_nat_rules_automatic_tonathosts($with_descr = false) { + global $config, $FilterIflist, $GatewaysList; + + $tonathosts = array("127.0.0.0/8"); + $descriptions = array(gettext("localhost")); + + foreach (get_staticroutes() as $route) { + $netip = explode("/", $route['network']); + if (isset($GatewaysList[$route['gateway']])) { + $gateway =& $GatewaysList[$route['gateway']]; + if (!interface_has_gateway($gateway['interface']) && is_private_ip($netip[0])) { + $tonathosts[] = $route['network']; + $descriptions[] = gettext("static route"); + } + } + } + + /* create outbound nat entries for all local networks */ + foreach ($FilterIflist as $ocname => $oc) { + if (interface_has_gateway($ocname)) { + continue; + } + if (is_ipaddr($oc['alias-address'])) { + $tonathosts[] = "{$oc['alias-address']}/{$oc['alias-subnet']}"; + $descriptions[] = $oc['descr'] . " " . gettext("DHCP alias address"); + } + if ($oc['sa']) { + $tonathosts[] = "{$oc['sa']}/{$oc['sn']}"; + $descriptions[] = $oc['descr']; + if (isset($oc['vips']) && is_array($oc['vips'])) { + $if_subnets = array("{$oc['sa']}/{$oc['sn']}"); + foreach ($oc['vips'] as $vip) { + if (!is_ipaddrv4($vip['ip'])) { + continue; + } + + foreach ($if_subnets as $subnet) { + if (ip_in_subnet($vip['ip'], $subnet)) { + continue 2; + } + } + + $network = gen_subnet($vip['ip'], $vip['sn']); + array_unshift($tonathosts, $network . '/' . $vip['sn']); + array_unshift($descriptions, "Virtual IP ({$oc['descr']})"); + $if_subnets[] = $network . '/' . $vip['sn']; + unset($network); + } + unset($if_subnets); + } + } + } + + /* PPTP subnet */ + if (($config['pptpd']['mode'] == "server") && is_private_ip($config['pptpd']['remoteip'])) { + if (isset($config['pptpd']['n_pptp_units']) && is_numeric($config['pptpd']['n_pptp_units'])) { + $pptp_subnets = ip_range_to_subnet_array($config['pptpd']['remoteip'], + long2ip32(ip2long($config['pptpd']['remoteip'])+($config['pptpd']['n_pptp_units']-1))); + } else { + $pptp_subnets = ip_range_to_subnet_array($config['pptpd']['remoteip'], + long2ip32(ip2long($config['pptpd']['remoteip']))); + } + + foreach ($pptp_subnets as $subnet) { + $tonathosts[] = $subnet; + $descriptions[] = gettext("PPTP server"); + } + } + + /* PPPoE subnet */ + if (is_array($FilterIflist['pppoe'])) { + foreach ($FilterIflist['pppoe'] as $pppoe) { + if (is_private_ip($pppoe['ip'])) { + $tonathosts[] = "{$pppoe['sa']}/{$pppoe['sn']}"; + $descriptions[] = gettext("PPPoE server"); + } + } + } + + /* L2TP subnet */ + if (isset($FilterIflist['l2tp']) && $FilterIflist['l2tp']['mode'] == "server") { + $l2tp_sa = $FilterIflist['l2tp']['sa']; + $l2tp_sn = $FilterIflist['l2tp']['sn']; + if (is_private_ip($l2tp_sa) && !empty($l2tp_sn)) { + $tonathosts[] = "{$l2tp_sa}/{$l2tp_sn}"; + $descriptions[] = gettext("L2TP server"); + } + } + + /* add openvpn interfaces */ + if (is_array($config['openvpn']['openvpn-server'])) { + foreach ($config['openvpn']['openvpn-server'] as $ovpnsrv) { + if (!isset($ovpnsrv['disable']) && !empty($ovpnsrv['tunnel_network'])) { + $tonathosts[] = $ovpnsrv['tunnel_network']; + $descriptions[] = gettext("OpenVPN server"); + } + } + } + + if (is_array($config['openvpn']['openvpn-client'])) { + foreach ($config['openvpn']['openvpn-client'] as $ovpncli) { + if (!isset($ovpncli['disable']) && !empty($ovpncli['tunnel_network'])) { + $tonathosts[] = $ovpncli['tunnel_network']; + $descriptions[] = gettext("OpenVPN client"); + } + } + } + + /* IPsec mode_cfg subnet */ + if ((isset($config['ipsec']['client']['enable'])) && + (!empty($config['ipsec']['client']['pool_address'])) && + (!empty($config['ipsec']['client']['pool_netbits']))) { + $tonathosts[] = "{$config['ipsec']['client']['pool_address']}/{$config['ipsec']['client']['pool_netbits']}"; + $descriptions[] = gettext("IPsec client"); + } + + if ($with_descr) { + $combined = array(); + foreach ($tonathosts as $idx => $subnet) { + $combined[] = array( + "subnet" => $subnet, + "descr" => $descriptions[$idx]); + } + + return $combined; + } else { + return $tonathosts; + } +} + +function filter_nat_rules_outbound_automatic($src) { + global $config, $FilterIflist; + + $rules = array(); + foreach ($FilterIflist as $if => $ifcfg) { + if (substr($ifcfg['if'], 0, 4) == "ovpn") { + continue; + } + if (!interface_has_gateway($if)) { + continue; + } + + $natent = array(); + $natent['interface'] = $if; + $natent['source']['network'] = $src; + $natent['dstport'] = "500"; + $natent['target'] = ""; + $natent['destination']['any'] = true; + $natent['staticnatport'] = true; + $natent['descr'] = gettext('Auto created rule for ISAKMP'); + $rules[] = $natent; + + $natent = array(); + $natent['interface'] = $if; + $natent['source']['network'] = $src; + $natent['sourceport'] = ""; + $natent['target'] = ""; + $natent['destination']['any'] = true; + $natent['natport'] = ""; + $natent['descr'] = gettext('Auto created rule'); + if (isset($ifcfg['nonat'])) { + $natent['nonat'] = true; + } + $rules[] = $natent; + } + + return $rules; +} + +/* Generate a 'nat on' or 'no nat on' rule for given interface */ +function filter_nat_rules_generate_if ($if, $src = "any", $srcport = "", $dst = "any", $dstport = "", $natip = "", $natport = "", $nonat = false, $staticnatport = false, $proto = "", $poolopts = "") { + global $config, $FilterIflist; + /* XXX: billm - any idea if this code is needed? */ + if ($src == "/32" || $src{0} == "/") { + return "# src incorrectly specified\n"; + } + if ($natip != "") { + if (is_subnet($natip)) { + $tgt = $natip; + } elseif (is_alias($natip)) { + $tgt = "\${$natip}"; + } else { + $tgt = "{$natip}/32"; + } + } else { + $natip = get_interface_ip($if); + if (is_ipaddr($natip)) { + $tgt = "{$natip}/32"; + } else { + $tgt = "(" . $FilterIflist[$if]['if'] . ")"; + } + } + /* Add the protocol, if defined */ + if (!empty($proto) && $proto != "any") { + if ($proto == "tcp/udp") { + $protocol = " proto { tcp udp }"; + } else { + $protocol = " proto {$proto}"; + } + } else { + $protocol = ""; + } + /* Set tgt for IPv6 */ + if ($proto == "ipv6") { + $natip = get_interface_ipv6($if); + if (is_ipaddrv6($natip)) { + $tgt = "{$natip}/128"; + } + } + /* Add the hard set source port (useful for ISAKMP) */ + if ($natport != "") { + $tgt .= " port {$natport}"; + } + /* sometimes this gets called with "" instead of a value */ + if ($src == "") { + $src = "any"; + } + /* Match on this source port */ + if ($srcport != "") { + $srcportexpand = alias_expand($srcport); + if (!$srcportexpand) { + $srcportexpand = $srcport; + } + $src .= " port {$srcportexpand}"; + } + /* sometimes this gets called with "" instead of a value */ + if ($dst == "") { + $dst = "any"; + } + /* Match on this dest port */ + if ($dstport != "") { + $dstportexpand = alias_expand($dstport); + if (!$dstportexpand) { + $dstportexpand = $dstport; + } + $dst .= " port {$dstportexpand}"; + } + /* outgoing static-port option, hamachi, Grandstream, VOIP, etc */ + $staticnatport_txt = ""; + if ($staticnatport) { + $staticnatport_txt = "static-port"; + } elseif (!$natport) { + $tgt .= " port 1024:65535"; // set source port range + } + /* Allow for negating NAT entries */ + if ($nonat) { + $nat = "no nat"; + $target = ""; + $staticnatport_txt = ""; + $poolopts = ""; + } else { + $nat = "nat"; + $target = "-> {$tgt}"; + } + $if_friendly = $FilterIflist[$if]['descr']; + /* Put all the pieces together */ + if ($if_friendly) { + $natrule = "{$nat} on \${$if_friendly} {$protocol} from {$src} to {$dst} {$target} {$poolopts} {$staticnatport_txt}\n"; + } else { + $natrule .= "# Could not convert {$if} to friendly name(alias)\n"; + } + return $natrule; +} + +function filter_nat_rules_generate() { + global $config, $g, $after_filter_configure_run, $FilterIflist, $GatewaysList, $aliases; + + $natrules = "no nat proto carp\n"; + $natrules .= "no rdr proto carp\n"; + $natrules .= "nat-anchor \"natearly/*\"\n"; + + $natrules .= "nat-anchor \"natrules/*\"\n\n"; + update_filter_reload_status(gettext("Creating 1:1 rules...")); + + $reflection_txt = ""; + $route_table = ""; + + /* any 1:1 mappings? */ + if (is_array($config['nat']['onetoone'])) { + foreach ($config['nat']['onetoone'] as $rule) { + if (isset($rule['disabled'])) { + continue; + } + + $sn = ""; + $sn1 = ""; + $target = alias_expand($rule['external']); + if (!$target) { + $natrules .= "# Unresolvable alias {$rule['target']}\n"; + continue; /* unresolvable alias */ + } + + if (!$rule['interface']) { + $natif = "wan"; + } else { + $natif = $rule['interface']; + } + if (!isset($FilterIflist[$natif])) { + continue; + } + + $srcaddr = filter_generate_address($rule, 'source'); + $dstaddr = filter_generate_address($rule, 'destination'); + if (!$dstaddr) { + $dstaddr = $FilterIflist[$natif]['ip']; + } + + $srcaddr = trim($srcaddr); + $dstaddr = trim($dstaddr); + + $tmp = explode('/', $srcaddr); + $srcip = $tmp[0]; + if (!empty($tmp[1]) && is_numeric($tmp[1])) { + $sn = $tmp[1]; + $sn1 = "/{$sn}"; + } + + $natif = $FilterIflist[$natif]['if']; + + /* + * If reflection is enabled, turn on extra redirections + * for this rule by adding other interfaces to an rdr rule. + */ + if ((isset($config['system']['enablebinatreflection']) || $rule['natreflection'] == "enable") && + ($rule['natreflection'] != "disable")) { + $nat_if_list = filter_get_reflection_interfaces($natif); + } else { + $nat_if_list = array(); + } + + $natrules .= "binat on {$natif} from {$srcaddr} to {$dstaddr} -> {$target}{$sn1}\n"; + if (!empty($nat_if_list)) { + $binat_if_list = implode(" ", $nat_if_list); + $binat_if_list = "{ {$binat_if_list} }"; + $reflection_txt .= "rdr on {$binat_if_list} from {$dstaddr} to {$target}{$sn1} -> {$srcaddr} bitmask\n"; + } + + $nat_if_list = array_merge(array($natif), $nat_if_list); + $reflection_txt .= filter_generate_reflection_nat($rule, $route_table, $nat_if_list, "", $srcaddr, $srcip, $sn); + } + } + + /* Add binat rules for Network Prefix translation */ + if (is_array($config['nat']['npt'])) { + foreach ($config['nat']['npt'] as $rule) { + if (isset($rule['disabled'])) { + continue; + } + + if (!$rule['interface']) { + $natif = "wan"; + } else { + $natif = $rule['interface']; + } + if (!isset($FilterIflist[$natif])) { + continue; + } + + $srcaddr = filter_generate_address($rule, 'source'); + $dstaddr = filter_generate_address($rule, 'destination'); + + $srcaddr = trim($srcaddr); + $dstaddr = trim($dstaddr); + + $natif = $FilterIflist[$natif]['descr']; + + $natrules .= "binat on \${$natif} from {$srcaddr} to any -> {$dstaddr}\n"; + $natrules .= "binat on \${$natif} from any to {$dstaddr} -> {$srcaddr}\n"; + + } + } + + /* ipsec nat */ + if (is_array($config['ipsec']) && isset($config['ipsec']['enable'])) { + if (is_array($config['ipsec']['phase2'])) { + foreach ($config['ipsec']['phase2'] as $ph2ent) { + if ($ph2ent['mode'] != 'transport' && !empty($ph2ent['natlocalid'])) { + if (!function_exists('ipsec_idinfo_to_cidr')) { + require_once("ipsec.inc"); + } + if (!is_array($ph2ent['localid'])) { + $ph2ent['localid'] = array(); + } + $ph2ent['localid']['mode'] = $ph2ent['mode']; + $local_subnet = ipsec_idinfo_to_cidr($ph2ent['localid']); + if (empty($local_subnet) || $local_subnet == "0.0.0.0/0") { + continue; + } + if (!is_subnet($local_subnet) && !is_ipaddr($local_subnet)) { + continue; + } + if (!is_array($ph2ent['natlocalid'])) { + $ph2ent['natlocalid'] = array(); + } + $ph2ent['natlocalid']['mode'] = $ph2ent['mode']; + $natlocal_subnet = ipsec_idinfo_to_cidr($ph2ent['natlocalid']); + if (empty($natlocal_subnet) || $natlocal_subnet == "0.0.0.0/0") { + continue; + } + if (!is_subnet($natlocal_subnet) && !is_ipaddr($natlocal_subnet)) { + continue; + } + if (!is_array($ph2ent['remoteid'])) { + $ph2ent['remoteid'] = array(); + } + $ph2ent['remoteid']['mode'] = $ph2ent['mode']; + $remote_subnet = ipsec_idinfo_to_cidr($ph2ent['remoteid']); + if (empty($remote_subnet)) { + continue; + } + if (!is_subnet($remote_subnet) && !is_ipaddr($remote_subnet)) { + continue; + } + if ($remote_subnet == "0.0.0.0/0") { + $remote_subnet = "any"; + } + if (is_ipaddr($natlocal_subnet) && !is_ipaddr($local_subnet)) { + $nattype = "nat"; + } else { + list($natnet, $natmask) = explode('/', $natlocal_subnet); + list($locnet, $locmask) = explode('/', $local_subnet); + if (intval($natmask) != intval($locmask)) { + $nattype = "nat"; + } else { + $nattype = "binat"; + } + unset($natnet, $natmask, $locnet, $locmask); + } + $natrules .= "{$nattype} on enc0 from {$local_subnet} to {$remote_subnet} -> {$natlocal_subnet}\n"; + } + } + } + } + + if ($config['nat']['outbound']['mode'] == "disabled") { + $natrules .= "\n# Outbound NAT rules are disabled\n"; + } + + if ($config['nat']['outbound']['mode'] == "advanced" || $config['nat']['outbound']['mode'] == "hybrid") { + $natrules .= "\n# Outbound NAT rules (manual)\n"; + /* advanced outbound rules */ + if (is_array($config['nat']['outbound']['rule'])) { + foreach ($config['nat']['outbound']['rule'] as $obent) { + if (isset($obent['disabled'])) { + continue; + } + update_filter_reload_status(sprintf(gettext("Creating advanced outbound rule %s"), $obent['descr'])); + $src = alias_expand($obent['source']['network']); + if (!$src) { + $src = $obent['source']['network']; + } + $dst = alias_expand($obent['destination']['address']); + if (!$dst) { + $dst = $obent['destination']['address']; + } + if (isset($obent['destination']['not']) && !isset($obent['destination']['any'])) { + $dst = "!" . $dst; + } + + if (!$obent['interface'] || !isset($FilterIflist[$obent['interface']])) { + continue; + } + + $obtarget = ($obent['target'] == "other-subnet") ? $obent['targetip'] . '/' . $obent['targetip_subnet']: $obent['target']; + $poolopts = (is_subnet($obtarget) || is_alias($obtarget)) ? $obent['poolopts'] : ""; + + $natrules .= filter_nat_rules_generate_if($obent['interface'], + $src, + $obent['sourceport'], + $dst, + $obent['dstport'], + $obtarget, + $obent['natport'], + isset($obent['nonat']), + isset($obent['staticnatport']), + $obent['protocol'], + $poolopts + ); + } + } + } + + /* outbound rules */ + if ((!isset($config['nat']['outbound']['mode'])) || + ($config['nat']['outbound']['mode'] == "automatic") || + ($config['nat']['outbound']['mode'] == "hybrid")) { + $natrules .= "\n# Outbound NAT rules (automatic)\n"; + /* standard outbound rules (one for each interface) */ + update_filter_reload_status(gettext("Creating outbound NAT rules")); + $tonathosts_array = filter_nat_rules_automatic_tonathosts(); + $tonathosts = implode(" ", $tonathosts_array); + $numberofnathosts = count($tonathosts_array); + + $natrules .= "\n# Subnets to NAT \n"; + if ($numberofnathosts > 0) { + update_filter_reload_status(gettext('Creating automatic outbound rules')); + + if ($numberofnathosts > 4) { + $natrules .= "table <tonatsubnets> { {$tonathosts} }\n"; + $macroortable = "<tonatsubnets>"; + } else { + $natrules .= "tonatsubnets = \"{ {$tonathosts} }\"\n"; + $macroortable = "\$tonatsubnets"; + } + + $a_outs = filter_nat_rules_outbound_automatic($macroortable); + foreach ($a_outs as $a_out) { + $natrules .= filter_nat_rules_generate_if($a_out['interface'], + $a_out['source']['network'], + $a_out['sourceport'], + $a_out['destination']['address'], + $a_out['dstport'], + $a_out['target'], + $a_out['natport'], + isset($a_out['nonat']), + isset($a_out['staticnatport'])); + } + } + unset($tonathosts, $tonathosts_array, $numberofnathosts); + } + + /* load balancer anchor */ + $natrules .= "\n# Load balancing anchor\n"; + $natrules .= "rdr-anchor \"relayd/*\"\n"; + + update_filter_reload_status(gettext("Setting up TFTP helper")); + $natrules .= "# TFTP proxy\n"; + $natrules .= "rdr-anchor \"tftp-proxy/*\"\n"; + + if (!empty($config['system']['tftpinterface'])) { + $tftpifs = explode(",", $config['system']['tftpinterface']); + foreach ($tftpifs as $tftpif) { + if ($FilterIflist[$tftpif]) { + $natrules .= "rdr pass on {$FilterIflist[$tftpif]['if']} proto udp from any to any port tftp -> 127.0.0.1 port 6969\n"; + } + } + } + + /* DIAG: add ipv6 NAT, if requested */ + if ((isset($config['diag']['ipv6nat']['enable'])) && + (is_ipaddr($config['diag']['ipv6nat']['ipaddr'])) && + (is_array($FilterIflist['wan']))) { + /* XXX: FIX ME! IPV6 */ + $natrules .= "rdr on \${$FilterIflist['wan']['descr']} proto ipv6 from any to any -> {$config['diag']['ipv6nat']['ipaddr']}\n"; + } + + if (file_exists("/var/etc/inetd.conf")) { + @unlink("/var/etc/inetd.conf"); + } + // Open inetd.conf write handle + $inetd_fd = fopen("/var/etc/inetd.conf", "w"); + /* add tftp protocol helper */ + fwrite($inetd_fd, "tftp-proxy\tdgram\tudp\twait\t\troot\t/usr/libexec/tftp-proxy\ttftp-proxy -v\n"); + + if (isset($config['nat']['rule'])) { + /* start reflection redirects on port 19000 of localhost */ + $starting_localhost_port = 19000; + $natrules .= "# NAT Inbound Redirects\n"; + foreach ($config['nat']['rule'] as $rule) { + update_filter_reload_status(sprintf(gettext("Creating NAT rule %s"), $rule['descr'])); + + if (isset($rule['disabled'])) { + continue; + } + + /* if item is an alias, expand */ + $dstport = ""; + $dstport[0] = alias_expand($rule['destination']['port']); + if (!$dstport[0]) { + $dstport = explode("-", $rule['destination']['port']); + } + + /* if item is an alias, expand */ + $localport = alias_expand($rule['local-port']); + if (!$localport || $dstport[0] == $localport) { + $localport = ""; + } else if (is_alias($rule['local-port'])) { + $localport = filter_expand_alias($rule['local-port']); + if ($localport) { + $localport = explode(" ", trim($localport)); + $localport = $localport[0]; + $localport = " port {$localport}"; + } + } else if (is_alias($rule['destination']['port'])) { + $localport = " port {$localport}"; + } else { + if (($dstport[1]) && ($dstport[0] != $dstport[1])) { + $localendport = $localport + ($dstport[1] - $dstport[0]); + + $localport .= ":$localendport"; + } + + $localport = " port {$localport}"; + } + + switch (strtolower($rule['protocol'])) { + case "tcp/udp": + $protocol = "{ tcp udp }"; + break; + case "tcp": + case "udp": + $protocol = strtolower($rule['protocol']); + break; + default: + $protocol = strtolower($rule['protocol']); + $localport = ""; + break; + } + + $target = alias_expand($rule['target']); + if (!$target && !isset($rule['nordr'])) { + $natrules .= "# Unresolvable alias {$rule['target']}\n"; + continue; /* unresolvable alias */ + } + + if (is_alias($rule['target'])) { + $target_ip = filter_expand_alias($rule['target']); + } else if (is_ipaddr($rule['target'])) { + $target_ip = $rule['target']; + } else if (is_ipaddr($FilterIflist[$rule['target']]['ip'])) { + $target_ip = $FilterIflist[$rule['target']]['ip']; + } else { + $target_ip = $rule['target']; + } + $target_ip = trim($target_ip); + + if ($rule['associated-rule-id'] == "pass") { + $rdrpass = "pass "; + } else { + $rdrpass = ""; + } + + if (isset($rule['nordr'])) { + $nordr = "no "; + $rdrpass = ""; + } else { + $nordr = ""; + } + + if (!$rule['interface']) { + $natif = "wan"; + } else { + $natif = $rule['interface']; + } + + if (!isset($FilterIflist[$natif])) { + continue; + } + + $srcaddr = filter_generate_address($rule, 'source', true); + $dstaddr = filter_generate_address($rule, 'destination', true); + $srcaddr = trim($srcaddr); + $dstaddr = trim($dstaddr); + + if (!$dstaddr) { + $dstaddr = $FilterIflist[$natif]['ip']; + } + + $dstaddr_port = explode(" ", $dstaddr); + if (empty($dstaddr_port[0]) || strtolower(trim($dstaddr_port[0])) == "port") { + continue; // Skip port forward if no destination address found + } + $dstaddr_reflect = $dstaddr; + if (isset($rule['destination']['any'])) { + /* With reflection enabled, destination of 'any' has side effects + * that most people would not expect, so change it on reflection rules. */ + + if (!empty($FilterIflist[$natif]['ip'])) { + $dstaddr_reflect = $FilterIflist[$natif]['ip']; + } else { + // no IP, bail + continue; + } + + if (!empty($FilterIflist[$natif]['sn'])) { + $dstaddr_reflect = gen_subnet($dstaddr_reflect, $FilterIflist[$natif]['sn']) . '/' . $FilterIflist[$natif]['sn']; + } + + if ($dstaddr_port[2]) { + $dstaddr_reflect .= " port " . $dstaddr_port[2]; + } + } + + $natif = $FilterIflist[$natif]['if']; + + $reflection_type = "none"; + if ($rule['natreflection'] != "disable" && $dstaddr_port[0] != "0.0.0.0") { + if ($rule['natreflection'] == "enable") { + $reflection_type = "proxy"; + } else if ($rule['natreflection'] == "purenat") { + $reflection_type = "purenat"; + } else if (!isset($config['system']['disablenatreflection'])) { + if (isset($config['system']['enablenatreflectionpurenat'])) { + $reflection_type = "purenat"; + } else { + $reflection_type = "proxy"; + } + } + } + + if ($reflection_type != "none") { + $nat_if_list = filter_get_reflection_interfaces($natif); + } else { + $nat_if_list = array(); + } + + if (empty($nat_if_list)) { + $reflection_type = "none"; + } + + $localport_nat = $localport; + if (empty($localport_nat) && $dstaddr_port[2]) { + $localport_nat = " port " . $dstaddr_port[2]; + } + + if ($srcaddr <> "" && $dstaddr <> "" && $natif) { + $natrules .= "{$nordr}rdr {$rdrpass}on {$natif} proto {$protocol} from {$srcaddr} to {$dstaddr}" . ($nordr == "" ? " -> {$target}{$localport}" : ""); + + /* Does this rule redirect back to a internal host? */ + if (isset($rule['destination']['any']) && !isset($rule['nordr']) && !isset($config['system']['enablenatreflectionhelper']) && !interface_has_gateway($rule['interface'])) { + $rule_interface_ip = find_interface_ip($natif); + $rule_interface_subnet = find_interface_subnet($natif); + if (!empty($rule_interface_ip) && !empty($rule_interface_subnet)) { + $rule_subnet = gen_subnet($rule_interface_ip, $rule_interface_subnet); + $natrules .= "\n"; + $natrules .= "no nat on {$natif} proto tcp from ({$natif}) to {$rule_subnet}/{$rule_interface_subnet}\n"; + $natrules .= "nat on {$natif} proto tcp from {$rule_subnet}/{$rule_interface_subnet} to {$target} port {$dstport[0]} -> ({$natif})\n"; + } + } + + if ($reflection_type != "none") { + if ($reflection_type == "proxy" && !isset($rule['nordr'])) { + $natrules .= filter_generate_reflection_proxy($rule, $nordr, $nat_if_list, $srcaddr, $dstaddr, $starting_localhost_port, $reflection_rules); + $nat_if_list = array($natif); + foreach ($reflection_rules as $txtline) { + fwrite($inetd_fd, $txtline); + } + } else if ($reflection_type == "purenat" || isset($rule['nordr'])) { + $rdr_if_list = implode(" ", $nat_if_list); + if (count($nat_if_list) > 1) { + $rdr_if_list = "{ {$rdr_if_list} }"; + } + $natrules .= "\n# Reflection redirect\n"; + $natrules .= "{$nordr}rdr {$rdrpass}on {$rdr_if_list} proto {$protocol} from {$srcaddr} to {$dstaddr_reflect}" . ($nordr == "" ? " -> {$target}{$localport}" : ""); + $nat_if_list = array_merge(array($natif), $nat_if_list); + } + } + + if (empty($nat_if_list)) { + $nat_if_list = array($natif); + } + + $natrules .= "\n"; + if (!isset($rule['nordr'])) { + $natrules .= filter_generate_reflection_nat($rule, $route_table, $nat_if_list, $protocol, "{$target}{$localport_nat}", $target_ip); + } + } + } + } + fclose($inetd_fd); // Close file handle + + if (isset($config['pptpd']['mode']) && ($config['pptpd']['mode'] != "off")) { + if ($config['pptpd']['mode'] == "redir") { + $pptpdtarget = $config['pptpd']['redir']; + $natrules .= "# PPTP\n"; + $natrules .= "rdr on \${$FilterIflist['wan']['descr']} proto gre from any to any -> {$pptpdtarget}\n"; + $natrules .= "rdr on \${$FilterIflist['wan']['descr']} proto tcp from any to any port 1723 -> {$pptpdtarget}\n"; + } + } + + $natrules .= discover_pkg_rules("nat"); + + $natrules .= "# UPnPd rdr anchor\n"; + $natrules .= "rdr-anchor \"miniupnpd\"\n"; + + if (!empty($reflection_txt)) { + $natrules .= "\n# Reflection redirects and NAT for 1:1 mappings\n" . $reflection_txt; + } + + // Check if inetd is running, if not start it. If so, restart it gracefully. + $helpers = isvalidproc("inetd"); + if (file_exists("/var/etc/inetd.conf")) { + if (!$helpers) { + mwexec("/usr/sbin/inetd -wW -R 0 -a 127.0.0.1 /var/etc/inetd.conf"); + } else { + sigkillbypid("/var/run/inetd.pid", "HUP"); + } + } + + return $natrules; +} + +function filter_generate_user_rule_arr($rule) { + global $config; + update_filter_reload_status(sprintf(gettext("Creating filter rule %s ..."), $rule['descr'])); + $ret = array(); + $line = filter_generate_user_rule($rule); + $ret['rule'] = $line; + $ret['interface'] = $rule['interface']; + if ($rule['descr'] != "" and $line != "") { + $ret['descr'] = "label \"" . fix_rule_label("USER_RULE: {$rule['descr']}") . "\""; + } else { + $ret['descr'] = "label \"USER_RULE\""; + } + + return $ret; +} + +function filter_generate_port(& $rule, $target = "source", $isnat = false) { + + $src = ""; + + $rule['protocol'] = strtolower($rule['protocol']); + if (in_array($rule['protocol'], array("tcp", "udp", "tcp/udp"))) { + if ($rule[$target]['port']) { + $srcport = explode("-", $rule[$target]['port']); + $srcporta = alias_expand($srcport[0]); + if (!$srcporta) { + log_error(sprintf(gettext("filter_generate_port: %s is not a valid {$target} port."), $srcport[0])); + } else if ((!$srcport[1]) || ($srcport[0] == $srcport[1])) { + $src .= " port {$srcporta} "; + } else if (($srcport[0] == 1) && ($srcport[1] == 65535)) { + /* no need for a port statement here */ + } else if ($isnat) { + $src .= " port {$srcport[0]}:{$srcport[1]}"; + } else { + if (is_port($srcporta) && $srcport[1] == 65535) { + $src .= " port >= {$srcporta} "; + } else if ($srcport[0] == 1) { + $src .= " port <= {$srcport[1]} "; + } else { + $srcport[0]--; + $srcport[1]++; + $src .= " port {$srcport[0]} >< {$srcport[1]} "; + } + } + } + } + + return $src; +} + +function filter_address_add_vips_subnets(&$subnets, $if, $not) { + global $FilterIflist; + + $if_subnets = array($subnets); + + if ($not == true) { + $subnets = "!{$subnets}"; + } + + if (!isset($FilterIflist[$if]['vips']) || !is_array($FilterIflist[$if]['vips'])) { + return; + } + + foreach ($FilterIflist[$if]['vips'] as $vip) { + foreach ($if_subnets as $subnet) { + if (ip_in_subnet($vip['ip'], $subnet)) { + continue 2; + } + } + + if (is_ipaddrv4($vip['ip'])) { + if (!is_subnetv4($if_subnets[0])) { + continue; + } + + $network = gen_subnet($vip['ip'], $vip['sn']); + } else if (is_ipaddrv6($vip['ip'])) { + if (!is_subnetv6($if_subnets[0])) { + continue; + } + + $network = gen_subnetv6($vip['ip'], $vip['sn']); + } else { + continue; + } + + $subnets .= ' ' . ($not == true ? '!' : '') . $network . '/' . $vip['sn']; + $if_subnets[] = $network . '/' . $vip['sn']; + } + unset($if_subnets); + + if (strpos($subnets, ' ') !== false) { + $subnets = "{ {$subnets} }"; + } +} + +function filter_generate_address(& $rule, $target = "source", $isnat = false) { + global $FilterIflist, $config; + $src = ""; + + if (isset($rule[$target]['any'])) { + $src = "any"; + } else if ($rule[$target]['network']) { + if (strstr($rule[$target]['network'], "opt")) { + $optmatch = ""; + $matches = ""; + if ($rule['ipprotocol'] == "inet6") { + if (preg_match("/opt([0-9]*)$/", $rule[$target]['network'], $optmatch)) { + $opt_sa = $FilterIflist["opt{$optmatch[1]}"]['sav6']; + if (!is_ipaddrv6($opt_sa)) { + return ""; + } + $src = $opt_sa . "/" . $FilterIflist["opt{$optmatch[1]}"]['snv6']; + /* check for opt$NUMip here */ + } else if (preg_match("/opt([0-9]*)ip/", $rule[$target]['network'], $matches)) { + $src = $FilterIflist["opt{$matches[1]}"]['ipv6']; + if (!is_ipaddrv6($src)) { + return ""; + } + if (isset($rule[$target]['not'])) { + $src = " !{$src}"; + } + } + } else { + if (preg_match("/opt([0-9]*)$/", $rule[$target]['network'], $optmatch)) { + $opt_sa = $FilterIflist["opt{$optmatch[1]}"]['sa']; + if (!is_ipaddrv4($opt_sa)) { + return ""; + } + $src = $opt_sa . "/" . $FilterIflist["opt{$optmatch[1]}"]['sn']; + /* check for opt$NUMip here */ + } else if (preg_match("/opt([0-9]*)ip/", $rule[$target]['network'], $matches)) { + $src = $FilterIflist["opt{$matches[1]}"]['ip']; + if (!is_ipaddrv4($src)) { + return ""; + } + if (isset($rule[$target]['not'])) { + $src = " !{$src}"; + } + } + } + } else { + if ($rule['ipprotocol'] == "inet6") { + switch ($rule[$target]['network']) { + case 'wan': + $wansa = $FilterIflist['wan']['sav6']; + if (!is_ipaddrv6($wansa)) { + return ""; + } + $wansn = $FilterIflist['wan']['snv6']; + $src = "{$wansa}/{$wansn}"; + break; + case 'wanip': + $src = $FilterIflist["wan"]['ipv6']; + if (!is_ipaddrv6($src)) { + return ""; + } + break; + case 'lanip': + $src = $FilterIflist["lan"]['ipv6']; + if (!is_ipaddrv6($src)) { + return ""; + } + break; + case 'lan': + $lansa = $FilterIflist['lan']['sav6']; + if (!is_ipaddrv6($lansa)) { + return ""; + } + $lansn = $FilterIflist['lan']['snv6']; + $src = "{$lansa}/{$lansn}"; + break; + case '(self)': + $src = "(self)"; + break; + case 'pptp': + $pptpsav6 = gen_subnetv6($FilterIflist['pptp']['sav6'], $FilterIflist['pptp']['snv6']); + $pptpsnv6 = $FilterIflist['pptp']['snv6']; + $src = "{$pptpsav6}/{$pptpsnv6}"; + break; + case 'pppoe': + if (is_array($FilterIflist['pppoe'])) { + $pppoesav6 = gen_subnetv6($FilterIflist['pppoe'][0]['ipv6'], $FilterIflist['pppoe'][0]['snv6']); + $pppoesnv6 = $FilterIflist['pppoe'][0]['snv6']; + $src = "{$pppoesav6}/{$pppoesnv6}"; + } + } + if (isset($rule[$target]['not']) && !is_subnet($src)) { + $src = " !{$src}"; + } + } else { + switch ($rule[$target]['network']) { + case 'wan': + $wansa = $FilterIflist['wan']['sa']; + if (!is_ipaddrv4($wansa)) { + return ""; + } + $wansn = $FilterIflist['wan']['sn']; + $src = "{$wansa}/{$wansn}"; + break; + case 'wanip': + $src = $FilterIflist["wan"]['ip']; + break; + case 'lanip': + $src = $FilterIflist["lan"]['ip']; + break; + case 'lan': + $lansa = $FilterIflist['lan']['sa']; + if (!is_ipaddrv4($lansa)) { + return ""; + } + $lansn = $FilterIflist['lan']['sn']; + $src = "{$lansa}/{$lansn}"; + break; + case '(self)': + $src = "(self)"; + break; + case 'pptp': + if (isset($config['pptpd']['n_pptp_units']) && is_numeric($config['pptpd']['n_pptp_units'])) { + $pptp_subnets = ip_range_to_subnet_array($config['pptpd']['remoteip'], long2ip32(ip2long($config['pptpd']['remoteip'])+($config['pptpd']['n_pptp_units']-1))); + } else { + $pptp_subnets = ip_range_to_subnet_array($config['pptpd']['remoteip'], long2ip32(ip2long($config['pptpd']['remoteip']))); + } + if (empty($pptp_subnets)) { + return ""; + } + if (isset($rule[$target]['not'])) { + array_walk($pptp_subnets, function (&$value, $key) { + $value="!{$value}"; + }); + } + $src = "{ " . implode(" ", $pptp_subnets) . " }"; + break; + case 'pppoe': + /* XXX: This needs to be fixed somehow! */ + if (is_array($FilterIflist['pppoe'])) { + $pppoesa = gen_subnet($FilterIflist['pppoe'][0]['ip'], $FilterIflist['pppoe'][0]['sn']); + $pppoesn = $FilterIflist['pppoe'][0]['sn']; + $src = "{$pppoesa}/{$pppoesn}"; + } + break; + } + if ((isset($rule[$target]['not'])) && + (!is_subnet($src)) && + (strpos($src, '{') === false)) { + $src = " !{$src}"; + } + } + } + if (is_subnet($src)) { + filter_address_add_vips_subnets($src, $rule[$target]['network'], isset($rule[$target]['not'])); + } + } else if ($rule[$target]['address']) { + $expsrc = alias_expand($rule[$target]['address']); + if (isset($rule[$target]['not'])) { + $not = "!"; + } else { + $not = ""; + } + $src = " {$not} {$expsrc}"; + } + + if (empty($src)) { + return ''; + } + + $src .= filter_generate_port($rule, $target, $isnat); + + return $src; +} + +function filter_generate_user_rule($rule) { + global $config, $g, $FilterIflist, $GatewaysList; + global $layer7_rules_list, $dummynet_name_list; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "filter_generate_user_rule() being called $mt\n"; + } + /* don't include disabled rules */ + if (isset($rule['disabled'])) { + return "# rule " . $rule['descr'] . " disabled \n"; + } + update_filter_reload_status("Creating filter rules {$rule['descr']} ..."); + $pptpdcfg = $config['pptpd']; + $int = ""; + $aline = array(); + + /* Check to see if the interface is in our list */ + if (isset($rule['floating'])) { + if (isset($rule['interface']) && $rule['interface'] <> "") { + $interfaces = explode(",", $rule['interface']); + $ifliste = ""; + foreach ($interfaces as $iface) { + if (array_key_exists($iface, $FilterIflist)) { + $ifliste .= " " . $FilterIflist[$iface]['if'] . " "; + } + } + if ($ifliste <> "") { + $aline['interface'] = " on { {$ifliste} } "; + } else { + $aline['interface'] = ""; + } + } else { + $aline['interface'] = ""; + } + } else if (!array_key_exists($rule['interface'], $FilterIflist)) { + foreach ($FilterIflist as $oc) { + $items .= $oc['descr'] . " "; + } + return "# array key \"{$rule['interface']}\" does not exist for \"" . $rule['descr'] . "\" in array: {{$items}}"; + } else if ((array_key_exists($rule['interface'], $FilterIflist)) && + (is_array($FilterIflist[$rule['interface']])) && + (is_array($FilterIflist[$rule['interface']][0]))) { + /* Currently the only case for this is the pppoe server. There should be an existing macro with this name. */ + $aline['interface'] = " on \$" . $rule['interface'] . " "; + } else { + $aline['interface'] = " on \$" . $FilterIflist[$rule['interface']]['descr'] . " "; + } + $ifcfg = $FilterIflist[$rule['interface']]; + if ($pptpdcfg['mode'] != "server") { + if (($rule['source']['network'] == "pptp") || + ($rule['destination']['network'] == "pptp")) { + return "# source network or destination network == pptp on " . $rule['descr']; + } + } + + switch ($rule['ipprotocol']) { + case "inet": + $aline['ipprotocol'] = "inet"; + break; + case "inet6": + $aline['ipprotocol'] = "inet6"; + break; + default: + $aline['ipprotocol'] = ""; + break; + } + + /* check for unresolvable aliases */ + if ($rule['source']['address'] && !alias_expand($rule['source']['address'])) { + $error_text = "Unresolvable source alias '{$rule['source']['address']}' for rule '{$rule['descr']}'"; + file_notice("Filter_Reload", $error_text); + return "# {$error_text}"; + } + if ($rule['destination']['address'] && !alias_expand($rule['destination']['address'])) { + $error_text = "Unresolvable destination alias '{$rule['destination']['address']}' for rule '{$rule['descr']}'"; + file_notice("Filter_Reload", $error_text); + return "# {$error_text}"; + } + update_filter_reload_status("Setting up pass/block rules"); + $type = $rule['type']; + if ($type != "pass" && $type != "block" && $type != "reject" && $type != "match") { + /* default (for older rules) is pass */ + $type = "pass"; + } + if ($type == "reject") { + $aline['type'] = "block return "; + } else { + $aline['type'] = $type . " "; + } + if (isset($rule['floating']) && $rule['floating'] == "yes") { + if ($rule['direction'] != "any") { + $aline['direction'] = " " . $rule['direction'] . " "; + } + } else { + /* ensure the direction is in */ + $aline['direction'] = " in "; + } + if (isset($rule['log'])) { + $aline['log'] = "log "; + } + if (!isset($rule['floating']) || isset($rule['quick'])) { + $aline['quick'] = " quick "; + } + + /* set the gateway interface */ + update_filter_reload_status(sprintf(gettext("Setting up pass/block rules %s"), $rule['descr'])); + + /* do not process reply-to for gateway'd rules */ + if ($rule['gateway'] == "" && $aline['direction'] <> "" && (interface_has_gateway($rule['interface']) || interface_has_gatewayv6($rule['interface'])) && !isset($config['system']['disablereplyto']) && !isset($rule['disablereplyto']) && $type != "match") { + if ($rule['ipprotocol'] == "inet6") { + $rg = get_interface_gateway_v6($rule['interface']); + if (is_ipaddrv6($rg)) { + $aline['reply'] = "reply-to ( {$ifcfg['ifv6']} {$rg} ) "; + } else if ($rule['interface'] <> "pptp") { + log_error(sprintf(gettext("Could not find IPv6 gateway for interface (%s)."), $rule['interface'])); + } + } else { + $rg = get_interface_gateway($rule['interface']); + if (is_ipaddrv4($rg)) { + $aline['reply'] = "reply-to ( {$ifcfg['if']} {$rg} ) "; + } else if ($rule['interface'] <> "pptp") { + log_error(sprintf(gettext("Could not find IPv4 gateway for interface (%s)."), $rule['interface'])); + } + } + } + /* if user has selected a custom gateway, lets work with it */ + else if ($rule['gateway'] <> "" && $type == "pass") { + if (isset($GatewaysList[$rule['gateway']])) { + /* Add the load balanced gateways */ + $aline['route'] = " \$GW{$rule['gateway']} "; + } else if (isset($config['system']['skip_rules_gw_down'])) { + return "# rule " . $rule['descr'] . " disabled because gateway " . $rule['gateway'] . " is down "; + } else { + log_error("The gateway: {$rule['gateway']} is invalid or unknown, not using it."); + } + } + + if (isset($rule['protocol']) && !empty($rule['protocol'])) { + if ($rule['protocol'] == "tcp/udp") { + $aline['prot'] = " proto { tcp udp } "; + } elseif (($rule['protocol'] == "icmp") && ($rule['ipprotocol'] == "inet6")) { + $aline['prot'] = " proto ipv6-icmp "; + } elseif ($rule['protocol'] == "icmp") { + $aline['prot'] = " proto icmp "; + } else { + $aline['prot'] = " proto {$rule['protocol']} "; + } + } else { + if ($rule['source']['port'] <> "" || $rule['destination']['port'] <> "") { + $aline['prot'] = " proto tcp "; + } + } + update_filter_reload_status(sprintf(gettext("Creating rule %s"), $rule['descr'])); + + /* source address */ + $src = trim(filter_generate_address($rule, "source")); + if (empty($src) || ($src == "/")) { + return "# at the break!"; + } + $aline['src'] = " from $src "; + + /* OS signatures */ + if (($rule['protocol'] == "tcp") && ($rule['os'] <> "")) { + $aline['os'] = " os \"{$rule['os']}\" "; + } + + /* destination address */ + $dst = trim(filter_generate_address($rule, "destination")); + if (empty($dst) || ($dst == "/")) { + return "# returning at dst $dst == \"/\""; + } + $aline['dst'] = "to $dst "; + + //Layer7 support + $l7_present = false; + $l7_structures = array(); + if (isset($rule['l7container']) && $rule['l7container'] != "none") { + $l7_present = true; + $l7rule =& $layer7_rules_list[$rule['l7container']]; + $l7_structures = $l7rule->get_unique_structures(); + $aline['divert'] = "divert-to " . $l7rule->GetRPort() . " "; + } + if (($rule['protocol'] == "icmp") && $rule['icmptype'] && ($rule['ipprotocol'] == "inet")) { + $aline['icmp-type'] = "icmp-type {$rule['icmptype']} "; + } + if (($rule['protocol'] == "icmp") && $rule['icmptype'] && ($rule['ipprotocol'] == "inet6")) { + $aline['icmp6-type'] = "icmp6-type {$rule['icmptype']} "; + } + if (!empty($rule['tag'])) { + if (ctype_digit($rule['tag'])) { + $aline['tag'] = " tag \"" .$rule['tag']. "\" "; + } else { + $aline['tag'] = " tag " .$rule['tag']. " "; + } + } + if (!empty($rule['tagged'])) { + $aline['tagged'] = " tagged " .$rule['tagged'] . " "; + } + if (!empty($rule['dscp'])) { + switch (strtolower($rule['dscp'])) { + case 'va': + $aline['dscp'] = " dscp \"44\" "; + break; + case 'VA': + $aline['dscp'] = " dscp \"44\" "; + break; + case 'cs1': + $aline['dscp'] = " dscp \"8\" "; + break; + case 'cs2': + $aline['dscp'] = " dscp \"16\" "; + break; + case 'cs3': + $aline['dscp'] = " dscp \"24\" "; + break; + case 'cs4': + $aline['dscp'] = " dscp \"32\" "; + break; + case 'cs5': + $aline['dscp'] = " dscp \"40\" "; + break; + case 'cs6': + $aline['dscp'] = " dscp \"48\" "; + break; + case 'cs7': + $aline['dscp'] = " dscp \"56\" "; + break; + default: + $aline['dscp'] = " dscp " . $rule['dscp'] . " "; + break; + } + } + if (!empty($rule['vlanprio']) && ($rule['vlanprio'] != "none")) { + $aline['vlanprio'] = " ieee8021q-pcp " . $rule['vlanprio'] . " "; + } + if (!empty($rule['vlanprioset']) && ($rule['vlanprioset'] != "none")) { + $aline['vlanprioset'] = " ieee8021q-setpcp " . $rule['vlanprioset'] . " "; + } + if ($type == "pass") { + if (isset($rule['allowopts'])) { + $aline['allowopts'] = " allow-opts "; + } + } + $aline['flags'] = ""; + if ($rule['protocol'] == "tcp") { + if (isset($rule['tcpflags_any'])) { + $aline['flags'] = "flags any "; + } else if (!empty($rule['tcpflags2'])) { + $aline['flags'] = "flags "; + if (!empty($rule['tcpflags1'])) { + $flags1 = explode(",", $rule['tcpflags1']); + foreach ($flags1 as $flag1) { + // CWR flag needs special treatment + if ($flag1[0] == "c") { + $aline['flags'] .= "W"; + } else { + $aline['flags'] .= strtoupper($flag1[0]); + } + } + } + $aline['flags'] .= "/"; + if (!empty($rule['tcpflags2'])) { + $flags2 = explode(",", $rule['tcpflags2']); + foreach ($flags2 as $flag2) { + // CWR flag needs special treatment + if ($flag2[0] == "c") { + $aline['flags'] .= "W"; + } else { + $aline['flags'] .= strtoupper($flag2[0]); + } + } + } + $aline['flags'] .= " "; + } else { + $aline['flags'] = "flags S/SA "; + } + } + if ($type == "pass") { + /* + * # keep state + * works with TCP, UDP, and ICMP. + * # modulate state + * works only with TCP. pfSense will generate strong Initial Sequence Numbers (ISNs) + * for packets matching this rule. + * # synproxy state + * proxies incoming TCP connections to help protect servers from spoofed TCP SYN floods. + * This option includes the functionality of keep state and modulate state combined. + * # none + * do not use state mechanisms to keep track. this is only useful if your doing advanced + * queueing in certain situations. please check the faq. + */ + $noadvoptions = false; + if (isset($rule['statetype']) && $rule['statetype'] <> "") { + switch ($rule['statetype']) { + case "none": + $noadvoptions = true; + $aline['flags'] .= " no state "; + break; + case "modulate state": + case "synproxy state": + if ($rule['protocol'] == "tcp") { + $aline['flags'] .= "{$rule['statetype']} "; + } + break; + case "sloppy state": + $aline['flags'] .= "keep state "; + $rule['sloppy'] = true; + break; + default: + $aline['flags'] .= "{$rule['statetype']} "; + break; + } + } else { + $aline['flags'] .= "keep state "; + } + + if ($noadvoptions == false && isset($rule['nopfsync'])) { + $rule['nopfsync'] = true; + } + + if ($noadvoptions == false || $l7_present) { + if ((isset($rule['source-track']) and $rule['source-track'] <> "") or + (isset($rule['max']) and $rule['max'] <> "") or + (isset($rule['max-src-nodes']) and $rule['max-src-nodes'] <> "") or + (isset($rule['max-src-states']) and $rule['max-src-states'] <> "") or + ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and + ((isset($rule['statetimeout']) and $rule['statetimeout'] <> "") or + (isset($rule['max-src-conn']) and $rule['max-src-conn'] <> "") or + (isset($rule['max-src-conn-rate']) and $rule['max-src-conn-rate'] <> "") or + (isset($rule['max-src-conn-rates']) and $rule['max-src-conn-rates'] <> ""))) or + (isset($rule['sloppy'])) or + (isset($rule['nopfsync'])) or + ($l7_present)) { + $aline['flags'] .= "( "; + if (isset($rule['sloppy'])) { + $aline['flags'] .= "sloppy "; + } + if (isset($rule['nopfsync'])) { + $aline['flags'] .= "no-sync "; + } + if (isset($rule['source-track']) and $rule['source-track'] <> "") { + $aline['flags'] .= "source-track rule "; + } + if (isset($rule['max']) and $rule['max'] <> "") { + $aline['flags'] .= "max " . $rule['max'] . " "; + } + if (isset($rule['max-src-nodes']) and $rule['max-src-nodes'] <> "") { + $aline['flags'] .= "max-src-nodes " . $rule['max-src-nodes'] . " "; + } + if ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and + (isset($rule['max-src-conn'])) and + ($rule['max-src-conn'] <> "")) { + $aline['flags'] .= "max-src-conn " . $rule['max-src-conn'] . " "; + } + if (isset($rule['max-src-states']) and $rule['max-src-states'] <> "") { + $aline['flags'] .= "max-src-states " . $rule['max-src-states'] . " "; + } + if ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and + (isset($rule['statetimeout'])) and + ($rule['statetimeout'] <> "")) { + $aline['flags'] .= "tcp.established " . $rule['statetimeout'] . " "; + } + if ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and + (isset($rule['max-src-conn-rate'])) and + ($rule['max-src-conn-rate'] <> "") and + (isset($rule['max-src-conn-rates'])) and + ($rule['max-src-conn-rates'] <> "")) { + $aline['flags'] .= "max-src-conn-rate " . $rule['max-src-conn-rate'] . " "; + $aline['flags'] .= "/" . $rule['max-src-conn-rates'] . ", overload <virusprot> flush global "; + } + + if (!empty($aline['divert'])) { + $aline['flags'] .= "max-packets 8 "; + } + + $aline['flags'] .= " ) "; + } + } + } + if ($rule['defaultqueue'] <> "") { + $aline['queue'] = " queue (".$rule['defaultqueue']; + if ($rule['ackqueue'] <> "") { + $aline['queue'] .= "," . $rule['ackqueue']; + } + $aline['queue'] .= ") "; + } + if ($rule['dnpipe'] <> "") { + if (!empty($dummynet_name_list[$rule['dnpipe']])) { + if ($dummynet_name_list[$rule['dnpipe']][0] == "?") { + $aline['dnpipe'] = " dnqueue( "; + $aline['dnpipe'] .= substr($dummynet_name_list[$rule['dnpipe']], 1); + if ($rule['pdnpipe'] <> "") { + $aline['dnpipe'] .= "," . substr($dummynet_name_list[$rule['pdnpipe']], 1); + } + } else { + $aline['dnpipe'] = " dnpipe ( " . $dummynet_name_list[$rule['dnpipe']]; + if ($rule['pdnpipe'] <> "") { + $aline['dnpipe'] .= "," . $dummynet_name_list[$rule['pdnpipe']]; + } + } + $aline['dnpipe'] .= ") "; + } + } + + /* is a time based rule schedule attached? */ + if (!empty($rule['sched']) && !empty($config['schedules'])) { + $aline['schedlabel'] = ""; + foreach ($config['schedules']['schedule'] as $sched) { + if ($sched['name'] == $rule['sched']) { + if (!filter_get_time_based_rule_status($sched)) { + if (!isset($config['system']['schedule_states'])) { + mwexec("/sbin/pfctl -y {$sched['schedlabel']}"); + } + return "# schedule finished - {$rule['descr']}"; + } else if ($g['debug']) { + log_error("[TDR DEBUG] status true -- rule type '$type'"); + } + + $aline['schedlabel'] = " schedule \"{$sched['schedlabel']}\" "; + break; + } + } + } + + if (!empty($rule['tracker'])) { + $aline['tracker'] = "tracker {$rule['tracker']} "; + } + + $line = ""; + /* exception(s) to a user rules can go here. */ + /* rules with a gateway or pool should create another rule for routing to vpns */ + if ((($aline['route'] <> "") && (trim($aline['type']) == "pass") && strstr($dst, "any")) && (!isset($config['system']['disablenegate']))) { + /* negate VPN/PPTP/PPPoE/Static Route networks for load balancer/gateway rules */ + $negate_networks = " to <negate_networks> " . filter_generate_port($rule, "destination"); + $line .= $aline['type'] . $aline['direction'] . $aline['log'] . $aline['quick'] . + $aline['interface'] . $aline['ipprotocol'] . $aline['prot'] . $aline['src'] . $aline['os'] . + $negate_networks . $aline['icmp-type'] . $aline['icmp6-type'] . $aline['tag'] . $aline['tagged'] . + $aline['vlanprio'] . $aline['vlanprioset'] . $aline['dscp'] . filter_negaterule_tracker() . $aline['allowopts'] . $aline['flags'] . + $aline['queue'] . $aline['dnpipe'] . $aline['schedlabel'] . + " label \"NEGATE_ROUTE: Negate policy routing for destination\"\n"; + + } + /* piece together the actual user rule */ + $line .= $aline['type'] . $aline['direction'] . $aline['log'] . $aline['quick'] . $aline['interface'] . + $aline['reply'] . $aline['route'] . $aline['ipprotocol'] . $aline['prot'] . $aline['src'] . $aline['os'] . $aline['dst'] . + $aline['divert'] . $aline['icmp-type'] . $aline['icmp6-type'] . $aline['tag'] . $aline['tagged'] . $aline['dscp'] . $aline['tracker'] . + $aline['vlanprio'] . $aline['vlanprioset'] . $aline['allowopts'] . $aline['flags'] . $aline['queue'] . $aline['dnpipe'] . $aline['schedlabel']; + + unset($aline); + + return $line; +} + +function filter_rules_generate() { + global $config, $g, $FilterIflist, $time_based_rules, $GatewaysList, $tracker; + + $fix_rule_label = 'fix_rule_label'; + $increment_tracker = 'filter_rule_tracker'; + + update_filter_reload_status(gettext("Creating default rules")); + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "filter_rules_generate() being called $mt\n"; + } + + $pptpdcfg = $config['pptpd']; + + $ipfrules = ""; + $ipfrules .= discover_pkg_rules("pfearly"); + + /* relayd */ + $ipfrules .= "anchor \"relayd/*\"\n"; + /* OpenVPN user rules from radius */ + $ipfrules .= "anchor \"openvpn/*\"\n"; + /* IPsec user rules from radius */ + $ipfrules .= "anchor \"ipsec/*\"\n"; + # BEGIN OF firewall rules + /* default block logging? */ + $log = array(); + if (!isset($config['syslog']['nologdefaultblock'])) { + $log['block'] = "log"; + } + if (isset($config['syslog']['nologdefaultpass'])) { + $log['pass'] = "log"; + } + + $saved_tracker = $tracker; + + if (!isset($config['system']['ipv6allow'])) { + $ipfrules .= "# Allow IPv6 on loopback\n"; + $ipfrules .= "pass in {$log['pass']} quick on \$loopback inet6 all tracker {$increment_tracker($tracker)} label \"pass IPv6 loopback\"\n"; + $ipfrules .= "pass out {$log['pass']} quick on \$loopback inet6 all tracker {$increment_tracker($tracker)} label \"pass IPv6 loopback\"\n"; + $ipfrules .= "# Block all IPv6\n"; + $ipfrules .= "block in {$log['block']} quick inet6 all tracker {$increment_tracker($tracker)} label \"Block all IPv6\"\n"; + $ipfrules .= "block out {$log['block']} quick inet6 all tracker {$increment_tracker($tracker)} label \"Block all IPv6\"\n"; + } + + $saved_tracker += 100; + $tracker = $saved_tracker; + + if (!isset($config['system']['no_apipa_block'])) { + $ipfrules .= <<<EOD +# block IPv4 link-local. Per RFC 3927, link local "MUST NOT" be forwarded by a routing device, +# and clients "MUST NOT" send such packets to a router. FreeBSD won't route 169.254./16, but +# route-to can override that, causing problems such as in redmine #2073 +block in {$log['block']} quick from 169.254.0.0/16 to any tracker {$increment_tracker($tracker)} label "Block IPv4 link-local" +block in {$log['block']} quick from any to 169.254.0.0/16 tracker {$increment_tracker($tracker)} label "Block IPv4 link-local" + +EOD; + } + + $ipfrules .= <<<EOD +#--------------------------------------------------------------------------- +# default deny rules +#--------------------------------------------------------------------------- +block in {$log['block']} inet all tracker {$increment_tracker($tracker)} label "Default deny rule IPv4" +block out {$log['block']} inet all tracker {$increment_tracker($tracker)} label "Default deny rule IPv4" +block in {$log['block']} inet6 all tracker {$increment_tracker($tracker)} label "Default deny rule IPv6" +block out {$log['block']} inet6 all tracker {$increment_tracker($tracker)} label "Default deny rule IPv6" + +# IPv6 ICMP is not auxilary, it is required for operation +# See man icmp6(4) +# 1 unreach Destination unreachable +# 2 toobig Packet too big +# 128 echoreq Echo service request +# 129 echorep Echo service reply +# 133 routersol Router solicitation +# 134 routeradv Router advertisement +# 135 neighbrsol Neighbor solicitation +# 136 neighbradv Neighbor advertisement +pass {$log['pass']} quick inet6 proto ipv6-icmp from any to any icmp6-type {1,2,135,136} tracker {$increment_tracker($tracker)} keep state + +# Allow only bare essential icmpv6 packets (NS, NA, and RA, echoreq, echorep) +pass out {$log['pass']} quick inet6 proto ipv6-icmp from fe80::/10 to fe80::/10 icmp6-type {129,133,134,135,136} tracker {$increment_tracker($tracker)} keep state +pass out {$log['pass']} quick inet6 proto ipv6-icmp from fe80::/10 to ff02::/16 icmp6-type {129,133,134,135,136} tracker {$increment_tracker($tracker)} keep state +pass in {$log['pass']} quick inet6 proto ipv6-icmp from fe80::/10 to fe80::/10 icmp6-type {128,133,134,135,136} tracker {$increment_tracker($tracker)} keep state +pass in {$log['pass']} quick inet6 proto ipv6-icmp from ff02::/16 to fe80::/10 icmp6-type {128,133,134,135,136} tracker {$increment_tracker($tracker)} keep state +pass in {$log['pass']} quick inet6 proto ipv6-icmp from fe80::/10 to ff02::/16 icmp6-type {128,133,134,135,136} tracker {$increment_tracker($tracker)} keep state + +# We use the mighty pf, we cannot be fooled. +block {$log['block']} quick inet proto { tcp, udp } from any port = 0 to any tracker {$increment_tracker($tracker)} label "Block traffic from port 0" +block {$log['block']} quick inet proto { tcp, udp } from any to any port = 0 tracker {$increment_tracker($tracker)} label "Block traffic to port 0" +block {$log['block']} quick inet6 proto { tcp, udp } from any port = 0 to any tracker {$increment_tracker($tracker)} label "Block traffic from port 0" +block {$log['block']} quick inet6 proto { tcp, udp } from any to any port = 0 tracker {$increment_tracker($tracker)} label "Block traffic to port 0" + +# Snort package +block {$log['block']} quick from <snort2c> to any tracker {$increment_tracker($tracker)} label "Block snort2c hosts" +block {$log['block']} quick from any to <snort2c> tracker {$increment_tracker($tracker)} label "Block snort2c hosts" + +EOD; + + $saved_tracker += 100; + $tracker = $saved_tracker; + + $ipfrules .= filter_process_carp_rules($log); + + $saved_tracker += 100; + $tracker = $saved_tracker; + + $ipfrules .= "\n# SSH lockout\n"; + if (is_array($config['system']['ssh']) && !empty($config['system']['ssh']['port'])) { + $ipfrules .= "block in {$log['block']} quick proto tcp from <sshlockout> to (self) port "; + $ipfrules .= $config['system']['ssh']['port']; + $ipfrules .= " tracker {$increment_tracker($tracker)} label \"sshlockout\"\n"; + } else { + if ($config['system']['ssh']['port'] <> "") { + $sshport = $config['system']['ssh']['port']; + } else { + $sshport = 22; + } + if ($sshport) { + $ipfrules .= "block in {$log['block']} quick proto tcp from <sshlockout> to (self) port {$sshport} tracker {$increment_tracker($tracker)} label \"sshlockout\"\n"; + } + } + + $saved_tracker += 50; + $tracker = $saved_tracker; + + $ipfrules .= "\n# webConfigurator lockout\n"; + if (!$config['system']['webgui']['port']) { + if ($config['system']['webgui']['protocol'] == "http") { + $webConfiguratorlockoutport = "80"; + } else { + $webConfiguratorlockoutport = "443"; + } + } else { + $webConfiguratorlockoutport = $config['system']['webgui']['port']; + } + if ($webConfiguratorlockoutport) { + $ipfrules .= "block in {$log['block']} quick proto tcp from <webConfiguratorlockout> to (self) port {$webConfiguratorlockoutport} tracker {$increment_tracker($tracker)} label \"webConfiguratorlockout\"\n"; + } + + $saved_tracker += 100; + $tracker = $saved_tracker; + + /* + * Support for allow limiting of TCP connections by establishment rate + * Useful for protecting against sudden outbursts, etc. + */ + $ipfrules .= "block in {$log['block']} quick from <virusprot> to any tracker 1000000400 label \"virusprot overload table\"\n"; + + $saved_tracker += 100; + $tracker = $saved_tracker; + + /* if captive portal is enabled, ensure that access to this port + * is allowed on a locked down interface + */ + if (is_array($config['captiveportal'])) { + foreach ($config['captiveportal'] as $cpcfg) { + if (!isset($cpcfg['enable'])) { + continue; + } + $cpinterfaces = explode(",", $cpcfg['interface']); + $cpiflist = array(); + $cpiplist = array(); + foreach ($cpinterfaces as $cpifgrp) { + if (!isset($FilterIflist[$cpifgrp])) { + continue; + } + $tmpif = get_real_interface($cpifgrp); + if (!empty($tmpif)) { + $cpiflist[] = "{$tmpif}"; + $cpipm = get_interface_ip($cpifgrp); + if (is_ipaddr($cpipm)) { + $cpiplist[] = $cpipm; + if (!is_array($config['virtualip']) || !is_array($config['virtualip']['vip'])) { + continue; + } + foreach ($config['virtualip']['vip'] as $vip) { + if (($vip['interface'] == $cpifgrp) && (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) { + $cpiplist[] = $vip['subnet']; + } + } + } + } + } + if (count($cpiplist) > 0 && count($cpiflist) > 0) { + $cpinterface = implode(" ", $cpiflist); + $cpaddresses = implode(" ", $cpiplist); + $listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : 8000 + ($cpcfg['zoneid'] + 1); + $listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : 8000 + $cpcfg['zoneid']; + $portalias = $listenporthttps; + $portalias .= " {$listenporthttp}"; + $ipfrules .= "pass in {$log['pass']} quick on { {$cpinterface} } proto tcp from any to { {$cpaddresses} } port { {$portalias} } tracker {$increment_tracker($tracker)} keep state(sloppy)\n"; + $ipfrules .= "pass out {$log['pass']} quick on { {$cpinterface} } proto tcp from any to any flags any tracker {$increment_tracker($tracker)} keep state(sloppy)\n"; + } + } + } + + $bogontableinstalled = 0; + foreach ($FilterIflist as $on => $oc) { + $saved_tracker += 10; + $tracker = $saved_tracker; + + if (isset($config['system']['ipv6allow']) && ($oc['type6'] == "slaac" || $oc['type6'] == "dhcp6")) { + // The DHCPv6 client rules ***MUST BE ABOVE BOGONSV6!*** https://redmine.pfsense.org/issues/3395 + $ipfrules .= <<<EOD +# allow our DHCPv6 client out to the {$oc['descr']} +pass in {$log['pass']} quick on \${$oc['descr']} proto udp from fe80::/10 port = 546 to fe80::/10 port = 546 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcpv6 client in {$oc['descr']}")}" +pass in {$log['pass']} quick on \${$oc['descr']} proto udp from any port = 547 to any port = 546 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcpv6 client in {$oc['descr']}")}" +pass out {$log['pass']} quick on \${$oc['descr']} proto udp from any port = 546 to any port = 547 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcpv6 client out {$oc['descr']}")}" + +EOD; + } + + /* XXX: Not static but give a step of 1000 for each interface to at least be able to match rules. */ + $saved_tracker += 1000; + $tracker = $saved_tracker; + + /* block bogon networks */ + /* http://www.cymru.com/Documents/bogon-bn-nonagg.txt */ + /* file is automatically in cron every 3000 minutes */ + if (!isset($config['syslog']['nologbogons'])) { + $bogonlog = "log"; + } else { + $bogonlog = ""; + } + + if (isset($config['interfaces'][$on]['blockbogons'])) { + $ipfrules .= <<<EOD +# block bogon networks (IPv4) +# http://www.cymru.com/Documents/bogon-bn-nonagg.txt +block in $bogonlog quick on \${$oc['descr']} from <bogons> to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("block bogon IPv4 networks from {$oc['descr']}")}" + +EOD; + + if (isset($config['system']['ipv6allow'])) { + $ipfrules .= <<<EOD +# block bogon networks (IPv6) +# http://www.team-cymru.org/Services/Bogons/fullbogons-ipv6.txt +block in $bogonlog quick on \${$oc['descr']} from <bogonsv6> to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("block bogon IPv6 networks from {$oc['descr']}")}" + +EOD; + } + } + + $saved_tracker += 10; + $tracker = $saved_tracker; + + $isbridged = false; + if (is_array($config['bridges']['bridged'])) { + foreach ($config['bridges']['bridged'] as $oc2) { + if (stristr($oc2['members'], $on)) { + $isbridged = true; + break; + } + } + } + + if ($oc['ip'] && !($isbridged) && isset($oc['spoofcheck'])) { + $ipfrules .= filter_rules_spoofcheck_generate($on, $oc, $log); + } + + /* block private networks ? */ + if (!isset($config['syslog']['nologprivatenets'])) { + $privnetlog = "log"; + } else { + $privnetlog = ""; + } + + $saved_tracker += 10; + $tracker = $saved_tracker; + + if (isset($config['interfaces'][$on]['blockpriv'])) { + if ($isbridged == false) { + $ipfrules .= <<<EOD +# block anything from private networks on interfaces with the option set +block in $privnetlog quick on \${$oc['descr']} from 10.0.0.0/8 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block private networks from {$oc['descr']} block 10/8")}" +block in $privnetlog quick on \${$oc['descr']} from 127.0.0.0/8 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block private networks from {$oc['descr']} block 127/8")}" +block in $privnetlog quick on \${$oc['descr']} from 172.16.0.0/12 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block private networks from {$oc['descr']} block 172.16/12")}" +block in $privnetlog quick on \${$oc['descr']} from 192.168.0.0/16 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block private networks from {$oc['descr']} block 192.168/16")}" +block in $privnetlog quick on \${$oc['descr']} from fc00::/7 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block ULA networks from {$oc['descr']} block fc00::/7")}" + +EOD; + } + } + + $saved_tracker += 10; + $tracker = $saved_tracker; + + switch ($oc['type']) { + case "pptp": + $ipfrules .= <<<EOD +# allow PPTP client +pass in {$log['pass']} on \${$oc['descr']} proto tcp from any to any port = 1723 flags S/SA modulate state tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow PPTP client on {$oc['descr']}")}" +pass in {$log['pass']} on \${$oc['descr']} proto gre from any to any keep state tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow PPTP client on {$oc['descr']}")}" + +EOD; + break; + case "dhcp": + $ipfrules .= <<<EOD +# allow our DHCP client out to the {$oc['descr']} +pass in {$log['pass']} on \${$oc['descr']} proto udp from any port = 67 to any port = 68 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcp client out {$oc['descr']}")}" +pass out {$log['pass']} on \${$oc['descr']} proto udp from any port = 68 to any port = 67 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcp client out {$oc['descr']}")}" +# Not installing DHCP server firewall rules for {$oc['descr']} which is configured for DHCP. + +EOD; + + break; + case "pppoe": + case "none": + /* XXX: Nothing to do in this case?! */ + break; + default: + /* allow access to DHCP server on interfaces */ + if (isset($config['dhcpd'][$on]['enable'])) { + $ipfrules .= <<<EOD +# allow access to DHCP server on {$oc['descr']} +pass in {$log['pass']} quick on \${$oc['descr']} proto udp from any port = 68 to 255.255.255.255 port = 67 tracker {$increment_tracker($tracker)} label "allow access to DHCP server" + +EOD; + if (is_ipaddrv4($oc['ip'])) { + $ipfrules .= <<<EOD +pass in {$log['pass']} quick on \${$oc['descr']} proto udp from any port = 68 to {$oc['ip']} port = 67 tracker {$increment_tracker($tracker)} label "allow access to DHCP server" +pass out {$log['pass']} quick on \${$oc['descr']} proto udp from {$oc['ip']} port = 67 to any port = 68 tracker {$increment_tracker($tracker)} label "allow access to DHCP server" + +EOD; + } + + if (is_ipaddrv4($oc['ip']) && $config['dhcpd'][$on]['failover_peerip'] <> "") { + $ipfrules .= <<<EOD +# allow access to DHCP failover on {$oc['descr']} from {$config['dhcpd'][$on]['failover_peerip']} +pass in {$log['pass']} quick on \${$oc['descr']} proto { tcp udp } from {$config['dhcpd'][$on]['failover_peerip']} to {$oc['ip']} port = 519 tracker {$increment_tracker($tracker)} label "allow access to DHCP failover" +pass in {$log['pass']} quick on \${$oc['descr']} proto { tcp udp } from {$config['dhcpd'][$on]['failover_peerip']} to {$oc['ip']} port = 520 tracker {$increment_tracker($tracker)} label "allow access to DHCP failover" + +EOD; + } + + } + break; + } + + $saved_tracker += 10; + $tracker = $saved_tracker; + switch ($oc['type6']) { + case "6rd": + $ipfrules .= <<<EOD +# allow our proto 41 traffic from the 6RD border relay in +pass in {$log['pass']} on \${$oc['descr']} proto 41 from {$config['interfaces'][$on]['gateway-6rd']} to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic in for 6rd on {$oc['descr']}")}" +pass out {$log['pass']} on \${$oc['descr']} proto 41 from any to {$config['interfaces'][$on]['gateway-6rd']} tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic out for 6rd on {$oc['descr']}")}" + +EOD; + /* XXX: Really need to allow 6rd traffic coming in for v6 this is against default behaviour! */ + if (0 && is_ipaddrv6($oc['ipv6'])) { + $ipfrules .= <<<EOD +pass in {$log['pass']} on \${$oc['descr']} inet6 from any to {$oc['ipv6']}/{$oc['snv6']} tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6rd traffic in for 6rd on {$oc['descr']}")}" +pass out {$log['pass']} on \${$oc['descr']} inet6 from {$oc['ipv6']}/{$oc['snv6']} to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6rd traffic out for 6rd on {$oc['descr']}")}" + +EOD; + } + break; + case "6to4": + if (is_ipaddrv4($oc['ip'])) { + $ipfrules .= <<<EOD +# allow our proto 41 traffic from the 6to4 border relay in +pass in {$log['pass']} on \${$oc['descr']} proto 41 from any to {$oc['ip']} tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic in for 6to4 on {$oc['descr']}")}" +pass out {$log['pass']} on \${$oc['descr']} proto 41 from {$oc['ip']} to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic out for 6to4 on {$oc['descr']}")}" + +EOD; + } + /* XXX: Really need to allow 6to4 traffic coming in for v6 this is against default behaviour! */ + if (0 && is_ipaddrv6($oc['ipv6'])) { + $ipfrules .= <<<EOD +pass in {$log['pass']} on \${$oc['descr']} inet6 from any to {$oc['ipv6']}/{$oc['snv6']} tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic in for 6to4 on {$oc['descr']}")}" +pass out {$log['pass']} on \${$oc['descr']} inet6 from {$oc['ipv6']}/{$oc['snv6']} to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic out for 6to4 on {$oc['descr']}")}" + +EOD; + } + break; + default: + if ((is_array($config['dhcpdv6'][$on]) && isset($config['dhcpdv6'][$on]['enable'])) || + (isset($oc['track6-interface'])) || + (is_array($config['dhcrelay6']) && !empty($config['dhcrelay6']['interface']) && in_array($on, explode(',', $config['dhcrelay6']['interface'])))) { + $ipfrules .= <<<EOD +# allow access to DHCPv6 server on {$oc['descr']} +# We need inet6 icmp for stateless autoconfig and dhcpv6 +pass {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from fe80::/10 to fe80::/10 port = 546 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server" +pass {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from fe80::/10 to ff02::/16 port = 546 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server" +pass {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from fe80::/10 to ff02::/16 port = 547 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server" +pass {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from ff02::/16 to fe80::/10 port = 547 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server" + +EOD; + if (is_ipaddrv6($oc['ipv6'])) { + $ipfrules .= <<<EOD +pass in {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from fe80::/10 to {$oc['ipv6']} port = 546 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server" +pass out {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from {$oc['ipv6']} port = 547 to fe80::/10 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server" + +EOD; + } + } + break; + } + } + + $saved_tracker += 10; + $tracker = $saved_tracker; + + /* + * NB: The loopback rules are needed here since the antispoof would take precedence then. + * If you ever add the 'quick' keyword to the antispoof rules above move the loopback + * rules before them. + */ + $ipfrules .= <<<EOD + +# loopback +pass in {$log['pass']} on \$loopback inet all tracker {$increment_tracker($tracker)} label "pass IPv4 loopback" +pass out {$log['pass']} on \$loopback inet all tracker {$increment_tracker($tracker)} label "pass IPv4 loopback" +pass in {$log['pass']} on \$loopback inet6 all tracker {$increment_tracker($tracker)} label "pass IPv6 loopback" +pass out {$log['pass']} on \$loopback inet6 all tracker {$increment_tracker($tracker)} label "pass IPv6 loopback" +# let out anything from the firewall host itself and decrypted IPsec traffic +pass out {$log['pass']} inet all keep state allow-opts tracker {$increment_tracker($tracker)} label "let out anything IPv4 from firewall host itself" +pass out {$log['pass']} inet6 all keep state allow-opts tracker {$increment_tracker($tracker)} label "let out anything IPv6 from firewall host itself" + +EOD; + + $saved_tracker += 100; + $tracker = $saved_tracker; + foreach ($FilterIflist as $ifdescr => $ifcfg) { + if (isset($ifcfg['virtual'])) { + continue; + } + + $gw = get_interface_gateway($ifdescr); + if (is_ipaddrv4($gw) && is_ipaddrv4($ifcfg['ip'])) { + $ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$ifcfg['ip']} to !{$ifcfg['sa']}/{$ifcfg['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; + if (is_array($ifcfg['vips'])) { + foreach ($ifcfg['vips'] as $vip) { + if (ip_in_subnet($vip['ip'], "{$ifcfg['sa']}/{$ifcfg['sn']}")) { + $ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$vip['ip']} to !{$ifcfg['sa']}/{$ifcfg['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; + } else { + $ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$vip['ip']} to !" . gen_subnet($vip['ip'], $vip['sn']) . "/{$vip['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; + } + } + } + } + + $gwv6 = get_interface_gateway_v6($ifdescr); + $stf = get_real_interface($ifdescr, "inet6"); + $pdlen = 64 - calculate_ipv6_delegation_length($ifdescr); + if (is_ipaddrv6($gwv6) && is_ipaddrv6($ifcfg['ipv6'])) { + $ipfrules .= "pass out {$log['pass']} route-to ( {$stf} {$gwv6} ) inet6 from {$ifcfg['ipv6']} to !{$ifcfg['ipv6']}/{$pdlen} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; + if (is_array($ifcfg['vips6'])) { + foreach ($ifcfg['vips6'] as $vip) { + $ipfrules .= "pass out {$log['pass']} route-to ( {$stf} {$gwv6} ) inet6 from {$vip['ip']} to !{$vip['ip']}/{$pdlen} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; + } + } + } + } + + + $saved_tracker += 300; + $tracker = $saved_tracker; + /* add ipsec interfaces */ + if (isset($config['ipsec']['enable']) || isset($config['ipsec']['client']['enable'])) { + $ipfrules .= "pass out {$log['pass']} on \$IPsec all tracker {$increment_tracker($tracker)} tracker {$increment_tracker($tracker)} keep state label \"IPsec internal host to host\"\n"; + } + + $saved_tracker += 10; + $tracker = $saved_tracker; + if (is_array($config['system']['webgui']) && !isset($config['system']['webgui']['noantilockout'])) { + $alports = filter_get_antilockout_ports(); + + if (count($config['interfaces']) > 1 && !empty($FilterIflist['lan']['if'])) { + /* if antilockout is enabled, LAN exists and has + * an IP and subnet mask assigned + */ + $lanif = $FilterIflist['lan']['if']; + $ipfrules .= <<<EOD +# make sure the user cannot lock himself out of the webConfigurator or SSH +pass in {$log['pass']} quick on {$lanif} proto tcp from any to ({$lanif}) port { {$alports} } tracker {$increment_tracker($tracker)} keep state label "anti-lockout rule" + +EOD; + } else if (count($config['interfaces']) == 1) { + /* single-interface deployment, add to WAN */ + $wanif = $FilterIflist["wan"]['if']; + $ipfrules .= <<<EOD +# make sure the user cannot lock himself out of the webConfigurator or SSH +pass in {$log['pass']} quick on {$wanif} proto tcp from any to ({$wanif}) port { {$alports} } tracker {$increment_tracker($tracker)} keep state label "anti-lockout rule" + +EOD; + } + unset($alports); + } + + $saved_tracker += 10; + $tracker = $saved_tracker; + /* PPTPd enabled? */ + if ($pptpdcfg['mode'] && ($pptpdcfg['mode'] != "off") && !isset($config['system']['disablevpnrules'])) { + if ($pptpdcfg['mode'] == "server") { + $pptpdtarget = get_interface_ip(); + } else { + $pptpdtarget = $pptpdcfg['redir']; + } + if (is_ipaddr($pptpdtarget) and is_array($FilterIflist['wan'])) { + $ipfrules .= <<<EOD +# PPTPd rules +pass in {$log['pass']} on \${$FilterIflist['wan']['descr']} proto tcp from any to $pptpdtarget port = 1723 tracker {$increment_tracker($tracker)} modulate state label "{$fix_rule_label("allow pptpd {$pptpdtarget}")}" +pass in {$log['pass']} on \${$FilterIflist['wan']['descr']} proto gre from any to any tracker {$increment_tracker($tracker)} keep state label "allow gre pptpd" + +EOD; + + } else { + /* this shouldnt ever happen but instead of breaking the clients ruleset + * log an error. + */ + log_error("ERROR! PPTP enabled but could not resolve the \$pptpdtarget"); + } + } + + $saved_tracker += 10; + $tracker = $saved_tracker; + if (isset($config['nat']['rule']) && is_array($config['nat']['rule'])) { + foreach ($config['nat']['rule'] as $rule) { + if ((!isset($config['system']['disablenatreflection']) || $rule['natreflection'] == "enable") && + ($rule['natreflection'] != "disable")) { + $ipfrules .= "# NAT Reflection rules\n"; + $ipfrules .= <<<EOD +pass in {$log['pass']} inet tagged PFREFLECT tracker {$increment_tracker($tracker)} keep state label "NAT REFLECT: Allow traffic to localhost" + +EOD; + break; + } + } + } + + if (isset($config['filter']['rule'])) { + /* Pre-cache all our rules so we only have to generate them once */ + $rule_arr1 = array(); + $rule_arr2 = array(); + $rule_arr3 = array(); + $vpn_and_ppp_ifs = array("l2tp", "pptp", "pppoe", "enc0", "openvpn"); + /* + * NB: The order must be: Floating rules, then interface group and then regular ones. + */ + foreach ($config['filter']['rule'] as $rule) { + update_filter_reload_status("Pre-caching {$rule['descr']}..."); + if (isset ($rule['disabled'])) { + continue; + } + + if (!empty($rule['ipprotocol']) && $rule['ipprotocol'] == "inet46") { + if (isset($rule['floating'])) { + $rule['ipprotocol'] = "inet"; + $rule_arr1[] = filter_generate_user_rule_arr($rule); + $rule['ipprotocol'] = "inet6"; + $rule_arr1[] = filter_generate_user_rule_arr($rule); + } else if (is_interface_group($rule['interface']) || in_array($rule['interface'], $vpn_and_ppp_ifs)) { + $rule['ipprotocol'] = "inet"; + $rule_arr2[] = filter_generate_user_rule_arr($rule); + $rule['ipprotocol'] = "inet6"; + $rule_arr2[] = filter_generate_user_rule_arr($rule); + } else { + $rule['ipprotocol'] = "inet"; + $rule_arr3[] = filter_generate_user_rule_arr($rule); + $rule['ipprotocol'] = "inet6"; + $rule_arr3[] = filter_generate_user_rule_arr($rule); + } + $rule['ipprotocol'] = "inet46"; + } else { + if (isset($rule['floating'])) { + $rule_arr1[] = filter_generate_user_rule_arr($rule); + } else if (is_interface_group($rule['interface']) || in_array($rule['interface'], $vpn_and_ppp_ifs)) { + $rule_arr2[] = filter_generate_user_rule_arr($rule); + } else { + $rule_arr3[] = filter_generate_user_rule_arr($rule); + } + } + if ($rule['sched']) { + $time_based_rules = true; + } + } + + $ipfrules .= "\n# User-defined rules follow\n"; + $ipfrules .= "\nanchor \"userrules/*\"\n"; + /* Generate user rule lines */ + foreach ($rule_arr1 as $rule) { + if (isset($rule['disabled'])) { + continue; + } + if (!$rule['rule']) { + continue; + } + $ipfrules .= "{$rule['rule']} {$rule['descr']}\n"; + } + foreach ($rule_arr2 as $rule) { + if (isset($rule['disabled'])) { + continue; + } + if (!$rule['rule']) { + continue; + } + $ipfrules .= "{$rule['rule']} {$rule['descr']}\n"; + } + foreach ($rule_arr3 as $rule) { + if (isset($rule['disabled'])) { + continue; + } + if (!$rule['rule']) { + continue; + } + $ipfrules .= "{$rule['rule']} {$rule['descr']}\n"; + } + unset($rule_arr1, $rule_arr2, $rule_arr3); + } + + $saved_tracker += 100; + $tracker = $saved_tracker; + + /* pass traffic between statically routed subnets and the subnet on the + * interface in question to avoid problems with complicated routing + * topologies + */ + if (isset($config['filter']['bypassstaticroutes']) && is_array($config['staticroutes']['route']) && count($config['staticroutes']['route'])) { + $ipfrules .= "# Add rules to bypass firewall rules for static routes\n"; + foreach (get_staticroutes() as $route) { + $friendly = $GatewaysList[$route['gateway']]['friendlyiface']; + if (is_array($FilterIflist[$friendly])) { + $oc = $FilterIflist[$friendly]; + $routeent = explode("/", $route['network']); + unset($sa); + if (is_ipaddrv4($oc['ip'])) { + $sa = $oc['sa']; + $sn = $oc['sn']; + } + if ($sa && is_ipaddrv4($routeent[0])) { + $ipfrules .= <<<EOD +pass {$log['pass']} quick on \${$oc['descr']} proto tcp from {$sa}/{$sn} to {$route['network']} flags any tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets" +pass {$log['pass']} quick on \${$oc['descr']} from {$sa}/{$sn} to {$route['network']} tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets" +pass {$log['pass']} quick on \${$oc['descr']} proto tcp from {$route['network']} to {$sa}/{$sn} flags any tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets" +pass {$log['pass']} quick on \${$oc['descr']} from {$route['network']} to {$sa}/{$sn} tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets" + +EOD; + } + unset($sa); + if (is_ipaddrv6($oc['ipv6'])) { + $sa = $oc['sav6']; + $sn = $oc['snv6']; + } + if ($sa && is_ipaddrv6($routeent[0])) { + $ipfrules .= <<<EOD +pass {$log['pass']} quick on \${$oc['descr']} inet6 proto tcp from {$sa}/{$sn} to {$route['network']} flags any tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets" +pass {$log['pass']} quick on \${$oc['descr']} inet6 from {$sa}/{$sn} to {$route['network']} tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets" +pass {$log['pass']} quick on \${$oc['descr']} inet6 proto tcp from {$route['network']} to {$sa}/{$sn} flags any tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets" +pass {$log['pass']} quick on \${$oc['descr']} inet6 from {$route['network']} to {$sa}/{$sn} tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets" + +EOD; + } + } + } + } + + update_filter_reload_status(gettext("Creating IPsec rules...")); + $saved_tracker += 100000; + $tracker = $saved_tracker; + $ipfrules .= filter_generate_ipsec_rules($log); + + $ipfrules .= "\nanchor \"tftp-proxy/*\"\n"; + + $saved_tracker += 200; + $tracker = $saved_tracker; + update_filter_reload_status("Creating uPNP rules..."); + if (is_array($config['installedpackages']['miniupnpd']) && is_array($config['installedpackages']['miniupnpd']['config'][0])) { + if (isset($config['installedpackages']['miniupnpd']['config'][0]['enable'])) { + $ipfrules .= "anchor \"miniupnpd\"\n"; + } + + if (is_array($config['installedpackages']['miniupnpd'][0]['config'])) { + $upnp_interfaces = explode(",", $config['installedpackages']['miniupnpd'][0]['config']['iface_array']); + foreach ($upnp_interfaces as $upnp_if) { + if (is_array($FilterIflist[$upnp_if])) { + $oc = $FilterIflist[$upnp_if]; + unset($sa); + if ($oc['ip']) { + $sa = $oc['sa']; + $sn = $oc['sn']; + } + if ($sa) { + $ipfrules .= <<<EOD +pass in {$log['pass']} on \${$oc['descr']} proto tcp from {$sa}/{$sn} to 239.255.255.250/32 port 1900 tracker {$increment_tracker($tracker)} keep state label "pass multicast traffic to miniupnpd" + +EOD; + } + } + } + } + } + + return $ipfrules; +} + +function filter_rules_spoofcheck_generate($ifname, $ifcfg, $log) { + global $g, $config, $tracker; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "filter_rules_spoofcheck_generate() being called $mt\n"; + } + $ipfrules = "antispoof {$log['block']} for \${$ifcfg['descr']} tracker {$tracker}\n"; + $tracker++; + + return $ipfrules; +} + +/* COMPAT Function */ +function tdr_install_cron($should_install) { + log_error(gettext("Please use filter_tdr_install_cron() function tdr_install_cron will be deprecated!")); + filter_tdr_install_cron($should_install); +} + +/****f* filter/filter_tdr_install_cron + * NAME + * filter_tdr_install_cron + * INPUTS + * $should_install true if the cron entry should be installed, false + * if the entry should be removed if it is present + * RESULT + * none + ******/ +function filter_tdr_install_cron($should_install) { + global $config, $g; + + if (platform_booting() == true) { + return; + } + + if (!is_array($config['cron'])) { + $config['cron'] = array(); + } + if (!is_array($config['cron']['item'])) { + $config['cron']['item'] = array(); + } + + $x = 0; + $is_installed = false; + foreach ($config['cron']['item'] as $item) { + if (strstr($item['command'], "filter_configure_sync")) { + $is_installed = true; + break; + } + $x++; + } + + switch ($should_install) { + case true: + if (!$is_installed) { + $cron_item = array(); + $cron_item['minute'] = "0,15,30,45"; + $cron_item['hour'] = "*"; + $cron_item['mday'] = "*"; + $cron_item['month'] = "*"; + $cron_item['wday'] = "*"; + $cron_item['who'] = "root"; + $cron_item['command'] = "/etc/rc.filter_configure_sync"; + $config['cron']['item'][] = $cron_item; + write_config(gettext("Installed 15 minute filter reload for Time Based Rules")); + configure_cron(); + } + break; + case false: + if ($is_installed) { + unset($config['cron']['item'][$x]); + write_config(gettext("Removed 15 minute filter reload for Time Based Rules")); + configure_cron(); + } + break; + } +} + +/****f* filter/filter_get_time_based_rule_status + * NAME + * filter_get_time_based_rule_status + * INPUTS + * xml schedule block + * RESULT + * true/false - true if the rule should be installed + ******/ +/* + <schedules> + <schedule> + <name>ScheduleMultipleTime</name> + <descr>main descr</descr> + <time> + <position>0,1,2</position> + <hour>0:0-24:0</hour> + <desc>time range 2</desc> + </time> + <time> + <position>4,5,6</position> + <hour>0:0-24:0</hour> + <desc>time range 1</desc> + </time> + </schedule> + </schedules> +*/ +function filter_get_time_based_rule_status($schedule) { + + /* no schedule? rule should be installed */ + if (empty($schedule)) { + return true; + } + /* + * iterate through time blocks and determine + * if the rule should be installed or not. + */ + foreach ($schedule['timerange'] as $timeday) { + if (empty($timeday['month'])) { + $monthstatus = true; + } else { + $monthstatus = filter_tdr_month($timeday['month']); + } + if (empty($timeday['day'])) { + $daystatus = true; + } else { + $daystatus = filter_tdr_day($timeday['day']); + } + if (empty($timeday['hour'])) { + $hourstatus = true; + } else { + $hourstatus = filter_tdr_hour($timeday['hour']); + } + if (empty($timeday['position'])) { + $positionstatus = true; + } else { + $positionstatus = filter_tdr_position($timeday['position']); + } + + if ($monthstatus == true && $daystatus == true && $positionstatus == true && $hourstatus == true) { + return true; + } + } + + return false; +} + +function filter_tdr_day($schedule) { + global $g; + + if ($g['debug']) { + log_error("[TDR DEBUG] filter_tdr_day($schedule)"); + } + + /* + * Calculate day of month. + * IE: 29th of may + */ + $date = date("d"); + $defined_days = explode(",", $schedule); + foreach ($defined_days as $dd) { + if ($date == $dd) { + return true; + } + } + return false; +} +function filter_tdr_hour($schedule) { + global $g; + + /* $schedule should be a string such as 16:00-19:00 */ + $tmp = explode("-", $schedule); + $starting_time = strtotime($tmp[0]); + $ending_time = strtotime($tmp[1]); + $now = strtotime("now"); + if ($g['debug']) { + log_error("[TDR DEBUG] S: $starting_time E: $ending_time N: $now"); + } + if ($now >= $starting_time and $now < $ending_time) { + return true; + } + return false; +} + +function fil |