1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
//! Routerstatus-specific parts of networkstatus parsing.
//!
//! This is a private module; relevant pieces are re-exported by its
//! parent.

#[cfg(feature = "build_docs")]
pub(crate) mod build;

use super::{
    ConsensusFlavor, NetstatusKwd, ParseRouterStatus, RelayFlags, RelayWeight, RouterStatus,
};
use crate::doc::microdesc::MdDigest;
use crate::doc::routerdesc::RdDigest;
use crate::types::misc::*;
use crate::{parse::parser::Section, util::private::Sealed};
use crate::{Error, Pos, Result};
use std::{net, time};

use tor_llcrypto::pk::rsa::RsaIdentity;
use tor_protover::Protocols;

use std::convert::TryInto;

/// A single relay's status, as represented in a microdesc consensus.
#[derive(Debug, Clone)]
pub struct MdConsensusRouterStatus {
    /// Underlying generic routerstatus object.
    ///
    /// This is private because we don't want to leak that these two
    /// types have the same implementation "under the hood".
    rs: GenericRouterStatus<MdDigest>,
}

/// A single relay's status, as represented in a "ns" consensus.
#[derive(Debug, Clone)]
pub struct NsConsensusRouterStatus {
    /// Underlying generic routerstatus object.
    ///
    /// This is private because we don't want to leak that these two
    /// types have the same implementation "under the hood".
    rs: GenericRouterStatus<RdDigest>,
}

impl From<GenericRouterStatus<RdDigest>> for NsConsensusRouterStatus {
    fn from(rs: GenericRouterStatus<RdDigest>) -> Self {
        NsConsensusRouterStatus { rs }
    }
}

impl From<GenericRouterStatus<MdDigest>> for MdConsensusRouterStatus {
    fn from(rs: GenericRouterStatus<MdDigest>) -> Self {
        MdConsensusRouterStatus { rs }
    }
}

/// Shared implementation of MdConsensusRouterStatus and NsConsensusRouterStatus.
#[derive(Debug, Clone)]
struct GenericRouterStatus<D> {
    /// The nickname for this relay.
    ///
    /// Nicknames can be used for convenience purpose, but no more:
    /// there is no mechanism to enforce their uniqueness.
    nickname: String,
    /// Fingerprint of the old-style RSA identity for this relay.
    identity: RsaIdentity,
    /// Declared time at which the router descriptor for this relay
    /// was published.
    ///
    /// This value should be ignored for all purposes; see
    /// [proposal 275](https://gitlab.torproject.org/tpo/core/torspec/-/blob/master/proposals/275-md-published-time-is-silly.txt).
    // TODO: so why not remove this?
    published: time::SystemTime,
    /// A list of address:port values where this relay can be reached.
    addrs: Vec<net::SocketAddr>,
    /// Declared OR port for this relay.
    or_port: u16,
    /// Declared directory port for this relay.
    dir_port: u16,
    /// Digest of the document for this relay.
    doc_digest: D,
    /// Flags applied by the authorities to this relay.
    flags: RelayFlags,
    /// Version of the software that this relay is running.
    version: Option<String>,
    /// List of subprotocol versions supported by this relay.
    protos: Protocols,
    /// Information about how to weight this relay when choosing a
    /// relay at random.
    weight: RelayWeight,
}

// TODO: These methods should probably become, in whole or in part,
// methods on the RouterStatus trait.
macro_rules! implement_accessors {
    ($name:ident) => {
        impl $name {
            /// Return an iterator of ORPort addresses for this routerstatus
            pub fn orport_addrs(&self) -> impl Iterator<Item = &net::SocketAddr> {
                self.rs.addrs.iter()
            }
            /// Return the declared weight of this routerstatus in the directory.
            pub fn weight(&self) -> &RelayWeight {
                &self.rs.weight
            }
            /// Return the ORPort addresses of this routerstatus
            pub fn addrs(&self) -> &[net::SocketAddr] {
                &self.rs.addrs[..]
            }
            /// Return the protovers that this routerstatus says it implements.
            pub fn protovers(&self) -> &Protocols {
                &self.rs.protos
            }
            /// Return the nickname of this routerstatus.
            pub fn nickname(&self) -> &String {
                &self.rs.nickname
            }
            /// Return the relay flags of this routerstatus.
            pub fn flags(&self) -> &RelayFlags {
                &self.rs.flags
            }
            /// Return the version of this routerstatus.
            pub fn version(&self) -> &Option<String> {
                &self.rs.version
            }
            /// Return true if the ed25519 identity on this relay reflects a
            /// true consensus among the authorities.
            pub fn ed25519_id_is_usable(&self) -> bool {
                !self.rs.flags.contains(RelayFlags::NO_ED_CONSENSUS)
            }
            /// Return true if this routerstatus is listed with the BadExit flag.
            pub fn is_flagged_bad_exit(&self) -> bool {
                self.rs.flags.contains(RelayFlags::BAD_EXIT)
            }
            /// Return true if this routerstatus is listed with the v2dir flag.
            pub fn is_flagged_v2dir(&self) -> bool {
                self.rs.flags.contains(RelayFlags::V2DIR)
            }
            /// Return true if this routerstatus is listed with the Exit flag.
            pub fn is_flagged_exit(&self) -> bool {
                self.rs.flags.contains(RelayFlags::EXIT)
            }
            /// Return true if this routerstatus is listed with the Guard flag.
            pub fn is_flagged_guard(&self) -> bool {
                self.rs.flags.contains(RelayFlags::GUARD)
            }
        }
    };
}

implement_accessors! {MdConsensusRouterStatus}
implement_accessors! {NsConsensusRouterStatus}

