Merge pull request #1080 from SirCmpwn/ipc-security
Revise IPC security configuration
This commit is contained in:
commit
f68d2fb33c
|
@ -203,7 +203,6 @@ enum secure_feature {
|
||||||
FEATURE_FULLSCREEN = 16,
|
FEATURE_FULLSCREEN = 16,
|
||||||
FEATURE_KEYBOARD = 32,
|
FEATURE_KEYBOARD = 32,
|
||||||
FEATURE_MOUSE = 64,
|
FEATURE_MOUSE = 64,
|
||||||
FEATURE_IPC = 128,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct feature_policy {
|
struct feature_policy {
|
||||||
|
@ -225,7 +224,17 @@ enum ipc_feature {
|
||||||
IPC_FEATURE_EVENT_MODE = 1024,
|
IPC_FEATURE_EVENT_MODE = 1024,
|
||||||
IPC_FEATURE_EVENT_WINDOW = 2048,
|
IPC_FEATURE_EVENT_WINDOW = 2048,
|
||||||
IPC_FEATURE_EVENT_BINDING = 4096,
|
IPC_FEATURE_EVENT_BINDING = 4096,
|
||||||
IPC_FEATURE_EVENT_INPUT = 8192
|
IPC_FEATURE_EVENT_INPUT = 8192,
|
||||||
|
|
||||||
|
IPC_FEATURE_ALL_COMMANDS = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
IPC_FEATURE_ALL_EVENTS = 256 | 512 | 1024 | 2048 | 4096 | 8192,
|
||||||
|
|
||||||
|
IPC_FEATURE_ALL = IPC_FEATURE_ALL_COMMANDS | IPC_FEATURE_ALL_EVENTS,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ipc_policy {
|
||||||
|
char *program;
|
||||||
|
uint32_t features;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -300,7 +309,7 @@ struct sway_config {
|
||||||
// Security
|
// Security
|
||||||
list_t *command_policies;
|
list_t *command_policies;
|
||||||
list_t *feature_policies;
|
list_t *feature_policies;
|
||||||
uint32_t ipc_policy;
|
list_t *ipc_policies;
|
||||||
};
|
};
|
||||||
|
|
||||||
void pid_workspace_add(struct pid_workspace *pw);
|
void pid_workspace_add(struct pid_workspace *pw);
|
||||||
|
@ -331,6 +340,8 @@ void free_config(struct sway_config *config);
|
||||||
*/
|
*/
|
||||||
char *do_var_replacement(char *str);
|
char *do_var_replacement(char *str);
|
||||||
|
|
||||||
|
struct cmd_results *check_security_config();
|
||||||
|
|
||||||
int input_identifier_cmp(const void *item, const void *data);
|
int input_identifier_cmp(const void *item, const void *data);
|
||||||
void merge_input_config(struct input_config *dst, struct input_config *src);
|
void merge_input_config(struct input_config *dst, struct input_config *src);
|
||||||
void apply_input_config(struct input_config *ic, struct libinput_device *dev);
|
void apply_input_config(struct input_config *ic, struct libinput_device *dev);
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "sway/config.h"
|
#include "sway/config.h"
|
||||||
|
|
||||||
enum secure_feature get_feature_policy(pid_t pid);
|
uint32_t get_feature_policy(pid_t pid);
|
||||||
enum command_context get_command_policy(const char *cmd);
|
uint32_t get_ipc_policy(pid_t pid);
|
||||||
|
uint32_t get_command_policy(const char *cmd);
|
||||||
|
|
||||||
const char *command_policy_str(enum command_context context);
|
const char *command_policy_str(enum command_context context);
|
||||||
|
|
||||||
struct feature_policy *alloc_feature_policy(const char *program);
|
struct feature_policy *alloc_feature_policy(const char *program);
|
||||||
|
struct ipc_policy *alloc_ipc_policy(const char *program);
|
||||||
struct command_policy *alloc_command_policy(const char *command);
|
struct command_policy *alloc_command_policy(const char *command);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -5,36 +5,42 @@
|
||||||
# You MUST read this man page if you intend to attempt to secure your sway
|
# You MUST read this man page if you intend to attempt to secure your sway
|
||||||
# installation.
|
# installation.
|
||||||
#
|
#
|
||||||
# This file should live at __SYSCONFDIR__/sway/security and will be
|
# DO NOT CHANGE THIS FILE. Override these defaults by writing new files in
|
||||||
# automatically read by sway.
|
# __SYSCONFDIR__/sway/security.d/*
|
||||||
|
|
||||||
# Configures which programs are allowed to use which sway features
|
# Configures enabled compositor features for specific programs
|
||||||
permit * fullscreen keyboard mouse ipc
|
permit * fullscreen keyboard mouse
|
||||||
permit __PREFIX__/bin/swaylock lock
|
permit __PREFIX__/bin/swaylock lock
|
||||||
permit __PREFIX__/bin/swaybar panel
|
|
||||||
permit __PREFIX__/bin/swaybg background
|
permit __PREFIX__/bin/swaybg background
|
||||||
permit __PREFIX__/bin/swaygrab screenshot
|
permit __PREFIX__/bin/swaygrab screenshot
|
||||||
|
permit __PREFIX__/bin/swaybar panel
|
||||||
|
|
||||||
# Configures which IPC features are enabled
|
# Configures enabled IPC features for specific programs
|
||||||
ipc {
|
ipc __PREFIX__/bin/swaymsg {
|
||||||
command enabled
|
* enabled
|
||||||
|
|
||||||
|
events {
|
||||||
|
* disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc __PREFIX__/bin/swaybar {
|
||||||
|
bar-config enabled
|
||||||
outputs enabled
|
outputs enabled
|
||||||
workspaces enabled
|
workspaces enabled
|
||||||
tree enabled
|
command enabled
|
||||||
marks enabled
|
|
||||||
bar-config enabled
|
|
||||||
inputs enabled
|
|
||||||
|
|
||||||
events {
|
events {
|
||||||
workspace enabled
|
workspace enabled
|
||||||
output enabled
|
|
||||||
mode enabled
|
mode enabled
|
||||||
window enabled
|
|
||||||
input enabled
|
|
||||||
binding disabled
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipc __PREFIX__/bin/swaygrab {
|
||||||
|
outputs enabled
|
||||||
|
tree enabled
|
||||||
|
}
|
||||||
|
|
||||||
# Limits the contexts from which certain commands are permitted
|
# Limits the contexts from which certain commands are permitted
|
||||||
commands {
|
commands {
|
||||||
* all
|
* all
|
|
@ -91,7 +91,7 @@ function(add_config name source destination)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
add_config(config config sway)
|
add_config(config config sway)
|
||||||
add_config(security security sway)
|
add_config(00-defaults security.d/00-defaults sway/security.d)
|
||||||
|
|
||||||
add_manpage(sway 1)
|
add_manpage(sway 1)
|
||||||
add_manpage(sway 5)
|
add_manpage(sway 5)
|
||||||
|
|
|
@ -297,6 +297,7 @@ static struct cmd_handler bar_colors_handlers[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct cmd_handler ipc_handlers[] = {
|
static struct cmd_handler ipc_handlers[] = {
|
||||||
|
{ "*", cmd_ipc_cmd },
|
||||||
{ "bar-config", cmd_ipc_cmd },
|
{ "bar-config", cmd_ipc_cmd },
|
||||||
{ "command", cmd_ipc_cmd },
|
{ "command", cmd_ipc_cmd },
|
||||||
{ "events", cmd_ipc_events },
|
{ "events", cmd_ipc_events },
|
||||||
|
@ -308,6 +309,7 @@ static struct cmd_handler ipc_handlers[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct cmd_handler ipc_event_handlers[] = {
|
static struct cmd_handler ipc_event_handlers[] = {
|
||||||
|
{ "*", cmd_ipc_event_cmd },
|
||||||
{ "binding", cmd_ipc_event_cmd },
|
{ "binding", cmd_ipc_event_cmd },
|
||||||
{ "input", cmd_ipc_event_cmd },
|
{ "input", cmd_ipc_event_cmd },
|
||||||
{ "mode", cmd_ipc_event_cmd },
|
{ "mode", cmd_ipc_event_cmd },
|
||||||
|
|
|
@ -10,6 +10,9 @@ struct cmd_results *cmd_commands(int argc, char **argv) {
|
||||||
if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) {
|
if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
if ((error = check_security_config())) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(argv[0], "{") != 0) {
|
if (strcmp(argv[0], "{") != 0) {
|
||||||
return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration");
|
return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration");
|
||||||
|
@ -19,10 +22,5 @@ struct cmd_results *cmd_commands(int argc, char **argv) {
|
||||||
return cmd_results_new(CMD_FAILURE, "commands", "Can only be used in config file.");
|
return cmd_results_new(CMD_FAILURE, "commands", "Can only be used in config file.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) {
|
|
||||||
return cmd_results_new(CMD_INVALID, "permit",
|
|
||||||
"This command is only permitted to run from " SYSCONFDIR "/sway/security");
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL);
|
return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,26 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "sway/security.h"
|
||||||
#include "sway/commands.h"
|
#include "sway/commands.h"
|
||||||
#include "sway/config.h"
|
#include "sway/config.h"
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
static struct ipc_policy *current_policy = NULL;
|
||||||
|
|
||||||
struct cmd_results *cmd_ipc(int argc, char **argv) {
|
struct cmd_results *cmd_ipc(int argc, char **argv) {
|
||||||
struct cmd_results *error = NULL;
|
struct cmd_results *error = NULL;
|
||||||
if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
|
if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 2))) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
if ((error = check_security_config())) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->reading && strcmp("{", argv[0]) != 0) {
|
const char *program = argv[0];
|
||||||
|
|
||||||
|
if (config->reading && strcmp("{", argv[1]) != 0) {
|
||||||
return cmd_results_new(CMD_INVALID, "ipc",
|
return cmd_results_new(CMD_INVALID, "ipc",
|
||||||
"Expected '{' at start of IPC config definition.");
|
"Expected '{' at start of IPC config definition.");
|
||||||
}
|
}
|
||||||
|
@ -21,10 +29,8 @@ struct cmd_results *cmd_ipc(int argc, char **argv) {
|
||||||
return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file.");
|
return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) {
|
current_policy = alloc_ipc_policy(program);
|
||||||
return cmd_results_new(CMD_INVALID, "permit",
|
list_add(config->ipc_policies, current_policy);
|
||||||
"This command is only permitted to run from " SYSCONFDIR "/sway/security");
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL);
|
return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
@ -67,6 +73,7 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
|
||||||
char *name;
|
char *name;
|
||||||
enum ipc_feature type;
|
enum ipc_feature type;
|
||||||
} types[] = {
|
} types[] = {
|
||||||
|
{ "*", IPC_FEATURE_ALL_COMMANDS },
|
||||||
{ "command", IPC_FEATURE_COMMAND },
|
{ "command", IPC_FEATURE_COMMAND },
|
||||||
{ "workspaces", IPC_FEATURE_GET_WORKSPACES },
|
{ "workspaces", IPC_FEATURE_GET_WORKSPACES },
|
||||||
{ "outputs", IPC_FEATURE_GET_OUTPUTS },
|
{ "outputs", IPC_FEATURE_GET_OUTPUTS },
|
||||||
|
@ -86,10 +93,10 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
config->ipc_policy |= type;
|
current_policy->features |= type;
|
||||||
sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]);
|
sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]);
|
||||||
} else {
|
} else {
|
||||||
config->ipc_policy &= ~type;
|
current_policy->features &= ~type;
|
||||||
sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]);
|
sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +123,7 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
|
||||||
char *name;
|
char *name;
|
||||||
enum ipc_feature type;
|
enum ipc_feature type;
|
||||||
} types[] = {
|
} types[] = {
|
||||||
|
{ "*", IPC_FEATURE_ALL_EVENTS },
|
||||||
{ "workspace", IPC_FEATURE_EVENT_WORKSPACE },
|
{ "workspace", IPC_FEATURE_EVENT_WORKSPACE },
|
||||||
{ "output", IPC_FEATURE_EVENT_OUTPUT },
|
{ "output", IPC_FEATURE_EVENT_OUTPUT },
|
||||||
{ "mode", IPC_FEATURE_EVENT_MODE },
|
{ "mode", IPC_FEATURE_EVENT_MODE },
|
||||||
|
@ -134,10 +142,10 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
config->ipc_policy |= type;
|
current_policy->features |= type;
|
||||||
sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]);
|
sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]);
|
||||||
} else {
|
} else {
|
||||||
config->ipc_policy &= ~type;
|
current_policy->features &= ~type;
|
||||||
sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]);
|
sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ static enum secure_feature get_features(int argc, char **argv,
|
||||||
{ "fullscreen", FEATURE_FULLSCREEN },
|
{ "fullscreen", FEATURE_FULLSCREEN },
|
||||||
{ "keyboard", FEATURE_KEYBOARD },
|
{ "keyboard", FEATURE_KEYBOARD },
|
||||||
{ "mouse", FEATURE_MOUSE },
|
{ "mouse", FEATURE_MOUSE },
|
||||||
{ "ipc", FEATURE_IPC },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
@ -63,19 +62,13 @@ struct cmd_results *cmd_permit(int argc, char **argv) {
|
||||||
if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) {
|
if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
if ((error = check_security_config())) {
|
||||||
if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) {
|
return error;
|
||||||
return cmd_results_new(CMD_INVALID, "permit",
|
|
||||||
"This command is only permitted to run from " SYSCONFDIR "/sway/security");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct feature_policy *policy = get_policy(argv[0]);
|
struct feature_policy *policy = get_policy(argv[0]);
|
||||||
policy->features |= get_features(argc, argv, &error);
|
policy->features |= get_features(argc, argv, &error);
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
sway_log(L_DEBUG, "Permissions granted to %s for features %d",
|
sway_log(L_DEBUG, "Permissions granted to %s for features %d",
|
||||||
policy->program, policy->features);
|
policy->program, policy->features);
|
||||||
|
|
||||||
|
@ -87,19 +80,13 @@ struct cmd_results *cmd_reject(int argc, char **argv) {
|
||||||
if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) {
|
if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
if ((error = check_security_config())) {
|
||||||
if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) {
|
return error;
|
||||||
return cmd_results_new(CMD_INVALID, "permit",
|
|
||||||
"This command is only permitted to run from " SYSCONFDIR "/sway/security");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct feature_policy *policy = get_policy(argv[0]);
|
struct feature_policy *policy = get_policy(argv[0]);
|
||||||
policy->features &= ~get_features(argc, argv, &error);
|
policy->features &= ~get_features(argc, argv, &error);
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
sway_log(L_DEBUG, "Permissions granted to %s for features %d",
|
sway_log(L_DEBUG, "Permissions granted to %s for features %d",
|
||||||
policy->program, policy->features);
|
policy->program, policy->features);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <libinput.h>
|
#include <libinput.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
#include <dirent.h>
|
||||||
#include "wayland-desktop-shell-server-protocol.h"
|
#include "wayland-desktop-shell-server-protocol.h"
|
||||||
#include "sway/commands.h"
|
#include "sway/commands.h"
|
||||||
#include "sway/config.h"
|
#include "sway/config.h"
|
||||||
|
@ -379,7 +380,7 @@ static void config_defaults(struct sway_config *config) {
|
||||||
// Security
|
// Security
|
||||||
if (!(config->command_policies = create_list())) goto cleanup;
|
if (!(config->command_policies = create_list())) goto cleanup;
|
||||||
if (!(config->feature_policies = create_list())) goto cleanup;
|
if (!(config->feature_policies = create_list())) goto cleanup;
|
||||||
config->ipc_policy = UINT32_MAX;
|
if (!(config->ipc_policies = create_list())) goto cleanup;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
cleanup:
|
cleanup:
|
||||||
|
@ -485,6 +486,10 @@ static bool load_config(const char *path, struct sway_config *config) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int qstrcmp(const void* a, const void* b) {
|
||||||
|
return strcmp(*((char**) a), *((char**) b));
|
||||||
|
}
|
||||||
|
|
||||||
bool load_main_config(const char *file, bool is_active) {
|
bool load_main_config(const char *file, bool is_active) {
|
||||||
input_init();
|
input_init();
|
||||||
|
|
||||||
|
@ -512,7 +517,43 @@ bool load_main_config(const char *file, bool is_active) {
|
||||||
list_add(config->config_chain, path);
|
list_add(config->config_chain, path);
|
||||||
|
|
||||||
config->reading = true;
|
config->reading = true;
|
||||||
bool success = load_config(SYSCONFDIR "/sway/security", config);
|
|
||||||
|
// Read security configs
|
||||||
|
bool success = true;
|
||||||
|
DIR *dir = opendir(SYSCONFDIR "/sway/security.d");
|
||||||
|
if (!dir) {
|
||||||
|
sway_log(L_ERROR, "%s does not exist, sway will have no security configuration"
|
||||||
|
" and will probably be broken", SYSCONFDIR "/sway/security.d");
|
||||||
|
} else {
|
||||||
|
list_t *secconfigs = create_list();
|
||||||
|
char *base = SYSCONFDIR "/sway/security.d/";
|
||||||
|
struct dirent *ent = readdir(dir);
|
||||||
|
while (ent != NULL) {
|
||||||
|
if (ent->d_type == DT_REG) {
|
||||||
|
char *_path = malloc(strlen(ent->d_name) + strlen(base) + 1);
|
||||||
|
strcpy(_path, base);
|
||||||
|
strcat(_path, ent->d_name);
|
||||||
|
list_add(secconfigs, _path);
|
||||||
|
}
|
||||||
|
ent = readdir(dir);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
list_qsort(secconfigs, qstrcmp);
|
||||||
|
for (int i = 0; i < secconfigs->length; ++i) {
|
||||||
|
char *_path = secconfigs->items[i];
|
||||||
|
struct stat s;
|
||||||
|
if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 || (s.st_mode & 0777) != 0644) {
|
||||||
|
sway_log(L_ERROR, "Refusing to load %s - it must be owned by root and mode 644", _path);
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
success = success && load_config(_path, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free_flat_list(secconfigs);
|
||||||
|
}
|
||||||
|
|
||||||
success = success && load_config(path, config);
|
success = success && load_config(path, config);
|
||||||
|
|
||||||
if (is_active) {
|
if (is_active) {
|
||||||
|
@ -620,6 +661,15 @@ bool load_include_configs(const char *path, struct sway_config *config) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct cmd_results *check_security_config() {
|
||||||
|
if (!current_config_path || strncmp(SYSCONFDIR "/sway/security.d/", current_config_path,
|
||||||
|
strlen(SYSCONFDIR "/sway/security.d/")) != 0) {
|
||||||
|
return cmd_results_new(CMD_INVALID, "permit",
|
||||||
|
"This command is only permitted to run from " SYSCONFDIR "/sway/security.d/*");
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
bool read_config(FILE *file, struct sway_config *config) {
|
bool read_config(FILE *file, struct sway_config *config) {
|
||||||
bool success = true;
|
bool success = true;
|
||||||
enum cmd_status block = CMD_BLOCK_END;
|
enum cmd_status block = CMD_BLOCK_END;
|
||||||
|
|
|
@ -35,6 +35,7 @@ struct ipc_client {
|
||||||
struct wlc_event_source *event_source;
|
struct wlc_event_source *event_source;
|
||||||
int fd;
|
int fd;
|
||||||
uint32_t payload_length;
|
uint32_t payload_length;
|
||||||
|
uint32_t security_policy;
|
||||||
enum ipc_command_type current_command;
|
enum ipc_command_type current_command;
|
||||||
enum ipc_command_type subscribed_events;
|
enum ipc_command_type subscribed_events;
|
||||||
};
|
};
|
||||||
|
@ -159,17 +160,6 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pid_t pid = get_client_pid(client_fd);
|
|
||||||
if (!(get_feature_policy(pid) & FEATURE_IPC)) {
|
|
||||||
sway_log(L_INFO, "Permission to connect to IPC socket denied to %d", pid);
|
|
||||||
const char *error = "{\"success\": false, \"message\": \"Permission denied\"}";
|
|
||||||
if (write(client_fd, &error, sizeof(error)) < (int)sizeof(error)) {
|
|
||||||
sway_log(L_DEBUG, "Failed to write entire error");
|
|
||||||
}
|
|
||||||
close(client_fd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ipc_client* client = malloc(sizeof(struct ipc_client));
|
struct ipc_client* client = malloc(sizeof(struct ipc_client));
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sway_log(L_ERROR, "Unable to allocate ipc client");
|
sway_log(L_ERROR, "Unable to allocate ipc client");
|
||||||
|
@ -181,6 +171,9 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
|
||||||
client->subscribed_events = 0;
|
client->subscribed_events = 0;
|
||||||
client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client);
|
client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client);
|
||||||
|
|
||||||
|
pid_t pid = get_client_pid(client->fd);
|
||||||
|
client->security_policy = get_ipc_policy(pid);
|
||||||
|
|
||||||
list_add(ipc_client_list, client);
|
list_add(ipc_client_list, client);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -248,8 +241,7 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ipc_client_disconnect(struct ipc_client *client)
|
void ipc_client_disconnect(struct ipc_client *client) {
|
||||||
{
|
|
||||||
if (!sway_assert(client != NULL, "client != NULL")) {
|
if (!sway_assert(client != NULL, "client != NULL")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -333,8 +325,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
|
||||||
ipc_client_disconnect(client);
|
ipc_client_disconnect(client);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (client->payload_length > 0)
|
if (client->payload_length > 0) {
|
||||||
{
|
|
||||||
ssize_t received = recv(client->fd, buf, client->payload_length, 0);
|
ssize_t received = recv(client->fd, buf, client->payload_length, 0);
|
||||||
if (received == -1)
|
if (received == -1)
|
||||||
{
|
{
|
||||||
|
@ -351,7 +342,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
|
||||||
switch (client->current_command) {
|
switch (client->current_command) {
|
||||||
case IPC_COMMAND:
|
case IPC_COMMAND:
|
||||||
{
|
{
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_COMMAND)) {
|
if (!(client->security_policy & IPC_FEATURE_COMMAND)) {
|
||||||
goto exit_denied;
|
goto exit_denied;
|
||||||
}
|
}
|
||||||
struct cmd_results *results = handle_command(buf, CONTEXT_IPC);
|
struct cmd_results *results = handle_command(buf, CONTEXT_IPC);
|
||||||
|
@ -365,6 +356,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
|
||||||
|
|
||||||
case IPC_SUBSCRIBE:
|
case IPC_SUBSCRIBE:
|
||||||
{
|
{
|
||||||
|
// TODO: Check if they're permitted to use these events
|
||||||
struct json_object *request = json_tokener_parse(buf);
|
struct json_object *request = json_tokener_parse(buf);
|
||||||
if (request == NULL) {
|
if (request == NULL) {
|
||||||
ipc_send_reply(client, "{\"success\": false}", 18);
|
ipc_send_reply(client, "{\"success\": false}", 18);
|
||||||
|
@ -403,7 +395,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
|
||||||
|
|
||||||
case IPC_GET_WORKSPACES:
|
case IPC_GET_WORKSPACES:
|
||||||
{
|
{
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_GET_WORKSPACES)) {
|
if (!(client->security_policy & IPC_FEATURE_GET_WORKSPACES)) {
|
||||||
goto exit_denied;
|
goto exit_denied;
|
||||||
}
|
}
|
||||||
json_object *workspaces = json_object_new_array();
|
json_object *workspaces = json_object_new_array();
|
||||||
|
@ -416,7 +408,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
|
||||||
|
|
||||||
case IPC_GET_INPUTS:
|
case IPC_GET_INPUTS:
|
||||||
{
|
{
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_GET_INPUTS)) {
|
if (!(client->security_policy & IPC_FEATURE_GET_INPUTS)) {
|
||||||
goto exit_denied;
|
goto exit_denied;
|
||||||
}
|
}
|
||||||
json_object *inputs = json_object_new_array();
|
json_object *inputs = json_object_new_array();
|
||||||
|
@ -442,7 +434,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
|
||||||
|
|
||||||
case IPC_GET_OUTPUTS:
|
case IPC_GET_OUTPUTS:
|
||||||
{
|
{
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_GET_OUTPUTS)) {
|
if (!(client->security_policy & IPC_FEATURE_GET_OUTPUTS)) {
|
||||||
goto exit_denied;
|
goto exit_denied;
|
||||||
}
|
}
|
||||||
json_object *outputs = json_object_new_array();
|
json_object *outputs = json_object_new_array();
|
||||||
|
@ -455,7 +447,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
|
||||||
|
|
||||||
case IPC_GET_TREE:
|
case IPC_GET_TREE:
|
||||||
{
|
{
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_GET_TREE)) {
|
if (!(client->security_policy & IPC_FEATURE_GET_TREE)) {
|
||||||
goto exit_denied;
|
goto exit_denied;
|
||||||
}
|
}
|
||||||
json_object *tree = ipc_json_describe_container_recursive(&root_container);
|
json_object *tree = ipc_json_describe_container_recursive(&root_container);
|
||||||
|
@ -522,7 +514,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
|
||||||
|
|
||||||
case IPC_GET_BAR_CONFIG:
|
case IPC_GET_BAR_CONFIG:
|
||||||
{
|
{
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_GET_BAR_CONFIG)) {
|
if (!(client->security_policy & IPC_FEATURE_GET_BAR_CONFIG)) {
|
||||||
goto exit_denied;
|
goto exit_denied;
|
||||||
}
|
}
|
||||||
if (!buf[0]) {
|
if (!buf[0]) {
|
||||||
|
@ -567,6 +559,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
|
||||||
|
|
||||||
exit_denied:
|
exit_denied:
|
||||||
ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied));
|
ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied));
|
||||||
|
sway_log(L_DEBUG, "Denied IPC client access to %i", client->current_command);
|
||||||
|
|
||||||
exit_cleanup:
|
exit_cleanup:
|
||||||
client->payload_length = 0;
|
client->payload_length = 0;
|
||||||
|
@ -594,6 +587,8 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sway_log(L_DEBUG, "Send IPC reply: %s", payload);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,10 +611,33 @@ void ipc_get_outputs_callback(swayc_t *container, void *data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ipc_send_event(const char *json_string, enum ipc_command_type event) {
|
void ipc_send_event(const char *json_string, enum ipc_command_type event) {
|
||||||
|
static struct {
|
||||||
|
enum ipc_command_type event;
|
||||||
|
enum ipc_feature feature;
|
||||||
|
} security_mappings[] = {
|
||||||
|
{ IPC_EVENT_WORKSPACE, IPC_FEATURE_EVENT_WORKSPACE },
|
||||||
|
{ IPC_EVENT_OUTPUT, IPC_FEATURE_EVENT_OUTPUT },
|
||||||
|
{ IPC_EVENT_MODE, IPC_FEATURE_EVENT_MODE },
|
||||||
|
{ IPC_EVENT_WINDOW, IPC_FEATURE_EVENT_WINDOW },
|
||||||
|
{ IPC_EVENT_BINDING, IPC_FEATURE_EVENT_BINDING },
|
||||||
|
{ IPC_EVENT_INPUT, IPC_FEATURE_EVENT_INPUT }
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t security_mask = 0;
|
||||||
|
for (size_t i = 0; i < sizeof(security_mappings) / sizeof(security_mappings[0]); ++i) {
|
||||||
|
if (security_mappings[i].event == event) {
|
||||||
|
security_mask = security_mappings[i].feature;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
struct ipc_client *client;
|
struct ipc_client *client;
|
||||||
for (i = 0; i < ipc_client_list->length; i++) {
|
for (i = 0; i < ipc_client_list->length; i++) {
|
||||||
client = ipc_client_list->items[i];
|
client = ipc_client_list->items[i];
|
||||||
|
if (!(client->security_policy & security_mask)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if ((client->subscribed_events & event_mask(event)) == 0) {
|
if ((client->subscribed_events & event_mask(event)) == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -632,9 +650,6 @@ void ipc_send_event(const char *json_string, enum ipc_command_type event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
|
void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_EVENT_WORKSPACE)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sway_log(L_DEBUG, "Sending workspace::%s event", change);
|
sway_log(L_DEBUG, "Sending workspace::%s event", change);
|
||||||
json_object *obj = json_object_new_object();
|
json_object *obj = json_object_new_object();
|
||||||
json_object_object_add(obj, "change", json_object_new_string(change));
|
json_object_object_add(obj, "change", json_object_new_string(change));
|
||||||
|
@ -659,9 +674,6 @@ void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ipc_event_window(swayc_t *window, const char *change) {
|
void ipc_event_window(swayc_t *window, const char *change) {
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_EVENT_WINDOW)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sway_log(L_DEBUG, "Sending window::%s event", change);
|
sway_log(L_DEBUG, "Sending window::%s event", change);
|
||||||
json_object *obj = json_object_new_object();
|
json_object *obj = json_object_new_object();
|
||||||
json_object_object_add(obj, "change", json_object_new_string(change));
|
json_object_object_add(obj, "change", json_object_new_string(change));
|
||||||
|
@ -687,9 +699,6 @@ void ipc_event_barconfig_update(struct bar_config *bar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ipc_event_mode(const char *mode) {
|
void ipc_event_mode(const char *mode) {
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_EVENT_MODE)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sway_log(L_DEBUG, "Sending mode::%s event", mode);
|
sway_log(L_DEBUG, "Sending mode::%s event", mode);
|
||||||
json_object *obj = json_object_new_object();
|
json_object *obj = json_object_new_object();
|
||||||
json_object_object_add(obj, "change", json_object_new_string(mode));
|
json_object_object_add(obj, "change", json_object_new_string(mode));
|
||||||
|
@ -715,9 +724,6 @@ void ipc_event_modifier(uint32_t modifier, const char *state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ipc_event_binding(json_object *sb_obj) {
|
static void ipc_event_binding(json_object *sb_obj) {
|
||||||
if (!(config->ipc_policy & IPC_FEATURE_EVENT_BINDING)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sway_log(L_DEBUG, "Sending binding::run event");
|
sway_log(L_DEBUG, "Sending binding::run event");
|
||||||
json_object *obj = json_object_new_object();
|
json_object *obj = json_object_new_object();
|
||||||
json_object_object_add(obj, "change", json_object_new_string("run"));
|
json_object_object_add(obj, "change", json_object_new_string("run"));
|
||||||
|
|
|
@ -175,13 +175,6 @@ static void security_sanity_check() {
|
||||||
cap_free(cap);
|
cap_free(cap);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (!stat(SYSCONFDIR "/sway", &s)) {
|
|
||||||
if (s.st_uid != 0 || s.st_gid != 0
|
|
||||||
|| (s.st_mode & S_IWGRP) || (s.st_mode & S_IWOTH)) {
|
|
||||||
sway_log(L_ERROR,
|
|
||||||
"!! DANGER !! " SYSCONFDIR "/sway is not secure! It should be owned by root and set to 0755 at the minimum");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
|
|
@ -27,6 +27,29 @@ struct feature_policy *alloc_feature_policy(const char *program) {
|
||||||
return policy;
|
return policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ipc_policy *alloc_ipc_policy(const char *program) {
|
||||||
|
uint32_t default_policy = 0;
|
||||||
|
for (int i = 0; i < config->ipc_policies->length; ++i) {
|
||||||
|
struct ipc_policy *policy = config->ipc_policies->items[i];
|
||||||
|
if (strcmp(policy->program, "*") == 0) {
|
||||||
|
default_policy = policy->features;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ipc_policy *policy = malloc(sizeof(struct ipc_policy));
|
||||||
|
if (!policy) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
policy->program = strdup(program);
|
||||||
|
if (!policy->program) {
|
||||||
|
free(policy);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
policy->features = default_policy;
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
struct command_policy *alloc_command_policy(const char *command) {
|
struct command_policy *alloc_command_policy(const char *command) {
|
||||||
struct command_policy *policy = malloc(sizeof(struct command_policy));
|
struct command_policy *policy = malloc(sizeof(struct command_policy));
|
||||||
if (!policy) {
|
if (!policy) {
|
||||||
|
@ -41,7 +64,7 @@ struct command_policy *alloc_command_policy(const char *command) {
|
||||||
return policy;
|
return policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum secure_feature get_feature_policy(pid_t pid) {
|
static const char *get_pid_exe(pid_t pid) {
|
||||||
#ifdef __FreeBSD__
|
#ifdef __FreeBSD__
|
||||||
const char *fmt = "/proc/%d/file";
|
const char *fmt = "/proc/%d/file";
|
||||||
#else
|
#else
|
||||||
|
@ -52,9 +75,8 @@ enum secure_feature get_feature_policy(pid_t pid) {
|
||||||
if (path) {
|
if (path) {
|
||||||
snprintf(path, pathlen + 1, fmt, pid);
|
snprintf(path, pathlen + 1, fmt, pid);
|
||||||
}
|
}
|
||||||
static char link[2048];
|
|
||||||
|
|
||||||
uint32_t default_policy = 0;
|
static char link[2048];
|
||||||
|
|
||||||
ssize_t len = !path ? -1 : readlink(path, link, sizeof(link));
|
ssize_t len = !path ? -1 : readlink(path, link, sizeof(link));
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
|
@ -67,6 +89,13 @@ enum secure_feature get_feature_policy(pid_t pid) {
|
||||||
}
|
}
|
||||||
free(path);
|
free(path);
|
||||||
|
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t get_feature_policy(pid_t pid) {
|
||||||
|
uint32_t default_policy = 0;
|
||||||
|
const char *link = get_pid_exe(pid);
|
||||||
|
|
||||||
for (int i = 0; i < config->feature_policies->length; ++i) {
|
for (int i = 0; i < config->feature_policies->length; ++i) {
|
||||||
struct feature_policy *policy = config->feature_policies->items[i];
|
struct feature_policy *policy = config->feature_policies->items[i];
|
||||||
if (strcmp(policy->program, "*") == 0) {
|
if (strcmp(policy->program, "*") == 0) {
|
||||||
|
@ -80,7 +109,24 @@ enum secure_feature get_feature_policy(pid_t pid) {
|
||||||
return default_policy;
|
return default_policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum command_context get_command_policy(const char *cmd) {
|
uint32_t get_ipc_policy(pid_t pid) {
|
||||||
|
uint32_t default_policy = 0;
|
||||||
|
const char *link = get_pid_exe(pid);
|
||||||
|
|
||||||
|
for (int i = 0; i < config->ipc_policies->length; ++i) {
|
||||||
|
struct ipc_policy *policy = config->ipc_policies->items[i];
|
||||||
|
if (strcmp(policy->program, "*") == 0) {
|
||||||
|
default_policy = policy->features;
|
||||||
|
}
|
||||||
|
if (strcmp(policy->program, link) == 0) {
|
||||||
|
return policy->features;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default_policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t get_command_policy(const char *cmd) {
|
||||||
uint32_t default_policy = 0;
|
uint32_t default_policy = 0;
|
||||||
|
|
||||||
for (int i = 0; i < config->command_policies->length; ++i) {
|
for (int i = 0; i < config->command_policies->length; ++i) {
|
||||||
|
|
|
@ -19,8 +19,13 @@ usually best suited to a distro maintainer who wants to ship a secure sway
|
||||||
environment in their distro. Sway provides a number of means of securing it but
|
environment in their distro. Sway provides a number of means of securing it but
|
||||||
you must make a few changes external to sway first.
|
you must make a few changes external to sway first.
|
||||||
|
|
||||||
Security-related configuration is only valid in /etc/sway/config (or whatever path
|
Configuration of security features is limited to files in the security directory
|
||||||
is appropriate for your system).
|
(this is likely /etc/sway/security.d/*, but depends on your installation prefix).
|
||||||
|
Files in this directory must be owned by root:root and chmod 644. The default
|
||||||
|
security configuration is installed to /etc/sway/security.d/00-defaults, and
|
||||||
|
should not be modified - it will be updated with the latest recommended security
|
||||||
|
defaults between releases. To override the defaults, you should add more files to
|
||||||
|
this directory.
|
||||||
|
|
||||||
Environment security
|
Environment security
|
||||||
--------------------
|
--------------------
|
||||||
|
@ -160,22 +165,20 @@ Setting a command policy overwrites any previous policy that was in place.
|
||||||
IPC policies
|
IPC policies
|
||||||
------------
|
------------
|
||||||
|
|
||||||
You may whitelist IPC access like so:
|
Disabling IPC access via swaymsg is encouraged if you intend to secure the IPC
|
||||||
|
socket, because any program that can execute swaymsg could circumvent its own
|
||||||
|
security policy by simply invoking swaymsg.
|
||||||
|
|
||||||
permit /usr/bin/swaybar ipc
|
You can configure which features of IPC are available for particular clients:
|
||||||
permit /usr/bin/swaygrab ipc
|
|
||||||
# etc
|
|
||||||
|
|
||||||
Note that it's suggested you do not enable swaymsg to access IPC if you intend to
|
ipc <executable> {
|
||||||
secure your IPC socket, because any program could just run swaymsg itself instead
|
|
||||||
of connecting to IPC directly.
|
|
||||||
|
|
||||||
You can also configure which features of IPC are available with an IPC block:
|
|
||||||
|
|
||||||
ipc {
|
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
You may use * for <executable> to configure the default policy for all clients.
|
||||||
|
Configuring IPC policies for specific executables is not supported on FreeBSD, and
|
||||||
|
the default policy will be applied to all IPC connections.
|
||||||
|
|
||||||
The following commands are available within this block:
|
The following commands are available within this block:
|
||||||
|
|
||||||
**bar-config** <enabled|disabled>::
|
**bar-config** <enabled|disabled>::
|
||||||
|
@ -201,7 +204,7 @@ The following commands are available within this block:
|
||||||
|
|
||||||
You can also control which IPC events can be raised with an events block:
|
You can also control which IPC events can be raised with an events block:
|
||||||
|
|
||||||
ipc {
|
ipc <executable> {
|
||||||
events {
|
events {
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
@ -227,7 +230,8 @@ The following commands are vaild within an ipc events block:
|
||||||
**workspace** <enabled|disabled>::
|
**workspace** <enabled|disabled>::
|
||||||
Controls workspace notifications.
|
Controls workspace notifications.
|
||||||
|
|
||||||
Disabling some of these may cause swaybar to behave incorrectly.
|
In each of these blocks, you may use * (as in "* enabled" or "* disabled") to
|
||||||
|
control access to every feature at once.
|
||||||
|
|
||||||
Authors
|
Authors
|
||||||
-------
|
-------
|
||||||
|
|
Loading…
Reference in a new issue