core: add ConditionUser and ConditionGroup
authorFelipe Sateler <fsateler@gmail.com>
Sat, 8 Apr 2017 23:32:13 +0000 (20:32 -0300)
committerFelipe Sateler <fsateler@gmail.com>
Fri, 26 May 2017 13:42:44 +0000 (09:42 -0400)
This adds two options that are useful for user units. In particular, it
is useful to check ConditionUser=!0 to not start for the root user.

Closes: #5187

man/systemd.unit.xml
src/core/load-fragment-gperf.gperf.m4
src/shared/condition.c
src/shared/condition.h
src/test/test-condition.c

index a7c37a0..cd9deaa 100644 (file)
         <term><varname>ConditionDirectoryNotEmpty=</varname></term>
         <term><varname>ConditionFileNotEmpty=</varname></term>
         <term><varname>ConditionFileIsExecutable=</varname></term>
+        <term><varname>ConditionUser=</varname></term>
+        <term><varname>ConditionGroup=</varname></term>
 
         <!-- We do not document ConditionNull=
              here, as it is not particularly
         whether a certain path exists, is a regular file and marked
         executable.</para>
 
+        <para><varname>ConditionUser=</varname> takes a numeric
+        <literal>UID</literal> or a UNIX user name. This condition
+        may be used to check whether the service manager is running
+        as the given real or effective user. This option is not
+        useful for system services, as the system manager exclusively
+        runs as the root user, and thus the test result is constant.</para>
+
+        <para><varname>ConditionGroup=</varname> is similar
+        to <varname>ConditionUser=</varname> but verifies that the
+        service manager's real or effective group, or any of its
+        auxiliary groups match the specified group or GID.</para>
+
         <para>If multiple conditions are specified, the unit will be
         executed if all of them apply (i.e. a logical AND is applied).
         Condition checks can be prefixed with a pipe symbol (|) in
         <term><varname>AssertDirectoryNotEmpty=</varname></term>
         <term><varname>AssertFileNotEmpty=</varname></term>
         <term><varname>AssertFileIsExecutable=</varname></term>
+        <term><varname>AssertUser=</varname></term>
+        <term><varname>AssertGroup=</varname></term>
 
         <listitem><para>Similar to the <varname>ConditionArchitecture=</varname>,
         <varname>ConditionVirtualization=</varname>, …, condition settings described above, these settings add
index 97adbdd..4f17fa8 100644 (file)
@@ -221,6 +221,8 @@ Unit.ConditionSecurity,          config_parse_unit_condition_string, CONDITION_S
 Unit.ConditionCapability,        config_parse_unit_condition_string, CONDITION_CAPABILITY,          offsetof(Unit, conditions)
 Unit.ConditionHost,              config_parse_unit_condition_string, CONDITION_HOST,                offsetof(Unit, conditions)
 Unit.ConditionACPower,           config_parse_unit_condition_string, CONDITION_AC_POWER,            offsetof(Unit, conditions)
+Unit.ConditionUser,              config_parse_unit_condition_string, CONDITION_USER,                offsetof(Unit, conditions)
+Unit.ConditionGroup,             config_parse_unit_condition_string, CONDITION_GROUP,               offsetof(Unit, conditions)
 Unit.ConditionNull,              config_parse_unit_condition_null,   0,                             offsetof(Unit, conditions)
 Unit.AssertPathExists,           config_parse_unit_condition_path,   CONDITION_PATH_EXISTS,         offsetof(Unit, asserts)
 Unit.AssertPathExistsGlob,       config_parse_unit_condition_path,   CONDITION_PATH_EXISTS_GLOB,    offsetof(Unit, asserts)
@@ -240,6 +242,8 @@ Unit.AssertSecurity,             config_parse_unit_condition_string, CONDITION_S
 Unit.AssertCapability,           config_parse_unit_condition_string, CONDITION_CAPABILITY,          offsetof(Unit, asserts)
 Unit.AssertHost,                 config_parse_unit_condition_string, CONDITION_HOST,                offsetof(Unit, asserts)
 Unit.AssertACPower,              config_parse_unit_condition_string, CONDITION_AC_POWER,            offsetof(Unit, asserts)