impl MdConsensusRouterStatus {
    /// Return the expected microdescriptor digest for this routerstatus
    pub fn md_digest(&self) -> &MdDigest {
        &self.rs.doc_digest
    }
}

impl NsConsensusRouterStatus {
    /// Return the expected router descriptor digest for this routerstatus
    pub fn rd_digest(&self) -> &RdDigest {
        &self.rs.doc_digest
    }
}

impl Sealed for MdConsensusRouterStatus {}

impl RouterStatus for MdConsensusRouterStatus {
    type DocumentDigest = MdDigest;

    /// Return the expected microdescriptor digest for this routerstatus
    fn rsa_identity(&self) -> &RsaIdentity {
        &self.rs.identity
    }

    fn doc_digest(&self) -> &MdDigest {
        self.md_digest()
    }
}

impl ParseRouterStatus for MdConsensusRouterStatus {
    fn flavor() -> ConsensusFlavor {
        ConsensusFlavor::Microdesc
    }

    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<MdConsensusRouterStatus> {
        let rs = GenericRouterStatus::from_section(sec, true)?;
        Ok(MdConsensusRouterStatus { rs })
    }
}

impl Sealed for NsConsensusRouterStatus {}

impl RouterStatus for NsConsensusRouterStatus {
    type DocumentDigest = RdDigest;

    /// Return the expected microdescriptor digest for this routerstatus
    fn rsa_identity(&self) -> &RsaIdentity {
        &self.rs.identity
    }

    fn doc_digest(&self) -> &RdDigest {
        self.rd_digest()
    }
}

impl ParseRouterStatus for NsConsensusRouterStatus {
    fn flavor() -> ConsensusFlavor {
        ConsensusFlavor::Ns
    }

    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<NsConsensusRouterStatus> {
        let rs = GenericRouterStatus::from_section(sec, false)?;
        Ok(NsConsensusRouterStatus { rs })
    }
}

/// Helper to decode a document digest in the format in which it
/// appears in a given kind of routerstatus.
trait FromRsString: Sized {
    /// Try to decode the given object.
    fn decode(s: &str) -> Result<Self>;
}

impl FromRsString for MdDigest {
    fn decode(s: &str) -> Result<MdDigest> {
        s.parse::<B64>()?
            .check_len(32..=32)?
            .as_bytes()
            .try_into()
            .map_err(|_| Error::Internal(Pos::None))
    }
}

impl FromRsString for RdDigest {
    fn decode(s: &str) -> Result<RdDigest> {
        s.parse::<B64>()?
            .check_len(20..=20)?
            .as_bytes()
            .try_into()
            .map_err(|_| Error::Internal(Pos::None))
    }
}

impl<D> GenericRouterStatus<D>
where
    D: FromRsString,
{
    /// Parse a generic routerstatus from a section.
    ///
    /// Requires that the section obeys the right SectionRules,
    /// matching microdesc_format.
    fn from_section(
        sec: &Section<'_, NetstatusKwd>,
        microdesc_format: bool,
    ) -> Result<GenericRouterStatus<D>> {
        use NetstatusKwd::*;
        // R line
        let r_item = sec.required(RS_R)?;
        let nickname = r_item.required_arg(0)?.to_string();
        let ident = r_item.required_arg(1)?.parse::<B64>()?;
        let identity = RsaIdentity::from_bytes(ident.as_bytes())
            .ok_or_else(|| Error::BadArgument(r_item.pos(), "Wrong identity length".to_string()))?;
        let skip = if microdesc_format { 0 } else { 1 };
        let published: time::SystemTime = {
            // TODO: It's annoying to have to do this allocation, since we
            // already have a slice that contains both of these arguments.
            // Instead, we could get a slice of arguments: we'd have to add
            // a feature for that.
            let mut p = r_item.required_arg(2 + skip)?.to_string();
            p.push(' ');
            p.push_str(r_item.required_arg(3 + skip)?);
            p.parse::<Iso8601TimeSp>()?.into()
        };
        let ipv4addr = r_item.required_arg(4 + skip)?.parse::<net::Ipv4Addr>()?;
        let or_port = r_item.required_arg(5 + skip)?.parse::<u16>()?;
        let dir_port = r_item.required_arg(6 + skip)?.parse::<u16>()?;

        let mut addrs: Vec<net::SocketAddr> = vec![net::SocketAddr::V4(net::SocketAddrV4::new(
            ipv4addr, or_port,
        ))];

        // A lines
        for a_item in sec.slice(RS_A) {
            addrs.push(a_item.required_arg(0)?.parse::<net::SocketAddr>()?);
        }

        // S line
        let flags = RelayFlags::from_item(sec.required(RS_S)?)?;

        // V line
        let version = sec.maybe(RS_V).args_as_str().map(str::to_string);

        // PR line
        let protos = {
            let tok = sec.required(RS_PR)?;
            tok.args_as_str()
                .parse::<Protocols>()
                .map_err(|e| Error::BadArgument(tok.pos(), e.to_string()))?
        };

        // W line
        let weight = sec
            .get(RS_W)
            .map(RelayWeight::from_item)
            .transpose()?
            .unwrap_or_else(Default::default);

        // No p line
        // no ID line

        // Try to find the document digest.  This is in different
        // places depending on the kind of consensus we're in.
        let doc_digest: D = if microdesc_format {
            // M line
            let m_item = sec.required(RS_M)?;
            D::decode(m_item.required_arg(0)?)?
        } else {
            D::decode(r_item.required_arg(2)?)?
        };

        Ok(GenericRouterStatus {
            nickname,
            identity,
            published,
            addrs,
            or_port,
            dir_port,
            doc_digest,
            flags,
            version,
            protos,
            weight,
        })
    }
}