mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-11-02 19:20:54 +00:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/main'
This commit is contained in:
		
						commit
						2abaf64c43
					
				
							
								
								
									
										21
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@ -1,6 +1,27 @@
 | 
				
			|||||||
CHANGELOG
 | 
					CHANGELOG
 | 
				
			||||||
=========
 | 
					=========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In Development
 | 
				
			||||||
 | 
					--------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					System:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* fail2ban didn't start after setup.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Mail:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Disable Roundcube password plugin since it was corrupting the user database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Control panel:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Fix changing existing backup settings when the rsync type is used.
 | 
				
			||||||
 | 
					* Allow setting a custom port for rsync backups.
 | 
				
			||||||
 | 
					* Fixes to DNS lookups during status checks when there are timeouts, enforce timeouts better.
 | 
				
			||||||
 | 
					* A new check is added to ensure fail2ban is running.
 | 
				
			||||||
 | 
					* Fixed a color.
 | 
				
			||||||
 | 
					* Disable Roundcube password plugin since it was corrupting the user database
 | 
				
			||||||
 | 
					* Improve error messages in the management tools when external command-line tools are run.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Version 60.1 (October 30, 2022)
 | 
					Version 60.1 (October 30, 2022)
 | 
				
			||||||
-------------------------------
 | 
					-------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -208,9 +208,18 @@ def get_duplicity_additional_args(env):
 | 
				
			|||||||
	config = get_backup_config(env)
 | 
						config = get_backup_config(env)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if config["type"] == 'rsync':
 | 
						if config["type"] == 'rsync':
 | 
				
			||||||
 | 
							# Extract a port number for the ssh transport.  Duplicity accepts the
 | 
				
			||||||
 | 
							# optional port number syntax in the target, but it doesn't appear to act
 | 
				
			||||||
 | 
							# on it, so we set the ssh port explicitly via the duplicity options.
 | 
				
			||||||
 | 
							from urllib.parse import urlsplit
 | 
				
			||||||
 | 
							try:
 | 
				
			||||||
 | 
								port = urlsplit(config["target_url"]).port
 | 
				
			||||||
 | 
							except ValueError:
 | 
				
			||||||
 | 
								port = 22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return [
 | 
							return [
 | 
				
			||||||
			"--ssh-options= -i /root/.ssh/id_rsa_miab",
 | 
								f"--ssh-options= -i /root/.ssh/id_rsa_miab -p {port}",
 | 
				
			||||||
			"--rsync-options= -e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
 | 
								f"--rsync-options= -e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p {port} -i /root/.ssh/id_rsa_miab\"",
 | 
				
			||||||
		]
 | 
							]
 | 
				
			||||||
	elif config["type"] == 's3':
 | 
						elif config["type"] == 's3':
 | 
				
			||||||
		additional_args = ["--s3-endpoint-url", config["s3_endpoint_url"]]
 | 
							additional_args = ["--s3-endpoint-url", config["s3_endpoint_url"]]
 | 
				
			||||||
@ -407,6 +416,14 @@ def list_target_files(config):
 | 
				
			|||||||
		rsync_fn_size_re = re.compile(r'.*    ([^ ]*) [^ ]* [^ ]* (.*)')
 | 
							rsync_fn_size_re = re.compile(r'.*    ([^ ]*) [^ ]* [^ ]* (.*)')
 | 
				
			||||||
		rsync_target = '{host}:{path}'
 | 
							rsync_target = '{host}:{path}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# Strip off any trailing port specifier because it's not valid in rsync's
 | 
				
			||||||
 | 
							# DEST syntax.  Explicitly set the port number for the ssh transport.
 | 
				
			||||||
 | 
							user_host, *_ = url.netloc.rsplit(':', 1)
 | 
				
			||||||
 | 
							try:
 | 
				
			||||||
 | 
								port = url.port
 | 
				
			||||||
 | 
							except ValueError:
 | 
				
			||||||
 | 
								port = 22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		url_path = url.path
 | 
							url_path = url.path
 | 
				
			||||||
		if not url_path.endswith('/'):
 | 
							if not url_path.endswith('/'):
 | 
				
			||||||
			url_path = url_path + '/'
 | 
								url_path = url_path + '/'
 | 
				
			||||||
