about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--users/Profpatsch/imap-idle.nix14
-rw-r--r--users/Profpatsch/imap-idle.rs132
-rw-r--r--users/Profpatsch/rust-crates.nix89
3 files changed, 235 insertions, 0 deletions
diff --git a/users/Profpatsch/imap-idle.nix b/users/Profpatsch/imap-idle.nix
new file mode 100644
index 000000000000..30c7b6e8aac8
--- /dev/null
+++ b/users/Profpatsch/imap-idle.nix
@@ -0,0 +1,14 @@
+{ depot, pkgs, lib, ... }:
+
+let
+  imap-idle = depot.users.Profpatsch.writers.rustSimple {
+    name = "imap-idle";
+    dependencies = [
+      depot.users.Profpatsch.arglib.netencode.rust
+      depot.users.Profpatsch.rust-crates.imap
+      depot.users.Profpatsch.rust-crates.epoll
+      depot.users.Profpatsch.execline.exec-helpers
+    ];
+  } (builtins.readFile ./imap-idle.rs);
+
+in imap-idle
diff --git a/users/Profpatsch/imap-idle.rs b/users/Profpatsch/imap-idle.rs
new file mode 100644
index 000000000000..9dce736d0d8a
--- /dev/null
+++ b/users/Profpatsch/imap-idle.rs
@@ -0,0 +1,132 @@
+extern crate exec_helpers;
+// extern crate arglib_netencode;
+// extern crate netencode;
+extern crate imap;
+extern crate epoll;
+
+// use netencode::dec;
+use std::convert::TryFrom;
+use std::io::{Read, Write};
+use std::fs::File;
+use std::os::unix::io::{FromRawFd, AsRawFd, RawFd};
+use std::time::Duration;
+use imap::extensions::idle::SetReadTimeout;
+
+/// Implements an UCSPI client that wraps fd 6 & 7
+/// and implements Write and Read with a timeout.
+/// See https://cr.yp.to/proto/ucspi.txt
+#[derive(Debug)]
+struct UcspiClient {
+    read: File,
+    read_epoll_fd: RawFd,
+    read_timeout: Option<Duration>,
+    write: File,
+}
+
+impl UcspiClient {
+    /// Use fd 6 and 7 to connect to the net, as is specified.
+    /// Unsafe because fd 6 and 7 are global resources and we don’t mutex them.
+    pub unsafe fn new_from_6_and_7() -> std::io::Result<Self> {
+        unsafe {
+            let read_epoll_fd = epoll::create(false)?;
+            Ok(UcspiClient {
+                read: File::from_raw_fd(6),
+                read_epoll_fd,
+                read_timeout: None,
+                write: File::from_raw_fd(7)
+            })
+        }
+    }
+}
+
+/// Emulates set_read_timeout() like on a TCP socket with an epoll on read.
+/// The BSD socket API is rather bad, so fd != fd,
+/// and if we cast the `UcspiClient` fds to `TcpStream` instead of `File`,
+/// we’d break any UCSPI client programs that *don’t* connect to TCP.
+/// Instead we use the (linux) `epoll` API in read to wait on the timeout.
+impl SetReadTimeout for UcspiClient {
+    fn set_read_timeout(&mut self, timeout: Option<Duration>) -> imap::Result<()> {
+        self.read_timeout = timeout;
+        Ok(())
+    }
+}
+
+impl Read for UcspiClient {
+    // TODO: test the epoll code with a short timeout
+    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+        const NO_DATA : u64 = 0;
+        // in order to implement the read_timeout,
+        // we use epoll to wait for either data or time out
+        epoll::ctl(
+            self.read_epoll_fd,
+            epoll::ControlOptions::EPOLL_CTL_ADD,
+            self.read.as_raw_fd(),
+            epoll::Event::new(epoll::Events::EPOLLIN, NO_DATA)
+        )?;
+        let UNUSED = epoll::Event::new(epoll::Events::EPOLLIN, NO_DATA);
+        let wait = epoll::wait(
+            self.read_epoll_fd,
+            match self.read_timeout {
+                Some(duration) => i32::try_from(duration.as_millis()).expect("duration too big for epoll"),
+                None => -1 // infinite
+            },
+            // event that was generated; but we don’t care
+            &mut vec![UNUSED; 1][..],
+        );
+        // Delete the listen fd from the epoll fd before reacting
+        // (otherwise it fails on the next read with `EPOLL_CTL_ADD`)
+        epoll::ctl(
+            self.read_epoll_fd,
+            epoll::ControlOptions::EPOLL_CTL_DEL,
+            self.read.as_raw_fd(),
+            UNUSED
+        )?;
+        match wait {
+            // timeout happened (0 events)
+            Ok(0) => Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "ucspi read timeout")),
+            // its ready for reading, we can read
+            Ok(_) => self.read.read(buf),
+            // error
+            err => err,
+        }
+    }
+}
+
+/// Just proxy through the `Write` of the write fd.
+impl Write for UcspiClient {
+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+        self.write.write(buf)
+    }
+    fn flush(&mut self) -> std::io::Result<()> {
+        self.write.flush()
+    }
+}
+
+/// Connect to IMAP account and listen for new mails on the INBOX.
+fn main() {
+    exec_helpers::no_args("imap-idle");
+
+    // TODO: use arglib_netencode
+    let username = std::env::var("IMAP_USERNAME").expect("username");
+    let password = std::env::var("IMAP_PASSWORD").expect("password");
+
+    let net = unsafe {
+        UcspiClient::new_from_6_and_7().expect("no ucspi client for you")
+    };
+    let client = imap::Client::new(net);
+    let mut session = client.login(username, password).map_err(|(err, _)| err).expect("unable to login");
+    eprintln!("{:#?}", session);
+    let list = session.list(None, Some("*"));
+    eprintln!("{:#?}", list);
+    let mailbox = session.examine("INBOX");
+    eprintln!("{:#?}", mailbox);
+    fn now() -> String {
+        String::from_utf8_lossy(&std::process::Command::new("date").output().unwrap().stdout).trim_right().to_string()
+    }
+    loop {
+        eprintln!("{}: idling on INBOX", now());
+        let mut handle = session.idle().expect("cannot idle on INBOX");
+        let () = handle.wait_keepalive().expect("waiting on idle failed");
+        eprintln!("{}: The mailbox has changed!", now());
+    }
+}
diff --git a/users/Profpatsch/rust-crates.nix b/users/Profpatsch/rust-crates.nix
index e6a191f70f4b..7004a699c4ca 100644
--- a/users/Profpatsch/rust-crates.nix
+++ b/users/Profpatsch/rust-crates.nix
@@ -119,4 +119,93 @@ rec {
     features = [ "std" "alloc" ];
   };
 
