taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

lib.rs (4804B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024, 2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::{fs::Permissions, io::ErrorKind, net::SocketAddr, os::unix::fs::PermissionsExt as _};
     18 
     19 use listenfd::ListenFd;
     20 use socket2::{Domain, Protocol, Socket, Type};
     21 use tokio::net::{TcpListener, UnixListener};
     22 use tracing::info;
     23 
     24 pub mod api;
     25 pub mod auth;
     26 pub mod config;
     27 pub mod constants;
     28 pub mod db;
     29 pub mod error;
     30 pub mod extract;
     31 pub mod notification;
     32 pub mod subject;
     33 #[cfg(test)]
     34 pub mod test;
     35 
     36 #[derive(Debug, Clone)]
     37 pub enum Serve {
     38     Tcp(SocketAddr),
     39     Unix {
     40         path: String,
     41         permission: Permissions,
     42     },
     43     Systemd,
     44 }
     45 
     46 impl Serve {
     47     /// Resolve listener from a config and environment
     48     fn resolve(&self) -> Result<Listener, std::io::Error> {
     49         match self {
     50             Serve::Tcp(socket_addr) => {
     51                 info!(target: "api", "Server listening on {socket_addr}");
     52                 let listener = bind_tcp(socket_addr)?;
     53                 listener.set_nonblocking(true)?;
     54                 Ok(Listener::Tcp(TcpListener::from_std(listener)?))
     55             }
     56             Serve::Unix { path, permission } => {
     57                 info!(target: "api",
     58                     "Server listening on unxis domain socket {path} {:o}",
     59                     permission.mode()
     60                 );
     61                 if let Err(e) = std::fs::remove_file(path) {
     62                     let kind = e.kind();
     63                     if kind != ErrorKind::NotFound {
     64                         return Err(e);
     65                     }
     66                 }
     67                 let listener = std::os::unix::net::UnixListener::bind(path)?;
     68                 std::fs::set_permissions(path, permission.clone())?;
     69                 listener.set_nonblocking(true)?;
     70                 Ok(Listener::Unix(UnixListener::from_std(listener)?))
     71             }
     72             Serve::Systemd => {
     73                 let mut listenfd = ListenFd::from_env();
     74                 if let Ok(Some(unix)) = listenfd.take_unix_listener(0) {
     75                     info!(target: "api",
     76                         "Server listening on activated unix socket {:?}",
     77                         unix.local_addr()?
     78                     );
     79                     unix.set_nonblocking(true)?;
     80                     Ok(Listener::Unix(UnixListener::from_std(unix)?))
     81                 } else if let Ok(Some(tcp)) = listenfd.take_tcp_listener(0) {
     82                     info!(target: "api",
     83                         "Server listening on activated TCP socket {:?}",
     84                         tcp.local_addr()?
     85                     );
     86                     tcp.set_nonblocking(true)?;
     87                     Ok(Listener::Tcp(TcpListener::from_std(tcp)?))
     88                 } else {
     89                     Err(std::io::Error::other("Missing systemd activated socket"))
     90                 }
     91             }
     92         }
     93     }
     94 }
     95 
     96 /// Listening backlog, the same value `std::net::TcpListener::bind` uses.
     97 const LISTEN_BACKLOG: i32 = 128;
     98 
     99 /// Bind a listening TCP socket to `addr`.
    100 ///
    101 /// Like `std::net::TcpListener::bind`, except that a socket for an IPv6
    102 /// address gets `IPV6_V6ONLY` cleared explicitly, so that the wildcard address
    103 /// `::` serves IPv4 clients as well (as v4-mapped addresses) instead of
    104 /// leaving that to the `net.ipv6.bindv6only` sysctl.  Without this a host whose
    105 /// resolver returns the IPv6 address of a name first -- which every dual-stack
    106 /// `localhost` does -- can only reach us if the operator happened to pick the
    107 /// matching address family in `BIND_TO`.  The C components have no such
    108 /// asymmetry: they resolve `BIND_TO` with `getaddrinfo()` and `AI_PASSIVE` and
    109 /// listen on every address it returns.
    110 fn bind_tcp(addr: &SocketAddr) -> Result<std::net::TcpListener, std::io::Error> {
    111     let domain = Domain::for_address(*addr);
    112     let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
    113     if domain == Domain::IPV6 {
    114         socket.set_only_v6(false)?;
    115     }
    116     // std sets this on unix too, keep the behaviour on restart
    117     socket.set_reuse_address(true)?;
    118     socket.bind(&(*addr).into())?;
    119     socket.listen(LISTEN_BACKLOG)?;
    120     Ok(socket.into())
    121 }
    122 
    123 enum Listener {
    124     Tcp(TcpListener),
    125     Unix(UnixListener),
    126 }