/* dbus-triggerd.c * - tool to trigger a shell-command upon receiving a given dbus-signal. * * Copyright (C) 2011 Robin Gareus * * based on dbus-monitor.c * Copyright (C) 2003 Philip Blundell * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef VERSIONSTRING #define VERSIONSTRING "0.3.2" #endif #define _GNU_SOURCE // asprintf #include #include #include #include #include #include #include char *handler_cmd = NULL; int want_verbose = 0; int want_shell = 0; int want_quit = 0; int want_wait = 1; #define SHELL_PATH "/bin/sh" #define SHELL_NAME "sh" /* from dbus-src/tools/dbus-print-message.c */ static const char* type_to_name (int message_type) { switch (message_type) { case DBUS_MESSAGE_TYPE_SIGNAL: return "signal"; case DBUS_MESSAGE_TYPE_METHOD_CALL: return "method call"; case DBUS_MESSAGE_TYPE_METHOD_RETURN: return "method return"; case DBUS_MESSAGE_TYPE_ERROR: return "error"; default: return "(unknown message type)"; } } static DBusHandlerResult handler_func (DBusConnection *connection, DBusMessage *message, void *user_data) { char *cmdargs = NULL; char *cmd = NULL; const char *sender; int message_type; //print_message (message, FALSE); message_type = dbus_message_get_type (message); sender = dbus_message_get_sender (message); if (dbus_message_is_signal (message, DBUS_INTERFACE_LOCAL, "Disconnected")) exit (0); /* ignore non signal messages */ if ( !strcmp(sender,DBUS_INTERFACE_DBUS) || message_type != DBUS_MESSAGE_TYPE_SIGNAL) /* Conceptually we want this to be * DBUS_HANDLER_RESULT_NOT_YET_HANDLED, but this raises * some problems. See bug 1719. */ return DBUS_HANDLER_RESULT_HANDLED; if (want_verbose) printf ("RCV: serial=%u path=%s; interface=%s; member=%s\n", dbus_message_get_serial (message), dbus_message_get_path (message), dbus_message_get_interface (message), dbus_message_get_member (message)); if (!handler_cmd) { return DBUS_HANDLER_RESULT_HANDLED; } /* parse dbus-args and build commandline */ #define ARGSIZ (BUFSIZ) #define ARGS_ADD_TYP(TYP,FMT,CAST,ARG) \ aofft+=snprintf(abuft+aofft, ARGSIZ-aofft, TYP ":"); \ aofft+=snprintf(abuft+aofft, ARGSIZ-aofft, FMT, (CAST)(ARG)); \ aofft+=snprintf(abuft+aofft, ARGSIZ-aofft, " "); #define ARGS_ADD_PLN(TYP,FMT,CAST,ARG) \ aoffp+=snprintf(abufp+aoffp, ARGSIZ-aoffp, FMT, (CAST)(ARG)); \ aofft+=snprintf(abuft+aofft, ARGSIZ-aofft, " "); #define ARGS_ADD_DEBUG(TYP,FMT,CAST,ARG) \ printf (FMT, (CAST)(ARG)); \ printf ("\n"); #define ARGS_ADD_ARGV(TYP,FMT,CAST,ARG) \ argv=(char**) realloc((void*)argv, (argc+2)*sizeof(char*)); \ asprintf(&argv[argc++], FMT, (CAST)(ARG)); argv[argc] = 0; #define ARGS_ADD(TYP,FMT,CAST,ARG) \ if (want_shell) { \ ARGS_ADD_PLN(TYP,FMT,CAST,ARG); \ ARGS_ADD_TYP(TYP,FMT,CAST,ARG); \ } else { \ ARGS_ADD_ARGV(TYP,FMT,CAST,ARG); \ } char *abufp=NULL; int aoffp= 0; char *abuft=NULL; int aofft=0; char **argv; int argc=0; if (want_shell) { abufp = malloc(ARGSIZ * sizeof(char)); abuft = malloc(ARGSIZ * sizeof(char)); abuft[0]=0; abufp[0]=0; cmd=SHELL_PATH; argv=(char**) calloc(4,sizeof(char*)); argv[argc++] = strdup(SHELL_NAME); argv[argc++] = strdup("-c"); argv[argc] = 0; } else { cmd=handler_cmd; argv=(char**) calloc(2,sizeof(char*)); argv[argc++] = strdup(basename(handler_cmd)); argv[argc] = 0; } if (!want_shell) { ARGS_ADD_ARGV("serial", "%u", unsigned, dbus_message_get_serial (message)); ARGS_ADD_ARGV("type", "%s", char *, type_to_name(message_type)); ARGS_ADD_ARGV("dest", "%s", char *, dbus_message_get_destination (message)); ARGS_ADD_ARGV("sender", "%s", char *, sender); ARGS_ADD_ARGV("path", "%s", char *, dbus_message_get_path (message)); ARGS_ADD_ARGV("interface", "%s", char *, dbus_message_get_interface (message)); ARGS_ADD_ARGV("member", "%s", char *, dbus_message_get_member (message)); } DBusMessageIter iter; dbus_message_iter_init (message, &iter); do { int type = dbus_message_iter_get_arg_type (&iter); if (type == DBUS_TYPE_INVALID) break; switch (type) { #define PARSE_DBUS_TYPE(DTYPEE, DTYPET, TYP, FMT, CAST) \ case DTYPEE: \ { \ DTYPET val; \ dbus_message_iter_get_basic (&iter, &val); \ ARGS_ADD(TYP, FMT, CAST, val); \ break; \ } PARSE_DBUS_TYPE(DBUS_TYPE_STRING, char *, "string", (want_shell?"\"%s\"":"%s"), char *) PARSE_DBUS_TYPE(DBUS_TYPE_INT16, dbus_int16_t, "int16", "%d", int) PARSE_DBUS_TYPE(DBUS_TYPE_UINT16, dbus_uint16_t, "uint16", "%u", unsigned int) PARSE_DBUS_TYPE(DBUS_TYPE_INT32, dbus_int32_t, "int32", "%ld", long) PARSE_DBUS_TYPE(DBUS_TYPE_UINT32, dbus_uint32_t, "uint32", "%lu", unsigned long) PARSE_DBUS_TYPE(DBUS_TYPE_INT64, dbus_int64_t, "int64", "%lld", long long) PARSE_DBUS_TYPE(DBUS_TYPE_UINT64, dbus_uint64_t, "uint64", "%llu", unsigned long long) PARSE_DBUS_TYPE(DBUS_TYPE_DOUBLE, double, "double", "%lg", double) PARSE_DBUS_TYPE(DBUS_TYPE_BYTE, unsigned char, "byte", "%02x", unsigned char) //PARSE_DBUS_GENERIC(DBUS_TYPE_BOOLEAN, dbus_bool_t, "boolean", "%s", val ? "true" : "false"); case DBUS_TYPE_BOOLEAN: { dbus_bool_t val; dbus_message_iter_get_basic (&iter, &val); ARGS_ADD("boolean", "%s", char *, val ? "true" : "false") break; } // Ignore others default: printf ("WARN: dbus-triggerd ignored arg type '%c'\n", type); break; } } while (dbus_message_iter_next (&iter)); /* NOW build command-line */ if (want_shell) { /* replace placeholders in shell command-line */ #define SUBST_PLACEHOLDER(PLACEHOLDER, REPLACEMENT) \ { \ char *tmp; \ const char *rplc = (REPLACEMENT)?(REPLACEMENT):""; \ int l0=strlen(PLACEHOLDER); \ int l1=strlen(rplc); \ while ((tmp=strstr(cmdargs, PLACEHOLDER))) { \ char *new =malloc((strlen(cmdargs) +1 + l1 - l0)*sizeof(char)); \ new[0]=0; \ strncat(new, cmdargs, (tmp-cmdargs)); \ strncat(new, rplc, l1); \ strcat(new, tmp+l0); \ free(cmdargs); cmdargs=new; \ } \ } #define SUBST_PLACEHOLDER_PRINTF(PLACEHOLDER, FMT, ARG) \ { \ char *tbuf=NULL;\ asprintf(&tbuf, FMT, ARG); \ SUBST_PLACEHOLDER(PLACEHOLDER,tbuf); \ free(tbuf); \ } cmdargs=strdup(handler_cmd); /* parse placeholders in given command */ SUBST_PLACEHOLDER("%interface%", dbus_message_get_interface (message)); SUBST_PLACEHOLDER("%path%", dbus_message_get_path (message)); SUBST_PLACEHOLDER("%dest%", dbus_message_get_destination (message)); SUBST_PLACEHOLDER("%sender%", sender); SUBST_PLACEHOLDER("%member%", dbus_message_get_member (message)); SUBST_PLACEHOLDER("%type%", type_to_name(message_type)); SUBST_PLACEHOLDER_PRINTF("%typeenum%", "%d", message_type); SUBST_PLACEHOLDER_PRINTF("%serial%", "%u", dbus_message_get_serial (message)); SUBST_PLACEHOLDER("%args%", abufp); /* note: this could potentially inject shell commands */ SUBST_PLACEHOLDER("%typedargs%", abuft); /* note: this could potentially inject shell commands */ argv[argc++] = strdup(cmdargs); argv[argc] = 0; } /* invoke external command */ if (want_verbose) { printf("EXE: %s ", cmd); for (argc=0;argv[argc];++argc) printf("'%s' ", argv[argc]); printf("\n"); } pid_t pid=fork(); if (pid==0) { /* child process */ execve (cmd, (char *const *) argv, environ); fprintf(stderr,"ERR: exec returned.\n"); exit(127); } /* parent/main process */ if (pid < 0 ) { fprintf(stderr,"ERR: can not fork child process\n"); } else { if (want_wait) { int rv; waitpid(pid, &rv, 0); if (want_verbose) printf("EXE: retuned: %i\n", rv); // TODO use WEXITSTATUS.. if (want_quit && rv) { if (WIFEXITED(rv)) { if (want_verbose) fprintf(stderr, "ERR: Child exited with status: %d \n",WEXITSTATUS(rv)); exit(WEXITSTATUS(rv)); } if (WIFSIGNALED(rv)) { if (want_verbose) fprintf(stderr, "ERR: Child exited via signal: %d\n",WTERMSIG(rv)); exit(1); } exit(-1); } } } /* clean up */ for (argc=0;argv[argc];++argc) { free(argv[argc]); } free (argv); if (cmdargs) free(cmdargs); if (abufp) free(abufp); if (abuft) free(abuft); return DBUS_HANDLER_RESULT_HANDLED; } static void printversion (char *name, int ecode) { fprintf (stdout, "%s version %s\n", name, VERSIONSTRING); fprintf (stdout, " GPL (C) 2011 Robin Garues \n"); exit (ecode); } static void usage (char *name, int ecode) { fprintf (stderr, "Usage: %s [OPTIONS] [watch expressions]\n", name); fprintf (stderr, "Options:\n"); fprintf (stderr, " --exec | --shell '