]> granicus.if.org Git - php/commitdiff
Fix bug #68526 Implement POSIX Access Control List for UDS
authorRemi Collet <remi@php.net>
Wed, 10 Dec 2014 07:15:21 +0000 (08:15 +0100)
committerRemi Collet <remi@php.net>
Wed, 10 Dec 2014 07:15:21 +0000 (08:15 +0100)
- add --with-fpm-acl build option which allow to manage ACL
on Unix Domain Socket
- add listen.acl_users pool option
- add listen.acl_groups pool option

Keep old behavior (chmod) if option not used or not supported.

sapi/fpm/config.m4
sapi/fpm/fpm/fpm_conf.c
sapi/fpm/fpm/fpm_conf.h
sapi/fpm/fpm/fpm_unix.c
sapi/fpm/fpm/fpm_unix.h
sapi/fpm/fpm/fpm_worker_pool.c
sapi/fpm/fpm/fpm_worker_pool.h
sapi/fpm/php-fpm.conf.in
sapi/fpm/tests/021-uds-acl.phpt [new file with mode: 0644]

index 9c10aa6be284e94b691b4e810d1bcf5f6f3ae402..f87776aa24e7210583e1c83c53f69b4a9cbb7d83 100644 (file)
@@ -583,6 +583,9 @@ if test "$PHP_FPM" != "no"; then
   PHP_ARG_WITH(fpm-systemd,,
   [  --with-fpm-systemd      Activate systemd integration], no, no)
 
+  PHP_ARG_WITH(fpm-acl,,
+  [  --with-fpm-acl          Use POSIX Access Control Lists], no, no)
+
   if test "$PHP_FPM_SYSTEMD" != "no" ; then
     if test -z "$PKG_CONFIG"; then
       AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
@@ -624,6 +627,17 @@ if test "$PHP_FPM" != "no"; then
   else
     php_fpm_systemd=simple
   fi
+
+  if test "$PHP_FPM_ACL" != "no" ; then
+    AC_CHECK_HEADERS([sys/acl.h])
+    AC_CHECK_LIB(acl, acl_free, [
+      PHP_ADD_LIBRARY(acl)
+      AC_DEFINE(HAVE_FPM_ACL, 1, [ POSIX Access Control List ])
+    ],[
+      AC_MSG_ERROR(libacl required not found)
+    ])
+  fi
+
   PHP_SUBST_OLD(php_fpm_systemd)
   AC_DEFINE_UNQUOTED(PHP_FPM_SYSTEMD, "$php_fpm_systemd", [fpm systemd service type])
 
