]>
Commit | Line | Data |
---|---|---|
1c1af145 | 1 | /* |
2 | * Platform-independent bits of X11 forwarding. | |
3 | */ | |
4 | ||
5 | #include <stdio.h> | |
6 | #include <stdlib.h> | |
7 | #include <assert.h> | |
8 | #include <time.h> | |
9 | ||
10 | #include "putty.h" | |
11 | #include "ssh.h" | |
12 | #include "tree234.h" | |
13 | ||
14 | #define GET_16BIT(endian, cp) \ | |
15 | (endian=='B' ? GET_16BIT_MSB_FIRST(cp) : GET_16BIT_LSB_FIRST(cp)) | |
16 | ||
17 | #define PUT_16BIT(endian, cp, val) \ | |
18 | (endian=='B' ? PUT_16BIT_MSB_FIRST(cp, val) : PUT_16BIT_LSB_FIRST(cp, val)) | |
19 | ||
20 | const char *const x11_authnames[] = { | |
21 | "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" | |
22 | }; | |
23 | ||
24 | struct XDMSeen { | |
25 | unsigned int time; | |
26 | unsigned char clientid[6]; | |
27 | }; | |
28 | ||
29 | struct X11Private { | |
30 | const struct plug_function_table *fn; | |
31 | /* the above variable absolutely *must* be the first in this structure */ | |
32 | unsigned char firstpkt[12]; /* first X data packet */ | |
33 | struct X11Display *disp; | |
34 | char *auth_protocol; | |
35 | unsigned char *auth_data; | |
36 | int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize; | |
37 | int verified; | |
38 | int throttled, throttle_override; | |
39 | unsigned long peer_ip; | |
40 | int peer_port; | |
41 | void *c; /* data used by ssh.c */ | |
42 | Socket s; | |
43 | }; | |
44 | ||
45 | static int xdmseen_cmp(void *a, void *b) | |
46 | { | |
47 | struct XDMSeen *sa = a, *sb = b; | |
48 | return sa->time > sb->time ? 1 : | |
49 | sa->time < sb->time ? -1 : | |
50 | memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid)); | |
51 | } | |
52 | ||
53 | /* Do-nothing "plug" implementation, used by x11_setup_display() when it | |
54 | * creates a trial connection (and then immediately closes it). | |
55 | * XXX: bit out of place here, could in principle live in a platform- | |
56 | * independent network.c or something */ | |
57 | static void dummy_plug_log(Plug p, int type, SockAddr addr, int port, | |
58 | const char *error_msg, int error_code) { } | |
59 | static int dummy_plug_closing | |
60 | (Plug p, const char *error_msg, int error_code, int calling_back) | |
61 | { return 1; } | |
62 | static int dummy_plug_receive(Plug p, int urgent, char *data, int len) | |
63 | { return 1; } | |
64 | static void dummy_plug_sent(Plug p, int bufsize) { } | |
65 | static int dummy_plug_accepting(Plug p, OSSocket sock) { return 1; } | |
66 | static const struct plug_function_table dummy_plug = { | |
67 | dummy_plug_log, dummy_plug_closing, dummy_plug_receive, | |
68 | dummy_plug_sent, dummy_plug_accepting | |
69 | }; | |
70 | ||
71 | struct X11Display *x11_setup_display(char *display, int authtype, | |
72 | const Config *cfg) | |
73 | { | |
74 | struct X11Display *disp = snew(struct X11Display); | |
75 | char *localcopy; | |
76 | int i; | |
77 | ||
78 | if (!display || !*display) { | |
79 | localcopy = platform_get_x_display(); | |
80 | if (!localcopy || !*localcopy) { | |
81 | sfree(localcopy); | |
82 | localcopy = dupstr(":0"); /* plausible default for any platform */ | |
83 | } | |
84 | } else | |
85 | localcopy = dupstr(display); | |
86 | ||
87 | /* | |
88 | * Parse the display name. | |
89 | * | |
90 | * We expect this to have one of the following forms: | |
91 | * | |
92 | * - the standard X format which looks like | |
93 | * [ [ protocol '/' ] host ] ':' displaynumber [ '.' screennumber ] | |
94 | * (X11 also permits a double colon to indicate DECnet, but | |
95 | * that's not our problem, thankfully!) | |
96 | * | |
97 | * - only seen in the wild on MacOS (so far): a pathname to a | |
98 | * Unix-domain socket, which will typically and confusingly | |
99 | * end in ":0", and which I'm currently distinguishing from | |
100 | * the standard scheme by noting that it starts with '/'. | |
101 | */ | |
102 | if (localcopy[0] == '/') { | |
103 | disp->unixsocketpath = localcopy; | |
104 | disp->unixdomain = TRUE; | |
105 | disp->hostname = NULL; | |
106 | disp->displaynum = -1; | |
107 | disp->screennum = 0; | |
108 | disp->addr = NULL; | |
109 | } else { | |
110 | char *colon, *dot, *slash; | |
111 | char *protocol, *hostname; | |
112 | ||
113 | colon = strrchr(localcopy, ':'); | |
114 | if (!colon) { | |
115 | sfree(disp); | |
116 | sfree(localcopy); | |
117 | return NULL; /* FIXME: report a specific error? */ | |
118 | } | |
119 | ||
120 | *colon++ = '\0'; | |
121 | dot = strchr(colon, '.'); | |
122 | if (dot) | |
123 | *dot++ = '\0'; | |
124 | ||
125 | disp->displaynum = atoi(colon); | |
126 | if (dot) | |
127 | disp->screennum = atoi(dot); | |
128 | else | |
129 | disp->screennum = 0; | |
130 | ||
131 | protocol = NULL; | |
132 | hostname = localcopy; | |
133 | if (colon > localcopy) { | |
134 | slash = strchr(localcopy, '/'); | |
135 | if (slash) { | |
136 | *slash++ = '\0'; | |
137 | protocol = localcopy; | |
138 | hostname = slash; | |
139 | } | |
140 | } | |
141 | ||
142 | disp->hostname = *hostname ? dupstr(hostname) : NULL; | |
143 | ||
144 | if (protocol) | |
145 | disp->unixdomain = (!strcmp(protocol, "local") || | |
146 | !strcmp(protocol, "unix")); | |
147 | else if (!*hostname || !strcmp(hostname, "unix")) | |
148 | disp->unixdomain = platform_uses_x11_unix_by_default; | |
149 | else | |
150 | disp->unixdomain = FALSE; | |
151 | ||
152 | if (!disp->hostname && !disp->unixdomain) | |
153 | disp->hostname = dupstr("localhost"); | |
154 | ||
155 | disp->unixsocketpath = NULL; | |
156 | disp->addr = NULL; | |
157 | ||
158 | sfree(localcopy); | |
159 | } | |
160 | ||
161 | /* | |
162 | * Look up the display hostname, if we need to. | |
163 | */ | |
164 | if (!disp->unixdomain) { | |
165 | const char *err; | |
166 | ||
167 | disp->port = 6000 + disp->displaynum; | |
168 | disp->addr = name_lookup(disp->hostname, disp->port, | |
169 | &disp->realhost, cfg, ADDRTYPE_UNSPEC); | |
170 | ||
171 | if ((err = sk_addr_error(disp->addr)) != NULL) { | |
172 | sk_addr_free(disp->addr); | |
173 | sfree(disp->hostname); | |
174 | sfree(disp->unixsocketpath); | |
175 | return NULL; /* FIXME: report an error */ | |
176 | } | |
177 | } | |
178 | ||
179 | /* | |
180 | * Try upgrading an IP-style localhost display to a Unix-socket | |
181 | * display (as the standard X connection libraries do). | |
182 | */ | |
183 | if (!disp->unixdomain && sk_address_is_local(disp->addr)) { | |
184 | SockAddr ux = platform_get_x11_unix_address(NULL, disp->displaynum); | |
185 | const char *err = sk_addr_error(ux); | |
186 | if (!err) { | |
187 | /* Create trial connection to see if there is a useful Unix-domain | |
188 | * socket */ | |
189 | const struct plug_function_table *dummy = &dummy_plug; | |
190 | Socket s = sk_new(sk_addr_dup(ux), 0, 0, 0, 0, 0, (Plug)&dummy); | |
191 | err = sk_socket_error(s); | |
192 | sk_close(s); | |
193 | } | |
194 | if (err) { | |
195 | sk_addr_free(ux); | |
196 | } else { | |
197 | sk_addr_free(disp->addr); | |
198 | disp->unixdomain = TRUE; | |
199 | disp->addr = ux; | |
200 | /* Fill in the rest in a moment */ | |
201 | } | |
202 | } | |
203 | ||
204 | if (disp->unixdomain) { | |
205 | if (!disp->addr) | |
206 | disp->addr = platform_get_x11_unix_address(disp->unixsocketpath, | |
207 | disp->displaynum); | |
208 | if (disp->unixsocketpath) | |
209 | disp->realhost = dupstr(disp->unixsocketpath); | |
210 | else | |
211 | disp->realhost = dupprintf("unix:%d", disp->displaynum); | |
212 | disp->port = 0; | |
213 | } | |
214 | ||
215 | /* | |
216 | * Invent the remote authorisation details. | |
217 | */ | |
218 | if (authtype == X11_MIT) { | |
219 | disp->remoteauthproto = X11_MIT; | |
220 | ||
221 | /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ | |
222 | disp->remoteauthdata = snewn(16, unsigned char); | |
223 | for (i = 0; i < 16; i++) | |
224 | disp->remoteauthdata[i] = random_byte(); | |
225 | disp->remoteauthdatalen = 16; | |
226 | ||
227 | disp->xdmseen = NULL; | |
228 | } else { | |
229 | assert(authtype == X11_XDM); | |
230 | disp->remoteauthproto = X11_XDM; | |
231 | ||
232 | /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */ | |
233 | disp->remoteauthdata = snewn(16, unsigned char); | |
234 | for (i = 0; i < 16; i++) | |
235 | disp->remoteauthdata[i] = (i == 8 ? 0 : random_byte()); | |
236 | disp->remoteauthdatalen = 16; | |
237 | ||
238 | disp->xdmseen = newtree234(xdmseen_cmp); | |
239 | } | |
240 | disp->remoteauthprotoname = dupstr(x11_authnames[disp->remoteauthproto]); | |
241 | disp->remoteauthdatastring = snewn(disp->remoteauthdatalen * 2 + 1, char); | |
242 | for (i = 0; i < disp->remoteauthdatalen; i++) | |
243 | sprintf(disp->remoteauthdatastring + i*2, "%02x", | |
244 | disp->remoteauthdata[i]); | |
245 | ||
246 | /* | |
247 | * Fetch the local authorisation details. | |
248 | */ | |
249 | disp->localauthproto = X11_NO_AUTH; | |
250 | disp->localauthdata = NULL; | |
251 | disp->localauthdatalen = 0; | |
252 | platform_get_x11_auth(disp, cfg); | |
253 | ||
254 | return disp; | |
255 | } | |
256 | ||
257 | void x11_free_display(struct X11Display *disp) | |
258 | { | |
259 | if (disp->xdmseen != NULL) { | |
260 | struct XDMSeen *seen; | |
261 | while ((seen = delpos234(disp->xdmseen, 0)) != NULL) | |
262 | sfree(seen); | |
263 | freetree234(disp->xdmseen); | |
264 | } | |
265 | sfree(disp->hostname); | |
266 | sfree(disp->unixsocketpath); | |
267 | if (disp->localauthdata) | |
268 | memset(disp->localauthdata, 0, disp->localauthdatalen); | |
269 | sfree(disp->localauthdata); | |
270 | if (disp->remoteauthdata) | |
271 | memset(disp->remoteauthdata, 0, disp->remoteauthdatalen); | |
272 | sfree(disp->remoteauthdata); | |
273 | sfree(disp->remoteauthprotoname); | |
274 | sfree(disp->remoteauthdatastring); | |
275 | sk_addr_free(disp->addr); | |
276 | sfree(disp); | |
277 | } | |
278 | ||
279 | #define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ | |
280 | ||
281 | static char *x11_verify(unsigned long peer_ip, int peer_port, | |
282 | struct X11Display *disp, char *proto, | |
283 | unsigned char *data, int dlen) | |
284 | { | |
285 | if (strcmp(proto, x11_authnames[disp->remoteauthproto]) != 0) | |
286 | return "wrong authorisation protocol attempted"; | |
287 | if (disp->remoteauthproto == X11_MIT) { | |
288 | if (dlen != disp->remoteauthdatalen) | |
289 | return "MIT-MAGIC-COOKIE-1 data was wrong length"; | |
290 | if (memcmp(disp->remoteauthdata, data, dlen) != 0) | |
291 | return "MIT-MAGIC-COOKIE-1 data did not match"; | |
292 | } | |
293 | if (disp->remoteauthproto == X11_XDM) { | |
294 | unsigned long t; | |
295 | time_t tim; | |
296 | int i; | |
297 | struct XDMSeen *seen, *ret; | |
298 | ||
299 | if (dlen != 24) | |
300 | return "XDM-AUTHORIZATION-1 data was wrong length"; | |
301 | if (peer_port == -1) | |
302 | return "cannot do XDM-AUTHORIZATION-1 without remote address data"; | |
303 | des_decrypt_xdmauth(disp->remoteauthdata+9, data, 24); | |
304 | if (memcmp(disp->remoteauthdata, data, 8) != 0) | |
305 | return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */ | |
306 | if (GET_32BIT_MSB_FIRST(data+8) != peer_ip) | |
307 | return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */ | |
308 | if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port) | |
309 | return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */ | |
310 | t = GET_32BIT_MSB_FIRST(data+14); | |
311 | for (i = 18; i < 24; i++) | |
312 | if (data[i] != 0) /* zero padding wrong */ | |
313 | return "XDM-AUTHORIZATION-1 data failed check"; | |
314 | tim = time(NULL); | |
315 | if (abs(t - tim) > XDM_MAXSKEW) | |
316 | return "XDM-AUTHORIZATION-1 time stamp was too far out"; | |
317 | seen = snew(struct XDMSeen); | |
318 | seen->time = t; | |
319 | memcpy(seen->clientid, data+8, 6); | |
320 | assert(disp->xdmseen != NULL); | |
321 | ret = add234(disp->xdmseen, seen); | |
322 | if (ret != seen) { | |
323 | sfree(seen); | |
324 | return "XDM-AUTHORIZATION-1 data replayed"; | |
325 | } | |
326 | /* While we're here, purge entries too old to be replayed. */ | |
327 | for (;;) { | |
328 | seen = index234(disp->xdmseen, 0); | |
329 | assert(seen != NULL); | |
330 | if (t - seen->time <= XDM_MAXSKEW) | |
331 | break; | |
332 | sfree(delpos234(disp->xdmseen, 0)); | |
333 | } | |
334 | } | |
335 | /* implement other protocols here if ever required */ | |
336 | return NULL; | |
337 | } | |
338 | ||
339 | void x11_get_auth_from_authfile(struct X11Display *disp, | |
340 | const char *authfilename) | |
341 | { | |
342 | FILE *authfp; | |
343 | char *buf, *ptr, *str[4]; | |
344 | int len[4]; | |
345 | int family, protocol; | |
346 | int ideal_match = FALSE; | |
347 | char *ourhostname = get_hostname(); | |
348 | ||
349 | /* | |
350 | * Normally we should look for precisely the details specified in | |
351 | * `disp'. However, there's an oddity when the display is local: | |
352 | * displays like "localhost:0" usually have their details stored | |
353 | * in a Unix-domain-socket record (even if there isn't actually a | |
354 | * real Unix-domain socket available, as with OpenSSH's proxy X11 | |
355 | * server). | |
356 | * | |
357 | * This is apparently a fudge to get round the meaninglessness of | |
358 | * "localhost" in a shared-home-directory context -- xauth entries | |
359 | * for Unix-domain sockets already disambiguate this by storing | |
360 | * the *local* hostname in the conveniently-blank hostname field, | |
361 | * but IP "localhost" records couldn't do this. So, typically, an | |
362 | * IP "localhost" entry in the auth database isn't present and if | |
363 | * it were it would be ignored. | |
364 | * | |
365 | * However, we don't entirely trust that (say) Windows X servers | |
366 | * won't rely on a straight "localhost" entry, bad idea though | |
367 | * that is; so if we can't find a Unix-domain-socket entry we'll | |
368 | * fall back to an IP-based entry if we can find one. | |
369 | */ | |
370 | int localhost = !disp->unixdomain && sk_address_is_local(disp->addr); | |
371 | ||
372 | authfp = fopen(authfilename, "rb"); | |
373 | if (!authfp) | |
374 | return; | |
375 | ||
376 | /* Records in .Xauthority contain four strings of up to 64K each */ | |
377 | buf = snewn(65537 * 4, char); | |
378 | ||
379 | while (!ideal_match) { | |
380 | int c, i, j, match = FALSE; | |
381 | ||
382 | #define GET do { c = fgetc(authfp); if (c == EOF) goto done; c = (unsigned char)c; } while (0) | |
383 | /* Expect a big-endian 2-byte number giving address family */ | |
384 | GET; family = c; | |
385 | GET; family = (family << 8) | c; | |
386 | /* Then expect four strings, each composed of a big-endian 2-byte | |
387 | * length field followed by that many bytes of data */ | |
388 | ptr = buf; | |
389 | for (i = 0; i < 4; i++) { | |
390 | GET; len[i] = c; | |
391 | GET; len[i] = (len[i] << 8) | c; | |
392 | str[i] = ptr; | |
393 | for (j = 0; j < len[i]; j++) { | |
394 | GET; *ptr++ = c; | |
395 | } | |
396 | *ptr++ = '\0'; | |
397 | } | |
398 | #undef GET | |
399 | ||
400 | /* | |
401 | * Now we have a full X authority record in memory. See | |
402 | * whether it matches the display we're trying to | |
403 | * authenticate to. | |
404 | * | |
405 | * The details we've just read should be interpreted as | |
406 | * follows: | |
407 | * | |
408 | * - 'family' is the network address family used to | |
409 | * connect to the display. 0 means IPv4; 6 means IPv6; | |
410 | * 256 means Unix-domain sockets. | |
411 | * | |
412 | * - str[0] is the network address itself. For IPv4 and | |
413 | * IPv6, this is a string of binary data of the | |
414 | * appropriate length (respectively 4 and 16 bytes) | |
415 | * representing the address in big-endian format, e.g. | |
416 | * 7F 00 00 01 means IPv4 localhost. For Unix-domain | |
417 | * sockets, this is the host name of the machine on | |
418 | * which the Unix-domain display resides (so that an | |
419 | * .Xauthority file on a shared file system can contain | |
420 | * authority entries for Unix-domain displays on | |
421 | * several machines without them clashing). | |
422 | * | |
423 | * - str[1] is the display number. I've no idea why | |
424 | * .Xauthority stores this as a string when it has a | |
425 | * perfectly good integer format, but there we go. | |
426 | * | |
427 | * - str[2] is the authorisation method, encoded as its | |
428 | * canonical string name (i.e. "MIT-MAGIC-COOKIE-1", | |
429 | * "XDM-AUTHORIZATION-1" or something we don't | |
430 | * recognise). | |
431 | * | |
432 | * - str[3] is the actual authorisation data, stored in | |
433 | * binary form. | |
434 | */ | |
435 | ||
436 | if (disp->displaynum < 0 || disp->displaynum != atoi(str[1])) | |
437 | continue; /* not the one */ | |
438 | ||
439 | for (protocol = 1; protocol < lenof(x11_authnames); protocol++) | |
440 | if (!strcmp(str[2], x11_authnames[protocol])) | |
441 | break; | |
442 | if (protocol == lenof(x11_authnames)) | |
443 | continue; /* don't recognise this protocol, look for another */ | |
444 | ||
445 | switch (family) { | |
446 | case 0: /* IPv4 */ | |
447 | if (!disp->unixdomain && | |
448 | sk_addrtype(disp->addr) == ADDRTYPE_IPV4) { | |
449 | char buf[4]; | |
450 | sk_addrcopy(disp->addr, buf); | |
451 | if (len[0] == 4 && !memcmp(str[0], buf, 4)) { | |
452 | match = TRUE; | |
453 | /* If this is a "localhost" entry, note it down | |
454 | * but carry on looking for a Unix-domain entry. */ | |
455 | ideal_match = !localhost; | |
456 | } | |
457 | } | |
458 | break; | |
459 | case 6: /* IPv6 */ | |
460 | if (!disp->unixdomain && | |
461 | sk_addrtype(disp->addr) == ADDRTYPE_IPV6) { | |
462 | char buf[16]; | |
463 | sk_addrcopy(disp->addr, buf); | |
464 | if (len[0] == 16 && !memcmp(str[0], buf, 16)) { | |
465 | match = TRUE; | |
466 | ideal_match = !localhost; | |
467 | } | |
468 | } | |
469 | break; | |
470 | case 256: /* Unix-domain / localhost */ | |
471 | if ((disp->unixdomain || localhost) | |
472 | && ourhostname && !strcmp(ourhostname, str[0])) | |
473 | /* A matching Unix-domain socket is always the best | |
474 | * match. */ | |
475 | match = ideal_match = TRUE; | |
476 | break; | |
477 | } | |
478 | ||
479 | if (match) { | |
480 | /* Current best guess -- may be overridden if !ideal_match */ | |
481 | disp->localauthproto = protocol; | |
482 | sfree(disp->localauthdata); /* free previous guess, if any */ | |
483 | disp->localauthdata = snewn(len[3], unsigned char); | |
484 | memcpy(disp->localauthdata, str[3], len[3]); | |
485 | disp->localauthdatalen = len[3]; | |
486 | } | |
487 | } | |
488 | ||
489 | done: | |
490 | fclose(authfp); | |
491 | memset(buf, 0, 65537 * 4); | |
492 | sfree(buf); | |
493 | sfree(ourhostname); | |
494 | } | |
495 | ||
496 | static void x11_log(Plug p, int type, SockAddr addr, int port, | |
497 | const char *error_msg, int error_code) | |
498 | { | |
499 | /* We have no interface to the logging module here, so we drop these. */ | |
500 | } | |
501 | ||
502 | static int x11_closing(Plug plug, const char *error_msg, int error_code, | |
503 | int calling_back) | |
504 | { | |
505 | struct X11Private *pr = (struct X11Private *) plug; | |
506 | ||
507 | /* | |
508 | * We have no way to communicate down the forwarded connection, | |
509 | * so if an error occurred on the socket, we just ignore it | |
510 | * and treat it like a proper close. | |
511 | */ | |
512 | sshfwd_close(pr->c); | |
513 | x11_close(pr->s); | |
514 | return 1; | |
515 | } | |
516 | ||
517 | static int x11_receive(Plug plug, int urgent, char *data, int len) | |
518 | { | |
519 | struct X11Private *pr = (struct X11Private *) plug; | |
520 | ||
521 | if (sshfwd_write(pr->c, data, len) > 0) { | |
522 | pr->throttled = 1; | |
523 | sk_set_frozen(pr->s, 1); | |
524 | } | |
525 | ||
526 | return 1; | |
527 | } | |
528 | ||
529 | static void x11_sent(Plug plug, int bufsize) | |
530 | { | |
531 | struct X11Private *pr = (struct X11Private *) plug; | |
532 | ||
533 | sshfwd_unthrottle(pr->c, bufsize); | |
534 | } | |
535 | ||
536 | /* | |
537 | * When setting up X forwarding, we should send the screen number | |
538 | * from the specified local display. This function extracts it from | |
539 | * the display string. | |
540 | */ | |
541 | int x11_get_screen_number(char *display) | |
542 | { | |
543 | int n; | |
544 | ||
545 | n = strcspn(display, ":"); | |
546 | if (!display[n]) | |
547 | return 0; | |
548 | n = strcspn(display, "."); | |
549 | if (!display[n]) | |
550 | return 0; | |
551 | return atoi(display + n + 1); | |
552 | } | |
553 | ||
554 | /* | |
555 | * Called to set up the raw connection. | |
556 | * | |
557 | * Returns an error message, or NULL on success. | |
558 | * also, fills the SocketsStructure | |
559 | */ | |
560 | extern const char *x11_init(Socket *s, struct X11Display *disp, void *c, | |
561 | const char *peeraddr, int peerport, | |
562 | const Config *cfg) | |
563 | { | |
564 | static const struct plug_function_table fn_table = { | |
565 | x11_log, | |
566 | x11_closing, | |
567 | x11_receive, | |
568 | x11_sent, | |
569 | NULL | |
570 | }; | |
571 | ||
572 | const char *err; | |
573 | struct X11Private *pr; | |
574 | ||
575 | /* | |
576 | * Open socket. | |
577 | */ | |
578 | pr = snew(struct X11Private); | |
579 | pr->fn = &fn_table; | |
580 | pr->auth_protocol = NULL; | |
581 | pr->disp = disp; | |
582 | pr->verified = 0; | |
583 | pr->data_read = 0; | |
584 | pr->throttled = pr->throttle_override = 0; | |
585 | pr->c = c; | |
586 | ||
587 | pr->s = *s = new_connection(sk_addr_dup(disp->addr), | |
588 | disp->realhost, disp->port, | |
589 | 0, 1, 0, 0, (Plug) pr, cfg); | |
590 | if ((err = sk_socket_error(*s)) != NULL) { | |
591 | sfree(pr); | |
592 | return err; | |
593 | } | |
594 | ||
595 | /* | |
596 | * See if we can make sense of the peer address we were given. | |
597 | */ | |
598 | { | |
599 | int i[4]; | |
600 | if (peeraddr && | |
601 | 4 == sscanf(peeraddr, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { | |
602 | pr->peer_ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; | |
603 | pr->peer_port = peerport; | |
604 | } else { | |
605 | pr->peer_ip = 0; | |
606 | pr->peer_port = -1; | |
607 | } | |
608 | } | |
609 | ||
610 | sk_set_private_ptr(*s, pr); | |
611 | return NULL; | |
612 | } | |
613 | ||
614 | void x11_close(Socket s) | |
615 | { | |
616 | struct X11Private *pr; | |
617 | if (!s) | |
618 | return; | |
619 | pr = (struct X11Private *) sk_get_private_ptr(s); | |
620 | if (pr->auth_protocol) { | |
621 | sfree(pr->auth_protocol); | |
622 | sfree(pr->auth_data); | |
623 | } | |
624 | ||
625 | sfree(pr); | |
626 | ||
627 | sk_close(s); | |
628 | } | |
629 | ||
630 | void x11_unthrottle(Socket s) | |
631 | { | |
632 | struct X11Private *pr; | |
633 | if (!s) | |
634 | return; | |
635 | pr = (struct X11Private *) sk_get_private_ptr(s); | |
636 | ||
637 | pr->throttled = 0; | |
638 | sk_set_frozen(s, pr->throttled || pr->throttle_override); | |
639 | } | |
640 | ||
641 | void x11_override_throttle(Socket s, int enable) | |
642 | { | |
643 | struct X11Private *pr; | |
644 | if (!s) | |
645 | return; | |
646 | pr = (struct X11Private *) sk_get_private_ptr(s); | |
647 | ||
648 | pr->throttle_override = enable; | |
649 | sk_set_frozen(s, pr->throttled || pr->throttle_override); | |
650 | } | |
651 | ||
652 | /* | |
653 | * Called to send data down the raw connection. | |
654 | */ | |
655 | int x11_send(Socket s, char *data, int len) | |
656 | { | |
657 | struct X11Private *pr; | |
658 | if (!s) | |
659 | return 0; | |
660 | pr = (struct X11Private *) sk_get_private_ptr(s); | |
661 | ||
662 | /* | |
663 | * Read the first packet. | |
664 | */ | |
665 | while (len > 0 && pr->data_read < 12) | |
666 | pr->firstpkt[pr->data_read++] = (unsigned char) (len--, *data++); | |
667 | if (pr->data_read < 12) | |
668 | return 0; | |
669 | ||
670 | /* | |
671 | * If we have not allocated the auth_protocol and auth_data | |
672 | * strings, do so now. | |
673 | */ | |
674 | if (!pr->auth_protocol) { | |
675 | pr->auth_plen = GET_16BIT(pr->firstpkt[0], pr->firstpkt + 6); | |
676 | pr->auth_dlen = GET_16BIT(pr->firstpkt[0], pr->firstpkt + 8); | |
677 | pr->auth_psize = (pr->auth_plen + 3) & ~3; | |
678 | pr->auth_dsize = (pr->auth_dlen + 3) & ~3; | |
679 | /* Leave room for a terminating zero, to make our lives easier. */ | |
680 | pr->auth_protocol = snewn(pr->auth_psize + 1, char); | |
681 | pr->auth_data = snewn(pr->auth_dsize, unsigned char); | |
682 | } | |
683 | ||
684 | /* | |
685 | * Read the auth_protocol and auth_data strings. | |
686 | */ | |
687 | while (len > 0 && pr->data_read < 12 + pr->auth_psize) | |
688 | pr->auth_protocol[pr->data_read++ - 12] = (len--, *data++); | |
689 | while (len > 0 && pr->data_read < 12 + pr->auth_psize + pr->auth_dsize) | |
690 | pr->auth_data[pr->data_read++ - 12 - | |
691 | pr->auth_psize] = (unsigned char) (len--, *data++); | |
692 | if (pr->data_read < 12 + pr->auth_psize + pr->auth_dsize) | |
693 | return 0; | |
694 | ||
695 | /* | |
696 | * If we haven't verified the authorisation, do so now. | |
697 | */ | |
698 | if (!pr->verified) { | |
699 | char *err; | |
700 | ||
701 | pr->auth_protocol[pr->auth_plen] = '\0'; /* ASCIZ */ | |
702 | err = x11_verify(pr->peer_ip, pr->peer_port, | |
703 | pr->disp, pr->auth_protocol, | |
704 | pr->auth_data, pr->auth_dlen); | |
705 | ||
706 | /* | |
707 | * If authorisation failed, construct and send an error | |
708 | * packet, then terminate the connection. | |
709 | */ | |
710 | if (err) { | |
711 | char *message; | |
712 | int msglen, msgsize; | |
713 | unsigned char *reply; | |
714 | ||
715 | message = dupprintf("%s X11 proxy: %s", appname, err); | |
716 | msglen = strlen(message); | |
717 | reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */ | |
718 | msgsize = (msglen + 3) & ~3; | |
719 | reply[0] = 0; /* failure */ | |
720 | reply[1] = msglen; /* length of reason string */ | |
721 | memcpy(reply + 2, pr->firstpkt + 2, 4); /* major/minor proto vsn */ | |
722 | PUT_16BIT(pr->firstpkt[0], reply + 6, msgsize >> 2);/* data len */ | |
723 | memset(reply + 8, 0, msgsize); | |
724 | memcpy(reply + 8, message, msglen); | |
725 | sshfwd_write(pr->c, (char *)reply, 8 + msgsize); | |
726 | sshfwd_close(pr->c); | |
727 | x11_close(s); | |
728 | sfree(reply); | |
729 | sfree(message); | |
730 | return 0; | |
731 | } | |
732 | ||
733 | /* | |
734 | * Now we know we're going to accept the connection. Strip | |
735 | * the fake auth data, and optionally put real auth data in | |
736 | * instead. | |
737 | */ | |
738 | { | |
739 | char realauthdata[64]; | |
740 | int realauthlen = 0; | |
741 | int authstrlen = strlen(x11_authnames[pr->disp->localauthproto]); | |
742 | int buflen = 0; /* initialise to placate optimiser */ | |
743 | static const char zeroes[4] = { 0,0,0,0 }; | |
744 | void *buf; | |
745 | ||
746 | if (pr->disp->localauthproto == X11_MIT) { | |
747 | assert(pr->disp->localauthdatalen <= lenof(realauthdata)); | |
748 | realauthlen = pr->disp->localauthdatalen; | |
749 | memcpy(realauthdata, pr->disp->localauthdata, realauthlen); | |
750 | } else if (pr->disp->localauthproto == X11_XDM && | |
751 | pr->disp->localauthdatalen == 16 && | |
752 | ((buf = sk_getxdmdata(s, &buflen))!=0)) { | |
753 | time_t t; | |
754 | realauthlen = (buflen+12+7) & ~7; | |
755 | assert(realauthlen <= lenof(realauthdata)); | |
756 | memset(realauthdata, 0, realauthlen); | |
757 | memcpy(realauthdata, pr->disp->localauthdata, 8); | |
758 | memcpy(realauthdata+8, buf, buflen); | |
759 | t = time(NULL); | |
760 | PUT_32BIT_MSB_FIRST(realauthdata+8+buflen, t); | |
761 | des_encrypt_xdmauth(pr->disp->localauthdata+9, | |
762 | (unsigned char *)realauthdata, | |
763 | realauthlen); | |
764 | sfree(buf); | |
765 | } | |
766 | /* implement other auth methods here if required */ | |
767 | ||
768 | PUT_16BIT(pr->firstpkt[0], pr->firstpkt + 6, authstrlen); | |
769 | PUT_16BIT(pr->firstpkt[0], pr->firstpkt + 8, realauthlen); | |
770 | ||
771 | sk_write(s, (char *)pr->firstpkt, 12); | |
772 | ||
773 | if (authstrlen) { | |
774 | sk_write(s, x11_authnames[pr->disp->localauthproto], | |
775 | authstrlen); | |
776 | sk_write(s, zeroes, 3 & (-authstrlen)); | |
777 | } | |
778 | if (realauthlen) { | |
779 | sk_write(s, realauthdata, realauthlen); | |
780 | sk_write(s, zeroes, 3 & (-realauthlen)); | |
781 | } | |
782 | } | |
783 | pr->verified = 1; | |
784 | } | |
785 | ||
786 | /* | |
787 | * After initialisation, just copy data simply. | |
788 | */ | |
789 | ||
790 | return sk_write(s, data, len); | |
791 | } |