之前在系上利用 pfSense 做了無線網路認證的機制後,使用狀況大致上還是順利。最近系上想要增加訪客帳號的機制。訪客帳號的機制主要是想提供給外賓使用,一組有時效性的帳號密碼。本來想使用 pfSense 內建的 Local users 機制來做,可是他沒辦法跟 RADIUS 認證同時使用。因此沒辦法,勢必得稍微 hack 一下 pfSense :P
要做到這件事情,首先我利用 Sinatra 做了一個後台的網站。這個訪客機制網站可以允許整批新增帳號,看到帳號密碼跟失效時間等等基本的管理功能。最重要的是,必須提供一個簡單的 API 介面讓外部的機器可以透過這個網站確認帳號密碼是否正確以及可以使用。這種簡單的網站透過 Sinatra 作最適合了,這個網站的程式碼部分 source code 剛剛好是 123 行 XDD 這次不打算公開出來,不過我會在這邊稍微說明一下用到的一些小技巧。
Session in Sinatra
要在 Sinatra 中使用 session 最簡單的是用 Cookie-based session。可是用 cookie-based session 很有可能被偽造,沒有辦法達到紀錄登入狀態的效果。為此,我們改用了寫在 Rack 上的 session 機制。我在這個網站中為了方便就直接使用了 memcached 作為 backend。在 ArchLinux 上安裝 memcached 很簡單,透過 pacman 即可安裝。啟動之後預設就會 listen 在 127.0.0.1 防止外部連線。在 Sinatra 中使用只要一行:
2 | use Rack::Session::Memcache |
之後在你的 action 裡面透過 session[] 就可以存取了,非常簡單。
DataMapper in Sinatra
在這次的實作中,我試著使用 DataMapper 。其實 DataMapper 用起來也滿簡單的,甚至可以省掉 migration 的程式。在這個程式裡面用到的 Data model 如下:
2 3 4 5 6 7 8 9 10 11 12 13 14 | DataMapper.setup(:default, "mysql://user:pass@host/db_name") class Guest include DataMapper::Resource property :id, Serial property :uid, String property :pwd, String property :comment, String property :expire, DateTime end DataMapper.auto_upgrade! |
請注意是使用 auto_upgrade! 而不是 auto_migrate! ,後者是破壞性的先把表格 DROP 掉重新建立。前者則是會修改表格符合 class 中宣告的狀況。建立完 Model 後,使用的方式也很簡單:
2 3 4 | Guest.all # 傳回包含所有資料的陣列 Guset.get(id) # 找 id 一樣的資料,同 ActiveRecord 的 Guest.find(id) Guest.first(:uid => params[:uid]) # 找第一筆 uid = 輸入的資料 |
Authentication in Sinatra
在 Sinatra 中檢查使用者有沒有登入有很多方法,利用 helper 檢查也是一種方式。不過由於我的程式很小,我利用了 before hook 來做這件事情。
2 3 4 5 6 | before do unless ['/', '/login'].include? request.path_info redirect '/' if !session[:logged_in] end end |
也就是說除非是進入首頁或是登入中,其他網頁沒登入的話就自動導回首頁要求登入。不過 Sinatra 目前版本中沒有支援 after hook ,所以有時候要實作一些機制變的還滿麻煩的 :(
列印帳號卡
這個地方談的是 CSS 技巧,因為產生大批帳號後必須要讓系辦可以直接列印出來。在網頁上用了 CSS 排成一個一個的框框,但是列印出來總是會被裁到。因此最後用了 print media 的 css。做法是在網頁上適當位置(例如說每十個小卡)加入一個輔助性的分頁 div,並且把不需要列印的 div 套用 noprint 的 class。
2 3 4 5 | <!--不需要列印--> <div class="noprint">...</div> <!--分頁輔助div--> <div class="page-break"> </div> |
接著寫了一個 print.css 如下,指定分頁和隱藏 noprint 的元件:
2 3 | .noprint { display: none; } .page-break { page-break-after: always; display: block; } |
這樣一來列印出來的成品就是會漂漂亮亮排好的小卡了!
pfSense hack!
其實要 hack pfSense 非常簡單,因為你本來就可以 SSH 登入 pfSense 主機,並且 pfSense 大多數程式用 PHP 撰寫。很容易就可以找到地方 hack :P 首先登入 pfSense 後用 ps ax 先查一下Captive Portal 是誰負責的,會發現是一隻 lighttpd 而且有獨立的設定檔。因此用 ee 打開設定檔後找到 document-root (/usr/local/captiveportal) 再打開其中的 index.php 編輯。
我希望做到的樣子是首先檢查外部認證是否成功,成功的話就直接讓使用者通過認證、開放使用網路。如果失敗就 fallback 使用原先的 RADIUS 認證機制提供全校認證及跨校漫遊的功能。所以我在認證之前加上了這段 code :
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /* pfSense 原先就有的 code /* find out if we need RADIUS + RADIUSMAC or not */ if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) { $radius_enable = TRUE; if ($radius_enable && isset($config['captiveportal']['radmac_enable'])) $radmac_enable = TRUE; }*/ /// Zero.20090505: NCU eecesnmg Wireless Guest account authentication if ($_POST['auth_user'] && $_POST['auth_pass']) { $auth_result = file_get_contents("http://auth_host/auth?uid=" . $_POST['auth_user'] . "&pwd=" . $_POST['au if ($auth_result == 'OK') { captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"LOGIN"); portal_allow($clientip, $clientmac,$_POST['auth_user']); exit; } // We don't handle the error here, because if it's failed. // Let's fallback to RADIUS auth. } /* pfSense 原先就有的 code if ($_POST['logout_id']) { disconnect_client($_POST['logout_id']); echo <<<EOD */ |
這就是這次的心得報告,以上!

留言 Comments