index 1f73bc915c930d16c1a4f4048d7b41dcbf2b0eb6..cca4e1dbb8f1eb81fd3d6f54d324bee8cbe9602a 100644 (file)
@@ -123,6 +123,10 @@ static struct ini_value_parser_s ini_fpm_pool_options[] = {
        { "group",                     &fpm_conf_set_string,      WPO(group) },
        { "listen",                    &fpm_conf_set_string,      WPO(listen_address) },
        { "listen.backlog",            &fpm_conf_set_integer,     WPO(listen_backlog) },
+#ifdef HAVE_FPM_ACL
+       { "listen.acl_users",          &fpm_conf_set_string,      WPO(listen_acl_users) },
+       { "listen.acl_groups",         &fpm_conf_set_string,      WPO(listen_acl_groups) },
+#endif
        { "listen.owner",              &fpm_conf_set_string,      WPO(listen_owner) },
        { "listen.group",              &fpm_conf_set_string,      WPO(listen_group) },
        { "listen.mode",               &fpm_conf_set_string,      WPO(listen_mode) },
@@ -1583,6 +1587,10 @@ static void fpm_conf_dump() /* {{{ */
                zlog(ZLOG_NOTICE, "\tgroup = %s",                      STR2STR(wp->config->group));
                zlog(ZLOG_NOTICE, "\tlisten = %s",                     STR2STR(wp->config->listen_address));
                zlog(ZLOG_NOTICE, "\tlisten.backlog = %d",             wp->config->listen_backlog);
+#ifdef HAVE_FPM_ACL
+               zlog(ZLOG_NOTICE, "\tlisten.acl_users = %s",           STR2STR(wp->config->listen_acl_users));
+               zlog(ZLOG_NOTICE, "\tlisten.acl_groups = %s",          STR2STR(wp->config->listen_acl_groups));
+#endif
                zlog(ZLOG_NOTICE, "\tlisten.owner = %s",               STR2STR(wp->config->listen_owner));
                zlog(ZLOG_NOTICE, "\tlisten.group = %s",               STR2STR(wp->config->listen_group));
                zlog(ZLOG_NOTICE, "\tlisten.mode = %s",                STR2STR(wp->config->listen_mode));
index 12fabe28053719df4a9892113334021ae7b8ffbb..540b22795df3404000cad88b7e90acf4ddd3933d 100644 (file)
@@ -58,6 +58,7 @@ struct fpm_worker_pool_config_s {
        char *group;
        char *listen_address;
        int listen_backlog;
+       /* Using chown */
        char *listen_owner;
        char *listen_group;
        char *listen_mode;
@@ -91,6 +92,11 @@ struct fpm_worker_pool_config_s {
 #ifdef HAVE_APPARMOR
        char *apparmor_hat;
 #endif
+#ifdef HAVE_FPM_ACL
+       /* Using Posix ACL */
+       char *listen_acl_users;
+       char *listen_acl_groups;
+#endif
 };
 
 struct ini_value_parser_s {
index 57707d8f8a2ff97653ec50f60574f0eccb4000f4..f0d45734833a4d6bab8260b9158680f0d79c417b 100644 (file)
 #include <sys/apparmor.h>
 #endif
 
+#ifdef HAVE_SYS_ACL_H
+#include <sys/acl.h>
+#endif
+
 #include "fpm.h"
 #include "fpm_conf.h"
 #include "fpm_cleanup.h"
@@ -35,8 +39,12 @@ size_t fpm_pagesize;
 int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
 {
        struct fpm_worker_pool_config_s *c = wp->config;
+#ifdef HAVE_FPM_ACL
+       int n;
 
        /* uninitialized */
+       wp->socket_acl  = NULL;
+#endif
        wp->socket_uid = -1;
        wp->socket_gid = -1;
        wp->socket_mode = 0660;
@@ -45,6 +53,117 @@ int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
                return 0;
        }
 
+       if (c->listen_mode && *c->listen_mode) {
+               wp->socket_mode = strtoul(c->listen_mode, 0, 8);
+       }
+
+#ifdef HAVE_FPM_ACL
+       /* count the users and groups configured */
+       n = 0;
+       if (c->listen_acl_users && *c->listen_acl_users) {
+               char *p;
+               n++;
+               for (p=strchr(c->listen_acl_users, ',') ; p ; p=strchr(p+1, ',')) {
+                       n++;
+               }
+       }
+       if (c->listen_acl_groups && *c->listen_acl_groups) {
+               char *p;
+               n++;
+               for (p=strchr(c->listen_acl_groups, ',') ; p ; p=strchr(p+1, ',')) {
+                       n++;
+               }
+       }
+       /* if ACL configured */
+       if (n) {
+               acl_t acl;
+               acl_entry_t entry;
+               acl_permset_t perm;
+               char *tmp, *p, *end;
+
+               acl = acl_init(n);
+               if (!acl) {
+                       zlog(ZLOG_SYSERROR, "[pool %s] cannot allocate ACL", wp->config->name);
+                       return -1;
+               }
+               /* Create USER ACL */
+               if (c->listen_acl_users && *c->listen_acl_users) {
+                       struct passwd *pwd;
+
+                       tmp = estrdup(c->listen_acl_users);
+                       for (p=tmp ; p ; p=end) {
+                               if ((end = strchr(p, ','))) {
+                                       *end++ = 0;
+                               }
+                               pwd = getpwnam(p);
+                               if (pwd) {
+                                       zlog(ZLOG_DEBUG, "[pool %s] user '%s' have uid=%d", wp->config->name, p, pwd->pw_uid);
+                               } else {
+                                       zlog(ZLOG_SYSERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, p);
+                                       acl_free(acl);
+                                       efree(tmp);
+                                       return -1;
+                               }
+                               if (0 > acl_create_entry(&acl, &entry) ||
+                                       0 > acl_set_tag_type(entry, ACL_USER) ||
+                                       0 > acl_set_qualifier(entry, &pwd->pw_uid) ||
+                                       0 > acl_get_permset(entry, &perm) ||
+                                       0 > acl_clear_perms (perm) ||
+                                       0 > acl_add_perm (perm, ACL_READ) ||
+                                       0 > acl_add_perm (perm, ACL_WRITE)) {
+                                       zlog(ZLOG_SYSERROR, "[pool %s] cannot create ACL for user '%s'", wp->config->name, p);
+                                       acl_free(acl);
+                                       efree(tmp);
+                                       return -1;
+                               }
+                       }
+                       efree(tmp);
+               }
+               /* Create GROUP ACL */
+               if (c->listen_acl_groups && *c->listen_acl_groups) {
+                       struct group *grp;
+
+                       tmp = estrdup(c->listen_acl_groups);
+                       for (p=tmp ; p ; p=end) {
+                               if ((end = strchr(p, ','))) {
+                                       *end++ = 0;
+                               }
+                               grp = getgrnam(p);
+                               if (grp) {
+                                       zlog(ZLOG_DEBUG, "[pool %s] group '%s' have gid=%d", wp->config->name, p, grp->gr_gid);
+                               } else {
+                                       zlog(ZLOG_SYSERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, p);
+                                       acl_free(acl);
+                                       efree(tmp);
+                                       return -1;
+                               }
+                               if (0 > acl_create_entry(&acl, &entry) ||
+                                       0 > acl_set_tag_type(entry, ACL_GROUP) ||
+                                       0 > acl_set_qualifier(entry, &grp->gr_gid) ||
+                                       0 > acl_get_permset(entry, &perm) ||
+                                       0 > acl_clear_perms (perm) ||
+                                       0 > acl_add_perm (perm, ACL_READ) ||
+                                       0 > acl_add_perm (perm, ACL_WRITE)) {
+                                       zlog(ZLOG_SYSERROR, "[pool %s] cannot create ACL for group '%s'", wp->config->name, p);
+                                       acl_free(acl);
+                                       efree(tmp);
+                                       return -1;
+                               }
+                       }
+                       efree(tmp);
+               }
+               if (c->listen_owner && *c->listen_owner) {
+                       zlog(ZLOG_WARNING, "[pool %s] ACL set, listen.owner = '%s' is ignored", wp->config->name, c->listen_owner);
+               }
+               if (c->listen_group && *c->listen_group) {
+                       zlog(ZLOG_WARNING, "[pool %s] ACL set, listen.group = '%s' is ignored", wp->config->name, c->listen_group);
+               }
+               wp->socket_acl  = acl;
+               return 0;
+       }
+       /* When listen.users and listen.groups not configured, continue with standard right */
+#endif
+
        if (c->listen_owner && *c->listen_owner) {
                struct passwd *pwd;
 
@@ -69,18 +188,54 @@ int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
                wp->socket_gid = grp->gr_gid;
        }
 
-       if (c->listen_mode && *c->listen_mode) {
-               wp->socket_mode = strtoul(c->listen_mode, 0, 8);
-       }
        return 0;
 }
 /* }}} */
 
 int fpm_unix_set_socket_premissions(struct fpm_worker_pool_s *wp, const char *path) /* {{{ */
 {
+#ifdef HAVE_FPM_ACL
+       if (wp->socket_acl) {
+               acl_t aclfile, aclconf;
+               acl_entry_t entryfile, entryconf;
+               int i;
+
+               /* Read the socket ACL */
+               aclconf = wp->socket_acl;
+               aclfile = acl_get_file (path, ACL_TYPE_ACCESS);
+               if (!aclfile) {
+                       zlog(ZLOG_SYSERROR, "[pool %s] failed to read the ACL of the socket '%s'", wp->config->name, path);
+                       return -1;
+               }
+               /* Copy the new ACL entry from config */
+               for (i=ACL_FIRST_ENTRY ; acl_get_entry(aclconf, i, &entryconf) ; i=ACL_NEXT_ENTRY) {
+                       if (0 > acl_create_entry (&aclfile, &entryfile) ||
+                           0 > acl_copy_entry(entryfile, entryconf)) {
+                               zlog(ZLOG_SYSERROR, "[pool %s] failed to add entry to the ACL of the socket '%s'", wp->config->name, path);
+                               acl_free(aclfile);
+                               return -1;
+                       }
+               }
+               /* Write the socket ACL */
+               if (0 > acl_calc_mask (&aclfile) ||
+                       0 > acl_valid (aclfile) ||
+                       0 > acl_set_file (path, ACL_TYPE_ACCESS, aclfile)) {
+                       zlog(ZLOG_SYSERROR, "[pool %s] failed to write the ACL of the socket '%s'", wp->config->name, path);
+                       acl_free(aclfile);
+                       return -1;
+               } else {
+                       zlog(ZLOG_DEBUG, "[pool %s] ACL of the socket '%s' is set", wp->config->name, path);
+               }
+
+               acl_free(aclfile);
+               return 0;
+       }
+       /* When listen.users and listen.groups not configured, continue with standard right */
+#endif
+
        if (wp->socket_uid != -1 || wp->socket_gid != -1) {
                if (0 > chown(path, wp->socket_uid, wp->socket_gid)) {
-                       zlog(ZLOG_SYSERROR, "failed to chown() the socket '%s'", wp->config->listen_address);
+                       zlog(ZLOG_SYSERROR, "[pool %s] failed to chown() the socket '%s'", wp->config->name, wp->config->listen_address);
                        return -1;
                }
        }
@@ -88,6 +243,17 @@ int fpm_unix_set_socket_premissions(struct fpm_worker_pool_s *wp, const char *pa
 }
 /* }}} */
 
+int fpm_unix_free_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
+{
+#ifdef HAVE_FPM_ACL
+       if (wp->socket_acl) {
+               return acl_free(wp->socket_acl);
+       }
+#endif
+       return 0;
+}
+/* }}} */
+
 static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
 {
        struct passwd *pwd;
index b2995ff3e0ab0dddf8b827a4046c9d55c6a27166..a79559f9e6a716e2f5c0d54a345ef8359e6b5a3d 100644 (file)
@@ -9,6 +9,8 @@
 
 int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp);
 int fpm_unix_set_socket_premissions(struct fpm_worker_pool_s *wp, const char *path);
+int fpm_unix_free_socket_premissions(struct fpm_worker_pool_s *wp);
+
 int fpm_unix_init_child(struct fpm_worker_pool_s *wp);
 int fpm_unix_init_main();
 
index ebe1866c8a14644f2fc4c5684a86fc7626043f19..a0022915cdce790b9b5a47728db1f833a5b399b7 100644 (file)
@@ -15,6 +15,7 @@
 #include "fpm_shm.h"
 #include "fpm_scoreboard.h"
 #include "fpm_conf.h"
+#include "fpm_unix.h"
 
 struct fpm_worker_pool_s *fpm_worker_all_pools;
 
@@ -29,6 +30,7 @@ void fpm_worker_pool_free(struct fpm_worker_pool_s *wp) /* {{{ */
        if (wp->home) {
                free(wp->home);
        }
+       fpm_unix_free_socket_premissions(wp);
        free(wp);
 }
 /* }}} */
index 05c993de4e8c1fe8057ce943f07d4403626cb9e0..6b2bc908dc7e827ed58bd3595271892a71b43340 100644 (file)
@@ -42,6 +42,10 @@ struct fpm_worker_pool_s {
        /* for ondemand PM */
        struct fpm_event_s *ondemand_event;
        int socket_event_set;
+
+#ifdef HAVE_FPM_ACL
+       void *socket_acl;
+#endif
 };
 
 struct fpm_worker_pool_s *fpm_worker_pool_alloc();
index 833a4f4f8c1277b2f3283565dd96d0857833257d..0daf1070fb6877cba5825f61fa8839a359f55c5b 100644 (file)
@@ -175,6 +175,11 @@ listen = 127.0.0.1:9000
 ;listen.owner = @php_fpm_user@
 ;listen.group = @php_fpm_group@
 ;listen.mode = 0660
+; When POSIX Access Control Lists are supported you can set them using
+; these options, value is a coma separated list of user/group names.
+; When set, listen.owner and listen.group are ignored
+;listen.acl_users =
+;listen.acl_groups =
  
 ; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect.
 ; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
diff --git a/sapi/fpm/tests/021-uds-acl.phpt b/sapi/fpm/tests/021-uds-acl.phpt
new file mode 100644 (file)
index 0000000..f39c526
--- /dev/null
@@ -0,0 +1,89 @@
+--TEST--
+FPM: Test Unix Domain Socket with Posix ACL
+--SKIPIF--
+<?php
+include "skipif.inc";
+if (!(file_exists('/usr/bin/getfacl') && file_exists('/etc/passwd') && file_exists('/etc/group'))) die ("skip missing getfacl command");
+?>
+--XFAIL--
+Mark as XFAIL because --with-fpm-acl is not enabled in default build
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$socket  = dirname(__FILE__).'/php-fpm.sock';
+
+// Select 3 users and 2 groups known by system (avoid root)
+$users = $groups = [];
+$tmp = file('/etc/passwd');
+for ($i=1 ; $i<=3 ; $i++) {
+       $tab = explode(':', $tmp[$i]);
+       $users[] = $tab[0];
+}
+$users = implode(',', $users);
+$tmp = file('/etc/group');
+for ($i=1 ; $i<=2 ; $i++) {
+       $tab = explode(':', $tmp[$i]);
+       $groups[] = $tab[0];
+}
+$groups = implode(',', $groups);
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = $socket
+listen.acl_users = $users
+listen.acl_groups = $groups
+listen.mode = 0600
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+    fpm_display_log($tail, 2);
+    try {
+               var_dump(strpos(run_request('unix://'.$socket, -1), 'pong'));
+               echo "UDS ok\n";
+       } catch (Exception $e) {
+               echo "UDS error\n";
+       }
+       passthru("/usr/bin/getfacl -cp $socket");
+
+       proc_terminate($fpm);
+    echo stream_get_contents($tail);
+    fclose($tail);
+    proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+int(%d)
+UDS ok
+user::rw-
+user:%s:rw-
+user:%s:rw-
+user:%s:rw-
+group::---
+group:%s:rw-
+group:%s:rw-
+mask::rw-
+other::---
+
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+--CLEAN--
+<?php
+    $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+    @unlink($logfile);
+?>