]>
Commit | Line | Data |
---|---|---|
1 | /* tkTCP.c -- | |
2 | * | |
3 | * This file provides basic capabilities to establish a server, | |
4 | * attached to a TCP/IP port, that attaches to a Tcl interpreter. | |
5 | * Such servers provide a remote-procedure-call mechanism for Tcl | |
6 | * without needing to resort to Tk's X-window-based complexities, and | |
7 | * also allow access to services that are not bound to any particular | |
8 | * display. | |
9 | */ | |
10 | ||
11 | static char RCSid [] = | |
12 | "$Header: /cluster21/kennykb/src/tclTCP.1.0beta/RCS/tclTCP.c,v 1.7 1992/05/05 18:31:13 kennykb Exp kennykb $"; | |
13 | /* $Source: /cluster21/kennykb/src/tclTCP.1.0beta/RCS/tclTCP.c,v $ | |
14 | * $Log: tclTCP.c,v $ | |
15 | * Revision 1.7 1992/05/05 18:31:13 kennykb | |
16 | * Changed the flow through the `delete server' code to make it work even | |
17 | * if a server is deleted while a client is active. | |
18 | * The change avoids aborts at termination time if the server delete code | |
19 | * is reached before the application exits. | |
20 | * | |
21 | * Revision 1.6 1992/03/04 20:04:00 kennykb | |
22 | * Modified source code to use the Tcl configurator and corresponding include | |
23 | * files. | |
24 | * | |
25 | * Revision 1.5 1992/02/25 15:21:30 kennykb | |
26 | * Modifications to quiet warnings from gcc | |
27 | * , | |
28 | * | |
29 | * Revision 1.4 1992/02/24 19:30:30 kennykb | |
30 | * Merged branches (a) updated tcpTrustedHost and (b) broken-out event mgr. | |
31 | * | |
32 | * Revision 1.3 1992/02/20 16:22:53 kennykb | |
33 | * Event management code removed and broken out into a separate file, | |
34 | * simpleEvent.c | |
35 | * | |
36 | * Revision 1.2.1.1 1992/02/24 19:12:30 kennykb | |
37 | * Rewrote tcpTrustedHost to be more forgiving of unusual configurations. | |
38 | * It now looks through all aliases for the local host and the loopback | |
39 | * pseudo-host. | |
40 | * | |
41 | * Revision 1.2 1992/02/18 14:43:21 kennykb | |
42 | * Fix for bug 920218.1 in `History' file. | |
43 | * | |
44 | * Revision 1.1 1992/02/14 19:57:51 kennykb | |
45 | * Initial revision | |
46 | * | |
47 | */ | |
48 | ||
49 | static char copyright [] = | |
50 | "Copyright (C) 1992 General Electric. All rights reserved." ; | |
51 | ||
52 | /* | |
53 | * Permission to use, copy, modify, and distribute this | |
54 | * software and its documentation for any purpose and without | |
55 | * fee is hereby granted, provided that the above copyright | |
56 | * notice appear in all copies and that both that copyright | |
57 | * notice and this permission notice appear in supporting | |
58 | * documentation, and that the name of General Electric not be used in | |
59 | * advertising or publicity pertaining to distribution of the | |
60 | * software without specific, written prior permission. | |
61 | * General Electric makes no representations about the suitability of | |
62 | * this software for any purpose. It is provided "as is" | |
63 | * without express or implied warranty. | |
64 | * | |
65 | * This work was supported by the DARPA Initiative in Concurrent | |
66 | * Engineering (DICE) through DARPA Contract MDA972-88-C-0047. | |
67 | */ | |
68 | ||
69 | #include <errno.h> | |
70 | #include <sys/types.h> | |
71 | #include <sys/ioctl.h> | |
72 | #include <sys/socket.h> | |
73 | #include <netinet/in.h> | |
74 | #include <netdb.h> | |
75 | #include <arpa/inet.h> | |
76 | ||
77 | /* Only some copies of netinet/in.h have the following defined. */ | |
78 | ||
79 | #ifndef INADDR_LOOPBACK | |
80 | #ifdef __STDC__ | |
81 | #define INADDR_LOOPBACK 0x7f000001UL | |
82 | #else | |
83 | #define INADDR_LOOPBACK (unsigned long) 0x7f000001L | |
84 | #endif /* __STDC__ */ | |
85 | #endif /* INADDR_LOOPBACK */ | |
86 | ||
87 | #include <signal.h> | |
88 | ||
89 | #include <tclInt.h> | |
90 | #include <tclUnix.h> | |
91 | #include <tk.h> | |
92 | #include "tkTCP.h" | |
93 | ||
94 | /* Kernel calls */ | |
95 | ||
96 | /* There doesn't seem to be any place to get these.... | |
97 | * certainly not a portable one. | |
98 | */ | |
99 | ||
100 | extern int accept _ANSI_ARGS_((int, struct sockaddr *, int *)); | |
101 | extern int bind _ANSI_ARGS_((int, const struct sockaddr *, int)); | |
102 | extern int close _ANSI_ARGS_((int)); | |
103 | extern int connect _ANSI_ARGS_((int, const struct sockaddr *, int)); | |
104 | extern int gethostname _ANSI_ARGS_((char *, int)); | |
105 | extern int getsockname _ANSI_ARGS_((int, struct sockaddr *, int *)); | |
106 | extern int ioctl _ANSI_ARGS_((int, int, char *)); | |
107 | extern int listen _ANSI_ARGS_((int, int)); | |
108 | extern int read _ANSI_ARGS_((int, char *, int)); | |
109 | extern int select _ANSI_ARGS_((int, fd_set *, fd_set *, fd_set *, | |
110 | struct timeval *)); | |
111 | extern int socket _ANSI_ARGS_((int, int, int)); | |
112 | extern int getdtablesize _ANSI_ARGS_((void)); | |
113 | ||
114 | \f | |
115 | /* Configuration parameters */ | |
116 | ||
117 | /* | |
118 | * TCP_LISTEN_BACKLOG gives the maximum backlog of connection requests | |
119 | * that may be queued for any server | |
120 | */ | |
121 | ||
122 | #define TCP_LISTEN_BACKLOG 3 | |
123 | ||
124 | /* Internal data structures */ | |
125 | ||
126 | /* | |
127 | * For each server that is established in any interpreter, there's a | |
128 | * record of the following type. Note that only one server may be | |
129 | * running at a time in any interpreter, unless the Tk services are | |
130 | * available for event management. | |
131 | */ | |
132 | ||
133 | typedef struct tcp_ServerData { | |
134 | Tcl_Interp * interp; /* Interpreter in which connections */ | |
135 | /* are processed. */ | |
136 | char name[ 16 ]; | |
137 | /* Name of the server object. */ | |
138 | int socketfd; | |
139 | /* Filedescriptor of the socket at */ | |
140 | /* which the server listens for connections */ | |
141 | char * command; | |
142 | /* Command to be executed (using */ | |
143 | /* Tcl_Eval) when a connection request */ | |
144 | /* arrives. */ | |
145 | Tcl_FreeProc * freeCommand; | |
146 | /* Procedure to free the command when */ | |
147 | /* it's no longer needed. */ | |
148 | int stopFlag; | |
149 | /* Flag == TRUE if the server is trying */ | |
150 | /* to shut down. */ | |
151 | int raw; /* Flag == TRUE if for raw socket mode. */ | |
152 | struct tcp_ClientData * firstClient; | |
153 | /* First in the list of clients at this */ | |
154 | /* server */ | |
155 | struct tcp_ServerData * next, * prev; | |
156 | /* Linkage in the list of all active servers */ | |
157 | } Tcp_ServerData; | |
158 | ||
159 | /* | |
160 | * Each client of a server will have a record of the following type. | |
161 | */ | |
162 | ||
163 | typedef struct tcp_ClientData { | |
164 | struct tcp_ServerData * server; | |
165 | /* Server to which the client belongs */ | |
166 | char name [16]; | |
167 | /* Name of the client */ | |
168 | int socketfd; | |
169 | /* Filedescriptor of the socket of the */ | |
170 | /* the client's connection. */ | |
171 | struct sockaddr_in peeraddr; | |
172 | /* IP address from which the client */ | |
173 | /* established the connection. */ | |
174 | char * command; | |
175 | /* Command to execute when the client */ | |
176 | /* sends a message */ | |
177 | Tcl_FreeProc * freeCommand; | |
178 | /* Procedure to free the command when it's */ | |
179 | /* no longer needed */ | |
180 | Tcl_CmdBuf inputBuffer; | |
181 | /* Buffer where client commands are stored */ | |
182 | char * resultString; | |
183 | /* Result of executing a command on the */ | |
184 | /* client */ | |
185 | char * resultPointer; | |
186 | /* Pointer to the portion of resultString */ | |
187 | /* that remains to be transmitted back */ | |
188 | /* to the client */ | |
189 | Tcl_FreeProc * freeResultString; | |
190 | /* Procedure to free the result string when */ | |
191 | /* it's no longer needed. */ | |
192 | int activeFlag; | |
193 | /* Flag == 1 iff a command is pending on */ | |
194 | /* this client. */ | |
195 | int closeFlag; | |
196 | /* Flag == 1 if the client should be closed */ | |
197 | /* once its result has been returned. */ | |
198 | struct tcp_ClientData *next, *prev; | |
199 | /* Next and previous entries in the list of */ | |
200 | /* clients at this server */ | |
201 | } Tcp_ClientData; | |
202 | ||
203 | /* Static variables in this file */ | |
204 | ||
205 | static char * tcpCurrentClient = NULL; | |
206 | /* The name of the client for which a */ | |
207 | /* command is being processed. */ | |
208 | static Tcp_ServerData * tcpFirstServer = NULL; | |
209 | /* Pointer to the first in a list of */ | |
210 | /* servers active in the current process. */ | |
211 | \f | |
212 | /* Declarations for static functions within this file. */ | |
213 | ||
214 | /* Static procedures in this file */ | |
215 | ||
216 | static void simpleDeleteFileHandler1 _ANSI_ARGS_((ClientData, int)); | |
217 | ||
218 | static void simpleDeleteFileHandler2 _ANSI_ARGS_((ClientData)); | |
219 | ||
220 | static int | |
221 | tcpClientCommand _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
222 | int argc, char * * argv)); | |
223 | ||
224 | static int | |
225 | tcpConnectCommand _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
226 | int argc, char * * argv)); | |
227 | ||
228 | static int | |
229 | tcpEvalCommand _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
230 | int argc, char * * argv)); | |
231 | ||
232 | static int | |
233 | tcpLoginCommand _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
234 | int argc, char * * argv)); | |
235 | ||
236 | static int | |
237 | tcpMainLoopCommand _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
238 | int argc, char * * argv)); | |
239 | ||
240 | static int | |
241 | tcpPollCommand _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
242 | int argc, char * * argv)); | |
243 | ||
244 | static int | |
245 | tcpServerCommand _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
246 | int argc, char * * argv)); | |
247 | ||
248 | static int | |
249 | tcpServersCommand _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
250 | int argc, char * * argv)); | |
251 | static int | |
252 | tcpWaitCommand _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
253 | int argc, char * * argv)); | |
254 | ||
255 | static int | |
256 | tcpServerObjectCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
257 | int argc, char * * argv)); | |
258 | static void | |
259 | deleteTcpServerObjectCmd _ANSI_ARGS_((ClientData clientData)); | |
260 | ||
261 | static int | |
262 | tcpServerObjectAcceptCmd _ANSI_ARGS_((ClientData clientData, | |
263 | Tcl_Interp * interp, int argc, | |
264 | char * * argv)); | |
265 | ||
266 | static int | |
267 | tcpServerObjectClientsCmd _ANSI_ARGS_((ClientData clientData, | |
268 | Tcl_Interp * interp, int argc, | |
269 | char * * argv)); | |
270 | ||
271 | static int | |
272 | tcpServerObjectConfigCmd _ANSI_ARGS_((ClientData clientData, | |
273 | Tcl_Interp * interp, int argc, | |
274 | char * * argv)); | |
275 | ||
276 | static int | |
277 | tcpServerObjectStartCmd _ANSI_ARGS_((ClientData clientData, | |
278 | Tcl_Interp * interp, int argc, | |
279 | char * * argv)); | |
280 | ||
281 | static int | |
282 | tcpServerObjectStopCmd _ANSI_ARGS_((ClientData clientData, | |
283 | Tcl_Interp * interp, int argc, | |
284 | char * * argv)); | |
285 | ||
286 | static void | |
287 | tcpDeleteServer _ANSI_ARGS_((Tcp_ServerData * server)); | |
288 | ||
289 | static int | |
290 | tcpServerObjectConfig _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
291 | int argc, char * * argv)); | |
292 | ||
293 | static int | |
294 | tcpClientObjectCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
295 | int argc, char * * argv)); | |
296 | ||
297 | static int | |
298 | tcpClientObjectCloseCmd _ANSI_ARGS_((ClientData clientData, | |
299 | Tcl_Interp * interp, | |
300 | int argc, char * * argv)); | |
301 | ||
302 | static int | |
303 | tcpClientObjectCommandCmd _ANSI_ARGS_((ClientData clientData, | |
304 | Tcl_Interp * interp, | |
305 | int argc, char * * argv)); | |
306 | ||
307 | static int | |
308 | tcpClientObjectDoCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp * interp, | |
309 | int argc, char * * argv)); | |
310 | ||
311 | static int | |
312 | tcpClientObjectHostnameCmd _ANSI_ARGS_((ClientData clientData, | |
313 | Tcl_Interp * interp, | |
314 | int argc, char * * argv)); | |
315 | ||
316 | static int | |
317 | tcpClientObjectServerCmd _ANSI_ARGS_((ClientData clientData, | |
318 | Tcl_Interp * interp, | |
319 | int argc, char * * argv)); | |
320 | ||
321 | static void | |
322 | deleteTcpClientObjectCmd _ANSI_ARGS_((ClientData clientData)); | |
323 | ||
324 | static int | |
325 | tcpConnectionObjectCmd _ANSI_ARGS_((ClientData clientData, | |
326 | Tcl_Interp * interp, | |
327 | int argc, char * * argv)); | |
328 | ||
329 | static int | |
330 | tcpConnectionObjectCloseCmd _ANSI_ARGS_((ClientData clientData, | |
331 | Tcl_Interp * interp, | |
332 | int argc, char * * argv)); | |
333 | ||
334 | static int | |
335 | tcpConnectionObjectSendCmd _ANSI_ARGS_((ClientData clientData, | |
336 | Tcl_Interp * interp, | |
337 | int argc, char * * argv)); | |
338 | ||
339 | static void | |
340 | deleteTcpConnectionObjectCmd _ANSI_ARGS_((ClientData clientData)); | |
341 | ||
342 | static void | |
343 | tcpServerAcceptConnection _ANSI_ARGS_((ClientData clientData, int mask)); | |
344 | ||
345 | static void | |
346 | tcpReturnResultToClient _ANSI_ARGS_((Tcp_ClientData * client, | |
347 | Tcl_Interp * interp, | |
348 | int status, int closeflag)); | |
349 | ||
350 | static void | |
351 | tcpWriteResultToClient _ANSI_ARGS_((ClientData clientData, int mask)); | |
352 | ||
353 | static void | |
354 | tcpClientReadError _ANSI_ARGS_((Tcp_ClientData * client)); | |
355 | ||
356 | static void | |
357 | tcpClientWriteError _ANSI_ARGS_((Tcp_ClientData * client)); | |
358 | ||
359 | static void | |
360 | tcpPrepareClientForInput _ANSI_ARGS_((Tcp_ClientData * client)); | |
361 | ||
362 | static void | |
363 | tcpReceiveClientInput _ANSI_ARGS_((ClientData clientData, int mask)); | |
364 | ||
365 | static void | |
366 | tcpCloseClient _ANSI_ARGS_((Tcp_ClientData * client)); | |
367 | ||
368 | static int | |
369 | tcpTrustedHost _ANSI_ARGS_((char * hostname)); | |
370 | ||
371 | static int | |
372 | tcpSendCmdToServer _ANSI_ARGS_((Tcl_Interp * interp, int s, char * message)); | |
373 | ||
374 | static int | |
375 | tcpReceiveResultFromServer _ANSI_ARGS_((Tcl_Interp * interp, int s)); | |
376 | \f | |
377 | /* | |
378 | * simpleReportBackgroundError -- | |
379 | * | |
380 | * This procedure is invoked to report a Tcl error in the background, | |
381 | * when TCL_ERROR has been passed out to the outermost level. | |
382 | * | |
383 | * It tries to run `bgerror' giving it the error message. If this | |
384 | * fails, it reports the problem on stderr. | |
385 | */ | |
386 | ||
387 | void | |
388 | simpleReportBackgroundError (interp) | |
389 | Tcl_Interp * interp; | |
390 | { | |
391 | ||
392 | char *argv[2]; | |
393 | char *command; | |
394 | char *error; | |
395 | char *errorInfo, *tmp; | |
396 | int status; | |
397 | int unixStatus; | |
398 | ||
399 | /* Get the error message out of the interpreter. */ | |
400 | ||
401 | error = (char *) ckalloc (strlen (interp -> result) + 1); | |
402 | strcpy (error, interp -> result); | |
403 | ||
404 | /* Get errorInfo, too */ | |
405 | ||
406 | tmp = Tcl_GetVar (interp, "errorInfo", TCL_GLOBAL_ONLY); | |
407 | if (tmp == NULL) { | |
408 | errorInfo = error; | |
409 | } else { | |
410 | errorInfo = (char *) ckalloc (strlen (tmp) + 1); | |
411 | strcpy (errorInfo, tmp); | |
412 | } | |
413 | ||
414 | /* Build a `bgerror' command to report the error */ | |
415 | ||
416 | argv[0] = "bgerror"; | |
417 | argv[1] = error; | |
418 | command = Tcl_Merge (2, argv); | |
419 | ||
420 | /* Try to run the command */ | |
421 | ||
422 | status = Tcl_Eval (interp, command, 0, (char **) NULL); | |
423 | ||
424 | if (status != TCL_OK) { | |
425 | ||
426 | /* Command failed. Report the problem to stderr. */ | |
427 | ||
428 | tmp = Tcl_GetVar (interp, "errorInfo", TCL_GLOBAL_ONLY); | |
429 | if (tmp == NULL) { | |
430 | tmp = interp -> result; | |
431 | } | |
432 | unixStatus = fprintf (stderr, "\n\ | |
433 | ------------------------------------------------------------------------\n\ | |
434 | Tcl interpreter detected a background error.\n\ | |
435 | Original error:\n\ | |
436 | %s\n\ | |
437 | \n\ | |
438 | User \"bgerror\" procedure failed to handle the background error.\n\ | |
439 | Error in bgerror:\n\ | |
440 | %s\n", | |
441 | errorInfo, tmp); | |
442 | if (unixStatus < 0) { | |
443 | abort (); | |
444 | } | |
445 | } | |
446 | ||
447 | Tcl_ResetResult (interp); | |
448 | ||
449 | free (command); | |
450 | ||
451 | ckfree (error); | |
452 | ||
453 | if (errorInfo != error) { | |
454 | ckfree (errorInfo); | |
455 | } | |
456 | } | |
457 | \f | |
458 | /* | |
459 | * simpleCreateFileHandler -- | |
460 | * | |
461 | * This procedure is invoked to create a handle to cause a callback | |
462 | * whenever a condition (readable, writable, exception) is | |
463 | * present on a given file. | |
464 | * | |
465 | * In the Tk environment, the file handler is created using Tk's | |
466 | * Tk_CreateFileHandler procedure, and the callback takes place | |
467 | * from the Tk main loop. In a non-Tk environment, a | |
468 | * Tcp_FileHandler structure is created to describe the file, and | |
469 | * this structure is linked to a chain of such structures | |
470 | * processed by the server main loop. | |
471 | */ | |
472 | ||
473 | void | |
474 | simpleCreateFileHandler (fd, mask, proc, clientData) | |
475 | int fd; | |
476 | int mask; | |
477 | Tk_FileProc * proc; | |
478 | ClientData clientData; | |
479 | { | |
480 | Tk_CreateFileHandler (fd, mask, (Tk_FileProc *) proc, clientData); | |
481 | ||
482 | /* It is possible that we have a file handler scheduled for deletion. | |
483 | * This deletion has to be cancelled if we've requested creation of | |
484 | * another one. | |
485 | */ | |
486 | ||
487 | Tk_CancelIdleCall ((Tk_IdleProc *) simpleDeleteFileHandler2, | |
488 | (ClientData) fd); | |
489 | } | |
490 | \f | |
491 | /* | |
492 | * simpleDeleteFileHandler -- | |
493 | * | |
494 | * This function is invoked when the program is no longer interested in | |
495 | * handling events on a file. It removes any outstanding handler on the file. | |
496 | * | |
497 | * The function is a little tricky because a file handler on the file may | |
498 | * be active. In a non-Tk environment, this is simple; the SIMPLE_DELETE flag | |
499 | * is set in the handler's mask, and the main loop deletes the handler once | |
500 | * it is quiescent. In Tk, the event loop won't do that, so what we do | |
501 | * is set a DoWhenIdle to delete the handler and return. The DoWhenIdle | |
502 | * gets called back from the Tk event loop at a time that the handler is | |
503 | * quiescent, and deletes the handler. | |
504 | */ | |
505 | ||
506 | void | |
507 | simpleDeleteFileHandler (fd) | |
508 | int fd; | |
509 | { | |
510 | /* First of all, we have to zero the file's mask to avoid calling the same | |
511 | handler over again if the file is still ready. */ | |
512 | Tk_CreateFileHandler (fd, 0, (Tk_FileProc *) simpleDeleteFileHandler1, | |
513 | (ClientData) NULL); | |
514 | Tk_DoWhenIdle ((Tk_IdleProc *) simpleDeleteFileHandler2, | |
515 | (ClientData) fd); | |
516 | } | |
517 | ||
518 | ||
519 | /* ARGSUSED */ | |
520 | static void | |
521 | simpleDeleteFileHandler1 (clientData, mask) | |
522 | ClientData clientData; | |
523 | int mask; | |
524 | { | |
525 | (void) fprintf (stderr, "in simpleDeleteFileHandler1: bug in tkEvent.c"); | |
526 | abort (); | |
527 | } | |
528 | ||
529 | static void | |
530 | simpleDeleteFileHandler2 (clientData) | |
531 | ClientData clientData; | |
532 | { | |
533 | int fd = (int) clientData; | |
534 | ||
535 | Tk_DeleteFileHandler (fd); | |
536 | } | |
537 | \f | |
538 | /* | |
539 | *---------------------------------------------------------------------- | |
540 | * Tk_TcpCmd: | |
541 | * | |
542 | * This procedure implements a `tcp' command for Tcl. It provides the | |
543 | * top-level actions for TCP/IP connections. | |
544 | * | |
545 | * This command is divided into variants, each with its own procedure: | |
546 | * | |
547 | * tcp client | |
548 | * Returns the current active client, or an error if there is | |
549 | * none. | |
550 | * tcp connect host port | |
551 | * Establish a connection to a server running at `port' on | |
552 | * `host.' | |
553 | * tcp eval client command | |
554 | * Do default command processing for command "$command", | |
555 | * originating at client "$client". | |
556 | * tcp login client | |
557 | * Do default login processing for $client. | |
558 | * tcp mainloop | |
559 | * Start the main loop for a server or group of servers. | |
560 | * tcp poll | |
561 | * Poll for whether servers have work to do. | |
562 | * tcp servers | |
563 | * Returns a list of the currently active servers. | |
564 | * tcp server ?args? | |
565 | * Set up a server to run in the current interpreter. | |
566 | * tcp wait | |
567 | * Wait for a server to have work to do. | |
568 | *---------------------------------------------------------------------- | |
569 | */ | |
570 | ||
571 | int | |
572 | Tk_TcpCmd (clientData, interp, argc, argv) | |
573 | ClientData clientData; | |
574 | Tcl_Interp * interp; | |
575 | int argc; | |
576 | char * * argv; | |
577 | { | |
578 | char c; | |
579 | unsigned length; | |
580 | ||
581 | if (argc < 2) { | |
582 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [0], | |
583 | " command ?args?\"", (char *) NULL); | |
584 | return TCL_ERROR; | |
585 | } | |
586 | ||
587 | c = argv [1] [0]; | |
588 | length = strlen (argv [1]); | |
589 | ||
590 | if ((c == 'c') && (length >= 2) && | |
591 | (strncmp (argv [1], "client", length) == 0)) { | |
592 | return tcpClientCommand (clientData, interp, argc-1, argv+1); | |
593 | } | |
594 | if ((c == 'c') && (length >= 2) && | |
595 | (strncmp (argv [1], "connect", length) == 0)) { | |
596 | return tcpConnectCommand (clientData, interp, argc-1, argv+1); | |
597 | } | |
598 | if ((c == 'e') && (strncmp (argv [1], "eval", length) == 0)) { | |
599 | return tcpEvalCommand (clientData, interp, argc-1, argv+1); | |
600 | } | |
601 | if ((c == 'l') && (strncmp (argv [1], "login", length) == 0)) { | |
602 | return tcpLoginCommand (clientData, interp, argc-1, argv+1); | |
603 | } | |
604 | if ((c == 'm') && (strncmp (argv [1], "mainloop", length) == 0)) { | |
605 | return tcpMainLoopCommand (clientData, interp, argc-1, argv+1); | |
606 | } | |
607 | if ((c == 'p') && (strncmp (argv [1], "poll", length) == 0)) { | |
608 | return tcpPollCommand (clientData, interp, argc-1, argv+1); | |
609 | } | |
610 | if ((c == 's') && (length >= 7) | |
611 | && (strncmp (argv [1], "servers", length) == 0)) { | |
612 | return tcpServersCommand (clientData, interp, argc-1, argv+1); | |
613 | } | |
614 | if ((c == 's') && (strncmp (argv [1], "server", length) == 0)) { | |
615 | return tcpServerCommand (clientData, interp, argc-1, argv+1); | |
616 | } | |
617 | if ((c == 'w') && (strncmp (argv [1], "wait", length) == 0)) { | |
618 | return tcpWaitCommand (clientData, interp, argc-1, argv+1); | |
619 | } | |
620 | Tcl_AppendResult (interp, "bad option \"", argv [1], | |
621 | "\": should be client, eval, login,", | |
622 | " mainloop, poll, servers, server or wait", | |
623 | (char *) NULL); | |
624 | return TCL_ERROR; | |
625 | ||
626 | } | |
627 | \f | |
628 | /* | |
629 | * tcpClientCommand -- | |
630 | * | |
631 | * This procedure is invoked to process the "tcp client" Tcl command. | |
632 | * It returns the name of the currently-active client, or an error if there | |
633 | * is none. | |
634 | */ | |
635 | ||
636 | /* ARGSUSED */ | |
637 | static int | |
638 | tcpClientCommand (clientData, interp, argc, argv) | |
639 | ClientData clientData; | |
640 | Tcl_Interp * interp; | |
641 | int argc; | |
642 | char * * argv; | |
643 | { | |
644 | /* Check syntax */ | |
645 | ||
646 | if (argc != 1) { | |
647 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], " ", | |
648 | argv [0], "\"", (char *) NULL); | |
649 | return TCL_ERROR; | |
650 | } | |
651 | ||
652 | /* Make sure there is a current client */ | |
653 | ||
654 | if (tcpCurrentClient == NULL) { | |
655 | Tcl_SetResult (interp, "no current client", TCL_STATIC); | |
656 | return TCL_ERROR; | |
657 | } | |
658 | ||
659 | Tcl_SetResult (interp, tcpCurrentClient, TCL_VOLATILE); | |
660 | return TCL_OK; | |
661 | } | |
662 | \f | |
663 | /* tcpConnectCommand -- | |
664 | * | |
665 | * This procedure is invoked to process the "tcp connect" Tcl command. | |
666 | * It takes two arguments: a host name and a port. It tries to establish a | |
667 | * connection to the specified port and host. | |
668 | */ | |
669 | ||
670 | /* ARGSUSED */ | |
671 | static int | |
672 | tcpConnectCommand (clientData, interp, argc, argv) | |
673 | ClientData clientData; | |
674 | Tcl_Interp * interp; | |
675 | int argc; | |
676 | char * * argv; | |
677 | { | |
678 | struct hostent * host; | |
679 | struct sockaddr_in sockaddr; | |
680 | int haddr; | |
681 | int port; | |
682 | int status; | |
683 | int f; | |
684 | char name [20]; | |
685 | ||
686 | /* Check syntax */ | |
687 | ||
688 | if (argc != 3) { | |
689 | Tcl_AppendResult (interp, "wrong # args, should be \"", argv [-1], " ", | |
690 | argv [0], " hostname port#\"", (char *) NULL); | |
691 | return TCL_ERROR; | |
692 | } | |
693 | ||
694 | /* Decode the host name */ | |
695 | ||
696 | sockaddr.sin_family = AF_INET; | |
697 | host = gethostbyname (argv [1]); | |
698 | if (host != NULL) { | |
699 | memcpy ((char *) &(sockaddr.sin_addr.s_addr), | |
700 | (char *) (host -> h_addr_list [0]), | |
701 | (size_t) (host -> h_length)); | |
702 | } else { | |
703 | haddr = inet_addr (argv [1]); | |
704 | if (haddr == -1) { | |
705 | Tcl_AppendResult (interp, argv[1], ": host unknown", (char *) NULL); | |
706 | return TCL_ERROR; | |
707 | } | |
708 | sockaddr.sin_addr.s_addr = haddr; | |
709 | } | |
710 | ||
711 | /* Decode the port number */ | |
712 | ||
713 | status = Tcl_GetInt (interp, argv [2], &port); | |
714 | if (status) return status; | |
715 | sockaddr.sin_port = htons (port); | |
716 | ||
717 | /* Make a socket to talk to the server */ | |
718 | ||
719 | f = socket (AF_INET, SOCK_STREAM, 0); | |
720 | if (f < 0) { | |
721 | Tcl_AppendResult (interp, "can't create socket: ", | |
722 | Tcl_UnixError (interp), (char *) NULL); | |
723 | return TCL_ERROR; | |
724 | } | |
725 | ||
726 | /* Connect to the server */ | |
727 | ||
728 | status = connect (f, (struct sockaddr *) &sockaddr, sizeof sockaddr); | |
729 | if (status < 0) { | |
730 | Tcl_AppendResult (interp, "can't connect to server: ", | |
731 | Tcl_UnixError (interp), (char *) NULL); | |
732 | (void) close (f); | |
733 | return TCL_ERROR; | |
734 | } | |
735 | ||
736 | /* Get the server's greeting message */ | |
737 | ||
738 | status = tcpReceiveResultFromServer (interp, f); | |
739 | ||
740 | if (status == TCL_OK) { | |
741 | ||
742 | /* Stash the greeting, make the connection object and return it. */ | |
743 | ||
744 | sprintf (name, "tcp_connection_%d", f); | |
745 | (void) Tcl_SetVar2 (interp, "tcp_greeting", name, interp -> result, | |
746 | TCL_GLOBAL_ONLY); | |
747 | Tcl_CreateCommand (interp, name, (Tcl_CmdProc *) tcpConnectionObjectCmd, | |
748 | (ClientData) f, | |
749 | (Tcl_CmdDeleteProc *) deleteTcpConnectionObjectCmd); | |
750 | Tcl_SetResult (interp, name, TCL_VOLATILE); | |
751 | return TCL_OK; | |
752 | } else { | |
753 | ||
754 | /* Error reading greeting, quit */ | |
755 | ||
756 | (void) close (f); | |
757 | return TCL_ERROR; | |
758 | } | |
759 | } | |
760 | \f | |
761 | /* | |
762 | * tcpEvalCommand -- | |
763 | * | |
764 | * This procedure is invoked to process the "tcp eval" Tcl command. | |
765 | * "tcp eval" is the default command invoked to process connections once | |
766 | * a connection has been accepted by "tcp login". | |
767 | */ | |
768 | ||
769 | /* ARGSUSED */ | |
770 | static int | |
771 | tcpEvalCommand (clientData, interp, argc, argv) | |
772 | ClientData clientData; | |
773 | Tcl_Interp * interp; | |
774 | int argc; | |
775 | char * * argv; | |
776 | { | |
777 | int status; | |
778 | ||
779 | /* Argc == 2 means that we're logging out a client. Default is to ignore | |
780 | * the logout. | |
781 | */ | |
782 | ||
783 | if (argc == 2) { | |
784 | return TCL_OK; | |
785 | } | |
786 | ||
787 | /* Three-argument form is a command from a client. Default is to eval | |
788 | * the command */ | |
789 | ||
790 | if (argc != 3) { | |
791 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], | |
792 | " ", argv [0], " client command\"", (char *) NULL); | |
793 | return TCL_ERROR; | |
794 | } | |
795 | ||
796 | status = Tcl_Eval (interp, argv [2], 0, (char * *) NULL); | |
797 | return status; | |
798 | } | |
799 | \f | |
800 | /* | |
801 | * tcpLoginCommand -- | |
802 | * | |
803 | * This procedure is invoked to process the "tcp login" Tcl command. | |
804 | * It is the default command procedure at initial connection to a server. | |
805 | * It is invoked with the name of a client. It returns TCL_OK, together | |
806 | * with a greeting message, if the login succeeds, and TCL_ERROR, together | |
807 | * with a denial message, if it fails. | |
808 | * | |
809 | * The authentication procedure is as follows: | |
810 | * | |
811 | * - If the client is on the local host, the connection is accepted. | |
812 | * - If the client's IP address is the same as the local host's IP address, | |
813 | * the connection is accepted. | |
814 | * - Otherwise, the connection is refused. | |
815 | * | |
816 | * Obviously, there are other authentication techniques. The use can | |
817 | * replace this command with an arbitrary Tcl script. | |
818 | */ | |
819 | ||
820 | /*ARGSUSED*/ | |
821 | static int | |
822 | tcpLoginCommand (clientData, interp, argc, argv) | |
823 | ClientData clientData; | |
824 | Tcl_Interp * interp; | |
825 | int argc; | |
826 | char * * argv; | |
827 | { | |
828 | char * hostName; /* Name of the client's host */ | |
829 | int status; | |
830 | ||
831 | /* Check command syntax */ | |
832 | ||
833 | if (argc != 2) { | |
834 | Tcl_AppendResult (interp, "wrong # args; should be \"", argv [-1], " ", | |
835 | argv [0], " clientName\"", (char *) NULL); | |
836 | return TCL_ERROR; | |
837 | } | |
838 | ||
839 | /* Get the hostname by doing $client hostname */ | |
840 | ||
841 | status = Tcl_VarEval (interp, argv [1], " hostname", (char *) NULL); | |
842 | if (status == TCL_OK) { | |
843 | hostName = (char *) ckalloc (strlen (interp -> result) + 1); | |
844 | strcpy (hostName, interp -> result); | |
845 | ||
846 | /* Check that the host is trusted */ | |
847 | ||
848 | if (tcpTrustedHost (hostName)) { | |
849 | ||
850 | /* Change the command to `tcp eval' for next time */ | |
851 | ||
852 | status = Tcl_VarEval (interp, argv [1], " command {tcp eval}", | |
853 | (char *) NULL); | |
854 | ||
855 | ||
856 | if (status == TCL_OK) { | |
857 | ||
858 | /* Return a greeting message */ | |
859 | ||
860 | Tcl_ResetResult (interp); | |
861 | Tcl_AppendResult (interp, "GE DICE TCP-based Tcl server\n", RCSid, | |
862 | "\n", copyright, (char *) NULL); | |
863 | ||
864 | return TCL_OK; | |
865 | ||
866 | } | |
867 | ||
868 | } | |
869 | ||
870 | ckfree ((char *) hostName); | |
871 | } | |
872 | ||
873 | /* Host isn't trusted or one of the commands failed. */ | |
874 | ||
875 | Tcl_SetResult (interp, "Permission denied", TCL_STATIC); | |
876 | return TCL_ERROR; | |
877 | } | |
878 | \f | |
879 | /* | |
880 | * tcpMainLoopCommand: | |
881 | * | |
882 | * This procedure is invoked in a non-Tk environment when the server | |
883 | * implementor wishes to use a main loop built into the library. It | |
884 | * repeatedly polls ofr work to be done, returning only when the last server | |
885 | * is closed. | |
886 | * | |
887 | * In a Tk environment, the procedure returns immediately. | |
888 | */ | |
889 | ||
890 | /*ARGSUSED*/ | |
891 | static int | |
892 | tcpMainLoopCommand (clientData, interp, argc, argv) | |
893 | ClientData clientData; | |
894 | Tcl_Interp * interp; | |
895 | int argc; | |
896 | char * * argv; | |
897 | { | |
898 | ||
899 | int status; | |
900 | ||
901 | if (argc != 1) { | |
902 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], | |
903 | " ", argv [0], "\"", (char *) NULL); | |
904 | return TCL_ERROR; | |
905 | } | |
906 | ||
907 | errno = 0; status = -1; | |
908 | ||
909 | /* In a non-Tk environment, errno has a Unix error or 0 for no clients | |
910 | * or servers. In a Tk environment, errno is zero at this point. | |
911 | */ | |
912 | ||
913 | if (errno != 0) { | |
914 | Tcl_AppendResult (interp, "select: ", Tcl_UnixError (interp), | |
915 | (char *) NULL); | |
916 | return TCL_ERROR; | |
917 | } | |
918 | ||
919 | return TCL_OK; | |
920 | } | |
921 | ||
922 | \f | |
923 | /* | |
924 | * tcpPollCommand: | |
925 | * | |
926 | * This procedure is invoked to process the "tcp poll" Tcl | |
927 | * command. It requests that pending events for the servers be processed. | |
928 | * It returns a count of events that were processed successfully. | |
929 | * | |
930 | * In a Tk environment, the procedure reports that no servers are known | |
931 | * to the event handler. This is correct -- servers register with Tk, not | |
932 | * with the simple event handler. | |
933 | */ | |
934 | ||
935 | /*ARGSUSED*/ | |
936 | static int | |
937 | tcpPollCommand (clientData, interp, argc, argv) | |
938 | ClientData clientData; | |
939 | Tcl_Interp * interp; | |
940 | int argc; | |
941 | char * * argv; | |
942 | { | |
943 | int status; | |
944 | ||
945 | if (argc != 1) { | |
946 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], | |
947 | " ", argv [0], "\"", (char *) NULL); | |
948 | return TCL_ERROR; | |
949 | } | |
950 | ||
951 | /* Do the poll */ | |
952 | ||
953 | errno = 0; status = -1; | |
954 | ||
955 | /* Check for trouble */ | |
956 | ||
957 | if (status < 0) { | |
958 | if (errno == 0) { | |
959 | Tcl_SetResult (interp, "no servers known to event handler", TCL_STATIC); | |
960 | } else { | |
961 | Tcl_AppendResult (interp, "select: ", Tcl_UnixError (interp), | |
962 | (char *) NULL); | |
963 | } | |
964 | return TCL_ERROR; | |
965 | } | |
966 | ||
967 | /* Return the number of events processed. */ | |
968 | ||
969 | sprintf (interp -> result, "%d", status); | |
970 | return TCL_OK; | |
971 | } | |
972 | \f | |
973 | /* tcpServerCommand: | |
974 | * | |
975 | * This procedure is invoked to process the "tcp server" Tcl | |
976 | * command. It requests that a server be created to listen at a | |
977 | * TCP/IP port, whose number may be assigned by the system or | |
978 | * specified by the user with the "-port" option. | |
979 | * | |
980 | * A command string is supplied for use when the server begins to | |
981 | * accept connections. See the documentation of tcpServerObjectCmd | |
982 | * for a description of the command string. | |
983 | * | |
984 | * If the server is created successfully, the return value will | |
985 | * be the name of a "server object" that can be used for future | |
986 | * actions upon the server. This object will be usable as a Tcl | |
987 | * command; the command is processed by the tcpServerObjectCmd function. | |
988 | * | |
989 | * Syntax: | |
990 | * tcp server ?-port #? ?-command string? | |
991 | * | |
992 | * Results: | |
993 | * A standard Tcl result. Return value is the name of the server | |
994 | * object, which may be invoked as a Tcl command (see | |
995 | * tcpServerObjectCmd for details). | |
996 | */ | |
997 | ||
998 | /* ARGSUSED */ | |
999 | static int | |
1000 | tcpServerCommand (clientData, interp, argc, argv) | |
1001 | ClientData clientData; | |
1002 | Tcl_Interp * interp; | |
1003 | int argc; | |
1004 | char * * argv; | |
1005 | { | |
1006 | int unixStatus; | |
1007 | int one; | |
1008 | char * message; | |
1009 | char * nargv [3]; | |
1010 | int nargc; | |
1011 | ||
1012 | /* Create a structure to hold the tcp server's description. */ | |
1013 | ||
1014 | Tcp_ServerData * server = | |
1015 | (Tcp_ServerData *) ckalloc (sizeof (Tcp_ServerData)); | |
1016 | ||
1017 | /* Set up the interpreter and the default command. Clear the list of | |
1018 | * clients. */ | |
1019 | ||
1020 | server -> interp = interp; | |
1021 | server -> command = "tcp login"; | |
1022 | server -> freeCommand = TCL_STATIC; | |
1023 | server -> stopFlag = 0; | |
1024 | server -> raw = 0; | |
1025 | server -> firstClient = (Tcp_ClientData *) NULL; | |
1026 | ||
1027 | /* Create the socket at which the server will listen. */ | |
1028 | ||
1029 | server -> socketfd = socket (AF_INET, SOCK_STREAM, 0); | |
1030 | if (server -> socketfd < 0) { | |
1031 | Tcl_AppendResult (interp, "can't create socket: ", | |
1032 | Tcl_UnixError (interp), (char *) NULL); | |
1033 | } else { | |
1034 | ||
1035 | /* Set up the socket for non-blocking I/O. */ | |
1036 | ||
1037 | one = 1; | |
1038 | unixStatus = ioctl (server -> socketfd, FIONBIO, (char *) &one); | |
1039 | if (unixStatus < 0) { | |
1040 | Tcl_AppendResult (interp, "can't set non-blocking I/O on socket: ", | |
1041 | Tcl_UnixError (interp), (char *) NULL); | |
1042 | } else { | |
1043 | ||
1044 | /* Server structure has been created and socket has been opened. | |
1045 | * Now configure the server. | |
1046 | */ | |
1047 | ||
1048 | if (tcpServerObjectConfig ((ClientData) server, interp, argc, argv) | |
1049 | == TCL_OK) | |
1050 | { | |
1051 | ||
1052 | /* Link the server on the list of active servers */ | |
1053 | ||
1054 | if (tcpFirstServer) | |
1055 | tcpFirstServer -> prev = server; | |
1056 | server -> next = tcpFirstServer; | |
1057 | tcpFirstServer = server; | |
1058 | server -> prev = NULL; | |
1059 | ||
1060 | /* Add the server object command */ | |
1061 | ||
1062 | sprintf (server -> name, "tcp_server_%d", server -> socketfd); | |
1063 | ||
1064 | Tcl_CreateCommand (interp, server -> name, | |
1065 | (Tcl_CmdProc *) tcpServerObjectCmd, | |
1066 | (ClientData) server, | |
1067 | (Tcl_CmdDeleteProc *) deleteTcpServerObjectCmd); | |
1068 | ||
1069 | Tcl_SetResult (interp, server -> name, TCL_STATIC); | |
1070 | ||
1071 | return TCL_OK; | |
1072 | ||
1073 | } | |
1074 | } | |
1075 | ||
1076 | /* Error in configuring the server. Trash the socket. */ | |
1077 | ||
1078 | unixStatus = close (server -> socketfd); | |
1079 | if (unixStatus < 0) { | |
1080 | nargc = 3; | |
1081 | nargv [0] = "(also failed to close socket: "; | |
1082 | nargv [1] = Tcl_UnixError (interp); | |
1083 | nargv [2] = ")"; | |
1084 | message = Tcl_Concat (nargc, nargv); | |
1085 | Tcl_AddErrorInfo (interp, message); | |
1086 | free (message); | |
1087 | } | |
1088 | } | |
1089 | ||
1090 | /* Error in creating the server -- get rid of the data structure */ | |
1091 | ||
1092 | if (server -> freeCommand != NULL) { | |
1093 | (*(server -> freeCommand)) (server -> command); | |
1094 | } | |
1095 | ckfree ((char *) server); | |
1096 | return TCL_ERROR; | |
1097 | } | |
1098 | \f | |
1099 | /* | |
1100 | * tcpServersCommand: | |
1101 | * | |
1102 | * The following procedure is invoked to process the `tcp servers' Tcl | |
1103 | * command. It returns a list of the servers that are currently known. | |
1104 | */ | |
1105 | ||
1106 | /* ARGSUSED */ | |
1107 | static int | |
1108 | tcpServersCommand (clientData, interp, argc, argv) | |
1109 | ClientData clientData; | |
1110 | Tcl_Interp * interp; | |
1111 | int argc; | |
1112 | char * * argv; | |
1113 | { | |
1114 | Tcp_ServerData * server; | |
1115 | ||
1116 | /* Check syntax */ | |
1117 | ||
1118 | if (argc != 1) { | |
1119 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], " ", | |
1120 | argv [0], "\"", (char *) NULL); | |
1121 | return TCL_ERROR; | |
1122 | } | |
1123 | ||
1124 | for (server = tcpFirstServer; server != NULL; server = server -> next) { | |
1125 | Tcl_AppendElement (interp, server -> name, 0); | |
1126 | } | |
1127 | ||
1128 | return TCL_OK; | |
1129 | } | |
1130 | \f | |
1131 | /* | |
1132 | * tcpWaitCommand: | |
1133 | * | |
1134 | * This procedure is invoked to process the "tcp wait" Tcl | |
1135 | * command. It requests that the process delay until an event is | |
1136 | * pending for a TCP server. | |
1137 | * | |
1138 | * It returns a count of pending events. | |
1139 | * | |
1140 | * In a Tk environment, the procedure returns an error message stating | |
1141 | * that no servers are known to the event handler. This is correct. The | |
1142 | * servers register with Tk's event handler, and are not known to the simple | |
1143 | * event handler. | |
1144 | */ | |
1145 | ||
1146 | /*ARGSUSED*/ | |
1147 | static int | |
1148 | tcpWaitCommand (clientData, interp, argc, argv) | |
1149 | ClientData clientData; | |
1150 | Tcl_Interp * interp; | |
1151 | int argc; | |
1152 | char * * argv; | |
1153 | { | |
1154 | int status; | |
1155 | ||
1156 | if (argc != 1) { | |
1157 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], | |
1158 | " ", argv [0], "\"", (char *) NULL); | |
1159 | return TCL_ERROR; | |
1160 | } | |
1161 | ||
1162 | /* Do the poll */ | |
1163 | ||
1164 | errno = 0; status = -1; | |
1165 | ||
1166 | /* Check for trouble */ | |
1167 | ||
1168 | if (status < 0) { | |
1169 | if (errno == 0) { | |
1170 | Tcl_SetResult (interp, "no servers known to event handler", TCL_STATIC); | |
1171 | } else { | |
1172 | Tcl_AppendResult (interp, "select: ", Tcl_UnixError (interp), | |
1173 | (char *) NULL); | |
1174 | } | |
1175 | return TCL_ERROR; | |
1176 | } | |
1177 | ||
1178 | /* Return the number of events pending. */ | |
1179 | ||
1180 | sprintf (interp -> result, "%d", status); | |
1181 | return TCL_OK; | |
1182 | } | |
1183 | \f | |
1184 | /* | |
1185 | * tcpServerObjectCmd -- | |
1186 | * | |
1187 | * This procedure is invoked when a command is called on a server | |
1188 | * object directly. It dispatches to the appropriate command processing | |
1189 | * procedure to handle the command. | |
1190 | * | |
1191 | * $server accept | |
1192 | * [Internal call] - Accept a connection. | |
1193 | * $server clients | |
1194 | * Return a list of all clients connected to a server. | |
1195 | * $server configure ?args? | |
1196 | * Revise or query a server's configuration. | |
1197 | * $server start | |
1198 | * Start a server running. | |
1199 | * $server stop | |
1200 | * Terminate a server. | |
1201 | */ | |
1202 | ||
1203 | static int | |
1204 | tcpServerObjectCmd (clientData, interp, argc, argv) | |
1205 | ClientData clientData; | |
1206 | Tcl_Interp * interp; | |
1207 | int argc; | |
1208 | char * * argv; | |
1209 | { | |
1210 | int c; | |
1211 | unsigned length; | |
1212 | ||
1213 | if (argc < 2) { | |
1214 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [0], | |
1215 | " command ?args?\"", (char *) NULL); | |
1216 | return TCL_ERROR; | |
1217 | } | |
1218 | ||
1219 | c = argv [1] [0]; | |
1220 | length = strlen (argv [1]); | |
1221 | ||
1222 | if (c == 'a' && strncmp (argv [1], "accept", length) == 0) { | |
1223 | return tcpServerObjectAcceptCmd (clientData, interp, argc-1, argv+1); | |
1224 | } | |
1225 | if (c == 'c' && length >= 2 && strncmp (argv [1], "clients", length) == 0) { | |
1226 | return tcpServerObjectClientsCmd (clientData, interp, argc-1, argv+1); | |
1227 | } | |
1228 | if (c == 'c' && length >= 2 | |
1229 | && strncmp (argv [1], "configure", length) == 0) { | |
1230 | return tcpServerObjectConfigCmd (clientData, interp, argc-1, argv+1); | |
1231 | } | |
1232 | if (c == 's' && length >= 3 && strncmp (argv [1], "start", length) == 0) { | |
1233 | return tcpServerObjectStartCmd (clientData, interp, argc-1, argv+1); | |
1234 | } | |
1235 | if (c == 's' && length >= 3 && strncmp (argv [1], "stop", length) == 0) { | |
1236 | return tcpServerObjectStopCmd (clientData, interp, argc-1, argv+1); | |
1237 | } | |
1238 | Tcl_AppendResult (interp, argv [0], ": ", "bad option \"", argv [1], | |
1239 | "\": should be clients, configure, start, or stop", | |
1240 | (char *) NULL); | |
1241 | return TCL_ERROR; | |
1242 | } | |
1243 | \f | |
1244 | /* | |
1245 | * tcpServerObjectAcceptCmd -- | |
1246 | * | |
1247 | * The following procedure handles the `accept' command on a | |
1248 | * server object. It is called in the background by | |
1249 | * tcpServerAcceptConnection when a connection request appears on | |
1250 | * a server. It is responsible for creating the client and | |
1251 | * accepting the connection request. | |
1252 | * | |
1253 | * Results: | |
1254 | * Returns a standard TCL result. The return value is the name | |
1255 | * of the client if the call is successful. | |
1256 | * | |
1257 | * Side effects: | |
1258 | * A Tcl command named after the client object is created. | |
1259 | */ | |
1260 | ||
1261 | static int | |
1262 | tcpServerObjectAcceptCmd (clientData, interp, argc, argv) | |
1263 | ClientData clientData; | |
1264 | Tcl_Interp * interp; | |
1265 | int argc; | |
1266 | char * * argv; | |
1267 | { | |
1268 | register Tcp_ServerData * server = (Tcp_ServerData *) clientData; | |
1269 | register Tcp_ClientData * client; | |
1270 | int rubbish; | |
1271 | int unixStatus; | |
1272 | int nargc; | |
1273 | char * nargv [3]; | |
1274 | char * message; | |
1275 | ||
1276 | /* Check command syntax */ | |
1277 | ||
1278 | if (argc != 1) { | |
1279 | Tcl_AppendResult (interp, "wrong # args, should be \"", argv [-1], " ", | |
1280 | argv [0], "\"", (char *) NULL); | |
1281 | return TCL_ERROR; | |
1282 | } | |
1283 | ||
1284 | /* Create the client data structure */ | |
1285 | ||
1286 | client = (Tcp_ClientData *) ckalloc (sizeof (Tcp_ClientData)); | |
1287 | ||
1288 | /* Accept the client's connection request */ | |
1289 | ||
1290 | rubbish = sizeof (struct sockaddr_in); | |
1291 | client -> socketfd = accept (server -> socketfd, | |
1292 | (struct sockaddr *) &(client -> peeraddr), | |
1293 | &rubbish); | |
1294 | if (client -> socketfd < 0) { | |
1295 | Tcl_AppendResult (interp, "can't accept connection: ", | |
1296 | Tcl_UnixError (interp), (char *) NULL); | |
1297 | ckfree ((char *) client); | |
1298 | return TCL_ERROR; | |
1299 | } | |
1300 | ||
1301 | /* Set up the socket for non-blocking I/O */ | |
1302 | ||
1303 | rubbish = 1; | |
1304 | unixStatus = ioctl (client -> socketfd, FIONBIO, (char *) &rubbish); | |
1305 | if (unixStatus < 0) { | |
1306 | Tcl_AppendResult (interp, | |
1307 | "can't set non-blocking I/O on client's socket: ", | |
1308 | Tcl_UnixError (interp), (char *) NULL); | |
1309 | unixStatus = close (client -> socketfd); | |
1310 | if (unixStatus < 0) { | |
1311 | nargc = 3; | |
1312 | nargv [0] = "(also failed to close socket: "; | |
1313 | nargv [1] = Tcl_UnixError (interp); | |
1314 | nargv [2] = ")"; | |
1315 | message = Tcl_Concat (nargc, nargv); | |
1316 | Tcl_AddErrorInfo (interp, message); | |
1317 | free (message); | |
1318 | } | |
1319 | ckfree ((char *) client); | |
1320 | return TCL_ERROR; | |
1321 | } | |
1322 | ||
1323 | /* Set up the client's description */ | |
1324 | ||
1325 | client -> server = server; | |
1326 | sprintf (client -> name, "tcp_client_%d", client -> socketfd); | |
1327 | client -> command = malloc (strlen (server -> command) + 1); | |
1328 | client -> freeCommand = (Tcl_FreeProc *) free; | |
1329 | strcpy (client -> command, server -> command); | |
1330 | client -> inputBuffer = Tcl_CreateCmdBuf (); | |
1331 | client -> resultString = client -> resultPointer = (char *) NULL; | |
1332 | client -> freeResultString = (Tcl_FreeProc *) NULL; | |
1333 | client -> activeFlag = 0; | |
1334 | client -> closeFlag = 0; | |
1335 | client -> next = server -> firstClient; | |
1336 | if (client -> next != NULL) { | |
1337 | client -> next -> prev = client; | |
1338 | } | |
1339 | client -> prev = NULL; | |
1340 | server -> firstClient = client; | |
1341 | ||
1342 | /* Create the Tcl command for the client */ | |
1343 | ||
1344 | Tcl_CreateCommand (interp, client -> name, | |
1345 | (Tcl_CmdProc *) tcpClientObjectCmd, | |
1346 | (ClientData) client, | |
1347 | (Tcl_CmdDeleteProc *) deleteTcpClientObjectCmd); | |
1348 | ||
1349 | /* Return the client's name */ | |
1350 | ||
1351 | Tcl_SetResult (interp, client -> name, TCL_STATIC); | |
1352 | return TCL_OK; | |
1353 | } | |
1354 | \f | |
1355 | /* | |
1356 | * tcpServerObjectClientsCmd -- | |
1357 | * | |
1358 | * This procedure in invoked in response to the `clients' command | |
1359 | * on a TCP server object. It returns a list of clients for the server. | |
1360 | */ | |
1361 | ||
1362 | static int | |
1363 | tcpServerObjectClientsCmd (clientData, interp, argc, argv) | |
1364 | ClientData clientData; | |
1365 | Tcl_Interp * interp; | |
1366 | int argc; | |
1367 | char * * argv; | |
1368 | { | |
1369 | Tcp_ServerData * server = (Tcp_ServerData *) clientData; | |
1370 | Tcp_ClientData * client; | |
1371 | ||
1372 | /* Check syntax */ | |
1373 | ||
1374 | if (argc != 1) { | |
1375 | Tcl_AppendResult (interp, "wrong # args, should be\"", argv [-1], " ", | |
1376 | argv [0], "\"", (char *) NULL); | |
1377 | return TCL_ERROR; | |
1378 | } | |
1379 | ||
1380 | for (client = server -> firstClient; client != NULL; | |
1381 | client = client -> next) { | |
1382 | Tcl_AppendElement (interp, client -> name, 0); | |
1383 | } | |
1384 | ||
1385 | return TCL_OK; | |
1386 | } | |
1387 | \f | |
1388 | /* | |
1389 | * tcpServerObjectConfigCmd -- | |
1390 | * | |
1391 | * This procedure is invoked in response to the `config' command | |
1392 | * on a TCP server object. With no arguments, it returns a list | |
1393 | * of valid arguments. With one argument, it returns the current | |
1394 | * value of that option. With multiple arguments, it attempts to | |
1395 | * configure the server according to that argument list. | |
1396 | * Results: | |
1397 | * Returns a standard Tcl result. | |
1398 | */ | |
1399 | ||
1400 | static int | |
1401 | tcpServerObjectConfigCmd (clientData, interp, argc, argv) | |
1402 | ClientData clientData; | |
1403 | Tcl_Interp * interp; | |
1404 | int argc; | |
1405 | char * * argv; | |
1406 | { | |
1407 | int unixStatus; | |
1408 | int c; | |
1409 | unsigned length; | |
1410 | ||
1411 | /* No arguments -- return a list of valid options. */ | |
1412 | ||
1413 | if (argc <= 1) { | |
1414 | Tcl_SetResult (interp, "-command -port", TCL_STATIC); | |
1415 | return TCL_OK; | |
1416 | } | |
1417 | ||
1418 | /* One argument -- query a particular option */ | |
1419 | ||
1420 | if (argc == 2) { | |
1421 | register Tcp_ServerData * server = (Tcp_ServerData *) clientData; | |
1422 | char * arg = argv [1]; | |
1423 | ||
1424 | if (arg [0] != '-') { | |
1425 | Tcl_AppendResult (interp, argv [-1], " ", argv [0], | |
1426 | ": bad option \"", arg, | |
1427 | "\" -- each option must begin with a hyphen.", | |
1428 | (char *) NULL); | |
1429 | return TCL_ERROR; | |
1430 | ||
1431 | } | |
1432 | ||
1433 | length = strlen (++arg); | |
1434 | c = arg [0]; | |
1435 | ||
1436 | if (c == 'c' && strncmp (arg, "command", length) == 0) { | |
1437 | ||
1438 | /* Command option -- Get the command name */ | |
1439 | ||
1440 | Tcl_SetResult (interp, server->name, TCL_STATIC); | |
1441 | return TCL_OK; | |
1442 | ||
1443 | } | |
1444 | ||
1445 | if (c == 'p' && strncmp (arg, "port", length) == 0) { | |
1446 | ||
1447 | /* Port option -- Get the port number */ | |
1448 | ||
1449 | struct sockaddr_in portaddr; | |
1450 | int rubbish = sizeof (struct sockaddr_in); | |
1451 | ||
1452 | unixStatus = getsockname (server -> socketfd, | |
1453 | (struct sockaddr *) &portaddr, &rubbish); | |
1454 | if (unixStatus < 0) { | |
1455 | Tcl_AppendResult (interp, argv [-1], ": can't read port #: ", | |
1456 | Tcl_UnixError (interp), (char *) NULL); | |
1457 | return TCL_ERROR; | |
1458 | } | |
1459 | Tcl_ResetResult (interp); | |
1460 | sprintf (interp -> result, "%d", (int) ntohs (portaddr.sin_port)); | |
1461 | return TCL_OK; | |
1462 | } | |
1463 | ||
1464 | /* Unknown option */ | |
1465 | ||
1466 | Tcl_AppendResult (interp, argv [-1], ": unknown option \"", arg, | |
1467 | "\" -- must be -command or -port", (char *) NULL); | |
1468 | return TCL_ERROR; | |
1469 | } | |
1470 | ||
1471 | return tcpServerObjectConfig (clientData, interp, argc, argv); | |
1472 | } | |
1473 | \f | |
1474 | /* | |
1475 | * tcpServerObjectStartCmd -- | |
1476 | * | |
1477 | * This procedure is invoked to process the "start" command on a | |
1478 | * TCP server object. It sets the server up so that new | |
1479 | * connection requests will create "server-client" objects and | |
1480 | * invoke the server's command with them. | |
1481 | * | |
1482 | * If Tk is available, the "start" command returns to the caller. | |
1483 | * If Tk is not available, the "start" command immediately enters | |
1484 | * a loop that attempts to process the connection events (and | |
1485 | * other file events as well). The loop may be exited by | |
1486 | * executing a `stop' command on the server object. (The `stop' | |
1487 | * command also exists in the Tk environment, since there is more | |
1488 | * to stopping a server than just breaking out of its event | |
1489 | * loop.) | |
1490 | */ | |
1491 | ||
1492 | static int | |
1493 | tcpServerObjectStartCmd (clientData, interp, argc, argv) | |
1494 | ClientData clientData; | |
1495 | Tcl_Interp * interp; | |
1496 | int argc; | |
1497 | char * * argv; | |
1498 | { | |
1499 | register Tcp_ServerData * server = (Tcp_ServerData *) clientData; | |
1500 | int unixStatus; | |
1501 | ||
1502 | /* Check command syntax */ | |
1503 | ||
1504 | if (argc != 1) { | |
1505 | Tcl_AppendResult (interp, "wrong # args, should be \"", argv [-1], " ", | |
1506 | argv [0], "\"", (char *) NULL); | |
1507 | return TCL_ERROR; | |
1508 | } | |
1509 | ||
1510 | /* Listen at the server's socket */ | |
1511 | ||
1512 | unixStatus = listen (server -> socketfd, TCP_LISTEN_BACKLOG); | |
1513 | if (unixStatus < 0) { | |
1514 | Tcl_AppendResult (interp, argv [-1], ": can't listen at socket: ", | |
1515 | Tcl_UnixError (interp), (char *) NULL); | |
1516 | return TCL_ERROR; | |
1517 | } | |
1518 | ||
1519 | /* Add a file handler to gain control at tcpServerAcceptConnection | |
1520 | * whenever a client attempts to connect. | |
1521 | */ | |
1522 | ||
1523 | simpleCreateFileHandler (server -> socketfd, TK_READABLE, | |
1524 | (Tk_FileProc *) tcpServerAcceptConnection, | |
1525 | clientData); | |
1526 | return TCL_OK; | |
1527 | } | |
1528 | \f | |
1529 | /* | |
1530 | * tcpServerObjectStopCmd | |
1531 | * | |
1532 | * This procedure is invoked in response to the `$server stop' Tcl | |
1533 | * command. It destroys the server's object command. Destroying the object | |
1534 | * command, in turn, attempts to shut down the server in question. It closes | |
1535 | * the listen socket, closes all the clients, and sets the `stop' flag for | |
1536 | * the server itself. It then calls `tcpServerClose' to try to get rid of | |
1537 | * the server. | |
1538 | * | |
1539 | * If one or more clients are active, the server does not shut down | |
1540 | * until they can be closed properly. | |
1541 | */ | |
1542 | ||
1543 | static int | |
1544 | tcpServerObjectStopCmd (clientData, interp, argc, argv) | |
1545 | ClientData clientData; | |
1546 | Tcl_Interp * interp; | |
1547 | int argc; | |
1548 | char * * argv; | |
1549 | { | |
1550 | register Tcp_ServerData * server = (Tcp_ServerData *) clientData; | |
1551 | ||
1552 | if (argc != 1) { | |
1553 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], | |
1554 | " ", argv [0], "\"", (char *) NULL); | |
1555 | return TCL_ERROR; | |
1556 | } | |
1557 | ||
1558 | /* Delete the server command */ | |
1559 | ||
1560 | Tcl_DeleteCommand (interp, server -> name); | |
1561 | ||
1562 | return TCL_OK; | |
1563 | } | |
1564 | \f | |
1565 | /* | |
1566 | * deleteTcpServerObjectCmd -- | |
1567 | * | |
1568 | * This procedure is called when a server's object command is deleted. | |
1569 | * | |
1570 | * It is the first procedure called when a server is shut down. It | |
1571 | * closes the listen socket and deletes its file handler. It also attempts | |
1572 | * to close all the clients. | |
1573 | * | |
1574 | * It may be that a client needs to be able to complete a data transfer | |
1575 | * before it can be closed. In this case, the `close flag' for the client is | |
1576 | * set. The client will be deleted when it reaches a quiescent point. | |
1577 | * | |
1578 | * Once all the clients are gone, tcpDeleteServer removes the server's | |
1579 | * client data structure. | |
1580 | */ | |
1581 | ||
1582 | static void | |
1583 | deleteTcpServerObjectCmd (clientData) | |
1584 | ClientData clientData; | |
1585 | { | |
1586 | register Tcp_ServerData * server = (Tcp_ServerData *) clientData; | |
1587 | register Tcp_ClientData * client, * nextClient; | |
1588 | ||
1589 | /* Close the listen socket and delete its handler */ | |
1590 | ||
1591 | simpleDeleteFileHandler (server -> socketfd); | |
1592 | (void) close (server -> socketfd); | |
1593 | server -> socketfd = -1; | |
1594 | ||
1595 | /* Close all clients */ | |
1596 | ||
1597 | for (client = server -> firstClient; client != NULL; client = nextClient) { | |
1598 | nextClient = client -> next; | |
1599 | if (client -> activeFlag) | |
1600 | client -> closeFlag = 1; | |
1601 | else | |
1602 | tcpCloseClient (client); | |
1603 | } | |
1604 | ||
1605 | /* Remove the server from the list of servers. */ | |
1606 | ||
1607 | if (server -> next != NULL) | |
1608 | server -> next -> prev = server -> prev; | |
1609 | if (server -> prev != NULL) | |
1610 | server -> prev -> next = server -> next; | |
1611 | else | |
1612 | tcpFirstServer = server -> next; | |
1613 | ||
1614 | /* If all clients are closed, get to tcpDeleteServer now. Otherwise, set | |
1615 | * the server's stop flag and return. | |
1616 | */ | |
1617 | ||
1618 | if (server -> firstClient == NULL) { | |
1619 | tcpDeleteServer (server); | |
1620 | } else { | |
1621 | server -> stopFlag = 1; | |
1622 | } | |
1623 | } | |
1624 | \f | |
1625 | /* | |
1626 | * tcpDeleteServer -- | |
1627 | * | |
1628 | * This procedure is invoked as the final phase of deleting a TCP server. | |
1629 | * When execution gets here, the server's listen socket has been closed and | |
1630 | * the handler has been removed. The server's object command has been deleted. | |
1631 | * The server has been removed from the list of active servers. All the | |
1632 | * server's clients have been closed. The server's login command has been | |
1633 | * deleted. All that remains is to deallocate the server's data structures. | |
1634 | */ | |
1635 | ||
1636 | static void | |
1637 | tcpDeleteServer (server) | |
1638 | Tcp_ServerData * server; | |
1639 | { | |
1640 | /* Get rid of the server's initial command */ | |
1641 | ||
1642 | if (server -> command != NULL && server -> freeCommand != NULL) { | |
1643 | (*(server -> freeCommand)) (server -> command); | |
1644 | } | |
1645 | ||
1646 | /* Get rid of the server's own data structure */ | |
1647 | ||
1648 | (void) ckfree ((char *) server); | |
1649 | } | |
1650 | \f | |
1651 | /* | |
1652 | * tcpServerObjectConfig -- | |
1653 | * | |
1654 | * This procedure is invoked to configure a TCP server object. | |
1655 | * It may be called from tcpServerCommand when the server is | |
1656 | * first being created, or else from tcpServerObjectCmd if the | |
1657 | * server object is called with the "config" option. | |
1658 | * | |
1659 | * In any case, the arguments are expected to contain zero or | |
1660 | * more of the following: | |
1661 | * | |
1662 | * -port <number> | |
1663 | * Requests that the server listen at a specific port. | |
1664 | * Default is whatever the system assigns. | |
1665 | * | |
1666 | * -command <string> | |
1667 | * Specifies the initial command used when a client | |
1668 | * first connects to the server. The command is | |
1669 | * concatenated with the name of a "server-client" object | |
1670 | * that identifies the client, and then called: | |
1671 | * command client | |
1672 | * Default is "tcp login" | |
1673 | * | |
1674 | * -raw | |
1675 | * Puts the server in raw socket mode. | |
1676 | * | |
1677 | * Result: | |
1678 | * A standard TCL result. | |
1679 | */ | |
1680 | ||
1681 | static int | |
1682 | tcpServerObjectConfig (clientData, interp, argc, argv) | |
1683 | ClientData clientData; | |
1684 | Tcl_Interp * interp; | |
1685 | int argc; | |
1686 | char * * argv; | |
1687 | { | |
1688 | ||
1689 | register Tcp_ServerData * server = (Tcp_ServerData *) clientData; | |
1690 | ||
1691 | int status; | |
1692 | int unixStatus; | |
1693 | ||
1694 | /* On entry, argc shows one plus the number of parameters. Argv[-1] */ | |
1695 | /* and argv[0] give the command that got us here: either "tcp */ | |
1696 | /* server" or else "serverName config" */ | |
1697 | ||
1698 | int a; | |
1699 | unsigned length; | |
1700 | int c; | |
1701 | ||
1702 | /* Step through the parameters */ | |
1703 | ||
1704 | for (a = 1; a < argc; ++a) { | |
1705 | char * arg = argv [a]; | |
1706 | ||
1707 | if (arg [0] != '-') { | |
1708 | Tcl_AppendResult (interp, argv [-1], ": bad option \"", arg, | |
1709 | "\" -- each option must begin with a hyphen.", | |
1710 | (char *) NULL); | |
1711 | return TCL_ERROR; | |
1712 | } else { | |
1713 | ||
1714 | length = strlen (++arg); | |
1715 | c = arg [0]; | |
1716 | ||
1717 | if (c == 'c' && strncmp (arg, "command", length) == 0) { | |
1718 | ||
1719 | /* Command option -- Get the command name */ | |
1720 | ||
1721 | ++a; | |
1722 | if (a >= argc) { | |
1723 | Tcl_AppendResult (interp, argv [-1], | |
1724 | ": \"-command\" must be followed by a string.", | |
1725 | (char *) NULL); | |
1726 | return TCL_ERROR; | |
1727 | } | |
1728 | ||
1729 | /* Free the old command name */ | |
1730 | ||
1731 | if (server -> freeCommand != NULL) { | |
1732 | (*(server -> freeCommand)) (server -> command); | |
1733 | } | |
1734 | ||
1735 | /* Put in the new command name */ | |
1736 | ||
1737 | server -> command = (char *) malloc (strlen (argv [a]) + 1); | |
1738 | strcpy (server -> command, argv [a]); | |
1739 | server -> freeCommand = (Tcl_FreeProc *) free; | |
1740 | ||
1741 | } else if (c == 'p' && strncmp (arg, "port", length) == 0) { | |
1742 | ||
1743 | /* Port option -- get the port number */ | |
1744 | ||
1745 | char * portstr; | |
1746 | int portno; | |
1747 | struct sockaddr_in portaddr; | |
1748 | ||
1749 | ++a; | |
1750 | if (a >= argc) { | |
1751 | Tcl_AppendResult (interp, argv [-1], | |
1752 | ": \"-port\" must be followed by a number.", | |
1753 | (char *) NULL); | |
1754 | return TCL_ERROR; | |
1755 | } | |
1756 | portstr = argv [a]; | |
1757 | status = Tcl_GetInt (interp, portstr, &portno); | |
1758 | if (status) return status; | |
1759 | ||
1760 | /* Set the port number */ | |
1761 | ||
1762 | memset ((void *) & portaddr, 0, sizeof (struct sockaddr_in)); | |
1763 | portaddr.sin_port = htons (portno); | |
1764 | unixStatus = bind (server -> socketfd, | |
1765 | (struct sockaddr *) &portaddr, | |
1766 | sizeof (struct sockaddr_in)); | |
1767 | if (unixStatus < 0) { | |
1768 | Tcl_AppendResult (interp, argv [-1], | |
1769 | ": can't set port number: ", | |
1770 | Tcl_UnixError (interp), (char *) NULL); | |
1771 | return TCL_ERROR; | |
1772 | } | |
1773 | ||
1774 | } else if (c == 'r' && strncmp (arg, "raw", length) == 0) { | |
1775 | ||
1776 | /* raw option -- set raw socket mode */ | |
1777 | ||
1778 | server -> raw = 1; | |
1779 | ||
1780 | } else { | |
1781 | ||
1782 | /* Unknown option */ | |
1783 | ||
1784 | Tcl_AppendResult (interp, argv [-1], | |
1785 | ": unknown option \"", arg - 1, | |
1786 | "\" -- must be -command or -port", (char *) NULL); | |
1787 | return TCL_ERROR; | |
1788 | } | |
1789 | } | |
1790 | } | |
1791 | ||
1792 | Tcl_SetResult (interp, server -> name, TCL_STATIC); | |
1793 | return TCL_OK; | |
1794 | } | |
1795 | \f | |
1796 | /* | |
1797 | * tcpClientObjectCmd -- | |
1798 | * | |
1799 | * This procedure handles the object command for a Tcp client (on | |
1800 | * the server side). It takes several forms: | |
1801 | * $client command ?command? | |
1802 | * With no arguments, returns the client's | |
1803 | * current command. With arguments, replaces the | |
1804 | * client's command with the arguments | |
1805 | * $client close | |
1806 | * Deletes the client. If a command is being | |
1807 | * processed on the client's behalf, the client | |
1808 | * will not be deleted until the command's result | |
1809 | * is returned. | |
1810 | * $client do ?args? | |
1811 | * Concatenate the client's command with ?args?, | |
1812 | * and execute the result. Called in background | |
1813 | * when a command arrives and on initial | |
1814 | * connection. | |
1815 | * $client hostname | |
1816 | * Returns the name of the host where the client | |
1817 | * is running. | |
1818 | * $client server | |
1819 | * Returns the name of the server to which the client | |
1820 | * is connected. | |
1821 | */ | |
1822 | ||
1823 | static int | |
1824 | tcpClientObjectCmd (clientData, interp, argc, argv) | |
1825 | ClientData clientData; | |
1826 | Tcl_Interp * interp; | |
1827 | int argc; | |
1828 | char * * argv; | |
1829 | { | |
1830 | int c; | |
1831 | unsigned length; | |
1832 | ||
1833 | if (argc < 2) { | |
1834 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [0], | |
1835 | " command ?args?\"", (char *) NULL); | |
1836 | return TCL_ERROR; | |
1837 | } | |
1838 | ||
1839 | c = argv [1] [0]; | |
1840 | length = strlen (argv [1]); | |
1841 | ||
1842 | if (c == 'c' && length >= 2 && strncmp (argv [1], "close", length) == 0) { | |
1843 | return tcpClientObjectCloseCmd (clientData, interp, argc-1, argv+1); | |
1844 | } | |
1845 | if (c == 'c' && length >= 2 && strncmp (argv [1], "command", length) == 0) { | |
1846 | return tcpClientObjectCommandCmd (clientData, interp, argc-1, argv+1); | |
1847 | } | |
1848 | if (c == 'd' && strncmp (argv [1], "do", length) == 0) { | |
1849 | return tcpClientObjectDoCmd (clientData, interp, argc-1, argv+1); | |
1850 | } | |
1851 | if (c == 'h' && strncmp (argv [1], "hostname", length) == 0) { | |
1852 | return tcpClientObjectHostnameCmd (clientData, interp, argc-1, argv+1); | |
1853 | } | |
1854 | if (c == 's' && strncmp (argv [1], "server", length) == 0) { | |
1855 | return tcpClientObjectServerCmd (clientData, interp, argc-1, argv+1); | |
1856 | } | |
1857 | ||
1858 | Tcl_AppendResult (interp, "bad option \"", argv [1], | |
1859 | "\": should be close, command, do, hostname or server", | |
1860 | (char *) NULL); | |
1861 | return TCL_ERROR; | |
1862 | } | |
1863 | \f | |
1864 | /* | |
1865 | * tcpClientObjectCloseCmd -- | |
1866 | * | |
1867 | * This procedure is called when the Tcl program wants to close a client. | |
1868 | * If the client is active, it sets a flag to close the client when it | |
1869 | * becomes quiescent. Otherwise, it closes the client immediately. | |
1870 | */ | |
1871 | ||
1872 | static int | |
1873 | tcpClientObjectCloseCmd (clientData, interp, argc, argv) | |
1874 | ClientData clientData; | |
1875 | Tcl_Interp * interp; | |
1876 | int argc; | |
1877 | char * * argv; | |
1878 | { | |
1879 | register Tcp_ClientData * client = (Tcp_ClientData *) clientData; | |
1880 | ||
1881 | if (argc != 1) { | |
1882 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], " ", | |
1883 | argv [0], "\"", (char *) NULL); | |
1884 | return TCL_ERROR; | |
1885 | } | |
1886 | ||
1887 | if (client -> activeFlag) | |
1888 | client -> closeFlag = 1; | |
1889 | else | |
1890 | tcpCloseClient (client); | |
1891 | ||
1892 | return TCL_OK; | |
1893 | } | |
1894 | \f | |
1895 | /* | |
1896 | * tcpClientObjectCommandCmd -- | |
1897 | * | |
1898 | * Query/change the command associated with a client object | |
1899 | * | |
1900 | * Syntax: | |
1901 | * $client command ?newcommand? | |
1902 | * | |
1903 | * Return: | |
1904 | * A standard Tcl result containing the client's command. | |
1905 | */ | |
1906 | ||
1907 | static int | |
1908 | tcpClientObjectCommandCmd (clientData, interp, argc, argv) | |
1909 | ClientData clientData; | |
1910 | Tcl_Interp * interp; | |
1911 | int argc; | |
1912 | char * * argv; | |
1913 | { | |
1914 | register Tcp_ClientData * client = (Tcp_ClientData *) clientData; | |
1915 | ||
1916 | /* Check syntax */ | |
1917 | ||
1918 | if (argc > 2) { | |
1919 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], " ", | |
1920 | argv [0], " ?command?\"", (char *) NULL); | |
1921 | return TCL_ERROR; | |
1922 | } | |
1923 | ||
1924 | /* Set command if necessary */ | |
1925 | ||
1926 | if (argc == 2) { | |
1927 | if (client -> freeCommand != (Tcl_FreeProc *) NULL) { | |
1928 | (*client -> freeCommand) (client -> command); | |
1929 | } | |
1930 | client -> command = malloc (strlen (argv [1]) + 1); | |
1931 | strcpy (client -> command, argv [1]); | |
1932 | client -> freeCommand = (Tcl_FreeProc *) free; | |
1933 | } | |
1934 | ||
1935 | /* Return command in any case */ | |
1936 | ||
1937 | Tcl_SetResult (interp, client -> command, TCL_STATIC); | |
1938 | ||
1939 | return TCL_OK; | |
1940 | } | |
1941 | \f | |
1942 | /* | |
1943 | * tcpClientObjectDoCmd -- | |
1944 | * | |
1945 | * The following procedure handles the `do' command on a client | |
1946 | * object. It is called | |
1947 | * (a) as "$client do", at login. | |
1948 | * (b) as "$client do <command>", when the client sends a | |
1949 | * command. | |
1950 | * (c) as "$client do", with no further arguments, when | |
1951 | * the connection is closed. | |
1952 | * It concatenates the client's saved command string with the | |
1953 | * client's name, and then with the passed command, resulting in | |
1954 | * a command: | |
1955 | * saved_command client passed_command | |
1956 | * which is then passed to Tcl_Eval for processing. | |
1957 | * During the processing of the command, the `active' flag is set for | |
1958 | * the client, to avoid having the client closed prematurely. | |
1959 | */ | |
1960 | static int | |
1961 | tcpClientObjectDoCmd (clientData, interp, argc, argv) | |
1962 | ClientData clientData; | |
1963 | Tcl_Interp * interp; | |
1964 | int argc; | |
1965 | char * * argv; | |
1966 | { | |
1967 | ||
1968 | register Tcp_ClientData * client = (Tcp_ClientData *) clientData; | |
1969 | int status; | |
1970 | int closeflag; | |
1971 | char * prevClient; | |
1972 | char * excmd; | |
1973 | unsigned excmdl; | |
1974 | int scanflags; | |
1975 | ||
1976 | /* Check command syntax */ | |
1977 | ||
1978 | if (argc > 2) { | |
1979 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], | |
1980 | " ", argv [0], " ?command?\"", (char *) NULL); | |
1981 | return TCL_ERROR; | |
1982 | } | |
1983 | ||
1984 | prevClient = tcpCurrentClient; | |
1985 | tcpCurrentClient = client -> name; | |
1986 | ||
1987 | /* Evaluate the client's command, passing the client name and message */ | |
1988 | ||
1989 | closeflag = 0; | |
1990 | client -> activeFlag = 1; | |
1991 | ||
1992 | if (argc == 2) { | |
1993 | excmdl = Tcl_ScanElement (argv [1], &scanflags) + 1; | |
1994 | excmd = (char *) ckalloc (excmdl); | |
1995 | excmdl = Tcl_ConvertElement (argv [1], excmd, scanflags); | |
1996 | excmd [excmdl] = '\0'; | |
1997 | } else { | |
1998 | excmd = (char *) NULL; | |
1999 | } | |
2000 | ||
2001 | status = Tcl_VarEval (interp, client -> command, " ", client -> name, " ", | |
2002 | excmd, (char *) NULL); | |
2003 | ||
2004 | if (excmd) | |
2005 | ckfree (excmd); | |
2006 | ||
2007 | if (status != TCL_OK && argc < 2) { | |
2008 | closeflag = 1; | |
2009 | } | |
2010 | ||
2011 | client -> activeFlag = 0; | |
2012 | tcpCurrentClient = prevClient; | |
2013 | ||
2014 | /* If the client command throws an error on login or logout, | |
2015 | * the client should be disconnected. | |
2016 | * In any case, the result should be reported back to the client. | |
2017 | */ | |
2018 | ||
2019 | if (! (client -> server -> raw)) { | |
2020 | tcpReturnResultToClient (client, interp, status, closeflag); | |
2021 | } else { | |
2022 | tcpPrepareClientForInput (client); | |
2023 | } | |
2024 | ||
2025 | /* The client may have been closed by the ReturnResult operation. DON'T | |
2026 | * USE IT AFTER THIS POINT. | |
2027 | */ | |
2028 | ||
2029 | return TCL_OK; | |
2030 | } | |
2031 | \f | |
2032 | /* | |
2033 | * tcpClientObjectHostnameCmd -- | |
2034 | * | |
2035 | * This procedure is invoked in response to the `$client hostname' | |
2036 | * Tcl command. It returns the name of the peer host on which the client | |
2037 | * runs. | |
2038 | */ | |
2039 | ||
2040 | static int | |
2041 | tcpClientObjectHostnameCmd (clientData, interp, argc, argv) | |
2042 | ClientData clientData; | |
2043 | Tcl_Interp * interp; | |
2044 | int argc; | |
2045 | char * * argv; | |
2046 | { | |
2047 | register Tcp_ClientData * client = (Tcp_ClientData *) clientData; | |
2048 | ||
2049 | struct hostent * hostdesc; | |
2050 | ||
2051 | if (argc != 1) { | |
2052 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], " ", | |
2053 | argv [0], "\"", (char *) NULL); | |
2054 | return TCL_ERROR; | |
2055 | } | |
2056 | ||
2057 | hostdesc = gethostbyaddr ((char *) &(client -> peeraddr.sin_addr.s_addr), | |
2058 | sizeof (client -> peeraddr.sin_addr.s_addr), | |
2059 | AF_INET); | |
2060 | ||
2061 | if (hostdesc != (struct hostent *) NULL) { | |
2062 | Tcl_SetResult (interp, hostdesc -> h_name, TCL_VOLATILE); | |
2063 | } else { | |
2064 | Tcl_SetResult (interp, inet_ntoa (client -> peeraddr.sin_addr), | |
2065 | TCL_VOLATILE); | |
2066 | } | |
2067 | ||
2068 | return TCL_OK; | |
2069 | } | |
2070 | \f | |
2071 | /* | |
2072 | * tcpClientObjectServerCmd -- | |
2073 | * | |
2074 | * This procedure is invoked in response to the `$client server' | |
2075 | * Tcl command. It returns the name of the server to which the client | |
2076 | * is connected. | |
2077 | */ | |
2078 | ||
2079 | static int | |
2080 | tcpClientObjectServerCmd (clientData, interp, argc, argv) | |
2081 | ClientData clientData; | |
2082 | Tcl_Interp * interp; | |
2083 | int argc; | |
2084 | char * * argv; | |
2085 | { | |
2086 | register Tcp_ClientData * client = (Tcp_ClientData *) clientData; | |
2087 | ||
2088 | if (argc != 1) { | |
2089 | Tcl_AppendResult (interp, "wrong # args: should be \"", argv [-1], " ", | |
2090 | argv [0], "\"", (char *) NULL); | |
2091 | return TCL_ERROR; | |
2092 | } | |
2093 | ||
2094 | Tcl_SetResult (interp, client -> server -> name, TCL_STATIC); | |
2095 | ||
2096 | return TCL_OK; | |
2097 | } | |
2098 | \f | |
2099 | /* | |
2100 | * deleteTcpClientObjectCmd -- | |
2101 | * | |
2102 | * This procedure is invoked when a client object's command has | |
2103 | * been deleted. WARNING -- deleting a client object command when the | |
2104 | * client is active is a FATAL error that cannot be reported through the | |
2105 | * Tcl interpreter. | |
2106 | * | |
2107 | * This procedure does all the cleanup necessary to getting rid of the | |
2108 | * client. | |
2109 | */ | |
2110 | ||
2111 | static void | |
2112 | deleteTcpClientObjectCmd (clientData) | |
2113 | ClientData clientData; | |
2114 | { | |
2115 | register Tcp_ClientData * client = (Tcp_ClientData *) clientData; | |
2116 | register Tcp_ServerData * server = client -> server; | |
2117 | ||
2118 | /* Make sure the client is really dead. */ | |
2119 | ||
2120 | if (client -> activeFlag) { | |
2121 | fprintf (stderr, "attempt to delete an active TCP client!\n\n"); | |
2122 | abort (); | |
2123 | } | |
2124 | ||
2125 | /* Remove any handler for data on the client's socket. */ | |
2126 | ||
2127 | simpleDeleteFileHandler (client -> socketfd); | |
2128 | ||
2129 | /* Now it's safe to close the socket */ | |
2130 | ||
2131 | (void) close (client -> socketfd); | |
2132 | ||
2133 | /* Get rid of the command */ | |
2134 | ||
2135 | if (client -> command != NULL && client -> freeCommand != NULL) { | |
2136 | (*(client -> freeCommand)) (client -> command); | |
2137 | } | |
2138 | ||
2139 | /* Get rid of the input buffer */ | |
2140 | ||
2141 | Tcl_DeleteCmdBuf (client -> inputBuffer); | |
2142 | ||
2143 | /* Get rid of any pending result */ | |
2144 | ||
2145 | if (client -> resultString != NULL && client -> freeResultString != NULL) { | |
2146 | (*(client -> freeResultString)) (client -> resultString); | |
2147 | } | |
2148 | ||
2149 | /* Unlink the client from the list of active clients */ | |
2150 | ||
2151 | if (client -> prev == NULL) | |
2152 | client -> server -> firstClient = client -> next; | |
2153 | else | |
2154 | client -> prev -> next = client -> next; | |
2155 | ||
2156 | if (client -> next != NULL) | |
2157 | client -> next -> prev = client -> prev; | |
2158 | ||
2159 | /* Now it's ok to destroy the client's data structure */ | |
2160 | ||
2161 | ckfree ((char *) client); | |
2162 | ||
2163 | /* Handle a deferred close on the server if necessary */ | |
2164 | ||
2165 | if (server -> stopFlag && server -> firstClient == NULL) | |
2166 | tcpDeleteServer (server); | |
2167 | } | |
2168 | \f | |
2169 | /* | |
2170 | * tcpConnectionObjectCmd -- | |
2171 | * | |
2172 | * This procedure is invoked to process the object command for a client- | |
2173 | * side connection object. It takes a couple of diferent forms: | |
2174 | * | |
2175 | * $connection close | |
2176 | * Closes the connection. | |
2177 | * $connection send arg ?arg....? | |
2178 | * Catenates the arguments into a Tcl command, and sends them | |
2179 | * to the server. | |
2180 | */ | |
2181 | ||
2182 | static int | |
2183 | tcpConnectionObjectCmd (clientData, interp, argc, argv) | |
2184 | ClientData clientData; | |
2185 | Tcl_Interp * interp; | |
2186 | int argc; | |
2187 | char * * argv; | |
2188 | { | |
2189 | unsigned length; | |
2190 | int c; | |
2191 | char * arg; | |
2192 | ||
2193 | if (argc < 2) { | |
2194 | Tcl_AppendResult (interp, "wrong # args, should be \"", argv [0], " ", | |
2195 | "command ?args?\"", (char *) NULL); | |
2196 | return TCL_ERROR; | |
2197 | } | |
2198 | ||
2199 | arg = argv [1]; | |
2200 | c = arg [0]; | |
2201 | length = strlen (arg); | |
2202 | ||
2203 | if (c == 'c' && strncmp (arg, "close", length) == 0) { | |
2204 | return tcpConnectionObjectCloseCmd (clientData, interp, argc-1, argv+1); | |
2205 | } | |
2206 | if (c == 's' && strncmp (arg, "send", length) == 0) { | |
2207 | return tcpConnectionObjectSendCmd (clientData, interp, argc-1, argv+1); | |
2208 | } | |
2209 | ||
2210 | Tcl_AppendResult (interp, "unknown command \"", arg, | |
2211 | "\": must be close or send", (char *) NULL); | |
2212 | return TCL_ERROR; | |
2213 | } | |
2214 | \f | |
2215 | /* | |
2216 | * tcpConnectionObjectCloseCmd -- | |
2217 | * | |
2218 | * This procedure is invoked in response to a `close' command on a | |
2219 | * client-side connection object. It closes the socket and deletes the | |
2220 | * object command. | |
2221 | */ | |
2222 | ||
2223 | /* ARGSUSED */ | |
2224 | static int | |
2225 | tcpConnectionObjectCloseCmd (clientData, interp, argc, argv) | |
2226 | ClientData clientData; | |
2227 | Tcl_Interp * interp; | |
2228 | int argc; | |
2229 | char * * argv; | |
2230 | { | |
2231 | if (argc != 1) { | |
2232 | Tcl_AppendResult (interp, "wrong # args, should be \"", argv [-1], " ", | |
2233 | argv [0], "\"", (char *) NULL); | |
2234 | return TCL_ERROR; | |
2235 | } | |
2236 | ||
2237 | Tcl_DeleteCommand (interp, argv [-1]); | |
2238 | return TCL_OK; | |
2239 | } | |
2240 | \f | |
2241 | /* | |
2242 | * tcpConnectionObjectSendCmd -- | |
2243 | * | |
2244 | * This procedure is invoked in response to a `send' command on a client- | |
2245 | * side connection object. It catenates the `send' arguments into a single | |
2246 | * string, presents that string to the server as a command, and returns the | |
2247 | * server's reply. | |
2248 | */ | |
2249 | ||
2250 | static int | |
2251 | tcpConnectionObjectSendCmd (clientData, interp, argc, argv) | |
2252 | ClientData clientData; | |
2253 | Tcl_Interp * interp; | |
2254 | int argc; | |
2255 | char * * argv; | |
2256 | { | |
2257 | char * message; | |
2258 | int f = (int) clientData; | |
2259 | int status; | |
2260 | ||
2261 | if (argc < 2) { | |
2262 | Tcl_AppendResult (interp, "wrong # args, should be \"", argv [-1], " ", | |
2263 | argv [0], " command\"", (char *) NULL); | |
2264 | return TCL_ERROR; | |
2265 | } | |
2266 | ||
2267 | /* Paste together the message */ | |
2268 | ||
2269 | message = Tcl_Merge (argc-1, argv+1); | |
2270 | ||
2271 | /* Send the command to the server */ | |
2272 | ||
2273 | status = tcpSendCmdToServer (interp, f, message); | |
2274 | if (status != TCL_OK) | |
2275 | return status; | |
2276 | ||
2277 | /* Get the server's reply */ | |
2278 | ||
2279 | return tcpReceiveResultFromServer (interp, f); | |
2280 | } | |
2281 | \f | |
2282 | /* | |
2283 | * deleteTcpConnectionObjectCmd -- | |
2284 | * | |
2285 | * This procedure is called when a connection object is to be | |
2286 | * deleted. It just has to close the socket that the object uses. | |
2287 | */ | |
2288 | ||
2289 | static void | |
2290 | deleteTcpConnectionObjectCmd (clientData) | |
2291 | ClientData clientData; | |
2292 | { | |
2293 | int f = (int) clientData; | |
2294 | (void) close (f); | |
2295 | } | |
2296 | \f | |
2297 | /* | |
2298 | * tcpCloseClient -- | |
2299 | * | |
2300 | * This procedure is called when the program is completely done with | |
2301 | * a client object. If the `active' flag is set, there is still a reference | |
2302 | * to the dead client, but we shouldn't have come here in that case. | |
2303 | */ | |
2304 | ||
2305 | static void | |
2306 | tcpCloseClient (client) | |
2307 | Tcp_ClientData * client; | |
2308 | { | |
2309 | if (client -> activeFlag) | |
2310 | abort (); | |
2311 | ||
2312 | /* Deleting the client command is all we need to do -- the delete | |
2313 | * procedure does everything else. | |
2314 | */ | |
2315 | ||
2316 | Tcl_DeleteCommand (client -> server -> interp, client -> name); | |
2317 | } | |
2318 | \f | |
2319 | /* | |
2320 | * tcpServerAcceptConnection -- | |
2321 | * | |
2322 | * This procedure is invoked as a file handler whenever a server's | |
2323 | * socket is ready for `reading' -- i.e., has a connection request | |
2324 | * outstanding. | |
2325 | * | |
2326 | * It calls the `accept' command on the server to create a client. | |
2327 | * If the `accept' is successful, it then calls the `do' | |
2328 | * command on the client. If either call fails, a background error | |
2329 | * is reported. | |
2330 | */ | |
2331 | ||
2332 | /* ARGSUSED */ | |
2333 | static void | |
2334 | tcpServerAcceptConnection (clientData, mask) | |
2335 | ClientData clientData; | |
2336 | int mask; | |
2337 | { | |
2338 | register Tcp_ServerData * server = (Tcp_ServerData *) clientData; | |
2339 | ||
2340 | int status; | |
2341 | ||
2342 | char * clientName; | |
2343 | ||
2344 | /* Accept the connection with `$server accept' */ | |
2345 | ||
2346 | status = Tcl_VarEval (server -> interp, server -> name, " accept", | |
2347 | (char *) NULL); | |
2348 | ||
2349 | /* On success, try to execute the client's command with `$client do' */ | |
2350 | ||
2351 | if (status == TCL_OK) { | |
2352 | clientName = (char *) ckalloc (strlen (server -> interp -> result) + 1); | |
2353 | strcpy (clientName, server -> interp -> result); | |
2354 | status = Tcl_VarEval (server -> interp, clientName, " do", (char *) NULL); | |
2355 | ||
2356 | /* Client may have been closed at this point. Don't refer to it again. */ | |
2357 | } | |
2358 | ||
2359 | if (status != TCL_OK) { | |
2360 | simpleReportBackgroundError (server -> interp); | |
2361 | } | |
2362 | Tcl_ResetResult (server -> interp); | |
2363 | } | |
2364 | \f | |
2365 | /* | |
2366 | * tcpTrustedHost -- | |
2367 | * | |
2368 | * This procedure is invoked whenever the code must determine whether | |
2369 | * a host is trusted. A host is considered trusted only if it is the local | |
2370 | * host. | |
2371 | * | |
2372 | * Results: | |
2373 | * Returns a Boolean value that is TRUE iff the host is trusted. | |
2374 | */ | |
2375 | ||
2376 | /* The HOSTCMP macro is just strcmp, but puts its args on stderr if | |
2377 | * the DEBUG_TRUSTED_HOST flag is #define'd. It's used because this | |
2378 | * code can be a little flaky; if `hostname' returns a name that is | |
2379 | * completely unknown in the database, this macro will trace what happened. | |
2380 | */ | |
2381 | ||
2382 | #ifdef DEBUG_TRUSTED_HOST | |
2383 | #define HOSTCMP( name1, name2 ) \ | |
2384 | (fprintf (stderr, "tcpTrustedHost: comparing %s with %s\n", \ | |
2385 | (name1), (name2)), \ | |
2386 | strcmp ((name1), (name2))) | |
2387 | #else | |
2388 | #define HOSTCMP( name1, name2 ) \ | |
2389 | strcmp ((name1), (name2)) | |
2390 | #endif | |
2391 | ||
2392 | static int | |
2393 | tcpTrustedHost (hostName) | |
2394 | char * hostName; | |
2395 | { | |
2396 | char localName [128]; | |
2397 | struct hostent * hostEnt; | |
2398 | struct in_addr hostAddr; | |
2399 | int unixStatus; | |
2400 | int i; | |
2401 | ||
2402 | /* This procedure really has to do things the hard way. The problem is | |
2403 | * that the hostname() kernel call returns the host name set by the system | |
2404 | * administrator, which may not be the host's primary name as known to | |
2405 | * the domain name system. Furthermore, the host presented may be one | |
2406 | * of the names for the loopback port, 127.0.0.1, and this must be checked, | |
2407 | * too. | |
2408 | */ | |
2409 | ||
2410 | /* Start assembling a list of possibilities for the host name. First | |
2411 | * possibility is the name that the kernel returns as hostname (). | |
2412 | */ | |
2413 | ||
2414 | unixStatus = gethostname (localName, 127); | |
2415 | if (unixStatus >= 0) { | |
2416 | ||
2417 | if (!HOSTCMP( hostName, localName )) return 1; | |
2418 | ||
2419 | /* Next possibility is a.b.c.d notation for all of the local addresses, | |
2420 | * plus all the nicknames for the host. | |
2421 | */ | |
2422 | ||
2423 | hostEnt = gethostbyname (localName); | |
2424 | if (hostEnt != (struct hostent *) NULL) { | |
2425 | if (!HOSTCMP( hostName, hostEnt -> h_name )) return 1; | |
2426 | if (hostEnt -> h_aliases != (char * *) NULL) { | |
2427 | for (i = 0; hostEnt -> h_aliases [i] != (char *) NULL; ++i) { | |
2428 | if (!HOSTCMP( hostName, hostEnt -> h_aliases [i] )) return 1; | |
2429 | } | |
2430 | } | |
2431 | if (hostEnt -> h_addr_list != (char * *) NULL) { | |
2432 | for (i = 0; hostEnt -> h_addr_list [i] != (char *) NULL; ++i) { | |
2433 | /* note that the address doesn't have to be word-aligned (!) */ | |
2434 | memcpy ((char *) &hostAddr, | |
2435 | hostEnt -> h_addr_list [i], | |
2436 | hostEnt -> h_length); | |
2437 | if (!HOSTCMP( hostName, inet_ntoa (hostAddr) )) return 1; | |
2438 | } | |
2439 | } | |
2440 | } | |
2441 | } | |
2442 | ||
2443 | /* Finally, there's the possibility of the loopback address, and all of | |
2444 | * its aliases.*/ | |
2445 | ||
2446 | if (!HOSTCMP( hostName, "0.0.0.0" )) return 1; | |
2447 | if (!HOSTCMP( hostName, "127.0.0.1" )) return 1; | |
2448 | hostAddr.s_addr = htonl (INADDR_LOOPBACK); | |
2449 | hostEnt = gethostbyaddr ((char *) &hostAddr, sizeof hostAddr, AF_INET); | |
2450 | if (hostEnt != (struct hostent *) NULL) { | |
2451 | if (!HOSTCMP( hostName, hostEnt -> h_name )) return 1; | |
2452 | if (hostEnt -> h_aliases != (char * *) NULL) { | |
2453 | for (i = 0; hostEnt -> h_aliases [i] != (char *) NULL; ++i) { | |
2454 | if (!HOSTCMP( hostName, hostEnt -> h_aliases [i] )) return 1; | |
2455 | } | |
2456 | } | |
2457 | if (hostEnt -> h_addr_list != (char * *) NULL) { | |
2458 | for (i = 0; hostEnt -> h_addr_list [i] != (char *) NULL; ++i) { | |
2459 | /* note that the address doesn't have to be word-aligned (!) */ | |
2460 | memcpy ((char *) &hostAddr, | |
2461 | hostEnt -> h_addr_list [i], | |
2462 | hostEnt -> h_length); | |
2463 | if (!HOSTCMP( hostName, inet_ntoa (hostAddr) )) return 1; | |
2464 | } | |
2465 | } | |
2466 | } | |
2467 | ||
2468 | return 0; | |
2469 | } | |
2470 | \f | |
2471 | /* | |
2472 | * tcpReturnResultToClient -- | |
2473 | * | |
2474 | * This procedure is invoked to return a result to a client. It | |
2475 | * extracts the interpreter's result string, bundles it with the return | |
2476 | * status, and stores it in the client's `resultString' area. | |
2477 | * | |
2478 | * It then calls tcpWriteResultToClient to try to start sending the | |
2479 | * result. | |
2480 | */ | |
2481 | ||
2482 | static void | |
2483 | tcpReturnResultToClient (client, interp, status, closeflag) | |
2484 | Tcp_ClientData * client; | |
2485 | Tcl_Interp * interp; | |
2486 | int status; | |
2487 | int closeflag; | |
2488 | { | |
2489 | char * argv [2]; | |
2490 | char rint [16]; | |
2491 | unsigned length; | |
2492 | char * result; | |
2493 | ||
2494 | /* Put together a message comprising the return status and the interpreter | |
2495 | * result */ | |
2496 | ||
2497 | sprintf (rint, "%d", status); | |
2498 | argv [0] = rint; | |
2499 | argv [1] = interp -> result; | |
2500 | result = Tcl_Merge (2, argv); | |
2501 | length = strlen (result); | |
2502 | client -> resultString = (char *) malloc (length + 2); | |
2503 | strcpy (client -> resultString, result); | |
2504 | strcpy (client -> resultString + length, "\n"); | |
2505 | free (result); | |
2506 | client -> resultPointer = client -> resultString; | |
2507 | client -> freeResultString = (Tcl_FreeProc *) free; | |
2508 | ||
2509 | Tcl_ResetResult (interp); | |
2510 | client -> closeFlag |= closeflag; | |
2511 | ||
2512 | /* Now try to send the reply. */ | |
2513 | ||
2514 | tcpWriteResultToClient ((ClientData) client, TK_WRITABLE); | |
2515 | ||
2516 | /* tcpWriteResultToClient closes the client if it fails; don't depend on | |
2517 | * having the client still be usable. */ | |
2518 | } | |
2519 | \f | |
2520 | /* | |
2521 | * tcpWriteResultToClient -- | |
2522 | * | |
2523 | * This procedure is invoked to issue a write on a client socket. | |
2524 | * It can be called directly by tcpReturnResultToClient, to attempt the | |
2525 | * initial write of results. It can also be called as a file handler, | |
2526 | * to retry a write that was previously blocked. | |
2527 | */ | |
2528 | ||
2529 | /* ARGSUSED */ | |
2530 | static void | |
2531 | tcpWriteResultToClient (clientData, mask) | |
2532 | ClientData clientData; | |
2533 | int mask; | |
2534 | { | |
2535 | register Tcp_ClientData * client = (Tcp_ClientData *) clientData; | |
2536 | ||
2537 | int unixStatus; | |
2538 | int length; | |
2539 | ||
2540 | length = strlen (client -> resultPointer); | |
2541 | ||
2542 | /* Issue the write */ | |
2543 | ||
2544 | unixStatus = write (client -> socketfd, client -> resultPointer, | |
2545 | length); | |
2546 | ||
2547 | /* Test for a total failure */ | |
2548 | ||
2549 | if (unixStatus < 0) { | |
2550 | if (errno != EWOULDBLOCK) { | |
2551 | tcpClientWriteError (client); | |
2552 | /* tcpClientWriteError closes the client as a side effect. Don't depend | |
2553 | * on the client still being there. | |
2554 | */ | |
2555 | return; | |
2556 | } else { | |
2557 | unixStatus = 0; /* Pretend that EWOULDBLOCK succeeded at | |
2558 | * writing zero characters. */ | |
2559 | } | |
2560 | } | |
2561 | ||
2562 | /* Test for a partial success */ | |
2563 | ||
2564 | if (unixStatus < length) { | |
2565 | client -> resultPointer += unixStatus; | |
2566 | simpleCreateFileHandler (client -> socketfd, TK_WRITABLE, | |
2567 | (Tk_FileProc *) tcpWriteResultToClient, | |
2568 | clientData); | |
2569 | } | |
2570 | ||
2571 | /* Total success -- prepare the client for the next input */ | |
2572 | ||
2573 | else { | |
2574 | if (client -> freeResultString != NULL) { | |
2575 | (*(client -> freeResultString)) (client -> resultString); | |
2576 | } | |
2577 | client -> resultString = client -> resultPointer = (char *) NULL; | |
2578 | client -> freeResultString = (Tcl_FreeProc *) NULL; | |
2579 | simpleDeleteFileHandler (client -> socketfd); | |
2580 | if (client -> closeFlag) { | |
2581 | tcpCloseClient (client); | |
2582 | ||
2583 | /* After tcpCloseClient executes, the client goes away. Don't depend | |
2584 | on it's still being there. */ | |
2585 | ||
2586 | } else { | |
2587 | tcpPrepareClientForInput (client); | |
2588 | } | |
2589 | } | |
2590 | } | |
2591 | \f | |
2592 | /* | |
2593 | * tcpPrepareClientForInput -- | |
2594 | * | |
2595 | * This procedure is invoked to prepare a client to accept command | |
2596 | * input. It establishes a handler, tcpReceiveClientInput, that does the | |
2597 | * actual command buffering. | |
2598 | */ | |
2599 | ||
2600 | static void | |
2601 | tcpPrepareClientForInput (client) | |
2602 | Tcp_ClientData * client; | |
2603 | { | |
2604 | simpleCreateFileHandler (client -> socketfd, TK_READABLE, | |
2605 | (Tk_FileProc *) tcpReceiveClientInput, | |
2606 | (ClientData) client); | |
2607 | } | |
2608 | \f | |
2609 | /* | |
2610 | * tcpReceiveClientInput -- | |
2611 | * | |
2612 | * This procedure is called when a server is awaiting input from a client | |
2613 | * and the client socket tests to be `ready to read'. It reads a bufferload | |
2614 | * of data from the client, and places it in the client's command buffer. If | |
2615 | * the command is complete, it then tries to invoke the command. | |
2616 | */ | |
2617 | ||
2618 | /* ARGSUSED */ | |
2619 | static void | |
2620 | tcpReceiveClientInput (clientData, mask) | |
2621 | ClientData clientData; | |
2622 | int mask; | |
2623 | { | |
2624 | register Tcp_ClientData * client = (Tcp_ClientData *) clientData; | |
2625 | register Tcp_ServerData * server = client -> server; | |
2626 | register Tcl_Interp * interp = server -> interp; | |
2627 | ||
2628 | static char buffer [BUFSIZ+1]; | |
2629 | int unixStatus; | |
2630 | char * command; | |
2631 | int status; | |
2632 | char * docmd; | |
2633 | char * argv [3]; | |
2634 | int argc; | |
2635 | int i; | |
2636 | ||
2637 | /* Try to read from the client */ | |
2638 | ||
2639 | errno = 0; | |
2640 | unixStatus = read (client -> socketfd, buffer, BUFSIZ); | |
2641 | if (unixStatus <= 0 && errno != EWOULDBLOCK) | |
2642 | tcpClientReadError (client); | |
2643 | ||
2644 | /* tcpClientReadError closes the client and reports the error. | |
2645 | In any case, if the read failed, we want to return. */ | |
2646 | ||
2647 | if (unixStatus <= 0) | |
2648 | return; | |
2649 | ||
2650 | if (server -> raw) { | |
2651 | char buf[512]; | |
2652 | ||
2653 | sprintf(buf, "RawInput %s %d %d", client -> name, buffer, unixStatus); | |
2654 | printf("TCP executing: %s\n", buf); | |
2655 | status = Tcl_Eval (interp, buf, 0, (char * *) NULL); | |
2656 | ||
2657 | tcpPrepareClientForInput (client); | |
2658 | ||
2659 | } else { | |
2660 | ||
2661 | /* Assemble the received data into the buffer */ | |
2662 | ||
2663 | buffer [unixStatus] = '\0'; | |
2664 | command = Tcl_AssembleCmd (client -> inputBuffer, buffer); | |
2665 | if (command != (char *) NULL) { | |
2666 | ||
2667 | /* Process the received command. */ | |
2668 | ||
2669 | simpleDeleteFileHandler (client -> socketfd); | |
2670 | argc = 3; | |
2671 | argv [0] = client -> name; | |
2672 | argv [1] = "do"; | |
2673 | argv [2] = command; | |
2674 | docmd = Tcl_Merge (argc, argv); | |
2675 | status = Tcl_Eval (interp, docmd, 0, (char * *) NULL); | |
2676 | free (docmd); | |
2677 | ||
2678 | /* At this point, the client may have been closed. Don't try to | |
2679 | refer to it. */ | |
2680 | ||
2681 | if (status != TCL_OK) { | |
2682 | simpleReportBackgroundError (interp); | |
2683 | } | |
2684 | } | |
2685 | } | |
2686 | } | |
2687 | \f | |
2688 | /* tcpClientReadError -- | |
2689 | * | |
2690 | * This procedure is called when an attempt to read the command from a | |
2691 | * client fails. There are two possibilities: | |
2692 | * | |
2693 | * The first is that there really was a read error, originating in the | |
2694 | * socket system. In this case, the error should be reported at background | |
2695 | * level, and the client should be closed. | |
2696 | * | |
2697 | * The second is that the read reached the end-of-information marker in | |
2698 | * the client's stream. In this case, the `do' command should be called on | |
2699 | * the client one last time, and then the client should be closed. | |
2700 | * | |
2701 | * If the application needs to clean the client up after a read error, | |
2702 | * it must define the `tcperror' procedure and process the error. | |
2703 | */ | |
2704 | ||
2705 | static void | |
2706 | tcpClientReadError (client) | |
2707 | Tcp_ClientData * client; | |
2708 | { | |
2709 | Tcp_ServerData * server = client -> server; | |
2710 | Tcl_Interp * interp = server -> interp; | |
2711 | int status; | |
2712 | ||
2713 | if (errno != 0) { | |
2714 | ||
2715 | /* Read error */ | |
2716 | ||
2717 | status = Tcl_VarEval (interp, "error {", client -> name, ": read error: ", | |
2718 | Tcl_UnixError (interp), "}", (char *) NULL); | |
2719 | simpleReportBackgroundError (interp); | |
2720 | ||
2721 | } else { | |
2722 | ||
2723 | /* End of file */ | |
2724 | ||
2725 | status = Tcl_VarEval (interp, client -> name, " do", (char *) NULL); | |
2726 | if (status != TCL_OK) | |
2727 | simpleReportBackgroundError (interp); | |
2728 | } | |
2729 | ||
2730 | tcpCloseClient (client); | |
2731 | } | |
2732 | \f | |
2733 | /* tcpClientWriteError -- | |
2734 | * | |
2735 | * This procedure is invoked when an attempt to return results to a client | |
2736 | * has failed. It reports the error at background level and closes the client. | |
2737 | * | |
2738 | * If the application needs to clean up the client after a write error, | |
2739 | * it must define the `tcperror' procedure to catch the error. | |
2740 | */ | |
2741 | ||
2742 | static void | |
2743 | tcpClientWriteError (client) | |
2744 | Tcp_ClientData * client; | |
2745 | { | |
2746 | Tcp_ServerData * server = client -> server; | |
2747 | Tcl_Interp * interp = server -> interp; | |
2748 | ||
2749 | (void) Tcl_VarEval (interp, "error {", client -> name, ": read error: ", | |
2750 | Tcl_UnixError (interp), "}", (char *) NULL); | |
2751 | simpleReportBackgroundError (interp); | |
2752 | tcpCloseClient (client); | |
2753 | } | |
2754 | \f | |
2755 | /* tcpSendCmdToServer -- | |
2756 | * | |
2757 | * This procedure is invoked to send a command originated by a client | |
2758 | * using the `$connection send' Tcl command. | |
2759 | * | |
2760 | * The message is passed without a newline appended. The server requires | |
2761 | * a newline, which is sent in a separate call. | |
2762 | */ | |
2763 | ||
2764 | static int | |
2765 | tcpSendCmdToServer (interp, s, message) | |
2766 | Tcl_Interp * interp; | |
2767 | int s; | |
2768 | char * message; | |
2769 | { | |
2770 | int length; | |
2771 | int unixStatus; | |
2772 | int rubbish; | |
2773 | static char newline = '\n'; | |
2774 | void (*oldPipeHandler) (); | |
2775 | ||
2776 | /* Set the socket for blocking I/O */ | |
2777 | ||
2778 | rubbish = 0; | |
2779 | unixStatus = ioctl (s, FIONBIO, (char *) &rubbish); | |
2780 | if (unixStatus < 0) { | |
2781 | Tcl_AppendResult (interp, "can't set blocking I/O on socket: ", | |
2782 | Tcl_UnixError (interp), (char *) NULL); | |
2783 | return TCL_ERROR; | |
2784 | } | |
2785 | ||
2786 | /* Keep a possible broken pipe from killing us silently */ | |
2787 | ||
2788 | oldPipeHandler = signal (SIGPIPE, SIG_IGN); | |
2789 | ||
2790 | /* Write the message */ | |
2791 | ||
2792 | length = strlen (message); | |
2793 | unixStatus = write (s, message, length); | |
2794 | if (unixStatus < length) { | |
2795 | (void) signal (SIGPIPE, oldPipeHandler); | |
2796 | Tcl_AppendResult (interp, "can't send message to server: ", | |
2797 | Tcl_UnixError (interp), (char *) NULL); | |
2798 | return TCL_ERROR; | |
2799 | } | |
2800 | ||
2801 | /* Write the terminating newline */ | |
2802 | ||
2803 | unixStatus = write (s, &newline, 1); | |
2804 | if (unixStatus < 1) { | |
2805 | (void) signal (SIGPIPE, oldPipeHandler); | |
2806 | Tcl_AppendResult (interp, "can't send newline to server: ", | |
2807 | Tcl_UnixError (interp), (char *) NULL); | |
2808 | return TCL_ERROR; | |
2809 | } | |
2810 | ||
2811 | (void) signal (SIGPIPE, oldPipeHandler); | |
2812 | return TCL_OK; | |
2813 | } | |
2814 | \f | |
2815 | /* | |
2816 | * tcpReceiveResultFromServer -- | |
2817 | * | |
2818 | * This procedure is invoked to get the result transmitted from | |
2819 | * a remote server, either on establishing the connection or on processing | |
2820 | * a command. It returns a standard Tcl result that is usually the result | |
2821 | * returned by the server. | |
2822 | */ | |
2823 | ||
2824 | static int | |
2825 | tcpReceiveResultFromServer (interp, s) | |
2826 | Tcl_Interp * interp; | |
2827 | int s; | |
2828 | { | |
2829 | int status; | |
2830 | int unixStatus; | |
2831 | int junk; | |
2832 | Tcl_CmdBuf cmdbuf; | |
2833 | struct timeval tick; | |
2834 | struct timeval * tickp; | |
2835 | fd_set readfds; | |
2836 | char buf [BUFSIZ+1]; | |
2837 | char * reply; | |
2838 | int rargc; | |
2839 | char * * rargv; | |
2840 | int rstatus; | |
2841 | ||
2842 | /* Read the result using non-blocking I/O */ | |
2843 | ||
2844 | junk = 1; | |
2845 | unixStatus = ioctl (s, FIONBIO, (char *) &junk); | |
2846 | if (unixStatus < 0) { | |
2847 | Tcl_AppendResult (interp, "can't set nonblocking I/O on socket: ", | |
2848 | Tcl_UnixError (interp), (char *) NULL); | |
2849 | return TCL_ERROR; | |
2850 | } | |
2851 | ||
2852 | /* Make a buffer to receive the result */ | |
2853 | ||
2854 | cmdbuf = Tcl_CreateCmdBuf (); | |
2855 | ||
2856 | /* Wait for the result to appear */ | |
2857 | ||
2858 | tickp = (struct timeval *) 0; | |
2859 | FD_ZERO( &readfds ); | |
2860 | FD_SET( s, &readfds ); | |
2861 | for ( ; ; ) { | |
2862 | ||
2863 | unixStatus = select (s + 1, &readfds, (fd_set *) NULL, (fd_set *) NULL, | |
2864 | tickp); | |
2865 | ||
2866 | if (unixStatus < 0) { | |
2867 | status = TCL_ERROR; | |
2868 | Tcl_AppendResult (interp, "error selecting socket for reply: ", | |
2869 | Tcl_UnixError (interp), (char *) NULL); | |
2870 | break; | |
2871 | } | |
2872 | ||
2873 | if (unixStatus == 0) { | |
2874 | status = TCL_ERROR; | |
2875 | Tcl_SetResult (interp, "timed out waiting for server reply", TCL_STATIC); | |
2876 | break; | |
2877 | } | |
2878 | ||
2879 | /* Read the result */ | |
2880 | ||
2881 | unixStatus = read (s, buf, BUFSIZ); | |
2882 | ||
2883 | if (unixStatus < 0) { | |
2884 | status = TCL_ERROR; | |
2885 | Tcl_AppendResult (interp, "error reading server reply: ", | |
2886 | Tcl_UnixError (interp), (char *) NULL); | |
2887 | break; | |
2888 | } | |
2889 | ||
2890 | if (unixStatus == 0) { | |
2891 | status = TCL_ERROR; | |
2892 | Tcl_SetResult (interp, "Connection closed.", TCL_STATIC); | |
2893 | break; | |
2894 | } | |
2895 | ||
2896 | /* Parse the (partial) command */ | |
2897 | ||
2898 | buf [unixStatus] = '\0'; | |
2899 | reply = Tcl_AssembleCmd (cmdbuf, buf); | |
2900 | if (reply != NULL) { | |
2901 | status = TCL_OK; | |
2902 | break; | |
2903 | } | |
2904 | ||
2905 | /* Partial command not yet complete. Set timeout for reading the | |
2906 | * rest of the result. */ | |
2907 | ||
2908 | tick.tv_sec = 30; | |
2909 | tick.tv_usec = 0; | |
2910 | tickp = &tick; | |
2911 | } | |
2912 | ||
2913 | /* When we come here, either the status is TCL_ERROR and the error | |
2914 | * message is already set, or else the status is TCL_OK and `reply' | |
2915 | * contains the result that we have to return. The first element of | |
2916 | * `reply' has the status, and the second has the result string. */ | |
2917 | ||
2918 | /* Split the list elements */ | |
2919 | ||
2920 | if (status == TCL_OK) { | |
2921 | status = Tcl_SplitList (interp, reply, &rargc, &rargv); | |
2922 | if (status != TCL_OK) { | |
2923 | Tcl_SetResult (interp, "server returned malformed list", TCL_STATIC); | |
2924 | status = TCL_ERROR; | |
2925 | } | |
2926 | } | |
2927 | ||
2928 | /* Verify the element count */ | |
2929 | ||
2930 | if (status == TCL_OK) { | |
2931 | if (rargc != 2) { | |
2932 | Tcl_SetResult (interp, "server returned malformed list", TCL_STATIC); | |
2933 | status = TCL_ERROR; | |
2934 | free ((char *) rargv); | |
2935 | } else { | |
2936 | status = Tcl_GetInt (interp, rargv [0], &rstatus); | |
2937 | if (status != TCL_OK) { | |
2938 | Tcl_SetResult (interp, "server returned unrecognizable status", | |
2939 | TCL_STATIC); | |
2940 | status = TCL_ERROR; | |
2941 | free ((char *) rargv); | |
2942 | } | |
2943 | } | |
2944 | } | |
2945 | ||
2946 | /* Return the result reported by the server */ | |
2947 | ||
2948 | if (status == TCL_OK) { | |
2949 | Tcl_SetResult (interp, rargv [1], TCL_VOLATILE); | |
2950 | status = rstatus; | |
2951 | free ((char *) rargv); | |
2952 | } | |
2953 | ||
2954 | Tcl_DeleteCmdBuf (cmdbuf); | |
2955 | return status; | |
2956 | } |