From 8f018be3272d8972164291707b91b0bf89377970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tanaus=C3=BA=20M=2E?= Date: Sun, 26 Apr 2026 16:53:30 +0100 Subject: [PATCH 01/20] =========================================================================== MULTI-PLATFORM CHANGES (all OS) =========================================================================== --- Bug fixes --- binkd.c: - Fixed null pointer dereference: added null check after strdup() on SSH_CONNECTION - Fixed potential use-after-free: mypid declaration moved to shared scope (HAVE_FORK || AMIGA) - Added config file validation: reject filenames that look like FTN addresses readcfg.c: - Fixed null pointer dereference: added null check after calloc() in print_node_info_1() - Fixed use-after-free: checkcfg() error path restructured (unlock before log) - Fixed config reload storm: mtime stability check + 5s minimum between reloads (prevents partial-file parse and bind() failures) inbound.c: - Fixed double PATH_SEPARATOR: strnzcat(PATH_SEPARATOR) now checks if path already ends with separator (5 locations) server.c: - Fixed use-after-free: config->rescan_delay saved to local variable before main loop, re-read from new config after checkcfg() reload - Fixed ENOTSOCK/EOPNOTSUPP recovery: extended OS/2-only ENOTSOCK handler to also cover EOPNOTSUPP ftnq.c: - Fixed try file deletion: only delete if file exists (prevents error on missing .try files) --- New features --- NC_flag (no-compression per node): btypes.h - Added NC_flag field to FTN_NODE struct ftnnode.h - Added NC_ON/NC_OFF/NC_USE_OLD defines, NC_flag parameter to add_node() ftnnode.c - NC_flag propagation in add_node(), add_node_nolock(), copy_node() readcfg.c - NC_flag parsing in node configuration keyword protocol.c - Skip zlib/bzip2 compression when NC_flag is set on the node no-call-delay config keyword: readcfg.h - Added no_call_delay field to BINKD_CONFIG readcfg.c - Added "no-call-delay" config keyword (read_bool) client.c - Skip SLEEP(call_delay) when no_call_delay is set ftnnode.c: - NC_flag propagation in add_node(), add_node_nolock(), copy_node() ftnnode.h: - Added NC_ON/NC_OFF/NC_USE_OLD defines, NC_flag parameter to add_node() --- New files --- bsycleanup.c: - BSY/CSY/TRY file cleanup at startup, scans domain outbounds bsycleanup.h: - API: cleanup_old_bsy() --- Multi-platform refactoring --- protocol.c: - Changed static functions to extern: init_protocol(), deinit_protocol(), process_rcvdlist(), send_block(), recv_block(), banner(), start_file_transfer(), log_end_of_session(), ND_set_status() binlog.c: - Added extern blsem declaration under (HAVE_THREADS || AMIGA) client.c: - Changed call0() from static to extern (required for Amiga evloop direct calls) server.c: - Added extern eothread declaration under (HAVE_THREADS || AMIGA) --- Misc tools (new) --- misc/decompress.c: - Decompress FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA) - Uses MAXPATHLEN from portable.h instead of local #define misc/freq.c: - Create .req/.clo files (ASO flat layout, BSO BinkleyStyle layout) - Uses MAXPATHLEN from portable.h - Removed local str_tolower() implementation (now in portable.c) misc/nodelist.c: - Compile FidoNet nodelist to binkd.conf node lines (extracts IBN/INA flags) - Uses MAX_LINE from portable.h - Uses MAXPATHLEN from portable.h - Removed local str_trim() implementation misc/process_tic.c: - Process .tic files to filebox (supports config file and legacy args) - Uses MAXPATHLEN from portable.h - Uses MAX_LINE from portable.h - Removed local implementations: trim_nl, skip_ws, ensure_dir, copy_file, move_file, get_file_size misc/srifreq.c: - Reimplementation in C of pgul's misc/srifreq shell script (password protection, aliases, wildcards, rate limiting) - Uses MAXPATHLEN from portable.h - Uses MAX_LINE from portable.h - Converted MAX_ALIASES to dynamic array (realloc) - Removed local implementations: wildmatch, is_wildcard, get_file_mtime, str_trim, str_upper - Added example aliases file format in header comment - Fixed: sizeof(logbuf) on pointer parameter gave wrong size (4/8 bytes instead of buffer size) - Fixed: memory leak -- g_aliases array was never freed - Fixed: memory leak -- g_conf.tracking linked list was never freed - Fixed: sscanf format widths did not match buffer sizes in load_config(), parse_srif(), load_aliases() - Fixed: tracking_load() did not reject future/corrupt timestamps - Fixed: request line parser offset was wrong when line had leading whitespace - Fixed: get_file_size() returning -1 was added to tracking bytes as negative - Fixed: parse_srif() TIME keyword without value changed time_limit from -1 to 0 misc/portable.c: - Shared implementations: string utilities, file operations, path utilities misc/portable.h: - Portable declarations and platform-specific includes (POSIX, Win32, OS/2, DOS, AmigaOS) - Fixed: duplicate declaration of safe_strncpy removed misc/decompress.txt: - Usage documentation for decompress utility misc/freq.txt: - Usage documentation for freq utility misc/nodelist.txt: - Usage documentation for nodelist utility misc/process_tic.txt: - Usage documentation and config file example for process_tic misc/srifreq.txt: - Usage documentation, config file and aliases file examples for srifreq --- Makefile updates (all platforms) --- All Makefiles updated to: - Include bsycleanup.c in main binary - Add portable.c to misc tool compilations - Add -I misc to TOOL_CFLAGS for portable.h includes mkfls/unix/Makefile.in: - Added bsycleanup.c to SRCS - Added portable.c to misc tool builds - Added -I misc to TOOL_CFLAGS mkfls/nt95-mingw/Makefile: - Added bsycleanup.c to SRCS - Added portable.c to misc tool builds - Added -I misc to TOOL_CFLAGS mkfls/nt95-msvc/Makefile: - Added portable.c to misc tool builds - Added -I misc to TOOL_CFLAGS mkfls/os2-emx/Makefile: - Added bsycleanup.c to SRCS - Added portable.c to misc tool builds - Added -I misc to TOOL_CFLAGS mkfls/amiga/Makefile.bebbo: - New: Complete rewrite for bebbo gcc cross-compiler (no ixemul fork, libnix, Roadshow TCP/IP) - Added bsycleanup.c, portable.c, all amiga/ sources - Added misc tool compilation rules + -I misc - Added AMIGADOS_4D_OUTBOUND define mkfls/amiga/Makefile.analyze.bebbo: - New: Static analysis Makefile for bebbo gcc mkfls/unix/Makefile.analyze.unix: - New: Static analysis Makefile for Unix/GCC --- Documentation --- README.md: - Added AmigaOS compilation instructions (ADE, ixemul/ixnet library versions) - Added usage documentation for all misc tools (decompress, freq, process_tic, srifreq, nodelist) - Added note about config reload instability =========================================================================== AMIGA-SPECIFIC CHANGES (#ifdef AMIGA / AMIGADOS_4D_OUTBOUND) =========================================================================== --- New Amiga-specific files (.c) --- amiga/evloop.c: - Non-blocking event loop with WaitSelect(), replaces servmgr()/clientmgr() amiga/session.c: - Session allocation, accept(), non-blocking connect(), outbound scan amiga/sock.c: - Listen socket management (open_listen_sockets, close_listen_sockets) amiga/utime.c: - utime() stub for AmigaDOS (converts Unix epoch to AmigaDOS epoch) amiga/rfc2553_amiga.c: - getaddrinfo/getnameinfo fallback for Roadshow TCP/IP amiga/bsdsock.c: - BSD socket layer for Roadshow TCP/IP (SocketBase initialization) amiga/dirent.c: - POSIX dirent emulation for AmigaDOS amiga/proto_amiga.c: - Non-blocking BinkP protocol (amiga_proto_open, amiga_proto_step, amiga_proto_close) --- New Amiga-specific files (.h) --- amiga/evloop.h: - Event loop API (amiga_evloop_run) amiga/evloop_int.h: - Internal session types (sess_t, sess_phase_t, session table) amiga/bsdsock.h: - BSD socket API (SocketBase, TCPERR macros) amiga/compat_netinet_in.h: - Compatibility header for netinet/in.h amiga/dirent.h: - POSIX dirent API (DIR, dirent, opendir, readdir) amiga/proto_amiga.h: - Non-blocking protocol API (APROTO_RUNNING, APROTO_DONE_OK, APROTO_DONE_ERR) --- Modified Amiga-specific files (existed in pgul, rewritten/extended) --- amiga/rename.c: - Complete rewrite: added duplicate name handling with .001/.002 suffixes, directory scanning for next free name, path splitting, buffer overflow protection amiga/sem.c: - Complete rewrite: replaced ixemul inline/strsup.h with direct Exec calls, added EVENTSEM implementation (AllocSignal/FreeSignal/Signal/Wait), added _InitEventSem/_PostSem/_WaitSem/_CleanEventSem --- Modified .c files (Amiga-specific changes, all under #ifdef AMIGA) --- binkd.c: - Added #include "bsycleanup.h" (unconditional) - Added cleanup_old_bsy() call at startup (unconditional) - Added #if defined(HAVE_THREADS) || defined(AMIGA) semaphore declarations (hostsem, resolvsem, lsem, blsem, varsem, config_sem, eothread, wakecmgr) - Added #ifdef AMIGA block calling amiga_evloop_run() instead of servmgr()/clientmgr() - Added #elif defined(AMIGA) for NIL: null device (instead of /dev/null or nul) - Added #ifndef AMIGA guards around fork-only code paths (-i inetd, -a remote addr options) readcfg.c: - Added #ifdef AMIGA extern for config_sem (SignalSemaphore) - Added #ifdef AMIGA tcp_nodelay default initialization - Added #ifdef AMIGA config keywords: tcp-nodelay, so-sndbuf, so-rcvbuf protocol.c: - Added #ifdef AMIGA include for amiga/proto_amiga.h - Added #if defined(HAVE_THREADS) || defined(AMIGA) for extern lsem - Added #ifdef AMIGA setsockopts_amiga() calls for tcp-nodelay/so-sndbuf/so-rcvbuf run.c: - Added #ifdef AMIGA includes (dos/dos.h, dos/dostags.h, proto/dos.h) - Added #elif defined(AMIGA) SHELL/SHELL_META defines ("c:execute") - Added #ifdef AMIGA run() implementation via SystemTagList() with NIL: I/O (synchronous execution, error output to temp file, command existence check) - Added #ifdef AMIGA in run3(): pipe/tunnel not supported, returns -1 with log message - Added #ifdef AMIGA in execl(): no SHELLOPT parameter (AmigaDOS shell syntax) tools.c: - Added #ifdef AMIGA include for amiga/bsdsock.h - Added #if defined(HAVE_THREADS) || defined(AMIGA) for extern lsem - Added #ifdef AMIGA console logging rewrite in vLog(): need_newline tracking, \r carriage return for status line overwriting branch.c: - Removed ix_vfork() implementation under #ifdef AMIGA (code was in pgul but not needed for Amiga evloop) breaksig.c: - Added #ifdef AMIGA signal handlers: SIGINT/SIGTERM via signal() (replaces SIGBREAK/SIGHUP used on other platforms) exitproc.c: - Added #if defined(HAVE_THREADS) || defined(AMIGA) extern semaphore declarations (hostsem, resolvsem, lsem, blsem, varsem, config_sem, eothread, wakecmgr) - Added #ifdef AMIGA cleanup block in exitfunc(): close_srvmgr_socket(), CleanEventSem/CleanSem for all semaphores, sock_deinit(), nodes_deinit(), bsy_remove_all(), pid_file removal https.c: - Added #ifdef AMIGA: inet_ntoa() uses .sin_addr.s_addr (Roadshow struct in_addr is u_long, not struct) iptools.c: - Added #ifdef AMIGA include for netinet/tcp.h - Added #ifdef AMIGA ioctl() size parameter (Amiga ioctl doesn't take size parameter) server.c: - Added #ifdef AMIGA include for amiga/bsdsock.h - Added #ifdef AMIGA: force PF_INET (no IPv6 on AmigaOS 3) - Added #ifdef AMIGA: bind() retry loop (6 retries, 2s delay) for socket release timing - Added #ifdef AMIGA: select() ENOTSOCK/EBADF recovery (restart listen sockets) - Added #ifndef AMIGA guard around HAVE_FORK child process handling client.c: - Added #ifdef AMIGA include for amiga/bsdsock.h --- Modified .h files (Amiga-specific changes, all under #ifdef AMIGA) --- Config.h: - Added AMIGA to platform check: #if defined(HAVE_FORK)+defined(HAVE_THREADS)+defined(DOS)+defined(AMIGA)==0 btypes.h: - Added #ifdef AMIGA uintmax_t typedef for long long unsigned on m68k client.h: - Added #ifdef AMIGA includes (netinet/in.h, netinet/tcp.h, sys/socket.h) - Added #ifdef AMIGA call0() declaration for direct evloop outbound calls iphdr.h: - Added #ifdef AMIGA: include amiga/bsdsock.h, define sock_init()/sock_deinit()/soclose() macros iptools.h: - Added #ifdef AMIGA includes (sys/socket.h, netinet/in.h, netdb.h) - Added #ifdef AMIGA setsockopts_amiga() declaration readcfg.h: - Added #ifdef AMIGA fields: tcp_nodelay, so_sndbuf, so_rcvbuf readdir.h: - Added #elif defined(AMIGA) include for amiga/dirent.h rfc2553.h: - Added #ifdef AMIGA: EAI_* constant redefinitions (prevent libnix conflicts) - Added #ifdef AMIGA: include chain (sys/types, sys/socket, netinet/in, arpa/inet, netdb.h) - Added #ifdef AMIGA: undef/redefine getaddrinfo/freeaddrinfo/gai_strerror macros - Added #ifndef EAI_ADDRFAMILY portability define (maps to EAI_FAMILY) sem.h: - Added #ifdef AMIGA EVENTSEM typedef (struct with Task* waiter + sigbit) - Added #ifdef AMIGA typed prototypes for _InitEventSem, _PostSem, _WaitSem, _CleanEventSem server.h: - Added #ifdef AMIGA include for netinet/in.h sys.h: - Added #ifdef AMIGA include for proto/exec.h - Added #ifdef AMIGA: undef getpid/sleep, redefine as FindTask(NULL)/Delay() - Added #ifdef AMIGA PRIuMAX define as "llu" (uintmax_t is long long unsigned on m68k) - Added #ifdef HAVE_FORK includes for signal.h and sys/wait.h --- Config.h | 4 +- README.md | 7 + amiga/bsdsock.c | 79 + amiga/bsdsock.h | 155 + amiga/compat_netinet_in.h | 19 + amiga/dirent.c | 137 + amiga/dirent.h | 53 + amiga/evloop.c | 509 + amiga/evloop.h | 34 + amiga/evloop_int.h | 68 + amiga/proto_amiga.c | 269 + amiga/proto_amiga.h | 30 + amiga/rename.c | 133 +- amiga/rfc2553_amiga.c | 323 + amiga/sem.c | 99 +- amiga/session.c | 462 + amiga/sock.c | 130 + amiga/utime.c | 77 + binkd.c | 71 +- binlog.c | 4 + branch.c | 23 - breaksig.c | 11 + bsycleanup.c | 122 + bsycleanup.h | 19 + btypes.h | 1 + changes.diff | 28013 +++++++++++++++++++++++++++ changes.txt | 334 + client.c | 22 +- client.h | 11 + doc/decompress.txt | 36 + doc/freq_new.txt | 37 + doc/nodelist.txt | 32 + doc/process_tic.txt | 45 + doc/srifreq_new.txt | 67 + exitproc.c | 38 + ftnnode.c | 14 +- ftnnode.h | 6 +- ftnq.c | 5 +- https.c | 6 +- inbound.c | 20 +- iphdr.h | 14 +- iptools.c | 47 +- iptools.h | 10 + misc/decompress.c | 188 + misc/freq.c | 220 + misc/nodelist.c | 200 + misc/portable.c | 402 + misc/portable.h | 135 + misc/process_tic.c | 552 + misc/srifreq.c | 1024 + mkfls/amiga/Makefile.analyze.bebbo | 96 + mkfls/amiga/Makefile.bebbo | 208 + mkfls/nt95-mingw/Makefile | 30 +- mkfls/nt95-msvc/Makefile | 27 +- mkfls/os2-emx/Makefile | 30 +- mkfls/unix/Makefile.analyze.unix | 65 + mkfls/unix/Makefile.in | 33 +- protocol.c | 35 +- readcfg.c | 120 +- readcfg.h | 10 +- readdir.h | 2 + rfc2553.h | 58 +- run.c | 139 + sem.h | 18 +- server.c | 101 +- server.h | 4 + sys.h | 33 + tools.c | 26 + 68 files changed, 35240 insertions(+), 112 deletions(-) mode change 100644 => 100755 Config.h create mode 100644 amiga/bsdsock.c create mode 100644 amiga/bsdsock.h create mode 100644 amiga/compat_netinet_in.h create mode 100644 amiga/dirent.c create mode 100644 amiga/dirent.h create mode 100644 amiga/evloop.c create mode 100644 amiga/evloop.h create mode 100644 amiga/evloop_int.h create mode 100644 amiga/proto_amiga.c create mode 100644 amiga/proto_amiga.h mode change 100644 => 100755 amiga/rename.c create mode 100644 amiga/rfc2553_amiga.c mode change 100644 => 100755 amiga/sem.c create mode 100644 amiga/session.c create mode 100644 amiga/sock.c create mode 100644 amiga/utime.c mode change 100644 => 100755 binkd.c mode change 100644 => 100755 binlog.c mode change 100644 => 100755 branch.c mode change 100644 => 100755 breaksig.c create mode 100644 bsycleanup.c create mode 100644 bsycleanup.h mode change 100644 => 100755 btypes.h create mode 100644 changes.diff create mode 100644 changes.txt mode change 100644 => 100755 client.c mode change 100644 => 100755 client.h create mode 100644 doc/decompress.txt create mode 100644 doc/freq_new.txt create mode 100644 doc/nodelist.txt create mode 100644 doc/process_tic.txt create mode 100644 doc/srifreq_new.txt mode change 100644 => 100755 exitproc.c mode change 100644 => 100755 ftnnode.c mode change 100644 => 100755 ftnnode.h mode change 100644 => 100755 ftnq.c mode change 100644 => 100755 https.c mode change 100644 => 100755 inbound.c mode change 100644 => 100755 iphdr.h mode change 100644 => 100755 iptools.c mode change 100644 => 100755 iptools.h create mode 100755 misc/decompress.c create mode 100755 misc/freq.c create mode 100755 misc/nodelist.c create mode 100644 misc/portable.c create mode 100644 misc/portable.h create mode 100755 misc/process_tic.c create mode 100755 misc/srifreq.c create mode 100644 mkfls/amiga/Makefile.analyze.bebbo create mode 100644 mkfls/amiga/Makefile.bebbo mode change 100644 => 100755 mkfls/nt95-mingw/Makefile mode change 100644 => 100755 mkfls/nt95-msvc/Makefile mode change 100644 => 100755 mkfls/os2-emx/Makefile create mode 100644 mkfls/unix/Makefile.analyze.unix mode change 100644 => 100755 mkfls/unix/Makefile.in mode change 100644 => 100755 protocol.c mode change 100644 => 100755 readcfg.c mode change 100644 => 100755 readcfg.h mode change 100644 => 100755 readdir.h mode change 100644 => 100755 rfc2553.h mode change 100644 => 100755 run.c mode change 100644 => 100755 sem.h mode change 100644 => 100755 server.c mode change 100644 => 100755 server.h mode change 100644 => 100755 sys.h mode change 100644 => 100755 tools.c diff --git a/Config.h b/Config.h old mode 100644 new mode 100755 index 5ee17caf..56b6a43e --- a/Config.h +++ b/Config.h @@ -14,8 +14,8 @@ #ifndef _Config_h #define _Config_h -#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) == 0 -#error You must define either HAVE_FORK or HAVE_THREADS! +#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) + defined(AMIGA) == 0 +#error You must define HAVE_FORK, HAVE_THREADS, DOS, or AMIGA! #endif #ifdef __WATCOMC__ diff --git a/README.md b/README.md index 0e288e69..a6d6e0c9 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,13 @@ non-UNIX: 2. Run make (nmake, wmake or gmake, name of make's binary is rely with C compiler). +AmigaOS: +It does not require ixemul. The entire API has been replaced with the Amigaos 3 API and bsdsocket.library (TCP/IP roadshow). + +The release version was compiled with bebbo's gcc using ndk 3.2, so it requires amigaos > 3.1 + +It is still in the testing phase. + UNIXes: 1.) Clone the repo: diff --git a/amiga/bsdsock.c b/amiga/bsdsock.c new file mode 100644 index 00000000..fd15a924 --- /dev/null +++ b/amiga/bsdsock.c @@ -0,0 +1,79 @@ +/* + * bsdsock.c -- bsdsocket.library lifecycle for AmigaOS 3 + * + * bsdsock.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include +#include +#include +#include +#include +#include + +/* Linker-compatibility global. Never used at runtime */ +struct Library *SocketBase = NULL; + +/* Suppress conflicting C prototypes from clib/bsdsocket_protos.h */ +#ifndef CLIB_BSDSOCKET_PROTOS_H +#define CLIB_BSDSOCKET_PROTOS_H +#endif + +#include +#include + +extern void Log(int lev, const char *s, ...); + +/* _amiga_get_socket_base -- returns bsdsocket.library handle for calling task */ +struct Library *_amiga_get_socket_base(void) +{ + return (struct Library *)FindTask(NULL)->tc_UserData; +} + +int amiga_sock_init(void) +{ + struct Task *me = FindTask(NULL); + struct Library *base; + + if (me->tc_UserData) + return 0; /* already open for this task */ + + base = OpenLibrary("bsdsocket.library", 0UL); + + if (!base) + { + fprintf(stderr, "amiga_sock_init: cannot open bsdsocket.library\n"); + return -1; + } + + /* Store in tc_UserData and global SocketBase */ + me->tc_UserData = (APTR)base; + SocketBase = base; + + /* Link the per-task errno to the TCP stack. */ + SetErrnoPtr(&errno, (LONG)sizeof(errno)); + + return 0; +} + +void amiga_sock_cleanup(void) +{ + struct Task *me = FindTask(NULL); + struct Library *base = (struct Library *)me->tc_UserData; + + if (base) + { + me->tc_UserData = NULL; + SocketBase = NULL; + CloseLibrary(base); + } +} + +int amiga_child_sock_init(void) +{ + /* Child inherits tc_UserData = NULL, opens new handle */ + return amiga_sock_init(); +} diff --git a/amiga/bsdsock.h b/amiga/bsdsock.h new file mode 100644 index 00000000..993fae37 --- /dev/null +++ b/amiga/bsdsock.h @@ -0,0 +1,155 @@ +/* + * bsdsock.h -- bsdsocket.library init and POSIX compat shims for AmigaOS 3 + * + * bsdsock.h is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifndef _AMIGA_BSDSOCK_H +#define _AMIGA_BSDSOCK_H + +#ifdef AMIGA + +#include +#include +#include +#include + +/* Suppress conflicting C prototypes from roadshow */ +#ifndef CLIB_BSDSOCKET_PROTOS_H +#define CLIB_BSDSOCKET_PROTOS_H +#endif + +/* Undefine MCLBYTES/MCLSHIFT before roadshow headers */ +#ifdef MCLBYTES +#undef MCLBYTES +#endif +#ifdef MCLSHIFT +#undef MCLSHIFT +#endif + +/* Roadshow SDK network headers */ +#include +#include +#include "compat_netinet_in.h" +#include +#include +#include /* inline/bsdsocket.h, no clib protos */ + +/* Undefine conflicting unistd.h macros */ +#ifdef gethostid +#undef gethostid +#endif +#ifdef getdtablesize +#undef getdtablesize +#endif +#ifdef gethostname +#undef gethostname +#endif + +/* Per-task SocketBase override */ +struct Library *_amiga_get_socket_base(void); + +#ifdef SocketBase +#undef SocketBase +#endif +#define SocketBase _amiga_get_socket_base() + +/* Roadshow socket-specific errno values */ +#include + +#define BSDSOCK_HAS_TIMEVAL 1 + +/* Socket-specific errno values from roadshow sys/errno.h */ +#ifndef ENOTSOCK +#define ENOTSOCK 38 /* Socket operation on non-socket */ +#endif +#ifndef EOPNOTSUPP +#define EOPNOTSUPP 45 /* Operation not supported on socket */ +#endif +#ifndef ECONNREFUSED +#define ECONNREFUSED 61 /* Connection refused */ +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 60 /* Connection timed out */ +#endif +#ifndef ECONNRESET +#define ECONNRESET 54 /* Connection reset by peer */ +#endif +#ifndef EHOSTUNREACH +#define EHOSTUNREACH 65 /* No route to host */ +#endif + +#include + +/* sockaddr_storage fallback for roadshow */ +#ifndef HAVE_SOCKADDR_STORAGE +#ifndef sockaddr_storage +struct sockaddr_storage +{ + unsigned short ss_family; + char __ss_pad[22]; /* enough for IPv4 */ +}; +#endif +#define HAVE_SOCKADDR_STORAGE 1 +#endif + +/* Library base functions */ +int amiga_sock_init(void); +void amiga_sock_cleanup(void); +int amiga_child_sock_init(void); + +/* getpid() is defined in sys.h for AMIGA */ +/* amiga_sleep and sleep are defined in sys.h for AMIGA */ + +/* select() -> WaitSelect() wrapper with Ctrl+C handling */ +#ifndef AMIGA_SELECT_DEFINED +#define AMIGA_SELECT_DEFINED + +/* Forward-declare binkd_exit */ +extern int binkd_exit; + +/* Forward-declare Log to avoid implicit declaration warning */ +extern void Log(int lev, char *s, ...); + +static int amiga_select_wrap(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +{ + ULONG sigmask = SIGBREAKF_CTRL_C; + int rc = WaitSelect(nfds, readfds, writefds, exceptfds, timeout, &sigmask); + + /* Ctrl+C should break blocked select() loops immediately. */ + if ((sigmask & SIGBREAKF_CTRL_C) != 0) + { + Log(1, "Ctrl+C detected in WaitSelect, setting binkd_exit=1"); + binkd_exit = 1; + errno = EINTR; + return -1; + } + + return rc; +} + +#define select(n, r, w, e, t) amiga_select_wrap((n), (r), (w), (e), (t)) + +#endif /* AMIGA_SELECT_DEFINED */ + +/* FIONBIO via IoctlSocket */ +#ifndef FIONBIO +#define FIONBIO 0x8004667E +#endif +#ifndef ioctl +#define ioctl(s, req, arg) IoctlSocket((s), (req), (char *)(arg)) +#endif + +/* inet_ntoa -> Inet_NtoA (Amiga only) */ +#ifdef AMIGA +#ifdef inet_ntoa +#undef inet_ntoa +#endif +#define inet_ntoa(a) Inet_NtoA(a) +#endif + +#endif /* AMIGA */ +#endif /* _AMIGA_BSDSOCK_H */ diff --git a/amiga/compat_netinet_in.h b/amiga/compat_netinet_in.h new file mode 100644 index 00000000..9763f963 --- /dev/null +++ b/amiga/compat_netinet_in.h @@ -0,0 +1,19 @@ +/* + * compat_netinet_in.h -- Wrapper for netinet/in.h typedef clash + * + * compat_netinet_in.h is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifndef _AMIGA_COMPAT_NETINET_IN_H +#define _AMIGA_COMPAT_NETINET_IN_H + +#ifdef __GNUC__ +#include_next +#else +#include +#endif + +#endif /* _AMIGA_COMPAT_NETINET_IN_H */ diff --git a/amiga/dirent.c b/amiga/dirent.c new file mode 100644 index 00000000..efa26ab2 --- /dev/null +++ b/amiga/dirent.c @@ -0,0 +1,137 @@ +/* + * dirent.c -- POSIX directory scanning for AmigaOS 3 + * + * dirent.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifdef AMIGA + +#include +#include +#include +#include +#include +#include + +#include "amiga/dirent.h" + +/* opendir -- locks directory and allocates state for readdir() */ +DIR *opendir(const char *path) +{ + DIR *dir; + + if (!path) + { + errno = EINVAL; + return NULL; + } + + dir = (DIR *)AllocMem((LONG)sizeof(DIR), MEMF_CLEAR); + + if (!dir) + { + errno = ENOMEM; + return NULL; + } + + dir->fib = (struct FileInfoBlock *)AllocMem((LONG)sizeof(struct FileInfoBlock), MEMF_CLEAR); + + if (!dir->fib) + { + FreeMem(dir, (LONG)sizeof(DIR)); + errno = ENOMEM; + return NULL; + } + + dir->lock = Lock((STRPTR)path, ACCESS_READ); + + if (!dir->lock) + { + FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); + FreeMem(dir, (LONG)sizeof(DIR)); + errno = ENOENT; + return NULL; + } + + /* Examine the directory itself; this positions FIB for ExNext() */ + if (!Examine(dir->lock, dir->fib)) + { + UnLock(dir->lock); + FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); + FreeMem(dir, (LONG)sizeof(DIR)); + errno = EACCES; + return NULL; + } + + /* fib_DirEntryType > 0 means this IS a directory */ + if (dir->fib->fib_DirEntryType <= 0) + { + UnLock(dir->lock); + FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); + FreeMem(dir, (LONG)sizeof(DIR)); + errno = ENOTDIR; + return NULL; + } + + dir->first = 1; /* ExNext() has not been called yet */ + + return dir; +} + +/* readdir -- advances to next directory entry */ +struct dirent *readdir(DIR *dir) +{ + LONG dos_rc; + LONG dos_err; + + if (!dir) + { + errno = EINVAL; + return NULL; + } + + /* ExNext() advances past the last entry returned by Examine/ExNext */ + dos_rc = ExNext(dir->lock, dir->fib); + + if (!dos_rc) + { + dos_err = IoErr(); + + if (dos_err == ERROR_NO_MORE_ENTRIES) + return NULL; + + errno = EIO; + return NULL; + } + + /* Copy name into the entry buffer */ + strncpy(dir->entry.d_name, dir->fib->fib_FileName, AMIGA_NAME_MAX - 1); + dir->entry.d_name[AMIGA_NAME_MAX - 1] = '\0'; + dir->entry.d_ino = 0; + + return &dir->entry; +} + +/* closedir -- releases all resources associated with dir */ +int closedir(DIR *dir) +{ + if (!dir) + { + errno = EINVAL; + return -1; + } + + if (dir->lock) + UnLock(dir->lock); + + if (dir->fib) + FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); + + FreeMem(dir, (LONG)sizeof(DIR)); + return 0; +} + +#endif /* AMIGA */ diff --git a/amiga/dirent.h b/amiga/dirent.h new file mode 100644 index 00000000..0210cfbf --- /dev/null +++ b/amiga/dirent.h @@ -0,0 +1,53 @@ +/* + * dirent.h -- POSIX directory scanning for AmigaOS 3 + * + * dirent.h is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifndef _AMIGA_DIRENT_H +#define _AMIGA_DIRENT_H + +#ifdef AMIGA + +#include +#include + +/* Maximum name length: AmigaDOS allows 107 characters */ +#define AMIGA_NAME_MAX 108 + +struct dirent +{ + unsigned long d_ino; /* inode -- always 0 on AmigaDOS */ + char d_name[AMIGA_NAME_MAX]; /* null-terminated file name */ +}; + +/* struct utimbuf for AmigaOS 3 without ixemul */ +#ifndef _AMIGA_UTIMBUF_DEFINED +#define _AMIGA_UTIMBUF_DEFINED + +struct utimbuf +{ + long actime; /* access time (unused by SetFileDate) */ + long modtime; /* modification time (POSIX time_t) */ +}; + +int utime(const char *path, const struct utimbuf *times); +#endif + +typedef struct _amiga_dir +{ + BPTR lock; /* directory lock */ + struct FileInfoBlock *fib; /* reusable FileInfoBlock */ + int first; /* flag: first call not yet */ + struct dirent entry; /* storage returned to caller */ +} DIR; + +DIR *opendir(const char *path); +struct dirent *readdir(DIR *dir); +int closedir(DIR *dir); + +#endif /* AMIGA */ +#endif /* _AMIGA_DIRENT_H */ diff --git a/amiga/evloop.c b/amiga/evloop.c new file mode 100644 index 00000000..8e470504 --- /dev/null +++ b/amiga/evloop.c @@ -0,0 +1,509 @@ +/* + * evloop.c -- non-blocking event loop for AmigaOS 3 + * + * evloop.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +/* Suppress clib bsdsocket prototypes before any socket header */ +#ifndef CLIB_BSDSOCKET_PROTOS_H +#define CLIB_BSDSOCKET_PROTOS_H +#endif + +#include +#include +#include + +#include +#include +#include + +#include "sys.h" +#include "readcfg.h" +#include "common.h" +#include "tools.h" +#include "protocol.h" +#include "sem.h" +#include "server.h" +#include "amiga/bsdsock.h" +#include "amiga/evloop.h" +#include "amiga/evloop_int.h" +#include "amiga/proto_amiga.h" + +/* Externals */ +extern SOCKET sockfd[MAX_LISTENSOCK]; +extern int sockfd_used; +extern int binkd_exit; +extern int server_flag, client_flag; + +/* Session table (shared with sock.c and session.c) */ +sess_t *sessions = NULL; +int max_sessions = 0; + +/* + * calc_max_sessions -- Compute session slot count from config + flags + * Shared by init and config-reload paths + */ +static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) +{ + int servers = config->max_servers; + int clients = config->max_clients; + int total; + + if (servers == 0 && clients == 0) + { + Log(5, "DEBUG: Using default 2 slots (no config found)"); + return 2; + } + + Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); + + if (srv_flag && servers < 1) + servers = 1; + + if (cli_flag && clients < 1) + clients = 1; + + total = servers + clients; + + if (total < 2) + total = 2; + + Log(5, "DEBUG: Calculated max_sessions=%d", total); + + return total; +} + +/* init_session_table -- Allocate and zero-initialise the session array */ +static int init_session_table(int slots) +{ + int i; + + sessions = calloc(slots, sizeof(sess_t)); + + if (!sessions) + { + Log(1, "Failed to allocate session table"); + return 0; + } + + for (i = 0; i < slots; i++) + { + memset(&sessions[i], 0, sizeof(sess_t)); + memset(&sessions[i].state, 0, sizeof(STATE)); + sessions[i].fd = INVALID_SOCKET; + sessions[i].phase = SESS_FREE; + } + + return 1; +} + +/* + * build_fdsets -- Populate r/w fd_sets from listen sockets and sessions + * Returns the highest fd seen (maxfd) + */ +static int build_fdsets(fd_set *r, fd_set *w) +{ + int i, maxfd = 0; + + FD_ZERO(r); + FD_ZERO(w); + + /* server side: listen sockets */ + for (i = 0; i < sockfd_used; i++) + { + if (sockfd[i] != INVALID_SOCKET) + { + FD_SET(sockfd[i], r); + + if ((int)sockfd[i] > maxfd) + maxfd = (int)sockfd[i]; + } + } + + /* client + server sessions */ + for (i = 0; i < max_sessions; i++) + { + sess_t *s = &sessions[i]; + + if (s->phase == SESS_FREE || s->fd == INVALID_SOCKET) + continue; + + if ((int)s->fd > maxfd) + maxfd = (int)s->fd; + + if (s->phase == SESS_CONNECTING) + { + /* client: waiting for non-blocking connect() */ + FD_SET(s->fd, w); + } + else + { + /* Server or established client session */ + FD_SET(s->fd, r); + + if (s->state.msgs || s->state.oleft || s->state.send_eof || (s->state.out.f && !s->state.off_req_sent && !s->state.waiting_for_GOT)) + FD_SET(s->fd, w); + } + } + + Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); + return maxfd; +} + +/* + * handle_server_accept -- Accept new inbound connections on all listen fds + * Returns 0 normally, -1 if binkd_exit was set during accept + */ +static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) +{ + int i; + + Log(5, "DEBUG: Before accept loop"); + + for (i = 0; i < sockfd_used; i++) + { + if (FD_ISSET(sockfd[i], r)) + do_accept(sockfd[i], config); + + if (binkd_exit) + { + Log(5, "DEBUG: binkd_exit during accept loop"); + return -1; + } + } + + Log(5, "DEBUG: After accept loop"); + + return 0; +} + +/* + * advance_sessions -- Step every active session (server + client) + * Returns the number of non-free sessions processed + */ +static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) +{ + int i, active = 0; + + Log(5, "DEBUG: Before advance sessions"); + + for (i = 0; i < max_sessions; i++) + { + sess_t *s = &sessions[i]; + + Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); + + if (s->phase == SESS_FREE) + continue; + + active++; + + if (s->phase == SESS_CONNECTING) + { + /* client: Complete the non-blocking connect */ + if (FD_ISSET(s->fd, w)) + check_connect(i, config); + } + else + { + int rd = FD_ISSET(s->fd, r); + int wr = FD_ISSET(s->fd, w); + + /* Always step: protocol must advance internal state even + * when WaitSelect reports no activity (e.g. second batch + * EOB send, TCP FIN detection after remote closes) */ + do_session_step(i, rd, wr, config); + } + + if (binkd_exit) + break; + } + + return active; +} + +/* + * handle_config_reload -- Resize session table and reopen listen sockets + * Returns 1 if the caller should break out of the main loop, 0 otherwise + */ +static int handle_config_reload(BINKD_CONFIG **config, int srv_flag, int cli_flag) +{ + int i, new_max; + BINKD_CONFIG *nc = lock_current_config(); + + if (nc) + { + new_max = calc_max_sessions(nc, srv_flag, cli_flag); + + if (new_max != max_sessions) + { + sess_t *ns = realloc(sessions, new_max * sizeof(sess_t)); + + if (ns) + { + for (i = max_sessions; i < new_max; i++) + { + memset(&ns[i], 0, sizeof(sess_t)); + memset(&ns[i].state, 0, sizeof(STATE)); + ns[i].fd = INVALID_SOCKET; + ns[i].phase = SESS_FREE; + } + + sessions = ns; + max_sessions = new_max; + + Log(4, "Session table resized to %d slots", max_sessions); + } + else + { + Log(1, "Failed to resize session table, keeping current size"); + } + } + + unlock_config_structure(nc, 0); + } + + close_listen_sockets(); + *config = lock_current_config(); + + if (srv_flag && open_listen_sockets(*config) < 0) + { + unlock_config_structure(*config, 0); + return 1; /* fatal — break main loop */ + } + + unlock_config_structure(*config, 0); + *config = lock_current_config(); + return 0; +} + +/* evloop_cleanup -- Close sessions and free resources on exit */ +static void evloop_cleanup(BINKD_CONFIG *config, int config_locked) +{ + int i; + + if (config_locked) + unlock_config_structure(config, 0); + + if (sessions) + { + for (i = 0; i < max_sessions; i++) + { + if (sessions[i].phase == SESS_RUNNING) + { + amiga_proto_close(&sessions[i].state, config, 0); + + if (sessions[i].inbound) + n_servers--; + else + n_clients--; + } + else if (sessions[i].phase == SESS_CONNECTING) + { + n_clients--; + } + sess_free(i); + } + + free(sessions); + sessions = NULL; + } + + close_listen_sockets(); + amiga_sock_cleanup(); + Log(4, "evloop done"); +} + +/* amiga_evloop_run -- Entry point: init, then main WaitSelect() loop */ +void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) +{ + int config_locked = 0; + time_t last_rescan = 0; + time_t now; + fd_set r, w; + struct timeval tv; + int n, maxfd; + int active_sessions = 0; + static int idle_loops = 0; + + /* Sync globals so try_outbound() and friends see the correct flags */ + server_flag = srv_flag; + client_flag = cli_flag; + + sockfd_used = 0; + srand((unsigned int)time(NULL)); + + Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); + Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); + + /* Initialise session table */ + max_sessions = calc_max_sessions(config, server_flag, client_flag); + + if (max_sessions < 2) + { + Log(2, "WARNING: max_sessions=%d is too low, forcing to 2", max_sessions); + max_sessions = 2; + } + + Log(4, "evloop start (AmigaOS 3, WaitSelect, %d slots)", max_sessions); + + if (!init_session_table(max_sessions)) + return; + + /* server: Open listen sockets */ + if (server_flag && open_listen_sockets(config) < 0) + { + Log(0, "evloop: cannot open listen sockets"); + free(sessions); + sessions = NULL; + return; + } + + Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); + + /* Initial outbound attempt before waiting (important for poll -p mode) */ + Log(5, "DEBUG: Initial try_outbound before main loop"); + try_outbound(config); + last_rescan = time(NULL); /* Reset timer since we just did an attempt */ + + /* ===== Main loop ===== */ + for (;;) + { + if (binkd_exit) + { + Log(1, "binkd_exit detected at loop start, exiting"); + break; + } + + /* Build fd_sets */ + Log(5, "DEBUG: Building fd_sets"); + maxfd = build_fdsets(&r, &w); + + tv.tv_sec = 1; + tv.tv_usec = 0L; + + /* WaitSelect() with nfds>0 but empty fd_sets causes guru #80000006 :/ + * Use select(0,...) as a pure sleep when no sockets are active */ + if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) + maxfd = 1; + + Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); + + if (maxfd == 0) + { + /* No sockets yet -- use Delay() instead of select(0,...) + * as WaitSelect with nfds=0 can block indefinitely on AmigaOS */ + Delay(50); /* 1 second = 50 ticks at 50Hz PAL */ + n = 0; /* simulate timeout */ + } + else + n = select(maxfd + 1, &r, &w, NULL, &tv); + + Log(5, "DEBUG: select() returned n=%d", n); + + if (binkd_exit) + { + Log(1, "binkd_exit detected after select(), exiting"); + break; + } + + Delay(1UL); /* 1 tick = 20ms @ 50Hz, prevents CPU hogging */ + + /* Handle select errors */ + if (n < 0) + { + if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) + { + Log(5, "DEBUG: select interrupted, continuing"); + continue; + } + + if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) + { + Log(2, "select: %s, reopening", TCPERR()); + + close_listen_sockets(); + + if (server_flag && open_listen_sockets(config) < 0) + break; + + continue; + } + + Log(1, "select: %s", TCPERR()); + break; + } + else if (n == 0) + { + Log(5, "DEBUG: select timeout, continuing"); + } + + /* server: Accept new inbound connections */ + if (server_flag) + { + if (handle_server_accept(&r, config) < 0) + break; + } + + if (binkd_exit) + break; + + /* server + client: Advance all active sessions */ + active_sessions = advance_sessions(&r, &w, config); + + if (binkd_exit) + break; + + /* client: Time-based outbound scan + config reload */ + now = time(NULL); + + if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) + { + Log(5, "DEBUG: Before try_outbound"); + + try_outbound(config); + + Log(5, "DEBUG: After try_outbound"); + + if (checkcfg()) + { + if (handle_config_reload(&config, server_flag, client_flag)) + break; + + config_locked = 1; + } + + config->q_present = 0; + last_rescan = now; + } + + /* client: Poll-mode idle exit */ + /* Reset counter whenever there is something going on */ + if (n_clients > 0 || active_sessions > 0) + { + if (idle_loops > 0) + Log(2, "Activity detected, reset idle counter"); + + idle_loops = 0; + } + + if (!server_flag && active_sessions == 0 && n_clients == 0) + { + idle_loops++; + + Log(2, "Idle loop %d/2 (no server, no sessions, no clients)", idle_loops); + + if (idle_loops > 1) + { + Log(0, "the queue is empty, quitting..."); + break; + } + } + } + /* ===== End main loop ===== */ + + evloop_cleanup(config, config_locked); +} diff --git a/amiga/evloop.h b/amiga/evloop.h new file mode 100644 index 00000000..eb4aaffc --- /dev/null +++ b/amiga/evloop.h @@ -0,0 +1,34 @@ +/* + * evloop.h -- non-blocking event loop for AmigaOS 3 + * + * evloop.h is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifndef _AMIGA_EVLOOP_H +#define _AMIGA_EVLOOP_H + +#ifdef AMIGA + +#include "readcfg.h" +#include "protoco2.h" /* STATE */ + +/* amiga_proto_step() return codes — also used by protocol.c */ +#define APROTO_RUNNING 0 /* session alive, call again */ +#define APROTO_DONE_OK 1 /* session finished, success */ +#define APROTO_DONE_ERR 2 /* session failed */ + +/* + * amiga_evloop_run -- entry point replacing servmgr() + clientmgr() + * + * Opens listen sockets (when server_flag), then runs a WaitSelect() + * loop that multiplexes sessions dynamically based on config->max_servers + * and config->max_clients. Minimum 2 sessions are always allocated + * Returns only when binkd_exit != 0 + */ +void amiga_evloop_run(BINKD_CONFIG *config, int server_flag, int client_flag); + +#endif /* AMIGA */ +#endif /* _AMIGA_EVLOOP_H */ diff --git a/amiga/evloop_int.h b/amiga/evloop_int.h new file mode 100644 index 00000000..4090515d --- /dev/null +++ b/amiga/evloop_int.h @@ -0,0 +1,68 @@ +/* + * evloop_int.h -- internal types shared by evloop.c, sock.c, session.c + * + * evloop_int.h is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifndef AMIGA_EVLOOP_INT_H +#define AMIGA_EVLOOP_INT_H + +#include "protoco2.h" +#include "amiga/bsdsock.h" +#include "ftnnode.h" +#include "readcfg.h" + +/* Session lifecycle */ +typedef enum +{ + SESS_FREE = 0, /* slot available */ + SESS_CONNECTING = 1, /* waiting for connect() */ + SESS_RUNNING = 2 /* BinkP session active */ +} sess_phase_t; + +/* Per-session state */ +typedef struct +{ + sess_phase_t phase; + SOCKET fd; + STATE state; + int inbound; /* 1=accepted, 0=outbound */ + + FTN_NODE *node; + struct addrinfo *ai_head; /* full getaddrinfo list */ + struct addrinfo *ai_cur; /* candidate being tried */ + time_t conn_start; + + char host[BINKD_FQDNLEN + 1]; + char port[MAXPORTSTRLEN + 1]; + char ip[BINKD_FQDNLEN + 1]; + + time_t last_io; +} sess_t; + +/* Globals defined in evloop.c */ +extern sess_t *sessions; +extern int max_sessions; + +/* Defined in server.c and client.c respectively */ +extern int n_servers; +extern int n_clients; + +/* sock.c */ +void set_nonblock(SOCKET fd); +int open_listen_sockets(BINKD_CONFIG *config); +void close_listen_sockets(void); + +/* session.c */ +int sess_alloc(void); +void sess_free(int idx); +void do_accept(SOCKET lfd, BINKD_CONFIG *config); +int start_connect(sess_t *s, BINKD_CONFIG *config); +void check_connect(int idx, BINKD_CONFIG *config); +int try_outbound(BINKD_CONFIG *config); +void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config); + +#endif /* AMIGA_EVLOOP_INT_H */ diff --git a/amiga/proto_amiga.c b/amiga/proto_amiga.c new file mode 100644 index 00000000..0ad26221 --- /dev/null +++ b/amiga/proto_amiga.c @@ -0,0 +1,269 @@ +/* + * proto_amiga.c -- Amiga non-blocking BinkP protocol implementation + * + * proto_amiga.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include +#include +#include +#include +#include + +#include "sys.h" +#include "readcfg.h" +#include "common.h" +#include "protocol.h" +#include "ftnaddr.h" +#include "ftnnode.h" +#include "ftnq.h" +#include "tools.h" +#include "bsy.h" +#include "inbound.h" +#include "protoco2.h" +#include "prothlp.h" +#include "binlog.h" +#include "evloop.h" + +/* External functions from protocol.c */ +extern int init_protocol(STATE *state, SOCKET s_in, SOCKET s_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config); +extern int banner(STATE *state, BINKD_CONFIG *config); +extern int recv_block(STATE *state, BINKD_CONFIG *config); +extern int send_block(STATE *state, BINKD_CONFIG *config); +extern void bsy_touch(BINKD_CONFIG *config); +extern FTNQ *process_rcvdlist(STATE *state, FTNQ *q, BINKD_CONFIG *config); +extern int start_file_transfer(STATE *state, FTNQ *q, BINKD_CONFIG *config); +extern void ND_set_status(const char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config); +extern void deinit_protocol(STATE *state, BINKD_CONFIG *config, int status); +extern void evt_set(EVTQ *eq); +extern void msg_send2(STATE *state, t_msg m, char *s1, char *s2); + +/* External functions from other modules */ +extern void log_end_of_session(int err, STATE *state, BINKD_CONFIG *config); +extern void inb_remove_partial(STATE *state, BINKD_CONFIG *config); +extern void good_try(FTN_ADDR *fa, char *comment, BINKD_CONFIG *config); +extern void bad_try(FTN_ADDR *fa, const char *error, const int where, BINKD_CONFIG *config); +extern int create_poll(FTN_ADDR *fa, int flvr, BINKD_CONFIG *config); +extern void hold_node(FTN_ADDR *fa, time_t hold_until, BINKD_CONFIG *config); +extern int binkd_exit; + +/* External variables */ +extern int n_servers; + +/* + * amiga_proto_open -- Initialise a session and send the BinkP banner + * + * fd : Connected socket (same fd for in and out) + * to : Outbound node, NULL for inbound + * fa : Local AKA to use, may be NULL + * host : Remote hostname or dotted-IP string (caller-owned, stable) + * port : Remote port string, may be NULL + * dst_ip : Numeric remote IP, may be NULL (falls back to host) + * config : Current config + * + * Returns 0 on success, -1 on error (caller must close fd) + */ +int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config) +{ + struct sockaddr_storage sa; + socklen_t salen = (socklen_t)sizeof(sa); + char ownhost[BINKD_FQDNLEN + 1]; + char ownserv[MAXSERVNAME + 1]; + int rc; + + if (!init_protocol(state, fd, fd, to, fa, config)) + return -1; + + /* Peer identity for logging and %ip config checks */ + state->ipaddr = dst_ip ? (char *)dst_ip : (char *)host; + state->peer_name = (host && *host) ? (char *)host : state->ipaddr; + + /* local endpoint: Not used further, skip to avoid dangling pointer */ + + Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); + + /* banner() sends M_NUL lines and ADR messages */ + if (!banner(state, config)) + return -1; + + /* refuse if server limit reached */ + if (!to && n_servers > config->max_servers) + { + Log(1, "too many servers"); + msg_send2(state, M_BSY, "Too many servers", 0); + + return -1; + } + + return 0; +} + +/* + * amiga_proto_step -- Run one recv/send iteration of the BinkP loop + * + * readable : Non-zero if the socket has incoming data (from WaitSelect) + * writable : Non-zero if the socket can accept outgoing data + * + * Returns APROTO_RUNNING, APROTO_DONE_OK, or APROTO_DONE_ERR + * + * This is the loop body of protocol() with the select() call removed + * recv_block() and send_block() already handle EWOULDBLOCK gracefully, + * so calling this on a non-blocking socket is safe + */ +int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config) +{ + FTNQ *q; + int no; + + if (state->io_error) + return APROTO_DONE_ERR; + + /* Advance outgoing file queue if nothing is being sent */ + if (!state->local_EOB && state->q && !state->out.f && !state->waiting_for_GOT && !state->off_req_sent && state->state != P_NULL) + { + while (1) + { + q = 0; + if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) + { + if (start_file_transfer(state, q, config)) + break; + } + else + { + q_free(state->q, config); + state->q = 0; + break; + } + } + } + + /* Nothing left to send — issue EOB */ + if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) + { + if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) + { + state->local_EOB = 1; + msg_send2(state, M_EOB, 0, 0); + } + } + + /* Recv step: Only when socket is readable */ + if (readable) + { + if (!recv_block(state, config)) + return APROTO_DONE_ERR; + } + + /* + * send step: drive even when writable=0 if there is buffered data, + * pending messages, a file mid-transfer, or an EOF to flush. + */ + if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) + { + no = send_block(state, config); + + if (!no && no != 2) + return APROTO_DONE_ERR; + } + + bsy_touch(config); + + /* Batch/Session-end detection — Mirrors the break logic in protocol() */ + if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) + { + if (state->rcvdlist) + { + state->q = process_rcvdlist(state, state->q, config); + + q_to_killlist(&state->killlist, &state->n_killlist, state->q); + free_rcvdlist(&state->rcvdlist, &state->n_rcvdlist); + } + + Log(6, "batch: %i msgs", state->msgs_in_batch); + + if (state->msgs_in_batch <= 2 || (state->major * 100 + state->minor <= 100)) + { + /* Session done */ + ND_set_status("", &state->ND_addr, state, config); + state->ND_addr.z = -1; + + return APROTO_DONE_OK; + } + + /* Start next batch */ + state->msgs_in_batch = 0; + state->remote_EOB = 0; + state->local_EOB = 0; + + if (OK_SEND_FILES(state, config)) + { + state->q = q_scan_boxes(state->q, state->fa, state->nfa, state->to ? 1 : 0, config); + state->q = q_sort(state->q, state->fa, state->nfa, config); + } + } + + return APROTO_RUNNING; +} + +/* + * amiga_proto_close -- Flush remaining I/O and release STATE resources + * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR + */ +void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) +{ + int no; + char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; + int status = ok ? 0 : 1; + + /* Drain inbound queue */ + if (!state->io_error) + { + while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) + Log(9, "purged %d bytes", no); + } + + /* Flush pending outbound messages */ + while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) + ; + + if (ok) + { + log_end_of_session(0, state, config); + process_killlist(state->killlist, state->n_killlist, 's'); + inb_remove_partial(state, config); + + if (state->to) + good_try(&state->to->fa, "CONNECT/BND", config); + } + else + { + log_end_of_session(1, state, config); + process_killlist(state->killlist, state->n_killlist, 0); + + if (!binkd_exit && state->to) + bad_try(&state->to->fa, "Bad session", BAD_IO, config); + + /* Restore poll flavour if files were left mid-transfer */ + if (state->to && tolower(state->maxflvr) != 'h') + { + Log(4, "restoring poll with '%c' flavour", state->maxflvr); + + create_poll(&state->to->fa, state->maxflvr, config); + } + } + + if (state->to && state->r_skipped_flag && config->hold_skipped > 0) + { + Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); + + hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); + } + + deinit_protocol(state, config, status); + evt_set(state->evt_queue); + state->evt_queue = NULL; +} diff --git a/amiga/proto_amiga.h b/amiga/proto_amiga.h new file mode 100644 index 00000000..58e80c85 --- /dev/null +++ b/amiga/proto_amiga.h @@ -0,0 +1,30 @@ +/* + * proto_amiga.h -- Amiga non-blocking BinkP protocol implementation + * + * proto_amiga.h is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifndef _PROTO_AMIGA_H +#define _PROTO_AMIGA_H + +#include "protoco2.h" +#include "readcfg.h" + +/* amiga_proto_step() return codes */ +#define APROTO_RUNNING 0 +#define APROTO_DONE_OK 1 +#define APROTO_DONE_ERR 2 + +/* amiga_proto_open -- Initialise a session and send the BinkP banner */ +int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config); + +/* amiga_proto_step-- run one recv / send iteration of the BinkP loop */ +int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config); + +/* amiga_proto_close -- flush remaining I/O and release STATE resources */ +void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok); + +#endif /* _PROTO_AMIGA_H */ diff --git a/amiga/rename.c b/amiga/rename.c old mode 100644 new mode 100755 index 7e97a07f..58b967bd --- a/amiga/rename.c +++ b/amiga/rename.c @@ -1,10 +1,137 @@ #include #include +#include + +#include +#include /* atoi */ +#include /* isdigit */ + +#define PATHBUF 512 int o_rename(char *from, char *to) { - if (Rename((STRPTR)from, (STRPTR)to)) /* cross-volume move won't work */ + struct FileInfoBlock *fib; + char dir[PATHBUF]; + char base[PATHBUF]; + char newname[PATHBUF]; + char *slash; + ULONG max = 0; + BPTR dirlock; + char *d = NULL; + const char *src = NULL; + ULONG n = 0; + + /* Try direct rename first */ + if (Rename((STRPTR)from, (STRPTR)to)) + return 0; + + /* Split path */ + slash = strrchr(to, '/'); + + if (!slash) + slash = strrchr(to, ':'); + + if (slash) + { + ULONG len = slash - to; + + if (len >= PATHBUF) + len = PATHBUF - 1; + + strncpy(dir, to, len); + dir[len] = '\0'; + + strncpy(base, slash + 1, PATHBUF - 1); + base[PATHBUF - 1] = '\0'; + } + else + { + strcpy(dir, "."); + strncpy(base, to, PATHBUF - 1); + base[PATHBUF - 1] = '\0'; + } + + /* Lock directory */ + dirlock = Lock((STRPTR)dir, ACCESS_READ); + + if (!dirlock) + { + errno = ENOENT; + return -1; + } + + fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); + + if (!fib) + { + UnLock(dirlock); + errno = ENOMEM; + return -1; + } + + /* Scan directory safely under lock */ + if (Examine(dirlock, fib)) + { + while (ExNext(dirlock, fib)) + { + if (strncmp(fib->fib_FileName, base, strlen(base)) == 0) + { + const char *p = NULL; + + p = fib->fib_FileName + strlen(base); + + if (*p != '.') + { + continue; + } + + /* .001 style */ + if (isdigit((UBYTE)p[1]) && isdigit((UBYTE)p[2]) && isdigit((UBYTE)p[3])) + { + n = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); + if (n > max) + max = n; + } + + /* FIDO volume style (.mo0 .th1 etc) */ + if (isdigit((UBYTE)p[1]) && !isdigit((UBYTE)p[2])) + { + n = p[1] - '0'; + if (n > max) + max = n; + } + } + } + } + + FreeDosObject(DOS_FIB, fib); + UnLock(dirlock); + + /* Build new name */ + d = newname; + src = to; + n = max + 1; + + if (n > 999) + n = 0; + + /* Copy base */ + while (*src && (d - newname) < (PATHBUF - 5)) + *d++ = *src++; + + *d++ = '.'; + + /* Manual 3-digit write */ + *d++ = '0' + (n / 100); + n %= 100; + *d++ = '0' + (n / 10); + *d++ = '0' + (n % 10); + *d = '\0'; + + /* Rename */ + if (Rename((STRPTR)from, (STRPTR)newname)) + return 0; + + errno = EACCES; return -1; - else - return 0; } diff --git a/amiga/rfc2553_amiga.c b/amiga/rfc2553_amiga.c new file mode 100644 index 00000000..56458400 --- /dev/null +++ b/amiga/rfc2553_amiga.c @@ -0,0 +1,323 @@ +/* + * rfc2553_amiga.c -- getaddrinfo/getnameinfo fallback for AmigaOS 3 + * + * rfc2553_amiga.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifdef AMIGA + +#include "amiga/bsdsock.h" /* LP stubs + SocketBase */ +#include "rfc2553.h" /* sets HAVE_GETADDRINFO / HAVE_GETNAMEINFO */ +#include "sem.h" + +#include +#include +#include +#include + +#define safe_strncpy(dst, src, n) \ + do \ + { \ + strncpy((dst), (src), (n)); \ + (dst)[(n) - 1] = '\0'; \ + } while (0) + +#ifndef HAVE_GETADDRINFO + +void freeaddrinfo(struct addrinfo *ai); /* forward decl */ + +int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) +{ + struct addrinfo **tail = res; + struct hostent *hent = NULL; + unsigned int port; + int proto; + const char *end; + char **addrp; + + static char passive_dummy = '\0'; + char *passive_list[2] = {&passive_dummy, NULL}; + + if (!res) + { + return EAI_UNKNOWN; + } + + *res = NULL; + + port = servname ? htons((unsigned short)strtol(servname, (char **)&end, 0)) : 0; + proto = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; + + lockresolvsem(); + + if (servname && end != servname + strlen(servname)) + { + struct servent *se = NULL; + + if (!hints || hints->ai_socktype == SOCK_STREAM) + se = getservbyname((char *)servname, "tcp"); + + if (hints && hints->ai_socktype == SOCK_DGRAM) + se = getservbyname((char *)servname, "udp"); + + if (!se) + { + releaseresolvsem(); + return EAI_NONAME; + } + + port = se->s_port; + + if (strcmp((char *)se->s_proto, "tcp") == 0) + proto = SOCK_STREAM; + else if (strcmp((char *)se->s_proto, "udp") == 0) + proto = SOCK_DGRAM; + else + { + releaseresolvsem(); + return EAI_NONAME; + } + + if (hints && hints->ai_socktype && hints->ai_socktype != proto) + { + releaseresolvsem(); + return EAI_SERVICE; + } + } + + if (!hints || !(hints->ai_flags & AI_PASSIVE)) + { + unsigned long nip = inet_addr((char *)nodename); + + if (nip != (unsigned long)INADDR_NONE) + { + struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); + struct sockaddr_in *sin; + + if (!ai) + { + releaseresolvsem(); + return EAI_MEMORY; + } + *tail = ai; + + sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); + + if (!sin) + { + free(ai); + releaseresolvsem(); + return EAI_MEMORY; + } + + ai->ai_family = AF_INET; + ai->ai_socktype = proto; + ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; + ai->ai_addrlen = sizeof(*sin); + ai->ai_addr = (struct sockaddr *)sin; + sin->sin_family = AF_INET; + sin->sin_port = port; + sin->sin_addr.s_addr = nip; + + releaseresolvsem(); + return 0; + } + + hent = gethostbyname((char *)nodename); + + if (!hent) + { + int herr = errno; + releaseresolvsem(); + return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL + : EAI_NONAME; + } + + if (!hent->h_addr_list || !hent->h_addr_list[0]) + { + releaseresolvsem(); + return EAI_NONAME; + } + + addrp = hent->h_addr_list; + } + else + { + addrp = passive_list; + } + + for (; *addrp; addrp++) + { + struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); + struct sockaddr_in *sin; + + if (!ai) + { + releaseresolvsem(); + freeaddrinfo(*res); + *res = NULL; + return EAI_MEMORY; + } + + if (!*res) + *res = ai; + *tail = ai; + tail = &ai->ai_next; + + sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); + + if (!sin) + { + releaseresolvsem(); + freeaddrinfo(*res); + *res = NULL; + return EAI_MEMORY; + } + + ai->ai_family = AF_INET; + ai->ai_socktype = proto; + ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; + ai->ai_addrlen = sizeof(*sin); + ai->ai_addr = (struct sockaddr *)sin; + sin->sin_family = AF_INET; + sin->sin_port = port; + + if (!hints || !(hints->ai_flags & AI_PASSIVE)) + { + size_t cpylen = sizeof(sin->sin_addr); + + if (hent->h_length > 0 && (size_t)hent->h_length < cpylen) + cpylen = (size_t)hent->h_length; + + memcpy(&sin->sin_addr, *addrp, cpylen); + } + } + + releaseresolvsem(); + return 0; +} + +void freeaddrinfo(struct addrinfo *ai) +{ + struct addrinfo *next; + + while (ai) + { + free(ai->ai_addr); + next = ai->ai_next; + free(ai); + ai = next; + } +} + +static const char *ai_errlist[] = + { + "Success", + "hostname nor servname provided, or not known", + "Temporary failure in name resolution", + "Non-recoverable failure in name resolution", + "No address associated with hostname", + "ai_family not supported", + "ai_socktype not supported", + "service name not supported for ai_socktype", + "Address family for hostname not supported", + "Memory allocation failure", + "System error returned in errno", + "Unknown error", +}; + +char *gai_strerror(int ecode) +{ + if (ecode > 0 || ecode < EAI_UNKNOWN) + ecode = EAI_UNKNOWN; + return (char *)ai_errlist[-ecode]; +} + +#endif /* !HAVE_GETADDRINFO */ + +#ifndef HAVE_GETNAMEINFO + +#ifndef NI_DATAGRAM +#define NI_DATAGRAM (1 << 4) +#endif + +int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; + + (void)salen; + + if (sa->sa_family != AF_INET) + return EAI_ADDRFAMILY; + + if (host && hostlen > 0) + { + if (!(flags & NI_NUMERICHOST)) + { + struct hostent *he; + + lockresolvsem(); + he = gethostbyaddr((char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET); + + if (he) + { + safe_strncpy(host, (char *)he->h_name, hostlen); + releaseresolvsem(); + } + else + { + int herr = errno; + releaseresolvsem(); + if (flags & NI_NAMEREQD) + return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL + : EAI_NONAME; + flags |= NI_NUMERICHOST; + } + } + + if (flags & NI_NUMERICHOST) + { + lockhostsem(); + safe_strncpy(host, (char *)Inet_NtoA(sin->sin_addr.s_addr), hostlen); + releasehostsem(); + } + } + + if (serv && servlen > 0) + { + if (!(flags & NI_NUMERICSERV)) + { + struct servent *se; + + lockresolvsem(); + + se = (flags & NI_DATAGRAM) ? getservbyport(ntohs(sin->sin_port), "udp") : getservbyport(ntohs(sin->sin_port), "tcp"); + + if (se) + { + safe_strncpy(serv, (char *)se->s_name, servlen); + releaseresolvsem(); + } + else + { + releaseresolvsem(); + + if (flags & NI_NAMEREQD) + return EAI_NONAME; + + flags |= NI_NUMERICSERV; + } + } + + if (flags & NI_NUMERICSERV) + snprintf(serv, servlen, "%u", ntohs(sin->sin_port)); + } + + return 0; +} + +#endif /* !HAVE_GETNAMEINFO */ +#endif /* AMIGA */ diff --git a/amiga/sem.c b/amiga/sem.c old mode 100644 new mode 100755 index 33b10d99..45075c17 --- a/amiga/sem.c +++ b/amiga/sem.c @@ -1,29 +1,100 @@ /* * Amiga semaphores */ + #include #include -#include +#include +#include + +extern void Log(int lev, char *s, ...); + +int _InitSem(void *vpSem) +{ + memset(vpSem, 0, sizeof(struct SignalSemaphore)); + InitSemaphore((struct SignalSemaphore *)vpSem); + return 0; +} + +int _CleanSem(void *vpSem) +{ + return 0; +} + +int _LockSem(void *vpSem) +{ + ObtainSemaphore((struct SignalSemaphore *)vpSem); + return 0; +} + +int _ReleaseSem(void *vpSem) +{ + ReleaseSemaphore((struct SignalSemaphore *)vpSem); + return 0; +} + +int _InitEventSem(EVENTSEM *sem) +{ + if (!sem) + return -1; -extern void Log (int lev, char *s,...); + sem->waiter = NULL; + sem->sigbit = AllocSignal(-1); -int _InitSem(void *vpSem) { - memset(vpSem, 0, sizeof (struct SignalSemaphore)); - InitSemaphore ((struct SignalSemaphore*)vpSem); - return(0); + if (sem->sigbit == (ULONG)-1) + return -1; + + return 0; } -int _CleanSem(void *vpSem) { - return (0); +int _CleanEventSem(EVENTSEM *sem) +{ + if (!sem) + return -1; + + if (sem->sigbit != (ULONG)-1) + { + FreeSignal((LONG)sem->sigbit); + sem->sigbit = (ULONG)-1; + } + + sem->waiter = NULL; + return 0; } -int _LockSem(void *vpSem) { - ObtainSemaphore ((struct SignalSemaphore *)vpSem); - return (0); +int _PostSem(EVENTSEM *sem) +{ + if (!sem) + return -1; + + if (sem->waiter && sem->sigbit != (ULONG)-1) + { + Signal((struct Task *)sem->waiter, 1UL << sem->sigbit); + } + + return 0; } -int _ReleaseSem(void *vpSem) { - ReleaseSemaphore ((struct SignalSemaphore *)vpSem); - return (0); +int _WaitSem(EVENTSEM *sem, int sec) +{ + ULONG mask; + struct Task *me; + + if (!sem || sem->sigbit == (ULONG)-1) + return -1; + + me = FindTask(NULL); + sem->waiter = me; + + /* Wait on SIGBREAKF_CTRL_C to avoid hanging on race */ + mask = Wait((1UL << sem->sigbit) | SIGBREAKF_CTRL_C); + + sem->waiter = NULL; + + /* Return timeout/break indication if only CTRL_C fired */ + if (!(mask & (1UL << sem->sigbit)) && (mask & SIGBREAKF_CTRL_C)) + return -1; + + return 0; } diff --git a/amiga/session.c b/amiga/session.c new file mode 100644 index 00000000..cc5dcd49 --- /dev/null +++ b/amiga/session.c @@ -0,0 +1,462 @@ +/* + * session.c -- session management for AmigaOS 3 binkd + * + * session.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include +#include + +#include +#include +#include +#include + +#include "sys.h" +#include "iphdr.h" +#include "readcfg.h" +#include "common.h" +#include "tools.h" +#include "client.h" +#include "protocol.h" +#include "ftnq.h" +#include "ftnnode.h" +#include "ftnaddr.h" +#include "bsy.h" +#include "iptools.h" +#include "rfc2553.h" +#include "srv_gai.h" +#include "amiga/bsdsock.h" +#include "amiga/evloop_int.h" +#include "amiga/proto_amiga.h" + +extern int binkd_exit; +extern int ext_rand; +extern int client_flag; +extern int poll_flag; + +/* Session table */ +int sess_alloc(void) +{ + int i; + + for (i = 0; i < max_sessions; i++) + { + if (sessions[i].phase == SESS_FREE) + { + memset(&sessions[i], 0, sizeof(sess_t)); + sessions[i].fd = INVALID_SOCKET; + sessions[i].phase = SESS_FREE; + return i; + } + } + + return -1; +} + +void sess_free(int idx) +{ + sess_t *s = &sessions[idx]; + + if (s->fd != INVALID_SOCKET) + { + soclose(s->fd); + s->fd = INVALID_SOCKET; + } + + if (s->ai_head) + { + freeaddrinfo(s->ai_head); + s->ai_head = NULL; + } + + memset(&s->state, 0, sizeof(STATE)); + s->phase = SESS_FREE; +} + +/* Inbound: accept a new connection */ +void do_accept(SOCKET lfd, BINKD_CONFIG *config) +{ + struct sockaddr_storage sa; + socklen_t salen = (socklen_t)sizeof(sa); + SOCKET fd; + int idx; + sess_t *s; + char host[BINKD_FQDNLEN + 1]; + char ip[BINKD_FQDNLEN + 1]; + + fd = accept(lfd, (struct sockaddr *)&sa, &salen); + + if (fd == INVALID_SOCKET) + { + if (TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN) + Log(1, "accept(): %s", TCPERR()); + + return; + } + + if (binkd_exit) + { + soclose(fd); + return; + } + + idx = sess_alloc(); + + if (idx < 0) + { + Log(1, "session table full, refusing inbound"); + soclose(fd); + return; + } + + /* getnameinfo() Is unreliable on AmiTCP: use inet_ntoa directly */ + if (((struct sockaddr *)&sa)->sa_family == AF_INET) + { + struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; + strnzcpy(ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); + } + else + { + strnzcpy(ip, "unknown", BINKD_FQDNLEN); + } + + /* Backresolv not supported on AmiTCP: always use IP as host */ + strnzcpy(host, ip, BINKD_FQDNLEN); + + set_nonblock(fd); + + s = &sessions[idx]; + s->fd = fd; + s->inbound = 1; + s->node = NULL; + s->ai_head = NULL; + s->last_io = time(NULL); + strnzcpy(s->host, host, BINKD_FQDNLEN); + strnzcpy(s->ip, ip, BINKD_FQDNLEN); + s->port[0] = '\0'; + + if (amiga_proto_open(&s->state, fd, NULL, NULL, s->host, NULL, s->ip, config) != 0) + { + Log(1, "proto_open failed for %s", ip); + sess_free(idx); + return; + } + + s->phase = SESS_RUNNING; + n_servers++; + Log(4, "inbound slot[%d] from %s", idx, ip); +} + +/* Outbound: Non-blocking connect() */ +int start_connect(sess_t *s, BINKD_CONFIG *config) +{ + SOCKET fd; + int rc; + + s->ip[0] = '\0'; + s->port[0] = '\0'; + + fd = socket(s->ai_cur->ai_family, s->ai_cur->ai_socktype, s->ai_cur->ai_protocol); + + if (fd == INVALID_SOCKET) + { + Log(1, "outbound socket(): %s", TCPERR()); + return -1; + } + + /* getnameinfo() is unreliable on AmiTCP: may return rc=0 with garbage + * Use inet_ntoa/ntohs directly */ + if (s->ai_cur->ai_family == AF_INET) + { + struct sockaddr_in *sa4 = (struct sockaddr_in *)s->ai_cur->ai_addr; + strnzcpy(s->ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); + snprintf(s->port, MAXPORTSTRLEN, "%u", (unsigned)ntohs(sa4->sin_port)); + } + else + { + strnzcpy(s->ip, "unknown", BINKD_FQDNLEN); + strnzcpy(s->port, "0", MAXPORTSTRLEN); + } + + Log(4, "connecting %s [%s]:%s", s->host, s->ip, s->port); + + if (config->bindaddr[0]) + { + struct addrinfo src_h, *src_ai; + memset(&src_h, 0, sizeof(src_h)); + src_h.ai_family = s->ai_cur->ai_family; + src_h.ai_socktype = SOCK_STREAM; + src_h.ai_protocol = IPPROTO_TCP; + + if (getaddrinfo(config->bindaddr, NULL, &src_h, &src_ai) == 0) + { + bind(fd, src_ai->ai_addr, (int)src_ai->ai_addrlen); + freeaddrinfo(src_ai); + } + } + + set_nonblock(fd); + + rc = connect(fd, s->ai_cur->ai_addr, (int)s->ai_cur->ai_addrlen); + + if (rc == 0 || TCPERRNO == EINPROGRESS || TCPERRNO == EWOULDBLOCK) + { + s->fd = fd; + s->conn_start = time(NULL); + return 0; + } + + Log(1, "connect %s: %s", s->host, TCPERR()); + + bad_try(&s->node->fa, TCPERR(), BAD_CALL, config); + soclose(fd); + + return -1; +} + +int try_outbound(BINKD_CONFIG *config) +{ + FTN_NODE *node; + sess_t *s; + int idx, rc; + struct addrinfo hints; + char dest[FTN_ADDR_SZ + 1]; + char host[BINKD_FQDNLEN + 5 + 1]; + char port[MAXPORTSTRLEN + 1]; + + if (!client_flag) + return 0; + + if (!config->q_present) + { + q_free(SCAN_LISTED, config); + + if (config->printq) + Log(-1, "scan\r"); + + q_scan(SCAN_LISTED, config); + config->q_present = 1; + + if (config->printq) + { + q_list(stderr, SCAN_LISTED, config); + Log(-1, "idle\r"); + } + } + + if (n_clients >= config->max_clients) + return 0; + + node = q_next_node(config); + + if (!node) + return 0; + + ftnaddress_to_str(dest, &node->fa); + + if (!bsy_test(&node->fa, F_BSY, config) || !bsy_test(&node->fa, F_CSY, config)) + { + Log(4, "%s busy", dest); + return 0; + } + + idx = sess_alloc(); + + if (idx < 0) + { + Log(2, "table full, deferring %s", dest); + return 0; + } + + s = &sessions[idx]; + memset(s, 0, sizeof(*s)); + s->fd = INVALID_SOCKET; + s->node = node; + s->inbound = 0; + + rc = get_host_and_port(1, host, port, node->hosts, &node->fa, config); + + if (rc <= 0) + { + Log(1, "%s: bad host list", dest); + sess_free(idx); + + return 0; + } + + strnzcpy(s->host, host, BINKD_FQDNLEN); + strnzcpy(s->port, port, MAXPORTSTRLEN); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = node->IP_afamily; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + rc = srv_getaddrinfo(host, port, &hints, &s->ai_head); + + if (rc != 0) + { + Log(1, "%s: getaddrinfo error code=%d: %s", dest, rc, gai_strerror(rc)); + + bad_try(&node->fa, "getaddrinfo failed", BAD_CALL, config); + sess_free(idx); + + return 0; + } + + s->ai_cur = s->ai_head; + + if (start_connect(s, config) != 0) + { + sess_free(idx); + return 0; + } + + s->phase = SESS_CONNECTING; + n_clients++; + + Log(4, "outbound slot[%d] -> %s", idx, dest); + + return 1; +} + +/* Check completion of async connect() */ +void check_connect(int idx, BINKD_CONFIG *config) +{ + sess_t *s = &sessions[idx]; + int err = 0; + socklen_t el = (socklen_t)sizeof(err); + int tmo; + + tmo = config->connect_timeout ? config->connect_timeout : 30; + + if ((int)(time(NULL) - s->conn_start) >= tmo) + { + Log(1, "connect timeout -> %s", s->host); + + bad_try(&s->node->fa, "Timeout", BAD_CALL, config); + n_clients--; + sess_free(idx); + + return; + } + + getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &el); + + if (err) + { + Log(1, "connect -> %s: %s", s->host, strerror(err)); + + bad_try(&s->node->fa, strerror(err), BAD_CALL, config); + + soclose(s->fd); + s->fd = INVALID_SOCKET; + s->ai_cur = s->ai_cur->ai_next; + + if (s->ai_cur && start_connect(s, config) == 0) + return; /* trying next address */ + + n_clients--; + sess_free(idx); + + return; + } + + Log(4, "connected -> %s [%s]", s->host, s->ip); + ext_rand = rand(); + + if (amiga_proto_open(&s->state, s->fd, s->node, NULL, s->host, s->port, s->ip, config) != 0) + { + Log(1, "proto_open failed for %s", s->host); + + n_clients--; + sess_free(idx); + return; + } + + s->phase = SESS_RUNNING; + s->last_io = time(NULL); +} + +/* Run one protocol step on an active session */ +void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config) +{ + sess_t *s = &sessions[idx]; + int rc; + int tmo; + + tmo = config->nettimeout ? config->nettimeout : 300; + + if ((int)(time(NULL) - s->last_io) >= tmo) + { + Log(1, "slot[%d] net timeout", idx); + + if (s->node) + bad_try(&s->node->fa, "Timeout", BAD_IO, config); + + amiga_proto_close(&s->state, config, 0); + + if (s->inbound) + n_servers--; + else + n_clients--; + + sess_free(idx); + + return; + } + + if (s->fd == INVALID_SOCKET) + { + Log(1, "slot[%d] invalid socket, closing session", idx); + + if (s->node) + bad_try(&s->node->fa, "Invalid socket", BAD_IO, config); + + if (s->inbound) + n_servers--; + else + n_clients--; + + sess_free(idx); + + return; + } + + /* WaitSelect() may not report a readable socket when the remote has + * sent a TCP END. Probe with MSG_PEEK so recv_block() sees the EOF */ + if (!rd && !wr && s->state.state != P_NULL) + { + char peek; + int pr = recv(s->fd, &peek, 1, MSG_PEEK); + + if (pr == 0 || (pr < 0 && TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN)) + rd = 1; + } + + rc = amiga_proto_step(&s->state, rd, wr, config); + + if (rd || wr) + s->last_io = time(NULL); + + if (rc == APROTO_RUNNING) + return; + + amiga_proto_close(&s->state, config, rc == APROTO_DONE_OK); + + Log(4, "slot[%d] %s", idx, rc == APROTO_DONE_OK ? "OK" : "ERR"); + + if (s->inbound) + n_servers--; + else + n_clients--; + + sess_free(idx); + + if (poll_flag && n_clients == 0 && n_servers == 0) + binkd_exit = 1; +} diff --git a/amiga/sock.c b/amiga/sock.c new file mode 100644 index 00000000..ca170071 --- /dev/null +++ b/amiga/sock.c @@ -0,0 +1,130 @@ +/* + * sock.c -- listen socket management for AmigaOS 3 + * + * sock.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include +#include + +#include +#include + +#include "sys.h" +#include "readcfg.h" +#include "tools.h" +#include "server.h" +#include "rfc2553.h" +#include "amiga/bsdsock.h" +#include "amiga/evloop_int.h" + +extern SOCKET sockfd[]; +extern int sockfd_used; +extern int server_flag; + +void set_nonblock(SOCKET fd) +{ + long flag = 1L; + + if (IoctlSocket(fd, FIONBIO, (char *)&flag) != 0) + Log(2, "IoctlSocket(FIONBIO) failed: %s", TCPERR()); +} + +int open_listen_sockets(BINKD_CONFIG *config) +{ + struct listenchain *ll; + struct addrinfo hints, *ai, *head; + int err, opt = 1; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + sockfd_used = 0; + + for (ll = config->listen.first; ll; ll = ll->next) + { + err = getaddrinfo(ll->addr[0] ? ll->addr : NULL, ll->port, &hints, &head); + + if (err) + { + Log(1, "listen getaddrinfo(%s:%s): %s", ll->addr[0] ? ll->addr : "*", ll->port, gai_strerror(err)); + return -1; + } + + for (ai = head; ai && sockfd_used < MAX_LISTENSOCK; ai = ai->ai_next) + { + SOCKET fd; + int retries = 6; + + fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + + if (fd == INVALID_SOCKET) + { + Log(1, "listen socket(): %s", TCPERR()); + continue; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, (int)sizeof(opt)) != 0) + Log(2, "setsockopt(SO_REUSEADDR) failed: %s", TCPERR()); + + /* Bsdsocket may hold the port briefly after socket close */ + while (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) != 0) + { + if (--retries == 0) + { + Log(1, "listen bind(): %s", TCPERR()); + + soclose(fd); + freeaddrinfo(head); + return -1; + } + + Log(2, "bind retry in 2s: %s", TCPERR()); + + Delay(100UL); /* 100 ticks = 2s @ 50Hz */ + } + + if (listen(fd, 5) != 0) + { + Log(1, "listen(): %s", TCPERR()); + + soclose(fd); + freeaddrinfo(head); + return -1; + } + + set_nonblock(fd); + sockfd[sockfd_used] = fd; + sockfd_used++; + } + + freeaddrinfo(head); + + Log(3, "listening on %s:%s", + ll->addr[0] ? ll->addr : "*", ll->port); + } + + if (sockfd_used == 0 && server_flag) + { + Log(1, "evloop: no listen sockets opened"); + return -1; + } + + return 0; +} + +void close_listen_sockets(void) +{ + int i; + + for (i = 0; i < sockfd_used; i++) + soclose(sockfd[i]); + + sockfd_used = 0; +} diff --git a/amiga/utime.c b/amiga/utime.c new file mode 100644 index 00000000..ab26a43c --- /dev/null +++ b/amiga/utime.c @@ -0,0 +1,77 @@ +/* + * utime.c -- utime() stub for AmigaOS 3 without ixemul + * + * utime.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifdef AMIGA + +#include +#include +#include +#include + +#include "amiga/dirent.h" /* declares struct utimbuf and utime() prototype */ + +/* Days between AmigaDOS epoch (1978-01-01) and POSIX epoch (1970-01-01) */ +#define AMIGA_EPOCH_DELTA_DAYS 2922UL +#define SECONDS_PER_DAY 86400UL +#define SECONDS_PER_MINUTE 60UL + +int utime(const char *path, const struct utimbuf *times) +{ + struct DateStamp ds; + LONG seconds_today; + LONG total_seconds; + + if (!path) + { + errno = EINVAL; + return -1; + } + + if (!times) + { + /* Use current time */ + DateStamp(&ds); + } + else + { + LONG t = (LONG)times->modtime; + + if (t < (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY)) + { + /* Time predates AmigaDOS epoch -- clamp to epoch */ + t = 0; + } + else + { + t -= (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY); + } + + ds.ds_Days = (LONG)(t / (LONG)SECONDS_PER_DAY); + total_seconds = t % (LONG)SECONDS_PER_DAY; + ds.ds_Minute = (LONG)(total_seconds / (LONG)SECONDS_PER_MINUTE); + seconds_today = total_seconds % (LONG)SECONDS_PER_MINUTE; + ds.ds_Tick = seconds_today * (LONG)TICKS_PER_SECOND; + } + + if (!SetFileDate((STRPTR)path, &ds)) + { + /* Most likely cause: file does not exist or is write-protected */ + LONG err = IoErr(); + + if (err == ERROR_OBJECT_NOT_FOUND || err == ERROR_DIR_NOT_FOUND) + errno = ENOENT; + else + errno = EACCES; + return -1; + } + + return 0; +} + +#endif /* AMIGA */ diff --git a/binkd.c b/binkd.c old mode 100644 new mode 100755 index bbf2edb8..a58ea195 --- a/binkd.c +++ b/binkd.c @@ -54,6 +54,10 @@ #include "unix/daemonize.h" #endif +#ifdef AMIGA +/* amiga/bsdsock.h pulled in via iphdr.h for AMIGA */ +#include "amiga/evloop.h" +#endif #ifdef WIN32 #include "nt/service.h" #include "nt/w32tools.h" @@ -62,9 +66,11 @@ #endif #endif +#include "bsycleanup.h" + #include "confopt.h" -#ifdef HAVE_THREADS +#if defined(HAVE_THREADS) || defined(AMIGA) MUTEXSEM hostsem; MUTEXSEM resolvsem; MUTEXSEM lsem; @@ -94,9 +100,13 @@ static char *remote_addr, *remote_node; char *configpath = NULL; /* Config file name */ char **saved_envp; -#ifdef HAVE_FORK +/* mypid: needed by HAVE_FORK and AMIGA targets */ +#if defined(HAVE_FORK) || defined(AMIGA) +int mypid; +#endif -int mypid, got_sighup, got_sigchld; +#ifdef HAVE_FORK +int got_sighup, got_sigchld; void chld (int *childcount) { @@ -195,9 +205,13 @@ void usage (void) #endif " -C reload on config change\n" " -c run client only\n" +#ifndef AMIGA " -i run server on stdin/stdout pipe (by inetd or other)\n" +#endif " -f node run server protected session with this node\n" +#ifndef AMIGA " -a ip assume remote address when running with '-i' switch\n" +#endif #if defined(BINKD9X) " -t cmd (start|stop|restart|status|install|uninstall) service(s)\n" " -S name set Win9x service name, all - use all services\n" @@ -312,12 +326,14 @@ char *parseargs (int argc, char *argv[]) case 'c': client_flag = 1; break; +#ifndef AMIGA case 'i': inetd_flag = 1; break; case 'a': /* remote IP address */ remote_addr = strdup(optarg); break; +#endif case 'f': /* remote FTN address */ remote_node = strdup(optarg); break; @@ -416,6 +432,28 @@ char *parseargs (int argc, char *argv[]) } if (optind\n", extract_filename(argv[0])); + fprintf(stderr, " Use -P
for polling a specific node (e.g., -P 1:23/456.7)\n"); + exit(1); + } + + /* Check for leftover FTN addresses in extra arguments */ + while (optind < argc) + { + char *extra = argv[optind++]; + if (strchr(extra, ':') || strchr(extra, '@')) + { + fprintf(stderr, "%s: Error: Unexpected FTN address '%s' in arguments.\n", extract_filename(argv[0]), extra); + fprintf(stderr, " Use -P
before the config file to poll a node.\n"); + exit(1); + } + } + #ifdef OS2 if (optindloglevel, current_config->conlog, current_config->logpath, current_config->nolog.first); + + /* Clean up old .bsy/.csy files at startup */ + cleanup_old_bsy(current_config); } else if (verbose_flag) { @@ -665,9 +706,13 @@ int main (int argc, char *argv[]) if (p) { - remote_addr = strdup(p); - p = strchr(remote_addr, ' '); - if (p) *p = '\0'; + remote_addr = strdup(p); + /* Guard against null pointer dereference if strdup fails */ + if (remote_addr) + { + p = strchr(remote_addr, ' '); + if (p) *p = '\0'; + } } } /* not using stdin/stdout itself to avoid possible collisions */ @@ -677,6 +722,9 @@ int main (int argc, char *argv[]) inetd_socket_out = dup(fileno(stdout)); #ifdef UNIX tempfd = open("/dev/null", O_RDWR); +#elif defined(AMIGA) + /* NIL: is the native AmigaDOS null device (no ixemul) */ + tempfd = open("NIL:", O_RDWR); #else tempfd = open("nul", O_RDWR); #endif @@ -707,6 +755,15 @@ int main (int argc, char *argv[]) signal (SIGHUP, sighandler); #endif +#ifdef AMIGA + /* AmigaOS 3: WaitSelect() loop, no fork/threads */ + { + BINKD_CONFIG *ev_cfg = lock_current_config(); + amiga_evloop_run(ev_cfg, server_flag, client_flag); + unlock_config_structure(ev_cfg, 0); + return 0; + } +#else if (client_flag && !server_flag) { clientmgr (0); @@ -721,7 +778,9 @@ int main (int argc, char *argv[]) if (client_flag && (pidcmgr = branch (clientmgr, 0, 0)) < 0) { Log (0, "cannot branch out"); + exit (1); } +#endif /* !AMIGA */ if (*current_config->pid_file) { diff --git a/binlog.c b/binlog.c old mode 100644 new mode 100755 index 0fe0f085..49764b0f --- a/binlog.c +++ b/binlog.c @@ -35,6 +35,10 @@ #include "tools.h" #include "sem.h" +#if defined(HAVE_THREADS) || defined(AMIGA) +extern MUTEXSEM blsem; +#endif + /* Write 16-bit integer to file in intel bytes order */ static int fput16(u16 arg, FILE *file) { diff --git a/branch.c b/branch.c old mode 100644 new mode 100755 index f75f4e7a..874290a2 --- a/branch.c +++ b/branch.c @@ -20,12 +20,6 @@ #include "tools.h" #include "sem.h" -#ifdef AMIGA -int ix_vfork (void); -void vfork_setup_child (void); -void ix_vfork_resume (void); -#endif - #ifdef WITH_PTHREADS typedef struct { void (*F) (void *); @@ -132,23 +126,6 @@ int branch (register void (*F) (void *), register void *arg, register size_t siz #endif #endif -#ifdef AMIGA - /* this is rather bizzare. this function pretends to be a fork and behaves - * like one, but actually it's a kind of a thread. so we'll need semaphores */ - - if (!(rc = ix_vfork ())) - { - vfork_setup_child (); - ix_vfork_resume (); - F (arg); - exit (0); - } - else if (rc < 0) - { - Log (1, "ix_vfork: %s", strerror (errno)); - } -#endif - #if defined(DOS) || defined(DEBUGCHILD) rc = 0; F (arg); diff --git a/breaksig.c b/breaksig.c old mode 100644 new mode 100755 index b2dacc9c..83f98a22 --- a/breaksig.c +++ b/breaksig.c @@ -46,6 +46,16 @@ int set_break_handlers (void) { atexit (exitfunc); +#ifdef AMIGA + /* AmigaOS / libnix: signal() maps SIGINT -> SIGBREAKF_CTRL_C + * Register exitsig() so that Ctrl+C sets binkd_exit=1 even when + * the process is NOT blocked inside WaitSelect() (e.g. during disk + * I/O). When inside WaitSelect(), amiga_select_wrap() in bsdsock.h + * detects the break and sets binkd_exit=1 directly without going + * through this signal handler. */ + signal (SIGINT, exitsig); + signal (SIGTERM, exitsig); +#else #ifdef SIGBREAK signal (SIGBREAK, exitsig); #endif @@ -58,5 +68,6 @@ int set_break_handlers (void) #ifdef SIGTERM signal (SIGTERM, exitsig); #endif +#endif /* AMIGA */ return 1; } diff --git a/bsycleanup.c b/bsycleanup.c new file mode 100644 index 00000000..40a2e65a --- /dev/null +++ b/bsycleanup.c @@ -0,0 +1,122 @@ +/* + * bsycleanup.c -- Cleanup functions for .bsy/.csy/.try files + * + * bsycleanup.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include +#include +#include +#include +#include + +#include "sys.h" +#include "readcfg.h" +#include "ftnq.h" +#include "ftnnode.h" +#include "tools.h" +#include "readdir.h" + +/* + * Clean up old .bsy and .csy files at startup + * Scans all domain outbounds + */ + +/* Helper: scan a single directory for .bsy/.csy files and delete them */ +static void scan_and_delete_bsy_in_dir(const char *dir, BINKD_CONFIG *config) +{ + DIR *dp; + struct dirent *de; + char buf[MAXPATHLEN + 1]; + + if ((dp = opendir(dir)) == 0) + { + return; + } + + while ((de = readdir(dp)) != 0) + { + char *s = de->d_name; + int len = strlen(s); + + if (len > 4 && (!STRICMP(s + len - 4, ".bsy") || !STRICMP(s + len - 4, ".csy") || !STRICMP(s + len - 4, ".try"))) + { + strnzcpy(buf, dir, sizeof(buf)); + strnzcat(buf, PATH_SEPARATOR, sizeof(buf)); + strnzcat(buf, s, sizeof(buf)); + + Log(2, "deleting %s", buf); + + delete(buf); + } + } + + closedir(dp); +} + +void cleanup_old_bsy(BINKD_CONFIG *config) +{ + DIR *dp; + char outb_path[MAXPATHLEN + 1], base_path[MAXPATHLEN + 1]; + struct dirent *de; + FTN_DOMAIN *curr_domain; + int len; + + Log(2, "cleaning up .bsy/.csy/.try files at startup"); + + /* Scan all domain outbounds */ + for (curr_domain = config->pDomains.first; curr_domain; curr_domain = curr_domain->next) + { + if (curr_domain->alias4 != 0) + continue; + + /* Build base path: path + separator */ + strnzcpy(base_path, curr_domain->path, sizeof(base_path)); +#ifndef AMIGA + if (base_path[strlen(base_path) - 1] == ':') + strcat(base_path, PATH_SEPARATOR); +#endif + +#ifdef AMIGADOS_4D_OUTBOUND + if (config->aso) + { + /* ASO mode: direct outbound path */ + strnzcpy(outb_path, base_path, sizeof(outb_path)); + strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); + strnzcat(outb_path, curr_domain->dir, sizeof(outb_path)); + Log(7, "cleanup_old_bsy (ASO): scanning domain '%s', path '%s'", curr_domain->name, outb_path); + scan_and_delete_bsy_in_dir(outb_path, config); + } + else +#endif + { + /* BSO mode: scan for outbound.xxx directories */ + Log(7, "cleanup_old_bsy (BSO): scanning domain '%s', base '%s'", curr_domain->name, base_path); + + if ((dp = opendir(base_path)) == 0) + continue; + + len = strlen(curr_domain->dir); + + while ((de = readdir(dp)) != 0) + { + /* Match outbound or outbound.xxx */ + if (!STRNICMP(de->d_name, curr_domain->dir, len) && (de->d_name[len] == 0 || (de->d_name[len] == '.' && isxdigit(de->d_name[len + 1])))) + { + strnzcpy(outb_path, base_path, sizeof(outb_path)); + strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); + strnzcat(outb_path, de->d_name, sizeof(outb_path)); + + Log(7, "cleanup_old_bsy (BSO): scanning outbound dir '%s'", outb_path); + + scan_and_delete_bsy_in_dir(outb_path, config); + } + } + + closedir(dp); + } + } +} diff --git a/bsycleanup.h b/bsycleanup.h new file mode 100644 index 00000000..75514875 --- /dev/null +++ b/bsycleanup.h @@ -0,0 +1,19 @@ +/* + * bsycleanup.h -- Cleanup functions for .bsy/.csy/.try files + * + * bsycleanup.h is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#ifndef _BSYCLEANUP_H +#define _BSYCLEANUP_H + +#include "readcfg.h" + + +/* cleanup_old_bsy -- Clean up old .bsy/.csy/.try files at startup */ +void cleanup_old_bsy(BINKD_CONFIG *config); + +#endif /* _BSYCLEANUP_H */ diff --git a/btypes.h b/btypes.h old mode 100644 new mode 100755 index c645809c..a583599c --- a/btypes.h +++ b/btypes.h @@ -73,6 +73,7 @@ struct _FTN_NODE int HC_flag; int restrictIP; int NP_flag; /* no proxy */ + int NC_flag; /* no compression */ time_t hold_until; int busy; /* 0=free, 'c'=.csy, other=.bsy */ diff --git a/changes.diff b/changes.diff new file mode 100644 index 00000000..d6f6b4b6 --- /dev/null +++ b/changes.diff @@ -0,0 +1,28013 @@ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/bsdsock.c binkd/amiga/bsdsock.c +--- binkd_pgul/amiga/bsdsock.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/bsdsock.c 2026-04-26 11:01:10.438650436 +0100 +@@ -0,0 +1,79 @@ ++/* ++ * bsdsock.c -- bsdsocket.library lifecycle for AmigaOS 3 ++ * ++ * bsdsock.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Linker-compatibility global. Never used at runtime */ ++struct Library *SocketBase = NULL; ++ ++/* Suppress conflicting C prototypes from clib/bsdsocket_protos.h */ ++#ifndef CLIB_BSDSOCKET_PROTOS_H ++#define CLIB_BSDSOCKET_PROTOS_H ++#endif ++ ++#include ++#include ++ ++extern void Log(int lev, const char *s, ...); ++ ++/* _amiga_get_socket_base -- returns bsdsocket.library handle for calling task */ ++struct Library *_amiga_get_socket_base(void) ++{ ++ return (struct Library *)FindTask(NULL)->tc_UserData; ++} ++ ++int amiga_sock_init(void) ++{ ++ struct Task *me = FindTask(NULL); ++ struct Library *base; ++ ++ if (me->tc_UserData) ++ return 0; /* already open for this task */ ++ ++ base = OpenLibrary("bsdsocket.library", 0UL); ++ ++ if (!base) ++ { ++ fprintf(stderr, "amiga_sock_init: cannot open bsdsocket.library\n"); ++ return -1; ++ } ++ ++ /* Store in tc_UserData and global SocketBase */ ++ me->tc_UserData = (APTR)base; ++ SocketBase = base; ++ ++ /* Link the per-task errno to the TCP stack. */ ++ SetErrnoPtr(&errno, (LONG)sizeof(errno)); ++ ++ return 0; ++} ++ ++void amiga_sock_cleanup(void) ++{ ++ struct Task *me = FindTask(NULL); ++ struct Library *base = (struct Library *)me->tc_UserData; ++ ++ if (base) ++ { ++ me->tc_UserData = NULL; ++ SocketBase = NULL; ++ CloseLibrary(base); ++ } ++} ++ ++int amiga_child_sock_init(void) ++{ ++ /* Child inherits tc_UserData = NULL, opens new handle */ ++ return amiga_sock_init(); ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/bsdsock.h binkd/amiga/bsdsock.h +--- binkd_pgul/amiga/bsdsock.h 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/bsdsock.h 2026-04-26 10:49:27.706200054 +0100 +@@ -0,0 +1,155 @@ ++/* ++ * bsdsock.h -- bsdsocket.library init and POSIX compat shims for AmigaOS 3 ++ * ++ * bsdsock.h is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifndef _AMIGA_BSDSOCK_H ++#define _AMIGA_BSDSOCK_H ++ ++#ifdef AMIGA ++ ++#include ++#include ++#include ++#include ++ ++/* Suppress conflicting C prototypes from roadshow */ ++#ifndef CLIB_BSDSOCKET_PROTOS_H ++#define CLIB_BSDSOCKET_PROTOS_H ++#endif ++ ++/* Undefine MCLBYTES/MCLSHIFT before roadshow headers */ ++#ifdef MCLBYTES ++#undef MCLBYTES ++#endif ++#ifdef MCLSHIFT ++#undef MCLSHIFT ++#endif ++ ++/* Roadshow SDK network headers */ ++#include ++#include ++#include "compat_netinet_in.h" ++#include ++#include ++#include /* inline/bsdsocket.h, no clib protos */ ++ ++/* Undefine conflicting unistd.h macros */ ++#ifdef gethostid ++#undef gethostid ++#endif ++#ifdef getdtablesize ++#undef getdtablesize ++#endif ++#ifdef gethostname ++#undef gethostname ++#endif ++ ++/* Per-task SocketBase override */ ++struct Library *_amiga_get_socket_base(void); ++ ++#ifdef SocketBase ++#undef SocketBase ++#endif ++#define SocketBase _amiga_get_socket_base() ++ ++/* Roadshow socket-specific errno values */ ++#include ++ ++#define BSDSOCK_HAS_TIMEVAL 1 ++ ++/* Socket-specific errno values from roadshow sys/errno.h */ ++#ifndef ENOTSOCK ++#define ENOTSOCK 38 /* Socket operation on non-socket */ ++#endif ++#ifndef EOPNOTSUPP ++#define EOPNOTSUPP 45 /* Operation not supported on socket */ ++#endif ++#ifndef ECONNREFUSED ++#define ECONNREFUSED 61 /* Connection refused */ ++#endif ++#ifndef ETIMEDOUT ++#define ETIMEDOUT 60 /* Connection timed out */ ++#endif ++#ifndef ECONNRESET ++#define ECONNRESET 54 /* Connection reset by peer */ ++#endif ++#ifndef EHOSTUNREACH ++#define EHOSTUNREACH 65 /* No route to host */ ++#endif ++ ++#include ++ ++/* sockaddr_storage fallback for roadshow */ ++#ifndef HAVE_SOCKADDR_STORAGE ++#ifndef sockaddr_storage ++struct sockaddr_storage ++{ ++ unsigned short ss_family; ++ char __ss_pad[22]; /* enough for IPv4 */ ++}; ++#endif ++#define HAVE_SOCKADDR_STORAGE 1 ++#endif ++ ++/* Library base functions */ ++int amiga_sock_init(void); ++void amiga_sock_cleanup(void); ++int amiga_child_sock_init(void); ++ ++/* getpid() is defined in sys.h for AMIGA */ ++/* amiga_sleep and sleep are defined in sys.h for AMIGA */ ++ ++/* select() -> WaitSelect() wrapper with Ctrl+C handling */ ++#ifndef AMIGA_SELECT_DEFINED ++#define AMIGA_SELECT_DEFINED ++ ++/* Forward-declare binkd_exit */ ++extern int binkd_exit; ++ ++/* Forward-declare Log to avoid implicit declaration warning */ ++extern void Log(int lev, char *s, ...); ++ ++static int amiga_select_wrap(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) ++{ ++ ULONG sigmask = SIGBREAKF_CTRL_C; ++ int rc = WaitSelect(nfds, readfds, writefds, exceptfds, timeout, &sigmask); ++ ++ /* Ctrl+C should break blocked select() loops immediately. */ ++ if ((sigmask & SIGBREAKF_CTRL_C) != 0) ++ { ++ Log(1, "Ctrl+C detected in WaitSelect, setting binkd_exit=1"); ++ binkd_exit = 1; ++ errno = EINTR; ++ return -1; ++ } ++ ++ return rc; ++} ++ ++#define select(n, r, w, e, t) amiga_select_wrap((n), (r), (w), (e), (t)) ++ ++#endif /* AMIGA_SELECT_DEFINED */ ++ ++/* FIONBIO via IoctlSocket */ ++#ifndef FIONBIO ++#define FIONBIO 0x8004667E ++#endif ++#ifndef ioctl ++#define ioctl(s, req, arg) IoctlSocket((s), (req), (char *)(arg)) ++#endif ++ ++/* inet_ntoa -> Inet_NtoA (Amiga only) */ ++#ifdef AMIGA ++#ifdef inet_ntoa ++#undef inet_ntoa ++#endif ++#define inet_ntoa(a) Inet_NtoA(a) ++#endif ++ ++#endif /* AMIGA */ ++#endif /* _AMIGA_BSDSOCK_H */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/compat_netinet_in.h binkd/amiga/compat_netinet_in.h +--- binkd_pgul/amiga/compat_netinet_in.h 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/compat_netinet_in.h 2026-04-26 09:53:12.337417283 +0100 +@@ -0,0 +1,19 @@ ++/* ++ * compat_netinet_in.h -- Wrapper for netinet/in.h typedef clash ++ * ++ * compat_netinet_in.h is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifndef _AMIGA_COMPAT_NETINET_IN_H ++#define _AMIGA_COMPAT_NETINET_IN_H ++ ++#ifdef __GNUC__ ++#include_next ++#else ++#include ++#endif ++ ++#endif /* _AMIGA_COMPAT_NETINET_IN_H */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/dirent.c binkd/amiga/dirent.c +--- binkd_pgul/amiga/dirent.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/dirent.c 2026-04-26 11:40:07.539429919 +0100 +@@ -0,0 +1,137 @@ ++/* ++ * dirent.c -- POSIX directory scanning for AmigaOS 3 ++ * ++ * dirent.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifdef AMIGA ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "amiga/dirent.h" ++ ++/* opendir -- locks directory and allocates state for readdir() */ ++DIR *opendir(const char *path) ++{ ++ DIR *dir; ++ ++ if (!path) ++ { ++ errno = EINVAL; ++ return NULL; ++ } ++ ++ dir = (DIR *)AllocMem((LONG)sizeof(DIR), MEMF_CLEAR); ++ ++ if (!dir) ++ { ++ errno = ENOMEM; ++ return NULL; ++ } ++ ++ dir->fib = (struct FileInfoBlock *)AllocMem((LONG)sizeof(struct FileInfoBlock), MEMF_CLEAR); ++ ++ if (!dir->fib) ++ { ++ FreeMem(dir, (LONG)sizeof(DIR)); ++ errno = ENOMEM; ++ return NULL; ++ } ++ ++ dir->lock = Lock((STRPTR)path, ACCESS_READ); ++ ++ if (!dir->lock) ++ { ++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); ++ FreeMem(dir, (LONG)sizeof(DIR)); ++ errno = ENOENT; ++ return NULL; ++ } ++ ++ /* Examine the directory itself; this positions FIB for ExNext() */ ++ if (!Examine(dir->lock, dir->fib)) ++ { ++ UnLock(dir->lock); ++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); ++ FreeMem(dir, (LONG)sizeof(DIR)); ++ errno = EACCES; ++ return NULL; ++ } ++ ++ /* fib_DirEntryType > 0 means this IS a directory */ ++ if (dir->fib->fib_DirEntryType <= 0) ++ { ++ UnLock(dir->lock); ++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); ++ FreeMem(dir, (LONG)sizeof(DIR)); ++ errno = ENOTDIR; ++ return NULL; ++ } ++ ++ dir->first = 1; /* ExNext() has not been called yet */ ++ ++ return dir; ++} ++ ++/* readdir -- advances to next directory entry */ ++struct dirent *readdir(DIR *dir) ++{ ++ LONG dos_rc; ++ LONG dos_err; ++ ++ if (!dir) ++ { ++ errno = EINVAL; ++ return NULL; ++ } ++ ++ /* ExNext() advances past the last entry returned by Examine/ExNext */ ++ dos_rc = ExNext(dir->lock, dir->fib); ++ ++ if (!dos_rc) ++ { ++ dos_err = IoErr(); ++ ++ if (dos_err == ERROR_NO_MORE_ENTRIES) ++ return NULL; ++ ++ errno = EIO; ++ return NULL; ++ } ++ ++ /* Copy name into the entry buffer */ ++ strncpy(dir->entry.d_name, dir->fib->fib_FileName, AMIGA_NAME_MAX - 1); ++ dir->entry.d_name[AMIGA_NAME_MAX - 1] = '\0'; ++ dir->entry.d_ino = 0; ++ ++ return &dir->entry; ++} ++ ++/* closedir -- releases all resources associated with dir */ ++int closedir(DIR *dir) ++{ ++ if (!dir) ++ { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ if (dir->lock) ++ UnLock(dir->lock); ++ ++ if (dir->fib) ++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); ++ ++ FreeMem(dir, (LONG)sizeof(DIR)); ++ return 0; ++} ++ ++#endif /* AMIGA */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/dirent.h binkd/amiga/dirent.h +--- binkd_pgul/amiga/dirent.h 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/dirent.h 2026-04-26 09:53:13.628932082 +0100 +@@ -0,0 +1,53 @@ ++/* ++ * dirent.h -- POSIX directory scanning for AmigaOS 3 ++ * ++ * dirent.h is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifndef _AMIGA_DIRENT_H ++#define _AMIGA_DIRENT_H ++ ++#ifdef AMIGA ++ ++#include ++#include ++ ++/* Maximum name length: AmigaDOS allows 107 characters */ ++#define AMIGA_NAME_MAX 108 ++ ++struct dirent ++{ ++ unsigned long d_ino; /* inode -- always 0 on AmigaDOS */ ++ char d_name[AMIGA_NAME_MAX]; /* null-terminated file name */ ++}; ++ ++/* struct utimbuf for AmigaOS 3 without ixemul */ ++#ifndef _AMIGA_UTIMBUF_DEFINED ++#define _AMIGA_UTIMBUF_DEFINED ++ ++struct utimbuf ++{ ++ long actime; /* access time (unused by SetFileDate) */ ++ long modtime; /* modification time (POSIX time_t) */ ++}; ++ ++int utime(const char *path, const struct utimbuf *times); ++#endif ++ ++typedef struct _amiga_dir ++{ ++ BPTR lock; /* directory lock */ ++ struct FileInfoBlock *fib; /* reusable FileInfoBlock */ ++ int first; /* flag: first call not yet */ ++ struct dirent entry; /* storage returned to caller */ ++} DIR; ++ ++DIR *opendir(const char *path); ++struct dirent *readdir(DIR *dir); ++int closedir(DIR *dir); ++ ++#endif /* AMIGA */ ++#endif /* _AMIGA_DIRENT_H */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop.c binkd/amiga/evloop.c +--- binkd_pgul/amiga/evloop.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/evloop.c 2026-04-26 11:46:47.344533199 +0100 +@@ -0,0 +1,509 @@ ++/* ++ * evloop.c -- non-blocking event loop for AmigaOS 3 ++ * ++ * evloop.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++/* Suppress clib bsdsocket prototypes before any socket header */ ++#ifndef CLIB_BSDSOCKET_PROTOS_H ++#define CLIB_BSDSOCKET_PROTOS_H ++#endif ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "sys.h" ++#include "readcfg.h" ++#include "common.h" ++#include "tools.h" ++#include "protocol.h" ++#include "sem.h" ++#include "server.h" ++#include "amiga/bsdsock.h" ++#include "amiga/evloop.h" ++#include "amiga/evloop_int.h" ++#include "amiga/proto_amiga.h" ++ ++/* Externals */ ++extern SOCKET sockfd[MAX_LISTENSOCK]; ++extern int sockfd_used; ++extern int binkd_exit; ++extern int server_flag, client_flag; ++ ++/* Session table (shared with sock.c and session.c) */ ++sess_t *sessions = NULL; ++int max_sessions = 0; ++ ++/* ++ * calc_max_sessions -- Compute session slot count from config + flags ++ * Shared by init and config-reload paths ++ */ ++static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) ++{ ++ int servers = config->max_servers; ++ int clients = config->max_clients; ++ int total; ++ ++ if (servers == 0 && clients == 0) ++ { ++ Log(5, "DEBUG: Using default 2 slots (no config found)"); ++ return 2; ++ } ++ ++ Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); ++ ++ if (srv_flag && servers < 1) ++ servers = 1; ++ ++ if (cli_flag && clients < 1) ++ clients = 1; ++ ++ total = servers + clients; ++ ++ if (total < 2) ++ total = 2; ++ ++ Log(5, "DEBUG: Calculated max_sessions=%d", total); ++ ++ return total; ++} ++ ++/* init_session_table -- Allocate and zero-initialise the session array */ ++static int init_session_table(int slots) ++{ ++ int i; ++ ++ sessions = calloc(slots, sizeof(sess_t)); ++ ++ if (!sessions) ++ { ++ Log(1, "Failed to allocate session table"); ++ return 0; ++ } ++ ++ for (i = 0; i < slots; i++) ++ { ++ memset(&sessions[i], 0, sizeof(sess_t)); ++ memset(&sessions[i].state, 0, sizeof(STATE)); ++ sessions[i].fd = INVALID_SOCKET; ++ sessions[i].phase = SESS_FREE; ++ } ++ ++ return 1; ++} ++ ++/* ++ * build_fdsets -- Populate r/w fd_sets from listen sockets and sessions ++ * Returns the highest fd seen (maxfd) ++ */ ++static int build_fdsets(fd_set *r, fd_set *w) ++{ ++ int i, maxfd = 0; ++ ++ FD_ZERO(r); ++ FD_ZERO(w); ++ ++ /* server side: listen sockets */ ++ for (i = 0; i < sockfd_used; i++) ++ { ++ if (sockfd[i] != INVALID_SOCKET) ++ { ++ FD_SET(sockfd[i], r); ++ ++ if ((int)sockfd[i] > maxfd) ++ maxfd = (int)sockfd[i]; ++ } ++ } ++ ++ /* client + server sessions */ ++ for (i = 0; i < max_sessions; i++) ++ { ++ sess_t *s = &sessions[i]; ++ ++ if (s->phase == SESS_FREE || s->fd == INVALID_SOCKET) ++ continue; ++ ++ if ((int)s->fd > maxfd) ++ maxfd = (int)s->fd; ++ ++ if (s->phase == SESS_CONNECTING) ++ { ++ /* client: waiting for non-blocking connect() */ ++ FD_SET(s->fd, w); ++ } ++ else ++ { ++ /* Server or established client session */ ++ FD_SET(s->fd, r); ++ ++ if (s->state.msgs || s->state.oleft || s->state.send_eof || (s->state.out.f && !s->state.off_req_sent && !s->state.waiting_for_GOT)) ++ FD_SET(s->fd, w); ++ } ++ } ++ ++ Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); ++ return maxfd; ++} ++ ++/* ++ * handle_server_accept -- Accept new inbound connections on all listen fds ++ * Returns 0 normally, -1 if binkd_exit was set during accept ++ */ ++static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) ++{ ++ int i; ++ ++ Log(5, "DEBUG: Before accept loop"); ++ ++ for (i = 0; i < sockfd_used; i++) ++ { ++ if (FD_ISSET(sockfd[i], r)) ++ do_accept(sockfd[i], config); ++ ++ if (binkd_exit) ++ { ++ Log(5, "DEBUG: binkd_exit during accept loop"); ++ return -1; ++ } ++ } ++ ++ Log(5, "DEBUG: After accept loop"); ++ ++ return 0; ++} ++ ++/* ++ * advance_sessions -- Step every active session (server + client) ++ * Returns the number of non-free sessions processed ++ */ ++static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) ++{ ++ int i, active = 0; ++ ++ Log(5, "DEBUG: Before advance sessions"); ++ ++ for (i = 0; i < max_sessions; i++) ++ { ++ sess_t *s = &sessions[i]; ++ ++ Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); ++ ++ if (s->phase == SESS_FREE) ++ continue; ++ ++ active++; ++ ++ if (s->phase == SESS_CONNECTING) ++ { ++ /* client: Complete the non-blocking connect */ ++ if (FD_ISSET(s->fd, w)) ++ check_connect(i, config); ++ } ++ else ++ { ++ int rd = FD_ISSET(s->fd, r); ++ int wr = FD_ISSET(s->fd, w); ++ ++ /* Always step: protocol must advance internal state even ++ * when WaitSelect reports no activity (e.g. second batch ++ * EOB send, TCP FIN detection after remote closes) */ ++ do_session_step(i, rd, wr, config); ++ } ++ ++ if (binkd_exit) ++ break; ++ } ++ ++ return active; ++} ++ ++/* ++ * handle_config_reload -- Resize session table and reopen listen sockets ++ * Returns 1 if the caller should break out of the main loop, 0 otherwise ++ */ ++static int handle_config_reload(BINKD_CONFIG **config, int srv_flag, int cli_flag) ++{ ++ int i, new_max; ++ BINKD_CONFIG *nc = lock_current_config(); ++ ++ if (nc) ++ { ++ new_max = calc_max_sessions(nc, srv_flag, cli_flag); ++ ++ if (new_max != max_sessions) ++ { ++ sess_t *ns = realloc(sessions, new_max * sizeof(sess_t)); ++ ++ if (ns) ++ { ++ for (i = max_sessions; i < new_max; i++) ++ { ++ memset(&ns[i], 0, sizeof(sess_t)); ++ memset(&ns[i].state, 0, sizeof(STATE)); ++ ns[i].fd = INVALID_SOCKET; ++ ns[i].phase = SESS_FREE; ++ } ++ ++ sessions = ns; ++ max_sessions = new_max; ++ ++ Log(4, "Session table resized to %d slots", max_sessions); ++ } ++ else ++ { ++ Log(1, "Failed to resize session table, keeping current size"); ++ } ++ } ++ ++ unlock_config_structure(nc, 0); ++ } ++ ++ close_listen_sockets(); ++ *config = lock_current_config(); ++ ++ if (srv_flag && open_listen_sockets(*config) < 0) ++ { ++ unlock_config_structure(*config, 0); ++ return 1; /* fatal — break main loop */ ++ } ++ ++ unlock_config_structure(*config, 0); ++ *config = lock_current_config(); ++ return 0; ++} ++ ++/* evloop_cleanup -- Close sessions and free resources on exit */ ++static void evloop_cleanup(BINKD_CONFIG *config, int config_locked) ++{ ++ int i; ++ ++ if (config_locked) ++ unlock_config_structure(config, 0); ++ ++ if (sessions) ++ { ++ for (i = 0; i < max_sessions; i++) ++ { ++ if (sessions[i].phase == SESS_RUNNING) ++ { ++ amiga_proto_close(&sessions[i].state, config, 0); ++ ++ if (sessions[i].inbound) ++ n_servers--; ++ else ++ n_clients--; ++ } ++ else if (sessions[i].phase == SESS_CONNECTING) ++ { ++ n_clients--; ++ } ++ sess_free(i); ++ } ++ ++ free(sessions); ++ sessions = NULL; ++ } ++ ++ close_listen_sockets(); ++ amiga_sock_cleanup(); ++ Log(4, "evloop done"); ++} ++ ++/* amiga_evloop_run -- Entry point: init, then main WaitSelect() loop */ ++void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) ++{ ++ int config_locked = 0; ++ time_t last_rescan = 0; ++ time_t now; ++ fd_set r, w; ++ struct timeval tv; ++ int n, maxfd; ++ int active_sessions = 0; ++ static int idle_loops = 0; ++ ++ /* Sync globals so try_outbound() and friends see the correct flags */ ++ server_flag = srv_flag; ++ client_flag = cli_flag; ++ ++ sockfd_used = 0; ++ srand((unsigned int)time(NULL)); ++ ++ Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); ++ Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); ++ ++ /* Initialise session table */ ++ max_sessions = calc_max_sessions(config, server_flag, client_flag); ++ ++ if (max_sessions < 2) ++ { ++ Log(2, "WARNING: max_sessions=%d is too low, forcing to 2", max_sessions); ++ max_sessions = 2; ++ } ++ ++ Log(4, "evloop start (AmigaOS 3, WaitSelect, %d slots)", max_sessions); ++ ++ if (!init_session_table(max_sessions)) ++ return; ++ ++ /* server: Open listen sockets */ ++ if (server_flag && open_listen_sockets(config) < 0) ++ { ++ Log(0, "evloop: cannot open listen sockets"); ++ free(sessions); ++ sessions = NULL; ++ return; ++ } ++ ++ Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); ++ ++ /* Initial outbound attempt before waiting (important for poll -p mode) */ ++ Log(5, "DEBUG: Initial try_outbound before main loop"); ++ try_outbound(config); ++ last_rescan = time(NULL); /* Reset timer since we just did an attempt */ ++ ++ /* ===== Main loop ===== */ ++ for (;;) ++ { ++ if (binkd_exit) ++ { ++ Log(1, "binkd_exit detected at loop start, exiting"); ++ break; ++ } ++ ++ /* Build fd_sets */ ++ Log(5, "DEBUG: Building fd_sets"); ++ maxfd = build_fdsets(&r, &w); ++ ++ tv.tv_sec = 1; ++ tv.tv_usec = 0L; ++ ++ /* WaitSelect() with nfds>0 but empty fd_sets causes guru #80000006 :/ ++ * Use select(0,...) as a pure sleep when no sockets are active */ ++ if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) ++ maxfd = 1; ++ ++ Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); ++ ++ if (maxfd == 0) ++ { ++ /* No sockets yet -- use Delay() instead of select(0,...) ++ * as WaitSelect with nfds=0 can block indefinitely on AmigaOS */ ++ Delay(50); /* 1 second = 50 ticks at 50Hz PAL */ ++ n = 0; /* simulate timeout */ ++ } ++ else ++ n = select(maxfd + 1, &r, &w, NULL, &tv); ++ ++ Log(5, "DEBUG: select() returned n=%d", n); ++ ++ if (binkd_exit) ++ { ++ Log(1, "binkd_exit detected after select(), exiting"); ++ break; ++ } ++ ++ Delay(1UL); /* 1 tick = 20ms @ 50Hz, prevents CPU hogging */ ++ ++ /* Handle select errors */ ++ if (n < 0) ++ { ++ if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) ++ { ++ Log(5, "DEBUG: select interrupted, continuing"); ++ continue; ++ } ++ ++ if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) ++ { ++ Log(2, "select: %s, reopening", TCPERR()); ++ ++ close_listen_sockets(); ++ ++ if (server_flag && open_listen_sockets(config) < 0) ++ break; ++ ++ continue; ++ } ++ ++ Log(1, "select: %s", TCPERR()); ++ break; ++ } ++ else if (n == 0) ++ { ++ Log(5, "DEBUG: select timeout, continuing"); ++ } ++ ++ /* server: Accept new inbound connections */ ++ if (server_flag) ++ { ++ if (handle_server_accept(&r, config) < 0) ++ break; ++ } ++ ++ if (binkd_exit) ++ break; ++ ++ /* server + client: Advance all active sessions */ ++ active_sessions = advance_sessions(&r, &w, config); ++ ++ if (binkd_exit) ++ break; ++ ++ /* client: Time-based outbound scan + config reload */ ++ now = time(NULL); ++ ++ if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) ++ { ++ Log(5, "DEBUG: Before try_outbound"); ++ ++ try_outbound(config); ++ ++ Log(5, "DEBUG: After try_outbound"); ++ ++ if (checkcfg()) ++ { ++ if (handle_config_reload(&config, server_flag, client_flag)) ++ break; ++ ++ config_locked = 1; ++ } ++ ++ config->q_present = 0; ++ last_rescan = now; ++ } ++ ++ /* client: Poll-mode idle exit */ ++ /* Reset counter whenever there is something going on */ ++ if (n_clients > 0 || active_sessions > 0) ++ { ++ if (idle_loops > 0) ++ Log(2, "Activity detected, reset idle counter"); ++ ++ idle_loops = 0; ++ } ++ ++ if (!server_flag && active_sessions == 0 && n_clients == 0) ++ { ++ idle_loops++; ++ ++ Log(2, "Idle loop %d/2 (no server, no sessions, no clients)", idle_loops); ++ ++ if (idle_loops > 1) ++ { ++ Log(0, "the queue is empty, quitting..."); ++ break; ++ } ++ } ++ } ++ /* ===== End main loop ===== */ ++ ++ evloop_cleanup(config, config_locked); ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop.h binkd/amiga/evloop.h +--- binkd_pgul/amiga/evloop.h 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/evloop.h 2026-04-26 11:47:03.034239655 +0100 +@@ -0,0 +1,34 @@ ++/* ++ * evloop.h -- non-blocking event loop for AmigaOS 3 ++ * ++ * evloop.h is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifndef _AMIGA_EVLOOP_H ++#define _AMIGA_EVLOOP_H ++ ++#ifdef AMIGA ++ ++#include "readcfg.h" ++#include "protoco2.h" /* STATE */ ++ ++/* amiga_proto_step() return codes — also used by protocol.c */ ++#define APROTO_RUNNING 0 /* session alive, call again */ ++#define APROTO_DONE_OK 1 /* session finished, success */ ++#define APROTO_DONE_ERR 2 /* session failed */ ++ ++/* ++ * amiga_evloop_run -- entry point replacing servmgr() + clientmgr() ++ * ++ * Opens listen sockets (when server_flag), then runs a WaitSelect() ++ * loop that multiplexes sessions dynamically based on config->max_servers ++ * and config->max_clients. Minimum 2 sessions are always allocated ++ * Returns only when binkd_exit != 0 ++ */ ++void amiga_evloop_run(BINKD_CONFIG *config, int server_flag, int client_flag); ++ ++#endif /* AMIGA */ ++#endif /* _AMIGA_EVLOOP_H */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop_int.h binkd/amiga/evloop_int.h +--- binkd_pgul/amiga/evloop_int.h 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/evloop_int.h 2026-04-26 11:02:58.166969078 +0100 +@@ -0,0 +1,68 @@ ++/* ++ * evloop_int.h -- internal types shared by evloop.c, sock.c, session.c ++ * ++ * evloop_int.h is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifndef AMIGA_EVLOOP_INT_H ++#define AMIGA_EVLOOP_INT_H ++ ++#include "protoco2.h" ++#include "amiga/bsdsock.h" ++#include "ftnnode.h" ++#include "readcfg.h" ++ ++/* Session lifecycle */ ++typedef enum ++{ ++ SESS_FREE = 0, /* slot available */ ++ SESS_CONNECTING = 1, /* waiting for connect() */ ++ SESS_RUNNING = 2 /* BinkP session active */ ++} sess_phase_t; ++ ++/* Per-session state */ ++typedef struct ++{ ++ sess_phase_t phase; ++ SOCKET fd; ++ STATE state; ++ int inbound; /* 1=accepted, 0=outbound */ ++ ++ FTN_NODE *node; ++ struct addrinfo *ai_head; /* full getaddrinfo list */ ++ struct addrinfo *ai_cur; /* candidate being tried */ ++ time_t conn_start; ++ ++ char host[BINKD_FQDNLEN + 1]; ++ char port[MAXPORTSTRLEN + 1]; ++ char ip[BINKD_FQDNLEN + 1]; ++ ++ time_t last_io; ++} sess_t; ++ ++/* Globals defined in evloop.c */ ++extern sess_t *sessions; ++extern int max_sessions; ++ ++/* Defined in server.c and client.c respectively */ ++extern int n_servers; ++extern int n_clients; ++ ++/* sock.c */ ++void set_nonblock(SOCKET fd); ++int open_listen_sockets(BINKD_CONFIG *config); ++void close_listen_sockets(void); ++ ++/* session.c */ ++int sess_alloc(void); ++void sess_free(int idx); ++void do_accept(SOCKET lfd, BINKD_CONFIG *config); ++int start_connect(sess_t *s, BINKD_CONFIG *config); ++void check_connect(int idx, BINKD_CONFIG *config); ++int try_outbound(BINKD_CONFIG *config); ++void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config); ++ ++#endif /* AMIGA_EVLOOP_INT_H */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/proto_amiga.c binkd/amiga/proto_amiga.c +--- binkd_pgul/amiga/proto_amiga.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/proto_amiga.c 2026-04-26 11:52:04.887130443 +0100 +@@ -0,0 +1,269 @@ ++/* ++ * proto_amiga.c -- Amiga non-blocking BinkP protocol implementation ++ * ++ * proto_amiga.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "sys.h" ++#include "readcfg.h" ++#include "common.h" ++#include "protocol.h" ++#include "ftnaddr.h" ++#include "ftnnode.h" ++#include "ftnq.h" ++#include "tools.h" ++#include "bsy.h" ++#include "inbound.h" ++#include "protoco2.h" ++#include "prothlp.h" ++#include "binlog.h" ++#include "evloop.h" ++ ++/* External functions from protocol.c */ ++extern int init_protocol(STATE *state, SOCKET s_in, SOCKET s_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config); ++extern int banner(STATE *state, BINKD_CONFIG *config); ++extern int recv_block(STATE *state, BINKD_CONFIG *config); ++extern int send_block(STATE *state, BINKD_CONFIG *config); ++extern void bsy_touch(BINKD_CONFIG *config); ++extern FTNQ *process_rcvdlist(STATE *state, FTNQ *q, BINKD_CONFIG *config); ++extern int start_file_transfer(STATE *state, FTNQ *q, BINKD_CONFIG *config); ++extern void ND_set_status(const char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config); ++extern void deinit_protocol(STATE *state, BINKD_CONFIG *config, int status); ++extern void evt_set(EVTQ *eq); ++extern void msg_send2(STATE *state, t_msg m, char *s1, char *s2); ++ ++/* External functions from other modules */ ++extern void log_end_of_session(int err, STATE *state, BINKD_CONFIG *config); ++extern void inb_remove_partial(STATE *state, BINKD_CONFIG *config); ++extern void good_try(FTN_ADDR *fa, char *comment, BINKD_CONFIG *config); ++extern void bad_try(FTN_ADDR *fa, const char *error, const int where, BINKD_CONFIG *config); ++extern int create_poll(FTN_ADDR *fa, int flvr, BINKD_CONFIG *config); ++extern void hold_node(FTN_ADDR *fa, time_t hold_until, BINKD_CONFIG *config); ++extern int binkd_exit; ++ ++/* External variables */ ++extern int n_servers; ++ ++/* ++ * amiga_proto_open -- Initialise a session and send the BinkP banner ++ * ++ * fd : Connected socket (same fd for in and out) ++ * to : Outbound node, NULL for inbound ++ * fa : Local AKA to use, may be NULL ++ * host : Remote hostname or dotted-IP string (caller-owned, stable) ++ * port : Remote port string, may be NULL ++ * dst_ip : Numeric remote IP, may be NULL (falls back to host) ++ * config : Current config ++ * ++ * Returns 0 on success, -1 on error (caller must close fd) ++ */ ++int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config) ++{ ++ struct sockaddr_storage sa; ++ socklen_t salen = (socklen_t)sizeof(sa); ++ char ownhost[BINKD_FQDNLEN + 1]; ++ char ownserv[MAXSERVNAME + 1]; ++ int rc; ++ ++ if (!init_protocol(state, fd, fd, to, fa, config)) ++ return -1; ++ ++ /* Peer identity for logging and %ip config checks */ ++ state->ipaddr = dst_ip ? (char *)dst_ip : (char *)host; ++ state->peer_name = (host && *host) ? (char *)host : state->ipaddr; ++ ++ /* local endpoint: Not used further, skip to avoid dangling pointer */ ++ ++ Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); ++ ++ /* banner() sends M_NUL lines and ADR messages */ ++ if (!banner(state, config)) ++ return -1; ++ ++ /* refuse if server limit reached */ ++ if (!to && n_servers > config->max_servers) ++ { ++ Log(1, "too many servers"); ++ msg_send2(state, M_BSY, "Too many servers", 0); ++ ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/* ++ * amiga_proto_step -- Run one recv/send iteration of the BinkP loop ++ * ++ * readable : Non-zero if the socket has incoming data (from WaitSelect) ++ * writable : Non-zero if the socket can accept outgoing data ++ * ++ * Returns APROTO_RUNNING, APROTO_DONE_OK, or APROTO_DONE_ERR ++ * ++ * This is the loop body of protocol() with the select() call removed ++ * recv_block() and send_block() already handle EWOULDBLOCK gracefully, ++ * so calling this on a non-blocking socket is safe ++ */ ++int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config) ++{ ++ FTNQ *q; ++ int no; ++ ++ if (state->io_error) ++ return APROTO_DONE_ERR; ++ ++ /* Advance outgoing file queue if nothing is being sent */ ++ if (!state->local_EOB && state->q && !state->out.f && !state->waiting_for_GOT && !state->off_req_sent && state->state != P_NULL) ++ { ++ while (1) ++ { ++ q = 0; ++ if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) ++ { ++ if (start_file_transfer(state, q, config)) ++ break; ++ } ++ else ++ { ++ q_free(state->q, config); ++ state->q = 0; ++ break; ++ } ++ } ++ } ++ ++ /* Nothing left to send — issue EOB */ ++ if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) ++ { ++ if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) ++ { ++ state->local_EOB = 1; ++ msg_send2(state, M_EOB, 0, 0); ++ } ++ } ++ ++ /* Recv step: Only when socket is readable */ ++ if (readable) ++ { ++ if (!recv_block(state, config)) ++ return APROTO_DONE_ERR; ++ } ++ ++ /* ++ * send step: drive even when writable=0 if there is buffered data, ++ * pending messages, a file mid-transfer, or an EOF to flush. ++ */ ++ if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) ++ { ++ no = send_block(state, config); ++ ++ if (!no && no != 2) ++ return APROTO_DONE_ERR; ++ } ++ ++ bsy_touch(config); ++ ++ /* Batch/Session-end detection — Mirrors the break logic in protocol() */ ++ if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) ++ { ++ if (state->rcvdlist) ++ { ++ state->q = process_rcvdlist(state, state->q, config); ++ ++ q_to_killlist(&state->killlist, &state->n_killlist, state->q); ++ free_rcvdlist(&state->rcvdlist, &state->n_rcvdlist); ++ } ++ ++ Log(6, "batch: %i msgs", state->msgs_in_batch); ++ ++ if (state->msgs_in_batch <= 2 || (state->major * 100 + state->minor <= 100)) ++ { ++ /* Session done */ ++ ND_set_status("", &state->ND_addr, state, config); ++ state->ND_addr.z = -1; ++ ++ return APROTO_DONE_OK; ++ } ++ ++ /* Start next batch */ ++ state->msgs_in_batch = 0; ++ state->remote_EOB = 0; ++ state->local_EOB = 0; ++ ++ if (OK_SEND_FILES(state, config)) ++ { ++ state->q = q_scan_boxes(state->q, state->fa, state->nfa, state->to ? 1 : 0, config); ++ state->q = q_sort(state->q, state->fa, state->nfa, config); ++ } ++ } ++ ++ return APROTO_RUNNING; ++} ++ ++/* ++ * amiga_proto_close -- Flush remaining I/O and release STATE resources ++ * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR ++ */ ++void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) ++{ ++ int no; ++ char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; ++ int status = ok ? 0 : 1; ++ ++ /* Drain inbound queue */ ++ if (!state->io_error) ++ { ++ while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) ++ Log(9, "purged %d bytes", no); ++ } ++ ++ /* Flush pending outbound messages */ ++ while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) ++ ; ++ ++ if (ok) ++ { ++ log_end_of_session(0, state, config); ++ process_killlist(state->killlist, state->n_killlist, 's'); ++ inb_remove_partial(state, config); ++ ++ if (state->to) ++ good_try(&state->to->fa, "CONNECT/BND", config); ++ } ++ else ++ { ++ log_end_of_session(1, state, config); ++ process_killlist(state->killlist, state->n_killlist, 0); ++ ++ if (!binkd_exit && state->to) ++ bad_try(&state->to->fa, "Bad session", BAD_IO, config); ++ ++ /* Restore poll flavour if files were left mid-transfer */ ++ if (state->to && tolower(state->maxflvr) != 'h') ++ { ++ Log(4, "restoring poll with '%c' flavour", state->maxflvr); ++ ++ create_poll(&state->to->fa, state->maxflvr, config); ++ } ++ } ++ ++ if (state->to && state->r_skipped_flag && config->hold_skipped > 0) ++ { ++ Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); ++ ++ hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); ++ } ++ ++ deinit_protocol(state, config, status); ++ evt_set(state->evt_queue); ++ state->evt_queue = NULL; ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/proto_amiga.h binkd/amiga/proto_amiga.h +--- binkd_pgul/amiga/proto_amiga.h 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/proto_amiga.h 2026-04-26 11:53:22.716799421 +0100 +@@ -0,0 +1,30 @@ ++/* ++ * proto_amiga.h -- Amiga non-blocking BinkP protocol implementation ++ * ++ * proto_amiga.h is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifndef _PROTO_AMIGA_H ++#define _PROTO_AMIGA_H ++ ++#include "protoco2.h" ++#include "readcfg.h" ++ ++/* amiga_proto_step() return codes */ ++#define APROTO_RUNNING 0 ++#define APROTO_DONE_OK 1 ++#define APROTO_DONE_ERR 2 ++ ++/* amiga_proto_open -- Initialise a session and send the BinkP banner */ ++int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config); ++ ++/* amiga_proto_step-- run one recv / send iteration of the BinkP loop */ ++int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config); ++ ++/* amiga_proto_close -- flush remaining I/O and release STATE resources */ ++void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok); ++ ++#endif /* _PROTO_AMIGA_H */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/rename.c binkd/amiga/rename.c +--- binkd_pgul/amiga/rename.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/amiga/rename.c 2026-04-25 19:33:26.785601976 +0100 +@@ -1,10 +1,137 @@ + #include + #include ++#include ++ ++#include ++#include /* atoi */ ++#include /* isdigit */ ++ ++#define PATHBUF 512 + + int o_rename(char *from, char *to) + { +- if (Rename((STRPTR)from, (STRPTR)to)) /* cross-volume move won't work */ ++ struct FileInfoBlock *fib; ++ char dir[PATHBUF]; ++ char base[PATHBUF]; ++ char newname[PATHBUF]; ++ char *slash; ++ ULONG max = 0; ++ BPTR dirlock; ++ char *d = NULL; ++ const char *src = NULL; ++ ULONG n = 0; ++ ++ /* Try direct rename first */ ++ if (Rename((STRPTR)from, (STRPTR)to)) ++ return 0; ++ ++ /* Split path */ ++ slash = strrchr(to, '/'); ++ ++ if (!slash) ++ slash = strrchr(to, ':'); ++ ++ if (slash) ++ { ++ ULONG len = slash - to; ++ ++ if (len >= PATHBUF) ++ len = PATHBUF - 1; ++ ++ strncpy(dir, to, len); ++ dir[len] = '\0'; ++ ++ strncpy(base, slash + 1, PATHBUF - 1); ++ base[PATHBUF - 1] = '\0'; ++ } ++ else ++ { ++ strcpy(dir, "."); ++ strncpy(base, to, PATHBUF - 1); ++ base[PATHBUF - 1] = '\0'; ++ } ++ ++ /* Lock directory */ ++ dirlock = Lock((STRPTR)dir, ACCESS_READ); ++ ++ if (!dirlock) ++ { ++ errno = ENOENT; ++ return -1; ++ } ++ ++ fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); ++ ++ if (!fib) ++ { ++ UnLock(dirlock); ++ errno = ENOMEM; ++ return -1; ++ } ++ ++ /* Scan directory safely under lock */ ++ if (Examine(dirlock, fib)) ++ { ++ while (ExNext(dirlock, fib)) ++ { ++ if (strncmp(fib->fib_FileName, base, strlen(base)) == 0) ++ { ++ const char *p = NULL; ++ ++ p = fib->fib_FileName + strlen(base); ++ ++ if (*p != '.') ++ { ++ continue; ++ } ++ ++ /* .001 style */ ++ if (isdigit((UBYTE)p[1]) && isdigit((UBYTE)p[2]) && isdigit((UBYTE)p[3])) ++ { ++ n = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); ++ if (n > max) ++ max = n; ++ } ++ ++ /* FIDO volume style (.mo0 .th1 etc) */ ++ if (isdigit((UBYTE)p[1]) && !isdigit((UBYTE)p[2])) ++ { ++ n = p[1] - '0'; ++ if (n > max) ++ max = n; ++ } ++ } ++ } ++ } ++ ++ FreeDosObject(DOS_FIB, fib); ++ UnLock(dirlock); ++ ++ /* Build new name */ ++ d = newname; ++ src = to; ++ n = max + 1; ++ ++ if (n > 999) ++ n = 0; ++ ++ /* Copy base */ ++ while (*src && (d - newname) < (PATHBUF - 5)) ++ *d++ = *src++; ++ ++ *d++ = '.'; ++ ++ /* Manual 3-digit write */ ++ *d++ = '0' + (n / 100); ++ n %= 100; ++ *d++ = '0' + (n / 10); ++ *d++ = '0' + (n % 10); ++ *d = '\0'; ++ ++ /* Rename */ ++ if (Rename((STRPTR)from, (STRPTR)newname)) ++ return 0; ++ ++ errno = EACCES; + return -1; +- else +- return 0; + } +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/rfc2553_amiga.c binkd/amiga/rfc2553_amiga.c +--- binkd_pgul/amiga/rfc2553_amiga.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/rfc2553_amiga.c 2026-04-26 11:54:19.321503648 +0100 +@@ -0,0 +1,323 @@ ++/* ++ * rfc2553_amiga.c -- getaddrinfo/getnameinfo fallback for AmigaOS 3 ++ * ++ * rfc2553_amiga.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifdef AMIGA ++ ++#include "amiga/bsdsock.h" /* LP stubs + SocketBase */ ++#include "rfc2553.h" /* sets HAVE_GETADDRINFO / HAVE_GETNAMEINFO */ ++#include "sem.h" ++ ++#include ++#include ++#include ++#include ++ ++#define safe_strncpy(dst, src, n) \ ++ do \ ++ { \ ++ strncpy((dst), (src), (n)); \ ++ (dst)[(n) - 1] = '\0'; \ ++ } while (0) ++ ++#ifndef HAVE_GETADDRINFO ++ ++void freeaddrinfo(struct addrinfo *ai); /* forward decl */ ++ ++int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) ++{ ++ struct addrinfo **tail = res; ++ struct hostent *hent = NULL; ++ unsigned int port; ++ int proto; ++ const char *end; ++ char **addrp; ++ ++ static char passive_dummy = '\0'; ++ char *passive_list[2] = {&passive_dummy, NULL}; ++ ++ if (!res) ++ { ++ return EAI_UNKNOWN; ++ } ++ ++ *res = NULL; ++ ++ port = servname ? htons((unsigned short)strtol(servname, (char **)&end, 0)) : 0; ++ proto = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; ++ ++ lockresolvsem(); ++ ++ if (servname && end != servname + strlen(servname)) ++ { ++ struct servent *se = NULL; ++ ++ if (!hints || hints->ai_socktype == SOCK_STREAM) ++ se = getservbyname((char *)servname, "tcp"); ++ ++ if (hints && hints->ai_socktype == SOCK_DGRAM) ++ se = getservbyname((char *)servname, "udp"); ++ ++ if (!se) ++ { ++ releaseresolvsem(); ++ return EAI_NONAME; ++ } ++ ++ port = se->s_port; ++ ++ if (strcmp((char *)se->s_proto, "tcp") == 0) ++ proto = SOCK_STREAM; ++ else if (strcmp((char *)se->s_proto, "udp") == 0) ++ proto = SOCK_DGRAM; ++ else ++ { ++ releaseresolvsem(); ++ return EAI_NONAME; ++ } ++ ++ if (hints && hints->ai_socktype && hints->ai_socktype != proto) ++ { ++ releaseresolvsem(); ++ return EAI_SERVICE; ++ } ++ } ++ ++ if (!hints || !(hints->ai_flags & AI_PASSIVE)) ++ { ++ unsigned long nip = inet_addr((char *)nodename); ++ ++ if (nip != (unsigned long)INADDR_NONE) ++ { ++ struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); ++ struct sockaddr_in *sin; ++ ++ if (!ai) ++ { ++ releaseresolvsem(); ++ return EAI_MEMORY; ++ } ++ *tail = ai; ++ ++ sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); ++ ++ if (!sin) ++ { ++ free(ai); ++ releaseresolvsem(); ++ return EAI_MEMORY; ++ } ++ ++ ai->ai_family = AF_INET; ++ ai->ai_socktype = proto; ++ ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; ++ ai->ai_addrlen = sizeof(*sin); ++ ai->ai_addr = (struct sockaddr *)sin; ++ sin->sin_family = AF_INET; ++ sin->sin_port = port; ++ sin->sin_addr.s_addr = nip; ++ ++ releaseresolvsem(); ++ return 0; ++ } ++ ++ hent = gethostbyname((char *)nodename); ++ ++ if (!hent) ++ { ++ int herr = errno; ++ releaseresolvsem(); ++ return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL ++ : EAI_NONAME; ++ } ++ ++ if (!hent->h_addr_list || !hent->h_addr_list[0]) ++ { ++ releaseresolvsem(); ++ return EAI_NONAME; ++ } ++ ++ addrp = hent->h_addr_list; ++ } ++ else ++ { ++ addrp = passive_list; ++ } ++ ++ for (; *addrp; addrp++) ++ { ++ struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); ++ struct sockaddr_in *sin; ++ ++ if (!ai) ++ { ++ releaseresolvsem(); ++ freeaddrinfo(*res); ++ *res = NULL; ++ return EAI_MEMORY; ++ } ++ ++ if (!*res) ++ *res = ai; ++ *tail = ai; ++ tail = &ai->ai_next; ++ ++ sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); ++ ++ if (!sin) ++ { ++ releaseresolvsem(); ++ freeaddrinfo(*res); ++ *res = NULL; ++ return EAI_MEMORY; ++ } ++ ++ ai->ai_family = AF_INET; ++ ai->ai_socktype = proto; ++ ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; ++ ai->ai_addrlen = sizeof(*sin); ++ ai->ai_addr = (struct sockaddr *)sin; ++ sin->sin_family = AF_INET; ++ sin->sin_port = port; ++ ++ if (!hints || !(hints->ai_flags & AI_PASSIVE)) ++ { ++ size_t cpylen = sizeof(sin->sin_addr); ++ ++ if (hent->h_length > 0 && (size_t)hent->h_length < cpylen) ++ cpylen = (size_t)hent->h_length; ++ ++ memcpy(&sin->sin_addr, *addrp, cpylen); ++ } ++ } ++ ++ releaseresolvsem(); ++ return 0; ++} ++ ++void freeaddrinfo(struct addrinfo *ai) ++{ ++ struct addrinfo *next; ++ ++ while (ai) ++ { ++ free(ai->ai_addr); ++ next = ai->ai_next; ++ free(ai); ++ ai = next; ++ } ++} ++ ++static const char *ai_errlist[] = ++ { ++ "Success", ++ "hostname nor servname provided, or not known", ++ "Temporary failure in name resolution", ++ "Non-recoverable failure in name resolution", ++ "No address associated with hostname", ++ "ai_family not supported", ++ "ai_socktype not supported", ++ "service name not supported for ai_socktype", ++ "Address family for hostname not supported", ++ "Memory allocation failure", ++ "System error returned in errno", ++ "Unknown error", ++}; ++ ++char *gai_strerror(int ecode) ++{ ++ if (ecode > 0 || ecode < EAI_UNKNOWN) ++ ecode = EAI_UNKNOWN; ++ return (char *)ai_errlist[-ecode]; ++} ++ ++#endif /* !HAVE_GETADDRINFO */ ++ ++#ifndef HAVE_GETNAMEINFO ++ ++#ifndef NI_DATAGRAM ++#define NI_DATAGRAM (1 << 4) ++#endif ++ ++int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) ++{ ++ const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; ++ ++ (void)salen; ++ ++ if (sa->sa_family != AF_INET) ++ return EAI_ADDRFAMILY; ++ ++ if (host && hostlen > 0) ++ { ++ if (!(flags & NI_NUMERICHOST)) ++ { ++ struct hostent *he; ++ ++ lockresolvsem(); ++ he = gethostbyaddr((char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET); ++ ++ if (he) ++ { ++ safe_strncpy(host, (char *)he->h_name, hostlen); ++ releaseresolvsem(); ++ } ++ else ++ { ++ int herr = errno; ++ releaseresolvsem(); ++ if (flags & NI_NAMEREQD) ++ return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL ++ : EAI_NONAME; ++ flags |= NI_NUMERICHOST; ++ } ++ } ++ ++ if (flags & NI_NUMERICHOST) ++ { ++ lockhostsem(); ++ safe_strncpy(host, (char *)Inet_NtoA(sin->sin_addr.s_addr), hostlen); ++ releasehostsem(); ++ } ++ } ++ ++ if (serv && servlen > 0) ++ { ++ if (!(flags & NI_NUMERICSERV)) ++ { ++ struct servent *se; ++ ++ lockresolvsem(); ++ ++ se = (flags & NI_DATAGRAM) ? getservbyport(ntohs(sin->sin_port), "udp") : getservbyport(ntohs(sin->sin_port), "tcp"); ++ ++ if (se) ++ { ++ safe_strncpy(serv, (char *)se->s_name, servlen); ++ releaseresolvsem(); ++ } ++ else ++ { ++ releaseresolvsem(); ++ ++ if (flags & NI_NAMEREQD) ++ return EAI_NONAME; ++ ++ flags |= NI_NUMERICSERV; ++ } ++ } ++ ++ if (flags & NI_NUMERICSERV) ++ snprintf(serv, servlen, "%u", ntohs(sin->sin_port)); ++ } ++ ++ return 0; ++} ++ ++#endif /* !HAVE_GETNAMEINFO */ ++#endif /* AMIGA */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/sem.c binkd/amiga/sem.c +--- binkd_pgul/amiga/sem.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/amiga/sem.c 2026-04-25 19:33:46.416919700 +0100 +@@ -1,29 +1,100 @@ + /* + * Amiga semaphores + */ ++ + #include + #include +-#include ++#include ++#include ++ ++extern void Log(int lev, char *s, ...); ++ ++int _InitSem(void *vpSem) ++{ ++ memset(vpSem, 0, sizeof(struct SignalSemaphore)); ++ InitSemaphore((struct SignalSemaphore *)vpSem); ++ return 0; ++} ++ ++int _CleanSem(void *vpSem) ++{ ++ return 0; ++} ++ ++int _LockSem(void *vpSem) ++{ ++ ObtainSemaphore((struct SignalSemaphore *)vpSem); ++ return 0; ++} ++ ++int _ReleaseSem(void *vpSem) ++{ ++ ReleaseSemaphore((struct SignalSemaphore *)vpSem); ++ return 0; ++} + +-extern void Log (int lev, char *s,...); ++int _InitEventSem(EVENTSEM *sem) ++{ ++ if (!sem) ++ return -1; + ++ sem->waiter = NULL; + +-int _InitSem(void *vpSem) { +- memset(vpSem, 0, sizeof (struct SignalSemaphore)); +- InitSemaphore ((struct SignalSemaphore*)vpSem); +- return(0); ++ sem->sigbit = AllocSignal(-1); ++ ++ if (sem->sigbit == (ULONG)-1) ++ return -1; ++ ++ return 0; + } + +-int _CleanSem(void *vpSem) { +- return (0); ++int _CleanEventSem(EVENTSEM *sem) ++{ ++ if (!sem) ++ return -1; ++ ++ if (sem->sigbit != (ULONG)-1) ++ { ++ FreeSignal((LONG)sem->sigbit); ++ sem->sigbit = (ULONG)-1; ++ } ++ ++ sem->waiter = NULL; ++ return 0; + } + +-int _LockSem(void *vpSem) { +- ObtainSemaphore ((struct SignalSemaphore *)vpSem); +- return (0); ++int _PostSem(EVENTSEM *sem) ++{ ++ if (!sem) ++ return -1; ++ ++ if (sem->waiter && sem->sigbit != (ULONG)-1) ++ { ++ Signal((struct Task *)sem->waiter, 1UL << sem->sigbit); ++ } ++ ++ return 0; + } + +-int _ReleaseSem(void *vpSem) { +- ReleaseSemaphore ((struct SignalSemaphore *)vpSem); +- return (0); ++int _WaitSem(EVENTSEM *sem, int sec) ++{ ++ ULONG mask; ++ struct Task *me; ++ ++ if (!sem || sem->sigbit == (ULONG)-1) ++ return -1; ++ ++ me = FindTask(NULL); ++ sem->waiter = me; ++ ++ /* Wait on SIGBREAKF_CTRL_C to avoid hanging on race */ ++ mask = Wait((1UL << sem->sigbit) | SIGBREAKF_CTRL_C); ++ ++ sem->waiter = NULL; ++ ++ /* Return timeout/break indication if only CTRL_C fired */ ++ if (!(mask & (1UL << sem->sigbit)) && (mask & SIGBREAKF_CTRL_C)) ++ return -1; ++ ++ return 0; + } +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/session.c binkd/amiga/session.c +--- binkd_pgul/amiga/session.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/session.c 2026-04-26 11:57:30.521108284 +0100 +@@ -0,0 +1,462 @@ ++/* ++ * session.c -- session management for AmigaOS 3 binkd ++ * ++ * session.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "sys.h" ++#include "iphdr.h" ++#include "readcfg.h" ++#include "common.h" ++#include "tools.h" ++#include "client.h" ++#include "protocol.h" ++#include "ftnq.h" ++#include "ftnnode.h" ++#include "ftnaddr.h" ++#include "bsy.h" ++#include "iptools.h" ++#include "rfc2553.h" ++#include "srv_gai.h" ++#include "amiga/bsdsock.h" ++#include "amiga/evloop_int.h" ++#include "amiga/proto_amiga.h" ++ ++extern int binkd_exit; ++extern int ext_rand; ++extern int client_flag; ++extern int poll_flag; ++ ++/* Session table */ ++int sess_alloc(void) ++{ ++ int i; ++ ++ for (i = 0; i < max_sessions; i++) ++ { ++ if (sessions[i].phase == SESS_FREE) ++ { ++ memset(&sessions[i], 0, sizeof(sess_t)); ++ sessions[i].fd = INVALID_SOCKET; ++ sessions[i].phase = SESS_FREE; ++ return i; ++ } ++ } ++ ++ return -1; ++} ++ ++void sess_free(int idx) ++{ ++ sess_t *s = &sessions[idx]; ++ ++ if (s->fd != INVALID_SOCKET) ++ { ++ soclose(s->fd); ++ s->fd = INVALID_SOCKET; ++ } ++ ++ if (s->ai_head) ++ { ++ freeaddrinfo(s->ai_head); ++ s->ai_head = NULL; ++ } ++ ++ memset(&s->state, 0, sizeof(STATE)); ++ s->phase = SESS_FREE; ++} ++ ++/* Inbound: accept a new connection */ ++void do_accept(SOCKET lfd, BINKD_CONFIG *config) ++{ ++ struct sockaddr_storage sa; ++ socklen_t salen = (socklen_t)sizeof(sa); ++ SOCKET fd; ++ int idx; ++ sess_t *s; ++ char host[BINKD_FQDNLEN + 1]; ++ char ip[BINKD_FQDNLEN + 1]; ++ ++ fd = accept(lfd, (struct sockaddr *)&sa, &salen); ++ ++ if (fd == INVALID_SOCKET) ++ { ++ if (TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN) ++ Log(1, "accept(): %s", TCPERR()); ++ ++ return; ++ } ++ ++ if (binkd_exit) ++ { ++ soclose(fd); ++ return; ++ } ++ ++ idx = sess_alloc(); ++ ++ if (idx < 0) ++ { ++ Log(1, "session table full, refusing inbound"); ++ soclose(fd); ++ return; ++ } ++ ++ /* getnameinfo() Is unreliable on AmiTCP: use inet_ntoa directly */ ++ if (((struct sockaddr *)&sa)->sa_family == AF_INET) ++ { ++ struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; ++ strnzcpy(ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); ++ } ++ else ++ { ++ strnzcpy(ip, "unknown", BINKD_FQDNLEN); ++ } ++ ++ /* Backresolv not supported on AmiTCP: always use IP as host */ ++ strnzcpy(host, ip, BINKD_FQDNLEN); ++ ++ set_nonblock(fd); ++ ++ s = &sessions[idx]; ++ s->fd = fd; ++ s->inbound = 1; ++ s->node = NULL; ++ s->ai_head = NULL; ++ s->last_io = time(NULL); ++ strnzcpy(s->host, host, BINKD_FQDNLEN); ++ strnzcpy(s->ip, ip, BINKD_FQDNLEN); ++ s->port[0] = '\0'; ++ ++ if (amiga_proto_open(&s->state, fd, NULL, NULL, s->host, NULL, s->ip, config) != 0) ++ { ++ Log(1, "proto_open failed for %s", ip); ++ sess_free(idx); ++ return; ++ } ++ ++ s->phase = SESS_RUNNING; ++ n_servers++; ++ Log(4, "inbound slot[%d] from %s", idx, ip); ++} ++ ++/* Outbound: Non-blocking connect() */ ++int start_connect(sess_t *s, BINKD_CONFIG *config) ++{ ++ SOCKET fd; ++ int rc; ++ ++ s->ip[0] = '\0'; ++ s->port[0] = '\0'; ++ ++ fd = socket(s->ai_cur->ai_family, s->ai_cur->ai_socktype, s->ai_cur->ai_protocol); ++ ++ if (fd == INVALID_SOCKET) ++ { ++ Log(1, "outbound socket(): %s", TCPERR()); ++ return -1; ++ } ++ ++ /* getnameinfo() is unreliable on AmiTCP: may return rc=0 with garbage ++ * Use inet_ntoa/ntohs directly */ ++ if (s->ai_cur->ai_family == AF_INET) ++ { ++ struct sockaddr_in *sa4 = (struct sockaddr_in *)s->ai_cur->ai_addr; ++ strnzcpy(s->ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); ++ snprintf(s->port, MAXPORTSTRLEN, "%u", (unsigned)ntohs(sa4->sin_port)); ++ } ++ else ++ { ++ strnzcpy(s->ip, "unknown", BINKD_FQDNLEN); ++ strnzcpy(s->port, "0", MAXPORTSTRLEN); ++ } ++ ++ Log(4, "connecting %s [%s]:%s", s->host, s->ip, s->port); ++ ++ if (config->bindaddr[0]) ++ { ++ struct addrinfo src_h, *src_ai; ++ memset(&src_h, 0, sizeof(src_h)); ++ src_h.ai_family = s->ai_cur->ai_family; ++ src_h.ai_socktype = SOCK_STREAM; ++ src_h.ai_protocol = IPPROTO_TCP; ++ ++ if (getaddrinfo(config->bindaddr, NULL, &src_h, &src_ai) == 0) ++ { ++ bind(fd, src_ai->ai_addr, (int)src_ai->ai_addrlen); ++ freeaddrinfo(src_ai); ++ } ++ } ++ ++ set_nonblock(fd); ++ ++ rc = connect(fd, s->ai_cur->ai_addr, (int)s->ai_cur->ai_addrlen); ++ ++ if (rc == 0 || TCPERRNO == EINPROGRESS || TCPERRNO == EWOULDBLOCK) ++ { ++ s->fd = fd; ++ s->conn_start = time(NULL); ++ return 0; ++ } ++ ++ Log(1, "connect %s: %s", s->host, TCPERR()); ++ ++ bad_try(&s->node->fa, TCPERR(), BAD_CALL, config); ++ soclose(fd); ++ ++ return -1; ++} ++ ++int try_outbound(BINKD_CONFIG *config) ++{ ++ FTN_NODE *node; ++ sess_t *s; ++ int idx, rc; ++ struct addrinfo hints; ++ char dest[FTN_ADDR_SZ + 1]; ++ char host[BINKD_FQDNLEN + 5 + 1]; ++ char port[MAXPORTSTRLEN + 1]; ++ ++ if (!client_flag) ++ return 0; ++ ++ if (!config->q_present) ++ { ++ q_free(SCAN_LISTED, config); ++ ++ if (config->printq) ++ Log(-1, "scan\r"); ++ ++ q_scan(SCAN_LISTED, config); ++ config->q_present = 1; ++ ++ if (config->printq) ++ { ++ q_list(stderr, SCAN_LISTED, config); ++ Log(-1, "idle\r"); ++ } ++ } ++ ++ if (n_clients >= config->max_clients) ++ return 0; ++ ++ node = q_next_node(config); ++ ++ if (!node) ++ return 0; ++ ++ ftnaddress_to_str(dest, &node->fa); ++ ++ if (!bsy_test(&node->fa, F_BSY, config) || !bsy_test(&node->fa, F_CSY, config)) ++ { ++ Log(4, "%s busy", dest); ++ return 0; ++ } ++ ++ idx = sess_alloc(); ++ ++ if (idx < 0) ++ { ++ Log(2, "table full, deferring %s", dest); ++ return 0; ++ } ++ ++ s = &sessions[idx]; ++ memset(s, 0, sizeof(*s)); ++ s->fd = INVALID_SOCKET; ++ s->node = node; ++ s->inbound = 0; ++ ++ rc = get_host_and_port(1, host, port, node->hosts, &node->fa, config); ++ ++ if (rc <= 0) ++ { ++ Log(1, "%s: bad host list", dest); ++ sess_free(idx); ++ ++ return 0; ++ } ++ ++ strnzcpy(s->host, host, BINKD_FQDNLEN); ++ strnzcpy(s->port, port, MAXPORTSTRLEN); ++ ++ memset(&hints, 0, sizeof(hints)); ++ hints.ai_family = node->IP_afamily; ++ hints.ai_socktype = SOCK_STREAM; ++ hints.ai_protocol = IPPROTO_TCP; ++ ++ rc = srv_getaddrinfo(host, port, &hints, &s->ai_head); ++ ++ if (rc != 0) ++ { ++ Log(1, "%s: getaddrinfo error code=%d: %s", dest, rc, gai_strerror(rc)); ++ ++ bad_try(&node->fa, "getaddrinfo failed", BAD_CALL, config); ++ sess_free(idx); ++ ++ return 0; ++ } ++ ++ s->ai_cur = s->ai_head; ++ ++ if (start_connect(s, config) != 0) ++ { ++ sess_free(idx); ++ return 0; ++ } ++ ++ s->phase = SESS_CONNECTING; ++ n_clients++; ++ ++ Log(4, "outbound slot[%d] -> %s", idx, dest); ++ ++ return 1; ++} ++ ++/* Check completion of async connect() */ ++void check_connect(int idx, BINKD_CONFIG *config) ++{ ++ sess_t *s = &sessions[idx]; ++ int err = 0; ++ socklen_t el = (socklen_t)sizeof(err); ++ int tmo; ++ ++ tmo = config->connect_timeout ? config->connect_timeout : 30; ++ ++ if ((int)(time(NULL) - s->conn_start) >= tmo) ++ { ++ Log(1, "connect timeout -> %s", s->host); ++ ++ bad_try(&s->node->fa, "Timeout", BAD_CALL, config); ++ n_clients--; ++ sess_free(idx); ++ ++ return; ++ } ++ ++ getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &el); ++ ++ if (err) ++ { ++ Log(1, "connect -> %s: %s", s->host, strerror(err)); ++ ++ bad_try(&s->node->fa, strerror(err), BAD_CALL, config); ++ ++ soclose(s->fd); ++ s->fd = INVALID_SOCKET; ++ s->ai_cur = s->ai_cur->ai_next; ++ ++ if (s->ai_cur && start_connect(s, config) == 0) ++ return; /* trying next address */ ++ ++ n_clients--; ++ sess_free(idx); ++ ++ return; ++ } ++ ++ Log(4, "connected -> %s [%s]", s->host, s->ip); ++ ext_rand = rand(); ++ ++ if (amiga_proto_open(&s->state, s->fd, s->node, NULL, s->host, s->port, s->ip, config) != 0) ++ { ++ Log(1, "proto_open failed for %s", s->host); ++ ++ n_clients--; ++ sess_free(idx); ++ return; ++ } ++ ++ s->phase = SESS_RUNNING; ++ s->last_io = time(NULL); ++} ++ ++/* Run one protocol step on an active session */ ++void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config) ++{ ++ sess_t *s = &sessions[idx]; ++ int rc; ++ int tmo; ++ ++ tmo = config->nettimeout ? config->nettimeout : 300; ++ ++ if ((int)(time(NULL) - s->last_io) >= tmo) ++ { ++ Log(1, "slot[%d] net timeout", idx); ++ ++ if (s->node) ++ bad_try(&s->node->fa, "Timeout", BAD_IO, config); ++ ++ amiga_proto_close(&s->state, config, 0); ++ ++ if (s->inbound) ++ n_servers--; ++ else ++ n_clients--; ++ ++ sess_free(idx); ++ ++ return; ++ } ++ ++ if (s->fd == INVALID_SOCKET) ++ { ++ Log(1, "slot[%d] invalid socket, closing session", idx); ++ ++ if (s->node) ++ bad_try(&s->node->fa, "Invalid socket", BAD_IO, config); ++ ++ if (s->inbound) ++ n_servers--; ++ else ++ n_clients--; ++ ++ sess_free(idx); ++ ++ return; ++ } ++ ++ /* WaitSelect() may not report a readable socket when the remote has ++ * sent a TCP END. Probe with MSG_PEEK so recv_block() sees the EOF */ ++ if (!rd && !wr && s->state.state != P_NULL) ++ { ++ char peek; ++ int pr = recv(s->fd, &peek, 1, MSG_PEEK); ++ ++ if (pr == 0 || (pr < 0 && TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN)) ++ rd = 1; ++ } ++ ++ rc = amiga_proto_step(&s->state, rd, wr, config); ++ ++ if (rd || wr) ++ s->last_io = time(NULL); ++ ++ if (rc == APROTO_RUNNING) ++ return; ++ ++ amiga_proto_close(&s->state, config, rc == APROTO_DONE_OK); ++ ++ Log(4, "slot[%d] %s", idx, rc == APROTO_DONE_OK ? "OK" : "ERR"); ++ ++ if (s->inbound) ++ n_servers--; ++ else ++ n_clients--; ++ ++ sess_free(idx); ++ ++ if (poll_flag && n_clients == 0 && n_servers == 0) ++ binkd_exit = 1; ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/sock.c binkd/amiga/sock.c +--- binkd_pgul/amiga/sock.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/sock.c 2026-04-26 11:59:26.645813693 +0100 +@@ -0,0 +1,130 @@ ++/* ++ * sock.c -- listen socket management for AmigaOS 3 ++ * ++ * sock.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include ++#include ++ ++#include ++#include ++ ++#include "sys.h" ++#include "readcfg.h" ++#include "tools.h" ++#include "server.h" ++#include "rfc2553.h" ++#include "amiga/bsdsock.h" ++#include "amiga/evloop_int.h" ++ ++extern SOCKET sockfd[]; ++extern int sockfd_used; ++extern int server_flag; ++ ++void set_nonblock(SOCKET fd) ++{ ++ long flag = 1L; ++ ++ if (IoctlSocket(fd, FIONBIO, (char *)&flag) != 0) ++ Log(2, "IoctlSocket(FIONBIO) failed: %s", TCPERR()); ++} ++ ++int open_listen_sockets(BINKD_CONFIG *config) ++{ ++ struct listenchain *ll; ++ struct addrinfo hints, *ai, *head; ++ int err, opt = 1; ++ ++ memset(&hints, 0, sizeof(hints)); ++ hints.ai_flags = AI_PASSIVE; ++ hints.ai_family = PF_UNSPEC; ++ hints.ai_socktype = SOCK_STREAM; ++ hints.ai_protocol = IPPROTO_TCP; ++ ++ sockfd_used = 0; ++ ++ for (ll = config->listen.first; ll; ll = ll->next) ++ { ++ err = getaddrinfo(ll->addr[0] ? ll->addr : NULL, ll->port, &hints, &head); ++ ++ if (err) ++ { ++ Log(1, "listen getaddrinfo(%s:%s): %s", ll->addr[0] ? ll->addr : "*", ll->port, gai_strerror(err)); ++ return -1; ++ } ++ ++ for (ai = head; ai && sockfd_used < MAX_LISTENSOCK; ai = ai->ai_next) ++ { ++ SOCKET fd; ++ int retries = 6; ++ ++ fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); ++ ++ if (fd == INVALID_SOCKET) ++ { ++ Log(1, "listen socket(): %s", TCPERR()); ++ continue; ++ } ++ ++ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, (int)sizeof(opt)) != 0) ++ Log(2, "setsockopt(SO_REUSEADDR) failed: %s", TCPERR()); ++ ++ /* Bsdsocket may hold the port briefly after socket close */ ++ while (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) != 0) ++ { ++ if (--retries == 0) ++ { ++ Log(1, "listen bind(): %s", TCPERR()); ++ ++ soclose(fd); ++ freeaddrinfo(head); ++ return -1; ++ } ++ ++ Log(2, "bind retry in 2s: %s", TCPERR()); ++ ++ Delay(100UL); /* 100 ticks = 2s @ 50Hz */ ++ } ++ ++ if (listen(fd, 5) != 0) ++ { ++ Log(1, "listen(): %s", TCPERR()); ++ ++ soclose(fd); ++ freeaddrinfo(head); ++ return -1; ++ } ++ ++ set_nonblock(fd); ++ sockfd[sockfd_used] = fd; ++ sockfd_used++; ++ } ++ ++ freeaddrinfo(head); ++ ++ Log(3, "listening on %s:%s", ++ ll->addr[0] ? ll->addr : "*", ll->port); ++ } ++ ++ if (sockfd_used == 0 && server_flag) ++ { ++ Log(1, "evloop: no listen sockets opened"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++void close_listen_sockets(void) ++{ ++ int i; ++ ++ for (i = 0; i < sockfd_used; i++) ++ soclose(sockfd[i]); ++ ++ sockfd_used = 0; ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/utime.c binkd/amiga/utime.c +--- binkd_pgul/amiga/utime.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/amiga/utime.c 2026-04-26 11:59:41.408046130 +0100 +@@ -0,0 +1,77 @@ ++/* ++ * utime.c -- utime() stub for AmigaOS 3 without ixemul ++ * ++ * utime.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifdef AMIGA ++ ++#include ++#include ++#include ++#include ++ ++#include "amiga/dirent.h" /* declares struct utimbuf and utime() prototype */ ++ ++/* Days between AmigaDOS epoch (1978-01-01) and POSIX epoch (1970-01-01) */ ++#define AMIGA_EPOCH_DELTA_DAYS 2922UL ++#define SECONDS_PER_DAY 86400UL ++#define SECONDS_PER_MINUTE 60UL ++ ++int utime(const char *path, const struct utimbuf *times) ++{ ++ struct DateStamp ds; ++ LONG seconds_today; ++ LONG total_seconds; ++ ++ if (!path) ++ { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ if (!times) ++ { ++ /* Use current time */ ++ DateStamp(&ds); ++ } ++ else ++ { ++ LONG t = (LONG)times->modtime; ++ ++ if (t < (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY)) ++ { ++ /* Time predates AmigaDOS epoch -- clamp to epoch */ ++ t = 0; ++ } ++ else ++ { ++ t -= (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY); ++ } ++ ++ ds.ds_Days = (LONG)(t / (LONG)SECONDS_PER_DAY); ++ total_seconds = t % (LONG)SECONDS_PER_DAY; ++ ds.ds_Minute = (LONG)(total_seconds / (LONG)SECONDS_PER_MINUTE); ++ seconds_today = total_seconds % (LONG)SECONDS_PER_MINUTE; ++ ds.ds_Tick = seconds_today * (LONG)TICKS_PER_SECOND; ++ } ++ ++ if (!SetFileDate((STRPTR)path, &ds)) ++ { ++ /* Most likely cause: file does not exist or is write-protected */ ++ LONG err = IoErr(); ++ ++ if (err == ERROR_OBJECT_NOT_FOUND || err == ERROR_DIR_NOT_FOUND) ++ errno = ENOENT; ++ else ++ errno = EACCES; ++ return -1; ++ } ++ ++ return 0; ++} ++ ++#endif /* AMIGA */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/binkd.c binkd/binkd.c +--- binkd_pgul/binkd.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/binkd.c 2026-04-26 13:20:44.986212073 +0100 +@@ -54,6 +54,10 @@ + #include "unix/daemonize.h" + #endif + ++#ifdef AMIGA ++/* amiga/bsdsock.h pulled in via iphdr.h for AMIGA */ ++#include "amiga/evloop.h" ++#endif + #ifdef WIN32 + #include "nt/service.h" + #include "nt/w32tools.h" +@@ -62,9 +66,11 @@ + #endif + #endif + ++#include "bsycleanup.h" ++ + #include "confopt.h" + +-#ifdef HAVE_THREADS ++#if defined(HAVE_THREADS) || defined(AMIGA) + MUTEXSEM hostsem; + MUTEXSEM resolvsem; + MUTEXSEM lsem; +@@ -94,9 +100,13 @@ + char *configpath = NULL; /* Config file name */ + char **saved_envp; + +-#ifdef HAVE_FORK ++/* mypid: needed by HAVE_FORK and AMIGA targets */ ++#if defined(HAVE_FORK) || defined(AMIGA) ++int mypid; ++#endif + +-int mypid, got_sighup, got_sigchld; ++#ifdef HAVE_FORK ++int got_sighup, got_sigchld; + + void chld (int *childcount) + { +@@ -195,9 +205,13 @@ + #endif + " -C reload on config change\n" + " -c run client only\n" ++#ifndef AMIGA + " -i run server on stdin/stdout pipe (by inetd or other)\n" ++#endif + " -f node run server protected session with this node\n" ++#ifndef AMIGA + " -a ip assume remote address when running with '-i' switch\n" ++#endif + #if defined(BINKD9X) + " -t cmd (start|stop|restart|status|install|uninstall) service(s)\n" + " -S name set Win9x service name, all - use all services\n" +@@ -312,12 +326,14 @@ + case 'c': + client_flag = 1; + break; ++#ifndef AMIGA + case 'i': + inetd_flag = 1; + break; + case 'a': /* remote IP address */ + remote_addr = strdup(optarg); + break; ++#endif + case 'f': /* remote FTN address */ + remote_node = strdup(optarg); + break; +@@ -416,6 +432,28 @@ + } + if (optind\n", extract_filename(argv[0])); ++ fprintf(stderr, " Use -P
for polling a specific node (e.g., -P 1:23/456.7)\n"); ++ exit(1); ++ } ++ ++ /* Check for leftover FTN addresses in extra arguments */ ++ while (optind < argc) ++ { ++ char *extra = argv[optind++]; ++ if (strchr(extra, ':') || strchr(extra, '@')) ++ { ++ fprintf(stderr, "%s: Error: Unexpected FTN address '%s' in arguments.\n", extract_filename(argv[0]), extra); ++ fprintf(stderr, " Use -P
before the config file to poll a node.\n"); ++ exit(1); ++ } ++ } ++ + #ifdef OS2 + if (optindloglevel, current_config->conlog, + current_config->logpath, current_config->nolog.first); ++ ++ /* Clean up old .bsy/.csy files at startup */ ++ cleanup_old_bsy(current_config); + } + else if (verbose_flag) + { +@@ -665,9 +706,13 @@ + + if (p) + { +- remote_addr = strdup(p); +- p = strchr(remote_addr, ' '); +- if (p) *p = '\0'; ++ remote_addr = strdup(p); ++ /* Guard against null pointer dereference if strdup fails */ ++ if (remote_addr) ++ { ++ p = strchr(remote_addr, ' '); ++ if (p) *p = '\0'; ++ } + } + } + /* not using stdin/stdout itself to avoid possible collisions */ +@@ -677,6 +722,9 @@ + inetd_socket_out = dup(fileno(stdout)); + #ifdef UNIX + tempfd = open("/dev/null", O_RDWR); ++#elif defined(AMIGA) ++ /* NIL: is the native AmigaDOS null device (no ixemul) */ ++ tempfd = open("NIL:", O_RDWR); + #else + tempfd = open("nul", O_RDWR); + #endif +@@ -707,6 +755,15 @@ + signal (SIGHUP, sighandler); + #endif + ++#ifdef AMIGA ++ /* AmigaOS 3: WaitSelect() loop, no fork/threads */ ++ { ++ BINKD_CONFIG *ev_cfg = lock_current_config(); ++ amiga_evloop_run(ev_cfg, server_flag, client_flag); ++ unlock_config_structure(ev_cfg, 0); ++ return 0; ++ } ++#else + if (client_flag && !server_flag) + { + clientmgr (0); +@@ -721,7 +778,9 @@ + if (client_flag && (pidcmgr = branch (clientmgr, 0, 0)) < 0) + { + Log (0, "cannot branch out"); ++ exit (1); + } ++#endif /* !AMIGA */ + + if (*current_config->pid_file) + { +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/binlog.c binkd/binlog.c +--- binkd_pgul/binlog.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/binlog.c 2026-04-22 06:50:46.000000000 +0100 +@@ -35,6 +35,10 @@ + #include "tools.h" + #include "sem.h" + ++#if defined(HAVE_THREADS) || defined(AMIGA) ++extern MUTEXSEM blsem; ++#endif ++ + /* Write 16-bit integer to file in intel bytes order */ + static int fput16(u16 arg, FILE *file) + { +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/branch.c binkd/branch.c +--- binkd_pgul/branch.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/branch.c 2026-04-26 09:12:44.314385608 +0100 +@@ -20,12 +20,6 @@ + #include "tools.h" + #include "sem.h" + +-#ifdef AMIGA +-int ix_vfork (void); +-void vfork_setup_child (void); +-void ix_vfork_resume (void); +-#endif +- + #ifdef WITH_PTHREADS + typedef struct { + void (*F) (void *); +@@ -132,23 +126,6 @@ + #endif + #endif + +-#ifdef AMIGA +- /* this is rather bizzare. this function pretends to be a fork and behaves +- * like one, but actually it's a kind of a thread. so we'll need semaphores */ +- +- if (!(rc = ix_vfork ())) +- { +- vfork_setup_child (); +- ix_vfork_resume (); +- F (arg); +- exit (0); +- } +- else if (rc < 0) +- { +- Log (1, "ix_vfork: %s", strerror (errno)); +- } +-#endif +- + #if defined(DOS) || defined(DEBUGCHILD) + rc = 0; + F (arg); +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/breaksig.c binkd/breaksig.c +--- binkd_pgul/breaksig.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/breaksig.c 2026-04-26 10:25:19.576809578 +0100 +@@ -46,6 +46,16 @@ + { + atexit (exitfunc); + ++#ifdef AMIGA ++ /* AmigaOS / libnix: signal() maps SIGINT -> SIGBREAKF_CTRL_C ++ * Register exitsig() so that Ctrl+C sets binkd_exit=1 even when ++ * the process is NOT blocked inside WaitSelect() (e.g. during disk ++ * I/O). When inside WaitSelect(), amiga_select_wrap() in bsdsock.h ++ * detects the break and sets binkd_exit=1 directly without going ++ * through this signal handler. */ ++ signal (SIGINT, exitsig); ++ signal (SIGTERM, exitsig); ++#else + #ifdef SIGBREAK + signal (SIGBREAK, exitsig); + #endif +@@ -58,5 +68,6 @@ + #ifdef SIGTERM + signal (SIGTERM, exitsig); + #endif ++#endif /* AMIGA */ + return 1; + } +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/bsycleanup.c binkd/bsycleanup.c +--- binkd_pgul/bsycleanup.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/bsycleanup.c 2026-04-26 11:01:23.269049076 +0100 +@@ -0,0 +1,122 @@ ++/* ++ * bsycleanup.c -- Cleanup functions for .bsy/.csy/.try files ++ * ++ * bsycleanup.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "sys.h" ++#include "readcfg.h" ++#include "ftnq.h" ++#include "ftnnode.h" ++#include "tools.h" ++#include "readdir.h" ++ ++/* ++ * Clean up old .bsy and .csy files at startup ++ * Scans all domain outbounds ++ */ ++ ++/* Helper: scan a single directory for .bsy/.csy files and delete them */ ++static void scan_and_delete_bsy_in_dir(const char *dir, BINKD_CONFIG *config) ++{ ++ DIR *dp; ++ struct dirent *de; ++ char buf[MAXPATHLEN + 1]; ++ ++ if ((dp = opendir(dir)) == 0) ++ { ++ return; ++ } ++ ++ while ((de = readdir(dp)) != 0) ++ { ++ char *s = de->d_name; ++ int len = strlen(s); ++ ++ if (len > 4 && (!STRICMP(s + len - 4, ".bsy") || !STRICMP(s + len - 4, ".csy") || !STRICMP(s + len - 4, ".try"))) ++ { ++ strnzcpy(buf, dir, sizeof(buf)); ++ strnzcat(buf, PATH_SEPARATOR, sizeof(buf)); ++ strnzcat(buf, s, sizeof(buf)); ++ ++ Log(2, "deleting %s", buf); ++ ++ delete(buf); ++ } ++ } ++ ++ closedir(dp); ++} ++ ++void cleanup_old_bsy(BINKD_CONFIG *config) ++{ ++ DIR *dp; ++ char outb_path[MAXPATHLEN + 1], base_path[MAXPATHLEN + 1]; ++ struct dirent *de; ++ FTN_DOMAIN *curr_domain; ++ int len; ++ ++ Log(2, "cleaning up .bsy/.csy/.try files at startup"); ++ ++ /* Scan all domain outbounds */ ++ for (curr_domain = config->pDomains.first; curr_domain; curr_domain = curr_domain->next) ++ { ++ if (curr_domain->alias4 != 0) ++ continue; ++ ++ /* Build base path: path + separator */ ++ strnzcpy(base_path, curr_domain->path, sizeof(base_path)); ++#ifndef AMIGA ++ if (base_path[strlen(base_path) - 1] == ':') ++ strcat(base_path, PATH_SEPARATOR); ++#endif ++ ++#ifdef AMIGADOS_4D_OUTBOUND ++ if (config->aso) ++ { ++ /* ASO mode: direct outbound path */ ++ strnzcpy(outb_path, base_path, sizeof(outb_path)); ++ strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); ++ strnzcat(outb_path, curr_domain->dir, sizeof(outb_path)); ++ Log(7, "cleanup_old_bsy (ASO): scanning domain '%s', path '%s'", curr_domain->name, outb_path); ++ scan_and_delete_bsy_in_dir(outb_path, config); ++ } ++ else ++#endif ++ { ++ /* BSO mode: scan for outbound.xxx directories */ ++ Log(7, "cleanup_old_bsy (BSO): scanning domain '%s', base '%s'", curr_domain->name, base_path); ++ ++ if ((dp = opendir(base_path)) == 0) ++ continue; ++ ++ len = strlen(curr_domain->dir); ++ ++ while ((de = readdir(dp)) != 0) ++ { ++ /* Match outbound or outbound.xxx */ ++ if (!STRNICMP(de->d_name, curr_domain->dir, len) && (de->d_name[len] == 0 || (de->d_name[len] == '.' && isxdigit(de->d_name[len + 1])))) ++ { ++ strnzcpy(outb_path, base_path, sizeof(outb_path)); ++ strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); ++ strnzcat(outb_path, de->d_name, sizeof(outb_path)); ++ ++ Log(7, "cleanup_old_bsy (BSO): scanning outbound dir '%s'", outb_path); ++ ++ scan_and_delete_bsy_in_dir(outb_path, config); ++ } ++ } ++ ++ closedir(dp); ++ } ++ } ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/bsycleanup.h binkd/bsycleanup.h +--- binkd_pgul/bsycleanup.h 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/bsycleanup.h 2026-04-26 13:11:39.607856201 +0100 +@@ -0,0 +1,19 @@ ++/* ++ * bsycleanup.h -- Cleanup functions for .bsy/.csy/.try files ++ * ++ * bsycleanup.h is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#ifndef _BSYCLEANUP_H ++#define _BSYCLEANUP_H ++ ++#include "readcfg.h" ++ ++ ++/* cleanup_old_bsy -- Clean up old .bsy/.csy/.try files at startup */ ++void cleanup_old_bsy(BINKD_CONFIG *config); ++ ++#endif /* _BSYCLEANUP_H */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/btypes.h binkd/btypes.h +--- binkd_pgul/btypes.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/btypes.h 2026-04-25 20:39:58.604145925 +0100 +@@ -73,6 +73,7 @@ + int HC_flag; + int restrictIP; + int NP_flag; /* no proxy */ ++ int NC_flag; /* no compression */ + + time_t hold_until; + int busy; /* 0=free, 'c'=.csy, other=.bsy */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/cambios.diff binkd/cambios.diff +--- binkd_pgul/cambios.diff 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/cambios.diff 2026-04-26 16:03:26.856780412 +0100 +@@ -0,0 +1,8223 @@ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/bsdsock.c binkd_pgul/amiga/bsdsock.c ++--- binkd/amiga/bsdsock.c 2026-04-26 11:01:10.438650436 +0100 +++++ binkd_pgul/amiga/bsdsock.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,79 +0,0 @@ ++-/* ++- * bsdsock.c -- bsdsocket.library lifecycle for AmigaOS 3 ++- * ++- * bsdsock.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include ++-#include ++-#include ++-#include ++-#include ++-#include ++- ++-/* Linker-compatibility global. Never used at runtime */ ++-struct Library *SocketBase = NULL; ++- ++-/* Suppress conflicting C prototypes from clib/bsdsocket_protos.h */ ++-#ifndef CLIB_BSDSOCKET_PROTOS_H ++-#define CLIB_BSDSOCKET_PROTOS_H ++-#endif ++- ++-#include ++-#include ++- ++-extern void Log(int lev, const char *s, ...); ++- ++-/* _amiga_get_socket_base -- returns bsdsocket.library handle for calling task */ ++-struct Library *_amiga_get_socket_base(void) ++-{ ++- return (struct Library *)FindTask(NULL)->tc_UserData; ++-} ++- ++-int amiga_sock_init(void) ++-{ ++- struct Task *me = FindTask(NULL); ++- struct Library *base; ++- ++- if (me->tc_UserData) ++- return 0; /* already open for this task */ ++- ++- base = OpenLibrary("bsdsocket.library", 0UL); ++- ++- if (!base) ++- { ++- fprintf(stderr, "amiga_sock_init: cannot open bsdsocket.library\n"); ++- return -1; ++- } ++- ++- /* Store in tc_UserData and global SocketBase */ ++- me->tc_UserData = (APTR)base; ++- SocketBase = base; ++- ++- /* Link the per-task errno to the TCP stack. */ ++- SetErrnoPtr(&errno, (LONG)sizeof(errno)); ++- ++- return 0; ++-} ++- ++-void amiga_sock_cleanup(void) ++-{ ++- struct Task *me = FindTask(NULL); ++- struct Library *base = (struct Library *)me->tc_UserData; ++- ++- if (base) ++- { ++- me->tc_UserData = NULL; ++- SocketBase = NULL; ++- CloseLibrary(base); ++- } ++-} ++- ++-int amiga_child_sock_init(void) ++-{ ++- /* Child inherits tc_UserData = NULL, opens new handle */ ++- return amiga_sock_init(); ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/bsdsock.h binkd_pgul/amiga/bsdsock.h ++--- binkd/amiga/bsdsock.h 2026-04-26 10:49:27.706200054 +0100 +++++ binkd_pgul/amiga/bsdsock.h 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,155 +0,0 @@ ++-/* ++- * bsdsock.h -- bsdsocket.library init and POSIX compat shims for AmigaOS 3 ++- * ++- * bsdsock.h is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifndef _AMIGA_BSDSOCK_H ++-#define _AMIGA_BSDSOCK_H ++- ++-#ifdef AMIGA ++- ++-#include ++-#include ++-#include ++-#include ++- ++-/* Suppress conflicting C prototypes from roadshow */ ++-#ifndef CLIB_BSDSOCKET_PROTOS_H ++-#define CLIB_BSDSOCKET_PROTOS_H ++-#endif ++- ++-/* Undefine MCLBYTES/MCLSHIFT before roadshow headers */ ++-#ifdef MCLBYTES ++-#undef MCLBYTES ++-#endif ++-#ifdef MCLSHIFT ++-#undef MCLSHIFT ++-#endif ++- ++-/* Roadshow SDK network headers */ ++-#include ++-#include ++-#include "compat_netinet_in.h" ++-#include ++-#include ++-#include /* inline/bsdsocket.h, no clib protos */ ++- ++-/* Undefine conflicting unistd.h macros */ ++-#ifdef gethostid ++-#undef gethostid ++-#endif ++-#ifdef getdtablesize ++-#undef getdtablesize ++-#endif ++-#ifdef gethostname ++-#undef gethostname ++-#endif ++- ++-/* Per-task SocketBase override */ ++-struct Library *_amiga_get_socket_base(void); ++- ++-#ifdef SocketBase ++-#undef SocketBase ++-#endif ++-#define SocketBase _amiga_get_socket_base() ++- ++-/* Roadshow socket-specific errno values */ ++-#include ++- ++-#define BSDSOCK_HAS_TIMEVAL 1 ++- ++-/* Socket-specific errno values from roadshow sys/errno.h */ ++-#ifndef ENOTSOCK ++-#define ENOTSOCK 38 /* Socket operation on non-socket */ ++-#endif ++-#ifndef EOPNOTSUPP ++-#define EOPNOTSUPP 45 /* Operation not supported on socket */ ++-#endif ++-#ifndef ECONNREFUSED ++-#define ECONNREFUSED 61 /* Connection refused */ ++-#endif ++-#ifndef ETIMEDOUT ++-#define ETIMEDOUT 60 /* Connection timed out */ ++-#endif ++-#ifndef ECONNRESET ++-#define ECONNRESET 54 /* Connection reset by peer */ ++-#endif ++-#ifndef EHOSTUNREACH ++-#define EHOSTUNREACH 65 /* No route to host */ ++-#endif ++- ++-#include ++- ++-/* sockaddr_storage fallback for roadshow */ ++-#ifndef HAVE_SOCKADDR_STORAGE ++-#ifndef sockaddr_storage ++-struct sockaddr_storage ++-{ ++- unsigned short ss_family; ++- char __ss_pad[22]; /* enough for IPv4 */ ++-}; ++-#endif ++-#define HAVE_SOCKADDR_STORAGE 1 ++-#endif ++- ++-/* Library base functions */ ++-int amiga_sock_init(void); ++-void amiga_sock_cleanup(void); ++-int amiga_child_sock_init(void); ++- ++-/* getpid() is defined in sys.h for AMIGA */ ++-/* amiga_sleep and sleep are defined in sys.h for AMIGA */ ++- ++-/* select() -> WaitSelect() wrapper with Ctrl+C handling */ ++-#ifndef AMIGA_SELECT_DEFINED ++-#define AMIGA_SELECT_DEFINED ++- ++-/* Forward-declare binkd_exit */ ++-extern int binkd_exit; ++- ++-/* Forward-declare Log to avoid implicit declaration warning */ ++-extern void Log(int lev, char *s, ...); ++- ++-static int amiga_select_wrap(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) ++-{ ++- ULONG sigmask = SIGBREAKF_CTRL_C; ++- int rc = WaitSelect(nfds, readfds, writefds, exceptfds, timeout, &sigmask); ++- ++- /* Ctrl+C should break blocked select() loops immediately. */ ++- if ((sigmask & SIGBREAKF_CTRL_C) != 0) ++- { ++- Log(1, "Ctrl+C detected in WaitSelect, setting binkd_exit=1"); ++- binkd_exit = 1; ++- errno = EINTR; ++- return -1; ++- } ++- ++- return rc; ++-} ++- ++-#define select(n, r, w, e, t) amiga_select_wrap((n), (r), (w), (e), (t)) ++- ++-#endif /* AMIGA_SELECT_DEFINED */ ++- ++-/* FIONBIO via IoctlSocket */ ++-#ifndef FIONBIO ++-#define FIONBIO 0x8004667E ++-#endif ++-#ifndef ioctl ++-#define ioctl(s, req, arg) IoctlSocket((s), (req), (char *)(arg)) ++-#endif ++- ++-/* inet_ntoa -> Inet_NtoA (Amiga only) */ ++-#ifdef AMIGA ++-#ifdef inet_ntoa ++-#undef inet_ntoa ++-#endif ++-#define inet_ntoa(a) Inet_NtoA(a) ++-#endif ++- ++-#endif /* AMIGA */ ++-#endif /* _AMIGA_BSDSOCK_H */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/compat_netinet_in.h binkd_pgul/amiga/compat_netinet_in.h ++--- binkd/amiga/compat_netinet_in.h 2026-04-26 09:53:12.337417283 +0100 +++++ binkd_pgul/amiga/compat_netinet_in.h 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,19 +0,0 @@ ++-/* ++- * compat_netinet_in.h -- Wrapper for netinet/in.h typedef clash ++- * ++- * compat_netinet_in.h is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifndef _AMIGA_COMPAT_NETINET_IN_H ++-#define _AMIGA_COMPAT_NETINET_IN_H ++- ++-#ifdef __GNUC__ ++-#include_next ++-#else ++-#include ++-#endif ++- ++-#endif /* _AMIGA_COMPAT_NETINET_IN_H */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/dirent.c binkd_pgul/amiga/dirent.c ++--- binkd/amiga/dirent.c 2026-04-26 11:40:07.539429919 +0100 +++++ binkd_pgul/amiga/dirent.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,137 +0,0 @@ ++-/* ++- * dirent.c -- POSIX directory scanning for AmigaOS 3 ++- * ++- * dirent.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifdef AMIGA ++- ++-#include ++-#include ++-#include ++-#include ++-#include ++-#include ++- ++-#include "amiga/dirent.h" ++- ++-/* opendir -- locks directory and allocates state for readdir() */ ++-DIR *opendir(const char *path) ++-{ ++- DIR *dir; ++- ++- if (!path) ++- { ++- errno = EINVAL; ++- return NULL; ++- } ++- ++- dir = (DIR *)AllocMem((LONG)sizeof(DIR), MEMF_CLEAR); ++- ++- if (!dir) ++- { ++- errno = ENOMEM; ++- return NULL; ++- } ++- ++- dir->fib = (struct FileInfoBlock *)AllocMem((LONG)sizeof(struct FileInfoBlock), MEMF_CLEAR); ++- ++- if (!dir->fib) ++- { ++- FreeMem(dir, (LONG)sizeof(DIR)); ++- errno = ENOMEM; ++- return NULL; ++- } ++- ++- dir->lock = Lock((STRPTR)path, ACCESS_READ); ++- ++- if (!dir->lock) ++- { ++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); ++- FreeMem(dir, (LONG)sizeof(DIR)); ++- errno = ENOENT; ++- return NULL; ++- } ++- ++- /* Examine the directory itself; this positions FIB for ExNext() */ ++- if (!Examine(dir->lock, dir->fib)) ++- { ++- UnLock(dir->lock); ++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); ++- FreeMem(dir, (LONG)sizeof(DIR)); ++- errno = EACCES; ++- return NULL; ++- } ++- ++- /* fib_DirEntryType > 0 means this IS a directory */ ++- if (dir->fib->fib_DirEntryType <= 0) ++- { ++- UnLock(dir->lock); ++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); ++- FreeMem(dir, (LONG)sizeof(DIR)); ++- errno = ENOTDIR; ++- return NULL; ++- } ++- ++- dir->first = 1; /* ExNext() has not been called yet */ ++- ++- return dir; ++-} ++- ++-/* readdir -- advances to next directory entry */ ++-struct dirent *readdir(DIR *dir) ++-{ ++- LONG dos_rc; ++- LONG dos_err; ++- ++- if (!dir) ++- { ++- errno = EINVAL; ++- return NULL; ++- } ++- ++- /* ExNext() advances past the last entry returned by Examine/ExNext */ ++- dos_rc = ExNext(dir->lock, dir->fib); ++- ++- if (!dos_rc) ++- { ++- dos_err = IoErr(); ++- ++- if (dos_err == ERROR_NO_MORE_ENTRIES) ++- return NULL; ++- ++- errno = EIO; ++- return NULL; ++- } ++- ++- /* Copy name into the entry buffer */ ++- strncpy(dir->entry.d_name, dir->fib->fib_FileName, AMIGA_NAME_MAX - 1); ++- dir->entry.d_name[AMIGA_NAME_MAX - 1] = '\0'; ++- dir->entry.d_ino = 0; ++- ++- return &dir->entry; ++-} ++- ++-/* closedir -- releases all resources associated with dir */ ++-int closedir(DIR *dir) ++-{ ++- if (!dir) ++- { ++- errno = EINVAL; ++- return -1; ++- } ++- ++- if (dir->lock) ++- UnLock(dir->lock); ++- ++- if (dir->fib) ++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); ++- ++- FreeMem(dir, (LONG)sizeof(DIR)); ++- return 0; ++-} ++- ++-#endif /* AMIGA */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/dirent.h binkd_pgul/amiga/dirent.h ++--- binkd/amiga/dirent.h 2026-04-26 09:53:13.628932082 +0100 +++++ binkd_pgul/amiga/dirent.h 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,53 +0,0 @@ ++-/* ++- * dirent.h -- POSIX directory scanning for AmigaOS 3 ++- * ++- * dirent.h is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifndef _AMIGA_DIRENT_H ++-#define _AMIGA_DIRENT_H ++- ++-#ifdef AMIGA ++- ++-#include ++-#include ++- ++-/* Maximum name length: AmigaDOS allows 107 characters */ ++-#define AMIGA_NAME_MAX 108 ++- ++-struct dirent ++-{ ++- unsigned long d_ino; /* inode -- always 0 on AmigaDOS */ ++- char d_name[AMIGA_NAME_MAX]; /* null-terminated file name */ ++-}; ++- ++-/* struct utimbuf for AmigaOS 3 without ixemul */ ++-#ifndef _AMIGA_UTIMBUF_DEFINED ++-#define _AMIGA_UTIMBUF_DEFINED ++- ++-struct utimbuf ++-{ ++- long actime; /* access time (unused by SetFileDate) */ ++- long modtime; /* modification time (POSIX time_t) */ ++-}; ++- ++-int utime(const char *path, const struct utimbuf *times); ++-#endif ++- ++-typedef struct _amiga_dir ++-{ ++- BPTR lock; /* directory lock */ ++- struct FileInfoBlock *fib; /* reusable FileInfoBlock */ ++- int first; /* flag: first call not yet */ ++- struct dirent entry; /* storage returned to caller */ ++-} DIR; ++- ++-DIR *opendir(const char *path); ++-struct dirent *readdir(DIR *dir); ++-int closedir(DIR *dir); ++- ++-#endif /* AMIGA */ ++-#endif /* _AMIGA_DIRENT_H */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop.c binkd_pgul/amiga/evloop.c ++--- binkd/amiga/evloop.c 2026-04-26 11:46:47.344533199 +0100 +++++ binkd_pgul/amiga/evloop.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,509 +0,0 @@ ++-/* ++- * evloop.c -- non-blocking event loop for AmigaOS 3 ++- * ++- * evloop.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-/* Suppress clib bsdsocket prototypes before any socket header */ ++-#ifndef CLIB_BSDSOCKET_PROTOS_H ++-#define CLIB_BSDSOCKET_PROTOS_H ++-#endif ++- ++-#include ++-#include ++-#include ++- ++-#include ++-#include ++-#include ++- ++-#include "sys.h" ++-#include "readcfg.h" ++-#include "common.h" ++-#include "tools.h" ++-#include "protocol.h" ++-#include "sem.h" ++-#include "server.h" ++-#include "amiga/bsdsock.h" ++-#include "amiga/evloop.h" ++-#include "amiga/evloop_int.h" ++-#include "amiga/proto_amiga.h" ++- ++-/* Externals */ ++-extern SOCKET sockfd[MAX_LISTENSOCK]; ++-extern int sockfd_used; ++-extern int binkd_exit; ++-extern int server_flag, client_flag; ++- ++-/* Session table (shared with sock.c and session.c) */ ++-sess_t *sessions = NULL; ++-int max_sessions = 0; ++- ++-/* ++- * calc_max_sessions -- Compute session slot count from config + flags ++- * Shared by init and config-reload paths ++- */ ++-static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) ++-{ ++- int servers = config->max_servers; ++- int clients = config->max_clients; ++- int total; ++- ++- if (servers == 0 && clients == 0) ++- { ++- Log(5, "DEBUG: Using default 2 slots (no config found)"); ++- return 2; ++- } ++- ++- Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); ++- ++- if (srv_flag && servers < 1) ++- servers = 1; ++- ++- if (cli_flag && clients < 1) ++- clients = 1; ++- ++- total = servers + clients; ++- ++- if (total < 2) ++- total = 2; ++- ++- Log(5, "DEBUG: Calculated max_sessions=%d", total); ++- ++- return total; ++-} ++- ++-/* init_session_table -- Allocate and zero-initialise the session array */ ++-static int init_session_table(int slots) ++-{ ++- int i; ++- ++- sessions = calloc(slots, sizeof(sess_t)); ++- ++- if (!sessions) ++- { ++- Log(1, "Failed to allocate session table"); ++- return 0; ++- } ++- ++- for (i = 0; i < slots; i++) ++- { ++- memset(&sessions[i], 0, sizeof(sess_t)); ++- memset(&sessions[i].state, 0, sizeof(STATE)); ++- sessions[i].fd = INVALID_SOCKET; ++- sessions[i].phase = SESS_FREE; ++- } ++- ++- return 1; ++-} ++- ++-/* ++- * build_fdsets -- Populate r/w fd_sets from listen sockets and sessions ++- * Returns the highest fd seen (maxfd) ++- */ ++-static int build_fdsets(fd_set *r, fd_set *w) ++-{ ++- int i, maxfd = 0; ++- ++- FD_ZERO(r); ++- FD_ZERO(w); ++- ++- /* server side: listen sockets */ ++- for (i = 0; i < sockfd_used; i++) ++- { ++- if (sockfd[i] != INVALID_SOCKET) ++- { ++- FD_SET(sockfd[i], r); ++- ++- if ((int)sockfd[i] > maxfd) ++- maxfd = (int)sockfd[i]; ++- } ++- } ++- ++- /* client + server sessions */ ++- for (i = 0; i < max_sessions; i++) ++- { ++- sess_t *s = &sessions[i]; ++- ++- if (s->phase == SESS_FREE || s->fd == INVALID_SOCKET) ++- continue; ++- ++- if ((int)s->fd > maxfd) ++- maxfd = (int)s->fd; ++- ++- if (s->phase == SESS_CONNECTING) ++- { ++- /* client: waiting for non-blocking connect() */ ++- FD_SET(s->fd, w); ++- } ++- else ++- { ++- /* Server or established client session */ ++- FD_SET(s->fd, r); ++- ++- if (s->state.msgs || s->state.oleft || s->state.send_eof || (s->state.out.f && !s->state.off_req_sent && !s->state.waiting_for_GOT)) ++- FD_SET(s->fd, w); ++- } ++- } ++- ++- Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); ++- return maxfd; ++-} ++- ++-/* ++- * handle_server_accept -- Accept new inbound connections on all listen fds ++- * Returns 0 normally, -1 if binkd_exit was set during accept ++- */ ++-static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) ++-{ ++- int i; ++- ++- Log(5, "DEBUG: Before accept loop"); ++- ++- for (i = 0; i < sockfd_used; i++) ++- { ++- if (FD_ISSET(sockfd[i], r)) ++- do_accept(sockfd[i], config); ++- ++- if (binkd_exit) ++- { ++- Log(5, "DEBUG: binkd_exit during accept loop"); ++- return -1; ++- } ++- } ++- ++- Log(5, "DEBUG: After accept loop"); ++- ++- return 0; ++-} ++- ++-/* ++- * advance_sessions -- Step every active session (server + client) ++- * Returns the number of non-free sessions processed ++- */ ++-static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) ++-{ ++- int i, active = 0; ++- ++- Log(5, "DEBUG: Before advance sessions"); ++- ++- for (i = 0; i < max_sessions; i++) ++- { ++- sess_t *s = &sessions[i]; ++- ++- Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); ++- ++- if (s->phase == SESS_FREE) ++- continue; ++- ++- active++; ++- ++- if (s->phase == SESS_CONNECTING) ++- { ++- /* client: Complete the non-blocking connect */ ++- if (FD_ISSET(s->fd, w)) ++- check_connect(i, config); ++- } ++- else ++- { ++- int rd = FD_ISSET(s->fd, r); ++- int wr = FD_ISSET(s->fd, w); ++- ++- /* Always step: protocol must advance internal state even ++- * when WaitSelect reports no activity (e.g. second batch ++- * EOB send, TCP FIN detection after remote closes) */ ++- do_session_step(i, rd, wr, config); ++- } ++- ++- if (binkd_exit) ++- break; ++- } ++- ++- return active; ++-} ++- ++-/* ++- * handle_config_reload -- Resize session table and reopen listen sockets ++- * Returns 1 if the caller should break out of the main loop, 0 otherwise ++- */ ++-static int handle_config_reload(BINKD_CONFIG **config, int srv_flag, int cli_flag) ++-{ ++- int i, new_max; ++- BINKD_CONFIG *nc = lock_current_config(); ++- ++- if (nc) ++- { ++- new_max = calc_max_sessions(nc, srv_flag, cli_flag); ++- ++- if (new_max != max_sessions) ++- { ++- sess_t *ns = realloc(sessions, new_max * sizeof(sess_t)); ++- ++- if (ns) ++- { ++- for (i = max_sessions; i < new_max; i++) ++- { ++- memset(&ns[i], 0, sizeof(sess_t)); ++- memset(&ns[i].state, 0, sizeof(STATE)); ++- ns[i].fd = INVALID_SOCKET; ++- ns[i].phase = SESS_FREE; ++- } ++- ++- sessions = ns; ++- max_sessions = new_max; ++- ++- Log(4, "Session table resized to %d slots", max_sessions); ++- } ++- else ++- { ++- Log(1, "Failed to resize session table, keeping current size"); ++- } ++- } ++- ++- unlock_config_structure(nc, 0); ++- } ++- ++- close_listen_sockets(); ++- *config = lock_current_config(); ++- ++- if (srv_flag && open_listen_sockets(*config) < 0) ++- { ++- unlock_config_structure(*config, 0); ++- return 1; /* fatal — break main loop */ ++- } ++- ++- unlock_config_structure(*config, 0); ++- *config = lock_current_config(); ++- return 0; ++-} ++- ++-/* evloop_cleanup -- Close sessions and free resources on exit */ ++-static void evloop_cleanup(BINKD_CONFIG *config, int config_locked) ++-{ ++- int i; ++- ++- if (config_locked) ++- unlock_config_structure(config, 0); ++- ++- if (sessions) ++- { ++- for (i = 0; i < max_sessions; i++) ++- { ++- if (sessions[i].phase == SESS_RUNNING) ++- { ++- amiga_proto_close(&sessions[i].state, config, 0); ++- ++- if (sessions[i].inbound) ++- n_servers--; ++- else ++- n_clients--; ++- } ++- else if (sessions[i].phase == SESS_CONNECTING) ++- { ++- n_clients--; ++- } ++- sess_free(i); ++- } ++- ++- free(sessions); ++- sessions = NULL; ++- } ++- ++- close_listen_sockets(); ++- amiga_sock_cleanup(); ++- Log(4, "evloop done"); ++-} ++- ++-/* amiga_evloop_run -- Entry point: init, then main WaitSelect() loop */ ++-void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) ++-{ ++- int config_locked = 0; ++- time_t last_rescan = 0; ++- time_t now; ++- fd_set r, w; ++- struct timeval tv; ++- int n, maxfd; ++- int active_sessions = 0; ++- static int idle_loops = 0; ++- ++- /* Sync globals so try_outbound() and friends see the correct flags */ ++- server_flag = srv_flag; ++- client_flag = cli_flag; ++- ++- sockfd_used = 0; ++- srand((unsigned int)time(NULL)); ++- ++- Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); ++- Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); ++- ++- /* Initialise session table */ ++- max_sessions = calc_max_sessions(config, server_flag, client_flag); ++- ++- if (max_sessions < 2) ++- { ++- Log(2, "WARNING: max_sessions=%d is too low, forcing to 2", max_sessions); ++- max_sessions = 2; ++- } ++- ++- Log(4, "evloop start (AmigaOS 3, WaitSelect, %d slots)", max_sessions); ++- ++- if (!init_session_table(max_sessions)) ++- return; ++- ++- /* server: Open listen sockets */ ++- if (server_flag && open_listen_sockets(config) < 0) ++- { ++- Log(0, "evloop: cannot open listen sockets"); ++- free(sessions); ++- sessions = NULL; ++- return; ++- } ++- ++- Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); ++- ++- /* Initial outbound attempt before waiting (important for poll -p mode) */ ++- Log(5, "DEBUG: Initial try_outbound before main loop"); ++- try_outbound(config); ++- last_rescan = time(NULL); /* Reset timer since we just did an attempt */ ++- ++- /* ===== Main loop ===== */ ++- for (;;) ++- { ++- if (binkd_exit) ++- { ++- Log(1, "binkd_exit detected at loop start, exiting"); ++- break; ++- } ++- ++- /* Build fd_sets */ ++- Log(5, "DEBUG: Building fd_sets"); ++- maxfd = build_fdsets(&r, &w); ++- ++- tv.tv_sec = 1; ++- tv.tv_usec = 0L; ++- ++- /* WaitSelect() with nfds>0 but empty fd_sets causes guru #80000006 :/ ++- * Use select(0,...) as a pure sleep when no sockets are active */ ++- if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) ++- maxfd = 1; ++- ++- Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); ++- ++- if (maxfd == 0) ++- { ++- /* No sockets yet -- use Delay() instead of select(0,...) ++- * as WaitSelect with nfds=0 can block indefinitely on AmigaOS */ ++- Delay(50); /* 1 second = 50 ticks at 50Hz PAL */ ++- n = 0; /* simulate timeout */ ++- } ++- else ++- n = select(maxfd + 1, &r, &w, NULL, &tv); ++- ++- Log(5, "DEBUG: select() returned n=%d", n); ++- ++- if (binkd_exit) ++- { ++- Log(1, "binkd_exit detected after select(), exiting"); ++- break; ++- } ++- ++- Delay(1UL); /* 1 tick = 20ms @ 50Hz, prevents CPU hogging */ ++- ++- /* Handle select errors */ ++- if (n < 0) ++- { ++- if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) ++- { ++- Log(5, "DEBUG: select interrupted, continuing"); ++- continue; ++- } ++- ++- if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) ++- { ++- Log(2, "select: %s, reopening", TCPERR()); ++- ++- close_listen_sockets(); ++- ++- if (server_flag && open_listen_sockets(config) < 0) ++- break; ++- ++- continue; ++- } ++- ++- Log(1, "select: %s", TCPERR()); ++- break; ++- } ++- else if (n == 0) ++- { ++- Log(5, "DEBUG: select timeout, continuing"); ++- } ++- ++- /* server: Accept new inbound connections */ ++- if (server_flag) ++- { ++- if (handle_server_accept(&r, config) < 0) ++- break; ++- } ++- ++- if (binkd_exit) ++- break; ++- ++- /* server + client: Advance all active sessions */ ++- active_sessions = advance_sessions(&r, &w, config); ++- ++- if (binkd_exit) ++- break; ++- ++- /* client: Time-based outbound scan + config reload */ ++- now = time(NULL); ++- ++- if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) ++- { ++- Log(5, "DEBUG: Before try_outbound"); ++- ++- try_outbound(config); ++- ++- Log(5, "DEBUG: After try_outbound"); ++- ++- if (checkcfg()) ++- { ++- if (handle_config_reload(&config, server_flag, client_flag)) ++- break; ++- ++- config_locked = 1; ++- } ++- ++- config->q_present = 0; ++- last_rescan = now; ++- } ++- ++- /* client: Poll-mode idle exit */ ++- /* Reset counter whenever there is something going on */ ++- if (n_clients > 0 || active_sessions > 0) ++- { ++- if (idle_loops > 0) ++- Log(2, "Activity detected, reset idle counter"); ++- ++- idle_loops = 0; ++- } ++- ++- if (!server_flag && active_sessions == 0 && n_clients == 0) ++- { ++- idle_loops++; ++- ++- Log(2, "Idle loop %d/2 (no server, no sessions, no clients)", idle_loops); ++- ++- if (idle_loops > 1) ++- { ++- Log(0, "the queue is empty, quitting..."); ++- break; ++- } ++- } ++- } ++- /* ===== End main loop ===== */ ++- ++- evloop_cleanup(config, config_locked); ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop.h binkd_pgul/amiga/evloop.h ++--- binkd/amiga/evloop.h 2026-04-26 11:47:03.034239655 +0100 +++++ binkd_pgul/amiga/evloop.h 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,34 +0,0 @@ ++-/* ++- * evloop.h -- non-blocking event loop for AmigaOS 3 ++- * ++- * evloop.h is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifndef _AMIGA_EVLOOP_H ++-#define _AMIGA_EVLOOP_H ++- ++-#ifdef AMIGA ++- ++-#include "readcfg.h" ++-#include "protoco2.h" /* STATE */ ++- ++-/* amiga_proto_step() return codes — also used by protocol.c */ ++-#define APROTO_RUNNING 0 /* session alive, call again */ ++-#define APROTO_DONE_OK 1 /* session finished, success */ ++-#define APROTO_DONE_ERR 2 /* session failed */ ++- ++-/* ++- * amiga_evloop_run -- entry point replacing servmgr() + clientmgr() ++- * ++- * Opens listen sockets (when server_flag), then runs a WaitSelect() ++- * loop that multiplexes sessions dynamically based on config->max_servers ++- * and config->max_clients. Minimum 2 sessions are always allocated ++- * Returns only when binkd_exit != 0 ++- */ ++-void amiga_evloop_run(BINKD_CONFIG *config, int server_flag, int client_flag); ++- ++-#endif /* AMIGA */ ++-#endif /* _AMIGA_EVLOOP_H */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop_int.h binkd_pgul/amiga/evloop_int.h ++--- binkd/amiga/evloop_int.h 2026-04-26 11:02:58.166969078 +0100 +++++ binkd_pgul/amiga/evloop_int.h 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,68 +0,0 @@ ++-/* ++- * evloop_int.h -- internal types shared by evloop.c, sock.c, session.c ++- * ++- * evloop_int.h is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifndef AMIGA_EVLOOP_INT_H ++-#define AMIGA_EVLOOP_INT_H ++- ++-#include "protoco2.h" ++-#include "amiga/bsdsock.h" ++-#include "ftnnode.h" ++-#include "readcfg.h" ++- ++-/* Session lifecycle */ ++-typedef enum ++-{ ++- SESS_FREE = 0, /* slot available */ ++- SESS_CONNECTING = 1, /* waiting for connect() */ ++- SESS_RUNNING = 2 /* BinkP session active */ ++-} sess_phase_t; ++- ++-/* Per-session state */ ++-typedef struct ++-{ ++- sess_phase_t phase; ++- SOCKET fd; ++- STATE state; ++- int inbound; /* 1=accepted, 0=outbound */ ++- ++- FTN_NODE *node; ++- struct addrinfo *ai_head; /* full getaddrinfo list */ ++- struct addrinfo *ai_cur; /* candidate being tried */ ++- time_t conn_start; ++- ++- char host[BINKD_FQDNLEN + 1]; ++- char port[MAXPORTSTRLEN + 1]; ++- char ip[BINKD_FQDNLEN + 1]; ++- ++- time_t last_io; ++-} sess_t; ++- ++-/* Globals defined in evloop.c */ ++-extern sess_t *sessions; ++-extern int max_sessions; ++- ++-/* Defined in server.c and client.c respectively */ ++-extern int n_servers; ++-extern int n_clients; ++- ++-/* sock.c */ ++-void set_nonblock(SOCKET fd); ++-int open_listen_sockets(BINKD_CONFIG *config); ++-void close_listen_sockets(void); ++- ++-/* session.c */ ++-int sess_alloc(void); ++-void sess_free(int idx); ++-void do_accept(SOCKET lfd, BINKD_CONFIG *config); ++-int start_connect(sess_t *s, BINKD_CONFIG *config); ++-void check_connect(int idx, BINKD_CONFIG *config); ++-int try_outbound(BINKD_CONFIG *config); ++-void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config); ++- ++-#endif /* AMIGA_EVLOOP_INT_H */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/proto_amiga.c binkd_pgul/amiga/proto_amiga.c ++--- binkd/amiga/proto_amiga.c 2026-04-26 11:52:04.887130443 +0100 +++++ binkd_pgul/amiga/proto_amiga.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,269 +0,0 @@ ++-/* ++- * proto_amiga.c -- Amiga non-blocking BinkP protocol implementation ++- * ++- * proto_amiga.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include ++-#include ++-#include ++-#include ++-#include ++- ++-#include "sys.h" ++-#include "readcfg.h" ++-#include "common.h" ++-#include "protocol.h" ++-#include "ftnaddr.h" ++-#include "ftnnode.h" ++-#include "ftnq.h" ++-#include "tools.h" ++-#include "bsy.h" ++-#include "inbound.h" ++-#include "protoco2.h" ++-#include "prothlp.h" ++-#include "binlog.h" ++-#include "evloop.h" ++- ++-/* External functions from protocol.c */ ++-extern int init_protocol(STATE *state, SOCKET s_in, SOCKET s_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config); ++-extern int banner(STATE *state, BINKD_CONFIG *config); ++-extern int recv_block(STATE *state, BINKD_CONFIG *config); ++-extern int send_block(STATE *state, BINKD_CONFIG *config); ++-extern void bsy_touch(BINKD_CONFIG *config); ++-extern FTNQ *process_rcvdlist(STATE *state, FTNQ *q, BINKD_CONFIG *config); ++-extern int start_file_transfer(STATE *state, FTNQ *q, BINKD_CONFIG *config); ++-extern void ND_set_status(const char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config); ++-extern void deinit_protocol(STATE *state, BINKD_CONFIG *config, int status); ++-extern void evt_set(EVTQ *eq); ++-extern void msg_send2(STATE *state, t_msg m, char *s1, char *s2); ++- ++-/* External functions from other modules */ ++-extern void log_end_of_session(int err, STATE *state, BINKD_CONFIG *config); ++-extern void inb_remove_partial(STATE *state, BINKD_CONFIG *config); ++-extern void good_try(FTN_ADDR *fa, char *comment, BINKD_CONFIG *config); ++-extern void bad_try(FTN_ADDR *fa, const char *error, const int where, BINKD_CONFIG *config); ++-extern int create_poll(FTN_ADDR *fa, int flvr, BINKD_CONFIG *config); ++-extern void hold_node(FTN_ADDR *fa, time_t hold_until, BINKD_CONFIG *config); ++-extern int binkd_exit; ++- ++-/* External variables */ ++-extern int n_servers; ++- ++-/* ++- * amiga_proto_open -- Initialise a session and send the BinkP banner ++- * ++- * fd : Connected socket (same fd for in and out) ++- * to : Outbound node, NULL for inbound ++- * fa : Local AKA to use, may be NULL ++- * host : Remote hostname or dotted-IP string (caller-owned, stable) ++- * port : Remote port string, may be NULL ++- * dst_ip : Numeric remote IP, may be NULL (falls back to host) ++- * config : Current config ++- * ++- * Returns 0 on success, -1 on error (caller must close fd) ++- */ ++-int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config) ++-{ ++- struct sockaddr_storage sa; ++- socklen_t salen = (socklen_t)sizeof(sa); ++- char ownhost[BINKD_FQDNLEN + 1]; ++- char ownserv[MAXSERVNAME + 1]; ++- int rc; ++- ++- if (!init_protocol(state, fd, fd, to, fa, config)) ++- return -1; ++- ++- /* Peer identity for logging and %ip config checks */ ++- state->ipaddr = dst_ip ? (char *)dst_ip : (char *)host; ++- state->peer_name = (host && *host) ? (char *)host : state->ipaddr; ++- ++- /* local endpoint: Not used further, skip to avoid dangling pointer */ ++- ++- Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); ++- ++- /* banner() sends M_NUL lines and ADR messages */ ++- if (!banner(state, config)) ++- return -1; ++- ++- /* refuse if server limit reached */ ++- if (!to && n_servers > config->max_servers) ++- { ++- Log(1, "too many servers"); ++- msg_send2(state, M_BSY, "Too many servers", 0); ++- ++- return -1; ++- } ++- ++- return 0; ++-} ++- ++-/* ++- * amiga_proto_step -- Run one recv/send iteration of the BinkP loop ++- * ++- * readable : Non-zero if the socket has incoming data (from WaitSelect) ++- * writable : Non-zero if the socket can accept outgoing data ++- * ++- * Returns APROTO_RUNNING, APROTO_DONE_OK, or APROTO_DONE_ERR ++- * ++- * This is the loop body of protocol() with the select() call removed ++- * recv_block() and send_block() already handle EWOULDBLOCK gracefully, ++- * so calling this on a non-blocking socket is safe ++- */ ++-int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config) ++-{ ++- FTNQ *q; ++- int no; ++- ++- if (state->io_error) ++- return APROTO_DONE_ERR; ++- ++- /* Advance outgoing file queue if nothing is being sent */ ++- if (!state->local_EOB && state->q && !state->out.f && !state->waiting_for_GOT && !state->off_req_sent && state->state != P_NULL) ++- { ++- while (1) ++- { ++- q = 0; ++- if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) ++- { ++- if (start_file_transfer(state, q, config)) ++- break; ++- } ++- else ++- { ++- q_free(state->q, config); ++- state->q = 0; ++- break; ++- } ++- } ++- } ++- ++- /* Nothing left to send — issue EOB */ ++- if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) ++- { ++- if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) ++- { ++- state->local_EOB = 1; ++- msg_send2(state, M_EOB, 0, 0); ++- } ++- } ++- ++- /* Recv step: Only when socket is readable */ ++- if (readable) ++- { ++- if (!recv_block(state, config)) ++- return APROTO_DONE_ERR; ++- } ++- ++- /* ++- * send step: drive even when writable=0 if there is buffered data, ++- * pending messages, a file mid-transfer, or an EOF to flush. ++- */ ++- if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) ++- { ++- no = send_block(state, config); ++- ++- if (!no && no != 2) ++- return APROTO_DONE_ERR; ++- } ++- ++- bsy_touch(config); ++- ++- /* Batch/Session-end detection — Mirrors the break logic in protocol() */ ++- if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) ++- { ++- if (state->rcvdlist) ++- { ++- state->q = process_rcvdlist(state, state->q, config); ++- ++- q_to_killlist(&state->killlist, &state->n_killlist, state->q); ++- free_rcvdlist(&state->rcvdlist, &state->n_rcvdlist); ++- } ++- ++- Log(6, "batch: %i msgs", state->msgs_in_batch); ++- ++- if (state->msgs_in_batch <= 2 || (state->major * 100 + state->minor <= 100)) ++- { ++- /* Session done */ ++- ND_set_status("", &state->ND_addr, state, config); ++- state->ND_addr.z = -1; ++- ++- return APROTO_DONE_OK; ++- } ++- ++- /* Start next batch */ ++- state->msgs_in_batch = 0; ++- state->remote_EOB = 0; ++- state->local_EOB = 0; ++- ++- if (OK_SEND_FILES(state, config)) ++- { ++- state->q = q_scan_boxes(state->q, state->fa, state->nfa, state->to ? 1 : 0, config); ++- state->q = q_sort(state->q, state->fa, state->nfa, config); ++- } ++- } ++- ++- return APROTO_RUNNING; ++-} ++- ++-/* ++- * amiga_proto_close -- Flush remaining I/O and release STATE resources ++- * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR ++- */ ++-void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) ++-{ ++- int no; ++- char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; ++- int status = ok ? 0 : 1; ++- ++- /* Drain inbound queue */ ++- if (!state->io_error) ++- { ++- while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) ++- Log(9, "purged %d bytes", no); ++- } ++- ++- /* Flush pending outbound messages */ ++- while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) ++- ; ++- ++- if (ok) ++- { ++- log_end_of_session(0, state, config); ++- process_killlist(state->killlist, state->n_killlist, 's'); ++- inb_remove_partial(state, config); ++- ++- if (state->to) ++- good_try(&state->to->fa, "CONNECT/BND", config); ++- } ++- else ++- { ++- log_end_of_session(1, state, config); ++- process_killlist(state->killlist, state->n_killlist, 0); ++- ++- if (!binkd_exit && state->to) ++- bad_try(&state->to->fa, "Bad session", BAD_IO, config); ++- ++- /* Restore poll flavour if files were left mid-transfer */ ++- if (state->to && tolower(state->maxflvr) != 'h') ++- { ++- Log(4, "restoring poll with '%c' flavour", state->maxflvr); ++- ++- create_poll(&state->to->fa, state->maxflvr, config); ++- } ++- } ++- ++- if (state->to && state->r_skipped_flag && config->hold_skipped > 0) ++- { ++- Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); ++- ++- hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); ++- } ++- ++- deinit_protocol(state, config, status); ++- evt_set(state->evt_queue); ++- state->evt_queue = NULL; ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/proto_amiga.h binkd_pgul/amiga/proto_amiga.h ++--- binkd/amiga/proto_amiga.h 2026-04-26 11:53:22.716799421 +0100 +++++ binkd_pgul/amiga/proto_amiga.h 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,30 +0,0 @@ ++-/* ++- * proto_amiga.h -- Amiga non-blocking BinkP protocol implementation ++- * ++- * proto_amiga.h is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifndef _PROTO_AMIGA_H ++-#define _PROTO_AMIGA_H ++- ++-#include "protoco2.h" ++-#include "readcfg.h" ++- ++-/* amiga_proto_step() return codes */ ++-#define APROTO_RUNNING 0 ++-#define APROTO_DONE_OK 1 ++-#define APROTO_DONE_ERR 2 ++- ++-/* amiga_proto_open -- Initialise a session and send the BinkP banner */ ++-int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config); ++- ++-/* amiga_proto_step-- run one recv / send iteration of the BinkP loop */ ++-int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config); ++- ++-/* amiga_proto_close -- flush remaining I/O and release STATE resources */ ++-void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok); ++- ++-#endif /* _PROTO_AMIGA_H */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/rename.c binkd_pgul/amiga/rename.c ++--- binkd/amiga/rename.c 2026-04-25 19:33:26.785601976 +0100 +++++ binkd_pgul/amiga/rename.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -1,137 +1,10 @@ ++ #include ++ #include ++-#include ++- ++-#include ++-#include /* atoi */ ++-#include /* isdigit */ ++- ++-#define PATHBUF 512 ++ ++ int o_rename(char *from, char *to) ++ { ++- struct FileInfoBlock *fib; ++- char dir[PATHBUF]; ++- char base[PATHBUF]; ++- char newname[PATHBUF]; ++- char *slash; ++- ULONG max = 0; ++- BPTR dirlock; ++- char *d = NULL; ++- const char *src = NULL; ++- ULONG n = 0; ++- ++- /* Try direct rename first */ ++- if (Rename((STRPTR)from, (STRPTR)to)) ++- return 0; ++- ++- /* Split path */ ++- slash = strrchr(to, '/'); ++- ++- if (!slash) ++- slash = strrchr(to, ':'); ++- ++- if (slash) ++- { ++- ULONG len = slash - to; ++- ++- if (len >= PATHBUF) ++- len = PATHBUF - 1; ++- ++- strncpy(dir, to, len); ++- dir[len] = '\0'; ++- ++- strncpy(base, slash + 1, PATHBUF - 1); ++- base[PATHBUF - 1] = '\0'; ++- } ++- else ++- { ++- strcpy(dir, "."); ++- strncpy(base, to, PATHBUF - 1); ++- base[PATHBUF - 1] = '\0'; ++- } ++- ++- /* Lock directory */ ++- dirlock = Lock((STRPTR)dir, ACCESS_READ); ++- ++- if (!dirlock) ++- { ++- errno = ENOENT; ++- return -1; ++- } ++- ++- fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); ++- ++- if (!fib) ++- { ++- UnLock(dirlock); ++- errno = ENOMEM; ++- return -1; ++- } ++- ++- /* Scan directory safely under lock */ ++- if (Examine(dirlock, fib)) ++- { ++- while (ExNext(dirlock, fib)) ++- { ++- if (strncmp(fib->fib_FileName, base, strlen(base)) == 0) ++- { ++- const char *p = NULL; ++- ++- p = fib->fib_FileName + strlen(base); ++- ++- if (*p != '.') ++- { ++- continue; ++- } ++- ++- /* .001 style */ ++- if (isdigit((UBYTE)p[1]) && isdigit((UBYTE)p[2]) && isdigit((UBYTE)p[3])) ++- { ++- n = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); ++- if (n > max) ++- max = n; ++- } ++- ++- /* FIDO volume style (.mo0 .th1 etc) */ ++- if (isdigit((UBYTE)p[1]) && !isdigit((UBYTE)p[2])) ++- { ++- n = p[1] - '0'; ++- if (n > max) ++- max = n; ++- } ++- } ++- } ++- } ++- ++- FreeDosObject(DOS_FIB, fib); ++- UnLock(dirlock); ++- ++- /* Build new name */ ++- d = newname; ++- src = to; ++- n = max + 1; ++- ++- if (n > 999) ++- n = 0; ++- ++- /* Copy base */ ++- while (*src && (d - newname) < (PATHBUF - 5)) ++- *d++ = *src++; ++- ++- *d++ = '.'; ++- ++- /* Manual 3-digit write */ ++- *d++ = '0' + (n / 100); ++- n %= 100; ++- *d++ = '0' + (n / 10); ++- *d++ = '0' + (n % 10); ++- *d = '\0'; ++- ++- /* Rename */ ++- if (Rename((STRPTR)from, (STRPTR)newname)) ++- return 0; ++- ++- errno = EACCES; +++ if (Rename((STRPTR)from, (STRPTR)to)) /* cross-volume move won't work */ ++ return -1; +++ else +++ return 0; ++ } ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/rfc2553_amiga.c binkd_pgul/amiga/rfc2553_amiga.c ++--- binkd/amiga/rfc2553_amiga.c 2026-04-26 11:54:19.321503648 +0100 +++++ binkd_pgul/amiga/rfc2553_amiga.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,323 +0,0 @@ ++-/* ++- * rfc2553_amiga.c -- getaddrinfo/getnameinfo fallback for AmigaOS 3 ++- * ++- * rfc2553_amiga.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifdef AMIGA ++- ++-#include "amiga/bsdsock.h" /* LP stubs + SocketBase */ ++-#include "rfc2553.h" /* sets HAVE_GETADDRINFO / HAVE_GETNAMEINFO */ ++-#include "sem.h" ++- ++-#include ++-#include ++-#include ++-#include ++- ++-#define safe_strncpy(dst, src, n) \ ++- do \ ++- { \ ++- strncpy((dst), (src), (n)); \ ++- (dst)[(n) - 1] = '\0'; \ ++- } while (0) ++- ++-#ifndef HAVE_GETADDRINFO ++- ++-void freeaddrinfo(struct addrinfo *ai); /* forward decl */ ++- ++-int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) ++-{ ++- struct addrinfo **tail = res; ++- struct hostent *hent = NULL; ++- unsigned int port; ++- int proto; ++- const char *end; ++- char **addrp; ++- ++- static char passive_dummy = '\0'; ++- char *passive_list[2] = {&passive_dummy, NULL}; ++- ++- if (!res) ++- { ++- return EAI_UNKNOWN; ++- } ++- ++- *res = NULL; ++- ++- port = servname ? htons((unsigned short)strtol(servname, (char **)&end, 0)) : 0; ++- proto = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; ++- ++- lockresolvsem(); ++- ++- if (servname && end != servname + strlen(servname)) ++- { ++- struct servent *se = NULL; ++- ++- if (!hints || hints->ai_socktype == SOCK_STREAM) ++- se = getservbyname((char *)servname, "tcp"); ++- ++- if (hints && hints->ai_socktype == SOCK_DGRAM) ++- se = getservbyname((char *)servname, "udp"); ++- ++- if (!se) ++- { ++- releaseresolvsem(); ++- return EAI_NONAME; ++- } ++- ++- port = se->s_port; ++- ++- if (strcmp((char *)se->s_proto, "tcp") == 0) ++- proto = SOCK_STREAM; ++- else if (strcmp((char *)se->s_proto, "udp") == 0) ++- proto = SOCK_DGRAM; ++- else ++- { ++- releaseresolvsem(); ++- return EAI_NONAME; ++- } ++- ++- if (hints && hints->ai_socktype && hints->ai_socktype != proto) ++- { ++- releaseresolvsem(); ++- return EAI_SERVICE; ++- } ++- } ++- ++- if (!hints || !(hints->ai_flags & AI_PASSIVE)) ++- { ++- unsigned long nip = inet_addr((char *)nodename); ++- ++- if (nip != (unsigned long)INADDR_NONE) ++- { ++- struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); ++- struct sockaddr_in *sin; ++- ++- if (!ai) ++- { ++- releaseresolvsem(); ++- return EAI_MEMORY; ++- } ++- *tail = ai; ++- ++- sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); ++- ++- if (!sin) ++- { ++- free(ai); ++- releaseresolvsem(); ++- return EAI_MEMORY; ++- } ++- ++- ai->ai_family = AF_INET; ++- ai->ai_socktype = proto; ++- ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; ++- ai->ai_addrlen = sizeof(*sin); ++- ai->ai_addr = (struct sockaddr *)sin; ++- sin->sin_family = AF_INET; ++- sin->sin_port = port; ++- sin->sin_addr.s_addr = nip; ++- ++- releaseresolvsem(); ++- return 0; ++- } ++- ++- hent = gethostbyname((char *)nodename); ++- ++- if (!hent) ++- { ++- int herr = errno; ++- releaseresolvsem(); ++- return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL ++- : EAI_NONAME; ++- } ++- ++- if (!hent->h_addr_list || !hent->h_addr_list[0]) ++- { ++- releaseresolvsem(); ++- return EAI_NONAME; ++- } ++- ++- addrp = hent->h_addr_list; ++- } ++- else ++- { ++- addrp = passive_list; ++- } ++- ++- for (; *addrp; addrp++) ++- { ++- struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); ++- struct sockaddr_in *sin; ++- ++- if (!ai) ++- { ++- releaseresolvsem(); ++- freeaddrinfo(*res); ++- *res = NULL; ++- return EAI_MEMORY; ++- } ++- ++- if (!*res) ++- *res = ai; ++- *tail = ai; ++- tail = &ai->ai_next; ++- ++- sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); ++- ++- if (!sin) ++- { ++- releaseresolvsem(); ++- freeaddrinfo(*res); ++- *res = NULL; ++- return EAI_MEMORY; ++- } ++- ++- ai->ai_family = AF_INET; ++- ai->ai_socktype = proto; ++- ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; ++- ai->ai_addrlen = sizeof(*sin); ++- ai->ai_addr = (struct sockaddr *)sin; ++- sin->sin_family = AF_INET; ++- sin->sin_port = port; ++- ++- if (!hints || !(hints->ai_flags & AI_PASSIVE)) ++- { ++- size_t cpylen = sizeof(sin->sin_addr); ++- ++- if (hent->h_length > 0 && (size_t)hent->h_length < cpylen) ++- cpylen = (size_t)hent->h_length; ++- ++- memcpy(&sin->sin_addr, *addrp, cpylen); ++- } ++- } ++- ++- releaseresolvsem(); ++- return 0; ++-} ++- ++-void freeaddrinfo(struct addrinfo *ai) ++-{ ++- struct addrinfo *next; ++- ++- while (ai) ++- { ++- free(ai->ai_addr); ++- next = ai->ai_next; ++- free(ai); ++- ai = next; ++- } ++-} ++- ++-static const char *ai_errlist[] = ++- { ++- "Success", ++- "hostname nor servname provided, or not known", ++- "Temporary failure in name resolution", ++- "Non-recoverable failure in name resolution", ++- "No address associated with hostname", ++- "ai_family not supported", ++- "ai_socktype not supported", ++- "service name not supported for ai_socktype", ++- "Address family for hostname not supported", ++- "Memory allocation failure", ++- "System error returned in errno", ++- "Unknown error", ++-}; ++- ++-char *gai_strerror(int ecode) ++-{ ++- if (ecode > 0 || ecode < EAI_UNKNOWN) ++- ecode = EAI_UNKNOWN; ++- return (char *)ai_errlist[-ecode]; ++-} ++- ++-#endif /* !HAVE_GETADDRINFO */ ++- ++-#ifndef HAVE_GETNAMEINFO ++- ++-#ifndef NI_DATAGRAM ++-#define NI_DATAGRAM (1 << 4) ++-#endif ++- ++-int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) ++-{ ++- const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; ++- ++- (void)salen; ++- ++- if (sa->sa_family != AF_INET) ++- return EAI_ADDRFAMILY; ++- ++- if (host && hostlen > 0) ++- { ++- if (!(flags & NI_NUMERICHOST)) ++- { ++- struct hostent *he; ++- ++- lockresolvsem(); ++- he = gethostbyaddr((char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET); ++- ++- if (he) ++- { ++- safe_strncpy(host, (char *)he->h_name, hostlen); ++- releaseresolvsem(); ++- } ++- else ++- { ++- int herr = errno; ++- releaseresolvsem(); ++- if (flags & NI_NAMEREQD) ++- return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL ++- : EAI_NONAME; ++- flags |= NI_NUMERICHOST; ++- } ++- } ++- ++- if (flags & NI_NUMERICHOST) ++- { ++- lockhostsem(); ++- safe_strncpy(host, (char *)Inet_NtoA(sin->sin_addr.s_addr), hostlen); ++- releasehostsem(); ++- } ++- } ++- ++- if (serv && servlen > 0) ++- { ++- if (!(flags & NI_NUMERICSERV)) ++- { ++- struct servent *se; ++- ++- lockresolvsem(); ++- ++- se = (flags & NI_DATAGRAM) ? getservbyport(ntohs(sin->sin_port), "udp") : getservbyport(ntohs(sin->sin_port), "tcp"); ++- ++- if (se) ++- { ++- safe_strncpy(serv, (char *)se->s_name, servlen); ++- releaseresolvsem(); ++- } ++- else ++- { ++- releaseresolvsem(); ++- ++- if (flags & NI_NAMEREQD) ++- return EAI_NONAME; ++- ++- flags |= NI_NUMERICSERV; ++- } ++- } ++- ++- if (flags & NI_NUMERICSERV) ++- snprintf(serv, servlen, "%u", ntohs(sin->sin_port)); ++- } ++- ++- return 0; ++-} ++- ++-#endif /* !HAVE_GETNAMEINFO */ ++-#endif /* AMIGA */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/sem.c binkd_pgul/amiga/sem.c ++--- binkd/amiga/sem.c 2026-04-25 19:33:46.416919700 +0100 +++++ binkd_pgul/amiga/sem.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -1,100 +1,29 @@ ++ /* ++ * Amiga semaphores ++ */ ++- ++ #include ++ #include ++-#include ++-#include ++- ++-extern void Log(int lev, char *s, ...); ++- ++-int _InitSem(void *vpSem) ++-{ ++- memset(vpSem, 0, sizeof(struct SignalSemaphore)); ++- InitSemaphore((struct SignalSemaphore *)vpSem); ++- return 0; ++-} ++- ++-int _CleanSem(void *vpSem) ++-{ ++- return 0; ++-} ++- ++-int _LockSem(void *vpSem) ++-{ ++- ObtainSemaphore((struct SignalSemaphore *)vpSem); ++- return 0; ++-} ++- ++-int _ReleaseSem(void *vpSem) ++-{ ++- ReleaseSemaphore((struct SignalSemaphore *)vpSem); ++- return 0; ++-} +++#include ++ ++-int _InitEventSem(EVENTSEM *sem) ++-{ ++- if (!sem) ++- return -1; +++extern void Log (int lev, char *s,...); ++ ++- sem->waiter = NULL; ++ ++- sem->sigbit = AllocSignal(-1); ++- ++- if (sem->sigbit == (ULONG)-1) ++- return -1; ++- ++- return 0; +++int _InitSem(void *vpSem) { +++ memset(vpSem, 0, sizeof (struct SignalSemaphore)); +++ InitSemaphore ((struct SignalSemaphore*)vpSem); +++ return(0); ++ } ++ ++-int _CleanEventSem(EVENTSEM *sem) ++-{ ++- if (!sem) ++- return -1; ++- ++- if (sem->sigbit != (ULONG)-1) ++- { ++- FreeSignal((LONG)sem->sigbit); ++- sem->sigbit = (ULONG)-1; ++- } ++- ++- sem->waiter = NULL; ++- return 0; +++int _CleanSem(void *vpSem) { +++ return (0); ++ } ++ ++-int _PostSem(EVENTSEM *sem) ++-{ ++- if (!sem) ++- return -1; ++- ++- if (sem->waiter && sem->sigbit != (ULONG)-1) ++- { ++- Signal((struct Task *)sem->waiter, 1UL << sem->sigbit); ++- } ++- ++- return 0; +++int _LockSem(void *vpSem) { +++ ObtainSemaphore ((struct SignalSemaphore *)vpSem); +++ return (0); ++ } ++ ++-int _WaitSem(EVENTSEM *sem, int sec) ++-{ ++- ULONG mask; ++- struct Task *me; ++- ++- if (!sem || sem->sigbit == (ULONG)-1) ++- return -1; ++- ++- me = FindTask(NULL); ++- sem->waiter = me; ++- ++- /* Wait on SIGBREAKF_CTRL_C to avoid hanging on race */ ++- mask = Wait((1UL << sem->sigbit) | SIGBREAKF_CTRL_C); ++- ++- sem->waiter = NULL; ++- ++- /* Return timeout/break indication if only CTRL_C fired */ ++- if (!(mask & (1UL << sem->sigbit)) && (mask & SIGBREAKF_CTRL_C)) ++- return -1; ++- ++- return 0; +++int _ReleaseSem(void *vpSem) { +++ ReleaseSemaphore ((struct SignalSemaphore *)vpSem); +++ return (0); ++ } ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/session.c binkd_pgul/amiga/session.c ++--- binkd/amiga/session.c 2026-04-26 11:57:30.521108284 +0100 +++++ binkd_pgul/amiga/session.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,462 +0,0 @@ ++-/* ++- * session.c -- session management for AmigaOS 3 binkd ++- * ++- * session.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include ++-#include ++- ++-#include ++-#include ++-#include ++-#include ++- ++-#include "sys.h" ++-#include "iphdr.h" ++-#include "readcfg.h" ++-#include "common.h" ++-#include "tools.h" ++-#include "client.h" ++-#include "protocol.h" ++-#include "ftnq.h" ++-#include "ftnnode.h" ++-#include "ftnaddr.h" ++-#include "bsy.h" ++-#include "iptools.h" ++-#include "rfc2553.h" ++-#include "srv_gai.h" ++-#include "amiga/bsdsock.h" ++-#include "amiga/evloop_int.h" ++-#include "amiga/proto_amiga.h" ++- ++-extern int binkd_exit; ++-extern int ext_rand; ++-extern int client_flag; ++-extern int poll_flag; ++- ++-/* Session table */ ++-int sess_alloc(void) ++-{ ++- int i; ++- ++- for (i = 0; i < max_sessions; i++) ++- { ++- if (sessions[i].phase == SESS_FREE) ++- { ++- memset(&sessions[i], 0, sizeof(sess_t)); ++- sessions[i].fd = INVALID_SOCKET; ++- sessions[i].phase = SESS_FREE; ++- return i; ++- } ++- } ++- ++- return -1; ++-} ++- ++-void sess_free(int idx) ++-{ ++- sess_t *s = &sessions[idx]; ++- ++- if (s->fd != INVALID_SOCKET) ++- { ++- soclose(s->fd); ++- s->fd = INVALID_SOCKET; ++- } ++- ++- if (s->ai_head) ++- { ++- freeaddrinfo(s->ai_head); ++- s->ai_head = NULL; ++- } ++- ++- memset(&s->state, 0, sizeof(STATE)); ++- s->phase = SESS_FREE; ++-} ++- ++-/* Inbound: accept a new connection */ ++-void do_accept(SOCKET lfd, BINKD_CONFIG *config) ++-{ ++- struct sockaddr_storage sa; ++- socklen_t salen = (socklen_t)sizeof(sa); ++- SOCKET fd; ++- int idx; ++- sess_t *s; ++- char host[BINKD_FQDNLEN + 1]; ++- char ip[BINKD_FQDNLEN + 1]; ++- ++- fd = accept(lfd, (struct sockaddr *)&sa, &salen); ++- ++- if (fd == INVALID_SOCKET) ++- { ++- if (TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN) ++- Log(1, "accept(): %s", TCPERR()); ++- ++- return; ++- } ++- ++- if (binkd_exit) ++- { ++- soclose(fd); ++- return; ++- } ++- ++- idx = sess_alloc(); ++- ++- if (idx < 0) ++- { ++- Log(1, "session table full, refusing inbound"); ++- soclose(fd); ++- return; ++- } ++- ++- /* getnameinfo() Is unreliable on AmiTCP: use inet_ntoa directly */ ++- if (((struct sockaddr *)&sa)->sa_family == AF_INET) ++- { ++- struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; ++- strnzcpy(ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); ++- } ++- else ++- { ++- strnzcpy(ip, "unknown", BINKD_FQDNLEN); ++- } ++- ++- /* Backresolv not supported on AmiTCP: always use IP as host */ ++- strnzcpy(host, ip, BINKD_FQDNLEN); ++- ++- set_nonblock(fd); ++- ++- s = &sessions[idx]; ++- s->fd = fd; ++- s->inbound = 1; ++- s->node = NULL; ++- s->ai_head = NULL; ++- s->last_io = time(NULL); ++- strnzcpy(s->host, host, BINKD_FQDNLEN); ++- strnzcpy(s->ip, ip, BINKD_FQDNLEN); ++- s->port[0] = '\0'; ++- ++- if (amiga_proto_open(&s->state, fd, NULL, NULL, s->host, NULL, s->ip, config) != 0) ++- { ++- Log(1, "proto_open failed for %s", ip); ++- sess_free(idx); ++- return; ++- } ++- ++- s->phase = SESS_RUNNING; ++- n_servers++; ++- Log(4, "inbound slot[%d] from %s", idx, ip); ++-} ++- ++-/* Outbound: Non-blocking connect() */ ++-int start_connect(sess_t *s, BINKD_CONFIG *config) ++-{ ++- SOCKET fd; ++- int rc; ++- ++- s->ip[0] = '\0'; ++- s->port[0] = '\0'; ++- ++- fd = socket(s->ai_cur->ai_family, s->ai_cur->ai_socktype, s->ai_cur->ai_protocol); ++- ++- if (fd == INVALID_SOCKET) ++- { ++- Log(1, "outbound socket(): %s", TCPERR()); ++- return -1; ++- } ++- ++- /* getnameinfo() is unreliable on AmiTCP: may return rc=0 with garbage ++- * Use inet_ntoa/ntohs directly */ ++- if (s->ai_cur->ai_family == AF_INET) ++- { ++- struct sockaddr_in *sa4 = (struct sockaddr_in *)s->ai_cur->ai_addr; ++- strnzcpy(s->ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); ++- snprintf(s->port, MAXPORTSTRLEN, "%u", (unsigned)ntohs(sa4->sin_port)); ++- } ++- else ++- { ++- strnzcpy(s->ip, "unknown", BINKD_FQDNLEN); ++- strnzcpy(s->port, "0", MAXPORTSTRLEN); ++- } ++- ++- Log(4, "connecting %s [%s]:%s", s->host, s->ip, s->port); ++- ++- if (config->bindaddr[0]) ++- { ++- struct addrinfo src_h, *src_ai; ++- memset(&src_h, 0, sizeof(src_h)); ++- src_h.ai_family = s->ai_cur->ai_family; ++- src_h.ai_socktype = SOCK_STREAM; ++- src_h.ai_protocol = IPPROTO_TCP; ++- ++- if (getaddrinfo(config->bindaddr, NULL, &src_h, &src_ai) == 0) ++- { ++- bind(fd, src_ai->ai_addr, (int)src_ai->ai_addrlen); ++- freeaddrinfo(src_ai); ++- } ++- } ++- ++- set_nonblock(fd); ++- ++- rc = connect(fd, s->ai_cur->ai_addr, (int)s->ai_cur->ai_addrlen); ++- ++- if (rc == 0 || TCPERRNO == EINPROGRESS || TCPERRNO == EWOULDBLOCK) ++- { ++- s->fd = fd; ++- s->conn_start = time(NULL); ++- return 0; ++- } ++- ++- Log(1, "connect %s: %s", s->host, TCPERR()); ++- ++- bad_try(&s->node->fa, TCPERR(), BAD_CALL, config); ++- soclose(fd); ++- ++- return -1; ++-} ++- ++-int try_outbound(BINKD_CONFIG *config) ++-{ ++- FTN_NODE *node; ++- sess_t *s; ++- int idx, rc; ++- struct addrinfo hints; ++- char dest[FTN_ADDR_SZ + 1]; ++- char host[BINKD_FQDNLEN + 5 + 1]; ++- char port[MAXPORTSTRLEN + 1]; ++- ++- if (!client_flag) ++- return 0; ++- ++- if (!config->q_present) ++- { ++- q_free(SCAN_LISTED, config); ++- ++- if (config->printq) ++- Log(-1, "scan\r"); ++- ++- q_scan(SCAN_LISTED, config); ++- config->q_present = 1; ++- ++- if (config->printq) ++- { ++- q_list(stderr, SCAN_LISTED, config); ++- Log(-1, "idle\r"); ++- } ++- } ++- ++- if (n_clients >= config->max_clients) ++- return 0; ++- ++- node = q_next_node(config); ++- ++- if (!node) ++- return 0; ++- ++- ftnaddress_to_str(dest, &node->fa); ++- ++- if (!bsy_test(&node->fa, F_BSY, config) || !bsy_test(&node->fa, F_CSY, config)) ++- { ++- Log(4, "%s busy", dest); ++- return 0; ++- } ++- ++- idx = sess_alloc(); ++- ++- if (idx < 0) ++- { ++- Log(2, "table full, deferring %s", dest); ++- return 0; ++- } ++- ++- s = &sessions[idx]; ++- memset(s, 0, sizeof(*s)); ++- s->fd = INVALID_SOCKET; ++- s->node = node; ++- s->inbound = 0; ++- ++- rc = get_host_and_port(1, host, port, node->hosts, &node->fa, config); ++- ++- if (rc <= 0) ++- { ++- Log(1, "%s: bad host list", dest); ++- sess_free(idx); ++- ++- return 0; ++- } ++- ++- strnzcpy(s->host, host, BINKD_FQDNLEN); ++- strnzcpy(s->port, port, MAXPORTSTRLEN); ++- ++- memset(&hints, 0, sizeof(hints)); ++- hints.ai_family = node->IP_afamily; ++- hints.ai_socktype = SOCK_STREAM; ++- hints.ai_protocol = IPPROTO_TCP; ++- ++- rc = srv_getaddrinfo(host, port, &hints, &s->ai_head); ++- ++- if (rc != 0) ++- { ++- Log(1, "%s: getaddrinfo error code=%d: %s", dest, rc, gai_strerror(rc)); ++- ++- bad_try(&node->fa, "getaddrinfo failed", BAD_CALL, config); ++- sess_free(idx); ++- ++- return 0; ++- } ++- ++- s->ai_cur = s->ai_head; ++- ++- if (start_connect(s, config) != 0) ++- { ++- sess_free(idx); ++- return 0; ++- } ++- ++- s->phase = SESS_CONNECTING; ++- n_clients++; ++- ++- Log(4, "outbound slot[%d] -> %s", idx, dest); ++- ++- return 1; ++-} ++- ++-/* Check completion of async connect() */ ++-void check_connect(int idx, BINKD_CONFIG *config) ++-{ ++- sess_t *s = &sessions[idx]; ++- int err = 0; ++- socklen_t el = (socklen_t)sizeof(err); ++- int tmo; ++- ++- tmo = config->connect_timeout ? config->connect_timeout : 30; ++- ++- if ((int)(time(NULL) - s->conn_start) >= tmo) ++- { ++- Log(1, "connect timeout -> %s", s->host); ++- ++- bad_try(&s->node->fa, "Timeout", BAD_CALL, config); ++- n_clients--; ++- sess_free(idx); ++- ++- return; ++- } ++- ++- getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &el); ++- ++- if (err) ++- { ++- Log(1, "connect -> %s: %s", s->host, strerror(err)); ++- ++- bad_try(&s->node->fa, strerror(err), BAD_CALL, config); ++- ++- soclose(s->fd); ++- s->fd = INVALID_SOCKET; ++- s->ai_cur = s->ai_cur->ai_next; ++- ++- if (s->ai_cur && start_connect(s, config) == 0) ++- return; /* trying next address */ ++- ++- n_clients--; ++- sess_free(idx); ++- ++- return; ++- } ++- ++- Log(4, "connected -> %s [%s]", s->host, s->ip); ++- ext_rand = rand(); ++- ++- if (amiga_proto_open(&s->state, s->fd, s->node, NULL, s->host, s->port, s->ip, config) != 0) ++- { ++- Log(1, "proto_open failed for %s", s->host); ++- ++- n_clients--; ++- sess_free(idx); ++- return; ++- } ++- ++- s->phase = SESS_RUNNING; ++- s->last_io = time(NULL); ++-} ++- ++-/* Run one protocol step on an active session */ ++-void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config) ++-{ ++- sess_t *s = &sessions[idx]; ++- int rc; ++- int tmo; ++- ++- tmo = config->nettimeout ? config->nettimeout : 300; ++- ++- if ((int)(time(NULL) - s->last_io) >= tmo) ++- { ++- Log(1, "slot[%d] net timeout", idx); ++- ++- if (s->node) ++- bad_try(&s->node->fa, "Timeout", BAD_IO, config); ++- ++- amiga_proto_close(&s->state, config, 0); ++- ++- if (s->inbound) ++- n_servers--; ++- else ++- n_clients--; ++- ++- sess_free(idx); ++- ++- return; ++- } ++- ++- if (s->fd == INVALID_SOCKET) ++- { ++- Log(1, "slot[%d] invalid socket, closing session", idx); ++- ++- if (s->node) ++- bad_try(&s->node->fa, "Invalid socket", BAD_IO, config); ++- ++- if (s->inbound) ++- n_servers--; ++- else ++- n_clients--; ++- ++- sess_free(idx); ++- ++- return; ++- } ++- ++- /* WaitSelect() may not report a readable socket when the remote has ++- * sent a TCP END. Probe with MSG_PEEK so recv_block() sees the EOF */ ++- if (!rd && !wr && s->state.state != P_NULL) ++- { ++- char peek; ++- int pr = recv(s->fd, &peek, 1, MSG_PEEK); ++- ++- if (pr == 0 || (pr < 0 && TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN)) ++- rd = 1; ++- } ++- ++- rc = amiga_proto_step(&s->state, rd, wr, config); ++- ++- if (rd || wr) ++- s->last_io = time(NULL); ++- ++- if (rc == APROTO_RUNNING) ++- return; ++- ++- amiga_proto_close(&s->state, config, rc == APROTO_DONE_OK); ++- ++- Log(4, "slot[%d] %s", idx, rc == APROTO_DONE_OK ? "OK" : "ERR"); ++- ++- if (s->inbound) ++- n_servers--; ++- else ++- n_clients--; ++- ++- sess_free(idx); ++- ++- if (poll_flag && n_clients == 0 && n_servers == 0) ++- binkd_exit = 1; ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/sock.c binkd_pgul/amiga/sock.c ++--- binkd/amiga/sock.c 2026-04-26 11:59:26.645813693 +0100 +++++ binkd_pgul/amiga/sock.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,130 +0,0 @@ ++-/* ++- * sock.c -- listen socket management for AmigaOS 3 ++- * ++- * sock.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include ++-#include ++- ++-#include ++-#include ++- ++-#include "sys.h" ++-#include "readcfg.h" ++-#include "tools.h" ++-#include "server.h" ++-#include "rfc2553.h" ++-#include "amiga/bsdsock.h" ++-#include "amiga/evloop_int.h" ++- ++-extern SOCKET sockfd[]; ++-extern int sockfd_used; ++-extern int server_flag; ++- ++-void set_nonblock(SOCKET fd) ++-{ ++- long flag = 1L; ++- ++- if (IoctlSocket(fd, FIONBIO, (char *)&flag) != 0) ++- Log(2, "IoctlSocket(FIONBIO) failed: %s", TCPERR()); ++-} ++- ++-int open_listen_sockets(BINKD_CONFIG *config) ++-{ ++- struct listenchain *ll; ++- struct addrinfo hints, *ai, *head; ++- int err, opt = 1; ++- ++- memset(&hints, 0, sizeof(hints)); ++- hints.ai_flags = AI_PASSIVE; ++- hints.ai_family = PF_UNSPEC; ++- hints.ai_socktype = SOCK_STREAM; ++- hints.ai_protocol = IPPROTO_TCP; ++- ++- sockfd_used = 0; ++- ++- for (ll = config->listen.first; ll; ll = ll->next) ++- { ++- err = getaddrinfo(ll->addr[0] ? ll->addr : NULL, ll->port, &hints, &head); ++- ++- if (err) ++- { ++- Log(1, "listen getaddrinfo(%s:%s): %s", ll->addr[0] ? ll->addr : "*", ll->port, gai_strerror(err)); ++- return -1; ++- } ++- ++- for (ai = head; ai && sockfd_used < MAX_LISTENSOCK; ai = ai->ai_next) ++- { ++- SOCKET fd; ++- int retries = 6; ++- ++- fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); ++- ++- if (fd == INVALID_SOCKET) ++- { ++- Log(1, "listen socket(): %s", TCPERR()); ++- continue; ++- } ++- ++- if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, (int)sizeof(opt)) != 0) ++- Log(2, "setsockopt(SO_REUSEADDR) failed: %s", TCPERR()); ++- ++- /* Bsdsocket may hold the port briefly after socket close */ ++- while (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) != 0) ++- { ++- if (--retries == 0) ++- { ++- Log(1, "listen bind(): %s", TCPERR()); ++- ++- soclose(fd); ++- freeaddrinfo(head); ++- return -1; ++- } ++- ++- Log(2, "bind retry in 2s: %s", TCPERR()); ++- ++- Delay(100UL); /* 100 ticks = 2s @ 50Hz */ ++- } ++- ++- if (listen(fd, 5) != 0) ++- { ++- Log(1, "listen(): %s", TCPERR()); ++- ++- soclose(fd); ++- freeaddrinfo(head); ++- return -1; ++- } ++- ++- set_nonblock(fd); ++- sockfd[sockfd_used] = fd; ++- sockfd_used++; ++- } ++- ++- freeaddrinfo(head); ++- ++- Log(3, "listening on %s:%s", ++- ll->addr[0] ? ll->addr : "*", ll->port); ++- } ++- ++- if (sockfd_used == 0 && server_flag) ++- { ++- Log(1, "evloop: no listen sockets opened"); ++- return -1; ++- } ++- ++- return 0; ++-} ++- ++-void close_listen_sockets(void) ++-{ ++- int i; ++- ++- for (i = 0; i < sockfd_used; i++) ++- soclose(sockfd[i]); ++- ++- sockfd_used = 0; ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/utime.c binkd_pgul/amiga/utime.c ++--- binkd/amiga/utime.c 2026-04-26 11:59:41.408046130 +0100 +++++ binkd_pgul/amiga/utime.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,77 +0,0 @@ ++-/* ++- * utime.c -- utime() stub for AmigaOS 3 without ixemul ++- * ++- * utime.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifdef AMIGA ++- ++-#include ++-#include ++-#include ++-#include ++- ++-#include "amiga/dirent.h" /* declares struct utimbuf and utime() prototype */ ++- ++-/* Days between AmigaDOS epoch (1978-01-01) and POSIX epoch (1970-01-01) */ ++-#define AMIGA_EPOCH_DELTA_DAYS 2922UL ++-#define SECONDS_PER_DAY 86400UL ++-#define SECONDS_PER_MINUTE 60UL ++- ++-int utime(const char *path, const struct utimbuf *times) ++-{ ++- struct DateStamp ds; ++- LONG seconds_today; ++- LONG total_seconds; ++- ++- if (!path) ++- { ++- errno = EINVAL; ++- return -1; ++- } ++- ++- if (!times) ++- { ++- /* Use current time */ ++- DateStamp(&ds); ++- } ++- else ++- { ++- LONG t = (LONG)times->modtime; ++- ++- if (t < (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY)) ++- { ++- /* Time predates AmigaDOS epoch -- clamp to epoch */ ++- t = 0; ++- } ++- else ++- { ++- t -= (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY); ++- } ++- ++- ds.ds_Days = (LONG)(t / (LONG)SECONDS_PER_DAY); ++- total_seconds = t % (LONG)SECONDS_PER_DAY; ++- ds.ds_Minute = (LONG)(total_seconds / (LONG)SECONDS_PER_MINUTE); ++- seconds_today = total_seconds % (LONG)SECONDS_PER_MINUTE; ++- ds.ds_Tick = seconds_today * (LONG)TICKS_PER_SECOND; ++- } ++- ++- if (!SetFileDate((STRPTR)path, &ds)) ++- { ++- /* Most likely cause: file does not exist or is write-protected */ ++- LONG err = IoErr(); ++- ++- if (err == ERROR_OBJECT_NOT_FOUND || err == ERROR_DIR_NOT_FOUND) ++- errno = ENOENT; ++- else ++- errno = EACCES; ++- return -1; ++- } ++- ++- return 0; ++-} ++- ++-#endif /* AMIGA */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/binkd.c binkd_pgul/binkd.c ++--- binkd/binkd.c 2026-04-26 13:20:44.986212073 +0100 +++++ binkd_pgul/binkd.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -54,10 +54,6 @@ ++ #include "unix/daemonize.h" ++ #endif ++ ++-#ifdef AMIGA ++-/* amiga/bsdsock.h pulled in via iphdr.h for AMIGA */ ++-#include "amiga/evloop.h" ++-#endif ++ #ifdef WIN32 ++ #include "nt/service.h" ++ #include "nt/w32tools.h" ++@@ -66,11 +62,9 @@ ++ #endif ++ #endif ++ ++-#include "bsycleanup.h" ++- ++ #include "confopt.h" ++ ++-#if defined(HAVE_THREADS) || defined(AMIGA) +++#ifdef HAVE_THREADS ++ MUTEXSEM hostsem; ++ MUTEXSEM resolvsem; ++ MUTEXSEM lsem; ++@@ -100,13 +94,9 @@ ++ char *configpath = NULL; /* Config file name */ ++ char **saved_envp; ++ ++-/* mypid: needed by HAVE_FORK and AMIGA targets */ ++-#if defined(HAVE_FORK) || defined(AMIGA) ++-int mypid; ++-#endif ++- ++ #ifdef HAVE_FORK ++-int got_sighup, got_sigchld; +++ +++int mypid, got_sighup, got_sigchld; ++ ++ void chld (int *childcount) ++ { ++@@ -205,13 +195,9 @@ ++ #endif ++ " -C reload on config change\n" ++ " -c run client only\n" ++-#ifndef AMIGA ++ " -i run server on stdin/stdout pipe (by inetd or other)\n" ++-#endif ++ " -f node run server protected session with this node\n" ++-#ifndef AMIGA ++ " -a ip assume remote address when running with '-i' switch\n" ++-#endif ++ #if defined(BINKD9X) ++ " -t cmd (start|stop|restart|status|install|uninstall) service(s)\n" ++ " -S name set Win9x service name, all - use all services\n" ++@@ -326,14 +312,12 @@ ++ case 'c': ++ client_flag = 1; ++ break; ++-#ifndef AMIGA ++ case 'i': ++ inetd_flag = 1; ++ break; ++ case 'a': /* remote IP address */ ++ remote_addr = strdup(optarg); ++ break; ++-#endif ++ case 'f': /* remote FTN address */ ++ remote_node = strdup(optarg); ++ break; ++@@ -432,28 +416,6 @@ ++ } ++ if (optind\n", extract_filename(argv[0])); ++- fprintf(stderr, " Use -P
for polling a specific node (e.g., -P 1:23/456.7)\n"); ++- exit(1); ++- } ++- ++- /* Check for leftover FTN addresses in extra arguments */ ++- while (optind < argc) ++- { ++- char *extra = argv[optind++]; ++- if (strchr(extra, ':') || strchr(extra, '@')) ++- { ++- fprintf(stderr, "%s: Error: Unexpected FTN address '%s' in arguments.\n", extract_filename(argv[0]), extra); ++- fprintf(stderr, " Use -P
before the config file to poll a node.\n"); ++- exit(1); ++- } ++- } ++- ++ #ifdef OS2 ++ if (optindloglevel, current_config->conlog, ++ current_config->logpath, current_config->nolog.first); ++- ++- /* Clean up old .bsy/.csy files at startup */ ++- cleanup_old_bsy(current_config); ++ } ++ else if (verbose_flag) ++ { ++@@ -706,13 +665,9 @@ ++ ++ if (p) ++ { ++- remote_addr = strdup(p); ++- /* Guard against null pointer dereference if strdup fails */ ++- if (remote_addr) ++- { ++- p = strchr(remote_addr, ' '); ++- if (p) *p = '\0'; ++- } +++ remote_addr = strdup(p); +++ p = strchr(remote_addr, ' '); +++ if (p) *p = '\0'; ++ } ++ } ++ /* not using stdin/stdout itself to avoid possible collisions */ ++@@ -722,9 +677,6 @@ ++ inetd_socket_out = dup(fileno(stdout)); ++ #ifdef UNIX ++ tempfd = open("/dev/null", O_RDWR); ++-#elif defined(AMIGA) ++- /* NIL: is the native AmigaDOS null device (no ixemul) */ ++- tempfd = open("NIL:", O_RDWR); ++ #else ++ tempfd = open("nul", O_RDWR); ++ #endif ++@@ -755,15 +707,6 @@ ++ signal (SIGHUP, sighandler); ++ #endif ++ ++-#ifdef AMIGA ++- /* AmigaOS 3: WaitSelect() loop, no fork/threads */ ++- { ++- BINKD_CONFIG *ev_cfg = lock_current_config(); ++- amiga_evloop_run(ev_cfg, server_flag, client_flag); ++- unlock_config_structure(ev_cfg, 0); ++- return 0; ++- } ++-#else ++ if (client_flag && !server_flag) ++ { ++ clientmgr (0); ++@@ -778,9 +721,7 @@ ++ if (client_flag && (pidcmgr = branch (clientmgr, 0, 0)) < 0) ++ { ++ Log (0, "cannot branch out"); ++- exit (1); ++ } ++-#endif /* !AMIGA */ ++ ++ if (*current_config->pid_file) ++ { ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/binlog.c binkd_pgul/binlog.c ++--- binkd/binlog.c 2026-04-22 06:50:46.000000000 +0100 +++++ binkd_pgul/binlog.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -35,10 +35,6 @@ ++ #include "tools.h" ++ #include "sem.h" ++ ++-#if defined(HAVE_THREADS) || defined(AMIGA) ++-extern MUTEXSEM blsem; ++-#endif ++- ++ /* Write 16-bit integer to file in intel bytes order */ ++ static int fput16(u16 arg, FILE *file) ++ { ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/branch.c binkd_pgul/branch.c ++--- binkd/branch.c 2026-04-26 09:12:44.314385608 +0100 +++++ binkd_pgul/branch.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -20,6 +20,12 @@ ++ #include "tools.h" ++ #include "sem.h" ++ +++#ifdef AMIGA +++int ix_vfork (void); +++void vfork_setup_child (void); +++void ix_vfork_resume (void); +++#endif +++ ++ #ifdef WITH_PTHREADS ++ typedef struct { ++ void (*F) (void *); ++@@ -126,6 +132,23 @@ ++ #endif ++ #endif ++ +++#ifdef AMIGA +++ /* this is rather bizzare. this function pretends to be a fork and behaves +++ * like one, but actually it's a kind of a thread. so we'll need semaphores */ +++ +++ if (!(rc = ix_vfork ())) +++ { +++ vfork_setup_child (); +++ ix_vfork_resume (); +++ F (arg); +++ exit (0); +++ } +++ else if (rc < 0) +++ { +++ Log (1, "ix_vfork: %s", strerror (errno)); +++ } +++#endif +++ ++ #if defined(DOS) || defined(DEBUGCHILD) ++ rc = 0; ++ F (arg); ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/breaksig.c binkd_pgul/breaksig.c ++--- binkd/breaksig.c 2026-04-26 10:25:19.576809578 +0100 +++++ binkd_pgul/breaksig.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -46,16 +46,6 @@ ++ { ++ atexit (exitfunc); ++ ++-#ifdef AMIGA ++- /* AmigaOS / libnix: signal() maps SIGINT -> SIGBREAKF_CTRL_C ++- * Register exitsig() so that Ctrl+C sets binkd_exit=1 even when ++- * the process is NOT blocked inside WaitSelect() (e.g. during disk ++- * I/O). When inside WaitSelect(), amiga_select_wrap() in bsdsock.h ++- * detects the break and sets binkd_exit=1 directly without going ++- * through this signal handler. */ ++- signal (SIGINT, exitsig); ++- signal (SIGTERM, exitsig); ++-#else ++ #ifdef SIGBREAK ++ signal (SIGBREAK, exitsig); ++ #endif ++@@ -68,6 +58,5 @@ ++ #ifdef SIGTERM ++ signal (SIGTERM, exitsig); ++ #endif ++-#endif /* AMIGA */ ++ return 1; ++ } ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/bsycleanup.c binkd_pgul/bsycleanup.c ++--- binkd/bsycleanup.c 2026-04-26 11:01:23.269049076 +0100 +++++ binkd_pgul/bsycleanup.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,122 +0,0 @@ ++-/* ++- * bsycleanup.c -- Cleanup functions for .bsy/.csy/.try files ++- * ++- * bsycleanup.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include ++-#include ++-#include ++-#include ++-#include ++- ++-#include "sys.h" ++-#include "readcfg.h" ++-#include "ftnq.h" ++-#include "ftnnode.h" ++-#include "tools.h" ++-#include "readdir.h" ++- ++-/* ++- * Clean up old .bsy and .csy files at startup ++- * Scans all domain outbounds ++- */ ++- ++-/* Helper: scan a single directory for .bsy/.csy files and delete them */ ++-static void scan_and_delete_bsy_in_dir(const char *dir, BINKD_CONFIG *config) ++-{ ++- DIR *dp; ++- struct dirent *de; ++- char buf[MAXPATHLEN + 1]; ++- ++- if ((dp = opendir(dir)) == 0) ++- { ++- return; ++- } ++- ++- while ((de = readdir(dp)) != 0) ++- { ++- char *s = de->d_name; ++- int len = strlen(s); ++- ++- if (len > 4 && (!STRICMP(s + len - 4, ".bsy") || !STRICMP(s + len - 4, ".csy") || !STRICMP(s + len - 4, ".try"))) ++- { ++- strnzcpy(buf, dir, sizeof(buf)); ++- strnzcat(buf, PATH_SEPARATOR, sizeof(buf)); ++- strnzcat(buf, s, sizeof(buf)); ++- ++- Log(2, "deleting %s", buf); ++- ++- delete(buf); ++- } ++- } ++- ++- closedir(dp); ++-} ++- ++-void cleanup_old_bsy(BINKD_CONFIG *config) ++-{ ++- DIR *dp; ++- char outb_path[MAXPATHLEN + 1], base_path[MAXPATHLEN + 1]; ++- struct dirent *de; ++- FTN_DOMAIN *curr_domain; ++- int len; ++- ++- Log(2, "cleaning up .bsy/.csy/.try files at startup"); ++- ++- /* Scan all domain outbounds */ ++- for (curr_domain = config->pDomains.first; curr_domain; curr_domain = curr_domain->next) ++- { ++- if (curr_domain->alias4 != 0) ++- continue; ++- ++- /* Build base path: path + separator */ ++- strnzcpy(base_path, curr_domain->path, sizeof(base_path)); ++-#ifndef AMIGA ++- if (base_path[strlen(base_path) - 1] == ':') ++- strcat(base_path, PATH_SEPARATOR); ++-#endif ++- ++-#ifdef AMIGADOS_4D_OUTBOUND ++- if (config->aso) ++- { ++- /* ASO mode: direct outbound path */ ++- strnzcpy(outb_path, base_path, sizeof(outb_path)); ++- strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); ++- strnzcat(outb_path, curr_domain->dir, sizeof(outb_path)); ++- Log(7, "cleanup_old_bsy (ASO): scanning domain '%s', path '%s'", curr_domain->name, outb_path); ++- scan_and_delete_bsy_in_dir(outb_path, config); ++- } ++- else ++-#endif ++- { ++- /* BSO mode: scan for outbound.xxx directories */ ++- Log(7, "cleanup_old_bsy (BSO): scanning domain '%s', base '%s'", curr_domain->name, base_path); ++- ++- if ((dp = opendir(base_path)) == 0) ++- continue; ++- ++- len = strlen(curr_domain->dir); ++- ++- while ((de = readdir(dp)) != 0) ++- { ++- /* Match outbound or outbound.xxx */ ++- if (!STRNICMP(de->d_name, curr_domain->dir, len) && (de->d_name[len] == 0 || (de->d_name[len] == '.' && isxdigit(de->d_name[len + 1])))) ++- { ++- strnzcpy(outb_path, base_path, sizeof(outb_path)); ++- strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); ++- strnzcat(outb_path, de->d_name, sizeof(outb_path)); ++- ++- Log(7, "cleanup_old_bsy (BSO): scanning outbound dir '%s'", outb_path); ++- ++- scan_and_delete_bsy_in_dir(outb_path, config); ++- } ++- } ++- ++- closedir(dp); ++- } ++- } ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/bsycleanup.h binkd_pgul/bsycleanup.h ++--- binkd/bsycleanup.h 2026-04-26 13:11:39.607856201 +0100 +++++ binkd_pgul/bsycleanup.h 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,19 +0,0 @@ ++-/* ++- * bsycleanup.h -- Cleanup functions for .bsy/.csy/.try files ++- * ++- * bsycleanup.h is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#ifndef _BSYCLEANUP_H ++-#define _BSYCLEANUP_H ++- ++-#include "readcfg.h" ++- ++- ++-/* cleanup_old_bsy -- Clean up old .bsy/.csy/.try files at startup */ ++-void cleanup_old_bsy(BINKD_CONFIG *config); ++- ++-#endif /* _BSYCLEANUP_H */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/btypes.h binkd_pgul/btypes.h ++--- binkd/btypes.h 2026-04-25 20:39:58.604145925 +0100 +++++ binkd_pgul/btypes.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -73,7 +73,6 @@ ++ int HC_flag; ++ int restrictIP; ++ int NP_flag; /* no proxy */ ++- int NC_flag; /* no compression */ ++ ++ time_t hold_until; ++ int busy; /* 0=free, 'c'=.csy, other=.bsy */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/client.c binkd_pgul/client.c ++--- binkd/client.c 2026-04-26 10:25:03.436098652 +0100 +++++ binkd_pgul/client.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -19,10 +19,6 @@ ++ #include ++ #endif ++ ++-#ifdef AMIGA ++-#include "amiga/bsdsock.h" ++-#endif ++- ++ #include "sys.h" ++ #include "readcfg.h" ++ #include "client.h" ++@@ -48,11 +44,6 @@ ++ #include "rfc2553.h" ++ #include "srv_gai.h" ++ ++-#if defined(HAVE_THREADS) || defined(AMIGA) ++-extern MUTEXSEM lsem; ++-extern EVENTSEM eothread; ++-#endif ++- ++ static void call (void *arg); ++ ++ int n_clients = 0; ++@@ -211,8 +202,7 @@ ++ /* This sleep can be interrupted by signal, it's OK */ ++ unblocksig(); ++ check_child(&n_clients); ++- if (!config->no_call_delay) ++- SLEEP (config->call_delay); +++ SLEEP (config->call_delay); ++ check_child(&n_clients); ++ blocksig(); ++ } ++@@ -292,16 +282,8 @@ ++ #ifdef HAVE_THREADS ++ !server_flag && ++ #endif ++- /* AmigaOS uses shared-memory evloop — only main process calls checkcfg() ++- * On fork systems (Linux/FreeBSD) each process has separate memory ++- * so independent reloads are safe. */ ++ !poll_flag) ++-#ifndef AMIGA ++ checkcfg(); ++-#else ++- { ++- } ++-#endif ++ } ++ ++ Log (5, "downing clientmgr..."); ++@@ -324,7 +306,7 @@ ++ exit (0); ++ } ++ ++-int call0 (FTN_NODE *node, BINKD_CONFIG *config) +++static int call0 (FTN_NODE *node, BINKD_CONFIG *config) ++ { ++ int sockfd = INVALID_SOCKET; ++ int sock_out; ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/client.h binkd_pgul/client.h ++--- binkd/client.h 2026-04-26 10:24:36.096878628 +0100 +++++ binkd_pgul/client.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -1,20 +1,9 @@ ++ #ifndef _client_h ++ #define _client_h ++ ++-#ifdef AMIGA ++-#include ++-#include ++-#include ++-#endif ++- ++ /* ++ * Scans queue, makes outbound ``call'', than calls protocol() ++ */ ++ void clientmgr(void *arg); ++ ++-#ifdef AMIGA ++-/* Direct outbound call for evloop.c (no-ixemul, no-threads build) */ ++-int call0(FTN_NODE *node, BINKD_CONFIG *config); ++-#endif ++- ++ #endif ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/Config.h binkd_pgul/Config.h ++--- binkd/Config.h 2026-04-25 19:53:34.520405599 +0100 +++++ binkd_pgul/Config.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -14,8 +14,8 @@ ++ #ifndef _Config_h ++ #define _Config_h ++ ++-#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) + defined(AMIGA) == 0 ++-#error You must define HAVE_FORK, HAVE_THREADS, DOS, or AMIGA! +++#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) == 0 +++#error You must define either HAVE_FORK or HAVE_THREADS! ++ #endif ++ ++ #ifdef __WATCOMC__ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/exitproc.c binkd_pgul/exitproc.c ++--- binkd/exitproc.c 2026-04-26 10:21:07.372917687 +0100 +++++ binkd_pgul/exitproc.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -32,17 +32,6 @@ ++ #include "nt/w32tools.h" ++ #endif ++ ++-#if defined(HAVE_THREADS) || defined(AMIGA) ++-extern MUTEXSEM hostsem; ++-extern MUTEXSEM resolvsem; ++-extern MUTEXSEM lsem; ++-extern MUTEXSEM blsem; ++-extern MUTEXSEM varsem; ++-extern MUTEXSEM config_sem; ++-extern EVENTSEM eothread; ++-extern EVENTSEM wakecmgr; ++-#endif ++- ++ int binkd_exit; ++ ++ #ifdef HAVE_THREADS ++@@ -149,33 +138,6 @@ ++ close_srvmgr_socket(); ++ #endif ++ ++-#ifdef AMIGA ++- /* evloop: single process, no children ++- * Clean Exec semaphores in safe order before freeing config */ ++- close_srvmgr_socket(); ++- CleanEventSem(&wakecmgr); ++- CleanEventSem(&eothread); ++- CleanSem(&varsem); ++- CleanSem(&blsem); ++- CleanSem(&lsem); ++- CleanSem(&resolvsem); ++- CleanSem(&hostsem); ++- CleanSem(&config_sem); ++- sock_deinit(); ++- nodes_deinit(); ++- { ++- BINKD_CONFIG *cfg = lock_current_config(); ++- if (cfg) ++- bsy_remove_all(cfg); ++- if (cfg && *cfg->pid_file && pidsmgr == (int)getpid()) ++- delete(cfg->pid_file); ++- if (cfg) ++- unlock_config_structure(cfg, 1); ++- } ++- Log(6, "exitfunc: AmigaOS cleanup done"); ++- return; ++-#endif /* AMIGA */ ++- ++ config = lock_current_config(); ++ if (config) ++ bsy_remove_all (config); ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnnode.c binkd_pgul/ftnnode.c ++--- binkd/ftnnode.c 2026-04-26 09:22:13.159382256 +0100 +++++ binkd_pgul/ftnnode.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -74,7 +74,7 @@ ++ */ ++ static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, ++ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, ++- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, +++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, ++ int IP_afamily, ++ #ifdef BW_LIM ++ long bw_send, long bw_recv, ++@@ -107,7 +107,6 @@ ++ pn->NR_flag = NR_OFF; ++ pn->ND_flag = ND_OFF; ++ pn->NP_flag = NP_OFF; ++- pn->NC_flag = NC_OFF; ++ pn->MD_flag = MD_USE_OLD; ++ pn->HC_flag = HC_USE_OLD; ++ pn->pipe = NULL; ++@@ -135,8 +134,6 @@ ++ pn->ND_flag = ND_flag; ++ if (NP_flag != NP_USE_OLD) ++ pn->NP_flag = NP_flag; ++- if (NC_flag != NC_USE_OLD) ++- pn->NC_flag = NC_flag; ++ if (HC_flag != HC_USE_OLD) ++ pn->HC_flag = HC_flag; ++ if (IP_afamily != AF_USE_OLD) ++@@ -198,7 +195,7 @@ ++ ++ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, ++ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, ++- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, +++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, ++ int IP_afamily, ++ #ifdef BW_LIM ++ long bw_send, long bw_recv, ++@@ -212,7 +209,7 @@ ++ ++ locknodesem(); ++ pn = add_node_nolock(fa, hosts, pwd, pkt_pwd, out_pwd, obox_flvr, obox, ibox, ++- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, +++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, ++ IP_afamily, ++ #ifdef BW_LIM ++ bw_send, bw_recv, ++@@ -278,7 +275,6 @@ ++ on->ND_flag=np->ND_flag; ++ on->MD_flag=np->MD_flag; ++ on->NP_flag=np->NP_flag; ++- on->NC_flag=np->NC_flag; ++ on->HC_flag=np->HC_flag; ++ on->restrictIP=np->restrictIP; ++ on->pipe=np->pipe; ++@@ -294,7 +290,7 @@ ++ ++ add_node_nolock(fa, np->hosts, NULL, NULL, NULL, np->obox_flvr, np->obox, ++ np->ibox, np->NR_flag, np->ND_flag, np->MD_flag, np->restrictIP, ++- np->HC_flag, np->NP_flag, np->NC_flag, np->pipe, np->IP_afamily, +++ np->HC_flag, np->NP_flag, np->pipe, np->IP_afamily, ++ #ifdef BW_LIM ++ np->bw_send, np->bw_recv, ++ #endif ++@@ -403,7 +399,7 @@ ++ if (!get_node_info_nolock (&target, config)) ++ add_node_nolock (&target, "*", NULL, NULL, NULL, '-', NULL, NULL, ++ NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, ++- HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, +++ HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, ++ #ifdef BW_LIM ++ BW_DEF, BW_DEF, ++ #endif ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnnode.h binkd_pgul/ftnnode.h ++--- binkd/ftnnode.h 2026-04-25 20:40:18.652899844 +0100 +++++ binkd_pgul/ftnnode.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -36,7 +36,7 @@ ++ */ ++ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, ++ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, ++- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, +++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, ++ int IP_afamily, ++ #ifdef BW_LIM ++ long bw_send, long bw_recv, ++@@ -75,10 +75,6 @@ ++ #define NP_OFF 0 ++ #define NP_USE_OLD -1 /* Use old value */ ++ ++-#define NC_ON 1 ++-#define NC_OFF 0 ++-#define NC_USE_OLD -1 /* Use old value */ ++- ++ #define AF_USE_OLD -1 /* Use old value */ ++ ++ #ifdef BW_LIM ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnq.c binkd_pgul/ftnq.c ++--- binkd/ftnq.c 2026-04-26 10:34:47.939032694 +0100 +++++ binkd_pgul/ftnq.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -1147,8 +1147,7 @@ ++ if (*buf) ++ { ++ strnzcat (buf, ".try", sizeof (buf)); ++- /* Delete only if the file exists */ ++- if (stat(buf, &sb) == 0) ++- delete (buf); +++ if (stat(buf, &sb) == -1) return; +++ delete (buf); ++ } ++ } ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/https.c binkd_pgul/https.c ++--- binkd/https.c 2026-04-22 21:36:50.649712064 +0100 +++++ binkd_pgul/https.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -318,11 +318,7 @@ ++ buf[1]=1; ++ lockhostsem(); ++ Log (4, strcmp(port, config->oport) == 0 ? "trying %s..." : "trying %s:%u...", ++-#ifdef AMIGA ++- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr.s_addr), portnum); ++-#else ++- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); ++-#endif +++ inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); ++ releasehostsem(); ++ buf[2]=(unsigned char)((portnum>>8)&0xFF); ++ buf[3]=(unsigned char)(portnum&0xFF); ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/inbound.c binkd_pgul/inbound.c ++--- binkd/inbound.c 2026-04-26 09:25:07.283995051 +0100 +++++ binkd_pgul/inbound.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -49,9 +49,7 @@ ++ char node[FTN_ADDR_SZ + 1]; ++ ++ strnzcpy (s, inbound, MAXPATHLEN); ++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++- if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) ++- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); +++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); ++ t = s + strlen (s); ++ while (1) ++ { ++@@ -130,9 +128,7 @@ ++ } ++ ++ strnzcpy (s, inbound, MAXPATHLEN); ++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++- if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) ++- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); +++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); ++ t = s + strlen (s); ++ while ((de = readdir (dp)) != 0) ++ { ++@@ -474,9 +470,7 @@ ++ } ++ ++ strnzcpy (real_name, state->inbound, MAXPATHLEN); ++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++- if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) ++- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); +++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); ++ s = real_name + strlen (real_name); ++ strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); ++ free (u); ++@@ -547,9 +541,7 @@ ++ { ++ ren_style = RENAME_POSTFIX; ++ strnzcpy (real_name, state->inbound, MAXPATHLEN); ++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++- if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) ++- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); +++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); ++ s = real_name + strlen (real_name); ++ strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); ++ free (u); ++@@ -599,9 +591,7 @@ ++ struct stat sb; ++ ++ strnzcpy (fp, inbound, MAXPATHLEN); ++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++- if (strlen(fp) > 0 && fp[strlen(fp) - 1] != PATH_SEPARATOR[0]) ++- strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); +++ strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); ++ s = fp + strlen (fp); ++ strnzcat (fp, u = strdequote (filename), MAXPATHLEN); ++ free (u); ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iphdr.h binkd_pgul/iphdr.h ++--- binkd/iphdr.h 2026-04-26 09:25:41.562664644 +0100 +++++ binkd_pgul/iphdr.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -124,17 +124,9 @@ ++ #define TCPERRNO errno ++ #define TCPERR_WOULDBLOCK EWOULDBLOCK ++ #define TCPERR_AGAIN EAGAIN ++- #ifdef AMIGA ++- /* AmigaOS 3: open bsdsocket.library via amiga/bsdsock.c */ ++- #include "amiga/bsdsock.h" ++- #define sock_init() amiga_sock_init() ++- #define sock_deinit() amiga_sock_cleanup() ++- #define soclose(h) CloseSocket(h) ++- #else ++- #define sock_init() 0 ++- #define sock_deinit() ++- #define soclose(h) close(h) ++- #endif +++ #define sock_init() 0 +++ #define sock_deinit() +++ #define soclose(h) close(h) ++ #endif ++ ++ #if !defined(WIN32) ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iptools.c binkd_pgul/iptools.c ++--- binkd/iptools.c 2026-04-26 10:33:19.517261538 +0100 +++++ binkd_pgul/iptools.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -20,10 +20,6 @@ ++ #include ++ #endif ++ ++-#ifdef AMIGA ++-#include ++-#endif ++- ++ #include "sys.h" ++ #include "Config.h" ++ #include "iphdr.h" ++@@ -44,11 +40,7 @@ ++ int arg; ++ ++ arg = 1; ++-#if defined(AMIGA) ++- if (ioctl (s, FIONBIO, (char *) &arg) < 0) ++-#else ++ if (ioctl (s, FIONBIO, (char *) &arg, sizeof arg) < 0) ++-#endif ++ Log (1, "ioctl (FIONBIO): %s", TCPERR ()); ++ ++ #elif defined(WIN32) ++@@ -61,49 +53,12 @@ ++ #endif ++ #endif ++ ++-#if defined(UNIX) || defined(EMX) /* NOT AMIGA: sockets are not AmigaDOS fds */ +++#if defined(UNIX) || defined(EMX) || defined(AMIGA) ++ if (fcntl (s, F_SETFL, O_NONBLOCK) == -1) ++ Log (1, "fcntl: %s", strerror (errno)); ++ #endif ++ } ++ ++-#if defined(AMIGA) ++-void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf) ++-{ ++- /* Disable Nagle algorithm: BinkP mixes small control messages with data ++- * Without TCP_NODELAY each small message waits up to 200ms (Nagle delay), ++- * making sessions 2-5x slower than other BinkP implementations ++- * All other BinkP mailers (BinkIT, Argus, etc.) set this explicitly */ ++- ++- if (tcpdelay) ++- { ++- int nodelay = tcpdelay; ++- ++- if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)) < 0) ++- Log (4, "setsockopt TCP_NODELAY: %s", TCPERR()); ++- } ++- ++- /* ixnet default TCP buffers are very small (~8KB). Increase them so the ++- * sender does not stall waiting for ACK after every small burst */ ++- ++- if (so_sndbuf) ++- { ++- int sndbuf = so_sndbuf; ++- ++- if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, sizeof(sndbuf)) < 0) ++- Log (5, "setsockopt SO_SNDBUF: %s", TCPERR()); ++- } ++- ++- if (so_rcvbuf) ++- { ++- int rcvbuf = so_rcvbuf; ++- ++- if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) < 0) ++- Log (5, "setsockopt SO_RCVBUF: %s", TCPERR()); ++- } ++-} ++-#endif ++- ++ /* ++ * Find the appropriate port string to be used. ++ * Find_port ("") will return binkp's port from /etc/services or even ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iptools.h binkd_pgul/iptools.h ++--- binkd/iptools.h 2026-04-26 09:26:42.811498024 +0100 +++++ binkd_pgul/iptools.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -11,21 +11,11 @@ ++ * (at your option) any later version. See COPYING. ++ */ ++ ++-#if defined(AMIGA) ++-#include ++-#include ++-#include ++-#endif ++- ++ /* ++ * Sets non-blocking mode for a given socket ++ */ ++ void setsockopts (SOCKET s); ++ ++-#if defined(AMIGA) ++-void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf); ++-#endif ++- ++ /* ++ * Find the port number (in the host byte order) by a port number string or ++ * a service name. Find_port ("") will return binkp's port from ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/decompress.c binkd_pgul/misc/decompress.c ++--- binkd/misc/decompress.c 2026-04-26 13:39:53.974576140 +0100 +++++ binkd_pgul/misc/decompress.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,188 +0,0 @@ ++-/* ++- * decompress.c -- Decompress FTN bundle archives from an inbound directory ++- * ++- * decompress.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include "portable.h" /* Canonical portable layer */ ++-#include ++- ++-#define MAX_CMD 1100 ++- ++-/* Archive format codes detected by magic bytes */ ++-#define FMT_UNKNOWN 0 ++-#define FMT_ZIP 1 ++-#define FMT_LZH 2 ++-#define FMT_ARC 3 ++- ++-/* detect_format -- Read first bytes and identify archive type */ ++-static int detect_format(const char *path) ++-{ ++- unsigned char buf[8]; ++- FILE *f = fopen(path, "rb"); ++- int n; ++- ++- if (!f) ++- return FMT_UNKNOWN; ++- ++- n = (int)fread(buf, 1, sizeof(buf), f); ++- ++- fclose(f); ++- ++- if (n < 2) ++- return FMT_UNKNOWN; ++- ++- /* ZIP: PK\x03\x04 */ ++- if (n >= 4 && buf[0] == 0x50 && buf[1] == 0x4B && buf[2] == 0x03 && buf[3] == 0x04) ++- return FMT_ZIP; ++- ++- /* LZH: offset 2 = '-', offset 3 = 'l', offset 6 = '-' (e.g. -lh5-) */ ++- if (n >= 7 && buf[2] == '-' && buf[3] == 'l' && buf[6] == '-') ++- return FMT_LZH; ++- ++- /* ARC: 0x1A followed by type byte 1..18 */ ++- if (buf[0] == 0x1A && buf[1] >= 1 && buf[1] <= 18) ++- return FMT_ARC; ++- ++- return FMT_UNKNOWN; ++-} ++- ++-/* is_ftn_bundle -- Check filename has an FTN day-of-week extension */ ++-static int is_ftn_bundle(const char *filename) ++-{ ++- const char *p; ++- ++- for (p = filename; *p; p++) ++- { ++- if (p[0] == '.' && p[1] && p[2]) ++- { ++- char a = (char)tolower((unsigned char)p[1]); ++- char b = (char)tolower((unsigned char)p[2]); ++- ++- if ((a == 's' && b == 'u') || (a == 'm' && b == 'o') || ++- (a == 't' && b == 'u') || (a == 'w' && b == 'e') || ++- (a == 't' && b == 'h') || (a == 'f' && b == 'r') || ++- (a == 's' && b == 'a')) ++- { ++- /* .TH .TH0 .TH.001 */ ++- if (p[3] == '\0' || isdigit((unsigned char)p[3]) || p[3] == '.') ++- return 1; ++- } ++- } ++- } ++- return 0; ++-} ++- ++-/* delete_file -- Remove a file, portable */ ++-static void delete_file(const char *path) ++-{ ++-#if defined(AMIGA) ++- DeleteFile((STRPTR)path); ++-#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) ++- DeleteFileA(path); ++-#else ++- remove(path); ++-#endif ++-} ++- ++-/* run_decompressor -- Invoke external tool for the detected format ++- * outdir must end without trailing slash on POSIX; lha needs trailing / */ ++-static int run_decompressor(int fmt, const char *path, const char *outdir) ++-{ ++- char cmd[MAX_CMD]; ++- ++- switch (fmt) ++- { ++- case FMT_ZIP: ++-#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) ++- snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); ++-#else ++- snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); ++-#endif ++- break; ++- ++- case FMT_LZH: ++-#ifdef AMIGA ++- snprintf(cmd, MAX_CMD, "lha x \"%s\" \"%s/\"", path, outdir); ++-#else ++- snprintf(cmd, MAX_CMD, "lha e \"%s\" \"%s/\"", path, outdir); ++-#endif ++- break; ++- ++- case FMT_ARC: ++-#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) ++- snprintf(cmd, MAX_CMD, "arc x \"%s\" \"%s\"", path, outdir); ++-#else ++- snprintf(cmd, MAX_CMD, "cd \"%s\" && arc x \"%s\"", outdir, path); ++-#endif ++- break; ++- ++- default: ++- return -1; ++- } ++- ++- return system(cmd); ++-} ++- ++-int main(int argc, char *argv[]) ++-{ ++- DIR *dp; ++- struct dirent *entry; ++- char path[MAXPATHLEN]; ++- const char *inbound; ++- const char *outdir; ++- int total = 0; ++- int ok = 0; ++- ++- if (argc < 3) ++- { ++- fprintf(stderr, ++- "Usage: decompress \n" ++- "Detects format by magic bytes (ZIP/LZH/ARC).\n" ++- "Processes FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA).\n"); ++- ++- return 1; ++- } ++- ++- inbound = argv[1]; ++- outdir = argv[2]; ++- ++- dp = opendir(inbound); ++- ++- if (dp == NULL) ++- return 1; ++- ++- while ((entry = readdir(dp)) != NULL) ++- { ++- int fmt; ++- ++- /* Skip . and .. (AmigaOS readdir does not return these, POSIX does) */ ++- if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) ++- continue; ++- ++- if (!is_ftn_bundle(entry->d_name)) ++- continue; ++- ++- path_join(path, MAXPATHLEN, inbound, entry->d_name); ++- ++- fmt = detect_format(path); ++- ++- if (fmt == FMT_UNKNOWN) ++- continue; ++- ++- total++; ++- ++- if (run_decompressor(fmt, path, outdir) == 0) ++- { ++- delete_file(path); ++- ok++; ++- } ++- } ++- ++- closedir(dp); ++- ++- return (total == 0 || ok == total) ? 0 : 1; ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/decompress.txt binkd_pgul/misc/decompress.txt ++--- binkd/misc/decompress.txt 2026-04-26 13:44:08.749250093 +0100 +++++ binkd_pgul/misc/decompress.txt 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,36 +0,0 @@ ++-decompress -- Decompress FTN bundle archives ++- ++-USAGE: ++- decompress ++- ++-DESCRIPTION: ++- Scans the inbound directory for FTN day-of-week bundle archives and ++- decompresses them to the output directory. ++- ++- Recognized archive formats (by magic bytes, not extension): ++- - ZIP files ++- - LZH files ++- - ARC files ++- ++- Recognized bundle extensions (case-insensitive): ++- - .SU - Sunday bundle ++- - .MO - Monday bundle ++- - .TU - Tuesday bundle ++- - .WE - Wednesday bundle ++- - .TH - Thursday bundle ++- - .FR - Friday bundle ++- - .SA - Saturday bundle ++- ++- Also handles renamed bundles (duplicates): ++- - ABCD1234.SU ++- - ABCD1234.SU.001 ++- - ABCD1234.SU.002 ++- ++-EXAMPLES: ++- decompress Work:Inbound Work:Unpacked ++- decompress /var/spool/binkd/inbound /tmp/unpacked ++- decompress C:\Binkd\Inbound C:\Binkd\Unpacked ++- exec "work:fido/decompress work:fido/inbound work:fido/inbound" *.su? *.mo? *.tu? *.we? *.th? *.fr? *.sa? *.SU? *.MO? *.TU? *.WE? *.TH? *.FR? *.SA? ++- ++-CONFIGURATION FILE: ++- None. All parameters are command-line arguments. ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/freq.c binkd_pgul/misc/freq.c ++--- binkd/misc/freq.c 2026-04-26 13:39:53.974576140 +0100 +++++ binkd_pgul/misc/freq.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,220 +0,0 @@ ++-/* ++- * freq.c -- Append a file-request entry to an outbound .req / .clo pair ++- * ++- * freq.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include "portable.h" /* Canonical portable layer */ ++- ++-#define FREQ_MAX_PATH (MAXPATHLEN + 1) ++- ++-/* build_aso_paths -- ASO flat layout */ ++-static int build_aso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) ++-{ ++- char *dot; ++- ++- if (mkdir_recursive(outbound) < 0 && !path_exists(outbound)) ++- return -1; ++- ++- snprintf(req_path, (size_t)pathsize, "%s/%u.%u.%u.%u.req", outbound, zone, net, node, point); ++- safe_strncpy(clo_path, req_path, pathsize); ++- dot = strrchr(clo_path, '.'); ++- ++- if (dot) ++- strcpy(dot, ".clo"); ++- ++- return 0; ++-} ++- ++-/* build_bso_paths -- BSO BinkleyStyle layout (lowercase hex) */ ++-static int build_bso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) ++-{ ++- char zone_dir[FREQ_MAX_PATH]; ++- char node_dir[FREQ_MAX_PATH]; ++- ++- /* Zone dir: .0ZZ (lowercase hex) */ ++- snprintf(zone_dir, sizeof(zone_dir), "%s.%03x", outbound, zone); ++- str_tolower(zone_dir); ++- ++- if (mkdir_recursive(zone_dir) < 0 && !path_exists(zone_dir)) ++- return -1; ++- ++- if (point == 0) ++- { ++- snprintf(req_path, (size_t)pathsize, "%s/%04x%04x.req", zone_dir, net, node); ++- snprintf(clo_path, (size_t)pathsize, "%s/%04x%04x.clo", zone_dir, net, node); ++- } ++- else ++- { ++- snprintf(node_dir, sizeof(node_dir), "%s/%04x%04x.pnt", zone_dir, net, node); ++- ++- if (mkdir_recursive(node_dir) < 0 && !path_exists(node_dir)) ++- return -1; ++- ++- snprintf(req_path, (size_t)pathsize, "%s/%08x.req", node_dir, point); ++- snprintf(clo_path, (size_t)pathsize, "%s/%08x.clo", node_dir, point); ++- } ++- ++- return 0; ++-} ++- ++-int main(int argc, char *argv[]) ++-{ ++- unsigned int zone, net, node, point; ++- char addr_copy[128]; ++- char req_path[FREQ_MAX_PATH]; ++- char clo_path[FREQ_MAX_PATH]; ++- char abs_outbound[FREQ_MAX_PATH]; ++- FILE *f; ++- const char *outbound; ++- const char *arg_outbound; ++- const char *arg_addr; ++- const char *password = NULL; /* --password → !pass suffix */ ++- long newer_than = 0; /* --newer-than → +ts suffix */ ++- int update = 0; /* --update → U suffix */ ++- int use_bso = 0; ++- int argi = 1; ++- int nfiles = 0; ++- ++- zone = net = node = point = 0; ++- ++- /* Parse flags -- All optional, order-independent, before positional args */ ++- while (argi < argc && argv[argi][0] == '-' && argv[argi][1] == '-') ++- { ++- if (strcmp(argv[argi], "--bso") == 0) ++- { ++- use_bso = 1; ++- argi++; ++- } ++- else if (strcmp(argv[argi], "--aso") == 0) ++- { ++- use_bso = 0; ++- argi++; ++- } ++- else if (strcmp(argv[argi], "--update") == 0) ++- { ++- update = 1; ++- argi++; ++- } ++- else if (strcmp(argv[argi], "--password") == 0 && argi + 1 < argc) ++- { ++- password = argv[++argi]; ++- argi++; ++- } ++- else if (strcmp(argv[argi], "--newer-than") == 0 && argi + 1 < argc) ++- { ++- newer_than = atol(argv[++argi]); ++- argi++; ++- } ++- else ++- break; /* unknown flag — stop, treat rest as positional */ ++- } ++- ++- if (argc - argi < 3) ++- { ++- fprintf(stderr, ++- "Usage: freq [options] Z:N/NODE[.POINT] [...]\n" ++- "Options:\n" ++- " --aso flat layout (default): outbound/Z.N.NODE.POINT.req\n" ++- " --bso BSO layout: outbound.0ZZ/nnnnnnnn[.pnt/pppppppp].req\n" ++- " --password append !pw to each request line\n" ++- " --newer-than append + (request if newer)\n" ++- " --update append U flag (update request)\n" ++- "Multiple filenames can be listed after the address.\n"); ++- return 1; ++- } ++- ++- arg_outbound = argv[argi++]; ++- arg_addr = argv[argi++]; ++- ++- /* Remaining args are filenames */ ++- ++- make_abs_path(arg_outbound, abs_outbound, (int)sizeof(abs_outbound)); ++- outbound = abs_outbound; ++- ++- safe_strncpy(addr_copy, arg_addr, (int)sizeof(addr_copy)); ++- ++- if (sscanf(addr_copy, "%u:%u/%u.%u", &zone, &net, &node, &point) < 3 && sscanf(addr_copy, "%u:%u/%u", &zone, &net, &node) < 3) ++- { ++- fprintf(stderr, "freq: invalid address: %s\n", arg_addr); ++- return 1; ++- } ++- ++- if (use_bso) ++- { ++- if (build_bso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) ++- { ++- fprintf(stderr, "freq: cannot create BSO dirs under: %s\n", outbound); ++- return 1; ++- } ++- } ++- else ++- { ++- if (build_aso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) ++- { ++- fprintf(stderr, "freq: cannot create outbound dir: %s\n", outbound); ++- return 1; ++- } ++- } ++- ++- /* Append all filenames to .req */ ++- f = fopen(req_path, "a"); ++- ++- if (!f) ++- { ++- fprintf(stderr, "freq: cannot open REQ: %s\n", req_path); ++- return 1; ++- } ++- ++- for (; argi < argc; argi++) ++- { ++- const char *fname = argv[argi]; ++- ++- /* Build request line: filename [!password] [+timestamp] [U] */ ++- fprintf(f, "%s", fname); ++- ++- if (password && password[0]) ++- fprintf(f, " !%s", password); ++- ++- if (newer_than > 0) ++- fprintf(f, " +%ld", newer_than); ++- ++- if (update) ++- fprintf(f, " U"); ++- ++- fprintf(f, "\r\n"); ++- nfiles++; ++- } ++- ++- fclose(f); ++- ++- if (nfiles == 0) ++- { ++- fprintf(stderr, "freq: no filenames specified\n"); ++- return 1; ++- } ++- ++- /* Append .req full path to .clo (once per invocation) */ ++- f = fopen(clo_path, "a"); ++- ++- if (!f) ++- { ++- fprintf(stderr, "freq: cannot open CLO: %s\n", clo_path); ++- return 1; ++- } ++- ++- fprintf(f, "%s\r\n", req_path); ++- fclose(f); ++- ++- printf("freq (%s): node %u:%u/%u", use_bso ? "bso" : "aso", zone, net, node); ++- ++- if (point) ++- printf(".%u", point); ++- ++- printf(" %d file(s)\n REQ : %s\n CLO : %s\n", nfiles, req_path, clo_path); ++- ++- return 0; ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/freq.txt binkd_pgul/misc/freq.txt ++--- binkd/misc/freq.txt 2026-04-26 13:33:14.698749181 +0100 +++++ binkd_pgul/misc/freq.txt 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,37 +0,0 @@ ++-freq -- Create .req and .clo request files for FidoNet ++- ++-USAGE: ++- freq [options] [...] ++- ++-DESCRIPTION: ++- Creates file request (.req) and close (.clo) files in the outbound ++- directory using FidoNet 4D/5D addressing. ++- ++- Address format: ++- Zone:Net/Node - 4D address (e.g., 39:190/101) ++- Zone:Net/Node.Point - 5D address with point (e.g., 39:190/101.1) ++- ++- Multiple files can be specified to create multiple request lines. ++- ++-OPTIONS: ++- --aso Amiga Style Outbound (default) - flat layout ++- Format: /Z.N.NODE.POINT.req ++- ++- --bso Binkley Style Outbound - hex directory layout ++- No point: .0ZZ/nnnnnnnn.req ++- Point: .0ZZ/nnnnnnnn.pnt/pppppppp.req ++- ++- --password Append !pw suffix to each request line ++- --newer-than Append + (request only if file is newer) ++- --update Append U flag (update request, checks TRANX) ++- ++-EXAMPLES: ++- freq Work:Outbound 39:190/101 file.lha ++- freq --aso Work:Outbound 39:190/101.1 readme.txt ++- freq --bso /var/spool/binkd/outbound 2:123/456 door.zip ++- freq --bso C:\Binkd\Outbound 1:100/200 update.lzh ++- freq --password secret --newer-than 1234567890 Work:Outbound 39:190/101 file.zip ++- freq --update Work:Outbound 39:190/101 file1.zip file2.zip file3.zip ++- ++-CONFIGURATION FILE: ++- None. All parameters are command-line arguments. ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/nodelist.c binkd_pgul/misc/nodelist.c ++--- binkd/misc/nodelist.c 2026-04-26 13:39:53.974576140 +0100 +++++ binkd_pgul/misc/nodelist.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,200 +0,0 @@ ++-/* ++- * nodelist.c -- FidoNet nodelist compiler for binkd ++- * ++- * nodelist.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include "portable.h" /* Canonical portable layer */ ++-#include ++-#include ++-#include ++-#include ++- ++-#define MAX_FIELDS 20 ++-#define MAX_VAL 256 ++- ++-static int split_fields(char *buf, char **fields, int maxfields) ++-{ ++- int n = 0; ++- char *p = buf; ++- ++- while (n < maxfields) ++- { ++- fields[n++] = p; ++- p = strchr(p, ','); ++- ++- if (!p) ++- break; ++- ++- *p++ = '\0'; ++- } ++- ++- return n; ++-} ++- ++-/* Case-insensitive flag search. Returns 1 if found; fills val if present */ ++-static int find_flag(char **fields, int nfields, int start, const char *flag, char *val, int vsize) ++-{ ++- int flen = (int)strlen(flag); ++- int i; ++- ++- for (i = start; i < nfields; i++) ++- { ++- char *f = fields[i]; ++- int j; ++- ++- for (j = 0; j < flen; j++) ++- { ++- if (toupper((unsigned char)f[j]) != toupper((unsigned char)flag[j])) ++- break; ++- } ++- ++- if (j == flen) ++- { ++- if (val && vsize > 0) ++- { ++- if (f[flen] == ':') ++- { ++- strncpy(val, f + flen + 1, (size_t)(vsize - 1)); ++- val[vsize - 1] = '\0'; ++- } ++- else ++- val[0] = '\0'; ++- } ++- return 1; ++- } ++- } ++- return 0; ++-} ++- ++-int main(int argc, char *argv[]) ++-{ ++- const char *nl_file; ++- const char *domain; ++- FILE *in; ++- FILE *out; ++- char buf[MAX_LINE]; ++- char *fields[MAX_FIELDS]; ++- int nf; ++- int cur_zone; ++- int cur_net; ++- long count; ++- ++- cur_zone = 0; ++- cur_net = 0; ++- count = 0; ++- ++- if (argc < 3) ++- { ++- fprintf(stderr, ++- "Usage: nodelist []\n"); ++- return 1; ++- } ++- ++- nl_file = argv[1]; ++- domain = argv[2]; ++- out = (argc >= 4) ? fopen(argv[3], "w") : stdout; ++- ++- if (!out) ++- { ++- perror(argv[3]); ++- return 1; ++- } ++- ++- in = fopen(nl_file, "r"); ++- ++- if (!in) ++- { ++- perror(nl_file); ++- ++- if (out != stdout) ++- fclose(out); ++- ++- return 1; ++- } ++- ++- while (fgets(buf, sizeof(buf), in)) ++- { ++- char type[32]; ++- char ibn_port[32]; ++- char ina_host[MAX_VAL]; ++- int node_num; ++- int port; ++- int flags_start; ++- ++- str_trim(buf); ++- ++- if (!buf[0] || buf[0] == ';') ++- continue; ++- ++- nf = split_fields(buf, fields, MAX_FIELDS); ++- ++- if (nf < 2) ++- continue; ++- ++- if (fields[0][0] == '\0') ++- { ++- /* Line started with comma -- plain Node entry */ ++- strcpy(type, "Node"); ++- node_num = atoi(fields[1]); ++- flags_start = 7; ++- } ++- else ++- { ++- strncpy(type, fields[0], sizeof(type) - 1); ++- type[sizeof(type) - 1] = '\0'; ++- node_num = atoi(fields[1]); ++- flags_start = 7; ++- } ++- ++- /* Update zone / net context */ ++- if (!strcmp(type, "Zone") || !strcmp(type, "ZONE")) ++- { ++- cur_zone = node_num; ++- cur_net = node_num; ++- continue; ++- } ++- ++- if (!strcmp(type, "Region") || !strcmp(type, "REGION")) ++- { ++- cur_net = node_num; ++- continue; ++- } ++- ++- if (!strcmp(type, "Host") || !strcmp(type, "HOST")) ++- cur_net = node_num; ++- ++- /* Skip unusable types */ ++- if (!strcmp(type, "Pvt") || !strcmp(type, "PVT") || !strcmp(type, "Hold") || !strcmp(type, "HOLD") || !strcmp(type, "Down") || !strcmp(type, "DOWN") || !strcmp(type, "Boss") || !strcmp(type, "BOSS")) ++- continue; ++- ++- /* Must have IBN flag */ ++- if (!find_flag(fields, nf, flags_start, "IBN", ibn_port, (int)sizeof(ibn_port))) ++- continue; ++- ++- /* Need INA:hostname */ ++- ina_host[0] = '\0'; ++- find_flag(fields, nf, flags_start, "INA", ina_host, (int)sizeof(ina_host)); ++- ++- if (!ina_host[0]) ++- continue; ++- ++- port = (ibn_port[0] && atoi(ibn_port) > 0) ? atoi(ibn_port) : 24554; ++- ++- fprintf(out, "node %d:%d/%d@%s %s:%d -\n", cur_zone, cur_net, node_num, domain, ina_host, port); ++- ++- count++; ++- } ++- ++- fclose(in); ++- ++- if (out != stdout) ++- fclose(out); ++- ++- fprintf(stderr, "nodelist: %ld BinkP node(s) found\n", count); ++- ++- return 0; ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/nodelist.txt binkd_pgul/misc/nodelist.txt ++--- binkd/misc/nodelist.txt 2026-04-26 13:32:27.866048972 +0100 +++++ binkd_pgul/misc/nodelist.txt 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,32 +0,0 @@ ++-nodelist -- Compile FidoNet nodelist to binkd.conf format ++- ++-USAGE: ++- nodelist [] ++- ++-DESCRIPTION: ++- Reads a FidoNet nodelist and outputs binkd.conf compatible ++- "node" configuration lines. ++- ++- Arguments: ++- nodelist_file Path to the FidoNet nodelist file ++- domain Domain name for the node entries (e.g., fidonet) ++- output_file Optional output file (default: stdout) ++- ++- Extracts the following flags: ++- IBN[:port] - BinkP protocol flag (Internet BinkP Node) ++- INA:hostname - IP hostname/address ++- ++- Output format: ++- node
@ : - ++- ++- The nodelist format is comma-separated: ++- [type,]node_num,name,city,sysop,phone,baud,flag1,flag2,... ++- type = Zone, Region, Host, Hub, Pvt, Hold, Down, Boss (empty = Node) ++- ++-EXAMPLES: ++- nodelist Work:Fido/nodelist.123 fidonet > binkd-nodes.conf ++- nodelist /etc/fido/nodelist.456 fidonet >> binkd.conf ++- nodelist C:\Fido\NODELIST.001 fidonet C:\Fido\nodes.conf ++- ++-CONFIGURATION FILE: ++- None. All parameters are command-line arguments. ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/portable.c binkd_pgul/misc/portable.c ++--- binkd/misc/portable.c 2026-04-26 13:04:59.214902887 +0100 +++++ binkd_pgul/misc/portable.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,402 +0,0 @@ ++-/* ++- * portable.c -- Shared implementations for misc tools portable layer ++- * ++- * portable.c is a part of binkd project ++- * ++- * This file provides implementations for common utility functions ++- * used across binkd misc tools. Include portable.h for declarations ++- * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- * ++- */ ++- ++-#include "portable.h" ++-#include ++- ++-/* trim_nl -- Strip trailing newline (\n and \r) from string */ ++-void trim_nl(char *s) ++-{ ++- char *p = strchr(s, '\n'); ++- ++- if (p) ++- *p = '\0'; ++- ++- p = strchr(s, '\r'); ++- ++- if (p) ++- *p = '\0'; ++-} ++- ++-/* str_trim -- Strip trailing whitespace (space, \r, \n) from string */ ++-void str_trim(char *s) ++-{ ++- int n = (int)strlen(s); ++- ++- while (n > 0 && (s[n - 1] == '\r' || s[n - 1] == '\n' || s[n - 1] == ' ')) ++- s[--n] = '\0'; ++-} ++- ++-/* str_upper -- Convert string to uppercase in-place */ ++-void str_upper(char *s) ++-{ ++- while (*s) ++- { ++- *s = (char)toupper((unsigned char)*s); ++- s++; ++- } ++-} ++- ++-/* str_tolower -- Convert string to lowercase in-place */ ++-void str_tolower(char *s) ++-{ ++- for (; *s; s++) ++- { ++- if (*s >= 'A' && *s <= 'Z') ++- *s = (char)(*s + ('a' - 'A')); ++- } ++-} ++- ++-/* skip_ws -- Skip leading whitespace */ ++-char *skip_ws(char *s) ++-{ ++- while (*s == ' ' || *s == '\t') ++- s++; ++- ++- return s; ++-} ++- ++-/* wildmatch -- Portable wildcard match: case-insensitive, supports * and ? */ ++-int wildmatch(const char *pat, const char *str) ++-{ ++- while (*pat) ++- { ++- if (*pat == '*') ++- { ++- while (*pat == '*') ++- pat++; ++- ++- if (!*pat) ++- return 1; ++- ++- while (*str) ++- { ++- if (wildmatch(pat, str++)) ++- return 1; ++- } ++- ++- return 0; ++- } ++- ++- if (*pat == '?') ++- { ++- if (!*str) ++- return 0; ++- ++- pat++; ++- str++; ++- } ++- else ++- { ++- if (toupper((unsigned char)*pat) != toupper((unsigned char)*str)) ++- return 0; ++- ++- pat++; ++- str++; ++- } ++- } ++- ++- return (*str == '\0') ? 1 : 0; ++-} ++- ++-/* is_wildcard -- True if name contains * or ? */ ++-int is_wildcard(const char *s) ++-{ ++- while (*s) ++- { ++- if (*s == '*' || *s == '?') ++- return 1; ++- ++- s++; ++- } ++- ++- return 0; ++-} ++- ++-/* ensure_dir -- Ensure directory exists, creating if necessary */ ++-int ensure_dir(const char *path) ++-{ ++- if (path_exists(path)) ++- return 1; ++- ++- return (mkdir_recursive(path) == 0) ? 1 : 0; ++-} ++- ++-/* copy_file -- Portable binary file copy */ ++-int copy_file(const char *src, const char *dst) ++-{ ++- FILE *in, *out; ++- char buf[4096]; ++- int n; ++- ++- in = fopen(src, "rb"); ++- ++- if (!in) ++- return 0; ++- ++- out = fopen(dst, "wb"); ++- ++- if (!out) ++- { ++- fclose(in); ++- return 0; ++- } ++- ++- while ((n = (int)fread(buf, 1, sizeof(buf), in)) > 0) ++- fwrite(buf, 1, (size_t)n, out); ++- ++- fclose(out); ++- fclose(in); ++- ++- return 1; ++-} ++- ++-/* move_file -- Try rename first, fall back to copy+delete */ ++-int move_file(const char *src, const char *dst) ++-{ ++- remove(dst); ++- ++- if (rename(src, dst) == 0) ++- return 1; ++- ++- if (copy_file(src, dst)) ++- { ++- remove(src); ++- return 1; ++- } ++- ++- return 0; ++-} ++- ++-/* get_file_size -- Return file size in bytes, or -1 on error */ ++-long get_file_size(const char *path) ++-{ ++- struct stat st; ++- ++- if (stat(path, &st) == 0) ++- return (long)st.st_size; ++- ++- return -1; ++-} ++- ++-/* get_file_mtime -- Return Unix mtime of a file, or 0 on error */ ++-long get_file_mtime(const char *path) ++-{ ++- struct stat st; ++- ++- if (stat(path, &st) != 0) ++- return 0; ++- ++- return (long)st.st_mtime; ++-} ++- ++-/* port_path_exists -- Check if path exists (native per OS) */ ++- ++-int port_path_exists(const char *p) ++-{ ++-#ifdef AMIGA ++- BPTR l = Lock((STRPTR)p, ACCESS_READ); ++- ++- if (l) ++- { ++- UnLock(l); ++- return 1; ++- } ++- ++- return 0; ++-#else ++- struct stat st; ++- return (stat(p, &st) == 0) ? 1 : 0; ++-#endif ++-} ++- ++-/* port_mkdir_one -- Create single directory (native per OS) */ ++-int port_mkdir_one(const char *p) ++-{ ++-#ifdef AMIGA ++- BPTR l = CreateDir((STRPTR)p); ++- ++- if (l) ++- { ++- UnLock(l); ++- return 0; ++- } ++- ++- return -1; ++-#else ++- return mkdir(p, 0755); ++-#endif ++-} ++- ++-/* safe_localtime -- Thread-safe localtime, portable across all OS */ ++-void safe_localtime(const time_t *t, struct tm *tm) ++-{ ++-#if defined(AMIGA) || defined(DOS) ++- *tm = *localtime(t); ++-#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) ++- localtime_s(tm, t); ++-#else ++- localtime_r(t, tm); ++-#endif ++-} ++- ++-/* mkdir_recursive -- Create full path, making all missing components */ ++-int mkdir_recursive(const char *path) ++-{ ++- char tmp[MP_MAXPATH]; ++- char *p; ++- int len; ++- ++- if (!path || !path[0]) ++- return -1; ++- ++- strncpy(tmp, path, MP_MAXPATH - 1); ++- tmp[MP_MAXPATH - 1] = '\0'; ++- ++- len = (int)strlen(tmp); ++- ++- /* Strip trailing slash */ ++- while (len > 1 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\')) ++- tmp[--len] = '\0'; ++- ++- /* Walk every '/' component and create missing dirs */ ++- for (p = tmp + 1; *p; p++) ++- { ++- if (*p == '/' || *p == '\\') ++- { ++- *p = '\0'; ++- ++- if (!path_exists(tmp)) ++- mkdir_one(tmp); /* ignore per-component errors */ ++- ++- *p = '/'; ++- } ++- } ++- ++- /* Create the leaf */ ++- if (!path_exists(tmp)) ++- return mkdir_one(tmp); ++- ++- return 0; ++-} ++- ++-/* safe_strncpy -- Ctrncpy that always NUL-terminates */ ++-void safe_strncpy(char *dst, const char *src, int dstsize) ++-{ ++- int len; ++- ++- if (dstsize <= 0) ++- return; ++- ++- len = (int)strlen(src); ++- ++- if (len > dstsize - 1) ++- len = dstsize - 1; ++- ++- memcpy(dst, src, (size_t)len); ++- dst[len] = '\0'; ++-} ++- ++-/* path_join -- Concatenate base path with sub path */ ++-void path_join(char *out, int outsize, const char *base, const char *sub) ++-{ ++- int blen; ++- char last; ++- ++- safe_strncpy(out, base, outsize); ++- blen = (int)strlen(out); ++- last = (blen > 0) ? out[blen - 1] : '\0'; ++- ++- if (last != '/' && last != ':' && last != '\\') ++- { ++- if (outsize - 1 - blen > 0) ++- { ++- out[blen] = '/'; ++- out[blen + 1] = '\0'; ++- blen++; ++- } ++- } ++- ++- safe_strncpy(out + blen, sub, outsize - blen); ++-} ++- ++-/* make_abs_path -- Resolve a possibly-relative path to absolute ++- * Covers AmigaOS, Win32, OS/2, DOS and Unix ++- * Returns 1 on success, 0 on failure (src copied verbatim as fallback) ++- */ ++-int make_abs_path(const char *src, char *dst, int dstlen) ++-{ ++-#ifdef AMIGA ++- BPTR lock = Lock((STRPTR)src, SHARED_LOCK); ++- ++- if (!lock) ++- { ++- safe_strncpy(dst, src, dstlen); ++- return 0; ++- } ++- ++- if (!NameFromLock(lock, (STRPTR)dst, dstlen)) ++- { ++- UnLock(lock); ++- safe_strncpy(dst, src, dstlen); ++- return 0; ++- } ++- ++- UnLock(lock); ++- ++- return 1; ++-#elif defined(WIN32) || defined(__MINGW32__) || defined(__WATCOMC__) || defined(VISUALCPP) || defined(OS2) ++- if (_fullpath(dst, src, (size_t)dstlen) != NULL) ++- return 1; ++- ++- safe_strncpy(dst, src, dstlen); ++- return 0; ++-#elif defined(DOS) ++- if (src[0] != '\\' && src[1] != ':') ++- { ++- char cwd[MAXPATHLEN + 1]; ++- ++- if (getcwd(cwd, sizeof(cwd)) != NULL) ++- { ++- snprintf(dst, dstlen, "%s\\%s", cwd, src); ++- return 1; ++- } ++- } ++- ++- safe_strncpy(dst, src, dstlen); ++- return 0; ++-#else ++- char buf[MAXPATHLEN + 1]; ++- ++- if (realpath(src, buf) != NULL) ++- { ++- safe_strncpy(dst, buf, dstlen); ++- return 1; ++- } ++- ++- if (src[0] != '/') ++- { ++- char cwd[MAXPATHLEN + 1]; ++- ++- if (getcwd(cwd, sizeof(cwd)) != NULL) ++- { ++- snprintf(dst, dstlen, "%s/%s", cwd, src); ++- return 1; ++- } ++- } ++- ++- safe_strncpy(dst, src, dstlen); ++- return 0; ++-#endif ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/portable.h binkd_pgul/misc/portable.h ++--- binkd/misc/portable.h 2026-04-26 14:19:41.472724309 +0100 +++++ binkd_pgul/misc/portable.h 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,135 +0,0 @@ ++-/* ++- * portable.h -- Portability layer for standalone binkd misc tools ++- * ++- * portable.h is a part of binkd project ++- * ++- * This is the single canonical portable.h; all misc utilities include this ++- * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- * ++- */ ++- ++-#ifndef BINKD_PORTABLE_H ++-#define BINKD_PORTABLE_H ++- ++-/* _POSIX_C_SOURCE for opendir/readdir/localtime_r under -std=c89 ++- * _XOPEN_SOURCE 500 additionally exposes realpath() on glibc */ ++-#ifndef AMIGA ++-#ifndef _POSIX_C_SOURCE ++-#define _POSIX_C_SOURCE 200112L ++-#endif ++-#ifndef _XOPEN_SOURCE ++-#define _XOPEN_SOURCE 500 ++-#endif ++-#endif ++- ++-#include ++-#include ++-#include ++-#include ++-#include ++- ++-#ifdef AMIGA ++- ++-#include ++-#include ++-#include ++-#include ++-#include ++-#include /* stat() / struct stat via libnix/ADE */ ++-#include ++-#include "amiga/dirent.h" /* opendir / readdir / closedir */ ++- ++-/* snprintf/vsnprintf: ADE/libnix declares them in stdio.h (already included ++- * above via ). The implementation is provided by snprintf.c which ++- * must be linked when building the misc tools. No redeclaration needed */ ++- ++-#elif defined(VISUALCPP) ++-#include ++-#include ++-#include ++-#include "nt/dirwin32.h" /* opendir/readdir/closedir for MSVC */ ++-#elif defined(__MINGW32__) || defined(WIN32) ++-#include ++-#include /* MinGW provides dirent.h natively */ ++-#include ++-#include ++-#elif defined(OS2) && (defined(IBMC) || defined(__WATCOMC__)) ++-#include ++-#include ++-#include ++-#include "os2/dirent.h" /* opendir/readdir/closedir for OS/2 ICC/WC */ ++-#elif defined(OS2) ++-#include ++-#include /* EMX provides dirent.h natively */ ++-#include ++-#include ++-#include ++-#elif defined(DOS) ++-#include ++-#include ++-#include "dos/dirent.h" /* opendir/readdir/closedir for DOS/DJGPP */ ++-#else /* POSIX / *nix */ ++-#include ++-#include ++-#include ++-#include ++-#include ++-#endif ++- ++-#ifndef MAXPATHLEN ++-#if defined(_MAX_PATH) ++-#define MAXPATHLEN _MAX_PATH ++-#elif defined(PATH_MAX) ++-#define MAXPATHLEN PATH_MAX ++-#else ++-#define MAXPATHLEN 1024 ++-#endif ++-#endif ++- ++-/* Generic line buffer size for config files and text processing */ ++-#ifndef MAX_LINE ++-#define MAX_LINE 1024 ++-#endif ++- ++-/* path_exists / mkdir_one -- native implementations per OS */ ++-int port_path_exists(const char *p); ++-int port_mkdir_one(const char *p); ++-#define path_exists(p) port_path_exists(p) ++-#define mkdir_one(p) port_mkdir_one(p) ++- ++-/* safe_localtime -- thread-safe localtime, portable across all OS */ ++-void safe_localtime(const time_t *t, struct tm *tm); ++- ++-/* mkdir_recursive -- create full path, making all missing components */ ++-#define MP_MAXPATH 512 ++-int mkdir_recursive(const char *path); ++- ++-/* safe_strncpy -- strncpy that always NUL-terminates */ ++-void safe_strncpy(char *dst, const char *src, int dstsize); ++- ++-/* String utilities */ ++-void trim_nl(char *s); ++-void str_trim(char *s); ++-void str_upper(char *s); ++-void str_tolower(char *s); ++-char *skip_ws(char *s); ++- ++-/* Wildcard matching */ ++-int wildmatch(const char *pat, const char *str); ++-int is_wildcard(const char *s); ++- ++-/* File operations */ ++-int ensure_dir(const char *path); ++-int copy_file(const char *src, const char *dst); ++-int move_file(const char *src, const char *dst); ++-long get_file_size(const char *path); ++-long get_file_mtime(const char *path); ++- ++-/* Path utilities */ ++-void path_join(char *out, int outsize, const char *base, const char *sub); ++-int make_abs_path(const char *src, char *dst, int dstlen); ++- ++-#endif /* BINKD_PORTABLE_H */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/process_tic.c binkd_pgul/misc/process_tic.c ++--- binkd/misc/process_tic.c 2026-04-26 13:39:53.974576140 +0100 +++++ binkd_pgul/misc/process_tic.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,552 +0,0 @@ ++-/* ++- * process_tic -- Process FTN .tic files from inbound to filebox ++- * ++- * process_tic.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include "portable.h" /* Canonical portable layer */ ++-#include ++- ++-static int my_toupper(int c) ++-{ ++- if (c >= 'a' && c <= 'z') ++- return c - 'a' + 'A'; ++- ++- return c; ++-} ++- ++-static int my_strnicmp(const char *a, const char *b, int n) ++-{ ++- int i, ca, cb; ++- for (i = 0; i < n; i++) ++- { ++- ca = my_toupper((unsigned char)a[i]); ++- cb = my_toupper((unsigned char)b[i]); ++- ++- if (ca != cb) ++- return ca - cb; ++- ++- if (ca == 0) ++- return 0; ++- } ++- ++- return 0; ++-} ++- ++-static int parse_file_field(char *line, char *out, int outsize) ++-{ ++- char *p; ++- char *end; ++- int len; ++- ++- p = skip_ws(line); ++- ++- if (my_strnicmp(p, "File", 4) != 0) ++- return 0; ++- ++- p += 4; ++- ++- if (*p != ' ' && *p != '\t') ++- return 0; ++- ++- p = skip_ws(p); ++- trim_nl(p); ++- end = p; ++- ++- while (*end && *end != ' ' && *end != '\t') ++- end++; ++- ++- *end = '\0'; ++- ++- len = (int)strlen(p); ++- ++- if (len <= 0 || len >= outsize) ++- return 0; ++- ++- strncpy(out, p, outsize - 1); ++- out[outsize - 1] = '\0'; ++- ++- return 1; ++-} ++- ++-static int parse_area_field(char *line, char *out, int outsize) ++-{ ++- char *p; ++- char *end; ++- int len; ++- ++- p = skip_ws(line); ++- ++- if (my_strnicmp(p, "Area", 4) != 0) ++- return 0; ++- ++- p += 4; ++- ++- if (*p != ' ' && *p != '\t') ++- return 0; ++- ++- p = skip_ws(p); ++- trim_nl(p); ++- end = p; ++- ++- while (*end && *end != ' ' && *end != '\t') ++- end++; ++- ++- *end = '\0'; ++- len = (int)strlen(p); ++- ++- if (len <= 0 || len >= outsize) ++- return 0; ++- ++- strncpy(out, p, outsize - 1); ++- out[outsize - 1] = '\0'; ++- ++- return 1; ++-} ++- ++-static int parse_origin_field(char *line, char *out, int outsize) ++-{ ++- char *p; ++- char *end; ++- int len; ++- ++- p = skip_ws(line); ++- ++- if (my_strnicmp(p, "Origin", 6) != 0) ++- return 0; ++- ++- p += 6; ++- ++- if (*p != ' ' && *p != '\t') ++- return 0; ++- ++- p = skip_ws(p); ++- trim_nl(p); ++- end = p; ++- ++- while (*end && *end != ' ' && *end != '\t') ++- end++; ++- ++- *end = '\0'; ++- len = (int)strlen(p); ++- ++- if (len <= 0 || len >= outsize) ++- return 0; ++- ++- strncpy(out, p, outsize - 1); ++- out[outsize - 1] = '\0'; ++- ++- return 1; ++-} ++- ++-static int parse_from_field(char *line, char *out, int outsize) ++-{ ++- char *p; ++- char *end; ++- int len; ++- ++- p = skip_ws(line); ++- ++- if (my_strnicmp(p, "From", 4) != 0) ++- return 0; ++- ++- p += 4; ++- ++- if (*p != ' ' && *p != '\t') ++- return 0; ++- ++- p = skip_ws(p); ++- trim_nl(p); ++- end = p; ++- ++- while (*end && *end != ' ' && *end != '\t') ++- end++; ++- ++- *end = '\0'; ++- len = (int)strlen(p); ++- ++- if (len <= 0 || len >= outsize) ++- return 0; ++- ++- strncpy(out, p, outsize - 1); ++- out[outsize - 1] = '\0'; ++- ++- return 1; ++-} ++- ++-static void append_filelist(const char *listpath, const char *file_name, long filesize, const char *dst_path) ++-{ ++- FILE *f; ++- time_t t; ++- struct tm tm; ++- char timestamp[32]; ++- ++- if (!listpath || !listpath[0]) ++- return; ++- ++- f = fopen(listpath, "a"); ++- if (!f) ++- return; ++- ++- t = time(NULL); ++- safe_localtime(&t, &tm); ++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); ++- ++- fprintf(f, "%s\t%s\t%ld\t%s\n", timestamp, file_name, filesize, dst_path); ++- fclose(f); ++-} ++- ++-static void append_newfiles(const char *newprefix, const char *file_name, long filesize, const char *dst_path) ++-{ ++- FILE *f; ++- char newpath[MAXPATHLEN]; ++- time_t t; ++- struct tm tm; ++- char datebuf[16]; ++- ++- if (!newprefix || !newprefix[0]) ++- return; ++- ++- t = time(NULL); ++- safe_localtime(&t, &tm); ++- strftime(datebuf, sizeof(datebuf), "%Y%m%d", &tm); ++- ++- ensure_dir(newprefix); ++- path_join(newpath, (int)sizeof(newpath), newprefix, "newfiles-"); ++- strncat(newpath, datebuf, sizeof(newpath) - strlen(newpath) - 1); ++- strncat(newpath, ".txt", sizeof(newpath) - strlen(newpath) - 1); ++- ++- f = fopen(newpath, "a"); ++- ++- if (!f) ++- return; ++- ++- fprintf(f, "%s\t%ld\t%s\n", file_name, filesize, dst_path); ++- ++- fclose(f); ++-} ++- ++-static void write_ticlog(const char *ticlog, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) ++-{ ++- FILE *f; ++- time_t t; ++- struct tm tm; ++- char timestamp[64]; ++- ++- if (!ticlog || !ticlog[0]) ++- return; ++- ++- f = fopen(ticlog, "a"); ++- if (!f) ++- return; ++- ++- t = time(NULL); ++- safe_localtime(&t, &tm); ++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); ++- ++- fprintf(f, "[%s] File: %s\n", timestamp, file_name); ++- fprintf(f, " Area: %s\n", area_name); ++- ++- if (origin_name[0]) ++- fprintf(f, " Origin: %s\n", origin_name); ++- ++- if (from_name[0]) ++- fprintf(f, " From: %s\n", from_name); ++- ++- fprintf(f, " Src: %s\n", src_path); ++- fprintf(f, " To: %s\n", dst_path); ++- fprintf(f, "\n"); ++- ++- fclose(f); ++-} ++- ++-static void write_log(const char *logfile, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) ++-{ ++- FILE *f; ++- time_t t; ++- struct tm tm; ++- char timestamp[64]; ++- ++- if (!logfile || !logfile[0]) ++- return; ++- ++- f = fopen(logfile, "a"); ++- if (!f) ++- return; ++- ++- t = time(NULL); ++- safe_localtime(&t, &tm); ++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); ++- ++- fprintf(f, "[%s] File: %s\n", timestamp, file_name); ++- fprintf(f, " Area: %s\n", area_name); ++- ++- if (origin_name[0]) ++- fprintf(f, " Origin: %s\n", origin_name); ++- ++- if (from_name[0]) ++- fprintf(f, " From: %s\n", from_name); ++- ++- fprintf(f, " Src: %s\n", src_path); ++- fprintf(f, " To: %s\n", dst_path); ++- fprintf(f, "\n"); ++- fclose(f); ++-} ++- ++-static void process_one_tic(const char *ticpath, const char *inbound, const char *filebox, int copypublic, const char *pubdir, const char *logfile, const char *filelist, const char *newfiles, const char *ticlog) ++-{ ++- FILE *f; ++- char line[MAX_LINE]; ++- char file_name[MAXPATHLEN]; ++- char area_name[MAXPATHLEN]; ++- char origin_name[MAXPATHLEN]; ++- char from_name[MAXPATHLEN]; ++- char src_path[MAXPATHLEN]; ++- char area_dir[MAXPATHLEN]; ++- char dst_path[MAXPATHLEN]; ++- ++- file_name[0] = '\0'; ++- area_name[0] = '\0'; ++- origin_name[0] = '\0'; ++- from_name[0] = '\0'; ++- long fsize = 0; ++- ++- f = fopen(ticpath, "r"); ++- ++- if (!f) ++- return; ++- ++- while (fgets(line, sizeof(line), f)) ++- { ++- if (!file_name[0]) ++- parse_file_field(line, file_name, sizeof(file_name)); ++- ++- if (!area_name[0]) ++- parse_area_field(line, area_name, sizeof(area_name)); ++- ++- if (!origin_name[0]) ++- parse_origin_field(line, origin_name, sizeof(origin_name)); ++- ++- if (!from_name[0]) ++- parse_from_field(line, from_name, sizeof(from_name)); ++- } ++- ++- fclose(f); ++- ++- if (!file_name[0] || !area_name[0]) ++- return; ++- ++- path_join(src_path, sizeof(src_path), inbound, file_name); ++- path_join(area_dir, sizeof(area_dir), filebox, area_name); ++- path_join(dst_path, sizeof(dst_path), area_dir, file_name); ++- ++- if (!path_exists(src_path)) ++- return; ++- ++- if (!ensure_dir(filebox) || !ensure_dir(area_dir)) ++- return; ++- ++- if (copypublic && pubdir && pubdir[0]) ++- { ++- char pub_dst[MAXPATHLEN]; ++- ++- path_join(pub_dst, sizeof(pub_dst), pubdir, file_name); ++- ++- if (ensure_dir(pubdir)) ++- copy_file(src_path, pub_dst); ++- } ++- ++- fsize = get_file_size(src_path); ++- ++- if (!move_file(src_path, dst_path)) ++- return; ++- ++- write_log(logfile, file_name, area_name, origin_name, from_name, src_path, dst_path); ++- write_ticlog(ticlog, file_name, area_name, origin_name, from_name, src_path, dst_path); ++- append_filelist(filelist, file_name, fsize, dst_path); ++- append_newfiles(newfiles, file_name, fsize, dst_path); ++- ++- remove(ticpath); ++-} ++- ++-static int is_tic_file(const char *name) ++-{ ++- int len = (int)strlen(name); ++- ++- if (len < 5) ++- return 0; ++- ++- return (my_strnicmp(name + len - 4, ".tic", 4) == 0); ++-} ++- ++-/* Config structure */ ++-static struct ++-{ ++- char inbound[MAXPATHLEN]; ++- char filebox[MAXPATHLEN]; ++- char pubdir[MAXPATHLEN]; ++- char logfile[MAXPATHLEN]; ++- char filelist[MAXPATHLEN]; ++- char newfiles[MAXPATHLEN]; ++- char ticlog[MAXPATHLEN]; ++- int copypublic; ++-} cfg; ++- ++-/* Parse configuration file */ ++-static int parse_config(const char *conffile) ++-{ ++- FILE *f; ++- char line[MAX_LINE]; ++- char *key, *value; ++- ++- memset(&cfg, 0, sizeof(cfg)); ++- ++- f = fopen(conffile, "r"); ++- ++- if (!f) ++- { ++- fprintf(stderr, "process_tic: cannot open config file: %s\n", conffile); ++- return 0; ++- } ++- ++- while (fgets(line, sizeof(line), f)) ++- { ++- trim_nl(line); ++- key = skip_ws(line); ++- ++- /* Skip comments and empty lines */ ++- if (*key == '#' || *key == '\0') ++- continue; ++- ++- /* Find value after key */ ++- value = key; ++- ++- while (*value && *value != ' ' && *value != '\t') ++- value++; ++- ++- if (*value) ++- { ++- *value = '\0'; ++- value = skip_ws(value + 1); ++- } ++- ++- /* Parse key-value pairs */ ++- if (strcmp(key, "inbound") == 0) ++- safe_strncpy(cfg.inbound, value, (int)sizeof(cfg.inbound)); ++- else if (strcmp(key, "filebox") == 0) ++- safe_strncpy(cfg.filebox, value, (int)sizeof(cfg.filebox)); ++- else if (strcmp(key, "pubdir") == 0) ++- { ++- safe_strncpy(cfg.pubdir, value, (int)sizeof(cfg.pubdir)); ++- cfg.copypublic = 1; ++- } ++- else if (strcmp(key, "logfile") == 0) ++- safe_strncpy(cfg.logfile, value, (int)sizeof(cfg.logfile)); ++- else if (strcmp(key, "filelist") == 0) ++- safe_strncpy(cfg.filelist, value, (int)sizeof(cfg.filelist)); ++- else if (strcmp(key, "newfiles") == 0) ++- safe_strncpy(cfg.newfiles, value, (int)sizeof(cfg.newfiles)); ++- else if (strcmp(key, "ticlog") == 0) ++- safe_strncpy(cfg.ticlog, value, (int)sizeof(cfg.ticlog)); ++- } ++- ++- fclose(f); ++- ++- /* Validate required fields */ ++- if (!cfg.inbound[0] || !cfg.filebox[0]) ++- { ++- fprintf(stderr, "process_tic: config file missing required 'inbound' or 'filebox'\n"); ++- return 0; ++- } ++- ++- return 1; ++-} ++- ++-int main(int argc, char *argv[]) ++-{ ++- char inbound[MAXPATHLEN]; ++- char filebox[MAXPATHLEN]; ++- char ticpath[MAXPATHLEN]; ++- char pubdir[MAXPATHLEN]; ++- char logfile[MAXPATHLEN]; ++- char filelist[MAXPATHLEN]; ++- char newfiles[MAXPATHLEN]; ++- char ticlog[MAXPATHLEN]; ++- int copypublic = 0; ++- DIR *dp; ++- struct dirent *de; ++- int found; ++- int i; ++- int use_config = 0; ++- ++- inbound[0] = '\0'; ++- filebox[0] = '\0'; ++- pubdir[0] = '\0'; ++- logfile[0] = '\0'; ++- filelist[0] = '\0'; ++- newfiles[0] = '\0'; ++- ticlog[0] = '\0'; ++- ++- /* Check for --conf option */ ++- for (i = 1; i < argc; i++) ++- { ++- if (strcmp(argv[i], "--conf") == 0 && i + 1 < argc) ++- { ++- if (!parse_config(argv[i + 1])) ++- return 1; ++- ++- use_config = 1; ++- i++; /* Skip config file path */ ++- ++- break; ++- } ++- } ++- ++- if (use_config) ++- { ++- /* Use config file values */ ++- safe_strncpy(inbound, cfg.inbound, (int)sizeof(inbound)); ++- safe_strncpy(filebox, cfg.filebox, (int)sizeof(filebox)); ++- safe_strncpy(pubdir, cfg.pubdir, (int)sizeof(pubdir)); ++- safe_strncpy(logfile, cfg.logfile, (int)sizeof(logfile)); ++- safe_strncpy(filelist, cfg.filelist, (int)sizeof(filelist)); ++- safe_strncpy(newfiles, cfg.newfiles, (int)sizeof(newfiles)); ++- safe_strncpy(ticlog, cfg.ticlog, (int)sizeof(ticlog)); ++- copypublic = cfg.copypublic; ++- } ++- ++- if (!inbound[0] || !filebox[0]) ++- { ++- fprintf(stderr, ++- "Usage: process_tic --conf [*.tic]\n"); ++- ++- return 1; ++- } ++- ++- dp = opendir(inbound); ++- ++- if (!dp) ++- return 1; ++- ++- found = 0; ++- ++- while ((de = readdir(dp)) != NULL) ++- { ++- /* Skip . and .. */ ++- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) ++- continue; ++- ++- if (!is_tic_file(de->d_name)) ++- continue; ++- ++- path_join(ticpath, sizeof(ticpath), inbound, de->d_name); ++- process_one_tic(ticpath, inbound, filebox, copypublic, pubdir, logfile, filelist, newfiles, ticlog); ++- found++; ++- } ++- ++- closedir(dp); ++- return 0; ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/process_tic.txt binkd_pgul/misc/process_tic.txt ++--- binkd/misc/process_tic.txt 2026-04-26 13:43:48.542112490 +0100 +++++ binkd_pgul/misc/process_tic.txt 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,45 +0,0 @@ ++-process_tic -- Process .tic file announcements ++- ++-USAGE: ++- process_tic --conf [files.tic...] ++- ++-DESCRIPTION: ++- Processes .tic (Ticker announcement) files from the inbound directory ++- and moves/copies files to their destination filebox or public directory. ++- ++- The .tic file is parsed for File, Area, Origin, and From fields. ++- The actual file is moved from inbound to filebox/AreaName/. ++- ++- All settings are read from the configuration file. ++- ++-OPTIONS: ++- --conf Configuration file (required) ++- ++-CONFIGURATION FILE FORMAT: ++- # Lines starting with # are comments ++- # Blank lines are ignored ++- ++- inbound Inbound directory (required) ++- filebox Filebox destination (required) ++- pubdir Public directory for --copy-public ++- logfile Log file path ++- ticlog TIC processing log ++- filelist File list output ++- newfiles New files list output ++- ++-EXAMPLE CONFIG FILE (process_tic.conf): ++- # process_tic.conf - Configuration for TIC processor ++- ++- inbound Work:Inbound ++- filebox Work:Filebox ++- pubdir Work:Public ++- logfile Work:Logs/process_tic.log ++- ticlog Work:Logs/tic.log ++- filelist Work:Filebox/filelist.txt ++- newfiles Work:Filebox/newfiles.txt ++- ++-EXAMPLES: ++- process_tic --conf process_tic.conf ++- process_tic --conf process_tic.conf inbound/*.tic ++- ++- exec "process_tic --conf work:fido/process_tic.conf" *.tic *.TIC ++\ No hay ningún carácter de nueva línea al final del archivo ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/srifreq.c binkd_pgul/misc/srifreq.c ++--- binkd/misc/srifreq.c 2026-04-26 15:01:11.006850708 +0100 +++++ binkd_pgul/misc/srifreq.c 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,1024 +0,0 @@ ++-/* ++- * srifreq.c -- SRIF-compatible file-request server for binkd ++- * ++- * srifreq.c is a part of binkd project ++- * ++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++- * Licensed under the GNU GPL v2 or later ++- */ ++- ++-#include "portable.h" /* Canonical portable layer */ ++-#include ++-#include ++- ++-/* Private directory entry (dynamically allocated list) */ ++-typedef struct PrivDir ++-{ ++- char path[MAXPATHLEN]; ++- char password[64]; ++- struct PrivDir *next; ++-} PrivDir; ++- ++-/* Node tracking entry for rate limiting */ ++-typedef struct NodeTrack ++-{ ++- char aka[256]; /* Node address (4D/5D) */ ++- int files; /* Files downloaded in window */ ++- long bytes; /* Bytes downloaded in window */ ++- time_t last_time; /* Timestamp of last download */ ++- struct NodeTrack *next; ++-} NodeTrack; ++- ++-/* Global configuration (filled from --conf file) */ ++-typedef struct ++-{ ++- char pubdir[MAXPATHLEN]; ++- char logfile[MAXPATHLEN]; ++- char aliases[MAXPATHLEN]; ++- char trackfile[MAXPATHLEN]; /* Path to tracking file */ ++- int maxfiles; /* Max files per node per window (0=unlimited) */ ++- long maxbytes; /* Max bytes per node per window (0=unlimited) */ ++- long timewindow; /* Time window in seconds (0=no window) */ ++- PrivDir *privdirs; /* Linked list, NULL if none */ ++- NodeTrack *tracking; /* Linked list of tracked nodes */ ++-} Config; ++- ++-/* Alias table -- Loaded from file at startup */ ++-typedef struct ++-{ ++- char name[64]; ++- char path[MAXPATHLEN]; ++-} Alias; ++- ++-/* SRIF parsing */ ++-typedef struct ++-{ ++- char sysop[128]; ++- char aka[256]; ++- char request_list[MAXPATHLEN]; ++- char response_list[MAXPATHLEN]; ++- char our_aka[128]; ++- char caller_id[64]; /* CallerID: IP or phone of remote */ ++- char password[64]; /* Password: session password */ ++- int time_limit; /* Time: minutes left, -1 = unlimited */ ++- long tranx; /* TRANX: remote local time as Unix ts (hex in SRIF) */ ++- int protected_sess; /* RemoteStatus: 1=PROTECTED, 0=UNPROTECTED */ ++- int listed; /* SystemStatus: 1=LISTED, 0=UNLISTED */ ++- int got_request_list; ++- int got_response_list; ++-} SRIF; ++- ++-static Alias *g_aliases = NULL; ++-static int g_nalias = 0; ++-static int g_alias_cap = 0; ++-static Config g_conf; ++- ++-static void config_init(void) ++-{ ++- memset(&g_conf, 0, sizeof(g_conf)); ++- g_conf.privdirs = NULL; ++- g_conf.tracking = NULL; ++- g_conf.maxfiles = 0; /* 0 = unlimited */ ++- g_conf.maxbytes = 0; /* 0 = unlimited */ ++- g_conf.timewindow = 0; /* 0 = no window */ ++-} ++- ++-static void config_add_private(const char *path, const char *password) ++-{ ++- PrivDir *pd = (PrivDir *)malloc(sizeof(PrivDir)); ++- PrivDir *tail; ++- ++- if (!pd) ++- return; ++- ++- safe_strncpy(pd->path, path, (int)sizeof(pd->path)); ++- safe_strncpy(pd->password, password, (int)sizeof(pd->password)); ++- ++- pd->next = NULL; ++- ++- /* Append to tail */ ++- if (!g_conf.privdirs) ++- g_conf.privdirs = pd; ++- else ++- { ++- tail = g_conf.privdirs; ++- ++- while (tail->next) ++- tail = tail->next; ++- ++- tail->next = pd; ++- } ++-} ++- ++-static void config_free(void) ++-{ ++- PrivDir *pd = g_conf.privdirs; ++- NodeTrack *nt = g_conf.tracking; ++- ++- while (pd) ++- { ++- PrivDir *next = pd->next; ++- free(pd); ++- pd = next; ++- } ++- ++- g_conf.privdirs = NULL; ++- ++- while (nt) ++- { ++- NodeTrack *next = nt->next; ++- free(nt); ++- nt = next; ++- } ++- ++- g_conf.tracking = NULL; ++- ++- if (g_aliases) ++- { ++- free(g_aliases); ++- g_aliases = NULL; ++- g_nalias = 0; ++- g_alias_cap = 0; ++- } ++-} ++- ++-static int load_config(const char *path) ++-{ ++- FILE *f; ++- char line[MAX_LINE]; ++- char key[64], val[MAXPATHLEN], pw[64]; ++- int n; ++- ++- f = fopen(path, "r"); ++- ++- if (!f) ++- { ++- fprintf(stderr, "srifreq: cannot open config: %s\n", path); ++- return 0; ++- } ++- ++- while (fgets(line, sizeof(line), f)) ++- { ++- /* Strip trailing whitespace and newlines */ ++- n = (int)strlen(line); ++- ++- while (n > 0 && ++- (line[n - 1] == '\r' || line[n - 1] == '\n' || line[n - 1] == ' ')) ++- line[--n] = '\0'; ++- ++- /* Skip blank and comment lines */ ++- if (!line[0] || line[0] == '#') ++- continue; ++- ++- key[0] = val[0] = pw[0] = '\0'; ++- ++- if (sscanf(line, "%63s %1023s %63s", key, val, pw) < 2) ++- continue; ++- ++- if (strcmp(key, "pubdir") == 0) ++- safe_strncpy(g_conf.pubdir, val, (int)sizeof(g_conf.pubdir)); ++- else if (strcmp(key, "logfile") == 0) ++- safe_strncpy(g_conf.logfile, val, (int)sizeof(g_conf.logfile)); ++- else if (strcmp(key, "aliases") == 0) ++- safe_strncpy(g_conf.aliases, val, (int)sizeof(g_conf.aliases)); ++- else if (strcmp(key, "trackfile") == 0) ++- safe_strncpy(g_conf.trackfile, val, (int)sizeof(g_conf.trackfile)); ++- else if (strcmp(key, "maxfiles") == 0) ++- g_conf.maxfiles = atoi(val); ++- else if (strcmp(key, "maxsize") == 0) ++- g_conf.maxbytes = atol(val); ++- else if (strcmp(key, "timewindow") == 0) ++- g_conf.timewindow = atol(val); ++- else if (strcmp(key, "private") == 0 && pw[0]) ++- config_add_private(val, pw); ++- } ++- ++- fclose(f); ++- ++- return 1; ++-} ++- ++-/* tracking_load -- Load node tracking data from file */ ++-static void tracking_load(void) ++-{ ++- FILE *f; ++- char line[MAX_LINE]; ++- char aka[256]; ++- int files; ++- long bytes; ++- long timestamp; ++- NodeTrack *nt; ++- time_t now = time(NULL); ++- ++- if (!g_conf.trackfile[0]) ++- return; ++- ++- f = fopen(g_conf.trackfile, "r"); ++- ++- if (!f) ++- return; ++- ++- while (fgets(line, sizeof(line), f)) ++- { ++- str_trim(line); ++- ++- if (!line[0] || line[0] == '#') ++- continue; ++- ++- if (sscanf(line, "%255s %d %ld %ld", aka, &files, &bytes, ×tamp) != 4) ++- continue; ++- ++- /* Skip if outside time window (also reject future/corrupt timestamps) */ ++- if (g_conf.timewindow > 0 && (timestamp > now || (now - timestamp) > g_conf.timewindow)) ++- continue; ++- ++- /* Create new tracking entry */ ++- nt = (NodeTrack *)malloc(sizeof(NodeTrack)); ++- ++- if (!nt) ++- continue; ++- ++- safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); ++- nt->files = files; ++- nt->bytes = bytes; ++- nt->last_time = (time_t)timestamp; ++- nt->next = g_conf.tracking; ++- g_conf.tracking = nt; ++- } ++- ++- fclose(f); ++-} ++- ++-/* tracking_save -- Save node tracking data to file */ ++-static void tracking_save(void) ++-{ ++- FILE *f; ++- NodeTrack *nt; ++- ++- if (!g_conf.trackfile[0]) ++- return; ++- ++- f = fopen(g_conf.trackfile, "w"); ++- ++- if (!f) ++- return; ++- ++- fprintf(f, "# srifreq tracking file - Format: AKA files bytes timestamp\n"); ++- ++- for (nt = g_conf.tracking; nt; nt = nt->next) ++- { ++- fprintf(f, "%s %d %ld %ld\n", nt->aka, nt->files, nt->bytes, (long)nt->last_time); ++- } ++- ++- fclose(f); ++-} ++- ++-/* tracking_find -- Find tracking entry for a node */ ++-static NodeTrack *tracking_find(const char *aka) ++-{ ++- NodeTrack *nt; ++- ++- for (nt = g_conf.tracking; nt; nt = nt->next) ++- { ++- if (strcmp(nt->aka, aka) == 0) ++- return nt; ++- } ++- ++- return NULL; ++-} ++- ++-/* tracking_update -- Update tracking after serving a file */ ++-static void tracking_update(const char *aka, long filesize) ++-{ ++- NodeTrack *nt = tracking_find(aka); ++- time_t now = time(NULL); ++- ++- if (nt) ++- { ++- /* Update existing entry */ ++- nt->files++; ++- nt->bytes += filesize; ++- nt->last_time = now; ++- } ++- else ++- { ++- /* Create new entry */ ++- nt = (NodeTrack *)malloc(sizeof(NodeTrack)); ++- ++- if (nt) ++- { ++- safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); ++- nt->files = 1; ++- nt->bytes = filesize; ++- nt->last_time = now; ++- nt->next = g_conf.tracking; ++- g_conf.tracking = nt; ++- } ++- } ++-} ++- ++-/* tracking_check -- Check if node exceeds limits */ ++-static int tracking_check(const char *aka, char *msg, int msglen) ++-{ ++- NodeTrack *nt = tracking_find(aka); ++- ++- if (!nt) ++- return 1; /* No tracking yet, allow */ ++- ++- /* Check max files */ ++- if (g_conf.maxfiles > 0 && nt->files >= g_conf.maxfiles) ++- { ++- snprintf(msg, msglen, "RATE LIMIT: max files (%d) reached for %s", g_conf.maxfiles, aka); ++- return 0; ++- } ++- ++- /* Check max bytes */ ++- if (g_conf.maxbytes > 0 && nt->bytes >= g_conf.maxbytes) ++- { ++- snprintf(msg, msglen, "RATE LIMIT: max bytes (%ld) reached for %s", g_conf.maxbytes, aka); ++- return 0; ++- } ++- ++- return 1; /* Within limits */ ++-} ++- ++-/* is_abs_path -- True if path is absolute (POSIX, Win32, AmigaDOS device:) */ ++-static int is_abs_path(const char *p) ++-{ ++- if (!p || !p[0]) ++- return 0; ++- ++- if (p[0] == '/' || p[0] == '\\') ++- return 1; ++- ++-#ifdef AMIGA ++- if (strchr(p, ':') != NULL) ++- return 1; ++-#else ++- if (p[1] == ':') ++- return 1; /* C:\ etc. */ ++-#endif ++- return 0; ++-} ++- ++-/* ++- * load_aliases -- Read alias definitions from file ++- * Lines starting with '#' or empty are skipped ++- * Format: ++- */ ++-static void load_aliases(const char *filepath) ++-{ ++- FILE *f; ++- char line[MAX_LINE]; ++- char name[64]; ++- char path[MAXPATHLEN]; ++- int n; ++- ++- /* Free previous aliases and start fresh */ ++- if (g_aliases) ++- { ++- free(g_aliases); ++- g_aliases = NULL; ++- } ++- ++- g_nalias = 0; ++- g_alias_cap = 0; ++- ++- if (!filepath || !filepath[0] || strcmp(filepath, "-") == 0) ++- return; ++- ++- f = fopen(filepath, "r"); ++- ++- if (!f) ++- { ++- /*fprintf(stderr, "srifreq: cannot open aliases file: %s\n", filepath);*/ ++- return; ++- } ++- ++- while (fgets(line, sizeof(line), f)) ++- { ++- char *p; ++- ++- /* Strip trailing newline */ ++- n = (int)strlen(line); ++- ++- while (n > 0 && (line[n - 1] == '\r' || line[n - 1] == '\n')) ++- line[--n] = '\0'; ++- ++- /* Skip blanks and comments */ ++- p = line; ++- ++- while (*p == ' ' || *p == '\t') ++- p++; ++- ++- if (!*p || *p == '#') ++- continue; ++- ++- name[0] = '\0'; ++- path[0] = '\0'; ++- ++- if (sscanf(p, "%63s %1023[^\n]", name, path) < 2) ++- continue; ++- ++- if (!name[0] || !path[0]) ++- continue; ++- ++- /* Grow array dynamically if needed */ ++- if (g_nalias >= g_alias_cap) ++- { ++- int new_cap = g_alias_cap ? g_alias_cap * 2 : 16; ++- Alias *new_arr = realloc(g_aliases, (size_t)new_cap * sizeof(Alias)); ++- ++- if (!new_arr) ++- break; ++- ++- g_aliases = new_arr; ++- g_alias_cap = new_cap; ++- } ++- ++- safe_strncpy(g_aliases[g_nalias].name, name, (int)sizeof(g_aliases[g_nalias].name)); ++- safe_strncpy(g_aliases[g_nalias].path, path, (int)sizeof(g_aliases[g_nalias].path)); ++- g_nalias++; ++- } ++- ++- fclose(f); ++- ++- /*printf("srifreq: loaded %d alias(es) from %s\n", g_nalias, filepath);*/ ++-} ++- ++-/* ++- * find_alias -- look up name in alias table (case-insensitive) ++- * Returns the path string, or NULL if not found ++- */ ++-static const char *find_alias(const char *name) ++-{ ++- char upper[64]; ++- char aname[64]; ++- int i; ++- int n; ++- ++- /* Convert name to uppercase */ ++- n = (int)strlen(name); ++- ++- if (n >= (int)sizeof(upper)) ++- n = (int)sizeof(upper) - 1; ++- ++- for (i = 0; i < n; i++) ++- upper[i] = (char)toupper((unsigned char)name[i]); ++- ++- upper[n] = '\0'; ++- ++- for (i = 0; i < g_nalias; i++) ++- { ++- int an; ++- an = (int)strlen(g_aliases[i].name); ++- ++- if (an >= (int)sizeof(aname)) an = (int)sizeof(aname) - 1; ++- { ++- int j; ++- ++- for (j = 0; j < an; j++) ++- aname[j] = (char)toupper((unsigned char)g_aliases[i].name[j]); ++- ++- aname[an] = '\0'; ++- } ++- ++- if (strcmp(upper, aname) == 0) ++- return g_aliases[i].path; ++- } ++- ++- return NULL; ++-} ++- ++-static int parse_srif(const char *path, SRIF *srif) ++-{ ++- FILE *f; ++- char line[MAX_LINE]; ++- char token[MAX_LINE]; ++- char value[MAX_LINE]; ++- ++- memset(srif, 0, sizeof(SRIF)); ++- srif->time_limit = -1; /* default: unlimited */ ++- srif->listed = 1; /* default: assume listed */ ++- srif->protected_sess = 0; ++- ++- f = fopen(path, "r"); ++- if (!f) ++- return 0; ++- ++- while (fgets(line, sizeof(line), f)) ++- { ++- str_trim(line); ++- if (!line[0]) ++- continue; ++- ++- token[0] = '\0'; ++- value[0] = '\0'; ++- ++- if (sscanf(line, "%1023s %1023[^\n]", token, value) < 1) ++- continue; ++- ++- str_upper(token); ++- ++- if (!value[0]) ++- continue; ++- ++- if (!strcmp(token, "SYSOP")) ++- safe_strncpy(srif->sysop, value, (int)sizeof(srif->sysop)); ++- else if (!strcmp(token, "AKA") && !srif->aka[0]) ++- safe_strncpy(srif->aka, value, (int)sizeof(srif->aka)); ++- else if (!strcmp(token, "REQUESTLIST")) ++- { ++- safe_strncpy(srif->request_list, value, MAXPATHLEN); ++- srif->got_request_list = 1; ++- } ++- else if (!strcmp(token, "RESPONSELIST")) ++- { ++- safe_strncpy(srif->response_list, value, MAXPATHLEN); ++- srif->got_response_list = 1; ++- } ++- else if (!strcmp(token, "OURAKA")) ++- safe_strncpy(srif->our_aka, value, (int)sizeof(srif->our_aka)); ++- else if (!strcmp(token, "PASSWORD")) ++- safe_strncpy(srif->password, value, (int)sizeof(srif->password)); ++- else if (!strcmp(token, "CALLERID")) ++- safe_strncpy(srif->caller_id, value, (int)sizeof(srif->caller_id)); ++- else if (!strcmp(token, "TIME")) ++- srif->time_limit = atoi(value); ++- else if (!strcmp(token, "TRANX")) ++- { ++- /* TRANX is a hex Unix timestamp: 5a326682 or 16-digit */ ++- unsigned long v = 0; ++- sscanf(value, "%lx", &v); ++- srif->tranx = (long)v; ++- } ++- else if (!strcmp(token, "REMOTESTATUS")) ++- { ++- char tmp[32]; ++- safe_strncpy(tmp, value, (int)sizeof(tmp)); ++- str_upper(tmp); ++- srif->protected_sess = (strncmp(tmp, "PROTECTED", 9) == 0) ? 1 : 0; ++- } ++- else if (!strcmp(token, "SYSTEMSTATUS")) ++- { ++- char tmp[32]; ++- safe_strncpy(tmp, value, (int)sizeof(tmp)); ++- str_upper(tmp); ++- srif->listed = (strncmp(tmp, "LISTED", 6) == 0) ? 1 : 0; ++- } ++- } ++- ++- fclose(f); ++- ++- return srif->got_request_list; ++-} ++- ++-/* Logging */ ++-static void do_log(const char *logpath, const char *msg) ++-{ ++- FILE *lf; ++- time_t t; ++- struct tm tm; ++- char timestamp[32]; ++- ++- if (!logpath || !logpath[0] || strcmp(logpath, "-") == 0) ++- return; ++- ++- t = time(NULL); ++- safe_localtime(&t, &tm); ++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); ++- ++- lf = fopen(logpath, "a"); ++- ++- if (lf) ++- { ++- fprintf(lf, "[%s] srifreq: %s\n", timestamp, msg); ++- fclose(lf); ++- } ++-} ++- ++-/* serve_one -- resolve one request name, check password/timestamp/update ++- * write to response list. Returns 1 if served ++- */ ++-static int serve_one(const char *req_name, const char *found_path, const char *req_pass, long req_newer, int req_update, const SRIF *srif, FILE *rsp_f, const char *log_path, char *logbuf, int logbuf_size) ++-{ ++- long fsize; ++- ++- /* Check rate limits before serving */ ++- if (g_conf.trackfile[0] && !tracking_check(srif->aka, logbuf, logbuf_size)) ++- { ++- do_log(log_path, logbuf); ++- return 0; ++- } ++- ++- /* RemoteStatus: if session is unprotected and a password is required, deny */ ++- if (req_pass[0] && !srif->protected_sess) ++- { ++- snprintf(logbuf, logbuf_size, "PASSWORD DENY (unprotected session): %s", req_name); ++- do_log(log_path, logbuf); ++- return 0; ++- } ++- ++- /* Password check: !pw must match SRIF PASSWORD (case-insensitive) */ ++- if (req_pass[0]) ++- { ++- char rp[64], sp[64]; ++- int i; ++- ++- safe_strncpy(rp, req_pass, (int)sizeof(rp)); ++- safe_strncpy(sp, srif->password, (int)sizeof(sp)); ++- ++- for (i = 0; rp[i]; i++) ++- rp[i] = (char)toupper((unsigned char)rp[i]); ++- ++- for (i = 0; sp[i]; i++) ++- sp[i] = (char)toupper((unsigned char)sp[i]); ++- ++- if (strcmp(rp, sp) != 0) ++- { ++- snprintf(logbuf, logbuf_size, "PASSWORD FAIL: %s", req_name); ++- do_log(log_path, logbuf); ++- ++- return 0; ++- } ++- } ++- ++- /* Update request (U flag): serve only if file is newer than TRANX */ ++- if (req_update && srif->tranx > 0) ++- { ++- long mtime = get_file_mtime(found_path); ++- ++- if (mtime <= srif->tranx) ++- { ++- snprintf(logbuf, logbuf_size, "NOT UPDATED: %s (mtime=%ld tranx=%ld)", req_name, mtime, srif->tranx); ++- do_log(log_path, logbuf); ++- return 0; ++- } ++- } ++- ++- /* Timestamp check: +ts means "only if file is newer than ts" */ ++- if (req_newer > 0) ++- { ++- long mtime = get_file_mtime(found_path); ++- ++- if (mtime <= req_newer) ++- { ++- snprintf(logbuf, logbuf_size, "NOT NEWER: %s (mtime=%ld req=%ld)", req_name, mtime, req_newer); ++- ++- do_log(log_path, logbuf); ++- return 0; ++- } ++- } ++- ++- snprintf(logbuf, logbuf_size, "FOUND: %s -> %s", req_name, found_path); ++- do_log(log_path, logbuf); ++- ++- if (rsp_f) ++- fprintf(rsp_f, "+%s\r\n", found_path); ++- ++- /* Update tracking after successful serve */ ++- if (g_conf.trackfile[0]) ++- { ++- fsize = get_file_size(found_path); ++- ++- if (fsize < 0) ++- fsize = 0; ++- ++- tracking_update(srif->aka, fsize); ++- } ++- ++- return 1; ++-} ++- ++-int main(int argc, char *argv[]) ++-{ ++- const char *srif_path; ++- SRIF srif; ++- FILE *req_f; ++- FILE *rsp_f; ++- char line[MAX_LINE]; ++- char req_name[MAX_LINE]; ++- char req_pass[64]; ++- long req_newer; ++- int req_update; ++- char found_path[MAXPATHLEN]; ++- char logbuf[MAXPATHLEN * 4 + 128]; ++- int found_count; ++- ++- config_init(); ++- ++- /* --conf */ ++- if (argc >= 4 && strcmp(argv[1], "--conf") == 0) ++- { ++- if (!load_config(argv[2])) ++- return 1; ++- ++- srif_path = argv[3]; ++- } ++- else ++- { ++- fprintf(stderr, "Usage:\n" ++- " srifreq --conf \n" ++- "\n" ++- "Config file keys: pubdir, logfile, aliases, private " ++- "\n"); ++- ++- return 1; ++- } ++- ++- if (!g_conf.pubdir[0]) ++- { ++- fprintf(stderr, "srifreq: pubdir not set\n"); ++- config_free(); ++- return 1; ++- } ++- ++- /* Load tracking data if configured */ ++- tracking_load(); ++- ++- load_aliases(g_conf.aliases[0] ? g_conf.aliases : NULL); ++- ++- snprintf(logbuf, sizeof(logbuf), "processing SRIF: %s", srif_path); ++- do_log(g_conf.logfile, logbuf); ++- ++- if (!parse_srif(srif_path, &srif)) ++- { ++- snprintf(logbuf, sizeof(logbuf), "ERROR: cannot parse SRIF or missing RequestList: %s", srif_path); ++- do_log(g_conf.logfile, logbuf); ++- fprintf(stderr, "srifreq: %s\n", logbuf); ++- config_free(); ++- return 1; ++- } ++- ++- /* SystemStatus: deny unlisted systems entirely */ ++- if (!srif.listed) ++- { ++- snprintf(logbuf, sizeof(logbuf), "DENIED: system is UNLISTED (aka: %s)", srif.aka); ++- do_log(g_conf.logfile, logbuf); ++- config_free(); ++- return 1; ++- } ++- ++- snprintf(logbuf, sizeof(logbuf), "sysop: %s aka: %s status: %s%s caller: %s req: %s", srif.sysop, srif.aka, srif.protected_sess ? "PROTECTED" : "UNPROTECTED", srif.tranx ? " (TRANX)" : "", srif.caller_id[0] ? srif.caller_id : "?", srif.request_list); ++- do_log(g_conf.logfile, logbuf); ++- ++- /* Log rate limiting status if active */ ++- if (g_conf.trackfile[0] && (g_conf.maxfiles > 0 || g_conf.maxbytes > 0)) ++- { ++- snprintf(logbuf, sizeof(logbuf), "rate limits: maxfiles=%d maxbytes=%ld window=%lds", g_conf.maxfiles, g_conf.maxbytes, g_conf.timewindow); ++- do_log(g_conf.logfile, logbuf); ++- } ++- ++- req_f = fopen(srif.request_list, "r"); ++- ++- if (!req_f) ++- { ++- snprintf(logbuf, sizeof(logbuf), "WARN: RequestList not available: %s", srif.request_list); ++- do_log(g_conf.logfile, logbuf); ++- config_free(); ++- return 0; ++- } ++- ++- rsp_f = NULL; ++- ++- if (srif.got_response_list && srif.response_list[0]) ++- { ++- rsp_f = fopen(srif.response_list, "w"); ++- ++- if (!rsp_f) ++- { ++- snprintf(logbuf, sizeof(logbuf), "WARN: cannot create ResponseList: %s", srif.response_list); ++- do_log(g_conf.logfile, logbuf); ++- } ++- } ++- ++- found_count = 0; ++- ++- /* Check and update track file */ ++- while (fgets(line, sizeof(line), req_f)) ++- { ++- char *p; ++- const char *alias_path; ++- ++- str_trim(line); ++- ++- if (!line[0] || line[0] == ';' || line[0] == '#') ++- continue; ++- ++- /* Parse: filename [!password] [+timestamp] [U] */ ++- req_name[0] = '\0'; ++- req_pass[0] = '\0'; ++- req_newer = 0; ++- req_update = 0; ++- ++- if (sscanf(line, "%1023s", req_name) < 1) ++- continue; ++- ++- /* Skip URLs */ ++- if (strncmp(req_name, "http", 4) == 0 || strncmp(req_name, "ftp", 3) == 0) ++- continue; ++- ++- /* Parse modifiers from the rest of the line */ ++- p = strstr(line, req_name); ++- ++- if (p) ++- p += strlen(req_name); ++- else ++- p = line + strlen(line); ++- ++- while (*p) ++- { ++- while (*p == ' ' || *p == '\t') ++- p++; ++- ++- if (*p == '!') ++- { ++- /* !password */ ++- int i = 0; ++- p++; ++- ++- while (*p && *p != ' ' && *p != '\t' && i < (int)sizeof(req_pass) - 1) ++- req_pass[i++] = *p++; ++- ++- req_pass[i] = '\0'; ++- } ++- else if (*p == '+') ++- { ++- /* +unix_timestamp */ ++- p++; ++- req_newer = atol(p); ++- ++- while (*p && *p != ' ' && *p != '\t') ++- p++; ++- } ++- else if (*p == 'U' && (p[1] == '\0' || p[1] == ' ' || p[1] == '\t')) ++- { ++- /* U = update request */ ++- req_update = 1; ++- p++; ++- } ++- else if (*p) ++- p++; /* Skip unknown token */ ++- } ++- ++- found_path[0] = '\0'; ++- ++- /* Check alias table first */ ++- alias_path = find_alias(req_name); ++- ++- if (alias_path) ++- { ++- if (is_abs_path(alias_path)) ++- safe_strncpy(found_path, alias_path, MAXPATHLEN); ++- else ++- path_join(found_path, MAXPATHLEN, g_conf.pubdir, alias_path); ++- ++- if (path_exists(found_path)) ++- found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++- else ++- { ++- snprintf(logbuf, sizeof(logbuf), "NOT FOUND (alias): %s -> %s", req_name, found_path); ++- do_log(g_conf.logfile, logbuf); ++- } ++- ++- continue; ++- } ++- ++- /* Wildcard: scan pubdir and all privdirs whose password matches */ ++- if (is_wildcard(req_name)) ++- { ++- DIR *dp; ++- struct dirent *de; ++- PrivDir *pd; ++- ++- /* Scan pubdir (no password needed) */ ++- dp = opendir(g_conf.pubdir); ++- if (dp) ++- { ++- while ((de = readdir(dp)) != NULL) ++- { ++- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) ++- continue; ++- ++- if (!wildmatch(req_name, de->d_name)) ++- continue; ++- ++- path_join(found_path, MAXPATHLEN, g_conf.pubdir, de->d_name); ++- ++- if (path_exists(found_path)) ++- found_count += serve_one(de->d_name, found_path, "", req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++- } ++- ++- closedir(dp); ++- } ++- ++- /* Scan matching privdirs */ ++- for (pd = g_conf.privdirs; pd; pd = pd->next) ++- { ++- char rp[64], pp[64]; ++- int ci; ++- ++- safe_strncpy(rp, req_pass, (int)sizeof(rp)); ++- safe_strncpy(pp, pd->password, (int)sizeof(pp)); ++- ++- for (ci = 0; rp[ci]; ci++) ++- rp[ci] = (char)toupper((unsigned char)rp[ci]); ++- ++- for (ci = 0; pp[ci]; ci++) ++- pp[ci] = (char)toupper((unsigned char)pp[ci]); ++- ++- if (strcmp(rp, pp) != 0) ++- continue; ++- ++- dp = opendir(pd->path); ++- ++- if (!dp) ++- continue; ++- ++- while ((de = readdir(dp)) != NULL) ++- { ++- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) ++- continue; ++- ++- if (!wildmatch(req_name, de->d_name)) ++- continue; ++- ++- path_join(found_path, MAXPATHLEN, pd->path, de->d_name); ++- ++- if (path_exists(found_path)) ++- found_count += serve_one(de->d_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++- } ++- ++- closedir(dp); ++- } ++- ++- continue; ++- } ++- ++- /* Plain filename: try pubdir first, then privdirs if password given */ ++- path_join(found_path, MAXPATHLEN, g_conf.pubdir, req_name); ++- ++- if (path_exists(found_path)) ++- { ++- found_count += ++- serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++- } ++- else if (req_pass[0]) ++- { ++- /* Try each private dir whose password matches */ ++- PrivDir *pd; ++- int served = 0; ++- ++- for (pd = g_conf.privdirs; pd && !served; pd = pd->next) ++- { ++- char rp[64], pp[64]; ++- int ci; ++- ++- safe_strncpy(rp, req_pass, (int)sizeof(rp)); ++- safe_strncpy(pp, pd->password, (int)sizeof(pp)); ++- ++- for (ci = 0; rp[ci]; ci++) ++- rp[ci] = (char)toupper((unsigned char)rp[ci]); ++- ++- for (ci = 0; pp[ci]; ci++) ++- pp[ci] = (char)toupper((unsigned char)pp[ci]); ++- ++- if (strcmp(rp, pp) != 0) ++- continue; ++- ++- path_join(found_path, MAXPATHLEN, pd->path, req_name); ++- ++- if (path_exists(found_path)) ++- { ++- found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++- served = 1; ++- } ++- } ++- if (!served) ++- { ++- snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s", req_name); ++- do_log(g_conf.logfile, logbuf); ++- } ++- } ++- else ++- { ++- snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s (pub: %s)", req_name, g_conf.pubdir); ++- do_log(g_conf.logfile, logbuf); ++- } ++- } ++- ++- fclose(req_f); ++- ++- if (rsp_f) ++- fclose(rsp_f); ++- ++- snprintf(logbuf, sizeof(logbuf), "done: %d file(s) found", found_count); ++- do_log(g_conf.logfile, logbuf); ++- ++- /* Save tracking data */ ++- tracking_save(); ++- ++- config_free(); ++- ++- return (found_count > 0) ? 0 : 1; ++-} ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/srifreq.txt binkd_pgul/misc/srifreq.txt ++--- binkd/misc/srifreq.txt 2026-04-26 13:54:14.035795187 +0100 +++++ binkd_pgul/misc/srifreq.txt 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,67 +0,0 @@ ++-srifreq -- SRIF-compatible file request server ++- ++-USAGE: ++- srifreq --conf ++- ++-DESCRIPTION: ++- SRIF (Standard Request Information Format) compatible file ++- request server for binkd. Processes incoming .req files and ++- serves files based on password protection and aliases. ++- ++-CONFIGURATION FILE FORMAT: ++- # Lines starting with # are comments ++- # Blank lines are ignored ++- ++- pubdir # Public directory (no password required) ++- logfile # Log file, or - to disable ++- aliases # Magic-name aliases file (optional) ++- private # Private directory (requires !password) ++- ++- # Rate limiting options (optional): ++- trackfile # File to track node download statistics ++- maxfiles # Max files per node per time window (0=unlimited) ++- maxsize # Max bytes per node per time window (0=unlimited) ++- timewindow # Time window in seconds (0=no window) ++- ++-ALIASES FILE FORMAT: ++- # Lines starting with # are comments ++- # Format: ++- # Names are matched case-insensitively ++- ++-EXAMPLE CONFIG FILE (srifreq.conf): ++- # srifreq.conf - SRIF Request Server Configuration ++- ++- pubdir Work:Fido/Public ++- logfile Work:Logs/srifreq.log ++- aliases Work:Fido/srifreq.aliases ++- ++- # Private directories (password protected) ++- private Work:Fido/Private/Uploader1 secretpass1 ++- private Work:Fido/Private/Node190 node190pwd ++- ++- # Rate limiting: max 10 files or 50MB per node per 24 hours ++- trackfile Work:Logs/srifreq.track ++- maxfiles 10 ++- maxsize 52428800 ++- timewindow 86400 ++- ++-EXAMPLE ALIASES FILE (srifreq.aliases): ++- # srifreq.aliases - Magic-name to file mappings ++- # Names are case-insensitive ++- ++- DOORWAY Games:Utils/Doorway/doorway.zip ++- NETMAIL Work:Comm/Fido/netmail.lha ++- README Docs:Readme.txt ++- 4DOUT AmiTCP:4DOut.lha ++- BINKD Apps:Comm/Binkd/binkd.lha ++- ++-REQUEST FILE FORMAT (.req): ++- Files listed one per line. Modifiers: ++- !password - Required password for private areas ++- +timestamp - Only serve if file is newer than timestamp ++- U - Update request (only if newer than client's TRANX) ++- ++-EXAMPLES: ++- srifreq --conf srifreq.conf inbound/srif_file.req ++- srifreq --conf srifreq.conf inbound/*.req ++- exec "work:fido/srifreq --conf work:fido/srifreq.conf *S" *.req *.REQ ++\ No hay ningún carácter de nueva línea al final del archivo ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile binkd_pgul/mkfls/amiga/Makefile ++--- binkd/mkfls/amiga/Makefile 2026-04-26 14:55:09.963221741 +0100 +++++ binkd_pgul/mkfls/amiga/Makefile 2026-04-16 17:49:00.000000000 +0100 ++@@ -26,4 +26,4 @@ ++ $(CC) -c $(CFLAGS) amiga/getfree.c ++ sem.o: ++ $(CC) -c $(CFLAGS) amiga/sem.c ++-include Makefile.dep ++\ No hay ningún carácter de nueva línea al final del archivo +++include Makefile.dep ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile.analyze.bebbo binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo ++--- binkd/mkfls/amiga/Makefile.analyze.bebbo 2026-04-25 16:52:14.088635220 +0100 +++++ binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,96 +0,0 @@ ++-# Makefile.analyze.bebbo -- Static analysis for Amiga bebbo (GCC 6.5.0b) ++-# Includes amiga/ code with bebbo-specific defines ++-# Usage: make -f Makefile.analyze.bebbo ++- ++-# All sources including Amiga-specific ++-ALL_SRCS = \ ++- binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ ++- bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ ++- readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ ++- setpttl.c https.c md5b.c crypt.c compress.c \ ++- amiga/rename.c amiga/getfree.c amiga/bsdsock.c amiga/dirent.c \ ++- amiga/utime.c amiga/rfc2553_amiga.c amiga/sem.c amiga/evloop.c \ ++- amiga/sock.c amiga/session.c \ ++- misc/decompress.c misc/freq.c misc/process_tic.c misc/srifreq.c misc/nodelist.c ++- ++-INCLUDES = -I. -Iamiga ++- ++-# bebbo-specific defines (GCC 6.5.0b, HAS native snprintf) ++-BEBBO_DEFINES = \ ++- -DAMIGA \ ++- -DHAVE_SOCKLEN_T \ ++- -DHAVE_INTMAX_T \ ++- -DHAVE_SNPRINTF \ ++- -DHAVE_GETOPT \ ++- -DHAVE_UNISTD_H \ ++- -DHAVE_SYS_TIME_H \ ++- -DHAVE_SYS_PARAM_H \ ++- -DHAVE_SYS_IOCTL_H \ ++- -DHAVE_NETINET_IN_H \ ++- -DHAVE_NETDB_H \ ++- -DHAVE_ARPA_INET_H \ ++- -DHAVE_STDARG_H \ ++- -DHAVE_VSNPRINTF \ ++- -DWITH_ZLIB ++- ++-CPPCHECK_FLAGS = \ ++- --enable=all \ ++- --inconclusive \ ++- --std=c89 \ ++- --quiet \ ++- --suppress=missingIncludeSystem \ ++- --suppress=unusedFunction \ ++- --suppress=checkersReport \ ++- $(INCLUDES) ++- ++-# Log files ++-LOG_DIR = analysis_logs ++-CPPCHECK_LOG = $(LOG_DIR)/cppcheck_bebbo.log ++-CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_bebbo.log ++- ++-.PHONY: all cppcheck clang-tidy analyze clean ++- ++-all: analyze ++- ++-cppcheck: ++- @mkdir -p $(LOG_DIR) ++- @echo "=== cppcheck Amiga bebbo (GCC 6.5.0b) ===" ++- @echo "Defines: bebbo, HAS native snprintf, no snprintf.c needed" ++- @echo "Saving output to: $(CPPCHECK_LOG)" ++- @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) $(ALL_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true ++- @echo "=== done ===" ++- @echo "Log saved: $(CPPCHECK_LOG)" ++- ++-clang-tidy: ++- @mkdir -p $(LOG_DIR) ++- @echo "=== clang-tidy Amiga bebbo ===" ++- @echo "Note: Some Amiga headers may not resolve on Linux host" ++- @echo "Saving output to: $(CLANG_TIDY_LOG)" ++- @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) ++- @for src in binkd.c readcfg.c amiga/evloop.c amiga/session.c; do \ ++- echo "" >> $(CLANG_TIDY_LOG); \ ++- echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ ++- clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ ++- $(INCLUDES) $(BEBBO_DEFINES) -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ ++- done ++- @echo "=== done ===" ++- @echo "Log saved: $(CLANG_TIDY_LOG)" ++- ++-analyze: cppcheck clang-tidy ++- ++-# Focus on Amiga-specific code only ++-amiga-only: ++- @echo "=== cppcheck Amiga-specific code only (bebbo) ===" ++- @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) \ ++- amiga/*.c 2>&1 || true ++- ++-# Check what differs between ADE and bebbo ++-diff-defines: ++- @echo "=== ADE vs bebbo define differences ===" ++- @echo "ADE only: -DHAVE_VSNPRINTF (no -DHAVE_SNPRINTF)" ++- @echo "bebbo: -DHAVE_SNPRINTF -DHAVE_VSNPRINTF" ++- @echo "" ++- @echo "ADE needs snprintf.c, bebbo does not" ++- ++-clean: ++- @true ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile.bebbo binkd_pgul/mkfls/amiga/Makefile.bebbo ++--- binkd/mkfls/amiga/Makefile.bebbo 2026-04-26 13:11:27.902433254 +0100 +++++ binkd_pgul/mkfls/amiga/Makefile.bebbo 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,208 +0,0 @@ ++-CC = m68k-amigaos-gcc ++- ++-# Common flags for compiler and linker ++-COMMON_CFLAGS = -Wall -Wextra -Wunused -Wunused-function -Wunused-variable -Wmissing-prototypes -Wno-pointer-sign -ffunction-sections -fdata-sections -noixemul ++-ARCH_FLAGS = -O -m68000 -msoft-float -fomit-frame-pointer ++- ++-CFLAGS = $(DEFINES) $(COMMON_CFLAGS) $(ARCH_FLAGS) ++-LIBS = -lz -lm -lamiga ++-LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_gcc.map -s ++- ++-# Tools use same flags as main binary ++-TOOL_CFLAGS = $(DEFINES) $(COMMON_CFLAGS) -O -m68000 -msoft-float -fomit-frame-pointer -I misc ++-TOOL_LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_tools.map -s ++- ++-OBJDIR = objs ++- ++-DEFINES = \ ++- -DAMIGA \ ++- -DHAVE_SOCKLEN_T \ ++- -DHAVE_INTMAX_T \ ++- -DHAVE_SNPRINTF \ ++- -DHAVE_GETOPT \ ++- -DHAVE_UNISTD_H \ ++- -DHAVE_SYS_TIME_H \ ++- -DHAVE_SYS_PARAM_H \ ++- -DHAVE_SYS_IOCTL_H \ ++- -DHAVE_NETINET_IN_H \ ++- -DHAVE_NETDB_H \ ++- -DHAVE_ARPA_INET_H \ ++- -DHTTPS \ ++- -DAMIGADOS_4D_OUTBOUND \ ++- -DHAVE_STDARG_H \ ++- -DHAVE_VSNPRINTF \ ++- -DWITH_ZLIB \ ++- -DOS=\"Amiga\" \ ++- -I. \ ++- -Iamiga ++- ++-SRCS = \ ++- binkd.c \ ++- readcfg.c \ ++- tools.c \ ++- ftnaddr.c \ ++- ftnq.c \ ++- client.c \ ++- server.c \ ++- protocol.c \ ++- bsy.c \ ++- inbound.c \ ++- breaksig.c \ ++- branch.c \ ++- amiga/rename.c \ ++- amiga/getfree.c \ ++- amiga/bsdsock.c \ ++- amiga/dirent.c \ ++- amiga/utime.c \ ++- amiga/rfc2553_amiga.c \ ++- amiga/sem.c \ ++- amiga/evloop.c \ ++- amiga/sock.c \ ++- amiga/session.c \ ++- bsycleanup.c \ ++- amiga/proto_amiga.c \ ++- ftndom.c \ ++- ftnnode.c \ ++- srif.c \ ++- pmatch.c \ ++- readflo.c \ ++- prothlp.c \ ++- iptools.c \ ++- run.c \ ++- binlog.c \ ++- exitproc.c \ ++- getw.c \ ++- xalloc.c \ ++- setpttl.c \ ++- https.c \ ++- md5b.c \ ++- crypt.c \ ++- compress.c ++- ++-OBJS = \ ++- $(OBJDIR)/binkd.o \ ++- $(OBJDIR)/readcfg.o \ ++- $(OBJDIR)/tools.o \ ++- $(OBJDIR)/ftnaddr.o \ ++- $(OBJDIR)/ftnq.o \ ++- $(OBJDIR)/client.o \ ++- $(OBJDIR)/server.o \ ++- $(OBJDIR)/protocol.o \ ++- $(OBJDIR)/bsy.o \ ++- $(OBJDIR)/inbound.o \ ++- $(OBJDIR)/breaksig.o \ ++- $(OBJDIR)/branch.o \ ++- $(OBJDIR)/rename.o \ ++- $(OBJDIR)/getfree.o \ ++- $(OBJDIR)/bsdsock.o \ ++- $(OBJDIR)/dirent.o \ ++- $(OBJDIR)/utime.o \ ++- $(OBJDIR)/rfc2553_amiga.o \ ++- $(OBJDIR)/sem.o \ ++- $(OBJDIR)/evloop.o \ ++- $(OBJDIR)/sock.o \ ++- $(OBJDIR)/session.o \ ++- $(OBJDIR)/bsycleanup.o \ ++- $(OBJDIR)/proto_amiga.o \ ++- $(OBJDIR)/ftndom.o \ ++- $(OBJDIR)/ftnnode.o \ ++- $(OBJDIR)/srif.o \ ++- $(OBJDIR)/pmatch.o \ ++- $(OBJDIR)/readflo.o \ ++- $(OBJDIR)/prothlp.o \ ++- $(OBJDIR)/iptools.o \ ++- $(OBJDIR)/run.o \ ++- $(OBJDIR)/binlog.o \ ++- $(OBJDIR)/exitproc.o \ ++- $(OBJDIR)/getw.o \ ++- $(OBJDIR)/xalloc.o \ ++- $(OBJDIR)/setpttl.o \ ++- $(OBJDIR)/https.o \ ++- $(OBJDIR)/md5b.o \ ++- $(OBJDIR)/crypt.o \ ++- $(OBJDIR)/compress.o ++- ++-all: binkd decompress process_tic freq srifreq nodelist ++- ++-$(OBJDIR): ++- mkdir -p $(OBJDIR) ++- ++-$(OBJDIR)/%.o: %.c ++- $(CC) -c $(CFLAGS) $< -o $@ ++- ++-binkd: $(OBJDIR) $(OBJS) ++- $(CC) $(CFLAGS) -o binkd $(OBJS) $(LIBS) $(LDFLAGS) ++- ++-# ---------- Utility tools (stand-alone, multi-platform) ---------- ++-# TOOL_CFLAGS and TOOL_LDFLAGS defined above ++- ++-decompress: ++- $(CC) $(TOOL_CFLAGS) -o decompress misc/decompress.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) ++- ++-process_tic: ++- $(CC) $(TOOL_CFLAGS) -o process_tic misc/process_tic.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) ++- ++-freq: ++- $(CC) $(TOOL_CFLAGS) -o freq misc/freq.c misc/portable.c $(TOOL_LDFLAGS) ++- ++-srifreq: ++- $(CC) $(TOOL_CFLAGS) -o srifreq misc/srifreq.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) ++- ++-nodelist: ++- $(CC) $(TOOL_CFLAGS) -o nodelist misc/nodelist.c misc/portable.c $(TOOL_LDFLAGS) ++- ++-install: all clean ++- ++-clean: ++- rm -f *.[bo] *.BAK *.core *.obj *.err *~ core ++- rm -rf $(OBJDIR) ++- rm -f binkd decompress process_tic freq srifreq nodelist ++- ++-# ---------- Explicit rules for amiga/ objects ---------- ++-$(OBJDIR)/rename.o: amiga/rename.c ++- $(CC) -c $(CFLAGS) amiga/rename.c -o $(OBJDIR)/rename.o ++- ++-$(OBJDIR)/getfree.o: amiga/getfree.c ++- $(CC) -c $(CFLAGS) amiga/getfree.c -o $(OBJDIR)/getfree.o ++- ++-$(OBJDIR)/sem.o: amiga/sem.c ++- $(CC) -c $(CFLAGS) amiga/sem.c -o $(OBJDIR)/sem.o ++- ++-$(OBJDIR)/bsdsock.o: amiga/bsdsock.c ++- $(CC) -c $(CFLAGS) amiga/bsdsock.c -o $(OBJDIR)/bsdsock.o ++- ++-$(OBJDIR)/dirent.o: amiga/dirent.c ++- $(CC) -c $(CFLAGS) amiga/dirent.c -o $(OBJDIR)/dirent.o ++- ++-$(OBJDIR)/utime.o: amiga/utime.c ++- $(CC) -c $(CFLAGS) amiga/utime.c -o $(OBJDIR)/utime.o ++- ++-$(OBJDIR)/rfc2553_amiga.o: amiga/rfc2553_amiga.c ++- $(CC) -c $(CFLAGS) amiga/rfc2553_amiga.c -o $(OBJDIR)/rfc2553_amiga.o ++- ++-$(OBJDIR)/evloop.o: amiga/evloop.c ++- $(CC) -c $(CFLAGS) amiga/evloop.c -o $(OBJDIR)/evloop.o ++- ++-$(OBJDIR)/sock.o: amiga/sock.c ++- $(CC) -c $(CFLAGS) amiga/sock.c -o $(OBJDIR)/sock.o ++- ++-$(OBJDIR)/session.o: amiga/session.c ++- $(CC) -c $(CFLAGS) amiga/session.c -o $(OBJDIR)/session.o ++- ++-$(OBJDIR)/bsycleanup.o: bsycleanup.c ++- $(CC) -c $(CFLAGS) bsycleanup.c -o $(OBJDIR)/bsycleanup.o ++- ++-$(OBJDIR)/proto_amiga.o: amiga/proto_amiga.c ++- $(CC) -c $(CFLAGS) amiga/proto_amiga.c -o $(OBJDIR)/proto_amiga.o ++- ++-depend Makefile.dep: Makefile ++- $(CC) -MM $(CFLAGS) $(SRCS) $(SYS) | \ ++- awk '{ if ($$1 != prev) { if (rec != "") print rec; \ ++- rec = $$0; prev = $$1; } \ ++- else { if (length(rec $$2) > 78) { print rec; rec = $$0; } \ ++- else rec = rec " " $$2 } } \ ++- END { print rec }' | tee Makefile.dep ++- ++--include Makefile.dep ++- ++-.PHONY: all binkd decompress process_tic freq srifreq nodelist clean install ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/nt95-mingw/Makefile binkd_pgul/mkfls/nt95-mingw/Makefile ++--- binkd/mkfls/nt95-mingw/Makefile 2026-04-26 13:12:24.499178632 +0100 +++++ binkd_pgul/mkfls/nt95-mingw/Makefile 2026-04-16 17:49:00.000000000 +0100 ++@@ -38,8 +38,7 @@ ++ setpttl.c https.c md5b.c crypt.c getopt.c nt/breaksig.c nt/getfree.c \ ++ nt/sem.c nt/TCPErr.c nt/WSock.c nt/w32tools.c nt/tray.c snprintf.c \ ++ ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c ntlm/des_enc.c \ ++- ntlm/helpers.c \ ++- bsycleanup.c +++ ntlm/helpers.c ++ ++ RES= nt/binkdres.rc ++ ++@@ -222,32 +221,7 @@ ++ OBJS=$(addprefix $(OBJDIR)/,$(patsubst %.c,%.o, $(SRCS))) ++ RESOBJS=$(addprefix $(OBJDIR)/, $(patsubst %.rc,%.o, $(RES))) ++ ++-# ---------- Utility tools (stand-alone) ---------- ++-TOOL_CFLAGS = -O2 -Wall -DWIN32 -I. -I misc ++- ++-utils: decompress process_tic freq srifreq nodelist ++- ++-decompress: ++- @echo Compiling decompress... ++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/decompress.exe misc/decompress.c misc/portable.c ++- ++-process_tic: ++- @echo Compiling process_tic... ++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/process_tic.exe misc/process_tic.c misc/portable.c ++- ++-freq: ++- @echo Compiling freq... ++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/freq.exe misc/freq.c misc/portable.c ++- ++-srifreq: ++- @echo Compiling srifreq... ++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/srifreq.exe misc/srifreq.c misc/portable.c ++- ++-nodelist: ++- @echo Compiling nodelist... ++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/nodelist.exe misc/nodelist.c misc/portable.c ++- ++-.PHONY: all printinfo install html clean distclean makedirs utils decompress process_tic freq srifreq nodelist +++.PHONY: all printinfo install html clean distclean makedirs ++ ++ all: printinfo makedirs $(OUTDIR)/$(BINKDEXE) ++ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/nt95-msvc/Makefile binkd_pgul/mkfls/nt95-msvc/Makefile ++--- binkd/mkfls/nt95-msvc/Makefile 2026-04-26 12:41:43.750120247 +0100 +++++ binkd_pgul/mkfls/nt95-msvc/Makefile 2026-04-16 17:49:00.000000000 +0100 ++@@ -327,32 +327,7 @@ ++ ++ BINKDEXE = $(BINKDNAME).exe ++ ++-all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) utils ++- ++-TOOL_CFLAGS = -nologo -W3 -O2 -DWIN32 -DVISUALCPP -I. -I misc ++-TOOL_CC = $(CC) $(TOOL_CFLAGS) ++- ++-utils: "$(OUTDIR)\decompress.exe" "$(OUTDIR)\process_tic.exe" "$(OUTDIR)\freq.exe" "$(OUTDIR)\srifreq.exe" "$(OUTDIR)\nodelist.exe" ++- ++-"$(OUTDIR)\decompress.exe": misc\decompress.c misc\portable.c nt\dirwin32.c ++- @echo Compiling decompress... ++- @$(TOOL_CC) -Fe"$(OUTDIR)\decompress.exe" misc\decompress.c misc\portable.c nt\dirwin32.c ++- ++-"$(OUTDIR)\process_tic.exe": misc\process_tic.c misc\portable.c nt\dirwin32.c ++- @echo Compiling process_tic... ++- @$(TOOL_CC) -Fe"$(OUTDIR)\process_tic.exe" misc\process_tic.c misc\portable.c nt\dirwin32.c ++- ++-"$(OUTDIR)\freq.exe": misc\freq.c misc\portable.c ++- @echo Compiling freq... ++- @$(TOOL_CC) -Fe"$(OUTDIR)\freq.exe" misc\freq.c misc\portable.c ++- ++-"$(OUTDIR)\srifreq.exe": misc\srifreq.c misc\portable.c nt\dirwin32.c ++- @echo Compiling srifreq... ++- @$(TOOL_CC) -Fe"$(OUTDIR)\srifreq.exe" misc\srifreq.c misc\portable.c nt\dirwin32.c ++- ++-"$(OUTDIR)\nodelist.exe": misc\nodelist.c misc\portable.c ++- @echo Compiling nodelist... ++- @$(TOOL_CC) -Fe"$(OUTDIR)\nodelist.exe" misc\nodelist.c misc\portable.c +++all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) ++ ++ printinfo: ++ @echo on ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/os2-emx/Makefile binkd_pgul/mkfls/os2-emx/Makefile ++--- binkd/mkfls/os2-emx/Makefile 2026-04-26 13:12:44.342586479 +0100 +++++ binkd_pgul/mkfls/os2-emx/Makefile 2026-04-16 17:49:00.000000000 +0100 ++@@ -13,7 +13,7 @@ ++ LFLAGS=-Los2 ++ LIBS=-lsocket -lresolv ++ NTLM_SRC=ntlm/des_enc.c ntlm/helpers.c ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c ++-SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c bsycleanup.c ${NTLM_SRC} +++SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c ${NTLM_SRC} ++ TARGET=binkd2emx ++ ++ #PERLDIR=../perl5.00553/os2 ++@@ -122,33 +122,7 @@ ++ ++ TARGET:=$(TARGET).exe ++ ++-all: $(TARGET) utils ++- ++-TOOL_CFLAGS = $(CFLAGS) -I. -I misc ++- ++-utils: decompress process_tic freq srifreq nodelist ++- ++-decompress: misc/decompress.c ++- @echo Compiling decompress... ++- @$(CC) $(TOOL_CFLAGS) -o decompress.exe misc/decompress.c misc/portable.c os2/dirent.c ++- ++-process_tic: misc/process_tic.c ++- @echo Compiling process_tic... ++- @$(CC) $(TOOL_CFLAGS) -o process_tic.exe misc/process_tic.c misc/portable.c os2/dirent.c ++- ++-freq: misc/freq.c ++- @echo Compiling freq... ++- @$(CC) $(TOOL_CFLAGS) -o freq.exe misc/freq.c misc/portable.c ++- ++-srifreq: misc/srifreq.c ++- @echo Compiling srifreq... ++- @$(CC) $(TOOL_CFLAGS) -o srifreq.exe misc/srifreq.c misc/portable.c os2/dirent.c ++- ++-nodelist: misc/nodelist.c ++- @echo Compiling nodelist... ++- @$(CC) $(TOOL_CFLAGS) -o nodelist.exe misc/nodelist.c misc/portable.c ++- ++-.PHONY: utils decompress process_tic freq srifreq nodelist +++all: $(TARGET) ++ ++ .c.o: ++ @echo Compiling $*.c... ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/unix/Makefile.analyze.unix binkd_pgul/mkfls/unix/Makefile.analyze.unix ++--- binkd/mkfls/unix/Makefile.analyze.unix 2026-04-25 16:52:02.225860998 +0100 +++++ binkd_pgul/mkfls/unix/Makefile.analyze.unix 1970-01-01 00:00:00.000000000 +0000 ++@@ -1,65 +0,0 @@ ++-# Makefile.analyze.unix -- Static analysis for Unix/Linux code only ++-# Excludes Amiga-specific code (amiga/ directory) ++-# Usage: make -f Makefile.analyze.unix ++- ++-# Unix-portable sources only (no amiga/ directory) ++-UNIX_SRCS = \ ++- binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ ++- bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ ++- readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ ++- setpttl.c https.c md5b.c crypt.c compress.c ++- ++-# Unix-specific files (if any) ++-UNIX_SYS_SRCS = \ ++- unix/getwd.c unix/lock.c unix/tcperr.c ++- ++-INCLUDES = -I. -Iunix ++- ++-CPPCHECK_FLAGS = \ ++- --enable=all \ ++- --inconclusive \ ++- --std=c89 \ ++- --quiet \ ++- --suppress=missingIncludeSystem \ ++- --suppress=unusedFunction \ ++- $(INCLUDES) ++- ++-# Log files ++-LOG_DIR = analysis_logs ++-CPPCHECK_LOG = $(LOG_DIR)/cppcheck_unix.log ++-CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_unix.log ++- ++-.PHONY: all cppcheck clang-tidy analyze clean ++- ++-all: analyze ++- ++-cppcheck: ++- @mkdir -p $(LOG_DIR) ++- @echo "=== cppcheck Unix/Linux code ===" ++- @echo "Saving output to: $(CPPCHECK_LOG)" ++- @cppcheck $(CPPCHECK_FLAGS) $(UNIX_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true ++- @echo "=== done ===" ++- @echo "Log saved: $(CPPCHECK_LOG)" ++- ++-clang-tidy: ++- @mkdir -p $(LOG_DIR) ++- @echo "=== clang-tidy Unix/Linux code ===" ++- @echo "Saving output to: $(CLANG_TIDY_LOG)" ++- @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) ++- @for src in $(UNIX_SRCS); do \ ++- echo "" >> $(CLANG_TIDY_LOG); \ ++- echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ ++- clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ ++- $(INCLUDES) -DUNIX -DHAVE_SNPRINTF -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ ++- done ++- @echo "=== done ===" ++- @echo "Log saved: $(CLANG_TIDY_LOG)" ++- ++-analyze: cppcheck clang-tidy ++- ++-quick: ++- @echo "=== Quick error check (Unix) ===" ++- @cppcheck --enable=error --std=c89 --quiet $(INCLUDES) $(UNIX_SRCS) 2>&1 || true ++- ++-clean: ++- @true ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/unix/Makefile.in binkd_pgul/mkfls/unix/Makefile.in ++--- binkd/mkfls/unix/Makefile.in 2026-04-26 13:11:58.084940863 +0100 +++++ binkd_pgul/mkfls/unix/Makefile.in 2026-04-16 17:49:00.000000000 +0100 ++@@ -12,17 +12,17 @@ ++ MANDIR=$(DATADIR)/man ++ DOCDIR=$(DATADIR)/doc/$(APPL) ++ ++-SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c bsycleanup.c @OPT_SRC@ +++SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c @OPT_SRC@ ++ OBJS=${SRCS:.c=.o} ++ AUTODEFS=@DEFS@ ++ AUTOLIBS=@LIBS@ ++-DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -DPROTOTYPES +++DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" ++ CPPFLAGS=@CPPFLAGS@ ++ CFLAGS=@CFLAGS@ ++ LDFLAGS=@LDFLAGS@ ++ LIBS=$(AUTOLIBS) ++ ++-all: compile banner utils +++all: compile banner ++ ++ compile: $(APPL) ++ ++@@ -48,32 +48,6 @@ ++ @echo " run \`configure --prefix=/another/path' and go to step 1. " ++ @echo ++ ++-utils: decompress process_tic freq srifreq nodelist ++- ++-TOOL_CFLAGS = $(DEFINES) $(CPPFLAGS) $(CFLAGS) -I. -I misc ++- ++-decompress: misc/decompress.c misc/portable.c misc/portable.h ++- @echo Compiling decompress... ++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/decompress.c misc/portable.c ++- ++-process_tic: misc/process_tic.c misc/portable.c misc/portable.h ++- @echo Compiling process_tic... ++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/process_tic.c misc/portable.c ++- ++-freq: misc/freq.c misc/portable.c misc/portable.h ++- @echo Compiling freq... ++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/freq.c misc/portable.c ++- ++-srifreq: misc/srifreq.c misc/portable.c misc/portable.h ++- @echo Compiling srifreq... ++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/srifreq.c misc/portable.c ++- ++-nodelist: misc/nodelist.c misc/portable.c ++- @echo Compiling nodelist... ++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/nodelist.c misc/portable.c ++- ++-.PHONY: decompress process_tic freq srifreq nodelist ++- ++ .version: $(APPL) ++ @./$(APPL) -v | $(AWK) '{ print $$2; }' > $@ ++ ++@@ -92,7 +66,6 @@ ++ clean: ++ rm -f *.[bo] unix/*.[bo] ntlm/*.[bo] *.BAK *.core *.obj *.err ++ rm -f *~ core config.cache config.log config.status ++- rm -f decompress process_tic freq srifreq nodelist ++ ++ cleanall: clean ++ rm -f $(APPL) Makefile Makefile.dep Makefile.in ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/protocol.c binkd_pgul/protocol.c ++--- binkd/protocol.c 2026-04-26 09:45:52.230049023 +0100 +++++ binkd_pgul/protocol.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -45,19 +45,12 @@ ++ #include "md5b.h" ++ #include "crypt.h" ++ #include "compress.h" ++-#ifdef AMIGA ++-#include "amiga/proto_amiga.h" ++-#endif ++ ++ #ifdef WITH_PERL ++ #include "perlhooks.h" ++ #endif ++ #include "rfc2553.h" ++ ++-#if defined(HAVE_THREADS) || defined(AMIGA) ++-extern MUTEXSEM lsem; ++-#endif ++- ++ /* define to enable val's code for -ip checks (default is gul's code) */ ++ #undef VAL_STYLE ++ #ifdef VAL_STYLE ++@@ -70,7 +63,7 @@ ++ /* ++ * Fills <> with initial values, allocates buffers, etc. ++ */ ++-int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) +++static int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) ++ { ++ char val[4]; ++ socklen_t lval; ++@@ -133,12 +126,6 @@ ++ #endif ++ setsockopts (state->s_in = socket_in); ++ setsockopts (state->s_out = socket_out); ++- ++-#if defined(AMIGA) ++- setsockopts_amiga(socket_in, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); ++- setsockopts_amiga(socket_out, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); ++-#endif ++- ++ TF_ZERO (&state->in); ++ TF_ZERO (&state->out); ++ TF_ZERO (&state->flo); ++@@ -194,7 +181,7 @@ ++ /* ++ * Clears protocol buffers and queues, closes files, etc. ++ */ ++-int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) +++static int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) ++ { ++ int i; ++ ++@@ -238,7 +225,7 @@ ++ } ++ ++ /* Process rcvdlist */ ++-FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) +++static FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) ++ { ++ int i; ++ ++@@ -328,7 +315,7 @@ ++ /* ++ * Sends next msg from the msg queue or next data block ++ */ ++-int send_block (STATE *state, BINKD_CONFIG *config) +++static int send_block (STATE *state, BINKD_CONFIG *config) ++ { ++ int i, n, save_errno; ++ const char *save_err; ++@@ -2104,7 +2091,7 @@ ++ return 0; ++ } ++ ++-int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) +++static int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) ++ { ++ char buf[MAXPATHLEN+1]; ++ FILE *f; ++@@ -2158,8 +2145,7 @@ ++ ++ *extra = ""; ++ if (state->z_cansend && state->extcmd && state->out.size >= config->zminsize ++- && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first) ++- && !(state->to && state->to->NC_flag)) { +++ && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first)) { ++ #ifdef WITH_BZLIB2 ++ if (!state->z_send && (state->z_cansend & 2)) { ++ *extra = " BZ2"; state->z_send = 2; ++@@ -2462,7 +2448,6 @@ ++ { ++ char szAddr[FTN_ADDR_SZ + 1]; ++ ++- memset(szAddr, 0, sizeof(szAddr)); ++ ftnaddress_to_str (szAddr, &state->sent_fls[n].fa); ++ state->bytes_sent += state->sent_fls[n].size; ++ ++state->files_sent; ++@@ -2553,7 +2538,7 @@ ++ }; ++ ++ /* Recvs next block, processes msgs or writes down the data from the remote */ ++-int recv_block (STATE *state, BINKD_CONFIG *config) +++static int recv_block (STATE *state, BINKD_CONFIG *config) ++ { ++ int no; ++ ++@@ -2785,7 +2770,7 @@ ++ return 1; ++ } ++ ++-int banner (STATE *state, BINKD_CONFIG *config) +++static int banner (STATE *state, BINKD_CONFIG *config) ++ { ++ int tz; ++ char szLocalTime[60]; ++@@ -2865,7 +2850,7 @@ ++ return 1; ++ } ++ ++-int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) +++static int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) ++ { ++ struct stat sb; ++ FILE *f = NULL; ++@@ -3046,7 +3031,7 @@ ++ return 1; ++ } ++ ++-void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) +++static void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) ++ { ++ char szFTNAddr[FTN_ADDR_SZ + 1]; ++ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readcfg.c binkd_pgul/readcfg.c ++--- binkd/readcfg.c 2026-04-26 15:04:45.397917107 +0100 +++++ binkd_pgul/readcfg.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -44,10 +44,6 @@ ++ */ ++ BINKD_CONFIG *current_config; ++ ++-#ifdef AMIGA ++-extern struct SignalSemaphore config_sem; ++-#endif ++- ++ /* ++ * Temporary static structure for configuration reading ++ */ ++@@ -214,15 +210,9 @@ ++ snprintf(c->iport, sizeof(c->iport), "%s", find_port("")); ++ snprintf(c->oport, sizeof(c->oport), "%s", find_port("")); ++ c->call_delay = 60; ++- c->no_call_delay = 0; ++ c->rescan_delay = 60; ++ c->nettimeout = DEF_TIMEOUT; ++ c->oblksize = DEF_BLKSIZE; ++- ++-#ifdef AMIGA ++- c->tcp_nodelay = 0; ++-#endif ++- ++ #if defined(WITH_ZLIB) || defined(WITH_BZLIB2) ++ c->zminsize = 1024; ++ c->zlevel = 0; ++@@ -401,16 +391,8 @@ ++ {"oport", read_port, &work_config.oport, 0, 0}, ++ {"rescan-delay", read_time, &work_config.rescan_delay, 1, DONT_CHECK}, ++ {"call-delay", read_time, &work_config.call_delay, 1, DONT_CHECK}, ++- {"no-call-delay", read_bool, &work_config.no_call_delay, 0, 0}, ++ {"timeout", read_time, &work_config.nettimeout, 1, DONT_CHECK}, ++ {"oblksize", read_int, &work_config.oblksize, MIN_BLKSIZE, MAX_BLKSIZE}, ++- ++-#ifdef AMIGA ++- {"tcp-nodelay", read_bool, &work_config.tcp_nodelay, 0, 0}, ++- {"so-sndbuf", read_int, &work_config.so_sndbuf, 0, 65535}, ++- {"so-rcvbuf", read_int, &work_config.so_rcvbuf, 0, 65535}, ++-#endif ++- ++ {"maxservers", read_int, &work_config.max_servers, 0, DONT_CHECK}, ++ {"maxclients", read_int, &work_config.max_clients, 0, DONT_CHECK}, ++ {"inbound", read_string, work_config.inbound, 'd', 0}, ++@@ -684,7 +666,7 @@ ++ exp_ftnaddress (&fa, work_config.pAddr, work_config.nAddr, work_config.pDomains.first); ++ pn = add_node (&fa, NULL, password, pkt_pwd, out_pwd, '-', NULL, NULL, ++ NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, ++- HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, +++ HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, ++ #ifdef BW_LIM ++ BW_DEF, BW_DEF, ++ #endif ++@@ -848,10 +830,11 @@ ++ if (!new_config) ++ { ++ /* Config error. Abort or continue? */ ++- unlock_config_structure(&work_config, 0); ++- ++- if (current_config) ++- Log(1, "error in configuration, using old config"); +++ if (current_config) +++ { +++ Log(1, "error in configuration, using old config"); +++ unlock_config_structure(&work_config, 0); +++ } ++ } ++ ++ return new_config; ++@@ -874,16 +857,6 @@ ++ Log (2, "got SIGHUP"); ++ need_reload = got_sighup; ++ got_sighup = 0; ++-#elif defined(AMIGA) ++- /* No SIGHUP on AmigaOS: detect config change by mtime */ ++- need_reload = 0; ++- if (current_config->config_list.first) ++- { ++- if (stat(current_config->config_list.first->path, &sb) == 0 && ++- current_config->config_list.first->mtime != 0 && ++- sb.st_mtime != current_config->config_list.first->mtime) ++- need_reload = 1; ++- } ++ #else ++ need_reload = 0; ++ #endif ++@@ -913,75 +886,8 @@ ++ } ++ #endif ++ ++- if (!need_reload) ++- return 0; ++- ++-#ifdef AMIGA ++- /* Prevent reload storms and partial-file reads. ++- * ++- * On AmigaOS (and some Unix editors), config files are written in multiple ++- * passes, so binkd may see the mtime change while the file is still being ++- * written. Attempting to parse an incomplete file gives "unknown keyword" ++- * errors, and rapid repeated mtime changes cause bind() to fail because the ++- * previous listen socket has not been released yet. ++- * ++- * Strategy: after first detecting a change, wait until the mtime has been ++- * stable for at least 2 seconds before actually reloading. Also enforce a ++- * minimum of 5 seconds between successive successful reloads. ++- */ ++- { ++- static time_t last_reload = 0; /* time of last successful reload */ ++- static time_t change_seen = 0; /* time we first noticed the change */ ++- static time_t stable_mtime = 0; /* mtime we are waiting to stabilize */ ++- static int reload_pending = 0; /* persists between calls */ ++- time_t now = time(NULL); ++- ++- /* The loop has already updated pc->mtime, so in the next call ++- * need_reload will be 0 even though we haven't reloaded yet. ++- * reload_pending keeps the reload intent alive. ++- */ ++- if (need_reload) reload_pending = 1; ++- ++- if (!reload_pending) ++- return 0; ++- ++- /* Get the mtime of the primary config file */ ++- { struct stat sb2; ++- time_t cur_mtime = 0; ++- ++- if (current_config->config_list.first && stat(current_config->config_list.first->path, &sb2) == 0) ++- cur_mtime = sb2.st_mtime; ++- ++- /* mtime just changed (or changed again) — reset the stability clock */ ++- if (cur_mtime != stable_mtime) ++- { ++- stable_mtime = cur_mtime; ++- change_seen = now; ++- Log(5, "checkcfg: config mtime changed, waiting for stability..."); ++- return 0; ++- } ++- ++- /* mtime has been stable since change_seen */ ++- if (now - change_seen < 2) ++- { ++- Log(5, "checkcfg: config not yet stable (%lds), waiting...", ++- (long)(now - change_seen)); ++- return 0; ++- } ++- } ++- ++- /* Enforce minimum gap between reloads to let the OS release sockets */ ++- if (now - last_reload < 5) ++- { ++- Log(5, "checkcfg: reload suppressed (too soon, %lds)", (long)(now - last_reload)); ++- return 0; ++- } ++- ++- last_reload = now; ++- reload_pending = 0; /* Once the intent has been consumed, the reload is executed */ ++- } ++-#endif ++- +++ if (!need_reload) +++ return 0; ++ /* Reload starting from first file in list */ ++ Log(2, "Reloading configuration..."); ++ pc = current_config->config_list.first; ++@@ -1219,7 +1125,7 @@ ++ char *w[ARGNUM], *tmp, *pkt_pwd, *out_pwd, *pipe; ++ int i, j; ++ int NR_flag = NR_USE_OLD, ND_flag = ND_USE_OLD, HC_flag = HC_USE_OLD, ++- MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, NC_flag = NC_USE_OLD, restrictIP = RIP_USE_OLD, +++ MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, restrictIP = RIP_USE_OLD, ++ IP_afamily = AF_USE_OLD; ++ #ifdef BW_LIM ++ long bw_send = BW_DEF, bw_recv = BW_DEF; ++@@ -1269,8 +1175,6 @@ ++ HC_flag = HC_OFF; ++ else if (STRICMP (tmp, "-noproxy") == 0) ++ NP_flag = NP_ON; ++- else if (STRICMP (tmp, "-nc") == 0) ++- NC_flag = NC_ON; ++ #ifdef BW_LIM ++ else if (STRICMP (tmp, "-bw") == 0) ++ { ++@@ -1354,7 +1258,7 @@ ++ ++ split_passwords(w[2], &pkt_pwd, &out_pwd); ++ pn = add_node (&fa, w[1], w[2], pkt_pwd, out_pwd, (char)(w[3] ? w[3][0] : '-'), w[4], w[5], ++- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, +++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, ++ IP_afamily, ++ #ifdef BW_LIM ++ bw_send, bw_recv, ++@@ -2087,9 +1991,6 @@ ++ if (fn->pkt_pwd) pwd_len += strlen(fn->pkt_pwd)+1; else pwd_len += 2; ++ if (fn->out_pwd) pwd_len += strlen(fn->out_pwd)+1; else pwd_len += 2; ++ pwd = calloc (1, pwd_len+1); ++- /* Guard against null pointer dereference if calloc fails */ ++- if (!pwd) ++- return 0; ++ strcpy(pwd, fn->pwd); ++ if (fn->pkt_pwd != (char*)&(fn->pwd) || fn->out_pwd != (char*)&(fn->pwd)) { ++ strcat(strcat(pwd, ","), (fn->pkt_pwd) ? fn->pkt_pwd : "-"); ++@@ -2423,3 +2324,4 @@ ++ return 1; ++ } ++ #endif +++ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readcfg.h binkd_pgul/readcfg.h ++--- binkd/readcfg.h 2026-04-25 20:39:08.569961699 +0100 +++++ binkd_pgul/readcfg.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -111,16 +111,8 @@ ++ #endif ++ int nettimeout; ++ int connect_timeout; ++- ++-#ifdef AMIGA ++- int tcp_nodelay; ++- int so_sndbuf; ++- int so_rcvbuf; ++-#endif ++- ++- int call_delay; ++- int no_call_delay; ++ int rescan_delay; +++ int call_delay; ++ int max_servers; ++ int max_clients; ++ int kill_dup_partial_files; ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readdir.h binkd_pgul/readdir.h ++--- binkd/readdir.h 2026-04-22 20:37:22.121954324 +0100 +++++ binkd_pgul/readdir.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -23,8 +23,6 @@ ++ #elif defined(__MINGW32__) ++ #include ++ #include ++-#elif defined(AMIGA) ++-#include "amiga/dirent.h" ++ #else ++ #include ++ #include ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/README.md binkd_pgul/README.md ++--- binkd/README.md 2026-04-22 18:44:10.839451402 +0100 +++++ binkd_pgul/README.md 2026-04-16 17:49:00.000000000 +0100 ++@@ -6,79 +6,6 @@ ++ ++ ## Compiling ++ ++-AmigaOS: ++- ++-Copy "mkfls/amiga/Makefile" to root and make ++- ++-Need ADE to compile project with ixemul library. ++- ++-https://aminet.net/package/dev/gcc/ADE ++- ++-It no longer requires ADE to be installed for execution, as it doesn't need /bin/sh to run scripts or external programs from binkd.conf. However, it does require ixemul.library and ixnet.library, either in the libs directory or in the same directory as the executable. ++- ++-You can find the ADE ixemul.library and ixnet.library libraries in the aminet package or in the released versions, along with the program and utilities already compiled. ++- ++-https://github.com/skbn/binkd/releases ++- ++-5.Work:fido> version work:fido/ixnet.library ++-ixnet.library 63.1 ++- ++-5.Work:fido> version work:fido/ixemul.library ++-ixemul.library 63.1 ++- ++- ++-I've attached six programs for your assistance: ++- ++-[decompress] which decompresses incoming files in lha or zip format, if necessary. ++- ++-``` ++-[freq] which generates file requests in ASO mode and places the necessary files in outbound directory. ++- ++-Usage:freq Z:N/NODE[.POINT] ++-``` ++-``` ++-[freq_bso] same but BSO style ++-Usage: freq_bso Z:N/NODE[.POINT] ++- No point : .0ZZ/NNNNNNNN.req ++- Point : .0ZZ/N ++-``` ++- ++-``` ++-[process_tic] which processes tic files and places them in the filebox folder. ++-With --copypublic option, copy the file you receive to a directory named pub/ from PROGDIR ++->> exec "path/process_tic --copypublic" *.tic *.TIC ++->> exec "path/process_tic" *.tic *.TIC ++-``` ++- ++-``` ++-[srifreq] copy of "misc/srifreq" but in c. SRIF-compatible file-request server for binkd ++- ++-Usage: srifreq [] ++- SRIF control file passed by binkd (exec "srifreq *S") ++- directory containing public downloadable files ++- log file path (pass "" or - to disable logging) ++- optional file mapping magic names to real paths ++- ++-Aliases file format (one alias per line, # = comment): ++-MAGIC_NAME relative/or/absolute/path/to/file ++-Example: ++-NODELIST pub/NODELIST.ZIP ++-FILES pub/ALLFILES.TXT ++- ++-binkd.conf example: exec "srifreq *S pub log/srifreq.log aliases.txt" *.req ++-``` ++- ++-``` ++-[nodelist] FidoNet nodelist compiler for binkd - AmigaOS version in c from "misc/nodelist.pl" ++- ++-Usage: nodelist [] ++-``` ++- ++-Also compileable on *nix >> "gcc -O2 -Wall -o nodelist nodelist.c" ++- ++-BUGFIXES: ++-Option "-C" to reload config It remains unstable ++- ++ non-UNIX: ++ ++ 1. Find in mkfls/ a subdirectory for your system/compiler, copy all files ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/rfc2553.h binkd_pgul/rfc2553.h ++--- binkd/rfc2553.h 2026-04-26 10:31:43.475331092 +0100 +++++ binkd_pgul/rfc2553.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -21,52 +21,8 @@ ++ ++ #include "iphdr.h" ++ ++-/* Amiga: define EAI_* before including netdb.h to prevent libnix redefinition */ ++-#if defined(AMIGA) ++-#include ++-#include ++-#include ++-#include ++- ++- #undef EAI_NONAME ++- #undef EAI_AGAIN ++- #undef EAI_FAIL ++- #undef EAI_NODATA ++- #undef EAI_FAMILY ++- #undef EAI_SOCKTYPE ++- #undef EAI_SERVICE ++- #undef EAI_ADDRFAMILY ++- #undef EAI_MEMORY ++- #undef EAI_SYSTEM ++- #undef EAI_UNKNOWN ++- #define EAI_NONAME -1 ++- #define EAI_AGAIN -2 ++- #define EAI_FAIL -3 ++- #define EAI_NODATA -4 ++- #define EAI_FAMILY -5 ++- #define EAI_SOCKTYPE -6 ++- #define EAI_SERVICE -7 ++- #define EAI_ADDRFAMILY -8 ++- #define EAI_MEMORY -9 ++- #define EAI_SYSTEM -10 ++- #define EAI_UNKNOWN -11 ++- ++-#include ++- ++-/* EAI_ADDRFAMILY is BSD/macOS specific; Linux/glibc does not define it ++- * Map it to EAI_FAMILY which has the same meaning on those platforms */ ++-#ifndef EAI_ADDRFAMILY ++-#ifdef EAI_FAMILY ++-#define EAI_ADDRFAMILY EAI_FAMILY ++-#else ++-#define EAI_ADDRFAMILY -9 ++-#endif ++-#endif ++- ++-#endif ++- ++ /* Autosense getaddrinfo */ ++-#if defined(AI_PASSIVE) && defined(EAI_NONAME) && !defined(AMIGA) +++#if defined(AI_PASSIVE) && defined(EAI_NONAME) ++ #define HAVE_GETADDRINFO ++ #endif ++ ++@@ -91,18 +47,6 @@ ++ }; ++ #define addrinfo addrinfo_emu ++ ++-#ifdef AMIGA ++-#ifdef getaddrinfo ++-#undef getaddrinfo ++-#endif ++-#ifdef freeaddrinfo ++-#undef freeaddrinfo ++-#endif ++-#ifdef gai_strerror ++-#undef gai_strerror ++-#endif ++-#endif ++- ++ int getaddrinfo(const char *nodename, const char *servname, ++ const struct addrinfo *hints, ++ struct addrinfo **res); ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/run.c binkd_pgul/run.c ++--- binkd/run.c 2026-04-26 10:31:17.108612481 +0100 +++++ binkd_pgul/run.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -18,11 +18,6 @@ ++ #ifdef HAVE_UNISTD_H ++ #include ++ #endif ++-#ifdef AMIGA ++-#include ++-#include ++-#include ++-#endif ++ ++ #include "sys.h" ++ #include "run.h" ++@@ -45,128 +40,10 @@ ++ #define SHELL (getenv("COMSPEC") ? getenv("COMSPEC") : "command.com") ++ #define SHELL_META "\"\'\\%<>|&^@" ++ #define SHELLOPT "/c" ++-#elif defined(AMIGA) ++-/* AmigaOS shell */ ++-#define SHELL "c:execute" ++-#define SHELL_META "\"\'\\*?(){};&|<>" ++ #else ++ #error "Unknown platform" ++ #endif ++ ++-#ifdef AMIGA ++-/* run(): execute an AmigaDOS command via SystemTagList() with NIL: I/O ++- * Runs synchronously so binkd waits for completion (needed for srifreq ++- * to create .rsp before parse_response). NIL: I/O prevents CLI freezing ++- * Error output goes to a temporary file for logging on failure */ ++-int run(char *cmd) ++-{ ++- /* All declarations at the top for C89/ADE GCC 2.95 compatibility */ ++- BPTR nil_in; ++- char errfile[MAXPATHLEN]; ++- BPTR err_out = 0; ++- int rc = 0; ++- char cmd_copy[MAXPATHLEN]; ++- char *cmd_start; ++- char *cmd_end; ++- BPTR lock; ++- struct TagItem exec_tags[5]; ++- BPTR errfile_ptr; ++- char buf[512]; ++- int len; ++- ++- /* Open NIL: for input/output */ ++- nil_in = Open("NIL:", MODE_OLDFILE); ++- ++- /* Create temporary error file in current directory */ ++- snprintf(errfile, sizeof(errfile), "binkd_err_%ld.txt", (long)time(NULL)); ++- err_out = Open(errfile, MODE_NEWFILE); ++- if (err_out == 0) ++- { ++- Log(2, "cannot create error file %s, using NIL: for error output", errfile); ++- } ++- ++- /* Extract the command (first word) to check if it exists */ ++- strncpy(cmd_copy, cmd, sizeof(cmd_copy) - 1); ++- cmd_copy[sizeof(cmd_copy) - 1] = '\0'; ++- cmd_start = cmd_copy; /* Work on copy, never modify original cmd */ ++- ++- /* Skip leading whitespace */ ++- while (*cmd_start && (*cmd_start == ' ' || *cmd_start == '\t')) ++- cmd_start++; ++- ++- /* Find end of command (first space or end) */ ++- cmd_end = cmd_start; ++- while (*cmd_end && *cmd_end != ' ' && *cmd_end != '\t') ++- cmd_end++; ++- *cmd_end = '\0'; ++- ++- /* Check if command exists */ ++- lock = Lock((STRPTR)cmd_start, SHARED_LOCK); ++- if (lock == 0) ++- { ++- Log(2, "command not found, skipping: '%s'", cmd_start); ++- Close(nil_in); ++- if (err_out) ++- Close(err_out); ++- DeleteFile((STRPTR)errfile); ++- return 0; ++- } ++- UnLock(lock); ++- ++- Log(3, "executing '%s'", cmd); ++- ++- /* Set up tags with NIL: input and error file output. ++- * Use NP_* (New Process) tags instead of SYS_* to avoid sharing ++- * file handles with parent process - prevents stderr from being ++- * closed when child exits. */ ++- exec_tags[0].ti_Tag = NP_Input; ++- exec_tags[0].ti_Data = (ULONG)nil_in; ++- exec_tags[1].ti_Tag = NP_Output; ++- exec_tags[1].ti_Data = (ULONG)nil_in; ++- exec_tags[2].ti_Tag = NP_Error; ++- exec_tags[2].ti_Data = (ULONG)err_out; ++- exec_tags[3].ti_Tag = NP_Synchronous; ++- exec_tags[3].ti_Data = TRUE; ++- exec_tags[4].ti_Tag = TAG_DONE; ++- exec_tags[4].ti_Data = 0; ++- ++- rc = SystemTagList((STRPTR)cmd, exec_tags); ++- ++- /* Close handles */ ++- Close(nil_in); ++- if (err_out) ++- Close(err_out); ++- ++- /* Log error output if command failed */ ++- if (rc != 0) ++- { ++- errfile_ptr = Open(errfile, MODE_OLDFILE); ++- if (errfile_ptr) ++- { ++- Log(2, "command failed with rc=%d, output:", rc); ++- while ((len = Read(errfile_ptr, buf, sizeof(buf) - 1)) > 0) ++- { ++- buf[len] = '\0'; ++- Log(2, "%s", buf); ++- } ++- Close(errfile_ptr); ++- } ++- } ++- ++- DeleteFile((STRPTR)errfile); ++- return rc; ++-} ++- ++-/* run3(): pipe/tunnel not supported on AmigaOS without ixemul. */ ++-int run3(const char *cmd, int *in, int *out, int *err) ++-{ ++- (void)cmd; (void)in; (void)out; (void)err; ++- Log(1, "run3: pipe connections not supported on Amiga"); ++- return -1; ++-} ++-#endif /* AMIGA */ ++- ++-#ifndef AMIGA ++ int run (char *cmd) ++ { ++ int rc=-1; ++@@ -234,7 +111,6 @@ ++ #endif ++ return rc; ++ } ++-#endif /* !AMIGA */ ++ ++ #ifdef __MINGW32__ ++ static int set_cloexec(int fd) ++@@ -260,7 +136,6 @@ ++ } ++ #endif ++ ++-#ifndef AMIGA ++ int run3 (const char *cmd, int *in, int *out, int *err) ++ { ++ int pid; ++@@ -287,14 +162,6 @@ ++ } ++ ++ #ifdef HAVE_FORK ++-#ifdef AMIGA ++- /* Pipe tunneling not supported on AmigaOS without fork() */ ++- Log(1, "run3: pipe/tunnel not supported on Amiga: %s", cmd); ++- if (in) close(pin[1]), close(pin[0]); ++- if (out) close(pout[1]), close(pout[0]); ++- if (err) close(perr[1]), close(perr[0]); ++- return -1; ++-#else ++ pid = fork(); ++ if (pid == -1) ++ { ++@@ -327,11 +194,7 @@ ++ if (strpbrk(cmd, SHELL_META)) ++ { ++ shell = SHELL; ++-#ifdef AMIGA ++- execl(shell, shell, cmd, (char *)NULL); ++-#else ++ execl(shell, shell, SHELLOPT, cmd, (char *)NULL); ++-#endif ++ } ++ else ++ { ++@@ -369,7 +232,6 @@ ++ *err = perr[0]; ++ close(perr[1]); ++ } ++-#endif /* !AMIGA */ ++ #else ++ ++ /* redirect stdin/stdout/stderr takes effect for all threads */ ++@@ -474,4 +336,3 @@ ++ return pid; ++ } ++ ++-#endif /* !AMIGA */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/sem.h binkd_pgul/sem.h ++--- binkd/sem.h 2026-04-26 10:09:54.148733026 +0100 +++++ binkd_pgul/sem.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -34,14 +34,6 @@ ++ #include ++ typedef struct SignalSemaphore MUTEXSEM; ++ ++-#ifdef AMIGA ++-typedef struct ++-{ ++- struct Task *waiter; ++- ULONG sigbit; ++-} EVENTSEM; ++-#endif ++- ++ #elif defined(WITH_PTHREADS) ++ ++ #include ++@@ -81,27 +73,25 @@ ++ * Initialise Event Semaphores. ++ */ ++ ++-#ifdef AMIGA ++-int _InitEventSem (EVENTSEM *); +++int _InitEventSem (void *); ++ ++ /* ++ * Post Semaphore. ++ */ ++ ++-int _PostSem (EVENTSEM *); +++int _PostSem (void *); ++ ++ /* ++ * Wait Semaphore. ++ */ ++ ++-int _WaitSem (EVENTSEM *, int); +++int _WaitSem (void *, int); ++ ++ /* ++ * Clean Event Semaphores. ++ */ ++ ++-int _CleanEventSem (EVENTSEM *); ++-#endif +++int _CleanEventSem (void *); ++ ++ #if defined(WITH_PTHREADS) ++ #define InitSem(sem) pthread_mutex_init(sem, NULL) ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/server.c binkd_pgul/server.c ++--- binkd/server.c 2026-04-26 10:30:03.538324924 +0100 +++++ binkd_pgul/server.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -23,10 +23,6 @@ ++ #include ++ #endif ++ ++-#ifdef AMIGA ++-#include "amiga/bsdsock.h" ++-#endif ++- ++ #include "sys.h" ++ #include "iphdr.h" ++ #include "readcfg.h" ++@@ -43,10 +39,6 @@ ++ #endif ++ #include "rfc2553.h" ++ ++-#if defined(HAVE_THREADS) || defined(AMIGA) ++-extern EVENTSEM eothread; ++-#endif ++- ++ int n_servers = 0; ++ int ext_rand = 0; ++ ++@@ -61,8 +53,7 @@ ++ void *cperl; ++ #endif ++ ++-/* Prevent shared socket closure */ ++-#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(AMIGA) && !defined(DEBUGCHILD) +++#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(DEBUGCHILD) ++ int curfd; ++ pidcmgr = 0; ++ for (curfd=0; curfdai_addr, ai->ai_addrlen) != 0) ++ { ++-#ifdef AMIGA ++- /* bsdsocket may hold the port briefly after socket close. Retry */ ++- int bind_retries = 6; ++- ++- while (bind(sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) ++- { ++- if (--bind_retries == 0) ++- { ++- Log(1, "servmgr bind(): %s", TCPERR()); ++- soclose(sockfd[sockfd_used]); ++- return -1; ++- } ++- ++- Log(2, "servmgr bind(): %s, retry in 2s...", TCPERR()); ++- sleep(2); ++- } ++-#else ++- if (bind (sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) ++- { ++- Log(1, "servmgr bind(): %s", TCPERR ()); ++- soclose(sockfd[sockfd_used]); ++- return -1; ++- } ++-#endif +++ Log(1, "servmgr bind(): %s", TCPERR ()); +++ soclose(sockfd[sockfd_used]); +++ return -1; ++ } ++ if (listen (sockfd[sockfd_used], 5) != 0) ++ { ++@@ -201,12 +168,6 @@ ++ ++ setproctitle ("server manager (listen %s)", config->listen.first->port); ++ ++- /* Save rescan_delay locally. checkcfg() may free 'config' (old config ++- * is released when usageCount reaches 0 after reload), so we must not ++- * access config->rescan_delay inside the loop after a reload */ ++- { ++- int rescan = config->rescan_delay; ++- ++ for (;;) ++ { ++ struct timeval tv; ++@@ -222,7 +183,7 @@ ++ maxfd = sockfd[curfd]; ++ } ++ tv.tv_usec = 0; ++- tv.tv_sec = rescan; +++ tv.tv_sec = CHECKCFG_INTERVAL; ++ unblocksig(); ++ check_child(&n_servers); ++ n = select(maxfd+1, &r, NULL, NULL, &tv); ++@@ -231,20 +192,8 @@ ++ { case 0: /* timeout */ ++ if (checkcfg()) ++ { ++- /* config may have been freed by checkcfg() — read rescan from ++- * the new current_config before returning for restart */ ++- { ++- BINKD_CONFIG *nc = lock_current_config(); ++- if (nc) ++- { ++- rescan = nc->rescan_delay; ++- unlock_config_structure(nc, 0); ++- } ++- } ++- ++ for (curfd=0; curfdrescan_delay; ++- unlock_config_structure(nc, 0); ++- } ++- } ++- ++ for (curfd=0; curfd ++-#endif ++- ++ /* ++ * Listens... Than calls protocol() ++ */ ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/sys.h binkd_pgul/sys.h ++--- binkd/sys.h 2026-04-26 08:48:04.281489117 +0100 +++++ binkd_pgul/sys.h 2026-04-16 17:49:00.000000000 +0100 ++@@ -22,28 +22,8 @@ ++ #ifdef HAVE_STDINT_H ++ #include ++ #endif ++-#ifdef AMIGA ++- /* Include Amiga exec proto for Delay() function */ ++- #include ++-#endif ++ #ifdef HAVE_UNISTD_H ++ #include ++- /* Undefine conflicting unistd.h macros for AMIGA */ ++- #ifdef AMIGA ++- #ifdef getpid ++- #undef getpid ++- #endif ++- ++- #ifdef sleep ++- #undef sleep ++- #endif ++- ++- /* Redefine with our Amiga implementations */ ++- #define getpid() ((int)(ULONG)FindTask(NULL)) ++- ++- #define sleep(s) Delay((ULONG)((s) * 50)) ++- ++- #endif /* AMIGA */ ++ #endif ++ #ifdef HAVE_IO_H ++ #include ++@@ -125,11 +105,6 @@ ++ #define PID() mypid ++ #endif ++ ++-#ifdef HAVE_FORK ++- #include /* Needed for SIG_BLOCK/SIG_UNBLOCK and WIFEXITED/WEXITSTATUS */ ++- #include ++-#endif ++- ++ #if defined(HAVE_FORK) && defined(HAVE_SIGPROCMASK) && defined(HAVE_WAITPID) && defined(SIG_BLOCK) ++ void switchsignal(int how); ++ #define blocksig() switchsignal(SIG_BLOCK) ++@@ -317,16 +292,8 @@ ++ ++ #ifndef PRIdMAX ++ #define PRIdMAX "ld" ++-#endif ++- ++-#ifndef PRIuMAX ++-#ifdef AMIGA ++-/* On AmigaOS m68k, uintmax_t is long long unsigned int */ ++-#define PRIuMAX "llu" ++-#else ++ #define PRIuMAX "lu" ++ #endif ++-#endif ++ ++ #ifndef HAVE_STRTOUMAX ++ #define strtoumax(ptr, endptr, base) strtoul(ptr, endptr, base) ++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/tools.c binkd_pgul/tools.c ++--- binkd/tools.c 2026-04-25 23:49:26.267090428 +0100 +++++ binkd_pgul/tools.c 2026-04-16 17:49:00.000000000 +0100 ++@@ -22,10 +22,6 @@ ++ #include ++ #endif ++ ++-#ifdef AMIGA ++-#include "amiga/bsdsock.h" ++-#endif ++- ++ #include "sys.h" ++ #include "readcfg.h" ++ #include "common.h" ++@@ -42,12 +38,6 @@ ++ #include "nt/w32tools.h" ++ #endif ++ ++-#if defined(HAVE_THREADS) || defined(AMIGA) ++-extern MUTEXSEM lsem; ++-#endif ++- ++-extern void vLog (int lev, char *s, va_list ap); ++- ++ /* ++ * We can call Log() even when we have no config ready. So, we must keep ++ * internal variables which will be updated when config is loaded ++@@ -300,10 +290,6 @@ ++ char buf[1024]; ++ int ok = 1; ++ ++-#ifdef AMIGA ++- static int need_newline = 0; ++-#endif ++- ++ /* make string in buffer */ ++ vsnprintf(buf, sizeof(buf), s, ap); ++ /* do perl hooks */ ++@@ -327,20 +313,8 @@ ++ if (lev <= current_conlog && !inetd_flag) ++ { ++ LockSem(&lsem); ++-#ifdef AMIGA ++- /* AmigaOS: go to new line for status messages to avoid overwriting */ ++- if (lev < 0 && need_newline) ++- { ++- need_newline = 0; ++- } ++- fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, ++- tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : "\r"); ++- if (lev >= 0) ++- need_newline = 1; ++-#else ++ fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, ++ tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : ""); ++-#endif ++ fflush (stderr); ++ ReleaseSem(&lsem); ++ if (lev < 0) +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/changes.diff binkd/changes.diff +--- binkd_pgul/changes.diff 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/changes.diff 2026-04-26 16:16:05.399512990 +0100 +@@ -0,0 +1,11295 @@ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/bsdsock.c binkd/amiga/bsdsock.c ++--- binkd_pgul/amiga/bsdsock.c 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/bsdsock.c 2026-04-26 11:01:10.438650436 +0100 ++@@ -0,0 +1,79 @@ +++/* +++ * bsdsock.c -- bsdsocket.library lifecycle for AmigaOS 3 +++ * +++ * bsdsock.c is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#include +++#include +++#include +++#include +++#include +++#include +++ +++/* Linker-compatibility global. Never used at runtime */ +++struct Library *SocketBase = NULL; +++ +++/* Suppress conflicting C prototypes from clib/bsdsocket_protos.h */ +++#ifndef CLIB_BSDSOCKET_PROTOS_H +++#define CLIB_BSDSOCKET_PROTOS_H +++#endif +++ +++#include +++#include +++ +++extern void Log(int lev, const char *s, ...); +++ +++/* _amiga_get_socket_base -- returns bsdsocket.library handle for calling task */ +++struct Library *_amiga_get_socket_base(void) +++{ +++ return (struct Library *)FindTask(NULL)->tc_UserData; +++} +++ +++int amiga_sock_init(void) +++{ +++ struct Task *me = FindTask(NULL); +++ struct Library *base; +++ +++ if (me->tc_UserData) +++ return 0; /* already open for this task */ +++ +++ base = OpenLibrary("bsdsocket.library", 0UL); +++ +++ if (!base) +++ { +++ fprintf(stderr, "amiga_sock_init: cannot open bsdsocket.library\n"); +++ return -1; +++ } +++ +++ /* Store in tc_UserData and global SocketBase */ +++ me->tc_UserData = (APTR)base; +++ SocketBase = base; +++ +++ /* Link the per-task errno to the TCP stack. */ +++ SetErrnoPtr(&errno, (LONG)sizeof(errno)); +++ +++ return 0; +++} +++ +++void amiga_sock_cleanup(void) +++{ +++ struct Task *me = FindTask(NULL); +++ struct Library *base = (struct Library *)me->tc_UserData; +++ +++ if (base) +++ { +++ me->tc_UserData = NULL; +++ SocketBase = NULL; +++ CloseLibrary(base); +++ } +++} +++ +++int amiga_child_sock_init(void) +++{ +++ /* Child inherits tc_UserData = NULL, opens new handle */ +++ return amiga_sock_init(); +++} ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/bsdsock.h binkd/amiga/bsdsock.h ++--- binkd_pgul/amiga/bsdsock.h 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/bsdsock.h 2026-04-26 10:49:27.706200054 +0100 ++@@ -0,0 +1,155 @@ +++/* +++ * bsdsock.h -- bsdsocket.library init and POSIX compat shims for AmigaOS 3 +++ * +++ * bsdsock.h is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifndef _AMIGA_BSDSOCK_H +++#define _AMIGA_BSDSOCK_H +++ +++#ifdef AMIGA +++ +++#include +++#include +++#include +++#include +++ +++/* Suppress conflicting C prototypes from roadshow */ +++#ifndef CLIB_BSDSOCKET_PROTOS_H +++#define CLIB_BSDSOCKET_PROTOS_H +++#endif +++ +++/* Undefine MCLBYTES/MCLSHIFT before roadshow headers */ +++#ifdef MCLBYTES +++#undef MCLBYTES +++#endif +++#ifdef MCLSHIFT +++#undef MCLSHIFT +++#endif +++ +++/* Roadshow SDK network headers */ +++#include +++#include +++#include "compat_netinet_in.h" +++#include +++#include +++#include /* inline/bsdsocket.h, no clib protos */ +++ +++/* Undefine conflicting unistd.h macros */ +++#ifdef gethostid +++#undef gethostid +++#endif +++#ifdef getdtablesize +++#undef getdtablesize +++#endif +++#ifdef gethostname +++#undef gethostname +++#endif +++ +++/* Per-task SocketBase override */ +++struct Library *_amiga_get_socket_base(void); +++ +++#ifdef SocketBase +++#undef SocketBase +++#endif +++#define SocketBase _amiga_get_socket_base() +++ +++/* Roadshow socket-specific errno values */ +++#include +++ +++#define BSDSOCK_HAS_TIMEVAL 1 +++ +++/* Socket-specific errno values from roadshow sys/errno.h */ +++#ifndef ENOTSOCK +++#define ENOTSOCK 38 /* Socket operation on non-socket */ +++#endif +++#ifndef EOPNOTSUPP +++#define EOPNOTSUPP 45 /* Operation not supported on socket */ +++#endif +++#ifndef ECONNREFUSED +++#define ECONNREFUSED 61 /* Connection refused */ +++#endif +++#ifndef ETIMEDOUT +++#define ETIMEDOUT 60 /* Connection timed out */ +++#endif +++#ifndef ECONNRESET +++#define ECONNRESET 54 /* Connection reset by peer */ +++#endif +++#ifndef EHOSTUNREACH +++#define EHOSTUNREACH 65 /* No route to host */ +++#endif +++ +++#include +++ +++/* sockaddr_storage fallback for roadshow */ +++#ifndef HAVE_SOCKADDR_STORAGE +++#ifndef sockaddr_storage +++struct sockaddr_storage +++{ +++ unsigned short ss_family; +++ char __ss_pad[22]; /* enough for IPv4 */ +++}; +++#endif +++#define HAVE_SOCKADDR_STORAGE 1 +++#endif +++ +++/* Library base functions */ +++int amiga_sock_init(void); +++void amiga_sock_cleanup(void); +++int amiga_child_sock_init(void); +++ +++/* getpid() is defined in sys.h for AMIGA */ +++/* amiga_sleep and sleep are defined in sys.h for AMIGA */ +++ +++/* select() -> WaitSelect() wrapper with Ctrl+C handling */ +++#ifndef AMIGA_SELECT_DEFINED +++#define AMIGA_SELECT_DEFINED +++ +++/* Forward-declare binkd_exit */ +++extern int binkd_exit; +++ +++/* Forward-declare Log to avoid implicit declaration warning */ +++extern void Log(int lev, char *s, ...); +++ +++static int amiga_select_wrap(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +++{ +++ ULONG sigmask = SIGBREAKF_CTRL_C; +++ int rc = WaitSelect(nfds, readfds, writefds, exceptfds, timeout, &sigmask); +++ +++ /* Ctrl+C should break blocked select() loops immediately. */ +++ if ((sigmask & SIGBREAKF_CTRL_C) != 0) +++ { +++ Log(1, "Ctrl+C detected in WaitSelect, setting binkd_exit=1"); +++ binkd_exit = 1; +++ errno = EINTR; +++ return -1; +++ } +++ +++ return rc; +++} +++ +++#define select(n, r, w, e, t) amiga_select_wrap((n), (r), (w), (e), (t)) +++ +++#endif /* AMIGA_SELECT_DEFINED */ +++ +++/* FIONBIO via IoctlSocket */ +++#ifndef FIONBIO +++#define FIONBIO 0x8004667E +++#endif +++#ifndef ioctl +++#define ioctl(s, req, arg) IoctlSocket((s), (req), (char *)(arg)) +++#endif +++ +++/* inet_ntoa -> Inet_NtoA (Amiga only) */ +++#ifdef AMIGA +++#ifdef inet_ntoa +++#undef inet_ntoa +++#endif +++#define inet_ntoa(a) Inet_NtoA(a) +++#endif +++ +++#endif /* AMIGA */ +++#endif /* _AMIGA_BSDSOCK_H */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/compat_netinet_in.h binkd/amiga/compat_netinet_in.h ++--- binkd_pgul/amiga/compat_netinet_in.h 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/compat_netinet_in.h 2026-04-26 09:53:12.337417283 +0100 ++@@ -0,0 +1,19 @@ +++/* +++ * compat_netinet_in.h -- Wrapper for netinet/in.h typedef clash +++ * +++ * compat_netinet_in.h is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifndef _AMIGA_COMPAT_NETINET_IN_H +++#define _AMIGA_COMPAT_NETINET_IN_H +++ +++#ifdef __GNUC__ +++#include_next +++#else +++#include +++#endif +++ +++#endif /* _AMIGA_COMPAT_NETINET_IN_H */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/dirent.c binkd/amiga/dirent.c ++--- binkd_pgul/amiga/dirent.c 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/dirent.c 2026-04-26 11:40:07.539429919 +0100 ++@@ -0,0 +1,137 @@ +++/* +++ * dirent.c -- POSIX directory scanning for AmigaOS 3 +++ * +++ * dirent.c is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifdef AMIGA +++ +++#include +++#include +++#include +++#include +++#include +++#include +++ +++#include "amiga/dirent.h" +++ +++/* opendir -- locks directory and allocates state for readdir() */ +++DIR *opendir(const char *path) +++{ +++ DIR *dir; +++ +++ if (!path) +++ { +++ errno = EINVAL; +++ return NULL; +++ } +++ +++ dir = (DIR *)AllocMem((LONG)sizeof(DIR), MEMF_CLEAR); +++ +++ if (!dir) +++ { +++ errno = ENOMEM; +++ return NULL; +++ } +++ +++ dir->fib = (struct FileInfoBlock *)AllocMem((LONG)sizeof(struct FileInfoBlock), MEMF_CLEAR); +++ +++ if (!dir->fib) +++ { +++ FreeMem(dir, (LONG)sizeof(DIR)); +++ errno = ENOMEM; +++ return NULL; +++ } +++ +++ dir->lock = Lock((STRPTR)path, ACCESS_READ); +++ +++ if (!dir->lock) +++ { +++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); +++ FreeMem(dir, (LONG)sizeof(DIR)); +++ errno = ENOENT; +++ return NULL; +++ } +++ +++ /* Examine the directory itself; this positions FIB for ExNext() */ +++ if (!Examine(dir->lock, dir->fib)) +++ { +++ UnLock(dir->lock); +++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); +++ FreeMem(dir, (LONG)sizeof(DIR)); +++ errno = EACCES; +++ return NULL; +++ } +++ +++ /* fib_DirEntryType > 0 means this IS a directory */ +++ if (dir->fib->fib_DirEntryType <= 0) +++ { +++ UnLock(dir->lock); +++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); +++ FreeMem(dir, (LONG)sizeof(DIR)); +++ errno = ENOTDIR; +++ return NULL; +++ } +++ +++ dir->first = 1; /* ExNext() has not been called yet */ +++ +++ return dir; +++} +++ +++/* readdir -- advances to next directory entry */ +++struct dirent *readdir(DIR *dir) +++{ +++ LONG dos_rc; +++ LONG dos_err; +++ +++ if (!dir) +++ { +++ errno = EINVAL; +++ return NULL; +++ } +++ +++ /* ExNext() advances past the last entry returned by Examine/ExNext */ +++ dos_rc = ExNext(dir->lock, dir->fib); +++ +++ if (!dos_rc) +++ { +++ dos_err = IoErr(); +++ +++ if (dos_err == ERROR_NO_MORE_ENTRIES) +++ return NULL; +++ +++ errno = EIO; +++ return NULL; +++ } +++ +++ /* Copy name into the entry buffer */ +++ strncpy(dir->entry.d_name, dir->fib->fib_FileName, AMIGA_NAME_MAX - 1); +++ dir->entry.d_name[AMIGA_NAME_MAX - 1] = '\0'; +++ dir->entry.d_ino = 0; +++ +++ return &dir->entry; +++} +++ +++/* closedir -- releases all resources associated with dir */ +++int closedir(DIR *dir) +++{ +++ if (!dir) +++ { +++ errno = EINVAL; +++ return -1; +++ } +++ +++ if (dir->lock) +++ UnLock(dir->lock); +++ +++ if (dir->fib) +++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); +++ +++ FreeMem(dir, (LONG)sizeof(DIR)); +++ return 0; +++} +++ +++#endif /* AMIGA */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/dirent.h binkd/amiga/dirent.h ++--- binkd_pgul/amiga/dirent.h 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/dirent.h 2026-04-26 09:53:13.628932082 +0100 ++@@ -0,0 +1,53 @@ +++/* +++ * dirent.h -- POSIX directory scanning for AmigaOS 3 +++ * +++ * dirent.h is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifndef _AMIGA_DIRENT_H +++#define _AMIGA_DIRENT_H +++ +++#ifdef AMIGA +++ +++#include +++#include +++ +++/* Maximum name length: AmigaDOS allows 107 characters */ +++#define AMIGA_NAME_MAX 108 +++ +++struct dirent +++{ +++ unsigned long d_ino; /* inode -- always 0 on AmigaDOS */ +++ char d_name[AMIGA_NAME_MAX]; /* null-terminated file name */ +++}; +++ +++/* struct utimbuf for AmigaOS 3 without ixemul */ +++#ifndef _AMIGA_UTIMBUF_DEFINED +++#define _AMIGA_UTIMBUF_DEFINED +++ +++struct utimbuf +++{ +++ long actime; /* access time (unused by SetFileDate) */ +++ long modtime; /* modification time (POSIX time_t) */ +++}; +++ +++int utime(const char *path, const struct utimbuf *times); +++#endif +++ +++typedef struct _amiga_dir +++{ +++ BPTR lock; /* directory lock */ +++ struct FileInfoBlock *fib; /* reusable FileInfoBlock */ +++ int first; /* flag: first call not yet */ +++ struct dirent entry; /* storage returned to caller */ +++} DIR; +++ +++DIR *opendir(const char *path); +++struct dirent *readdir(DIR *dir); +++int closedir(DIR *dir); +++ +++#endif /* AMIGA */ +++#endif /* _AMIGA_DIRENT_H */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop.c binkd/amiga/evloop.c ++--- binkd_pgul/amiga/evloop.c 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/evloop.c 2026-04-26 11:46:47.344533199 +0100 ++@@ -0,0 +1,509 @@ +++/* +++ * evloop.c -- non-blocking event loop for AmigaOS 3 +++ * +++ * evloop.c is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++/* Suppress clib bsdsocket prototypes before any socket header */ +++#ifndef CLIB_BSDSOCKET_PROTOS_H +++#define CLIB_BSDSOCKET_PROTOS_H +++#endif +++ +++#include +++#include +++#include +++ +++#include +++#include +++#include +++ +++#include "sys.h" +++#include "readcfg.h" +++#include "common.h" +++#include "tools.h" +++#include "protocol.h" +++#include "sem.h" +++#include "server.h" +++#include "amiga/bsdsock.h" +++#include "amiga/evloop.h" +++#include "amiga/evloop_int.h" +++#include "amiga/proto_amiga.h" +++ +++/* Externals */ +++extern SOCKET sockfd[MAX_LISTENSOCK]; +++extern int sockfd_used; +++extern int binkd_exit; +++extern int server_flag, client_flag; +++ +++/* Session table (shared with sock.c and session.c) */ +++sess_t *sessions = NULL; +++int max_sessions = 0; +++ +++/* +++ * calc_max_sessions -- Compute session slot count from config + flags +++ * Shared by init and config-reload paths +++ */ +++static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) +++{ +++ int servers = config->max_servers; +++ int clients = config->max_clients; +++ int total; +++ +++ if (servers == 0 && clients == 0) +++ { +++ Log(5, "DEBUG: Using default 2 slots (no config found)"); +++ return 2; +++ } +++ +++ Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); +++ +++ if (srv_flag && servers < 1) +++ servers = 1; +++ +++ if (cli_flag && clients < 1) +++ clients = 1; +++ +++ total = servers + clients; +++ +++ if (total < 2) +++ total = 2; +++ +++ Log(5, "DEBUG: Calculated max_sessions=%d", total); +++ +++ return total; +++} +++ +++/* init_session_table -- Allocate and zero-initialise the session array */ +++static int init_session_table(int slots) +++{ +++ int i; +++ +++ sessions = calloc(slots, sizeof(sess_t)); +++ +++ if (!sessions) +++ { +++ Log(1, "Failed to allocate session table"); +++ return 0; +++ } +++ +++ for (i = 0; i < slots; i++) +++ { +++ memset(&sessions[i], 0, sizeof(sess_t)); +++ memset(&sessions[i].state, 0, sizeof(STATE)); +++ sessions[i].fd = INVALID_SOCKET; +++ sessions[i].phase = SESS_FREE; +++ } +++ +++ return 1; +++} +++ +++/* +++ * build_fdsets -- Populate r/w fd_sets from listen sockets and sessions +++ * Returns the highest fd seen (maxfd) +++ */ +++static int build_fdsets(fd_set *r, fd_set *w) +++{ +++ int i, maxfd = 0; +++ +++ FD_ZERO(r); +++ FD_ZERO(w); +++ +++ /* server side: listen sockets */ +++ for (i = 0; i < sockfd_used; i++) +++ { +++ if (sockfd[i] != INVALID_SOCKET) +++ { +++ FD_SET(sockfd[i], r); +++ +++ if ((int)sockfd[i] > maxfd) +++ maxfd = (int)sockfd[i]; +++ } +++ } +++ +++ /* client + server sessions */ +++ for (i = 0; i < max_sessions; i++) +++ { +++ sess_t *s = &sessions[i]; +++ +++ if (s->phase == SESS_FREE || s->fd == INVALID_SOCKET) +++ continue; +++ +++ if ((int)s->fd > maxfd) +++ maxfd = (int)s->fd; +++ +++ if (s->phase == SESS_CONNECTING) +++ { +++ /* client: waiting for non-blocking connect() */ +++ FD_SET(s->fd, w); +++ } +++ else +++ { +++ /* Server or established client session */ +++ FD_SET(s->fd, r); +++ +++ if (s->state.msgs || s->state.oleft || s->state.send_eof || (s->state.out.f && !s->state.off_req_sent && !s->state.waiting_for_GOT)) +++ FD_SET(s->fd, w); +++ } +++ } +++ +++ Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); +++ return maxfd; +++} +++ +++/* +++ * handle_server_accept -- Accept new inbound connections on all listen fds +++ * Returns 0 normally, -1 if binkd_exit was set during accept +++ */ +++static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) +++{ +++ int i; +++ +++ Log(5, "DEBUG: Before accept loop"); +++ +++ for (i = 0; i < sockfd_used; i++) +++ { +++ if (FD_ISSET(sockfd[i], r)) +++ do_accept(sockfd[i], config); +++ +++ if (binkd_exit) +++ { +++ Log(5, "DEBUG: binkd_exit during accept loop"); +++ return -1; +++ } +++ } +++ +++ Log(5, "DEBUG: After accept loop"); +++ +++ return 0; +++} +++ +++/* +++ * advance_sessions -- Step every active session (server + client) +++ * Returns the number of non-free sessions processed +++ */ +++static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) +++{ +++ int i, active = 0; +++ +++ Log(5, "DEBUG: Before advance sessions"); +++ +++ for (i = 0; i < max_sessions; i++) +++ { +++ sess_t *s = &sessions[i]; +++ +++ Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); +++ +++ if (s->phase == SESS_FREE) +++ continue; +++ +++ active++; +++ +++ if (s->phase == SESS_CONNECTING) +++ { +++ /* client: Complete the non-blocking connect */ +++ if (FD_ISSET(s->fd, w)) +++ check_connect(i, config); +++ } +++ else +++ { +++ int rd = FD_ISSET(s->fd, r); +++ int wr = FD_ISSET(s->fd, w); +++ +++ /* Always step: protocol must advance internal state even +++ * when WaitSelect reports no activity (e.g. second batch +++ * EOB send, TCP FIN detection after remote closes) */ +++ do_session_step(i, rd, wr, config); +++ } +++ +++ if (binkd_exit) +++ break; +++ } +++ +++ return active; +++} +++ +++/* +++ * handle_config_reload -- Resize session table and reopen listen sockets +++ * Returns 1 if the caller should break out of the main loop, 0 otherwise +++ */ +++static int handle_config_reload(BINKD_CONFIG **config, int srv_flag, int cli_flag) +++{ +++ int i, new_max; +++ BINKD_CONFIG *nc = lock_current_config(); +++ +++ if (nc) +++ { +++ new_max = calc_max_sessions(nc, srv_flag, cli_flag); +++ +++ if (new_max != max_sessions) +++ { +++ sess_t *ns = realloc(sessions, new_max * sizeof(sess_t)); +++ +++ if (ns) +++ { +++ for (i = max_sessions; i < new_max; i++) +++ { +++ memset(&ns[i], 0, sizeof(sess_t)); +++ memset(&ns[i].state, 0, sizeof(STATE)); +++ ns[i].fd = INVALID_SOCKET; +++ ns[i].phase = SESS_FREE; +++ } +++ +++ sessions = ns; +++ max_sessions = new_max; +++ +++ Log(4, "Session table resized to %d slots", max_sessions); +++ } +++ else +++ { +++ Log(1, "Failed to resize session table, keeping current size"); +++ } +++ } +++ +++ unlock_config_structure(nc, 0); +++ } +++ +++ close_listen_sockets(); +++ *config = lock_current_config(); +++ +++ if (srv_flag && open_listen_sockets(*config) < 0) +++ { +++ unlock_config_structure(*config, 0); +++ return 1; /* fatal — break main loop */ +++ } +++ +++ unlock_config_structure(*config, 0); +++ *config = lock_current_config(); +++ return 0; +++} +++ +++/* evloop_cleanup -- Close sessions and free resources on exit */ +++static void evloop_cleanup(BINKD_CONFIG *config, int config_locked) +++{ +++ int i; +++ +++ if (config_locked) +++ unlock_config_structure(config, 0); +++ +++ if (sessions) +++ { +++ for (i = 0; i < max_sessions; i++) +++ { +++ if (sessions[i].phase == SESS_RUNNING) +++ { +++ amiga_proto_close(&sessions[i].state, config, 0); +++ +++ if (sessions[i].inbound) +++ n_servers--; +++ else +++ n_clients--; +++ } +++ else if (sessions[i].phase == SESS_CONNECTING) +++ { +++ n_clients--; +++ } +++ sess_free(i); +++ } +++ +++ free(sessions); +++ sessions = NULL; +++ } +++ +++ close_listen_sockets(); +++ amiga_sock_cleanup(); +++ Log(4, "evloop done"); +++} +++ +++/* amiga_evloop_run -- Entry point: init, then main WaitSelect() loop */ +++void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) +++{ +++ int config_locked = 0; +++ time_t last_rescan = 0; +++ time_t now; +++ fd_set r, w; +++ struct timeval tv; +++ int n, maxfd; +++ int active_sessions = 0; +++ static int idle_loops = 0; +++ +++ /* Sync globals so try_outbound() and friends see the correct flags */ +++ server_flag = srv_flag; +++ client_flag = cli_flag; +++ +++ sockfd_used = 0; +++ srand((unsigned int)time(NULL)); +++ +++ Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); +++ Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); +++ +++ /* Initialise session table */ +++ max_sessions = calc_max_sessions(config, server_flag, client_flag); +++ +++ if (max_sessions < 2) +++ { +++ Log(2, "WARNING: max_sessions=%d is too low, forcing to 2", max_sessions); +++ max_sessions = 2; +++ } +++ +++ Log(4, "evloop start (AmigaOS 3, WaitSelect, %d slots)", max_sessions); +++ +++ if (!init_session_table(max_sessions)) +++ return; +++ +++ /* server: Open listen sockets */ +++ if (server_flag && open_listen_sockets(config) < 0) +++ { +++ Log(0, "evloop: cannot open listen sockets"); +++ free(sessions); +++ sessions = NULL; +++ return; +++ } +++ +++ Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); +++ +++ /* Initial outbound attempt before waiting (important for poll -p mode) */ +++ Log(5, "DEBUG: Initial try_outbound before main loop"); +++ try_outbound(config); +++ last_rescan = time(NULL); /* Reset timer since we just did an attempt */ +++ +++ /* ===== Main loop ===== */ +++ for (;;) +++ { +++ if (binkd_exit) +++ { +++ Log(1, "binkd_exit detected at loop start, exiting"); +++ break; +++ } +++ +++ /* Build fd_sets */ +++ Log(5, "DEBUG: Building fd_sets"); +++ maxfd = build_fdsets(&r, &w); +++ +++ tv.tv_sec = 1; +++ tv.tv_usec = 0L; +++ +++ /* WaitSelect() with nfds>0 but empty fd_sets causes guru #80000006 :/ +++ * Use select(0,...) as a pure sleep when no sockets are active */ +++ if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) +++ maxfd = 1; +++ +++ Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); +++ +++ if (maxfd == 0) +++ { +++ /* No sockets yet -- use Delay() instead of select(0,...) +++ * as WaitSelect with nfds=0 can block indefinitely on AmigaOS */ +++ Delay(50); /* 1 second = 50 ticks at 50Hz PAL */ +++ n = 0; /* simulate timeout */ +++ } +++ else +++ n = select(maxfd + 1, &r, &w, NULL, &tv); +++ +++ Log(5, "DEBUG: select() returned n=%d", n); +++ +++ if (binkd_exit) +++ { +++ Log(1, "binkd_exit detected after select(), exiting"); +++ break; +++ } +++ +++ Delay(1UL); /* 1 tick = 20ms @ 50Hz, prevents CPU hogging */ +++ +++ /* Handle select errors */ +++ if (n < 0) +++ { +++ if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) +++ { +++ Log(5, "DEBUG: select interrupted, continuing"); +++ continue; +++ } +++ +++ if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) +++ { +++ Log(2, "select: %s, reopening", TCPERR()); +++ +++ close_listen_sockets(); +++ +++ if (server_flag && open_listen_sockets(config) < 0) +++ break; +++ +++ continue; +++ } +++ +++ Log(1, "select: %s", TCPERR()); +++ break; +++ } +++ else if (n == 0) +++ { +++ Log(5, "DEBUG: select timeout, continuing"); +++ } +++ +++ /* server: Accept new inbound connections */ +++ if (server_flag) +++ { +++ if (handle_server_accept(&r, config) < 0) +++ break; +++ } +++ +++ if (binkd_exit) +++ break; +++ +++ /* server + client: Advance all active sessions */ +++ active_sessions = advance_sessions(&r, &w, config); +++ +++ if (binkd_exit) +++ break; +++ +++ /* client: Time-based outbound scan + config reload */ +++ now = time(NULL); +++ +++ if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) +++ { +++ Log(5, "DEBUG: Before try_outbound"); +++ +++ try_outbound(config); +++ +++ Log(5, "DEBUG: After try_outbound"); +++ +++ if (checkcfg()) +++ { +++ if (handle_config_reload(&config, server_flag, client_flag)) +++ break; +++ +++ config_locked = 1; +++ } +++ +++ config->q_present = 0; +++ last_rescan = now; +++ } +++ +++ /* client: Poll-mode idle exit */ +++ /* Reset counter whenever there is something going on */ +++ if (n_clients > 0 || active_sessions > 0) +++ { +++ if (idle_loops > 0) +++ Log(2, "Activity detected, reset idle counter"); +++ +++ idle_loops = 0; +++ } +++ +++ if (!server_flag && active_sessions == 0 && n_clients == 0) +++ { +++ idle_loops++; +++ +++ Log(2, "Idle loop %d/2 (no server, no sessions, no clients)", idle_loops); +++ +++ if (idle_loops > 1) +++ { +++ Log(0, "the queue is empty, quitting..."); +++ break; +++ } +++ } +++ } +++ /* ===== End main loop ===== */ +++ +++ evloop_cleanup(config, config_locked); +++} ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop.h binkd/amiga/evloop.h ++--- binkd_pgul/amiga/evloop.h 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/evloop.h 2026-04-26 11:47:03.034239655 +0100 ++@@ -0,0 +1,34 @@ +++/* +++ * evloop.h -- non-blocking event loop for AmigaOS 3 +++ * +++ * evloop.h is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifndef _AMIGA_EVLOOP_H +++#define _AMIGA_EVLOOP_H +++ +++#ifdef AMIGA +++ +++#include "readcfg.h" +++#include "protoco2.h" /* STATE */ +++ +++/* amiga_proto_step() return codes — also used by protocol.c */ +++#define APROTO_RUNNING 0 /* session alive, call again */ +++#define APROTO_DONE_OK 1 /* session finished, success */ +++#define APROTO_DONE_ERR 2 /* session failed */ +++ +++/* +++ * amiga_evloop_run -- entry point replacing servmgr() + clientmgr() +++ * +++ * Opens listen sockets (when server_flag), then runs a WaitSelect() +++ * loop that multiplexes sessions dynamically based on config->max_servers +++ * and config->max_clients. Minimum 2 sessions are always allocated +++ * Returns only when binkd_exit != 0 +++ */ +++void amiga_evloop_run(BINKD_CONFIG *config, int server_flag, int client_flag); +++ +++#endif /* AMIGA */ +++#endif /* _AMIGA_EVLOOP_H */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop_int.h binkd/amiga/evloop_int.h ++--- binkd_pgul/amiga/evloop_int.h 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/evloop_int.h 2026-04-26 11:02:58.166969078 +0100 ++@@ -0,0 +1,68 @@ +++/* +++ * evloop_int.h -- internal types shared by evloop.c, sock.c, session.c +++ * +++ * evloop_int.h is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifndef AMIGA_EVLOOP_INT_H +++#define AMIGA_EVLOOP_INT_H +++ +++#include "protoco2.h" +++#include "amiga/bsdsock.h" +++#include "ftnnode.h" +++#include "readcfg.h" +++ +++/* Session lifecycle */ +++typedef enum +++{ +++ SESS_FREE = 0, /* slot available */ +++ SESS_CONNECTING = 1, /* waiting for connect() */ +++ SESS_RUNNING = 2 /* BinkP session active */ +++} sess_phase_t; +++ +++/* Per-session state */ +++typedef struct +++{ +++ sess_phase_t phase; +++ SOCKET fd; +++ STATE state; +++ int inbound; /* 1=accepted, 0=outbound */ +++ +++ FTN_NODE *node; +++ struct addrinfo *ai_head; /* full getaddrinfo list */ +++ struct addrinfo *ai_cur; /* candidate being tried */ +++ time_t conn_start; +++ +++ char host[BINKD_FQDNLEN + 1]; +++ char port[MAXPORTSTRLEN + 1]; +++ char ip[BINKD_FQDNLEN + 1]; +++ +++ time_t last_io; +++} sess_t; +++ +++/* Globals defined in evloop.c */ +++extern sess_t *sessions; +++extern int max_sessions; +++ +++/* Defined in server.c and client.c respectively */ +++extern int n_servers; +++extern int n_clients; +++ +++/* sock.c */ +++void set_nonblock(SOCKET fd); +++int open_listen_sockets(BINKD_CONFIG *config); +++void close_listen_sockets(void); +++ +++/* session.c */ +++int sess_alloc(void); +++void sess_free(int idx); +++void do_accept(SOCKET lfd, BINKD_CONFIG *config); +++int start_connect(sess_t *s, BINKD_CONFIG *config); +++void check_connect(int idx, BINKD_CONFIG *config); +++int try_outbound(BINKD_CONFIG *config); +++void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config); +++ +++#endif /* AMIGA_EVLOOP_INT_H */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/proto_amiga.c binkd/amiga/proto_amiga.c ++--- binkd_pgul/amiga/proto_amiga.c 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/proto_amiga.c 2026-04-26 11:52:04.887130443 +0100 ++@@ -0,0 +1,269 @@ +++/* +++ * proto_amiga.c -- Amiga non-blocking BinkP protocol implementation +++ * +++ * proto_amiga.c is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#include +++#include +++#include +++#include +++#include +++ +++#include "sys.h" +++#include "readcfg.h" +++#include "common.h" +++#include "protocol.h" +++#include "ftnaddr.h" +++#include "ftnnode.h" +++#include "ftnq.h" +++#include "tools.h" +++#include "bsy.h" +++#include "inbound.h" +++#include "protoco2.h" +++#include "prothlp.h" +++#include "binlog.h" +++#include "evloop.h" +++ +++/* External functions from protocol.c */ +++extern int init_protocol(STATE *state, SOCKET s_in, SOCKET s_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config); +++extern int banner(STATE *state, BINKD_CONFIG *config); +++extern int recv_block(STATE *state, BINKD_CONFIG *config); +++extern int send_block(STATE *state, BINKD_CONFIG *config); +++extern void bsy_touch(BINKD_CONFIG *config); +++extern FTNQ *process_rcvdlist(STATE *state, FTNQ *q, BINKD_CONFIG *config); +++extern int start_file_transfer(STATE *state, FTNQ *q, BINKD_CONFIG *config); +++extern void ND_set_status(const char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config); +++extern void deinit_protocol(STATE *state, BINKD_CONFIG *config, int status); +++extern void evt_set(EVTQ *eq); +++extern void msg_send2(STATE *state, t_msg m, char *s1, char *s2); +++ +++/* External functions from other modules */ +++extern void log_end_of_session(int err, STATE *state, BINKD_CONFIG *config); +++extern void inb_remove_partial(STATE *state, BINKD_CONFIG *config); +++extern void good_try(FTN_ADDR *fa, char *comment, BINKD_CONFIG *config); +++extern void bad_try(FTN_ADDR *fa, const char *error, const int where, BINKD_CONFIG *config); +++extern int create_poll(FTN_ADDR *fa, int flvr, BINKD_CONFIG *config); +++extern void hold_node(FTN_ADDR *fa, time_t hold_until, BINKD_CONFIG *config); +++extern int binkd_exit; +++ +++/* External variables */ +++extern int n_servers; +++ +++/* +++ * amiga_proto_open -- Initialise a session and send the BinkP banner +++ * +++ * fd : Connected socket (same fd for in and out) +++ * to : Outbound node, NULL for inbound +++ * fa : Local AKA to use, may be NULL +++ * host : Remote hostname or dotted-IP string (caller-owned, stable) +++ * port : Remote port string, may be NULL +++ * dst_ip : Numeric remote IP, may be NULL (falls back to host) +++ * config : Current config +++ * +++ * Returns 0 on success, -1 on error (caller must close fd) +++ */ +++int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config) +++{ +++ struct sockaddr_storage sa; +++ socklen_t salen = (socklen_t)sizeof(sa); +++ char ownhost[BINKD_FQDNLEN + 1]; +++ char ownserv[MAXSERVNAME + 1]; +++ int rc; +++ +++ if (!init_protocol(state, fd, fd, to, fa, config)) +++ return -1; +++ +++ /* Peer identity for logging and %ip config checks */ +++ state->ipaddr = dst_ip ? (char *)dst_ip : (char *)host; +++ state->peer_name = (host && *host) ? (char *)host : state->ipaddr; +++ +++ /* local endpoint: Not used further, skip to avoid dangling pointer */ +++ +++ Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); +++ +++ /* banner() sends M_NUL lines and ADR messages */ +++ if (!banner(state, config)) +++ return -1; +++ +++ /* refuse if server limit reached */ +++ if (!to && n_servers > config->max_servers) +++ { +++ Log(1, "too many servers"); +++ msg_send2(state, M_BSY, "Too many servers", 0); +++ +++ return -1; +++ } +++ +++ return 0; +++} +++ +++/* +++ * amiga_proto_step -- Run one recv/send iteration of the BinkP loop +++ * +++ * readable : Non-zero if the socket has incoming data (from WaitSelect) +++ * writable : Non-zero if the socket can accept outgoing data +++ * +++ * Returns APROTO_RUNNING, APROTO_DONE_OK, or APROTO_DONE_ERR +++ * +++ * This is the loop body of protocol() with the select() call removed +++ * recv_block() and send_block() already handle EWOULDBLOCK gracefully, +++ * so calling this on a non-blocking socket is safe +++ */ +++int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config) +++{ +++ FTNQ *q; +++ int no; +++ +++ if (state->io_error) +++ return APROTO_DONE_ERR; +++ +++ /* Advance outgoing file queue if nothing is being sent */ +++ if (!state->local_EOB && state->q && !state->out.f && !state->waiting_for_GOT && !state->off_req_sent && state->state != P_NULL) +++ { +++ while (1) +++ { +++ q = 0; +++ if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) +++ { +++ if (start_file_transfer(state, q, config)) +++ break; +++ } +++ else +++ { +++ q_free(state->q, config); +++ state->q = 0; +++ break; +++ } +++ } +++ } +++ +++ /* Nothing left to send — issue EOB */ +++ if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) +++ { +++ if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) +++ { +++ state->local_EOB = 1; +++ msg_send2(state, M_EOB, 0, 0); +++ } +++ } +++ +++ /* Recv step: Only when socket is readable */ +++ if (readable) +++ { +++ if (!recv_block(state, config)) +++ return APROTO_DONE_ERR; +++ } +++ +++ /* +++ * send step: drive even when writable=0 if there is buffered data, +++ * pending messages, a file mid-transfer, or an EOF to flush. +++ */ +++ if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) +++ { +++ no = send_block(state, config); +++ +++ if (!no && no != 2) +++ return APROTO_DONE_ERR; +++ } +++ +++ bsy_touch(config); +++ +++ /* Batch/Session-end detection — Mirrors the break logic in protocol() */ +++ if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) +++ { +++ if (state->rcvdlist) +++ { +++ state->q = process_rcvdlist(state, state->q, config); +++ +++ q_to_killlist(&state->killlist, &state->n_killlist, state->q); +++ free_rcvdlist(&state->rcvdlist, &state->n_rcvdlist); +++ } +++ +++ Log(6, "batch: %i msgs", state->msgs_in_batch); +++ +++ if (state->msgs_in_batch <= 2 || (state->major * 100 + state->minor <= 100)) +++ { +++ /* Session done */ +++ ND_set_status("", &state->ND_addr, state, config); +++ state->ND_addr.z = -1; +++ +++ return APROTO_DONE_OK; +++ } +++ +++ /* Start next batch */ +++ state->msgs_in_batch = 0; +++ state->remote_EOB = 0; +++ state->local_EOB = 0; +++ +++ if (OK_SEND_FILES(state, config)) +++ { +++ state->q = q_scan_boxes(state->q, state->fa, state->nfa, state->to ? 1 : 0, config); +++ state->q = q_sort(state->q, state->fa, state->nfa, config); +++ } +++ } +++ +++ return APROTO_RUNNING; +++} +++ +++/* +++ * amiga_proto_close -- Flush remaining I/O and release STATE resources +++ * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR +++ */ +++void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) +++{ +++ int no; +++ char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; +++ int status = ok ? 0 : 1; +++ +++ /* Drain inbound queue */ +++ if (!state->io_error) +++ { +++ while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) +++ Log(9, "purged %d bytes", no); +++ } +++ +++ /* Flush pending outbound messages */ +++ while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) +++ ; +++ +++ if (ok) +++ { +++ log_end_of_session(0, state, config); +++ process_killlist(state->killlist, state->n_killlist, 's'); +++ inb_remove_partial(state, config); +++ +++ if (state->to) +++ good_try(&state->to->fa, "CONNECT/BND", config); +++ } +++ else +++ { +++ log_end_of_session(1, state, config); +++ process_killlist(state->killlist, state->n_killlist, 0); +++ +++ if (!binkd_exit && state->to) +++ bad_try(&state->to->fa, "Bad session", BAD_IO, config); +++ +++ /* Restore poll flavour if files were left mid-transfer */ +++ if (state->to && tolower(state->maxflvr) != 'h') +++ { +++ Log(4, "restoring poll with '%c' flavour", state->maxflvr); +++ +++ create_poll(&state->to->fa, state->maxflvr, config); +++ } +++ } +++ +++ if (state->to && state->r_skipped_flag && config->hold_skipped > 0) +++ { +++ Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); +++ +++ hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); +++ } +++ +++ deinit_protocol(state, config, status); +++ evt_set(state->evt_queue); +++ state->evt_queue = NULL; +++} ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/proto_amiga.h binkd/amiga/proto_amiga.h ++--- binkd_pgul/amiga/proto_amiga.h 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/proto_amiga.h 2026-04-26 11:53:22.716799421 +0100 ++@@ -0,0 +1,30 @@ +++/* +++ * proto_amiga.h -- Amiga non-blocking BinkP protocol implementation +++ * +++ * proto_amiga.h is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifndef _PROTO_AMIGA_H +++#define _PROTO_AMIGA_H +++ +++#include "protoco2.h" +++#include "readcfg.h" +++ +++/* amiga_proto_step() return codes */ +++#define APROTO_RUNNING 0 +++#define APROTO_DONE_OK 1 +++#define APROTO_DONE_ERR 2 +++ +++/* amiga_proto_open -- Initialise a session and send the BinkP banner */ +++int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config); +++ +++/* amiga_proto_step-- run one recv / send iteration of the BinkP loop */ +++int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config); +++ +++/* amiga_proto_close -- flush remaining I/O and release STATE resources */ +++void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok); +++ +++#endif /* _PROTO_AMIGA_H */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/rename.c binkd/amiga/rename.c ++--- binkd_pgul/amiga/rename.c 2026-04-16 17:49:00.000000000 +0100 +++++ binkd/amiga/rename.c 2026-04-25 19:33:26.785601976 +0100 ++@@ -1,10 +1,137 @@ ++ #include ++ #include +++#include +++ +++#include +++#include /* atoi */ +++#include /* isdigit */ +++ +++#define PATHBUF 512 ++ ++ int o_rename(char *from, char *to) ++ { ++- if (Rename((STRPTR)from, (STRPTR)to)) /* cross-volume move won't work */ +++ struct FileInfoBlock *fib; +++ char dir[PATHBUF]; +++ char base[PATHBUF]; +++ char newname[PATHBUF]; +++ char *slash; +++ ULONG max = 0; +++ BPTR dirlock; +++ char *d = NULL; +++ const char *src = NULL; +++ ULONG n = 0; +++ +++ /* Try direct rename first */ +++ if (Rename((STRPTR)from, (STRPTR)to)) +++ return 0; +++ +++ /* Split path */ +++ slash = strrchr(to, '/'); +++ +++ if (!slash) +++ slash = strrchr(to, ':'); +++ +++ if (slash) +++ { +++ ULONG len = slash - to; +++ +++ if (len >= PATHBUF) +++ len = PATHBUF - 1; +++ +++ strncpy(dir, to, len); +++ dir[len] = '\0'; +++ +++ strncpy(base, slash + 1, PATHBUF - 1); +++ base[PATHBUF - 1] = '\0'; +++ } +++ else +++ { +++ strcpy(dir, "."); +++ strncpy(base, to, PATHBUF - 1); +++ base[PATHBUF - 1] = '\0'; +++ } +++ +++ /* Lock directory */ +++ dirlock = Lock((STRPTR)dir, ACCESS_READ); +++ +++ if (!dirlock) +++ { +++ errno = ENOENT; +++ return -1; +++ } +++ +++ fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); +++ +++ if (!fib) +++ { +++ UnLock(dirlock); +++ errno = ENOMEM; +++ return -1; +++ } +++ +++ /* Scan directory safely under lock */ +++ if (Examine(dirlock, fib)) +++ { +++ while (ExNext(dirlock, fib)) +++ { +++ if (strncmp(fib->fib_FileName, base, strlen(base)) == 0) +++ { +++ const char *p = NULL; +++ +++ p = fib->fib_FileName + strlen(base); +++ +++ if (*p != '.') +++ { +++ continue; +++ } +++ +++ /* .001 style */ +++ if (isdigit((UBYTE)p[1]) && isdigit((UBYTE)p[2]) && isdigit((UBYTE)p[3])) +++ { +++ n = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); +++ if (n > max) +++ max = n; +++ } +++ +++ /* FIDO volume style (.mo0 .th1 etc) */ +++ if (isdigit((UBYTE)p[1]) && !isdigit((UBYTE)p[2])) +++ { +++ n = p[1] - '0'; +++ if (n > max) +++ max = n; +++ } +++ } +++ } +++ } +++ +++ FreeDosObject(DOS_FIB, fib); +++ UnLock(dirlock); +++ +++ /* Build new name */ +++ d = newname; +++ src = to; +++ n = max + 1; +++ +++ if (n > 999) +++ n = 0; +++ +++ /* Copy base */ +++ while (*src && (d - newname) < (PATHBUF - 5)) +++ *d++ = *src++; +++ +++ *d++ = '.'; +++ +++ /* Manual 3-digit write */ +++ *d++ = '0' + (n / 100); +++ n %= 100; +++ *d++ = '0' + (n / 10); +++ *d++ = '0' + (n % 10); +++ *d = '\0'; +++ +++ /* Rename */ +++ if (Rename((STRPTR)from, (STRPTR)newname)) +++ return 0; +++ +++ errno = EACCES; ++ return -1; ++- else ++- return 0; ++ } ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/rfc2553_amiga.c binkd/amiga/rfc2553_amiga.c ++--- binkd_pgul/amiga/rfc2553_amiga.c 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/rfc2553_amiga.c 2026-04-26 11:54:19.321503648 +0100 ++@@ -0,0 +1,323 @@ +++/* +++ * rfc2553_amiga.c -- getaddrinfo/getnameinfo fallback for AmigaOS 3 +++ * +++ * rfc2553_amiga.c is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifdef AMIGA +++ +++#include "amiga/bsdsock.h" /* LP stubs + SocketBase */ +++#include "rfc2553.h" /* sets HAVE_GETADDRINFO / HAVE_GETNAMEINFO */ +++#include "sem.h" +++ +++#include +++#include +++#include +++#include +++ +++#define safe_strncpy(dst, src, n) \ +++ do \ +++ { \ +++ strncpy((dst), (src), (n)); \ +++ (dst)[(n) - 1] = '\0'; \ +++ } while (0) +++ +++#ifndef HAVE_GETADDRINFO +++ +++void freeaddrinfo(struct addrinfo *ai); /* forward decl */ +++ +++int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) +++{ +++ struct addrinfo **tail = res; +++ struct hostent *hent = NULL; +++ unsigned int port; +++ int proto; +++ const char *end; +++ char **addrp; +++ +++ static char passive_dummy = '\0'; +++ char *passive_list[2] = {&passive_dummy, NULL}; +++ +++ if (!res) +++ { +++ return EAI_UNKNOWN; +++ } +++ +++ *res = NULL; +++ +++ port = servname ? htons((unsigned short)strtol(servname, (char **)&end, 0)) : 0; +++ proto = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; +++ +++ lockresolvsem(); +++ +++ if (servname && end != servname + strlen(servname)) +++ { +++ struct servent *se = NULL; +++ +++ if (!hints || hints->ai_socktype == SOCK_STREAM) +++ se = getservbyname((char *)servname, "tcp"); +++ +++ if (hints && hints->ai_socktype == SOCK_DGRAM) +++ se = getservbyname((char *)servname, "udp"); +++ +++ if (!se) +++ { +++ releaseresolvsem(); +++ return EAI_NONAME; +++ } +++ +++ port = se->s_port; +++ +++ if (strcmp((char *)se->s_proto, "tcp") == 0) +++ proto = SOCK_STREAM; +++ else if (strcmp((char *)se->s_proto, "udp") == 0) +++ proto = SOCK_DGRAM; +++ else +++ { +++ releaseresolvsem(); +++ return EAI_NONAME; +++ } +++ +++ if (hints && hints->ai_socktype && hints->ai_socktype != proto) +++ { +++ releaseresolvsem(); +++ return EAI_SERVICE; +++ } +++ } +++ +++ if (!hints || !(hints->ai_flags & AI_PASSIVE)) +++ { +++ unsigned long nip = inet_addr((char *)nodename); +++ +++ if (nip != (unsigned long)INADDR_NONE) +++ { +++ struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); +++ struct sockaddr_in *sin; +++ +++ if (!ai) +++ { +++ releaseresolvsem(); +++ return EAI_MEMORY; +++ } +++ *tail = ai; +++ +++ sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); +++ +++ if (!sin) +++ { +++ free(ai); +++ releaseresolvsem(); +++ return EAI_MEMORY; +++ } +++ +++ ai->ai_family = AF_INET; +++ ai->ai_socktype = proto; +++ ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; +++ ai->ai_addrlen = sizeof(*sin); +++ ai->ai_addr = (struct sockaddr *)sin; +++ sin->sin_family = AF_INET; +++ sin->sin_port = port; +++ sin->sin_addr.s_addr = nip; +++ +++ releaseresolvsem(); +++ return 0; +++ } +++ +++ hent = gethostbyname((char *)nodename); +++ +++ if (!hent) +++ { +++ int herr = errno; +++ releaseresolvsem(); +++ return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL +++ : EAI_NONAME; +++ } +++ +++ if (!hent->h_addr_list || !hent->h_addr_list[0]) +++ { +++ releaseresolvsem(); +++ return EAI_NONAME; +++ } +++ +++ addrp = hent->h_addr_list; +++ } +++ else +++ { +++ addrp = passive_list; +++ } +++ +++ for (; *addrp; addrp++) +++ { +++ struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); +++ struct sockaddr_in *sin; +++ +++ if (!ai) +++ { +++ releaseresolvsem(); +++ freeaddrinfo(*res); +++ *res = NULL; +++ return EAI_MEMORY; +++ } +++ +++ if (!*res) +++ *res = ai; +++ *tail = ai; +++ tail = &ai->ai_next; +++ +++ sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); +++ +++ if (!sin) +++ { +++ releaseresolvsem(); +++ freeaddrinfo(*res); +++ *res = NULL; +++ return EAI_MEMORY; +++ } +++ +++ ai->ai_family = AF_INET; +++ ai->ai_socktype = proto; +++ ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; +++ ai->ai_addrlen = sizeof(*sin); +++ ai->ai_addr = (struct sockaddr *)sin; +++ sin->sin_family = AF_INET; +++ sin->sin_port = port; +++ +++ if (!hints || !(hints->ai_flags & AI_PASSIVE)) +++ { +++ size_t cpylen = sizeof(sin->sin_addr); +++ +++ if (hent->h_length > 0 && (size_t)hent->h_length < cpylen) +++ cpylen = (size_t)hent->h_length; +++ +++ memcpy(&sin->sin_addr, *addrp, cpylen); +++ } +++ } +++ +++ releaseresolvsem(); +++ return 0; +++} +++ +++void freeaddrinfo(struct addrinfo *ai) +++{ +++ struct addrinfo *next; +++ +++ while (ai) +++ { +++ free(ai->ai_addr); +++ next = ai->ai_next; +++ free(ai); +++ ai = next; +++ } +++} +++ +++static const char *ai_errlist[] = +++ { +++ "Success", +++ "hostname nor servname provided, or not known", +++ "Temporary failure in name resolution", +++ "Non-recoverable failure in name resolution", +++ "No address associated with hostname", +++ "ai_family not supported", +++ "ai_socktype not supported", +++ "service name not supported for ai_socktype", +++ "Address family for hostname not supported", +++ "Memory allocation failure", +++ "System error returned in errno", +++ "Unknown error", +++}; +++ +++char *gai_strerror(int ecode) +++{ +++ if (ecode > 0 || ecode < EAI_UNKNOWN) +++ ecode = EAI_UNKNOWN; +++ return (char *)ai_errlist[-ecode]; +++} +++ +++#endif /* !HAVE_GETADDRINFO */ +++ +++#ifndef HAVE_GETNAMEINFO +++ +++#ifndef NI_DATAGRAM +++#define NI_DATAGRAM (1 << 4) +++#endif +++ +++int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) +++{ +++ const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; +++ +++ (void)salen; +++ +++ if (sa->sa_family != AF_INET) +++ return EAI_ADDRFAMILY; +++ +++ if (host && hostlen > 0) +++ { +++ if (!(flags & NI_NUMERICHOST)) +++ { +++ struct hostent *he; +++ +++ lockresolvsem(); +++ he = gethostbyaddr((char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET); +++ +++ if (he) +++ { +++ safe_strncpy(host, (char *)he->h_name, hostlen); +++ releaseresolvsem(); +++ } +++ else +++ { +++ int herr = errno; +++ releaseresolvsem(); +++ if (flags & NI_NAMEREQD) +++ return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL +++ : EAI_NONAME; +++ flags |= NI_NUMERICHOST; +++ } +++ } +++ +++ if (flags & NI_NUMERICHOST) +++ { +++ lockhostsem(); +++ safe_strncpy(host, (char *)Inet_NtoA(sin->sin_addr.s_addr), hostlen); +++ releasehostsem(); +++ } +++ } +++ +++ if (serv && servlen > 0) +++ { +++ if (!(flags & NI_NUMERICSERV)) +++ { +++ struct servent *se; +++ +++ lockresolvsem(); +++ +++ se = (flags & NI_DATAGRAM) ? getservbyport(ntohs(sin->sin_port), "udp") : getservbyport(ntohs(sin->sin_port), "tcp"); +++ +++ if (se) +++ { +++ safe_strncpy(serv, (char *)se->s_name, servlen); +++ releaseresolvsem(); +++ } +++ else +++ { +++ releaseresolvsem(); +++ +++ if (flags & NI_NAMEREQD) +++ return EAI_NONAME; +++ +++ flags |= NI_NUMERICSERV; +++ } +++ } +++ +++ if (flags & NI_NUMERICSERV) +++ snprintf(serv, servlen, "%u", ntohs(sin->sin_port)); +++ } +++ +++ return 0; +++} +++ +++#endif /* !HAVE_GETNAMEINFO */ +++#endif /* AMIGA */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/sem.c binkd/amiga/sem.c ++--- binkd_pgul/amiga/sem.c 2026-04-16 17:49:00.000000000 +0100 +++++ binkd/amiga/sem.c 2026-04-25 19:33:46.416919700 +0100 ++@@ -1,29 +1,100 @@ ++ /* ++ * Amiga semaphores ++ */ +++ ++ #include ++ #include ++-#include +++#include +++#include +++ +++extern void Log(int lev, char *s, ...); +++ +++int _InitSem(void *vpSem) +++{ +++ memset(vpSem, 0, sizeof(struct SignalSemaphore)); +++ InitSemaphore((struct SignalSemaphore *)vpSem); +++ return 0; +++} +++ +++int _CleanSem(void *vpSem) +++{ +++ return 0; +++} +++ +++int _LockSem(void *vpSem) +++{ +++ ObtainSemaphore((struct SignalSemaphore *)vpSem); +++ return 0; +++} +++ +++int _ReleaseSem(void *vpSem) +++{ +++ ReleaseSemaphore((struct SignalSemaphore *)vpSem); +++ return 0; +++} ++ ++-extern void Log (int lev, char *s,...); +++int _InitEventSem(EVENTSEM *sem) +++{ +++ if (!sem) +++ return -1; ++ +++ sem->waiter = NULL; ++ ++-int _InitSem(void *vpSem) { ++- memset(vpSem, 0, sizeof (struct SignalSemaphore)); ++- InitSemaphore ((struct SignalSemaphore*)vpSem); ++- return(0); +++ sem->sigbit = AllocSignal(-1); +++ +++ if (sem->sigbit == (ULONG)-1) +++ return -1; +++ +++ return 0; ++ } ++ ++-int _CleanSem(void *vpSem) { ++- return (0); +++int _CleanEventSem(EVENTSEM *sem) +++{ +++ if (!sem) +++ return -1; +++ +++ if (sem->sigbit != (ULONG)-1) +++ { +++ FreeSignal((LONG)sem->sigbit); +++ sem->sigbit = (ULONG)-1; +++ } +++ +++ sem->waiter = NULL; +++ return 0; ++ } ++ ++-int _LockSem(void *vpSem) { ++- ObtainSemaphore ((struct SignalSemaphore *)vpSem); ++- return (0); +++int _PostSem(EVENTSEM *sem) +++{ +++ if (!sem) +++ return -1; +++ +++ if (sem->waiter && sem->sigbit != (ULONG)-1) +++ { +++ Signal((struct Task *)sem->waiter, 1UL << sem->sigbit); +++ } +++ +++ return 0; ++ } ++ ++-int _ReleaseSem(void *vpSem) { ++- ReleaseSemaphore ((struct SignalSemaphore *)vpSem); ++- return (0); +++int _WaitSem(EVENTSEM *sem, int sec) +++{ +++ ULONG mask; +++ struct Task *me; +++ +++ if (!sem || sem->sigbit == (ULONG)-1) +++ return -1; +++ +++ me = FindTask(NULL); +++ sem->waiter = me; +++ +++ /* Wait on SIGBREAKF_CTRL_C to avoid hanging on race */ +++ mask = Wait((1UL << sem->sigbit) | SIGBREAKF_CTRL_C); +++ +++ sem->waiter = NULL; +++ +++ /* Return timeout/break indication if only CTRL_C fired */ +++ if (!(mask & (1UL << sem->sigbit)) && (mask & SIGBREAKF_CTRL_C)) +++ return -1; +++ +++ return 0; ++ } ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/session.c binkd/amiga/session.c ++--- binkd_pgul/amiga/session.c 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/session.c 2026-04-26 11:57:30.521108284 +0100 ++@@ -0,0 +1,462 @@ +++/* +++ * session.c -- session management for AmigaOS 3 binkd +++ * +++ * session.c is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#include +++#include +++ +++#include +++#include +++#include +++#include +++ +++#include "sys.h" +++#include "iphdr.h" +++#include "readcfg.h" +++#include "common.h" +++#include "tools.h" +++#include "client.h" +++#include "protocol.h" +++#include "ftnq.h" +++#include "ftnnode.h" +++#include "ftnaddr.h" +++#include "bsy.h" +++#include "iptools.h" +++#include "rfc2553.h" +++#include "srv_gai.h" +++#include "amiga/bsdsock.h" +++#include "amiga/evloop_int.h" +++#include "amiga/proto_amiga.h" +++ +++extern int binkd_exit; +++extern int ext_rand; +++extern int client_flag; +++extern int poll_flag; +++ +++/* Session table */ +++int sess_alloc(void) +++{ +++ int i; +++ +++ for (i = 0; i < max_sessions; i++) +++ { +++ if (sessions[i].phase == SESS_FREE) +++ { +++ memset(&sessions[i], 0, sizeof(sess_t)); +++ sessions[i].fd = INVALID_SOCKET; +++ sessions[i].phase = SESS_FREE; +++ return i; +++ } +++ } +++ +++ return -1; +++} +++ +++void sess_free(int idx) +++{ +++ sess_t *s = &sessions[idx]; +++ +++ if (s->fd != INVALID_SOCKET) +++ { +++ soclose(s->fd); +++ s->fd = INVALID_SOCKET; +++ } +++ +++ if (s->ai_head) +++ { +++ freeaddrinfo(s->ai_head); +++ s->ai_head = NULL; +++ } +++ +++ memset(&s->state, 0, sizeof(STATE)); +++ s->phase = SESS_FREE; +++} +++ +++/* Inbound: accept a new connection */ +++void do_accept(SOCKET lfd, BINKD_CONFIG *config) +++{ +++ struct sockaddr_storage sa; +++ socklen_t salen = (socklen_t)sizeof(sa); +++ SOCKET fd; +++ int idx; +++ sess_t *s; +++ char host[BINKD_FQDNLEN + 1]; +++ char ip[BINKD_FQDNLEN + 1]; +++ +++ fd = accept(lfd, (struct sockaddr *)&sa, &salen); +++ +++ if (fd == INVALID_SOCKET) +++ { +++ if (TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN) +++ Log(1, "accept(): %s", TCPERR()); +++ +++ return; +++ } +++ +++ if (binkd_exit) +++ { +++ soclose(fd); +++ return; +++ } +++ +++ idx = sess_alloc(); +++ +++ if (idx < 0) +++ { +++ Log(1, "session table full, refusing inbound"); +++ soclose(fd); +++ return; +++ } +++ +++ /* getnameinfo() Is unreliable on AmiTCP: use inet_ntoa directly */ +++ if (((struct sockaddr *)&sa)->sa_family == AF_INET) +++ { +++ struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; +++ strnzcpy(ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); +++ } +++ else +++ { +++ strnzcpy(ip, "unknown", BINKD_FQDNLEN); +++ } +++ +++ /* Backresolv not supported on AmiTCP: always use IP as host */ +++ strnzcpy(host, ip, BINKD_FQDNLEN); +++ +++ set_nonblock(fd); +++ +++ s = &sessions[idx]; +++ s->fd = fd; +++ s->inbound = 1; +++ s->node = NULL; +++ s->ai_head = NULL; +++ s->last_io = time(NULL); +++ strnzcpy(s->host, host, BINKD_FQDNLEN); +++ strnzcpy(s->ip, ip, BINKD_FQDNLEN); +++ s->port[0] = '\0'; +++ +++ if (amiga_proto_open(&s->state, fd, NULL, NULL, s->host, NULL, s->ip, config) != 0) +++ { +++ Log(1, "proto_open failed for %s", ip); +++ sess_free(idx); +++ return; +++ } +++ +++ s->phase = SESS_RUNNING; +++ n_servers++; +++ Log(4, "inbound slot[%d] from %s", idx, ip); +++} +++ +++/* Outbound: Non-blocking connect() */ +++int start_connect(sess_t *s, BINKD_CONFIG *config) +++{ +++ SOCKET fd; +++ int rc; +++ +++ s->ip[0] = '\0'; +++ s->port[0] = '\0'; +++ +++ fd = socket(s->ai_cur->ai_family, s->ai_cur->ai_socktype, s->ai_cur->ai_protocol); +++ +++ if (fd == INVALID_SOCKET) +++ { +++ Log(1, "outbound socket(): %s", TCPERR()); +++ return -1; +++ } +++ +++ /* getnameinfo() is unreliable on AmiTCP: may return rc=0 with garbage +++ * Use inet_ntoa/ntohs directly */ +++ if (s->ai_cur->ai_family == AF_INET) +++ { +++ struct sockaddr_in *sa4 = (struct sockaddr_in *)s->ai_cur->ai_addr; +++ strnzcpy(s->ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); +++ snprintf(s->port, MAXPORTSTRLEN, "%u", (unsigned)ntohs(sa4->sin_port)); +++ } +++ else +++ { +++ strnzcpy(s->ip, "unknown", BINKD_FQDNLEN); +++ strnzcpy(s->port, "0", MAXPORTSTRLEN); +++ } +++ +++ Log(4, "connecting %s [%s]:%s", s->host, s->ip, s->port); +++ +++ if (config->bindaddr[0]) +++ { +++ struct addrinfo src_h, *src_ai; +++ memset(&src_h, 0, sizeof(src_h)); +++ src_h.ai_family = s->ai_cur->ai_family; +++ src_h.ai_socktype = SOCK_STREAM; +++ src_h.ai_protocol = IPPROTO_TCP; +++ +++ if (getaddrinfo(config->bindaddr, NULL, &src_h, &src_ai) == 0) +++ { +++ bind(fd, src_ai->ai_addr, (int)src_ai->ai_addrlen); +++ freeaddrinfo(src_ai); +++ } +++ } +++ +++ set_nonblock(fd); +++ +++ rc = connect(fd, s->ai_cur->ai_addr, (int)s->ai_cur->ai_addrlen); +++ +++ if (rc == 0 || TCPERRNO == EINPROGRESS || TCPERRNO == EWOULDBLOCK) +++ { +++ s->fd = fd; +++ s->conn_start = time(NULL); +++ return 0; +++ } +++ +++ Log(1, "connect %s: %s", s->host, TCPERR()); +++ +++ bad_try(&s->node->fa, TCPERR(), BAD_CALL, config); +++ soclose(fd); +++ +++ return -1; +++} +++ +++int try_outbound(BINKD_CONFIG *config) +++{ +++ FTN_NODE *node; +++ sess_t *s; +++ int idx, rc; +++ struct addrinfo hints; +++ char dest[FTN_ADDR_SZ + 1]; +++ char host[BINKD_FQDNLEN + 5 + 1]; +++ char port[MAXPORTSTRLEN + 1]; +++ +++ if (!client_flag) +++ return 0; +++ +++ if (!config->q_present) +++ { +++ q_free(SCAN_LISTED, config); +++ +++ if (config->printq) +++ Log(-1, "scan\r"); +++ +++ q_scan(SCAN_LISTED, config); +++ config->q_present = 1; +++ +++ if (config->printq) +++ { +++ q_list(stderr, SCAN_LISTED, config); +++ Log(-1, "idle\r"); +++ } +++ } +++ +++ if (n_clients >= config->max_clients) +++ return 0; +++ +++ node = q_next_node(config); +++ +++ if (!node) +++ return 0; +++ +++ ftnaddress_to_str(dest, &node->fa); +++ +++ if (!bsy_test(&node->fa, F_BSY, config) || !bsy_test(&node->fa, F_CSY, config)) +++ { +++ Log(4, "%s busy", dest); +++ return 0; +++ } +++ +++ idx = sess_alloc(); +++ +++ if (idx < 0) +++ { +++ Log(2, "table full, deferring %s", dest); +++ return 0; +++ } +++ +++ s = &sessions[idx]; +++ memset(s, 0, sizeof(*s)); +++ s->fd = INVALID_SOCKET; +++ s->node = node; +++ s->inbound = 0; +++ +++ rc = get_host_and_port(1, host, port, node->hosts, &node->fa, config); +++ +++ if (rc <= 0) +++ { +++ Log(1, "%s: bad host list", dest); +++ sess_free(idx); +++ +++ return 0; +++ } +++ +++ strnzcpy(s->host, host, BINKD_FQDNLEN); +++ strnzcpy(s->port, port, MAXPORTSTRLEN); +++ +++ memset(&hints, 0, sizeof(hints)); +++ hints.ai_family = node->IP_afamily; +++ hints.ai_socktype = SOCK_STREAM; +++ hints.ai_protocol = IPPROTO_TCP; +++ +++ rc = srv_getaddrinfo(host, port, &hints, &s->ai_head); +++ +++ if (rc != 0) +++ { +++ Log(1, "%s: getaddrinfo error code=%d: %s", dest, rc, gai_strerror(rc)); +++ +++ bad_try(&node->fa, "getaddrinfo failed", BAD_CALL, config); +++ sess_free(idx); +++ +++ return 0; +++ } +++ +++ s->ai_cur = s->ai_head; +++ +++ if (start_connect(s, config) != 0) +++ { +++ sess_free(idx); +++ return 0; +++ } +++ +++ s->phase = SESS_CONNECTING; +++ n_clients++; +++ +++ Log(4, "outbound slot[%d] -> %s", idx, dest); +++ +++ return 1; +++} +++ +++/* Check completion of async connect() */ +++void check_connect(int idx, BINKD_CONFIG *config) +++{ +++ sess_t *s = &sessions[idx]; +++ int err = 0; +++ socklen_t el = (socklen_t)sizeof(err); +++ int tmo; +++ +++ tmo = config->connect_timeout ? config->connect_timeout : 30; +++ +++ if ((int)(time(NULL) - s->conn_start) >= tmo) +++ { +++ Log(1, "connect timeout -> %s", s->host); +++ +++ bad_try(&s->node->fa, "Timeout", BAD_CALL, config); +++ n_clients--; +++ sess_free(idx); +++ +++ return; +++ } +++ +++ getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &el); +++ +++ if (err) +++ { +++ Log(1, "connect -> %s: %s", s->host, strerror(err)); +++ +++ bad_try(&s->node->fa, strerror(err), BAD_CALL, config); +++ +++ soclose(s->fd); +++ s->fd = INVALID_SOCKET; +++ s->ai_cur = s->ai_cur->ai_next; +++ +++ if (s->ai_cur && start_connect(s, config) == 0) +++ return; /* trying next address */ +++ +++ n_clients--; +++ sess_free(idx); +++ +++ return; +++ } +++ +++ Log(4, "connected -> %s [%s]", s->host, s->ip); +++ ext_rand = rand(); +++ +++ if (amiga_proto_open(&s->state, s->fd, s->node, NULL, s->host, s->port, s->ip, config) != 0) +++ { +++ Log(1, "proto_open failed for %s", s->host); +++ +++ n_clients--; +++ sess_free(idx); +++ return; +++ } +++ +++ s->phase = SESS_RUNNING; +++ s->last_io = time(NULL); +++} +++ +++/* Run one protocol step on an active session */ +++void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config) +++{ +++ sess_t *s = &sessions[idx]; +++ int rc; +++ int tmo; +++ +++ tmo = config->nettimeout ? config->nettimeout : 300; +++ +++ if ((int)(time(NULL) - s->last_io) >= tmo) +++ { +++ Log(1, "slot[%d] net timeout", idx); +++ +++ if (s->node) +++ bad_try(&s->node->fa, "Timeout", BAD_IO, config); +++ +++ amiga_proto_close(&s->state, config, 0); +++ +++ if (s->inbound) +++ n_servers--; +++ else +++ n_clients--; +++ +++ sess_free(idx); +++ +++ return; +++ } +++ +++ if (s->fd == INVALID_SOCKET) +++ { +++ Log(1, "slot[%d] invalid socket, closing session", idx); +++ +++ if (s->node) +++ bad_try(&s->node->fa, "Invalid socket", BAD_IO, config); +++ +++ if (s->inbound) +++ n_servers--; +++ else +++ n_clients--; +++ +++ sess_free(idx); +++ +++ return; +++ } +++ +++ /* WaitSelect() may not report a readable socket when the remote has +++ * sent a TCP END. Probe with MSG_PEEK so recv_block() sees the EOF */ +++ if (!rd && !wr && s->state.state != P_NULL) +++ { +++ char peek; +++ int pr = recv(s->fd, &peek, 1, MSG_PEEK); +++ +++ if (pr == 0 || (pr < 0 && TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN)) +++ rd = 1; +++ } +++ +++ rc = amiga_proto_step(&s->state, rd, wr, config); +++ +++ if (rd || wr) +++ s->last_io = time(NULL); +++ +++ if (rc == APROTO_RUNNING) +++ return; +++ +++ amiga_proto_close(&s->state, config, rc == APROTO_DONE_OK); +++ +++ Log(4, "slot[%d] %s", idx, rc == APROTO_DONE_OK ? "OK" : "ERR"); +++ +++ if (s->inbound) +++ n_servers--; +++ else +++ n_clients--; +++ +++ sess_free(idx); +++ +++ if (poll_flag && n_clients == 0 && n_servers == 0) +++ binkd_exit = 1; +++} ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/sock.c binkd/amiga/sock.c ++--- binkd_pgul/amiga/sock.c 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/sock.c 2026-04-26 11:59:26.645813693 +0100 ++@@ -0,0 +1,130 @@ +++/* +++ * sock.c -- listen socket management for AmigaOS 3 +++ * +++ * sock.c is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#include +++#include +++ +++#include +++#include +++ +++#include "sys.h" +++#include "readcfg.h" +++#include "tools.h" +++#include "server.h" +++#include "rfc2553.h" +++#include "amiga/bsdsock.h" +++#include "amiga/evloop_int.h" +++ +++extern SOCKET sockfd[]; +++extern int sockfd_used; +++extern int server_flag; +++ +++void set_nonblock(SOCKET fd) +++{ +++ long flag = 1L; +++ +++ if (IoctlSocket(fd, FIONBIO, (char *)&flag) != 0) +++ Log(2, "IoctlSocket(FIONBIO) failed: %s", TCPERR()); +++} +++ +++int open_listen_sockets(BINKD_CONFIG *config) +++{ +++ struct listenchain *ll; +++ struct addrinfo hints, *ai, *head; +++ int err, opt = 1; +++ +++ memset(&hints, 0, sizeof(hints)); +++ hints.ai_flags = AI_PASSIVE; +++ hints.ai_family = PF_UNSPEC; +++ hints.ai_socktype = SOCK_STREAM; +++ hints.ai_protocol = IPPROTO_TCP; +++ +++ sockfd_used = 0; +++ +++ for (ll = config->listen.first; ll; ll = ll->next) +++ { +++ err = getaddrinfo(ll->addr[0] ? ll->addr : NULL, ll->port, &hints, &head); +++ +++ if (err) +++ { +++ Log(1, "listen getaddrinfo(%s:%s): %s", ll->addr[0] ? ll->addr : "*", ll->port, gai_strerror(err)); +++ return -1; +++ } +++ +++ for (ai = head; ai && sockfd_used < MAX_LISTENSOCK; ai = ai->ai_next) +++ { +++ SOCKET fd; +++ int retries = 6; +++ +++ fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); +++ +++ if (fd == INVALID_SOCKET) +++ { +++ Log(1, "listen socket(): %s", TCPERR()); +++ continue; +++ } +++ +++ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, (int)sizeof(opt)) != 0) +++ Log(2, "setsockopt(SO_REUSEADDR) failed: %s", TCPERR()); +++ +++ /* Bsdsocket may hold the port briefly after socket close */ +++ while (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) != 0) +++ { +++ if (--retries == 0) +++ { +++ Log(1, "listen bind(): %s", TCPERR()); +++ +++ soclose(fd); +++ freeaddrinfo(head); +++ return -1; +++ } +++ +++ Log(2, "bind retry in 2s: %s", TCPERR()); +++ +++ Delay(100UL); /* 100 ticks = 2s @ 50Hz */ +++ } +++ +++ if (listen(fd, 5) != 0) +++ { +++ Log(1, "listen(): %s", TCPERR()); +++ +++ soclose(fd); +++ freeaddrinfo(head); +++ return -1; +++ } +++ +++ set_nonblock(fd); +++ sockfd[sockfd_used] = fd; +++ sockfd_used++; +++ } +++ +++ freeaddrinfo(head); +++ +++ Log(3, "listening on %s:%s", +++ ll->addr[0] ? ll->addr : "*", ll->port); +++ } +++ +++ if (sockfd_used == 0 && server_flag) +++ { +++ Log(1, "evloop: no listen sockets opened"); +++ return -1; +++ } +++ +++ return 0; +++} +++ +++void close_listen_sockets(void) +++{ +++ int i; +++ +++ for (i = 0; i < sockfd_used; i++) +++ soclose(sockfd[i]); +++ +++ sockfd_used = 0; +++} ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/utime.c binkd/amiga/utime.c ++--- binkd_pgul/amiga/utime.c 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/amiga/utime.c 2026-04-26 11:59:41.408046130 +0100 ++@@ -0,0 +1,77 @@ +++/* +++ * utime.c -- utime() stub for AmigaOS 3 without ixemul +++ * +++ * utime.c is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifdef AMIGA +++ +++#include +++#include +++#include +++#include +++ +++#include "amiga/dirent.h" /* declares struct utimbuf and utime() prototype */ +++ +++/* Days between AmigaDOS epoch (1978-01-01) and POSIX epoch (1970-01-01) */ +++#define AMIGA_EPOCH_DELTA_DAYS 2922UL +++#define SECONDS_PER_DAY 86400UL +++#define SECONDS_PER_MINUTE 60UL +++ +++int utime(const char *path, const struct utimbuf *times) +++{ +++ struct DateStamp ds; +++ LONG seconds_today; +++ LONG total_seconds; +++ +++ if (!path) +++ { +++ errno = EINVAL; +++ return -1; +++ } +++ +++ if (!times) +++ { +++ /* Use current time */ +++ DateStamp(&ds); +++ } +++ else +++ { +++ LONG t = (LONG)times->modtime; +++ +++ if (t < (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY)) +++ { +++ /* Time predates AmigaDOS epoch -- clamp to epoch */ +++ t = 0; +++ } +++ else +++ { +++ t -= (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY); +++ } +++ +++ ds.ds_Days = (LONG)(t / (LONG)SECONDS_PER_DAY); +++ total_seconds = t % (LONG)SECONDS_PER_DAY; +++ ds.ds_Minute = (LONG)(total_seconds / (LONG)SECONDS_PER_MINUTE); +++ seconds_today = total_seconds % (LONG)SECONDS_PER_MINUTE; +++ ds.ds_Tick = seconds_today * (LONG)TICKS_PER_SECOND; +++ } +++ +++ if (!SetFileDate((STRPTR)path, &ds)) +++ { +++ /* Most likely cause: file does not exist or is write-protected */ +++ LONG err = IoErr(); +++ +++ if (err == ERROR_OBJECT_NOT_FOUND || err == ERROR_DIR_NOT_FOUND) +++ errno = ENOENT; +++ else +++ errno = EACCES; +++ return -1; +++ } +++ +++ return 0; +++} +++ +++#endif /* AMIGA */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/binkd.c binkd/binkd.c ++--- binkd_pgul/binkd.c 2026-04-16 17:49:00.000000000 +0100 +++++ binkd/binkd.c 2026-04-26 13:20:44.986212073 +0100 ++@@ -54,6 +54,10 @@ ++ #include "unix/daemonize.h" ++ #endif ++ +++#ifdef AMIGA +++/* amiga/bsdsock.h pulled in via iphdr.h for AMIGA */ +++#include "amiga/evloop.h" +++#endif ++ #ifdef WIN32 ++ #include "nt/service.h" ++ #include "nt/w32tools.h" ++@@ -62,9 +66,11 @@ ++ #endif ++ #endif ++ +++#include "bsycleanup.h" +++ ++ #include "confopt.h" ++ ++-#ifdef HAVE_THREADS +++#if defined(HAVE_THREADS) || defined(AMIGA) ++ MUTEXSEM hostsem; ++ MUTEXSEM resolvsem; ++ MUTEXSEM lsem; ++@@ -94,9 +100,13 @@ ++ char *configpath = NULL; /* Config file name */ ++ char **saved_envp; ++ ++-#ifdef HAVE_FORK +++/* mypid: needed by HAVE_FORK and AMIGA targets */ +++#if defined(HAVE_FORK) || defined(AMIGA) +++int mypid; +++#endif ++ ++-int mypid, got_sighup, got_sigchld; +++#ifdef HAVE_FORK +++int got_sighup, got_sigchld; ++ ++ void chld (int *childcount) ++ { ++@@ -195,9 +205,13 @@ ++ #endif ++ " -C reload on config change\n" ++ " -c run client only\n" +++#ifndef AMIGA ++ " -i run server on stdin/stdout pipe (by inetd or other)\n" +++#endif ++ " -f node run server protected session with this node\n" +++#ifndef AMIGA ++ " -a ip assume remote address when running with '-i' switch\n" +++#endif ++ #if defined(BINKD9X) ++ " -t cmd (start|stop|restart|status|install|uninstall) service(s)\n" ++ " -S name set Win9x service name, all - use all services\n" ++@@ -312,12 +326,14 @@ ++ case 'c': ++ client_flag = 1; ++ break; +++#ifndef AMIGA ++ case 'i': ++ inetd_flag = 1; ++ break; ++ case 'a': /* remote IP address */ ++ remote_addr = strdup(optarg); ++ break; +++#endif ++ case 'f': /* remote FTN address */ ++ remote_node = strdup(optarg); ++ break; ++@@ -416,6 +432,28 @@ ++ } ++ if (optind\n", extract_filename(argv[0])); +++ fprintf(stderr, " Use -P
for polling a specific node (e.g., -P 1:23/456.7)\n"); +++ exit(1); +++ } +++ +++ /* Check for leftover FTN addresses in extra arguments */ +++ while (optind < argc) +++ { +++ char *extra = argv[optind++]; +++ if (strchr(extra, ':') || strchr(extra, '@')) +++ { +++ fprintf(stderr, "%s: Error: Unexpected FTN address '%s' in arguments.\n", extract_filename(argv[0]), extra); +++ fprintf(stderr, " Use -P
before the config file to poll a node.\n"); +++ exit(1); +++ } +++ } +++ ++ #ifdef OS2 ++ if (optindloglevel, current_config->conlog, ++ current_config->logpath, current_config->nolog.first); +++ +++ /* Clean up old .bsy/.csy files at startup */ +++ cleanup_old_bsy(current_config); ++ } ++ else if (verbose_flag) ++ { ++@@ -665,9 +706,13 @@ ++ ++ if (p) ++ { ++- remote_addr = strdup(p); ++- p = strchr(remote_addr, ' '); ++- if (p) *p = '\0'; +++ remote_addr = strdup(p); +++ /* Guard against null pointer dereference if strdup fails */ +++ if (remote_addr) +++ { +++ p = strchr(remote_addr, ' '); +++ if (p) *p = '\0'; +++ } ++ } ++ } ++ /* not using stdin/stdout itself to avoid possible collisions */ ++@@ -677,6 +722,9 @@ ++ inetd_socket_out = dup(fileno(stdout)); ++ #ifdef UNIX ++ tempfd = open("/dev/null", O_RDWR); +++#elif defined(AMIGA) +++ /* NIL: is the native AmigaDOS null device (no ixemul) */ +++ tempfd = open("NIL:", O_RDWR); ++ #else ++ tempfd = open("nul", O_RDWR); ++ #endif ++@@ -707,6 +755,15 @@ ++ signal (SIGHUP, sighandler); ++ #endif ++ +++#ifdef AMIGA +++ /* AmigaOS 3: WaitSelect() loop, no fork/threads */ +++ { +++ BINKD_CONFIG *ev_cfg = lock_current_config(); +++ amiga_evloop_run(ev_cfg, server_flag, client_flag); +++ unlock_config_structure(ev_cfg, 0); +++ return 0; +++ } +++#else ++ if (client_flag && !server_flag) ++ { ++ clientmgr (0); ++@@ -721,7 +778,9 @@ ++ if (client_flag && (pidcmgr = branch (clientmgr, 0, 0)) < 0) ++ { ++ Log (0, "cannot branch out"); +++ exit (1); ++ } +++#endif /* !AMIGA */ ++ ++ if (*current_config->pid_file) ++ { ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/binlog.c binkd/binlog.c ++--- binkd_pgul/binlog.c 2026-04-16 17:49:00.000000000 +0100 +++++ binkd/binlog.c 2026-04-22 06:50:46.000000000 +0100 ++@@ -35,6 +35,10 @@ ++ #include "tools.h" ++ #include "sem.h" ++ +++#if defined(HAVE_THREADS) || defined(AMIGA) +++extern MUTEXSEM blsem; +++#endif +++ ++ /* Write 16-bit integer to file in intel bytes order */ ++ static int fput16(u16 arg, FILE *file) ++ { ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/branch.c binkd/branch.c ++--- binkd_pgul/branch.c 2026-04-16 17:49:00.000000000 +0100 +++++ binkd/branch.c 2026-04-26 09:12:44.314385608 +0100 ++@@ -20,12 +20,6 @@ ++ #include "tools.h" ++ #include "sem.h" ++ ++-#ifdef AMIGA ++-int ix_vfork (void); ++-void vfork_setup_child (void); ++-void ix_vfork_resume (void); ++-#endif ++- ++ #ifdef WITH_PTHREADS ++ typedef struct { ++ void (*F) (void *); ++@@ -132,23 +126,6 @@ ++ #endif ++ #endif ++ ++-#ifdef AMIGA ++- /* this is rather bizzare. this function pretends to be a fork and behaves ++- * like one, but actually it's a kind of a thread. so we'll need semaphores */ ++- ++- if (!(rc = ix_vfork ())) ++- { ++- vfork_setup_child (); ++- ix_vfork_resume (); ++- F (arg); ++- exit (0); ++- } ++- else if (rc < 0) ++- { ++- Log (1, "ix_vfork: %s", strerror (errno)); ++- } ++-#endif ++- ++ #if defined(DOS) || defined(DEBUGCHILD) ++ rc = 0; ++ F (arg); ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/breaksig.c binkd/breaksig.c ++--- binkd_pgul/breaksig.c 2026-04-16 17:49:00.000000000 +0100 +++++ binkd/breaksig.c 2026-04-26 10:25:19.576809578 +0100 ++@@ -46,6 +46,16 @@ ++ { ++ atexit (exitfunc); ++ +++#ifdef AMIGA +++ /* AmigaOS / libnix: signal() maps SIGINT -> SIGBREAKF_CTRL_C +++ * Register exitsig() so that Ctrl+C sets binkd_exit=1 even when +++ * the process is NOT blocked inside WaitSelect() (e.g. during disk +++ * I/O). When inside WaitSelect(), amiga_select_wrap() in bsdsock.h +++ * detects the break and sets binkd_exit=1 directly without going +++ * through this signal handler. */ +++ signal (SIGINT, exitsig); +++ signal (SIGTERM, exitsig); +++#else ++ #ifdef SIGBREAK ++ signal (SIGBREAK, exitsig); ++ #endif ++@@ -58,5 +68,6 @@ ++ #ifdef SIGTERM ++ signal (SIGTERM, exitsig); ++ #endif +++#endif /* AMIGA */ ++ return 1; ++ } ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/bsycleanup.c binkd/bsycleanup.c ++--- binkd_pgul/bsycleanup.c 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/bsycleanup.c 2026-04-26 11:01:23.269049076 +0100 ++@@ -0,0 +1,122 @@ +++/* +++ * bsycleanup.c -- Cleanup functions for .bsy/.csy/.try files +++ * +++ * bsycleanup.c is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#include +++#include +++#include +++#include +++#include +++ +++#include "sys.h" +++#include "readcfg.h" +++#include "ftnq.h" +++#include "ftnnode.h" +++#include "tools.h" +++#include "readdir.h" +++ +++/* +++ * Clean up old .bsy and .csy files at startup +++ * Scans all domain outbounds +++ */ +++ +++/* Helper: scan a single directory for .bsy/.csy files and delete them */ +++static void scan_and_delete_bsy_in_dir(const char *dir, BINKD_CONFIG *config) +++{ +++ DIR *dp; +++ struct dirent *de; +++ char buf[MAXPATHLEN + 1]; +++ +++ if ((dp = opendir(dir)) == 0) +++ { +++ return; +++ } +++ +++ while ((de = readdir(dp)) != 0) +++ { +++ char *s = de->d_name; +++ int len = strlen(s); +++ +++ if (len > 4 && (!STRICMP(s + len - 4, ".bsy") || !STRICMP(s + len - 4, ".csy") || !STRICMP(s + len - 4, ".try"))) +++ { +++ strnzcpy(buf, dir, sizeof(buf)); +++ strnzcat(buf, PATH_SEPARATOR, sizeof(buf)); +++ strnzcat(buf, s, sizeof(buf)); +++ +++ Log(2, "deleting %s", buf); +++ +++ delete(buf); +++ } +++ } +++ +++ closedir(dp); +++} +++ +++void cleanup_old_bsy(BINKD_CONFIG *config) +++{ +++ DIR *dp; +++ char outb_path[MAXPATHLEN + 1], base_path[MAXPATHLEN + 1]; +++ struct dirent *de; +++ FTN_DOMAIN *curr_domain; +++ int len; +++ +++ Log(2, "cleaning up .bsy/.csy/.try files at startup"); +++ +++ /* Scan all domain outbounds */ +++ for (curr_domain = config->pDomains.first; curr_domain; curr_domain = curr_domain->next) +++ { +++ if (curr_domain->alias4 != 0) +++ continue; +++ +++ /* Build base path: path + separator */ +++ strnzcpy(base_path, curr_domain->path, sizeof(base_path)); +++#ifndef AMIGA +++ if (base_path[strlen(base_path) - 1] == ':') +++ strcat(base_path, PATH_SEPARATOR); +++#endif +++ +++#ifdef AMIGADOS_4D_OUTBOUND +++ if (config->aso) +++ { +++ /* ASO mode: direct outbound path */ +++ strnzcpy(outb_path, base_path, sizeof(outb_path)); +++ strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); +++ strnzcat(outb_path, curr_domain->dir, sizeof(outb_path)); +++ Log(7, "cleanup_old_bsy (ASO): scanning domain '%s', path '%s'", curr_domain->name, outb_path); +++ scan_and_delete_bsy_in_dir(outb_path, config); +++ } +++ else +++#endif +++ { +++ /* BSO mode: scan for outbound.xxx directories */ +++ Log(7, "cleanup_old_bsy (BSO): scanning domain '%s', base '%s'", curr_domain->name, base_path); +++ +++ if ((dp = opendir(base_path)) == 0) +++ continue; +++ +++ len = strlen(curr_domain->dir); +++ +++ while ((de = readdir(dp)) != 0) +++ { +++ /* Match outbound or outbound.xxx */ +++ if (!STRNICMP(de->d_name, curr_domain->dir, len) && (de->d_name[len] == 0 || (de->d_name[len] == '.' && isxdigit(de->d_name[len + 1])))) +++ { +++ strnzcpy(outb_path, base_path, sizeof(outb_path)); +++ strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); +++ strnzcat(outb_path, de->d_name, sizeof(outb_path)); +++ +++ Log(7, "cleanup_old_bsy (BSO): scanning outbound dir '%s'", outb_path); +++ +++ scan_and_delete_bsy_in_dir(outb_path, config); +++ } +++ } +++ +++ closedir(dp); +++ } +++ } +++} ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/bsycleanup.h binkd/bsycleanup.h ++--- binkd_pgul/bsycleanup.h 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/bsycleanup.h 2026-04-26 13:11:39.607856201 +0100 ++@@ -0,0 +1,19 @@ +++/* +++ * bsycleanup.h -- Cleanup functions for .bsy/.csy/.try files +++ * +++ * bsycleanup.h is a part of binkd project +++ * +++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++ * Licensed under the GNU GPL v2 or later +++ */ +++ +++#ifndef _BSYCLEANUP_H +++#define _BSYCLEANUP_H +++ +++#include "readcfg.h" +++ +++ +++/* cleanup_old_bsy -- Clean up old .bsy/.csy/.try files at startup */ +++void cleanup_old_bsy(BINKD_CONFIG *config); +++ +++#endif /* _BSYCLEANUP_H */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/btypes.h binkd/btypes.h ++--- binkd_pgul/btypes.h 2026-04-16 17:49:00.000000000 +0100 +++++ binkd/btypes.h 2026-04-25 20:39:58.604145925 +0100 ++@@ -73,6 +73,7 @@ ++ int HC_flag; ++ int restrictIP; ++ int NP_flag; /* no proxy */ +++ int NC_flag; /* no compression */ ++ ++ time_t hold_until; ++ int busy; /* 0=free, 'c'=.csy, other=.bsy */ ++diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/cambios.diff binkd/cambios.diff ++--- binkd_pgul/cambios.diff 1970-01-01 00:00:00.000000000 +0000 +++++ binkd/cambios.diff 2026-04-26 16:03:26.856780412 +0100 ++@@ -0,0 +1,8223 @@ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/bsdsock.c binkd_pgul/amiga/bsdsock.c +++--- binkd/amiga/bsdsock.c 2026-04-26 11:01:10.438650436 +0100 ++++++ binkd_pgul/amiga/bsdsock.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,79 +0,0 @@ +++-/* +++- * bsdsock.c -- bsdsocket.library lifecycle for AmigaOS 3 +++- * +++- * bsdsock.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include +++-#include +++-#include +++-#include +++-#include +++-#include +++- +++-/* Linker-compatibility global. Never used at runtime */ +++-struct Library *SocketBase = NULL; +++- +++-/* Suppress conflicting C prototypes from clib/bsdsocket_protos.h */ +++-#ifndef CLIB_BSDSOCKET_PROTOS_H +++-#define CLIB_BSDSOCKET_PROTOS_H +++-#endif +++- +++-#include +++-#include +++- +++-extern void Log(int lev, const char *s, ...); +++- +++-/* _amiga_get_socket_base -- returns bsdsocket.library handle for calling task */ +++-struct Library *_amiga_get_socket_base(void) +++-{ +++- return (struct Library *)FindTask(NULL)->tc_UserData; +++-} +++- +++-int amiga_sock_init(void) +++-{ +++- struct Task *me = FindTask(NULL); +++- struct Library *base; +++- +++- if (me->tc_UserData) +++- return 0; /* already open for this task */ +++- +++- base = OpenLibrary("bsdsocket.library", 0UL); +++- +++- if (!base) +++- { +++- fprintf(stderr, "amiga_sock_init: cannot open bsdsocket.library\n"); +++- return -1; +++- } +++- +++- /* Store in tc_UserData and global SocketBase */ +++- me->tc_UserData = (APTR)base; +++- SocketBase = base; +++- +++- /* Link the per-task errno to the TCP stack. */ +++- SetErrnoPtr(&errno, (LONG)sizeof(errno)); +++- +++- return 0; +++-} +++- +++-void amiga_sock_cleanup(void) +++-{ +++- struct Task *me = FindTask(NULL); +++- struct Library *base = (struct Library *)me->tc_UserData; +++- +++- if (base) +++- { +++- me->tc_UserData = NULL; +++- SocketBase = NULL; +++- CloseLibrary(base); +++- } +++-} +++- +++-int amiga_child_sock_init(void) +++-{ +++- /* Child inherits tc_UserData = NULL, opens new handle */ +++- return amiga_sock_init(); +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/bsdsock.h binkd_pgul/amiga/bsdsock.h +++--- binkd/amiga/bsdsock.h 2026-04-26 10:49:27.706200054 +0100 ++++++ binkd_pgul/amiga/bsdsock.h 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,155 +0,0 @@ +++-/* +++- * bsdsock.h -- bsdsocket.library init and POSIX compat shims for AmigaOS 3 +++- * +++- * bsdsock.h is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifndef _AMIGA_BSDSOCK_H +++-#define _AMIGA_BSDSOCK_H +++- +++-#ifdef AMIGA +++- +++-#include +++-#include +++-#include +++-#include +++- +++-/* Suppress conflicting C prototypes from roadshow */ +++-#ifndef CLIB_BSDSOCKET_PROTOS_H +++-#define CLIB_BSDSOCKET_PROTOS_H +++-#endif +++- +++-/* Undefine MCLBYTES/MCLSHIFT before roadshow headers */ +++-#ifdef MCLBYTES +++-#undef MCLBYTES +++-#endif +++-#ifdef MCLSHIFT +++-#undef MCLSHIFT +++-#endif +++- +++-/* Roadshow SDK network headers */ +++-#include +++-#include +++-#include "compat_netinet_in.h" +++-#include +++-#include +++-#include /* inline/bsdsocket.h, no clib protos */ +++- +++-/* Undefine conflicting unistd.h macros */ +++-#ifdef gethostid +++-#undef gethostid +++-#endif +++-#ifdef getdtablesize +++-#undef getdtablesize +++-#endif +++-#ifdef gethostname +++-#undef gethostname +++-#endif +++- +++-/* Per-task SocketBase override */ +++-struct Library *_amiga_get_socket_base(void); +++- +++-#ifdef SocketBase +++-#undef SocketBase +++-#endif +++-#define SocketBase _amiga_get_socket_base() +++- +++-/* Roadshow socket-specific errno values */ +++-#include +++- +++-#define BSDSOCK_HAS_TIMEVAL 1 +++- +++-/* Socket-specific errno values from roadshow sys/errno.h */ +++-#ifndef ENOTSOCK +++-#define ENOTSOCK 38 /* Socket operation on non-socket */ +++-#endif +++-#ifndef EOPNOTSUPP +++-#define EOPNOTSUPP 45 /* Operation not supported on socket */ +++-#endif +++-#ifndef ECONNREFUSED +++-#define ECONNREFUSED 61 /* Connection refused */ +++-#endif +++-#ifndef ETIMEDOUT +++-#define ETIMEDOUT 60 /* Connection timed out */ +++-#endif +++-#ifndef ECONNRESET +++-#define ECONNRESET 54 /* Connection reset by peer */ +++-#endif +++-#ifndef EHOSTUNREACH +++-#define EHOSTUNREACH 65 /* No route to host */ +++-#endif +++- +++-#include +++- +++-/* sockaddr_storage fallback for roadshow */ +++-#ifndef HAVE_SOCKADDR_STORAGE +++-#ifndef sockaddr_storage +++-struct sockaddr_storage +++-{ +++- unsigned short ss_family; +++- char __ss_pad[22]; /* enough for IPv4 */ +++-}; +++-#endif +++-#define HAVE_SOCKADDR_STORAGE 1 +++-#endif +++- +++-/* Library base functions */ +++-int amiga_sock_init(void); +++-void amiga_sock_cleanup(void); +++-int amiga_child_sock_init(void); +++- +++-/* getpid() is defined in sys.h for AMIGA */ +++-/* amiga_sleep and sleep are defined in sys.h for AMIGA */ +++- +++-/* select() -> WaitSelect() wrapper with Ctrl+C handling */ +++-#ifndef AMIGA_SELECT_DEFINED +++-#define AMIGA_SELECT_DEFINED +++- +++-/* Forward-declare binkd_exit */ +++-extern int binkd_exit; +++- +++-/* Forward-declare Log to avoid implicit declaration warning */ +++-extern void Log(int lev, char *s, ...); +++- +++-static int amiga_select_wrap(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +++-{ +++- ULONG sigmask = SIGBREAKF_CTRL_C; +++- int rc = WaitSelect(nfds, readfds, writefds, exceptfds, timeout, &sigmask); +++- +++- /* Ctrl+C should break blocked select() loops immediately. */ +++- if ((sigmask & SIGBREAKF_CTRL_C) != 0) +++- { +++- Log(1, "Ctrl+C detected in WaitSelect, setting binkd_exit=1"); +++- binkd_exit = 1; +++- errno = EINTR; +++- return -1; +++- } +++- +++- return rc; +++-} +++- +++-#define select(n, r, w, e, t) amiga_select_wrap((n), (r), (w), (e), (t)) +++- +++-#endif /* AMIGA_SELECT_DEFINED */ +++- +++-/* FIONBIO via IoctlSocket */ +++-#ifndef FIONBIO +++-#define FIONBIO 0x8004667E +++-#endif +++-#ifndef ioctl +++-#define ioctl(s, req, arg) IoctlSocket((s), (req), (char *)(arg)) +++-#endif +++- +++-/* inet_ntoa -> Inet_NtoA (Amiga only) */ +++-#ifdef AMIGA +++-#ifdef inet_ntoa +++-#undef inet_ntoa +++-#endif +++-#define inet_ntoa(a) Inet_NtoA(a) +++-#endif +++- +++-#endif /* AMIGA */ +++-#endif /* _AMIGA_BSDSOCK_H */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/compat_netinet_in.h binkd_pgul/amiga/compat_netinet_in.h +++--- binkd/amiga/compat_netinet_in.h 2026-04-26 09:53:12.337417283 +0100 ++++++ binkd_pgul/amiga/compat_netinet_in.h 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,19 +0,0 @@ +++-/* +++- * compat_netinet_in.h -- Wrapper for netinet/in.h typedef clash +++- * +++- * compat_netinet_in.h is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifndef _AMIGA_COMPAT_NETINET_IN_H +++-#define _AMIGA_COMPAT_NETINET_IN_H +++- +++-#ifdef __GNUC__ +++-#include_next +++-#else +++-#include +++-#endif +++- +++-#endif /* _AMIGA_COMPAT_NETINET_IN_H */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/dirent.c binkd_pgul/amiga/dirent.c +++--- binkd/amiga/dirent.c 2026-04-26 11:40:07.539429919 +0100 ++++++ binkd_pgul/amiga/dirent.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,137 +0,0 @@ +++-/* +++- * dirent.c -- POSIX directory scanning for AmigaOS 3 +++- * +++- * dirent.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifdef AMIGA +++- +++-#include +++-#include +++-#include +++-#include +++-#include +++-#include +++- +++-#include "amiga/dirent.h" +++- +++-/* opendir -- locks directory and allocates state for readdir() */ +++-DIR *opendir(const char *path) +++-{ +++- DIR *dir; +++- +++- if (!path) +++- { +++- errno = EINVAL; +++- return NULL; +++- } +++- +++- dir = (DIR *)AllocMem((LONG)sizeof(DIR), MEMF_CLEAR); +++- +++- if (!dir) +++- { +++- errno = ENOMEM; +++- return NULL; +++- } +++- +++- dir->fib = (struct FileInfoBlock *)AllocMem((LONG)sizeof(struct FileInfoBlock), MEMF_CLEAR); +++- +++- if (!dir->fib) +++- { +++- FreeMem(dir, (LONG)sizeof(DIR)); +++- errno = ENOMEM; +++- return NULL; +++- } +++- +++- dir->lock = Lock((STRPTR)path, ACCESS_READ); +++- +++- if (!dir->lock) +++- { +++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); +++- FreeMem(dir, (LONG)sizeof(DIR)); +++- errno = ENOENT; +++- return NULL; +++- } +++- +++- /* Examine the directory itself; this positions FIB for ExNext() */ +++- if (!Examine(dir->lock, dir->fib)) +++- { +++- UnLock(dir->lock); +++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); +++- FreeMem(dir, (LONG)sizeof(DIR)); +++- errno = EACCES; +++- return NULL; +++- } +++- +++- /* fib_DirEntryType > 0 means this IS a directory */ +++- if (dir->fib->fib_DirEntryType <= 0) +++- { +++- UnLock(dir->lock); +++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); +++- FreeMem(dir, (LONG)sizeof(DIR)); +++- errno = ENOTDIR; +++- return NULL; +++- } +++- +++- dir->first = 1; /* ExNext() has not been called yet */ +++- +++- return dir; +++-} +++- +++-/* readdir -- advances to next directory entry */ +++-struct dirent *readdir(DIR *dir) +++-{ +++- LONG dos_rc; +++- LONG dos_err; +++- +++- if (!dir) +++- { +++- errno = EINVAL; +++- return NULL; +++- } +++- +++- /* ExNext() advances past the last entry returned by Examine/ExNext */ +++- dos_rc = ExNext(dir->lock, dir->fib); +++- +++- if (!dos_rc) +++- { +++- dos_err = IoErr(); +++- +++- if (dos_err == ERROR_NO_MORE_ENTRIES) +++- return NULL; +++- +++- errno = EIO; +++- return NULL; +++- } +++- +++- /* Copy name into the entry buffer */ +++- strncpy(dir->entry.d_name, dir->fib->fib_FileName, AMIGA_NAME_MAX - 1); +++- dir->entry.d_name[AMIGA_NAME_MAX - 1] = '\0'; +++- dir->entry.d_ino = 0; +++- +++- return &dir->entry; +++-} +++- +++-/* closedir -- releases all resources associated with dir */ +++-int closedir(DIR *dir) +++-{ +++- if (!dir) +++- { +++- errno = EINVAL; +++- return -1; +++- } +++- +++- if (dir->lock) +++- UnLock(dir->lock); +++- +++- if (dir->fib) +++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); +++- +++- FreeMem(dir, (LONG)sizeof(DIR)); +++- return 0; +++-} +++- +++-#endif /* AMIGA */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/dirent.h binkd_pgul/amiga/dirent.h +++--- binkd/amiga/dirent.h 2026-04-26 09:53:13.628932082 +0100 ++++++ binkd_pgul/amiga/dirent.h 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,53 +0,0 @@ +++-/* +++- * dirent.h -- POSIX directory scanning for AmigaOS 3 +++- * +++- * dirent.h is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifndef _AMIGA_DIRENT_H +++-#define _AMIGA_DIRENT_H +++- +++-#ifdef AMIGA +++- +++-#include +++-#include +++- +++-/* Maximum name length: AmigaDOS allows 107 characters */ +++-#define AMIGA_NAME_MAX 108 +++- +++-struct dirent +++-{ +++- unsigned long d_ino; /* inode -- always 0 on AmigaDOS */ +++- char d_name[AMIGA_NAME_MAX]; /* null-terminated file name */ +++-}; +++- +++-/* struct utimbuf for AmigaOS 3 without ixemul */ +++-#ifndef _AMIGA_UTIMBUF_DEFINED +++-#define _AMIGA_UTIMBUF_DEFINED +++- +++-struct utimbuf +++-{ +++- long actime; /* access time (unused by SetFileDate) */ +++- long modtime; /* modification time (POSIX time_t) */ +++-}; +++- +++-int utime(const char *path, const struct utimbuf *times); +++-#endif +++- +++-typedef struct _amiga_dir +++-{ +++- BPTR lock; /* directory lock */ +++- struct FileInfoBlock *fib; /* reusable FileInfoBlock */ +++- int first; /* flag: first call not yet */ +++- struct dirent entry; /* storage returned to caller */ +++-} DIR; +++- +++-DIR *opendir(const char *path); +++-struct dirent *readdir(DIR *dir); +++-int closedir(DIR *dir); +++- +++-#endif /* AMIGA */ +++-#endif /* _AMIGA_DIRENT_H */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop.c binkd_pgul/amiga/evloop.c +++--- binkd/amiga/evloop.c 2026-04-26 11:46:47.344533199 +0100 ++++++ binkd_pgul/amiga/evloop.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,509 +0,0 @@ +++-/* +++- * evloop.c -- non-blocking event loop for AmigaOS 3 +++- * +++- * evloop.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-/* Suppress clib bsdsocket prototypes before any socket header */ +++-#ifndef CLIB_BSDSOCKET_PROTOS_H +++-#define CLIB_BSDSOCKET_PROTOS_H +++-#endif +++- +++-#include +++-#include +++-#include +++- +++-#include +++-#include +++-#include +++- +++-#include "sys.h" +++-#include "readcfg.h" +++-#include "common.h" +++-#include "tools.h" +++-#include "protocol.h" +++-#include "sem.h" +++-#include "server.h" +++-#include "amiga/bsdsock.h" +++-#include "amiga/evloop.h" +++-#include "amiga/evloop_int.h" +++-#include "amiga/proto_amiga.h" +++- +++-/* Externals */ +++-extern SOCKET sockfd[MAX_LISTENSOCK]; +++-extern int sockfd_used; +++-extern int binkd_exit; +++-extern int server_flag, client_flag; +++- +++-/* Session table (shared with sock.c and session.c) */ +++-sess_t *sessions = NULL; +++-int max_sessions = 0; +++- +++-/* +++- * calc_max_sessions -- Compute session slot count from config + flags +++- * Shared by init and config-reload paths +++- */ +++-static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) +++-{ +++- int servers = config->max_servers; +++- int clients = config->max_clients; +++- int total; +++- +++- if (servers == 0 && clients == 0) +++- { +++- Log(5, "DEBUG: Using default 2 slots (no config found)"); +++- return 2; +++- } +++- +++- Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); +++- +++- if (srv_flag && servers < 1) +++- servers = 1; +++- +++- if (cli_flag && clients < 1) +++- clients = 1; +++- +++- total = servers + clients; +++- +++- if (total < 2) +++- total = 2; +++- +++- Log(5, "DEBUG: Calculated max_sessions=%d", total); +++- +++- return total; +++-} +++- +++-/* init_session_table -- Allocate and zero-initialise the session array */ +++-static int init_session_table(int slots) +++-{ +++- int i; +++- +++- sessions = calloc(slots, sizeof(sess_t)); +++- +++- if (!sessions) +++- { +++- Log(1, "Failed to allocate session table"); +++- return 0; +++- } +++- +++- for (i = 0; i < slots; i++) +++- { +++- memset(&sessions[i], 0, sizeof(sess_t)); +++- memset(&sessions[i].state, 0, sizeof(STATE)); +++- sessions[i].fd = INVALID_SOCKET; +++- sessions[i].phase = SESS_FREE; +++- } +++- +++- return 1; +++-} +++- +++-/* +++- * build_fdsets -- Populate r/w fd_sets from listen sockets and sessions +++- * Returns the highest fd seen (maxfd) +++- */ +++-static int build_fdsets(fd_set *r, fd_set *w) +++-{ +++- int i, maxfd = 0; +++- +++- FD_ZERO(r); +++- FD_ZERO(w); +++- +++- /* server side: listen sockets */ +++- for (i = 0; i < sockfd_used; i++) +++- { +++- if (sockfd[i] != INVALID_SOCKET) +++- { +++- FD_SET(sockfd[i], r); +++- +++- if ((int)sockfd[i] > maxfd) +++- maxfd = (int)sockfd[i]; +++- } +++- } +++- +++- /* client + server sessions */ +++- for (i = 0; i < max_sessions; i++) +++- { +++- sess_t *s = &sessions[i]; +++- +++- if (s->phase == SESS_FREE || s->fd == INVALID_SOCKET) +++- continue; +++- +++- if ((int)s->fd > maxfd) +++- maxfd = (int)s->fd; +++- +++- if (s->phase == SESS_CONNECTING) +++- { +++- /* client: waiting for non-blocking connect() */ +++- FD_SET(s->fd, w); +++- } +++- else +++- { +++- /* Server or established client session */ +++- FD_SET(s->fd, r); +++- +++- if (s->state.msgs || s->state.oleft || s->state.send_eof || (s->state.out.f && !s->state.off_req_sent && !s->state.waiting_for_GOT)) +++- FD_SET(s->fd, w); +++- } +++- } +++- +++- Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); +++- return maxfd; +++-} +++- +++-/* +++- * handle_server_accept -- Accept new inbound connections on all listen fds +++- * Returns 0 normally, -1 if binkd_exit was set during accept +++- */ +++-static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) +++-{ +++- int i; +++- +++- Log(5, "DEBUG: Before accept loop"); +++- +++- for (i = 0; i < sockfd_used; i++) +++- { +++- if (FD_ISSET(sockfd[i], r)) +++- do_accept(sockfd[i], config); +++- +++- if (binkd_exit) +++- { +++- Log(5, "DEBUG: binkd_exit during accept loop"); +++- return -1; +++- } +++- } +++- +++- Log(5, "DEBUG: After accept loop"); +++- +++- return 0; +++-} +++- +++-/* +++- * advance_sessions -- Step every active session (server + client) +++- * Returns the number of non-free sessions processed +++- */ +++-static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) +++-{ +++- int i, active = 0; +++- +++- Log(5, "DEBUG: Before advance sessions"); +++- +++- for (i = 0; i < max_sessions; i++) +++- { +++- sess_t *s = &sessions[i]; +++- +++- Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); +++- +++- if (s->phase == SESS_FREE) +++- continue; +++- +++- active++; +++- +++- if (s->phase == SESS_CONNECTING) +++- { +++- /* client: Complete the non-blocking connect */ +++- if (FD_ISSET(s->fd, w)) +++- check_connect(i, config); +++- } +++- else +++- { +++- int rd = FD_ISSET(s->fd, r); +++- int wr = FD_ISSET(s->fd, w); +++- +++- /* Always step: protocol must advance internal state even +++- * when WaitSelect reports no activity (e.g. second batch +++- * EOB send, TCP FIN detection after remote closes) */ +++- do_session_step(i, rd, wr, config); +++- } +++- +++- if (binkd_exit) +++- break; +++- } +++- +++- return active; +++-} +++- +++-/* +++- * handle_config_reload -- Resize session table and reopen listen sockets +++- * Returns 1 if the caller should break out of the main loop, 0 otherwise +++- */ +++-static int handle_config_reload(BINKD_CONFIG **config, int srv_flag, int cli_flag) +++-{ +++- int i, new_max; +++- BINKD_CONFIG *nc = lock_current_config(); +++- +++- if (nc) +++- { +++- new_max = calc_max_sessions(nc, srv_flag, cli_flag); +++- +++- if (new_max != max_sessions) +++- { +++- sess_t *ns = realloc(sessions, new_max * sizeof(sess_t)); +++- +++- if (ns) +++- { +++- for (i = max_sessions; i < new_max; i++) +++- { +++- memset(&ns[i], 0, sizeof(sess_t)); +++- memset(&ns[i].state, 0, sizeof(STATE)); +++- ns[i].fd = INVALID_SOCKET; +++- ns[i].phase = SESS_FREE; +++- } +++- +++- sessions = ns; +++- max_sessions = new_max; +++- +++- Log(4, "Session table resized to %d slots", max_sessions); +++- } +++- else +++- { +++- Log(1, "Failed to resize session table, keeping current size"); +++- } +++- } +++- +++- unlock_config_structure(nc, 0); +++- } +++- +++- close_listen_sockets(); +++- *config = lock_current_config(); +++- +++- if (srv_flag && open_listen_sockets(*config) < 0) +++- { +++- unlock_config_structure(*config, 0); +++- return 1; /* fatal — break main loop */ +++- } +++- +++- unlock_config_structure(*config, 0); +++- *config = lock_current_config(); +++- return 0; +++-} +++- +++-/* evloop_cleanup -- Close sessions and free resources on exit */ +++-static void evloop_cleanup(BINKD_CONFIG *config, int config_locked) +++-{ +++- int i; +++- +++- if (config_locked) +++- unlock_config_structure(config, 0); +++- +++- if (sessions) +++- { +++- for (i = 0; i < max_sessions; i++) +++- { +++- if (sessions[i].phase == SESS_RUNNING) +++- { +++- amiga_proto_close(&sessions[i].state, config, 0); +++- +++- if (sessions[i].inbound) +++- n_servers--; +++- else +++- n_clients--; +++- } +++- else if (sessions[i].phase == SESS_CONNECTING) +++- { +++- n_clients--; +++- } +++- sess_free(i); +++- } +++- +++- free(sessions); +++- sessions = NULL; +++- } +++- +++- close_listen_sockets(); +++- amiga_sock_cleanup(); +++- Log(4, "evloop done"); +++-} +++- +++-/* amiga_evloop_run -- Entry point: init, then main WaitSelect() loop */ +++-void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) +++-{ +++- int config_locked = 0; +++- time_t last_rescan = 0; +++- time_t now; +++- fd_set r, w; +++- struct timeval tv; +++- int n, maxfd; +++- int active_sessions = 0; +++- static int idle_loops = 0; +++- +++- /* Sync globals so try_outbound() and friends see the correct flags */ +++- server_flag = srv_flag; +++- client_flag = cli_flag; +++- +++- sockfd_used = 0; +++- srand((unsigned int)time(NULL)); +++- +++- Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); +++- Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); +++- +++- /* Initialise session table */ +++- max_sessions = calc_max_sessions(config, server_flag, client_flag); +++- +++- if (max_sessions < 2) +++- { +++- Log(2, "WARNING: max_sessions=%d is too low, forcing to 2", max_sessions); +++- max_sessions = 2; +++- } +++- +++- Log(4, "evloop start (AmigaOS 3, WaitSelect, %d slots)", max_sessions); +++- +++- if (!init_session_table(max_sessions)) +++- return; +++- +++- /* server: Open listen sockets */ +++- if (server_flag && open_listen_sockets(config) < 0) +++- { +++- Log(0, "evloop: cannot open listen sockets"); +++- free(sessions); +++- sessions = NULL; +++- return; +++- } +++- +++- Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); +++- +++- /* Initial outbound attempt before waiting (important for poll -p mode) */ +++- Log(5, "DEBUG: Initial try_outbound before main loop"); +++- try_outbound(config); +++- last_rescan = time(NULL); /* Reset timer since we just did an attempt */ +++- +++- /* ===== Main loop ===== */ +++- for (;;) +++- { +++- if (binkd_exit) +++- { +++- Log(1, "binkd_exit detected at loop start, exiting"); +++- break; +++- } +++- +++- /* Build fd_sets */ +++- Log(5, "DEBUG: Building fd_sets"); +++- maxfd = build_fdsets(&r, &w); +++- +++- tv.tv_sec = 1; +++- tv.tv_usec = 0L; +++- +++- /* WaitSelect() with nfds>0 but empty fd_sets causes guru #80000006 :/ +++- * Use select(0,...) as a pure sleep when no sockets are active */ +++- if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) +++- maxfd = 1; +++- +++- Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); +++- +++- if (maxfd == 0) +++- { +++- /* No sockets yet -- use Delay() instead of select(0,...) +++- * as WaitSelect with nfds=0 can block indefinitely on AmigaOS */ +++- Delay(50); /* 1 second = 50 ticks at 50Hz PAL */ +++- n = 0; /* simulate timeout */ +++- } +++- else +++- n = select(maxfd + 1, &r, &w, NULL, &tv); +++- +++- Log(5, "DEBUG: select() returned n=%d", n); +++- +++- if (binkd_exit) +++- { +++- Log(1, "binkd_exit detected after select(), exiting"); +++- break; +++- } +++- +++- Delay(1UL); /* 1 tick = 20ms @ 50Hz, prevents CPU hogging */ +++- +++- /* Handle select errors */ +++- if (n < 0) +++- { +++- if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) +++- { +++- Log(5, "DEBUG: select interrupted, continuing"); +++- continue; +++- } +++- +++- if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) +++- { +++- Log(2, "select: %s, reopening", TCPERR()); +++- +++- close_listen_sockets(); +++- +++- if (server_flag && open_listen_sockets(config) < 0) +++- break; +++- +++- continue; +++- } +++- +++- Log(1, "select: %s", TCPERR()); +++- break; +++- } +++- else if (n == 0) +++- { +++- Log(5, "DEBUG: select timeout, continuing"); +++- } +++- +++- /* server: Accept new inbound connections */ +++- if (server_flag) +++- { +++- if (handle_server_accept(&r, config) < 0) +++- break; +++- } +++- +++- if (binkd_exit) +++- break; +++- +++- /* server + client: Advance all active sessions */ +++- active_sessions = advance_sessions(&r, &w, config); +++- +++- if (binkd_exit) +++- break; +++- +++- /* client: Time-based outbound scan + config reload */ +++- now = time(NULL); +++- +++- if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) +++- { +++- Log(5, "DEBUG: Before try_outbound"); +++- +++- try_outbound(config); +++- +++- Log(5, "DEBUG: After try_outbound"); +++- +++- if (checkcfg()) +++- { +++- if (handle_config_reload(&config, server_flag, client_flag)) +++- break; +++- +++- config_locked = 1; +++- } +++- +++- config->q_present = 0; +++- last_rescan = now; +++- } +++- +++- /* client: Poll-mode idle exit */ +++- /* Reset counter whenever there is something going on */ +++- if (n_clients > 0 || active_sessions > 0) +++- { +++- if (idle_loops > 0) +++- Log(2, "Activity detected, reset idle counter"); +++- +++- idle_loops = 0; +++- } +++- +++- if (!server_flag && active_sessions == 0 && n_clients == 0) +++- { +++- idle_loops++; +++- +++- Log(2, "Idle loop %d/2 (no server, no sessions, no clients)", idle_loops); +++- +++- if (idle_loops > 1) +++- { +++- Log(0, "the queue is empty, quitting..."); +++- break; +++- } +++- } +++- } +++- /* ===== End main loop ===== */ +++- +++- evloop_cleanup(config, config_locked); +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop.h binkd_pgul/amiga/evloop.h +++--- binkd/amiga/evloop.h 2026-04-26 11:47:03.034239655 +0100 ++++++ binkd_pgul/amiga/evloop.h 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,34 +0,0 @@ +++-/* +++- * evloop.h -- non-blocking event loop for AmigaOS 3 +++- * +++- * evloop.h is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifndef _AMIGA_EVLOOP_H +++-#define _AMIGA_EVLOOP_H +++- +++-#ifdef AMIGA +++- +++-#include "readcfg.h" +++-#include "protoco2.h" /* STATE */ +++- +++-/* amiga_proto_step() return codes — also used by protocol.c */ +++-#define APROTO_RUNNING 0 /* session alive, call again */ +++-#define APROTO_DONE_OK 1 /* session finished, success */ +++-#define APROTO_DONE_ERR 2 /* session failed */ +++- +++-/* +++- * amiga_evloop_run -- entry point replacing servmgr() + clientmgr() +++- * +++- * Opens listen sockets (when server_flag), then runs a WaitSelect() +++- * loop that multiplexes sessions dynamically based on config->max_servers +++- * and config->max_clients. Minimum 2 sessions are always allocated +++- * Returns only when binkd_exit != 0 +++- */ +++-void amiga_evloop_run(BINKD_CONFIG *config, int server_flag, int client_flag); +++- +++-#endif /* AMIGA */ +++-#endif /* _AMIGA_EVLOOP_H */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop_int.h binkd_pgul/amiga/evloop_int.h +++--- binkd/amiga/evloop_int.h 2026-04-26 11:02:58.166969078 +0100 ++++++ binkd_pgul/amiga/evloop_int.h 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,68 +0,0 @@ +++-/* +++- * evloop_int.h -- internal types shared by evloop.c, sock.c, session.c +++- * +++- * evloop_int.h is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifndef AMIGA_EVLOOP_INT_H +++-#define AMIGA_EVLOOP_INT_H +++- +++-#include "protoco2.h" +++-#include "amiga/bsdsock.h" +++-#include "ftnnode.h" +++-#include "readcfg.h" +++- +++-/* Session lifecycle */ +++-typedef enum +++-{ +++- SESS_FREE = 0, /* slot available */ +++- SESS_CONNECTING = 1, /* waiting for connect() */ +++- SESS_RUNNING = 2 /* BinkP session active */ +++-} sess_phase_t; +++- +++-/* Per-session state */ +++-typedef struct +++-{ +++- sess_phase_t phase; +++- SOCKET fd; +++- STATE state; +++- int inbound; /* 1=accepted, 0=outbound */ +++- +++- FTN_NODE *node; +++- struct addrinfo *ai_head; /* full getaddrinfo list */ +++- struct addrinfo *ai_cur; /* candidate being tried */ +++- time_t conn_start; +++- +++- char host[BINKD_FQDNLEN + 1]; +++- char port[MAXPORTSTRLEN + 1]; +++- char ip[BINKD_FQDNLEN + 1]; +++- +++- time_t last_io; +++-} sess_t; +++- +++-/* Globals defined in evloop.c */ +++-extern sess_t *sessions; +++-extern int max_sessions; +++- +++-/* Defined in server.c and client.c respectively */ +++-extern int n_servers; +++-extern int n_clients; +++- +++-/* sock.c */ +++-void set_nonblock(SOCKET fd); +++-int open_listen_sockets(BINKD_CONFIG *config); +++-void close_listen_sockets(void); +++- +++-/* session.c */ +++-int sess_alloc(void); +++-void sess_free(int idx); +++-void do_accept(SOCKET lfd, BINKD_CONFIG *config); +++-int start_connect(sess_t *s, BINKD_CONFIG *config); +++-void check_connect(int idx, BINKD_CONFIG *config); +++-int try_outbound(BINKD_CONFIG *config); +++-void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config); +++- +++-#endif /* AMIGA_EVLOOP_INT_H */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/proto_amiga.c binkd_pgul/amiga/proto_amiga.c +++--- binkd/amiga/proto_amiga.c 2026-04-26 11:52:04.887130443 +0100 ++++++ binkd_pgul/amiga/proto_amiga.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,269 +0,0 @@ +++-/* +++- * proto_amiga.c -- Amiga non-blocking BinkP protocol implementation +++- * +++- * proto_amiga.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include +++-#include +++-#include +++-#include +++-#include +++- +++-#include "sys.h" +++-#include "readcfg.h" +++-#include "common.h" +++-#include "protocol.h" +++-#include "ftnaddr.h" +++-#include "ftnnode.h" +++-#include "ftnq.h" +++-#include "tools.h" +++-#include "bsy.h" +++-#include "inbound.h" +++-#include "protoco2.h" +++-#include "prothlp.h" +++-#include "binlog.h" +++-#include "evloop.h" +++- +++-/* External functions from protocol.c */ +++-extern int init_protocol(STATE *state, SOCKET s_in, SOCKET s_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config); +++-extern int banner(STATE *state, BINKD_CONFIG *config); +++-extern int recv_block(STATE *state, BINKD_CONFIG *config); +++-extern int send_block(STATE *state, BINKD_CONFIG *config); +++-extern void bsy_touch(BINKD_CONFIG *config); +++-extern FTNQ *process_rcvdlist(STATE *state, FTNQ *q, BINKD_CONFIG *config); +++-extern int start_file_transfer(STATE *state, FTNQ *q, BINKD_CONFIG *config); +++-extern void ND_set_status(const char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config); +++-extern void deinit_protocol(STATE *state, BINKD_CONFIG *config, int status); +++-extern void evt_set(EVTQ *eq); +++-extern void msg_send2(STATE *state, t_msg m, char *s1, char *s2); +++- +++-/* External functions from other modules */ +++-extern void log_end_of_session(int err, STATE *state, BINKD_CONFIG *config); +++-extern void inb_remove_partial(STATE *state, BINKD_CONFIG *config); +++-extern void good_try(FTN_ADDR *fa, char *comment, BINKD_CONFIG *config); +++-extern void bad_try(FTN_ADDR *fa, const char *error, const int where, BINKD_CONFIG *config); +++-extern int create_poll(FTN_ADDR *fa, int flvr, BINKD_CONFIG *config); +++-extern void hold_node(FTN_ADDR *fa, time_t hold_until, BINKD_CONFIG *config); +++-extern int binkd_exit; +++- +++-/* External variables */ +++-extern int n_servers; +++- +++-/* +++- * amiga_proto_open -- Initialise a session and send the BinkP banner +++- * +++- * fd : Connected socket (same fd for in and out) +++- * to : Outbound node, NULL for inbound +++- * fa : Local AKA to use, may be NULL +++- * host : Remote hostname or dotted-IP string (caller-owned, stable) +++- * port : Remote port string, may be NULL +++- * dst_ip : Numeric remote IP, may be NULL (falls back to host) +++- * config : Current config +++- * +++- * Returns 0 on success, -1 on error (caller must close fd) +++- */ +++-int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config) +++-{ +++- struct sockaddr_storage sa; +++- socklen_t salen = (socklen_t)sizeof(sa); +++- char ownhost[BINKD_FQDNLEN + 1]; +++- char ownserv[MAXSERVNAME + 1]; +++- int rc; +++- +++- if (!init_protocol(state, fd, fd, to, fa, config)) +++- return -1; +++- +++- /* Peer identity for logging and %ip config checks */ +++- state->ipaddr = dst_ip ? (char *)dst_ip : (char *)host; +++- state->peer_name = (host && *host) ? (char *)host : state->ipaddr; +++- +++- /* local endpoint: Not used further, skip to avoid dangling pointer */ +++- +++- Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); +++- +++- /* banner() sends M_NUL lines and ADR messages */ +++- if (!banner(state, config)) +++- return -1; +++- +++- /* refuse if server limit reached */ +++- if (!to && n_servers > config->max_servers) +++- { +++- Log(1, "too many servers"); +++- msg_send2(state, M_BSY, "Too many servers", 0); +++- +++- return -1; +++- } +++- +++- return 0; +++-} +++- +++-/* +++- * amiga_proto_step -- Run one recv/send iteration of the BinkP loop +++- * +++- * readable : Non-zero if the socket has incoming data (from WaitSelect) +++- * writable : Non-zero if the socket can accept outgoing data +++- * +++- * Returns APROTO_RUNNING, APROTO_DONE_OK, or APROTO_DONE_ERR +++- * +++- * This is the loop body of protocol() with the select() call removed +++- * recv_block() and send_block() already handle EWOULDBLOCK gracefully, +++- * so calling this on a non-blocking socket is safe +++- */ +++-int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config) +++-{ +++- FTNQ *q; +++- int no; +++- +++- if (state->io_error) +++- return APROTO_DONE_ERR; +++- +++- /* Advance outgoing file queue if nothing is being sent */ +++- if (!state->local_EOB && state->q && !state->out.f && !state->waiting_for_GOT && !state->off_req_sent && state->state != P_NULL) +++- { +++- while (1) +++- { +++- q = 0; +++- if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) +++- { +++- if (start_file_transfer(state, q, config)) +++- break; +++- } +++- else +++- { +++- q_free(state->q, config); +++- state->q = 0; +++- break; +++- } +++- } +++- } +++- +++- /* Nothing left to send — issue EOB */ +++- if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) +++- { +++- if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) +++- { +++- state->local_EOB = 1; +++- msg_send2(state, M_EOB, 0, 0); +++- } +++- } +++- +++- /* Recv step: Only when socket is readable */ +++- if (readable) +++- { +++- if (!recv_block(state, config)) +++- return APROTO_DONE_ERR; +++- } +++- +++- /* +++- * send step: drive even when writable=0 if there is buffered data, +++- * pending messages, a file mid-transfer, or an EOF to flush. +++- */ +++- if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) +++- { +++- no = send_block(state, config); +++- +++- if (!no && no != 2) +++- return APROTO_DONE_ERR; +++- } +++- +++- bsy_touch(config); +++- +++- /* Batch/Session-end detection — Mirrors the break logic in protocol() */ +++- if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) +++- { +++- if (state->rcvdlist) +++- { +++- state->q = process_rcvdlist(state, state->q, config); +++- +++- q_to_killlist(&state->killlist, &state->n_killlist, state->q); +++- free_rcvdlist(&state->rcvdlist, &state->n_rcvdlist); +++- } +++- +++- Log(6, "batch: %i msgs", state->msgs_in_batch); +++- +++- if (state->msgs_in_batch <= 2 || (state->major * 100 + state->minor <= 100)) +++- { +++- /* Session done */ +++- ND_set_status("", &state->ND_addr, state, config); +++- state->ND_addr.z = -1; +++- +++- return APROTO_DONE_OK; +++- } +++- +++- /* Start next batch */ +++- state->msgs_in_batch = 0; +++- state->remote_EOB = 0; +++- state->local_EOB = 0; +++- +++- if (OK_SEND_FILES(state, config)) +++- { +++- state->q = q_scan_boxes(state->q, state->fa, state->nfa, state->to ? 1 : 0, config); +++- state->q = q_sort(state->q, state->fa, state->nfa, config); +++- } +++- } +++- +++- return APROTO_RUNNING; +++-} +++- +++-/* +++- * amiga_proto_close -- Flush remaining I/O and release STATE resources +++- * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR +++- */ +++-void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) +++-{ +++- int no; +++- char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; +++- int status = ok ? 0 : 1; +++- +++- /* Drain inbound queue */ +++- if (!state->io_error) +++- { +++- while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) +++- Log(9, "purged %d bytes", no); +++- } +++- +++- /* Flush pending outbound messages */ +++- while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) +++- ; +++- +++- if (ok) +++- { +++- log_end_of_session(0, state, config); +++- process_killlist(state->killlist, state->n_killlist, 's'); +++- inb_remove_partial(state, config); +++- +++- if (state->to) +++- good_try(&state->to->fa, "CONNECT/BND", config); +++- } +++- else +++- { +++- log_end_of_session(1, state, config); +++- process_killlist(state->killlist, state->n_killlist, 0); +++- +++- if (!binkd_exit && state->to) +++- bad_try(&state->to->fa, "Bad session", BAD_IO, config); +++- +++- /* Restore poll flavour if files were left mid-transfer */ +++- if (state->to && tolower(state->maxflvr) != 'h') +++- { +++- Log(4, "restoring poll with '%c' flavour", state->maxflvr); +++- +++- create_poll(&state->to->fa, state->maxflvr, config); +++- } +++- } +++- +++- if (state->to && state->r_skipped_flag && config->hold_skipped > 0) +++- { +++- Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); +++- +++- hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); +++- } +++- +++- deinit_protocol(state, config, status); +++- evt_set(state->evt_queue); +++- state->evt_queue = NULL; +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/proto_amiga.h binkd_pgul/amiga/proto_amiga.h +++--- binkd/amiga/proto_amiga.h 2026-04-26 11:53:22.716799421 +0100 ++++++ binkd_pgul/amiga/proto_amiga.h 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,30 +0,0 @@ +++-/* +++- * proto_amiga.h -- Amiga non-blocking BinkP protocol implementation +++- * +++- * proto_amiga.h is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifndef _PROTO_AMIGA_H +++-#define _PROTO_AMIGA_H +++- +++-#include "protoco2.h" +++-#include "readcfg.h" +++- +++-/* amiga_proto_step() return codes */ +++-#define APROTO_RUNNING 0 +++-#define APROTO_DONE_OK 1 +++-#define APROTO_DONE_ERR 2 +++- +++-/* amiga_proto_open -- Initialise a session and send the BinkP banner */ +++-int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config); +++- +++-/* amiga_proto_step-- run one recv / send iteration of the BinkP loop */ +++-int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config); +++- +++-/* amiga_proto_close -- flush remaining I/O and release STATE resources */ +++-void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok); +++- +++-#endif /* _PROTO_AMIGA_H */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/rename.c binkd_pgul/amiga/rename.c +++--- binkd/amiga/rename.c 2026-04-25 19:33:26.785601976 +0100 ++++++ binkd_pgul/amiga/rename.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -1,137 +1,10 @@ +++ #include +++ #include +++-#include +++- +++-#include +++-#include /* atoi */ +++-#include /* isdigit */ +++- +++-#define PATHBUF 512 +++ +++ int o_rename(char *from, char *to) +++ { +++- struct FileInfoBlock *fib; +++- char dir[PATHBUF]; +++- char base[PATHBUF]; +++- char newname[PATHBUF]; +++- char *slash; +++- ULONG max = 0; +++- BPTR dirlock; +++- char *d = NULL; +++- const char *src = NULL; +++- ULONG n = 0; +++- +++- /* Try direct rename first */ +++- if (Rename((STRPTR)from, (STRPTR)to)) +++- return 0; +++- +++- /* Split path */ +++- slash = strrchr(to, '/'); +++- +++- if (!slash) +++- slash = strrchr(to, ':'); +++- +++- if (slash) +++- { +++- ULONG len = slash - to; +++- +++- if (len >= PATHBUF) +++- len = PATHBUF - 1; +++- +++- strncpy(dir, to, len); +++- dir[len] = '\0'; +++- +++- strncpy(base, slash + 1, PATHBUF - 1); +++- base[PATHBUF - 1] = '\0'; +++- } +++- else +++- { +++- strcpy(dir, "."); +++- strncpy(base, to, PATHBUF - 1); +++- base[PATHBUF - 1] = '\0'; +++- } +++- +++- /* Lock directory */ +++- dirlock = Lock((STRPTR)dir, ACCESS_READ); +++- +++- if (!dirlock) +++- { +++- errno = ENOENT; +++- return -1; +++- } +++- +++- fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); +++- +++- if (!fib) +++- { +++- UnLock(dirlock); +++- errno = ENOMEM; +++- return -1; +++- } +++- +++- /* Scan directory safely under lock */ +++- if (Examine(dirlock, fib)) +++- { +++- while (ExNext(dirlock, fib)) +++- { +++- if (strncmp(fib->fib_FileName, base, strlen(base)) == 0) +++- { +++- const char *p = NULL; +++- +++- p = fib->fib_FileName + strlen(base); +++- +++- if (*p != '.') +++- { +++- continue; +++- } +++- +++- /* .001 style */ +++- if (isdigit((UBYTE)p[1]) && isdigit((UBYTE)p[2]) && isdigit((UBYTE)p[3])) +++- { +++- n = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); +++- if (n > max) +++- max = n; +++- } +++- +++- /* FIDO volume style (.mo0 .th1 etc) */ +++- if (isdigit((UBYTE)p[1]) && !isdigit((UBYTE)p[2])) +++- { +++- n = p[1] - '0'; +++- if (n > max) +++- max = n; +++- } +++- } +++- } +++- } +++- +++- FreeDosObject(DOS_FIB, fib); +++- UnLock(dirlock); +++- +++- /* Build new name */ +++- d = newname; +++- src = to; +++- n = max + 1; +++- +++- if (n > 999) +++- n = 0; +++- +++- /* Copy base */ +++- while (*src && (d - newname) < (PATHBUF - 5)) +++- *d++ = *src++; +++- +++- *d++ = '.'; +++- +++- /* Manual 3-digit write */ +++- *d++ = '0' + (n / 100); +++- n %= 100; +++- *d++ = '0' + (n / 10); +++- *d++ = '0' + (n % 10); +++- *d = '\0'; +++- +++- /* Rename */ +++- if (Rename((STRPTR)from, (STRPTR)newname)) +++- return 0; +++- +++- errno = EACCES; ++++ if (Rename((STRPTR)from, (STRPTR)to)) /* cross-volume move won't work */ +++ return -1; ++++ else ++++ return 0; +++ } +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/rfc2553_amiga.c binkd_pgul/amiga/rfc2553_amiga.c +++--- binkd/amiga/rfc2553_amiga.c 2026-04-26 11:54:19.321503648 +0100 ++++++ binkd_pgul/amiga/rfc2553_amiga.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,323 +0,0 @@ +++-/* +++- * rfc2553_amiga.c -- getaddrinfo/getnameinfo fallback for AmigaOS 3 +++- * +++- * rfc2553_amiga.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifdef AMIGA +++- +++-#include "amiga/bsdsock.h" /* LP stubs + SocketBase */ +++-#include "rfc2553.h" /* sets HAVE_GETADDRINFO / HAVE_GETNAMEINFO */ +++-#include "sem.h" +++- +++-#include +++-#include +++-#include +++-#include +++- +++-#define safe_strncpy(dst, src, n) \ +++- do \ +++- { \ +++- strncpy((dst), (src), (n)); \ +++- (dst)[(n) - 1] = '\0'; \ +++- } while (0) +++- +++-#ifndef HAVE_GETADDRINFO +++- +++-void freeaddrinfo(struct addrinfo *ai); /* forward decl */ +++- +++-int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) +++-{ +++- struct addrinfo **tail = res; +++- struct hostent *hent = NULL; +++- unsigned int port; +++- int proto; +++- const char *end; +++- char **addrp; +++- +++- static char passive_dummy = '\0'; +++- char *passive_list[2] = {&passive_dummy, NULL}; +++- +++- if (!res) +++- { +++- return EAI_UNKNOWN; +++- } +++- +++- *res = NULL; +++- +++- port = servname ? htons((unsigned short)strtol(servname, (char **)&end, 0)) : 0; +++- proto = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; +++- +++- lockresolvsem(); +++- +++- if (servname && end != servname + strlen(servname)) +++- { +++- struct servent *se = NULL; +++- +++- if (!hints || hints->ai_socktype == SOCK_STREAM) +++- se = getservbyname((char *)servname, "tcp"); +++- +++- if (hints && hints->ai_socktype == SOCK_DGRAM) +++- se = getservbyname((char *)servname, "udp"); +++- +++- if (!se) +++- { +++- releaseresolvsem(); +++- return EAI_NONAME; +++- } +++- +++- port = se->s_port; +++- +++- if (strcmp((char *)se->s_proto, "tcp") == 0) +++- proto = SOCK_STREAM; +++- else if (strcmp((char *)se->s_proto, "udp") == 0) +++- proto = SOCK_DGRAM; +++- else +++- { +++- releaseresolvsem(); +++- return EAI_NONAME; +++- } +++- +++- if (hints && hints->ai_socktype && hints->ai_socktype != proto) +++- { +++- releaseresolvsem(); +++- return EAI_SERVICE; +++- } +++- } +++- +++- if (!hints || !(hints->ai_flags & AI_PASSIVE)) +++- { +++- unsigned long nip = inet_addr((char *)nodename); +++- +++- if (nip != (unsigned long)INADDR_NONE) +++- { +++- struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); +++- struct sockaddr_in *sin; +++- +++- if (!ai) +++- { +++- releaseresolvsem(); +++- return EAI_MEMORY; +++- } +++- *tail = ai; +++- +++- sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); +++- +++- if (!sin) +++- { +++- free(ai); +++- releaseresolvsem(); +++- return EAI_MEMORY; +++- } +++- +++- ai->ai_family = AF_INET; +++- ai->ai_socktype = proto; +++- ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; +++- ai->ai_addrlen = sizeof(*sin); +++- ai->ai_addr = (struct sockaddr *)sin; +++- sin->sin_family = AF_INET; +++- sin->sin_port = port; +++- sin->sin_addr.s_addr = nip; +++- +++- releaseresolvsem(); +++- return 0; +++- } +++- +++- hent = gethostbyname((char *)nodename); +++- +++- if (!hent) +++- { +++- int herr = errno; +++- releaseresolvsem(); +++- return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL +++- : EAI_NONAME; +++- } +++- +++- if (!hent->h_addr_list || !hent->h_addr_list[0]) +++- { +++- releaseresolvsem(); +++- return EAI_NONAME; +++- } +++- +++- addrp = hent->h_addr_list; +++- } +++- else +++- { +++- addrp = passive_list; +++- } +++- +++- for (; *addrp; addrp++) +++- { +++- struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); +++- struct sockaddr_in *sin; +++- +++- if (!ai) +++- { +++- releaseresolvsem(); +++- freeaddrinfo(*res); +++- *res = NULL; +++- return EAI_MEMORY; +++- } +++- +++- if (!*res) +++- *res = ai; +++- *tail = ai; +++- tail = &ai->ai_next; +++- +++- sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); +++- +++- if (!sin) +++- { +++- releaseresolvsem(); +++- freeaddrinfo(*res); +++- *res = NULL; +++- return EAI_MEMORY; +++- } +++- +++- ai->ai_family = AF_INET; +++- ai->ai_socktype = proto; +++- ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; +++- ai->ai_addrlen = sizeof(*sin); +++- ai->ai_addr = (struct sockaddr *)sin; +++- sin->sin_family = AF_INET; +++- sin->sin_port = port; +++- +++- if (!hints || !(hints->ai_flags & AI_PASSIVE)) +++- { +++- size_t cpylen = sizeof(sin->sin_addr); +++- +++- if (hent->h_length > 0 && (size_t)hent->h_length < cpylen) +++- cpylen = (size_t)hent->h_length; +++- +++- memcpy(&sin->sin_addr, *addrp, cpylen); +++- } +++- } +++- +++- releaseresolvsem(); +++- return 0; +++-} +++- +++-void freeaddrinfo(struct addrinfo *ai) +++-{ +++- struct addrinfo *next; +++- +++- while (ai) +++- { +++- free(ai->ai_addr); +++- next = ai->ai_next; +++- free(ai); +++- ai = next; +++- } +++-} +++- +++-static const char *ai_errlist[] = +++- { +++- "Success", +++- "hostname nor servname provided, or not known", +++- "Temporary failure in name resolution", +++- "Non-recoverable failure in name resolution", +++- "No address associated with hostname", +++- "ai_family not supported", +++- "ai_socktype not supported", +++- "service name not supported for ai_socktype", +++- "Address family for hostname not supported", +++- "Memory allocation failure", +++- "System error returned in errno", +++- "Unknown error", +++-}; +++- +++-char *gai_strerror(int ecode) +++-{ +++- if (ecode > 0 || ecode < EAI_UNKNOWN) +++- ecode = EAI_UNKNOWN; +++- return (char *)ai_errlist[-ecode]; +++-} +++- +++-#endif /* !HAVE_GETADDRINFO */ +++- +++-#ifndef HAVE_GETNAMEINFO +++- +++-#ifndef NI_DATAGRAM +++-#define NI_DATAGRAM (1 << 4) +++-#endif +++- +++-int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) +++-{ +++- const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; +++- +++- (void)salen; +++- +++- if (sa->sa_family != AF_INET) +++- return EAI_ADDRFAMILY; +++- +++- if (host && hostlen > 0) +++- { +++- if (!(flags & NI_NUMERICHOST)) +++- { +++- struct hostent *he; +++- +++- lockresolvsem(); +++- he = gethostbyaddr((char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET); +++- +++- if (he) +++- { +++- safe_strncpy(host, (char *)he->h_name, hostlen); +++- releaseresolvsem(); +++- } +++- else +++- { +++- int herr = errno; +++- releaseresolvsem(); +++- if (flags & NI_NAMEREQD) +++- return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL +++- : EAI_NONAME; +++- flags |= NI_NUMERICHOST; +++- } +++- } +++- +++- if (flags & NI_NUMERICHOST) +++- { +++- lockhostsem(); +++- safe_strncpy(host, (char *)Inet_NtoA(sin->sin_addr.s_addr), hostlen); +++- releasehostsem(); +++- } +++- } +++- +++- if (serv && servlen > 0) +++- { +++- if (!(flags & NI_NUMERICSERV)) +++- { +++- struct servent *se; +++- +++- lockresolvsem(); +++- +++- se = (flags & NI_DATAGRAM) ? getservbyport(ntohs(sin->sin_port), "udp") : getservbyport(ntohs(sin->sin_port), "tcp"); +++- +++- if (se) +++- { +++- safe_strncpy(serv, (char *)se->s_name, servlen); +++- releaseresolvsem(); +++- } +++- else +++- { +++- releaseresolvsem(); +++- +++- if (flags & NI_NAMEREQD) +++- return EAI_NONAME; +++- +++- flags |= NI_NUMERICSERV; +++- } +++- } +++- +++- if (flags & NI_NUMERICSERV) +++- snprintf(serv, servlen, "%u", ntohs(sin->sin_port)); +++- } +++- +++- return 0; +++-} +++- +++-#endif /* !HAVE_GETNAMEINFO */ +++-#endif /* AMIGA */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/sem.c binkd_pgul/amiga/sem.c +++--- binkd/amiga/sem.c 2026-04-25 19:33:46.416919700 +0100 ++++++ binkd_pgul/amiga/sem.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -1,100 +1,29 @@ +++ /* +++ * Amiga semaphores +++ */ +++- +++ #include +++ #include +++-#include +++-#include +++- +++-extern void Log(int lev, char *s, ...); +++- +++-int _InitSem(void *vpSem) +++-{ +++- memset(vpSem, 0, sizeof(struct SignalSemaphore)); +++- InitSemaphore((struct SignalSemaphore *)vpSem); +++- return 0; +++-} +++- +++-int _CleanSem(void *vpSem) +++-{ +++- return 0; +++-} +++- +++-int _LockSem(void *vpSem) +++-{ +++- ObtainSemaphore((struct SignalSemaphore *)vpSem); +++- return 0; +++-} +++- +++-int _ReleaseSem(void *vpSem) +++-{ +++- ReleaseSemaphore((struct SignalSemaphore *)vpSem); +++- return 0; +++-} ++++#include +++ +++-int _InitEventSem(EVENTSEM *sem) +++-{ +++- if (!sem) +++- return -1; ++++extern void Log (int lev, char *s,...); +++ +++- sem->waiter = NULL; +++ +++- sem->sigbit = AllocSignal(-1); +++- +++- if (sem->sigbit == (ULONG)-1) +++- return -1; +++- +++- return 0; ++++int _InitSem(void *vpSem) { ++++ memset(vpSem, 0, sizeof (struct SignalSemaphore)); ++++ InitSemaphore ((struct SignalSemaphore*)vpSem); ++++ return(0); +++ } +++ +++-int _CleanEventSem(EVENTSEM *sem) +++-{ +++- if (!sem) +++- return -1; +++- +++- if (sem->sigbit != (ULONG)-1) +++- { +++- FreeSignal((LONG)sem->sigbit); +++- sem->sigbit = (ULONG)-1; +++- } +++- +++- sem->waiter = NULL; +++- return 0; ++++int _CleanSem(void *vpSem) { ++++ return (0); +++ } +++ +++-int _PostSem(EVENTSEM *sem) +++-{ +++- if (!sem) +++- return -1; +++- +++- if (sem->waiter && sem->sigbit != (ULONG)-1) +++- { +++- Signal((struct Task *)sem->waiter, 1UL << sem->sigbit); +++- } +++- +++- return 0; ++++int _LockSem(void *vpSem) { ++++ ObtainSemaphore ((struct SignalSemaphore *)vpSem); ++++ return (0); +++ } +++ +++-int _WaitSem(EVENTSEM *sem, int sec) +++-{ +++- ULONG mask; +++- struct Task *me; +++- +++- if (!sem || sem->sigbit == (ULONG)-1) +++- return -1; +++- +++- me = FindTask(NULL); +++- sem->waiter = me; +++- +++- /* Wait on SIGBREAKF_CTRL_C to avoid hanging on race */ +++- mask = Wait((1UL << sem->sigbit) | SIGBREAKF_CTRL_C); +++- +++- sem->waiter = NULL; +++- +++- /* Return timeout/break indication if only CTRL_C fired */ +++- if (!(mask & (1UL << sem->sigbit)) && (mask & SIGBREAKF_CTRL_C)) +++- return -1; +++- +++- return 0; ++++int _ReleaseSem(void *vpSem) { ++++ ReleaseSemaphore ((struct SignalSemaphore *)vpSem); ++++ return (0); +++ } +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/session.c binkd_pgul/amiga/session.c +++--- binkd/amiga/session.c 2026-04-26 11:57:30.521108284 +0100 ++++++ binkd_pgul/amiga/session.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,462 +0,0 @@ +++-/* +++- * session.c -- session management for AmigaOS 3 binkd +++- * +++- * session.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include +++-#include +++- +++-#include +++-#include +++-#include +++-#include +++- +++-#include "sys.h" +++-#include "iphdr.h" +++-#include "readcfg.h" +++-#include "common.h" +++-#include "tools.h" +++-#include "client.h" +++-#include "protocol.h" +++-#include "ftnq.h" +++-#include "ftnnode.h" +++-#include "ftnaddr.h" +++-#include "bsy.h" +++-#include "iptools.h" +++-#include "rfc2553.h" +++-#include "srv_gai.h" +++-#include "amiga/bsdsock.h" +++-#include "amiga/evloop_int.h" +++-#include "amiga/proto_amiga.h" +++- +++-extern int binkd_exit; +++-extern int ext_rand; +++-extern int client_flag; +++-extern int poll_flag; +++- +++-/* Session table */ +++-int sess_alloc(void) +++-{ +++- int i; +++- +++- for (i = 0; i < max_sessions; i++) +++- { +++- if (sessions[i].phase == SESS_FREE) +++- { +++- memset(&sessions[i], 0, sizeof(sess_t)); +++- sessions[i].fd = INVALID_SOCKET; +++- sessions[i].phase = SESS_FREE; +++- return i; +++- } +++- } +++- +++- return -1; +++-} +++- +++-void sess_free(int idx) +++-{ +++- sess_t *s = &sessions[idx]; +++- +++- if (s->fd != INVALID_SOCKET) +++- { +++- soclose(s->fd); +++- s->fd = INVALID_SOCKET; +++- } +++- +++- if (s->ai_head) +++- { +++- freeaddrinfo(s->ai_head); +++- s->ai_head = NULL; +++- } +++- +++- memset(&s->state, 0, sizeof(STATE)); +++- s->phase = SESS_FREE; +++-} +++- +++-/* Inbound: accept a new connection */ +++-void do_accept(SOCKET lfd, BINKD_CONFIG *config) +++-{ +++- struct sockaddr_storage sa; +++- socklen_t salen = (socklen_t)sizeof(sa); +++- SOCKET fd; +++- int idx; +++- sess_t *s; +++- char host[BINKD_FQDNLEN + 1]; +++- char ip[BINKD_FQDNLEN + 1]; +++- +++- fd = accept(lfd, (struct sockaddr *)&sa, &salen); +++- +++- if (fd == INVALID_SOCKET) +++- { +++- if (TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN) +++- Log(1, "accept(): %s", TCPERR()); +++- +++- return; +++- } +++- +++- if (binkd_exit) +++- { +++- soclose(fd); +++- return; +++- } +++- +++- idx = sess_alloc(); +++- +++- if (idx < 0) +++- { +++- Log(1, "session table full, refusing inbound"); +++- soclose(fd); +++- return; +++- } +++- +++- /* getnameinfo() Is unreliable on AmiTCP: use inet_ntoa directly */ +++- if (((struct sockaddr *)&sa)->sa_family == AF_INET) +++- { +++- struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; +++- strnzcpy(ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); +++- } +++- else +++- { +++- strnzcpy(ip, "unknown", BINKD_FQDNLEN); +++- } +++- +++- /* Backresolv not supported on AmiTCP: always use IP as host */ +++- strnzcpy(host, ip, BINKD_FQDNLEN); +++- +++- set_nonblock(fd); +++- +++- s = &sessions[idx]; +++- s->fd = fd; +++- s->inbound = 1; +++- s->node = NULL; +++- s->ai_head = NULL; +++- s->last_io = time(NULL); +++- strnzcpy(s->host, host, BINKD_FQDNLEN); +++- strnzcpy(s->ip, ip, BINKD_FQDNLEN); +++- s->port[0] = '\0'; +++- +++- if (amiga_proto_open(&s->state, fd, NULL, NULL, s->host, NULL, s->ip, config) != 0) +++- { +++- Log(1, "proto_open failed for %s", ip); +++- sess_free(idx); +++- return; +++- } +++- +++- s->phase = SESS_RUNNING; +++- n_servers++; +++- Log(4, "inbound slot[%d] from %s", idx, ip); +++-} +++- +++-/* Outbound: Non-blocking connect() */ +++-int start_connect(sess_t *s, BINKD_CONFIG *config) +++-{ +++- SOCKET fd; +++- int rc; +++- +++- s->ip[0] = '\0'; +++- s->port[0] = '\0'; +++- +++- fd = socket(s->ai_cur->ai_family, s->ai_cur->ai_socktype, s->ai_cur->ai_protocol); +++- +++- if (fd == INVALID_SOCKET) +++- { +++- Log(1, "outbound socket(): %s", TCPERR()); +++- return -1; +++- } +++- +++- /* getnameinfo() is unreliable on AmiTCP: may return rc=0 with garbage +++- * Use inet_ntoa/ntohs directly */ +++- if (s->ai_cur->ai_family == AF_INET) +++- { +++- struct sockaddr_in *sa4 = (struct sockaddr_in *)s->ai_cur->ai_addr; +++- strnzcpy(s->ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); +++- snprintf(s->port, MAXPORTSTRLEN, "%u", (unsigned)ntohs(sa4->sin_port)); +++- } +++- else +++- { +++- strnzcpy(s->ip, "unknown", BINKD_FQDNLEN); +++- strnzcpy(s->port, "0", MAXPORTSTRLEN); +++- } +++- +++- Log(4, "connecting %s [%s]:%s", s->host, s->ip, s->port); +++- +++- if (config->bindaddr[0]) +++- { +++- struct addrinfo src_h, *src_ai; +++- memset(&src_h, 0, sizeof(src_h)); +++- src_h.ai_family = s->ai_cur->ai_family; +++- src_h.ai_socktype = SOCK_STREAM; +++- src_h.ai_protocol = IPPROTO_TCP; +++- +++- if (getaddrinfo(config->bindaddr, NULL, &src_h, &src_ai) == 0) +++- { +++- bind(fd, src_ai->ai_addr, (int)src_ai->ai_addrlen); +++- freeaddrinfo(src_ai); +++- } +++- } +++- +++- set_nonblock(fd); +++- +++- rc = connect(fd, s->ai_cur->ai_addr, (int)s->ai_cur->ai_addrlen); +++- +++- if (rc == 0 || TCPERRNO == EINPROGRESS || TCPERRNO == EWOULDBLOCK) +++- { +++- s->fd = fd; +++- s->conn_start = time(NULL); +++- return 0; +++- } +++- +++- Log(1, "connect %s: %s", s->host, TCPERR()); +++- +++- bad_try(&s->node->fa, TCPERR(), BAD_CALL, config); +++- soclose(fd); +++- +++- return -1; +++-} +++- +++-int try_outbound(BINKD_CONFIG *config) +++-{ +++- FTN_NODE *node; +++- sess_t *s; +++- int idx, rc; +++- struct addrinfo hints; +++- char dest[FTN_ADDR_SZ + 1]; +++- char host[BINKD_FQDNLEN + 5 + 1]; +++- char port[MAXPORTSTRLEN + 1]; +++- +++- if (!client_flag) +++- return 0; +++- +++- if (!config->q_present) +++- { +++- q_free(SCAN_LISTED, config); +++- +++- if (config->printq) +++- Log(-1, "scan\r"); +++- +++- q_scan(SCAN_LISTED, config); +++- config->q_present = 1; +++- +++- if (config->printq) +++- { +++- q_list(stderr, SCAN_LISTED, config); +++- Log(-1, "idle\r"); +++- } +++- } +++- +++- if (n_clients >= config->max_clients) +++- return 0; +++- +++- node = q_next_node(config); +++- +++- if (!node) +++- return 0; +++- +++- ftnaddress_to_str(dest, &node->fa); +++- +++- if (!bsy_test(&node->fa, F_BSY, config) || !bsy_test(&node->fa, F_CSY, config)) +++- { +++- Log(4, "%s busy", dest); +++- return 0; +++- } +++- +++- idx = sess_alloc(); +++- +++- if (idx < 0) +++- { +++- Log(2, "table full, deferring %s", dest); +++- return 0; +++- } +++- +++- s = &sessions[idx]; +++- memset(s, 0, sizeof(*s)); +++- s->fd = INVALID_SOCKET; +++- s->node = node; +++- s->inbound = 0; +++- +++- rc = get_host_and_port(1, host, port, node->hosts, &node->fa, config); +++- +++- if (rc <= 0) +++- { +++- Log(1, "%s: bad host list", dest); +++- sess_free(idx); +++- +++- return 0; +++- } +++- +++- strnzcpy(s->host, host, BINKD_FQDNLEN); +++- strnzcpy(s->port, port, MAXPORTSTRLEN); +++- +++- memset(&hints, 0, sizeof(hints)); +++- hints.ai_family = node->IP_afamily; +++- hints.ai_socktype = SOCK_STREAM; +++- hints.ai_protocol = IPPROTO_TCP; +++- +++- rc = srv_getaddrinfo(host, port, &hints, &s->ai_head); +++- +++- if (rc != 0) +++- { +++- Log(1, "%s: getaddrinfo error code=%d: %s", dest, rc, gai_strerror(rc)); +++- +++- bad_try(&node->fa, "getaddrinfo failed", BAD_CALL, config); +++- sess_free(idx); +++- +++- return 0; +++- } +++- +++- s->ai_cur = s->ai_head; +++- +++- if (start_connect(s, config) != 0) +++- { +++- sess_free(idx); +++- return 0; +++- } +++- +++- s->phase = SESS_CONNECTING; +++- n_clients++; +++- +++- Log(4, "outbound slot[%d] -> %s", idx, dest); +++- +++- return 1; +++-} +++- +++-/* Check completion of async connect() */ +++-void check_connect(int idx, BINKD_CONFIG *config) +++-{ +++- sess_t *s = &sessions[idx]; +++- int err = 0; +++- socklen_t el = (socklen_t)sizeof(err); +++- int tmo; +++- +++- tmo = config->connect_timeout ? config->connect_timeout : 30; +++- +++- if ((int)(time(NULL) - s->conn_start) >= tmo) +++- { +++- Log(1, "connect timeout -> %s", s->host); +++- +++- bad_try(&s->node->fa, "Timeout", BAD_CALL, config); +++- n_clients--; +++- sess_free(idx); +++- +++- return; +++- } +++- +++- getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &el); +++- +++- if (err) +++- { +++- Log(1, "connect -> %s: %s", s->host, strerror(err)); +++- +++- bad_try(&s->node->fa, strerror(err), BAD_CALL, config); +++- +++- soclose(s->fd); +++- s->fd = INVALID_SOCKET; +++- s->ai_cur = s->ai_cur->ai_next; +++- +++- if (s->ai_cur && start_connect(s, config) == 0) +++- return; /* trying next address */ +++- +++- n_clients--; +++- sess_free(idx); +++- +++- return; +++- } +++- +++- Log(4, "connected -> %s [%s]", s->host, s->ip); +++- ext_rand = rand(); +++- +++- if (amiga_proto_open(&s->state, s->fd, s->node, NULL, s->host, s->port, s->ip, config) != 0) +++- { +++- Log(1, "proto_open failed for %s", s->host); +++- +++- n_clients--; +++- sess_free(idx); +++- return; +++- } +++- +++- s->phase = SESS_RUNNING; +++- s->last_io = time(NULL); +++-} +++- +++-/* Run one protocol step on an active session */ +++-void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config) +++-{ +++- sess_t *s = &sessions[idx]; +++- int rc; +++- int tmo; +++- +++- tmo = config->nettimeout ? config->nettimeout : 300; +++- +++- if ((int)(time(NULL) - s->last_io) >= tmo) +++- { +++- Log(1, "slot[%d] net timeout", idx); +++- +++- if (s->node) +++- bad_try(&s->node->fa, "Timeout", BAD_IO, config); +++- +++- amiga_proto_close(&s->state, config, 0); +++- +++- if (s->inbound) +++- n_servers--; +++- else +++- n_clients--; +++- +++- sess_free(idx); +++- +++- return; +++- } +++- +++- if (s->fd == INVALID_SOCKET) +++- { +++- Log(1, "slot[%d] invalid socket, closing session", idx); +++- +++- if (s->node) +++- bad_try(&s->node->fa, "Invalid socket", BAD_IO, config); +++- +++- if (s->inbound) +++- n_servers--; +++- else +++- n_clients--; +++- +++- sess_free(idx); +++- +++- return; +++- } +++- +++- /* WaitSelect() may not report a readable socket when the remote has +++- * sent a TCP END. Probe with MSG_PEEK so recv_block() sees the EOF */ +++- if (!rd && !wr && s->state.state != P_NULL) +++- { +++- char peek; +++- int pr = recv(s->fd, &peek, 1, MSG_PEEK); +++- +++- if (pr == 0 || (pr < 0 && TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN)) +++- rd = 1; +++- } +++- +++- rc = amiga_proto_step(&s->state, rd, wr, config); +++- +++- if (rd || wr) +++- s->last_io = time(NULL); +++- +++- if (rc == APROTO_RUNNING) +++- return; +++- +++- amiga_proto_close(&s->state, config, rc == APROTO_DONE_OK); +++- +++- Log(4, "slot[%d] %s", idx, rc == APROTO_DONE_OK ? "OK" : "ERR"); +++- +++- if (s->inbound) +++- n_servers--; +++- else +++- n_clients--; +++- +++- sess_free(idx); +++- +++- if (poll_flag && n_clients == 0 && n_servers == 0) +++- binkd_exit = 1; +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/sock.c binkd_pgul/amiga/sock.c +++--- binkd/amiga/sock.c 2026-04-26 11:59:26.645813693 +0100 ++++++ binkd_pgul/amiga/sock.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,130 +0,0 @@ +++-/* +++- * sock.c -- listen socket management for AmigaOS 3 +++- * +++- * sock.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include +++-#include +++- +++-#include +++-#include +++- +++-#include "sys.h" +++-#include "readcfg.h" +++-#include "tools.h" +++-#include "server.h" +++-#include "rfc2553.h" +++-#include "amiga/bsdsock.h" +++-#include "amiga/evloop_int.h" +++- +++-extern SOCKET sockfd[]; +++-extern int sockfd_used; +++-extern int server_flag; +++- +++-void set_nonblock(SOCKET fd) +++-{ +++- long flag = 1L; +++- +++- if (IoctlSocket(fd, FIONBIO, (char *)&flag) != 0) +++- Log(2, "IoctlSocket(FIONBIO) failed: %s", TCPERR()); +++-} +++- +++-int open_listen_sockets(BINKD_CONFIG *config) +++-{ +++- struct listenchain *ll; +++- struct addrinfo hints, *ai, *head; +++- int err, opt = 1; +++- +++- memset(&hints, 0, sizeof(hints)); +++- hints.ai_flags = AI_PASSIVE; +++- hints.ai_family = PF_UNSPEC; +++- hints.ai_socktype = SOCK_STREAM; +++- hints.ai_protocol = IPPROTO_TCP; +++- +++- sockfd_used = 0; +++- +++- for (ll = config->listen.first; ll; ll = ll->next) +++- { +++- err = getaddrinfo(ll->addr[0] ? ll->addr : NULL, ll->port, &hints, &head); +++- +++- if (err) +++- { +++- Log(1, "listen getaddrinfo(%s:%s): %s", ll->addr[0] ? ll->addr : "*", ll->port, gai_strerror(err)); +++- return -1; +++- } +++- +++- for (ai = head; ai && sockfd_used < MAX_LISTENSOCK; ai = ai->ai_next) +++- { +++- SOCKET fd; +++- int retries = 6; +++- +++- fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); +++- +++- if (fd == INVALID_SOCKET) +++- { +++- Log(1, "listen socket(): %s", TCPERR()); +++- continue; +++- } +++- +++- if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, (int)sizeof(opt)) != 0) +++- Log(2, "setsockopt(SO_REUSEADDR) failed: %s", TCPERR()); +++- +++- /* Bsdsocket may hold the port briefly after socket close */ +++- while (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) != 0) +++- { +++- if (--retries == 0) +++- { +++- Log(1, "listen bind(): %s", TCPERR()); +++- +++- soclose(fd); +++- freeaddrinfo(head); +++- return -1; +++- } +++- +++- Log(2, "bind retry in 2s: %s", TCPERR()); +++- +++- Delay(100UL); /* 100 ticks = 2s @ 50Hz */ +++- } +++- +++- if (listen(fd, 5) != 0) +++- { +++- Log(1, "listen(): %s", TCPERR()); +++- +++- soclose(fd); +++- freeaddrinfo(head); +++- return -1; +++- } +++- +++- set_nonblock(fd); +++- sockfd[sockfd_used] = fd; +++- sockfd_used++; +++- } +++- +++- freeaddrinfo(head); +++- +++- Log(3, "listening on %s:%s", +++- ll->addr[0] ? ll->addr : "*", ll->port); +++- } +++- +++- if (sockfd_used == 0 && server_flag) +++- { +++- Log(1, "evloop: no listen sockets opened"); +++- return -1; +++- } +++- +++- return 0; +++-} +++- +++-void close_listen_sockets(void) +++-{ +++- int i; +++- +++- for (i = 0; i < sockfd_used; i++) +++- soclose(sockfd[i]); +++- +++- sockfd_used = 0; +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/utime.c binkd_pgul/amiga/utime.c +++--- binkd/amiga/utime.c 2026-04-26 11:59:41.408046130 +0100 ++++++ binkd_pgul/amiga/utime.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,77 +0,0 @@ +++-/* +++- * utime.c -- utime() stub for AmigaOS 3 without ixemul +++- * +++- * utime.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifdef AMIGA +++- +++-#include +++-#include +++-#include +++-#include +++- +++-#include "amiga/dirent.h" /* declares struct utimbuf and utime() prototype */ +++- +++-/* Days between AmigaDOS epoch (1978-01-01) and POSIX epoch (1970-01-01) */ +++-#define AMIGA_EPOCH_DELTA_DAYS 2922UL +++-#define SECONDS_PER_DAY 86400UL +++-#define SECONDS_PER_MINUTE 60UL +++- +++-int utime(const char *path, const struct utimbuf *times) +++-{ +++- struct DateStamp ds; +++- LONG seconds_today; +++- LONG total_seconds; +++- +++- if (!path) +++- { +++- errno = EINVAL; +++- return -1; +++- } +++- +++- if (!times) +++- { +++- /* Use current time */ +++- DateStamp(&ds); +++- } +++- else +++- { +++- LONG t = (LONG)times->modtime; +++- +++- if (t < (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY)) +++- { +++- /* Time predates AmigaDOS epoch -- clamp to epoch */ +++- t = 0; +++- } +++- else +++- { +++- t -= (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY); +++- } +++- +++- ds.ds_Days = (LONG)(t / (LONG)SECONDS_PER_DAY); +++- total_seconds = t % (LONG)SECONDS_PER_DAY; +++- ds.ds_Minute = (LONG)(total_seconds / (LONG)SECONDS_PER_MINUTE); +++- seconds_today = total_seconds % (LONG)SECONDS_PER_MINUTE; +++- ds.ds_Tick = seconds_today * (LONG)TICKS_PER_SECOND; +++- } +++- +++- if (!SetFileDate((STRPTR)path, &ds)) +++- { +++- /* Most likely cause: file does not exist or is write-protected */ +++- LONG err = IoErr(); +++- +++- if (err == ERROR_OBJECT_NOT_FOUND || err == ERROR_DIR_NOT_FOUND) +++- errno = ENOENT; +++- else +++- errno = EACCES; +++- return -1; +++- } +++- +++- return 0; +++-} +++- +++-#endif /* AMIGA */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/binkd.c binkd_pgul/binkd.c +++--- binkd/binkd.c 2026-04-26 13:20:44.986212073 +0100 ++++++ binkd_pgul/binkd.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -54,10 +54,6 @@ +++ #include "unix/daemonize.h" +++ #endif +++ +++-#ifdef AMIGA +++-/* amiga/bsdsock.h pulled in via iphdr.h for AMIGA */ +++-#include "amiga/evloop.h" +++-#endif +++ #ifdef WIN32 +++ #include "nt/service.h" +++ #include "nt/w32tools.h" +++@@ -66,11 +62,9 @@ +++ #endif +++ #endif +++ +++-#include "bsycleanup.h" +++- +++ #include "confopt.h" +++ +++-#if defined(HAVE_THREADS) || defined(AMIGA) ++++#ifdef HAVE_THREADS +++ MUTEXSEM hostsem; +++ MUTEXSEM resolvsem; +++ MUTEXSEM lsem; +++@@ -100,13 +94,9 @@ +++ char *configpath = NULL; /* Config file name */ +++ char **saved_envp; +++ +++-/* mypid: needed by HAVE_FORK and AMIGA targets */ +++-#if defined(HAVE_FORK) || defined(AMIGA) +++-int mypid; +++-#endif +++- +++ #ifdef HAVE_FORK +++-int got_sighup, got_sigchld; ++++ ++++int mypid, got_sighup, got_sigchld; +++ +++ void chld (int *childcount) +++ { +++@@ -205,13 +195,9 @@ +++ #endif +++ " -C reload on config change\n" +++ " -c run client only\n" +++-#ifndef AMIGA +++ " -i run server on stdin/stdout pipe (by inetd or other)\n" +++-#endif +++ " -f node run server protected session with this node\n" +++-#ifndef AMIGA +++ " -a ip assume remote address when running with '-i' switch\n" +++-#endif +++ #if defined(BINKD9X) +++ " -t cmd (start|stop|restart|status|install|uninstall) service(s)\n" +++ " -S name set Win9x service name, all - use all services\n" +++@@ -326,14 +312,12 @@ +++ case 'c': +++ client_flag = 1; +++ break; +++-#ifndef AMIGA +++ case 'i': +++ inetd_flag = 1; +++ break; +++ case 'a': /* remote IP address */ +++ remote_addr = strdup(optarg); +++ break; +++-#endif +++ case 'f': /* remote FTN address */ +++ remote_node = strdup(optarg); +++ break; +++@@ -432,28 +416,6 @@ +++ } +++ if (optind\n", extract_filename(argv[0])); +++- fprintf(stderr, " Use -P
for polling a specific node (e.g., -P 1:23/456.7)\n"); +++- exit(1); +++- } +++- +++- /* Check for leftover FTN addresses in extra arguments */ +++- while (optind < argc) +++- { +++- char *extra = argv[optind++]; +++- if (strchr(extra, ':') || strchr(extra, '@')) +++- { +++- fprintf(stderr, "%s: Error: Unexpected FTN address '%s' in arguments.\n", extract_filename(argv[0]), extra); +++- fprintf(stderr, " Use -P
before the config file to poll a node.\n"); +++- exit(1); +++- } +++- } +++- +++ #ifdef OS2 +++ if (optindloglevel, current_config->conlog, +++ current_config->logpath, current_config->nolog.first); +++- +++- /* Clean up old .bsy/.csy files at startup */ +++- cleanup_old_bsy(current_config); +++ } +++ else if (verbose_flag) +++ { +++@@ -706,13 +665,9 @@ +++ +++ if (p) +++ { +++- remote_addr = strdup(p); +++- /* Guard against null pointer dereference if strdup fails */ +++- if (remote_addr) +++- { +++- p = strchr(remote_addr, ' '); +++- if (p) *p = '\0'; +++- } ++++ remote_addr = strdup(p); ++++ p = strchr(remote_addr, ' '); ++++ if (p) *p = '\0'; +++ } +++ } +++ /* not using stdin/stdout itself to avoid possible collisions */ +++@@ -722,9 +677,6 @@ +++ inetd_socket_out = dup(fileno(stdout)); +++ #ifdef UNIX +++ tempfd = open("/dev/null", O_RDWR); +++-#elif defined(AMIGA) +++- /* NIL: is the native AmigaDOS null device (no ixemul) */ +++- tempfd = open("NIL:", O_RDWR); +++ #else +++ tempfd = open("nul", O_RDWR); +++ #endif +++@@ -755,15 +707,6 @@ +++ signal (SIGHUP, sighandler); +++ #endif +++ +++-#ifdef AMIGA +++- /* AmigaOS 3: WaitSelect() loop, no fork/threads */ +++- { +++- BINKD_CONFIG *ev_cfg = lock_current_config(); +++- amiga_evloop_run(ev_cfg, server_flag, client_flag); +++- unlock_config_structure(ev_cfg, 0); +++- return 0; +++- } +++-#else +++ if (client_flag && !server_flag) +++ { +++ clientmgr (0); +++@@ -778,9 +721,7 @@ +++ if (client_flag && (pidcmgr = branch (clientmgr, 0, 0)) < 0) +++ { +++ Log (0, "cannot branch out"); +++- exit (1); +++ } +++-#endif /* !AMIGA */ +++ +++ if (*current_config->pid_file) +++ { +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/binlog.c binkd_pgul/binlog.c +++--- binkd/binlog.c 2026-04-22 06:50:46.000000000 +0100 ++++++ binkd_pgul/binlog.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -35,10 +35,6 @@ +++ #include "tools.h" +++ #include "sem.h" +++ +++-#if defined(HAVE_THREADS) || defined(AMIGA) +++-extern MUTEXSEM blsem; +++-#endif +++- +++ /* Write 16-bit integer to file in intel bytes order */ +++ static int fput16(u16 arg, FILE *file) +++ { +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/branch.c binkd_pgul/branch.c +++--- binkd/branch.c 2026-04-26 09:12:44.314385608 +0100 ++++++ binkd_pgul/branch.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -20,6 +20,12 @@ +++ #include "tools.h" +++ #include "sem.h" +++ ++++#ifdef AMIGA ++++int ix_vfork (void); ++++void vfork_setup_child (void); ++++void ix_vfork_resume (void); ++++#endif ++++ +++ #ifdef WITH_PTHREADS +++ typedef struct { +++ void (*F) (void *); +++@@ -126,6 +132,23 @@ +++ #endif +++ #endif +++ ++++#ifdef AMIGA ++++ /* this is rather bizzare. this function pretends to be a fork and behaves ++++ * like one, but actually it's a kind of a thread. so we'll need semaphores */ ++++ ++++ if (!(rc = ix_vfork ())) ++++ { ++++ vfork_setup_child (); ++++ ix_vfork_resume (); ++++ F (arg); ++++ exit (0); ++++ } ++++ else if (rc < 0) ++++ { ++++ Log (1, "ix_vfork: %s", strerror (errno)); ++++ } ++++#endif ++++ +++ #if defined(DOS) || defined(DEBUGCHILD) +++ rc = 0; +++ F (arg); +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/breaksig.c binkd_pgul/breaksig.c +++--- binkd/breaksig.c 2026-04-26 10:25:19.576809578 +0100 ++++++ binkd_pgul/breaksig.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -46,16 +46,6 @@ +++ { +++ atexit (exitfunc); +++ +++-#ifdef AMIGA +++- /* AmigaOS / libnix: signal() maps SIGINT -> SIGBREAKF_CTRL_C +++- * Register exitsig() so that Ctrl+C sets binkd_exit=1 even when +++- * the process is NOT blocked inside WaitSelect() (e.g. during disk +++- * I/O). When inside WaitSelect(), amiga_select_wrap() in bsdsock.h +++- * detects the break and sets binkd_exit=1 directly without going +++- * through this signal handler. */ +++- signal (SIGINT, exitsig); +++- signal (SIGTERM, exitsig); +++-#else +++ #ifdef SIGBREAK +++ signal (SIGBREAK, exitsig); +++ #endif +++@@ -68,6 +58,5 @@ +++ #ifdef SIGTERM +++ signal (SIGTERM, exitsig); +++ #endif +++-#endif /* AMIGA */ +++ return 1; +++ } +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/bsycleanup.c binkd_pgul/bsycleanup.c +++--- binkd/bsycleanup.c 2026-04-26 11:01:23.269049076 +0100 ++++++ binkd_pgul/bsycleanup.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,122 +0,0 @@ +++-/* +++- * bsycleanup.c -- Cleanup functions for .bsy/.csy/.try files +++- * +++- * bsycleanup.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include +++-#include +++-#include +++-#include +++-#include +++- +++-#include "sys.h" +++-#include "readcfg.h" +++-#include "ftnq.h" +++-#include "ftnnode.h" +++-#include "tools.h" +++-#include "readdir.h" +++- +++-/* +++- * Clean up old .bsy and .csy files at startup +++- * Scans all domain outbounds +++- */ +++- +++-/* Helper: scan a single directory for .bsy/.csy files and delete them */ +++-static void scan_and_delete_bsy_in_dir(const char *dir, BINKD_CONFIG *config) +++-{ +++- DIR *dp; +++- struct dirent *de; +++- char buf[MAXPATHLEN + 1]; +++- +++- if ((dp = opendir(dir)) == 0) +++- { +++- return; +++- } +++- +++- while ((de = readdir(dp)) != 0) +++- { +++- char *s = de->d_name; +++- int len = strlen(s); +++- +++- if (len > 4 && (!STRICMP(s + len - 4, ".bsy") || !STRICMP(s + len - 4, ".csy") || !STRICMP(s + len - 4, ".try"))) +++- { +++- strnzcpy(buf, dir, sizeof(buf)); +++- strnzcat(buf, PATH_SEPARATOR, sizeof(buf)); +++- strnzcat(buf, s, sizeof(buf)); +++- +++- Log(2, "deleting %s", buf); +++- +++- delete(buf); +++- } +++- } +++- +++- closedir(dp); +++-} +++- +++-void cleanup_old_bsy(BINKD_CONFIG *config) +++-{ +++- DIR *dp; +++- char outb_path[MAXPATHLEN + 1], base_path[MAXPATHLEN + 1]; +++- struct dirent *de; +++- FTN_DOMAIN *curr_domain; +++- int len; +++- +++- Log(2, "cleaning up .bsy/.csy/.try files at startup"); +++- +++- /* Scan all domain outbounds */ +++- for (curr_domain = config->pDomains.first; curr_domain; curr_domain = curr_domain->next) +++- { +++- if (curr_domain->alias4 != 0) +++- continue; +++- +++- /* Build base path: path + separator */ +++- strnzcpy(base_path, curr_domain->path, sizeof(base_path)); +++-#ifndef AMIGA +++- if (base_path[strlen(base_path) - 1] == ':') +++- strcat(base_path, PATH_SEPARATOR); +++-#endif +++- +++-#ifdef AMIGADOS_4D_OUTBOUND +++- if (config->aso) +++- { +++- /* ASO mode: direct outbound path */ +++- strnzcpy(outb_path, base_path, sizeof(outb_path)); +++- strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); +++- strnzcat(outb_path, curr_domain->dir, sizeof(outb_path)); +++- Log(7, "cleanup_old_bsy (ASO): scanning domain '%s', path '%s'", curr_domain->name, outb_path); +++- scan_and_delete_bsy_in_dir(outb_path, config); +++- } +++- else +++-#endif +++- { +++- /* BSO mode: scan for outbound.xxx directories */ +++- Log(7, "cleanup_old_bsy (BSO): scanning domain '%s', base '%s'", curr_domain->name, base_path); +++- +++- if ((dp = opendir(base_path)) == 0) +++- continue; +++- +++- len = strlen(curr_domain->dir); +++- +++- while ((de = readdir(dp)) != 0) +++- { +++- /* Match outbound or outbound.xxx */ +++- if (!STRNICMP(de->d_name, curr_domain->dir, len) && (de->d_name[len] == 0 || (de->d_name[len] == '.' && isxdigit(de->d_name[len + 1])))) +++- { +++- strnzcpy(outb_path, base_path, sizeof(outb_path)); +++- strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); +++- strnzcat(outb_path, de->d_name, sizeof(outb_path)); +++- +++- Log(7, "cleanup_old_bsy (BSO): scanning outbound dir '%s'", outb_path); +++- +++- scan_and_delete_bsy_in_dir(outb_path, config); +++- } +++- } +++- +++- closedir(dp); +++- } +++- } +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/bsycleanup.h binkd_pgul/bsycleanup.h +++--- binkd/bsycleanup.h 2026-04-26 13:11:39.607856201 +0100 ++++++ binkd_pgul/bsycleanup.h 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,19 +0,0 @@ +++-/* +++- * bsycleanup.h -- Cleanup functions for .bsy/.csy/.try files +++- * +++- * bsycleanup.h is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#ifndef _BSYCLEANUP_H +++-#define _BSYCLEANUP_H +++- +++-#include "readcfg.h" +++- +++- +++-/* cleanup_old_bsy -- Clean up old .bsy/.csy/.try files at startup */ +++-void cleanup_old_bsy(BINKD_CONFIG *config); +++- +++-#endif /* _BSYCLEANUP_H */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/btypes.h binkd_pgul/btypes.h +++--- binkd/btypes.h 2026-04-25 20:39:58.604145925 +0100 ++++++ binkd_pgul/btypes.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -73,7 +73,6 @@ +++ int HC_flag; +++ int restrictIP; +++ int NP_flag; /* no proxy */ +++- int NC_flag; /* no compression */ +++ +++ time_t hold_until; +++ int busy; /* 0=free, 'c'=.csy, other=.bsy */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/client.c binkd_pgul/client.c +++--- binkd/client.c 2026-04-26 10:25:03.436098652 +0100 ++++++ binkd_pgul/client.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -19,10 +19,6 @@ +++ #include +++ #endif +++ +++-#ifdef AMIGA +++-#include "amiga/bsdsock.h" +++-#endif +++- +++ #include "sys.h" +++ #include "readcfg.h" +++ #include "client.h" +++@@ -48,11 +44,6 @@ +++ #include "rfc2553.h" +++ #include "srv_gai.h" +++ +++-#if defined(HAVE_THREADS) || defined(AMIGA) +++-extern MUTEXSEM lsem; +++-extern EVENTSEM eothread; +++-#endif +++- +++ static void call (void *arg); +++ +++ int n_clients = 0; +++@@ -211,8 +202,7 @@ +++ /* This sleep can be interrupted by signal, it's OK */ +++ unblocksig(); +++ check_child(&n_clients); +++- if (!config->no_call_delay) +++- SLEEP (config->call_delay); ++++ SLEEP (config->call_delay); +++ check_child(&n_clients); +++ blocksig(); +++ } +++@@ -292,16 +282,8 @@ +++ #ifdef HAVE_THREADS +++ !server_flag && +++ #endif +++- /* AmigaOS uses shared-memory evloop — only main process calls checkcfg() +++- * On fork systems (Linux/FreeBSD) each process has separate memory +++- * so independent reloads are safe. */ +++ !poll_flag) +++-#ifndef AMIGA +++ checkcfg(); +++-#else +++- { +++- } +++-#endif +++ } +++ +++ Log (5, "downing clientmgr..."); +++@@ -324,7 +306,7 @@ +++ exit (0); +++ } +++ +++-int call0 (FTN_NODE *node, BINKD_CONFIG *config) ++++static int call0 (FTN_NODE *node, BINKD_CONFIG *config) +++ { +++ int sockfd = INVALID_SOCKET; +++ int sock_out; +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/client.h binkd_pgul/client.h +++--- binkd/client.h 2026-04-26 10:24:36.096878628 +0100 ++++++ binkd_pgul/client.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -1,20 +1,9 @@ +++ #ifndef _client_h +++ #define _client_h +++ +++-#ifdef AMIGA +++-#include +++-#include +++-#include +++-#endif +++- +++ /* +++ * Scans queue, makes outbound ``call'', than calls protocol() +++ */ +++ void clientmgr(void *arg); +++ +++-#ifdef AMIGA +++-/* Direct outbound call for evloop.c (no-ixemul, no-threads build) */ +++-int call0(FTN_NODE *node, BINKD_CONFIG *config); +++-#endif +++- +++ #endif +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/Config.h binkd_pgul/Config.h +++--- binkd/Config.h 2026-04-25 19:53:34.520405599 +0100 ++++++ binkd_pgul/Config.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -14,8 +14,8 @@ +++ #ifndef _Config_h +++ #define _Config_h +++ +++-#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) + defined(AMIGA) == 0 +++-#error You must define HAVE_FORK, HAVE_THREADS, DOS, or AMIGA! ++++#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) == 0 ++++#error You must define either HAVE_FORK or HAVE_THREADS! +++ #endif +++ +++ #ifdef __WATCOMC__ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/exitproc.c binkd_pgul/exitproc.c +++--- binkd/exitproc.c 2026-04-26 10:21:07.372917687 +0100 ++++++ binkd_pgul/exitproc.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -32,17 +32,6 @@ +++ #include "nt/w32tools.h" +++ #endif +++ +++-#if defined(HAVE_THREADS) || defined(AMIGA) +++-extern MUTEXSEM hostsem; +++-extern MUTEXSEM resolvsem; +++-extern MUTEXSEM lsem; +++-extern MUTEXSEM blsem; +++-extern MUTEXSEM varsem; +++-extern MUTEXSEM config_sem; +++-extern EVENTSEM eothread; +++-extern EVENTSEM wakecmgr; +++-#endif +++- +++ int binkd_exit; +++ +++ #ifdef HAVE_THREADS +++@@ -149,33 +138,6 @@ +++ close_srvmgr_socket(); +++ #endif +++ +++-#ifdef AMIGA +++- /* evloop: single process, no children +++- * Clean Exec semaphores in safe order before freeing config */ +++- close_srvmgr_socket(); +++- CleanEventSem(&wakecmgr); +++- CleanEventSem(&eothread); +++- CleanSem(&varsem); +++- CleanSem(&blsem); +++- CleanSem(&lsem); +++- CleanSem(&resolvsem); +++- CleanSem(&hostsem); +++- CleanSem(&config_sem); +++- sock_deinit(); +++- nodes_deinit(); +++- { +++- BINKD_CONFIG *cfg = lock_current_config(); +++- if (cfg) +++- bsy_remove_all(cfg); +++- if (cfg && *cfg->pid_file && pidsmgr == (int)getpid()) +++- delete(cfg->pid_file); +++- if (cfg) +++- unlock_config_structure(cfg, 1); +++- } +++- Log(6, "exitfunc: AmigaOS cleanup done"); +++- return; +++-#endif /* AMIGA */ +++- +++ config = lock_current_config(); +++ if (config) +++ bsy_remove_all (config); +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnnode.c binkd_pgul/ftnnode.c +++--- binkd/ftnnode.c 2026-04-26 09:22:13.159382256 +0100 ++++++ binkd_pgul/ftnnode.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -74,7 +74,7 @@ +++ */ +++ static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, +++ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, +++- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, ++++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, +++ int IP_afamily, +++ #ifdef BW_LIM +++ long bw_send, long bw_recv, +++@@ -107,7 +107,6 @@ +++ pn->NR_flag = NR_OFF; +++ pn->ND_flag = ND_OFF; +++ pn->NP_flag = NP_OFF; +++- pn->NC_flag = NC_OFF; +++ pn->MD_flag = MD_USE_OLD; +++ pn->HC_flag = HC_USE_OLD; +++ pn->pipe = NULL; +++@@ -135,8 +134,6 @@ +++ pn->ND_flag = ND_flag; +++ if (NP_flag != NP_USE_OLD) +++ pn->NP_flag = NP_flag; +++- if (NC_flag != NC_USE_OLD) +++- pn->NC_flag = NC_flag; +++ if (HC_flag != HC_USE_OLD) +++ pn->HC_flag = HC_flag; +++ if (IP_afamily != AF_USE_OLD) +++@@ -198,7 +195,7 @@ +++ +++ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, +++ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, +++- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, ++++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, +++ int IP_afamily, +++ #ifdef BW_LIM +++ long bw_send, long bw_recv, +++@@ -212,7 +209,7 @@ +++ +++ locknodesem(); +++ pn = add_node_nolock(fa, hosts, pwd, pkt_pwd, out_pwd, obox_flvr, obox, ibox, +++- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, ++++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, +++ IP_afamily, +++ #ifdef BW_LIM +++ bw_send, bw_recv, +++@@ -278,7 +275,6 @@ +++ on->ND_flag=np->ND_flag; +++ on->MD_flag=np->MD_flag; +++ on->NP_flag=np->NP_flag; +++- on->NC_flag=np->NC_flag; +++ on->HC_flag=np->HC_flag; +++ on->restrictIP=np->restrictIP; +++ on->pipe=np->pipe; +++@@ -294,7 +290,7 @@ +++ +++ add_node_nolock(fa, np->hosts, NULL, NULL, NULL, np->obox_flvr, np->obox, +++ np->ibox, np->NR_flag, np->ND_flag, np->MD_flag, np->restrictIP, +++- np->HC_flag, np->NP_flag, np->NC_flag, np->pipe, np->IP_afamily, ++++ np->HC_flag, np->NP_flag, np->pipe, np->IP_afamily, +++ #ifdef BW_LIM +++ np->bw_send, np->bw_recv, +++ #endif +++@@ -403,7 +399,7 @@ +++ if (!get_node_info_nolock (&target, config)) +++ add_node_nolock (&target, "*", NULL, NULL, NULL, '-', NULL, NULL, +++ NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, +++- HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, ++++ HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, +++ #ifdef BW_LIM +++ BW_DEF, BW_DEF, +++ #endif +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnnode.h binkd_pgul/ftnnode.h +++--- binkd/ftnnode.h 2026-04-25 20:40:18.652899844 +0100 ++++++ binkd_pgul/ftnnode.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -36,7 +36,7 @@ +++ */ +++ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, +++ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, +++- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, ++++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, +++ int IP_afamily, +++ #ifdef BW_LIM +++ long bw_send, long bw_recv, +++@@ -75,10 +75,6 @@ +++ #define NP_OFF 0 +++ #define NP_USE_OLD -1 /* Use old value */ +++ +++-#define NC_ON 1 +++-#define NC_OFF 0 +++-#define NC_USE_OLD -1 /* Use old value */ +++- +++ #define AF_USE_OLD -1 /* Use old value */ +++ +++ #ifdef BW_LIM +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnq.c binkd_pgul/ftnq.c +++--- binkd/ftnq.c 2026-04-26 10:34:47.939032694 +0100 ++++++ binkd_pgul/ftnq.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -1147,8 +1147,7 @@ +++ if (*buf) +++ { +++ strnzcat (buf, ".try", sizeof (buf)); +++- /* Delete only if the file exists */ +++- if (stat(buf, &sb) == 0) +++- delete (buf); ++++ if (stat(buf, &sb) == -1) return; ++++ delete (buf); +++ } +++ } +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/https.c binkd_pgul/https.c +++--- binkd/https.c 2026-04-22 21:36:50.649712064 +0100 ++++++ binkd_pgul/https.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -318,11 +318,7 @@ +++ buf[1]=1; +++ lockhostsem(); +++ Log (4, strcmp(port, config->oport) == 0 ? "trying %s..." : "trying %s:%u...", +++-#ifdef AMIGA +++- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr.s_addr), portnum); +++-#else +++- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); +++-#endif ++++ inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); +++ releasehostsem(); +++ buf[2]=(unsigned char)((portnum>>8)&0xFF); +++ buf[3]=(unsigned char)(portnum&0xFF); +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/inbound.c binkd_pgul/inbound.c +++--- binkd/inbound.c 2026-04-26 09:25:07.283995051 +0100 ++++++ binkd_pgul/inbound.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -49,9 +49,7 @@ +++ char node[FTN_ADDR_SZ + 1]; +++ +++ strnzcpy (s, inbound, MAXPATHLEN); +++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ +++- if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) +++- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); ++++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); +++ t = s + strlen (s); +++ while (1) +++ { +++@@ -130,9 +128,7 @@ +++ } +++ +++ strnzcpy (s, inbound, MAXPATHLEN); +++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ +++- if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) +++- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); ++++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); +++ t = s + strlen (s); +++ while ((de = readdir (dp)) != 0) +++ { +++@@ -474,9 +470,7 @@ +++ } +++ +++ strnzcpy (real_name, state->inbound, MAXPATHLEN); +++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ +++- if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) +++- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); ++++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); +++ s = real_name + strlen (real_name); +++ strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); +++ free (u); +++@@ -547,9 +541,7 @@ +++ { +++ ren_style = RENAME_POSTFIX; +++ strnzcpy (real_name, state->inbound, MAXPATHLEN); +++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ +++- if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) +++- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); ++++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); +++ s = real_name + strlen (real_name); +++ strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); +++ free (u); +++@@ -599,9 +591,7 @@ +++ struct stat sb; +++ +++ strnzcpy (fp, inbound, MAXPATHLEN); +++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ +++- if (strlen(fp) > 0 && fp[strlen(fp) - 1] != PATH_SEPARATOR[0]) +++- strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); ++++ strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); +++ s = fp + strlen (fp); +++ strnzcat (fp, u = strdequote (filename), MAXPATHLEN); +++ free (u); +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iphdr.h binkd_pgul/iphdr.h +++--- binkd/iphdr.h 2026-04-26 09:25:41.562664644 +0100 ++++++ binkd_pgul/iphdr.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -124,17 +124,9 @@ +++ #define TCPERRNO errno +++ #define TCPERR_WOULDBLOCK EWOULDBLOCK +++ #define TCPERR_AGAIN EAGAIN +++- #ifdef AMIGA +++- /* AmigaOS 3: open bsdsocket.library via amiga/bsdsock.c */ +++- #include "amiga/bsdsock.h" +++- #define sock_init() amiga_sock_init() +++- #define sock_deinit() amiga_sock_cleanup() +++- #define soclose(h) CloseSocket(h) +++- #else +++- #define sock_init() 0 +++- #define sock_deinit() +++- #define soclose(h) close(h) +++- #endif ++++ #define sock_init() 0 ++++ #define sock_deinit() ++++ #define soclose(h) close(h) +++ #endif +++ +++ #if !defined(WIN32) +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iptools.c binkd_pgul/iptools.c +++--- binkd/iptools.c 2026-04-26 10:33:19.517261538 +0100 ++++++ binkd_pgul/iptools.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -20,10 +20,6 @@ +++ #include +++ #endif +++ +++-#ifdef AMIGA +++-#include +++-#endif +++- +++ #include "sys.h" +++ #include "Config.h" +++ #include "iphdr.h" +++@@ -44,11 +40,7 @@ +++ int arg; +++ +++ arg = 1; +++-#if defined(AMIGA) +++- if (ioctl (s, FIONBIO, (char *) &arg) < 0) +++-#else +++ if (ioctl (s, FIONBIO, (char *) &arg, sizeof arg) < 0) +++-#endif +++ Log (1, "ioctl (FIONBIO): %s", TCPERR ()); +++ +++ #elif defined(WIN32) +++@@ -61,49 +53,12 @@ +++ #endif +++ #endif +++ +++-#if defined(UNIX) || defined(EMX) /* NOT AMIGA: sockets are not AmigaDOS fds */ ++++#if defined(UNIX) || defined(EMX) || defined(AMIGA) +++ if (fcntl (s, F_SETFL, O_NONBLOCK) == -1) +++ Log (1, "fcntl: %s", strerror (errno)); +++ #endif +++ } +++ +++-#if defined(AMIGA) +++-void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf) +++-{ +++- /* Disable Nagle algorithm: BinkP mixes small control messages with data +++- * Without TCP_NODELAY each small message waits up to 200ms (Nagle delay), +++- * making sessions 2-5x slower than other BinkP implementations +++- * All other BinkP mailers (BinkIT, Argus, etc.) set this explicitly */ +++- +++- if (tcpdelay) +++- { +++- int nodelay = tcpdelay; +++- +++- if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)) < 0) +++- Log (4, "setsockopt TCP_NODELAY: %s", TCPERR()); +++- } +++- +++- /* ixnet default TCP buffers are very small (~8KB). Increase them so the +++- * sender does not stall waiting for ACK after every small burst */ +++- +++- if (so_sndbuf) +++- { +++- int sndbuf = so_sndbuf; +++- +++- if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, sizeof(sndbuf)) < 0) +++- Log (5, "setsockopt SO_SNDBUF: %s", TCPERR()); +++- } +++- +++- if (so_rcvbuf) +++- { +++- int rcvbuf = so_rcvbuf; +++- +++- if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) < 0) +++- Log (5, "setsockopt SO_RCVBUF: %s", TCPERR()); +++- } +++-} +++-#endif +++- +++ /* +++ * Find the appropriate port string to be used. +++ * Find_port ("") will return binkp's port from /etc/services or even +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iptools.h binkd_pgul/iptools.h +++--- binkd/iptools.h 2026-04-26 09:26:42.811498024 +0100 ++++++ binkd_pgul/iptools.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -11,21 +11,11 @@ +++ * (at your option) any later version. See COPYING. +++ */ +++ +++-#if defined(AMIGA) +++-#include +++-#include +++-#include +++-#endif +++- +++ /* +++ * Sets non-blocking mode for a given socket +++ */ +++ void setsockopts (SOCKET s); +++ +++-#if defined(AMIGA) +++-void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf); +++-#endif +++- +++ /* +++ * Find the port number (in the host byte order) by a port number string or +++ * a service name. Find_port ("") will return binkp's port from +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/decompress.c binkd_pgul/misc/decompress.c +++--- binkd/misc/decompress.c 2026-04-26 13:39:53.974576140 +0100 ++++++ binkd_pgul/misc/decompress.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,188 +0,0 @@ +++-/* +++- * decompress.c -- Decompress FTN bundle archives from an inbound directory +++- * +++- * decompress.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include "portable.h" /* Canonical portable layer */ +++-#include +++- +++-#define MAX_CMD 1100 +++- +++-/* Archive format codes detected by magic bytes */ +++-#define FMT_UNKNOWN 0 +++-#define FMT_ZIP 1 +++-#define FMT_LZH 2 +++-#define FMT_ARC 3 +++- +++-/* detect_format -- Read first bytes and identify archive type */ +++-static int detect_format(const char *path) +++-{ +++- unsigned char buf[8]; +++- FILE *f = fopen(path, "rb"); +++- int n; +++- +++- if (!f) +++- return FMT_UNKNOWN; +++- +++- n = (int)fread(buf, 1, sizeof(buf), f); +++- +++- fclose(f); +++- +++- if (n < 2) +++- return FMT_UNKNOWN; +++- +++- /* ZIP: PK\x03\x04 */ +++- if (n >= 4 && buf[0] == 0x50 && buf[1] == 0x4B && buf[2] == 0x03 && buf[3] == 0x04) +++- return FMT_ZIP; +++- +++- /* LZH: offset 2 = '-', offset 3 = 'l', offset 6 = '-' (e.g. -lh5-) */ +++- if (n >= 7 && buf[2] == '-' && buf[3] == 'l' && buf[6] == '-') +++- return FMT_LZH; +++- +++- /* ARC: 0x1A followed by type byte 1..18 */ +++- if (buf[0] == 0x1A && buf[1] >= 1 && buf[1] <= 18) +++- return FMT_ARC; +++- +++- return FMT_UNKNOWN; +++-} +++- +++-/* is_ftn_bundle -- Check filename has an FTN day-of-week extension */ +++-static int is_ftn_bundle(const char *filename) +++-{ +++- const char *p; +++- +++- for (p = filename; *p; p++) +++- { +++- if (p[0] == '.' && p[1] && p[2]) +++- { +++- char a = (char)tolower((unsigned char)p[1]); +++- char b = (char)tolower((unsigned char)p[2]); +++- +++- if ((a == 's' && b == 'u') || (a == 'm' && b == 'o') || +++- (a == 't' && b == 'u') || (a == 'w' && b == 'e') || +++- (a == 't' && b == 'h') || (a == 'f' && b == 'r') || +++- (a == 's' && b == 'a')) +++- { +++- /* .TH .TH0 .TH.001 */ +++- if (p[3] == '\0' || isdigit((unsigned char)p[3]) || p[3] == '.') +++- return 1; +++- } +++- } +++- } +++- return 0; +++-} +++- +++-/* delete_file -- Remove a file, portable */ +++-static void delete_file(const char *path) +++-{ +++-#if defined(AMIGA) +++- DeleteFile((STRPTR)path); +++-#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) +++- DeleteFileA(path); +++-#else +++- remove(path); +++-#endif +++-} +++- +++-/* run_decompressor -- Invoke external tool for the detected format +++- * outdir must end without trailing slash on POSIX; lha needs trailing / */ +++-static int run_decompressor(int fmt, const char *path, const char *outdir) +++-{ +++- char cmd[MAX_CMD]; +++- +++- switch (fmt) +++- { +++- case FMT_ZIP: +++-#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) +++- snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); +++-#else +++- snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); +++-#endif +++- break; +++- +++- case FMT_LZH: +++-#ifdef AMIGA +++- snprintf(cmd, MAX_CMD, "lha x \"%s\" \"%s/\"", path, outdir); +++-#else +++- snprintf(cmd, MAX_CMD, "lha e \"%s\" \"%s/\"", path, outdir); +++-#endif +++- break; +++- +++- case FMT_ARC: +++-#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) +++- snprintf(cmd, MAX_CMD, "arc x \"%s\" \"%s\"", path, outdir); +++-#else +++- snprintf(cmd, MAX_CMD, "cd \"%s\" && arc x \"%s\"", outdir, path); +++-#endif +++- break; +++- +++- default: +++- return -1; +++- } +++- +++- return system(cmd); +++-} +++- +++-int main(int argc, char *argv[]) +++-{ +++- DIR *dp; +++- struct dirent *entry; +++- char path[MAXPATHLEN]; +++- const char *inbound; +++- const char *outdir; +++- int total = 0; +++- int ok = 0; +++- +++- if (argc < 3) +++- { +++- fprintf(stderr, +++- "Usage: decompress \n" +++- "Detects format by magic bytes (ZIP/LZH/ARC).\n" +++- "Processes FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA).\n"); +++- +++- return 1; +++- } +++- +++- inbound = argv[1]; +++- outdir = argv[2]; +++- +++- dp = opendir(inbound); +++- +++- if (dp == NULL) +++- return 1; +++- +++- while ((entry = readdir(dp)) != NULL) +++- { +++- int fmt; +++- +++- /* Skip . and .. (AmigaOS readdir does not return these, POSIX does) */ +++- if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) +++- continue; +++- +++- if (!is_ftn_bundle(entry->d_name)) +++- continue; +++- +++- path_join(path, MAXPATHLEN, inbound, entry->d_name); +++- +++- fmt = detect_format(path); +++- +++- if (fmt == FMT_UNKNOWN) +++- continue; +++- +++- total++; +++- +++- if (run_decompressor(fmt, path, outdir) == 0) +++- { +++- delete_file(path); +++- ok++; +++- } +++- } +++- +++- closedir(dp); +++- +++- return (total == 0 || ok == total) ? 0 : 1; +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/decompress.txt binkd_pgul/misc/decompress.txt +++--- binkd/misc/decompress.txt 2026-04-26 13:44:08.749250093 +0100 ++++++ binkd_pgul/misc/decompress.txt 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,36 +0,0 @@ +++-decompress -- Decompress FTN bundle archives +++- +++-USAGE: +++- decompress +++- +++-DESCRIPTION: +++- Scans the inbound directory for FTN day-of-week bundle archives and +++- decompresses them to the output directory. +++- +++- Recognized archive formats (by magic bytes, not extension): +++- - ZIP files +++- - LZH files +++- - ARC files +++- +++- Recognized bundle extensions (case-insensitive): +++- - .SU - Sunday bundle +++- - .MO - Monday bundle +++- - .TU - Tuesday bundle +++- - .WE - Wednesday bundle +++- - .TH - Thursday bundle +++- - .FR - Friday bundle +++- - .SA - Saturday bundle +++- +++- Also handles renamed bundles (duplicates): +++- - ABCD1234.SU +++- - ABCD1234.SU.001 +++- - ABCD1234.SU.002 +++- +++-EXAMPLES: +++- decompress Work:Inbound Work:Unpacked +++- decompress /var/spool/binkd/inbound /tmp/unpacked +++- decompress C:\Binkd\Inbound C:\Binkd\Unpacked +++- exec "work:fido/decompress work:fido/inbound work:fido/inbound" *.su? *.mo? *.tu? *.we? *.th? *.fr? *.sa? *.SU? *.MO? *.TU? *.WE? *.TH? *.FR? *.SA? +++- +++-CONFIGURATION FILE: +++- None. All parameters are command-line arguments. +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/freq.c binkd_pgul/misc/freq.c +++--- binkd/misc/freq.c 2026-04-26 13:39:53.974576140 +0100 ++++++ binkd_pgul/misc/freq.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,220 +0,0 @@ +++-/* +++- * freq.c -- Append a file-request entry to an outbound .req / .clo pair +++- * +++- * freq.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include "portable.h" /* Canonical portable layer */ +++- +++-#define FREQ_MAX_PATH (MAXPATHLEN + 1) +++- +++-/* build_aso_paths -- ASO flat layout */ +++-static int build_aso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) +++-{ +++- char *dot; +++- +++- if (mkdir_recursive(outbound) < 0 && !path_exists(outbound)) +++- return -1; +++- +++- snprintf(req_path, (size_t)pathsize, "%s/%u.%u.%u.%u.req", outbound, zone, net, node, point); +++- safe_strncpy(clo_path, req_path, pathsize); +++- dot = strrchr(clo_path, '.'); +++- +++- if (dot) +++- strcpy(dot, ".clo"); +++- +++- return 0; +++-} +++- +++-/* build_bso_paths -- BSO BinkleyStyle layout (lowercase hex) */ +++-static int build_bso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) +++-{ +++- char zone_dir[FREQ_MAX_PATH]; +++- char node_dir[FREQ_MAX_PATH]; +++- +++- /* Zone dir: .0ZZ (lowercase hex) */ +++- snprintf(zone_dir, sizeof(zone_dir), "%s.%03x", outbound, zone); +++- str_tolower(zone_dir); +++- +++- if (mkdir_recursive(zone_dir) < 0 && !path_exists(zone_dir)) +++- return -1; +++- +++- if (point == 0) +++- { +++- snprintf(req_path, (size_t)pathsize, "%s/%04x%04x.req", zone_dir, net, node); +++- snprintf(clo_path, (size_t)pathsize, "%s/%04x%04x.clo", zone_dir, net, node); +++- } +++- else +++- { +++- snprintf(node_dir, sizeof(node_dir), "%s/%04x%04x.pnt", zone_dir, net, node); +++- +++- if (mkdir_recursive(node_dir) < 0 && !path_exists(node_dir)) +++- return -1; +++- +++- snprintf(req_path, (size_t)pathsize, "%s/%08x.req", node_dir, point); +++- snprintf(clo_path, (size_t)pathsize, "%s/%08x.clo", node_dir, point); +++- } +++- +++- return 0; +++-} +++- +++-int main(int argc, char *argv[]) +++-{ +++- unsigned int zone, net, node, point; +++- char addr_copy[128]; +++- char req_path[FREQ_MAX_PATH]; +++- char clo_path[FREQ_MAX_PATH]; +++- char abs_outbound[FREQ_MAX_PATH]; +++- FILE *f; +++- const char *outbound; +++- const char *arg_outbound; +++- const char *arg_addr; +++- const char *password = NULL; /* --password → !pass suffix */ +++- long newer_than = 0; /* --newer-than → +ts suffix */ +++- int update = 0; /* --update → U suffix */ +++- int use_bso = 0; +++- int argi = 1; +++- int nfiles = 0; +++- +++- zone = net = node = point = 0; +++- +++- /* Parse flags -- All optional, order-independent, before positional args */ +++- while (argi < argc && argv[argi][0] == '-' && argv[argi][1] == '-') +++- { +++- if (strcmp(argv[argi], "--bso") == 0) +++- { +++- use_bso = 1; +++- argi++; +++- } +++- else if (strcmp(argv[argi], "--aso") == 0) +++- { +++- use_bso = 0; +++- argi++; +++- } +++- else if (strcmp(argv[argi], "--update") == 0) +++- { +++- update = 1; +++- argi++; +++- } +++- else if (strcmp(argv[argi], "--password") == 0 && argi + 1 < argc) +++- { +++- password = argv[++argi]; +++- argi++; +++- } +++- else if (strcmp(argv[argi], "--newer-than") == 0 && argi + 1 < argc) +++- { +++- newer_than = atol(argv[++argi]); +++- argi++; +++- } +++- else +++- break; /* unknown flag — stop, treat rest as positional */ +++- } +++- +++- if (argc - argi < 3) +++- { +++- fprintf(stderr, +++- "Usage: freq [options] Z:N/NODE[.POINT] [...]\n" +++- "Options:\n" +++- " --aso flat layout (default): outbound/Z.N.NODE.POINT.req\n" +++- " --bso BSO layout: outbound.0ZZ/nnnnnnnn[.pnt/pppppppp].req\n" +++- " --password append !pw to each request line\n" +++- " --newer-than append + (request if newer)\n" +++- " --update append U flag (update request)\n" +++- "Multiple filenames can be listed after the address.\n"); +++- return 1; +++- } +++- +++- arg_outbound = argv[argi++]; +++- arg_addr = argv[argi++]; +++- +++- /* Remaining args are filenames */ +++- +++- make_abs_path(arg_outbound, abs_outbound, (int)sizeof(abs_outbound)); +++- outbound = abs_outbound; +++- +++- safe_strncpy(addr_copy, arg_addr, (int)sizeof(addr_copy)); +++- +++- if (sscanf(addr_copy, "%u:%u/%u.%u", &zone, &net, &node, &point) < 3 && sscanf(addr_copy, "%u:%u/%u", &zone, &net, &node) < 3) +++- { +++- fprintf(stderr, "freq: invalid address: %s\n", arg_addr); +++- return 1; +++- } +++- +++- if (use_bso) +++- { +++- if (build_bso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) +++- { +++- fprintf(stderr, "freq: cannot create BSO dirs under: %s\n", outbound); +++- return 1; +++- } +++- } +++- else +++- { +++- if (build_aso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) +++- { +++- fprintf(stderr, "freq: cannot create outbound dir: %s\n", outbound); +++- return 1; +++- } +++- } +++- +++- /* Append all filenames to .req */ +++- f = fopen(req_path, "a"); +++- +++- if (!f) +++- { +++- fprintf(stderr, "freq: cannot open REQ: %s\n", req_path); +++- return 1; +++- } +++- +++- for (; argi < argc; argi++) +++- { +++- const char *fname = argv[argi]; +++- +++- /* Build request line: filename [!password] [+timestamp] [U] */ +++- fprintf(f, "%s", fname); +++- +++- if (password && password[0]) +++- fprintf(f, " !%s", password); +++- +++- if (newer_than > 0) +++- fprintf(f, " +%ld", newer_than); +++- +++- if (update) +++- fprintf(f, " U"); +++- +++- fprintf(f, "\r\n"); +++- nfiles++; +++- } +++- +++- fclose(f); +++- +++- if (nfiles == 0) +++- { +++- fprintf(stderr, "freq: no filenames specified\n"); +++- return 1; +++- } +++- +++- /* Append .req full path to .clo (once per invocation) */ +++- f = fopen(clo_path, "a"); +++- +++- if (!f) +++- { +++- fprintf(stderr, "freq: cannot open CLO: %s\n", clo_path); +++- return 1; +++- } +++- +++- fprintf(f, "%s\r\n", req_path); +++- fclose(f); +++- +++- printf("freq (%s): node %u:%u/%u", use_bso ? "bso" : "aso", zone, net, node); +++- +++- if (point) +++- printf(".%u", point); +++- +++- printf(" %d file(s)\n REQ : %s\n CLO : %s\n", nfiles, req_path, clo_path); +++- +++- return 0; +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/freq.txt binkd_pgul/misc/freq.txt +++--- binkd/misc/freq.txt 2026-04-26 13:33:14.698749181 +0100 ++++++ binkd_pgul/misc/freq.txt 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,37 +0,0 @@ +++-freq -- Create .req and .clo request files for FidoNet +++- +++-USAGE: +++- freq [options] [...] +++- +++-DESCRIPTION: +++- Creates file request (.req) and close (.clo) files in the outbound +++- directory using FidoNet 4D/5D addressing. +++- +++- Address format: +++- Zone:Net/Node - 4D address (e.g., 39:190/101) +++- Zone:Net/Node.Point - 5D address with point (e.g., 39:190/101.1) +++- +++- Multiple files can be specified to create multiple request lines. +++- +++-OPTIONS: +++- --aso Amiga Style Outbound (default) - flat layout +++- Format: /Z.N.NODE.POINT.req +++- +++- --bso Binkley Style Outbound - hex directory layout +++- No point: .0ZZ/nnnnnnnn.req +++- Point: .0ZZ/nnnnnnnn.pnt/pppppppp.req +++- +++- --password Append !pw suffix to each request line +++- --newer-than Append + (request only if file is newer) +++- --update Append U flag (update request, checks TRANX) +++- +++-EXAMPLES: +++- freq Work:Outbound 39:190/101 file.lha +++- freq --aso Work:Outbound 39:190/101.1 readme.txt +++- freq --bso /var/spool/binkd/outbound 2:123/456 door.zip +++- freq --bso C:\Binkd\Outbound 1:100/200 update.lzh +++- freq --password secret --newer-than 1234567890 Work:Outbound 39:190/101 file.zip +++- freq --update Work:Outbound 39:190/101 file1.zip file2.zip file3.zip +++- +++-CONFIGURATION FILE: +++- None. All parameters are command-line arguments. +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/nodelist.c binkd_pgul/misc/nodelist.c +++--- binkd/misc/nodelist.c 2026-04-26 13:39:53.974576140 +0100 ++++++ binkd_pgul/misc/nodelist.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,200 +0,0 @@ +++-/* +++- * nodelist.c -- FidoNet nodelist compiler for binkd +++- * +++- * nodelist.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include "portable.h" /* Canonical portable layer */ +++-#include +++-#include +++-#include +++-#include +++- +++-#define MAX_FIELDS 20 +++-#define MAX_VAL 256 +++- +++-static int split_fields(char *buf, char **fields, int maxfields) +++-{ +++- int n = 0; +++- char *p = buf; +++- +++- while (n < maxfields) +++- { +++- fields[n++] = p; +++- p = strchr(p, ','); +++- +++- if (!p) +++- break; +++- +++- *p++ = '\0'; +++- } +++- +++- return n; +++-} +++- +++-/* Case-insensitive flag search. Returns 1 if found; fills val if present */ +++-static int find_flag(char **fields, int nfields, int start, const char *flag, char *val, int vsize) +++-{ +++- int flen = (int)strlen(flag); +++- int i; +++- +++- for (i = start; i < nfields; i++) +++- { +++- char *f = fields[i]; +++- int j; +++- +++- for (j = 0; j < flen; j++) +++- { +++- if (toupper((unsigned char)f[j]) != toupper((unsigned char)flag[j])) +++- break; +++- } +++- +++- if (j == flen) +++- { +++- if (val && vsize > 0) +++- { +++- if (f[flen] == ':') +++- { +++- strncpy(val, f + flen + 1, (size_t)(vsize - 1)); +++- val[vsize - 1] = '\0'; +++- } +++- else +++- val[0] = '\0'; +++- } +++- return 1; +++- } +++- } +++- return 0; +++-} +++- +++-int main(int argc, char *argv[]) +++-{ +++- const char *nl_file; +++- const char *domain; +++- FILE *in; +++- FILE *out; +++- char buf[MAX_LINE]; +++- char *fields[MAX_FIELDS]; +++- int nf; +++- int cur_zone; +++- int cur_net; +++- long count; +++- +++- cur_zone = 0; +++- cur_net = 0; +++- count = 0; +++- +++- if (argc < 3) +++- { +++- fprintf(stderr, +++- "Usage: nodelist []\n"); +++- return 1; +++- } +++- +++- nl_file = argv[1]; +++- domain = argv[2]; +++- out = (argc >= 4) ? fopen(argv[3], "w") : stdout; +++- +++- if (!out) +++- { +++- perror(argv[3]); +++- return 1; +++- } +++- +++- in = fopen(nl_file, "r"); +++- +++- if (!in) +++- { +++- perror(nl_file); +++- +++- if (out != stdout) +++- fclose(out); +++- +++- return 1; +++- } +++- +++- while (fgets(buf, sizeof(buf), in)) +++- { +++- char type[32]; +++- char ibn_port[32]; +++- char ina_host[MAX_VAL]; +++- int node_num; +++- int port; +++- int flags_start; +++- +++- str_trim(buf); +++- +++- if (!buf[0] || buf[0] == ';') +++- continue; +++- +++- nf = split_fields(buf, fields, MAX_FIELDS); +++- +++- if (nf < 2) +++- continue; +++- +++- if (fields[0][0] == '\0') +++- { +++- /* Line started with comma -- plain Node entry */ +++- strcpy(type, "Node"); +++- node_num = atoi(fields[1]); +++- flags_start = 7; +++- } +++- else +++- { +++- strncpy(type, fields[0], sizeof(type) - 1); +++- type[sizeof(type) - 1] = '\0'; +++- node_num = atoi(fields[1]); +++- flags_start = 7; +++- } +++- +++- /* Update zone / net context */ +++- if (!strcmp(type, "Zone") || !strcmp(type, "ZONE")) +++- { +++- cur_zone = node_num; +++- cur_net = node_num; +++- continue; +++- } +++- +++- if (!strcmp(type, "Region") || !strcmp(type, "REGION")) +++- { +++- cur_net = node_num; +++- continue; +++- } +++- +++- if (!strcmp(type, "Host") || !strcmp(type, "HOST")) +++- cur_net = node_num; +++- +++- /* Skip unusable types */ +++- if (!strcmp(type, "Pvt") || !strcmp(type, "PVT") || !strcmp(type, "Hold") || !strcmp(type, "HOLD") || !strcmp(type, "Down") || !strcmp(type, "DOWN") || !strcmp(type, "Boss") || !strcmp(type, "BOSS")) +++- continue; +++- +++- /* Must have IBN flag */ +++- if (!find_flag(fields, nf, flags_start, "IBN", ibn_port, (int)sizeof(ibn_port))) +++- continue; +++- +++- /* Need INA:hostname */ +++- ina_host[0] = '\0'; +++- find_flag(fields, nf, flags_start, "INA", ina_host, (int)sizeof(ina_host)); +++- +++- if (!ina_host[0]) +++- continue; +++- +++- port = (ibn_port[0] && atoi(ibn_port) > 0) ? atoi(ibn_port) : 24554; +++- +++- fprintf(out, "node %d:%d/%d@%s %s:%d -\n", cur_zone, cur_net, node_num, domain, ina_host, port); +++- +++- count++; +++- } +++- +++- fclose(in); +++- +++- if (out != stdout) +++- fclose(out); +++- +++- fprintf(stderr, "nodelist: %ld BinkP node(s) found\n", count); +++- +++- return 0; +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/nodelist.txt binkd_pgul/misc/nodelist.txt +++--- binkd/misc/nodelist.txt 2026-04-26 13:32:27.866048972 +0100 ++++++ binkd_pgul/misc/nodelist.txt 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,32 +0,0 @@ +++-nodelist -- Compile FidoNet nodelist to binkd.conf format +++- +++-USAGE: +++- nodelist [] +++- +++-DESCRIPTION: +++- Reads a FidoNet nodelist and outputs binkd.conf compatible +++- "node" configuration lines. +++- +++- Arguments: +++- nodelist_file Path to the FidoNet nodelist file +++- domain Domain name for the node entries (e.g., fidonet) +++- output_file Optional output file (default: stdout) +++- +++- Extracts the following flags: +++- IBN[:port] - BinkP protocol flag (Internet BinkP Node) +++- INA:hostname - IP hostname/address +++- +++- Output format: +++- node
@ : - +++- +++- The nodelist format is comma-separated: +++- [type,]node_num,name,city,sysop,phone,baud,flag1,flag2,... +++- type = Zone, Region, Host, Hub, Pvt, Hold, Down, Boss (empty = Node) +++- +++-EXAMPLES: +++- nodelist Work:Fido/nodelist.123 fidonet > binkd-nodes.conf +++- nodelist /etc/fido/nodelist.456 fidonet >> binkd.conf +++- nodelist C:\Fido\NODELIST.001 fidonet C:\Fido\nodes.conf +++- +++-CONFIGURATION FILE: +++- None. All parameters are command-line arguments. +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/portable.c binkd_pgul/misc/portable.c +++--- binkd/misc/portable.c 2026-04-26 13:04:59.214902887 +0100 ++++++ binkd_pgul/misc/portable.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,402 +0,0 @@ +++-/* +++- * portable.c -- Shared implementations for misc tools portable layer +++- * +++- * portable.c is a part of binkd project +++- * +++- * This file provides implementations for common utility functions +++- * used across binkd misc tools. Include portable.h for declarations +++- * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- * +++- */ +++- +++-#include "portable.h" +++-#include +++- +++-/* trim_nl -- Strip trailing newline (\n and \r) from string */ +++-void trim_nl(char *s) +++-{ +++- char *p = strchr(s, '\n'); +++- +++- if (p) +++- *p = '\0'; +++- +++- p = strchr(s, '\r'); +++- +++- if (p) +++- *p = '\0'; +++-} +++- +++-/* str_trim -- Strip trailing whitespace (space, \r, \n) from string */ +++-void str_trim(char *s) +++-{ +++- int n = (int)strlen(s); +++- +++- while (n > 0 && (s[n - 1] == '\r' || s[n - 1] == '\n' || s[n - 1] == ' ')) +++- s[--n] = '\0'; +++-} +++- +++-/* str_upper -- Convert string to uppercase in-place */ +++-void str_upper(char *s) +++-{ +++- while (*s) +++- { +++- *s = (char)toupper((unsigned char)*s); +++- s++; +++- } +++-} +++- +++-/* str_tolower -- Convert string to lowercase in-place */ +++-void str_tolower(char *s) +++-{ +++- for (; *s; s++) +++- { +++- if (*s >= 'A' && *s <= 'Z') +++- *s = (char)(*s + ('a' - 'A')); +++- } +++-} +++- +++-/* skip_ws -- Skip leading whitespace */ +++-char *skip_ws(char *s) +++-{ +++- while (*s == ' ' || *s == '\t') +++- s++; +++- +++- return s; +++-} +++- +++-/* wildmatch -- Portable wildcard match: case-insensitive, supports * and ? */ +++-int wildmatch(const char *pat, const char *str) +++-{ +++- while (*pat) +++- { +++- if (*pat == '*') +++- { +++- while (*pat == '*') +++- pat++; +++- +++- if (!*pat) +++- return 1; +++- +++- while (*str) +++- { +++- if (wildmatch(pat, str++)) +++- return 1; +++- } +++- +++- return 0; +++- } +++- +++- if (*pat == '?') +++- { +++- if (!*str) +++- return 0; +++- +++- pat++; +++- str++; +++- } +++- else +++- { +++- if (toupper((unsigned char)*pat) != toupper((unsigned char)*str)) +++- return 0; +++- +++- pat++; +++- str++; +++- } +++- } +++- +++- return (*str == '\0') ? 1 : 0; +++-} +++- +++-/* is_wildcard -- True if name contains * or ? */ +++-int is_wildcard(const char *s) +++-{ +++- while (*s) +++- { +++- if (*s == '*' || *s == '?') +++- return 1; +++- +++- s++; +++- } +++- +++- return 0; +++-} +++- +++-/* ensure_dir -- Ensure directory exists, creating if necessary */ +++-int ensure_dir(const char *path) +++-{ +++- if (path_exists(path)) +++- return 1; +++- +++- return (mkdir_recursive(path) == 0) ? 1 : 0; +++-} +++- +++-/* copy_file -- Portable binary file copy */ +++-int copy_file(const char *src, const char *dst) +++-{ +++- FILE *in, *out; +++- char buf[4096]; +++- int n; +++- +++- in = fopen(src, "rb"); +++- +++- if (!in) +++- return 0; +++- +++- out = fopen(dst, "wb"); +++- +++- if (!out) +++- { +++- fclose(in); +++- return 0; +++- } +++- +++- while ((n = (int)fread(buf, 1, sizeof(buf), in)) > 0) +++- fwrite(buf, 1, (size_t)n, out); +++- +++- fclose(out); +++- fclose(in); +++- +++- return 1; +++-} +++- +++-/* move_file -- Try rename first, fall back to copy+delete */ +++-int move_file(const char *src, const char *dst) +++-{ +++- remove(dst); +++- +++- if (rename(src, dst) == 0) +++- return 1; +++- +++- if (copy_file(src, dst)) +++- { +++- remove(src); +++- return 1; +++- } +++- +++- return 0; +++-} +++- +++-/* get_file_size -- Return file size in bytes, or -1 on error */ +++-long get_file_size(const char *path) +++-{ +++- struct stat st; +++- +++- if (stat(path, &st) == 0) +++- return (long)st.st_size; +++- +++- return -1; +++-} +++- +++-/* get_file_mtime -- Return Unix mtime of a file, or 0 on error */ +++-long get_file_mtime(const char *path) +++-{ +++- struct stat st; +++- +++- if (stat(path, &st) != 0) +++- return 0; +++- +++- return (long)st.st_mtime; +++-} +++- +++-/* port_path_exists -- Check if path exists (native per OS) */ +++- +++-int port_path_exists(const char *p) +++-{ +++-#ifdef AMIGA +++- BPTR l = Lock((STRPTR)p, ACCESS_READ); +++- +++- if (l) +++- { +++- UnLock(l); +++- return 1; +++- } +++- +++- return 0; +++-#else +++- struct stat st; +++- return (stat(p, &st) == 0) ? 1 : 0; +++-#endif +++-} +++- +++-/* port_mkdir_one -- Create single directory (native per OS) */ +++-int port_mkdir_one(const char *p) +++-{ +++-#ifdef AMIGA +++- BPTR l = CreateDir((STRPTR)p); +++- +++- if (l) +++- { +++- UnLock(l); +++- return 0; +++- } +++- +++- return -1; +++-#else +++- return mkdir(p, 0755); +++-#endif +++-} +++- +++-/* safe_localtime -- Thread-safe localtime, portable across all OS */ +++-void safe_localtime(const time_t *t, struct tm *tm) +++-{ +++-#if defined(AMIGA) || defined(DOS) +++- *tm = *localtime(t); +++-#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) +++- localtime_s(tm, t); +++-#else +++- localtime_r(t, tm); +++-#endif +++-} +++- +++-/* mkdir_recursive -- Create full path, making all missing components */ +++-int mkdir_recursive(const char *path) +++-{ +++- char tmp[MP_MAXPATH]; +++- char *p; +++- int len; +++- +++- if (!path || !path[0]) +++- return -1; +++- +++- strncpy(tmp, path, MP_MAXPATH - 1); +++- tmp[MP_MAXPATH - 1] = '\0'; +++- +++- len = (int)strlen(tmp); +++- +++- /* Strip trailing slash */ +++- while (len > 1 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\')) +++- tmp[--len] = '\0'; +++- +++- /* Walk every '/' component and create missing dirs */ +++- for (p = tmp + 1; *p; p++) +++- { +++- if (*p == '/' || *p == '\\') +++- { +++- *p = '\0'; +++- +++- if (!path_exists(tmp)) +++- mkdir_one(tmp); /* ignore per-component errors */ +++- +++- *p = '/'; +++- } +++- } +++- +++- /* Create the leaf */ +++- if (!path_exists(tmp)) +++- return mkdir_one(tmp); +++- +++- return 0; +++-} +++- +++-/* safe_strncpy -- Ctrncpy that always NUL-terminates */ +++-void safe_strncpy(char *dst, const char *src, int dstsize) +++-{ +++- int len; +++- +++- if (dstsize <= 0) +++- return; +++- +++- len = (int)strlen(src); +++- +++- if (len > dstsize - 1) +++- len = dstsize - 1; +++- +++- memcpy(dst, src, (size_t)len); +++- dst[len] = '\0'; +++-} +++- +++-/* path_join -- Concatenate base path with sub path */ +++-void path_join(char *out, int outsize, const char *base, const char *sub) +++-{ +++- int blen; +++- char last; +++- +++- safe_strncpy(out, base, outsize); +++- blen = (int)strlen(out); +++- last = (blen > 0) ? out[blen - 1] : '\0'; +++- +++- if (last != '/' && last != ':' && last != '\\') +++- { +++- if (outsize - 1 - blen > 0) +++- { +++- out[blen] = '/'; +++- out[blen + 1] = '\0'; +++- blen++; +++- } +++- } +++- +++- safe_strncpy(out + blen, sub, outsize - blen); +++-} +++- +++-/* make_abs_path -- Resolve a possibly-relative path to absolute +++- * Covers AmigaOS, Win32, OS/2, DOS and Unix +++- * Returns 1 on success, 0 on failure (src copied verbatim as fallback) +++- */ +++-int make_abs_path(const char *src, char *dst, int dstlen) +++-{ +++-#ifdef AMIGA +++- BPTR lock = Lock((STRPTR)src, SHARED_LOCK); +++- +++- if (!lock) +++- { +++- safe_strncpy(dst, src, dstlen); +++- return 0; +++- } +++- +++- if (!NameFromLock(lock, (STRPTR)dst, dstlen)) +++- { +++- UnLock(lock); +++- safe_strncpy(dst, src, dstlen); +++- return 0; +++- } +++- +++- UnLock(lock); +++- +++- return 1; +++-#elif defined(WIN32) || defined(__MINGW32__) || defined(__WATCOMC__) || defined(VISUALCPP) || defined(OS2) +++- if (_fullpath(dst, src, (size_t)dstlen) != NULL) +++- return 1; +++- +++- safe_strncpy(dst, src, dstlen); +++- return 0; +++-#elif defined(DOS) +++- if (src[0] != '\\' && src[1] != ':') +++- { +++- char cwd[MAXPATHLEN + 1]; +++- +++- if (getcwd(cwd, sizeof(cwd)) != NULL) +++- { +++- snprintf(dst, dstlen, "%s\\%s", cwd, src); +++- return 1; +++- } +++- } +++- +++- safe_strncpy(dst, src, dstlen); +++- return 0; +++-#else +++- char buf[MAXPATHLEN + 1]; +++- +++- if (realpath(src, buf) != NULL) +++- { +++- safe_strncpy(dst, buf, dstlen); +++- return 1; +++- } +++- +++- if (src[0] != '/') +++- { +++- char cwd[MAXPATHLEN + 1]; +++- +++- if (getcwd(cwd, sizeof(cwd)) != NULL) +++- { +++- snprintf(dst, dstlen, "%s/%s", cwd, src); +++- return 1; +++- } +++- } +++- +++- safe_strncpy(dst, src, dstlen); +++- return 0; +++-#endif +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/portable.h binkd_pgul/misc/portable.h +++--- binkd/misc/portable.h 2026-04-26 14:19:41.472724309 +0100 ++++++ binkd_pgul/misc/portable.h 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,135 +0,0 @@ +++-/* +++- * portable.h -- Portability layer for standalone binkd misc tools +++- * +++- * portable.h is a part of binkd project +++- * +++- * This is the single canonical portable.h; all misc utilities include this +++- * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- * +++- */ +++- +++-#ifndef BINKD_PORTABLE_H +++-#define BINKD_PORTABLE_H +++- +++-/* _POSIX_C_SOURCE for opendir/readdir/localtime_r under -std=c89 +++- * _XOPEN_SOURCE 500 additionally exposes realpath() on glibc */ +++-#ifndef AMIGA +++-#ifndef _POSIX_C_SOURCE +++-#define _POSIX_C_SOURCE 200112L +++-#endif +++-#ifndef _XOPEN_SOURCE +++-#define _XOPEN_SOURCE 500 +++-#endif +++-#endif +++- +++-#include +++-#include +++-#include +++-#include +++-#include +++- +++-#ifdef AMIGA +++- +++-#include +++-#include +++-#include +++-#include +++-#include +++-#include /* stat() / struct stat via libnix/ADE */ +++-#include +++-#include "amiga/dirent.h" /* opendir / readdir / closedir */ +++- +++-/* snprintf/vsnprintf: ADE/libnix declares them in stdio.h (already included +++- * above via ). The implementation is provided by snprintf.c which +++- * must be linked when building the misc tools. No redeclaration needed */ +++- +++-#elif defined(VISUALCPP) +++-#include +++-#include +++-#include +++-#include "nt/dirwin32.h" /* opendir/readdir/closedir for MSVC */ +++-#elif defined(__MINGW32__) || defined(WIN32) +++-#include +++-#include /* MinGW provides dirent.h natively */ +++-#include +++-#include +++-#elif defined(OS2) && (defined(IBMC) || defined(__WATCOMC__)) +++-#include +++-#include +++-#include +++-#include "os2/dirent.h" /* opendir/readdir/closedir for OS/2 ICC/WC */ +++-#elif defined(OS2) +++-#include +++-#include /* EMX provides dirent.h natively */ +++-#include +++-#include +++-#include +++-#elif defined(DOS) +++-#include +++-#include +++-#include "dos/dirent.h" /* opendir/readdir/closedir for DOS/DJGPP */ +++-#else /* POSIX / *nix */ +++-#include +++-#include +++-#include +++-#include +++-#include +++-#endif +++- +++-#ifndef MAXPATHLEN +++-#if defined(_MAX_PATH) +++-#define MAXPATHLEN _MAX_PATH +++-#elif defined(PATH_MAX) +++-#define MAXPATHLEN PATH_MAX +++-#else +++-#define MAXPATHLEN 1024 +++-#endif +++-#endif +++- +++-/* Generic line buffer size for config files and text processing */ +++-#ifndef MAX_LINE +++-#define MAX_LINE 1024 +++-#endif +++- +++-/* path_exists / mkdir_one -- native implementations per OS */ +++-int port_path_exists(const char *p); +++-int port_mkdir_one(const char *p); +++-#define path_exists(p) port_path_exists(p) +++-#define mkdir_one(p) port_mkdir_one(p) +++- +++-/* safe_localtime -- thread-safe localtime, portable across all OS */ +++-void safe_localtime(const time_t *t, struct tm *tm); +++- +++-/* mkdir_recursive -- create full path, making all missing components */ +++-#define MP_MAXPATH 512 +++-int mkdir_recursive(const char *path); +++- +++-/* safe_strncpy -- strncpy that always NUL-terminates */ +++-void safe_strncpy(char *dst, const char *src, int dstsize); +++- +++-/* String utilities */ +++-void trim_nl(char *s); +++-void str_trim(char *s); +++-void str_upper(char *s); +++-void str_tolower(char *s); +++-char *skip_ws(char *s); +++- +++-/* Wildcard matching */ +++-int wildmatch(const char *pat, const char *str); +++-int is_wildcard(const char *s); +++- +++-/* File operations */ +++-int ensure_dir(const char *path); +++-int copy_file(const char *src, const char *dst); +++-int move_file(const char *src, const char *dst); +++-long get_file_size(const char *path); +++-long get_file_mtime(const char *path); +++- +++-/* Path utilities */ +++-void path_join(char *out, int outsize, const char *base, const char *sub); +++-int make_abs_path(const char *src, char *dst, int dstlen); +++- +++-#endif /* BINKD_PORTABLE_H */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/process_tic.c binkd_pgul/misc/process_tic.c +++--- binkd/misc/process_tic.c 2026-04-26 13:39:53.974576140 +0100 ++++++ binkd_pgul/misc/process_tic.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,552 +0,0 @@ +++-/* +++- * process_tic -- Process FTN .tic files from inbound to filebox +++- * +++- * process_tic.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include "portable.h" /* Canonical portable layer */ +++-#include +++- +++-static int my_toupper(int c) +++-{ +++- if (c >= 'a' && c <= 'z') +++- return c - 'a' + 'A'; +++- +++- return c; +++-} +++- +++-static int my_strnicmp(const char *a, const char *b, int n) +++-{ +++- int i, ca, cb; +++- for (i = 0; i < n; i++) +++- { +++- ca = my_toupper((unsigned char)a[i]); +++- cb = my_toupper((unsigned char)b[i]); +++- +++- if (ca != cb) +++- return ca - cb; +++- +++- if (ca == 0) +++- return 0; +++- } +++- +++- return 0; +++-} +++- +++-static int parse_file_field(char *line, char *out, int outsize) +++-{ +++- char *p; +++- char *end; +++- int len; +++- +++- p = skip_ws(line); +++- +++- if (my_strnicmp(p, "File", 4) != 0) +++- return 0; +++- +++- p += 4; +++- +++- if (*p != ' ' && *p != '\t') +++- return 0; +++- +++- p = skip_ws(p); +++- trim_nl(p); +++- end = p; +++- +++- while (*end && *end != ' ' && *end != '\t') +++- end++; +++- +++- *end = '\0'; +++- +++- len = (int)strlen(p); +++- +++- if (len <= 0 || len >= outsize) +++- return 0; +++- +++- strncpy(out, p, outsize - 1); +++- out[outsize - 1] = '\0'; +++- +++- return 1; +++-} +++- +++-static int parse_area_field(char *line, char *out, int outsize) +++-{ +++- char *p; +++- char *end; +++- int len; +++- +++- p = skip_ws(line); +++- +++- if (my_strnicmp(p, "Area", 4) != 0) +++- return 0; +++- +++- p += 4; +++- +++- if (*p != ' ' && *p != '\t') +++- return 0; +++- +++- p = skip_ws(p); +++- trim_nl(p); +++- end = p; +++- +++- while (*end && *end != ' ' && *end != '\t') +++- end++; +++- +++- *end = '\0'; +++- len = (int)strlen(p); +++- +++- if (len <= 0 || len >= outsize) +++- return 0; +++- +++- strncpy(out, p, outsize - 1); +++- out[outsize - 1] = '\0'; +++- +++- return 1; +++-} +++- +++-static int parse_origin_field(char *line, char *out, int outsize) +++-{ +++- char *p; +++- char *end; +++- int len; +++- +++- p = skip_ws(line); +++- +++- if (my_strnicmp(p, "Origin", 6) != 0) +++- return 0; +++- +++- p += 6; +++- +++- if (*p != ' ' && *p != '\t') +++- return 0; +++- +++- p = skip_ws(p); +++- trim_nl(p); +++- end = p; +++- +++- while (*end && *end != ' ' && *end != '\t') +++- end++; +++- +++- *end = '\0'; +++- len = (int)strlen(p); +++- +++- if (len <= 0 || len >= outsize) +++- return 0; +++- +++- strncpy(out, p, outsize - 1); +++- out[outsize - 1] = '\0'; +++- +++- return 1; +++-} +++- +++-static int parse_from_field(char *line, char *out, int outsize) +++-{ +++- char *p; +++- char *end; +++- int len; +++- +++- p = skip_ws(line); +++- +++- if (my_strnicmp(p, "From", 4) != 0) +++- return 0; +++- +++- p += 4; +++- +++- if (*p != ' ' && *p != '\t') +++- return 0; +++- +++- p = skip_ws(p); +++- trim_nl(p); +++- end = p; +++- +++- while (*end && *end != ' ' && *end != '\t') +++- end++; +++- +++- *end = '\0'; +++- len = (int)strlen(p); +++- +++- if (len <= 0 || len >= outsize) +++- return 0; +++- +++- strncpy(out, p, outsize - 1); +++- out[outsize - 1] = '\0'; +++- +++- return 1; +++-} +++- +++-static void append_filelist(const char *listpath, const char *file_name, long filesize, const char *dst_path) +++-{ +++- FILE *f; +++- time_t t; +++- struct tm tm; +++- char timestamp[32]; +++- +++- if (!listpath || !listpath[0]) +++- return; +++- +++- f = fopen(listpath, "a"); +++- if (!f) +++- return; +++- +++- t = time(NULL); +++- safe_localtime(&t, &tm); +++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); +++- +++- fprintf(f, "%s\t%s\t%ld\t%s\n", timestamp, file_name, filesize, dst_path); +++- fclose(f); +++-} +++- +++-static void append_newfiles(const char *newprefix, const char *file_name, long filesize, const char *dst_path) +++-{ +++- FILE *f; +++- char newpath[MAXPATHLEN]; +++- time_t t; +++- struct tm tm; +++- char datebuf[16]; +++- +++- if (!newprefix || !newprefix[0]) +++- return; +++- +++- t = time(NULL); +++- safe_localtime(&t, &tm); +++- strftime(datebuf, sizeof(datebuf), "%Y%m%d", &tm); +++- +++- ensure_dir(newprefix); +++- path_join(newpath, (int)sizeof(newpath), newprefix, "newfiles-"); +++- strncat(newpath, datebuf, sizeof(newpath) - strlen(newpath) - 1); +++- strncat(newpath, ".txt", sizeof(newpath) - strlen(newpath) - 1); +++- +++- f = fopen(newpath, "a"); +++- +++- if (!f) +++- return; +++- +++- fprintf(f, "%s\t%ld\t%s\n", file_name, filesize, dst_path); +++- +++- fclose(f); +++-} +++- +++-static void write_ticlog(const char *ticlog, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) +++-{ +++- FILE *f; +++- time_t t; +++- struct tm tm; +++- char timestamp[64]; +++- +++- if (!ticlog || !ticlog[0]) +++- return; +++- +++- f = fopen(ticlog, "a"); +++- if (!f) +++- return; +++- +++- t = time(NULL); +++- safe_localtime(&t, &tm); +++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); +++- +++- fprintf(f, "[%s] File: %s\n", timestamp, file_name); +++- fprintf(f, " Area: %s\n", area_name); +++- +++- if (origin_name[0]) +++- fprintf(f, " Origin: %s\n", origin_name); +++- +++- if (from_name[0]) +++- fprintf(f, " From: %s\n", from_name); +++- +++- fprintf(f, " Src: %s\n", src_path); +++- fprintf(f, " To: %s\n", dst_path); +++- fprintf(f, "\n"); +++- +++- fclose(f); +++-} +++- +++-static void write_log(const char *logfile, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) +++-{ +++- FILE *f; +++- time_t t; +++- struct tm tm; +++- char timestamp[64]; +++- +++- if (!logfile || !logfile[0]) +++- return; +++- +++- f = fopen(logfile, "a"); +++- if (!f) +++- return; +++- +++- t = time(NULL); +++- safe_localtime(&t, &tm); +++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); +++- +++- fprintf(f, "[%s] File: %s\n", timestamp, file_name); +++- fprintf(f, " Area: %s\n", area_name); +++- +++- if (origin_name[0]) +++- fprintf(f, " Origin: %s\n", origin_name); +++- +++- if (from_name[0]) +++- fprintf(f, " From: %s\n", from_name); +++- +++- fprintf(f, " Src: %s\n", src_path); +++- fprintf(f, " To: %s\n", dst_path); +++- fprintf(f, "\n"); +++- fclose(f); +++-} +++- +++-static void process_one_tic(const char *ticpath, const char *inbound, const char *filebox, int copypublic, const char *pubdir, const char *logfile, const char *filelist, const char *newfiles, const char *ticlog) +++-{ +++- FILE *f; +++- char line[MAX_LINE]; +++- char file_name[MAXPATHLEN]; +++- char area_name[MAXPATHLEN]; +++- char origin_name[MAXPATHLEN]; +++- char from_name[MAXPATHLEN]; +++- char src_path[MAXPATHLEN]; +++- char area_dir[MAXPATHLEN]; +++- char dst_path[MAXPATHLEN]; +++- +++- file_name[0] = '\0'; +++- area_name[0] = '\0'; +++- origin_name[0] = '\0'; +++- from_name[0] = '\0'; +++- long fsize = 0; +++- +++- f = fopen(ticpath, "r"); +++- +++- if (!f) +++- return; +++- +++- while (fgets(line, sizeof(line), f)) +++- { +++- if (!file_name[0]) +++- parse_file_field(line, file_name, sizeof(file_name)); +++- +++- if (!area_name[0]) +++- parse_area_field(line, area_name, sizeof(area_name)); +++- +++- if (!origin_name[0]) +++- parse_origin_field(line, origin_name, sizeof(origin_name)); +++- +++- if (!from_name[0]) +++- parse_from_field(line, from_name, sizeof(from_name)); +++- } +++- +++- fclose(f); +++- +++- if (!file_name[0] || !area_name[0]) +++- return; +++- +++- path_join(src_path, sizeof(src_path), inbound, file_name); +++- path_join(area_dir, sizeof(area_dir), filebox, area_name); +++- path_join(dst_path, sizeof(dst_path), area_dir, file_name); +++- +++- if (!path_exists(src_path)) +++- return; +++- +++- if (!ensure_dir(filebox) || !ensure_dir(area_dir)) +++- return; +++- +++- if (copypublic && pubdir && pubdir[0]) +++- { +++- char pub_dst[MAXPATHLEN]; +++- +++- path_join(pub_dst, sizeof(pub_dst), pubdir, file_name); +++- +++- if (ensure_dir(pubdir)) +++- copy_file(src_path, pub_dst); +++- } +++- +++- fsize = get_file_size(src_path); +++- +++- if (!move_file(src_path, dst_path)) +++- return; +++- +++- write_log(logfile, file_name, area_name, origin_name, from_name, src_path, dst_path); +++- write_ticlog(ticlog, file_name, area_name, origin_name, from_name, src_path, dst_path); +++- append_filelist(filelist, file_name, fsize, dst_path); +++- append_newfiles(newfiles, file_name, fsize, dst_path); +++- +++- remove(ticpath); +++-} +++- +++-static int is_tic_file(const char *name) +++-{ +++- int len = (int)strlen(name); +++- +++- if (len < 5) +++- return 0; +++- +++- return (my_strnicmp(name + len - 4, ".tic", 4) == 0); +++-} +++- +++-/* Config structure */ +++-static struct +++-{ +++- char inbound[MAXPATHLEN]; +++- char filebox[MAXPATHLEN]; +++- char pubdir[MAXPATHLEN]; +++- char logfile[MAXPATHLEN]; +++- char filelist[MAXPATHLEN]; +++- char newfiles[MAXPATHLEN]; +++- char ticlog[MAXPATHLEN]; +++- int copypublic; +++-} cfg; +++- +++-/* Parse configuration file */ +++-static int parse_config(const char *conffile) +++-{ +++- FILE *f; +++- char line[MAX_LINE]; +++- char *key, *value; +++- +++- memset(&cfg, 0, sizeof(cfg)); +++- +++- f = fopen(conffile, "r"); +++- +++- if (!f) +++- { +++- fprintf(stderr, "process_tic: cannot open config file: %s\n", conffile); +++- return 0; +++- } +++- +++- while (fgets(line, sizeof(line), f)) +++- { +++- trim_nl(line); +++- key = skip_ws(line); +++- +++- /* Skip comments and empty lines */ +++- if (*key == '#' || *key == '\0') +++- continue; +++- +++- /* Find value after key */ +++- value = key; +++- +++- while (*value && *value != ' ' && *value != '\t') +++- value++; +++- +++- if (*value) +++- { +++- *value = '\0'; +++- value = skip_ws(value + 1); +++- } +++- +++- /* Parse key-value pairs */ +++- if (strcmp(key, "inbound") == 0) +++- safe_strncpy(cfg.inbound, value, (int)sizeof(cfg.inbound)); +++- else if (strcmp(key, "filebox") == 0) +++- safe_strncpy(cfg.filebox, value, (int)sizeof(cfg.filebox)); +++- else if (strcmp(key, "pubdir") == 0) +++- { +++- safe_strncpy(cfg.pubdir, value, (int)sizeof(cfg.pubdir)); +++- cfg.copypublic = 1; +++- } +++- else if (strcmp(key, "logfile") == 0) +++- safe_strncpy(cfg.logfile, value, (int)sizeof(cfg.logfile)); +++- else if (strcmp(key, "filelist") == 0) +++- safe_strncpy(cfg.filelist, value, (int)sizeof(cfg.filelist)); +++- else if (strcmp(key, "newfiles") == 0) +++- safe_strncpy(cfg.newfiles, value, (int)sizeof(cfg.newfiles)); +++- else if (strcmp(key, "ticlog") == 0) +++- safe_strncpy(cfg.ticlog, value, (int)sizeof(cfg.ticlog)); +++- } +++- +++- fclose(f); +++- +++- /* Validate required fields */ +++- if (!cfg.inbound[0] || !cfg.filebox[0]) +++- { +++- fprintf(stderr, "process_tic: config file missing required 'inbound' or 'filebox'\n"); +++- return 0; +++- } +++- +++- return 1; +++-} +++- +++-int main(int argc, char *argv[]) +++-{ +++- char inbound[MAXPATHLEN]; +++- char filebox[MAXPATHLEN]; +++- char ticpath[MAXPATHLEN]; +++- char pubdir[MAXPATHLEN]; +++- char logfile[MAXPATHLEN]; +++- char filelist[MAXPATHLEN]; +++- char newfiles[MAXPATHLEN]; +++- char ticlog[MAXPATHLEN]; +++- int copypublic = 0; +++- DIR *dp; +++- struct dirent *de; +++- int found; +++- int i; +++- int use_config = 0; +++- +++- inbound[0] = '\0'; +++- filebox[0] = '\0'; +++- pubdir[0] = '\0'; +++- logfile[0] = '\0'; +++- filelist[0] = '\0'; +++- newfiles[0] = '\0'; +++- ticlog[0] = '\0'; +++- +++- /* Check for --conf option */ +++- for (i = 1; i < argc; i++) +++- { +++- if (strcmp(argv[i], "--conf") == 0 && i + 1 < argc) +++- { +++- if (!parse_config(argv[i + 1])) +++- return 1; +++- +++- use_config = 1; +++- i++; /* Skip config file path */ +++- +++- break; +++- } +++- } +++- +++- if (use_config) +++- { +++- /* Use config file values */ +++- safe_strncpy(inbound, cfg.inbound, (int)sizeof(inbound)); +++- safe_strncpy(filebox, cfg.filebox, (int)sizeof(filebox)); +++- safe_strncpy(pubdir, cfg.pubdir, (int)sizeof(pubdir)); +++- safe_strncpy(logfile, cfg.logfile, (int)sizeof(logfile)); +++- safe_strncpy(filelist, cfg.filelist, (int)sizeof(filelist)); +++- safe_strncpy(newfiles, cfg.newfiles, (int)sizeof(newfiles)); +++- safe_strncpy(ticlog, cfg.ticlog, (int)sizeof(ticlog)); +++- copypublic = cfg.copypublic; +++- } +++- +++- if (!inbound[0] || !filebox[0]) +++- { +++- fprintf(stderr, +++- "Usage: process_tic --conf [*.tic]\n"); +++- +++- return 1; +++- } +++- +++- dp = opendir(inbound); +++- +++- if (!dp) +++- return 1; +++- +++- found = 0; +++- +++- while ((de = readdir(dp)) != NULL) +++- { +++- /* Skip . and .. */ +++- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) +++- continue; +++- +++- if (!is_tic_file(de->d_name)) +++- continue; +++- +++- path_join(ticpath, sizeof(ticpath), inbound, de->d_name); +++- process_one_tic(ticpath, inbound, filebox, copypublic, pubdir, logfile, filelist, newfiles, ticlog); +++- found++; +++- } +++- +++- closedir(dp); +++- return 0; +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/process_tic.txt binkd_pgul/misc/process_tic.txt +++--- binkd/misc/process_tic.txt 2026-04-26 13:43:48.542112490 +0100 ++++++ binkd_pgul/misc/process_tic.txt 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,45 +0,0 @@ +++-process_tic -- Process .tic file announcements +++- +++-USAGE: +++- process_tic --conf [files.tic...] +++- +++-DESCRIPTION: +++- Processes .tic (Ticker announcement) files from the inbound directory +++- and moves/copies files to their destination filebox or public directory. +++- +++- The .tic file is parsed for File, Area, Origin, and From fields. +++- The actual file is moved from inbound to filebox/AreaName/. +++- +++- All settings are read from the configuration file. +++- +++-OPTIONS: +++- --conf Configuration file (required) +++- +++-CONFIGURATION FILE FORMAT: +++- # Lines starting with # are comments +++- # Blank lines are ignored +++- +++- inbound Inbound directory (required) +++- filebox Filebox destination (required) +++- pubdir Public directory for --copy-public +++- logfile Log file path +++- ticlog TIC processing log +++- filelist File list output +++- newfiles New files list output +++- +++-EXAMPLE CONFIG FILE (process_tic.conf): +++- # process_tic.conf - Configuration for TIC processor +++- +++- inbound Work:Inbound +++- filebox Work:Filebox +++- pubdir Work:Public +++- logfile Work:Logs/process_tic.log +++- ticlog Work:Logs/tic.log +++- filelist Work:Filebox/filelist.txt +++- newfiles Work:Filebox/newfiles.txt +++- +++-EXAMPLES: +++- process_tic --conf process_tic.conf +++- process_tic --conf process_tic.conf inbound/*.tic +++- +++- exec "process_tic --conf work:fido/process_tic.conf" *.tic *.TIC +++\ No hay ningún carácter de nueva línea al final del archivo +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/srifreq.c binkd_pgul/misc/srifreq.c +++--- binkd/misc/srifreq.c 2026-04-26 15:01:11.006850708 +0100 ++++++ binkd_pgul/misc/srifreq.c 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,1024 +0,0 @@ +++-/* +++- * srifreq.c -- SRIF-compatible file-request server for binkd +++- * +++- * srifreq.c is a part of binkd project +++- * +++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet +++- * Licensed under the GNU GPL v2 or later +++- */ +++- +++-#include "portable.h" /* Canonical portable layer */ +++-#include +++-#include +++- +++-/* Private directory entry (dynamically allocated list) */ +++-typedef struct PrivDir +++-{ +++- char path[MAXPATHLEN]; +++- char password[64]; +++- struct PrivDir *next; +++-} PrivDir; +++- +++-/* Node tracking entry for rate limiting */ +++-typedef struct NodeTrack +++-{ +++- char aka[256]; /* Node address (4D/5D) */ +++- int files; /* Files downloaded in window */ +++- long bytes; /* Bytes downloaded in window */ +++- time_t last_time; /* Timestamp of last download */ +++- struct NodeTrack *next; +++-} NodeTrack; +++- +++-/* Global configuration (filled from --conf file) */ +++-typedef struct +++-{ +++- char pubdir[MAXPATHLEN]; +++- char logfile[MAXPATHLEN]; +++- char aliases[MAXPATHLEN]; +++- char trackfile[MAXPATHLEN]; /* Path to tracking file */ +++- int maxfiles; /* Max files per node per window (0=unlimited) */ +++- long maxbytes; /* Max bytes per node per window (0=unlimited) */ +++- long timewindow; /* Time window in seconds (0=no window) */ +++- PrivDir *privdirs; /* Linked list, NULL if none */ +++- NodeTrack *tracking; /* Linked list of tracked nodes */ +++-} Config; +++- +++-/* Alias table -- Loaded from file at startup */ +++-typedef struct +++-{ +++- char name[64]; +++- char path[MAXPATHLEN]; +++-} Alias; +++- +++-/* SRIF parsing */ +++-typedef struct +++-{ +++- char sysop[128]; +++- char aka[256]; +++- char request_list[MAXPATHLEN]; +++- char response_list[MAXPATHLEN]; +++- char our_aka[128]; +++- char caller_id[64]; /* CallerID: IP or phone of remote */ +++- char password[64]; /* Password: session password */ +++- int time_limit; /* Time: minutes left, -1 = unlimited */ +++- long tranx; /* TRANX: remote local time as Unix ts (hex in SRIF) */ +++- int protected_sess; /* RemoteStatus: 1=PROTECTED, 0=UNPROTECTED */ +++- int listed; /* SystemStatus: 1=LISTED, 0=UNLISTED */ +++- int got_request_list; +++- int got_response_list; +++-} SRIF; +++- +++-static Alias *g_aliases = NULL; +++-static int g_nalias = 0; +++-static int g_alias_cap = 0; +++-static Config g_conf; +++- +++-static void config_init(void) +++-{ +++- memset(&g_conf, 0, sizeof(g_conf)); +++- g_conf.privdirs = NULL; +++- g_conf.tracking = NULL; +++- g_conf.maxfiles = 0; /* 0 = unlimited */ +++- g_conf.maxbytes = 0; /* 0 = unlimited */ +++- g_conf.timewindow = 0; /* 0 = no window */ +++-} +++- +++-static void config_add_private(const char *path, const char *password) +++-{ +++- PrivDir *pd = (PrivDir *)malloc(sizeof(PrivDir)); +++- PrivDir *tail; +++- +++- if (!pd) +++- return; +++- +++- safe_strncpy(pd->path, path, (int)sizeof(pd->path)); +++- safe_strncpy(pd->password, password, (int)sizeof(pd->password)); +++- +++- pd->next = NULL; +++- +++- /* Append to tail */ +++- if (!g_conf.privdirs) +++- g_conf.privdirs = pd; +++- else +++- { +++- tail = g_conf.privdirs; +++- +++- while (tail->next) +++- tail = tail->next; +++- +++- tail->next = pd; +++- } +++-} +++- +++-static void config_free(void) +++-{ +++- PrivDir *pd = g_conf.privdirs; +++- NodeTrack *nt = g_conf.tracking; +++- +++- while (pd) +++- { +++- PrivDir *next = pd->next; +++- free(pd); +++- pd = next; +++- } +++- +++- g_conf.privdirs = NULL; +++- +++- while (nt) +++- { +++- NodeTrack *next = nt->next; +++- free(nt); +++- nt = next; +++- } +++- +++- g_conf.tracking = NULL; +++- +++- if (g_aliases) +++- { +++- free(g_aliases); +++- g_aliases = NULL; +++- g_nalias = 0; +++- g_alias_cap = 0; +++- } +++-} +++- +++-static int load_config(const char *path) +++-{ +++- FILE *f; +++- char line[MAX_LINE]; +++- char key[64], val[MAXPATHLEN], pw[64]; +++- int n; +++- +++- f = fopen(path, "r"); +++- +++- if (!f) +++- { +++- fprintf(stderr, "srifreq: cannot open config: %s\n", path); +++- return 0; +++- } +++- +++- while (fgets(line, sizeof(line), f)) +++- { +++- /* Strip trailing whitespace and newlines */ +++- n = (int)strlen(line); +++- +++- while (n > 0 && +++- (line[n - 1] == '\r' || line[n - 1] == '\n' || line[n - 1] == ' ')) +++- line[--n] = '\0'; +++- +++- /* Skip blank and comment lines */ +++- if (!line[0] || line[0] == '#') +++- continue; +++- +++- key[0] = val[0] = pw[0] = '\0'; +++- +++- if (sscanf(line, "%63s %1023s %63s", key, val, pw) < 2) +++- continue; +++- +++- if (strcmp(key, "pubdir") == 0) +++- safe_strncpy(g_conf.pubdir, val, (int)sizeof(g_conf.pubdir)); +++- else if (strcmp(key, "logfile") == 0) +++- safe_strncpy(g_conf.logfile, val, (int)sizeof(g_conf.logfile)); +++- else if (strcmp(key, "aliases") == 0) +++- safe_strncpy(g_conf.aliases, val, (int)sizeof(g_conf.aliases)); +++- else if (strcmp(key, "trackfile") == 0) +++- safe_strncpy(g_conf.trackfile, val, (int)sizeof(g_conf.trackfile)); +++- else if (strcmp(key, "maxfiles") == 0) +++- g_conf.maxfiles = atoi(val); +++- else if (strcmp(key, "maxsize") == 0) +++- g_conf.maxbytes = atol(val); +++- else if (strcmp(key, "timewindow") == 0) +++- g_conf.timewindow = atol(val); +++- else if (strcmp(key, "private") == 0 && pw[0]) +++- config_add_private(val, pw); +++- } +++- +++- fclose(f); +++- +++- return 1; +++-} +++- +++-/* tracking_load -- Load node tracking data from file */ +++-static void tracking_load(void) +++-{ +++- FILE *f; +++- char line[MAX_LINE]; +++- char aka[256]; +++- int files; +++- long bytes; +++- long timestamp; +++- NodeTrack *nt; +++- time_t now = time(NULL); +++- +++- if (!g_conf.trackfile[0]) +++- return; +++- +++- f = fopen(g_conf.trackfile, "r"); +++- +++- if (!f) +++- return; +++- +++- while (fgets(line, sizeof(line), f)) +++- { +++- str_trim(line); +++- +++- if (!line[0] || line[0] == '#') +++- continue; +++- +++- if (sscanf(line, "%255s %d %ld %ld", aka, &files, &bytes, ×tamp) != 4) +++- continue; +++- +++- /* Skip if outside time window (also reject future/corrupt timestamps) */ +++- if (g_conf.timewindow > 0 && (timestamp > now || (now - timestamp) > g_conf.timewindow)) +++- continue; +++- +++- /* Create new tracking entry */ +++- nt = (NodeTrack *)malloc(sizeof(NodeTrack)); +++- +++- if (!nt) +++- continue; +++- +++- safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); +++- nt->files = files; +++- nt->bytes = bytes; +++- nt->last_time = (time_t)timestamp; +++- nt->next = g_conf.tracking; +++- g_conf.tracking = nt; +++- } +++- +++- fclose(f); +++-} +++- +++-/* tracking_save -- Save node tracking data to file */ +++-static void tracking_save(void) +++-{ +++- FILE *f; +++- NodeTrack *nt; +++- +++- if (!g_conf.trackfile[0]) +++- return; +++- +++- f = fopen(g_conf.trackfile, "w"); +++- +++- if (!f) +++- return; +++- +++- fprintf(f, "# srifreq tracking file - Format: AKA files bytes timestamp\n"); +++- +++- for (nt = g_conf.tracking; nt; nt = nt->next) +++- { +++- fprintf(f, "%s %d %ld %ld\n", nt->aka, nt->files, nt->bytes, (long)nt->last_time); +++- } +++- +++- fclose(f); +++-} +++- +++-/* tracking_find -- Find tracking entry for a node */ +++-static NodeTrack *tracking_find(const char *aka) +++-{ +++- NodeTrack *nt; +++- +++- for (nt = g_conf.tracking; nt; nt = nt->next) +++- { +++- if (strcmp(nt->aka, aka) == 0) +++- return nt; +++- } +++- +++- return NULL; +++-} +++- +++-/* tracking_update -- Update tracking after serving a file */ +++-static void tracking_update(const char *aka, long filesize) +++-{ +++- NodeTrack *nt = tracking_find(aka); +++- time_t now = time(NULL); +++- +++- if (nt) +++- { +++- /* Update existing entry */ +++- nt->files++; +++- nt->bytes += filesize; +++- nt->last_time = now; +++- } +++- else +++- { +++- /* Create new entry */ +++- nt = (NodeTrack *)malloc(sizeof(NodeTrack)); +++- +++- if (nt) +++- { +++- safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); +++- nt->files = 1; +++- nt->bytes = filesize; +++- nt->last_time = now; +++- nt->next = g_conf.tracking; +++- g_conf.tracking = nt; +++- } +++- } +++-} +++- +++-/* tracking_check -- Check if node exceeds limits */ +++-static int tracking_check(const char *aka, char *msg, int msglen) +++-{ +++- NodeTrack *nt = tracking_find(aka); +++- +++- if (!nt) +++- return 1; /* No tracking yet, allow */ +++- +++- /* Check max files */ +++- if (g_conf.maxfiles > 0 && nt->files >= g_conf.maxfiles) +++- { +++- snprintf(msg, msglen, "RATE LIMIT: max files (%d) reached for %s", g_conf.maxfiles, aka); +++- return 0; +++- } +++- +++- /* Check max bytes */ +++- if (g_conf.maxbytes > 0 && nt->bytes >= g_conf.maxbytes) +++- { +++- snprintf(msg, msglen, "RATE LIMIT: max bytes (%ld) reached for %s", g_conf.maxbytes, aka); +++- return 0; +++- } +++- +++- return 1; /* Within limits */ +++-} +++- +++-/* is_abs_path -- True if path is absolute (POSIX, Win32, AmigaDOS device:) */ +++-static int is_abs_path(const char *p) +++-{ +++- if (!p || !p[0]) +++- return 0; +++- +++- if (p[0] == '/' || p[0] == '\\') +++- return 1; +++- +++-#ifdef AMIGA +++- if (strchr(p, ':') != NULL) +++- return 1; +++-#else +++- if (p[1] == ':') +++- return 1; /* C:\ etc. */ +++-#endif +++- return 0; +++-} +++- +++-/* +++- * load_aliases -- Read alias definitions from file +++- * Lines starting with '#' or empty are skipped +++- * Format: +++- */ +++-static void load_aliases(const char *filepath) +++-{ +++- FILE *f; +++- char line[MAX_LINE]; +++- char name[64]; +++- char path[MAXPATHLEN]; +++- int n; +++- +++- /* Free previous aliases and start fresh */ +++- if (g_aliases) +++- { +++- free(g_aliases); +++- g_aliases = NULL; +++- } +++- +++- g_nalias = 0; +++- g_alias_cap = 0; +++- +++- if (!filepath || !filepath[0] || strcmp(filepath, "-") == 0) +++- return; +++- +++- f = fopen(filepath, "r"); +++- +++- if (!f) +++- { +++- /*fprintf(stderr, "srifreq: cannot open aliases file: %s\n", filepath);*/ +++- return; +++- } +++- +++- while (fgets(line, sizeof(line), f)) +++- { +++- char *p; +++- +++- /* Strip trailing newline */ +++- n = (int)strlen(line); +++- +++- while (n > 0 && (line[n - 1] == '\r' || line[n - 1] == '\n')) +++- line[--n] = '\0'; +++- +++- /* Skip blanks and comments */ +++- p = line; +++- +++- while (*p == ' ' || *p == '\t') +++- p++; +++- +++- if (!*p || *p == '#') +++- continue; +++- +++- name[0] = '\0'; +++- path[0] = '\0'; +++- +++- if (sscanf(p, "%63s %1023[^\n]", name, path) < 2) +++- continue; +++- +++- if (!name[0] || !path[0]) +++- continue; +++- +++- /* Grow array dynamically if needed */ +++- if (g_nalias >= g_alias_cap) +++- { +++- int new_cap = g_alias_cap ? g_alias_cap * 2 : 16; +++- Alias *new_arr = realloc(g_aliases, (size_t)new_cap * sizeof(Alias)); +++- +++- if (!new_arr) +++- break; +++- +++- g_aliases = new_arr; +++- g_alias_cap = new_cap; +++- } +++- +++- safe_strncpy(g_aliases[g_nalias].name, name, (int)sizeof(g_aliases[g_nalias].name)); +++- safe_strncpy(g_aliases[g_nalias].path, path, (int)sizeof(g_aliases[g_nalias].path)); +++- g_nalias++; +++- } +++- +++- fclose(f); +++- +++- /*printf("srifreq: loaded %d alias(es) from %s\n", g_nalias, filepath);*/ +++-} +++- +++-/* +++- * find_alias -- look up name in alias table (case-insensitive) +++- * Returns the path string, or NULL if not found +++- */ +++-static const char *find_alias(const char *name) +++-{ +++- char upper[64]; +++- char aname[64]; +++- int i; +++- int n; +++- +++- /* Convert name to uppercase */ +++- n = (int)strlen(name); +++- +++- if (n >= (int)sizeof(upper)) +++- n = (int)sizeof(upper) - 1; +++- +++- for (i = 0; i < n; i++) +++- upper[i] = (char)toupper((unsigned char)name[i]); +++- +++- upper[n] = '\0'; +++- +++- for (i = 0; i < g_nalias; i++) +++- { +++- int an; +++- an = (int)strlen(g_aliases[i].name); +++- +++- if (an >= (int)sizeof(aname)) an = (int)sizeof(aname) - 1; +++- { +++- int j; +++- +++- for (j = 0; j < an; j++) +++- aname[j] = (char)toupper((unsigned char)g_aliases[i].name[j]); +++- +++- aname[an] = '\0'; +++- } +++- +++- if (strcmp(upper, aname) == 0) +++- return g_aliases[i].path; +++- } +++- +++- return NULL; +++-} +++- +++-static int parse_srif(const char *path, SRIF *srif) +++-{ +++- FILE *f; +++- char line[MAX_LINE]; +++- char token[MAX_LINE]; +++- char value[MAX_LINE]; +++- +++- memset(srif, 0, sizeof(SRIF)); +++- srif->time_limit = -1; /* default: unlimited */ +++- srif->listed = 1; /* default: assume listed */ +++- srif->protected_sess = 0; +++- +++- f = fopen(path, "r"); +++- if (!f) +++- return 0; +++- +++- while (fgets(line, sizeof(line), f)) +++- { +++- str_trim(line); +++- if (!line[0]) +++- continue; +++- +++- token[0] = '\0'; +++- value[0] = '\0'; +++- +++- if (sscanf(line, "%1023s %1023[^\n]", token, value) < 1) +++- continue; +++- +++- str_upper(token); +++- +++- if (!value[0]) +++- continue; +++- +++- if (!strcmp(token, "SYSOP")) +++- safe_strncpy(srif->sysop, value, (int)sizeof(srif->sysop)); +++- else if (!strcmp(token, "AKA") && !srif->aka[0]) +++- safe_strncpy(srif->aka, value, (int)sizeof(srif->aka)); +++- else if (!strcmp(token, "REQUESTLIST")) +++- { +++- safe_strncpy(srif->request_list, value, MAXPATHLEN); +++- srif->got_request_list = 1; +++- } +++- else if (!strcmp(token, "RESPONSELIST")) +++- { +++- safe_strncpy(srif->response_list, value, MAXPATHLEN); +++- srif->got_response_list = 1; +++- } +++- else if (!strcmp(token, "OURAKA")) +++- safe_strncpy(srif->our_aka, value, (int)sizeof(srif->our_aka)); +++- else if (!strcmp(token, "PASSWORD")) +++- safe_strncpy(srif->password, value, (int)sizeof(srif->password)); +++- else if (!strcmp(token, "CALLERID")) +++- safe_strncpy(srif->caller_id, value, (int)sizeof(srif->caller_id)); +++- else if (!strcmp(token, "TIME")) +++- srif->time_limit = atoi(value); +++- else if (!strcmp(token, "TRANX")) +++- { +++- /* TRANX is a hex Unix timestamp: 5a326682 or 16-digit */ +++- unsigned long v = 0; +++- sscanf(value, "%lx", &v); +++- srif->tranx = (long)v; +++- } +++- else if (!strcmp(token, "REMOTESTATUS")) +++- { +++- char tmp[32]; +++- safe_strncpy(tmp, value, (int)sizeof(tmp)); +++- str_upper(tmp); +++- srif->protected_sess = (strncmp(tmp, "PROTECTED", 9) == 0) ? 1 : 0; +++- } +++- else if (!strcmp(token, "SYSTEMSTATUS")) +++- { +++- char tmp[32]; +++- safe_strncpy(tmp, value, (int)sizeof(tmp)); +++- str_upper(tmp); +++- srif->listed = (strncmp(tmp, "LISTED", 6) == 0) ? 1 : 0; +++- } +++- } +++- +++- fclose(f); +++- +++- return srif->got_request_list; +++-} +++- +++-/* Logging */ +++-static void do_log(const char *logpath, const char *msg) +++-{ +++- FILE *lf; +++- time_t t; +++- struct tm tm; +++- char timestamp[32]; +++- +++- if (!logpath || !logpath[0] || strcmp(logpath, "-") == 0) +++- return; +++- +++- t = time(NULL); +++- safe_localtime(&t, &tm); +++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); +++- +++- lf = fopen(logpath, "a"); +++- +++- if (lf) +++- { +++- fprintf(lf, "[%s] srifreq: %s\n", timestamp, msg); +++- fclose(lf); +++- } +++-} +++- +++-/* serve_one -- resolve one request name, check password/timestamp/update +++- * write to response list. Returns 1 if served +++- */ +++-static int serve_one(const char *req_name, const char *found_path, const char *req_pass, long req_newer, int req_update, const SRIF *srif, FILE *rsp_f, const char *log_path, char *logbuf, int logbuf_size) +++-{ +++- long fsize; +++- +++- /* Check rate limits before serving */ +++- if (g_conf.trackfile[0] && !tracking_check(srif->aka, logbuf, logbuf_size)) +++- { +++- do_log(log_path, logbuf); +++- return 0; +++- } +++- +++- /* RemoteStatus: if session is unprotected and a password is required, deny */ +++- if (req_pass[0] && !srif->protected_sess) +++- { +++- snprintf(logbuf, logbuf_size, "PASSWORD DENY (unprotected session): %s", req_name); +++- do_log(log_path, logbuf); +++- return 0; +++- } +++- +++- /* Password check: !pw must match SRIF PASSWORD (case-insensitive) */ +++- if (req_pass[0]) +++- { +++- char rp[64], sp[64]; +++- int i; +++- +++- safe_strncpy(rp, req_pass, (int)sizeof(rp)); +++- safe_strncpy(sp, srif->password, (int)sizeof(sp)); +++- +++- for (i = 0; rp[i]; i++) +++- rp[i] = (char)toupper((unsigned char)rp[i]); +++- +++- for (i = 0; sp[i]; i++) +++- sp[i] = (char)toupper((unsigned char)sp[i]); +++- +++- if (strcmp(rp, sp) != 0) +++- { +++- snprintf(logbuf, logbuf_size, "PASSWORD FAIL: %s", req_name); +++- do_log(log_path, logbuf); +++- +++- return 0; +++- } +++- } +++- +++- /* Update request (U flag): serve only if file is newer than TRANX */ +++- if (req_update && srif->tranx > 0) +++- { +++- long mtime = get_file_mtime(found_path); +++- +++- if (mtime <= srif->tranx) +++- { +++- snprintf(logbuf, logbuf_size, "NOT UPDATED: %s (mtime=%ld tranx=%ld)", req_name, mtime, srif->tranx); +++- do_log(log_path, logbuf); +++- return 0; +++- } +++- } +++- +++- /* Timestamp check: +ts means "only if file is newer than ts" */ +++- if (req_newer > 0) +++- { +++- long mtime = get_file_mtime(found_path); +++- +++- if (mtime <= req_newer) +++- { +++- snprintf(logbuf, logbuf_size, "NOT NEWER: %s (mtime=%ld req=%ld)", req_name, mtime, req_newer); +++- +++- do_log(log_path, logbuf); +++- return 0; +++- } +++- } +++- +++- snprintf(logbuf, logbuf_size, "FOUND: %s -> %s", req_name, found_path); +++- do_log(log_path, logbuf); +++- +++- if (rsp_f) +++- fprintf(rsp_f, "+%s\r\n", found_path); +++- +++- /* Update tracking after successful serve */ +++- if (g_conf.trackfile[0]) +++- { +++- fsize = get_file_size(found_path); +++- +++- if (fsize < 0) +++- fsize = 0; +++- +++- tracking_update(srif->aka, fsize); +++- } +++- +++- return 1; +++-} +++- +++-int main(int argc, char *argv[]) +++-{ +++- const char *srif_path; +++- SRIF srif; +++- FILE *req_f; +++- FILE *rsp_f; +++- char line[MAX_LINE]; +++- char req_name[MAX_LINE]; +++- char req_pass[64]; +++- long req_newer; +++- int req_update; +++- char found_path[MAXPATHLEN]; +++- char logbuf[MAXPATHLEN * 4 + 128]; +++- int found_count; +++- +++- config_init(); +++- +++- /* --conf */ +++- if (argc >= 4 && strcmp(argv[1], "--conf") == 0) +++- { +++- if (!load_config(argv[2])) +++- return 1; +++- +++- srif_path = argv[3]; +++- } +++- else +++- { +++- fprintf(stderr, "Usage:\n" +++- " srifreq --conf \n" +++- "\n" +++- "Config file keys: pubdir, logfile, aliases, private " +++- "\n"); +++- +++- return 1; +++- } +++- +++- if (!g_conf.pubdir[0]) +++- { +++- fprintf(stderr, "srifreq: pubdir not set\n"); +++- config_free(); +++- return 1; +++- } +++- +++- /* Load tracking data if configured */ +++- tracking_load(); +++- +++- load_aliases(g_conf.aliases[0] ? g_conf.aliases : NULL); +++- +++- snprintf(logbuf, sizeof(logbuf), "processing SRIF: %s", srif_path); +++- do_log(g_conf.logfile, logbuf); +++- +++- if (!parse_srif(srif_path, &srif)) +++- { +++- snprintf(logbuf, sizeof(logbuf), "ERROR: cannot parse SRIF or missing RequestList: %s", srif_path); +++- do_log(g_conf.logfile, logbuf); +++- fprintf(stderr, "srifreq: %s\n", logbuf); +++- config_free(); +++- return 1; +++- } +++- +++- /* SystemStatus: deny unlisted systems entirely */ +++- if (!srif.listed) +++- { +++- snprintf(logbuf, sizeof(logbuf), "DENIED: system is UNLISTED (aka: %s)", srif.aka); +++- do_log(g_conf.logfile, logbuf); +++- config_free(); +++- return 1; +++- } +++- +++- snprintf(logbuf, sizeof(logbuf), "sysop: %s aka: %s status: %s%s caller: %s req: %s", srif.sysop, srif.aka, srif.protected_sess ? "PROTECTED" : "UNPROTECTED", srif.tranx ? " (TRANX)" : "", srif.caller_id[0] ? srif.caller_id : "?", srif.request_list); +++- do_log(g_conf.logfile, logbuf); +++- +++- /* Log rate limiting status if active */ +++- if (g_conf.trackfile[0] && (g_conf.maxfiles > 0 || g_conf.maxbytes > 0)) +++- { +++- snprintf(logbuf, sizeof(logbuf), "rate limits: maxfiles=%d maxbytes=%ld window=%lds", g_conf.maxfiles, g_conf.maxbytes, g_conf.timewindow); +++- do_log(g_conf.logfile, logbuf); +++- } +++- +++- req_f = fopen(srif.request_list, "r"); +++- +++- if (!req_f) +++- { +++- snprintf(logbuf, sizeof(logbuf), "WARN: RequestList not available: %s", srif.request_list); +++- do_log(g_conf.logfile, logbuf); +++- config_free(); +++- return 0; +++- } +++- +++- rsp_f = NULL; +++- +++- if (srif.got_response_list && srif.response_list[0]) +++- { +++- rsp_f = fopen(srif.response_list, "w"); +++- +++- if (!rsp_f) +++- { +++- snprintf(logbuf, sizeof(logbuf), "WARN: cannot create ResponseList: %s", srif.response_list); +++- do_log(g_conf.logfile, logbuf); +++- } +++- } +++- +++- found_count = 0; +++- +++- /* Check and update track file */ +++- while (fgets(line, sizeof(line), req_f)) +++- { +++- char *p; +++- const char *alias_path; +++- +++- str_trim(line); +++- +++- if (!line[0] || line[0] == ';' || line[0] == '#') +++- continue; +++- +++- /* Parse: filename [!password] [+timestamp] [U] */ +++- req_name[0] = '\0'; +++- req_pass[0] = '\0'; +++- req_newer = 0; +++- req_update = 0; +++- +++- if (sscanf(line, "%1023s", req_name) < 1) +++- continue; +++- +++- /* Skip URLs */ +++- if (strncmp(req_name, "http", 4) == 0 || strncmp(req_name, "ftp", 3) == 0) +++- continue; +++- +++- /* Parse modifiers from the rest of the line */ +++- p = strstr(line, req_name); +++- +++- if (p) +++- p += strlen(req_name); +++- else +++- p = line + strlen(line); +++- +++- while (*p) +++- { +++- while (*p == ' ' || *p == '\t') +++- p++; +++- +++- if (*p == '!') +++- { +++- /* !password */ +++- int i = 0; +++- p++; +++- +++- while (*p && *p != ' ' && *p != '\t' && i < (int)sizeof(req_pass) - 1) +++- req_pass[i++] = *p++; +++- +++- req_pass[i] = '\0'; +++- } +++- else if (*p == '+') +++- { +++- /* +unix_timestamp */ +++- p++; +++- req_newer = atol(p); +++- +++- while (*p && *p != ' ' && *p != '\t') +++- p++; +++- } +++- else if (*p == 'U' && (p[1] == '\0' || p[1] == ' ' || p[1] == '\t')) +++- { +++- /* U = update request */ +++- req_update = 1; +++- p++; +++- } +++- else if (*p) +++- p++; /* Skip unknown token */ +++- } +++- +++- found_path[0] = '\0'; +++- +++- /* Check alias table first */ +++- alias_path = find_alias(req_name); +++- +++- if (alias_path) +++- { +++- if (is_abs_path(alias_path)) +++- safe_strncpy(found_path, alias_path, MAXPATHLEN); +++- else +++- path_join(found_path, MAXPATHLEN, g_conf.pubdir, alias_path); +++- +++- if (path_exists(found_path)) +++- found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); +++- else +++- { +++- snprintf(logbuf, sizeof(logbuf), "NOT FOUND (alias): %s -> %s", req_name, found_path); +++- do_log(g_conf.logfile, logbuf); +++- } +++- +++- continue; +++- } +++- +++- /* Wildcard: scan pubdir and all privdirs whose password matches */ +++- if (is_wildcard(req_name)) +++- { +++- DIR *dp; +++- struct dirent *de; +++- PrivDir *pd; +++- +++- /* Scan pubdir (no password needed) */ +++- dp = opendir(g_conf.pubdir); +++- if (dp) +++- { +++- while ((de = readdir(dp)) != NULL) +++- { +++- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) +++- continue; +++- +++- if (!wildmatch(req_name, de->d_name)) +++- continue; +++- +++- path_join(found_path, MAXPATHLEN, g_conf.pubdir, de->d_name); +++- +++- if (path_exists(found_path)) +++- found_count += serve_one(de->d_name, found_path, "", req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); +++- } +++- +++- closedir(dp); +++- } +++- +++- /* Scan matching privdirs */ +++- for (pd = g_conf.privdirs; pd; pd = pd->next) +++- { +++- char rp[64], pp[64]; +++- int ci; +++- +++- safe_strncpy(rp, req_pass, (int)sizeof(rp)); +++- safe_strncpy(pp, pd->password, (int)sizeof(pp)); +++- +++- for (ci = 0; rp[ci]; ci++) +++- rp[ci] = (char)toupper((unsigned char)rp[ci]); +++- +++- for (ci = 0; pp[ci]; ci++) +++- pp[ci] = (char)toupper((unsigned char)pp[ci]); +++- +++- if (strcmp(rp, pp) != 0) +++- continue; +++- +++- dp = opendir(pd->path); +++- +++- if (!dp) +++- continue; +++- +++- while ((de = readdir(dp)) != NULL) +++- { +++- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) +++- continue; +++- +++- if (!wildmatch(req_name, de->d_name)) +++- continue; +++- +++- path_join(found_path, MAXPATHLEN, pd->path, de->d_name); +++- +++- if (path_exists(found_path)) +++- found_count += serve_one(de->d_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); +++- } +++- +++- closedir(dp); +++- } +++- +++- continue; +++- } +++- +++- /* Plain filename: try pubdir first, then privdirs if password given */ +++- path_join(found_path, MAXPATHLEN, g_conf.pubdir, req_name); +++- +++- if (path_exists(found_path)) +++- { +++- found_count += +++- serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); +++- } +++- else if (req_pass[0]) +++- { +++- /* Try each private dir whose password matches */ +++- PrivDir *pd; +++- int served = 0; +++- +++- for (pd = g_conf.privdirs; pd && !served; pd = pd->next) +++- { +++- char rp[64], pp[64]; +++- int ci; +++- +++- safe_strncpy(rp, req_pass, (int)sizeof(rp)); +++- safe_strncpy(pp, pd->password, (int)sizeof(pp)); +++- +++- for (ci = 0; rp[ci]; ci++) +++- rp[ci] = (char)toupper((unsigned char)rp[ci]); +++- +++- for (ci = 0; pp[ci]; ci++) +++- pp[ci] = (char)toupper((unsigned char)pp[ci]); +++- +++- if (strcmp(rp, pp) != 0) +++- continue; +++- +++- path_join(found_path, MAXPATHLEN, pd->path, req_name); +++- +++- if (path_exists(found_path)) +++- { +++- found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); +++- served = 1; +++- } +++- } +++- if (!served) +++- { +++- snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s", req_name); +++- do_log(g_conf.logfile, logbuf); +++- } +++- } +++- else +++- { +++- snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s (pub: %s)", req_name, g_conf.pubdir); +++- do_log(g_conf.logfile, logbuf); +++- } +++- } +++- +++- fclose(req_f); +++- +++- if (rsp_f) +++- fclose(rsp_f); +++- +++- snprintf(logbuf, sizeof(logbuf), "done: %d file(s) found", found_count); +++- do_log(g_conf.logfile, logbuf); +++- +++- /* Save tracking data */ +++- tracking_save(); +++- +++- config_free(); +++- +++- return (found_count > 0) ? 0 : 1; +++-} +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/srifreq.txt binkd_pgul/misc/srifreq.txt +++--- binkd/misc/srifreq.txt 2026-04-26 13:54:14.035795187 +0100 ++++++ binkd_pgul/misc/srifreq.txt 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,67 +0,0 @@ +++-srifreq -- SRIF-compatible file request server +++- +++-USAGE: +++- srifreq --conf +++- +++-DESCRIPTION: +++- SRIF (Standard Request Information Format) compatible file +++- request server for binkd. Processes incoming .req files and +++- serves files based on password protection and aliases. +++- +++-CONFIGURATION FILE FORMAT: +++- # Lines starting with # are comments +++- # Blank lines are ignored +++- +++- pubdir # Public directory (no password required) +++- logfile # Log file, or - to disable +++- aliases # Magic-name aliases file (optional) +++- private # Private directory (requires !password) +++- +++- # Rate limiting options (optional): +++- trackfile # File to track node download statistics +++- maxfiles # Max files per node per time window (0=unlimited) +++- maxsize # Max bytes per node per time window (0=unlimited) +++- timewindow # Time window in seconds (0=no window) +++- +++-ALIASES FILE FORMAT: +++- # Lines starting with # are comments +++- # Format: +++- # Names are matched case-insensitively +++- +++-EXAMPLE CONFIG FILE (srifreq.conf): +++- # srifreq.conf - SRIF Request Server Configuration +++- +++- pubdir Work:Fido/Public +++- logfile Work:Logs/srifreq.log +++- aliases Work:Fido/srifreq.aliases +++- +++- # Private directories (password protected) +++- private Work:Fido/Private/Uploader1 secretpass1 +++- private Work:Fido/Private/Node190 node190pwd +++- +++- # Rate limiting: max 10 files or 50MB per node per 24 hours +++- trackfile Work:Logs/srifreq.track +++- maxfiles 10 +++- maxsize 52428800 +++- timewindow 86400 +++- +++-EXAMPLE ALIASES FILE (srifreq.aliases): +++- # srifreq.aliases - Magic-name to file mappings +++- # Names are case-insensitive +++- +++- DOORWAY Games:Utils/Doorway/doorway.zip +++- NETMAIL Work:Comm/Fido/netmail.lha +++- README Docs:Readme.txt +++- 4DOUT AmiTCP:4DOut.lha +++- BINKD Apps:Comm/Binkd/binkd.lha +++- +++-REQUEST FILE FORMAT (.req): +++- Files listed one per line. Modifiers: +++- !password - Required password for private areas +++- +timestamp - Only serve if file is newer than timestamp +++- U - Update request (only if newer than client's TRANX) +++- +++-EXAMPLES: +++- srifreq --conf srifreq.conf inbound/srif_file.req +++- srifreq --conf srifreq.conf inbound/*.req +++- exec "work:fido/srifreq --conf work:fido/srifreq.conf *S" *.req *.REQ +++\ No hay ningún carácter de nueva línea al final del archivo +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile binkd_pgul/mkfls/amiga/Makefile +++--- binkd/mkfls/amiga/Makefile 2026-04-26 14:55:09.963221741 +0100 ++++++ binkd_pgul/mkfls/amiga/Makefile 2026-04-16 17:49:00.000000000 +0100 +++@@ -26,4 +26,4 @@ +++ $(CC) -c $(CFLAGS) amiga/getfree.c +++ sem.o: +++ $(CC) -c $(CFLAGS) amiga/sem.c +++-include Makefile.dep +++\ No hay ningún carácter de nueva línea al final del archivo ++++include Makefile.dep +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile.analyze.bebbo binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo +++--- binkd/mkfls/amiga/Makefile.analyze.bebbo 2026-04-25 16:52:14.088635220 +0100 ++++++ binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,96 +0,0 @@ +++-# Makefile.analyze.bebbo -- Static analysis for Amiga bebbo (GCC 6.5.0b) +++-# Includes amiga/ code with bebbo-specific defines +++-# Usage: make -f Makefile.analyze.bebbo +++- +++-# All sources including Amiga-specific +++-ALL_SRCS = \ +++- binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ +++- bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ +++- readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ +++- setpttl.c https.c md5b.c crypt.c compress.c \ +++- amiga/rename.c amiga/getfree.c amiga/bsdsock.c amiga/dirent.c \ +++- amiga/utime.c amiga/rfc2553_amiga.c amiga/sem.c amiga/evloop.c \ +++- amiga/sock.c amiga/session.c \ +++- misc/decompress.c misc/freq.c misc/process_tic.c misc/srifreq.c misc/nodelist.c +++- +++-INCLUDES = -I. -Iamiga +++- +++-# bebbo-specific defines (GCC 6.5.0b, HAS native snprintf) +++-BEBBO_DEFINES = \ +++- -DAMIGA \ +++- -DHAVE_SOCKLEN_T \ +++- -DHAVE_INTMAX_T \ +++- -DHAVE_SNPRINTF \ +++- -DHAVE_GETOPT \ +++- -DHAVE_UNISTD_H \ +++- -DHAVE_SYS_TIME_H \ +++- -DHAVE_SYS_PARAM_H \ +++- -DHAVE_SYS_IOCTL_H \ +++- -DHAVE_NETINET_IN_H \ +++- -DHAVE_NETDB_H \ +++- -DHAVE_ARPA_INET_H \ +++- -DHAVE_STDARG_H \ +++- -DHAVE_VSNPRINTF \ +++- -DWITH_ZLIB +++- +++-CPPCHECK_FLAGS = \ +++- --enable=all \ +++- --inconclusive \ +++- --std=c89 \ +++- --quiet \ +++- --suppress=missingIncludeSystem \ +++- --suppress=unusedFunction \ +++- --suppress=checkersReport \ +++- $(INCLUDES) +++- +++-# Log files +++-LOG_DIR = analysis_logs +++-CPPCHECK_LOG = $(LOG_DIR)/cppcheck_bebbo.log +++-CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_bebbo.log +++- +++-.PHONY: all cppcheck clang-tidy analyze clean +++- +++-all: analyze +++- +++-cppcheck: +++- @mkdir -p $(LOG_DIR) +++- @echo "=== cppcheck Amiga bebbo (GCC 6.5.0b) ===" +++- @echo "Defines: bebbo, HAS native snprintf, no snprintf.c needed" +++- @echo "Saving output to: $(CPPCHECK_LOG)" +++- @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) $(ALL_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true +++- @echo "=== done ===" +++- @echo "Log saved: $(CPPCHECK_LOG)" +++- +++-clang-tidy: +++- @mkdir -p $(LOG_DIR) +++- @echo "=== clang-tidy Amiga bebbo ===" +++- @echo "Note: Some Amiga headers may not resolve on Linux host" +++- @echo "Saving output to: $(CLANG_TIDY_LOG)" +++- @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) +++- @for src in binkd.c readcfg.c amiga/evloop.c amiga/session.c; do \ +++- echo "" >> $(CLANG_TIDY_LOG); \ +++- echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ +++- clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ +++- $(INCLUDES) $(BEBBO_DEFINES) -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ +++- done +++- @echo "=== done ===" +++- @echo "Log saved: $(CLANG_TIDY_LOG)" +++- +++-analyze: cppcheck clang-tidy +++- +++-# Focus on Amiga-specific code only +++-amiga-only: +++- @echo "=== cppcheck Amiga-specific code only (bebbo) ===" +++- @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) \ +++- amiga/*.c 2>&1 || true +++- +++-# Check what differs between ADE and bebbo +++-diff-defines: +++- @echo "=== ADE vs bebbo define differences ===" +++- @echo "ADE only: -DHAVE_VSNPRINTF (no -DHAVE_SNPRINTF)" +++- @echo "bebbo: -DHAVE_SNPRINTF -DHAVE_VSNPRINTF" +++- @echo "" +++- @echo "ADE needs snprintf.c, bebbo does not" +++- +++-clean: +++- @true +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile.bebbo binkd_pgul/mkfls/amiga/Makefile.bebbo +++--- binkd/mkfls/amiga/Makefile.bebbo 2026-04-26 13:11:27.902433254 +0100 ++++++ binkd_pgul/mkfls/amiga/Makefile.bebbo 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,208 +0,0 @@ +++-CC = m68k-amigaos-gcc +++- +++-# Common flags for compiler and linker +++-COMMON_CFLAGS = -Wall -Wextra -Wunused -Wunused-function -Wunused-variable -Wmissing-prototypes -Wno-pointer-sign -ffunction-sections -fdata-sections -noixemul +++-ARCH_FLAGS = -O -m68000 -msoft-float -fomit-frame-pointer +++- +++-CFLAGS = $(DEFINES) $(COMMON_CFLAGS) $(ARCH_FLAGS) +++-LIBS = -lz -lm -lamiga +++-LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_gcc.map -s +++- +++-# Tools use same flags as main binary +++-TOOL_CFLAGS = $(DEFINES) $(COMMON_CFLAGS) -O -m68000 -msoft-float -fomit-frame-pointer -I misc +++-TOOL_LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_tools.map -s +++- +++-OBJDIR = objs +++- +++-DEFINES = \ +++- -DAMIGA \ +++- -DHAVE_SOCKLEN_T \ +++- -DHAVE_INTMAX_T \ +++- -DHAVE_SNPRINTF \ +++- -DHAVE_GETOPT \ +++- -DHAVE_UNISTD_H \ +++- -DHAVE_SYS_TIME_H \ +++- -DHAVE_SYS_PARAM_H \ +++- -DHAVE_SYS_IOCTL_H \ +++- -DHAVE_NETINET_IN_H \ +++- -DHAVE_NETDB_H \ +++- -DHAVE_ARPA_INET_H \ +++- -DHTTPS \ +++- -DAMIGADOS_4D_OUTBOUND \ +++- -DHAVE_STDARG_H \ +++- -DHAVE_VSNPRINTF \ +++- -DWITH_ZLIB \ +++- -DOS=\"Amiga\" \ +++- -I. \ +++- -Iamiga +++- +++-SRCS = \ +++- binkd.c \ +++- readcfg.c \ +++- tools.c \ +++- ftnaddr.c \ +++- ftnq.c \ +++- client.c \ +++- server.c \ +++- protocol.c \ +++- bsy.c \ +++- inbound.c \ +++- breaksig.c \ +++- branch.c \ +++- amiga/rename.c \ +++- amiga/getfree.c \ +++- amiga/bsdsock.c \ +++- amiga/dirent.c \ +++- amiga/utime.c \ +++- amiga/rfc2553_amiga.c \ +++- amiga/sem.c \ +++- amiga/evloop.c \ +++- amiga/sock.c \ +++- amiga/session.c \ +++- bsycleanup.c \ +++- amiga/proto_amiga.c \ +++- ftndom.c \ +++- ftnnode.c \ +++- srif.c \ +++- pmatch.c \ +++- readflo.c \ +++- prothlp.c \ +++- iptools.c \ +++- run.c \ +++- binlog.c \ +++- exitproc.c \ +++- getw.c \ +++- xalloc.c \ +++- setpttl.c \ +++- https.c \ +++- md5b.c \ +++- crypt.c \ +++- compress.c +++- +++-OBJS = \ +++- $(OBJDIR)/binkd.o \ +++- $(OBJDIR)/readcfg.o \ +++- $(OBJDIR)/tools.o \ +++- $(OBJDIR)/ftnaddr.o \ +++- $(OBJDIR)/ftnq.o \ +++- $(OBJDIR)/client.o \ +++- $(OBJDIR)/server.o \ +++- $(OBJDIR)/protocol.o \ +++- $(OBJDIR)/bsy.o \ +++- $(OBJDIR)/inbound.o \ +++- $(OBJDIR)/breaksig.o \ +++- $(OBJDIR)/branch.o \ +++- $(OBJDIR)/rename.o \ +++- $(OBJDIR)/getfree.o \ +++- $(OBJDIR)/bsdsock.o \ +++- $(OBJDIR)/dirent.o \ +++- $(OBJDIR)/utime.o \ +++- $(OBJDIR)/rfc2553_amiga.o \ +++- $(OBJDIR)/sem.o \ +++- $(OBJDIR)/evloop.o \ +++- $(OBJDIR)/sock.o \ +++- $(OBJDIR)/session.o \ +++- $(OBJDIR)/bsycleanup.o \ +++- $(OBJDIR)/proto_amiga.o \ +++- $(OBJDIR)/ftndom.o \ +++- $(OBJDIR)/ftnnode.o \ +++- $(OBJDIR)/srif.o \ +++- $(OBJDIR)/pmatch.o \ +++- $(OBJDIR)/readflo.o \ +++- $(OBJDIR)/prothlp.o \ +++- $(OBJDIR)/iptools.o \ +++- $(OBJDIR)/run.o \ +++- $(OBJDIR)/binlog.o \ +++- $(OBJDIR)/exitproc.o \ +++- $(OBJDIR)/getw.o \ +++- $(OBJDIR)/xalloc.o \ +++- $(OBJDIR)/setpttl.o \ +++- $(OBJDIR)/https.o \ +++- $(OBJDIR)/md5b.o \ +++- $(OBJDIR)/crypt.o \ +++- $(OBJDIR)/compress.o +++- +++-all: binkd decompress process_tic freq srifreq nodelist +++- +++-$(OBJDIR): +++- mkdir -p $(OBJDIR) +++- +++-$(OBJDIR)/%.o: %.c +++- $(CC) -c $(CFLAGS) $< -o $@ +++- +++-binkd: $(OBJDIR) $(OBJS) +++- $(CC) $(CFLAGS) -o binkd $(OBJS) $(LIBS) $(LDFLAGS) +++- +++-# ---------- Utility tools (stand-alone, multi-platform) ---------- +++-# TOOL_CFLAGS and TOOL_LDFLAGS defined above +++- +++-decompress: +++- $(CC) $(TOOL_CFLAGS) -o decompress misc/decompress.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) +++- +++-process_tic: +++- $(CC) $(TOOL_CFLAGS) -o process_tic misc/process_tic.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) +++- +++-freq: +++- $(CC) $(TOOL_CFLAGS) -o freq misc/freq.c misc/portable.c $(TOOL_LDFLAGS) +++- +++-srifreq: +++- $(CC) $(TOOL_CFLAGS) -o srifreq misc/srifreq.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) +++- +++-nodelist: +++- $(CC) $(TOOL_CFLAGS) -o nodelist misc/nodelist.c misc/portable.c $(TOOL_LDFLAGS) +++- +++-install: all clean +++- +++-clean: +++- rm -f *.[bo] *.BAK *.core *.obj *.err *~ core +++- rm -rf $(OBJDIR) +++- rm -f binkd decompress process_tic freq srifreq nodelist +++- +++-# ---------- Explicit rules for amiga/ objects ---------- +++-$(OBJDIR)/rename.o: amiga/rename.c +++- $(CC) -c $(CFLAGS) amiga/rename.c -o $(OBJDIR)/rename.o +++- +++-$(OBJDIR)/getfree.o: amiga/getfree.c +++- $(CC) -c $(CFLAGS) amiga/getfree.c -o $(OBJDIR)/getfree.o +++- +++-$(OBJDIR)/sem.o: amiga/sem.c +++- $(CC) -c $(CFLAGS) amiga/sem.c -o $(OBJDIR)/sem.o +++- +++-$(OBJDIR)/bsdsock.o: amiga/bsdsock.c +++- $(CC) -c $(CFLAGS) amiga/bsdsock.c -o $(OBJDIR)/bsdsock.o +++- +++-$(OBJDIR)/dirent.o: amiga/dirent.c +++- $(CC) -c $(CFLAGS) amiga/dirent.c -o $(OBJDIR)/dirent.o +++- +++-$(OBJDIR)/utime.o: amiga/utime.c +++- $(CC) -c $(CFLAGS) amiga/utime.c -o $(OBJDIR)/utime.o +++- +++-$(OBJDIR)/rfc2553_amiga.o: amiga/rfc2553_amiga.c +++- $(CC) -c $(CFLAGS) amiga/rfc2553_amiga.c -o $(OBJDIR)/rfc2553_amiga.o +++- +++-$(OBJDIR)/evloop.o: amiga/evloop.c +++- $(CC) -c $(CFLAGS) amiga/evloop.c -o $(OBJDIR)/evloop.o +++- +++-$(OBJDIR)/sock.o: amiga/sock.c +++- $(CC) -c $(CFLAGS) amiga/sock.c -o $(OBJDIR)/sock.o +++- +++-$(OBJDIR)/session.o: amiga/session.c +++- $(CC) -c $(CFLAGS) amiga/session.c -o $(OBJDIR)/session.o +++- +++-$(OBJDIR)/bsycleanup.o: bsycleanup.c +++- $(CC) -c $(CFLAGS) bsycleanup.c -o $(OBJDIR)/bsycleanup.o +++- +++-$(OBJDIR)/proto_amiga.o: amiga/proto_amiga.c +++- $(CC) -c $(CFLAGS) amiga/proto_amiga.c -o $(OBJDIR)/proto_amiga.o +++- +++-depend Makefile.dep: Makefile +++- $(CC) -MM $(CFLAGS) $(SRCS) $(SYS) | \ +++- awk '{ if ($$1 != prev) { if (rec != "") print rec; \ +++- rec = $$0; prev = $$1; } \ +++- else { if (length(rec $$2) > 78) { print rec; rec = $$0; } \ +++- else rec = rec " " $$2 } } \ +++- END { print rec }' | tee Makefile.dep +++- +++--include Makefile.dep +++- +++-.PHONY: all binkd decompress process_tic freq srifreq nodelist clean install +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/nt95-mingw/Makefile binkd_pgul/mkfls/nt95-mingw/Makefile +++--- binkd/mkfls/nt95-mingw/Makefile 2026-04-26 13:12:24.499178632 +0100 ++++++ binkd_pgul/mkfls/nt95-mingw/Makefile 2026-04-16 17:49:00.000000000 +0100 +++@@ -38,8 +38,7 @@ +++ setpttl.c https.c md5b.c crypt.c getopt.c nt/breaksig.c nt/getfree.c \ +++ nt/sem.c nt/TCPErr.c nt/WSock.c nt/w32tools.c nt/tray.c snprintf.c \ +++ ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c ntlm/des_enc.c \ +++- ntlm/helpers.c \ +++- bsycleanup.c ++++ ntlm/helpers.c +++ +++ RES= nt/binkdres.rc +++ +++@@ -222,32 +221,7 @@ +++ OBJS=$(addprefix $(OBJDIR)/,$(patsubst %.c,%.o, $(SRCS))) +++ RESOBJS=$(addprefix $(OBJDIR)/, $(patsubst %.rc,%.o, $(RES))) +++ +++-# ---------- Utility tools (stand-alone) ---------- +++-TOOL_CFLAGS = -O2 -Wall -DWIN32 -I. -I misc +++- +++-utils: decompress process_tic freq srifreq nodelist +++- +++-decompress: +++- @echo Compiling decompress... +++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/decompress.exe misc/decompress.c misc/portable.c +++- +++-process_tic: +++- @echo Compiling process_tic... +++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/process_tic.exe misc/process_tic.c misc/portable.c +++- +++-freq: +++- @echo Compiling freq... +++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/freq.exe misc/freq.c misc/portable.c +++- +++-srifreq: +++- @echo Compiling srifreq... +++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/srifreq.exe misc/srifreq.c misc/portable.c +++- +++-nodelist: +++- @echo Compiling nodelist... +++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/nodelist.exe misc/nodelist.c misc/portable.c +++- +++-.PHONY: all printinfo install html clean distclean makedirs utils decompress process_tic freq srifreq nodelist ++++.PHONY: all printinfo install html clean distclean makedirs +++ +++ all: printinfo makedirs $(OUTDIR)/$(BINKDEXE) +++ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/nt95-msvc/Makefile binkd_pgul/mkfls/nt95-msvc/Makefile +++--- binkd/mkfls/nt95-msvc/Makefile 2026-04-26 12:41:43.750120247 +0100 ++++++ binkd_pgul/mkfls/nt95-msvc/Makefile 2026-04-16 17:49:00.000000000 +0100 +++@@ -327,32 +327,7 @@ +++ +++ BINKDEXE = $(BINKDNAME).exe +++ +++-all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) utils +++- +++-TOOL_CFLAGS = -nologo -W3 -O2 -DWIN32 -DVISUALCPP -I. -I misc +++-TOOL_CC = $(CC) $(TOOL_CFLAGS) +++- +++-utils: "$(OUTDIR)\decompress.exe" "$(OUTDIR)\process_tic.exe" "$(OUTDIR)\freq.exe" "$(OUTDIR)\srifreq.exe" "$(OUTDIR)\nodelist.exe" +++- +++-"$(OUTDIR)\decompress.exe": misc\decompress.c misc\portable.c nt\dirwin32.c +++- @echo Compiling decompress... +++- @$(TOOL_CC) -Fe"$(OUTDIR)\decompress.exe" misc\decompress.c misc\portable.c nt\dirwin32.c +++- +++-"$(OUTDIR)\process_tic.exe": misc\process_tic.c misc\portable.c nt\dirwin32.c +++- @echo Compiling process_tic... +++- @$(TOOL_CC) -Fe"$(OUTDIR)\process_tic.exe" misc\process_tic.c misc\portable.c nt\dirwin32.c +++- +++-"$(OUTDIR)\freq.exe": misc\freq.c misc\portable.c +++- @echo Compiling freq... +++- @$(TOOL_CC) -Fe"$(OUTDIR)\freq.exe" misc\freq.c misc\portable.c +++- +++-"$(OUTDIR)\srifreq.exe": misc\srifreq.c misc\portable.c nt\dirwin32.c +++- @echo Compiling srifreq... +++- @$(TOOL_CC) -Fe"$(OUTDIR)\srifreq.exe" misc\srifreq.c misc\portable.c nt\dirwin32.c +++- +++-"$(OUTDIR)\nodelist.exe": misc\nodelist.c misc\portable.c +++- @echo Compiling nodelist... +++- @$(TOOL_CC) -Fe"$(OUTDIR)\nodelist.exe" misc\nodelist.c misc\portable.c ++++all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) +++ +++ printinfo: +++ @echo on +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/os2-emx/Makefile binkd_pgul/mkfls/os2-emx/Makefile +++--- binkd/mkfls/os2-emx/Makefile 2026-04-26 13:12:44.342586479 +0100 ++++++ binkd_pgul/mkfls/os2-emx/Makefile 2026-04-16 17:49:00.000000000 +0100 +++@@ -13,7 +13,7 @@ +++ LFLAGS=-Los2 +++ LIBS=-lsocket -lresolv +++ NTLM_SRC=ntlm/des_enc.c ntlm/helpers.c ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c +++-SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c bsycleanup.c ${NTLM_SRC} ++++SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c ${NTLM_SRC} +++ TARGET=binkd2emx +++ +++ #PERLDIR=../perl5.00553/os2 +++@@ -122,33 +122,7 @@ +++ +++ TARGET:=$(TARGET).exe +++ +++-all: $(TARGET) utils +++- +++-TOOL_CFLAGS = $(CFLAGS) -I. -I misc +++- +++-utils: decompress process_tic freq srifreq nodelist +++- +++-decompress: misc/decompress.c +++- @echo Compiling decompress... +++- @$(CC) $(TOOL_CFLAGS) -o decompress.exe misc/decompress.c misc/portable.c os2/dirent.c +++- +++-process_tic: misc/process_tic.c +++- @echo Compiling process_tic... +++- @$(CC) $(TOOL_CFLAGS) -o process_tic.exe misc/process_tic.c misc/portable.c os2/dirent.c +++- +++-freq: misc/freq.c +++- @echo Compiling freq... +++- @$(CC) $(TOOL_CFLAGS) -o freq.exe misc/freq.c misc/portable.c +++- +++-srifreq: misc/srifreq.c +++- @echo Compiling srifreq... +++- @$(CC) $(TOOL_CFLAGS) -o srifreq.exe misc/srifreq.c misc/portable.c os2/dirent.c +++- +++-nodelist: misc/nodelist.c +++- @echo Compiling nodelist... +++- @$(CC) $(TOOL_CFLAGS) -o nodelist.exe misc/nodelist.c misc/portable.c +++- +++-.PHONY: utils decompress process_tic freq srifreq nodelist ++++all: $(TARGET) +++ +++ .c.o: +++ @echo Compiling $*.c... +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/unix/Makefile.analyze.unix binkd_pgul/mkfls/unix/Makefile.analyze.unix +++--- binkd/mkfls/unix/Makefile.analyze.unix 2026-04-25 16:52:02.225860998 +0100 ++++++ binkd_pgul/mkfls/unix/Makefile.analyze.unix 1970-01-01 00:00:00.000000000 +0000 +++@@ -1,65 +0,0 @@ +++-# Makefile.analyze.unix -- Static analysis for Unix/Linux code only +++-# Excludes Amiga-specific code (amiga/ directory) +++-# Usage: make -f Makefile.analyze.unix +++- +++-# Unix-portable sources only (no amiga/ directory) +++-UNIX_SRCS = \ +++- binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ +++- bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ +++- readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ +++- setpttl.c https.c md5b.c crypt.c compress.c +++- +++-# Unix-specific files (if any) +++-UNIX_SYS_SRCS = \ +++- unix/getwd.c unix/lock.c unix/tcperr.c +++- +++-INCLUDES = -I. -Iunix +++- +++-CPPCHECK_FLAGS = \ +++- --enable=all \ +++- --inconclusive \ +++- --std=c89 \ +++- --quiet \ +++- --suppress=missingIncludeSystem \ +++- --suppress=unusedFunction \ +++- $(INCLUDES) +++- +++-# Log files +++-LOG_DIR = analysis_logs +++-CPPCHECK_LOG = $(LOG_DIR)/cppcheck_unix.log +++-CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_unix.log +++- +++-.PHONY: all cppcheck clang-tidy analyze clean +++- +++-all: analyze +++- +++-cppcheck: +++- @mkdir -p $(LOG_DIR) +++- @echo "=== cppcheck Unix/Linux code ===" +++- @echo "Saving output to: $(CPPCHECK_LOG)" +++- @cppcheck $(CPPCHECK_FLAGS) $(UNIX_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true +++- @echo "=== done ===" +++- @echo "Log saved: $(CPPCHECK_LOG)" +++- +++-clang-tidy: +++- @mkdir -p $(LOG_DIR) +++- @echo "=== clang-tidy Unix/Linux code ===" +++- @echo "Saving output to: $(CLANG_TIDY_LOG)" +++- @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) +++- @for src in $(UNIX_SRCS); do \ +++- echo "" >> $(CLANG_TIDY_LOG); \ +++- echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ +++- clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ +++- $(INCLUDES) -DUNIX -DHAVE_SNPRINTF -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ +++- done +++- @echo "=== done ===" +++- @echo "Log saved: $(CLANG_TIDY_LOG)" +++- +++-analyze: cppcheck clang-tidy +++- +++-quick: +++- @echo "=== Quick error check (Unix) ===" +++- @cppcheck --enable=error --std=c89 --quiet $(INCLUDES) $(UNIX_SRCS) 2>&1 || true +++- +++-clean: +++- @true +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/unix/Makefile.in binkd_pgul/mkfls/unix/Makefile.in +++--- binkd/mkfls/unix/Makefile.in 2026-04-26 13:11:58.084940863 +0100 ++++++ binkd_pgul/mkfls/unix/Makefile.in 2026-04-16 17:49:00.000000000 +0100 +++@@ -12,17 +12,17 @@ +++ MANDIR=$(DATADIR)/man +++ DOCDIR=$(DATADIR)/doc/$(APPL) +++ +++-SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c bsycleanup.c @OPT_SRC@ ++++SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c @OPT_SRC@ +++ OBJS=${SRCS:.c=.o} +++ AUTODEFS=@DEFS@ +++ AUTOLIBS=@LIBS@ +++-DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -DPROTOTYPES ++++DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" +++ CPPFLAGS=@CPPFLAGS@ +++ CFLAGS=@CFLAGS@ +++ LDFLAGS=@LDFLAGS@ +++ LIBS=$(AUTOLIBS) +++ +++-all: compile banner utils ++++all: compile banner +++ +++ compile: $(APPL) +++ +++@@ -48,32 +48,6 @@ +++ @echo " run \`configure --prefix=/another/path' and go to step 1. " +++ @echo +++ +++-utils: decompress process_tic freq srifreq nodelist +++- +++-TOOL_CFLAGS = $(DEFINES) $(CPPFLAGS) $(CFLAGS) -I. -I misc +++- +++-decompress: misc/decompress.c misc/portable.c misc/portable.h +++- @echo Compiling decompress... +++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/decompress.c misc/portable.c +++- +++-process_tic: misc/process_tic.c misc/portable.c misc/portable.h +++- @echo Compiling process_tic... +++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/process_tic.c misc/portable.c +++- +++-freq: misc/freq.c misc/portable.c misc/portable.h +++- @echo Compiling freq... +++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/freq.c misc/portable.c +++- +++-srifreq: misc/srifreq.c misc/portable.c misc/portable.h +++- @echo Compiling srifreq... +++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/srifreq.c misc/portable.c +++- +++-nodelist: misc/nodelist.c misc/portable.c +++- @echo Compiling nodelist... +++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/nodelist.c misc/portable.c +++- +++-.PHONY: decompress process_tic freq srifreq nodelist +++- +++ .version: $(APPL) +++ @./$(APPL) -v | $(AWK) '{ print $$2; }' > $@ +++ +++@@ -92,7 +66,6 @@ +++ clean: +++ rm -f *.[bo] unix/*.[bo] ntlm/*.[bo] *.BAK *.core *.obj *.err +++ rm -f *~ core config.cache config.log config.status +++- rm -f decompress process_tic freq srifreq nodelist +++ +++ cleanall: clean +++ rm -f $(APPL) Makefile Makefile.dep Makefile.in +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/protocol.c binkd_pgul/protocol.c +++--- binkd/protocol.c 2026-04-26 09:45:52.230049023 +0100 ++++++ binkd_pgul/protocol.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -45,19 +45,12 @@ +++ #include "md5b.h" +++ #include "crypt.h" +++ #include "compress.h" +++-#ifdef AMIGA +++-#include "amiga/proto_amiga.h" +++-#endif +++ +++ #ifdef WITH_PERL +++ #include "perlhooks.h" +++ #endif +++ #include "rfc2553.h" +++ +++-#if defined(HAVE_THREADS) || defined(AMIGA) +++-extern MUTEXSEM lsem; +++-#endif +++- +++ /* define to enable val's code for -ip checks (default is gul's code) */ +++ #undef VAL_STYLE +++ #ifdef VAL_STYLE +++@@ -70,7 +63,7 @@ +++ /* +++ * Fills <> with initial values, allocates buffers, etc. +++ */ +++-int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) ++++static int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) +++ { +++ char val[4]; +++ socklen_t lval; +++@@ -133,12 +126,6 @@ +++ #endif +++ setsockopts (state->s_in = socket_in); +++ setsockopts (state->s_out = socket_out); +++- +++-#if defined(AMIGA) +++- setsockopts_amiga(socket_in, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); +++- setsockopts_amiga(socket_out, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); +++-#endif +++- +++ TF_ZERO (&state->in); +++ TF_ZERO (&state->out); +++ TF_ZERO (&state->flo); +++@@ -194,7 +181,7 @@ +++ /* +++ * Clears protocol buffers and queues, closes files, etc. +++ */ +++-int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) ++++static int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) +++ { +++ int i; +++ +++@@ -238,7 +225,7 @@ +++ } +++ +++ /* Process rcvdlist */ +++-FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) ++++static FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) +++ { +++ int i; +++ +++@@ -328,7 +315,7 @@ +++ /* +++ * Sends next msg from the msg queue or next data block +++ */ +++-int send_block (STATE *state, BINKD_CONFIG *config) ++++static int send_block (STATE *state, BINKD_CONFIG *config) +++ { +++ int i, n, save_errno; +++ const char *save_err; +++@@ -2104,7 +2091,7 @@ +++ return 0; +++ } +++ +++-int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) ++++static int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) +++ { +++ char buf[MAXPATHLEN+1]; +++ FILE *f; +++@@ -2158,8 +2145,7 @@ +++ +++ *extra = ""; +++ if (state->z_cansend && state->extcmd && state->out.size >= config->zminsize +++- && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first) +++- && !(state->to && state->to->NC_flag)) { ++++ && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first)) { +++ #ifdef WITH_BZLIB2 +++ if (!state->z_send && (state->z_cansend & 2)) { +++ *extra = " BZ2"; state->z_send = 2; +++@@ -2462,7 +2448,6 @@ +++ { +++ char szAddr[FTN_ADDR_SZ + 1]; +++ +++- memset(szAddr, 0, sizeof(szAddr)); +++ ftnaddress_to_str (szAddr, &state->sent_fls[n].fa); +++ state->bytes_sent += state->sent_fls[n].size; +++ ++state->files_sent; +++@@ -2553,7 +2538,7 @@ +++ }; +++ +++ /* Recvs next block, processes msgs or writes down the data from the remote */ +++-int recv_block (STATE *state, BINKD_CONFIG *config) ++++static int recv_block (STATE *state, BINKD_CONFIG *config) +++ { +++ int no; +++ +++@@ -2785,7 +2770,7 @@ +++ return 1; +++ } +++ +++-int banner (STATE *state, BINKD_CONFIG *config) ++++static int banner (STATE *state, BINKD_CONFIG *config) +++ { +++ int tz; +++ char szLocalTime[60]; +++@@ -2865,7 +2850,7 @@ +++ return 1; +++ } +++ +++-int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) ++++static int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) +++ { +++ struct stat sb; +++ FILE *f = NULL; +++@@ -3046,7 +3031,7 @@ +++ return 1; +++ } +++ +++-void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) ++++static void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) +++ { +++ char szFTNAddr[FTN_ADDR_SZ + 1]; +++ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readcfg.c binkd_pgul/readcfg.c +++--- binkd/readcfg.c 2026-04-26 15:04:45.397917107 +0100 ++++++ binkd_pgul/readcfg.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -44,10 +44,6 @@ +++ */ +++ BINKD_CONFIG *current_config; +++ +++-#ifdef AMIGA +++-extern struct SignalSemaphore config_sem; +++-#endif +++- +++ /* +++ * Temporary static structure for configuration reading +++ */ +++@@ -214,15 +210,9 @@ +++ snprintf(c->iport, sizeof(c->iport), "%s", find_port("")); +++ snprintf(c->oport, sizeof(c->oport), "%s", find_port("")); +++ c->call_delay = 60; +++- c->no_call_delay = 0; +++ c->rescan_delay = 60; +++ c->nettimeout = DEF_TIMEOUT; +++ c->oblksize = DEF_BLKSIZE; +++- +++-#ifdef AMIGA +++- c->tcp_nodelay = 0; +++-#endif +++- +++ #if defined(WITH_ZLIB) || defined(WITH_BZLIB2) +++ c->zminsize = 1024; +++ c->zlevel = 0; +++@@ -401,16 +391,8 @@ +++ {"oport", read_port, &work_config.oport, 0, 0}, +++ {"rescan-delay", read_time, &work_config.rescan_delay, 1, DONT_CHECK}, +++ {"call-delay", read_time, &work_config.call_delay, 1, DONT_CHECK}, +++- {"no-call-delay", read_bool, &work_config.no_call_delay, 0, 0}, +++ {"timeout", read_time, &work_config.nettimeout, 1, DONT_CHECK}, +++ {"oblksize", read_int, &work_config.oblksize, MIN_BLKSIZE, MAX_BLKSIZE}, +++- +++-#ifdef AMIGA +++- {"tcp-nodelay", read_bool, &work_config.tcp_nodelay, 0, 0}, +++- {"so-sndbuf", read_int, &work_config.so_sndbuf, 0, 65535}, +++- {"so-rcvbuf", read_int, &work_config.so_rcvbuf, 0, 65535}, +++-#endif +++- +++ {"maxservers", read_int, &work_config.max_servers, 0, DONT_CHECK}, +++ {"maxclients", read_int, &work_config.max_clients, 0, DONT_CHECK}, +++ {"inbound", read_string, work_config.inbound, 'd', 0}, +++@@ -684,7 +666,7 @@ +++ exp_ftnaddress (&fa, work_config.pAddr, work_config.nAddr, work_config.pDomains.first); +++ pn = add_node (&fa, NULL, password, pkt_pwd, out_pwd, '-', NULL, NULL, +++ NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, +++- HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, ++++ HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, +++ #ifdef BW_LIM +++ BW_DEF, BW_DEF, +++ #endif +++@@ -848,10 +830,11 @@ +++ if (!new_config) +++ { +++ /* Config error. Abort or continue? */ +++- unlock_config_structure(&work_config, 0); +++- +++- if (current_config) +++- Log(1, "error in configuration, using old config"); ++++ if (current_config) ++++ { ++++ Log(1, "error in configuration, using old config"); ++++ unlock_config_structure(&work_config, 0); ++++ } +++ } +++ +++ return new_config; +++@@ -874,16 +857,6 @@ +++ Log (2, "got SIGHUP"); +++ need_reload = got_sighup; +++ got_sighup = 0; +++-#elif defined(AMIGA) +++- /* No SIGHUP on AmigaOS: detect config change by mtime */ +++- need_reload = 0; +++- if (current_config->config_list.first) +++- { +++- if (stat(current_config->config_list.first->path, &sb) == 0 && +++- current_config->config_list.first->mtime != 0 && +++- sb.st_mtime != current_config->config_list.first->mtime) +++- need_reload = 1; +++- } +++ #else +++ need_reload = 0; +++ #endif +++@@ -913,75 +886,8 @@ +++ } +++ #endif +++ +++- if (!need_reload) +++- return 0; +++- +++-#ifdef AMIGA +++- /* Prevent reload storms and partial-file reads. +++- * +++- * On AmigaOS (and some Unix editors), config files are written in multiple +++- * passes, so binkd may see the mtime change while the file is still being +++- * written. Attempting to parse an incomplete file gives "unknown keyword" +++- * errors, and rapid repeated mtime changes cause bind() to fail because the +++- * previous listen socket has not been released yet. +++- * +++- * Strategy: after first detecting a change, wait until the mtime has been +++- * stable for at least 2 seconds before actually reloading. Also enforce a +++- * minimum of 5 seconds between successive successful reloads. +++- */ +++- { +++- static time_t last_reload = 0; /* time of last successful reload */ +++- static time_t change_seen = 0; /* time we first noticed the change */ +++- static time_t stable_mtime = 0; /* mtime we are waiting to stabilize */ +++- static int reload_pending = 0; /* persists between calls */ +++- time_t now = time(NULL); +++- +++- /* The loop has already updated pc->mtime, so in the next call +++- * need_reload will be 0 even though we haven't reloaded yet. +++- * reload_pending keeps the reload intent alive. +++- */ +++- if (need_reload) reload_pending = 1; +++- +++- if (!reload_pending) +++- return 0; +++- +++- /* Get the mtime of the primary config file */ +++- { struct stat sb2; +++- time_t cur_mtime = 0; +++- +++- if (current_config->config_list.first && stat(current_config->config_list.first->path, &sb2) == 0) +++- cur_mtime = sb2.st_mtime; +++- +++- /* mtime just changed (or changed again) — reset the stability clock */ +++- if (cur_mtime != stable_mtime) +++- { +++- stable_mtime = cur_mtime; +++- change_seen = now; +++- Log(5, "checkcfg: config mtime changed, waiting for stability..."); +++- return 0; +++- } +++- +++- /* mtime has been stable since change_seen */ +++- if (now - change_seen < 2) +++- { +++- Log(5, "checkcfg: config not yet stable (%lds), waiting...", +++- (long)(now - change_seen)); +++- return 0; +++- } +++- } +++- +++- /* Enforce minimum gap between reloads to let the OS release sockets */ +++- if (now - last_reload < 5) +++- { +++- Log(5, "checkcfg: reload suppressed (too soon, %lds)", (long)(now - last_reload)); +++- return 0; +++- } +++- +++- last_reload = now; +++- reload_pending = 0; /* Once the intent has been consumed, the reload is executed */ +++- } +++-#endif +++- ++++ if (!need_reload) ++++ return 0; +++ /* Reload starting from first file in list */ +++ Log(2, "Reloading configuration..."); +++ pc = current_config->config_list.first; +++@@ -1219,7 +1125,7 @@ +++ char *w[ARGNUM], *tmp, *pkt_pwd, *out_pwd, *pipe; +++ int i, j; +++ int NR_flag = NR_USE_OLD, ND_flag = ND_USE_OLD, HC_flag = HC_USE_OLD, +++- MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, NC_flag = NC_USE_OLD, restrictIP = RIP_USE_OLD, ++++ MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, restrictIP = RIP_USE_OLD, +++ IP_afamily = AF_USE_OLD; +++ #ifdef BW_LIM +++ long bw_send = BW_DEF, bw_recv = BW_DEF; +++@@ -1269,8 +1175,6 @@ +++ HC_flag = HC_OFF; +++ else if (STRICMP (tmp, "-noproxy") == 0) +++ NP_flag = NP_ON; +++- else if (STRICMP (tmp, "-nc") == 0) +++- NC_flag = NC_ON; +++ #ifdef BW_LIM +++ else if (STRICMP (tmp, "-bw") == 0) +++ { +++@@ -1354,7 +1258,7 @@ +++ +++ split_passwords(w[2], &pkt_pwd, &out_pwd); +++ pn = add_node (&fa, w[1], w[2], pkt_pwd, out_pwd, (char)(w[3] ? w[3][0] : '-'), w[4], w[5], +++- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, ++++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, +++ IP_afamily, +++ #ifdef BW_LIM +++ bw_send, bw_recv, +++@@ -2087,9 +1991,6 @@ +++ if (fn->pkt_pwd) pwd_len += strlen(fn->pkt_pwd)+1; else pwd_len += 2; +++ if (fn->out_pwd) pwd_len += strlen(fn->out_pwd)+1; else pwd_len += 2; +++ pwd = calloc (1, pwd_len+1); +++- /* Guard against null pointer dereference if calloc fails */ +++- if (!pwd) +++- return 0; +++ strcpy(pwd, fn->pwd); +++ if (fn->pkt_pwd != (char*)&(fn->pwd) || fn->out_pwd != (char*)&(fn->pwd)) { +++ strcat(strcat(pwd, ","), (fn->pkt_pwd) ? fn->pkt_pwd : "-"); +++@@ -2423,3 +2324,4 @@ +++ return 1; +++ } +++ #endif ++++ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readcfg.h binkd_pgul/readcfg.h +++--- binkd/readcfg.h 2026-04-25 20:39:08.569961699 +0100 ++++++ binkd_pgul/readcfg.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -111,16 +111,8 @@ +++ #endif +++ int nettimeout; +++ int connect_timeout; +++- +++-#ifdef AMIGA +++- int tcp_nodelay; +++- int so_sndbuf; +++- int so_rcvbuf; +++-#endif +++- +++- int call_delay; +++- int no_call_delay; +++ int rescan_delay; ++++ int call_delay; +++ int max_servers; +++ int max_clients; +++ int kill_dup_partial_files; +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readdir.h binkd_pgul/readdir.h +++--- binkd/readdir.h 2026-04-22 20:37:22.121954324 +0100 ++++++ binkd_pgul/readdir.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -23,8 +23,6 @@ +++ #elif defined(__MINGW32__) +++ #include +++ #include +++-#elif defined(AMIGA) +++-#include "amiga/dirent.h" +++ #else +++ #include +++ #include +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/README.md binkd_pgul/README.md +++--- binkd/README.md 2026-04-22 18:44:10.839451402 +0100 ++++++ binkd_pgul/README.md 2026-04-16 17:49:00.000000000 +0100 +++@@ -6,79 +6,6 @@ +++ +++ ## Compiling +++ +++-AmigaOS: +++- +++-Copy "mkfls/amiga/Makefile" to root and make +++- +++-Need ADE to compile project with ixemul library. +++- +++-https://aminet.net/package/dev/gcc/ADE +++- +++-It no longer requires ADE to be installed for execution, as it doesn't need /bin/sh to run scripts or external programs from binkd.conf. However, it does require ixemul.library and ixnet.library, either in the libs directory or in the same directory as the executable. +++- +++-You can find the ADE ixemul.library and ixnet.library libraries in the aminet package or in the released versions, along with the program and utilities already compiled. +++- +++-https://github.com/skbn/binkd/releases +++- +++-5.Work:fido> version work:fido/ixnet.library +++-ixnet.library 63.1 +++- +++-5.Work:fido> version work:fido/ixemul.library +++-ixemul.library 63.1 +++- +++- +++-I've attached six programs for your assistance: +++- +++-[decompress] which decompresses incoming files in lha or zip format, if necessary. +++- +++-``` +++-[freq] which generates file requests in ASO mode and places the necessary files in outbound directory. +++- +++-Usage:freq Z:N/NODE[.POINT] +++-``` +++-``` +++-[freq_bso] same but BSO style +++-Usage: freq_bso Z:N/NODE[.POINT] +++- No point : .0ZZ/NNNNNNNN.req +++- Point : .0ZZ/N +++-``` +++- +++-``` +++-[process_tic] which processes tic files and places them in the filebox folder. +++-With --copypublic option, copy the file you receive to a directory named pub/ from PROGDIR +++->> exec "path/process_tic --copypublic" *.tic *.TIC +++->> exec "path/process_tic" *.tic *.TIC +++-``` +++- +++-``` +++-[srifreq] copy of "misc/srifreq" but in c. SRIF-compatible file-request server for binkd +++- +++-Usage: srifreq [] +++- SRIF control file passed by binkd (exec "srifreq *S") +++- directory containing public downloadable files +++- log file path (pass "" or - to disable logging) +++- optional file mapping magic names to real paths +++- +++-Aliases file format (one alias per line, # = comment): +++-MAGIC_NAME relative/or/absolute/path/to/file +++-Example: +++-NODELIST pub/NODELIST.ZIP +++-FILES pub/ALLFILES.TXT +++- +++-binkd.conf example: exec "srifreq *S pub log/srifreq.log aliases.txt" *.req +++-``` +++- +++-``` +++-[nodelist] FidoNet nodelist compiler for binkd - AmigaOS version in c from "misc/nodelist.pl" +++- +++-Usage: nodelist [] +++-``` +++- +++-Also compileable on *nix >> "gcc -O2 -Wall -o nodelist nodelist.c" +++- +++-BUGFIXES: +++-Option "-C" to reload config It remains unstable +++- +++ non-UNIX: +++ +++ 1. Find in mkfls/ a subdirectory for your system/compiler, copy all files +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/rfc2553.h binkd_pgul/rfc2553.h +++--- binkd/rfc2553.h 2026-04-26 10:31:43.475331092 +0100 ++++++ binkd_pgul/rfc2553.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -21,52 +21,8 @@ +++ +++ #include "iphdr.h" +++ +++-/* Amiga: define EAI_* before including netdb.h to prevent libnix redefinition */ +++-#if defined(AMIGA) +++-#include +++-#include +++-#include +++-#include +++- +++- #undef EAI_NONAME +++- #undef EAI_AGAIN +++- #undef EAI_FAIL +++- #undef EAI_NODATA +++- #undef EAI_FAMILY +++- #undef EAI_SOCKTYPE +++- #undef EAI_SERVICE +++- #undef EAI_ADDRFAMILY +++- #undef EAI_MEMORY +++- #undef EAI_SYSTEM +++- #undef EAI_UNKNOWN +++- #define EAI_NONAME -1 +++- #define EAI_AGAIN -2 +++- #define EAI_FAIL -3 +++- #define EAI_NODATA -4 +++- #define EAI_FAMILY -5 +++- #define EAI_SOCKTYPE -6 +++- #define EAI_SERVICE -7 +++- #define EAI_ADDRFAMILY -8 +++- #define EAI_MEMORY -9 +++- #define EAI_SYSTEM -10 +++- #define EAI_UNKNOWN -11 +++- +++-#include +++- +++-/* EAI_ADDRFAMILY is BSD/macOS specific; Linux/glibc does not define it +++- * Map it to EAI_FAMILY which has the same meaning on those platforms */ +++-#ifndef EAI_ADDRFAMILY +++-#ifdef EAI_FAMILY +++-#define EAI_ADDRFAMILY EAI_FAMILY +++-#else +++-#define EAI_ADDRFAMILY -9 +++-#endif +++-#endif +++- +++-#endif +++- +++ /* Autosense getaddrinfo */ +++-#if defined(AI_PASSIVE) && defined(EAI_NONAME) && !defined(AMIGA) ++++#if defined(AI_PASSIVE) && defined(EAI_NONAME) +++ #define HAVE_GETADDRINFO +++ #endif +++ +++@@ -91,18 +47,6 @@ +++ }; +++ #define addrinfo addrinfo_emu +++ +++-#ifdef AMIGA +++-#ifdef getaddrinfo +++-#undef getaddrinfo +++-#endif +++-#ifdef freeaddrinfo +++-#undef freeaddrinfo +++-#endif +++-#ifdef gai_strerror +++-#undef gai_strerror +++-#endif +++-#endif +++- +++ int getaddrinfo(const char *nodename, const char *servname, +++ const struct addrinfo *hints, +++ struct addrinfo **res); +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/run.c binkd_pgul/run.c +++--- binkd/run.c 2026-04-26 10:31:17.108612481 +0100 ++++++ binkd_pgul/run.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -18,11 +18,6 @@ +++ #ifdef HAVE_UNISTD_H +++ #include +++ #endif +++-#ifdef AMIGA +++-#include +++-#include +++-#include +++-#endif +++ +++ #include "sys.h" +++ #include "run.h" +++@@ -45,128 +40,10 @@ +++ #define SHELL (getenv("COMSPEC") ? getenv("COMSPEC") : "command.com") +++ #define SHELL_META "\"\'\\%<>|&^@" +++ #define SHELLOPT "/c" +++-#elif defined(AMIGA) +++-/* AmigaOS shell */ +++-#define SHELL "c:execute" +++-#define SHELL_META "\"\'\\*?(){};&|<>" +++ #else +++ #error "Unknown platform" +++ #endif +++ +++-#ifdef AMIGA +++-/* run(): execute an AmigaDOS command via SystemTagList() with NIL: I/O +++- * Runs synchronously so binkd waits for completion (needed for srifreq +++- * to create .rsp before parse_response). NIL: I/O prevents CLI freezing +++- * Error output goes to a temporary file for logging on failure */ +++-int run(char *cmd) +++-{ +++- /* All declarations at the top for C89/ADE GCC 2.95 compatibility */ +++- BPTR nil_in; +++- char errfile[MAXPATHLEN]; +++- BPTR err_out = 0; +++- int rc = 0; +++- char cmd_copy[MAXPATHLEN]; +++- char *cmd_start; +++- char *cmd_end; +++- BPTR lock; +++- struct TagItem exec_tags[5]; +++- BPTR errfile_ptr; +++- char buf[512]; +++- int len; +++- +++- /* Open NIL: for input/output */ +++- nil_in = Open("NIL:", MODE_OLDFILE); +++- +++- /* Create temporary error file in current directory */ +++- snprintf(errfile, sizeof(errfile), "binkd_err_%ld.txt", (long)time(NULL)); +++- err_out = Open(errfile, MODE_NEWFILE); +++- if (err_out == 0) +++- { +++- Log(2, "cannot create error file %s, using NIL: for error output", errfile); +++- } +++- +++- /* Extract the command (first word) to check if it exists */ +++- strncpy(cmd_copy, cmd, sizeof(cmd_copy) - 1); +++- cmd_copy[sizeof(cmd_copy) - 1] = '\0'; +++- cmd_start = cmd_copy; /* Work on copy, never modify original cmd */ +++- +++- /* Skip leading whitespace */ +++- while (*cmd_start && (*cmd_start == ' ' || *cmd_start == '\t')) +++- cmd_start++; +++- +++- /* Find end of command (first space or end) */ +++- cmd_end = cmd_start; +++- while (*cmd_end && *cmd_end != ' ' && *cmd_end != '\t') +++- cmd_end++; +++- *cmd_end = '\0'; +++- +++- /* Check if command exists */ +++- lock = Lock((STRPTR)cmd_start, SHARED_LOCK); +++- if (lock == 0) +++- { +++- Log(2, "command not found, skipping: '%s'", cmd_start); +++- Close(nil_in); +++- if (err_out) +++- Close(err_out); +++- DeleteFile((STRPTR)errfile); +++- return 0; +++- } +++- UnLock(lock); +++- +++- Log(3, "executing '%s'", cmd); +++- +++- /* Set up tags with NIL: input and error file output. +++- * Use NP_* (New Process) tags instead of SYS_* to avoid sharing +++- * file handles with parent process - prevents stderr from being +++- * closed when child exits. */ +++- exec_tags[0].ti_Tag = NP_Input; +++- exec_tags[0].ti_Data = (ULONG)nil_in; +++- exec_tags[1].ti_Tag = NP_Output; +++- exec_tags[1].ti_Data = (ULONG)nil_in; +++- exec_tags[2].ti_Tag = NP_Error; +++- exec_tags[2].ti_Data = (ULONG)err_out; +++- exec_tags[3].ti_Tag = NP_Synchronous; +++- exec_tags[3].ti_Data = TRUE; +++- exec_tags[4].ti_Tag = TAG_DONE; +++- exec_tags[4].ti_Data = 0; +++- +++- rc = SystemTagList((STRPTR)cmd, exec_tags); +++- +++- /* Close handles */ +++- Close(nil_in); +++- if (err_out) +++- Close(err_out); +++- +++- /* Log error output if command failed */ +++- if (rc != 0) +++- { +++- errfile_ptr = Open(errfile, MODE_OLDFILE); +++- if (errfile_ptr) +++- { +++- Log(2, "command failed with rc=%d, output:", rc); +++- while ((len = Read(errfile_ptr, buf, sizeof(buf) - 1)) > 0) +++- { +++- buf[len] = '\0'; +++- Log(2, "%s", buf); +++- } +++- Close(errfile_ptr); +++- } +++- } +++- +++- DeleteFile((STRPTR)errfile); +++- return rc; +++-} +++- +++-/* run3(): pipe/tunnel not supported on AmigaOS without ixemul. */ +++-int run3(const char *cmd, int *in, int *out, int *err) +++-{ +++- (void)cmd; (void)in; (void)out; (void)err; +++- Log(1, "run3: pipe connections not supported on Amiga"); +++- return -1; +++-} +++-#endif /* AMIGA */ +++- +++-#ifndef AMIGA +++ int run (char *cmd) +++ { +++ int rc=-1; +++@@ -234,7 +111,6 @@ +++ #endif +++ return rc; +++ } +++-#endif /* !AMIGA */ +++ +++ #ifdef __MINGW32__ +++ static int set_cloexec(int fd) +++@@ -260,7 +136,6 @@ +++ } +++ #endif +++ +++-#ifndef AMIGA +++ int run3 (const char *cmd, int *in, int *out, int *err) +++ { +++ int pid; +++@@ -287,14 +162,6 @@ +++ } +++ +++ #ifdef HAVE_FORK +++-#ifdef AMIGA +++- /* Pipe tunneling not supported on AmigaOS without fork() */ +++- Log(1, "run3: pipe/tunnel not supported on Amiga: %s", cmd); +++- if (in) close(pin[1]), close(pin[0]); +++- if (out) close(pout[1]), close(pout[0]); +++- if (err) close(perr[1]), close(perr[0]); +++- return -1; +++-#else +++ pid = fork(); +++ if (pid == -1) +++ { +++@@ -327,11 +194,7 @@ +++ if (strpbrk(cmd, SHELL_META)) +++ { +++ shell = SHELL; +++-#ifdef AMIGA +++- execl(shell, shell, cmd, (char *)NULL); +++-#else +++ execl(shell, shell, SHELLOPT, cmd, (char *)NULL); +++-#endif +++ } +++ else +++ { +++@@ -369,7 +232,6 @@ +++ *err = perr[0]; +++ close(perr[1]); +++ } +++-#endif /* !AMIGA */ +++ #else +++ +++ /* redirect stdin/stdout/stderr takes effect for all threads */ +++@@ -474,4 +336,3 @@ +++ return pid; +++ } +++ +++-#endif /* !AMIGA */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/sem.h binkd_pgul/sem.h +++--- binkd/sem.h 2026-04-26 10:09:54.148733026 +0100 ++++++ binkd_pgul/sem.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -34,14 +34,6 @@ +++ #include +++ typedef struct SignalSemaphore MUTEXSEM; +++ +++-#ifdef AMIGA +++-typedef struct +++-{ +++- struct Task *waiter; +++- ULONG sigbit; +++-} EVENTSEM; +++-#endif +++- +++ #elif defined(WITH_PTHREADS) +++ +++ #include +++@@ -81,27 +73,25 @@ +++ * Initialise Event Semaphores. +++ */ +++ +++-#ifdef AMIGA +++-int _InitEventSem (EVENTSEM *); ++++int _InitEventSem (void *); +++ +++ /* +++ * Post Semaphore. +++ */ +++ +++-int _PostSem (EVENTSEM *); ++++int _PostSem (void *); +++ +++ /* +++ * Wait Semaphore. +++ */ +++ +++-int _WaitSem (EVENTSEM *, int); ++++int _WaitSem (void *, int); +++ +++ /* +++ * Clean Event Semaphores. +++ */ +++ +++-int _CleanEventSem (EVENTSEM *); +++-#endif ++++int _CleanEventSem (void *); +++ +++ #if defined(WITH_PTHREADS) +++ #define InitSem(sem) pthread_mutex_init(sem, NULL) +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/server.c binkd_pgul/server.c +++--- binkd/server.c 2026-04-26 10:30:03.538324924 +0100 ++++++ binkd_pgul/server.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -23,10 +23,6 @@ +++ #include +++ #endif +++ +++-#ifdef AMIGA +++-#include "amiga/bsdsock.h" +++-#endif +++- +++ #include "sys.h" +++ #include "iphdr.h" +++ #include "readcfg.h" +++@@ -43,10 +39,6 @@ +++ #endif +++ #include "rfc2553.h" +++ +++-#if defined(HAVE_THREADS) || defined(AMIGA) +++-extern EVENTSEM eothread; +++-#endif +++- +++ int n_servers = 0; +++ int ext_rand = 0; +++ +++@@ -61,8 +53,7 @@ +++ void *cperl; +++ #endif +++ +++-/* Prevent shared socket closure */ +++-#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(AMIGA) && !defined(DEBUGCHILD) ++++#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(DEBUGCHILD) +++ int curfd; +++ pidcmgr = 0; +++ for (curfd=0; curfdai_addr, ai->ai_addrlen) != 0) +++ { +++-#ifdef AMIGA +++- /* bsdsocket may hold the port briefly after socket close. Retry */ +++- int bind_retries = 6; +++- +++- while (bind(sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) +++- { +++- if (--bind_retries == 0) +++- { +++- Log(1, "servmgr bind(): %s", TCPERR()); +++- soclose(sockfd[sockfd_used]); +++- return -1; +++- } +++- +++- Log(2, "servmgr bind(): %s, retry in 2s...", TCPERR()); +++- sleep(2); +++- } +++-#else +++- if (bind (sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) +++- { +++- Log(1, "servmgr bind(): %s", TCPERR ()); +++- soclose(sockfd[sockfd_used]); +++- return -1; +++- } +++-#endif ++++ Log(1, "servmgr bind(): %s", TCPERR ()); ++++ soclose(sockfd[sockfd_used]); ++++ return -1; +++ } +++ if (listen (sockfd[sockfd_used], 5) != 0) +++ { +++@@ -201,12 +168,6 @@ +++ +++ setproctitle ("server manager (listen %s)", config->listen.first->port); +++ +++- /* Save rescan_delay locally. checkcfg() may free 'config' (old config +++- * is released when usageCount reaches 0 after reload), so we must not +++- * access config->rescan_delay inside the loop after a reload */ +++- { +++- int rescan = config->rescan_delay; +++- +++ for (;;) +++ { +++ struct timeval tv; +++@@ -222,7 +183,7 @@ +++ maxfd = sockfd[curfd]; +++ } +++ tv.tv_usec = 0; +++- tv.tv_sec = rescan; ++++ tv.tv_sec = CHECKCFG_INTERVAL; +++ unblocksig(); +++ check_child(&n_servers); +++ n = select(maxfd+1, &r, NULL, NULL, &tv); +++@@ -231,20 +192,8 @@ +++ { case 0: /* timeout */ +++ if (checkcfg()) +++ { +++- /* config may have been freed by checkcfg() — read rescan from +++- * the new current_config before returning for restart */ +++- { +++- BINKD_CONFIG *nc = lock_current_config(); +++- if (nc) +++- { +++- rescan = nc->rescan_delay; +++- unlock_config_structure(nc, 0); +++- } +++- } +++- +++ for (curfd=0; curfdrescan_delay; +++- unlock_config_structure(nc, 0); +++- } +++- } +++- +++ for (curfd=0; curfd +++-#endif +++- +++ /* +++ * Listens... Than calls protocol() +++ */ +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/sys.h binkd_pgul/sys.h +++--- binkd/sys.h 2026-04-26 08:48:04.281489117 +0100 ++++++ binkd_pgul/sys.h 2026-04-16 17:49:00.000000000 +0100 +++@@ -22,28 +22,8 @@ +++ #ifdef HAVE_STDINT_H +++ #include +++ #endif +++-#ifdef AMIGA +++- /* Include Amiga exec proto for Delay() function */ +++- #include +++-#endif +++ #ifdef HAVE_UNISTD_H +++ #include +++- /* Undefine conflicting unistd.h macros for AMIGA */ +++- #ifdef AMIGA +++- #ifdef getpid +++- #undef getpid +++- #endif +++- +++- #ifdef sleep +++- #undef sleep +++- #endif +++- +++- /* Redefine with our Amiga implementations */ +++- #define getpid() ((int)(ULONG)FindTask(NULL)) +++- +++- #define sleep(s) Delay((ULONG)((s) * 50)) +++- +++- #endif /* AMIGA */ +++ #endif +++ #ifdef HAVE_IO_H +++ #include +++@@ -125,11 +105,6 @@ +++ #define PID() mypid +++ #endif +++ +++-#ifdef HAVE_FORK +++- #include /* Needed for SIG_BLOCK/SIG_UNBLOCK and WIFEXITED/WEXITSTATUS */ +++- #include +++-#endif +++- +++ #if defined(HAVE_FORK) && defined(HAVE_SIGPROCMASK) && defined(HAVE_WAITPID) && defined(SIG_BLOCK) +++ void switchsignal(int how); +++ #define blocksig() switchsignal(SIG_BLOCK) +++@@ -317,16 +292,8 @@ +++ +++ #ifndef PRIdMAX +++ #define PRIdMAX "ld" +++-#endif +++- +++-#ifndef PRIuMAX +++-#ifdef AMIGA +++-/* On AmigaOS m68k, uintmax_t is long long unsigned int */ +++-#define PRIuMAX "llu" +++-#else +++ #define PRIuMAX "lu" +++ #endif +++-#endif +++ +++ #ifndef HAVE_STRTOUMAX +++ #define strtoumax(ptr, endptr, base) strtoul(ptr, endptr, base) +++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/tools.c binkd_pgul/tools.c +++--- binkd/tools.c 2026-04-25 23:49:26.267090428 +0100 ++++++ binkd_pgul/tools.c 2026-04-16 17:49:00.000000000 +0100 +++@@ -22,10 +22,6 @@ +++ #include +++ #endif +++ +++-#ifdef AMIGA +++-#include "amiga/bsdsock.h" +++-#endif +++- +++ #include "sys.h" +++ #include "readcfg.h" +++ #include "common.h" +++@@ -42,12 +38,6 @@ +++ #include "nt/w32tools.h" +++ #endif +++ +++-#if defined(HAVE_THREADS) || defined(AMIGA) +++-extern MUTEXSEM lsem; +++-#endif +++- +++-extern void vLog (int lev, char *s, va_list ap); +++- +++ /* +++ * We can call Log() even when we have no config ready. So, we must keep +++ * internal variables which will be updated when config is loaded +++@@ -300,10 +290,6 @@ +++ char buf[1024]; +++ int ok = 1; +++ +++-#ifdef AMIGA +++- static int need_newline = 0; +++-#endif +++- +++ /* make string in buffer */ +++ vsnprintf(buf, sizeof(buf), s, ap); +++ /* do perl hooks */ +++@@ -327,20 +313,8 @@ +++ if (lev <= current_conlog && !inetd_flag) +++ { +++ LockSem(&lsem); +++-#ifdef AMIGA +++- /* AmigaOS: go to new line for status messages to avoid overwriting */ +++- if (lev < 0 && need_newline) +++- { +++- need_newline = 0; +++- } +++- fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, +++- tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : "\r"); +++- if (lev >= 0) +++- need_newline = 1; +++-#else +++ fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, +++ tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : ""); +++-#endif +++ fflush (stderr); +++ ReleaseSem(&lsem); +++ if (lev < 0) +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/changes.txt binkd/changes.txt +--- binkd_pgul/changes.txt 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/changes.txt 2026-04-26 16:13:03.777428944 +0100 +@@ -0,0 +1,260 @@ ++=========================================================================== ++ CHANGES - binkd fork from pgul ++=========================================================================== ++ ++This diff shows the changes between binkd/ (current version) and binkd_pgul/ (pgul's fork). ++ ++=========================================================================== ++MULTI-PLATFORM CHANGES (all OS) ++=========================================================================== ++ ++--- Bug fixes --- ++ ++ftnq.c: ++- Fixed try file deletion: only delete if file exists (prevents error on missing .try files) ++ ++inbound.c: ++- Fixed double PATH_SEPARATOR: removed checks, now always adds separator ++ ++protocol.c: ++- Changed init_protocol() and deinit_protocol() from int to static ++ ++client.c: ++- Removed check for no_call_delay (now always does SLEEP) ++- Removed Amiga-specific checkcfg() block ++ ++--- New files --- ++ ++bsycleanup.c: ++- BSY/CSY/TRY file cleanup at startup, scans domain outbounds ++ ++bsycleanup.h: ++- cleanup_old_bsy() ++ ++--- Misc tools (new) --- ++ ++misc/decompress.c: ++- Decompress FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA) ++ ++misc/freq.c: ++- Create .req/.clo files (ASO, BSO) ++ ++misc/nodelist.c: ++- Compile FidoNet nodelist to binkd.conf node lines (extracts IBN/INA flags) ++ ++misc/process_tic.c: ++- Process .tic files to filebox (supports config file and legacy args) ++ ++misc/srifreq.c: ++- SRIF-compatible file request server (password protection, aliases, wildcards, rate limiting) ++ ++misc/portable.c: ++- Shared implementations: string utilities, file operations, path utilities ++ ++misc/portable.h: ++- Portable declarations and platform-specific includes (POSIX, Win32, OS/2, DOS, AmigaOS) ++ ++--- Documentation --- ++ ++misc/decompress.txt, misc/freq.txt, misc/nodelist.txt, misc/process_tic.txt, misc/srifreq.txt: ++- Usage documentation for misc tools ++ ++--- Makefile updates --- ++ ++mkfls/unix/Makefile.in: ++- Added bsycleanup.c to SRCS ++- Added portable.c to misc tool builds ++- Added -I misc to TOOL_CFLAGS ++ ++mkfls/nt95-mingw/Makefile: ++- Added bsycleanup.c to SRCS ++- Added portable.c to misc tool builds ++- Added -I misc to TOOL_CFLAGS ++ ++mkfls/nt95-msvc/Makefile: ++- Added portable.c to misc tool builds ++- Added -I misc to TOOL_CFLAGS ++ ++mkfls/os2-emx/Makefile: ++- Added bsycleanup.c to SRCS ++- Added portable.c to misc tool builds ++- Added -I misc to TOOL_CFLAGS ++ ++mkfls/unix/Makefile.analyze.unix: ++- New: Static analysis Makefile for Unix/GCC ++ ++=========================================================================== ++AMIGA-SPECIFIC CHANGES ++=========================================================================== ++ ++--- New Amiga files (17 files) --- ++ ++amiga/evloop.c: ++- Non-blocking event loop with WaitSelect(), replaces servmgr()/clientmgr() ++ ++amiga/evloop.h: ++- Event loop API (amiga_evloop_run) ++ ++amiga/evloop_int.h: ++- Internal session types (sess_t, sess_phase_t, session table) ++ ++amiga/proto_amiga.c: ++- Non-blocking BinkP protocol (amiga_proto_open, amiga_proto_step, amiga_proto_close) ++ ++amiga/proto_amiga.h: ++- Non-blocking protocol API (APROTO_RUNNING, APROTO_DONE_OK, APROTO_DONE_ERR) ++ ++amiga/session.c: ++- Session allocation, accept(), non-blocking connect(), outbound scan ++ ++amiga/sock.c: ++- Listen socket management (open_listen_sockets, close_listen_sockets) ++ ++amiga/utime.c: ++- utime() stub for AmigaDOS (converts Unix epoch to AmigaDOS epoch) ++ ++amiga/rfc2553_amiga.c: ++- getaddrinfo/getnameinfo fallback for Roadshow TCP/IP ++ ++amiga/bsdsock.c: ++- BSD socket layer for Roadshow TCP/IP (SocketBase initialization) ++ ++amiga/bsdsock.h: ++- BSD socket API (SocketBase, TCPERR macros) ++ ++amiga/compat_netinet_in.h: ++- Compatibility header for netinet/in.h ++ ++amiga/dirent.c: ++- POSIX dirent emulation for AmigaDOS ++ ++amiga/dirent.h: ++- POSIX dirent API (DIR, dirent, opendir, readdir) ++ ++amiga/rename.c: ++- Complete rewrite: duplicate name handling with .001/.002 suffixes, directory scanning, buffer overflow protection ++ ++amiga/sem.c: ++- Complete rewrite: replaced ixemul with direct Exec calls, added EVENTSEM implementation ++ ++--- Modified Amiga files --- ++ ++mkfls/amiga/Makefile.bebbo: ++- New: Complete rewrite for bebbo gcc cross-compiler (no ixemul, libnix, Roadshow TCP/IP) ++- Added bsycleanup.c, portable.c, all amiga/ sources ++- Added misc tool compilation rules + -I misc ++- Added AMIGADOS_4D_OUTBOUND define ++ ++mkfls/amiga/Makefile.analyze.bebbo: ++- New: Static analysis Makefile for bebbo gcc ++ ++--- Core files: Amiga code REMOVED (moved to amiga/ directory) --- ++ ++binkd.c: ++- REMOVED: #include "amiga/evloop.h" ++- REMOVED: #include "bsycleanup.h" ++- REMOVED: cleanup_old_bsy() call ++- REMOVED: Amiga semaphore declarations (changed from HAVE_THREADS||AMIGA to HAVE_THREADS) ++- REMOVED: mypid under AMIGA ++- REMOVED: NIL: null device for Amiga ++- REMOVED: #ifndef AMIGA guards around -i and -a options ++- REMOVED: amiga_evloop_run() block ++- REMOVED: config file validation ++ ++binlog.c: ++- REMOVED: extern blsem under (HAVE_THREADS || AMIGA) ++ ++branch.c: ++- ADDED: ix_vfork() implementation under #ifdef AMIGA (appears to be incorrect code) ++ ++readcfg.c: ++- REMOVED: extern config_sem under #ifdef AMIGA ++- REMOVED: no_call_delay initialization ++- REMOVED: tcp_nodelay initialization under #ifdef AMIGA ++ ++readcfg.h: ++- REMOVED: tcp_nodelay, so_sndbuf, so_rcvbuf fields under #ifdef AMIGA ++ ++server.c: ++- REMOVED: #include "amiga/bsdsock.h" ++- REMOVED: extern eothread under (HAVE_THREADS || AMIGA) ++- REMOVED: PF_INET forced for Amiga ++- REMOVED: bind() retry loop for Amiga ++- REMOVED: select() ENOTSOCK/EBADF recovery for Amiga ++ ++protocol.c: ++- REMOVED: #include "amiga/proto_amiga.h" ++- REMOVED: extern lsem under (HAVE_THREADS || AMIGA) ++- REMOVED: setsockopts_amiga() calls ++ ++client.c: ++- REMOVED: #include "amiga/bsdsock.h" ++- REMOVED: extern lsem and eothread under (HAVE_THREADS || AMIGA) ++ ++client.h: ++- REMOVED: includes under #ifdef AMIGA ++- REMOVED: call0() declaration under #ifdef AMIGA ++ ++run.c: ++- REMOVED: All Amiga-specific code (includes, defines, SystemTagList() implementation) ++ ++tools.c: ++- REMOVED: #include "amiga/bsdsock.h" ++- REMOVED: extern lsem under (HAVE_THREADS || AMIGA) ++- REMOVED: extern vLog() ++- REMOVED: Amiga console logging (need_newline, \r carriage return) ++ ++breaksig.c: ++- REMOVED: SIGINT/SIGTERM signal handlers under #ifdef AMIGA ++ ++exitproc.c: ++- REMOVED: All Amiga cleanup block (close_srvmgr_socket, CleanEventSem, CleanSem, sock_deinit, nodes_deinit, bsy_remove_all) ++ ++https.c: ++- REMOVED: .sin_addr.s_addr for Amiga (now uses .sin_addr) ++ ++iptools.c: ++- REMOVED: #include under #ifdef AMIGA ++- REMOVED: ioctl() size parameter for Amiga ++- REMOVED: setsockopts_amiga() function ++ ++iptools.h: ++- REMOVED: includes under #ifdef AMIGA ++- REMOVED: setsockopts_amiga() declaration ++ ++Config.h: ++- REMOVED: AMIGA from platform check (changed from +defined(AMIGA) to without AMIGA) ++ ++btypes.h: ++- REMOVED: NC_flag field ++ ++ftnnode.h: ++- REMOVED: NC_flag parameter to add_node() ++- REMOVED: NC_ON/NC_OFF/NC_USE_OLD defines ++ ++ftnnode.c: ++- REMOVED: NC_flag propagation ++ ++iphdr.h: ++- REMOVED: amiga/bsdsock.h include ++- REMOVED: sock_init()/sock_deinit()/soclose() macros for Amiga ++ ++readdir.h: ++- REMOVED: #elif defined(AMIGA) include for amiga/dirent.h ++ ++rfc2553.h: ++- REMOVED: EAI_* constant redefinitions under #ifdef AMIGA ++- REMOVED: include chain under #ifdef AMIGA ++- REMOVED: getaddrinfo/freeaddrinfo macros under #ifdef AMIGA ++ ++sem.h: ++- REMOVED: EVENTSEM typedef under #ifdef AMIGA ++- REMOVED: _InitEventSem/_PostSem/_WaitSem/_CleanEventSem prototypes ++ ++server.h: ++- REMOVED: #include under #ifdef AMIGA ++ ++sys.h: ++- REMOVED: #include under #ifdef AMIGA ++- REMOVED: getpid/sleep redefines under #ifdef AMIGA ++- REMOVED: PRIuMAX define under #ifdef AMIGA +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/client.c binkd/client.c +--- binkd_pgul/client.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/client.c 2026-04-26 10:25:03.436098652 +0100 +@@ -19,6 +19,10 @@ + #include + #endif + ++#ifdef AMIGA ++#include "amiga/bsdsock.h" ++#endif ++ + #include "sys.h" + #include "readcfg.h" + #include "client.h" +@@ -44,6 +48,11 @@ + #include "rfc2553.h" + #include "srv_gai.h" + ++#if defined(HAVE_THREADS) || defined(AMIGA) ++extern MUTEXSEM lsem; ++extern EVENTSEM eothread; ++#endif ++ + static void call (void *arg); + + int n_clients = 0; +@@ -202,7 +211,8 @@ + /* This sleep can be interrupted by signal, it's OK */ + unblocksig(); + check_child(&n_clients); +- SLEEP (config->call_delay); ++ if (!config->no_call_delay) ++ SLEEP (config->call_delay); + check_child(&n_clients); + blocksig(); + } +@@ -282,8 +292,16 @@ + #ifdef HAVE_THREADS + !server_flag && + #endif ++ /* AmigaOS uses shared-memory evloop — only main process calls checkcfg() ++ * On fork systems (Linux/FreeBSD) each process has separate memory ++ * so independent reloads are safe. */ + !poll_flag) ++#ifndef AMIGA + checkcfg(); ++#else ++ { ++ } ++#endif + } + + Log (5, "downing clientmgr..."); +@@ -306,7 +324,7 @@ + exit (0); + } + +-static int call0 (FTN_NODE *node, BINKD_CONFIG *config) ++int call0 (FTN_NODE *node, BINKD_CONFIG *config) + { + int sockfd = INVALID_SOCKET; + int sock_out; +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/client.h binkd/client.h +--- binkd_pgul/client.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/client.h 2026-04-26 10:24:36.096878628 +0100 +@@ -1,9 +1,20 @@ + #ifndef _client_h + #define _client_h + ++#ifdef AMIGA ++#include ++#include ++#include ++#endif ++ + /* + * Scans queue, makes outbound ``call'', than calls protocol() + */ + void clientmgr(void *arg); + ++#ifdef AMIGA ++/* Direct outbound call for evloop.c (no-ixemul, no-threads build) */ ++int call0(FTN_NODE *node, BINKD_CONFIG *config); ++#endif ++ + #endif +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/Config.h binkd/Config.h +--- binkd_pgul/Config.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/Config.h 2026-04-25 19:53:34.520405599 +0100 +@@ -14,8 +14,8 @@ + #ifndef _Config_h + #define _Config_h + +-#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) == 0 +-#error You must define either HAVE_FORK or HAVE_THREADS! ++#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) + defined(AMIGA) == 0 ++#error You must define HAVE_FORK, HAVE_THREADS, DOS, or AMIGA! + #endif + + #ifdef __WATCOMC__ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/exitproc.c binkd/exitproc.c +--- binkd_pgul/exitproc.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/exitproc.c 2026-04-26 10:21:07.372917687 +0100 +@@ -32,6 +32,17 @@ + #include "nt/w32tools.h" + #endif + ++#if defined(HAVE_THREADS) || defined(AMIGA) ++extern MUTEXSEM hostsem; ++extern MUTEXSEM resolvsem; ++extern MUTEXSEM lsem; ++extern MUTEXSEM blsem; ++extern MUTEXSEM varsem; ++extern MUTEXSEM config_sem; ++extern EVENTSEM eothread; ++extern EVENTSEM wakecmgr; ++#endif ++ + int binkd_exit; + + #ifdef HAVE_THREADS +@@ -138,6 +149,33 @@ + close_srvmgr_socket(); + #endif + ++#ifdef AMIGA ++ /* evloop: single process, no children ++ * Clean Exec semaphores in safe order before freeing config */ ++ close_srvmgr_socket(); ++ CleanEventSem(&wakecmgr); ++ CleanEventSem(&eothread); ++ CleanSem(&varsem); ++ CleanSem(&blsem); ++ CleanSem(&lsem); ++ CleanSem(&resolvsem); ++ CleanSem(&hostsem); ++ CleanSem(&config_sem); ++ sock_deinit(); ++ nodes_deinit(); ++ { ++ BINKD_CONFIG *cfg = lock_current_config(); ++ if (cfg) ++ bsy_remove_all(cfg); ++ if (cfg && *cfg->pid_file && pidsmgr == (int)getpid()) ++ delete(cfg->pid_file); ++ if (cfg) ++ unlock_config_structure(cfg, 1); ++ } ++ Log(6, "exitfunc: AmigaOS cleanup done"); ++ return; ++#endif /* AMIGA */ ++ + config = lock_current_config(); + if (config) + bsy_remove_all (config); +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/ftnnode.c binkd/ftnnode.c +--- binkd_pgul/ftnnode.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/ftnnode.c 2026-04-26 09:22:13.159382256 +0100 +@@ -74,7 +74,7 @@ + */ + static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, + char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, +- int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, ++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, + int IP_afamily, + #ifdef BW_LIM + long bw_send, long bw_recv, +@@ -107,6 +107,7 @@ + pn->NR_flag = NR_OFF; + pn->ND_flag = ND_OFF; + pn->NP_flag = NP_OFF; ++ pn->NC_flag = NC_OFF; + pn->MD_flag = MD_USE_OLD; + pn->HC_flag = HC_USE_OLD; + pn->pipe = NULL; +@@ -134,6 +135,8 @@ + pn->ND_flag = ND_flag; + if (NP_flag != NP_USE_OLD) + pn->NP_flag = NP_flag; ++ if (NC_flag != NC_USE_OLD) ++ pn->NC_flag = NC_flag; + if (HC_flag != HC_USE_OLD) + pn->HC_flag = HC_flag; + if (IP_afamily != AF_USE_OLD) +@@ -195,7 +198,7 @@ + + FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, + char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, +- int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, ++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, + int IP_afamily, + #ifdef BW_LIM + long bw_send, long bw_recv, +@@ -209,7 +212,7 @@ + + locknodesem(); + pn = add_node_nolock(fa, hosts, pwd, pkt_pwd, out_pwd, obox_flvr, obox, ibox, +- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, ++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, + IP_afamily, + #ifdef BW_LIM + bw_send, bw_recv, +@@ -275,6 +278,7 @@ + on->ND_flag=np->ND_flag; + on->MD_flag=np->MD_flag; + on->NP_flag=np->NP_flag; ++ on->NC_flag=np->NC_flag; + on->HC_flag=np->HC_flag; + on->restrictIP=np->restrictIP; + on->pipe=np->pipe; +@@ -290,7 +294,7 @@ + + add_node_nolock(fa, np->hosts, NULL, NULL, NULL, np->obox_flvr, np->obox, + np->ibox, np->NR_flag, np->ND_flag, np->MD_flag, np->restrictIP, +- np->HC_flag, np->NP_flag, np->pipe, np->IP_afamily, ++ np->HC_flag, np->NP_flag, np->NC_flag, np->pipe, np->IP_afamily, + #ifdef BW_LIM + np->bw_send, np->bw_recv, + #endif +@@ -399,7 +403,7 @@ + if (!get_node_info_nolock (&target, config)) + add_node_nolock (&target, "*", NULL, NULL, NULL, '-', NULL, NULL, + NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, +- HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, ++ HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, + #ifdef BW_LIM + BW_DEF, BW_DEF, + #endif +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/ftnnode.h binkd/ftnnode.h +--- binkd_pgul/ftnnode.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/ftnnode.h 2026-04-25 20:40:18.652899844 +0100 +@@ -36,7 +36,7 @@ + */ + FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, + char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, +- int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, ++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, + int IP_afamily, + #ifdef BW_LIM + long bw_send, long bw_recv, +@@ -75,6 +75,10 @@ + #define NP_OFF 0 + #define NP_USE_OLD -1 /* Use old value */ + ++#define NC_ON 1 ++#define NC_OFF 0 ++#define NC_USE_OLD -1 /* Use old value */ ++ + #define AF_USE_OLD -1 /* Use old value */ + + #ifdef BW_LIM +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/ftnq.c binkd/ftnq.c +--- binkd_pgul/ftnq.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/ftnq.c 2026-04-26 10:34:47.939032694 +0100 +@@ -1147,7 +1147,8 @@ + if (*buf) + { + strnzcat (buf, ".try", sizeof (buf)); +- if (stat(buf, &sb) == -1) return; +- delete (buf); ++ /* Delete only if the file exists */ ++ if (stat(buf, &sb) == 0) ++ delete (buf); + } + } +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/https.c binkd/https.c +--- binkd_pgul/https.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/https.c 2026-04-22 21:36:50.649712064 +0100 +@@ -318,7 +318,11 @@ + buf[1]=1; + lockhostsem(); + Log (4, strcmp(port, config->oport) == 0 ? "trying %s..." : "trying %s:%u...", +- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); ++#ifdef AMIGA ++ inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr.s_addr), portnum); ++#else ++ inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); ++#endif + releasehostsem(); + buf[2]=(unsigned char)((portnum>>8)&0xFF); + buf[3]=(unsigned char)(portnum&0xFF); +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/inbound.c binkd/inbound.c +--- binkd_pgul/inbound.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/inbound.c 2026-04-26 09:25:07.283995051 +0100 +@@ -49,7 +49,9 @@ + char node[FTN_ADDR_SZ + 1]; + + strnzcpy (s, inbound, MAXPATHLEN); +- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); ++ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++ if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) ++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); + t = s + strlen (s); + while (1) + { +@@ -128,7 +130,9 @@ + } + + strnzcpy (s, inbound, MAXPATHLEN); +- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); ++ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++ if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) ++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); + t = s + strlen (s); + while ((de = readdir (dp)) != 0) + { +@@ -470,7 +474,9 @@ + } + + strnzcpy (real_name, state->inbound, MAXPATHLEN); +- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); ++ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++ if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) ++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); + s = real_name + strlen (real_name); + strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); + free (u); +@@ -541,7 +547,9 @@ + { + ren_style = RENAME_POSTFIX; + strnzcpy (real_name, state->inbound, MAXPATHLEN); +- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); ++ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++ if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) ++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); + s = real_name + strlen (real_name); + strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); + free (u); +@@ -591,7 +599,9 @@ + struct stat sb; + + strnzcpy (fp, inbound, MAXPATHLEN); +- strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); ++ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ ++ if (strlen(fp) > 0 && fp[strlen(fp) - 1] != PATH_SEPARATOR[0]) ++ strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); + s = fp + strlen (fp); + strnzcat (fp, u = strdequote (filename), MAXPATHLEN); + free (u); +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/iphdr.h binkd/iphdr.h +--- binkd_pgul/iphdr.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/iphdr.h 2026-04-26 09:25:41.562664644 +0100 +@@ -124,9 +124,17 @@ + #define TCPERRNO errno + #define TCPERR_WOULDBLOCK EWOULDBLOCK + #define TCPERR_AGAIN EAGAIN +- #define sock_init() 0 +- #define sock_deinit() +- #define soclose(h) close(h) ++ #ifdef AMIGA ++ /* AmigaOS 3: open bsdsocket.library via amiga/bsdsock.c */ ++ #include "amiga/bsdsock.h" ++ #define sock_init() amiga_sock_init() ++ #define sock_deinit() amiga_sock_cleanup() ++ #define soclose(h) CloseSocket(h) ++ #else ++ #define sock_init() 0 ++ #define sock_deinit() ++ #define soclose(h) close(h) ++ #endif + #endif + + #if !defined(WIN32) +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/iptools.c binkd/iptools.c +--- binkd_pgul/iptools.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/iptools.c 2026-04-26 10:33:19.517261538 +0100 +@@ -20,6 +20,10 @@ + #include + #endif + ++#ifdef AMIGA ++#include ++#endif ++ + #include "sys.h" + #include "Config.h" + #include "iphdr.h" +@@ -40,7 +44,11 @@ + int arg; + + arg = 1; ++#if defined(AMIGA) ++ if (ioctl (s, FIONBIO, (char *) &arg) < 0) ++#else + if (ioctl (s, FIONBIO, (char *) &arg, sizeof arg) < 0) ++#endif + Log (1, "ioctl (FIONBIO): %s", TCPERR ()); + + #elif defined(WIN32) +@@ -53,12 +61,49 @@ + #endif + #endif + +-#if defined(UNIX) || defined(EMX) || defined(AMIGA) ++#if defined(UNIX) || defined(EMX) /* NOT AMIGA: sockets are not AmigaDOS fds */ + if (fcntl (s, F_SETFL, O_NONBLOCK) == -1) + Log (1, "fcntl: %s", strerror (errno)); + #endif + } + ++#if defined(AMIGA) ++void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf) ++{ ++ /* Disable Nagle algorithm: BinkP mixes small control messages with data ++ * Without TCP_NODELAY each small message waits up to 200ms (Nagle delay), ++ * making sessions 2-5x slower than other BinkP implementations ++ * All other BinkP mailers (BinkIT, Argus, etc.) set this explicitly */ ++ ++ if (tcpdelay) ++ { ++ int nodelay = tcpdelay; ++ ++ if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)) < 0) ++ Log (4, "setsockopt TCP_NODELAY: %s", TCPERR()); ++ } ++ ++ /* ixnet default TCP buffers are very small (~8KB). Increase them so the ++ * sender does not stall waiting for ACK after every small burst */ ++ ++ if (so_sndbuf) ++ { ++ int sndbuf = so_sndbuf; ++ ++ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, sizeof(sndbuf)) < 0) ++ Log (5, "setsockopt SO_SNDBUF: %s", TCPERR()); ++ } ++ ++ if (so_rcvbuf) ++ { ++ int rcvbuf = so_rcvbuf; ++ ++ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) < 0) ++ Log (5, "setsockopt SO_RCVBUF: %s", TCPERR()); ++ } ++} ++#endif ++ + /* + * Find the appropriate port string to be used. + * Find_port ("") will return binkp's port from /etc/services or even +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/iptools.h binkd/iptools.h +--- binkd_pgul/iptools.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/iptools.h 2026-04-26 09:26:42.811498024 +0100 +@@ -11,11 +11,21 @@ + * (at your option) any later version. See COPYING. + */ + ++#if defined(AMIGA) ++#include ++#include ++#include ++#endif ++ + /* + * Sets non-blocking mode for a given socket + */ + void setsockopts (SOCKET s); + ++#if defined(AMIGA) ++void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf); ++#endif ++ + /* + * Find the port number (in the host byte order) by a port number string or + * a service name. Find_port ("") will return binkp's port from +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/decompress.c binkd/misc/decompress.c +--- binkd_pgul/misc/decompress.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/decompress.c 2026-04-26 13:39:53.974576140 +0100 +@@ -0,0 +1,188 @@ ++/* ++ * decompress.c -- Decompress FTN bundle archives from an inbound directory ++ * ++ * decompress.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include "portable.h" /* Canonical portable layer */ ++#include ++ ++#define MAX_CMD 1100 ++ ++/* Archive format codes detected by magic bytes */ ++#define FMT_UNKNOWN 0 ++#define FMT_ZIP 1 ++#define FMT_LZH 2 ++#define FMT_ARC 3 ++ ++/* detect_format -- Read first bytes and identify archive type */ ++static int detect_format(const char *path) ++{ ++ unsigned char buf[8]; ++ FILE *f = fopen(path, "rb"); ++ int n; ++ ++ if (!f) ++ return FMT_UNKNOWN; ++ ++ n = (int)fread(buf, 1, sizeof(buf), f); ++ ++ fclose(f); ++ ++ if (n < 2) ++ return FMT_UNKNOWN; ++ ++ /* ZIP: PK\x03\x04 */ ++ if (n >= 4 && buf[0] == 0x50 && buf[1] == 0x4B && buf[2] == 0x03 && buf[3] == 0x04) ++ return FMT_ZIP; ++ ++ /* LZH: offset 2 = '-', offset 3 = 'l', offset 6 = '-' (e.g. -lh5-) */ ++ if (n >= 7 && buf[2] == '-' && buf[3] == 'l' && buf[6] == '-') ++ return FMT_LZH; ++ ++ /* ARC: 0x1A followed by type byte 1..18 */ ++ if (buf[0] == 0x1A && buf[1] >= 1 && buf[1] <= 18) ++ return FMT_ARC; ++ ++ return FMT_UNKNOWN; ++} ++ ++/* is_ftn_bundle -- Check filename has an FTN day-of-week extension */ ++static int is_ftn_bundle(const char *filename) ++{ ++ const char *p; ++ ++ for (p = filename; *p; p++) ++ { ++ if (p[0] == '.' && p[1] && p[2]) ++ { ++ char a = (char)tolower((unsigned char)p[1]); ++ char b = (char)tolower((unsigned char)p[2]); ++ ++ if ((a == 's' && b == 'u') || (a == 'm' && b == 'o') || ++ (a == 't' && b == 'u') || (a == 'w' && b == 'e') || ++ (a == 't' && b == 'h') || (a == 'f' && b == 'r') || ++ (a == 's' && b == 'a')) ++ { ++ /* .TH .TH0 .TH.001 */ ++ if (p[3] == '\0' || isdigit((unsigned char)p[3]) || p[3] == '.') ++ return 1; ++ } ++ } ++ } ++ return 0; ++} ++ ++/* delete_file -- Remove a file, portable */ ++static void delete_file(const char *path) ++{ ++#if defined(AMIGA) ++ DeleteFile((STRPTR)path); ++#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) ++ DeleteFileA(path); ++#else ++ remove(path); ++#endif ++} ++ ++/* run_decompressor -- Invoke external tool for the detected format ++ * outdir must end without trailing slash on POSIX; lha needs trailing / */ ++static int run_decompressor(int fmt, const char *path, const char *outdir) ++{ ++ char cmd[MAX_CMD]; ++ ++ switch (fmt) ++ { ++ case FMT_ZIP: ++#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) ++ snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); ++#else ++ snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); ++#endif ++ break; ++ ++ case FMT_LZH: ++#ifdef AMIGA ++ snprintf(cmd, MAX_CMD, "lha x \"%s\" \"%s/\"", path, outdir); ++#else ++ snprintf(cmd, MAX_CMD, "lha e \"%s\" \"%s/\"", path, outdir); ++#endif ++ break; ++ ++ case FMT_ARC: ++#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) ++ snprintf(cmd, MAX_CMD, "arc x \"%s\" \"%s\"", path, outdir); ++#else ++ snprintf(cmd, MAX_CMD, "cd \"%s\" && arc x \"%s\"", outdir, path); ++#endif ++ break; ++ ++ default: ++ return -1; ++ } ++ ++ return system(cmd); ++} ++ ++int main(int argc, char *argv[]) ++{ ++ DIR *dp; ++ struct dirent *entry; ++ char path[MAXPATHLEN]; ++ const char *inbound; ++ const char *outdir; ++ int total = 0; ++ int ok = 0; ++ ++ if (argc < 3) ++ { ++ fprintf(stderr, ++ "Usage: decompress \n" ++ "Detects format by magic bytes (ZIP/LZH/ARC).\n" ++ "Processes FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA).\n"); ++ ++ return 1; ++ } ++ ++ inbound = argv[1]; ++ outdir = argv[2]; ++ ++ dp = opendir(inbound); ++ ++ if (dp == NULL) ++ return 1; ++ ++ while ((entry = readdir(dp)) != NULL) ++ { ++ int fmt; ++ ++ /* Skip . and .. (AmigaOS readdir does not return these, POSIX does) */ ++ if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) ++ continue; ++ ++ if (!is_ftn_bundle(entry->d_name)) ++ continue; ++ ++ path_join(path, MAXPATHLEN, inbound, entry->d_name); ++ ++ fmt = detect_format(path); ++ ++ if (fmt == FMT_UNKNOWN) ++ continue; ++ ++ total++; ++ ++ if (run_decompressor(fmt, path, outdir) == 0) ++ { ++ delete_file(path); ++ ok++; ++ } ++ } ++ ++ closedir(dp); ++ ++ return (total == 0 || ok == total) ? 0 : 1; ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/decompress.txt binkd/misc/decompress.txt +--- binkd_pgul/misc/decompress.txt 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/decompress.txt 2026-04-26 13:44:08.749250093 +0100 +@@ -0,0 +1,36 @@ ++decompress -- Decompress FTN bundle archives ++ ++USAGE: ++ decompress ++ ++DESCRIPTION: ++ Scans the inbound directory for FTN day-of-week bundle archives and ++ decompresses them to the output directory. ++ ++ Recognized archive formats (by magic bytes, not extension): ++ - ZIP files ++ - LZH files ++ - ARC files ++ ++ Recognized bundle extensions (case-insensitive): ++ - .SU - Sunday bundle ++ - .MO - Monday bundle ++ - .TU - Tuesday bundle ++ - .WE - Wednesday bundle ++ - .TH - Thursday bundle ++ - .FR - Friday bundle ++ - .SA - Saturday bundle ++ ++ Also handles renamed bundles (duplicates): ++ - ABCD1234.SU ++ - ABCD1234.SU.001 ++ - ABCD1234.SU.002 ++ ++EXAMPLES: ++ decompress Work:Inbound Work:Unpacked ++ decompress /var/spool/binkd/inbound /tmp/unpacked ++ decompress C:\Binkd\Inbound C:\Binkd\Unpacked ++ exec "work:fido/decompress work:fido/inbound work:fido/inbound" *.su? *.mo? *.tu? *.we? *.th? *.fr? *.sa? *.SU? *.MO? *.TU? *.WE? *.TH? *.FR? *.SA? ++ ++CONFIGURATION FILE: ++ None. All parameters are command-line arguments. +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/freq.c binkd/misc/freq.c +--- binkd_pgul/misc/freq.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/freq.c 2026-04-26 13:39:53.974576140 +0100 +@@ -0,0 +1,220 @@ ++/* ++ * freq.c -- Append a file-request entry to an outbound .req / .clo pair ++ * ++ * freq.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include "portable.h" /* Canonical portable layer */ ++ ++#define FREQ_MAX_PATH (MAXPATHLEN + 1) ++ ++/* build_aso_paths -- ASO flat layout */ ++static int build_aso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) ++{ ++ char *dot; ++ ++ if (mkdir_recursive(outbound) < 0 && !path_exists(outbound)) ++ return -1; ++ ++ snprintf(req_path, (size_t)pathsize, "%s/%u.%u.%u.%u.req", outbound, zone, net, node, point); ++ safe_strncpy(clo_path, req_path, pathsize); ++ dot = strrchr(clo_path, '.'); ++ ++ if (dot) ++ strcpy(dot, ".clo"); ++ ++ return 0; ++} ++ ++/* build_bso_paths -- BSO BinkleyStyle layout (lowercase hex) */ ++static int build_bso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) ++{ ++ char zone_dir[FREQ_MAX_PATH]; ++ char node_dir[FREQ_MAX_PATH]; ++ ++ /* Zone dir: .0ZZ (lowercase hex) */ ++ snprintf(zone_dir, sizeof(zone_dir), "%s.%03x", outbound, zone); ++ str_tolower(zone_dir); ++ ++ if (mkdir_recursive(zone_dir) < 0 && !path_exists(zone_dir)) ++ return -1; ++ ++ if (point == 0) ++ { ++ snprintf(req_path, (size_t)pathsize, "%s/%04x%04x.req", zone_dir, net, node); ++ snprintf(clo_path, (size_t)pathsize, "%s/%04x%04x.clo", zone_dir, net, node); ++ } ++ else ++ { ++ snprintf(node_dir, sizeof(node_dir), "%s/%04x%04x.pnt", zone_dir, net, node); ++ ++ if (mkdir_recursive(node_dir) < 0 && !path_exists(node_dir)) ++ return -1; ++ ++ snprintf(req_path, (size_t)pathsize, "%s/%08x.req", node_dir, point); ++ snprintf(clo_path, (size_t)pathsize, "%s/%08x.clo", node_dir, point); ++ } ++ ++ return 0; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ unsigned int zone, net, node, point; ++ char addr_copy[128]; ++ char req_path[FREQ_MAX_PATH]; ++ char clo_path[FREQ_MAX_PATH]; ++ char abs_outbound[FREQ_MAX_PATH]; ++ FILE *f; ++ const char *outbound; ++ const char *arg_outbound; ++ const char *arg_addr; ++ const char *password = NULL; /* --password → !pass suffix */ ++ long newer_than = 0; /* --newer-than → +ts suffix */ ++ int update = 0; /* --update → U suffix */ ++ int use_bso = 0; ++ int argi = 1; ++ int nfiles = 0; ++ ++ zone = net = node = point = 0; ++ ++ /* Parse flags -- All optional, order-independent, before positional args */ ++ while (argi < argc && argv[argi][0] == '-' && argv[argi][1] == '-') ++ { ++ if (strcmp(argv[argi], "--bso") == 0) ++ { ++ use_bso = 1; ++ argi++; ++ } ++ else if (strcmp(argv[argi], "--aso") == 0) ++ { ++ use_bso = 0; ++ argi++; ++ } ++ else if (strcmp(argv[argi], "--update") == 0) ++ { ++ update = 1; ++ argi++; ++ } ++ else if (strcmp(argv[argi], "--password") == 0 && argi + 1 < argc) ++ { ++ password = argv[++argi]; ++ argi++; ++ } ++ else if (strcmp(argv[argi], "--newer-than") == 0 && argi + 1 < argc) ++ { ++ newer_than = atol(argv[++argi]); ++ argi++; ++ } ++ else ++ break; /* unknown flag — stop, treat rest as positional */ ++ } ++ ++ if (argc - argi < 3) ++ { ++ fprintf(stderr, ++ "Usage: freq [options] Z:N/NODE[.POINT] [...]\n" ++ "Options:\n" ++ " --aso flat layout (default): outbound/Z.N.NODE.POINT.req\n" ++ " --bso BSO layout: outbound.0ZZ/nnnnnnnn[.pnt/pppppppp].req\n" ++ " --password append !pw to each request line\n" ++ " --newer-than append + (request if newer)\n" ++ " --update append U flag (update request)\n" ++ "Multiple filenames can be listed after the address.\n"); ++ return 1; ++ } ++ ++ arg_outbound = argv[argi++]; ++ arg_addr = argv[argi++]; ++ ++ /* Remaining args are filenames */ ++ ++ make_abs_path(arg_outbound, abs_outbound, (int)sizeof(abs_outbound)); ++ outbound = abs_outbound; ++ ++ safe_strncpy(addr_copy, arg_addr, (int)sizeof(addr_copy)); ++ ++ if (sscanf(addr_copy, "%u:%u/%u.%u", &zone, &net, &node, &point) < 3 && sscanf(addr_copy, "%u:%u/%u", &zone, &net, &node) < 3) ++ { ++ fprintf(stderr, "freq: invalid address: %s\n", arg_addr); ++ return 1; ++ } ++ ++ if (use_bso) ++ { ++ if (build_bso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) ++ { ++ fprintf(stderr, "freq: cannot create BSO dirs under: %s\n", outbound); ++ return 1; ++ } ++ } ++ else ++ { ++ if (build_aso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) ++ { ++ fprintf(stderr, "freq: cannot create outbound dir: %s\n", outbound); ++ return 1; ++ } ++ } ++ ++ /* Append all filenames to .req */ ++ f = fopen(req_path, "a"); ++ ++ if (!f) ++ { ++ fprintf(stderr, "freq: cannot open REQ: %s\n", req_path); ++ return 1; ++ } ++ ++ for (; argi < argc; argi++) ++ { ++ const char *fname = argv[argi]; ++ ++ /* Build request line: filename [!password] [+timestamp] [U] */ ++ fprintf(f, "%s", fname); ++ ++ if (password && password[0]) ++ fprintf(f, " !%s", password); ++ ++ if (newer_than > 0) ++ fprintf(f, " +%ld", newer_than); ++ ++ if (update) ++ fprintf(f, " U"); ++ ++ fprintf(f, "\r\n"); ++ nfiles++; ++ } ++ ++ fclose(f); ++ ++ if (nfiles == 0) ++ { ++ fprintf(stderr, "freq: no filenames specified\n"); ++ return 1; ++ } ++ ++ /* Append .req full path to .clo (once per invocation) */ ++ f = fopen(clo_path, "a"); ++ ++ if (!f) ++ { ++ fprintf(stderr, "freq: cannot open CLO: %s\n", clo_path); ++ return 1; ++ } ++ ++ fprintf(f, "%s\r\n", req_path); ++ fclose(f); ++ ++ printf("freq (%s): node %u:%u/%u", use_bso ? "bso" : "aso", zone, net, node); ++ ++ if (point) ++ printf(".%u", point); ++ ++ printf(" %d file(s)\n REQ : %s\n CLO : %s\n", nfiles, req_path, clo_path); ++ ++ return 0; ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/freq.txt binkd/misc/freq.txt +--- binkd_pgul/misc/freq.txt 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/freq.txt 2026-04-26 13:33:14.698749181 +0100 +@@ -0,0 +1,37 @@ ++freq -- Create .req and .clo request files for FidoNet ++ ++USAGE: ++ freq [options] [...] ++ ++DESCRIPTION: ++ Creates file request (.req) and close (.clo) files in the outbound ++ directory using FidoNet 4D/5D addressing. ++ ++ Address format: ++ Zone:Net/Node - 4D address (e.g., 39:190/101) ++ Zone:Net/Node.Point - 5D address with point (e.g., 39:190/101.1) ++ ++ Multiple files can be specified to create multiple request lines. ++ ++OPTIONS: ++ --aso Amiga Style Outbound (default) - flat layout ++ Format: /Z.N.NODE.POINT.req ++ ++ --bso Binkley Style Outbound - hex directory layout ++ No point: .0ZZ/nnnnnnnn.req ++ Point: .0ZZ/nnnnnnnn.pnt/pppppppp.req ++ ++ --password Append !pw suffix to each request line ++ --newer-than Append + (request only if file is newer) ++ --update Append U flag (update request, checks TRANX) ++ ++EXAMPLES: ++ freq Work:Outbound 39:190/101 file.lha ++ freq --aso Work:Outbound 39:190/101.1 readme.txt ++ freq --bso /var/spool/binkd/outbound 2:123/456 door.zip ++ freq --bso C:\Binkd\Outbound 1:100/200 update.lzh ++ freq --password secret --newer-than 1234567890 Work:Outbound 39:190/101 file.zip ++ freq --update Work:Outbound 39:190/101 file1.zip file2.zip file3.zip ++ ++CONFIGURATION FILE: ++ None. All parameters are command-line arguments. +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/nodelist.c binkd/misc/nodelist.c +--- binkd_pgul/misc/nodelist.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/nodelist.c 2026-04-26 13:39:53.974576140 +0100 +@@ -0,0 +1,200 @@ ++/* ++ * nodelist.c -- FidoNet nodelist compiler for binkd ++ * ++ * nodelist.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include "portable.h" /* Canonical portable layer */ ++#include ++#include ++#include ++#include ++ ++#define MAX_FIELDS 20 ++#define MAX_VAL 256 ++ ++static int split_fields(char *buf, char **fields, int maxfields) ++{ ++ int n = 0; ++ char *p = buf; ++ ++ while (n < maxfields) ++ { ++ fields[n++] = p; ++ p = strchr(p, ','); ++ ++ if (!p) ++ break; ++ ++ *p++ = '\0'; ++ } ++ ++ return n; ++} ++ ++/* Case-insensitive flag search. Returns 1 if found; fills val if present */ ++static int find_flag(char **fields, int nfields, int start, const char *flag, char *val, int vsize) ++{ ++ int flen = (int)strlen(flag); ++ int i; ++ ++ for (i = start; i < nfields; i++) ++ { ++ char *f = fields[i]; ++ int j; ++ ++ for (j = 0; j < flen; j++) ++ { ++ if (toupper((unsigned char)f[j]) != toupper((unsigned char)flag[j])) ++ break; ++ } ++ ++ if (j == flen) ++ { ++ if (val && vsize > 0) ++ { ++ if (f[flen] == ':') ++ { ++ strncpy(val, f + flen + 1, (size_t)(vsize - 1)); ++ val[vsize - 1] = '\0'; ++ } ++ else ++ val[0] = '\0'; ++ } ++ return 1; ++ } ++ } ++ return 0; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ const char *nl_file; ++ const char *domain; ++ FILE *in; ++ FILE *out; ++ char buf[MAX_LINE]; ++ char *fields[MAX_FIELDS]; ++ int nf; ++ int cur_zone; ++ int cur_net; ++ long count; ++ ++ cur_zone = 0; ++ cur_net = 0; ++ count = 0; ++ ++ if (argc < 3) ++ { ++ fprintf(stderr, ++ "Usage: nodelist []\n"); ++ return 1; ++ } ++ ++ nl_file = argv[1]; ++ domain = argv[2]; ++ out = (argc >= 4) ? fopen(argv[3], "w") : stdout; ++ ++ if (!out) ++ { ++ perror(argv[3]); ++ return 1; ++ } ++ ++ in = fopen(nl_file, "r"); ++ ++ if (!in) ++ { ++ perror(nl_file); ++ ++ if (out != stdout) ++ fclose(out); ++ ++ return 1; ++ } ++ ++ while (fgets(buf, sizeof(buf), in)) ++ { ++ char type[32]; ++ char ibn_port[32]; ++ char ina_host[MAX_VAL]; ++ int node_num; ++ int port; ++ int flags_start; ++ ++ str_trim(buf); ++ ++ if (!buf[0] || buf[0] == ';') ++ continue; ++ ++ nf = split_fields(buf, fields, MAX_FIELDS); ++ ++ if (nf < 2) ++ continue; ++ ++ if (fields[0][0] == '\0') ++ { ++ /* Line started with comma -- plain Node entry */ ++ strcpy(type, "Node"); ++ node_num = atoi(fields[1]); ++ flags_start = 7; ++ } ++ else ++ { ++ strncpy(type, fields[0], sizeof(type) - 1); ++ type[sizeof(type) - 1] = '\0'; ++ node_num = atoi(fields[1]); ++ flags_start = 7; ++ } ++ ++ /* Update zone / net context */ ++ if (!strcmp(type, "Zone") || !strcmp(type, "ZONE")) ++ { ++ cur_zone = node_num; ++ cur_net = node_num; ++ continue; ++ } ++ ++ if (!strcmp(type, "Region") || !strcmp(type, "REGION")) ++ { ++ cur_net = node_num; ++ continue; ++ } ++ ++ if (!strcmp(type, "Host") || !strcmp(type, "HOST")) ++ cur_net = node_num; ++ ++ /* Skip unusable types */ ++ if (!strcmp(type, "Pvt") || !strcmp(type, "PVT") || !strcmp(type, "Hold") || !strcmp(type, "HOLD") || !strcmp(type, "Down") || !strcmp(type, "DOWN") || !strcmp(type, "Boss") || !strcmp(type, "BOSS")) ++ continue; ++ ++ /* Must have IBN flag */ ++ if (!find_flag(fields, nf, flags_start, "IBN", ibn_port, (int)sizeof(ibn_port))) ++ continue; ++ ++ /* Need INA:hostname */ ++ ina_host[0] = '\0'; ++ find_flag(fields, nf, flags_start, "INA", ina_host, (int)sizeof(ina_host)); ++ ++ if (!ina_host[0]) ++ continue; ++ ++ port = (ibn_port[0] && atoi(ibn_port) > 0) ? atoi(ibn_port) : 24554; ++ ++ fprintf(out, "node %d:%d/%d@%s %s:%d -\n", cur_zone, cur_net, node_num, domain, ina_host, port); ++ ++ count++; ++ } ++ ++ fclose(in); ++ ++ if (out != stdout) ++ fclose(out); ++ ++ fprintf(stderr, "nodelist: %ld BinkP node(s) found\n", count); ++ ++ return 0; ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/nodelist.txt binkd/misc/nodelist.txt +--- binkd_pgul/misc/nodelist.txt 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/nodelist.txt 2026-04-26 13:32:27.866048972 +0100 +@@ -0,0 +1,32 @@ ++nodelist -- Compile FidoNet nodelist to binkd.conf format ++ ++USAGE: ++ nodelist [] ++ ++DESCRIPTION: ++ Reads a FidoNet nodelist and outputs binkd.conf compatible ++ "node" configuration lines. ++ ++ Arguments: ++ nodelist_file Path to the FidoNet nodelist file ++ domain Domain name for the node entries (e.g., fidonet) ++ output_file Optional output file (default: stdout) ++ ++ Extracts the following flags: ++ IBN[:port] - BinkP protocol flag (Internet BinkP Node) ++ INA:hostname - IP hostname/address ++ ++ Output format: ++ node
@ : - ++ ++ The nodelist format is comma-separated: ++ [type,]node_num,name,city,sysop,phone,baud,flag1,flag2,... ++ type = Zone, Region, Host, Hub, Pvt, Hold, Down, Boss (empty = Node) ++ ++EXAMPLES: ++ nodelist Work:Fido/nodelist.123 fidonet > binkd-nodes.conf ++ nodelist /etc/fido/nodelist.456 fidonet >> binkd.conf ++ nodelist C:\Fido\NODELIST.001 fidonet C:\Fido\nodes.conf ++ ++CONFIGURATION FILE: ++ None. All parameters are command-line arguments. +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/portable.c binkd/misc/portable.c +--- binkd_pgul/misc/portable.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/portable.c 2026-04-26 13:04:59.214902887 +0100 +@@ -0,0 +1,402 @@ ++/* ++ * portable.c -- Shared implementations for misc tools portable layer ++ * ++ * portable.c is a part of binkd project ++ * ++ * This file provides implementations for common utility functions ++ * used across binkd misc tools. Include portable.h for declarations ++ * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ * ++ */ ++ ++#include "portable.h" ++#include ++ ++/* trim_nl -- Strip trailing newline (\n and \r) from string */ ++void trim_nl(char *s) ++{ ++ char *p = strchr(s, '\n'); ++ ++ if (p) ++ *p = '\0'; ++ ++ p = strchr(s, '\r'); ++ ++ if (p) ++ *p = '\0'; ++} ++ ++/* str_trim -- Strip trailing whitespace (space, \r, \n) from string */ ++void str_trim(char *s) ++{ ++ int n = (int)strlen(s); ++ ++ while (n > 0 && (s[n - 1] == '\r' || s[n - 1] == '\n' || s[n - 1] == ' ')) ++ s[--n] = '\0'; ++} ++ ++/* str_upper -- Convert string to uppercase in-place */ ++void str_upper(char *s) ++{ ++ while (*s) ++ { ++ *s = (char)toupper((unsigned char)*s); ++ s++; ++ } ++} ++ ++/* str_tolower -- Convert string to lowercase in-place */ ++void str_tolower(char *s) ++{ ++ for (; *s; s++) ++ { ++ if (*s >= 'A' && *s <= 'Z') ++ *s = (char)(*s + ('a' - 'A')); ++ } ++} ++ ++/* skip_ws -- Skip leading whitespace */ ++char *skip_ws(char *s) ++{ ++ while (*s == ' ' || *s == '\t') ++ s++; ++ ++ return s; ++} ++ ++/* wildmatch -- Portable wildcard match: case-insensitive, supports * and ? */ ++int wildmatch(const char *pat, const char *str) ++{ ++ while (*pat) ++ { ++ if (*pat == '*') ++ { ++ while (*pat == '*') ++ pat++; ++ ++ if (!*pat) ++ return 1; ++ ++ while (*str) ++ { ++ if (wildmatch(pat, str++)) ++ return 1; ++ } ++ ++ return 0; ++ } ++ ++ if (*pat == '?') ++ { ++ if (!*str) ++ return 0; ++ ++ pat++; ++ str++; ++ } ++ else ++ { ++ if (toupper((unsigned char)*pat) != toupper((unsigned char)*str)) ++ return 0; ++ ++ pat++; ++ str++; ++ } ++ } ++ ++ return (*str == '\0') ? 1 : 0; ++} ++ ++/* is_wildcard -- True if name contains * or ? */ ++int is_wildcard(const char *s) ++{ ++ while (*s) ++ { ++ if (*s == '*' || *s == '?') ++ return 1; ++ ++ s++; ++ } ++ ++ return 0; ++} ++ ++/* ensure_dir -- Ensure directory exists, creating if necessary */ ++int ensure_dir(const char *path) ++{ ++ if (path_exists(path)) ++ return 1; ++ ++ return (mkdir_recursive(path) == 0) ? 1 : 0; ++} ++ ++/* copy_file -- Portable binary file copy */ ++int copy_file(const char *src, const char *dst) ++{ ++ FILE *in, *out; ++ char buf[4096]; ++ int n; ++ ++ in = fopen(src, "rb"); ++ ++ if (!in) ++ return 0; ++ ++ out = fopen(dst, "wb"); ++ ++ if (!out) ++ { ++ fclose(in); ++ return 0; ++ } ++ ++ while ((n = (int)fread(buf, 1, sizeof(buf), in)) > 0) ++ fwrite(buf, 1, (size_t)n, out); ++ ++ fclose(out); ++ fclose(in); ++ ++ return 1; ++} ++ ++/* move_file -- Try rename first, fall back to copy+delete */ ++int move_file(const char *src, const char *dst) ++{ ++ remove(dst); ++ ++ if (rename(src, dst) == 0) ++ return 1; ++ ++ if (copy_file(src, dst)) ++ { ++ remove(src); ++ return 1; ++ } ++ ++ return 0; ++} ++ ++/* get_file_size -- Return file size in bytes, or -1 on error */ ++long get_file_size(const char *path) ++{ ++ struct stat st; ++ ++ if (stat(path, &st) == 0) ++ return (long)st.st_size; ++ ++ return -1; ++} ++ ++/* get_file_mtime -- Return Unix mtime of a file, or 0 on error */ ++long get_file_mtime(const char *path) ++{ ++ struct stat st; ++ ++ if (stat(path, &st) != 0) ++ return 0; ++ ++ return (long)st.st_mtime; ++} ++ ++/* port_path_exists -- Check if path exists (native per OS) */ ++ ++int port_path_exists(const char *p) ++{ ++#ifdef AMIGA ++ BPTR l = Lock((STRPTR)p, ACCESS_READ); ++ ++ if (l) ++ { ++ UnLock(l); ++ return 1; ++ } ++ ++ return 0; ++#else ++ struct stat st; ++ return (stat(p, &st) == 0) ? 1 : 0; ++#endif ++} ++ ++/* port_mkdir_one -- Create single directory (native per OS) */ ++int port_mkdir_one(const char *p) ++{ ++#ifdef AMIGA ++ BPTR l = CreateDir((STRPTR)p); ++ ++ if (l) ++ { ++ UnLock(l); ++ return 0; ++ } ++ ++ return -1; ++#else ++ return mkdir(p, 0755); ++#endif ++} ++ ++/* safe_localtime -- Thread-safe localtime, portable across all OS */ ++void safe_localtime(const time_t *t, struct tm *tm) ++{ ++#if defined(AMIGA) || defined(DOS) ++ *tm = *localtime(t); ++#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) ++ localtime_s(tm, t); ++#else ++ localtime_r(t, tm); ++#endif ++} ++ ++/* mkdir_recursive -- Create full path, making all missing components */ ++int mkdir_recursive(const char *path) ++{ ++ char tmp[MP_MAXPATH]; ++ char *p; ++ int len; ++ ++ if (!path || !path[0]) ++ return -1; ++ ++ strncpy(tmp, path, MP_MAXPATH - 1); ++ tmp[MP_MAXPATH - 1] = '\0'; ++ ++ len = (int)strlen(tmp); ++ ++ /* Strip trailing slash */ ++ while (len > 1 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\')) ++ tmp[--len] = '\0'; ++ ++ /* Walk every '/' component and create missing dirs */ ++ for (p = tmp + 1; *p; p++) ++ { ++ if (*p == '/' || *p == '\\') ++ { ++ *p = '\0'; ++ ++ if (!path_exists(tmp)) ++ mkdir_one(tmp); /* ignore per-component errors */ ++ ++ *p = '/'; ++ } ++ } ++ ++ /* Create the leaf */ ++ if (!path_exists(tmp)) ++ return mkdir_one(tmp); ++ ++ return 0; ++} ++ ++/* safe_strncpy -- Ctrncpy that always NUL-terminates */ ++void safe_strncpy(char *dst, const char *src, int dstsize) ++{ ++ int len; ++ ++ if (dstsize <= 0) ++ return; ++ ++ len = (int)strlen(src); ++ ++ if (len > dstsize - 1) ++ len = dstsize - 1; ++ ++ memcpy(dst, src, (size_t)len); ++ dst[len] = '\0'; ++} ++ ++/* path_join -- Concatenate base path with sub path */ ++void path_join(char *out, int outsize, const char *base, const char *sub) ++{ ++ int blen; ++ char last; ++ ++ safe_strncpy(out, base, outsize); ++ blen = (int)strlen(out); ++ last = (blen > 0) ? out[blen - 1] : '\0'; ++ ++ if (last != '/' && last != ':' && last != '\\') ++ { ++ if (outsize - 1 - blen > 0) ++ { ++ out[blen] = '/'; ++ out[blen + 1] = '\0'; ++ blen++; ++ } ++ } ++ ++ safe_strncpy(out + blen, sub, outsize - blen); ++} ++ ++/* make_abs_path -- Resolve a possibly-relative path to absolute ++ * Covers AmigaOS, Win32, OS/2, DOS and Unix ++ * Returns 1 on success, 0 on failure (src copied verbatim as fallback) ++ */ ++int make_abs_path(const char *src, char *dst, int dstlen) ++{ ++#ifdef AMIGA ++ BPTR lock = Lock((STRPTR)src, SHARED_LOCK); ++ ++ if (!lock) ++ { ++ safe_strncpy(dst, src, dstlen); ++ return 0; ++ } ++ ++ if (!NameFromLock(lock, (STRPTR)dst, dstlen)) ++ { ++ UnLock(lock); ++ safe_strncpy(dst, src, dstlen); ++ return 0; ++ } ++ ++ UnLock(lock); ++ ++ return 1; ++#elif defined(WIN32) || defined(__MINGW32__) || defined(__WATCOMC__) || defined(VISUALCPP) || defined(OS2) ++ if (_fullpath(dst, src, (size_t)dstlen) != NULL) ++ return 1; ++ ++ safe_strncpy(dst, src, dstlen); ++ return 0; ++#elif defined(DOS) ++ if (src[0] != '\\' && src[1] != ':') ++ { ++ char cwd[MAXPATHLEN + 1]; ++ ++ if (getcwd(cwd, sizeof(cwd)) != NULL) ++ { ++ snprintf(dst, dstlen, "%s\\%s", cwd, src); ++ return 1; ++ } ++ } ++ ++ safe_strncpy(dst, src, dstlen); ++ return 0; ++#else ++ char buf[MAXPATHLEN + 1]; ++ ++ if (realpath(src, buf) != NULL) ++ { ++ safe_strncpy(dst, buf, dstlen); ++ return 1; ++ } ++ ++ if (src[0] != '/') ++ { ++ char cwd[MAXPATHLEN + 1]; ++ ++ if (getcwd(cwd, sizeof(cwd)) != NULL) ++ { ++ snprintf(dst, dstlen, "%s/%s", cwd, src); ++ return 1; ++ } ++ } ++ ++ safe_strncpy(dst, src, dstlen); ++ return 0; ++#endif ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/portable.h binkd/misc/portable.h +--- binkd_pgul/misc/portable.h 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/portable.h 2026-04-26 14:19:41.472724309 +0100 +@@ -0,0 +1,135 @@ ++/* ++ * portable.h -- Portability layer for standalone binkd misc tools ++ * ++ * portable.h is a part of binkd project ++ * ++ * This is the single canonical portable.h; all misc utilities include this ++ * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ * ++ */ ++ ++#ifndef BINKD_PORTABLE_H ++#define BINKD_PORTABLE_H ++ ++/* _POSIX_C_SOURCE for opendir/readdir/localtime_r under -std=c89 ++ * _XOPEN_SOURCE 500 additionally exposes realpath() on glibc */ ++#ifndef AMIGA ++#ifndef _POSIX_C_SOURCE ++#define _POSIX_C_SOURCE 200112L ++#endif ++#ifndef _XOPEN_SOURCE ++#define _XOPEN_SOURCE 500 ++#endif ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef AMIGA ++ ++#include ++#include ++#include ++#include ++#include ++#include /* stat() / struct stat via libnix/ADE */ ++#include ++#include "amiga/dirent.h" /* opendir / readdir / closedir */ ++ ++/* snprintf/vsnprintf: ADE/libnix declares them in stdio.h (already included ++ * above via ). The implementation is provided by snprintf.c which ++ * must be linked when building the misc tools. No redeclaration needed */ ++ ++#elif defined(VISUALCPP) ++#include ++#include ++#include ++#include "nt/dirwin32.h" /* opendir/readdir/closedir for MSVC */ ++#elif defined(__MINGW32__) || defined(WIN32) ++#include ++#include /* MinGW provides dirent.h natively */ ++#include ++#include ++#elif defined(OS2) && (defined(IBMC) || defined(__WATCOMC__)) ++#include ++#include ++#include ++#include "os2/dirent.h" /* opendir/readdir/closedir for OS/2 ICC/WC */ ++#elif defined(OS2) ++#include ++#include /* EMX provides dirent.h natively */ ++#include ++#include ++#include ++#elif defined(DOS) ++#include ++#include ++#include "dos/dirent.h" /* opendir/readdir/closedir for DOS/DJGPP */ ++#else /* POSIX / *nix */ ++#include ++#include ++#include ++#include ++#include ++#endif ++ ++#ifndef MAXPATHLEN ++#if defined(_MAX_PATH) ++#define MAXPATHLEN _MAX_PATH ++#elif defined(PATH_MAX) ++#define MAXPATHLEN PATH_MAX ++#else ++#define MAXPATHLEN 1024 ++#endif ++#endif ++ ++/* Generic line buffer size for config files and text processing */ ++#ifndef MAX_LINE ++#define MAX_LINE 1024 ++#endif ++ ++/* path_exists / mkdir_one -- native implementations per OS */ ++int port_path_exists(const char *p); ++int port_mkdir_one(const char *p); ++#define path_exists(p) port_path_exists(p) ++#define mkdir_one(p) port_mkdir_one(p) ++ ++/* safe_localtime -- thread-safe localtime, portable across all OS */ ++void safe_localtime(const time_t *t, struct tm *tm); ++ ++/* mkdir_recursive -- create full path, making all missing components */ ++#define MP_MAXPATH 512 ++int mkdir_recursive(const char *path); ++ ++/* safe_strncpy -- strncpy that always NUL-terminates */ ++void safe_strncpy(char *dst, const char *src, int dstsize); ++ ++/* String utilities */ ++void trim_nl(char *s); ++void str_trim(char *s); ++void str_upper(char *s); ++void str_tolower(char *s); ++char *skip_ws(char *s); ++ ++/* Wildcard matching */ ++int wildmatch(const char *pat, const char *str); ++int is_wildcard(const char *s); ++ ++/* File operations */ ++int ensure_dir(const char *path); ++int copy_file(const char *src, const char *dst); ++int move_file(const char *src, const char *dst); ++long get_file_size(const char *path); ++long get_file_mtime(const char *path); ++ ++/* Path utilities */ ++void path_join(char *out, int outsize, const char *base, const char *sub); ++int make_abs_path(const char *src, char *dst, int dstlen); ++ ++#endif /* BINKD_PORTABLE_H */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/process_tic.c binkd/misc/process_tic.c +--- binkd_pgul/misc/process_tic.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/process_tic.c 2026-04-26 13:39:53.974576140 +0100 +@@ -0,0 +1,552 @@ ++/* ++ * process_tic -- Process FTN .tic files from inbound to filebox ++ * ++ * process_tic.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include "portable.h" /* Canonical portable layer */ ++#include ++ ++static int my_toupper(int c) ++{ ++ if (c >= 'a' && c <= 'z') ++ return c - 'a' + 'A'; ++ ++ return c; ++} ++ ++static int my_strnicmp(const char *a, const char *b, int n) ++{ ++ int i, ca, cb; ++ for (i = 0; i < n; i++) ++ { ++ ca = my_toupper((unsigned char)a[i]); ++ cb = my_toupper((unsigned char)b[i]); ++ ++ if (ca != cb) ++ return ca - cb; ++ ++ if (ca == 0) ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int parse_file_field(char *line, char *out, int outsize) ++{ ++ char *p; ++ char *end; ++ int len; ++ ++ p = skip_ws(line); ++ ++ if (my_strnicmp(p, "File", 4) != 0) ++ return 0; ++ ++ p += 4; ++ ++ if (*p != ' ' && *p != '\t') ++ return 0; ++ ++ p = skip_ws(p); ++ trim_nl(p); ++ end = p; ++ ++ while (*end && *end != ' ' && *end != '\t') ++ end++; ++ ++ *end = '\0'; ++ ++ len = (int)strlen(p); ++ ++ if (len <= 0 || len >= outsize) ++ return 0; ++ ++ strncpy(out, p, outsize - 1); ++ out[outsize - 1] = '\0'; ++ ++ return 1; ++} ++ ++static int parse_area_field(char *line, char *out, int outsize) ++{ ++ char *p; ++ char *end; ++ int len; ++ ++ p = skip_ws(line); ++ ++ if (my_strnicmp(p, "Area", 4) != 0) ++ return 0; ++ ++ p += 4; ++ ++ if (*p != ' ' && *p != '\t') ++ return 0; ++ ++ p = skip_ws(p); ++ trim_nl(p); ++ end = p; ++ ++ while (*end && *end != ' ' && *end != '\t') ++ end++; ++ ++ *end = '\0'; ++ len = (int)strlen(p); ++ ++ if (len <= 0 || len >= outsize) ++ return 0; ++ ++ strncpy(out, p, outsize - 1); ++ out[outsize - 1] = '\0'; ++ ++ return 1; ++} ++ ++static int parse_origin_field(char *line, char *out, int outsize) ++{ ++ char *p; ++ char *end; ++ int len; ++ ++ p = skip_ws(line); ++ ++ if (my_strnicmp(p, "Origin", 6) != 0) ++ return 0; ++ ++ p += 6; ++ ++ if (*p != ' ' && *p != '\t') ++ return 0; ++ ++ p = skip_ws(p); ++ trim_nl(p); ++ end = p; ++ ++ while (*end && *end != ' ' && *end != '\t') ++ end++; ++ ++ *end = '\0'; ++ len = (int)strlen(p); ++ ++ if (len <= 0 || len >= outsize) ++ return 0; ++ ++ strncpy(out, p, outsize - 1); ++ out[outsize - 1] = '\0'; ++ ++ return 1; ++} ++ ++static int parse_from_field(char *line, char *out, int outsize) ++{ ++ char *p; ++ char *end; ++ int len; ++ ++ p = skip_ws(line); ++ ++ if (my_strnicmp(p, "From", 4) != 0) ++ return 0; ++ ++ p += 4; ++ ++ if (*p != ' ' && *p != '\t') ++ return 0; ++ ++ p = skip_ws(p); ++ trim_nl(p); ++ end = p; ++ ++ while (*end && *end != ' ' && *end != '\t') ++ end++; ++ ++ *end = '\0'; ++ len = (int)strlen(p); ++ ++ if (len <= 0 || len >= outsize) ++ return 0; ++ ++ strncpy(out, p, outsize - 1); ++ out[outsize - 1] = '\0'; ++ ++ return 1; ++} ++ ++static void append_filelist(const char *listpath, const char *file_name, long filesize, const char *dst_path) ++{ ++ FILE *f; ++ time_t t; ++ struct tm tm; ++ char timestamp[32]; ++ ++ if (!listpath || !listpath[0]) ++ return; ++ ++ f = fopen(listpath, "a"); ++ if (!f) ++ return; ++ ++ t = time(NULL); ++ safe_localtime(&t, &tm); ++ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); ++ ++ fprintf(f, "%s\t%s\t%ld\t%s\n", timestamp, file_name, filesize, dst_path); ++ fclose(f); ++} ++ ++static void append_newfiles(const char *newprefix, const char *file_name, long filesize, const char *dst_path) ++{ ++ FILE *f; ++ char newpath[MAXPATHLEN]; ++ time_t t; ++ struct tm tm; ++ char datebuf[16]; ++ ++ if (!newprefix || !newprefix[0]) ++ return; ++ ++ t = time(NULL); ++ safe_localtime(&t, &tm); ++ strftime(datebuf, sizeof(datebuf), "%Y%m%d", &tm); ++ ++ ensure_dir(newprefix); ++ path_join(newpath, (int)sizeof(newpath), newprefix, "newfiles-"); ++ strncat(newpath, datebuf, sizeof(newpath) - strlen(newpath) - 1); ++ strncat(newpath, ".txt", sizeof(newpath) - strlen(newpath) - 1); ++ ++ f = fopen(newpath, "a"); ++ ++ if (!f) ++ return; ++ ++ fprintf(f, "%s\t%ld\t%s\n", file_name, filesize, dst_path); ++ ++ fclose(f); ++} ++ ++static void write_ticlog(const char *ticlog, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) ++{ ++ FILE *f; ++ time_t t; ++ struct tm tm; ++ char timestamp[64]; ++ ++ if (!ticlog || !ticlog[0]) ++ return; ++ ++ f = fopen(ticlog, "a"); ++ if (!f) ++ return; ++ ++ t = time(NULL); ++ safe_localtime(&t, &tm); ++ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); ++ ++ fprintf(f, "[%s] File: %s\n", timestamp, file_name); ++ fprintf(f, " Area: %s\n", area_name); ++ ++ if (origin_name[0]) ++ fprintf(f, " Origin: %s\n", origin_name); ++ ++ if (from_name[0]) ++ fprintf(f, " From: %s\n", from_name); ++ ++ fprintf(f, " Src: %s\n", src_path); ++ fprintf(f, " To: %s\n", dst_path); ++ fprintf(f, "\n"); ++ ++ fclose(f); ++} ++ ++static void write_log(const char *logfile, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) ++{ ++ FILE *f; ++ time_t t; ++ struct tm tm; ++ char timestamp[64]; ++ ++ if (!logfile || !logfile[0]) ++ return; ++ ++ f = fopen(logfile, "a"); ++ if (!f) ++ return; ++ ++ t = time(NULL); ++ safe_localtime(&t, &tm); ++ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); ++ ++ fprintf(f, "[%s] File: %s\n", timestamp, file_name); ++ fprintf(f, " Area: %s\n", area_name); ++ ++ if (origin_name[0]) ++ fprintf(f, " Origin: %s\n", origin_name); ++ ++ if (from_name[0]) ++ fprintf(f, " From: %s\n", from_name); ++ ++ fprintf(f, " Src: %s\n", src_path); ++ fprintf(f, " To: %s\n", dst_path); ++ fprintf(f, "\n"); ++ fclose(f); ++} ++ ++static void process_one_tic(const char *ticpath, const char *inbound, const char *filebox, int copypublic, const char *pubdir, const char *logfile, const char *filelist, const char *newfiles, const char *ticlog) ++{ ++ FILE *f; ++ char line[MAX_LINE]; ++ char file_name[MAXPATHLEN]; ++ char area_name[MAXPATHLEN]; ++ char origin_name[MAXPATHLEN]; ++ char from_name[MAXPATHLEN]; ++ char src_path[MAXPATHLEN]; ++ char area_dir[MAXPATHLEN]; ++ char dst_path[MAXPATHLEN]; ++ ++ file_name[0] = '\0'; ++ area_name[0] = '\0'; ++ origin_name[0] = '\0'; ++ from_name[0] = '\0'; ++ long fsize = 0; ++ ++ f = fopen(ticpath, "r"); ++ ++ if (!f) ++ return; ++ ++ while (fgets(line, sizeof(line), f)) ++ { ++ if (!file_name[0]) ++ parse_file_field(line, file_name, sizeof(file_name)); ++ ++ if (!area_name[0]) ++ parse_area_field(line, area_name, sizeof(area_name)); ++ ++ if (!origin_name[0]) ++ parse_origin_field(line, origin_name, sizeof(origin_name)); ++ ++ if (!from_name[0]) ++ parse_from_field(line, from_name, sizeof(from_name)); ++ } ++ ++ fclose(f); ++ ++ if (!file_name[0] || !area_name[0]) ++ return; ++ ++ path_join(src_path, sizeof(src_path), inbound, file_name); ++ path_join(area_dir, sizeof(area_dir), filebox, area_name); ++ path_join(dst_path, sizeof(dst_path), area_dir, file_name); ++ ++ if (!path_exists(src_path)) ++ return; ++ ++ if (!ensure_dir(filebox) || !ensure_dir(area_dir)) ++ return; ++ ++ if (copypublic && pubdir && pubdir[0]) ++ { ++ char pub_dst[MAXPATHLEN]; ++ ++ path_join(pub_dst, sizeof(pub_dst), pubdir, file_name); ++ ++ if (ensure_dir(pubdir)) ++ copy_file(src_path, pub_dst); ++ } ++ ++ fsize = get_file_size(src_path); ++ ++ if (!move_file(src_path, dst_path)) ++ return; ++ ++ write_log(logfile, file_name, area_name, origin_name, from_name, src_path, dst_path); ++ write_ticlog(ticlog, file_name, area_name, origin_name, from_name, src_path, dst_path); ++ append_filelist(filelist, file_name, fsize, dst_path); ++ append_newfiles(newfiles, file_name, fsize, dst_path); ++ ++ remove(ticpath); ++} ++ ++static int is_tic_file(const char *name) ++{ ++ int len = (int)strlen(name); ++ ++ if (len < 5) ++ return 0; ++ ++ return (my_strnicmp(name + len - 4, ".tic", 4) == 0); ++} ++ ++/* Config structure */ ++static struct ++{ ++ char inbound[MAXPATHLEN]; ++ char filebox[MAXPATHLEN]; ++ char pubdir[MAXPATHLEN]; ++ char logfile[MAXPATHLEN]; ++ char filelist[MAXPATHLEN]; ++ char newfiles[MAXPATHLEN]; ++ char ticlog[MAXPATHLEN]; ++ int copypublic; ++} cfg; ++ ++/* Parse configuration file */ ++static int parse_config(const char *conffile) ++{ ++ FILE *f; ++ char line[MAX_LINE]; ++ char *key, *value; ++ ++ memset(&cfg, 0, sizeof(cfg)); ++ ++ f = fopen(conffile, "r"); ++ ++ if (!f) ++ { ++ fprintf(stderr, "process_tic: cannot open config file: %s\n", conffile); ++ return 0; ++ } ++ ++ while (fgets(line, sizeof(line), f)) ++ { ++ trim_nl(line); ++ key = skip_ws(line); ++ ++ /* Skip comments and empty lines */ ++ if (*key == '#' || *key == '\0') ++ continue; ++ ++ /* Find value after key */ ++ value = key; ++ ++ while (*value && *value != ' ' && *value != '\t') ++ value++; ++ ++ if (*value) ++ { ++ *value = '\0'; ++ value = skip_ws(value + 1); ++ } ++ ++ /* Parse key-value pairs */ ++ if (strcmp(key, "inbound") == 0) ++ safe_strncpy(cfg.inbound, value, (int)sizeof(cfg.inbound)); ++ else if (strcmp(key, "filebox") == 0) ++ safe_strncpy(cfg.filebox, value, (int)sizeof(cfg.filebox)); ++ else if (strcmp(key, "pubdir") == 0) ++ { ++ safe_strncpy(cfg.pubdir, value, (int)sizeof(cfg.pubdir)); ++ cfg.copypublic = 1; ++ } ++ else if (strcmp(key, "logfile") == 0) ++ safe_strncpy(cfg.logfile, value, (int)sizeof(cfg.logfile)); ++ else if (strcmp(key, "filelist") == 0) ++ safe_strncpy(cfg.filelist, value, (int)sizeof(cfg.filelist)); ++ else if (strcmp(key, "newfiles") == 0) ++ safe_strncpy(cfg.newfiles, value, (int)sizeof(cfg.newfiles)); ++ else if (strcmp(key, "ticlog") == 0) ++ safe_strncpy(cfg.ticlog, value, (int)sizeof(cfg.ticlog)); ++ } ++ ++ fclose(f); ++ ++ /* Validate required fields */ ++ if (!cfg.inbound[0] || !cfg.filebox[0]) ++ { ++ fprintf(stderr, "process_tic: config file missing required 'inbound' or 'filebox'\n"); ++ return 0; ++ } ++ ++ return 1; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ char inbound[MAXPATHLEN]; ++ char filebox[MAXPATHLEN]; ++ char ticpath[MAXPATHLEN]; ++ char pubdir[MAXPATHLEN]; ++ char logfile[MAXPATHLEN]; ++ char filelist[MAXPATHLEN]; ++ char newfiles[MAXPATHLEN]; ++ char ticlog[MAXPATHLEN]; ++ int copypublic = 0; ++ DIR *dp; ++ struct dirent *de; ++ int found; ++ int i; ++ int use_config = 0; ++ ++ inbound[0] = '\0'; ++ filebox[0] = '\0'; ++ pubdir[0] = '\0'; ++ logfile[0] = '\0'; ++ filelist[0] = '\0'; ++ newfiles[0] = '\0'; ++ ticlog[0] = '\0'; ++ ++ /* Check for --conf option */ ++ for (i = 1; i < argc; i++) ++ { ++ if (strcmp(argv[i], "--conf") == 0 && i + 1 < argc) ++ { ++ if (!parse_config(argv[i + 1])) ++ return 1; ++ ++ use_config = 1; ++ i++; /* Skip config file path */ ++ ++ break; ++ } ++ } ++ ++ if (use_config) ++ { ++ /* Use config file values */ ++ safe_strncpy(inbound, cfg.inbound, (int)sizeof(inbound)); ++ safe_strncpy(filebox, cfg.filebox, (int)sizeof(filebox)); ++ safe_strncpy(pubdir, cfg.pubdir, (int)sizeof(pubdir)); ++ safe_strncpy(logfile, cfg.logfile, (int)sizeof(logfile)); ++ safe_strncpy(filelist, cfg.filelist, (int)sizeof(filelist)); ++ safe_strncpy(newfiles, cfg.newfiles, (int)sizeof(newfiles)); ++ safe_strncpy(ticlog, cfg.ticlog, (int)sizeof(ticlog)); ++ copypublic = cfg.copypublic; ++ } ++ ++ if (!inbound[0] || !filebox[0]) ++ { ++ fprintf(stderr, ++ "Usage: process_tic --conf [*.tic]\n"); ++ ++ return 1; ++ } ++ ++ dp = opendir(inbound); ++ ++ if (!dp) ++ return 1; ++ ++ found = 0; ++ ++ while ((de = readdir(dp)) != NULL) ++ { ++ /* Skip . and .. */ ++ if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) ++ continue; ++ ++ if (!is_tic_file(de->d_name)) ++ continue; ++ ++ path_join(ticpath, sizeof(ticpath), inbound, de->d_name); ++ process_one_tic(ticpath, inbound, filebox, copypublic, pubdir, logfile, filelist, newfiles, ticlog); ++ found++; ++ } ++ ++ closedir(dp); ++ return 0; ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/process_tic.txt binkd/misc/process_tic.txt +--- binkd_pgul/misc/process_tic.txt 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/process_tic.txt 2026-04-26 13:43:48.542112490 +0100 +@@ -0,0 +1,45 @@ ++process_tic -- Process .tic file announcements ++ ++USAGE: ++ process_tic --conf [files.tic...] ++ ++DESCRIPTION: ++ Processes .tic (Ticker announcement) files from the inbound directory ++ and moves/copies files to their destination filebox or public directory. ++ ++ The .tic file is parsed for File, Area, Origin, and From fields. ++ The actual file is moved from inbound to filebox/AreaName/. ++ ++ All settings are read from the configuration file. ++ ++OPTIONS: ++ --conf Configuration file (required) ++ ++CONFIGURATION FILE FORMAT: ++ # Lines starting with # are comments ++ # Blank lines are ignored ++ ++ inbound Inbound directory (required) ++ filebox Filebox destination (required) ++ pubdir Public directory for --copy-public ++ logfile Log file path ++ ticlog TIC processing log ++ filelist File list output ++ newfiles New files list output ++ ++EXAMPLE CONFIG FILE (process_tic.conf): ++ # process_tic.conf - Configuration for TIC processor ++ ++ inbound Work:Inbound ++ filebox Work:Filebox ++ pubdir Work:Public ++ logfile Work:Logs/process_tic.log ++ ticlog Work:Logs/tic.log ++ filelist Work:Filebox/filelist.txt ++ newfiles Work:Filebox/newfiles.txt ++ ++EXAMPLES: ++ process_tic --conf process_tic.conf ++ process_tic --conf process_tic.conf inbound/*.tic ++ ++ exec "process_tic --conf work:fido/process_tic.conf" *.tic *.TIC +\ No hay ningún carácter de nueva línea al final del archivo +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/srifreq.c binkd/misc/srifreq.c +--- binkd_pgul/misc/srifreq.c 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/srifreq.c 2026-04-26 15:01:11.006850708 +0100 +@@ -0,0 +1,1024 @@ ++/* ++ * srifreq.c -- SRIF-compatible file-request server for binkd ++ * ++ * srifreq.c is a part of binkd project ++ * ++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet ++ * Licensed under the GNU GPL v2 or later ++ */ ++ ++#include "portable.h" /* Canonical portable layer */ ++#include ++#include ++ ++/* Private directory entry (dynamically allocated list) */ ++typedef struct PrivDir ++{ ++ char path[MAXPATHLEN]; ++ char password[64]; ++ struct PrivDir *next; ++} PrivDir; ++ ++/* Node tracking entry for rate limiting */ ++typedef struct NodeTrack ++{ ++ char aka[256]; /* Node address (4D/5D) */ ++ int files; /* Files downloaded in window */ ++ long bytes; /* Bytes downloaded in window */ ++ time_t last_time; /* Timestamp of last download */ ++ struct NodeTrack *next; ++} NodeTrack; ++ ++/* Global configuration (filled from --conf file) */ ++typedef struct ++{ ++ char pubdir[MAXPATHLEN]; ++ char logfile[MAXPATHLEN]; ++ char aliases[MAXPATHLEN]; ++ char trackfile[MAXPATHLEN]; /* Path to tracking file */ ++ int maxfiles; /* Max files per node per window (0=unlimited) */ ++ long maxbytes; /* Max bytes per node per window (0=unlimited) */ ++ long timewindow; /* Time window in seconds (0=no window) */ ++ PrivDir *privdirs; /* Linked list, NULL if none */ ++ NodeTrack *tracking; /* Linked list of tracked nodes */ ++} Config; ++ ++/* Alias table -- Loaded from file at startup */ ++typedef struct ++{ ++ char name[64]; ++ char path[MAXPATHLEN]; ++} Alias; ++ ++/* SRIF parsing */ ++typedef struct ++{ ++ char sysop[128]; ++ char aka[256]; ++ char request_list[MAXPATHLEN]; ++ char response_list[MAXPATHLEN]; ++ char our_aka[128]; ++ char caller_id[64]; /* CallerID: IP or phone of remote */ ++ char password[64]; /* Password: session password */ ++ int time_limit; /* Time: minutes left, -1 = unlimited */ ++ long tranx; /* TRANX: remote local time as Unix ts (hex in SRIF) */ ++ int protected_sess; /* RemoteStatus: 1=PROTECTED, 0=UNPROTECTED */ ++ int listed; /* SystemStatus: 1=LISTED, 0=UNLISTED */ ++ int got_request_list; ++ int got_response_list; ++} SRIF; ++ ++static Alias *g_aliases = NULL; ++static int g_nalias = 0; ++static int g_alias_cap = 0; ++static Config g_conf; ++ ++static void config_init(void) ++{ ++ memset(&g_conf, 0, sizeof(g_conf)); ++ g_conf.privdirs = NULL; ++ g_conf.tracking = NULL; ++ g_conf.maxfiles = 0; /* 0 = unlimited */ ++ g_conf.maxbytes = 0; /* 0 = unlimited */ ++ g_conf.timewindow = 0; /* 0 = no window */ ++} ++ ++static void config_add_private(const char *path, const char *password) ++{ ++ PrivDir *pd = (PrivDir *)malloc(sizeof(PrivDir)); ++ PrivDir *tail; ++ ++ if (!pd) ++ return; ++ ++ safe_strncpy(pd->path, path, (int)sizeof(pd->path)); ++ safe_strncpy(pd->password, password, (int)sizeof(pd->password)); ++ ++ pd->next = NULL; ++ ++ /* Append to tail */ ++ if (!g_conf.privdirs) ++ g_conf.privdirs = pd; ++ else ++ { ++ tail = g_conf.privdirs; ++ ++ while (tail->next) ++ tail = tail->next; ++ ++ tail->next = pd; ++ } ++} ++ ++static void config_free(void) ++{ ++ PrivDir *pd = g_conf.privdirs; ++ NodeTrack *nt = g_conf.tracking; ++ ++ while (pd) ++ { ++ PrivDir *next = pd->next; ++ free(pd); ++ pd = next; ++ } ++ ++ g_conf.privdirs = NULL; ++ ++ while (nt) ++ { ++ NodeTrack *next = nt->next; ++ free(nt); ++ nt = next; ++ } ++ ++ g_conf.tracking = NULL; ++ ++ if (g_aliases) ++ { ++ free(g_aliases); ++ g_aliases = NULL; ++ g_nalias = 0; ++ g_alias_cap = 0; ++ } ++} ++ ++static int load_config(const char *path) ++{ ++ FILE *f; ++ char line[MAX_LINE]; ++ char key[64], val[MAXPATHLEN], pw[64]; ++ int n; ++ ++ f = fopen(path, "r"); ++ ++ if (!f) ++ { ++ fprintf(stderr, "srifreq: cannot open config: %s\n", path); ++ return 0; ++ } ++ ++ while (fgets(line, sizeof(line), f)) ++ { ++ /* Strip trailing whitespace and newlines */ ++ n = (int)strlen(line); ++ ++ while (n > 0 && ++ (line[n - 1] == '\r' || line[n - 1] == '\n' || line[n - 1] == ' ')) ++ line[--n] = '\0'; ++ ++ /* Skip blank and comment lines */ ++ if (!line[0] || line[0] == '#') ++ continue; ++ ++ key[0] = val[0] = pw[0] = '\0'; ++ ++ if (sscanf(line, "%63s %1023s %63s", key, val, pw) < 2) ++ continue; ++ ++ if (strcmp(key, "pubdir") == 0) ++ safe_strncpy(g_conf.pubdir, val, (int)sizeof(g_conf.pubdir)); ++ else if (strcmp(key, "logfile") == 0) ++ safe_strncpy(g_conf.logfile, val, (int)sizeof(g_conf.logfile)); ++ else if (strcmp(key, "aliases") == 0) ++ safe_strncpy(g_conf.aliases, val, (int)sizeof(g_conf.aliases)); ++ else if (strcmp(key, "trackfile") == 0) ++ safe_strncpy(g_conf.trackfile, val, (int)sizeof(g_conf.trackfile)); ++ else if (strcmp(key, "maxfiles") == 0) ++ g_conf.maxfiles = atoi(val); ++ else if (strcmp(key, "maxsize") == 0) ++ g_conf.maxbytes = atol(val); ++ else if (strcmp(key, "timewindow") == 0) ++ g_conf.timewindow = atol(val); ++ else if (strcmp(key, "private") == 0 && pw[0]) ++ config_add_private(val, pw); ++ } ++ ++ fclose(f); ++ ++ return 1; ++} ++ ++/* tracking_load -- Load node tracking data from file */ ++static void tracking_load(void) ++{ ++ FILE *f; ++ char line[MAX_LINE]; ++ char aka[256]; ++ int files; ++ long bytes; ++ long timestamp; ++ NodeTrack *nt; ++ time_t now = time(NULL); ++ ++ if (!g_conf.trackfile[0]) ++ return; ++ ++ f = fopen(g_conf.trackfile, "r"); ++ ++ if (!f) ++ return; ++ ++ while (fgets(line, sizeof(line), f)) ++ { ++ str_trim(line); ++ ++ if (!line[0] || line[0] == '#') ++ continue; ++ ++ if (sscanf(line, "%255s %d %ld %ld", aka, &files, &bytes, ×tamp) != 4) ++ continue; ++ ++ /* Skip if outside time window (also reject future/corrupt timestamps) */ ++ if (g_conf.timewindow > 0 && (timestamp > now || (now - timestamp) > g_conf.timewindow)) ++ continue; ++ ++ /* Create new tracking entry */ ++ nt = (NodeTrack *)malloc(sizeof(NodeTrack)); ++ ++ if (!nt) ++ continue; ++ ++ safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); ++ nt->files = files; ++ nt->bytes = bytes; ++ nt->last_time = (time_t)timestamp; ++ nt->next = g_conf.tracking; ++ g_conf.tracking = nt; ++ } ++ ++ fclose(f); ++} ++ ++/* tracking_save -- Save node tracking data to file */ ++static void tracking_save(void) ++{ ++ FILE *f; ++ NodeTrack *nt; ++ ++ if (!g_conf.trackfile[0]) ++ return; ++ ++ f = fopen(g_conf.trackfile, "w"); ++ ++ if (!f) ++ return; ++ ++ fprintf(f, "# srifreq tracking file - Format: AKA files bytes timestamp\n"); ++ ++ for (nt = g_conf.tracking; nt; nt = nt->next) ++ { ++ fprintf(f, "%s %d %ld %ld\n", nt->aka, nt->files, nt->bytes, (long)nt->last_time); ++ } ++ ++ fclose(f); ++} ++ ++/* tracking_find -- Find tracking entry for a node */ ++static NodeTrack *tracking_find(const char *aka) ++{ ++ NodeTrack *nt; ++ ++ for (nt = g_conf.tracking; nt; nt = nt->next) ++ { ++ if (strcmp(nt->aka, aka) == 0) ++ return nt; ++ } ++ ++ return NULL; ++} ++ ++/* tracking_update -- Update tracking after serving a file */ ++static void tracking_update(const char *aka, long filesize) ++{ ++ NodeTrack *nt = tracking_find(aka); ++ time_t now = time(NULL); ++ ++ if (nt) ++ { ++ /* Update existing entry */ ++ nt->files++; ++ nt->bytes += filesize; ++ nt->last_time = now; ++ } ++ else ++ { ++ /* Create new entry */ ++ nt = (NodeTrack *)malloc(sizeof(NodeTrack)); ++ ++ if (nt) ++ { ++ safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); ++ nt->files = 1; ++ nt->bytes = filesize; ++ nt->last_time = now; ++ nt->next = g_conf.tracking; ++ g_conf.tracking = nt; ++ } ++ } ++} ++ ++/* tracking_check -- Check if node exceeds limits */ ++static int tracking_check(const char *aka, char *msg, int msglen) ++{ ++ NodeTrack *nt = tracking_find(aka); ++ ++ if (!nt) ++ return 1; /* No tracking yet, allow */ ++ ++ /* Check max files */ ++ if (g_conf.maxfiles > 0 && nt->files >= g_conf.maxfiles) ++ { ++ snprintf(msg, msglen, "RATE LIMIT: max files (%d) reached for %s", g_conf.maxfiles, aka); ++ return 0; ++ } ++ ++ /* Check max bytes */ ++ if (g_conf.maxbytes > 0 && nt->bytes >= g_conf.maxbytes) ++ { ++ snprintf(msg, msglen, "RATE LIMIT: max bytes (%ld) reached for %s", g_conf.maxbytes, aka); ++ return 0; ++ } ++ ++ return 1; /* Within limits */ ++} ++ ++/* is_abs_path -- True if path is absolute (POSIX, Win32, AmigaDOS device:) */ ++static int is_abs_path(const char *p) ++{ ++ if (!p || !p[0]) ++ return 0; ++ ++ if (p[0] == '/' || p[0] == '\\') ++ return 1; ++ ++#ifdef AMIGA ++ if (strchr(p, ':') != NULL) ++ return 1; ++#else ++ if (p[1] == ':') ++ return 1; /* C:\ etc. */ ++#endif ++ return 0; ++} ++ ++/* ++ * load_aliases -- Read alias definitions from file ++ * Lines starting with '#' or empty are skipped ++ * Format: ++ */ ++static void load_aliases(const char *filepath) ++{ ++ FILE *f; ++ char line[MAX_LINE]; ++ char name[64]; ++ char path[MAXPATHLEN]; ++ int n; ++ ++ /* Free previous aliases and start fresh */ ++ if (g_aliases) ++ { ++ free(g_aliases); ++ g_aliases = NULL; ++ } ++ ++ g_nalias = 0; ++ g_alias_cap = 0; ++ ++ if (!filepath || !filepath[0] || strcmp(filepath, "-") == 0) ++ return; ++ ++ f = fopen(filepath, "r"); ++ ++ if (!f) ++ { ++ /*fprintf(stderr, "srifreq: cannot open aliases file: %s\n", filepath);*/ ++ return; ++ } ++ ++ while (fgets(line, sizeof(line), f)) ++ { ++ char *p; ++ ++ /* Strip trailing newline */ ++ n = (int)strlen(line); ++ ++ while (n > 0 && (line[n - 1] == '\r' || line[n - 1] == '\n')) ++ line[--n] = '\0'; ++ ++ /* Skip blanks and comments */ ++ p = line; ++ ++ while (*p == ' ' || *p == '\t') ++ p++; ++ ++ if (!*p || *p == '#') ++ continue; ++ ++ name[0] = '\0'; ++ path[0] = '\0'; ++ ++ if (sscanf(p, "%63s %1023[^\n]", name, path) < 2) ++ continue; ++ ++ if (!name[0] || !path[0]) ++ continue; ++ ++ /* Grow array dynamically if needed */ ++ if (g_nalias >= g_alias_cap) ++ { ++ int new_cap = g_alias_cap ? g_alias_cap * 2 : 16; ++ Alias *new_arr = realloc(g_aliases, (size_t)new_cap * sizeof(Alias)); ++ ++ if (!new_arr) ++ break; ++ ++ g_aliases = new_arr; ++ g_alias_cap = new_cap; ++ } ++ ++ safe_strncpy(g_aliases[g_nalias].name, name, (int)sizeof(g_aliases[g_nalias].name)); ++ safe_strncpy(g_aliases[g_nalias].path, path, (int)sizeof(g_aliases[g_nalias].path)); ++ g_nalias++; ++ } ++ ++ fclose(f); ++ ++ /*printf("srifreq: loaded %d alias(es) from %s\n", g_nalias, filepath);*/ ++} ++ ++/* ++ * find_alias -- look up name in alias table (case-insensitive) ++ * Returns the path string, or NULL if not found ++ */ ++static const char *find_alias(const char *name) ++{ ++ char upper[64]; ++ char aname[64]; ++ int i; ++ int n; ++ ++ /* Convert name to uppercase */ ++ n = (int)strlen(name); ++ ++ if (n >= (int)sizeof(upper)) ++ n = (int)sizeof(upper) - 1; ++ ++ for (i = 0; i < n; i++) ++ upper[i] = (char)toupper((unsigned char)name[i]); ++ ++ upper[n] = '\0'; ++ ++ for (i = 0; i < g_nalias; i++) ++ { ++ int an; ++ an = (int)strlen(g_aliases[i].name); ++ ++ if (an >= (int)sizeof(aname)) an = (int)sizeof(aname) - 1; ++ { ++ int j; ++ ++ for (j = 0; j < an; j++) ++ aname[j] = (char)toupper((unsigned char)g_aliases[i].name[j]); ++ ++ aname[an] = '\0'; ++ } ++ ++ if (strcmp(upper, aname) == 0) ++ return g_aliases[i].path; ++ } ++ ++ return NULL; ++} ++ ++static int parse_srif(const char *path, SRIF *srif) ++{ ++ FILE *f; ++ char line[MAX_LINE]; ++ char token[MAX_LINE]; ++ char value[MAX_LINE]; ++ ++ memset(srif, 0, sizeof(SRIF)); ++ srif->time_limit = -1; /* default: unlimited */ ++ srif->listed = 1; /* default: assume listed */ ++ srif->protected_sess = 0; ++ ++ f = fopen(path, "r"); ++ if (!f) ++ return 0; ++ ++ while (fgets(line, sizeof(line), f)) ++ { ++ str_trim(line); ++ if (!line[0]) ++ continue; ++ ++ token[0] = '\0'; ++ value[0] = '\0'; ++ ++ if (sscanf(line, "%1023s %1023[^\n]", token, value) < 1) ++ continue; ++ ++ str_upper(token); ++ ++ if (!value[0]) ++ continue; ++ ++ if (!strcmp(token, "SYSOP")) ++ safe_strncpy(srif->sysop, value, (int)sizeof(srif->sysop)); ++ else if (!strcmp(token, "AKA") && !srif->aka[0]) ++ safe_strncpy(srif->aka, value, (int)sizeof(srif->aka)); ++ else if (!strcmp(token, "REQUESTLIST")) ++ { ++ safe_strncpy(srif->request_list, value, MAXPATHLEN); ++ srif->got_request_list = 1; ++ } ++ else if (!strcmp(token, "RESPONSELIST")) ++ { ++ safe_strncpy(srif->response_list, value, MAXPATHLEN); ++ srif->got_response_list = 1; ++ } ++ else if (!strcmp(token, "OURAKA")) ++ safe_strncpy(srif->our_aka, value, (int)sizeof(srif->our_aka)); ++ else if (!strcmp(token, "PASSWORD")) ++ safe_strncpy(srif->password, value, (int)sizeof(srif->password)); ++ else if (!strcmp(token, "CALLERID")) ++ safe_strncpy(srif->caller_id, value, (int)sizeof(srif->caller_id)); ++ else if (!strcmp(token, "TIME")) ++ srif->time_limit = atoi(value); ++ else if (!strcmp(token, "TRANX")) ++ { ++ /* TRANX is a hex Unix timestamp: 5a326682 or 16-digit */ ++ unsigned long v = 0; ++ sscanf(value, "%lx", &v); ++ srif->tranx = (long)v; ++ } ++ else if (!strcmp(token, "REMOTESTATUS")) ++ { ++ char tmp[32]; ++ safe_strncpy(tmp, value, (int)sizeof(tmp)); ++ str_upper(tmp); ++ srif->protected_sess = (strncmp(tmp, "PROTECTED", 9) == 0) ? 1 : 0; ++ } ++ else if (!strcmp(token, "SYSTEMSTATUS")) ++ { ++ char tmp[32]; ++ safe_strncpy(tmp, value, (int)sizeof(tmp)); ++ str_upper(tmp); ++ srif->listed = (strncmp(tmp, "LISTED", 6) == 0) ? 1 : 0; ++ } ++ } ++ ++ fclose(f); ++ ++ return srif->got_request_list; ++} ++ ++/* Logging */ ++static void do_log(const char *logpath, const char *msg) ++{ ++ FILE *lf; ++ time_t t; ++ struct tm tm; ++ char timestamp[32]; ++ ++ if (!logpath || !logpath[0] || strcmp(logpath, "-") == 0) ++ return; ++ ++ t = time(NULL); ++ safe_localtime(&t, &tm); ++ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); ++ ++ lf = fopen(logpath, "a"); ++ ++ if (lf) ++ { ++ fprintf(lf, "[%s] srifreq: %s\n", timestamp, msg); ++ fclose(lf); ++ } ++} ++ ++/* serve_one -- resolve one request name, check password/timestamp/update ++ * write to response list. Returns 1 if served ++ */ ++static int serve_one(const char *req_name, const char *found_path, const char *req_pass, long req_newer, int req_update, const SRIF *srif, FILE *rsp_f, const char *log_path, char *logbuf, int logbuf_size) ++{ ++ long fsize; ++ ++ /* Check rate limits before serving */ ++ if (g_conf.trackfile[0] && !tracking_check(srif->aka, logbuf, logbuf_size)) ++ { ++ do_log(log_path, logbuf); ++ return 0; ++ } ++ ++ /* RemoteStatus: if session is unprotected and a password is required, deny */ ++ if (req_pass[0] && !srif->protected_sess) ++ { ++ snprintf(logbuf, logbuf_size, "PASSWORD DENY (unprotected session): %s", req_name); ++ do_log(log_path, logbuf); ++ return 0; ++ } ++ ++ /* Password check: !pw must match SRIF PASSWORD (case-insensitive) */ ++ if (req_pass[0]) ++ { ++ char rp[64], sp[64]; ++ int i; ++ ++ safe_strncpy(rp, req_pass, (int)sizeof(rp)); ++ safe_strncpy(sp, srif->password, (int)sizeof(sp)); ++ ++ for (i = 0; rp[i]; i++) ++ rp[i] = (char)toupper((unsigned char)rp[i]); ++ ++ for (i = 0; sp[i]; i++) ++ sp[i] = (char)toupper((unsigned char)sp[i]); ++ ++ if (strcmp(rp, sp) != 0) ++ { ++ snprintf(logbuf, logbuf_size, "PASSWORD FAIL: %s", req_name); ++ do_log(log_path, logbuf); ++ ++ return 0; ++ } ++ } ++ ++ /* Update request (U flag): serve only if file is newer than TRANX */ ++ if (req_update && srif->tranx > 0) ++ { ++ long mtime = get_file_mtime(found_path); ++ ++ if (mtime <= srif->tranx) ++ { ++ snprintf(logbuf, logbuf_size, "NOT UPDATED: %s (mtime=%ld tranx=%ld)", req_name, mtime, srif->tranx); ++ do_log(log_path, logbuf); ++ return 0; ++ } ++ } ++ ++ /* Timestamp check: +ts means "only if file is newer than ts" */ ++ if (req_newer > 0) ++ { ++ long mtime = get_file_mtime(found_path); ++ ++ if (mtime <= req_newer) ++ { ++ snprintf(logbuf, logbuf_size, "NOT NEWER: %s (mtime=%ld req=%ld)", req_name, mtime, req_newer); ++ ++ do_log(log_path, logbuf); ++ return 0; ++ } ++ } ++ ++ snprintf(logbuf, logbuf_size, "FOUND: %s -> %s", req_name, found_path); ++ do_log(log_path, logbuf); ++ ++ if (rsp_f) ++ fprintf(rsp_f, "+%s\r\n", found_path); ++ ++ /* Update tracking after successful serve */ ++ if (g_conf.trackfile[0]) ++ { ++ fsize = get_file_size(found_path); ++ ++ if (fsize < 0) ++ fsize = 0; ++ ++ tracking_update(srif->aka, fsize); ++ } ++ ++ return 1; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ const char *srif_path; ++ SRIF srif; ++ FILE *req_f; ++ FILE *rsp_f; ++ char line[MAX_LINE]; ++ char req_name[MAX_LINE]; ++ char req_pass[64]; ++ long req_newer; ++ int req_update; ++ char found_path[MAXPATHLEN]; ++ char logbuf[MAXPATHLEN * 4 + 128]; ++ int found_count; ++ ++ config_init(); ++ ++ /* --conf */ ++ if (argc >= 4 && strcmp(argv[1], "--conf") == 0) ++ { ++ if (!load_config(argv[2])) ++ return 1; ++ ++ srif_path = argv[3]; ++ } ++ else ++ { ++ fprintf(stderr, "Usage:\n" ++ " srifreq --conf \n" ++ "\n" ++ "Config file keys: pubdir, logfile, aliases, private " ++ "\n"); ++ ++ return 1; ++ } ++ ++ if (!g_conf.pubdir[0]) ++ { ++ fprintf(stderr, "srifreq: pubdir not set\n"); ++ config_free(); ++ return 1; ++ } ++ ++ /* Load tracking data if configured */ ++ tracking_load(); ++ ++ load_aliases(g_conf.aliases[0] ? g_conf.aliases : NULL); ++ ++ snprintf(logbuf, sizeof(logbuf), "processing SRIF: %s", srif_path); ++ do_log(g_conf.logfile, logbuf); ++ ++ if (!parse_srif(srif_path, &srif)) ++ { ++ snprintf(logbuf, sizeof(logbuf), "ERROR: cannot parse SRIF or missing RequestList: %s", srif_path); ++ do_log(g_conf.logfile, logbuf); ++ fprintf(stderr, "srifreq: %s\n", logbuf); ++ config_free(); ++ return 1; ++ } ++ ++ /* SystemStatus: deny unlisted systems entirely */ ++ if (!srif.listed) ++ { ++ snprintf(logbuf, sizeof(logbuf), "DENIED: system is UNLISTED (aka: %s)", srif.aka); ++ do_log(g_conf.logfile, logbuf); ++ config_free(); ++ return 1; ++ } ++ ++ snprintf(logbuf, sizeof(logbuf), "sysop: %s aka: %s status: %s%s caller: %s req: %s", srif.sysop, srif.aka, srif.protected_sess ? "PROTECTED" : "UNPROTECTED", srif.tranx ? " (TRANX)" : "", srif.caller_id[0] ? srif.caller_id : "?", srif.request_list); ++ do_log(g_conf.logfile, logbuf); ++ ++ /* Log rate limiting status if active */ ++ if (g_conf.trackfile[0] && (g_conf.maxfiles > 0 || g_conf.maxbytes > 0)) ++ { ++ snprintf(logbuf, sizeof(logbuf), "rate limits: maxfiles=%d maxbytes=%ld window=%lds", g_conf.maxfiles, g_conf.maxbytes, g_conf.timewindow); ++ do_log(g_conf.logfile, logbuf); ++ } ++ ++ req_f = fopen(srif.request_list, "r"); ++ ++ if (!req_f) ++ { ++ snprintf(logbuf, sizeof(logbuf), "WARN: RequestList not available: %s", srif.request_list); ++ do_log(g_conf.logfile, logbuf); ++ config_free(); ++ return 0; ++ } ++ ++ rsp_f = NULL; ++ ++ if (srif.got_response_list && srif.response_list[0]) ++ { ++ rsp_f = fopen(srif.response_list, "w"); ++ ++ if (!rsp_f) ++ { ++ snprintf(logbuf, sizeof(logbuf), "WARN: cannot create ResponseList: %s", srif.response_list); ++ do_log(g_conf.logfile, logbuf); ++ } ++ } ++ ++ found_count = 0; ++ ++ /* Check and update track file */ ++ while (fgets(line, sizeof(line), req_f)) ++ { ++ char *p; ++ const char *alias_path; ++ ++ str_trim(line); ++ ++ if (!line[0] || line[0] == ';' || line[0] == '#') ++ continue; ++ ++ /* Parse: filename [!password] [+timestamp] [U] */ ++ req_name[0] = '\0'; ++ req_pass[0] = '\0'; ++ req_newer = 0; ++ req_update = 0; ++ ++ if (sscanf(line, "%1023s", req_name) < 1) ++ continue; ++ ++ /* Skip URLs */ ++ if (strncmp(req_name, "http", 4) == 0 || strncmp(req_name, "ftp", 3) == 0) ++ continue; ++ ++ /* Parse modifiers from the rest of the line */ ++ p = strstr(line, req_name); ++ ++ if (p) ++ p += strlen(req_name); ++ else ++ p = line + strlen(line); ++ ++ while (*p) ++ { ++ while (*p == ' ' || *p == '\t') ++ p++; ++ ++ if (*p == '!') ++ { ++ /* !password */ ++ int i = 0; ++ p++; ++ ++ while (*p && *p != ' ' && *p != '\t' && i < (int)sizeof(req_pass) - 1) ++ req_pass[i++] = *p++; ++ ++ req_pass[i] = '\0'; ++ } ++ else if (*p == '+') ++ { ++ /* +unix_timestamp */ ++ p++; ++ req_newer = atol(p); ++ ++ while (*p && *p != ' ' && *p != '\t') ++ p++; ++ } ++ else if (*p == 'U' && (p[1] == '\0' || p[1] == ' ' || p[1] == '\t')) ++ { ++ /* U = update request */ ++ req_update = 1; ++ p++; ++ } ++ else if (*p) ++ p++; /* Skip unknown token */ ++ } ++ ++ found_path[0] = '\0'; ++ ++ /* Check alias table first */ ++ alias_path = find_alias(req_name); ++ ++ if (alias_path) ++ { ++ if (is_abs_path(alias_path)) ++ safe_strncpy(found_path, alias_path, MAXPATHLEN); ++ else ++ path_join(found_path, MAXPATHLEN, g_conf.pubdir, alias_path); ++ ++ if (path_exists(found_path)) ++ found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++ else ++ { ++ snprintf(logbuf, sizeof(logbuf), "NOT FOUND (alias): %s -> %s", req_name, found_path); ++ do_log(g_conf.logfile, logbuf); ++ } ++ ++ continue; ++ } ++ ++ /* Wildcard: scan pubdir and all privdirs whose password matches */ ++ if (is_wildcard(req_name)) ++ { ++ DIR *dp; ++ struct dirent *de; ++ PrivDir *pd; ++ ++ /* Scan pubdir (no password needed) */ ++ dp = opendir(g_conf.pubdir); ++ if (dp) ++ { ++ while ((de = readdir(dp)) != NULL) ++ { ++ if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) ++ continue; ++ ++ if (!wildmatch(req_name, de->d_name)) ++ continue; ++ ++ path_join(found_path, MAXPATHLEN, g_conf.pubdir, de->d_name); ++ ++ if (path_exists(found_path)) ++ found_count += serve_one(de->d_name, found_path, "", req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++ } ++ ++ closedir(dp); ++ } ++ ++ /* Scan matching privdirs */ ++ for (pd = g_conf.privdirs; pd; pd = pd->next) ++ { ++ char rp[64], pp[64]; ++ int ci; ++ ++ safe_strncpy(rp, req_pass, (int)sizeof(rp)); ++ safe_strncpy(pp, pd->password, (int)sizeof(pp)); ++ ++ for (ci = 0; rp[ci]; ci++) ++ rp[ci] = (char)toupper((unsigned char)rp[ci]); ++ ++ for (ci = 0; pp[ci]; ci++) ++ pp[ci] = (char)toupper((unsigned char)pp[ci]); ++ ++ if (strcmp(rp, pp) != 0) ++ continue; ++ ++ dp = opendir(pd->path); ++ ++ if (!dp) ++ continue; ++ ++ while ((de = readdir(dp)) != NULL) ++ { ++ if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) ++ continue; ++ ++ if (!wildmatch(req_name, de->d_name)) ++ continue; ++ ++ path_join(found_path, MAXPATHLEN, pd->path, de->d_name); ++ ++ if (path_exists(found_path)) ++ found_count += serve_one(de->d_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++ } ++ ++ closedir(dp); ++ } ++ ++ continue; ++ } ++ ++ /* Plain filename: try pubdir first, then privdirs if password given */ ++ path_join(found_path, MAXPATHLEN, g_conf.pubdir, req_name); ++ ++ if (path_exists(found_path)) ++ { ++ found_count += ++ serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++ } ++ else if (req_pass[0]) ++ { ++ /* Try each private dir whose password matches */ ++ PrivDir *pd; ++ int served = 0; ++ ++ for (pd = g_conf.privdirs; pd && !served; pd = pd->next) ++ { ++ char rp[64], pp[64]; ++ int ci; ++ ++ safe_strncpy(rp, req_pass, (int)sizeof(rp)); ++ safe_strncpy(pp, pd->password, (int)sizeof(pp)); ++ ++ for (ci = 0; rp[ci]; ci++) ++ rp[ci] = (char)toupper((unsigned char)rp[ci]); ++ ++ for (ci = 0; pp[ci]; ci++) ++ pp[ci] = (char)toupper((unsigned char)pp[ci]); ++ ++ if (strcmp(rp, pp) != 0) ++ continue; ++ ++ path_join(found_path, MAXPATHLEN, pd->path, req_name); ++ ++ if (path_exists(found_path)) ++ { ++ found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); ++ served = 1; ++ } ++ } ++ if (!served) ++ { ++ snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s", req_name); ++ do_log(g_conf.logfile, logbuf); ++ } ++ } ++ else ++ { ++ snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s (pub: %s)", req_name, g_conf.pubdir); ++ do_log(g_conf.logfile, logbuf); ++ } ++ } ++ ++ fclose(req_f); ++ ++ if (rsp_f) ++ fclose(rsp_f); ++ ++ snprintf(logbuf, sizeof(logbuf), "done: %d file(s) found", found_count); ++ do_log(g_conf.logfile, logbuf); ++ ++ /* Save tracking data */ ++ tracking_save(); ++ ++ config_free(); ++ ++ return (found_count > 0) ? 0 : 1; ++} +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/srifreq.txt binkd/misc/srifreq.txt +--- binkd_pgul/misc/srifreq.txt 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/misc/srifreq.txt 2026-04-26 13:54:14.035795187 +0100 +@@ -0,0 +1,67 @@ ++srifreq -- SRIF-compatible file request server ++ ++USAGE: ++ srifreq --conf ++ ++DESCRIPTION: ++ SRIF (Standard Request Information Format) compatible file ++ request server for binkd. Processes incoming .req files and ++ serves files based on password protection and aliases. ++ ++CONFIGURATION FILE FORMAT: ++ # Lines starting with # are comments ++ # Blank lines are ignored ++ ++ pubdir # Public directory (no password required) ++ logfile # Log file, or - to disable ++ aliases # Magic-name aliases file (optional) ++ private # Private directory (requires !password) ++ ++ # Rate limiting options (optional): ++ trackfile # File to track node download statistics ++ maxfiles # Max files per node per time window (0=unlimited) ++ maxsize # Max bytes per node per time window (0=unlimited) ++ timewindow # Time window in seconds (0=no window) ++ ++ALIASES FILE FORMAT: ++ # Lines starting with # are comments ++ # Format: ++ # Names are matched case-insensitively ++ ++EXAMPLE CONFIG FILE (srifreq.conf): ++ # srifreq.conf - SRIF Request Server Configuration ++ ++ pubdir Work:Fido/Public ++ logfile Work:Logs/srifreq.log ++ aliases Work:Fido/srifreq.aliases ++ ++ # Private directories (password protected) ++ private Work:Fido/Private/Uploader1 secretpass1 ++ private Work:Fido/Private/Node190 node190pwd ++ ++ # Rate limiting: max 10 files or 50MB per node per 24 hours ++ trackfile Work:Logs/srifreq.track ++ maxfiles 10 ++ maxsize 52428800 ++ timewindow 86400 ++ ++EXAMPLE ALIASES FILE (srifreq.aliases): ++ # srifreq.aliases - Magic-name to file mappings ++ # Names are case-insensitive ++ ++ DOORWAY Games:Utils/Doorway/doorway.zip ++ NETMAIL Work:Comm/Fido/netmail.lha ++ README Docs:Readme.txt ++ 4DOUT AmiTCP:4DOut.lha ++ BINKD Apps:Comm/Binkd/binkd.lha ++ ++REQUEST FILE FORMAT (.req): ++ Files listed one per line. Modifiers: ++ !password - Required password for private areas ++ +timestamp - Only serve if file is newer than timestamp ++ U - Update request (only if newer than client's TRANX) ++ ++EXAMPLES: ++ srifreq --conf srifreq.conf inbound/srif_file.req ++ srifreq --conf srifreq.conf inbound/*.req ++ exec "work:fido/srifreq --conf work:fido/srifreq.conf *S" *.req *.REQ +\ No hay ningún carácter de nueva línea al final del archivo +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/amiga/Makefile binkd/mkfls/amiga/Makefile +--- binkd_pgul/mkfls/amiga/Makefile 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/mkfls/amiga/Makefile 2026-04-26 14:55:09.963221741 +0100 +@@ -26,4 +26,4 @@ + $(CC) -c $(CFLAGS) amiga/getfree.c + sem.o: + $(CC) -c $(CFLAGS) amiga/sem.c +-include Makefile.dep ++include Makefile.dep +\ No hay ningún carácter de nueva línea al final del archivo +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo binkd/mkfls/amiga/Makefile.analyze.bebbo +--- binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/mkfls/amiga/Makefile.analyze.bebbo 2026-04-25 16:52:14.088635220 +0100 +@@ -0,0 +1,96 @@ ++# Makefile.analyze.bebbo -- Static analysis for Amiga bebbo (GCC 6.5.0b) ++# Includes amiga/ code with bebbo-specific defines ++# Usage: make -f Makefile.analyze.bebbo ++ ++# All sources including Amiga-specific ++ALL_SRCS = \ ++ binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ ++ bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ ++ readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ ++ setpttl.c https.c md5b.c crypt.c compress.c \ ++ amiga/rename.c amiga/getfree.c amiga/bsdsock.c amiga/dirent.c \ ++ amiga/utime.c amiga/rfc2553_amiga.c amiga/sem.c amiga/evloop.c \ ++ amiga/sock.c amiga/session.c \ ++ misc/decompress.c misc/freq.c misc/process_tic.c misc/srifreq.c misc/nodelist.c ++ ++INCLUDES = -I. -Iamiga ++ ++# bebbo-specific defines (GCC 6.5.0b, HAS native snprintf) ++BEBBO_DEFINES = \ ++ -DAMIGA \ ++ -DHAVE_SOCKLEN_T \ ++ -DHAVE_INTMAX_T \ ++ -DHAVE_SNPRINTF \ ++ -DHAVE_GETOPT \ ++ -DHAVE_UNISTD_H \ ++ -DHAVE_SYS_TIME_H \ ++ -DHAVE_SYS_PARAM_H \ ++ -DHAVE_SYS_IOCTL_H \ ++ -DHAVE_NETINET_IN_H \ ++ -DHAVE_NETDB_H \ ++ -DHAVE_ARPA_INET_H \ ++ -DHAVE_STDARG_H \ ++ -DHAVE_VSNPRINTF \ ++ -DWITH_ZLIB ++ ++CPPCHECK_FLAGS = \ ++ --enable=all \ ++ --inconclusive \ ++ --std=c89 \ ++ --quiet \ ++ --suppress=missingIncludeSystem \ ++ --suppress=unusedFunction \ ++ --suppress=checkersReport \ ++ $(INCLUDES) ++ ++# Log files ++LOG_DIR = analysis_logs ++CPPCHECK_LOG = $(LOG_DIR)/cppcheck_bebbo.log ++CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_bebbo.log ++ ++.PHONY: all cppcheck clang-tidy analyze clean ++ ++all: analyze ++ ++cppcheck: ++ @mkdir -p $(LOG_DIR) ++ @echo "=== cppcheck Amiga bebbo (GCC 6.5.0b) ===" ++ @echo "Defines: bebbo, HAS native snprintf, no snprintf.c needed" ++ @echo "Saving output to: $(CPPCHECK_LOG)" ++ @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) $(ALL_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true ++ @echo "=== done ===" ++ @echo "Log saved: $(CPPCHECK_LOG)" ++ ++clang-tidy: ++ @mkdir -p $(LOG_DIR) ++ @echo "=== clang-tidy Amiga bebbo ===" ++ @echo "Note: Some Amiga headers may not resolve on Linux host" ++ @echo "Saving output to: $(CLANG_TIDY_LOG)" ++ @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) ++ @for src in binkd.c readcfg.c amiga/evloop.c amiga/session.c; do \ ++ echo "" >> $(CLANG_TIDY_LOG); \ ++ echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ ++ clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ ++ $(INCLUDES) $(BEBBO_DEFINES) -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ ++ done ++ @echo "=== done ===" ++ @echo "Log saved: $(CLANG_TIDY_LOG)" ++ ++analyze: cppcheck clang-tidy ++ ++# Focus on Amiga-specific code only ++amiga-only: ++ @echo "=== cppcheck Amiga-specific code only (bebbo) ===" ++ @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) \ ++ amiga/*.c 2>&1 || true ++ ++# Check what differs between ADE and bebbo ++diff-defines: ++ @echo "=== ADE vs bebbo define differences ===" ++ @echo "ADE only: -DHAVE_VSNPRINTF (no -DHAVE_SNPRINTF)" ++ @echo "bebbo: -DHAVE_SNPRINTF -DHAVE_VSNPRINTF" ++ @echo "" ++ @echo "ADE needs snprintf.c, bebbo does not" ++ ++clean: ++ @true +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/amiga/Makefile.bebbo binkd/mkfls/amiga/Makefile.bebbo +--- binkd_pgul/mkfls/amiga/Makefile.bebbo 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/mkfls/amiga/Makefile.bebbo 2026-04-26 13:11:27.902433254 +0100 +@@ -0,0 +1,208 @@ ++CC = m68k-amigaos-gcc ++ ++# Common flags for compiler and linker ++COMMON_CFLAGS = -Wall -Wextra -Wunused -Wunused-function -Wunused-variable -Wmissing-prototypes -Wno-pointer-sign -ffunction-sections -fdata-sections -noixemul ++ARCH_FLAGS = -O -m68000 -msoft-float -fomit-frame-pointer ++ ++CFLAGS = $(DEFINES) $(COMMON_CFLAGS) $(ARCH_FLAGS) ++LIBS = -lz -lm -lamiga ++LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_gcc.map -s ++ ++# Tools use same flags as main binary ++TOOL_CFLAGS = $(DEFINES) $(COMMON_CFLAGS) -O -m68000 -msoft-float -fomit-frame-pointer -I misc ++TOOL_LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_tools.map -s ++ ++OBJDIR = objs ++ ++DEFINES = \ ++ -DAMIGA \ ++ -DHAVE_SOCKLEN_T \ ++ -DHAVE_INTMAX_T \ ++ -DHAVE_SNPRINTF \ ++ -DHAVE_GETOPT \ ++ -DHAVE_UNISTD_H \ ++ -DHAVE_SYS_TIME_H \ ++ -DHAVE_SYS_PARAM_H \ ++ -DHAVE_SYS_IOCTL_H \ ++ -DHAVE_NETINET_IN_H \ ++ -DHAVE_NETDB_H \ ++ -DHAVE_ARPA_INET_H \ ++ -DHTTPS \ ++ -DAMIGADOS_4D_OUTBOUND \ ++ -DHAVE_STDARG_H \ ++ -DHAVE_VSNPRINTF \ ++ -DWITH_ZLIB \ ++ -DOS=\"Amiga\" \ ++ -I. \ ++ -Iamiga ++ ++SRCS = \ ++ binkd.c \ ++ readcfg.c \ ++ tools.c \ ++ ftnaddr.c \ ++ ftnq.c \ ++ client.c \ ++ server.c \ ++ protocol.c \ ++ bsy.c \ ++ inbound.c \ ++ breaksig.c \ ++ branch.c \ ++ amiga/rename.c \ ++ amiga/getfree.c \ ++ amiga/bsdsock.c \ ++ amiga/dirent.c \ ++ amiga/utime.c \ ++ amiga/rfc2553_amiga.c \ ++ amiga/sem.c \ ++ amiga/evloop.c \ ++ amiga/sock.c \ ++ amiga/session.c \ ++ bsycleanup.c \ ++ amiga/proto_amiga.c \ ++ ftndom.c \ ++ ftnnode.c \ ++ srif.c \ ++ pmatch.c \ ++ readflo.c \ ++ prothlp.c \ ++ iptools.c \ ++ run.c \ ++ binlog.c \ ++ exitproc.c \ ++ getw.c \ ++ xalloc.c \ ++ setpttl.c \ ++ https.c \ ++ md5b.c \ ++ crypt.c \ ++ compress.c ++ ++OBJS = \ ++ $(OBJDIR)/binkd.o \ ++ $(OBJDIR)/readcfg.o \ ++ $(OBJDIR)/tools.o \ ++ $(OBJDIR)/ftnaddr.o \ ++ $(OBJDIR)/ftnq.o \ ++ $(OBJDIR)/client.o \ ++ $(OBJDIR)/server.o \ ++ $(OBJDIR)/protocol.o \ ++ $(OBJDIR)/bsy.o \ ++ $(OBJDIR)/inbound.o \ ++ $(OBJDIR)/breaksig.o \ ++ $(OBJDIR)/branch.o \ ++ $(OBJDIR)/rename.o \ ++ $(OBJDIR)/getfree.o \ ++ $(OBJDIR)/bsdsock.o \ ++ $(OBJDIR)/dirent.o \ ++ $(OBJDIR)/utime.o \ ++ $(OBJDIR)/rfc2553_amiga.o \ ++ $(OBJDIR)/sem.o \ ++ $(OBJDIR)/evloop.o \ ++ $(OBJDIR)/sock.o \ ++ $(OBJDIR)/session.o \ ++ $(OBJDIR)/bsycleanup.o \ ++ $(OBJDIR)/proto_amiga.o \ ++ $(OBJDIR)/ftndom.o \ ++ $(OBJDIR)/ftnnode.o \ ++ $(OBJDIR)/srif.o \ ++ $(OBJDIR)/pmatch.o \ ++ $(OBJDIR)/readflo.o \ ++ $(OBJDIR)/prothlp.o \ ++ $(OBJDIR)/iptools.o \ ++ $(OBJDIR)/run.o \ ++ $(OBJDIR)/binlog.o \ ++ $(OBJDIR)/exitproc.o \ ++ $(OBJDIR)/getw.o \ ++ $(OBJDIR)/xalloc.o \ ++ $(OBJDIR)/setpttl.o \ ++ $(OBJDIR)/https.o \ ++ $(OBJDIR)/md5b.o \ ++ $(OBJDIR)/crypt.o \ ++ $(OBJDIR)/compress.o ++ ++all: binkd decompress process_tic freq srifreq nodelist ++ ++$(OBJDIR): ++ mkdir -p $(OBJDIR) ++ ++$(OBJDIR)/%.o: %.c ++ $(CC) -c $(CFLAGS) $< -o $@ ++ ++binkd: $(OBJDIR) $(OBJS) ++ $(CC) $(CFLAGS) -o binkd $(OBJS) $(LIBS) $(LDFLAGS) ++ ++# ---------- Utility tools (stand-alone, multi-platform) ---------- ++# TOOL_CFLAGS and TOOL_LDFLAGS defined above ++ ++decompress: ++ $(CC) $(TOOL_CFLAGS) -o decompress misc/decompress.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) ++ ++process_tic: ++ $(CC) $(TOOL_CFLAGS) -o process_tic misc/process_tic.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) ++ ++freq: ++ $(CC) $(TOOL_CFLAGS) -o freq misc/freq.c misc/portable.c $(TOOL_LDFLAGS) ++ ++srifreq: ++ $(CC) $(TOOL_CFLAGS) -o srifreq misc/srifreq.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) ++ ++nodelist: ++ $(CC) $(TOOL_CFLAGS) -o nodelist misc/nodelist.c misc/portable.c $(TOOL_LDFLAGS) ++ ++install: all clean ++ ++clean: ++ rm -f *.[bo] *.BAK *.core *.obj *.err *~ core ++ rm -rf $(OBJDIR) ++ rm -f binkd decompress process_tic freq srifreq nodelist ++ ++# ---------- Explicit rules for amiga/ objects ---------- ++$(OBJDIR)/rename.o: amiga/rename.c ++ $(CC) -c $(CFLAGS) amiga/rename.c -o $(OBJDIR)/rename.o ++ ++$(OBJDIR)/getfree.o: amiga/getfree.c ++ $(CC) -c $(CFLAGS) amiga/getfree.c -o $(OBJDIR)/getfree.o ++ ++$(OBJDIR)/sem.o: amiga/sem.c ++ $(CC) -c $(CFLAGS) amiga/sem.c -o $(OBJDIR)/sem.o ++ ++$(OBJDIR)/bsdsock.o: amiga/bsdsock.c ++ $(CC) -c $(CFLAGS) amiga/bsdsock.c -o $(OBJDIR)/bsdsock.o ++ ++$(OBJDIR)/dirent.o: amiga/dirent.c ++ $(CC) -c $(CFLAGS) amiga/dirent.c -o $(OBJDIR)/dirent.o ++ ++$(OBJDIR)/utime.o: amiga/utime.c ++ $(CC) -c $(CFLAGS) amiga/utime.c -o $(OBJDIR)/utime.o ++ ++$(OBJDIR)/rfc2553_amiga.o: amiga/rfc2553_amiga.c ++ $(CC) -c $(CFLAGS) amiga/rfc2553_amiga.c -o $(OBJDIR)/rfc2553_amiga.o ++ ++$(OBJDIR)/evloop.o: amiga/evloop.c ++ $(CC) -c $(CFLAGS) amiga/evloop.c -o $(OBJDIR)/evloop.o ++ ++$(OBJDIR)/sock.o: amiga/sock.c ++ $(CC) -c $(CFLAGS) amiga/sock.c -o $(OBJDIR)/sock.o ++ ++$(OBJDIR)/session.o: amiga/session.c ++ $(CC) -c $(CFLAGS) amiga/session.c -o $(OBJDIR)/session.o ++ ++$(OBJDIR)/bsycleanup.o: bsycleanup.c ++ $(CC) -c $(CFLAGS) bsycleanup.c -o $(OBJDIR)/bsycleanup.o ++ ++$(OBJDIR)/proto_amiga.o: amiga/proto_amiga.c ++ $(CC) -c $(CFLAGS) amiga/proto_amiga.c -o $(OBJDIR)/proto_amiga.o ++ ++depend Makefile.dep: Makefile ++ $(CC) -MM $(CFLAGS) $(SRCS) $(SYS) | \ ++ awk '{ if ($$1 != prev) { if (rec != "") print rec; \ ++ rec = $$0; prev = $$1; } \ ++ else { if (length(rec $$2) > 78) { print rec; rec = $$0; } \ ++ else rec = rec " " $$2 } } \ ++ END { print rec }' | tee Makefile.dep ++ ++-include Makefile.dep ++ ++.PHONY: all binkd decompress process_tic freq srifreq nodelist clean install +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/nt95-mingw/Makefile binkd/mkfls/nt95-mingw/Makefile +--- binkd_pgul/mkfls/nt95-mingw/Makefile 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/mkfls/nt95-mingw/Makefile 2026-04-26 13:12:24.499178632 +0100 +@@ -38,7 +38,8 @@ + setpttl.c https.c md5b.c crypt.c getopt.c nt/breaksig.c nt/getfree.c \ + nt/sem.c nt/TCPErr.c nt/WSock.c nt/w32tools.c nt/tray.c snprintf.c \ + ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c ntlm/des_enc.c \ +- ntlm/helpers.c ++ ntlm/helpers.c \ ++ bsycleanup.c + + RES= nt/binkdres.rc + +@@ -221,7 +222,32 @@ + OBJS=$(addprefix $(OBJDIR)/,$(patsubst %.c,%.o, $(SRCS))) + RESOBJS=$(addprefix $(OBJDIR)/, $(patsubst %.rc,%.o, $(RES))) + +-.PHONY: all printinfo install html clean distclean makedirs ++# ---------- Utility tools (stand-alone) ---------- ++TOOL_CFLAGS = -O2 -Wall -DWIN32 -I. -I misc ++ ++utils: decompress process_tic freq srifreq nodelist ++ ++decompress: ++ @echo Compiling decompress... ++ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/decompress.exe misc/decompress.c misc/portable.c ++ ++process_tic: ++ @echo Compiling process_tic... ++ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/process_tic.exe misc/process_tic.c misc/portable.c ++ ++freq: ++ @echo Compiling freq... ++ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/freq.exe misc/freq.c misc/portable.c ++ ++srifreq: ++ @echo Compiling srifreq... ++ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/srifreq.exe misc/srifreq.c misc/portable.c ++ ++nodelist: ++ @echo Compiling nodelist... ++ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/nodelist.exe misc/nodelist.c misc/portable.c ++ ++.PHONY: all printinfo install html clean distclean makedirs utils decompress process_tic freq srifreq nodelist + + all: printinfo makedirs $(OUTDIR)/$(BINKDEXE) + +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/nt95-msvc/Makefile binkd/mkfls/nt95-msvc/Makefile +--- binkd_pgul/mkfls/nt95-msvc/Makefile 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/mkfls/nt95-msvc/Makefile 2026-04-26 12:41:43.750120247 +0100 +@@ -327,7 +327,32 @@ + + BINKDEXE = $(BINKDNAME).exe + +-all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) ++all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) utils ++ ++TOOL_CFLAGS = -nologo -W3 -O2 -DWIN32 -DVISUALCPP -I. -I misc ++TOOL_CC = $(CC) $(TOOL_CFLAGS) ++ ++utils: "$(OUTDIR)\decompress.exe" "$(OUTDIR)\process_tic.exe" "$(OUTDIR)\freq.exe" "$(OUTDIR)\srifreq.exe" "$(OUTDIR)\nodelist.exe" ++ ++"$(OUTDIR)\decompress.exe": misc\decompress.c misc\portable.c nt\dirwin32.c ++ @echo Compiling decompress... ++ @$(TOOL_CC) -Fe"$(OUTDIR)\decompress.exe" misc\decompress.c misc\portable.c nt\dirwin32.c ++ ++"$(OUTDIR)\process_tic.exe": misc\process_tic.c misc\portable.c nt\dirwin32.c ++ @echo Compiling process_tic... ++ @$(TOOL_CC) -Fe"$(OUTDIR)\process_tic.exe" misc\process_tic.c misc\portable.c nt\dirwin32.c ++ ++"$(OUTDIR)\freq.exe": misc\freq.c misc\portable.c ++ @echo Compiling freq... ++ @$(TOOL_CC) -Fe"$(OUTDIR)\freq.exe" misc\freq.c misc\portable.c ++ ++"$(OUTDIR)\srifreq.exe": misc\srifreq.c misc\portable.c nt\dirwin32.c ++ @echo Compiling srifreq... ++ @$(TOOL_CC) -Fe"$(OUTDIR)\srifreq.exe" misc\srifreq.c misc\portable.c nt\dirwin32.c ++ ++"$(OUTDIR)\nodelist.exe": misc\nodelist.c misc\portable.c ++ @echo Compiling nodelist... ++ @$(TOOL_CC) -Fe"$(OUTDIR)\nodelist.exe" misc\nodelist.c misc\portable.c + + printinfo: + @echo on +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/os2-emx/Makefile binkd/mkfls/os2-emx/Makefile +--- binkd_pgul/mkfls/os2-emx/Makefile 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/mkfls/os2-emx/Makefile 2026-04-26 13:12:44.342586479 +0100 +@@ -13,7 +13,7 @@ + LFLAGS=-Los2 + LIBS=-lsocket -lresolv + NTLM_SRC=ntlm/des_enc.c ntlm/helpers.c ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c +-SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c ${NTLM_SRC} ++SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c bsycleanup.c ${NTLM_SRC} + TARGET=binkd2emx + + #PERLDIR=../perl5.00553/os2 +@@ -122,7 +122,33 @@ + + TARGET:=$(TARGET).exe + +-all: $(TARGET) ++all: $(TARGET) utils ++ ++TOOL_CFLAGS = $(CFLAGS) -I. -I misc ++ ++utils: decompress process_tic freq srifreq nodelist ++ ++decompress: misc/decompress.c ++ @echo Compiling decompress... ++ @$(CC) $(TOOL_CFLAGS) -o decompress.exe misc/decompress.c misc/portable.c os2/dirent.c ++ ++process_tic: misc/process_tic.c ++ @echo Compiling process_tic... ++ @$(CC) $(TOOL_CFLAGS) -o process_tic.exe misc/process_tic.c misc/portable.c os2/dirent.c ++ ++freq: misc/freq.c ++ @echo Compiling freq... ++ @$(CC) $(TOOL_CFLAGS) -o freq.exe misc/freq.c misc/portable.c ++ ++srifreq: misc/srifreq.c ++ @echo Compiling srifreq... ++ @$(CC) $(TOOL_CFLAGS) -o srifreq.exe misc/srifreq.c misc/portable.c os2/dirent.c ++ ++nodelist: misc/nodelist.c ++ @echo Compiling nodelist... ++ @$(CC) $(TOOL_CFLAGS) -o nodelist.exe misc/nodelist.c misc/portable.c ++ ++.PHONY: utils decompress process_tic freq srifreq nodelist + + .c.o: + @echo Compiling $*.c... +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/unix/Makefile.analyze.unix binkd/mkfls/unix/Makefile.analyze.unix +--- binkd_pgul/mkfls/unix/Makefile.analyze.unix 1970-01-01 00:00:00.000000000 +0000 ++++ binkd/mkfls/unix/Makefile.analyze.unix 2026-04-25 16:52:02.225860998 +0100 +@@ -0,0 +1,65 @@ ++# Makefile.analyze.unix -- Static analysis for Unix/Linux code only ++# Excludes Amiga-specific code (amiga/ directory) ++# Usage: make -f Makefile.analyze.unix ++ ++# Unix-portable sources only (no amiga/ directory) ++UNIX_SRCS = \ ++ binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ ++ bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ ++ readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ ++ setpttl.c https.c md5b.c crypt.c compress.c ++ ++# Unix-specific files (if any) ++UNIX_SYS_SRCS = \ ++ unix/getwd.c unix/lock.c unix/tcperr.c ++ ++INCLUDES = -I. -Iunix ++ ++CPPCHECK_FLAGS = \ ++ --enable=all \ ++ --inconclusive \ ++ --std=c89 \ ++ --quiet \ ++ --suppress=missingIncludeSystem \ ++ --suppress=unusedFunction \ ++ $(INCLUDES) ++ ++# Log files ++LOG_DIR = analysis_logs ++CPPCHECK_LOG = $(LOG_DIR)/cppcheck_unix.log ++CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_unix.log ++ ++.PHONY: all cppcheck clang-tidy analyze clean ++ ++all: analyze ++ ++cppcheck: ++ @mkdir -p $(LOG_DIR) ++ @echo "=== cppcheck Unix/Linux code ===" ++ @echo "Saving output to: $(CPPCHECK_LOG)" ++ @cppcheck $(CPPCHECK_FLAGS) $(UNIX_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true ++ @echo "=== done ===" ++ @echo "Log saved: $(CPPCHECK_LOG)" ++ ++clang-tidy: ++ @mkdir -p $(LOG_DIR) ++ @echo "=== clang-tidy Unix/Linux code ===" ++ @echo "Saving output to: $(CLANG_TIDY_LOG)" ++ @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) ++ @for src in $(UNIX_SRCS); do \ ++ echo "" >> $(CLANG_TIDY_LOG); \ ++ echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ ++ clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ ++ $(INCLUDES) -DUNIX -DHAVE_SNPRINTF -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ ++ done ++ @echo "=== done ===" ++ @echo "Log saved: $(CLANG_TIDY_LOG)" ++ ++analyze: cppcheck clang-tidy ++ ++quick: ++ @echo "=== Quick error check (Unix) ===" ++ @cppcheck --enable=error --std=c89 --quiet $(INCLUDES) $(UNIX_SRCS) 2>&1 || true ++ ++clean: ++ @true +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/unix/Makefile.in binkd/mkfls/unix/Makefile.in +--- binkd_pgul/mkfls/unix/Makefile.in 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/mkfls/unix/Makefile.in 2026-04-26 13:11:58.084940863 +0100 +@@ -12,17 +12,17 @@ + MANDIR=$(DATADIR)/man + DOCDIR=$(DATADIR)/doc/$(APPL) + +-SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c @OPT_SRC@ ++SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c bsycleanup.c @OPT_SRC@ + OBJS=${SRCS:.c=.o} + AUTODEFS=@DEFS@ + AUTOLIBS=@LIBS@ +-DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" ++DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -DPROTOTYPES + CPPFLAGS=@CPPFLAGS@ + CFLAGS=@CFLAGS@ + LDFLAGS=@LDFLAGS@ + LIBS=$(AUTOLIBS) + +-all: compile banner ++all: compile banner utils + + compile: $(APPL) + +@@ -48,6 +48,32 @@ + @echo " run \`configure --prefix=/another/path' and go to step 1. " + @echo + ++utils: decompress process_tic freq srifreq nodelist ++ ++TOOL_CFLAGS = $(DEFINES) $(CPPFLAGS) $(CFLAGS) -I. -I misc ++ ++decompress: misc/decompress.c misc/portable.c misc/portable.h ++ @echo Compiling decompress... ++ @$(CC) $(TOOL_CFLAGS) -o $@ misc/decompress.c misc/portable.c ++ ++process_tic: misc/process_tic.c misc/portable.c misc/portable.h ++ @echo Compiling process_tic... ++ @$(CC) $(TOOL_CFLAGS) -o $@ misc/process_tic.c misc/portable.c ++ ++freq: misc/freq.c misc/portable.c misc/portable.h ++ @echo Compiling freq... ++ @$(CC) $(TOOL_CFLAGS) -o $@ misc/freq.c misc/portable.c ++ ++srifreq: misc/srifreq.c misc/portable.c misc/portable.h ++ @echo Compiling srifreq... ++ @$(CC) $(TOOL_CFLAGS) -o $@ misc/srifreq.c misc/portable.c ++ ++nodelist: misc/nodelist.c misc/portable.c ++ @echo Compiling nodelist... ++ @$(CC) $(TOOL_CFLAGS) -o $@ misc/nodelist.c misc/portable.c ++ ++.PHONY: decompress process_tic freq srifreq nodelist ++ + .version: $(APPL) + @./$(APPL) -v | $(AWK) '{ print $$2; }' > $@ + +@@ -66,6 +92,7 @@ + clean: + rm -f *.[bo] unix/*.[bo] ntlm/*.[bo] *.BAK *.core *.obj *.err + rm -f *~ core config.cache config.log config.status ++ rm -f decompress process_tic freq srifreq nodelist + + cleanall: clean + rm -f $(APPL) Makefile Makefile.dep Makefile.in +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/protocol.c binkd/protocol.c +--- binkd_pgul/protocol.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/protocol.c 2026-04-26 09:45:52.230049023 +0100 +@@ -45,12 +45,19 @@ + #include "md5b.h" + #include "crypt.h" + #include "compress.h" ++#ifdef AMIGA ++#include "amiga/proto_amiga.h" ++#endif + + #ifdef WITH_PERL + #include "perlhooks.h" + #endif + #include "rfc2553.h" + ++#if defined(HAVE_THREADS) || defined(AMIGA) ++extern MUTEXSEM lsem; ++#endif ++ + /* define to enable val's code for -ip checks (default is gul's code) */ + #undef VAL_STYLE + #ifdef VAL_STYLE +@@ -63,7 +70,7 @@ + /* + * Fills <> with initial values, allocates buffers, etc. + */ +-static int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) ++int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) + { + char val[4]; + socklen_t lval; +@@ -126,6 +133,12 @@ + #endif + setsockopts (state->s_in = socket_in); + setsockopts (state->s_out = socket_out); ++ ++#if defined(AMIGA) ++ setsockopts_amiga(socket_in, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); ++ setsockopts_amiga(socket_out, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); ++#endif ++ + TF_ZERO (&state->in); + TF_ZERO (&state->out); + TF_ZERO (&state->flo); +@@ -181,7 +194,7 @@ + /* + * Clears protocol buffers and queues, closes files, etc. + */ +-static int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) ++int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) + { + int i; + +@@ -225,7 +238,7 @@ + } + + /* Process rcvdlist */ +-static FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) ++FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) + { + int i; + +@@ -315,7 +328,7 @@ + /* + * Sends next msg from the msg queue or next data block + */ +-static int send_block (STATE *state, BINKD_CONFIG *config) ++int send_block (STATE *state, BINKD_CONFIG *config) + { + int i, n, save_errno; + const char *save_err; +@@ -2091,7 +2104,7 @@ + return 0; + } + +-static int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) ++int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) + { + char buf[MAXPATHLEN+1]; + FILE *f; +@@ -2145,7 +2158,8 @@ + + *extra = ""; + if (state->z_cansend && state->extcmd && state->out.size >= config->zminsize +- && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first)) { ++ && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first) ++ && !(state->to && state->to->NC_flag)) { + #ifdef WITH_BZLIB2 + if (!state->z_send && (state->z_cansend & 2)) { + *extra = " BZ2"; state->z_send = 2; +@@ -2448,6 +2462,7 @@ + { + char szAddr[FTN_ADDR_SZ + 1]; + ++ memset(szAddr, 0, sizeof(szAddr)); + ftnaddress_to_str (szAddr, &state->sent_fls[n].fa); + state->bytes_sent += state->sent_fls[n].size; + ++state->files_sent; +@@ -2538,7 +2553,7 @@ + }; + + /* Recvs next block, processes msgs or writes down the data from the remote */ +-static int recv_block (STATE *state, BINKD_CONFIG *config) ++int recv_block (STATE *state, BINKD_CONFIG *config) + { + int no; + +@@ -2770,7 +2785,7 @@ + return 1; + } + +-static int banner (STATE *state, BINKD_CONFIG *config) ++int banner (STATE *state, BINKD_CONFIG *config) + { + int tz; + char szLocalTime[60]; +@@ -2850,7 +2865,7 @@ + return 1; + } + +-static int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) ++int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) + { + struct stat sb; + FILE *f = NULL; +@@ -3031,7 +3046,7 @@ + return 1; + } + +-static void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) ++void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) + { + char szFTNAddr[FTN_ADDR_SZ + 1]; + +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/readcfg.c binkd/readcfg.c +--- binkd_pgul/readcfg.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/readcfg.c 2026-04-26 15:04:45.397917107 +0100 +@@ -44,6 +44,10 @@ + */ + BINKD_CONFIG *current_config; + ++#ifdef AMIGA ++extern struct SignalSemaphore config_sem; ++#endif ++ + /* + * Temporary static structure for configuration reading + */ +@@ -210,9 +214,15 @@ + snprintf(c->iport, sizeof(c->iport), "%s", find_port("")); + snprintf(c->oport, sizeof(c->oport), "%s", find_port("")); + c->call_delay = 60; ++ c->no_call_delay = 0; + c->rescan_delay = 60; + c->nettimeout = DEF_TIMEOUT; + c->oblksize = DEF_BLKSIZE; ++ ++#ifdef AMIGA ++ c->tcp_nodelay = 0; ++#endif ++ + #if defined(WITH_ZLIB) || defined(WITH_BZLIB2) + c->zminsize = 1024; + c->zlevel = 0; +@@ -391,8 +401,16 @@ + {"oport", read_port, &work_config.oport, 0, 0}, + {"rescan-delay", read_time, &work_config.rescan_delay, 1, DONT_CHECK}, + {"call-delay", read_time, &work_config.call_delay, 1, DONT_CHECK}, ++ {"no-call-delay", read_bool, &work_config.no_call_delay, 0, 0}, + {"timeout", read_time, &work_config.nettimeout, 1, DONT_CHECK}, + {"oblksize", read_int, &work_config.oblksize, MIN_BLKSIZE, MAX_BLKSIZE}, ++ ++#ifdef AMIGA ++ {"tcp-nodelay", read_bool, &work_config.tcp_nodelay, 0, 0}, ++ {"so-sndbuf", read_int, &work_config.so_sndbuf, 0, 65535}, ++ {"so-rcvbuf", read_int, &work_config.so_rcvbuf, 0, 65535}, ++#endif ++ + {"maxservers", read_int, &work_config.max_servers, 0, DONT_CHECK}, + {"maxclients", read_int, &work_config.max_clients, 0, DONT_CHECK}, + {"inbound", read_string, work_config.inbound, 'd', 0}, +@@ -666,7 +684,7 @@ + exp_ftnaddress (&fa, work_config.pAddr, work_config.nAddr, work_config.pDomains.first); + pn = add_node (&fa, NULL, password, pkt_pwd, out_pwd, '-', NULL, NULL, + NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, +- HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, ++ HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, + #ifdef BW_LIM + BW_DEF, BW_DEF, + #endif +@@ -830,11 +848,10 @@ + if (!new_config) + { + /* Config error. Abort or continue? */ +- if (current_config) +- { +- Log(1, "error in configuration, using old config"); +- unlock_config_structure(&work_config, 0); +- } ++ unlock_config_structure(&work_config, 0); ++ ++ if (current_config) ++ Log(1, "error in configuration, using old config"); + } + + return new_config; +@@ -857,6 +874,16 @@ + Log (2, "got SIGHUP"); + need_reload = got_sighup; + got_sighup = 0; ++#elif defined(AMIGA) ++ /* No SIGHUP on AmigaOS: detect config change by mtime */ ++ need_reload = 0; ++ if (current_config->config_list.first) ++ { ++ if (stat(current_config->config_list.first->path, &sb) == 0 && ++ current_config->config_list.first->mtime != 0 && ++ sb.st_mtime != current_config->config_list.first->mtime) ++ need_reload = 1; ++ } + #else + need_reload = 0; + #endif +@@ -886,8 +913,75 @@ + } + #endif + +- if (!need_reload) +- return 0; ++ if (!need_reload) ++ return 0; ++ ++#ifdef AMIGA ++ /* Prevent reload storms and partial-file reads. ++ * ++ * On AmigaOS (and some Unix editors), config files are written in multiple ++ * passes, so binkd may see the mtime change while the file is still being ++ * written. Attempting to parse an incomplete file gives "unknown keyword" ++ * errors, and rapid repeated mtime changes cause bind() to fail because the ++ * previous listen socket has not been released yet. ++ * ++ * Strategy: after first detecting a change, wait until the mtime has been ++ * stable for at least 2 seconds before actually reloading. Also enforce a ++ * minimum of 5 seconds between successive successful reloads. ++ */ ++ { ++ static time_t last_reload = 0; /* time of last successful reload */ ++ static time_t change_seen = 0; /* time we first noticed the change */ ++ static time_t stable_mtime = 0; /* mtime we are waiting to stabilize */ ++ static int reload_pending = 0; /* persists between calls */ ++ time_t now = time(NULL); ++ ++ /* The loop has already updated pc->mtime, so in the next call ++ * need_reload will be 0 even though we haven't reloaded yet. ++ * reload_pending keeps the reload intent alive. ++ */ ++ if (need_reload) reload_pending = 1; ++ ++ if (!reload_pending) ++ return 0; ++ ++ /* Get the mtime of the primary config file */ ++ { struct stat sb2; ++ time_t cur_mtime = 0; ++ ++ if (current_config->config_list.first && stat(current_config->config_list.first->path, &sb2) == 0) ++ cur_mtime = sb2.st_mtime; ++ ++ /* mtime just changed (or changed again) — reset the stability clock */ ++ if (cur_mtime != stable_mtime) ++ { ++ stable_mtime = cur_mtime; ++ change_seen = now; ++ Log(5, "checkcfg: config mtime changed, waiting for stability..."); ++ return 0; ++ } ++ ++ /* mtime has been stable since change_seen */ ++ if (now - change_seen < 2) ++ { ++ Log(5, "checkcfg: config not yet stable (%lds), waiting...", ++ (long)(now - change_seen)); ++ return 0; ++ } ++ } ++ ++ /* Enforce minimum gap between reloads to let the OS release sockets */ ++ if (now - last_reload < 5) ++ { ++ Log(5, "checkcfg: reload suppressed (too soon, %lds)", (long)(now - last_reload)); ++ return 0; ++ } ++ ++ last_reload = now; ++ reload_pending = 0; /* Once the intent has been consumed, the reload is executed */ ++ } ++#endif ++ + /* Reload starting from first file in list */ + Log(2, "Reloading configuration..."); + pc = current_config->config_list.first; +@@ -1125,7 +1219,7 @@ + char *w[ARGNUM], *tmp, *pkt_pwd, *out_pwd, *pipe; + int i, j; + int NR_flag = NR_USE_OLD, ND_flag = ND_USE_OLD, HC_flag = HC_USE_OLD, +- MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, restrictIP = RIP_USE_OLD, ++ MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, NC_flag = NC_USE_OLD, restrictIP = RIP_USE_OLD, + IP_afamily = AF_USE_OLD; + #ifdef BW_LIM + long bw_send = BW_DEF, bw_recv = BW_DEF; +@@ -1175,6 +1269,8 @@ + HC_flag = HC_OFF; + else if (STRICMP (tmp, "-noproxy") == 0) + NP_flag = NP_ON; ++ else if (STRICMP (tmp, "-nc") == 0) ++ NC_flag = NC_ON; + #ifdef BW_LIM + else if (STRICMP (tmp, "-bw") == 0) + { +@@ -1258,7 +1354,7 @@ + + split_passwords(w[2], &pkt_pwd, &out_pwd); + pn = add_node (&fa, w[1], w[2], pkt_pwd, out_pwd, (char)(w[3] ? w[3][0] : '-'), w[4], w[5], +- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, ++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, + IP_afamily, + #ifdef BW_LIM + bw_send, bw_recv, +@@ -1991,6 +2087,9 @@ + if (fn->pkt_pwd) pwd_len += strlen(fn->pkt_pwd)+1; else pwd_len += 2; + if (fn->out_pwd) pwd_len += strlen(fn->out_pwd)+1; else pwd_len += 2; + pwd = calloc (1, pwd_len+1); ++ /* Guard against null pointer dereference if calloc fails */ ++ if (!pwd) ++ return 0; + strcpy(pwd, fn->pwd); + if (fn->pkt_pwd != (char*)&(fn->pwd) || fn->out_pwd != (char*)&(fn->pwd)) { + strcat(strcat(pwd, ","), (fn->pkt_pwd) ? fn->pkt_pwd : "-"); +@@ -2324,4 +2423,3 @@ + return 1; + } + #endif +- +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/readcfg.h binkd/readcfg.h +--- binkd_pgul/readcfg.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/readcfg.h 2026-04-25 20:39:08.569961699 +0100 +@@ -111,8 +111,16 @@ + #endif + int nettimeout; + int connect_timeout; +- int rescan_delay; ++ ++#ifdef AMIGA ++ int tcp_nodelay; ++ int so_sndbuf; ++ int so_rcvbuf; ++#endif ++ + int call_delay; ++ int no_call_delay; ++ int rescan_delay; + int max_servers; + int max_clients; + int kill_dup_partial_files; +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/readdir.h binkd/readdir.h +--- binkd_pgul/readdir.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/readdir.h 2026-04-22 20:37:22.121954324 +0100 +@@ -23,6 +23,8 @@ + #elif defined(__MINGW32__) + #include + #include ++#elif defined(AMIGA) ++#include "amiga/dirent.h" + #else + #include + #include +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/README.md binkd/README.md +--- binkd_pgul/README.md 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/README.md 2026-04-22 18:44:10.839451402 +0100 +@@ -6,6 +6,79 @@ + + ## Compiling + ++AmigaOS: ++ ++Copy "mkfls/amiga/Makefile" to root and make ++ ++Need ADE to compile project with ixemul library. ++ ++https://aminet.net/package/dev/gcc/ADE ++ ++It no longer requires ADE to be installed for execution, as it doesn't need /bin/sh to run scripts or external programs from binkd.conf. However, it does require ixemul.library and ixnet.library, either in the libs directory or in the same directory as the executable. ++ ++You can find the ADE ixemul.library and ixnet.library libraries in the aminet package or in the released versions, along with the program and utilities already compiled. ++ ++https://github.com/skbn/binkd/releases ++ ++5.Work:fido> version work:fido/ixnet.library ++ixnet.library 63.1 ++ ++5.Work:fido> version work:fido/ixemul.library ++ixemul.library 63.1 ++ ++ ++I've attached six programs for your assistance: ++ ++[decompress] which decompresses incoming files in lha or zip format, if necessary. ++ ++``` ++[freq] which generates file requests in ASO mode and places the necessary files in outbound directory. ++ ++Usage:freq Z:N/NODE[.POINT] ++``` ++``` ++[freq_bso] same but BSO style ++Usage: freq_bso Z:N/NODE[.POINT] ++ No point : .0ZZ/NNNNNNNN.req ++ Point : .0ZZ/N ++``` ++ ++``` ++[process_tic] which processes tic files and places them in the filebox folder. ++With --copypublic option, copy the file you receive to a directory named pub/ from PROGDIR ++>> exec "path/process_tic --copypublic" *.tic *.TIC ++>> exec "path/process_tic" *.tic *.TIC ++``` ++ ++``` ++[srifreq] copy of "misc/srifreq" but in c. SRIF-compatible file-request server for binkd ++ ++Usage: srifreq [] ++ SRIF control file passed by binkd (exec "srifreq *S") ++ directory containing public downloadable files ++ log file path (pass "" or - to disable logging) ++ optional file mapping magic names to real paths ++ ++Aliases file format (one alias per line, # = comment): ++MAGIC_NAME relative/or/absolute/path/to/file ++Example: ++NODELIST pub/NODELIST.ZIP ++FILES pub/ALLFILES.TXT ++ ++binkd.conf example: exec "srifreq *S pub log/srifreq.log aliases.txt" *.req ++``` ++ ++``` ++[nodelist] FidoNet nodelist compiler for binkd - AmigaOS version in c from "misc/nodelist.pl" ++ ++Usage: nodelist [] ++``` ++ ++Also compileable on *nix >> "gcc -O2 -Wall -o nodelist nodelist.c" ++ ++BUGFIXES: ++Option "-C" to reload config It remains unstable ++ + non-UNIX: + + 1. Find in mkfls/ a subdirectory for your system/compiler, copy all files +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/rfc2553.h binkd/rfc2553.h +--- binkd_pgul/rfc2553.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/rfc2553.h 2026-04-26 10:31:43.475331092 +0100 +@@ -21,8 +21,52 @@ + + #include "iphdr.h" + ++/* Amiga: define EAI_* before including netdb.h to prevent libnix redefinition */ ++#if defined(AMIGA) ++#include ++#include ++#include ++#include ++ ++ #undef EAI_NONAME ++ #undef EAI_AGAIN ++ #undef EAI_FAIL ++ #undef EAI_NODATA ++ #undef EAI_FAMILY ++ #undef EAI_SOCKTYPE ++ #undef EAI_SERVICE ++ #undef EAI_ADDRFAMILY ++ #undef EAI_MEMORY ++ #undef EAI_SYSTEM ++ #undef EAI_UNKNOWN ++ #define EAI_NONAME -1 ++ #define EAI_AGAIN -2 ++ #define EAI_FAIL -3 ++ #define EAI_NODATA -4 ++ #define EAI_FAMILY -5 ++ #define EAI_SOCKTYPE -6 ++ #define EAI_SERVICE -7 ++ #define EAI_ADDRFAMILY -8 ++ #define EAI_MEMORY -9 ++ #define EAI_SYSTEM -10 ++ #define EAI_UNKNOWN -11 ++ ++#include ++ ++/* EAI_ADDRFAMILY is BSD/macOS specific; Linux/glibc does not define it ++ * Map it to EAI_FAMILY which has the same meaning on those platforms */ ++#ifndef EAI_ADDRFAMILY ++#ifdef EAI_FAMILY ++#define EAI_ADDRFAMILY EAI_FAMILY ++#else ++#define EAI_ADDRFAMILY -9 ++#endif ++#endif ++ ++#endif ++ + /* Autosense getaddrinfo */ +-#if defined(AI_PASSIVE) && defined(EAI_NONAME) ++#if defined(AI_PASSIVE) && defined(EAI_NONAME) && !defined(AMIGA) + #define HAVE_GETADDRINFO + #endif + +@@ -47,6 +91,18 @@ + }; + #define addrinfo addrinfo_emu + ++#ifdef AMIGA ++#ifdef getaddrinfo ++#undef getaddrinfo ++#endif ++#ifdef freeaddrinfo ++#undef freeaddrinfo ++#endif ++#ifdef gai_strerror ++#undef gai_strerror ++#endif ++#endif ++ + int getaddrinfo(const char *nodename, const char *servname, + const struct addrinfo *hints, + struct addrinfo **res); +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/run.c binkd/run.c +--- binkd_pgul/run.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/run.c 2026-04-26 10:31:17.108612481 +0100 +@@ -18,6 +18,11 @@ + #ifdef HAVE_UNISTD_H + #include + #endif ++#ifdef AMIGA ++#include ++#include ++#include ++#endif + + #include "sys.h" + #include "run.h" +@@ -40,10 +45,128 @@ + #define SHELL (getenv("COMSPEC") ? getenv("COMSPEC") : "command.com") + #define SHELL_META "\"\'\\%<>|&^@" + #define SHELLOPT "/c" ++#elif defined(AMIGA) ++/* AmigaOS shell */ ++#define SHELL "c:execute" ++#define SHELL_META "\"\'\\*?(){};&|<>" + #else + #error "Unknown platform" + #endif + ++#ifdef AMIGA ++/* run(): execute an AmigaDOS command via SystemTagList() with NIL: I/O ++ * Runs synchronously so binkd waits for completion (needed for srifreq ++ * to create .rsp before parse_response). NIL: I/O prevents CLI freezing ++ * Error output goes to a temporary file for logging on failure */ ++int run(char *cmd) ++{ ++ /* All declarations at the top for C89/ADE GCC 2.95 compatibility */ ++ BPTR nil_in; ++ char errfile[MAXPATHLEN]; ++ BPTR err_out = 0; ++ int rc = 0; ++ char cmd_copy[MAXPATHLEN]; ++ char *cmd_start; ++ char *cmd_end; ++ BPTR lock; ++ struct TagItem exec_tags[5]; ++ BPTR errfile_ptr; ++ char buf[512]; ++ int len; ++ ++ /* Open NIL: for input/output */ ++ nil_in = Open("NIL:", MODE_OLDFILE); ++ ++ /* Create temporary error file in current directory */ ++ snprintf(errfile, sizeof(errfile), "binkd_err_%ld.txt", (long)time(NULL)); ++ err_out = Open(errfile, MODE_NEWFILE); ++ if (err_out == 0) ++ { ++ Log(2, "cannot create error file %s, using NIL: for error output", errfile); ++ } ++ ++ /* Extract the command (first word) to check if it exists */ ++ strncpy(cmd_copy, cmd, sizeof(cmd_copy) - 1); ++ cmd_copy[sizeof(cmd_copy) - 1] = '\0'; ++ cmd_start = cmd_copy; /* Work on copy, never modify original cmd */ ++ ++ /* Skip leading whitespace */ ++ while (*cmd_start && (*cmd_start == ' ' || *cmd_start == '\t')) ++ cmd_start++; ++ ++ /* Find end of command (first space or end) */ ++ cmd_end = cmd_start; ++ while (*cmd_end && *cmd_end != ' ' && *cmd_end != '\t') ++ cmd_end++; ++ *cmd_end = '\0'; ++ ++ /* Check if command exists */ ++ lock = Lock((STRPTR)cmd_start, SHARED_LOCK); ++ if (lock == 0) ++ { ++ Log(2, "command not found, skipping: '%s'", cmd_start); ++ Close(nil_in); ++ if (err_out) ++ Close(err_out); ++ DeleteFile((STRPTR)errfile); ++ return 0; ++ } ++ UnLock(lock); ++ ++ Log(3, "executing '%s'", cmd); ++ ++ /* Set up tags with NIL: input and error file output. ++ * Use NP_* (New Process) tags instead of SYS_* to avoid sharing ++ * file handles with parent process - prevents stderr from being ++ * closed when child exits. */ ++ exec_tags[0].ti_Tag = NP_Input; ++ exec_tags[0].ti_Data = (ULONG)nil_in; ++ exec_tags[1].ti_Tag = NP_Output; ++ exec_tags[1].ti_Data = (ULONG)nil_in; ++ exec_tags[2].ti_Tag = NP_Error; ++ exec_tags[2].ti_Data = (ULONG)err_out; ++ exec_tags[3].ti_Tag = NP_Synchronous; ++ exec_tags[3].ti_Data = TRUE; ++ exec_tags[4].ti_Tag = TAG_DONE; ++ exec_tags[4].ti_Data = 0; ++ ++ rc = SystemTagList((STRPTR)cmd, exec_tags); ++ ++ /* Close handles */ ++ Close(nil_in); ++ if (err_out) ++ Close(err_out); ++ ++ /* Log error output if command failed */ ++ if (rc != 0) ++ { ++ errfile_ptr = Open(errfile, MODE_OLDFILE); ++ if (errfile_ptr) ++ { ++ Log(2, "command failed with rc=%d, output:", rc); ++ while ((len = Read(errfile_ptr, buf, sizeof(buf) - 1)) > 0) ++ { ++ buf[len] = '\0'; ++ Log(2, "%s", buf); ++ } ++ Close(errfile_ptr); ++ } ++ } ++ ++ DeleteFile((STRPTR)errfile); ++ return rc; ++} ++ ++/* run3(): pipe/tunnel not supported on AmigaOS without ixemul. */ ++int run3(const char *cmd, int *in, int *out, int *err) ++{ ++ (void)cmd; (void)in; (void)out; (void)err; ++ Log(1, "run3: pipe connections not supported on Amiga"); ++ return -1; ++} ++#endif /* AMIGA */ ++ ++#ifndef AMIGA + int run (char *cmd) + { + int rc=-1; +@@ -111,6 +234,7 @@ + #endif + return rc; + } ++#endif /* !AMIGA */ + + #ifdef __MINGW32__ + static int set_cloexec(int fd) +@@ -136,6 +260,7 @@ + } + #endif + ++#ifndef AMIGA + int run3 (const char *cmd, int *in, int *out, int *err) + { + int pid; +@@ -162,6 +287,14 @@ + } + + #ifdef HAVE_FORK ++#ifdef AMIGA ++ /* Pipe tunneling not supported on AmigaOS without fork() */ ++ Log(1, "run3: pipe/tunnel not supported on Amiga: %s", cmd); ++ if (in) close(pin[1]), close(pin[0]); ++ if (out) close(pout[1]), close(pout[0]); ++ if (err) close(perr[1]), close(perr[0]); ++ return -1; ++#else + pid = fork(); + if (pid == -1) + { +@@ -194,7 +327,11 @@ + if (strpbrk(cmd, SHELL_META)) + { + shell = SHELL; ++#ifdef AMIGA ++ execl(shell, shell, cmd, (char *)NULL); ++#else + execl(shell, shell, SHELLOPT, cmd, (char *)NULL); ++#endif + } + else + { +@@ -232,6 +369,7 @@ + *err = perr[0]; + close(perr[1]); + } ++#endif /* !AMIGA */ + #else + + /* redirect stdin/stdout/stderr takes effect for all threads */ +@@ -336,3 +474,4 @@ + return pid; + } + ++#endif /* !AMIGA */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/sem.h binkd/sem.h +--- binkd_pgul/sem.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/sem.h 2026-04-26 10:09:54.148733026 +0100 +@@ -34,6 +34,14 @@ + #include + typedef struct SignalSemaphore MUTEXSEM; + ++#ifdef AMIGA ++typedef struct ++{ ++ struct Task *waiter; ++ ULONG sigbit; ++} EVENTSEM; ++#endif ++ + #elif defined(WITH_PTHREADS) + + #include +@@ -73,25 +81,27 @@ + * Initialise Event Semaphores. + */ + +-int _InitEventSem (void *); ++#ifdef AMIGA ++int _InitEventSem (EVENTSEM *); + + /* + * Post Semaphore. + */ + +-int _PostSem (void *); ++int _PostSem (EVENTSEM *); + + /* + * Wait Semaphore. + */ + +-int _WaitSem (void *, int); ++int _WaitSem (EVENTSEM *, int); + + /* + * Clean Event Semaphores. + */ + +-int _CleanEventSem (void *); ++int _CleanEventSem (EVENTSEM *); ++#endif + + #if defined(WITH_PTHREADS) + #define InitSem(sem) pthread_mutex_init(sem, NULL) +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/server.c binkd/server.c +--- binkd_pgul/server.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/server.c 2026-04-26 10:30:03.538324924 +0100 +@@ -23,6 +23,10 @@ + #include + #endif + ++#ifdef AMIGA ++#include "amiga/bsdsock.h" ++#endif ++ + #include "sys.h" + #include "iphdr.h" + #include "readcfg.h" +@@ -39,6 +43,10 @@ + #endif + #include "rfc2553.h" + ++#if defined(HAVE_THREADS) || defined(AMIGA) ++extern EVENTSEM eothread; ++#endif ++ + int n_servers = 0; + int ext_rand = 0; + +@@ -53,7 +61,8 @@ + void *cperl; + #endif + +-#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(DEBUGCHILD) ++/* Prevent shared socket closure */ ++#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(AMIGA) && !defined(DEBUGCHILD) + int curfd; + pidcmgr = 0; + for (curfd=0; curfdai_addr, ai->ai_addrlen) != 0) + { +- Log(1, "servmgr bind(): %s", TCPERR ()); +- soclose(sockfd[sockfd_used]); +- return -1; ++#ifdef AMIGA ++ /* bsdsocket may hold the port briefly after socket close. Retry */ ++ int bind_retries = 6; ++ ++ while (bind(sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) ++ { ++ if (--bind_retries == 0) ++ { ++ Log(1, "servmgr bind(): %s", TCPERR()); ++ soclose(sockfd[sockfd_used]); ++ return -1; ++ } ++ ++ Log(2, "servmgr bind(): %s, retry in 2s...", TCPERR()); ++ sleep(2); ++ } ++#else ++ if (bind (sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) ++ { ++ Log(1, "servmgr bind(): %s", TCPERR ()); ++ soclose(sockfd[sockfd_used]); ++ return -1; ++ } ++#endif + } + if (listen (sockfd[sockfd_used], 5) != 0) + { +@@ -168,6 +201,12 @@ + + setproctitle ("server manager (listen %s)", config->listen.first->port); + ++ /* Save rescan_delay locally. checkcfg() may free 'config' (old config ++ * is released when usageCount reaches 0 after reload), so we must not ++ * access config->rescan_delay inside the loop after a reload */ ++ { ++ int rescan = config->rescan_delay; ++ + for (;;) + { + struct timeval tv; +@@ -183,7 +222,7 @@ + maxfd = sockfd[curfd]; + } + tv.tv_usec = 0; +- tv.tv_sec = CHECKCFG_INTERVAL; ++ tv.tv_sec = rescan; + unblocksig(); + check_child(&n_servers); + n = select(maxfd+1, &r, NULL, NULL, &tv); +@@ -192,8 +231,20 @@ + { case 0: /* timeout */ + if (checkcfg()) + { ++ /* config may have been freed by checkcfg() — read rescan from ++ * the new current_config before returning for restart */ ++ { ++ BINKD_CONFIG *nc = lock_current_config(); ++ if (nc) ++ { ++ rescan = nc->rescan_delay; ++ unlock_config_structure(nc, 0); ++ } ++ } ++ + for (curfd=0; curfdrescan_delay; ++ unlock_config_structure(nc, 0); ++ } ++ } ++ + for (curfd=0; curfd ++#endif ++ + /* + * Listens... Than calls protocol() + */ +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/sys.h binkd/sys.h +--- binkd_pgul/sys.h 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/sys.h 2026-04-26 08:48:04.281489117 +0100 +@@ -22,8 +22,28 @@ + #ifdef HAVE_STDINT_H + #include + #endif ++#ifdef AMIGA ++ /* Include Amiga exec proto for Delay() function */ ++ #include ++#endif + #ifdef HAVE_UNISTD_H + #include ++ /* Undefine conflicting unistd.h macros for AMIGA */ ++ #ifdef AMIGA ++ #ifdef getpid ++ #undef getpid ++ #endif ++ ++ #ifdef sleep ++ #undef sleep ++ #endif ++ ++ /* Redefine with our Amiga implementations */ ++ #define getpid() ((int)(ULONG)FindTask(NULL)) ++ ++ #define sleep(s) Delay((ULONG)((s) * 50)) ++ ++ #endif /* AMIGA */ + #endif + #ifdef HAVE_IO_H + #include +@@ -105,6 +125,11 @@ + #define PID() mypid + #endif + ++#ifdef HAVE_FORK ++ #include /* Needed for SIG_BLOCK/SIG_UNBLOCK and WIFEXITED/WEXITSTATUS */ ++ #include ++#endif ++ + #if defined(HAVE_FORK) && defined(HAVE_SIGPROCMASK) && defined(HAVE_WAITPID) && defined(SIG_BLOCK) + void switchsignal(int how); + #define blocksig() switchsignal(SIG_BLOCK) +@@ -292,8 +317,16 @@ + + #ifndef PRIdMAX + #define PRIdMAX "ld" ++#endif ++ ++#ifndef PRIuMAX ++#ifdef AMIGA ++/* On AmigaOS m68k, uintmax_t is long long unsigned int */ ++#define PRIuMAX "llu" ++#else + #define PRIuMAX "lu" + #endif ++#endif + + #ifndef HAVE_STRTOUMAX + #define strtoumax(ptr, endptr, base) strtoul(ptr, endptr, base) +diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/tools.c binkd/tools.c +--- binkd_pgul/tools.c 2026-04-16 17:49:00.000000000 +0100 ++++ binkd/tools.c 2026-04-25 23:49:26.267090428 +0100 +@@ -22,6 +22,10 @@ + #include + #endif + ++#ifdef AMIGA ++#include "amiga/bsdsock.h" ++#endif ++ + #include "sys.h" + #include "readcfg.h" + #include "common.h" +@@ -38,6 +42,12 @@ + #include "nt/w32tools.h" + #endif + ++#if defined(HAVE_THREADS) || defined(AMIGA) ++extern MUTEXSEM lsem; ++#endif ++ ++extern void vLog (int lev, char *s, va_list ap); ++ + /* + * We can call Log() even when we have no config ready. So, we must keep + * internal variables which will be updated when config is loaded +@@ -290,6 +300,10 @@ + char buf[1024]; + int ok = 1; + ++#ifdef AMIGA ++ static int need_newline = 0; ++#endif ++ + /* make string in buffer */ + vsnprintf(buf, sizeof(buf), s, ap); + /* do perl hooks */ +@@ -313,8 +327,20 @@ + if (lev <= current_conlog && !inetd_flag) + { + LockSem(&lsem); ++#ifdef AMIGA ++ /* AmigaOS: go to new line for status messages to avoid overwriting */ ++ if (lev < 0 && need_newline) ++ { ++ need_newline = 0; ++ } ++ fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, ++ tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : "\r"); ++ if (lev >= 0) ++ need_newline = 1; ++#else + fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, + tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : ""); ++#endif + fflush (stderr); + ReleaseSem(&lsem); + if (lev < 0) diff --git a/changes.txt b/changes.txt new file mode 100644 index 00000000..2ceb66b0 --- /dev/null +++ b/changes.txt @@ -0,0 +1,334 @@ +=========================================================================== +MULTI-PLATFORM CHANGES (all OS) +=========================================================================== + +--- Bug fixes --- + +binkd.c: +- Fixed null pointer dereference: added null check after strdup() on SSH_CONNECTION +- Fixed potential use-after-free: mypid declaration moved to shared scope (HAVE_FORK || AMIGA) +- Added config file validation: reject filenames that look like FTN addresses + +readcfg.c: +- Fixed null pointer dereference: added null check after calloc() in print_node_info_1() +- Fixed use-after-free: checkcfg() error path restructured (unlock before log) +- Fixed config reload storm: mtime stability check + 5s minimum between reloads (prevents partial-file parse and bind() failures) + +inbound.c: +- Fixed double PATH_SEPARATOR: strnzcat(PATH_SEPARATOR) now checks if path already ends with separator (5 locations) + +server.c: +- Fixed use-after-free: config->rescan_delay saved to local variable before main loop, re-read from new config after checkcfg() reload +- Fixed ENOTSOCK/EOPNOTSUPP recovery: extended OS/2-only ENOTSOCK handler to also cover EOPNOTSUPP + +ftnq.c: +- Fixed try file deletion: only delete if file exists (prevents error on missing .try files) + +--- New features --- + +NC_flag (no-compression per node): + btypes.h - Added NC_flag field to FTN_NODE struct + ftnnode.h - Added NC_ON/NC_OFF/NC_USE_OLD defines, NC_flag parameter to add_node() + ftnnode.c - NC_flag propagation in add_node(), add_node_nolock(), copy_node() + readcfg.c - NC_flag parsing in node configuration keyword + protocol.c - Skip zlib/bzip2 compression when NC_flag is set on the node + +no-call-delay config keyword: + readcfg.h - Added no_call_delay field to BINKD_CONFIG + readcfg.c - Added "no-call-delay" config keyword (read_bool) + client.c - Skip SLEEP(call_delay) when no_call_delay is set + +ftnnode.c: +- NC_flag propagation in add_node(), add_node_nolock(), copy_node() + +ftnnode.h: +- Added NC_ON/NC_OFF/NC_USE_OLD defines, NC_flag parameter to add_node() + +--- New files --- + +bsycleanup.c: +- BSY/CSY/TRY file cleanup at startup, scans domain outbounds + +bsycleanup.h: +- API: cleanup_old_bsy() + +--- Multi-platform refactoring --- + +protocol.c: +- Changed static functions to extern: init_protocol(), deinit_protocol(), process_rcvdlist(), send_block(), recv_block(), banner(), start_file_transfer(), log_end_of_session(), ND_set_status() + +binlog.c: +- Added extern blsem declaration under (HAVE_THREADS || AMIGA) + +client.c: +- Changed call0() from static to extern (required for Amiga evloop direct calls) + +server.c: +- Added extern eothread declaration under (HAVE_THREADS || AMIGA) + +--- Misc tools (new) --- + +misc/decompress.c: +- Decompress FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA) +- Uses MAXPATHLEN from portable.h instead of local #define + +misc/freq.c: +- Create .req/.clo files (ASO flat layout, BSO BinkleyStyle layout) +- Uses MAXPATHLEN from portable.h +- Removed local str_tolower() implementation (now in portable.c) + +misc/nodelist.c: +- Compile FidoNet nodelist to binkd.conf node lines (extracts IBN/INA flags) +- Uses MAX_LINE from portable.h +- Uses MAXPATHLEN from portable.h +- Removed local str_trim() implementation + +misc/process_tic.c: +- Process .tic files to filebox (supports config file and legacy args) +- Uses MAXPATHLEN from portable.h +- Uses MAX_LINE from portable.h +- Removed local implementations: trim_nl, skip_ws, ensure_dir, copy_file, move_file, get_file_size + +misc/srifreq.c: +- Reimplementation in C of pgul's misc/srifreq shell script (password protection, aliases, wildcards, rate limiting) +- Uses MAXPATHLEN from portable.h +- Uses MAX_LINE from portable.h +- Converted MAX_ALIASES to dynamic array (realloc) +- Removed local implementations: wildmatch, is_wildcard, get_file_mtime, str_trim, str_upper +- Added example aliases file format in header comment +- Fixed: sizeof(logbuf) on pointer parameter gave wrong size (4/8 bytes instead of buffer size) +- Fixed: memory leak -- g_aliases array was never freed +- Fixed: memory leak -- g_conf.tracking linked list was never freed +- Fixed: sscanf format widths did not match buffer sizes in load_config(), parse_srif(), load_aliases() +- Fixed: tracking_load() did not reject future/corrupt timestamps +- Fixed: request line parser offset was wrong when line had leading whitespace +- Fixed: get_file_size() returning -1 was added to tracking bytes as negative +- Fixed: parse_srif() TIME keyword without value changed time_limit from -1 to 0 + +misc/portable.c: +- Shared implementations: string utilities, file operations, path utilities + +misc/portable.h: +- Portable declarations and platform-specific includes (POSIX, Win32, OS/2, DOS, AmigaOS) +- Fixed: duplicate declaration of safe_strncpy removed + +misc/decompress.txt: +- Usage documentation for decompress utility + +misc/freq.txt: +- Usage documentation for freq utility + +misc/nodelist.txt: +- Usage documentation for nodelist utility + +misc/process_tic.txt: +- Usage documentation and config file example for process_tic + +misc/srifreq.txt: +- Usage documentation, config file and aliases file examples for srifreq + +--- Makefile updates (all platforms) --- + +All Makefiles updated to: +- Include bsycleanup.c in main binary +- Add portable.c to misc tool compilations +- Add -I misc to TOOL_CFLAGS for portable.h includes + +mkfls/unix/Makefile.in: +- Added bsycleanup.c to SRCS +- Added portable.c to misc tool builds +- Added -I misc to TOOL_CFLAGS + +mkfls/nt95-mingw/Makefile: +- Added bsycleanup.c to SRCS +- Added portable.c to misc tool builds +- Added -I misc to TOOL_CFLAGS + +mkfls/nt95-msvc/Makefile: +- Added portable.c to misc tool builds +- Added -I misc to TOOL_CFLAGS + +mkfls/os2-emx/Makefile: +- Added bsycleanup.c to SRCS +- Added portable.c to misc tool builds +- Added -I misc to TOOL_CFLAGS + +mkfls/amiga/Makefile.bebbo: +- New: Complete rewrite for bebbo gcc cross-compiler (no ixemul fork, libnix, Roadshow TCP/IP) +- Added bsycleanup.c, portable.c, all amiga/ sources +- Added misc tool compilation rules + -I misc +- Added AMIGADOS_4D_OUTBOUND define + +mkfls/amiga/Makefile.analyze.bebbo: +- New: Static analysis Makefile for bebbo gcc + +mkfls/unix/Makefile.analyze.unix: +- New: Static analysis Makefile for Unix/GCC + +--- Documentation --- + +README.md: +- Added AmigaOS compilation instructions (ADE, ixemul/ixnet library versions) +- Added usage documentation for all misc tools (decompress, freq, process_tic, srifreq, nodelist) +- Added note about config reload instability + +=========================================================================== +AMIGA-SPECIFIC CHANGES (#ifdef AMIGA / AMIGADOS_4D_OUTBOUND) +=========================================================================== + +--- New Amiga-specific files (.c) --- + +amiga/evloop.c: +- Non-blocking event loop with WaitSelect(), replaces servmgr()/clientmgr() + +amiga/session.c: +- Session allocation, accept(), non-blocking connect(), outbound scan + +amiga/sock.c: +- Listen socket management (open_listen_sockets, close_listen_sockets) + +amiga/utime.c: +- utime() stub for AmigaDOS (converts Unix epoch to AmigaDOS epoch) + +amiga/rfc2553_amiga.c: +- getaddrinfo/getnameinfo fallback for Roadshow TCP/IP + +amiga/bsdsock.c: +- BSD socket layer for Roadshow TCP/IP (SocketBase initialization) + +amiga/dirent.c: +- POSIX dirent emulation for AmigaDOS + +amiga/proto_amiga.c: +- Non-blocking BinkP protocol (amiga_proto_open, amiga_proto_step, amiga_proto_close) + +--- New Amiga-specific files (.h) --- + +amiga/evloop.h: +- Event loop API (amiga_evloop_run) + +amiga/evloop_int.h: +- Internal session types (sess_t, sess_phase_t, session table) + +amiga/bsdsock.h: +- BSD socket API (SocketBase, TCPERR macros) + +amiga/compat_netinet_in.h: +- Compatibility header for netinet/in.h + +amiga/dirent.h: +- POSIX dirent API (DIR, dirent, opendir, readdir) + +amiga/proto_amiga.h: +- Non-blocking protocol API (APROTO_RUNNING, APROTO_DONE_OK, APROTO_DONE_ERR) + +--- Modified Amiga-specific files (existed in pgul, rewritten/extended) --- + +amiga/rename.c: +- Complete rewrite: added duplicate name handling with .001/.002 suffixes, directory scanning for next free name, path splitting, buffer overflow protection + +amiga/sem.c: +- Complete rewrite: replaced ixemul inline/strsup.h with direct Exec calls, added EVENTSEM implementation (AllocSignal/FreeSignal/Signal/Wait), added _InitEventSem/_PostSem/_WaitSem/_CleanEventSem + +--- Modified .c files (Amiga-specific changes, all under #ifdef AMIGA) --- + +binkd.c: +- Added #include "bsycleanup.h" (unconditional) +- Added cleanup_old_bsy() call at startup (unconditional) +- Added #if defined(HAVE_THREADS) || defined(AMIGA) semaphore declarations (hostsem, resolvsem, lsem, blsem, varsem, config_sem, eothread, wakecmgr) +- Added #ifdef AMIGA block calling amiga_evloop_run() instead of servmgr()/clientmgr() +- Added #elif defined(AMIGA) for NIL: null device (instead of /dev/null or nul) +- Added #ifndef AMIGA guards around fork-only code paths (-i inetd, -a remote addr options) + +readcfg.c: +- Added #ifdef AMIGA extern for config_sem (SignalSemaphore) +- Added #ifdef AMIGA tcp_nodelay default initialization +- Added #ifdef AMIGA config keywords: tcp-nodelay, so-sndbuf, so-rcvbuf + +protocol.c: +- Added #ifdef AMIGA include for amiga/proto_amiga.h +- Added #if defined(HAVE_THREADS) || defined(AMIGA) for extern lsem +- Added #ifdef AMIGA setsockopts_amiga() calls for tcp-nodelay/so-sndbuf/so-rcvbuf + +run.c: +- Added #ifdef AMIGA includes (dos/dos.h, dos/dostags.h, proto/dos.h) +- Added #elif defined(AMIGA) SHELL/SHELL_META defines ("c:execute") +- Added #ifdef AMIGA run() implementation via SystemTagList() with NIL: I/O (synchronous execution, error output to temp file, command existence check) +- Added #ifdef AMIGA in run3(): pipe/tunnel not supported, returns -1 with log message +- Added #ifdef AMIGA in execl(): no SHELLOPT parameter (AmigaDOS shell syntax) + +tools.c: +- Added #ifdef AMIGA include for amiga/bsdsock.h +- Added #if defined(HAVE_THREADS) || defined(AMIGA) for extern lsem +- Added #ifdef AMIGA console logging rewrite in vLog(): need_newline tracking, \r carriage return for status line overwriting + +branch.c: +- Removed ix_vfork() implementation under #ifdef AMIGA (code was in pgul but not needed for Amiga evloop) + +breaksig.c: +- Added #ifdef AMIGA signal handlers: SIGINT/SIGTERM via signal() (replaces SIGBREAK/SIGHUP used on other platforms) + +exitproc.c: +- Added #if defined(HAVE_THREADS) || defined(AMIGA) extern semaphore declarations (hostsem, resolvsem, lsem, blsem, varsem, config_sem, eothread, wakecmgr) +- Added #ifdef AMIGA cleanup block in exitfunc(): close_srvmgr_socket(), CleanEventSem/CleanSem for all semaphores, sock_deinit(), nodes_deinit(), bsy_remove_all(), pid_file removal + +https.c: +- Added #ifdef AMIGA: inet_ntoa() uses .sin_addr.s_addr (Roadshow struct in_addr is u_long, not struct) + +iptools.c: +- Added #ifdef AMIGA include for netinet/tcp.h +- Added #ifdef AMIGA ioctl() size parameter (Amiga ioctl doesn't take size parameter) + +server.c: +- Added #ifdef AMIGA include for amiga/bsdsock.h +- Added #ifdef AMIGA: force PF_INET (no IPv6 on AmigaOS 3) +- Added #ifdef AMIGA: bind() retry loop (6 retries, 2s delay) for socket release timing +- Added #ifdef AMIGA: select() ENOTSOCK/EBADF recovery (restart listen sockets) +- Added #ifndef AMIGA guard around HAVE_FORK child process handling + +client.c: +- Added #ifdef AMIGA include for amiga/bsdsock.h + +--- Modified .h files (Amiga-specific changes, all under #ifdef AMIGA) --- + +Config.h: +- Added AMIGA to platform check: #if defined(HAVE_FORK)+defined(HAVE_THREADS)+defined(DOS)+defined(AMIGA)==0 + +btypes.h: +- Added #ifdef AMIGA uintmax_t typedef for long long unsigned on m68k + +client.h: +- Added #ifdef AMIGA includes (netinet/in.h, netinet/tcp.h, sys/socket.h) +- Added #ifdef AMIGA call0() declaration for direct evloop outbound calls + +iphdr.h: +- Added #ifdef AMIGA: include amiga/bsdsock.h, define sock_init()/sock_deinit()/soclose() macros + +iptools.h: +- Added #ifdef AMIGA includes (sys/socket.h, netinet/in.h, netdb.h) +- Added #ifdef AMIGA setsockopts_amiga() declaration + +readcfg.h: +- Added #ifdef AMIGA fields: tcp_nodelay, so_sndbuf, so_rcvbuf + +readdir.h: +- Added #elif defined(AMIGA) include for amiga/dirent.h + +rfc2553.h: +- Added #ifdef AMIGA: EAI_* constant redefinitions (prevent libnix conflicts) +- Added #ifdef AMIGA: include chain (sys/types, sys/socket, netinet/in, arpa/inet, netdb.h) +- Added #ifdef AMIGA: undef/redefine getaddrinfo/freeaddrinfo/gai_strerror macros +- Added #ifndef EAI_ADDRFAMILY portability define (maps to EAI_FAMILY) + +sem.h: +- Added #ifdef AMIGA EVENTSEM typedef (struct with Task* waiter + sigbit) +- Added #ifdef AMIGA typed prototypes for _InitEventSem, _PostSem, _WaitSem, _CleanEventSem + +server.h: +- Added #ifdef AMIGA include for netinet/in.h + +sys.h: +- Added #ifdef AMIGA include for proto/exec.h +- Added #ifdef AMIGA: undef getpid/sleep, redefine as FindTask(NULL)/Delay() +- Added #ifdef AMIGA PRIuMAX define as "llu" (uintmax_t is long long unsigned on m68k) +- Added #ifdef HAVE_FORK includes for signal.h and sys/wait.h diff --git a/client.c b/client.c old mode 100644 new mode 100755 index 81dfefeb..94cea2bf --- a/client.c +++ b/client.c @@ -19,6 +19,10 @@ #include #endif +#ifdef AMIGA +#include "amiga/bsdsock.h" +#endif + #include "sys.h" #include "readcfg.h" #include "client.h" @@ -44,6 +48,11 @@ #include "rfc2553.h" #include "srv_gai.h" +#if defined(HAVE_THREADS) || defined(AMIGA) +extern MUTEXSEM lsem; +extern EVENTSEM eothread; +#endif + static void call (void *arg); int n_clients = 0; @@ -202,7 +211,8 @@ static int do_client(BINKD_CONFIG *config) /* This sleep can be interrupted by signal, it's OK */ unblocksig(); check_child(&n_clients); - SLEEP (config->call_delay); + if (!config->no_call_delay) + SLEEP (config->call_delay); check_child(&n_clients); blocksig(); } @@ -282,8 +292,16 @@ void clientmgr (void *arg) #ifdef HAVE_THREADS !server_flag && #endif + /* AmigaOS uses shared-memory evloop — only main process calls checkcfg() + * On fork systems (Linux/FreeBSD) each process has separate memory + * so independent reloads are safe. */ !poll_flag) +#ifndef AMIGA checkcfg(); +#else + { + } +#endif } Log (5, "downing clientmgr..."); @@ -306,7 +324,7 @@ void clientmgr (void *arg) exit (0); } -static int call0 (FTN_NODE *node, BINKD_CONFIG *config) +int call0 (FTN_NODE *node, BINKD_CONFIG *config) { int sockfd = INVALID_SOCKET; int sock_out; diff --git a/client.h b/client.h old mode 100644 new mode 100755 index 12d88935..2c7c3fc0 --- a/client.h +++ b/client.h @@ -1,9 +1,20 @@ #ifndef _client_h #define _client_h +#ifdef AMIGA +#include +#include +#include +#endif + /* * Scans queue, makes outbound ``call'', than calls protocol() */ void clientmgr(void *arg); +#ifdef AMIGA +/* Direct outbound call for evloop.c (no-ixemul, no-threads build) */ +int call0(FTN_NODE *node, BINKD_CONFIG *config); +#endif + #endif diff --git a/doc/decompress.txt b/doc/decompress.txt new file mode 100644 index 00000000..b23cb691 --- /dev/null +++ b/doc/decompress.txt @@ -0,0 +1,36 @@ +decompress -- Decompress FTN bundle archives + +USAGE: + decompress + +DESCRIPTION: + Scans the inbound directory for FTN day-of-week bundle archives and + decompresses them to the output directory. + + Recognized archive formats (by magic bytes, not extension): + - ZIP files + - LZH files + - ARC files + + Recognized bundle extensions (case-insensitive): + - .SU - Sunday bundle + - .MO - Monday bundle + - .TU - Tuesday bundle + - .WE - Wednesday bundle + - .TH - Thursday bundle + - .FR - Friday bundle + - .SA - Saturday bundle + + Also handles renamed bundles (duplicates): + - ABCD1234.SU + - ABCD1234.SU.001 + - ABCD1234.SU.002 + +EXAMPLES: + decompress Work:Inbound Work:Unpacked + decompress /var/spool/binkd/inbound /tmp/unpacked + decompress C:\Binkd\Inbound C:\Binkd\Unpacked + exec "work:fido/decompress work:fido/inbound work:fido/inbound" *.su? *.mo? *.tu? *.we? *.th? *.fr? *.sa? *.SU? *.MO? *.TU? *.WE? *.TH? *.FR? *.SA? + +CONFIGURATION FILE: + None. All parameters are command-line arguments. diff --git a/doc/freq_new.txt b/doc/freq_new.txt new file mode 100644 index 00000000..6a73467c --- /dev/null +++ b/doc/freq_new.txt @@ -0,0 +1,37 @@ +freq -- Create .req and .clo request files for FidoNet + +USAGE: + freq [options] [...] + +DESCRIPTION: + Creates file request (.req) and close (.clo) files in the outbound + directory using FidoNet 4D/5D addressing. + + Address format: + Zone:Net/Node - 4D address (e.g., 39:190/101) + Zone:Net/Node.Point - 5D address with point (e.g., 39:190/101.1) + + Multiple files can be specified to create multiple request lines. + +OPTIONS: + --aso Amiga Style Outbound (default) - flat layout + Format: /Z.N.NODE.POINT.req + + --bso Binkley Style Outbound - hex directory layout + No point: .0ZZ/nnnnnnnn.req + Point: .0ZZ/nnnnnnnn.pnt/pppppppp.req + + --password Append !pw suffix to each request line + --newer-than Append + (request only if file is newer) + --update Append U flag (update request, checks TRANX) + +EXAMPLES: + freq Work:Outbound 39:190/101 file.lha + freq --aso Work:Outbound 39:190/101.1 readme.txt + freq --bso /var/spool/binkd/outbound 2:123/456 door.zip + freq --bso C:\Binkd\Outbound 1:100/200 update.lzh + freq --password secret --newer-than 1234567890 Work:Outbound 39:190/101 file.zip + freq --update Work:Outbound 39:190/101 file1.zip file2.zip file3.zip + +CONFIGURATION FILE: + None. All parameters are command-line arguments. diff --git a/doc/nodelist.txt b/doc/nodelist.txt new file mode 100644 index 00000000..6fbe44ae --- /dev/null +++ b/doc/nodelist.txt @@ -0,0 +1,32 @@ +nodelist -- Compile FidoNet nodelist to binkd.conf format + +USAGE: + nodelist [] + +DESCRIPTION: + Reads a FidoNet nodelist and outputs binkd.conf compatible + "node" configuration lines. + + Arguments: + nodelist_file Path to the FidoNet nodelist file + domain Domain name for the node entries (e.g., fidonet) + output_file Optional output file (default: stdout) + + Extracts the following flags: + IBN[:port] - BinkP protocol flag (Internet BinkP Node) + INA:hostname - IP hostname/address + + Output format: + node
@ : - + + The nodelist format is comma-separated: + [type,]node_num,name,city,sysop,phone,baud,flag1,flag2,... + type = Zone, Region, Host, Hub, Pvt, Hold, Down, Boss (empty = Node) + +EXAMPLES: + nodelist Work:Fido/nodelist.123 fidonet > binkd-nodes.conf + nodelist /etc/fido/nodelist.456 fidonet >> binkd.conf + nodelist C:\Fido\NODELIST.001 fidonet C:\Fido\nodes.conf + +CONFIGURATION FILE: + None. All parameters are command-line arguments. diff --git a/doc/process_tic.txt b/doc/process_tic.txt new file mode 100644 index 00000000..e7d9c6b7 --- /dev/null +++ b/doc/process_tic.txt @@ -0,0 +1,45 @@ +process_tic -- Process .tic file announcements + +USAGE: + process_tic --conf [files.tic...] + +DESCRIPTION: + Processes .tic (Ticker announcement) files from the inbound directory + and moves/copies files to their destination filebox or public directory. + + The .tic file is parsed for File, Area, Origin, and From fields. + The actual file is moved from inbound to filebox/AreaName/. + + All settings are read from the configuration file. + +OPTIONS: + --conf Configuration file (required) + +CONFIGURATION FILE FORMAT: + # Lines starting with # are comments + # Blank lines are ignored + + inbound Inbound directory (required) + filebox Filebox destination (required) + pubdir Public directory for --copy-public + logfile Log file path + ticlog TIC processing log + filelist File list output + newfiles New files list output + +EXAMPLE CONFIG FILE (process_tic.conf): + # process_tic.conf - Configuration for TIC processor + + inbound Work:Inbound + filebox Work:Filebox + pubdir Work:Public + logfile Work:Logs/process_tic.log + ticlog Work:Logs/tic.log + filelist Work:Filebox/filelist.txt + newfiles Work:Filebox/newfiles.txt + +EXAMPLES: + process_tic --conf process_tic.conf + process_tic --conf process_tic.conf inbound/*.tic + + exec "process_tic --conf work:fido/process_tic.conf" *.tic *.TIC \ No newline at end of file diff --git a/doc/srifreq_new.txt b/doc/srifreq_new.txt new file mode 100644 index 00000000..fb53e368 --- /dev/null +++ b/doc/srifreq_new.txt @@ -0,0 +1,67 @@ +srifreq -- SRIF-compatible file request server + +USAGE: + srifreq --conf + +DESCRIPTION: + SRIF (Standard Request Information Format) compatible file + request server for binkd. Processes incoming .req files and + serves files based on password protection and aliases. + +CONFIGURATION FILE FORMAT: + # Lines starting with # are comments + # Blank lines are ignored + + pubdir # Public directory (no password required) + logfile # Log file, or - to disable + aliases # Magic-name aliases file (optional) + private # Private directory (requires !password) + + # Rate limiting options (optional): + trackfile # File to track node download statistics + maxfiles # Max files per node per time window (0=unlimited) + maxsize # Max bytes per node per time window (0=unlimited) + timewindow # Time window in seconds (0=no window) + +ALIASES FILE FORMAT: + # Lines starting with # are comments + # Format: + # Names are matched case-insensitively + +EXAMPLE CONFIG FILE (srifreq.conf): + # srifreq.conf - SRIF Request Server Configuration + + pubdir Work:Fido/Public + logfile Work:Logs/srifreq.log + aliases Work:Fido/srifreq.aliases + + # Private directories (password protected) + private Work:Fido/Private/Uploader1 secretpass1 + private Work:Fido/Private/Node190 node190pwd + + # Rate limiting: max 10 files or 50MB per node per 24 hours + trackfile Work:Logs/srifreq.track + maxfiles 10 + maxsize 52428800 + timewindow 86400 + +EXAMPLE ALIASES FILE (srifreq.aliases): + # srifreq.aliases - Magic-name to file mappings + # Names are case-insensitive + + DOORWAY Games:Utils/Doorway/doorway.zip + NETMAIL Work:Comm/Fido/netmail.lha + README Docs:Readme.txt + 4DOUT AmiTCP:4DOut.lha + BINKD Apps:Comm/Binkd/binkd.lha + +REQUEST FILE FORMAT (.req): + Files listed one per line. Modifiers: + !password - Required password for private areas + +timestamp - Only serve if file is newer than timestamp + U - Update request (only if newer than client's TRANX) + +EXAMPLES: + srifreq --conf srifreq.conf inbound/srif_file.req + srifreq --conf srifreq.conf inbound/*.req + exec "work:fido/srifreq --conf work:fido/srifreq.conf *S" *.req *.REQ \ No newline at end of file diff --git a/exitproc.c b/exitproc.c old mode 100644 new mode 100755 index 25e2b84f..a5f3735d --- a/exitproc.c +++ b/exitproc.c @@ -32,6 +32,17 @@ #include "nt/w32tools.h" #endif +#if defined(HAVE_THREADS) || defined(AMIGA) +extern MUTEXSEM hostsem; +extern MUTEXSEM resolvsem; +extern MUTEXSEM lsem; +extern MUTEXSEM blsem; +extern MUTEXSEM varsem; +extern MUTEXSEM config_sem; +extern EVENTSEM eothread; +extern EVENTSEM wakecmgr; +#endif + int binkd_exit; #ifdef HAVE_THREADS @@ -138,6 +149,33 @@ void exitfunc (void) close_srvmgr_socket(); #endif +#ifdef AMIGA + /* evloop: single process, no children + * Clean Exec semaphores in safe order before freeing config */ + close_srvmgr_socket(); + CleanEventSem(&wakecmgr); + CleanEventSem(&eothread); + CleanSem(&varsem); + CleanSem(&blsem); + CleanSem(&lsem); + CleanSem(&resolvsem); + CleanSem(&hostsem); + CleanSem(&config_sem); + sock_deinit(); + nodes_deinit(); + { + BINKD_CONFIG *cfg = lock_current_config(); + if (cfg) + bsy_remove_all(cfg); + if (cfg && *cfg->pid_file && pidsmgr == (int)getpid()) + delete(cfg->pid_file); + if (cfg) + unlock_config_structure(cfg, 1); + } + Log(6, "exitfunc: AmigaOS cleanup done"); + return; +#endif /* AMIGA */ + config = lock_current_config(); if (config) bsy_remove_all (config); diff --git a/ftnnode.c b/ftnnode.c old mode 100644 new mode 100755 index 11c3fe14..2de98993 --- a/ftnnode.c +++ b/ftnnode.c @@ -74,7 +74,7 @@ static void sort_nodes (BINKD_CONFIG *config) */ static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, - int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, + int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, int IP_afamily, #ifdef BW_LIM long bw_send, long bw_recv, @@ -107,6 +107,7 @@ static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pk pn->NR_flag = NR_OFF; pn->ND_flag = ND_OFF; pn->NP_flag = NP_OFF; + pn->NC_flag = NC_OFF; pn->MD_flag = MD_USE_OLD; pn->HC_flag = HC_USE_OLD; pn->pipe = NULL; @@ -134,6 +135,8 @@ static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pk pn->ND_flag = ND_flag; if (NP_flag != NP_USE_OLD) pn->NP_flag = NP_flag; + if (NC_flag != NC_USE_OLD) + pn->NC_flag = NC_flag; if (HC_flag != HC_USE_OLD) pn->HC_flag = HC_flag; if (IP_afamily != AF_USE_OLD) @@ -195,7 +198,7 @@ static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pk FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, - int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, + int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, int IP_afamily, #ifdef BW_LIM long bw_send, long bw_recv, @@ -209,7 +212,7 @@ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *o locknodesem(); pn = add_node_nolock(fa, hosts, pwd, pkt_pwd, out_pwd, obox_flvr, obox, ibox, - NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, + NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, IP_afamily, #ifdef BW_LIM bw_send, bw_recv, @@ -275,6 +278,7 @@ static FTN_NODE *get_defnode_info(FTN_ADDR *fa, FTN_NODE *on, BINKD_CONFIG *conf on->ND_flag=np->ND_flag; on->MD_flag=np->MD_flag; on->NP_flag=np->NP_flag; + on->NC_flag=np->NC_flag; on->HC_flag=np->HC_flag; on->restrictIP=np->restrictIP; on->pipe=np->pipe; @@ -290,7 +294,7 @@ static FTN_NODE *get_defnode_info(FTN_ADDR *fa, FTN_NODE *on, BINKD_CONFIG *conf add_node_nolock(fa, np->hosts, NULL, NULL, NULL, np->obox_flvr, np->obox, np->ibox, np->NR_flag, np->ND_flag, np->MD_flag, np->restrictIP, - np->HC_flag, np->NP_flag, np->pipe, np->IP_afamily, + np->HC_flag, np->NP_flag, np->NC_flag, np->pipe, np->IP_afamily, #ifdef BW_LIM np->bw_send, np->bw_recv, #endif @@ -399,7 +403,7 @@ int poll_node (char *s, BINKD_CONFIG *config) if (!get_node_info_nolock (&target, config)) add_node_nolock (&target, "*", NULL, NULL, NULL, '-', NULL, NULL, NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, - HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, + HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, #ifdef BW_LIM BW_DEF, BW_DEF, #endif diff --git a/ftnnode.h b/ftnnode.h old mode 100644 new mode 100755 index 8a85f2e1..e524caf6 --- a/ftnnode.h +++ b/ftnnode.h @@ -36,7 +36,7 @@ FTN_NODE *get_node_info (FTN_ADDR *fa, BINKD_CONFIG *config); */ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, - int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, + int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, int IP_afamily, #ifdef BW_LIM long bw_send, long bw_recv, @@ -75,6 +75,10 @@ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *o #define NP_OFF 0 #define NP_USE_OLD -1 /* Use old value */ +#define NC_ON 1 +#define NC_OFF 0 +#define NC_USE_OLD -1 /* Use old value */ + #define AF_USE_OLD -1 /* Use old value */ #ifdef BW_LIM diff --git a/ftnq.c b/ftnq.c old mode 100644 new mode 100755 index dcdaef8d..340c272b --- a/ftnq.c +++ b/ftnq.c @@ -1147,7 +1147,8 @@ void remove_try (FTN_ADDR *fa, BINKD_CONFIG *config) if (*buf) { strnzcat (buf, ".try", sizeof (buf)); - if (stat(buf, &sb) == -1) return; - delete (buf); + /* Delete only if the file exists */ + if (stat(buf, &sb) == 0) + delete (buf); } } diff --git a/https.c b/https.c old mode 100644 new mode 100755 index 05f98b06..1c35cb2b --- a/https.c +++ b/https.c @@ -318,7 +318,11 @@ int h_connect(int so, const char *host, const char *port, BINKD_CONFIG *config, buf[1]=1; lockhostsem(); Log (4, strcmp(port, config->oport) == 0 ? "trying %s..." : "trying %s:%u...", - inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); +#ifdef AMIGA + inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr.s_addr), portnum); +#else + inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); +#endif releasehostsem(); buf[2]=(unsigned char)((portnum>>8)&0xFF); buf[3]=(unsigned char)(portnum&0xFF); diff --git a/inbound.c b/inbound.c old mode 100644 new mode 100755 index 3e65613c..ce8a8541 --- a/inbound.c +++ b/inbound.c @@ -49,7 +49,9 @@ static int creat_tmp_name (char *s, TFILE *file, FTN_ADDR *from, char *inbound) char node[FTN_ADDR_SZ + 1]; strnzcpy (s, inbound, MAXPATHLEN); - strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); + /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ + if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) + strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); t = s + strlen (s); while (1) { @@ -128,7 +130,9 @@ static int find_tmp_name (char *s, TFILE *file, STATE *state, BINKD_CONFIG *conf } strnzcpy (s, inbound, MAXPATHLEN); - strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); + /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ + if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) + strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); t = s + strlen (s); while ((de = readdir (dp)) != 0) { @@ -470,7 +474,9 @@ int inb_done (TFILE *file, STATE *state, BINKD_CONFIG *config) } strnzcpy (real_name, state->inbound, MAXPATHLEN); - strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); + /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ + if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) + strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); s = real_name + strlen (real_name); strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); free (u); @@ -541,7 +547,9 @@ int inb_done (TFILE *file, STATE *state, BINKD_CONFIG *config) { ren_style = RENAME_POSTFIX; strnzcpy (real_name, state->inbound, MAXPATHLEN); - strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); + /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ + if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) + strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); s = real_name + strlen (real_name); strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); free (u); @@ -591,7 +599,9 @@ int inb_test (char *filename, boff_t size, time_t t, struct stat sb; strnzcpy (fp, inbound, MAXPATHLEN); - strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); + /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ + if (strlen(fp) > 0 && fp[strlen(fp) - 1] != PATH_SEPARATOR[0]) + strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); s = fp + strlen (fp); strnzcat (fp, u = strdequote (filename), MAXPATHLEN); free (u); diff --git a/iphdr.h b/iphdr.h old mode 100644 new mode 100755 index 7e2806c9..08a3b2f3 --- a/iphdr.h +++ b/iphdr.h @@ -124,9 +124,17 @@ void ReleaseErrorList(void); #define TCPERRNO errno #define TCPERR_WOULDBLOCK EWOULDBLOCK #define TCPERR_AGAIN EAGAIN - #define sock_init() 0 - #define sock_deinit() - #define soclose(h) close(h) + #ifdef AMIGA + /* AmigaOS 3: open bsdsocket.library via amiga/bsdsock.c */ + #include "amiga/bsdsock.h" + #define sock_init() amiga_sock_init() + #define sock_deinit() amiga_sock_cleanup() + #define soclose(h) CloseSocket(h) + #else + #define sock_init() 0 + #define sock_deinit() + #define soclose(h) close(h) + #endif #endif #if !defined(WIN32) diff --git a/iptools.c b/iptools.c old mode 100644 new mode 100755 index be1def9e..7b882019 --- a/iptools.c +++ b/iptools.c @@ -20,6 +20,10 @@ #include #endif +#ifdef AMIGA +#include +#endif + #include "sys.h" #include "Config.h" #include "iphdr.h" @@ -40,7 +44,11 @@ void setsockopts (SOCKET s) int arg; arg = 1; +#if defined(AMIGA) + if (ioctl (s, FIONBIO, (char *) &arg) < 0) +#else if (ioctl (s, FIONBIO, (char *) &arg, sizeof arg) < 0) +#endif Log (1, "ioctl (FIONBIO): %s", TCPERR ()); #elif defined(WIN32) @@ -53,12 +61,49 @@ void setsockopts (SOCKET s) #endif #endif -#if defined(UNIX) || defined(EMX) || defined(AMIGA) +#if defined(UNIX) || defined(EMX) /* NOT AMIGA: sockets are not AmigaDOS fds */ if (fcntl (s, F_SETFL, O_NONBLOCK) == -1) Log (1, "fcntl: %s", strerror (errno)); #endif } +#if defined(AMIGA) +void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf) +{ + /* Disable Nagle algorithm: BinkP mixes small control messages with data + * Without TCP_NODELAY each small message waits up to 200ms (Nagle delay), + * making sessions 2-5x slower than other BinkP implementations + * All other BinkP mailers (BinkIT, Argus, etc.) set this explicitly */ + + if (tcpdelay) + { + int nodelay = tcpdelay; + + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)) < 0) + Log (4, "setsockopt TCP_NODELAY: %s", TCPERR()); + } + + /* ixnet default TCP buffers are very small (~8KB). Increase them so the + * sender does not stall waiting for ACK after every small burst */ + + if (so_sndbuf) + { + int sndbuf = so_sndbuf; + + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, sizeof(sndbuf)) < 0) + Log (5, "setsockopt SO_SNDBUF: %s", TCPERR()); + } + + if (so_rcvbuf) + { + int rcvbuf = so_rcvbuf; + + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) < 0) + Log (5, "setsockopt SO_RCVBUF: %s", TCPERR()); + } +} +#endif + /* * Find the appropriate port string to be used. * Find_port ("") will return binkp's port from /etc/services or even diff --git a/iptools.h b/iptools.h old mode 100644 new mode 100755 index c8eaedf2..63430293 --- a/iptools.h +++ b/iptools.h @@ -11,11 +11,21 @@ * (at your option) any later version. See COPYING. */ +#if defined(AMIGA) +#include +#include +#include +#endif + /* * Sets non-blocking mode for a given socket */ void setsockopts (SOCKET s); +#if defined(AMIGA) +void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf); +#endif + /* * Find the port number (in the host byte order) by a port number string or * a service name. Find_port ("") will return binkp's port from diff --git a/misc/decompress.c b/misc/decompress.c new file mode 100755 index 00000000..1d66dc10 --- /dev/null +++ b/misc/decompress.c @@ -0,0 +1,188 @@ +/* + * decompress.c -- Decompress FTN bundle archives from an inbound directory + * + * decompress.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include "portable.h" /* Canonical portable layer */ +#include + +#define MAX_CMD 1100 + +/* Archive format codes detected by magic bytes */ +#define FMT_UNKNOWN 0 +#define FMT_ZIP 1 +#define FMT_LZH 2 +#define FMT_ARC 3 + +/* detect_format -- Read first bytes and identify archive type */ +static int detect_format(const char *path) +{ + unsigned char buf[8]; + FILE *f = fopen(path, "rb"); + int n; + + if (!f) + return FMT_UNKNOWN; + + n = (int)fread(buf, 1, sizeof(buf), f); + + fclose(f); + + if (n < 2) + return FMT_UNKNOWN; + + /* ZIP: PK\x03\x04 */ + if (n >= 4 && buf[0] == 0x50 && buf[1] == 0x4B && buf[2] == 0x03 && buf[3] == 0x04) + return FMT_ZIP; + + /* LZH: offset 2 = '-', offset 3 = 'l', offset 6 = '-' (e.g. -lh5-) */ + if (n >= 7 && buf[2] == '-' && buf[3] == 'l' && buf[6] == '-') + return FMT_LZH; + + /* ARC: 0x1A followed by type byte 1..18 */ + if (buf[0] == 0x1A && buf[1] >= 1 && buf[1] <= 18) + return FMT_ARC; + + return FMT_UNKNOWN; +} + +/* is_ftn_bundle -- Check filename has an FTN day-of-week extension */ +static int is_ftn_bundle(const char *filename) +{ + const char *p; + + for (p = filename; *p; p++) + { + if (p[0] == '.' && p[1] && p[2]) + { + char a = (char)tolower((unsigned char)p[1]); + char b = (char)tolower((unsigned char)p[2]); + + if ((a == 's' && b == 'u') || (a == 'm' && b == 'o') || + (a == 't' && b == 'u') || (a == 'w' && b == 'e') || + (a == 't' && b == 'h') || (a == 'f' && b == 'r') || + (a == 's' && b == 'a')) + { + /* .TH .TH0 .TH.001 */ + if (p[3] == '\0' || isdigit((unsigned char)p[3]) || p[3] == '.') + return 1; + } + } + } + return 0; +} + +/* delete_file -- Remove a file, portable */ +static void delete_file(const char *path) +{ +#if defined(AMIGA) + DeleteFile((STRPTR)path); +#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) + DeleteFileA(path); +#else + remove(path); +#endif +} + +/* run_decompressor -- Invoke external tool for the detected format + * outdir must end without trailing slash on POSIX; lha needs trailing / */ +static int run_decompressor(int fmt, const char *path, const char *outdir) +{ + char cmd[MAX_CMD]; + + switch (fmt) + { + case FMT_ZIP: +#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) + snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); +#else + snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); +#endif + break; + + case FMT_LZH: +#ifdef AMIGA + snprintf(cmd, MAX_CMD, "lha x \"%s\" \"%s/\"", path, outdir); +#else + snprintf(cmd, MAX_CMD, "lha e \"%s\" \"%s/\"", path, outdir); +#endif + break; + + case FMT_ARC: +#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) + snprintf(cmd, MAX_CMD, "arc x \"%s\" \"%s\"", path, outdir); +#else + snprintf(cmd, MAX_CMD, "cd \"%s\" && arc x \"%s\"", outdir, path); +#endif + break; + + default: + return -1; + } + + return system(cmd); +} + +int main(int argc, char *argv[]) +{ + DIR *dp; + struct dirent *entry; + char path[MAXPATHLEN]; + const char *inbound; + const char *outdir; + int total = 0; + int ok = 0; + + if (argc < 3) + { + fprintf(stderr, + "Usage: decompress \n" + "Detects format by magic bytes (ZIP/LZH/ARC).\n" + "Processes FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA).\n"); + + return 1; + } + + inbound = argv[1]; + outdir = argv[2]; + + dp = opendir(inbound); + + if (dp == NULL) + return 1; + + while ((entry = readdir(dp)) != NULL) + { + int fmt; + + /* Skip . and .. (AmigaOS readdir does not return these, POSIX does) */ + if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) + continue; + + if (!is_ftn_bundle(entry->d_name)) + continue; + + path_join(path, MAXPATHLEN, inbound, entry->d_name); + + fmt = detect_format(path); + + if (fmt == FMT_UNKNOWN) + continue; + + total++; + + if (run_decompressor(fmt, path, outdir) == 0) + { + delete_file(path); + ok++; + } + } + + closedir(dp); + + return (total == 0 || ok == total) ? 0 : 1; +} diff --git a/misc/freq.c b/misc/freq.c new file mode 100755 index 00000000..611c61e0 --- /dev/null +++ b/misc/freq.c @@ -0,0 +1,220 @@ +/* + * freq.c -- Append a file-request entry to an outbound .req / .clo pair + * + * freq.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include "portable.h" /* Canonical portable layer */ + +#define FREQ_MAX_PATH (MAXPATHLEN + 1) + +/* build_aso_paths -- ASO flat layout */ +static int build_aso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) +{ + char *dot; + + if (mkdir_recursive(outbound) < 0 && !path_exists(outbound)) + return -1; + + snprintf(req_path, (size_t)pathsize, "%s/%u.%u.%u.%u.req", outbound, zone, net, node, point); + safe_strncpy(clo_path, req_path, pathsize); + dot = strrchr(clo_path, '.'); + + if (dot) + strcpy(dot, ".clo"); + + return 0; +} + +/* build_bso_paths -- BSO BinkleyStyle layout (lowercase hex) */ +static int build_bso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) +{ + char zone_dir[FREQ_MAX_PATH]; + char node_dir[FREQ_MAX_PATH]; + + /* Zone dir: .0ZZ (lowercase hex) */ + snprintf(zone_dir, sizeof(zone_dir), "%s.%03x", outbound, zone); + str_tolower(zone_dir); + + if (mkdir_recursive(zone_dir) < 0 && !path_exists(zone_dir)) + return -1; + + if (point == 0) + { + snprintf(req_path, (size_t)pathsize, "%s/%04x%04x.req", zone_dir, net, node); + snprintf(clo_path, (size_t)pathsize, "%s/%04x%04x.clo", zone_dir, net, node); + } + else + { + snprintf(node_dir, sizeof(node_dir), "%s/%04x%04x.pnt", zone_dir, net, node); + + if (mkdir_recursive(node_dir) < 0 && !path_exists(node_dir)) + return -1; + + snprintf(req_path, (size_t)pathsize, "%s/%08x.req", node_dir, point); + snprintf(clo_path, (size_t)pathsize, "%s/%08x.clo", node_dir, point); + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + unsigned int zone, net, node, point; + char addr_copy[128]; + char req_path[FREQ_MAX_PATH]; + char clo_path[FREQ_MAX_PATH]; + char abs_outbound[FREQ_MAX_PATH]; + FILE *f; + const char *outbound; + const char *arg_outbound; + const char *arg_addr; + const char *password = NULL; /* --password → !pass suffix */ + long newer_than = 0; /* --newer-than → +ts suffix */ + int update = 0; /* --update → U suffix */ + int use_bso = 0; + int argi = 1; + int nfiles = 0; + + zone = net = node = point = 0; + + /* Parse flags -- All optional, order-independent, before positional args */ + while (argi < argc && argv[argi][0] == '-' && argv[argi][1] == '-') + { + if (strcmp(argv[argi], "--bso") == 0) + { + use_bso = 1; + argi++; + } + else if (strcmp(argv[argi], "--aso") == 0) + { + use_bso = 0; + argi++; + } + else if (strcmp(argv[argi], "--update") == 0) + { + update = 1; + argi++; + } + else if (strcmp(argv[argi], "--password") == 0 && argi + 1 < argc) + { + password = argv[++argi]; + argi++; + } + else if (strcmp(argv[argi], "--newer-than") == 0 && argi + 1 < argc) + { + newer_than = atol(argv[++argi]); + argi++; + } + else + break; /* unknown flag — stop, treat rest as positional */ + } + + if (argc - argi < 3) + { + fprintf(stderr, + "Usage: freq [options] Z:N/NODE[.POINT] [...]\n" + "Options:\n" + " --aso flat layout (default): outbound/Z.N.NODE.POINT.req\n" + " --bso BSO layout: outbound.0ZZ/nnnnnnnn[.pnt/pppppppp].req\n" + " --password append !pw to each request line\n" + " --newer-than append + (request if newer)\n" + " --update append U flag (update request)\n" + "Multiple filenames can be listed after the address.\n"); + return 1; + } + + arg_outbound = argv[argi++]; + arg_addr = argv[argi++]; + + /* Remaining args are filenames */ + + make_abs_path(arg_outbound, abs_outbound, (int)sizeof(abs_outbound)); + outbound = abs_outbound; + + safe_strncpy(addr_copy, arg_addr, (int)sizeof(addr_copy)); + + if (sscanf(addr_copy, "%u:%u/%u.%u", &zone, &net, &node, &point) < 3 && sscanf(addr_copy, "%u:%u/%u", &zone, &net, &node) < 3) + { + fprintf(stderr, "freq: invalid address: %s\n", arg_addr); + return 1; + } + + if (use_bso) + { + if (build_bso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) + { + fprintf(stderr, "freq: cannot create BSO dirs under: %s\n", outbound); + return 1; + } + } + else + { + if (build_aso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) + { + fprintf(stderr, "freq: cannot create outbound dir: %s\n", outbound); + return 1; + } + } + + /* Append all filenames to .req */ + f = fopen(req_path, "a"); + + if (!f) + { + fprintf(stderr, "freq: cannot open REQ: %s\n", req_path); + return 1; + } + + for (; argi < argc; argi++) + { + const char *fname = argv[argi]; + + /* Build request line: filename [!password] [+timestamp] [U] */ + fprintf(f, "%s", fname); + + if (password && password[0]) + fprintf(f, " !%s", password); + + if (newer_than > 0) + fprintf(f, " +%ld", newer_than); + + if (update) + fprintf(f, " U"); + + fprintf(f, "\r\n"); + nfiles++; + } + + fclose(f); + + if (nfiles == 0) + { + fprintf(stderr, "freq: no filenames specified\n"); + return 1; + } + + /* Append .req full path to .clo (once per invocation) */ + f = fopen(clo_path, "a"); + + if (!f) + { + fprintf(stderr, "freq: cannot open CLO: %s\n", clo_path); + return 1; + } + + fprintf(f, "%s\r\n", req_path); + fclose(f); + + printf("freq (%s): node %u:%u/%u", use_bso ? "bso" : "aso", zone, net, node); + + if (point) + printf(".%u", point); + + printf(" %d file(s)\n REQ : %s\n CLO : %s\n", nfiles, req_path, clo_path); + + return 0; +} diff --git a/misc/nodelist.c b/misc/nodelist.c new file mode 100755 index 00000000..b4bc1a05 --- /dev/null +++ b/misc/nodelist.c @@ -0,0 +1,200 @@ +/* + * nodelist.c -- FidoNet nodelist compiler for binkd + * + * nodelist.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include "portable.h" /* Canonical portable layer */ +#include +#include +#include +#include + +#define MAX_FIELDS 20 +#define MAX_VAL 256 + +static int split_fields(char *buf, char **fields, int maxfields) +{ + int n = 0; + char *p = buf; + + while (n < maxfields) + { + fields[n++] = p; + p = strchr(p, ','); + + if (!p) + break; + + *p++ = '\0'; + } + + return n; +} + +/* Case-insensitive flag search. Returns 1 if found; fills val if present */ +static int find_flag(char **fields, int nfields, int start, const char *flag, char *val, int vsize) +{ + int flen = (int)strlen(flag); + int i; + + for (i = start; i < nfields; i++) + { + char *f = fields[i]; + int j; + + for (j = 0; j < flen; j++) + { + if (toupper((unsigned char)f[j]) != toupper((unsigned char)flag[j])) + break; + } + + if (j == flen) + { + if (val && vsize > 0) + { + if (f[flen] == ':') + { + strncpy(val, f + flen + 1, (size_t)(vsize - 1)); + val[vsize - 1] = '\0'; + } + else + val[0] = '\0'; + } + return 1; + } + } + return 0; +} + +int main(int argc, char *argv[]) +{ + const char *nl_file; + const char *domain; + FILE *in; + FILE *out; + char buf[MAX_LINE]; + char *fields[MAX_FIELDS]; + int nf; + int cur_zone; + int cur_net; + long count; + + cur_zone = 0; + cur_net = 0; + count = 0; + + if (argc < 3) + { + fprintf(stderr, + "Usage: nodelist []\n"); + return 1; + } + + nl_file = argv[1]; + domain = argv[2]; + out = (argc >= 4) ? fopen(argv[3], "w") : stdout; + + if (!out) + { + perror(argv[3]); + return 1; + } + + in = fopen(nl_file, "r"); + + if (!in) + { + perror(nl_file); + + if (out != stdout) + fclose(out); + + return 1; + } + + while (fgets(buf, sizeof(buf), in)) + { + char type[32]; + char ibn_port[32]; + char ina_host[MAX_VAL]; + int node_num; + int port; + int flags_start; + + str_trim(buf); + + if (!buf[0] || buf[0] == ';') + continue; + + nf = split_fields(buf, fields, MAX_FIELDS); + + if (nf < 2) + continue; + + if (fields[0][0] == '\0') + { + /* Line started with comma -- plain Node entry */ + strcpy(type, "Node"); + node_num = atoi(fields[1]); + flags_start = 7; + } + else + { + strncpy(type, fields[0], sizeof(type) - 1); + type[sizeof(type) - 1] = '\0'; + node_num = atoi(fields[1]); + flags_start = 7; + } + + /* Update zone / net context */ + if (!strcmp(type, "Zone") || !strcmp(type, "ZONE")) + { + cur_zone = node_num; + cur_net = node_num; + continue; + } + + if (!strcmp(type, "Region") || !strcmp(type, "REGION")) + { + cur_net = node_num; + continue; + } + + if (!strcmp(type, "Host") || !strcmp(type, "HOST")) + cur_net = node_num; + + /* Skip unusable types */ + if (!strcmp(type, "Pvt") || !strcmp(type, "PVT") || !strcmp(type, "Hold") || !strcmp(type, "HOLD") || !strcmp(type, "Down") || !strcmp(type, "DOWN") || !strcmp(type, "Boss") || !strcmp(type, "BOSS")) + continue; + + /* Must have IBN flag */ + if (!find_flag(fields, nf, flags_start, "IBN", ibn_port, (int)sizeof(ibn_port))) + continue; + + /* Need INA:hostname */ + ina_host[0] = '\0'; + find_flag(fields, nf, flags_start, "INA", ina_host, (int)sizeof(ina_host)); + + if (!ina_host[0]) + continue; + + port = (ibn_port[0] && atoi(ibn_port) > 0) ? atoi(ibn_port) : 24554; + + fprintf(out, "node %d:%d/%d@%s %s:%d -\n", cur_zone, cur_net, node_num, domain, ina_host, port); + + count++; + } + + fclose(in); + + if (out != stdout) + fclose(out); + + fprintf(stderr, "nodelist: %ld BinkP node(s) found\n", count); + + return 0; +} diff --git a/misc/portable.c b/misc/portable.c new file mode 100644 index 00000000..d06dc5ac --- /dev/null +++ b/misc/portable.c @@ -0,0 +1,402 @@ +/* + * portable.c -- Shared implementations for misc tools portable layer + * + * portable.c is a part of binkd project + * + * This file provides implementations for common utility functions + * used across binkd misc tools. Include portable.h for declarations + * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + * + */ + +#include "portable.h" +#include + +/* trim_nl -- Strip trailing newline (\n and \r) from string */ +void trim_nl(char *s) +{ + char *p = strchr(s, '\n'); + + if (p) + *p = '\0'; + + p = strchr(s, '\r'); + + if (p) + *p = '\0'; +} + +/* str_trim -- Strip trailing whitespace (space, \r, \n) from string */ +void str_trim(char *s) +{ + int n = (int)strlen(s); + + while (n > 0 && (s[n - 1] == '\r' || s[n - 1] == '\n' || s[n - 1] == ' ')) + s[--n] = '\0'; +} + +/* str_upper -- Convert string to uppercase in-place */ +void str_upper(char *s) +{ + while (*s) + { + *s = (char)toupper((unsigned char)*s); + s++; + } +} + +/* str_tolower -- Convert string to lowercase in-place */ +void str_tolower(char *s) +{ + for (; *s; s++) + { + if (*s >= 'A' && *s <= 'Z') + *s = (char)(*s + ('a' - 'A')); + } +} + +/* skip_ws -- Skip leading whitespace */ +char *skip_ws(char *s) +{ + while (*s == ' ' || *s == '\t') + s++; + + return s; +} + +/* wildmatch -- Portable wildcard match: case-insensitive, supports * and ? */ +int wildmatch(const char *pat, const char *str) +{ + while (*pat) + { + if (*pat == '*') + { + while (*pat == '*') + pat++; + + if (!*pat) + return 1; + + while (*str) + { + if (wildmatch(pat, str++)) + return 1; + } + + return 0; + } + + if (*pat == '?') + { + if (!*str) + return 0; + + pat++; + str++; + } + else + { + if (toupper((unsigned char)*pat) != toupper((unsigned char)*str)) + return 0; + + pat++; + str++; + } + } + + return (*str == '\0') ? 1 : 0; +} + +/* is_wildcard -- True if name contains * or ? */ +int is_wildcard(const char *s) +{ + while (*s) + { + if (*s == '*' || *s == '?') + return 1; + + s++; + } + + return 0; +} + +/* ensure_dir -- Ensure directory exists, creating if necessary */ +int ensure_dir(const char *path) +{ + if (path_exists(path)) + return 1; + + return (mkdir_recursive(path) == 0) ? 1 : 0; +} + +/* copy_file -- Portable binary file copy */ +int copy_file(const char *src, const char *dst) +{ + FILE *in, *out; + char buf[4096]; + int n; + + in = fopen(src, "rb"); + + if (!in) + return 0; + + out = fopen(dst, "wb"); + + if (!out) + { + fclose(in); + return 0; + } + + while ((n = (int)fread(buf, 1, sizeof(buf), in)) > 0) + fwrite(buf, 1, (size_t)n, out); + + fclose(out); + fclose(in); + + return 1; +} + +/* move_file -- Try rename first, fall back to copy+delete */ +int move_file(const char *src, const char *dst) +{ + remove(dst); + + if (rename(src, dst) == 0) + return 1; + + if (copy_file(src, dst)) + { + remove(src); + return 1; + } + + return 0; +} + +/* get_file_size -- Return file size in bytes, or -1 on error */ +long get_file_size(const char *path) +{ + struct stat st; + + if (stat(path, &st) == 0) + return (long)st.st_size; + + return -1; +} + +/* get_file_mtime -- Return Unix mtime of a file, or 0 on error */ +long get_file_mtime(const char *path) +{ + struct stat st; + + if (stat(path, &st) != 0) + return 0; + + return (long)st.st_mtime; +} + +/* port_path_exists -- Check if path exists (native per OS) */ + +int port_path_exists(const char *p) +{ +#ifdef AMIGA + BPTR l = Lock((STRPTR)p, ACCESS_READ); + + if (l) + { + UnLock(l); + return 1; + } + + return 0; +#else + struct stat st; + return (stat(p, &st) == 0) ? 1 : 0; +#endif +} + +/* port_mkdir_one -- Create single directory (native per OS) */ +int port_mkdir_one(const char *p) +{ +#ifdef AMIGA + BPTR l = CreateDir((STRPTR)p); + + if (l) + { + UnLock(l); + return 0; + } + + return -1; +#else + return mkdir(p, 0755); +#endif +} + +/* safe_localtime -- Thread-safe localtime, portable across all OS */ +void safe_localtime(const time_t *t, struct tm *tm) +{ +#if defined(AMIGA) || defined(DOS) + *tm = *localtime(t); +#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) + localtime_s(tm, t); +#else + localtime_r(t, tm); +#endif +} + +/* mkdir_recursive -- Create full path, making all missing components */ +int mkdir_recursive(const char *path) +{ + char tmp[MP_MAXPATH]; + char *p; + int len; + + if (!path || !path[0]) + return -1; + + strncpy(tmp, path, MP_MAXPATH - 1); + tmp[MP_MAXPATH - 1] = '\0'; + + len = (int)strlen(tmp); + + /* Strip trailing slash */ + while (len > 1 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\')) + tmp[--len] = '\0'; + + /* Walk every '/' component and create missing dirs */ + for (p = tmp + 1; *p; p++) + { + if (*p == '/' || *p == '\\') + { + *p = '\0'; + + if (!path_exists(tmp)) + mkdir_one(tmp); /* ignore per-component errors */ + + *p = '/'; + } + } + + /* Create the leaf */ + if (!path_exists(tmp)) + return mkdir_one(tmp); + + return 0; +} + +/* safe_strncpy -- Ctrncpy that always NUL-terminates */ +void safe_strncpy(char *dst, const char *src, int dstsize) +{ + int len; + + if (dstsize <= 0) + return; + + len = (int)strlen(src); + + if (len > dstsize - 1) + len = dstsize - 1; + + memcpy(dst, src, (size_t)len); + dst[len] = '\0'; +} + +/* path_join -- Concatenate base path with sub path */ +void path_join(char *out, int outsize, const char *base, const char *sub) +{ + int blen; + char last; + + safe_strncpy(out, base, outsize); + blen = (int)strlen(out); + last = (blen > 0) ? out[blen - 1] : '\0'; + + if (last != '/' && last != ':' && last != '\\') + { + if (outsize - 1 - blen > 0) + { + out[blen] = '/'; + out[blen + 1] = '\0'; + blen++; + } + } + + safe_strncpy(out + blen, sub, outsize - blen); +} + +/* make_abs_path -- Resolve a possibly-relative path to absolute + * Covers AmigaOS, Win32, OS/2, DOS and Unix + * Returns 1 on success, 0 on failure (src copied verbatim as fallback) + */ +int make_abs_path(const char *src, char *dst, int dstlen) +{ +#ifdef AMIGA + BPTR lock = Lock((STRPTR)src, SHARED_LOCK); + + if (!lock) + { + safe_strncpy(dst, src, dstlen); + return 0; + } + + if (!NameFromLock(lock, (STRPTR)dst, dstlen)) + { + UnLock(lock); + safe_strncpy(dst, src, dstlen); + return 0; + } + + UnLock(lock); + + return 1; +#elif defined(WIN32) || defined(__MINGW32__) || defined(__WATCOMC__) || defined(VISUALCPP) || defined(OS2) + if (_fullpath(dst, src, (size_t)dstlen) != NULL) + return 1; + + safe_strncpy(dst, src, dstlen); + return 0; +#elif defined(DOS) + if (src[0] != '\\' && src[1] != ':') + { + char cwd[MAXPATHLEN + 1]; + + if (getcwd(cwd, sizeof(cwd)) != NULL) + { + snprintf(dst, dstlen, "%s\\%s", cwd, src); + return 1; + } + } + + safe_strncpy(dst, src, dstlen); + return 0; +#else + char buf[MAXPATHLEN + 1]; + + if (realpath(src, buf) != NULL) + { + safe_strncpy(dst, buf, dstlen); + return 1; + } + + if (src[0] != '/') + { + char cwd[MAXPATHLEN + 1]; + + if (getcwd(cwd, sizeof(cwd)) != NULL) + { + snprintf(dst, dstlen, "%s/%s", cwd, src); + return 1; + } + } + + safe_strncpy(dst, src, dstlen); + return 0; +#endif +} diff --git a/misc/portable.h b/misc/portable.h new file mode 100644 index 00000000..946683dc --- /dev/null +++ b/misc/portable.h @@ -0,0 +1,135 @@ +/* + * portable.h -- Portability layer for standalone binkd misc tools + * + * portable.h is a part of binkd project + * + * This is the single canonical portable.h; all misc utilities include this + * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + * + */ + +#ifndef BINKD_PORTABLE_H +#define BINKD_PORTABLE_H + +/* _POSIX_C_SOURCE for opendir/readdir/localtime_r under -std=c89 + * _XOPEN_SOURCE 500 additionally exposes realpath() on glibc */ +#ifndef AMIGA +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200112L +#endif +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 500 +#endif +#endif + +#include +#include +#include +#include +#include + +#ifdef AMIGA + +#include +#include +#include +#include +#include +#include /* stat() / struct stat via libnix/ADE */ +#include +#include "amiga/dirent.h" /* opendir / readdir / closedir */ + +/* snprintf/vsnprintf: ADE/libnix declares them in stdio.h (already included + * above via ). The implementation is provided by snprintf.c which + * must be linked when building the misc tools. No redeclaration needed */ + +#elif defined(VISUALCPP) +#include +#include +#include +#include "nt/dirwin32.h" /* opendir/readdir/closedir for MSVC */ +#elif defined(__MINGW32__) || defined(WIN32) +#include +#include /* MinGW provides dirent.h natively */ +#include +#include +#elif defined(OS2) && (defined(IBMC) || defined(__WATCOMC__)) +#include +#include +#include +#include "os2/dirent.h" /* opendir/readdir/closedir for OS/2 ICC/WC */ +#elif defined(OS2) +#include +#include /* EMX provides dirent.h natively */ +#include +#include +#include +#elif defined(DOS) +#include +#include +#include "dos/dirent.h" /* opendir/readdir/closedir for DOS/DJGPP */ +#else /* POSIX / *nix */ +#include +#include +#include +#include +#include +#endif + +#ifndef MAXPATHLEN +#if defined(_MAX_PATH) +#define MAXPATHLEN _MAX_PATH +#elif defined(PATH_MAX) +#define MAXPATHLEN PATH_MAX +#else +#define MAXPATHLEN 1024 +#endif +#endif + +/* Generic line buffer size for config files and text processing */ +#ifndef MAX_LINE +#define MAX_LINE 1024 +#endif + +/* path_exists / mkdir_one -- native implementations per OS */ +int port_path_exists(const char *p); +int port_mkdir_one(const char *p); +#define path_exists(p) port_path_exists(p) +#define mkdir_one(p) port_mkdir_one(p) + +/* safe_localtime -- thread-safe localtime, portable across all OS */ +void safe_localtime(const time_t *t, struct tm *tm); + +/* mkdir_recursive -- create full path, making all missing components */ +#define MP_MAXPATH 512 +int mkdir_recursive(const char *path); + +/* safe_strncpy -- strncpy that always NUL-terminates */ +void safe_strncpy(char *dst, const char *src, int dstsize); + +/* String utilities */ +void trim_nl(char *s); +void str_trim(char *s); +void str_upper(char *s); +void str_tolower(char *s); +char *skip_ws(char *s); + +/* Wildcard matching */ +int wildmatch(const char *pat, const char *str); +int is_wildcard(const char *s); + +/* File operations */ +int ensure_dir(const char *path); +int copy_file(const char *src, const char *dst); +int move_file(const char *src, const char *dst); +long get_file_size(const char *path); +long get_file_mtime(const char *path); + +/* Path utilities */ +void path_join(char *out, int outsize, const char *base, const char *sub); +int make_abs_path(const char *src, char *dst, int dstlen); + +#endif /* BINKD_PORTABLE_H */ diff --git a/misc/process_tic.c b/misc/process_tic.c new file mode 100755 index 00000000..51061e64 --- /dev/null +++ b/misc/process_tic.c @@ -0,0 +1,552 @@ +/* + * process_tic -- Process FTN .tic files from inbound to filebox + * + * process_tic.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include "portable.h" /* Canonical portable layer */ +#include + +static int my_toupper(int c) +{ + if (c >= 'a' && c <= 'z') + return c - 'a' + 'A'; + + return c; +} + +static int my_strnicmp(const char *a, const char *b, int n) +{ + int i, ca, cb; + for (i = 0; i < n; i++) + { + ca = my_toupper((unsigned char)a[i]); + cb = my_toupper((unsigned char)b[i]); + + if (ca != cb) + return ca - cb; + + if (ca == 0) + return 0; + } + + return 0; +} + +static int parse_file_field(char *line, char *out, int outsize) +{ + char *p; + char *end; + int len; + + p = skip_ws(line); + + if (my_strnicmp(p, "File", 4) != 0) + return 0; + + p += 4; + + if (*p != ' ' && *p != '\t') + return 0; + + p = skip_ws(p); + trim_nl(p); + end = p; + + while (*end && *end != ' ' && *end != '\t') + end++; + + *end = '\0'; + + len = (int)strlen(p); + + if (len <= 0 || len >= outsize) + return 0; + + strncpy(out, p, outsize - 1); + out[outsize - 1] = '\0'; + + return 1; +} + +static int parse_area_field(char *line, char *out, int outsize) +{ + char *p; + char *end; + int len; + + p = skip_ws(line); + + if (my_strnicmp(p, "Area", 4) != 0) + return 0; + + p += 4; + + if (*p != ' ' && *p != '\t') + return 0; + + p = skip_ws(p); + trim_nl(p); + end = p; + + while (*end && *end != ' ' && *end != '\t') + end++; + + *end = '\0'; + len = (int)strlen(p); + + if (len <= 0 || len >= outsize) + return 0; + + strncpy(out, p, outsize - 1); + out[outsize - 1] = '\0'; + + return 1; +} + +static int parse_origin_field(char *line, char *out, int outsize) +{ + char *p; + char *end; + int len; + + p = skip_ws(line); + + if (my_strnicmp(p, "Origin", 6) != 0) + return 0; + + p += 6; + + if (*p != ' ' && *p != '\t') + return 0; + + p = skip_ws(p); + trim_nl(p); + end = p; + + while (*end && *end != ' ' && *end != '\t') + end++; + + *end = '\0'; + len = (int)strlen(p); + + if (len <= 0 || len >= outsize) + return 0; + + strncpy(out, p, outsize - 1); + out[outsize - 1] = '\0'; + + return 1; +} + +static int parse_from_field(char *line, char *out, int outsize) +{ + char *p; + char *end; + int len; + + p = skip_ws(line); + + if (my_strnicmp(p, "From", 4) != 0) + return 0; + + p += 4; + + if (*p != ' ' && *p != '\t') + return 0; + + p = skip_ws(p); + trim_nl(p); + end = p; + + while (*end && *end != ' ' && *end != '\t') + end++; + + *end = '\0'; + len = (int)strlen(p); + + if (len <= 0 || len >= outsize) + return 0; + + strncpy(out, p, outsize - 1); + out[outsize - 1] = '\0'; + + return 1; +} + +static void append_filelist(const char *listpath, const char *file_name, long filesize, const char *dst_path) +{ + FILE *f; + time_t t; + struct tm tm; + char timestamp[32]; + + if (!listpath || !listpath[0]) + return; + + f = fopen(listpath, "a"); + if (!f) + return; + + t = time(NULL); + safe_localtime(&t, &tm); + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); + + fprintf(f, "%s\t%s\t%ld\t%s\n", timestamp, file_name, filesize, dst_path); + fclose(f); +} + +static void append_newfiles(const char *newprefix, const char *file_name, long filesize, const char *dst_path) +{ + FILE *f; + char newpath[MAXPATHLEN]; + time_t t; + struct tm tm; + char datebuf[16]; + + if (!newprefix || !newprefix[0]) + return; + + t = time(NULL); + safe_localtime(&t, &tm); + strftime(datebuf, sizeof(datebuf), "%Y%m%d", &tm); + + ensure_dir(newprefix); + path_join(newpath, (int)sizeof(newpath), newprefix, "newfiles-"); + strncat(newpath, datebuf, sizeof(newpath) - strlen(newpath) - 1); + strncat(newpath, ".txt", sizeof(newpath) - strlen(newpath) - 1); + + f = fopen(newpath, "a"); + + if (!f) + return; + + fprintf(f, "%s\t%ld\t%s\n", file_name, filesize, dst_path); + + fclose(f); +} + +static void write_ticlog(const char *ticlog, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) +{ + FILE *f; + time_t t; + struct tm tm; + char timestamp[64]; + + if (!ticlog || !ticlog[0]) + return; + + f = fopen(ticlog, "a"); + if (!f) + return; + + t = time(NULL); + safe_localtime(&t, &tm); + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); + + fprintf(f, "[%s] File: %s\n", timestamp, file_name); + fprintf(f, " Area: %s\n", area_name); + + if (origin_name[0]) + fprintf(f, " Origin: %s\n", origin_name); + + if (from_name[0]) + fprintf(f, " From: %s\n", from_name); + + fprintf(f, " Src: %s\n", src_path); + fprintf(f, " To: %s\n", dst_path); + fprintf(f, "\n"); + + fclose(f); +} + +static void write_log(const char *logfile, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) +{ + FILE *f; + time_t t; + struct tm tm; + char timestamp[64]; + + if (!logfile || !logfile[0]) + return; + + f = fopen(logfile, "a"); + if (!f) + return; + + t = time(NULL); + safe_localtime(&t, &tm); + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); + + fprintf(f, "[%s] File: %s\n", timestamp, file_name); + fprintf(f, " Area: %s\n", area_name); + + if (origin_name[0]) + fprintf(f, " Origin: %s\n", origin_name); + + if (from_name[0]) + fprintf(f, " From: %s\n", from_name); + + fprintf(f, " Src: %s\n", src_path); + fprintf(f, " To: %s\n", dst_path); + fprintf(f, "\n"); + fclose(f); +} + +static void process_one_tic(const char *ticpath, const char *inbound, const char *filebox, int copypublic, const char *pubdir, const char *logfile, const char *filelist, const char *newfiles, const char *ticlog) +{ + FILE *f; + char line[MAX_LINE]; + char file_name[MAXPATHLEN]; + char area_name[MAXPATHLEN]; + char origin_name[MAXPATHLEN]; + char from_name[MAXPATHLEN]; + char src_path[MAXPATHLEN]; + char area_dir[MAXPATHLEN]; + char dst_path[MAXPATHLEN]; + + file_name[0] = '\0'; + area_name[0] = '\0'; + origin_name[0] = '\0'; + from_name[0] = '\0'; + long fsize = 0; + + f = fopen(ticpath, "r"); + + if (!f) + return; + + while (fgets(line, sizeof(line), f)) + { + if (!file_name[0]) + parse_file_field(line, file_name, sizeof(file_name)); + + if (!area_name[0]) + parse_area_field(line, area_name, sizeof(area_name)); + + if (!origin_name[0]) + parse_origin_field(line, origin_name, sizeof(origin_name)); + + if (!from_name[0]) + parse_from_field(line, from_name, sizeof(from_name)); + } + + fclose(f); + + if (!file_name[0] || !area_name[0]) + return; + + path_join(src_path, sizeof(src_path), inbound, file_name); + path_join(area_dir, sizeof(area_dir), filebox, area_name); + path_join(dst_path, sizeof(dst_path), area_dir, file_name); + + if (!path_exists(src_path)) + return; + + if (!ensure_dir(filebox) || !ensure_dir(area_dir)) + return; + + if (copypublic && pubdir && pubdir[0]) + { + char pub_dst[MAXPATHLEN]; + + path_join(pub_dst, sizeof(pub_dst), pubdir, file_name); + + if (ensure_dir(pubdir)) + copy_file(src_path, pub_dst); + } + + fsize = get_file_size(src_path); + + if (!move_file(src_path, dst_path)) + return; + + write_log(logfile, file_name, area_name, origin_name, from_name, src_path, dst_path); + write_ticlog(ticlog, file_name, area_name, origin_name, from_name, src_path, dst_path); + append_filelist(filelist, file_name, fsize, dst_path); + append_newfiles(newfiles, file_name, fsize, dst_path); + + remove(ticpath); +} + +static int is_tic_file(const char *name) +{ + int len = (int)strlen(name); + + if (len < 5) + return 0; + + return (my_strnicmp(name + len - 4, ".tic", 4) == 0); +} + +/* Config structure */ +static struct +{ + char inbound[MAXPATHLEN]; + char filebox[MAXPATHLEN]; + char pubdir[MAXPATHLEN]; + char logfile[MAXPATHLEN]; + char filelist[MAXPATHLEN]; + char newfiles[MAXPATHLEN]; + char ticlog[MAXPATHLEN]; + int copypublic; +} cfg; + +/* Parse configuration file */ +static int parse_config(const char *conffile) +{ + FILE *f; + char line[MAX_LINE]; + char *key, *value; + + memset(&cfg, 0, sizeof(cfg)); + + f = fopen(conffile, "r"); + + if (!f) + { + fprintf(stderr, "process_tic: cannot open config file: %s\n", conffile); + return 0; + } + + while (fgets(line, sizeof(line), f)) + { + trim_nl(line); + key = skip_ws(line); + + /* Skip comments and empty lines */ + if (*key == '#' || *key == '\0') + continue; + + /* Find value after key */ + value = key; + + while (*value && *value != ' ' && *value != '\t') + value++; + + if (*value) + { + *value = '\0'; + value = skip_ws(value + 1); + } + + /* Parse key-value pairs */ + if (strcmp(key, "inbound") == 0) + safe_strncpy(cfg.inbound, value, (int)sizeof(cfg.inbound)); + else if (strcmp(key, "filebox") == 0) + safe_strncpy(cfg.filebox, value, (int)sizeof(cfg.filebox)); + else if (strcmp(key, "pubdir") == 0) + { + safe_strncpy(cfg.pubdir, value, (int)sizeof(cfg.pubdir)); + cfg.copypublic = 1; + } + else if (strcmp(key, "logfile") == 0) + safe_strncpy(cfg.logfile, value, (int)sizeof(cfg.logfile)); + else if (strcmp(key, "filelist") == 0) + safe_strncpy(cfg.filelist, value, (int)sizeof(cfg.filelist)); + else if (strcmp(key, "newfiles") == 0) + safe_strncpy(cfg.newfiles, value, (int)sizeof(cfg.newfiles)); + else if (strcmp(key, "ticlog") == 0) + safe_strncpy(cfg.ticlog, value, (int)sizeof(cfg.ticlog)); + } + + fclose(f); + + /* Validate required fields */ + if (!cfg.inbound[0] || !cfg.filebox[0]) + { + fprintf(stderr, "process_tic: config file missing required 'inbound' or 'filebox'\n"); + return 0; + } + + return 1; +} + +int main(int argc, char *argv[]) +{ + char inbound[MAXPATHLEN]; + char filebox[MAXPATHLEN]; + char ticpath[MAXPATHLEN]; + char pubdir[MAXPATHLEN]; + char logfile[MAXPATHLEN]; + char filelist[MAXPATHLEN]; + char newfiles[MAXPATHLEN]; + char ticlog[MAXPATHLEN]; + int copypublic = 0; + DIR *dp; + struct dirent *de; + int found; + int i; + int use_config = 0; + + inbound[0] = '\0'; + filebox[0] = '\0'; + pubdir[0] = '\0'; + logfile[0] = '\0'; + filelist[0] = '\0'; + newfiles[0] = '\0'; + ticlog[0] = '\0'; + + /* Check for --conf option */ + for (i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--conf") == 0 && i + 1 < argc) + { + if (!parse_config(argv[i + 1])) + return 1; + + use_config = 1; + i++; /* Skip config file path */ + + break; + } + } + + if (use_config) + { + /* Use config file values */ + safe_strncpy(inbound, cfg.inbound, (int)sizeof(inbound)); + safe_strncpy(filebox, cfg.filebox, (int)sizeof(filebox)); + safe_strncpy(pubdir, cfg.pubdir, (int)sizeof(pubdir)); + safe_strncpy(logfile, cfg.logfile, (int)sizeof(logfile)); + safe_strncpy(filelist, cfg.filelist, (int)sizeof(filelist)); + safe_strncpy(newfiles, cfg.newfiles, (int)sizeof(newfiles)); + safe_strncpy(ticlog, cfg.ticlog, (int)sizeof(ticlog)); + copypublic = cfg.copypublic; + } + + if (!inbound[0] || !filebox[0]) + { + fprintf(stderr, + "Usage: process_tic --conf [*.tic]\n"); + + return 1; + } + + dp = opendir(inbound); + + if (!dp) + return 1; + + found = 0; + + while ((de = readdir(dp)) != NULL) + { + /* Skip . and .. */ + if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) + continue; + + if (!is_tic_file(de->d_name)) + continue; + + path_join(ticpath, sizeof(ticpath), inbound, de->d_name); + process_one_tic(ticpath, inbound, filebox, copypublic, pubdir, logfile, filelist, newfiles, ticlog); + found++; + } + + closedir(dp); + return 0; +} diff --git a/misc/srifreq.c b/misc/srifreq.c new file mode 100755 index 00000000..810cc780 --- /dev/null +++ b/misc/srifreq.c @@ -0,0 +1,1024 @@ +/* + * srifreq.c -- SRIF-compatible file-request server for binkd + * + * srifreq.c is a part of binkd project + * + * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet + * Licensed under the GNU GPL v2 or later + */ + +#include "portable.h" /* Canonical portable layer */ +#include +#include + +/* Private directory entry (dynamically allocated list) */ +typedef struct PrivDir +{ + char path[MAXPATHLEN]; + char password[64]; + struct PrivDir *next; +} PrivDir; + +/* Node tracking entry for rate limiting */ +typedef struct NodeTrack +{ + char aka[256]; /* Node address (4D/5D) */ + int files; /* Files downloaded in window */ + long bytes; /* Bytes downloaded in window */ + time_t last_time; /* Timestamp of last download */ + struct NodeTrack *next; +} NodeTrack; + +/* Global configuration (filled from --conf file) */ +typedef struct +{ + char pubdir[MAXPATHLEN]; + char logfile[MAXPATHLEN]; + char aliases[MAXPATHLEN]; + char trackfile[MAXPATHLEN]; /* Path to tracking file */ + int maxfiles; /* Max files per node per window (0=unlimited) */ + long maxbytes; /* Max bytes per node per window (0=unlimited) */ + long timewindow; /* Time window in seconds (0=no window) */ + PrivDir *privdirs; /* Linked list, NULL if none */ + NodeTrack *tracking; /* Linked list of tracked nodes */ +} Config; + +/* Alias table -- Loaded from file at startup */ +typedef struct +{ + char name[64]; + char path[MAXPATHLEN]; +} Alias; + +/* SRIF parsing */ +typedef struct +{ + char sysop[128]; + char aka[256]; + char request_list[MAXPATHLEN]; + char response_list[MAXPATHLEN]; + char our_aka[128]; + char caller_id[64]; /* CallerID: IP or phone of remote */ + char password[64]; /* Password: session password */ + int time_limit; /* Time: minutes left, -1 = unlimited */ + long tranx; /* TRANX: remote local time as Unix ts (hex in SRIF) */ + int protected_sess; /* RemoteStatus: 1=PROTECTED, 0=UNPROTECTED */ + int listed; /* SystemStatus: 1=LISTED, 0=UNLISTED */ + int got_request_list; + int got_response_list; +} SRIF; + +static Alias *g_aliases = NULL; +static int g_nalias = 0; +static int g_alias_cap = 0; +static Config g_conf; + +static void config_init(void) +{ + memset(&g_conf, 0, sizeof(g_conf)); + g_conf.privdirs = NULL; + g_conf.tracking = NULL; + g_conf.maxfiles = 0; /* 0 = unlimited */ + g_conf.maxbytes = 0; /* 0 = unlimited */ + g_conf.timewindow = 0; /* 0 = no window */ +} + +static void config_add_private(const char *path, const char *password) +{ + PrivDir *pd = (PrivDir *)malloc(sizeof(PrivDir)); + PrivDir *tail; + + if (!pd) + return; + + safe_strncpy(pd->path, path, (int)sizeof(pd->path)); + safe_strncpy(pd->password, password, (int)sizeof(pd->password)); + + pd->next = NULL; + + /* Append to tail */ + if (!g_conf.privdirs) + g_conf.privdirs = pd; + else + { + tail = g_conf.privdirs; + + while (tail->next) + tail = tail->next; + + tail->next = pd; + } +} + +static void config_free(void) +{ + PrivDir *pd = g_conf.privdirs; + NodeTrack *nt = g_conf.tracking; + + while (pd) + { + PrivDir *next = pd->next; + free(pd); + pd = next; + } + + g_conf.privdirs = NULL; + + while (nt) + { + NodeTrack *next = nt->next; + free(nt); + nt = next; + } + + g_conf.tracking = NULL; + + if (g_aliases) + { + free(g_aliases); + g_aliases = NULL; + g_nalias = 0; + g_alias_cap = 0; + } +} + +static int load_config(const char *path) +{ + FILE *f; + char line[MAX_LINE]; + char key[64], val[MAXPATHLEN], pw[64]; + int n; + + f = fopen(path, "r"); + + if (!f) + { + fprintf(stderr, "srifreq: cannot open config: %s\n", path); + return 0; + } + + while (fgets(line, sizeof(line), f)) + { + /* Strip trailing whitespace and newlines */ + n = (int)strlen(line); + + while (n > 0 && + (line[n - 1] == '\r' || line[n - 1] == '\n' || line[n - 1] == ' ')) + line[--n] = '\0'; + + /* Skip blank and comment lines */ + if (!line[0] || line[0] == '#') + continue; + + key[0] = val[0] = pw[0] = '\0'; + + if (sscanf(line, "%63s %1023s %63s", key, val, pw) < 2) + continue; + + if (strcmp(key, "pubdir") == 0) + safe_strncpy(g_conf.pubdir, val, (int)sizeof(g_conf.pubdir)); + else if (strcmp(key, "logfile") == 0) + safe_strncpy(g_conf.logfile, val, (int)sizeof(g_conf.logfile)); + else if (strcmp(key, "aliases") == 0) + safe_strncpy(g_conf.aliases, val, (int)sizeof(g_conf.aliases)); + else if (strcmp(key, "trackfile") == 0) + safe_strncpy(g_conf.trackfile, val, (int)sizeof(g_conf.trackfile)); + else if (strcmp(key, "maxfiles") == 0) + g_conf.maxfiles = atoi(val); + else if (strcmp(key, "maxsize") == 0) + g_conf.maxbytes = atol(val); + else if (strcmp(key, "timewindow") == 0) + g_conf.timewindow = atol(val); + else if (strcmp(key, "private") == 0 && pw[0]) + config_add_private(val, pw); + } + + fclose(f); + + return 1; +} + +/* tracking_load -- Load node tracking data from file */ +static void tracking_load(void) +{ + FILE *f; + char line[MAX_LINE]; + char aka[256]; + int files; + long bytes; + long timestamp; + NodeTrack *nt; + time_t now = time(NULL); + + if (!g_conf.trackfile[0]) + return; + + f = fopen(g_conf.trackfile, "r"); + + if (!f) + return; + + while (fgets(line, sizeof(line), f)) + { + str_trim(line); + + if (!line[0] || line[0] == '#') + continue; + + if (sscanf(line, "%255s %d %ld %ld", aka, &files, &bytes, ×tamp) != 4) + continue; + + /* Skip if outside time window (also reject future/corrupt timestamps) */ + if (g_conf.timewindow > 0 && (timestamp > now || (now - timestamp) > g_conf.timewindow)) + continue; + + /* Create new tracking entry */ + nt = (NodeTrack *)malloc(sizeof(NodeTrack)); + + if (!nt) + continue; + + safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); + nt->files = files; + nt->bytes = bytes; + nt->last_time = (time_t)timestamp; + nt->next = g_conf.tracking; + g_conf.tracking = nt; + } + + fclose(f); +} + +/* tracking_save -- Save node tracking data to file */ +static void tracking_save(void) +{ + FILE *f; + NodeTrack *nt; + + if (!g_conf.trackfile[0]) + return; + + f = fopen(g_conf.trackfile, "w"); + + if (!f) + return; + + fprintf(f, "# srifreq tracking file - Format: AKA files bytes timestamp\n"); + + for (nt = g_conf.tracking; nt; nt = nt->next) + { + fprintf(f, "%s %d %ld %ld\n", nt->aka, nt->files, nt->bytes, (long)nt->last_time); + } + + fclose(f); +} + +/* tracking_find -- Find tracking entry for a node */ +static NodeTrack *tracking_find(const char *aka) +{ + NodeTrack *nt; + + for (nt = g_conf.tracking; nt; nt = nt->next) + { + if (strcmp(nt->aka, aka) == 0) + return nt; + } + + return NULL; +} + +/* tracking_update -- Update tracking after serving a file */ +static void tracking_update(const char *aka, long filesize) +{ + NodeTrack *nt = tracking_find(aka); + time_t now = time(NULL); + + if (nt) + { + /* Update existing entry */ + nt->files++; + nt->bytes += filesize; + nt->last_time = now; + } + else + { + /* Create new entry */ + nt = (NodeTrack *)malloc(sizeof(NodeTrack)); + + if (nt) + { + safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); + nt->files = 1; + nt->bytes = filesize; + nt->last_time = now; + nt->next = g_conf.tracking; + g_conf.tracking = nt; + } + } +} + +/* tracking_check -- Check if node exceeds limits */ +static int tracking_check(const char *aka, char *msg, int msglen) +{ + NodeTrack *nt = tracking_find(aka); + + if (!nt) + return 1; /* No tracking yet, allow */ + + /* Check max files */ + if (g_conf.maxfiles > 0 && nt->files >= g_conf.maxfiles) + { + snprintf(msg, msglen, "RATE LIMIT: max files (%d) reached for %s", g_conf.maxfiles, aka); + return 0; + } + + /* Check max bytes */ + if (g_conf.maxbytes > 0 && nt->bytes >= g_conf.maxbytes) + { + snprintf(msg, msglen, "RATE LIMIT: max bytes (%ld) reached for %s", g_conf.maxbytes, aka); + return 0; + } + + return 1; /* Within limits */ +} + +/* is_abs_path -- True if path is absolute (POSIX, Win32, AmigaDOS device:) */ +static int is_abs_path(const char *p) +{ + if (!p || !p[0]) + return 0; + + if (p[0] == '/' || p[0] == '\\') + return 1; + +#ifdef AMIGA + if (strchr(p, ':') != NULL) + return 1; +#else + if (p[1] == ':') + return 1; /* C:\ etc. */ +#endif + return 0; +} + +/* + * load_aliases -- Read alias definitions from file + * Lines starting with '#' or empty are skipped + * Format: + */ +static void load_aliases(const char *filepath) +{ + FILE *f; + char line[MAX_LINE]; + char name[64]; + char path[MAXPATHLEN]; + int n; + + /* Free previous aliases and start fresh */ + if (g_aliases) + { + free(g_aliases); + g_aliases = NULL; + } + + g_nalias = 0; + g_alias_cap = 0; + + if (!filepath || !filepath[0] || strcmp(filepath, "-") == 0) + return; + + f = fopen(filepath, "r"); + + if (!f) + { + /*fprintf(stderr, "srifreq: cannot open aliases file: %s\n", filepath);*/ + return; + } + + while (fgets(line, sizeof(line), f)) + { + char *p; + + /* Strip trailing newline */ + n = (int)strlen(line); + + while (n > 0 && (line[n - 1] == '\r' || line[n - 1] == '\n')) + line[--n] = '\0'; + + /* Skip blanks and comments */ + p = line; + + while (*p == ' ' || *p == '\t') + p++; + + if (!*p || *p == '#') + continue; + + name[0] = '\0'; + path[0] = '\0'; + + if (sscanf(p, "%63s %1023[^\n]", name, path) < 2) + continue; + + if (!name[0] || !path[0]) + continue; + + /* Grow array dynamically if needed */ + if (g_nalias >= g_alias_cap) + { + int new_cap = g_alias_cap ? g_alias_cap * 2 : 16; + Alias *new_arr = realloc(g_aliases, (size_t)new_cap * sizeof(Alias)); + + if (!new_arr) + break; + + g_aliases = new_arr; + g_alias_cap = new_cap; + } + + safe_strncpy(g_aliases[g_nalias].name, name, (int)sizeof(g_aliases[g_nalias].name)); + safe_strncpy(g_aliases[g_nalias].path, path, (int)sizeof(g_aliases[g_nalias].path)); + g_nalias++; + } + + fclose(f); + + /*printf("srifreq: loaded %d alias(es) from %s\n", g_nalias, filepath);*/ +} + +/* + * find_alias -- look up name in alias table (case-insensitive) + * Returns the path string, or NULL if not found + */ +static const char *find_alias(const char *name) +{ + char upper[64]; + char aname[64]; + int i; + int n; + + /* Convert name to uppercase */ + n = (int)strlen(name); + + if (n >= (int)sizeof(upper)) + n = (int)sizeof(upper) - 1; + + for (i = 0; i < n; i++) + upper[i] = (char)toupper((unsigned char)name[i]); + + upper[n] = '\0'; + + for (i = 0; i < g_nalias; i++) + { + int an; + an = (int)strlen(g_aliases[i].name); + + if (an >= (int)sizeof(aname)) an = (int)sizeof(aname) - 1; + { + int j; + + for (j = 0; j < an; j++) + aname[j] = (char)toupper((unsigned char)g_aliases[i].name[j]); + + aname[an] = '\0'; + } + + if (strcmp(upper, aname) == 0) + return g_aliases[i].path; + } + + return NULL; +} + +static int parse_srif(const char *path, SRIF *srif) +{ + FILE *f; + char line[MAX_LINE]; + char token[MAX_LINE]; + char value[MAX_LINE]; + + memset(srif, 0, sizeof(SRIF)); + srif->time_limit = -1; /* default: unlimited */ + srif->listed = 1; /* default: assume listed */ + srif->protected_sess = 0; + + f = fopen(path, "r"); + if (!f) + return 0; + + while (fgets(line, sizeof(line), f)) + { + str_trim(line); + if (!line[0]) + continue; + + token[0] = '\0'; + value[0] = '\0'; + + if (sscanf(line, "%1023s %1023[^\n]", token, value) < 1) + continue; + + str_upper(token); + + if (!value[0]) + continue; + + if (!strcmp(token, "SYSOP")) + safe_strncpy(srif->sysop, value, (int)sizeof(srif->sysop)); + else if (!strcmp(token, "AKA") && !srif->aka[0]) + safe_strncpy(srif->aka, value, (int)sizeof(srif->aka)); + else if (!strcmp(token, "REQUESTLIST")) + { + safe_strncpy(srif->request_list, value, MAXPATHLEN); + srif->got_request_list = 1; + } + else if (!strcmp(token, "RESPONSELIST")) + { + safe_strncpy(srif->response_list, value, MAXPATHLEN); + srif->got_response_list = 1; + } + else if (!strcmp(token, "OURAKA")) + safe_strncpy(srif->our_aka, value, (int)sizeof(srif->our_aka)); + else if (!strcmp(token, "PASSWORD")) + safe_strncpy(srif->password, value, (int)sizeof(srif->password)); + else if (!strcmp(token, "CALLERID")) + safe_strncpy(srif->caller_id, value, (int)sizeof(srif->caller_id)); + else if (!strcmp(token, "TIME")) + srif->time_limit = atoi(value); + else if (!strcmp(token, "TRANX")) + { + /* TRANX is a hex Unix timestamp: 5a326682 or 16-digit */ + unsigned long v = 0; + sscanf(value, "%lx", &v); + srif->tranx = (long)v; + } + else if (!strcmp(token, "REMOTESTATUS")) + { + char tmp[32]; + safe_strncpy(tmp, value, (int)sizeof(tmp)); + str_upper(tmp); + srif->protected_sess = (strncmp(tmp, "PROTECTED", 9) == 0) ? 1 : 0; + } + else if (!strcmp(token, "SYSTEMSTATUS")) + { + char tmp[32]; + safe_strncpy(tmp, value, (int)sizeof(tmp)); + str_upper(tmp); + srif->listed = (strncmp(tmp, "LISTED", 6) == 0) ? 1 : 0; + } + } + + fclose(f); + + return srif->got_request_list; +} + +/* Logging */ +static void do_log(const char *logpath, const char *msg) +{ + FILE *lf; + time_t t; + struct tm tm; + char timestamp[32]; + + if (!logpath || !logpath[0] || strcmp(logpath, "-") == 0) + return; + + t = time(NULL); + safe_localtime(&t, &tm); + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); + + lf = fopen(logpath, "a"); + + if (lf) + { + fprintf(lf, "[%s] srifreq: %s\n", timestamp, msg); + fclose(lf); + } +} + +/* serve_one -- resolve one request name, check password/timestamp/update + * write to response list. Returns 1 if served + */ +static int serve_one(const char *req_name, const char *found_path, const char *req_pass, long req_newer, int req_update, const SRIF *srif, FILE *rsp_f, const char *log_path, char *logbuf, int logbuf_size) +{ + long fsize; + + /* Check rate limits before serving */ + if (g_conf.trackfile[0] && !tracking_check(srif->aka, logbuf, logbuf_size)) + { + do_log(log_path, logbuf); + return 0; + } + + /* RemoteStatus: if session is unprotected and a password is required, deny */ + if (req_pass[0] && !srif->protected_sess) + { + snprintf(logbuf, logbuf_size, "PASSWORD DENY (unprotected session): %s", req_name); + do_log(log_path, logbuf); + return 0; + } + + /* Password check: !pw must match SRIF PASSWORD (case-insensitive) */ + if (req_pass[0]) + { + char rp[64], sp[64]; + int i; + + safe_strncpy(rp, req_pass, (int)sizeof(rp)); + safe_strncpy(sp, srif->password, (int)sizeof(sp)); + + for (i = 0; rp[i]; i++) + rp[i] = (char)toupper((unsigned char)rp[i]); + + for (i = 0; sp[i]; i++) + sp[i] = (char)toupper((unsigned char)sp[i]); + + if (strcmp(rp, sp) != 0) + { + snprintf(logbuf, logbuf_size, "PASSWORD FAIL: %s", req_name); + do_log(log_path, logbuf); + + return 0; + } + } + + /* Update request (U flag): serve only if file is newer than TRANX */ + if (req_update && srif->tranx > 0) + { + long mtime = get_file_mtime(found_path); + + if (mtime <= srif->tranx) + { + snprintf(logbuf, logbuf_size, "NOT UPDATED: %s (mtime=%ld tranx=%ld)", req_name, mtime, srif->tranx); + do_log(log_path, logbuf); + return 0; + } + } + + /* Timestamp check: +ts means "only if file is newer than ts" */ + if (req_newer > 0) + { + long mtime = get_file_mtime(found_path); + + if (mtime <= req_newer) + { + snprintf(logbuf, logbuf_size, "NOT NEWER: %s (mtime=%ld req=%ld)", req_name, mtime, req_newer); + + do_log(log_path, logbuf); + return 0; + } + } + + snprintf(logbuf, logbuf_size, "FOUND: %s -> %s", req_name, found_path); + do_log(log_path, logbuf); + + if (rsp_f) + fprintf(rsp_f, "+%s\r\n", found_path); + + /* Update tracking after successful serve */ + if (g_conf.trackfile[0]) + { + fsize = get_file_size(found_path); + + if (fsize < 0) + fsize = 0; + + tracking_update(srif->aka, fsize); + } + + return 1; +} + +int main(int argc, char *argv[]) +{ + const char *srif_path; + SRIF srif; + FILE *req_f; + FILE *rsp_f; + char line[MAX_LINE]; + char req_name[MAX_LINE]; + char req_pass[64]; + long req_newer; + int req_update; + char found_path[MAXPATHLEN]; + char logbuf[MAXPATHLEN * 4 + 128]; + int found_count; + + config_init(); + + /* --conf */ + if (argc >= 4 && strcmp(argv[1], "--conf") == 0) + { + if (!load_config(argv[2])) + return 1; + + srif_path = argv[3]; + } + else + { + fprintf(stderr, "Usage:\n" + " srifreq --conf \n" + "\n" + "Config file keys: pubdir, logfile, aliases, private " + "\n"); + + return 1; + } + + if (!g_conf.pubdir[0]) + { + fprintf(stderr, "srifreq: pubdir not set\n"); + config_free(); + return 1; + } + + /* Load tracking data if configured */ + tracking_load(); + + load_aliases(g_conf.aliases[0] ? g_conf.aliases : NULL); + + snprintf(logbuf, sizeof(logbuf), "processing SRIF: %s", srif_path); + do_log(g_conf.logfile, logbuf); + + if (!parse_srif(srif_path, &srif)) + { + snprintf(logbuf, sizeof(logbuf), "ERROR: cannot parse SRIF or missing RequestList: %s", srif_path); + do_log(g_conf.logfile, logbuf); + fprintf(stderr, "srifreq: %s\n", logbuf); + config_free(); + return 1; + } + + /* SystemStatus: deny unlisted systems entirely */ + if (!srif.listed) + { + snprintf(logbuf, sizeof(logbuf), "DENIED: system is UNLISTED (aka: %s)", srif.aka); + do_log(g_conf.logfile, logbuf); + config_free(); + return 1; + } + + snprintf(logbuf, sizeof(logbuf), "sysop: %s aka: %s status: %s%s caller: %s req: %s", srif.sysop, srif.aka, srif.protected_sess ? "PROTECTED" : "UNPROTECTED", srif.tranx ? " (TRANX)" : "", srif.caller_id[0] ? srif.caller_id : "?", srif.request_list); + do_log(g_conf.logfile, logbuf); + + /* Log rate limiting status if active */ + if (g_conf.trackfile[0] && (g_conf.maxfiles > 0 || g_conf.maxbytes > 0)) + { + snprintf(logbuf, sizeof(logbuf), "rate limits: maxfiles=%d maxbytes=%ld window=%lds", g_conf.maxfiles, g_conf.maxbytes, g_conf.timewindow); + do_log(g_conf.logfile, logbuf); + } + + req_f = fopen(srif.request_list, "r"); + + if (!req_f) + { + snprintf(logbuf, sizeof(logbuf), "WARN: RequestList not available: %s", srif.request_list); + do_log(g_conf.logfile, logbuf); + config_free(); + return 0; + } + + rsp_f = NULL; + + if (srif.got_response_list && srif.response_list[0]) + { + rsp_f = fopen(srif.response_list, "w"); + + if (!rsp_f) + { + snprintf(logbuf, sizeof(logbuf), "WARN: cannot create ResponseList: %s", srif.response_list); + do_log(g_conf.logfile, logbuf); + } + } + + found_count = 0; + + /* Check and update track file */ + while (fgets(line, sizeof(line), req_f)) + { + char *p; + const char *alias_path; + + str_trim(line); + + if (!line[0] || line[0] == ';' || line[0] == '#') + continue; + + /* Parse: filename [!password] [+timestamp] [U] */ + req_name[0] = '\0'; + req_pass[0] = '\0'; + req_newer = 0; + req_update = 0; + + if (sscanf(line, "%1023s", req_name) < 1) + continue; + + /* Skip URLs */ + if (strncmp(req_name, "http", 4) == 0 || strncmp(req_name, "ftp", 3) == 0) + continue; + + /* Parse modifiers from the rest of the line */ + p = strstr(line, req_name); + + if (p) + p += strlen(req_name); + else + p = line + strlen(line); + + while (*p) + { + while (*p == ' ' || *p == '\t') + p++; + + if (*p == '!') + { + /* !password */ + int i = 0; + p++; + + while (*p && *p != ' ' && *p != '\t' && i < (int)sizeof(req_pass) - 1) + req_pass[i++] = *p++; + + req_pass[i] = '\0'; + } + else if (*p == '+') + { + /* +unix_timestamp */ + p++; + req_newer = atol(p); + + while (*p && *p != ' ' && *p != '\t') + p++; + } + else if (*p == 'U' && (p[1] == '\0' || p[1] == ' ' || p[1] == '\t')) + { + /* U = update request */ + req_update = 1; + p++; + } + else if (*p) + p++; /* Skip unknown token */ + } + + found_path[0] = '\0'; + + /* Check alias table first */ + alias_path = find_alias(req_name); + + if (alias_path) + { + if (is_abs_path(alias_path)) + safe_strncpy(found_path, alias_path, MAXPATHLEN); + else + path_join(found_path, MAXPATHLEN, g_conf.pubdir, alias_path); + + if (path_exists(found_path)) + found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); + else + { + snprintf(logbuf, sizeof(logbuf), "NOT FOUND (alias): %s -> %s", req_name, found_path); + do_log(g_conf.logfile, logbuf); + } + + continue; + } + + /* Wildcard: scan pubdir and all privdirs whose password matches */ + if (is_wildcard(req_name)) + { + DIR *dp; + struct dirent *de; + PrivDir *pd; + + /* Scan pubdir (no password needed) */ + dp = opendir(g_conf.pubdir); + if (dp) + { + while ((de = readdir(dp)) != NULL) + { + if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) + continue; + + if (!wildmatch(req_name, de->d_name)) + continue; + + path_join(found_path, MAXPATHLEN, g_conf.pubdir, de->d_name); + + if (path_exists(found_path)) + found_count += serve_one(de->d_name, found_path, "", req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); + } + + closedir(dp); + } + + /* Scan matching privdirs */ + for (pd = g_conf.privdirs; pd; pd = pd->next) + { + char rp[64], pp[64]; + int ci; + + safe_strncpy(rp, req_pass, (int)sizeof(rp)); + safe_strncpy(pp, pd->password, (int)sizeof(pp)); + + for (ci = 0; rp[ci]; ci++) + rp[ci] = (char)toupper((unsigned char)rp[ci]); + + for (ci = 0; pp[ci]; ci++) + pp[ci] = (char)toupper((unsigned char)pp[ci]); + + if (strcmp(rp, pp) != 0) + continue; + + dp = opendir(pd->path); + + if (!dp) + continue; + + while ((de = readdir(dp)) != NULL) + { + if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) + continue; + + if (!wildmatch(req_name, de->d_name)) + continue; + + path_join(found_path, MAXPATHLEN, pd->path, de->d_name); + + if (path_exists(found_path)) + found_count += serve_one(de->d_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); + } + + closedir(dp); + } + + continue; + } + + /* Plain filename: try pubdir first, then privdirs if password given */ + path_join(found_path, MAXPATHLEN, g_conf.pubdir, req_name); + + if (path_exists(found_path)) + { + found_count += + serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); + } + else if (req_pass[0]) + { + /* Try each private dir whose password matches */ + PrivDir *pd; + int served = 0; + + for (pd = g_conf.privdirs; pd && !served; pd = pd->next) + { + char rp[64], pp[64]; + int ci; + + safe_strncpy(rp, req_pass, (int)sizeof(rp)); + safe_strncpy(pp, pd->password, (int)sizeof(pp)); + + for (ci = 0; rp[ci]; ci++) + rp[ci] = (char)toupper((unsigned char)rp[ci]); + + for (ci = 0; pp[ci]; ci++) + pp[ci] = (char)toupper((unsigned char)pp[ci]); + + if (strcmp(rp, pp) != 0) + continue; + + path_join(found_path, MAXPATHLEN, pd->path, req_name); + + if (path_exists(found_path)) + { + found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); + served = 1; + } + } + if (!served) + { + snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s", req_name); + do_log(g_conf.logfile, logbuf); + } + } + else + { + snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s (pub: %s)", req_name, g_conf.pubdir); + do_log(g_conf.logfile, logbuf); + } + } + + fclose(req_f); + + if (rsp_f) + fclose(rsp_f); + + snprintf(logbuf, sizeof(logbuf), "done: %d file(s) found", found_count); + do_log(g_conf.logfile, logbuf); + + /* Save tracking data */ + tracking_save(); + + config_free(); + + return (found_count > 0) ? 0 : 1; +} diff --git a/mkfls/amiga/Makefile.analyze.bebbo b/mkfls/amiga/Makefile.analyze.bebbo new file mode 100644 index 00000000..007e9897 --- /dev/null +++ b/mkfls/amiga/Makefile.analyze.bebbo @@ -0,0 +1,96 @@ +# Makefile.analyze.bebbo -- Static analysis for Amiga bebbo (GCC 6.5.0b) +# Includes amiga/ code with bebbo-specific defines +# Usage: make -f Makefile.analyze.bebbo + +# All sources including Amiga-specific +ALL_SRCS = \ + binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ + bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ + readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ + setpttl.c https.c md5b.c crypt.c compress.c \ + amiga/rename.c amiga/getfree.c amiga/bsdsock.c amiga/dirent.c \ + amiga/utime.c amiga/rfc2553_amiga.c amiga/sem.c amiga/evloop.c \ + amiga/sock.c amiga/session.c \ + misc/decompress.c misc/freq.c misc/process_tic.c misc/srifreq.c misc/nodelist.c + +INCLUDES = -I. -Iamiga + +# bebbo-specific defines (GCC 6.5.0b, HAS native snprintf) +BEBBO_DEFINES = \ + -DAMIGA \ + -DHAVE_SOCKLEN_T \ + -DHAVE_INTMAX_T \ + -DHAVE_SNPRINTF \ + -DHAVE_GETOPT \ + -DHAVE_UNISTD_H \ + -DHAVE_SYS_TIME_H \ + -DHAVE_SYS_PARAM_H \ + -DHAVE_SYS_IOCTL_H \ + -DHAVE_NETINET_IN_H \ + -DHAVE_NETDB_H \ + -DHAVE_ARPA_INET_H \ + -DHAVE_STDARG_H \ + -DHAVE_VSNPRINTF \ + -DWITH_ZLIB + +CPPCHECK_FLAGS = \ + --enable=all \ + --inconclusive \ + --std=c89 \ + --quiet \ + --suppress=missingIncludeSystem \ + --suppress=unusedFunction \ + --suppress=checkersReport \ + $(INCLUDES) + +# Log files +LOG_DIR = analysis_logs +CPPCHECK_LOG = $(LOG_DIR)/cppcheck_bebbo.log +CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_bebbo.log + +.PHONY: all cppcheck clang-tidy analyze clean + +all: analyze + +cppcheck: + @mkdir -p $(LOG_DIR) + @echo "=== cppcheck Amiga bebbo (GCC 6.5.0b) ===" + @echo "Defines: bebbo, HAS native snprintf, no snprintf.c needed" + @echo "Saving output to: $(CPPCHECK_LOG)" + @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) $(ALL_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true + @echo "=== done ===" + @echo "Log saved: $(CPPCHECK_LOG)" + +clang-tidy: + @mkdir -p $(LOG_DIR) + @echo "=== clang-tidy Amiga bebbo ===" + @echo "Note: Some Amiga headers may not resolve on Linux host" + @echo "Saving output to: $(CLANG_TIDY_LOG)" + @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) + @for src in binkd.c readcfg.c amiga/evloop.c amiga/session.c; do \ + echo "" >> $(CLANG_TIDY_LOG); \ + echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ + clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ + $(INCLUDES) $(BEBBO_DEFINES) -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ + done + @echo "=== done ===" + @echo "Log saved: $(CLANG_TIDY_LOG)" + +analyze: cppcheck clang-tidy + +# Focus on Amiga-specific code only +amiga-only: + @echo "=== cppcheck Amiga-specific code only (bebbo) ===" + @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) \ + amiga/*.c 2>&1 || true + +# Check what differs between ADE and bebbo +diff-defines: + @echo "=== ADE vs bebbo define differences ===" + @echo "ADE only: -DHAVE_VSNPRINTF (no -DHAVE_SNPRINTF)" + @echo "bebbo: -DHAVE_SNPRINTF -DHAVE_VSNPRINTF" + @echo "" + @echo "ADE needs snprintf.c, bebbo does not" + +clean: + @true diff --git a/mkfls/amiga/Makefile.bebbo b/mkfls/amiga/Makefile.bebbo new file mode 100644 index 00000000..0966c542 --- /dev/null +++ b/mkfls/amiga/Makefile.bebbo @@ -0,0 +1,208 @@ +CC = m68k-amigaos-gcc + +# Common flags for compiler and linker +COMMON_CFLAGS = -Wall -Wextra -Wunused -Wunused-function -Wunused-variable -Wmissing-prototypes -Wno-pointer-sign -ffunction-sections -fdata-sections -noixemul +ARCH_FLAGS = -O -m68000 -msoft-float -fomit-frame-pointer + +CFLAGS = $(DEFINES) $(COMMON_CFLAGS) $(ARCH_FLAGS) +LIBS = -lz -lm -lamiga +LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_gcc.map -s + +# Tools use same flags as main binary +TOOL_CFLAGS = $(DEFINES) $(COMMON_CFLAGS) -O -m68000 -msoft-float -fomit-frame-pointer -I misc +TOOL_LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_tools.map -s + +OBJDIR = objs + +DEFINES = \ + -DAMIGA \ + -DHAVE_SOCKLEN_T \ + -DHAVE_INTMAX_T \ + -DHAVE_SNPRINTF \ + -DHAVE_GETOPT \ + -DHAVE_UNISTD_H \ + -DHAVE_SYS_TIME_H \ + -DHAVE_SYS_PARAM_H \ + -DHAVE_SYS_IOCTL_H \ + -DHAVE_NETINET_IN_H \ + -DHAVE_NETDB_H \ + -DHAVE_ARPA_INET_H \ + -DHTTPS \ + -DAMIGADOS_4D_OUTBOUND \ + -DHAVE_STDARG_H \ + -DHAVE_VSNPRINTF \ + -DWITH_ZLIB \ + -DOS=\"Amiga\" \ + -I. \ + -Iamiga + +SRCS = \ + binkd.c \ + readcfg.c \ + tools.c \ + ftnaddr.c \ + ftnq.c \ + client.c \ + server.c \ + protocol.c \ + bsy.c \ + inbound.c \ + breaksig.c \ + branch.c \ + amiga/rename.c \ + amiga/getfree.c \ + amiga/bsdsock.c \ + amiga/dirent.c \ + amiga/utime.c \ + amiga/rfc2553_amiga.c \ + amiga/sem.c \ + amiga/evloop.c \ + amiga/sock.c \ + amiga/session.c \ + bsycleanup.c \ + amiga/proto_amiga.c \ + ftndom.c \ + ftnnode.c \ + srif.c \ + pmatch.c \ + readflo.c \ + prothlp.c \ + iptools.c \ + run.c \ + binlog.c \ + exitproc.c \ + getw.c \ + xalloc.c \ + setpttl.c \ + https.c \ + md5b.c \ + crypt.c \ + compress.c + +OBJS = \ + $(OBJDIR)/binkd.o \ + $(OBJDIR)/readcfg.o \ + $(OBJDIR)/tools.o \ + $(OBJDIR)/ftnaddr.o \ + $(OBJDIR)/ftnq.o \ + $(OBJDIR)/client.o \ + $(OBJDIR)/server.o \ + $(OBJDIR)/protocol.o \ + $(OBJDIR)/bsy.o \ + $(OBJDIR)/inbound.o \ + $(OBJDIR)/breaksig.o \ + $(OBJDIR)/branch.o \ + $(OBJDIR)/rename.o \ + $(OBJDIR)/getfree.o \ + $(OBJDIR)/bsdsock.o \ + $(OBJDIR)/dirent.o \ + $(OBJDIR)/utime.o \ + $(OBJDIR)/rfc2553_amiga.o \ + $(OBJDIR)/sem.o \ + $(OBJDIR)/evloop.o \ + $(OBJDIR)/sock.o \ + $(OBJDIR)/session.o \ + $(OBJDIR)/bsycleanup.o \ + $(OBJDIR)/proto_amiga.o \ + $(OBJDIR)/ftndom.o \ + $(OBJDIR)/ftnnode.o \ + $(OBJDIR)/srif.o \ + $(OBJDIR)/pmatch.o \ + $(OBJDIR)/readflo.o \ + $(OBJDIR)/prothlp.o \ + $(OBJDIR)/iptools.o \ + $(OBJDIR)/run.o \ + $(OBJDIR)/binlog.o \ + $(OBJDIR)/exitproc.o \ + $(OBJDIR)/getw.o \ + $(OBJDIR)/xalloc.o \ + $(OBJDIR)/setpttl.o \ + $(OBJDIR)/https.o \ + $(OBJDIR)/md5b.o \ + $(OBJDIR)/crypt.o \ + $(OBJDIR)/compress.o + +all: binkd decompress process_tic freq srifreq nodelist + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(OBJDIR)/%.o: %.c + $(CC) -c $(CFLAGS) $< -o $@ + +binkd: $(OBJDIR) $(OBJS) + $(CC) $(CFLAGS) -o binkd $(OBJS) $(LIBS) $(LDFLAGS) + +# ---------- Utility tools (stand-alone, multi-platform) ---------- +# TOOL_CFLAGS and TOOL_LDFLAGS defined above + +decompress: + $(CC) $(TOOL_CFLAGS) -o decompress misc/decompress.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) + +process_tic: + $(CC) $(TOOL_CFLAGS) -o process_tic misc/process_tic.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) + +freq: + $(CC) $(TOOL_CFLAGS) -o freq misc/freq.c misc/portable.c $(TOOL_LDFLAGS) + +srifreq: + $(CC) $(TOOL_CFLAGS) -o srifreq misc/srifreq.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) + +nodelist: + $(CC) $(TOOL_CFLAGS) -o nodelist misc/nodelist.c misc/portable.c $(TOOL_LDFLAGS) + +install: all clean + +clean: + rm -f *.[bo] *.BAK *.core *.obj *.err *~ core + rm -rf $(OBJDIR) + rm -f binkd decompress process_tic freq srifreq nodelist + +# ---------- Explicit rules for amiga/ objects ---------- +$(OBJDIR)/rename.o: amiga/rename.c + $(CC) -c $(CFLAGS) amiga/rename.c -o $(OBJDIR)/rename.o + +$(OBJDIR)/getfree.o: amiga/getfree.c + $(CC) -c $(CFLAGS) amiga/getfree.c -o $(OBJDIR)/getfree.o + +$(OBJDIR)/sem.o: amiga/sem.c + $(CC) -c $(CFLAGS) amiga/sem.c -o $(OBJDIR)/sem.o + +$(OBJDIR)/bsdsock.o: amiga/bsdsock.c + $(CC) -c $(CFLAGS) amiga/bsdsock.c -o $(OBJDIR)/bsdsock.o + +$(OBJDIR)/dirent.o: amiga/dirent.c + $(CC) -c $(CFLAGS) amiga/dirent.c -o $(OBJDIR)/dirent.o + +$(OBJDIR)/utime.o: amiga/utime.c + $(CC) -c $(CFLAGS) amiga/utime.c -o $(OBJDIR)/utime.o + +$(OBJDIR)/rfc2553_amiga.o: amiga/rfc2553_amiga.c + $(CC) -c $(CFLAGS) amiga/rfc2553_amiga.c -o $(OBJDIR)/rfc2553_amiga.o + +$(OBJDIR)/evloop.o: amiga/evloop.c + $(CC) -c $(CFLAGS) amiga/evloop.c -o $(OBJDIR)/evloop.o + +$(OBJDIR)/sock.o: amiga/sock.c + $(CC) -c $(CFLAGS) amiga/sock.c -o $(OBJDIR)/sock.o + +$(OBJDIR)/session.o: amiga/session.c + $(CC) -c $(CFLAGS) amiga/session.c -o $(OBJDIR)/session.o + +$(OBJDIR)/bsycleanup.o: bsycleanup.c + $(CC) -c $(CFLAGS) bsycleanup.c -o $(OBJDIR)/bsycleanup.o + +$(OBJDIR)/proto_amiga.o: amiga/proto_amiga.c + $(CC) -c $(CFLAGS) amiga/proto_amiga.c -o $(OBJDIR)/proto_amiga.o + +depend Makefile.dep: Makefile + $(CC) -MM $(CFLAGS) $(SRCS) $(SYS) | \ + awk '{ if ($$1 != prev) { if (rec != "") print rec; \ + rec = $$0; prev = $$1; } \ + else { if (length(rec $$2) > 78) { print rec; rec = $$0; } \ + else rec = rec " " $$2 } } \ + END { print rec }' | tee Makefile.dep + +-include Makefile.dep + +.PHONY: all binkd decompress process_tic freq srifreq nodelist clean install diff --git a/mkfls/nt95-mingw/Makefile b/mkfls/nt95-mingw/Makefile old mode 100644 new mode 100755 index 05025fc6..42ff3fe6 --- a/mkfls/nt95-mingw/Makefile +++ b/mkfls/nt95-mingw/Makefile @@ -38,7 +38,8 @@ SRCS= binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ setpttl.c https.c md5b.c crypt.c getopt.c nt/breaksig.c nt/getfree.c \ nt/sem.c nt/TCPErr.c nt/WSock.c nt/w32tools.c nt/tray.c snprintf.c \ ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c ntlm/des_enc.c \ - ntlm/helpers.c + ntlm/helpers.c \ + bsycleanup.c RES= nt/binkdres.rc @@ -221,7 +222,32 @@ BINKDEXE = $(BINKDNAME).exe OBJS=$(addprefix $(OBJDIR)/,$(patsubst %.c,%.o, $(SRCS))) RESOBJS=$(addprefix $(OBJDIR)/, $(patsubst %.rc,%.o, $(RES))) -.PHONY: all printinfo install html clean distclean makedirs +# ---------- Utility tools (stand-alone) ---------- +TOOL_CFLAGS = -O2 -Wall -DWIN32 -I. -I misc + +utils: decompress process_tic freq srifreq nodelist + +decompress: + @echo Compiling decompress... + @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/decompress.exe misc/decompress.c misc/portable.c + +process_tic: + @echo Compiling process_tic... + @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/process_tic.exe misc/process_tic.c misc/portable.c + +freq: + @echo Compiling freq... + @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/freq.exe misc/freq.c misc/portable.c + +srifreq: + @echo Compiling srifreq... + @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/srifreq.exe misc/srifreq.c misc/portable.c + +nodelist: + @echo Compiling nodelist... + @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/nodelist.exe misc/nodelist.c misc/portable.c + +.PHONY: all printinfo install html clean distclean makedirs utils decompress process_tic freq srifreq nodelist all: printinfo makedirs $(OUTDIR)/$(BINKDEXE) diff --git a/mkfls/nt95-msvc/Makefile b/mkfls/nt95-msvc/Makefile old mode 100644 new mode 100755 index 62d5258c..70cdc9db --- a/mkfls/nt95-msvc/Makefile +++ b/mkfls/nt95-msvc/Makefile @@ -327,7 +327,32 @@ PERLDLL=/delayload:perl$(PERLV).dll BINKDEXE = $(BINKDNAME).exe -all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) +all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) utils + +TOOL_CFLAGS = -nologo -W3 -O2 -DWIN32 -DVISUALCPP -I. -I misc +TOOL_CC = $(CC) $(TOOL_CFLAGS) + +utils: "$(OUTDIR)\decompress.exe" "$(OUTDIR)\process_tic.exe" "$(OUTDIR)\freq.exe" "$(OUTDIR)\srifreq.exe" "$(OUTDIR)\nodelist.exe" + +"$(OUTDIR)\decompress.exe": misc\decompress.c misc\portable.c nt\dirwin32.c + @echo Compiling decompress... + @$(TOOL_CC) -Fe"$(OUTDIR)\decompress.exe" misc\decompress.c misc\portable.c nt\dirwin32.c + +"$(OUTDIR)\process_tic.exe": misc\process_tic.c misc\portable.c nt\dirwin32.c + @echo Compiling process_tic... + @$(TOOL_CC) -Fe"$(OUTDIR)\process_tic.exe" misc\process_tic.c misc\portable.c nt\dirwin32.c + +"$(OUTDIR)\freq.exe": misc\freq.c misc\portable.c + @echo Compiling freq... + @$(TOOL_CC) -Fe"$(OUTDIR)\freq.exe" misc\freq.c misc\portable.c + +"$(OUTDIR)\srifreq.exe": misc\srifreq.c misc\portable.c nt\dirwin32.c + @echo Compiling srifreq... + @$(TOOL_CC) -Fe"$(OUTDIR)\srifreq.exe" misc\srifreq.c misc\portable.c nt\dirwin32.c + +"$(OUTDIR)\nodelist.exe": misc\nodelist.c misc\portable.c + @echo Compiling nodelist... + @$(TOOL_CC) -Fe"$(OUTDIR)\nodelist.exe" misc\nodelist.c misc\portable.c printinfo: @echo on diff --git a/mkfls/os2-emx/Makefile b/mkfls/os2-emx/Makefile old mode 100644 new mode 100755 index 20f213f3..9a6d1dea --- a/mkfls/os2-emx/Makefile +++ b/mkfls/os2-emx/Makefile @@ -13,7 +13,7 @@ CFLAGS=$(DEFINES) -Wall -funsigned-char -Wno-char-subscripts LFLAGS=-Los2 LIBS=-lsocket -lresolv NTLM_SRC=ntlm/des_enc.c ntlm/helpers.c ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c -SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c ${NTLM_SRC} +SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c bsycleanup.c ${NTLM_SRC} TARGET=binkd2emx #PERLDIR=../perl5.00553/os2 @@ -122,7 +122,33 @@ OBJS=${SRCS:.c=.o} TARGET:=$(TARGET).exe -all: $(TARGET) +all: $(TARGET) utils + +TOOL_CFLAGS = $(CFLAGS) -I. -I misc + +utils: decompress process_tic freq srifreq nodelist + +decompress: misc/decompress.c + @echo Compiling decompress... + @$(CC) $(TOOL_CFLAGS) -o decompress.exe misc/decompress.c misc/portable.c os2/dirent.c + +process_tic: misc/process_tic.c + @echo Compiling process_tic... + @$(CC) $(TOOL_CFLAGS) -o process_tic.exe misc/process_tic.c misc/portable.c os2/dirent.c + +freq: misc/freq.c + @echo Compiling freq... + @$(CC) $(TOOL_CFLAGS) -o freq.exe misc/freq.c misc/portable.c + +srifreq: misc/srifreq.c + @echo Compiling srifreq... + @$(CC) $(TOOL_CFLAGS) -o srifreq.exe misc/srifreq.c misc/portable.c os2/dirent.c + +nodelist: misc/nodelist.c + @echo Compiling nodelist... + @$(CC) $(TOOL_CFLAGS) -o nodelist.exe misc/nodelist.c misc/portable.c + +.PHONY: utils decompress process_tic freq srifreq nodelist .c.o: @echo Compiling $*.c... diff --git a/mkfls/unix/Makefile.analyze.unix b/mkfls/unix/Makefile.analyze.unix new file mode 100644 index 00000000..4f3cc438 --- /dev/null +++ b/mkfls/unix/Makefile.analyze.unix @@ -0,0 +1,65 @@ +# Makefile.analyze.unix -- Static analysis for Unix/Linux code only +# Excludes Amiga-specific code (amiga/ directory) +# Usage: make -f Makefile.analyze.unix + +# Unix-portable sources only (no amiga/ directory) +UNIX_SRCS = \ + binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ + bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ + readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ + setpttl.c https.c md5b.c crypt.c compress.c + +# Unix-specific files (if any) +UNIX_SYS_SRCS = \ + unix/getwd.c unix/lock.c unix/tcperr.c + +INCLUDES = -I. -Iunix + +CPPCHECK_FLAGS = \ + --enable=all \ + --inconclusive \ + --std=c89 \ + --quiet \ + --suppress=missingIncludeSystem \ + --suppress=unusedFunction \ + $(INCLUDES) + +# Log files +LOG_DIR = analysis_logs +CPPCHECK_LOG = $(LOG_DIR)/cppcheck_unix.log +CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_unix.log + +.PHONY: all cppcheck clang-tidy analyze clean + +all: analyze + +cppcheck: + @mkdir -p $(LOG_DIR) + @echo "=== cppcheck Unix/Linux code ===" + @echo "Saving output to: $(CPPCHECK_LOG)" + @cppcheck $(CPPCHECK_FLAGS) $(UNIX_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true + @echo "=== done ===" + @echo "Log saved: $(CPPCHECK_LOG)" + +clang-tidy: + @mkdir -p $(LOG_DIR) + @echo "=== clang-tidy Unix/Linux code ===" + @echo "Saving output to: $(CLANG_TIDY_LOG)" + @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) + @for src in $(UNIX_SRCS); do \ + echo "" >> $(CLANG_TIDY_LOG); \ + echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ + clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ + $(INCLUDES) -DUNIX -DHAVE_SNPRINTF -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ + done + @echo "=== done ===" + @echo "Log saved: $(CLANG_TIDY_LOG)" + +analyze: cppcheck clang-tidy + +quick: + @echo "=== Quick error check (Unix) ===" + @cppcheck --enable=error --std=c89 --quiet $(INCLUDES) $(UNIX_SRCS) 2>&1 || true + +clean: + @true diff --git a/mkfls/unix/Makefile.in b/mkfls/unix/Makefile.in old mode 100644 new mode 100755 index a37338bb..5e493e6d --- a/mkfls/unix/Makefile.in +++ b/mkfls/unix/Makefile.in @@ -12,17 +12,17 @@ DATADIR=@prefix@/share MANDIR=$(DATADIR)/man DOCDIR=$(DATADIR)/doc/$(APPL) -SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c @OPT_SRC@ +SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c bsycleanup.c @OPT_SRC@ OBJS=${SRCS:.c=.o} AUTODEFS=@DEFS@ AUTOLIBS=@LIBS@ -DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" +DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -DPROTOTYPES CPPFLAGS=@CPPFLAGS@ CFLAGS=@CFLAGS@ LDFLAGS=@LDFLAGS@ LIBS=$(AUTOLIBS) -all: compile banner +all: compile banner utils compile: $(APPL) @@ -48,6 +48,32 @@ banner: @echo " run \`configure --prefix=/another/path' and go to step 1. " @echo +utils: decompress process_tic freq srifreq nodelist + +TOOL_CFLAGS = $(DEFINES) $(CPPFLAGS) $(CFLAGS) -I. -I misc + +decompress: misc/decompress.c misc/portable.c misc/portable.h + @echo Compiling decompress... + @$(CC) $(TOOL_CFLAGS) -o $@ misc/decompress.c misc/portable.c + +process_tic: misc/process_tic.c misc/portable.c misc/portable.h + @echo Compiling process_tic... + @$(CC) $(TOOL_CFLAGS) -o $@ misc/process_tic.c misc/portable.c + +freq: misc/freq.c misc/portable.c misc/portable.h + @echo Compiling freq... + @$(CC) $(TOOL_CFLAGS) -o $@ misc/freq.c misc/portable.c + +srifreq: misc/srifreq.c misc/portable.c misc/portable.h + @echo Compiling srifreq... + @$(CC) $(TOOL_CFLAGS) -o $@ misc/srifreq.c misc/portable.c + +nodelist: misc/nodelist.c misc/portable.c + @echo Compiling nodelist... + @$(CC) $(TOOL_CFLAGS) -o $@ misc/nodelist.c misc/portable.c + +.PHONY: decompress process_tic freq srifreq nodelist + .version: $(APPL) @./$(APPL) -v | $(AWK) '{ print $$2; }' > $@ @@ -66,6 +92,7 @@ install: compile .version clean: rm -f *.[bo] unix/*.[bo] ntlm/*.[bo] *.BAK *.core *.obj *.err rm -f *~ core config.cache config.log config.status + rm -f decompress process_tic freq srifreq nodelist cleanall: clean rm -f $(APPL) Makefile Makefile.dep Makefile.in diff --git a/protocol.c b/protocol.c old mode 100644 new mode 100755 index 677cb445..becac87e --- a/protocol.c +++ b/protocol.c @@ -45,12 +45,19 @@ #include "md5b.h" #include "crypt.h" #include "compress.h" +#ifdef AMIGA +#include "amiga/proto_amiga.h" +#endif #ifdef WITH_PERL #include "perlhooks.h" #endif #include "rfc2553.h" +#if defined(HAVE_THREADS) || defined(AMIGA) +extern MUTEXSEM lsem; +#endif + /* define to enable val's code for -ip checks (default is gul's code) */ #undef VAL_STYLE #ifdef VAL_STYLE @@ -63,7 +70,7 @@ static char *scommand[] = {"NUL", "ADR", "PWD", "FILE", "OK", "EOB", /* * Fills <> with initial values, allocates buffers, etc. */ -static int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) +int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) { char val[4]; socklen_t lval; @@ -126,6 +133,12 @@ static int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN #endif setsockopts (state->s_in = socket_in); setsockopts (state->s_out = socket_out); + +#if defined(AMIGA) + setsockopts_amiga(socket_in, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); + setsockopts_amiga(socket_out, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); +#endif + TF_ZERO (&state->in); TF_ZERO (&state->out); TF_ZERO (&state->flo); @@ -181,7 +194,7 @@ static int close_partial (STATE *state, BINKD_CONFIG *config) /* * Clears protocol buffers and queues, closes files, etc. */ -static int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) +int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) { int i; @@ -225,7 +238,7 @@ static int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) } /* Process rcvdlist */ -static FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) +FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) { int i; @@ -315,7 +328,7 @@ static void current_file_was_sent (STATE *state) /* * Sends next msg from the msg queue or next data block */ -static int send_block (STATE *state, BINKD_CONFIG *config) +int send_block (STATE *state, BINKD_CONFIG *config) { int i, n, save_errno; const char *save_err; @@ -2091,7 +2104,7 @@ static int start_file_recv (STATE *state, char *args, int sz, BINKD_CONFIG *conf return 0; } -static int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) +int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) { char buf[MAXPATHLEN+1]; FILE *f; @@ -2145,7 +2158,8 @@ static void z_send_init(STATE *state, BINKD_CONFIG *config, char **extra) *extra = ""; if (state->z_cansend && state->extcmd && state->out.size >= config->zminsize - && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first)) { + && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first) + && !(state->to && state->to->NC_flag)) { #ifdef WITH_BZLIB2 if (!state->z_send && (state->z_cansend & 2)) { *extra = " BZ2"; state->z_send = 2; @@ -2448,6 +2462,7 @@ static int GOT (STATE *state, char *args, int sz, BINKD_CONFIG *config) { char szAddr[FTN_ADDR_SZ + 1]; + memset(szAddr, 0, sizeof(szAddr)); ftnaddress_to_str (szAddr, &state->sent_fls[n].fa); state->bytes_sent += state->sent_fls[n].size; ++state->files_sent; @@ -2538,7 +2553,7 @@ static command *commands[] = }; /* Recvs next block, processes msgs or writes down the data from the remote */ -static int recv_block (STATE *state, BINKD_CONFIG *config) +int recv_block (STATE *state, BINKD_CONFIG *config) { int no; @@ -2770,7 +2785,7 @@ static int recv_block (STATE *state, BINKD_CONFIG *config) return 1; } -static int banner (STATE *state, BINKD_CONFIG *config) +int banner (STATE *state, BINKD_CONFIG *config) { int tz; char szLocalTime[60]; @@ -2850,7 +2865,7 @@ static int banner (STATE *state, BINKD_CONFIG *config) return 1; } -static int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) +int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) { struct stat sb; FILE *f = NULL; @@ -3031,7 +3046,7 @@ static int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) return 1; } -static void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) +void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) { char szFTNAddr[FTN_ADDR_SZ + 1]; diff --git a/readcfg.c b/readcfg.c old mode 100644 new mode 100755 index 467e0fea..89fce450 --- a/readcfg.c +++ b/readcfg.c @@ -44,6 +44,10 @@ */ BINKD_CONFIG *current_config; +#ifdef AMIGA +extern struct SignalSemaphore config_sem; +#endif + /* * Temporary static structure for configuration reading */ @@ -210,9 +214,15 @@ void lock_config_structure(BINKD_CONFIG *c) snprintf(c->iport, sizeof(c->iport), "%s", find_port("")); snprintf(c->oport, sizeof(c->oport), "%s", find_port("")); c->call_delay = 60; + c->no_call_delay = 0; c->rescan_delay = 60; c->nettimeout = DEF_TIMEOUT; c->oblksize = DEF_BLKSIZE; + +#ifdef AMIGA + c->tcp_nodelay = 0; +#endif + #if defined(WITH_ZLIB) || defined(WITH_BZLIB2) c->zminsize = 1024; c->zlevel = 0; @@ -391,8 +401,16 @@ static KEYWORD keywords[] = {"oport", read_port, &work_config.oport, 0, 0}, {"rescan-delay", read_time, &work_config.rescan_delay, 1, DONT_CHECK}, {"call-delay", read_time, &work_config.call_delay, 1, DONT_CHECK}, + {"no-call-delay", read_bool, &work_config.no_call_delay, 0, 0}, {"timeout", read_time, &work_config.nettimeout, 1, DONT_CHECK}, {"oblksize", read_int, &work_config.oblksize, MIN_BLKSIZE, MAX_BLKSIZE}, + +#ifdef AMIGA + {"tcp-nodelay", read_bool, &work_config.tcp_nodelay, 0, 0}, + {"so-sndbuf", read_int, &work_config.so_sndbuf, 0, 65535}, + {"so-rcvbuf", read_int, &work_config.so_rcvbuf, 0, 65535}, +#endif + {"maxservers", read_int, &work_config.max_servers, 0, DONT_CHECK}, {"maxclients", read_int, &work_config.max_clients, 0, DONT_CHECK}, {"inbound", read_string, work_config.inbound, 'd', 0}, @@ -666,7 +684,7 @@ static int read_passwords(char *filename) exp_ftnaddress (&fa, work_config.pAddr, work_config.nAddr, work_config.pDomains.first); pn = add_node (&fa, NULL, password, pkt_pwd, out_pwd, '-', NULL, NULL, NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, - HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, + HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, #ifdef BW_LIM BW_DEF, BW_DEF, #endif @@ -830,11 +848,10 @@ BINKD_CONFIG *readcfg (char *path) if (!new_config) { /* Config error. Abort or continue? */ - if (current_config) - { - Log(1, "error in configuration, using old config"); - unlock_config_structure(&work_config, 0); - } + unlock_config_structure(&work_config, 0); + + if (current_config) + Log(1, "error in configuration, using old config"); } return new_config; @@ -857,6 +874,16 @@ int checkcfg(void) Log (2, "got SIGHUP"); need_reload = got_sighup; got_sighup = 0; +#elif defined(AMIGA) + /* No SIGHUP on AmigaOS: detect config change by mtime */ + need_reload = 0; + if (current_config->config_list.first) + { + if (stat(current_config->config_list.first->path, &sb) == 0 && + current_config->config_list.first->mtime != 0 && + sb.st_mtime != current_config->config_list.first->mtime) + need_reload = 1; + } #else need_reload = 0; #endif @@ -886,8 +913,75 @@ int checkcfg(void) } #endif - if (!need_reload) - return 0; + if (!need_reload) + return 0; + +#ifdef AMIGA + /* Prevent reload storms and partial-file reads. + * + * On AmigaOS (and some Unix editors), config files are written in multiple + * passes, so binkd may see the mtime change while the file is still being + * written. Attempting to parse an incomplete file gives "unknown keyword" + * errors, and rapid repeated mtime changes cause bind() to fail because the + * previous listen socket has not been released yet. + * + * Strategy: after first detecting a change, wait until the mtime has been + * stable for at least 2 seconds before actually reloading. Also enforce a + * minimum of 5 seconds between successive successful reloads. + */ + { + static time_t last_reload = 0; /* time of last successful reload */ + static time_t change_seen = 0; /* time we first noticed the change */ + static time_t stable_mtime = 0; /* mtime we are waiting to stabilize */ + static int reload_pending = 0; /* persists between calls */ + time_t now = time(NULL); + + /* The loop has already updated pc->mtime, so in the next call + * need_reload will be 0 even though we haven't reloaded yet. + * reload_pending keeps the reload intent alive. + */ + if (need_reload) reload_pending = 1; + + if (!reload_pending) + return 0; + + /* Get the mtime of the primary config file */ + { struct stat sb2; + time_t cur_mtime = 0; + + if (current_config->config_list.first && stat(current_config->config_list.first->path, &sb2) == 0) + cur_mtime = sb2.st_mtime; + + /* mtime just changed (or changed again) — reset the stability clock */ + if (cur_mtime != stable_mtime) + { + stable_mtime = cur_mtime; + change_seen = now; + Log(5, "checkcfg: config mtime changed, waiting for stability..."); + return 0; + } + + /* mtime has been stable since change_seen */ + if (now - change_seen < 2) + { + Log(5, "checkcfg: config not yet stable (%lds), waiting...", + (long)(now - change_seen)); + return 0; + } + } + + /* Enforce minimum gap between reloads to let the OS release sockets */ + if (now - last_reload < 5) + { + Log(5, "checkcfg: reload suppressed (too soon, %lds)", (long)(now - last_reload)); + return 0; + } + + last_reload = now; + reload_pending = 0; /* Once the intent has been consumed, the reload is executed */ + } +#endif + /* Reload starting from first file in list */ Log(2, "Reloading configuration..."); pc = current_config->config_list.first; @@ -1125,7 +1219,7 @@ static int read_node_info (KEYWORD *key, int wordcount, char **words) char *w[ARGNUM], *tmp, *pkt_pwd, *out_pwd, *pipe; int i, j; int NR_flag = NR_USE_OLD, ND_flag = ND_USE_OLD, HC_flag = HC_USE_OLD, - MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, restrictIP = RIP_USE_OLD, + MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, NC_flag = NC_USE_OLD, restrictIP = RIP_USE_OLD, IP_afamily = AF_USE_OLD; #ifdef BW_LIM long bw_send = BW_DEF, bw_recv = BW_DEF; @@ -1175,6 +1269,8 @@ static int read_node_info (KEYWORD *key, int wordcount, char **words) HC_flag = HC_OFF; else if (STRICMP (tmp, "-noproxy") == 0) NP_flag = NP_ON; + else if (STRICMP (tmp, "-nc") == 0) + NC_flag = NC_ON; #ifdef BW_LIM else if (STRICMP (tmp, "-bw") == 0) { @@ -1258,7 +1354,7 @@ static int read_node_info (KEYWORD *key, int wordcount, char **words) split_passwords(w[2], &pkt_pwd, &out_pwd); pn = add_node (&fa, w[1], w[2], pkt_pwd, out_pwd, (char)(w[3] ? w[3][0] : '-'), w[4], w[5], - NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, + NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, IP_afamily, #ifdef BW_LIM bw_send, bw_recv, @@ -1991,6 +2087,9 @@ static int print_node_info_1 (FTN_NODE *fn, void *arg) if (fn->pkt_pwd) pwd_len += strlen(fn->pkt_pwd)+1; else pwd_len += 2; if (fn->out_pwd) pwd_len += strlen(fn->out_pwd)+1; else pwd_len += 2; pwd = calloc (1, pwd_len+1); + /* Guard against null pointer dereference if calloc fails */ + if (!pwd) + return 0; strcpy(pwd, fn->pwd); if (fn->pkt_pwd != (char*)&(fn->pwd) || fn->out_pwd != (char*)&(fn->pwd)) { strcat(strcat(pwd, ","), (fn->pkt_pwd) ? fn->pkt_pwd : "-"); @@ -2324,4 +2423,3 @@ static int read_perlvar (KEYWORD *key, int wordcount, char **words) return 1; } #endif - diff --git a/readcfg.h b/readcfg.h old mode 100644 new mode 100755 index 835abdca..77decb3b --- a/readcfg.h +++ b/readcfg.h @@ -111,8 +111,16 @@ struct _BINKD_CONFIG #endif int nettimeout; int connect_timeout; - int rescan_delay; + +#ifdef AMIGA + int tcp_nodelay; + int so_sndbuf; + int so_rcvbuf; +#endif + int call_delay; + int no_call_delay; + int rescan_delay; int max_servers; int max_clients; int kill_dup_partial_files; diff --git a/readdir.h b/readdir.h old mode 100644 new mode 100755 index 91a0ab75..5caff5d9 --- a/readdir.h +++ b/readdir.h @@ -23,6 +23,8 @@ #elif defined(__MINGW32__) #include #include +#elif defined(AMIGA) +#include "amiga/dirent.h" #else #include #include diff --git a/rfc2553.h b/rfc2553.h old mode 100644 new mode 100755 index dcaba6b6..59e763c5 --- a/rfc2553.h +++ b/rfc2553.h @@ -21,8 +21,52 @@ #include "iphdr.h" +/* Amiga: define EAI_* before including netdb.h to prevent libnix redefinition */ +#if defined(AMIGA) +#include +#include +#include +#include + + #undef EAI_NONAME + #undef EAI_AGAIN + #undef EAI_FAIL + #undef EAI_NODATA + #undef EAI_FAMILY + #undef EAI_SOCKTYPE + #undef EAI_SERVICE + #undef EAI_ADDRFAMILY + #undef EAI_MEMORY + #undef EAI_SYSTEM + #undef EAI_UNKNOWN + #define EAI_NONAME -1 + #define EAI_AGAIN -2 + #define EAI_FAIL -3 + #define EAI_NODATA -4 + #define EAI_FAMILY -5 + #define EAI_SOCKTYPE -6 + #define EAI_SERVICE -7 + #define EAI_ADDRFAMILY -8 + #define EAI_MEMORY -9 + #define EAI_SYSTEM -10 + #define EAI_UNKNOWN -11 + +#include + +/* EAI_ADDRFAMILY is BSD/macOS specific; Linux/glibc does not define it + * Map it to EAI_FAMILY which has the same meaning on those platforms */ +#ifndef EAI_ADDRFAMILY +#ifdef EAI_FAMILY +#define EAI_ADDRFAMILY EAI_FAMILY +#else +#define EAI_ADDRFAMILY -9 +#endif +#endif + +#endif + /* Autosense getaddrinfo */ -#if defined(AI_PASSIVE) && defined(EAI_NONAME) +#if defined(AI_PASSIVE) && defined(EAI_NONAME) && !defined(AMIGA) #define HAVE_GETADDRINFO #endif @@ -47,6 +91,18 @@ }; #define addrinfo addrinfo_emu +#ifdef AMIGA +#ifdef getaddrinfo +#undef getaddrinfo +#endif +#ifdef freeaddrinfo +#undef freeaddrinfo +#endif +#ifdef gai_strerror +#undef gai_strerror +#endif +#endif + int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res); diff --git a/run.c b/run.c old mode 100644 new mode 100755 index 83bc8154..9904d6e2 --- a/run.c +++ b/run.c @@ -18,6 +18,11 @@ #ifdef HAVE_UNISTD_H #include #endif +#ifdef AMIGA +#include +#include +#include +#endif #include "sys.h" #include "run.h" @@ -40,10 +45,128 @@ #define SHELL (getenv("COMSPEC") ? getenv("COMSPEC") : "command.com") #define SHELL_META "\"\'\\%<>|&^@" #define SHELLOPT "/c" +#elif defined(AMIGA) +/* AmigaOS shell */ +#define SHELL "c:execute" +#define SHELL_META "\"\'\\*?(){};&|<>" #else #error "Unknown platform" #endif +#ifdef AMIGA +/* run(): execute an AmigaDOS command via SystemTagList() with NIL: I/O + * Runs synchronously so binkd waits for completion (needed for srifreq + * to create .rsp before parse_response). NIL: I/O prevents CLI freezing + * Error output goes to a temporary file for logging on failure */ +int run(char *cmd) +{ + /* All declarations at the top for C89/ADE GCC 2.95 compatibility */ + BPTR nil_in; + char errfile[MAXPATHLEN]; + BPTR err_out = 0; + int rc = 0; + char cmd_copy[MAXPATHLEN]; + char *cmd_start; + char *cmd_end; + BPTR lock; + struct TagItem exec_tags[5]; + BPTR errfile_ptr; + char buf[512]; + int len; + + /* Open NIL: for input/output */ + nil_in = Open("NIL:", MODE_OLDFILE); + + /* Create temporary error file in current directory */ + snprintf(errfile, sizeof(errfile), "binkd_err_%ld.txt", (long)time(NULL)); + err_out = Open(errfile, MODE_NEWFILE); + if (err_out == 0) + { + Log(2, "cannot create error file %s, using NIL: for error output", errfile); + } + + /* Extract the command (first word) to check if it exists */ + strncpy(cmd_copy, cmd, sizeof(cmd_copy) - 1); + cmd_copy[sizeof(cmd_copy) - 1] = '\0'; + cmd_start = cmd_copy; /* Work on copy, never modify original cmd */ + + /* Skip leading whitespace */ + while (*cmd_start && (*cmd_start == ' ' || *cmd_start == '\t')) + cmd_start++; + + /* Find end of command (first space or end) */ + cmd_end = cmd_start; + while (*cmd_end && *cmd_end != ' ' && *cmd_end != '\t') + cmd_end++; + *cmd_end = '\0'; + + /* Check if command exists */ + lock = Lock((STRPTR)cmd_start, SHARED_LOCK); + if (lock == 0) + { + Log(2, "command not found, skipping: '%s'", cmd_start); + Close(nil_in); + if (err_out) + Close(err_out); + DeleteFile((STRPTR)errfile); + return 0; + } + UnLock(lock); + + Log(3, "executing '%s'", cmd); + + /* Set up tags with NIL: input and error file output. + * Use NP_* (New Process) tags instead of SYS_* to avoid sharing + * file handles with parent process - prevents stderr from being + * closed when child exits. */ + exec_tags[0].ti_Tag = NP_Input; + exec_tags[0].ti_Data = (ULONG)nil_in; + exec_tags[1].ti_Tag = NP_Output; + exec_tags[1].ti_Data = (ULONG)nil_in; + exec_tags[2].ti_Tag = NP_Error; + exec_tags[2].ti_Data = (ULONG)err_out; + exec_tags[3].ti_Tag = NP_Synchronous; + exec_tags[3].ti_Data = TRUE; + exec_tags[4].ti_Tag = TAG_DONE; + exec_tags[4].ti_Data = 0; + + rc = SystemTagList((STRPTR)cmd, exec_tags); + + /* Close handles */ + Close(nil_in); + if (err_out) + Close(err_out); + + /* Log error output if command failed */ + if (rc != 0) + { + errfile_ptr = Open(errfile, MODE_OLDFILE); + if (errfile_ptr) + { + Log(2, "command failed with rc=%d, output:", rc); + while ((len = Read(errfile_ptr, buf, sizeof(buf) - 1)) > 0) + { + buf[len] = '\0'; + Log(2, "%s", buf); + } + Close(errfile_ptr); + } + } + + DeleteFile((STRPTR)errfile); + return rc; +} + +/* run3(): pipe/tunnel not supported on AmigaOS without ixemul. */ +int run3(const char *cmd, int *in, int *out, int *err) +{ + (void)cmd; (void)in; (void)out; (void)err; + Log(1, "run3: pipe connections not supported on Amiga"); + return -1; +} +#endif /* AMIGA */ + +#ifndef AMIGA int run (char *cmd) { int rc=-1; @@ -111,6 +234,7 @@ int run (char *cmd) #endif return rc; } +#endif /* !AMIGA */ #ifdef __MINGW32__ static int set_cloexec(int fd) @@ -136,6 +260,7 @@ static int set_cloexec(int fd) } #endif +#ifndef AMIGA int run3 (const char *cmd, int *in, int *out, int *err) { int pid; @@ -162,6 +287,14 @@ int run3 (const char *cmd, int *in, int *out, int *err) } #ifdef HAVE_FORK +#ifdef AMIGA + /* Pipe tunneling not supported on AmigaOS without fork() */ + Log(1, "run3: pipe/tunnel not supported on Amiga: %s", cmd); + if (in) close(pin[1]), close(pin[0]); + if (out) close(pout[1]), close(pout[0]); + if (err) close(perr[1]), close(perr[0]); + return -1; +#else pid = fork(); if (pid == -1) { @@ -194,7 +327,11 @@ int run3 (const char *cmd, int *in, int *out, int *err) if (strpbrk(cmd, SHELL_META)) { shell = SHELL; +#ifdef AMIGA + execl(shell, shell, cmd, (char *)NULL); +#else execl(shell, shell, SHELLOPT, cmd, (char *)NULL); +#endif } else { @@ -232,6 +369,7 @@ int run3 (const char *cmd, int *in, int *out, int *err) *err = perr[0]; close(perr[1]); } +#endif /* !AMIGA */ #else /* redirect stdin/stdout/stderr takes effect for all threads */ @@ -336,3 +474,4 @@ int run3 (const char *cmd, int *in, int *out, int *err) return pid; } +#endif /* !AMIGA */ diff --git a/sem.h b/sem.h old mode 100644 new mode 100755 index 7bdd3b34..5e4a5f4f --- a/sem.h +++ b/sem.h @@ -34,6 +34,14 @@ typedef HEV EVENTSEM; #include typedef struct SignalSemaphore MUTEXSEM; +#ifdef AMIGA +typedef struct +{ + struct Task *waiter; + ULONG sigbit; +} EVENTSEM; +#endif + #elif defined(WITH_PTHREADS) #include @@ -73,25 +81,27 @@ int _ReleaseSem (void *); * Initialise Event Semaphores. */ -int _InitEventSem (void *); +#ifdef AMIGA +int _InitEventSem (EVENTSEM *); /* * Post Semaphore. */ -int _PostSem (void *); +int _PostSem (EVENTSEM *); /* * Wait Semaphore. */ -int _WaitSem (void *, int); +int _WaitSem (EVENTSEM *, int); /* * Clean Event Semaphores. */ -int _CleanEventSem (void *); +int _CleanEventSem (EVENTSEM *); +#endif #if defined(WITH_PTHREADS) #define InitSem(sem) pthread_mutex_init(sem, NULL) diff --git a/server.c b/server.c old mode 100644 new mode 100755 index 7c02605e..8d51237b --- a/server.c +++ b/server.c @@ -23,6 +23,10 @@ #include #endif +#ifdef AMIGA +#include "amiga/bsdsock.h" +#endif + #include "sys.h" #include "iphdr.h" #include "readcfg.h" @@ -39,6 +43,10 @@ #endif #include "rfc2553.h" +#if defined(HAVE_THREADS) || defined(AMIGA) +extern EVENTSEM eothread; +#endif + int n_servers = 0; int ext_rand = 0; @@ -53,7 +61,8 @@ static void serv (void *arg) void *cperl; #endif -#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(DEBUGCHILD) +/* Prevent shared socket closure */ +#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(AMIGA) && !defined(DEBUGCHILD) int curfd; pidcmgr = 0; for (curfd=0; curfdai_addr, ai->ai_addrlen) != 0) { - Log(1, "servmgr bind(): %s", TCPERR ()); - soclose(sockfd[sockfd_used]); - return -1; +#ifdef AMIGA + /* bsdsocket may hold the port briefly after socket close. Retry */ + int bind_retries = 6; + + while (bind(sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) + { + if (--bind_retries == 0) + { + Log(1, "servmgr bind(): %s", TCPERR()); + soclose(sockfd[sockfd_used]); + return -1; + } + + Log(2, "servmgr bind(): %s, retry in 2s...", TCPERR()); + sleep(2); + } +#else + if (bind (sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) + { + Log(1, "servmgr bind(): %s", TCPERR ()); + soclose(sockfd[sockfd_used]); + return -1; + } +#endif } if (listen (sockfd[sockfd_used], 5) != 0) { @@ -168,6 +201,12 @@ static int do_server(BINKD_CONFIG *config) setproctitle ("server manager (listen %s)", config->listen.first->port); + /* Save rescan_delay locally. checkcfg() may free 'config' (old config + * is released when usageCount reaches 0 after reload), so we must not + * access config->rescan_delay inside the loop after a reload */ + { + int rescan = config->rescan_delay; + for (;;) { struct timeval tv; @@ -183,7 +222,7 @@ static int do_server(BINKD_CONFIG *config) maxfd = sockfd[curfd]; } tv.tv_usec = 0; - tv.tv_sec = CHECKCFG_INTERVAL; + tv.tv_sec = rescan; unblocksig(); check_child(&n_servers); n = select(maxfd+1, &r, NULL, NULL, &tv); @@ -192,8 +231,20 @@ static int do_server(BINKD_CONFIG *config) { case 0: /* timeout */ if (checkcfg()) { + /* config may have been freed by checkcfg() — read rescan from + * the new current_config before returning for restart */ + { + BINKD_CONFIG *nc = lock_current_config(); + if (nc) + { + rescan = nc->rescan_delay; + unlock_config_structure(nc, 0); + } + } + for (curfd=0; curfdrescan_delay; + unlock_config_structure(nc, 0); + } + } + for (curfd=0; curfd +#endif + /* * Listens... Than calls protocol() */ diff --git a/sys.h b/sys.h old mode 100644 new mode 100755 index d85f8bc3..dd34c3d3 --- a/sys.h +++ b/sys.h @@ -22,8 +22,28 @@ #ifdef HAVE_STDINT_H #include #endif +#ifdef AMIGA + /* Include Amiga exec proto for Delay() function */ + #include +#endif #ifdef HAVE_UNISTD_H #include + /* Undefine conflicting unistd.h macros for AMIGA */ + #ifdef AMIGA + #ifdef getpid + #undef getpid + #endif + + #ifdef sleep + #undef sleep + #endif + + /* Redefine with our Amiga implementations */ + #define getpid() ((int)(ULONG)FindTask(NULL)) + + #define sleep(s) Delay((ULONG)((s) * 50)) + + #endif /* AMIGA */ #endif #ifdef HAVE_IO_H #include @@ -105,6 +125,11 @@ #define PID() mypid #endif +#ifdef HAVE_FORK + #include /* Needed for SIG_BLOCK/SIG_UNBLOCK and WIFEXITED/WEXITSTATUS */ + #include +#endif + #if defined(HAVE_FORK) && defined(HAVE_SIGPROCMASK) && defined(HAVE_WAITPID) && defined(SIG_BLOCK) void switchsignal(int how); #define blocksig() switchsignal(SIG_BLOCK) @@ -292,8 +317,16 @@ typedef unsigned long int u32; #ifndef PRIdMAX #define PRIdMAX "ld" +#endif + +#ifndef PRIuMAX +#ifdef AMIGA +/* On AmigaOS m68k, uintmax_t is long long unsigned int */ +#define PRIuMAX "llu" +#else #define PRIuMAX "lu" #endif +#endif #ifndef HAVE_STRTOUMAX #define strtoumax(ptr, endptr, base) strtoul(ptr, endptr, base) diff --git a/tools.c b/tools.c old mode 100644 new mode 100755 index efdc3040..f466f067 --- a/tools.c +++ b/tools.c @@ -22,6 +22,10 @@ #include #endif +#ifdef AMIGA +#include "amiga/bsdsock.h" +#endif + #include "sys.h" #include "readcfg.h" #include "common.h" @@ -38,6 +42,12 @@ #include "nt/w32tools.h" #endif +#if defined(HAVE_THREADS) || defined(AMIGA) +extern MUTEXSEM lsem; +#endif + +extern void vLog (int lev, char *s, va_list ap); + /* * We can call Log() even when we have no config ready. So, we must keep * internal variables which will be updated when config is loaded @@ -290,6 +300,10 @@ void vLog (int lev, char *s, va_list ap) char buf[1024]; int ok = 1; +#ifdef AMIGA + static int need_newline = 0; +#endif + /* make string in buffer */ vsnprintf(buf, sizeof(buf), s, ap); /* do perl hooks */ @@ -313,8 +327,20 @@ void vLog (int lev, char *s, va_list ap) if (lev <= current_conlog && !inetd_flag) { LockSem(&lsem); +#ifdef AMIGA + /* AmigaOS: go to new line for status messages to avoid overwriting */ + if (lev < 0 && need_newline) + { + need_newline = 0; + } + fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, + tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : "\r"); + if (lev >= 0) + need_newline = 1; +#else fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : ""); +#endif fflush (stderr); ReleaseSem(&lsem); if (lev < 0) From 2349a9bae11c0b987e2a9871396eee4fc28f2f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tanaus=C3=BA=20M=2E?= Date: Sun, 26 Apr 2026 20:33:12 +0100 Subject: [PATCH 02/20] Session time in "done" line: protocol.c - log_end_of_session() now includes session duration in seconds Auto NR-mode on incomplete files: inbound.c - Added inb_has_partials(): scans inbound for .hr files matching remote AKAs inbound.h - Added inb_has_partials() declaration protocol.c - Call inb_has_partials() in banner() (outgoing) and PWD() (incoming) to automatically set WANT_NR when partial files exist for the remote link update changes.txt --- changes.txt | 30 +++++++---------- inbound.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++ inbound.h | 7 ++++ protocol.c | 26 +++++++++++++-- 4 files changed, 137 insertions(+), 21 deletions(-) diff --git a/changes.txt b/changes.txt index 2ceb66b0..a6fd4d4a 100644 --- a/changes.txt +++ b/changes.txt @@ -26,6 +26,15 @@ ftnq.c: --- New features --- +Session time in "done" line: + protocol.c - log_end_of_session() now includes session duration in seconds + +Auto NR-mode on incomplete files: + inbound.c - Added inb_has_partials(): scans inbound for .hr files matching remote AKAs + inbound.h - Added inb_has_partials() declaration + protocol.c - Call inb_has_partials() in banner() (outgoing) and PWD() (incoming) to + automatically set WANT_NR when partial files exist for the remote link + NC_flag (no-compression per node): btypes.h - Added NC_flag field to FTN_NODE struct ftnnode.h - Added NC_ON/NC_OFF/NC_USE_OLD defines, NC_flag parameter to add_node() @@ -35,7 +44,7 @@ NC_flag (no-compression per node): no-call-delay config keyword: readcfg.h - Added no_call_delay field to BINKD_CONFIG - readcfg.c - Added "no-call-delay" config keyword (read_bool) + readcfg.c - Added "no-call-delay" config keyword client.c - Skip SLEEP(call_delay) when no_call_delay is set ftnnode.c: @@ -50,7 +59,7 @@ bsycleanup.c: - BSY/CSY/TRY file cleanup at startup, scans domain outbounds bsycleanup.h: -- API: cleanup_old_bsy() +- cleanup_old_bsy() --- Multi-platform refactoring --- @@ -75,42 +84,28 @@ misc/decompress.c: misc/freq.c: - Create .req/.clo files (ASO flat layout, BSO BinkleyStyle layout) - Uses MAXPATHLEN from portable.h -- Removed local str_tolower() implementation (now in portable.c) misc/nodelist.c: - Compile FidoNet nodelist to binkd.conf node lines (extracts IBN/INA flags) - Uses MAX_LINE from portable.h - Uses MAXPATHLEN from portable.h -- Removed local str_trim() implementation misc/process_tic.c: - Process .tic files to filebox (supports config file and legacy args) - Uses MAXPATHLEN from portable.h - Uses MAX_LINE from portable.h -- Removed local implementations: trim_nl, skip_ws, ensure_dir, copy_file, move_file, get_file_size misc/srifreq.c: - Reimplementation in C of pgul's misc/srifreq shell script (password protection, aliases, wildcards, rate limiting) - Uses MAXPATHLEN from portable.h - Uses MAX_LINE from portable.h - Converted MAX_ALIASES to dynamic array (realloc) -- Removed local implementations: wildmatch, is_wildcard, get_file_mtime, str_trim, str_upper -- Added example aliases file format in header comment -- Fixed: sizeof(logbuf) on pointer parameter gave wrong size (4/8 bytes instead of buffer size) -- Fixed: memory leak -- g_aliases array was never freed -- Fixed: memory leak -- g_conf.tracking linked list was never freed -- Fixed: sscanf format widths did not match buffer sizes in load_config(), parse_srif(), load_aliases() -- Fixed: tracking_load() did not reject future/corrupt timestamps -- Fixed: request line parser offset was wrong when line had leading whitespace -- Fixed: get_file_size() returning -1 was added to tracking bytes as negative -- Fixed: parse_srif() TIME keyword without value changed time_limit from -1 to 0 misc/portable.c: - Shared implementations: string utilities, file operations, path utilities misc/portable.h: - Portable declarations and platform-specific includes (POSIX, Win32, OS/2, DOS, AmigaOS) -- Fixed: duplicate declaration of safe_strncpy removed misc/decompress.txt: - Usage documentation for decompress utility @@ -167,10 +162,7 @@ mkfls/unix/Makefile.analyze.unix: --- Documentation --- -README.md: -- Added AmigaOS compilation instructions (ADE, ixemul/ixnet library versions) - Added usage documentation for all misc tools (decompress, freq, process_tic, srifreq, nodelist) -- Added note about config reload instability =========================================================================== AMIGA-SPECIFIC CHANGES (#ifdef AMIGA / AMIGADOS_4D_OUTBOUND) diff --git a/inbound.c b/inbound.c index ce8a8541..69ad5f79 100755 --- a/inbound.c +++ b/inbound.c @@ -106,6 +106,101 @@ static int to_be_deleted (char *tmp_name, char *netname, boff_t filesize, BINKD_ return 0; } +/* + * Returns 1 if there are partial files (.hr) in the inbound belonging to + * one of the active AKAs in state->fa[0..state->nfa-1]. Does not remove + * anything. Used to auto-enable NR-mode when incomplete files exist + * + * Copied from find_tmp_name() + */ +int inb_has_partials (STATE *state, BINKD_CONFIG *config) +{ + char s[MAXPATHLEN + 1]; + char buf[MAXPATHLEN + 80]; + DIR *dp; + struct dirent *de; + FILE *f; + int i, found = 0; + char *t, *inbound; + + /* Prefer temp_inbound if configured, same logic as find_tmp_name() */ + inbound = state->inbound; + + if (config->temp_inbound[0]) + { + inbound = config->temp_inbound; + } + + if ((dp = opendir (inbound)) == 0) + { + return 0; + } + + /* Build base path with trailing separator, t points to the filename part */ + strnzcpy (s, inbound, MAXPATHLEN); + + if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) + { + strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); + } + + t = s + strlen (s); + + while ((de = readdir (dp)) != 0 && !found) + { + /* .hr files are named 8 hex digits + ".hr" */ + for (i = 0; i < 8; ++i) + { + if (!isxdigit (de->d_name[i])) + break; + } + + if (i < 8 || STRICMP (de->d_name + 8, ".hr")) + continue; + + strnzcat (s, de->d_name, MAXPATHLEN); + + if ((f = fopen (s, "r")) != NULL) + { + if (fgets (buf, sizeof (buf), f) != NULL) + { + /* Field 4 in .hr is the FTN address of the sender */ + char *w3 = getwordx (buf, 4, GWX_NOESC); + + if (w3) + { + FTN_ADDR fa; + + FA_ZERO (&fa); + + if (parse_ftnaddress (w3, &fa, config->pDomains.first)) + { + /* Match against active (non-busy) AKAs only */ + for (i = 0; i < state->nfa; i++) + { + if (!ftnaddress_cmp (&fa, state->fa + i)) + { + found = 1; + break; + } + } + } + + xfree (w3); + } + } + + fclose (f); + } + + *t = 0; /* Reset filename part for next entry */ + } + + closedir (dp); + + return found; +} + /* * Searches for the ``file'' in the inbound and returns it's tmp name in s. * S must have MAXPATHLEN chars. Returns 0 on error, 1=found, 2=created. diff --git a/inbound.h b/inbound.h index 082bb6e6..2db2938e 100644 --- a/inbound.h +++ b/inbound.h @@ -33,4 +33,11 @@ int inb_reject (STATE *state, BINKD_CONFIG *config); */ void inb_remove_partial (STATE *state, BINKD_CONFIG *config); +/* + * Returns 1 if there are partial files in the inbound for any of the + * active AKAs in state. Used to auto-enable NR-mode + * Copied from find_tmp_name() + */ +int inb_has_partials (STATE *state, BINKD_CONFIG *config); + #endif diff --git a/protocol.c b/protocol.c index becac87e..b9e57ac6 100755 --- a/protocol.c +++ b/protocol.c @@ -1729,6 +1729,21 @@ static int PWD (STATE *state, char *pwd, int sz, BINKD_CONFIG *config) Log (5, "Turn on NR-mode with this link (remote has buggy NR)"); } + if ((state->NR_flag & (WANT_NR | WE_NR)) == 0) + { + char *tmp_inbound = select_inbound (state->fa, state->state, config); + char *saved_inbound = state->inbound; + state->inbound = tmp_inbound; + + if (inb_has_partials (state, config)) + { + state->NR_flag |= WANT_NR; + Log (4, "auto NR-mode: incomplete files found for this link"); + } + + state->inbound = saved_inbound; + } + szOpt = xstrdup(" EXTCMD"); if (state->NR_flag & WANT_NR) xstrcat(&szOpt, " NR"); if (state->ND_flag & THEY_ND) xstrcat(&szOpt, " ND"); @@ -2849,6 +2864,12 @@ int banner (STATE *state, BINKD_CONFIG *config) if (state->to || !state->delay_ADR) send_ADR (state, config); if (state->to) { + if ((state->NR_flag & (WANT_NR | WE_NR)) == 0 && inb_has_partials (state, config)) + { + state->NR_flag |= WANT_NR; + Log (4, "auto NR-mode: incomplete files found for this link"); + } + szOpt = xstrdup(" NDA EXTCMD"); if (state->NR_flag & WANT_NR) xstrcat(&szOpt, " NR"); if (state->ND_flag & THEY_ND) xstrcat(&szOpt, " ND"); @@ -3059,11 +3080,12 @@ void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) else strcpy (szFTNAddr, "?"); - Log (2, "done (%s%s, %s, S/R: %i/%i (%" PRIuMAX "/%" PRIuMAX " bytes))", + Log (2, "done (%s%s, %s, S/R: %i/%i (%" PRIuMAX "/%" PRIuMAX " bytes), %lus)", state->to ? "to " : (state->fa ? "from " : ""), szFTNAddr, status ? "failed" : "OK", state->files_sent, state->files_rcvd, - state->bytes_sent, state->bytes_rcvd); + state->bytes_sent, state->bytes_rcvd, + (unsigned long)(safe_time() - state->start_time)); } void protocol (SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, From dec480264be82e0597adb21870fe27f0c53b38d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tanaus=C3=BA=20M=2E?= Date: Sun, 26 Apr 2026 22:35:07 +0100 Subject: [PATCH 03/20] =?UTF-8?q?Fixed=20premature=20M=5FEOB:=20added=20!s?= =?UTF-8?q?tate->in.f=20to=20EOB=20condition=20=E2=80=94=20prevents=20send?= =?UTF-8?q?ing=20M=5FEOB=20while=20a=20remote=20file=20is=20still=20being?= =?UTF-8?q?=20received=20(avoids=20"M=5FEOB=20but=20N=20files=20pending=20?= =?UTF-8?q?M=5FGOT"=20error=20on=20the=20remote=20side)=20Fixed=20startup?= =?UTF-8?q?=20delay:=20last=5Frescan=3D0=20after=20pre-loop=20try=5Foutbou?= =?UTF-8?q?nd=20forces=20immediate=20retry=20in=20first=20loop=20iteration?= =?UTF-8?q?=20(avoids=20waiting=20rescan-delay=20seconds=20if=20DNS=20fail?= =?UTF-8?q?ed=20at=20startup)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- amiga/evloop.c | 2 +- amiga/proto_amiga.c | 2 +- changes.diff | 28013 ------------------------------------------ changes.txt | 2 + 4 files changed, 4 insertions(+), 28015 deletions(-) delete mode 100644 changes.diff diff --git a/amiga/evloop.c b/amiga/evloop.c index 8e470504..1033b6f5 100644 --- a/amiga/evloop.c +++ b/amiga/evloop.c @@ -367,7 +367,7 @@ void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) /* Initial outbound attempt before waiting (important for poll -p mode) */ Log(5, "DEBUG: Initial try_outbound before main loop"); try_outbound(config); - last_rescan = time(NULL); /* Reset timer since we just did an attempt */ + last_rescan = 0; /* Force immediate retry in first loop iteration */ /* ===== Main loop ===== */ for (;;) diff --git a/amiga/proto_amiga.c b/amiga/proto_amiga.c index 0ad26221..ea4b7798 100644 --- a/amiga/proto_amiga.c +++ b/amiga/proto_amiga.c @@ -142,7 +142,7 @@ int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *con } /* Nothing left to send — issue EOB */ - if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) + if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls && !state->in.f) { if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) { diff --git a/changes.diff b/changes.diff deleted file mode 100644 index d6f6b4b6..00000000 --- a/changes.diff +++ /dev/null @@ -1,28013 +0,0 @@ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/bsdsock.c binkd/amiga/bsdsock.c ---- binkd_pgul/amiga/bsdsock.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/bsdsock.c 2026-04-26 11:01:10.438650436 +0100 -@@ -0,0 +1,79 @@ -+/* -+ * bsdsock.c -- bsdsocket.library lifecycle for AmigaOS 3 -+ * -+ * bsdsock.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* Linker-compatibility global. Never used at runtime */ -+struct Library *SocketBase = NULL; -+ -+/* Suppress conflicting C prototypes from clib/bsdsocket_protos.h */ -+#ifndef CLIB_BSDSOCKET_PROTOS_H -+#define CLIB_BSDSOCKET_PROTOS_H -+#endif -+ -+#include -+#include -+ -+extern void Log(int lev, const char *s, ...); -+ -+/* _amiga_get_socket_base -- returns bsdsocket.library handle for calling task */ -+struct Library *_amiga_get_socket_base(void) -+{ -+ return (struct Library *)FindTask(NULL)->tc_UserData; -+} -+ -+int amiga_sock_init(void) -+{ -+ struct Task *me = FindTask(NULL); -+ struct Library *base; -+ -+ if (me->tc_UserData) -+ return 0; /* already open for this task */ -+ -+ base = OpenLibrary("bsdsocket.library", 0UL); -+ -+ if (!base) -+ { -+ fprintf(stderr, "amiga_sock_init: cannot open bsdsocket.library\n"); -+ return -1; -+ } -+ -+ /* Store in tc_UserData and global SocketBase */ -+ me->tc_UserData = (APTR)base; -+ SocketBase = base; -+ -+ /* Link the per-task errno to the TCP stack. */ -+ SetErrnoPtr(&errno, (LONG)sizeof(errno)); -+ -+ return 0; -+} -+ -+void amiga_sock_cleanup(void) -+{ -+ struct Task *me = FindTask(NULL); -+ struct Library *base = (struct Library *)me->tc_UserData; -+ -+ if (base) -+ { -+ me->tc_UserData = NULL; -+ SocketBase = NULL; -+ CloseLibrary(base); -+ } -+} -+ -+int amiga_child_sock_init(void) -+{ -+ /* Child inherits tc_UserData = NULL, opens new handle */ -+ return amiga_sock_init(); -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/bsdsock.h binkd/amiga/bsdsock.h ---- binkd_pgul/amiga/bsdsock.h 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/bsdsock.h 2026-04-26 10:49:27.706200054 +0100 -@@ -0,0 +1,155 @@ -+/* -+ * bsdsock.h -- bsdsocket.library init and POSIX compat shims for AmigaOS 3 -+ * -+ * bsdsock.h is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifndef _AMIGA_BSDSOCK_H -+#define _AMIGA_BSDSOCK_H -+ -+#ifdef AMIGA -+ -+#include -+#include -+#include -+#include -+ -+/* Suppress conflicting C prototypes from roadshow */ -+#ifndef CLIB_BSDSOCKET_PROTOS_H -+#define CLIB_BSDSOCKET_PROTOS_H -+#endif -+ -+/* Undefine MCLBYTES/MCLSHIFT before roadshow headers */ -+#ifdef MCLBYTES -+#undef MCLBYTES -+#endif -+#ifdef MCLSHIFT -+#undef MCLSHIFT -+#endif -+ -+/* Roadshow SDK network headers */ -+#include -+#include -+#include "compat_netinet_in.h" -+#include -+#include -+#include /* inline/bsdsocket.h, no clib protos */ -+ -+/* Undefine conflicting unistd.h macros */ -+#ifdef gethostid -+#undef gethostid -+#endif -+#ifdef getdtablesize -+#undef getdtablesize -+#endif -+#ifdef gethostname -+#undef gethostname -+#endif -+ -+/* Per-task SocketBase override */ -+struct Library *_amiga_get_socket_base(void); -+ -+#ifdef SocketBase -+#undef SocketBase -+#endif -+#define SocketBase _amiga_get_socket_base() -+ -+/* Roadshow socket-specific errno values */ -+#include -+ -+#define BSDSOCK_HAS_TIMEVAL 1 -+ -+/* Socket-specific errno values from roadshow sys/errno.h */ -+#ifndef ENOTSOCK -+#define ENOTSOCK 38 /* Socket operation on non-socket */ -+#endif -+#ifndef EOPNOTSUPP -+#define EOPNOTSUPP 45 /* Operation not supported on socket */ -+#endif -+#ifndef ECONNREFUSED -+#define ECONNREFUSED 61 /* Connection refused */ -+#endif -+#ifndef ETIMEDOUT -+#define ETIMEDOUT 60 /* Connection timed out */ -+#endif -+#ifndef ECONNRESET -+#define ECONNRESET 54 /* Connection reset by peer */ -+#endif -+#ifndef EHOSTUNREACH -+#define EHOSTUNREACH 65 /* No route to host */ -+#endif -+ -+#include -+ -+/* sockaddr_storage fallback for roadshow */ -+#ifndef HAVE_SOCKADDR_STORAGE -+#ifndef sockaddr_storage -+struct sockaddr_storage -+{ -+ unsigned short ss_family; -+ char __ss_pad[22]; /* enough for IPv4 */ -+}; -+#endif -+#define HAVE_SOCKADDR_STORAGE 1 -+#endif -+ -+/* Library base functions */ -+int amiga_sock_init(void); -+void amiga_sock_cleanup(void); -+int amiga_child_sock_init(void); -+ -+/* getpid() is defined in sys.h for AMIGA */ -+/* amiga_sleep and sleep are defined in sys.h for AMIGA */ -+ -+/* select() -> WaitSelect() wrapper with Ctrl+C handling */ -+#ifndef AMIGA_SELECT_DEFINED -+#define AMIGA_SELECT_DEFINED -+ -+/* Forward-declare binkd_exit */ -+extern int binkd_exit; -+ -+/* Forward-declare Log to avoid implicit declaration warning */ -+extern void Log(int lev, char *s, ...); -+ -+static int amiga_select_wrap(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) -+{ -+ ULONG sigmask = SIGBREAKF_CTRL_C; -+ int rc = WaitSelect(nfds, readfds, writefds, exceptfds, timeout, &sigmask); -+ -+ /* Ctrl+C should break blocked select() loops immediately. */ -+ if ((sigmask & SIGBREAKF_CTRL_C) != 0) -+ { -+ Log(1, "Ctrl+C detected in WaitSelect, setting binkd_exit=1"); -+ binkd_exit = 1; -+ errno = EINTR; -+ return -1; -+ } -+ -+ return rc; -+} -+ -+#define select(n, r, w, e, t) amiga_select_wrap((n), (r), (w), (e), (t)) -+ -+#endif /* AMIGA_SELECT_DEFINED */ -+ -+/* FIONBIO via IoctlSocket */ -+#ifndef FIONBIO -+#define FIONBIO 0x8004667E -+#endif -+#ifndef ioctl -+#define ioctl(s, req, arg) IoctlSocket((s), (req), (char *)(arg)) -+#endif -+ -+/* inet_ntoa -> Inet_NtoA (Amiga only) */ -+#ifdef AMIGA -+#ifdef inet_ntoa -+#undef inet_ntoa -+#endif -+#define inet_ntoa(a) Inet_NtoA(a) -+#endif -+ -+#endif /* AMIGA */ -+#endif /* _AMIGA_BSDSOCK_H */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/compat_netinet_in.h binkd/amiga/compat_netinet_in.h ---- binkd_pgul/amiga/compat_netinet_in.h 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/compat_netinet_in.h 2026-04-26 09:53:12.337417283 +0100 -@@ -0,0 +1,19 @@ -+/* -+ * compat_netinet_in.h -- Wrapper for netinet/in.h typedef clash -+ * -+ * compat_netinet_in.h is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifndef _AMIGA_COMPAT_NETINET_IN_H -+#define _AMIGA_COMPAT_NETINET_IN_H -+ -+#ifdef __GNUC__ -+#include_next -+#else -+#include -+#endif -+ -+#endif /* _AMIGA_COMPAT_NETINET_IN_H */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/dirent.c binkd/amiga/dirent.c ---- binkd_pgul/amiga/dirent.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/dirent.c 2026-04-26 11:40:07.539429919 +0100 -@@ -0,0 +1,137 @@ -+/* -+ * dirent.c -- POSIX directory scanning for AmigaOS 3 -+ * -+ * dirent.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifdef AMIGA -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "amiga/dirent.h" -+ -+/* opendir -- locks directory and allocates state for readdir() */ -+DIR *opendir(const char *path) -+{ -+ DIR *dir; -+ -+ if (!path) -+ { -+ errno = EINVAL; -+ return NULL; -+ } -+ -+ dir = (DIR *)AllocMem((LONG)sizeof(DIR), MEMF_CLEAR); -+ -+ if (!dir) -+ { -+ errno = ENOMEM; -+ return NULL; -+ } -+ -+ dir->fib = (struct FileInfoBlock *)AllocMem((LONG)sizeof(struct FileInfoBlock), MEMF_CLEAR); -+ -+ if (!dir->fib) -+ { -+ FreeMem(dir, (LONG)sizeof(DIR)); -+ errno = ENOMEM; -+ return NULL; -+ } -+ -+ dir->lock = Lock((STRPTR)path, ACCESS_READ); -+ -+ if (!dir->lock) -+ { -+ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -+ FreeMem(dir, (LONG)sizeof(DIR)); -+ errno = ENOENT; -+ return NULL; -+ } -+ -+ /* Examine the directory itself; this positions FIB for ExNext() */ -+ if (!Examine(dir->lock, dir->fib)) -+ { -+ UnLock(dir->lock); -+ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -+ FreeMem(dir, (LONG)sizeof(DIR)); -+ errno = EACCES; -+ return NULL; -+ } -+ -+ /* fib_DirEntryType > 0 means this IS a directory */ -+ if (dir->fib->fib_DirEntryType <= 0) -+ { -+ UnLock(dir->lock); -+ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -+ FreeMem(dir, (LONG)sizeof(DIR)); -+ errno = ENOTDIR; -+ return NULL; -+ } -+ -+ dir->first = 1; /* ExNext() has not been called yet */ -+ -+ return dir; -+} -+ -+/* readdir -- advances to next directory entry */ -+struct dirent *readdir(DIR *dir) -+{ -+ LONG dos_rc; -+ LONG dos_err; -+ -+ if (!dir) -+ { -+ errno = EINVAL; -+ return NULL; -+ } -+ -+ /* ExNext() advances past the last entry returned by Examine/ExNext */ -+ dos_rc = ExNext(dir->lock, dir->fib); -+ -+ if (!dos_rc) -+ { -+ dos_err = IoErr(); -+ -+ if (dos_err == ERROR_NO_MORE_ENTRIES) -+ return NULL; -+ -+ errno = EIO; -+ return NULL; -+ } -+ -+ /* Copy name into the entry buffer */ -+ strncpy(dir->entry.d_name, dir->fib->fib_FileName, AMIGA_NAME_MAX - 1); -+ dir->entry.d_name[AMIGA_NAME_MAX - 1] = '\0'; -+ dir->entry.d_ino = 0; -+ -+ return &dir->entry; -+} -+ -+/* closedir -- releases all resources associated with dir */ -+int closedir(DIR *dir) -+{ -+ if (!dir) -+ { -+ errno = EINVAL; -+ return -1; -+ } -+ -+ if (dir->lock) -+ UnLock(dir->lock); -+ -+ if (dir->fib) -+ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -+ -+ FreeMem(dir, (LONG)sizeof(DIR)); -+ return 0; -+} -+ -+#endif /* AMIGA */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/dirent.h binkd/amiga/dirent.h ---- binkd_pgul/amiga/dirent.h 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/dirent.h 2026-04-26 09:53:13.628932082 +0100 -@@ -0,0 +1,53 @@ -+/* -+ * dirent.h -- POSIX directory scanning for AmigaOS 3 -+ * -+ * dirent.h is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifndef _AMIGA_DIRENT_H -+#define _AMIGA_DIRENT_H -+ -+#ifdef AMIGA -+ -+#include -+#include -+ -+/* Maximum name length: AmigaDOS allows 107 characters */ -+#define AMIGA_NAME_MAX 108 -+ -+struct dirent -+{ -+ unsigned long d_ino; /* inode -- always 0 on AmigaDOS */ -+ char d_name[AMIGA_NAME_MAX]; /* null-terminated file name */ -+}; -+ -+/* struct utimbuf for AmigaOS 3 without ixemul */ -+#ifndef _AMIGA_UTIMBUF_DEFINED -+#define _AMIGA_UTIMBUF_DEFINED -+ -+struct utimbuf -+{ -+ long actime; /* access time (unused by SetFileDate) */ -+ long modtime; /* modification time (POSIX time_t) */ -+}; -+ -+int utime(const char *path, const struct utimbuf *times); -+#endif -+ -+typedef struct _amiga_dir -+{ -+ BPTR lock; /* directory lock */ -+ struct FileInfoBlock *fib; /* reusable FileInfoBlock */ -+ int first; /* flag: first call not yet */ -+ struct dirent entry; /* storage returned to caller */ -+} DIR; -+ -+DIR *opendir(const char *path); -+struct dirent *readdir(DIR *dir); -+int closedir(DIR *dir); -+ -+#endif /* AMIGA */ -+#endif /* _AMIGA_DIRENT_H */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop.c binkd/amiga/evloop.c ---- binkd_pgul/amiga/evloop.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/evloop.c 2026-04-26 11:46:47.344533199 +0100 -@@ -0,0 +1,509 @@ -+/* -+ * evloop.c -- non-blocking event loop for AmigaOS 3 -+ * -+ * evloop.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+/* Suppress clib bsdsocket prototypes before any socket header */ -+#ifndef CLIB_BSDSOCKET_PROTOS_H -+#define CLIB_BSDSOCKET_PROTOS_H -+#endif -+ -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#include "sys.h" -+#include "readcfg.h" -+#include "common.h" -+#include "tools.h" -+#include "protocol.h" -+#include "sem.h" -+#include "server.h" -+#include "amiga/bsdsock.h" -+#include "amiga/evloop.h" -+#include "amiga/evloop_int.h" -+#include "amiga/proto_amiga.h" -+ -+/* Externals */ -+extern SOCKET sockfd[MAX_LISTENSOCK]; -+extern int sockfd_used; -+extern int binkd_exit; -+extern int server_flag, client_flag; -+ -+/* Session table (shared with sock.c and session.c) */ -+sess_t *sessions = NULL; -+int max_sessions = 0; -+ -+/* -+ * calc_max_sessions -- Compute session slot count from config + flags -+ * Shared by init and config-reload paths -+ */ -+static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) -+{ -+ int servers = config->max_servers; -+ int clients = config->max_clients; -+ int total; -+ -+ if (servers == 0 && clients == 0) -+ { -+ Log(5, "DEBUG: Using default 2 slots (no config found)"); -+ return 2; -+ } -+ -+ Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); -+ -+ if (srv_flag && servers < 1) -+ servers = 1; -+ -+ if (cli_flag && clients < 1) -+ clients = 1; -+ -+ total = servers + clients; -+ -+ if (total < 2) -+ total = 2; -+ -+ Log(5, "DEBUG: Calculated max_sessions=%d", total); -+ -+ return total; -+} -+ -+/* init_session_table -- Allocate and zero-initialise the session array */ -+static int init_session_table(int slots) -+{ -+ int i; -+ -+ sessions = calloc(slots, sizeof(sess_t)); -+ -+ if (!sessions) -+ { -+ Log(1, "Failed to allocate session table"); -+ return 0; -+ } -+ -+ for (i = 0; i < slots; i++) -+ { -+ memset(&sessions[i], 0, sizeof(sess_t)); -+ memset(&sessions[i].state, 0, sizeof(STATE)); -+ sessions[i].fd = INVALID_SOCKET; -+ sessions[i].phase = SESS_FREE; -+ } -+ -+ return 1; -+} -+ -+/* -+ * build_fdsets -- Populate r/w fd_sets from listen sockets and sessions -+ * Returns the highest fd seen (maxfd) -+ */ -+static int build_fdsets(fd_set *r, fd_set *w) -+{ -+ int i, maxfd = 0; -+ -+ FD_ZERO(r); -+ FD_ZERO(w); -+ -+ /* server side: listen sockets */ -+ for (i = 0; i < sockfd_used; i++) -+ { -+ if (sockfd[i] != INVALID_SOCKET) -+ { -+ FD_SET(sockfd[i], r); -+ -+ if ((int)sockfd[i] > maxfd) -+ maxfd = (int)sockfd[i]; -+ } -+ } -+ -+ /* client + server sessions */ -+ for (i = 0; i < max_sessions; i++) -+ { -+ sess_t *s = &sessions[i]; -+ -+ if (s->phase == SESS_FREE || s->fd == INVALID_SOCKET) -+ continue; -+ -+ if ((int)s->fd > maxfd) -+ maxfd = (int)s->fd; -+ -+ if (s->phase == SESS_CONNECTING) -+ { -+ /* client: waiting for non-blocking connect() */ -+ FD_SET(s->fd, w); -+ } -+ else -+ { -+ /* Server or established client session */ -+ FD_SET(s->fd, r); -+ -+ if (s->state.msgs || s->state.oleft || s->state.send_eof || (s->state.out.f && !s->state.off_req_sent && !s->state.waiting_for_GOT)) -+ FD_SET(s->fd, w); -+ } -+ } -+ -+ Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); -+ return maxfd; -+} -+ -+/* -+ * handle_server_accept -- Accept new inbound connections on all listen fds -+ * Returns 0 normally, -1 if binkd_exit was set during accept -+ */ -+static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) -+{ -+ int i; -+ -+ Log(5, "DEBUG: Before accept loop"); -+ -+ for (i = 0; i < sockfd_used; i++) -+ { -+ if (FD_ISSET(sockfd[i], r)) -+ do_accept(sockfd[i], config); -+ -+ if (binkd_exit) -+ { -+ Log(5, "DEBUG: binkd_exit during accept loop"); -+ return -1; -+ } -+ } -+ -+ Log(5, "DEBUG: After accept loop"); -+ -+ return 0; -+} -+ -+/* -+ * advance_sessions -- Step every active session (server + client) -+ * Returns the number of non-free sessions processed -+ */ -+static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) -+{ -+ int i, active = 0; -+ -+ Log(5, "DEBUG: Before advance sessions"); -+ -+ for (i = 0; i < max_sessions; i++) -+ { -+ sess_t *s = &sessions[i]; -+ -+ Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); -+ -+ if (s->phase == SESS_FREE) -+ continue; -+ -+ active++; -+ -+ if (s->phase == SESS_CONNECTING) -+ { -+ /* client: Complete the non-blocking connect */ -+ if (FD_ISSET(s->fd, w)) -+ check_connect(i, config); -+ } -+ else -+ { -+ int rd = FD_ISSET(s->fd, r); -+ int wr = FD_ISSET(s->fd, w); -+ -+ /* Always step: protocol must advance internal state even -+ * when WaitSelect reports no activity (e.g. second batch -+ * EOB send, TCP FIN detection after remote closes) */ -+ do_session_step(i, rd, wr, config); -+ } -+ -+ if (binkd_exit) -+ break; -+ } -+ -+ return active; -+} -+ -+/* -+ * handle_config_reload -- Resize session table and reopen listen sockets -+ * Returns 1 if the caller should break out of the main loop, 0 otherwise -+ */ -+static int handle_config_reload(BINKD_CONFIG **config, int srv_flag, int cli_flag) -+{ -+ int i, new_max; -+ BINKD_CONFIG *nc = lock_current_config(); -+ -+ if (nc) -+ { -+ new_max = calc_max_sessions(nc, srv_flag, cli_flag); -+ -+ if (new_max != max_sessions) -+ { -+ sess_t *ns = realloc(sessions, new_max * sizeof(sess_t)); -+ -+ if (ns) -+ { -+ for (i = max_sessions; i < new_max; i++) -+ { -+ memset(&ns[i], 0, sizeof(sess_t)); -+ memset(&ns[i].state, 0, sizeof(STATE)); -+ ns[i].fd = INVALID_SOCKET; -+ ns[i].phase = SESS_FREE; -+ } -+ -+ sessions = ns; -+ max_sessions = new_max; -+ -+ Log(4, "Session table resized to %d slots", max_sessions); -+ } -+ else -+ { -+ Log(1, "Failed to resize session table, keeping current size"); -+ } -+ } -+ -+ unlock_config_structure(nc, 0); -+ } -+ -+ close_listen_sockets(); -+ *config = lock_current_config(); -+ -+ if (srv_flag && open_listen_sockets(*config) < 0) -+ { -+ unlock_config_structure(*config, 0); -+ return 1; /* fatal — break main loop */ -+ } -+ -+ unlock_config_structure(*config, 0); -+ *config = lock_current_config(); -+ return 0; -+} -+ -+/* evloop_cleanup -- Close sessions and free resources on exit */ -+static void evloop_cleanup(BINKD_CONFIG *config, int config_locked) -+{ -+ int i; -+ -+ if (config_locked) -+ unlock_config_structure(config, 0); -+ -+ if (sessions) -+ { -+ for (i = 0; i < max_sessions; i++) -+ { -+ if (sessions[i].phase == SESS_RUNNING) -+ { -+ amiga_proto_close(&sessions[i].state, config, 0); -+ -+ if (sessions[i].inbound) -+ n_servers--; -+ else -+ n_clients--; -+ } -+ else if (sessions[i].phase == SESS_CONNECTING) -+ { -+ n_clients--; -+ } -+ sess_free(i); -+ } -+ -+ free(sessions); -+ sessions = NULL; -+ } -+ -+ close_listen_sockets(); -+ amiga_sock_cleanup(); -+ Log(4, "evloop done"); -+} -+ -+/* amiga_evloop_run -- Entry point: init, then main WaitSelect() loop */ -+void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) -+{ -+ int config_locked = 0; -+ time_t last_rescan = 0; -+ time_t now; -+ fd_set r, w; -+ struct timeval tv; -+ int n, maxfd; -+ int active_sessions = 0; -+ static int idle_loops = 0; -+ -+ /* Sync globals so try_outbound() and friends see the correct flags */ -+ server_flag = srv_flag; -+ client_flag = cli_flag; -+ -+ sockfd_used = 0; -+ srand((unsigned int)time(NULL)); -+ -+ Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); -+ Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); -+ -+ /* Initialise session table */ -+ max_sessions = calc_max_sessions(config, server_flag, client_flag); -+ -+ if (max_sessions < 2) -+ { -+ Log(2, "WARNING: max_sessions=%d is too low, forcing to 2", max_sessions); -+ max_sessions = 2; -+ } -+ -+ Log(4, "evloop start (AmigaOS 3, WaitSelect, %d slots)", max_sessions); -+ -+ if (!init_session_table(max_sessions)) -+ return; -+ -+ /* server: Open listen sockets */ -+ if (server_flag && open_listen_sockets(config) < 0) -+ { -+ Log(0, "evloop: cannot open listen sockets"); -+ free(sessions); -+ sessions = NULL; -+ return; -+ } -+ -+ Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); -+ -+ /* Initial outbound attempt before waiting (important for poll -p mode) */ -+ Log(5, "DEBUG: Initial try_outbound before main loop"); -+ try_outbound(config); -+ last_rescan = time(NULL); /* Reset timer since we just did an attempt */ -+ -+ /* ===== Main loop ===== */ -+ for (;;) -+ { -+ if (binkd_exit) -+ { -+ Log(1, "binkd_exit detected at loop start, exiting"); -+ break; -+ } -+ -+ /* Build fd_sets */ -+ Log(5, "DEBUG: Building fd_sets"); -+ maxfd = build_fdsets(&r, &w); -+ -+ tv.tv_sec = 1; -+ tv.tv_usec = 0L; -+ -+ /* WaitSelect() with nfds>0 but empty fd_sets causes guru #80000006 :/ -+ * Use select(0,...) as a pure sleep when no sockets are active */ -+ if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) -+ maxfd = 1; -+ -+ Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); -+ -+ if (maxfd == 0) -+ { -+ /* No sockets yet -- use Delay() instead of select(0,...) -+ * as WaitSelect with nfds=0 can block indefinitely on AmigaOS */ -+ Delay(50); /* 1 second = 50 ticks at 50Hz PAL */ -+ n = 0; /* simulate timeout */ -+ } -+ else -+ n = select(maxfd + 1, &r, &w, NULL, &tv); -+ -+ Log(5, "DEBUG: select() returned n=%d", n); -+ -+ if (binkd_exit) -+ { -+ Log(1, "binkd_exit detected after select(), exiting"); -+ break; -+ } -+ -+ Delay(1UL); /* 1 tick = 20ms @ 50Hz, prevents CPU hogging */ -+ -+ /* Handle select errors */ -+ if (n < 0) -+ { -+ if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) -+ { -+ Log(5, "DEBUG: select interrupted, continuing"); -+ continue; -+ } -+ -+ if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) -+ { -+ Log(2, "select: %s, reopening", TCPERR()); -+ -+ close_listen_sockets(); -+ -+ if (server_flag && open_listen_sockets(config) < 0) -+ break; -+ -+ continue; -+ } -+ -+ Log(1, "select: %s", TCPERR()); -+ break; -+ } -+ else if (n == 0) -+ { -+ Log(5, "DEBUG: select timeout, continuing"); -+ } -+ -+ /* server: Accept new inbound connections */ -+ if (server_flag) -+ { -+ if (handle_server_accept(&r, config) < 0) -+ break; -+ } -+ -+ if (binkd_exit) -+ break; -+ -+ /* server + client: Advance all active sessions */ -+ active_sessions = advance_sessions(&r, &w, config); -+ -+ if (binkd_exit) -+ break; -+ -+ /* client: Time-based outbound scan + config reload */ -+ now = time(NULL); -+ -+ if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) -+ { -+ Log(5, "DEBUG: Before try_outbound"); -+ -+ try_outbound(config); -+ -+ Log(5, "DEBUG: After try_outbound"); -+ -+ if (checkcfg()) -+ { -+ if (handle_config_reload(&config, server_flag, client_flag)) -+ break; -+ -+ config_locked = 1; -+ } -+ -+ config->q_present = 0; -+ last_rescan = now; -+ } -+ -+ /* client: Poll-mode idle exit */ -+ /* Reset counter whenever there is something going on */ -+ if (n_clients > 0 || active_sessions > 0) -+ { -+ if (idle_loops > 0) -+ Log(2, "Activity detected, reset idle counter"); -+ -+ idle_loops = 0; -+ } -+ -+ if (!server_flag && active_sessions == 0 && n_clients == 0) -+ { -+ idle_loops++; -+ -+ Log(2, "Idle loop %d/2 (no server, no sessions, no clients)", idle_loops); -+ -+ if (idle_loops > 1) -+ { -+ Log(0, "the queue is empty, quitting..."); -+ break; -+ } -+ } -+ } -+ /* ===== End main loop ===== */ -+ -+ evloop_cleanup(config, config_locked); -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop.h binkd/amiga/evloop.h ---- binkd_pgul/amiga/evloop.h 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/evloop.h 2026-04-26 11:47:03.034239655 +0100 -@@ -0,0 +1,34 @@ -+/* -+ * evloop.h -- non-blocking event loop for AmigaOS 3 -+ * -+ * evloop.h is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifndef _AMIGA_EVLOOP_H -+#define _AMIGA_EVLOOP_H -+ -+#ifdef AMIGA -+ -+#include "readcfg.h" -+#include "protoco2.h" /* STATE */ -+ -+/* amiga_proto_step() return codes — also used by protocol.c */ -+#define APROTO_RUNNING 0 /* session alive, call again */ -+#define APROTO_DONE_OK 1 /* session finished, success */ -+#define APROTO_DONE_ERR 2 /* session failed */ -+ -+/* -+ * amiga_evloop_run -- entry point replacing servmgr() + clientmgr() -+ * -+ * Opens listen sockets (when server_flag), then runs a WaitSelect() -+ * loop that multiplexes sessions dynamically based on config->max_servers -+ * and config->max_clients. Minimum 2 sessions are always allocated -+ * Returns only when binkd_exit != 0 -+ */ -+void amiga_evloop_run(BINKD_CONFIG *config, int server_flag, int client_flag); -+ -+#endif /* AMIGA */ -+#endif /* _AMIGA_EVLOOP_H */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop_int.h binkd/amiga/evloop_int.h ---- binkd_pgul/amiga/evloop_int.h 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/evloop_int.h 2026-04-26 11:02:58.166969078 +0100 -@@ -0,0 +1,68 @@ -+/* -+ * evloop_int.h -- internal types shared by evloop.c, sock.c, session.c -+ * -+ * evloop_int.h is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifndef AMIGA_EVLOOP_INT_H -+#define AMIGA_EVLOOP_INT_H -+ -+#include "protoco2.h" -+#include "amiga/bsdsock.h" -+#include "ftnnode.h" -+#include "readcfg.h" -+ -+/* Session lifecycle */ -+typedef enum -+{ -+ SESS_FREE = 0, /* slot available */ -+ SESS_CONNECTING = 1, /* waiting for connect() */ -+ SESS_RUNNING = 2 /* BinkP session active */ -+} sess_phase_t; -+ -+/* Per-session state */ -+typedef struct -+{ -+ sess_phase_t phase; -+ SOCKET fd; -+ STATE state; -+ int inbound; /* 1=accepted, 0=outbound */ -+ -+ FTN_NODE *node; -+ struct addrinfo *ai_head; /* full getaddrinfo list */ -+ struct addrinfo *ai_cur; /* candidate being tried */ -+ time_t conn_start; -+ -+ char host[BINKD_FQDNLEN + 1]; -+ char port[MAXPORTSTRLEN + 1]; -+ char ip[BINKD_FQDNLEN + 1]; -+ -+ time_t last_io; -+} sess_t; -+ -+/* Globals defined in evloop.c */ -+extern sess_t *sessions; -+extern int max_sessions; -+ -+/* Defined in server.c and client.c respectively */ -+extern int n_servers; -+extern int n_clients; -+ -+/* sock.c */ -+void set_nonblock(SOCKET fd); -+int open_listen_sockets(BINKD_CONFIG *config); -+void close_listen_sockets(void); -+ -+/* session.c */ -+int sess_alloc(void); -+void sess_free(int idx); -+void do_accept(SOCKET lfd, BINKD_CONFIG *config); -+int start_connect(sess_t *s, BINKD_CONFIG *config); -+void check_connect(int idx, BINKD_CONFIG *config); -+int try_outbound(BINKD_CONFIG *config); -+void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config); -+ -+#endif /* AMIGA_EVLOOP_INT_H */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/proto_amiga.c binkd/amiga/proto_amiga.c ---- binkd_pgul/amiga/proto_amiga.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/proto_amiga.c 2026-04-26 11:52:04.887130443 +0100 -@@ -0,0 +1,269 @@ -+/* -+ * proto_amiga.c -- Amiga non-blocking BinkP protocol implementation -+ * -+ * proto_amiga.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "sys.h" -+#include "readcfg.h" -+#include "common.h" -+#include "protocol.h" -+#include "ftnaddr.h" -+#include "ftnnode.h" -+#include "ftnq.h" -+#include "tools.h" -+#include "bsy.h" -+#include "inbound.h" -+#include "protoco2.h" -+#include "prothlp.h" -+#include "binlog.h" -+#include "evloop.h" -+ -+/* External functions from protocol.c */ -+extern int init_protocol(STATE *state, SOCKET s_in, SOCKET s_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config); -+extern int banner(STATE *state, BINKD_CONFIG *config); -+extern int recv_block(STATE *state, BINKD_CONFIG *config); -+extern int send_block(STATE *state, BINKD_CONFIG *config); -+extern void bsy_touch(BINKD_CONFIG *config); -+extern FTNQ *process_rcvdlist(STATE *state, FTNQ *q, BINKD_CONFIG *config); -+extern int start_file_transfer(STATE *state, FTNQ *q, BINKD_CONFIG *config); -+extern void ND_set_status(const char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config); -+extern void deinit_protocol(STATE *state, BINKD_CONFIG *config, int status); -+extern void evt_set(EVTQ *eq); -+extern void msg_send2(STATE *state, t_msg m, char *s1, char *s2); -+ -+/* External functions from other modules */ -+extern void log_end_of_session(int err, STATE *state, BINKD_CONFIG *config); -+extern void inb_remove_partial(STATE *state, BINKD_CONFIG *config); -+extern void good_try(FTN_ADDR *fa, char *comment, BINKD_CONFIG *config); -+extern void bad_try(FTN_ADDR *fa, const char *error, const int where, BINKD_CONFIG *config); -+extern int create_poll(FTN_ADDR *fa, int flvr, BINKD_CONFIG *config); -+extern void hold_node(FTN_ADDR *fa, time_t hold_until, BINKD_CONFIG *config); -+extern int binkd_exit; -+ -+/* External variables */ -+extern int n_servers; -+ -+/* -+ * amiga_proto_open -- Initialise a session and send the BinkP banner -+ * -+ * fd : Connected socket (same fd for in and out) -+ * to : Outbound node, NULL for inbound -+ * fa : Local AKA to use, may be NULL -+ * host : Remote hostname or dotted-IP string (caller-owned, stable) -+ * port : Remote port string, may be NULL -+ * dst_ip : Numeric remote IP, may be NULL (falls back to host) -+ * config : Current config -+ * -+ * Returns 0 on success, -1 on error (caller must close fd) -+ */ -+int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config) -+{ -+ struct sockaddr_storage sa; -+ socklen_t salen = (socklen_t)sizeof(sa); -+ char ownhost[BINKD_FQDNLEN + 1]; -+ char ownserv[MAXSERVNAME + 1]; -+ int rc; -+ -+ if (!init_protocol(state, fd, fd, to, fa, config)) -+ return -1; -+ -+ /* Peer identity for logging and %ip config checks */ -+ state->ipaddr = dst_ip ? (char *)dst_ip : (char *)host; -+ state->peer_name = (host && *host) ? (char *)host : state->ipaddr; -+ -+ /* local endpoint: Not used further, skip to avoid dangling pointer */ -+ -+ Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); -+ -+ /* banner() sends M_NUL lines and ADR messages */ -+ if (!banner(state, config)) -+ return -1; -+ -+ /* refuse if server limit reached */ -+ if (!to && n_servers > config->max_servers) -+ { -+ Log(1, "too many servers"); -+ msg_send2(state, M_BSY, "Too many servers", 0); -+ -+ return -1; -+ } -+ -+ return 0; -+} -+ -+/* -+ * amiga_proto_step -- Run one recv/send iteration of the BinkP loop -+ * -+ * readable : Non-zero if the socket has incoming data (from WaitSelect) -+ * writable : Non-zero if the socket can accept outgoing data -+ * -+ * Returns APROTO_RUNNING, APROTO_DONE_OK, or APROTO_DONE_ERR -+ * -+ * This is the loop body of protocol() with the select() call removed -+ * recv_block() and send_block() already handle EWOULDBLOCK gracefully, -+ * so calling this on a non-blocking socket is safe -+ */ -+int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config) -+{ -+ FTNQ *q; -+ int no; -+ -+ if (state->io_error) -+ return APROTO_DONE_ERR; -+ -+ /* Advance outgoing file queue if nothing is being sent */ -+ if (!state->local_EOB && state->q && !state->out.f && !state->waiting_for_GOT && !state->off_req_sent && state->state != P_NULL) -+ { -+ while (1) -+ { -+ q = 0; -+ if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) -+ { -+ if (start_file_transfer(state, q, config)) -+ break; -+ } -+ else -+ { -+ q_free(state->q, config); -+ state->q = 0; -+ break; -+ } -+ } -+ } -+ -+ /* Nothing left to send — issue EOB */ -+ if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) -+ { -+ if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) -+ { -+ state->local_EOB = 1; -+ msg_send2(state, M_EOB, 0, 0); -+ } -+ } -+ -+ /* Recv step: Only when socket is readable */ -+ if (readable) -+ { -+ if (!recv_block(state, config)) -+ return APROTO_DONE_ERR; -+ } -+ -+ /* -+ * send step: drive even when writable=0 if there is buffered data, -+ * pending messages, a file mid-transfer, or an EOF to flush. -+ */ -+ if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) -+ { -+ no = send_block(state, config); -+ -+ if (!no && no != 2) -+ return APROTO_DONE_ERR; -+ } -+ -+ bsy_touch(config); -+ -+ /* Batch/Session-end detection — Mirrors the break logic in protocol() */ -+ if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) -+ { -+ if (state->rcvdlist) -+ { -+ state->q = process_rcvdlist(state, state->q, config); -+ -+ q_to_killlist(&state->killlist, &state->n_killlist, state->q); -+ free_rcvdlist(&state->rcvdlist, &state->n_rcvdlist); -+ } -+ -+ Log(6, "batch: %i msgs", state->msgs_in_batch); -+ -+ if (state->msgs_in_batch <= 2 || (state->major * 100 + state->minor <= 100)) -+ { -+ /* Session done */ -+ ND_set_status("", &state->ND_addr, state, config); -+ state->ND_addr.z = -1; -+ -+ return APROTO_DONE_OK; -+ } -+ -+ /* Start next batch */ -+ state->msgs_in_batch = 0; -+ state->remote_EOB = 0; -+ state->local_EOB = 0; -+ -+ if (OK_SEND_FILES(state, config)) -+ { -+ state->q = q_scan_boxes(state->q, state->fa, state->nfa, state->to ? 1 : 0, config); -+ state->q = q_sort(state->q, state->fa, state->nfa, config); -+ } -+ } -+ -+ return APROTO_RUNNING; -+} -+ -+/* -+ * amiga_proto_close -- Flush remaining I/O and release STATE resources -+ * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR -+ */ -+void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) -+{ -+ int no; -+ char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; -+ int status = ok ? 0 : 1; -+ -+ /* Drain inbound queue */ -+ if (!state->io_error) -+ { -+ while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) -+ Log(9, "purged %d bytes", no); -+ } -+ -+ /* Flush pending outbound messages */ -+ while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) -+ ; -+ -+ if (ok) -+ { -+ log_end_of_session(0, state, config); -+ process_killlist(state->killlist, state->n_killlist, 's'); -+ inb_remove_partial(state, config); -+ -+ if (state->to) -+ good_try(&state->to->fa, "CONNECT/BND", config); -+ } -+ else -+ { -+ log_end_of_session(1, state, config); -+ process_killlist(state->killlist, state->n_killlist, 0); -+ -+ if (!binkd_exit && state->to) -+ bad_try(&state->to->fa, "Bad session", BAD_IO, config); -+ -+ /* Restore poll flavour if files were left mid-transfer */ -+ if (state->to && tolower(state->maxflvr) != 'h') -+ { -+ Log(4, "restoring poll with '%c' flavour", state->maxflvr); -+ -+ create_poll(&state->to->fa, state->maxflvr, config); -+ } -+ } -+ -+ if (state->to && state->r_skipped_flag && config->hold_skipped > 0) -+ { -+ Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); -+ -+ hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); -+ } -+ -+ deinit_protocol(state, config, status); -+ evt_set(state->evt_queue); -+ state->evt_queue = NULL; -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/proto_amiga.h binkd/amiga/proto_amiga.h ---- binkd_pgul/amiga/proto_amiga.h 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/proto_amiga.h 2026-04-26 11:53:22.716799421 +0100 -@@ -0,0 +1,30 @@ -+/* -+ * proto_amiga.h -- Amiga non-blocking BinkP protocol implementation -+ * -+ * proto_amiga.h is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifndef _PROTO_AMIGA_H -+#define _PROTO_AMIGA_H -+ -+#include "protoco2.h" -+#include "readcfg.h" -+ -+/* amiga_proto_step() return codes */ -+#define APROTO_RUNNING 0 -+#define APROTO_DONE_OK 1 -+#define APROTO_DONE_ERR 2 -+ -+/* amiga_proto_open -- Initialise a session and send the BinkP banner */ -+int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config); -+ -+/* amiga_proto_step-- run one recv / send iteration of the BinkP loop */ -+int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config); -+ -+/* amiga_proto_close -- flush remaining I/O and release STATE resources */ -+void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok); -+ -+#endif /* _PROTO_AMIGA_H */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/rename.c binkd/amiga/rename.c ---- binkd_pgul/amiga/rename.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/amiga/rename.c 2026-04-25 19:33:26.785601976 +0100 -@@ -1,10 +1,137 @@ - #include - #include -+#include -+ -+#include -+#include /* atoi */ -+#include /* isdigit */ -+ -+#define PATHBUF 512 - - int o_rename(char *from, char *to) - { -- if (Rename((STRPTR)from, (STRPTR)to)) /* cross-volume move won't work */ -+ struct FileInfoBlock *fib; -+ char dir[PATHBUF]; -+ char base[PATHBUF]; -+ char newname[PATHBUF]; -+ char *slash; -+ ULONG max = 0; -+ BPTR dirlock; -+ char *d = NULL; -+ const char *src = NULL; -+ ULONG n = 0; -+ -+ /* Try direct rename first */ -+ if (Rename((STRPTR)from, (STRPTR)to)) -+ return 0; -+ -+ /* Split path */ -+ slash = strrchr(to, '/'); -+ -+ if (!slash) -+ slash = strrchr(to, ':'); -+ -+ if (slash) -+ { -+ ULONG len = slash - to; -+ -+ if (len >= PATHBUF) -+ len = PATHBUF - 1; -+ -+ strncpy(dir, to, len); -+ dir[len] = '\0'; -+ -+ strncpy(base, slash + 1, PATHBUF - 1); -+ base[PATHBUF - 1] = '\0'; -+ } -+ else -+ { -+ strcpy(dir, "."); -+ strncpy(base, to, PATHBUF - 1); -+ base[PATHBUF - 1] = '\0'; -+ } -+ -+ /* Lock directory */ -+ dirlock = Lock((STRPTR)dir, ACCESS_READ); -+ -+ if (!dirlock) -+ { -+ errno = ENOENT; -+ return -1; -+ } -+ -+ fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); -+ -+ if (!fib) -+ { -+ UnLock(dirlock); -+ errno = ENOMEM; -+ return -1; -+ } -+ -+ /* Scan directory safely under lock */ -+ if (Examine(dirlock, fib)) -+ { -+ while (ExNext(dirlock, fib)) -+ { -+ if (strncmp(fib->fib_FileName, base, strlen(base)) == 0) -+ { -+ const char *p = NULL; -+ -+ p = fib->fib_FileName + strlen(base); -+ -+ if (*p != '.') -+ { -+ continue; -+ } -+ -+ /* .001 style */ -+ if (isdigit((UBYTE)p[1]) && isdigit((UBYTE)p[2]) && isdigit((UBYTE)p[3])) -+ { -+ n = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); -+ if (n > max) -+ max = n; -+ } -+ -+ /* FIDO volume style (.mo0 .th1 etc) */ -+ if (isdigit((UBYTE)p[1]) && !isdigit((UBYTE)p[2])) -+ { -+ n = p[1] - '0'; -+ if (n > max) -+ max = n; -+ } -+ } -+ } -+ } -+ -+ FreeDosObject(DOS_FIB, fib); -+ UnLock(dirlock); -+ -+ /* Build new name */ -+ d = newname; -+ src = to; -+ n = max + 1; -+ -+ if (n > 999) -+ n = 0; -+ -+ /* Copy base */ -+ while (*src && (d - newname) < (PATHBUF - 5)) -+ *d++ = *src++; -+ -+ *d++ = '.'; -+ -+ /* Manual 3-digit write */ -+ *d++ = '0' + (n / 100); -+ n %= 100; -+ *d++ = '0' + (n / 10); -+ *d++ = '0' + (n % 10); -+ *d = '\0'; -+ -+ /* Rename */ -+ if (Rename((STRPTR)from, (STRPTR)newname)) -+ return 0; -+ -+ errno = EACCES; - return -1; -- else -- return 0; - } -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/rfc2553_amiga.c binkd/amiga/rfc2553_amiga.c ---- binkd_pgul/amiga/rfc2553_amiga.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/rfc2553_amiga.c 2026-04-26 11:54:19.321503648 +0100 -@@ -0,0 +1,323 @@ -+/* -+ * rfc2553_amiga.c -- getaddrinfo/getnameinfo fallback for AmigaOS 3 -+ * -+ * rfc2553_amiga.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifdef AMIGA -+ -+#include "amiga/bsdsock.h" /* LP stubs + SocketBase */ -+#include "rfc2553.h" /* sets HAVE_GETADDRINFO / HAVE_GETNAMEINFO */ -+#include "sem.h" -+ -+#include -+#include -+#include -+#include -+ -+#define safe_strncpy(dst, src, n) \ -+ do \ -+ { \ -+ strncpy((dst), (src), (n)); \ -+ (dst)[(n) - 1] = '\0'; \ -+ } while (0) -+ -+#ifndef HAVE_GETADDRINFO -+ -+void freeaddrinfo(struct addrinfo *ai); /* forward decl */ -+ -+int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) -+{ -+ struct addrinfo **tail = res; -+ struct hostent *hent = NULL; -+ unsigned int port; -+ int proto; -+ const char *end; -+ char **addrp; -+ -+ static char passive_dummy = '\0'; -+ char *passive_list[2] = {&passive_dummy, NULL}; -+ -+ if (!res) -+ { -+ return EAI_UNKNOWN; -+ } -+ -+ *res = NULL; -+ -+ port = servname ? htons((unsigned short)strtol(servname, (char **)&end, 0)) : 0; -+ proto = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; -+ -+ lockresolvsem(); -+ -+ if (servname && end != servname + strlen(servname)) -+ { -+ struct servent *se = NULL; -+ -+ if (!hints || hints->ai_socktype == SOCK_STREAM) -+ se = getservbyname((char *)servname, "tcp"); -+ -+ if (hints && hints->ai_socktype == SOCK_DGRAM) -+ se = getservbyname((char *)servname, "udp"); -+ -+ if (!se) -+ { -+ releaseresolvsem(); -+ return EAI_NONAME; -+ } -+ -+ port = se->s_port; -+ -+ if (strcmp((char *)se->s_proto, "tcp") == 0) -+ proto = SOCK_STREAM; -+ else if (strcmp((char *)se->s_proto, "udp") == 0) -+ proto = SOCK_DGRAM; -+ else -+ { -+ releaseresolvsem(); -+ return EAI_NONAME; -+ } -+ -+ if (hints && hints->ai_socktype && hints->ai_socktype != proto) -+ { -+ releaseresolvsem(); -+ return EAI_SERVICE; -+ } -+ } -+ -+ if (!hints || !(hints->ai_flags & AI_PASSIVE)) -+ { -+ unsigned long nip = inet_addr((char *)nodename); -+ -+ if (nip != (unsigned long)INADDR_NONE) -+ { -+ struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); -+ struct sockaddr_in *sin; -+ -+ if (!ai) -+ { -+ releaseresolvsem(); -+ return EAI_MEMORY; -+ } -+ *tail = ai; -+ -+ sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); -+ -+ if (!sin) -+ { -+ free(ai); -+ releaseresolvsem(); -+ return EAI_MEMORY; -+ } -+ -+ ai->ai_family = AF_INET; -+ ai->ai_socktype = proto; -+ ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; -+ ai->ai_addrlen = sizeof(*sin); -+ ai->ai_addr = (struct sockaddr *)sin; -+ sin->sin_family = AF_INET; -+ sin->sin_port = port; -+ sin->sin_addr.s_addr = nip; -+ -+ releaseresolvsem(); -+ return 0; -+ } -+ -+ hent = gethostbyname((char *)nodename); -+ -+ if (!hent) -+ { -+ int herr = errno; -+ releaseresolvsem(); -+ return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL -+ : EAI_NONAME; -+ } -+ -+ if (!hent->h_addr_list || !hent->h_addr_list[0]) -+ { -+ releaseresolvsem(); -+ return EAI_NONAME; -+ } -+ -+ addrp = hent->h_addr_list; -+ } -+ else -+ { -+ addrp = passive_list; -+ } -+ -+ for (; *addrp; addrp++) -+ { -+ struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); -+ struct sockaddr_in *sin; -+ -+ if (!ai) -+ { -+ releaseresolvsem(); -+ freeaddrinfo(*res); -+ *res = NULL; -+ return EAI_MEMORY; -+ } -+ -+ if (!*res) -+ *res = ai; -+ *tail = ai; -+ tail = &ai->ai_next; -+ -+ sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); -+ -+ if (!sin) -+ { -+ releaseresolvsem(); -+ freeaddrinfo(*res); -+ *res = NULL; -+ return EAI_MEMORY; -+ } -+ -+ ai->ai_family = AF_INET; -+ ai->ai_socktype = proto; -+ ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; -+ ai->ai_addrlen = sizeof(*sin); -+ ai->ai_addr = (struct sockaddr *)sin; -+ sin->sin_family = AF_INET; -+ sin->sin_port = port; -+ -+ if (!hints || !(hints->ai_flags & AI_PASSIVE)) -+ { -+ size_t cpylen = sizeof(sin->sin_addr); -+ -+ if (hent->h_length > 0 && (size_t)hent->h_length < cpylen) -+ cpylen = (size_t)hent->h_length; -+ -+ memcpy(&sin->sin_addr, *addrp, cpylen); -+ } -+ } -+ -+ releaseresolvsem(); -+ return 0; -+} -+ -+void freeaddrinfo(struct addrinfo *ai) -+{ -+ struct addrinfo *next; -+ -+ while (ai) -+ { -+ free(ai->ai_addr); -+ next = ai->ai_next; -+ free(ai); -+ ai = next; -+ } -+} -+ -+static const char *ai_errlist[] = -+ { -+ "Success", -+ "hostname nor servname provided, or not known", -+ "Temporary failure in name resolution", -+ "Non-recoverable failure in name resolution", -+ "No address associated with hostname", -+ "ai_family not supported", -+ "ai_socktype not supported", -+ "service name not supported for ai_socktype", -+ "Address family for hostname not supported", -+ "Memory allocation failure", -+ "System error returned in errno", -+ "Unknown error", -+}; -+ -+char *gai_strerror(int ecode) -+{ -+ if (ecode > 0 || ecode < EAI_UNKNOWN) -+ ecode = EAI_UNKNOWN; -+ return (char *)ai_errlist[-ecode]; -+} -+ -+#endif /* !HAVE_GETADDRINFO */ -+ -+#ifndef HAVE_GETNAMEINFO -+ -+#ifndef NI_DATAGRAM -+#define NI_DATAGRAM (1 << 4) -+#endif -+ -+int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) -+{ -+ const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; -+ -+ (void)salen; -+ -+ if (sa->sa_family != AF_INET) -+ return EAI_ADDRFAMILY; -+ -+ if (host && hostlen > 0) -+ { -+ if (!(flags & NI_NUMERICHOST)) -+ { -+ struct hostent *he; -+ -+ lockresolvsem(); -+ he = gethostbyaddr((char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET); -+ -+ if (he) -+ { -+ safe_strncpy(host, (char *)he->h_name, hostlen); -+ releaseresolvsem(); -+ } -+ else -+ { -+ int herr = errno; -+ releaseresolvsem(); -+ if (flags & NI_NAMEREQD) -+ return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL -+ : EAI_NONAME; -+ flags |= NI_NUMERICHOST; -+ } -+ } -+ -+ if (flags & NI_NUMERICHOST) -+ { -+ lockhostsem(); -+ safe_strncpy(host, (char *)Inet_NtoA(sin->sin_addr.s_addr), hostlen); -+ releasehostsem(); -+ } -+ } -+ -+ if (serv && servlen > 0) -+ { -+ if (!(flags & NI_NUMERICSERV)) -+ { -+ struct servent *se; -+ -+ lockresolvsem(); -+ -+ se = (flags & NI_DATAGRAM) ? getservbyport(ntohs(sin->sin_port), "udp") : getservbyport(ntohs(sin->sin_port), "tcp"); -+ -+ if (se) -+ { -+ safe_strncpy(serv, (char *)se->s_name, servlen); -+ releaseresolvsem(); -+ } -+ else -+ { -+ releaseresolvsem(); -+ -+ if (flags & NI_NAMEREQD) -+ return EAI_NONAME; -+ -+ flags |= NI_NUMERICSERV; -+ } -+ } -+ -+ if (flags & NI_NUMERICSERV) -+ snprintf(serv, servlen, "%u", ntohs(sin->sin_port)); -+ } -+ -+ return 0; -+} -+ -+#endif /* !HAVE_GETNAMEINFO */ -+#endif /* AMIGA */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/sem.c binkd/amiga/sem.c ---- binkd_pgul/amiga/sem.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/amiga/sem.c 2026-04-25 19:33:46.416919700 +0100 -@@ -1,29 +1,100 @@ - /* - * Amiga semaphores - */ -+ - #include - #include --#include -+#include -+#include -+ -+extern void Log(int lev, char *s, ...); -+ -+int _InitSem(void *vpSem) -+{ -+ memset(vpSem, 0, sizeof(struct SignalSemaphore)); -+ InitSemaphore((struct SignalSemaphore *)vpSem); -+ return 0; -+} -+ -+int _CleanSem(void *vpSem) -+{ -+ return 0; -+} -+ -+int _LockSem(void *vpSem) -+{ -+ ObtainSemaphore((struct SignalSemaphore *)vpSem); -+ return 0; -+} -+ -+int _ReleaseSem(void *vpSem) -+{ -+ ReleaseSemaphore((struct SignalSemaphore *)vpSem); -+ return 0; -+} - --extern void Log (int lev, char *s,...); -+int _InitEventSem(EVENTSEM *sem) -+{ -+ if (!sem) -+ return -1; - -+ sem->waiter = NULL; - --int _InitSem(void *vpSem) { -- memset(vpSem, 0, sizeof (struct SignalSemaphore)); -- InitSemaphore ((struct SignalSemaphore*)vpSem); -- return(0); -+ sem->sigbit = AllocSignal(-1); -+ -+ if (sem->sigbit == (ULONG)-1) -+ return -1; -+ -+ return 0; - } - --int _CleanSem(void *vpSem) { -- return (0); -+int _CleanEventSem(EVENTSEM *sem) -+{ -+ if (!sem) -+ return -1; -+ -+ if (sem->sigbit != (ULONG)-1) -+ { -+ FreeSignal((LONG)sem->sigbit); -+ sem->sigbit = (ULONG)-1; -+ } -+ -+ sem->waiter = NULL; -+ return 0; - } - --int _LockSem(void *vpSem) { -- ObtainSemaphore ((struct SignalSemaphore *)vpSem); -- return (0); -+int _PostSem(EVENTSEM *sem) -+{ -+ if (!sem) -+ return -1; -+ -+ if (sem->waiter && sem->sigbit != (ULONG)-1) -+ { -+ Signal((struct Task *)sem->waiter, 1UL << sem->sigbit); -+ } -+ -+ return 0; - } - --int _ReleaseSem(void *vpSem) { -- ReleaseSemaphore ((struct SignalSemaphore *)vpSem); -- return (0); -+int _WaitSem(EVENTSEM *sem, int sec) -+{ -+ ULONG mask; -+ struct Task *me; -+ -+ if (!sem || sem->sigbit == (ULONG)-1) -+ return -1; -+ -+ me = FindTask(NULL); -+ sem->waiter = me; -+ -+ /* Wait on SIGBREAKF_CTRL_C to avoid hanging on race */ -+ mask = Wait((1UL << sem->sigbit) | SIGBREAKF_CTRL_C); -+ -+ sem->waiter = NULL; -+ -+ /* Return timeout/break indication if only CTRL_C fired */ -+ if (!(mask & (1UL << sem->sigbit)) && (mask & SIGBREAKF_CTRL_C)) -+ return -1; -+ -+ return 0; - } -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/session.c binkd/amiga/session.c ---- binkd_pgul/amiga/session.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/session.c 2026-04-26 11:57:30.521108284 +0100 -@@ -0,0 +1,462 @@ -+/* -+ * session.c -- session management for AmigaOS 3 binkd -+ * -+ * session.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include -+#include -+ -+#include -+#include -+#include -+#include -+ -+#include "sys.h" -+#include "iphdr.h" -+#include "readcfg.h" -+#include "common.h" -+#include "tools.h" -+#include "client.h" -+#include "protocol.h" -+#include "ftnq.h" -+#include "ftnnode.h" -+#include "ftnaddr.h" -+#include "bsy.h" -+#include "iptools.h" -+#include "rfc2553.h" -+#include "srv_gai.h" -+#include "amiga/bsdsock.h" -+#include "amiga/evloop_int.h" -+#include "amiga/proto_amiga.h" -+ -+extern int binkd_exit; -+extern int ext_rand; -+extern int client_flag; -+extern int poll_flag; -+ -+/* Session table */ -+int sess_alloc(void) -+{ -+ int i; -+ -+ for (i = 0; i < max_sessions; i++) -+ { -+ if (sessions[i].phase == SESS_FREE) -+ { -+ memset(&sessions[i], 0, sizeof(sess_t)); -+ sessions[i].fd = INVALID_SOCKET; -+ sessions[i].phase = SESS_FREE; -+ return i; -+ } -+ } -+ -+ return -1; -+} -+ -+void sess_free(int idx) -+{ -+ sess_t *s = &sessions[idx]; -+ -+ if (s->fd != INVALID_SOCKET) -+ { -+ soclose(s->fd); -+ s->fd = INVALID_SOCKET; -+ } -+ -+ if (s->ai_head) -+ { -+ freeaddrinfo(s->ai_head); -+ s->ai_head = NULL; -+ } -+ -+ memset(&s->state, 0, sizeof(STATE)); -+ s->phase = SESS_FREE; -+} -+ -+/* Inbound: accept a new connection */ -+void do_accept(SOCKET lfd, BINKD_CONFIG *config) -+{ -+ struct sockaddr_storage sa; -+ socklen_t salen = (socklen_t)sizeof(sa); -+ SOCKET fd; -+ int idx; -+ sess_t *s; -+ char host[BINKD_FQDNLEN + 1]; -+ char ip[BINKD_FQDNLEN + 1]; -+ -+ fd = accept(lfd, (struct sockaddr *)&sa, &salen); -+ -+ if (fd == INVALID_SOCKET) -+ { -+ if (TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN) -+ Log(1, "accept(): %s", TCPERR()); -+ -+ return; -+ } -+ -+ if (binkd_exit) -+ { -+ soclose(fd); -+ return; -+ } -+ -+ idx = sess_alloc(); -+ -+ if (idx < 0) -+ { -+ Log(1, "session table full, refusing inbound"); -+ soclose(fd); -+ return; -+ } -+ -+ /* getnameinfo() Is unreliable on AmiTCP: use inet_ntoa directly */ -+ if (((struct sockaddr *)&sa)->sa_family == AF_INET) -+ { -+ struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; -+ strnzcpy(ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); -+ } -+ else -+ { -+ strnzcpy(ip, "unknown", BINKD_FQDNLEN); -+ } -+ -+ /* Backresolv not supported on AmiTCP: always use IP as host */ -+ strnzcpy(host, ip, BINKD_FQDNLEN); -+ -+ set_nonblock(fd); -+ -+ s = &sessions[idx]; -+ s->fd = fd; -+ s->inbound = 1; -+ s->node = NULL; -+ s->ai_head = NULL; -+ s->last_io = time(NULL); -+ strnzcpy(s->host, host, BINKD_FQDNLEN); -+ strnzcpy(s->ip, ip, BINKD_FQDNLEN); -+ s->port[0] = '\0'; -+ -+ if (amiga_proto_open(&s->state, fd, NULL, NULL, s->host, NULL, s->ip, config) != 0) -+ { -+ Log(1, "proto_open failed for %s", ip); -+ sess_free(idx); -+ return; -+ } -+ -+ s->phase = SESS_RUNNING; -+ n_servers++; -+ Log(4, "inbound slot[%d] from %s", idx, ip); -+} -+ -+/* Outbound: Non-blocking connect() */ -+int start_connect(sess_t *s, BINKD_CONFIG *config) -+{ -+ SOCKET fd; -+ int rc; -+ -+ s->ip[0] = '\0'; -+ s->port[0] = '\0'; -+ -+ fd = socket(s->ai_cur->ai_family, s->ai_cur->ai_socktype, s->ai_cur->ai_protocol); -+ -+ if (fd == INVALID_SOCKET) -+ { -+ Log(1, "outbound socket(): %s", TCPERR()); -+ return -1; -+ } -+ -+ /* getnameinfo() is unreliable on AmiTCP: may return rc=0 with garbage -+ * Use inet_ntoa/ntohs directly */ -+ if (s->ai_cur->ai_family == AF_INET) -+ { -+ struct sockaddr_in *sa4 = (struct sockaddr_in *)s->ai_cur->ai_addr; -+ strnzcpy(s->ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); -+ snprintf(s->port, MAXPORTSTRLEN, "%u", (unsigned)ntohs(sa4->sin_port)); -+ } -+ else -+ { -+ strnzcpy(s->ip, "unknown", BINKD_FQDNLEN); -+ strnzcpy(s->port, "0", MAXPORTSTRLEN); -+ } -+ -+ Log(4, "connecting %s [%s]:%s", s->host, s->ip, s->port); -+ -+ if (config->bindaddr[0]) -+ { -+ struct addrinfo src_h, *src_ai; -+ memset(&src_h, 0, sizeof(src_h)); -+ src_h.ai_family = s->ai_cur->ai_family; -+ src_h.ai_socktype = SOCK_STREAM; -+ src_h.ai_protocol = IPPROTO_TCP; -+ -+ if (getaddrinfo(config->bindaddr, NULL, &src_h, &src_ai) == 0) -+ { -+ bind(fd, src_ai->ai_addr, (int)src_ai->ai_addrlen); -+ freeaddrinfo(src_ai); -+ } -+ } -+ -+ set_nonblock(fd); -+ -+ rc = connect(fd, s->ai_cur->ai_addr, (int)s->ai_cur->ai_addrlen); -+ -+ if (rc == 0 || TCPERRNO == EINPROGRESS || TCPERRNO == EWOULDBLOCK) -+ { -+ s->fd = fd; -+ s->conn_start = time(NULL); -+ return 0; -+ } -+ -+ Log(1, "connect %s: %s", s->host, TCPERR()); -+ -+ bad_try(&s->node->fa, TCPERR(), BAD_CALL, config); -+ soclose(fd); -+ -+ return -1; -+} -+ -+int try_outbound(BINKD_CONFIG *config) -+{ -+ FTN_NODE *node; -+ sess_t *s; -+ int idx, rc; -+ struct addrinfo hints; -+ char dest[FTN_ADDR_SZ + 1]; -+ char host[BINKD_FQDNLEN + 5 + 1]; -+ char port[MAXPORTSTRLEN + 1]; -+ -+ if (!client_flag) -+ return 0; -+ -+ if (!config->q_present) -+ { -+ q_free(SCAN_LISTED, config); -+ -+ if (config->printq) -+ Log(-1, "scan\r"); -+ -+ q_scan(SCAN_LISTED, config); -+ config->q_present = 1; -+ -+ if (config->printq) -+ { -+ q_list(stderr, SCAN_LISTED, config); -+ Log(-1, "idle\r"); -+ } -+ } -+ -+ if (n_clients >= config->max_clients) -+ return 0; -+ -+ node = q_next_node(config); -+ -+ if (!node) -+ return 0; -+ -+ ftnaddress_to_str(dest, &node->fa); -+ -+ if (!bsy_test(&node->fa, F_BSY, config) || !bsy_test(&node->fa, F_CSY, config)) -+ { -+ Log(4, "%s busy", dest); -+ return 0; -+ } -+ -+ idx = sess_alloc(); -+ -+ if (idx < 0) -+ { -+ Log(2, "table full, deferring %s", dest); -+ return 0; -+ } -+ -+ s = &sessions[idx]; -+ memset(s, 0, sizeof(*s)); -+ s->fd = INVALID_SOCKET; -+ s->node = node; -+ s->inbound = 0; -+ -+ rc = get_host_and_port(1, host, port, node->hosts, &node->fa, config); -+ -+ if (rc <= 0) -+ { -+ Log(1, "%s: bad host list", dest); -+ sess_free(idx); -+ -+ return 0; -+ } -+ -+ strnzcpy(s->host, host, BINKD_FQDNLEN); -+ strnzcpy(s->port, port, MAXPORTSTRLEN); -+ -+ memset(&hints, 0, sizeof(hints)); -+ hints.ai_family = node->IP_afamily; -+ hints.ai_socktype = SOCK_STREAM; -+ hints.ai_protocol = IPPROTO_TCP; -+ -+ rc = srv_getaddrinfo(host, port, &hints, &s->ai_head); -+ -+ if (rc != 0) -+ { -+ Log(1, "%s: getaddrinfo error code=%d: %s", dest, rc, gai_strerror(rc)); -+ -+ bad_try(&node->fa, "getaddrinfo failed", BAD_CALL, config); -+ sess_free(idx); -+ -+ return 0; -+ } -+ -+ s->ai_cur = s->ai_head; -+ -+ if (start_connect(s, config) != 0) -+ { -+ sess_free(idx); -+ return 0; -+ } -+ -+ s->phase = SESS_CONNECTING; -+ n_clients++; -+ -+ Log(4, "outbound slot[%d] -> %s", idx, dest); -+ -+ return 1; -+} -+ -+/* Check completion of async connect() */ -+void check_connect(int idx, BINKD_CONFIG *config) -+{ -+ sess_t *s = &sessions[idx]; -+ int err = 0; -+ socklen_t el = (socklen_t)sizeof(err); -+ int tmo; -+ -+ tmo = config->connect_timeout ? config->connect_timeout : 30; -+ -+ if ((int)(time(NULL) - s->conn_start) >= tmo) -+ { -+ Log(1, "connect timeout -> %s", s->host); -+ -+ bad_try(&s->node->fa, "Timeout", BAD_CALL, config); -+ n_clients--; -+ sess_free(idx); -+ -+ return; -+ } -+ -+ getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &el); -+ -+ if (err) -+ { -+ Log(1, "connect -> %s: %s", s->host, strerror(err)); -+ -+ bad_try(&s->node->fa, strerror(err), BAD_CALL, config); -+ -+ soclose(s->fd); -+ s->fd = INVALID_SOCKET; -+ s->ai_cur = s->ai_cur->ai_next; -+ -+ if (s->ai_cur && start_connect(s, config) == 0) -+ return; /* trying next address */ -+ -+ n_clients--; -+ sess_free(idx); -+ -+ return; -+ } -+ -+ Log(4, "connected -> %s [%s]", s->host, s->ip); -+ ext_rand = rand(); -+ -+ if (amiga_proto_open(&s->state, s->fd, s->node, NULL, s->host, s->port, s->ip, config) != 0) -+ { -+ Log(1, "proto_open failed for %s", s->host); -+ -+ n_clients--; -+ sess_free(idx); -+ return; -+ } -+ -+ s->phase = SESS_RUNNING; -+ s->last_io = time(NULL); -+} -+ -+/* Run one protocol step on an active session */ -+void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config) -+{ -+ sess_t *s = &sessions[idx]; -+ int rc; -+ int tmo; -+ -+ tmo = config->nettimeout ? config->nettimeout : 300; -+ -+ if ((int)(time(NULL) - s->last_io) >= tmo) -+ { -+ Log(1, "slot[%d] net timeout", idx); -+ -+ if (s->node) -+ bad_try(&s->node->fa, "Timeout", BAD_IO, config); -+ -+ amiga_proto_close(&s->state, config, 0); -+ -+ if (s->inbound) -+ n_servers--; -+ else -+ n_clients--; -+ -+ sess_free(idx); -+ -+ return; -+ } -+ -+ if (s->fd == INVALID_SOCKET) -+ { -+ Log(1, "slot[%d] invalid socket, closing session", idx); -+ -+ if (s->node) -+ bad_try(&s->node->fa, "Invalid socket", BAD_IO, config); -+ -+ if (s->inbound) -+ n_servers--; -+ else -+ n_clients--; -+ -+ sess_free(idx); -+ -+ return; -+ } -+ -+ /* WaitSelect() may not report a readable socket when the remote has -+ * sent a TCP END. Probe with MSG_PEEK so recv_block() sees the EOF */ -+ if (!rd && !wr && s->state.state != P_NULL) -+ { -+ char peek; -+ int pr = recv(s->fd, &peek, 1, MSG_PEEK); -+ -+ if (pr == 0 || (pr < 0 && TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN)) -+ rd = 1; -+ } -+ -+ rc = amiga_proto_step(&s->state, rd, wr, config); -+ -+ if (rd || wr) -+ s->last_io = time(NULL); -+ -+ if (rc == APROTO_RUNNING) -+ return; -+ -+ amiga_proto_close(&s->state, config, rc == APROTO_DONE_OK); -+ -+ Log(4, "slot[%d] %s", idx, rc == APROTO_DONE_OK ? "OK" : "ERR"); -+ -+ if (s->inbound) -+ n_servers--; -+ else -+ n_clients--; -+ -+ sess_free(idx); -+ -+ if (poll_flag && n_clients == 0 && n_servers == 0) -+ binkd_exit = 1; -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/sock.c binkd/amiga/sock.c ---- binkd_pgul/amiga/sock.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/sock.c 2026-04-26 11:59:26.645813693 +0100 -@@ -0,0 +1,130 @@ -+/* -+ * sock.c -- listen socket management for AmigaOS 3 -+ * -+ * sock.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include -+#include -+ -+#include -+#include -+ -+#include "sys.h" -+#include "readcfg.h" -+#include "tools.h" -+#include "server.h" -+#include "rfc2553.h" -+#include "amiga/bsdsock.h" -+#include "amiga/evloop_int.h" -+ -+extern SOCKET sockfd[]; -+extern int sockfd_used; -+extern int server_flag; -+ -+void set_nonblock(SOCKET fd) -+{ -+ long flag = 1L; -+ -+ if (IoctlSocket(fd, FIONBIO, (char *)&flag) != 0) -+ Log(2, "IoctlSocket(FIONBIO) failed: %s", TCPERR()); -+} -+ -+int open_listen_sockets(BINKD_CONFIG *config) -+{ -+ struct listenchain *ll; -+ struct addrinfo hints, *ai, *head; -+ int err, opt = 1; -+ -+ memset(&hints, 0, sizeof(hints)); -+ hints.ai_flags = AI_PASSIVE; -+ hints.ai_family = PF_UNSPEC; -+ hints.ai_socktype = SOCK_STREAM; -+ hints.ai_protocol = IPPROTO_TCP; -+ -+ sockfd_used = 0; -+ -+ for (ll = config->listen.first; ll; ll = ll->next) -+ { -+ err = getaddrinfo(ll->addr[0] ? ll->addr : NULL, ll->port, &hints, &head); -+ -+ if (err) -+ { -+ Log(1, "listen getaddrinfo(%s:%s): %s", ll->addr[0] ? ll->addr : "*", ll->port, gai_strerror(err)); -+ return -1; -+ } -+ -+ for (ai = head; ai && sockfd_used < MAX_LISTENSOCK; ai = ai->ai_next) -+ { -+ SOCKET fd; -+ int retries = 6; -+ -+ fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); -+ -+ if (fd == INVALID_SOCKET) -+ { -+ Log(1, "listen socket(): %s", TCPERR()); -+ continue; -+ } -+ -+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, (int)sizeof(opt)) != 0) -+ Log(2, "setsockopt(SO_REUSEADDR) failed: %s", TCPERR()); -+ -+ /* Bsdsocket may hold the port briefly after socket close */ -+ while (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) != 0) -+ { -+ if (--retries == 0) -+ { -+ Log(1, "listen bind(): %s", TCPERR()); -+ -+ soclose(fd); -+ freeaddrinfo(head); -+ return -1; -+ } -+ -+ Log(2, "bind retry in 2s: %s", TCPERR()); -+ -+ Delay(100UL); /* 100 ticks = 2s @ 50Hz */ -+ } -+ -+ if (listen(fd, 5) != 0) -+ { -+ Log(1, "listen(): %s", TCPERR()); -+ -+ soclose(fd); -+ freeaddrinfo(head); -+ return -1; -+ } -+ -+ set_nonblock(fd); -+ sockfd[sockfd_used] = fd; -+ sockfd_used++; -+ } -+ -+ freeaddrinfo(head); -+ -+ Log(3, "listening on %s:%s", -+ ll->addr[0] ? ll->addr : "*", ll->port); -+ } -+ -+ if (sockfd_used == 0 && server_flag) -+ { -+ Log(1, "evloop: no listen sockets opened"); -+ return -1; -+ } -+ -+ return 0; -+} -+ -+void close_listen_sockets(void) -+{ -+ int i; -+ -+ for (i = 0; i < sockfd_used; i++) -+ soclose(sockfd[i]); -+ -+ sockfd_used = 0; -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/utime.c binkd/amiga/utime.c ---- binkd_pgul/amiga/utime.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/amiga/utime.c 2026-04-26 11:59:41.408046130 +0100 -@@ -0,0 +1,77 @@ -+/* -+ * utime.c -- utime() stub for AmigaOS 3 without ixemul -+ * -+ * utime.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifdef AMIGA -+ -+#include -+#include -+#include -+#include -+ -+#include "amiga/dirent.h" /* declares struct utimbuf and utime() prototype */ -+ -+/* Days between AmigaDOS epoch (1978-01-01) and POSIX epoch (1970-01-01) */ -+#define AMIGA_EPOCH_DELTA_DAYS 2922UL -+#define SECONDS_PER_DAY 86400UL -+#define SECONDS_PER_MINUTE 60UL -+ -+int utime(const char *path, const struct utimbuf *times) -+{ -+ struct DateStamp ds; -+ LONG seconds_today; -+ LONG total_seconds; -+ -+ if (!path) -+ { -+ errno = EINVAL; -+ return -1; -+ } -+ -+ if (!times) -+ { -+ /* Use current time */ -+ DateStamp(&ds); -+ } -+ else -+ { -+ LONG t = (LONG)times->modtime; -+ -+ if (t < (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY)) -+ { -+ /* Time predates AmigaDOS epoch -- clamp to epoch */ -+ t = 0; -+ } -+ else -+ { -+ t -= (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY); -+ } -+ -+ ds.ds_Days = (LONG)(t / (LONG)SECONDS_PER_DAY); -+ total_seconds = t % (LONG)SECONDS_PER_DAY; -+ ds.ds_Minute = (LONG)(total_seconds / (LONG)SECONDS_PER_MINUTE); -+ seconds_today = total_seconds % (LONG)SECONDS_PER_MINUTE; -+ ds.ds_Tick = seconds_today * (LONG)TICKS_PER_SECOND; -+ } -+ -+ if (!SetFileDate((STRPTR)path, &ds)) -+ { -+ /* Most likely cause: file does not exist or is write-protected */ -+ LONG err = IoErr(); -+ -+ if (err == ERROR_OBJECT_NOT_FOUND || err == ERROR_DIR_NOT_FOUND) -+ errno = ENOENT; -+ else -+ errno = EACCES; -+ return -1; -+ } -+ -+ return 0; -+} -+ -+#endif /* AMIGA */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/binkd.c binkd/binkd.c ---- binkd_pgul/binkd.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/binkd.c 2026-04-26 13:20:44.986212073 +0100 -@@ -54,6 +54,10 @@ - #include "unix/daemonize.h" - #endif - -+#ifdef AMIGA -+/* amiga/bsdsock.h pulled in via iphdr.h for AMIGA */ -+#include "amiga/evloop.h" -+#endif - #ifdef WIN32 - #include "nt/service.h" - #include "nt/w32tools.h" -@@ -62,9 +66,11 @@ - #endif - #endif - -+#include "bsycleanup.h" -+ - #include "confopt.h" - --#ifdef HAVE_THREADS -+#if defined(HAVE_THREADS) || defined(AMIGA) - MUTEXSEM hostsem; - MUTEXSEM resolvsem; - MUTEXSEM lsem; -@@ -94,9 +100,13 @@ - char *configpath = NULL; /* Config file name */ - char **saved_envp; - --#ifdef HAVE_FORK -+/* mypid: needed by HAVE_FORK and AMIGA targets */ -+#if defined(HAVE_FORK) || defined(AMIGA) -+int mypid; -+#endif - --int mypid, got_sighup, got_sigchld; -+#ifdef HAVE_FORK -+int got_sighup, got_sigchld; - - void chld (int *childcount) - { -@@ -195,9 +205,13 @@ - #endif - " -C reload on config change\n" - " -c run client only\n" -+#ifndef AMIGA - " -i run server on stdin/stdout pipe (by inetd or other)\n" -+#endif - " -f node run server protected session with this node\n" -+#ifndef AMIGA - " -a ip assume remote address when running with '-i' switch\n" -+#endif - #if defined(BINKD9X) - " -t cmd (start|stop|restart|status|install|uninstall) service(s)\n" - " -S name set Win9x service name, all - use all services\n" -@@ -312,12 +326,14 @@ - case 'c': - client_flag = 1; - break; -+#ifndef AMIGA - case 'i': - inetd_flag = 1; - break; - case 'a': /* remote IP address */ - remote_addr = strdup(optarg); - break; -+#endif - case 'f': /* remote FTN address */ - remote_node = strdup(optarg); - break; -@@ -416,6 +432,28 @@ - } - if (optind\n", extract_filename(argv[0])); -+ fprintf(stderr, " Use -P
for polling a specific node (e.g., -P 1:23/456.7)\n"); -+ exit(1); -+ } -+ -+ /* Check for leftover FTN addresses in extra arguments */ -+ while (optind < argc) -+ { -+ char *extra = argv[optind++]; -+ if (strchr(extra, ':') || strchr(extra, '@')) -+ { -+ fprintf(stderr, "%s: Error: Unexpected FTN address '%s' in arguments.\n", extract_filename(argv[0]), extra); -+ fprintf(stderr, " Use -P
before the config file to poll a node.\n"); -+ exit(1); -+ } -+ } -+ - #ifdef OS2 - if (optindloglevel, current_config->conlog, - current_config->logpath, current_config->nolog.first); -+ -+ /* Clean up old .bsy/.csy files at startup */ -+ cleanup_old_bsy(current_config); - } - else if (verbose_flag) - { -@@ -665,9 +706,13 @@ - - if (p) - { -- remote_addr = strdup(p); -- p = strchr(remote_addr, ' '); -- if (p) *p = '\0'; -+ remote_addr = strdup(p); -+ /* Guard against null pointer dereference if strdup fails */ -+ if (remote_addr) -+ { -+ p = strchr(remote_addr, ' '); -+ if (p) *p = '\0'; -+ } - } - } - /* not using stdin/stdout itself to avoid possible collisions */ -@@ -677,6 +722,9 @@ - inetd_socket_out = dup(fileno(stdout)); - #ifdef UNIX - tempfd = open("/dev/null", O_RDWR); -+#elif defined(AMIGA) -+ /* NIL: is the native AmigaDOS null device (no ixemul) */ -+ tempfd = open("NIL:", O_RDWR); - #else - tempfd = open("nul", O_RDWR); - #endif -@@ -707,6 +755,15 @@ - signal (SIGHUP, sighandler); - #endif - -+#ifdef AMIGA -+ /* AmigaOS 3: WaitSelect() loop, no fork/threads */ -+ { -+ BINKD_CONFIG *ev_cfg = lock_current_config(); -+ amiga_evloop_run(ev_cfg, server_flag, client_flag); -+ unlock_config_structure(ev_cfg, 0); -+ return 0; -+ } -+#else - if (client_flag && !server_flag) - { - clientmgr (0); -@@ -721,7 +778,9 @@ - if (client_flag && (pidcmgr = branch (clientmgr, 0, 0)) < 0) - { - Log (0, "cannot branch out"); -+ exit (1); - } -+#endif /* !AMIGA */ - - if (*current_config->pid_file) - { -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/binlog.c binkd/binlog.c ---- binkd_pgul/binlog.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/binlog.c 2026-04-22 06:50:46.000000000 +0100 -@@ -35,6 +35,10 @@ - #include "tools.h" - #include "sem.h" - -+#if defined(HAVE_THREADS) || defined(AMIGA) -+extern MUTEXSEM blsem; -+#endif -+ - /* Write 16-bit integer to file in intel bytes order */ - static int fput16(u16 arg, FILE *file) - { -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/branch.c binkd/branch.c ---- binkd_pgul/branch.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/branch.c 2026-04-26 09:12:44.314385608 +0100 -@@ -20,12 +20,6 @@ - #include "tools.h" - #include "sem.h" - --#ifdef AMIGA --int ix_vfork (void); --void vfork_setup_child (void); --void ix_vfork_resume (void); --#endif -- - #ifdef WITH_PTHREADS - typedef struct { - void (*F) (void *); -@@ -132,23 +126,6 @@ - #endif - #endif - --#ifdef AMIGA -- /* this is rather bizzare. this function pretends to be a fork and behaves -- * like one, but actually it's a kind of a thread. so we'll need semaphores */ -- -- if (!(rc = ix_vfork ())) -- { -- vfork_setup_child (); -- ix_vfork_resume (); -- F (arg); -- exit (0); -- } -- else if (rc < 0) -- { -- Log (1, "ix_vfork: %s", strerror (errno)); -- } --#endif -- - #if defined(DOS) || defined(DEBUGCHILD) - rc = 0; - F (arg); -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/breaksig.c binkd/breaksig.c ---- binkd_pgul/breaksig.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/breaksig.c 2026-04-26 10:25:19.576809578 +0100 -@@ -46,6 +46,16 @@ - { - atexit (exitfunc); - -+#ifdef AMIGA -+ /* AmigaOS / libnix: signal() maps SIGINT -> SIGBREAKF_CTRL_C -+ * Register exitsig() so that Ctrl+C sets binkd_exit=1 even when -+ * the process is NOT blocked inside WaitSelect() (e.g. during disk -+ * I/O). When inside WaitSelect(), amiga_select_wrap() in bsdsock.h -+ * detects the break and sets binkd_exit=1 directly without going -+ * through this signal handler. */ -+ signal (SIGINT, exitsig); -+ signal (SIGTERM, exitsig); -+#else - #ifdef SIGBREAK - signal (SIGBREAK, exitsig); - #endif -@@ -58,5 +68,6 @@ - #ifdef SIGTERM - signal (SIGTERM, exitsig); - #endif -+#endif /* AMIGA */ - return 1; - } -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/bsycleanup.c binkd/bsycleanup.c ---- binkd_pgul/bsycleanup.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/bsycleanup.c 2026-04-26 11:01:23.269049076 +0100 -@@ -0,0 +1,122 @@ -+/* -+ * bsycleanup.c -- Cleanup functions for .bsy/.csy/.try files -+ * -+ * bsycleanup.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "sys.h" -+#include "readcfg.h" -+#include "ftnq.h" -+#include "ftnnode.h" -+#include "tools.h" -+#include "readdir.h" -+ -+/* -+ * Clean up old .bsy and .csy files at startup -+ * Scans all domain outbounds -+ */ -+ -+/* Helper: scan a single directory for .bsy/.csy files and delete them */ -+static void scan_and_delete_bsy_in_dir(const char *dir, BINKD_CONFIG *config) -+{ -+ DIR *dp; -+ struct dirent *de; -+ char buf[MAXPATHLEN + 1]; -+ -+ if ((dp = opendir(dir)) == 0) -+ { -+ return; -+ } -+ -+ while ((de = readdir(dp)) != 0) -+ { -+ char *s = de->d_name; -+ int len = strlen(s); -+ -+ if (len > 4 && (!STRICMP(s + len - 4, ".bsy") || !STRICMP(s + len - 4, ".csy") || !STRICMP(s + len - 4, ".try"))) -+ { -+ strnzcpy(buf, dir, sizeof(buf)); -+ strnzcat(buf, PATH_SEPARATOR, sizeof(buf)); -+ strnzcat(buf, s, sizeof(buf)); -+ -+ Log(2, "deleting %s", buf); -+ -+ delete(buf); -+ } -+ } -+ -+ closedir(dp); -+} -+ -+void cleanup_old_bsy(BINKD_CONFIG *config) -+{ -+ DIR *dp; -+ char outb_path[MAXPATHLEN + 1], base_path[MAXPATHLEN + 1]; -+ struct dirent *de; -+ FTN_DOMAIN *curr_domain; -+ int len; -+ -+ Log(2, "cleaning up .bsy/.csy/.try files at startup"); -+ -+ /* Scan all domain outbounds */ -+ for (curr_domain = config->pDomains.first; curr_domain; curr_domain = curr_domain->next) -+ { -+ if (curr_domain->alias4 != 0) -+ continue; -+ -+ /* Build base path: path + separator */ -+ strnzcpy(base_path, curr_domain->path, sizeof(base_path)); -+#ifndef AMIGA -+ if (base_path[strlen(base_path) - 1] == ':') -+ strcat(base_path, PATH_SEPARATOR); -+#endif -+ -+#ifdef AMIGADOS_4D_OUTBOUND -+ if (config->aso) -+ { -+ /* ASO mode: direct outbound path */ -+ strnzcpy(outb_path, base_path, sizeof(outb_path)); -+ strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); -+ strnzcat(outb_path, curr_domain->dir, sizeof(outb_path)); -+ Log(7, "cleanup_old_bsy (ASO): scanning domain '%s', path '%s'", curr_domain->name, outb_path); -+ scan_and_delete_bsy_in_dir(outb_path, config); -+ } -+ else -+#endif -+ { -+ /* BSO mode: scan for outbound.xxx directories */ -+ Log(7, "cleanup_old_bsy (BSO): scanning domain '%s', base '%s'", curr_domain->name, base_path); -+ -+ if ((dp = opendir(base_path)) == 0) -+ continue; -+ -+ len = strlen(curr_domain->dir); -+ -+ while ((de = readdir(dp)) != 0) -+ { -+ /* Match outbound or outbound.xxx */ -+ if (!STRNICMP(de->d_name, curr_domain->dir, len) && (de->d_name[len] == 0 || (de->d_name[len] == '.' && isxdigit(de->d_name[len + 1])))) -+ { -+ strnzcpy(outb_path, base_path, sizeof(outb_path)); -+ strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); -+ strnzcat(outb_path, de->d_name, sizeof(outb_path)); -+ -+ Log(7, "cleanup_old_bsy (BSO): scanning outbound dir '%s'", outb_path); -+ -+ scan_and_delete_bsy_in_dir(outb_path, config); -+ } -+ } -+ -+ closedir(dp); -+ } -+ } -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/bsycleanup.h binkd/bsycleanup.h ---- binkd_pgul/bsycleanup.h 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/bsycleanup.h 2026-04-26 13:11:39.607856201 +0100 -@@ -0,0 +1,19 @@ -+/* -+ * bsycleanup.h -- Cleanup functions for .bsy/.csy/.try files -+ * -+ * bsycleanup.h is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#ifndef _BSYCLEANUP_H -+#define _BSYCLEANUP_H -+ -+#include "readcfg.h" -+ -+ -+/* cleanup_old_bsy -- Clean up old .bsy/.csy/.try files at startup */ -+void cleanup_old_bsy(BINKD_CONFIG *config); -+ -+#endif /* _BSYCLEANUP_H */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/btypes.h binkd/btypes.h ---- binkd_pgul/btypes.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/btypes.h 2026-04-25 20:39:58.604145925 +0100 -@@ -73,6 +73,7 @@ - int HC_flag; - int restrictIP; - int NP_flag; /* no proxy */ -+ int NC_flag; /* no compression */ - - time_t hold_until; - int busy; /* 0=free, 'c'=.csy, other=.bsy */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/cambios.diff binkd/cambios.diff ---- binkd_pgul/cambios.diff 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/cambios.diff 2026-04-26 16:03:26.856780412 +0100 -@@ -0,0 +1,8223 @@ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/bsdsock.c binkd_pgul/amiga/bsdsock.c -+--- binkd/amiga/bsdsock.c 2026-04-26 11:01:10.438650436 +0100 -++++ binkd_pgul/amiga/bsdsock.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,79 +0,0 @@ -+-/* -+- * bsdsock.c -- bsdsocket.library lifecycle for AmigaOS 3 -+- * -+- * bsdsock.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include -+-#include -+-#include -+-#include -+-#include -+-#include -+- -+-/* Linker-compatibility global. Never used at runtime */ -+-struct Library *SocketBase = NULL; -+- -+-/* Suppress conflicting C prototypes from clib/bsdsocket_protos.h */ -+-#ifndef CLIB_BSDSOCKET_PROTOS_H -+-#define CLIB_BSDSOCKET_PROTOS_H -+-#endif -+- -+-#include -+-#include -+- -+-extern void Log(int lev, const char *s, ...); -+- -+-/* _amiga_get_socket_base -- returns bsdsocket.library handle for calling task */ -+-struct Library *_amiga_get_socket_base(void) -+-{ -+- return (struct Library *)FindTask(NULL)->tc_UserData; -+-} -+- -+-int amiga_sock_init(void) -+-{ -+- struct Task *me = FindTask(NULL); -+- struct Library *base; -+- -+- if (me->tc_UserData) -+- return 0; /* already open for this task */ -+- -+- base = OpenLibrary("bsdsocket.library", 0UL); -+- -+- if (!base) -+- { -+- fprintf(stderr, "amiga_sock_init: cannot open bsdsocket.library\n"); -+- return -1; -+- } -+- -+- /* Store in tc_UserData and global SocketBase */ -+- me->tc_UserData = (APTR)base; -+- SocketBase = base; -+- -+- /* Link the per-task errno to the TCP stack. */ -+- SetErrnoPtr(&errno, (LONG)sizeof(errno)); -+- -+- return 0; -+-} -+- -+-void amiga_sock_cleanup(void) -+-{ -+- struct Task *me = FindTask(NULL); -+- struct Library *base = (struct Library *)me->tc_UserData; -+- -+- if (base) -+- { -+- me->tc_UserData = NULL; -+- SocketBase = NULL; -+- CloseLibrary(base); -+- } -+-} -+- -+-int amiga_child_sock_init(void) -+-{ -+- /* Child inherits tc_UserData = NULL, opens new handle */ -+- return amiga_sock_init(); -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/bsdsock.h binkd_pgul/amiga/bsdsock.h -+--- binkd/amiga/bsdsock.h 2026-04-26 10:49:27.706200054 +0100 -++++ binkd_pgul/amiga/bsdsock.h 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,155 +0,0 @@ -+-/* -+- * bsdsock.h -- bsdsocket.library init and POSIX compat shims for AmigaOS 3 -+- * -+- * bsdsock.h is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifndef _AMIGA_BSDSOCK_H -+-#define _AMIGA_BSDSOCK_H -+- -+-#ifdef AMIGA -+- -+-#include -+-#include -+-#include -+-#include -+- -+-/* Suppress conflicting C prototypes from roadshow */ -+-#ifndef CLIB_BSDSOCKET_PROTOS_H -+-#define CLIB_BSDSOCKET_PROTOS_H -+-#endif -+- -+-/* Undefine MCLBYTES/MCLSHIFT before roadshow headers */ -+-#ifdef MCLBYTES -+-#undef MCLBYTES -+-#endif -+-#ifdef MCLSHIFT -+-#undef MCLSHIFT -+-#endif -+- -+-/* Roadshow SDK network headers */ -+-#include -+-#include -+-#include "compat_netinet_in.h" -+-#include -+-#include -+-#include /* inline/bsdsocket.h, no clib protos */ -+- -+-/* Undefine conflicting unistd.h macros */ -+-#ifdef gethostid -+-#undef gethostid -+-#endif -+-#ifdef getdtablesize -+-#undef getdtablesize -+-#endif -+-#ifdef gethostname -+-#undef gethostname -+-#endif -+- -+-/* Per-task SocketBase override */ -+-struct Library *_amiga_get_socket_base(void); -+- -+-#ifdef SocketBase -+-#undef SocketBase -+-#endif -+-#define SocketBase _amiga_get_socket_base() -+- -+-/* Roadshow socket-specific errno values */ -+-#include -+- -+-#define BSDSOCK_HAS_TIMEVAL 1 -+- -+-/* Socket-specific errno values from roadshow sys/errno.h */ -+-#ifndef ENOTSOCK -+-#define ENOTSOCK 38 /* Socket operation on non-socket */ -+-#endif -+-#ifndef EOPNOTSUPP -+-#define EOPNOTSUPP 45 /* Operation not supported on socket */ -+-#endif -+-#ifndef ECONNREFUSED -+-#define ECONNREFUSED 61 /* Connection refused */ -+-#endif -+-#ifndef ETIMEDOUT -+-#define ETIMEDOUT 60 /* Connection timed out */ -+-#endif -+-#ifndef ECONNRESET -+-#define ECONNRESET 54 /* Connection reset by peer */ -+-#endif -+-#ifndef EHOSTUNREACH -+-#define EHOSTUNREACH 65 /* No route to host */ -+-#endif -+- -+-#include -+- -+-/* sockaddr_storage fallback for roadshow */ -+-#ifndef HAVE_SOCKADDR_STORAGE -+-#ifndef sockaddr_storage -+-struct sockaddr_storage -+-{ -+- unsigned short ss_family; -+- char __ss_pad[22]; /* enough for IPv4 */ -+-}; -+-#endif -+-#define HAVE_SOCKADDR_STORAGE 1 -+-#endif -+- -+-/* Library base functions */ -+-int amiga_sock_init(void); -+-void amiga_sock_cleanup(void); -+-int amiga_child_sock_init(void); -+- -+-/* getpid() is defined in sys.h for AMIGA */ -+-/* amiga_sleep and sleep are defined in sys.h for AMIGA */ -+- -+-/* select() -> WaitSelect() wrapper with Ctrl+C handling */ -+-#ifndef AMIGA_SELECT_DEFINED -+-#define AMIGA_SELECT_DEFINED -+- -+-/* Forward-declare binkd_exit */ -+-extern int binkd_exit; -+- -+-/* Forward-declare Log to avoid implicit declaration warning */ -+-extern void Log(int lev, char *s, ...); -+- -+-static int amiga_select_wrap(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) -+-{ -+- ULONG sigmask = SIGBREAKF_CTRL_C; -+- int rc = WaitSelect(nfds, readfds, writefds, exceptfds, timeout, &sigmask); -+- -+- /* Ctrl+C should break blocked select() loops immediately. */ -+- if ((sigmask & SIGBREAKF_CTRL_C) != 0) -+- { -+- Log(1, "Ctrl+C detected in WaitSelect, setting binkd_exit=1"); -+- binkd_exit = 1; -+- errno = EINTR; -+- return -1; -+- } -+- -+- return rc; -+-} -+- -+-#define select(n, r, w, e, t) amiga_select_wrap((n), (r), (w), (e), (t)) -+- -+-#endif /* AMIGA_SELECT_DEFINED */ -+- -+-/* FIONBIO via IoctlSocket */ -+-#ifndef FIONBIO -+-#define FIONBIO 0x8004667E -+-#endif -+-#ifndef ioctl -+-#define ioctl(s, req, arg) IoctlSocket((s), (req), (char *)(arg)) -+-#endif -+- -+-/* inet_ntoa -> Inet_NtoA (Amiga only) */ -+-#ifdef AMIGA -+-#ifdef inet_ntoa -+-#undef inet_ntoa -+-#endif -+-#define inet_ntoa(a) Inet_NtoA(a) -+-#endif -+- -+-#endif /* AMIGA */ -+-#endif /* _AMIGA_BSDSOCK_H */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/compat_netinet_in.h binkd_pgul/amiga/compat_netinet_in.h -+--- binkd/amiga/compat_netinet_in.h 2026-04-26 09:53:12.337417283 +0100 -++++ binkd_pgul/amiga/compat_netinet_in.h 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,19 +0,0 @@ -+-/* -+- * compat_netinet_in.h -- Wrapper for netinet/in.h typedef clash -+- * -+- * compat_netinet_in.h is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifndef _AMIGA_COMPAT_NETINET_IN_H -+-#define _AMIGA_COMPAT_NETINET_IN_H -+- -+-#ifdef __GNUC__ -+-#include_next -+-#else -+-#include -+-#endif -+- -+-#endif /* _AMIGA_COMPAT_NETINET_IN_H */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/dirent.c binkd_pgul/amiga/dirent.c -+--- binkd/amiga/dirent.c 2026-04-26 11:40:07.539429919 +0100 -++++ binkd_pgul/amiga/dirent.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,137 +0,0 @@ -+-/* -+- * dirent.c -- POSIX directory scanning for AmigaOS 3 -+- * -+- * dirent.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifdef AMIGA -+- -+-#include -+-#include -+-#include -+-#include -+-#include -+-#include -+- -+-#include "amiga/dirent.h" -+- -+-/* opendir -- locks directory and allocates state for readdir() */ -+-DIR *opendir(const char *path) -+-{ -+- DIR *dir; -+- -+- if (!path) -+- { -+- errno = EINVAL; -+- return NULL; -+- } -+- -+- dir = (DIR *)AllocMem((LONG)sizeof(DIR), MEMF_CLEAR); -+- -+- if (!dir) -+- { -+- errno = ENOMEM; -+- return NULL; -+- } -+- -+- dir->fib = (struct FileInfoBlock *)AllocMem((LONG)sizeof(struct FileInfoBlock), MEMF_CLEAR); -+- -+- if (!dir->fib) -+- { -+- FreeMem(dir, (LONG)sizeof(DIR)); -+- errno = ENOMEM; -+- return NULL; -+- } -+- -+- dir->lock = Lock((STRPTR)path, ACCESS_READ); -+- -+- if (!dir->lock) -+- { -+- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -+- FreeMem(dir, (LONG)sizeof(DIR)); -+- errno = ENOENT; -+- return NULL; -+- } -+- -+- /* Examine the directory itself; this positions FIB for ExNext() */ -+- if (!Examine(dir->lock, dir->fib)) -+- { -+- UnLock(dir->lock); -+- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -+- FreeMem(dir, (LONG)sizeof(DIR)); -+- errno = EACCES; -+- return NULL; -+- } -+- -+- /* fib_DirEntryType > 0 means this IS a directory */ -+- if (dir->fib->fib_DirEntryType <= 0) -+- { -+- UnLock(dir->lock); -+- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -+- FreeMem(dir, (LONG)sizeof(DIR)); -+- errno = ENOTDIR; -+- return NULL; -+- } -+- -+- dir->first = 1; /* ExNext() has not been called yet */ -+- -+- return dir; -+-} -+- -+-/* readdir -- advances to next directory entry */ -+-struct dirent *readdir(DIR *dir) -+-{ -+- LONG dos_rc; -+- LONG dos_err; -+- -+- if (!dir) -+- { -+- errno = EINVAL; -+- return NULL; -+- } -+- -+- /* ExNext() advances past the last entry returned by Examine/ExNext */ -+- dos_rc = ExNext(dir->lock, dir->fib); -+- -+- if (!dos_rc) -+- { -+- dos_err = IoErr(); -+- -+- if (dos_err == ERROR_NO_MORE_ENTRIES) -+- return NULL; -+- -+- errno = EIO; -+- return NULL; -+- } -+- -+- /* Copy name into the entry buffer */ -+- strncpy(dir->entry.d_name, dir->fib->fib_FileName, AMIGA_NAME_MAX - 1); -+- dir->entry.d_name[AMIGA_NAME_MAX - 1] = '\0'; -+- dir->entry.d_ino = 0; -+- -+- return &dir->entry; -+-} -+- -+-/* closedir -- releases all resources associated with dir */ -+-int closedir(DIR *dir) -+-{ -+- if (!dir) -+- { -+- errno = EINVAL; -+- return -1; -+- } -+- -+- if (dir->lock) -+- UnLock(dir->lock); -+- -+- if (dir->fib) -+- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -+- -+- FreeMem(dir, (LONG)sizeof(DIR)); -+- return 0; -+-} -+- -+-#endif /* AMIGA */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/dirent.h binkd_pgul/amiga/dirent.h -+--- binkd/amiga/dirent.h 2026-04-26 09:53:13.628932082 +0100 -++++ binkd_pgul/amiga/dirent.h 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,53 +0,0 @@ -+-/* -+- * dirent.h -- POSIX directory scanning for AmigaOS 3 -+- * -+- * dirent.h is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifndef _AMIGA_DIRENT_H -+-#define _AMIGA_DIRENT_H -+- -+-#ifdef AMIGA -+- -+-#include -+-#include -+- -+-/* Maximum name length: AmigaDOS allows 107 characters */ -+-#define AMIGA_NAME_MAX 108 -+- -+-struct dirent -+-{ -+- unsigned long d_ino; /* inode -- always 0 on AmigaDOS */ -+- char d_name[AMIGA_NAME_MAX]; /* null-terminated file name */ -+-}; -+- -+-/* struct utimbuf for AmigaOS 3 without ixemul */ -+-#ifndef _AMIGA_UTIMBUF_DEFINED -+-#define _AMIGA_UTIMBUF_DEFINED -+- -+-struct utimbuf -+-{ -+- long actime; /* access time (unused by SetFileDate) */ -+- long modtime; /* modification time (POSIX time_t) */ -+-}; -+- -+-int utime(const char *path, const struct utimbuf *times); -+-#endif -+- -+-typedef struct _amiga_dir -+-{ -+- BPTR lock; /* directory lock */ -+- struct FileInfoBlock *fib; /* reusable FileInfoBlock */ -+- int first; /* flag: first call not yet */ -+- struct dirent entry; /* storage returned to caller */ -+-} DIR; -+- -+-DIR *opendir(const char *path); -+-struct dirent *readdir(DIR *dir); -+-int closedir(DIR *dir); -+- -+-#endif /* AMIGA */ -+-#endif /* _AMIGA_DIRENT_H */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop.c binkd_pgul/amiga/evloop.c -+--- binkd/amiga/evloop.c 2026-04-26 11:46:47.344533199 +0100 -++++ binkd_pgul/amiga/evloop.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,509 +0,0 @@ -+-/* -+- * evloop.c -- non-blocking event loop for AmigaOS 3 -+- * -+- * evloop.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-/* Suppress clib bsdsocket prototypes before any socket header */ -+-#ifndef CLIB_BSDSOCKET_PROTOS_H -+-#define CLIB_BSDSOCKET_PROTOS_H -+-#endif -+- -+-#include -+-#include -+-#include -+- -+-#include -+-#include -+-#include -+- -+-#include "sys.h" -+-#include "readcfg.h" -+-#include "common.h" -+-#include "tools.h" -+-#include "protocol.h" -+-#include "sem.h" -+-#include "server.h" -+-#include "amiga/bsdsock.h" -+-#include "amiga/evloop.h" -+-#include "amiga/evloop_int.h" -+-#include "amiga/proto_amiga.h" -+- -+-/* Externals */ -+-extern SOCKET sockfd[MAX_LISTENSOCK]; -+-extern int sockfd_used; -+-extern int binkd_exit; -+-extern int server_flag, client_flag; -+- -+-/* Session table (shared with sock.c and session.c) */ -+-sess_t *sessions = NULL; -+-int max_sessions = 0; -+- -+-/* -+- * calc_max_sessions -- Compute session slot count from config + flags -+- * Shared by init and config-reload paths -+- */ -+-static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) -+-{ -+- int servers = config->max_servers; -+- int clients = config->max_clients; -+- int total; -+- -+- if (servers == 0 && clients == 0) -+- { -+- Log(5, "DEBUG: Using default 2 slots (no config found)"); -+- return 2; -+- } -+- -+- Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); -+- -+- if (srv_flag && servers < 1) -+- servers = 1; -+- -+- if (cli_flag && clients < 1) -+- clients = 1; -+- -+- total = servers + clients; -+- -+- if (total < 2) -+- total = 2; -+- -+- Log(5, "DEBUG: Calculated max_sessions=%d", total); -+- -+- return total; -+-} -+- -+-/* init_session_table -- Allocate and zero-initialise the session array */ -+-static int init_session_table(int slots) -+-{ -+- int i; -+- -+- sessions = calloc(slots, sizeof(sess_t)); -+- -+- if (!sessions) -+- { -+- Log(1, "Failed to allocate session table"); -+- return 0; -+- } -+- -+- for (i = 0; i < slots; i++) -+- { -+- memset(&sessions[i], 0, sizeof(sess_t)); -+- memset(&sessions[i].state, 0, sizeof(STATE)); -+- sessions[i].fd = INVALID_SOCKET; -+- sessions[i].phase = SESS_FREE; -+- } -+- -+- return 1; -+-} -+- -+-/* -+- * build_fdsets -- Populate r/w fd_sets from listen sockets and sessions -+- * Returns the highest fd seen (maxfd) -+- */ -+-static int build_fdsets(fd_set *r, fd_set *w) -+-{ -+- int i, maxfd = 0; -+- -+- FD_ZERO(r); -+- FD_ZERO(w); -+- -+- /* server side: listen sockets */ -+- for (i = 0; i < sockfd_used; i++) -+- { -+- if (sockfd[i] != INVALID_SOCKET) -+- { -+- FD_SET(sockfd[i], r); -+- -+- if ((int)sockfd[i] > maxfd) -+- maxfd = (int)sockfd[i]; -+- } -+- } -+- -+- /* client + server sessions */ -+- for (i = 0; i < max_sessions; i++) -+- { -+- sess_t *s = &sessions[i]; -+- -+- if (s->phase == SESS_FREE || s->fd == INVALID_SOCKET) -+- continue; -+- -+- if ((int)s->fd > maxfd) -+- maxfd = (int)s->fd; -+- -+- if (s->phase == SESS_CONNECTING) -+- { -+- /* client: waiting for non-blocking connect() */ -+- FD_SET(s->fd, w); -+- } -+- else -+- { -+- /* Server or established client session */ -+- FD_SET(s->fd, r); -+- -+- if (s->state.msgs || s->state.oleft || s->state.send_eof || (s->state.out.f && !s->state.off_req_sent && !s->state.waiting_for_GOT)) -+- FD_SET(s->fd, w); -+- } -+- } -+- -+- Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); -+- return maxfd; -+-} -+- -+-/* -+- * handle_server_accept -- Accept new inbound connections on all listen fds -+- * Returns 0 normally, -1 if binkd_exit was set during accept -+- */ -+-static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) -+-{ -+- int i; -+- -+- Log(5, "DEBUG: Before accept loop"); -+- -+- for (i = 0; i < sockfd_used; i++) -+- { -+- if (FD_ISSET(sockfd[i], r)) -+- do_accept(sockfd[i], config); -+- -+- if (binkd_exit) -+- { -+- Log(5, "DEBUG: binkd_exit during accept loop"); -+- return -1; -+- } -+- } -+- -+- Log(5, "DEBUG: After accept loop"); -+- -+- return 0; -+-} -+- -+-/* -+- * advance_sessions -- Step every active session (server + client) -+- * Returns the number of non-free sessions processed -+- */ -+-static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) -+-{ -+- int i, active = 0; -+- -+- Log(5, "DEBUG: Before advance sessions"); -+- -+- for (i = 0; i < max_sessions; i++) -+- { -+- sess_t *s = &sessions[i]; -+- -+- Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); -+- -+- if (s->phase == SESS_FREE) -+- continue; -+- -+- active++; -+- -+- if (s->phase == SESS_CONNECTING) -+- { -+- /* client: Complete the non-blocking connect */ -+- if (FD_ISSET(s->fd, w)) -+- check_connect(i, config); -+- } -+- else -+- { -+- int rd = FD_ISSET(s->fd, r); -+- int wr = FD_ISSET(s->fd, w); -+- -+- /* Always step: protocol must advance internal state even -+- * when WaitSelect reports no activity (e.g. second batch -+- * EOB send, TCP FIN detection after remote closes) */ -+- do_session_step(i, rd, wr, config); -+- } -+- -+- if (binkd_exit) -+- break; -+- } -+- -+- return active; -+-} -+- -+-/* -+- * handle_config_reload -- Resize session table and reopen listen sockets -+- * Returns 1 if the caller should break out of the main loop, 0 otherwise -+- */ -+-static int handle_config_reload(BINKD_CONFIG **config, int srv_flag, int cli_flag) -+-{ -+- int i, new_max; -+- BINKD_CONFIG *nc = lock_current_config(); -+- -+- if (nc) -+- { -+- new_max = calc_max_sessions(nc, srv_flag, cli_flag); -+- -+- if (new_max != max_sessions) -+- { -+- sess_t *ns = realloc(sessions, new_max * sizeof(sess_t)); -+- -+- if (ns) -+- { -+- for (i = max_sessions; i < new_max; i++) -+- { -+- memset(&ns[i], 0, sizeof(sess_t)); -+- memset(&ns[i].state, 0, sizeof(STATE)); -+- ns[i].fd = INVALID_SOCKET; -+- ns[i].phase = SESS_FREE; -+- } -+- -+- sessions = ns; -+- max_sessions = new_max; -+- -+- Log(4, "Session table resized to %d slots", max_sessions); -+- } -+- else -+- { -+- Log(1, "Failed to resize session table, keeping current size"); -+- } -+- } -+- -+- unlock_config_structure(nc, 0); -+- } -+- -+- close_listen_sockets(); -+- *config = lock_current_config(); -+- -+- if (srv_flag && open_listen_sockets(*config) < 0) -+- { -+- unlock_config_structure(*config, 0); -+- return 1; /* fatal — break main loop */ -+- } -+- -+- unlock_config_structure(*config, 0); -+- *config = lock_current_config(); -+- return 0; -+-} -+- -+-/* evloop_cleanup -- Close sessions and free resources on exit */ -+-static void evloop_cleanup(BINKD_CONFIG *config, int config_locked) -+-{ -+- int i; -+- -+- if (config_locked) -+- unlock_config_structure(config, 0); -+- -+- if (sessions) -+- { -+- for (i = 0; i < max_sessions; i++) -+- { -+- if (sessions[i].phase == SESS_RUNNING) -+- { -+- amiga_proto_close(&sessions[i].state, config, 0); -+- -+- if (sessions[i].inbound) -+- n_servers--; -+- else -+- n_clients--; -+- } -+- else if (sessions[i].phase == SESS_CONNECTING) -+- { -+- n_clients--; -+- } -+- sess_free(i); -+- } -+- -+- free(sessions); -+- sessions = NULL; -+- } -+- -+- close_listen_sockets(); -+- amiga_sock_cleanup(); -+- Log(4, "evloop done"); -+-} -+- -+-/* amiga_evloop_run -- Entry point: init, then main WaitSelect() loop */ -+-void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) -+-{ -+- int config_locked = 0; -+- time_t last_rescan = 0; -+- time_t now; -+- fd_set r, w; -+- struct timeval tv; -+- int n, maxfd; -+- int active_sessions = 0; -+- static int idle_loops = 0; -+- -+- /* Sync globals so try_outbound() and friends see the correct flags */ -+- server_flag = srv_flag; -+- client_flag = cli_flag; -+- -+- sockfd_used = 0; -+- srand((unsigned int)time(NULL)); -+- -+- Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); -+- Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); -+- -+- /* Initialise session table */ -+- max_sessions = calc_max_sessions(config, server_flag, client_flag); -+- -+- if (max_sessions < 2) -+- { -+- Log(2, "WARNING: max_sessions=%d is too low, forcing to 2", max_sessions); -+- max_sessions = 2; -+- } -+- -+- Log(4, "evloop start (AmigaOS 3, WaitSelect, %d slots)", max_sessions); -+- -+- if (!init_session_table(max_sessions)) -+- return; -+- -+- /* server: Open listen sockets */ -+- if (server_flag && open_listen_sockets(config) < 0) -+- { -+- Log(0, "evloop: cannot open listen sockets"); -+- free(sessions); -+- sessions = NULL; -+- return; -+- } -+- -+- Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); -+- -+- /* Initial outbound attempt before waiting (important for poll -p mode) */ -+- Log(5, "DEBUG: Initial try_outbound before main loop"); -+- try_outbound(config); -+- last_rescan = time(NULL); /* Reset timer since we just did an attempt */ -+- -+- /* ===== Main loop ===== */ -+- for (;;) -+- { -+- if (binkd_exit) -+- { -+- Log(1, "binkd_exit detected at loop start, exiting"); -+- break; -+- } -+- -+- /* Build fd_sets */ -+- Log(5, "DEBUG: Building fd_sets"); -+- maxfd = build_fdsets(&r, &w); -+- -+- tv.tv_sec = 1; -+- tv.tv_usec = 0L; -+- -+- /* WaitSelect() with nfds>0 but empty fd_sets causes guru #80000006 :/ -+- * Use select(0,...) as a pure sleep when no sockets are active */ -+- if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) -+- maxfd = 1; -+- -+- Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); -+- -+- if (maxfd == 0) -+- { -+- /* No sockets yet -- use Delay() instead of select(0,...) -+- * as WaitSelect with nfds=0 can block indefinitely on AmigaOS */ -+- Delay(50); /* 1 second = 50 ticks at 50Hz PAL */ -+- n = 0; /* simulate timeout */ -+- } -+- else -+- n = select(maxfd + 1, &r, &w, NULL, &tv); -+- -+- Log(5, "DEBUG: select() returned n=%d", n); -+- -+- if (binkd_exit) -+- { -+- Log(1, "binkd_exit detected after select(), exiting"); -+- break; -+- } -+- -+- Delay(1UL); /* 1 tick = 20ms @ 50Hz, prevents CPU hogging */ -+- -+- /* Handle select errors */ -+- if (n < 0) -+- { -+- if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) -+- { -+- Log(5, "DEBUG: select interrupted, continuing"); -+- continue; -+- } -+- -+- if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) -+- { -+- Log(2, "select: %s, reopening", TCPERR()); -+- -+- close_listen_sockets(); -+- -+- if (server_flag && open_listen_sockets(config) < 0) -+- break; -+- -+- continue; -+- } -+- -+- Log(1, "select: %s", TCPERR()); -+- break; -+- } -+- else if (n == 0) -+- { -+- Log(5, "DEBUG: select timeout, continuing"); -+- } -+- -+- /* server: Accept new inbound connections */ -+- if (server_flag) -+- { -+- if (handle_server_accept(&r, config) < 0) -+- break; -+- } -+- -+- if (binkd_exit) -+- break; -+- -+- /* server + client: Advance all active sessions */ -+- active_sessions = advance_sessions(&r, &w, config); -+- -+- if (binkd_exit) -+- break; -+- -+- /* client: Time-based outbound scan + config reload */ -+- now = time(NULL); -+- -+- if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) -+- { -+- Log(5, "DEBUG: Before try_outbound"); -+- -+- try_outbound(config); -+- -+- Log(5, "DEBUG: After try_outbound"); -+- -+- if (checkcfg()) -+- { -+- if (handle_config_reload(&config, server_flag, client_flag)) -+- break; -+- -+- config_locked = 1; -+- } -+- -+- config->q_present = 0; -+- last_rescan = now; -+- } -+- -+- /* client: Poll-mode idle exit */ -+- /* Reset counter whenever there is something going on */ -+- if (n_clients > 0 || active_sessions > 0) -+- { -+- if (idle_loops > 0) -+- Log(2, "Activity detected, reset idle counter"); -+- -+- idle_loops = 0; -+- } -+- -+- if (!server_flag && active_sessions == 0 && n_clients == 0) -+- { -+- idle_loops++; -+- -+- Log(2, "Idle loop %d/2 (no server, no sessions, no clients)", idle_loops); -+- -+- if (idle_loops > 1) -+- { -+- Log(0, "the queue is empty, quitting..."); -+- break; -+- } -+- } -+- } -+- /* ===== End main loop ===== */ -+- -+- evloop_cleanup(config, config_locked); -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop.h binkd_pgul/amiga/evloop.h -+--- binkd/amiga/evloop.h 2026-04-26 11:47:03.034239655 +0100 -++++ binkd_pgul/amiga/evloop.h 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,34 +0,0 @@ -+-/* -+- * evloop.h -- non-blocking event loop for AmigaOS 3 -+- * -+- * evloop.h is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifndef _AMIGA_EVLOOP_H -+-#define _AMIGA_EVLOOP_H -+- -+-#ifdef AMIGA -+- -+-#include "readcfg.h" -+-#include "protoco2.h" /* STATE */ -+- -+-/* amiga_proto_step() return codes — also used by protocol.c */ -+-#define APROTO_RUNNING 0 /* session alive, call again */ -+-#define APROTO_DONE_OK 1 /* session finished, success */ -+-#define APROTO_DONE_ERR 2 /* session failed */ -+- -+-/* -+- * amiga_evloop_run -- entry point replacing servmgr() + clientmgr() -+- * -+- * Opens listen sockets (when server_flag), then runs a WaitSelect() -+- * loop that multiplexes sessions dynamically based on config->max_servers -+- * and config->max_clients. Minimum 2 sessions are always allocated -+- * Returns only when binkd_exit != 0 -+- */ -+-void amiga_evloop_run(BINKD_CONFIG *config, int server_flag, int client_flag); -+- -+-#endif /* AMIGA */ -+-#endif /* _AMIGA_EVLOOP_H */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop_int.h binkd_pgul/amiga/evloop_int.h -+--- binkd/amiga/evloop_int.h 2026-04-26 11:02:58.166969078 +0100 -++++ binkd_pgul/amiga/evloop_int.h 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,68 +0,0 @@ -+-/* -+- * evloop_int.h -- internal types shared by evloop.c, sock.c, session.c -+- * -+- * evloop_int.h is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifndef AMIGA_EVLOOP_INT_H -+-#define AMIGA_EVLOOP_INT_H -+- -+-#include "protoco2.h" -+-#include "amiga/bsdsock.h" -+-#include "ftnnode.h" -+-#include "readcfg.h" -+- -+-/* Session lifecycle */ -+-typedef enum -+-{ -+- SESS_FREE = 0, /* slot available */ -+- SESS_CONNECTING = 1, /* waiting for connect() */ -+- SESS_RUNNING = 2 /* BinkP session active */ -+-} sess_phase_t; -+- -+-/* Per-session state */ -+-typedef struct -+-{ -+- sess_phase_t phase; -+- SOCKET fd; -+- STATE state; -+- int inbound; /* 1=accepted, 0=outbound */ -+- -+- FTN_NODE *node; -+- struct addrinfo *ai_head; /* full getaddrinfo list */ -+- struct addrinfo *ai_cur; /* candidate being tried */ -+- time_t conn_start; -+- -+- char host[BINKD_FQDNLEN + 1]; -+- char port[MAXPORTSTRLEN + 1]; -+- char ip[BINKD_FQDNLEN + 1]; -+- -+- time_t last_io; -+-} sess_t; -+- -+-/* Globals defined in evloop.c */ -+-extern sess_t *sessions; -+-extern int max_sessions; -+- -+-/* Defined in server.c and client.c respectively */ -+-extern int n_servers; -+-extern int n_clients; -+- -+-/* sock.c */ -+-void set_nonblock(SOCKET fd); -+-int open_listen_sockets(BINKD_CONFIG *config); -+-void close_listen_sockets(void); -+- -+-/* session.c */ -+-int sess_alloc(void); -+-void sess_free(int idx); -+-void do_accept(SOCKET lfd, BINKD_CONFIG *config); -+-int start_connect(sess_t *s, BINKD_CONFIG *config); -+-void check_connect(int idx, BINKD_CONFIG *config); -+-int try_outbound(BINKD_CONFIG *config); -+-void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config); -+- -+-#endif /* AMIGA_EVLOOP_INT_H */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/proto_amiga.c binkd_pgul/amiga/proto_amiga.c -+--- binkd/amiga/proto_amiga.c 2026-04-26 11:52:04.887130443 +0100 -++++ binkd_pgul/amiga/proto_amiga.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,269 +0,0 @@ -+-/* -+- * proto_amiga.c -- Amiga non-blocking BinkP protocol implementation -+- * -+- * proto_amiga.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include -+-#include -+-#include -+-#include -+-#include -+- -+-#include "sys.h" -+-#include "readcfg.h" -+-#include "common.h" -+-#include "protocol.h" -+-#include "ftnaddr.h" -+-#include "ftnnode.h" -+-#include "ftnq.h" -+-#include "tools.h" -+-#include "bsy.h" -+-#include "inbound.h" -+-#include "protoco2.h" -+-#include "prothlp.h" -+-#include "binlog.h" -+-#include "evloop.h" -+- -+-/* External functions from protocol.c */ -+-extern int init_protocol(STATE *state, SOCKET s_in, SOCKET s_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config); -+-extern int banner(STATE *state, BINKD_CONFIG *config); -+-extern int recv_block(STATE *state, BINKD_CONFIG *config); -+-extern int send_block(STATE *state, BINKD_CONFIG *config); -+-extern void bsy_touch(BINKD_CONFIG *config); -+-extern FTNQ *process_rcvdlist(STATE *state, FTNQ *q, BINKD_CONFIG *config); -+-extern int start_file_transfer(STATE *state, FTNQ *q, BINKD_CONFIG *config); -+-extern void ND_set_status(const char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config); -+-extern void deinit_protocol(STATE *state, BINKD_CONFIG *config, int status); -+-extern void evt_set(EVTQ *eq); -+-extern void msg_send2(STATE *state, t_msg m, char *s1, char *s2); -+- -+-/* External functions from other modules */ -+-extern void log_end_of_session(int err, STATE *state, BINKD_CONFIG *config); -+-extern void inb_remove_partial(STATE *state, BINKD_CONFIG *config); -+-extern void good_try(FTN_ADDR *fa, char *comment, BINKD_CONFIG *config); -+-extern void bad_try(FTN_ADDR *fa, const char *error, const int where, BINKD_CONFIG *config); -+-extern int create_poll(FTN_ADDR *fa, int flvr, BINKD_CONFIG *config); -+-extern void hold_node(FTN_ADDR *fa, time_t hold_until, BINKD_CONFIG *config); -+-extern int binkd_exit; -+- -+-/* External variables */ -+-extern int n_servers; -+- -+-/* -+- * amiga_proto_open -- Initialise a session and send the BinkP banner -+- * -+- * fd : Connected socket (same fd for in and out) -+- * to : Outbound node, NULL for inbound -+- * fa : Local AKA to use, may be NULL -+- * host : Remote hostname or dotted-IP string (caller-owned, stable) -+- * port : Remote port string, may be NULL -+- * dst_ip : Numeric remote IP, may be NULL (falls back to host) -+- * config : Current config -+- * -+- * Returns 0 on success, -1 on error (caller must close fd) -+- */ -+-int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config) -+-{ -+- struct sockaddr_storage sa; -+- socklen_t salen = (socklen_t)sizeof(sa); -+- char ownhost[BINKD_FQDNLEN + 1]; -+- char ownserv[MAXSERVNAME + 1]; -+- int rc; -+- -+- if (!init_protocol(state, fd, fd, to, fa, config)) -+- return -1; -+- -+- /* Peer identity for logging and %ip config checks */ -+- state->ipaddr = dst_ip ? (char *)dst_ip : (char *)host; -+- state->peer_name = (host && *host) ? (char *)host : state->ipaddr; -+- -+- /* local endpoint: Not used further, skip to avoid dangling pointer */ -+- -+- Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); -+- -+- /* banner() sends M_NUL lines and ADR messages */ -+- if (!banner(state, config)) -+- return -1; -+- -+- /* refuse if server limit reached */ -+- if (!to && n_servers > config->max_servers) -+- { -+- Log(1, "too many servers"); -+- msg_send2(state, M_BSY, "Too many servers", 0); -+- -+- return -1; -+- } -+- -+- return 0; -+-} -+- -+-/* -+- * amiga_proto_step -- Run one recv/send iteration of the BinkP loop -+- * -+- * readable : Non-zero if the socket has incoming data (from WaitSelect) -+- * writable : Non-zero if the socket can accept outgoing data -+- * -+- * Returns APROTO_RUNNING, APROTO_DONE_OK, or APROTO_DONE_ERR -+- * -+- * This is the loop body of protocol() with the select() call removed -+- * recv_block() and send_block() already handle EWOULDBLOCK gracefully, -+- * so calling this on a non-blocking socket is safe -+- */ -+-int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config) -+-{ -+- FTNQ *q; -+- int no; -+- -+- if (state->io_error) -+- return APROTO_DONE_ERR; -+- -+- /* Advance outgoing file queue if nothing is being sent */ -+- if (!state->local_EOB && state->q && !state->out.f && !state->waiting_for_GOT && !state->off_req_sent && state->state != P_NULL) -+- { -+- while (1) -+- { -+- q = 0; -+- if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) -+- { -+- if (start_file_transfer(state, q, config)) -+- break; -+- } -+- else -+- { -+- q_free(state->q, config); -+- state->q = 0; -+- break; -+- } -+- } -+- } -+- -+- /* Nothing left to send — issue EOB */ -+- if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) -+- { -+- if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) -+- { -+- state->local_EOB = 1; -+- msg_send2(state, M_EOB, 0, 0); -+- } -+- } -+- -+- /* Recv step: Only when socket is readable */ -+- if (readable) -+- { -+- if (!recv_block(state, config)) -+- return APROTO_DONE_ERR; -+- } -+- -+- /* -+- * send step: drive even when writable=0 if there is buffered data, -+- * pending messages, a file mid-transfer, or an EOF to flush. -+- */ -+- if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) -+- { -+- no = send_block(state, config); -+- -+- if (!no && no != 2) -+- return APROTO_DONE_ERR; -+- } -+- -+- bsy_touch(config); -+- -+- /* Batch/Session-end detection — Mirrors the break logic in protocol() */ -+- if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) -+- { -+- if (state->rcvdlist) -+- { -+- state->q = process_rcvdlist(state, state->q, config); -+- -+- q_to_killlist(&state->killlist, &state->n_killlist, state->q); -+- free_rcvdlist(&state->rcvdlist, &state->n_rcvdlist); -+- } -+- -+- Log(6, "batch: %i msgs", state->msgs_in_batch); -+- -+- if (state->msgs_in_batch <= 2 || (state->major * 100 + state->minor <= 100)) -+- { -+- /* Session done */ -+- ND_set_status("", &state->ND_addr, state, config); -+- state->ND_addr.z = -1; -+- -+- return APROTO_DONE_OK; -+- } -+- -+- /* Start next batch */ -+- state->msgs_in_batch = 0; -+- state->remote_EOB = 0; -+- state->local_EOB = 0; -+- -+- if (OK_SEND_FILES(state, config)) -+- { -+- state->q = q_scan_boxes(state->q, state->fa, state->nfa, state->to ? 1 : 0, config); -+- state->q = q_sort(state->q, state->fa, state->nfa, config); -+- } -+- } -+- -+- return APROTO_RUNNING; -+-} -+- -+-/* -+- * amiga_proto_close -- Flush remaining I/O and release STATE resources -+- * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR -+- */ -+-void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) -+-{ -+- int no; -+- char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; -+- int status = ok ? 0 : 1; -+- -+- /* Drain inbound queue */ -+- if (!state->io_error) -+- { -+- while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) -+- Log(9, "purged %d bytes", no); -+- } -+- -+- /* Flush pending outbound messages */ -+- while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) -+- ; -+- -+- if (ok) -+- { -+- log_end_of_session(0, state, config); -+- process_killlist(state->killlist, state->n_killlist, 's'); -+- inb_remove_partial(state, config); -+- -+- if (state->to) -+- good_try(&state->to->fa, "CONNECT/BND", config); -+- } -+- else -+- { -+- log_end_of_session(1, state, config); -+- process_killlist(state->killlist, state->n_killlist, 0); -+- -+- if (!binkd_exit && state->to) -+- bad_try(&state->to->fa, "Bad session", BAD_IO, config); -+- -+- /* Restore poll flavour if files were left mid-transfer */ -+- if (state->to && tolower(state->maxflvr) != 'h') -+- { -+- Log(4, "restoring poll with '%c' flavour", state->maxflvr); -+- -+- create_poll(&state->to->fa, state->maxflvr, config); -+- } -+- } -+- -+- if (state->to && state->r_skipped_flag && config->hold_skipped > 0) -+- { -+- Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); -+- -+- hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); -+- } -+- -+- deinit_protocol(state, config, status); -+- evt_set(state->evt_queue); -+- state->evt_queue = NULL; -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/proto_amiga.h binkd_pgul/amiga/proto_amiga.h -+--- binkd/amiga/proto_amiga.h 2026-04-26 11:53:22.716799421 +0100 -++++ binkd_pgul/amiga/proto_amiga.h 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,30 +0,0 @@ -+-/* -+- * proto_amiga.h -- Amiga non-blocking BinkP protocol implementation -+- * -+- * proto_amiga.h is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifndef _PROTO_AMIGA_H -+-#define _PROTO_AMIGA_H -+- -+-#include "protoco2.h" -+-#include "readcfg.h" -+- -+-/* amiga_proto_step() return codes */ -+-#define APROTO_RUNNING 0 -+-#define APROTO_DONE_OK 1 -+-#define APROTO_DONE_ERR 2 -+- -+-/* amiga_proto_open -- Initialise a session and send the BinkP banner */ -+-int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config); -+- -+-/* amiga_proto_step-- run one recv / send iteration of the BinkP loop */ -+-int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config); -+- -+-/* amiga_proto_close -- flush remaining I/O and release STATE resources */ -+-void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok); -+- -+-#endif /* _PROTO_AMIGA_H */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/rename.c binkd_pgul/amiga/rename.c -+--- binkd/amiga/rename.c 2026-04-25 19:33:26.785601976 +0100 -++++ binkd_pgul/amiga/rename.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -1,137 +1,10 @@ -+ #include -+ #include -+-#include -+- -+-#include -+-#include /* atoi */ -+-#include /* isdigit */ -+- -+-#define PATHBUF 512 -+ -+ int o_rename(char *from, char *to) -+ { -+- struct FileInfoBlock *fib; -+- char dir[PATHBUF]; -+- char base[PATHBUF]; -+- char newname[PATHBUF]; -+- char *slash; -+- ULONG max = 0; -+- BPTR dirlock; -+- char *d = NULL; -+- const char *src = NULL; -+- ULONG n = 0; -+- -+- /* Try direct rename first */ -+- if (Rename((STRPTR)from, (STRPTR)to)) -+- return 0; -+- -+- /* Split path */ -+- slash = strrchr(to, '/'); -+- -+- if (!slash) -+- slash = strrchr(to, ':'); -+- -+- if (slash) -+- { -+- ULONG len = slash - to; -+- -+- if (len >= PATHBUF) -+- len = PATHBUF - 1; -+- -+- strncpy(dir, to, len); -+- dir[len] = '\0'; -+- -+- strncpy(base, slash + 1, PATHBUF - 1); -+- base[PATHBUF - 1] = '\0'; -+- } -+- else -+- { -+- strcpy(dir, "."); -+- strncpy(base, to, PATHBUF - 1); -+- base[PATHBUF - 1] = '\0'; -+- } -+- -+- /* Lock directory */ -+- dirlock = Lock((STRPTR)dir, ACCESS_READ); -+- -+- if (!dirlock) -+- { -+- errno = ENOENT; -+- return -1; -+- } -+- -+- fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); -+- -+- if (!fib) -+- { -+- UnLock(dirlock); -+- errno = ENOMEM; -+- return -1; -+- } -+- -+- /* Scan directory safely under lock */ -+- if (Examine(dirlock, fib)) -+- { -+- while (ExNext(dirlock, fib)) -+- { -+- if (strncmp(fib->fib_FileName, base, strlen(base)) == 0) -+- { -+- const char *p = NULL; -+- -+- p = fib->fib_FileName + strlen(base); -+- -+- if (*p != '.') -+- { -+- continue; -+- } -+- -+- /* .001 style */ -+- if (isdigit((UBYTE)p[1]) && isdigit((UBYTE)p[2]) && isdigit((UBYTE)p[3])) -+- { -+- n = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); -+- if (n > max) -+- max = n; -+- } -+- -+- /* FIDO volume style (.mo0 .th1 etc) */ -+- if (isdigit((UBYTE)p[1]) && !isdigit((UBYTE)p[2])) -+- { -+- n = p[1] - '0'; -+- if (n > max) -+- max = n; -+- } -+- } -+- } -+- } -+- -+- FreeDosObject(DOS_FIB, fib); -+- UnLock(dirlock); -+- -+- /* Build new name */ -+- d = newname; -+- src = to; -+- n = max + 1; -+- -+- if (n > 999) -+- n = 0; -+- -+- /* Copy base */ -+- while (*src && (d - newname) < (PATHBUF - 5)) -+- *d++ = *src++; -+- -+- *d++ = '.'; -+- -+- /* Manual 3-digit write */ -+- *d++ = '0' + (n / 100); -+- n %= 100; -+- *d++ = '0' + (n / 10); -+- *d++ = '0' + (n % 10); -+- *d = '\0'; -+- -+- /* Rename */ -+- if (Rename((STRPTR)from, (STRPTR)newname)) -+- return 0; -+- -+- errno = EACCES; -++ if (Rename((STRPTR)from, (STRPTR)to)) /* cross-volume move won't work */ -+ return -1; -++ else -++ return 0; -+ } -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/rfc2553_amiga.c binkd_pgul/amiga/rfc2553_amiga.c -+--- binkd/amiga/rfc2553_amiga.c 2026-04-26 11:54:19.321503648 +0100 -++++ binkd_pgul/amiga/rfc2553_amiga.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,323 +0,0 @@ -+-/* -+- * rfc2553_amiga.c -- getaddrinfo/getnameinfo fallback for AmigaOS 3 -+- * -+- * rfc2553_amiga.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifdef AMIGA -+- -+-#include "amiga/bsdsock.h" /* LP stubs + SocketBase */ -+-#include "rfc2553.h" /* sets HAVE_GETADDRINFO / HAVE_GETNAMEINFO */ -+-#include "sem.h" -+- -+-#include -+-#include -+-#include -+-#include -+- -+-#define safe_strncpy(dst, src, n) \ -+- do \ -+- { \ -+- strncpy((dst), (src), (n)); \ -+- (dst)[(n) - 1] = '\0'; \ -+- } while (0) -+- -+-#ifndef HAVE_GETADDRINFO -+- -+-void freeaddrinfo(struct addrinfo *ai); /* forward decl */ -+- -+-int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) -+-{ -+- struct addrinfo **tail = res; -+- struct hostent *hent = NULL; -+- unsigned int port; -+- int proto; -+- const char *end; -+- char **addrp; -+- -+- static char passive_dummy = '\0'; -+- char *passive_list[2] = {&passive_dummy, NULL}; -+- -+- if (!res) -+- { -+- return EAI_UNKNOWN; -+- } -+- -+- *res = NULL; -+- -+- port = servname ? htons((unsigned short)strtol(servname, (char **)&end, 0)) : 0; -+- proto = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; -+- -+- lockresolvsem(); -+- -+- if (servname && end != servname + strlen(servname)) -+- { -+- struct servent *se = NULL; -+- -+- if (!hints || hints->ai_socktype == SOCK_STREAM) -+- se = getservbyname((char *)servname, "tcp"); -+- -+- if (hints && hints->ai_socktype == SOCK_DGRAM) -+- se = getservbyname((char *)servname, "udp"); -+- -+- if (!se) -+- { -+- releaseresolvsem(); -+- return EAI_NONAME; -+- } -+- -+- port = se->s_port; -+- -+- if (strcmp((char *)se->s_proto, "tcp") == 0) -+- proto = SOCK_STREAM; -+- else if (strcmp((char *)se->s_proto, "udp") == 0) -+- proto = SOCK_DGRAM; -+- else -+- { -+- releaseresolvsem(); -+- return EAI_NONAME; -+- } -+- -+- if (hints && hints->ai_socktype && hints->ai_socktype != proto) -+- { -+- releaseresolvsem(); -+- return EAI_SERVICE; -+- } -+- } -+- -+- if (!hints || !(hints->ai_flags & AI_PASSIVE)) -+- { -+- unsigned long nip = inet_addr((char *)nodename); -+- -+- if (nip != (unsigned long)INADDR_NONE) -+- { -+- struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); -+- struct sockaddr_in *sin; -+- -+- if (!ai) -+- { -+- releaseresolvsem(); -+- return EAI_MEMORY; -+- } -+- *tail = ai; -+- -+- sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); -+- -+- if (!sin) -+- { -+- free(ai); -+- releaseresolvsem(); -+- return EAI_MEMORY; -+- } -+- -+- ai->ai_family = AF_INET; -+- ai->ai_socktype = proto; -+- ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; -+- ai->ai_addrlen = sizeof(*sin); -+- ai->ai_addr = (struct sockaddr *)sin; -+- sin->sin_family = AF_INET; -+- sin->sin_port = port; -+- sin->sin_addr.s_addr = nip; -+- -+- releaseresolvsem(); -+- return 0; -+- } -+- -+- hent = gethostbyname((char *)nodename); -+- -+- if (!hent) -+- { -+- int herr = errno; -+- releaseresolvsem(); -+- return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL -+- : EAI_NONAME; -+- } -+- -+- if (!hent->h_addr_list || !hent->h_addr_list[0]) -+- { -+- releaseresolvsem(); -+- return EAI_NONAME; -+- } -+- -+- addrp = hent->h_addr_list; -+- } -+- else -+- { -+- addrp = passive_list; -+- } -+- -+- for (; *addrp; addrp++) -+- { -+- struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); -+- struct sockaddr_in *sin; -+- -+- if (!ai) -+- { -+- releaseresolvsem(); -+- freeaddrinfo(*res); -+- *res = NULL; -+- return EAI_MEMORY; -+- } -+- -+- if (!*res) -+- *res = ai; -+- *tail = ai; -+- tail = &ai->ai_next; -+- -+- sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); -+- -+- if (!sin) -+- { -+- releaseresolvsem(); -+- freeaddrinfo(*res); -+- *res = NULL; -+- return EAI_MEMORY; -+- } -+- -+- ai->ai_family = AF_INET; -+- ai->ai_socktype = proto; -+- ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; -+- ai->ai_addrlen = sizeof(*sin); -+- ai->ai_addr = (struct sockaddr *)sin; -+- sin->sin_family = AF_INET; -+- sin->sin_port = port; -+- -+- if (!hints || !(hints->ai_flags & AI_PASSIVE)) -+- { -+- size_t cpylen = sizeof(sin->sin_addr); -+- -+- if (hent->h_length > 0 && (size_t)hent->h_length < cpylen) -+- cpylen = (size_t)hent->h_length; -+- -+- memcpy(&sin->sin_addr, *addrp, cpylen); -+- } -+- } -+- -+- releaseresolvsem(); -+- return 0; -+-} -+- -+-void freeaddrinfo(struct addrinfo *ai) -+-{ -+- struct addrinfo *next; -+- -+- while (ai) -+- { -+- free(ai->ai_addr); -+- next = ai->ai_next; -+- free(ai); -+- ai = next; -+- } -+-} -+- -+-static const char *ai_errlist[] = -+- { -+- "Success", -+- "hostname nor servname provided, or not known", -+- "Temporary failure in name resolution", -+- "Non-recoverable failure in name resolution", -+- "No address associated with hostname", -+- "ai_family not supported", -+- "ai_socktype not supported", -+- "service name not supported for ai_socktype", -+- "Address family for hostname not supported", -+- "Memory allocation failure", -+- "System error returned in errno", -+- "Unknown error", -+-}; -+- -+-char *gai_strerror(int ecode) -+-{ -+- if (ecode > 0 || ecode < EAI_UNKNOWN) -+- ecode = EAI_UNKNOWN; -+- return (char *)ai_errlist[-ecode]; -+-} -+- -+-#endif /* !HAVE_GETADDRINFO */ -+- -+-#ifndef HAVE_GETNAMEINFO -+- -+-#ifndef NI_DATAGRAM -+-#define NI_DATAGRAM (1 << 4) -+-#endif -+- -+-int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) -+-{ -+- const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; -+- -+- (void)salen; -+- -+- if (sa->sa_family != AF_INET) -+- return EAI_ADDRFAMILY; -+- -+- if (host && hostlen > 0) -+- { -+- if (!(flags & NI_NUMERICHOST)) -+- { -+- struct hostent *he; -+- -+- lockresolvsem(); -+- he = gethostbyaddr((char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET); -+- -+- if (he) -+- { -+- safe_strncpy(host, (char *)he->h_name, hostlen); -+- releaseresolvsem(); -+- } -+- else -+- { -+- int herr = errno; -+- releaseresolvsem(); -+- if (flags & NI_NAMEREQD) -+- return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL -+- : EAI_NONAME; -+- flags |= NI_NUMERICHOST; -+- } -+- } -+- -+- if (flags & NI_NUMERICHOST) -+- { -+- lockhostsem(); -+- safe_strncpy(host, (char *)Inet_NtoA(sin->sin_addr.s_addr), hostlen); -+- releasehostsem(); -+- } -+- } -+- -+- if (serv && servlen > 0) -+- { -+- if (!(flags & NI_NUMERICSERV)) -+- { -+- struct servent *se; -+- -+- lockresolvsem(); -+- -+- se = (flags & NI_DATAGRAM) ? getservbyport(ntohs(sin->sin_port), "udp") : getservbyport(ntohs(sin->sin_port), "tcp"); -+- -+- if (se) -+- { -+- safe_strncpy(serv, (char *)se->s_name, servlen); -+- releaseresolvsem(); -+- } -+- else -+- { -+- releaseresolvsem(); -+- -+- if (flags & NI_NAMEREQD) -+- return EAI_NONAME; -+- -+- flags |= NI_NUMERICSERV; -+- } -+- } -+- -+- if (flags & NI_NUMERICSERV) -+- snprintf(serv, servlen, "%u", ntohs(sin->sin_port)); -+- } -+- -+- return 0; -+-} -+- -+-#endif /* !HAVE_GETNAMEINFO */ -+-#endif /* AMIGA */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/sem.c binkd_pgul/amiga/sem.c -+--- binkd/amiga/sem.c 2026-04-25 19:33:46.416919700 +0100 -++++ binkd_pgul/amiga/sem.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -1,100 +1,29 @@ -+ /* -+ * Amiga semaphores -+ */ -+- -+ #include -+ #include -+-#include -+-#include -+- -+-extern void Log(int lev, char *s, ...); -+- -+-int _InitSem(void *vpSem) -+-{ -+- memset(vpSem, 0, sizeof(struct SignalSemaphore)); -+- InitSemaphore((struct SignalSemaphore *)vpSem); -+- return 0; -+-} -+- -+-int _CleanSem(void *vpSem) -+-{ -+- return 0; -+-} -+- -+-int _LockSem(void *vpSem) -+-{ -+- ObtainSemaphore((struct SignalSemaphore *)vpSem); -+- return 0; -+-} -+- -+-int _ReleaseSem(void *vpSem) -+-{ -+- ReleaseSemaphore((struct SignalSemaphore *)vpSem); -+- return 0; -+-} -++#include -+ -+-int _InitEventSem(EVENTSEM *sem) -+-{ -+- if (!sem) -+- return -1; -++extern void Log (int lev, char *s,...); -+ -+- sem->waiter = NULL; -+ -+- sem->sigbit = AllocSignal(-1); -+- -+- if (sem->sigbit == (ULONG)-1) -+- return -1; -+- -+- return 0; -++int _InitSem(void *vpSem) { -++ memset(vpSem, 0, sizeof (struct SignalSemaphore)); -++ InitSemaphore ((struct SignalSemaphore*)vpSem); -++ return(0); -+ } -+ -+-int _CleanEventSem(EVENTSEM *sem) -+-{ -+- if (!sem) -+- return -1; -+- -+- if (sem->sigbit != (ULONG)-1) -+- { -+- FreeSignal((LONG)sem->sigbit); -+- sem->sigbit = (ULONG)-1; -+- } -+- -+- sem->waiter = NULL; -+- return 0; -++int _CleanSem(void *vpSem) { -++ return (0); -+ } -+ -+-int _PostSem(EVENTSEM *sem) -+-{ -+- if (!sem) -+- return -1; -+- -+- if (sem->waiter && sem->sigbit != (ULONG)-1) -+- { -+- Signal((struct Task *)sem->waiter, 1UL << sem->sigbit); -+- } -+- -+- return 0; -++int _LockSem(void *vpSem) { -++ ObtainSemaphore ((struct SignalSemaphore *)vpSem); -++ return (0); -+ } -+ -+-int _WaitSem(EVENTSEM *sem, int sec) -+-{ -+- ULONG mask; -+- struct Task *me; -+- -+- if (!sem || sem->sigbit == (ULONG)-1) -+- return -1; -+- -+- me = FindTask(NULL); -+- sem->waiter = me; -+- -+- /* Wait on SIGBREAKF_CTRL_C to avoid hanging on race */ -+- mask = Wait((1UL << sem->sigbit) | SIGBREAKF_CTRL_C); -+- -+- sem->waiter = NULL; -+- -+- /* Return timeout/break indication if only CTRL_C fired */ -+- if (!(mask & (1UL << sem->sigbit)) && (mask & SIGBREAKF_CTRL_C)) -+- return -1; -+- -+- return 0; -++int _ReleaseSem(void *vpSem) { -++ ReleaseSemaphore ((struct SignalSemaphore *)vpSem); -++ return (0); -+ } -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/session.c binkd_pgul/amiga/session.c -+--- binkd/amiga/session.c 2026-04-26 11:57:30.521108284 +0100 -++++ binkd_pgul/amiga/session.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,462 +0,0 @@ -+-/* -+- * session.c -- session management for AmigaOS 3 binkd -+- * -+- * session.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include -+-#include -+- -+-#include -+-#include -+-#include -+-#include -+- -+-#include "sys.h" -+-#include "iphdr.h" -+-#include "readcfg.h" -+-#include "common.h" -+-#include "tools.h" -+-#include "client.h" -+-#include "protocol.h" -+-#include "ftnq.h" -+-#include "ftnnode.h" -+-#include "ftnaddr.h" -+-#include "bsy.h" -+-#include "iptools.h" -+-#include "rfc2553.h" -+-#include "srv_gai.h" -+-#include "amiga/bsdsock.h" -+-#include "amiga/evloop_int.h" -+-#include "amiga/proto_amiga.h" -+- -+-extern int binkd_exit; -+-extern int ext_rand; -+-extern int client_flag; -+-extern int poll_flag; -+- -+-/* Session table */ -+-int sess_alloc(void) -+-{ -+- int i; -+- -+- for (i = 0; i < max_sessions; i++) -+- { -+- if (sessions[i].phase == SESS_FREE) -+- { -+- memset(&sessions[i], 0, sizeof(sess_t)); -+- sessions[i].fd = INVALID_SOCKET; -+- sessions[i].phase = SESS_FREE; -+- return i; -+- } -+- } -+- -+- return -1; -+-} -+- -+-void sess_free(int idx) -+-{ -+- sess_t *s = &sessions[idx]; -+- -+- if (s->fd != INVALID_SOCKET) -+- { -+- soclose(s->fd); -+- s->fd = INVALID_SOCKET; -+- } -+- -+- if (s->ai_head) -+- { -+- freeaddrinfo(s->ai_head); -+- s->ai_head = NULL; -+- } -+- -+- memset(&s->state, 0, sizeof(STATE)); -+- s->phase = SESS_FREE; -+-} -+- -+-/* Inbound: accept a new connection */ -+-void do_accept(SOCKET lfd, BINKD_CONFIG *config) -+-{ -+- struct sockaddr_storage sa; -+- socklen_t salen = (socklen_t)sizeof(sa); -+- SOCKET fd; -+- int idx; -+- sess_t *s; -+- char host[BINKD_FQDNLEN + 1]; -+- char ip[BINKD_FQDNLEN + 1]; -+- -+- fd = accept(lfd, (struct sockaddr *)&sa, &salen); -+- -+- if (fd == INVALID_SOCKET) -+- { -+- if (TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN) -+- Log(1, "accept(): %s", TCPERR()); -+- -+- return; -+- } -+- -+- if (binkd_exit) -+- { -+- soclose(fd); -+- return; -+- } -+- -+- idx = sess_alloc(); -+- -+- if (idx < 0) -+- { -+- Log(1, "session table full, refusing inbound"); -+- soclose(fd); -+- return; -+- } -+- -+- /* getnameinfo() Is unreliable on AmiTCP: use inet_ntoa directly */ -+- if (((struct sockaddr *)&sa)->sa_family == AF_INET) -+- { -+- struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; -+- strnzcpy(ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); -+- } -+- else -+- { -+- strnzcpy(ip, "unknown", BINKD_FQDNLEN); -+- } -+- -+- /* Backresolv not supported on AmiTCP: always use IP as host */ -+- strnzcpy(host, ip, BINKD_FQDNLEN); -+- -+- set_nonblock(fd); -+- -+- s = &sessions[idx]; -+- s->fd = fd; -+- s->inbound = 1; -+- s->node = NULL; -+- s->ai_head = NULL; -+- s->last_io = time(NULL); -+- strnzcpy(s->host, host, BINKD_FQDNLEN); -+- strnzcpy(s->ip, ip, BINKD_FQDNLEN); -+- s->port[0] = '\0'; -+- -+- if (amiga_proto_open(&s->state, fd, NULL, NULL, s->host, NULL, s->ip, config) != 0) -+- { -+- Log(1, "proto_open failed for %s", ip); -+- sess_free(idx); -+- return; -+- } -+- -+- s->phase = SESS_RUNNING; -+- n_servers++; -+- Log(4, "inbound slot[%d] from %s", idx, ip); -+-} -+- -+-/* Outbound: Non-blocking connect() */ -+-int start_connect(sess_t *s, BINKD_CONFIG *config) -+-{ -+- SOCKET fd; -+- int rc; -+- -+- s->ip[0] = '\0'; -+- s->port[0] = '\0'; -+- -+- fd = socket(s->ai_cur->ai_family, s->ai_cur->ai_socktype, s->ai_cur->ai_protocol); -+- -+- if (fd == INVALID_SOCKET) -+- { -+- Log(1, "outbound socket(): %s", TCPERR()); -+- return -1; -+- } -+- -+- /* getnameinfo() is unreliable on AmiTCP: may return rc=0 with garbage -+- * Use inet_ntoa/ntohs directly */ -+- if (s->ai_cur->ai_family == AF_INET) -+- { -+- struct sockaddr_in *sa4 = (struct sockaddr_in *)s->ai_cur->ai_addr; -+- strnzcpy(s->ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); -+- snprintf(s->port, MAXPORTSTRLEN, "%u", (unsigned)ntohs(sa4->sin_port)); -+- } -+- else -+- { -+- strnzcpy(s->ip, "unknown", BINKD_FQDNLEN); -+- strnzcpy(s->port, "0", MAXPORTSTRLEN); -+- } -+- -+- Log(4, "connecting %s [%s]:%s", s->host, s->ip, s->port); -+- -+- if (config->bindaddr[0]) -+- { -+- struct addrinfo src_h, *src_ai; -+- memset(&src_h, 0, sizeof(src_h)); -+- src_h.ai_family = s->ai_cur->ai_family; -+- src_h.ai_socktype = SOCK_STREAM; -+- src_h.ai_protocol = IPPROTO_TCP; -+- -+- if (getaddrinfo(config->bindaddr, NULL, &src_h, &src_ai) == 0) -+- { -+- bind(fd, src_ai->ai_addr, (int)src_ai->ai_addrlen); -+- freeaddrinfo(src_ai); -+- } -+- } -+- -+- set_nonblock(fd); -+- -+- rc = connect(fd, s->ai_cur->ai_addr, (int)s->ai_cur->ai_addrlen); -+- -+- if (rc == 0 || TCPERRNO == EINPROGRESS || TCPERRNO == EWOULDBLOCK) -+- { -+- s->fd = fd; -+- s->conn_start = time(NULL); -+- return 0; -+- } -+- -+- Log(1, "connect %s: %s", s->host, TCPERR()); -+- -+- bad_try(&s->node->fa, TCPERR(), BAD_CALL, config); -+- soclose(fd); -+- -+- return -1; -+-} -+- -+-int try_outbound(BINKD_CONFIG *config) -+-{ -+- FTN_NODE *node; -+- sess_t *s; -+- int idx, rc; -+- struct addrinfo hints; -+- char dest[FTN_ADDR_SZ + 1]; -+- char host[BINKD_FQDNLEN + 5 + 1]; -+- char port[MAXPORTSTRLEN + 1]; -+- -+- if (!client_flag) -+- return 0; -+- -+- if (!config->q_present) -+- { -+- q_free(SCAN_LISTED, config); -+- -+- if (config->printq) -+- Log(-1, "scan\r"); -+- -+- q_scan(SCAN_LISTED, config); -+- config->q_present = 1; -+- -+- if (config->printq) -+- { -+- q_list(stderr, SCAN_LISTED, config); -+- Log(-1, "idle\r"); -+- } -+- } -+- -+- if (n_clients >= config->max_clients) -+- return 0; -+- -+- node = q_next_node(config); -+- -+- if (!node) -+- return 0; -+- -+- ftnaddress_to_str(dest, &node->fa); -+- -+- if (!bsy_test(&node->fa, F_BSY, config) || !bsy_test(&node->fa, F_CSY, config)) -+- { -+- Log(4, "%s busy", dest); -+- return 0; -+- } -+- -+- idx = sess_alloc(); -+- -+- if (idx < 0) -+- { -+- Log(2, "table full, deferring %s", dest); -+- return 0; -+- } -+- -+- s = &sessions[idx]; -+- memset(s, 0, sizeof(*s)); -+- s->fd = INVALID_SOCKET; -+- s->node = node; -+- s->inbound = 0; -+- -+- rc = get_host_and_port(1, host, port, node->hosts, &node->fa, config); -+- -+- if (rc <= 0) -+- { -+- Log(1, "%s: bad host list", dest); -+- sess_free(idx); -+- -+- return 0; -+- } -+- -+- strnzcpy(s->host, host, BINKD_FQDNLEN); -+- strnzcpy(s->port, port, MAXPORTSTRLEN); -+- -+- memset(&hints, 0, sizeof(hints)); -+- hints.ai_family = node->IP_afamily; -+- hints.ai_socktype = SOCK_STREAM; -+- hints.ai_protocol = IPPROTO_TCP; -+- -+- rc = srv_getaddrinfo(host, port, &hints, &s->ai_head); -+- -+- if (rc != 0) -+- { -+- Log(1, "%s: getaddrinfo error code=%d: %s", dest, rc, gai_strerror(rc)); -+- -+- bad_try(&node->fa, "getaddrinfo failed", BAD_CALL, config); -+- sess_free(idx); -+- -+- return 0; -+- } -+- -+- s->ai_cur = s->ai_head; -+- -+- if (start_connect(s, config) != 0) -+- { -+- sess_free(idx); -+- return 0; -+- } -+- -+- s->phase = SESS_CONNECTING; -+- n_clients++; -+- -+- Log(4, "outbound slot[%d] -> %s", idx, dest); -+- -+- return 1; -+-} -+- -+-/* Check completion of async connect() */ -+-void check_connect(int idx, BINKD_CONFIG *config) -+-{ -+- sess_t *s = &sessions[idx]; -+- int err = 0; -+- socklen_t el = (socklen_t)sizeof(err); -+- int tmo; -+- -+- tmo = config->connect_timeout ? config->connect_timeout : 30; -+- -+- if ((int)(time(NULL) - s->conn_start) >= tmo) -+- { -+- Log(1, "connect timeout -> %s", s->host); -+- -+- bad_try(&s->node->fa, "Timeout", BAD_CALL, config); -+- n_clients--; -+- sess_free(idx); -+- -+- return; -+- } -+- -+- getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &el); -+- -+- if (err) -+- { -+- Log(1, "connect -> %s: %s", s->host, strerror(err)); -+- -+- bad_try(&s->node->fa, strerror(err), BAD_CALL, config); -+- -+- soclose(s->fd); -+- s->fd = INVALID_SOCKET; -+- s->ai_cur = s->ai_cur->ai_next; -+- -+- if (s->ai_cur && start_connect(s, config) == 0) -+- return; /* trying next address */ -+- -+- n_clients--; -+- sess_free(idx); -+- -+- return; -+- } -+- -+- Log(4, "connected -> %s [%s]", s->host, s->ip); -+- ext_rand = rand(); -+- -+- if (amiga_proto_open(&s->state, s->fd, s->node, NULL, s->host, s->port, s->ip, config) != 0) -+- { -+- Log(1, "proto_open failed for %s", s->host); -+- -+- n_clients--; -+- sess_free(idx); -+- return; -+- } -+- -+- s->phase = SESS_RUNNING; -+- s->last_io = time(NULL); -+-} -+- -+-/* Run one protocol step on an active session */ -+-void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config) -+-{ -+- sess_t *s = &sessions[idx]; -+- int rc; -+- int tmo; -+- -+- tmo = config->nettimeout ? config->nettimeout : 300; -+- -+- if ((int)(time(NULL) - s->last_io) >= tmo) -+- { -+- Log(1, "slot[%d] net timeout", idx); -+- -+- if (s->node) -+- bad_try(&s->node->fa, "Timeout", BAD_IO, config); -+- -+- amiga_proto_close(&s->state, config, 0); -+- -+- if (s->inbound) -+- n_servers--; -+- else -+- n_clients--; -+- -+- sess_free(idx); -+- -+- return; -+- } -+- -+- if (s->fd == INVALID_SOCKET) -+- { -+- Log(1, "slot[%d] invalid socket, closing session", idx); -+- -+- if (s->node) -+- bad_try(&s->node->fa, "Invalid socket", BAD_IO, config); -+- -+- if (s->inbound) -+- n_servers--; -+- else -+- n_clients--; -+- -+- sess_free(idx); -+- -+- return; -+- } -+- -+- /* WaitSelect() may not report a readable socket when the remote has -+- * sent a TCP END. Probe with MSG_PEEK so recv_block() sees the EOF */ -+- if (!rd && !wr && s->state.state != P_NULL) -+- { -+- char peek; -+- int pr = recv(s->fd, &peek, 1, MSG_PEEK); -+- -+- if (pr == 0 || (pr < 0 && TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN)) -+- rd = 1; -+- } -+- -+- rc = amiga_proto_step(&s->state, rd, wr, config); -+- -+- if (rd || wr) -+- s->last_io = time(NULL); -+- -+- if (rc == APROTO_RUNNING) -+- return; -+- -+- amiga_proto_close(&s->state, config, rc == APROTO_DONE_OK); -+- -+- Log(4, "slot[%d] %s", idx, rc == APROTO_DONE_OK ? "OK" : "ERR"); -+- -+- if (s->inbound) -+- n_servers--; -+- else -+- n_clients--; -+- -+- sess_free(idx); -+- -+- if (poll_flag && n_clients == 0 && n_servers == 0) -+- binkd_exit = 1; -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/sock.c binkd_pgul/amiga/sock.c -+--- binkd/amiga/sock.c 2026-04-26 11:59:26.645813693 +0100 -++++ binkd_pgul/amiga/sock.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,130 +0,0 @@ -+-/* -+- * sock.c -- listen socket management for AmigaOS 3 -+- * -+- * sock.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include -+-#include -+- -+-#include -+-#include -+- -+-#include "sys.h" -+-#include "readcfg.h" -+-#include "tools.h" -+-#include "server.h" -+-#include "rfc2553.h" -+-#include "amiga/bsdsock.h" -+-#include "amiga/evloop_int.h" -+- -+-extern SOCKET sockfd[]; -+-extern int sockfd_used; -+-extern int server_flag; -+- -+-void set_nonblock(SOCKET fd) -+-{ -+- long flag = 1L; -+- -+- if (IoctlSocket(fd, FIONBIO, (char *)&flag) != 0) -+- Log(2, "IoctlSocket(FIONBIO) failed: %s", TCPERR()); -+-} -+- -+-int open_listen_sockets(BINKD_CONFIG *config) -+-{ -+- struct listenchain *ll; -+- struct addrinfo hints, *ai, *head; -+- int err, opt = 1; -+- -+- memset(&hints, 0, sizeof(hints)); -+- hints.ai_flags = AI_PASSIVE; -+- hints.ai_family = PF_UNSPEC; -+- hints.ai_socktype = SOCK_STREAM; -+- hints.ai_protocol = IPPROTO_TCP; -+- -+- sockfd_used = 0; -+- -+- for (ll = config->listen.first; ll; ll = ll->next) -+- { -+- err = getaddrinfo(ll->addr[0] ? ll->addr : NULL, ll->port, &hints, &head); -+- -+- if (err) -+- { -+- Log(1, "listen getaddrinfo(%s:%s): %s", ll->addr[0] ? ll->addr : "*", ll->port, gai_strerror(err)); -+- return -1; -+- } -+- -+- for (ai = head; ai && sockfd_used < MAX_LISTENSOCK; ai = ai->ai_next) -+- { -+- SOCKET fd; -+- int retries = 6; -+- -+- fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); -+- -+- if (fd == INVALID_SOCKET) -+- { -+- Log(1, "listen socket(): %s", TCPERR()); -+- continue; -+- } -+- -+- if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, (int)sizeof(opt)) != 0) -+- Log(2, "setsockopt(SO_REUSEADDR) failed: %s", TCPERR()); -+- -+- /* Bsdsocket may hold the port briefly after socket close */ -+- while (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) != 0) -+- { -+- if (--retries == 0) -+- { -+- Log(1, "listen bind(): %s", TCPERR()); -+- -+- soclose(fd); -+- freeaddrinfo(head); -+- return -1; -+- } -+- -+- Log(2, "bind retry in 2s: %s", TCPERR()); -+- -+- Delay(100UL); /* 100 ticks = 2s @ 50Hz */ -+- } -+- -+- if (listen(fd, 5) != 0) -+- { -+- Log(1, "listen(): %s", TCPERR()); -+- -+- soclose(fd); -+- freeaddrinfo(head); -+- return -1; -+- } -+- -+- set_nonblock(fd); -+- sockfd[sockfd_used] = fd; -+- sockfd_used++; -+- } -+- -+- freeaddrinfo(head); -+- -+- Log(3, "listening on %s:%s", -+- ll->addr[0] ? ll->addr : "*", ll->port); -+- } -+- -+- if (sockfd_used == 0 && server_flag) -+- { -+- Log(1, "evloop: no listen sockets opened"); -+- return -1; -+- } -+- -+- return 0; -+-} -+- -+-void close_listen_sockets(void) -+-{ -+- int i; -+- -+- for (i = 0; i < sockfd_used; i++) -+- soclose(sockfd[i]); -+- -+- sockfd_used = 0; -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/utime.c binkd_pgul/amiga/utime.c -+--- binkd/amiga/utime.c 2026-04-26 11:59:41.408046130 +0100 -++++ binkd_pgul/amiga/utime.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,77 +0,0 @@ -+-/* -+- * utime.c -- utime() stub for AmigaOS 3 without ixemul -+- * -+- * utime.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifdef AMIGA -+- -+-#include -+-#include -+-#include -+-#include -+- -+-#include "amiga/dirent.h" /* declares struct utimbuf and utime() prototype */ -+- -+-/* Days between AmigaDOS epoch (1978-01-01) and POSIX epoch (1970-01-01) */ -+-#define AMIGA_EPOCH_DELTA_DAYS 2922UL -+-#define SECONDS_PER_DAY 86400UL -+-#define SECONDS_PER_MINUTE 60UL -+- -+-int utime(const char *path, const struct utimbuf *times) -+-{ -+- struct DateStamp ds; -+- LONG seconds_today; -+- LONG total_seconds; -+- -+- if (!path) -+- { -+- errno = EINVAL; -+- return -1; -+- } -+- -+- if (!times) -+- { -+- /* Use current time */ -+- DateStamp(&ds); -+- } -+- else -+- { -+- LONG t = (LONG)times->modtime; -+- -+- if (t < (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY)) -+- { -+- /* Time predates AmigaDOS epoch -- clamp to epoch */ -+- t = 0; -+- } -+- else -+- { -+- t -= (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY); -+- } -+- -+- ds.ds_Days = (LONG)(t / (LONG)SECONDS_PER_DAY); -+- total_seconds = t % (LONG)SECONDS_PER_DAY; -+- ds.ds_Minute = (LONG)(total_seconds / (LONG)SECONDS_PER_MINUTE); -+- seconds_today = total_seconds % (LONG)SECONDS_PER_MINUTE; -+- ds.ds_Tick = seconds_today * (LONG)TICKS_PER_SECOND; -+- } -+- -+- if (!SetFileDate((STRPTR)path, &ds)) -+- { -+- /* Most likely cause: file does not exist or is write-protected */ -+- LONG err = IoErr(); -+- -+- if (err == ERROR_OBJECT_NOT_FOUND || err == ERROR_DIR_NOT_FOUND) -+- errno = ENOENT; -+- else -+- errno = EACCES; -+- return -1; -+- } -+- -+- return 0; -+-} -+- -+-#endif /* AMIGA */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/binkd.c binkd_pgul/binkd.c -+--- binkd/binkd.c 2026-04-26 13:20:44.986212073 +0100 -++++ binkd_pgul/binkd.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -54,10 +54,6 @@ -+ #include "unix/daemonize.h" -+ #endif -+ -+-#ifdef AMIGA -+-/* amiga/bsdsock.h pulled in via iphdr.h for AMIGA */ -+-#include "amiga/evloop.h" -+-#endif -+ #ifdef WIN32 -+ #include "nt/service.h" -+ #include "nt/w32tools.h" -+@@ -66,11 +62,9 @@ -+ #endif -+ #endif -+ -+-#include "bsycleanup.h" -+- -+ #include "confopt.h" -+ -+-#if defined(HAVE_THREADS) || defined(AMIGA) -++#ifdef HAVE_THREADS -+ MUTEXSEM hostsem; -+ MUTEXSEM resolvsem; -+ MUTEXSEM lsem; -+@@ -100,13 +94,9 @@ -+ char *configpath = NULL; /* Config file name */ -+ char **saved_envp; -+ -+-/* mypid: needed by HAVE_FORK and AMIGA targets */ -+-#if defined(HAVE_FORK) || defined(AMIGA) -+-int mypid; -+-#endif -+- -+ #ifdef HAVE_FORK -+-int got_sighup, got_sigchld; -++ -++int mypid, got_sighup, got_sigchld; -+ -+ void chld (int *childcount) -+ { -+@@ -205,13 +195,9 @@ -+ #endif -+ " -C reload on config change\n" -+ " -c run client only\n" -+-#ifndef AMIGA -+ " -i run server on stdin/stdout pipe (by inetd or other)\n" -+-#endif -+ " -f node run server protected session with this node\n" -+-#ifndef AMIGA -+ " -a ip assume remote address when running with '-i' switch\n" -+-#endif -+ #if defined(BINKD9X) -+ " -t cmd (start|stop|restart|status|install|uninstall) service(s)\n" -+ " -S name set Win9x service name, all - use all services\n" -+@@ -326,14 +312,12 @@ -+ case 'c': -+ client_flag = 1; -+ break; -+-#ifndef AMIGA -+ case 'i': -+ inetd_flag = 1; -+ break; -+ case 'a': /* remote IP address */ -+ remote_addr = strdup(optarg); -+ break; -+-#endif -+ case 'f': /* remote FTN address */ -+ remote_node = strdup(optarg); -+ break; -+@@ -432,28 +416,6 @@ -+ } -+ if (optind\n", extract_filename(argv[0])); -+- fprintf(stderr, " Use -P
for polling a specific node (e.g., -P 1:23/456.7)\n"); -+- exit(1); -+- } -+- -+- /* Check for leftover FTN addresses in extra arguments */ -+- while (optind < argc) -+- { -+- char *extra = argv[optind++]; -+- if (strchr(extra, ':') || strchr(extra, '@')) -+- { -+- fprintf(stderr, "%s: Error: Unexpected FTN address '%s' in arguments.\n", extract_filename(argv[0]), extra); -+- fprintf(stderr, " Use -P
before the config file to poll a node.\n"); -+- exit(1); -+- } -+- } -+- -+ #ifdef OS2 -+ if (optindloglevel, current_config->conlog, -+ current_config->logpath, current_config->nolog.first); -+- -+- /* Clean up old .bsy/.csy files at startup */ -+- cleanup_old_bsy(current_config); -+ } -+ else if (verbose_flag) -+ { -+@@ -706,13 +665,9 @@ -+ -+ if (p) -+ { -+- remote_addr = strdup(p); -+- /* Guard against null pointer dereference if strdup fails */ -+- if (remote_addr) -+- { -+- p = strchr(remote_addr, ' '); -+- if (p) *p = '\0'; -+- } -++ remote_addr = strdup(p); -++ p = strchr(remote_addr, ' '); -++ if (p) *p = '\0'; -+ } -+ } -+ /* not using stdin/stdout itself to avoid possible collisions */ -+@@ -722,9 +677,6 @@ -+ inetd_socket_out = dup(fileno(stdout)); -+ #ifdef UNIX -+ tempfd = open("/dev/null", O_RDWR); -+-#elif defined(AMIGA) -+- /* NIL: is the native AmigaDOS null device (no ixemul) */ -+- tempfd = open("NIL:", O_RDWR); -+ #else -+ tempfd = open("nul", O_RDWR); -+ #endif -+@@ -755,15 +707,6 @@ -+ signal (SIGHUP, sighandler); -+ #endif -+ -+-#ifdef AMIGA -+- /* AmigaOS 3: WaitSelect() loop, no fork/threads */ -+- { -+- BINKD_CONFIG *ev_cfg = lock_current_config(); -+- amiga_evloop_run(ev_cfg, server_flag, client_flag); -+- unlock_config_structure(ev_cfg, 0); -+- return 0; -+- } -+-#else -+ if (client_flag && !server_flag) -+ { -+ clientmgr (0); -+@@ -778,9 +721,7 @@ -+ if (client_flag && (pidcmgr = branch (clientmgr, 0, 0)) < 0) -+ { -+ Log (0, "cannot branch out"); -+- exit (1); -+ } -+-#endif /* !AMIGA */ -+ -+ if (*current_config->pid_file) -+ { -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/binlog.c binkd_pgul/binlog.c -+--- binkd/binlog.c 2026-04-22 06:50:46.000000000 +0100 -++++ binkd_pgul/binlog.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -35,10 +35,6 @@ -+ #include "tools.h" -+ #include "sem.h" -+ -+-#if defined(HAVE_THREADS) || defined(AMIGA) -+-extern MUTEXSEM blsem; -+-#endif -+- -+ /* Write 16-bit integer to file in intel bytes order */ -+ static int fput16(u16 arg, FILE *file) -+ { -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/branch.c binkd_pgul/branch.c -+--- binkd/branch.c 2026-04-26 09:12:44.314385608 +0100 -++++ binkd_pgul/branch.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -20,6 +20,12 @@ -+ #include "tools.h" -+ #include "sem.h" -+ -++#ifdef AMIGA -++int ix_vfork (void); -++void vfork_setup_child (void); -++void ix_vfork_resume (void); -++#endif -++ -+ #ifdef WITH_PTHREADS -+ typedef struct { -+ void (*F) (void *); -+@@ -126,6 +132,23 @@ -+ #endif -+ #endif -+ -++#ifdef AMIGA -++ /* this is rather bizzare. this function pretends to be a fork and behaves -++ * like one, but actually it's a kind of a thread. so we'll need semaphores */ -++ -++ if (!(rc = ix_vfork ())) -++ { -++ vfork_setup_child (); -++ ix_vfork_resume (); -++ F (arg); -++ exit (0); -++ } -++ else if (rc < 0) -++ { -++ Log (1, "ix_vfork: %s", strerror (errno)); -++ } -++#endif -++ -+ #if defined(DOS) || defined(DEBUGCHILD) -+ rc = 0; -+ F (arg); -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/breaksig.c binkd_pgul/breaksig.c -+--- binkd/breaksig.c 2026-04-26 10:25:19.576809578 +0100 -++++ binkd_pgul/breaksig.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -46,16 +46,6 @@ -+ { -+ atexit (exitfunc); -+ -+-#ifdef AMIGA -+- /* AmigaOS / libnix: signal() maps SIGINT -> SIGBREAKF_CTRL_C -+- * Register exitsig() so that Ctrl+C sets binkd_exit=1 even when -+- * the process is NOT blocked inside WaitSelect() (e.g. during disk -+- * I/O). When inside WaitSelect(), amiga_select_wrap() in bsdsock.h -+- * detects the break and sets binkd_exit=1 directly without going -+- * through this signal handler. */ -+- signal (SIGINT, exitsig); -+- signal (SIGTERM, exitsig); -+-#else -+ #ifdef SIGBREAK -+ signal (SIGBREAK, exitsig); -+ #endif -+@@ -68,6 +58,5 @@ -+ #ifdef SIGTERM -+ signal (SIGTERM, exitsig); -+ #endif -+-#endif /* AMIGA */ -+ return 1; -+ } -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/bsycleanup.c binkd_pgul/bsycleanup.c -+--- binkd/bsycleanup.c 2026-04-26 11:01:23.269049076 +0100 -++++ binkd_pgul/bsycleanup.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,122 +0,0 @@ -+-/* -+- * bsycleanup.c -- Cleanup functions for .bsy/.csy/.try files -+- * -+- * bsycleanup.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include -+-#include -+-#include -+-#include -+-#include -+- -+-#include "sys.h" -+-#include "readcfg.h" -+-#include "ftnq.h" -+-#include "ftnnode.h" -+-#include "tools.h" -+-#include "readdir.h" -+- -+-/* -+- * Clean up old .bsy and .csy files at startup -+- * Scans all domain outbounds -+- */ -+- -+-/* Helper: scan a single directory for .bsy/.csy files and delete them */ -+-static void scan_and_delete_bsy_in_dir(const char *dir, BINKD_CONFIG *config) -+-{ -+- DIR *dp; -+- struct dirent *de; -+- char buf[MAXPATHLEN + 1]; -+- -+- if ((dp = opendir(dir)) == 0) -+- { -+- return; -+- } -+- -+- while ((de = readdir(dp)) != 0) -+- { -+- char *s = de->d_name; -+- int len = strlen(s); -+- -+- if (len > 4 && (!STRICMP(s + len - 4, ".bsy") || !STRICMP(s + len - 4, ".csy") || !STRICMP(s + len - 4, ".try"))) -+- { -+- strnzcpy(buf, dir, sizeof(buf)); -+- strnzcat(buf, PATH_SEPARATOR, sizeof(buf)); -+- strnzcat(buf, s, sizeof(buf)); -+- -+- Log(2, "deleting %s", buf); -+- -+- delete(buf); -+- } -+- } -+- -+- closedir(dp); -+-} -+- -+-void cleanup_old_bsy(BINKD_CONFIG *config) -+-{ -+- DIR *dp; -+- char outb_path[MAXPATHLEN + 1], base_path[MAXPATHLEN + 1]; -+- struct dirent *de; -+- FTN_DOMAIN *curr_domain; -+- int len; -+- -+- Log(2, "cleaning up .bsy/.csy/.try files at startup"); -+- -+- /* Scan all domain outbounds */ -+- for (curr_domain = config->pDomains.first; curr_domain; curr_domain = curr_domain->next) -+- { -+- if (curr_domain->alias4 != 0) -+- continue; -+- -+- /* Build base path: path + separator */ -+- strnzcpy(base_path, curr_domain->path, sizeof(base_path)); -+-#ifndef AMIGA -+- if (base_path[strlen(base_path) - 1] == ':') -+- strcat(base_path, PATH_SEPARATOR); -+-#endif -+- -+-#ifdef AMIGADOS_4D_OUTBOUND -+- if (config->aso) -+- { -+- /* ASO mode: direct outbound path */ -+- strnzcpy(outb_path, base_path, sizeof(outb_path)); -+- strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); -+- strnzcat(outb_path, curr_domain->dir, sizeof(outb_path)); -+- Log(7, "cleanup_old_bsy (ASO): scanning domain '%s', path '%s'", curr_domain->name, outb_path); -+- scan_and_delete_bsy_in_dir(outb_path, config); -+- } -+- else -+-#endif -+- { -+- /* BSO mode: scan for outbound.xxx directories */ -+- Log(7, "cleanup_old_bsy (BSO): scanning domain '%s', base '%s'", curr_domain->name, base_path); -+- -+- if ((dp = opendir(base_path)) == 0) -+- continue; -+- -+- len = strlen(curr_domain->dir); -+- -+- while ((de = readdir(dp)) != 0) -+- { -+- /* Match outbound or outbound.xxx */ -+- if (!STRNICMP(de->d_name, curr_domain->dir, len) && (de->d_name[len] == 0 || (de->d_name[len] == '.' && isxdigit(de->d_name[len + 1])))) -+- { -+- strnzcpy(outb_path, base_path, sizeof(outb_path)); -+- strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); -+- strnzcat(outb_path, de->d_name, sizeof(outb_path)); -+- -+- Log(7, "cleanup_old_bsy (BSO): scanning outbound dir '%s'", outb_path); -+- -+- scan_and_delete_bsy_in_dir(outb_path, config); -+- } -+- } -+- -+- closedir(dp); -+- } -+- } -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/bsycleanup.h binkd_pgul/bsycleanup.h -+--- binkd/bsycleanup.h 2026-04-26 13:11:39.607856201 +0100 -++++ binkd_pgul/bsycleanup.h 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,19 +0,0 @@ -+-/* -+- * bsycleanup.h -- Cleanup functions for .bsy/.csy/.try files -+- * -+- * bsycleanup.h is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#ifndef _BSYCLEANUP_H -+-#define _BSYCLEANUP_H -+- -+-#include "readcfg.h" -+- -+- -+-/* cleanup_old_bsy -- Clean up old .bsy/.csy/.try files at startup */ -+-void cleanup_old_bsy(BINKD_CONFIG *config); -+- -+-#endif /* _BSYCLEANUP_H */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/btypes.h binkd_pgul/btypes.h -+--- binkd/btypes.h 2026-04-25 20:39:58.604145925 +0100 -++++ binkd_pgul/btypes.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -73,7 +73,6 @@ -+ int HC_flag; -+ int restrictIP; -+ int NP_flag; /* no proxy */ -+- int NC_flag; /* no compression */ -+ -+ time_t hold_until; -+ int busy; /* 0=free, 'c'=.csy, other=.bsy */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/client.c binkd_pgul/client.c -+--- binkd/client.c 2026-04-26 10:25:03.436098652 +0100 -++++ binkd_pgul/client.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -19,10 +19,6 @@ -+ #include -+ #endif -+ -+-#ifdef AMIGA -+-#include "amiga/bsdsock.h" -+-#endif -+- -+ #include "sys.h" -+ #include "readcfg.h" -+ #include "client.h" -+@@ -48,11 +44,6 @@ -+ #include "rfc2553.h" -+ #include "srv_gai.h" -+ -+-#if defined(HAVE_THREADS) || defined(AMIGA) -+-extern MUTEXSEM lsem; -+-extern EVENTSEM eothread; -+-#endif -+- -+ static void call (void *arg); -+ -+ int n_clients = 0; -+@@ -211,8 +202,7 @@ -+ /* This sleep can be interrupted by signal, it's OK */ -+ unblocksig(); -+ check_child(&n_clients); -+- if (!config->no_call_delay) -+- SLEEP (config->call_delay); -++ SLEEP (config->call_delay); -+ check_child(&n_clients); -+ blocksig(); -+ } -+@@ -292,16 +282,8 @@ -+ #ifdef HAVE_THREADS -+ !server_flag && -+ #endif -+- /* AmigaOS uses shared-memory evloop — only main process calls checkcfg() -+- * On fork systems (Linux/FreeBSD) each process has separate memory -+- * so independent reloads are safe. */ -+ !poll_flag) -+-#ifndef AMIGA -+ checkcfg(); -+-#else -+- { -+- } -+-#endif -+ } -+ -+ Log (5, "downing clientmgr..."); -+@@ -324,7 +306,7 @@ -+ exit (0); -+ } -+ -+-int call0 (FTN_NODE *node, BINKD_CONFIG *config) -++static int call0 (FTN_NODE *node, BINKD_CONFIG *config) -+ { -+ int sockfd = INVALID_SOCKET; -+ int sock_out; -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/client.h binkd_pgul/client.h -+--- binkd/client.h 2026-04-26 10:24:36.096878628 +0100 -++++ binkd_pgul/client.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -1,20 +1,9 @@ -+ #ifndef _client_h -+ #define _client_h -+ -+-#ifdef AMIGA -+-#include -+-#include -+-#include -+-#endif -+- -+ /* -+ * Scans queue, makes outbound ``call'', than calls protocol() -+ */ -+ void clientmgr(void *arg); -+ -+-#ifdef AMIGA -+-/* Direct outbound call for evloop.c (no-ixemul, no-threads build) */ -+-int call0(FTN_NODE *node, BINKD_CONFIG *config); -+-#endif -+- -+ #endif -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/Config.h binkd_pgul/Config.h -+--- binkd/Config.h 2026-04-25 19:53:34.520405599 +0100 -++++ binkd_pgul/Config.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -14,8 +14,8 @@ -+ #ifndef _Config_h -+ #define _Config_h -+ -+-#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) + defined(AMIGA) == 0 -+-#error You must define HAVE_FORK, HAVE_THREADS, DOS, or AMIGA! -++#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) == 0 -++#error You must define either HAVE_FORK or HAVE_THREADS! -+ #endif -+ -+ #ifdef __WATCOMC__ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/exitproc.c binkd_pgul/exitproc.c -+--- binkd/exitproc.c 2026-04-26 10:21:07.372917687 +0100 -++++ binkd_pgul/exitproc.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -32,17 +32,6 @@ -+ #include "nt/w32tools.h" -+ #endif -+ -+-#if defined(HAVE_THREADS) || defined(AMIGA) -+-extern MUTEXSEM hostsem; -+-extern MUTEXSEM resolvsem; -+-extern MUTEXSEM lsem; -+-extern MUTEXSEM blsem; -+-extern MUTEXSEM varsem; -+-extern MUTEXSEM config_sem; -+-extern EVENTSEM eothread; -+-extern EVENTSEM wakecmgr; -+-#endif -+- -+ int binkd_exit; -+ -+ #ifdef HAVE_THREADS -+@@ -149,33 +138,6 @@ -+ close_srvmgr_socket(); -+ #endif -+ -+-#ifdef AMIGA -+- /* evloop: single process, no children -+- * Clean Exec semaphores in safe order before freeing config */ -+- close_srvmgr_socket(); -+- CleanEventSem(&wakecmgr); -+- CleanEventSem(&eothread); -+- CleanSem(&varsem); -+- CleanSem(&blsem); -+- CleanSem(&lsem); -+- CleanSem(&resolvsem); -+- CleanSem(&hostsem); -+- CleanSem(&config_sem); -+- sock_deinit(); -+- nodes_deinit(); -+- { -+- BINKD_CONFIG *cfg = lock_current_config(); -+- if (cfg) -+- bsy_remove_all(cfg); -+- if (cfg && *cfg->pid_file && pidsmgr == (int)getpid()) -+- delete(cfg->pid_file); -+- if (cfg) -+- unlock_config_structure(cfg, 1); -+- } -+- Log(6, "exitfunc: AmigaOS cleanup done"); -+- return; -+-#endif /* AMIGA */ -+- -+ config = lock_current_config(); -+ if (config) -+ bsy_remove_all (config); -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnnode.c binkd_pgul/ftnnode.c -+--- binkd/ftnnode.c 2026-04-26 09:22:13.159382256 +0100 -++++ binkd_pgul/ftnnode.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -74,7 +74,7 @@ -+ */ -+ static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, -+ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, -+- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, -++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, -+ int IP_afamily, -+ #ifdef BW_LIM -+ long bw_send, long bw_recv, -+@@ -107,7 +107,6 @@ -+ pn->NR_flag = NR_OFF; -+ pn->ND_flag = ND_OFF; -+ pn->NP_flag = NP_OFF; -+- pn->NC_flag = NC_OFF; -+ pn->MD_flag = MD_USE_OLD; -+ pn->HC_flag = HC_USE_OLD; -+ pn->pipe = NULL; -+@@ -135,8 +134,6 @@ -+ pn->ND_flag = ND_flag; -+ if (NP_flag != NP_USE_OLD) -+ pn->NP_flag = NP_flag; -+- if (NC_flag != NC_USE_OLD) -+- pn->NC_flag = NC_flag; -+ if (HC_flag != HC_USE_OLD) -+ pn->HC_flag = HC_flag; -+ if (IP_afamily != AF_USE_OLD) -+@@ -198,7 +195,7 @@ -+ -+ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, -+ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, -+- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, -++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, -+ int IP_afamily, -+ #ifdef BW_LIM -+ long bw_send, long bw_recv, -+@@ -212,7 +209,7 @@ -+ -+ locknodesem(); -+ pn = add_node_nolock(fa, hosts, pwd, pkt_pwd, out_pwd, obox_flvr, obox, ibox, -+- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, -++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, -+ IP_afamily, -+ #ifdef BW_LIM -+ bw_send, bw_recv, -+@@ -278,7 +275,6 @@ -+ on->ND_flag=np->ND_flag; -+ on->MD_flag=np->MD_flag; -+ on->NP_flag=np->NP_flag; -+- on->NC_flag=np->NC_flag; -+ on->HC_flag=np->HC_flag; -+ on->restrictIP=np->restrictIP; -+ on->pipe=np->pipe; -+@@ -294,7 +290,7 @@ -+ -+ add_node_nolock(fa, np->hosts, NULL, NULL, NULL, np->obox_flvr, np->obox, -+ np->ibox, np->NR_flag, np->ND_flag, np->MD_flag, np->restrictIP, -+- np->HC_flag, np->NP_flag, np->NC_flag, np->pipe, np->IP_afamily, -++ np->HC_flag, np->NP_flag, np->pipe, np->IP_afamily, -+ #ifdef BW_LIM -+ np->bw_send, np->bw_recv, -+ #endif -+@@ -403,7 +399,7 @@ -+ if (!get_node_info_nolock (&target, config)) -+ add_node_nolock (&target, "*", NULL, NULL, NULL, '-', NULL, NULL, -+ NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, -+- HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, -++ HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, -+ #ifdef BW_LIM -+ BW_DEF, BW_DEF, -+ #endif -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnnode.h binkd_pgul/ftnnode.h -+--- binkd/ftnnode.h 2026-04-25 20:40:18.652899844 +0100 -++++ binkd_pgul/ftnnode.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -36,7 +36,7 @@ -+ */ -+ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, -+ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, -+- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, -++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, -+ int IP_afamily, -+ #ifdef BW_LIM -+ long bw_send, long bw_recv, -+@@ -75,10 +75,6 @@ -+ #define NP_OFF 0 -+ #define NP_USE_OLD -1 /* Use old value */ -+ -+-#define NC_ON 1 -+-#define NC_OFF 0 -+-#define NC_USE_OLD -1 /* Use old value */ -+- -+ #define AF_USE_OLD -1 /* Use old value */ -+ -+ #ifdef BW_LIM -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnq.c binkd_pgul/ftnq.c -+--- binkd/ftnq.c 2026-04-26 10:34:47.939032694 +0100 -++++ binkd_pgul/ftnq.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -1147,8 +1147,7 @@ -+ if (*buf) -+ { -+ strnzcat (buf, ".try", sizeof (buf)); -+- /* Delete only if the file exists */ -+- if (stat(buf, &sb) == 0) -+- delete (buf); -++ if (stat(buf, &sb) == -1) return; -++ delete (buf); -+ } -+ } -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/https.c binkd_pgul/https.c -+--- binkd/https.c 2026-04-22 21:36:50.649712064 +0100 -++++ binkd_pgul/https.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -318,11 +318,7 @@ -+ buf[1]=1; -+ lockhostsem(); -+ Log (4, strcmp(port, config->oport) == 0 ? "trying %s..." : "trying %s:%u...", -+-#ifdef AMIGA -+- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr.s_addr), portnum); -+-#else -+- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); -+-#endif -++ inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); -+ releasehostsem(); -+ buf[2]=(unsigned char)((portnum>>8)&0xFF); -+ buf[3]=(unsigned char)(portnum&0xFF); -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/inbound.c binkd_pgul/inbound.c -+--- binkd/inbound.c 2026-04-26 09:25:07.283995051 +0100 -++++ binkd_pgul/inbound.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -49,9 +49,7 @@ -+ char node[FTN_ADDR_SZ + 1]; -+ -+ strnzcpy (s, inbound, MAXPATHLEN); -+- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+- if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) -+- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -+ t = s + strlen (s); -+ while (1) -+ { -+@@ -130,9 +128,7 @@ -+ } -+ -+ strnzcpy (s, inbound, MAXPATHLEN); -+- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+- if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) -+- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -+ t = s + strlen (s); -+ while ((de = readdir (dp)) != 0) -+ { -+@@ -474,9 +470,7 @@ -+ } -+ -+ strnzcpy (real_name, state->inbound, MAXPATHLEN); -+- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+- if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) -+- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -+ s = real_name + strlen (real_name); -+ strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); -+ free (u); -+@@ -547,9 +541,7 @@ -+ { -+ ren_style = RENAME_POSTFIX; -+ strnzcpy (real_name, state->inbound, MAXPATHLEN); -+- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+- if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) -+- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -+ s = real_name + strlen (real_name); -+ strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); -+ free (u); -+@@ -599,9 +591,7 @@ -+ struct stat sb; -+ -+ strnzcpy (fp, inbound, MAXPATHLEN); -+- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+- if (strlen(fp) > 0 && fp[strlen(fp) - 1] != PATH_SEPARATOR[0]) -+- strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); -++ strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); -+ s = fp + strlen (fp); -+ strnzcat (fp, u = strdequote (filename), MAXPATHLEN); -+ free (u); -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iphdr.h binkd_pgul/iphdr.h -+--- binkd/iphdr.h 2026-04-26 09:25:41.562664644 +0100 -++++ binkd_pgul/iphdr.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -124,17 +124,9 @@ -+ #define TCPERRNO errno -+ #define TCPERR_WOULDBLOCK EWOULDBLOCK -+ #define TCPERR_AGAIN EAGAIN -+- #ifdef AMIGA -+- /* AmigaOS 3: open bsdsocket.library via amiga/bsdsock.c */ -+- #include "amiga/bsdsock.h" -+- #define sock_init() amiga_sock_init() -+- #define sock_deinit() amiga_sock_cleanup() -+- #define soclose(h) CloseSocket(h) -+- #else -+- #define sock_init() 0 -+- #define sock_deinit() -+- #define soclose(h) close(h) -+- #endif -++ #define sock_init() 0 -++ #define sock_deinit() -++ #define soclose(h) close(h) -+ #endif -+ -+ #if !defined(WIN32) -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iptools.c binkd_pgul/iptools.c -+--- binkd/iptools.c 2026-04-26 10:33:19.517261538 +0100 -++++ binkd_pgul/iptools.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -20,10 +20,6 @@ -+ #include -+ #endif -+ -+-#ifdef AMIGA -+-#include -+-#endif -+- -+ #include "sys.h" -+ #include "Config.h" -+ #include "iphdr.h" -+@@ -44,11 +40,7 @@ -+ int arg; -+ -+ arg = 1; -+-#if defined(AMIGA) -+- if (ioctl (s, FIONBIO, (char *) &arg) < 0) -+-#else -+ if (ioctl (s, FIONBIO, (char *) &arg, sizeof arg) < 0) -+-#endif -+ Log (1, "ioctl (FIONBIO): %s", TCPERR ()); -+ -+ #elif defined(WIN32) -+@@ -61,49 +53,12 @@ -+ #endif -+ #endif -+ -+-#if defined(UNIX) || defined(EMX) /* NOT AMIGA: sockets are not AmigaDOS fds */ -++#if defined(UNIX) || defined(EMX) || defined(AMIGA) -+ if (fcntl (s, F_SETFL, O_NONBLOCK) == -1) -+ Log (1, "fcntl: %s", strerror (errno)); -+ #endif -+ } -+ -+-#if defined(AMIGA) -+-void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf) -+-{ -+- /* Disable Nagle algorithm: BinkP mixes small control messages with data -+- * Without TCP_NODELAY each small message waits up to 200ms (Nagle delay), -+- * making sessions 2-5x slower than other BinkP implementations -+- * All other BinkP mailers (BinkIT, Argus, etc.) set this explicitly */ -+- -+- if (tcpdelay) -+- { -+- int nodelay = tcpdelay; -+- -+- if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)) < 0) -+- Log (4, "setsockopt TCP_NODELAY: %s", TCPERR()); -+- } -+- -+- /* ixnet default TCP buffers are very small (~8KB). Increase them so the -+- * sender does not stall waiting for ACK after every small burst */ -+- -+- if (so_sndbuf) -+- { -+- int sndbuf = so_sndbuf; -+- -+- if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, sizeof(sndbuf)) < 0) -+- Log (5, "setsockopt SO_SNDBUF: %s", TCPERR()); -+- } -+- -+- if (so_rcvbuf) -+- { -+- int rcvbuf = so_rcvbuf; -+- -+- if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) < 0) -+- Log (5, "setsockopt SO_RCVBUF: %s", TCPERR()); -+- } -+-} -+-#endif -+- -+ /* -+ * Find the appropriate port string to be used. -+ * Find_port ("") will return binkp's port from /etc/services or even -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iptools.h binkd_pgul/iptools.h -+--- binkd/iptools.h 2026-04-26 09:26:42.811498024 +0100 -++++ binkd_pgul/iptools.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -11,21 +11,11 @@ -+ * (at your option) any later version. See COPYING. -+ */ -+ -+-#if defined(AMIGA) -+-#include -+-#include -+-#include -+-#endif -+- -+ /* -+ * Sets non-blocking mode for a given socket -+ */ -+ void setsockopts (SOCKET s); -+ -+-#if defined(AMIGA) -+-void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf); -+-#endif -+- -+ /* -+ * Find the port number (in the host byte order) by a port number string or -+ * a service name. Find_port ("") will return binkp's port from -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/decompress.c binkd_pgul/misc/decompress.c -+--- binkd/misc/decompress.c 2026-04-26 13:39:53.974576140 +0100 -++++ binkd_pgul/misc/decompress.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,188 +0,0 @@ -+-/* -+- * decompress.c -- Decompress FTN bundle archives from an inbound directory -+- * -+- * decompress.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include "portable.h" /* Canonical portable layer */ -+-#include -+- -+-#define MAX_CMD 1100 -+- -+-/* Archive format codes detected by magic bytes */ -+-#define FMT_UNKNOWN 0 -+-#define FMT_ZIP 1 -+-#define FMT_LZH 2 -+-#define FMT_ARC 3 -+- -+-/* detect_format -- Read first bytes and identify archive type */ -+-static int detect_format(const char *path) -+-{ -+- unsigned char buf[8]; -+- FILE *f = fopen(path, "rb"); -+- int n; -+- -+- if (!f) -+- return FMT_UNKNOWN; -+- -+- n = (int)fread(buf, 1, sizeof(buf), f); -+- -+- fclose(f); -+- -+- if (n < 2) -+- return FMT_UNKNOWN; -+- -+- /* ZIP: PK\x03\x04 */ -+- if (n >= 4 && buf[0] == 0x50 && buf[1] == 0x4B && buf[2] == 0x03 && buf[3] == 0x04) -+- return FMT_ZIP; -+- -+- /* LZH: offset 2 = '-', offset 3 = 'l', offset 6 = '-' (e.g. -lh5-) */ -+- if (n >= 7 && buf[2] == '-' && buf[3] == 'l' && buf[6] == '-') -+- return FMT_LZH; -+- -+- /* ARC: 0x1A followed by type byte 1..18 */ -+- if (buf[0] == 0x1A && buf[1] >= 1 && buf[1] <= 18) -+- return FMT_ARC; -+- -+- return FMT_UNKNOWN; -+-} -+- -+-/* is_ftn_bundle -- Check filename has an FTN day-of-week extension */ -+-static int is_ftn_bundle(const char *filename) -+-{ -+- const char *p; -+- -+- for (p = filename; *p; p++) -+- { -+- if (p[0] == '.' && p[1] && p[2]) -+- { -+- char a = (char)tolower((unsigned char)p[1]); -+- char b = (char)tolower((unsigned char)p[2]); -+- -+- if ((a == 's' && b == 'u') || (a == 'm' && b == 'o') || -+- (a == 't' && b == 'u') || (a == 'w' && b == 'e') || -+- (a == 't' && b == 'h') || (a == 'f' && b == 'r') || -+- (a == 's' && b == 'a')) -+- { -+- /* .TH .TH0 .TH.001 */ -+- if (p[3] == '\0' || isdigit((unsigned char)p[3]) || p[3] == '.') -+- return 1; -+- } -+- } -+- } -+- return 0; -+-} -+- -+-/* delete_file -- Remove a file, portable */ -+-static void delete_file(const char *path) -+-{ -+-#if defined(AMIGA) -+- DeleteFile((STRPTR)path); -+-#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -+- DeleteFileA(path); -+-#else -+- remove(path); -+-#endif -+-} -+- -+-/* run_decompressor -- Invoke external tool for the detected format -+- * outdir must end without trailing slash on POSIX; lha needs trailing / */ -+-static int run_decompressor(int fmt, const char *path, const char *outdir) -+-{ -+- char cmd[MAX_CMD]; -+- -+- switch (fmt) -+- { -+- case FMT_ZIP: -+-#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -+- snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); -+-#else -+- snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); -+-#endif -+- break; -+- -+- case FMT_LZH: -+-#ifdef AMIGA -+- snprintf(cmd, MAX_CMD, "lha x \"%s\" \"%s/\"", path, outdir); -+-#else -+- snprintf(cmd, MAX_CMD, "lha e \"%s\" \"%s/\"", path, outdir); -+-#endif -+- break; -+- -+- case FMT_ARC: -+-#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -+- snprintf(cmd, MAX_CMD, "arc x \"%s\" \"%s\"", path, outdir); -+-#else -+- snprintf(cmd, MAX_CMD, "cd \"%s\" && arc x \"%s\"", outdir, path); -+-#endif -+- break; -+- -+- default: -+- return -1; -+- } -+- -+- return system(cmd); -+-} -+- -+-int main(int argc, char *argv[]) -+-{ -+- DIR *dp; -+- struct dirent *entry; -+- char path[MAXPATHLEN]; -+- const char *inbound; -+- const char *outdir; -+- int total = 0; -+- int ok = 0; -+- -+- if (argc < 3) -+- { -+- fprintf(stderr, -+- "Usage: decompress \n" -+- "Detects format by magic bytes (ZIP/LZH/ARC).\n" -+- "Processes FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA).\n"); -+- -+- return 1; -+- } -+- -+- inbound = argv[1]; -+- outdir = argv[2]; -+- -+- dp = opendir(inbound); -+- -+- if (dp == NULL) -+- return 1; -+- -+- while ((entry = readdir(dp)) != NULL) -+- { -+- int fmt; -+- -+- /* Skip . and .. (AmigaOS readdir does not return these, POSIX does) */ -+- if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) -+- continue; -+- -+- if (!is_ftn_bundle(entry->d_name)) -+- continue; -+- -+- path_join(path, MAXPATHLEN, inbound, entry->d_name); -+- -+- fmt = detect_format(path); -+- -+- if (fmt == FMT_UNKNOWN) -+- continue; -+- -+- total++; -+- -+- if (run_decompressor(fmt, path, outdir) == 0) -+- { -+- delete_file(path); -+- ok++; -+- } -+- } -+- -+- closedir(dp); -+- -+- return (total == 0 || ok == total) ? 0 : 1; -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/decompress.txt binkd_pgul/misc/decompress.txt -+--- binkd/misc/decompress.txt 2026-04-26 13:44:08.749250093 +0100 -++++ binkd_pgul/misc/decompress.txt 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,36 +0,0 @@ -+-decompress -- Decompress FTN bundle archives -+- -+-USAGE: -+- decompress -+- -+-DESCRIPTION: -+- Scans the inbound directory for FTN day-of-week bundle archives and -+- decompresses them to the output directory. -+- -+- Recognized archive formats (by magic bytes, not extension): -+- - ZIP files -+- - LZH files -+- - ARC files -+- -+- Recognized bundle extensions (case-insensitive): -+- - .SU - Sunday bundle -+- - .MO - Monday bundle -+- - .TU - Tuesday bundle -+- - .WE - Wednesday bundle -+- - .TH - Thursday bundle -+- - .FR - Friday bundle -+- - .SA - Saturday bundle -+- -+- Also handles renamed bundles (duplicates): -+- - ABCD1234.SU -+- - ABCD1234.SU.001 -+- - ABCD1234.SU.002 -+- -+-EXAMPLES: -+- decompress Work:Inbound Work:Unpacked -+- decompress /var/spool/binkd/inbound /tmp/unpacked -+- decompress C:\Binkd\Inbound C:\Binkd\Unpacked -+- exec "work:fido/decompress work:fido/inbound work:fido/inbound" *.su? *.mo? *.tu? *.we? *.th? *.fr? *.sa? *.SU? *.MO? *.TU? *.WE? *.TH? *.FR? *.SA? -+- -+-CONFIGURATION FILE: -+- None. All parameters are command-line arguments. -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/freq.c binkd_pgul/misc/freq.c -+--- binkd/misc/freq.c 2026-04-26 13:39:53.974576140 +0100 -++++ binkd_pgul/misc/freq.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,220 +0,0 @@ -+-/* -+- * freq.c -- Append a file-request entry to an outbound .req / .clo pair -+- * -+- * freq.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include "portable.h" /* Canonical portable layer */ -+- -+-#define FREQ_MAX_PATH (MAXPATHLEN + 1) -+- -+-/* build_aso_paths -- ASO flat layout */ -+-static int build_aso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) -+-{ -+- char *dot; -+- -+- if (mkdir_recursive(outbound) < 0 && !path_exists(outbound)) -+- return -1; -+- -+- snprintf(req_path, (size_t)pathsize, "%s/%u.%u.%u.%u.req", outbound, zone, net, node, point); -+- safe_strncpy(clo_path, req_path, pathsize); -+- dot = strrchr(clo_path, '.'); -+- -+- if (dot) -+- strcpy(dot, ".clo"); -+- -+- return 0; -+-} -+- -+-/* build_bso_paths -- BSO BinkleyStyle layout (lowercase hex) */ -+-static int build_bso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) -+-{ -+- char zone_dir[FREQ_MAX_PATH]; -+- char node_dir[FREQ_MAX_PATH]; -+- -+- /* Zone dir: .0ZZ (lowercase hex) */ -+- snprintf(zone_dir, sizeof(zone_dir), "%s.%03x", outbound, zone); -+- str_tolower(zone_dir); -+- -+- if (mkdir_recursive(zone_dir) < 0 && !path_exists(zone_dir)) -+- return -1; -+- -+- if (point == 0) -+- { -+- snprintf(req_path, (size_t)pathsize, "%s/%04x%04x.req", zone_dir, net, node); -+- snprintf(clo_path, (size_t)pathsize, "%s/%04x%04x.clo", zone_dir, net, node); -+- } -+- else -+- { -+- snprintf(node_dir, sizeof(node_dir), "%s/%04x%04x.pnt", zone_dir, net, node); -+- -+- if (mkdir_recursive(node_dir) < 0 && !path_exists(node_dir)) -+- return -1; -+- -+- snprintf(req_path, (size_t)pathsize, "%s/%08x.req", node_dir, point); -+- snprintf(clo_path, (size_t)pathsize, "%s/%08x.clo", node_dir, point); -+- } -+- -+- return 0; -+-} -+- -+-int main(int argc, char *argv[]) -+-{ -+- unsigned int zone, net, node, point; -+- char addr_copy[128]; -+- char req_path[FREQ_MAX_PATH]; -+- char clo_path[FREQ_MAX_PATH]; -+- char abs_outbound[FREQ_MAX_PATH]; -+- FILE *f; -+- const char *outbound; -+- const char *arg_outbound; -+- const char *arg_addr; -+- const char *password = NULL; /* --password → !pass suffix */ -+- long newer_than = 0; /* --newer-than → +ts suffix */ -+- int update = 0; /* --update → U suffix */ -+- int use_bso = 0; -+- int argi = 1; -+- int nfiles = 0; -+- -+- zone = net = node = point = 0; -+- -+- /* Parse flags -- All optional, order-independent, before positional args */ -+- while (argi < argc && argv[argi][0] == '-' && argv[argi][1] == '-') -+- { -+- if (strcmp(argv[argi], "--bso") == 0) -+- { -+- use_bso = 1; -+- argi++; -+- } -+- else if (strcmp(argv[argi], "--aso") == 0) -+- { -+- use_bso = 0; -+- argi++; -+- } -+- else if (strcmp(argv[argi], "--update") == 0) -+- { -+- update = 1; -+- argi++; -+- } -+- else if (strcmp(argv[argi], "--password") == 0 && argi + 1 < argc) -+- { -+- password = argv[++argi]; -+- argi++; -+- } -+- else if (strcmp(argv[argi], "--newer-than") == 0 && argi + 1 < argc) -+- { -+- newer_than = atol(argv[++argi]); -+- argi++; -+- } -+- else -+- break; /* unknown flag — stop, treat rest as positional */ -+- } -+- -+- if (argc - argi < 3) -+- { -+- fprintf(stderr, -+- "Usage: freq [options] Z:N/NODE[.POINT] [...]\n" -+- "Options:\n" -+- " --aso flat layout (default): outbound/Z.N.NODE.POINT.req\n" -+- " --bso BSO layout: outbound.0ZZ/nnnnnnnn[.pnt/pppppppp].req\n" -+- " --password append !pw to each request line\n" -+- " --newer-than append + (request if newer)\n" -+- " --update append U flag (update request)\n" -+- "Multiple filenames can be listed after the address.\n"); -+- return 1; -+- } -+- -+- arg_outbound = argv[argi++]; -+- arg_addr = argv[argi++]; -+- -+- /* Remaining args are filenames */ -+- -+- make_abs_path(arg_outbound, abs_outbound, (int)sizeof(abs_outbound)); -+- outbound = abs_outbound; -+- -+- safe_strncpy(addr_copy, arg_addr, (int)sizeof(addr_copy)); -+- -+- if (sscanf(addr_copy, "%u:%u/%u.%u", &zone, &net, &node, &point) < 3 && sscanf(addr_copy, "%u:%u/%u", &zone, &net, &node) < 3) -+- { -+- fprintf(stderr, "freq: invalid address: %s\n", arg_addr); -+- return 1; -+- } -+- -+- if (use_bso) -+- { -+- if (build_bso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) -+- { -+- fprintf(stderr, "freq: cannot create BSO dirs under: %s\n", outbound); -+- return 1; -+- } -+- } -+- else -+- { -+- if (build_aso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) -+- { -+- fprintf(stderr, "freq: cannot create outbound dir: %s\n", outbound); -+- return 1; -+- } -+- } -+- -+- /* Append all filenames to .req */ -+- f = fopen(req_path, "a"); -+- -+- if (!f) -+- { -+- fprintf(stderr, "freq: cannot open REQ: %s\n", req_path); -+- return 1; -+- } -+- -+- for (; argi < argc; argi++) -+- { -+- const char *fname = argv[argi]; -+- -+- /* Build request line: filename [!password] [+timestamp] [U] */ -+- fprintf(f, "%s", fname); -+- -+- if (password && password[0]) -+- fprintf(f, " !%s", password); -+- -+- if (newer_than > 0) -+- fprintf(f, " +%ld", newer_than); -+- -+- if (update) -+- fprintf(f, " U"); -+- -+- fprintf(f, "\r\n"); -+- nfiles++; -+- } -+- -+- fclose(f); -+- -+- if (nfiles == 0) -+- { -+- fprintf(stderr, "freq: no filenames specified\n"); -+- return 1; -+- } -+- -+- /* Append .req full path to .clo (once per invocation) */ -+- f = fopen(clo_path, "a"); -+- -+- if (!f) -+- { -+- fprintf(stderr, "freq: cannot open CLO: %s\n", clo_path); -+- return 1; -+- } -+- -+- fprintf(f, "%s\r\n", req_path); -+- fclose(f); -+- -+- printf("freq (%s): node %u:%u/%u", use_bso ? "bso" : "aso", zone, net, node); -+- -+- if (point) -+- printf(".%u", point); -+- -+- printf(" %d file(s)\n REQ : %s\n CLO : %s\n", nfiles, req_path, clo_path); -+- -+- return 0; -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/freq.txt binkd_pgul/misc/freq.txt -+--- binkd/misc/freq.txt 2026-04-26 13:33:14.698749181 +0100 -++++ binkd_pgul/misc/freq.txt 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,37 +0,0 @@ -+-freq -- Create .req and .clo request files for FidoNet -+- -+-USAGE: -+- freq [options] [...] -+- -+-DESCRIPTION: -+- Creates file request (.req) and close (.clo) files in the outbound -+- directory using FidoNet 4D/5D addressing. -+- -+- Address format: -+- Zone:Net/Node - 4D address (e.g., 39:190/101) -+- Zone:Net/Node.Point - 5D address with point (e.g., 39:190/101.1) -+- -+- Multiple files can be specified to create multiple request lines. -+- -+-OPTIONS: -+- --aso Amiga Style Outbound (default) - flat layout -+- Format: /Z.N.NODE.POINT.req -+- -+- --bso Binkley Style Outbound - hex directory layout -+- No point: .0ZZ/nnnnnnnn.req -+- Point: .0ZZ/nnnnnnnn.pnt/pppppppp.req -+- -+- --password Append !pw suffix to each request line -+- --newer-than Append + (request only if file is newer) -+- --update Append U flag (update request, checks TRANX) -+- -+-EXAMPLES: -+- freq Work:Outbound 39:190/101 file.lha -+- freq --aso Work:Outbound 39:190/101.1 readme.txt -+- freq --bso /var/spool/binkd/outbound 2:123/456 door.zip -+- freq --bso C:\Binkd\Outbound 1:100/200 update.lzh -+- freq --password secret --newer-than 1234567890 Work:Outbound 39:190/101 file.zip -+- freq --update Work:Outbound 39:190/101 file1.zip file2.zip file3.zip -+- -+-CONFIGURATION FILE: -+- None. All parameters are command-line arguments. -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/nodelist.c binkd_pgul/misc/nodelist.c -+--- binkd/misc/nodelist.c 2026-04-26 13:39:53.974576140 +0100 -++++ binkd_pgul/misc/nodelist.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,200 +0,0 @@ -+-/* -+- * nodelist.c -- FidoNet nodelist compiler for binkd -+- * -+- * nodelist.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include "portable.h" /* Canonical portable layer */ -+-#include -+-#include -+-#include -+-#include -+- -+-#define MAX_FIELDS 20 -+-#define MAX_VAL 256 -+- -+-static int split_fields(char *buf, char **fields, int maxfields) -+-{ -+- int n = 0; -+- char *p = buf; -+- -+- while (n < maxfields) -+- { -+- fields[n++] = p; -+- p = strchr(p, ','); -+- -+- if (!p) -+- break; -+- -+- *p++ = '\0'; -+- } -+- -+- return n; -+-} -+- -+-/* Case-insensitive flag search. Returns 1 if found; fills val if present */ -+-static int find_flag(char **fields, int nfields, int start, const char *flag, char *val, int vsize) -+-{ -+- int flen = (int)strlen(flag); -+- int i; -+- -+- for (i = start; i < nfields; i++) -+- { -+- char *f = fields[i]; -+- int j; -+- -+- for (j = 0; j < flen; j++) -+- { -+- if (toupper((unsigned char)f[j]) != toupper((unsigned char)flag[j])) -+- break; -+- } -+- -+- if (j == flen) -+- { -+- if (val && vsize > 0) -+- { -+- if (f[flen] == ':') -+- { -+- strncpy(val, f + flen + 1, (size_t)(vsize - 1)); -+- val[vsize - 1] = '\0'; -+- } -+- else -+- val[0] = '\0'; -+- } -+- return 1; -+- } -+- } -+- return 0; -+-} -+- -+-int main(int argc, char *argv[]) -+-{ -+- const char *nl_file; -+- const char *domain; -+- FILE *in; -+- FILE *out; -+- char buf[MAX_LINE]; -+- char *fields[MAX_FIELDS]; -+- int nf; -+- int cur_zone; -+- int cur_net; -+- long count; -+- -+- cur_zone = 0; -+- cur_net = 0; -+- count = 0; -+- -+- if (argc < 3) -+- { -+- fprintf(stderr, -+- "Usage: nodelist []\n"); -+- return 1; -+- } -+- -+- nl_file = argv[1]; -+- domain = argv[2]; -+- out = (argc >= 4) ? fopen(argv[3], "w") : stdout; -+- -+- if (!out) -+- { -+- perror(argv[3]); -+- return 1; -+- } -+- -+- in = fopen(nl_file, "r"); -+- -+- if (!in) -+- { -+- perror(nl_file); -+- -+- if (out != stdout) -+- fclose(out); -+- -+- return 1; -+- } -+- -+- while (fgets(buf, sizeof(buf), in)) -+- { -+- char type[32]; -+- char ibn_port[32]; -+- char ina_host[MAX_VAL]; -+- int node_num; -+- int port; -+- int flags_start; -+- -+- str_trim(buf); -+- -+- if (!buf[0] || buf[0] == ';') -+- continue; -+- -+- nf = split_fields(buf, fields, MAX_FIELDS); -+- -+- if (nf < 2) -+- continue; -+- -+- if (fields[0][0] == '\0') -+- { -+- /* Line started with comma -- plain Node entry */ -+- strcpy(type, "Node"); -+- node_num = atoi(fields[1]); -+- flags_start = 7; -+- } -+- else -+- { -+- strncpy(type, fields[0], sizeof(type) - 1); -+- type[sizeof(type) - 1] = '\0'; -+- node_num = atoi(fields[1]); -+- flags_start = 7; -+- } -+- -+- /* Update zone / net context */ -+- if (!strcmp(type, "Zone") || !strcmp(type, "ZONE")) -+- { -+- cur_zone = node_num; -+- cur_net = node_num; -+- continue; -+- } -+- -+- if (!strcmp(type, "Region") || !strcmp(type, "REGION")) -+- { -+- cur_net = node_num; -+- continue; -+- } -+- -+- if (!strcmp(type, "Host") || !strcmp(type, "HOST")) -+- cur_net = node_num; -+- -+- /* Skip unusable types */ -+- if (!strcmp(type, "Pvt") || !strcmp(type, "PVT") || !strcmp(type, "Hold") || !strcmp(type, "HOLD") || !strcmp(type, "Down") || !strcmp(type, "DOWN") || !strcmp(type, "Boss") || !strcmp(type, "BOSS")) -+- continue; -+- -+- /* Must have IBN flag */ -+- if (!find_flag(fields, nf, flags_start, "IBN", ibn_port, (int)sizeof(ibn_port))) -+- continue; -+- -+- /* Need INA:hostname */ -+- ina_host[0] = '\0'; -+- find_flag(fields, nf, flags_start, "INA", ina_host, (int)sizeof(ina_host)); -+- -+- if (!ina_host[0]) -+- continue; -+- -+- port = (ibn_port[0] && atoi(ibn_port) > 0) ? atoi(ibn_port) : 24554; -+- -+- fprintf(out, "node %d:%d/%d@%s %s:%d -\n", cur_zone, cur_net, node_num, domain, ina_host, port); -+- -+- count++; -+- } -+- -+- fclose(in); -+- -+- if (out != stdout) -+- fclose(out); -+- -+- fprintf(stderr, "nodelist: %ld BinkP node(s) found\n", count); -+- -+- return 0; -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/nodelist.txt binkd_pgul/misc/nodelist.txt -+--- binkd/misc/nodelist.txt 2026-04-26 13:32:27.866048972 +0100 -++++ binkd_pgul/misc/nodelist.txt 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,32 +0,0 @@ -+-nodelist -- Compile FidoNet nodelist to binkd.conf format -+- -+-USAGE: -+- nodelist [] -+- -+-DESCRIPTION: -+- Reads a FidoNet nodelist and outputs binkd.conf compatible -+- "node" configuration lines. -+- -+- Arguments: -+- nodelist_file Path to the FidoNet nodelist file -+- domain Domain name for the node entries (e.g., fidonet) -+- output_file Optional output file (default: stdout) -+- -+- Extracts the following flags: -+- IBN[:port] - BinkP protocol flag (Internet BinkP Node) -+- INA:hostname - IP hostname/address -+- -+- Output format: -+- node
@ : - -+- -+- The nodelist format is comma-separated: -+- [type,]node_num,name,city,sysop,phone,baud,flag1,flag2,... -+- type = Zone, Region, Host, Hub, Pvt, Hold, Down, Boss (empty = Node) -+- -+-EXAMPLES: -+- nodelist Work:Fido/nodelist.123 fidonet > binkd-nodes.conf -+- nodelist /etc/fido/nodelist.456 fidonet >> binkd.conf -+- nodelist C:\Fido\NODELIST.001 fidonet C:\Fido\nodes.conf -+- -+-CONFIGURATION FILE: -+- None. All parameters are command-line arguments. -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/portable.c binkd_pgul/misc/portable.c -+--- binkd/misc/portable.c 2026-04-26 13:04:59.214902887 +0100 -++++ binkd_pgul/misc/portable.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,402 +0,0 @@ -+-/* -+- * portable.c -- Shared implementations for misc tools portable layer -+- * -+- * portable.c is a part of binkd project -+- * -+- * This file provides implementations for common utility functions -+- * used across binkd misc tools. Include portable.h for declarations -+- * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- * -+- */ -+- -+-#include "portable.h" -+-#include -+- -+-/* trim_nl -- Strip trailing newline (\n and \r) from string */ -+-void trim_nl(char *s) -+-{ -+- char *p = strchr(s, '\n'); -+- -+- if (p) -+- *p = '\0'; -+- -+- p = strchr(s, '\r'); -+- -+- if (p) -+- *p = '\0'; -+-} -+- -+-/* str_trim -- Strip trailing whitespace (space, \r, \n) from string */ -+-void str_trim(char *s) -+-{ -+- int n = (int)strlen(s); -+- -+- while (n > 0 && (s[n - 1] == '\r' || s[n - 1] == '\n' || s[n - 1] == ' ')) -+- s[--n] = '\0'; -+-} -+- -+-/* str_upper -- Convert string to uppercase in-place */ -+-void str_upper(char *s) -+-{ -+- while (*s) -+- { -+- *s = (char)toupper((unsigned char)*s); -+- s++; -+- } -+-} -+- -+-/* str_tolower -- Convert string to lowercase in-place */ -+-void str_tolower(char *s) -+-{ -+- for (; *s; s++) -+- { -+- if (*s >= 'A' && *s <= 'Z') -+- *s = (char)(*s + ('a' - 'A')); -+- } -+-} -+- -+-/* skip_ws -- Skip leading whitespace */ -+-char *skip_ws(char *s) -+-{ -+- while (*s == ' ' || *s == '\t') -+- s++; -+- -+- return s; -+-} -+- -+-/* wildmatch -- Portable wildcard match: case-insensitive, supports * and ? */ -+-int wildmatch(const char *pat, const char *str) -+-{ -+- while (*pat) -+- { -+- if (*pat == '*') -+- { -+- while (*pat == '*') -+- pat++; -+- -+- if (!*pat) -+- return 1; -+- -+- while (*str) -+- { -+- if (wildmatch(pat, str++)) -+- return 1; -+- } -+- -+- return 0; -+- } -+- -+- if (*pat == '?') -+- { -+- if (!*str) -+- return 0; -+- -+- pat++; -+- str++; -+- } -+- else -+- { -+- if (toupper((unsigned char)*pat) != toupper((unsigned char)*str)) -+- return 0; -+- -+- pat++; -+- str++; -+- } -+- } -+- -+- return (*str == '\0') ? 1 : 0; -+-} -+- -+-/* is_wildcard -- True if name contains * or ? */ -+-int is_wildcard(const char *s) -+-{ -+- while (*s) -+- { -+- if (*s == '*' || *s == '?') -+- return 1; -+- -+- s++; -+- } -+- -+- return 0; -+-} -+- -+-/* ensure_dir -- Ensure directory exists, creating if necessary */ -+-int ensure_dir(const char *path) -+-{ -+- if (path_exists(path)) -+- return 1; -+- -+- return (mkdir_recursive(path) == 0) ? 1 : 0; -+-} -+- -+-/* copy_file -- Portable binary file copy */ -+-int copy_file(const char *src, const char *dst) -+-{ -+- FILE *in, *out; -+- char buf[4096]; -+- int n; -+- -+- in = fopen(src, "rb"); -+- -+- if (!in) -+- return 0; -+- -+- out = fopen(dst, "wb"); -+- -+- if (!out) -+- { -+- fclose(in); -+- return 0; -+- } -+- -+- while ((n = (int)fread(buf, 1, sizeof(buf), in)) > 0) -+- fwrite(buf, 1, (size_t)n, out); -+- -+- fclose(out); -+- fclose(in); -+- -+- return 1; -+-} -+- -+-/* move_file -- Try rename first, fall back to copy+delete */ -+-int move_file(const char *src, const char *dst) -+-{ -+- remove(dst); -+- -+- if (rename(src, dst) == 0) -+- return 1; -+- -+- if (copy_file(src, dst)) -+- { -+- remove(src); -+- return 1; -+- } -+- -+- return 0; -+-} -+- -+-/* get_file_size -- Return file size in bytes, or -1 on error */ -+-long get_file_size(const char *path) -+-{ -+- struct stat st; -+- -+- if (stat(path, &st) == 0) -+- return (long)st.st_size; -+- -+- return -1; -+-} -+- -+-/* get_file_mtime -- Return Unix mtime of a file, or 0 on error */ -+-long get_file_mtime(const char *path) -+-{ -+- struct stat st; -+- -+- if (stat(path, &st) != 0) -+- return 0; -+- -+- return (long)st.st_mtime; -+-} -+- -+-/* port_path_exists -- Check if path exists (native per OS) */ -+- -+-int port_path_exists(const char *p) -+-{ -+-#ifdef AMIGA -+- BPTR l = Lock((STRPTR)p, ACCESS_READ); -+- -+- if (l) -+- { -+- UnLock(l); -+- return 1; -+- } -+- -+- return 0; -+-#else -+- struct stat st; -+- return (stat(p, &st) == 0) ? 1 : 0; -+-#endif -+-} -+- -+-/* port_mkdir_one -- Create single directory (native per OS) */ -+-int port_mkdir_one(const char *p) -+-{ -+-#ifdef AMIGA -+- BPTR l = CreateDir((STRPTR)p); -+- -+- if (l) -+- { -+- UnLock(l); -+- return 0; -+- } -+- -+- return -1; -+-#else -+- return mkdir(p, 0755); -+-#endif -+-} -+- -+-/* safe_localtime -- Thread-safe localtime, portable across all OS */ -+-void safe_localtime(const time_t *t, struct tm *tm) -+-{ -+-#if defined(AMIGA) || defined(DOS) -+- *tm = *localtime(t); -+-#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -+- localtime_s(tm, t); -+-#else -+- localtime_r(t, tm); -+-#endif -+-} -+- -+-/* mkdir_recursive -- Create full path, making all missing components */ -+-int mkdir_recursive(const char *path) -+-{ -+- char tmp[MP_MAXPATH]; -+- char *p; -+- int len; -+- -+- if (!path || !path[0]) -+- return -1; -+- -+- strncpy(tmp, path, MP_MAXPATH - 1); -+- tmp[MP_MAXPATH - 1] = '\0'; -+- -+- len = (int)strlen(tmp); -+- -+- /* Strip trailing slash */ -+- while (len > 1 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\')) -+- tmp[--len] = '\0'; -+- -+- /* Walk every '/' component and create missing dirs */ -+- for (p = tmp + 1; *p; p++) -+- { -+- if (*p == '/' || *p == '\\') -+- { -+- *p = '\0'; -+- -+- if (!path_exists(tmp)) -+- mkdir_one(tmp); /* ignore per-component errors */ -+- -+- *p = '/'; -+- } -+- } -+- -+- /* Create the leaf */ -+- if (!path_exists(tmp)) -+- return mkdir_one(tmp); -+- -+- return 0; -+-} -+- -+-/* safe_strncpy -- Ctrncpy that always NUL-terminates */ -+-void safe_strncpy(char *dst, const char *src, int dstsize) -+-{ -+- int len; -+- -+- if (dstsize <= 0) -+- return; -+- -+- len = (int)strlen(src); -+- -+- if (len > dstsize - 1) -+- len = dstsize - 1; -+- -+- memcpy(dst, src, (size_t)len); -+- dst[len] = '\0'; -+-} -+- -+-/* path_join -- Concatenate base path with sub path */ -+-void path_join(char *out, int outsize, const char *base, const char *sub) -+-{ -+- int blen; -+- char last; -+- -+- safe_strncpy(out, base, outsize); -+- blen = (int)strlen(out); -+- last = (blen > 0) ? out[blen - 1] : '\0'; -+- -+- if (last != '/' && last != ':' && last != '\\') -+- { -+- if (outsize - 1 - blen > 0) -+- { -+- out[blen] = '/'; -+- out[blen + 1] = '\0'; -+- blen++; -+- } -+- } -+- -+- safe_strncpy(out + blen, sub, outsize - blen); -+-} -+- -+-/* make_abs_path -- Resolve a possibly-relative path to absolute -+- * Covers AmigaOS, Win32, OS/2, DOS and Unix -+- * Returns 1 on success, 0 on failure (src copied verbatim as fallback) -+- */ -+-int make_abs_path(const char *src, char *dst, int dstlen) -+-{ -+-#ifdef AMIGA -+- BPTR lock = Lock((STRPTR)src, SHARED_LOCK); -+- -+- if (!lock) -+- { -+- safe_strncpy(dst, src, dstlen); -+- return 0; -+- } -+- -+- if (!NameFromLock(lock, (STRPTR)dst, dstlen)) -+- { -+- UnLock(lock); -+- safe_strncpy(dst, src, dstlen); -+- return 0; -+- } -+- -+- UnLock(lock); -+- -+- return 1; -+-#elif defined(WIN32) || defined(__MINGW32__) || defined(__WATCOMC__) || defined(VISUALCPP) || defined(OS2) -+- if (_fullpath(dst, src, (size_t)dstlen) != NULL) -+- return 1; -+- -+- safe_strncpy(dst, src, dstlen); -+- return 0; -+-#elif defined(DOS) -+- if (src[0] != '\\' && src[1] != ':') -+- { -+- char cwd[MAXPATHLEN + 1]; -+- -+- if (getcwd(cwd, sizeof(cwd)) != NULL) -+- { -+- snprintf(dst, dstlen, "%s\\%s", cwd, src); -+- return 1; -+- } -+- } -+- -+- safe_strncpy(dst, src, dstlen); -+- return 0; -+-#else -+- char buf[MAXPATHLEN + 1]; -+- -+- if (realpath(src, buf) != NULL) -+- { -+- safe_strncpy(dst, buf, dstlen); -+- return 1; -+- } -+- -+- if (src[0] != '/') -+- { -+- char cwd[MAXPATHLEN + 1]; -+- -+- if (getcwd(cwd, sizeof(cwd)) != NULL) -+- { -+- snprintf(dst, dstlen, "%s/%s", cwd, src); -+- return 1; -+- } -+- } -+- -+- safe_strncpy(dst, src, dstlen); -+- return 0; -+-#endif -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/portable.h binkd_pgul/misc/portable.h -+--- binkd/misc/portable.h 2026-04-26 14:19:41.472724309 +0100 -++++ binkd_pgul/misc/portable.h 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,135 +0,0 @@ -+-/* -+- * portable.h -- Portability layer for standalone binkd misc tools -+- * -+- * portable.h is a part of binkd project -+- * -+- * This is the single canonical portable.h; all misc utilities include this -+- * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- * -+- */ -+- -+-#ifndef BINKD_PORTABLE_H -+-#define BINKD_PORTABLE_H -+- -+-/* _POSIX_C_SOURCE for opendir/readdir/localtime_r under -std=c89 -+- * _XOPEN_SOURCE 500 additionally exposes realpath() on glibc */ -+-#ifndef AMIGA -+-#ifndef _POSIX_C_SOURCE -+-#define _POSIX_C_SOURCE 200112L -+-#endif -+-#ifndef _XOPEN_SOURCE -+-#define _XOPEN_SOURCE 500 -+-#endif -+-#endif -+- -+-#include -+-#include -+-#include -+-#include -+-#include -+- -+-#ifdef AMIGA -+- -+-#include -+-#include -+-#include -+-#include -+-#include -+-#include /* stat() / struct stat via libnix/ADE */ -+-#include -+-#include "amiga/dirent.h" /* opendir / readdir / closedir */ -+- -+-/* snprintf/vsnprintf: ADE/libnix declares them in stdio.h (already included -+- * above via ). The implementation is provided by snprintf.c which -+- * must be linked when building the misc tools. No redeclaration needed */ -+- -+-#elif defined(VISUALCPP) -+-#include -+-#include -+-#include -+-#include "nt/dirwin32.h" /* opendir/readdir/closedir for MSVC */ -+-#elif defined(__MINGW32__) || defined(WIN32) -+-#include -+-#include /* MinGW provides dirent.h natively */ -+-#include -+-#include -+-#elif defined(OS2) && (defined(IBMC) || defined(__WATCOMC__)) -+-#include -+-#include -+-#include -+-#include "os2/dirent.h" /* opendir/readdir/closedir for OS/2 ICC/WC */ -+-#elif defined(OS2) -+-#include -+-#include /* EMX provides dirent.h natively */ -+-#include -+-#include -+-#include -+-#elif defined(DOS) -+-#include -+-#include -+-#include "dos/dirent.h" /* opendir/readdir/closedir for DOS/DJGPP */ -+-#else /* POSIX / *nix */ -+-#include -+-#include -+-#include -+-#include -+-#include -+-#endif -+- -+-#ifndef MAXPATHLEN -+-#if defined(_MAX_PATH) -+-#define MAXPATHLEN _MAX_PATH -+-#elif defined(PATH_MAX) -+-#define MAXPATHLEN PATH_MAX -+-#else -+-#define MAXPATHLEN 1024 -+-#endif -+-#endif -+- -+-/* Generic line buffer size for config files and text processing */ -+-#ifndef MAX_LINE -+-#define MAX_LINE 1024 -+-#endif -+- -+-/* path_exists / mkdir_one -- native implementations per OS */ -+-int port_path_exists(const char *p); -+-int port_mkdir_one(const char *p); -+-#define path_exists(p) port_path_exists(p) -+-#define mkdir_one(p) port_mkdir_one(p) -+- -+-/* safe_localtime -- thread-safe localtime, portable across all OS */ -+-void safe_localtime(const time_t *t, struct tm *tm); -+- -+-/* mkdir_recursive -- create full path, making all missing components */ -+-#define MP_MAXPATH 512 -+-int mkdir_recursive(const char *path); -+- -+-/* safe_strncpy -- strncpy that always NUL-terminates */ -+-void safe_strncpy(char *dst, const char *src, int dstsize); -+- -+-/* String utilities */ -+-void trim_nl(char *s); -+-void str_trim(char *s); -+-void str_upper(char *s); -+-void str_tolower(char *s); -+-char *skip_ws(char *s); -+- -+-/* Wildcard matching */ -+-int wildmatch(const char *pat, const char *str); -+-int is_wildcard(const char *s); -+- -+-/* File operations */ -+-int ensure_dir(const char *path); -+-int copy_file(const char *src, const char *dst); -+-int move_file(const char *src, const char *dst); -+-long get_file_size(const char *path); -+-long get_file_mtime(const char *path); -+- -+-/* Path utilities */ -+-void path_join(char *out, int outsize, const char *base, const char *sub); -+-int make_abs_path(const char *src, char *dst, int dstlen); -+- -+-#endif /* BINKD_PORTABLE_H */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/process_tic.c binkd_pgul/misc/process_tic.c -+--- binkd/misc/process_tic.c 2026-04-26 13:39:53.974576140 +0100 -++++ binkd_pgul/misc/process_tic.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,552 +0,0 @@ -+-/* -+- * process_tic -- Process FTN .tic files from inbound to filebox -+- * -+- * process_tic.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include "portable.h" /* Canonical portable layer */ -+-#include -+- -+-static int my_toupper(int c) -+-{ -+- if (c >= 'a' && c <= 'z') -+- return c - 'a' + 'A'; -+- -+- return c; -+-} -+- -+-static int my_strnicmp(const char *a, const char *b, int n) -+-{ -+- int i, ca, cb; -+- for (i = 0; i < n; i++) -+- { -+- ca = my_toupper((unsigned char)a[i]); -+- cb = my_toupper((unsigned char)b[i]); -+- -+- if (ca != cb) -+- return ca - cb; -+- -+- if (ca == 0) -+- return 0; -+- } -+- -+- return 0; -+-} -+- -+-static int parse_file_field(char *line, char *out, int outsize) -+-{ -+- char *p; -+- char *end; -+- int len; -+- -+- p = skip_ws(line); -+- -+- if (my_strnicmp(p, "File", 4) != 0) -+- return 0; -+- -+- p += 4; -+- -+- if (*p != ' ' && *p != '\t') -+- return 0; -+- -+- p = skip_ws(p); -+- trim_nl(p); -+- end = p; -+- -+- while (*end && *end != ' ' && *end != '\t') -+- end++; -+- -+- *end = '\0'; -+- -+- len = (int)strlen(p); -+- -+- if (len <= 0 || len >= outsize) -+- return 0; -+- -+- strncpy(out, p, outsize - 1); -+- out[outsize - 1] = '\0'; -+- -+- return 1; -+-} -+- -+-static int parse_area_field(char *line, char *out, int outsize) -+-{ -+- char *p; -+- char *end; -+- int len; -+- -+- p = skip_ws(line); -+- -+- if (my_strnicmp(p, "Area", 4) != 0) -+- return 0; -+- -+- p += 4; -+- -+- if (*p != ' ' && *p != '\t') -+- return 0; -+- -+- p = skip_ws(p); -+- trim_nl(p); -+- end = p; -+- -+- while (*end && *end != ' ' && *end != '\t') -+- end++; -+- -+- *end = '\0'; -+- len = (int)strlen(p); -+- -+- if (len <= 0 || len >= outsize) -+- return 0; -+- -+- strncpy(out, p, outsize - 1); -+- out[outsize - 1] = '\0'; -+- -+- return 1; -+-} -+- -+-static int parse_origin_field(char *line, char *out, int outsize) -+-{ -+- char *p; -+- char *end; -+- int len; -+- -+- p = skip_ws(line); -+- -+- if (my_strnicmp(p, "Origin", 6) != 0) -+- return 0; -+- -+- p += 6; -+- -+- if (*p != ' ' && *p != '\t') -+- return 0; -+- -+- p = skip_ws(p); -+- trim_nl(p); -+- end = p; -+- -+- while (*end && *end != ' ' && *end != '\t') -+- end++; -+- -+- *end = '\0'; -+- len = (int)strlen(p); -+- -+- if (len <= 0 || len >= outsize) -+- return 0; -+- -+- strncpy(out, p, outsize - 1); -+- out[outsize - 1] = '\0'; -+- -+- return 1; -+-} -+- -+-static int parse_from_field(char *line, char *out, int outsize) -+-{ -+- char *p; -+- char *end; -+- int len; -+- -+- p = skip_ws(line); -+- -+- if (my_strnicmp(p, "From", 4) != 0) -+- return 0; -+- -+- p += 4; -+- -+- if (*p != ' ' && *p != '\t') -+- return 0; -+- -+- p = skip_ws(p); -+- trim_nl(p); -+- end = p; -+- -+- while (*end && *end != ' ' && *end != '\t') -+- end++; -+- -+- *end = '\0'; -+- len = (int)strlen(p); -+- -+- if (len <= 0 || len >= outsize) -+- return 0; -+- -+- strncpy(out, p, outsize - 1); -+- out[outsize - 1] = '\0'; -+- -+- return 1; -+-} -+- -+-static void append_filelist(const char *listpath, const char *file_name, long filesize, const char *dst_path) -+-{ -+- FILE *f; -+- time_t t; -+- struct tm tm; -+- char timestamp[32]; -+- -+- if (!listpath || !listpath[0]) -+- return; -+- -+- f = fopen(listpath, "a"); -+- if (!f) -+- return; -+- -+- t = time(NULL); -+- safe_localtime(&t, &tm); -+- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -+- -+- fprintf(f, "%s\t%s\t%ld\t%s\n", timestamp, file_name, filesize, dst_path); -+- fclose(f); -+-} -+- -+-static void append_newfiles(const char *newprefix, const char *file_name, long filesize, const char *dst_path) -+-{ -+- FILE *f; -+- char newpath[MAXPATHLEN]; -+- time_t t; -+- struct tm tm; -+- char datebuf[16]; -+- -+- if (!newprefix || !newprefix[0]) -+- return; -+- -+- t = time(NULL); -+- safe_localtime(&t, &tm); -+- strftime(datebuf, sizeof(datebuf), "%Y%m%d", &tm); -+- -+- ensure_dir(newprefix); -+- path_join(newpath, (int)sizeof(newpath), newprefix, "newfiles-"); -+- strncat(newpath, datebuf, sizeof(newpath) - strlen(newpath) - 1); -+- strncat(newpath, ".txt", sizeof(newpath) - strlen(newpath) - 1); -+- -+- f = fopen(newpath, "a"); -+- -+- if (!f) -+- return; -+- -+- fprintf(f, "%s\t%ld\t%s\n", file_name, filesize, dst_path); -+- -+- fclose(f); -+-} -+- -+-static void write_ticlog(const char *ticlog, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) -+-{ -+- FILE *f; -+- time_t t; -+- struct tm tm; -+- char timestamp[64]; -+- -+- if (!ticlog || !ticlog[0]) -+- return; -+- -+- f = fopen(ticlog, "a"); -+- if (!f) -+- return; -+- -+- t = time(NULL); -+- safe_localtime(&t, &tm); -+- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -+- -+- fprintf(f, "[%s] File: %s\n", timestamp, file_name); -+- fprintf(f, " Area: %s\n", area_name); -+- -+- if (origin_name[0]) -+- fprintf(f, " Origin: %s\n", origin_name); -+- -+- if (from_name[0]) -+- fprintf(f, " From: %s\n", from_name); -+- -+- fprintf(f, " Src: %s\n", src_path); -+- fprintf(f, " To: %s\n", dst_path); -+- fprintf(f, "\n"); -+- -+- fclose(f); -+-} -+- -+-static void write_log(const char *logfile, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) -+-{ -+- FILE *f; -+- time_t t; -+- struct tm tm; -+- char timestamp[64]; -+- -+- if (!logfile || !logfile[0]) -+- return; -+- -+- f = fopen(logfile, "a"); -+- if (!f) -+- return; -+- -+- t = time(NULL); -+- safe_localtime(&t, &tm); -+- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -+- -+- fprintf(f, "[%s] File: %s\n", timestamp, file_name); -+- fprintf(f, " Area: %s\n", area_name); -+- -+- if (origin_name[0]) -+- fprintf(f, " Origin: %s\n", origin_name); -+- -+- if (from_name[0]) -+- fprintf(f, " From: %s\n", from_name); -+- -+- fprintf(f, " Src: %s\n", src_path); -+- fprintf(f, " To: %s\n", dst_path); -+- fprintf(f, "\n"); -+- fclose(f); -+-} -+- -+-static void process_one_tic(const char *ticpath, const char *inbound, const char *filebox, int copypublic, const char *pubdir, const char *logfile, const char *filelist, const char *newfiles, const char *ticlog) -+-{ -+- FILE *f; -+- char line[MAX_LINE]; -+- char file_name[MAXPATHLEN]; -+- char area_name[MAXPATHLEN]; -+- char origin_name[MAXPATHLEN]; -+- char from_name[MAXPATHLEN]; -+- char src_path[MAXPATHLEN]; -+- char area_dir[MAXPATHLEN]; -+- char dst_path[MAXPATHLEN]; -+- -+- file_name[0] = '\0'; -+- area_name[0] = '\0'; -+- origin_name[0] = '\0'; -+- from_name[0] = '\0'; -+- long fsize = 0; -+- -+- f = fopen(ticpath, "r"); -+- -+- if (!f) -+- return; -+- -+- while (fgets(line, sizeof(line), f)) -+- { -+- if (!file_name[0]) -+- parse_file_field(line, file_name, sizeof(file_name)); -+- -+- if (!area_name[0]) -+- parse_area_field(line, area_name, sizeof(area_name)); -+- -+- if (!origin_name[0]) -+- parse_origin_field(line, origin_name, sizeof(origin_name)); -+- -+- if (!from_name[0]) -+- parse_from_field(line, from_name, sizeof(from_name)); -+- } -+- -+- fclose(f); -+- -+- if (!file_name[0] || !area_name[0]) -+- return; -+- -+- path_join(src_path, sizeof(src_path), inbound, file_name); -+- path_join(area_dir, sizeof(area_dir), filebox, area_name); -+- path_join(dst_path, sizeof(dst_path), area_dir, file_name); -+- -+- if (!path_exists(src_path)) -+- return; -+- -+- if (!ensure_dir(filebox) || !ensure_dir(area_dir)) -+- return; -+- -+- if (copypublic && pubdir && pubdir[0]) -+- { -+- char pub_dst[MAXPATHLEN]; -+- -+- path_join(pub_dst, sizeof(pub_dst), pubdir, file_name); -+- -+- if (ensure_dir(pubdir)) -+- copy_file(src_path, pub_dst); -+- } -+- -+- fsize = get_file_size(src_path); -+- -+- if (!move_file(src_path, dst_path)) -+- return; -+- -+- write_log(logfile, file_name, area_name, origin_name, from_name, src_path, dst_path); -+- write_ticlog(ticlog, file_name, area_name, origin_name, from_name, src_path, dst_path); -+- append_filelist(filelist, file_name, fsize, dst_path); -+- append_newfiles(newfiles, file_name, fsize, dst_path); -+- -+- remove(ticpath); -+-} -+- -+-static int is_tic_file(const char *name) -+-{ -+- int len = (int)strlen(name); -+- -+- if (len < 5) -+- return 0; -+- -+- return (my_strnicmp(name + len - 4, ".tic", 4) == 0); -+-} -+- -+-/* Config structure */ -+-static struct -+-{ -+- char inbound[MAXPATHLEN]; -+- char filebox[MAXPATHLEN]; -+- char pubdir[MAXPATHLEN]; -+- char logfile[MAXPATHLEN]; -+- char filelist[MAXPATHLEN]; -+- char newfiles[MAXPATHLEN]; -+- char ticlog[MAXPATHLEN]; -+- int copypublic; -+-} cfg; -+- -+-/* Parse configuration file */ -+-static int parse_config(const char *conffile) -+-{ -+- FILE *f; -+- char line[MAX_LINE]; -+- char *key, *value; -+- -+- memset(&cfg, 0, sizeof(cfg)); -+- -+- f = fopen(conffile, "r"); -+- -+- if (!f) -+- { -+- fprintf(stderr, "process_tic: cannot open config file: %s\n", conffile); -+- return 0; -+- } -+- -+- while (fgets(line, sizeof(line), f)) -+- { -+- trim_nl(line); -+- key = skip_ws(line); -+- -+- /* Skip comments and empty lines */ -+- if (*key == '#' || *key == '\0') -+- continue; -+- -+- /* Find value after key */ -+- value = key; -+- -+- while (*value && *value != ' ' && *value != '\t') -+- value++; -+- -+- if (*value) -+- { -+- *value = '\0'; -+- value = skip_ws(value + 1); -+- } -+- -+- /* Parse key-value pairs */ -+- if (strcmp(key, "inbound") == 0) -+- safe_strncpy(cfg.inbound, value, (int)sizeof(cfg.inbound)); -+- else if (strcmp(key, "filebox") == 0) -+- safe_strncpy(cfg.filebox, value, (int)sizeof(cfg.filebox)); -+- else if (strcmp(key, "pubdir") == 0) -+- { -+- safe_strncpy(cfg.pubdir, value, (int)sizeof(cfg.pubdir)); -+- cfg.copypublic = 1; -+- } -+- else if (strcmp(key, "logfile") == 0) -+- safe_strncpy(cfg.logfile, value, (int)sizeof(cfg.logfile)); -+- else if (strcmp(key, "filelist") == 0) -+- safe_strncpy(cfg.filelist, value, (int)sizeof(cfg.filelist)); -+- else if (strcmp(key, "newfiles") == 0) -+- safe_strncpy(cfg.newfiles, value, (int)sizeof(cfg.newfiles)); -+- else if (strcmp(key, "ticlog") == 0) -+- safe_strncpy(cfg.ticlog, value, (int)sizeof(cfg.ticlog)); -+- } -+- -+- fclose(f); -+- -+- /* Validate required fields */ -+- if (!cfg.inbound[0] || !cfg.filebox[0]) -+- { -+- fprintf(stderr, "process_tic: config file missing required 'inbound' or 'filebox'\n"); -+- return 0; -+- } -+- -+- return 1; -+-} -+- -+-int main(int argc, char *argv[]) -+-{ -+- char inbound[MAXPATHLEN]; -+- char filebox[MAXPATHLEN]; -+- char ticpath[MAXPATHLEN]; -+- char pubdir[MAXPATHLEN]; -+- char logfile[MAXPATHLEN]; -+- char filelist[MAXPATHLEN]; -+- char newfiles[MAXPATHLEN]; -+- char ticlog[MAXPATHLEN]; -+- int copypublic = 0; -+- DIR *dp; -+- struct dirent *de; -+- int found; -+- int i; -+- int use_config = 0; -+- -+- inbound[0] = '\0'; -+- filebox[0] = '\0'; -+- pubdir[0] = '\0'; -+- logfile[0] = '\0'; -+- filelist[0] = '\0'; -+- newfiles[0] = '\0'; -+- ticlog[0] = '\0'; -+- -+- /* Check for --conf option */ -+- for (i = 1; i < argc; i++) -+- { -+- if (strcmp(argv[i], "--conf") == 0 && i + 1 < argc) -+- { -+- if (!parse_config(argv[i + 1])) -+- return 1; -+- -+- use_config = 1; -+- i++; /* Skip config file path */ -+- -+- break; -+- } -+- } -+- -+- if (use_config) -+- { -+- /* Use config file values */ -+- safe_strncpy(inbound, cfg.inbound, (int)sizeof(inbound)); -+- safe_strncpy(filebox, cfg.filebox, (int)sizeof(filebox)); -+- safe_strncpy(pubdir, cfg.pubdir, (int)sizeof(pubdir)); -+- safe_strncpy(logfile, cfg.logfile, (int)sizeof(logfile)); -+- safe_strncpy(filelist, cfg.filelist, (int)sizeof(filelist)); -+- safe_strncpy(newfiles, cfg.newfiles, (int)sizeof(newfiles)); -+- safe_strncpy(ticlog, cfg.ticlog, (int)sizeof(ticlog)); -+- copypublic = cfg.copypublic; -+- } -+- -+- if (!inbound[0] || !filebox[0]) -+- { -+- fprintf(stderr, -+- "Usage: process_tic --conf [*.tic]\n"); -+- -+- return 1; -+- } -+- -+- dp = opendir(inbound); -+- -+- if (!dp) -+- return 1; -+- -+- found = 0; -+- -+- while ((de = readdir(dp)) != NULL) -+- { -+- /* Skip . and .. */ -+- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) -+- continue; -+- -+- if (!is_tic_file(de->d_name)) -+- continue; -+- -+- path_join(ticpath, sizeof(ticpath), inbound, de->d_name); -+- process_one_tic(ticpath, inbound, filebox, copypublic, pubdir, logfile, filelist, newfiles, ticlog); -+- found++; -+- } -+- -+- closedir(dp); -+- return 0; -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/process_tic.txt binkd_pgul/misc/process_tic.txt -+--- binkd/misc/process_tic.txt 2026-04-26 13:43:48.542112490 +0100 -++++ binkd_pgul/misc/process_tic.txt 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,45 +0,0 @@ -+-process_tic -- Process .tic file announcements -+- -+-USAGE: -+- process_tic --conf [files.tic...] -+- -+-DESCRIPTION: -+- Processes .tic (Ticker announcement) files from the inbound directory -+- and moves/copies files to their destination filebox or public directory. -+- -+- The .tic file is parsed for File, Area, Origin, and From fields. -+- The actual file is moved from inbound to filebox/AreaName/. -+- -+- All settings are read from the configuration file. -+- -+-OPTIONS: -+- --conf Configuration file (required) -+- -+-CONFIGURATION FILE FORMAT: -+- # Lines starting with # are comments -+- # Blank lines are ignored -+- -+- inbound Inbound directory (required) -+- filebox Filebox destination (required) -+- pubdir Public directory for --copy-public -+- logfile Log file path -+- ticlog TIC processing log -+- filelist File list output -+- newfiles New files list output -+- -+-EXAMPLE CONFIG FILE (process_tic.conf): -+- # process_tic.conf - Configuration for TIC processor -+- -+- inbound Work:Inbound -+- filebox Work:Filebox -+- pubdir Work:Public -+- logfile Work:Logs/process_tic.log -+- ticlog Work:Logs/tic.log -+- filelist Work:Filebox/filelist.txt -+- newfiles Work:Filebox/newfiles.txt -+- -+-EXAMPLES: -+- process_tic --conf process_tic.conf -+- process_tic --conf process_tic.conf inbound/*.tic -+- -+- exec "process_tic --conf work:fido/process_tic.conf" *.tic *.TIC -+\ No hay ningún carácter de nueva línea al final del archivo -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/srifreq.c binkd_pgul/misc/srifreq.c -+--- binkd/misc/srifreq.c 2026-04-26 15:01:11.006850708 +0100 -++++ binkd_pgul/misc/srifreq.c 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,1024 +0,0 @@ -+-/* -+- * srifreq.c -- SRIF-compatible file-request server for binkd -+- * -+- * srifreq.c is a part of binkd project -+- * -+- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+- * Licensed under the GNU GPL v2 or later -+- */ -+- -+-#include "portable.h" /* Canonical portable layer */ -+-#include -+-#include -+- -+-/* Private directory entry (dynamically allocated list) */ -+-typedef struct PrivDir -+-{ -+- char path[MAXPATHLEN]; -+- char password[64]; -+- struct PrivDir *next; -+-} PrivDir; -+- -+-/* Node tracking entry for rate limiting */ -+-typedef struct NodeTrack -+-{ -+- char aka[256]; /* Node address (4D/5D) */ -+- int files; /* Files downloaded in window */ -+- long bytes; /* Bytes downloaded in window */ -+- time_t last_time; /* Timestamp of last download */ -+- struct NodeTrack *next; -+-} NodeTrack; -+- -+-/* Global configuration (filled from --conf file) */ -+-typedef struct -+-{ -+- char pubdir[MAXPATHLEN]; -+- char logfile[MAXPATHLEN]; -+- char aliases[MAXPATHLEN]; -+- char trackfile[MAXPATHLEN]; /* Path to tracking file */ -+- int maxfiles; /* Max files per node per window (0=unlimited) */ -+- long maxbytes; /* Max bytes per node per window (0=unlimited) */ -+- long timewindow; /* Time window in seconds (0=no window) */ -+- PrivDir *privdirs; /* Linked list, NULL if none */ -+- NodeTrack *tracking; /* Linked list of tracked nodes */ -+-} Config; -+- -+-/* Alias table -- Loaded from file at startup */ -+-typedef struct -+-{ -+- char name[64]; -+- char path[MAXPATHLEN]; -+-} Alias; -+- -+-/* SRIF parsing */ -+-typedef struct -+-{ -+- char sysop[128]; -+- char aka[256]; -+- char request_list[MAXPATHLEN]; -+- char response_list[MAXPATHLEN]; -+- char our_aka[128]; -+- char caller_id[64]; /* CallerID: IP or phone of remote */ -+- char password[64]; /* Password: session password */ -+- int time_limit; /* Time: minutes left, -1 = unlimited */ -+- long tranx; /* TRANX: remote local time as Unix ts (hex in SRIF) */ -+- int protected_sess; /* RemoteStatus: 1=PROTECTED, 0=UNPROTECTED */ -+- int listed; /* SystemStatus: 1=LISTED, 0=UNLISTED */ -+- int got_request_list; -+- int got_response_list; -+-} SRIF; -+- -+-static Alias *g_aliases = NULL; -+-static int g_nalias = 0; -+-static int g_alias_cap = 0; -+-static Config g_conf; -+- -+-static void config_init(void) -+-{ -+- memset(&g_conf, 0, sizeof(g_conf)); -+- g_conf.privdirs = NULL; -+- g_conf.tracking = NULL; -+- g_conf.maxfiles = 0; /* 0 = unlimited */ -+- g_conf.maxbytes = 0; /* 0 = unlimited */ -+- g_conf.timewindow = 0; /* 0 = no window */ -+-} -+- -+-static void config_add_private(const char *path, const char *password) -+-{ -+- PrivDir *pd = (PrivDir *)malloc(sizeof(PrivDir)); -+- PrivDir *tail; -+- -+- if (!pd) -+- return; -+- -+- safe_strncpy(pd->path, path, (int)sizeof(pd->path)); -+- safe_strncpy(pd->password, password, (int)sizeof(pd->password)); -+- -+- pd->next = NULL; -+- -+- /* Append to tail */ -+- if (!g_conf.privdirs) -+- g_conf.privdirs = pd; -+- else -+- { -+- tail = g_conf.privdirs; -+- -+- while (tail->next) -+- tail = tail->next; -+- -+- tail->next = pd; -+- } -+-} -+- -+-static void config_free(void) -+-{ -+- PrivDir *pd = g_conf.privdirs; -+- NodeTrack *nt = g_conf.tracking; -+- -+- while (pd) -+- { -+- PrivDir *next = pd->next; -+- free(pd); -+- pd = next; -+- } -+- -+- g_conf.privdirs = NULL; -+- -+- while (nt) -+- { -+- NodeTrack *next = nt->next; -+- free(nt); -+- nt = next; -+- } -+- -+- g_conf.tracking = NULL; -+- -+- if (g_aliases) -+- { -+- free(g_aliases); -+- g_aliases = NULL; -+- g_nalias = 0; -+- g_alias_cap = 0; -+- } -+-} -+- -+-static int load_config(const char *path) -+-{ -+- FILE *f; -+- char line[MAX_LINE]; -+- char key[64], val[MAXPATHLEN], pw[64]; -+- int n; -+- -+- f = fopen(path, "r"); -+- -+- if (!f) -+- { -+- fprintf(stderr, "srifreq: cannot open config: %s\n", path); -+- return 0; -+- } -+- -+- while (fgets(line, sizeof(line), f)) -+- { -+- /* Strip trailing whitespace and newlines */ -+- n = (int)strlen(line); -+- -+- while (n > 0 && -+- (line[n - 1] == '\r' || line[n - 1] == '\n' || line[n - 1] == ' ')) -+- line[--n] = '\0'; -+- -+- /* Skip blank and comment lines */ -+- if (!line[0] || line[0] == '#') -+- continue; -+- -+- key[0] = val[0] = pw[0] = '\0'; -+- -+- if (sscanf(line, "%63s %1023s %63s", key, val, pw) < 2) -+- continue; -+- -+- if (strcmp(key, "pubdir") == 0) -+- safe_strncpy(g_conf.pubdir, val, (int)sizeof(g_conf.pubdir)); -+- else if (strcmp(key, "logfile") == 0) -+- safe_strncpy(g_conf.logfile, val, (int)sizeof(g_conf.logfile)); -+- else if (strcmp(key, "aliases") == 0) -+- safe_strncpy(g_conf.aliases, val, (int)sizeof(g_conf.aliases)); -+- else if (strcmp(key, "trackfile") == 0) -+- safe_strncpy(g_conf.trackfile, val, (int)sizeof(g_conf.trackfile)); -+- else if (strcmp(key, "maxfiles") == 0) -+- g_conf.maxfiles = atoi(val); -+- else if (strcmp(key, "maxsize") == 0) -+- g_conf.maxbytes = atol(val); -+- else if (strcmp(key, "timewindow") == 0) -+- g_conf.timewindow = atol(val); -+- else if (strcmp(key, "private") == 0 && pw[0]) -+- config_add_private(val, pw); -+- } -+- -+- fclose(f); -+- -+- return 1; -+-} -+- -+-/* tracking_load -- Load node tracking data from file */ -+-static void tracking_load(void) -+-{ -+- FILE *f; -+- char line[MAX_LINE]; -+- char aka[256]; -+- int files; -+- long bytes; -+- long timestamp; -+- NodeTrack *nt; -+- time_t now = time(NULL); -+- -+- if (!g_conf.trackfile[0]) -+- return; -+- -+- f = fopen(g_conf.trackfile, "r"); -+- -+- if (!f) -+- return; -+- -+- while (fgets(line, sizeof(line), f)) -+- { -+- str_trim(line); -+- -+- if (!line[0] || line[0] == '#') -+- continue; -+- -+- if (sscanf(line, "%255s %d %ld %ld", aka, &files, &bytes, ×tamp) != 4) -+- continue; -+- -+- /* Skip if outside time window (also reject future/corrupt timestamps) */ -+- if (g_conf.timewindow > 0 && (timestamp > now || (now - timestamp) > g_conf.timewindow)) -+- continue; -+- -+- /* Create new tracking entry */ -+- nt = (NodeTrack *)malloc(sizeof(NodeTrack)); -+- -+- if (!nt) -+- continue; -+- -+- safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); -+- nt->files = files; -+- nt->bytes = bytes; -+- nt->last_time = (time_t)timestamp; -+- nt->next = g_conf.tracking; -+- g_conf.tracking = nt; -+- } -+- -+- fclose(f); -+-} -+- -+-/* tracking_save -- Save node tracking data to file */ -+-static void tracking_save(void) -+-{ -+- FILE *f; -+- NodeTrack *nt; -+- -+- if (!g_conf.trackfile[0]) -+- return; -+- -+- f = fopen(g_conf.trackfile, "w"); -+- -+- if (!f) -+- return; -+- -+- fprintf(f, "# srifreq tracking file - Format: AKA files bytes timestamp\n"); -+- -+- for (nt = g_conf.tracking; nt; nt = nt->next) -+- { -+- fprintf(f, "%s %d %ld %ld\n", nt->aka, nt->files, nt->bytes, (long)nt->last_time); -+- } -+- -+- fclose(f); -+-} -+- -+-/* tracking_find -- Find tracking entry for a node */ -+-static NodeTrack *tracking_find(const char *aka) -+-{ -+- NodeTrack *nt; -+- -+- for (nt = g_conf.tracking; nt; nt = nt->next) -+- { -+- if (strcmp(nt->aka, aka) == 0) -+- return nt; -+- } -+- -+- return NULL; -+-} -+- -+-/* tracking_update -- Update tracking after serving a file */ -+-static void tracking_update(const char *aka, long filesize) -+-{ -+- NodeTrack *nt = tracking_find(aka); -+- time_t now = time(NULL); -+- -+- if (nt) -+- { -+- /* Update existing entry */ -+- nt->files++; -+- nt->bytes += filesize; -+- nt->last_time = now; -+- } -+- else -+- { -+- /* Create new entry */ -+- nt = (NodeTrack *)malloc(sizeof(NodeTrack)); -+- -+- if (nt) -+- { -+- safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); -+- nt->files = 1; -+- nt->bytes = filesize; -+- nt->last_time = now; -+- nt->next = g_conf.tracking; -+- g_conf.tracking = nt; -+- } -+- } -+-} -+- -+-/* tracking_check -- Check if node exceeds limits */ -+-static int tracking_check(const char *aka, char *msg, int msglen) -+-{ -+- NodeTrack *nt = tracking_find(aka); -+- -+- if (!nt) -+- return 1; /* No tracking yet, allow */ -+- -+- /* Check max files */ -+- if (g_conf.maxfiles > 0 && nt->files >= g_conf.maxfiles) -+- { -+- snprintf(msg, msglen, "RATE LIMIT: max files (%d) reached for %s", g_conf.maxfiles, aka); -+- return 0; -+- } -+- -+- /* Check max bytes */ -+- if (g_conf.maxbytes > 0 && nt->bytes >= g_conf.maxbytes) -+- { -+- snprintf(msg, msglen, "RATE LIMIT: max bytes (%ld) reached for %s", g_conf.maxbytes, aka); -+- return 0; -+- } -+- -+- return 1; /* Within limits */ -+-} -+- -+-/* is_abs_path -- True if path is absolute (POSIX, Win32, AmigaDOS device:) */ -+-static int is_abs_path(const char *p) -+-{ -+- if (!p || !p[0]) -+- return 0; -+- -+- if (p[0] == '/' || p[0] == '\\') -+- return 1; -+- -+-#ifdef AMIGA -+- if (strchr(p, ':') != NULL) -+- return 1; -+-#else -+- if (p[1] == ':') -+- return 1; /* C:\ etc. */ -+-#endif -+- return 0; -+-} -+- -+-/* -+- * load_aliases -- Read alias definitions from file -+- * Lines starting with '#' or empty are skipped -+- * Format: -+- */ -+-static void load_aliases(const char *filepath) -+-{ -+- FILE *f; -+- char line[MAX_LINE]; -+- char name[64]; -+- char path[MAXPATHLEN]; -+- int n; -+- -+- /* Free previous aliases and start fresh */ -+- if (g_aliases) -+- { -+- free(g_aliases); -+- g_aliases = NULL; -+- } -+- -+- g_nalias = 0; -+- g_alias_cap = 0; -+- -+- if (!filepath || !filepath[0] || strcmp(filepath, "-") == 0) -+- return; -+- -+- f = fopen(filepath, "r"); -+- -+- if (!f) -+- { -+- /*fprintf(stderr, "srifreq: cannot open aliases file: %s\n", filepath);*/ -+- return; -+- } -+- -+- while (fgets(line, sizeof(line), f)) -+- { -+- char *p; -+- -+- /* Strip trailing newline */ -+- n = (int)strlen(line); -+- -+- while (n > 0 && (line[n - 1] == '\r' || line[n - 1] == '\n')) -+- line[--n] = '\0'; -+- -+- /* Skip blanks and comments */ -+- p = line; -+- -+- while (*p == ' ' || *p == '\t') -+- p++; -+- -+- if (!*p || *p == '#') -+- continue; -+- -+- name[0] = '\0'; -+- path[0] = '\0'; -+- -+- if (sscanf(p, "%63s %1023[^\n]", name, path) < 2) -+- continue; -+- -+- if (!name[0] || !path[0]) -+- continue; -+- -+- /* Grow array dynamically if needed */ -+- if (g_nalias >= g_alias_cap) -+- { -+- int new_cap = g_alias_cap ? g_alias_cap * 2 : 16; -+- Alias *new_arr = realloc(g_aliases, (size_t)new_cap * sizeof(Alias)); -+- -+- if (!new_arr) -+- break; -+- -+- g_aliases = new_arr; -+- g_alias_cap = new_cap; -+- } -+- -+- safe_strncpy(g_aliases[g_nalias].name, name, (int)sizeof(g_aliases[g_nalias].name)); -+- safe_strncpy(g_aliases[g_nalias].path, path, (int)sizeof(g_aliases[g_nalias].path)); -+- g_nalias++; -+- } -+- -+- fclose(f); -+- -+- /*printf("srifreq: loaded %d alias(es) from %s\n", g_nalias, filepath);*/ -+-} -+- -+-/* -+- * find_alias -- look up name in alias table (case-insensitive) -+- * Returns the path string, or NULL if not found -+- */ -+-static const char *find_alias(const char *name) -+-{ -+- char upper[64]; -+- char aname[64]; -+- int i; -+- int n; -+- -+- /* Convert name to uppercase */ -+- n = (int)strlen(name); -+- -+- if (n >= (int)sizeof(upper)) -+- n = (int)sizeof(upper) - 1; -+- -+- for (i = 0; i < n; i++) -+- upper[i] = (char)toupper((unsigned char)name[i]); -+- -+- upper[n] = '\0'; -+- -+- for (i = 0; i < g_nalias; i++) -+- { -+- int an; -+- an = (int)strlen(g_aliases[i].name); -+- -+- if (an >= (int)sizeof(aname)) an = (int)sizeof(aname) - 1; -+- { -+- int j; -+- -+- for (j = 0; j < an; j++) -+- aname[j] = (char)toupper((unsigned char)g_aliases[i].name[j]); -+- -+- aname[an] = '\0'; -+- } -+- -+- if (strcmp(upper, aname) == 0) -+- return g_aliases[i].path; -+- } -+- -+- return NULL; -+-} -+- -+-static int parse_srif(const char *path, SRIF *srif) -+-{ -+- FILE *f; -+- char line[MAX_LINE]; -+- char token[MAX_LINE]; -+- char value[MAX_LINE]; -+- -+- memset(srif, 0, sizeof(SRIF)); -+- srif->time_limit = -1; /* default: unlimited */ -+- srif->listed = 1; /* default: assume listed */ -+- srif->protected_sess = 0; -+- -+- f = fopen(path, "r"); -+- if (!f) -+- return 0; -+- -+- while (fgets(line, sizeof(line), f)) -+- { -+- str_trim(line); -+- if (!line[0]) -+- continue; -+- -+- token[0] = '\0'; -+- value[0] = '\0'; -+- -+- if (sscanf(line, "%1023s %1023[^\n]", token, value) < 1) -+- continue; -+- -+- str_upper(token); -+- -+- if (!value[0]) -+- continue; -+- -+- if (!strcmp(token, "SYSOP")) -+- safe_strncpy(srif->sysop, value, (int)sizeof(srif->sysop)); -+- else if (!strcmp(token, "AKA") && !srif->aka[0]) -+- safe_strncpy(srif->aka, value, (int)sizeof(srif->aka)); -+- else if (!strcmp(token, "REQUESTLIST")) -+- { -+- safe_strncpy(srif->request_list, value, MAXPATHLEN); -+- srif->got_request_list = 1; -+- } -+- else if (!strcmp(token, "RESPONSELIST")) -+- { -+- safe_strncpy(srif->response_list, value, MAXPATHLEN); -+- srif->got_response_list = 1; -+- } -+- else if (!strcmp(token, "OURAKA")) -+- safe_strncpy(srif->our_aka, value, (int)sizeof(srif->our_aka)); -+- else if (!strcmp(token, "PASSWORD")) -+- safe_strncpy(srif->password, value, (int)sizeof(srif->password)); -+- else if (!strcmp(token, "CALLERID")) -+- safe_strncpy(srif->caller_id, value, (int)sizeof(srif->caller_id)); -+- else if (!strcmp(token, "TIME")) -+- srif->time_limit = atoi(value); -+- else if (!strcmp(token, "TRANX")) -+- { -+- /* TRANX is a hex Unix timestamp: 5a326682 or 16-digit */ -+- unsigned long v = 0; -+- sscanf(value, "%lx", &v); -+- srif->tranx = (long)v; -+- } -+- else if (!strcmp(token, "REMOTESTATUS")) -+- { -+- char tmp[32]; -+- safe_strncpy(tmp, value, (int)sizeof(tmp)); -+- str_upper(tmp); -+- srif->protected_sess = (strncmp(tmp, "PROTECTED", 9) == 0) ? 1 : 0; -+- } -+- else if (!strcmp(token, "SYSTEMSTATUS")) -+- { -+- char tmp[32]; -+- safe_strncpy(tmp, value, (int)sizeof(tmp)); -+- str_upper(tmp); -+- srif->listed = (strncmp(tmp, "LISTED", 6) == 0) ? 1 : 0; -+- } -+- } -+- -+- fclose(f); -+- -+- return srif->got_request_list; -+-} -+- -+-/* Logging */ -+-static void do_log(const char *logpath, const char *msg) -+-{ -+- FILE *lf; -+- time_t t; -+- struct tm tm; -+- char timestamp[32]; -+- -+- if (!logpath || !logpath[0] || strcmp(logpath, "-") == 0) -+- return; -+- -+- t = time(NULL); -+- safe_localtime(&t, &tm); -+- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -+- -+- lf = fopen(logpath, "a"); -+- -+- if (lf) -+- { -+- fprintf(lf, "[%s] srifreq: %s\n", timestamp, msg); -+- fclose(lf); -+- } -+-} -+- -+-/* serve_one -- resolve one request name, check password/timestamp/update -+- * write to response list. Returns 1 if served -+- */ -+-static int serve_one(const char *req_name, const char *found_path, const char *req_pass, long req_newer, int req_update, const SRIF *srif, FILE *rsp_f, const char *log_path, char *logbuf, int logbuf_size) -+-{ -+- long fsize; -+- -+- /* Check rate limits before serving */ -+- if (g_conf.trackfile[0] && !tracking_check(srif->aka, logbuf, logbuf_size)) -+- { -+- do_log(log_path, logbuf); -+- return 0; -+- } -+- -+- /* RemoteStatus: if session is unprotected and a password is required, deny */ -+- if (req_pass[0] && !srif->protected_sess) -+- { -+- snprintf(logbuf, logbuf_size, "PASSWORD DENY (unprotected session): %s", req_name); -+- do_log(log_path, logbuf); -+- return 0; -+- } -+- -+- /* Password check: !pw must match SRIF PASSWORD (case-insensitive) */ -+- if (req_pass[0]) -+- { -+- char rp[64], sp[64]; -+- int i; -+- -+- safe_strncpy(rp, req_pass, (int)sizeof(rp)); -+- safe_strncpy(sp, srif->password, (int)sizeof(sp)); -+- -+- for (i = 0; rp[i]; i++) -+- rp[i] = (char)toupper((unsigned char)rp[i]); -+- -+- for (i = 0; sp[i]; i++) -+- sp[i] = (char)toupper((unsigned char)sp[i]); -+- -+- if (strcmp(rp, sp) != 0) -+- { -+- snprintf(logbuf, logbuf_size, "PASSWORD FAIL: %s", req_name); -+- do_log(log_path, logbuf); -+- -+- return 0; -+- } -+- } -+- -+- /* Update request (U flag): serve only if file is newer than TRANX */ -+- if (req_update && srif->tranx > 0) -+- { -+- long mtime = get_file_mtime(found_path); -+- -+- if (mtime <= srif->tranx) -+- { -+- snprintf(logbuf, logbuf_size, "NOT UPDATED: %s (mtime=%ld tranx=%ld)", req_name, mtime, srif->tranx); -+- do_log(log_path, logbuf); -+- return 0; -+- } -+- } -+- -+- /* Timestamp check: +ts means "only if file is newer than ts" */ -+- if (req_newer > 0) -+- { -+- long mtime = get_file_mtime(found_path); -+- -+- if (mtime <= req_newer) -+- { -+- snprintf(logbuf, logbuf_size, "NOT NEWER: %s (mtime=%ld req=%ld)", req_name, mtime, req_newer); -+- -+- do_log(log_path, logbuf); -+- return 0; -+- } -+- } -+- -+- snprintf(logbuf, logbuf_size, "FOUND: %s -> %s", req_name, found_path); -+- do_log(log_path, logbuf); -+- -+- if (rsp_f) -+- fprintf(rsp_f, "+%s\r\n", found_path); -+- -+- /* Update tracking after successful serve */ -+- if (g_conf.trackfile[0]) -+- { -+- fsize = get_file_size(found_path); -+- -+- if (fsize < 0) -+- fsize = 0; -+- -+- tracking_update(srif->aka, fsize); -+- } -+- -+- return 1; -+-} -+- -+-int main(int argc, char *argv[]) -+-{ -+- const char *srif_path; -+- SRIF srif; -+- FILE *req_f; -+- FILE *rsp_f; -+- char line[MAX_LINE]; -+- char req_name[MAX_LINE]; -+- char req_pass[64]; -+- long req_newer; -+- int req_update; -+- char found_path[MAXPATHLEN]; -+- char logbuf[MAXPATHLEN * 4 + 128]; -+- int found_count; -+- -+- config_init(); -+- -+- /* --conf */ -+- if (argc >= 4 && strcmp(argv[1], "--conf") == 0) -+- { -+- if (!load_config(argv[2])) -+- return 1; -+- -+- srif_path = argv[3]; -+- } -+- else -+- { -+- fprintf(stderr, "Usage:\n" -+- " srifreq --conf \n" -+- "\n" -+- "Config file keys: pubdir, logfile, aliases, private " -+- "\n"); -+- -+- return 1; -+- } -+- -+- if (!g_conf.pubdir[0]) -+- { -+- fprintf(stderr, "srifreq: pubdir not set\n"); -+- config_free(); -+- return 1; -+- } -+- -+- /* Load tracking data if configured */ -+- tracking_load(); -+- -+- load_aliases(g_conf.aliases[0] ? g_conf.aliases : NULL); -+- -+- snprintf(logbuf, sizeof(logbuf), "processing SRIF: %s", srif_path); -+- do_log(g_conf.logfile, logbuf); -+- -+- if (!parse_srif(srif_path, &srif)) -+- { -+- snprintf(logbuf, sizeof(logbuf), "ERROR: cannot parse SRIF or missing RequestList: %s", srif_path); -+- do_log(g_conf.logfile, logbuf); -+- fprintf(stderr, "srifreq: %s\n", logbuf); -+- config_free(); -+- return 1; -+- } -+- -+- /* SystemStatus: deny unlisted systems entirely */ -+- if (!srif.listed) -+- { -+- snprintf(logbuf, sizeof(logbuf), "DENIED: system is UNLISTED (aka: %s)", srif.aka); -+- do_log(g_conf.logfile, logbuf); -+- config_free(); -+- return 1; -+- } -+- -+- snprintf(logbuf, sizeof(logbuf), "sysop: %s aka: %s status: %s%s caller: %s req: %s", srif.sysop, srif.aka, srif.protected_sess ? "PROTECTED" : "UNPROTECTED", srif.tranx ? " (TRANX)" : "", srif.caller_id[0] ? srif.caller_id : "?", srif.request_list); -+- do_log(g_conf.logfile, logbuf); -+- -+- /* Log rate limiting status if active */ -+- if (g_conf.trackfile[0] && (g_conf.maxfiles > 0 || g_conf.maxbytes > 0)) -+- { -+- snprintf(logbuf, sizeof(logbuf), "rate limits: maxfiles=%d maxbytes=%ld window=%lds", g_conf.maxfiles, g_conf.maxbytes, g_conf.timewindow); -+- do_log(g_conf.logfile, logbuf); -+- } -+- -+- req_f = fopen(srif.request_list, "r"); -+- -+- if (!req_f) -+- { -+- snprintf(logbuf, sizeof(logbuf), "WARN: RequestList not available: %s", srif.request_list); -+- do_log(g_conf.logfile, logbuf); -+- config_free(); -+- return 0; -+- } -+- -+- rsp_f = NULL; -+- -+- if (srif.got_response_list && srif.response_list[0]) -+- { -+- rsp_f = fopen(srif.response_list, "w"); -+- -+- if (!rsp_f) -+- { -+- snprintf(logbuf, sizeof(logbuf), "WARN: cannot create ResponseList: %s", srif.response_list); -+- do_log(g_conf.logfile, logbuf); -+- } -+- } -+- -+- found_count = 0; -+- -+- /* Check and update track file */ -+- while (fgets(line, sizeof(line), req_f)) -+- { -+- char *p; -+- const char *alias_path; -+- -+- str_trim(line); -+- -+- if (!line[0] || line[0] == ';' || line[0] == '#') -+- continue; -+- -+- /* Parse: filename [!password] [+timestamp] [U] */ -+- req_name[0] = '\0'; -+- req_pass[0] = '\0'; -+- req_newer = 0; -+- req_update = 0; -+- -+- if (sscanf(line, "%1023s", req_name) < 1) -+- continue; -+- -+- /* Skip URLs */ -+- if (strncmp(req_name, "http", 4) == 0 || strncmp(req_name, "ftp", 3) == 0) -+- continue; -+- -+- /* Parse modifiers from the rest of the line */ -+- p = strstr(line, req_name); -+- -+- if (p) -+- p += strlen(req_name); -+- else -+- p = line + strlen(line); -+- -+- while (*p) -+- { -+- while (*p == ' ' || *p == '\t') -+- p++; -+- -+- if (*p == '!') -+- { -+- /* !password */ -+- int i = 0; -+- p++; -+- -+- while (*p && *p != ' ' && *p != '\t' && i < (int)sizeof(req_pass) - 1) -+- req_pass[i++] = *p++; -+- -+- req_pass[i] = '\0'; -+- } -+- else if (*p == '+') -+- { -+- /* +unix_timestamp */ -+- p++; -+- req_newer = atol(p); -+- -+- while (*p && *p != ' ' && *p != '\t') -+- p++; -+- } -+- else if (*p == 'U' && (p[1] == '\0' || p[1] == ' ' || p[1] == '\t')) -+- { -+- /* U = update request */ -+- req_update = 1; -+- p++; -+- } -+- else if (*p) -+- p++; /* Skip unknown token */ -+- } -+- -+- found_path[0] = '\0'; -+- -+- /* Check alias table first */ -+- alias_path = find_alias(req_name); -+- -+- if (alias_path) -+- { -+- if (is_abs_path(alias_path)) -+- safe_strncpy(found_path, alias_path, MAXPATHLEN); -+- else -+- path_join(found_path, MAXPATHLEN, g_conf.pubdir, alias_path); -+- -+- if (path_exists(found_path)) -+- found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+- else -+- { -+- snprintf(logbuf, sizeof(logbuf), "NOT FOUND (alias): %s -> %s", req_name, found_path); -+- do_log(g_conf.logfile, logbuf); -+- } -+- -+- continue; -+- } -+- -+- /* Wildcard: scan pubdir and all privdirs whose password matches */ -+- if (is_wildcard(req_name)) -+- { -+- DIR *dp; -+- struct dirent *de; -+- PrivDir *pd; -+- -+- /* Scan pubdir (no password needed) */ -+- dp = opendir(g_conf.pubdir); -+- if (dp) -+- { -+- while ((de = readdir(dp)) != NULL) -+- { -+- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) -+- continue; -+- -+- if (!wildmatch(req_name, de->d_name)) -+- continue; -+- -+- path_join(found_path, MAXPATHLEN, g_conf.pubdir, de->d_name); -+- -+- if (path_exists(found_path)) -+- found_count += serve_one(de->d_name, found_path, "", req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+- } -+- -+- closedir(dp); -+- } -+- -+- /* Scan matching privdirs */ -+- for (pd = g_conf.privdirs; pd; pd = pd->next) -+- { -+- char rp[64], pp[64]; -+- int ci; -+- -+- safe_strncpy(rp, req_pass, (int)sizeof(rp)); -+- safe_strncpy(pp, pd->password, (int)sizeof(pp)); -+- -+- for (ci = 0; rp[ci]; ci++) -+- rp[ci] = (char)toupper((unsigned char)rp[ci]); -+- -+- for (ci = 0; pp[ci]; ci++) -+- pp[ci] = (char)toupper((unsigned char)pp[ci]); -+- -+- if (strcmp(rp, pp) != 0) -+- continue; -+- -+- dp = opendir(pd->path); -+- -+- if (!dp) -+- continue; -+- -+- while ((de = readdir(dp)) != NULL) -+- { -+- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) -+- continue; -+- -+- if (!wildmatch(req_name, de->d_name)) -+- continue; -+- -+- path_join(found_path, MAXPATHLEN, pd->path, de->d_name); -+- -+- if (path_exists(found_path)) -+- found_count += serve_one(de->d_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+- } -+- -+- closedir(dp); -+- } -+- -+- continue; -+- } -+- -+- /* Plain filename: try pubdir first, then privdirs if password given */ -+- path_join(found_path, MAXPATHLEN, g_conf.pubdir, req_name); -+- -+- if (path_exists(found_path)) -+- { -+- found_count += -+- serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+- } -+- else if (req_pass[0]) -+- { -+- /* Try each private dir whose password matches */ -+- PrivDir *pd; -+- int served = 0; -+- -+- for (pd = g_conf.privdirs; pd && !served; pd = pd->next) -+- { -+- char rp[64], pp[64]; -+- int ci; -+- -+- safe_strncpy(rp, req_pass, (int)sizeof(rp)); -+- safe_strncpy(pp, pd->password, (int)sizeof(pp)); -+- -+- for (ci = 0; rp[ci]; ci++) -+- rp[ci] = (char)toupper((unsigned char)rp[ci]); -+- -+- for (ci = 0; pp[ci]; ci++) -+- pp[ci] = (char)toupper((unsigned char)pp[ci]); -+- -+- if (strcmp(rp, pp) != 0) -+- continue; -+- -+- path_join(found_path, MAXPATHLEN, pd->path, req_name); -+- -+- if (path_exists(found_path)) -+- { -+- found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+- served = 1; -+- } -+- } -+- if (!served) -+- { -+- snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s", req_name); -+- do_log(g_conf.logfile, logbuf); -+- } -+- } -+- else -+- { -+- snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s (pub: %s)", req_name, g_conf.pubdir); -+- do_log(g_conf.logfile, logbuf); -+- } -+- } -+- -+- fclose(req_f); -+- -+- if (rsp_f) -+- fclose(rsp_f); -+- -+- snprintf(logbuf, sizeof(logbuf), "done: %d file(s) found", found_count); -+- do_log(g_conf.logfile, logbuf); -+- -+- /* Save tracking data */ -+- tracking_save(); -+- -+- config_free(); -+- -+- return (found_count > 0) ? 0 : 1; -+-} -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/srifreq.txt binkd_pgul/misc/srifreq.txt -+--- binkd/misc/srifreq.txt 2026-04-26 13:54:14.035795187 +0100 -++++ binkd_pgul/misc/srifreq.txt 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,67 +0,0 @@ -+-srifreq -- SRIF-compatible file request server -+- -+-USAGE: -+- srifreq --conf -+- -+-DESCRIPTION: -+- SRIF (Standard Request Information Format) compatible file -+- request server for binkd. Processes incoming .req files and -+- serves files based on password protection and aliases. -+- -+-CONFIGURATION FILE FORMAT: -+- # Lines starting with # are comments -+- # Blank lines are ignored -+- -+- pubdir # Public directory (no password required) -+- logfile # Log file, or - to disable -+- aliases # Magic-name aliases file (optional) -+- private # Private directory (requires !password) -+- -+- # Rate limiting options (optional): -+- trackfile # File to track node download statistics -+- maxfiles # Max files per node per time window (0=unlimited) -+- maxsize # Max bytes per node per time window (0=unlimited) -+- timewindow # Time window in seconds (0=no window) -+- -+-ALIASES FILE FORMAT: -+- # Lines starting with # are comments -+- # Format: -+- # Names are matched case-insensitively -+- -+-EXAMPLE CONFIG FILE (srifreq.conf): -+- # srifreq.conf - SRIF Request Server Configuration -+- -+- pubdir Work:Fido/Public -+- logfile Work:Logs/srifreq.log -+- aliases Work:Fido/srifreq.aliases -+- -+- # Private directories (password protected) -+- private Work:Fido/Private/Uploader1 secretpass1 -+- private Work:Fido/Private/Node190 node190pwd -+- -+- # Rate limiting: max 10 files or 50MB per node per 24 hours -+- trackfile Work:Logs/srifreq.track -+- maxfiles 10 -+- maxsize 52428800 -+- timewindow 86400 -+- -+-EXAMPLE ALIASES FILE (srifreq.aliases): -+- # srifreq.aliases - Magic-name to file mappings -+- # Names are case-insensitive -+- -+- DOORWAY Games:Utils/Doorway/doorway.zip -+- NETMAIL Work:Comm/Fido/netmail.lha -+- README Docs:Readme.txt -+- 4DOUT AmiTCP:4DOut.lha -+- BINKD Apps:Comm/Binkd/binkd.lha -+- -+-REQUEST FILE FORMAT (.req): -+- Files listed one per line. Modifiers: -+- !password - Required password for private areas -+- +timestamp - Only serve if file is newer than timestamp -+- U - Update request (only if newer than client's TRANX) -+- -+-EXAMPLES: -+- srifreq --conf srifreq.conf inbound/srif_file.req -+- srifreq --conf srifreq.conf inbound/*.req -+- exec "work:fido/srifreq --conf work:fido/srifreq.conf *S" *.req *.REQ -+\ No hay ningún carácter de nueva línea al final del archivo -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile binkd_pgul/mkfls/amiga/Makefile -+--- binkd/mkfls/amiga/Makefile 2026-04-26 14:55:09.963221741 +0100 -++++ binkd_pgul/mkfls/amiga/Makefile 2026-04-16 17:49:00.000000000 +0100 -+@@ -26,4 +26,4 @@ -+ $(CC) -c $(CFLAGS) amiga/getfree.c -+ sem.o: -+ $(CC) -c $(CFLAGS) amiga/sem.c -+-include Makefile.dep -+\ No hay ningún carácter de nueva línea al final del archivo -++include Makefile.dep -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile.analyze.bebbo binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo -+--- binkd/mkfls/amiga/Makefile.analyze.bebbo 2026-04-25 16:52:14.088635220 +0100 -++++ binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,96 +0,0 @@ -+-# Makefile.analyze.bebbo -- Static analysis for Amiga bebbo (GCC 6.5.0b) -+-# Includes amiga/ code with bebbo-specific defines -+-# Usage: make -f Makefile.analyze.bebbo -+- -+-# All sources including Amiga-specific -+-ALL_SRCS = \ -+- binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ -+- bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ -+- readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ -+- setpttl.c https.c md5b.c crypt.c compress.c \ -+- amiga/rename.c amiga/getfree.c amiga/bsdsock.c amiga/dirent.c \ -+- amiga/utime.c amiga/rfc2553_amiga.c amiga/sem.c amiga/evloop.c \ -+- amiga/sock.c amiga/session.c \ -+- misc/decompress.c misc/freq.c misc/process_tic.c misc/srifreq.c misc/nodelist.c -+- -+-INCLUDES = -I. -Iamiga -+- -+-# bebbo-specific defines (GCC 6.5.0b, HAS native snprintf) -+-BEBBO_DEFINES = \ -+- -DAMIGA \ -+- -DHAVE_SOCKLEN_T \ -+- -DHAVE_INTMAX_T \ -+- -DHAVE_SNPRINTF \ -+- -DHAVE_GETOPT \ -+- -DHAVE_UNISTD_H \ -+- -DHAVE_SYS_TIME_H \ -+- -DHAVE_SYS_PARAM_H \ -+- -DHAVE_SYS_IOCTL_H \ -+- -DHAVE_NETINET_IN_H \ -+- -DHAVE_NETDB_H \ -+- -DHAVE_ARPA_INET_H \ -+- -DHAVE_STDARG_H \ -+- -DHAVE_VSNPRINTF \ -+- -DWITH_ZLIB -+- -+-CPPCHECK_FLAGS = \ -+- --enable=all \ -+- --inconclusive \ -+- --std=c89 \ -+- --quiet \ -+- --suppress=missingIncludeSystem \ -+- --suppress=unusedFunction \ -+- --suppress=checkersReport \ -+- $(INCLUDES) -+- -+-# Log files -+-LOG_DIR = analysis_logs -+-CPPCHECK_LOG = $(LOG_DIR)/cppcheck_bebbo.log -+-CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_bebbo.log -+- -+-.PHONY: all cppcheck clang-tidy analyze clean -+- -+-all: analyze -+- -+-cppcheck: -+- @mkdir -p $(LOG_DIR) -+- @echo "=== cppcheck Amiga bebbo (GCC 6.5.0b) ===" -+- @echo "Defines: bebbo, HAS native snprintf, no snprintf.c needed" -+- @echo "Saving output to: $(CPPCHECK_LOG)" -+- @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) $(ALL_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true -+- @echo "=== done ===" -+- @echo "Log saved: $(CPPCHECK_LOG)" -+- -+-clang-tidy: -+- @mkdir -p $(LOG_DIR) -+- @echo "=== clang-tidy Amiga bebbo ===" -+- @echo "Note: Some Amiga headers may not resolve on Linux host" -+- @echo "Saving output to: $(CLANG_TIDY_LOG)" -+- @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) -+- @for src in binkd.c readcfg.c amiga/evloop.c amiga/session.c; do \ -+- echo "" >> $(CLANG_TIDY_LOG); \ -+- echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ -+- clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ -+- $(INCLUDES) $(BEBBO_DEFINES) -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ -+- done -+- @echo "=== done ===" -+- @echo "Log saved: $(CLANG_TIDY_LOG)" -+- -+-analyze: cppcheck clang-tidy -+- -+-# Focus on Amiga-specific code only -+-amiga-only: -+- @echo "=== cppcheck Amiga-specific code only (bebbo) ===" -+- @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) \ -+- amiga/*.c 2>&1 || true -+- -+-# Check what differs between ADE and bebbo -+-diff-defines: -+- @echo "=== ADE vs bebbo define differences ===" -+- @echo "ADE only: -DHAVE_VSNPRINTF (no -DHAVE_SNPRINTF)" -+- @echo "bebbo: -DHAVE_SNPRINTF -DHAVE_VSNPRINTF" -+- @echo "" -+- @echo "ADE needs snprintf.c, bebbo does not" -+- -+-clean: -+- @true -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile.bebbo binkd_pgul/mkfls/amiga/Makefile.bebbo -+--- binkd/mkfls/amiga/Makefile.bebbo 2026-04-26 13:11:27.902433254 +0100 -++++ binkd_pgul/mkfls/amiga/Makefile.bebbo 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,208 +0,0 @@ -+-CC = m68k-amigaos-gcc -+- -+-# Common flags for compiler and linker -+-COMMON_CFLAGS = -Wall -Wextra -Wunused -Wunused-function -Wunused-variable -Wmissing-prototypes -Wno-pointer-sign -ffunction-sections -fdata-sections -noixemul -+-ARCH_FLAGS = -O -m68000 -msoft-float -fomit-frame-pointer -+- -+-CFLAGS = $(DEFINES) $(COMMON_CFLAGS) $(ARCH_FLAGS) -+-LIBS = -lz -lm -lamiga -+-LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_gcc.map -s -+- -+-# Tools use same flags as main binary -+-TOOL_CFLAGS = $(DEFINES) $(COMMON_CFLAGS) -O -m68000 -msoft-float -fomit-frame-pointer -I misc -+-TOOL_LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_tools.map -s -+- -+-OBJDIR = objs -+- -+-DEFINES = \ -+- -DAMIGA \ -+- -DHAVE_SOCKLEN_T \ -+- -DHAVE_INTMAX_T \ -+- -DHAVE_SNPRINTF \ -+- -DHAVE_GETOPT \ -+- -DHAVE_UNISTD_H \ -+- -DHAVE_SYS_TIME_H \ -+- -DHAVE_SYS_PARAM_H \ -+- -DHAVE_SYS_IOCTL_H \ -+- -DHAVE_NETINET_IN_H \ -+- -DHAVE_NETDB_H \ -+- -DHAVE_ARPA_INET_H \ -+- -DHTTPS \ -+- -DAMIGADOS_4D_OUTBOUND \ -+- -DHAVE_STDARG_H \ -+- -DHAVE_VSNPRINTF \ -+- -DWITH_ZLIB \ -+- -DOS=\"Amiga\" \ -+- -I. \ -+- -Iamiga -+- -+-SRCS = \ -+- binkd.c \ -+- readcfg.c \ -+- tools.c \ -+- ftnaddr.c \ -+- ftnq.c \ -+- client.c \ -+- server.c \ -+- protocol.c \ -+- bsy.c \ -+- inbound.c \ -+- breaksig.c \ -+- branch.c \ -+- amiga/rename.c \ -+- amiga/getfree.c \ -+- amiga/bsdsock.c \ -+- amiga/dirent.c \ -+- amiga/utime.c \ -+- amiga/rfc2553_amiga.c \ -+- amiga/sem.c \ -+- amiga/evloop.c \ -+- amiga/sock.c \ -+- amiga/session.c \ -+- bsycleanup.c \ -+- amiga/proto_amiga.c \ -+- ftndom.c \ -+- ftnnode.c \ -+- srif.c \ -+- pmatch.c \ -+- readflo.c \ -+- prothlp.c \ -+- iptools.c \ -+- run.c \ -+- binlog.c \ -+- exitproc.c \ -+- getw.c \ -+- xalloc.c \ -+- setpttl.c \ -+- https.c \ -+- md5b.c \ -+- crypt.c \ -+- compress.c -+- -+-OBJS = \ -+- $(OBJDIR)/binkd.o \ -+- $(OBJDIR)/readcfg.o \ -+- $(OBJDIR)/tools.o \ -+- $(OBJDIR)/ftnaddr.o \ -+- $(OBJDIR)/ftnq.o \ -+- $(OBJDIR)/client.o \ -+- $(OBJDIR)/server.o \ -+- $(OBJDIR)/protocol.o \ -+- $(OBJDIR)/bsy.o \ -+- $(OBJDIR)/inbound.o \ -+- $(OBJDIR)/breaksig.o \ -+- $(OBJDIR)/branch.o \ -+- $(OBJDIR)/rename.o \ -+- $(OBJDIR)/getfree.o \ -+- $(OBJDIR)/bsdsock.o \ -+- $(OBJDIR)/dirent.o \ -+- $(OBJDIR)/utime.o \ -+- $(OBJDIR)/rfc2553_amiga.o \ -+- $(OBJDIR)/sem.o \ -+- $(OBJDIR)/evloop.o \ -+- $(OBJDIR)/sock.o \ -+- $(OBJDIR)/session.o \ -+- $(OBJDIR)/bsycleanup.o \ -+- $(OBJDIR)/proto_amiga.o \ -+- $(OBJDIR)/ftndom.o \ -+- $(OBJDIR)/ftnnode.o \ -+- $(OBJDIR)/srif.o \ -+- $(OBJDIR)/pmatch.o \ -+- $(OBJDIR)/readflo.o \ -+- $(OBJDIR)/prothlp.o \ -+- $(OBJDIR)/iptools.o \ -+- $(OBJDIR)/run.o \ -+- $(OBJDIR)/binlog.o \ -+- $(OBJDIR)/exitproc.o \ -+- $(OBJDIR)/getw.o \ -+- $(OBJDIR)/xalloc.o \ -+- $(OBJDIR)/setpttl.o \ -+- $(OBJDIR)/https.o \ -+- $(OBJDIR)/md5b.o \ -+- $(OBJDIR)/crypt.o \ -+- $(OBJDIR)/compress.o -+- -+-all: binkd decompress process_tic freq srifreq nodelist -+- -+-$(OBJDIR): -+- mkdir -p $(OBJDIR) -+- -+-$(OBJDIR)/%.o: %.c -+- $(CC) -c $(CFLAGS) $< -o $@ -+- -+-binkd: $(OBJDIR) $(OBJS) -+- $(CC) $(CFLAGS) -o binkd $(OBJS) $(LIBS) $(LDFLAGS) -+- -+-# ---------- Utility tools (stand-alone, multi-platform) ---------- -+-# TOOL_CFLAGS and TOOL_LDFLAGS defined above -+- -+-decompress: -+- $(CC) $(TOOL_CFLAGS) -o decompress misc/decompress.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) -+- -+-process_tic: -+- $(CC) $(TOOL_CFLAGS) -o process_tic misc/process_tic.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) -+- -+-freq: -+- $(CC) $(TOOL_CFLAGS) -o freq misc/freq.c misc/portable.c $(TOOL_LDFLAGS) -+- -+-srifreq: -+- $(CC) $(TOOL_CFLAGS) -o srifreq misc/srifreq.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) -+- -+-nodelist: -+- $(CC) $(TOOL_CFLAGS) -o nodelist misc/nodelist.c misc/portable.c $(TOOL_LDFLAGS) -+- -+-install: all clean -+- -+-clean: -+- rm -f *.[bo] *.BAK *.core *.obj *.err *~ core -+- rm -rf $(OBJDIR) -+- rm -f binkd decompress process_tic freq srifreq nodelist -+- -+-# ---------- Explicit rules for amiga/ objects ---------- -+-$(OBJDIR)/rename.o: amiga/rename.c -+- $(CC) -c $(CFLAGS) amiga/rename.c -o $(OBJDIR)/rename.o -+- -+-$(OBJDIR)/getfree.o: amiga/getfree.c -+- $(CC) -c $(CFLAGS) amiga/getfree.c -o $(OBJDIR)/getfree.o -+- -+-$(OBJDIR)/sem.o: amiga/sem.c -+- $(CC) -c $(CFLAGS) amiga/sem.c -o $(OBJDIR)/sem.o -+- -+-$(OBJDIR)/bsdsock.o: amiga/bsdsock.c -+- $(CC) -c $(CFLAGS) amiga/bsdsock.c -o $(OBJDIR)/bsdsock.o -+- -+-$(OBJDIR)/dirent.o: amiga/dirent.c -+- $(CC) -c $(CFLAGS) amiga/dirent.c -o $(OBJDIR)/dirent.o -+- -+-$(OBJDIR)/utime.o: amiga/utime.c -+- $(CC) -c $(CFLAGS) amiga/utime.c -o $(OBJDIR)/utime.o -+- -+-$(OBJDIR)/rfc2553_amiga.o: amiga/rfc2553_amiga.c -+- $(CC) -c $(CFLAGS) amiga/rfc2553_amiga.c -o $(OBJDIR)/rfc2553_amiga.o -+- -+-$(OBJDIR)/evloop.o: amiga/evloop.c -+- $(CC) -c $(CFLAGS) amiga/evloop.c -o $(OBJDIR)/evloop.o -+- -+-$(OBJDIR)/sock.o: amiga/sock.c -+- $(CC) -c $(CFLAGS) amiga/sock.c -o $(OBJDIR)/sock.o -+- -+-$(OBJDIR)/session.o: amiga/session.c -+- $(CC) -c $(CFLAGS) amiga/session.c -o $(OBJDIR)/session.o -+- -+-$(OBJDIR)/bsycleanup.o: bsycleanup.c -+- $(CC) -c $(CFLAGS) bsycleanup.c -o $(OBJDIR)/bsycleanup.o -+- -+-$(OBJDIR)/proto_amiga.o: amiga/proto_amiga.c -+- $(CC) -c $(CFLAGS) amiga/proto_amiga.c -o $(OBJDIR)/proto_amiga.o -+- -+-depend Makefile.dep: Makefile -+- $(CC) -MM $(CFLAGS) $(SRCS) $(SYS) | \ -+- awk '{ if ($$1 != prev) { if (rec != "") print rec; \ -+- rec = $$0; prev = $$1; } \ -+- else { if (length(rec $$2) > 78) { print rec; rec = $$0; } \ -+- else rec = rec " " $$2 } } \ -+- END { print rec }' | tee Makefile.dep -+- -+--include Makefile.dep -+- -+-.PHONY: all binkd decompress process_tic freq srifreq nodelist clean install -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/nt95-mingw/Makefile binkd_pgul/mkfls/nt95-mingw/Makefile -+--- binkd/mkfls/nt95-mingw/Makefile 2026-04-26 13:12:24.499178632 +0100 -++++ binkd_pgul/mkfls/nt95-mingw/Makefile 2026-04-16 17:49:00.000000000 +0100 -+@@ -38,8 +38,7 @@ -+ setpttl.c https.c md5b.c crypt.c getopt.c nt/breaksig.c nt/getfree.c \ -+ nt/sem.c nt/TCPErr.c nt/WSock.c nt/w32tools.c nt/tray.c snprintf.c \ -+ ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c ntlm/des_enc.c \ -+- ntlm/helpers.c \ -+- bsycleanup.c -++ ntlm/helpers.c -+ -+ RES= nt/binkdres.rc -+ -+@@ -222,32 +221,7 @@ -+ OBJS=$(addprefix $(OBJDIR)/,$(patsubst %.c,%.o, $(SRCS))) -+ RESOBJS=$(addprefix $(OBJDIR)/, $(patsubst %.rc,%.o, $(RES))) -+ -+-# ---------- Utility tools (stand-alone) ---------- -+-TOOL_CFLAGS = -O2 -Wall -DWIN32 -I. -I misc -+- -+-utils: decompress process_tic freq srifreq nodelist -+- -+-decompress: -+- @echo Compiling decompress... -+- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/decompress.exe misc/decompress.c misc/portable.c -+- -+-process_tic: -+- @echo Compiling process_tic... -+- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/process_tic.exe misc/process_tic.c misc/portable.c -+- -+-freq: -+- @echo Compiling freq... -+- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/freq.exe misc/freq.c misc/portable.c -+- -+-srifreq: -+- @echo Compiling srifreq... -+- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/srifreq.exe misc/srifreq.c misc/portable.c -+- -+-nodelist: -+- @echo Compiling nodelist... -+- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/nodelist.exe misc/nodelist.c misc/portable.c -+- -+-.PHONY: all printinfo install html clean distclean makedirs utils decompress process_tic freq srifreq nodelist -++.PHONY: all printinfo install html clean distclean makedirs -+ -+ all: printinfo makedirs $(OUTDIR)/$(BINKDEXE) -+ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/nt95-msvc/Makefile binkd_pgul/mkfls/nt95-msvc/Makefile -+--- binkd/mkfls/nt95-msvc/Makefile 2026-04-26 12:41:43.750120247 +0100 -++++ binkd_pgul/mkfls/nt95-msvc/Makefile 2026-04-16 17:49:00.000000000 +0100 -+@@ -327,32 +327,7 @@ -+ -+ BINKDEXE = $(BINKDNAME).exe -+ -+-all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) utils -+- -+-TOOL_CFLAGS = -nologo -W3 -O2 -DWIN32 -DVISUALCPP -I. -I misc -+-TOOL_CC = $(CC) $(TOOL_CFLAGS) -+- -+-utils: "$(OUTDIR)\decompress.exe" "$(OUTDIR)\process_tic.exe" "$(OUTDIR)\freq.exe" "$(OUTDIR)\srifreq.exe" "$(OUTDIR)\nodelist.exe" -+- -+-"$(OUTDIR)\decompress.exe": misc\decompress.c misc\portable.c nt\dirwin32.c -+- @echo Compiling decompress... -+- @$(TOOL_CC) -Fe"$(OUTDIR)\decompress.exe" misc\decompress.c misc\portable.c nt\dirwin32.c -+- -+-"$(OUTDIR)\process_tic.exe": misc\process_tic.c misc\portable.c nt\dirwin32.c -+- @echo Compiling process_tic... -+- @$(TOOL_CC) -Fe"$(OUTDIR)\process_tic.exe" misc\process_tic.c misc\portable.c nt\dirwin32.c -+- -+-"$(OUTDIR)\freq.exe": misc\freq.c misc\portable.c -+- @echo Compiling freq... -+- @$(TOOL_CC) -Fe"$(OUTDIR)\freq.exe" misc\freq.c misc\portable.c -+- -+-"$(OUTDIR)\srifreq.exe": misc\srifreq.c misc\portable.c nt\dirwin32.c -+- @echo Compiling srifreq... -+- @$(TOOL_CC) -Fe"$(OUTDIR)\srifreq.exe" misc\srifreq.c misc\portable.c nt\dirwin32.c -+- -+-"$(OUTDIR)\nodelist.exe": misc\nodelist.c misc\portable.c -+- @echo Compiling nodelist... -+- @$(TOOL_CC) -Fe"$(OUTDIR)\nodelist.exe" misc\nodelist.c misc\portable.c -++all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) -+ -+ printinfo: -+ @echo on -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/os2-emx/Makefile binkd_pgul/mkfls/os2-emx/Makefile -+--- binkd/mkfls/os2-emx/Makefile 2026-04-26 13:12:44.342586479 +0100 -++++ binkd_pgul/mkfls/os2-emx/Makefile 2026-04-16 17:49:00.000000000 +0100 -+@@ -13,7 +13,7 @@ -+ LFLAGS=-Los2 -+ LIBS=-lsocket -lresolv -+ NTLM_SRC=ntlm/des_enc.c ntlm/helpers.c ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c -+-SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c bsycleanup.c ${NTLM_SRC} -++SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c ${NTLM_SRC} -+ TARGET=binkd2emx -+ -+ #PERLDIR=../perl5.00553/os2 -+@@ -122,33 +122,7 @@ -+ -+ TARGET:=$(TARGET).exe -+ -+-all: $(TARGET) utils -+- -+-TOOL_CFLAGS = $(CFLAGS) -I. -I misc -+- -+-utils: decompress process_tic freq srifreq nodelist -+- -+-decompress: misc/decompress.c -+- @echo Compiling decompress... -+- @$(CC) $(TOOL_CFLAGS) -o decompress.exe misc/decompress.c misc/portable.c os2/dirent.c -+- -+-process_tic: misc/process_tic.c -+- @echo Compiling process_tic... -+- @$(CC) $(TOOL_CFLAGS) -o process_tic.exe misc/process_tic.c misc/portable.c os2/dirent.c -+- -+-freq: misc/freq.c -+- @echo Compiling freq... -+- @$(CC) $(TOOL_CFLAGS) -o freq.exe misc/freq.c misc/portable.c -+- -+-srifreq: misc/srifreq.c -+- @echo Compiling srifreq... -+- @$(CC) $(TOOL_CFLAGS) -o srifreq.exe misc/srifreq.c misc/portable.c os2/dirent.c -+- -+-nodelist: misc/nodelist.c -+- @echo Compiling nodelist... -+- @$(CC) $(TOOL_CFLAGS) -o nodelist.exe misc/nodelist.c misc/portable.c -+- -+-.PHONY: utils decompress process_tic freq srifreq nodelist -++all: $(TARGET) -+ -+ .c.o: -+ @echo Compiling $*.c... -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/unix/Makefile.analyze.unix binkd_pgul/mkfls/unix/Makefile.analyze.unix -+--- binkd/mkfls/unix/Makefile.analyze.unix 2026-04-25 16:52:02.225860998 +0100 -++++ binkd_pgul/mkfls/unix/Makefile.analyze.unix 1970-01-01 00:00:00.000000000 +0000 -+@@ -1,65 +0,0 @@ -+-# Makefile.analyze.unix -- Static analysis for Unix/Linux code only -+-# Excludes Amiga-specific code (amiga/ directory) -+-# Usage: make -f Makefile.analyze.unix -+- -+-# Unix-portable sources only (no amiga/ directory) -+-UNIX_SRCS = \ -+- binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ -+- bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ -+- readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ -+- setpttl.c https.c md5b.c crypt.c compress.c -+- -+-# Unix-specific files (if any) -+-UNIX_SYS_SRCS = \ -+- unix/getwd.c unix/lock.c unix/tcperr.c -+- -+-INCLUDES = -I. -Iunix -+- -+-CPPCHECK_FLAGS = \ -+- --enable=all \ -+- --inconclusive \ -+- --std=c89 \ -+- --quiet \ -+- --suppress=missingIncludeSystem \ -+- --suppress=unusedFunction \ -+- $(INCLUDES) -+- -+-# Log files -+-LOG_DIR = analysis_logs -+-CPPCHECK_LOG = $(LOG_DIR)/cppcheck_unix.log -+-CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_unix.log -+- -+-.PHONY: all cppcheck clang-tidy analyze clean -+- -+-all: analyze -+- -+-cppcheck: -+- @mkdir -p $(LOG_DIR) -+- @echo "=== cppcheck Unix/Linux code ===" -+- @echo "Saving output to: $(CPPCHECK_LOG)" -+- @cppcheck $(CPPCHECK_FLAGS) $(UNIX_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true -+- @echo "=== done ===" -+- @echo "Log saved: $(CPPCHECK_LOG)" -+- -+-clang-tidy: -+- @mkdir -p $(LOG_DIR) -+- @echo "=== clang-tidy Unix/Linux code ===" -+- @echo "Saving output to: $(CLANG_TIDY_LOG)" -+- @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) -+- @for src in $(UNIX_SRCS); do \ -+- echo "" >> $(CLANG_TIDY_LOG); \ -+- echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ -+- clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ -+- $(INCLUDES) -DUNIX -DHAVE_SNPRINTF -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ -+- done -+- @echo "=== done ===" -+- @echo "Log saved: $(CLANG_TIDY_LOG)" -+- -+-analyze: cppcheck clang-tidy -+- -+-quick: -+- @echo "=== Quick error check (Unix) ===" -+- @cppcheck --enable=error --std=c89 --quiet $(INCLUDES) $(UNIX_SRCS) 2>&1 || true -+- -+-clean: -+- @true -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/unix/Makefile.in binkd_pgul/mkfls/unix/Makefile.in -+--- binkd/mkfls/unix/Makefile.in 2026-04-26 13:11:58.084940863 +0100 -++++ binkd_pgul/mkfls/unix/Makefile.in 2026-04-16 17:49:00.000000000 +0100 -+@@ -12,17 +12,17 @@ -+ MANDIR=$(DATADIR)/man -+ DOCDIR=$(DATADIR)/doc/$(APPL) -+ -+-SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c bsycleanup.c @OPT_SRC@ -++SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c @OPT_SRC@ -+ OBJS=${SRCS:.c=.o} -+ AUTODEFS=@DEFS@ -+ AUTOLIBS=@LIBS@ -+-DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -DPROTOTYPES -++DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -+ CPPFLAGS=@CPPFLAGS@ -+ CFLAGS=@CFLAGS@ -+ LDFLAGS=@LDFLAGS@ -+ LIBS=$(AUTOLIBS) -+ -+-all: compile banner utils -++all: compile banner -+ -+ compile: $(APPL) -+ -+@@ -48,32 +48,6 @@ -+ @echo " run \`configure --prefix=/another/path' and go to step 1. " -+ @echo -+ -+-utils: decompress process_tic freq srifreq nodelist -+- -+-TOOL_CFLAGS = $(DEFINES) $(CPPFLAGS) $(CFLAGS) -I. -I misc -+- -+-decompress: misc/decompress.c misc/portable.c misc/portable.h -+- @echo Compiling decompress... -+- @$(CC) $(TOOL_CFLAGS) -o $@ misc/decompress.c misc/portable.c -+- -+-process_tic: misc/process_tic.c misc/portable.c misc/portable.h -+- @echo Compiling process_tic... -+- @$(CC) $(TOOL_CFLAGS) -o $@ misc/process_tic.c misc/portable.c -+- -+-freq: misc/freq.c misc/portable.c misc/portable.h -+- @echo Compiling freq... -+- @$(CC) $(TOOL_CFLAGS) -o $@ misc/freq.c misc/portable.c -+- -+-srifreq: misc/srifreq.c misc/portable.c misc/portable.h -+- @echo Compiling srifreq... -+- @$(CC) $(TOOL_CFLAGS) -o $@ misc/srifreq.c misc/portable.c -+- -+-nodelist: misc/nodelist.c misc/portable.c -+- @echo Compiling nodelist... -+- @$(CC) $(TOOL_CFLAGS) -o $@ misc/nodelist.c misc/portable.c -+- -+-.PHONY: decompress process_tic freq srifreq nodelist -+- -+ .version: $(APPL) -+ @./$(APPL) -v | $(AWK) '{ print $$2; }' > $@ -+ -+@@ -92,7 +66,6 @@ -+ clean: -+ rm -f *.[bo] unix/*.[bo] ntlm/*.[bo] *.BAK *.core *.obj *.err -+ rm -f *~ core config.cache config.log config.status -+- rm -f decompress process_tic freq srifreq nodelist -+ -+ cleanall: clean -+ rm -f $(APPL) Makefile Makefile.dep Makefile.in -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/protocol.c binkd_pgul/protocol.c -+--- binkd/protocol.c 2026-04-26 09:45:52.230049023 +0100 -++++ binkd_pgul/protocol.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -45,19 +45,12 @@ -+ #include "md5b.h" -+ #include "crypt.h" -+ #include "compress.h" -+-#ifdef AMIGA -+-#include "amiga/proto_amiga.h" -+-#endif -+ -+ #ifdef WITH_PERL -+ #include "perlhooks.h" -+ #endif -+ #include "rfc2553.h" -+ -+-#if defined(HAVE_THREADS) || defined(AMIGA) -+-extern MUTEXSEM lsem; -+-#endif -+- -+ /* define to enable val's code for -ip checks (default is gul's code) */ -+ #undef VAL_STYLE -+ #ifdef VAL_STYLE -+@@ -70,7 +63,7 @@ -+ /* -+ * Fills <> with initial values, allocates buffers, etc. -+ */ -+-int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) -++static int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) -+ { -+ char val[4]; -+ socklen_t lval; -+@@ -133,12 +126,6 @@ -+ #endif -+ setsockopts (state->s_in = socket_in); -+ setsockopts (state->s_out = socket_out); -+- -+-#if defined(AMIGA) -+- setsockopts_amiga(socket_in, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); -+- setsockopts_amiga(socket_out, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); -+-#endif -+- -+ TF_ZERO (&state->in); -+ TF_ZERO (&state->out); -+ TF_ZERO (&state->flo); -+@@ -194,7 +181,7 @@ -+ /* -+ * Clears protocol buffers and queues, closes files, etc. -+ */ -+-int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) -++static int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) -+ { -+ int i; -+ -+@@ -238,7 +225,7 @@ -+ } -+ -+ /* Process rcvdlist */ -+-FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) -++static FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) -+ { -+ int i; -+ -+@@ -328,7 +315,7 @@ -+ /* -+ * Sends next msg from the msg queue or next data block -+ */ -+-int send_block (STATE *state, BINKD_CONFIG *config) -++static int send_block (STATE *state, BINKD_CONFIG *config) -+ { -+ int i, n, save_errno; -+ const char *save_err; -+@@ -2104,7 +2091,7 @@ -+ return 0; -+ } -+ -+-int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) -++static int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) -+ { -+ char buf[MAXPATHLEN+1]; -+ FILE *f; -+@@ -2158,8 +2145,7 @@ -+ -+ *extra = ""; -+ if (state->z_cansend && state->extcmd && state->out.size >= config->zminsize -+- && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first) -+- && !(state->to && state->to->NC_flag)) { -++ && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first)) { -+ #ifdef WITH_BZLIB2 -+ if (!state->z_send && (state->z_cansend & 2)) { -+ *extra = " BZ2"; state->z_send = 2; -+@@ -2462,7 +2448,6 @@ -+ { -+ char szAddr[FTN_ADDR_SZ + 1]; -+ -+- memset(szAddr, 0, sizeof(szAddr)); -+ ftnaddress_to_str (szAddr, &state->sent_fls[n].fa); -+ state->bytes_sent += state->sent_fls[n].size; -+ ++state->files_sent; -+@@ -2553,7 +2538,7 @@ -+ }; -+ -+ /* Recvs next block, processes msgs or writes down the data from the remote */ -+-int recv_block (STATE *state, BINKD_CONFIG *config) -++static int recv_block (STATE *state, BINKD_CONFIG *config) -+ { -+ int no; -+ -+@@ -2785,7 +2770,7 @@ -+ return 1; -+ } -+ -+-int banner (STATE *state, BINKD_CONFIG *config) -++static int banner (STATE *state, BINKD_CONFIG *config) -+ { -+ int tz; -+ char szLocalTime[60]; -+@@ -2865,7 +2850,7 @@ -+ return 1; -+ } -+ -+-int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) -++static int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) -+ { -+ struct stat sb; -+ FILE *f = NULL; -+@@ -3046,7 +3031,7 @@ -+ return 1; -+ } -+ -+-void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) -++static void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) -+ { -+ char szFTNAddr[FTN_ADDR_SZ + 1]; -+ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readcfg.c binkd_pgul/readcfg.c -+--- binkd/readcfg.c 2026-04-26 15:04:45.397917107 +0100 -++++ binkd_pgul/readcfg.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -44,10 +44,6 @@ -+ */ -+ BINKD_CONFIG *current_config; -+ -+-#ifdef AMIGA -+-extern struct SignalSemaphore config_sem; -+-#endif -+- -+ /* -+ * Temporary static structure for configuration reading -+ */ -+@@ -214,15 +210,9 @@ -+ snprintf(c->iport, sizeof(c->iport), "%s", find_port("")); -+ snprintf(c->oport, sizeof(c->oport), "%s", find_port("")); -+ c->call_delay = 60; -+- c->no_call_delay = 0; -+ c->rescan_delay = 60; -+ c->nettimeout = DEF_TIMEOUT; -+ c->oblksize = DEF_BLKSIZE; -+- -+-#ifdef AMIGA -+- c->tcp_nodelay = 0; -+-#endif -+- -+ #if defined(WITH_ZLIB) || defined(WITH_BZLIB2) -+ c->zminsize = 1024; -+ c->zlevel = 0; -+@@ -401,16 +391,8 @@ -+ {"oport", read_port, &work_config.oport, 0, 0}, -+ {"rescan-delay", read_time, &work_config.rescan_delay, 1, DONT_CHECK}, -+ {"call-delay", read_time, &work_config.call_delay, 1, DONT_CHECK}, -+- {"no-call-delay", read_bool, &work_config.no_call_delay, 0, 0}, -+ {"timeout", read_time, &work_config.nettimeout, 1, DONT_CHECK}, -+ {"oblksize", read_int, &work_config.oblksize, MIN_BLKSIZE, MAX_BLKSIZE}, -+- -+-#ifdef AMIGA -+- {"tcp-nodelay", read_bool, &work_config.tcp_nodelay, 0, 0}, -+- {"so-sndbuf", read_int, &work_config.so_sndbuf, 0, 65535}, -+- {"so-rcvbuf", read_int, &work_config.so_rcvbuf, 0, 65535}, -+-#endif -+- -+ {"maxservers", read_int, &work_config.max_servers, 0, DONT_CHECK}, -+ {"maxclients", read_int, &work_config.max_clients, 0, DONT_CHECK}, -+ {"inbound", read_string, work_config.inbound, 'd', 0}, -+@@ -684,7 +666,7 @@ -+ exp_ftnaddress (&fa, work_config.pAddr, work_config.nAddr, work_config.pDomains.first); -+ pn = add_node (&fa, NULL, password, pkt_pwd, out_pwd, '-', NULL, NULL, -+ NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, -+- HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, -++ HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, -+ #ifdef BW_LIM -+ BW_DEF, BW_DEF, -+ #endif -+@@ -848,10 +830,11 @@ -+ if (!new_config) -+ { -+ /* Config error. Abort or continue? */ -+- unlock_config_structure(&work_config, 0); -+- -+- if (current_config) -+- Log(1, "error in configuration, using old config"); -++ if (current_config) -++ { -++ Log(1, "error in configuration, using old config"); -++ unlock_config_structure(&work_config, 0); -++ } -+ } -+ -+ return new_config; -+@@ -874,16 +857,6 @@ -+ Log (2, "got SIGHUP"); -+ need_reload = got_sighup; -+ got_sighup = 0; -+-#elif defined(AMIGA) -+- /* No SIGHUP on AmigaOS: detect config change by mtime */ -+- need_reload = 0; -+- if (current_config->config_list.first) -+- { -+- if (stat(current_config->config_list.first->path, &sb) == 0 && -+- current_config->config_list.first->mtime != 0 && -+- sb.st_mtime != current_config->config_list.first->mtime) -+- need_reload = 1; -+- } -+ #else -+ need_reload = 0; -+ #endif -+@@ -913,75 +886,8 @@ -+ } -+ #endif -+ -+- if (!need_reload) -+- return 0; -+- -+-#ifdef AMIGA -+- /* Prevent reload storms and partial-file reads. -+- * -+- * On AmigaOS (and some Unix editors), config files are written in multiple -+- * passes, so binkd may see the mtime change while the file is still being -+- * written. Attempting to parse an incomplete file gives "unknown keyword" -+- * errors, and rapid repeated mtime changes cause bind() to fail because the -+- * previous listen socket has not been released yet. -+- * -+- * Strategy: after first detecting a change, wait until the mtime has been -+- * stable for at least 2 seconds before actually reloading. Also enforce a -+- * minimum of 5 seconds between successive successful reloads. -+- */ -+- { -+- static time_t last_reload = 0; /* time of last successful reload */ -+- static time_t change_seen = 0; /* time we first noticed the change */ -+- static time_t stable_mtime = 0; /* mtime we are waiting to stabilize */ -+- static int reload_pending = 0; /* persists between calls */ -+- time_t now = time(NULL); -+- -+- /* The loop has already updated pc->mtime, so in the next call -+- * need_reload will be 0 even though we haven't reloaded yet. -+- * reload_pending keeps the reload intent alive. -+- */ -+- if (need_reload) reload_pending = 1; -+- -+- if (!reload_pending) -+- return 0; -+- -+- /* Get the mtime of the primary config file */ -+- { struct stat sb2; -+- time_t cur_mtime = 0; -+- -+- if (current_config->config_list.first && stat(current_config->config_list.first->path, &sb2) == 0) -+- cur_mtime = sb2.st_mtime; -+- -+- /* mtime just changed (or changed again) — reset the stability clock */ -+- if (cur_mtime != stable_mtime) -+- { -+- stable_mtime = cur_mtime; -+- change_seen = now; -+- Log(5, "checkcfg: config mtime changed, waiting for stability..."); -+- return 0; -+- } -+- -+- /* mtime has been stable since change_seen */ -+- if (now - change_seen < 2) -+- { -+- Log(5, "checkcfg: config not yet stable (%lds), waiting...", -+- (long)(now - change_seen)); -+- return 0; -+- } -+- } -+- -+- /* Enforce minimum gap between reloads to let the OS release sockets */ -+- if (now - last_reload < 5) -+- { -+- Log(5, "checkcfg: reload suppressed (too soon, %lds)", (long)(now - last_reload)); -+- return 0; -+- } -+- -+- last_reload = now; -+- reload_pending = 0; /* Once the intent has been consumed, the reload is executed */ -+- } -+-#endif -+- -++ if (!need_reload) -++ return 0; -+ /* Reload starting from first file in list */ -+ Log(2, "Reloading configuration..."); -+ pc = current_config->config_list.first; -+@@ -1219,7 +1125,7 @@ -+ char *w[ARGNUM], *tmp, *pkt_pwd, *out_pwd, *pipe; -+ int i, j; -+ int NR_flag = NR_USE_OLD, ND_flag = ND_USE_OLD, HC_flag = HC_USE_OLD, -+- MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, NC_flag = NC_USE_OLD, restrictIP = RIP_USE_OLD, -++ MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, restrictIP = RIP_USE_OLD, -+ IP_afamily = AF_USE_OLD; -+ #ifdef BW_LIM -+ long bw_send = BW_DEF, bw_recv = BW_DEF; -+@@ -1269,8 +1175,6 @@ -+ HC_flag = HC_OFF; -+ else if (STRICMP (tmp, "-noproxy") == 0) -+ NP_flag = NP_ON; -+- else if (STRICMP (tmp, "-nc") == 0) -+- NC_flag = NC_ON; -+ #ifdef BW_LIM -+ else if (STRICMP (tmp, "-bw") == 0) -+ { -+@@ -1354,7 +1258,7 @@ -+ -+ split_passwords(w[2], &pkt_pwd, &out_pwd); -+ pn = add_node (&fa, w[1], w[2], pkt_pwd, out_pwd, (char)(w[3] ? w[3][0] : '-'), w[4], w[5], -+- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, -++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, -+ IP_afamily, -+ #ifdef BW_LIM -+ bw_send, bw_recv, -+@@ -2087,9 +1991,6 @@ -+ if (fn->pkt_pwd) pwd_len += strlen(fn->pkt_pwd)+1; else pwd_len += 2; -+ if (fn->out_pwd) pwd_len += strlen(fn->out_pwd)+1; else pwd_len += 2; -+ pwd = calloc (1, pwd_len+1); -+- /* Guard against null pointer dereference if calloc fails */ -+- if (!pwd) -+- return 0; -+ strcpy(pwd, fn->pwd); -+ if (fn->pkt_pwd != (char*)&(fn->pwd) || fn->out_pwd != (char*)&(fn->pwd)) { -+ strcat(strcat(pwd, ","), (fn->pkt_pwd) ? fn->pkt_pwd : "-"); -+@@ -2423,3 +2324,4 @@ -+ return 1; -+ } -+ #endif -++ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readcfg.h binkd_pgul/readcfg.h -+--- binkd/readcfg.h 2026-04-25 20:39:08.569961699 +0100 -++++ binkd_pgul/readcfg.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -111,16 +111,8 @@ -+ #endif -+ int nettimeout; -+ int connect_timeout; -+- -+-#ifdef AMIGA -+- int tcp_nodelay; -+- int so_sndbuf; -+- int so_rcvbuf; -+-#endif -+- -+- int call_delay; -+- int no_call_delay; -+ int rescan_delay; -++ int call_delay; -+ int max_servers; -+ int max_clients; -+ int kill_dup_partial_files; -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readdir.h binkd_pgul/readdir.h -+--- binkd/readdir.h 2026-04-22 20:37:22.121954324 +0100 -++++ binkd_pgul/readdir.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -23,8 +23,6 @@ -+ #elif defined(__MINGW32__) -+ #include -+ #include -+-#elif defined(AMIGA) -+-#include "amiga/dirent.h" -+ #else -+ #include -+ #include -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/README.md binkd_pgul/README.md -+--- binkd/README.md 2026-04-22 18:44:10.839451402 +0100 -++++ binkd_pgul/README.md 2026-04-16 17:49:00.000000000 +0100 -+@@ -6,79 +6,6 @@ -+ -+ ## Compiling -+ -+-AmigaOS: -+- -+-Copy "mkfls/amiga/Makefile" to root and make -+- -+-Need ADE to compile project with ixemul library. -+- -+-https://aminet.net/package/dev/gcc/ADE -+- -+-It no longer requires ADE to be installed for execution, as it doesn't need /bin/sh to run scripts or external programs from binkd.conf. However, it does require ixemul.library and ixnet.library, either in the libs directory or in the same directory as the executable. -+- -+-You can find the ADE ixemul.library and ixnet.library libraries in the aminet package or in the released versions, along with the program and utilities already compiled. -+- -+-https://github.com/skbn/binkd/releases -+- -+-5.Work:fido> version work:fido/ixnet.library -+-ixnet.library 63.1 -+- -+-5.Work:fido> version work:fido/ixemul.library -+-ixemul.library 63.1 -+- -+- -+-I've attached six programs for your assistance: -+- -+-[decompress] which decompresses incoming files in lha or zip format, if necessary. -+- -+-``` -+-[freq] which generates file requests in ASO mode and places the necessary files in outbound directory. -+- -+-Usage:freq Z:N/NODE[.POINT] -+-``` -+-``` -+-[freq_bso] same but BSO style -+-Usage: freq_bso Z:N/NODE[.POINT] -+- No point : .0ZZ/NNNNNNNN.req -+- Point : .0ZZ/N -+-``` -+- -+-``` -+-[process_tic] which processes tic files and places them in the filebox folder. -+-With --copypublic option, copy the file you receive to a directory named pub/ from PROGDIR -+->> exec "path/process_tic --copypublic" *.tic *.TIC -+->> exec "path/process_tic" *.tic *.TIC -+-``` -+- -+-``` -+-[srifreq] copy of "misc/srifreq" but in c. SRIF-compatible file-request server for binkd -+- -+-Usage: srifreq [] -+- SRIF control file passed by binkd (exec "srifreq *S") -+- directory containing public downloadable files -+- log file path (pass "" or - to disable logging) -+- optional file mapping magic names to real paths -+- -+-Aliases file format (one alias per line, # = comment): -+-MAGIC_NAME relative/or/absolute/path/to/file -+-Example: -+-NODELIST pub/NODELIST.ZIP -+-FILES pub/ALLFILES.TXT -+- -+-binkd.conf example: exec "srifreq *S pub log/srifreq.log aliases.txt" *.req -+-``` -+- -+-``` -+-[nodelist] FidoNet nodelist compiler for binkd - AmigaOS version in c from "misc/nodelist.pl" -+- -+-Usage: nodelist [] -+-``` -+- -+-Also compileable on *nix >> "gcc -O2 -Wall -o nodelist nodelist.c" -+- -+-BUGFIXES: -+-Option "-C" to reload config It remains unstable -+- -+ non-UNIX: -+ -+ 1. Find in mkfls/ a subdirectory for your system/compiler, copy all files -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/rfc2553.h binkd_pgul/rfc2553.h -+--- binkd/rfc2553.h 2026-04-26 10:31:43.475331092 +0100 -++++ binkd_pgul/rfc2553.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -21,52 +21,8 @@ -+ -+ #include "iphdr.h" -+ -+-/* Amiga: define EAI_* before including netdb.h to prevent libnix redefinition */ -+-#if defined(AMIGA) -+-#include -+-#include -+-#include -+-#include -+- -+- #undef EAI_NONAME -+- #undef EAI_AGAIN -+- #undef EAI_FAIL -+- #undef EAI_NODATA -+- #undef EAI_FAMILY -+- #undef EAI_SOCKTYPE -+- #undef EAI_SERVICE -+- #undef EAI_ADDRFAMILY -+- #undef EAI_MEMORY -+- #undef EAI_SYSTEM -+- #undef EAI_UNKNOWN -+- #define EAI_NONAME -1 -+- #define EAI_AGAIN -2 -+- #define EAI_FAIL -3 -+- #define EAI_NODATA -4 -+- #define EAI_FAMILY -5 -+- #define EAI_SOCKTYPE -6 -+- #define EAI_SERVICE -7 -+- #define EAI_ADDRFAMILY -8 -+- #define EAI_MEMORY -9 -+- #define EAI_SYSTEM -10 -+- #define EAI_UNKNOWN -11 -+- -+-#include -+- -+-/* EAI_ADDRFAMILY is BSD/macOS specific; Linux/glibc does not define it -+- * Map it to EAI_FAMILY which has the same meaning on those platforms */ -+-#ifndef EAI_ADDRFAMILY -+-#ifdef EAI_FAMILY -+-#define EAI_ADDRFAMILY EAI_FAMILY -+-#else -+-#define EAI_ADDRFAMILY -9 -+-#endif -+-#endif -+- -+-#endif -+- -+ /* Autosense getaddrinfo */ -+-#if defined(AI_PASSIVE) && defined(EAI_NONAME) && !defined(AMIGA) -++#if defined(AI_PASSIVE) && defined(EAI_NONAME) -+ #define HAVE_GETADDRINFO -+ #endif -+ -+@@ -91,18 +47,6 @@ -+ }; -+ #define addrinfo addrinfo_emu -+ -+-#ifdef AMIGA -+-#ifdef getaddrinfo -+-#undef getaddrinfo -+-#endif -+-#ifdef freeaddrinfo -+-#undef freeaddrinfo -+-#endif -+-#ifdef gai_strerror -+-#undef gai_strerror -+-#endif -+-#endif -+- -+ int getaddrinfo(const char *nodename, const char *servname, -+ const struct addrinfo *hints, -+ struct addrinfo **res); -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/run.c binkd_pgul/run.c -+--- binkd/run.c 2026-04-26 10:31:17.108612481 +0100 -++++ binkd_pgul/run.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -18,11 +18,6 @@ -+ #ifdef HAVE_UNISTD_H -+ #include -+ #endif -+-#ifdef AMIGA -+-#include -+-#include -+-#include -+-#endif -+ -+ #include "sys.h" -+ #include "run.h" -+@@ -45,128 +40,10 @@ -+ #define SHELL (getenv("COMSPEC") ? getenv("COMSPEC") : "command.com") -+ #define SHELL_META "\"\'\\%<>|&^@" -+ #define SHELLOPT "/c" -+-#elif defined(AMIGA) -+-/* AmigaOS shell */ -+-#define SHELL "c:execute" -+-#define SHELL_META "\"\'\\*?(){};&|<>" -+ #else -+ #error "Unknown platform" -+ #endif -+ -+-#ifdef AMIGA -+-/* run(): execute an AmigaDOS command via SystemTagList() with NIL: I/O -+- * Runs synchronously so binkd waits for completion (needed for srifreq -+- * to create .rsp before parse_response). NIL: I/O prevents CLI freezing -+- * Error output goes to a temporary file for logging on failure */ -+-int run(char *cmd) -+-{ -+- /* All declarations at the top for C89/ADE GCC 2.95 compatibility */ -+- BPTR nil_in; -+- char errfile[MAXPATHLEN]; -+- BPTR err_out = 0; -+- int rc = 0; -+- char cmd_copy[MAXPATHLEN]; -+- char *cmd_start; -+- char *cmd_end; -+- BPTR lock; -+- struct TagItem exec_tags[5]; -+- BPTR errfile_ptr; -+- char buf[512]; -+- int len; -+- -+- /* Open NIL: for input/output */ -+- nil_in = Open("NIL:", MODE_OLDFILE); -+- -+- /* Create temporary error file in current directory */ -+- snprintf(errfile, sizeof(errfile), "binkd_err_%ld.txt", (long)time(NULL)); -+- err_out = Open(errfile, MODE_NEWFILE); -+- if (err_out == 0) -+- { -+- Log(2, "cannot create error file %s, using NIL: for error output", errfile); -+- } -+- -+- /* Extract the command (first word) to check if it exists */ -+- strncpy(cmd_copy, cmd, sizeof(cmd_copy) - 1); -+- cmd_copy[sizeof(cmd_copy) - 1] = '\0'; -+- cmd_start = cmd_copy; /* Work on copy, never modify original cmd */ -+- -+- /* Skip leading whitespace */ -+- while (*cmd_start && (*cmd_start == ' ' || *cmd_start == '\t')) -+- cmd_start++; -+- -+- /* Find end of command (first space or end) */ -+- cmd_end = cmd_start; -+- while (*cmd_end && *cmd_end != ' ' && *cmd_end != '\t') -+- cmd_end++; -+- *cmd_end = '\0'; -+- -+- /* Check if command exists */ -+- lock = Lock((STRPTR)cmd_start, SHARED_LOCK); -+- if (lock == 0) -+- { -+- Log(2, "command not found, skipping: '%s'", cmd_start); -+- Close(nil_in); -+- if (err_out) -+- Close(err_out); -+- DeleteFile((STRPTR)errfile); -+- return 0; -+- } -+- UnLock(lock); -+- -+- Log(3, "executing '%s'", cmd); -+- -+- /* Set up tags with NIL: input and error file output. -+- * Use NP_* (New Process) tags instead of SYS_* to avoid sharing -+- * file handles with parent process - prevents stderr from being -+- * closed when child exits. */ -+- exec_tags[0].ti_Tag = NP_Input; -+- exec_tags[0].ti_Data = (ULONG)nil_in; -+- exec_tags[1].ti_Tag = NP_Output; -+- exec_tags[1].ti_Data = (ULONG)nil_in; -+- exec_tags[2].ti_Tag = NP_Error; -+- exec_tags[2].ti_Data = (ULONG)err_out; -+- exec_tags[3].ti_Tag = NP_Synchronous; -+- exec_tags[3].ti_Data = TRUE; -+- exec_tags[4].ti_Tag = TAG_DONE; -+- exec_tags[4].ti_Data = 0; -+- -+- rc = SystemTagList((STRPTR)cmd, exec_tags); -+- -+- /* Close handles */ -+- Close(nil_in); -+- if (err_out) -+- Close(err_out); -+- -+- /* Log error output if command failed */ -+- if (rc != 0) -+- { -+- errfile_ptr = Open(errfile, MODE_OLDFILE); -+- if (errfile_ptr) -+- { -+- Log(2, "command failed with rc=%d, output:", rc); -+- while ((len = Read(errfile_ptr, buf, sizeof(buf) - 1)) > 0) -+- { -+- buf[len] = '\0'; -+- Log(2, "%s", buf); -+- } -+- Close(errfile_ptr); -+- } -+- } -+- -+- DeleteFile((STRPTR)errfile); -+- return rc; -+-} -+- -+-/* run3(): pipe/tunnel not supported on AmigaOS without ixemul. */ -+-int run3(const char *cmd, int *in, int *out, int *err) -+-{ -+- (void)cmd; (void)in; (void)out; (void)err; -+- Log(1, "run3: pipe connections not supported on Amiga"); -+- return -1; -+-} -+-#endif /* AMIGA */ -+- -+-#ifndef AMIGA -+ int run (char *cmd) -+ { -+ int rc=-1; -+@@ -234,7 +111,6 @@ -+ #endif -+ return rc; -+ } -+-#endif /* !AMIGA */ -+ -+ #ifdef __MINGW32__ -+ static int set_cloexec(int fd) -+@@ -260,7 +136,6 @@ -+ } -+ #endif -+ -+-#ifndef AMIGA -+ int run3 (const char *cmd, int *in, int *out, int *err) -+ { -+ int pid; -+@@ -287,14 +162,6 @@ -+ } -+ -+ #ifdef HAVE_FORK -+-#ifdef AMIGA -+- /* Pipe tunneling not supported on AmigaOS without fork() */ -+- Log(1, "run3: pipe/tunnel not supported on Amiga: %s", cmd); -+- if (in) close(pin[1]), close(pin[0]); -+- if (out) close(pout[1]), close(pout[0]); -+- if (err) close(perr[1]), close(perr[0]); -+- return -1; -+-#else -+ pid = fork(); -+ if (pid == -1) -+ { -+@@ -327,11 +194,7 @@ -+ if (strpbrk(cmd, SHELL_META)) -+ { -+ shell = SHELL; -+-#ifdef AMIGA -+- execl(shell, shell, cmd, (char *)NULL); -+-#else -+ execl(shell, shell, SHELLOPT, cmd, (char *)NULL); -+-#endif -+ } -+ else -+ { -+@@ -369,7 +232,6 @@ -+ *err = perr[0]; -+ close(perr[1]); -+ } -+-#endif /* !AMIGA */ -+ #else -+ -+ /* redirect stdin/stdout/stderr takes effect for all threads */ -+@@ -474,4 +336,3 @@ -+ return pid; -+ } -+ -+-#endif /* !AMIGA */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/sem.h binkd_pgul/sem.h -+--- binkd/sem.h 2026-04-26 10:09:54.148733026 +0100 -++++ binkd_pgul/sem.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -34,14 +34,6 @@ -+ #include -+ typedef struct SignalSemaphore MUTEXSEM; -+ -+-#ifdef AMIGA -+-typedef struct -+-{ -+- struct Task *waiter; -+- ULONG sigbit; -+-} EVENTSEM; -+-#endif -+- -+ #elif defined(WITH_PTHREADS) -+ -+ #include -+@@ -81,27 +73,25 @@ -+ * Initialise Event Semaphores. -+ */ -+ -+-#ifdef AMIGA -+-int _InitEventSem (EVENTSEM *); -++int _InitEventSem (void *); -+ -+ /* -+ * Post Semaphore. -+ */ -+ -+-int _PostSem (EVENTSEM *); -++int _PostSem (void *); -+ -+ /* -+ * Wait Semaphore. -+ */ -+ -+-int _WaitSem (EVENTSEM *, int); -++int _WaitSem (void *, int); -+ -+ /* -+ * Clean Event Semaphores. -+ */ -+ -+-int _CleanEventSem (EVENTSEM *); -+-#endif -++int _CleanEventSem (void *); -+ -+ #if defined(WITH_PTHREADS) -+ #define InitSem(sem) pthread_mutex_init(sem, NULL) -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/server.c binkd_pgul/server.c -+--- binkd/server.c 2026-04-26 10:30:03.538324924 +0100 -++++ binkd_pgul/server.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -23,10 +23,6 @@ -+ #include -+ #endif -+ -+-#ifdef AMIGA -+-#include "amiga/bsdsock.h" -+-#endif -+- -+ #include "sys.h" -+ #include "iphdr.h" -+ #include "readcfg.h" -+@@ -43,10 +39,6 @@ -+ #endif -+ #include "rfc2553.h" -+ -+-#if defined(HAVE_THREADS) || defined(AMIGA) -+-extern EVENTSEM eothread; -+-#endif -+- -+ int n_servers = 0; -+ int ext_rand = 0; -+ -+@@ -61,8 +53,7 @@ -+ void *cperl; -+ #endif -+ -+-/* Prevent shared socket closure */ -+-#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(AMIGA) && !defined(DEBUGCHILD) -++#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(DEBUGCHILD) -+ int curfd; -+ pidcmgr = 0; -+ for (curfd=0; curfdai_addr, ai->ai_addrlen) != 0) -+ { -+-#ifdef AMIGA -+- /* bsdsocket may hold the port briefly after socket close. Retry */ -+- int bind_retries = 6; -+- -+- while (bind(sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) -+- { -+- if (--bind_retries == 0) -+- { -+- Log(1, "servmgr bind(): %s", TCPERR()); -+- soclose(sockfd[sockfd_used]); -+- return -1; -+- } -+- -+- Log(2, "servmgr bind(): %s, retry in 2s...", TCPERR()); -+- sleep(2); -+- } -+-#else -+- if (bind (sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) -+- { -+- Log(1, "servmgr bind(): %s", TCPERR ()); -+- soclose(sockfd[sockfd_used]); -+- return -1; -+- } -+-#endif -++ Log(1, "servmgr bind(): %s", TCPERR ()); -++ soclose(sockfd[sockfd_used]); -++ return -1; -+ } -+ if (listen (sockfd[sockfd_used], 5) != 0) -+ { -+@@ -201,12 +168,6 @@ -+ -+ setproctitle ("server manager (listen %s)", config->listen.first->port); -+ -+- /* Save rescan_delay locally. checkcfg() may free 'config' (old config -+- * is released when usageCount reaches 0 after reload), so we must not -+- * access config->rescan_delay inside the loop after a reload */ -+- { -+- int rescan = config->rescan_delay; -+- -+ for (;;) -+ { -+ struct timeval tv; -+@@ -222,7 +183,7 @@ -+ maxfd = sockfd[curfd]; -+ } -+ tv.tv_usec = 0; -+- tv.tv_sec = rescan; -++ tv.tv_sec = CHECKCFG_INTERVAL; -+ unblocksig(); -+ check_child(&n_servers); -+ n = select(maxfd+1, &r, NULL, NULL, &tv); -+@@ -231,20 +192,8 @@ -+ { case 0: /* timeout */ -+ if (checkcfg()) -+ { -+- /* config may have been freed by checkcfg() — read rescan from -+- * the new current_config before returning for restart */ -+- { -+- BINKD_CONFIG *nc = lock_current_config(); -+- if (nc) -+- { -+- rescan = nc->rescan_delay; -+- unlock_config_structure(nc, 0); -+- } -+- } -+- -+ for (curfd=0; curfdrescan_delay; -+- unlock_config_structure(nc, 0); -+- } -+- } -+- -+ for (curfd=0; curfd -+-#endif -+- -+ /* -+ * Listens... Than calls protocol() -+ */ -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/sys.h binkd_pgul/sys.h -+--- binkd/sys.h 2026-04-26 08:48:04.281489117 +0100 -++++ binkd_pgul/sys.h 2026-04-16 17:49:00.000000000 +0100 -+@@ -22,28 +22,8 @@ -+ #ifdef HAVE_STDINT_H -+ #include -+ #endif -+-#ifdef AMIGA -+- /* Include Amiga exec proto for Delay() function */ -+- #include -+-#endif -+ #ifdef HAVE_UNISTD_H -+ #include -+- /* Undefine conflicting unistd.h macros for AMIGA */ -+- #ifdef AMIGA -+- #ifdef getpid -+- #undef getpid -+- #endif -+- -+- #ifdef sleep -+- #undef sleep -+- #endif -+- -+- /* Redefine with our Amiga implementations */ -+- #define getpid() ((int)(ULONG)FindTask(NULL)) -+- -+- #define sleep(s) Delay((ULONG)((s) * 50)) -+- -+- #endif /* AMIGA */ -+ #endif -+ #ifdef HAVE_IO_H -+ #include -+@@ -125,11 +105,6 @@ -+ #define PID() mypid -+ #endif -+ -+-#ifdef HAVE_FORK -+- #include /* Needed for SIG_BLOCK/SIG_UNBLOCK and WIFEXITED/WEXITSTATUS */ -+- #include -+-#endif -+- -+ #if defined(HAVE_FORK) && defined(HAVE_SIGPROCMASK) && defined(HAVE_WAITPID) && defined(SIG_BLOCK) -+ void switchsignal(int how); -+ #define blocksig() switchsignal(SIG_BLOCK) -+@@ -317,16 +292,8 @@ -+ -+ #ifndef PRIdMAX -+ #define PRIdMAX "ld" -+-#endif -+- -+-#ifndef PRIuMAX -+-#ifdef AMIGA -+-/* On AmigaOS m68k, uintmax_t is long long unsigned int */ -+-#define PRIuMAX "llu" -+-#else -+ #define PRIuMAX "lu" -+ #endif -+-#endif -+ -+ #ifndef HAVE_STRTOUMAX -+ #define strtoumax(ptr, endptr, base) strtoul(ptr, endptr, base) -+diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/tools.c binkd_pgul/tools.c -+--- binkd/tools.c 2026-04-25 23:49:26.267090428 +0100 -++++ binkd_pgul/tools.c 2026-04-16 17:49:00.000000000 +0100 -+@@ -22,10 +22,6 @@ -+ #include -+ #endif -+ -+-#ifdef AMIGA -+-#include "amiga/bsdsock.h" -+-#endif -+- -+ #include "sys.h" -+ #include "readcfg.h" -+ #include "common.h" -+@@ -42,12 +38,6 @@ -+ #include "nt/w32tools.h" -+ #endif -+ -+-#if defined(HAVE_THREADS) || defined(AMIGA) -+-extern MUTEXSEM lsem; -+-#endif -+- -+-extern void vLog (int lev, char *s, va_list ap); -+- -+ /* -+ * We can call Log() even when we have no config ready. So, we must keep -+ * internal variables which will be updated when config is loaded -+@@ -300,10 +290,6 @@ -+ char buf[1024]; -+ int ok = 1; -+ -+-#ifdef AMIGA -+- static int need_newline = 0; -+-#endif -+- -+ /* make string in buffer */ -+ vsnprintf(buf, sizeof(buf), s, ap); -+ /* do perl hooks */ -+@@ -327,20 +313,8 @@ -+ if (lev <= current_conlog && !inetd_flag) -+ { -+ LockSem(&lsem); -+-#ifdef AMIGA -+- /* AmigaOS: go to new line for status messages to avoid overwriting */ -+- if (lev < 0 && need_newline) -+- { -+- need_newline = 0; -+- } -+- fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, -+- tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : "\r"); -+- if (lev >= 0) -+- need_newline = 1; -+-#else -+ fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, -+ tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : ""); -+-#endif -+ fflush (stderr); -+ ReleaseSem(&lsem); -+ if (lev < 0) -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/changes.diff binkd/changes.diff ---- binkd_pgul/changes.diff 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/changes.diff 2026-04-26 16:16:05.399512990 +0100 -@@ -0,0 +1,11295 @@ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/bsdsock.c binkd/amiga/bsdsock.c -+--- binkd_pgul/amiga/bsdsock.c 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/bsdsock.c 2026-04-26 11:01:10.438650436 +0100 -+@@ -0,0 +1,79 @@ -++/* -++ * bsdsock.c -- bsdsocket.library lifecycle for AmigaOS 3 -++ * -++ * bsdsock.c is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#include -++#include -++#include -++#include -++#include -++#include -++ -++/* Linker-compatibility global. Never used at runtime */ -++struct Library *SocketBase = NULL; -++ -++/* Suppress conflicting C prototypes from clib/bsdsocket_protos.h */ -++#ifndef CLIB_BSDSOCKET_PROTOS_H -++#define CLIB_BSDSOCKET_PROTOS_H -++#endif -++ -++#include -++#include -++ -++extern void Log(int lev, const char *s, ...); -++ -++/* _amiga_get_socket_base -- returns bsdsocket.library handle for calling task */ -++struct Library *_amiga_get_socket_base(void) -++{ -++ return (struct Library *)FindTask(NULL)->tc_UserData; -++} -++ -++int amiga_sock_init(void) -++{ -++ struct Task *me = FindTask(NULL); -++ struct Library *base; -++ -++ if (me->tc_UserData) -++ return 0; /* already open for this task */ -++ -++ base = OpenLibrary("bsdsocket.library", 0UL); -++ -++ if (!base) -++ { -++ fprintf(stderr, "amiga_sock_init: cannot open bsdsocket.library\n"); -++ return -1; -++ } -++ -++ /* Store in tc_UserData and global SocketBase */ -++ me->tc_UserData = (APTR)base; -++ SocketBase = base; -++ -++ /* Link the per-task errno to the TCP stack. */ -++ SetErrnoPtr(&errno, (LONG)sizeof(errno)); -++ -++ return 0; -++} -++ -++void amiga_sock_cleanup(void) -++{ -++ struct Task *me = FindTask(NULL); -++ struct Library *base = (struct Library *)me->tc_UserData; -++ -++ if (base) -++ { -++ me->tc_UserData = NULL; -++ SocketBase = NULL; -++ CloseLibrary(base); -++ } -++} -++ -++int amiga_child_sock_init(void) -++{ -++ /* Child inherits tc_UserData = NULL, opens new handle */ -++ return amiga_sock_init(); -++} -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/bsdsock.h binkd/amiga/bsdsock.h -+--- binkd_pgul/amiga/bsdsock.h 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/bsdsock.h 2026-04-26 10:49:27.706200054 +0100 -+@@ -0,0 +1,155 @@ -++/* -++ * bsdsock.h -- bsdsocket.library init and POSIX compat shims for AmigaOS 3 -++ * -++ * bsdsock.h is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifndef _AMIGA_BSDSOCK_H -++#define _AMIGA_BSDSOCK_H -++ -++#ifdef AMIGA -++ -++#include -++#include -++#include -++#include -++ -++/* Suppress conflicting C prototypes from roadshow */ -++#ifndef CLIB_BSDSOCKET_PROTOS_H -++#define CLIB_BSDSOCKET_PROTOS_H -++#endif -++ -++/* Undefine MCLBYTES/MCLSHIFT before roadshow headers */ -++#ifdef MCLBYTES -++#undef MCLBYTES -++#endif -++#ifdef MCLSHIFT -++#undef MCLSHIFT -++#endif -++ -++/* Roadshow SDK network headers */ -++#include -++#include -++#include "compat_netinet_in.h" -++#include -++#include -++#include /* inline/bsdsocket.h, no clib protos */ -++ -++/* Undefine conflicting unistd.h macros */ -++#ifdef gethostid -++#undef gethostid -++#endif -++#ifdef getdtablesize -++#undef getdtablesize -++#endif -++#ifdef gethostname -++#undef gethostname -++#endif -++ -++/* Per-task SocketBase override */ -++struct Library *_amiga_get_socket_base(void); -++ -++#ifdef SocketBase -++#undef SocketBase -++#endif -++#define SocketBase _amiga_get_socket_base() -++ -++/* Roadshow socket-specific errno values */ -++#include -++ -++#define BSDSOCK_HAS_TIMEVAL 1 -++ -++/* Socket-specific errno values from roadshow sys/errno.h */ -++#ifndef ENOTSOCK -++#define ENOTSOCK 38 /* Socket operation on non-socket */ -++#endif -++#ifndef EOPNOTSUPP -++#define EOPNOTSUPP 45 /* Operation not supported on socket */ -++#endif -++#ifndef ECONNREFUSED -++#define ECONNREFUSED 61 /* Connection refused */ -++#endif -++#ifndef ETIMEDOUT -++#define ETIMEDOUT 60 /* Connection timed out */ -++#endif -++#ifndef ECONNRESET -++#define ECONNRESET 54 /* Connection reset by peer */ -++#endif -++#ifndef EHOSTUNREACH -++#define EHOSTUNREACH 65 /* No route to host */ -++#endif -++ -++#include -++ -++/* sockaddr_storage fallback for roadshow */ -++#ifndef HAVE_SOCKADDR_STORAGE -++#ifndef sockaddr_storage -++struct sockaddr_storage -++{ -++ unsigned short ss_family; -++ char __ss_pad[22]; /* enough for IPv4 */ -++}; -++#endif -++#define HAVE_SOCKADDR_STORAGE 1 -++#endif -++ -++/* Library base functions */ -++int amiga_sock_init(void); -++void amiga_sock_cleanup(void); -++int amiga_child_sock_init(void); -++ -++/* getpid() is defined in sys.h for AMIGA */ -++/* amiga_sleep and sleep are defined in sys.h for AMIGA */ -++ -++/* select() -> WaitSelect() wrapper with Ctrl+C handling */ -++#ifndef AMIGA_SELECT_DEFINED -++#define AMIGA_SELECT_DEFINED -++ -++/* Forward-declare binkd_exit */ -++extern int binkd_exit; -++ -++/* Forward-declare Log to avoid implicit declaration warning */ -++extern void Log(int lev, char *s, ...); -++ -++static int amiga_select_wrap(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) -++{ -++ ULONG sigmask = SIGBREAKF_CTRL_C; -++ int rc = WaitSelect(nfds, readfds, writefds, exceptfds, timeout, &sigmask); -++ -++ /* Ctrl+C should break blocked select() loops immediately. */ -++ if ((sigmask & SIGBREAKF_CTRL_C) != 0) -++ { -++ Log(1, "Ctrl+C detected in WaitSelect, setting binkd_exit=1"); -++ binkd_exit = 1; -++ errno = EINTR; -++ return -1; -++ } -++ -++ return rc; -++} -++ -++#define select(n, r, w, e, t) amiga_select_wrap((n), (r), (w), (e), (t)) -++ -++#endif /* AMIGA_SELECT_DEFINED */ -++ -++/* FIONBIO via IoctlSocket */ -++#ifndef FIONBIO -++#define FIONBIO 0x8004667E -++#endif -++#ifndef ioctl -++#define ioctl(s, req, arg) IoctlSocket((s), (req), (char *)(arg)) -++#endif -++ -++/* inet_ntoa -> Inet_NtoA (Amiga only) */ -++#ifdef AMIGA -++#ifdef inet_ntoa -++#undef inet_ntoa -++#endif -++#define inet_ntoa(a) Inet_NtoA(a) -++#endif -++ -++#endif /* AMIGA */ -++#endif /* _AMIGA_BSDSOCK_H */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/compat_netinet_in.h binkd/amiga/compat_netinet_in.h -+--- binkd_pgul/amiga/compat_netinet_in.h 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/compat_netinet_in.h 2026-04-26 09:53:12.337417283 +0100 -+@@ -0,0 +1,19 @@ -++/* -++ * compat_netinet_in.h -- Wrapper for netinet/in.h typedef clash -++ * -++ * compat_netinet_in.h is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifndef _AMIGA_COMPAT_NETINET_IN_H -++#define _AMIGA_COMPAT_NETINET_IN_H -++ -++#ifdef __GNUC__ -++#include_next -++#else -++#include -++#endif -++ -++#endif /* _AMIGA_COMPAT_NETINET_IN_H */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/dirent.c binkd/amiga/dirent.c -+--- binkd_pgul/amiga/dirent.c 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/dirent.c 2026-04-26 11:40:07.539429919 +0100 -+@@ -0,0 +1,137 @@ -++/* -++ * dirent.c -- POSIX directory scanning for AmigaOS 3 -++ * -++ * dirent.c is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifdef AMIGA -++ -++#include -++#include -++#include -++#include -++#include -++#include -++ -++#include "amiga/dirent.h" -++ -++/* opendir -- locks directory and allocates state for readdir() */ -++DIR *opendir(const char *path) -++{ -++ DIR *dir; -++ -++ if (!path) -++ { -++ errno = EINVAL; -++ return NULL; -++ } -++ -++ dir = (DIR *)AllocMem((LONG)sizeof(DIR), MEMF_CLEAR); -++ -++ if (!dir) -++ { -++ errno = ENOMEM; -++ return NULL; -++ } -++ -++ dir->fib = (struct FileInfoBlock *)AllocMem((LONG)sizeof(struct FileInfoBlock), MEMF_CLEAR); -++ -++ if (!dir->fib) -++ { -++ FreeMem(dir, (LONG)sizeof(DIR)); -++ errno = ENOMEM; -++ return NULL; -++ } -++ -++ dir->lock = Lock((STRPTR)path, ACCESS_READ); -++ -++ if (!dir->lock) -++ { -++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -++ FreeMem(dir, (LONG)sizeof(DIR)); -++ errno = ENOENT; -++ return NULL; -++ } -++ -++ /* Examine the directory itself; this positions FIB for ExNext() */ -++ if (!Examine(dir->lock, dir->fib)) -++ { -++ UnLock(dir->lock); -++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -++ FreeMem(dir, (LONG)sizeof(DIR)); -++ errno = EACCES; -++ return NULL; -++ } -++ -++ /* fib_DirEntryType > 0 means this IS a directory */ -++ if (dir->fib->fib_DirEntryType <= 0) -++ { -++ UnLock(dir->lock); -++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -++ FreeMem(dir, (LONG)sizeof(DIR)); -++ errno = ENOTDIR; -++ return NULL; -++ } -++ -++ dir->first = 1; /* ExNext() has not been called yet */ -++ -++ return dir; -++} -++ -++/* readdir -- advances to next directory entry */ -++struct dirent *readdir(DIR *dir) -++{ -++ LONG dos_rc; -++ LONG dos_err; -++ -++ if (!dir) -++ { -++ errno = EINVAL; -++ return NULL; -++ } -++ -++ /* ExNext() advances past the last entry returned by Examine/ExNext */ -++ dos_rc = ExNext(dir->lock, dir->fib); -++ -++ if (!dos_rc) -++ { -++ dos_err = IoErr(); -++ -++ if (dos_err == ERROR_NO_MORE_ENTRIES) -++ return NULL; -++ -++ errno = EIO; -++ return NULL; -++ } -++ -++ /* Copy name into the entry buffer */ -++ strncpy(dir->entry.d_name, dir->fib->fib_FileName, AMIGA_NAME_MAX - 1); -++ dir->entry.d_name[AMIGA_NAME_MAX - 1] = '\0'; -++ dir->entry.d_ino = 0; -++ -++ return &dir->entry; -++} -++ -++/* closedir -- releases all resources associated with dir */ -++int closedir(DIR *dir) -++{ -++ if (!dir) -++ { -++ errno = EINVAL; -++ return -1; -++ } -++ -++ if (dir->lock) -++ UnLock(dir->lock); -++ -++ if (dir->fib) -++ FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -++ -++ FreeMem(dir, (LONG)sizeof(DIR)); -++ return 0; -++} -++ -++#endif /* AMIGA */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/dirent.h binkd/amiga/dirent.h -+--- binkd_pgul/amiga/dirent.h 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/dirent.h 2026-04-26 09:53:13.628932082 +0100 -+@@ -0,0 +1,53 @@ -++/* -++ * dirent.h -- POSIX directory scanning for AmigaOS 3 -++ * -++ * dirent.h is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifndef _AMIGA_DIRENT_H -++#define _AMIGA_DIRENT_H -++ -++#ifdef AMIGA -++ -++#include -++#include -++ -++/* Maximum name length: AmigaDOS allows 107 characters */ -++#define AMIGA_NAME_MAX 108 -++ -++struct dirent -++{ -++ unsigned long d_ino; /* inode -- always 0 on AmigaDOS */ -++ char d_name[AMIGA_NAME_MAX]; /* null-terminated file name */ -++}; -++ -++/* struct utimbuf for AmigaOS 3 without ixemul */ -++#ifndef _AMIGA_UTIMBUF_DEFINED -++#define _AMIGA_UTIMBUF_DEFINED -++ -++struct utimbuf -++{ -++ long actime; /* access time (unused by SetFileDate) */ -++ long modtime; /* modification time (POSIX time_t) */ -++}; -++ -++int utime(const char *path, const struct utimbuf *times); -++#endif -++ -++typedef struct _amiga_dir -++{ -++ BPTR lock; /* directory lock */ -++ struct FileInfoBlock *fib; /* reusable FileInfoBlock */ -++ int first; /* flag: first call not yet */ -++ struct dirent entry; /* storage returned to caller */ -++} DIR; -++ -++DIR *opendir(const char *path); -++struct dirent *readdir(DIR *dir); -++int closedir(DIR *dir); -++ -++#endif /* AMIGA */ -++#endif /* _AMIGA_DIRENT_H */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop.c binkd/amiga/evloop.c -+--- binkd_pgul/amiga/evloop.c 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/evloop.c 2026-04-26 11:46:47.344533199 +0100 -+@@ -0,0 +1,509 @@ -++/* -++ * evloop.c -- non-blocking event loop for AmigaOS 3 -++ * -++ * evloop.c is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++/* Suppress clib bsdsocket prototypes before any socket header */ -++#ifndef CLIB_BSDSOCKET_PROTOS_H -++#define CLIB_BSDSOCKET_PROTOS_H -++#endif -++ -++#include -++#include -++#include -++ -++#include -++#include -++#include -++ -++#include "sys.h" -++#include "readcfg.h" -++#include "common.h" -++#include "tools.h" -++#include "protocol.h" -++#include "sem.h" -++#include "server.h" -++#include "amiga/bsdsock.h" -++#include "amiga/evloop.h" -++#include "amiga/evloop_int.h" -++#include "amiga/proto_amiga.h" -++ -++/* Externals */ -++extern SOCKET sockfd[MAX_LISTENSOCK]; -++extern int sockfd_used; -++extern int binkd_exit; -++extern int server_flag, client_flag; -++ -++/* Session table (shared with sock.c and session.c) */ -++sess_t *sessions = NULL; -++int max_sessions = 0; -++ -++/* -++ * calc_max_sessions -- Compute session slot count from config + flags -++ * Shared by init and config-reload paths -++ */ -++static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) -++{ -++ int servers = config->max_servers; -++ int clients = config->max_clients; -++ int total; -++ -++ if (servers == 0 && clients == 0) -++ { -++ Log(5, "DEBUG: Using default 2 slots (no config found)"); -++ return 2; -++ } -++ -++ Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); -++ -++ if (srv_flag && servers < 1) -++ servers = 1; -++ -++ if (cli_flag && clients < 1) -++ clients = 1; -++ -++ total = servers + clients; -++ -++ if (total < 2) -++ total = 2; -++ -++ Log(5, "DEBUG: Calculated max_sessions=%d", total); -++ -++ return total; -++} -++ -++/* init_session_table -- Allocate and zero-initialise the session array */ -++static int init_session_table(int slots) -++{ -++ int i; -++ -++ sessions = calloc(slots, sizeof(sess_t)); -++ -++ if (!sessions) -++ { -++ Log(1, "Failed to allocate session table"); -++ return 0; -++ } -++ -++ for (i = 0; i < slots; i++) -++ { -++ memset(&sessions[i], 0, sizeof(sess_t)); -++ memset(&sessions[i].state, 0, sizeof(STATE)); -++ sessions[i].fd = INVALID_SOCKET; -++ sessions[i].phase = SESS_FREE; -++ } -++ -++ return 1; -++} -++ -++/* -++ * build_fdsets -- Populate r/w fd_sets from listen sockets and sessions -++ * Returns the highest fd seen (maxfd) -++ */ -++static int build_fdsets(fd_set *r, fd_set *w) -++{ -++ int i, maxfd = 0; -++ -++ FD_ZERO(r); -++ FD_ZERO(w); -++ -++ /* server side: listen sockets */ -++ for (i = 0; i < sockfd_used; i++) -++ { -++ if (sockfd[i] != INVALID_SOCKET) -++ { -++ FD_SET(sockfd[i], r); -++ -++ if ((int)sockfd[i] > maxfd) -++ maxfd = (int)sockfd[i]; -++ } -++ } -++ -++ /* client + server sessions */ -++ for (i = 0; i < max_sessions; i++) -++ { -++ sess_t *s = &sessions[i]; -++ -++ if (s->phase == SESS_FREE || s->fd == INVALID_SOCKET) -++ continue; -++ -++ if ((int)s->fd > maxfd) -++ maxfd = (int)s->fd; -++ -++ if (s->phase == SESS_CONNECTING) -++ { -++ /* client: waiting for non-blocking connect() */ -++ FD_SET(s->fd, w); -++ } -++ else -++ { -++ /* Server or established client session */ -++ FD_SET(s->fd, r); -++ -++ if (s->state.msgs || s->state.oleft || s->state.send_eof || (s->state.out.f && !s->state.off_req_sent && !s->state.waiting_for_GOT)) -++ FD_SET(s->fd, w); -++ } -++ } -++ -++ Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); -++ return maxfd; -++} -++ -++/* -++ * handle_server_accept -- Accept new inbound connections on all listen fds -++ * Returns 0 normally, -1 if binkd_exit was set during accept -++ */ -++static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) -++{ -++ int i; -++ -++ Log(5, "DEBUG: Before accept loop"); -++ -++ for (i = 0; i < sockfd_used; i++) -++ { -++ if (FD_ISSET(sockfd[i], r)) -++ do_accept(sockfd[i], config); -++ -++ if (binkd_exit) -++ { -++ Log(5, "DEBUG: binkd_exit during accept loop"); -++ return -1; -++ } -++ } -++ -++ Log(5, "DEBUG: After accept loop"); -++ -++ return 0; -++} -++ -++/* -++ * advance_sessions -- Step every active session (server + client) -++ * Returns the number of non-free sessions processed -++ */ -++static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) -++{ -++ int i, active = 0; -++ -++ Log(5, "DEBUG: Before advance sessions"); -++ -++ for (i = 0; i < max_sessions; i++) -++ { -++ sess_t *s = &sessions[i]; -++ -++ Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); -++ -++ if (s->phase == SESS_FREE) -++ continue; -++ -++ active++; -++ -++ if (s->phase == SESS_CONNECTING) -++ { -++ /* client: Complete the non-blocking connect */ -++ if (FD_ISSET(s->fd, w)) -++ check_connect(i, config); -++ } -++ else -++ { -++ int rd = FD_ISSET(s->fd, r); -++ int wr = FD_ISSET(s->fd, w); -++ -++ /* Always step: protocol must advance internal state even -++ * when WaitSelect reports no activity (e.g. second batch -++ * EOB send, TCP FIN detection after remote closes) */ -++ do_session_step(i, rd, wr, config); -++ } -++ -++ if (binkd_exit) -++ break; -++ } -++ -++ return active; -++} -++ -++/* -++ * handle_config_reload -- Resize session table and reopen listen sockets -++ * Returns 1 if the caller should break out of the main loop, 0 otherwise -++ */ -++static int handle_config_reload(BINKD_CONFIG **config, int srv_flag, int cli_flag) -++{ -++ int i, new_max; -++ BINKD_CONFIG *nc = lock_current_config(); -++ -++ if (nc) -++ { -++ new_max = calc_max_sessions(nc, srv_flag, cli_flag); -++ -++ if (new_max != max_sessions) -++ { -++ sess_t *ns = realloc(sessions, new_max * sizeof(sess_t)); -++ -++ if (ns) -++ { -++ for (i = max_sessions; i < new_max; i++) -++ { -++ memset(&ns[i], 0, sizeof(sess_t)); -++ memset(&ns[i].state, 0, sizeof(STATE)); -++ ns[i].fd = INVALID_SOCKET; -++ ns[i].phase = SESS_FREE; -++ } -++ -++ sessions = ns; -++ max_sessions = new_max; -++ -++ Log(4, "Session table resized to %d slots", max_sessions); -++ } -++ else -++ { -++ Log(1, "Failed to resize session table, keeping current size"); -++ } -++ } -++ -++ unlock_config_structure(nc, 0); -++ } -++ -++ close_listen_sockets(); -++ *config = lock_current_config(); -++ -++ if (srv_flag && open_listen_sockets(*config) < 0) -++ { -++ unlock_config_structure(*config, 0); -++ return 1; /* fatal — break main loop */ -++ } -++ -++ unlock_config_structure(*config, 0); -++ *config = lock_current_config(); -++ return 0; -++} -++ -++/* evloop_cleanup -- Close sessions and free resources on exit */ -++static void evloop_cleanup(BINKD_CONFIG *config, int config_locked) -++{ -++ int i; -++ -++ if (config_locked) -++ unlock_config_structure(config, 0); -++ -++ if (sessions) -++ { -++ for (i = 0; i < max_sessions; i++) -++ { -++ if (sessions[i].phase == SESS_RUNNING) -++ { -++ amiga_proto_close(&sessions[i].state, config, 0); -++ -++ if (sessions[i].inbound) -++ n_servers--; -++ else -++ n_clients--; -++ } -++ else if (sessions[i].phase == SESS_CONNECTING) -++ { -++ n_clients--; -++ } -++ sess_free(i); -++ } -++ -++ free(sessions); -++ sessions = NULL; -++ } -++ -++ close_listen_sockets(); -++ amiga_sock_cleanup(); -++ Log(4, "evloop done"); -++} -++ -++/* amiga_evloop_run -- Entry point: init, then main WaitSelect() loop */ -++void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) -++{ -++ int config_locked = 0; -++ time_t last_rescan = 0; -++ time_t now; -++ fd_set r, w; -++ struct timeval tv; -++ int n, maxfd; -++ int active_sessions = 0; -++ static int idle_loops = 0; -++ -++ /* Sync globals so try_outbound() and friends see the correct flags */ -++ server_flag = srv_flag; -++ client_flag = cli_flag; -++ -++ sockfd_used = 0; -++ srand((unsigned int)time(NULL)); -++ -++ Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); -++ Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); -++ -++ /* Initialise session table */ -++ max_sessions = calc_max_sessions(config, server_flag, client_flag); -++ -++ if (max_sessions < 2) -++ { -++ Log(2, "WARNING: max_sessions=%d is too low, forcing to 2", max_sessions); -++ max_sessions = 2; -++ } -++ -++ Log(4, "evloop start (AmigaOS 3, WaitSelect, %d slots)", max_sessions); -++ -++ if (!init_session_table(max_sessions)) -++ return; -++ -++ /* server: Open listen sockets */ -++ if (server_flag && open_listen_sockets(config) < 0) -++ { -++ Log(0, "evloop: cannot open listen sockets"); -++ free(sessions); -++ sessions = NULL; -++ return; -++ } -++ -++ Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); -++ -++ /* Initial outbound attempt before waiting (important for poll -p mode) */ -++ Log(5, "DEBUG: Initial try_outbound before main loop"); -++ try_outbound(config); -++ last_rescan = time(NULL); /* Reset timer since we just did an attempt */ -++ -++ /* ===== Main loop ===== */ -++ for (;;) -++ { -++ if (binkd_exit) -++ { -++ Log(1, "binkd_exit detected at loop start, exiting"); -++ break; -++ } -++ -++ /* Build fd_sets */ -++ Log(5, "DEBUG: Building fd_sets"); -++ maxfd = build_fdsets(&r, &w); -++ -++ tv.tv_sec = 1; -++ tv.tv_usec = 0L; -++ -++ /* WaitSelect() with nfds>0 but empty fd_sets causes guru #80000006 :/ -++ * Use select(0,...) as a pure sleep when no sockets are active */ -++ if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) -++ maxfd = 1; -++ -++ Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); -++ -++ if (maxfd == 0) -++ { -++ /* No sockets yet -- use Delay() instead of select(0,...) -++ * as WaitSelect with nfds=0 can block indefinitely on AmigaOS */ -++ Delay(50); /* 1 second = 50 ticks at 50Hz PAL */ -++ n = 0; /* simulate timeout */ -++ } -++ else -++ n = select(maxfd + 1, &r, &w, NULL, &tv); -++ -++ Log(5, "DEBUG: select() returned n=%d", n); -++ -++ if (binkd_exit) -++ { -++ Log(1, "binkd_exit detected after select(), exiting"); -++ break; -++ } -++ -++ Delay(1UL); /* 1 tick = 20ms @ 50Hz, prevents CPU hogging */ -++ -++ /* Handle select errors */ -++ if (n < 0) -++ { -++ if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) -++ { -++ Log(5, "DEBUG: select interrupted, continuing"); -++ continue; -++ } -++ -++ if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) -++ { -++ Log(2, "select: %s, reopening", TCPERR()); -++ -++ close_listen_sockets(); -++ -++ if (server_flag && open_listen_sockets(config) < 0) -++ break; -++ -++ continue; -++ } -++ -++ Log(1, "select: %s", TCPERR()); -++ break; -++ } -++ else if (n == 0) -++ { -++ Log(5, "DEBUG: select timeout, continuing"); -++ } -++ -++ /* server: Accept new inbound connections */ -++ if (server_flag) -++ { -++ if (handle_server_accept(&r, config) < 0) -++ break; -++ } -++ -++ if (binkd_exit) -++ break; -++ -++ /* server + client: Advance all active sessions */ -++ active_sessions = advance_sessions(&r, &w, config); -++ -++ if (binkd_exit) -++ break; -++ -++ /* client: Time-based outbound scan + config reload */ -++ now = time(NULL); -++ -++ if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) -++ { -++ Log(5, "DEBUG: Before try_outbound"); -++ -++ try_outbound(config); -++ -++ Log(5, "DEBUG: After try_outbound"); -++ -++ if (checkcfg()) -++ { -++ if (handle_config_reload(&config, server_flag, client_flag)) -++ break; -++ -++ config_locked = 1; -++ } -++ -++ config->q_present = 0; -++ last_rescan = now; -++ } -++ -++ /* client: Poll-mode idle exit */ -++ /* Reset counter whenever there is something going on */ -++ if (n_clients > 0 || active_sessions > 0) -++ { -++ if (idle_loops > 0) -++ Log(2, "Activity detected, reset idle counter"); -++ -++ idle_loops = 0; -++ } -++ -++ if (!server_flag && active_sessions == 0 && n_clients == 0) -++ { -++ idle_loops++; -++ -++ Log(2, "Idle loop %d/2 (no server, no sessions, no clients)", idle_loops); -++ -++ if (idle_loops > 1) -++ { -++ Log(0, "the queue is empty, quitting..."); -++ break; -++ } -++ } -++ } -++ /* ===== End main loop ===== */ -++ -++ evloop_cleanup(config, config_locked); -++} -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop.h binkd/amiga/evloop.h -+--- binkd_pgul/amiga/evloop.h 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/evloop.h 2026-04-26 11:47:03.034239655 +0100 -+@@ -0,0 +1,34 @@ -++/* -++ * evloop.h -- non-blocking event loop for AmigaOS 3 -++ * -++ * evloop.h is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifndef _AMIGA_EVLOOP_H -++#define _AMIGA_EVLOOP_H -++ -++#ifdef AMIGA -++ -++#include "readcfg.h" -++#include "protoco2.h" /* STATE */ -++ -++/* amiga_proto_step() return codes — also used by protocol.c */ -++#define APROTO_RUNNING 0 /* session alive, call again */ -++#define APROTO_DONE_OK 1 /* session finished, success */ -++#define APROTO_DONE_ERR 2 /* session failed */ -++ -++/* -++ * amiga_evloop_run -- entry point replacing servmgr() + clientmgr() -++ * -++ * Opens listen sockets (when server_flag), then runs a WaitSelect() -++ * loop that multiplexes sessions dynamically based on config->max_servers -++ * and config->max_clients. Minimum 2 sessions are always allocated -++ * Returns only when binkd_exit != 0 -++ */ -++void amiga_evloop_run(BINKD_CONFIG *config, int server_flag, int client_flag); -++ -++#endif /* AMIGA */ -++#endif /* _AMIGA_EVLOOP_H */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/evloop_int.h binkd/amiga/evloop_int.h -+--- binkd_pgul/amiga/evloop_int.h 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/evloop_int.h 2026-04-26 11:02:58.166969078 +0100 -+@@ -0,0 +1,68 @@ -++/* -++ * evloop_int.h -- internal types shared by evloop.c, sock.c, session.c -++ * -++ * evloop_int.h is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifndef AMIGA_EVLOOP_INT_H -++#define AMIGA_EVLOOP_INT_H -++ -++#include "protoco2.h" -++#include "amiga/bsdsock.h" -++#include "ftnnode.h" -++#include "readcfg.h" -++ -++/* Session lifecycle */ -++typedef enum -++{ -++ SESS_FREE = 0, /* slot available */ -++ SESS_CONNECTING = 1, /* waiting for connect() */ -++ SESS_RUNNING = 2 /* BinkP session active */ -++} sess_phase_t; -++ -++/* Per-session state */ -++typedef struct -++{ -++ sess_phase_t phase; -++ SOCKET fd; -++ STATE state; -++ int inbound; /* 1=accepted, 0=outbound */ -++ -++ FTN_NODE *node; -++ struct addrinfo *ai_head; /* full getaddrinfo list */ -++ struct addrinfo *ai_cur; /* candidate being tried */ -++ time_t conn_start; -++ -++ char host[BINKD_FQDNLEN + 1]; -++ char port[MAXPORTSTRLEN + 1]; -++ char ip[BINKD_FQDNLEN + 1]; -++ -++ time_t last_io; -++} sess_t; -++ -++/* Globals defined in evloop.c */ -++extern sess_t *sessions; -++extern int max_sessions; -++ -++/* Defined in server.c and client.c respectively */ -++extern int n_servers; -++extern int n_clients; -++ -++/* sock.c */ -++void set_nonblock(SOCKET fd); -++int open_listen_sockets(BINKD_CONFIG *config); -++void close_listen_sockets(void); -++ -++/* session.c */ -++int sess_alloc(void); -++void sess_free(int idx); -++void do_accept(SOCKET lfd, BINKD_CONFIG *config); -++int start_connect(sess_t *s, BINKD_CONFIG *config); -++void check_connect(int idx, BINKD_CONFIG *config); -++int try_outbound(BINKD_CONFIG *config); -++void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config); -++ -++#endif /* AMIGA_EVLOOP_INT_H */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/proto_amiga.c binkd/amiga/proto_amiga.c -+--- binkd_pgul/amiga/proto_amiga.c 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/proto_amiga.c 2026-04-26 11:52:04.887130443 +0100 -+@@ -0,0 +1,269 @@ -++/* -++ * proto_amiga.c -- Amiga non-blocking BinkP protocol implementation -++ * -++ * proto_amiga.c is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#include -++#include -++#include -++#include -++#include -++ -++#include "sys.h" -++#include "readcfg.h" -++#include "common.h" -++#include "protocol.h" -++#include "ftnaddr.h" -++#include "ftnnode.h" -++#include "ftnq.h" -++#include "tools.h" -++#include "bsy.h" -++#include "inbound.h" -++#include "protoco2.h" -++#include "prothlp.h" -++#include "binlog.h" -++#include "evloop.h" -++ -++/* External functions from protocol.c */ -++extern int init_protocol(STATE *state, SOCKET s_in, SOCKET s_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config); -++extern int banner(STATE *state, BINKD_CONFIG *config); -++extern int recv_block(STATE *state, BINKD_CONFIG *config); -++extern int send_block(STATE *state, BINKD_CONFIG *config); -++extern void bsy_touch(BINKD_CONFIG *config); -++extern FTNQ *process_rcvdlist(STATE *state, FTNQ *q, BINKD_CONFIG *config); -++extern int start_file_transfer(STATE *state, FTNQ *q, BINKD_CONFIG *config); -++extern void ND_set_status(const char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config); -++extern void deinit_protocol(STATE *state, BINKD_CONFIG *config, int status); -++extern void evt_set(EVTQ *eq); -++extern void msg_send2(STATE *state, t_msg m, char *s1, char *s2); -++ -++/* External functions from other modules */ -++extern void log_end_of_session(int err, STATE *state, BINKD_CONFIG *config); -++extern void inb_remove_partial(STATE *state, BINKD_CONFIG *config); -++extern void good_try(FTN_ADDR *fa, char *comment, BINKD_CONFIG *config); -++extern void bad_try(FTN_ADDR *fa, const char *error, const int where, BINKD_CONFIG *config); -++extern int create_poll(FTN_ADDR *fa, int flvr, BINKD_CONFIG *config); -++extern void hold_node(FTN_ADDR *fa, time_t hold_until, BINKD_CONFIG *config); -++extern int binkd_exit; -++ -++/* External variables */ -++extern int n_servers; -++ -++/* -++ * amiga_proto_open -- Initialise a session and send the BinkP banner -++ * -++ * fd : Connected socket (same fd for in and out) -++ * to : Outbound node, NULL for inbound -++ * fa : Local AKA to use, may be NULL -++ * host : Remote hostname or dotted-IP string (caller-owned, stable) -++ * port : Remote port string, may be NULL -++ * dst_ip : Numeric remote IP, may be NULL (falls back to host) -++ * config : Current config -++ * -++ * Returns 0 on success, -1 on error (caller must close fd) -++ */ -++int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config) -++{ -++ struct sockaddr_storage sa; -++ socklen_t salen = (socklen_t)sizeof(sa); -++ char ownhost[BINKD_FQDNLEN + 1]; -++ char ownserv[MAXSERVNAME + 1]; -++ int rc; -++ -++ if (!init_protocol(state, fd, fd, to, fa, config)) -++ return -1; -++ -++ /* Peer identity for logging and %ip config checks */ -++ state->ipaddr = dst_ip ? (char *)dst_ip : (char *)host; -++ state->peer_name = (host && *host) ? (char *)host : state->ipaddr; -++ -++ /* local endpoint: Not used further, skip to avoid dangling pointer */ -++ -++ Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); -++ -++ /* banner() sends M_NUL lines and ADR messages */ -++ if (!banner(state, config)) -++ return -1; -++ -++ /* refuse if server limit reached */ -++ if (!to && n_servers > config->max_servers) -++ { -++ Log(1, "too many servers"); -++ msg_send2(state, M_BSY, "Too many servers", 0); -++ -++ return -1; -++ } -++ -++ return 0; -++} -++ -++/* -++ * amiga_proto_step -- Run one recv/send iteration of the BinkP loop -++ * -++ * readable : Non-zero if the socket has incoming data (from WaitSelect) -++ * writable : Non-zero if the socket can accept outgoing data -++ * -++ * Returns APROTO_RUNNING, APROTO_DONE_OK, or APROTO_DONE_ERR -++ * -++ * This is the loop body of protocol() with the select() call removed -++ * recv_block() and send_block() already handle EWOULDBLOCK gracefully, -++ * so calling this on a non-blocking socket is safe -++ */ -++int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config) -++{ -++ FTNQ *q; -++ int no; -++ -++ if (state->io_error) -++ return APROTO_DONE_ERR; -++ -++ /* Advance outgoing file queue if nothing is being sent */ -++ if (!state->local_EOB && state->q && !state->out.f && !state->waiting_for_GOT && !state->off_req_sent && state->state != P_NULL) -++ { -++ while (1) -++ { -++ q = 0; -++ if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) -++ { -++ if (start_file_transfer(state, q, config)) -++ break; -++ } -++ else -++ { -++ q_free(state->q, config); -++ state->q = 0; -++ break; -++ } -++ } -++ } -++ -++ /* Nothing left to send — issue EOB */ -++ if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) -++ { -++ if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) -++ { -++ state->local_EOB = 1; -++ msg_send2(state, M_EOB, 0, 0); -++ } -++ } -++ -++ /* Recv step: Only when socket is readable */ -++ if (readable) -++ { -++ if (!recv_block(state, config)) -++ return APROTO_DONE_ERR; -++ } -++ -++ /* -++ * send step: drive even when writable=0 if there is buffered data, -++ * pending messages, a file mid-transfer, or an EOF to flush. -++ */ -++ if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) -++ { -++ no = send_block(state, config); -++ -++ if (!no && no != 2) -++ return APROTO_DONE_ERR; -++ } -++ -++ bsy_touch(config); -++ -++ /* Batch/Session-end detection — Mirrors the break logic in protocol() */ -++ if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) -++ { -++ if (state->rcvdlist) -++ { -++ state->q = process_rcvdlist(state, state->q, config); -++ -++ q_to_killlist(&state->killlist, &state->n_killlist, state->q); -++ free_rcvdlist(&state->rcvdlist, &state->n_rcvdlist); -++ } -++ -++ Log(6, "batch: %i msgs", state->msgs_in_batch); -++ -++ if (state->msgs_in_batch <= 2 || (state->major * 100 + state->minor <= 100)) -++ { -++ /* Session done */ -++ ND_set_status("", &state->ND_addr, state, config); -++ state->ND_addr.z = -1; -++ -++ return APROTO_DONE_OK; -++ } -++ -++ /* Start next batch */ -++ state->msgs_in_batch = 0; -++ state->remote_EOB = 0; -++ state->local_EOB = 0; -++ -++ if (OK_SEND_FILES(state, config)) -++ { -++ state->q = q_scan_boxes(state->q, state->fa, state->nfa, state->to ? 1 : 0, config); -++ state->q = q_sort(state->q, state->fa, state->nfa, config); -++ } -++ } -++ -++ return APROTO_RUNNING; -++} -++ -++/* -++ * amiga_proto_close -- Flush remaining I/O and release STATE resources -++ * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR -++ */ -++void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) -++{ -++ int no; -++ char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; -++ int status = ok ? 0 : 1; -++ -++ /* Drain inbound queue */ -++ if (!state->io_error) -++ { -++ while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) -++ Log(9, "purged %d bytes", no); -++ } -++ -++ /* Flush pending outbound messages */ -++ while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) -++ ; -++ -++ if (ok) -++ { -++ log_end_of_session(0, state, config); -++ process_killlist(state->killlist, state->n_killlist, 's'); -++ inb_remove_partial(state, config); -++ -++ if (state->to) -++ good_try(&state->to->fa, "CONNECT/BND", config); -++ } -++ else -++ { -++ log_end_of_session(1, state, config); -++ process_killlist(state->killlist, state->n_killlist, 0); -++ -++ if (!binkd_exit && state->to) -++ bad_try(&state->to->fa, "Bad session", BAD_IO, config); -++ -++ /* Restore poll flavour if files were left mid-transfer */ -++ if (state->to && tolower(state->maxflvr) != 'h') -++ { -++ Log(4, "restoring poll with '%c' flavour", state->maxflvr); -++ -++ create_poll(&state->to->fa, state->maxflvr, config); -++ } -++ } -++ -++ if (state->to && state->r_skipped_flag && config->hold_skipped > 0) -++ { -++ Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); -++ -++ hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); -++ } -++ -++ deinit_protocol(state, config, status); -++ evt_set(state->evt_queue); -++ state->evt_queue = NULL; -++} -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/proto_amiga.h binkd/amiga/proto_amiga.h -+--- binkd_pgul/amiga/proto_amiga.h 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/proto_amiga.h 2026-04-26 11:53:22.716799421 +0100 -+@@ -0,0 +1,30 @@ -++/* -++ * proto_amiga.h -- Amiga non-blocking BinkP protocol implementation -++ * -++ * proto_amiga.h is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifndef _PROTO_AMIGA_H -++#define _PROTO_AMIGA_H -++ -++#include "protoco2.h" -++#include "readcfg.h" -++ -++/* amiga_proto_step() return codes */ -++#define APROTO_RUNNING 0 -++#define APROTO_DONE_OK 1 -++#define APROTO_DONE_ERR 2 -++ -++/* amiga_proto_open -- Initialise a session and send the BinkP banner */ -++int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config); -++ -++/* amiga_proto_step-- run one recv / send iteration of the BinkP loop */ -++int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config); -++ -++/* amiga_proto_close -- flush remaining I/O and release STATE resources */ -++void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok); -++ -++#endif /* _PROTO_AMIGA_H */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/rename.c binkd/amiga/rename.c -+--- binkd_pgul/amiga/rename.c 2026-04-16 17:49:00.000000000 +0100 -++++ binkd/amiga/rename.c 2026-04-25 19:33:26.785601976 +0100 -+@@ -1,10 +1,137 @@ -+ #include -+ #include -++#include -++ -++#include -++#include /* atoi */ -++#include /* isdigit */ -++ -++#define PATHBUF 512 -+ -+ int o_rename(char *from, char *to) -+ { -+- if (Rename((STRPTR)from, (STRPTR)to)) /* cross-volume move won't work */ -++ struct FileInfoBlock *fib; -++ char dir[PATHBUF]; -++ char base[PATHBUF]; -++ char newname[PATHBUF]; -++ char *slash; -++ ULONG max = 0; -++ BPTR dirlock; -++ char *d = NULL; -++ const char *src = NULL; -++ ULONG n = 0; -++ -++ /* Try direct rename first */ -++ if (Rename((STRPTR)from, (STRPTR)to)) -++ return 0; -++ -++ /* Split path */ -++ slash = strrchr(to, '/'); -++ -++ if (!slash) -++ slash = strrchr(to, ':'); -++ -++ if (slash) -++ { -++ ULONG len = slash - to; -++ -++ if (len >= PATHBUF) -++ len = PATHBUF - 1; -++ -++ strncpy(dir, to, len); -++ dir[len] = '\0'; -++ -++ strncpy(base, slash + 1, PATHBUF - 1); -++ base[PATHBUF - 1] = '\0'; -++ } -++ else -++ { -++ strcpy(dir, "."); -++ strncpy(base, to, PATHBUF - 1); -++ base[PATHBUF - 1] = '\0'; -++ } -++ -++ /* Lock directory */ -++ dirlock = Lock((STRPTR)dir, ACCESS_READ); -++ -++ if (!dirlock) -++ { -++ errno = ENOENT; -++ return -1; -++ } -++ -++ fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); -++ -++ if (!fib) -++ { -++ UnLock(dirlock); -++ errno = ENOMEM; -++ return -1; -++ } -++ -++ /* Scan directory safely under lock */ -++ if (Examine(dirlock, fib)) -++ { -++ while (ExNext(dirlock, fib)) -++ { -++ if (strncmp(fib->fib_FileName, base, strlen(base)) == 0) -++ { -++ const char *p = NULL; -++ -++ p = fib->fib_FileName + strlen(base); -++ -++ if (*p != '.') -++ { -++ continue; -++ } -++ -++ /* .001 style */ -++ if (isdigit((UBYTE)p[1]) && isdigit((UBYTE)p[2]) && isdigit((UBYTE)p[3])) -++ { -++ n = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); -++ if (n > max) -++ max = n; -++ } -++ -++ /* FIDO volume style (.mo0 .th1 etc) */ -++ if (isdigit((UBYTE)p[1]) && !isdigit((UBYTE)p[2])) -++ { -++ n = p[1] - '0'; -++ if (n > max) -++ max = n; -++ } -++ } -++ } -++ } -++ -++ FreeDosObject(DOS_FIB, fib); -++ UnLock(dirlock); -++ -++ /* Build new name */ -++ d = newname; -++ src = to; -++ n = max + 1; -++ -++ if (n > 999) -++ n = 0; -++ -++ /* Copy base */ -++ while (*src && (d - newname) < (PATHBUF - 5)) -++ *d++ = *src++; -++ -++ *d++ = '.'; -++ -++ /* Manual 3-digit write */ -++ *d++ = '0' + (n / 100); -++ n %= 100; -++ *d++ = '0' + (n / 10); -++ *d++ = '0' + (n % 10); -++ *d = '\0'; -++ -++ /* Rename */ -++ if (Rename((STRPTR)from, (STRPTR)newname)) -++ return 0; -++ -++ errno = EACCES; -+ return -1; -+- else -+- return 0; -+ } -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/rfc2553_amiga.c binkd/amiga/rfc2553_amiga.c -+--- binkd_pgul/amiga/rfc2553_amiga.c 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/rfc2553_amiga.c 2026-04-26 11:54:19.321503648 +0100 -+@@ -0,0 +1,323 @@ -++/* -++ * rfc2553_amiga.c -- getaddrinfo/getnameinfo fallback for AmigaOS 3 -++ * -++ * rfc2553_amiga.c is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifdef AMIGA -++ -++#include "amiga/bsdsock.h" /* LP stubs + SocketBase */ -++#include "rfc2553.h" /* sets HAVE_GETADDRINFO / HAVE_GETNAMEINFO */ -++#include "sem.h" -++ -++#include -++#include -++#include -++#include -++ -++#define safe_strncpy(dst, src, n) \ -++ do \ -++ { \ -++ strncpy((dst), (src), (n)); \ -++ (dst)[(n) - 1] = '\0'; \ -++ } while (0) -++ -++#ifndef HAVE_GETADDRINFO -++ -++void freeaddrinfo(struct addrinfo *ai); /* forward decl */ -++ -++int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) -++{ -++ struct addrinfo **tail = res; -++ struct hostent *hent = NULL; -++ unsigned int port; -++ int proto; -++ const char *end; -++ char **addrp; -++ -++ static char passive_dummy = '\0'; -++ char *passive_list[2] = {&passive_dummy, NULL}; -++ -++ if (!res) -++ { -++ return EAI_UNKNOWN; -++ } -++ -++ *res = NULL; -++ -++ port = servname ? htons((unsigned short)strtol(servname, (char **)&end, 0)) : 0; -++ proto = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; -++ -++ lockresolvsem(); -++ -++ if (servname && end != servname + strlen(servname)) -++ { -++ struct servent *se = NULL; -++ -++ if (!hints || hints->ai_socktype == SOCK_STREAM) -++ se = getservbyname((char *)servname, "tcp"); -++ -++ if (hints && hints->ai_socktype == SOCK_DGRAM) -++ se = getservbyname((char *)servname, "udp"); -++ -++ if (!se) -++ { -++ releaseresolvsem(); -++ return EAI_NONAME; -++ } -++ -++ port = se->s_port; -++ -++ if (strcmp((char *)se->s_proto, "tcp") == 0) -++ proto = SOCK_STREAM; -++ else if (strcmp((char *)se->s_proto, "udp") == 0) -++ proto = SOCK_DGRAM; -++ else -++ { -++ releaseresolvsem(); -++ return EAI_NONAME; -++ } -++ -++ if (hints && hints->ai_socktype && hints->ai_socktype != proto) -++ { -++ releaseresolvsem(); -++ return EAI_SERVICE; -++ } -++ } -++ -++ if (!hints || !(hints->ai_flags & AI_PASSIVE)) -++ { -++ unsigned long nip = inet_addr((char *)nodename); -++ -++ if (nip != (unsigned long)INADDR_NONE) -++ { -++ struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); -++ struct sockaddr_in *sin; -++ -++ if (!ai) -++ { -++ releaseresolvsem(); -++ return EAI_MEMORY; -++ } -++ *tail = ai; -++ -++ sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); -++ -++ if (!sin) -++ { -++ free(ai); -++ releaseresolvsem(); -++ return EAI_MEMORY; -++ } -++ -++ ai->ai_family = AF_INET; -++ ai->ai_socktype = proto; -++ ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; -++ ai->ai_addrlen = sizeof(*sin); -++ ai->ai_addr = (struct sockaddr *)sin; -++ sin->sin_family = AF_INET; -++ sin->sin_port = port; -++ sin->sin_addr.s_addr = nip; -++ -++ releaseresolvsem(); -++ return 0; -++ } -++ -++ hent = gethostbyname((char *)nodename); -++ -++ if (!hent) -++ { -++ int herr = errno; -++ releaseresolvsem(); -++ return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL -++ : EAI_NONAME; -++ } -++ -++ if (!hent->h_addr_list || !hent->h_addr_list[0]) -++ { -++ releaseresolvsem(); -++ return EAI_NONAME; -++ } -++ -++ addrp = hent->h_addr_list; -++ } -++ else -++ { -++ addrp = passive_list; -++ } -++ -++ for (; *addrp; addrp++) -++ { -++ struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); -++ struct sockaddr_in *sin; -++ -++ if (!ai) -++ { -++ releaseresolvsem(); -++ freeaddrinfo(*res); -++ *res = NULL; -++ return EAI_MEMORY; -++ } -++ -++ if (!*res) -++ *res = ai; -++ *tail = ai; -++ tail = &ai->ai_next; -++ -++ sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); -++ -++ if (!sin) -++ { -++ releaseresolvsem(); -++ freeaddrinfo(*res); -++ *res = NULL; -++ return EAI_MEMORY; -++ } -++ -++ ai->ai_family = AF_INET; -++ ai->ai_socktype = proto; -++ ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; -++ ai->ai_addrlen = sizeof(*sin); -++ ai->ai_addr = (struct sockaddr *)sin; -++ sin->sin_family = AF_INET; -++ sin->sin_port = port; -++ -++ if (!hints || !(hints->ai_flags & AI_PASSIVE)) -++ { -++ size_t cpylen = sizeof(sin->sin_addr); -++ -++ if (hent->h_length > 0 && (size_t)hent->h_length < cpylen) -++ cpylen = (size_t)hent->h_length; -++ -++ memcpy(&sin->sin_addr, *addrp, cpylen); -++ } -++ } -++ -++ releaseresolvsem(); -++ return 0; -++} -++ -++void freeaddrinfo(struct addrinfo *ai) -++{ -++ struct addrinfo *next; -++ -++ while (ai) -++ { -++ free(ai->ai_addr); -++ next = ai->ai_next; -++ free(ai); -++ ai = next; -++ } -++} -++ -++static const char *ai_errlist[] = -++ { -++ "Success", -++ "hostname nor servname provided, or not known", -++ "Temporary failure in name resolution", -++ "Non-recoverable failure in name resolution", -++ "No address associated with hostname", -++ "ai_family not supported", -++ "ai_socktype not supported", -++ "service name not supported for ai_socktype", -++ "Address family for hostname not supported", -++ "Memory allocation failure", -++ "System error returned in errno", -++ "Unknown error", -++}; -++ -++char *gai_strerror(int ecode) -++{ -++ if (ecode > 0 || ecode < EAI_UNKNOWN) -++ ecode = EAI_UNKNOWN; -++ return (char *)ai_errlist[-ecode]; -++} -++ -++#endif /* !HAVE_GETADDRINFO */ -++ -++#ifndef HAVE_GETNAMEINFO -++ -++#ifndef NI_DATAGRAM -++#define NI_DATAGRAM (1 << 4) -++#endif -++ -++int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) -++{ -++ const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; -++ -++ (void)salen; -++ -++ if (sa->sa_family != AF_INET) -++ return EAI_ADDRFAMILY; -++ -++ if (host && hostlen > 0) -++ { -++ if (!(flags & NI_NUMERICHOST)) -++ { -++ struct hostent *he; -++ -++ lockresolvsem(); -++ he = gethostbyaddr((char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET); -++ -++ if (he) -++ { -++ safe_strncpy(host, (char *)he->h_name, hostlen); -++ releaseresolvsem(); -++ } -++ else -++ { -++ int herr = errno; -++ releaseresolvsem(); -++ if (flags & NI_NAMEREQD) -++ return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL -++ : EAI_NONAME; -++ flags |= NI_NUMERICHOST; -++ } -++ } -++ -++ if (flags & NI_NUMERICHOST) -++ { -++ lockhostsem(); -++ safe_strncpy(host, (char *)Inet_NtoA(sin->sin_addr.s_addr), hostlen); -++ releasehostsem(); -++ } -++ } -++ -++ if (serv && servlen > 0) -++ { -++ if (!(flags & NI_NUMERICSERV)) -++ { -++ struct servent *se; -++ -++ lockresolvsem(); -++ -++ se = (flags & NI_DATAGRAM) ? getservbyport(ntohs(sin->sin_port), "udp") : getservbyport(ntohs(sin->sin_port), "tcp"); -++ -++ if (se) -++ { -++ safe_strncpy(serv, (char *)se->s_name, servlen); -++ releaseresolvsem(); -++ } -++ else -++ { -++ releaseresolvsem(); -++ -++ if (flags & NI_NAMEREQD) -++ return EAI_NONAME; -++ -++ flags |= NI_NUMERICSERV; -++ } -++ } -++ -++ if (flags & NI_NUMERICSERV) -++ snprintf(serv, servlen, "%u", ntohs(sin->sin_port)); -++ } -++ -++ return 0; -++} -++ -++#endif /* !HAVE_GETNAMEINFO */ -++#endif /* AMIGA */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/sem.c binkd/amiga/sem.c -+--- binkd_pgul/amiga/sem.c 2026-04-16 17:49:00.000000000 +0100 -++++ binkd/amiga/sem.c 2026-04-25 19:33:46.416919700 +0100 -+@@ -1,29 +1,100 @@ -+ /* -+ * Amiga semaphores -+ */ -++ -+ #include -+ #include -+-#include -++#include -++#include -++ -++extern void Log(int lev, char *s, ...); -++ -++int _InitSem(void *vpSem) -++{ -++ memset(vpSem, 0, sizeof(struct SignalSemaphore)); -++ InitSemaphore((struct SignalSemaphore *)vpSem); -++ return 0; -++} -++ -++int _CleanSem(void *vpSem) -++{ -++ return 0; -++} -++ -++int _LockSem(void *vpSem) -++{ -++ ObtainSemaphore((struct SignalSemaphore *)vpSem); -++ return 0; -++} -++ -++int _ReleaseSem(void *vpSem) -++{ -++ ReleaseSemaphore((struct SignalSemaphore *)vpSem); -++ return 0; -++} -+ -+-extern void Log (int lev, char *s,...); -++int _InitEventSem(EVENTSEM *sem) -++{ -++ if (!sem) -++ return -1; -+ -++ sem->waiter = NULL; -+ -+-int _InitSem(void *vpSem) { -+- memset(vpSem, 0, sizeof (struct SignalSemaphore)); -+- InitSemaphore ((struct SignalSemaphore*)vpSem); -+- return(0); -++ sem->sigbit = AllocSignal(-1); -++ -++ if (sem->sigbit == (ULONG)-1) -++ return -1; -++ -++ return 0; -+ } -+ -+-int _CleanSem(void *vpSem) { -+- return (0); -++int _CleanEventSem(EVENTSEM *sem) -++{ -++ if (!sem) -++ return -1; -++ -++ if (sem->sigbit != (ULONG)-1) -++ { -++ FreeSignal((LONG)sem->sigbit); -++ sem->sigbit = (ULONG)-1; -++ } -++ -++ sem->waiter = NULL; -++ return 0; -+ } -+ -+-int _LockSem(void *vpSem) { -+- ObtainSemaphore ((struct SignalSemaphore *)vpSem); -+- return (0); -++int _PostSem(EVENTSEM *sem) -++{ -++ if (!sem) -++ return -1; -++ -++ if (sem->waiter && sem->sigbit != (ULONG)-1) -++ { -++ Signal((struct Task *)sem->waiter, 1UL << sem->sigbit); -++ } -++ -++ return 0; -+ } -+ -+-int _ReleaseSem(void *vpSem) { -+- ReleaseSemaphore ((struct SignalSemaphore *)vpSem); -+- return (0); -++int _WaitSem(EVENTSEM *sem, int sec) -++{ -++ ULONG mask; -++ struct Task *me; -++ -++ if (!sem || sem->sigbit == (ULONG)-1) -++ return -1; -++ -++ me = FindTask(NULL); -++ sem->waiter = me; -++ -++ /* Wait on SIGBREAKF_CTRL_C to avoid hanging on race */ -++ mask = Wait((1UL << sem->sigbit) | SIGBREAKF_CTRL_C); -++ -++ sem->waiter = NULL; -++ -++ /* Return timeout/break indication if only CTRL_C fired */ -++ if (!(mask & (1UL << sem->sigbit)) && (mask & SIGBREAKF_CTRL_C)) -++ return -1; -++ -++ return 0; -+ } -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/session.c binkd/amiga/session.c -+--- binkd_pgul/amiga/session.c 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/session.c 2026-04-26 11:57:30.521108284 +0100 -+@@ -0,0 +1,462 @@ -++/* -++ * session.c -- session management for AmigaOS 3 binkd -++ * -++ * session.c is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#include -++#include -++ -++#include -++#include -++#include -++#include -++ -++#include "sys.h" -++#include "iphdr.h" -++#include "readcfg.h" -++#include "common.h" -++#include "tools.h" -++#include "client.h" -++#include "protocol.h" -++#include "ftnq.h" -++#include "ftnnode.h" -++#include "ftnaddr.h" -++#include "bsy.h" -++#include "iptools.h" -++#include "rfc2553.h" -++#include "srv_gai.h" -++#include "amiga/bsdsock.h" -++#include "amiga/evloop_int.h" -++#include "amiga/proto_amiga.h" -++ -++extern int binkd_exit; -++extern int ext_rand; -++extern int client_flag; -++extern int poll_flag; -++ -++/* Session table */ -++int sess_alloc(void) -++{ -++ int i; -++ -++ for (i = 0; i < max_sessions; i++) -++ { -++ if (sessions[i].phase == SESS_FREE) -++ { -++ memset(&sessions[i], 0, sizeof(sess_t)); -++ sessions[i].fd = INVALID_SOCKET; -++ sessions[i].phase = SESS_FREE; -++ return i; -++ } -++ } -++ -++ return -1; -++} -++ -++void sess_free(int idx) -++{ -++ sess_t *s = &sessions[idx]; -++ -++ if (s->fd != INVALID_SOCKET) -++ { -++ soclose(s->fd); -++ s->fd = INVALID_SOCKET; -++ } -++ -++ if (s->ai_head) -++ { -++ freeaddrinfo(s->ai_head); -++ s->ai_head = NULL; -++ } -++ -++ memset(&s->state, 0, sizeof(STATE)); -++ s->phase = SESS_FREE; -++} -++ -++/* Inbound: accept a new connection */ -++void do_accept(SOCKET lfd, BINKD_CONFIG *config) -++{ -++ struct sockaddr_storage sa; -++ socklen_t salen = (socklen_t)sizeof(sa); -++ SOCKET fd; -++ int idx; -++ sess_t *s; -++ char host[BINKD_FQDNLEN + 1]; -++ char ip[BINKD_FQDNLEN + 1]; -++ -++ fd = accept(lfd, (struct sockaddr *)&sa, &salen); -++ -++ if (fd == INVALID_SOCKET) -++ { -++ if (TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN) -++ Log(1, "accept(): %s", TCPERR()); -++ -++ return; -++ } -++ -++ if (binkd_exit) -++ { -++ soclose(fd); -++ return; -++ } -++ -++ idx = sess_alloc(); -++ -++ if (idx < 0) -++ { -++ Log(1, "session table full, refusing inbound"); -++ soclose(fd); -++ return; -++ } -++ -++ /* getnameinfo() Is unreliable on AmiTCP: use inet_ntoa directly */ -++ if (((struct sockaddr *)&sa)->sa_family == AF_INET) -++ { -++ struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; -++ strnzcpy(ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); -++ } -++ else -++ { -++ strnzcpy(ip, "unknown", BINKD_FQDNLEN); -++ } -++ -++ /* Backresolv not supported on AmiTCP: always use IP as host */ -++ strnzcpy(host, ip, BINKD_FQDNLEN); -++ -++ set_nonblock(fd); -++ -++ s = &sessions[idx]; -++ s->fd = fd; -++ s->inbound = 1; -++ s->node = NULL; -++ s->ai_head = NULL; -++ s->last_io = time(NULL); -++ strnzcpy(s->host, host, BINKD_FQDNLEN); -++ strnzcpy(s->ip, ip, BINKD_FQDNLEN); -++ s->port[0] = '\0'; -++ -++ if (amiga_proto_open(&s->state, fd, NULL, NULL, s->host, NULL, s->ip, config) != 0) -++ { -++ Log(1, "proto_open failed for %s", ip); -++ sess_free(idx); -++ return; -++ } -++ -++ s->phase = SESS_RUNNING; -++ n_servers++; -++ Log(4, "inbound slot[%d] from %s", idx, ip); -++} -++ -++/* Outbound: Non-blocking connect() */ -++int start_connect(sess_t *s, BINKD_CONFIG *config) -++{ -++ SOCKET fd; -++ int rc; -++ -++ s->ip[0] = '\0'; -++ s->port[0] = '\0'; -++ -++ fd = socket(s->ai_cur->ai_family, s->ai_cur->ai_socktype, s->ai_cur->ai_protocol); -++ -++ if (fd == INVALID_SOCKET) -++ { -++ Log(1, "outbound socket(): %s", TCPERR()); -++ return -1; -++ } -++ -++ /* getnameinfo() is unreliable on AmiTCP: may return rc=0 with garbage -++ * Use inet_ntoa/ntohs directly */ -++ if (s->ai_cur->ai_family == AF_INET) -++ { -++ struct sockaddr_in *sa4 = (struct sockaddr_in *)s->ai_cur->ai_addr; -++ strnzcpy(s->ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); -++ snprintf(s->port, MAXPORTSTRLEN, "%u", (unsigned)ntohs(sa4->sin_port)); -++ } -++ else -++ { -++ strnzcpy(s->ip, "unknown", BINKD_FQDNLEN); -++ strnzcpy(s->port, "0", MAXPORTSTRLEN); -++ } -++ -++ Log(4, "connecting %s [%s]:%s", s->host, s->ip, s->port); -++ -++ if (config->bindaddr[0]) -++ { -++ struct addrinfo src_h, *src_ai; -++ memset(&src_h, 0, sizeof(src_h)); -++ src_h.ai_family = s->ai_cur->ai_family; -++ src_h.ai_socktype = SOCK_STREAM; -++ src_h.ai_protocol = IPPROTO_TCP; -++ -++ if (getaddrinfo(config->bindaddr, NULL, &src_h, &src_ai) == 0) -++ { -++ bind(fd, src_ai->ai_addr, (int)src_ai->ai_addrlen); -++ freeaddrinfo(src_ai); -++ } -++ } -++ -++ set_nonblock(fd); -++ -++ rc = connect(fd, s->ai_cur->ai_addr, (int)s->ai_cur->ai_addrlen); -++ -++ if (rc == 0 || TCPERRNO == EINPROGRESS || TCPERRNO == EWOULDBLOCK) -++ { -++ s->fd = fd; -++ s->conn_start = time(NULL); -++ return 0; -++ } -++ -++ Log(1, "connect %s: %s", s->host, TCPERR()); -++ -++ bad_try(&s->node->fa, TCPERR(), BAD_CALL, config); -++ soclose(fd); -++ -++ return -1; -++} -++ -++int try_outbound(BINKD_CONFIG *config) -++{ -++ FTN_NODE *node; -++ sess_t *s; -++ int idx, rc; -++ struct addrinfo hints; -++ char dest[FTN_ADDR_SZ + 1]; -++ char host[BINKD_FQDNLEN + 5 + 1]; -++ char port[MAXPORTSTRLEN + 1]; -++ -++ if (!client_flag) -++ return 0; -++ -++ if (!config->q_present) -++ { -++ q_free(SCAN_LISTED, config); -++ -++ if (config->printq) -++ Log(-1, "scan\r"); -++ -++ q_scan(SCAN_LISTED, config); -++ config->q_present = 1; -++ -++ if (config->printq) -++ { -++ q_list(stderr, SCAN_LISTED, config); -++ Log(-1, "idle\r"); -++ } -++ } -++ -++ if (n_clients >= config->max_clients) -++ return 0; -++ -++ node = q_next_node(config); -++ -++ if (!node) -++ return 0; -++ -++ ftnaddress_to_str(dest, &node->fa); -++ -++ if (!bsy_test(&node->fa, F_BSY, config) || !bsy_test(&node->fa, F_CSY, config)) -++ { -++ Log(4, "%s busy", dest); -++ return 0; -++ } -++ -++ idx = sess_alloc(); -++ -++ if (idx < 0) -++ { -++ Log(2, "table full, deferring %s", dest); -++ return 0; -++ } -++ -++ s = &sessions[idx]; -++ memset(s, 0, sizeof(*s)); -++ s->fd = INVALID_SOCKET; -++ s->node = node; -++ s->inbound = 0; -++ -++ rc = get_host_and_port(1, host, port, node->hosts, &node->fa, config); -++ -++ if (rc <= 0) -++ { -++ Log(1, "%s: bad host list", dest); -++ sess_free(idx); -++ -++ return 0; -++ } -++ -++ strnzcpy(s->host, host, BINKD_FQDNLEN); -++ strnzcpy(s->port, port, MAXPORTSTRLEN); -++ -++ memset(&hints, 0, sizeof(hints)); -++ hints.ai_family = node->IP_afamily; -++ hints.ai_socktype = SOCK_STREAM; -++ hints.ai_protocol = IPPROTO_TCP; -++ -++ rc = srv_getaddrinfo(host, port, &hints, &s->ai_head); -++ -++ if (rc != 0) -++ { -++ Log(1, "%s: getaddrinfo error code=%d: %s", dest, rc, gai_strerror(rc)); -++ -++ bad_try(&node->fa, "getaddrinfo failed", BAD_CALL, config); -++ sess_free(idx); -++ -++ return 0; -++ } -++ -++ s->ai_cur = s->ai_head; -++ -++ if (start_connect(s, config) != 0) -++ { -++ sess_free(idx); -++ return 0; -++ } -++ -++ s->phase = SESS_CONNECTING; -++ n_clients++; -++ -++ Log(4, "outbound slot[%d] -> %s", idx, dest); -++ -++ return 1; -++} -++ -++/* Check completion of async connect() */ -++void check_connect(int idx, BINKD_CONFIG *config) -++{ -++ sess_t *s = &sessions[idx]; -++ int err = 0; -++ socklen_t el = (socklen_t)sizeof(err); -++ int tmo; -++ -++ tmo = config->connect_timeout ? config->connect_timeout : 30; -++ -++ if ((int)(time(NULL) - s->conn_start) >= tmo) -++ { -++ Log(1, "connect timeout -> %s", s->host); -++ -++ bad_try(&s->node->fa, "Timeout", BAD_CALL, config); -++ n_clients--; -++ sess_free(idx); -++ -++ return; -++ } -++ -++ getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &el); -++ -++ if (err) -++ { -++ Log(1, "connect -> %s: %s", s->host, strerror(err)); -++ -++ bad_try(&s->node->fa, strerror(err), BAD_CALL, config); -++ -++ soclose(s->fd); -++ s->fd = INVALID_SOCKET; -++ s->ai_cur = s->ai_cur->ai_next; -++ -++ if (s->ai_cur && start_connect(s, config) == 0) -++ return; /* trying next address */ -++ -++ n_clients--; -++ sess_free(idx); -++ -++ return; -++ } -++ -++ Log(4, "connected -> %s [%s]", s->host, s->ip); -++ ext_rand = rand(); -++ -++ if (amiga_proto_open(&s->state, s->fd, s->node, NULL, s->host, s->port, s->ip, config) != 0) -++ { -++ Log(1, "proto_open failed for %s", s->host); -++ -++ n_clients--; -++ sess_free(idx); -++ return; -++ } -++ -++ s->phase = SESS_RUNNING; -++ s->last_io = time(NULL); -++} -++ -++/* Run one protocol step on an active session */ -++void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config) -++{ -++ sess_t *s = &sessions[idx]; -++ int rc; -++ int tmo; -++ -++ tmo = config->nettimeout ? config->nettimeout : 300; -++ -++ if ((int)(time(NULL) - s->last_io) >= tmo) -++ { -++ Log(1, "slot[%d] net timeout", idx); -++ -++ if (s->node) -++ bad_try(&s->node->fa, "Timeout", BAD_IO, config); -++ -++ amiga_proto_close(&s->state, config, 0); -++ -++ if (s->inbound) -++ n_servers--; -++ else -++ n_clients--; -++ -++ sess_free(idx); -++ -++ return; -++ } -++ -++ if (s->fd == INVALID_SOCKET) -++ { -++ Log(1, "slot[%d] invalid socket, closing session", idx); -++ -++ if (s->node) -++ bad_try(&s->node->fa, "Invalid socket", BAD_IO, config); -++ -++ if (s->inbound) -++ n_servers--; -++ else -++ n_clients--; -++ -++ sess_free(idx); -++ -++ return; -++ } -++ -++ /* WaitSelect() may not report a readable socket when the remote has -++ * sent a TCP END. Probe with MSG_PEEK so recv_block() sees the EOF */ -++ if (!rd && !wr && s->state.state != P_NULL) -++ { -++ char peek; -++ int pr = recv(s->fd, &peek, 1, MSG_PEEK); -++ -++ if (pr == 0 || (pr < 0 && TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN)) -++ rd = 1; -++ } -++ -++ rc = amiga_proto_step(&s->state, rd, wr, config); -++ -++ if (rd || wr) -++ s->last_io = time(NULL); -++ -++ if (rc == APROTO_RUNNING) -++ return; -++ -++ amiga_proto_close(&s->state, config, rc == APROTO_DONE_OK); -++ -++ Log(4, "slot[%d] %s", idx, rc == APROTO_DONE_OK ? "OK" : "ERR"); -++ -++ if (s->inbound) -++ n_servers--; -++ else -++ n_clients--; -++ -++ sess_free(idx); -++ -++ if (poll_flag && n_clients == 0 && n_servers == 0) -++ binkd_exit = 1; -++} -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/sock.c binkd/amiga/sock.c -+--- binkd_pgul/amiga/sock.c 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/sock.c 2026-04-26 11:59:26.645813693 +0100 -+@@ -0,0 +1,130 @@ -++/* -++ * sock.c -- listen socket management for AmigaOS 3 -++ * -++ * sock.c is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#include -++#include -++ -++#include -++#include -++ -++#include "sys.h" -++#include "readcfg.h" -++#include "tools.h" -++#include "server.h" -++#include "rfc2553.h" -++#include "amiga/bsdsock.h" -++#include "amiga/evloop_int.h" -++ -++extern SOCKET sockfd[]; -++extern int sockfd_used; -++extern int server_flag; -++ -++void set_nonblock(SOCKET fd) -++{ -++ long flag = 1L; -++ -++ if (IoctlSocket(fd, FIONBIO, (char *)&flag) != 0) -++ Log(2, "IoctlSocket(FIONBIO) failed: %s", TCPERR()); -++} -++ -++int open_listen_sockets(BINKD_CONFIG *config) -++{ -++ struct listenchain *ll; -++ struct addrinfo hints, *ai, *head; -++ int err, opt = 1; -++ -++ memset(&hints, 0, sizeof(hints)); -++ hints.ai_flags = AI_PASSIVE; -++ hints.ai_family = PF_UNSPEC; -++ hints.ai_socktype = SOCK_STREAM; -++ hints.ai_protocol = IPPROTO_TCP; -++ -++ sockfd_used = 0; -++ -++ for (ll = config->listen.first; ll; ll = ll->next) -++ { -++ err = getaddrinfo(ll->addr[0] ? ll->addr : NULL, ll->port, &hints, &head); -++ -++ if (err) -++ { -++ Log(1, "listen getaddrinfo(%s:%s): %s", ll->addr[0] ? ll->addr : "*", ll->port, gai_strerror(err)); -++ return -1; -++ } -++ -++ for (ai = head; ai && sockfd_used < MAX_LISTENSOCK; ai = ai->ai_next) -++ { -++ SOCKET fd; -++ int retries = 6; -++ -++ fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); -++ -++ if (fd == INVALID_SOCKET) -++ { -++ Log(1, "listen socket(): %s", TCPERR()); -++ continue; -++ } -++ -++ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, (int)sizeof(opt)) != 0) -++ Log(2, "setsockopt(SO_REUSEADDR) failed: %s", TCPERR()); -++ -++ /* Bsdsocket may hold the port briefly after socket close */ -++ while (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) != 0) -++ { -++ if (--retries == 0) -++ { -++ Log(1, "listen bind(): %s", TCPERR()); -++ -++ soclose(fd); -++ freeaddrinfo(head); -++ return -1; -++ } -++ -++ Log(2, "bind retry in 2s: %s", TCPERR()); -++ -++ Delay(100UL); /* 100 ticks = 2s @ 50Hz */ -++ } -++ -++ if (listen(fd, 5) != 0) -++ { -++ Log(1, "listen(): %s", TCPERR()); -++ -++ soclose(fd); -++ freeaddrinfo(head); -++ return -1; -++ } -++ -++ set_nonblock(fd); -++ sockfd[sockfd_used] = fd; -++ sockfd_used++; -++ } -++ -++ freeaddrinfo(head); -++ -++ Log(3, "listening on %s:%s", -++ ll->addr[0] ? ll->addr : "*", ll->port); -++ } -++ -++ if (sockfd_used == 0 && server_flag) -++ { -++ Log(1, "evloop: no listen sockets opened"); -++ return -1; -++ } -++ -++ return 0; -++} -++ -++void close_listen_sockets(void) -++{ -++ int i; -++ -++ for (i = 0; i < sockfd_used; i++) -++ soclose(sockfd[i]); -++ -++ sockfd_used = 0; -++} -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/amiga/utime.c binkd/amiga/utime.c -+--- binkd_pgul/amiga/utime.c 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/amiga/utime.c 2026-04-26 11:59:41.408046130 +0100 -+@@ -0,0 +1,77 @@ -++/* -++ * utime.c -- utime() stub for AmigaOS 3 without ixemul -++ * -++ * utime.c is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifdef AMIGA -++ -++#include -++#include -++#include -++#include -++ -++#include "amiga/dirent.h" /* declares struct utimbuf and utime() prototype */ -++ -++/* Days between AmigaDOS epoch (1978-01-01) and POSIX epoch (1970-01-01) */ -++#define AMIGA_EPOCH_DELTA_DAYS 2922UL -++#define SECONDS_PER_DAY 86400UL -++#define SECONDS_PER_MINUTE 60UL -++ -++int utime(const char *path, const struct utimbuf *times) -++{ -++ struct DateStamp ds; -++ LONG seconds_today; -++ LONG total_seconds; -++ -++ if (!path) -++ { -++ errno = EINVAL; -++ return -1; -++ } -++ -++ if (!times) -++ { -++ /* Use current time */ -++ DateStamp(&ds); -++ } -++ else -++ { -++ LONG t = (LONG)times->modtime; -++ -++ if (t < (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY)) -++ { -++ /* Time predates AmigaDOS epoch -- clamp to epoch */ -++ t = 0; -++ } -++ else -++ { -++ t -= (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY); -++ } -++ -++ ds.ds_Days = (LONG)(t / (LONG)SECONDS_PER_DAY); -++ total_seconds = t % (LONG)SECONDS_PER_DAY; -++ ds.ds_Minute = (LONG)(total_seconds / (LONG)SECONDS_PER_MINUTE); -++ seconds_today = total_seconds % (LONG)SECONDS_PER_MINUTE; -++ ds.ds_Tick = seconds_today * (LONG)TICKS_PER_SECOND; -++ } -++ -++ if (!SetFileDate((STRPTR)path, &ds)) -++ { -++ /* Most likely cause: file does not exist or is write-protected */ -++ LONG err = IoErr(); -++ -++ if (err == ERROR_OBJECT_NOT_FOUND || err == ERROR_DIR_NOT_FOUND) -++ errno = ENOENT; -++ else -++ errno = EACCES; -++ return -1; -++ } -++ -++ return 0; -++} -++ -++#endif /* AMIGA */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/binkd.c binkd/binkd.c -+--- binkd_pgul/binkd.c 2026-04-16 17:49:00.000000000 +0100 -++++ binkd/binkd.c 2026-04-26 13:20:44.986212073 +0100 -+@@ -54,6 +54,10 @@ -+ #include "unix/daemonize.h" -+ #endif -+ -++#ifdef AMIGA -++/* amiga/bsdsock.h pulled in via iphdr.h for AMIGA */ -++#include "amiga/evloop.h" -++#endif -+ #ifdef WIN32 -+ #include "nt/service.h" -+ #include "nt/w32tools.h" -+@@ -62,9 +66,11 @@ -+ #endif -+ #endif -+ -++#include "bsycleanup.h" -++ -+ #include "confopt.h" -+ -+-#ifdef HAVE_THREADS -++#if defined(HAVE_THREADS) || defined(AMIGA) -+ MUTEXSEM hostsem; -+ MUTEXSEM resolvsem; -+ MUTEXSEM lsem; -+@@ -94,9 +100,13 @@ -+ char *configpath = NULL; /* Config file name */ -+ char **saved_envp; -+ -+-#ifdef HAVE_FORK -++/* mypid: needed by HAVE_FORK and AMIGA targets */ -++#if defined(HAVE_FORK) || defined(AMIGA) -++int mypid; -++#endif -+ -+-int mypid, got_sighup, got_sigchld; -++#ifdef HAVE_FORK -++int got_sighup, got_sigchld; -+ -+ void chld (int *childcount) -+ { -+@@ -195,9 +205,13 @@ -+ #endif -+ " -C reload on config change\n" -+ " -c run client only\n" -++#ifndef AMIGA -+ " -i run server on stdin/stdout pipe (by inetd or other)\n" -++#endif -+ " -f node run server protected session with this node\n" -++#ifndef AMIGA -+ " -a ip assume remote address when running with '-i' switch\n" -++#endif -+ #if defined(BINKD9X) -+ " -t cmd (start|stop|restart|status|install|uninstall) service(s)\n" -+ " -S name set Win9x service name, all - use all services\n" -+@@ -312,12 +326,14 @@ -+ case 'c': -+ client_flag = 1; -+ break; -++#ifndef AMIGA -+ case 'i': -+ inetd_flag = 1; -+ break; -+ case 'a': /* remote IP address */ -+ remote_addr = strdup(optarg); -+ break; -++#endif -+ case 'f': /* remote FTN address */ -+ remote_node = strdup(optarg); -+ break; -+@@ -416,6 +432,28 @@ -+ } -+ if (optind\n", extract_filename(argv[0])); -++ fprintf(stderr, " Use -P
for polling a specific node (e.g., -P 1:23/456.7)\n"); -++ exit(1); -++ } -++ -++ /* Check for leftover FTN addresses in extra arguments */ -++ while (optind < argc) -++ { -++ char *extra = argv[optind++]; -++ if (strchr(extra, ':') || strchr(extra, '@')) -++ { -++ fprintf(stderr, "%s: Error: Unexpected FTN address '%s' in arguments.\n", extract_filename(argv[0]), extra); -++ fprintf(stderr, " Use -P
before the config file to poll a node.\n"); -++ exit(1); -++ } -++ } -++ -+ #ifdef OS2 -+ if (optindloglevel, current_config->conlog, -+ current_config->logpath, current_config->nolog.first); -++ -++ /* Clean up old .bsy/.csy files at startup */ -++ cleanup_old_bsy(current_config); -+ } -+ else if (verbose_flag) -+ { -+@@ -665,9 +706,13 @@ -+ -+ if (p) -+ { -+- remote_addr = strdup(p); -+- p = strchr(remote_addr, ' '); -+- if (p) *p = '\0'; -++ remote_addr = strdup(p); -++ /* Guard against null pointer dereference if strdup fails */ -++ if (remote_addr) -++ { -++ p = strchr(remote_addr, ' '); -++ if (p) *p = '\0'; -++ } -+ } -+ } -+ /* not using stdin/stdout itself to avoid possible collisions */ -+@@ -677,6 +722,9 @@ -+ inetd_socket_out = dup(fileno(stdout)); -+ #ifdef UNIX -+ tempfd = open("/dev/null", O_RDWR); -++#elif defined(AMIGA) -++ /* NIL: is the native AmigaDOS null device (no ixemul) */ -++ tempfd = open("NIL:", O_RDWR); -+ #else -+ tempfd = open("nul", O_RDWR); -+ #endif -+@@ -707,6 +755,15 @@ -+ signal (SIGHUP, sighandler); -+ #endif -+ -++#ifdef AMIGA -++ /* AmigaOS 3: WaitSelect() loop, no fork/threads */ -++ { -++ BINKD_CONFIG *ev_cfg = lock_current_config(); -++ amiga_evloop_run(ev_cfg, server_flag, client_flag); -++ unlock_config_structure(ev_cfg, 0); -++ return 0; -++ } -++#else -+ if (client_flag && !server_flag) -+ { -+ clientmgr (0); -+@@ -721,7 +778,9 @@ -+ if (client_flag && (pidcmgr = branch (clientmgr, 0, 0)) < 0) -+ { -+ Log (0, "cannot branch out"); -++ exit (1); -+ } -++#endif /* !AMIGA */ -+ -+ if (*current_config->pid_file) -+ { -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/binlog.c binkd/binlog.c -+--- binkd_pgul/binlog.c 2026-04-16 17:49:00.000000000 +0100 -++++ binkd/binlog.c 2026-04-22 06:50:46.000000000 +0100 -+@@ -35,6 +35,10 @@ -+ #include "tools.h" -+ #include "sem.h" -+ -++#if defined(HAVE_THREADS) || defined(AMIGA) -++extern MUTEXSEM blsem; -++#endif -++ -+ /* Write 16-bit integer to file in intel bytes order */ -+ static int fput16(u16 arg, FILE *file) -+ { -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/branch.c binkd/branch.c -+--- binkd_pgul/branch.c 2026-04-16 17:49:00.000000000 +0100 -++++ binkd/branch.c 2026-04-26 09:12:44.314385608 +0100 -+@@ -20,12 +20,6 @@ -+ #include "tools.h" -+ #include "sem.h" -+ -+-#ifdef AMIGA -+-int ix_vfork (void); -+-void vfork_setup_child (void); -+-void ix_vfork_resume (void); -+-#endif -+- -+ #ifdef WITH_PTHREADS -+ typedef struct { -+ void (*F) (void *); -+@@ -132,23 +126,6 @@ -+ #endif -+ #endif -+ -+-#ifdef AMIGA -+- /* this is rather bizzare. this function pretends to be a fork and behaves -+- * like one, but actually it's a kind of a thread. so we'll need semaphores */ -+- -+- if (!(rc = ix_vfork ())) -+- { -+- vfork_setup_child (); -+- ix_vfork_resume (); -+- F (arg); -+- exit (0); -+- } -+- else if (rc < 0) -+- { -+- Log (1, "ix_vfork: %s", strerror (errno)); -+- } -+-#endif -+- -+ #if defined(DOS) || defined(DEBUGCHILD) -+ rc = 0; -+ F (arg); -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/breaksig.c binkd/breaksig.c -+--- binkd_pgul/breaksig.c 2026-04-16 17:49:00.000000000 +0100 -++++ binkd/breaksig.c 2026-04-26 10:25:19.576809578 +0100 -+@@ -46,6 +46,16 @@ -+ { -+ atexit (exitfunc); -+ -++#ifdef AMIGA -++ /* AmigaOS / libnix: signal() maps SIGINT -> SIGBREAKF_CTRL_C -++ * Register exitsig() so that Ctrl+C sets binkd_exit=1 even when -++ * the process is NOT blocked inside WaitSelect() (e.g. during disk -++ * I/O). When inside WaitSelect(), amiga_select_wrap() in bsdsock.h -++ * detects the break and sets binkd_exit=1 directly without going -++ * through this signal handler. */ -++ signal (SIGINT, exitsig); -++ signal (SIGTERM, exitsig); -++#else -+ #ifdef SIGBREAK -+ signal (SIGBREAK, exitsig); -+ #endif -+@@ -58,5 +68,6 @@ -+ #ifdef SIGTERM -+ signal (SIGTERM, exitsig); -+ #endif -++#endif /* AMIGA */ -+ return 1; -+ } -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/bsycleanup.c binkd/bsycleanup.c -+--- binkd_pgul/bsycleanup.c 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/bsycleanup.c 2026-04-26 11:01:23.269049076 +0100 -+@@ -0,0 +1,122 @@ -++/* -++ * bsycleanup.c -- Cleanup functions for .bsy/.csy/.try files -++ * -++ * bsycleanup.c is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#include -++#include -++#include -++#include -++#include -++ -++#include "sys.h" -++#include "readcfg.h" -++#include "ftnq.h" -++#include "ftnnode.h" -++#include "tools.h" -++#include "readdir.h" -++ -++/* -++ * Clean up old .bsy and .csy files at startup -++ * Scans all domain outbounds -++ */ -++ -++/* Helper: scan a single directory for .bsy/.csy files and delete them */ -++static void scan_and_delete_bsy_in_dir(const char *dir, BINKD_CONFIG *config) -++{ -++ DIR *dp; -++ struct dirent *de; -++ char buf[MAXPATHLEN + 1]; -++ -++ if ((dp = opendir(dir)) == 0) -++ { -++ return; -++ } -++ -++ while ((de = readdir(dp)) != 0) -++ { -++ char *s = de->d_name; -++ int len = strlen(s); -++ -++ if (len > 4 && (!STRICMP(s + len - 4, ".bsy") || !STRICMP(s + len - 4, ".csy") || !STRICMP(s + len - 4, ".try"))) -++ { -++ strnzcpy(buf, dir, sizeof(buf)); -++ strnzcat(buf, PATH_SEPARATOR, sizeof(buf)); -++ strnzcat(buf, s, sizeof(buf)); -++ -++ Log(2, "deleting %s", buf); -++ -++ delete(buf); -++ } -++ } -++ -++ closedir(dp); -++} -++ -++void cleanup_old_bsy(BINKD_CONFIG *config) -++{ -++ DIR *dp; -++ char outb_path[MAXPATHLEN + 1], base_path[MAXPATHLEN + 1]; -++ struct dirent *de; -++ FTN_DOMAIN *curr_domain; -++ int len; -++ -++ Log(2, "cleaning up .bsy/.csy/.try files at startup"); -++ -++ /* Scan all domain outbounds */ -++ for (curr_domain = config->pDomains.first; curr_domain; curr_domain = curr_domain->next) -++ { -++ if (curr_domain->alias4 != 0) -++ continue; -++ -++ /* Build base path: path + separator */ -++ strnzcpy(base_path, curr_domain->path, sizeof(base_path)); -++#ifndef AMIGA -++ if (base_path[strlen(base_path) - 1] == ':') -++ strcat(base_path, PATH_SEPARATOR); -++#endif -++ -++#ifdef AMIGADOS_4D_OUTBOUND -++ if (config->aso) -++ { -++ /* ASO mode: direct outbound path */ -++ strnzcpy(outb_path, base_path, sizeof(outb_path)); -++ strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); -++ strnzcat(outb_path, curr_domain->dir, sizeof(outb_path)); -++ Log(7, "cleanup_old_bsy (ASO): scanning domain '%s', path '%s'", curr_domain->name, outb_path); -++ scan_and_delete_bsy_in_dir(outb_path, config); -++ } -++ else -++#endif -++ { -++ /* BSO mode: scan for outbound.xxx directories */ -++ Log(7, "cleanup_old_bsy (BSO): scanning domain '%s', base '%s'", curr_domain->name, base_path); -++ -++ if ((dp = opendir(base_path)) == 0) -++ continue; -++ -++ len = strlen(curr_domain->dir); -++ -++ while ((de = readdir(dp)) != 0) -++ { -++ /* Match outbound or outbound.xxx */ -++ if (!STRNICMP(de->d_name, curr_domain->dir, len) && (de->d_name[len] == 0 || (de->d_name[len] == '.' && isxdigit(de->d_name[len + 1])))) -++ { -++ strnzcpy(outb_path, base_path, sizeof(outb_path)); -++ strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); -++ strnzcat(outb_path, de->d_name, sizeof(outb_path)); -++ -++ Log(7, "cleanup_old_bsy (BSO): scanning outbound dir '%s'", outb_path); -++ -++ scan_and_delete_bsy_in_dir(outb_path, config); -++ } -++ } -++ -++ closedir(dp); -++ } -++ } -++} -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/bsycleanup.h binkd/bsycleanup.h -+--- binkd_pgul/bsycleanup.h 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/bsycleanup.h 2026-04-26 13:11:39.607856201 +0100 -+@@ -0,0 +1,19 @@ -++/* -++ * bsycleanup.h -- Cleanup functions for .bsy/.csy/.try files -++ * -++ * bsycleanup.h is a part of binkd project -++ * -++ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++ * Licensed under the GNU GPL v2 or later -++ */ -++ -++#ifndef _BSYCLEANUP_H -++#define _BSYCLEANUP_H -++ -++#include "readcfg.h" -++ -++ -++/* cleanup_old_bsy -- Clean up old .bsy/.csy/.try files at startup */ -++void cleanup_old_bsy(BINKD_CONFIG *config); -++ -++#endif /* _BSYCLEANUP_H */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/btypes.h binkd/btypes.h -+--- binkd_pgul/btypes.h 2026-04-16 17:49:00.000000000 +0100 -++++ binkd/btypes.h 2026-04-25 20:39:58.604145925 +0100 -+@@ -73,6 +73,7 @@ -+ int HC_flag; -+ int restrictIP; -+ int NP_flag; /* no proxy */ -++ int NC_flag; /* no compression */ -+ -+ time_t hold_until; -+ int busy; /* 0=free, 'c'=.csy, other=.bsy */ -+diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/cambios.diff binkd/cambios.diff -+--- binkd_pgul/cambios.diff 1970-01-01 00:00:00.000000000 +0000 -++++ binkd/cambios.diff 2026-04-26 16:03:26.856780412 +0100 -+@@ -0,0 +1,8223 @@ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/bsdsock.c binkd_pgul/amiga/bsdsock.c -++--- binkd/amiga/bsdsock.c 2026-04-26 11:01:10.438650436 +0100 -+++++ binkd_pgul/amiga/bsdsock.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,79 +0,0 @@ -++-/* -++- * bsdsock.c -- bsdsocket.library lifecycle for AmigaOS 3 -++- * -++- * bsdsock.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include -++-#include -++-#include -++-#include -++-#include -++-#include -++- -++-/* Linker-compatibility global. Never used at runtime */ -++-struct Library *SocketBase = NULL; -++- -++-/* Suppress conflicting C prototypes from clib/bsdsocket_protos.h */ -++-#ifndef CLIB_BSDSOCKET_PROTOS_H -++-#define CLIB_BSDSOCKET_PROTOS_H -++-#endif -++- -++-#include -++-#include -++- -++-extern void Log(int lev, const char *s, ...); -++- -++-/* _amiga_get_socket_base -- returns bsdsocket.library handle for calling task */ -++-struct Library *_amiga_get_socket_base(void) -++-{ -++- return (struct Library *)FindTask(NULL)->tc_UserData; -++-} -++- -++-int amiga_sock_init(void) -++-{ -++- struct Task *me = FindTask(NULL); -++- struct Library *base; -++- -++- if (me->tc_UserData) -++- return 0; /* already open for this task */ -++- -++- base = OpenLibrary("bsdsocket.library", 0UL); -++- -++- if (!base) -++- { -++- fprintf(stderr, "amiga_sock_init: cannot open bsdsocket.library\n"); -++- return -1; -++- } -++- -++- /* Store in tc_UserData and global SocketBase */ -++- me->tc_UserData = (APTR)base; -++- SocketBase = base; -++- -++- /* Link the per-task errno to the TCP stack. */ -++- SetErrnoPtr(&errno, (LONG)sizeof(errno)); -++- -++- return 0; -++-} -++- -++-void amiga_sock_cleanup(void) -++-{ -++- struct Task *me = FindTask(NULL); -++- struct Library *base = (struct Library *)me->tc_UserData; -++- -++- if (base) -++- { -++- me->tc_UserData = NULL; -++- SocketBase = NULL; -++- CloseLibrary(base); -++- } -++-} -++- -++-int amiga_child_sock_init(void) -++-{ -++- /* Child inherits tc_UserData = NULL, opens new handle */ -++- return amiga_sock_init(); -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/bsdsock.h binkd_pgul/amiga/bsdsock.h -++--- binkd/amiga/bsdsock.h 2026-04-26 10:49:27.706200054 +0100 -+++++ binkd_pgul/amiga/bsdsock.h 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,155 +0,0 @@ -++-/* -++- * bsdsock.h -- bsdsocket.library init and POSIX compat shims for AmigaOS 3 -++- * -++- * bsdsock.h is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifndef _AMIGA_BSDSOCK_H -++-#define _AMIGA_BSDSOCK_H -++- -++-#ifdef AMIGA -++- -++-#include -++-#include -++-#include -++-#include -++- -++-/* Suppress conflicting C prototypes from roadshow */ -++-#ifndef CLIB_BSDSOCKET_PROTOS_H -++-#define CLIB_BSDSOCKET_PROTOS_H -++-#endif -++- -++-/* Undefine MCLBYTES/MCLSHIFT before roadshow headers */ -++-#ifdef MCLBYTES -++-#undef MCLBYTES -++-#endif -++-#ifdef MCLSHIFT -++-#undef MCLSHIFT -++-#endif -++- -++-/* Roadshow SDK network headers */ -++-#include -++-#include -++-#include "compat_netinet_in.h" -++-#include -++-#include -++-#include /* inline/bsdsocket.h, no clib protos */ -++- -++-/* Undefine conflicting unistd.h macros */ -++-#ifdef gethostid -++-#undef gethostid -++-#endif -++-#ifdef getdtablesize -++-#undef getdtablesize -++-#endif -++-#ifdef gethostname -++-#undef gethostname -++-#endif -++- -++-/* Per-task SocketBase override */ -++-struct Library *_amiga_get_socket_base(void); -++- -++-#ifdef SocketBase -++-#undef SocketBase -++-#endif -++-#define SocketBase _amiga_get_socket_base() -++- -++-/* Roadshow socket-specific errno values */ -++-#include -++- -++-#define BSDSOCK_HAS_TIMEVAL 1 -++- -++-/* Socket-specific errno values from roadshow sys/errno.h */ -++-#ifndef ENOTSOCK -++-#define ENOTSOCK 38 /* Socket operation on non-socket */ -++-#endif -++-#ifndef EOPNOTSUPP -++-#define EOPNOTSUPP 45 /* Operation not supported on socket */ -++-#endif -++-#ifndef ECONNREFUSED -++-#define ECONNREFUSED 61 /* Connection refused */ -++-#endif -++-#ifndef ETIMEDOUT -++-#define ETIMEDOUT 60 /* Connection timed out */ -++-#endif -++-#ifndef ECONNRESET -++-#define ECONNRESET 54 /* Connection reset by peer */ -++-#endif -++-#ifndef EHOSTUNREACH -++-#define EHOSTUNREACH 65 /* No route to host */ -++-#endif -++- -++-#include -++- -++-/* sockaddr_storage fallback for roadshow */ -++-#ifndef HAVE_SOCKADDR_STORAGE -++-#ifndef sockaddr_storage -++-struct sockaddr_storage -++-{ -++- unsigned short ss_family; -++- char __ss_pad[22]; /* enough for IPv4 */ -++-}; -++-#endif -++-#define HAVE_SOCKADDR_STORAGE 1 -++-#endif -++- -++-/* Library base functions */ -++-int amiga_sock_init(void); -++-void amiga_sock_cleanup(void); -++-int amiga_child_sock_init(void); -++- -++-/* getpid() is defined in sys.h for AMIGA */ -++-/* amiga_sleep and sleep are defined in sys.h for AMIGA */ -++- -++-/* select() -> WaitSelect() wrapper with Ctrl+C handling */ -++-#ifndef AMIGA_SELECT_DEFINED -++-#define AMIGA_SELECT_DEFINED -++- -++-/* Forward-declare binkd_exit */ -++-extern int binkd_exit; -++- -++-/* Forward-declare Log to avoid implicit declaration warning */ -++-extern void Log(int lev, char *s, ...); -++- -++-static int amiga_select_wrap(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) -++-{ -++- ULONG sigmask = SIGBREAKF_CTRL_C; -++- int rc = WaitSelect(nfds, readfds, writefds, exceptfds, timeout, &sigmask); -++- -++- /* Ctrl+C should break blocked select() loops immediately. */ -++- if ((sigmask & SIGBREAKF_CTRL_C) != 0) -++- { -++- Log(1, "Ctrl+C detected in WaitSelect, setting binkd_exit=1"); -++- binkd_exit = 1; -++- errno = EINTR; -++- return -1; -++- } -++- -++- return rc; -++-} -++- -++-#define select(n, r, w, e, t) amiga_select_wrap((n), (r), (w), (e), (t)) -++- -++-#endif /* AMIGA_SELECT_DEFINED */ -++- -++-/* FIONBIO via IoctlSocket */ -++-#ifndef FIONBIO -++-#define FIONBIO 0x8004667E -++-#endif -++-#ifndef ioctl -++-#define ioctl(s, req, arg) IoctlSocket((s), (req), (char *)(arg)) -++-#endif -++- -++-/* inet_ntoa -> Inet_NtoA (Amiga only) */ -++-#ifdef AMIGA -++-#ifdef inet_ntoa -++-#undef inet_ntoa -++-#endif -++-#define inet_ntoa(a) Inet_NtoA(a) -++-#endif -++- -++-#endif /* AMIGA */ -++-#endif /* _AMIGA_BSDSOCK_H */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/compat_netinet_in.h binkd_pgul/amiga/compat_netinet_in.h -++--- binkd/amiga/compat_netinet_in.h 2026-04-26 09:53:12.337417283 +0100 -+++++ binkd_pgul/amiga/compat_netinet_in.h 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,19 +0,0 @@ -++-/* -++- * compat_netinet_in.h -- Wrapper for netinet/in.h typedef clash -++- * -++- * compat_netinet_in.h is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifndef _AMIGA_COMPAT_NETINET_IN_H -++-#define _AMIGA_COMPAT_NETINET_IN_H -++- -++-#ifdef __GNUC__ -++-#include_next -++-#else -++-#include -++-#endif -++- -++-#endif /* _AMIGA_COMPAT_NETINET_IN_H */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/dirent.c binkd_pgul/amiga/dirent.c -++--- binkd/amiga/dirent.c 2026-04-26 11:40:07.539429919 +0100 -+++++ binkd_pgul/amiga/dirent.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,137 +0,0 @@ -++-/* -++- * dirent.c -- POSIX directory scanning for AmigaOS 3 -++- * -++- * dirent.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifdef AMIGA -++- -++-#include -++-#include -++-#include -++-#include -++-#include -++-#include -++- -++-#include "amiga/dirent.h" -++- -++-/* opendir -- locks directory and allocates state for readdir() */ -++-DIR *opendir(const char *path) -++-{ -++- DIR *dir; -++- -++- if (!path) -++- { -++- errno = EINVAL; -++- return NULL; -++- } -++- -++- dir = (DIR *)AllocMem((LONG)sizeof(DIR), MEMF_CLEAR); -++- -++- if (!dir) -++- { -++- errno = ENOMEM; -++- return NULL; -++- } -++- -++- dir->fib = (struct FileInfoBlock *)AllocMem((LONG)sizeof(struct FileInfoBlock), MEMF_CLEAR); -++- -++- if (!dir->fib) -++- { -++- FreeMem(dir, (LONG)sizeof(DIR)); -++- errno = ENOMEM; -++- return NULL; -++- } -++- -++- dir->lock = Lock((STRPTR)path, ACCESS_READ); -++- -++- if (!dir->lock) -++- { -++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -++- FreeMem(dir, (LONG)sizeof(DIR)); -++- errno = ENOENT; -++- return NULL; -++- } -++- -++- /* Examine the directory itself; this positions FIB for ExNext() */ -++- if (!Examine(dir->lock, dir->fib)) -++- { -++- UnLock(dir->lock); -++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -++- FreeMem(dir, (LONG)sizeof(DIR)); -++- errno = EACCES; -++- return NULL; -++- } -++- -++- /* fib_DirEntryType > 0 means this IS a directory */ -++- if (dir->fib->fib_DirEntryType <= 0) -++- { -++- UnLock(dir->lock); -++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -++- FreeMem(dir, (LONG)sizeof(DIR)); -++- errno = ENOTDIR; -++- return NULL; -++- } -++- -++- dir->first = 1; /* ExNext() has not been called yet */ -++- -++- return dir; -++-} -++- -++-/* readdir -- advances to next directory entry */ -++-struct dirent *readdir(DIR *dir) -++-{ -++- LONG dos_rc; -++- LONG dos_err; -++- -++- if (!dir) -++- { -++- errno = EINVAL; -++- return NULL; -++- } -++- -++- /* ExNext() advances past the last entry returned by Examine/ExNext */ -++- dos_rc = ExNext(dir->lock, dir->fib); -++- -++- if (!dos_rc) -++- { -++- dos_err = IoErr(); -++- -++- if (dos_err == ERROR_NO_MORE_ENTRIES) -++- return NULL; -++- -++- errno = EIO; -++- return NULL; -++- } -++- -++- /* Copy name into the entry buffer */ -++- strncpy(dir->entry.d_name, dir->fib->fib_FileName, AMIGA_NAME_MAX - 1); -++- dir->entry.d_name[AMIGA_NAME_MAX - 1] = '\0'; -++- dir->entry.d_ino = 0; -++- -++- return &dir->entry; -++-} -++- -++-/* closedir -- releases all resources associated with dir */ -++-int closedir(DIR *dir) -++-{ -++- if (!dir) -++- { -++- errno = EINVAL; -++- return -1; -++- } -++- -++- if (dir->lock) -++- UnLock(dir->lock); -++- -++- if (dir->fib) -++- FreeMem(dir->fib, (LONG)sizeof(struct FileInfoBlock)); -++- -++- FreeMem(dir, (LONG)sizeof(DIR)); -++- return 0; -++-} -++- -++-#endif /* AMIGA */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/dirent.h binkd_pgul/amiga/dirent.h -++--- binkd/amiga/dirent.h 2026-04-26 09:53:13.628932082 +0100 -+++++ binkd_pgul/amiga/dirent.h 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,53 +0,0 @@ -++-/* -++- * dirent.h -- POSIX directory scanning for AmigaOS 3 -++- * -++- * dirent.h is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifndef _AMIGA_DIRENT_H -++-#define _AMIGA_DIRENT_H -++- -++-#ifdef AMIGA -++- -++-#include -++-#include -++- -++-/* Maximum name length: AmigaDOS allows 107 characters */ -++-#define AMIGA_NAME_MAX 108 -++- -++-struct dirent -++-{ -++- unsigned long d_ino; /* inode -- always 0 on AmigaDOS */ -++- char d_name[AMIGA_NAME_MAX]; /* null-terminated file name */ -++-}; -++- -++-/* struct utimbuf for AmigaOS 3 without ixemul */ -++-#ifndef _AMIGA_UTIMBUF_DEFINED -++-#define _AMIGA_UTIMBUF_DEFINED -++- -++-struct utimbuf -++-{ -++- long actime; /* access time (unused by SetFileDate) */ -++- long modtime; /* modification time (POSIX time_t) */ -++-}; -++- -++-int utime(const char *path, const struct utimbuf *times); -++-#endif -++- -++-typedef struct _amiga_dir -++-{ -++- BPTR lock; /* directory lock */ -++- struct FileInfoBlock *fib; /* reusable FileInfoBlock */ -++- int first; /* flag: first call not yet */ -++- struct dirent entry; /* storage returned to caller */ -++-} DIR; -++- -++-DIR *opendir(const char *path); -++-struct dirent *readdir(DIR *dir); -++-int closedir(DIR *dir); -++- -++-#endif /* AMIGA */ -++-#endif /* _AMIGA_DIRENT_H */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop.c binkd_pgul/amiga/evloop.c -++--- binkd/amiga/evloop.c 2026-04-26 11:46:47.344533199 +0100 -+++++ binkd_pgul/amiga/evloop.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,509 +0,0 @@ -++-/* -++- * evloop.c -- non-blocking event loop for AmigaOS 3 -++- * -++- * evloop.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-/* Suppress clib bsdsocket prototypes before any socket header */ -++-#ifndef CLIB_BSDSOCKET_PROTOS_H -++-#define CLIB_BSDSOCKET_PROTOS_H -++-#endif -++- -++-#include -++-#include -++-#include -++- -++-#include -++-#include -++-#include -++- -++-#include "sys.h" -++-#include "readcfg.h" -++-#include "common.h" -++-#include "tools.h" -++-#include "protocol.h" -++-#include "sem.h" -++-#include "server.h" -++-#include "amiga/bsdsock.h" -++-#include "amiga/evloop.h" -++-#include "amiga/evloop_int.h" -++-#include "amiga/proto_amiga.h" -++- -++-/* Externals */ -++-extern SOCKET sockfd[MAX_LISTENSOCK]; -++-extern int sockfd_used; -++-extern int binkd_exit; -++-extern int server_flag, client_flag; -++- -++-/* Session table (shared with sock.c and session.c) */ -++-sess_t *sessions = NULL; -++-int max_sessions = 0; -++- -++-/* -++- * calc_max_sessions -- Compute session slot count from config + flags -++- * Shared by init and config-reload paths -++- */ -++-static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) -++-{ -++- int servers = config->max_servers; -++- int clients = config->max_clients; -++- int total; -++- -++- if (servers == 0 && clients == 0) -++- { -++- Log(5, "DEBUG: Using default 2 slots (no config found)"); -++- return 2; -++- } -++- -++- Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); -++- -++- if (srv_flag && servers < 1) -++- servers = 1; -++- -++- if (cli_flag && clients < 1) -++- clients = 1; -++- -++- total = servers + clients; -++- -++- if (total < 2) -++- total = 2; -++- -++- Log(5, "DEBUG: Calculated max_sessions=%d", total); -++- -++- return total; -++-} -++- -++-/* init_session_table -- Allocate and zero-initialise the session array */ -++-static int init_session_table(int slots) -++-{ -++- int i; -++- -++- sessions = calloc(slots, sizeof(sess_t)); -++- -++- if (!sessions) -++- { -++- Log(1, "Failed to allocate session table"); -++- return 0; -++- } -++- -++- for (i = 0; i < slots; i++) -++- { -++- memset(&sessions[i], 0, sizeof(sess_t)); -++- memset(&sessions[i].state, 0, sizeof(STATE)); -++- sessions[i].fd = INVALID_SOCKET; -++- sessions[i].phase = SESS_FREE; -++- } -++- -++- return 1; -++-} -++- -++-/* -++- * build_fdsets -- Populate r/w fd_sets from listen sockets and sessions -++- * Returns the highest fd seen (maxfd) -++- */ -++-static int build_fdsets(fd_set *r, fd_set *w) -++-{ -++- int i, maxfd = 0; -++- -++- FD_ZERO(r); -++- FD_ZERO(w); -++- -++- /* server side: listen sockets */ -++- for (i = 0; i < sockfd_used; i++) -++- { -++- if (sockfd[i] != INVALID_SOCKET) -++- { -++- FD_SET(sockfd[i], r); -++- -++- if ((int)sockfd[i] > maxfd) -++- maxfd = (int)sockfd[i]; -++- } -++- } -++- -++- /* client + server sessions */ -++- for (i = 0; i < max_sessions; i++) -++- { -++- sess_t *s = &sessions[i]; -++- -++- if (s->phase == SESS_FREE || s->fd == INVALID_SOCKET) -++- continue; -++- -++- if ((int)s->fd > maxfd) -++- maxfd = (int)s->fd; -++- -++- if (s->phase == SESS_CONNECTING) -++- { -++- /* client: waiting for non-blocking connect() */ -++- FD_SET(s->fd, w); -++- } -++- else -++- { -++- /* Server or established client session */ -++- FD_SET(s->fd, r); -++- -++- if (s->state.msgs || s->state.oleft || s->state.send_eof || (s->state.out.f && !s->state.off_req_sent && !s->state.waiting_for_GOT)) -++- FD_SET(s->fd, w); -++- } -++- } -++- -++- Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); -++- return maxfd; -++-} -++- -++-/* -++- * handle_server_accept -- Accept new inbound connections on all listen fds -++- * Returns 0 normally, -1 if binkd_exit was set during accept -++- */ -++-static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) -++-{ -++- int i; -++- -++- Log(5, "DEBUG: Before accept loop"); -++- -++- for (i = 0; i < sockfd_used; i++) -++- { -++- if (FD_ISSET(sockfd[i], r)) -++- do_accept(sockfd[i], config); -++- -++- if (binkd_exit) -++- { -++- Log(5, "DEBUG: binkd_exit during accept loop"); -++- return -1; -++- } -++- } -++- -++- Log(5, "DEBUG: After accept loop"); -++- -++- return 0; -++-} -++- -++-/* -++- * advance_sessions -- Step every active session (server + client) -++- * Returns the number of non-free sessions processed -++- */ -++-static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) -++-{ -++- int i, active = 0; -++- -++- Log(5, "DEBUG: Before advance sessions"); -++- -++- for (i = 0; i < max_sessions; i++) -++- { -++- sess_t *s = &sessions[i]; -++- -++- Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); -++- -++- if (s->phase == SESS_FREE) -++- continue; -++- -++- active++; -++- -++- if (s->phase == SESS_CONNECTING) -++- { -++- /* client: Complete the non-blocking connect */ -++- if (FD_ISSET(s->fd, w)) -++- check_connect(i, config); -++- } -++- else -++- { -++- int rd = FD_ISSET(s->fd, r); -++- int wr = FD_ISSET(s->fd, w); -++- -++- /* Always step: protocol must advance internal state even -++- * when WaitSelect reports no activity (e.g. second batch -++- * EOB send, TCP FIN detection after remote closes) */ -++- do_session_step(i, rd, wr, config); -++- } -++- -++- if (binkd_exit) -++- break; -++- } -++- -++- return active; -++-} -++- -++-/* -++- * handle_config_reload -- Resize session table and reopen listen sockets -++- * Returns 1 if the caller should break out of the main loop, 0 otherwise -++- */ -++-static int handle_config_reload(BINKD_CONFIG **config, int srv_flag, int cli_flag) -++-{ -++- int i, new_max; -++- BINKD_CONFIG *nc = lock_current_config(); -++- -++- if (nc) -++- { -++- new_max = calc_max_sessions(nc, srv_flag, cli_flag); -++- -++- if (new_max != max_sessions) -++- { -++- sess_t *ns = realloc(sessions, new_max * sizeof(sess_t)); -++- -++- if (ns) -++- { -++- for (i = max_sessions; i < new_max; i++) -++- { -++- memset(&ns[i], 0, sizeof(sess_t)); -++- memset(&ns[i].state, 0, sizeof(STATE)); -++- ns[i].fd = INVALID_SOCKET; -++- ns[i].phase = SESS_FREE; -++- } -++- -++- sessions = ns; -++- max_sessions = new_max; -++- -++- Log(4, "Session table resized to %d slots", max_sessions); -++- } -++- else -++- { -++- Log(1, "Failed to resize session table, keeping current size"); -++- } -++- } -++- -++- unlock_config_structure(nc, 0); -++- } -++- -++- close_listen_sockets(); -++- *config = lock_current_config(); -++- -++- if (srv_flag && open_listen_sockets(*config) < 0) -++- { -++- unlock_config_structure(*config, 0); -++- return 1; /* fatal — break main loop */ -++- } -++- -++- unlock_config_structure(*config, 0); -++- *config = lock_current_config(); -++- return 0; -++-} -++- -++-/* evloop_cleanup -- Close sessions and free resources on exit */ -++-static void evloop_cleanup(BINKD_CONFIG *config, int config_locked) -++-{ -++- int i; -++- -++- if (config_locked) -++- unlock_config_structure(config, 0); -++- -++- if (sessions) -++- { -++- for (i = 0; i < max_sessions; i++) -++- { -++- if (sessions[i].phase == SESS_RUNNING) -++- { -++- amiga_proto_close(&sessions[i].state, config, 0); -++- -++- if (sessions[i].inbound) -++- n_servers--; -++- else -++- n_clients--; -++- } -++- else if (sessions[i].phase == SESS_CONNECTING) -++- { -++- n_clients--; -++- } -++- sess_free(i); -++- } -++- -++- free(sessions); -++- sessions = NULL; -++- } -++- -++- close_listen_sockets(); -++- amiga_sock_cleanup(); -++- Log(4, "evloop done"); -++-} -++- -++-/* amiga_evloop_run -- Entry point: init, then main WaitSelect() loop */ -++-void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) -++-{ -++- int config_locked = 0; -++- time_t last_rescan = 0; -++- time_t now; -++- fd_set r, w; -++- struct timeval tv; -++- int n, maxfd; -++- int active_sessions = 0; -++- static int idle_loops = 0; -++- -++- /* Sync globals so try_outbound() and friends see the correct flags */ -++- server_flag = srv_flag; -++- client_flag = cli_flag; -++- -++- sockfd_used = 0; -++- srand((unsigned int)time(NULL)); -++- -++- Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); -++- Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); -++- -++- /* Initialise session table */ -++- max_sessions = calc_max_sessions(config, server_flag, client_flag); -++- -++- if (max_sessions < 2) -++- { -++- Log(2, "WARNING: max_sessions=%d is too low, forcing to 2", max_sessions); -++- max_sessions = 2; -++- } -++- -++- Log(4, "evloop start (AmigaOS 3, WaitSelect, %d slots)", max_sessions); -++- -++- if (!init_session_table(max_sessions)) -++- return; -++- -++- /* server: Open listen sockets */ -++- if (server_flag && open_listen_sockets(config) < 0) -++- { -++- Log(0, "evloop: cannot open listen sockets"); -++- free(sessions); -++- sessions = NULL; -++- return; -++- } -++- -++- Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); -++- -++- /* Initial outbound attempt before waiting (important for poll -p mode) */ -++- Log(5, "DEBUG: Initial try_outbound before main loop"); -++- try_outbound(config); -++- last_rescan = time(NULL); /* Reset timer since we just did an attempt */ -++- -++- /* ===== Main loop ===== */ -++- for (;;) -++- { -++- if (binkd_exit) -++- { -++- Log(1, "binkd_exit detected at loop start, exiting"); -++- break; -++- } -++- -++- /* Build fd_sets */ -++- Log(5, "DEBUG: Building fd_sets"); -++- maxfd = build_fdsets(&r, &w); -++- -++- tv.tv_sec = 1; -++- tv.tv_usec = 0L; -++- -++- /* WaitSelect() with nfds>0 but empty fd_sets causes guru #80000006 :/ -++- * Use select(0,...) as a pure sleep when no sockets are active */ -++- if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) -++- maxfd = 1; -++- -++- Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); -++- -++- if (maxfd == 0) -++- { -++- /* No sockets yet -- use Delay() instead of select(0,...) -++- * as WaitSelect with nfds=0 can block indefinitely on AmigaOS */ -++- Delay(50); /* 1 second = 50 ticks at 50Hz PAL */ -++- n = 0; /* simulate timeout */ -++- } -++- else -++- n = select(maxfd + 1, &r, &w, NULL, &tv); -++- -++- Log(5, "DEBUG: select() returned n=%d", n); -++- -++- if (binkd_exit) -++- { -++- Log(1, "binkd_exit detected after select(), exiting"); -++- break; -++- } -++- -++- Delay(1UL); /* 1 tick = 20ms @ 50Hz, prevents CPU hogging */ -++- -++- /* Handle select errors */ -++- if (n < 0) -++- { -++- if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) -++- { -++- Log(5, "DEBUG: select interrupted, continuing"); -++- continue; -++- } -++- -++- if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) -++- { -++- Log(2, "select: %s, reopening", TCPERR()); -++- -++- close_listen_sockets(); -++- -++- if (server_flag && open_listen_sockets(config) < 0) -++- break; -++- -++- continue; -++- } -++- -++- Log(1, "select: %s", TCPERR()); -++- break; -++- } -++- else if (n == 0) -++- { -++- Log(5, "DEBUG: select timeout, continuing"); -++- } -++- -++- /* server: Accept new inbound connections */ -++- if (server_flag) -++- { -++- if (handle_server_accept(&r, config) < 0) -++- break; -++- } -++- -++- if (binkd_exit) -++- break; -++- -++- /* server + client: Advance all active sessions */ -++- active_sessions = advance_sessions(&r, &w, config); -++- -++- if (binkd_exit) -++- break; -++- -++- /* client: Time-based outbound scan + config reload */ -++- now = time(NULL); -++- -++- if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) -++- { -++- Log(5, "DEBUG: Before try_outbound"); -++- -++- try_outbound(config); -++- -++- Log(5, "DEBUG: After try_outbound"); -++- -++- if (checkcfg()) -++- { -++- if (handle_config_reload(&config, server_flag, client_flag)) -++- break; -++- -++- config_locked = 1; -++- } -++- -++- config->q_present = 0; -++- last_rescan = now; -++- } -++- -++- /* client: Poll-mode idle exit */ -++- /* Reset counter whenever there is something going on */ -++- if (n_clients > 0 || active_sessions > 0) -++- { -++- if (idle_loops > 0) -++- Log(2, "Activity detected, reset idle counter"); -++- -++- idle_loops = 0; -++- } -++- -++- if (!server_flag && active_sessions == 0 && n_clients == 0) -++- { -++- idle_loops++; -++- -++- Log(2, "Idle loop %d/2 (no server, no sessions, no clients)", idle_loops); -++- -++- if (idle_loops > 1) -++- { -++- Log(0, "the queue is empty, quitting..."); -++- break; -++- } -++- } -++- } -++- /* ===== End main loop ===== */ -++- -++- evloop_cleanup(config, config_locked); -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop.h binkd_pgul/amiga/evloop.h -++--- binkd/amiga/evloop.h 2026-04-26 11:47:03.034239655 +0100 -+++++ binkd_pgul/amiga/evloop.h 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,34 +0,0 @@ -++-/* -++- * evloop.h -- non-blocking event loop for AmigaOS 3 -++- * -++- * evloop.h is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifndef _AMIGA_EVLOOP_H -++-#define _AMIGA_EVLOOP_H -++- -++-#ifdef AMIGA -++- -++-#include "readcfg.h" -++-#include "protoco2.h" /* STATE */ -++- -++-/* amiga_proto_step() return codes — also used by protocol.c */ -++-#define APROTO_RUNNING 0 /* session alive, call again */ -++-#define APROTO_DONE_OK 1 /* session finished, success */ -++-#define APROTO_DONE_ERR 2 /* session failed */ -++- -++-/* -++- * amiga_evloop_run -- entry point replacing servmgr() + clientmgr() -++- * -++- * Opens listen sockets (when server_flag), then runs a WaitSelect() -++- * loop that multiplexes sessions dynamically based on config->max_servers -++- * and config->max_clients. Minimum 2 sessions are always allocated -++- * Returns only when binkd_exit != 0 -++- */ -++-void amiga_evloop_run(BINKD_CONFIG *config, int server_flag, int client_flag); -++- -++-#endif /* AMIGA */ -++-#endif /* _AMIGA_EVLOOP_H */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/evloop_int.h binkd_pgul/amiga/evloop_int.h -++--- binkd/amiga/evloop_int.h 2026-04-26 11:02:58.166969078 +0100 -+++++ binkd_pgul/amiga/evloop_int.h 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,68 +0,0 @@ -++-/* -++- * evloop_int.h -- internal types shared by evloop.c, sock.c, session.c -++- * -++- * evloop_int.h is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifndef AMIGA_EVLOOP_INT_H -++-#define AMIGA_EVLOOP_INT_H -++- -++-#include "protoco2.h" -++-#include "amiga/bsdsock.h" -++-#include "ftnnode.h" -++-#include "readcfg.h" -++- -++-/* Session lifecycle */ -++-typedef enum -++-{ -++- SESS_FREE = 0, /* slot available */ -++- SESS_CONNECTING = 1, /* waiting for connect() */ -++- SESS_RUNNING = 2 /* BinkP session active */ -++-} sess_phase_t; -++- -++-/* Per-session state */ -++-typedef struct -++-{ -++- sess_phase_t phase; -++- SOCKET fd; -++- STATE state; -++- int inbound; /* 1=accepted, 0=outbound */ -++- -++- FTN_NODE *node; -++- struct addrinfo *ai_head; /* full getaddrinfo list */ -++- struct addrinfo *ai_cur; /* candidate being tried */ -++- time_t conn_start; -++- -++- char host[BINKD_FQDNLEN + 1]; -++- char port[MAXPORTSTRLEN + 1]; -++- char ip[BINKD_FQDNLEN + 1]; -++- -++- time_t last_io; -++-} sess_t; -++- -++-/* Globals defined in evloop.c */ -++-extern sess_t *sessions; -++-extern int max_sessions; -++- -++-/* Defined in server.c and client.c respectively */ -++-extern int n_servers; -++-extern int n_clients; -++- -++-/* sock.c */ -++-void set_nonblock(SOCKET fd); -++-int open_listen_sockets(BINKD_CONFIG *config); -++-void close_listen_sockets(void); -++- -++-/* session.c */ -++-int sess_alloc(void); -++-void sess_free(int idx); -++-void do_accept(SOCKET lfd, BINKD_CONFIG *config); -++-int start_connect(sess_t *s, BINKD_CONFIG *config); -++-void check_connect(int idx, BINKD_CONFIG *config); -++-int try_outbound(BINKD_CONFIG *config); -++-void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config); -++- -++-#endif /* AMIGA_EVLOOP_INT_H */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/proto_amiga.c binkd_pgul/amiga/proto_amiga.c -++--- binkd/amiga/proto_amiga.c 2026-04-26 11:52:04.887130443 +0100 -+++++ binkd_pgul/amiga/proto_amiga.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,269 +0,0 @@ -++-/* -++- * proto_amiga.c -- Amiga non-blocking BinkP protocol implementation -++- * -++- * proto_amiga.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include -++-#include -++-#include -++-#include -++-#include -++- -++-#include "sys.h" -++-#include "readcfg.h" -++-#include "common.h" -++-#include "protocol.h" -++-#include "ftnaddr.h" -++-#include "ftnnode.h" -++-#include "ftnq.h" -++-#include "tools.h" -++-#include "bsy.h" -++-#include "inbound.h" -++-#include "protoco2.h" -++-#include "prothlp.h" -++-#include "binlog.h" -++-#include "evloop.h" -++- -++-/* External functions from protocol.c */ -++-extern int init_protocol(STATE *state, SOCKET s_in, SOCKET s_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config); -++-extern int banner(STATE *state, BINKD_CONFIG *config); -++-extern int recv_block(STATE *state, BINKD_CONFIG *config); -++-extern int send_block(STATE *state, BINKD_CONFIG *config); -++-extern void bsy_touch(BINKD_CONFIG *config); -++-extern FTNQ *process_rcvdlist(STATE *state, FTNQ *q, BINKD_CONFIG *config); -++-extern int start_file_transfer(STATE *state, FTNQ *q, BINKD_CONFIG *config); -++-extern void ND_set_status(const char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config); -++-extern void deinit_protocol(STATE *state, BINKD_CONFIG *config, int status); -++-extern void evt_set(EVTQ *eq); -++-extern void msg_send2(STATE *state, t_msg m, char *s1, char *s2); -++- -++-/* External functions from other modules */ -++-extern void log_end_of_session(int err, STATE *state, BINKD_CONFIG *config); -++-extern void inb_remove_partial(STATE *state, BINKD_CONFIG *config); -++-extern void good_try(FTN_ADDR *fa, char *comment, BINKD_CONFIG *config); -++-extern void bad_try(FTN_ADDR *fa, const char *error, const int where, BINKD_CONFIG *config); -++-extern int create_poll(FTN_ADDR *fa, int flvr, BINKD_CONFIG *config); -++-extern void hold_node(FTN_ADDR *fa, time_t hold_until, BINKD_CONFIG *config); -++-extern int binkd_exit; -++- -++-/* External variables */ -++-extern int n_servers; -++- -++-/* -++- * amiga_proto_open -- Initialise a session and send the BinkP banner -++- * -++- * fd : Connected socket (same fd for in and out) -++- * to : Outbound node, NULL for inbound -++- * fa : Local AKA to use, may be NULL -++- * host : Remote hostname or dotted-IP string (caller-owned, stable) -++- * port : Remote port string, may be NULL -++- * dst_ip : Numeric remote IP, may be NULL (falls back to host) -++- * config : Current config -++- * -++- * Returns 0 on success, -1 on error (caller must close fd) -++- */ -++-int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config) -++-{ -++- struct sockaddr_storage sa; -++- socklen_t salen = (socklen_t)sizeof(sa); -++- char ownhost[BINKD_FQDNLEN + 1]; -++- char ownserv[MAXSERVNAME + 1]; -++- int rc; -++- -++- if (!init_protocol(state, fd, fd, to, fa, config)) -++- return -1; -++- -++- /* Peer identity for logging and %ip config checks */ -++- state->ipaddr = dst_ip ? (char *)dst_ip : (char *)host; -++- state->peer_name = (host && *host) ? (char *)host : state->ipaddr; -++- -++- /* local endpoint: Not used further, skip to avoid dangling pointer */ -++- -++- Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); -++- -++- /* banner() sends M_NUL lines and ADR messages */ -++- if (!banner(state, config)) -++- return -1; -++- -++- /* refuse if server limit reached */ -++- if (!to && n_servers > config->max_servers) -++- { -++- Log(1, "too many servers"); -++- msg_send2(state, M_BSY, "Too many servers", 0); -++- -++- return -1; -++- } -++- -++- return 0; -++-} -++- -++-/* -++- * amiga_proto_step -- Run one recv/send iteration of the BinkP loop -++- * -++- * readable : Non-zero if the socket has incoming data (from WaitSelect) -++- * writable : Non-zero if the socket can accept outgoing data -++- * -++- * Returns APROTO_RUNNING, APROTO_DONE_OK, or APROTO_DONE_ERR -++- * -++- * This is the loop body of protocol() with the select() call removed -++- * recv_block() and send_block() already handle EWOULDBLOCK gracefully, -++- * so calling this on a non-blocking socket is safe -++- */ -++-int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config) -++-{ -++- FTNQ *q; -++- int no; -++- -++- if (state->io_error) -++- return APROTO_DONE_ERR; -++- -++- /* Advance outgoing file queue if nothing is being sent */ -++- if (!state->local_EOB && state->q && !state->out.f && !state->waiting_for_GOT && !state->off_req_sent && state->state != P_NULL) -++- { -++- while (1) -++- { -++- q = 0; -++- if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) -++- { -++- if (start_file_transfer(state, q, config)) -++- break; -++- } -++- else -++- { -++- q_free(state->q, config); -++- state->q = 0; -++- break; -++- } -++- } -++- } -++- -++- /* Nothing left to send — issue EOB */ -++- if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls) -++- { -++- if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) -++- { -++- state->local_EOB = 1; -++- msg_send2(state, M_EOB, 0, 0); -++- } -++- } -++- -++- /* Recv step: Only when socket is readable */ -++- if (readable) -++- { -++- if (!recv_block(state, config)) -++- return APROTO_DONE_ERR; -++- } -++- -++- /* -++- * send step: drive even when writable=0 if there is buffered data, -++- * pending messages, a file mid-transfer, or an EOF to flush. -++- */ -++- if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) -++- { -++- no = send_block(state, config); -++- -++- if (!no && no != 2) -++- return APROTO_DONE_ERR; -++- } -++- -++- bsy_touch(config); -++- -++- /* Batch/Session-end detection — Mirrors the break logic in protocol() */ -++- if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) -++- { -++- if (state->rcvdlist) -++- { -++- state->q = process_rcvdlist(state, state->q, config); -++- -++- q_to_killlist(&state->killlist, &state->n_killlist, state->q); -++- free_rcvdlist(&state->rcvdlist, &state->n_rcvdlist); -++- } -++- -++- Log(6, "batch: %i msgs", state->msgs_in_batch); -++- -++- if (state->msgs_in_batch <= 2 || (state->major * 100 + state->minor <= 100)) -++- { -++- /* Session done */ -++- ND_set_status("", &state->ND_addr, state, config); -++- state->ND_addr.z = -1; -++- -++- return APROTO_DONE_OK; -++- } -++- -++- /* Start next batch */ -++- state->msgs_in_batch = 0; -++- state->remote_EOB = 0; -++- state->local_EOB = 0; -++- -++- if (OK_SEND_FILES(state, config)) -++- { -++- state->q = q_scan_boxes(state->q, state->fa, state->nfa, state->to ? 1 : 0, config); -++- state->q = q_sort(state->q, state->fa, state->nfa, config); -++- } -++- } -++- -++- return APROTO_RUNNING; -++-} -++- -++-/* -++- * amiga_proto_close -- Flush remaining I/O and release STATE resources -++- * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR -++- */ -++-void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) -++-{ -++- int no; -++- char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; -++- int status = ok ? 0 : 1; -++- -++- /* Drain inbound queue */ -++- if (!state->io_error) -++- { -++- while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) -++- Log(9, "purged %d bytes", no); -++- } -++- -++- /* Flush pending outbound messages */ -++- while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) -++- ; -++- -++- if (ok) -++- { -++- log_end_of_session(0, state, config); -++- process_killlist(state->killlist, state->n_killlist, 's'); -++- inb_remove_partial(state, config); -++- -++- if (state->to) -++- good_try(&state->to->fa, "CONNECT/BND", config); -++- } -++- else -++- { -++- log_end_of_session(1, state, config); -++- process_killlist(state->killlist, state->n_killlist, 0); -++- -++- if (!binkd_exit && state->to) -++- bad_try(&state->to->fa, "Bad session", BAD_IO, config); -++- -++- /* Restore poll flavour if files were left mid-transfer */ -++- if (state->to && tolower(state->maxflvr) != 'h') -++- { -++- Log(4, "restoring poll with '%c' flavour", state->maxflvr); -++- -++- create_poll(&state->to->fa, state->maxflvr, config); -++- } -++- } -++- -++- if (state->to && state->r_skipped_flag && config->hold_skipped > 0) -++- { -++- Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); -++- -++- hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); -++- } -++- -++- deinit_protocol(state, config, status); -++- evt_set(state->evt_queue); -++- state->evt_queue = NULL; -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/proto_amiga.h binkd_pgul/amiga/proto_amiga.h -++--- binkd/amiga/proto_amiga.h 2026-04-26 11:53:22.716799421 +0100 -+++++ binkd_pgul/amiga/proto_amiga.h 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,30 +0,0 @@ -++-/* -++- * proto_amiga.h -- Amiga non-blocking BinkP protocol implementation -++- * -++- * proto_amiga.h is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifndef _PROTO_AMIGA_H -++-#define _PROTO_AMIGA_H -++- -++-#include "protoco2.h" -++-#include "readcfg.h" -++- -++-/* amiga_proto_step() return codes */ -++-#define APROTO_RUNNING 0 -++-#define APROTO_DONE_OK 1 -++-#define APROTO_DONE_ERR 2 -++- -++-/* amiga_proto_open -- Initialise a session and send the BinkP banner */ -++-int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const char *host, const char *port, const char *dst_ip, BINKD_CONFIG *config); -++- -++-/* amiga_proto_step-- run one recv / send iteration of the BinkP loop */ -++-int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *config); -++- -++-/* amiga_proto_close -- flush remaining I/O and release STATE resources */ -++-void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok); -++- -++-#endif /* _PROTO_AMIGA_H */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/rename.c binkd_pgul/amiga/rename.c -++--- binkd/amiga/rename.c 2026-04-25 19:33:26.785601976 +0100 -+++++ binkd_pgul/amiga/rename.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -1,137 +1,10 @@ -++ #include -++ #include -++-#include -++- -++-#include -++-#include /* atoi */ -++-#include /* isdigit */ -++- -++-#define PATHBUF 512 -++ -++ int o_rename(char *from, char *to) -++ { -++- struct FileInfoBlock *fib; -++- char dir[PATHBUF]; -++- char base[PATHBUF]; -++- char newname[PATHBUF]; -++- char *slash; -++- ULONG max = 0; -++- BPTR dirlock; -++- char *d = NULL; -++- const char *src = NULL; -++- ULONG n = 0; -++- -++- /* Try direct rename first */ -++- if (Rename((STRPTR)from, (STRPTR)to)) -++- return 0; -++- -++- /* Split path */ -++- slash = strrchr(to, '/'); -++- -++- if (!slash) -++- slash = strrchr(to, ':'); -++- -++- if (slash) -++- { -++- ULONG len = slash - to; -++- -++- if (len >= PATHBUF) -++- len = PATHBUF - 1; -++- -++- strncpy(dir, to, len); -++- dir[len] = '\0'; -++- -++- strncpy(base, slash + 1, PATHBUF - 1); -++- base[PATHBUF - 1] = '\0'; -++- } -++- else -++- { -++- strcpy(dir, "."); -++- strncpy(base, to, PATHBUF - 1); -++- base[PATHBUF - 1] = '\0'; -++- } -++- -++- /* Lock directory */ -++- dirlock = Lock((STRPTR)dir, ACCESS_READ); -++- -++- if (!dirlock) -++- { -++- errno = ENOENT; -++- return -1; -++- } -++- -++- fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); -++- -++- if (!fib) -++- { -++- UnLock(dirlock); -++- errno = ENOMEM; -++- return -1; -++- } -++- -++- /* Scan directory safely under lock */ -++- if (Examine(dirlock, fib)) -++- { -++- while (ExNext(dirlock, fib)) -++- { -++- if (strncmp(fib->fib_FileName, base, strlen(base)) == 0) -++- { -++- const char *p = NULL; -++- -++- p = fib->fib_FileName + strlen(base); -++- -++- if (*p != '.') -++- { -++- continue; -++- } -++- -++- /* .001 style */ -++- if (isdigit((UBYTE)p[1]) && isdigit((UBYTE)p[2]) && isdigit((UBYTE)p[3])) -++- { -++- n = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); -++- if (n > max) -++- max = n; -++- } -++- -++- /* FIDO volume style (.mo0 .th1 etc) */ -++- if (isdigit((UBYTE)p[1]) && !isdigit((UBYTE)p[2])) -++- { -++- n = p[1] - '0'; -++- if (n > max) -++- max = n; -++- } -++- } -++- } -++- } -++- -++- FreeDosObject(DOS_FIB, fib); -++- UnLock(dirlock); -++- -++- /* Build new name */ -++- d = newname; -++- src = to; -++- n = max + 1; -++- -++- if (n > 999) -++- n = 0; -++- -++- /* Copy base */ -++- while (*src && (d - newname) < (PATHBUF - 5)) -++- *d++ = *src++; -++- -++- *d++ = '.'; -++- -++- /* Manual 3-digit write */ -++- *d++ = '0' + (n / 100); -++- n %= 100; -++- *d++ = '0' + (n / 10); -++- *d++ = '0' + (n % 10); -++- *d = '\0'; -++- -++- /* Rename */ -++- if (Rename((STRPTR)from, (STRPTR)newname)) -++- return 0; -++- -++- errno = EACCES; -+++ if (Rename((STRPTR)from, (STRPTR)to)) /* cross-volume move won't work */ -++ return -1; -+++ else -+++ return 0; -++ } -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/rfc2553_amiga.c binkd_pgul/amiga/rfc2553_amiga.c -++--- binkd/amiga/rfc2553_amiga.c 2026-04-26 11:54:19.321503648 +0100 -+++++ binkd_pgul/amiga/rfc2553_amiga.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,323 +0,0 @@ -++-/* -++- * rfc2553_amiga.c -- getaddrinfo/getnameinfo fallback for AmigaOS 3 -++- * -++- * rfc2553_amiga.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifdef AMIGA -++- -++-#include "amiga/bsdsock.h" /* LP stubs + SocketBase */ -++-#include "rfc2553.h" /* sets HAVE_GETADDRINFO / HAVE_GETNAMEINFO */ -++-#include "sem.h" -++- -++-#include -++-#include -++-#include -++-#include -++- -++-#define safe_strncpy(dst, src, n) \ -++- do \ -++- { \ -++- strncpy((dst), (src), (n)); \ -++- (dst)[(n) - 1] = '\0'; \ -++- } while (0) -++- -++-#ifndef HAVE_GETADDRINFO -++- -++-void freeaddrinfo(struct addrinfo *ai); /* forward decl */ -++- -++-int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) -++-{ -++- struct addrinfo **tail = res; -++- struct hostent *hent = NULL; -++- unsigned int port; -++- int proto; -++- const char *end; -++- char **addrp; -++- -++- static char passive_dummy = '\0'; -++- char *passive_list[2] = {&passive_dummy, NULL}; -++- -++- if (!res) -++- { -++- return EAI_UNKNOWN; -++- } -++- -++- *res = NULL; -++- -++- port = servname ? htons((unsigned short)strtol(servname, (char **)&end, 0)) : 0; -++- proto = (hints && hints->ai_socktype) ? hints->ai_socktype : SOCK_STREAM; -++- -++- lockresolvsem(); -++- -++- if (servname && end != servname + strlen(servname)) -++- { -++- struct servent *se = NULL; -++- -++- if (!hints || hints->ai_socktype == SOCK_STREAM) -++- se = getservbyname((char *)servname, "tcp"); -++- -++- if (hints && hints->ai_socktype == SOCK_DGRAM) -++- se = getservbyname((char *)servname, "udp"); -++- -++- if (!se) -++- { -++- releaseresolvsem(); -++- return EAI_NONAME; -++- } -++- -++- port = se->s_port; -++- -++- if (strcmp((char *)se->s_proto, "tcp") == 0) -++- proto = SOCK_STREAM; -++- else if (strcmp((char *)se->s_proto, "udp") == 0) -++- proto = SOCK_DGRAM; -++- else -++- { -++- releaseresolvsem(); -++- return EAI_NONAME; -++- } -++- -++- if (hints && hints->ai_socktype && hints->ai_socktype != proto) -++- { -++- releaseresolvsem(); -++- return EAI_SERVICE; -++- } -++- } -++- -++- if (!hints || !(hints->ai_flags & AI_PASSIVE)) -++- { -++- unsigned long nip = inet_addr((char *)nodename); -++- -++- if (nip != (unsigned long)INADDR_NONE) -++- { -++- struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); -++- struct sockaddr_in *sin; -++- -++- if (!ai) -++- { -++- releaseresolvsem(); -++- return EAI_MEMORY; -++- } -++- *tail = ai; -++- -++- sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); -++- -++- if (!sin) -++- { -++- free(ai); -++- releaseresolvsem(); -++- return EAI_MEMORY; -++- } -++- -++- ai->ai_family = AF_INET; -++- ai->ai_socktype = proto; -++- ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; -++- ai->ai_addrlen = sizeof(*sin); -++- ai->ai_addr = (struct sockaddr *)sin; -++- sin->sin_family = AF_INET; -++- sin->sin_port = port; -++- sin->sin_addr.s_addr = nip; -++- -++- releaseresolvsem(); -++- return 0; -++- } -++- -++- hent = gethostbyname((char *)nodename); -++- -++- if (!hent) -++- { -++- int herr = errno; -++- releaseresolvsem(); -++- return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL -++- : EAI_NONAME; -++- } -++- -++- if (!hent->h_addr_list || !hent->h_addr_list[0]) -++- { -++- releaseresolvsem(); -++- return EAI_NONAME; -++- } -++- -++- addrp = hent->h_addr_list; -++- } -++- else -++- { -++- addrp = passive_list; -++- } -++- -++- for (; *addrp; addrp++) -++- { -++- struct addrinfo *ai = (struct addrinfo *)calloc(1, sizeof(*ai)); -++- struct sockaddr_in *sin; -++- -++- if (!ai) -++- { -++- releaseresolvsem(); -++- freeaddrinfo(*res); -++- *res = NULL; -++- return EAI_MEMORY; -++- } -++- -++- if (!*res) -++- *res = ai; -++- *tail = ai; -++- tail = &ai->ai_next; -++- -++- sin = (struct sockaddr_in *)calloc(1, sizeof(*sin)); -++- -++- if (!sin) -++- { -++- releaseresolvsem(); -++- freeaddrinfo(*res); -++- *res = NULL; -++- return EAI_MEMORY; -++- } -++- -++- ai->ai_family = AF_INET; -++- ai->ai_socktype = proto; -++- ai->ai_protocol = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; -++- ai->ai_addrlen = sizeof(*sin); -++- ai->ai_addr = (struct sockaddr *)sin; -++- sin->sin_family = AF_INET; -++- sin->sin_port = port; -++- -++- if (!hints || !(hints->ai_flags & AI_PASSIVE)) -++- { -++- size_t cpylen = sizeof(sin->sin_addr); -++- -++- if (hent->h_length > 0 && (size_t)hent->h_length < cpylen) -++- cpylen = (size_t)hent->h_length; -++- -++- memcpy(&sin->sin_addr, *addrp, cpylen); -++- } -++- } -++- -++- releaseresolvsem(); -++- return 0; -++-} -++- -++-void freeaddrinfo(struct addrinfo *ai) -++-{ -++- struct addrinfo *next; -++- -++- while (ai) -++- { -++- free(ai->ai_addr); -++- next = ai->ai_next; -++- free(ai); -++- ai = next; -++- } -++-} -++- -++-static const char *ai_errlist[] = -++- { -++- "Success", -++- "hostname nor servname provided, or not known", -++- "Temporary failure in name resolution", -++- "Non-recoverable failure in name resolution", -++- "No address associated with hostname", -++- "ai_family not supported", -++- "ai_socktype not supported", -++- "service name not supported for ai_socktype", -++- "Address family for hostname not supported", -++- "Memory allocation failure", -++- "System error returned in errno", -++- "Unknown error", -++-}; -++- -++-char *gai_strerror(int ecode) -++-{ -++- if (ecode > 0 || ecode < EAI_UNKNOWN) -++- ecode = EAI_UNKNOWN; -++- return (char *)ai_errlist[-ecode]; -++-} -++- -++-#endif /* !HAVE_GETADDRINFO */ -++- -++-#ifndef HAVE_GETNAMEINFO -++- -++-#ifndef NI_DATAGRAM -++-#define NI_DATAGRAM (1 << 4) -++-#endif -++- -++-int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) -++-{ -++- const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; -++- -++- (void)salen; -++- -++- if (sa->sa_family != AF_INET) -++- return EAI_ADDRFAMILY; -++- -++- if (host && hostlen > 0) -++- { -++- if (!(flags & NI_NUMERICHOST)) -++- { -++- struct hostent *he; -++- -++- lockresolvsem(); -++- he = gethostbyaddr((char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET); -++- -++- if (he) -++- { -++- safe_strncpy(host, (char *)he->h_name, hostlen); -++- releaseresolvsem(); -++- } -++- else -++- { -++- int herr = errno; -++- releaseresolvsem(); -++- if (flags & NI_NAMEREQD) -++- return (herr == TRY_AGAIN) ? EAI_AGAIN : (herr == NO_RECOVERY) ? EAI_FAIL -++- : EAI_NONAME; -++- flags |= NI_NUMERICHOST; -++- } -++- } -++- -++- if (flags & NI_NUMERICHOST) -++- { -++- lockhostsem(); -++- safe_strncpy(host, (char *)Inet_NtoA(sin->sin_addr.s_addr), hostlen); -++- releasehostsem(); -++- } -++- } -++- -++- if (serv && servlen > 0) -++- { -++- if (!(flags & NI_NUMERICSERV)) -++- { -++- struct servent *se; -++- -++- lockresolvsem(); -++- -++- se = (flags & NI_DATAGRAM) ? getservbyport(ntohs(sin->sin_port), "udp") : getservbyport(ntohs(sin->sin_port), "tcp"); -++- -++- if (se) -++- { -++- safe_strncpy(serv, (char *)se->s_name, servlen); -++- releaseresolvsem(); -++- } -++- else -++- { -++- releaseresolvsem(); -++- -++- if (flags & NI_NAMEREQD) -++- return EAI_NONAME; -++- -++- flags |= NI_NUMERICSERV; -++- } -++- } -++- -++- if (flags & NI_NUMERICSERV) -++- snprintf(serv, servlen, "%u", ntohs(sin->sin_port)); -++- } -++- -++- return 0; -++-} -++- -++-#endif /* !HAVE_GETNAMEINFO */ -++-#endif /* AMIGA */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/sem.c binkd_pgul/amiga/sem.c -++--- binkd/amiga/sem.c 2026-04-25 19:33:46.416919700 +0100 -+++++ binkd_pgul/amiga/sem.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -1,100 +1,29 @@ -++ /* -++ * Amiga semaphores -++ */ -++- -++ #include -++ #include -++-#include -++-#include -++- -++-extern void Log(int lev, char *s, ...); -++- -++-int _InitSem(void *vpSem) -++-{ -++- memset(vpSem, 0, sizeof(struct SignalSemaphore)); -++- InitSemaphore((struct SignalSemaphore *)vpSem); -++- return 0; -++-} -++- -++-int _CleanSem(void *vpSem) -++-{ -++- return 0; -++-} -++- -++-int _LockSem(void *vpSem) -++-{ -++- ObtainSemaphore((struct SignalSemaphore *)vpSem); -++- return 0; -++-} -++- -++-int _ReleaseSem(void *vpSem) -++-{ -++- ReleaseSemaphore((struct SignalSemaphore *)vpSem); -++- return 0; -++-} -+++#include -++ -++-int _InitEventSem(EVENTSEM *sem) -++-{ -++- if (!sem) -++- return -1; -+++extern void Log (int lev, char *s,...); -++ -++- sem->waiter = NULL; -++ -++- sem->sigbit = AllocSignal(-1); -++- -++- if (sem->sigbit == (ULONG)-1) -++- return -1; -++- -++- return 0; -+++int _InitSem(void *vpSem) { -+++ memset(vpSem, 0, sizeof (struct SignalSemaphore)); -+++ InitSemaphore ((struct SignalSemaphore*)vpSem); -+++ return(0); -++ } -++ -++-int _CleanEventSem(EVENTSEM *sem) -++-{ -++- if (!sem) -++- return -1; -++- -++- if (sem->sigbit != (ULONG)-1) -++- { -++- FreeSignal((LONG)sem->sigbit); -++- sem->sigbit = (ULONG)-1; -++- } -++- -++- sem->waiter = NULL; -++- return 0; -+++int _CleanSem(void *vpSem) { -+++ return (0); -++ } -++ -++-int _PostSem(EVENTSEM *sem) -++-{ -++- if (!sem) -++- return -1; -++- -++- if (sem->waiter && sem->sigbit != (ULONG)-1) -++- { -++- Signal((struct Task *)sem->waiter, 1UL << sem->sigbit); -++- } -++- -++- return 0; -+++int _LockSem(void *vpSem) { -+++ ObtainSemaphore ((struct SignalSemaphore *)vpSem); -+++ return (0); -++ } -++ -++-int _WaitSem(EVENTSEM *sem, int sec) -++-{ -++- ULONG mask; -++- struct Task *me; -++- -++- if (!sem || sem->sigbit == (ULONG)-1) -++- return -1; -++- -++- me = FindTask(NULL); -++- sem->waiter = me; -++- -++- /* Wait on SIGBREAKF_CTRL_C to avoid hanging on race */ -++- mask = Wait((1UL << sem->sigbit) | SIGBREAKF_CTRL_C); -++- -++- sem->waiter = NULL; -++- -++- /* Return timeout/break indication if only CTRL_C fired */ -++- if (!(mask & (1UL << sem->sigbit)) && (mask & SIGBREAKF_CTRL_C)) -++- return -1; -++- -++- return 0; -+++int _ReleaseSem(void *vpSem) { -+++ ReleaseSemaphore ((struct SignalSemaphore *)vpSem); -+++ return (0); -++ } -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/session.c binkd_pgul/amiga/session.c -++--- binkd/amiga/session.c 2026-04-26 11:57:30.521108284 +0100 -+++++ binkd_pgul/amiga/session.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,462 +0,0 @@ -++-/* -++- * session.c -- session management for AmigaOS 3 binkd -++- * -++- * session.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include -++-#include -++- -++-#include -++-#include -++-#include -++-#include -++- -++-#include "sys.h" -++-#include "iphdr.h" -++-#include "readcfg.h" -++-#include "common.h" -++-#include "tools.h" -++-#include "client.h" -++-#include "protocol.h" -++-#include "ftnq.h" -++-#include "ftnnode.h" -++-#include "ftnaddr.h" -++-#include "bsy.h" -++-#include "iptools.h" -++-#include "rfc2553.h" -++-#include "srv_gai.h" -++-#include "amiga/bsdsock.h" -++-#include "amiga/evloop_int.h" -++-#include "amiga/proto_amiga.h" -++- -++-extern int binkd_exit; -++-extern int ext_rand; -++-extern int client_flag; -++-extern int poll_flag; -++- -++-/* Session table */ -++-int sess_alloc(void) -++-{ -++- int i; -++- -++- for (i = 0; i < max_sessions; i++) -++- { -++- if (sessions[i].phase == SESS_FREE) -++- { -++- memset(&sessions[i], 0, sizeof(sess_t)); -++- sessions[i].fd = INVALID_SOCKET; -++- sessions[i].phase = SESS_FREE; -++- return i; -++- } -++- } -++- -++- return -1; -++-} -++- -++-void sess_free(int idx) -++-{ -++- sess_t *s = &sessions[idx]; -++- -++- if (s->fd != INVALID_SOCKET) -++- { -++- soclose(s->fd); -++- s->fd = INVALID_SOCKET; -++- } -++- -++- if (s->ai_head) -++- { -++- freeaddrinfo(s->ai_head); -++- s->ai_head = NULL; -++- } -++- -++- memset(&s->state, 0, sizeof(STATE)); -++- s->phase = SESS_FREE; -++-} -++- -++-/* Inbound: accept a new connection */ -++-void do_accept(SOCKET lfd, BINKD_CONFIG *config) -++-{ -++- struct sockaddr_storage sa; -++- socklen_t salen = (socklen_t)sizeof(sa); -++- SOCKET fd; -++- int idx; -++- sess_t *s; -++- char host[BINKD_FQDNLEN + 1]; -++- char ip[BINKD_FQDNLEN + 1]; -++- -++- fd = accept(lfd, (struct sockaddr *)&sa, &salen); -++- -++- if (fd == INVALID_SOCKET) -++- { -++- if (TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN) -++- Log(1, "accept(): %s", TCPERR()); -++- -++- return; -++- } -++- -++- if (binkd_exit) -++- { -++- soclose(fd); -++- return; -++- } -++- -++- idx = sess_alloc(); -++- -++- if (idx < 0) -++- { -++- Log(1, "session table full, refusing inbound"); -++- soclose(fd); -++- return; -++- } -++- -++- /* getnameinfo() Is unreliable on AmiTCP: use inet_ntoa directly */ -++- if (((struct sockaddr *)&sa)->sa_family == AF_INET) -++- { -++- struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa; -++- strnzcpy(ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); -++- } -++- else -++- { -++- strnzcpy(ip, "unknown", BINKD_FQDNLEN); -++- } -++- -++- /* Backresolv not supported on AmiTCP: always use IP as host */ -++- strnzcpy(host, ip, BINKD_FQDNLEN); -++- -++- set_nonblock(fd); -++- -++- s = &sessions[idx]; -++- s->fd = fd; -++- s->inbound = 1; -++- s->node = NULL; -++- s->ai_head = NULL; -++- s->last_io = time(NULL); -++- strnzcpy(s->host, host, BINKD_FQDNLEN); -++- strnzcpy(s->ip, ip, BINKD_FQDNLEN); -++- s->port[0] = '\0'; -++- -++- if (amiga_proto_open(&s->state, fd, NULL, NULL, s->host, NULL, s->ip, config) != 0) -++- { -++- Log(1, "proto_open failed for %s", ip); -++- sess_free(idx); -++- return; -++- } -++- -++- s->phase = SESS_RUNNING; -++- n_servers++; -++- Log(4, "inbound slot[%d] from %s", idx, ip); -++-} -++- -++-/* Outbound: Non-blocking connect() */ -++-int start_connect(sess_t *s, BINKD_CONFIG *config) -++-{ -++- SOCKET fd; -++- int rc; -++- -++- s->ip[0] = '\0'; -++- s->port[0] = '\0'; -++- -++- fd = socket(s->ai_cur->ai_family, s->ai_cur->ai_socktype, s->ai_cur->ai_protocol); -++- -++- if (fd == INVALID_SOCKET) -++- { -++- Log(1, "outbound socket(): %s", TCPERR()); -++- return -1; -++- } -++- -++- /* getnameinfo() is unreliable on AmiTCP: may return rc=0 with garbage -++- * Use inet_ntoa/ntohs directly */ -++- if (s->ai_cur->ai_family == AF_INET) -++- { -++- struct sockaddr_in *sa4 = (struct sockaddr_in *)s->ai_cur->ai_addr; -++- strnzcpy(s->ip, inet_ntoa(sa4->sin_addr.s_addr), BINKD_FQDNLEN); -++- snprintf(s->port, MAXPORTSTRLEN, "%u", (unsigned)ntohs(sa4->sin_port)); -++- } -++- else -++- { -++- strnzcpy(s->ip, "unknown", BINKD_FQDNLEN); -++- strnzcpy(s->port, "0", MAXPORTSTRLEN); -++- } -++- -++- Log(4, "connecting %s [%s]:%s", s->host, s->ip, s->port); -++- -++- if (config->bindaddr[0]) -++- { -++- struct addrinfo src_h, *src_ai; -++- memset(&src_h, 0, sizeof(src_h)); -++- src_h.ai_family = s->ai_cur->ai_family; -++- src_h.ai_socktype = SOCK_STREAM; -++- src_h.ai_protocol = IPPROTO_TCP; -++- -++- if (getaddrinfo(config->bindaddr, NULL, &src_h, &src_ai) == 0) -++- { -++- bind(fd, src_ai->ai_addr, (int)src_ai->ai_addrlen); -++- freeaddrinfo(src_ai); -++- } -++- } -++- -++- set_nonblock(fd); -++- -++- rc = connect(fd, s->ai_cur->ai_addr, (int)s->ai_cur->ai_addrlen); -++- -++- if (rc == 0 || TCPERRNO == EINPROGRESS || TCPERRNO == EWOULDBLOCK) -++- { -++- s->fd = fd; -++- s->conn_start = time(NULL); -++- return 0; -++- } -++- -++- Log(1, "connect %s: %s", s->host, TCPERR()); -++- -++- bad_try(&s->node->fa, TCPERR(), BAD_CALL, config); -++- soclose(fd); -++- -++- return -1; -++-} -++- -++-int try_outbound(BINKD_CONFIG *config) -++-{ -++- FTN_NODE *node; -++- sess_t *s; -++- int idx, rc; -++- struct addrinfo hints; -++- char dest[FTN_ADDR_SZ + 1]; -++- char host[BINKD_FQDNLEN + 5 + 1]; -++- char port[MAXPORTSTRLEN + 1]; -++- -++- if (!client_flag) -++- return 0; -++- -++- if (!config->q_present) -++- { -++- q_free(SCAN_LISTED, config); -++- -++- if (config->printq) -++- Log(-1, "scan\r"); -++- -++- q_scan(SCAN_LISTED, config); -++- config->q_present = 1; -++- -++- if (config->printq) -++- { -++- q_list(stderr, SCAN_LISTED, config); -++- Log(-1, "idle\r"); -++- } -++- } -++- -++- if (n_clients >= config->max_clients) -++- return 0; -++- -++- node = q_next_node(config); -++- -++- if (!node) -++- return 0; -++- -++- ftnaddress_to_str(dest, &node->fa); -++- -++- if (!bsy_test(&node->fa, F_BSY, config) || !bsy_test(&node->fa, F_CSY, config)) -++- { -++- Log(4, "%s busy", dest); -++- return 0; -++- } -++- -++- idx = sess_alloc(); -++- -++- if (idx < 0) -++- { -++- Log(2, "table full, deferring %s", dest); -++- return 0; -++- } -++- -++- s = &sessions[idx]; -++- memset(s, 0, sizeof(*s)); -++- s->fd = INVALID_SOCKET; -++- s->node = node; -++- s->inbound = 0; -++- -++- rc = get_host_and_port(1, host, port, node->hosts, &node->fa, config); -++- -++- if (rc <= 0) -++- { -++- Log(1, "%s: bad host list", dest); -++- sess_free(idx); -++- -++- return 0; -++- } -++- -++- strnzcpy(s->host, host, BINKD_FQDNLEN); -++- strnzcpy(s->port, port, MAXPORTSTRLEN); -++- -++- memset(&hints, 0, sizeof(hints)); -++- hints.ai_family = node->IP_afamily; -++- hints.ai_socktype = SOCK_STREAM; -++- hints.ai_protocol = IPPROTO_TCP; -++- -++- rc = srv_getaddrinfo(host, port, &hints, &s->ai_head); -++- -++- if (rc != 0) -++- { -++- Log(1, "%s: getaddrinfo error code=%d: %s", dest, rc, gai_strerror(rc)); -++- -++- bad_try(&node->fa, "getaddrinfo failed", BAD_CALL, config); -++- sess_free(idx); -++- -++- return 0; -++- } -++- -++- s->ai_cur = s->ai_head; -++- -++- if (start_connect(s, config) != 0) -++- { -++- sess_free(idx); -++- return 0; -++- } -++- -++- s->phase = SESS_CONNECTING; -++- n_clients++; -++- -++- Log(4, "outbound slot[%d] -> %s", idx, dest); -++- -++- return 1; -++-} -++- -++-/* Check completion of async connect() */ -++-void check_connect(int idx, BINKD_CONFIG *config) -++-{ -++- sess_t *s = &sessions[idx]; -++- int err = 0; -++- socklen_t el = (socklen_t)sizeof(err); -++- int tmo; -++- -++- tmo = config->connect_timeout ? config->connect_timeout : 30; -++- -++- if ((int)(time(NULL) - s->conn_start) >= tmo) -++- { -++- Log(1, "connect timeout -> %s", s->host); -++- -++- bad_try(&s->node->fa, "Timeout", BAD_CALL, config); -++- n_clients--; -++- sess_free(idx); -++- -++- return; -++- } -++- -++- getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &el); -++- -++- if (err) -++- { -++- Log(1, "connect -> %s: %s", s->host, strerror(err)); -++- -++- bad_try(&s->node->fa, strerror(err), BAD_CALL, config); -++- -++- soclose(s->fd); -++- s->fd = INVALID_SOCKET; -++- s->ai_cur = s->ai_cur->ai_next; -++- -++- if (s->ai_cur && start_connect(s, config) == 0) -++- return; /* trying next address */ -++- -++- n_clients--; -++- sess_free(idx); -++- -++- return; -++- } -++- -++- Log(4, "connected -> %s [%s]", s->host, s->ip); -++- ext_rand = rand(); -++- -++- if (amiga_proto_open(&s->state, s->fd, s->node, NULL, s->host, s->port, s->ip, config) != 0) -++- { -++- Log(1, "proto_open failed for %s", s->host); -++- -++- n_clients--; -++- sess_free(idx); -++- return; -++- } -++- -++- s->phase = SESS_RUNNING; -++- s->last_io = time(NULL); -++-} -++- -++-/* Run one protocol step on an active session */ -++-void do_session_step(int idx, int rd, int wr, BINKD_CONFIG *config) -++-{ -++- sess_t *s = &sessions[idx]; -++- int rc; -++- int tmo; -++- -++- tmo = config->nettimeout ? config->nettimeout : 300; -++- -++- if ((int)(time(NULL) - s->last_io) >= tmo) -++- { -++- Log(1, "slot[%d] net timeout", idx); -++- -++- if (s->node) -++- bad_try(&s->node->fa, "Timeout", BAD_IO, config); -++- -++- amiga_proto_close(&s->state, config, 0); -++- -++- if (s->inbound) -++- n_servers--; -++- else -++- n_clients--; -++- -++- sess_free(idx); -++- -++- return; -++- } -++- -++- if (s->fd == INVALID_SOCKET) -++- { -++- Log(1, "slot[%d] invalid socket, closing session", idx); -++- -++- if (s->node) -++- bad_try(&s->node->fa, "Invalid socket", BAD_IO, config); -++- -++- if (s->inbound) -++- n_servers--; -++- else -++- n_clients--; -++- -++- sess_free(idx); -++- -++- return; -++- } -++- -++- /* WaitSelect() may not report a readable socket when the remote has -++- * sent a TCP END. Probe with MSG_PEEK so recv_block() sees the EOF */ -++- if (!rd && !wr && s->state.state != P_NULL) -++- { -++- char peek; -++- int pr = recv(s->fd, &peek, 1, MSG_PEEK); -++- -++- if (pr == 0 || (pr < 0 && TCPERRNO != EWOULDBLOCK && TCPERRNO != EAGAIN)) -++- rd = 1; -++- } -++- -++- rc = amiga_proto_step(&s->state, rd, wr, config); -++- -++- if (rd || wr) -++- s->last_io = time(NULL); -++- -++- if (rc == APROTO_RUNNING) -++- return; -++- -++- amiga_proto_close(&s->state, config, rc == APROTO_DONE_OK); -++- -++- Log(4, "slot[%d] %s", idx, rc == APROTO_DONE_OK ? "OK" : "ERR"); -++- -++- if (s->inbound) -++- n_servers--; -++- else -++- n_clients--; -++- -++- sess_free(idx); -++- -++- if (poll_flag && n_clients == 0 && n_servers == 0) -++- binkd_exit = 1; -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/sock.c binkd_pgul/amiga/sock.c -++--- binkd/amiga/sock.c 2026-04-26 11:59:26.645813693 +0100 -+++++ binkd_pgul/amiga/sock.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,130 +0,0 @@ -++-/* -++- * sock.c -- listen socket management for AmigaOS 3 -++- * -++- * sock.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include -++-#include -++- -++-#include -++-#include -++- -++-#include "sys.h" -++-#include "readcfg.h" -++-#include "tools.h" -++-#include "server.h" -++-#include "rfc2553.h" -++-#include "amiga/bsdsock.h" -++-#include "amiga/evloop_int.h" -++- -++-extern SOCKET sockfd[]; -++-extern int sockfd_used; -++-extern int server_flag; -++- -++-void set_nonblock(SOCKET fd) -++-{ -++- long flag = 1L; -++- -++- if (IoctlSocket(fd, FIONBIO, (char *)&flag) != 0) -++- Log(2, "IoctlSocket(FIONBIO) failed: %s", TCPERR()); -++-} -++- -++-int open_listen_sockets(BINKD_CONFIG *config) -++-{ -++- struct listenchain *ll; -++- struct addrinfo hints, *ai, *head; -++- int err, opt = 1; -++- -++- memset(&hints, 0, sizeof(hints)); -++- hints.ai_flags = AI_PASSIVE; -++- hints.ai_family = PF_UNSPEC; -++- hints.ai_socktype = SOCK_STREAM; -++- hints.ai_protocol = IPPROTO_TCP; -++- -++- sockfd_used = 0; -++- -++- for (ll = config->listen.first; ll; ll = ll->next) -++- { -++- err = getaddrinfo(ll->addr[0] ? ll->addr : NULL, ll->port, &hints, &head); -++- -++- if (err) -++- { -++- Log(1, "listen getaddrinfo(%s:%s): %s", ll->addr[0] ? ll->addr : "*", ll->port, gai_strerror(err)); -++- return -1; -++- } -++- -++- for (ai = head; ai && sockfd_used < MAX_LISTENSOCK; ai = ai->ai_next) -++- { -++- SOCKET fd; -++- int retries = 6; -++- -++- fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); -++- -++- if (fd == INVALID_SOCKET) -++- { -++- Log(1, "listen socket(): %s", TCPERR()); -++- continue; -++- } -++- -++- if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, (int)sizeof(opt)) != 0) -++- Log(2, "setsockopt(SO_REUSEADDR) failed: %s", TCPERR()); -++- -++- /* Bsdsocket may hold the port briefly after socket close */ -++- while (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) != 0) -++- { -++- if (--retries == 0) -++- { -++- Log(1, "listen bind(): %s", TCPERR()); -++- -++- soclose(fd); -++- freeaddrinfo(head); -++- return -1; -++- } -++- -++- Log(2, "bind retry in 2s: %s", TCPERR()); -++- -++- Delay(100UL); /* 100 ticks = 2s @ 50Hz */ -++- } -++- -++- if (listen(fd, 5) != 0) -++- { -++- Log(1, "listen(): %s", TCPERR()); -++- -++- soclose(fd); -++- freeaddrinfo(head); -++- return -1; -++- } -++- -++- set_nonblock(fd); -++- sockfd[sockfd_used] = fd; -++- sockfd_used++; -++- } -++- -++- freeaddrinfo(head); -++- -++- Log(3, "listening on %s:%s", -++- ll->addr[0] ? ll->addr : "*", ll->port); -++- } -++- -++- if (sockfd_used == 0 && server_flag) -++- { -++- Log(1, "evloop: no listen sockets opened"); -++- return -1; -++- } -++- -++- return 0; -++-} -++- -++-void close_listen_sockets(void) -++-{ -++- int i; -++- -++- for (i = 0; i < sockfd_used; i++) -++- soclose(sockfd[i]); -++- -++- sockfd_used = 0; -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/amiga/utime.c binkd_pgul/amiga/utime.c -++--- binkd/amiga/utime.c 2026-04-26 11:59:41.408046130 +0100 -+++++ binkd_pgul/amiga/utime.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,77 +0,0 @@ -++-/* -++- * utime.c -- utime() stub for AmigaOS 3 without ixemul -++- * -++- * utime.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifdef AMIGA -++- -++-#include -++-#include -++-#include -++-#include -++- -++-#include "amiga/dirent.h" /* declares struct utimbuf and utime() prototype */ -++- -++-/* Days between AmigaDOS epoch (1978-01-01) and POSIX epoch (1970-01-01) */ -++-#define AMIGA_EPOCH_DELTA_DAYS 2922UL -++-#define SECONDS_PER_DAY 86400UL -++-#define SECONDS_PER_MINUTE 60UL -++- -++-int utime(const char *path, const struct utimbuf *times) -++-{ -++- struct DateStamp ds; -++- LONG seconds_today; -++- LONG total_seconds; -++- -++- if (!path) -++- { -++- errno = EINVAL; -++- return -1; -++- } -++- -++- if (!times) -++- { -++- /* Use current time */ -++- DateStamp(&ds); -++- } -++- else -++- { -++- LONG t = (LONG)times->modtime; -++- -++- if (t < (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY)) -++- { -++- /* Time predates AmigaDOS epoch -- clamp to epoch */ -++- t = 0; -++- } -++- else -++- { -++- t -= (LONG)(AMIGA_EPOCH_DELTA_DAYS * SECONDS_PER_DAY); -++- } -++- -++- ds.ds_Days = (LONG)(t / (LONG)SECONDS_PER_DAY); -++- total_seconds = t % (LONG)SECONDS_PER_DAY; -++- ds.ds_Minute = (LONG)(total_seconds / (LONG)SECONDS_PER_MINUTE); -++- seconds_today = total_seconds % (LONG)SECONDS_PER_MINUTE; -++- ds.ds_Tick = seconds_today * (LONG)TICKS_PER_SECOND; -++- } -++- -++- if (!SetFileDate((STRPTR)path, &ds)) -++- { -++- /* Most likely cause: file does not exist or is write-protected */ -++- LONG err = IoErr(); -++- -++- if (err == ERROR_OBJECT_NOT_FOUND || err == ERROR_DIR_NOT_FOUND) -++- errno = ENOENT; -++- else -++- errno = EACCES; -++- return -1; -++- } -++- -++- return 0; -++-} -++- -++-#endif /* AMIGA */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/binkd.c binkd_pgul/binkd.c -++--- binkd/binkd.c 2026-04-26 13:20:44.986212073 +0100 -+++++ binkd_pgul/binkd.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -54,10 +54,6 @@ -++ #include "unix/daemonize.h" -++ #endif -++ -++-#ifdef AMIGA -++-/* amiga/bsdsock.h pulled in via iphdr.h for AMIGA */ -++-#include "amiga/evloop.h" -++-#endif -++ #ifdef WIN32 -++ #include "nt/service.h" -++ #include "nt/w32tools.h" -++@@ -66,11 +62,9 @@ -++ #endif -++ #endif -++ -++-#include "bsycleanup.h" -++- -++ #include "confopt.h" -++ -++-#if defined(HAVE_THREADS) || defined(AMIGA) -+++#ifdef HAVE_THREADS -++ MUTEXSEM hostsem; -++ MUTEXSEM resolvsem; -++ MUTEXSEM lsem; -++@@ -100,13 +94,9 @@ -++ char *configpath = NULL; /* Config file name */ -++ char **saved_envp; -++ -++-/* mypid: needed by HAVE_FORK and AMIGA targets */ -++-#if defined(HAVE_FORK) || defined(AMIGA) -++-int mypid; -++-#endif -++- -++ #ifdef HAVE_FORK -++-int got_sighup, got_sigchld; -+++ -+++int mypid, got_sighup, got_sigchld; -++ -++ void chld (int *childcount) -++ { -++@@ -205,13 +195,9 @@ -++ #endif -++ " -C reload on config change\n" -++ " -c run client only\n" -++-#ifndef AMIGA -++ " -i run server on stdin/stdout pipe (by inetd or other)\n" -++-#endif -++ " -f node run server protected session with this node\n" -++-#ifndef AMIGA -++ " -a ip assume remote address when running with '-i' switch\n" -++-#endif -++ #if defined(BINKD9X) -++ " -t cmd (start|stop|restart|status|install|uninstall) service(s)\n" -++ " -S name set Win9x service name, all - use all services\n" -++@@ -326,14 +312,12 @@ -++ case 'c': -++ client_flag = 1; -++ break; -++-#ifndef AMIGA -++ case 'i': -++ inetd_flag = 1; -++ break; -++ case 'a': /* remote IP address */ -++ remote_addr = strdup(optarg); -++ break; -++-#endif -++ case 'f': /* remote FTN address */ -++ remote_node = strdup(optarg); -++ break; -++@@ -432,28 +416,6 @@ -++ } -++ if (optind\n", extract_filename(argv[0])); -++- fprintf(stderr, " Use -P
for polling a specific node (e.g., -P 1:23/456.7)\n"); -++- exit(1); -++- } -++- -++- /* Check for leftover FTN addresses in extra arguments */ -++- while (optind < argc) -++- { -++- char *extra = argv[optind++]; -++- if (strchr(extra, ':') || strchr(extra, '@')) -++- { -++- fprintf(stderr, "%s: Error: Unexpected FTN address '%s' in arguments.\n", extract_filename(argv[0]), extra); -++- fprintf(stderr, " Use -P
before the config file to poll a node.\n"); -++- exit(1); -++- } -++- } -++- -++ #ifdef OS2 -++ if (optindloglevel, current_config->conlog, -++ current_config->logpath, current_config->nolog.first); -++- -++- /* Clean up old .bsy/.csy files at startup */ -++- cleanup_old_bsy(current_config); -++ } -++ else if (verbose_flag) -++ { -++@@ -706,13 +665,9 @@ -++ -++ if (p) -++ { -++- remote_addr = strdup(p); -++- /* Guard against null pointer dereference if strdup fails */ -++- if (remote_addr) -++- { -++- p = strchr(remote_addr, ' '); -++- if (p) *p = '\0'; -++- } -+++ remote_addr = strdup(p); -+++ p = strchr(remote_addr, ' '); -+++ if (p) *p = '\0'; -++ } -++ } -++ /* not using stdin/stdout itself to avoid possible collisions */ -++@@ -722,9 +677,6 @@ -++ inetd_socket_out = dup(fileno(stdout)); -++ #ifdef UNIX -++ tempfd = open("/dev/null", O_RDWR); -++-#elif defined(AMIGA) -++- /* NIL: is the native AmigaDOS null device (no ixemul) */ -++- tempfd = open("NIL:", O_RDWR); -++ #else -++ tempfd = open("nul", O_RDWR); -++ #endif -++@@ -755,15 +707,6 @@ -++ signal (SIGHUP, sighandler); -++ #endif -++ -++-#ifdef AMIGA -++- /* AmigaOS 3: WaitSelect() loop, no fork/threads */ -++- { -++- BINKD_CONFIG *ev_cfg = lock_current_config(); -++- amiga_evloop_run(ev_cfg, server_flag, client_flag); -++- unlock_config_structure(ev_cfg, 0); -++- return 0; -++- } -++-#else -++ if (client_flag && !server_flag) -++ { -++ clientmgr (0); -++@@ -778,9 +721,7 @@ -++ if (client_flag && (pidcmgr = branch (clientmgr, 0, 0)) < 0) -++ { -++ Log (0, "cannot branch out"); -++- exit (1); -++ } -++-#endif /* !AMIGA */ -++ -++ if (*current_config->pid_file) -++ { -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/binlog.c binkd_pgul/binlog.c -++--- binkd/binlog.c 2026-04-22 06:50:46.000000000 +0100 -+++++ binkd_pgul/binlog.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -35,10 +35,6 @@ -++ #include "tools.h" -++ #include "sem.h" -++ -++-#if defined(HAVE_THREADS) || defined(AMIGA) -++-extern MUTEXSEM blsem; -++-#endif -++- -++ /* Write 16-bit integer to file in intel bytes order */ -++ static int fput16(u16 arg, FILE *file) -++ { -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/branch.c binkd_pgul/branch.c -++--- binkd/branch.c 2026-04-26 09:12:44.314385608 +0100 -+++++ binkd_pgul/branch.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -20,6 +20,12 @@ -++ #include "tools.h" -++ #include "sem.h" -++ -+++#ifdef AMIGA -+++int ix_vfork (void); -+++void vfork_setup_child (void); -+++void ix_vfork_resume (void); -+++#endif -+++ -++ #ifdef WITH_PTHREADS -++ typedef struct { -++ void (*F) (void *); -++@@ -126,6 +132,23 @@ -++ #endif -++ #endif -++ -+++#ifdef AMIGA -+++ /* this is rather bizzare. this function pretends to be a fork and behaves -+++ * like one, but actually it's a kind of a thread. so we'll need semaphores */ -+++ -+++ if (!(rc = ix_vfork ())) -+++ { -+++ vfork_setup_child (); -+++ ix_vfork_resume (); -+++ F (arg); -+++ exit (0); -+++ } -+++ else if (rc < 0) -+++ { -+++ Log (1, "ix_vfork: %s", strerror (errno)); -+++ } -+++#endif -+++ -++ #if defined(DOS) || defined(DEBUGCHILD) -++ rc = 0; -++ F (arg); -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/breaksig.c binkd_pgul/breaksig.c -++--- binkd/breaksig.c 2026-04-26 10:25:19.576809578 +0100 -+++++ binkd_pgul/breaksig.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -46,16 +46,6 @@ -++ { -++ atexit (exitfunc); -++ -++-#ifdef AMIGA -++- /* AmigaOS / libnix: signal() maps SIGINT -> SIGBREAKF_CTRL_C -++- * Register exitsig() so that Ctrl+C sets binkd_exit=1 even when -++- * the process is NOT blocked inside WaitSelect() (e.g. during disk -++- * I/O). When inside WaitSelect(), amiga_select_wrap() in bsdsock.h -++- * detects the break and sets binkd_exit=1 directly without going -++- * through this signal handler. */ -++- signal (SIGINT, exitsig); -++- signal (SIGTERM, exitsig); -++-#else -++ #ifdef SIGBREAK -++ signal (SIGBREAK, exitsig); -++ #endif -++@@ -68,6 +58,5 @@ -++ #ifdef SIGTERM -++ signal (SIGTERM, exitsig); -++ #endif -++-#endif /* AMIGA */ -++ return 1; -++ } -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/bsycleanup.c binkd_pgul/bsycleanup.c -++--- binkd/bsycleanup.c 2026-04-26 11:01:23.269049076 +0100 -+++++ binkd_pgul/bsycleanup.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,122 +0,0 @@ -++-/* -++- * bsycleanup.c -- Cleanup functions for .bsy/.csy/.try files -++- * -++- * bsycleanup.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include -++-#include -++-#include -++-#include -++-#include -++- -++-#include "sys.h" -++-#include "readcfg.h" -++-#include "ftnq.h" -++-#include "ftnnode.h" -++-#include "tools.h" -++-#include "readdir.h" -++- -++-/* -++- * Clean up old .bsy and .csy files at startup -++- * Scans all domain outbounds -++- */ -++- -++-/* Helper: scan a single directory for .bsy/.csy files and delete them */ -++-static void scan_and_delete_bsy_in_dir(const char *dir, BINKD_CONFIG *config) -++-{ -++- DIR *dp; -++- struct dirent *de; -++- char buf[MAXPATHLEN + 1]; -++- -++- if ((dp = opendir(dir)) == 0) -++- { -++- return; -++- } -++- -++- while ((de = readdir(dp)) != 0) -++- { -++- char *s = de->d_name; -++- int len = strlen(s); -++- -++- if (len > 4 && (!STRICMP(s + len - 4, ".bsy") || !STRICMP(s + len - 4, ".csy") || !STRICMP(s + len - 4, ".try"))) -++- { -++- strnzcpy(buf, dir, sizeof(buf)); -++- strnzcat(buf, PATH_SEPARATOR, sizeof(buf)); -++- strnzcat(buf, s, sizeof(buf)); -++- -++- Log(2, "deleting %s", buf); -++- -++- delete(buf); -++- } -++- } -++- -++- closedir(dp); -++-} -++- -++-void cleanup_old_bsy(BINKD_CONFIG *config) -++-{ -++- DIR *dp; -++- char outb_path[MAXPATHLEN + 1], base_path[MAXPATHLEN + 1]; -++- struct dirent *de; -++- FTN_DOMAIN *curr_domain; -++- int len; -++- -++- Log(2, "cleaning up .bsy/.csy/.try files at startup"); -++- -++- /* Scan all domain outbounds */ -++- for (curr_domain = config->pDomains.first; curr_domain; curr_domain = curr_domain->next) -++- { -++- if (curr_domain->alias4 != 0) -++- continue; -++- -++- /* Build base path: path + separator */ -++- strnzcpy(base_path, curr_domain->path, sizeof(base_path)); -++-#ifndef AMIGA -++- if (base_path[strlen(base_path) - 1] == ':') -++- strcat(base_path, PATH_SEPARATOR); -++-#endif -++- -++-#ifdef AMIGADOS_4D_OUTBOUND -++- if (config->aso) -++- { -++- /* ASO mode: direct outbound path */ -++- strnzcpy(outb_path, base_path, sizeof(outb_path)); -++- strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); -++- strnzcat(outb_path, curr_domain->dir, sizeof(outb_path)); -++- Log(7, "cleanup_old_bsy (ASO): scanning domain '%s', path '%s'", curr_domain->name, outb_path); -++- scan_and_delete_bsy_in_dir(outb_path, config); -++- } -++- else -++-#endif -++- { -++- /* BSO mode: scan for outbound.xxx directories */ -++- Log(7, "cleanup_old_bsy (BSO): scanning domain '%s', base '%s'", curr_domain->name, base_path); -++- -++- if ((dp = opendir(base_path)) == 0) -++- continue; -++- -++- len = strlen(curr_domain->dir); -++- -++- while ((de = readdir(dp)) != 0) -++- { -++- /* Match outbound or outbound.xxx */ -++- if (!STRNICMP(de->d_name, curr_domain->dir, len) && (de->d_name[len] == 0 || (de->d_name[len] == '.' && isxdigit(de->d_name[len + 1])))) -++- { -++- strnzcpy(outb_path, base_path, sizeof(outb_path)); -++- strnzcat(outb_path, PATH_SEPARATOR, sizeof(outb_path)); -++- strnzcat(outb_path, de->d_name, sizeof(outb_path)); -++- -++- Log(7, "cleanup_old_bsy (BSO): scanning outbound dir '%s'", outb_path); -++- -++- scan_and_delete_bsy_in_dir(outb_path, config); -++- } -++- } -++- -++- closedir(dp); -++- } -++- } -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/bsycleanup.h binkd_pgul/bsycleanup.h -++--- binkd/bsycleanup.h 2026-04-26 13:11:39.607856201 +0100 -+++++ binkd_pgul/bsycleanup.h 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,19 +0,0 @@ -++-/* -++- * bsycleanup.h -- Cleanup functions for .bsy/.csy/.try files -++- * -++- * bsycleanup.h is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#ifndef _BSYCLEANUP_H -++-#define _BSYCLEANUP_H -++- -++-#include "readcfg.h" -++- -++- -++-/* cleanup_old_bsy -- Clean up old .bsy/.csy/.try files at startup */ -++-void cleanup_old_bsy(BINKD_CONFIG *config); -++- -++-#endif /* _BSYCLEANUP_H */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/btypes.h binkd_pgul/btypes.h -++--- binkd/btypes.h 2026-04-25 20:39:58.604145925 +0100 -+++++ binkd_pgul/btypes.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -73,7 +73,6 @@ -++ int HC_flag; -++ int restrictIP; -++ int NP_flag; /* no proxy */ -++- int NC_flag; /* no compression */ -++ -++ time_t hold_until; -++ int busy; /* 0=free, 'c'=.csy, other=.bsy */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/client.c binkd_pgul/client.c -++--- binkd/client.c 2026-04-26 10:25:03.436098652 +0100 -+++++ binkd_pgul/client.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -19,10 +19,6 @@ -++ #include -++ #endif -++ -++-#ifdef AMIGA -++-#include "amiga/bsdsock.h" -++-#endif -++- -++ #include "sys.h" -++ #include "readcfg.h" -++ #include "client.h" -++@@ -48,11 +44,6 @@ -++ #include "rfc2553.h" -++ #include "srv_gai.h" -++ -++-#if defined(HAVE_THREADS) || defined(AMIGA) -++-extern MUTEXSEM lsem; -++-extern EVENTSEM eothread; -++-#endif -++- -++ static void call (void *arg); -++ -++ int n_clients = 0; -++@@ -211,8 +202,7 @@ -++ /* This sleep can be interrupted by signal, it's OK */ -++ unblocksig(); -++ check_child(&n_clients); -++- if (!config->no_call_delay) -++- SLEEP (config->call_delay); -+++ SLEEP (config->call_delay); -++ check_child(&n_clients); -++ blocksig(); -++ } -++@@ -292,16 +282,8 @@ -++ #ifdef HAVE_THREADS -++ !server_flag && -++ #endif -++- /* AmigaOS uses shared-memory evloop — only main process calls checkcfg() -++- * On fork systems (Linux/FreeBSD) each process has separate memory -++- * so independent reloads are safe. */ -++ !poll_flag) -++-#ifndef AMIGA -++ checkcfg(); -++-#else -++- { -++- } -++-#endif -++ } -++ -++ Log (5, "downing clientmgr..."); -++@@ -324,7 +306,7 @@ -++ exit (0); -++ } -++ -++-int call0 (FTN_NODE *node, BINKD_CONFIG *config) -+++static int call0 (FTN_NODE *node, BINKD_CONFIG *config) -++ { -++ int sockfd = INVALID_SOCKET; -++ int sock_out; -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/client.h binkd_pgul/client.h -++--- binkd/client.h 2026-04-26 10:24:36.096878628 +0100 -+++++ binkd_pgul/client.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -1,20 +1,9 @@ -++ #ifndef _client_h -++ #define _client_h -++ -++-#ifdef AMIGA -++-#include -++-#include -++-#include -++-#endif -++- -++ /* -++ * Scans queue, makes outbound ``call'', than calls protocol() -++ */ -++ void clientmgr(void *arg); -++ -++-#ifdef AMIGA -++-/* Direct outbound call for evloop.c (no-ixemul, no-threads build) */ -++-int call0(FTN_NODE *node, BINKD_CONFIG *config); -++-#endif -++- -++ #endif -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/Config.h binkd_pgul/Config.h -++--- binkd/Config.h 2026-04-25 19:53:34.520405599 +0100 -+++++ binkd_pgul/Config.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -14,8 +14,8 @@ -++ #ifndef _Config_h -++ #define _Config_h -++ -++-#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) + defined(AMIGA) == 0 -++-#error You must define HAVE_FORK, HAVE_THREADS, DOS, or AMIGA! -+++#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) == 0 -+++#error You must define either HAVE_FORK or HAVE_THREADS! -++ #endif -++ -++ #ifdef __WATCOMC__ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/exitproc.c binkd_pgul/exitproc.c -++--- binkd/exitproc.c 2026-04-26 10:21:07.372917687 +0100 -+++++ binkd_pgul/exitproc.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -32,17 +32,6 @@ -++ #include "nt/w32tools.h" -++ #endif -++ -++-#if defined(HAVE_THREADS) || defined(AMIGA) -++-extern MUTEXSEM hostsem; -++-extern MUTEXSEM resolvsem; -++-extern MUTEXSEM lsem; -++-extern MUTEXSEM blsem; -++-extern MUTEXSEM varsem; -++-extern MUTEXSEM config_sem; -++-extern EVENTSEM eothread; -++-extern EVENTSEM wakecmgr; -++-#endif -++- -++ int binkd_exit; -++ -++ #ifdef HAVE_THREADS -++@@ -149,33 +138,6 @@ -++ close_srvmgr_socket(); -++ #endif -++ -++-#ifdef AMIGA -++- /* evloop: single process, no children -++- * Clean Exec semaphores in safe order before freeing config */ -++- close_srvmgr_socket(); -++- CleanEventSem(&wakecmgr); -++- CleanEventSem(&eothread); -++- CleanSem(&varsem); -++- CleanSem(&blsem); -++- CleanSem(&lsem); -++- CleanSem(&resolvsem); -++- CleanSem(&hostsem); -++- CleanSem(&config_sem); -++- sock_deinit(); -++- nodes_deinit(); -++- { -++- BINKD_CONFIG *cfg = lock_current_config(); -++- if (cfg) -++- bsy_remove_all(cfg); -++- if (cfg && *cfg->pid_file && pidsmgr == (int)getpid()) -++- delete(cfg->pid_file); -++- if (cfg) -++- unlock_config_structure(cfg, 1); -++- } -++- Log(6, "exitfunc: AmigaOS cleanup done"); -++- return; -++-#endif /* AMIGA */ -++- -++ config = lock_current_config(); -++ if (config) -++ bsy_remove_all (config); -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnnode.c binkd_pgul/ftnnode.c -++--- binkd/ftnnode.c 2026-04-26 09:22:13.159382256 +0100 -+++++ binkd_pgul/ftnnode.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -74,7 +74,7 @@ -++ */ -++ static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, -++ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, -++- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, -+++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, -++ int IP_afamily, -++ #ifdef BW_LIM -++ long bw_send, long bw_recv, -++@@ -107,7 +107,6 @@ -++ pn->NR_flag = NR_OFF; -++ pn->ND_flag = ND_OFF; -++ pn->NP_flag = NP_OFF; -++- pn->NC_flag = NC_OFF; -++ pn->MD_flag = MD_USE_OLD; -++ pn->HC_flag = HC_USE_OLD; -++ pn->pipe = NULL; -++@@ -135,8 +134,6 @@ -++ pn->ND_flag = ND_flag; -++ if (NP_flag != NP_USE_OLD) -++ pn->NP_flag = NP_flag; -++- if (NC_flag != NC_USE_OLD) -++- pn->NC_flag = NC_flag; -++ if (HC_flag != HC_USE_OLD) -++ pn->HC_flag = HC_flag; -++ if (IP_afamily != AF_USE_OLD) -++@@ -198,7 +195,7 @@ -++ -++ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, -++ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, -++- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, -+++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, -++ int IP_afamily, -++ #ifdef BW_LIM -++ long bw_send, long bw_recv, -++@@ -212,7 +209,7 @@ -++ -++ locknodesem(); -++ pn = add_node_nolock(fa, hosts, pwd, pkt_pwd, out_pwd, obox_flvr, obox, ibox, -++- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, -+++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, -++ IP_afamily, -++ #ifdef BW_LIM -++ bw_send, bw_recv, -++@@ -278,7 +275,6 @@ -++ on->ND_flag=np->ND_flag; -++ on->MD_flag=np->MD_flag; -++ on->NP_flag=np->NP_flag; -++- on->NC_flag=np->NC_flag; -++ on->HC_flag=np->HC_flag; -++ on->restrictIP=np->restrictIP; -++ on->pipe=np->pipe; -++@@ -294,7 +290,7 @@ -++ -++ add_node_nolock(fa, np->hosts, NULL, NULL, NULL, np->obox_flvr, np->obox, -++ np->ibox, np->NR_flag, np->ND_flag, np->MD_flag, np->restrictIP, -++- np->HC_flag, np->NP_flag, np->NC_flag, np->pipe, np->IP_afamily, -+++ np->HC_flag, np->NP_flag, np->pipe, np->IP_afamily, -++ #ifdef BW_LIM -++ np->bw_send, np->bw_recv, -++ #endif -++@@ -403,7 +399,7 @@ -++ if (!get_node_info_nolock (&target, config)) -++ add_node_nolock (&target, "*", NULL, NULL, NULL, '-', NULL, NULL, -++ NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, -++- HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, -+++ HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, -++ #ifdef BW_LIM -++ BW_DEF, BW_DEF, -++ #endif -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnnode.h binkd_pgul/ftnnode.h -++--- binkd/ftnnode.h 2026-04-25 20:40:18.652899844 +0100 -+++++ binkd_pgul/ftnnode.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -36,7 +36,7 @@ -++ */ -++ FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, -++ char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, -++- int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, -+++ int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, -++ int IP_afamily, -++ #ifdef BW_LIM -++ long bw_send, long bw_recv, -++@@ -75,10 +75,6 @@ -++ #define NP_OFF 0 -++ #define NP_USE_OLD -1 /* Use old value */ -++ -++-#define NC_ON 1 -++-#define NC_OFF 0 -++-#define NC_USE_OLD -1 /* Use old value */ -++- -++ #define AF_USE_OLD -1 /* Use old value */ -++ -++ #ifdef BW_LIM -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/ftnq.c binkd_pgul/ftnq.c -++--- binkd/ftnq.c 2026-04-26 10:34:47.939032694 +0100 -+++++ binkd_pgul/ftnq.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -1147,8 +1147,7 @@ -++ if (*buf) -++ { -++ strnzcat (buf, ".try", sizeof (buf)); -++- /* Delete only if the file exists */ -++- if (stat(buf, &sb) == 0) -++- delete (buf); -+++ if (stat(buf, &sb) == -1) return; -+++ delete (buf); -++ } -++ } -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/https.c binkd_pgul/https.c -++--- binkd/https.c 2026-04-22 21:36:50.649712064 +0100 -+++++ binkd_pgul/https.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -318,11 +318,7 @@ -++ buf[1]=1; -++ lockhostsem(); -++ Log (4, strcmp(port, config->oport) == 0 ? "trying %s..." : "trying %s:%u...", -++-#ifdef AMIGA -++- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr.s_addr), portnum); -++-#else -++- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); -++-#endif -+++ inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); -++ releasehostsem(); -++ buf[2]=(unsigned char)((portnum>>8)&0xFF); -++ buf[3]=(unsigned char)(portnum&0xFF); -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/inbound.c binkd_pgul/inbound.c -++--- binkd/inbound.c 2026-04-26 09:25:07.283995051 +0100 -+++++ binkd_pgul/inbound.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -49,9 +49,7 @@ -++ char node[FTN_ADDR_SZ + 1]; -++ -++ strnzcpy (s, inbound, MAXPATHLEN); -++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -++- if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) -++- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -+++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -++ t = s + strlen (s); -++ while (1) -++ { -++@@ -130,9 +128,7 @@ -++ } -++ -++ strnzcpy (s, inbound, MAXPATHLEN); -++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -++- if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) -++- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -+++ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -++ t = s + strlen (s); -++ while ((de = readdir (dp)) != 0) -++ { -++@@ -474,9 +470,7 @@ -++ } -++ -++ strnzcpy (real_name, state->inbound, MAXPATHLEN); -++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -++- if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) -++- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -+++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -++ s = real_name + strlen (real_name); -++ strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); -++ free (u); -++@@ -547,9 +541,7 @@ -++ { -++ ren_style = RENAME_POSTFIX; -++ strnzcpy (real_name, state->inbound, MAXPATHLEN); -++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -++- if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) -++- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -+++ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -++ s = real_name + strlen (real_name); -++ strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); -++ free (u); -++@@ -599,9 +591,7 @@ -++ struct stat sb; -++ -++ strnzcpy (fp, inbound, MAXPATHLEN); -++- /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -++- if (strlen(fp) > 0 && fp[strlen(fp) - 1] != PATH_SEPARATOR[0]) -++- strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); -+++ strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); -++ s = fp + strlen (fp); -++ strnzcat (fp, u = strdequote (filename), MAXPATHLEN); -++ free (u); -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iphdr.h binkd_pgul/iphdr.h -++--- binkd/iphdr.h 2026-04-26 09:25:41.562664644 +0100 -+++++ binkd_pgul/iphdr.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -124,17 +124,9 @@ -++ #define TCPERRNO errno -++ #define TCPERR_WOULDBLOCK EWOULDBLOCK -++ #define TCPERR_AGAIN EAGAIN -++- #ifdef AMIGA -++- /* AmigaOS 3: open bsdsocket.library via amiga/bsdsock.c */ -++- #include "amiga/bsdsock.h" -++- #define sock_init() amiga_sock_init() -++- #define sock_deinit() amiga_sock_cleanup() -++- #define soclose(h) CloseSocket(h) -++- #else -++- #define sock_init() 0 -++- #define sock_deinit() -++- #define soclose(h) close(h) -++- #endif -+++ #define sock_init() 0 -+++ #define sock_deinit() -+++ #define soclose(h) close(h) -++ #endif -++ -++ #if !defined(WIN32) -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iptools.c binkd_pgul/iptools.c -++--- binkd/iptools.c 2026-04-26 10:33:19.517261538 +0100 -+++++ binkd_pgul/iptools.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -20,10 +20,6 @@ -++ #include -++ #endif -++ -++-#ifdef AMIGA -++-#include -++-#endif -++- -++ #include "sys.h" -++ #include "Config.h" -++ #include "iphdr.h" -++@@ -44,11 +40,7 @@ -++ int arg; -++ -++ arg = 1; -++-#if defined(AMIGA) -++- if (ioctl (s, FIONBIO, (char *) &arg) < 0) -++-#else -++ if (ioctl (s, FIONBIO, (char *) &arg, sizeof arg) < 0) -++-#endif -++ Log (1, "ioctl (FIONBIO): %s", TCPERR ()); -++ -++ #elif defined(WIN32) -++@@ -61,49 +53,12 @@ -++ #endif -++ #endif -++ -++-#if defined(UNIX) || defined(EMX) /* NOT AMIGA: sockets are not AmigaDOS fds */ -+++#if defined(UNIX) || defined(EMX) || defined(AMIGA) -++ if (fcntl (s, F_SETFL, O_NONBLOCK) == -1) -++ Log (1, "fcntl: %s", strerror (errno)); -++ #endif -++ } -++ -++-#if defined(AMIGA) -++-void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf) -++-{ -++- /* Disable Nagle algorithm: BinkP mixes small control messages with data -++- * Without TCP_NODELAY each small message waits up to 200ms (Nagle delay), -++- * making sessions 2-5x slower than other BinkP implementations -++- * All other BinkP mailers (BinkIT, Argus, etc.) set this explicitly */ -++- -++- if (tcpdelay) -++- { -++- int nodelay = tcpdelay; -++- -++- if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)) < 0) -++- Log (4, "setsockopt TCP_NODELAY: %s", TCPERR()); -++- } -++- -++- /* ixnet default TCP buffers are very small (~8KB). Increase them so the -++- * sender does not stall waiting for ACK after every small burst */ -++- -++- if (so_sndbuf) -++- { -++- int sndbuf = so_sndbuf; -++- -++- if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, sizeof(sndbuf)) < 0) -++- Log (5, "setsockopt SO_SNDBUF: %s", TCPERR()); -++- } -++- -++- if (so_rcvbuf) -++- { -++- int rcvbuf = so_rcvbuf; -++- -++- if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) < 0) -++- Log (5, "setsockopt SO_RCVBUF: %s", TCPERR()); -++- } -++-} -++-#endif -++- -++ /* -++ * Find the appropriate port string to be used. -++ * Find_port ("") will return binkp's port from /etc/services or even -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/iptools.h binkd_pgul/iptools.h -++--- binkd/iptools.h 2026-04-26 09:26:42.811498024 +0100 -+++++ binkd_pgul/iptools.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -11,21 +11,11 @@ -++ * (at your option) any later version. See COPYING. -++ */ -++ -++-#if defined(AMIGA) -++-#include -++-#include -++-#include -++-#endif -++- -++ /* -++ * Sets non-blocking mode for a given socket -++ */ -++ void setsockopts (SOCKET s); -++ -++-#if defined(AMIGA) -++-void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf); -++-#endif -++- -++ /* -++ * Find the port number (in the host byte order) by a port number string or -++ * a service name. Find_port ("") will return binkp's port from -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/decompress.c binkd_pgul/misc/decompress.c -++--- binkd/misc/decompress.c 2026-04-26 13:39:53.974576140 +0100 -+++++ binkd_pgul/misc/decompress.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,188 +0,0 @@ -++-/* -++- * decompress.c -- Decompress FTN bundle archives from an inbound directory -++- * -++- * decompress.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include "portable.h" /* Canonical portable layer */ -++-#include -++- -++-#define MAX_CMD 1100 -++- -++-/* Archive format codes detected by magic bytes */ -++-#define FMT_UNKNOWN 0 -++-#define FMT_ZIP 1 -++-#define FMT_LZH 2 -++-#define FMT_ARC 3 -++- -++-/* detect_format -- Read first bytes and identify archive type */ -++-static int detect_format(const char *path) -++-{ -++- unsigned char buf[8]; -++- FILE *f = fopen(path, "rb"); -++- int n; -++- -++- if (!f) -++- return FMT_UNKNOWN; -++- -++- n = (int)fread(buf, 1, sizeof(buf), f); -++- -++- fclose(f); -++- -++- if (n < 2) -++- return FMT_UNKNOWN; -++- -++- /* ZIP: PK\x03\x04 */ -++- if (n >= 4 && buf[0] == 0x50 && buf[1] == 0x4B && buf[2] == 0x03 && buf[3] == 0x04) -++- return FMT_ZIP; -++- -++- /* LZH: offset 2 = '-', offset 3 = 'l', offset 6 = '-' (e.g. -lh5-) */ -++- if (n >= 7 && buf[2] == '-' && buf[3] == 'l' && buf[6] == '-') -++- return FMT_LZH; -++- -++- /* ARC: 0x1A followed by type byte 1..18 */ -++- if (buf[0] == 0x1A && buf[1] >= 1 && buf[1] <= 18) -++- return FMT_ARC; -++- -++- return FMT_UNKNOWN; -++-} -++- -++-/* is_ftn_bundle -- Check filename has an FTN day-of-week extension */ -++-static int is_ftn_bundle(const char *filename) -++-{ -++- const char *p; -++- -++- for (p = filename; *p; p++) -++- { -++- if (p[0] == '.' && p[1] && p[2]) -++- { -++- char a = (char)tolower((unsigned char)p[1]); -++- char b = (char)tolower((unsigned char)p[2]); -++- -++- if ((a == 's' && b == 'u') || (a == 'm' && b == 'o') || -++- (a == 't' && b == 'u') || (a == 'w' && b == 'e') || -++- (a == 't' && b == 'h') || (a == 'f' && b == 'r') || -++- (a == 's' && b == 'a')) -++- { -++- /* .TH .TH0 .TH.001 */ -++- if (p[3] == '\0' || isdigit((unsigned char)p[3]) || p[3] == '.') -++- return 1; -++- } -++- } -++- } -++- return 0; -++-} -++- -++-/* delete_file -- Remove a file, portable */ -++-static void delete_file(const char *path) -++-{ -++-#if defined(AMIGA) -++- DeleteFile((STRPTR)path); -++-#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -++- DeleteFileA(path); -++-#else -++- remove(path); -++-#endif -++-} -++- -++-/* run_decompressor -- Invoke external tool for the detected format -++- * outdir must end without trailing slash on POSIX; lha needs trailing / */ -++-static int run_decompressor(int fmt, const char *path, const char *outdir) -++-{ -++- char cmd[MAX_CMD]; -++- -++- switch (fmt) -++- { -++- case FMT_ZIP: -++-#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -++- snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); -++-#else -++- snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); -++-#endif -++- break; -++- -++- case FMT_LZH: -++-#ifdef AMIGA -++- snprintf(cmd, MAX_CMD, "lha x \"%s\" \"%s/\"", path, outdir); -++-#else -++- snprintf(cmd, MAX_CMD, "lha e \"%s\" \"%s/\"", path, outdir); -++-#endif -++- break; -++- -++- case FMT_ARC: -++-#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -++- snprintf(cmd, MAX_CMD, "arc x \"%s\" \"%s\"", path, outdir); -++-#else -++- snprintf(cmd, MAX_CMD, "cd \"%s\" && arc x \"%s\"", outdir, path); -++-#endif -++- break; -++- -++- default: -++- return -1; -++- } -++- -++- return system(cmd); -++-} -++- -++-int main(int argc, char *argv[]) -++-{ -++- DIR *dp; -++- struct dirent *entry; -++- char path[MAXPATHLEN]; -++- const char *inbound; -++- const char *outdir; -++- int total = 0; -++- int ok = 0; -++- -++- if (argc < 3) -++- { -++- fprintf(stderr, -++- "Usage: decompress \n" -++- "Detects format by magic bytes (ZIP/LZH/ARC).\n" -++- "Processes FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA).\n"); -++- -++- return 1; -++- } -++- -++- inbound = argv[1]; -++- outdir = argv[2]; -++- -++- dp = opendir(inbound); -++- -++- if (dp == NULL) -++- return 1; -++- -++- while ((entry = readdir(dp)) != NULL) -++- { -++- int fmt; -++- -++- /* Skip . and .. (AmigaOS readdir does not return these, POSIX does) */ -++- if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) -++- continue; -++- -++- if (!is_ftn_bundle(entry->d_name)) -++- continue; -++- -++- path_join(path, MAXPATHLEN, inbound, entry->d_name); -++- -++- fmt = detect_format(path); -++- -++- if (fmt == FMT_UNKNOWN) -++- continue; -++- -++- total++; -++- -++- if (run_decompressor(fmt, path, outdir) == 0) -++- { -++- delete_file(path); -++- ok++; -++- } -++- } -++- -++- closedir(dp); -++- -++- return (total == 0 || ok == total) ? 0 : 1; -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/decompress.txt binkd_pgul/misc/decompress.txt -++--- binkd/misc/decompress.txt 2026-04-26 13:44:08.749250093 +0100 -+++++ binkd_pgul/misc/decompress.txt 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,36 +0,0 @@ -++-decompress -- Decompress FTN bundle archives -++- -++-USAGE: -++- decompress -++- -++-DESCRIPTION: -++- Scans the inbound directory for FTN day-of-week bundle archives and -++- decompresses them to the output directory. -++- -++- Recognized archive formats (by magic bytes, not extension): -++- - ZIP files -++- - LZH files -++- - ARC files -++- -++- Recognized bundle extensions (case-insensitive): -++- - .SU - Sunday bundle -++- - .MO - Monday bundle -++- - .TU - Tuesday bundle -++- - .WE - Wednesday bundle -++- - .TH - Thursday bundle -++- - .FR - Friday bundle -++- - .SA - Saturday bundle -++- -++- Also handles renamed bundles (duplicates): -++- - ABCD1234.SU -++- - ABCD1234.SU.001 -++- - ABCD1234.SU.002 -++- -++-EXAMPLES: -++- decompress Work:Inbound Work:Unpacked -++- decompress /var/spool/binkd/inbound /tmp/unpacked -++- decompress C:\Binkd\Inbound C:\Binkd\Unpacked -++- exec "work:fido/decompress work:fido/inbound work:fido/inbound" *.su? *.mo? *.tu? *.we? *.th? *.fr? *.sa? *.SU? *.MO? *.TU? *.WE? *.TH? *.FR? *.SA? -++- -++-CONFIGURATION FILE: -++- None. All parameters are command-line arguments. -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/freq.c binkd_pgul/misc/freq.c -++--- binkd/misc/freq.c 2026-04-26 13:39:53.974576140 +0100 -+++++ binkd_pgul/misc/freq.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,220 +0,0 @@ -++-/* -++- * freq.c -- Append a file-request entry to an outbound .req / .clo pair -++- * -++- * freq.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include "portable.h" /* Canonical portable layer */ -++- -++-#define FREQ_MAX_PATH (MAXPATHLEN + 1) -++- -++-/* build_aso_paths -- ASO flat layout */ -++-static int build_aso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) -++-{ -++- char *dot; -++- -++- if (mkdir_recursive(outbound) < 0 && !path_exists(outbound)) -++- return -1; -++- -++- snprintf(req_path, (size_t)pathsize, "%s/%u.%u.%u.%u.req", outbound, zone, net, node, point); -++- safe_strncpy(clo_path, req_path, pathsize); -++- dot = strrchr(clo_path, '.'); -++- -++- if (dot) -++- strcpy(dot, ".clo"); -++- -++- return 0; -++-} -++- -++-/* build_bso_paths -- BSO BinkleyStyle layout (lowercase hex) */ -++-static int build_bso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) -++-{ -++- char zone_dir[FREQ_MAX_PATH]; -++- char node_dir[FREQ_MAX_PATH]; -++- -++- /* Zone dir: .0ZZ (lowercase hex) */ -++- snprintf(zone_dir, sizeof(zone_dir), "%s.%03x", outbound, zone); -++- str_tolower(zone_dir); -++- -++- if (mkdir_recursive(zone_dir) < 0 && !path_exists(zone_dir)) -++- return -1; -++- -++- if (point == 0) -++- { -++- snprintf(req_path, (size_t)pathsize, "%s/%04x%04x.req", zone_dir, net, node); -++- snprintf(clo_path, (size_t)pathsize, "%s/%04x%04x.clo", zone_dir, net, node); -++- } -++- else -++- { -++- snprintf(node_dir, sizeof(node_dir), "%s/%04x%04x.pnt", zone_dir, net, node); -++- -++- if (mkdir_recursive(node_dir) < 0 && !path_exists(node_dir)) -++- return -1; -++- -++- snprintf(req_path, (size_t)pathsize, "%s/%08x.req", node_dir, point); -++- snprintf(clo_path, (size_t)pathsize, "%s/%08x.clo", node_dir, point); -++- } -++- -++- return 0; -++-} -++- -++-int main(int argc, char *argv[]) -++-{ -++- unsigned int zone, net, node, point; -++- char addr_copy[128]; -++- char req_path[FREQ_MAX_PATH]; -++- char clo_path[FREQ_MAX_PATH]; -++- char abs_outbound[FREQ_MAX_PATH]; -++- FILE *f; -++- const char *outbound; -++- const char *arg_outbound; -++- const char *arg_addr; -++- const char *password = NULL; /* --password → !pass suffix */ -++- long newer_than = 0; /* --newer-than → +ts suffix */ -++- int update = 0; /* --update → U suffix */ -++- int use_bso = 0; -++- int argi = 1; -++- int nfiles = 0; -++- -++- zone = net = node = point = 0; -++- -++- /* Parse flags -- All optional, order-independent, before positional args */ -++- while (argi < argc && argv[argi][0] == '-' && argv[argi][1] == '-') -++- { -++- if (strcmp(argv[argi], "--bso") == 0) -++- { -++- use_bso = 1; -++- argi++; -++- } -++- else if (strcmp(argv[argi], "--aso") == 0) -++- { -++- use_bso = 0; -++- argi++; -++- } -++- else if (strcmp(argv[argi], "--update") == 0) -++- { -++- update = 1; -++- argi++; -++- } -++- else if (strcmp(argv[argi], "--password") == 0 && argi + 1 < argc) -++- { -++- password = argv[++argi]; -++- argi++; -++- } -++- else if (strcmp(argv[argi], "--newer-than") == 0 && argi + 1 < argc) -++- { -++- newer_than = atol(argv[++argi]); -++- argi++; -++- } -++- else -++- break; /* unknown flag — stop, treat rest as positional */ -++- } -++- -++- if (argc - argi < 3) -++- { -++- fprintf(stderr, -++- "Usage: freq [options] Z:N/NODE[.POINT] [...]\n" -++- "Options:\n" -++- " --aso flat layout (default): outbound/Z.N.NODE.POINT.req\n" -++- " --bso BSO layout: outbound.0ZZ/nnnnnnnn[.pnt/pppppppp].req\n" -++- " --password append !pw to each request line\n" -++- " --newer-than append + (request if newer)\n" -++- " --update append U flag (update request)\n" -++- "Multiple filenames can be listed after the address.\n"); -++- return 1; -++- } -++- -++- arg_outbound = argv[argi++]; -++- arg_addr = argv[argi++]; -++- -++- /* Remaining args are filenames */ -++- -++- make_abs_path(arg_outbound, abs_outbound, (int)sizeof(abs_outbound)); -++- outbound = abs_outbound; -++- -++- safe_strncpy(addr_copy, arg_addr, (int)sizeof(addr_copy)); -++- -++- if (sscanf(addr_copy, "%u:%u/%u.%u", &zone, &net, &node, &point) < 3 && sscanf(addr_copy, "%u:%u/%u", &zone, &net, &node) < 3) -++- { -++- fprintf(stderr, "freq: invalid address: %s\n", arg_addr); -++- return 1; -++- } -++- -++- if (use_bso) -++- { -++- if (build_bso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) -++- { -++- fprintf(stderr, "freq: cannot create BSO dirs under: %s\n", outbound); -++- return 1; -++- } -++- } -++- else -++- { -++- if (build_aso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) -++- { -++- fprintf(stderr, "freq: cannot create outbound dir: %s\n", outbound); -++- return 1; -++- } -++- } -++- -++- /* Append all filenames to .req */ -++- f = fopen(req_path, "a"); -++- -++- if (!f) -++- { -++- fprintf(stderr, "freq: cannot open REQ: %s\n", req_path); -++- return 1; -++- } -++- -++- for (; argi < argc; argi++) -++- { -++- const char *fname = argv[argi]; -++- -++- /* Build request line: filename [!password] [+timestamp] [U] */ -++- fprintf(f, "%s", fname); -++- -++- if (password && password[0]) -++- fprintf(f, " !%s", password); -++- -++- if (newer_than > 0) -++- fprintf(f, " +%ld", newer_than); -++- -++- if (update) -++- fprintf(f, " U"); -++- -++- fprintf(f, "\r\n"); -++- nfiles++; -++- } -++- -++- fclose(f); -++- -++- if (nfiles == 0) -++- { -++- fprintf(stderr, "freq: no filenames specified\n"); -++- return 1; -++- } -++- -++- /* Append .req full path to .clo (once per invocation) */ -++- f = fopen(clo_path, "a"); -++- -++- if (!f) -++- { -++- fprintf(stderr, "freq: cannot open CLO: %s\n", clo_path); -++- return 1; -++- } -++- -++- fprintf(f, "%s\r\n", req_path); -++- fclose(f); -++- -++- printf("freq (%s): node %u:%u/%u", use_bso ? "bso" : "aso", zone, net, node); -++- -++- if (point) -++- printf(".%u", point); -++- -++- printf(" %d file(s)\n REQ : %s\n CLO : %s\n", nfiles, req_path, clo_path); -++- -++- return 0; -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/freq.txt binkd_pgul/misc/freq.txt -++--- binkd/misc/freq.txt 2026-04-26 13:33:14.698749181 +0100 -+++++ binkd_pgul/misc/freq.txt 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,37 +0,0 @@ -++-freq -- Create .req and .clo request files for FidoNet -++- -++-USAGE: -++- freq [options] [...] -++- -++-DESCRIPTION: -++- Creates file request (.req) and close (.clo) files in the outbound -++- directory using FidoNet 4D/5D addressing. -++- -++- Address format: -++- Zone:Net/Node - 4D address (e.g., 39:190/101) -++- Zone:Net/Node.Point - 5D address with point (e.g., 39:190/101.1) -++- -++- Multiple files can be specified to create multiple request lines. -++- -++-OPTIONS: -++- --aso Amiga Style Outbound (default) - flat layout -++- Format: /Z.N.NODE.POINT.req -++- -++- --bso Binkley Style Outbound - hex directory layout -++- No point: .0ZZ/nnnnnnnn.req -++- Point: .0ZZ/nnnnnnnn.pnt/pppppppp.req -++- -++- --password Append !pw suffix to each request line -++- --newer-than Append + (request only if file is newer) -++- --update Append U flag (update request, checks TRANX) -++- -++-EXAMPLES: -++- freq Work:Outbound 39:190/101 file.lha -++- freq --aso Work:Outbound 39:190/101.1 readme.txt -++- freq --bso /var/spool/binkd/outbound 2:123/456 door.zip -++- freq --bso C:\Binkd\Outbound 1:100/200 update.lzh -++- freq --password secret --newer-than 1234567890 Work:Outbound 39:190/101 file.zip -++- freq --update Work:Outbound 39:190/101 file1.zip file2.zip file3.zip -++- -++-CONFIGURATION FILE: -++- None. All parameters are command-line arguments. -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/nodelist.c binkd_pgul/misc/nodelist.c -++--- binkd/misc/nodelist.c 2026-04-26 13:39:53.974576140 +0100 -+++++ binkd_pgul/misc/nodelist.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,200 +0,0 @@ -++-/* -++- * nodelist.c -- FidoNet nodelist compiler for binkd -++- * -++- * nodelist.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include "portable.h" /* Canonical portable layer */ -++-#include -++-#include -++-#include -++-#include -++- -++-#define MAX_FIELDS 20 -++-#define MAX_VAL 256 -++- -++-static int split_fields(char *buf, char **fields, int maxfields) -++-{ -++- int n = 0; -++- char *p = buf; -++- -++- while (n < maxfields) -++- { -++- fields[n++] = p; -++- p = strchr(p, ','); -++- -++- if (!p) -++- break; -++- -++- *p++ = '\0'; -++- } -++- -++- return n; -++-} -++- -++-/* Case-insensitive flag search. Returns 1 if found; fills val if present */ -++-static int find_flag(char **fields, int nfields, int start, const char *flag, char *val, int vsize) -++-{ -++- int flen = (int)strlen(flag); -++- int i; -++- -++- for (i = start; i < nfields; i++) -++- { -++- char *f = fields[i]; -++- int j; -++- -++- for (j = 0; j < flen; j++) -++- { -++- if (toupper((unsigned char)f[j]) != toupper((unsigned char)flag[j])) -++- break; -++- } -++- -++- if (j == flen) -++- { -++- if (val && vsize > 0) -++- { -++- if (f[flen] == ':') -++- { -++- strncpy(val, f + flen + 1, (size_t)(vsize - 1)); -++- val[vsize - 1] = '\0'; -++- } -++- else -++- val[0] = '\0'; -++- } -++- return 1; -++- } -++- } -++- return 0; -++-} -++- -++-int main(int argc, char *argv[]) -++-{ -++- const char *nl_file; -++- const char *domain; -++- FILE *in; -++- FILE *out; -++- char buf[MAX_LINE]; -++- char *fields[MAX_FIELDS]; -++- int nf; -++- int cur_zone; -++- int cur_net; -++- long count; -++- -++- cur_zone = 0; -++- cur_net = 0; -++- count = 0; -++- -++- if (argc < 3) -++- { -++- fprintf(stderr, -++- "Usage: nodelist []\n"); -++- return 1; -++- } -++- -++- nl_file = argv[1]; -++- domain = argv[2]; -++- out = (argc >= 4) ? fopen(argv[3], "w") : stdout; -++- -++- if (!out) -++- { -++- perror(argv[3]); -++- return 1; -++- } -++- -++- in = fopen(nl_file, "r"); -++- -++- if (!in) -++- { -++- perror(nl_file); -++- -++- if (out != stdout) -++- fclose(out); -++- -++- return 1; -++- } -++- -++- while (fgets(buf, sizeof(buf), in)) -++- { -++- char type[32]; -++- char ibn_port[32]; -++- char ina_host[MAX_VAL]; -++- int node_num; -++- int port; -++- int flags_start; -++- -++- str_trim(buf); -++- -++- if (!buf[0] || buf[0] == ';') -++- continue; -++- -++- nf = split_fields(buf, fields, MAX_FIELDS); -++- -++- if (nf < 2) -++- continue; -++- -++- if (fields[0][0] == '\0') -++- { -++- /* Line started with comma -- plain Node entry */ -++- strcpy(type, "Node"); -++- node_num = atoi(fields[1]); -++- flags_start = 7; -++- } -++- else -++- { -++- strncpy(type, fields[0], sizeof(type) - 1); -++- type[sizeof(type) - 1] = '\0'; -++- node_num = atoi(fields[1]); -++- flags_start = 7; -++- } -++- -++- /* Update zone / net context */ -++- if (!strcmp(type, "Zone") || !strcmp(type, "ZONE")) -++- { -++- cur_zone = node_num; -++- cur_net = node_num; -++- continue; -++- } -++- -++- if (!strcmp(type, "Region") || !strcmp(type, "REGION")) -++- { -++- cur_net = node_num; -++- continue; -++- } -++- -++- if (!strcmp(type, "Host") || !strcmp(type, "HOST")) -++- cur_net = node_num; -++- -++- /* Skip unusable types */ -++- if (!strcmp(type, "Pvt") || !strcmp(type, "PVT") || !strcmp(type, "Hold") || !strcmp(type, "HOLD") || !strcmp(type, "Down") || !strcmp(type, "DOWN") || !strcmp(type, "Boss") || !strcmp(type, "BOSS")) -++- continue; -++- -++- /* Must have IBN flag */ -++- if (!find_flag(fields, nf, flags_start, "IBN", ibn_port, (int)sizeof(ibn_port))) -++- continue; -++- -++- /* Need INA:hostname */ -++- ina_host[0] = '\0'; -++- find_flag(fields, nf, flags_start, "INA", ina_host, (int)sizeof(ina_host)); -++- -++- if (!ina_host[0]) -++- continue; -++- -++- port = (ibn_port[0] && atoi(ibn_port) > 0) ? atoi(ibn_port) : 24554; -++- -++- fprintf(out, "node %d:%d/%d@%s %s:%d -\n", cur_zone, cur_net, node_num, domain, ina_host, port); -++- -++- count++; -++- } -++- -++- fclose(in); -++- -++- if (out != stdout) -++- fclose(out); -++- -++- fprintf(stderr, "nodelist: %ld BinkP node(s) found\n", count); -++- -++- return 0; -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/nodelist.txt binkd_pgul/misc/nodelist.txt -++--- binkd/misc/nodelist.txt 2026-04-26 13:32:27.866048972 +0100 -+++++ binkd_pgul/misc/nodelist.txt 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,32 +0,0 @@ -++-nodelist -- Compile FidoNet nodelist to binkd.conf format -++- -++-USAGE: -++- nodelist [] -++- -++-DESCRIPTION: -++- Reads a FidoNet nodelist and outputs binkd.conf compatible -++- "node" configuration lines. -++- -++- Arguments: -++- nodelist_file Path to the FidoNet nodelist file -++- domain Domain name for the node entries (e.g., fidonet) -++- output_file Optional output file (default: stdout) -++- -++- Extracts the following flags: -++- IBN[:port] - BinkP protocol flag (Internet BinkP Node) -++- INA:hostname - IP hostname/address -++- -++- Output format: -++- node
@ : - -++- -++- The nodelist format is comma-separated: -++- [type,]node_num,name,city,sysop,phone,baud,flag1,flag2,... -++- type = Zone, Region, Host, Hub, Pvt, Hold, Down, Boss (empty = Node) -++- -++-EXAMPLES: -++- nodelist Work:Fido/nodelist.123 fidonet > binkd-nodes.conf -++- nodelist /etc/fido/nodelist.456 fidonet >> binkd.conf -++- nodelist C:\Fido\NODELIST.001 fidonet C:\Fido\nodes.conf -++- -++-CONFIGURATION FILE: -++- None. All parameters are command-line arguments. -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/portable.c binkd_pgul/misc/portable.c -++--- binkd/misc/portable.c 2026-04-26 13:04:59.214902887 +0100 -+++++ binkd_pgul/misc/portable.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,402 +0,0 @@ -++-/* -++- * portable.c -- Shared implementations for misc tools portable layer -++- * -++- * portable.c is a part of binkd project -++- * -++- * This file provides implementations for common utility functions -++- * used across binkd misc tools. Include portable.h for declarations -++- * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- * -++- */ -++- -++-#include "portable.h" -++-#include -++- -++-/* trim_nl -- Strip trailing newline (\n and \r) from string */ -++-void trim_nl(char *s) -++-{ -++- char *p = strchr(s, '\n'); -++- -++- if (p) -++- *p = '\0'; -++- -++- p = strchr(s, '\r'); -++- -++- if (p) -++- *p = '\0'; -++-} -++- -++-/* str_trim -- Strip trailing whitespace (space, \r, \n) from string */ -++-void str_trim(char *s) -++-{ -++- int n = (int)strlen(s); -++- -++- while (n > 0 && (s[n - 1] == '\r' || s[n - 1] == '\n' || s[n - 1] == ' ')) -++- s[--n] = '\0'; -++-} -++- -++-/* str_upper -- Convert string to uppercase in-place */ -++-void str_upper(char *s) -++-{ -++- while (*s) -++- { -++- *s = (char)toupper((unsigned char)*s); -++- s++; -++- } -++-} -++- -++-/* str_tolower -- Convert string to lowercase in-place */ -++-void str_tolower(char *s) -++-{ -++- for (; *s; s++) -++- { -++- if (*s >= 'A' && *s <= 'Z') -++- *s = (char)(*s + ('a' - 'A')); -++- } -++-} -++- -++-/* skip_ws -- Skip leading whitespace */ -++-char *skip_ws(char *s) -++-{ -++- while (*s == ' ' || *s == '\t') -++- s++; -++- -++- return s; -++-} -++- -++-/* wildmatch -- Portable wildcard match: case-insensitive, supports * and ? */ -++-int wildmatch(const char *pat, const char *str) -++-{ -++- while (*pat) -++- { -++- if (*pat == '*') -++- { -++- while (*pat == '*') -++- pat++; -++- -++- if (!*pat) -++- return 1; -++- -++- while (*str) -++- { -++- if (wildmatch(pat, str++)) -++- return 1; -++- } -++- -++- return 0; -++- } -++- -++- if (*pat == '?') -++- { -++- if (!*str) -++- return 0; -++- -++- pat++; -++- str++; -++- } -++- else -++- { -++- if (toupper((unsigned char)*pat) != toupper((unsigned char)*str)) -++- return 0; -++- -++- pat++; -++- str++; -++- } -++- } -++- -++- return (*str == '\0') ? 1 : 0; -++-} -++- -++-/* is_wildcard -- True if name contains * or ? */ -++-int is_wildcard(const char *s) -++-{ -++- while (*s) -++- { -++- if (*s == '*' || *s == '?') -++- return 1; -++- -++- s++; -++- } -++- -++- return 0; -++-} -++- -++-/* ensure_dir -- Ensure directory exists, creating if necessary */ -++-int ensure_dir(const char *path) -++-{ -++- if (path_exists(path)) -++- return 1; -++- -++- return (mkdir_recursive(path) == 0) ? 1 : 0; -++-} -++- -++-/* copy_file -- Portable binary file copy */ -++-int copy_file(const char *src, const char *dst) -++-{ -++- FILE *in, *out; -++- char buf[4096]; -++- int n; -++- -++- in = fopen(src, "rb"); -++- -++- if (!in) -++- return 0; -++- -++- out = fopen(dst, "wb"); -++- -++- if (!out) -++- { -++- fclose(in); -++- return 0; -++- } -++- -++- while ((n = (int)fread(buf, 1, sizeof(buf), in)) > 0) -++- fwrite(buf, 1, (size_t)n, out); -++- -++- fclose(out); -++- fclose(in); -++- -++- return 1; -++-} -++- -++-/* move_file -- Try rename first, fall back to copy+delete */ -++-int move_file(const char *src, const char *dst) -++-{ -++- remove(dst); -++- -++- if (rename(src, dst) == 0) -++- return 1; -++- -++- if (copy_file(src, dst)) -++- { -++- remove(src); -++- return 1; -++- } -++- -++- return 0; -++-} -++- -++-/* get_file_size -- Return file size in bytes, or -1 on error */ -++-long get_file_size(const char *path) -++-{ -++- struct stat st; -++- -++- if (stat(path, &st) == 0) -++- return (long)st.st_size; -++- -++- return -1; -++-} -++- -++-/* get_file_mtime -- Return Unix mtime of a file, or 0 on error */ -++-long get_file_mtime(const char *path) -++-{ -++- struct stat st; -++- -++- if (stat(path, &st) != 0) -++- return 0; -++- -++- return (long)st.st_mtime; -++-} -++- -++-/* port_path_exists -- Check if path exists (native per OS) */ -++- -++-int port_path_exists(const char *p) -++-{ -++-#ifdef AMIGA -++- BPTR l = Lock((STRPTR)p, ACCESS_READ); -++- -++- if (l) -++- { -++- UnLock(l); -++- return 1; -++- } -++- -++- return 0; -++-#else -++- struct stat st; -++- return (stat(p, &st) == 0) ? 1 : 0; -++-#endif -++-} -++- -++-/* port_mkdir_one -- Create single directory (native per OS) */ -++-int port_mkdir_one(const char *p) -++-{ -++-#ifdef AMIGA -++- BPTR l = CreateDir((STRPTR)p); -++- -++- if (l) -++- { -++- UnLock(l); -++- return 0; -++- } -++- -++- return -1; -++-#else -++- return mkdir(p, 0755); -++-#endif -++-} -++- -++-/* safe_localtime -- Thread-safe localtime, portable across all OS */ -++-void safe_localtime(const time_t *t, struct tm *tm) -++-{ -++-#if defined(AMIGA) || defined(DOS) -++- *tm = *localtime(t); -++-#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -++- localtime_s(tm, t); -++-#else -++- localtime_r(t, tm); -++-#endif -++-} -++- -++-/* mkdir_recursive -- Create full path, making all missing components */ -++-int mkdir_recursive(const char *path) -++-{ -++- char tmp[MP_MAXPATH]; -++- char *p; -++- int len; -++- -++- if (!path || !path[0]) -++- return -1; -++- -++- strncpy(tmp, path, MP_MAXPATH - 1); -++- tmp[MP_MAXPATH - 1] = '\0'; -++- -++- len = (int)strlen(tmp); -++- -++- /* Strip trailing slash */ -++- while (len > 1 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\')) -++- tmp[--len] = '\0'; -++- -++- /* Walk every '/' component and create missing dirs */ -++- for (p = tmp + 1; *p; p++) -++- { -++- if (*p == '/' || *p == '\\') -++- { -++- *p = '\0'; -++- -++- if (!path_exists(tmp)) -++- mkdir_one(tmp); /* ignore per-component errors */ -++- -++- *p = '/'; -++- } -++- } -++- -++- /* Create the leaf */ -++- if (!path_exists(tmp)) -++- return mkdir_one(tmp); -++- -++- return 0; -++-} -++- -++-/* safe_strncpy -- Ctrncpy that always NUL-terminates */ -++-void safe_strncpy(char *dst, const char *src, int dstsize) -++-{ -++- int len; -++- -++- if (dstsize <= 0) -++- return; -++- -++- len = (int)strlen(src); -++- -++- if (len > dstsize - 1) -++- len = dstsize - 1; -++- -++- memcpy(dst, src, (size_t)len); -++- dst[len] = '\0'; -++-} -++- -++-/* path_join -- Concatenate base path with sub path */ -++-void path_join(char *out, int outsize, const char *base, const char *sub) -++-{ -++- int blen; -++- char last; -++- -++- safe_strncpy(out, base, outsize); -++- blen = (int)strlen(out); -++- last = (blen > 0) ? out[blen - 1] : '\0'; -++- -++- if (last != '/' && last != ':' && last != '\\') -++- { -++- if (outsize - 1 - blen > 0) -++- { -++- out[blen] = '/'; -++- out[blen + 1] = '\0'; -++- blen++; -++- } -++- } -++- -++- safe_strncpy(out + blen, sub, outsize - blen); -++-} -++- -++-/* make_abs_path -- Resolve a possibly-relative path to absolute -++- * Covers AmigaOS, Win32, OS/2, DOS and Unix -++- * Returns 1 on success, 0 on failure (src copied verbatim as fallback) -++- */ -++-int make_abs_path(const char *src, char *dst, int dstlen) -++-{ -++-#ifdef AMIGA -++- BPTR lock = Lock((STRPTR)src, SHARED_LOCK); -++- -++- if (!lock) -++- { -++- safe_strncpy(dst, src, dstlen); -++- return 0; -++- } -++- -++- if (!NameFromLock(lock, (STRPTR)dst, dstlen)) -++- { -++- UnLock(lock); -++- safe_strncpy(dst, src, dstlen); -++- return 0; -++- } -++- -++- UnLock(lock); -++- -++- return 1; -++-#elif defined(WIN32) || defined(__MINGW32__) || defined(__WATCOMC__) || defined(VISUALCPP) || defined(OS2) -++- if (_fullpath(dst, src, (size_t)dstlen) != NULL) -++- return 1; -++- -++- safe_strncpy(dst, src, dstlen); -++- return 0; -++-#elif defined(DOS) -++- if (src[0] != '\\' && src[1] != ':') -++- { -++- char cwd[MAXPATHLEN + 1]; -++- -++- if (getcwd(cwd, sizeof(cwd)) != NULL) -++- { -++- snprintf(dst, dstlen, "%s\\%s", cwd, src); -++- return 1; -++- } -++- } -++- -++- safe_strncpy(dst, src, dstlen); -++- return 0; -++-#else -++- char buf[MAXPATHLEN + 1]; -++- -++- if (realpath(src, buf) != NULL) -++- { -++- safe_strncpy(dst, buf, dstlen); -++- return 1; -++- } -++- -++- if (src[0] != '/') -++- { -++- char cwd[MAXPATHLEN + 1]; -++- -++- if (getcwd(cwd, sizeof(cwd)) != NULL) -++- { -++- snprintf(dst, dstlen, "%s/%s", cwd, src); -++- return 1; -++- } -++- } -++- -++- safe_strncpy(dst, src, dstlen); -++- return 0; -++-#endif -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/portable.h binkd_pgul/misc/portable.h -++--- binkd/misc/portable.h 2026-04-26 14:19:41.472724309 +0100 -+++++ binkd_pgul/misc/portable.h 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,135 +0,0 @@ -++-/* -++- * portable.h -- Portability layer for standalone binkd misc tools -++- * -++- * portable.h is a part of binkd project -++- * -++- * This is the single canonical portable.h; all misc utilities include this -++- * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- * -++- */ -++- -++-#ifndef BINKD_PORTABLE_H -++-#define BINKD_PORTABLE_H -++- -++-/* _POSIX_C_SOURCE for opendir/readdir/localtime_r under -std=c89 -++- * _XOPEN_SOURCE 500 additionally exposes realpath() on glibc */ -++-#ifndef AMIGA -++-#ifndef _POSIX_C_SOURCE -++-#define _POSIX_C_SOURCE 200112L -++-#endif -++-#ifndef _XOPEN_SOURCE -++-#define _XOPEN_SOURCE 500 -++-#endif -++-#endif -++- -++-#include -++-#include -++-#include -++-#include -++-#include -++- -++-#ifdef AMIGA -++- -++-#include -++-#include -++-#include -++-#include -++-#include -++-#include /* stat() / struct stat via libnix/ADE */ -++-#include -++-#include "amiga/dirent.h" /* opendir / readdir / closedir */ -++- -++-/* snprintf/vsnprintf: ADE/libnix declares them in stdio.h (already included -++- * above via ). The implementation is provided by snprintf.c which -++- * must be linked when building the misc tools. No redeclaration needed */ -++- -++-#elif defined(VISUALCPP) -++-#include -++-#include -++-#include -++-#include "nt/dirwin32.h" /* opendir/readdir/closedir for MSVC */ -++-#elif defined(__MINGW32__) || defined(WIN32) -++-#include -++-#include /* MinGW provides dirent.h natively */ -++-#include -++-#include -++-#elif defined(OS2) && (defined(IBMC) || defined(__WATCOMC__)) -++-#include -++-#include -++-#include -++-#include "os2/dirent.h" /* opendir/readdir/closedir for OS/2 ICC/WC */ -++-#elif defined(OS2) -++-#include -++-#include /* EMX provides dirent.h natively */ -++-#include -++-#include -++-#include -++-#elif defined(DOS) -++-#include -++-#include -++-#include "dos/dirent.h" /* opendir/readdir/closedir for DOS/DJGPP */ -++-#else /* POSIX / *nix */ -++-#include -++-#include -++-#include -++-#include -++-#include -++-#endif -++- -++-#ifndef MAXPATHLEN -++-#if defined(_MAX_PATH) -++-#define MAXPATHLEN _MAX_PATH -++-#elif defined(PATH_MAX) -++-#define MAXPATHLEN PATH_MAX -++-#else -++-#define MAXPATHLEN 1024 -++-#endif -++-#endif -++- -++-/* Generic line buffer size for config files and text processing */ -++-#ifndef MAX_LINE -++-#define MAX_LINE 1024 -++-#endif -++- -++-/* path_exists / mkdir_one -- native implementations per OS */ -++-int port_path_exists(const char *p); -++-int port_mkdir_one(const char *p); -++-#define path_exists(p) port_path_exists(p) -++-#define mkdir_one(p) port_mkdir_one(p) -++- -++-/* safe_localtime -- thread-safe localtime, portable across all OS */ -++-void safe_localtime(const time_t *t, struct tm *tm); -++- -++-/* mkdir_recursive -- create full path, making all missing components */ -++-#define MP_MAXPATH 512 -++-int mkdir_recursive(const char *path); -++- -++-/* safe_strncpy -- strncpy that always NUL-terminates */ -++-void safe_strncpy(char *dst, const char *src, int dstsize); -++- -++-/* String utilities */ -++-void trim_nl(char *s); -++-void str_trim(char *s); -++-void str_upper(char *s); -++-void str_tolower(char *s); -++-char *skip_ws(char *s); -++- -++-/* Wildcard matching */ -++-int wildmatch(const char *pat, const char *str); -++-int is_wildcard(const char *s); -++- -++-/* File operations */ -++-int ensure_dir(const char *path); -++-int copy_file(const char *src, const char *dst); -++-int move_file(const char *src, const char *dst); -++-long get_file_size(const char *path); -++-long get_file_mtime(const char *path); -++- -++-/* Path utilities */ -++-void path_join(char *out, int outsize, const char *base, const char *sub); -++-int make_abs_path(const char *src, char *dst, int dstlen); -++- -++-#endif /* BINKD_PORTABLE_H */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/process_tic.c binkd_pgul/misc/process_tic.c -++--- binkd/misc/process_tic.c 2026-04-26 13:39:53.974576140 +0100 -+++++ binkd_pgul/misc/process_tic.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,552 +0,0 @@ -++-/* -++- * process_tic -- Process FTN .tic files from inbound to filebox -++- * -++- * process_tic.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include "portable.h" /* Canonical portable layer */ -++-#include -++- -++-static int my_toupper(int c) -++-{ -++- if (c >= 'a' && c <= 'z') -++- return c - 'a' + 'A'; -++- -++- return c; -++-} -++- -++-static int my_strnicmp(const char *a, const char *b, int n) -++-{ -++- int i, ca, cb; -++- for (i = 0; i < n; i++) -++- { -++- ca = my_toupper((unsigned char)a[i]); -++- cb = my_toupper((unsigned char)b[i]); -++- -++- if (ca != cb) -++- return ca - cb; -++- -++- if (ca == 0) -++- return 0; -++- } -++- -++- return 0; -++-} -++- -++-static int parse_file_field(char *line, char *out, int outsize) -++-{ -++- char *p; -++- char *end; -++- int len; -++- -++- p = skip_ws(line); -++- -++- if (my_strnicmp(p, "File", 4) != 0) -++- return 0; -++- -++- p += 4; -++- -++- if (*p != ' ' && *p != '\t') -++- return 0; -++- -++- p = skip_ws(p); -++- trim_nl(p); -++- end = p; -++- -++- while (*end && *end != ' ' && *end != '\t') -++- end++; -++- -++- *end = '\0'; -++- -++- len = (int)strlen(p); -++- -++- if (len <= 0 || len >= outsize) -++- return 0; -++- -++- strncpy(out, p, outsize - 1); -++- out[outsize - 1] = '\0'; -++- -++- return 1; -++-} -++- -++-static int parse_area_field(char *line, char *out, int outsize) -++-{ -++- char *p; -++- char *end; -++- int len; -++- -++- p = skip_ws(line); -++- -++- if (my_strnicmp(p, "Area", 4) != 0) -++- return 0; -++- -++- p += 4; -++- -++- if (*p != ' ' && *p != '\t') -++- return 0; -++- -++- p = skip_ws(p); -++- trim_nl(p); -++- end = p; -++- -++- while (*end && *end != ' ' && *end != '\t') -++- end++; -++- -++- *end = '\0'; -++- len = (int)strlen(p); -++- -++- if (len <= 0 || len >= outsize) -++- return 0; -++- -++- strncpy(out, p, outsize - 1); -++- out[outsize - 1] = '\0'; -++- -++- return 1; -++-} -++- -++-static int parse_origin_field(char *line, char *out, int outsize) -++-{ -++- char *p; -++- char *end; -++- int len; -++- -++- p = skip_ws(line); -++- -++- if (my_strnicmp(p, "Origin", 6) != 0) -++- return 0; -++- -++- p += 6; -++- -++- if (*p != ' ' && *p != '\t') -++- return 0; -++- -++- p = skip_ws(p); -++- trim_nl(p); -++- end = p; -++- -++- while (*end && *end != ' ' && *end != '\t') -++- end++; -++- -++- *end = '\0'; -++- len = (int)strlen(p); -++- -++- if (len <= 0 || len >= outsize) -++- return 0; -++- -++- strncpy(out, p, outsize - 1); -++- out[outsize - 1] = '\0'; -++- -++- return 1; -++-} -++- -++-static int parse_from_field(char *line, char *out, int outsize) -++-{ -++- char *p; -++- char *end; -++- int len; -++- -++- p = skip_ws(line); -++- -++- if (my_strnicmp(p, "From", 4) != 0) -++- return 0; -++- -++- p += 4; -++- -++- if (*p != ' ' && *p != '\t') -++- return 0; -++- -++- p = skip_ws(p); -++- trim_nl(p); -++- end = p; -++- -++- while (*end && *end != ' ' && *end != '\t') -++- end++; -++- -++- *end = '\0'; -++- len = (int)strlen(p); -++- -++- if (len <= 0 || len >= outsize) -++- return 0; -++- -++- strncpy(out, p, outsize - 1); -++- out[outsize - 1] = '\0'; -++- -++- return 1; -++-} -++- -++-static void append_filelist(const char *listpath, const char *file_name, long filesize, const char *dst_path) -++-{ -++- FILE *f; -++- time_t t; -++- struct tm tm; -++- char timestamp[32]; -++- -++- if (!listpath || !listpath[0]) -++- return; -++- -++- f = fopen(listpath, "a"); -++- if (!f) -++- return; -++- -++- t = time(NULL); -++- safe_localtime(&t, &tm); -++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -++- -++- fprintf(f, "%s\t%s\t%ld\t%s\n", timestamp, file_name, filesize, dst_path); -++- fclose(f); -++-} -++- -++-static void append_newfiles(const char *newprefix, const char *file_name, long filesize, const char *dst_path) -++-{ -++- FILE *f; -++- char newpath[MAXPATHLEN]; -++- time_t t; -++- struct tm tm; -++- char datebuf[16]; -++- -++- if (!newprefix || !newprefix[0]) -++- return; -++- -++- t = time(NULL); -++- safe_localtime(&t, &tm); -++- strftime(datebuf, sizeof(datebuf), "%Y%m%d", &tm); -++- -++- ensure_dir(newprefix); -++- path_join(newpath, (int)sizeof(newpath), newprefix, "newfiles-"); -++- strncat(newpath, datebuf, sizeof(newpath) - strlen(newpath) - 1); -++- strncat(newpath, ".txt", sizeof(newpath) - strlen(newpath) - 1); -++- -++- f = fopen(newpath, "a"); -++- -++- if (!f) -++- return; -++- -++- fprintf(f, "%s\t%ld\t%s\n", file_name, filesize, dst_path); -++- -++- fclose(f); -++-} -++- -++-static void write_ticlog(const char *ticlog, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) -++-{ -++- FILE *f; -++- time_t t; -++- struct tm tm; -++- char timestamp[64]; -++- -++- if (!ticlog || !ticlog[0]) -++- return; -++- -++- f = fopen(ticlog, "a"); -++- if (!f) -++- return; -++- -++- t = time(NULL); -++- safe_localtime(&t, &tm); -++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -++- -++- fprintf(f, "[%s] File: %s\n", timestamp, file_name); -++- fprintf(f, " Area: %s\n", area_name); -++- -++- if (origin_name[0]) -++- fprintf(f, " Origin: %s\n", origin_name); -++- -++- if (from_name[0]) -++- fprintf(f, " From: %s\n", from_name); -++- -++- fprintf(f, " Src: %s\n", src_path); -++- fprintf(f, " To: %s\n", dst_path); -++- fprintf(f, "\n"); -++- -++- fclose(f); -++-} -++- -++-static void write_log(const char *logfile, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) -++-{ -++- FILE *f; -++- time_t t; -++- struct tm tm; -++- char timestamp[64]; -++- -++- if (!logfile || !logfile[0]) -++- return; -++- -++- f = fopen(logfile, "a"); -++- if (!f) -++- return; -++- -++- t = time(NULL); -++- safe_localtime(&t, &tm); -++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -++- -++- fprintf(f, "[%s] File: %s\n", timestamp, file_name); -++- fprintf(f, " Area: %s\n", area_name); -++- -++- if (origin_name[0]) -++- fprintf(f, " Origin: %s\n", origin_name); -++- -++- if (from_name[0]) -++- fprintf(f, " From: %s\n", from_name); -++- -++- fprintf(f, " Src: %s\n", src_path); -++- fprintf(f, " To: %s\n", dst_path); -++- fprintf(f, "\n"); -++- fclose(f); -++-} -++- -++-static void process_one_tic(const char *ticpath, const char *inbound, const char *filebox, int copypublic, const char *pubdir, const char *logfile, const char *filelist, const char *newfiles, const char *ticlog) -++-{ -++- FILE *f; -++- char line[MAX_LINE]; -++- char file_name[MAXPATHLEN]; -++- char area_name[MAXPATHLEN]; -++- char origin_name[MAXPATHLEN]; -++- char from_name[MAXPATHLEN]; -++- char src_path[MAXPATHLEN]; -++- char area_dir[MAXPATHLEN]; -++- char dst_path[MAXPATHLEN]; -++- -++- file_name[0] = '\0'; -++- area_name[0] = '\0'; -++- origin_name[0] = '\0'; -++- from_name[0] = '\0'; -++- long fsize = 0; -++- -++- f = fopen(ticpath, "r"); -++- -++- if (!f) -++- return; -++- -++- while (fgets(line, sizeof(line), f)) -++- { -++- if (!file_name[0]) -++- parse_file_field(line, file_name, sizeof(file_name)); -++- -++- if (!area_name[0]) -++- parse_area_field(line, area_name, sizeof(area_name)); -++- -++- if (!origin_name[0]) -++- parse_origin_field(line, origin_name, sizeof(origin_name)); -++- -++- if (!from_name[0]) -++- parse_from_field(line, from_name, sizeof(from_name)); -++- } -++- -++- fclose(f); -++- -++- if (!file_name[0] || !area_name[0]) -++- return; -++- -++- path_join(src_path, sizeof(src_path), inbound, file_name); -++- path_join(area_dir, sizeof(area_dir), filebox, area_name); -++- path_join(dst_path, sizeof(dst_path), area_dir, file_name); -++- -++- if (!path_exists(src_path)) -++- return; -++- -++- if (!ensure_dir(filebox) || !ensure_dir(area_dir)) -++- return; -++- -++- if (copypublic && pubdir && pubdir[0]) -++- { -++- char pub_dst[MAXPATHLEN]; -++- -++- path_join(pub_dst, sizeof(pub_dst), pubdir, file_name); -++- -++- if (ensure_dir(pubdir)) -++- copy_file(src_path, pub_dst); -++- } -++- -++- fsize = get_file_size(src_path); -++- -++- if (!move_file(src_path, dst_path)) -++- return; -++- -++- write_log(logfile, file_name, area_name, origin_name, from_name, src_path, dst_path); -++- write_ticlog(ticlog, file_name, area_name, origin_name, from_name, src_path, dst_path); -++- append_filelist(filelist, file_name, fsize, dst_path); -++- append_newfiles(newfiles, file_name, fsize, dst_path); -++- -++- remove(ticpath); -++-} -++- -++-static int is_tic_file(const char *name) -++-{ -++- int len = (int)strlen(name); -++- -++- if (len < 5) -++- return 0; -++- -++- return (my_strnicmp(name + len - 4, ".tic", 4) == 0); -++-} -++- -++-/* Config structure */ -++-static struct -++-{ -++- char inbound[MAXPATHLEN]; -++- char filebox[MAXPATHLEN]; -++- char pubdir[MAXPATHLEN]; -++- char logfile[MAXPATHLEN]; -++- char filelist[MAXPATHLEN]; -++- char newfiles[MAXPATHLEN]; -++- char ticlog[MAXPATHLEN]; -++- int copypublic; -++-} cfg; -++- -++-/* Parse configuration file */ -++-static int parse_config(const char *conffile) -++-{ -++- FILE *f; -++- char line[MAX_LINE]; -++- char *key, *value; -++- -++- memset(&cfg, 0, sizeof(cfg)); -++- -++- f = fopen(conffile, "r"); -++- -++- if (!f) -++- { -++- fprintf(stderr, "process_tic: cannot open config file: %s\n", conffile); -++- return 0; -++- } -++- -++- while (fgets(line, sizeof(line), f)) -++- { -++- trim_nl(line); -++- key = skip_ws(line); -++- -++- /* Skip comments and empty lines */ -++- if (*key == '#' || *key == '\0') -++- continue; -++- -++- /* Find value after key */ -++- value = key; -++- -++- while (*value && *value != ' ' && *value != '\t') -++- value++; -++- -++- if (*value) -++- { -++- *value = '\0'; -++- value = skip_ws(value + 1); -++- } -++- -++- /* Parse key-value pairs */ -++- if (strcmp(key, "inbound") == 0) -++- safe_strncpy(cfg.inbound, value, (int)sizeof(cfg.inbound)); -++- else if (strcmp(key, "filebox") == 0) -++- safe_strncpy(cfg.filebox, value, (int)sizeof(cfg.filebox)); -++- else if (strcmp(key, "pubdir") == 0) -++- { -++- safe_strncpy(cfg.pubdir, value, (int)sizeof(cfg.pubdir)); -++- cfg.copypublic = 1; -++- } -++- else if (strcmp(key, "logfile") == 0) -++- safe_strncpy(cfg.logfile, value, (int)sizeof(cfg.logfile)); -++- else if (strcmp(key, "filelist") == 0) -++- safe_strncpy(cfg.filelist, value, (int)sizeof(cfg.filelist)); -++- else if (strcmp(key, "newfiles") == 0) -++- safe_strncpy(cfg.newfiles, value, (int)sizeof(cfg.newfiles)); -++- else if (strcmp(key, "ticlog") == 0) -++- safe_strncpy(cfg.ticlog, value, (int)sizeof(cfg.ticlog)); -++- } -++- -++- fclose(f); -++- -++- /* Validate required fields */ -++- if (!cfg.inbound[0] || !cfg.filebox[0]) -++- { -++- fprintf(stderr, "process_tic: config file missing required 'inbound' or 'filebox'\n"); -++- return 0; -++- } -++- -++- return 1; -++-} -++- -++-int main(int argc, char *argv[]) -++-{ -++- char inbound[MAXPATHLEN]; -++- char filebox[MAXPATHLEN]; -++- char ticpath[MAXPATHLEN]; -++- char pubdir[MAXPATHLEN]; -++- char logfile[MAXPATHLEN]; -++- char filelist[MAXPATHLEN]; -++- char newfiles[MAXPATHLEN]; -++- char ticlog[MAXPATHLEN]; -++- int copypublic = 0; -++- DIR *dp; -++- struct dirent *de; -++- int found; -++- int i; -++- int use_config = 0; -++- -++- inbound[0] = '\0'; -++- filebox[0] = '\0'; -++- pubdir[0] = '\0'; -++- logfile[0] = '\0'; -++- filelist[0] = '\0'; -++- newfiles[0] = '\0'; -++- ticlog[0] = '\0'; -++- -++- /* Check for --conf option */ -++- for (i = 1; i < argc; i++) -++- { -++- if (strcmp(argv[i], "--conf") == 0 && i + 1 < argc) -++- { -++- if (!parse_config(argv[i + 1])) -++- return 1; -++- -++- use_config = 1; -++- i++; /* Skip config file path */ -++- -++- break; -++- } -++- } -++- -++- if (use_config) -++- { -++- /* Use config file values */ -++- safe_strncpy(inbound, cfg.inbound, (int)sizeof(inbound)); -++- safe_strncpy(filebox, cfg.filebox, (int)sizeof(filebox)); -++- safe_strncpy(pubdir, cfg.pubdir, (int)sizeof(pubdir)); -++- safe_strncpy(logfile, cfg.logfile, (int)sizeof(logfile)); -++- safe_strncpy(filelist, cfg.filelist, (int)sizeof(filelist)); -++- safe_strncpy(newfiles, cfg.newfiles, (int)sizeof(newfiles)); -++- safe_strncpy(ticlog, cfg.ticlog, (int)sizeof(ticlog)); -++- copypublic = cfg.copypublic; -++- } -++- -++- if (!inbound[0] || !filebox[0]) -++- { -++- fprintf(stderr, -++- "Usage: process_tic --conf [*.tic]\n"); -++- -++- return 1; -++- } -++- -++- dp = opendir(inbound); -++- -++- if (!dp) -++- return 1; -++- -++- found = 0; -++- -++- while ((de = readdir(dp)) != NULL) -++- { -++- /* Skip . and .. */ -++- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) -++- continue; -++- -++- if (!is_tic_file(de->d_name)) -++- continue; -++- -++- path_join(ticpath, sizeof(ticpath), inbound, de->d_name); -++- process_one_tic(ticpath, inbound, filebox, copypublic, pubdir, logfile, filelist, newfiles, ticlog); -++- found++; -++- } -++- -++- closedir(dp); -++- return 0; -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/process_tic.txt binkd_pgul/misc/process_tic.txt -++--- binkd/misc/process_tic.txt 2026-04-26 13:43:48.542112490 +0100 -+++++ binkd_pgul/misc/process_tic.txt 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,45 +0,0 @@ -++-process_tic -- Process .tic file announcements -++- -++-USAGE: -++- process_tic --conf [files.tic...] -++- -++-DESCRIPTION: -++- Processes .tic (Ticker announcement) files from the inbound directory -++- and moves/copies files to their destination filebox or public directory. -++- -++- The .tic file is parsed for File, Area, Origin, and From fields. -++- The actual file is moved from inbound to filebox/AreaName/. -++- -++- All settings are read from the configuration file. -++- -++-OPTIONS: -++- --conf Configuration file (required) -++- -++-CONFIGURATION FILE FORMAT: -++- # Lines starting with # are comments -++- # Blank lines are ignored -++- -++- inbound Inbound directory (required) -++- filebox Filebox destination (required) -++- pubdir Public directory for --copy-public -++- logfile Log file path -++- ticlog TIC processing log -++- filelist File list output -++- newfiles New files list output -++- -++-EXAMPLE CONFIG FILE (process_tic.conf): -++- # process_tic.conf - Configuration for TIC processor -++- -++- inbound Work:Inbound -++- filebox Work:Filebox -++- pubdir Work:Public -++- logfile Work:Logs/process_tic.log -++- ticlog Work:Logs/tic.log -++- filelist Work:Filebox/filelist.txt -++- newfiles Work:Filebox/newfiles.txt -++- -++-EXAMPLES: -++- process_tic --conf process_tic.conf -++- process_tic --conf process_tic.conf inbound/*.tic -++- -++- exec "process_tic --conf work:fido/process_tic.conf" *.tic *.TIC -++\ No hay ningún carácter de nueva línea al final del archivo -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/srifreq.c binkd_pgul/misc/srifreq.c -++--- binkd/misc/srifreq.c 2026-04-26 15:01:11.006850708 +0100 -+++++ binkd_pgul/misc/srifreq.c 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,1024 +0,0 @@ -++-/* -++- * srifreq.c -- SRIF-compatible file-request server for binkd -++- * -++- * srifreq.c is a part of binkd project -++- * -++- * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -++- * Licensed under the GNU GPL v2 or later -++- */ -++- -++-#include "portable.h" /* Canonical portable layer */ -++-#include -++-#include -++- -++-/* Private directory entry (dynamically allocated list) */ -++-typedef struct PrivDir -++-{ -++- char path[MAXPATHLEN]; -++- char password[64]; -++- struct PrivDir *next; -++-} PrivDir; -++- -++-/* Node tracking entry for rate limiting */ -++-typedef struct NodeTrack -++-{ -++- char aka[256]; /* Node address (4D/5D) */ -++- int files; /* Files downloaded in window */ -++- long bytes; /* Bytes downloaded in window */ -++- time_t last_time; /* Timestamp of last download */ -++- struct NodeTrack *next; -++-} NodeTrack; -++- -++-/* Global configuration (filled from --conf file) */ -++-typedef struct -++-{ -++- char pubdir[MAXPATHLEN]; -++- char logfile[MAXPATHLEN]; -++- char aliases[MAXPATHLEN]; -++- char trackfile[MAXPATHLEN]; /* Path to tracking file */ -++- int maxfiles; /* Max files per node per window (0=unlimited) */ -++- long maxbytes; /* Max bytes per node per window (0=unlimited) */ -++- long timewindow; /* Time window in seconds (0=no window) */ -++- PrivDir *privdirs; /* Linked list, NULL if none */ -++- NodeTrack *tracking; /* Linked list of tracked nodes */ -++-} Config; -++- -++-/* Alias table -- Loaded from file at startup */ -++-typedef struct -++-{ -++- char name[64]; -++- char path[MAXPATHLEN]; -++-} Alias; -++- -++-/* SRIF parsing */ -++-typedef struct -++-{ -++- char sysop[128]; -++- char aka[256]; -++- char request_list[MAXPATHLEN]; -++- char response_list[MAXPATHLEN]; -++- char our_aka[128]; -++- char caller_id[64]; /* CallerID: IP or phone of remote */ -++- char password[64]; /* Password: session password */ -++- int time_limit; /* Time: minutes left, -1 = unlimited */ -++- long tranx; /* TRANX: remote local time as Unix ts (hex in SRIF) */ -++- int protected_sess; /* RemoteStatus: 1=PROTECTED, 0=UNPROTECTED */ -++- int listed; /* SystemStatus: 1=LISTED, 0=UNLISTED */ -++- int got_request_list; -++- int got_response_list; -++-} SRIF; -++- -++-static Alias *g_aliases = NULL; -++-static int g_nalias = 0; -++-static int g_alias_cap = 0; -++-static Config g_conf; -++- -++-static void config_init(void) -++-{ -++- memset(&g_conf, 0, sizeof(g_conf)); -++- g_conf.privdirs = NULL; -++- g_conf.tracking = NULL; -++- g_conf.maxfiles = 0; /* 0 = unlimited */ -++- g_conf.maxbytes = 0; /* 0 = unlimited */ -++- g_conf.timewindow = 0; /* 0 = no window */ -++-} -++- -++-static void config_add_private(const char *path, const char *password) -++-{ -++- PrivDir *pd = (PrivDir *)malloc(sizeof(PrivDir)); -++- PrivDir *tail; -++- -++- if (!pd) -++- return; -++- -++- safe_strncpy(pd->path, path, (int)sizeof(pd->path)); -++- safe_strncpy(pd->password, password, (int)sizeof(pd->password)); -++- -++- pd->next = NULL; -++- -++- /* Append to tail */ -++- if (!g_conf.privdirs) -++- g_conf.privdirs = pd; -++- else -++- { -++- tail = g_conf.privdirs; -++- -++- while (tail->next) -++- tail = tail->next; -++- -++- tail->next = pd; -++- } -++-} -++- -++-static void config_free(void) -++-{ -++- PrivDir *pd = g_conf.privdirs; -++- NodeTrack *nt = g_conf.tracking; -++- -++- while (pd) -++- { -++- PrivDir *next = pd->next; -++- free(pd); -++- pd = next; -++- } -++- -++- g_conf.privdirs = NULL; -++- -++- while (nt) -++- { -++- NodeTrack *next = nt->next; -++- free(nt); -++- nt = next; -++- } -++- -++- g_conf.tracking = NULL; -++- -++- if (g_aliases) -++- { -++- free(g_aliases); -++- g_aliases = NULL; -++- g_nalias = 0; -++- g_alias_cap = 0; -++- } -++-} -++- -++-static int load_config(const char *path) -++-{ -++- FILE *f; -++- char line[MAX_LINE]; -++- char key[64], val[MAXPATHLEN], pw[64]; -++- int n; -++- -++- f = fopen(path, "r"); -++- -++- if (!f) -++- { -++- fprintf(stderr, "srifreq: cannot open config: %s\n", path); -++- return 0; -++- } -++- -++- while (fgets(line, sizeof(line), f)) -++- { -++- /* Strip trailing whitespace and newlines */ -++- n = (int)strlen(line); -++- -++- while (n > 0 && -++- (line[n - 1] == '\r' || line[n - 1] == '\n' || line[n - 1] == ' ')) -++- line[--n] = '\0'; -++- -++- /* Skip blank and comment lines */ -++- if (!line[0] || line[0] == '#') -++- continue; -++- -++- key[0] = val[0] = pw[0] = '\0'; -++- -++- if (sscanf(line, "%63s %1023s %63s", key, val, pw) < 2) -++- continue; -++- -++- if (strcmp(key, "pubdir") == 0) -++- safe_strncpy(g_conf.pubdir, val, (int)sizeof(g_conf.pubdir)); -++- else if (strcmp(key, "logfile") == 0) -++- safe_strncpy(g_conf.logfile, val, (int)sizeof(g_conf.logfile)); -++- else if (strcmp(key, "aliases") == 0) -++- safe_strncpy(g_conf.aliases, val, (int)sizeof(g_conf.aliases)); -++- else if (strcmp(key, "trackfile") == 0) -++- safe_strncpy(g_conf.trackfile, val, (int)sizeof(g_conf.trackfile)); -++- else if (strcmp(key, "maxfiles") == 0) -++- g_conf.maxfiles = atoi(val); -++- else if (strcmp(key, "maxsize") == 0) -++- g_conf.maxbytes = atol(val); -++- else if (strcmp(key, "timewindow") == 0) -++- g_conf.timewindow = atol(val); -++- else if (strcmp(key, "private") == 0 && pw[0]) -++- config_add_private(val, pw); -++- } -++- -++- fclose(f); -++- -++- return 1; -++-} -++- -++-/* tracking_load -- Load node tracking data from file */ -++-static void tracking_load(void) -++-{ -++- FILE *f; -++- char line[MAX_LINE]; -++- char aka[256]; -++- int files; -++- long bytes; -++- long timestamp; -++- NodeTrack *nt; -++- time_t now = time(NULL); -++- -++- if (!g_conf.trackfile[0]) -++- return; -++- -++- f = fopen(g_conf.trackfile, "r"); -++- -++- if (!f) -++- return; -++- -++- while (fgets(line, sizeof(line), f)) -++- { -++- str_trim(line); -++- -++- if (!line[0] || line[0] == '#') -++- continue; -++- -++- if (sscanf(line, "%255s %d %ld %ld", aka, &files, &bytes, ×tamp) != 4) -++- continue; -++- -++- /* Skip if outside time window (also reject future/corrupt timestamps) */ -++- if (g_conf.timewindow > 0 && (timestamp > now || (now - timestamp) > g_conf.timewindow)) -++- continue; -++- -++- /* Create new tracking entry */ -++- nt = (NodeTrack *)malloc(sizeof(NodeTrack)); -++- -++- if (!nt) -++- continue; -++- -++- safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); -++- nt->files = files; -++- nt->bytes = bytes; -++- nt->last_time = (time_t)timestamp; -++- nt->next = g_conf.tracking; -++- g_conf.tracking = nt; -++- } -++- -++- fclose(f); -++-} -++- -++-/* tracking_save -- Save node tracking data to file */ -++-static void tracking_save(void) -++-{ -++- FILE *f; -++- NodeTrack *nt; -++- -++- if (!g_conf.trackfile[0]) -++- return; -++- -++- f = fopen(g_conf.trackfile, "w"); -++- -++- if (!f) -++- return; -++- -++- fprintf(f, "# srifreq tracking file - Format: AKA files bytes timestamp\n"); -++- -++- for (nt = g_conf.tracking; nt; nt = nt->next) -++- { -++- fprintf(f, "%s %d %ld %ld\n", nt->aka, nt->files, nt->bytes, (long)nt->last_time); -++- } -++- -++- fclose(f); -++-} -++- -++-/* tracking_find -- Find tracking entry for a node */ -++-static NodeTrack *tracking_find(const char *aka) -++-{ -++- NodeTrack *nt; -++- -++- for (nt = g_conf.tracking; nt; nt = nt->next) -++- { -++- if (strcmp(nt->aka, aka) == 0) -++- return nt; -++- } -++- -++- return NULL; -++-} -++- -++-/* tracking_update -- Update tracking after serving a file */ -++-static void tracking_update(const char *aka, long filesize) -++-{ -++- NodeTrack *nt = tracking_find(aka); -++- time_t now = time(NULL); -++- -++- if (nt) -++- { -++- /* Update existing entry */ -++- nt->files++; -++- nt->bytes += filesize; -++- nt->last_time = now; -++- } -++- else -++- { -++- /* Create new entry */ -++- nt = (NodeTrack *)malloc(sizeof(NodeTrack)); -++- -++- if (nt) -++- { -++- safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); -++- nt->files = 1; -++- nt->bytes = filesize; -++- nt->last_time = now; -++- nt->next = g_conf.tracking; -++- g_conf.tracking = nt; -++- } -++- } -++-} -++- -++-/* tracking_check -- Check if node exceeds limits */ -++-static int tracking_check(const char *aka, char *msg, int msglen) -++-{ -++- NodeTrack *nt = tracking_find(aka); -++- -++- if (!nt) -++- return 1; /* No tracking yet, allow */ -++- -++- /* Check max files */ -++- if (g_conf.maxfiles > 0 && nt->files >= g_conf.maxfiles) -++- { -++- snprintf(msg, msglen, "RATE LIMIT: max files (%d) reached for %s", g_conf.maxfiles, aka); -++- return 0; -++- } -++- -++- /* Check max bytes */ -++- if (g_conf.maxbytes > 0 && nt->bytes >= g_conf.maxbytes) -++- { -++- snprintf(msg, msglen, "RATE LIMIT: max bytes (%ld) reached for %s", g_conf.maxbytes, aka); -++- return 0; -++- } -++- -++- return 1; /* Within limits */ -++-} -++- -++-/* is_abs_path -- True if path is absolute (POSIX, Win32, AmigaDOS device:) */ -++-static int is_abs_path(const char *p) -++-{ -++- if (!p || !p[0]) -++- return 0; -++- -++- if (p[0] == '/' || p[0] == '\\') -++- return 1; -++- -++-#ifdef AMIGA -++- if (strchr(p, ':') != NULL) -++- return 1; -++-#else -++- if (p[1] == ':') -++- return 1; /* C:\ etc. */ -++-#endif -++- return 0; -++-} -++- -++-/* -++- * load_aliases -- Read alias definitions from file -++- * Lines starting with '#' or empty are skipped -++- * Format: -++- */ -++-static void load_aliases(const char *filepath) -++-{ -++- FILE *f; -++- char line[MAX_LINE]; -++- char name[64]; -++- char path[MAXPATHLEN]; -++- int n; -++- -++- /* Free previous aliases and start fresh */ -++- if (g_aliases) -++- { -++- free(g_aliases); -++- g_aliases = NULL; -++- } -++- -++- g_nalias = 0; -++- g_alias_cap = 0; -++- -++- if (!filepath || !filepath[0] || strcmp(filepath, "-") == 0) -++- return; -++- -++- f = fopen(filepath, "r"); -++- -++- if (!f) -++- { -++- /*fprintf(stderr, "srifreq: cannot open aliases file: %s\n", filepath);*/ -++- return; -++- } -++- -++- while (fgets(line, sizeof(line), f)) -++- { -++- char *p; -++- -++- /* Strip trailing newline */ -++- n = (int)strlen(line); -++- -++- while (n > 0 && (line[n - 1] == '\r' || line[n - 1] == '\n')) -++- line[--n] = '\0'; -++- -++- /* Skip blanks and comments */ -++- p = line; -++- -++- while (*p == ' ' || *p == '\t') -++- p++; -++- -++- if (!*p || *p == '#') -++- continue; -++- -++- name[0] = '\0'; -++- path[0] = '\0'; -++- -++- if (sscanf(p, "%63s %1023[^\n]", name, path) < 2) -++- continue; -++- -++- if (!name[0] || !path[0]) -++- continue; -++- -++- /* Grow array dynamically if needed */ -++- if (g_nalias >= g_alias_cap) -++- { -++- int new_cap = g_alias_cap ? g_alias_cap * 2 : 16; -++- Alias *new_arr = realloc(g_aliases, (size_t)new_cap * sizeof(Alias)); -++- -++- if (!new_arr) -++- break; -++- -++- g_aliases = new_arr; -++- g_alias_cap = new_cap; -++- } -++- -++- safe_strncpy(g_aliases[g_nalias].name, name, (int)sizeof(g_aliases[g_nalias].name)); -++- safe_strncpy(g_aliases[g_nalias].path, path, (int)sizeof(g_aliases[g_nalias].path)); -++- g_nalias++; -++- } -++- -++- fclose(f); -++- -++- /*printf("srifreq: loaded %d alias(es) from %s\n", g_nalias, filepath);*/ -++-} -++- -++-/* -++- * find_alias -- look up name in alias table (case-insensitive) -++- * Returns the path string, or NULL if not found -++- */ -++-static const char *find_alias(const char *name) -++-{ -++- char upper[64]; -++- char aname[64]; -++- int i; -++- int n; -++- -++- /* Convert name to uppercase */ -++- n = (int)strlen(name); -++- -++- if (n >= (int)sizeof(upper)) -++- n = (int)sizeof(upper) - 1; -++- -++- for (i = 0; i < n; i++) -++- upper[i] = (char)toupper((unsigned char)name[i]); -++- -++- upper[n] = '\0'; -++- -++- for (i = 0; i < g_nalias; i++) -++- { -++- int an; -++- an = (int)strlen(g_aliases[i].name); -++- -++- if (an >= (int)sizeof(aname)) an = (int)sizeof(aname) - 1; -++- { -++- int j; -++- -++- for (j = 0; j < an; j++) -++- aname[j] = (char)toupper((unsigned char)g_aliases[i].name[j]); -++- -++- aname[an] = '\0'; -++- } -++- -++- if (strcmp(upper, aname) == 0) -++- return g_aliases[i].path; -++- } -++- -++- return NULL; -++-} -++- -++-static int parse_srif(const char *path, SRIF *srif) -++-{ -++- FILE *f; -++- char line[MAX_LINE]; -++- char token[MAX_LINE]; -++- char value[MAX_LINE]; -++- -++- memset(srif, 0, sizeof(SRIF)); -++- srif->time_limit = -1; /* default: unlimited */ -++- srif->listed = 1; /* default: assume listed */ -++- srif->protected_sess = 0; -++- -++- f = fopen(path, "r"); -++- if (!f) -++- return 0; -++- -++- while (fgets(line, sizeof(line), f)) -++- { -++- str_trim(line); -++- if (!line[0]) -++- continue; -++- -++- token[0] = '\0'; -++- value[0] = '\0'; -++- -++- if (sscanf(line, "%1023s %1023[^\n]", token, value) < 1) -++- continue; -++- -++- str_upper(token); -++- -++- if (!value[0]) -++- continue; -++- -++- if (!strcmp(token, "SYSOP")) -++- safe_strncpy(srif->sysop, value, (int)sizeof(srif->sysop)); -++- else if (!strcmp(token, "AKA") && !srif->aka[0]) -++- safe_strncpy(srif->aka, value, (int)sizeof(srif->aka)); -++- else if (!strcmp(token, "REQUESTLIST")) -++- { -++- safe_strncpy(srif->request_list, value, MAXPATHLEN); -++- srif->got_request_list = 1; -++- } -++- else if (!strcmp(token, "RESPONSELIST")) -++- { -++- safe_strncpy(srif->response_list, value, MAXPATHLEN); -++- srif->got_response_list = 1; -++- } -++- else if (!strcmp(token, "OURAKA")) -++- safe_strncpy(srif->our_aka, value, (int)sizeof(srif->our_aka)); -++- else if (!strcmp(token, "PASSWORD")) -++- safe_strncpy(srif->password, value, (int)sizeof(srif->password)); -++- else if (!strcmp(token, "CALLERID")) -++- safe_strncpy(srif->caller_id, value, (int)sizeof(srif->caller_id)); -++- else if (!strcmp(token, "TIME")) -++- srif->time_limit = atoi(value); -++- else if (!strcmp(token, "TRANX")) -++- { -++- /* TRANX is a hex Unix timestamp: 5a326682 or 16-digit */ -++- unsigned long v = 0; -++- sscanf(value, "%lx", &v); -++- srif->tranx = (long)v; -++- } -++- else if (!strcmp(token, "REMOTESTATUS")) -++- { -++- char tmp[32]; -++- safe_strncpy(tmp, value, (int)sizeof(tmp)); -++- str_upper(tmp); -++- srif->protected_sess = (strncmp(tmp, "PROTECTED", 9) == 0) ? 1 : 0; -++- } -++- else if (!strcmp(token, "SYSTEMSTATUS")) -++- { -++- char tmp[32]; -++- safe_strncpy(tmp, value, (int)sizeof(tmp)); -++- str_upper(tmp); -++- srif->listed = (strncmp(tmp, "LISTED", 6) == 0) ? 1 : 0; -++- } -++- } -++- -++- fclose(f); -++- -++- return srif->got_request_list; -++-} -++- -++-/* Logging */ -++-static void do_log(const char *logpath, const char *msg) -++-{ -++- FILE *lf; -++- time_t t; -++- struct tm tm; -++- char timestamp[32]; -++- -++- if (!logpath || !logpath[0] || strcmp(logpath, "-") == 0) -++- return; -++- -++- t = time(NULL); -++- safe_localtime(&t, &tm); -++- strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -++- -++- lf = fopen(logpath, "a"); -++- -++- if (lf) -++- { -++- fprintf(lf, "[%s] srifreq: %s\n", timestamp, msg); -++- fclose(lf); -++- } -++-} -++- -++-/* serve_one -- resolve one request name, check password/timestamp/update -++- * write to response list. Returns 1 if served -++- */ -++-static int serve_one(const char *req_name, const char *found_path, const char *req_pass, long req_newer, int req_update, const SRIF *srif, FILE *rsp_f, const char *log_path, char *logbuf, int logbuf_size) -++-{ -++- long fsize; -++- -++- /* Check rate limits before serving */ -++- if (g_conf.trackfile[0] && !tracking_check(srif->aka, logbuf, logbuf_size)) -++- { -++- do_log(log_path, logbuf); -++- return 0; -++- } -++- -++- /* RemoteStatus: if session is unprotected and a password is required, deny */ -++- if (req_pass[0] && !srif->protected_sess) -++- { -++- snprintf(logbuf, logbuf_size, "PASSWORD DENY (unprotected session): %s", req_name); -++- do_log(log_path, logbuf); -++- return 0; -++- } -++- -++- /* Password check: !pw must match SRIF PASSWORD (case-insensitive) */ -++- if (req_pass[0]) -++- { -++- char rp[64], sp[64]; -++- int i; -++- -++- safe_strncpy(rp, req_pass, (int)sizeof(rp)); -++- safe_strncpy(sp, srif->password, (int)sizeof(sp)); -++- -++- for (i = 0; rp[i]; i++) -++- rp[i] = (char)toupper((unsigned char)rp[i]); -++- -++- for (i = 0; sp[i]; i++) -++- sp[i] = (char)toupper((unsigned char)sp[i]); -++- -++- if (strcmp(rp, sp) != 0) -++- { -++- snprintf(logbuf, logbuf_size, "PASSWORD FAIL: %s", req_name); -++- do_log(log_path, logbuf); -++- -++- return 0; -++- } -++- } -++- -++- /* Update request (U flag): serve only if file is newer than TRANX */ -++- if (req_update && srif->tranx > 0) -++- { -++- long mtime = get_file_mtime(found_path); -++- -++- if (mtime <= srif->tranx) -++- { -++- snprintf(logbuf, logbuf_size, "NOT UPDATED: %s (mtime=%ld tranx=%ld)", req_name, mtime, srif->tranx); -++- do_log(log_path, logbuf); -++- return 0; -++- } -++- } -++- -++- /* Timestamp check: +ts means "only if file is newer than ts" */ -++- if (req_newer > 0) -++- { -++- long mtime = get_file_mtime(found_path); -++- -++- if (mtime <= req_newer) -++- { -++- snprintf(logbuf, logbuf_size, "NOT NEWER: %s (mtime=%ld req=%ld)", req_name, mtime, req_newer); -++- -++- do_log(log_path, logbuf); -++- return 0; -++- } -++- } -++- -++- snprintf(logbuf, logbuf_size, "FOUND: %s -> %s", req_name, found_path); -++- do_log(log_path, logbuf); -++- -++- if (rsp_f) -++- fprintf(rsp_f, "+%s\r\n", found_path); -++- -++- /* Update tracking after successful serve */ -++- if (g_conf.trackfile[0]) -++- { -++- fsize = get_file_size(found_path); -++- -++- if (fsize < 0) -++- fsize = 0; -++- -++- tracking_update(srif->aka, fsize); -++- } -++- -++- return 1; -++-} -++- -++-int main(int argc, char *argv[]) -++-{ -++- const char *srif_path; -++- SRIF srif; -++- FILE *req_f; -++- FILE *rsp_f; -++- char line[MAX_LINE]; -++- char req_name[MAX_LINE]; -++- char req_pass[64]; -++- long req_newer; -++- int req_update; -++- char found_path[MAXPATHLEN]; -++- char logbuf[MAXPATHLEN * 4 + 128]; -++- int found_count; -++- -++- config_init(); -++- -++- /* --conf */ -++- if (argc >= 4 && strcmp(argv[1], "--conf") == 0) -++- { -++- if (!load_config(argv[2])) -++- return 1; -++- -++- srif_path = argv[3]; -++- } -++- else -++- { -++- fprintf(stderr, "Usage:\n" -++- " srifreq --conf \n" -++- "\n" -++- "Config file keys: pubdir, logfile, aliases, private " -++- "\n"); -++- -++- return 1; -++- } -++- -++- if (!g_conf.pubdir[0]) -++- { -++- fprintf(stderr, "srifreq: pubdir not set\n"); -++- config_free(); -++- return 1; -++- } -++- -++- /* Load tracking data if configured */ -++- tracking_load(); -++- -++- load_aliases(g_conf.aliases[0] ? g_conf.aliases : NULL); -++- -++- snprintf(logbuf, sizeof(logbuf), "processing SRIF: %s", srif_path); -++- do_log(g_conf.logfile, logbuf); -++- -++- if (!parse_srif(srif_path, &srif)) -++- { -++- snprintf(logbuf, sizeof(logbuf), "ERROR: cannot parse SRIF or missing RequestList: %s", srif_path); -++- do_log(g_conf.logfile, logbuf); -++- fprintf(stderr, "srifreq: %s\n", logbuf); -++- config_free(); -++- return 1; -++- } -++- -++- /* SystemStatus: deny unlisted systems entirely */ -++- if (!srif.listed) -++- { -++- snprintf(logbuf, sizeof(logbuf), "DENIED: system is UNLISTED (aka: %s)", srif.aka); -++- do_log(g_conf.logfile, logbuf); -++- config_free(); -++- return 1; -++- } -++- -++- snprintf(logbuf, sizeof(logbuf), "sysop: %s aka: %s status: %s%s caller: %s req: %s", srif.sysop, srif.aka, srif.protected_sess ? "PROTECTED" : "UNPROTECTED", srif.tranx ? " (TRANX)" : "", srif.caller_id[0] ? srif.caller_id : "?", srif.request_list); -++- do_log(g_conf.logfile, logbuf); -++- -++- /* Log rate limiting status if active */ -++- if (g_conf.trackfile[0] && (g_conf.maxfiles > 0 || g_conf.maxbytes > 0)) -++- { -++- snprintf(logbuf, sizeof(logbuf), "rate limits: maxfiles=%d maxbytes=%ld window=%lds", g_conf.maxfiles, g_conf.maxbytes, g_conf.timewindow); -++- do_log(g_conf.logfile, logbuf); -++- } -++- -++- req_f = fopen(srif.request_list, "r"); -++- -++- if (!req_f) -++- { -++- snprintf(logbuf, sizeof(logbuf), "WARN: RequestList not available: %s", srif.request_list); -++- do_log(g_conf.logfile, logbuf); -++- config_free(); -++- return 0; -++- } -++- -++- rsp_f = NULL; -++- -++- if (srif.got_response_list && srif.response_list[0]) -++- { -++- rsp_f = fopen(srif.response_list, "w"); -++- -++- if (!rsp_f) -++- { -++- snprintf(logbuf, sizeof(logbuf), "WARN: cannot create ResponseList: %s", srif.response_list); -++- do_log(g_conf.logfile, logbuf); -++- } -++- } -++- -++- found_count = 0; -++- -++- /* Check and update track file */ -++- while (fgets(line, sizeof(line), req_f)) -++- { -++- char *p; -++- const char *alias_path; -++- -++- str_trim(line); -++- -++- if (!line[0] || line[0] == ';' || line[0] == '#') -++- continue; -++- -++- /* Parse: filename [!password] [+timestamp] [U] */ -++- req_name[0] = '\0'; -++- req_pass[0] = '\0'; -++- req_newer = 0; -++- req_update = 0; -++- -++- if (sscanf(line, "%1023s", req_name) < 1) -++- continue; -++- -++- /* Skip URLs */ -++- if (strncmp(req_name, "http", 4) == 0 || strncmp(req_name, "ftp", 3) == 0) -++- continue; -++- -++- /* Parse modifiers from the rest of the line */ -++- p = strstr(line, req_name); -++- -++- if (p) -++- p += strlen(req_name); -++- else -++- p = line + strlen(line); -++- -++- while (*p) -++- { -++- while (*p == ' ' || *p == '\t') -++- p++; -++- -++- if (*p == '!') -++- { -++- /* !password */ -++- int i = 0; -++- p++; -++- -++- while (*p && *p != ' ' && *p != '\t' && i < (int)sizeof(req_pass) - 1) -++- req_pass[i++] = *p++; -++- -++- req_pass[i] = '\0'; -++- } -++- else if (*p == '+') -++- { -++- /* +unix_timestamp */ -++- p++; -++- req_newer = atol(p); -++- -++- while (*p && *p != ' ' && *p != '\t') -++- p++; -++- } -++- else if (*p == 'U' && (p[1] == '\0' || p[1] == ' ' || p[1] == '\t')) -++- { -++- /* U = update request */ -++- req_update = 1; -++- p++; -++- } -++- else if (*p) -++- p++; /* Skip unknown token */ -++- } -++- -++- found_path[0] = '\0'; -++- -++- /* Check alias table first */ -++- alias_path = find_alias(req_name); -++- -++- if (alias_path) -++- { -++- if (is_abs_path(alias_path)) -++- safe_strncpy(found_path, alias_path, MAXPATHLEN); -++- else -++- path_join(found_path, MAXPATHLEN, g_conf.pubdir, alias_path); -++- -++- if (path_exists(found_path)) -++- found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -++- else -++- { -++- snprintf(logbuf, sizeof(logbuf), "NOT FOUND (alias): %s -> %s", req_name, found_path); -++- do_log(g_conf.logfile, logbuf); -++- } -++- -++- continue; -++- } -++- -++- /* Wildcard: scan pubdir and all privdirs whose password matches */ -++- if (is_wildcard(req_name)) -++- { -++- DIR *dp; -++- struct dirent *de; -++- PrivDir *pd; -++- -++- /* Scan pubdir (no password needed) */ -++- dp = opendir(g_conf.pubdir); -++- if (dp) -++- { -++- while ((de = readdir(dp)) != NULL) -++- { -++- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) -++- continue; -++- -++- if (!wildmatch(req_name, de->d_name)) -++- continue; -++- -++- path_join(found_path, MAXPATHLEN, g_conf.pubdir, de->d_name); -++- -++- if (path_exists(found_path)) -++- found_count += serve_one(de->d_name, found_path, "", req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -++- } -++- -++- closedir(dp); -++- } -++- -++- /* Scan matching privdirs */ -++- for (pd = g_conf.privdirs; pd; pd = pd->next) -++- { -++- char rp[64], pp[64]; -++- int ci; -++- -++- safe_strncpy(rp, req_pass, (int)sizeof(rp)); -++- safe_strncpy(pp, pd->password, (int)sizeof(pp)); -++- -++- for (ci = 0; rp[ci]; ci++) -++- rp[ci] = (char)toupper((unsigned char)rp[ci]); -++- -++- for (ci = 0; pp[ci]; ci++) -++- pp[ci] = (char)toupper((unsigned char)pp[ci]); -++- -++- if (strcmp(rp, pp) != 0) -++- continue; -++- -++- dp = opendir(pd->path); -++- -++- if (!dp) -++- continue; -++- -++- while ((de = readdir(dp)) != NULL) -++- { -++- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) -++- continue; -++- -++- if (!wildmatch(req_name, de->d_name)) -++- continue; -++- -++- path_join(found_path, MAXPATHLEN, pd->path, de->d_name); -++- -++- if (path_exists(found_path)) -++- found_count += serve_one(de->d_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -++- } -++- -++- closedir(dp); -++- } -++- -++- continue; -++- } -++- -++- /* Plain filename: try pubdir first, then privdirs if password given */ -++- path_join(found_path, MAXPATHLEN, g_conf.pubdir, req_name); -++- -++- if (path_exists(found_path)) -++- { -++- found_count += -++- serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -++- } -++- else if (req_pass[0]) -++- { -++- /* Try each private dir whose password matches */ -++- PrivDir *pd; -++- int served = 0; -++- -++- for (pd = g_conf.privdirs; pd && !served; pd = pd->next) -++- { -++- char rp[64], pp[64]; -++- int ci; -++- -++- safe_strncpy(rp, req_pass, (int)sizeof(rp)); -++- safe_strncpy(pp, pd->password, (int)sizeof(pp)); -++- -++- for (ci = 0; rp[ci]; ci++) -++- rp[ci] = (char)toupper((unsigned char)rp[ci]); -++- -++- for (ci = 0; pp[ci]; ci++) -++- pp[ci] = (char)toupper((unsigned char)pp[ci]); -++- -++- if (strcmp(rp, pp) != 0) -++- continue; -++- -++- path_join(found_path, MAXPATHLEN, pd->path, req_name); -++- -++- if (path_exists(found_path)) -++- { -++- found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -++- served = 1; -++- } -++- } -++- if (!served) -++- { -++- snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s", req_name); -++- do_log(g_conf.logfile, logbuf); -++- } -++- } -++- else -++- { -++- snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s (pub: %s)", req_name, g_conf.pubdir); -++- do_log(g_conf.logfile, logbuf); -++- } -++- } -++- -++- fclose(req_f); -++- -++- if (rsp_f) -++- fclose(rsp_f); -++- -++- snprintf(logbuf, sizeof(logbuf), "done: %d file(s) found", found_count); -++- do_log(g_conf.logfile, logbuf); -++- -++- /* Save tracking data */ -++- tracking_save(); -++- -++- config_free(); -++- -++- return (found_count > 0) ? 0 : 1; -++-} -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/misc/srifreq.txt binkd_pgul/misc/srifreq.txt -++--- binkd/misc/srifreq.txt 2026-04-26 13:54:14.035795187 +0100 -+++++ binkd_pgul/misc/srifreq.txt 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,67 +0,0 @@ -++-srifreq -- SRIF-compatible file request server -++- -++-USAGE: -++- srifreq --conf -++- -++-DESCRIPTION: -++- SRIF (Standard Request Information Format) compatible file -++- request server for binkd. Processes incoming .req files and -++- serves files based on password protection and aliases. -++- -++-CONFIGURATION FILE FORMAT: -++- # Lines starting with # are comments -++- # Blank lines are ignored -++- -++- pubdir # Public directory (no password required) -++- logfile # Log file, or - to disable -++- aliases # Magic-name aliases file (optional) -++- private # Private directory (requires !password) -++- -++- # Rate limiting options (optional): -++- trackfile # File to track node download statistics -++- maxfiles # Max files per node per time window (0=unlimited) -++- maxsize # Max bytes per node per time window (0=unlimited) -++- timewindow # Time window in seconds (0=no window) -++- -++-ALIASES FILE FORMAT: -++- # Lines starting with # are comments -++- # Format: -++- # Names are matched case-insensitively -++- -++-EXAMPLE CONFIG FILE (srifreq.conf): -++- # srifreq.conf - SRIF Request Server Configuration -++- -++- pubdir Work:Fido/Public -++- logfile Work:Logs/srifreq.log -++- aliases Work:Fido/srifreq.aliases -++- -++- # Private directories (password protected) -++- private Work:Fido/Private/Uploader1 secretpass1 -++- private Work:Fido/Private/Node190 node190pwd -++- -++- # Rate limiting: max 10 files or 50MB per node per 24 hours -++- trackfile Work:Logs/srifreq.track -++- maxfiles 10 -++- maxsize 52428800 -++- timewindow 86400 -++- -++-EXAMPLE ALIASES FILE (srifreq.aliases): -++- # srifreq.aliases - Magic-name to file mappings -++- # Names are case-insensitive -++- -++- DOORWAY Games:Utils/Doorway/doorway.zip -++- NETMAIL Work:Comm/Fido/netmail.lha -++- README Docs:Readme.txt -++- 4DOUT AmiTCP:4DOut.lha -++- BINKD Apps:Comm/Binkd/binkd.lha -++- -++-REQUEST FILE FORMAT (.req): -++- Files listed one per line. Modifiers: -++- !password - Required password for private areas -++- +timestamp - Only serve if file is newer than timestamp -++- U - Update request (only if newer than client's TRANX) -++- -++-EXAMPLES: -++- srifreq --conf srifreq.conf inbound/srif_file.req -++- srifreq --conf srifreq.conf inbound/*.req -++- exec "work:fido/srifreq --conf work:fido/srifreq.conf *S" *.req *.REQ -++\ No hay ningún carácter de nueva línea al final del archivo -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile binkd_pgul/mkfls/amiga/Makefile -++--- binkd/mkfls/amiga/Makefile 2026-04-26 14:55:09.963221741 +0100 -+++++ binkd_pgul/mkfls/amiga/Makefile 2026-04-16 17:49:00.000000000 +0100 -++@@ -26,4 +26,4 @@ -++ $(CC) -c $(CFLAGS) amiga/getfree.c -++ sem.o: -++ $(CC) -c $(CFLAGS) amiga/sem.c -++-include Makefile.dep -++\ No hay ningún carácter de nueva línea al final del archivo -+++include Makefile.dep -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile.analyze.bebbo binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo -++--- binkd/mkfls/amiga/Makefile.analyze.bebbo 2026-04-25 16:52:14.088635220 +0100 -+++++ binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,96 +0,0 @@ -++-# Makefile.analyze.bebbo -- Static analysis for Amiga bebbo (GCC 6.5.0b) -++-# Includes amiga/ code with bebbo-specific defines -++-# Usage: make -f Makefile.analyze.bebbo -++- -++-# All sources including Amiga-specific -++-ALL_SRCS = \ -++- binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ -++- bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ -++- readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ -++- setpttl.c https.c md5b.c crypt.c compress.c \ -++- amiga/rename.c amiga/getfree.c amiga/bsdsock.c amiga/dirent.c \ -++- amiga/utime.c amiga/rfc2553_amiga.c amiga/sem.c amiga/evloop.c \ -++- amiga/sock.c amiga/session.c \ -++- misc/decompress.c misc/freq.c misc/process_tic.c misc/srifreq.c misc/nodelist.c -++- -++-INCLUDES = -I. -Iamiga -++- -++-# bebbo-specific defines (GCC 6.5.0b, HAS native snprintf) -++-BEBBO_DEFINES = \ -++- -DAMIGA \ -++- -DHAVE_SOCKLEN_T \ -++- -DHAVE_INTMAX_T \ -++- -DHAVE_SNPRINTF \ -++- -DHAVE_GETOPT \ -++- -DHAVE_UNISTD_H \ -++- -DHAVE_SYS_TIME_H \ -++- -DHAVE_SYS_PARAM_H \ -++- -DHAVE_SYS_IOCTL_H \ -++- -DHAVE_NETINET_IN_H \ -++- -DHAVE_NETDB_H \ -++- -DHAVE_ARPA_INET_H \ -++- -DHAVE_STDARG_H \ -++- -DHAVE_VSNPRINTF \ -++- -DWITH_ZLIB -++- -++-CPPCHECK_FLAGS = \ -++- --enable=all \ -++- --inconclusive \ -++- --std=c89 \ -++- --quiet \ -++- --suppress=missingIncludeSystem \ -++- --suppress=unusedFunction \ -++- --suppress=checkersReport \ -++- $(INCLUDES) -++- -++-# Log files -++-LOG_DIR = analysis_logs -++-CPPCHECK_LOG = $(LOG_DIR)/cppcheck_bebbo.log -++-CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_bebbo.log -++- -++-.PHONY: all cppcheck clang-tidy analyze clean -++- -++-all: analyze -++- -++-cppcheck: -++- @mkdir -p $(LOG_DIR) -++- @echo "=== cppcheck Amiga bebbo (GCC 6.5.0b) ===" -++- @echo "Defines: bebbo, HAS native snprintf, no snprintf.c needed" -++- @echo "Saving output to: $(CPPCHECK_LOG)" -++- @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) $(ALL_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true -++- @echo "=== done ===" -++- @echo "Log saved: $(CPPCHECK_LOG)" -++- -++-clang-tidy: -++- @mkdir -p $(LOG_DIR) -++- @echo "=== clang-tidy Amiga bebbo ===" -++- @echo "Note: Some Amiga headers may not resolve on Linux host" -++- @echo "Saving output to: $(CLANG_TIDY_LOG)" -++- @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) -++- @for src in binkd.c readcfg.c amiga/evloop.c amiga/session.c; do \ -++- echo "" >> $(CLANG_TIDY_LOG); \ -++- echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ -++- clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ -++- $(INCLUDES) $(BEBBO_DEFINES) -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ -++- done -++- @echo "=== done ===" -++- @echo "Log saved: $(CLANG_TIDY_LOG)" -++- -++-analyze: cppcheck clang-tidy -++- -++-# Focus on Amiga-specific code only -++-amiga-only: -++- @echo "=== cppcheck Amiga-specific code only (bebbo) ===" -++- @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) \ -++- amiga/*.c 2>&1 || true -++- -++-# Check what differs between ADE and bebbo -++-diff-defines: -++- @echo "=== ADE vs bebbo define differences ===" -++- @echo "ADE only: -DHAVE_VSNPRINTF (no -DHAVE_SNPRINTF)" -++- @echo "bebbo: -DHAVE_SNPRINTF -DHAVE_VSNPRINTF" -++- @echo "" -++- @echo "ADE needs snprintf.c, bebbo does not" -++- -++-clean: -++- @true -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/amiga/Makefile.bebbo binkd_pgul/mkfls/amiga/Makefile.bebbo -++--- binkd/mkfls/amiga/Makefile.bebbo 2026-04-26 13:11:27.902433254 +0100 -+++++ binkd_pgul/mkfls/amiga/Makefile.bebbo 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,208 +0,0 @@ -++-CC = m68k-amigaos-gcc -++- -++-# Common flags for compiler and linker -++-COMMON_CFLAGS = -Wall -Wextra -Wunused -Wunused-function -Wunused-variable -Wmissing-prototypes -Wno-pointer-sign -ffunction-sections -fdata-sections -noixemul -++-ARCH_FLAGS = -O -m68000 -msoft-float -fomit-frame-pointer -++- -++-CFLAGS = $(DEFINES) $(COMMON_CFLAGS) $(ARCH_FLAGS) -++-LIBS = -lz -lm -lamiga -++-LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_gcc.map -s -++- -++-# Tools use same flags as main binary -++-TOOL_CFLAGS = $(DEFINES) $(COMMON_CFLAGS) -O -m68000 -msoft-float -fomit-frame-pointer -I misc -++-TOOL_LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_tools.map -s -++- -++-OBJDIR = objs -++- -++-DEFINES = \ -++- -DAMIGA \ -++- -DHAVE_SOCKLEN_T \ -++- -DHAVE_INTMAX_T \ -++- -DHAVE_SNPRINTF \ -++- -DHAVE_GETOPT \ -++- -DHAVE_UNISTD_H \ -++- -DHAVE_SYS_TIME_H \ -++- -DHAVE_SYS_PARAM_H \ -++- -DHAVE_SYS_IOCTL_H \ -++- -DHAVE_NETINET_IN_H \ -++- -DHAVE_NETDB_H \ -++- -DHAVE_ARPA_INET_H \ -++- -DHTTPS \ -++- -DAMIGADOS_4D_OUTBOUND \ -++- -DHAVE_STDARG_H \ -++- -DHAVE_VSNPRINTF \ -++- -DWITH_ZLIB \ -++- -DOS=\"Amiga\" \ -++- -I. \ -++- -Iamiga -++- -++-SRCS = \ -++- binkd.c \ -++- readcfg.c \ -++- tools.c \ -++- ftnaddr.c \ -++- ftnq.c \ -++- client.c \ -++- server.c \ -++- protocol.c \ -++- bsy.c \ -++- inbound.c \ -++- breaksig.c \ -++- branch.c \ -++- amiga/rename.c \ -++- amiga/getfree.c \ -++- amiga/bsdsock.c \ -++- amiga/dirent.c \ -++- amiga/utime.c \ -++- amiga/rfc2553_amiga.c \ -++- amiga/sem.c \ -++- amiga/evloop.c \ -++- amiga/sock.c \ -++- amiga/session.c \ -++- bsycleanup.c \ -++- amiga/proto_amiga.c \ -++- ftndom.c \ -++- ftnnode.c \ -++- srif.c \ -++- pmatch.c \ -++- readflo.c \ -++- prothlp.c \ -++- iptools.c \ -++- run.c \ -++- binlog.c \ -++- exitproc.c \ -++- getw.c \ -++- xalloc.c \ -++- setpttl.c \ -++- https.c \ -++- md5b.c \ -++- crypt.c \ -++- compress.c -++- -++-OBJS = \ -++- $(OBJDIR)/binkd.o \ -++- $(OBJDIR)/readcfg.o \ -++- $(OBJDIR)/tools.o \ -++- $(OBJDIR)/ftnaddr.o \ -++- $(OBJDIR)/ftnq.o \ -++- $(OBJDIR)/client.o \ -++- $(OBJDIR)/server.o \ -++- $(OBJDIR)/protocol.o \ -++- $(OBJDIR)/bsy.o \ -++- $(OBJDIR)/inbound.o \ -++- $(OBJDIR)/breaksig.o \ -++- $(OBJDIR)/branch.o \ -++- $(OBJDIR)/rename.o \ -++- $(OBJDIR)/getfree.o \ -++- $(OBJDIR)/bsdsock.o \ -++- $(OBJDIR)/dirent.o \ -++- $(OBJDIR)/utime.o \ -++- $(OBJDIR)/rfc2553_amiga.o \ -++- $(OBJDIR)/sem.o \ -++- $(OBJDIR)/evloop.o \ -++- $(OBJDIR)/sock.o \ -++- $(OBJDIR)/session.o \ -++- $(OBJDIR)/bsycleanup.o \ -++- $(OBJDIR)/proto_amiga.o \ -++- $(OBJDIR)/ftndom.o \ -++- $(OBJDIR)/ftnnode.o \ -++- $(OBJDIR)/srif.o \ -++- $(OBJDIR)/pmatch.o \ -++- $(OBJDIR)/readflo.o \ -++- $(OBJDIR)/prothlp.o \ -++- $(OBJDIR)/iptools.o \ -++- $(OBJDIR)/run.o \ -++- $(OBJDIR)/binlog.o \ -++- $(OBJDIR)/exitproc.o \ -++- $(OBJDIR)/getw.o \ -++- $(OBJDIR)/xalloc.o \ -++- $(OBJDIR)/setpttl.o \ -++- $(OBJDIR)/https.o \ -++- $(OBJDIR)/md5b.o \ -++- $(OBJDIR)/crypt.o \ -++- $(OBJDIR)/compress.o -++- -++-all: binkd decompress process_tic freq srifreq nodelist -++- -++-$(OBJDIR): -++- mkdir -p $(OBJDIR) -++- -++-$(OBJDIR)/%.o: %.c -++- $(CC) -c $(CFLAGS) $< -o $@ -++- -++-binkd: $(OBJDIR) $(OBJS) -++- $(CC) $(CFLAGS) -o binkd $(OBJS) $(LIBS) $(LDFLAGS) -++- -++-# ---------- Utility tools (stand-alone, multi-platform) ---------- -++-# TOOL_CFLAGS and TOOL_LDFLAGS defined above -++- -++-decompress: -++- $(CC) $(TOOL_CFLAGS) -o decompress misc/decompress.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) -++- -++-process_tic: -++- $(CC) $(TOOL_CFLAGS) -o process_tic misc/process_tic.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) -++- -++-freq: -++- $(CC) $(TOOL_CFLAGS) -o freq misc/freq.c misc/portable.c $(TOOL_LDFLAGS) -++- -++-srifreq: -++- $(CC) $(TOOL_CFLAGS) -o srifreq misc/srifreq.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) -++- -++-nodelist: -++- $(CC) $(TOOL_CFLAGS) -o nodelist misc/nodelist.c misc/portable.c $(TOOL_LDFLAGS) -++- -++-install: all clean -++- -++-clean: -++- rm -f *.[bo] *.BAK *.core *.obj *.err *~ core -++- rm -rf $(OBJDIR) -++- rm -f binkd decompress process_tic freq srifreq nodelist -++- -++-# ---------- Explicit rules for amiga/ objects ---------- -++-$(OBJDIR)/rename.o: amiga/rename.c -++- $(CC) -c $(CFLAGS) amiga/rename.c -o $(OBJDIR)/rename.o -++- -++-$(OBJDIR)/getfree.o: amiga/getfree.c -++- $(CC) -c $(CFLAGS) amiga/getfree.c -o $(OBJDIR)/getfree.o -++- -++-$(OBJDIR)/sem.o: amiga/sem.c -++- $(CC) -c $(CFLAGS) amiga/sem.c -o $(OBJDIR)/sem.o -++- -++-$(OBJDIR)/bsdsock.o: amiga/bsdsock.c -++- $(CC) -c $(CFLAGS) amiga/bsdsock.c -o $(OBJDIR)/bsdsock.o -++- -++-$(OBJDIR)/dirent.o: amiga/dirent.c -++- $(CC) -c $(CFLAGS) amiga/dirent.c -o $(OBJDIR)/dirent.o -++- -++-$(OBJDIR)/utime.o: amiga/utime.c -++- $(CC) -c $(CFLAGS) amiga/utime.c -o $(OBJDIR)/utime.o -++- -++-$(OBJDIR)/rfc2553_amiga.o: amiga/rfc2553_amiga.c -++- $(CC) -c $(CFLAGS) amiga/rfc2553_amiga.c -o $(OBJDIR)/rfc2553_amiga.o -++- -++-$(OBJDIR)/evloop.o: amiga/evloop.c -++- $(CC) -c $(CFLAGS) amiga/evloop.c -o $(OBJDIR)/evloop.o -++- -++-$(OBJDIR)/sock.o: amiga/sock.c -++- $(CC) -c $(CFLAGS) amiga/sock.c -o $(OBJDIR)/sock.o -++- -++-$(OBJDIR)/session.o: amiga/session.c -++- $(CC) -c $(CFLAGS) amiga/session.c -o $(OBJDIR)/session.o -++- -++-$(OBJDIR)/bsycleanup.o: bsycleanup.c -++- $(CC) -c $(CFLAGS) bsycleanup.c -o $(OBJDIR)/bsycleanup.o -++- -++-$(OBJDIR)/proto_amiga.o: amiga/proto_amiga.c -++- $(CC) -c $(CFLAGS) amiga/proto_amiga.c -o $(OBJDIR)/proto_amiga.o -++- -++-depend Makefile.dep: Makefile -++- $(CC) -MM $(CFLAGS) $(SRCS) $(SYS) | \ -++- awk '{ if ($$1 != prev) { if (rec != "") print rec; \ -++- rec = $$0; prev = $$1; } \ -++- else { if (length(rec $$2) > 78) { print rec; rec = $$0; } \ -++- else rec = rec " " $$2 } } \ -++- END { print rec }' | tee Makefile.dep -++- -++--include Makefile.dep -++- -++-.PHONY: all binkd decompress process_tic freq srifreq nodelist clean install -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/nt95-mingw/Makefile binkd_pgul/mkfls/nt95-mingw/Makefile -++--- binkd/mkfls/nt95-mingw/Makefile 2026-04-26 13:12:24.499178632 +0100 -+++++ binkd_pgul/mkfls/nt95-mingw/Makefile 2026-04-16 17:49:00.000000000 +0100 -++@@ -38,8 +38,7 @@ -++ setpttl.c https.c md5b.c crypt.c getopt.c nt/breaksig.c nt/getfree.c \ -++ nt/sem.c nt/TCPErr.c nt/WSock.c nt/w32tools.c nt/tray.c snprintf.c \ -++ ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c ntlm/des_enc.c \ -++- ntlm/helpers.c \ -++- bsycleanup.c -+++ ntlm/helpers.c -++ -++ RES= nt/binkdres.rc -++ -++@@ -222,32 +221,7 @@ -++ OBJS=$(addprefix $(OBJDIR)/,$(patsubst %.c,%.o, $(SRCS))) -++ RESOBJS=$(addprefix $(OBJDIR)/, $(patsubst %.rc,%.o, $(RES))) -++ -++-# ---------- Utility tools (stand-alone) ---------- -++-TOOL_CFLAGS = -O2 -Wall -DWIN32 -I. -I misc -++- -++-utils: decompress process_tic freq srifreq nodelist -++- -++-decompress: -++- @echo Compiling decompress... -++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/decompress.exe misc/decompress.c misc/portable.c -++- -++-process_tic: -++- @echo Compiling process_tic... -++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/process_tic.exe misc/process_tic.c misc/portable.c -++- -++-freq: -++- @echo Compiling freq... -++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/freq.exe misc/freq.c misc/portable.c -++- -++-srifreq: -++- @echo Compiling srifreq... -++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/srifreq.exe misc/srifreq.c misc/portable.c -++- -++-nodelist: -++- @echo Compiling nodelist... -++- @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/nodelist.exe misc/nodelist.c misc/portable.c -++- -++-.PHONY: all printinfo install html clean distclean makedirs utils decompress process_tic freq srifreq nodelist -+++.PHONY: all printinfo install html clean distclean makedirs -++ -++ all: printinfo makedirs $(OUTDIR)/$(BINKDEXE) -++ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/nt95-msvc/Makefile binkd_pgul/mkfls/nt95-msvc/Makefile -++--- binkd/mkfls/nt95-msvc/Makefile 2026-04-26 12:41:43.750120247 +0100 -+++++ binkd_pgul/mkfls/nt95-msvc/Makefile 2026-04-16 17:49:00.000000000 +0100 -++@@ -327,32 +327,7 @@ -++ -++ BINKDEXE = $(BINKDNAME).exe -++ -++-all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) utils -++- -++-TOOL_CFLAGS = -nologo -W3 -O2 -DWIN32 -DVISUALCPP -I. -I misc -++-TOOL_CC = $(CC) $(TOOL_CFLAGS) -++- -++-utils: "$(OUTDIR)\decompress.exe" "$(OUTDIR)\process_tic.exe" "$(OUTDIR)\freq.exe" "$(OUTDIR)\srifreq.exe" "$(OUTDIR)\nodelist.exe" -++- -++-"$(OUTDIR)\decompress.exe": misc\decompress.c misc\portable.c nt\dirwin32.c -++- @echo Compiling decompress... -++- @$(TOOL_CC) -Fe"$(OUTDIR)\decompress.exe" misc\decompress.c misc\portable.c nt\dirwin32.c -++- -++-"$(OUTDIR)\process_tic.exe": misc\process_tic.c misc\portable.c nt\dirwin32.c -++- @echo Compiling process_tic... -++- @$(TOOL_CC) -Fe"$(OUTDIR)\process_tic.exe" misc\process_tic.c misc\portable.c nt\dirwin32.c -++- -++-"$(OUTDIR)\freq.exe": misc\freq.c misc\portable.c -++- @echo Compiling freq... -++- @$(TOOL_CC) -Fe"$(OUTDIR)\freq.exe" misc\freq.c misc\portable.c -++- -++-"$(OUTDIR)\srifreq.exe": misc\srifreq.c misc\portable.c nt\dirwin32.c -++- @echo Compiling srifreq... -++- @$(TOOL_CC) -Fe"$(OUTDIR)\srifreq.exe" misc\srifreq.c misc\portable.c nt\dirwin32.c -++- -++-"$(OUTDIR)\nodelist.exe": misc\nodelist.c misc\portable.c -++- @echo Compiling nodelist... -++- @$(TOOL_CC) -Fe"$(OUTDIR)\nodelist.exe" misc\nodelist.c misc\portable.c -+++all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) -++ -++ printinfo: -++ @echo on -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/os2-emx/Makefile binkd_pgul/mkfls/os2-emx/Makefile -++--- binkd/mkfls/os2-emx/Makefile 2026-04-26 13:12:44.342586479 +0100 -+++++ binkd_pgul/mkfls/os2-emx/Makefile 2026-04-16 17:49:00.000000000 +0100 -++@@ -13,7 +13,7 @@ -++ LFLAGS=-Los2 -++ LIBS=-lsocket -lresolv -++ NTLM_SRC=ntlm/des_enc.c ntlm/helpers.c ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c -++-SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c bsycleanup.c ${NTLM_SRC} -+++SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c ${NTLM_SRC} -++ TARGET=binkd2emx -++ -++ #PERLDIR=../perl5.00553/os2 -++@@ -122,33 +122,7 @@ -++ -++ TARGET:=$(TARGET).exe -++ -++-all: $(TARGET) utils -++- -++-TOOL_CFLAGS = $(CFLAGS) -I. -I misc -++- -++-utils: decompress process_tic freq srifreq nodelist -++- -++-decompress: misc/decompress.c -++- @echo Compiling decompress... -++- @$(CC) $(TOOL_CFLAGS) -o decompress.exe misc/decompress.c misc/portable.c os2/dirent.c -++- -++-process_tic: misc/process_tic.c -++- @echo Compiling process_tic... -++- @$(CC) $(TOOL_CFLAGS) -o process_tic.exe misc/process_tic.c misc/portable.c os2/dirent.c -++- -++-freq: misc/freq.c -++- @echo Compiling freq... -++- @$(CC) $(TOOL_CFLAGS) -o freq.exe misc/freq.c misc/portable.c -++- -++-srifreq: misc/srifreq.c -++- @echo Compiling srifreq... -++- @$(CC) $(TOOL_CFLAGS) -o srifreq.exe misc/srifreq.c misc/portable.c os2/dirent.c -++- -++-nodelist: misc/nodelist.c -++- @echo Compiling nodelist... -++- @$(CC) $(TOOL_CFLAGS) -o nodelist.exe misc/nodelist.c misc/portable.c -++- -++-.PHONY: utils decompress process_tic freq srifreq nodelist -+++all: $(TARGET) -++ -++ .c.o: -++ @echo Compiling $*.c... -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/unix/Makefile.analyze.unix binkd_pgul/mkfls/unix/Makefile.analyze.unix -++--- binkd/mkfls/unix/Makefile.analyze.unix 2026-04-25 16:52:02.225860998 +0100 -+++++ binkd_pgul/mkfls/unix/Makefile.analyze.unix 1970-01-01 00:00:00.000000000 +0000 -++@@ -1,65 +0,0 @@ -++-# Makefile.analyze.unix -- Static analysis for Unix/Linux code only -++-# Excludes Amiga-specific code (amiga/ directory) -++-# Usage: make -f Makefile.analyze.unix -++- -++-# Unix-portable sources only (no amiga/ directory) -++-UNIX_SRCS = \ -++- binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ -++- bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ -++- readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ -++- setpttl.c https.c md5b.c crypt.c compress.c -++- -++-# Unix-specific files (if any) -++-UNIX_SYS_SRCS = \ -++- unix/getwd.c unix/lock.c unix/tcperr.c -++- -++-INCLUDES = -I. -Iunix -++- -++-CPPCHECK_FLAGS = \ -++- --enable=all \ -++- --inconclusive \ -++- --std=c89 \ -++- --quiet \ -++- --suppress=missingIncludeSystem \ -++- --suppress=unusedFunction \ -++- $(INCLUDES) -++- -++-# Log files -++-LOG_DIR = analysis_logs -++-CPPCHECK_LOG = $(LOG_DIR)/cppcheck_unix.log -++-CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_unix.log -++- -++-.PHONY: all cppcheck clang-tidy analyze clean -++- -++-all: analyze -++- -++-cppcheck: -++- @mkdir -p $(LOG_DIR) -++- @echo "=== cppcheck Unix/Linux code ===" -++- @echo "Saving output to: $(CPPCHECK_LOG)" -++- @cppcheck $(CPPCHECK_FLAGS) $(UNIX_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true -++- @echo "=== done ===" -++- @echo "Log saved: $(CPPCHECK_LOG)" -++- -++-clang-tidy: -++- @mkdir -p $(LOG_DIR) -++- @echo "=== clang-tidy Unix/Linux code ===" -++- @echo "Saving output to: $(CLANG_TIDY_LOG)" -++- @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) -++- @for src in $(UNIX_SRCS); do \ -++- echo "" >> $(CLANG_TIDY_LOG); \ -++- echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ -++- clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ -++- $(INCLUDES) -DUNIX -DHAVE_SNPRINTF -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ -++- done -++- @echo "=== done ===" -++- @echo "Log saved: $(CLANG_TIDY_LOG)" -++- -++-analyze: cppcheck clang-tidy -++- -++-quick: -++- @echo "=== Quick error check (Unix) ===" -++- @cppcheck --enable=error --std=c89 --quiet $(INCLUDES) $(UNIX_SRCS) 2>&1 || true -++- -++-clean: -++- @true -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/mkfls/unix/Makefile.in binkd_pgul/mkfls/unix/Makefile.in -++--- binkd/mkfls/unix/Makefile.in 2026-04-26 13:11:58.084940863 +0100 -+++++ binkd_pgul/mkfls/unix/Makefile.in 2026-04-16 17:49:00.000000000 +0100 -++@@ -12,17 +12,17 @@ -++ MANDIR=$(DATADIR)/man -++ DOCDIR=$(DATADIR)/doc/$(APPL) -++ -++-SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c bsycleanup.c @OPT_SRC@ -+++SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c @OPT_SRC@ -++ OBJS=${SRCS:.c=.o} -++ AUTODEFS=@DEFS@ -++ AUTOLIBS=@LIBS@ -++-DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -DPROTOTYPES -+++DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -++ CPPFLAGS=@CPPFLAGS@ -++ CFLAGS=@CFLAGS@ -++ LDFLAGS=@LDFLAGS@ -++ LIBS=$(AUTOLIBS) -++ -++-all: compile banner utils -+++all: compile banner -++ -++ compile: $(APPL) -++ -++@@ -48,32 +48,6 @@ -++ @echo " run \`configure --prefix=/another/path' and go to step 1. " -++ @echo -++ -++-utils: decompress process_tic freq srifreq nodelist -++- -++-TOOL_CFLAGS = $(DEFINES) $(CPPFLAGS) $(CFLAGS) -I. -I misc -++- -++-decompress: misc/decompress.c misc/portable.c misc/portable.h -++- @echo Compiling decompress... -++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/decompress.c misc/portable.c -++- -++-process_tic: misc/process_tic.c misc/portable.c misc/portable.h -++- @echo Compiling process_tic... -++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/process_tic.c misc/portable.c -++- -++-freq: misc/freq.c misc/portable.c misc/portable.h -++- @echo Compiling freq... -++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/freq.c misc/portable.c -++- -++-srifreq: misc/srifreq.c misc/portable.c misc/portable.h -++- @echo Compiling srifreq... -++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/srifreq.c misc/portable.c -++- -++-nodelist: misc/nodelist.c misc/portable.c -++- @echo Compiling nodelist... -++- @$(CC) $(TOOL_CFLAGS) -o $@ misc/nodelist.c misc/portable.c -++- -++-.PHONY: decompress process_tic freq srifreq nodelist -++- -++ .version: $(APPL) -++ @./$(APPL) -v | $(AWK) '{ print $$2; }' > $@ -++ -++@@ -92,7 +66,6 @@ -++ clean: -++ rm -f *.[bo] unix/*.[bo] ntlm/*.[bo] *.BAK *.core *.obj *.err -++ rm -f *~ core config.cache config.log config.status -++- rm -f decompress process_tic freq srifreq nodelist -++ -++ cleanall: clean -++ rm -f $(APPL) Makefile Makefile.dep Makefile.in -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/protocol.c binkd_pgul/protocol.c -++--- binkd/protocol.c 2026-04-26 09:45:52.230049023 +0100 -+++++ binkd_pgul/protocol.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -45,19 +45,12 @@ -++ #include "md5b.h" -++ #include "crypt.h" -++ #include "compress.h" -++-#ifdef AMIGA -++-#include "amiga/proto_amiga.h" -++-#endif -++ -++ #ifdef WITH_PERL -++ #include "perlhooks.h" -++ #endif -++ #include "rfc2553.h" -++ -++-#if defined(HAVE_THREADS) || defined(AMIGA) -++-extern MUTEXSEM lsem; -++-#endif -++- -++ /* define to enable val's code for -ip checks (default is gul's code) */ -++ #undef VAL_STYLE -++ #ifdef VAL_STYLE -++@@ -70,7 +63,7 @@ -++ /* -++ * Fills <> with initial values, allocates buffers, etc. -++ */ -++-int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) -+++static int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) -++ { -++ char val[4]; -++ socklen_t lval; -++@@ -133,12 +126,6 @@ -++ #endif -++ setsockopts (state->s_in = socket_in); -++ setsockopts (state->s_out = socket_out); -++- -++-#if defined(AMIGA) -++- setsockopts_amiga(socket_in, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); -++- setsockopts_amiga(socket_out, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); -++-#endif -++- -++ TF_ZERO (&state->in); -++ TF_ZERO (&state->out); -++ TF_ZERO (&state->flo); -++@@ -194,7 +181,7 @@ -++ /* -++ * Clears protocol buffers and queues, closes files, etc. -++ */ -++-int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) -+++static int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) -++ { -++ int i; -++ -++@@ -238,7 +225,7 @@ -++ } -++ -++ /* Process rcvdlist */ -++-FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) -+++static FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) -++ { -++ int i; -++ -++@@ -328,7 +315,7 @@ -++ /* -++ * Sends next msg from the msg queue or next data block -++ */ -++-int send_block (STATE *state, BINKD_CONFIG *config) -+++static int send_block (STATE *state, BINKD_CONFIG *config) -++ { -++ int i, n, save_errno; -++ const char *save_err; -++@@ -2104,7 +2091,7 @@ -++ return 0; -++ } -++ -++-int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) -+++static int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) -++ { -++ char buf[MAXPATHLEN+1]; -++ FILE *f; -++@@ -2158,8 +2145,7 @@ -++ -++ *extra = ""; -++ if (state->z_cansend && state->extcmd && state->out.size >= config->zminsize -++- && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first) -++- && !(state->to && state->to->NC_flag)) { -+++ && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first)) { -++ #ifdef WITH_BZLIB2 -++ if (!state->z_send && (state->z_cansend & 2)) { -++ *extra = " BZ2"; state->z_send = 2; -++@@ -2462,7 +2448,6 @@ -++ { -++ char szAddr[FTN_ADDR_SZ + 1]; -++ -++- memset(szAddr, 0, sizeof(szAddr)); -++ ftnaddress_to_str (szAddr, &state->sent_fls[n].fa); -++ state->bytes_sent += state->sent_fls[n].size; -++ ++state->files_sent; -++@@ -2553,7 +2538,7 @@ -++ }; -++ -++ /* Recvs next block, processes msgs or writes down the data from the remote */ -++-int recv_block (STATE *state, BINKD_CONFIG *config) -+++static int recv_block (STATE *state, BINKD_CONFIG *config) -++ { -++ int no; -++ -++@@ -2785,7 +2770,7 @@ -++ return 1; -++ } -++ -++-int banner (STATE *state, BINKD_CONFIG *config) -+++static int banner (STATE *state, BINKD_CONFIG *config) -++ { -++ int tz; -++ char szLocalTime[60]; -++@@ -2865,7 +2850,7 @@ -++ return 1; -++ } -++ -++-int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) -+++static int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) -++ { -++ struct stat sb; -++ FILE *f = NULL; -++@@ -3046,7 +3031,7 @@ -++ return 1; -++ } -++ -++-void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) -+++static void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) -++ { -++ char szFTNAddr[FTN_ADDR_SZ + 1]; -++ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readcfg.c binkd_pgul/readcfg.c -++--- binkd/readcfg.c 2026-04-26 15:04:45.397917107 +0100 -+++++ binkd_pgul/readcfg.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -44,10 +44,6 @@ -++ */ -++ BINKD_CONFIG *current_config; -++ -++-#ifdef AMIGA -++-extern struct SignalSemaphore config_sem; -++-#endif -++- -++ /* -++ * Temporary static structure for configuration reading -++ */ -++@@ -214,15 +210,9 @@ -++ snprintf(c->iport, sizeof(c->iport), "%s", find_port("")); -++ snprintf(c->oport, sizeof(c->oport), "%s", find_port("")); -++ c->call_delay = 60; -++- c->no_call_delay = 0; -++ c->rescan_delay = 60; -++ c->nettimeout = DEF_TIMEOUT; -++ c->oblksize = DEF_BLKSIZE; -++- -++-#ifdef AMIGA -++- c->tcp_nodelay = 0; -++-#endif -++- -++ #if defined(WITH_ZLIB) || defined(WITH_BZLIB2) -++ c->zminsize = 1024; -++ c->zlevel = 0; -++@@ -401,16 +391,8 @@ -++ {"oport", read_port, &work_config.oport, 0, 0}, -++ {"rescan-delay", read_time, &work_config.rescan_delay, 1, DONT_CHECK}, -++ {"call-delay", read_time, &work_config.call_delay, 1, DONT_CHECK}, -++- {"no-call-delay", read_bool, &work_config.no_call_delay, 0, 0}, -++ {"timeout", read_time, &work_config.nettimeout, 1, DONT_CHECK}, -++ {"oblksize", read_int, &work_config.oblksize, MIN_BLKSIZE, MAX_BLKSIZE}, -++- -++-#ifdef AMIGA -++- {"tcp-nodelay", read_bool, &work_config.tcp_nodelay, 0, 0}, -++- {"so-sndbuf", read_int, &work_config.so_sndbuf, 0, 65535}, -++- {"so-rcvbuf", read_int, &work_config.so_rcvbuf, 0, 65535}, -++-#endif -++- -++ {"maxservers", read_int, &work_config.max_servers, 0, DONT_CHECK}, -++ {"maxclients", read_int, &work_config.max_clients, 0, DONT_CHECK}, -++ {"inbound", read_string, work_config.inbound, 'd', 0}, -++@@ -684,7 +666,7 @@ -++ exp_ftnaddress (&fa, work_config.pAddr, work_config.nAddr, work_config.pDomains.first); -++ pn = add_node (&fa, NULL, password, pkt_pwd, out_pwd, '-', NULL, NULL, -++ NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, -++- HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, -+++ HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, -++ #ifdef BW_LIM -++ BW_DEF, BW_DEF, -++ #endif -++@@ -848,10 +830,11 @@ -++ if (!new_config) -++ { -++ /* Config error. Abort or continue? */ -++- unlock_config_structure(&work_config, 0); -++- -++- if (current_config) -++- Log(1, "error in configuration, using old config"); -+++ if (current_config) -+++ { -+++ Log(1, "error in configuration, using old config"); -+++ unlock_config_structure(&work_config, 0); -+++ } -++ } -++ -++ return new_config; -++@@ -874,16 +857,6 @@ -++ Log (2, "got SIGHUP"); -++ need_reload = got_sighup; -++ got_sighup = 0; -++-#elif defined(AMIGA) -++- /* No SIGHUP on AmigaOS: detect config change by mtime */ -++- need_reload = 0; -++- if (current_config->config_list.first) -++- { -++- if (stat(current_config->config_list.first->path, &sb) == 0 && -++- current_config->config_list.first->mtime != 0 && -++- sb.st_mtime != current_config->config_list.first->mtime) -++- need_reload = 1; -++- } -++ #else -++ need_reload = 0; -++ #endif -++@@ -913,75 +886,8 @@ -++ } -++ #endif -++ -++- if (!need_reload) -++- return 0; -++- -++-#ifdef AMIGA -++- /* Prevent reload storms and partial-file reads. -++- * -++- * On AmigaOS (and some Unix editors), config files are written in multiple -++- * passes, so binkd may see the mtime change while the file is still being -++- * written. Attempting to parse an incomplete file gives "unknown keyword" -++- * errors, and rapid repeated mtime changes cause bind() to fail because the -++- * previous listen socket has not been released yet. -++- * -++- * Strategy: after first detecting a change, wait until the mtime has been -++- * stable for at least 2 seconds before actually reloading. Also enforce a -++- * minimum of 5 seconds between successive successful reloads. -++- */ -++- { -++- static time_t last_reload = 0; /* time of last successful reload */ -++- static time_t change_seen = 0; /* time we first noticed the change */ -++- static time_t stable_mtime = 0; /* mtime we are waiting to stabilize */ -++- static int reload_pending = 0; /* persists between calls */ -++- time_t now = time(NULL); -++- -++- /* The loop has already updated pc->mtime, so in the next call -++- * need_reload will be 0 even though we haven't reloaded yet. -++- * reload_pending keeps the reload intent alive. -++- */ -++- if (need_reload) reload_pending = 1; -++- -++- if (!reload_pending) -++- return 0; -++- -++- /* Get the mtime of the primary config file */ -++- { struct stat sb2; -++- time_t cur_mtime = 0; -++- -++- if (current_config->config_list.first && stat(current_config->config_list.first->path, &sb2) == 0) -++- cur_mtime = sb2.st_mtime; -++- -++- /* mtime just changed (or changed again) — reset the stability clock */ -++- if (cur_mtime != stable_mtime) -++- { -++- stable_mtime = cur_mtime; -++- change_seen = now; -++- Log(5, "checkcfg: config mtime changed, waiting for stability..."); -++- return 0; -++- } -++- -++- /* mtime has been stable since change_seen */ -++- if (now - change_seen < 2) -++- { -++- Log(5, "checkcfg: config not yet stable (%lds), waiting...", -++- (long)(now - change_seen)); -++- return 0; -++- } -++- } -++- -++- /* Enforce minimum gap between reloads to let the OS release sockets */ -++- if (now - last_reload < 5) -++- { -++- Log(5, "checkcfg: reload suppressed (too soon, %lds)", (long)(now - last_reload)); -++- return 0; -++- } -++- -++- last_reload = now; -++- reload_pending = 0; /* Once the intent has been consumed, the reload is executed */ -++- } -++-#endif -++- -+++ if (!need_reload) -+++ return 0; -++ /* Reload starting from first file in list */ -++ Log(2, "Reloading configuration..."); -++ pc = current_config->config_list.first; -++@@ -1219,7 +1125,7 @@ -++ char *w[ARGNUM], *tmp, *pkt_pwd, *out_pwd, *pipe; -++ int i, j; -++ int NR_flag = NR_USE_OLD, ND_flag = ND_USE_OLD, HC_flag = HC_USE_OLD, -++- MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, NC_flag = NC_USE_OLD, restrictIP = RIP_USE_OLD, -+++ MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, restrictIP = RIP_USE_OLD, -++ IP_afamily = AF_USE_OLD; -++ #ifdef BW_LIM -++ long bw_send = BW_DEF, bw_recv = BW_DEF; -++@@ -1269,8 +1175,6 @@ -++ HC_flag = HC_OFF; -++ else if (STRICMP (tmp, "-noproxy") == 0) -++ NP_flag = NP_ON; -++- else if (STRICMP (tmp, "-nc") == 0) -++- NC_flag = NC_ON; -++ #ifdef BW_LIM -++ else if (STRICMP (tmp, "-bw") == 0) -++ { -++@@ -1354,7 +1258,7 @@ -++ -++ split_passwords(w[2], &pkt_pwd, &out_pwd); -++ pn = add_node (&fa, w[1], w[2], pkt_pwd, out_pwd, (char)(w[3] ? w[3][0] : '-'), w[4], w[5], -++- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, -+++ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, -++ IP_afamily, -++ #ifdef BW_LIM -++ bw_send, bw_recv, -++@@ -2087,9 +1991,6 @@ -++ if (fn->pkt_pwd) pwd_len += strlen(fn->pkt_pwd)+1; else pwd_len += 2; -++ if (fn->out_pwd) pwd_len += strlen(fn->out_pwd)+1; else pwd_len += 2; -++ pwd = calloc (1, pwd_len+1); -++- /* Guard against null pointer dereference if calloc fails */ -++- if (!pwd) -++- return 0; -++ strcpy(pwd, fn->pwd); -++ if (fn->pkt_pwd != (char*)&(fn->pwd) || fn->out_pwd != (char*)&(fn->pwd)) { -++ strcat(strcat(pwd, ","), (fn->pkt_pwd) ? fn->pkt_pwd : "-"); -++@@ -2423,3 +2324,4 @@ -++ return 1; -++ } -++ #endif -+++ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readcfg.h binkd_pgul/readcfg.h -++--- binkd/readcfg.h 2026-04-25 20:39:08.569961699 +0100 -+++++ binkd_pgul/readcfg.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -111,16 +111,8 @@ -++ #endif -++ int nettimeout; -++ int connect_timeout; -++- -++-#ifdef AMIGA -++- int tcp_nodelay; -++- int so_sndbuf; -++- int so_rcvbuf; -++-#endif -++- -++- int call_delay; -++- int no_call_delay; -++ int rescan_delay; -+++ int call_delay; -++ int max_servers; -++ int max_clients; -++ int kill_dup_partial_files; -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/readdir.h binkd_pgul/readdir.h -++--- binkd/readdir.h 2026-04-22 20:37:22.121954324 +0100 -+++++ binkd_pgul/readdir.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -23,8 +23,6 @@ -++ #elif defined(__MINGW32__) -++ #include -++ #include -++-#elif defined(AMIGA) -++-#include "amiga/dirent.h" -++ #else -++ #include -++ #include -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/README.md binkd_pgul/README.md -++--- binkd/README.md 2026-04-22 18:44:10.839451402 +0100 -+++++ binkd_pgul/README.md 2026-04-16 17:49:00.000000000 +0100 -++@@ -6,79 +6,6 @@ -++ -++ ## Compiling -++ -++-AmigaOS: -++- -++-Copy "mkfls/amiga/Makefile" to root and make -++- -++-Need ADE to compile project with ixemul library. -++- -++-https://aminet.net/package/dev/gcc/ADE -++- -++-It no longer requires ADE to be installed for execution, as it doesn't need /bin/sh to run scripts or external programs from binkd.conf. However, it does require ixemul.library and ixnet.library, either in the libs directory or in the same directory as the executable. -++- -++-You can find the ADE ixemul.library and ixnet.library libraries in the aminet package or in the released versions, along with the program and utilities already compiled. -++- -++-https://github.com/skbn/binkd/releases -++- -++-5.Work:fido> version work:fido/ixnet.library -++-ixnet.library 63.1 -++- -++-5.Work:fido> version work:fido/ixemul.library -++-ixemul.library 63.1 -++- -++- -++-I've attached six programs for your assistance: -++- -++-[decompress] which decompresses incoming files in lha or zip format, if necessary. -++- -++-``` -++-[freq] which generates file requests in ASO mode and places the necessary files in outbound directory. -++- -++-Usage:freq Z:N/NODE[.POINT] -++-``` -++-``` -++-[freq_bso] same but BSO style -++-Usage: freq_bso Z:N/NODE[.POINT] -++- No point : .0ZZ/NNNNNNNN.req -++- Point : .0ZZ/N -++-``` -++- -++-``` -++-[process_tic] which processes tic files and places them in the filebox folder. -++-With --copypublic option, copy the file you receive to a directory named pub/ from PROGDIR -++->> exec "path/process_tic --copypublic" *.tic *.TIC -++->> exec "path/process_tic" *.tic *.TIC -++-``` -++- -++-``` -++-[srifreq] copy of "misc/srifreq" but in c. SRIF-compatible file-request server for binkd -++- -++-Usage: srifreq [] -++- SRIF control file passed by binkd (exec "srifreq *S") -++- directory containing public downloadable files -++- log file path (pass "" or - to disable logging) -++- optional file mapping magic names to real paths -++- -++-Aliases file format (one alias per line, # = comment): -++-MAGIC_NAME relative/or/absolute/path/to/file -++-Example: -++-NODELIST pub/NODELIST.ZIP -++-FILES pub/ALLFILES.TXT -++- -++-binkd.conf example: exec "srifreq *S pub log/srifreq.log aliases.txt" *.req -++-``` -++- -++-``` -++-[nodelist] FidoNet nodelist compiler for binkd - AmigaOS version in c from "misc/nodelist.pl" -++- -++-Usage: nodelist [] -++-``` -++- -++-Also compileable on *nix >> "gcc -O2 -Wall -o nodelist nodelist.c" -++- -++-BUGFIXES: -++-Option "-C" to reload config It remains unstable -++- -++ non-UNIX: -++ -++ 1. Find in mkfls/ a subdirectory for your system/compiler, copy all files -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/rfc2553.h binkd_pgul/rfc2553.h -++--- binkd/rfc2553.h 2026-04-26 10:31:43.475331092 +0100 -+++++ binkd_pgul/rfc2553.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -21,52 +21,8 @@ -++ -++ #include "iphdr.h" -++ -++-/* Amiga: define EAI_* before including netdb.h to prevent libnix redefinition */ -++-#if defined(AMIGA) -++-#include -++-#include -++-#include -++-#include -++- -++- #undef EAI_NONAME -++- #undef EAI_AGAIN -++- #undef EAI_FAIL -++- #undef EAI_NODATA -++- #undef EAI_FAMILY -++- #undef EAI_SOCKTYPE -++- #undef EAI_SERVICE -++- #undef EAI_ADDRFAMILY -++- #undef EAI_MEMORY -++- #undef EAI_SYSTEM -++- #undef EAI_UNKNOWN -++- #define EAI_NONAME -1 -++- #define EAI_AGAIN -2 -++- #define EAI_FAIL -3 -++- #define EAI_NODATA -4 -++- #define EAI_FAMILY -5 -++- #define EAI_SOCKTYPE -6 -++- #define EAI_SERVICE -7 -++- #define EAI_ADDRFAMILY -8 -++- #define EAI_MEMORY -9 -++- #define EAI_SYSTEM -10 -++- #define EAI_UNKNOWN -11 -++- -++-#include -++- -++-/* EAI_ADDRFAMILY is BSD/macOS specific; Linux/glibc does not define it -++- * Map it to EAI_FAMILY which has the same meaning on those platforms */ -++-#ifndef EAI_ADDRFAMILY -++-#ifdef EAI_FAMILY -++-#define EAI_ADDRFAMILY EAI_FAMILY -++-#else -++-#define EAI_ADDRFAMILY -9 -++-#endif -++-#endif -++- -++-#endif -++- -++ /* Autosense getaddrinfo */ -++-#if defined(AI_PASSIVE) && defined(EAI_NONAME) && !defined(AMIGA) -+++#if defined(AI_PASSIVE) && defined(EAI_NONAME) -++ #define HAVE_GETADDRINFO -++ #endif -++ -++@@ -91,18 +47,6 @@ -++ }; -++ #define addrinfo addrinfo_emu -++ -++-#ifdef AMIGA -++-#ifdef getaddrinfo -++-#undef getaddrinfo -++-#endif -++-#ifdef freeaddrinfo -++-#undef freeaddrinfo -++-#endif -++-#ifdef gai_strerror -++-#undef gai_strerror -++-#endif -++-#endif -++- -++ int getaddrinfo(const char *nodename, const char *servname, -++ const struct addrinfo *hints, -++ struct addrinfo **res); -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/run.c binkd_pgul/run.c -++--- binkd/run.c 2026-04-26 10:31:17.108612481 +0100 -+++++ binkd_pgul/run.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -18,11 +18,6 @@ -++ #ifdef HAVE_UNISTD_H -++ #include -++ #endif -++-#ifdef AMIGA -++-#include -++-#include -++-#include -++-#endif -++ -++ #include "sys.h" -++ #include "run.h" -++@@ -45,128 +40,10 @@ -++ #define SHELL (getenv("COMSPEC") ? getenv("COMSPEC") : "command.com") -++ #define SHELL_META "\"\'\\%<>|&^@" -++ #define SHELLOPT "/c" -++-#elif defined(AMIGA) -++-/* AmigaOS shell */ -++-#define SHELL "c:execute" -++-#define SHELL_META "\"\'\\*?(){};&|<>" -++ #else -++ #error "Unknown platform" -++ #endif -++ -++-#ifdef AMIGA -++-/* run(): execute an AmigaDOS command via SystemTagList() with NIL: I/O -++- * Runs synchronously so binkd waits for completion (needed for srifreq -++- * to create .rsp before parse_response). NIL: I/O prevents CLI freezing -++- * Error output goes to a temporary file for logging on failure */ -++-int run(char *cmd) -++-{ -++- /* All declarations at the top for C89/ADE GCC 2.95 compatibility */ -++- BPTR nil_in; -++- char errfile[MAXPATHLEN]; -++- BPTR err_out = 0; -++- int rc = 0; -++- char cmd_copy[MAXPATHLEN]; -++- char *cmd_start; -++- char *cmd_end; -++- BPTR lock; -++- struct TagItem exec_tags[5]; -++- BPTR errfile_ptr; -++- char buf[512]; -++- int len; -++- -++- /* Open NIL: for input/output */ -++- nil_in = Open("NIL:", MODE_OLDFILE); -++- -++- /* Create temporary error file in current directory */ -++- snprintf(errfile, sizeof(errfile), "binkd_err_%ld.txt", (long)time(NULL)); -++- err_out = Open(errfile, MODE_NEWFILE); -++- if (err_out == 0) -++- { -++- Log(2, "cannot create error file %s, using NIL: for error output", errfile); -++- } -++- -++- /* Extract the command (first word) to check if it exists */ -++- strncpy(cmd_copy, cmd, sizeof(cmd_copy) - 1); -++- cmd_copy[sizeof(cmd_copy) - 1] = '\0'; -++- cmd_start = cmd_copy; /* Work on copy, never modify original cmd */ -++- -++- /* Skip leading whitespace */ -++- while (*cmd_start && (*cmd_start == ' ' || *cmd_start == '\t')) -++- cmd_start++; -++- -++- /* Find end of command (first space or end) */ -++- cmd_end = cmd_start; -++- while (*cmd_end && *cmd_end != ' ' && *cmd_end != '\t') -++- cmd_end++; -++- *cmd_end = '\0'; -++- -++- /* Check if command exists */ -++- lock = Lock((STRPTR)cmd_start, SHARED_LOCK); -++- if (lock == 0) -++- { -++- Log(2, "command not found, skipping: '%s'", cmd_start); -++- Close(nil_in); -++- if (err_out) -++- Close(err_out); -++- DeleteFile((STRPTR)errfile); -++- return 0; -++- } -++- UnLock(lock); -++- -++- Log(3, "executing '%s'", cmd); -++- -++- /* Set up tags with NIL: input and error file output. -++- * Use NP_* (New Process) tags instead of SYS_* to avoid sharing -++- * file handles with parent process - prevents stderr from being -++- * closed when child exits. */ -++- exec_tags[0].ti_Tag = NP_Input; -++- exec_tags[0].ti_Data = (ULONG)nil_in; -++- exec_tags[1].ti_Tag = NP_Output; -++- exec_tags[1].ti_Data = (ULONG)nil_in; -++- exec_tags[2].ti_Tag = NP_Error; -++- exec_tags[2].ti_Data = (ULONG)err_out; -++- exec_tags[3].ti_Tag = NP_Synchronous; -++- exec_tags[3].ti_Data = TRUE; -++- exec_tags[4].ti_Tag = TAG_DONE; -++- exec_tags[4].ti_Data = 0; -++- -++- rc = SystemTagList((STRPTR)cmd, exec_tags); -++- -++- /* Close handles */ -++- Close(nil_in); -++- if (err_out) -++- Close(err_out); -++- -++- /* Log error output if command failed */ -++- if (rc != 0) -++- { -++- errfile_ptr = Open(errfile, MODE_OLDFILE); -++- if (errfile_ptr) -++- { -++- Log(2, "command failed with rc=%d, output:", rc); -++- while ((len = Read(errfile_ptr, buf, sizeof(buf) - 1)) > 0) -++- { -++- buf[len] = '\0'; -++- Log(2, "%s", buf); -++- } -++- Close(errfile_ptr); -++- } -++- } -++- -++- DeleteFile((STRPTR)errfile); -++- return rc; -++-} -++- -++-/* run3(): pipe/tunnel not supported on AmigaOS without ixemul. */ -++-int run3(const char *cmd, int *in, int *out, int *err) -++-{ -++- (void)cmd; (void)in; (void)out; (void)err; -++- Log(1, "run3: pipe connections not supported on Amiga"); -++- return -1; -++-} -++-#endif /* AMIGA */ -++- -++-#ifndef AMIGA -++ int run (char *cmd) -++ { -++ int rc=-1; -++@@ -234,7 +111,6 @@ -++ #endif -++ return rc; -++ } -++-#endif /* !AMIGA */ -++ -++ #ifdef __MINGW32__ -++ static int set_cloexec(int fd) -++@@ -260,7 +136,6 @@ -++ } -++ #endif -++ -++-#ifndef AMIGA -++ int run3 (const char *cmd, int *in, int *out, int *err) -++ { -++ int pid; -++@@ -287,14 +162,6 @@ -++ } -++ -++ #ifdef HAVE_FORK -++-#ifdef AMIGA -++- /* Pipe tunneling not supported on AmigaOS without fork() */ -++- Log(1, "run3: pipe/tunnel not supported on Amiga: %s", cmd); -++- if (in) close(pin[1]), close(pin[0]); -++- if (out) close(pout[1]), close(pout[0]); -++- if (err) close(perr[1]), close(perr[0]); -++- return -1; -++-#else -++ pid = fork(); -++ if (pid == -1) -++ { -++@@ -327,11 +194,7 @@ -++ if (strpbrk(cmd, SHELL_META)) -++ { -++ shell = SHELL; -++-#ifdef AMIGA -++- execl(shell, shell, cmd, (char *)NULL); -++-#else -++ execl(shell, shell, SHELLOPT, cmd, (char *)NULL); -++-#endif -++ } -++ else -++ { -++@@ -369,7 +232,6 @@ -++ *err = perr[0]; -++ close(perr[1]); -++ } -++-#endif /* !AMIGA */ -++ #else -++ -++ /* redirect stdin/stdout/stderr takes effect for all threads */ -++@@ -474,4 +336,3 @@ -++ return pid; -++ } -++ -++-#endif /* !AMIGA */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/sem.h binkd_pgul/sem.h -++--- binkd/sem.h 2026-04-26 10:09:54.148733026 +0100 -+++++ binkd_pgul/sem.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -34,14 +34,6 @@ -++ #include -++ typedef struct SignalSemaphore MUTEXSEM; -++ -++-#ifdef AMIGA -++-typedef struct -++-{ -++- struct Task *waiter; -++- ULONG sigbit; -++-} EVENTSEM; -++-#endif -++- -++ #elif defined(WITH_PTHREADS) -++ -++ #include -++@@ -81,27 +73,25 @@ -++ * Initialise Event Semaphores. -++ */ -++ -++-#ifdef AMIGA -++-int _InitEventSem (EVENTSEM *); -+++int _InitEventSem (void *); -++ -++ /* -++ * Post Semaphore. -++ */ -++ -++-int _PostSem (EVENTSEM *); -+++int _PostSem (void *); -++ -++ /* -++ * Wait Semaphore. -++ */ -++ -++-int _WaitSem (EVENTSEM *, int); -+++int _WaitSem (void *, int); -++ -++ /* -++ * Clean Event Semaphores. -++ */ -++ -++-int _CleanEventSem (EVENTSEM *); -++-#endif -+++int _CleanEventSem (void *); -++ -++ #if defined(WITH_PTHREADS) -++ #define InitSem(sem) pthread_mutex_init(sem, NULL) -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/server.c binkd_pgul/server.c -++--- binkd/server.c 2026-04-26 10:30:03.538324924 +0100 -+++++ binkd_pgul/server.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -23,10 +23,6 @@ -++ #include -++ #endif -++ -++-#ifdef AMIGA -++-#include "amiga/bsdsock.h" -++-#endif -++- -++ #include "sys.h" -++ #include "iphdr.h" -++ #include "readcfg.h" -++@@ -43,10 +39,6 @@ -++ #endif -++ #include "rfc2553.h" -++ -++-#if defined(HAVE_THREADS) || defined(AMIGA) -++-extern EVENTSEM eothread; -++-#endif -++- -++ int n_servers = 0; -++ int ext_rand = 0; -++ -++@@ -61,8 +53,7 @@ -++ void *cperl; -++ #endif -++ -++-/* Prevent shared socket closure */ -++-#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(AMIGA) && !defined(DEBUGCHILD) -+++#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(DEBUGCHILD) -++ int curfd; -++ pidcmgr = 0; -++ for (curfd=0; curfdai_addr, ai->ai_addrlen) != 0) -++ { -++-#ifdef AMIGA -++- /* bsdsocket may hold the port briefly after socket close. Retry */ -++- int bind_retries = 6; -++- -++- while (bind(sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) -++- { -++- if (--bind_retries == 0) -++- { -++- Log(1, "servmgr bind(): %s", TCPERR()); -++- soclose(sockfd[sockfd_used]); -++- return -1; -++- } -++- -++- Log(2, "servmgr bind(): %s, retry in 2s...", TCPERR()); -++- sleep(2); -++- } -++-#else -++- if (bind (sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) -++- { -++- Log(1, "servmgr bind(): %s", TCPERR ()); -++- soclose(sockfd[sockfd_used]); -++- return -1; -++- } -++-#endif -+++ Log(1, "servmgr bind(): %s", TCPERR ()); -+++ soclose(sockfd[sockfd_used]); -+++ return -1; -++ } -++ if (listen (sockfd[sockfd_used], 5) != 0) -++ { -++@@ -201,12 +168,6 @@ -++ -++ setproctitle ("server manager (listen %s)", config->listen.first->port); -++ -++- /* Save rescan_delay locally. checkcfg() may free 'config' (old config -++- * is released when usageCount reaches 0 after reload), so we must not -++- * access config->rescan_delay inside the loop after a reload */ -++- { -++- int rescan = config->rescan_delay; -++- -++ for (;;) -++ { -++ struct timeval tv; -++@@ -222,7 +183,7 @@ -++ maxfd = sockfd[curfd]; -++ } -++ tv.tv_usec = 0; -++- tv.tv_sec = rescan; -+++ tv.tv_sec = CHECKCFG_INTERVAL; -++ unblocksig(); -++ check_child(&n_servers); -++ n = select(maxfd+1, &r, NULL, NULL, &tv); -++@@ -231,20 +192,8 @@ -++ { case 0: /* timeout */ -++ if (checkcfg()) -++ { -++- /* config may have been freed by checkcfg() — read rescan from -++- * the new current_config before returning for restart */ -++- { -++- BINKD_CONFIG *nc = lock_current_config(); -++- if (nc) -++- { -++- rescan = nc->rescan_delay; -++- unlock_config_structure(nc, 0); -++- } -++- } -++- -++ for (curfd=0; curfdrescan_delay; -++- unlock_config_structure(nc, 0); -++- } -++- } -++- -++ for (curfd=0; curfd -++-#endif -++- -++ /* -++ * Listens... Than calls protocol() -++ */ -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/sys.h binkd_pgul/sys.h -++--- binkd/sys.h 2026-04-26 08:48:04.281489117 +0100 -+++++ binkd_pgul/sys.h 2026-04-16 17:49:00.000000000 +0100 -++@@ -22,28 +22,8 @@ -++ #ifdef HAVE_STDINT_H -++ #include -++ #endif -++-#ifdef AMIGA -++- /* Include Amiga exec proto for Delay() function */ -++- #include -++-#endif -++ #ifdef HAVE_UNISTD_H -++ #include -++- /* Undefine conflicting unistd.h macros for AMIGA */ -++- #ifdef AMIGA -++- #ifdef getpid -++- #undef getpid -++- #endif -++- -++- #ifdef sleep -++- #undef sleep -++- #endif -++- -++- /* Redefine with our Amiga implementations */ -++- #define getpid() ((int)(ULONG)FindTask(NULL)) -++- -++- #define sleep(s) Delay((ULONG)((s) * 50)) -++- -++- #endif /* AMIGA */ -++ #endif -++ #ifdef HAVE_IO_H -++ #include -++@@ -125,11 +105,6 @@ -++ #define PID() mypid -++ #endif -++ -++-#ifdef HAVE_FORK -++- #include /* Needed for SIG_BLOCK/SIG_UNBLOCK and WIFEXITED/WEXITSTATUS */ -++- #include -++-#endif -++- -++ #if defined(HAVE_FORK) && defined(HAVE_SIGPROCMASK) && defined(HAVE_WAITPID) && defined(SIG_BLOCK) -++ void switchsignal(int how); -++ #define blocksig() switchsignal(SIG_BLOCK) -++@@ -317,16 +292,8 @@ -++ -++ #ifndef PRIdMAX -++ #define PRIdMAX "ld" -++-#endif -++- -++-#ifndef PRIuMAX -++-#ifdef AMIGA -++-/* On AmigaOS m68k, uintmax_t is long long unsigned int */ -++-#define PRIuMAX "llu" -++-#else -++ #define PRIuMAX "lu" -++ #endif -++-#endif -++ -++ #ifndef HAVE_STRTOUMAX -++ #define strtoumax(ptr, endptr, base) strtoul(ptr, endptr, base) -++diff -ruN '--exclude=.git' '--exclude=.gitignore' '--exclude=cambios.diff' '--exclude=changes.txt' '--exclude=changes.diff' '--exclude=.*' binkd/tools.c binkd_pgul/tools.c -++--- binkd/tools.c 2026-04-25 23:49:26.267090428 +0100 -+++++ binkd_pgul/tools.c 2026-04-16 17:49:00.000000000 +0100 -++@@ -22,10 +22,6 @@ -++ #include -++ #endif -++ -++-#ifdef AMIGA -++-#include "amiga/bsdsock.h" -++-#endif -++- -++ #include "sys.h" -++ #include "readcfg.h" -++ #include "common.h" -++@@ -42,12 +38,6 @@ -++ #include "nt/w32tools.h" -++ #endif -++ -++-#if defined(HAVE_THREADS) || defined(AMIGA) -++-extern MUTEXSEM lsem; -++-#endif -++- -++-extern void vLog (int lev, char *s, va_list ap); -++- -++ /* -++ * We can call Log() even when we have no config ready. So, we must keep -++ * internal variables which will be updated when config is loaded -++@@ -300,10 +290,6 @@ -++ char buf[1024]; -++ int ok = 1; -++ -++-#ifdef AMIGA -++- static int need_newline = 0; -++-#endif -++- -++ /* make string in buffer */ -++ vsnprintf(buf, sizeof(buf), s, ap); -++ /* do perl hooks */ -++@@ -327,20 +313,8 @@ -++ if (lev <= current_conlog && !inetd_flag) -++ { -++ LockSem(&lsem); -++-#ifdef AMIGA -++- /* AmigaOS: go to new line for status messages to avoid overwriting */ -++- if (lev < 0 && need_newline) -++- { -++- need_newline = 0; -++- } -++- fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, -++- tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : "\r"); -++- if (lev >= 0) -++- need_newline = 1; -++-#else -++ fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, -++ tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : ""); -++-#endif -++ fflush (stderr); -++ ReleaseSem(&lsem); -++ if (lev < 0) -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/changes.txt binkd/changes.txt ---- binkd_pgul/changes.txt 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/changes.txt 2026-04-26 16:13:03.777428944 +0100 -@@ -0,0 +1,260 @@ -+=========================================================================== -+ CHANGES - binkd fork from pgul -+=========================================================================== -+ -+This diff shows the changes between binkd/ (current version) and binkd_pgul/ (pgul's fork). -+ -+=========================================================================== -+MULTI-PLATFORM CHANGES (all OS) -+=========================================================================== -+ -+--- Bug fixes --- -+ -+ftnq.c: -+- Fixed try file deletion: only delete if file exists (prevents error on missing .try files) -+ -+inbound.c: -+- Fixed double PATH_SEPARATOR: removed checks, now always adds separator -+ -+protocol.c: -+- Changed init_protocol() and deinit_protocol() from int to static -+ -+client.c: -+- Removed check for no_call_delay (now always does SLEEP) -+- Removed Amiga-specific checkcfg() block -+ -+--- New files --- -+ -+bsycleanup.c: -+- BSY/CSY/TRY file cleanup at startup, scans domain outbounds -+ -+bsycleanup.h: -+- cleanup_old_bsy() -+ -+--- Misc tools (new) --- -+ -+misc/decompress.c: -+- Decompress FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA) -+ -+misc/freq.c: -+- Create .req/.clo files (ASO, BSO) -+ -+misc/nodelist.c: -+- Compile FidoNet nodelist to binkd.conf node lines (extracts IBN/INA flags) -+ -+misc/process_tic.c: -+- Process .tic files to filebox (supports config file and legacy args) -+ -+misc/srifreq.c: -+- SRIF-compatible file request server (password protection, aliases, wildcards, rate limiting) -+ -+misc/portable.c: -+- Shared implementations: string utilities, file operations, path utilities -+ -+misc/portable.h: -+- Portable declarations and platform-specific includes (POSIX, Win32, OS/2, DOS, AmigaOS) -+ -+--- Documentation --- -+ -+misc/decompress.txt, misc/freq.txt, misc/nodelist.txt, misc/process_tic.txt, misc/srifreq.txt: -+- Usage documentation for misc tools -+ -+--- Makefile updates --- -+ -+mkfls/unix/Makefile.in: -+- Added bsycleanup.c to SRCS -+- Added portable.c to misc tool builds -+- Added -I misc to TOOL_CFLAGS -+ -+mkfls/nt95-mingw/Makefile: -+- Added bsycleanup.c to SRCS -+- Added portable.c to misc tool builds -+- Added -I misc to TOOL_CFLAGS -+ -+mkfls/nt95-msvc/Makefile: -+- Added portable.c to misc tool builds -+- Added -I misc to TOOL_CFLAGS -+ -+mkfls/os2-emx/Makefile: -+- Added bsycleanup.c to SRCS -+- Added portable.c to misc tool builds -+- Added -I misc to TOOL_CFLAGS -+ -+mkfls/unix/Makefile.analyze.unix: -+- New: Static analysis Makefile for Unix/GCC -+ -+=========================================================================== -+AMIGA-SPECIFIC CHANGES -+=========================================================================== -+ -+--- New Amiga files (17 files) --- -+ -+amiga/evloop.c: -+- Non-blocking event loop with WaitSelect(), replaces servmgr()/clientmgr() -+ -+amiga/evloop.h: -+- Event loop API (amiga_evloop_run) -+ -+amiga/evloop_int.h: -+- Internal session types (sess_t, sess_phase_t, session table) -+ -+amiga/proto_amiga.c: -+- Non-blocking BinkP protocol (amiga_proto_open, amiga_proto_step, amiga_proto_close) -+ -+amiga/proto_amiga.h: -+- Non-blocking protocol API (APROTO_RUNNING, APROTO_DONE_OK, APROTO_DONE_ERR) -+ -+amiga/session.c: -+- Session allocation, accept(), non-blocking connect(), outbound scan -+ -+amiga/sock.c: -+- Listen socket management (open_listen_sockets, close_listen_sockets) -+ -+amiga/utime.c: -+- utime() stub for AmigaDOS (converts Unix epoch to AmigaDOS epoch) -+ -+amiga/rfc2553_amiga.c: -+- getaddrinfo/getnameinfo fallback for Roadshow TCP/IP -+ -+amiga/bsdsock.c: -+- BSD socket layer for Roadshow TCP/IP (SocketBase initialization) -+ -+amiga/bsdsock.h: -+- BSD socket API (SocketBase, TCPERR macros) -+ -+amiga/compat_netinet_in.h: -+- Compatibility header for netinet/in.h -+ -+amiga/dirent.c: -+- POSIX dirent emulation for AmigaDOS -+ -+amiga/dirent.h: -+- POSIX dirent API (DIR, dirent, opendir, readdir) -+ -+amiga/rename.c: -+- Complete rewrite: duplicate name handling with .001/.002 suffixes, directory scanning, buffer overflow protection -+ -+amiga/sem.c: -+- Complete rewrite: replaced ixemul with direct Exec calls, added EVENTSEM implementation -+ -+--- Modified Amiga files --- -+ -+mkfls/amiga/Makefile.bebbo: -+- New: Complete rewrite for bebbo gcc cross-compiler (no ixemul, libnix, Roadshow TCP/IP) -+- Added bsycleanup.c, portable.c, all amiga/ sources -+- Added misc tool compilation rules + -I misc -+- Added AMIGADOS_4D_OUTBOUND define -+ -+mkfls/amiga/Makefile.analyze.bebbo: -+- New: Static analysis Makefile for bebbo gcc -+ -+--- Core files: Amiga code REMOVED (moved to amiga/ directory) --- -+ -+binkd.c: -+- REMOVED: #include "amiga/evloop.h" -+- REMOVED: #include "bsycleanup.h" -+- REMOVED: cleanup_old_bsy() call -+- REMOVED: Amiga semaphore declarations (changed from HAVE_THREADS||AMIGA to HAVE_THREADS) -+- REMOVED: mypid under AMIGA -+- REMOVED: NIL: null device for Amiga -+- REMOVED: #ifndef AMIGA guards around -i and -a options -+- REMOVED: amiga_evloop_run() block -+- REMOVED: config file validation -+ -+binlog.c: -+- REMOVED: extern blsem under (HAVE_THREADS || AMIGA) -+ -+branch.c: -+- ADDED: ix_vfork() implementation under #ifdef AMIGA (appears to be incorrect code) -+ -+readcfg.c: -+- REMOVED: extern config_sem under #ifdef AMIGA -+- REMOVED: no_call_delay initialization -+- REMOVED: tcp_nodelay initialization under #ifdef AMIGA -+ -+readcfg.h: -+- REMOVED: tcp_nodelay, so_sndbuf, so_rcvbuf fields under #ifdef AMIGA -+ -+server.c: -+- REMOVED: #include "amiga/bsdsock.h" -+- REMOVED: extern eothread under (HAVE_THREADS || AMIGA) -+- REMOVED: PF_INET forced for Amiga -+- REMOVED: bind() retry loop for Amiga -+- REMOVED: select() ENOTSOCK/EBADF recovery for Amiga -+ -+protocol.c: -+- REMOVED: #include "amiga/proto_amiga.h" -+- REMOVED: extern lsem under (HAVE_THREADS || AMIGA) -+- REMOVED: setsockopts_amiga() calls -+ -+client.c: -+- REMOVED: #include "amiga/bsdsock.h" -+- REMOVED: extern lsem and eothread under (HAVE_THREADS || AMIGA) -+ -+client.h: -+- REMOVED: includes under #ifdef AMIGA -+- REMOVED: call0() declaration under #ifdef AMIGA -+ -+run.c: -+- REMOVED: All Amiga-specific code (includes, defines, SystemTagList() implementation) -+ -+tools.c: -+- REMOVED: #include "amiga/bsdsock.h" -+- REMOVED: extern lsem under (HAVE_THREADS || AMIGA) -+- REMOVED: extern vLog() -+- REMOVED: Amiga console logging (need_newline, \r carriage return) -+ -+breaksig.c: -+- REMOVED: SIGINT/SIGTERM signal handlers under #ifdef AMIGA -+ -+exitproc.c: -+- REMOVED: All Amiga cleanup block (close_srvmgr_socket, CleanEventSem, CleanSem, sock_deinit, nodes_deinit, bsy_remove_all) -+ -+https.c: -+- REMOVED: .sin_addr.s_addr for Amiga (now uses .sin_addr) -+ -+iptools.c: -+- REMOVED: #include under #ifdef AMIGA -+- REMOVED: ioctl() size parameter for Amiga -+- REMOVED: setsockopts_amiga() function -+ -+iptools.h: -+- REMOVED: includes under #ifdef AMIGA -+- REMOVED: setsockopts_amiga() declaration -+ -+Config.h: -+- REMOVED: AMIGA from platform check (changed from +defined(AMIGA) to without AMIGA) -+ -+btypes.h: -+- REMOVED: NC_flag field -+ -+ftnnode.h: -+- REMOVED: NC_flag parameter to add_node() -+- REMOVED: NC_ON/NC_OFF/NC_USE_OLD defines -+ -+ftnnode.c: -+- REMOVED: NC_flag propagation -+ -+iphdr.h: -+- REMOVED: amiga/bsdsock.h include -+- REMOVED: sock_init()/sock_deinit()/soclose() macros for Amiga -+ -+readdir.h: -+- REMOVED: #elif defined(AMIGA) include for amiga/dirent.h -+ -+rfc2553.h: -+- REMOVED: EAI_* constant redefinitions under #ifdef AMIGA -+- REMOVED: include chain under #ifdef AMIGA -+- REMOVED: getaddrinfo/freeaddrinfo macros under #ifdef AMIGA -+ -+sem.h: -+- REMOVED: EVENTSEM typedef under #ifdef AMIGA -+- REMOVED: _InitEventSem/_PostSem/_WaitSem/_CleanEventSem prototypes -+ -+server.h: -+- REMOVED: #include under #ifdef AMIGA -+ -+sys.h: -+- REMOVED: #include under #ifdef AMIGA -+- REMOVED: getpid/sleep redefines under #ifdef AMIGA -+- REMOVED: PRIuMAX define under #ifdef AMIGA -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/client.c binkd/client.c ---- binkd_pgul/client.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/client.c 2026-04-26 10:25:03.436098652 +0100 -@@ -19,6 +19,10 @@ - #include - #endif - -+#ifdef AMIGA -+#include "amiga/bsdsock.h" -+#endif -+ - #include "sys.h" - #include "readcfg.h" - #include "client.h" -@@ -44,6 +48,11 @@ - #include "rfc2553.h" - #include "srv_gai.h" - -+#if defined(HAVE_THREADS) || defined(AMIGA) -+extern MUTEXSEM lsem; -+extern EVENTSEM eothread; -+#endif -+ - static void call (void *arg); - - int n_clients = 0; -@@ -202,7 +211,8 @@ - /* This sleep can be interrupted by signal, it's OK */ - unblocksig(); - check_child(&n_clients); -- SLEEP (config->call_delay); -+ if (!config->no_call_delay) -+ SLEEP (config->call_delay); - check_child(&n_clients); - blocksig(); - } -@@ -282,8 +292,16 @@ - #ifdef HAVE_THREADS - !server_flag && - #endif -+ /* AmigaOS uses shared-memory evloop — only main process calls checkcfg() -+ * On fork systems (Linux/FreeBSD) each process has separate memory -+ * so independent reloads are safe. */ - !poll_flag) -+#ifndef AMIGA - checkcfg(); -+#else -+ { -+ } -+#endif - } - - Log (5, "downing clientmgr..."); -@@ -306,7 +324,7 @@ - exit (0); - } - --static int call0 (FTN_NODE *node, BINKD_CONFIG *config) -+int call0 (FTN_NODE *node, BINKD_CONFIG *config) - { - int sockfd = INVALID_SOCKET; - int sock_out; -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/client.h binkd/client.h ---- binkd_pgul/client.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/client.h 2026-04-26 10:24:36.096878628 +0100 -@@ -1,9 +1,20 @@ - #ifndef _client_h - #define _client_h - -+#ifdef AMIGA -+#include -+#include -+#include -+#endif -+ - /* - * Scans queue, makes outbound ``call'', than calls protocol() - */ - void clientmgr(void *arg); - -+#ifdef AMIGA -+/* Direct outbound call for evloop.c (no-ixemul, no-threads build) */ -+int call0(FTN_NODE *node, BINKD_CONFIG *config); -+#endif -+ - #endif -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/Config.h binkd/Config.h ---- binkd_pgul/Config.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/Config.h 2026-04-25 19:53:34.520405599 +0100 -@@ -14,8 +14,8 @@ - #ifndef _Config_h - #define _Config_h - --#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) == 0 --#error You must define either HAVE_FORK or HAVE_THREADS! -+#if defined(HAVE_FORK) + defined(HAVE_THREADS) + defined(DOS) + defined(AMIGA) == 0 -+#error You must define HAVE_FORK, HAVE_THREADS, DOS, or AMIGA! - #endif - - #ifdef __WATCOMC__ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/exitproc.c binkd/exitproc.c ---- binkd_pgul/exitproc.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/exitproc.c 2026-04-26 10:21:07.372917687 +0100 -@@ -32,6 +32,17 @@ - #include "nt/w32tools.h" - #endif - -+#if defined(HAVE_THREADS) || defined(AMIGA) -+extern MUTEXSEM hostsem; -+extern MUTEXSEM resolvsem; -+extern MUTEXSEM lsem; -+extern MUTEXSEM blsem; -+extern MUTEXSEM varsem; -+extern MUTEXSEM config_sem; -+extern EVENTSEM eothread; -+extern EVENTSEM wakecmgr; -+#endif -+ - int binkd_exit; - - #ifdef HAVE_THREADS -@@ -138,6 +149,33 @@ - close_srvmgr_socket(); - #endif - -+#ifdef AMIGA -+ /* evloop: single process, no children -+ * Clean Exec semaphores in safe order before freeing config */ -+ close_srvmgr_socket(); -+ CleanEventSem(&wakecmgr); -+ CleanEventSem(&eothread); -+ CleanSem(&varsem); -+ CleanSem(&blsem); -+ CleanSem(&lsem); -+ CleanSem(&resolvsem); -+ CleanSem(&hostsem); -+ CleanSem(&config_sem); -+ sock_deinit(); -+ nodes_deinit(); -+ { -+ BINKD_CONFIG *cfg = lock_current_config(); -+ if (cfg) -+ bsy_remove_all(cfg); -+ if (cfg && *cfg->pid_file && pidsmgr == (int)getpid()) -+ delete(cfg->pid_file); -+ if (cfg) -+ unlock_config_structure(cfg, 1); -+ } -+ Log(6, "exitfunc: AmigaOS cleanup done"); -+ return; -+#endif /* AMIGA */ -+ - config = lock_current_config(); - if (config) - bsy_remove_all (config); -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/ftnnode.c binkd/ftnnode.c ---- binkd_pgul/ftnnode.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/ftnnode.c 2026-04-26 09:22:13.159382256 +0100 -@@ -74,7 +74,7 @@ - */ - static FTN_NODE *add_node_nolock (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, - char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, -- int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, -+ int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, - int IP_afamily, - #ifdef BW_LIM - long bw_send, long bw_recv, -@@ -107,6 +107,7 @@ - pn->NR_flag = NR_OFF; - pn->ND_flag = ND_OFF; - pn->NP_flag = NP_OFF; -+ pn->NC_flag = NC_OFF; - pn->MD_flag = MD_USE_OLD; - pn->HC_flag = HC_USE_OLD; - pn->pipe = NULL; -@@ -134,6 +135,8 @@ - pn->ND_flag = ND_flag; - if (NP_flag != NP_USE_OLD) - pn->NP_flag = NP_flag; -+ if (NC_flag != NC_USE_OLD) -+ pn->NC_flag = NC_flag; - if (HC_flag != HC_USE_OLD) - pn->HC_flag = HC_flag; - if (IP_afamily != AF_USE_OLD) -@@ -195,7 +198,7 @@ - - FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, - char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, -- int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, -+ int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, - int IP_afamily, - #ifdef BW_LIM - long bw_send, long bw_recv, -@@ -209,7 +212,7 @@ - - locknodesem(); - pn = add_node_nolock(fa, hosts, pwd, pkt_pwd, out_pwd, obox_flvr, obox, ibox, -- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, -+ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, - IP_afamily, - #ifdef BW_LIM - bw_send, bw_recv, -@@ -275,6 +278,7 @@ - on->ND_flag=np->ND_flag; - on->MD_flag=np->MD_flag; - on->NP_flag=np->NP_flag; -+ on->NC_flag=np->NC_flag; - on->HC_flag=np->HC_flag; - on->restrictIP=np->restrictIP; - on->pipe=np->pipe; -@@ -290,7 +294,7 @@ - - add_node_nolock(fa, np->hosts, NULL, NULL, NULL, np->obox_flvr, np->obox, - np->ibox, np->NR_flag, np->ND_flag, np->MD_flag, np->restrictIP, -- np->HC_flag, np->NP_flag, np->pipe, np->IP_afamily, -+ np->HC_flag, np->NP_flag, np->NC_flag, np->pipe, np->IP_afamily, - #ifdef BW_LIM - np->bw_send, np->bw_recv, - #endif -@@ -399,7 +403,7 @@ - if (!get_node_info_nolock (&target, config)) - add_node_nolock (&target, "*", NULL, NULL, NULL, '-', NULL, NULL, - NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, -- HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, -+ HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, - #ifdef BW_LIM - BW_DEF, BW_DEF, - #endif -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/ftnnode.h binkd/ftnnode.h ---- binkd_pgul/ftnnode.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/ftnnode.h 2026-04-25 20:40:18.652899844 +0100 -@@ -36,7 +36,7 @@ - */ - FTN_NODE *add_node (FTN_ADDR *fa, char *hosts, char *pwd, char *pkt_pwd, char *out_pwd, - char obox_flvr, char *obox, char *ibox, int NR_flag, int ND_flag, -- int MD_flag, int restrictIP, int HC_flag, int NP_flag, char *pipe, -+ int MD_flag, int restrictIP, int HC_flag, int NP_flag, int NC_flag, char *pipe, - int IP_afamily, - #ifdef BW_LIM - long bw_send, long bw_recv, -@@ -75,6 +75,10 @@ - #define NP_OFF 0 - #define NP_USE_OLD -1 /* Use old value */ - -+#define NC_ON 1 -+#define NC_OFF 0 -+#define NC_USE_OLD -1 /* Use old value */ -+ - #define AF_USE_OLD -1 /* Use old value */ - - #ifdef BW_LIM -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/ftnq.c binkd/ftnq.c ---- binkd_pgul/ftnq.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/ftnq.c 2026-04-26 10:34:47.939032694 +0100 -@@ -1147,7 +1147,8 @@ - if (*buf) - { - strnzcat (buf, ".try", sizeof (buf)); -- if (stat(buf, &sb) == -1) return; -- delete (buf); -+ /* Delete only if the file exists */ -+ if (stat(buf, &sb) == 0) -+ delete (buf); - } - } -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/https.c binkd/https.c ---- binkd_pgul/https.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/https.c 2026-04-22 21:36:50.649712064 +0100 -@@ -318,7 +318,11 @@ - buf[1]=1; - lockhostsem(); - Log (4, strcmp(port, config->oport) == 0 ? "trying %s..." : "trying %s:%u...", -- inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); -+#ifdef AMIGA -+ inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr.s_addr), portnum); -+#else -+ inet_ntoa(((struct sockaddr_in*)(ai->ai_addr))->sin_addr), portnum); -+#endif - releasehostsem(); - buf[2]=(unsigned char)((portnum>>8)&0xFF); - buf[3]=(unsigned char)(portnum&0xFF); -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/inbound.c binkd/inbound.c ---- binkd_pgul/inbound.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/inbound.c 2026-04-26 09:25:07.283995051 +0100 -@@ -49,7 +49,9 @@ - char node[FTN_ADDR_SZ + 1]; - - strnzcpy (s, inbound, MAXPATHLEN); -- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -+ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+ if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) -+ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); - t = s + strlen (s); - while (1) - { -@@ -128,7 +130,9 @@ - } - - strnzcpy (s, inbound, MAXPATHLEN); -- strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); -+ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+ if (strlen(s) > 0 && s[strlen(s) - 1] != PATH_SEPARATOR[0]) -+ strnzcat (s, PATH_SEPARATOR, MAXPATHLEN); - t = s + strlen (s); - while ((de = readdir (dp)) != 0) - { -@@ -470,7 +474,9 @@ - } - - strnzcpy (real_name, state->inbound, MAXPATHLEN); -- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -+ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+ if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) -+ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); - s = real_name + strlen (real_name); - strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); - free (u); -@@ -541,7 +547,9 @@ - { - ren_style = RENAME_POSTFIX; - strnzcpy (real_name, state->inbound, MAXPATHLEN); -- strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); -+ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+ if (strlen(real_name) > 0 && real_name[strlen(real_name) - 1] != PATH_SEPARATOR[0]) -+ strnzcat (real_name, PATH_SEPARATOR, MAXPATHLEN); - s = real_name + strlen (real_name); - strnzcat (real_name, u = makeinboundcase (strdequote (netname), (int)config->inboundcase), MAXPATHLEN); - free (u); -@@ -591,7 +599,9 @@ - struct stat sb; - - strnzcpy (fp, inbound, MAXPATHLEN); -- strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); -+ /* Only add PATH_SEPARATOR if inbound doesn't already end with one */ -+ if (strlen(fp) > 0 && fp[strlen(fp) - 1] != PATH_SEPARATOR[0]) -+ strnzcat (fp, PATH_SEPARATOR, MAXPATHLEN); - s = fp + strlen (fp); - strnzcat (fp, u = strdequote (filename), MAXPATHLEN); - free (u); -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/iphdr.h binkd/iphdr.h ---- binkd_pgul/iphdr.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/iphdr.h 2026-04-26 09:25:41.562664644 +0100 -@@ -124,9 +124,17 @@ - #define TCPERRNO errno - #define TCPERR_WOULDBLOCK EWOULDBLOCK - #define TCPERR_AGAIN EAGAIN -- #define sock_init() 0 -- #define sock_deinit() -- #define soclose(h) close(h) -+ #ifdef AMIGA -+ /* AmigaOS 3: open bsdsocket.library via amiga/bsdsock.c */ -+ #include "amiga/bsdsock.h" -+ #define sock_init() amiga_sock_init() -+ #define sock_deinit() amiga_sock_cleanup() -+ #define soclose(h) CloseSocket(h) -+ #else -+ #define sock_init() 0 -+ #define sock_deinit() -+ #define soclose(h) close(h) -+ #endif - #endif - - #if !defined(WIN32) -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/iptools.c binkd/iptools.c ---- binkd_pgul/iptools.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/iptools.c 2026-04-26 10:33:19.517261538 +0100 -@@ -20,6 +20,10 @@ - #include - #endif - -+#ifdef AMIGA -+#include -+#endif -+ - #include "sys.h" - #include "Config.h" - #include "iphdr.h" -@@ -40,7 +44,11 @@ - int arg; - - arg = 1; -+#if defined(AMIGA) -+ if (ioctl (s, FIONBIO, (char *) &arg) < 0) -+#else - if (ioctl (s, FIONBIO, (char *) &arg, sizeof arg) < 0) -+#endif - Log (1, "ioctl (FIONBIO): %s", TCPERR ()); - - #elif defined(WIN32) -@@ -53,12 +61,49 @@ - #endif - #endif - --#if defined(UNIX) || defined(EMX) || defined(AMIGA) -+#if defined(UNIX) || defined(EMX) /* NOT AMIGA: sockets are not AmigaDOS fds */ - if (fcntl (s, F_SETFL, O_NONBLOCK) == -1) - Log (1, "fcntl: %s", strerror (errno)); - #endif - } - -+#if defined(AMIGA) -+void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf) -+{ -+ /* Disable Nagle algorithm: BinkP mixes small control messages with data -+ * Without TCP_NODELAY each small message waits up to 200ms (Nagle delay), -+ * making sessions 2-5x slower than other BinkP implementations -+ * All other BinkP mailers (BinkIT, Argus, etc.) set this explicitly */ -+ -+ if (tcpdelay) -+ { -+ int nodelay = tcpdelay; -+ -+ if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)) < 0) -+ Log (4, "setsockopt TCP_NODELAY: %s", TCPERR()); -+ } -+ -+ /* ixnet default TCP buffers are very small (~8KB). Increase them so the -+ * sender does not stall waiting for ACK after every small burst */ -+ -+ if (so_sndbuf) -+ { -+ int sndbuf = so_sndbuf; -+ -+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, sizeof(sndbuf)) < 0) -+ Log (5, "setsockopt SO_SNDBUF: %s", TCPERR()); -+ } -+ -+ if (so_rcvbuf) -+ { -+ int rcvbuf = so_rcvbuf; -+ -+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&rcvbuf, sizeof(rcvbuf)) < 0) -+ Log (5, "setsockopt SO_RCVBUF: %s", TCPERR()); -+ } -+} -+#endif -+ - /* - * Find the appropriate port string to be used. - * Find_port ("") will return binkp's port from /etc/services or even -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/iptools.h binkd/iptools.h ---- binkd_pgul/iptools.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/iptools.h 2026-04-26 09:26:42.811498024 +0100 -@@ -11,11 +11,21 @@ - * (at your option) any later version. See COPYING. - */ - -+#if defined(AMIGA) -+#include -+#include -+#include -+#endif -+ - /* - * Sets non-blocking mode for a given socket - */ - void setsockopts (SOCKET s); - -+#if defined(AMIGA) -+void setsockopts_amiga(SOCKET s, int tcpdelay, int so_sndbuf, int so_rcvbuf); -+#endif -+ - /* - * Find the port number (in the host byte order) by a port number string or - * a service name. Find_port ("") will return binkp's port from -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/decompress.c binkd/misc/decompress.c ---- binkd_pgul/misc/decompress.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/decompress.c 2026-04-26 13:39:53.974576140 +0100 -@@ -0,0 +1,188 @@ -+/* -+ * decompress.c -- Decompress FTN bundle archives from an inbound directory -+ * -+ * decompress.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include "portable.h" /* Canonical portable layer */ -+#include -+ -+#define MAX_CMD 1100 -+ -+/* Archive format codes detected by magic bytes */ -+#define FMT_UNKNOWN 0 -+#define FMT_ZIP 1 -+#define FMT_LZH 2 -+#define FMT_ARC 3 -+ -+/* detect_format -- Read first bytes and identify archive type */ -+static int detect_format(const char *path) -+{ -+ unsigned char buf[8]; -+ FILE *f = fopen(path, "rb"); -+ int n; -+ -+ if (!f) -+ return FMT_UNKNOWN; -+ -+ n = (int)fread(buf, 1, sizeof(buf), f); -+ -+ fclose(f); -+ -+ if (n < 2) -+ return FMT_UNKNOWN; -+ -+ /* ZIP: PK\x03\x04 */ -+ if (n >= 4 && buf[0] == 0x50 && buf[1] == 0x4B && buf[2] == 0x03 && buf[3] == 0x04) -+ return FMT_ZIP; -+ -+ /* LZH: offset 2 = '-', offset 3 = 'l', offset 6 = '-' (e.g. -lh5-) */ -+ if (n >= 7 && buf[2] == '-' && buf[3] == 'l' && buf[6] == '-') -+ return FMT_LZH; -+ -+ /* ARC: 0x1A followed by type byte 1..18 */ -+ if (buf[0] == 0x1A && buf[1] >= 1 && buf[1] <= 18) -+ return FMT_ARC; -+ -+ return FMT_UNKNOWN; -+} -+ -+/* is_ftn_bundle -- Check filename has an FTN day-of-week extension */ -+static int is_ftn_bundle(const char *filename) -+{ -+ const char *p; -+ -+ for (p = filename; *p; p++) -+ { -+ if (p[0] == '.' && p[1] && p[2]) -+ { -+ char a = (char)tolower((unsigned char)p[1]); -+ char b = (char)tolower((unsigned char)p[2]); -+ -+ if ((a == 's' && b == 'u') || (a == 'm' && b == 'o') || -+ (a == 't' && b == 'u') || (a == 'w' && b == 'e') || -+ (a == 't' && b == 'h') || (a == 'f' && b == 'r') || -+ (a == 's' && b == 'a')) -+ { -+ /* .TH .TH0 .TH.001 */ -+ if (p[3] == '\0' || isdigit((unsigned char)p[3]) || p[3] == '.') -+ return 1; -+ } -+ } -+ } -+ return 0; -+} -+ -+/* delete_file -- Remove a file, portable */ -+static void delete_file(const char *path) -+{ -+#if defined(AMIGA) -+ DeleteFile((STRPTR)path); -+#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -+ DeleteFileA(path); -+#else -+ remove(path); -+#endif -+} -+ -+/* run_decompressor -- Invoke external tool for the detected format -+ * outdir must end without trailing slash on POSIX; lha needs trailing / */ -+static int run_decompressor(int fmt, const char *path, const char *outdir) -+{ -+ char cmd[MAX_CMD]; -+ -+ switch (fmt) -+ { -+ case FMT_ZIP: -+#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -+ snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); -+#else -+ snprintf(cmd, MAX_CMD, "unzip -o \"%s\" -d \"%s\"", path, outdir); -+#endif -+ break; -+ -+ case FMT_LZH: -+#ifdef AMIGA -+ snprintf(cmd, MAX_CMD, "lha x \"%s\" \"%s/\"", path, outdir); -+#else -+ snprintf(cmd, MAX_CMD, "lha e \"%s\" \"%s/\"", path, outdir); -+#endif -+ break; -+ -+ case FMT_ARC: -+#if defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -+ snprintf(cmd, MAX_CMD, "arc x \"%s\" \"%s\"", path, outdir); -+#else -+ snprintf(cmd, MAX_CMD, "cd \"%s\" && arc x \"%s\"", outdir, path); -+#endif -+ break; -+ -+ default: -+ return -1; -+ } -+ -+ return system(cmd); -+} -+ -+int main(int argc, char *argv[]) -+{ -+ DIR *dp; -+ struct dirent *entry; -+ char path[MAXPATHLEN]; -+ const char *inbound; -+ const char *outdir; -+ int total = 0; -+ int ok = 0; -+ -+ if (argc < 3) -+ { -+ fprintf(stderr, -+ "Usage: decompress \n" -+ "Detects format by magic bytes (ZIP/LZH/ARC).\n" -+ "Processes FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA).\n"); -+ -+ return 1; -+ } -+ -+ inbound = argv[1]; -+ outdir = argv[2]; -+ -+ dp = opendir(inbound); -+ -+ if (dp == NULL) -+ return 1; -+ -+ while ((entry = readdir(dp)) != NULL) -+ { -+ int fmt; -+ -+ /* Skip . and .. (AmigaOS readdir does not return these, POSIX does) */ -+ if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) -+ continue; -+ -+ if (!is_ftn_bundle(entry->d_name)) -+ continue; -+ -+ path_join(path, MAXPATHLEN, inbound, entry->d_name); -+ -+ fmt = detect_format(path); -+ -+ if (fmt == FMT_UNKNOWN) -+ continue; -+ -+ total++; -+ -+ if (run_decompressor(fmt, path, outdir) == 0) -+ { -+ delete_file(path); -+ ok++; -+ } -+ } -+ -+ closedir(dp); -+ -+ return (total == 0 || ok == total) ? 0 : 1; -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/decompress.txt binkd/misc/decompress.txt ---- binkd_pgul/misc/decompress.txt 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/decompress.txt 2026-04-26 13:44:08.749250093 +0100 -@@ -0,0 +1,36 @@ -+decompress -- Decompress FTN bundle archives -+ -+USAGE: -+ decompress -+ -+DESCRIPTION: -+ Scans the inbound directory for FTN day-of-week bundle archives and -+ decompresses them to the output directory. -+ -+ Recognized archive formats (by magic bytes, not extension): -+ - ZIP files -+ - LZH files -+ - ARC files -+ -+ Recognized bundle extensions (case-insensitive): -+ - .SU - Sunday bundle -+ - .MO - Monday bundle -+ - .TU - Tuesday bundle -+ - .WE - Wednesday bundle -+ - .TH - Thursday bundle -+ - .FR - Friday bundle -+ - .SA - Saturday bundle -+ -+ Also handles renamed bundles (duplicates): -+ - ABCD1234.SU -+ - ABCD1234.SU.001 -+ - ABCD1234.SU.002 -+ -+EXAMPLES: -+ decompress Work:Inbound Work:Unpacked -+ decompress /var/spool/binkd/inbound /tmp/unpacked -+ decompress C:\Binkd\Inbound C:\Binkd\Unpacked -+ exec "work:fido/decompress work:fido/inbound work:fido/inbound" *.su? *.mo? *.tu? *.we? *.th? *.fr? *.sa? *.SU? *.MO? *.TU? *.WE? *.TH? *.FR? *.SA? -+ -+CONFIGURATION FILE: -+ None. All parameters are command-line arguments. -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/freq.c binkd/misc/freq.c ---- binkd_pgul/misc/freq.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/freq.c 2026-04-26 13:39:53.974576140 +0100 -@@ -0,0 +1,220 @@ -+/* -+ * freq.c -- Append a file-request entry to an outbound .req / .clo pair -+ * -+ * freq.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include "portable.h" /* Canonical portable layer */ -+ -+#define FREQ_MAX_PATH (MAXPATHLEN + 1) -+ -+/* build_aso_paths -- ASO flat layout */ -+static int build_aso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) -+{ -+ char *dot; -+ -+ if (mkdir_recursive(outbound) < 0 && !path_exists(outbound)) -+ return -1; -+ -+ snprintf(req_path, (size_t)pathsize, "%s/%u.%u.%u.%u.req", outbound, zone, net, node, point); -+ safe_strncpy(clo_path, req_path, pathsize); -+ dot = strrchr(clo_path, '.'); -+ -+ if (dot) -+ strcpy(dot, ".clo"); -+ -+ return 0; -+} -+ -+/* build_bso_paths -- BSO BinkleyStyle layout (lowercase hex) */ -+static int build_bso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) -+{ -+ char zone_dir[FREQ_MAX_PATH]; -+ char node_dir[FREQ_MAX_PATH]; -+ -+ /* Zone dir: .0ZZ (lowercase hex) */ -+ snprintf(zone_dir, sizeof(zone_dir), "%s.%03x", outbound, zone); -+ str_tolower(zone_dir); -+ -+ if (mkdir_recursive(zone_dir) < 0 && !path_exists(zone_dir)) -+ return -1; -+ -+ if (point == 0) -+ { -+ snprintf(req_path, (size_t)pathsize, "%s/%04x%04x.req", zone_dir, net, node); -+ snprintf(clo_path, (size_t)pathsize, "%s/%04x%04x.clo", zone_dir, net, node); -+ } -+ else -+ { -+ snprintf(node_dir, sizeof(node_dir), "%s/%04x%04x.pnt", zone_dir, net, node); -+ -+ if (mkdir_recursive(node_dir) < 0 && !path_exists(node_dir)) -+ return -1; -+ -+ snprintf(req_path, (size_t)pathsize, "%s/%08x.req", node_dir, point); -+ snprintf(clo_path, (size_t)pathsize, "%s/%08x.clo", node_dir, point); -+ } -+ -+ return 0; -+} -+ -+int main(int argc, char *argv[]) -+{ -+ unsigned int zone, net, node, point; -+ char addr_copy[128]; -+ char req_path[FREQ_MAX_PATH]; -+ char clo_path[FREQ_MAX_PATH]; -+ char abs_outbound[FREQ_MAX_PATH]; -+ FILE *f; -+ const char *outbound; -+ const char *arg_outbound; -+ const char *arg_addr; -+ const char *password = NULL; /* --password → !pass suffix */ -+ long newer_than = 0; /* --newer-than → +ts suffix */ -+ int update = 0; /* --update → U suffix */ -+ int use_bso = 0; -+ int argi = 1; -+ int nfiles = 0; -+ -+ zone = net = node = point = 0; -+ -+ /* Parse flags -- All optional, order-independent, before positional args */ -+ while (argi < argc && argv[argi][0] == '-' && argv[argi][1] == '-') -+ { -+ if (strcmp(argv[argi], "--bso") == 0) -+ { -+ use_bso = 1; -+ argi++; -+ } -+ else if (strcmp(argv[argi], "--aso") == 0) -+ { -+ use_bso = 0; -+ argi++; -+ } -+ else if (strcmp(argv[argi], "--update") == 0) -+ { -+ update = 1; -+ argi++; -+ } -+ else if (strcmp(argv[argi], "--password") == 0 && argi + 1 < argc) -+ { -+ password = argv[++argi]; -+ argi++; -+ } -+ else if (strcmp(argv[argi], "--newer-than") == 0 && argi + 1 < argc) -+ { -+ newer_than = atol(argv[++argi]); -+ argi++; -+ } -+ else -+ break; /* unknown flag — stop, treat rest as positional */ -+ } -+ -+ if (argc - argi < 3) -+ { -+ fprintf(stderr, -+ "Usage: freq [options] Z:N/NODE[.POINT] [...]\n" -+ "Options:\n" -+ " --aso flat layout (default): outbound/Z.N.NODE.POINT.req\n" -+ " --bso BSO layout: outbound.0ZZ/nnnnnnnn[.pnt/pppppppp].req\n" -+ " --password append !pw to each request line\n" -+ " --newer-than append + (request if newer)\n" -+ " --update append U flag (update request)\n" -+ "Multiple filenames can be listed after the address.\n"); -+ return 1; -+ } -+ -+ arg_outbound = argv[argi++]; -+ arg_addr = argv[argi++]; -+ -+ /* Remaining args are filenames */ -+ -+ make_abs_path(arg_outbound, abs_outbound, (int)sizeof(abs_outbound)); -+ outbound = abs_outbound; -+ -+ safe_strncpy(addr_copy, arg_addr, (int)sizeof(addr_copy)); -+ -+ if (sscanf(addr_copy, "%u:%u/%u.%u", &zone, &net, &node, &point) < 3 && sscanf(addr_copy, "%u:%u/%u", &zone, &net, &node) < 3) -+ { -+ fprintf(stderr, "freq: invalid address: %s\n", arg_addr); -+ return 1; -+ } -+ -+ if (use_bso) -+ { -+ if (build_bso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) -+ { -+ fprintf(stderr, "freq: cannot create BSO dirs under: %s\n", outbound); -+ return 1; -+ } -+ } -+ else -+ { -+ if (build_aso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) -+ { -+ fprintf(stderr, "freq: cannot create outbound dir: %s\n", outbound); -+ return 1; -+ } -+ } -+ -+ /* Append all filenames to .req */ -+ f = fopen(req_path, "a"); -+ -+ if (!f) -+ { -+ fprintf(stderr, "freq: cannot open REQ: %s\n", req_path); -+ return 1; -+ } -+ -+ for (; argi < argc; argi++) -+ { -+ const char *fname = argv[argi]; -+ -+ /* Build request line: filename [!password] [+timestamp] [U] */ -+ fprintf(f, "%s", fname); -+ -+ if (password && password[0]) -+ fprintf(f, " !%s", password); -+ -+ if (newer_than > 0) -+ fprintf(f, " +%ld", newer_than); -+ -+ if (update) -+ fprintf(f, " U"); -+ -+ fprintf(f, "\r\n"); -+ nfiles++; -+ } -+ -+ fclose(f); -+ -+ if (nfiles == 0) -+ { -+ fprintf(stderr, "freq: no filenames specified\n"); -+ return 1; -+ } -+ -+ /* Append .req full path to .clo (once per invocation) */ -+ f = fopen(clo_path, "a"); -+ -+ if (!f) -+ { -+ fprintf(stderr, "freq: cannot open CLO: %s\n", clo_path); -+ return 1; -+ } -+ -+ fprintf(f, "%s\r\n", req_path); -+ fclose(f); -+ -+ printf("freq (%s): node %u:%u/%u", use_bso ? "bso" : "aso", zone, net, node); -+ -+ if (point) -+ printf(".%u", point); -+ -+ printf(" %d file(s)\n REQ : %s\n CLO : %s\n", nfiles, req_path, clo_path); -+ -+ return 0; -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/freq.txt binkd/misc/freq.txt ---- binkd_pgul/misc/freq.txt 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/freq.txt 2026-04-26 13:33:14.698749181 +0100 -@@ -0,0 +1,37 @@ -+freq -- Create .req and .clo request files for FidoNet -+ -+USAGE: -+ freq [options] [...] -+ -+DESCRIPTION: -+ Creates file request (.req) and close (.clo) files in the outbound -+ directory using FidoNet 4D/5D addressing. -+ -+ Address format: -+ Zone:Net/Node - 4D address (e.g., 39:190/101) -+ Zone:Net/Node.Point - 5D address with point (e.g., 39:190/101.1) -+ -+ Multiple files can be specified to create multiple request lines. -+ -+OPTIONS: -+ --aso Amiga Style Outbound (default) - flat layout -+ Format: /Z.N.NODE.POINT.req -+ -+ --bso Binkley Style Outbound - hex directory layout -+ No point: .0ZZ/nnnnnnnn.req -+ Point: .0ZZ/nnnnnnnn.pnt/pppppppp.req -+ -+ --password Append !pw suffix to each request line -+ --newer-than Append + (request only if file is newer) -+ --update Append U flag (update request, checks TRANX) -+ -+EXAMPLES: -+ freq Work:Outbound 39:190/101 file.lha -+ freq --aso Work:Outbound 39:190/101.1 readme.txt -+ freq --bso /var/spool/binkd/outbound 2:123/456 door.zip -+ freq --bso C:\Binkd\Outbound 1:100/200 update.lzh -+ freq --password secret --newer-than 1234567890 Work:Outbound 39:190/101 file.zip -+ freq --update Work:Outbound 39:190/101 file1.zip file2.zip file3.zip -+ -+CONFIGURATION FILE: -+ None. All parameters are command-line arguments. -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/nodelist.c binkd/misc/nodelist.c ---- binkd_pgul/misc/nodelist.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/nodelist.c 2026-04-26 13:39:53.974576140 +0100 -@@ -0,0 +1,200 @@ -+/* -+ * nodelist.c -- FidoNet nodelist compiler for binkd -+ * -+ * nodelist.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include "portable.h" /* Canonical portable layer */ -+#include -+#include -+#include -+#include -+ -+#define MAX_FIELDS 20 -+#define MAX_VAL 256 -+ -+static int split_fields(char *buf, char **fields, int maxfields) -+{ -+ int n = 0; -+ char *p = buf; -+ -+ while (n < maxfields) -+ { -+ fields[n++] = p; -+ p = strchr(p, ','); -+ -+ if (!p) -+ break; -+ -+ *p++ = '\0'; -+ } -+ -+ return n; -+} -+ -+/* Case-insensitive flag search. Returns 1 if found; fills val if present */ -+static int find_flag(char **fields, int nfields, int start, const char *flag, char *val, int vsize) -+{ -+ int flen = (int)strlen(flag); -+ int i; -+ -+ for (i = start; i < nfields; i++) -+ { -+ char *f = fields[i]; -+ int j; -+ -+ for (j = 0; j < flen; j++) -+ { -+ if (toupper((unsigned char)f[j]) != toupper((unsigned char)flag[j])) -+ break; -+ } -+ -+ if (j == flen) -+ { -+ if (val && vsize > 0) -+ { -+ if (f[flen] == ':') -+ { -+ strncpy(val, f + flen + 1, (size_t)(vsize - 1)); -+ val[vsize - 1] = '\0'; -+ } -+ else -+ val[0] = '\0'; -+ } -+ return 1; -+ } -+ } -+ return 0; -+} -+ -+int main(int argc, char *argv[]) -+{ -+ const char *nl_file; -+ const char *domain; -+ FILE *in; -+ FILE *out; -+ char buf[MAX_LINE]; -+ char *fields[MAX_FIELDS]; -+ int nf; -+ int cur_zone; -+ int cur_net; -+ long count; -+ -+ cur_zone = 0; -+ cur_net = 0; -+ count = 0; -+ -+ if (argc < 3) -+ { -+ fprintf(stderr, -+ "Usage: nodelist []\n"); -+ return 1; -+ } -+ -+ nl_file = argv[1]; -+ domain = argv[2]; -+ out = (argc >= 4) ? fopen(argv[3], "w") : stdout; -+ -+ if (!out) -+ { -+ perror(argv[3]); -+ return 1; -+ } -+ -+ in = fopen(nl_file, "r"); -+ -+ if (!in) -+ { -+ perror(nl_file); -+ -+ if (out != stdout) -+ fclose(out); -+ -+ return 1; -+ } -+ -+ while (fgets(buf, sizeof(buf), in)) -+ { -+ char type[32]; -+ char ibn_port[32]; -+ char ina_host[MAX_VAL]; -+ int node_num; -+ int port; -+ int flags_start; -+ -+ str_trim(buf); -+ -+ if (!buf[0] || buf[0] == ';') -+ continue; -+ -+ nf = split_fields(buf, fields, MAX_FIELDS); -+ -+ if (nf < 2) -+ continue; -+ -+ if (fields[0][0] == '\0') -+ { -+ /* Line started with comma -- plain Node entry */ -+ strcpy(type, "Node"); -+ node_num = atoi(fields[1]); -+ flags_start = 7; -+ } -+ else -+ { -+ strncpy(type, fields[0], sizeof(type) - 1); -+ type[sizeof(type) - 1] = '\0'; -+ node_num = atoi(fields[1]); -+ flags_start = 7; -+ } -+ -+ /* Update zone / net context */ -+ if (!strcmp(type, "Zone") || !strcmp(type, "ZONE")) -+ { -+ cur_zone = node_num; -+ cur_net = node_num; -+ continue; -+ } -+ -+ if (!strcmp(type, "Region") || !strcmp(type, "REGION")) -+ { -+ cur_net = node_num; -+ continue; -+ } -+ -+ if (!strcmp(type, "Host") || !strcmp(type, "HOST")) -+ cur_net = node_num; -+ -+ /* Skip unusable types */ -+ if (!strcmp(type, "Pvt") || !strcmp(type, "PVT") || !strcmp(type, "Hold") || !strcmp(type, "HOLD") || !strcmp(type, "Down") || !strcmp(type, "DOWN") || !strcmp(type, "Boss") || !strcmp(type, "BOSS")) -+ continue; -+ -+ /* Must have IBN flag */ -+ if (!find_flag(fields, nf, flags_start, "IBN", ibn_port, (int)sizeof(ibn_port))) -+ continue; -+ -+ /* Need INA:hostname */ -+ ina_host[0] = '\0'; -+ find_flag(fields, nf, flags_start, "INA", ina_host, (int)sizeof(ina_host)); -+ -+ if (!ina_host[0]) -+ continue; -+ -+ port = (ibn_port[0] && atoi(ibn_port) > 0) ? atoi(ibn_port) : 24554; -+ -+ fprintf(out, "node %d:%d/%d@%s %s:%d -\n", cur_zone, cur_net, node_num, domain, ina_host, port); -+ -+ count++; -+ } -+ -+ fclose(in); -+ -+ if (out != stdout) -+ fclose(out); -+ -+ fprintf(stderr, "nodelist: %ld BinkP node(s) found\n", count); -+ -+ return 0; -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/nodelist.txt binkd/misc/nodelist.txt ---- binkd_pgul/misc/nodelist.txt 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/nodelist.txt 2026-04-26 13:32:27.866048972 +0100 -@@ -0,0 +1,32 @@ -+nodelist -- Compile FidoNet nodelist to binkd.conf format -+ -+USAGE: -+ nodelist [] -+ -+DESCRIPTION: -+ Reads a FidoNet nodelist and outputs binkd.conf compatible -+ "node" configuration lines. -+ -+ Arguments: -+ nodelist_file Path to the FidoNet nodelist file -+ domain Domain name for the node entries (e.g., fidonet) -+ output_file Optional output file (default: stdout) -+ -+ Extracts the following flags: -+ IBN[:port] - BinkP protocol flag (Internet BinkP Node) -+ INA:hostname - IP hostname/address -+ -+ Output format: -+ node
@ : - -+ -+ The nodelist format is comma-separated: -+ [type,]node_num,name,city,sysop,phone,baud,flag1,flag2,... -+ type = Zone, Region, Host, Hub, Pvt, Hold, Down, Boss (empty = Node) -+ -+EXAMPLES: -+ nodelist Work:Fido/nodelist.123 fidonet > binkd-nodes.conf -+ nodelist /etc/fido/nodelist.456 fidonet >> binkd.conf -+ nodelist C:\Fido\NODELIST.001 fidonet C:\Fido\nodes.conf -+ -+CONFIGURATION FILE: -+ None. All parameters are command-line arguments. -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/portable.c binkd/misc/portable.c ---- binkd_pgul/misc/portable.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/portable.c 2026-04-26 13:04:59.214902887 +0100 -@@ -0,0 +1,402 @@ -+/* -+ * portable.c -- Shared implementations for misc tools portable layer -+ * -+ * portable.c is a part of binkd project -+ * -+ * This file provides implementations for common utility functions -+ * used across binkd misc tools. Include portable.h for declarations -+ * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ * -+ */ -+ -+#include "portable.h" -+#include -+ -+/* trim_nl -- Strip trailing newline (\n and \r) from string */ -+void trim_nl(char *s) -+{ -+ char *p = strchr(s, '\n'); -+ -+ if (p) -+ *p = '\0'; -+ -+ p = strchr(s, '\r'); -+ -+ if (p) -+ *p = '\0'; -+} -+ -+/* str_trim -- Strip trailing whitespace (space, \r, \n) from string */ -+void str_trim(char *s) -+{ -+ int n = (int)strlen(s); -+ -+ while (n > 0 && (s[n - 1] == '\r' || s[n - 1] == '\n' || s[n - 1] == ' ')) -+ s[--n] = '\0'; -+} -+ -+/* str_upper -- Convert string to uppercase in-place */ -+void str_upper(char *s) -+{ -+ while (*s) -+ { -+ *s = (char)toupper((unsigned char)*s); -+ s++; -+ } -+} -+ -+/* str_tolower -- Convert string to lowercase in-place */ -+void str_tolower(char *s) -+{ -+ for (; *s; s++) -+ { -+ if (*s >= 'A' && *s <= 'Z') -+ *s = (char)(*s + ('a' - 'A')); -+ } -+} -+ -+/* skip_ws -- Skip leading whitespace */ -+char *skip_ws(char *s) -+{ -+ while (*s == ' ' || *s == '\t') -+ s++; -+ -+ return s; -+} -+ -+/* wildmatch -- Portable wildcard match: case-insensitive, supports * and ? */ -+int wildmatch(const char *pat, const char *str) -+{ -+ while (*pat) -+ { -+ if (*pat == '*') -+ { -+ while (*pat == '*') -+ pat++; -+ -+ if (!*pat) -+ return 1; -+ -+ while (*str) -+ { -+ if (wildmatch(pat, str++)) -+ return 1; -+ } -+ -+ return 0; -+ } -+ -+ if (*pat == '?') -+ { -+ if (!*str) -+ return 0; -+ -+ pat++; -+ str++; -+ } -+ else -+ { -+ if (toupper((unsigned char)*pat) != toupper((unsigned char)*str)) -+ return 0; -+ -+ pat++; -+ str++; -+ } -+ } -+ -+ return (*str == '\0') ? 1 : 0; -+} -+ -+/* is_wildcard -- True if name contains * or ? */ -+int is_wildcard(const char *s) -+{ -+ while (*s) -+ { -+ if (*s == '*' || *s == '?') -+ return 1; -+ -+ s++; -+ } -+ -+ return 0; -+} -+ -+/* ensure_dir -- Ensure directory exists, creating if necessary */ -+int ensure_dir(const char *path) -+{ -+ if (path_exists(path)) -+ return 1; -+ -+ return (mkdir_recursive(path) == 0) ? 1 : 0; -+} -+ -+/* copy_file -- Portable binary file copy */ -+int copy_file(const char *src, const char *dst) -+{ -+ FILE *in, *out; -+ char buf[4096]; -+ int n; -+ -+ in = fopen(src, "rb"); -+ -+ if (!in) -+ return 0; -+ -+ out = fopen(dst, "wb"); -+ -+ if (!out) -+ { -+ fclose(in); -+ return 0; -+ } -+ -+ while ((n = (int)fread(buf, 1, sizeof(buf), in)) > 0) -+ fwrite(buf, 1, (size_t)n, out); -+ -+ fclose(out); -+ fclose(in); -+ -+ return 1; -+} -+ -+/* move_file -- Try rename first, fall back to copy+delete */ -+int move_file(const char *src, const char *dst) -+{ -+ remove(dst); -+ -+ if (rename(src, dst) == 0) -+ return 1; -+ -+ if (copy_file(src, dst)) -+ { -+ remove(src); -+ return 1; -+ } -+ -+ return 0; -+} -+ -+/* get_file_size -- Return file size in bytes, or -1 on error */ -+long get_file_size(const char *path) -+{ -+ struct stat st; -+ -+ if (stat(path, &st) == 0) -+ return (long)st.st_size; -+ -+ return -1; -+} -+ -+/* get_file_mtime -- Return Unix mtime of a file, or 0 on error */ -+long get_file_mtime(const char *path) -+{ -+ struct stat st; -+ -+ if (stat(path, &st) != 0) -+ return 0; -+ -+ return (long)st.st_mtime; -+} -+ -+/* port_path_exists -- Check if path exists (native per OS) */ -+ -+int port_path_exists(const char *p) -+{ -+#ifdef AMIGA -+ BPTR l = Lock((STRPTR)p, ACCESS_READ); -+ -+ if (l) -+ { -+ UnLock(l); -+ return 1; -+ } -+ -+ return 0; -+#else -+ struct stat st; -+ return (stat(p, &st) == 0) ? 1 : 0; -+#endif -+} -+ -+/* port_mkdir_one -- Create single directory (native per OS) */ -+int port_mkdir_one(const char *p) -+{ -+#ifdef AMIGA -+ BPTR l = CreateDir((STRPTR)p); -+ -+ if (l) -+ { -+ UnLock(l); -+ return 0; -+ } -+ -+ return -1; -+#else -+ return mkdir(p, 0755); -+#endif -+} -+ -+/* safe_localtime -- Thread-safe localtime, portable across all OS */ -+void safe_localtime(const time_t *t, struct tm *tm) -+{ -+#if defined(AMIGA) || defined(DOS) -+ *tm = *localtime(t); -+#elif defined(WIN32) || defined(__MINGW32__) || defined(VISUALCPP) -+ localtime_s(tm, t); -+#else -+ localtime_r(t, tm); -+#endif -+} -+ -+/* mkdir_recursive -- Create full path, making all missing components */ -+int mkdir_recursive(const char *path) -+{ -+ char tmp[MP_MAXPATH]; -+ char *p; -+ int len; -+ -+ if (!path || !path[0]) -+ return -1; -+ -+ strncpy(tmp, path, MP_MAXPATH - 1); -+ tmp[MP_MAXPATH - 1] = '\0'; -+ -+ len = (int)strlen(tmp); -+ -+ /* Strip trailing slash */ -+ while (len > 1 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\')) -+ tmp[--len] = '\0'; -+ -+ /* Walk every '/' component and create missing dirs */ -+ for (p = tmp + 1; *p; p++) -+ { -+ if (*p == '/' || *p == '\\') -+ { -+ *p = '\0'; -+ -+ if (!path_exists(tmp)) -+ mkdir_one(tmp); /* ignore per-component errors */ -+ -+ *p = '/'; -+ } -+ } -+ -+ /* Create the leaf */ -+ if (!path_exists(tmp)) -+ return mkdir_one(tmp); -+ -+ return 0; -+} -+ -+/* safe_strncpy -- Ctrncpy that always NUL-terminates */ -+void safe_strncpy(char *dst, const char *src, int dstsize) -+{ -+ int len; -+ -+ if (dstsize <= 0) -+ return; -+ -+ len = (int)strlen(src); -+ -+ if (len > dstsize - 1) -+ len = dstsize - 1; -+ -+ memcpy(dst, src, (size_t)len); -+ dst[len] = '\0'; -+} -+ -+/* path_join -- Concatenate base path with sub path */ -+void path_join(char *out, int outsize, const char *base, const char *sub) -+{ -+ int blen; -+ char last; -+ -+ safe_strncpy(out, base, outsize); -+ blen = (int)strlen(out); -+ last = (blen > 0) ? out[blen - 1] : '\0'; -+ -+ if (last != '/' && last != ':' && last != '\\') -+ { -+ if (outsize - 1 - blen > 0) -+ { -+ out[blen] = '/'; -+ out[blen + 1] = '\0'; -+ blen++; -+ } -+ } -+ -+ safe_strncpy(out + blen, sub, outsize - blen); -+} -+ -+/* make_abs_path -- Resolve a possibly-relative path to absolute -+ * Covers AmigaOS, Win32, OS/2, DOS and Unix -+ * Returns 1 on success, 0 on failure (src copied verbatim as fallback) -+ */ -+int make_abs_path(const char *src, char *dst, int dstlen) -+{ -+#ifdef AMIGA -+ BPTR lock = Lock((STRPTR)src, SHARED_LOCK); -+ -+ if (!lock) -+ { -+ safe_strncpy(dst, src, dstlen); -+ return 0; -+ } -+ -+ if (!NameFromLock(lock, (STRPTR)dst, dstlen)) -+ { -+ UnLock(lock); -+ safe_strncpy(dst, src, dstlen); -+ return 0; -+ } -+ -+ UnLock(lock); -+ -+ return 1; -+#elif defined(WIN32) || defined(__MINGW32__) || defined(__WATCOMC__) || defined(VISUALCPP) || defined(OS2) -+ if (_fullpath(dst, src, (size_t)dstlen) != NULL) -+ return 1; -+ -+ safe_strncpy(dst, src, dstlen); -+ return 0; -+#elif defined(DOS) -+ if (src[0] != '\\' && src[1] != ':') -+ { -+ char cwd[MAXPATHLEN + 1]; -+ -+ if (getcwd(cwd, sizeof(cwd)) != NULL) -+ { -+ snprintf(dst, dstlen, "%s\\%s", cwd, src); -+ return 1; -+ } -+ } -+ -+ safe_strncpy(dst, src, dstlen); -+ return 0; -+#else -+ char buf[MAXPATHLEN + 1]; -+ -+ if (realpath(src, buf) != NULL) -+ { -+ safe_strncpy(dst, buf, dstlen); -+ return 1; -+ } -+ -+ if (src[0] != '/') -+ { -+ char cwd[MAXPATHLEN + 1]; -+ -+ if (getcwd(cwd, sizeof(cwd)) != NULL) -+ { -+ snprintf(dst, dstlen, "%s/%s", cwd, src); -+ return 1; -+ } -+ } -+ -+ safe_strncpy(dst, src, dstlen); -+ return 0; -+#endif -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/portable.h binkd/misc/portable.h ---- binkd_pgul/misc/portable.h 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/portable.h 2026-04-26 14:19:41.472724309 +0100 -@@ -0,0 +1,135 @@ -+/* -+ * portable.h -- Portability layer for standalone binkd misc tools -+ * -+ * portable.h is a part of binkd project -+ * -+ * This is the single canonical portable.h; all misc utilities include this -+ * C89 strict. Covers AmigaOS 3, POSIX, Win32, OS/2, DOS -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ * -+ */ -+ -+#ifndef BINKD_PORTABLE_H -+#define BINKD_PORTABLE_H -+ -+/* _POSIX_C_SOURCE for opendir/readdir/localtime_r under -std=c89 -+ * _XOPEN_SOURCE 500 additionally exposes realpath() on glibc */ -+#ifndef AMIGA -+#ifndef _POSIX_C_SOURCE -+#define _POSIX_C_SOURCE 200112L -+#endif -+#ifndef _XOPEN_SOURCE -+#define _XOPEN_SOURCE 500 -+#endif -+#endif -+ -+#include -+#include -+#include -+#include -+#include -+ -+#ifdef AMIGA -+ -+#include -+#include -+#include -+#include -+#include -+#include /* stat() / struct stat via libnix/ADE */ -+#include -+#include "amiga/dirent.h" /* opendir / readdir / closedir */ -+ -+/* snprintf/vsnprintf: ADE/libnix declares them in stdio.h (already included -+ * above via ). The implementation is provided by snprintf.c which -+ * must be linked when building the misc tools. No redeclaration needed */ -+ -+#elif defined(VISUALCPP) -+#include -+#include -+#include -+#include "nt/dirwin32.h" /* opendir/readdir/closedir for MSVC */ -+#elif defined(__MINGW32__) || defined(WIN32) -+#include -+#include /* MinGW provides dirent.h natively */ -+#include -+#include -+#elif defined(OS2) && (defined(IBMC) || defined(__WATCOMC__)) -+#include -+#include -+#include -+#include "os2/dirent.h" /* opendir/readdir/closedir for OS/2 ICC/WC */ -+#elif defined(OS2) -+#include -+#include /* EMX provides dirent.h natively */ -+#include -+#include -+#include -+#elif defined(DOS) -+#include -+#include -+#include "dos/dirent.h" /* opendir/readdir/closedir for DOS/DJGPP */ -+#else /* POSIX / *nix */ -+#include -+#include -+#include -+#include -+#include -+#endif -+ -+#ifndef MAXPATHLEN -+#if defined(_MAX_PATH) -+#define MAXPATHLEN _MAX_PATH -+#elif defined(PATH_MAX) -+#define MAXPATHLEN PATH_MAX -+#else -+#define MAXPATHLEN 1024 -+#endif -+#endif -+ -+/* Generic line buffer size for config files and text processing */ -+#ifndef MAX_LINE -+#define MAX_LINE 1024 -+#endif -+ -+/* path_exists / mkdir_one -- native implementations per OS */ -+int port_path_exists(const char *p); -+int port_mkdir_one(const char *p); -+#define path_exists(p) port_path_exists(p) -+#define mkdir_one(p) port_mkdir_one(p) -+ -+/* safe_localtime -- thread-safe localtime, portable across all OS */ -+void safe_localtime(const time_t *t, struct tm *tm); -+ -+/* mkdir_recursive -- create full path, making all missing components */ -+#define MP_MAXPATH 512 -+int mkdir_recursive(const char *path); -+ -+/* safe_strncpy -- strncpy that always NUL-terminates */ -+void safe_strncpy(char *dst, const char *src, int dstsize); -+ -+/* String utilities */ -+void trim_nl(char *s); -+void str_trim(char *s); -+void str_upper(char *s); -+void str_tolower(char *s); -+char *skip_ws(char *s); -+ -+/* Wildcard matching */ -+int wildmatch(const char *pat, const char *str); -+int is_wildcard(const char *s); -+ -+/* File operations */ -+int ensure_dir(const char *path); -+int copy_file(const char *src, const char *dst); -+int move_file(const char *src, const char *dst); -+long get_file_size(const char *path); -+long get_file_mtime(const char *path); -+ -+/* Path utilities */ -+void path_join(char *out, int outsize, const char *base, const char *sub); -+int make_abs_path(const char *src, char *dst, int dstlen); -+ -+#endif /* BINKD_PORTABLE_H */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/process_tic.c binkd/misc/process_tic.c ---- binkd_pgul/misc/process_tic.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/process_tic.c 2026-04-26 13:39:53.974576140 +0100 -@@ -0,0 +1,552 @@ -+/* -+ * process_tic -- Process FTN .tic files from inbound to filebox -+ * -+ * process_tic.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include "portable.h" /* Canonical portable layer */ -+#include -+ -+static int my_toupper(int c) -+{ -+ if (c >= 'a' && c <= 'z') -+ return c - 'a' + 'A'; -+ -+ return c; -+} -+ -+static int my_strnicmp(const char *a, const char *b, int n) -+{ -+ int i, ca, cb; -+ for (i = 0; i < n; i++) -+ { -+ ca = my_toupper((unsigned char)a[i]); -+ cb = my_toupper((unsigned char)b[i]); -+ -+ if (ca != cb) -+ return ca - cb; -+ -+ if (ca == 0) -+ return 0; -+ } -+ -+ return 0; -+} -+ -+static int parse_file_field(char *line, char *out, int outsize) -+{ -+ char *p; -+ char *end; -+ int len; -+ -+ p = skip_ws(line); -+ -+ if (my_strnicmp(p, "File", 4) != 0) -+ return 0; -+ -+ p += 4; -+ -+ if (*p != ' ' && *p != '\t') -+ return 0; -+ -+ p = skip_ws(p); -+ trim_nl(p); -+ end = p; -+ -+ while (*end && *end != ' ' && *end != '\t') -+ end++; -+ -+ *end = '\0'; -+ -+ len = (int)strlen(p); -+ -+ if (len <= 0 || len >= outsize) -+ return 0; -+ -+ strncpy(out, p, outsize - 1); -+ out[outsize - 1] = '\0'; -+ -+ return 1; -+} -+ -+static int parse_area_field(char *line, char *out, int outsize) -+{ -+ char *p; -+ char *end; -+ int len; -+ -+ p = skip_ws(line); -+ -+ if (my_strnicmp(p, "Area", 4) != 0) -+ return 0; -+ -+ p += 4; -+ -+ if (*p != ' ' && *p != '\t') -+ return 0; -+ -+ p = skip_ws(p); -+ trim_nl(p); -+ end = p; -+ -+ while (*end && *end != ' ' && *end != '\t') -+ end++; -+ -+ *end = '\0'; -+ len = (int)strlen(p); -+ -+ if (len <= 0 || len >= outsize) -+ return 0; -+ -+ strncpy(out, p, outsize - 1); -+ out[outsize - 1] = '\0'; -+ -+ return 1; -+} -+ -+static int parse_origin_field(char *line, char *out, int outsize) -+{ -+ char *p; -+ char *end; -+ int len; -+ -+ p = skip_ws(line); -+ -+ if (my_strnicmp(p, "Origin", 6) != 0) -+ return 0; -+ -+ p += 6; -+ -+ if (*p != ' ' && *p != '\t') -+ return 0; -+ -+ p = skip_ws(p); -+ trim_nl(p); -+ end = p; -+ -+ while (*end && *end != ' ' && *end != '\t') -+ end++; -+ -+ *end = '\0'; -+ len = (int)strlen(p); -+ -+ if (len <= 0 || len >= outsize) -+ return 0; -+ -+ strncpy(out, p, outsize - 1); -+ out[outsize - 1] = '\0'; -+ -+ return 1; -+} -+ -+static int parse_from_field(char *line, char *out, int outsize) -+{ -+ char *p; -+ char *end; -+ int len; -+ -+ p = skip_ws(line); -+ -+ if (my_strnicmp(p, "From", 4) != 0) -+ return 0; -+ -+ p += 4; -+ -+ if (*p != ' ' && *p != '\t') -+ return 0; -+ -+ p = skip_ws(p); -+ trim_nl(p); -+ end = p; -+ -+ while (*end && *end != ' ' && *end != '\t') -+ end++; -+ -+ *end = '\0'; -+ len = (int)strlen(p); -+ -+ if (len <= 0 || len >= outsize) -+ return 0; -+ -+ strncpy(out, p, outsize - 1); -+ out[outsize - 1] = '\0'; -+ -+ return 1; -+} -+ -+static void append_filelist(const char *listpath, const char *file_name, long filesize, const char *dst_path) -+{ -+ FILE *f; -+ time_t t; -+ struct tm tm; -+ char timestamp[32]; -+ -+ if (!listpath || !listpath[0]) -+ return; -+ -+ f = fopen(listpath, "a"); -+ if (!f) -+ return; -+ -+ t = time(NULL); -+ safe_localtime(&t, &tm); -+ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -+ -+ fprintf(f, "%s\t%s\t%ld\t%s\n", timestamp, file_name, filesize, dst_path); -+ fclose(f); -+} -+ -+static void append_newfiles(const char *newprefix, const char *file_name, long filesize, const char *dst_path) -+{ -+ FILE *f; -+ char newpath[MAXPATHLEN]; -+ time_t t; -+ struct tm tm; -+ char datebuf[16]; -+ -+ if (!newprefix || !newprefix[0]) -+ return; -+ -+ t = time(NULL); -+ safe_localtime(&t, &tm); -+ strftime(datebuf, sizeof(datebuf), "%Y%m%d", &tm); -+ -+ ensure_dir(newprefix); -+ path_join(newpath, (int)sizeof(newpath), newprefix, "newfiles-"); -+ strncat(newpath, datebuf, sizeof(newpath) - strlen(newpath) - 1); -+ strncat(newpath, ".txt", sizeof(newpath) - strlen(newpath) - 1); -+ -+ f = fopen(newpath, "a"); -+ -+ if (!f) -+ return; -+ -+ fprintf(f, "%s\t%ld\t%s\n", file_name, filesize, dst_path); -+ -+ fclose(f); -+} -+ -+static void write_ticlog(const char *ticlog, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) -+{ -+ FILE *f; -+ time_t t; -+ struct tm tm; -+ char timestamp[64]; -+ -+ if (!ticlog || !ticlog[0]) -+ return; -+ -+ f = fopen(ticlog, "a"); -+ if (!f) -+ return; -+ -+ t = time(NULL); -+ safe_localtime(&t, &tm); -+ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -+ -+ fprintf(f, "[%s] File: %s\n", timestamp, file_name); -+ fprintf(f, " Area: %s\n", area_name); -+ -+ if (origin_name[0]) -+ fprintf(f, " Origin: %s\n", origin_name); -+ -+ if (from_name[0]) -+ fprintf(f, " From: %s\n", from_name); -+ -+ fprintf(f, " Src: %s\n", src_path); -+ fprintf(f, " To: %s\n", dst_path); -+ fprintf(f, "\n"); -+ -+ fclose(f); -+} -+ -+static void write_log(const char *logfile, const char *file_name, const char *area_name, const char *origin_name, const char *from_name, const char *src_path, const char *dst_path) -+{ -+ FILE *f; -+ time_t t; -+ struct tm tm; -+ char timestamp[64]; -+ -+ if (!logfile || !logfile[0]) -+ return; -+ -+ f = fopen(logfile, "a"); -+ if (!f) -+ return; -+ -+ t = time(NULL); -+ safe_localtime(&t, &tm); -+ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -+ -+ fprintf(f, "[%s] File: %s\n", timestamp, file_name); -+ fprintf(f, " Area: %s\n", area_name); -+ -+ if (origin_name[0]) -+ fprintf(f, " Origin: %s\n", origin_name); -+ -+ if (from_name[0]) -+ fprintf(f, " From: %s\n", from_name); -+ -+ fprintf(f, " Src: %s\n", src_path); -+ fprintf(f, " To: %s\n", dst_path); -+ fprintf(f, "\n"); -+ fclose(f); -+} -+ -+static void process_one_tic(const char *ticpath, const char *inbound, const char *filebox, int copypublic, const char *pubdir, const char *logfile, const char *filelist, const char *newfiles, const char *ticlog) -+{ -+ FILE *f; -+ char line[MAX_LINE]; -+ char file_name[MAXPATHLEN]; -+ char area_name[MAXPATHLEN]; -+ char origin_name[MAXPATHLEN]; -+ char from_name[MAXPATHLEN]; -+ char src_path[MAXPATHLEN]; -+ char area_dir[MAXPATHLEN]; -+ char dst_path[MAXPATHLEN]; -+ -+ file_name[0] = '\0'; -+ area_name[0] = '\0'; -+ origin_name[0] = '\0'; -+ from_name[0] = '\0'; -+ long fsize = 0; -+ -+ f = fopen(ticpath, "r"); -+ -+ if (!f) -+ return; -+ -+ while (fgets(line, sizeof(line), f)) -+ { -+ if (!file_name[0]) -+ parse_file_field(line, file_name, sizeof(file_name)); -+ -+ if (!area_name[0]) -+ parse_area_field(line, area_name, sizeof(area_name)); -+ -+ if (!origin_name[0]) -+ parse_origin_field(line, origin_name, sizeof(origin_name)); -+ -+ if (!from_name[0]) -+ parse_from_field(line, from_name, sizeof(from_name)); -+ } -+ -+ fclose(f); -+ -+ if (!file_name[0] || !area_name[0]) -+ return; -+ -+ path_join(src_path, sizeof(src_path), inbound, file_name); -+ path_join(area_dir, sizeof(area_dir), filebox, area_name); -+ path_join(dst_path, sizeof(dst_path), area_dir, file_name); -+ -+ if (!path_exists(src_path)) -+ return; -+ -+ if (!ensure_dir(filebox) || !ensure_dir(area_dir)) -+ return; -+ -+ if (copypublic && pubdir && pubdir[0]) -+ { -+ char pub_dst[MAXPATHLEN]; -+ -+ path_join(pub_dst, sizeof(pub_dst), pubdir, file_name); -+ -+ if (ensure_dir(pubdir)) -+ copy_file(src_path, pub_dst); -+ } -+ -+ fsize = get_file_size(src_path); -+ -+ if (!move_file(src_path, dst_path)) -+ return; -+ -+ write_log(logfile, file_name, area_name, origin_name, from_name, src_path, dst_path); -+ write_ticlog(ticlog, file_name, area_name, origin_name, from_name, src_path, dst_path); -+ append_filelist(filelist, file_name, fsize, dst_path); -+ append_newfiles(newfiles, file_name, fsize, dst_path); -+ -+ remove(ticpath); -+} -+ -+static int is_tic_file(const char *name) -+{ -+ int len = (int)strlen(name); -+ -+ if (len < 5) -+ return 0; -+ -+ return (my_strnicmp(name + len - 4, ".tic", 4) == 0); -+} -+ -+/* Config structure */ -+static struct -+{ -+ char inbound[MAXPATHLEN]; -+ char filebox[MAXPATHLEN]; -+ char pubdir[MAXPATHLEN]; -+ char logfile[MAXPATHLEN]; -+ char filelist[MAXPATHLEN]; -+ char newfiles[MAXPATHLEN]; -+ char ticlog[MAXPATHLEN]; -+ int copypublic; -+} cfg; -+ -+/* Parse configuration file */ -+static int parse_config(const char *conffile) -+{ -+ FILE *f; -+ char line[MAX_LINE]; -+ char *key, *value; -+ -+ memset(&cfg, 0, sizeof(cfg)); -+ -+ f = fopen(conffile, "r"); -+ -+ if (!f) -+ { -+ fprintf(stderr, "process_tic: cannot open config file: %s\n", conffile); -+ return 0; -+ } -+ -+ while (fgets(line, sizeof(line), f)) -+ { -+ trim_nl(line); -+ key = skip_ws(line); -+ -+ /* Skip comments and empty lines */ -+ if (*key == '#' || *key == '\0') -+ continue; -+ -+ /* Find value after key */ -+ value = key; -+ -+ while (*value && *value != ' ' && *value != '\t') -+ value++; -+ -+ if (*value) -+ { -+ *value = '\0'; -+ value = skip_ws(value + 1); -+ } -+ -+ /* Parse key-value pairs */ -+ if (strcmp(key, "inbound") == 0) -+ safe_strncpy(cfg.inbound, value, (int)sizeof(cfg.inbound)); -+ else if (strcmp(key, "filebox") == 0) -+ safe_strncpy(cfg.filebox, value, (int)sizeof(cfg.filebox)); -+ else if (strcmp(key, "pubdir") == 0) -+ { -+ safe_strncpy(cfg.pubdir, value, (int)sizeof(cfg.pubdir)); -+ cfg.copypublic = 1; -+ } -+ else if (strcmp(key, "logfile") == 0) -+ safe_strncpy(cfg.logfile, value, (int)sizeof(cfg.logfile)); -+ else if (strcmp(key, "filelist") == 0) -+ safe_strncpy(cfg.filelist, value, (int)sizeof(cfg.filelist)); -+ else if (strcmp(key, "newfiles") == 0) -+ safe_strncpy(cfg.newfiles, value, (int)sizeof(cfg.newfiles)); -+ else if (strcmp(key, "ticlog") == 0) -+ safe_strncpy(cfg.ticlog, value, (int)sizeof(cfg.ticlog)); -+ } -+ -+ fclose(f); -+ -+ /* Validate required fields */ -+ if (!cfg.inbound[0] || !cfg.filebox[0]) -+ { -+ fprintf(stderr, "process_tic: config file missing required 'inbound' or 'filebox'\n"); -+ return 0; -+ } -+ -+ return 1; -+} -+ -+int main(int argc, char *argv[]) -+{ -+ char inbound[MAXPATHLEN]; -+ char filebox[MAXPATHLEN]; -+ char ticpath[MAXPATHLEN]; -+ char pubdir[MAXPATHLEN]; -+ char logfile[MAXPATHLEN]; -+ char filelist[MAXPATHLEN]; -+ char newfiles[MAXPATHLEN]; -+ char ticlog[MAXPATHLEN]; -+ int copypublic = 0; -+ DIR *dp; -+ struct dirent *de; -+ int found; -+ int i; -+ int use_config = 0; -+ -+ inbound[0] = '\0'; -+ filebox[0] = '\0'; -+ pubdir[0] = '\0'; -+ logfile[0] = '\0'; -+ filelist[0] = '\0'; -+ newfiles[0] = '\0'; -+ ticlog[0] = '\0'; -+ -+ /* Check for --conf option */ -+ for (i = 1; i < argc; i++) -+ { -+ if (strcmp(argv[i], "--conf") == 0 && i + 1 < argc) -+ { -+ if (!parse_config(argv[i + 1])) -+ return 1; -+ -+ use_config = 1; -+ i++; /* Skip config file path */ -+ -+ break; -+ } -+ } -+ -+ if (use_config) -+ { -+ /* Use config file values */ -+ safe_strncpy(inbound, cfg.inbound, (int)sizeof(inbound)); -+ safe_strncpy(filebox, cfg.filebox, (int)sizeof(filebox)); -+ safe_strncpy(pubdir, cfg.pubdir, (int)sizeof(pubdir)); -+ safe_strncpy(logfile, cfg.logfile, (int)sizeof(logfile)); -+ safe_strncpy(filelist, cfg.filelist, (int)sizeof(filelist)); -+ safe_strncpy(newfiles, cfg.newfiles, (int)sizeof(newfiles)); -+ safe_strncpy(ticlog, cfg.ticlog, (int)sizeof(ticlog)); -+ copypublic = cfg.copypublic; -+ } -+ -+ if (!inbound[0] || !filebox[0]) -+ { -+ fprintf(stderr, -+ "Usage: process_tic --conf [*.tic]\n"); -+ -+ return 1; -+ } -+ -+ dp = opendir(inbound); -+ -+ if (!dp) -+ return 1; -+ -+ found = 0; -+ -+ while ((de = readdir(dp)) != NULL) -+ { -+ /* Skip . and .. */ -+ if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) -+ continue; -+ -+ if (!is_tic_file(de->d_name)) -+ continue; -+ -+ path_join(ticpath, sizeof(ticpath), inbound, de->d_name); -+ process_one_tic(ticpath, inbound, filebox, copypublic, pubdir, logfile, filelist, newfiles, ticlog); -+ found++; -+ } -+ -+ closedir(dp); -+ return 0; -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/process_tic.txt binkd/misc/process_tic.txt ---- binkd_pgul/misc/process_tic.txt 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/process_tic.txt 2026-04-26 13:43:48.542112490 +0100 -@@ -0,0 +1,45 @@ -+process_tic -- Process .tic file announcements -+ -+USAGE: -+ process_tic --conf [files.tic...] -+ -+DESCRIPTION: -+ Processes .tic (Ticker announcement) files from the inbound directory -+ and moves/copies files to their destination filebox or public directory. -+ -+ The .tic file is parsed for File, Area, Origin, and From fields. -+ The actual file is moved from inbound to filebox/AreaName/. -+ -+ All settings are read from the configuration file. -+ -+OPTIONS: -+ --conf Configuration file (required) -+ -+CONFIGURATION FILE FORMAT: -+ # Lines starting with # are comments -+ # Blank lines are ignored -+ -+ inbound Inbound directory (required) -+ filebox Filebox destination (required) -+ pubdir Public directory for --copy-public -+ logfile Log file path -+ ticlog TIC processing log -+ filelist File list output -+ newfiles New files list output -+ -+EXAMPLE CONFIG FILE (process_tic.conf): -+ # process_tic.conf - Configuration for TIC processor -+ -+ inbound Work:Inbound -+ filebox Work:Filebox -+ pubdir Work:Public -+ logfile Work:Logs/process_tic.log -+ ticlog Work:Logs/tic.log -+ filelist Work:Filebox/filelist.txt -+ newfiles Work:Filebox/newfiles.txt -+ -+EXAMPLES: -+ process_tic --conf process_tic.conf -+ process_tic --conf process_tic.conf inbound/*.tic -+ -+ exec "process_tic --conf work:fido/process_tic.conf" *.tic *.TIC -\ No hay ningún carácter de nueva línea al final del archivo -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/srifreq.c binkd/misc/srifreq.c ---- binkd_pgul/misc/srifreq.c 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/srifreq.c 2026-04-26 15:01:11.006850708 +0100 -@@ -0,0 +1,1024 @@ -+/* -+ * srifreq.c -- SRIF-compatible file-request server for binkd -+ * -+ * srifreq.c is a part of binkd project -+ * -+ * Copyright (C) 2026 Tanausú M. 39:190/101@amiganet -+ * Licensed under the GNU GPL v2 or later -+ */ -+ -+#include "portable.h" /* Canonical portable layer */ -+#include -+#include -+ -+/* Private directory entry (dynamically allocated list) */ -+typedef struct PrivDir -+{ -+ char path[MAXPATHLEN]; -+ char password[64]; -+ struct PrivDir *next; -+} PrivDir; -+ -+/* Node tracking entry for rate limiting */ -+typedef struct NodeTrack -+{ -+ char aka[256]; /* Node address (4D/5D) */ -+ int files; /* Files downloaded in window */ -+ long bytes; /* Bytes downloaded in window */ -+ time_t last_time; /* Timestamp of last download */ -+ struct NodeTrack *next; -+} NodeTrack; -+ -+/* Global configuration (filled from --conf file) */ -+typedef struct -+{ -+ char pubdir[MAXPATHLEN]; -+ char logfile[MAXPATHLEN]; -+ char aliases[MAXPATHLEN]; -+ char trackfile[MAXPATHLEN]; /* Path to tracking file */ -+ int maxfiles; /* Max files per node per window (0=unlimited) */ -+ long maxbytes; /* Max bytes per node per window (0=unlimited) */ -+ long timewindow; /* Time window in seconds (0=no window) */ -+ PrivDir *privdirs; /* Linked list, NULL if none */ -+ NodeTrack *tracking; /* Linked list of tracked nodes */ -+} Config; -+ -+/* Alias table -- Loaded from file at startup */ -+typedef struct -+{ -+ char name[64]; -+ char path[MAXPATHLEN]; -+} Alias; -+ -+/* SRIF parsing */ -+typedef struct -+{ -+ char sysop[128]; -+ char aka[256]; -+ char request_list[MAXPATHLEN]; -+ char response_list[MAXPATHLEN]; -+ char our_aka[128]; -+ char caller_id[64]; /* CallerID: IP or phone of remote */ -+ char password[64]; /* Password: session password */ -+ int time_limit; /* Time: minutes left, -1 = unlimited */ -+ long tranx; /* TRANX: remote local time as Unix ts (hex in SRIF) */ -+ int protected_sess; /* RemoteStatus: 1=PROTECTED, 0=UNPROTECTED */ -+ int listed; /* SystemStatus: 1=LISTED, 0=UNLISTED */ -+ int got_request_list; -+ int got_response_list; -+} SRIF; -+ -+static Alias *g_aliases = NULL; -+static int g_nalias = 0; -+static int g_alias_cap = 0; -+static Config g_conf; -+ -+static void config_init(void) -+{ -+ memset(&g_conf, 0, sizeof(g_conf)); -+ g_conf.privdirs = NULL; -+ g_conf.tracking = NULL; -+ g_conf.maxfiles = 0; /* 0 = unlimited */ -+ g_conf.maxbytes = 0; /* 0 = unlimited */ -+ g_conf.timewindow = 0; /* 0 = no window */ -+} -+ -+static void config_add_private(const char *path, const char *password) -+{ -+ PrivDir *pd = (PrivDir *)malloc(sizeof(PrivDir)); -+ PrivDir *tail; -+ -+ if (!pd) -+ return; -+ -+ safe_strncpy(pd->path, path, (int)sizeof(pd->path)); -+ safe_strncpy(pd->password, password, (int)sizeof(pd->password)); -+ -+ pd->next = NULL; -+ -+ /* Append to tail */ -+ if (!g_conf.privdirs) -+ g_conf.privdirs = pd; -+ else -+ { -+ tail = g_conf.privdirs; -+ -+ while (tail->next) -+ tail = tail->next; -+ -+ tail->next = pd; -+ } -+} -+ -+static void config_free(void) -+{ -+ PrivDir *pd = g_conf.privdirs; -+ NodeTrack *nt = g_conf.tracking; -+ -+ while (pd) -+ { -+ PrivDir *next = pd->next; -+ free(pd); -+ pd = next; -+ } -+ -+ g_conf.privdirs = NULL; -+ -+ while (nt) -+ { -+ NodeTrack *next = nt->next; -+ free(nt); -+ nt = next; -+ } -+ -+ g_conf.tracking = NULL; -+ -+ if (g_aliases) -+ { -+ free(g_aliases); -+ g_aliases = NULL; -+ g_nalias = 0; -+ g_alias_cap = 0; -+ } -+} -+ -+static int load_config(const char *path) -+{ -+ FILE *f; -+ char line[MAX_LINE]; -+ char key[64], val[MAXPATHLEN], pw[64]; -+ int n; -+ -+ f = fopen(path, "r"); -+ -+ if (!f) -+ { -+ fprintf(stderr, "srifreq: cannot open config: %s\n", path); -+ return 0; -+ } -+ -+ while (fgets(line, sizeof(line), f)) -+ { -+ /* Strip trailing whitespace and newlines */ -+ n = (int)strlen(line); -+ -+ while (n > 0 && -+ (line[n - 1] == '\r' || line[n - 1] == '\n' || line[n - 1] == ' ')) -+ line[--n] = '\0'; -+ -+ /* Skip blank and comment lines */ -+ if (!line[0] || line[0] == '#') -+ continue; -+ -+ key[0] = val[0] = pw[0] = '\0'; -+ -+ if (sscanf(line, "%63s %1023s %63s", key, val, pw) < 2) -+ continue; -+ -+ if (strcmp(key, "pubdir") == 0) -+ safe_strncpy(g_conf.pubdir, val, (int)sizeof(g_conf.pubdir)); -+ else if (strcmp(key, "logfile") == 0) -+ safe_strncpy(g_conf.logfile, val, (int)sizeof(g_conf.logfile)); -+ else if (strcmp(key, "aliases") == 0) -+ safe_strncpy(g_conf.aliases, val, (int)sizeof(g_conf.aliases)); -+ else if (strcmp(key, "trackfile") == 0) -+ safe_strncpy(g_conf.trackfile, val, (int)sizeof(g_conf.trackfile)); -+ else if (strcmp(key, "maxfiles") == 0) -+ g_conf.maxfiles = atoi(val); -+ else if (strcmp(key, "maxsize") == 0) -+ g_conf.maxbytes = atol(val); -+ else if (strcmp(key, "timewindow") == 0) -+ g_conf.timewindow = atol(val); -+ else if (strcmp(key, "private") == 0 && pw[0]) -+ config_add_private(val, pw); -+ } -+ -+ fclose(f); -+ -+ return 1; -+} -+ -+/* tracking_load -- Load node tracking data from file */ -+static void tracking_load(void) -+{ -+ FILE *f; -+ char line[MAX_LINE]; -+ char aka[256]; -+ int files; -+ long bytes; -+ long timestamp; -+ NodeTrack *nt; -+ time_t now = time(NULL); -+ -+ if (!g_conf.trackfile[0]) -+ return; -+ -+ f = fopen(g_conf.trackfile, "r"); -+ -+ if (!f) -+ return; -+ -+ while (fgets(line, sizeof(line), f)) -+ { -+ str_trim(line); -+ -+ if (!line[0] || line[0] == '#') -+ continue; -+ -+ if (sscanf(line, "%255s %d %ld %ld", aka, &files, &bytes, ×tamp) != 4) -+ continue; -+ -+ /* Skip if outside time window (also reject future/corrupt timestamps) */ -+ if (g_conf.timewindow > 0 && (timestamp > now || (now - timestamp) > g_conf.timewindow)) -+ continue; -+ -+ /* Create new tracking entry */ -+ nt = (NodeTrack *)malloc(sizeof(NodeTrack)); -+ -+ if (!nt) -+ continue; -+ -+ safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); -+ nt->files = files; -+ nt->bytes = bytes; -+ nt->last_time = (time_t)timestamp; -+ nt->next = g_conf.tracking; -+ g_conf.tracking = nt; -+ } -+ -+ fclose(f); -+} -+ -+/* tracking_save -- Save node tracking data to file */ -+static void tracking_save(void) -+{ -+ FILE *f; -+ NodeTrack *nt; -+ -+ if (!g_conf.trackfile[0]) -+ return; -+ -+ f = fopen(g_conf.trackfile, "w"); -+ -+ if (!f) -+ return; -+ -+ fprintf(f, "# srifreq tracking file - Format: AKA files bytes timestamp\n"); -+ -+ for (nt = g_conf.tracking; nt; nt = nt->next) -+ { -+ fprintf(f, "%s %d %ld %ld\n", nt->aka, nt->files, nt->bytes, (long)nt->last_time); -+ } -+ -+ fclose(f); -+} -+ -+/* tracking_find -- Find tracking entry for a node */ -+static NodeTrack *tracking_find(const char *aka) -+{ -+ NodeTrack *nt; -+ -+ for (nt = g_conf.tracking; nt; nt = nt->next) -+ { -+ if (strcmp(nt->aka, aka) == 0) -+ return nt; -+ } -+ -+ return NULL; -+} -+ -+/* tracking_update -- Update tracking after serving a file */ -+static void tracking_update(const char *aka, long filesize) -+{ -+ NodeTrack *nt = tracking_find(aka); -+ time_t now = time(NULL); -+ -+ if (nt) -+ { -+ /* Update existing entry */ -+ nt->files++; -+ nt->bytes += filesize; -+ nt->last_time = now; -+ } -+ else -+ { -+ /* Create new entry */ -+ nt = (NodeTrack *)malloc(sizeof(NodeTrack)); -+ -+ if (nt) -+ { -+ safe_strncpy(nt->aka, aka, (int)sizeof(nt->aka)); -+ nt->files = 1; -+ nt->bytes = filesize; -+ nt->last_time = now; -+ nt->next = g_conf.tracking; -+ g_conf.tracking = nt; -+ } -+ } -+} -+ -+/* tracking_check -- Check if node exceeds limits */ -+static int tracking_check(const char *aka, char *msg, int msglen) -+{ -+ NodeTrack *nt = tracking_find(aka); -+ -+ if (!nt) -+ return 1; /* No tracking yet, allow */ -+ -+ /* Check max files */ -+ if (g_conf.maxfiles > 0 && nt->files >= g_conf.maxfiles) -+ { -+ snprintf(msg, msglen, "RATE LIMIT: max files (%d) reached for %s", g_conf.maxfiles, aka); -+ return 0; -+ } -+ -+ /* Check max bytes */ -+ if (g_conf.maxbytes > 0 && nt->bytes >= g_conf.maxbytes) -+ { -+ snprintf(msg, msglen, "RATE LIMIT: max bytes (%ld) reached for %s", g_conf.maxbytes, aka); -+ return 0; -+ } -+ -+ return 1; /* Within limits */ -+} -+ -+/* is_abs_path -- True if path is absolute (POSIX, Win32, AmigaDOS device:) */ -+static int is_abs_path(const char *p) -+{ -+ if (!p || !p[0]) -+ return 0; -+ -+ if (p[0] == '/' || p[0] == '\\') -+ return 1; -+ -+#ifdef AMIGA -+ if (strchr(p, ':') != NULL) -+ return 1; -+#else -+ if (p[1] == ':') -+ return 1; /* C:\ etc. */ -+#endif -+ return 0; -+} -+ -+/* -+ * load_aliases -- Read alias definitions from file -+ * Lines starting with '#' or empty are skipped -+ * Format: -+ */ -+static void load_aliases(const char *filepath) -+{ -+ FILE *f; -+ char line[MAX_LINE]; -+ char name[64]; -+ char path[MAXPATHLEN]; -+ int n; -+ -+ /* Free previous aliases and start fresh */ -+ if (g_aliases) -+ { -+ free(g_aliases); -+ g_aliases = NULL; -+ } -+ -+ g_nalias = 0; -+ g_alias_cap = 0; -+ -+ if (!filepath || !filepath[0] || strcmp(filepath, "-") == 0) -+ return; -+ -+ f = fopen(filepath, "r"); -+ -+ if (!f) -+ { -+ /*fprintf(stderr, "srifreq: cannot open aliases file: %s\n", filepath);*/ -+ return; -+ } -+ -+ while (fgets(line, sizeof(line), f)) -+ { -+ char *p; -+ -+ /* Strip trailing newline */ -+ n = (int)strlen(line); -+ -+ while (n > 0 && (line[n - 1] == '\r' || line[n - 1] == '\n')) -+ line[--n] = '\0'; -+ -+ /* Skip blanks and comments */ -+ p = line; -+ -+ while (*p == ' ' || *p == '\t') -+ p++; -+ -+ if (!*p || *p == '#') -+ continue; -+ -+ name[0] = '\0'; -+ path[0] = '\0'; -+ -+ if (sscanf(p, "%63s %1023[^\n]", name, path) < 2) -+ continue; -+ -+ if (!name[0] || !path[0]) -+ continue; -+ -+ /* Grow array dynamically if needed */ -+ if (g_nalias >= g_alias_cap) -+ { -+ int new_cap = g_alias_cap ? g_alias_cap * 2 : 16; -+ Alias *new_arr = realloc(g_aliases, (size_t)new_cap * sizeof(Alias)); -+ -+ if (!new_arr) -+ break; -+ -+ g_aliases = new_arr; -+ g_alias_cap = new_cap; -+ } -+ -+ safe_strncpy(g_aliases[g_nalias].name, name, (int)sizeof(g_aliases[g_nalias].name)); -+ safe_strncpy(g_aliases[g_nalias].path, path, (int)sizeof(g_aliases[g_nalias].path)); -+ g_nalias++; -+ } -+ -+ fclose(f); -+ -+ /*printf("srifreq: loaded %d alias(es) from %s\n", g_nalias, filepath);*/ -+} -+ -+/* -+ * find_alias -- look up name in alias table (case-insensitive) -+ * Returns the path string, or NULL if not found -+ */ -+static const char *find_alias(const char *name) -+{ -+ char upper[64]; -+ char aname[64]; -+ int i; -+ int n; -+ -+ /* Convert name to uppercase */ -+ n = (int)strlen(name); -+ -+ if (n >= (int)sizeof(upper)) -+ n = (int)sizeof(upper) - 1; -+ -+ for (i = 0; i < n; i++) -+ upper[i] = (char)toupper((unsigned char)name[i]); -+ -+ upper[n] = '\0'; -+ -+ for (i = 0; i < g_nalias; i++) -+ { -+ int an; -+ an = (int)strlen(g_aliases[i].name); -+ -+ if (an >= (int)sizeof(aname)) an = (int)sizeof(aname) - 1; -+ { -+ int j; -+ -+ for (j = 0; j < an; j++) -+ aname[j] = (char)toupper((unsigned char)g_aliases[i].name[j]); -+ -+ aname[an] = '\0'; -+ } -+ -+ if (strcmp(upper, aname) == 0) -+ return g_aliases[i].path; -+ } -+ -+ return NULL; -+} -+ -+static int parse_srif(const char *path, SRIF *srif) -+{ -+ FILE *f; -+ char line[MAX_LINE]; -+ char token[MAX_LINE]; -+ char value[MAX_LINE]; -+ -+ memset(srif, 0, sizeof(SRIF)); -+ srif->time_limit = -1; /* default: unlimited */ -+ srif->listed = 1; /* default: assume listed */ -+ srif->protected_sess = 0; -+ -+ f = fopen(path, "r"); -+ if (!f) -+ return 0; -+ -+ while (fgets(line, sizeof(line), f)) -+ { -+ str_trim(line); -+ if (!line[0]) -+ continue; -+ -+ token[0] = '\0'; -+ value[0] = '\0'; -+ -+ if (sscanf(line, "%1023s %1023[^\n]", token, value) < 1) -+ continue; -+ -+ str_upper(token); -+ -+ if (!value[0]) -+ continue; -+ -+ if (!strcmp(token, "SYSOP")) -+ safe_strncpy(srif->sysop, value, (int)sizeof(srif->sysop)); -+ else if (!strcmp(token, "AKA") && !srif->aka[0]) -+ safe_strncpy(srif->aka, value, (int)sizeof(srif->aka)); -+ else if (!strcmp(token, "REQUESTLIST")) -+ { -+ safe_strncpy(srif->request_list, value, MAXPATHLEN); -+ srif->got_request_list = 1; -+ } -+ else if (!strcmp(token, "RESPONSELIST")) -+ { -+ safe_strncpy(srif->response_list, value, MAXPATHLEN); -+ srif->got_response_list = 1; -+ } -+ else if (!strcmp(token, "OURAKA")) -+ safe_strncpy(srif->our_aka, value, (int)sizeof(srif->our_aka)); -+ else if (!strcmp(token, "PASSWORD")) -+ safe_strncpy(srif->password, value, (int)sizeof(srif->password)); -+ else if (!strcmp(token, "CALLERID")) -+ safe_strncpy(srif->caller_id, value, (int)sizeof(srif->caller_id)); -+ else if (!strcmp(token, "TIME")) -+ srif->time_limit = atoi(value); -+ else if (!strcmp(token, "TRANX")) -+ { -+ /* TRANX is a hex Unix timestamp: 5a326682 or 16-digit */ -+ unsigned long v = 0; -+ sscanf(value, "%lx", &v); -+ srif->tranx = (long)v; -+ } -+ else if (!strcmp(token, "REMOTESTATUS")) -+ { -+ char tmp[32]; -+ safe_strncpy(tmp, value, (int)sizeof(tmp)); -+ str_upper(tmp); -+ srif->protected_sess = (strncmp(tmp, "PROTECTED", 9) == 0) ? 1 : 0; -+ } -+ else if (!strcmp(token, "SYSTEMSTATUS")) -+ { -+ char tmp[32]; -+ safe_strncpy(tmp, value, (int)sizeof(tmp)); -+ str_upper(tmp); -+ srif->listed = (strncmp(tmp, "LISTED", 6) == 0) ? 1 : 0; -+ } -+ } -+ -+ fclose(f); -+ -+ return srif->got_request_list; -+} -+ -+/* Logging */ -+static void do_log(const char *logpath, const char *msg) -+{ -+ FILE *lf; -+ time_t t; -+ struct tm tm; -+ char timestamp[32]; -+ -+ if (!logpath || !logpath[0] || strcmp(logpath, "-") == 0) -+ return; -+ -+ t = time(NULL); -+ safe_localtime(&t, &tm); -+ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); -+ -+ lf = fopen(logpath, "a"); -+ -+ if (lf) -+ { -+ fprintf(lf, "[%s] srifreq: %s\n", timestamp, msg); -+ fclose(lf); -+ } -+} -+ -+/* serve_one -- resolve one request name, check password/timestamp/update -+ * write to response list. Returns 1 if served -+ */ -+static int serve_one(const char *req_name, const char *found_path, const char *req_pass, long req_newer, int req_update, const SRIF *srif, FILE *rsp_f, const char *log_path, char *logbuf, int logbuf_size) -+{ -+ long fsize; -+ -+ /* Check rate limits before serving */ -+ if (g_conf.trackfile[0] && !tracking_check(srif->aka, logbuf, logbuf_size)) -+ { -+ do_log(log_path, logbuf); -+ return 0; -+ } -+ -+ /* RemoteStatus: if session is unprotected and a password is required, deny */ -+ if (req_pass[0] && !srif->protected_sess) -+ { -+ snprintf(logbuf, logbuf_size, "PASSWORD DENY (unprotected session): %s", req_name); -+ do_log(log_path, logbuf); -+ return 0; -+ } -+ -+ /* Password check: !pw must match SRIF PASSWORD (case-insensitive) */ -+ if (req_pass[0]) -+ { -+ char rp[64], sp[64]; -+ int i; -+ -+ safe_strncpy(rp, req_pass, (int)sizeof(rp)); -+ safe_strncpy(sp, srif->password, (int)sizeof(sp)); -+ -+ for (i = 0; rp[i]; i++) -+ rp[i] = (char)toupper((unsigned char)rp[i]); -+ -+ for (i = 0; sp[i]; i++) -+ sp[i] = (char)toupper((unsigned char)sp[i]); -+ -+ if (strcmp(rp, sp) != 0) -+ { -+ snprintf(logbuf, logbuf_size, "PASSWORD FAIL: %s", req_name); -+ do_log(log_path, logbuf); -+ -+ return 0; -+ } -+ } -+ -+ /* Update request (U flag): serve only if file is newer than TRANX */ -+ if (req_update && srif->tranx > 0) -+ { -+ long mtime = get_file_mtime(found_path); -+ -+ if (mtime <= srif->tranx) -+ { -+ snprintf(logbuf, logbuf_size, "NOT UPDATED: %s (mtime=%ld tranx=%ld)", req_name, mtime, srif->tranx); -+ do_log(log_path, logbuf); -+ return 0; -+ } -+ } -+ -+ /* Timestamp check: +ts means "only if file is newer than ts" */ -+ if (req_newer > 0) -+ { -+ long mtime = get_file_mtime(found_path); -+ -+ if (mtime <= req_newer) -+ { -+ snprintf(logbuf, logbuf_size, "NOT NEWER: %s (mtime=%ld req=%ld)", req_name, mtime, req_newer); -+ -+ do_log(log_path, logbuf); -+ return 0; -+ } -+ } -+ -+ snprintf(logbuf, logbuf_size, "FOUND: %s -> %s", req_name, found_path); -+ do_log(log_path, logbuf); -+ -+ if (rsp_f) -+ fprintf(rsp_f, "+%s\r\n", found_path); -+ -+ /* Update tracking after successful serve */ -+ if (g_conf.trackfile[0]) -+ { -+ fsize = get_file_size(found_path); -+ -+ if (fsize < 0) -+ fsize = 0; -+ -+ tracking_update(srif->aka, fsize); -+ } -+ -+ return 1; -+} -+ -+int main(int argc, char *argv[]) -+{ -+ const char *srif_path; -+ SRIF srif; -+ FILE *req_f; -+ FILE *rsp_f; -+ char line[MAX_LINE]; -+ char req_name[MAX_LINE]; -+ char req_pass[64]; -+ long req_newer; -+ int req_update; -+ char found_path[MAXPATHLEN]; -+ char logbuf[MAXPATHLEN * 4 + 128]; -+ int found_count; -+ -+ config_init(); -+ -+ /* --conf */ -+ if (argc >= 4 && strcmp(argv[1], "--conf") == 0) -+ { -+ if (!load_config(argv[2])) -+ return 1; -+ -+ srif_path = argv[3]; -+ } -+ else -+ { -+ fprintf(stderr, "Usage:\n" -+ " srifreq --conf \n" -+ "\n" -+ "Config file keys: pubdir, logfile, aliases, private " -+ "\n"); -+ -+ return 1; -+ } -+ -+ if (!g_conf.pubdir[0]) -+ { -+ fprintf(stderr, "srifreq: pubdir not set\n"); -+ config_free(); -+ return 1; -+ } -+ -+ /* Load tracking data if configured */ -+ tracking_load(); -+ -+ load_aliases(g_conf.aliases[0] ? g_conf.aliases : NULL); -+ -+ snprintf(logbuf, sizeof(logbuf), "processing SRIF: %s", srif_path); -+ do_log(g_conf.logfile, logbuf); -+ -+ if (!parse_srif(srif_path, &srif)) -+ { -+ snprintf(logbuf, sizeof(logbuf), "ERROR: cannot parse SRIF or missing RequestList: %s", srif_path); -+ do_log(g_conf.logfile, logbuf); -+ fprintf(stderr, "srifreq: %s\n", logbuf); -+ config_free(); -+ return 1; -+ } -+ -+ /* SystemStatus: deny unlisted systems entirely */ -+ if (!srif.listed) -+ { -+ snprintf(logbuf, sizeof(logbuf), "DENIED: system is UNLISTED (aka: %s)", srif.aka); -+ do_log(g_conf.logfile, logbuf); -+ config_free(); -+ return 1; -+ } -+ -+ snprintf(logbuf, sizeof(logbuf), "sysop: %s aka: %s status: %s%s caller: %s req: %s", srif.sysop, srif.aka, srif.protected_sess ? "PROTECTED" : "UNPROTECTED", srif.tranx ? " (TRANX)" : "", srif.caller_id[0] ? srif.caller_id : "?", srif.request_list); -+ do_log(g_conf.logfile, logbuf); -+ -+ /* Log rate limiting status if active */ -+ if (g_conf.trackfile[0] && (g_conf.maxfiles > 0 || g_conf.maxbytes > 0)) -+ { -+ snprintf(logbuf, sizeof(logbuf), "rate limits: maxfiles=%d maxbytes=%ld window=%lds", g_conf.maxfiles, g_conf.maxbytes, g_conf.timewindow); -+ do_log(g_conf.logfile, logbuf); -+ } -+ -+ req_f = fopen(srif.request_list, "r"); -+ -+ if (!req_f) -+ { -+ snprintf(logbuf, sizeof(logbuf), "WARN: RequestList not available: %s", srif.request_list); -+ do_log(g_conf.logfile, logbuf); -+ config_free(); -+ return 0; -+ } -+ -+ rsp_f = NULL; -+ -+ if (srif.got_response_list && srif.response_list[0]) -+ { -+ rsp_f = fopen(srif.response_list, "w"); -+ -+ if (!rsp_f) -+ { -+ snprintf(logbuf, sizeof(logbuf), "WARN: cannot create ResponseList: %s", srif.response_list); -+ do_log(g_conf.logfile, logbuf); -+ } -+ } -+ -+ found_count = 0; -+ -+ /* Check and update track file */ -+ while (fgets(line, sizeof(line), req_f)) -+ { -+ char *p; -+ const char *alias_path; -+ -+ str_trim(line); -+ -+ if (!line[0] || line[0] == ';' || line[0] == '#') -+ continue; -+ -+ /* Parse: filename [!password] [+timestamp] [U] */ -+ req_name[0] = '\0'; -+ req_pass[0] = '\0'; -+ req_newer = 0; -+ req_update = 0; -+ -+ if (sscanf(line, "%1023s", req_name) < 1) -+ continue; -+ -+ /* Skip URLs */ -+ if (strncmp(req_name, "http", 4) == 0 || strncmp(req_name, "ftp", 3) == 0) -+ continue; -+ -+ /* Parse modifiers from the rest of the line */ -+ p = strstr(line, req_name); -+ -+ if (p) -+ p += strlen(req_name); -+ else -+ p = line + strlen(line); -+ -+ while (*p) -+ { -+ while (*p == ' ' || *p == '\t') -+ p++; -+ -+ if (*p == '!') -+ { -+ /* !password */ -+ int i = 0; -+ p++; -+ -+ while (*p && *p != ' ' && *p != '\t' && i < (int)sizeof(req_pass) - 1) -+ req_pass[i++] = *p++; -+ -+ req_pass[i] = '\0'; -+ } -+ else if (*p == '+') -+ { -+ /* +unix_timestamp */ -+ p++; -+ req_newer = atol(p); -+ -+ while (*p && *p != ' ' && *p != '\t') -+ p++; -+ } -+ else if (*p == 'U' && (p[1] == '\0' || p[1] == ' ' || p[1] == '\t')) -+ { -+ /* U = update request */ -+ req_update = 1; -+ p++; -+ } -+ else if (*p) -+ p++; /* Skip unknown token */ -+ } -+ -+ found_path[0] = '\0'; -+ -+ /* Check alias table first */ -+ alias_path = find_alias(req_name); -+ -+ if (alias_path) -+ { -+ if (is_abs_path(alias_path)) -+ safe_strncpy(found_path, alias_path, MAXPATHLEN); -+ else -+ path_join(found_path, MAXPATHLEN, g_conf.pubdir, alias_path); -+ -+ if (path_exists(found_path)) -+ found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+ else -+ { -+ snprintf(logbuf, sizeof(logbuf), "NOT FOUND (alias): %s -> %s", req_name, found_path); -+ do_log(g_conf.logfile, logbuf); -+ } -+ -+ continue; -+ } -+ -+ /* Wildcard: scan pubdir and all privdirs whose password matches */ -+ if (is_wildcard(req_name)) -+ { -+ DIR *dp; -+ struct dirent *de; -+ PrivDir *pd; -+ -+ /* Scan pubdir (no password needed) */ -+ dp = opendir(g_conf.pubdir); -+ if (dp) -+ { -+ while ((de = readdir(dp)) != NULL) -+ { -+ if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) -+ continue; -+ -+ if (!wildmatch(req_name, de->d_name)) -+ continue; -+ -+ path_join(found_path, MAXPATHLEN, g_conf.pubdir, de->d_name); -+ -+ if (path_exists(found_path)) -+ found_count += serve_one(de->d_name, found_path, "", req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+ } -+ -+ closedir(dp); -+ } -+ -+ /* Scan matching privdirs */ -+ for (pd = g_conf.privdirs; pd; pd = pd->next) -+ { -+ char rp[64], pp[64]; -+ int ci; -+ -+ safe_strncpy(rp, req_pass, (int)sizeof(rp)); -+ safe_strncpy(pp, pd->password, (int)sizeof(pp)); -+ -+ for (ci = 0; rp[ci]; ci++) -+ rp[ci] = (char)toupper((unsigned char)rp[ci]); -+ -+ for (ci = 0; pp[ci]; ci++) -+ pp[ci] = (char)toupper((unsigned char)pp[ci]); -+ -+ if (strcmp(rp, pp) != 0) -+ continue; -+ -+ dp = opendir(pd->path); -+ -+ if (!dp) -+ continue; -+ -+ while ((de = readdir(dp)) != NULL) -+ { -+ if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) -+ continue; -+ -+ if (!wildmatch(req_name, de->d_name)) -+ continue; -+ -+ path_join(found_path, MAXPATHLEN, pd->path, de->d_name); -+ -+ if (path_exists(found_path)) -+ found_count += serve_one(de->d_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+ } -+ -+ closedir(dp); -+ } -+ -+ continue; -+ } -+ -+ /* Plain filename: try pubdir first, then privdirs if password given */ -+ path_join(found_path, MAXPATHLEN, g_conf.pubdir, req_name); -+ -+ if (path_exists(found_path)) -+ { -+ found_count += -+ serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+ } -+ else if (req_pass[0]) -+ { -+ /* Try each private dir whose password matches */ -+ PrivDir *pd; -+ int served = 0; -+ -+ for (pd = g_conf.privdirs; pd && !served; pd = pd->next) -+ { -+ char rp[64], pp[64]; -+ int ci; -+ -+ safe_strncpy(rp, req_pass, (int)sizeof(rp)); -+ safe_strncpy(pp, pd->password, (int)sizeof(pp)); -+ -+ for (ci = 0; rp[ci]; ci++) -+ rp[ci] = (char)toupper((unsigned char)rp[ci]); -+ -+ for (ci = 0; pp[ci]; ci++) -+ pp[ci] = (char)toupper((unsigned char)pp[ci]); -+ -+ if (strcmp(rp, pp) != 0) -+ continue; -+ -+ path_join(found_path, MAXPATHLEN, pd->path, req_name); -+ -+ if (path_exists(found_path)) -+ { -+ found_count += serve_one(req_name, found_path, req_pass, req_newer, req_update, &srif, rsp_f, g_conf.logfile, logbuf, (int)sizeof(logbuf)); -+ served = 1; -+ } -+ } -+ if (!served) -+ { -+ snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s", req_name); -+ do_log(g_conf.logfile, logbuf); -+ } -+ } -+ else -+ { -+ snprintf(logbuf, sizeof(logbuf), "NOT FOUND: %s (pub: %s)", req_name, g_conf.pubdir); -+ do_log(g_conf.logfile, logbuf); -+ } -+ } -+ -+ fclose(req_f); -+ -+ if (rsp_f) -+ fclose(rsp_f); -+ -+ snprintf(logbuf, sizeof(logbuf), "done: %d file(s) found", found_count); -+ do_log(g_conf.logfile, logbuf); -+ -+ /* Save tracking data */ -+ tracking_save(); -+ -+ config_free(); -+ -+ return (found_count > 0) ? 0 : 1; -+} -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/misc/srifreq.txt binkd/misc/srifreq.txt ---- binkd_pgul/misc/srifreq.txt 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/misc/srifreq.txt 2026-04-26 13:54:14.035795187 +0100 -@@ -0,0 +1,67 @@ -+srifreq -- SRIF-compatible file request server -+ -+USAGE: -+ srifreq --conf -+ -+DESCRIPTION: -+ SRIF (Standard Request Information Format) compatible file -+ request server for binkd. Processes incoming .req files and -+ serves files based on password protection and aliases. -+ -+CONFIGURATION FILE FORMAT: -+ # Lines starting with # are comments -+ # Blank lines are ignored -+ -+ pubdir # Public directory (no password required) -+ logfile # Log file, or - to disable -+ aliases # Magic-name aliases file (optional) -+ private # Private directory (requires !password) -+ -+ # Rate limiting options (optional): -+ trackfile # File to track node download statistics -+ maxfiles # Max files per node per time window (0=unlimited) -+ maxsize # Max bytes per node per time window (0=unlimited) -+ timewindow # Time window in seconds (0=no window) -+ -+ALIASES FILE FORMAT: -+ # Lines starting with # are comments -+ # Format: -+ # Names are matched case-insensitively -+ -+EXAMPLE CONFIG FILE (srifreq.conf): -+ # srifreq.conf - SRIF Request Server Configuration -+ -+ pubdir Work:Fido/Public -+ logfile Work:Logs/srifreq.log -+ aliases Work:Fido/srifreq.aliases -+ -+ # Private directories (password protected) -+ private Work:Fido/Private/Uploader1 secretpass1 -+ private Work:Fido/Private/Node190 node190pwd -+ -+ # Rate limiting: max 10 files or 50MB per node per 24 hours -+ trackfile Work:Logs/srifreq.track -+ maxfiles 10 -+ maxsize 52428800 -+ timewindow 86400 -+ -+EXAMPLE ALIASES FILE (srifreq.aliases): -+ # srifreq.aliases - Magic-name to file mappings -+ # Names are case-insensitive -+ -+ DOORWAY Games:Utils/Doorway/doorway.zip -+ NETMAIL Work:Comm/Fido/netmail.lha -+ README Docs:Readme.txt -+ 4DOUT AmiTCP:4DOut.lha -+ BINKD Apps:Comm/Binkd/binkd.lha -+ -+REQUEST FILE FORMAT (.req): -+ Files listed one per line. Modifiers: -+ !password - Required password for private areas -+ +timestamp - Only serve if file is newer than timestamp -+ U - Update request (only if newer than client's TRANX) -+ -+EXAMPLES: -+ srifreq --conf srifreq.conf inbound/srif_file.req -+ srifreq --conf srifreq.conf inbound/*.req -+ exec "work:fido/srifreq --conf work:fido/srifreq.conf *S" *.req *.REQ -\ No hay ningún carácter de nueva línea al final del archivo -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/amiga/Makefile binkd/mkfls/amiga/Makefile ---- binkd_pgul/mkfls/amiga/Makefile 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/mkfls/amiga/Makefile 2026-04-26 14:55:09.963221741 +0100 -@@ -26,4 +26,4 @@ - $(CC) -c $(CFLAGS) amiga/getfree.c - sem.o: - $(CC) -c $(CFLAGS) amiga/sem.c --include Makefile.dep -+include Makefile.dep -\ No hay ningún carácter de nueva línea al final del archivo -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo binkd/mkfls/amiga/Makefile.analyze.bebbo ---- binkd_pgul/mkfls/amiga/Makefile.analyze.bebbo 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/mkfls/amiga/Makefile.analyze.bebbo 2026-04-25 16:52:14.088635220 +0100 -@@ -0,0 +1,96 @@ -+# Makefile.analyze.bebbo -- Static analysis for Amiga bebbo (GCC 6.5.0b) -+# Includes amiga/ code with bebbo-specific defines -+# Usage: make -f Makefile.analyze.bebbo -+ -+# All sources including Amiga-specific -+ALL_SRCS = \ -+ binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ -+ bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ -+ readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ -+ setpttl.c https.c md5b.c crypt.c compress.c \ -+ amiga/rename.c amiga/getfree.c amiga/bsdsock.c amiga/dirent.c \ -+ amiga/utime.c amiga/rfc2553_amiga.c amiga/sem.c amiga/evloop.c \ -+ amiga/sock.c amiga/session.c \ -+ misc/decompress.c misc/freq.c misc/process_tic.c misc/srifreq.c misc/nodelist.c -+ -+INCLUDES = -I. -Iamiga -+ -+# bebbo-specific defines (GCC 6.5.0b, HAS native snprintf) -+BEBBO_DEFINES = \ -+ -DAMIGA \ -+ -DHAVE_SOCKLEN_T \ -+ -DHAVE_INTMAX_T \ -+ -DHAVE_SNPRINTF \ -+ -DHAVE_GETOPT \ -+ -DHAVE_UNISTD_H \ -+ -DHAVE_SYS_TIME_H \ -+ -DHAVE_SYS_PARAM_H \ -+ -DHAVE_SYS_IOCTL_H \ -+ -DHAVE_NETINET_IN_H \ -+ -DHAVE_NETDB_H \ -+ -DHAVE_ARPA_INET_H \ -+ -DHAVE_STDARG_H \ -+ -DHAVE_VSNPRINTF \ -+ -DWITH_ZLIB -+ -+CPPCHECK_FLAGS = \ -+ --enable=all \ -+ --inconclusive \ -+ --std=c89 \ -+ --quiet \ -+ --suppress=missingIncludeSystem \ -+ --suppress=unusedFunction \ -+ --suppress=checkersReport \ -+ $(INCLUDES) -+ -+# Log files -+LOG_DIR = analysis_logs -+CPPCHECK_LOG = $(LOG_DIR)/cppcheck_bebbo.log -+CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_bebbo.log -+ -+.PHONY: all cppcheck clang-tidy analyze clean -+ -+all: analyze -+ -+cppcheck: -+ @mkdir -p $(LOG_DIR) -+ @echo "=== cppcheck Amiga bebbo (GCC 6.5.0b) ===" -+ @echo "Defines: bebbo, HAS native snprintf, no snprintf.c needed" -+ @echo "Saving output to: $(CPPCHECK_LOG)" -+ @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) $(ALL_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true -+ @echo "=== done ===" -+ @echo "Log saved: $(CPPCHECK_LOG)" -+ -+clang-tidy: -+ @mkdir -p $(LOG_DIR) -+ @echo "=== clang-tidy Amiga bebbo ===" -+ @echo "Note: Some Amiga headers may not resolve on Linux host" -+ @echo "Saving output to: $(CLANG_TIDY_LOG)" -+ @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) -+ @for src in binkd.c readcfg.c amiga/evloop.c amiga/session.c; do \ -+ echo "" >> $(CLANG_TIDY_LOG); \ -+ echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ -+ clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ -+ $(INCLUDES) $(BEBBO_DEFINES) -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ -+ done -+ @echo "=== done ===" -+ @echo "Log saved: $(CLANG_TIDY_LOG)" -+ -+analyze: cppcheck clang-tidy -+ -+# Focus on Amiga-specific code only -+amiga-only: -+ @echo "=== cppcheck Amiga-specific code only (bebbo) ===" -+ @cppcheck $(CPPCHECK_FLAGS) $(BEBBO_DEFINES) \ -+ amiga/*.c 2>&1 || true -+ -+# Check what differs between ADE and bebbo -+diff-defines: -+ @echo "=== ADE vs bebbo define differences ===" -+ @echo "ADE only: -DHAVE_VSNPRINTF (no -DHAVE_SNPRINTF)" -+ @echo "bebbo: -DHAVE_SNPRINTF -DHAVE_VSNPRINTF" -+ @echo "" -+ @echo "ADE needs snprintf.c, bebbo does not" -+ -+clean: -+ @true -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/amiga/Makefile.bebbo binkd/mkfls/amiga/Makefile.bebbo ---- binkd_pgul/mkfls/amiga/Makefile.bebbo 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/mkfls/amiga/Makefile.bebbo 2026-04-26 13:11:27.902433254 +0100 -@@ -0,0 +1,208 @@ -+CC = m68k-amigaos-gcc -+ -+# Common flags for compiler and linker -+COMMON_CFLAGS = -Wall -Wextra -Wunused -Wunused-function -Wunused-variable -Wmissing-prototypes -Wno-pointer-sign -ffunction-sections -fdata-sections -noixemul -+ARCH_FLAGS = -O -m68000 -msoft-float -fomit-frame-pointer -+ -+CFLAGS = $(DEFINES) $(COMMON_CFLAGS) $(ARCH_FLAGS) -+LIBS = -lz -lm -lamiga -+LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_gcc.map -s -+ -+# Tools use same flags as main binary -+TOOL_CFLAGS = $(DEFINES) $(COMMON_CFLAGS) -O -m68000 -msoft-float -fomit-frame-pointer -I misc -+TOOL_LDFLAGS = -Wl,--gc-sections -Wl,-Map=bebbo_tools.map -s -+ -+OBJDIR = objs -+ -+DEFINES = \ -+ -DAMIGA \ -+ -DHAVE_SOCKLEN_T \ -+ -DHAVE_INTMAX_T \ -+ -DHAVE_SNPRINTF \ -+ -DHAVE_GETOPT \ -+ -DHAVE_UNISTD_H \ -+ -DHAVE_SYS_TIME_H \ -+ -DHAVE_SYS_PARAM_H \ -+ -DHAVE_SYS_IOCTL_H \ -+ -DHAVE_NETINET_IN_H \ -+ -DHAVE_NETDB_H \ -+ -DHAVE_ARPA_INET_H \ -+ -DHTTPS \ -+ -DAMIGADOS_4D_OUTBOUND \ -+ -DHAVE_STDARG_H \ -+ -DHAVE_VSNPRINTF \ -+ -DWITH_ZLIB \ -+ -DOS=\"Amiga\" \ -+ -I. \ -+ -Iamiga -+ -+SRCS = \ -+ binkd.c \ -+ readcfg.c \ -+ tools.c \ -+ ftnaddr.c \ -+ ftnq.c \ -+ client.c \ -+ server.c \ -+ protocol.c \ -+ bsy.c \ -+ inbound.c \ -+ breaksig.c \ -+ branch.c \ -+ amiga/rename.c \ -+ amiga/getfree.c \ -+ amiga/bsdsock.c \ -+ amiga/dirent.c \ -+ amiga/utime.c \ -+ amiga/rfc2553_amiga.c \ -+ amiga/sem.c \ -+ amiga/evloop.c \ -+ amiga/sock.c \ -+ amiga/session.c \ -+ bsycleanup.c \ -+ amiga/proto_amiga.c \ -+ ftndom.c \ -+ ftnnode.c \ -+ srif.c \ -+ pmatch.c \ -+ readflo.c \ -+ prothlp.c \ -+ iptools.c \ -+ run.c \ -+ binlog.c \ -+ exitproc.c \ -+ getw.c \ -+ xalloc.c \ -+ setpttl.c \ -+ https.c \ -+ md5b.c \ -+ crypt.c \ -+ compress.c -+ -+OBJS = \ -+ $(OBJDIR)/binkd.o \ -+ $(OBJDIR)/readcfg.o \ -+ $(OBJDIR)/tools.o \ -+ $(OBJDIR)/ftnaddr.o \ -+ $(OBJDIR)/ftnq.o \ -+ $(OBJDIR)/client.o \ -+ $(OBJDIR)/server.o \ -+ $(OBJDIR)/protocol.o \ -+ $(OBJDIR)/bsy.o \ -+ $(OBJDIR)/inbound.o \ -+ $(OBJDIR)/breaksig.o \ -+ $(OBJDIR)/branch.o \ -+ $(OBJDIR)/rename.o \ -+ $(OBJDIR)/getfree.o \ -+ $(OBJDIR)/bsdsock.o \ -+ $(OBJDIR)/dirent.o \ -+ $(OBJDIR)/utime.o \ -+ $(OBJDIR)/rfc2553_amiga.o \ -+ $(OBJDIR)/sem.o \ -+ $(OBJDIR)/evloop.o \ -+ $(OBJDIR)/sock.o \ -+ $(OBJDIR)/session.o \ -+ $(OBJDIR)/bsycleanup.o \ -+ $(OBJDIR)/proto_amiga.o \ -+ $(OBJDIR)/ftndom.o \ -+ $(OBJDIR)/ftnnode.o \ -+ $(OBJDIR)/srif.o \ -+ $(OBJDIR)/pmatch.o \ -+ $(OBJDIR)/readflo.o \ -+ $(OBJDIR)/prothlp.o \ -+ $(OBJDIR)/iptools.o \ -+ $(OBJDIR)/run.o \ -+ $(OBJDIR)/binlog.o \ -+ $(OBJDIR)/exitproc.o \ -+ $(OBJDIR)/getw.o \ -+ $(OBJDIR)/xalloc.o \ -+ $(OBJDIR)/setpttl.o \ -+ $(OBJDIR)/https.o \ -+ $(OBJDIR)/md5b.o \ -+ $(OBJDIR)/crypt.o \ -+ $(OBJDIR)/compress.o -+ -+all: binkd decompress process_tic freq srifreq nodelist -+ -+$(OBJDIR): -+ mkdir -p $(OBJDIR) -+ -+$(OBJDIR)/%.o: %.c -+ $(CC) -c $(CFLAGS) $< -o $@ -+ -+binkd: $(OBJDIR) $(OBJS) -+ $(CC) $(CFLAGS) -o binkd $(OBJS) $(LIBS) $(LDFLAGS) -+ -+# ---------- Utility tools (stand-alone, multi-platform) ---------- -+# TOOL_CFLAGS and TOOL_LDFLAGS defined above -+ -+decompress: -+ $(CC) $(TOOL_CFLAGS) -o decompress misc/decompress.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) -+ -+process_tic: -+ $(CC) $(TOOL_CFLAGS) -o process_tic misc/process_tic.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) -+ -+freq: -+ $(CC) $(TOOL_CFLAGS) -o freq misc/freq.c misc/portable.c $(TOOL_LDFLAGS) -+ -+srifreq: -+ $(CC) $(TOOL_CFLAGS) -o srifreq misc/srifreq.c misc/portable.c amiga/dirent.c $(TOOL_LDFLAGS) -+ -+nodelist: -+ $(CC) $(TOOL_CFLAGS) -o nodelist misc/nodelist.c misc/portable.c $(TOOL_LDFLAGS) -+ -+install: all clean -+ -+clean: -+ rm -f *.[bo] *.BAK *.core *.obj *.err *~ core -+ rm -rf $(OBJDIR) -+ rm -f binkd decompress process_tic freq srifreq nodelist -+ -+# ---------- Explicit rules for amiga/ objects ---------- -+$(OBJDIR)/rename.o: amiga/rename.c -+ $(CC) -c $(CFLAGS) amiga/rename.c -o $(OBJDIR)/rename.o -+ -+$(OBJDIR)/getfree.o: amiga/getfree.c -+ $(CC) -c $(CFLAGS) amiga/getfree.c -o $(OBJDIR)/getfree.o -+ -+$(OBJDIR)/sem.o: amiga/sem.c -+ $(CC) -c $(CFLAGS) amiga/sem.c -o $(OBJDIR)/sem.o -+ -+$(OBJDIR)/bsdsock.o: amiga/bsdsock.c -+ $(CC) -c $(CFLAGS) amiga/bsdsock.c -o $(OBJDIR)/bsdsock.o -+ -+$(OBJDIR)/dirent.o: amiga/dirent.c -+ $(CC) -c $(CFLAGS) amiga/dirent.c -o $(OBJDIR)/dirent.o -+ -+$(OBJDIR)/utime.o: amiga/utime.c -+ $(CC) -c $(CFLAGS) amiga/utime.c -o $(OBJDIR)/utime.o -+ -+$(OBJDIR)/rfc2553_amiga.o: amiga/rfc2553_amiga.c -+ $(CC) -c $(CFLAGS) amiga/rfc2553_amiga.c -o $(OBJDIR)/rfc2553_amiga.o -+ -+$(OBJDIR)/evloop.o: amiga/evloop.c -+ $(CC) -c $(CFLAGS) amiga/evloop.c -o $(OBJDIR)/evloop.o -+ -+$(OBJDIR)/sock.o: amiga/sock.c -+ $(CC) -c $(CFLAGS) amiga/sock.c -o $(OBJDIR)/sock.o -+ -+$(OBJDIR)/session.o: amiga/session.c -+ $(CC) -c $(CFLAGS) amiga/session.c -o $(OBJDIR)/session.o -+ -+$(OBJDIR)/bsycleanup.o: bsycleanup.c -+ $(CC) -c $(CFLAGS) bsycleanup.c -o $(OBJDIR)/bsycleanup.o -+ -+$(OBJDIR)/proto_amiga.o: amiga/proto_amiga.c -+ $(CC) -c $(CFLAGS) amiga/proto_amiga.c -o $(OBJDIR)/proto_amiga.o -+ -+depend Makefile.dep: Makefile -+ $(CC) -MM $(CFLAGS) $(SRCS) $(SYS) | \ -+ awk '{ if ($$1 != prev) { if (rec != "") print rec; \ -+ rec = $$0; prev = $$1; } \ -+ else { if (length(rec $$2) > 78) { print rec; rec = $$0; } \ -+ else rec = rec " " $$2 } } \ -+ END { print rec }' | tee Makefile.dep -+ -+-include Makefile.dep -+ -+.PHONY: all binkd decompress process_tic freq srifreq nodelist clean install -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/nt95-mingw/Makefile binkd/mkfls/nt95-mingw/Makefile ---- binkd_pgul/mkfls/nt95-mingw/Makefile 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/mkfls/nt95-mingw/Makefile 2026-04-26 13:12:24.499178632 +0100 -@@ -38,7 +38,8 @@ - setpttl.c https.c md5b.c crypt.c getopt.c nt/breaksig.c nt/getfree.c \ - nt/sem.c nt/TCPErr.c nt/WSock.c nt/w32tools.c nt/tray.c snprintf.c \ - ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c ntlm/des_enc.c \ -- ntlm/helpers.c -+ ntlm/helpers.c \ -+ bsycleanup.c - - RES= nt/binkdres.rc - -@@ -221,7 +222,32 @@ - OBJS=$(addprefix $(OBJDIR)/,$(patsubst %.c,%.o, $(SRCS))) - RESOBJS=$(addprefix $(OBJDIR)/, $(patsubst %.rc,%.o, $(RES))) - --.PHONY: all printinfo install html clean distclean makedirs -+# ---------- Utility tools (stand-alone) ---------- -+TOOL_CFLAGS = -O2 -Wall -DWIN32 -I. -I misc -+ -+utils: decompress process_tic freq srifreq nodelist -+ -+decompress: -+ @echo Compiling decompress... -+ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/decompress.exe misc/decompress.c misc/portable.c -+ -+process_tic: -+ @echo Compiling process_tic... -+ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/process_tic.exe misc/process_tic.c misc/portable.c -+ -+freq: -+ @echo Compiling freq... -+ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/freq.exe misc/freq.c misc/portable.c -+ -+srifreq: -+ @echo Compiling srifreq... -+ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/srifreq.exe misc/srifreq.c misc/portable.c -+ -+nodelist: -+ @echo Compiling nodelist... -+ @$(CC) $(TOOL_CFLAGS) -o $(OUTDIR)/nodelist.exe misc/nodelist.c misc/portable.c -+ -+.PHONY: all printinfo install html clean distclean makedirs utils decompress process_tic freq srifreq nodelist - - all: printinfo makedirs $(OUTDIR)/$(BINKDEXE) - -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/nt95-msvc/Makefile binkd/mkfls/nt95-msvc/Makefile ---- binkd_pgul/mkfls/nt95-msvc/Makefile 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/mkfls/nt95-msvc/Makefile 2026-04-26 12:41:43.750120247 +0100 -@@ -327,7 +327,32 @@ - - BINKDEXE = $(BINKDNAME).exe - --all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) -+all: printinfo makedirs "$(OUTDIR)\$(BINKDEXE)" $(BINKDBSC) utils -+ -+TOOL_CFLAGS = -nologo -W3 -O2 -DWIN32 -DVISUALCPP -I. -I misc -+TOOL_CC = $(CC) $(TOOL_CFLAGS) -+ -+utils: "$(OUTDIR)\decompress.exe" "$(OUTDIR)\process_tic.exe" "$(OUTDIR)\freq.exe" "$(OUTDIR)\srifreq.exe" "$(OUTDIR)\nodelist.exe" -+ -+"$(OUTDIR)\decompress.exe": misc\decompress.c misc\portable.c nt\dirwin32.c -+ @echo Compiling decompress... -+ @$(TOOL_CC) -Fe"$(OUTDIR)\decompress.exe" misc\decompress.c misc\portable.c nt\dirwin32.c -+ -+"$(OUTDIR)\process_tic.exe": misc\process_tic.c misc\portable.c nt\dirwin32.c -+ @echo Compiling process_tic... -+ @$(TOOL_CC) -Fe"$(OUTDIR)\process_tic.exe" misc\process_tic.c misc\portable.c nt\dirwin32.c -+ -+"$(OUTDIR)\freq.exe": misc\freq.c misc\portable.c -+ @echo Compiling freq... -+ @$(TOOL_CC) -Fe"$(OUTDIR)\freq.exe" misc\freq.c misc\portable.c -+ -+"$(OUTDIR)\srifreq.exe": misc\srifreq.c misc\portable.c nt\dirwin32.c -+ @echo Compiling srifreq... -+ @$(TOOL_CC) -Fe"$(OUTDIR)\srifreq.exe" misc\srifreq.c misc\portable.c nt\dirwin32.c -+ -+"$(OUTDIR)\nodelist.exe": misc\nodelist.c misc\portable.c -+ @echo Compiling nodelist... -+ @$(TOOL_CC) -Fe"$(OUTDIR)\nodelist.exe" misc\nodelist.c misc\portable.c - - printinfo: - @echo on -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/os2-emx/Makefile binkd/mkfls/os2-emx/Makefile ---- binkd_pgul/mkfls/os2-emx/Makefile 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/mkfls/os2-emx/Makefile 2026-04-26 13:12:44.342586479 +0100 -@@ -13,7 +13,7 @@ - LFLAGS=-Los2 - LIBS=-lsocket -lresolv - NTLM_SRC=ntlm/des_enc.c ntlm/helpers.c ntlm/ecb_enc.c ntlm/md4_dgst.c ntlm/set_key.c --SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c ${NTLM_SRC} -+SRCS=binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c os2/gettid.c os2/sem.c ftndom.c ftnnode.c os2/getfree.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c setpttl.c https.c md5b.c crypt.c srv_gai.c os2/ns_parse.c bsycleanup.c ${NTLM_SRC} - TARGET=binkd2emx - - #PERLDIR=../perl5.00553/os2 -@@ -122,7 +122,33 @@ - - TARGET:=$(TARGET).exe - --all: $(TARGET) -+all: $(TARGET) utils -+ -+TOOL_CFLAGS = $(CFLAGS) -I. -I misc -+ -+utils: decompress process_tic freq srifreq nodelist -+ -+decompress: misc/decompress.c -+ @echo Compiling decompress... -+ @$(CC) $(TOOL_CFLAGS) -o decompress.exe misc/decompress.c misc/portable.c os2/dirent.c -+ -+process_tic: misc/process_tic.c -+ @echo Compiling process_tic... -+ @$(CC) $(TOOL_CFLAGS) -o process_tic.exe misc/process_tic.c misc/portable.c os2/dirent.c -+ -+freq: misc/freq.c -+ @echo Compiling freq... -+ @$(CC) $(TOOL_CFLAGS) -o freq.exe misc/freq.c misc/portable.c -+ -+srifreq: misc/srifreq.c -+ @echo Compiling srifreq... -+ @$(CC) $(TOOL_CFLAGS) -o srifreq.exe misc/srifreq.c misc/portable.c os2/dirent.c -+ -+nodelist: misc/nodelist.c -+ @echo Compiling nodelist... -+ @$(CC) $(TOOL_CFLAGS) -o nodelist.exe misc/nodelist.c misc/portable.c -+ -+.PHONY: utils decompress process_tic freq srifreq nodelist - - .c.o: - @echo Compiling $*.c... -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/unix/Makefile.analyze.unix binkd/mkfls/unix/Makefile.analyze.unix ---- binkd_pgul/mkfls/unix/Makefile.analyze.unix 1970-01-01 00:00:00.000000000 +0000 -+++ binkd/mkfls/unix/Makefile.analyze.unix 2026-04-25 16:52:02.225860998 +0100 -@@ -0,0 +1,65 @@ -+# Makefile.analyze.unix -- Static analysis for Unix/Linux code only -+# Excludes Amiga-specific code (amiga/ directory) -+# Usage: make -f Makefile.analyze.unix -+ -+# Unix-portable sources only (no amiga/ directory) -+UNIX_SRCS = \ -+ binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c \ -+ bsy.c inbound.c breaksig.c branch.c ftndom.c ftnnode.c srif.c pmatch.c \ -+ readflo.c prothlp.c iptools.c run.c binlog.c exitproc.c getw.c xalloc.c \ -+ setpttl.c https.c md5b.c crypt.c compress.c -+ -+# Unix-specific files (if any) -+UNIX_SYS_SRCS = \ -+ unix/getwd.c unix/lock.c unix/tcperr.c -+ -+INCLUDES = -I. -Iunix -+ -+CPPCHECK_FLAGS = \ -+ --enable=all \ -+ --inconclusive \ -+ --std=c89 \ -+ --quiet \ -+ --suppress=missingIncludeSystem \ -+ --suppress=unusedFunction \ -+ $(INCLUDES) -+ -+# Log files -+LOG_DIR = analysis_logs -+CPPCHECK_LOG = $(LOG_DIR)/cppcheck_unix.log -+CLANG_TIDY_LOG = $(LOG_DIR)/clang_tidy_unix.log -+ -+.PHONY: all cppcheck clang-tidy analyze clean -+ -+all: analyze -+ -+cppcheck: -+ @mkdir -p $(LOG_DIR) -+ @echo "=== cppcheck Unix/Linux code ===" -+ @echo "Saving output to: $(CPPCHECK_LOG)" -+ @cppcheck $(CPPCHECK_FLAGS) $(UNIX_SRCS) 2>&1 | tee $(CPPCHECK_LOG) || true -+ @echo "=== done ===" -+ @echo "Log saved: $(CPPCHECK_LOG)" -+ -+clang-tidy: -+ @mkdir -p $(LOG_DIR) -+ @echo "=== clang-tidy Unix/Linux code ===" -+ @echo "Saving output to: $(CLANG_TIDY_LOG)" -+ @echo "clang-tidy analysis started at $$(date)" > $(CLANG_TIDY_LOG) -+ @for src in $(UNIX_SRCS); do \ -+ echo "" >> $(CLANG_TIDY_LOG); \ -+ echo "=== Analyzing: $$src ===" | tee -a $(CLANG_TIDY_LOG); \ -+ clang-tidy $$src --checks=-*,clang-analyzer-*,bugprone-*,portability-* -- \ -+ $(INCLUDES) -DUNIX -DHAVE_SNPRINTF -std=c89 2>&1 | tee -a $(CLANG_TIDY_LOG) || true; \ -+ done -+ @echo "=== done ===" -+ @echo "Log saved: $(CLANG_TIDY_LOG)" -+ -+analyze: cppcheck clang-tidy -+ -+quick: -+ @echo "=== Quick error check (Unix) ===" -+ @cppcheck --enable=error --std=c89 --quiet $(INCLUDES) $(UNIX_SRCS) 2>&1 || true -+ -+clean: -+ @true -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/mkfls/unix/Makefile.in binkd/mkfls/unix/Makefile.in ---- binkd_pgul/mkfls/unix/Makefile.in 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/mkfls/unix/Makefile.in 2026-04-26 13:11:58.084940863 +0100 -@@ -12,17 +12,17 @@ - MANDIR=$(DATADIR)/man - DOCDIR=$(DATADIR)/doc/$(APPL) - --SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c @OPT_SRC@ -+SRCS=md5b.c binkd.c readcfg.c tools.c ftnaddr.c ftnq.c client.c server.c protocol.c bsy.c inbound.c breaksig.c branch.c unix/rename.c unix/getfree.c ftndom.c ftnnode.c srif.c pmatch.c readflo.c prothlp.c iptools.c rfc2553.c run.c binlog.c exitproc.c getw.c xalloc.c crypt.c unix/setpttl.c unix/daemonize.c bsycleanup.c @OPT_SRC@ - OBJS=${SRCS:.c=.o} - AUTODEFS=@DEFS@ - AUTOLIBS=@LIBS@ --DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -+DEFINES=$(AUTODEFS) -DHAVE_FORK -DUNIX -DOS="\"UNIX\"" -DPROTOTYPES - CPPFLAGS=@CPPFLAGS@ - CFLAGS=@CFLAGS@ - LDFLAGS=@LDFLAGS@ - LIBS=$(AUTOLIBS) - --all: compile banner -+all: compile banner utils - - compile: $(APPL) - -@@ -48,6 +48,32 @@ - @echo " run \`configure --prefix=/another/path' and go to step 1. " - @echo - -+utils: decompress process_tic freq srifreq nodelist -+ -+TOOL_CFLAGS = $(DEFINES) $(CPPFLAGS) $(CFLAGS) -I. -I misc -+ -+decompress: misc/decompress.c misc/portable.c misc/portable.h -+ @echo Compiling decompress... -+ @$(CC) $(TOOL_CFLAGS) -o $@ misc/decompress.c misc/portable.c -+ -+process_tic: misc/process_tic.c misc/portable.c misc/portable.h -+ @echo Compiling process_tic... -+ @$(CC) $(TOOL_CFLAGS) -o $@ misc/process_tic.c misc/portable.c -+ -+freq: misc/freq.c misc/portable.c misc/portable.h -+ @echo Compiling freq... -+ @$(CC) $(TOOL_CFLAGS) -o $@ misc/freq.c misc/portable.c -+ -+srifreq: misc/srifreq.c misc/portable.c misc/portable.h -+ @echo Compiling srifreq... -+ @$(CC) $(TOOL_CFLAGS) -o $@ misc/srifreq.c misc/portable.c -+ -+nodelist: misc/nodelist.c misc/portable.c -+ @echo Compiling nodelist... -+ @$(CC) $(TOOL_CFLAGS) -o $@ misc/nodelist.c misc/portable.c -+ -+.PHONY: decompress process_tic freq srifreq nodelist -+ - .version: $(APPL) - @./$(APPL) -v | $(AWK) '{ print $$2; }' > $@ - -@@ -66,6 +92,7 @@ - clean: - rm -f *.[bo] unix/*.[bo] ntlm/*.[bo] *.BAK *.core *.obj *.err - rm -f *~ core config.cache config.log config.status -+ rm -f decompress process_tic freq srifreq nodelist - - cleanall: clean - rm -f $(APPL) Makefile Makefile.dep Makefile.in -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/protocol.c binkd/protocol.c ---- binkd_pgul/protocol.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/protocol.c 2026-04-26 09:45:52.230049023 +0100 -@@ -45,12 +45,19 @@ - #include "md5b.h" - #include "crypt.h" - #include "compress.h" -+#ifdef AMIGA -+#include "amiga/proto_amiga.h" -+#endif - - #ifdef WITH_PERL - #include "perlhooks.h" - #endif - #include "rfc2553.h" - -+#if defined(HAVE_THREADS) || defined(AMIGA) -+extern MUTEXSEM lsem; -+#endif -+ - /* define to enable val's code for -ip checks (default is gul's code) */ - #undef VAL_STYLE - #ifdef VAL_STYLE -@@ -63,7 +70,7 @@ - /* - * Fills <> with initial values, allocates buffers, etc. - */ --static int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) -+int init_protocol (STATE *state, SOCKET socket_in, SOCKET socket_out, FTN_NODE *to, FTN_ADDR *fa, BINKD_CONFIG *config) - { - char val[4]; - socklen_t lval; -@@ -126,6 +133,12 @@ - #endif - setsockopts (state->s_in = socket_in); - setsockopts (state->s_out = socket_out); -+ -+#if defined(AMIGA) -+ setsockopts_amiga(socket_in, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); -+ setsockopts_amiga(socket_out, config->tcp_nodelay, config->so_sndbuf, config->so_rcvbuf); -+#endif -+ - TF_ZERO (&state->in); - TF_ZERO (&state->out); - TF_ZERO (&state->flo); -@@ -181,7 +194,7 @@ - /* - * Clears protocol buffers and queues, closes files, etc. - */ --static int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) -+int deinit_protocol (STATE *state, BINKD_CONFIG *config, int status) - { - int i; - -@@ -225,7 +238,7 @@ - } - - /* Process rcvdlist */ --static FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) -+FTNQ *process_rcvdlist (STATE *state, FTNQ *q, BINKD_CONFIG *config) - { - int i; - -@@ -315,7 +328,7 @@ - /* - * Sends next msg from the msg queue or next data block - */ --static int send_block (STATE *state, BINKD_CONFIG *config) -+int send_block (STATE *state, BINKD_CONFIG *config) - { - int i, n, save_errno; - const char *save_err; -@@ -2091,7 +2104,7 @@ - return 0; - } - --static int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) -+int ND_set_status(char *status, FTN_ADDR *fa, STATE *state, BINKD_CONFIG *config) - { - char buf[MAXPATHLEN+1]; - FILE *f; -@@ -2145,7 +2158,8 @@ - - *extra = ""; - if (state->z_cansend && state->extcmd && state->out.size >= config->zminsize -- && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first)) { -+ && zrule_test(ZRULE_ALLOW, state->out.netname, config->zrules.first) -+ && !(state->to && state->to->NC_flag)) { - #ifdef WITH_BZLIB2 - if (!state->z_send && (state->z_cansend & 2)) { - *extra = " BZ2"; state->z_send = 2; -@@ -2448,6 +2462,7 @@ - { - char szAddr[FTN_ADDR_SZ + 1]; - -+ memset(szAddr, 0, sizeof(szAddr)); - ftnaddress_to_str (szAddr, &state->sent_fls[n].fa); - state->bytes_sent += state->sent_fls[n].size; - ++state->files_sent; -@@ -2538,7 +2553,7 @@ - }; - - /* Recvs next block, processes msgs or writes down the data from the remote */ --static int recv_block (STATE *state, BINKD_CONFIG *config) -+int recv_block (STATE *state, BINKD_CONFIG *config) - { - int no; - -@@ -2770,7 +2785,7 @@ - return 1; - } - --static int banner (STATE *state, BINKD_CONFIG *config) -+int banner (STATE *state, BINKD_CONFIG *config) - { - int tz; - char szLocalTime[60]; -@@ -2850,7 +2865,7 @@ - return 1; - } - --static int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) -+int start_file_transfer (STATE *state, FTNQ *file, BINKD_CONFIG *config) - { - struct stat sb; - FILE *f = NULL; -@@ -3031,7 +3046,7 @@ - return 1; - } - --static void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) -+void log_end_of_session (int status, STATE *state, BINKD_CONFIG *config) - { - char szFTNAddr[FTN_ADDR_SZ + 1]; - -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/readcfg.c binkd/readcfg.c ---- binkd_pgul/readcfg.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/readcfg.c 2026-04-26 15:04:45.397917107 +0100 -@@ -44,6 +44,10 @@ - */ - BINKD_CONFIG *current_config; - -+#ifdef AMIGA -+extern struct SignalSemaphore config_sem; -+#endif -+ - /* - * Temporary static structure for configuration reading - */ -@@ -210,9 +214,15 @@ - snprintf(c->iport, sizeof(c->iport), "%s", find_port("")); - snprintf(c->oport, sizeof(c->oport), "%s", find_port("")); - c->call_delay = 60; -+ c->no_call_delay = 0; - c->rescan_delay = 60; - c->nettimeout = DEF_TIMEOUT; - c->oblksize = DEF_BLKSIZE; -+ -+#ifdef AMIGA -+ c->tcp_nodelay = 0; -+#endif -+ - #if defined(WITH_ZLIB) || defined(WITH_BZLIB2) - c->zminsize = 1024; - c->zlevel = 0; -@@ -391,8 +401,16 @@ - {"oport", read_port, &work_config.oport, 0, 0}, - {"rescan-delay", read_time, &work_config.rescan_delay, 1, DONT_CHECK}, - {"call-delay", read_time, &work_config.call_delay, 1, DONT_CHECK}, -+ {"no-call-delay", read_bool, &work_config.no_call_delay, 0, 0}, - {"timeout", read_time, &work_config.nettimeout, 1, DONT_CHECK}, - {"oblksize", read_int, &work_config.oblksize, MIN_BLKSIZE, MAX_BLKSIZE}, -+ -+#ifdef AMIGA -+ {"tcp-nodelay", read_bool, &work_config.tcp_nodelay, 0, 0}, -+ {"so-sndbuf", read_int, &work_config.so_sndbuf, 0, 65535}, -+ {"so-rcvbuf", read_int, &work_config.so_rcvbuf, 0, 65535}, -+#endif -+ - {"maxservers", read_int, &work_config.max_servers, 0, DONT_CHECK}, - {"maxclients", read_int, &work_config.max_clients, 0, DONT_CHECK}, - {"inbound", read_string, work_config.inbound, 'd', 0}, -@@ -666,7 +684,7 @@ - exp_ftnaddress (&fa, work_config.pAddr, work_config.nAddr, work_config.pDomains.first); - pn = add_node (&fa, NULL, password, pkt_pwd, out_pwd, '-', NULL, NULL, - NR_USE_OLD, ND_USE_OLD, MD_USE_OLD, RIP_USE_OLD, -- HC_USE_OLD, NP_USE_OLD, NULL, AF_USE_OLD, -+ HC_USE_OLD, NP_USE_OLD, NC_USE_OLD, NULL, AF_USE_OLD, - #ifdef BW_LIM - BW_DEF, BW_DEF, - #endif -@@ -830,11 +848,10 @@ - if (!new_config) - { - /* Config error. Abort or continue? */ -- if (current_config) -- { -- Log(1, "error in configuration, using old config"); -- unlock_config_structure(&work_config, 0); -- } -+ unlock_config_structure(&work_config, 0); -+ -+ if (current_config) -+ Log(1, "error in configuration, using old config"); - } - - return new_config; -@@ -857,6 +874,16 @@ - Log (2, "got SIGHUP"); - need_reload = got_sighup; - got_sighup = 0; -+#elif defined(AMIGA) -+ /* No SIGHUP on AmigaOS: detect config change by mtime */ -+ need_reload = 0; -+ if (current_config->config_list.first) -+ { -+ if (stat(current_config->config_list.first->path, &sb) == 0 && -+ current_config->config_list.first->mtime != 0 && -+ sb.st_mtime != current_config->config_list.first->mtime) -+ need_reload = 1; -+ } - #else - need_reload = 0; - #endif -@@ -886,8 +913,75 @@ - } - #endif - -- if (!need_reload) -- return 0; -+ if (!need_reload) -+ return 0; -+ -+#ifdef AMIGA -+ /* Prevent reload storms and partial-file reads. -+ * -+ * On AmigaOS (and some Unix editors), config files are written in multiple -+ * passes, so binkd may see the mtime change while the file is still being -+ * written. Attempting to parse an incomplete file gives "unknown keyword" -+ * errors, and rapid repeated mtime changes cause bind() to fail because the -+ * previous listen socket has not been released yet. -+ * -+ * Strategy: after first detecting a change, wait until the mtime has been -+ * stable for at least 2 seconds before actually reloading. Also enforce a -+ * minimum of 5 seconds between successive successful reloads. -+ */ -+ { -+ static time_t last_reload = 0; /* time of last successful reload */ -+ static time_t change_seen = 0; /* time we first noticed the change */ -+ static time_t stable_mtime = 0; /* mtime we are waiting to stabilize */ -+ static int reload_pending = 0; /* persists between calls */ -+ time_t now = time(NULL); -+ -+ /* The loop has already updated pc->mtime, so in the next call -+ * need_reload will be 0 even though we haven't reloaded yet. -+ * reload_pending keeps the reload intent alive. -+ */ -+ if (need_reload) reload_pending = 1; -+ -+ if (!reload_pending) -+ return 0; -+ -+ /* Get the mtime of the primary config file */ -+ { struct stat sb2; -+ time_t cur_mtime = 0; -+ -+ if (current_config->config_list.first && stat(current_config->config_list.first->path, &sb2) == 0) -+ cur_mtime = sb2.st_mtime; -+ -+ /* mtime just changed (or changed again) — reset the stability clock */ -+ if (cur_mtime != stable_mtime) -+ { -+ stable_mtime = cur_mtime; -+ change_seen = now; -+ Log(5, "checkcfg: config mtime changed, waiting for stability..."); -+ return 0; -+ } -+ -+ /* mtime has been stable since change_seen */ -+ if (now - change_seen < 2) -+ { -+ Log(5, "checkcfg: config not yet stable (%lds), waiting...", -+ (long)(now - change_seen)); -+ return 0; -+ } -+ } -+ -+ /* Enforce minimum gap between reloads to let the OS release sockets */ -+ if (now - last_reload < 5) -+ { -+ Log(5, "checkcfg: reload suppressed (too soon, %lds)", (long)(now - last_reload)); -+ return 0; -+ } -+ -+ last_reload = now; -+ reload_pending = 0; /* Once the intent has been consumed, the reload is executed */ -+ } -+#endif -+ - /* Reload starting from first file in list */ - Log(2, "Reloading configuration..."); - pc = current_config->config_list.first; -@@ -1125,7 +1219,7 @@ - char *w[ARGNUM], *tmp, *pkt_pwd, *out_pwd, *pipe; - int i, j; - int NR_flag = NR_USE_OLD, ND_flag = ND_USE_OLD, HC_flag = HC_USE_OLD, -- MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, restrictIP = RIP_USE_OLD, -+ MD_flag = MD_USE_OLD, NP_flag = NP_USE_OLD, NC_flag = NC_USE_OLD, restrictIP = RIP_USE_OLD, - IP_afamily = AF_USE_OLD; - #ifdef BW_LIM - long bw_send = BW_DEF, bw_recv = BW_DEF; -@@ -1175,6 +1269,8 @@ - HC_flag = HC_OFF; - else if (STRICMP (tmp, "-noproxy") == 0) - NP_flag = NP_ON; -+ else if (STRICMP (tmp, "-nc") == 0) -+ NC_flag = NC_ON; - #ifdef BW_LIM - else if (STRICMP (tmp, "-bw") == 0) - { -@@ -1258,7 +1354,7 @@ - - split_passwords(w[2], &pkt_pwd, &out_pwd); - pn = add_node (&fa, w[1], w[2], pkt_pwd, out_pwd, (char)(w[3] ? w[3][0] : '-'), w[4], w[5], -- NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, pipe, -+ NR_flag, ND_flag, MD_flag, restrictIP, HC_flag, NP_flag, NC_flag, pipe, - IP_afamily, - #ifdef BW_LIM - bw_send, bw_recv, -@@ -1991,6 +2087,9 @@ - if (fn->pkt_pwd) pwd_len += strlen(fn->pkt_pwd)+1; else pwd_len += 2; - if (fn->out_pwd) pwd_len += strlen(fn->out_pwd)+1; else pwd_len += 2; - pwd = calloc (1, pwd_len+1); -+ /* Guard against null pointer dereference if calloc fails */ -+ if (!pwd) -+ return 0; - strcpy(pwd, fn->pwd); - if (fn->pkt_pwd != (char*)&(fn->pwd) || fn->out_pwd != (char*)&(fn->pwd)) { - strcat(strcat(pwd, ","), (fn->pkt_pwd) ? fn->pkt_pwd : "-"); -@@ -2324,4 +2423,3 @@ - return 1; - } - #endif -- -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/readcfg.h binkd/readcfg.h ---- binkd_pgul/readcfg.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/readcfg.h 2026-04-25 20:39:08.569961699 +0100 -@@ -111,8 +111,16 @@ - #endif - int nettimeout; - int connect_timeout; -- int rescan_delay; -+ -+#ifdef AMIGA -+ int tcp_nodelay; -+ int so_sndbuf; -+ int so_rcvbuf; -+#endif -+ - int call_delay; -+ int no_call_delay; -+ int rescan_delay; - int max_servers; - int max_clients; - int kill_dup_partial_files; -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/readdir.h binkd/readdir.h ---- binkd_pgul/readdir.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/readdir.h 2026-04-22 20:37:22.121954324 +0100 -@@ -23,6 +23,8 @@ - #elif defined(__MINGW32__) - #include - #include -+#elif defined(AMIGA) -+#include "amiga/dirent.h" - #else - #include - #include -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/README.md binkd/README.md ---- binkd_pgul/README.md 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/README.md 2026-04-22 18:44:10.839451402 +0100 -@@ -6,6 +6,79 @@ - - ## Compiling - -+AmigaOS: -+ -+Copy "mkfls/amiga/Makefile" to root and make -+ -+Need ADE to compile project with ixemul library. -+ -+https://aminet.net/package/dev/gcc/ADE -+ -+It no longer requires ADE to be installed for execution, as it doesn't need /bin/sh to run scripts or external programs from binkd.conf. However, it does require ixemul.library and ixnet.library, either in the libs directory or in the same directory as the executable. -+ -+You can find the ADE ixemul.library and ixnet.library libraries in the aminet package or in the released versions, along with the program and utilities already compiled. -+ -+https://github.com/skbn/binkd/releases -+ -+5.Work:fido> version work:fido/ixnet.library -+ixnet.library 63.1 -+ -+5.Work:fido> version work:fido/ixemul.library -+ixemul.library 63.1 -+ -+ -+I've attached six programs for your assistance: -+ -+[decompress] which decompresses incoming files in lha or zip format, if necessary. -+ -+``` -+[freq] which generates file requests in ASO mode and places the necessary files in outbound directory. -+ -+Usage:freq Z:N/NODE[.POINT] -+``` -+``` -+[freq_bso] same but BSO style -+Usage: freq_bso Z:N/NODE[.POINT] -+ No point : .0ZZ/NNNNNNNN.req -+ Point : .0ZZ/N -+``` -+ -+``` -+[process_tic] which processes tic files and places them in the filebox folder. -+With --copypublic option, copy the file you receive to a directory named pub/ from PROGDIR -+>> exec "path/process_tic --copypublic" *.tic *.TIC -+>> exec "path/process_tic" *.tic *.TIC -+``` -+ -+``` -+[srifreq] copy of "misc/srifreq" but in c. SRIF-compatible file-request server for binkd -+ -+Usage: srifreq [] -+ SRIF control file passed by binkd (exec "srifreq *S") -+ directory containing public downloadable files -+ log file path (pass "" or - to disable logging) -+ optional file mapping magic names to real paths -+ -+Aliases file format (one alias per line, # = comment): -+MAGIC_NAME relative/or/absolute/path/to/file -+Example: -+NODELIST pub/NODELIST.ZIP -+FILES pub/ALLFILES.TXT -+ -+binkd.conf example: exec "srifreq *S pub log/srifreq.log aliases.txt" *.req -+``` -+ -+``` -+[nodelist] FidoNet nodelist compiler for binkd - AmigaOS version in c from "misc/nodelist.pl" -+ -+Usage: nodelist [] -+``` -+ -+Also compileable on *nix >> "gcc -O2 -Wall -o nodelist nodelist.c" -+ -+BUGFIXES: -+Option "-C" to reload config It remains unstable -+ - non-UNIX: - - 1. Find in mkfls/ a subdirectory for your system/compiler, copy all files -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/rfc2553.h binkd/rfc2553.h ---- binkd_pgul/rfc2553.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/rfc2553.h 2026-04-26 10:31:43.475331092 +0100 -@@ -21,8 +21,52 @@ - - #include "iphdr.h" - -+/* Amiga: define EAI_* before including netdb.h to prevent libnix redefinition */ -+#if defined(AMIGA) -+#include -+#include -+#include -+#include -+ -+ #undef EAI_NONAME -+ #undef EAI_AGAIN -+ #undef EAI_FAIL -+ #undef EAI_NODATA -+ #undef EAI_FAMILY -+ #undef EAI_SOCKTYPE -+ #undef EAI_SERVICE -+ #undef EAI_ADDRFAMILY -+ #undef EAI_MEMORY -+ #undef EAI_SYSTEM -+ #undef EAI_UNKNOWN -+ #define EAI_NONAME -1 -+ #define EAI_AGAIN -2 -+ #define EAI_FAIL -3 -+ #define EAI_NODATA -4 -+ #define EAI_FAMILY -5 -+ #define EAI_SOCKTYPE -6 -+ #define EAI_SERVICE -7 -+ #define EAI_ADDRFAMILY -8 -+ #define EAI_MEMORY -9 -+ #define EAI_SYSTEM -10 -+ #define EAI_UNKNOWN -11 -+ -+#include -+ -+/* EAI_ADDRFAMILY is BSD/macOS specific; Linux/glibc does not define it -+ * Map it to EAI_FAMILY which has the same meaning on those platforms */ -+#ifndef EAI_ADDRFAMILY -+#ifdef EAI_FAMILY -+#define EAI_ADDRFAMILY EAI_FAMILY -+#else -+#define EAI_ADDRFAMILY -9 -+#endif -+#endif -+ -+#endif -+ - /* Autosense getaddrinfo */ --#if defined(AI_PASSIVE) && defined(EAI_NONAME) -+#if defined(AI_PASSIVE) && defined(EAI_NONAME) && !defined(AMIGA) - #define HAVE_GETADDRINFO - #endif - -@@ -47,6 +91,18 @@ - }; - #define addrinfo addrinfo_emu - -+#ifdef AMIGA -+#ifdef getaddrinfo -+#undef getaddrinfo -+#endif -+#ifdef freeaddrinfo -+#undef freeaddrinfo -+#endif -+#ifdef gai_strerror -+#undef gai_strerror -+#endif -+#endif -+ - int getaddrinfo(const char *nodename, const char *servname, - const struct addrinfo *hints, - struct addrinfo **res); -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/run.c binkd/run.c ---- binkd_pgul/run.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/run.c 2026-04-26 10:31:17.108612481 +0100 -@@ -18,6 +18,11 @@ - #ifdef HAVE_UNISTD_H - #include - #endif -+#ifdef AMIGA -+#include -+#include -+#include -+#endif - - #include "sys.h" - #include "run.h" -@@ -40,10 +45,128 @@ - #define SHELL (getenv("COMSPEC") ? getenv("COMSPEC") : "command.com") - #define SHELL_META "\"\'\\%<>|&^@" - #define SHELLOPT "/c" -+#elif defined(AMIGA) -+/* AmigaOS shell */ -+#define SHELL "c:execute" -+#define SHELL_META "\"\'\\*?(){};&|<>" - #else - #error "Unknown platform" - #endif - -+#ifdef AMIGA -+/* run(): execute an AmigaDOS command via SystemTagList() with NIL: I/O -+ * Runs synchronously so binkd waits for completion (needed for srifreq -+ * to create .rsp before parse_response). NIL: I/O prevents CLI freezing -+ * Error output goes to a temporary file for logging on failure */ -+int run(char *cmd) -+{ -+ /* All declarations at the top for C89/ADE GCC 2.95 compatibility */ -+ BPTR nil_in; -+ char errfile[MAXPATHLEN]; -+ BPTR err_out = 0; -+ int rc = 0; -+ char cmd_copy[MAXPATHLEN]; -+ char *cmd_start; -+ char *cmd_end; -+ BPTR lock; -+ struct TagItem exec_tags[5]; -+ BPTR errfile_ptr; -+ char buf[512]; -+ int len; -+ -+ /* Open NIL: for input/output */ -+ nil_in = Open("NIL:", MODE_OLDFILE); -+ -+ /* Create temporary error file in current directory */ -+ snprintf(errfile, sizeof(errfile), "binkd_err_%ld.txt", (long)time(NULL)); -+ err_out = Open(errfile, MODE_NEWFILE); -+ if (err_out == 0) -+ { -+ Log(2, "cannot create error file %s, using NIL: for error output", errfile); -+ } -+ -+ /* Extract the command (first word) to check if it exists */ -+ strncpy(cmd_copy, cmd, sizeof(cmd_copy) - 1); -+ cmd_copy[sizeof(cmd_copy) - 1] = '\0'; -+ cmd_start = cmd_copy; /* Work on copy, never modify original cmd */ -+ -+ /* Skip leading whitespace */ -+ while (*cmd_start && (*cmd_start == ' ' || *cmd_start == '\t')) -+ cmd_start++; -+ -+ /* Find end of command (first space or end) */ -+ cmd_end = cmd_start; -+ while (*cmd_end && *cmd_end != ' ' && *cmd_end != '\t') -+ cmd_end++; -+ *cmd_end = '\0'; -+ -+ /* Check if command exists */ -+ lock = Lock((STRPTR)cmd_start, SHARED_LOCK); -+ if (lock == 0) -+ { -+ Log(2, "command not found, skipping: '%s'", cmd_start); -+ Close(nil_in); -+ if (err_out) -+ Close(err_out); -+ DeleteFile((STRPTR)errfile); -+ return 0; -+ } -+ UnLock(lock); -+ -+ Log(3, "executing '%s'", cmd); -+ -+ /* Set up tags with NIL: input and error file output. -+ * Use NP_* (New Process) tags instead of SYS_* to avoid sharing -+ * file handles with parent process - prevents stderr from being -+ * closed when child exits. */ -+ exec_tags[0].ti_Tag = NP_Input; -+ exec_tags[0].ti_Data = (ULONG)nil_in; -+ exec_tags[1].ti_Tag = NP_Output; -+ exec_tags[1].ti_Data = (ULONG)nil_in; -+ exec_tags[2].ti_Tag = NP_Error; -+ exec_tags[2].ti_Data = (ULONG)err_out; -+ exec_tags[3].ti_Tag = NP_Synchronous; -+ exec_tags[3].ti_Data = TRUE; -+ exec_tags[4].ti_Tag = TAG_DONE; -+ exec_tags[4].ti_Data = 0; -+ -+ rc = SystemTagList((STRPTR)cmd, exec_tags); -+ -+ /* Close handles */ -+ Close(nil_in); -+ if (err_out) -+ Close(err_out); -+ -+ /* Log error output if command failed */ -+ if (rc != 0) -+ { -+ errfile_ptr = Open(errfile, MODE_OLDFILE); -+ if (errfile_ptr) -+ { -+ Log(2, "command failed with rc=%d, output:", rc); -+ while ((len = Read(errfile_ptr, buf, sizeof(buf) - 1)) > 0) -+ { -+ buf[len] = '\0'; -+ Log(2, "%s", buf); -+ } -+ Close(errfile_ptr); -+ } -+ } -+ -+ DeleteFile((STRPTR)errfile); -+ return rc; -+} -+ -+/* run3(): pipe/tunnel not supported on AmigaOS without ixemul. */ -+int run3(const char *cmd, int *in, int *out, int *err) -+{ -+ (void)cmd; (void)in; (void)out; (void)err; -+ Log(1, "run3: pipe connections not supported on Amiga"); -+ return -1; -+} -+#endif /* AMIGA */ -+ -+#ifndef AMIGA - int run (char *cmd) - { - int rc=-1; -@@ -111,6 +234,7 @@ - #endif - return rc; - } -+#endif /* !AMIGA */ - - #ifdef __MINGW32__ - static int set_cloexec(int fd) -@@ -136,6 +260,7 @@ - } - #endif - -+#ifndef AMIGA - int run3 (const char *cmd, int *in, int *out, int *err) - { - int pid; -@@ -162,6 +287,14 @@ - } - - #ifdef HAVE_FORK -+#ifdef AMIGA -+ /* Pipe tunneling not supported on AmigaOS without fork() */ -+ Log(1, "run3: pipe/tunnel not supported on Amiga: %s", cmd); -+ if (in) close(pin[1]), close(pin[0]); -+ if (out) close(pout[1]), close(pout[0]); -+ if (err) close(perr[1]), close(perr[0]); -+ return -1; -+#else - pid = fork(); - if (pid == -1) - { -@@ -194,7 +327,11 @@ - if (strpbrk(cmd, SHELL_META)) - { - shell = SHELL; -+#ifdef AMIGA -+ execl(shell, shell, cmd, (char *)NULL); -+#else - execl(shell, shell, SHELLOPT, cmd, (char *)NULL); -+#endif - } - else - { -@@ -232,6 +369,7 @@ - *err = perr[0]; - close(perr[1]); - } -+#endif /* !AMIGA */ - #else - - /* redirect stdin/stdout/stderr takes effect for all threads */ -@@ -336,3 +474,4 @@ - return pid; - } - -+#endif /* !AMIGA */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/sem.h binkd/sem.h ---- binkd_pgul/sem.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/sem.h 2026-04-26 10:09:54.148733026 +0100 -@@ -34,6 +34,14 @@ - #include - typedef struct SignalSemaphore MUTEXSEM; - -+#ifdef AMIGA -+typedef struct -+{ -+ struct Task *waiter; -+ ULONG sigbit; -+} EVENTSEM; -+#endif -+ - #elif defined(WITH_PTHREADS) - - #include -@@ -73,25 +81,27 @@ - * Initialise Event Semaphores. - */ - --int _InitEventSem (void *); -+#ifdef AMIGA -+int _InitEventSem (EVENTSEM *); - - /* - * Post Semaphore. - */ - --int _PostSem (void *); -+int _PostSem (EVENTSEM *); - - /* - * Wait Semaphore. - */ - --int _WaitSem (void *, int); -+int _WaitSem (EVENTSEM *, int); - - /* - * Clean Event Semaphores. - */ - --int _CleanEventSem (void *); -+int _CleanEventSem (EVENTSEM *); -+#endif - - #if defined(WITH_PTHREADS) - #define InitSem(sem) pthread_mutex_init(sem, NULL) -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/server.c binkd/server.c ---- binkd_pgul/server.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/server.c 2026-04-26 10:30:03.538324924 +0100 -@@ -23,6 +23,10 @@ - #include - #endif - -+#ifdef AMIGA -+#include "amiga/bsdsock.h" -+#endif -+ - #include "sys.h" - #include "iphdr.h" - #include "readcfg.h" -@@ -39,6 +43,10 @@ - #endif - #include "rfc2553.h" - -+#if defined(HAVE_THREADS) || defined(AMIGA) -+extern EVENTSEM eothread; -+#endif -+ - int n_servers = 0; - int ext_rand = 0; - -@@ -53,7 +61,8 @@ - void *cperl; - #endif - --#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(DEBUGCHILD) -+/* Prevent shared socket closure */ -+#if defined(HAVE_FORK) && !defined(HAVE_THREADS) && !defined(AMIGA) && !defined(DEBUGCHILD) - int curfd; - pidcmgr = 0; - for (curfd=0; curfdai_addr, ai->ai_addrlen) != 0) - { -- Log(1, "servmgr bind(): %s", TCPERR ()); -- soclose(sockfd[sockfd_used]); -- return -1; -+#ifdef AMIGA -+ /* bsdsocket may hold the port briefly after socket close. Retry */ -+ int bind_retries = 6; -+ -+ while (bind(sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) -+ { -+ if (--bind_retries == 0) -+ { -+ Log(1, "servmgr bind(): %s", TCPERR()); -+ soclose(sockfd[sockfd_used]); -+ return -1; -+ } -+ -+ Log(2, "servmgr bind(): %s, retry in 2s...", TCPERR()); -+ sleep(2); -+ } -+#else -+ if (bind (sockfd[sockfd_used], ai->ai_addr, ai->ai_addrlen) != 0) -+ { -+ Log(1, "servmgr bind(): %s", TCPERR ()); -+ soclose(sockfd[sockfd_used]); -+ return -1; -+ } -+#endif - } - if (listen (sockfd[sockfd_used], 5) != 0) - { -@@ -168,6 +201,12 @@ - - setproctitle ("server manager (listen %s)", config->listen.first->port); - -+ /* Save rescan_delay locally. checkcfg() may free 'config' (old config -+ * is released when usageCount reaches 0 after reload), so we must not -+ * access config->rescan_delay inside the loop after a reload */ -+ { -+ int rescan = config->rescan_delay; -+ - for (;;) - { - struct timeval tv; -@@ -183,7 +222,7 @@ - maxfd = sockfd[curfd]; - } - tv.tv_usec = 0; -- tv.tv_sec = CHECKCFG_INTERVAL; -+ tv.tv_sec = rescan; - unblocksig(); - check_child(&n_servers); - n = select(maxfd+1, &r, NULL, NULL, &tv); -@@ -192,8 +231,20 @@ - { case 0: /* timeout */ - if (checkcfg()) - { -+ /* config may have been freed by checkcfg() — read rescan from -+ * the new current_config before returning for restart */ -+ { -+ BINKD_CONFIG *nc = lock_current_config(); -+ if (nc) -+ { -+ rescan = nc->rescan_delay; -+ unlock_config_structure(nc, 0); -+ } -+ } -+ - for (curfd=0; curfdrescan_delay; -+ unlock_config_structure(nc, 0); -+ } -+ } -+ - for (curfd=0; curfd -+#endif -+ - /* - * Listens... Than calls protocol() - */ -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/sys.h binkd/sys.h ---- binkd_pgul/sys.h 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/sys.h 2026-04-26 08:48:04.281489117 +0100 -@@ -22,8 +22,28 @@ - #ifdef HAVE_STDINT_H - #include - #endif -+#ifdef AMIGA -+ /* Include Amiga exec proto for Delay() function */ -+ #include -+#endif - #ifdef HAVE_UNISTD_H - #include -+ /* Undefine conflicting unistd.h macros for AMIGA */ -+ #ifdef AMIGA -+ #ifdef getpid -+ #undef getpid -+ #endif -+ -+ #ifdef sleep -+ #undef sleep -+ #endif -+ -+ /* Redefine with our Amiga implementations */ -+ #define getpid() ((int)(ULONG)FindTask(NULL)) -+ -+ #define sleep(s) Delay((ULONG)((s) * 50)) -+ -+ #endif /* AMIGA */ - #endif - #ifdef HAVE_IO_H - #include -@@ -105,6 +125,11 @@ - #define PID() mypid - #endif - -+#ifdef HAVE_FORK -+ #include /* Needed for SIG_BLOCK/SIG_UNBLOCK and WIFEXITED/WEXITSTATUS */ -+ #include -+#endif -+ - #if defined(HAVE_FORK) && defined(HAVE_SIGPROCMASK) && defined(HAVE_WAITPID) && defined(SIG_BLOCK) - void switchsignal(int how); - #define blocksig() switchsignal(SIG_BLOCK) -@@ -292,8 +317,16 @@ - - #ifndef PRIdMAX - #define PRIdMAX "ld" -+#endif -+ -+#ifndef PRIuMAX -+#ifdef AMIGA -+/* On AmigaOS m68k, uintmax_t is long long unsigned int */ -+#define PRIuMAX "llu" -+#else - #define PRIuMAX "lu" - #endif -+#endif - - #ifndef HAVE_STRTOUMAX - #define strtoumax(ptr, endptr, base) strtoul(ptr, endptr, base) -diff -ruN '--exclude=.*' '--exclude=.git' '--exclude=.svn' binkd_pgul/tools.c binkd/tools.c ---- binkd_pgul/tools.c 2026-04-16 17:49:00.000000000 +0100 -+++ binkd/tools.c 2026-04-25 23:49:26.267090428 +0100 -@@ -22,6 +22,10 @@ - #include - #endif - -+#ifdef AMIGA -+#include "amiga/bsdsock.h" -+#endif -+ - #include "sys.h" - #include "readcfg.h" - #include "common.h" -@@ -38,6 +42,12 @@ - #include "nt/w32tools.h" - #endif - -+#if defined(HAVE_THREADS) || defined(AMIGA) -+extern MUTEXSEM lsem; -+#endif -+ -+extern void vLog (int lev, char *s, va_list ap); -+ - /* - * We can call Log() even when we have no config ready. So, we must keep - * internal variables which will be updated when config is loaded -@@ -290,6 +300,10 @@ - char buf[1024]; - int ok = 1; - -+#ifdef AMIGA -+ static int need_newline = 0; -+#endif -+ - /* make string in buffer */ - vsnprintf(buf, sizeof(buf), s, ap); - /* do perl hooks */ -@@ -313,8 +327,20 @@ - if (lev <= current_conlog && !inetd_flag) - { - LockSem(&lsem); -+#ifdef AMIGA -+ /* AmigaOS: go to new line for status messages to avoid overwriting */ -+ if (lev < 0 && need_newline) -+ { -+ need_newline = 0; -+ } -+ fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, -+ tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : "\r"); -+ if (lev >= 0) -+ need_newline = 1; -+#else - fprintf (stderr, "%30.30s\r%c %02d:%02d [%u] %s%s", " ", ch, - tm.tm_hour, tm.tm_min, (unsigned) PID (), buf, (lev >= 0) ? "\n" : ""); -+#endif - fflush (stderr); - ReleaseSem(&lsem); - if (lev < 0) diff --git a/changes.txt b/changes.txt index a6fd4d4a..5c0c3489 100644 --- a/changes.txt +++ b/changes.txt @@ -172,6 +172,7 @@ AMIGA-SPECIFIC CHANGES (#ifdef AMIGA / AMIGADOS_4D_OUTBOUND) amiga/evloop.c: - Non-blocking event loop with WaitSelect(), replaces servmgr()/clientmgr() +- Fixed startup delay: last_rescan=0 after pre-loop try_outbound forces immediate retry in first loop iteration (avoids waiting rescan-delay seconds if DNS failed at startup) amiga/session.c: - Session allocation, accept(), non-blocking connect(), outbound scan @@ -193,6 +194,7 @@ amiga/dirent.c: amiga/proto_amiga.c: - Non-blocking BinkP protocol (amiga_proto_open, amiga_proto_step, amiga_proto_close) +- Fixed premature M_EOB: added !state->in.f to EOB condition — prevents sending M_EOB while a remote file is still being received (avoids "M_EOB but N files pending M_GOT" error on the remote side) --- New Amiga-specific files (.h) --- From 1fc5f98d99d64d4bc41c127420ed921018dc8b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tanaus=C3=BA=20M=2E?= Date: Mon, 27 Apr 2026 02:06:32 +0100 Subject: [PATCH 04/20] misc/freq.c - Added --zone-ext option: use zone extension (outbound.002/) with --bso - Default BSO format: outbound/ (no zone extension, binkd standard) misc/portable.c - Added parse_config_line(): robust config line parser (BOM, tabs, comments inline, whitespace) - Added config_get(): lookup single key in config file - Added config_load(): load entire config into memory cache - Added config_lookup(): lookup key in cached config (no file I/O) - Added config_cache_free(): free cached config memory (renamed from config_free to avoid conflict with srifreq.c) - Added parse_size(): parse size with suffixes (k/K/m/M/g/G) or plain bytes - Added parse_time(): parse time with suffixes (s/m/h/d) or plain seconds misc/portable.h: - Portable declarations and platform-specific includes (POSIX, Win32, OS/2, DOS, AmigaOS) misc/process_tic.c: - Updated parse_config() to use portable.c parse_config_line() misc/srifreq.c: - Updated load_config() to use portable.c config_load(), config_lookup(), config_cache_free() - Renamed local config_free() to srifreq_config_cleanup() to avoid conflict with portable.c - Error messages now go to log file (config logfile) instead of stderr --- changes.txt | 20 +++- misc/freq.c | 67 +++++++---- misc/portable.c | 278 +++++++++++++++++++++++++++++++++++++++++++++ misc/portable.h | 12 ++ misc/process_tic.c | 95 +++++++--------- misc/srifreq.c | 127 +++++++++++---------- 6 files changed, 454 insertions(+), 145 deletions(-) diff --git a/changes.txt b/changes.txt index 5c0c3489..86cd8b7e 100644 --- a/changes.txt +++ b/changes.txt @@ -84,6 +84,8 @@ misc/decompress.c: misc/freq.c: - Create .req/.clo files (ASO flat layout, BSO BinkleyStyle layout) - Uses MAXPATHLEN from portable.h +- Added --zone-ext option: use zone extension (outbound.002/) with --bso +- Default BSO format: outbound/ (no zone extension, binkd standard) misc/nodelist.c: - Compile FidoNet nodelist to binkd.conf node lines (extracts IBN/INA flags) @@ -94,15 +96,26 @@ misc/process_tic.c: - Process .tic files to filebox (supports config file and legacy args) - Uses MAXPATHLEN from portable.h - Uses MAX_LINE from portable.h +- Updated parse_config() to use portable.c parse_config_line() misc/srifreq.c: - Reimplementation in C of pgul's misc/srifreq shell script (password protection, aliases, wildcards, rate limiting) - Uses MAXPATHLEN from portable.h - Uses MAX_LINE from portable.h - Converted MAX_ALIASES to dynamic array (realloc) +- Updated load_config() to use portable.c config_load(), config_lookup(), config_cache_free() +- Renamed local config_free() to srifreq_config_cleanup() to avoid conflict with portable.c +- Error messages now go to log file (config logfile) instead of stderr misc/portable.c: - Shared implementations: string utilities, file operations, path utilities +- Added parse_config_line(): robust config line parser (BOM, tabs, comments inline, whitespace) +- Added config_get(): lookup single key in config file +- Added config_load(): load entire config into memory cache +- Added config_lookup(): lookup key in cached config (no file I/O) +- Added config_cache_free(): free cached config memory (renamed from config_free to avoid conflict with srifreq.c) +- Added parse_size(): parse size with suffixes (k/K/m/M/g/G) or plain bytes +- Added parse_time(): parse time with suffixes (s/m/h/d) or plain seconds misc/portable.h: - Portable declarations and platform-specific includes (POSIX, Win32, OS/2, DOS, AmigaOS) @@ -172,7 +185,8 @@ AMIGA-SPECIFIC CHANGES (#ifdef AMIGA / AMIGADOS_4D_OUTBOUND) amiga/evloop.c: - Non-blocking event loop with WaitSelect(), replaces servmgr()/clientmgr() -- Fixed startup delay: last_rescan=0 after pre-loop try_outbound forces immediate retry in first loop iteration (avoids waiting rescan-delay seconds if DNS failed at startup) +- Fixed startup delay: last_rescan=0 after pre-loop try_outbound forces immediate retry + in first loop iteration (avoids waiting rescan-delay seconds if DNS failed at startup) amiga/session.c: - Session allocation, accept(), non-blocking connect(), outbound scan @@ -194,7 +208,9 @@ amiga/dirent.c: amiga/proto_amiga.c: - Non-blocking BinkP protocol (amiga_proto_open, amiga_proto_step, amiga_proto_close) -- Fixed premature M_EOB: added !state->in.f to EOB condition — prevents sending M_EOB while a remote file is still being received (avoids "M_EOB but N files pending M_GOT" error on the remote side) +- Fixed premature M_EOB: added !state->in.f to EOB condition — prevents sending M_EOB + while a remote file is still being received (avoids "M_EOB but N files pending M_GOT" + error on the remote side) --- New Amiga-specific files (.h) --- diff --git a/misc/freq.c b/misc/freq.c index 611c61e0..6d930e8a 100755 --- a/misc/freq.c +++ b/misc/freq.c @@ -29,27 +29,39 @@ static int build_aso_paths(const char *outbound, unsigned int zone, unsigned int return 0; } -/* build_bso_paths -- BSO BinkleyStyle layout (lowercase hex) */ -static int build_bso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, char *req_path, char *clo_path, int pathsize) +/* build_bso_paths -- BSO BinkleyStyle layout (lowercase hex) + * use_zone_ext: 0 = outbound/ (binkd default), 1 = outbound.002/ (with zone ext) */ +static int build_bso_paths(const char *outbound, unsigned int zone, unsigned int net, unsigned int node, unsigned int point, int use_zone_ext, char *req_path, char *clo_path, int pathsize) { char zone_dir[FREQ_MAX_PATH]; char node_dir[FREQ_MAX_PATH]; + const char *base_dir; - /* Zone dir: .0ZZ (lowercase hex) */ - snprintf(zone_dir, sizeof(zone_dir), "%s.%03x", outbound, zone); - str_tolower(zone_dir); + if (use_zone_ext) + { + /* Zone dir: .0ZZ (lowercase hex) binkp compatible */ + snprintf(zone_dir, sizeof(zone_dir), "%s.%03x", outbound, zone); + str_tolower(zone_dir); + base_dir = zone_dir; + } + else + { + /* No zone extension: use outbound/ directly (binkd default) */ + base_dir = outbound; + } - if (mkdir_recursive(zone_dir) < 0 && !path_exists(zone_dir)) + if (mkdir_recursive(base_dir) < 0 && !path_exists(base_dir)) return -1; if (point == 0) { - snprintf(req_path, (size_t)pathsize, "%s/%04x%04x.req", zone_dir, net, node); - snprintf(clo_path, (size_t)pathsize, "%s/%04x%04x.clo", zone_dir, net, node); + snprintf(req_path, (size_t)pathsize, "%s/%04x%04x.req", base_dir, net, node); + snprintf(clo_path, (size_t)pathsize, "%s/%04x%04x.clo", base_dir, net, node); } else { - snprintf(node_dir, sizeof(node_dir), "%s/%04x%04x.pnt", zone_dir, net, node); + snprintf(node_dir, sizeof(node_dir), "%s/%04x%04x.pnt", base_dir, net, node); + str_tolower(node_dir); if (mkdir_recursive(node_dir) < 0 && !path_exists(node_dir)) return -1; @@ -72,10 +84,11 @@ int main(int argc, char *argv[]) const char *outbound; const char *arg_outbound; const char *arg_addr; - const char *password = NULL; /* --password → !pass suffix */ - long newer_than = 0; /* --newer-than → +ts suffix */ - int update = 0; /* --update → U suffix */ + const char *password = NULL; /* --password !pass suffix */ + long newer_than = 0; /* --newer-than +ts suffix */ + int update = 0; /* --update U suffix */ int use_bso = 0; + int use_zone_ext = 0; /* --zone-ext use outbound.002/ */ int argi = 1; int nfiles = 0; @@ -94,6 +107,11 @@ int main(int argc, char *argv[]) use_bso = 0; argi++; } + else if (strcmp(argv[argi], "--zone-ext") == 0) + { + use_zone_ext = 1; + argi++; + } else if (strcmp(argv[argi], "--update") == 0) { update = 1; @@ -110,20 +128,19 @@ int main(int argc, char *argv[]) argi++; } else - break; /* unknown flag — stop, treat rest as positional */ + break; /* Unknown flag — stop, treat rest as positional */ } if (argc - argi < 3) { - fprintf(stderr, - "Usage: freq [options] Z:N/NODE[.POINT] [...]\n" - "Options:\n" - " --aso flat layout (default): outbound/Z.N.NODE.POINT.req\n" - " --bso BSO layout: outbound.0ZZ/nnnnnnnn[.pnt/pppppppp].req\n" - " --password append !pw to each request line\n" - " --newer-than append + (request if newer)\n" - " --update append U flag (update request)\n" - "Multiple filenames can be listed after the address.\n"); + fprintf(stderr, "Usage: freq [--bso|--aso] [--zone-ext] [--update] [--password ] [--newer-than ]
...\n"); + fprintf(stderr, " --bso Use BSO outbound structure (default: outbound/)\n"); + fprintf(stderr, " --zone-ext Use zone extension (outbound.002/) with --bso\n"); + fprintf(stderr, " --aso Use ASO flat outbound structure\n"); + fprintf(stderr, " --password append !pw to each request line\n"); + fprintf(stderr, " --newer-than append + (request if newer)\n"); + fprintf(stderr, " --update append U flag (update request)\n"); + fprintf(stderr, "Multiple filenames can be listed after the address.\n"); return 1; } @@ -145,15 +162,15 @@ int main(int argc, char *argv[]) if (use_bso) { - if (build_bso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) + if (build_bso_paths(abs_outbound, zone, net, node, point, use_zone_ext, req_path, clo_path, FREQ_MAX_PATH) != 0) { - fprintf(stderr, "freq: cannot create BSO dirs under: %s\n", outbound); + fprintf(stderr, "freq: cannot create BSO outbound paths\n"); return 1; } } else { - if (build_aso_paths(outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) + if (build_aso_paths(abs_outbound, zone, net, node, point, req_path, clo_path, FREQ_MAX_PATH) < 0) { fprintf(stderr, "freq: cannot create outbound dir: %s\n", outbound); return 1; diff --git a/misc/portable.c b/misc/portable.c index d06dc5ac..ed125695 100644 --- a/misc/portable.c +++ b/misc/portable.c @@ -400,3 +400,281 @@ int make_abs_path(const char *src, char *dst, int dstlen) return 0; #endif } + +/* parse_config_line -- Parse a config line into key and value + * Handles: BOM, leading/trailing whitespace, tabs, comments inline (#) + * Returns 1 if valid key=value parsed, 0 if blank/comment/invalid + */ +int parse_config_line(const char *line, char *key, int klen, char *val, int vlen) +{ + const unsigned char *p = (const unsigned char *)line; + char *kdst, *vdst; + int kcnt = 0, vcnt = 0; + + if (!line || !key || klen <= 0 || !val || vlen <= 0) + return 0; + + /* Skip UTF-8 BOM */ + if (p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF) + p += 3; + + /* Skip leading whitespace */ + while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') + p++; + + /* Skip blank lines and comments */ + if (*p == '\0' || *p == '#' || *p == ';') + return 0; + + /* Parse key */ + kdst = key; + while (*p && *p != ' ' && *p != '\t' && *p != '=' && kcnt < klen - 1) + { + *kdst++ = (char)*p++; + kcnt++; + } + *kdst = '\0'; + + if (kcnt == 0) + return 0; + + /* Skip separator (spaces, tabs, =) */ + while (*p == ' ' || *p == '\t' || *p == '=') + p++; + + /* Parse value until comment or end of line */ + vdst = val; + while (*p && *p != '#' && *p != '\r' && *p != '\n' && vcnt < vlen - 1) + { + *vdst++ = (char)*p++; + vcnt++; + } + *vdst = '\0'; + + /* Trim trailing whitespace from value */ + while (vcnt > 0 && (val[vcnt - 1] == ' ' || val[vcnt - 1] == '\t')) + { + val[--vcnt] = '\0'; + } + + return (vcnt > 0) ? 1 : 1; /* Even if value is empty, key is valid */ +} + +/* config_get -- Open config file and return value for a specific key + * Returns 1 if found, 0 if not found or error + */ +int config_get(const char *filename, const char *key, char *val, int vlen) +{ + FILE *f; + char line[MAX_LINE]; + char kbuf[64]; + int found = 0; + + if (!filename || !key || !val || vlen <= 0) + return 0; + + f = fopen(filename, "r"); + + if (!f) + return 0; + + while (fgets(line, sizeof(line), f)) + { + kbuf[0] = '\0'; + val[0] = '\0'; + + if (parse_config_line(line, kbuf, (int)sizeof(kbuf), val, vlen)) + { + if (strcmp(kbuf, key) == 0) + { + found = 1; + break; + } + } + } + + fclose(f); + return found; +} + +/* parse_size -- Parse size string with suffixes (k, K, m, M, g, G) + * Plain number = bytes. Returns size in bytes, or 0 on error + */ +long parse_size(const char *s) +{ + long num; + char suffix; + int n; + + if (!s || !s[0]) + return 0; + + n = sscanf(s, "%ld%c", &num, &suffix); + + if (n == 1) + return num; /* Plain bytes */ + + if (n == 2) + { + suffix = (char)tolower((unsigned char)suffix); + + switch (suffix) + { + case 'k': + return num * 1024; + case 'm': + return num * 1024 * 1024; + case 'g': + return num * 1024 * 1024 * 1024; + default: + return 0; /* Invalid suffix */ + } + } + + return 0; +} + +/* parse_time -- Parse time string with suffixes (s, m, h, d) + * Plain number = seconds. Returns time in seconds, or 0 on error + */ +long parse_time(const char *s) +{ + long num; + char suffix; + int n; + + if (!s || !s[0]) + return 0; + + n = sscanf(s, "%ld%c", &num, &suffix); + + if (n == 1) + return num; /* Plain seconds */ + + if (n == 2) + { + suffix = (char)tolower((unsigned char)suffix); + + switch (suffix) + { + case 's': + return num; + case 'm': + return num * 60; + case 'h': + return num * 60 * 60; + case 'd': + return num * 60 * 60 * 24; + default: + return 0; /* Invalid suffix */ + } + } + + return 0; +} + +/* Config cache in memory implementation */ +typedef struct ConfigEntry +{ + char key[64]; + char value[MAXPATHLEN]; + struct ConfigEntry *next; +} ConfigEntry; + +struct ConfigCache +{ + ConfigEntry *entries; + int count; +}; + +/* config_load -- Load entire config file into memory cache + * Returns cache pointer on success, NULL on error + */ +ConfigCache *config_load(const char *filename) +{ + FILE *f; + char line[MAX_LINE]; + char key[64], val[MAXPATHLEN]; + ConfigCache *cache; + ConfigEntry *entry; + + if (!filename) + return NULL; + + f = fopen(filename, "r"); + if (!f) + return NULL; + + cache = (ConfigCache *)malloc(sizeof(ConfigCache)); + if (!cache) + { + fclose(f); + return NULL; + } + + cache->entries = NULL; + cache->count = 0; + + while (fgets(line, sizeof(line), f)) + { + key[0] = '\0'; + val[0] = '\0'; + + if (!parse_config_line(line, key, (int)sizeof(key), val, (int)sizeof(val))) + continue; + + entry = (ConfigEntry *)malloc(sizeof(ConfigEntry)); + if (!entry) + continue; + + safe_strncpy(entry->key, key, (int)sizeof(entry->key)); + safe_strncpy(entry->value, val, (int)sizeof(entry->value)); + entry->next = cache->entries; + cache->entries = entry; + cache->count++; + } + + fclose(f); + return cache; +} + +/* config_lookup -- Look up a key in cached config + * Returns 1 if found, 0 if not found + */ +int config_lookup(ConfigCache *cache, const char *key, char *val, int vlen) +{ + ConfigEntry *entry; + + if (!cache || !key || !val || vlen <= 0) + return 0; + + for (entry = cache->entries; entry; entry = entry->next) + { + if (strcmp(entry->key, key) == 0) + { + safe_strncpy(val, entry->value, vlen); + return 1; + } + } + + return 0; +} + +/* config_cache_free -- Free cached config memory */ +void config_cache_free(ConfigCache *cache) +{ + ConfigEntry *entry, *next; + + if (!cache) + return; + + entry = cache->entries; + while (entry) + { + next = entry->next; + free(entry); + entry = next; + } + + free(cache); +} diff --git a/misc/portable.h b/misc/portable.h index 946683dc..25815d77 100644 --- a/misc/portable.h +++ b/misc/portable.h @@ -132,4 +132,16 @@ long get_file_mtime(const char *path); void path_join(char *out, int outsize, const char *base, const char *sub); int make_abs_path(const char *src, char *dst, int dstlen); +/* Config parsing utilities */ +int parse_config_line(const char *line, char *key, int klen, char *val, int vlen); +int config_get(const char *filename, const char *key, char *val, int vlen); +long parse_size(const char *s); +long parse_time(const char *s); + +/* Config cache in memory */ +typedef struct ConfigCache ConfigCache; +ConfigCache *config_load(const char *filename); +int config_lookup(ConfigCache *cache, const char *key, char *val, int vlen); +void config_cache_free(ConfigCache *cache); + #endif /* BINKD_PORTABLE_H */ diff --git a/misc/process_tic.c b/misc/process_tic.c index 51061e64..49dae376 100755 --- a/misc/process_tic.c +++ b/misc/process_tic.c @@ -10,6 +10,19 @@ #include "portable.h" /* Canonical portable layer */ #include +/* Config structure */ +static struct +{ + char inbound[MAXPATHLEN]; + char filebox[MAXPATHLEN]; + char pubdir[MAXPATHLEN]; + char logfile[MAXPATHLEN]; + char filelist[MAXPATHLEN]; + char newfiles[MAXPATHLEN]; + char ticlog[MAXPATHLEN]; + int copypublic; +} cfg; + static int my_toupper(int c) { if (c >= 'a' && c <= 'z') @@ -382,78 +395,47 @@ static int is_tic_file(const char *name) return (my_strnicmp(name + len - 4, ".tic", 4) == 0); } -/* Config structure */ -static struct -{ - char inbound[MAXPATHLEN]; - char filebox[MAXPATHLEN]; - char pubdir[MAXPATHLEN]; - char logfile[MAXPATHLEN]; - char filelist[MAXPATHLEN]; - char newfiles[MAXPATHLEN]; - char ticlog[MAXPATHLEN]; - int copypublic; -} cfg; - /* Parse configuration file */ static int parse_config(const char *conffile) { - FILE *f; - char line[MAX_LINE]; - char *key, *value; + ConfigCache *cache; + char val[MAXPATHLEN]; memset(&cfg, 0, sizeof(cfg)); - f = fopen(conffile, "r"); - - if (!f) + cache = config_load(conffile); + if (!cache) { fprintf(stderr, "process_tic: cannot open config file: %s\n", conffile); return 0; } - while (fgets(line, sizeof(line), f)) - { - trim_nl(line); - key = skip_ws(line); + /* Read each field using config_lookup() */ + if (config_lookup(cache, "inbound", val, sizeof(val))) + safe_strncpy(cfg.inbound, val, (int)sizeof(cfg.inbound)); - /* Skip comments and empty lines */ - if (*key == '#' || *key == '\0') - continue; + if (config_lookup(cache, "filebox", val, sizeof(val))) + safe_strncpy(cfg.filebox, val, (int)sizeof(cfg.filebox)); - /* Find value after key */ - value = key; + if (config_lookup(cache, "pubdir", val, sizeof(val))) + { + safe_strncpy(cfg.pubdir, val, (int)sizeof(cfg.pubdir)); + cfg.copypublic = 1; + } - while (*value && *value != ' ' && *value != '\t') - value++; + if (config_lookup(cache, "logfile", val, sizeof(val))) + safe_strncpy(cfg.logfile, val, (int)sizeof(cfg.logfile)); - if (*value) - { - *value = '\0'; - value = skip_ws(value + 1); - } + if (config_lookup(cache, "filelist", val, sizeof(val))) + safe_strncpy(cfg.filelist, val, (int)sizeof(cfg.filelist)); - /* Parse key-value pairs */ - if (strcmp(key, "inbound") == 0) - safe_strncpy(cfg.inbound, value, (int)sizeof(cfg.inbound)); - else if (strcmp(key, "filebox") == 0) - safe_strncpy(cfg.filebox, value, (int)sizeof(cfg.filebox)); - else if (strcmp(key, "pubdir") == 0) - { - safe_strncpy(cfg.pubdir, value, (int)sizeof(cfg.pubdir)); - cfg.copypublic = 1; - } - else if (strcmp(key, "logfile") == 0) - safe_strncpy(cfg.logfile, value, (int)sizeof(cfg.logfile)); - else if (strcmp(key, "filelist") == 0) - safe_strncpy(cfg.filelist, value, (int)sizeof(cfg.filelist)); - else if (strcmp(key, "newfiles") == 0) - safe_strncpy(cfg.newfiles, value, (int)sizeof(cfg.newfiles)); - else if (strcmp(key, "ticlog") == 0) - safe_strncpy(cfg.ticlog, value, (int)sizeof(cfg.ticlog)); - } + if (config_lookup(cache, "newfiles", val, sizeof(val))) + safe_strncpy(cfg.newfiles, val, (int)sizeof(cfg.newfiles)); - fclose(f); + if (config_lookup(cache, "ticlog", val, sizeof(val))) + safe_strncpy(cfg.ticlog, val, (int)sizeof(cfg.ticlog)); + + config_cache_free(cache); /* Validate required fields */ if (!cfg.inbound[0] || !cfg.filebox[0]) @@ -520,8 +502,7 @@ int main(int argc, char *argv[]) if (!inbound[0] || !filebox[0]) { - fprintf(stderr, - "Usage: process_tic --conf [*.tic]\n"); + fprintf(stderr, "Usage: process_tic --conf [*.tic]\n"); return 1; } diff --git a/misc/srifreq.c b/misc/srifreq.c index 810cc780..be83d72d 100755 --- a/misc/srifreq.c +++ b/misc/srifreq.c @@ -110,7 +110,7 @@ static void config_add_private(const char *path, const char *password) } } -static void config_free(void) +static void srifreq_config_cleanup(void) { PrivDir *pd = g_conf.privdirs; NodeTrack *nt = g_conf.tracking; @@ -144,56 +144,67 @@ static void config_free(void) static int load_config(const char *path) { + ConfigCache *cache; + char val[MAXPATHLEN]; + char pw[64]; FILE *f; - char line[MAX_LINE]; - char key[64], val[MAXPATHLEN], pw[64]; - int n; - f = fopen(path, "r"); + cache = config_load(path); - if (!f) + if (!cache) { fprintf(stderr, "srifreq: cannot open config: %s\n", path); return 0; } - while (fgets(line, sizeof(line), f)) + if (config_lookup(cache, "pubdir", val, sizeof(val))) + safe_strncpy(g_conf.pubdir, val, (int)sizeof(g_conf.pubdir)); + + if (config_lookup(cache, "logfile", val, sizeof(val))) + safe_strncpy(g_conf.logfile, val, (int)sizeof(g_conf.logfile)); + + if (config_lookup(cache, "aliases", val, sizeof(val))) + safe_strncpy(g_conf.aliases, val, (int)sizeof(g_conf.aliases)); + + if (config_lookup(cache, "trackfile", val, sizeof(val))) + safe_strncpy(g_conf.trackfile, val, (int)sizeof(g_conf.trackfile)); + + if (config_lookup(cache, "maxfiles", val, sizeof(val))) + g_conf.maxfiles = atoi(val); + + if (config_lookup(cache, "maxsize", val, sizeof(val))) + g_conf.maxbytes = parse_size(val); + + if (config_lookup(cache, "timewindow", val, sizeof(val))) + g_conf.timewindow = parse_time(val); + + /* Handle private dirs - need to re-scan file for password field */ + f = fopen(path, "r"); + if (f) { - /* Strip trailing whitespace and newlines */ - n = (int)strlen(line); + char line[MAX_LINE]; + char key[64], v[MAXPATHLEN]; - while (n > 0 && - (line[n - 1] == '\r' || line[n - 1] == '\n' || line[n - 1] == ' ')) - line[--n] = '\0'; + while (fgets(line, sizeof(line), f)) + { + key[0] = v[0] = pw[0] = '\0'; - /* Skip blank and comment lines */ - if (!line[0] || line[0] == '#') - continue; + if (!parse_config_line(line, key, (int)sizeof(key), v, (int)sizeof(v))) + continue; - key[0] = val[0] = pw[0] = '\0'; + if (strcmp(key, "private") == 0) + { + sscanf(line, "%*s %*s %63s", pw); - if (sscanf(line, "%63s %1023s %63s", key, val, pw) < 2) - continue; + if (pw[0]) + config_add_private(v, pw); + } + } - if (strcmp(key, "pubdir") == 0) - safe_strncpy(g_conf.pubdir, val, (int)sizeof(g_conf.pubdir)); - else if (strcmp(key, "logfile") == 0) - safe_strncpy(g_conf.logfile, val, (int)sizeof(g_conf.logfile)); - else if (strcmp(key, "aliases") == 0) - safe_strncpy(g_conf.aliases, val, (int)sizeof(g_conf.aliases)); - else if (strcmp(key, "trackfile") == 0) - safe_strncpy(g_conf.trackfile, val, (int)sizeof(g_conf.trackfile)); - else if (strcmp(key, "maxfiles") == 0) - g_conf.maxfiles = atoi(val); - else if (strcmp(key, "maxsize") == 0) - g_conf.maxbytes = atol(val); - else if (strcmp(key, "timewindow") == 0) - g_conf.timewindow = atol(val); - else if (strcmp(key, "private") == 0 && pw[0]) - config_add_private(val, pw); + fclose(f); } - fclose(f); + config_cache_free(cache); return 1; } @@ -456,6 +467,7 @@ static const char *find_alias(const char *name) char aname[64]; int i; int n; + int j; /* Convert name to uppercase */ n = (int)strlen(name); @@ -473,15 +485,13 @@ static const char *find_alias(const char *name) int an; an = (int)strlen(g_aliases[i].name); - if (an >= (int)sizeof(aname)) an = (int)sizeof(aname) - 1; - { - int j; + if (an >= (int)sizeof(aname)) + an = (int)sizeof(aname) - 1; - for (j = 0; j < an; j++) - aname[j] = (char)toupper((unsigned char)g_aliases[i].name[j]); + for (j = 0; j < an; j++) + aname[j] = (char)toupper((unsigned char)g_aliases[i].name[j]); - aname[an] = '\0'; - } + aname[an] = '\0'; if (strcmp(upper, aname) == 0) return g_aliases[i].path; @@ -508,21 +518,21 @@ static int parse_srif(const char *path, SRIF *srif) while (fgets(line, sizeof(line), f)) { - str_trim(line); - if (!line[0]) - continue; + char *p = strchr(line, '\n'); + + if (p) + *p = '\0'; + + p = strchr(line, '\r'); - token[0] = '\0'; - value[0] = '\0'; + if (p) + *p = '\0'; if (sscanf(line, "%1023s %1023[^\n]", token, value) < 1) continue; str_upper(token); - if (!value[0]) - continue; - if (!strcmp(token, "SYSOP")) safe_strncpy(srif->sysop, value, (int)sizeof(srif->sysop)); else if (!strcmp(token, "AKA") && !srif->aka[0]) @@ -573,7 +583,6 @@ static int parse_srif(const char *path, SRIF *srif) return srif->got_request_list; } -/* Logging */ static void do_log(const char *logpath, const char *msg) { FILE *lf; @@ -597,9 +606,6 @@ static void do_log(const char *logpath, const char *msg) } } -/* serve_one -- resolve one request name, check password/timestamp/update - * write to response list. Returns 1 if served - */ static int serve_one(const char *req_name, const char *found_path, const char *req_pass, long req_newer, int req_update, const SRIF *srif, FILE *rsp_f, const char *log_path, char *logbuf, int logbuf_size) { long fsize; @@ -728,8 +734,8 @@ int main(int argc, char *argv[]) if (!g_conf.pubdir[0]) { - fprintf(stderr, "srifreq: pubdir not set\n"); - config_free(); + do_log(g_conf.logfile, "ERROR: pubdir not set in config"); + srifreq_config_cleanup(); return 1; } @@ -745,8 +751,7 @@ int main(int argc, char *argv[]) { snprintf(logbuf, sizeof(logbuf), "ERROR: cannot parse SRIF or missing RequestList: %s", srif_path); do_log(g_conf.logfile, logbuf); - fprintf(stderr, "srifreq: %s\n", logbuf); - config_free(); + srifreq_config_cleanup(); return 1; } @@ -755,7 +760,7 @@ int main(int argc, char *argv[]) { snprintf(logbuf, sizeof(logbuf), "DENIED: system is UNLISTED (aka: %s)", srif.aka); do_log(g_conf.logfile, logbuf); - config_free(); + srifreq_config_cleanup(); return 1; } @@ -775,7 +780,7 @@ int main(int argc, char *argv[]) { snprintf(logbuf, sizeof(logbuf), "WARN: RequestList not available: %s", srif.request_list); do_log(g_conf.logfile, logbuf); - config_free(); + srifreq_config_cleanup(); return 0; } @@ -1018,7 +1023,7 @@ int main(int argc, char *argv[]) /* Save tracking data */ tracking_save(); - config_free(); + srifreq_config_cleanup(); return (found_count > 0) ? 0 : 1; } From ff496e361b40a477de236f41d5172871d24eca7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tanaus=C3=BA=20M=2E?= Date: Mon, 27 Apr 2026 21:51:11 +0100 Subject: [PATCH 05/20] Bugfixes inbound.c: - Fixed resource leak: close(fd) when fdopen() fails misc/decompress.c: - Replaced local is_safe_filename() with portable.c version - Fixed: validate basename only, not full path - Fixed: basename extraction now supports Windows backslash separator (\) - Fixed: accept return code 1 (warnings) as success for all decompressors - Added validation: inbound must be a directory - Added validation: outdir must be a directory misc/freq.c: - Added validation: outbound must be a directory - Added validation: filenames must be safe misc/nodelist.c: - Added --pointlist option: process pointlist format (Boss/Point styles per FTS-5002) - Supports Boss format: Boss,Z:N/N followed by point entries - Supports Point format: Point,N,... (FTS-5002 2.2) - In pointlist mode, leading comma entries are treated as points (Boss format 2.1) misc/process_tic.c: - Added validation: inbound must be a directory - Added validation: logfile, filelist, newfiles, ticlog must be regular files - Added validation: .tic files must be regular files misc/srifreq.c: - Added validation: pubdir must be a directory - Added validation: logfile, aliases, trackfile must be regular files - Added validation: private paths must be directories misc/portable.c: - Added is_safe_filename(): validate filename for shell command injection protection (whitelist approach) misc/portable.h: - Added is_safe_filename() declaration validate filename doc/decompress.txt: - Usage documentation for decompress utility doc/freq_new.txt: - Usage documentation for freq utility doc/nodelist.txt: - Usage documentation for nodelist utility doc/process_tic.txt: - Usage documentation and config file example for process_tic doc/srifreq_new.txt: - Usage documentation, config file and aliases file examples for srifreq --- amiga/evloop.c | 39 -------- amiga/proto_amiga.c | 114 ++++++++++++++++------- changes.txt | 38 +++++++- doc/freq_new.txt | 17 +++- doc/nodelist.txt | 12 ++- doc/process_tic.txt | 6 +- doc/srifreq_new.txt | 12 ++- inbound.c | 1 + misc/decompress.c | 55 ++++++++++- misc/freq.c | 57 ++++++++++-- misc/nodelist.c | 145 ++++++++++++++++++++++++++--- misc/portable.c | 120 +++++++++++++++++++++++- misc/portable.h | 7 ++ misc/process_tic.c | 89 +++++++++++++++++- misc/srifreq.c | 80 ++++++++++++++-- mkfls/amiga/Makefile.analyze.bebbo | 2 +- mkfls/nt95-msvc/Makefile | 3 +- mkfls/unix/Makefile.analyze.unix | 2 +- 18 files changed, 670 insertions(+), 129 deletions(-) diff --git a/amiga/evloop.c b/amiga/evloop.c index 1033b6f5..67a31b0b 100644 --- a/amiga/evloop.c +++ b/amiga/evloop.c @@ -54,12 +54,9 @@ static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) if (servers == 0 && clients == 0) { - Log(5, "DEBUG: Using default 2 slots (no config found)"); return 2; } - Log(5, "DEBUG: Raw values: servers=%d, clients=%d", servers, clients); - if (srv_flag && servers < 1) servers = 1; @@ -71,8 +68,6 @@ static int calc_max_sessions(BINKD_CONFIG *config, int srv_flag, int cli_flag) if (total < 2) total = 2; - Log(5, "DEBUG: Calculated max_sessions=%d", total); - return total; } @@ -149,7 +144,6 @@ static int build_fdsets(fd_set *r, fd_set *w) } } - Log(5, "DEBUG: Sessions processed, maxfd=%d", maxfd); return maxfd; } @@ -161,22 +155,15 @@ static int handle_server_accept(fd_set *r, BINKD_CONFIG *config) { int i; - Log(5, "DEBUG: Before accept loop"); - for (i = 0; i < sockfd_used; i++) { if (FD_ISSET(sockfd[i], r)) do_accept(sockfd[i], config); if (binkd_exit) - { - Log(5, "DEBUG: binkd_exit during accept loop"); return -1; - } } - Log(5, "DEBUG: After accept loop"); - return 0; } @@ -188,14 +175,10 @@ static int advance_sessions(fd_set *r, fd_set *w, BINKD_CONFIG *config) { int i, active = 0; - Log(5, "DEBUG: Before advance sessions"); - for (i = 0; i < max_sessions; i++) { sess_t *s = &sessions[i]; - Log(5, "DEBUG: Session %d, phase=%d, fd=%d", i, s->phase, (int)s->fd); - if (s->phase == SESS_FREE) continue; @@ -336,9 +319,6 @@ void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) sockfd_used = 0; srand((unsigned int)time(NULL)); - Log(5, "DEBUG: server_flag=%d, client_flag=%d", server_flag, client_flag); - Log(5, "DEBUG: max_servers=%d, max_clients=%d", config->max_servers, config->max_clients); - /* Initialise session table */ max_sessions = calc_max_sessions(config, server_flag, client_flag); @@ -362,10 +342,7 @@ void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) return; } - Log(5, "DEBUG: Listen sockets opened, sockfd_used=%d", sockfd_used); - /* Initial outbound attempt before waiting (important for poll -p mode) */ - Log(5, "DEBUG: Initial try_outbound before main loop"); try_outbound(config); last_rescan = 0; /* Force immediate retry in first loop iteration */ @@ -379,7 +356,6 @@ void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) } /* Build fd_sets */ - Log(5, "DEBUG: Building fd_sets"); maxfd = build_fdsets(&r, &w); tv.tv_sec = 1; @@ -390,8 +366,6 @@ void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) if (maxfd < 1 && (sockfd_used > 0 || n_clients > 0)) maxfd = 1; - Log(5, "DEBUG: Calling select() with maxfd=%d", maxfd); - if (maxfd == 0) { /* No sockets yet -- use Delay() instead of select(0,...) @@ -402,8 +376,6 @@ void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) else n = select(maxfd + 1, &r, &w, NULL, &tv); - Log(5, "DEBUG: select() returned n=%d", n); - if (binkd_exit) { Log(1, "binkd_exit detected after select(), exiting"); @@ -416,10 +388,7 @@ void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) if (n < 0) { if (TCPERRNO == EINTR || TCPERRNO == EWOULDBLOCK) - { - Log(5, "DEBUG: select interrupted, continuing"); continue; - } if (TCPERRNO == ENOTSOCK || TCPERRNO == EBADF) { @@ -436,10 +405,6 @@ void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) Log(1, "select: %s", TCPERR()); break; } - else if (n == 0) - { - Log(5, "DEBUG: select timeout, continuing"); - } /* server: Accept new inbound connections */ if (server_flag) @@ -462,12 +427,8 @@ void amiga_evloop_run(BINKD_CONFIG *config, int srv_flag, int cli_flag) if (now - last_rescan >= (config->rescan_delay > 0 ? config->rescan_delay : 1) || last_rescan == 0) { - Log(5, "DEBUG: Before try_outbound"); - try_outbound(config); - Log(5, "DEBUG: After try_outbound"); - if (checkcfg()) { if (handle_config_reload(&config, server_flag, client_flag)) diff --git a/amiga/proto_amiga.c b/amiga/proto_amiga.c index ea4b7798..04011eaf 100644 --- a/amiga/proto_amiga.c +++ b/amiga/proto_amiga.c @@ -82,7 +82,6 @@ int amiga_proto_open(STATE *state, SOCKET fd, FTN_NODE *to, FTN_ADDR *fa, const state->peer_name = (host && *host) ? (char *)host : state->ipaddr; /* local endpoint: Not used further, skip to avoid dangling pointer */ - Log(2, "%s session with %s%s%s", to ? "outgoing" : "incoming", state->peer_name, port ? ":" : "", port ? port : ""); /* banner() sends M_NUL lines and ADR messages */ @@ -127,6 +126,7 @@ int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *con while (1) { q = 0; + if (state->flo.f || (q = select_next_file(state->q, state->fa, state->nfa)) != 0) { if (start_file_transfer(state, q, config)) @@ -141,16 +141,6 @@ int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *con } } - /* Nothing left to send — issue EOB */ - if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && !state->sent_fls && !state->in.f) - { - if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) - { - state->local_EOB = 1; - msg_send2(state, M_EOB, 0, 0); - } - } - /* Recv step: Only when socket is readable */ if (readable) { @@ -160,20 +150,66 @@ int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *con /* * send step: drive even when writable=0 if there is buffered data, - * pending messages, a file mid-transfer, or an EOF to flush. + * pending messages, a file mid-transfer, or an EOF to flush */ if (writable || state->msgs || state->oleft || state->send_eof || (state->out.f && !state->off_req_sent && !state->waiting_for_GOT)) { no = send_block(state, config); - if (!no && no != 2) return APROTO_DONE_ERR; } + /* Nothing left to send -> issue M_EOB. Must run AFTER recv/send so any + * M_GOTs queued during recv go out first. Same condition as protocol.c */ + if (!state->out.f && !state->q && !state->local_EOB && state->state != P_NULL && state->sent_fls == 0) + { + /* Defer M_EOB while peer activity may still be in flight, so all + * M_GOTs are sent before our M_EOB (avoids "files pending M_GOT" + * warnings in BinkIT and similar peers). Defer if remote_EOB has + * not arrived OR we are mid-receive. Bounded by nettimeout/2 to + * prevent deadlock vs another pure receiver (min 5s, max 300s) */ + int defer_eob = 0; + int receiving_file = (state->in.f != NULL); + + if (!state->remote_EOB || receiving_file) + { + /* The Amiga runs at its own pace :P */ + time_t now; + time_t elapsed; + unsigned long max_wait; + + now = safe_time(); + elapsed = (now >= state->start_time) ? (now - state->start_time) : 0; + + max_wait = (unsigned long)config->nettimeout / 2; + + if (max_wait < 5) + max_wait = 5; + + if (max_wait > 300) + max_wait = 300; + + /* Never time-out while a file is actively being received */ + if (receiving_file || (unsigned long)elapsed < max_wait) + { + defer_eob = 1; + } + } + + if (!defer_eob) + { + if (!state->delay_EOB || (state->major * 100 + state->minor > 100)) + { + state->local_EOB = 1; + msg_send2(state, M_EOB, 0, 0); + } + } + } + bsy_touch(config); - /* Batch/Session-end detection — Mirrors the break logic in protocol() */ - if (state->remote_EOB && !state->sent_fls && state->local_EOB && !state->GET_FILE_balance && !state->in.f && !state->out.f) + /* Batch/session-end detection - Same condition as protocol.c break logic */ + if (state->remote_EOB && state->sent_fls == 0 && state->local_EOB && state->GET_FILE_balance == 0 && state->in.f == 0 && state->out.f == 0) { if (state->rcvdlist) { @@ -212,27 +248,38 @@ int amiga_proto_step(STATE *state, int readable, int writable, BINKD_CONFIG *con /* * amiga_proto_close -- Flush remaining I/O and release STATE resources * Must be called after APROTO_DONE_OK or APROTO_DONE_ERR + * The same as in protocol.c */ void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) { int no; char buf[BLK_HDR_SIZE + MAX_BLKSIZE]; - int status = ok ? 0 : 1; + int status; + + (void)ok; /* status is derived from state, mirroring protocol.c */ /* Drain inbound queue */ if (!state->io_error) { while ((no = recv(state->s_in, buf, (int)sizeof(buf), 0)) > 0) - Log(9, "purged %d bytes", no); + Log(9, "Purged %d bytes from input queue", no); } - /* Flush pending outbound messages */ - while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)) - ; - - if (ok) + /* Flush any pending outbound */ + while (!state->io_error && (state->msgs || (state->optr && state->oleft)) && send_block(state, config)); + + /* Success: both EOBs exchanged and nothing pending (mirrors protocol.c) + * Or: files transferred and nothing pending (peer dropped during batch 2, + * or while we were deferring M_EOB - data is already safe) */ + if ((state->local_EOB && state->remote_EOB && state->sent_fls == 0 && + state->GET_FILE_balance == 0 && state->in.f == 0 && state->out.f == 0) || + ((state->files_rcvd > 0 || state->files_sent > 0) && + state->sent_fls == 0 && state->GET_FILE_balance == 0 && + state->in.f == 0 && state->out.f == 0)) { - log_end_of_session(0, state, config); + /* Successful session */ + status = 0; + log_end_of_session(status, state, config); process_killlist(state->killlist, state->n_killlist, 's'); inb_remove_partial(state, config); @@ -241,29 +288,30 @@ void amiga_proto_close(STATE *state, BINKD_CONFIG *config, int ok) } else { - log_end_of_session(1, state, config); + /* Unsuccessful session */ + status = 1; + log_end_of_session(status, state, config); process_killlist(state->killlist, state->n_killlist, 0); - if (!binkd_exit && state->to) - bad_try(&state->to->fa, "Bad session", BAD_IO, config); - - /* Restore poll flavour if files were left mid-transfer */ - if (state->to && tolower(state->maxflvr) != 'h') + if (state->to) { - Log(4, "restoring poll with '%c' flavour", state->maxflvr); - - create_poll(&state->to->fa, state->maxflvr, config); + /* We called and there were still files in transfer -- Restore poll */ + if (tolower(state->maxflvr) != 'h') + { + Log(4, "restoring poll with `%c' flavour", state->maxflvr); + create_poll(&state->to->fa, state->maxflvr, config); + } } } if (state->to && state->r_skipped_flag && config->hold_skipped > 0) { Log(2, "holding skipped mail for %lu sec", (unsigned long)config->hold_skipped); - hold_node(&state->to->fa, safe_time() + config->hold_skipped, config); } deinit_protocol(state, config, status); evt_set(state->evt_queue); state->evt_queue = NULL; + Log(4, "session closed, quitting..."); } diff --git a/changes.txt b/changes.txt index 86cd8b7e..ea102320 100644 --- a/changes.txt +++ b/changes.txt @@ -16,6 +16,7 @@ readcfg.c: inbound.c: - Fixed double PATH_SEPARATOR: strnzcat(PATH_SEPARATOR) now checks if path already ends with separator (5 locations) +- Fixed resource leak: close(fd) when fdopen() fails server.c: - Fixed use-after-free: config->rescan_delay saved to local variable before main loop, re-read from new config after checkcfg() reload @@ -80,23 +81,37 @@ server.c: misc/decompress.c: - Decompress FTN day bundles (.SU/.MO/.TU/.WE/.TH/.FR/.SA) - Uses MAXPATHLEN from portable.h instead of local #define +- Replaced local is_safe_filename() with portable.c version +- Fixed: validate basename only, not full path +- Fixed: basename extraction now supports Windows backslash separator (\) +- Added validation: inbound must be a directory +- Added validation: outdir must be a directory misc/freq.c: - Create .req/.clo files (ASO flat layout, BSO BinkleyStyle layout) - Uses MAXPATHLEN from portable.h - Added --zone-ext option: use zone extension (outbound.002/) with --bso - Default BSO format: outbound/ (no zone extension, binkd standard) +- Added validation: outbound must be a directory +- Added validation: filenames must be safe misc/nodelist.c: - Compile FidoNet nodelist to binkd.conf node lines (extracts IBN/INA flags) - Uses MAX_LINE from portable.h - Uses MAXPATHLEN from portable.h +- Added --pointlist option: process pointlist format (Boss/Point styles per FTS-5002) +- Supports Boss format: Boss,Z:N/N followed by point entries +- Supports Point format: Point,N,... (FTS-5002 2.2) +- In pointlist mode, leading comma entries are treated as points (Boss format 2.1) misc/process_tic.c: - Process .tic files to filebox (supports config file and legacy args) - Uses MAXPATHLEN from portable.h - Uses MAX_LINE from portable.h - Updated parse_config() to use portable.c parse_config_line() +- Added validation: inbound must be a directory +- Added validation: logfile, filelist, newfiles, ticlog must be regular files +- Added validation: .tic files must be regular files misc/srifreq.c: - Reimplementation in C of pgul's misc/srifreq shell script (password protection, aliases, wildcards, rate limiting) @@ -106,9 +121,13 @@ misc/srifreq.c: - Updated load_config() to use portable.c config_load(), config_lookup(), config_cache_free() - Renamed local config_free() to srifreq_config_cleanup() to avoid conflict with portable.c - Error messages now go to log file (config logfile) instead of stderr +- Added validation: pubdir must be a directory +- Added validation: logfile, aliases, trackfile must be regular files +- Added validation: private paths must be directories misc/portable.c: - Shared implementations: string utilities, file operations, path utilities +- Added is_safe_filename(): validate filename - Added parse_config_line(): robust config line parser (BOM, tabs, comments inline, whitespace) - Added config_get(): lookup single key in config file - Added config_load(): load entire config into memory cache @@ -119,20 +138,21 @@ misc/portable.c: misc/portable.h: - Portable declarations and platform-specific includes (POSIX, Win32, OS/2, DOS, AmigaOS) +- Added is_safe_filename() declaration -misc/decompress.txt: +doc/decompress.txt: - Usage documentation for decompress utility -misc/freq.txt: +doc/freq_new.txt: - Usage documentation for freq utility -misc/nodelist.txt: +doc/nodelist.txt: - Usage documentation for nodelist utility -misc/process_tic.txt: +doc/process_tic.txt: - Usage documentation and config file example for process_tic -misc/srifreq.txt: +doc/srifreq_new.txt: - Usage documentation, config file and aliases file examples for srifreq --- Makefile updates (all platforms) --- @@ -211,6 +231,14 @@ amiga/proto_amiga.c: - Fixed premature M_EOB: added !state->in.f to EOB condition — prevents sending M_EOB while a remote file is still being received (avoids "M_EOB but N files pending M_GOT" error on the remote side) +- Robust M_EOB deferral: defer sending M_EOB until remote_EOB is received OR + while actively receiving a file (state->in.f != NULL). Timeout bounded to nettimeout/2 + (min 5s, max 300s) to prevent deadlock vs another pure receiver. Handles clock skew + defensively (elapsed = (now >= start_time) ? (now - start_time) : 0) +- Session success condition: session is OK if files were transferred (files_rcvd > 0 || + files_sent > 0) and nothing is pending, even if local_EOB is not set (peer disconnected + during multi-batch or while deferring M_EOB - data is already safe) +- Removed debug messages in evloop proto_amiga functions --- New Amiga-specific files (.h) --- diff --git a/doc/freq_new.txt b/doc/freq_new.txt index 6a73467c..04125be2 100644 --- a/doc/freq_new.txt +++ b/doc/freq_new.txt @@ -18,11 +18,20 @@ OPTIONS: Format: /Z.N.NODE.POINT.req --bso Binkley Style Outbound - hex directory layout - No point: .0ZZ/nnnnnnnn.req - Point: .0ZZ/nnnnnnnn.pnt/pppppppp.req + No point: .ZZZ/nnnnnnnn.req + Point: .ZZZ/nnnnnnnn.pnt/pppppppp.req + Zones > 4095 use 4-digit hex: .ZZZZ/nnnnnnnn.req + + --zone-ext Use zone extension with --bso (outbound.002/) + Default (without --zone-ext): outbound/ (binkd default) --password Append !pw suffix to each request line - --newer-than Append + (request only if file is newer) + --newer-than Append + (request only if file is newer) + can be relative time with suffix: + 7d = 7 days ago, 1h = 1 hour ago, 30m = 30 minutes ago + Suffixes: d=days, h=hours, m=minutes, s=seconds + Or Unix timestamp (absolute seconds since epoch): + Values > 31536000 (1 year) treated as absolute timestamp --update Append U flag (update request, checks TRANX) EXAMPLES: @@ -31,6 +40,8 @@ EXAMPLES: freq --bso /var/spool/binkd/outbound 2:123/456 door.zip freq --bso C:\Binkd\Outbound 1:100/200 update.lzh freq --password secret --newer-than 1234567890 Work:Outbound 39:190/101 file.zip + freq --newer-than 7d Work:Outbound 39:190/101 weekly-update.zip (files newer than 7 days) + freq --newer-than 1h Work:Outbound 39:190/101 latest-files.zip (files newer than 1 hour) freq --update Work:Outbound 39:190/101 file1.zip file2.zip file3.zip CONFIGURATION FILE: diff --git a/doc/nodelist.txt b/doc/nodelist.txt index 6fbe44ae..96210322 100644 --- a/doc/nodelist.txt +++ b/doc/nodelist.txt @@ -1,14 +1,15 @@ -nodelist -- Compile FidoNet nodelist to binkd.conf format +nodelist -- Compile FidoNet nodelist/pointlist to binkd.conf format USAGE: - nodelist [] + nodelist [--pointlist] [] DESCRIPTION: - Reads a FidoNet nodelist and outputs binkd.conf compatible + Reads a FidoNet nodelist or pointlist and outputs binkd.conf compatible "node" configuration lines. Arguments: - nodelist_file Path to the FidoNet nodelist file + --pointlist Process pointlist format (FTS-5002 Boss/Point styles) + nodelist_file Path to the FidoNet nodelist/pointlist file domain Domain name for the node entries (e.g., fidonet) output_file Optional output file (default: stdout) @@ -21,11 +22,12 @@ DESCRIPTION: The nodelist format is comma-separated: [type,]node_num,name,city,sysop,phone,baud,flag1,flag2,... - type = Zone, Region, Host, Hub, Pvt, Hold, Down, Boss (empty = Node) + type = Zone, Region, Host, Hub, Pvt, Hold, Down, Boss, Point EXAMPLES: nodelist Work:Fido/nodelist.123 fidonet > binkd-nodes.conf nodelist /etc/fido/nodelist.456 fidonet >> binkd.conf + nodelist --pointlist Work:Fido/pointlist.123 fidonet >> binkd.conf nodelist C:\Fido\NODELIST.001 fidonet C:\Fido\nodes.conf CONFIGURATION FILE: diff --git a/doc/process_tic.txt b/doc/process_tic.txt index e7d9c6b7..39898101 100644 --- a/doc/process_tic.txt +++ b/doc/process_tic.txt @@ -19,9 +19,9 @@ CONFIGURATION FILE FORMAT: # Lines starting with # are comments # Blank lines are ignored - inbound Inbound directory (required) - filebox Filebox destination (required) - pubdir Public directory for --copy-public + inbound Inbound directory (required, must be a directory) + filebox Filebox destination (required, must be a directory) + pubdir Public directory (must be a directory if specified) logfile Log file path ticlog TIC processing log filelist File list output diff --git a/doc/srifreq_new.txt b/doc/srifreq_new.txt index fb53e368..1c7ed5d8 100644 --- a/doc/srifreq_new.txt +++ b/doc/srifreq_new.txt @@ -13,15 +13,19 @@ CONFIGURATION FILE FORMAT: # Blank lines are ignored pubdir # Public directory (no password required) + # Must be a valid directory (not a file) logfile # Log file, or - to disable aliases # Magic-name aliases file (optional) private # Private directory (requires !password) + # Must be a valid directory (not a file) # Rate limiting options (optional): trackfile # File to track node download statistics maxfiles # Max files per node per time window (0=unlimited) - maxsize # Max bytes per node per time window (0=unlimited) - timewindow # Time window in seconds (0=no window) + maxsize # Max bytes per node per time window (0=unlimited) + # Suffixes: k/K=KB, m/M=MB, g/G=GB (e.g., 50m = 50MB) + timewindow