2013-08-31 14:50:59 +00:00
|
|
|
#!/usr/bin/python3
|
2020-10-29 19:10:11 +00:00
|
|
|
#
|
|
|
|
# This is a command-line script for calling management APIs
|
|
|
|
# on the Mail-in-a-Box control panel backend. The script
|
|
|
|
# reads /var/lib/mailinabox/api.key for the backend's
|
|
|
|
# root API key. This file is readable only by root, so this
|
|
|
|
# tool can only be used as root.
|
2013-08-31 14:50:59 +00:00
|
|
|
|
2023-12-22 15:08:30 +00:00
|
|
|
import sys, getpass, urllib.request, urllib.error, json, csv
|
2023-12-23 13:26:32 +00:00
|
|
|
import contextlib
|
2013-08-31 14:50:59 +00:00
|
|
|
|
2024-10-01 23:17:27 +00:00
|
|
|
def mgmt(cmd, data=None, is_json=False):
|
2014-10-05 12:55:28 +00:00
|
|
|
# The base URL for the management daemon. (Listens on IPv4 only.)
|
|
|
|
mgmt_uri = 'http://127.0.0.1:10222'
|
2014-06-21 23:49:09 +00:00
|
|
|
|
|
|
|
setup_key_auth(mgmt_uri)
|
|
|
|
|
2024-10-01 23:17:27 +00:00
|
|
|
req = urllib.request.Request(mgmt_uri + cmd, urllib.parse.urlencode(data).encode("utf8") if data else None)
|
2014-06-03 13:24:48 +00:00
|
|
|
try:
|
|
|
|
response = urllib.request.urlopen(req)
|
|
|
|
except urllib.error.HTTPError as e:
|
2014-07-25 13:51:35 +00:00
|
|
|
if e.code == 401:
|
2023-12-23 13:26:32 +00:00
|
|
|
with contextlib.suppress(Exception):
|
2014-08-17 22:43:57 +00:00
|
|
|
print(e.read().decode("utf8"))
|
2014-07-25 13:51:35 +00:00
|
|
|
print("The management daemon refused access. The API key file may be out of sync. Try 'service mailinabox restart'.", file=sys.stderr)
|
|
|
|
elif hasattr(e, 'read'):
|
|
|
|
print(e.read().decode('utf8'), file=sys.stderr)
|
|
|
|
else:
|
|
|
|
print(e, file=sys.stderr)
|
2014-06-03 13:24:48 +00:00
|
|
|
sys.exit(1)
|
2014-08-08 12:31:22 +00:00
|
|
|
resp = response.read().decode('utf8')
|
|
|
|
if is_json: resp = json.loads(resp)
|
|
|
|
return resp
|
2013-08-31 14:50:59 +00:00
|
|
|
|
2014-06-06 21:07:30 +00:00
|
|
|
def read_password():
|
2015-03-29 16:54:37 +00:00
|
|
|
while True:
|
|
|
|
first = getpass.getpass('password: ')
|
2017-02-14 19:24:59 +00:00
|
|
|
if len(first) < 8:
|
|
|
|
print("Passwords must be at least eight characters.")
|
2015-03-30 17:59:07 +00:00
|
|
|
continue
|
2015-03-29 16:54:37 +00:00
|
|
|
second = getpass.getpass(' (again): ')
|
|
|
|
if first != second:
|
2015-03-30 17:59:07 +00:00
|
|
|
print("Passwords not the same. Try again.")
|
2015-03-29 16:54:37 +00:00
|
|
|
continue
|
|
|
|
break
|
|
|
|
return first
|
2014-06-06 21:07:30 +00:00
|
|
|
|
2014-06-21 23:49:09 +00:00
|
|
|
def setup_key_auth(mgmt_uri):
|
2023-12-23 13:07:25 +00:00
|
|
|
with open('/var/lib/mailinabox/api.key', encoding='utf-8') as f:
|
2023-01-15 13:28:43 +00:00
|
|
|
key = f.read().strip()
|
2014-06-21 23:49:09 +00:00
|
|
|
|
|
|
|
auth_handler = urllib.request.HTTPBasicAuthHandler()
|
|
|
|
auth_handler.add_password(
|
|
|
|
realm='Mail-in-a-Box Management Server',
|
|
|
|
uri=mgmt_uri,
|
|
|
|
user=key,
|
|
|
|
passwd='')
|
|
|
|
opener = urllib.request.build_opener(auth_handler)
|
|
|
|
urllib.request.install_opener(opener)
|
|
|
|
|
2013-08-31 14:50:59 +00:00
|
|
|
if len(sys.argv) < 2:
|
2020-10-29 19:10:11 +00:00
|
|
|
print("""Usage:
|
2020-10-29 19:41:34 +00:00
|
|
|
{cli} user (lists users)
|
2020-10-29 19:10:11 +00:00
|
|
|
{cli} user add user@domain.com [password]
|
|
|
|
{cli} user password user@domain.com [password]
|
|
|
|
{cli} user remove user@domain.com
|
|
|
|
{cli} user make-admin user@domain.com
|
2024-10-01 23:20:19 +00:00
|
|
|
{cli} user quota user@domain [new-quota] (get or set user quota)
|
2020-10-29 19:10:11 +00:00
|
|
|
{cli} user remove-admin user@domain.com
|
2020-10-29 19:41:34 +00:00
|
|
|
{cli} user admins (lists admins)
|
|
|
|
{cli} user mfa show user@domain.com (shows MFA devices for user, if any)
|
|
|
|
{cli} user mfa disable user@domain.com [id] (disables MFA for user)
|
|
|
|
{cli} alias (lists aliases)
|
2020-10-29 19:10:11 +00:00
|
|
|
{cli} alias add incoming.name@domain.com sent.to@other.domain.com
|
|
|
|
{cli} alias add incoming.name@domain.com 'sent.to@other.domain.com, multiple.people@other.domain.com'
|
|
|
|
{cli} alias remove incoming.name@domain.com
|
|
|
|
|
|
|
|
Removing a mail user does not delete their mail folders on disk. It only prevents IMAP/SMTP login.
|
|
|
|
""".format(
|
|
|
|
cli="management/cli.py"
|
|
|
|
))
|
2013-08-31 14:50:59 +00:00
|
|
|
|
|
|
|
elif sys.argv[1] == "user" and len(sys.argv) == 2:
|
2014-08-08 12:31:22 +00:00
|
|
|
# Dump a list of users, one per line. Mark admins with an asterisk.
|
|
|
|
users = mgmt("/mail/users?format=json", is_json=True)
|
2014-10-07 19:28:07 +00:00
|
|
|
for domain in users:
|
|
|
|
for user in domain["users"]:
|
|
|
|
if user['status'] == 'inactive': continue
|
|
|
|
print(user['email'], end='')
|
|
|
|
if "admin" in user['privileges']:
|
|
|
|
print("*", end='')
|
|
|
|
print()
|
2013-08-31 14:50:59 +00:00
|
|
|
|
2023-12-22 15:10:25 +00:00
|
|
|
elif sys.argv[1] == "user" and sys.argv[2] in {"add", "password"}:
|
2013-08-31 14:50:59 +00:00
|
|
|
if len(sys.argv) < 5:
|
2023-12-22 15:17:23 +00:00
|
|
|
email = input('email: ') if len(sys.argv) < 4 else sys.argv[3]
|
2014-06-06 21:07:30 +00:00
|
|
|
pw = read_password()
|
2013-08-31 14:50:59 +00:00
|
|
|
else:
|
|
|
|
email, pw = sys.argv[3:5]
|
|
|
|
|
|
|
|
if sys.argv[2] == "add":
|
2014-06-03 13:24:48 +00:00
|
|
|
print(mgmt("/mail/users/add", { "email": email, "password": pw }))
|
2013-08-31 14:50:59 +00:00
|
|
|
elif sys.argv[2] == "password":
|
2014-06-03 13:24:48 +00:00
|
|
|
print(mgmt("/mail/users/password", { "email": email, "password": pw }))
|
2013-08-31 14:50:59 +00:00
|
|
|
|
|
|
|
elif sys.argv[1] == "user" and sys.argv[2] == "remove" and len(sys.argv) == 4:
|
2014-06-03 13:24:48 +00:00
|
|
|
print(mgmt("/mail/users/remove", { "email": sys.argv[3] }))
|
2013-08-31 14:50:59 +00:00
|
|
|
|
2023-12-22 15:10:25 +00:00
|
|
|
elif sys.argv[1] == "user" and sys.argv[2] in {"make-admin", "remove-admin"} and len(sys.argv) == 4:
|
2023-12-22 15:17:23 +00:00
|
|
|
action = 'add' if sys.argv[2] == 'make-admin' else 'remove'
|
2014-08-08 12:31:22 +00:00
|
|
|
print(mgmt("/mail/users/privileges/" + action, { "email": sys.argv[3], "privilege": "admin" }))
|
|
|
|
|
2014-08-16 12:59:29 +00:00
|
|
|
elif sys.argv[1] == "user" and sys.argv[2] == "admins":
|
|
|
|
# Dump a list of admin users.
|
|
|
|
users = mgmt("/mail/users?format=json", is_json=True)
|
2014-10-07 19:28:07 +00:00
|
|
|
for domain in users:
|
|
|
|
for user in domain["users"]:
|
|
|
|
if "admin" in user['privileges']:
|
|
|
|
print(user['email'])
|
2014-08-16 12:59:29 +00:00
|
|
|
|
2024-04-27 22:41:35 +00:00
|
|
|
elif sys.argv[1] == "user" and sys.argv[2] == "quota" and len(sys.argv) == 4:
|
2024-10-01 23:20:19 +00:00
|
|
|
# Get a user's quota
|
2024-04-27 22:41:35 +00:00
|
|
|
print(mgmt("/mail/users/quota?text=1&email=%s" % sys.argv[3]))
|
|
|
|
|
|
|
|
elif sys.argv[1] == "user" and sys.argv[2] == "quota" and len(sys.argv) == 5:
|
|
|
|
# Set a user's quota
|
2024-10-01 23:17:27 +00:00
|
|
|
users = mgmt("/mail/users/quota", { "email": sys.argv[3], "quota": sys.argv[4] })
|
2024-04-27 22:41:35 +00:00
|
|
|
|
2020-10-29 19:41:34 +00:00
|
|
|
elif sys.argv[1] == "user" and len(sys.argv) == 5 and sys.argv[2:4] == ["mfa", "show"]:
|
|
|
|
# Show MFA status for a user.
|
|
|
|
status = mgmt("/mfa/status", { "user": sys.argv[4] }, is_json=True)
|
|
|
|
W = csv.writer(sys.stdout)
|
|
|
|
W.writerow(["id", "type", "label"])
|
|
|
|
for mfa in status["enabled_mfa"]:
|
|
|
|
W.writerow([mfa["id"], mfa["type"], mfa["label"]])
|
|
|
|
|
2023-12-22 15:10:25 +00:00
|
|
|
elif sys.argv[1] == "user" and len(sys.argv) in {5, 6} and sys.argv[2:4] == ["mfa", "disable"]:
|
2020-10-29 19:41:34 +00:00
|
|
|
# Disable MFA (all or a particular device) for a user.
|
|
|
|
print(mgmt("/mfa/disable", { "user": sys.argv[4], "mfa-id": sys.argv[5] if len(sys.argv) == 6 else None }))
|
|
|
|
|
2013-08-31 14:50:59 +00:00
|
|
|
elif sys.argv[1] == "alias" and len(sys.argv) == 2:
|
2014-06-03 13:24:48 +00:00
|
|
|
print(mgmt("/mail/aliases"))
|
2013-08-31 14:50:59 +00:00
|
|
|
|
|
|
|
elif sys.argv[1] == "alias" and sys.argv[2] == "add" and len(sys.argv) == 5:
|
2015-08-14 23:05:08 +00:00
|
|
|
print(mgmt("/mail/aliases/add", { "address": sys.argv[3], "forwards_to": sys.argv[4] }))
|
2013-08-31 14:50:59 +00:00
|
|
|
|
|
|
|
elif sys.argv[1] == "alias" and sys.argv[2] == "remove" and len(sys.argv) == 4:
|
2015-08-14 23:05:08 +00:00
|
|
|
print(mgmt("/mail/aliases/remove", { "address": sys.argv[3] }))
|
2013-08-31 14:50:59 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
print("Invalid command-line arguments.")
|
2014-08-10 14:10:25 +00:00
|
|
|
sys.exit(1)
|