Seregon/zftpd

Zero-copy FTP/HTTP Daemon compatible with all POSIX systems

C/11.0 KB/No license
src/main.c
zftpd / src / main.c
1/*
2MIT License
3 
4Copyright (c) 2026 Seregon
5 
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12 
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15 
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24 
25/**
26 * @file main.c
27 * @brief FTP server entry point (multi-platform)
28 *
29 * @author SeregonWar
30 * @version 1.2.0
31 * @date 2026-02-13
32 *
33 * PLATFORMS: Linux, PS3, PS4, PS5
34 *
35 */
36 
37#include "ftp_config.h"
38#include "ftp_server.h"
39#include "pal_fileio.h"
40#include "pal_network.h"
41#include "pal_notification.h"
42#include <errno.h>
43#include <getopt.h>
44#include <inttypes.h>
45#include <pthread.h>
46#include <signal.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <string.h>
50#include <unistd.h>
51 
52/*---------------------------------------------------------------------------*
53 * ZHTTPD (Web File Explorer) — conditional compilation
54 *---------------------------------------------------------------------------*/
55#ifndef ENABLE_ZHTTPD
56#define ENABLE_ZHTTPD 0
57#endif
58 
59#if ENABLE_ZHTTPD
60#include "event_loop.h"
61#include "ftp_log.h"
62#include "http_api.h"
63#include "http_config.h"
64#include "http_csrf.h"
65#include "http_server.h"
66 
67static event_loop_t *g_event_loop = NULL;
68static http_server_t *g_http_server = NULL;
69 
70/**
71 * @brief Background thread running the HTTP event loop
72 *
73 * The FTP server runs its own accept loop in a separate thread.
74 * This thread drives the non-blocking HTTP event loop alongside it.
75 */
76static void *http_event_loop_thread(void *arg) {
77 event_loop_t *loop = (event_loop_t *)arg;
78 event_loop_run(loop);
79 return NULL;
80}
81 
82static int start_http_thread(pthread_t *thread, event_loop_t *loop) {
83 if ((thread == NULL) || (loop == NULL)) {
84 return -1;
85 }
86 
87 pthread_attr_t attr;
88 int rc = pthread_attr_init(&attr);
89 if (rc != 0) {
90 return rc;
91 }
92 
93 rc = pthread_attr_setstacksize(&attr, (size_t)HTTP_THREAD_STACK_SIZE);
94 if (rc == 0) {
95 rc = pthread_create(thread, &attr, http_event_loop_thread, loop);
96 }
97 
98 (void)pthread_attr_destroy(&attr);
99 return rc;
100}
101#endif /* ENABLE_ZHTTPD */
102 
103/*---------------------------------------------------------------------------*
104 * MCP (Model Context Protocol) — Native Kernel Analysis Module
105 *---------------------------------------------------------------------------*/
106#ifndef ENABLE_MCP
107#define ENABLE_MCP 0
108#endif
109 
110#if ENABLE_MCP
111#include "mcp_server.h"
112#endif /* ENABLE_MCP */
113 
114#if defined(PLATFORM_PS4) || defined(PLATFORM_PS5)
115#include "pal_scratch.h"
116#include <stdint.h>
117#include <sys/syscall.h>
118#include <sys/sysctl.h>
119#endif
120 
121/*===========================================================================*
122 * GLOBAL SERVER CONTEXT
123 *===========================================================================*/
124 
125static ftp_server_context_t g_server_ctx;
126static volatile sig_atomic_t g_shutdown_requested = 0;
127 
128#if defined(PLATFORM_PS4) || defined(PLATFORM_PS5)
129static pid_t find_pid_by_name(const char *name) {
130 int mib[4] = {1, 14, 8, 0};
131 pid_t mypid = getpid();
132 pid_t pid = -1;
133 size_t buf_size = 0;
134 uint8_t *buf = NULL;
135 
136 if (sysctl(mib, 4, NULL, &buf_size, NULL, 0) != 0) {
137 return -1;
138 }
139 
140 if (pal_scratch_acquire(&buf, buf_size) != 0) {
141 return -1;
142 }
143 
144 if (sysctl(mib, 4, buf, &buf_size, NULL, 0) != 0) {
145 pal_scratch_release(buf);
146 return -1;
147 }
148 
149 for (uint8_t *ptr = buf; ptr < (buf + buf_size);) {
150 int ki_structsize = 0;
151 pid_t ki_pid = -1;
152 memcpy(&ki_structsize, ptr, sizeof(ki_structsize));
153 memcpy(&ki_pid, &ptr[72], sizeof(ki_pid));
154 char *ki_tdname = (char *)&ptr[447];
155 
156 if (ki_structsize <= 0) {
157 break;
158 }
159 ptr += ki_structsize;
160 if ((ki_pid != mypid) && (strcmp(name, ki_tdname) == 0)) {
161 pid = ki_pid;
162 }
163 }
164 
165 pal_scratch_release(buf);
166 return pid;
167}
168 
169static void terminate_existing_instance(const char *name) {
170 if (name == NULL) {
171 return;
172 }
173 
174 for (uint8_t i = 0U; i < 5U; i++) {
175 pid_t pid = find_pid_by_name(name);
176 if (pid <= 0) {
177 return;
178 }
179 (void)kill(pid, SIGKILL);
180 sleep(1);
181 }
182}
183 
184static ftp_error_t server_init_with_fallback(const char *bind_ip,
185 uint16_t base_port,
186 const char *root_path,
187 uint16_t *selected_port) {
188 if ((bind_ip == NULL) || (root_path == NULL) || (selected_port == NULL)) {
189 return FTP_ERR_INVALID_PARAM;
190 }
191 
192 for (uint16_t off = 0U; off < 10U; off++) {
193 uint16_t port = (uint16_t)(base_port + off);
194 ftp_error_t err = ftp_server_init(&g_server_ctx, bind_ip, port, root_path);
195 if (err == FTP_OK) {
196 *selected_port = port;
197 return FTP_OK;
198 }
199 if ((err == FTP_ERR_SOCKET_BIND) && (errno == EADDRINUSE)) {
200 continue;
201 }
202 return err;
203 }
204 
205 return FTP_ERR_SOCKET_BIND;
206}
207#endif
208 
209/*===========================================================================*
210 * SIGNAL HANDLERS
211 *===========================================================================*/
212 
213#ifndef PLATFORM_PS4
214/**
215 * @brief Signal handler for graceful shutdown
216 */
217static void signal_handler(int sig) {
218 (void)sig; /* Unused */
219 g_shutdown_requested = 1;
220}
221 
222/**
223 * @brief Install signal handlers
224 */
225static void install_signal_handlers(void) {
226 /* Standard POSIX signals */
227 signal(SIGINT, signal_handler); /* Ctrl+C */
228 signal(SIGTERM, signal_handler); /* kill */
229 signal(SIGPIPE, SIG_IGN); /* Broken pipe (ignore) */
230}
231#endif
232 
233/*===========================================================================*
234 * PLATFORM-SPECIFIC CODE
235 *===========================================================================*/
236 
237#ifdef PLATFORM_PS4
238 
239int main(void) {
240 printf("[zftpd - ps4] Version " RELEASE_VERSION "\n");
241 printf("[zftpd - ps4] Initializing...\n");
242 
243 (void)pal_notification_init();
244 
245 (void)syscall(SYS_thr_set_name, -1, "zftpd.elf");
246 
247 const char *bind_ip = "0.0.0.0";
248 char display_ip[INET_ADDRSTRLEN];
249 if (pal_network_get_primary_ip(display_ip, sizeof(display_ip)) != FTP_OK) {
250 (void)snprintf(display_ip, sizeof(display_ip), "%s", bind_ip);
251 }
252 
253 const char *root_path = "/";
254 
255 /* Initialize server */
256 uint16_t selected_port = FTP_DEFAULT_PORT;
257 pid_t existing = find_pid_by_name("zftpd.elf");
258 if (existing > 0) {
259 {
260 char msg[160];
261 (void)snprintf(msg, sizeof(msg), "zftpd v%s: port %u in use by zftpd",
262 RELEASE_VERSION, (unsigned)FTP_DEFAULT_PORT);
263 pal_notification_send(msg);
264 }
265 terminate_existing_instance("zftpd.elf");
266 {
267 char msg[160];
268 (void)snprintf(msg, sizeof(msg), "zftpd v%s: process terminated",
269 RELEASE_VERSION);
270 pal_notification_send(msg);
271 }
272 }
273 
274 ftp_error_t err = server_init_with_fallback(bind_ip, FTP_DEFAULT_PORT,
275 root_path, &selected_port);
276 
277 if (err != FTP_OK) {
278 printf("[zftpd - ps4] Initialization failed: %d\n", (int)err);
279 if (err == FTP_ERR_SOCKET_BIND) {
280 printf("[zftpd - ps4] Bind failed: %s\n", strerror(errno));
281 printf("[zftpd - ps4] Hint: port busy or invalid bind_ip.\n");
282 }
283 {
284 char msg[160];
285 (void)snprintf(msg, sizeof(msg), "zftpd v%s: init failed (%d)",
286 RELEASE_VERSION, (int)err);
287 pal_notification_send(msg);
288 }
289 return -1;
290 }
291 
292 if (selected_port != FTP_DEFAULT_PORT) {
293 char msg[160];
294 (void)snprintf(msg, sizeof(msg), "zftpd v%s: port %u in use, fallback %u",
295 RELEASE_VERSION, (unsigned)FTP_DEFAULT_PORT,
296 (unsigned)selected_port);
297 pal_notification_send(msg);
298 }
299 
300 printf("[zftpd - ps4] Listening on %s:%u\n", display_ip, selected_port);
301 printf("[zftpd - ps4] Root: %s\n", root_path);
302 
303 /* Start server */
304 err = ftp_server_start(&g_server_ctx);
305 
306 if (err != FTP_OK) {
307 printf("[zftpd - ps4] Failed to start: %d\n", (int)err);
308 ftp_server_cleanup(&g_server_ctx);
309 return -1;
310 }
311 
312 printf("[zftpd - ps4] Server started successfully\n");
313 
314 {
315 char notify_msg[128];
316 (void)snprintf(notify_msg, sizeof(notify_msg), "zftpd by SeregonWar v%s (PS4) started",
317 RELEASE_VERSION);
318 pal_notification_send(notify_msg);
319 
320 (void)snprintf(notify_msg, sizeof(notify_msg), "FTP: %s:%u", display_ip,
321 (unsigned)selected_port);
322 pal_notification_send(notify_msg);
323 }
324 
325 /*=========================================================================*
326 * ZHTTPD — Start Web File Explorer
327 *=========================================================================*/
328#if ENABLE_ZHTTPD
329#if ENABLE_MCP
330 static mcp_server_t *g_mcp_server = NULL;
331#endif
332 if (http_csrf_init() != 0) {
333 ftp_log_line(FTP_LOG_WARN, "CSRF init failed: web upload disabled");
334 }
335 /* Attach FTP context so /api/network/reset can reach the session pool */
336 http_api_set_server_ctx(&g_server_ctx);
337 g_event_loop = event_loop_create();
338 if (g_event_loop != NULL) {
339 char http_bind[64];
340 (void)snprintf(http_bind, sizeof(http_bind), "[::]:%u",
341 (unsigned)HTTP_DEFAULT_PORT);
342 g_http_server =
343 http_server_create(g_event_loop, http_bind, root_path);
344 if (g_http_server != NULL) {
345 pthread_t http_thread;
346 int rc = start_http_thread(&http_thread, g_event_loop);
347 
348 if (rc == 0) {
349 (void)pthread_detach(http_thread);
350 printf("[PS4 HTTP] Web Explorer: http://%s:%u\n", display_ip,
351 (unsigned)HTTP_DEFAULT_PORT);
352 {
353 char msg[128];
354 (void)snprintf(msg, sizeof(msg), "HTTP: %s:%u", display_ip,
355 (unsigned)HTTP_DEFAULT_PORT);
356 pal_notification_send(msg);
357 }
358 } else {
359 printf("[PS4 HTTP] Failed to start HTTP thread (rc=%d)\n", rc);
360 }
361 }
362 
363 /*=========================================================================*
364 * MCP — Start Kernel Analysis Server
365 *=========================================================================*/
366#if ENABLE_MCP
367 printf("[PS4 MCP] Initializing MCP server...\n");
368 pal_notification_send("MCP: Initializing server...");
369
370 if (g_event_loop == NULL) {
371 printf("[PS4 MCP] ERROR: Event loop not initialized!\n");
372 pal_notification_send("MCP ERROR: No event loop");
373 } else {
374 char mcp_bind[64];
375 (void)snprintf(mcp_bind, sizeof(mcp_bind), "0.0.0.0:%u", (unsigned)MCP_PORT);
376 printf("[PS4 MCP] Binding to %s\n", mcp_bind);
377
378 g_mcp_server = mcp_server_create(g_event_loop, mcp_bind);
379 if (g_mcp_server != NULL) {
380 printf("[PS4 MCP] SUCCESS: Server listening on tcp://%s:%u\n",
381 display_ip, (unsigned)MCP_PORT);
382 char msg[128];
383 (void)snprintf(msg, sizeof(msg), "MCP Ready: %s:%u", display_ip,
384 (unsigned)MCP_PORT);
385 pal_notification_send(msg);
386 } else {
387 printf("[PS4 MCP] ERROR: Failed to create MCP server (check logs)\n");
388 pal_notification_send("MCP ERROR: Server failed");
389 }
390 }
391#endif /* ENABLE_MCP */
392 }
393#endif
394 
395 /* Main loop */
396 while (!g_shutdown_requested) {
397 sleep(1);
398 }
399 
400 /* Shutdown */
401 printf("[zftpd - ps4] Shutting down...\n");
402 
403#if ENABLE_ZHTTPD
404 if (g_event_loop != NULL) {
405 event_loop_stop(g_event_loop);
406 }
407 /* Detach FTP context before tearing down HTTP — prevents use-after-free
408 * if a /api/network/reset request races with server shutdown */
409 http_api_set_server_ctx(NULL);
410 if (g_http_server != NULL) {
411 http_server_destroy(g_http_server);
412 g_http_server = NULL;
413 }
414#if ENABLE_MCP
415 if (g_mcp_server != NULL) {
416 mcp_server_destroy(g_mcp_server);
417 g_mcp_server = NULL;
418 printf("[PS4 MCP] Stopped\n");
419 }
420#endif
421 if (g_event_loop != NULL) {
422 event_loop_destroy(g_event_loop);
423 g_event_loop = NULL;
424 }
425 printf("[PS4 HTTP] Stopped\n");
426#endif
427 
428 ftp_server_stop(&g_server_ctx);
429 ftp_server_cleanup(&g_server_ctx);
430 pal_notification_shutdown();
431 
432 printf("[zftpd - ps4] Stopped\n");
433 
434 return 0;
435}
436 
437#elif defined(PLATFORM_PS5)
438 
439#include <ps5/kernel.h>
440#include "ps5_net_filter.h"
441 
442/**
443 * @brief Escalate privileges and spoof AuthID to bypass PPR
444 */
445static void ps5_jailbreak(void) {
446 pid_t pid = getpid();
447 
448 printf("[PS5] Escalating privileges...\n");
449 
450 /* 1. Root User/Group */
451 if (kernel_set_ucred_uid(pid, 0) != 0)
452 printf("[PS5] Warning: Failed to set UID=0\n");
453 if (kernel_set_ucred_ruid(pid, 0) != 0)
454 printf("[PS5] Warning: Failed to set RUID=0\n");
455 if (kernel_set_ucred_svuid(pid, 0) != 0)
456 printf("[PS5] Warning: Failed to set SVUID=0\n");
457 if (kernel_set_ucred_rgid(pid, 0) != 0)
458 printf("[PS5] Warning: Failed to set RGID=0\n");
459 if (kernel_set_ucred_svgid(pid, 0) != 0)
460 printf("[PS5] Warning: Failed to set SVGID=0\n");
461 
462 /* 2. AuthID Spoofing (System App) to bypass PPR checks (0x80410131) */
463 /* 0x4801000000000013 = Needed for character devices (sflash0) and widespread
464 * access */
465 uint64_t auth_id = 0x4801000000000013ULL;
466 if (kernel_set_ucred_authid(pid, auth_id) != 0) {
467 printf("[PS5] Error: Failed to set AuthID to 0x%llx\n",
468 (unsigned long long)auth_id);
469 } else {
470 printf("[PS5] AuthID set to 0x%llx (sflash0/char-dev access)\n",
471 (unsigned long long)auth_id);
472 }
473 
474 /* 3. Capabilities (Allow all) */
475 uint8_t caps[16];
476 memset(caps, 0xFF, sizeof(caps));
477 if (kernel_set_ucred_caps(pid, caps) != 0) {
478 printf("[PS5] Error: Failed to set capabilities\n");
479 }
480 
481 /* 4. Jailbreak (Break out of sandbox) */
482 intptr_t rootvnode = kernel_get_root_vnode();
483 if (rootvnode) {
484 if (kernel_set_proc_rootdir(pid, rootvnode) != 0)
485 printf("[PS5] Warning: Failed to set rootdir\n");
486 if (kernel_set_proc_jaildir(pid, rootvnode) != 0)
487 printf("[PS5] Warning: Failed to set jaildir\n");
488 printf("[PS5] Sandbox escaped (rootdir/jaildir set to rootvnode)\n");
489 } else {
490 printf("[PS5] Error: Failed to get rootvnode\n");
491 }
492}
493 
494/**
495 * @brief PlayStation 5 entry point
496 */
497int main(void) {
498 printf("[zftpd - ps5] Version " RELEASE_VERSION "\n");
499 printf("[zftpd - ps5] Initializing...\n");
500 
501 (void)syscall(SYS_thr_set_name, -1, "zftpd.elf");
502 signal(SIGPIPE, SIG_IGN);
503 (void)pal_notification_init();
504 
505 pid_t existing = find_pid_by_name("zftpd.elf");
506 if (existing > 0) {
507 {
508 char msg[160];
509 (void)snprintf(msg, sizeof(msg), "zftpd v%s: port %u in use by zftpd",
510 RELEASE_VERSION, (unsigned)FTP_DEFAULT_PORT);
511 pal_notification_send(msg);
512 }
513 terminate_existing_instance("zftpd.elf");
514 {
515 char msg[160];
516 (void)snprintf(msg, sizeof(msg), "zftpd v%s: process terminated",
517 RELEASE_VERSION);
518 pal_notification_send(msg);
519 }
520 }
521 
522 /* Apply kernel patches/jailbreak immediately */
523 ps5_jailbreak();
524 
525 /*
526 * Install outbound connection filter.
527 *
528 * PURPOSE: Sony system daemons (ScePatchChecker, PFAuthClient, etc.)
529 * enter aggressive retry loops when their servers are unreachable via
530 * DNS blocking. Each retry generates real packets that saturate the
531 * available bandwidth, reducing FTP transfer throughput by up to 40%.
532 *
533 * This filter intercepts connect(2) and sendto(2) at the kernel sysent
534 * level and returns ENETUNREACH immediately for connections to known
535 * Sony CDN/PSN IP ranges. The daemons interpret this as a local hardware
536 * error and cease retrying — no packets are generated.
537 *
538 * FAILURE IS NON-FATAL: if install fails (unsupported firmware, kernel
539 * write error, etc.) we log a warning and continue. The FTP server
540 * operates normally; bandwidth saturation will continue to occur.
541 */
542 {
543 ps5_net_filter_config_t filter_cfg = PS5_NET_FILTER_CONFIG_DEFAULT;
544 int filter_rc = ps5_net_filter_install(&filter_cfg);
545 
546 if (filter_rc == PS5_NET_FILTER_OK) {
547 printf("[zftpd - ps5] Net filter: installed (Sony retry suppression active)\n");
548 } else {
549 printf("[zftpd - ps5] Net filter: not installed (%s) — FTP will still work\n",
550 ps5_net_filter_strerror(filter_rc));
551 }
552 }
553 
554 install_signal_handlers();
555 
556 /*
557 * Get PS5 primary IP for display/notifications.
558 * Bind on 0.0.0.0 so loopback (127.0.0.1 localhost) is also reachable.
559 */
560 const char *bind_ip = "0.0.0.0";
561 char ip_address[INET_ADDRSTRLEN];
562 if (pal_network_get_primary_ip(ip_address, sizeof(ip_address)) != FTP_OK) {
563 (void)snprintf(ip_address, sizeof(ip_address), "%s", "0.0.0.0");
564 }
565 
566 uint16_t selected_port = FTP_DEFAULT_PORT;
567 ftp_error_t err = server_init_with_fallback(bind_ip, FTP_DEFAULT_PORT, "/",
568 &selected_port);
569 
570 if (err != FTP_OK) {
571 fprintf(stderr, "[zftpd - ps5] Init failed: %d\n", (int)err);
572 {
573 char msg[160];
574 (void)snprintf(msg, sizeof(msg), "zftpd v%s: init failed (%d)",
575 RELEASE_VERSION, (int)err);
576 pal_notification_send(msg);
577 }
578 return EXIT_FAILURE;
579 }
580 
581 if (selected_port != FTP_DEFAULT_PORT) {
582 char msg[160];
583 (void)snprintf(msg, sizeof(msg), "zftpd v%s: port %u in use, fallback %u",
584 RELEASE_VERSION, (unsigned)FTP_DEFAULT_PORT,
585 (unsigned)selected_port);
586 pal_notification_send(msg);
587 }
588 
589 printf("[zftpd - ps5] Listening on %s:%u\n", ip_address, selected_port);
590 
591 err = ftp_server_start(&g_server_ctx);
592 
593 if (err != FTP_OK) {
594 fprintf(stderr, "[zftpd - ps5] Start failed: %d\n", (int)err);
595 ftp_server_cleanup(&g_server_ctx);
596 return EXIT_FAILURE;
597 }
598 
599 printf("[zftpd - ps5] Server running. Press Ctrl+C to stop.\n");
600 
601 {
602 char notify_msg[128];
603 (void)snprintf(notify_msg, sizeof(notify_msg), "zftpd by SeregonWar v%s (PS5) started",
604 RELEASE_VERSION);
605 pal_notification_send(notify_msg);
606 
607 (void)snprintf(notify_msg, sizeof(notify_msg), "FTP: %s:%u", ip_address,
608 (unsigned)selected_port);
609 pal_notification_send(notify_msg);
610 }
611 
612 /*=========================================================================*
613 * ZHTTPD — Start Web File Explorer
614 *=========================================================================*/
615#if ENABLE_ZHTTPD
616#if ENABLE_MCP
617 static mcp_server_t *g_mcp_server = NULL;
618#endif
619 if (http_csrf_init() != 0) {
620 ftp_log_line(FTP_LOG_WARN, "CSRF init failed: web upload disabled");
621 }
622 /* Attach FTP context so /api/network/reset can reach the session pool */
623 http_api_set_server_ctx(&g_server_ctx);
624 g_event_loop = event_loop_create();
625 if (g_event_loop != NULL) {
626 char http_bind[64];
627 (void)snprintf(http_bind, sizeof(http_bind), "[::]:%u",
628 (unsigned)HTTP_DEFAULT_PORT);
629 g_http_server = http_server_create(g_event_loop, http_bind, "/");
630 if (g_http_server != NULL) {
631 pthread_t http_thread;
632 int rc = start_http_thread(&http_thread, g_event_loop);
633 if (rc == 0) {
634 pthread_detach(http_thread);
635 printf("[PS5 HTTP] Web Explorer: http://%s:%u\n", ip_address,
636 (unsigned)HTTP_DEFAULT_PORT);
637 {
638 char msg[128];
639 (void)snprintf(msg, sizeof(msg), "HTTP: %s:%u", ip_address,
640 (unsigned)HTTP_DEFAULT_PORT);
641 pal_notification_send(msg);
642 }
643 } else {
644 printf("[PS5 HTTP] Failed to start HTTP thread (rc=%d)\n", rc);
645 }
646 }
647 
648 /*=========================================================================*
649 * MCP — Start Kernel Analysis Server
650 *=========================================================================*/
651#if ENABLE_MCP
652 char mcp_bind[64];
653 (void)snprintf(mcp_bind, sizeof(mcp_bind), "0.0.0.0:%u", (unsigned)MCP_PORT);
654 g_mcp_server = mcp_server_create(g_event_loop, mcp_bind);
655 if (g_mcp_server != NULL) {
656 printf("[PS5 MCP] Kernel analysis: tcp://%s:%u\n", ip_address,
657 (unsigned)MCP_PORT);
658 {
659 char msg[128];
660 (void)snprintf(msg, sizeof(msg), "MCP: %s:%u", ip_address,
661 (unsigned)MCP_PORT);
662 pal_notification_send(msg);
663 }
664 } else {
665 printf("[PS5 MCP] Failed to create MCP server\n");
666 }
667#endif /* ENABLE_MCP */
668 }
669#endif
670 
671 /* Main loop */
672 while (!g_shutdown_requested) {
673 sleep(1);
674 
675 /* Display stats periodically */
676 static int counter = 0;
677 if ((counter++ % 60) == 0) { /* Every 60 seconds */
678 uint32_t active = ftp_server_get_active_sessions(&g_server_ctx);
679 if (active > 0U) {
680 printf("[zftpd - ps5] Active sessions: %u\n", active);
681 }
682 
683 /* Log net filter statistics every 60 seconds if active */
684 if (ps5_net_filter_is_active()) {
685 ps5_net_filter_stats_t fstats;
686 if (ps5_net_filter_get_stats(&fstats) == PS5_NET_FILTER_OK) {
687 printf("[net_filter] calls=%" PRIu64 " blocked=%" PRIu64
688 " self=%" PRIu64 " local=%" PRIu64 " other=%" PRIu64 "\n",
689 fstats.hook_calls_total,
690 fstats.blocked_total,
691 fstats.allowed_self,
692 fstats.allowed_local,
693 fstats.allowed_other);
694 }
695 }
696 }
697 }
698 
699 printf("\n[zftpd - ps5] Shutting down...\n");
700 
701 /* Uninstall net filter before tearing down the server.
702 * CRITICAL: must happen before exit — the hook page is freed here.
703 * If the payload exits with the sysent still patched, the kernel
704 * will jump to freed memory on the next connect() syscall. */
705 if (ps5_net_filter_is_active()) {
706 int urc = ps5_net_filter_uninstall();
707 if (urc != PS5_NET_FILTER_OK) {
708 printf("[zftpd - ps5] WARNING: net filter uninstall failed: %s\n",
709 ps5_net_filter_strerror(urc));
710 /* Log but continue shutdown — we've done our best. */
711 } else {
712 printf("[zftpd - ps5] Net filter: uninstalled\n");
713 }
714 }
715 
716#if ENABLE_ZHTTPD
717 if (g_event_loop != NULL) {
718 event_loop_stop(g_event_loop);
719 }
720 /* Detach FTP context before tearing down HTTP */
721 http_api_set_server_ctx(NULL);
722 if (g_http_server != NULL) {
723 http_server_destroy(g_http_server);
724 g_http_server = NULL;
725 }
726#if ENABLE_MCP
727 if (g_mcp_server != NULL) {
728 mcp_server_destroy(g_mcp_server);
729 g_mcp_server = NULL;
730 printf("[PS5 MCP] Stopped\n");
731 }
732#endif
733 if (g_event_loop != NULL) {
734 event_loop_destroy(g_event_loop);
735 g_event_loop = NULL;
736 }
737 printf("[PS5 HTTP] Stopped\n");
738#endif
739 
740 ftp_server_stop(&g_server_ctx);
741 ftp_server_cleanup(&g_server_ctx);
742 pal_notification_shutdown();
743 
744 printf("[zftpd - ps5] Goodbye!\n");
745 
746 return EXIT_SUCCESS;
747}
748 
749#else /* POSIX / Linux */
750 
751/**
752 * @brief Print usage information
753 */
754static void print_usage(const char *program) {
755 printf("Multi-Platform FTP Server v" RELEASE_VERSION "\n");
756 printf("\n");
757 printf("Usage: %s [OPTIONS]\n", program);
758 printf("\n");
759 printf("Options:\n");
760 printf(" -p PORT FTP listen port (default: %u)\n", FTP_DEFAULT_PORT);
761 printf(" -d DIR Root directory (default: current directory)\n");
762#if ENABLE_ZHTTPD
763 printf(" -w PORT HTTP listen port (default: %u)\n", HTTP_DEFAULT_PORT);
764#endif
765 printf(" -h Show this help message\n");
766 printf("\n");
767 printf("Example:\n");
768 printf(" %s -p 2121 -d /home/ftp\n", program);
769 printf("\n");
770}
771 
772/**
773 * @brief Linux/POSIX entry point
774 */
775int main(int argc, char **argv) {
776 uint16_t port = FTP_DEFAULT_PORT;
777 char root_path[FTP_PATH_MAX];
778#if ENABLE_ZHTTPD
779 uint16_t http_port = HTTP_DEFAULT_PORT;
780#endif
781 
782 /* Get current directory as default */
783 if (getcwd(root_path, sizeof(root_path)) == NULL) {
784 fprintf(stderr, "Error: Cannot get current directory\n");
785 return EXIT_FAILURE;
786 }
787 
788 /* Parse command-line arguments */
789 int opt;
790#if ENABLE_ZHTTPD
791 while ((opt = getopt(argc, argv, "p:d:w:h")) != -1) {
792#else
793 while ((opt = getopt(argc, argv, "p:d:h")) != -1) {
794#endif
795 switch (opt) {
796 case 'p': {
797 long port_arg = strtol(optarg, NULL, 10);
798 if ((port_arg <= 0) || (port_arg > 65535)) {
799 fprintf(stderr, "Error: Invalid port: %s\n", optarg);
800 return EXIT_FAILURE;
801 }
802 port = (uint16_t)port_arg;
803 } break;
804 
805 case 'd': {
806 size_t len = strlen(optarg);
807 if (len >= sizeof(root_path)) {
808 fprintf(stderr, "Error: Path too long\n");
809 return EXIT_FAILURE;
810 }
811 memcpy(root_path, optarg, len + 1U);
812 } break;
813 
814#if ENABLE_ZHTTPD
815 case 'w': {
816 long wp = strtol(optarg, NULL, 10);
817 if ((wp <= 0) || (wp > 65535)) {
818 fprintf(stderr, "Error: Invalid HTTP port: %s\n", optarg);
819 return EXIT_FAILURE;
820 }
821 http_port = (uint16_t)wp;
822 } break;
823#endif
824 
825 case 'h':
826 print_usage(argv[0]);
827 return EXIT_SUCCESS;
828 
829 default:
830 print_usage(argv[0]);
831 return EXIT_FAILURE;
832 }
833 }
834 
835 /* Install signal handlers */
836 install_signal_handlers();
837 
838 printf("Multi-Platform FTP Server v" RELEASE_VERSION "\n");
839 printf("=====================================\n");
840 printf("Root directory: %s\n", root_path);
841 printf("FTP port: %u\n", port);
842#if ENABLE_ZHTTPD
843 printf("HTTP port: %u\n", http_port);
844#endif
845 printf("Max sessions: %u\n", FTP_MAX_SESSIONS);
846 printf("=====================================\n");
847 
848 (void)pal_notification_init();
849 
850 /* Initialize FTP server */
851 ftp_error_t err = ftp_server_init(&g_server_ctx, "0.0.0.0", port, root_path);
852 
853 if (err != FTP_OK) {
854 fprintf(stderr, "Error: FTP server initialization failed: %d\n", (int)err);
855 
856 if (err == FTP_ERR_SOCKET_BIND) {
857 fprintf(stderr, "Hint: Port %u may already be in use.\n", port);
858 fprintf(stderr, " Try a different port with -p option.\n");
859 }
860 
861 return EXIT_FAILURE;
862 }
863 
864 /* Start FTP server */
865 err = ftp_server_start(&g_server_ctx);
866 
867 if (err != FTP_OK) {
868 fprintf(stderr, "Error: Failed to start FTP server: %d\n", (int)err);
869 ftp_server_cleanup(&g_server_ctx);
870 return EXIT_FAILURE;
871 }
872 
873 printf("\n");
874 printf("FTP server started on 0.0.0.0:%u\n", port);
875 
876 /*=========================================================================*
877 * ZHTTPD — Start Web File Explorer
878 *=========================================================================*/
879#if ENABLE_ZHTTPD
880 if (http_csrf_init() != 0) {
881 ftp_log_line(FTP_LOG_WARN, "CSRF init failed: web upload disabled");
882 }
883 g_event_loop = event_loop_create();
884 if (g_event_loop != NULL) {
885 char http_bind[64];
886 (void)snprintf(http_bind, sizeof(http_bind), "[::]:%u",
887 (unsigned)http_port);
888 g_http_server = http_server_create(g_event_loop, http_bind, root_path);
889 if (g_http_server != NULL) {
890 pthread_t http_thread;
891 int rc = start_http_thread(&http_thread, g_event_loop);
892 if (rc == 0) {
893 pthread_detach(http_thread);
894 printf("HTTP server started on 0.0.0.0:%u\n", http_port);
895 printf("Web File Explorer: http://localhost:%u\n", http_port);
896 {
897 char msg[128];
898 (void)snprintf(msg, sizeof(msg), "HTTP: 0.0.0.0:%u",
899 (unsigned)http_port);
900 pal_notification_send(msg);
901 }
902 } else {
903 fprintf(stderr, "Warning: Failed to start HTTP thread (rc=%d)\n", rc);
904 }
905 } else {
906 fprintf(stderr, "Warning: Failed to create HTTP server on port %u\n",
907 http_port);
908 }
909 } else {
910 fprintf(stderr, "Warning: Failed to create event loop\n");
911 }
912#endif
913 
914 printf("\nPress Ctrl+C to stop.\n\n");
915 
916 {
917 char notify_msg[128];
918 (void)snprintf(notify_msg, sizeof(notify_msg), "zftpd: FTP 0.0.0.0:%u",
919 (unsigned)port);
920 pal_notification_send(notify_msg);
921 }
922 
923 /* Main loop */
924 uint64_t last_total_conn = 0U;
925 
926 while (!g_shutdown_requested) {
927 sleep(5);
928 
929 /* Display periodic statistics */
930 uint32_t active = ftp_server_get_active_sessions(&g_server_ctx);
931 uint64_t total_conn = 0U;
932 uint64_t bytes_sent = 0U;
933 uint64_t bytes_recv = 0U;
934 
935 ftp_server_get_stats(&g_server_ctx, &total_conn, &bytes_sent, &bytes_recv);
936 
937 /* Show status if there's activity */
938 if ((active > 0U) || (total_conn != last_total_conn)) {
939 printf("[Status] Active: %u | Total: %llu | "
940 "Sent: %llu bytes | Recv: %llu bytes\n",
941 active, (unsigned long long)total_conn,
942 (unsigned long long)bytes_sent, (unsigned long long)bytes_recv);
943 
944 last_total_conn = total_conn;
945 }
946 }
947 
948 /* Graceful shutdown */
949 printf("\nShutdown requested...\n");
950 
951#if ENABLE_ZHTTPD
952 if (g_event_loop != NULL) {
953 event_loop_stop(g_event_loop);
954 }
955 if (g_http_server != NULL) {
956 http_server_destroy(g_http_server);
957 g_http_server = NULL;
958 }
959 if (g_event_loop != NULL) {
960 event_loop_destroy(g_event_loop);
961 g_event_loop = NULL;
962 }
963 printf("HTTP server stopped.\n");
964#endif
965 
966 ftp_server_stop(&g_server_ctx);
967 ftp_server_cleanup(&g_server_ctx);
968 pal_notification_shutdown();
969 
970 printf("FTP server stopped.\n");
971 
972 return EXIT_SUCCESS;
973}
974 
975#endif
976