@ -415,11 +432,11 @@ def list_target_files(config):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		rsync_command = [ 'rsync',
 | 
							rsync_command = [ 'rsync',
 | 
				
			||||||
					'-e',
 | 
										'-e',
 | 
				
			||||||
					'/usr/bin/ssh -i /root/.ssh/id_rsa_miab -oStrictHostKeyChecking=no -oBatchMode=yes',
 | 
										f'/usr/bin/ssh -i /root/.ssh/id_rsa_miab -oStrictHostKeyChecking=no -oBatchMode=yes -p {port}',
 | 
				
			||||||
					'--list-only',
 | 
										'--list-only',
 | 
				
			||||||
					'-r',
 | 
										'-r',
 | 
				
			||||||
					rsync_target.format(
 | 
										rsync_target.format(
 | 
				
			||||||
						host=url.netloc,
 | 
											host=user_host,
 | 
				
			||||||
						path=url_path)
 | 
											path=url_path)
 | 
				
			||||||
				]
 | 
									]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -95,6 +95,12 @@ def run_services_checks(env, output, pool):
 | 
				
			|||||||
		fatal = fatal or fatal2
 | 
							fatal = fatal or fatal2
 | 
				
			||||||
		output2.playback(output)
 | 
							output2.playback(output)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Check fail2ban.
 | 
				
			||||||
 | 
						code, ret = shell('check_output', ["fail2ban-client", "status"], capture_stderr=True, trap=True)
 | 
				
			||||||
 | 
						if code != 0:
 | 
				
			||||||
 | 
							output.print_error("fail2ban is not running.")
 | 
				
			||||||
 | 
							all_running = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if all_running:
 | 
						if all_running:
 | 
				
			||||||
		output.print_ok("All system services are running.")
 | 
							output.print_ok("All system services are running.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -72,11 +72,6 @@
 | 
				
			|||||||
        html {
 | 
					        html {
 | 
				
			||||||
          filter: invert(100%) hue-rotate(180deg);
 | 
					          filter: invert(100%) hue-rotate(180deg);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        /* Set explicit background color (necessary for Firefox) */
 | 
					 | 
				
			||||||
        html {
 | 
					 | 
				
			||||||
          background-color: #111;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
          
 | 
					          
 | 
				
			||||||
        /* Override Boostrap theme here to give more contrast. The black turns to white by the filter. */
 | 
					        /* Override Boostrap theme here to give more contrast. The black turns to white by the filter. */
 | 
				
			||||||
        .form-control {
 | 
					        .form-control {
 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,10 @@
 | 
				
			|||||||
    <label for="backup-target-rsync-host" class="col-sm-2 control-label">Hostname</label>
 | 
					    <label for="backup-target-rsync-host" class="col-sm-2 control-label">Hostname</label>
 | 
				
			||||||
    <div class="col-sm-8">
 | 
					    <div class="col-sm-8">
 | 
				
			||||||
      <input type="text" placeholder="hostname.local" class="form-control" rows="1" id="backup-target-rsync-host">
 | 
					      <input type="text" placeholder="hostname.local" class="form-control" rows="1" id="backup-target-rsync-host">
 | 
				
			||||||
 | 
					      <div class="small" style="margin-top: 2px">
 | 
				
			||||||
 | 
						The hostname at your rsync provider, e.g. <tt>da2327.rsync.net</tt>.  Optionally includes a colon
 | 
				
			||||||
 | 
						and the provider's non-standard ssh port number, e.g. <tt>u215843.your-storagebox.de:23</tt>. 
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div class="form-group backup-target-rsync">
 | 
					  <div class="form-group backup-target-rsync">
 | 
				
			||||||
@ -266,12 +270,11 @@ function show_backup_configuration() {
 | 
				
			|||||||
          $("#min-age").val(r.min_age_in_days);
 | 
					          $("#min-age").val(r.min_age_in_days);
 | 
				
			||||||
        } else if(r.type == "rsync") {
 | 
					        } else if(r.type == "rsync") {
 | 
				
			||||||
          $("#min-age").val(r.min_age_in_days);
 | 
					          $("#min-age").val(r.min_age_in_days);
 | 
				
			||||||
          $("#backup-target-type").val("rsync");
 | 
					          const spec = url_split(r.target_url);
 | 
				
			||||||
          var path = r.target_url.substring(8).split('//');
 | 
					          $("#backup-target-type").val(spec.scheme);
 | 
				
			||||||
          var host_parts = path.shift().split('@');
 | 
						  $("#backup-target-rsync-user").val(spec.user);
 | 
				
			||||||
          $("#backup-target-rsync-user").val(host_parts[0]);
 | 
						  $("#backup-target-rsync-host").val(spec.host);
 | 
				
			||||||
          $("#backup-target-rsync-host").val(host_parts[1]);
 | 
						  $("#backup-target-rsync-path").val(spec.path);
 | 
				
			||||||
          $("#backup-target-rsync-path").val('/'+path[0]);
 | 
					 | 
				
			||||||
        } else if(r.type == "s3") {
 | 
					        } else if(r.type == "s3") {
 | 
				
			||||||
          $("#backup-s3-access-key-id").val(r.s3_access_key_id);
 | 
					          $("#backup-s3-access-key-id").val(r.s3_access_key_id);
 | 
				
			||||||
          $("#backup-s3-secret-access-key").val(r.s3_secret_access_key);
 | 
					          $("#backup-s3-secret-access-key").val(r.s3_secret_access_key);
 | 
				
			||||||
@ -335,4 +338,31 @@ function set_backup_configuration() {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  return false;
 | 
					  return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Return a two-element array of the substring preceding and the substring following
 | 
				
			||||||
 | 
					// the first occurence of separator in string. Return [undefined, string] if the
 | 
				
			||||||
 | 
					// separator does not appear in string.
 | 
				
			||||||
 | 
					const split1_rest = (string, separator) => {
 | 
				
			||||||
 | 
					    const index = string.indexOf(separator);
 | 
				
			||||||
 | 
					    return (index >= 0) ? [string.substring(0, index), string.substring(index + separator.length)] : [undefined, string];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Note: The manifest JS URL class does not work in some security-conscious
 | 
				
			||||||
 | 
					// settings, e.g. Brave browser, so we roll our own that handles only what we need.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Use greedy separator parsing to get parts of a MIAB backup target url.
 | 
				
			||||||
 | 
					// Note: path will not include a leading forward slash '/'
 | 
				
			||||||
 | 
					const url_split = url => {
 | 
				
			||||||
 | 
					    const [ scheme, scheme_rest ] = split1_rest(url, '://');
 | 
				
			||||||
 | 
					    const [ user, user_rest ] = split1_rest(scheme_rest, '@');
 | 
				
			||||||
 | 
					    const [ host, path ] = split1_rest(user_rest, '/');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
						scheme,
 | 
				
			||||||
 | 
						user,
 | 
				
			||||||
 | 
						host,
 | 
				
			||||||
 | 
						path,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
				
			|||||||
@ -136,13 +136,16 @@ def shell(method, cmd_args, env={}, capture_stderr=False, return_bytes=False, tr
 | 
				
			|||||||
    if method == "check_output" and input is not None:
 | 
					    if method == "check_output" and input is not None:
 | 
				
			||||||
        kwargs['input'] = input
 | 
					        kwargs['input'] = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not trap:
 | 
					    try:
 | 
				
			||||||
        ret = getattr(subprocess, method)(cmd_args, **kwargs)
 | 
					        ret = getattr(subprocess, method)(cmd_args, **kwargs)
 | 
				
			||||||
    else:
 | 
					        code = 0
 | 
				
			||||||
        try:
 | 
					    except subprocess.CalledProcessError as e:
 | 
				
			||||||
            ret = getattr(subprocess, method)(cmd_args, **kwargs)
 | 
					        if not trap:
 | 
				
			||||||
            code = 0
 | 
					            # Reformat exception.
 | 
				
			||||||
        except subprocess.CalledProcessError as e:
 | 
					            msg = "Command failed with exit code {}: {}".format(e.returncode, subprocess.list2cmdline(cmd_args))
 | 
				
			||||||
 | 
					            if e.output: msg += "\n\nOutput:\n" + e.output
 | 
				
			||||||
 | 
					            raise Exception(msg)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            ret = e.output
 | 
					            ret = e.output
 | 
				
			||||||
            code = e.returncode
 | 
					            code = e.returncode
 | 
				
			||||||
    if not return_bytes and isinstance(ret, bytes): ret = ret.decode("utf8")
 | 
					    if not return_bytes and isinstance(ret, bytes): ret = ret.decode("utf8")
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
Mail-in-a-Box Security Guide
 | 
					Mail-in-a-Box Security Guide
 | 
				
			||||||
============================
 | 
					============================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Mail-in-a-Box turns a fresh Ubuntu 18.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components.
 | 
					Mail-in-a-Box turns a fresh Ubuntu 22.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This page documents the security posture of Mail-in-a-Box. The term “box” is used below to mean a configured Mail-in-a-Box.
 | 
					This page documents the security posture of Mail-in-a-Box. The term “box” is used below to mean a configured Mail-in-a-Box.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user