+Unit.AssertUser,                 config_parse_unit_condition_string, CONDITION_USER,                offsetof(Unit, asserts)
+Unit.AssertGroup,                config_parse_unit_condition_string, CONDITION_GROUP,               offsetof(Unit, asserts)
 Unit.AssertNull,                 config_parse_unit_condition_null,   0,                             offsetof(Unit, asserts)
 m4_dnl
 Service.PIDFile,                 config_parse_unit_path_printf,      0,                             offsetof(Service, pid_file)
index 0b77d2c..7320b53 100644 (file)
@@ -24,6 +24,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
 
@@ -52,6 +53,7 @@
 #include "stat-util.h"
 #include "string-table.h"
 #include "string-util.h"
+#include "user-util.h"
 #include "util.h"
 #include "virt.h"
 
@@ -138,6 +140,57 @@ static int condition_test_kernel_command_line(Condition *c) {
         return false;
 }
 
+static int condition_test_user(Condition *c) {
+        uid_t id;
+        int r;
+        _cleanup_free_ char *username = NULL;
+        const char *u;
+
+        assert(c);
+        assert(c->parameter);
+        assert(c->type == CONDITION_USER);
+
+        r = parse_uid(c->parameter, &id);
+        if (r >= 0)
+                return id == getuid() || id == geteuid();
+
+        username = getusername_malloc();
+        if (!username)
+                return -ENOMEM;
+
+        if (streq(username, c->parameter))
+                return 1;
+
+        if (getpid() == 1)
+                return streq(c->parameter, "root");
+
+        u = c->parameter;
+        r = get_user_creds(&u, &id, NULL, NULL, NULL);
+        if (r < 0)
+                return 0;
+
+        return id == getuid() || id == geteuid();
+}
+
+static int condition_test_group(Condition *c) {
+        gid_t id;
+        int r;
+
+        assert(c);
+        assert(c->parameter);
+        assert(c->type == CONDITION_GROUP);
+
+        r = parse_gid(c->parameter, &id);
+        if (r >= 0)
+                return in_gid(id);
+
+        /* Avoid any NSS lookups if we are PID1 */
+        if (getpid() == 1)
+                return streq(c->parameter, "root");
+
+        return in_group(c->parameter) > 0;
+}
+
 static int condition_test_virtualization(Condition *c) {
         int b, v;
 
@@ -475,6 +528,8 @@ int condition_test(Condition *c) {
                 [CONDITION_ARCHITECTURE] = condition_test_architecture,
                 [CONDITION_NEEDS_UPDATE] = condition_test_needs_update,
                 [CONDITION_FIRST_BOOT] = condition_test_first_boot,
+                [CONDITION_USER] = condition_test_user,
+                [CONDITION_GROUP] = condition_test_group,
                 [CONDITION_NULL] = condition_test_null,
         };
 
@@ -538,6 +593,8 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
         [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty",
         [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty",
         [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable",
+        [CONDITION_USER] = "ConditionUser",
+        [CONDITION_GROUP] = "ConditionGroup",
         [CONDITION_NULL] = "ConditionNull"
 };
 
@@ -562,6 +619,8 @@ static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
         [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty",
         [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty",
         [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable",
+        [CONDITION_USER] = "AssertUser",
+        [CONDITION_GROUP] = "AssertGroup",
         [CONDITION_NULL] = "AssertNull"
 };
 
index bdda04b..d0b592b 100644 (file)
@@ -49,6 +49,9 @@ typedef enum ConditionType {
 
         CONDITION_NULL,
 
+        CONDITION_USER,
+        CONDITION_GROUP,
+
         _CONDITION_TYPE_MAX,
         _CONDITION_TYPE_INVALID = -1
 } ConditionType;
index dd985f5..790716e 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
 #include "sd-id128.h"
 
 #include "alloc-util.h"
@@ -34,6 +38,7 @@
 #include "strv.h"
 #include "virt.h"
 #include "util.h"
+#include "user-util.h"
 
 static void test_condition_test_path(void) {
         Condition *condition;
@@ -323,6 +328,140 @@ static void test_condition_test_virtualization(void) {
         }
 }
 
+static void test_condition_test_user(void) {
+        Condition *condition;
+        char* uid;
+        char* username;
+        int r;
+
+        condition = condition_new(CONDITION_USER, "garbage oifdsjfoidsjoj", false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionUser=garbage → %i", r);
+        assert_se(r == 0);
+        condition_free(condition);
+
+        assert_se(asprintf(&uid, "%"PRIu32, UINT32_C(0xFFFF)) > 0);
+        condition = condition_new(CONDITION_USER, uid, false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionUser=%s → %i", uid, r);
+        assert_se(r == 0);
+        condition_free(condition);
+        free(uid);
+
+        assert_se(asprintf(&uid, "%u", (unsigned)getuid()) > 0);
+        condition = condition_new(CONDITION_USER, uid, false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionUser=%s → %i", uid, r);
+        assert_se(r > 0);
+        condition_free(condition);
+        free(uid);
+
+        assert_se(asprintf(&uid, "%u", (unsigned)getuid()+1) > 0);
+        condition = condition_new(CONDITION_USER, uid, false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionUser=%s → %i", uid, r);
+        assert_se(r == 0);
+        condition_free(condition);
+        free(uid);
+
+        username = getusername_malloc();
+        assert_se(username);
+        condition = condition_new(CONDITION_USER, username, false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionUser=%s → %i", username, r);
+        assert_se(r > 0);
+        condition_free(condition);
+        free(username);
+
+        username = (char*)(geteuid() == 0 ? NOBODY_USER_NAME : "root");
+        condition = condition_new(CONDITION_USER, username, false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionUser=%s → %i", username, r);
+        assert_se(r == 0);
+        condition_free(condition);
+}
+
+static void test_condition_test_group(void) {
+        Condition *condition;
+        char* gid;
+        char* groupname;
+        gid_t *gids, max_gid;
+        int ngroups_max, r, i;
+
+        assert_se(0 < asprintf(&gid, "%u", UINT32_C(0xFFFF)));
+        condition = condition_new(CONDITION_GROUP, gid, false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionGroup=%s → %i", gid, r);
+        assert_se(r == 0);
+        condition_free(condition);
+        free(gid);
+
+        assert_se(0 < asprintf(&gid, "%u", getgid()));
+        condition = condition_new(CONDITION_GROUP, gid, false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionGroup=%s → %i", gid, r);
+        assert_se(r > 0);
+        condition_free(condition);
+        free(gid);
+
+        ngroups_max = sysconf(_SC_NGROUPS_MAX);
+        assert(ngroups_max > 0);
+
+        gids = alloca(sizeof(gid_t) * ngroups_max);
+
+        r = getgroups(ngroups_max, gids);
+        assert(r >= 0);
+
+        max_gid = getgid();
+        for (i = 0; i < r; i++) {
+                assert_se(0 < asprintf(&gid, "%u", gids[i]));
+                condition = condition_new(CONDITION_GROUP, gid, false, false);
+                assert_se(condition);
+                r = condition_test(condition);
+                log_info("ConditionGroup=%s → %i", gid, r);
+                assert_se(r > 0);
+                condition_free(condition);
+                free(gid);
+                max_gid = gids[i] > max_gid ? gids[i] : max_gid;
+
+                groupname = gid_to_name(gids[i]);
+                assert_se(groupname);
+                condition = condition_new(CONDITION_GROUP, groupname, false, false);
+                assert_se(condition);
+                r = condition_test(condition);
+                log_info("ConditionGroup=%s → %i", groupname, r);
+                assert_se(r > 0);
+                condition_free(condition);
+                free(groupname);
+                max_gid = gids[i] > max_gid ? gids[i] : max_gid;
+        }
+
+        assert_se(0 < asprintf(&gid, "%u", max_gid+1));
+        condition = condition_new(CONDITION_GROUP, gid, false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionGroup=%s → %i", gid, r);
+        assert_se(r == 0);
+        condition_free(condition);
+        free(gid);
+
+        groupname = (char*)(geteuid() == 0 ? NOBODY_GROUP_NAME : "root");
+        condition = condition_new(CONDITION_GROUP, groupname, false, false);
+        assert_se(condition);
+        r = condition_test(condition);
+        log_info("ConditionGroup=%s → %i", groupname, r);
+        assert_se(r == 0);
+        condition_free(condition);
+}
+
 int main(int argc, char *argv[]) {
         log_set_max_level(LOG_DEBUG);
         log_parse_environment();
@@ -336,6 +475,8 @@ int main(int argc, char *argv[]) {
         test_condition_test_null();
         test_condition_test_security();
         test_condition_test_virtualization();
+        test_condition_test_user();
+        test_condition_test_group();
 
         return 0;
 }