Compare commits
2 Commits
9447523c6b
...
b067a23bba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b067a23bba | ||
|
|
7fdcc9fb10 |
@@ -30,6 +30,23 @@ func main() {
|
|||||||
log.Fatalf("config: %v", err)
|
log.Fatalf("config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
policyListener, err := mailcloak.OpenPolicyListener(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("policy listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
socketmapListener, err := mailcloak.OpenSocketmapListener(cfg)
|
||||||
|
if err != nil {
|
||||||
|
_ = policyListener.Close()
|
||||||
|
log.Fatalf("socketmap listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mailcloak.DropPrivileges(cfg); err != nil {
|
||||||
|
_ = policyListener.Close()
|
||||||
|
_ = socketmapListener.Close()
|
||||||
|
log.Fatalf("privileges: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
db, err := mailcloak.OpenAliasDB(cfg.SQLite.Path)
|
db, err := mailcloak.OpenAliasDB(cfg.SQLite.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("sqlite: %v", err)
|
log.Fatalf("sqlite: %v", err)
|
||||||
@@ -44,14 +61,14 @@ func main() {
|
|||||||
|
|
||||||
// Start socketmap server
|
// Start socketmap server
|
||||||
go func() {
|
go func() {
|
||||||
if err := mailcloak.RunSocketmap(ctx, cfg, db); err != nil {
|
if err := mailcloak.ServeSocketmap(ctx, cfg, db, socketmapListener); err != nil {
|
||||||
log.Fatalf("socketmap: %v", err)
|
log.Fatalf("socketmap: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Start policy server
|
// Start policy server
|
||||||
go func() {
|
go func() {
|
||||||
if err := mailcloak.RunPolicy(ctx, cfg, db, kc, cache); err != nil {
|
if err := mailcloak.ServePolicy(ctx, cfg, db, kc, cache, policyListener); err != nil {
|
||||||
log.Fatalf("policy: %v", err)
|
log.Fatalf("policy: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -24,3 +24,6 @@ sockets:
|
|||||||
socket_owner_user: "postfix"
|
socket_owner_user: "postfix"
|
||||||
socket_owner_group: "postfix"
|
socket_owner_group: "postfix"
|
||||||
socket_mode: "0660"
|
socket_mode: "0660"
|
||||||
|
|
||||||
|
daemon:
|
||||||
|
user: "mailcloak"
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
Daemon struct {
|
||||||
|
User string `yaml:"user"`
|
||||||
|
} `yaml:"daemon"`
|
||||||
|
|
||||||
Keycloak struct {
|
Keycloak struct {
|
||||||
BaseURL string `yaml:"base_url"`
|
BaseURL string `yaml:"base_url"`
|
||||||
Realm string `yaml:"realm"`
|
Realm string `yaml:"realm"`
|
||||||
@@ -58,5 +62,8 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
if cfg.Policy.KeycloakFailureMode == "" {
|
if cfg.Policy.KeycloakFailureMode == "" {
|
||||||
cfg.Policy.KeycloakFailureMode = "tempfail"
|
cfg.Policy.KeycloakFailureMode = "tempfail"
|
||||||
}
|
}
|
||||||
|
if cfg.Daemon.User == "" {
|
||||||
|
cfg.Daemon.User = "mailcloak"
|
||||||
|
}
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,20 +38,26 @@ func (c *Cache) Put(key, val string, ok bool) {
|
|||||||
c.m[key] = cacheItem{val: val, ok: ok, expires: time.Now().Add(c.ttl)}
|
c.m[key] = cacheItem{val: val, ok: ok, expires: time.Now().Add(c.ttl)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunPolicy(ctx context.Context, cfg *Config, db *AliasDB, kc *Keycloak, cache *Cache) error {
|
func OpenPolicyListener(cfg *Config) (net.Listener, error) {
|
||||||
sock := cfg.Sockets.PolicySocket
|
sock := cfg.Sockets.PolicySocket
|
||||||
_ = os.Remove(sock)
|
_ = os.Remove(sock)
|
||||||
|
|
||||||
l, err := net.Listen("unix", sock)
|
l, err := net.Listen("unix", sock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer l.Close()
|
|
||||||
|
|
||||||
if err := ChownChmodSocket(sock, cfg); err != nil {
|
if err := ChownChmodSocket(sock, cfg); err != nil {
|
||||||
return err
|
_ = l.Close()
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServePolicy(ctx context.Context, cfg *Config, db *AliasDB, kc *Keycloak, cache *Cache, l net.Listener) error {
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := l.Accept()
|
conn, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -66,6 +72,14 @@ func RunPolicy(ctx context.Context, cfg *Config, db *AliasDB, kc *Keycloak, cach
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunPolicy(ctx context.Context, cfg *Config, db *AliasDB, kc *Keycloak, cache *Cache) error {
|
||||||
|
l, err := OpenPolicyListener(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ServePolicy(ctx, cfg, db, kc, cache, l)
|
||||||
|
}
|
||||||
|
|
||||||
func handlePolicyConn(conn net.Conn, cfg *Config, db *AliasDB, kc *Keycloak, cache *Cache) {
|
func handlePolicyConn(conn net.Conn, cfg *Config, db *AliasDB, kc *Keycloak, cache *Cache) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
r := bufio.NewReader(conn)
|
r := bufio.NewReader(conn)
|
||||||
|
|||||||
60
internal/mailcloak/privileges.go
Normal file
60
internal/mailcloak/privileges.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package mailcloak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DropPrivileges(cfg *Config) error {
|
||||||
|
userName := cfg.Daemon.User
|
||||||
|
if userName == "" {
|
||||||
|
userName = "mailcloak"
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := user.Lookup(userName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("lookup user %s: %w", userName, err)
|
||||||
|
}
|
||||||
|
uid, err := strconv.Atoi(u.Uid)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bad uid for %s: %w", userName, err)
|
||||||
|
}
|
||||||
|
gid, err := strconv.Atoi(u.Gid)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bad gid for %s: %w", userName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
euid := os.Geteuid()
|
||||||
|
if euid == uid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if euid != 0 {
|
||||||
|
return fmt.Errorf("must run as root to switch user to %s", userName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if groups, err := u.GroupIds(); err == nil {
|
||||||
|
gids := make([]int, 0, len(groups))
|
||||||
|
for _, g := range groups {
|
||||||
|
if id, err := strconv.Atoi(g); err == nil {
|
||||||
|
gids = append(gids, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(gids) > 0 {
|
||||||
|
if err := syscall.Setgroups(gids); err != nil {
|
||||||
|
return fmt.Errorf("setgroups: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.Setgid(gid); err != nil {
|
||||||
|
return fmt.Errorf("setgid: %w", err)
|
||||||
|
}
|
||||||
|
if err := syscall.Setuid(uid); err != nil {
|
||||||
|
return fmt.Errorf("setuid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -11,20 +11,26 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunSocketmap(ctx context.Context, cfg *Config, db *AliasDB) error {
|
func OpenSocketmapListener(cfg *Config) (net.Listener, error) {
|
||||||
sock := cfg.Sockets.SocketmapSocket
|
sock := cfg.Sockets.SocketmapSocket
|
||||||
_ = os.Remove(sock)
|
_ = os.Remove(sock)
|
||||||
|
|
||||||
l, err := net.Listen("unix", sock)
|
l, err := net.Listen("unix", sock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer l.Close()
|
|
||||||
|
|
||||||
if err := ChownChmodSocket(sock, cfg); err != nil {
|
if err := ChownChmodSocket(sock, cfg); err != nil {
|
||||||
return err
|
_ = l.Close()
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServeSocketmap(ctx context.Context, cfg *Config, db *AliasDB, l net.Listener) error {
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := l.Accept()
|
conn, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -39,6 +45,14 @@ func RunSocketmap(ctx context.Context, cfg *Config, db *AliasDB) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunSocketmap(ctx context.Context, cfg *Config, db *AliasDB) error {
|
||||||
|
l, err := OpenSocketmapListener(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ServeSocketmap(ctx, cfg, db, l)
|
||||||
|
}
|
||||||
|
|
||||||
// Postfix socketmap framing: "<len>:<payload>,"
|
// Postfix socketmap framing: "<len>:<payload>,"
|
||||||
func handleSocketmapConn(conn net.Conn, cfg *Config, db *AliasDB) {
|
func handleSocketmapConn(conn net.Conn, cfg *Config, db *AliasDB) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ func OpenAliasDB(path string) (*AliasDB, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if _, err := db.Exec(`
|
if _, err := db.Exec(`
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
CREATE TABLE IF NOT EXISTS aliases (
|
CREATE TABLE IF NOT EXISTS aliases (
|
||||||
alias_email TEXT PRIMARY KEY,
|
alias_email TEXT PRIMARY KEY,
|
||||||
username TEXT NOT NULL,
|
username TEXT NOT NULL,
|
||||||
@@ -22,6 +23,19 @@ CREATE TABLE IF NOT EXISTS aliases (
|
|||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS idx_aliases_username ON aliases(username);
|
CREATE INDEX IF NOT EXISTS idx_aliases_username ON aliases(username);
|
||||||
|
CREATE TABLE IF NOT EXISTS apps (
|
||||||
|
app_id TEXT PRIMARY KEY,
|
||||||
|
secret_hash TEXT NOT NULL,
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
|
created_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS app_from (
|
||||||
|
app_id TEXT NOT NULL,
|
||||||
|
from_addr TEXT NOT NULL,
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
|
PRIMARY KEY (app_id, from_addr),
|
||||||
|
FOREIGN KEY (app_id) REFERENCES apps(app_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
`); err != nil {
|
`); err != nil {
|
||||||
_ = db.Close()
|
_ = db.Close()
|
||||||
return nil, fmt.Errorf("init schema: %w", err)
|
return nil, fmt.Errorf("init schema: %w", err)
|
||||||
|
|||||||
127
mailcloakctl
127
mailcloakctl
@@ -3,6 +3,7 @@ import argparse
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
|
from argon2 import PasswordHasher, Type
|
||||||
|
|
||||||
DEFAULT_DB = "/var/lib/mailcloak/state.db"
|
DEFAULT_DB = "/var/lib/mailcloak/state.db"
|
||||||
|
|
||||||
@@ -14,10 +15,24 @@ CREATE TABLE IF NOT EXISTS aliases (
|
|||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS idx_aliases_username ON aliases(username);
|
CREATE INDEX IF NOT EXISTS idx_aliases_username ON aliases(username);
|
||||||
|
CREATE TABLE IF NOT EXISTS apps (
|
||||||
|
app_id TEXT PRIMARY KEY,
|
||||||
|
secret_hash TEXT NOT NULL,
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
|
created_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS app_from (
|
||||||
|
app_id TEXT NOT NULL,
|
||||||
|
from_addr TEXT NOT NULL,
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
|
PRIMARY KEY (app_id, from_addr),
|
||||||
|
FOREIGN KEY (app_id) REFERENCES apps(app_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def connect(db_path: str):
|
def connect(db_path: str):
|
||||||
con = sqlite3.connect(db_path)
|
con = sqlite3.connect(db_path)
|
||||||
|
con.execute("PRAGMA foreign_keys=ON;")
|
||||||
con.execute("PRAGMA journal_mode=WAL;")
|
con.execute("PRAGMA journal_mode=WAL;")
|
||||||
con.execute("PRAGMA synchronous=NORMAL;")
|
con.execute("PRAGMA synchronous=NORMAL;")
|
||||||
con.executescript(SCHEMA)
|
con.executescript(SCHEMA)
|
||||||
@@ -26,7 +41,19 @@ def connect(db_path: str):
|
|||||||
def norm_email(s: str) -> str:
|
def norm_email(s: str) -> str:
|
||||||
return s.strip().lower()
|
return s.strip().lower()
|
||||||
|
|
||||||
def cmd_add(con, alias_email, username):
|
def norm_id(s: str) -> str:
|
||||||
|
return s.strip()
|
||||||
|
|
||||||
|
argon2_hasher = PasswordHasher(
|
||||||
|
time_cost=3,
|
||||||
|
memory_cost=65536,
|
||||||
|
parallelism=1,
|
||||||
|
hash_len=32,
|
||||||
|
salt_len=16,
|
||||||
|
type=Type.ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
def cmd_aliases_add(con, alias_email, username):
|
||||||
alias_email = norm_email(alias_email)
|
alias_email = norm_email(alias_email)
|
||||||
username = username.strip()
|
username = username.strip()
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
@@ -37,17 +64,17 @@ def cmd_add(con, alias_email, username):
|
|||||||
)
|
)
|
||||||
con.commit()
|
con.commit()
|
||||||
|
|
||||||
def cmd_del(con, alias_email):
|
def cmd_aliases_del(con, alias_email):
|
||||||
alias_email = norm_email(alias_email)
|
alias_email = norm_email(alias_email)
|
||||||
con.execute("DELETE FROM aliases WHERE alias_email=?", (alias_email,))
|
con.execute("DELETE FROM aliases WHERE alias_email=?", (alias_email,))
|
||||||
con.commit()
|
con.commit()
|
||||||
|
|
||||||
def cmd_disable(con, alias_email):
|
def cmd_aliases_disable(con, alias_email):
|
||||||
alias_email = norm_email(alias_email)
|
alias_email = norm_email(alias_email)
|
||||||
con.execute("UPDATE aliases SET enabled=0, updated_at=? WHERE alias_email=?", (int(time.time()), alias_email))
|
con.execute("UPDATE aliases SET enabled=0, updated_at=? WHERE alias_email=?", (int(time.time()), alias_email))
|
||||||
con.commit()
|
con.commit()
|
||||||
|
|
||||||
def cmd_list(con, username=None):
|
def cmd_aliases_list(con, username=None):
|
||||||
if username:
|
if username:
|
||||||
rows = con.execute(
|
rows = con.execute(
|
||||||
"SELECT alias_email, username, enabled, updated_at FROM aliases WHERE username=? ORDER BY alias_email",
|
"SELECT alias_email, username, enabled, updated_at FROM aliases WHERE username=? ORDER BY alias_email",
|
||||||
@@ -60,35 +87,109 @@ def cmd_list(con, username=None):
|
|||||||
for a,u,en,ts in rows:
|
for a,u,en,ts in rows:
|
||||||
print(f"{a}\t{u}\t{'enabled' if en else 'disabled'}\t{ts}")
|
print(f"{a}\t{u}\t{'enabled' if en else 'disabled'}\t{ts}")
|
||||||
|
|
||||||
|
def cmd_apps_add(con, app_id, password):
|
||||||
|
app_id = norm_id(app_id)
|
||||||
|
secret_hash = argon2_hasher.hash(password)
|
||||||
|
now = int(time.time())
|
||||||
|
con.execute(
|
||||||
|
"INSERT INTO apps(app_id, secret_hash, enabled, created_at) VALUES(?,?,1,?) "
|
||||||
|
"ON CONFLICT(app_id) DO UPDATE SET secret_hash=excluded.secret_hash, enabled=1, created_at=excluded.created_at",
|
||||||
|
(app_id, secret_hash, now),
|
||||||
|
)
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
def cmd_apps_del(con, app_id):
|
||||||
|
app_id = norm_id(app_id)
|
||||||
|
con.execute("DELETE FROM apps WHERE app_id=?", (app_id,))
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
def cmd_apps_allow(con, app_id, from_addr):
|
||||||
|
app_id = norm_id(app_id)
|
||||||
|
from_addr = norm_email(from_addr)
|
||||||
|
con.execute(
|
||||||
|
"INSERT INTO app_from(app_id, from_addr, enabled) VALUES(?,?,1) "
|
||||||
|
"ON CONFLICT(app_id, from_addr) DO UPDATE SET enabled=1",
|
||||||
|
(app_id, from_addr),
|
||||||
|
)
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
def cmd_apps_disallow(con, app_id, from_addr):
|
||||||
|
app_id = norm_id(app_id)
|
||||||
|
from_addr = norm_email(from_addr)
|
||||||
|
con.execute("DELETE FROM app_from WHERE app_id=? AND from_addr=?", (app_id, from_addr))
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
def cmd_apps_list(con):
|
||||||
|
rows = con.execute(
|
||||||
|
"SELECT app_id, enabled, created_at FROM apps ORDER BY app_id"
|
||||||
|
).fetchall()
|
||||||
|
for app_id,en,ts in rows:
|
||||||
|
print(f"{app_id}\t{'enabled' if en else 'disabled'}\t{ts}")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument("--db", default=DEFAULT_DB)
|
ap.add_argument("--db", default=DEFAULT_DB)
|
||||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
sub = ap.add_subparsers(dest="group", required=True)
|
||||||
|
|
||||||
p_add = sub.add_parser("add")
|
aliases = sub.add_parser("aliases")
|
||||||
|
aliases_sub = aliases.add_subparsers(dest="cmd", required=True)
|
||||||
|
|
||||||
|
p_add = aliases_sub.add_parser("add")
|
||||||
p_add.add_argument("alias_email")
|
p_add.add_argument("alias_email")
|
||||||
p_add.add_argument("username")
|
p_add.add_argument("username")
|
||||||
|
|
||||||
p_del = sub.add_parser("del")
|
p_del = aliases_sub.add_parser("del")
|
||||||
p_del.add_argument("alias_email")
|
p_del.add_argument("alias_email")
|
||||||
|
|
||||||
p_dis = sub.add_parser("disable")
|
p_dis = aliases_sub.add_parser("disable")
|
||||||
p_dis.add_argument("alias_email")
|
p_dis.add_argument("alias_email")
|
||||||
|
|
||||||
p_ls = sub.add_parser("list")
|
p_ls = aliases_sub.add_parser("list")
|
||||||
p_ls.add_argument("--user", default=None)
|
p_ls.add_argument("--user", default=None)
|
||||||
|
|
||||||
|
apps = sub.add_parser("apps")
|
||||||
|
apps_sub = apps.add_subparsers(dest="cmd", required=True)
|
||||||
|
|
||||||
|
p_app_add = apps_sub.add_parser("add")
|
||||||
|
p_app_add.add_argument("app_id")
|
||||||
|
p_app_add.add_argument("password")
|
||||||
|
|
||||||
|
p_app_del = apps_sub.add_parser("del")
|
||||||
|
p_app_del.add_argument("app_id")
|
||||||
|
|
||||||
|
p_app_allow = apps_sub.add_parser("allow")
|
||||||
|
p_app_allow.add_argument("app_id")
|
||||||
|
p_app_allow.add_argument("from_addr")
|
||||||
|
|
||||||
|
p_app_disallow = apps_sub.add_parser("disallow")
|
||||||
|
p_app_disallow.add_argument("app_id")
|
||||||
|
p_app_disallow.add_argument("from_addr")
|
||||||
|
|
||||||
|
p_app_ls = apps_sub.add_parser("list")
|
||||||
|
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
con = connect(args.db)
|
con = connect(args.db)
|
||||||
try:
|
try:
|
||||||
|
if args.group == "aliases":
|
||||||
if args.cmd == "add":
|
if args.cmd == "add":
|
||||||
cmd_add(con, args.alias_email, args.username)
|
cmd_aliases_add(con, args.alias_email, args.username)
|
||||||
elif args.cmd == "del":
|
elif args.cmd == "del":
|
||||||
cmd_del(con, args.alias_email)
|
cmd_aliases_del(con, args.alias_email)
|
||||||
elif args.cmd == "disable":
|
elif args.cmd == "disable":
|
||||||
cmd_disable(con, args.alias_email)
|
cmd_aliases_disable(con, args.alias_email)
|
||||||
elif args.cmd == "list":
|
elif args.cmd == "list":
|
||||||
cmd_list(con, args.user)
|
cmd_aliases_list(con, args.user)
|
||||||
|
elif args.group == "apps":
|
||||||
|
if args.cmd == "add":
|
||||||
|
cmd_apps_add(con, args.app_id, args.password)
|
||||||
|
elif args.cmd == "del":
|
||||||
|
cmd_apps_del(con, args.app_id)
|
||||||
|
elif args.cmd == "allow":
|
||||||
|
cmd_apps_allow(con, args.app_id, args.from_addr)
|
||||||
|
elif args.cmd == "disallow":
|
||||||
|
cmd_apps_disallow(con, args.app_id, args.from_addr)
|
||||||
|
elif args.cmd == "list":
|
||||||
|
cmd_apps_list(con)
|
||||||
finally:
|
finally:
|
||||||
con.close()
|
con.close()
|
||||||
|
|
||||||
|
|||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
argon2-cffi>=23.1.0
|
||||||
Reference in New Issue
Block a user