##// END OF EJS Templates
chg: verify XDG_RUNTIME_DIR...
Jun Wu -
r30884:a68510b6 default
parent child Browse files
Show More
@@ -1,439 +1,457 b''
1 1 /*
2 2 * A fast client for Mercurial command server
3 3 *
4 4 * Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
5 5 *
6 6 * This software may be used and distributed according to the terms of the
7 7 * GNU General Public License version 2 or any later version.
8 8 */
9 9
10 10 #include <assert.h>
11 11 #include <errno.h>
12 12 #include <fcntl.h>
13 13 #include <signal.h>
14 14 #include <stdio.h>
15 15 #include <stdlib.h>
16 16 #include <string.h>
17 17 #include <sys/file.h>
18 18 #include <sys/stat.h>
19 19 #include <sys/types.h>
20 20 #include <sys/un.h>
21 21 #include <sys/wait.h>
22 22 #include <time.h>
23 23 #include <unistd.h>
24 24
25 25 #include "hgclient.h"
26 26 #include "procutil.h"
27 27 #include "util.h"
28 28
29 29 #ifndef PATH_MAX
30 30 #define PATH_MAX 4096
31 31 #endif
32 32
33 33 struct cmdserveropts {
34 34 char sockname[PATH_MAX];
35 35 char initsockname[PATH_MAX];
36 36 char redirectsockname[PATH_MAX];
37 37 size_t argsize;
38 38 const char **args;
39 39 };
40 40
41 41 static void initcmdserveropts(struct cmdserveropts *opts) {
42 42 memset(opts, 0, sizeof(struct cmdserveropts));
43 43 }
44 44
45 45 static void freecmdserveropts(struct cmdserveropts *opts) {
46 46 free(opts->args);
47 47 opts->args = NULL;
48 48 opts->argsize = 0;
49 49 }
50 50
51 51 /*
52 52 * Test if an argument is a sensitive flag that should be passed to the server.
53 53 * Return 0 if not, otherwise the number of arguments starting from the current
54 54 * one that should be passed to the server.
55 55 */
56 56 static size_t testsensitiveflag(const char *arg)
57 57 {
58 58 static const struct {
59 59 const char *name;
60 60 size_t narg;
61 61 } flags[] = {
62 62 {"--config", 1},
63 63 {"--cwd", 1},
64 64 {"--repo", 1},
65 65 {"--repository", 1},
66 66 {"--traceback", 0},
67 67 {"-R", 1},
68 68 };
69 69 size_t i;
70 70 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
71 71 size_t len = strlen(flags[i].name);
72 72 size_t narg = flags[i].narg;
73 73 if (memcmp(arg, flags[i].name, len) == 0) {
74 74 if (arg[len] == '\0') {
75 75 /* --flag (value) */
76 76 return narg + 1;
77 77 } else if (arg[len] == '=' && narg > 0) {
78 78 /* --flag=value */
79 79 return 1;
80 80 } else if (flags[i].name[1] != '-') {
81 81 /* short flag */
82 82 return 1;
83 83 }
84 84 }
85 85 }
86 86 return 0;
87 87 }
88 88
89 89 /*
90 90 * Parse argv[] and put sensitive flags to opts->args
91 91 */
92 92 static void setcmdserverargs(struct cmdserveropts *opts,
93 93 int argc, const char *argv[])
94 94 {
95 95 size_t i, step;
96 96 opts->argsize = 0;
97 97 for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) {
98 98 if (!argv[i])
99 99 continue; /* pass clang-analyse */
100 100 if (strcmp(argv[i], "--") == 0)
101 101 break;
102 102 size_t n = testsensitiveflag(argv[i]);
103 103 if (n == 0 || i + n > (size_t)argc)
104 104 continue;
105 105 opts->args = reallocx(opts->args,
106 106 (n + opts->argsize) * sizeof(char *));
107 107 memcpy(opts->args + opts->argsize, argv + i,
108 108 sizeof(char *) * n);
109 109 opts->argsize += n;
110 110 step = n;
111 111 }
112 112 }
113 113
114 114 static void preparesockdir(const char *sockdir)
115 115 {
116 116 int r;
117 117 r = mkdir(sockdir, 0700);
118 118 if (r < 0 && errno != EEXIST)
119 119 abortmsgerrno("cannot create sockdir %s", sockdir);
120 120
121 121 struct stat st;
122 122 r = lstat(sockdir, &st);
123 123 if (r < 0)
124 124 abortmsgerrno("cannot stat %s", sockdir);
125 125 if (!S_ISDIR(st.st_mode))
126 126 abortmsg("cannot create sockdir %s (file exists)", sockdir);
127 127 if (st.st_uid != geteuid() || st.st_mode & 0077)
128 128 abortmsg("insecure sockdir %s", sockdir);
129 129 }
130 130
131 /*
132 * Check if a socket directory exists and is only owned by the current user.
133 * Return 1 if so, 0 if not. This is used to check if XDG_RUNTIME_DIR can be
134 * used or not. According to the specification [1], XDG_RUNTIME_DIR should be
135 * ignored if the directory is not owned by the user with mode 0700.
136 * [1]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
137 */
138 static int checkruntimedir(const char *sockdir)
139 {
140 struct stat st;
141 int r = lstat(sockdir, &st);
142 if (r < 0) /* ex. does not exist */
143 return 0;
144 if (!S_ISDIR(st.st_mode)) /* ex. is a file, not a directory */
145 return 0;
146 return st.st_uid == geteuid() && (st.st_mode & 0777) == 0700;
147 }
148
131 149 static void getdefaultsockdir(char sockdir[], size_t size)
132 150 {
133 151 /* by default, put socket file in secure directory
134 152 * (${XDG_RUNTIME_DIR}/chg, or /${TMPDIR:-tmp}/chg$UID)
135 153 * (permission of socket file may be ignored on some Unices) */
136 154 const char *runtimedir = getenv("XDG_RUNTIME_DIR");
137 155 int r;
138 if (runtimedir) {
156 if (runtimedir && checkruntimedir(runtimedir)) {
139 157 r = snprintf(sockdir, size, "%s/chg", runtimedir);
140 158 } else {
141 159 const char *tmpdir = getenv("TMPDIR");
142 160 if (!tmpdir)
143 161 tmpdir = "/tmp";
144 162 r = snprintf(sockdir, size, "%s/chg%d", tmpdir, geteuid());
145 163 }
146 164 if (r < 0 || (size_t)r >= size)
147 165 abortmsg("too long TMPDIR (r = %d)", r);
148 166 }
149 167
150 168 static void setcmdserveropts(struct cmdserveropts *opts)
151 169 {
152 170 int r;
153 171 char sockdir[PATH_MAX];
154 172 const char *envsockname = getenv("CHGSOCKNAME");
155 173 if (!envsockname) {
156 174 getdefaultsockdir(sockdir, sizeof(sockdir));
157 175 preparesockdir(sockdir);
158 176 }
159 177
160 178 const char *basename = (envsockname) ? envsockname : sockdir;
161 179 const char *sockfmt = (envsockname) ? "%s" : "%s/server";
162 180 r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename);
163 181 if (r < 0 || (size_t)r >= sizeof(opts->sockname))
164 182 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
165 183 r = snprintf(opts->initsockname, sizeof(opts->initsockname),
166 184 "%s.%u", opts->sockname, (unsigned)getpid());
167 185 if (r < 0 || (size_t)r >= sizeof(opts->initsockname))
168 186 abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r);
169 187 }
170 188
171 189 static const char *gethgcmd(void)
172 190 {
173 191 static const char *hgcmd = NULL;
174 192 if (!hgcmd) {
175 193 hgcmd = getenv("CHGHG");
176 194 if (!hgcmd || hgcmd[0] == '\0')
177 195 hgcmd = getenv("HG");
178 196 if (!hgcmd || hgcmd[0] == '\0')
179 197 #ifdef HGPATH
180 198 hgcmd = (HGPATH);
181 199 #else
182 200 hgcmd = "hg";
183 201 #endif
184 202 }
185 203 return hgcmd;
186 204 }
187 205
188 206 static void execcmdserver(const struct cmdserveropts *opts)
189 207 {
190 208 const char *hgcmd = gethgcmd();
191 209
192 210 const char *baseargv[] = {
193 211 hgcmd,
194 212 "serve",
195 213 "--cmdserver", "chgunix",
196 214 "--address", opts->initsockname,
197 215 "--daemon-postexec", "chdir:/",
198 216 };
199 217 size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]);
200 218 size_t argsize = baseargvsize + opts->argsize + 1;
201 219
202 220 const char **argv = mallocx(sizeof(char *) * argsize);
203 221 memcpy(argv, baseargv, sizeof(baseargv));
204 222 memcpy(argv + baseargvsize, opts->args, sizeof(char *) * opts->argsize);
205 223 argv[argsize - 1] = NULL;
206 224
207 225 if (putenv("CHGINTERNALMARK=") != 0)
208 226 abortmsgerrno("failed to putenv");
209 227 if (execvp(hgcmd, (char **)argv) < 0)
210 228 abortmsgerrno("failed to exec cmdserver");
211 229 free(argv);
212 230 }
213 231
214 232 /* Retry until we can connect to the server. Give up after some time. */
215 233 static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
216 234 {
217 235 static const struct timespec sleepreq = {0, 10 * 1000000};
218 236 int pst = 0;
219 237
220 238 debugmsg("try connect to %s repeatedly", opts->initsockname);
221 239
222 240 unsigned int timeoutsec = 60; /* default: 60 seconds */
223 241 const char *timeoutenv = getenv("CHGTIMEOUT");
224 242 if (timeoutenv)
225 243 sscanf(timeoutenv, "%u", &timeoutsec);
226 244
227 245 for (unsigned int i = 0; !timeoutsec || i < timeoutsec * 100; i++) {
228 246 hgclient_t *hgc = hgc_open(opts->initsockname);
229 247 if (hgc) {
230 248 debugmsg("rename %s to %s", opts->initsockname,
231 249 opts->sockname);
232 250 int r = rename(opts->initsockname, opts->sockname);
233 251 if (r != 0)
234 252 abortmsgerrno("cannot rename");
235 253 return hgc;
236 254 }
237 255
238 256 if (pid > 0) {
239 257 /* collect zombie if child process fails to start */
240 258 int r = waitpid(pid, &pst, WNOHANG);
241 259 if (r != 0)
242 260 goto cleanup;
243 261 }
244 262
245 263 nanosleep(&sleepreq, NULL);
246 264 }
247 265
248 266 abortmsg("timed out waiting for cmdserver %s", opts->initsockname);
249 267 return NULL;
250 268
251 269 cleanup:
252 270 if (WIFEXITED(pst)) {
253 271 if (WEXITSTATUS(pst) == 0)
254 272 abortmsg("could not connect to cmdserver "
255 273 "(exited with status 0)");
256 274 debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
257 275 exit(WEXITSTATUS(pst));
258 276 } else if (WIFSIGNALED(pst)) {
259 277 abortmsg("cmdserver killed by signal %d", WTERMSIG(pst));
260 278 } else {
261 279 abortmsg("error while waiting for cmdserver");
262 280 }
263 281 return NULL;
264 282 }
265 283
266 284 /* Connect to a cmdserver. Will start a new server on demand. */
267 285 static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
268 286 {
269 287 const char *sockname = opts->redirectsockname[0] ?
270 288 opts->redirectsockname : opts->sockname;
271 289 debugmsg("try connect to %s", sockname);
272 290 hgclient_t *hgc = hgc_open(sockname);
273 291 if (hgc)
274 292 return hgc;
275 293
276 294 /* prevent us from being connected to an outdated server: we were
277 295 * told by a server to redirect to opts->redirectsockname and that
278 296 * address does not work. we do not want to connect to the server
279 297 * again because it will probably tell us the same thing. */
280 298 if (sockname == opts->redirectsockname)
281 299 unlink(opts->sockname);
282 300
283 301 debugmsg("start cmdserver at %s", opts->initsockname);
284 302
285 303 pid_t pid = fork();
286 304 if (pid < 0)
287 305 abortmsg("failed to fork cmdserver process");
288 306 if (pid == 0) {
289 307 execcmdserver(opts);
290 308 } else {
291 309 hgc = retryconnectcmdserver(opts, pid);
292 310 }
293 311
294 312 return hgc;
295 313 }
296 314
297 315 static void killcmdserver(const struct cmdserveropts *opts)
298 316 {
299 317 /* resolve config hash */
300 318 char *resolvedpath = realpath(opts->sockname, NULL);
301 319 if (resolvedpath) {
302 320 unlink(resolvedpath);
303 321 free(resolvedpath);
304 322 }
305 323 }
306 324
307 325 /* Run instructions sent from the server like unlink and set redirect path
308 326 * Return 1 if reconnect is needed, otherwise 0 */
309 327 static int runinstructions(struct cmdserveropts *opts, const char **insts)
310 328 {
311 329 int needreconnect = 0;
312 330 if (!insts)
313 331 return needreconnect;
314 332
315 333 assert(insts);
316 334 opts->redirectsockname[0] = '\0';
317 335 const char **pinst;
318 336 for (pinst = insts; *pinst; pinst++) {
319 337 debugmsg("instruction: %s", *pinst);
320 338 if (strncmp(*pinst, "unlink ", 7) == 0) {
321 339 unlink(*pinst + 7);
322 340 } else if (strncmp(*pinst, "redirect ", 9) == 0) {
323 341 int r = snprintf(opts->redirectsockname,
324 342 sizeof(opts->redirectsockname),
325 343 "%s", *pinst + 9);
326 344 if (r < 0 || r >= (int)sizeof(opts->redirectsockname))
327 345 abortmsg("redirect path is too long (%d)", r);
328 346 needreconnect = 1;
329 347 } else if (strncmp(*pinst, "exit ", 5) == 0) {
330 348 int n = 0;
331 349 if (sscanf(*pinst + 5, "%d", &n) != 1)
332 350 abortmsg("cannot read the exit code");
333 351 exit(n);
334 352 } else if (strcmp(*pinst, "reconnect") == 0) {
335 353 needreconnect = 1;
336 354 } else {
337 355 abortmsg("unknown instruction: %s", *pinst);
338 356 }
339 357 }
340 358 return needreconnect;
341 359 }
342 360
343 361 /*
344 362 * Test whether the command is unsupported or not. This is not designed to
345 363 * cover all cases. But it's fast, does not depend on the server and does
346 364 * not return false positives.
347 365 */
348 366 static int isunsupported(int argc, const char *argv[])
349 367 {
350 368 enum {
351 369 SERVE = 1,
352 370 DAEMON = 2,
353 371 SERVEDAEMON = SERVE | DAEMON,
354 372 TIME = 4,
355 373 };
356 374 unsigned int state = 0;
357 375 int i;
358 376 for (i = 0; i < argc; ++i) {
359 377 if (strcmp(argv[i], "--") == 0)
360 378 break;
361 379 if (i == 0 && strcmp("serve", argv[i]) == 0)
362 380 state |= SERVE;
363 381 else if (strcmp("-d", argv[i]) == 0 ||
364 382 strcmp("--daemon", argv[i]) == 0)
365 383 state |= DAEMON;
366 384 else if (strcmp("--time", argv[i]) == 0)
367 385 state |= TIME;
368 386 }
369 387 return (state & TIME) == TIME ||
370 388 (state & SERVEDAEMON) == SERVEDAEMON;
371 389 }
372 390
373 391 static void execoriginalhg(const char *argv[])
374 392 {
375 393 debugmsg("execute original hg");
376 394 if (execvp(gethgcmd(), (char **)argv) < 0)
377 395 abortmsgerrno("failed to exec original hg");
378 396 }
379 397
380 398 int main(int argc, const char *argv[], const char *envp[])
381 399 {
382 400 if (getenv("CHGDEBUG"))
383 401 enabledebugmsg();
384 402
385 403 if (!getenv("HGPLAIN") && isatty(fileno(stderr)))
386 404 enablecolor();
387 405
388 406 if (getenv("CHGINTERNALMARK"))
389 407 abortmsg("chg started by chg detected.\n"
390 408 "Please make sure ${HG:-hg} is not a symlink or "
391 409 "wrapper to chg. Alternatively, set $CHGHG to the "
392 410 "path of real hg.");
393 411
394 412 if (isunsupported(argc - 1, argv + 1))
395 413 execoriginalhg(argv);
396 414
397 415 struct cmdserveropts opts;
398 416 initcmdserveropts(&opts);
399 417 setcmdserveropts(&opts);
400 418 setcmdserverargs(&opts, argc, argv);
401 419
402 420 if (argc == 2) {
403 421 if (strcmp(argv[1], "--kill-chg-daemon") == 0) {
404 422 killcmdserver(&opts);
405 423 return 0;
406 424 }
407 425 }
408 426
409 427 hgclient_t *hgc;
410 428 size_t retry = 0;
411 429 while (1) {
412 430 hgc = connectcmdserver(&opts);
413 431 if (!hgc)
414 432 abortmsg("cannot open hg client");
415 433 hgc_setenv(hgc, envp);
416 434 const char **insts = hgc_validate(hgc, argv + 1, argc - 1);
417 435 int needreconnect = runinstructions(&opts, insts);
418 436 free(insts);
419 437 if (!needreconnect)
420 438 break;
421 439 hgc_close(hgc);
422 440 if (++retry > 10)
423 441 abortmsg("too many redirections.\n"
424 442 "Please make sure %s is not a wrapper which "
425 443 "changes sensitive environment variables "
426 444 "before executing hg. If you have to use a "
427 445 "wrapper, wrap chg instead of hg.",
428 446 gethgcmd());
429 447 }
430 448
431 449 setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc));
432 450 int exitcode = hgc_runcommand(hgc, argv + 1, argc - 1);
433 451 restoresignalhandler();
434 452 hgc_close(hgc);
435 453 freecmdserveropts(&opts);
436 454 waitpager();
437 455
438 456 return exitcode;
439 457 }
General Comments 0
You need to be logged in to leave comments. Login now