use std::fs;
use std::io::Read;
use std::mem;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
use crate::support::diagnostic;
use crate::support::sysexits::*;
use crate::support::system_config::SystemConfig;
#[derive(StructOpt)]
#[structopt(max_term_width = 80)]
enum Command {
Remote(RemoteSubcommand),
Server(ServerSubcommand),
#[cfg(feature = "dev-tools")]
Dev(DevSubcommand),
}
#[cfg(feature = "dev-tools")]
#[derive(StructOpt)]
enum DevSubcommand {
ImapTest,
}
#[derive(StructOpt, Default)]
pub(super) struct ServerCommonOptions {
#[structopt(long, parse(from_os_str))]
root: Option<PathBuf>,
}
#[derive(StructOpt)]
enum ServerSubcommand {
Deliver(ServerDeliverSubcommand),
User(ServerUserSubcommand),
ServeImaps(ServerCommonOptions),
ServeLmtp(ServerCommonOptions),
}
impl ServerSubcommand {
fn common_options(&mut self) -> ServerCommonOptions {
match *self {
ServerSubcommand::Deliver(ref mut c) => mem::take(&mut c.common),
ServerSubcommand::User(ServerUserSubcommand::Add(ref mut c)) => {
mem::take(&mut c.common)
}
ServerSubcommand::ServeImaps(ref mut c) => mem::take(c),
ServerSubcommand::ServeLmtp(ref mut c) => mem::take(c),
}
}
}
#[derive(StructOpt)]
enum ServerUserSubcommand {
Add(ServerUserAddSubcommand),
}
#[derive(StructOpt)]
pub(super) struct ServerUserAddSubcommand {
#[structopt(flatten)]
pub(super) common: ServerCommonOptions,
#[structopt(long)]
pub(super) prompt_password: bool,
#[structopt(short, long)]
pub(super) uid: Option<nix::libc::uid_t>,
pub(super) name: String,
#[structopt(parse(from_os_str))]
pub(super) data_path: Option<PathBuf>,
}
#[derive(StructOpt)]
pub(super) struct ServerDeliverSubcommand {
#[structopt(flatten)]
pub(super) common: ServerCommonOptions,
#[structopt(short, long)]
pub(super) user: Option<String>,
#[structopt(short, long, default_value = "INBOX")]
pub(super) mailbox: String,
#[structopt(short, long)]
pub(super) create: bool,
#[structopt(parse(try_from_str), short, long, number_of_values(1))]
pub(super) flag: Vec<crate::account::model::Flag>,
#[structopt(long)]
pub(super) maildir_flags: bool,
#[structopt(parse(from_os_str), default_value = "-")]
pub(super) inputs: Vec<PathBuf>,
}
#[derive(StructOpt, Default)]
pub(super) struct RemoteCommonOptions {
#[structopt(long, short)]
pub(super) user: Option<String>,
#[structopt(long, short)]
pub(super) host: String,
#[structopt(long, short, default_value = "993")]
pub(super) port: u16,
#[structopt(long)]
pub(super) allow_insecure_tls_connections: bool,
#[structopt(long)]
pub(super) trace: bool,
}
#[derive(StructOpt)]
pub(super) enum RemoteSubcommand {
Test(RemoteCommonOptions),
Chpw(RemoteCommonOptions),
Config(RemoteConfigSubcommand),
}
impl RemoteSubcommand {
pub(super) fn common_options(&mut self) -> RemoteCommonOptions {
match *self {
RemoteSubcommand::Test(ref mut c)
| RemoteSubcommand::Chpw(ref mut c) => mem::take(c),
RemoteSubcommand::Config(ref mut c) => mem::take(&mut c.common),
}
}
}
#[derive(StructOpt)]
pub(super) struct RemoteConfigSubcommand {
#[structopt(flatten)]
pub(super) common: RemoteCommonOptions,
#[structopt(long)]
pub(super) internal_key_pattern: Option<String>,
#[structopt(long)]
pub(super) external_key_pattern: Option<String>,
}
pub fn main() {
let cmd = Command::from_clap(&match Command::clap().get_matches_safe() {
Ok(matches) => matches,
Err(
e @ clap::Error {
kind: clap::ErrorKind::HelpDisplayed,
..
},
)
| Err(
e @ clap::Error {
kind: clap::ErrorKind::VersionDisplayed,
..
},
) => {
println!("{}", e.message);
return;
}
Err(e) => {
eprintln!("{}", e.message);
EX_USAGE.exit()
}
});
match cmd {
#[cfg(feature = "dev-tools")]
Command::Dev(DevSubcommand::ImapTest) => super::imap_test::imap_test(),
Command::Remote(cmd) => super::remote::main(cmd),
Command::Server(cmd) => server(cmd),
}
}
fn server(mut cmd: ServerSubcommand) {
let common = cmd.common_options();
let root = common.root.unwrap_or_else(|| {
if Path::new("/etc/crymap/crymap.toml").is_file() {
"/etc/crymap".to_owned().into()
} else if Path::new("/usr/local/etc/crymap/crymap.toml").is_file() {
"/usr/local/etc/crymap".to_owned().into()
} else {
eprintln!(
"Neither /etc/crymap nor /usr/local/etc/crymap looks like\n\
the Crymap root; use --root=/path/to/crymap if your\n\
installation is elsewhere."
);
EX_CONFIG.exit()
}
});
let system_config_path = root.join("crymap.toml");
let mut system_config_toml = Vec::new();
if let Err(e) = fs::File::open(&system_config_path)
.and_then(|mut f| f.read_to_end(&mut system_config_toml))
{
eprintln!("Error reading '{}': {}", system_config_path.display(), e);
EX_CONFIG.exit();
}
let system_config: SystemConfig =
match toml::from_slice(&system_config_toml) {
Ok(config) => config,
Err(e) => {
eprintln!(
"Error in config file at '{}': {}",
system_config_path.display(),
e
);
EX_CONFIG.exit()
}
};
let stderr_is_tty = Ok(true) == nix::unistd::isatty(2);
if !stderr_is_tty
&& matches!(
cmd,
ServerSubcommand::Deliver(..)
| ServerSubcommand::ServeLmtp(..)
| ServerSubcommand::ServeImaps(..),
)
{
if let Err(exit) =
diagnostic::apply_diagnostics(&root, &system_config.diagnostic)
{
exit.exit();
}
}
let users_root = root.join("users");
if !users_root.is_dir() {
eprintln!("'{}' seems to be missing", users_root.display());
EX_CONFIG.exit();
}
let users_root = match users_root.canonicalize() {
Ok(ur) => ur,
Err(e) => {
eprintln!(
"Unable to canonicalise '{}': {}",
users_root.display(),
e
);
EX_IOERR.exit()
}
};
if stderr_is_tty {
crate::init_simple_log();
} else {
let log_config_file = root.join("logging.toml");
if log_config_file.is_file() {
log4rs::init_file(
log_config_file,
log4rs::config::Deserializers::new(),
)
.expect("Failed to initialise logging");
} else {
let formatter = syslog::Formatter3164 {
facility: syslog::Facility::LOG_MAIL,
hostname: None,
process: env!("CARGO_PKG_NAME").to_owned(),
pid: nix::unistd::getpid().as_raw(),
};
let logger =
syslog::unix(formatter).expect("Failed to connect to syslog");
log::set_boxed_logger(Box::new(syslog::BasicLogger::new(logger)))
.map(|_| log::set_max_level(log::LevelFilter::Info))
.expect("Failed to initialise logging");
}
}
match cmd {
ServerSubcommand::Deliver(cmd) => {
super::deliver::deliver(system_config, cmd, users_root);
}
ServerSubcommand::User(ServerUserSubcommand::Add(cmd)) => {
super::user::add(cmd, users_root);
}
ServerSubcommand::ServeImaps(_) => {
super::serve::imaps(system_config, root, users_root);
}
ServerSubcommand::ServeLmtp(_) => {
super::serve::lmtp(system_config, root, users_root);
}
}
}