2
0
Fork 0
lighttpd2/src/angel/angel_plugin_core.c

990 lines
29 KiB
C

#include <lighttpd/angel_plugin_core.h>
#include <lighttpd/angel_config_parser.h>
#include <lighttpd/ip_parsers.h>
#include <fnmatch.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
typedef struct listen_socket listen_socket;
typedef struct listen_ref_resource listen_ref_resource;
#ifndef DEFAULT_LIBEXECDIR
# define DEFAULT_LIBEXECDIR "/usr/local/lib/lighttpd2"
#endif
struct listen_socket {
gint refcount;
liSocketAddress addr;
int fd;
};
struct listen_ref_resource {
liInstanceResource ires;
listen_socket *sock;
};
static liValue* core_parse_check_parameter_string(liValue *value, const char* item, GError **err) {
value = li_value_get_single_argument(value);
if (LI_VALUE_STRING != li_value_type(value)) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"%s: expecting a string as parameter", item);
return NULL;
}
return value;
}
/* destroys value, extracting the contained string into *target */
static gboolean core_parse_store_string(liValue *value, const char* item, GString** target, GError **err) {
if (NULL != *target) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"%s: already specified, can only be used once", item);
return FALSE;
}
if (NULL == (value = core_parse_check_parameter_string(value, item, err))) return FALSE;
*target = li_value_extract_string(value);
return TRUE;
}
/* destroys value, adding the contained strings (char*) to target */
static gboolean core_parse_store_string_list(liValue *value, const char* item, GPtrArray* list, GError **err) {
value = li_value_get_single_argument(value);
if (LI_VALUE_STRING == li_value_type(value)) {
li_value_wrap_in_list(value);
}
else if (LI_VALUE_LIST != li_value_type(value)) goto parameter_type_error;
LI_VALUE_FOREACH(entry, value)
if (LI_VALUE_STRING != li_value_type(entry)) goto parameter_type_error;
g_ptr_array_add(list, g_string_free(li_value_extract_string(entry), FALSE));
LI_VALUE_END_FOREACH()
return TRUE;
parameter_type_error:
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"%s: expecting string list as parameter", item);
return FALSE;
}
/* extract the contained integer into *target */
static gboolean core_parse_store_integer(liValue *value, const char* item, gint64* target, GError **err) {
value = li_value_get_single_argument(value);
if (LI_VALUE_NUMBER != li_value_type(value)) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"%s: expecting a number as parameter", item);
return FALSE;
}
*target = value->data.number;
return TRUE;
}
static gboolean core_parse_user(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
struct passwd *pwd;
GString *user;
UNUSED(srv);
if (!core_parse_store_string(value, "user", &pc->parsing.user, err)) return FALSE;
user = pc->parsing.user;
if (NULL == (pwd = getpwnam(user->str))) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"user: couldn't find user '%s' ", user->str);
return FALSE;
}
if (0 == pwd->pw_uid) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"user: will not changed to uid 0");
return FALSE;
}
if (0 == pwd->pw_gid) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"user: will not changed to gid 0");
return FALSE;
}
pc->parsing.user_uid = pwd->pw_uid;
pc->parsing.user_gid = pwd->pw_gid;
return TRUE;
}
static gboolean core_parse_group(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
struct group *grp;
GString *group;
UNUSED(srv);
if (!core_parse_store_string(value, "group", &pc->parsing.group, err)) return FALSE;
group = pc->parsing.group;
if (NULL == (grp = getgrnam(group->str))) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"group: couldn't find group '%s' ", group->str);
return FALSE;
}
if (0 == grp->gr_gid) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"group: will not changed to gid 0");
return FALSE;
}
pc->parsing.group_gid = grp->gr_gid;
return TRUE;
}
static gboolean core_parse_binary(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
return core_parse_store_string(value, "binary", &pc->parsing.binary, err);
}
static gboolean core_parse_config(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
if (NULL != pc->parsing.luaconfig) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"config: already specified luaconfig");
return FALSE;
}
return core_parse_store_string(value, "config", &pc->parsing.config, err);
}
static gboolean core_parse_luaconfig(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
if (NULL != pc->parsing.config) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"luaconfig: already specified config");
return FALSE;
}
return core_parse_store_string(value, "luaconfig", &pc->parsing.luaconfig, err);
}
static gboolean core_parse_modules_path(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
return core_parse_store_string(value, "modules_path", &pc->parsing.modules_path, err);
}
static void add_env(GPtrArray *env, const char *key, size_t keylen, const char *value, size_t valuelen) {
gchar* entry = g_malloc(keylen + 1 /* "=" */ + valuelen + 1 /* \0 */);
memcpy(entry, key, keylen);
entry[keylen] = '=';
memcpy(entry + keylen + 1, value, valuelen);
entry[keylen + valuelen + 1] = '\0';
g_ptr_array_add(env, entry);
}
static gboolean core_parse_env(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
value = li_value_get_single_argument(value);
if (LI_VALUE_LIST != li_value_type(value)) goto parameter_type_error;
if (li_value_list_has_len(value, 2) && LI_VALUE_STRING == li_value_list_type_at(value, 0) && LI_VALUE_STRING == li_value_list_type_at(value, 1)) {
if (NULL == strchr(li_value_list_at(value, 0)->data.string->str, '=')) {
/* no '=' in first string: single key => value pair; otherwise a list with two entries ['foo=x', 'bar=y'] */
li_value_wrap_in_list(value);
}
}
LI_VALUE_FOREACH(entry, value)
if (LI_VALUE_STRING == li_value_type(entry)) {
g_ptr_array_add(pc->parsing.env, g_string_free(li_value_extract_string(entry), FALSE));
} else {
liValue *key = li_value_list_at(entry, 0);
liValue *val = li_value_list_at(entry, 1);
if (!li_value_list_has_len(entry, 2) || LI_VALUE_STRING != li_value_type(key) || LI_VALUE_STRING != li_value_type(val)) goto parameter_type_error;
add_env(pc->parsing.env, GSTR_LEN(key->data.string), GSTR_LEN(val->data.string));
}
LI_VALUE_END_FOREACH()
return TRUE;
parameter_type_error:
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"env: expecting key-value/string list as parameter");
return FALSE;
}
static gboolean core_parse_copy_env(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
value = li_value_get_single_argument(value);
if (LI_VALUE_STRING == li_value_type(value)) {
li_value_wrap_in_list(value);
}
else if (LI_VALUE_LIST != li_value_type(value)) goto parameter_type_error;
LI_VALUE_FOREACH(entry, value)
const char *val;
size_t vallen;
if (LI_VALUE_STRING != li_value_type(entry)) goto parameter_type_error;
val = getenv(entry->data.string->str);
if (NULL == val) continue;
vallen = strlen(val);
add_env(pc->parsing.env, GSTR_LEN(entry->data.string), val, vallen);
LI_VALUE_END_FOREACH()
return TRUE;
parameter_type_error:
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"copy_env: expecting string list as parameter");
return FALSE;
}
static gboolean core_parse_wrapper(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
return core_parse_store_string_list(value, "wrapper", pc->parsing.wrapper, err);
}
static gboolean core_parse_max_core_file_size(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
if (-1 != pc->parsing.rlim_core) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"max_core_file_size: already specified");
return FALSE;
}
return core_parse_store_integer(value, "max_core_file_size", &pc->parsing.rlim_core, err);
}
static gboolean core_parse_max_open_files(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
if (-1 != pc->parsing.rlim_nofile) {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"max_open_files: already specified");
return FALSE;
}
return core_parse_store_integer(value, "max_open_files", &pc->parsing.rlim_nofile, err);
}
static void core_listen_mask_free(liPluginCoreListenMask *mask) {
if (NULL == mask) return;
switch (mask->type) {
case LI_PLUGIN_CORE_LISTEN_MASK_IPV4:
case LI_PLUGIN_CORE_LISTEN_MASK_IPV6:
break;
case LI_PLUGIN_CORE_LISTEN_MASK_UNIX:
g_string_free(mask->value.unix_socket.path, TRUE);
break;
}
g_slice_free(liPluginCoreListenMask, mask);
}
static gboolean core_parse_allow_listen(liServer *srv, liPlugin *p, liValue *value, GError **err) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
value = li_value_get_single_argument(value);
if (LI_VALUE_LIST != li_value_type(value)) {
li_value_wrap_in_list(value);
}
LI_VALUE_FOREACH(entry, value)
GString *s;
liPluginCoreListenMask *mask;
if (LI_VALUE_STRING != li_value_type(entry)) goto parameter_type_error;
s = entry->data.string;
mask = g_slice_new0(liPluginCoreListenMask);
if (li_string_prefix(s, CONST_STR_LEN("unix:/"))) {
mask->type = LI_PLUGIN_CORE_LISTEN_MASK_UNIX;
mask->value.unix_socket.path = li_value_extract_string(entry);
g_string_erase(mask->value.unix_socket.path, 0, 5); /* remove "unix:" prefix */
}
else if (li_parse_ipv4(s->str, &mask->value.ipv4.addr, &mask->value.ipv4.networkmask, &mask->value.ipv4.port)) {
mask->type = LI_PLUGIN_CORE_LISTEN_MASK_IPV4;
}
else if (li_parse_ipv6(s->str, mask->value.ipv6.addr, &mask->value.ipv6.network, &mask->value.ipv6.port)) {
mask->type = LI_PLUGIN_CORE_LISTEN_MASK_IPV6;
}
else {
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"allow_listen: couldn't parse socket address mask '%s'", s->str);
g_slice_free(liPluginCoreListenMask, mask);
return FALSE;
}
g_ptr_array_add(pc->parsing.listen_masks, mask);
LI_VALUE_END_FOREACH()
return TRUE;
parameter_type_error:
g_set_error(err, LI_ANGEL_CONFIG_PARSER_ERROR, LI_ANGEL_CONFIG_PARSER_ERROR_PARSE,
"allow_listen: expecting string list as parameter");
return FALSE;
}
static const liPluginItem core_items[] = {
{ "user", core_parse_user },
{ "group", core_parse_group },
{ "binary", core_parse_binary },
{ "config", core_parse_config },
{ "luaconfig", core_parse_luaconfig },
{ "modules_path", core_parse_modules_path },
{ "wrapper", core_parse_wrapper },
{ "env", core_parse_env },
{ "copy_env", core_parse_copy_env },
{ "max_core_file_size", core_parse_max_core_file_size },
{ "max_open_files", core_parse_max_open_files },
{ "allow_listen", core_parse_allow_listen },
{ NULL, NULL }
};
#define INIT_STR(N) /* GString, init to NULL */ \
if (NULL != pc->parsing.N) { \
g_string_free(pc->parsing.N, TRUE); \
pc->parsing.N = NULL; \
}
#define INIT_STR_LIST(N) /* char* ptr array, init to empty array */ \
if (NULL != pc->parsing.N) { \
GPtrArray *a_ ## N = pc->parsing.N; \
guint i_ ## N; \
for (i_ ## N = 0; i_ ## N < a_ ## N->len; ++i_ ## N) { \
g_free(g_ptr_array_index(a_ ## N, i_ ## N)); \
} \
g_ptr_array_set_size(a_ ## N, 0); \
} else { \
pc->parsing.N = g_ptr_array_new(); \
}
static void core_parse_init(liServer *srv, liPlugin *p) {
liPluginCoreConfig *pc = p->data;
UNUSED(srv);
INIT_STR_LIST(env);
INIT_STR(user);
pc->parsing.user_uid = (uid_t) -1;
pc->parsing.user_gid = (gid_t) -1;
INIT_STR(group);
pc->parsing.group_gid = (gid_t) -1;
INIT_STR(binary);
INIT_STR(config);
INIT_STR(luaconfig);
INIT_STR(modules_path);
INIT_STR_LIST(wrapper);
pc->parsing.rlim_core = pc->parsing.rlim_nofile = -1;
if (NULL != pc->parsing.instconf) {
li_instance_conf_release(pc->parsing.instconf);
pc->parsing.instconf = NULL;
}
if (NULL != pc->parsing.listen_masks) {
GPtrArray *a = pc->parsing.listen_masks;
guint i;
for (i = 0; i < a->len; ++i) {
core_listen_mask_free(g_ptr_array_index(a, i));
}
g_ptr_array_set_size(a, 0);
} else {
pc->parsing.listen_masks = g_ptr_array_new();
}
}
static gboolean core_check(liServer *srv, liPlugin *p, GError **err) {
GPtrArray *cmd;
GString *user;
gchar **cmdarr, **envarr;
liPluginCoreConfig *pc = p->data;
gid_t gid = ((gid_t)-1 != pc->parsing.group_gid) ? pc->parsing.group_gid : pc->parsing.user_gid;
UNUSED(srv);
UNUSED(err);
cmd = pc->parsing.wrapper;
pc->parsing.wrapper = g_ptr_array_new();
if (NULL != pc->parsing.binary) {
g_ptr_array_add(cmd, g_string_free(pc->parsing.binary, FALSE));
pc->parsing.binary = NULL;
} else {
g_ptr_array_add(cmd, g_strndup(CONST_STR_LEN(DEFAULT_LIBEXECDIR "/lighttpd2-worker")));
}
g_ptr_array_add(cmd, g_strndup(CONST_STR_LEN("--angel")));
g_ptr_array_add(cmd, g_strndup(CONST_STR_LEN("-c")));
if (NULL != pc->parsing.config) {
g_ptr_array_add(cmd, g_string_free(pc->parsing.config, FALSE));
pc->parsing.config = NULL;
} else if (NULL != pc->parsing.luaconfig) {
g_ptr_array_add(cmd, g_string_free(pc->parsing.luaconfig, FALSE));
pc->parsing.luaconfig = NULL;
g_ptr_array_add(cmd, g_strndup(CONST_STR_LEN("-l")));
} else {
g_ptr_array_add(cmd, g_strndup(CONST_STR_LEN("/etc/lighttpd2/lighttpd.conf")));
}
if (NULL != pc->parsing.modules_path) {
g_ptr_array_add(cmd, g_strndup(CONST_STR_LEN("-m")));
g_ptr_array_add(cmd, g_string_free(pc->parsing.modules_path, FALSE));
pc->parsing.modules_path = NULL;
}
g_ptr_array_add(cmd, NULL);
g_ptr_array_add(pc->parsing.env, NULL);
cmdarr = (gchar**) g_ptr_array_free(cmd, FALSE);
envarr = (gchar**) g_ptr_array_free(pc->parsing.env, FALSE);
pc->parsing.env = g_ptr_array_new();
user = pc->parsing.user;
pc->parsing.user = NULL;
pc->parsing.instconf = li_instance_conf_new(cmdarr, envarr, user, pc->parsing.user_uid, gid,
pc->parsing.rlim_core, pc->parsing.rlim_nofile);
return TRUE;
}
static listen_socket* listen_new_socket(liSocketAddress *addr, int fd) {
listen_socket *sock = g_slice_new0(listen_socket);
sock->refcount = 0;
sock->addr = *addr;
sock->fd = fd;
return sock;
}
static void listen_socket_acquire(listen_socket *sock) {
g_atomic_int_inc(&sock->refcount);
}
static void listen_ref_release(liServer *srv, liInstance *i, liPlugin *p, liInstanceResource *res) {
listen_ref_resource *ref = res->data;
listen_socket *sock = ref->sock;
UNUSED(i);
UNUSED(srv);
LI_FORCE_ASSERT(g_atomic_int_get(&sock->refcount) > 0);
if (g_atomic_int_dec_and_test(&sock->refcount)) {
liPluginCoreConfig *config = (liPluginCoreConfig*) p->data;
g_hash_table_remove(config->listen_sockets, &sock->addr);
}
g_slice_free(listen_ref_resource, ref);
}
static void _listen_socket_free(gpointer ptr) {
listen_socket *sock = ptr;
li_sockaddr_clear(&sock->addr);
close(sock->fd);
g_slice_free(listen_socket, sock);
}
static void listen_socket_add(liInstance *i, liPlugin *p, listen_socket *sock) {
listen_ref_resource *ref = g_slice_new0(listen_ref_resource);
listen_socket_acquire(sock);
ref->sock = sock;
li_instance_add_resource(i, &ref->ires, listen_ref_release, p, ref);
}
static gboolean listen_check_acl(liServer *srv, liPluginCoreConfig *config, liSocketAddress *addr) {
guint i;
liPluginCoreListenMask *mask;
switch (addr->addr->plain.sa_family) {
case AF_INET: {
struct sockaddr_in *ipv4 = &addr->addr->ipv4;
guint port = ntohs(ipv4->sin_port);
if (config->listen_masks->len) {
for (i = 0; i < config->listen_masks->len; i++) {
mask = g_ptr_array_index(config->listen_masks, i);
switch (mask->type) {
case LI_PLUGIN_CORE_LISTEN_MASK_IPV4:
if (!li_ipv4_in_ipv4_net(ipv4->sin_addr.s_addr, mask->value.ipv4.addr, mask->value.ipv4.networkmask)) continue;
if ((mask->value.ipv4.port != port) && (mask->value.ipv4.port != 0 || (port != 80 && port != 443))) continue;
return TRUE;
/* strict matches only, no ipv4 in (ipv4-mapped) ipv6 */
default:
continue;
}
}
return FALSE;
} else {
return (port == 80 || port == 443);
}
} break;
#ifdef HAVE_IPV6
case AF_INET6: {
struct sockaddr_in6 *ipv6 = &addr->addr->ipv6;
guint port = ntohs(ipv6->sin6_port);
if (config->listen_masks->len) {
for (i = 0; i < config->listen_masks->len; i++) {
mask = g_ptr_array_index(config->listen_masks, i);
switch (mask->type) {
/* strict matches only, no (ipv4-mapped) ipv6 in ipv4 */
case LI_PLUGIN_CORE_LISTEN_MASK_IPV6:
if (!li_ipv6_in_ipv6_net(ipv6->sin6_addr.s6_addr, mask->value.ipv6.addr, mask->value.ipv6.network)) continue;
if ((mask->value.ipv6.port != port) && (mask->value.ipv6.port != 0 || (port != 80 && port != 443))) continue;
return TRUE;
default:
continue;
}
}
return FALSE;
} else {
return (port == 80 || port == 443);
}
} break;
#endif
#ifdef HAVE_SYS_UN_H
case AF_UNIX: {
if (config->listen_masks->len) {
const gchar *fname = addr->addr->un.sun_path;
for (i = 0; i < config->listen_masks->len; i++) {
mask = g_ptr_array_index(config->listen_masks, i);
switch (mask->type) {
case LI_PLUGIN_CORE_LISTEN_MASK_UNIX:
if (fnmatch(mask->value.unix_socket.path->str, fname, FNM_PERIOD | FNM_PATHNAME)) continue;
return TRUE;
default:
continue;
}
}
return FALSE;
} else {
return FALSE; /* don't allow unix by default */
}
} break;
#endif
default:
ERROR(srv, "Address family %i not supported", addr->addr->plain.sa_family);
break;
}
return FALSE;
}
static int do_listen(liServer *srv, liSocketAddress *addr, GString *str) {
int s, v;
GString *ipv6_str;
switch (addr->addr->plain.sa_family) {
case AF_INET:
if (-1 == (s = socket(AF_INET, SOCK_STREAM, 0))) {
ERROR(srv, "Couldn't open socket: %s", g_strerror(errno));
return -1;
}
v = 1;
if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v))) {
close(s);
ERROR(srv, "Couldn't setsockopt(SO_REUSEADDR): %s", g_strerror(errno));
return -1;
}
if (-1 == bind(s, &addr->addr->plain, addr->len)) {
close(s);
ERROR(srv, "Couldn't bind socket to '%s': %s", str->str, g_strerror(errno));
return -1;
}
#ifdef TCP_FASTOPEN
v = 1000;
setsockopt(s, SOL_TCP, TCP_FASTOPEN, &v, sizeof(v));
#endif
if (-1 == listen(s, 1000)) {
close(s);
ERROR(srv, "Couldn't listen on '%s': %s", str->str, g_strerror(errno));
return -1;
}
DEBUG(srv, "listen to ipv4: '%s' (port: %d)", str->str, ntohs(addr->addr->ipv4.sin_port));
return s;
#ifdef HAVE_IPV6
case AF_INET6:
ipv6_str = g_string_sized_new(0);
li_ipv6_tostring(ipv6_str, addr->addr->ipv6.sin6_addr.s6_addr);
if (-1 == (s = socket(AF_INET6, SOCK_STREAM, 0))) {
ERROR(srv, "Couldn't open socket: %s", g_strerror(errno));
g_string_free(ipv6_str, TRUE);
return -1;
}
v = 1;
if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v))) {
close(s);
ERROR(srv, "Couldn't setsockopt(SO_REUSEADDR): %s", g_strerror(errno));
g_string_free(ipv6_str, TRUE);
return -1;
}
if (-1 == setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &v, sizeof(v))) {
close(s);
ERROR(srv, "Couldn't setsockopt(IPV6_V6ONLY): %s", g_strerror(errno));
g_string_free(ipv6_str, TRUE);
return -1;
}
if (-1 == bind(s, &addr->addr->plain, addr->len)) {
close(s);
ERROR(srv, "Couldn't bind socket to '%s': %s", ipv6_str->str, g_strerror(errno));
g_string_free(ipv6_str, TRUE);
return -1;
}
#ifdef TCP_FASTOPEN
v = 1000;
setsockopt(s, SOL_TCP, TCP_FASTOPEN, &v, sizeof(v));
#endif
if (-1 == listen(s, 1000)) {
close(s);
ERROR(srv, "Couldn't listen on '%s': %s", ipv6_str->str, g_strerror(errno));
g_string_free(ipv6_str, TRUE);
return -1;
}
DEBUG(srv, "listen to ipv6: '%s' (port: %d)", ipv6_str->str, ntohs(addr->addr->ipv6.sin6_port));
g_string_free(ipv6_str, TRUE);
return s;
#endif
#ifdef HAVE_SYS_UN_H
case AF_UNIX:
if (-1 == unlink(addr->addr->un.sun_path)) {
switch (errno) {
case ENOENT:
break;
default:
ERROR(srv, "removing old socket '%s' failed: %s\n", str->str, g_strerror(errno));
return -1;
}
}
if (-1 == (s = socket(AF_UNIX, SOCK_STREAM, 0))) {
ERROR(srv, "Couldn't open socket: %s", g_strerror(errno));
return -1;
}
if (-1 == bind(s, &addr->addr->plain, addr->len)) {
close(s);
ERROR(srv, "Couldn't bind socket to '%s': %s", str->str, g_strerror(errno));
return -1;
}
if (-1 == listen(s, 1000)) {
close(s);
ERROR(srv, "Couldn't listen on '%s': %s", str->str, g_strerror(errno));
return -1;
}
DEBUG(srv, "listen to unix socket: '%s'", str->str);
return s;
#endif
default:
ERROR(srv, "Address family %i not supported", addr->addr->plain.sa_family);
break;
}
return -1;
}
static void core_listen(liServer *srv, liPlugin *p, liInstance *i, gint32 id, GString *data) {
GError *err = NULL;
gint fd;
GArray *fds;
liPluginCoreConfig *config = (liPluginCoreConfig*) p->data;
liSocketAddress addr;
listen_socket *sock;
/* DEBUG(srv, "core_listen(%i) '%s'", id, data->str); */
if (-1 == id) return; /* ignore simple calls */
addr = li_sockaddr_from_string(data, 80);
if (!addr.addr) {
GString *error = g_string_sized_new(0);
g_string_printf(error, "Invalid socket address: '%s'", data->str);
if (!li_angel_send_result(i->acon, id, error, NULL, NULL, &err)) {
ERROR(srv, "Couldn't send result: %s", err->message);
g_error_free(err);
}
return;
}
if (!listen_check_acl(srv, config, &addr)) {
GString *error = g_string_sized_new(0);
li_sockaddr_clear(&addr);
g_string_printf(error, "Socket address not allowed: '%s'", data->str);
if (!li_angel_send_result(i->acon, id, error, NULL, NULL, &err)) {
ERROR(srv, "Couldn't send result: %s", err->message);
g_error_free(err);
}
return;
}
if (NULL == (sock = g_hash_table_lookup(config->listen_sockets, &addr))) {
fd = do_listen(srv, &addr, data);
if (-1 == fd) {
GString *error = g_string_sized_new(0);
li_sockaddr_clear(&addr);
g_string_printf(error, "Couldn't listen to '%s'", data->str);
if (!li_angel_send_result(i->acon, id, error, NULL, NULL, &err)) {
ERROR(srv, "Couldn't send result: %s", err->message);
g_error_free(err);
}
return;
}
li_fd_init(fd);
sock = listen_new_socket(&addr, fd);
g_hash_table_insert(config->listen_sockets, &sock->addr, sock);
} else {
li_sockaddr_clear(&addr);
}
listen_socket_add(i, p, sock);
fd = dup(sock->fd);
if (-1 == fd) {
/* socket ref will be released when instance is released */
GString *error = g_string_sized_new(0);
g_string_printf(error, "Couldn't duplicate fd");
if (!li_angel_send_result(i->acon, id, error, NULL, NULL, &err)) {
ERROR(srv, "Couldn't send result: %s", err->message);
g_error_free(err);
}
return;
}
fds = g_array_new(FALSE, FALSE, sizeof(int));
g_array_append_val(fds, fd);
if (!li_angel_send_result(i->acon, id, NULL, NULL, fds, &err)) {
ERROR(srv, "Couldn't send result: %s", err->message);
g_error_free(err);
return;
}
}
static void core_reached_state(liServer *srv, liPlugin *p, liInstance *i, gint32 id, GString *data) {
UNUSED(srv);
UNUSED(p);
UNUSED(id);
if (0 == strcmp(data->str, "suspended")) {
li_instance_state_reached(i, LI_INSTANCE_SUSPENDED);
} else if (0 == strcmp(data->str, "warmup")) {
li_instance_state_reached(i, LI_INSTANCE_WARMUP);
} else if (0 == strcmp(data->str, "running")) {
li_instance_state_reached(i, LI_INSTANCE_RUNNING);
} else if (0 == strcmp(data->str, "suspending")) {
li_instance_state_reached(i, LI_INSTANCE_SUSPENDING);
}
}
static void core_log_open_file(liServer *srv, liPlugin *p, liInstance *i, gint32 id, GString *data) {
GError *err = NULL;
int fd = -1;
GArray *fds;
UNUSED(p);
DEBUG(srv, "core_log_open_file(%i) '%s'", id, data->str);
if (-1 == id) return; /* ignore simple calls */
li_path_simplify(data);
/* TODO: make path configurable */
if (g_str_has_prefix(data->str, "/var/log/lighttpd2/")) {
/* files can be read by everyone. if you don't like that, restrict access on the directory */
/* if you need group write access for a specific group, use chmod g+s on the directory */
/* "maybe-todo": add options for mode/owner/group */
fd = open(data->str, O_RDWR | O_CREAT | O_APPEND, 0664);
if (-1 == fd) {
int e = errno;
GString *error = g_string_sized_new(0);
g_string_printf(error, "Couldn't open log file '%s': '%s'", data->str, g_strerror(e));
ERROR(srv, "Couldn't open log file '%s': %s", data->str, g_strerror(e));
if (!li_angel_send_result(i->acon, id, error, NULL, NULL, &err)) {
ERROR(srv, "Couldn't send result: %s", err->message);
g_error_free(err);
}
return;
}
} else {
GString *error = g_string_sized_new(0);
g_string_printf(error, "Couldn't open log file '%s': path not allowed", data->str);
if (!li_angel_send_result(i->acon, id, error, NULL, NULL, &err)) {
ERROR(srv, "Couldn't send result: %s", err->message);
g_error_free(err);
}
return;
}
fds = g_array_new(FALSE, FALSE, sizeof(int));
g_array_append_val(fds, fd);
if (!li_angel_send_result(i->acon, id, NULL, NULL, fds, &err)) {
ERROR(srv, "Couldn't send result: %s", err->message);
g_error_free(err);
return;
}
}
static void core_free(liServer *srv, liPlugin *p) {
liPluginCoreConfig *config = (liPluginCoreConfig*) p->data;
guint i;
li_event_clear(&config->sig_hup);
core_parse_init(srv, p);
g_ptr_array_free(config->parsing.env, TRUE);
config->parsing.env = NULL;
g_ptr_array_free(config->parsing.wrapper, TRUE);
config->parsing.wrapper = NULL;
g_ptr_array_free(config->parsing.listen_masks, TRUE);
config->parsing.listen_masks = NULL;
if (config->instconf) {
li_instance_conf_release(config->instconf);
config->instconf = NULL;
}
if (config->inst) {
li_instance_set_state(config->inst, LI_INSTANCE_FINISHED);
li_instance_release(config->inst);
config->inst = NULL;
}
for (i = 0; i < config->listen_masks->len; i++) {
core_listen_mask_free(g_ptr_array_index(config->listen_masks, i));
}
g_ptr_array_free(config->listen_masks, TRUE);
g_hash_table_destroy(config->listen_sockets);
config->listen_masks = NULL;
g_slice_free(liPluginCoreConfig, config);
}
static void core_activate(liServer *srv, liPlugin *p) {
liPluginCoreConfig *config = (liPluginCoreConfig*) p->data;
GPtrArray *tmp_ptrarray;
guint i;
if (NULL != config->instconf) {
li_instance_conf_release(config->instconf);
config->instconf = NULL;
}
if (NULL != config->inst) {
li_instance_set_state(config->inst, LI_INSTANCE_FINISHED);
li_instance_release(config->inst);
config->inst = NULL;
}
for (i = 0; i < config->listen_masks->len; i++) {
core_listen_mask_free(g_ptr_array_index(config->listen_masks, i));
}
g_ptr_array_set_size(config->listen_masks, 0);
config->instconf = config->parsing.instconf;
config->parsing.instconf = NULL;
tmp_ptrarray = config->parsing.listen_masks; config->parsing.listen_masks = config->listen_masks; config->listen_masks = tmp_ptrarray;
if (NULL != config->instconf) {
config->inst = li_server_new_instance(srv, config->instconf);
li_instance_set_state(config->inst, LI_INSTANCE_RUNNING);
}
}
static void core_instance_replaced(liServer *srv, liPlugin *p, liInstance *oldi, liInstance *newi) {
liPluginCoreConfig *config = (liPluginCoreConfig*) p->data;
UNUSED(srv);
if (oldi == config->inst && LI_INSTANCE_FINISHED == oldi->s_cur) {
li_instance_acquire(newi);
config->inst = newi;
li_instance_release(oldi);
}
}
static void core_handle_sig_hup(liEventBase *watcher, int events) {
liPluginCoreConfig *config = LI_CONTAINER_OF(li_event_signal_from(watcher), liPluginCoreConfig, sig_hup);
liInstance *oldi, *newi;
UNUSED(events);
if (NULL == (oldi = config->inst)) return;
if (oldi->replace_by) return;
INFO(oldi->srv, "%s", "Received SIGHUP: graceful instance restart");
newi = li_server_new_instance(oldi->srv, config->instconf);
li_instance_replace(oldi, newi);
li_instance_release(newi);
}
static gboolean core_init(liServer *srv, liPlugin *p) {
liPluginCoreConfig *config;
UNUSED(srv);
p->data = config = g_slice_new0(liPluginCoreConfig);
p->items = core_items;
p->handle_free = core_free;
p->handle_clean_config = core_parse_init;
p->handle_check_config = core_check;
p->handle_activate_config = core_activate;
p->handle_instance_replaced = core_instance_replaced;
core_parse_init(srv, p);
config->listen_sockets = g_hash_table_new_full(li_hash_sockaddr, li_equal_sockaddr, NULL, _listen_socket_free);
config->listen_masks = g_ptr_array_new();
li_angel_plugin_add_angel_cb(p, "listen", core_listen);
li_angel_plugin_add_angel_cb(p, "reached-state", core_reached_state);
li_angel_plugin_add_angel_cb(p, "log-open-file", core_log_open_file);
li_event_signal_init(&srv->loop, "angel SIGHUP", &config->sig_hup, core_handle_sig_hup, SIGHUP);
return TRUE;
}
gboolean li_plugin_core_init(liServer *srv) {
/* load core plugins */
return NULL != li_angel_plugin_register(srv, NULL, "core", core_init);
}