2 * WINGs server.c: example how to create a network server using WMConnection
4 * Copyright (c) 2001-2002 Dan Pascu
13 #include <WINGs/WINGs.h>
17 #define MAXCMD_SIZE 512
20 char *SEConnectionShouldBeRemovedNotification
= "SEConnectionShouldBeRemovedNotification";
25 static void didReceiveInput(ConnectionDelegate
*self
, WMConnection
*cPtr
);
27 static void connectionDidDie(ConnectionDelegate
*self
, WMConnection
*cPtr
);
29 static void connectionDidTimeout(ConnectionDelegate
*self
, WMConnection
*cPtr
);
32 extern char *SEConnectionShouldBeRemovedNotification
;
34 static WMUserDefaults
*timeDB
= NULL
;
35 static char *ServerAddress
= NULL
;
36 static char *ServerPort
= NULL
;
37 static WMArray
*allowedHostList
= NULL
;
38 static WMArray
*clientConnections
= NULL
;
39 static WMConnection
*serverPtr
= NULL
;
43 static ConnectionDelegate socketDelegate
= {
44 NULL
, /* client data */
45 NULL
, /* canResumeSending */
46 NULL
, /* didCatchException */
47 connectionDidDie
, /* didDie */
48 NULL
, /* didInitialize */
49 didReceiveInput
, /* didReceiveInput */
50 connectionDidTimeout
/* didTimeout */
63 printHelp(char *progname
)
65 printf(_("usage: %s [options]\n\n"), progname
);
66 puts(_(" --help print this message"));
67 puts(_(" --listen [address:]port only listen on the specified address/port"));
68 puts(_(" --allow host1[,host2...] only allow connections from listed hosts\n"));
69 puts(_(" By default server listens on all interfaces and port 34567, unless"
70 " something\nelse is specified with the --listen option. If address is"
71 " omitted or the keyword\n'Any' is used, it will listen on all interfaces else"
72 " only on the specified one.\n\nFor example --listen localhost: will"
73 " listen on the default port 34567, but only\non connections comming"
74 " in through the loopback interface.\n\n Also by default the server"
75 " listens to incoming connections from any host,\nunless a list of"
76 " hosts is given with the --allow option, in which case it will\nreject"
77 " connections not comming from those hosts.\nThe list of hosts is comma"
78 " separated and should NOT contain ANY spaces."));
83 enqueueConnectionForRemoval(WMConnection
*cPtr
)
85 WMNotification
*notif
;
87 /*don't release notif here. it will be released by queue after processing */
88 notif
= WMCreateNotification(SEConnectionShouldBeRemovedNotification
,
90 WMEnqueueNotification(WMGetDefaultNotificationQueue(), notif
, WMPostASAP
);
95 sendMessage(WMConnection
*cPtr
, char *message
)
100 if (WMGetConnectionState(cPtr
)!=WCConnected
)
103 aData
= WMCreateDataWithBytes(message
, strlen(message
));
104 res
= WMSendConnectionData(cPtr
, aData
);
105 WMReleaseData(aData
);
112 enqueueMessage(WMConnection
*cPtr
, char *message
)
117 if (WMGetConnectionState(cPtr
)!=WCConnected
)
120 aData
= WMCreateDataWithBytes(message
, strlen(message
));
121 res
= WMEnqueueConnectionData(cPtr
, aData
);
122 WMReleaseData(aData
);
128 static unsigned char*
129 findDelimiter(unsigned char *data
, unsigned const char *endPtr
)
131 wassertrv(data
< endPtr
, NULL
);
133 while (data
<endPtr
&& *data
!='\n' && *data
!='\r' && *data
!=';' && *data
!='\0')
144 getAvailableMessages(WMConnection
*cPtr
)
146 char *ptr
, *crtPos
, *buffer
;
147 const char *bytes
, *endPtr
;
148 WMData
*aData
, *receivedData
, *holdData
;
153 receivedData
= WMGetConnectionAvailableData(cPtr
);
156 if ((length
=WMGetDataLength(receivedData
))==0) {
157 WMReleaseData(receivedData
);
161 holdData
= (WMData
*)WMGetConnectionClientData(cPtr
);
163 WMAppendData(holdData
, receivedData
);
164 WMReleaseData(receivedData
);
165 WMSetConnectionClientData(cPtr
, NULL
);
168 aData
= receivedData
;
171 length
= WMGetDataLength(aData
);
172 bytes
= (char*)WMDataBytes(aData
);
173 endPtr
= bytes
+ length
;
175 messages
= WMCreateArrayWithDestructor(1, wfree
);
176 crtPos
= (char*)bytes
;
177 while (crtPos
<endPtr
&& (ptr
= findDelimiter(crtPos
, endPtr
))!=NULL
) {
178 range
.position
= (crtPos
- bytes
);
179 range
.count
= (ptr
- crtPos
);
180 if (range
.count
> MAXCMD_SIZE
) {
181 /* Hmmm... The message is too long. Possibly that someone is
182 * flooding us, or there is a dumb client which do not know
183 * who is talking to. */
184 sendMessage(cPtr
, "Command too long\n\r");
185 WMFreeArray(messages
);
186 WMReleaseData(aData
);
187 WMCloseConnection(cPtr
);
188 enqueueConnectionForRemoval(cPtr
);
191 buffer
= wmalloc(range
.count
+1);
192 WMGetDataBytesWithRange(aData
, buffer
, range
);
193 buffer
[range
.count
] = '\0';
194 WMAddToArray(messages
, buffer
);
196 while (crtPos
<endPtr
&& (*crtPos
=='\n' || *crtPos
=='\r' ||
197 *crtPos
=='\t' || *crtPos
=='\0' ||
198 *crtPos
==';' || *crtPos
==' ')) {
204 range
.position
= (crtPos
- bytes
);
205 range
.count
= (endPtr
- crtPos
);
206 if (range
.count
> MAXCMD_SIZE
) {
207 /* Flooooooding!!!! */
208 sendMessage(cPtr
, "Message too long\n\r");
209 WMFreeArray(messages
);
210 WMReleaseData(aData
);
211 WMCloseConnection(cPtr
);
212 enqueueConnectionForRemoval(cPtr
);
215 holdData
= WMGetSubdataWithRange(aData
, range
);
216 WMSetConnectionClientData(cPtr
, holdData
);
218 WMReleaseData(aData
);
220 if (WMGetArrayItemCount(messages
)==0) {
221 WMFreeArray(messages
);
230 complainAboutBadArgs(WMConnection
*cPtr
, char *cmdName
, char *badArgs
)
232 char *buf
= wmalloc(strlen(cmdName
) + strlen(badArgs
) + 100);
234 sprintf(buf
, _("Invalid parameters '%s' for command %s. Use HELP for"
235 " a list of commands.\n"), badArgs
, cmdName
);
236 sendMessage(cPtr
, buf
);
242 sendUpdateMessage(WMConnection
*cPtr
, char *id
, int time
)
244 char *buf
= wmalloc(strlen(id
) + 100);
246 sprintf(buf
, "%s has %i minutes left\n", id
, time
);
247 sendMessage(cPtr
, buf
);
253 showId(WMConnection
*cPtr
)
255 sendMessage(cPtr
, "Server example based on WMConnection\n");
260 showHelp(WMConnection
*cPtr
)
262 char *buf
= wmalloc(strlen(WMGetApplicationName()) + 16);
264 sprintf(buf
, _("%s commands:\n\n"), WMGetApplicationName());
266 enqueueMessage(cPtr
, _("\n"));
267 enqueueMessage(cPtr
, buf
);
268 enqueueMessage(cPtr
, _("GET <id>\t- return time left (in minutes) "
269 "for user with id <id>\n"));
270 enqueueMessage(cPtr
, _("SET <id> <time>\t- set time limit to <time> "
271 "minutes for user with id <id>\n"));
272 enqueueMessage(cPtr
, _("ADD <id> <time>\t- add <time> minutes "
273 "for user with id <id>\n"));
274 enqueueMessage(cPtr
, _("SUB <id> <time>\t- subtract <time> minutes "
275 "for user with id <id>\n"));
276 enqueueMessage(cPtr
, _("REMOVE <id>\t- remove time limitations for "
277 "user with id <id>\n"));
278 enqueueMessage(cPtr
, _("LIST\t\t- list all users and their "
279 "corresponding time limit\n"));
280 enqueueMessage(cPtr
, _("ID\t\t- returns the Time Manager "
281 "identification string\n"));
282 enqueueMessage(cPtr
, _("EXIT\t\t- exits session\n"));
283 enqueueMessage(cPtr
, _("QUIT\t\t- exits session\n"));
284 enqueueMessage(cPtr
, _("HELP\t\t- show this message\n\n"));
285 /* Just flush the queue we made before */
286 WMFlushConnection(cPtr
);
292 listUsers(WMConnection
*cPtr
)
294 WMPropList
*userList
;
298 userList
= WMGetUDKeys(timeDB
);
300 for (i
=0; i
<WMGetPropListItemCount(userList
); i
++) {
301 id
= WMGetFromPLString(WMGetFromPLArray(userList
, i
));
302 time
= WMGetUDIntegerForKey(timeDB
, id
);
303 sendUpdateMessage(cPtr
, id
, time
);
306 WMReleasePropList(userList
);
311 setTimeForUser(WMConnection
*cPtr
, char *cmdArgs
)
316 id
= wmalloc(strlen(cmdArgs
));
317 if (sscanf(cmdArgs
, "%s %d", id
, &time
)!=2) {
318 complainAboutBadArgs(cPtr
, "SET", cmdArgs
);
325 WMSetUDIntegerForKey(timeDB
, time
, id
);
327 for (i
=0; i
<WMGetArrayItemCount(clientConnections
); i
++) {
328 cPtr
= WMGetFromArray(clientConnections
, i
);
329 sendUpdateMessage(cPtr
, id
, time
);
336 addTimeToUser(WMConnection
*cPtr
, char *cmdArgs
)
339 int i
, time
, newTime
;
341 id
= wmalloc(strlen(cmdArgs
));
342 if (sscanf(cmdArgs
, "%s %d", id
, &time
)!=2) {
343 complainAboutBadArgs(cPtr
, "ADD", cmdArgs
);
348 newTime
= WMGetUDIntegerForKey(timeDB
, id
) + time
;
352 WMSetUDIntegerForKey(timeDB
, newTime
, id
);
354 for (i
=0; i
<WMGetArrayItemCount(clientConnections
); i
++) {
355 cPtr
= WMGetFromArray(clientConnections
, i
);
356 sendUpdateMessage(cPtr
, id
, newTime
);
363 subTimeFromUser(WMConnection
*cPtr
, char *cmdArgs
)
366 int i
, time
, newTime
;
368 id
= wmalloc(strlen(cmdArgs
));
369 if (sscanf(cmdArgs
, "%s %d", id
, &time
)!=2) {
370 complainAboutBadArgs(cPtr
, "SUB", cmdArgs
);
375 newTime
= WMGetUDIntegerForKey(timeDB
, id
) - time
;
379 WMSetUDIntegerForKey(timeDB
, newTime
, id
);
381 for (i
=0; i
<WMGetArrayItemCount(clientConnections
); i
++) {
382 cPtr
= WMGetFromArray(clientConnections
, i
);
383 sendUpdateMessage(cPtr
, id
, newTime
);
390 removeTimeForUser(WMConnection
*cPtr
, char *cmdArgs
)
395 if (cmdArgs
[0]=='\0') {
396 sendMessage(cPtr
, _("Missing parameter for command REMOVE."
397 " Use HELP for a list of commands.\n"));
402 while (*ptr
&& *ptr
!=' ' && *ptr
!='\t')
406 WMRemoveUDObjectForKey(timeDB
, cmdArgs
);
408 for (i
=0; i
<WMGetArrayItemCount(clientConnections
); i
++) {
409 cPtr
= WMGetFromArray(clientConnections
, i
);
410 sendUpdateMessage(cPtr
, cmdArgs
, -1);
416 getTimeForUser(WMConnection
*cPtr
, char *cmdArgs
)
421 if (cmdArgs
[0]=='\0') {
422 sendMessage(cPtr
, _("Missing parameter for command GET."
423 " Use HELP for a list of commands.\n"));
428 while (*ptr
&& *ptr
!=' ' && *ptr
!='\t')
432 if (WMGetUDObjectForKey(timeDB
, cmdArgs
)!=NULL
)
433 time
= WMGetUDIntegerForKey(timeDB
, cmdArgs
);
437 sendUpdateMessage(cPtr
, cmdArgs
, time
);
442 handleConnection(WMConnection
*cPtr
)
444 char *command
, *ptr
, *cmdArgs
, *buffer
;
448 commands
= getAvailableMessages(cPtr
);
452 for (i
=0; i
<WMGetArrayItemCount(commands
); i
++) {
453 command
= WMGetFromArray(commands
, i
);
454 while (*command
&& (*command
==' ' || *command
=='\t'))
457 while(*ptr
&& *ptr
!=' ' && *ptr
!='\t')
463 while (*ptr
&& (*ptr
==' ' || *ptr
=='\t'))
468 fprintf(stderr
, "Command: '%s', args: '%s'\n", command
, cmdArgs
);
470 if (strcasecmp(command
, "quit")==0 || strcasecmp(command
, "exit")==0) {
471 sendMessage(cPtr
, "Bye\n");
472 WMCloseConnection(cPtr
);
473 enqueueConnectionForRemoval(cPtr
);
474 WMFreeArray(commands
);
476 } else if (strcasecmp(command
, "id")==0) {
478 } else if (strcasecmp(command
, "help")==0) {
480 } else if (strcasecmp(command
, "list")==0) {
482 } else if (strcasecmp(command
, "set")==0) {
483 setTimeForUser(cPtr
, cmdArgs
);
484 } else if (strcasecmp(command
, "add")==0) {
485 addTimeToUser(cPtr
, cmdArgs
);
486 } else if (strcasecmp(command
, "sub")==0) {
487 subTimeFromUser(cPtr
, cmdArgs
);
488 } else if (strcasecmp(command
, "remove")==0) {
489 removeTimeForUser(cPtr
, cmdArgs
);
490 } else if (strcasecmp(command
, "get")==0) {
491 getTimeForUser(cPtr
, cmdArgs
);
493 buffer
= wmalloc(strlen(command
) + 100);
494 sprintf(buffer
, _("Unknown command '%s'. Try HELP for"
495 " a list of commands.\n"), command
);
496 sendMessage(cPtr
, buffer
);
501 WMFreeArray(commands
);
506 isAllowedToConnect(WMConnection
*cPtr
)
511 if (allowedHostList
== NULL
)
512 return True
; /* No list. Allow all by default */
514 hPtr
= WMGetHostWithAddress(WMGetConnectionAddress(cPtr
));
515 for (i
=0; i
<WMGetArrayItemCount(allowedHostList
); i
++) {
516 if (WMIsHostEqualToHost(hPtr
, WMGetFromArray(allowedHostList
, i
))) {
529 didReceiveInput(ConnectionDelegate
*self
, WMConnection
*cPtr
)
531 if (cPtr
== serverPtr
) {
532 WMConnection
*newPtr
= WMAcceptConnection(cPtr
);
535 if (isAllowedToConnect(newPtr
)) {
536 WMSetConnectionDelegate(newPtr
, &socketDelegate
);
537 WMSetConnectionSendTimeout(newPtr
, 120);
538 WMAddToArray(clientConnections
, newPtr
);
540 sendMessage(newPtr
, "Sorry, you are not allowed to connect.\n");
541 WMDestroyConnection(newPtr
);
545 /* Data arriving on an already-connected socket */
546 handleConnection(cPtr
);
552 connectionDidTimeout(ConnectionDelegate
*self
, WMConnection
*cPtr
)
556 if (cPtr
== serverPtr
) {
557 wfatal(_("The server listening socket did timeout. Exiting."));
561 hPtr
= WMGetHostWithAddress(WMGetConnectionAddress(cPtr
));
562 wwarning(_("Connection with %s did timeout. Closing connection."),
563 WMGetHostName(hPtr
));
566 enqueueConnectionForRemoval(cPtr
);
571 connectionDidDie(ConnectionDelegate
*self
, WMConnection
*cPtr
)
573 if (cPtr
== serverPtr
) {
574 /* trouble. server listening port itself died!!! */
575 wfatal(_("The server listening socket died. Exiting."));
579 enqueueConnectionForRemoval(cPtr
);
584 removeConnection(void *observer
, WMNotification
*notification
)
586 WMConnection
*cPtr
= (WMConnection
*)WMGetNotificationObject(notification
);
589 WMRemoveFromArray(clientConnections
, cPtr
);
590 if ((data
= (WMData
*)WMGetConnectionClientData(cPtr
))!=NULL
)
592 WMDestroyConnection(cPtr
);
596 updatedDomain(void *observer
, WMNotification
*notification
)
598 wmessage("defaults domain file changed on disk. synchronizing.");
604 isDifferent(char *str1
, char *str2
)
606 if ((!str1
&& !str2
) || (str1
&& str2
&& strcmp(str1
, str2
)==0))
615 main(int argc
, char **argv
)
621 WMInitializeApplication("server", &argc
, argv
);
624 for (i
=1; i
<argc
; i
++) {
625 if (strcmp(argv
[i
], "--help")==0) {
628 } else if (strcmp(argv
[i
], "--listen")==0) {
631 if ((p
= strchr(argv
[++i
], ':')) != NULL
) {
633 ServerAddress
= wstrdup(argv
[i
]);
634 ServerPort
= wstrdup(p
+1);
636 if (ServerAddress
[0] == 0) {
637 wfree(ServerAddress
);
638 ServerAddress
= NULL
;
640 if (ServerPort
[0] == 0) {
642 ServerPort
= "34567";
644 } else if (argv
[i
][0]!=0) {
645 ServerPort
= argv
[i
];
647 } else if (strcmp(argv
[i
], "--allow")==0) {
655 if ((p
= strchr(ptr
, ',')) != NULL
) {
659 hPtr
= WMGetHostWithName(ptr
);
661 if (!allowedHostList
)
662 allowedHostList
= WMCreateArray(4);
663 WMAddToArray(allowedHostList
, hPtr
);
665 wwarning(_("Unknown host '%s'. Ignored."), ptr
);
677 printf(_("%s: invalid argument '%s'\n"), argv
[0], argv
[i
]);
678 printf(_("Try '%s --help' for more information\n"), argv
[0]);
684 timeDB
= WMGetDefaultsFromPath("./UserTime.plist");
685 WMAddNotificationObserver(updatedDomain
, NULL
,
686 WMUserDefaultsDidChangeNotification
, NULL
);
688 clientConnections
= WMCreateArray(4);
690 /* A NULL ServerAddress means to listen on any address the host has.
691 * Else if ServerAddress points to a specific address (like "localhost",
692 * "host.domain.com" or "192.168.1.1"), then it will only listen on that
693 * interface and ignore incoming connections on the others. */
694 if (ServerAddress
&& strcasecmp(ServerAddress
, "Any")==0)
695 ServerAddress
= NULL
;
696 if (ServerPort
==NULL
)
697 ServerPort
= "34567";
699 printf("Server will listen on '%s:%s'\n", ServerAddress
?ServerAddress
:"Any",
701 printf("This server will allow connections from:");
702 if (allowedHostList
) {
706 for (i
=0; i
<WMGetArrayItemCount(allowedHostList
); i
++) {
707 hName
= WMGetHostName(WMGetFromArray(allowedHostList
, i
));
708 printf("%s'%s'", i
==0?" ":", ", hName
);
712 printf(" any host.\n");
715 serverPtr
= WMCreateConnectionAsServerAtAddress(ServerAddress
, ServerPort
,
719 wfatal("could not create server on `%s:%s`. Exiting.",
720 ServerAddress
? ServerAddress
: "localhost", ServerPort
);
724 WMSetConnectionDelegate(serverPtr
, &socketDelegate
);
726 WMAddNotificationObserver(removeConnection
, NULL
,
727 SEConnectionShouldBeRemovedNotification
, NULL
);
730 /* The ASAP notification queue is called at the end of WHandleEvents()
731 * There's where died connections we get while running through
732 * WHandleEvents() get removed. */