]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tmpfiles: optionally, decode string to write to files with base64
authorLennart Poettering <lennart@poettering.net>
Tue, 12 Jul 2022 21:51:15 +0000 (23:51 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 15 Jul 2022 09:55:03 +0000 (11:55 +0200)
This is useful to use "f" or "w" to write arbitrary binary files to
disk, or files with newlines and similar (for example to provision SSH
host keys and similar).

man/tmpfiles.d.xml
src/tmpfiles/tmpfiles.c
test/test-systemd-tmpfiles.py

index 3267454f3b23e81ff7d67a8e3934ff0e0ca03785..79d0ff6bddf5657b1624ed98d8e8662a4dddb9db 100644 (file)
@@ -160,8 +160,9 @@ L     /tmp/foobar -    -    -     -   /dev/null</programlisting>
     <refsect2>
       <title>Type</title>
 
-      <para>The type consists of a single letter and optionally an exclamation mark (<literal>!</literal>)
-      minus sign (<literal>-</literal>), and/or equals sign (<literal>=</literal>).</para>
+      <para>The type consists of a single letter and optionally a plus sign (<literal>+</literal>),
+      exclamation mark (<literal>!</literal>), minus sign (<literal>-</literal>), equals sign
+      (<literal>=</literal>) and/or tilde character (<literal>~</literal>).</para>
 
       <para>The following line types are understood:</para>
 
@@ -330,7 +331,7 @@ L     /tmp/foobar -    -    -     -   /dev/null</programlisting>
           exists and is not empty. Instead, the entire copy operation is
           skipped. If the argument is omitted, files from the source directory
           <filename>/usr/share/factory/</filename> with the same name
-          are copied. Does not follow symlinks. Contents of the directories 
+          are copied. Does not follow symlinks. Contents of the directories
           are subject to time based cleanup if the age argument is specified.
           </para></listitem>
         </varlistentry>
@@ -489,6 +490,13 @@ w- /proc/sys/vm/swappiness - - - - 10</programlisting></para>
       be either directories or directory symlinks). For example, if there is a FIFO in place of one of the parent path
       components it will be replaced with a directory.</para>
 
