引言
之前的文章已經描述wifidog大概的一個工作流程,這里我們具體說說wifidog是怎么把一個新用戶重定向到認證服務器中的,它又是怎么對一個已認證的用戶實行放行操作的。我們已經知道wifidog在啟動時會刪除iptables中mangle、nat、filter表中的所有規則,並在這三個表中添加wifidog自己的規則,其規則簡單來說就是將網關80端口重定向到指定端口(默認為2060),禁止非認證用戶連接外網(除認證服務器外)。當有新用戶連接路由器上網時,wifidog會通過監聽2060端口獲取新用戶的http報文,通過報文即可知道報文是否攜帶token進行認證,如果沒有token,wifidog會返回一個重定向的http報文給新用戶,用戶則會跳轉到認證服務器進行認證,當認證成功后,認真服務器又會對用戶重定向到網關,並在重定向報文中添加關鍵路徑"/wifidog/auth"和token,wifidog重新獲取到用戶的http報文,檢測到包含關鍵路徑"/wifidog/auth"后,會通過認證服務器驗證token是否有效,有效則修改iptables放行此客戶端。
main_loop
此函數幾乎相當於我們寫程序的main函數,主要功能都是在這里面實現的,函數主要實現了主循環,並啟動了三個線程,這三個線程的功能具體見wifidog源碼分析 - wifidog原理,在此函數最后主循環中會等待用戶連接,新用戶只要通過瀏覽器打開非認證服務器網頁時主循環就會監聽到,監聽到后會啟動一個處理線程。其流程為
- 設置程序啟動時間
- 獲取網關信息
- 綁定http端口(80重定向到了2060)
- 設置關鍵路徑和404錯誤的回調函數
- 重新建立iptables規則
- 啟動客戶端檢測線程 (稍后文章分析)
- 啟動wdctrl交互線程 (稍后文章分析)
- 認證服務器心跳檢測線程 (稍后文章分析)
- 循環等待用戶http請求,為每個請求啟動一個處理線程。(代碼片段1.2 代碼片段1.3 代碼片段1.4)
代碼片段1.1
1 static void 2 main_loop(void) 3 { 4 int result; 5 pthread_t tid; 6 s_config *config = config_get_config(); 7 request *r; 8 void **params; 9 10 /* 設置啟動時間 */ 11 if (!started_time) { 12 debug(LOG_INFO, "Setting started_time"); 13 started_time = time(NULL); 14 } 15 else if (started_time < MINIMUM_STARTED_TIME) { 16 debug(LOG_WARNING, "Detected possible clock skew - re-setting started_time"); 17 started_time = time(NULL); 18 } 19 20 /* 獲取網關IP,失敗退出程序 */ 21 if (!config->gw_address) { 22 debug(LOG_DEBUG, "Finding IP address of %s", config->gw_interface); 23 if ((config->gw_address = get_iface_ip(config->gw_interface)) == NULL) { 24 debug(LOG_ERR, "Could not get IP address information of %s, exiting...", config->gw_interface); 25 exit(1); 26 } 27 debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_address); 28 } 29 30 /* 獲取網關ID,失敗退出程序 */ 31 if (!config->gw_id) { 32 debug(LOG_DEBUG, "Finding MAC address of %s", config->gw_interface); 33 if ((config->gw_id = get_iface_mac(config->gw_interface)) == NULL) { 34 debug(LOG_ERR, "Could not get MAC address information of %s, exiting...", config->gw_interface); 35 exit(1); 36 } 37 debug(LOG_DEBUG, "%s = %s", config->gw_interface, config->gw_id); 38 } 39 40 /* 初始化監聽網關2060端口的socket */ 41 debug(LOG_NOTICE, "Creating web server on %s:%d", config->gw_address, config->gw_port); 42 43 if ((webserver = httpdCreate(config->gw_address, config->gw_port)) == NULL) { 44 debug(LOG_ERR, "Could not create web server: %s", strerror(errno)); 45 exit(1); 46 } 47 48 debug(LOG_DEBUG, "Assigning callbacks to web server"); 49 /* 設置關鍵路徑及其回調函數,在代碼片段1.2中會使用到 */ 50 httpdAddCContent(webserver, "/", "wifidog", 0, NULL, http_callback_wifidog); 51 httpdAddCContent(webserver, "/wifidog", "", 0, NULL, http_callback_wifidog); 52 httpdAddCContent(webserver, "/wifidog", "about", 0, NULL, http_callback_about); 53 httpdAddCContent(webserver, "/wifidog", "status", 0, NULL, http_callback_status); 54 httpdAddCContent(webserver, "/wifidog", "auth", 0, NULL, http_callback_auth); 55 /* 設置404錯誤回調函數,在里面實現了重定向至認證服務器 */ 56 httpdAddC404Content(webserver, http_callback_404); 57 58 /* 清除iptables規則 */ 59 fw_destroy(); 60 /* 重新設置iptables規則 */ 61 if (!fw_init()) { 62 debug(LOG_ERR, "FATAL: Failed to initialize firewall"); 63 exit(1); 64 } 65 66 /* 客戶端檢測線程 */ 67 result = pthread_create(&tid_fw_counter, NULL, (void *)thread_client_timeout_check, NULL); 68 if (result != 0) { 69 debug(LOG_ERR, "FATAL: Failed to create a new thread (fw_counter) - exiting"); 70 termination_handler(0); 71 } 72 pthread_detach(tid_fw_counter); 73 74 /* wdctrl交互線程 */ 75 result = pthread_create(&tid, NULL, (void *)thread_wdctl, (void *)safe_strdup(config->wdctl_sock)); 76 if (result != 0) { 77 debug(LOG_ERR, "FATAL: Failed to create a new thread (wdctl) - exiting"); 78 termination_handler(0); 79 } 80 pthread_detach(tid); 81 82 /* 認證服務器心跳檢測線程 */ 83 result = pthread_create(&tid_ping, NULL, (void *)thread_ping, NULL); 84 if (result != 0) { 85 debug(LOG_ERR, "FATAL: Failed to create a new thread (ping) - exiting"); 86 termination_handler(0); 87 } 88 pthread_detach(tid_ping); 89 90 debug(LOG_NOTICE, "Waiting for connections"); 91 while(1) { 92 /* 監聽2060端口等待用戶http請求 */ 93 r = httpdGetConnection(webserver, NULL); 94 95 /* 錯誤處理 */ 96 if (webserver->lastError == -1) { 97 /* Interrupted system call */ 98 continue; /* restart loop */ 99 } 100 else if (webserver->lastError < -1) { 101 debug(LOG_ERR, "FATAL: httpdGetConnection returned unexpected value %d, exiting.", webserver->lastError); 102 termination_handler(0); 103 } 104 else if (r != NULL) { 105 /* 用戶http請求接收成功 */ 106 debug(LOG_INFO, "Received connection from %s, spawning worker thread", r->clientAddr); 107 params = safe_malloc(2 * sizeof(void *)); 108 *params = webserver; 109 *(params + 1) = r; 110 111 /* 開啟http請求處理線程 */ 112 result = pthread_create(&tid, NULL, (void *)thread_httpd, (void *)params); 113 if (result != 0) { 114 debug(LOG_ERR, "FATAL: Failed to create a new thread (httpd) - exiting"); 115 termination_handler(0); 116 } 117 pthread_detach(tid); 118 } 119 else { 120 ; 121 } 122 } 123 124 /* never reached */ 125 }
用戶連接啟動線程(void thread_httpd(void * args))
代碼片段1.2
此段代碼是當有新用戶(未認證的用戶 代碼片段1.3,已在認證服務器上認證但沒有在wifidog認證的用戶 代碼片段1.4)連接時創建的線程,其主要功能為
- 獲取用戶瀏覽器發送過來的http報頭
- 分析http報頭,分析是否包含關鍵路徑
- 不包含關鍵路徑則調用404回調函數
- 包含關鍵路徑則執行關鍵路徑回調函數(這里主要講解"/wifidog/auth"路徑)
1 void 2 thread_httpd(void *args) 3 { 4 void **params; 5 httpd *webserver; 6 request *r; 7 8 params = (void **)args; 9 webserver = *params; 10 r = *(params + 1); 11 free(params); 12 13 /* 獲取http報文 */ 14 if (httpdReadRequest(webserver, r) == 0) { 15 debug(LOG_DEBUG, "Processing request from %s", r->clientAddr); 16 debug(LOG_DEBUG, "Calling httpdProcessRequest() for %s", r->clientAddr); 17 /* 分析http報文 */ 18 httpdProcessRequest(webserver, r); 19 debug(LOG_DEBUG, "Returned from httpdProcessRequest() for %s", r->clientAddr); 20 } 21 else { 22 debug(LOG_DEBUG, "No valid request received from %s", r->clientAddr); 23 } 24 debug(LOG_DEBUG, "Closing connection with %s", r->clientAddr); 25 httpdEndRequest(r); 26 } 27 28 29 30 /* 被thread_httpd調用 */ 31 void httpdProcessRequest(httpd *server, request *r) 32 { 33 char dirName[HTTP_MAX_URL], 34 entryName[HTTP_MAX_URL], 35 *cp; 36 httpDir *dir; 37 httpContent *entry; 38 39 r->response.responseLength = 0; 40 strncpy(dirName, httpdRequestPath(r), HTTP_MAX_URL); 41 dirName[HTTP_MAX_URL-1]=0; 42 cp = rindex(dirName, '/'); 43 if (cp == NULL) 44 { 45 printf("Invalid request path '%s'\n",dirName); 46 return; 47 } 48 strncpy(entryName, cp + 1, HTTP_MAX_URL); 49 entryName[HTTP_MAX_URL-1]=0; 50 if (cp != dirName) 51 *cp = 0; 52 else 53 *(cp+1) = 0; 54 55 /* 獲取http報文中的關鍵路徑,在main_loop中已經設置 */ 56 dir = _httpd_findContentDir(server, dirName, HTTP_FALSE); 57 if (dir == NULL) 58 { 59 /* http報文中未包含關鍵路徑,執行404回調函數(在404回調函數中新用戶被重定向到認證服務器),見代碼片段1.3 */ 60 _httpd_send404(server, r); 61 _httpd_writeAccessLog(server, r); 62 return; 63 } 64 /* 獲取關鍵路徑內容描述符 */ 65 entry = _httpd_findContentEntry(r, dir, entryName); 66 if (entry == NULL) 67 { 68 _httpd_send404(server, r); 69 _httpd_writeAccessLog(server, r); 70 return; 71 } 72 if (entry->preload) 73 { 74 if ((entry->preload)(server) < 0) 75 { 76 _httpd_writeAccessLog(server, r); 77 return; 78 } 79 } 80 switch(entry->type) 81 { 82 case HTTP_C_FUNCT: 83 case HTTP_C_WILDCARD: 84 /* 如果是被認證服務器重定向到網關的用戶,此處的關鍵路徑為"/wifidog/auth",並執行回調函數 */ 85 (entry->function)(server, r); 86 break; 87 88 case HTTP_STATIC: 89 _httpd_sendStatic(server, r, entry->data); 90 break; 91 92 case HTTP_FILE: 93 _httpd_sendFile(server, r, entry->path); 94 break; 95 96 case HTTP_WILDCARD: 97 if (_httpd_sendDirectoryEntry(server, r, entry, 98 entryName)<0) 99 { 100 _httpd_send404(server, r); 101 } 102 break; 103 } 104 _httpd_writeAccessLog(server, r); 105 }
代碼片段1.3
此段代碼表示wifidog是如何通過http 404回調函數實現客戶端重定向了,實際上就是在404回調函數中封裝了一個307狀態的http報頭,http的307狀態在http協議中就是用於重定向的,封裝完成后通過已經與客戶端連接的socket返回給客戶端。步驟流程為
- 判斷本機是否處於離線狀態
- 判斷認證服務器是否在線
- 封裝http 307報文
- 發送於目標客戶端
1 void 2 http_callback_404(httpd *webserver, request *r) 3 { 4 char tmp_url[MAX_BUF], 5 *url; 6 s_config *config = config_get_config(); 7 t_auth_serv *auth_server = get_auth_server(); 8 9 memset(tmp_url, 0, sizeof(tmp_url)); 10 11 snprintf(tmp_url, (sizeof(tmp_url) - 1), "http://%s%s%s%s", 12 r->request.host, 13 r->request.path, 14 r->request.query[0] ? "?" : "", 15 r->request.query); 16 url = httpdUrlEncode(tmp_url); 17 18 if (!is_online()) { 19 /* 本機處於離線狀態,此函數調用結果由認證服務器檢測線程設置 */ 20 char * buf; 21 safe_asprintf(&buf, 22 "<p>We apologize, but it seems that the internet connection that powers this hotspot is temporarily unavailable.</p>" 23 "<p>If at all possible, please notify the owners of this hotspot that the internet connection is out of service.</p>" 24 "<p>The maintainers of this network are aware of this disruption. We hope that this situation will be resolved soon.</p>" 25 "<p>In a while please <a href='%s'>click here</a> to try your request again.</p>", tmp_url); 26 27 send_http_page(r, "Uh oh! Internet access unavailable!", buf); 28 free(buf); 29 debug(LOG_INFO, "Sent %s an apology since I am not online - no point sending them to auth server", r->clientAddr); 30 } 31 else if (!is_auth_online()) { 32 /* 認證服務器處於離線狀態 */ 33 char * buf; 34 safe_asprintf(&buf, 35 "<p>We apologize, but it seems that we are currently unable to re-direct you to the login screen.</p>" 36 "<p>The maintainers of this network are aware of this disruption. We hope that this situation will be resolved soon.</p>" 37 "<p>In a couple of minutes please <a href='%s'>click here</a> to try your request again.</p>", tmp_url); 38 39 send_http_page(r, "Uh oh! Login screen unavailable!", buf); 40 free(buf); 41 debug(LOG_INFO, "Sent %s an apology since auth server not online - no point sending them to auth server", r->clientAddr); 42 } 43 else { 44 /* 本機與認證服務器都在線,返回重定向包於客戶端 */ 45 char *urlFragment; 46 safe_asprintf(&urlFragment, "%sgw_address=%s&gw_port=%d&gw_id=%s&url=%s", 47 auth_server->authserv_login_script_path_fragment, 48 config->gw_address, 49 config->gw_port, 50 config->gw_id, 51 url); 52 debug(LOG_INFO, "Captured %s requesting [%s] and re-directing them to login page", r->clientAddr, url); 53 http_send_redirect_to_auth(r, urlFragment, "Redirect to login page"); /* 實際上此函數中通過socket返回一個307狀態的http報頭給客戶端,里面包含有認證服務器地址 */ 54 free(urlFragment); 55 } 56 free(url); 57 }
代碼片段1.4
此段表明當客戶端已經在認證服務器確認登陸,認證服務器將客戶端重新重定向回網關,並在重定向包中包含關鍵路徑"/wifidog/auth"和token,認證服務器所執行的操作。
1 void 2 http_callback_auth(httpd *webserver, request *r) 3 { 4 t_client *client; 5 httpVar * token; 6 char *mac; 7 /* 判斷http報文是否包含登出logout */ 8 httpVar *logout = httpdGetVariableByName(r, "logout"); 9 if ((token = httpdGetVariableByName(r, "token"))) { 10 /* 獲取http報文中的token */ 11 if (!(mac = arp_get(r->clientAddr))) { 12 /* 獲取客戶端mac地址失敗 */ 13 debug(LOG_ERR, "Failed to retrieve MAC address for ip %s", r->clientAddr); 14 send_http_page(r, "WiFiDog Error", "Failed to retrieve your MAC address"); 15 } else { 16 LOCK_CLIENT_LIST(); 17 /* 判斷客戶端是否存在於列表中 */ 18 if ((client = client_list_find(r->clientAddr, mac)) == NULL) { 19 debug(LOG_DEBUG, "New client for %s", r->clientAddr); 20 /* 將此客戶端添加到客戶端列表 */ 21 client_list_append(r->clientAddr, mac, token->value); 22 } else if (logout) { 23 /* http報文為登出 */ 24 t_authresponse authresponse; 25 s_config *config = config_get_config(); 26 unsigned long long incoming = client->counters.incoming; 27 unsigned long long outgoing = client->counters.outgoing; 28 char *ip = safe_strdup(client->ip); 29 char *urlFragment = NULL; 30 t_auth_serv *auth_server = get_auth_server(); 31 /* 修改iptables禁止客戶端訪問外網 */ 32 fw_deny(client->ip, client->mac, client->fw_connection_state); 33 /* 從客戶端列表中刪除此客戶端 */ 34 client_list_delete(client); 35 debug(LOG_DEBUG, "Got logout from %s", client->ip); 36 37 if (config->auth_servers != NULL) { 38 UNLOCK_CLIENT_LIST(); 39 /* 發送登出認證包給認證服務器 */ 40 auth_server_request(&authresponse, REQUEST_TYPE_LOGOUT, ip, mac, token->value, 41 incoming, outgoing); 42 LOCK_CLIENT_LIST(); 43 44 /* 將客戶端重定向到認證服務器 */ 45 debug(LOG_INFO, "Got manual logout from client ip %s, mac %s, token %s" 46 "- redirecting them to logout message", client->ip, client->mac, client->token); 47 safe_asprintf(&urlFragment, "%smessage=%s", 48 auth_server->authserv_msg_script_path_fragment, 49 GATEWAY_MESSAGE_ACCOUNT_LOGGED_OUT 50 ); 51 http_send_redirect_to_auth(r, urlFragment, "Redirect to logout message"); 52 free(urlFragment); 53 } 54 free(ip); 55 } 56 else { 57 debug(LOG_DEBUG, "Client for %s is already in the client list", client->ip); 58 } 59 UNLOCK_CLIENT_LIST(); 60 if (!logout) { 61 /* 通過認證服務器認證此客戶端token */ 62 authenticate_client(r); 63 } 64 free(mac); 65 } 66 } else { 67 send_http_page(r, "WiFiDog error", "Invalid token"); 68 } 69 } 70 71 /* 此函數用於提交token到認證服務器進行認證 */ 72 void 73 authenticate_client(request *r) 74 { 75 t_client *client; 76 t_authresponse auth_response; 77 char *mac, 78 *token; 79 char *urlFragment = NULL; 80 s_config *config = NULL; 81 t_auth_serv *auth_server = NULL; 82 83 LOCK_CLIENT_LIST(); 84 85 client = client_list_find_by_ip(r->clientAddr); 86 /* 判斷此客戶端是否在列表中 */ 87 if (client == NULL) { 88 debug(LOG_ERR, "Could not find client for %s", r->clientAddr); 89 UNLOCK_CLIENT_LIST(); 90 return; 91 } 92 93 mac = safe_strdup(client->mac); 94 token = safe_strdup(client->token); 95 96 UNLOCK_CLIENT_LIST(); 97 98 /* 提交token、客戶端ip、客戶端mac至認證服務器 */ 99 auth_server_request(&auth_response, REQUEST_TYPE_LOGIN, r->clientAddr, mac, token, 0, 0); 100 101 LOCK_CLIENT_LIST(); 102 103 /*再次判斷客戶端是否存在於列表中,保險起見,因為有可能在於認證服務器認證過程中,客戶端檢測線程把此客戶端下線 */ 104 client = client_list_find(r->clientAddr, mac); 105 106 if (client == NULL) { 107 debug(LOG_ERR, "Could not find client node for %s (%s)", r->clientAddr, mac); 108 UNLOCK_CLIENT_LIST(); 109 free(token); 110 free(mac); 111 return; 112 } 113 114 free(token); 115 free(mac); 116 117 config = config_get_config(); 118 auth_server = get_auth_server(); 119 120 /* 判斷認證服務器認證結果 */ 121 switch(auth_response.authcode) { 122 123 case AUTH_ERROR: 124 /* 認證錯誤 */ 125 debug(LOG_ERR, "Got %d from central server authenticating token %s from %s at %s", auth_response, client->token, client->ip, client->mac); 126 send_http_page(r, "Error!", "Error: We did not get a valid answer from the central server"); 127 break; 128 129 case AUTH_DENIED: 130 /* 認證服務器拒絕此客戶端 */ 131 debug(LOG_INFO, "Got DENIED from central server authenticating token %s from %s at %s - redirecting them to denied message", client->token, client->ip, client->mac); 132 safe_asprintf(&urlFragment, "%smessage=%s", 133 auth_server->authserv_msg_script_path_fragment, 134 GATEWAY_MESSAGE_DENIED 135 ); 136 http_send_redirect_to_auth(r, urlFragment, "Redirect to denied message"); 137 free(urlFragment); 138 break; 139 140 case AUTH_VALIDATION: 141 /* 認證服務器處於等待此客戶端電子郵件確認回執狀態 */ 142 debug(LOG_INFO, "Got VALIDATION from central server authenticating token %s from %s at %s" 143 "- adding to firewall and redirecting them to activate message", client->token, 144 client->ip, client->mac); 145 client->fw_connection_state = FW_MARK_PROBATION; 146 fw_allow(client->ip, client->mac, FW_MARK_PROBATION); 147 safe_asprintf(&urlFragment, "%smessage=%s", 148 auth_server->authserv_msg_script_path_fragment, 149 GATEWAY_MESSAGE_ACTIVATE_ACCOUNT 150 ); 151 http_send_redirect_to_auth(r, urlFragment, "Redirect to activate message"); 152 free(urlFragment); 153 break; 154 155 case AUTH_ALLOWED: 156 /* 認證通過 */ 157 debug(LOG_INFO, "Got ALLOWED from central server authenticating token %s from %s at %s - " 158 "adding to firewall and redirecting them to portal", client->token, client->ip, client->mac); 159 client->fw_connection_state = FW_MARK_KNOWN; 160 fw_allow(client->ip, client->mac, FW_MARK_KNOWN); 161 served_this_session++; 162 safe_asprintf(&urlFragment, "%sgw_id=%s", 163 auth_server->authserv_portal_script_path_fragment, 164 config->gw_id 165 ); 166 http_send_redirect_to_auth(r, urlFragment, "Redirect to portal"); 167 free(urlFragment); 168 break; 169 170 case AUTH_VALIDATION_FAILED: 171 /* 電子郵件確認回執超時 */ 172 debug(LOG_INFO, "Got VALIDATION_FAILED from central server authenticating token %s from %s at %s " 173 "- redirecting them to failed_validation message", client->token, client->ip, client->mac); 174 safe_asprintf(&urlFragment, "%smessage=%s", 175 auth_server->authserv_msg_script_path_fragment, 176 GATEWAY_MESSAGE_ACCOUNT_VALIDATION_FAILED 177 ); 178 http_send_redirect_to_auth(r, urlFragment, "Redirect to failed validation message"); 179 free(urlFragment); 180 break; 181 182 default: 183 debug(LOG_WARNING, "I don't know what the validation code %d means for token %s from %s at %s - sending error message", auth_response.authcode, client->token, client->ip, client->mac); 184 send_http_page(r, "Internal Error", "We can not validate your request at this time"); 185 break; 186 187 } 188 189 UNLOCK_CLIENT_LIST(); 190 return; 191 }