dbus_async/
peer.rs

1use crate::{DBus, DBusResult, Uuid};
2use dbus_message_parser::{
3    message::{Message, MessageHeader, MessageType},
4    value::Value,
5};
6use futures::{
7    channel::mpsc::{channel, Receiver},
8    StreamExt,
9};
10use hex::{decode_to_slice, encode, FromHexError};
11use std::{
12    convert::TryInto,
13    io::Error as IoError,
14    str::{from_utf8, Utf8Error},
15    vec::IntoIter,
16};
17use thiserror::Error;
18use tokio::{fs::File, io::AsyncReadExt, spawn};
19
20#[derive(Debug, Error)]
21enum MachineIdError {
22    #[error("IO Error: {0}")]
23    IoError(#[from] IoError),
24    #[error("Machine ID file is not UTF-8: {0}")]
25    Utf8Error(#[from] Utf8Error),
26    #[error("Machine ID is too large")]
27    FileTooLarge,
28    #[error("Machine ID is too small")]
29    FileTooSmall,
30    #[error("Could not decode hex string")]
31    FromHexError(#[from] FromHexError),
32}
33
34async fn read_machine_id_from_file(path: &str) -> Result<Uuid, MachineIdError> {
35    let mut file = File::open(path).await?;
36    let mut uuid_str: [u8; 32] = [0; 32];
37    let mut uuid: Uuid = [0; 16];
38    let mut new_line: [u8; 2] = [0; 2];
39
40    let read = file.read_exact(&mut uuid_str).await?;
41    if read == uuid_str.len() {
42        // Check if there is more bytes
43        let read = file.read(&mut new_line).await?;
44        let uuid_str = from_utf8(&uuid_str[..])?;
45        if (read == 0 || read == 1) && (new_line[0] == 0 || new_line[0] == b'\n') {
46            decode_to_slice(uuid_str, &mut uuid[..])?;
47            Ok(uuid)
48        } else {
49            Err(MachineIdError::FileTooLarge)
50        }
51    } else {
52        Err(MachineIdError::FileTooSmall)
53    }
54}
55
56async fn get_machine_id_from_file() -> Result<Uuid, ()> {
57    match read_machine_id_from_file("/var/lib/dbus/machine-id").await {
58        Ok(uuid) => Ok(uuid),
59        Err(e) => {
60            error!(
61                "Could not read Machine ID from /var/lib/dbus/machine-id: {}",
62                e
63            );
64            // Fallback to /etc/machine-id.
65            match read_machine_id_from_file("/etc/machine-id").await {
66                Ok(uuid) => Ok(uuid),
67                Err(e) => {
68                    error!("Could not read Machine ID from /etc/machine-id: {}", e);
69                    Err(())
70                }
71            }
72        }
73    }
74}
75
76async fn get_machine_id(header: &MessageHeader) -> Message {
77    match header.method_return() {
78        Ok(mut msg) => match get_machine_id_from_file().await {
79            Ok(uuid) => {
80                let uuid = encode(&uuid);
81                msg.add_value(Value::String(uuid));
82                msg
83            }
84            Err(_) => header.error(
85                "org.freedesktop.DBus.Peer.MachineIdError"
86                    .try_into()
87                    .unwrap(),
88                "Could not retrieve Machine ID.".to_string(),
89            ),
90        },
91        Err(msg) => msg,
92    }
93}
94
95/// This is the handle method for the [`org.freedesktop.DBus.Peer`] interface.
96///
97/// [`org.freedesktop.DBus.Peer`]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-peer
98pub async fn handle_peer(
99    dbus: &DBus,
100    header: MessageHeader,
101    mut body_iter: IntoIter<Value>,
102) -> DBusResult<()> {
103    let member = if let Some(member) = header.get_member() {
104        member
105    } else {
106        return Ok(());
107    };
108
109    let response = match member.as_ref() {
110        "GetMachineId" => {
111            if body_iter.next().is_none() {
112                get_machine_id(&header).await
113            } else {
114                header.invalid_args("Too many arguments".to_string())
115            }
116        }
117        "Ping" => {
118            if body_iter.next().is_none() {
119                // The unwrap function call will never panic because we check the type at the
120                // beginning of the while loop.
121                header.method_return().unwrap()
122            } else {
123                header.invalid_args("Too many arguments".to_string())
124            }
125        }
126        _ => {
127            if let Some(msg) = header.unknown_member() {
128                msg
129            } else {
130                return Ok(());
131            }
132        }
133    };
134
135    dbus.send(response)
136}
137
138async fn peer(dbus: DBus, mut receiver: Receiver<Message>) {
139    while let Some(request) = receiver.next().await {
140        if MessageType::MethodCall != request.get_type() {
141            continue;
142        }
143
144        if let Ok((header, body)) = request.split() {
145            if let Err(e) = handle_peer(&dbus, header, body.into_iter()).await {
146                error!(
147                    "Could not handle method for org.freedesktop.DBus.Peer: {}",
148                    e
149                );
150            }
151        }
152    }
153}
154
155pub(super) fn add_peer(dbus: DBus) -> DBusResult<()> {
156    let (sender, receiver) = channel(1024);
157    let interface = "org.freedesktop.DBus.Peer".try_into().unwrap();
158    // Try to add the interface handler.
159    if let Err(e) = dbus.add_method_call_interface(interface, sender) {
160        return Err(e);
161    }
162
163    spawn(peer(dbus, receiver));
164    Ok(())
165}