UNPKG

@tty-pt/ndc

Version:

ndc example project

358 lines (273 loc) 10.5 kB
# ndc > HTTP(S) + WS(S) + Terminal MUX A cross-platform C library for building network daemons - HTTP servers, WebSocket servers, telnet-like services, terminal multiplexers, or custom network applications. From <a href="https://github.com/tty-pt/neverdark">NeverDark</a> • Powers [tty.pt](https://tty.pt) <img src="https://github.com/tty-pt/ndc/blob/main/usage.gif?raw=true" width="512" /> ## What is ndc? **`libndc`** - C library for building network daemons **`ndc`** - Standalone server binary with HTTP/WS/terminal mux Build telnet-like servers, custom protocol handlers, HTTP APIs, WebSocket apps, or anything that needs persistent network connections with an event loop. ## Platform Support | Platform | Status | |----------|--------| | Linux, macOS, BSD | ✅ Full support | | Windows | ⚠️ HTTP/WS only (no PTY/CGI/privilege dropping) | ## Quick Start ```sh # Run simple HTTP server ndc -d -p 8888 # With SSL (POSIX) sudo ndc -C . -K certs.txt -d ``` ### Command Line Options | Option | Description | |--------|-------------| | `-p PORT` | Specify HTTP server port | | `-s PORT` | Specify HTTPS server port (POSIX) | | `-C PATH` | Change directory to PATH before starting | | `-K PATH` | Load SSL certificate mappings from file (POSIX) | | `-k CERT` | Add single SSL certificate mapping (POSIX) | | `-d` | Don't detach (run in foreground) | | `-r` | Root multiplex mode | | `-?` | Display help message | ### certs.txt Format (POSIX) ```txt example.com:cert.pem:key.pem ``` ## Building Custom Daemons ### Minimal Example ```c #include <ttypt/ndc.h> int main(void) { ndc_config.port = 8080; ndc_register("GET", do_GET, CF_NOAUTH); return ndc_main(); // Blocks, runs event loop } ``` ### Telnet-Style Server ```c void cmd_echo(socket_t fd, int argc, char *argv[]) { for (int i = 0; i < argc; i++) ndc_writef(fd, "%s ", argv[i]); ndc_writef(fd, "\n"); } int ndc_connect(socket_t fd) { ndc_writef(fd, "Welcome! Type 'echo hello'\n"); return 1; // Accept connection } int main(void) { ndc_config.port = 2323; ndc_register("echo", cmd_echo, CF_NOAUTH); return ndc_main(); } ``` ### Custom Protocol Handler ```c void my_handler(socket_t fd, char *body) { // Handle raw data from client ndc_write(fd, "RESPONSE", 8); } void ndc_command(socket_t fd, int argc, char *argv[]) { // Called on every command log_command(argv[0]); } void ndc_update(unsigned long long dt) { // Periodic tick (game loops, etc) } ``` ## Library API ### Core Functions | Function | Description | Return Value | |----------|-------------|--------------| | `ndc_main()` | Start event loop (blocking) | Exit code | | `ndc_register(name, cb, flags)` | Register command handler | - | | `ndc_register_handler(path, handler)` | Register HTTP handler for exact path | - | | `ndc_write(fd, data, len)` | Write raw bytes | Bytes written or -1 | | `ndc_writef(fd, fmt, ...)` | Write formatted data | Bytes written or -1 | | `ndc_dwritef(fd, fmt, va)` | Write formatted data with va_list | Bytes written or -1 | | `ndc_close(fd)` | Close connection | - | | `ndc_wall(msg)` | Broadcast message to all descriptors | - | ### HTTP Functions | Function | Description | Return Value | |----------|-------------|--------------| | `ndc_header(fd, key, val)` | Add response header (call before ndc_head) | - | | `ndc_head(fd, code)` | Send HTTP status and headers | - | | `ndc_body(fd, body)` | Send body and close connection | - | | `ndc_sendfile(fd, path)` | Serve static file with auto MIME type | - | | `ndc_status_text(code)` | Get HTTP status text for code | Status string | ### Descriptor Functions | Function | Description | Return Value | |----------|-------------|--------------| | `ndc_flags(fd)` | Get descriptor flags | Flags bitmask | | `ndc_set_flags(fd, flags)` | Set descriptor flags | - | ### Request Environment Functions | Function | Description | Return Value | |----------|-------------|--------------| | `ndc_env_get(fd, target, key)` | Get request environment value | 0 on success | | `ndc_env_put(fd, key, value)` | Set environment key/value | 0 on success | | `ndc_env_clear(fd)` | Clear request environment | - | | `ndc_env(fd)` | Get internal env handle (advanced) | Env handle | ### POSIX-Only Functions | Function | Description | Return Value | |----------|-------------|--------------| | `ndc_pty(fd, args[])` | Spawn PTY-backed command | - | | `ndc_exec(fd, args[], cb, input, len)` | Execute command with callback | - | | `ndc_auth(fd, username)` | Mark user as authenticated, drop privileges (POSIX) | 0 on success, 1 on failure | | `ndc_cert_add(str)` | Add cert mapping: `domain:cert.pem:key.pem` | - | | `ndc_certs_add(fname)` | Load certificate mappings from file | - | | `ndc_mmap(mapped, file)` | Map file into memory | File size or -1 | | `ndc_mmap_iter(start, pos)` | Iterate mapped lines separated by `\n` | Next line or NULL | | `ndc_sendfile(fd, path)` | Serve static file (POSIX uses sendfile syscall) | - | ### Built-in Command Handlers | Handler | Description | |---------|-------------| | `do_GET` | HTTP GET request handler | | `do_POST` | HTTP POST request handler | | `do_sh` | Shell PTY handler (POSIX-only) | ### Hook Functions (Optional) Define these weak symbol hooks to customize behavior: | Hook | Description | Return Value | |------|-------------|--------------| | `ndc_connect(socket_t fd)` | Accept/reject WebSocket connections | Non-zero to accept | | `ndc_disconnect(socket_t fd)` | Cleanup on disconnect | - | | `ndc_accept(socket_t fd)` | Called on socket accept | Ignored | | `ndc_command(socket_t fd, int argc, char *argv[])` | Before command execution | - | | `ndc_flush(socket_t fd, int argc, char *argv[])` | After command execution | - | | `ndc_vim(socket_t fd, int argc, char *argv[])` | Called when command not found | - | | `ndc_update(unsigned long long dt)` | Periodic updates (dt in milliseconds) | - | | `ndc_auth_check(socket_t fd)` | Custom auth hook: validate credentials, return username. Default: session file lookup in `./sessions/` | Username string or NULL | Example: ```c int ndc_connect(socket_t fd) { ndc_writef(fd, "Welcome!\n"); return 1; // Accept connection } void ndc_disconnect(socket_t fd) { // Cleanup resources } void ndc_command(socket_t fd, int argc, char *argv[]) { // Log or validate commands } void ndc_flush(socket_t fd, int argc, char *argv[]) { // Post-command processing } void ndc_vim(socket_t fd, int argc, char *argv[]) { ndc_writef(fd, "Unknown command: %s\n", argv[0]); } void ndc_update(unsigned long long dt) { // Game loop, periodic tasks, etc. } char *ndc_auth_check(socket_t fd) { // Check cookies, tokens, etc. // Return username or NULL return authenticated_user; } ``` ### Configuration ```c struct ndc_config { char *chroot; // chroot directory (POSIX) unsigned flags; // Server flags (see below) unsigned port; // HTTP listen port unsigned ssl_port; // HTTPS listen port (POSIX) ndc_handler_t *default_handler; // Fallback HTTP handler }; // Example usage ndc_config.port = 8080; // HTTP on port 8080 ndc_config.ssl_port = 8443; // HTTPS on port 8443 (POSIX) ndc_config.flags = NDC_SSL; // Enable TLS ndc_config.chroot = "/var/www"; // chroot directory (POSIX) ndc_config.default_handler = my_404; // Custom 404 handler ``` #### Server Flags | Flag | Description | |------|-------------| | `NDC_WAKE` | Wake on activity | | `NDC_SSL` | Enable TLS/SSL | | `NDC_ROOT` | Root multiplex mode | | `NDC_SSL_ONLY` | Redirect HTTP to HTTPS when SSL enabled | | `NDC_DETACH` | Detach into background (daemon mode) | #### Command Flags Use with `ndc_register()`: | Flag | Description | |------|-------------| | `CF_NOAUTH` | Allow command without authentication | | `CF_NOTRIM` | Do not trim trailing CR from input | #### Descriptor Flags Access with `ndc_flags()` and `ndc_set_flags()`: | Flag | Description | |------|-------------| | `DF_CONNECTED` | Connection established and accepted | | `DF_WEBSOCKET` | WebSocket mode enabled | | `DF_TO_CLOSE` | Marked to close after remaining output | | `DF_ACCEPTED` | Accepted socket (pre-WebSocket) | | `DF_AUTHENTICATED` | User authenticated | ## POSIX vs Windows | Feature | POSIX | Windows | |---------|-------|---------| | HTTP/WebSocket | ✅ | ✅ | | Custom commands | ✅ | ✅ | | PTY/Terminal | ✅ | ❌ | | CGI execution | ✅ | ❌ | | Authentication (privilege dropping) | ✅ | ❌ | | SSL certs | ✅ | ❌ | Windows build provides core networking only. ## CGI & Static Files (POSIX) Create `index.sh` for dynamic pages: ```sh #!/bin/sh # CGI scripts output status line without "HTTP/1.1" prefix printf "200 OK\r\n" printf "Content-Type: text/plain\r\n" printf "\r\n" printf "Hello world\n" printf "REQUEST_METHOD=%s\n" "$REQUEST_METHOD" printf "QUERY_STRING=%s\n" "$QUERY_STRING" ``` Control access with `serve.allow` and `serve.autoindex` files. ## NPM for Web Terminal Install the package: ```sh npm install @tty-pt/ndc ``` JavaScript/TypeScript API: ```js import { create } from "@tty-pt/ndc"; // Create terminal instance const term = create(document.getElementById("terminal"), { proto: "ws", // or "wss" for secure port: 4201, sub: { onOpen: (term, ws) => { console.log("Connected to server"); }, onClose: (ws) => { console.log("Disconnected, reconnecting..."); }, onMessage: (ev, arr) => { // Return true to continue default processing return true; }, cols: 80, rows: 25, }, debug: false, }); ``` See `types/ndc.d.ts` for full TypeScript definitions. ## Documentation - Man pages: `man ndc` and `man ndc.3` - Full API: `include/ttypt/ndc.h` - Examples: `src/test-*.c` ## Plugin System Load dynamic modules with dependency resolution via `libndx`: ```c // In your plugin module const char *ndx_deps[] = { "dependency.so", NULL }; // In main application ndx_load("plugin.so"); // Automatically loads dependencies ``` The binary automatically loads `core.so` at startup. Plugins can hook into lifecycle events (`ndc_update`, `ndc_connect`, etc.) to extend functionality. --- **Installation**: See [install docs](https://github.com/tty-pt/ci/blob/main/docs/install.md) **Entry points**: `src/ndc.c` (native), `ndc-cli.js` (npm)