Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
mailinabox
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
mailinabox
Commits
cecda9ce
Commit
cecda9ce
authored
Jun 09, 2014
by
Joshua Tauberer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
management: shell out external programs in a more secure way
parent
70bd96f6
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
58 additions
and
29 deletions
+58
-29
backup.py
management/backup.py
+28
-16
daemon.py
management/daemon.py
+13
-7
mailconfig.py
management/mailconfig.py
+6
-5
utils.py
management/utils.py
+11
-1
No files found.
management/backup.py
View file @
cecda9ce
...
@@ -12,7 +12,7 @@
...
@@ -12,7 +12,7 @@
import
os
,
os
.
path
,
subprocess
import
os
,
os
.
path
,
subprocess
from
utils
import
exclusive_process
,
load_environment
from
utils
import
exclusive_process
,
load_environment
,
shell
env
=
load_environment
()
env
=
load_environment
()
...
@@ -24,31 +24,43 @@ rdiff_backup_dir = os.path.join(backup_dir, 'rdiff-history')
...
@@ -24,31 +24,43 @@ rdiff_backup_dir = os.path.join(backup_dir, 'rdiff-history')
os
.
makedirs
(
backup_dir
,
exist_ok
=
True
)
os
.
makedirs
(
backup_dir
,
exist_ok
=
True
)
# Stop services.
# Stop services.
s
ubprocess
.
check_call
([
"
service"
,
"dovecot"
,
"stop"
])
s
hell
(
'check_call'
,
[
"/usr/sbin/
service"
,
"dovecot"
,
"stop"
])
s
ubprocess
.
check_call
([
"
service"
,
"postfix"
,
"stop"
])
s
hell
(
'check_call'
,
[
"/usr/sbin/
service"
,
"postfix"
,
"stop"
])
# Update the backup directory which stores increments.
# Update the backup directory which stores increments.
try
:
try
:
s
ubprocess
.
check_call
(
[
s
hell
(
'check_call'
,
[
"rdiff-backup"
,
"
/usr/bin/
rdiff-backup"
,
"--exclude"
,
backup_dir
,
"--exclude"
,
backup_dir
,
env
[
"STORAGE_ROOT"
],
env
[
"STORAGE_ROOT"
],
rdiff_backup_dir
])
rdiff_backup_dir
])
except
subprocess
.
CalledProcessError
:
except
subprocess
.
CalledProcessError
:
# Trap the error so we restart services again.
pass
pass
# Start services.
# Start services.
subprocess
.
check_call
([
"service"
,
"dovecot"
,
"start"
])
shell
(
'check_call'
,
[
"/usr/sbin/service"
,
"dovecot"
,
"start"
])
subprocess
.
check_call
([
"service"
,
"postfix"
,
"start"
])
shell
(
'check_call'
,
[
"/usr/sbin/service"
,
"postfix"
,
"start"
])
# Tar the rdiff-backup directory into a single file encrypted using the backup private key.
# Tar the rdiff-backup directory into a single file.
os
.
system
(
shell
(
'check_call'
,
[
"tar -zcC
%
s . | openssl enc -aes-256-cbc -a -salt -in /dev/stdin -out
%
s -pass file:
%
s"
"/bin/tar"
,
%
"-zc"
,
(
rdiff_backup_dir
,
"-f"
,
os
.
path
.
join
(
backup_dir
,
"latest.tgz"
),
os
.
path
.
join
(
backup_dir
,
"latest.tgz.enc"
),
"-C"
,
rdiff_backup_dir
,
os
.
path
.
join
(
backup_dir
,
"secret_key.txt"
),
"."
])
))
# Encrypt the backup using the backup private key.
shell
(
'check_call'
,
[
"/usr/bin/openssl"
,
"enc"
,
"-aes-256-cbc"
,
"-a"
,
"-salt"
,
"-in"
,
os
.
path
.
join
(
backup_dir
,
"latest.tgz"
),
"-out"
,
os
.
path
.
join
(
backup_dir
,
"latest.tgz.enc"
),
"-pass"
,
"file:
%
s"
%
os
.
path
.
join
(
backup_dir
,
"secret_key.txt"
),
])
# The backup can be decrypted with:
# The backup can be decrypted with:
# openssl enc -d -aes-256-cbc -a -in latest.tgz.enc -out /dev/stdout -pass file:secret_key.txt | tar -z
# openssl enc -d -aes-256-cbc -a -in latest.tgz.enc -out /dev/stdout -pass file:secret_key.txt | tar -z
management/daemon.py
View file @
cecda9ce
#!/usr/bin/python3
#!/usr/bin/python3
import
os
,
os
.
path
,
subprocess
import
os
,
os
.
path
,
re
from
flask
import
Flask
,
request
,
render_template
from
flask
import
Flask
,
request
,
render_template
app
=
Flask
(
__name__
)
app
=
Flask
(
__name__
)
...
@@ -59,15 +59,21 @@ def dns_update():
...
@@ -59,15 +59,21 @@ def dns_update():
@
app
.
route
(
'/system/updates'
)
@
app
.
route
(
'/system/updates'
)
def
show_updates
():
def
show_updates
():
subprocess
.
check_call
(
"apt-get -qq update"
,
shell
=
True
)
utils
.
shell
(
"check_call"
,
[
"/usr/bin/apt-get"
,
"-qq"
,
"update"
])
return
subprocess
.
check_output
(
simulated_install
=
utils
.
shell
(
"check_output"
,
[
"/usr/bin/apt-get"
,
"-qq"
,
"-s"
,
"upgrade"
])
r"""apt-get -qq -s upgrade | grep -v ^Conf | sed "s/^Inst /Updated Package Available: /" | sed "s/\[\(.*\)\] (\(\S*\).*/\(\1 => \2\)/" """
,
pkgs
=
[]
shell
=
True
)
for
line
in
simulated_install
.
split
(
'
\n
'
):
if
re
.
match
(
r'^Conf .*'
,
line
):
continue
# remove these lines, not informative
line
=
re
.
sub
(
r'^Inst (.*) \[(.*)\] \((\S*).*'
,
r'Updated Package Available: \1 (\3)'
,
line
)
# make these lines prettier
pkgs
.
append
(
line
)
return
"
\n
"
.
join
(
pkgs
)
@
app
.
route
(
'/system/update-packages'
,
methods
=
[
"POST"
])
@
app
.
route
(
'/system/update-packages'
,
methods
=
[
"POST"
])
def
do_updates
():
def
do_updates
():
subprocess
.
check_call
(
"apt-get -qq update"
,
shell
=
True
)
utils
.
shell
(
"check_call"
,
[
"/usr/bin/apt-get"
,
"-qq"
,
"update"
])
return
subprocess
.
check_output
(
"DEBIAN_FRONTEND=noninteractive apt-get -y upgrade"
,
shell
=
True
)
return
utils
.
shell
(
"check_output"
,
[
"/usr/bin/apt-get"
,
"-y"
,
"upgrade"
],
env
=
{
"DEBIAN_FRONTEND"
:
"noninteractive"
})
# APP
# APP
...
...
management/mailconfig.py
View file @
cecda9ce
import
subprocess
,
shutil
,
os
,
sqlite3
,
re
import
subprocess
,
shutil
,
os
,
sqlite3
,
re
import
utils
def
validate_email
(
email
,
strict
):
def
validate_email
(
email
,
strict
):
# There are a lot of characters permitted in email addresses, but
# There are a lot of characters permitted in email addresses, but
...
@@ -52,7 +53,7 @@ def add_mail_user(email, pw, env):
...
@@ -52,7 +53,7 @@ def add_mail_user(email, pw, env):
conn
,
c
=
open_database
(
env
,
with_connection
=
True
)
conn
,
c
=
open_database
(
env
,
with_connection
=
True
)
# hash the password
# hash the password
pw
=
subprocess
.
check_output
(
[
"/usr/bin/doveadm"
,
"pw"
,
"-s"
,
"SHA512-CRYPT"
,
"-p"
,
pw
])
.
strip
()
pw
=
utils
.
shell
(
'check_output'
,
[
"/usr/bin/doveadm"
,
"pw"
,
"-s"
,
"SHA512-CRYPT"
,
"-p"
,
pw
])
.
strip
()
# add the user to the database
# add the user to the database
try
:
try
:
...
@@ -68,14 +69,14 @@ def add_mail_user(email, pw, env):
...
@@ -68,14 +69,14 @@ def add_mail_user(email, pw, env):
# Check if the mailboxes exist before creating them. When creating a user that had previously
# Check if the mailboxes exist before creating them. When creating a user that had previously
# been deleted, the mailboxes will still exist because they are still on disk.
# been deleted, the mailboxes will still exist because they are still on disk.
try
:
try
:
existing_mboxes
=
subprocess
.
check_output
([
"doveadm"
,
"mailbox"
,
"list"
,
"-u"
,
email
,
"-8"
],
stderr
=
subprocess
.
STDOUT
)
.
decode
(
"utf8"
)
.
split
(
"
\n
"
)
existing_mboxes
=
utils
.
shell
(
'check_output'
,
[
"doveadm"
,
"mailbox"
,
"list"
,
"-u"
,
email
,
"-8"
],
capture_stderr
=
True
)
.
split
(
"
\n
"
)
except
subprocess
.
CalledProcessError
as
e
:
except
subprocess
.
CalledProcessError
as
e
:
c
.
execute
(
"DELETE FROM users WHERE email=?"
,
(
email
,))
c
.
execute
(
"DELETE FROM users WHERE email=?"
,
(
email
,))
conn
.
commit
()
conn
.
commit
()
return
(
"Failed to initialize the user: "
+
e
.
output
.
decode
(
"utf8"
),
400
)
return
(
"Failed to initialize the user: "
+
e
.
output
.
decode
(
"utf8"
),
400
)
if
"INBOX"
not
in
existing_mboxes
:
subprocess
.
check_call
(
[
"doveadm"
,
"mailbox"
,
"create"
,
"-u"
,
email
,
"-s"
,
"INBOX"
])
if
"INBOX"
not
in
existing_mboxes
:
utils
.
shell
(
'check_call'
,
[
"doveadm"
,
"mailbox"
,
"create"
,
"-u"
,
email
,
"-s"
,
"INBOX"
])
if
"Spam"
not
in
existing_mboxes
:
subprocess
.
check_call
(
[
"doveadm"
,
"mailbox"
,
"create"
,
"-u"
,
email
,
"-s"
,
"Spam"
])
if
"Spam"
not
in
existing_mboxes
:
utils
.
shell
(
'check_call'
,
[
"doveadm"
,
"mailbox"
,
"create"
,
"-u"
,
email
,
"-s"
,
"Spam"
])
# Create the user's sieve script to move spam into the Spam folder, and make it owned by mail.
# Create the user's sieve script to move spam into the Spam folder, and make it owned by mail.
maildirstat
=
os
.
stat
(
env
[
"STORAGE_ROOT"
]
+
"/mail/mailboxes"
)
maildirstat
=
os
.
stat
(
env
[
"STORAGE_ROOT"
]
+
"/mail/mailboxes"
)
...
@@ -93,7 +94,7 @@ def add_mail_user(email, pw, env):
...
@@ -93,7 +94,7 @@ def add_mail_user(email, pw, env):
def
set_mail_password
(
email
,
pw
,
env
):
def
set_mail_password
(
email
,
pw
,
env
):
# hash the password
# hash the password
pw
=
subprocess
.
check_output
(
[
"/usr/bin/doveadm"
,
"pw"
,
"-s"
,
"SHA512-CRYPT"
,
"-p"
,
pw
])
.
strip
()
pw
=
utils
.
shell
(
'check_output'
,
[
"/usr/bin/doveadm"
,
"pw"
,
"-s"
,
"SHA512-CRYPT"
,
"-p"
,
pw
])
.
strip
()
# update the database
# update the database
conn
,
c
=
open_database
(
env
,
with_connection
=
True
)
conn
,
c
=
open_database
(
env
,
with_connection
=
True
)
...
...
management/utils.py
View file @
cecda9ce
...
@@ -74,4 +74,14 @@ def is_pid_valid(pid):
...
@@ -74,4 +74,14 @@ def is_pid_valid(pid):
else
:
# EINVAL
else
:
# EINVAL
raise
raise
else
:
else
:
return
True
return
True
\ No newline at end of file
def
shell
(
method
,
cmd_args
,
env
=
{},
capture_stderr
=
False
):
# A safe way to execute processes.
# Some processes like apt-get require being given a sane PATH.
import
subprocess
env
.
update
({
"PATH"
:
"/sbin:/bin:/usr/sbin:/usr/bin"
})
stderr
=
None
if
not
capture_stderr
else
subprocess
.
STDOUT
ret
=
getattr
(
subprocess
,
method
)(
cmd_args
,
env
=
env
,
stderr
=
stderr
)
if
isinstance
(
ret
,
bytes
):
ret
=
ret
.
decode
(
"utf8"
)
return
ret
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment