/[LeafOK_CVS]/lbbs/src/net_server.c
ViewVC logotype

Contents of /lbbs/src/net_server.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.70 - (show annotations)
Sun Jun 29 03:32:39 2025 UTC (8 months, 2 weeks ago) by sysadm
Branch: MAIN
Changes since 1.69: +38 -5 lines
Content type: text/x-csrc
Send SIGKILL to child process which does not exit in time, to avoid blocking main process exit normally.

1 /***************************************************************************
2 net_server.c - description
3 -------------------
4 Copyright : (C) 2004-2025 by Leaflet
5 Email : leaflet@leafok.com
6 ***************************************************************************/
7
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 3 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 #include "bbs.h"
18 #include "bbs_main.h"
19 #include "common.h"
20 #include "database.h"
21 #include "file_loader.h"
22 #include "io.h"
23 #include "init.h"
24 #include "log.h"
25 #include "login.h"
26 #include "menu.h"
27 #include "net_server.h"
28 #include "section_list.h"
29 #include "section_list_loader.h"
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <signal.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <arpa/inet.h>
37 #include <libssh/callbacks.h>
38 #include <libssh/libssh.h>
39 #include <libssh/server.h>
40 #include <netinet/in.h>
41 #include <sys/epoll.h>
42 #include <sys/socket.h>
43 #include <sys/syscall.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 #include <systemd/sd-daemon.h>
47
48 #define WAIT_CHILD_PROCESS_EXIT_TIMEOUT 5 // second
49 #define WAIT_CHILD_PROCESS_KILL_TIMEOUT 1 // second
50
51 struct process_sockaddr_t
52 {
53 pid_t pid;
54 in_addr_t s_addr;
55 };
56 typedef struct process_sockaddr_t PROCESS_SOCKADDR;
57
58 static PROCESS_SOCKADDR process_sockaddr_pool[MAX_CLIENT_LIMIT];
59
60 #define SSH_AUTH_MAX_DURATION (60 * 1000) // milliseconds
61
62 struct ssl_server_cb_data_t
63 {
64 int tries;
65 int error;
66 };
67
68 static int auth_password(ssh_session session, const char *user,
69 const char *password, void *userdata)
70 {
71 struct ssl_server_cb_data_t *p_data = userdata;
72 int ret;
73
74 if (strcmp(user, "guest") == 0)
75 {
76 ret = load_guest_info();
77 }
78 else
79 {
80 ret = check_user(user, password);
81 }
82
83 if (ret == 0)
84 {
85 return SSH_AUTH_SUCCESS;
86 }
87
88 if ((++(p_data->tries)) >= BBS_login_retry_times)
89 {
90 p_data->error = 1;
91 }
92
93 return SSH_AUTH_DENIED;
94 }
95
96 static int pty_request(ssh_session session, ssh_channel channel, const char *term,
97 int x, int y, int px, int py, void *userdata)
98 {
99 return 0;
100 }
101
102 static int shell_request(ssh_session session, ssh_channel channel, void *userdata)
103 {
104 return 0;
105 }
106
107 static struct ssh_channel_callbacks_struct channel_cb = {
108 .channel_pty_request_function = pty_request,
109 .channel_shell_request_function = shell_request};
110
111 static ssh_channel new_session_channel(ssh_session session, void *userdata)
112 {
113 if (SSH_channel != NULL)
114 {
115 return NULL;
116 }
117
118 SSH_channel = ssh_channel_new(session);
119 ssh_callbacks_init(&channel_cb);
120 ssh_set_channel_callbacks(SSH_channel, &channel_cb);
121
122 return SSH_channel;
123 }
124
125 static int fork_server(void)
126 {
127 ssh_event event;
128 int pid;
129 int i;
130 int ret;
131
132 struct ssl_server_cb_data_t cb_data = {
133 .tries = 0,
134 .error = 0,
135 };
136
137 struct ssh_server_callbacks_struct cb = {
138 .userdata = &cb_data,
139 .auth_password_function = auth_password,
140 .channel_open_request_session_function = new_session_channel,
141 };
142
143 pid = fork();
144
145 if (pid > 0) // Parent process
146 {
147 SYS_child_process_count++;
148 log_common("Child process (%d) start\n", pid);
149 return pid;
150 }
151 else if (pid < 0) // Error
152 {
153 log_error("fork() error (%d)\n", errno);
154 return -1;
155 }
156
157 // Child process
158
159 if (close(socket_server[0]) == -1 || close(socket_server[1]) == -1)
160 {
161 log_error("Close server socket failed\n");
162 }
163
164 SSH_session = ssh_new();
165
166 if (SSH_v2)
167 {
168 if (ssh_bind_accept_fd(sshbind, SSH_session, socket_client) != SSH_OK)
169 {
170 log_error("ssh_bind_accept_fd() error: %s\n", ssh_get_error(SSH_session));
171 goto cleanup;
172 }
173
174 ssh_bind_free(sshbind);
175
176 ssh_callbacks_init(&cb);
177 ssh_set_server_callbacks(SSH_session, &cb);
178
179 if (ssh_handle_key_exchange(SSH_session))
180 {
181 log_error("ssh_handle_key_exchange() error: %s\n", ssh_get_error(SSH_session));
182 goto cleanup;
183 }
184 ssh_set_auth_methods(SSH_session, SSH_AUTH_METHOD_PASSWORD);
185
186 event = ssh_event_new();
187 ssh_event_add_session(event, SSH_session);
188
189 for (i = 0; i < SSH_AUTH_MAX_DURATION && !SYS_server_exit && !cb_data.error && SSH_channel == NULL; i += 100)
190 {
191 ret = ssh_event_dopoll(event, 100); // 0.1 second
192 if (ret == SSH_ERROR)
193 {
194 log_error("ssh_event_dopoll() error: %s\n", ssh_get_error(SSH_session));
195 goto cleanup;
196 }
197 }
198
199 if (cb_data.error)
200 {
201 log_error("SSH auth error, tried %d times\n", cb_data.tries);
202 goto cleanup;
203 }
204 }
205
206 // Redirect Input
207 close(STDIN_FILENO);
208 if (dup2(socket_client, STDIN_FILENO) == -1)
209 {
210 log_error("Redirect stdin to client socket failed\n");
211 goto cleanup;
212 }
213
214 // Redirect Output
215 close(STDOUT_FILENO);
216 if (dup2(socket_client, STDOUT_FILENO) == -1)
217 {
218 log_error("Redirect stdout to client socket failed\n");
219 goto cleanup;
220 }
221
222 SYS_child_process_count = 0;
223
224 bbs_main();
225
226 cleanup:
227 // Child process exit
228 SYS_server_exit = 1;
229
230 if (SSH_v2)
231 {
232 ssh_channel_free(SSH_channel);
233 ssh_disconnect(SSH_session);
234 }
235 else if (close(socket_client) == -1)
236 {
237 log_error("Close client socket failed\n");
238 }
239
240 ssh_free(SSH_session);
241 ssh_finalize();
242
243 // Close Input and Output for client
244 close(STDIN_FILENO);
245 close(STDOUT_FILENO);
246
247 log_common("Process exit normally\n");
248 log_end();
249
250 _exit(0);
251
252 return 0;
253 }
254
255 int net_server(const char *hostaddr, in_port_t port[])
256 {
257 unsigned int addrlen;
258 int ret;
259 int flags[2];
260 struct sockaddr_in sin;
261 struct epoll_event ev, events[MAX_EVENTS];
262 int nfds, epollfd;
263 siginfo_t siginfo;
264 int notify_child_exit = 0;
265 time_t tm_notify_child_exit = time(NULL);
266 int sd_notify_stopping = 0;
267 MENU_SET bbs_menu_new;
268 int i, j;
269 pid_t pid;
270 int ssh_log_level = SSH_LOG_NOLOG;
271
272 ssh_init();
273
274 sshbind = ssh_bind_new();
275
276 if (ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, hostaddr) < 0 ||
277 ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT, &port) < 0 ||
278 ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, SSH_HOST_KEYFILE) < 0 ||
279 ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, "ssh-rsa,rsa-sha2-512,rsa-sha2-256") < 0 ||
280 ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &ssh_log_level) < 0)
281 {
282 log_error("Error setting SSH bind options: %s\n", ssh_get_error(sshbind));
283 ssh_bind_free(sshbind);
284 return -1;
285 }
286
287 epollfd = epoll_create1(0);
288 if (epollfd < 0)
289 {
290 log_error("epoll_create1() error (%d)\n", errno);
291 return -1;
292 }
293
294 // Server socket
295 for (i = 0; i < 2; i++)
296 {
297 socket_server[i] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
298
299 if (socket_server[i] < 0)
300 {
301 log_error("Create socket_server error (%d)\n", errno);
302 return -1;
303 }
304
305 sin.sin_family = AF_INET;
306 sin.sin_addr.s_addr = (hostaddr[0] != '\0' ? inet_addr(hostaddr) : INADDR_ANY);
307 sin.sin_port = htons(port[i]);
308
309 // Reuse address and port
310 flags[i] = 1;
311 if (setsockopt(socket_server[i], SOL_SOCKET, SO_REUSEADDR, &flags[i], sizeof(flags[i])) < 0)
312 {
313 log_error("setsockopt SO_REUSEADDR error (%d)\n", errno);
314 }
315 if (setsockopt(socket_server[i], SOL_SOCKET, SO_REUSEPORT, &flags[i], sizeof(flags[i])) < 0)
316 {
317 log_error("setsockopt SO_REUSEPORT error (%d)\n", errno);
318 }
319
320 if (bind(socket_server[i], (struct sockaddr *)&sin, sizeof(sin)) < 0)
321 {
322 log_error("Bind address %s:%u error (%d)\n",
323 inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), errno);
324 return -1;
325 }
326
327 if (listen(socket_server[i], 10) < 0)
328 {
329 log_error("Telnet socket listen error (%d)\n", errno);
330 return -1;
331 }
332
333 log_common("Listening at %s:%u\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
334
335 ev.events = EPOLLIN;
336 ev.data.fd = socket_server[i];
337 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, socket_server[i], &ev) == -1)
338 {
339 log_error("epoll_ctl(socket_server[%d]) error (%d)\n", i, errno);
340 if (close(epollfd) < 0)
341 {
342 log_error("close(epoll) error (%d)\n");
343 }
344 return -1;
345 }
346
347 flags[i] = fcntl(socket_server[i], F_GETFL, 0);
348 fcntl(socket_server[i], F_SETFL, flags[i] | O_NONBLOCK);
349 }
350
351 // Startup complete
352 sd_notifyf(0, "READY=1\n"
353 "STATUS=Listening at %s:%d (Telnet) and %s:%d (SSH2)\n"
354 "MAINPID=%d",
355 hostaddr, port[0], hostaddr, port[1], getpid());
356
357 while (!SYS_server_exit || SYS_child_process_count > 0)
358 {
359 if (SYS_server_exit && !sd_notify_stopping)
360 {
361 sd_notify(0, "STOPPING=1");
362 sd_notify_stopping = 1;
363 }
364
365 while ((SYS_child_exit || SYS_server_exit) && SYS_child_process_count > 0)
366 {
367 SYS_child_exit = 0;
368
369 siginfo.si_pid = 0;
370 ret = waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG);
371 if (ret == 0 && siginfo.si_pid > 0)
372 {
373 SYS_child_exit = 1; // Retry waitid
374
375 SYS_child_process_count--;
376 log_common("Child process (%d) exited\n", siginfo.si_pid);
377
378 if (siginfo.si_pid != section_list_loader_pid)
379 {
380 i = 0;
381 for (; i < BBS_max_client; i++)
382 {
383 if (process_sockaddr_pool[i].pid == siginfo.si_pid)
384 {
385 process_sockaddr_pool[i].pid = 0;
386 break;
387 }
388 }
389 if (i >= BBS_max_client)
390 {
391 log_error("Child process (%d) not found in process sockaddr pool\n", siginfo.si_pid);
392 }
393 }
394 }
395 else if (ret == 0)
396 {
397 break;
398 }
399 else if (ret < 0)
400 {
401 log_error("Error in waitid: %d\n", errno);
402 break;
403 }
404 }
405
406 if (SYS_server_exit && !SYS_child_exit && SYS_child_process_count > 0)
407 {
408 if (notify_child_exit == 0)
409 {
410 sd_notifyf(0, "STATUS=Notify %d child process to exit", SYS_child_process_count);
411 log_common("Notify %d child process to exit\n", SYS_child_process_count);
412
413 if (kill(0, SIGTERM) < 0)
414 {
415 log_error("Send SIGTERM signal failed (%d)\n", errno);
416 }
417
418 notify_child_exit = 1;
419 tm_notify_child_exit = time(NULL);
420 }
421 else if (notify_child_exit == 1 && time(NULL) - tm_notify_child_exit >= WAIT_CHILD_PROCESS_EXIT_TIMEOUT)
422 {
423 sd_notifyf(0, "STATUS=Kill %d child process", SYS_child_process_count);
424
425 for (i = 0; i < BBS_max_client; i++)
426 {
427 if (process_sockaddr_pool[i].pid != 0)
428 {
429 log_error("Kill child process (pid=%d)\n", process_sockaddr_pool[i].pid);
430 if (kill(process_sockaddr_pool[i].pid, SIGKILL) < 0)
431 {
432 log_error("Send SIGKILL signal failed (%d)\n", errno);
433 }
434 }
435 }
436
437 notify_child_exit = 2;
438 tm_notify_child_exit = time(NULL);
439 }
440 else if (notify_child_exit == 2 && time(NULL) - tm_notify_child_exit >= WAIT_CHILD_PROCESS_KILL_TIMEOUT)
441 {
442 log_error("Main process prepare to exit without waiting for %d child process any longer\n", SYS_child_process_count);
443 SYS_child_process_count = 0;
444 }
445 }
446
447 if (SYS_conf_reload && !SYS_server_exit)
448 {
449 SYS_conf_reload = 0;
450 sd_notify(0, "RELOADING=1");
451
452 // Reload configuration
453 if (load_conf(CONF_BBSD) < 0)
454 {
455 log_error("Reload conf failed\n");
456 }
457
458 if (load_menu(&bbs_menu_new, CONF_MENU) < 0)
459 {
460 unload_menu(&bbs_menu_new);
461 log_error("Reload menu failed\n");
462 }
463 else
464 {
465 unload_menu(&bbs_menu);
466 memcpy(&bbs_menu, &bbs_menu_new, sizeof(bbs_menu_new));
467 log_common("Reload menu successfully\n");
468 }
469
470 for (int i = 0; i < data_files_load_startup_count; i++)
471 {
472 if (load_file(data_files_load_startup[i]) < 0)
473 {
474 log_error("load_file_mmap(%s) error\n", data_files_load_startup[i]);
475 }
476 }
477 log_common("Reload data files successfully\n");
478
479 // Load section config and gen_ex
480 if (load_section_config_from_db(1) < 0)
481 {
482 log_error("load_section_config_from_db(1) error\n");
483 }
484 else
485 {
486 log_common("Reload section config and gen_ex successfully\n");
487 }
488
489 sd_notify(0, "READY=1");
490 }
491
492 nfds = epoll_wait(epollfd, events, MAX_EVENTS, 100); // 0.1 second
493
494 if (nfds < 0)
495 {
496 if (errno != EINTR)
497 {
498 log_error("epoll_wait() error (%d)\n", errno);
499 break;
500 }
501 continue;
502 }
503
504 // Stop accept new connection on exit
505 if (SYS_server_exit)
506 {
507 continue;
508 }
509
510 for (int i = 0; i < nfds; i++)
511 {
512 if (events[i].data.fd == socket_server[0] || events[i].data.fd == socket_server[1])
513 {
514 SSH_v2 = (events[i].data.fd == socket_server[1] ? 1 : 0);
515
516 while (!SYS_server_exit) // Accept all incoming connections until error
517 {
518 addrlen = sizeof(sin);
519 socket_client = accept(socket_server[SSH_v2], (struct sockaddr *)&sin, &addrlen);
520 if (socket_client < 0)
521 {
522 if (errno == EAGAIN || errno == EWOULDBLOCK)
523 {
524 break;
525 }
526 else if (errno == EINTR)
527 {
528 continue;
529 }
530 else
531 {
532 log_error("accept(socket_server) error (%d)\n", errno);
533 break;
534 }
535 }
536
537 strncpy(hostaddr_client, inet_ntoa(sin.sin_addr), sizeof(hostaddr_client) - 1);
538 hostaddr_client[sizeof(hostaddr_client) - 1] = '\0';
539
540 port_client = ntohs(sin.sin_port);
541
542 log_common("Accept %s connection from %s:%d\n", (SSH_v2 ? "SSH" : "telnet"), hostaddr_client, port_client);
543
544 if (SYS_child_process_count - 1 < BBS_max_client)
545 {
546 j = 0;
547 for (i = 0; i < BBS_max_client; i++)
548 {
549 if (process_sockaddr_pool[i].pid != 0 && process_sockaddr_pool[i].s_addr == sin.sin_addr.s_addr)
550 {
551 j++;
552 if (j >= BBS_max_client_per_ip)
553 {
554 log_common("Too many client connections (%d) from %s\n", j, hostaddr_client);
555 break;
556 }
557 }
558 }
559
560 if (j < BBS_max_client_per_ip)
561 {
562 if ((pid = fork_server()) < 0)
563 {
564 log_error("fork_server() error\n");
565 }
566 else if (pid > 0)
567 {
568 i = 0;
569 for (; i < BBS_max_client; i++)
570 {
571 if (process_sockaddr_pool[i].pid == 0)
572 {
573 break;
574 }
575 }
576
577 if (i >= BBS_max_client)
578 {
579 log_error("Process sockaddr pool depleted\n");
580 }
581 else
582 {
583 process_sockaddr_pool[i].pid = pid;
584 process_sockaddr_pool[i].s_addr = sin.sin_addr.s_addr;
585 }
586 }
587 }
588 }
589 else
590 {
591 log_error("Rejected client connection over limit (%d)\n", SYS_child_process_count - 1);
592 }
593
594 if (close(socket_client) == -1)
595 {
596 log_error("close(socket_lient) error (%d)\n", errno);
597 }
598 }
599 }
600 }
601 }
602
603 if (close(epollfd) < 0)
604 {
605 log_error("close(epoll) error (%d)\n");
606 }
607
608 for (i = 0; i < 2; i++)
609 {
610 fcntl(socket_server[i], F_SETFL, flags[i]);
611
612 if (close(socket_server[i]) == -1)
613 {
614 log_error("Close server socket failed\n");
615 }
616 }
617
618 ssh_bind_free(sshbind);
619 ssh_finalize();
620
621 return 0;
622 }

webmaster@leafok.com
ViewVC Help
Powered by ViewVC 1.3.0-beta1