Skip to content

tty-pt/libndc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

138 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 NeverDark • Powers tty.pt

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

# 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)

example.com:cert.pem:key.pem

Building Custom Daemons

Minimal Example

#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

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

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:

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

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:

#!/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:

npm install @tty-pt/ndc

JavaScript/TypeScript API:

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:

// 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
Entry points: src/ndc.c (native), ndc-cli.js (npm)

About

Hypervisor / web terminal mux

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors