Skip to content

Commit 9ae1f96

Browse files
Taylor R Campbelljcjones
authored andcommitted
Add NetBSD support.
1 parent 0e7c042 commit 9ae1f96

File tree

8 files changed

+416
-2
lines changed

8 files changed

+416
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
This is a cross-platform library for interacting with Security Key-type devices via Rust.
77

8-
* **Supported Platforms**: Windows, Linux, FreeBSD, OpenBSD, and macOS.
8+
* **Supported Platforms**: Windows, Linux, FreeBSD, NetBSD, OpenBSD, and macOS.
99
* **Supported Transports**: USB HID.
1010
* **Supported Protocols**: [FIDO U2F over USB](https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html). [CTAP2 support](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html) is forthcoming, with work being done in the **unstable** [`ctap2` branch](https://github.com/mozilla/authenticator-rs/tree/ctap2).
1111

src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#[macro_use]
66
mod util;
77

8-
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
8+
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
99
pub mod hidproto;
1010

1111
#[cfg(any(target_os = "linux"))]
@@ -22,6 +22,10 @@ extern crate devd_rs;
2222
#[path = "freebsd/mod.rs"]
2323
pub mod platform;
2424

25+
#[cfg(any(target_os = "netbsd"))]
26+
#[path = "netbsd/mod.rs"]
27+
pub mod platform;
28+
2529
#[cfg(any(target_os = "openbsd"))]
2630
#[path = "openbsd/mod.rs"]
2731
pub mod platform;
@@ -41,6 +45,7 @@ pub mod platform;
4145
target_os = "linux",
4246
target_os = "freebsd",
4347
target_os = "openbsd",
48+
target_os = "netbsd",
4449
target_os = "macos",
4550
target_os = "windows"
4651
)))]

src/netbsd/device.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
extern crate libc;
6+
7+
use std::mem;
8+
use std::io::Read;
9+
use std::io::Write;
10+
use std::io;
11+
12+
use consts::CID_BROADCAST;
13+
use consts::HID_RPT_SIZE;
14+
use platform::fd::Fd;
15+
use platform::uhid;
16+
use u2ftypes::U2FDevice;
17+
use util::io_err;
18+
19+
#[derive(Debug)]
20+
pub struct Device {
21+
fd: Fd,
22+
cid: [u8; 4],
23+
}
24+
25+
impl Device {
26+
pub fn new(fd: Fd) -> io::Result<Self> {
27+
Ok(Self { fd, cid: CID_BROADCAST })
28+
}
29+
30+
pub fn is_u2f(&mut self) -> bool {
31+
if !uhid::is_u2f_device(&self.fd) {
32+
return false;
33+
}
34+
// This step is not strictly necessary -- NetBSD puts fido
35+
// devices into raw mode automatically by default, but in
36+
// principle that might change, and this serves as a test to
37+
// verify that we're running on a kernel with support for raw
38+
// mode at all so we don't get confused issuing writes that try
39+
// to set the report descriptor rather than transfer data on
40+
// the output interrupt pipe as we need.
41+
match uhid::hid_set_raw(&self.fd, true) {
42+
Ok(_) => (),
43+
Err(_) => return false,
44+
}
45+
if let Err(_) = self.ping() {
46+
return false;
47+
}
48+
true
49+
}
50+
51+
fn ping(&mut self) -> io::Result<()> {
52+
for i in 0..10 {
53+
let mut buf = vec![0u8; 1 + HID_RPT_SIZE];
54+
55+
buf[0] = 0; // report number
56+
buf[1] = 0xff; // CID_BROADCAST
57+
buf[2] = 0xff;
58+
buf[3] = 0xff;
59+
buf[4] = 0xff;
60+
buf[5] = 0x81; // ping
61+
buf[6] = 0;
62+
buf[7] = 1; // one byte
63+
64+
self.write(&buf[..])?;
65+
66+
// Wait for response
67+
let mut pfd: libc::pollfd = unsafe { mem::zeroed() };
68+
pfd.fd = self.fd.fileno;
69+
pfd.events = libc::POLLIN;
70+
let nfds = unsafe { libc::poll(&mut pfd, 1, 100) };
71+
if nfds == -1 {
72+
return Err(io::Error::last_os_error());
73+
}
74+
if nfds == 0 {
75+
debug!("device timeout {}", i);
76+
continue;
77+
}
78+
79+
// Read response
80+
self.read(&mut buf[..])?;
81+
82+
return Ok(());
83+
}
84+
85+
Err(io_err("no response from device"))
86+
}
87+
}
88+
89+
impl PartialEq for Device {
90+
fn eq(&self, other: &Device) -> bool {
91+
self.fd == other.fd
92+
}
93+
}
94+
95+
impl Read for Device {
96+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
97+
let bufp = buf.as_mut_ptr() as *mut libc::c_void;
98+
let nread = unsafe { libc::read(self.fd.fileno, bufp, buf.len()) };
99+
if nread == -1 {
100+
return Err(io::Error::last_os_error());
101+
}
102+
Ok(nread as usize)
103+
}
104+
}
105+
106+
impl Write for Device {
107+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
108+
// Always skip the first byte (report number)
109+
let data = &buf[1..];
110+
let data_ptr = data.as_ptr() as *const libc::c_void;
111+
let nwrit = unsafe {
112+
libc::write(self.fd.fileno, data_ptr, data.len())
113+
};
114+
if nwrit == -1 {
115+
return Err(io::Error::last_os_error());
116+
}
117+
// Pretend we wrote the report number byte
118+
Ok(nwrit as usize + 1)
119+
}
120+
121+
fn flush(&mut self) -> io::Result<()> {
122+
Ok(())
123+
}
124+
}
125+
126+
impl U2FDevice for Device {
127+
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
128+
&self.cid
129+
}
130+
131+
fn set_cid(&mut self, cid: [u8; 4]) {
132+
self.cid = cid;
133+
}
134+
}

