Commit e594ffdc authored by MrMan's avatar MrMan

Switch to dovecot

parent 7cfbad9d
......@@ -67,14 +67,14 @@ submission_qmgr_group_name = "submission"
postmaster_user = "root"
[postfix.auth]
backend = "Cyrus" # only "Cyrus" allowed
backend = "Dovecot" # only "Dovecot" allowed
[postfix.db]
backend = "SQLite" # "SQLite" or "Postgres"
[postfix.db.sqlite]
in_memory = false
path = "./infra/runtime/data/db.sqlite"
path = "/var/mail/db.sqlite"
#[postfix.db.postgres]
#host = ""
......
......@@ -64,14 +64,14 @@ submission_qmgr_group_name = "submission"
postmaster_user = "root"
[postfix.auth]
backend = "Cyrus" # only "Cyrus" allowed
backend = "Dovecot" # only "Dovecot" allowed
[postfix.db]
backend = "SQLite" # "SQLite" or "Postgres"
[postfix.db.sqlite]
in_memory = false
path = "./infra/runtime/data/db.sqlite"
path = "/var/mail/db.sqlite"
#[postfix.db.postgres]
#host = ""
......
......@@ -4,7 +4,8 @@ WORKDIR /usr/src/postmgr
COPY . .
# Install postfix
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install make postfix rsyslog telnet ca-certificates libsasl2-modules
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y ca-certificates install make postfix \
rsyslog telnet dovecot-imapd dovecot-pop3d
RUN ln -s /usr/sbin/postfix /usr/bin/postfix
# Add submission group for postfix to use
......
......@@ -77,7 +77,7 @@ pub fn make_admin_db(db_cfg: DBCfg) -> Result<Box<AdminDB>, Error> {
pub trait MailboxDB
where
Self: Connectable + SupportsVAliasLookup + SupportsVMailboxLookup + SupportsCyrusAuth
Self: Connectable + SupportsVAliasLookup + SupportsVMailboxLookup + SupportsDovecotAuth
{
// Initialize the database connection (if necessary), idempotently performing
// all early/pre-work data changes and migrations necessary for a Postfix data holding db
......@@ -204,38 +204,55 @@ impl SupportsVMailboxLookup for DB {
}
// TODO: this can probably be generalized in the future to SupportsAuth<T> when Dovecot auth is added
pub trait SupportsCyrusAuth {
type Template;
pub trait SupportsDovecotAuth {
type ConfigTemplate;
type SQLConfigTemplate;
fn cyrus_config_file_name(&self) -> Result<&'static str, Error>;
fn dovecot_sasl_path(&self) -> Result<&'static str, Error>;
fn cyrus_config_file_path(&self, config_dir: &Path) -> Result<String, Error>;
fn dovecot_config_dir(&self) -> Result<&'static str, Error>;
// Write out the config file that cyrus can use
fn write_cyrus_smtpd_config_file(&self, config_dir: &Path) -> Result<(), Error>;
fn dovecot_config_filename(&self) -> Result<&'static str, Error>;
fn dovecot_sql_config_filename(&self) -> Result<&'static str, Error>;
fn write_dovecot_config_files(&self) -> Result<(), Error>;
}
impl SupportsCyrusAuth for DB {
type Template = Box<Any>;
impl SupportsDovecotAuth for DB {
type ConfigTemplate = Box<Any>;
type SQLConfigTemplate = Box<Any>;
fn dovecot_sasl_path(&self) -> Result<&'static str, Error> {
match self {
DB::SQLite(db) => db.dovecot_sasl_path(),
_ => Err(Error::NotSupported)
}
}
fn dovecot_config_dir(&self) -> Result<&'static str, Error> {
match self {
DB::SQLite(db) => db.dovecot_config_dir(),
_ => Err(Error::NotSupported)
}
}
fn cyrus_config_file_name(&self) -> Result<&'static str, Error> {
fn dovecot_config_filename(&self) -> Result<&'static str, Error> {
match self {
DB::SQLite(db) => db.cyrus_config_file_name(),
DB::SQLite(db) => db.dovecot_config_filename(),
_ => Err(Error::NotSupported)
}
}
fn cyrus_config_file_path(&self, config_dir: &Path) -> Result<String, Error> {
fn dovecot_sql_config_filename(&self) -> Result<&'static str, Error> {
match self {
DB::SQLite(db) => db.cyrus_config_file_path(config_dir),
DB::SQLite(db) => db.dovecot_config_filename(),
_ => Err(Error::NotSupported)
}
}
// Write out the config file that cyrus can use
fn write_cyrus_smtpd_config_file(&self, config_dir: &Path) -> Result<(), Error> {
fn write_dovecot_config_files(&self) -> Result<(), Error> {
match self {
DB::SQLite(db) => db.write_cyrus_smtpd_config_file(config_dir),
DB::SQLite(db) => db.write_dovecot_config_files(),
_ => Err(Error::NotSupported)
}
}
......
......@@ -14,7 +14,7 @@ use self::schema::{ADMIN_MIGRATIONS, MAILBOX_MIGRATIONS};
use std::ffi::OsStr;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::Path;
use std::path::{Path, PathBuf};
use make_absolute_path_from_str;
pub struct SQLiteDB {
......@@ -279,45 +279,84 @@ impl SupportsVMailboxLookup for SQLiteDB {
}
#[derive(Template)]
#[template(path = "config/cyrus_sasl/smtpd.conf.jinja")]
pub struct CyrusSMTPDCfgTemplate {
db_type: DBType,
#[template(path = "config/dovecot/dovecot.conf")]
pub struct DovecotConfTemplate {
filename: String,
generation_time: String,
sql_config_file_abs_path: String,
abs_db_path: String
}
impl SupportsCyrusAuth for SQLiteDB {
type Template = CyrusSMTPDCfgTemplate;
#[derive(Template)]
#[template(path = "config/dovecot/dovecot-sql.conf")]
pub struct DovecotSQLConfTemplate {
filename: String,
generation_time: String,
abs_db_path: String
}
fn cyrus_config_file_name(&self) -> Result<&'static str, Error> { Ok("smtpd.conf") }
impl SupportsDovecotAuth for SQLiteDB {
type ConfigTemplate = DovecotConfTemplate;
type SQLConfigTemplate = DovecotSQLConfTemplate;
fn cyrus_config_file_path(&self, config_dir: &Path) -> Result<String, Error> {
let output_path = config_dir.join(self.cyrus_config_file_name()?);
output_path.to_str().map(String::from).ok_or(Error::InvalidOrMissingConfig("failed to generate path for cyrus config"))
}
fn dovecot_sasl_path(&self) -> Result<&'static str, Error> { Ok("private/auth") }
fn dovecot_config_dir(&self) -> Result<&'static str, Error> { Ok("/etc/dovecot") }
fn dovecot_config_filename(&self) -> Result<&'static str, Error> { Ok("dovecot.conf") }
fn dovecot_sql_config_filename(&self) -> Result<&'static str, Error> { Ok("dovecot-sql.conf") }
fn write_dovecot_config_files(&self) -> Result<(), Error> {
// Create the dovecot config directory if it doesn't exist
let config_dir = PathBuf::from(self.dovecot_config_dir()?);
if !config_dir.exists() { create_dir_all(&config_dir)?; }
fn write_cyrus_smtpd_config_file(&self, config_dir: &Path) -> Result<(), Error> {
if config_dir.is_absolute() { warn!("starting path while writing cyrus smtpd_config_file is not absolute"); }
let config_filename = self.dovecot_config_filename()?;
let sql_config_filename = self.dovecot_sql_config_filename()?;
let filename = self.cyrus_config_file_name()?;
let template = CyrusSMTPDCfgTemplate {
db_type: DBType::SQLite,
filename: filename.to_string(),
// Build path & string to represent dovecot config file path
let config_file_abs_path = PathBuf::from(&config_dir).join(config_filename);
let config_file_abs_path_str = config_file_abs_path
.to_str()
.map(String::from)
.ok_or(Error::InvalidEnvironment("invalid dovecot config file path"))?;
// Build path & string to represent dovecto SQL config file path
let sql_config_file_abs_path = PathBuf::from(&config_dir).join(sql_config_filename);
let sql_config_file_abs_path_str = sql_config_file_abs_path
.to_str()
.map(String::from)
.ok_or(Error::InvalidEnvironment("invalid dovecot sql config file path"))?;
if !config_file_abs_path.is_absolute() { warn!("dovecot config file path is not absolute"); }
if !sql_config_file_abs_path.is_absolute() { warn!("dovecot sql config file path is not absolute"); }
// Build the template for the main config file
let template = DovecotConfTemplate {
filename: config_filename.to_string(),
generation_time: Local::now().to_string(),
sql_config_file_abs_path: sql_config_file_abs_path_str,
abs_db_path: self.make_absolute_db_path()?
};
// Ensure the config directory exists
if !config_dir.exists() { create_dir_all(&config_dir)?; }
// Write the main config file to disk
debug!("writing dovecot config file @ [{}]", config_file_abs_path_str);
let mut config_file = File::create(config_file_abs_path)?;
config_file.write_all(template.render()?.as_bytes())?;
// Generate the output path the config will be written to
let output_path_str = self.cyrus_config_file_path(config_dir)?;
// // Build the template for the SQLite config file
// let template = DovecotSQLConfTemplate {
// filename: config_filename.to_string(),
// generation_time: Local::now().to_string(),
// sql_config_file_abs_path: sql_config_file_abs_path,
// abs_db_path: self.make_absolute_db_path()?
// };
// // Write the main config file to disk
// debug!("writing dovecot config file @ [{}]", sql_config_file_abs_path);
// let mut config_file = File::create(sql_config_file_abs_path)?;
// config_file.write_all(template.render()?.as_bytes())?;
// Render the file contents, write them to disk
debug!("writing cyrus smtpd config file @ [{}]", output_path_str);
let mut query_config_file = File::create(output_path_str)?;
query_config_file.write_all(template.render()?.as_bytes())?;
Ok(())
}
......
......@@ -6,7 +6,7 @@ use rusqlite::Row;
use rusqlite::types::ToSql;
use super::super::*;
const COLUMNS: &'static [&'static str] = &["uuid", "username", "password", "quota_gb"];
const COLUMNS: &'static [&'static str] = &["uuid", "username", "domain", "password", "quota_gb"];
impl HasSQLTable<SQLiteDB> for ModelWithUUID<MailboxUser> {
fn sql_tbl_name() -> &'static str { "mailbox_users" }
......@@ -17,9 +17,10 @@ impl<'a, 'stmt> DBEntity<SQLiteDB, Row<'a, 'stmt>> for ModelWithUUID<MailboxUser
fn from_row(row: &Row) -> Result<Self, Error> {
let uuid = row.get_checked("uuid")?;
let username = row.get_checked("username")?;
let domain = row.get_checked("domain")?;
let password = row.get_checked("password")?;
let quota_gb = row.get_checked("quota_gb")?;
let model = MailboxUser { username, password, quota_gb };
let model = MailboxUser { username, domain, password, quota_gb };
Ok(ModelWithUUID { uuid, model })
}
......@@ -30,6 +31,7 @@ impl<'a, 'stmt> DBEntity<SQLiteDB, Row<'a, 'stmt>> for ModelWithUUID<MailboxUser
let inserted_row_count = stmt.execute(&[
&self.uuid as &ToSql,
&self.model.username,
&self.model.domain,
&self.model.password,
&self.model.quota_gb
])?;
......
......@@ -12,12 +12,15 @@ pub const MAILBOX_MIGRATIONS: &'static [Migration] = &[
sql: "
CREATE TABLE mailbox_users (
uuid TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
username TEXT NOT NULL,
domain TEXT NOT NULL,
password TEXT NOT NULL,
quota_gb INTEGER DEFAULT 0,
quota_bytes INTEGER DEFAULT 0,
CONSTRAINT quota_gt_zero CHECK (quota_gb >= 0)
CONSTRAINT quota_gt_zero CHECK (quota_bytes >= 0)
);
CREATE UNIQUE INDEX mailbox_users_username_domain_idx ON users(username, domain);
"
}),
];
......
......@@ -278,9 +278,7 @@ impl FileConfigurable<PostfixCfg> for Postfix {
let vmailbox_lookup_cfg_file_path = make_absolute_path(vmailbox_lookup_cfg_path.to_path_buf())?;
// Get the postfix config dir absolute path
let cyrus_cfg_dir_path_str = make_absolute_path_from_str(&self.cfg.config_dir)?;
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)?);
let sasl_cfg_file_path = String::from(self.db.dovecot_sasl_path()?);
let main_cf = PostfixMainCFTemplate {
filename: "main.cf",
......@@ -333,8 +331,8 @@ impl FileConfigurable<PostfixCfg> for Postfix {
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.mail_spool_directory)?;
// Generate DB-specific configuration for Cyrus
self.db.write_cyrus_smtpd_config_file(&cyrus_cfg_dir_path)?;
// Generate DB-specific configuration for Dovecot
self.db.write_dovecot_config_files()?;
// Generate the directories the files are going to go into
let config_dir = dir.unwrap_or(self.config_dir_path());
......
......@@ -197,7 +197,7 @@ pub enum DBType {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum AuthBackendType {
Cyrus
Dovecot
}
impl From<String> for DBType {
......
......@@ -115,6 +115,12 @@ pub fn parse_arguments<'a>() -> ArgMatches<'a> {
.long("username")
.value_name("USERNAME")
.takes_value(true))
.arg(Arg::with_name("domain")
.required(true)
.short("e")
.long("domain")
.value_name("DOMAIN")
.takes_value(true))
.arg(Arg::with_name("password")
.required(true)
.short("p")
......
......@@ -91,11 +91,13 @@ fn main() {
let mut postfix = Postfix::new(app_cfg.postfix.clone(), None).expect("postfix component init failed");
let username = postfix_add_mailbox_user_m.value_of("username").expect("username address missing");
let domain = postfix_add_mailbox_user_m.value_of("domain").expect("domain address missing");
let password = postfix_add_mailbox_user_m.value_of("password").expect("password missing");
let quota_gb = postfix_add_mailbox_user_m.value_of("mailbox_quota_gb").unwrap_or("0").parse().expect("invalid quota");
let new_user = MailboxUser{
username: String::from(username),
domain: String::from(domain),
password: String::from(password),
quota_gb
};
......
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MailboxUser {
pub username: String,
pub domain: String,
pub password: String,
pub quota_gb: i32
}
impl MailboxUser {
pub fn new(username: String, password: String) -> MailboxUser {
pub fn new(username: String, domain: String, password: String) -> MailboxUser {
MailboxUser {
username,
domain,
password,
quota_gb: 0
}
......
### WARNING: this file was auto-generated by postmgr, avoid editing it by hand ###
# Filename: {{ filename }}
# Generated at: {{ generation_time }}
# This file configures Cyrus SASL based on current postmgr configuration
{% match db_type %}
{% when DBType::SQLite %}
## SQLite settings
pwcheck_method: auxprop
auxprop_plugin: sql
mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM
sql_engine: sqlite
sql_database: {{ abs_db_path }}
sql_select: SELECT password FROM mailbox_users WHERE username = '%u'
{% when DBType::PostgreSQL %}
{% endmatch %}
### WARNING: this file was auto-generated by postmgr, avoid editing it by hand ###
# Filename: {{ filename }}
# Generated at: {{ generation_time }}
driver = sqlite
connect = {{ abs_db_path }}
password_query = SELECT username, domain, password \
FROM mailbox_users WHERE username = '%n' AND domain = '%d'
user_query = SELECT home, uid, gid FROM users WHERE username = '%n' AND domain = '%d'
# For using doveadm -A:
iterate_query = SELECT username, domain FROM users
\ No newline at end of file
passdb {
driver = sql
args = {{ sql_config_file_abs_path }}
mail_uid = vmail
mail_gid = vmail
}
......@@ -717,6 +717,8 @@ smtp_tls_security_level = encrypt
smtp_tls_CAfile = {{ tls.ca_file_path }}
smtp_tls_loglevel = {{ tls.log_level }}
smtp_tls_session_cache_database = {{ tls.session_cache_db }}
{% else %}
smtp_tls_security_level = may
{% endif %}
{% when None %}
{% endmatch %}
......@@ -733,6 +735,8 @@ smtpd_sasl_authenticated_header = {% if tls.add_received_header %} yes {% else %
smtpd_tls_auth_only = {% if tls.sasl_tls_only %} yes {% else %} no {% endif %}
smtpd_tls_session_cache_database = {{ tls.session_cache_db }}
smtpd_tls_session_cache_timeout = {{ tls.session_cache_timeout_seconds }}s # seconds
{% else %}
smtpd_tls_security_level = may
{% endif %}
{% when None %}
{% endmatch %}
......@@ -740,7 +744,7 @@ smtpd_tls_session_cache_timeout = {{ tls.session_cache_timeout_seconds }}s # se
disable_vrfy_command = yes
## SASL settings
smtpd_sasl_type = cyrus
smtpd_sasl_type = dovecot
smtpd_sasl_path = {{ sasl_cfg_file_path }}
smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain = $myhostname
......
......@@ -79,19 +79,6 @@ scache unix - - n - 1 scache
#
# ====================================================================
#
# The Cyrus deliver program has changed incompatibly, multiple times.
#
#old-cyrus unix - n n - - pipe
# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus unix - n n - - pipe
# user=_cyrus argv=/usr/bin/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# Dovecot version 1.1.3
#
dovecot unix - n n - 25 pipe
......
......@@ -3,7 +3,8 @@
# Generated at: {{ generation_time }}
# This file contains instructions postfix uses to query the backing SQLite database that holds user information
# TODO: fix this, aliases won't be the same table as users
dbpath = {{ abs_db_path }}
query = SELECT email FROM mailbox_users WHERE email = '%s'
query = SELECT username FROM mailbox_users WHERE username = '%s'
result_format = %d/%u/
......@@ -5,5 +5,5 @@
# This file contains instructions postfix uses to query the backing SQLite database that holds user information
dbpath = {{ abs_db_path }}
query = SELECT uuid FROM mailbox_users WHERE email = '%s'
query = SELECT uuid FROM mailbox_users WHERE username = '%s'
result_format = {{ mailbox_base_dir }}/%s
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment