Skip to main content

netget/server/sunrpc/
wire.rs

1//! ONC RPC (RFC 5531) and the portmapper/rpcbind programs (RFC 1833), shared by both roles:
2//! XDR, call and reply messages, TCP record marking, and the PMAP v2 / RPCBIND v3-v4
3//! argument and result bodies. Every length the peer announces is bounded before use.
4use anyhow::{bail, ensure, Context, Result};
5use serde_json::{json, Value};
6use std::net::{IpAddr, SocketAddr};
7
8pub const PORT: u16 = 111;
9pub const PROGRAM: u32 = 100_000;
10/// The portmapper versions spoken: PMAP 2, RPCBIND 3 and 4.
11pub const LOW_VERSION: u32 = 2;
12pub const HIGH_VERSION: u32 = 4;
13/// One RPC message (a whole TCP record, or a UDP datagram) is at most this long. Nothing
14/// a portmapper exchanges comes near it; a DUMP of 1000 mappings is about 60 KiB.
15pub const MAX_RECORD: usize = 256 * 1024;
16/// Fragments one TCP record may be split into.
17pub const MAX_FRAGMENTS: usize = 64;
18/// XDR strings (netids, universal addresses, owners) are at most this long.
19pub const MAX_STRING: usize = 1024;
20/// Credential and verifier bodies are at most 400 bytes (RFC 5531 ยง8.2).
21pub const MAX_AUTH: usize = 400;
22/// Mappings one answer may carry.
23pub const MAX_MAPPINGS: usize = 1000;
24
25pub const IPPROTO_TCP: u32 = 6;
26pub const IPPROTO_UDP: u32 = 17;
27
28/// Well-known program numbers, so the handler sees a name beside the number.
29pub fn program_name(p: u32) -> Option<&'static str> {
30    Some(match p {
31        100_000 => "portmapper",
32        100_003 => "nfs",
33        100_005 => "mountd",
34        100_011 => "rquotad",
35        100_021 => "nlockmgr",
36        100_024 => "status",
37        100_227 => "nfs_acl",
38        100_004 => "ypserv",
39        100_007 => "ypbind",
40        150_001 => "pcnfsd",
41        _ => return None,
42    })
43}
44
45pub struct Reader<'a> {
46    b: &'a [u8],
47    pos: usize,
48}
49
50impl<'a> Reader<'a> {
51    pub fn new(b: &'a [u8]) -> Self {
52        Self { b, pos: 0 }
53    }
54    pub fn u32(&mut self) -> Result<u32> {
55        let v = self
56            .b
57            .get(self.pos..self.pos + 4)
58            .context("XDR value runs past the message")?;
59        self.pos += 4;
60        Ok(u32::from_be_bytes([v[0], v[1], v[2], v[3]]))
61    }
62    pub fn opaque(&mut self, max: usize) -> Result<&'a [u8]> {
63        let n = self.u32()? as usize;
64        ensure!(n <= max, "XDR opaque of {n} bytes; the limit is {max}");
65        let padded = n + (4 - n % 4) % 4;
66        let v = self
67            .b
68            .get(self.pos..self.pos + n)
69            .context("XDR opaque runs past the message")?;
70        ensure!(self.pos + padded <= self.b.len(), "XDR padding missing");
71        self.pos += padded;
72        Ok(v)
73    }
74    pub fn string(&mut self) -> Result<String> {
75        let b = self.opaque(MAX_STRING)?;
76        String::from_utf8(b.to_vec()).context("XDR string is not UTF-8")
77    }
78    pub fn bool(&mut self) -> Result<bool> {
79        match self.u32()? {
80            0 => Ok(false),
81            1 => Ok(true),
82            v => bail!("XDR bool must be 0 or 1, not {v}"),
83        }
84    }
85    pub fn rest(&self) -> &'a [u8] {
86        &self.b[self.pos..]
87    }
88    pub fn done(&self) -> bool {
89        self.pos == self.b.len()
90    }
91}
92
93#[derive(Default)]
94pub struct Writer(pub Vec<u8>);
95
96impl Writer {
97    pub fn u32(&mut self, v: u32) -> &mut Self {
98        self.0.extend_from_slice(&v.to_be_bytes());
99        self
100    }
101    pub fn opaque(&mut self, b: &[u8]) -> &mut Self {
102        self.u32(b.len() as u32);
103        self.0.extend_from_slice(b);
104        self.0
105            .extend(std::iter::repeat_n(0u8, (4 - b.len() % 4) % 4));
106        self
107    }
108    pub fn string(&mut self, s: &str) -> &mut Self {
109        self.opaque(s.as_bytes())
110    }
111}
112
113/// An RPC credential, as the handler is shown it.
114pub fn credential_json(flavor: u32, body: &[u8]) -> Value {
115    match flavor {
116        0 => json!({"flavor": "none"}),
117        1 => {
118            // AUTH_SYS: stamp, machine name, uid, gid, gids.
119            let mut r = Reader::new(body);
120            let parsed = (|| -> Result<Value> {
121                let stamp = r.u32()?;
122                let machine = String::from_utf8_lossy(r.opaque(255)?).into_owned();
123                let uid = r.u32()?;
124                let gid = r.u32()?;
125                let n = r.u32()? as usize;
126                ensure!(n <= 16, "too many gids");
127                let gids = (0..n).map(|_| r.u32()).collect::<Result<Vec<_>>>()?;
128                Ok(
129                    json!({"flavor": "sys", "stamp": stamp, "machine": machine, "uid": uid, "gid": gid, "gids": gids}),
130                )
131            })();
132            parsed.unwrap_or_else(|_| json!({"flavor": "sys", "malformed": true}))
133        }
134        f => json!({"flavor": f}),
135    }
136}
137
138pub struct Call<'a> {
139    pub xid: u32,
140    pub rpc_version: u32,
141    pub program: u32,
142    pub version: u32,
143    pub procedure: u32,
144    pub cred_flavor: u32,
145    pub cred: &'a [u8],
146    pub args: &'a [u8],
147}
148
149/// A message that is a call; `Ok(None)` for a reply (which a server ignores).
150pub fn parse_call(b: &[u8]) -> Result<Option<Call<'_>>> {
151    let mut r = Reader::new(b);
152    let xid = r.u32()?;
153    if r.u32()? != 0 {
154        return Ok(None);
155    }
156    let rpc_version = r.u32()?;
157    let program = r.u32()?;
158    let version = r.u32()?;
159    let procedure = r.u32()?;
160    let cred_flavor = r.u32()?;
161    let cred = r.opaque(MAX_AUTH)?;
162    let _verf_flavor = r.u32()?;
163    let _verf = r.opaque(MAX_AUTH)?;
164    Ok(Some(Call {
165        xid,
166        rpc_version,
167        program,
168        version,
169        procedure,
170        cred_flavor,
171        cred,
172        args: r.rest(),
173    }))
174}
175
176/// Just the xid, for answering a call too malformed to parse.
177pub fn xid_of(b: &[u8]) -> Option<u32> {
178    b.get(..4)
179        .map(|x| u32::from_be_bytes([x[0], x[1], x[2], x[3]]))
180}
181
182/// What an accepted call came to.
183pub enum Accepted {
184    Success(Vec<u8>),
185    ProgUnavail,
186    ProgMismatch(u32, u32),
187    ProcUnavail,
188    GarbageArgs,
189    SystemErr,
190}
191
192pub fn accepted_reply(xid: u32, a: &Accepted) -> Vec<u8> {
193    let mut w = Writer::default();
194    w.u32(xid).u32(1).u32(0); // REPLY, MSG_ACCEPTED
195    w.u32(0).u32(0); // verifier AUTH_NONE
196    match a {
197        Accepted::Success(body) => {
198            w.u32(0);
199            w.0.extend_from_slice(body);
200        }
201        Accepted::ProgUnavail => {
202            w.u32(1);
203        }
204        Accepted::ProgMismatch(lo, hi) => {
205            w.u32(2).u32(*lo).u32(*hi);
206        }
207        Accepted::ProcUnavail => {
208            w.u32(3);
209        }
210        Accepted::GarbageArgs => {
211            w.u32(4);
212        }
213        Accepted::SystemErr => {
214            w.u32(5);
215        }
216    }
217    w.0
218}
219
220/// MSG_DENIED / RPC_MISMATCH: this server speaks RPC version 2 only.
221pub fn rpc_mismatch_reply(xid: u32) -> Vec<u8> {
222    let mut w = Writer::default();
223    w.u32(xid).u32(1).u32(1).u32(0).u32(2).u32(2);
224    w.0
225}
226
227pub fn call_message(xid: u32, version: u32, procedure: u32, args: &[u8]) -> Vec<u8> {
228    let mut w = Writer::default();
229    w.u32(xid)
230        .u32(0)
231        .u32(2)
232        .u32(PROGRAM)
233        .u32(version)
234        .u32(procedure);
235    w.u32(0).u32(0).u32(0).u32(0); // AUTH_NONE credential and verifier
236    w.0.extend_from_slice(args);
237    w.0
238}
239
240/// A reply as the client reads it: its xid and either the result body or why there is none.
241pub fn parse_reply(b: &[u8]) -> Result<(u32, std::result::Result<Vec<u8>, String>)> {
242    let mut r = Reader::new(b);
243    let xid = r.u32()?;
244    ensure!(r.u32()? == 1, "not a reply");
245    match r.u32()? {
246        0 => {
247            r.u32()?;
248            r.opaque(MAX_AUTH)?;
249            let stat = r.u32()?;
250            Ok((
251                xid,
252                match stat {
253                    0 => Ok(r.rest().to_vec()),
254                    1 => Err("PROG_UNAVAIL".into()),
255                    2 => Err(format!(
256                        "PROG_MISMATCH (versions {}-{})",
257                        r.u32()?,
258                        r.u32()?
259                    )),
260                    3 => Err("PROC_UNAVAIL".into()),
261                    4 => Err("GARBAGE_ARGS".into()),
262                    5 => Err("SYSTEM_ERR".into()),
263                    s => Err(format!("accept_stat {s}")),
264                },
265            ))
266        }
267        1 => {
268            let why = match r.u32()? {
269                0 => format!("RPC_MISMATCH (versions {}-{})", r.u32()?, r.u32()?),
270                _ => format!("AUTH_ERROR {}", r.u32()?),
271            };
272            Ok((xid, Err(why)))
273        }
274        s => bail!("reply_stat {s}"),
275    }
276}
277
278/// TCP record marking: one fragment, last-fragment bit set.
279pub fn record(msg: &[u8]) -> Vec<u8> {
280    let mut out = (0x8000_0000u32 | msg.len() as u32).to_be_bytes().to_vec();
281    out.extend_from_slice(msg);
282    out
283}
284
285/// Read one record, refusing before reading past the bounds.
286pub async fn read_record<R: tokio::io::AsyncRead + Unpin>(r: &mut R) -> Result<Option<Vec<u8>>> {
287    use tokio::io::AsyncReadExt;
288    let mut out = Vec::new();
289    for i in 0..MAX_FRAGMENTS {
290        let mut h = [0u8; 4];
291        match r.read_exact(&mut h).await {
292            Ok(_) => {}
293            Err(e) if i == 0 && e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
294            Err(e) => return Err(e.into()),
295        }
296        let h = u32::from_be_bytes(h);
297        let len = (h & 0x7fff_ffff) as usize;
298        ensure!(
299            out.len() + len <= MAX_RECORD,
300            "record of more than {MAX_RECORD} bytes announced"
301        );
302        let start = out.len();
303        out.resize(start + len, 0);
304        r.read_exact(&mut out[start..]).await?;
305        if h & 0x8000_0000 != 0 {
306            return Ok(Some(out));
307        }
308    }
309    bail!("record split into more than {MAX_FRAGMENTS} fragments")
310}
311
312// ---------------------------------------------------------------------------------------
313// Mappings, the portmapper's one kind of data.
314
315#[derive(Clone, Debug, PartialEq)]
316pub struct Mapping {
317    pub program: u32,
318    pub version: u32,
319    /// tcp, udp, tcp6 or udp6.
320    pub netid: String,
321    pub port: u16,
322    pub owner: String,
323}
324
325pub fn netid_of(protocol: u32) -> Option<&'static str> {
326    match protocol {
327        IPPROTO_TCP => Some("tcp"),
328        IPPROTO_UDP => Some("udp"),
329        _ => None,
330    }
331}
332
333pub fn protocol_of(netid: &str) -> Option<u32> {
334    match netid {
335        "tcp" | "tcp6" => Some(IPPROTO_TCP),
336        "udp" | "udp6" => Some(IPPROTO_UDP),
337        _ => None,
338    }
339}
340
341/// A universal address: `h1.h2.h3.h4.p1.p2`, or an IPv6 address and `.p1.p2`.
342pub fn uaddr(ip: IpAddr, port: u16) -> String {
343    format!("{ip}.{}.{}", port >> 8, port & 0xff)
344}
345
346pub fn parse_uaddr(s: &str) -> Result<SocketAddr> {
347    let mut parts = s.rsplitn(3, '.');
348    let lo: u16 = parts.next().context("empty universal address")?.parse()?;
349    let hi: u16 = parts
350        .next()
351        .context("universal address has no port")?
352        .parse()?;
353    let ip: IpAddr = parts
354        .next()
355        .context("universal address has no host")?
356        .parse()?;
357    ensure!(
358        hi < 256 && lo < 256,
359        "universal address port bytes out of range"
360    );
361    Ok(SocketAddr::new(ip, hi << 8 | lo))
362}
363
364/// A mapping as the handler writes it: `{program, version, protocol, port, owner?}` with
365/// protocol one of tcp/udp/tcp6/udp6.
366pub fn mapping_from_json(v: &Value) -> Result<Mapping> {
367    let num = |k: &str| -> Result<u64> {
368        v[k].as_u64()
369            .with_context(|| format!("{k} must be a number"))
370    };
371    let program = num("program")?;
372    let version = num("version")?;
373    let port = num("port")?;
374    ensure!(
375        program <= u64::from(u32::MAX) && version <= u64::from(u32::MAX),
376        "program and version are 32-bit"
377    );
378    ensure!(port <= 65535, "port must be 0-65535");
379    let netid = v["protocol"].as_str().unwrap_or("tcp").to_string();
380    ensure!(
381        protocol_of(&netid).is_some(),
382        "protocol must be tcp, udp, tcp6 or udp6"
383    );
384    Ok(Mapping {
385        program: program as u32,
386        version: version as u32,
387        netid,
388        port: port as u16,
389        owner: v["owner"]
390            .as_str()
391            .unwrap_or("netget")
392            .chars()
393            .take(64)
394            .collect(),
395    })
396}
397
398pub fn mapping_json(m: &Mapping) -> Value {
399    json!({"program": m.program, "program_name": program_name(m.program), "version": m.version,
400           "protocol": m.netid, "port": m.port, "owner": m.owner})
401}
402
403/// PMAP v2 `pmap` (prog, vers, prot, port).
404pub fn read_pmap(r: &mut Reader) -> Result<(u32, u32, u32, u32)> {
405    Ok((r.u32()?, r.u32()?, r.u32()?, r.u32()?))
406}
407
408pub fn write_pmap(w: &mut Writer, m: &Mapping) {
409    let prot = protocol_of(&m.netid).unwrap_or(IPPROTO_TCP);
410    w.u32(m.program)
411        .u32(m.version)
412        .u32(prot)
413        .u32(u32::from(m.port));
414}
415
416/// RPCBIND `rpcb` (prog, vers, netid, addr, owner).
417pub struct Rpcb {
418    pub program: u32,
419    pub version: u32,
420    pub netid: String,
421    pub addr: String,
422    pub owner: String,
423}
424
425pub fn read_rpcb(r: &mut Reader) -> Result<Rpcb> {
426    Ok(Rpcb {
427        program: r.u32()?,
428        version: r.u32()?,
429        netid: r.string()?,
430        addr: r.string()?,
431        owner: r.string()?,
432    })
433}
434
435pub fn write_rpcb(w: &mut Writer, b: &Rpcb) {
436    w.u32(b.program).u32(b.version);
437    w.string(&b.netid).string(&b.addr).string(&b.owner);
438}
439
440/// The host part of a universal address for a mapping, as a client reaching `local` sees it.
441pub fn host_for(netid: &str, local: IpAddr) -> IpAddr {
442    match (netid.ends_with('6'), local) {
443        (true, IpAddr::V4(_)) => IpAddr::from(std::net::Ipv6Addr::LOCALHOST),
444        (false, IpAddr::V6(v6)) => v6
445            .to_ipv4_mapped()
446            .map(IpAddr::V4)
447            .unwrap_or(IpAddr::from([127, 0, 0, 1])),
448        _ => local,
449    }
450}
451
452/// PMAP v2 DUMP result: the linked list of mappings (only tcp and udp have a v2 form).
453pub fn pmaplist(ms: &[Mapping]) -> Vec<u8> {
454    let mut w = Writer::default();
455    for m in ms.iter().filter(|m| m.netid == "tcp" || m.netid == "udp") {
456        w.u32(1);
457        write_pmap(&mut w, m);
458    }
459    w.u32(0);
460    w.0
461}
462
463/// RPCBIND DUMP result.
464pub fn rpcblist(ms: &[Mapping], local: IpAddr) -> Vec<u8> {
465    let mut w = Writer::default();
466    for m in ms {
467        w.u32(1);
468        write_rpcb(
469            &mut w,
470            &Rpcb {
471                program: m.program,
472                version: m.version,
473                netid: m.netid.clone(),
474                addr: uaddr(host_for(&m.netid, local), m.port),
475                owner: m.owner.clone(),
476            },
477        );
478    }
479    w.u32(0);
480    w.0
481}
482
483/// RPCBIND v4 GETADDRLIST result: one `rpcb_entry` per mapping.
484pub fn entry_list(ms: &[Mapping], local: IpAddr) -> Vec<u8> {
485    let mut w = Writer::default();
486    for m in ms {
487        let tcp = m.netid.starts_with("tcp");
488        w.u32(1);
489        w.string(&uaddr(host_for(&m.netid, local), m.port));
490        w.string(&m.netid);
491        w.u32(if tcp { 3 } else { 1 }); // NC_TPI_COTS_ORD / NC_TPI_CLTS
492        w.string(if m.netid.ends_with('6') {
493            "inet6"
494        } else {
495            "inet"
496        });
497        w.string(if tcp { "tcp" } else { "udp" });
498    }
499    w.u32(0);
500    w.0
501}
502
503/// Read a PMAP v2 DUMP result.
504pub fn read_pmaplist(b: &[u8]) -> Result<Vec<Mapping>> {
505    let mut r = Reader::new(b);
506    let mut out = Vec::new();
507    while r.bool()? {
508        ensure!(
509            out.len() < MAX_MAPPINGS,
510            "more than {MAX_MAPPINGS} mappings"
511        );
512        let (program, version, prot, port) = read_pmap(&mut r)?;
513        out.push(Mapping {
514            program,
515            version,
516            netid: netid_of(prot).unwrap_or("unknown").to_string(),
517            port: port as u16,
518            owner: String::new(),
519        });
520    }
521    Ok(out)
522}
523
524/// Read an RPCBIND DUMP result.
525pub fn read_rpcblist(b: &[u8]) -> Result<Vec<Value>> {
526    let mut r = Reader::new(b);
527    let mut out = Vec::new();
528    while r.bool()? {
529        ensure!(
530            out.len() < MAX_MAPPINGS,
531            "more than {MAX_MAPPINGS} mappings"
532        );
533        let e = read_rpcb(&mut r)?;
534        let port = parse_uaddr(&e.addr).map(|a| a.port()).ok();
535        out.push(json!({"program": e.program, "program_name": program_name(e.program), "version": e.version,
536                        "netid": e.netid, "address": e.addr, "port": port, "owner": e.owner}));
537    }
538    Ok(out)
539}