src/netbsd/fd.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
extern crate libc;
6+
7+
use std::ffi::CString;
8+
use std::io;
9+
use std::mem;
10+
use std::os::raw::c_int;
11+
use std::os::unix::io::RawFd;
12+
13+
#[derive(Debug)]
14+
pub struct Fd {
15+
pub fileno: RawFd,
16+
}
17+
18+
impl Fd {
19+
pub fn open(path: &str, flags: c_int) -> io::Result<Fd> {
20+
let cpath = CString::new(path.as_bytes())?;
21+
let rv = unsafe { libc::open(cpath.as_ptr(), flags) };
22+
if rv == -1 {
23+
return Err(io::Error::last_os_error());
24+
}
25+
Ok(Fd { fileno: rv })
26+
}
27+
}
28+
29+
impl Drop for Fd {
30+
fn drop(&mut self) {
31+
unsafe { libc::close(self.fileno) };
32+
}
33+
}
34+
35+
impl PartialEq for Fd {
36+
fn eq(&self, other: &Fd) -> bool {
37+
let mut st: libc::stat = unsafe { mem::zeroed() };
38+
let mut sto: libc::stat = unsafe { mem::zeroed() };
39+
if unsafe { libc::fstat(self.fileno, &mut st) } == -1 {
40+
return false;
41+
}
42+
if unsafe { libc::fstat(other.fileno, &mut sto) } == -1 {
43+
return false;
44+
}
45+
(st.st_dev == sto.st_dev) & (st.st_ino == sto.st_ino)
46+
}
47+
}

src/netbsd/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
pub mod device;
6+
pub mod transaction;
7+
8+
mod fd;
9+
mod monitor;
10+
mod uhid;

