xhotplug.c in xhotplug

at master

1/* See LICENSE file for copyright and license details. */
2
3#include <errno.h>
4#include <libgen.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <sys/stat.h>
9#include <sys/syslimits.h>
10#include <unistd.h>
11#include <xcb/xcb.h>
12#include <xcb/randr.h>
13
14static char *argv0;
15
16enum {
17 ERR_NONE,
18 ERR_UNKNOWN_OPTION,
19 ERR_NUM_ARGS,
20 ERR_NOT_EXECUTABLE,
21 ERR_PLEDGE
22};
23
24void
25usage(const int e_val) {
26 printf("usage: %s [-hv] script\n", argv0);
27 exit(e_val);
28}
29
30void
31xhotplug(const char *cmd) {
32 xcb_connection_t* conn;
33 xcb_screen_t* screen;
34 xcb_window_t window;
35 xcb_generic_event_t* evt;
36 xcb_randr_screen_change_notify_event_t* randr_evt;
37 xcb_timestamp_t last_time;
38 int monitor_connected = 1;
39
40 conn = xcb_connect(NULL, NULL);
41
42 screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
43 window = screen->root;
44 xcb_randr_select_input(conn, window, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
45 xcb_flush(conn);
46
47 while ((evt = xcb_wait_for_event(conn)) != NULL) {
48 if (evt->response_type & XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE) {
49 randr_evt = (xcb_randr_screen_change_notify_event_t*) evt;
50 if (last_time != randr_evt->timestamp) {
51 last_time = randr_evt->timestamp;
52 monitor_connected = !monitor_connected;
53 if (monitor_connected) {
54 system(cmd);
55 } else {
56 system(cmd);
57 }
58 }
59 }
60 free(evt);
61 }
62 xcb_disconnect(conn);
63}
64
65int
66can_execute(const char *path) {
67 uid_t uid = getuid();
68 gid_t gid = getgid();
69 gid_t gidset[NGROUPS_MAX];
70 struct stat sb;
71
72 if (stat(path, &sb) != 0) {
73 fprintf(stderr, "%s: ERROR -- Unable to stat file %s\n", argv0, path);
74 fprintf(stderr, "%s: errno -- %d\n", argv0, errno);
75 return 0;
76 }
77 if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
78 fprintf(stderr, "%s: script is writeable by others\n", argv0);
79 return 0;
80 }
81 if (sb.st_uid != uid && sb.st_uid != 0) {
82 fprintf(stderr, "%s: script not owned by user or root\n", argv0);
83 return 0;
84 }
85 if (sb.st_uid == uid && (sb.st_mode & S_IXUSR) == 0) {
86 fprintf(stderr, "%s: script is not executable\n", argv0);
87 return 0;
88 }
89 if (sb.st_uid == 0) {
90 for (int i=0; i<getgroups(NGROUPS_MAX, gidset); i++) {
91 if (gidset[i] == gid) {
92 if ((sb.st_mode & S_IXGRP) == 0) {
93 fprintf(stderr, "%s: script is not executable\n", argv0);
94 return 0;
95 }
96 else {
97 return 1;
98 }
99 }
100 }
101 }
102 return 1;
103}
104
105int
106main(int argc, char **argv) {
107 char ch, script[PATH_MAX], sbuf[PATH_MAX];
108 ssize_t sbuf_len;
109 int pval;
110
111 argv0 = basename(argv[0]);
112
113 while ((ch = getopt(argc, argv, "vh")) != -1) {
114 switch (ch) {
115 case 'v':
116 printf("%s %s\n", argv0, VERSION);
117 exit(ERR_NONE);
118 break;
119 case 'h':
120 usage(ERR_NONE);
121 break;
122 default:
123 usage(ERR_UNKNOWN_OPTION);
124 }
125 }
126 argc -= optind;
127 argv += optind;
128
129 if ( argc < 1 || argc > 1) {
130 printf("%s: invalid number of arguments\n", argv0);
131 usage(ERR_NUM_ARGS);
132 }
133
134 sbuf_len = strnlen(argv[0], PATH_MAX);
135 strncpy(script, argv[0], PATH_MAX - 1);
136 script[PATH_MAX - 1] = '\0';
137 while ((sbuf_len = readlink(script, sbuf, PATH_MAX)) != -1) {
138 sbuf[sbuf_len] = '\0';
139 strncpy(script, sbuf, PATH_MAX - 1);
140 script[PATH_MAX - 1] = '\0';
141 }
142
143 if(!can_execute(script)) {
144 fprintf(stderr, "%s: cannot run provided script. quitting.\n", argv0);
145 usage(ERR_NOT_EXECUTABLE);
146 }
147
148#ifdef __OpenBSD__
149 if ((pval = pledge("stdio unix rpath proc exec", NULL)) != 0) {
150 fprintf(stderr, "%s: call to pledge(2) failed -- %d. quitting.\n", argv0, pval);
151 exit(ERR_PLEDGE);
152 }
153#endif
154
155 xhotplug(script);
156}