1 /*
2  * tkUnixSend.c --
3  *
4  *	This file provides functions that implement the "send" command,
5  *	allowing commands to be passed from interpreter to interpreter.
6  *
7  * Copyright (c) 1989-1994 The Regents of the University of California.
8  * Copyright (c) 1994-1996 Sun Microsystems, Inc.
9  * Copyright (c) 1998-1999 by Scriptics Corporation.
10  *
11  * See the file "license.terms" for information on usage and redistribution of
12  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
13  */
14 
15 #include "tkUnixInt.h"
16 
17 /*
18  * The following structure is used to keep track of the interpreters
19  * registered by this process.
20  */
21 
22 typedef struct RegisteredInterp {
23     char *name;			/* Interpreter's name (malloc-ed). */
24     Tcl_Interp *interp;		/* Interpreter associated with name. NULL
25 				 * means that the application was unregistered
26 				 * or deleted while a send was in progress to
27 				 * it. */
28     TkDisplay *dispPtr;		/* Display for the application. Needed because
29 				 * we may need to unregister the interpreter
30 				 * after its main window has been deleted. */
31     struct RegisteredInterp *nextPtr;
32 				/* Next in list of names associated with
33 				 * interps in this process. NULL means end of
34 				 * list. */
35 } RegisteredInterp;
36 
37 /*
38  * A registry of all interpreters for a display is kept in a property
39  * "InterpRegistry" on the root window of the display. It is organized as a
40  * series of zero or more concatenated strings (in no particular order), each
41  * of the form
42  * 	window space name '\0'
43  * where "window" is the hex id of the comm. window to use to talk to an
44  * interpreter named "name".
45  *
46  * When the registry is being manipulated by an application (e.g. to add or
47  * remove an entry), it is loaded into memory using a structure of the
48  * following type:
49  */
50 
51 typedef struct NameRegistry {
52     TkDisplay *dispPtr;		/* Display from which the registry was
53 				 * read. */
54     int locked;			/* Non-zero means that the display was locked
55 				 * when the property was read in. */
56     int modified;		/* Non-zero means that the property has been
57 				 * modified, so it needs to be written out
58 				 * when the NameRegistry is closed. */
59     unsigned long propLength;	/* Length of the property, in bytes. */
60     char *property;		/* The contents of the property, or NULL if
61 				 * none. See format description above; this is
62 				 * *not* terminated by the first null
63 				 * character. Dynamically allocated. */
64     int allocedByX;		/* Non-zero means must free property with
65 				 * XFree; zero means use ckfree. */
66 } NameRegistry;
67 
68 /*
69  * When a result is being awaited from a sent command, one of the following
70  * structures is present on a list of all outstanding sent commands. The
71  * information in the structure is used to process the result when it arrives.
72  * You're probably wondering how there could ever be multiple outstanding sent
73  * commands. This could happen if interpreters invoke each other recursively.
74  * It's unlikely, but possible.
75  */
76 
77 typedef struct PendingCommand {
78     int serial;			/* Serial number expected in result. */
79     TkDisplay *dispPtr;		/* Display being used for communication. */
80     const char *target;		/* Name of interpreter command is being sent
81 				 * to. */
82     Window commWindow;		/* Target's communication window. */
83     Tcl_Interp *interp;		/* Interpreter from which the send was
84 				 * invoked. */
85     int code;			/* Tcl return code for command will be stored
86 				 * here. */
87     char *result;		/* String result for command (malloc'ed), or
88 				 * NULL. */
89     char *errorInfo;		/* Information for "errorInfo" variable, or
90 				 * NULL (malloc'ed). */
91     char *errorCode;		/* Information for "errorCode" variable, or
92 				 * NULL (malloc'ed). */
93     int gotResponse;		/* 1 means a response has been received, 0
94 				 * means the command is still outstanding. */
95     struct PendingCommand *nextPtr;
96 				/* Next in list of all outstanding commands.
97 				 * NULL means end of list. */
98 } PendingCommand;
99 
100 typedef struct {
101     PendingCommand *pendingCommands;
102 				/* List of all commands currently being waited
103 				 * for. */
104     RegisteredInterp *interpListPtr;
105 				/* List of all interpreters registered in the
106 				 * current process. */
107 } ThreadSpecificData;
108 static Tcl_ThreadDataKey dataKey;
109 
110 /*
111  * The information below is used for communication between processes during
112  * "send" commands. Each process keeps a private window, never even mapped,
113  * with one property, "Comm". When a command is sent to an interpreter, the
114  * command is appended to the comm property of the communication window
115  * associated with the interp's process. Similarly, when a result is returned
116  * from a sent command, it is also appended to the comm property.
117  *
118  * Each command and each result takes the form of ASCII text. For a command,
119  * the text consists of a zero character followed by several null-terminated
120  * ASCII strings. The first string consists of the single letter "c".
121  * Subsequent strings have the form "option value" where the following options
122  * are supported:
123  *
124  * -r commWindow serial
125  *
126  *	This option means that a response should be sent to the window whose X
127  *	identifier is "commWindow" (in hex), and the response should be
128  *	identified with the serial number given by "serial" (in decimal). If
129  *	this option isn't specified then the send is asynchronous and no
130  *	response is sent.
131  *
132  * -n name
133  *
134  *	"Name" gives the name of the application for which the command is
135  *	intended. This option must be present.
136  *
137  * -s script
138  *
139  *	"Script" is the script to be executed. This option must be present.
140  *
141  * The options may appear in any order. The -n and -s options must be present,
142  * but -r may be omitted for asynchronous RPCs. For compatibility with future
143  * releases that may add new features, there may be additional options
144  * present; as long as they start with a "-" character, they will be ignored.
145  *
146  * A result also consists of a zero character followed by several null-
147  * terminated ASCII strings. The first string consists of the single letter
148  * "r". Subsequent strings have the form "option value" where the following
149  * options are supported:
150  *
151  * -s serial
152  *
153  *	Identifies the command for which this is the result. It is the same as
154  *	the "serial" field from the -s option in the command. This option must
155  *	be present.
156  *
157  * -c code
158  *
159  *	"Code" is the completion code for the script, in decimal. If the code
160  *	is omitted it defaults to TCL_OK.
161  *
162  * -r result
163  *
164  *	"Result" is the result string for the script, which may be either a
165  *	result or an error message. If this field is omitted then it defaults
166  *	to an empty string.
167  *
168  * -i errorInfo
169  *
170  *	"ErrorInfo" gives a string with which to initialize the errorInfo
171  *	variable. This option may be omitted; it is ignored unless the
172  *	completion code is TCL_ERROR.
173  *
174  * -e errorCode
175  *
176  *	"ErrorCode" gives a string with with to initialize the errorCode
177  *	variable. This option may be omitted; it is ignored unless the
178  *	completion code is TCL_ERROR.
179  *
180  * Options may appear in any order, and only the -s option must be present. As
181  * with commands, there may be additional options besides these; unknown
182  * options are ignored.
183  */
184 
185 /*
186  * Other miscellaneous per-process data:
187  */
188 
189 static struct {
190     int sendSerial;		/* The serial number that was used in the last
191 				 * "send" command. */
192     int sendDebug;		/* This can be set while debugging to do
193 				 * things like skip locking the server. */
194 } localData = {0, 0};
195 
196 /*
197  * Maximum size property that can be read at one time by this module:
198  */
199 
200 #define MAX_PROP_WORDS 100000
201 
202 /*
203  * Forward declarations for functions defined later in this file:
204  */
205 
206 static int		AppendErrorProc(ClientData clientData,
207 			    XErrorEvent *errorPtr);
208 static void		AppendPropCarefully(Display *display,
209 			    Window window, Atom property, char *value,
210 			    int length, PendingCommand *pendingPtr);
211 static void		DeleteProc(ClientData clientData);
212 static void		RegAddName(NameRegistry *regPtr,
213 			    const char *name, Window commWindow);
214 static void		RegClose(NameRegistry *regPtr);
215 static void		RegDeleteName(NameRegistry *regPtr, const char *name);
216 static Window		RegFindName(NameRegistry *regPtr, const char *name);
217 static NameRegistry *	RegOpen(Tcl_Interp *interp,
218 			    TkDisplay *dispPtr, int lock);
219 static void		SendEventProc(ClientData clientData, XEvent *eventPtr);
220 static int		SendInit(Tcl_Interp *interp, TkDisplay *dispPtr);
221 static Tk_RestrictProc SendRestrictProc;
222 static int		ServerSecure(TkDisplay *dispPtr);
223 static void		UpdateCommWindow(TkDisplay *dispPtr);
224 static int		ValidateName(TkDisplay *dispPtr, const char *name,
225 			    Window commWindow, int oldOK);
226 
227 /*
228  *----------------------------------------------------------------------
229  *
230  * RegOpen --
231  *
232  *	This function loads the name registry for a display into memory so
233  *	that it can be manipulated.
234  *
235  * Results:
236  *	The return value is a pointer to the loaded registry.
237  *
238  * Side effects:
239  *	If "lock" is set then the server will be locked. It is the caller's
240  *	responsibility to call RegClose when finished with the registry, so
241  *	that we can write back the registry if needed, unlock the server if
242  *	needed, and free memory.
243  *
244  *----------------------------------------------------------------------
245  */
246 
247 static NameRegistry *
RegOpen(Tcl_Interp * interp,TkDisplay * dispPtr,int lock)248 RegOpen(
249     Tcl_Interp *interp,		/* Interpreter to use for error reporting
250 				 * (errors cause a panic so in fact no error
251 				 * is ever returned, but the interpreter is
252 				 * needed anyway). */
253     TkDisplay *dispPtr,		/* Display whose name registry is to be
254 				 * opened. */
255     int lock)			/* Non-zero means lock the window server when
256 				 * opening the registry, so no-one else can
257 				 * use the registry until we close it. */
258 {
259     NameRegistry *regPtr;
260     int result, actualFormat;
261     unsigned long bytesAfter;
262     Atom actualType;
263     char **propertyPtr;
264     Tk_ErrorHandler handler;
265 
266     if (dispPtr->commTkwin == NULL) {
267 	SendInit(interp, dispPtr);
268     }
269 
270     handler = Tk_CreateErrorHandler(dispPtr->display, -1, -1, -1, NULL, NULL);
271 
272     regPtr = (NameRegistry *)ckalloc(sizeof(NameRegistry));
273     regPtr->dispPtr = dispPtr;
274     regPtr->locked = 0;
275     regPtr->modified = 0;
276     regPtr->allocedByX = 1;
277     propertyPtr = &regPtr->property;
278 
279     if (lock && !localData.sendDebug) {
280 	XGrabServer(dispPtr->display);
281 	regPtr->locked = 1;
282     }
283 
284     /*
285      * Read the registry property.
286      */
287 
288     result = XGetWindowProperty(dispPtr->display,
289 	    RootWindow(dispPtr->display, 0),
290 	    dispPtr->registryProperty, 0, MAX_PROP_WORDS,
291 	    False, XA_STRING, &actualType, &actualFormat,
292 	    &regPtr->propLength, &bytesAfter,
293 	    (unsigned char **) propertyPtr);
294 
295     if (actualType == None) {
296 	regPtr->propLength = 0;
297 	regPtr->property = NULL;
298     } else if ((result != Success) || (actualFormat != 8)
299 	    || (actualType != XA_STRING)) {
300 	/*
301 	 * The property is improperly formed; delete it.
302 	 */
303 
304 	if (regPtr->property != NULL) {
305 	    XFree(regPtr->property);
306 	    regPtr->propLength = 0;
307 	    regPtr->property = NULL;
308 	}
309 	XDeleteProperty(dispPtr->display,
310 		RootWindow(dispPtr->display, 0),
311 		dispPtr->registryProperty);
312         XSync(dispPtr->display, False);
313     }
314 
315     Tk_DeleteErrorHandler(handler);
316 
317     /*
318      * Xlib placed an extra null byte after the end of the property, just to
319      * make sure that it is always NULL-terminated. Be sure to include this
320      * byte in our count if it's needed to ensure null termination (note: as
321      * of 8/95 I'm no longer sure why this code is needed; seems like it
322      * shouldn't be).
323      */
324 
325     if ((regPtr->propLength > 0)
326 	    && (regPtr->property[regPtr->propLength-1] != 0)) {
327 	regPtr->propLength++;
328     }
329     return regPtr;
330 }
331 
332 /*
333  *----------------------------------------------------------------------
334  *
335  * RegFindName --
336  *
337  *	Given an open name registry, this function finds an entry with a given
338  *	name, if there is one, and returns information about that entry.
339  *
340  * Results:
341  *	The return value is the X identifier for the comm window for the
342  *	application named "name", or None if there is no such entry in the
343  *	registry.
344  *
345  * Side effects:
346  *	None.
347  *
348  *----------------------------------------------------------------------
349  */
350 
351 static Window
RegFindName(NameRegistry * regPtr,const char * name)352 RegFindName(
353     NameRegistry *regPtr,	/* Pointer to a registry opened with a
354 				 * previous call to RegOpen. */
355     const char *name)		/* Name of an application. */
356 {
357     char *p;
358 
359     for (p=regPtr->property ; p-regPtr->property<(int)regPtr->propLength ;) {
360 	char *entry = p;
361 
362 	while ((*p != 0) && (!isspace(UCHAR(*p)))) {
363 	    p++;
364 	}
365 	if ((*p != 0) && (strcmp(name, p+1) == 0)) {
366 	    unsigned id;
367 
368 	    if (sscanf(entry, "%x", &id) == 1) {
369 		/*
370 		 * Must cast from an unsigned int to a Window in case we are
371 		 * on a 64-bit architecture.
372 		 */
373 
374 		return (Window) id;
375 	    }
376 	}
377 	while (*p != 0) {
378 	    p++;
379 	}
380 	p++;
381     }
382     return None;
383 }
384 
385 /*
386  *----------------------------------------------------------------------
387  *
388  * RegDeleteName --
389  *
390  *	This function deletes the entry for a given name from an open
391  *	registry.
392  *
393  * Results:
394  *	None.
395  *
396  * Side effects:
397  *	If there used to be an entry named "name" in the registry, then it is
398  *	deleted and the registry is marked as modified so it will be written
399  *	back when closed.
400  *
401  *----------------------------------------------------------------------
402  */
403 
404 static void
RegDeleteName(NameRegistry * regPtr,const char * name)405 RegDeleteName(
406     NameRegistry *regPtr,	/* Pointer to a registry opened with a
407 				 * previous call to RegOpen. */
408     const char *name)		/* Name of an application. */
409 {
410     char *p;
411 
412     for (p=regPtr->property ; p-regPtr->property<(int)regPtr->propLength ;) {
413 	char *entry = p, *entryName;
414 
415 	while ((*p != 0) && (!isspace(UCHAR(*p)))) {
416 	    p++;
417 	}
418 	if (*p != 0) {
419 	    p++;
420 	}
421 	entryName = p;
422 	while (*p != 0) {
423 	    p++;
424 	}
425 	p++;
426 	if (strcmp(name, entryName) == 0) {
427 	    int count;
428 
429 	    /*
430 	     * Found the matching entry. Copy everything after it down on top
431 	     * of it.
432 	     */
433 
434 	    count = regPtr->propLength - (p - regPtr->property);
435 	    if (count > 0) {
436 		char *src, *dst;
437 
438 		for (src=p , dst=entry ; count>0 ; src++, dst++, count--) {
439 		    *dst = *src;
440 		}
441 	    }
442 	    regPtr->propLength -= p - entry;
443 	    regPtr->modified = 1;
444 	    return;
445 	}
446     }
447 }
448 
449 /*
450  *----------------------------------------------------------------------
451  *
452  * RegAddName --
453  *
454  *	Add a new entry to an open registry.
455  *
456  * Results:
457  *	None.
458  *
459  * Side effects:
460  *	The open registry is expanded; it is marked as modified so that it
461  *	will be written back when closed.
462  *
463  *----------------------------------------------------------------------
464  */
465 
466 static void
RegAddName(NameRegistry * regPtr,const char * name,Window commWindow)467 RegAddName(
468     NameRegistry *regPtr,	/* Pointer to a registry opened with a
469 				 * previous call to RegOpen. */
470     const char *name,		/* Name of an application. The caller must
471 				 * ensure that this name isn't already
472 				 * registered. */
473     Window commWindow)		/* X identifier for comm. window of
474 				 * application. */
475 {
476     char id[30], *newProp;
477     int idLength, newBytes;
478 
479     sprintf(id, "%x ", (unsigned) commWindow);
480     idLength = strlen(id);
481     newBytes = idLength + strlen(name) + 1;
482     newProp = (char *)ckalloc(regPtr->propLength + newBytes);
483     strcpy(newProp, id);
484     strcpy(newProp+idLength, name);
485     if (regPtr->property != NULL) {
486 	memcpy(newProp + newBytes, regPtr->property, regPtr->propLength);
487 	if (regPtr->allocedByX) {
488 	    XFree(regPtr->property);
489 	} else {
490 	    ckfree(regPtr->property);
491 	}
492     }
493     regPtr->modified = 1;
494     regPtr->propLength += newBytes;
495     regPtr->property = newProp;
496     regPtr->allocedByX = 0;
497 }
498 
499 /*
500  *----------------------------------------------------------------------
501  *
502  * RegClose --
503  *
504  *	This function is called to end a series of operations on a name
505  *	registry.
506  *
507  * Results:
508  *	None.
509  *
510  * Side effects:
511  *	The registry is written back if it has been modified, and the X server
512  *	is unlocked if it was locked. Memory for the registry is freed, so the
513  *	caller should never use regPtr again.
514  *
515  *----------------------------------------------------------------------
516  */
517 
518 static void
RegClose(NameRegistry * regPtr)519 RegClose(
520     NameRegistry *regPtr)	/* Pointer to a registry opened with a
521 				 * previous call to RegOpen. */
522 {
523     Tk_ErrorHandler handler;
524 
525     handler = Tk_CreateErrorHandler(regPtr->dispPtr->display, -1, -1, -1,
526             NULL, NULL);
527 
528     if (regPtr->modified) {
529 	if (!regPtr->locked && !localData.sendDebug) {
530 	    Tcl_Panic("The name registry was modified without being locked!");
531 	}
532 	XChangeProperty(regPtr->dispPtr->display,
533 		RootWindow(regPtr->dispPtr->display, 0),
534 		regPtr->dispPtr->registryProperty, XA_STRING, 8,
535 		PropModeReplace, (unsigned char *) regPtr->property,
536 		(int) regPtr->propLength);
537     }
538 
539     if (regPtr->locked) {
540 	XUngrabServer(regPtr->dispPtr->display);
541     }
542 
543     /*
544      * After ungrabbing the server, it's important to flush the output
545      * immediately so that the server sees the ungrab command. Otherwise we
546      * might do something else that needs to communicate with the server (such
547      * as invoking a subprocess that needs to do I/O to the screen); if the
548      * ungrab command is still sitting in our output buffer, we could
549      * deadlock.
550      */
551 
552     XFlush(regPtr->dispPtr->display);
553 
554     Tk_DeleteErrorHandler(handler);
555 
556     if (regPtr->property != NULL) {
557 	if (regPtr->allocedByX) {
558 	    XFree(regPtr->property);
559 	} else {
560 	    ckfree(regPtr->property);
561 	}
562     }
563     ckfree(regPtr);
564 }
565 
566 /*
567  *----------------------------------------------------------------------
568  *
569  * ValidateName --
570  *
571  *	This function checks to see if an entry in the registry is still
572  *	valid.
573  *
574  * Results:
575  *	The return value is 1 if the given commWindow exists and its name is
576  *	"name". Otherwise 0 is returned.
577  *
578  * Side effects:
579  *	None.
580  *
581  *----------------------------------------------------------------------
582  */
583 
584 static int
ValidateName(TkDisplay * dispPtr,const char * name,Window commWindow,int oldOK)585 ValidateName(
586     TkDisplay *dispPtr,		/* Display for which to perform the
587 				 * validation. */
588     const char *name,		/* The name of an application. */
589     Window commWindow,		/* X identifier for the application's comm.
590 				 * window. */
591     int oldOK)			/* Non-zero means that we should consider an
592 				 * application to be valid even if it looks
593 				 * like an old-style (pre-4.0) one; 0 means
594 				 * consider these invalid. */
595 {
596     int result, actualFormat, argc, i;
597     unsigned long length, bytesAfter;
598     Atom actualType;
599     char *property, **propertyPtr = &property;
600     Tk_ErrorHandler handler;
601     const char **argv;
602 
603     property = NULL;
604 
605     /*
606      * Ignore X errors when reading the property (e.g., the window might not
607      * exist). If an error occurs, result will be some value other than
608      * Success.
609      */
610 
611     handler = Tk_CreateErrorHandler(dispPtr->display, -1, -1, -1, NULL, NULL);
612     result = XGetWindowProperty(dispPtr->display, commWindow,
613 	    dispPtr->appNameProperty, 0, MAX_PROP_WORDS,
614 	    False, XA_STRING, &actualType, &actualFormat,
615 	    &length, &bytesAfter, (unsigned char **) propertyPtr);
616 
617     if ((result == Success) && (actualType == None)) {
618 	XWindowAttributes atts;
619 
620 	/*
621 	 * The comm. window exists but the property we're looking for doesn't
622 	 * exist. This probably means that the application comes from an older
623 	 * version of Tk (< 4.0) that didn't set the property; if this is the
624 	 * case, then assume for compatibility's sake that everything's OK.
625 	 * However, it's also possible that some random application has
626 	 * re-used the window id for something totally unrelated. Check a few
627 	 * characteristics of the window, such as its dimensions and mapped
628 	 * state, to be sure that it still "smells" like a commWindow.
629 	 */
630 
631 	if (!oldOK
632 		|| !XGetWindowAttributes(dispPtr->display, commWindow, &atts)
633 		|| (atts.width != 1) || (atts.height != 1)
634 		|| (atts.map_state != IsUnmapped)) {
635 	    result = 0;
636 	} else {
637 	    result = 1;
638 	}
639     } else if ((result == Success) && (actualFormat == 8)
640 	    && (actualType == XA_STRING)) {
641 	result = 0;
642 	if (Tcl_SplitList(NULL, property, &argc, &argv) == TCL_OK) {
643 	    for (i = 0; i < argc; i++) {
644 		if (strcmp(argv[i], name) == 0) {
645 		    result = 1;
646 		    break;
647 		}
648 	    }
649 	    ckfree(argv);
650 	}
651     } else {
652 	result = 0;
653     }
654     Tk_DeleteErrorHandler(handler);
655     if (property != NULL) {
656 	XFree(property);
657     }
658     return result;
659 }
660 
661 /*
662  *----------------------------------------------------------------------
663  *
664  * ServerSecure --
665  *
666  *	Check whether a server is secure enough for us to trust Tcl scripts
667  *	arriving via that server.
668  *
669  * Results:
670  *	The return value is 1 if the server is secure, which means that
671  *	host-style authentication is turned on but there are no hosts in the
672  *	enabled list. This means that some other form of authorization
673  *	(presumably more secure, such as xauth) is in use.
674  *
675  * Side effects:
676  *	None.
677  *
678  *----------------------------------------------------------------------
679  */
680 
681 static int
ServerSecure(TkDisplay * dispPtr)682 ServerSecure(
683     TkDisplay *dispPtr)		/* Display to check. */
684 {
685 #ifdef TK_NO_SECURITY
686     return 1;
687 #else
688     XHostAddress *addrPtr;
689     int numHosts, secure;
690     Bool enabled;
691 
692     addrPtr = XListHosts(dispPtr->display, &numHosts, &enabled);
693     if (!enabled) {
694     insecure:
695 	secure = 0;
696     } else if (numHosts == 0) {
697 	secure = 1;
698     } else {
699 	/*
700 	 * Recent versions of X11 have the extra feature of allowing more
701 	 * sophisticated authorization checks to be performed than the dozy
702 	 * old ones that used to plague xhost usage. However, not all deployed
703 	 * versions of Xlib know how to deal with this feature, so this code
704 	 * is conditional on having the right #def in place. [Bug 1909931]
705 	 *
706 	 * Note that at this point we know that there's at least one entry in
707 	 * the list returned by XListHosts. However there may be multiple
708 	 * entries; as long as each is one of either 'SI:localhost:*' or
709 	 * 'SI:localgroup:*' then we will claim to be secure enough.
710 	 */
711 
712 #ifdef FamilyServerInterpreted
713 	XServerInterpretedAddress *siPtr;
714 	int i;
715 
716 	for (i=0 ; i<numHosts ; i++) {
717 	    if (addrPtr[i].family != FamilyServerInterpreted) {
718 		/*
719 		 * We don't understand what the X server is letting in, so we
720 		 * err on the side of safety.
721 		 */
722 
723 		goto insecure;
724 	    }
725 	    siPtr = (XServerInterpretedAddress *) addrPtr[0].address;
726 
727 	    /*
728 	     * We don't check the username or group here. This is because it's
729 	     * officially non-portable and we are just making sure there
730 	     * aren't silly misconfigurations. (Apparently 'root' is not a
731 	     * very good choice, but we still don't put any effort in to spot
732 	     * that.) However we do check to see that the constraints are
733 	     * imposed against the connecting user and/or group.
734 	     */
735 
736 	    if (       !(siPtr->typelength == 9 /* ==strlen("localuser") */
737 			&& !memcmp(siPtr->type, "localuser", 9))
738 		    && !(siPtr->typelength == 10 /* ==strlen("localgroup") */
739 			&& !memcmp(siPtr->type, "localgroup", 10))) {
740 		/*
741 		 * The other defined types of server-interpreted controls
742 		 * involve particular hosts. These are still insecure for the
743 		 * same reasons that classic xhost access is insecure; there's
744 		 * just no way to be sure that the users on those systems are
745 		 * the ones who should be allowed to connect to this display.
746 		 */
747 
748 		goto insecure;
749 	    }
750 	}
751 	secure = 1;
752 #else
753 	/*
754 	 * We don't understand what the X server is letting in, so we err on
755 	 * the side of safety.
756 	 */
757 
758 	secure = 0;
759 #endif /* FamilyServerInterpreted */
760     }
761     if (addrPtr != NULL) {
762 	XFree((char *) addrPtr);
763     }
764     return secure;
765 #endif /* TK_NO_SECURITY */
766 }
767 
768 /*
769  *----------------------------------------------------------------------
770  *
771  * Tk_SetAppName --
772  *
773  *	This function is called to associate an ASCII name with a Tk
774  *	application. If the application has already been named, the name
775  *	replaces the old one.
776  *
777  * Results:
778  *	The return value is the name actually given to the application. This
779  *	will normally be the same as name, but if name was already in use for
780  *	an application then a name of the form "name #2" will be chosen, with
781  *	a high enough number to make the name unique.
782  *
783  * Side effects:
784  *	Registration info is saved, thereby allowing the "send" command to be
785  *	used later to invoke commands in the application. In addition, the
786  *	"send" command is created in the application's interpreter. The
787  *	registration will be removed automatically if the interpreter is
788  *	deleted or the "send" command is removed.
789  *
790  *----------------------------------------------------------------------
791  */
792 
793 const char *
Tk_SetAppName(Tk_Window tkwin,const char * name)794 Tk_SetAppName(
795     Tk_Window tkwin,		/* Token for any window in the application to
796 				 * be named: it is just used to identify the
797 				 * application and the display. */
798     const char *name)		/* The name that will be used to refer to the
799 				 * interpreter in later "send" commands. Must
800 				 * be globally unique. */
801 {
802     RegisteredInterp *riPtr, *riPtr2;
803     Window w;
804     TkWindow *winPtr = (TkWindow *) tkwin;
805     TkDisplay *dispPtr = winPtr->dispPtr;
806     NameRegistry *regPtr;
807     Tcl_Interp *interp;
808     const char *actualName;
809     Tcl_DString dString;
810     int offset, i;
811     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
812 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
813 
814     interp = winPtr->mainPtr->interp;
815     if (dispPtr->commTkwin == NULL) {
816 	SendInit(interp, winPtr->dispPtr);
817     }
818 
819     /*
820      * See if the application is already registered; if so, remove its current
821      * name from the registry.
822      */
823 
824     regPtr = RegOpen(interp, winPtr->dispPtr, 1);
825     for (riPtr = tsdPtr->interpListPtr; ; riPtr = riPtr->nextPtr) {
826 	if (riPtr == NULL) {
827 	    /*
828 	     * This interpreter isn't currently registered; create the data
829 	     * structure that will be used to register it locally, plus add
830 	     * the "send" command to the interpreter.
831 	     */
832 
833 	    riPtr = (RegisteredInterp *)ckalloc(sizeof(RegisteredInterp));
834 	    riPtr->interp = interp;
835 	    riPtr->dispPtr = winPtr->dispPtr;
836 	    riPtr->nextPtr = tsdPtr->interpListPtr;
837 	    tsdPtr->interpListPtr = riPtr;
838 	    riPtr->name = NULL;
839 	    Tcl_CreateObjCommand(interp, "send", Tk_SendObjCmd, riPtr, DeleteProc);
840 	    if (Tcl_IsSafe(interp)) {
841 		Tcl_HideCommand(interp, "send", "send");
842 	    }
843 	    break;
844 	}
845 	if (riPtr->interp == interp) {
846 	    /*
847 	     * The interpreter is currently registered; remove it from the
848 	     * name registry.
849 	     */
850 
851 	    if (riPtr->name) {
852 		RegDeleteName(regPtr, riPtr->name);
853 		ckfree(riPtr->name);
854 	    }
855 	    break;
856 	}
857     }
858 
859     /*
860      * Pick a name to use for the application. Use "name" if it's not already
861      * in use. Otherwise add a suffix such as " #2", trying larger and larger
862      * numbers until we eventually find one that is unique.
863      */
864 
865     actualName = name;
866     offset = 0;				/* Needed only to avoid "used before
867 					 * set" compiler warnings. */
868     for (i = 1; ; i++) {
869 	if (i > 1) {
870 	    if (i == 2) {
871 		Tcl_DStringInit(&dString);
872 		Tcl_DStringAppend(&dString, name, -1);
873 		Tcl_DStringAppend(&dString, " #", 2);
874 		offset = Tcl_DStringLength(&dString);
875 		Tcl_DStringSetLength(&dString, offset+TCL_INTEGER_SPACE);
876 		actualName = Tcl_DStringValue(&dString);
877 	    }
878 	    sprintf(Tcl_DStringValue(&dString) + offset, "%d", i);
879 	}
880 	w = RegFindName(regPtr, actualName);
881 	if (w == None) {
882 	    break;
883 	}
884 
885 	/*
886 	 * The name appears to be in use already, but double-check to be sure
887 	 * (perhaps the application died without removing its name from the
888 	 * registry?).
889 	 */
890 
891 	if (w == Tk_WindowId(dispPtr->commTkwin)) {
892 	    for (riPtr2 = tsdPtr->interpListPtr; riPtr2 != NULL;
893 		    riPtr2 = riPtr2->nextPtr) {
894 		if ((riPtr2->interp != interp) &&
895 			(strcmp(riPtr2->name, actualName) == 0)) {
896 		    goto nextSuffix;
897 		}
898 	    }
899 	    RegDeleteName(regPtr, actualName);
900 	    break;
901 	} else if (!ValidateName(winPtr->dispPtr, actualName, w, 1)) {
902 	    RegDeleteName(regPtr, actualName);
903 	    break;
904 	}
905     nextSuffix:
906 	continue;
907     }
908 
909     /*
910      * We've now got a name to use. Store it in the name registry and in the
911      * local entry for this application, plus put it in a property on the
912      * commWindow.
913      */
914 
915     RegAddName(regPtr, actualName, Tk_WindowId(dispPtr->commTkwin));
916     RegClose(regPtr);
917     riPtr->name = (char *)ckalloc(strlen(actualName) + 1);
918     strcpy(riPtr->name, actualName);
919     if (actualName != name) {
920 	Tcl_DStringFree(&dString);
921     }
922     UpdateCommWindow(dispPtr);
923 
924     return riPtr->name;
925 }
926 
927 /*
928  *--------------------------------------------------------------
929  *
930  * Tk_SendObjCmd --
931  *
932  *	This function is invoked to process the "send" Tcl command. See the
933  *	user documentation for details on what it does.
934  *
935  * Results:
936  *	A standard Tcl result.
937  *
938  * Side effects:
939  *	See the user documentation.
940  *
941  *--------------------------------------------------------------
942  */
943 
944 int
Tk_SendObjCmd(TCL_UNUSED (void *),Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])945 Tk_SendObjCmd(
946     TCL_UNUSED(void *),	/* Information about sender (only dispPtr
947 				 * field is used). */
948     Tcl_Interp *interp,		/* Current interpreter. */
949     int objc,			/* Number of arguments. */
950     Tcl_Obj *const objv[])	/* Argument strings. */
951 {
952     enum {
953 	SEND_ASYNC, SEND_DISPLAYOF, SEND_LAST
954     };
955     static const char *const sendOptions[] = {
956 	"-async",   "-displayof",   "--",  NULL
957     };
958     TkWindow *winPtr;
959     Window commWindow;
960     PendingCommand pending;
961     RegisteredInterp *riPtr;
962     const char *destName;
963     int result, index, async, i, firstArg;
964     Tk_RestrictProc *prevProc;
965     ClientData prevArg;
966     TkDisplay *dispPtr;
967     Tcl_Time timeout;
968     NameRegistry *regPtr;
969     Tcl_DString request;
970     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
971 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
972     Tcl_Interp *localInterp;	/* Used when the interpreter to send the
973 				 * command to is within the same process. */
974 
975     /*
976      * Process options, if any.
977      */
978 
979     async = 0;
980     winPtr = (TkWindow *) Tk_MainWindow(interp);
981     if (winPtr == NULL) {
982 	return TCL_ERROR;
983     }
984     for (i = 1; i < objc; i++) {
985 	if (Tcl_GetIndexFromObjStruct(interp, objv[i], sendOptions,
986 		sizeof(char *), "option", 0, &index) != TCL_OK) {
987 	    break;
988 	}
989 	if (index == SEND_ASYNC) {
990 	    ++async;
991 	} else if (index == SEND_DISPLAYOF) {
992 	    winPtr = (TkWindow *) Tk_NameToWindow(interp, Tcl_GetString(objv[++i]),
993 		    (Tk_Window) winPtr);
994 	    if (winPtr == NULL) {
995 		return TCL_ERROR;
996 	    }
997 	} else if (index == SEND_LAST) {
998 	    i++;
999 	    break;
1000 	}
1001     }
1002 
1003     if (objc < (i+2)) {
1004 	Tcl_WrongNumArgs(interp, 1, objv,
1005 		"?-option value ...? interpName arg ?arg ...?");
1006 	return TCL_ERROR;
1007     }
1008     destName = Tcl_GetString(objv[i]);
1009     firstArg = i+1;
1010 
1011     dispPtr = winPtr->dispPtr;
1012     if (dispPtr->commTkwin == NULL) {
1013 	SendInit(interp, winPtr->dispPtr);
1014     }
1015 
1016     /*
1017      * See if the target interpreter is local. If so, execute the command
1018      * directly without going through the X server. The only tricky thing is
1019      * passing the result from the target interpreter to the invoking
1020      * interpreter. Watch out: they could be the same!
1021      */
1022 
1023     for (riPtr = tsdPtr->interpListPtr; riPtr != NULL;
1024 	    riPtr = riPtr->nextPtr) {
1025 	if ((riPtr->dispPtr != dispPtr)
1026 		|| (strcmp(riPtr->name, destName) != 0)) {
1027 	    continue;
1028 	}
1029 	Tcl_Preserve(riPtr);
1030 	localInterp = riPtr->interp;
1031 	Tcl_Preserve(localInterp);
1032 	if (firstArg == (objc-1)) {
1033 	    result = Tcl_EvalEx(localInterp, Tcl_GetString(objv[firstArg]), -1, TCL_EVAL_GLOBAL);
1034 	} else {
1035 	    Tcl_DStringInit(&request);
1036 	    Tcl_DStringAppend(&request, Tcl_GetString(objv[firstArg]), -1);
1037 	    for (i = firstArg+1; i < objc; i++) {
1038 		Tcl_DStringAppend(&request, " ", 1);
1039 		Tcl_DStringAppend(&request, Tcl_GetString(objv[i]), -1);
1040 	    }
1041 	    result = Tcl_EvalEx(localInterp, Tcl_DStringValue(&request), -1, TCL_EVAL_GLOBAL);
1042 	    Tcl_DStringFree(&request);
1043 	}
1044 	if (interp != localInterp) {
1045 	    if (result == TCL_ERROR) {
1046 		Tcl_Obj *errorObjPtr;
1047 
1048 		/*
1049 		 * An error occurred, so transfer error information from the
1050 		 * destination interpreter back to our interpreter. Must clear
1051 		 * interp's result before calling Tcl_AddErrorInfo, since
1052 		 * Tcl_AddErrorInfo will store the interp's result in
1053 		 * errorInfo before appending riPtr's $errorInfo; we've
1054 		 * already got everything we need in riPtr's $errorInfo.
1055 		 */
1056 
1057 		Tcl_ResetResult(interp);
1058 		Tcl_AddErrorInfo(interp, Tcl_GetVar2(localInterp,
1059 			"errorInfo", NULL, TCL_GLOBAL_ONLY));
1060 		errorObjPtr = Tcl_GetVar2Ex(localInterp, "errorCode", NULL,
1061 			TCL_GLOBAL_ONLY);
1062 		Tcl_SetObjErrorCode(interp, errorObjPtr);
1063 	    }
1064 	    Tcl_SetObjResult(interp, Tcl_GetObjResult(localInterp));
1065 	    Tcl_ResetResult(localInterp);
1066 	}
1067 	Tcl_Release(riPtr);
1068 	Tcl_Release(localInterp);
1069 	return result;
1070     }
1071 
1072     /*
1073      * Bind the interpreter name to a communication window.
1074      */
1075 
1076     regPtr = RegOpen(interp, winPtr->dispPtr, 0);
1077     commWindow = RegFindName(regPtr, destName);
1078     RegClose(regPtr);
1079     if (commWindow == None) {
1080 	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1081 		"no application named \"%s\"", destName));
1082 	Tcl_SetErrorCode(interp, "TK", "LOOKUP", "APPLICATION", destName,
1083 		NULL);
1084 	return TCL_ERROR;
1085     }
1086 
1087     /*
1088      * Send the command to the target interpreter by appending it to the comm
1089      * window in the communication window.
1090      */
1091 
1092     localData.sendSerial++;
1093     Tcl_DStringInit(&request);
1094     Tcl_DStringAppend(&request, "\0c\0-n ", 6);
1095     Tcl_DStringAppend(&request, destName, -1);
1096     if (!async) {
1097 	char buffer[TCL_INTEGER_SPACE * 2];
1098 
1099 	sprintf(buffer, "%x %d",
1100 		(unsigned) Tk_WindowId(dispPtr->commTkwin),
1101 		localData.sendSerial);
1102 	Tcl_DStringAppend(&request, "\0-r ", 4);
1103 	Tcl_DStringAppend(&request, buffer, -1);
1104     }
1105     Tcl_DStringAppend(&request, "\0-s ", 4);
1106     Tcl_DStringAppend(&request, Tcl_GetString(objv[firstArg]), -1);
1107     for (i = firstArg+1; i < objc; i++) {
1108 	Tcl_DStringAppend(&request, " ", 1);
1109 	Tcl_DStringAppend(&request, Tcl_GetString(objv[i]), -1);
1110     }
1111 
1112     if (!async) {
1113 	/*
1114 	 * Register the fact that we're waiting for a command to complete
1115 	 * (this is needed by SendEventProc and by AppendErrorProc to pass
1116 	 * back the command's results). Set up a timeout handler so that
1117 	 * we can check during long sends to make sure that the destination
1118 	 * application is still alive.
1119 	 *
1120 	 * We prepare the pending struct here in order to catch potential
1121 	 * early X errors from AppendPropCarefully() due to XSync().
1122 	 */
1123 
1124 	pending.serial = localData.sendSerial;
1125 	pending.dispPtr = dispPtr;
1126 	pending.target = destName;
1127 	pending.commWindow = commWindow;
1128 	pending.interp = interp;
1129 	pending.result = NULL;
1130 	pending.errorInfo = NULL;
1131 	pending.errorCode = NULL;
1132 	pending.gotResponse = 0;
1133 	pending.nextPtr = tsdPtr->pendingCommands;
1134 	tsdPtr->pendingCommands = &pending;
1135     }
1136     (void) AppendPropCarefully(dispPtr->display, commWindow,
1137 	    dispPtr->commProperty, Tcl_DStringValue(&request),
1138 	    Tcl_DStringLength(&request) + 1, (async ? NULL : &pending));
1139     Tcl_DStringFree(&request);
1140     if (async) {
1141 	/*
1142 	 * This is an asynchronous send: return immediately without waiting
1143 	 * for a response.
1144 	 */
1145 
1146 	return TCL_OK;
1147     }
1148 
1149     /*
1150      * Enter a loop processing X events until the result comes in or the
1151      * target is declared to be dead. While waiting for a result, look only at
1152      * send-related events so that the send is synchronous with respect to
1153      * other events in the application.
1154      */
1155 
1156     prevProc = Tk_RestrictEvents(SendRestrictProc, NULL, &prevArg);
1157     Tcl_GetTime(&timeout);
1158     timeout.sec += 2;
1159     while (!pending.gotResponse) {
1160 	if (!TkUnixDoOneXEvent(&timeout)) {
1161 	    /*
1162 	     * An unusually long amount of time has elapsed during the
1163 	     * processing of a sent command. Check to make sure that the
1164 	     * target application still exists. If it does, reset the timeout.
1165 	     */
1166 
1167 	    if (!ValidateName(pending.dispPtr, pending.target,
1168 		    pending.commWindow, 0)) {
1169 		const char *msg;
1170 
1171 		if (ValidateName(pending.dispPtr, pending.target,
1172 			pending.commWindow, 1)) {
1173 		    msg = "target application died or uses a Tk version before 4.0";
1174 		} else {
1175 		    msg = "target application died";
1176 		}
1177 		pending.code = TCL_ERROR;
1178 		pending.result = (char *)ckalloc(strlen(msg) + 1);
1179 		strcpy(pending.result, msg);
1180 		pending.gotResponse = 1;
1181 	    } else {
1182 		Tcl_GetTime(&timeout);
1183 		timeout.sec += 2;
1184 	    }
1185 	}
1186     }
1187     Tk_RestrictEvents(prevProc, prevArg, &prevArg);
1188 
1189     /*
1190      * Unregister the information about the pending command and return the
1191      * result.
1192      */
1193 
1194     if (tsdPtr->pendingCommands != &pending) {
1195 	Tcl_Panic("Tk_SendCmd: corrupted send stack");
1196     }
1197     tsdPtr->pendingCommands = pending.nextPtr;
1198     if (pending.errorInfo != NULL) {
1199 	/*
1200 	 * Special trick: must clear the interp's result before calling
1201 	 * Tcl_AddErrorInfo, since Tcl_AddErrorInfo will store the interp's
1202 	 * result in errorInfo before appending pending.errorInfo; we've
1203 	 * already got everything we need in pending.errorInfo.
1204 	 */
1205 
1206 	Tcl_ResetResult(interp);
1207 	Tcl_AddErrorInfo(interp, pending.errorInfo);
1208 	ckfree(pending.errorInfo);
1209     }
1210     if (pending.errorCode != NULL) {
1211 	Tcl_SetObjErrorCode(interp, Tcl_NewStringObj(pending.errorCode, -1));
1212 	ckfree(pending.errorCode);
1213     }
1214     Tcl_SetObjResult(interp, Tcl_NewStringObj(pending.result, -1));
1215     ckfree(pending.result);
1216     return pending.code;
1217 }
1218 
1219 /*
1220  *----------------------------------------------------------------------
1221  *
1222  * TkGetInterpNames --
1223  *
1224  *	This function is invoked to fetch a list of all the interpreter names
1225  *	currently registered for the display of a particular window.
1226  *
1227  * Results:
1228  *	A standard Tcl return value. The interp's result will be set to hold a
1229  *	list of all the interpreter names defined for tkwin's display. If an
1230  *	error occurs, then TCL_ERROR is returned and the interp's result will
1231  *	hold an error message.
1232  *
1233  * Side effects:
1234  *	None.
1235  *
1236  *----------------------------------------------------------------------
1237  */
1238 
1239 int
TkGetInterpNames(Tcl_Interp * interp,Tk_Window tkwin)1240 TkGetInterpNames(
1241     Tcl_Interp *interp,		/* Interpreter for returning a result. */
1242     Tk_Window tkwin)		/* Window whose display is to be used for the
1243 				 * lookup. */
1244 {
1245     TkWindow *winPtr = (TkWindow *) tkwin;
1246     NameRegistry *regPtr;
1247     Tcl_Obj *resultObj = Tcl_NewObj();
1248     char *p;
1249 
1250     /*
1251      * Read the registry property, then scan through all of its entries.
1252      * Validate each entry to be sure that its application still exists.
1253      */
1254 
1255     regPtr = RegOpen(interp, winPtr->dispPtr, 1);
1256     for (p=regPtr->property ; p-regPtr->property<(int)regPtr->propLength ;) {
1257 	char *entry = p, *entryName;
1258 	Window commWindow;
1259 	unsigned id;
1260 
1261 	if (sscanf(p, "%x", (unsigned *) &id) != 1) {
1262 	    commWindow = None;
1263 	} else {
1264 	    commWindow = id;
1265 	}
1266 	while ((*p != 0) && (!isspace(UCHAR(*p)))) {
1267 	    p++;
1268 	}
1269 	if (*p != 0) {
1270 	    p++;
1271 	}
1272 	entryName = p;
1273 	while (*p != 0) {
1274 	    p++;
1275 	}
1276 	p++;
1277 	if (ValidateName(winPtr->dispPtr, entryName, commWindow, 1)) {
1278 	    /*
1279 	     * The application still exists; add its name to the result.
1280 	     */
1281 
1282 	    Tcl_ListObjAppendElement(NULL, resultObj,
1283 		    Tcl_NewStringObj(entryName, -1));
1284 	} else {
1285 	    int count;
1286 
1287 	    /*
1288 	     * This name is bogus (perhaps the application died without
1289 	     * cleaning up its entry in the registry?). Delete the name.
1290 	     */
1291 
1292 	    count = regPtr->propLength - (p - regPtr->property);
1293 	    if (count > 0) {
1294 		char *src, *dst;
1295 
1296 		for (src = p, dst = entry; count > 0; src++, dst++, count--) {
1297 		    *dst = *src;
1298 		}
1299 	    }
1300 	    regPtr->propLength -= p - entry;
1301 	    regPtr->modified = 1;
1302 	    p = entry;
1303 	}
1304     }
1305     RegClose(regPtr);
1306     Tcl_SetObjResult(interp, resultObj);
1307     return TCL_OK;
1308 }
1309 
1310 /*
1311  *--------------------------------------------------------------
1312  *
1313  * TkSendCleanup --
1314  *
1315  *	This function is called to free resources used by the communication
1316  *	channels for sending commands and receiving results.
1317  *
1318  * Results:
1319  *	None.
1320  *
1321  * Side effects:
1322  *	Frees various data structures and windows.
1323  *
1324  *--------------------------------------------------------------
1325  */
1326 
1327 void
TkSendCleanup(TkDisplay * dispPtr)1328 TkSendCleanup(
1329     TkDisplay *dispPtr)
1330 {
1331     if (dispPtr->commTkwin != NULL) {
1332 	Tk_DeleteEventHandler(dispPtr->commTkwin, PropertyChangeMask,
1333 		SendEventProc, dispPtr);
1334 	Tk_DestroyWindow(dispPtr->commTkwin);
1335 	Tcl_Release(dispPtr->commTkwin);
1336 	dispPtr->commTkwin = NULL;
1337     }
1338 }
1339 
1340 /*
1341  *--------------------------------------------------------------
1342  *
1343  * SendInit --
1344  *
1345  *	This function is called to initialize the communication channels for
1346  *	sending commands and receiving results.
1347  *
1348  * Results:
1349  *	None.
1350  *
1351  * Side effects:
1352  *	Sets up various data structures and windows.
1353  *
1354  *--------------------------------------------------------------
1355  */
1356 
1357 static int
SendInit(TCL_UNUSED (Tcl_Interp *),TkDisplay * dispPtr)1358 SendInit(
1359     TCL_UNUSED(Tcl_Interp *),	/* Interpreter to use for error reporting (no
1360 				 * errors are ever returned, but the
1361 				 * interpreter is needed anyway). */
1362     TkDisplay *dispPtr)		/* Display to initialize. */
1363 {
1364     XSetWindowAttributes atts;
1365 
1366     /*
1367      * Create the window used for communication, and set up an event handler
1368      * for it.
1369      */
1370 
1371     dispPtr->commTkwin = (Tk_Window) TkAllocWindow(dispPtr,
1372     	DefaultScreen(dispPtr->display), NULL);
1373     Tcl_Preserve(dispPtr->commTkwin);
1374     ((TkWindow *) dispPtr->commTkwin)->flags |=
1375 	    TK_TOP_HIERARCHY|TK_TOP_LEVEL|TK_HAS_WRAPPER|TK_WIN_MANAGED;
1376     TkWmNewWindow((TkWindow *) dispPtr->commTkwin);
1377     atts.override_redirect = True;
1378     Tk_ChangeWindowAttributes(dispPtr->commTkwin,
1379 	    CWOverrideRedirect, &atts);
1380     Tk_CreateEventHandler(dispPtr->commTkwin, PropertyChangeMask,
1381 	    SendEventProc, dispPtr);
1382     Tk_MakeWindowExist(dispPtr->commTkwin);
1383 
1384     /*
1385      * Get atoms used as property names.
1386      */
1387 
1388     dispPtr->commProperty = Tk_InternAtom(dispPtr->commTkwin, "Comm");
1389     dispPtr->registryProperty = Tk_InternAtom(dispPtr->commTkwin,
1390 	    "InterpRegistry");
1391     dispPtr->appNameProperty = Tk_InternAtom(dispPtr->commTkwin,
1392 	    "TK_APPLICATION");
1393 
1394     return TCL_OK;
1395 }
1396 
1397 /*
1398  *--------------------------------------------------------------
1399  *
1400  * SendEventProc --
1401  *
1402  *	This function is invoked automatically by the toolkit event manager
1403  *	when a property changes on the communication window. This function
1404  *	reads the property and handles command requests and responses.
1405  *
1406  * Results:
1407  *	None.
1408  *
1409  * Side effects:
1410  *	If there are command requests in the property, they are executed. If
1411  *	there are responses in the property, their information is saved for
1412  *	the (ostensibly waiting) "send" commands. The property is deleted.
1413  *
1414  *--------------------------------------------------------------
1415  */
1416 
1417 static void
SendEventProc(ClientData clientData,XEvent * eventPtr)1418 SendEventProc(
1419     ClientData clientData,	/* Display information. */
1420     XEvent *eventPtr)		/* Information about event. */
1421 {
1422     TkDisplay *dispPtr = (TkDisplay *)clientData;
1423     char *propInfo, **propInfoPtr = &propInfo;
1424     const char *p;
1425     int result, actualFormat;
1426     unsigned long numItems, bytesAfter;
1427     Atom actualType;
1428     Tcl_Interp *remoteInterp;	/* Interp in which to execute the command. */
1429     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
1430 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
1431 
1432     if ((eventPtr->xproperty.atom != dispPtr->commProperty)
1433 	    || (eventPtr->xproperty.state != PropertyNewValue)) {
1434 	return;
1435     }
1436 
1437     /*
1438      * Read the comm property and delete it.
1439      */
1440 
1441     propInfo = NULL;
1442     result = XGetWindowProperty(dispPtr->display,
1443 	    Tk_WindowId(dispPtr->commTkwin), dispPtr->commProperty, 0,
1444 	    MAX_PROP_WORDS, True, XA_STRING, &actualType, &actualFormat,
1445 	    &numItems, &bytesAfter, (unsigned char **) propInfoPtr);
1446 
1447     /*
1448      * If the property doesn't exist or is improperly formed then ignore it.
1449      */
1450 
1451     if ((result != Success) || (actualType != XA_STRING)
1452 	    || (actualFormat != 8)) {
1453 	if (propInfo != NULL) {
1454 	    XFree(propInfo);
1455 	}
1456 	return;
1457     }
1458 
1459     /*
1460      * Several commands and results could arrive in the property at one time;
1461      * each iteration through the outer loop handles a single command or
1462      * result.
1463      */
1464 
1465     for (p = propInfo; (p-propInfo) < (int) numItems; ) {
1466 	/*
1467 	 * Ignore leading NULLs; each command or result starts with a NULL so
1468 	 * that no matter how badly formed a preceding command is, we'll be
1469 	 * able to tell that a new command/result is starting.
1470 	 */
1471 
1472 	if (*p == 0) {
1473 	    p++;
1474 	    continue;
1475 	}
1476 
1477 	if ((*p == 'c') && (p[1] == 0)) {
1478 	    Window commWindow;
1479 	    const char *interpName, *script, *serial;
1480 	    char *end;
1481 	    Tcl_DString reply;
1482 	    RegisteredInterp *riPtr;
1483 
1484 	    /*
1485 	     *----------------------------------------------------------
1486 	     * This is an incoming command from some other application.
1487 	     * Iterate over all of its options. Stop when we reach the end of
1488 	     * the property or something that doesn't look like an option.
1489 	     *----------------------------------------------------------
1490 	     */
1491 
1492 	    p += 2;
1493 	    interpName = NULL;
1494 	    commWindow = None;
1495 	    serial = "";
1496 	    script = NULL;
1497 	    while (((p-propInfo) < (int) numItems) && (*p == '-')) {
1498 		switch (p[1]) {
1499 		case 'r':
1500 		    commWindow = (Window) strtoul(p+2, &end, 16);
1501 		    if ((end == p+2) || (*end != ' ')) {
1502 			commWindow = None;
1503 		    } else {
1504 			p = serial = end+1;
1505 		    }
1506 		    break;
1507 		case 'n':
1508 		    if (p[2] == ' ') {
1509 			interpName = p+3;
1510 		    }
1511 		    break;
1512 		case 's':
1513 		    if (p[2] == ' ') {
1514 			script = p+3;
1515 		    }
1516 		    break;
1517 		}
1518 		while (*p != 0) {
1519 		    p++;
1520 		}
1521 		p++;
1522 	    }
1523 
1524 	    if ((script == NULL) || (interpName == NULL)) {
1525 		continue;
1526 	    }
1527 
1528 	    /*
1529 	     * Initialize the result property, so that we're ready at any time
1530 	     * if we need to return an error.
1531 	     */
1532 
1533 	    if (commWindow != None) {
1534 		Tcl_DStringInit(&reply);
1535 		Tcl_DStringAppend(&reply, "\0r\0-s ", 6);
1536 		Tcl_DStringAppend(&reply, serial, -1);
1537 		Tcl_DStringAppend(&reply, "\0-r ", 4);
1538 	    }
1539 
1540 	    if (!ServerSecure(dispPtr)) {
1541 		if (commWindow != None) {
1542 		    Tcl_DStringAppend(&reply,
1543 			    "X server insecure (must use xauth-style "
1544 			    "authorization); command ignored", -1);
1545 		}
1546 		result = TCL_ERROR;
1547 		goto returnResult;
1548 	    }
1549 
1550 	    /*
1551 	     * Locate the application, then execute the script.
1552 	     */
1553 
1554 	    for (riPtr = tsdPtr->interpListPtr; ; riPtr = riPtr->nextPtr) {
1555 		if (riPtr == NULL) {
1556 		    if (commWindow != None) {
1557 			Tcl_DStringAppend(&reply,
1558 				"receiver never heard of interpreter \"", -1);
1559 			Tcl_DStringAppend(&reply, interpName, -1);
1560 			Tcl_DStringAppend(&reply, "\"", 1);
1561 		    }
1562 		    result = TCL_ERROR;
1563 		    goto returnResult;
1564 		}
1565 		if (strcmp(riPtr->name, interpName) == 0) {
1566 		    break;
1567 		}
1568 	    }
1569 	    Tcl_Preserve(riPtr);
1570 
1571 	    /*
1572 	     * We must protect the interpreter because the script may enter
1573 	     * another event loop, which might call Tcl_DeleteInterp.
1574 	     */
1575 
1576 	    remoteInterp = riPtr->interp;
1577 	    Tcl_Preserve(remoteInterp);
1578 
1579 	    result = Tcl_EvalEx(remoteInterp, script, -1, TCL_EVAL_GLOBAL);
1580 
1581 	    /*
1582 	     * The call to Tcl_Release may have released the interpreter which
1583 	     * will cause the "send" command for that interpreter to be
1584 	     * deleted. The command deletion callback will set the
1585 	     * riPtr->interp field to NULL, hence the check below for NULL.
1586 	     */
1587 
1588 	    if (commWindow != None) {
1589 		Tcl_DStringAppend(&reply, Tcl_GetString(Tcl_GetObjResult(remoteInterp)),
1590 			-1);
1591 		if (result == TCL_ERROR) {
1592 		    const char *varValue;
1593 
1594 		    varValue = Tcl_GetVar2(remoteInterp, "errorInfo",
1595 			    NULL, TCL_GLOBAL_ONLY);
1596 		    if (varValue != NULL) {
1597 			Tcl_DStringAppend(&reply, "\0-i ", 4);
1598 			Tcl_DStringAppend(&reply, varValue, -1);
1599 		    }
1600 		    varValue = Tcl_GetVar2(remoteInterp, "errorCode",
1601 			    NULL, TCL_GLOBAL_ONLY);
1602 		    if (varValue != NULL) {
1603 			Tcl_DStringAppend(&reply, "\0-e ", 4);
1604 			Tcl_DStringAppend(&reply, varValue, -1);
1605 		    }
1606 		}
1607 	    }
1608 	    Tcl_Release(remoteInterp);
1609 	    Tcl_Release(riPtr);
1610 
1611 	    /*
1612 	     * Return the result to the sender if a commWindow was specified
1613 	     * (if none was specified then this is an asynchronous call).
1614 	     * Right now reply has everything but the completion code, but it
1615 	     * needs the NULL to terminate the current option.
1616 	     */
1617 
1618 	returnResult:
1619 	    if (commWindow != None) {
1620 		if (result != TCL_OK) {
1621 		    char buffer[TCL_INTEGER_SPACE];
1622 
1623 		    sprintf(buffer, "%d", result);
1624 		    Tcl_DStringAppend(&reply, "\0-c ", 4);
1625 		    Tcl_DStringAppend(&reply, buffer, -1);
1626 		}
1627 		(void) AppendPropCarefully(dispPtr->display, commWindow,
1628 			dispPtr->commProperty, Tcl_DStringValue(&reply),
1629 			Tcl_DStringLength(&reply) + 1, NULL);
1630 		XFlush(dispPtr->display);
1631 		Tcl_DStringFree(&reply);
1632 	    }
1633 	} else if ((*p == 'r') && (p[1] == 0)) {
1634 	    int serial, code, gotSerial;
1635 	    const char *errorInfo, *errorCode, *resultString;
1636 	    PendingCommand *pcPtr;
1637 
1638 	    /*
1639 	     *----------------------------------------------------------
1640 	     * This is a reply to some command that we sent out. Iterate over
1641 	     * all of its options. Stop when we reach the end of the property
1642 	     * or something that doesn't look like an option.
1643 	     *----------------------------------------------------------
1644 	     */
1645 
1646 	    p += 2;
1647 	    code = TCL_OK;
1648 	    gotSerial = 0;
1649 	    errorInfo = NULL;
1650 	    errorCode = NULL;
1651 	    resultString = "";
1652 	    while (((p-propInfo) < (int) numItems) && (*p == '-')) {
1653 		switch (p[1]) {
1654 		case 'c':
1655 		    if (sscanf(p+2, " %d", &code) != 1) {
1656 			code = TCL_OK;
1657 		    }
1658 		    break;
1659 		case 'e':
1660 		    if (p[2] == ' ') {
1661 			errorCode = p+3;
1662 		    }
1663 		    break;
1664 		case 'i':
1665 		    if (p[2] == ' ') {
1666 			errorInfo = p+3;
1667 		    }
1668 		    break;
1669 		case 'r':
1670 		    if (p[2] == ' ') {
1671 			resultString = p+3;
1672 		    }
1673 		    break;
1674 		case 's':
1675 		    if (sscanf(p+2, " %d", &serial) == 1) {
1676 			gotSerial = 1;
1677 		    }
1678 		    break;
1679 		}
1680 		while (*p != 0) {
1681 		    p++;
1682 		}
1683 		p++;
1684 	    }
1685 
1686 	    if (!gotSerial) {
1687 		continue;
1688 	    }
1689 
1690 	    /*
1691 	     * Give the result information to anyone who's waiting for it.
1692 	     */
1693 
1694 	    for (pcPtr = tsdPtr->pendingCommands; pcPtr != NULL;
1695 		    pcPtr = pcPtr->nextPtr) {
1696 		if ((serial != pcPtr->serial) || (pcPtr->result != NULL)) {
1697 		    continue;
1698 		}
1699 		pcPtr->code = code;
1700 		if (resultString != NULL) {
1701 		    pcPtr->result = (char *)ckalloc(strlen(resultString) + 1);
1702 		    strcpy(pcPtr->result, resultString);
1703 		}
1704 		if (code == TCL_ERROR) {
1705 		    if (errorInfo != NULL) {
1706 			pcPtr->errorInfo = (char *)ckalloc(strlen(errorInfo) + 1);
1707 			strcpy(pcPtr->errorInfo, errorInfo);
1708 		    }
1709 		    if (errorCode != NULL) {
1710 			pcPtr->errorCode = (char *)ckalloc(strlen(errorCode) + 1);
1711 			strcpy(pcPtr->errorCode, errorCode);
1712 		    }
1713 		}
1714 		pcPtr->gotResponse = 1;
1715 		break;
1716 	    }
1717 	} else {
1718 	    /*
1719 	     * Didn't recognize this thing. Just skip through the next null
1720 	     * character and try again.
1721 	     */
1722 
1723 	    while (*p != 0) {
1724 		p++;
1725 	    }
1726 	    p++;
1727 	}
1728     }
1729     XFree(propInfo);
1730 }
1731 
1732 /*
1733  *--------------------------------------------------------------
1734  *
1735  * AppendPropCarefully --
1736  *
1737  *	Append a given property to a given window, but set up an X error
1738  *	handler so that if the append fails this function can return an error
1739  *	code rather than having Xlib panic.
1740  *
1741  * Results:
1742  *	None.
1743  *
1744  * Side effects:
1745  *	The given property on the given window is appended to. If this
1746  *	operation fails and if pendingPtr is non-NULL, then the pending
1747  *	operation is marked as complete with an error.
1748  *
1749  *--------------------------------------------------------------
1750  */
1751 
1752 static void
AppendPropCarefully(Display * display,Window window,Atom property,char * value,int length,PendingCommand * pendingPtr)1753 AppendPropCarefully(
1754     Display *display,		/* Display on which to operate. */
1755     Window window,		/* Window whose property is to be modified. */
1756     Atom property,		/* Name of property. */
1757     char *value,		/* Characters to append to property. */
1758     int length,			/* Number of bytes to append. */
1759     PendingCommand *pendingPtr)	/* Pending command to mark complete if an
1760 				 * error occurs during the property op. NULL
1761 				 * means just ignore the error. */
1762 {
1763     Tk_ErrorHandler handler;
1764 
1765     handler = Tk_CreateErrorHandler(display, -1, -1, -1, AppendErrorProc,
1766 	    pendingPtr);
1767     XChangeProperty(display, window, property, XA_STRING, 8,
1768 	    PropModeAppend, (unsigned char *) value, length);
1769     Tk_DeleteErrorHandler(handler);
1770 }
1771 
1772 /*
1773  * The function below is invoked if an error occurs during the XChangeProperty
1774  * operation above.
1775  */
1776 
1777 static int
AppendErrorProc(ClientData clientData,TCL_UNUSED (XErrorEvent *))1778 AppendErrorProc(
1779     ClientData clientData,	/* Command to mark complete, or NULL. */
1780     TCL_UNUSED(XErrorEvent *))	/* Information about error. */
1781 {
1782     PendingCommand *pendingPtr = (PendingCommand *)clientData;
1783     PendingCommand *pcPtr;
1784     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
1785 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
1786 
1787     if (pendingPtr == NULL) {
1788 	return 0;
1789     }
1790 
1791     /*
1792      * Make sure this command is still pending.
1793      */
1794 
1795     for (pcPtr = tsdPtr->pendingCommands; pcPtr != NULL;
1796 	    pcPtr = pcPtr->nextPtr) {
1797 	if ((pcPtr == pendingPtr) && (pcPtr->result == NULL)) {
1798 	    pcPtr->result = (char *)ckalloc(strlen(pcPtr->target) + 50);
1799 	    sprintf(pcPtr->result, "no application named \"%s\"",
1800 		    pcPtr->target);
1801 	    pcPtr->code = TCL_ERROR;
1802 	    pcPtr->gotResponse = 1;
1803 	    break;
1804 	}
1805     }
1806     return 0;
1807 }
1808 
1809 /*
1810  *--------------------------------------------------------------
1811  *
1812  * DeleteProc --
1813  *
1814  *	This function is invoked by Tcl when the "send" command is deleted in
1815  *	an interpreter. It unregisters the interpreter.
1816  *
1817  * Results:
1818  *	None.
1819  *
1820  * Side effects:
1821  *	The interpreter given by riPtr is unregistered.
1822  *
1823  *--------------------------------------------------------------
1824  */
1825 
1826 static void
DeleteProc(ClientData clientData)1827 DeleteProc(
1828     ClientData clientData)	/* Info about registration, passed as
1829 				 * ClientData. */
1830 {
1831     RegisteredInterp *riPtr = (RegisteredInterp *)clientData;
1832     RegisteredInterp *riPtr2;
1833     NameRegistry *regPtr;
1834     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
1835 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
1836 
1837     regPtr = RegOpen(riPtr->interp, riPtr->dispPtr, 1);
1838     RegDeleteName(regPtr, riPtr->name);
1839     RegClose(regPtr);
1840 
1841     if (tsdPtr->interpListPtr == riPtr) {
1842 	tsdPtr->interpListPtr = riPtr->nextPtr;
1843     } else {
1844 	for (riPtr2 = tsdPtr->interpListPtr; riPtr2 != NULL;
1845 		riPtr2 = riPtr2->nextPtr) {
1846 	    if (riPtr2->nextPtr == riPtr) {
1847 		riPtr2->nextPtr = riPtr->nextPtr;
1848 		break;
1849 	    }
1850 	}
1851     }
1852     ckfree(riPtr->name);
1853     riPtr->interp = NULL;
1854     UpdateCommWindow(riPtr->dispPtr);
1855     Tcl_EventuallyFree(riPtr, TCL_DYNAMIC);
1856 }
1857 
1858 /*
1859  *----------------------------------------------------------------------
1860  *
1861  * SendRestrictProc --
1862  *
1863  *	This function filters incoming events when a "send" command is
1864  *	outstanding. It defers all events except those containing send
1865  *	commands and results.
1866  *
1867  * Results:
1868  *	False is returned except for property-change events on a commWindow.
1869  *
1870  * Side effects:
1871  *	None.
1872  *
1873  *----------------------------------------------------------------------
1874  */
1875 
1876 static Tk_RestrictAction
SendRestrictProc(TCL_UNUSED (void *),XEvent * eventPtr)1877 SendRestrictProc(
1878     TCL_UNUSED(void *),		/* Not used. */
1879     XEvent *eventPtr)		/* Event that just arrived. */
1880 {
1881     TkDisplay *dispPtr;
1882 
1883     if (eventPtr->type != PropertyNotify) {
1884 	return TK_DEFER_EVENT;
1885     }
1886     for (dispPtr = TkGetDisplayList(); dispPtr != NULL;
1887 	    dispPtr = dispPtr->nextPtr) {
1888 	if ((eventPtr->xany.display == dispPtr->display)
1889 		&& (eventPtr->xproperty.window
1890 		== Tk_WindowId(dispPtr->commTkwin))) {
1891 	    return TK_PROCESS_EVENT;
1892 	}
1893     }
1894     return TK_DEFER_EVENT;
1895 }
1896 
1897 /*
1898  *----------------------------------------------------------------------
1899  *
1900  * UpdateCommWindow --
1901  *
1902  *	This function updates the list of application names stored on our
1903  *	commWindow. It is typically called when interpreters are registered
1904  *	and unregistered.
1905  *
1906  * Results:
1907  *	None.
1908  *
1909  * Side effects:
1910  *	The TK_APPLICATION property on the comm window is updated.
1911  *
1912  *----------------------------------------------------------------------
1913  */
1914 
1915 static void
UpdateCommWindow(TkDisplay * dispPtr)1916 UpdateCommWindow(
1917     TkDisplay *dispPtr)		/* Display whose commWindow is to be
1918 				 * updated. */
1919 {
1920     Tcl_DString names;
1921     RegisteredInterp *riPtr;
1922     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
1923 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
1924 
1925     Tcl_DStringInit(&names);
1926     for (riPtr = tsdPtr->interpListPtr; riPtr != NULL;
1927 	    riPtr = riPtr->nextPtr) {
1928 	Tcl_DStringAppendElement(&names, riPtr->name);
1929     }
1930     XChangeProperty(dispPtr->display, Tk_WindowId(dispPtr->commTkwin),
1931 	    dispPtr->appNameProperty, XA_STRING, 8, PropModeReplace,
1932 	    (unsigned char *) Tcl_DStringValue(&names),
1933 	    Tcl_DStringLength(&names));
1934     Tcl_DStringFree(&names);
1935 }
1936 
1937 /*
1938  *----------------------------------------------------------------------
1939  *
1940  * TkpTestsendCmd --
1941  *
1942  *	This function implements the "testsend" command. It provides a set of
1943  *	functions for testing the "send" command and support function in
1944  *	tkSend.c.
1945  *
1946  * Results:
1947  *	A standard Tcl result.
1948  *
1949  * Side effects:
1950  *	Depends on option; see below.
1951  *
1952  *----------------------------------------------------------------------
1953  */
1954 
1955 int
TkpTestsendCmd(ClientData clientData,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])1956 TkpTestsendCmd(
1957     ClientData clientData,	/* Main window for application. */
1958     Tcl_Interp *interp,		/* Current interpreter. */
1959     int objc,			/* Number of arguments. */
1960     Tcl_Obj *const objv[])		/* Argument strings. */
1961 {
1962     enum {
1963 	TESTSEND_BOGUS, TESTSEND_PROP, TESTSEND_SERIAL
1964     };
1965     static const char *const testsendOptions[] = {
1966 	"bogus",   "prop",   "serial",  NULL
1967     };
1968     TkWindow *winPtr = (TkWindow *)clientData;
1969     Tk_ErrorHandler handler;
1970     int index;
1971 
1972     if (objc < 2) {
1973 	Tcl_WrongNumArgs(interp, 1, objv,
1974 		"option ?arg ...?");
1975 	return TCL_ERROR;
1976     }
1977 
1978     if (Tcl_GetIndexFromObjStruct(interp, objv[1], testsendOptions,
1979 		sizeof(char *), "option", 0, &index) != TCL_OK) {
1980 	return TCL_ERROR;
1981     }
1982     if (index == TESTSEND_BOGUS) {
1983         handler = Tk_CreateErrorHandler(winPtr->dispPtr->display, -1, -1, -1,
1984                 NULL, NULL);
1985 	XChangeProperty(winPtr->dispPtr->display,
1986 		RootWindow(winPtr->dispPtr->display, 0),
1987 		winPtr->dispPtr->registryProperty, XA_INTEGER, 32,
1988 		PropModeReplace,
1989 		(unsigned char *) "This is bogus information", 6);
1990         Tk_DeleteErrorHandler(handler);
1991     } else if (index == TESTSEND_PROP) {
1992 	int result, actualFormat;
1993 	unsigned long length, bytesAfter;
1994 	Atom actualType, propName;
1995 	char *property, **propertyPtr = &property, *p, *end;
1996 	Window w;
1997 
1998 	if ((objc != 4) && (objc != 5)) {
1999 		Tcl_WrongNumArgs(interp, 1, objv,
2000 			"prop window name ?value ?");
2001 	    return TCL_ERROR;
2002 	}
2003 	if (strcmp(Tcl_GetString(objv[2]), "root") == 0) {
2004 	    w = RootWindow(winPtr->dispPtr->display, 0);
2005 	} else if (strcmp(Tcl_GetString(objv[2]), "comm") == 0) {
2006 	    w = Tk_WindowId(winPtr->dispPtr->commTkwin);
2007 	} else {
2008 	    w = strtoul(Tcl_GetString(objv[2]), &end, 0);
2009 	}
2010 	propName = Tk_InternAtom((Tk_Window) winPtr, Tcl_GetString(objv[3]));
2011 	if (objc == 4) {
2012 	    property = NULL;
2013 	    result = XGetWindowProperty(winPtr->dispPtr->display, w, propName,
2014 		    0, 100000, False, XA_STRING, &actualType, &actualFormat,
2015 		    &length, &bytesAfter, (unsigned char **) propertyPtr);
2016 	    if ((result == Success) && (actualType != None)
2017 		    && (actualFormat == 8) && (actualType == XA_STRING)) {
2018 		for (p = property; (unsigned long)(p-property) < length; p++) {
2019 		    if (*p == 0) {
2020 			*p = '\n';
2021 		    }
2022 		}
2023 		Tcl_SetObjResult(interp, Tcl_NewStringObj(property, -1));
2024 	    }
2025 	    if (property != NULL) {
2026 		XFree(property);
2027 	    }
2028 	} else if (Tcl_GetString(objv[4])[0] == 0) {
2029             handler = Tk_CreateErrorHandler(winPtr->dispPtr->display,
2030                     -1, -1, -1, NULL, NULL);
2031 	    XDeleteProperty(winPtr->dispPtr->display, w, propName);
2032             Tk_DeleteErrorHandler(handler);
2033 	} else {
2034 	    Tcl_DString tmp;
2035 
2036 	    Tcl_DStringInit(&tmp);
2037 	    for (p = Tcl_DStringAppend(&tmp, Tcl_GetString(objv[4]),
2038 		    (int) strlen(Tcl_GetString(objv[4]))); *p != 0; p++) {
2039 		if (*p == '\n') {
2040 		    *p = 0;
2041 		}
2042 	    }
2043             handler = Tk_CreateErrorHandler(winPtr->dispPtr->display,
2044                     -1, -1, -1, NULL, NULL);
2045 	    XChangeProperty(winPtr->dispPtr->display, w, propName, XA_STRING,
2046 		    8, PropModeReplace, (unsigned char*)Tcl_DStringValue(&tmp),
2047 		    p-Tcl_DStringValue(&tmp));
2048             Tk_DeleteErrorHandler(handler);
2049 	    Tcl_DStringFree(&tmp);
2050 	}
2051     } else if (index == TESTSEND_SERIAL) {
2052 	Tcl_SetObjResult(interp, Tcl_NewIntObj(localData.sendSerial+1));
2053     }
2054     return TCL_OK;
2055 }
2056 
2057 /*
2058  * Local Variables:
2059  * mode: c
2060  * c-basic-offset: 4
2061  * fill-column: 78
2062  * End:
2063  */
2064