2 * WINGs server.c: example how to create a network server using WMConnection
4 * Copyright (c) 2001-2003 Dan Pascu
13 #include <WINGs/WINGs.h>
16 #define MAXCMD_SIZE 512
18 char *SEConnectionShouldBeRemovedNotification
= "SEConnectionShouldBeRemovedNotification";
20 static void didReceiveInput(ConnectionDelegate
* self
, WMConnection
* cPtr
);
22 static void connectionDidDie(ConnectionDelegate
* self
, WMConnection
* cPtr
);
24 static void connectionDidTimeout(ConnectionDelegate
* self
, WMConnection
* cPtr
);
26 extern char *SEConnectionShouldBeRemovedNotification
;
28 static WMUserDefaults
*timeDB
= NULL
;
29 static char *ServerAddress
= NULL
;
30 static char *ServerPort
= NULL
;
31 static WMArray
*allowedHostList
= NULL
;
32 static WMArray
*clientConnections
= NULL
;
33 static WMConnection
*serverPtr
= NULL
;
35 static ConnectionDelegate socketDelegate
= {
36 NULL
, /* client data */
37 NULL
, /* canResumeSending */
38 NULL
, /* didCatchException */
39 connectionDidDie
, /* didDie */
40 NULL
, /* didInitialize */
41 didReceiveInput
, /* didReceiveInput */
42 connectionDidTimeout
/* didTimeout */
50 static void printHelp(char *progname
)
52 printf(_("usage: %s [options]\n\n"), progname
);
53 puts(_(" --help print this message"));
54 puts(_(" --listen [address:]port only listen on the specified address/port"));
55 puts(_(" --allow host1[,host2...] only allow connections from listed hosts\n"));
56 puts(_(" By default server listens on all interfaces and port 34567, unless"
57 " something\nelse is specified with the --listen option. If address is"
58 " omitted or the keyword\n'Any' is used, it will listen on all interfaces else"
59 " only on the specified one.\n\nFor example --listen localhost: will"
60 " listen on the default port 34567, but only\non connections comming"
61 " in through the loopback interface.\n\n Also by default the server"
62 " listens to incoming connections from any host,\nunless a list of"
63 " hosts is given with the --allow option, in which case it will\nreject"
64 " connections not comming from those hosts.\nThe list of hosts is comma"
65 " separated and should NOT contain ANY spaces."));
68 static void enqueueConnectionForRemoval(WMConnection
* cPtr
)
70 WMNotification
*notif
;
72 /*don't release notif here. it will be released by queue after processing */
73 notif
= WMCreateNotification(SEConnectionShouldBeRemovedNotification
, cPtr
, NULL
);
74 WMEnqueueNotification(WMGetDefaultNotificationQueue(), notif
, WMPostASAP
);
77 static int sendMessage(WMConnection
* cPtr
, char *message
)
82 if (WMGetConnectionState(cPtr
) != WCConnected
)
85 aData
= WMCreateDataWithBytes(message
, strlen(message
));
86 res
= WMSendConnectionData(cPtr
, aData
);
92 static Bool
enqueueMessage(WMConnection
* cPtr
, char *message
)
97 if (WMGetConnectionState(cPtr
) != WCConnected
)
100 aData
= WMCreateDataWithBytes(message
, strlen(message
));
101 res
= WMEnqueueConnectionData(cPtr
, aData
);
102 WMReleaseData(aData
);
107 static char *findDelimiter(char *data
, const char *endPtr
)
109 wassertrv(data
< endPtr
, NULL
);
111 while (data
< endPtr
&& *data
!= '\n' && *data
!= '\r' && *data
!= ';' && *data
!= '\0')
120 static WMArray
*getAvailableMessages(WMConnection
* cPtr
)
122 char *ptr
, *crtPos
, *buffer
;
123 const char *bytes
, *endPtr
;
124 WMData
*aData
, *receivedData
, *holdData
;
129 receivedData
= WMGetConnectionAvailableData(cPtr
);
132 if ((length
= WMGetDataLength(receivedData
)) == 0) {
133 WMReleaseData(receivedData
);
137 holdData
= (WMData
*) WMGetConnectionClientData(cPtr
);
139 WMAppendData(holdData
, receivedData
);
140 WMReleaseData(receivedData
);
141 WMSetConnectionClientData(cPtr
, NULL
);
144 aData
= receivedData
;
147 length
= WMGetDataLength(aData
);
148 bytes
= (char *)WMDataBytes(aData
);
149 endPtr
= bytes
+ length
;
151 messages
= WMCreateArrayWithDestructor(1, wfree
);
152 crtPos
= (char *)bytes
;
153 while (crtPos
< endPtr
&& (ptr
= findDelimiter(crtPos
, endPtr
)) != NULL
) {
154 range
.position
= (crtPos
- bytes
);
155 range
.count
= (ptr
- crtPos
);
156 if (range
.count
> MAXCMD_SIZE
) {
157 /* Hmmm... The message is too long. Possibly that someone is
158 * flooding us, or there is a dumb client which do not know
159 * who is talking to. */
160 sendMessage(cPtr
, "Command too long\n\r");
161 WMFreeArray(messages
);
162 WMReleaseData(aData
);
163 WMCloseConnection(cPtr
);
164 enqueueConnectionForRemoval(cPtr
);
167 buffer
= wmalloc(range
.count
+ 1);
168 WMGetDataBytesWithRange(aData
, buffer
, range
);
169 buffer
[range
.count
] = '\0';
170 WMAddToArray(messages
, buffer
);
172 while (crtPos
< endPtr
&& (*crtPos
== '\n' || *crtPos
== '\r' ||
173 *crtPos
== '\t' || *crtPos
== '\0' ||
174 *crtPos
== ';' || *crtPos
== ' ')) {
179 if (crtPos
< endPtr
) {
180 range
.position
= (crtPos
- bytes
);
181 range
.count
= (endPtr
- crtPos
);
182 if (range
.count
> MAXCMD_SIZE
) {
183 /* Flooooooding!!!! */
184 sendMessage(cPtr
, "Message too long\n\r");
185 WMFreeArray(messages
);
186 WMReleaseData(aData
);
187 WMCloseConnection(cPtr
);
188 enqueueConnectionForRemoval(cPtr
);
191 holdData
= WMGetSubdataWithRange(aData
, range
);
192 WMSetConnectionClientData(cPtr
, holdData
);
194 WMReleaseData(aData
);
196 if (WMGetArrayItemCount(messages
) == 0) {
197 WMFreeArray(messages
);
203 static void complainAboutBadArgs(WMConnection
* cPtr
, char *cmdName
, char *badArgs
)
205 char *buf
= wmalloc(strlen(cmdName
) + strlen(badArgs
) + 100);
207 sprintf(buf
, _("Invalid parameters '%s' for command %s. Use HELP for"
208 " a list of commands.\n"), badArgs
, cmdName
);
209 sendMessage(cPtr
, buf
);
213 static void sendUpdateMessage(WMConnection
* cPtr
, char *id
, int time
)
215 char *buf
= wmalloc(strlen(id
) + 100);
217 sprintf(buf
, "%s has %i minutes left\n", id
, time
);
218 sendMessage(cPtr
, buf
);
222 static void showId(WMConnection
* cPtr
)
224 sendMessage(cPtr
, "Server example based on WMConnection\n");
227 static void showHelp(WMConnection
* cPtr
)
229 char *buf
= wmalloc(strlen(WMGetApplicationName()) + 16);
231 sprintf(buf
, _("%s commands:\n\n"), WMGetApplicationName());
233 enqueueMessage(cPtr
, _("\n"));
234 enqueueMessage(cPtr
, buf
);
235 enqueueMessage(cPtr
, _("GET <id>\t- return time left (in minutes) " "for user with id <id>\n"));
236 enqueueMessage(cPtr
, _("SET <id> <time>\t- set time limit to <time> " "minutes for user with id <id>\n"));
237 enqueueMessage(cPtr
, _("ADD <id> <time>\t- add <time> minutes " "for user with id <id>\n"));
238 enqueueMessage(cPtr
, _("SUB <id> <time>\t- subtract <time> minutes " "for user with id <id>\n"));
239 enqueueMessage(cPtr
, _("REMOVE <id>\t- remove time limitations for " "user with id <id>\n"));
240 enqueueMessage(cPtr
, _("LIST\t\t- list all users and their " "corresponding time limit\n"));
241 enqueueMessage(cPtr
, _("ID\t\t- returns the Time Manager " "identification string\n"));
242 enqueueMessage(cPtr
, _("EXIT\t\t- exits session\n"));
243 enqueueMessage(cPtr
, _("QUIT\t\t- exits session\n"));
244 enqueueMessage(cPtr
, _("HELP\t\t- show this message\n\n"));
245 /* Just flush the queue we made before */
246 WMFlushConnection(cPtr
);
250 static void listUsers(WMConnection
* cPtr
)
252 WMPropList
*userList
;
256 userList
= WMGetUDKeys(timeDB
);
258 for (i
= 0; i
< WMGetPropListItemCount(userList
); i
++) {
259 id
= WMGetFromPLString(WMGetFromPLArray(userList
, i
));
260 time
= WMGetUDIntegerForKey(timeDB
, id
);
261 sendUpdateMessage(cPtr
, id
, time
);
264 WMReleasePropList(userList
);
267 static void setTimeForUser(WMConnection
* cPtr
, char *cmdArgs
)
272 id
= wmalloc(strlen(cmdArgs
));
273 if (sscanf(cmdArgs
, "%s %d", id
, &time
) != 2) {
274 complainAboutBadArgs(cPtr
, "SET", cmdArgs
);
281 WMSetUDIntegerForKey(timeDB
, time
, id
);
283 for (i
= 0; i
< WMGetArrayItemCount(clientConnections
); i
++) {
284 cPtr
= WMGetFromArray(clientConnections
, i
);
285 sendUpdateMessage(cPtr
, id
, time
);
290 static void addTimeToUser(WMConnection
* cPtr
, char *cmdArgs
)
293 int i
, time
, newTime
;
295 id
= wmalloc(strlen(cmdArgs
));
296 if (sscanf(cmdArgs
, "%s %d", id
, &time
) != 2) {
297 complainAboutBadArgs(cPtr
, "ADD", cmdArgs
);
302 newTime
= WMGetUDIntegerForKey(timeDB
, id
) + time
;
306 WMSetUDIntegerForKey(timeDB
, newTime
, id
);
308 for (i
= 0; i
< WMGetArrayItemCount(clientConnections
); i
++) {
309 cPtr
= WMGetFromArray(clientConnections
, i
);
310 sendUpdateMessage(cPtr
, id
, newTime
);
315 static void subTimeFromUser(WMConnection
* cPtr
, char *cmdArgs
)
318 int i
, time
, newTime
;
320 id
= wmalloc(strlen(cmdArgs
));
321 if (sscanf(cmdArgs
, "%s %d", id
, &time
) != 2) {
322 complainAboutBadArgs(cPtr
, "SUB", cmdArgs
);
327 newTime
= WMGetUDIntegerForKey(timeDB
, id
) - time
;
331 WMSetUDIntegerForKey(timeDB
, newTime
, id
);
333 for (i
= 0; i
< WMGetArrayItemCount(clientConnections
); i
++) {
334 cPtr
= WMGetFromArray(clientConnections
, i
);
335 sendUpdateMessage(cPtr
, id
, newTime
);
340 static void removeTimeForUser(WMConnection
* cPtr
, char *cmdArgs
)
345 if (cmdArgs
[0] == '\0') {
346 sendMessage(cPtr
, _("Missing parameter for command REMOVE."
347 " Use HELP for a list of commands.\n"));
352 while (*ptr
&& *ptr
!= ' ' && *ptr
!= '\t')
356 WMRemoveUDObjectForKey(timeDB
, cmdArgs
);
358 for (i
= 0; i
< WMGetArrayItemCount(clientConnections
); i
++) {
359 cPtr
= WMGetFromArray(clientConnections
, i
);
360 sendUpdateMessage(cPtr
, cmdArgs
, -1);
364 static void getTimeForUser(WMConnection
* cPtr
, char *cmdArgs
)
369 if (cmdArgs
[0] == '\0') {
370 sendMessage(cPtr
, _("Missing parameter for command GET." " Use HELP for a list of commands.\n"));
375 while (*ptr
&& *ptr
!= ' ' && *ptr
!= '\t')
379 if (WMGetUDObjectForKey(timeDB
, cmdArgs
) != NULL
)
380 time
= WMGetUDIntegerForKey(timeDB
, cmdArgs
);
384 sendUpdateMessage(cPtr
, cmdArgs
, time
);
387 static void handleConnection(WMConnection
* cPtr
)
389 char *command
, *ptr
, *cmdArgs
, *buffer
;
393 commands
= getAvailableMessages(cPtr
);
397 for (i
= 0; i
< WMGetArrayItemCount(commands
); i
++) {
398 command
= WMGetFromArray(commands
, i
);
399 while (*command
&& (*command
== ' ' || *command
== '\t'))
402 while (*ptr
&& *ptr
!= ' ' && *ptr
!= '\t')
408 while (*ptr
&& (*ptr
== ' ' || *ptr
== '\t'))
413 fprintf(stderr
, "Command: '%s', args: '%s'\n", command
, cmdArgs
);
415 if (strcasecmp(command
, "quit") == 0 || strcasecmp(command
, "exit") == 0) {
416 sendMessage(cPtr
, "Bye\n");
417 WMCloseConnection(cPtr
);
418 enqueueConnectionForRemoval(cPtr
);
419 WMFreeArray(commands
);
421 } else if (strcasecmp(command
, "id") == 0) {
423 } else if (strcasecmp(command
, "help") == 0) {
425 } else if (strcasecmp(command
, "list") == 0) {
427 } else if (strcasecmp(command
, "set") == 0) {
428 setTimeForUser(cPtr
, cmdArgs
);
429 } else if (strcasecmp(command
, "add") == 0) {
430 addTimeToUser(cPtr
, cmdArgs
);
431 } else if (strcasecmp(command
, "sub") == 0) {
432 subTimeFromUser(cPtr
, cmdArgs
);
433 } else if (strcasecmp(command
, "remove") == 0) {
434 removeTimeForUser(cPtr
, cmdArgs
);
435 } else if (strcasecmp(command
, "get") == 0) {
436 getTimeForUser(cPtr
, cmdArgs
);
438 buffer
= wmalloc(strlen(command
) + 100);
439 sprintf(buffer
, _("Unknown command '%s'. Try HELP for" " a list of commands.\n"), command
);
440 sendMessage(cPtr
, buffer
);
445 WMFreeArray(commands
);
448 static Bool
isAllowedToConnect(WMConnection
* cPtr
)
453 if (allowedHostList
== NULL
)
454 return True
; /* No list. Allow all by default */
456 hPtr
= WMGetHostWithAddress(WMGetConnectionAddress(cPtr
));
457 for (i
= 0; i
< WMGetArrayItemCount(allowedHostList
); i
++) {
458 if (WMIsHostEqualToHost(hPtr
, WMGetFromArray(allowedHostList
, i
))) {
469 static void didReceiveInput(ConnectionDelegate
* self
, WMConnection
* cPtr
)
471 if (cPtr
== serverPtr
) {
472 WMConnection
*newPtr
= WMAcceptConnection(cPtr
);
475 if (isAllowedToConnect(newPtr
)) {
476 WMSetConnectionDelegate(newPtr
, &socketDelegate
);
477 WMSetConnectionSendTimeout(newPtr
, 120);
478 WMAddToArray(clientConnections
, newPtr
);
480 sendMessage(newPtr
, "Sorry, you are not allowed to connect.\n");
481 WMDestroyConnection(newPtr
);
485 /* Data arriving on an already-connected socket */
486 handleConnection(cPtr
);
490 static void connectionDidTimeout(ConnectionDelegate
* self
, WMConnection
* cPtr
)
494 if (cPtr
== serverPtr
) {
495 wfatal(_("The server listening socket did timeout. Exiting."));
499 hPtr
= WMGetHostWithAddress(WMGetConnectionAddress(cPtr
));
500 wwarning(_("Connection with %s did timeout. Closing connection."), WMGetHostName(hPtr
));
503 enqueueConnectionForRemoval(cPtr
);
506 static void connectionDidDie(ConnectionDelegate
* self
, WMConnection
* cPtr
)
508 if (cPtr
== serverPtr
) {
509 /* trouble. server listening port itself died!!! */
510 wfatal(_("The server listening socket died. Exiting."));
514 enqueueConnectionForRemoval(cPtr
);
517 static void removeConnection(void *observer
, WMNotification
* notification
)
519 WMConnection
*cPtr
= (WMConnection
*) WMGetNotificationObject(notification
);
522 WMRemoveFromArray(clientConnections
, cPtr
);
523 if ((data
= (WMData
*) WMGetConnectionClientData(cPtr
)) != NULL
)
525 WMDestroyConnection(cPtr
);
528 static void updatedDomain(void *observer
, WMNotification
* notification
)
530 wmessage("defaults domain file changed on disk. synchronizing.");
534 static Bool
isDifferent(char *str1
, char *str2
)
536 if ((!str1
&& !str2
) || (str1
&& str2
&& strcmp(str1
, str2
) == 0))
543 int main(int argc
, char **argv
)
549 WMInitializeApplication("server", &argc
, argv
);
552 for (i
= 1; i
< argc
; i
++) {
553 if (strcmp(argv
[i
], "--help") == 0) {
556 } else if (strcmp(argv
[i
], "--listen") == 0) {
559 if ((p
= strchr(argv
[++i
], ':')) != NULL
) {
561 ServerAddress
= wstrdup(argv
[i
]);
562 ServerPort
= wstrdup(p
+ 1);
564 if (ServerAddress
[0] == 0) {
565 wfree(ServerAddress
);
566 ServerAddress
= NULL
;
568 if (ServerPort
[0] == 0) {
570 ServerPort
= "34567";
572 } else if (argv
[i
][0] != 0) {
573 ServerPort
= argv
[i
];
575 } else if (strcmp(argv
[i
], "--allow") == 0) {
583 if ((p
= strchr(ptr
, ',')) != NULL
) {
587 hPtr
= WMGetHostWithName(ptr
);
589 if (!allowedHostList
)
590 allowedHostList
= WMCreateArray(4);
591 WMAddToArray(allowedHostList
, hPtr
);
593 wwarning(_("Unknown host '%s'. Ignored."), ptr
);
605 printf(_("%s: invalid argument '%s'\n"), argv
[0], argv
[i
]);
606 printf(_("Try '%s --help' for more information\n"), argv
[0]);
612 timeDB
= WMGetDefaultsFromPath("./UserTime.plist");
613 WMAddNotificationObserver(updatedDomain
, NULL
, WMUserDefaultsDidChangeNotification
, NULL
);
615 clientConnections
= WMCreateArray(4);
617 /* A NULL ServerAddress means to listen on any address the host has.
618 * Else if ServerAddress points to a specific address (like "localhost",
619 * "host.domain.com" or "192.168.1.1"), then it will only listen on that
620 * interface and ignore incoming connections on the others. */
621 if (ServerAddress
&& strcasecmp(ServerAddress
, "Any") == 0)
622 ServerAddress
= NULL
;
623 if (ServerPort
== NULL
)
624 ServerPort
= "34567";
626 printf("Server will listen on '%s:%s'\n", ServerAddress
? ServerAddress
: "Any", ServerPort
);
627 printf("This server will allow connections from:");
628 if (allowedHostList
) {
632 for (i
= 0; i
< WMGetArrayItemCount(allowedHostList
); i
++) {
633 hName
= WMGetHostName(WMGetFromArray(allowedHostList
, i
));
634 printf("%s'%s'", i
== 0 ? " " : ", ", hName
);
638 printf(" any host.\n");
641 serverPtr
= WMCreateConnectionAsServerAtAddress(ServerAddress
, ServerPort
, NULL
);
644 wfatal("could not create server on `%s:%s`. Exiting.",
645 ServerAddress
? ServerAddress
: "localhost", ServerPort
);
649 WMSetConnectionDelegate(serverPtr
, &socketDelegate
);
651 WMAddNotificationObserver(removeConnection
, NULL
, SEConnectionShouldBeRemovedNotification
, NULL
);
654 /* The ASAP notification queue is called at the end of WHandleEvents()
655 * There's where died connections we get while running through
656 * WHandleEvents() get removed. */