- Fixed crashing bug in menu.c
[wmaker-crm.git] / WINGs / Examples / server.c
blob7c404a1d42c4a61e95d71cdcd8ad74f1148be2b8
1 /*
2 * WINGs server.c: example how to create a network server using WMConnection
4 * Copyright (c) 2001-2003 Dan Pascu
6 */
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <string.h>
14 #include <WINGs/WINGs.h>
17 #define _(P) P
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 */
56 void
57 wAbort(Bool foo)
59 exit(1);
63 static void
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."));
83 static void
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,
90 cPtr, NULL);
91 WMEnqueueNotification(WMGetDefaultNotificationQueue(), notif, WMPostASAP);
95 static int
96 sendMessage(WMConnection *cPtr, char *message)
98 WMData *aData;
99 int res;
101 if (WMGetConnectionState(cPtr)!=WCConnected)
102 return -1;
104 aData = WMCreateDataWithBytes(message, strlen(message));
105 res = WMSendConnectionData(cPtr, aData);
106 WMReleaseData(aData);
108 return res;
112 static Bool
113 enqueueMessage(WMConnection *cPtr, char *message)
115 WMData *aData;
116 Bool res;
118 if (WMGetConnectionState(cPtr)!=WCConnected)
119 return False;
121 aData = WMCreateDataWithBytes(message, strlen(message));
122 res = WMEnqueueConnectionData(cPtr, aData);
123 WMReleaseData(aData);
125 return res;
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')
135 data++;
137 if (data < endPtr)
138 return data;
140 return NULL;
144 static WMArray*
145 getAvailableMessages(WMConnection *cPtr)
147 char *ptr, *crtPos, *buffer;
148 const char *bytes, *endPtr;
149 WMData *aData, *receivedData, *holdData;
150 WMRange range;
151 WMArray *messages;
152 int length;
154 receivedData = WMGetConnectionAvailableData(cPtr);
155 if (!receivedData)
156 return NULL;
157 if ((length=WMGetDataLength(receivedData))==0) {
158 WMReleaseData(receivedData);
159 return NULL;
162 holdData = (WMData*)WMGetConnectionClientData(cPtr);
163 if (holdData) {
164 WMAppendData(holdData, receivedData);
165 WMReleaseData(receivedData);
166 WMSetConnectionClientData(cPtr, NULL);
167 aData = holdData;
168 } else {
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);
190 return NULL;
192 buffer = wmalloc(range.count+1);
193 WMGetDataBytesWithRange(aData, buffer, range);
194 buffer[range.count] = '\0';
195 WMAddToArray(messages, buffer);
196 crtPos = ptr;
197 while (crtPos<endPtr && (*crtPos=='\n' || *crtPos=='\r' ||
198 *crtPos=='\t' || *crtPos=='\0' ||
199 *crtPos==';' || *crtPos==' ')) {
200 crtPos++;
204 if (crtPos<endPtr) {
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);
214 return NULL;
216 holdData = WMGetSubdataWithRange(aData, range);
217 WMSetConnectionClientData(cPtr, holdData);
219 WMReleaseData(aData);
221 if (WMGetArrayItemCount(messages)==0) {
222 WMFreeArray(messages);
223 messages = NULL;
225 return messages;
230 static void
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);
238 wfree(buf);
242 static void
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);
249 wfree(buf);
253 static void
254 showId(WMConnection *cPtr)
256 sendMessage(cPtr, "Server example based on WMConnection\n");
260 static void
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);
288 wfree(buf);
292 static void
293 listUsers(WMConnection *cPtr)
295 WMPropList *userList;
296 char *id;
297 int i, time;
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);
311 static void
312 setTimeForUser(WMConnection *cPtr, char *cmdArgs)
314 char *id;
315 int i, time;
317 id = wmalloc(strlen(cmdArgs));
318 if (sscanf(cmdArgs, "%s %d", id, &time)!=2) {
319 complainAboutBadArgs(cPtr, "SET", cmdArgs);
320 wfree(id);
321 return;
323 if (time<0)
324 time = 0;
326 WMSetUDIntegerForKey(timeDB, time, id);
328 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
329 cPtr = WMGetFromArray(clientConnections, i);
330 sendUpdateMessage(cPtr, id, time);
332 wfree(id);
336 static void
337 addTimeToUser(WMConnection *cPtr, char *cmdArgs)
339 char *id;
340 int i, time, newTime;
342 id = wmalloc(strlen(cmdArgs));
343 if (sscanf(cmdArgs, "%s %d", id, &time)!=2) {
344 complainAboutBadArgs(cPtr, "ADD", cmdArgs);
345 wfree(id);
346 return;
349 newTime = WMGetUDIntegerForKey(timeDB, id) + time;
350 if (newTime<0)
351 newTime = 0;
353 WMSetUDIntegerForKey(timeDB, newTime, id);
355 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
356 cPtr = WMGetFromArray(clientConnections, i);
357 sendUpdateMessage(cPtr, id, newTime);
359 wfree(id);
363 static void
364 subTimeFromUser(WMConnection *cPtr, char *cmdArgs)
366 char *id;
367 int i, time, newTime;
369 id = wmalloc(strlen(cmdArgs));
370 if (sscanf(cmdArgs, "%s %d", id, &time)!=2) {
371 complainAboutBadArgs(cPtr, "SUB", cmdArgs);
372 wfree(id);
373 return;
376 newTime = WMGetUDIntegerForKey(timeDB, id) - time;
377 if (newTime<0)
378 newTime = 0;
380 WMSetUDIntegerForKey(timeDB, newTime, id);
382 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
383 cPtr = WMGetFromArray(clientConnections, i);
384 sendUpdateMessage(cPtr, id, newTime);
386 wfree(id);
390 static void
391 removeTimeForUser(WMConnection *cPtr, char *cmdArgs)
393 char *ptr;
394 int i;
396 if (cmdArgs[0]=='\0') {
397 sendMessage(cPtr, _("Missing parameter for command REMOVE."
398 " Use HELP for a list of commands.\n"));
399 return;
402 ptr = cmdArgs;
403 while (*ptr && *ptr!=' ' && *ptr!='\t')
404 ptr++;
405 *ptr = '\0';
407 WMRemoveUDObjectForKey(timeDB, cmdArgs);
409 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
410 cPtr = WMGetFromArray(clientConnections, i);
411 sendUpdateMessage(cPtr, cmdArgs, -1);
416 static void
417 getTimeForUser(WMConnection *cPtr, char *cmdArgs)
419 char *ptr;
420 int time;
422 if (cmdArgs[0]=='\0') {
423 sendMessage(cPtr, _("Missing parameter for command GET."
424 " Use HELP for a list of commands.\n"));
425 return;
428 ptr = cmdArgs;
429 while (*ptr && *ptr!=' ' && *ptr!='\t')
430 ptr++;
431 *ptr = '\0';
433 if (WMGetUDObjectForKey(timeDB, cmdArgs)!=NULL)
434 time = WMGetUDIntegerForKey(timeDB, cmdArgs);
435 else
436 time = -1;
438 sendUpdateMessage(cPtr, cmdArgs, time);
442 static void
443 handleConnection(WMConnection *cPtr)
445 char *command, *ptr, *cmdArgs, *buffer;
446 WMArray *commands;
447 int i;
449 commands = getAvailableMessages(cPtr);
450 if (!commands)
451 return;
453 for (i=0; i<WMGetArrayItemCount(commands); i++) {
454 command = WMGetFromArray(commands, i);
455 while (*command && (*command==' ' || *command=='\t'))
456 command++;
457 ptr = command;
458 while(*ptr && *ptr!=' ' && *ptr!='\t')
459 ptr++;
460 if (*ptr) {
461 *ptr = '\0';
462 ptr++;
464 while (*ptr && (*ptr==' ' || *ptr=='\t'))
465 ptr++;
467 cmdArgs = ptr;
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);
476 return;
477 } else if (strcasecmp(command, "id")==0) {
478 showId(cPtr);
479 } else if (strcasecmp(command, "help")==0) {
480 showHelp(cPtr);
481 } else if (strcasecmp(command, "list")==0) {
482 listUsers(cPtr);
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);
493 } else {
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);
498 wfree(buffer);
502 WMFreeArray(commands);
506 static Bool
507 isAllowedToConnect(WMConnection *cPtr)
509 WMHost *hPtr;
510 int i;
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))) {
518 WMReleaseHost(hPtr);
519 return True;
523 WMReleaseHost(hPtr);
525 return False;
529 static void
530 didReceiveInput(ConnectionDelegate *self, WMConnection *cPtr)
532 if (cPtr == serverPtr) {
533 WMConnection *newPtr = WMAcceptConnection(cPtr);
535 if (newPtr) {
536 if (isAllowedToConnect(newPtr)) {
537 WMSetConnectionDelegate(newPtr, &socketDelegate);
538 WMSetConnectionSendTimeout(newPtr, 120);
539 WMAddToArray(clientConnections, newPtr);
540 } else {
541 sendMessage(newPtr, "Sorry, you are not allowed to connect.\n");
542 WMDestroyConnection(newPtr);
545 } else {
546 /* Data arriving on an already-connected socket */
547 handleConnection(cPtr);
552 static void
553 connectionDidTimeout(ConnectionDelegate *self, WMConnection *cPtr)
555 WMHost *hPtr;
557 if (cPtr == serverPtr) {
558 wfatal(_("The server listening socket did timeout. Exiting."));
559 exit(1);
562 hPtr = WMGetHostWithAddress(WMGetConnectionAddress(cPtr));
563 wwarning(_("Connection with %s did timeout. Closing connection."),
564 WMGetHostName(hPtr));
565 WMReleaseHost(hPtr);
567 enqueueConnectionForRemoval(cPtr);
571 static void
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."));
577 exit(1);
580 enqueueConnectionForRemoval(cPtr);
584 static void
585 removeConnection(void *observer, WMNotification *notification)
587 WMConnection *cPtr = (WMConnection*)WMGetNotificationObject(notification);
588 WMData *data;
590 WMRemoveFromArray(clientConnections, cPtr);
591 if ((data = (WMData*)WMGetConnectionClientData(cPtr))!=NULL)
592 WMReleaseData(data);
593 WMDestroyConnection(cPtr);
596 static void
597 updatedDomain(void *observer, WMNotification *notification)
599 wmessage("defaults domain file changed on disk. synchronizing.");
603 #if 0
604 static Bool
605 isDifferent(char *str1, char *str2)
607 if ((!str1 && !str2) || (str1 && str2 && strcmp(str1, str2)==0))
608 return False;
610 return True;
612 #endif
616 main(int argc, char **argv)
618 int i;
620 wsetabort(wAbort);
622 WMInitializeApplication("server", &argc, argv);
624 if (argc>1) {
625 for (i=1; i<argc; i++) {
626 if (strcmp(argv[i], "--help")==0) {
627 printHelp(argv[0]);
628 exit(0);
629 } else if (strcmp(argv[i], "--listen")==0) {
630 char *p;
632 if ((p = strchr(argv[++i], ':')) != NULL) {
633 *p = 0;
634 ServerAddress = wstrdup(argv[i]);
635 ServerPort = wstrdup(p+1);
636 *p = ':';
637 if (ServerAddress[0] == 0) {
638 wfree(ServerAddress);
639 ServerAddress = NULL;
641 if (ServerPort[0] == 0) {
642 wfree(ServerPort);
643 ServerPort = "34567";
645 } else if (argv[i][0]!=0) {
646 ServerPort = argv[i];
648 } else if (strcmp(argv[i], "--allow")==0) {
649 char *p, *ptr;
650 int done;
651 WMHost *hPtr;
653 ptr = argv[++i];
654 done = 0;
655 while (!done) {
656 if ((p = strchr(ptr, ',')) != NULL) {
657 *p = 0;
659 if (*ptr != 0) {
660 hPtr = WMGetHostWithName(ptr);
661 if (hPtr) {
662 if (!allowedHostList)
663 allowedHostList = WMCreateArray(4);
664 WMAddToArray(allowedHostList, hPtr);
665 } else {
666 wwarning(_("Unknown host '%s'. Ignored."), ptr);
670 if (p!=NULL) {
671 *p = ',';
672 ptr = p+1;
673 } else {
674 done = 1;
677 } else {
678 printf(_("%s: invalid argument '%s'\n"), argv[0], argv[i]);
679 printf(_("Try '%s --help' for more information\n"), argv[0]);
680 exit(1);
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",
701 ServerPort);
702 printf("This server will allow connections from:");
703 if (allowedHostList) {
704 int i;
705 char *hName;
707 for (i=0; i<WMGetArrayItemCount(allowedHostList); i++) {
708 hName = WMGetHostName(WMGetFromArray(allowedHostList, i));
709 printf("%s'%s'", i==0?" ":", ", hName);
711 printf(".\n");
712 } else {
713 printf(" any host.\n");
716 serverPtr = WMCreateConnectionAsServerAtAddress(ServerAddress, ServerPort,
717 NULL);
719 if (!serverPtr) {
720 wfatal("could not create server on `%s:%s`. Exiting.",
721 ServerAddress ? ServerAddress : "localhost", ServerPort);
722 exit(1);
725 WMSetConnectionDelegate(serverPtr, &socketDelegate);
727 WMAddNotificationObserver(removeConnection, NULL,
728 SEConnectionShouldBeRemovedNotification, NULL);
730 while (1) {
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. */
734 WHandleEvents();
737 return 0;