+  base64 = pkgs.buildRustCrate {
+    pname = "base64";
+    version = "0.13.0";
+    crateName = "base64";
+    edition = "2018";
+    sha256 = "0i0jk5sgq37kc4c90d1g7dp7zvphbg0dbqc1ajnn0vffjxblgamg";
+    features = [ "alloc" "std" ];
+  };
+
+  bufstream = pkgs.buildRustCrate {
+    pname = "bufstream";
+    version = "0.1.4";
+    crateName = "bufstream";
+    sha256 = "10rqm7jly5jjx7wcc19q6q4m2zsrw3l2v3m1054wnbwvdh42xxf1";
+  };
+
+  autocfg = pkgs.buildRustCrate {
+    pname = "autocfg";
+    version = "1.0.1";
+    crateName = "autocfg";
+    sha256 = "1lsjz23jdcchcqbsmlzd4iksg3hc8bdvy677jy0938i2gp24frw1";
+  };
+
+  num-traits = pkgs.buildRustCrate {
+    pname = "num-traits";
+    version = "0.2.14";
+    crateName = "num-traits";
+    buildDependencies = [ autocfg ];
+    sha256 = "09ac9dcp6cr57vjzyiy213y7312jqcy84mkamp99zr40qd1gwnyk";
+  };
+
+  num-integer = pkgs.buildRustCrate {
+    pname = "num-integer";
+    version = "0.1.44";
+    crateName = "num-integer";
+    dependencies = [ num-traits ];
+    buildDependencies = [ autocfg ];
+    sha256 = "1gdbnfgnivp90h644wmqj4a20yfmdga2xxxacx53pjbcazvfvajc";
+  };
+
+  chrono = pkgs.buildRustCrate {
+    pname = "chrono";
+    version = "0.4.19";
+    crateName = "chrono";
+    dependencies = [ num-traits num-integer ];
+    features = [ "alloc" "std" ];
+    sha256 = "0cjf5dnfbk99607vz6n5r6bhwykcypq5psihvk845sxrhnzadsar";
+  };
+
+  imap-proto = pkgs.buildRustCrate {
+    pname = "imap-proto";
+    version = "0.10.2";
+    crateName = "imap-proto";
+    dependencies = [ nom ];
+    sha256 = "1bf5r4d0z7c8wxrvr7kjy26500wr7cd4sxz49ix3b3yzc6ayyqv1";
+  };
+
+  lazy_static = pkgs.buildRustCrate {
+    pname = "lazy_static";
+    version = "1.4.0";
+    crateName = "lazy_static";
+    sha256 = "13h6sdghdcy7vcqsm2gasfw3qg7ssa0fl3sw7lq6pdkbk52wbyfr";
+  };
+
+  imap = pkgs.buildRustCrate {
+    pname = "imap";
+    version = "2.4.0";
+    crateName = "imap";
+    edition = "2018";
+    dependencies = [
+      base64
+      bufstream
+      chrono
+      imap-proto
+      lazy_static
+      nom
+      regex
+    ];
+    sha256 = "1nj6x45qnid98nv637623rrh7imcxk0kad89ry8j5dkkgccvjyc0";
+  };
+
+  epoll = pkgs.buildRustCrate {
+    pname = "epoll";
+    version = "4.3.1";
+    crateName = "epoll";
+    dependencies = [ bitflags libc ];
+    sha256 = "0dgmgdmrfbjkpxn1w3xmmwsm2a623a9qdwn90s8yl78n4a36kbh9";
+  };
+
 }