119 lines
4.5 KiB
Rust
119 lines
4.5 KiB
Rust
//! rpc — a JSON-RPC 2.0 control interface over a small native HTTP server, the
|
|
//! echoIRCd analogue of InspIRCd's `m_httpd` + `m_jsonrpc` + `m_rpc_*`. Admin tools
|
|
//! call it to introspect and drive the ircd (list/kill users, manage bans, rehash…).
|
|
//!
|
|
//! Layering (each provider is its own file, per [[echoircd-module-per-file]]):
|
|
//! * [`httpd`] — the listener thread: accept, parse HTTP, authenticate, and hand
|
|
//! the JSON-RPC body to the core as `Event::RpcRequest`.
|
|
//! * [`json`] — native JSON scan/build (no serde).
|
|
//! * `core` / `user` / `channel` / `server` / `stats` / `ban` / `message` /
|
|
//! `whowas` / `spamfilter` / `log` — the method providers, called on the core
|
|
//! thread with `&mut Server`.
|
|
//!
|
|
//! Security: **off** unless `rpc = yes` AND `rpc_token` is set; binds `rpc_bind`
|
|
//! (default `127.0.0.1:8080`); every request must carry the token (HTTP Basic or
|
|
//! Bearer), checked constant-time on the listener thread before anything dispatches.
|
|
|
|
pub mod core;
|
|
pub mod httpd;
|
|
pub mod json;
|
|
|
|
use std::sync::mpsc::Sender;
|
|
|
|
use crate::config::Config;
|
|
use crate::ircd::Event;
|
|
use crate::server::Server;
|
|
|
|
/// A JSON-RPC error (standard codes plus app-specific negatives).
|
|
pub struct RpcError {
|
|
pub code: i64,
|
|
pub message: String,
|
|
}
|
|
|
|
impl RpcError {
|
|
pub fn method_not_found(m: &str) -> RpcError {
|
|
RpcError {
|
|
code: -32601,
|
|
message: format!("Method not found: {m}"),
|
|
}
|
|
}
|
|
pub fn invalid_params(msg: &str) -> RpcError {
|
|
RpcError {
|
|
code: -32602,
|
|
message: msg.to_string(),
|
|
}
|
|
}
|
|
pub fn not_found(msg: &str) -> RpcError {
|
|
RpcError {
|
|
code: -1000,
|
|
message: msg.to_string(),
|
|
}
|
|
}
|
|
pub fn internal(msg: &str) -> RpcError {
|
|
RpcError {
|
|
code: -32603,
|
|
message: msg.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Every method name the interface exposes (drives `rpc.methods`). Keep in sync
|
|
/// with the `dispatch` routes as providers are added.
|
|
pub const ALL_METHODS: &[&str] = &["rpc.methods", "rpc.info", "server.info", "stats.get"];
|
|
|
|
/// Run a parsed JSON-RPC request on the core thread. `params` is the raw JSON of
|
|
/// the `params` member (`{}` if none); `id` is the raw JSON of the request id
|
|
/// (echoed verbatim). Returns the full JSON-RPC response envelope.
|
|
pub fn dispatch(s: &mut Server, method: &str, _params: &str, id: &str) -> String {
|
|
// `_params` is consumed once the param-taking providers (user/channel/…) land.
|
|
let result: Result<String, RpcError> = match method {
|
|
"rpc.methods" | "rpc.info" => core::rpc_info(s, method),
|
|
"server.info" | "stats.get" => core::server_info(s),
|
|
other => Err(RpcError::method_not_found(other)),
|
|
};
|
|
envelope(method, id, result)
|
|
}
|
|
|
|
/// Wrap a provider result (or error) in the JSON-RPC 2.0 response envelope, echoing
|
|
/// the method and id (InspIRCd includes the method in its responses too).
|
|
pub fn envelope(method: &str, id: &str, result: Result<String, RpcError>) -> String {
|
|
let id = if id.trim().is_empty() { "null" } else { id };
|
|
match result {
|
|
Ok(res) => format!(
|
|
"{{\"jsonrpc\":\"2.0\",\"method\":{},\"result\":{res},\"id\":{id}}}",
|
|
json::qstr(method)
|
|
),
|
|
Err(e) => format!(
|
|
"{{\"jsonrpc\":\"2.0\",\"method\":{},\"error\":{{\"code\":{},\"message\":{}}},\"id\":{id}}}",
|
|
json::qstr(method),
|
|
e.code,
|
|
json::qstr(&e.message)
|
|
),
|
|
}
|
|
}
|
|
|
|
/// Start the RPC HTTP listener if configured. Called from `main` with a clone of
|
|
/// the core's event sender. No-op (with a stderr note) when disabled or misconfigured.
|
|
pub fn maybe_start(cfg: &Config, tx: Sender<Event>) {
|
|
let get = |k: &str| cfg.raw.get(k).and_then(|v| v.last()).map(|s| s.as_str());
|
|
let on = get("rpc").map(crate::config::yesish).unwrap_or(false);
|
|
if !on {
|
|
return;
|
|
}
|
|
let token = match get("rpc_token") {
|
|
Some(t) if !t.is_empty() => t.to_string(),
|
|
_ => {
|
|
eprintln!("echoircd: rpc enabled but no rpc_token set — RPC stays OFF");
|
|
return;
|
|
}
|
|
};
|
|
let bind = get("rpc_bind").unwrap_or("127.0.0.1:8080").to_string();
|
|
let user = get("rpc_user").unwrap_or("admin").to_string();
|
|
match std::net::TcpListener::bind(&bind) {
|
|
Ok(listener) => {
|
|
eprintln!("echoircd JSON-RPC on {bind} (token auth)");
|
|
std::thread::spawn(move || httpd::serve(listener, tx, user, token));
|
|
}
|
|
Err(e) => eprintln!("echoircd: cannot bind rpc {bind}: {e}"),
|
|
}
|
|
}
|