/** * MIT/X Consortium License * * Copyright © 2015 Mattias Andrée * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "bus.h" #include #include #include #include #include #include #include #include #include /** * Statement wrapper that goes to `fail` on failure */ #define t(inst) if ((inst) == -1) goto fail /** * The name of the process */ char *argv0; /** * The command to spawn when a message is received */ static const char *command; /** * Spawn a command because a message has been received * * @param message The received message * @param user_data Not used * @return 1 (continue listening) on success, -1 on error */ static int spawn_continue(const char *message, void *user_data) { pid_t pid; if (!message) return 1; if ((pid = fork())) return pid == -1 ? -1 : 1; setenv("arg", message, 1); execlp("sh", "sh", "-c", command, NULL); perror(argv0); exit(1); (void) user_data; } /** * Spawn a command because a message has been received * * @param message The received message * @param user_data Not used * @return 0 (stop listening) on success, -1 on error, or 1 if `message` is `NULL` */ static int spawn_break(const char *message, void *user_data) { pid_t pid; if (!message) return 1; if ((pid = fork())) return pid == -1 ? -1 : 0; setenv("arg", message, 1); execlp("sh", "sh", "-c", command, NULL); perror(argv0); exit(1); (void) user_data; } /** * Parse a permission string * * @param str The permission string * @param andnot Output paramter for the mask of bits to remove (before applying `*or`) * @param or Output paramter for the mask of bits to apply * @return 0 on success, -1 on error */ static int parse_mode(const char *str, mode_t *andnot, mode_t *or) { #define U S_IRWXU #define G S_IRWXG #define O S_IRWXO const char *s = str; int numerical = 1; char op = '='; int bits; *andnot = 0; *or = 0; if (!*s) return errno = 0, -1; for (s = str; *s; s++) { if (('0' >= *s) || (*s >= '9')) { numerical = 0; break; } } if (numerical) { *andnot = U | G | O; *or = atoi(str); *or &= U | G | O; *or = (*or & U) ? (*or | U) : (*or & ~U); *or = (*or & G) ? (*or | G) : (*or & ~G); *or = (*or & O) ? (*or | O) : (*or & ~O); return 0; } for (s = str; *s; s++) { if (strchr("+-=", *s)) { op = *s; } else if (strchr("ugo", *s)) { if (*str == 'u') bits = U; else if (*str == 'g') bits = G; else bits = O; if (op == '+') { *andnot |= bits; *or |= bits; } else if (op == '-') { *andnot |= bits; *or &= ~bits; } else if (op == '=') { *andnot |= U | G | O; *or |= bits; } } else { return errno = 0, -1; } } return 0; } /** * Parse a user name/identifier string * * @param str The user's name or identifier, parsings stops * a the first ':' or and the end of the srting * @param uid Output parameter for the user's identifier * @return 0 on success, -1 on error */ static int parse_uid(const char *str, uid_t *uid) { const char *s = str; int numerical = 1; uid_t rc = 0; struct passwd *pwd; if (!*s || (*s == ':')) return errno = 0, -1; for (s = str; *s && (*s != ':'); s++) { if (('0' >= *s) || (*s >= '9')) { numerical = 0; break; } } if (numerical) { for (s = str; !*s || (*s == ':'); s++) rc = (rc * 10) + (*s & 15); *uid = rc; return 0; } pwd = getpwnam(str); if (!pwd) return -1; *uid = pwd->pw_uid; return 0; } /** * Parse a group name/identifier string * * @param str The group's name or identifier * @param gid Output parameter for the group's identifier * @return 0 on success, -1 on error */ static int parse_gid(const char *str, gid_t *gid) { const char *s = str; int numerical = 1; uid_t rc = 0; struct group *grp; if (!*s || strchr(s, ':')) return errno = 0, -1; for (s = str; *s; s++) { if (('0' >= *s) || (*s >= '9')) { numerical = 0; break; } } if (numerical) { for (s = str; !*s || (*s == ':'); s++) rc = (rc * 10) + (*s & 15); *gid = rc; return 0; } grp = getgrnam(str); if (!grp) return -1; *gid = grp->gr_gid; return 0; } /** * Parse a ownership string * * @param str The ownership string * @param uid Output parameter for the owner, `NULL` if `str` only contains the group * @param gid Output parameter for the group, `NULL` if `str` only contains the owner * @return 0 on success, -1 on error */ static int parse_owner(const char *str, uid_t *uid, gid_t *gid) { int r = 0; if (!uid) return parse_gid(str, gid); if (!gid) return parse_uid(str, uid); r = parse_gid(strchr(str, ':') + 1, gid); if (r) return r; return parse_uid(str, uid); } /** * Main function of the command line interface for the bus system * * @param argc The number of elements in `argv` * @param argv The command. Valid commands: * create [] # create a bus * remove # remove a bus * listen # listen for new messages * wait # listen for one new message * broadcast # broadcast a message * chmod # change permissions * chown [:] # change ownership * chgrp # change group * will be spawned with $arg set to the message * @return 0 on sucess, 1 on error, 2 on invalid command */ int main(int argc, char *argv[]) { bus_t bus; char *file; struct stat attr; uid_t uid; gid_t gid; mode_t mode_andnot, mode_or; argv0 = *argv; /* Create a new bus with selected name. */ if ((argc == 3) && !strcmp(argv[1], "create")) { t(bus_create(argv[2], 0, NULL)); /* TODO add -x */ /* Create a new bus with random name. */ } else if ((argc == 2) && !strcmp(argv[1], "create")) { t(bus_create(NULL, 0, &file)); printf("%s\n", file); free(file); /* Remove a bus. */ } else if ((argc == 3) && !strcmp(argv[1], "remove")) { t(bus_unlink(argv[2])); /* Listen on a bus in a loop. */ } else if ((argc == 4) && !strcmp(argv[1], "listen")) { command = argv[3]; t(bus_open(&bus, argv[2], BUS_RDONLY)); t(bus_read(&bus, spawn_continue, NULL)); t(bus_close(&bus)); /* Listen on a bus for one message. */ } else if ((argc == 4) && !strcmp(argv[1], "wait")) { command = argv[3]; t(bus_open(&bus, argv[2], BUS_RDONLY)); t(bus_read(&bus, spawn_break, NULL)); t(bus_close(&bus)); /* Broadcast a message on a bus. */ } else if ((argc == 4) && !strcmp(argv[1], "broadcast")) { t(bus_open(&bus, argv[2], BUS_WRONLY)); t(bus_write(&bus, argv[3], 0)); t(bus_close(&bus)); /* TODO add -n */ /* Change permissions. */ } else if ((argc == 4) && !strcmp(argv[1], "chmod")) { t(parse_mode(argv[2], &mode_andnot, &mode_or)); t(stat(argv[3], &attr)); attr.st_mode &= ~mode_andnot; attr.st_mode |= mode_or; t(bus_chmod(argv[3], attr.st_mode)); /* Change ownership. */ } else if ((argc == 4) && !strcmp(argv[1], "chown")) { if (strchr(argv[2], ':')) { t(parse_owner(argv[2], &uid, &gid)); t(bus_chown(argv[3], uid, gid)); } else { t(parse_owner(argv[2], &uid, NULL)); t(stat(argv[3], &attr)); t(bus_chown(argv[3], uid, attr.st_gid)); } /* Change group. */ } else if ((argc == 4) && !strcmp(argv[1], "chgrp")) { t(parse_owner(argv[2], NULL, &gid)); t(stat(argv[3], &attr)); t(bus_chown(argv[3], attr.st_uid, gid)); } else return 2; return 0; fail: if (errno == 0) return 2; perror(argv0); return 1; }