src/netbsd/monitor.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
use std::collections::HashMap;
6+
use std::ffi::OsString;
7+
use std::io;
8+
use std::sync::Arc;
9+
use std::thread;
10+
use std::time::Duration;
11+
12+
use runloop::RunLoop;
13+
14+
use platform::fd::Fd;
15+
16+
// XXX Should use drvctl, but it doesn't do pubsub properly yet so
17+
// DRVGETEVENT requires write access to /dev/drvctl. Instead, for now,
18+
// just poll every 500ms.
19+
const POLL_TIMEOUT: u64 = 500;
20+
21+
pub struct Monitor<F>
22+
where
23+
F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static,
24+
{
25+
runloops: HashMap<OsString, RunLoop>,
26+
new_device_cb: Arc<F>,
27+
}
28+
29+
impl<F> Monitor<F>
30+
where
31+
F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static,
32+
{
33+
pub fn new(new_device_cb: F) -> Self {
34+
Self {
35+
runloops: HashMap::new(),
36+
new_device_cb: Arc::new(new_device_cb),
37+
}
38+
}
39+
40+
pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> {
41+
while alive() {
42+
for n in 0..100 {
43+
let uhidpath = format!("/dev/uhid{}", n);
44+
match Fd::open(&uhidpath, libc::O_RDWR | libc::O_CLOEXEC) {
45+
Ok(uhid) => {
46+
self.add_device(uhid, OsString::from(&uhidpath));
47+
},
48+
Err(ref err) => {
49+
match err.raw_os_error() {
50+
Some(libc::EBUSY) => continue,
51+
Some(libc::ENOENT) => break,
52+
_ => self.remove_device(OsString::from(&uhidpath)),
53+
}
54+
},
55+
}
56+
}
57+
thread::sleep(Duration::from_millis(POLL_TIMEOUT));
58+
}
59+
self.remove_all_devices();
60+
Ok(())
61+
}
62+
63+
fn add_device(&mut self, fd: Fd, path: OsString) {
64+
let f = self.new_device_cb.clone();
65+
66+
let runloop = RunLoop::new(move |alive| {
67+
if alive() {
68+
f(fd, alive);
69+
}
70+
});
71+
72+
if let Ok(runloop) = runloop {
73+
self.runloops.insert(path.clone(), runloop);
74+
}
75+
}
76+
77+
fn remove_device(&mut self, path: OsString) {
78+
if let Some(runloop) = self.runloops.remove(&path) {
79+
runloop.cancel();
80+
}
81+
}
82+
83+
fn remove_all_devices(&mut self) {
84+
while !self.runloops.is_empty() {
85+
let path = self.runloops.keys().next().unwrap().clone();
86+
self.remove_device(path);
87+
}
88+
}
89+
}

src/netbsd/transaction.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
use runloop::RunLoop;
6+
use util::OnceCallback;
7+
8+
use platform::fd::Fd;
9+
use platform::monitor::Monitor;
10+
11+
pub struct Transaction {
12+
// Handle to the thread loop.
13+
thread: Option<RunLoop>,
14+
}
15+
16+
impl Transaction {
17+
pub fn new<F, T>(
18+
timeout: u64,
19+
callback: OnceCallback<T>,
20+
new_device_cb: F,
21+
) -> Result<Self, ::Error>
22+
where
23+
F: Fn(Fd, &dyn Fn() -> bool) + Sync + Send + 'static,
24+
T: 'static,
25+
{
26+
let thread = RunLoop::new_with_timeout(
27+
move |alive| {
28+
// Create a new device monitor.
29+
let mut monitor = Monitor::new(new_device_cb);
30+
31+
// Start polling for new devices.
32+
try_or!(monitor.run(alive), |_| callback.call(Err(::Error::Unknown)));
33+
34+
// Send an error, if the callback wasn't called already.
35+
callback.call(Err(::Error::NotAllowed));
36+
},
37+
timeout,
38+
)
39+
.map_err(|_| ::Error::Unknown)?;
40+
41+
Ok(Self {
42+
thread: Some(thread),
43+
})
44+
}
45+
46+
pub fn cancel(&mut self) {
47+
// This must never be None.
48+
self.thread.take().unwrap().cancel();
49+
}
50+
}

0 commit comments

Comments
 (0)