Commit 744a4db0 authored by MrMan's avatar MrMan

Tons of working getting user creation & login to work

parent ab6070b2
Pipeline #47875312 failed with stage
in 2 minutes and 52 seconds
......@@ -1263,6 +1263,7 @@ version = "0.0.1"
dependencies = [
"actix-web 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)",
"askama 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
......
......@@ -31,4 +31,5 @@ tempfile = "3"
mail = { version = "0.6.7", features = ["smtp"] }
rand = "0.5.0"
new-tokio-smtp = "0.8.1"
reqwest = "0.9.9"
\ No newline at end of file
reqwest = "0.9.9"
base64 = "0.10.1"
......@@ -89,6 +89,7 @@ container:
docker stop $(CONTAINER_NAME) || true
docker rm $(CONTAINER_NAME) || true
docker run \
--detach \
-p 2525:25 \
-p 5875:587 \
-e CONFIG_PATH=/usr/src/postmgr/infra/conf/${ENV}.toml \
......
......@@ -2,19 +2,18 @@
`postmgr` is a tool for managing instsances of [Postfix][postfix]. `postmgr` aims to provide:
- Mostly transparent daemonization of Postfix
- Easier management, administration, and configuration for Postfix
- A web interface baked in
- An easy to use web interface for mailbox and other management tasks
# Quick Start #
The quickest way to get started with `postmgr` is to use one of our prebuilt containers. If you want to build your own container or other deployment method with `postmgr`, you can obtain the `postmgr` binary, and as long as you tell `postmgr` where it can find the other binaries it needs, it will manage them.
**`postmgr` is meant to be run in a properly sandboxed/isolated (i.e. containerized) environment** -- it attempts to overwrite files in directories like `/etc/postfix` due to `postfix`'s shoddy support for determining `config_directory` ("-c" and/or `MAIL_CONFIG` do not completely ensure alternate configuration directories) consistently for *all* related processes.
## Containerized ##
The quickest way to get started with `postmgr` is to use one of our prebuilt containers. If you want to build your own container or other deployment method with `postmgr`, you can obtain the `postmgr` binary, and as long as you tell `postmgr` where it can find the other binaries it needs, it will manage them.
[There is one primary container][gitlab-docker-repo] that is maintained by this project.
The [`postmgr` container][gitlab-docker-repo] is the primary container of this project.
You **should** be able to download the single container, connect it to your container orchestration process of choice, and have a fully functional, relatively secure, manageable Postfix installation. If this is ever not-true, [file a bug][gitlab-issues].
You **should** be able to download the single container, connect it to your container orchestration process of choice, and have a fully functional, mostly secure, manageable Postfix installation. If this is ever not-true, [file a bug][gitlab-issues].
## Binary ##
......
......@@ -8,14 +8,14 @@ newaliases_bin_path = "/usr/bin/newaliases"
sendmail_bin_path = "/usr/bin/sendmail"
mailq_bin_path = "/usr/bin/mailq"
# All on-disk data is stored at or below this folder
data_dir = "./infra/runtime/data/postfix"
# All on-disk config is stored at or below this folder
config_dir = "./infra/runtime/config/postfix"
# All generated configuration will be copied here
config_output_dir = "./infra/runtime/config/postfix/generated"
# NOTE: if you change this value, you must change it when launching the container as well
# Postfix (when launched as a subprocess) does *not* keep consistent ENV in subprocesses
# this means that postconf will report /etc/postfix (the default location) despite setting "-c" flag and/or MAIL_CONFIG
config_output_dir = "/etc/postfix"
# The server's internet hostname (in Postfix, `myhostname`)
internet_hostname = "mail.localhost.localdomain"
......@@ -30,7 +30,7 @@ default_domain_name = "$mydomain"
inbound_domain_names = ["$myhostname", "localhost.$mydomain", "localhost", "$mydomain"]
## Which subnets are allowed to relay mail through this server (in Postfix, `mynetworks`)
allowed_relay_cidrs = ["127.0.0.0/8"]
allowed_relay_cidrs = ["127.0.0.0/8", "172.17.0.0/16"]
## Which destinations to relay mail to (in Postfix, `relay_domains`)
allowed_relay_destinations = ["$mydestination"]
......
......@@ -8,9 +8,6 @@ newaliases_bin_path = "/usr/bin/newaliases"
sendmail_bin_path = "/usr/sbin/sendmail" # Default for debian stretch installation
mailq_bin_path = "/usr/bin/mailq"
# All on-disk data is stored at or below this folder
data_dir = "./infra/runtime/data/postfix"
# All on-disk config is stored at or below this folder
config_dir = "./infra/runtime/config/postfix"
......@@ -30,7 +27,7 @@ default_domain_name = "$mydomain"
inbound_domain_names = ["$myhostname", "localhost.$mydomain", "localhost", "$mydomain"]
## Which subnets are allowed to relay mail through this server (in Postfix, `mynetworks`)
allowed_relay_cidrs = ["127.0.0.0/8"]
allowed_relay_cidrs = ["127.0.0.0/8", "172.17.0.0/16"]
## Which destinations to relay mail to (in Postfix, `relay_domains`)
allowed_relay_destinations = ["$mydestination"]
......
......@@ -191,11 +191,11 @@ impl SupportsVAliasLookup for DB {
pub trait SupportsVMailboxLookup {
// Write out the file that postfix will use as configuration for performing virtual mailbox lookups
fn write_vmailbox_lookup_config_file(&self, output_path: &Path, mailbox_base_dir: String) -> Result<(), Error>;
fn write_vmailbox_lookup_config_file(&self, output_path: &Path, mailbox_base_dir: &str) -> Result<(), Error>;
}
impl SupportsVMailboxLookup for DB {
fn write_vmailbox_lookup_config_file(&self, output_path: &Path, mailbox_base_dir: String) -> Result<(), Error> {
fn write_vmailbox_lookup_config_file(&self, output_path: &Path, mailbox_base_dir: &str) -> Result<(), Error> {
match self {
DB::SQLite(db) => db.write_vmailbox_lookup_config_file(output_path, mailbox_base_dir),
_ => Err(Error::NotSupported)
......
......@@ -248,7 +248,7 @@ struct SQLiteVirtualMailboxLookupConfigTemplate<'a> {
}
impl SupportsVMailboxLookup for SQLiteDB {
fn write_vmailbox_lookup_config_file(&self, output_path: &Path, mailbox_base_dir: String) -> Result<(), Error> {
fn write_vmailbox_lookup_config_file(&self, output_path: &Path, mailbox_base_dir: &str) -> Result<(), Error> {
let filename = output_path.file_name()
.unwrap_or(OsStr::new("<filename unspecified>"))
.to_str()
......@@ -259,7 +259,7 @@ impl SupportsVMailboxLookup for SQLiteDB {
filename,
generation_time: Local::now().to_string(),
abs_db_path: self.make_absolute_db_path()?,
mailbox_base_dir
mailbox_base_dir: String::from(mailbox_base_dir)
};
// Ensure the output directory we're supposed to be writing to exists
......
......@@ -6,7 +6,7 @@ use rusqlite::Row;
use rusqlite::types::ToSql;
use super::super::*;
const COLUMNS: &'static [&'static str] = &["uuid", "email", "password", "quota"];
const COLUMNS: &'static [&'static str] = &["uuid", "email", "password", "quota_gb"];
impl HasSQLTable<SQLiteDB> for ModelWithUUID<MailboxUser> {
fn sql_tbl_name() -> &'static str { "mailbox_users" }
......@@ -18,8 +18,8 @@ impl<'a, 'stmt> DBEntity<SQLiteDB, Row<'a, 'stmt>> for ModelWithUUID<MailboxUser
let uuid = row.get_checked("uuid")?;
let email = row.get_checked("email")?;
let password = row.get_checked("password")?;
let quota = row.get_checked("quota")?;
let model = MailboxUser { email, password, quota };
let quota_gb = row.get_checked("quota_gb")?;
let model = MailboxUser { email, password, quota_gb };
Ok(ModelWithUUID { uuid, model })
}
......@@ -27,7 +27,12 @@ impl<'a, 'stmt> DBEntity<SQLiteDB, Row<'a, 'stmt>> for ModelWithUUID<MailboxUser
let c: &SQLiteConnection = get_sqlite_connection(db)?;
let mut stmt = gen_insert_stmt(c, Self::sql_tbl_name(), Self::sql_tbl_columns())?;
let inserted_row_count = stmt.execute(&[&self.uuid as &ToSql , &self.model.email, &self.model.password, &self.model.quota])?;
let inserted_row_count = stmt.execute(&[
&self.uuid as &ToSql,
&self.model.email,
&self.model.password,
&self.model.quota_gb
])?;
if inserted_row_count == 1 { return Ok(self); }
Err(Error::DBError(String::from("Statement executed but unaffected row count returned: insertion (likely) failed")))
......
......@@ -14,8 +14,9 @@ CREATE TABLE mailbox_users (
uuid TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
quota INTEGER DEFAULT 0,
CONSTRAINT quota_gt_zero CHECK (quota >= 0)
quota_gb INTEGER DEFAULT 0,
CONSTRAINT quota_gt_zero CHECK (quota_gb >= 0)
);
"
}),
......
......@@ -7,7 +7,7 @@ use std::io::Error as IOError;
use std::process::{Command, Child as ChildProcess};
use askama::Error as AskamaError;
use rusqlite::Error as SQLiteError;
use std::sync::mpsc::Sender;
use std::sync::mpsc::SyncSender;
use std::any::Any;
////////////
......@@ -34,7 +34,7 @@ pub trait Component {
pub trait HasReqRespCommandBus where Self: Component {
type Msg;
fn get_cmd_bus_tx(&self) -> Result<Sender<ReqResp<Self::Msg>>, Error>;
fn get_cmd_bus_tx(&self) -> Result<SyncSender<ReqResp<Self::Msg>>, Error>;
}
pub trait ChildProcessManager where Self: Component {
......@@ -47,13 +47,14 @@ pub trait ChildProcessManager where Self: Component {
// Commands used to trigger lifecycle for components
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum ComponentCmd {
Shutdown
Shutdown,
}
// For request/response communication between components
#[derive(Debug)]
pub struct ReqResp<M> {
msg: M,
pub response_chan: Option<Sender<Box<Any + Send>>>
pub response_chan: Option<SyncSender<Box<Any + Send>>>
}
pub trait Configurable<C>: Component {
......
use std::fs::{create_dir_all, File, copy};
use std::io::Write;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio, Child as ChildProcess};
use std::sync::mpsc::{channel, Sender, Receiver};
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
use askama::Template;
use chrono::prelude::Local;
......@@ -20,7 +20,7 @@ pub struct Postfix {
cfg: PostfixCfg,
cmd_bus_rx: Receiver<ReqResp<PostfixCmd>>,
cmd_bus_tx: Sender<ReqResp<PostfixCmd>>,
cmd_bus_tx: SyncSender<ReqResp<PostfixCmd>>,
pid: Option<u32>,
command: Option<Command>,
......@@ -43,7 +43,7 @@ impl Postfix {
db.connect()?;
// Create command bus channels
let (cmd_bus_tx, cmd_bus_rx) = channel();
let (cmd_bus_tx, cmd_bus_rx) = sync_channel(0);
Ok(Postfix {
cfg,
......@@ -66,45 +66,40 @@ impl Component for Postfix {
self.spawn_child_process()?;
// Control loop, this never exits
loop {
let cmd = self.cmd_bus_rx.recv();
// Ensure command is present
match cmd {
Ok(req) => {
// Match the command messages to functionality
match req.msg {
PostfixCmd::Lifecycle(ComponentCmd::Shutdown) => {
debug!("received shutdown command to component");
self.stop().expect("graceful shutdown of postfix failed");
break;
}
for req in self.cmd_bus_rx.iter() {
match req.msg {
PostfixCmd::Lifecycle(ComponentCmd::Shutdown) => {
debug!("received shutdown command to component");
break;
},
PostfixCmd::CreateUser(new_user) => {
debug!("creating new use {:?}", &new_user);
// Attempt to add the new user
let result = self.db.add_mailbox_user(new_user);
PostfixCmd::CreateUser(new_user) => {
debug!("creating new mailbox user with email [{}]", &new_user.email);
// Attempt to add the new user
let result = self.db.add_mailbox_user(new_user);
// respond if a repsonse channel was provided
match result {
Ok(new_user) => {
debug!("successfully created new user with email: [{}]", &new_user.model.email);
if let Some(chan) = req.response_chan {
let _ = chan.send(Box::new(result.unwrap()));
};
}
chan.send(Box::new(new_user)).unwrap();
}
},
Err(err) => {
error!("error creating new user: {:?}", err);
if let Some(chan) = req.response_chan {
chan.send(Box::new(err)).unwrap();
}
},
}
},
Err(e) => {
warn!("received invalid command: {}", e);
break;
}
}
}
// Stop the child process since we've left the control loop
self.stop()?;
Ok(())
}
......@@ -148,7 +143,7 @@ impl Component for Postfix {
impl HasReqRespCommandBus for Postfix {
type Msg = PostfixCmd;
fn get_cmd_bus_tx(&self) -> Result<Sender<ReqResp<Self::Msg>>, Error> {
fn get_cmd_bus_tx(&self) -> Result<SyncSender<ReqResp<Self::Msg>>, Error> {
Ok(self.cmd_bus_tx.clone())
}
}
......@@ -157,21 +152,33 @@ impl ChildProcessManager for Postfix {
fn spawn_child_process(&mut self) -> Result<&Option<ChildProcess>, Error> {
debug!("spawning postfix process...");
// Build arguments
let config_output_dir = make_absolute_path(PathBuf::from(&self.cfg.config_output_dir))?;
let args = &[
"start",
"-c",
&self.cfg.config_output_dir,
config_output_dir.as_str(),
"-v",
"start",
] ;
self.command = Some(Command::new(&self.cfg.bin_path));
debug!("command: [{} {}]!", &self.cfg.bin_path, args.join(" "));
// Save the command
// self.command = child.clone();
// Build & save command
let mut cmd = Command::new(&self.cfg.bin_path);
// Config directory for daemons must be specified with ENV
// http://www.postfix.org/postconf.5.html#config_directory
cmd.env("MAIL_CONFIG", &config_output_dir);
cmd.args(args);
cmd.stdin(Stdio::null());
cmd.stdout(Stdio::inherit());
debug!("command: [{:#?}]!", cmd);
self.command = Some(cmd);
// Actually run the command
// NOTE: unfortunately we have to build it all over again since Command can't be cloned
let child = Command::new(&self.cfg.bin_path)
.args(args)
.env("MAIL_CONFIG", &config_output_dir)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
//.stdout(Stdio::inherit())
.spawn()?;
self.pid = Some(child.id());
self.process = Some(child);
......@@ -275,7 +282,6 @@ impl FileConfigurable<PostfixCfg> for Postfix {
let cyrus_cfg_dir_path = Path::new(&cyrus_cfg_dir_path_str).join("cyrus_sasl");
let sasl_cfg_file_path = String::from(self.db.cyrus_config_file_path(&cyrus_cfg_dir_path)?);
// Generate main.cf
let main_cf = PostfixMainCFTemplate {
filename: "main.cf",
generation_time: Local::now().to_string(),
......@@ -306,7 +312,7 @@ impl FileConfigurable<PostfixCfg> for Postfix {
milters: &self.cfg.milters,
virtual_mailbox_domains: &self.cfg.virtual_mailbox_domains,
mailbox_base_dir: &self.cfg.mailbox_base_dir()?,
mailbox_base_dir: &self.cfg.mail_spool_directory,
valias_lookup_cfg_file_path,
vmailbox_lookup_cfg_file_path,
......@@ -325,7 +331,7 @@ impl FileConfigurable<PostfixCfg> for Postfix {
// Generate DB-specific query instruction file for vmailbox and valias mappings
debug!("writing db-specific valias and vmailbox lookup files...");
self.db.write_valias_lookup_config_file(valias_lookup_cfg_path.as_path())?;
self.db.write_vmailbox_lookup_config_file(vmailbox_lookup_cfg_path.as_path(), self.cfg.mailbox_base_dir()?)?;
self.db.write_vmailbox_lookup_config_file(vmailbox_lookup_cfg_path.as_path(), &self.cfg.mail_spool_directory)?;
// Generate DB-specific configuration for Cyrus
self.db.write_cyrus_smtpd_config_file(&cyrus_cfg_dir_path)?;
......@@ -342,7 +348,7 @@ impl FileConfigurable<PostfixCfg> for Postfix {
}
// Ensure the mail data base directory (which whill hold all the data) exists
let mailbox_base_dir = &self.cfg.mailbox_base_dir()?;
let mailbox_base_dir = &self.cfg.mail_spool_directory;
let mailbox_base_dir_path = Path::new(&mailbox_base_dir);
if !mailbox_base_dir_path.exists() { create_dir_all(&mailbox_base_dir_path)?; }
......
use actix_web::error::*;
use actix_web::http::Method;
use actix_web::{App, HttpRequest, HttpResponse, Json, Result, Path, FromRequest};
use actix_web::{App, HttpRequest, HttpResponse, Json, Result, Path, FromRequest, State};
use components::ReqResp;
use components::postfix::PostfixCmd;
use components::web_admin::AppState;
use models::user::MailboxUser;
use std::sync::mpsc::{channel, Sender};
use futures::future::Future;
use models::ModelWithUUID;
use std::sync::mpsc::{sync_channel, SyncSender};
use std::time::Duration;
use serde_json::json;
const DEFAULT_OPERATION_TIMEOUT: Duration = Duration::from_secs(1);
const DEFAULT_OPERATION_TIMEOUT: Duration = Duration::from_secs(5);
pub fn app(
postfix_tx: Sender<ReqResp<PostfixCmd>>
postfix_tx: SyncSender<ReqResp<PostfixCmd>>
) -> App<AppState> {
let state = AppState {
postfix_tx: Some(postfix_tx)
......@@ -21,9 +21,9 @@ pub fn app(
App::with_state(state)
.prefix("/api/v1")
.resource("/", |r| r.f(api_index))
.resource("/components/{name}/status", |r| r.method(Method::GET).with(component_status))
.resource("/users", |r| r.method(Method::POST).f(add_user))
.resource("", |r| r.f(api_index))
.resource("components/{name}/status", |r| r.method(Method::GET).with(component_status))
.resource("users", |r| r.method(Method::POST).with(add_user))
}
......@@ -55,20 +55,28 @@ fn component_status(info: Path<String>) -> Result<Json<ComponentStatus>> {
}
/// Add a user
fn add_user(req: &HttpRequest<AppState>) -> Result<HttpResponse> {
// Send a message to the postfix component to create the new user
let new_user = Json::<MailboxUser>::extract(req).wait()?.into_inner();
let (tx, rx) = channel();
fn add_user(state: State<AppState>, new_user: Json<MailboxUser>) -> Result<HttpResponse> {
// Create request to send to postfix component
let (tx, rx) = sync_channel(0);
let postfix_req = ReqResp::<PostfixCmd> {
msg: PostfixCmd::CreateUser(new_user),
msg: PostfixCmd::CreateUser(new_user.into_inner()),
response_chan: Some(tx),
};
// Ensure postfix tx is present
if state.postfix_tx.is_none() { return Err(ErrorInternalServerError("No postfix component present")); }
// Send the request to the postfix component
state.postfix_tx
.as_ref()
.unwrap()
.send(postfix_req)
.map_err(|_| ErrorInternalServerError("Failed contacting postfix component"))?;
// Wait for the response from postfix, which should contain the mailbox user
rx
.recv_timeout(DEFAULT_OPERATION_TIMEOUT)
.map(|b| match b.downcast::<MailboxUser>() {
.map(|b| match b.downcast::<ModelWithUUID<MailboxUser>>() {
Ok(u) => HttpResponse::Created().json(json!({
"status": "success",
......@@ -81,5 +89,5 @@ fn add_user(req: &HttpRequest<AppState>) -> Result<HttpResponse> {
})).into(),
})
.map_err(|_| ErrorInternalServerError("Unexpected failure"))
.map_err(|_| ErrorInternalServerError("No response from Postfix component"))
}
......@@ -7,14 +7,14 @@ use components::web_admin::api::v1::{app as api_v1_app};
use components::web_admin::root::{app as root_app};
use components::{Component, ReqResp, ComponentCmd, Error};
use config::WebAdminCfg;
use std::sync::mpsc::Sender;
use std::sync::mpsc::SyncSender;
#[allow(dead_code)]
pub struct WebAdmin {
cfg: WebAdminCfg,
// For communicating with a postfix component
postfix_tx_chan: Sender<ReqResp<PostfixCmd>>,
postfix_tx_chan: SyncSender<ReqResp<PostfixCmd>>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
......@@ -23,11 +23,11 @@ pub enum WebAdminCmd {
}
pub struct AppState {
postfix_tx: Option<Sender<ReqResp<PostfixCmd>>>
postfix_tx: Option<SyncSender<ReqResp<PostfixCmd>>>
}
impl WebAdmin {
pub fn new(cfg: WebAdminCfg, postfix_tx_chan: Sender<ReqResp<PostfixCmd>>) -> Result<WebAdmin, Error> {
pub fn new(cfg: WebAdminCfg, postfix_tx_chan: SyncSender<ReqResp<PostfixCmd>>) -> Result<WebAdmin, Error> {
Ok(WebAdmin { cfg, postfix_tx_chan })
}
}
......
......@@ -4,10 +4,7 @@ use std::fs::File;
use std::io::{Read, Error as IOError};
use std::net::Ipv4Addr;
use toml::de::Error as TomlError;
use std::path::Path;
use toml;
use make_absolute_path;
use components::Error;
////////////
// Errors //
......@@ -122,7 +119,6 @@ pub struct PostfixCfg {
pub config_dir: String,
pub config_output_dir: String,
pub data_dir: String,
postmaster_user: String,
......@@ -158,12 +154,6 @@ pub struct PostfixCfg {
pub auth: AuthCfg
}
impl PostfixCfg {
pub fn mailbox_base_dir(&self) -> Result<String, Error> {
make_absolute_path(Path::new(&self.data_dir).join("mail").to_path_buf())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct WebAdminCfg {
pub host: Ipv4Addr,
......
......@@ -48,10 +48,8 @@ pub fn generate_app_config(cfg_path_override: Option<&str>) -> AppCfg {
process::exit(1);
}
// TODO: Override with ENV
info!("app config loaded from file: [{}]", cfg_path);
return maybe_app_cfg.unwrap();
maybe_app_cfg.unwrap()
}
// Setup/Initialize fern based logging
......@@ -123,9 +121,9 @@ pub fn parse_arguments<'a>() -> ArgMatches<'a> {
.long("password")
.value_name("PASSWORD")
.takes_value(true))
.arg(Arg::with_name("mailbox_quota")
.arg(Arg::with_name("mailbox_quota_gb")
.short("q")
.long("mailbox-quota")
.long("mailbox-quota-gb")
.value_name("NUM_BYTES")
.takes_value(true)))
// `postmgr postfix mailbox-user-remove ...`
......
......@@ -93,12 +93,12 @@ fn main() {
// TODO: Check email address validity
let email = postfix_add_mailbox_user_m.value_of("email").expect("email address missing");
let password = postfix_add_mailbox_user_m.value_of("password").expect("password missing");
let quota = postfix_add_mailbox_user_m.value_of("quota").unwrap_or("0").parse().expect("invalid quota");
let quota_gb = postfix_add_mailbox_user_m.value_of("mailbox_quota_gb").unwrap_or("0").parse().expect("invalid quota");
let new_user = MailboxUser{
email: String::from(email),
password: String::from(password),
quota
quota_gb
};
let _ = postfix.db.connect().expect("failed to connect to db");
......
......@@ -2,7 +2,7 @@
pub struct MailboxUser {
pub email: String,
pub password: String,
pub quota: i32
pub quota_gb: i32
}
impl MailboxUser {
......@@ -10,7 +10,7 @@ impl MailboxUser {
MailboxUser {
email,
password,
quota: 0
quota_gb: 0
}
}
}
......@@ -740,12 +740,13 @@ smtpd_tls_session_cache_timeout = {{ tls.session_cache_timeout_seconds }}s # se
disable_vrfy_command = yes
## SASL settings
smtpd_sasl_type = cyrus
smtpd_sasl_path = {{ sasl_cfg_file_path }}
smtpd_sasl_auth_enable = yes
#smtpd_sasl_security_options = noanonymous # <--- ISSUE IS HERE, noanonymous stops connection, probably good though