1 /*
2  * SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <stdlib.h>
7 #include <esp_log.h>
8 #include <esp_err.h>
9 #include <fcntl.h>
10 #include <errno.h>
11 #include <unistd.h>
12 
13 #include <esp_http_server.h>
14 #include "esp_httpd_priv.h"
15 
16 static const char *TAG = "httpd_sess";
17 
18 typedef enum {
19     HTTPD_TASK_NONE = 0,
20     HTTPD_TASK_INIT,            // Init session
21     HTTPD_TASK_GET_ACTIVE,      // Get active session (fd!=-1)
22     HTTPD_TASK_GET_FREE,        // Get free session slot (fd<0)
23     HTTPD_TASK_FIND_FD,         // Find session with specific fd
24     HTTPD_TASK_SET_DESCRIPTOR,  // Set descriptor
25     HTTPD_TASK_DELETE_INVALID,  // Delete invalid session
26     HTTPD_TASK_FIND_LOWEST_LRU, // Find session with lowest lru
27     HTTPD_TASK_CLOSE            // Close session
28 } task_t;
29 
30 typedef struct {
31     task_t task;
32     int fd;
33     fd_set *fdset;
34     int max_fd;
35     struct httpd_data *hd;
36     uint64_t lru_counter;
37     struct sock_db    *session;
38 } enum_context_t;
39 
httpd_sess_enum(struct httpd_data * hd,httpd_session_enum_function enum_function,void * context)40 void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context)
41 {
42     if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
43         return;
44     }
45 
46     struct sock_db *current = hd->hd_sd;
47     struct sock_db *end = hd->hd_sd + hd->config.max_open_sockets - 1;
48 
49     while (current <= end) {
50         if (enum_function && (!enum_function(current, context))) {
51             break;
52         }
53         current++;
54     }
55 }
56 
57 // Check if a FD is valid
fd_is_valid(int fd)58 static int fd_is_valid(int fd)
59 {
60     return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
61 }
62 
enum_function(struct sock_db * session,void * context)63 static int enum_function(struct sock_db *session, void *context)
64 {
65     if ((!session) || (!context)) {
66         return 0;
67     }
68     enum_context_t *ctx = (enum_context_t *) context;
69     int found = 0;
70     switch (ctx->task) {
71     // Initialize session
72     case HTTPD_TASK_INIT:
73         session->fd = -1;
74         session->ctx = NULL;
75         break;
76     // Get active session
77     case HTTPD_TASK_GET_ACTIVE:
78         found = (session->fd != -1);
79         break;
80     // Get free slot
81     case HTTPD_TASK_GET_FREE:
82         found = (session->fd < 0);
83         break;
84     // Find fd
85     case HTTPD_TASK_FIND_FD:
86         found = (session->fd == ctx->fd);
87         break;
88     // Set descriptor
89     case HTTPD_TASK_SET_DESCRIPTOR:
90         if (session->fd != -1) {
91             FD_SET(session->fd, ctx->fdset);
92             if (session->fd > ctx->max_fd) {
93                 ctx->max_fd = session->fd;
94             }
95         }
96         break;
97     // Delete invalid session
98     case HTTPD_TASK_DELETE_INVALID:
99         if (!fd_is_valid(session->fd)) {
100             ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), session->fd);
101             httpd_sess_delete(ctx->hd, session);
102         }
103         break;
104     // Find lowest lru
105     case HTTPD_TASK_FIND_LOWEST_LRU:
106         // Found free slot - no need to check other sessions
107         if (session->fd == -1) {
108             return 0;
109         }
110         // Check/update lowest lru
111         if (session->lru_counter < ctx->lru_counter) {
112             ctx->lru_counter = session->lru_counter;
113             ctx->session = session;
114         }
115         break;
116     case HTTPD_TASK_CLOSE:
117         if (session->fd != -1) {
118             ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), session->fd);
119             httpd_sess_delete(ctx->hd, session);
120         }
121         break;
122     default:
123         return 0;
124     }
125     if (found) {
126         ctx->session = session;
127         return 0;
128     }
129     return 1;
130 }
131 
httpd_sess_close(void * arg)132 static void httpd_sess_close(void *arg)
133 {
134     struct sock_db *sock_db = (struct sock_db *) arg;
135     if (!sock_db) {
136         return;
137     }
138 
139     if (!sock_db->lru_counter && !sock_db->lru_socket) {
140         ESP_LOGD(TAG, "Skipping session close for %d as it seems to be a race condition", sock_db->fd);
141         return;
142     }
143     sock_db->lru_socket = false;
144     struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
145     httpd_sess_delete(hd, sock_db);
146 }
147 
httpd_sess_get_free(struct httpd_data * hd)148 struct sock_db *httpd_sess_get_free(struct httpd_data *hd)
149 {
150     if ((!hd) || (hd->hd_sd_active_count == hd->config.max_open_sockets)) {
151         return NULL;
152     }
153     enum_context_t context = {
154         .task = HTTPD_TASK_GET_FREE
155     };
156     httpd_sess_enum(hd, enum_function, &context);
157     return context.session;
158 }
159 
httpd_is_sess_available(struct httpd_data * hd)160 bool httpd_is_sess_available(struct httpd_data *hd)
161 {
162     return httpd_sess_get_free(hd) ? true : false;
163 }
164 
httpd_sess_get(struct httpd_data * hd,int sockfd)165 struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
166 {
167     if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
168         return NULL;
169     }
170 
171     // Check if called inside a request handler, and the session sockfd in use is same as the parameter
172     // => Just return the pointer to the sock_db corresponding to the request
173     if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
174         return hd->hd_req_aux.sd;
175     }
176 
177     enum_context_t context = {
178         .task = HTTPD_TASK_FIND_FD,
179         .fd = sockfd
180     };
181     httpd_sess_enum(hd, enum_function, &context);
182     return context.session;
183 }
184 
httpd_sess_new(struct httpd_data * hd,int newfd)185 esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
186 {
187     ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
188 
189     if (httpd_sess_get(hd, newfd)) {
190         ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
191         return ESP_FAIL;
192     }
193 
194     struct sock_db *session = httpd_sess_get_free(hd);
195     if (!session) {
196         ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
197         return ESP_FAIL;
198     }
199 
200     // Clear session data
201     memset(session, 0, sizeof (struct sock_db));
202     session->fd = newfd;
203     session->handle = (httpd_handle_t) hd;
204     session->send_fn = httpd_default_send;
205     session->recv_fn = httpd_default_recv;
206 
207     // Call user-defined session opening function
208     if (hd->config.open_fn) {
209         esp_err_t ret = hd->config.open_fn(hd, session->fd);
210         if (ret != ESP_OK) {
211             httpd_sess_delete(hd, session);
212             ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd);
213             return ret;
214         }
215     }
216 
217     // increment number of sessions
218     hd->hd_sd_active_count++;
219     ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
220 
221     return ESP_OK;
222 }
223 
httpd_sess_free_ctx(void ** ctx,httpd_free_ctx_fn_t free_fn)224 void httpd_sess_free_ctx(void **ctx, httpd_free_ctx_fn_t free_fn)
225 {
226     if ((!ctx) || (!*ctx)) {
227         return;
228     }
229     if (free_fn) {
230         free_fn(*ctx);
231     } else {
232         free(*ctx);
233     }
234     *ctx = NULL;
235 }
236 
httpd_sess_clear_ctx(struct sock_db * session)237 void httpd_sess_clear_ctx(struct sock_db *session)
238 {
239     if ((!session) || ((!session->ctx) && (!session->transport_ctx))) {
240         return;
241     }
242 
243     // free user ctx
244     if (session->ctx) {
245         httpd_sess_free_ctx(&session->ctx, session->free_ctx);
246         session->free_ctx = NULL;
247     }
248 
249     // Free 'transport' context
250     if (session->transport_ctx) {
251         httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
252         session->free_transport_ctx = NULL;
253     }
254 }
255 
httpd_sess_get_ctx(httpd_handle_t handle,int sockfd)256 void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
257 {
258     struct sock_db *session = httpd_sess_get(handle, sockfd);
259     if (!session) {
260         return NULL;
261     }
262 
263     // Check if the function has been called from inside a
264     // request handler, in which case fetch the context from
265     // the httpd_req_t structure
266     struct httpd_data *hd = (struct httpd_data *) handle;
267     if (hd->hd_req_aux.sd == session) {
268         return hd->hd_req.sess_ctx;
269     }
270     return session->ctx;
271 }
272 
httpd_sess_set_ctx(httpd_handle_t handle,int sockfd,void * ctx,httpd_free_ctx_fn_t free_fn)273 void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
274 {
275     struct sock_db *session = httpd_sess_get(handle, sockfd);
276     if (!session) {
277         return;
278     }
279 
280     // Check if the function has been called from inside a
281     // request handler, in which case set the context inside
282     // the httpd_req_t structure
283     struct httpd_data *hd = (struct httpd_data *) handle;
284     if (hd->hd_req_aux.sd == session) {
285         if (hd->hd_req.sess_ctx != ctx) {
286             // Don't free previous context if it is in sockdb
287             // as it will be freed inside httpd_req_cleanup()
288             if (session->ctx != hd->hd_req.sess_ctx) {
289                 httpd_sess_free_ctx(&hd->hd_req.sess_ctx, hd->hd_req.free_ctx); // Free previous context
290             }
291             hd->hd_req.sess_ctx = ctx;
292         }
293         hd->hd_req.free_ctx = free_fn;
294         return;
295     }
296 
297     // Else set the context inside the sock_db structure
298     if (session->ctx != ctx) {
299         // Free previous context
300         httpd_sess_free_ctx(&session->ctx, session->free_ctx);
301         session->ctx = ctx;
302     }
303     session->free_ctx = free_fn;
304 }
305 
httpd_sess_get_transport_ctx(httpd_handle_t handle,int sockfd)306 void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
307 {
308     struct sock_db *session = httpd_sess_get(handle, sockfd);
309     return session ? session->transport_ctx : NULL;
310 }
311 
httpd_sess_set_transport_ctx(httpd_handle_t handle,int sockfd,void * ctx,httpd_free_ctx_fn_t free_fn)312 void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
313 {
314     struct sock_db *session = httpd_sess_get(handle, sockfd);
315     if (!session) {
316         return;
317     }
318 
319     if (session->transport_ctx != ctx) {
320         // Free previous transport context
321         httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
322         session->transport_ctx = ctx;
323     }
324     session->free_transport_ctx = free_fn;
325 }
326 
httpd_sess_set_descriptors(struct httpd_data * hd,fd_set * fdset,int * maxfd)327 void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd)
328 {
329     enum_context_t context = {
330         .task = HTTPD_TASK_SET_DESCRIPTOR,
331         .max_fd = -1,
332         .fdset = fdset
333     };
334     httpd_sess_enum(hd, enum_function, &context);
335     if (maxfd) {
336         *maxfd = context.max_fd;
337     }
338 }
339 
httpd_sess_delete_invalid(struct httpd_data * hd)340 void httpd_sess_delete_invalid(struct httpd_data *hd)
341 {
342     enum_context_t context = {
343         .task = HTTPD_TASK_DELETE_INVALID,
344         .hd = hd
345     };
346     httpd_sess_enum(hd, enum_function, &context);
347 }
348 
httpd_sess_delete(struct httpd_data * hd,struct sock_db * session)349 void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session)
350 {
351     if ((!hd) || (!session) || (session->fd < 0)) {
352         return;
353     }
354 
355     ESP_LOGD(TAG, LOG_FMT("fd = %d"), session->fd);
356 
357     // Call close function if defined
358     if (hd->config.close_fn) {
359         hd->config.close_fn(hd, session->fd);
360     } else {
361         close(session->fd);
362     }
363 
364     // clear all contexts
365     httpd_sess_clear_ctx(session);
366 
367     // mark session slot as available
368     session->fd = -1;
369 
370     // decrement number of sessions
371     hd->hd_sd_active_count--;
372     ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
373     if (!hd->hd_sd_active_count) {
374         hd->lru_counter = 0;
375     }
376 }
377 
httpd_sess_init(struct httpd_data * hd)378 void httpd_sess_init(struct httpd_data *hd)
379 {
380     enum_context_t context = {
381         .task = HTTPD_TASK_INIT
382     };
383     httpd_sess_enum(hd, enum_function, &context);
384 }
385 
httpd_sess_pending(struct httpd_data * hd,struct sock_db * session)386 bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session)
387 {
388     if (!session) {
389         return false;
390     }
391     if (session->pending_fn) {
392         // test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
393         // this should check e.g. for the SSL data buffer
394         if (session->pending_fn(hd, session->fd) > 0) {
395             return true;
396         }
397     }
398     return (session->pending_len != 0);
399 }
400 
401 /* This MUST return ESP_OK on successful execution. If any other
402  * value is returned, everything related to this socket will be
403  * cleaned up and the socket will be closed.
404  */
httpd_sess_process(struct httpd_data * hd,struct sock_db * session)405 esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session)
406 {
407     if ((!hd) || (!session)) {
408         return ESP_FAIL;
409     }
410 
411     ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
412     if (httpd_req_new(hd, session) != ESP_OK) {
413         return ESP_FAIL;
414     }
415     ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
416     if (httpd_req_delete(hd) != ESP_OK) {
417         return ESP_FAIL;
418     }
419     ESP_LOGD(TAG, LOG_FMT("success"));
420     session->lru_counter = ++hd->lru_counter;
421     return ESP_OK;
422 }
423 
httpd_sess_update_lru_counter(httpd_handle_t handle,int sockfd)424 esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
425 {
426     if (handle == NULL) {
427         return ESP_ERR_INVALID_ARG;
428     }
429 
430     struct httpd_data *hd = (struct httpd_data *) handle;
431 
432     enum_context_t context = {
433         .task = HTTPD_TASK_FIND_FD,
434         .fd = sockfd
435     };
436     httpd_sess_enum(hd, enum_function, &context);
437     if (context.session) {
438         context.session->lru_counter = ++hd->lru_counter;
439         return ESP_OK;
440     }
441     return ESP_ERR_NOT_FOUND;
442 }
443 
httpd_sess_close_lru(struct httpd_data * hd)444 esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
445 {
446     enum_context_t context = {
447         .task = HTTPD_TASK_FIND_LOWEST_LRU,
448         .lru_counter = UINT64_MAX,
449         .fd = -1
450     };
451     httpd_sess_enum(hd, enum_function, &context);
452     if (!context.session) {
453         return ESP_OK;
454     }
455     ESP_LOGD(TAG, LOG_FMT("Closing session with fd %d"), context.session->fd);
456     context.session->lru_socket = true;
457     return httpd_sess_trigger_close_(hd, context.session);
458 }
459 
httpd_sess_trigger_close_(httpd_handle_t handle,struct sock_db * session)460 esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session)
461 {
462     if (!session) {
463         return ESP_ERR_NOT_FOUND;
464     }
465     return httpd_queue_work(handle, httpd_sess_close, session);
466 }
467 
httpd_sess_trigger_close(httpd_handle_t handle,int sockfd)468 esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
469 {
470     struct sock_db *session = httpd_sess_get(handle, sockfd);
471     if (!session) {
472         return ESP_ERR_NOT_FOUND;
473     }
474     return httpd_sess_trigger_close_(handle, session);
475 }
476 
httpd_sess_close_all(struct httpd_data * hd)477 void httpd_sess_close_all(struct httpd_data *hd)
478 {
479     enum_context_t context = {
480         .task = HTTPD_TASK_CLOSE,
481         .hd = hd
482     };
483     httpd_sess_enum(hd, enum_function, &context);
484 }
485