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);
129
static unsigned char*
130
findDelimiter(unsigned char *data, unsigned 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. */