diff --git a/data/etc/meson.build b/data/etc/meson.build index eec92a26c..726d29a88 100644 --- a/data/etc/meson.build +++ b/data/etc/meson.build @@ -1,3 +1,26 @@ +MOUNT = '/bin/mount' +UMOUNT = '/bin/umount' +EJECT = '/usr/bin/eject' + +if host_machine.system().contains('bsd') == true + MOUNT = '/sbin/mount' + UMOUNT = '/sbin/umount' + EJECT = '/usr/sbin/cdcontrol eject' +endif + +sysactions = configuration_data() +sysactions.set('MOUNT' , MOUNT) +sysactions.set('UMOUNT' , UMOUNT) +sysactions.set('EJECT' , EJECT) + +if get_option('install-sysactions') + configure_file(input : 'sysactions.conf.in', + output : 'sysactions.conf', + install_dir : join_paths(dir_sysconf, 'enlightenment'), + configuration: sysactions + ) +endif + if get_option('install-enlightenment-menu') install_data('e-applications.menu', install_dir: join_paths(dir_sysconf, 'xdg/menus') diff --git a/data/etc/sysactions.conf.in b/data/etc/sysactions.conf.in new file mode 100644 index 000000000..5ba74a24a --- /dev/null +++ b/data/etc/sysactions.conf.in @@ -0,0 +1,77 @@ +# ENLIGHTENMENT SYSTEM ACTIONS CONFIGURATION +# +# This is a system configuration for allowing or denying certain users or +# groups to be able to do certain actions that involve system restricted +# actions such as halt, reboot, suspend, hibernate etc. +# +# This file is read in order from top to bottom - the first rule to MATCH +# will be used for a user or a group, and nothing after that is read. +# +# You must put all the ACTION definitons BEFORE user and group rule matches. +# Any action definitons after a rule match has been found will be ignored. +# This allows actions to be re-defined for different user groups, so matches +# so the command for an action can change for matches to the rules later on. +# +# Any user or group NOT matched by an allow or a deny will be ALLOWED to +# perform the action by default (system administrators should be aware of +# this and implement whatever policies they see fit). Generally speaking +# a user of a workstation, desktop or laptop is intended to have such abilities +# to perform these actions, thus the default of allow. For multi-user systems +# the system administrator is considered capable enough to restrict what they +# see they need to. +# +# A WARNING to admins: do NOT allow access for users to this system remotely +# UNLESS you fully trust them or you have locked down permissions to halt/reboot +# suspend etc. here first. You have been warned. +# +# FORMAT: +# +# action: nam command to run +# +# user: username allow: halt reboot suspend hibernate +# group: groupname deny: * +# group: * deny: * +# user: * allow: suspend +# user: billy allow: halt reboot +# group: staff deny: halt suspend hibernate +# +# etc. +# +# user and group name can use glob matches (* == all for example) like the +# shell. as can action names allowed or denied. + +action: /bin/mount @MOUNT@ +action: /bin/umount @UMOUNT@ +action: /usr/bin/eject @EJECT@ + +# root is allowed to do anything - but it needs to be here explicitly anyway +user: root allow: * +# members of operator, staff and admin groups should be able to do all +group: operator allow: * +group: staff allow: * +group: admin allow: * +group: sys allow: * +group: wheel allow: * +group: adm allow: * +# common "user" groups for "console users" on desktops/laptops +group: dialout allow: * +group: disk allow: * +group: adm allow: * +group: cdrom allow: * +group: floppy allow: * +group: audio allow: * +group: dip allow: * +group: plugdev allow: * +group: netdev allow: * +group: bluetooth allow: * +group: video allow: * +group: voice allow: * +group: fax allow: * +group: tty allow: * +# put in a list of other users and groups here that are allowed or denied etc. +# e.g. +# user: myuser allow: * +# user: another allow: suspend hibernate +# +# deny everyone else by default +user: * deny: * diff --git a/src/bin/e_sys_l2ping.c b/src/bin/e_sys_l2ping.c new file mode 100644 index 000000000..4805f8171 --- /dev/null +++ b/src/bin/e_sys_l2ping.c @@ -0,0 +1,182 @@ +#include "config.h" + +#include + +#ifdef HAVE_BLUETOOTH +#include +#include +#include +#include +#include +#endif + +#define MAX_SZ 500 + +double +e_sys_l2ping(const char *bluetooth_mac, int timeout_ms) +{ +#ifdef HAVE_BLUETOOTH + char send_buf[L2CAP_CMD_HDR_SIZE + MAX_SZ]; + char recv_buf[L2CAP_CMD_HDR_SIZE + MAX_SZ]; + char tmp[18]; + bdaddr_t bdaddr; + l2cap_cmd_hdr *send_cmd; + l2cap_cmd_hdr *recv_cmd; + struct sockaddr_l2 addr; + socklen_t optlen; + double start; + int fd, err, size, i; + fd_set rfds, wfds, exfds; + struct timeval tv; + + // Create socket + fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_NONBLOCK, BTPROTO_L2CAP); + if (fd < 0) + { + perror("Can't create socket"); + return -1; + } + + // Bind to local address + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&bdaddr, BDADDR_ANY); + bacpy(&addr.l2_bdaddr, &bdaddr); + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + perror("Can't bind socket"); + close(fd); + return -1; + } + + if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) + { + perror("Can't set socket to non-blocking... continue"); + } + + // Connect to remote device + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + str2ba(bluetooth_mac, &addr.l2_bdaddr); + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) + { + perror("Can't bind connect socket"); + close(fd); + return -1; + } + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&exfds); + FD_SET(fd, &wfds); + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + start = ecore_time_get(); + err = select(fd + 1, &rfds, &wfds, &exfds, &tv); + if (err == 0) + { + fprintf(stderr, "Connect timeout %s\n", bluetooth_mac); + return -1; + } + // adjust timeout by how long we waited to connect + timeout_ms -= (ecore_time_get() - start) * 1000; + if (timeout_ms < 1) timeout_ms = 1; + + // Get local address + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + if (getsockname(fd, (struct sockaddr *)&addr, &optlen) < 0) + { + perror("Can't get local address"); + return -1; + } + + ba2str(&addr.l2_bdaddr, tmp); + + size = 44; // use std 44 byte ping size, but no more than MAX_SZ + + send_cmd = (l2cap_cmd_hdr *)send_buf; + send_cmd->ident = 200; + send_cmd->len = htobs(size); + send_cmd->code = L2CAP_ECHO_REQ; + // init buffer with some content + for (i = 0; i < size; i++) + { + // ABCDEF....WXYZABCEF... up to "size" chars + send_buf[L2CAP_CMD_HDR_SIZE + i] = 'A' + (i % 26); + } + // get our time just before a send + start = ecore_time_get(); + // send the ping + if (send(fd, send_buf, L2CAP_CMD_HDR_SIZE + size, 0) <= 0) + { + perror("Send failed"); + return -1; + } + + do + { + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&exfds); + FD_SET(fd, &rfds); + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + err = select(fd + 1, &rfds, &wfds, &exfds, &tv); + if (err < 0) + { + perror("Select failed"); + return -1; + } + else if (err == 0) + { + fprintf(stderr, "Select timeout %s\n", bluetooth_mac); + return -1; + } + err = recv(fd, recv_buf, L2CAP_CMD_HDR_SIZE + size, 0); + if (err == 0) + { + fprintf(stderr, "Disconnect %s\n", bluetooth_mac); + return -1; + } + else if (err < 0) + { + perror("Recv failed"); + return -1; + } + + recv_cmd = (l2cap_cmd_hdr *)recv_buf; + recv_cmd->len = btohs(recv_cmd->len); + // we only want the 200 ident response packets + if (recv_cmd->ident != 200) continue; + if (recv_cmd->code == L2CAP_COMMAND_REJ) + { + fprintf(stderr, "Peer %s doesn't do echo\n", bluetooth_mac); + return -1; + } + if (recv_cmd->len != size) + { + fprintf(stderr, "Size %i echo for %s does not match %i\n", + recv_cmd->len, bluetooth_mac, size); + return -1; + } + if (memcmp(send_buf + L2CAP_CMD_HDR_SIZE, + recv_buf + L2CAP_CMD_HDR_SIZE, size) != 0) + { + fprintf(stderr, "Echo response for %s data does not match sent data\n", bluetooth_mac); + return -1; + } + fprintf(stderr, "Device %s responded\n", bluetooth_mac); + break; + } + while (1); + + close(fd); + // time it took to send and get our response + return ecore_time_get() - start; +#else + (void) bluetooth_mac; + fprintf(stderr, "e_sys_l2ping nop mac=%s timeout=%ims\n", bluetooth_mac, timeout_ms); + return -1; +#endif +} diff --git a/src/bin/e_sys_main.c b/src/bin/e_sys_main.c new file mode 100644 index 000000000..abc2c3bff --- /dev/null +++ b/src/bin/e_sys_main.c @@ -0,0 +1,711 @@ +#include "config.h" + +#define __USE_MISC +#define _SVID_SOURCE +#define _DEFAULT_SOURCE + +#ifdef HAVE_FEATURES_H +# include +#endif + +#include +#include +#ifdef HAVE_ENVIRON +# define _GNU_SOURCE 1 +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_ALLOCA_H +#include +#endif +#include + +#ifdef HAVE_ENVIRON +extern char **environ; +#endif + +/* local subsystem functions */ +#ifdef HAVE_EEZE_MOUNT +static Eina_Bool mountopts_check(const char *opts); +static Eina_Bool mount_args_check(int argc, char **argv, const char *action); +#endif +static int auth_action_ok(char *a, + gid_t gid, + gid_t *gl, + int gn, + gid_t egid); +static int auth_etc_enlightenment_sysactions(char *a, + char *u, + char **g); +static void auth_etc_enlightenment_sysactions_perm(char *path); +static char *get_word(char *s, + char *d); +/* local subsystem globals */ +static Eina_Hash *actions = NULL; +static uid_t uid = -1; + +/* externally accessible functions */ +int +main(int argc, + char **argv) +{ + int i, gn; + int test = 0; + char *action = NULL, *cmd; +#ifdef HAVE_EEZE_MOUNT + Eina_Bool mnt = EINA_FALSE; + const char *act = NULL; +#endif + gid_t gid, gl[65536], egid; + + for (i = 1; i < argc; i++) + { + if ((!strcmp(argv[i], "-h")) || + (!strcmp(argv[i], "-help")) || + (!strcmp(argv[i], "--help"))) + { + printf( + "This is an internal tool for Enlightenment.\n" + "do not use it.\n" + ); + exit(0); + } + } + if (argc >= 3) + { + if ((argc == 3) && (!strcmp(argv[1], "-t"))) + { + test = 1; + action = argv[2]; + } +#ifdef HAVE_EEZE_MOUNT + else + { + const char *s; + + s = strrchr(argv[1], '/'); + if ((!s) || (!s[1])) exit(1); /* eeze always uses complete path */ + s++; + if (strcmp(s, "mount") && strcmp(s, "umount") && strcmp(s, "eject")) exit(1); + mnt = EINA_TRUE; + act = s; + action = argv[1]; + } +#endif + } + else if (argc == 2) + { + action = argv[1]; + } + else + { + exit(1); + } + if (!action) exit(1); + + eina_init(); + + uid = getuid(); + gid = getgid(); + egid = getegid(); + gn = getgroups(65536, gl); + if (gn < 0) + { + printf("ERROR: MEMBER OF MORE THAN 65536 GROUPS\n"); + exit(3); + } + if (setuid(0) != 0) + { + printf("ERROR: UNABLE TO ASSUME ROOT PRIVILEGES\n"); + exit(5); + } + if (setgid(0) != 0) + { + printf("ERROR: UNABLE TO ASSUME ROOT GROUP PRIVILEGES\n"); + exit(7); + } + + if (!auth_action_ok(action, gid, gl, gn, egid)) + { + printf("ERROR: ACTION NOT ALLOWED: %s\n", action); + exit(10); + } + /* we can add more levels of auth here */ + + /* when mounting, this will match the exact path to the exe, + * as required in sysactions.conf + * this is intentionally pedantic for security + */ + cmd = eina_hash_find(actions, action); + if (!cmd) + { + printf("ERROR: UNDEFINED ACTION: %s\n", action); + exit(20); + } + + /* sanitize environment */ +#ifdef HAVE_UNSETENV +# define NOENV(x) unsetenv(x) + /* pass 1 - just nuke known dangerous env vars brutally if possible via + * unsetenv(). if you don't have unsetenv... there's pass 2 and 3 */ + NOENV("IFS"); + NOENV("CDPATH"); + NOENV("LOCALDOMAIN"); + NOENV("RES_OPTIONS"); + NOENV("HOSTALIASES"); + NOENV("NLSPATH"); + NOENV("PATH_LOCALE"); + NOENV("COLORTERM"); + NOENV("LANG"); + NOENV("LANGUAGE"); + NOENV("LINGUAS"); + NOENV("TERM"); + NOENV("LD_PRELOAD"); + NOENV("LD_LIBRARY_PATH"); + NOENV("SHLIB_PATH"); + NOENV("LIBPATH"); + NOENV("AUTHSTATE"); + NOENV("DYLD_*"); + NOENV("KRB_CONF*"); + NOENV("KRBCONFDIR"); + NOENV("KRBTKFILE"); + NOENV("KRB5_CONFIG*"); + NOENV("KRB5_KTNAME"); + NOENV("VAR_ACE"); + NOENV("USR_ACE"); + NOENV("DLC_ACE"); + NOENV("TERMINFO"); + NOENV("TERMINFO_DIRS"); + NOENV("TERMPATH"); + NOENV("TERMCAP"); + NOENV("ENV"); + NOENV("BASH_ENV"); + NOENV("PS4"); + NOENV("GLOBIGNORE"); + NOENV("SHELLOPTS"); + NOENV("JAVA_TOOL_OPTIONS"); + NOENV("PERLIO_DEBUG"); + NOENV("PERLLIB"); + NOENV("PERL5LIB"); + NOENV("PERL5OPT"); + NOENV("PERL5DB"); + NOENV("FPATH"); + NOENV("NULLCMD"); + NOENV("READNULLCMD"); + NOENV("ZDOTDIR"); + NOENV("TMPPREFIX"); + NOENV("PYTHONPATH"); + NOENV("PYTHONHOME"); + NOENV("PYTHONINSPECT"); + NOENV("RUBYLIB"); + NOENV("RUBYOPT"); +# ifdef HAVE_ENVIRON + if (environ) + { + int again; + char *tmp, *p; + + /* go over environment array again and again... safely */ + do + { + again = 0; + + /* walk through and find first entry that we don't like */ + for (i = 0; environ[i]; i++) + { + /* if it begins with any of these, it's possibly nasty */ + if ((!strncmp(environ[i], "LD_", 3)) || + (!strncmp(environ[i], "_RLD_", 5)) || + (!strncmp(environ[i], "LC_", 3)) || + (!strncmp(environ[i], "LDR_", 3))) + { + /* unset it */ + tmp = strdup(environ[i]); + if (!tmp) abort(); + p = strchr(tmp, '='); + if (!p) abort(); + *p = 0; + NOENV(tmp); + free(tmp); + /* and mark our do to try again from the start in case + * unsetenv changes environ ptr */ + again = 1; + break; + } + } + } + while (again); + } +# endif +#endif + + /* pass 2 - clear entire environment so it doesn't exist at all. if you + * can't do this... you're possibly in trouble... but the worst is still + * fixed in pass 3 */ +#ifdef HAVE_CLEARENV + clearenv(); +#else +# ifdef HAVE_ENVIRON + environ = NULL; +# endif +#endif + + /* pass 3 - set path and ifs to minimal defaults */ + putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin"); + putenv("IFS= \t\n"); + + if ((!test) +#ifdef HAVE_EEZE_MOUNT + && (!mnt) +#endif + ) + return system(cmd); +#ifdef HAVE_EEZE_MOUNT + if (mnt) + { + int ret = 0; + const char *mp = NULL; + Eina_Strbuf *buf = NULL; + + if (!act) exit(40); + if (!mount_args_check(argc, argv, act)) exit(40); + /* all options are deemed safe at this point, so away we go! */ + if (!strcmp(act, "mount")) + { + struct stat s; + + mp = argv[5]; + if (stat("/media", &s)) + { + mode_t um; + + um = umask(0); + if (mkdir("/media", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) + { + printf("ERROR: COULD NOT CREATE DIRECTORY /media\n"); + exit(40); + } + umask(um); + } + else if (!S_ISDIR(s.st_mode)) + { + printf("ERROR: NOT A DIRECTORY: /media\n"); + exit(40); + } + + if (stat(mp, &s)) + { + mode_t um; + + um = umask(0); + if (mkdir(mp, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) + { + printf("ERROR: COULD NOT CREATE DIRECTORY %s\n", mp); + exit(40); + } + umask(um); + } + else if (!S_ISDIR(s.st_mode)) + { + printf("ERROR: NOT A DIRECTORY: %s\n", mp); + exit(40); + } + } + buf = eina_strbuf_new(); + if (!buf) exit(30); + for (i = 1; i < argc; i++) + eina_strbuf_append_printf(buf, "%s ", argv[i]); + ret = system(eina_strbuf_string_get(buf)); + if ((!strcmp(act, "umount")) && (!ret)) + { + Eina_Iterator *it; + char path[PATH_MAX]; + const char *s; + struct stat st; + Eina_Bool rm = EINA_TRUE; + + mp = strrchr(argv[2], '/'); + if (!mp) return ret; + snprintf(path, sizeof(path), "/media%s", mp); + if (stat(path, &st)) return ret; + if (!S_ISDIR(st.st_mode)) return ret; + it = eina_file_ls(path); + EINA_ITERATOR_FOREACH(it, s) + { + /* don't delete any directories with files in them */ + rm = EINA_FALSE; + eina_stringshare_del(s); + } + if (rm) + { + if (rmdir(path)) + printf("ERROR: COULD NOT UNLINK MOUNT POINT %s\n", path); + } + } + return ret; + } +#endif + eina_shutdown(); + + return 0; +} + +/* local subsystem functions */ +#ifdef HAVE_EEZE_MOUNT +static Eina_Bool +mountopts_check(const char *opts) +{ + char buf[64]; + const char *p; + char *end; + unsigned long muid; + Eina_Bool nosuid, nodev, noexec, nuid; + + nosuid = nodev = noexec = nuid = EINA_FALSE; + + /* these are the only possible options which can be present here; check them strictly */ + if (eina_strlcpy(buf, opts, sizeof(buf)) >= sizeof(buf)) return EINA_FALSE; + for (p = buf; p && p[1]; p = strchr(p + 1, ',')) + { + if (p[0] == ',') p++; +#define CMP(OPT) \ + if (!strncmp(p, OPT, sizeof(OPT) - 1)) + + CMP("nosuid,") + { + nosuid = EINA_TRUE; + continue; + } + CMP("nodev,") + { + nodev = EINA_TRUE; + continue; + } + CMP("noexec,") + { + noexec = EINA_TRUE; + continue; + } + CMP("utf8,") continue; + CMP("utf8=0,") continue; + CMP("utf8=1,") continue; + CMP("iocharset=utf8,") continue; + CMP("uid=") + { + p += 4; + errno = 0; + muid = strtoul(p, &end, 10); + if (muid == ULONG_MAX) return EINA_FALSE; + if (errno) return EINA_FALSE; + if (end[0] != ',') return EINA_FALSE; + if (muid != uid) return EINA_FALSE; + nuid = EINA_TRUE; + continue; + } + return EINA_FALSE; + } + if ((!nosuid) || (!nodev) || (!noexec) || (!nuid)) return EINA_FALSE; + return EINA_TRUE; +} + +static Eina_Bool +check_uuid(const char *uuid) +{ + const char *p; + + for (p = uuid; p[0]; p++) + if ((!isalnum(*p)) && (*p != '-')) return EINA_FALSE; + return EINA_TRUE; +} + +static Eina_Bool +mount_args_check(int argc, char **argv, const char *action) +{ + Eina_Bool opts = EINA_FALSE; + struct stat st; + const char *node; + char buf[PATH_MAX]; + + if (!strcmp(action, "mount")) + { + /* will ALWAYS be: + /path/to/mount -o nosuid,uid=XYZ,[utf8,] UUID=XXXX-XXXX[-XXXX-XXXX] /media/$devnode + */ + if (argc != 6) return EINA_FALSE; + if (argv[2][0] == '-') + { + /* disallow any -options other than -o */ + if (strcmp(argv[2], "-o")) return EINA_FALSE; + opts = mountopts_check(argv[3]); + } + if (!opts) return EINA_FALSE; + if (!strncmp(argv[4], "UUID=", sizeof("UUID=") - 1)) + { + if (!check_uuid(argv[4] + 5)) return EINA_FALSE; + } + else + { + if (strncmp(argv[4], "/dev/", 5)) return EINA_FALSE; + if (stat(argv[4], &st)) return EINA_FALSE; + } + + node = strrchr(argv[5], '/'); + if (!node) return EINA_FALSE; + if (!node[1]) return EINA_FALSE; + if (node - argv[5] != 6) return EINA_FALSE; + snprintf(buf, sizeof(buf), "/dev%s", node); + if (stat(buf, &st)) return EINA_FALSE; + } + else if (!strcmp(action, "umount")) + { + /* will ALWAYS be: + /path/to/umount /dev/$devnode + */ + if (argc != 3) return EINA_FALSE; + if (strncmp(argv[2], "/dev/", 5)) return EINA_FALSE; + if (stat(argv[2], &st)) return EINA_FALSE; + node = strrchr(argv[2], '/'); + if (!node) return EINA_FALSE; + if (!node[1]) return EINA_FALSE; + if (node - argv[2] != 4) return EINA_FALSE; + /* this is good, but it prevents umounting user-mounted removable media; + * need to figure out a better way... + * + snprintf(buf, sizeof(buf), "/media%s", node); + if (stat(buf, &st)) return EINA_FALSE; + if (!S_ISDIR(st.st_mode)) return EINA_FALSE; + */ + } + else if (!strcmp(action, "eject")) + { + /* will ALWAYS be: + /path/to/eject /dev/$devnode + */ + if (argc != 3) return EINA_FALSE; + if (strncmp(argv[2], "/dev/", 5)) return EINA_FALSE; + if (stat(argv[2], &st)) return EINA_FALSE; + node = strrchr(argv[2], '/'); + if (!node) return EINA_FALSE; + if (!node[1]) return EINA_FALSE; + if (node - argv[2] != 4) return EINA_FALSE; + } + else return EINA_FALSE; + return EINA_TRUE; +} + +#endif + +static int +auth_action_ok(char *a, + gid_t gid, + gid_t *gl, + int gn, + gid_t egid) +{ + struct passwd *pw; + struct group *gp; + char *usr = NULL, **grp, *g; + int ret, i, j; + + pw = getpwuid(uid); + if (!pw) return 0; + usr = pw->pw_name; + if (!usr) return 0; + grp = alloca(sizeof(char *) * (gn + 1 + 1)); + j = 0; + gp = getgrgid(gid); + if (gp) + { + grp[j] = gp->gr_name; + j++; + } + for (i = 0; i < gn; i++) + { + if (gl[i] != egid) + { + gp = getgrgid(gl[i]); + if (gp) + { + g = alloca(strlen(gp->gr_name) + 1); + strcpy(g, gp->gr_name); + grp[j] = g; + j++; + } + } + } + grp[j] = NULL; + /* first stage - check: + * PREFIX/etc/enlightenment/sysactions.conf + */ + ret = auth_etc_enlightenment_sysactions(a, usr, grp); + if (ret == 1) return 1; + else if (ret == -1) + return 0; + /* the DEFAULT - allow */ + return 1; +} + +static int +auth_etc_enlightenment_sysactions(char *a, + char *u, + char **g) +{ + FILE *f; + char file[4096], buf[4096], id[4096], ugname[4096], perm[4096], act[4096]; + char *p, *pp, *s, **gp; + int ok = 0; + size_t len, line = 0; + int allow = 0; + int deny = 0; + + snprintf(file, sizeof(file), "/etc/enlightenment/sysactions.conf"); + f = fopen(file, "r"); + if (!f) + { + snprintf(file, sizeof(file), PACKAGE_SYSCONF_DIR "/enlightenment/sysactions.conf"); + f = fopen(file, "r"); + if (!f) return 0; + } + + auth_etc_enlightenment_sysactions_perm(file); + + while (fgets(buf, sizeof(buf), f)) + { + line++; + len = strlen(buf); + if (len < 1) continue; + if (buf[len - 1] == '\n') buf[len - 1] = 0; + /* format: + * + * # comment + * user: username [allow:|deny:] halt reboot ... + * group: groupname [allow:|deny:] suspend ... + */ + if (buf[0] == '#') continue; + p = buf; + p = get_word(p, id); + p = get_word(p, ugname); + pp = p; + p = get_word(p, perm); + allow = 0; + deny = 0; + if (!strcmp(id, "user:")) + { + if (!fnmatch(ugname, u, 0)) + { + if (!strcmp(perm, "allow:")) allow = 1; + else if (!strcmp(perm, "deny:")) + deny = 1; + else + goto malformed; + } + else + continue; + } + else if (!strcmp(id, "group:")) + { + Eina_Bool matched = EINA_FALSE; + + for (gp = g; *gp; gp++) + { + if (!fnmatch(ugname, *gp, 0)) + { + matched = EINA_TRUE; + if (!strcmp(perm, "allow:")) allow = 1; + else if (!strcmp(perm, "deny:")) + deny = 1; + else + goto malformed; + } + } + if (!matched) continue; + } + else if (!strcmp(id, "action:")) + { + while ((*pp) && (isspace(*pp))) + pp++; + s = eina_hash_find(actions, ugname); + if (s) eina_hash_del(actions, ugname, s); + if (!actions) actions = eina_hash_string_superfast_new(free); + eina_hash_add(actions, ugname, strdup(pp)); + continue; + } + else if (id[0] == 0) + continue; + else + goto malformed; + + for (;; ) + { + p = get_word(p, act); + if (act[0] == 0) break; + if (!fnmatch(act, a, 0)) + { + if (allow) ok = 1; + else if (deny) + ok = -1; + goto done; + } + } + + continue; +malformed: + printf("WARNING: %s:%zu\n" + "LINE: '%s'\n" + "MALFORMED LINE. SKIPPED.\n", + file, line, buf); + } +done: + fclose(f); + return ok; +} + +static void +auth_etc_enlightenment_sysactions_perm(char *path) +{ + struct stat st; + if (stat(path, &st) == -1) + return; + + if ((st.st_mode & S_IWGRP) || (st.st_mode & S_IXGRP) || + (st.st_mode & S_IWOTH) || (st.st_mode & S_IXOTH)) + { + printf("ERROR: CONFIGURATION FILE HAS BAD PERMISSIONS (writable by group and/or others)\n"); + exit(10); + } +} + +static char * +get_word(char *s, + char *d) +{ + char *p1, *p2; + + p1 = s; + p2 = d; + while (*p1) + { + if (p2 == d) + { + if (isspace(*p1)) + { + p1++; + continue; + } + } + if (isspace(*p1)) break; + *p2 = *p1; + p1++; + p2++; + } + *p2 = 0; + return p1; +} + diff --git a/src/bin/meson.build b/src/bin/meson.build index f17bbb881..1de73fe21 100644 --- a/src/bin/meson.build +++ b/src/bin/meson.build @@ -535,6 +535,17 @@ executable('enlightenment_alert', install : true ) +executable('enlightenment_sys', + [ 'e_sys_main.c' ], + include_directories: include_directories('../..'), + dependencies : [dep_eina, dep_ecore, dep_bluez], + c_args : suid_cflags, + link_args : suid_ldflags, + install_dir : dir_e_utils, + install : true + ) +suid_exes += join_paths(dir_e_utils, 'enlightenment_sys') + executable('enlightenment_ckpasswd', 'e_ckpasswd_main.c', include_directories: include_directories('../..'),