+      <para>If the tilde character (<literal>~</literal>) is used, the argument (i.e. 6th) column is <ulink
+      url="https://www.rfc-editor.org/rfc/rfc4648.html">Base64 decoded</ulink> before use. This modifier is
+      only supported on line types that can write file contents, i.e. <varname>f</varname>,
+      <varname>f+</varname>, <varname>w</varname>. This is useful for writing arbitrary binary data
+      (including newlines and NUL bytes) to files. Note that if this switch is used, the argument is not
+      subject to specifier expansion, neither before nor after Base64 decoding.</para>
+
       <para>Note that for all line types that result in creation of any kind of file node
       (i.e. <varname>f</varname>/<varname>F</varname>,
       <varname>d</varname>/<varname>D</varname>/<varname>v</varname>/<varname>q</varname>/<varname>Q</varname>,
index 0069f444e930c50adaaf1cb5a6c3ca46e0d778cc..06d11f34b9b06e02a19700aa38cb83ff75063a9e 100644 (file)
@@ -36,6 +36,7 @@
 #include "format-util.h"
 #include "fs-util.h"
 #include "glob-util.h"
+#include "hexdecoct.h"
 #include "io-util.h"
 #include "label.h"
 #include "log.h"
@@ -127,6 +128,8 @@ typedef struct Item {
 
         char *path;
         char *argument;
+        void *binary_argument;        /* set if binary data, in which case it takes precedence over 'argument' */
+        size_t binary_argument_size;
         char **xattrs;
 #if HAVE_ACL
         acl_t acl_access;
@@ -461,6 +464,17 @@ static bool unix_socket_alive(const char *fn) {
         return set_contains(unix_sockets, fn);
 }
 
+/* Accessors for the argument in binary format */
+static const void* item_binary_argument(const Item *i) {
+        assert(i);
+        return i->binary_argument ?: i->argument;
+}
+
+static size_t item_binary_argument_size(const Item *i) {
+        assert(i);
+        return i->binary_argument ? i->binary_argument_size : strlen_ptr(i->argument);
+}
+
 static DIR* xopendirat_nomod(int dirfd, const char *path) {
         DIR *dir;
 
@@ -1329,6 +1343,27 @@ static int path_set_attribute(Item *item, const char *path) {
         return fd_set_attribute(item, fd, path, NULL);
 }
 
+static int write_argument_data(Item *i, int fd, const char *path) {
+        int r;
+
+        assert(i);
+        assert(fd >= 0);
+        assert(path);
+
+        if (item_binary_argument_size(i) == 0)
+                return 0;
+
+        assert(item_binary_argument(i));
+
+        log_debug("Writing to \"%s\".", path);
+
+        r = loop_write(fd, item_binary_argument(i), item_binary_argument_size(i), /* do_poll= */ false);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write file \"%s\": %m", path);
+
+        return 0;
+}
+
 static int write_one_file(Item *i, const char *path) {
         _cleanup_close_ int fd = -1, dir_fd = -1;
         _cleanup_free_ char *bn = NULL;
@@ -1336,7 +1371,6 @@ static int write_one_file(Item *i, const char *path) {
 
         assert(i);
         assert(path);
-        assert(i->argument);
         assert(i->type == WRITE_FILE);
 
         r = path_extract_filename(path, &bn);
@@ -1368,11 +1402,10 @@ static int write_one_file(Item *i, const char *path) {
         }
 
         /* 'w' is allowed to write into any kind of files. */
-        log_debug("Writing to \"%s\".", path);
 
-        r = loop_write(fd, i->argument, strlen(i->argument), false);
+        r = write_argument_data(i, fd, path);
         if (r < 0)
-                return log_error_errno(r, "Failed to write file \"%s\": %m", path);
+                return r;
 
         return fd_set_perms(i, fd, path, NULL);
 }
@@ -1432,17 +1465,10 @@ static int create_file(Item *i, const char *path) {
                                                path);
 
                 st = &stbuf;
-        } else {
-
-                log_debug("\"%s\" has been created.", path);
-
-                if (i->argument) {
-                        log_debug("Writing to \"%s\".", path);
-
-                        r = loop_write(fd, i->argument, strlen(i->argument), false);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to write file \"%s\": %m", path);
-                }
+        } else if (item_binary_argument(i)) {
+                r = write_argument_data(i, fd, path);
+                if (r < 0)
+                        return r;
         }
 
         return fd_set_perms(i, fd, path, st);
@@ -1522,12 +1548,10 @@ static int truncate_file(Item *i, const char *path) {
 
         log_debug("\"%s\" has been created.", path);
 
-        if (i->argument) {
-                log_debug("Writing to \"%s\".", path);
-
-                r = loop_write(fd, i->argument, strlen(i->argument), false);
+        if (item_binary_argument(i)) {
+                r = write_argument_data(i, fd, path);
                 if (r < 0)
-                        return log_error_errno(erofs ? -EROFS : r, "Failed to write file %s: %m", path);
+                        return r;
         }
 
         return fd_set_perms(i, fd, path, st);
@@ -2643,6 +2667,7 @@ static void item_free_contents(Item *i) {
         assert(i);
         free(i->path);
         free(i->argument);
+        free(i->binary_argument);
         strv_free(i->xattrs);
 
 #if HAVE_ACL
@@ -2684,7 +2709,8 @@ static bool item_compatible(const Item *a, const Item *b) {
 
         if (takes_ownership(a->type) && takes_ownership(b->type))
                 /* check if the items are the same */
-                return  streq_ptr(a->argument, b->argument) &&
+                return memcmp_nn(item_binary_argument(a), item_binary_argument_size(a),
+                                 item_binary_argument(b), item_binary_argument_size(b)) == 0 &&
 
                         a->uid_set == b->uid_set &&
                         a->uid == b->uid &&
@@ -2953,7 +2979,7 @@ static int parse_line(
         ItemArray *existing;
         OrderedHashmap *h;
         int r, pos;
-        bool append_or_force = false, boot = false, allow_failure = false, try_replace = false;
+        bool append_or_force = false, boot = false, allow_failure = false, try_replace = false, unbase64 = false;
 
         assert(fname);
         assert(line >= 1);
@@ -3024,6 +3050,8 @@ static int parse_line(
                         allow_failure = true;
                 else if (action[pos] == '=' && !try_replace)
                         try_replace = true;
+                else if (action[pos] == '~' && !unbase64)
+                        unbase64 = true;
                 else {
                         *invalid_config = true;
                         return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Unknown modifiers in command '%s'", action);
@@ -3079,6 +3107,11 @@ static int parse_line(
                 break;
 
         case CREATE_SYMLINK:
+                if (unbase64) {
+                        *invalid_config = true;
+                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for symlink targets.");
+                }
+
                 if (!i.argument) {
                         i.argument = path_join("/usr/share/factory", i.path);
                         if (!i.argument)
@@ -3094,11 +3127,15 @@ static int parse_line(
                 break;
 
         case COPY_FILES:
+                if (unbase64) {
+                        *invalid_config = true;
+                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for copy sources.");
+                }
+
                 if (!i.argument) {
                         i.argument = path_join("/usr/share/factory", i.path);
                         if (!i.argument)
                                 return log_oom();
-
                 } else if (!path_is_absolute(i.argument)) {
                         *invalid_config = true;
                         return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Source path '%s' is not absolute.", i.argument);
@@ -3119,6 +3156,11 @@ static int parse_line(
 
         case CREATE_CHAR_DEVICE:
         case CREATE_BLOCK_DEVICE:
+                if (unbase64) {
+                        *invalid_config = true;
+                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for device node creation.");
+                }
+
                 if (!i.argument) {
                         *invalid_config = true;
                         return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Device file requires argument.");
@@ -3134,6 +3176,10 @@ static int parse_line(
 
         case SET_XATTR:
         case RECURSIVE_SET_XATTR:
+                if (unbase64) {
+                        *invalid_config = true;
+                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for extended attributes.");
+                }
                 if (!i.argument) {
                         *invalid_config = true;
                         return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
@@ -3146,6 +3192,10 @@ static int parse_line(
 
         case SET_ACL:
         case RECURSIVE_SET_ACL:
+                if (unbase64) {
+                        *invalid_config = true;
+                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for ACLs.");
+                }
                 if (!i.argument) {
                         *invalid_config = true;
                         return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
@@ -3158,6 +3208,10 @@ static int parse_line(
 
         case SET_ATTRIBUTE:
         case RECURSIVE_SET_ATTRIBUTE:
+                if (unbase64) {
+                        *invalid_config = true;
+                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for file attributes.");
+                }
                 if (!i.argument) {
                         *invalid_config = true;
                         return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
@@ -3187,13 +3241,21 @@ static int parse_line(
         if (!should_include_path(i.path))
                 return 0;
 
-        r = specifier_expansion_from_arg(specifier_table, &i);
-        if (r == -ENXIO)
-                return log_unresolvable_specifier(fname, line);
-        if (r < 0) {
-                if (IN_SET(r, -EINVAL, -EBADSLT))
-                        *invalid_config = true;
-                return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to substitute specifiers in argument: %m");
+        if (unbase64) {
+                if (i.argument) {
+                        r = unbase64mem(i.argument, SIZE_MAX, &i.binary_argument, &i.binary_argument_size);
+                        if (r < 0)
+                                return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to base64 decode specified argument '%s': %m", i.argument);
+                }
+        } else {
+                r = specifier_expansion_from_arg(specifier_table, &i);
+                if (r == -ENXIO)
+                        return log_unresolvable_specifier(fname, line);
+                if (r < 0) {
+                        if (IN_SET(r, -EINVAL, -EBADSLT))
+                                *invalid_config = true;
+                        return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to substitute specifiers in argument: %m");
+                }
         }
 
         if (!empty_or_root(arg_root)) {
index ba42b3fa3702833cada8fc530807aa2d7b95d499..4b3bdf33644b14562bef5474db9e72d8106cf19c 100755 (executable)
@@ -188,6 +188,14 @@ def test_hard_cleanup(*, user):
     label = 'valid_symlink-deep'
     test_content('f= {} - - - - ' + label, label, user=user, subpath='/deep/1/2', path_cb=valid_symlink)
 
+def test_base64():
+    test_line('f~ /tmp/base64-test - - - - UGlmZgpQYWZmClB1ZmYgCg==', user=False, returncode=0)
+
+    with open("/tmp/base64-test", mode='r') as f:
+        d = f.read()
+
+    assert d == "Piff\nPaff\nPuff \n"
+
 if __name__ == '__main__':
     test_invalids(user=False)
     test_invalids(user=True)
@@ -198,3 +206,5 @@ if __name__ == '__main__':
 
     test_hard_cleanup(user=False)
     test_hard_cleanup(user=True)
+
+    test_base64()