2 * WINGs server.c: example how to create a network server using WMConnection
4 * Copyright (c) 2001-2003 Dan Pascu
14 #include <WINGs/WINGs.h>
18 #define MAXCMD_SIZE 512
21 char *SEConnectionShouldBeRemovedNotification
= "SEConnectionShouldBeRemovedNotification";
26 static void didReceiveInput(ConnectionDelegate
*self
, WMConnection
*cPtr
);
28 static void connectionDidDie(ConnectionDelegate
*self
, WMConnection
*cPtr
);
30 static void connectionDidTimeout(ConnectionDelegate
*self
, WMConnection
*cPtr
);
33 extern char *SEConnectionShouldBeRemovedNotification
;
35 static WMUserDefaults
*timeDB
= NULL
;
36 static char *ServerAddress
= NULL
;
37 static char *ServerPort
= NULL
;
38 static WMArray
*allowedHostList
= NULL
;
39 static WMArray
*clientConnections
= NULL
;
40 static WMConnection
*serverPtr
= NULL
;
44 static ConnectionDelegate socketDelegate
= {
45 NULL
, /* client data */
46 NULL
, /* canResumeSending */
47 NULL
, /* didCatchException */
48 connectionDidDie
, /* didDie */
49 NULL
, /* didInitialize */
50 didReceiveInput
, /* didReceiveInput */
51 connectionDidTimeout
/* didTimeout */
64 printHelp(char *progname
)
66 printf(_("usage: %s [options]\n\n"), progname
);
67 puts(_(" --help print this message"));
68 puts(_(" --listen [address:]port only listen on the specified address/port"));
69 puts(_(" --allow host1[,host2...] only allow connections from listed hosts\n"));
70 puts(_(" By default server listens on all interfaces and port 34567, unless"
71 " something\nelse is specified with the --listen option. If address is"
72 " omitted or the keyword\n'Any' is used, it will listen on all interfaces else"
73 " only on the specified one.\n\nFor example --listen localhost: will"
74 " listen on the default port 34567, but only\non connections comming"
75 " in through the loopback interface.\n\n Also by default the server"
76 " listens to incoming connections from any host,\nunless a list of"
77 " hosts is given with the --allow option, in which case it will\nreject"
78 " connections not comming from those hosts.\nThe list of hosts is comma"
79 " separated and should NOT contain ANY spaces."));
84 enqueueConnectionForRemoval(WMConnection
*cPtr
)
86 WMNotification
*notif
;
88 /*don't release notif here. it will be released by queue after processing */
89 notif
= WMCreateNotification(SEConnectionShouldBeRemovedNotification
,
91 WMEnqueueNotification(WMGetDefaultNotificationQueue(), notif
, WMPostASAP
);
96 sendMessage(WMConnection
*cPtr
, char *message
)
101 if (WMGetConnectionState(cPtr
)!=WCConnected
)
104 aData
= WMCreateDataWithBytes(message
, strlen(message
));
105 res
= WMSendConnectionData(cPtr
, aData
);
106 WMReleaseData(aData
);
113 enqueueMessage(WMConnection
*cPtr
, char *message
)
118 if (WMGetConnectionState(cPtr
)!=WCConnected
)
121 aData
= WMCreateDataWithBytes(message
, strlen(message
));
122 res
= WMEnqueueConnectionData(cPtr
, aData
);
123 WMReleaseData(aData
);
130 findDelimiter(char *data
, const char *endPtr
)
132 wassertrv(data
< endPtr
, NULL
);
134 while (data
<endPtr
&& *data
!='\n' && *data
!='\r' && *data
!=';' && *data
!='\0')
145 getAvailableMessages(WMConnection
*cPtr
)
147 char *ptr
, *crtPos
, *buffer
;
148 const char *bytes
, *endPtr
;
149 WMData
*aData
, *receivedData
, *holdData
;
154 receivedData
= WMGetConnectionAvailableData(cPtr
);
157 if ((length
=WMGetDataLength(receivedData
))==0) {
158 WMReleaseData(receivedData
);
162 holdData
= (WMData
*)WMGetConnectionClientData(cPtr
);
164 WMAppendData(holdData
, receivedData
);
165 WMReleaseData(receivedData
);
166 WMSetConnectionClientData(cPtr
, NULL
);
169 aData
= receivedData
;
172 length
= WMGetDataLength(aData
);
173 bytes
= (char*)WMDataBytes(aData
);
174 endPtr
= bytes
+ length
;
176 messages
= WMCreateArrayWithDestructor(1, wfree
);
177 crtPos
= (char*)bytes
;
178 while (crtPos
<endPtr
&& (ptr
= findDelimiter(crtPos
, endPtr
))!=NULL
) {
179 range
.position
= (crtPos
- bytes
);
180 range
.count
= (ptr
- crtPos
);
181 if (range
.count
> MAXCMD_SIZE
) {
182 /* Hmmm... The message is too long. Possibly that someone is
183 * flooding us, or there is a dumb client which do not know
184 * who is talking to. */
185 sendMessage(cPtr
, "Command too long\n\r");
186 WMFreeArray(messages
);
187 WMReleaseData(aData
);
188 WMCloseConnection(cPtr
);
189 enqueueConnectionForRemoval(cPtr
);
192 buffer
= wmalloc(range
.count
+1);
193 WMGetDataBytesWithRange(aData
, buffer
, range
);
194 buffer
[range
.count
] = '\0';
195 WMAddToArray(messages
, buffer
);
197 while (crtPos
<endPtr
&& (*crtPos
=='\n' || *crtPos
=='\r' ||
198 *crtPos
=='\t' || *crtPos
=='\0' ||
199 *crtPos
==';' || *crtPos
==' ')) {
205 range
.position
= (crtPos
- bytes
);
206 range
.count
= (endPtr
- crtPos
);
207 if (range
.count
> MAXCMD_SIZE
) {
208 /* Flooooooding!!!! */
209 sendMessage(cPtr
, "Message too long\n\r");
210 WMFreeArray(messages
);
211 WMReleaseData(aData
);
212 WMCloseConnection(cPtr
);
213 enqueueConnectionForRemoval(cPtr
);
216 holdData
= WMGetSubdataWithRange(aData
, range
);
217 WMSetConnectionClientData(cPtr
, holdData
);
219 WMReleaseData(aData
);
221 if (WMGetArrayItemCount(messages
)==0) {
222 WMFreeArray(messages
);
231 complainAboutBadArgs(WMConnection
*cPtr
, char *cmdName
, char *badArgs
)
233 char *buf
= wmalloc(strlen(cmdName
) + strlen(badArgs
) + 100);
235 sprintf(buf
, _("Invalid parameters '%s' for command %s. Use HELP for"
236 " a list of commands.\n"), badArgs
, cmdName
);
237 sendMessage(cPtr
, buf
);
243 sendUpdateMessage(WMConnection
*cPtr
, char *id
, int time
)
245 char *buf
= wmalloc(strlen(id
) + 100);
247 sprintf(buf
, "%s has %i minutes left\n", id
, time
);
248 sendMessage(cPtr
, buf
);
254 showId(WMConnection
*cPtr
)
256 sendMessage(cPtr
, "Server example based on WMConnection\n");
261 showHelp(WMConnection
*cPtr
)
263 char *buf
= wmalloc(strlen(WMGetApplicationName()) + 16);
265 sprintf(buf
, _("%s commands:\n\n"), WMGetApplicationName());
267 enqueueMessage(cPtr
, _("\n"));
268 enqueueMessage(cPtr
, buf
);
269 enqueueMessage(cPtr
, _("GET <id>\t- return time left (in minutes) "
270 "for user with id <id>\n"));
271 enqueueMessage(cPtr
, _("SET <id> <time>\t- set time limit to <time> "
272 "minutes for user with id <id>\n"));
273 enqueueMessage(cPtr
, _("ADD <id> <time>\t- add <time> minutes "
274 "for user with id <id>\n"));
275 enqueueMessage(cPtr
, _("SUB <id> <time>\t- subtract <time> minutes "
276 "for user with id <id>\n"));
277 enqueueMessage(cPtr
, _("REMOVE <id>\t- remove time limitations for "
278 "user with id <id>\n"));
279 enqueueMessage(cPtr
, _("LIST\t\t- list all users and their "
280 "corresponding time limit\n"));
281 enqueueMessage(cPtr
, _("ID\t\t- returns the Time Manager "
282 "identification string\n"));
283 enqueueMessage(cPtr
, _("EXIT\t\t- exits session\n"));
284 enqueueMessage(cPtr
, _("QUIT\t\t- exits session\n"));
285 enqueueMessage(cPtr
, _("HELP\t\t- show this message\n\n"));
286 /* Just flush the queue we made before */
287 WMFlushConnection(cPtr
);
293 listUsers(WMConnection
*cPtr
)
295 WMPropList
*userList
;
299 userList
= WMGetUDKeys(timeDB
);
301 for (i
=0; i
<WMGetPropListItemCount(userList
); i
++) {
302 id
= WMGetFromPLString(WMGetFromPLArray(userList
, i
));
303 time
= WMGetUDIntegerForKey(timeDB
, id
);
304 sendUpdateMessage(cPtr
, id
, time
);
307 WMReleasePropList(userList
);
312 setTimeForUser(WMConnection
*cPtr
, char *cmdArgs
)
317 id
= wmalloc(strlen(cmdArgs
));
318 if (sscanf(cmdArgs
, "%s %d", id
, &time
)!=2) {
319 complainAboutBadArgs(cPtr
, "SET", cmdArgs
);
326 WMSetUDIntegerForKey(timeDB
, time
, id
);
328 for (i
=0; i
<WMGetArrayItemCount(clientConnections
); i
++) {
329 cPtr
= WMGetFromArray(clientConnections
, i
);
330 sendUpdateMessage(cPtr
, id
, time
);
337 addTimeToUser(WMConnection
*cPtr
, char *cmdArgs
)
340 int i
, time
, newTime
;
342 id
= wmalloc(strlen(cmdArgs
));
343 if (sscanf(cmdArgs
, "%s %d", id
, &time
)!=2) {
344 complainAboutBadArgs(cPtr
, "ADD", cmdArgs
);
349 newTime
= WMGetUDIntegerForKey(timeDB
, id
) + time
;
353 WMSetUDIntegerForKey(timeDB
, newTime
, id
);
355 for (i
=0; i
<WMGetArrayItemCount(clientConnections
); i
++) {
356 cPtr
= WMGetFromArray(clientConnections
, i
);
357 sendUpdateMessage(cPtr
, id
, newTime
);
364 subTimeFromUser(WMConnection
*cPtr
, char *cmdArgs
)
367 int i
, time
, newTime
;
369 id
= wmalloc(strlen(cmdArgs
));
370 if (sscanf(cmdArgs
, "%s %d", id
, &time
)!=2) {
371 complainAboutBadArgs(cPtr
, "SUB", cmdArgs
);
376 newTime
= WMGetUDIntegerForKey(timeDB
, id
) - time
;
380 WMSetUDIntegerForKey(timeDB
, newTime
, id
);
382 for (i
=0; i
<WMGetArrayItemCount(clientConnections
); i
++) {
383 cPtr
= WMGetFromArray(clientConnections
, i
);
384 sendUpdateMessage(cPtr
, id
, newTime
);
391 removeTimeForUser(WMConnection
*cPtr
, char *cmdArgs
)
396 if (cmdArgs
[0]=='\0') {
397 sendMessage(cPtr
, _("Missing parameter for command REMOVE."
398 " Use HELP for a list of commands.\n"));
403 while (*ptr
&& *ptr
!=' ' && *ptr
!='\t')
407 WMRemoveUDObjectForKey(timeDB
, cmdArgs
);
409 for (i
=0; i
<WMGetArrayItemCount(clientConnections
); i
++) {
410 cPtr
= WMGetFromArray(clientConnections
, i
);
411 sendUpdateMessage(cPtr
, cmdArgs
, -1);
417 getTimeForUser(WMConnection
*cPtr
, char *cmdArgs
)
422 if (cmdArgs
[0]=='\0') {
423 sendMessage(cPtr
, _("Missing parameter for command GET."
424 " Use HELP for a list of commands.\n"));
429 while (*ptr
&& *ptr
!=' ' && *ptr
!='\t')
433 if (WMGetUDObjectForKey(timeDB
, cmdArgs
)!=NULL
)
434 time
= WMGetUDIntegerForKey(timeDB
, cmdArgs
);
438 sendUpdateMessage(cPtr
, cmdArgs
, time
);
443 handleConnection(WMConnection
*cPtr
)
445 char *command
, *ptr
, *cmdArgs
, *buffer
;
449 commands
= getAvailableMessages(cPtr
);
453 for (i
=0; i
<WMGetArrayItemCount(commands
); i
++) {
454 command
= WMGetFromArray(commands
, i
);
455 while (*command
&& (*command
==' ' || *command
=='\t'))
458 while(*ptr
&& *ptr
!=' ' && *ptr
!='\t')
464 while (*ptr
&& (*ptr
==' ' || *ptr
=='\t'))
469 fprintf(stderr
, "Command: '%s', args: '%s'\n", command
, cmdArgs
);
471 if (strcasecmp(command
, "quit")==0 || strcasecmp(command
, "exit")==0) {
472 sendMessage(cPtr
, "Bye\n");
473 WMCloseConnection(cPtr
);
474 enqueueConnectionForRemoval(cPtr
);
475 WMFreeArray(commands
);
477 } else if (strcasecmp(command
, "id")==0) {
479 } else if (strcasecmp(command
, "help")==0) {
481 } else if (strcasecmp(command
, "list")==0) {
483 } else if (strcasecmp(command
, "set")==0) {
484 setTimeForUser(cPtr
, cmdArgs
);
485 } else if (strcasecmp(command
, "add")==0) {
486 addTimeToUser(cPtr
, cmdArgs
);
487 } else if (strcasecmp(command
, "sub")==0) {
488 subTimeFromUser(cPtr
, cmdArgs
);
489 } else if (strcasecmp(command
, "remove")==0) {
490 removeTimeForUser(cPtr
, cmdArgs
);
491 } else if (strcasecmp(command
, "get")==0) {
492 getTimeForUser(cPtr
, cmdArgs
);
494 buffer
= wmalloc(strlen(command
) + 100);
495 sprintf(buffer
, _("Unknown command '%s'. Try HELP for"
496 " a list of commands.\n"), command
);
497 sendMessage(cPtr
, buffer
);
502 WMFreeArray(commands
);
507 isAllowedToConnect(WMConnection
*cPtr
)
512 if (allowedHostList
== NULL
)
513 return True
; /* No list. Allow all by default */
515 hPtr
= WMGetHostWithAddress(WMGetConnectionAddress(cPtr
));
516 for (i
=0; i
<WMGetArrayItemCount(allowedHostList
); i
++) {
517 if (WMIsHostEqualToHost(hPtr
, WMGetFromArray(allowedHostList
, i
))) {
530 didReceiveInput(ConnectionDelegate
*self
, WMConnection
*cPtr
)
532 if (cPtr
== serverPtr
) {
533 WMConnection
*newPtr
= WMAcceptConnection(cPtr
);
536 if (isAllowedToConnect(newPtr
)) {
537 WMSetConnectionDelegate(newPtr
, &socketDelegate
);
538 WMSetConnectionSendTimeout(newPtr
, 120);
539 WMAddToArray(clientConnections
, newPtr
);
541 sendMessage(newPtr
, "Sorry, you are not allowed to connect.\n");
542 WMDestroyConnection(newPtr
);
546 /* Data arriving on an already-connected socket */
547 handleConnection(cPtr
);
553 connectionDidTimeout(ConnectionDelegate
*self
, WMConnection
*cPtr
)
557 if (cPtr
== serverPtr
) {
558 wfatal(_("The server listening socket did timeout. Exiting."));
562 hPtr
= WMGetHostWithAddress(WMGetConnectionAddress(cPtr
));
563 wwarning(_("Connection with %s did timeout. Closing connection."),
564 WMGetHostName(hPtr
));
567 enqueueConnectionForRemoval(cPtr
);
572 connectionDidDie(ConnectionDelegate
*self
, WMConnection
*cPtr
)
574 if (cPtr
== serverPtr
) {
575 /* trouble. server listening port itself died!!! */
576 wfatal(_("The server listening socket died. Exiting."));
580 enqueueConnectionForRemoval(cPtr
);
585 removeConnection(void *observer
, WMNotification
*notification
)
587 WMConnection
*cPtr
= (WMConnection
*)WMGetNotificationObject(notification
);
590 WMRemoveFromArray(clientConnections
, cPtr
);
591 if ((data
= (WMData
*)WMGetConnectionClientData(cPtr
))!=NULL
)
593 WMDestroyConnection(cPtr
);
597 updatedDomain(void *observer
, WMNotification
*notification
)
599 wmessage("defaults domain file changed on disk. synchronizing.");
605 isDifferent(char *str1
, char *str2
)
607 if ((!str1
&& !str2
) || (str1
&& str2
&& strcmp(str1
, str2
)==0))
616 main(int argc
, char **argv
)
622 WMInitializeApplication("server", &argc
, argv
);
625 for (i
=1; i
<argc
; i
++) {
626 if (strcmp(argv
[i
], "--help")==0) {
629 } else if (strcmp(argv
[i
], "--listen")==0) {
632 if ((p
= strchr(argv
[++i
], ':')) != NULL
) {
634 ServerAddress
= wstrdup(argv
[i
]);
635 ServerPort
= wstrdup(p
+1);
637 if (ServerAddress
[0] == 0) {
638 wfree(ServerAddress
);
639 ServerAddress
= NULL
;
641 if (ServerPort
[0] == 0) {
643 ServerPort
= "34567";
645 } else if (argv
[i
][0]!=0) {
646 ServerPort
= argv
[i
];
648 } else if (strcmp(argv
[i
], "--allow")==0) {
656 if ((p
= strchr(ptr
, ',')) != NULL
) {
660 hPtr
= WMGetHostWithName(ptr
);
662 if (!allowedHostList
)
663 allowedHostList
= WMCreateArray(4);
664 WMAddToArray(allowedHostList
, hPtr
);
666 wwarning(_("Unknown host '%s'. Ignored."), ptr
);
678 printf(_("%s: invalid argument '%s'\n"), argv
[0], argv
[i
]);
679 printf(_("Try '%s --help' for more information\n"), argv
[0]);
685 timeDB
= WMGetDefaultsFromPath("./UserTime.plist");
686 WMAddNotificationObserver(updatedDomain
, NULL
,
687 WMUserDefaultsDidChangeNotification
, NULL
);
689 clientConnections
= WMCreateArray(4);
691 /* A NULL ServerAddress means to listen on any address the host has.
692 * Else if ServerAddress points to a specific address (like "localhost",
693 * "host.domain.com" or "192.168.1.1"), then it will only listen on that
694 * interface and ignore incoming connections on the others. */
695 if (ServerAddress
&& strcasecmp(ServerAddress
, "Any")==0)
696 ServerAddress
= NULL
;
697 if (ServerPort
==NULL
)
698 ServerPort
= "34567";
700 printf("Server will listen on '%s:%s'\n", ServerAddress
?ServerAddress
:"Any",
702 printf("This server will allow connections from:");
703 if (allowedHostList
) {
707 for (i
=0; i
<WMGetArrayItemCount(allowedHostList
); i
++) {
708 hName
= WMGetHostName(WMGetFromArray(allowedHostList
, i
));
709 printf("%s'%s'", i
==0?" ":", ", hName
);
713 printf(" any host.\n");
716 serverPtr
= WMCreateConnectionAsServerAtAddress(ServerAddress
, ServerPort
,
720 wfatal("could not create server on `%s:%s`. Exiting.",
721 ServerAddress
? ServerAddress
: "localhost", ServerPort
);
725 WMSetConnectionDelegate(serverPtr
, &socketDelegate
);
727 WMAddNotificationObserver(removeConnection
, NULL
,
728 SEConnectionShouldBeRemovedNotification
, NULL
);
731 /* The ASAP notification queue is called at the end of WHandleEvents()
732 * There's where died connections we get while running through
733 * WHandleEvents() get removed. */