Просмотр исходного кода

Fix a regression in which some MariaDB/MySQL passwords were not escaped correctly (#1017).

Dan Helfman 3 месяцев назад
Родитель
Сommit
1d486d024b
3 измененных файлов с 23 добавлено и 4 удалено
  1. 1 0
      NEWS
  2. 3 1
      borgmatic/hooks/data_source/mariadb.py
  3. 19 3
      tests/unit/hooks/data_source/test_mariadb.py

+ 1 - 0
NEWS

@@ -3,6 +3,7 @@
    incident UI. See the documentation for more information:
    incident UI. See the documentation for more information:
    https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#pagerduty-hook
    https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#pagerduty-hook
  * #936: Clarify Zabbix monitoring hook documentation about creating items.
  * #936: Clarify Zabbix monitoring hook documentation about creating items.
+ * #1017: Fix a regression in which some MariaDB/MySQL passwords were not escaped correctly.
 
 
 1.9.13
 1.9.13
  * #975: Add a "compression" option to the PostgreSQL database hook.
  * #975: Add a "compression" option to the PostgreSQL database hook.

+ 3 - 1
borgmatic/hooks/data_source/mariadb.py

@@ -65,10 +65,12 @@ def make_defaults_file_options(username=None, password=None, defaults_extra_file
     Do not use the returned value for multiple different command invocations. That will not work
     Do not use the returned value for multiple different command invocations. That will not work
     because each pipe is "used up" once read.
     because each pipe is "used up" once read.
     '''
     '''
+    escaped_password = None if password is None else password.replace('\\', '\\\\')
+
     values = '\n'.join(
     values = '\n'.join(
         (
         (
             (f'user={username}' if username is not None else ''),
             (f'user={username}' if username is not None else ''),
-            (f"password='{password}'" if password is not None else ''),
+            (f'password="{escaped_password}"' if escaped_password is not None else ''),
         )
         )
     ).strip()
     ).strip()
 
 

+ 19 - 3
tests/unit/hooks/data_source/test_mariadb.py

@@ -36,7 +36,7 @@ def test_make_defaults_file_option_with_username_and_password_writes_them_to_fil
 
 
     flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
     flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
     flexmock(module.os).should_receive('write').with_args(
     flexmock(module.os).should_receive('write').with_args(
-        write_descriptor, b"[client]\nuser=root\npassword='trustsome1'"
+        write_descriptor, b'[client]\nuser=root\npassword="trustsome1"'
     ).once()
     ).once()
     flexmock(module.os).should_receive('close')
     flexmock(module.os).should_receive('close')
     flexmock(module.os).should_receive('set_inheritable')
     flexmock(module.os).should_receive('set_inheritable')
@@ -46,6 +46,22 @@ def test_make_defaults_file_option_with_username_and_password_writes_them_to_fil
     )
     )
 
 
 
 
+def test_make_defaults_file_option_escapes_password_containing_backslash():
+    read_descriptor = 99
+    write_descriptor = flexmock()
+
+    flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
+    flexmock(module.os).should_receive('write').with_args(
+        write_descriptor, b'[client]\nuser=root\n' + br'password="trust\\nsome1"'
+    ).once()
+    flexmock(module.os).should_receive('close')
+    flexmock(module.os).should_receive('set_inheritable')
+
+    assert module.make_defaults_file_options(username='root', password=r'trust\nsome1') == (
+        '--defaults-extra-file=/dev/fd/99',
+    )
+
+
 def test_make_defaults_file_pipe_with_only_username_writes_it_to_file_descriptor():
 def test_make_defaults_file_pipe_with_only_username_writes_it_to_file_descriptor():
     read_descriptor = 99
     read_descriptor = 99
     write_descriptor = flexmock()
     write_descriptor = flexmock()
@@ -68,7 +84,7 @@ def test_make_defaults_file_pipe_with_only_password_writes_it_to_file_descriptor
 
 
     flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
     flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
     flexmock(module.os).should_receive('write').with_args(
     flexmock(module.os).should_receive('write').with_args(
-        write_descriptor, b"[client]\npassword='trustsome1'"
+        write_descriptor, b'[client]\npassword="trustsome1"'
     ).once()
     ).once()
     flexmock(module.os).should_receive('close')
     flexmock(module.os).should_receive('close')
     flexmock(module.os).should_receive('set_inheritable')
     flexmock(module.os).should_receive('set_inheritable')
@@ -84,7 +100,7 @@ def test_make_defaults_file_option_with_defaults_extra_filename_includes_it_in_f
 
 
     flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
     flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
     flexmock(module.os).should_receive('write').with_args(
     flexmock(module.os).should_receive('write').with_args(
-        write_descriptor, b"!include extra.cnf\n[client]\nuser=root\npassword='trustsome1'"
+        write_descriptor, b'!include extra.cnf\n[client]\nuser=root\npassword="trustsome1"'
     ).once()
     ).once()
     flexmock(module.os).should_receive('close')
     flexmock(module.os).should_receive('close')
     flexmock(module.os).should_receive('set_inheritable')
     flexmock(module.os).should_receive('set_inheritable')