- new function in WINGs: WMSetConnectionShutdownOnClose()
[wmaker-crm.git] / WINGs / Examples / server.c
blob502febcd92ca1a9cf818ea10b661ab1cb33717cb
1 /*
2 * WINGs server.c: example how to create a network server using WMConnection
4 * Copyright (c) 2001-2002 Dan Pascu
6 */
9 #include <stdio.h>
10 #include <unistd.h>
11 #include <string.h>
13 #include <WINGs/WINGs.h>
16 #define _(P) P
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 */
55 void
56 wAbort(Bool foo)
58 exit(1);
62 static void
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."));
82 static void
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,
89 cPtr, NULL);
90 WMEnqueueNotification(WMGetDefaultNotificationQueue(), notif, WMPostASAP);
94 static int
95 sendMessage(WMConnection *cPtr, char *message)
97 WMData *aData;
98 int res;
100 if (WMGetConnectionState(cPtr)!=WCConnected)
101 return -1;
103 aData = WMCreateDataWithBytes(message, strlen(message));
104 res = WMSendConnectionData(cPtr, aData);
105 WMReleaseData(aData);
107 return res;
111 static Bool
112 enqueueMessage(WMConnection *cPtr, char *message)
114 WMData *aData;
115 Bool res;
117 if (WMGetConnectionState(cPtr)!=WCConnected)
118 return False;
120 aData = WMCreateDataWithBytes(message, strlen(message));
121 res = WMEnqueueConnectionData(cPtr, aData);
122 WMReleaseData(aData);
124 return res;
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')
134 data++;
136 if (data < endPtr)
137 return data;
139 return NULL;
143 static WMArray*
144 getAvailableMessages(WMConnection *cPtr)
146 char *ptr, *crtPos, *buffer;
147 const char *bytes, *endPtr;
148 WMData *aData, *receivedData, *holdData;
149 WMRange range;
150 WMArray *messages;
151 int length;
153 receivedData = WMGetConnectionAvailableData(cPtr);
154 if (!receivedData)
155 return NULL;
156 if ((length=WMGetDataLength(receivedData))==0) {
157 WMReleaseData(receivedData);
158 return NULL;
161 holdData = (WMData*)WMGetConnectionClientData(cPtr);
162 if (holdData) {
163 WMAppendData(holdData, receivedData);
164 WMReleaseData(receivedData);
165 WMSetConnectionClientData(cPtr, NULL);
166 aData = holdData;
167 } else {
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);
189 return NULL;
191 buffer = wmalloc(range.count+1);
192 WMGetDataBytesWithRange(aData, buffer, range);
193 buffer[range.count] = '\0';
194 WMAddToArray(messages, buffer);
195 crtPos = ptr;
196 while (crtPos<endPtr && (*crtPos=='\n' || *crtPos=='\r' ||
197 *crtPos=='\t' || *crtPos=='\0' ||
198 *crtPos==';' || *crtPos==' ')) {
199 crtPos++;
203 if (crtPos<endPtr) {
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);
213 return NULL;
215 holdData = WMGetSubdataWithRange(aData, range);
216 WMSetConnectionClientData(cPtr, holdData);
218 WMReleaseData(aData);
220 if (WMGetArrayItemCount(messages)==0) {
221 WMFreeArray(messages);
222 messages = NULL;
224 return messages;
229 static void
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);
237 wfree(buf);
241 static void
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);
248 wfree(buf);
252 static void
253 showId(WMConnection *cPtr)
255 sendMessage(cPtr, "Server example based on WMConnection\n");
259 static void
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);
287 wfree(buf);
291 static void
292 listUsers(WMConnection *cPtr)
294 WMPropList *userList;
295 char *id;
296 int i, time;
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);
310 static void
311 setTimeForUser(WMConnection *cPtr, char *cmdArgs)
313 char *id;
314 int i, time;
316 id = wmalloc(strlen(cmdArgs));
317 if (sscanf(cmdArgs, "%s %d", id, &time)!=2) {
318 complainAboutBadArgs(cPtr, "SET", cmdArgs);
319 wfree(id);
320 return;
322 if (time<0)
323 time = 0;
325 WMSetUDIntegerForKey(timeDB, time, id);
327 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
328 cPtr = WMGetFromArray(clientConnections, i);
329 sendUpdateMessage(cPtr, id, time);
331 wfree(id);
335 static void
336 addTimeToUser(WMConnection *cPtr, char *cmdArgs)
338 char *id;
339 int i, time, newTime;
341 id = wmalloc(strlen(cmdArgs));
342 if (sscanf(cmdArgs, "%s %d", id, &time)!=2) {
343 complainAboutBadArgs(cPtr, "ADD", cmdArgs);
344 wfree(id);
345 return;
348 newTime = WMGetUDIntegerForKey(timeDB, id) + time;
349 if (newTime<0)
350 newTime = 0;
352 WMSetUDIntegerForKey(timeDB, newTime, id);
354 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
355 cPtr = WMGetFromArray(clientConnections, i);
356 sendUpdateMessage(cPtr, id, newTime);
358 wfree(id);
362 static void
363 subTimeFromUser(WMConnection *cPtr, char *cmdArgs)
365 char *id;
366 int i, time, newTime;
368 id = wmalloc(strlen(cmdArgs));
369 if (sscanf(cmdArgs, "%s %d", id, &time)!=2) {
370 complainAboutBadArgs(cPtr, "SUB", cmdArgs);
371 wfree(id);
372 return;
375 newTime = WMGetUDIntegerForKey(timeDB, id) - time;
376 if (newTime<0)
377 newTime = 0;
379 WMSetUDIntegerForKey(timeDB, newTime, id);
381 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
382 cPtr = WMGetFromArray(clientConnections, i);
383 sendUpdateMessage(cPtr, id, newTime);
385 wfree(id);
389 static void
390 removeTimeForUser(WMConnection *cPtr, char *cmdArgs)
392 char *ptr;
393 int i;
395 if (cmdArgs[0]=='\0') {
396 sendMessage(cPtr, _("Missing parameter for command REMOVE."
397 " Use HELP for a list of commands.\n"));
398 return;
401 ptr = cmdArgs;
402 while (*ptr && *ptr!=' ' && *ptr!='\t')
403 ptr++;
404 *ptr = '\0';
406 WMRemoveUDObjectForKey(timeDB, cmdArgs);
408 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
409 cPtr = WMGetFromArray(clientConnections, i);
410 sendUpdateMessage(cPtr, cmdArgs, -1);
415 static void
416 getTimeForUser(WMConnection *cPtr, char *cmdArgs)
418 char *ptr;
419 int time;
421 if (cmdArgs[0]=='\0') {
422 sendMessage(cPtr, _("Missing parameter for command GET."
423 " Use HELP for a list of commands.\n"));
424 return;
427 ptr = cmdArgs;
428 while (*ptr && *ptr!=' ' && *ptr!='\t')
429 ptr++;
430 *ptr = '\0';
432 if (WMGetUDObjectForKey(timeDB, cmdArgs)!=NULL)
433 time = WMGetUDIntegerForKey(timeDB, cmdArgs);
434 else
435 time = -1;
437 sendUpdateMessage(cPtr, cmdArgs, time);
441 static void
442 handleConnection(WMConnection *cPtr)
444 char *command, *ptr, *cmdArgs, *buffer;
445 WMArray *commands;
446 int i;
448 commands = getAvailableMessages(cPtr);
449 if (!commands)
450 return;
452 for (i=0; i<WMGetArrayItemCount(commands); i++) {
453 command = WMGetFromArray(commands, i);
454 while (*command && (*command==' ' || *command=='\t'))
455 command++;
456 ptr = command;
457 while(*ptr && *ptr!=' ' && *ptr!='\t')
458 ptr++;
459 if (*ptr) {
460 *ptr = '\0';
461 ptr++;
463 while (*ptr && (*ptr==' ' || *ptr=='\t'))
464 ptr++;
466 cmdArgs = ptr;
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);
475 return;
476 } else if (strcasecmp(command, "id")==0) {
477 showId(cPtr);
478 } else if (strcasecmp(command, "help")==0) {
479 showHelp(cPtr);
480 } else if (strcasecmp(command, "list")==0) {
481 listUsers(cPtr);
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);
492 } else {
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);
497 wfree(buffer);
501 WMFreeArray(commands);
505 static Bool
506 isAllowedToConnect(WMConnection *cPtr)
508 WMHost *hPtr;
509 int i;
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))) {
517 WMReleaseHost(hPtr);
518 return True;
522 WMReleaseHost(hPtr);
524 return False;
528 static void
529 didReceiveInput(ConnectionDelegate *self, WMConnection *cPtr)
531 if (cPtr == serverPtr) {
532 WMConnection *newPtr = WMAcceptConnection(cPtr);
534 if (newPtr) {
535 if (isAllowedToConnect(newPtr)) {
536 WMSetConnectionDelegate(newPtr, &socketDelegate);
537 WMSetConnectionSendTimeout(newPtr, 120);
538 WMAddToArray(clientConnections, newPtr);
539 } else {
540 sendMessage(newPtr, "Sorry, you are not allowed to connect.\n");
541 WMDestroyConnection(newPtr);
544 } else {
545 /* Data arriving on an already-connected socket */
546 handleConnection(cPtr);
551 static void
552 connectionDidTimeout(ConnectionDelegate *self, WMConnection *cPtr)
554 WMHost *hPtr;
556 if (cPtr == serverPtr) {
557 wfatal(_("The server listening socket did timeout. Exiting."));
558 exit(1);
561 hPtr = WMGetHostWithAddress(WMGetConnectionAddress(cPtr));
562 wwarning(_("Connection with %s did timeout. Closing connection."),
563 WMGetHostName(hPtr));
564 WMReleaseHost(hPtr);
566 enqueueConnectionForRemoval(cPtr);
570 static void
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."));
576 exit(1);
579 enqueueConnectionForRemoval(cPtr);
583 static void
584 removeConnection(void *observer, WMNotification *notification)
586 WMConnection *cPtr = (WMConnection*)WMGetNotificationObject(notification);
587 WMData *data;
589 WMRemoveFromArray(clientConnections, cPtr);
590 if ((data = (WMData*)WMGetConnectionClientData(cPtr))!=NULL)
591 WMReleaseData(data);
592 WMDestroyConnection(cPtr);
595 static void
596 updatedDomain(void *observer, WMNotification *notification)
598 wmessage("defaults domain file changed on disk. synchronizing.");
602 #if 0
603 static Bool
604 isDifferent(char *str1, char *str2)
606 if ((!str1 && !str2) || (str1 && str2 && strcmp(str1, str2)==0))
607 return False;
609 return True;
611 #endif
615 main(int argc, char **argv)
617 int i;
619 wsetabort(wAbort);
621 WMInitializeApplication("server", &argc, argv);
623 if (argc>1) {
624 for (i=1; i<argc; i++) {
625 if (strcmp(argv[i], "--help")==0) {
626 printHelp(argv[0]);
627 exit(0);
628 } else if (strcmp(argv[i], "--listen")==0) {
629 char *p;
631 if ((p = strchr(argv[++i], ':')) != NULL) {
632 *p = 0;
633 ServerAddress = wstrdup(argv[i]);
634 ServerPort = wstrdup(p+1);
635 *p = ':';
636 if (ServerAddress[0] == 0) {
637 wfree(ServerAddress);
638 ServerAddress = NULL;
640 if (ServerPort[0] == 0) {
641 wfree(ServerPort);
642 ServerPort = "34567";
644 } else if (argv[i][0]!=0) {
645 ServerPort = argv[i];
647 } else if (strcmp(argv[i], "--allow")==0) {
648 char *p, *ptr;
649 int done;
650 WMHost *hPtr;
652 ptr = argv[++i];
653 done = 0;
654 while (!done) {
655 if ((p = strchr(ptr, ',')) != NULL) {
656 *p = 0;
658 if (*ptr != 0) {
659 hPtr = WMGetHostWithName(ptr);
660 if (hPtr) {
661 if (!allowedHostList)
662 allowedHostList = WMCreateArray(4);
663 WMAddToArray(allowedHostList, hPtr);
664 } else {
665 wwarning(_("Unknown host '%s'. Ignored."), ptr);
669 if (p!=NULL) {
670 *p = ',';
671 ptr = p+1;
672 } else {
673 done = 1;
676 } else {
677 printf(_("%s: invalid argument '%s'\n"), argv[0], argv[i]);
678 printf(_("Try '%s --help' for more information\n"), argv[0]);
679 exit(1);
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",
700 ServerPort);
701 printf("This server will allow connections from:");
702 if (allowedHostList) {
703 int i;
704 char *hName;
706 for (i=0; i<WMGetArrayItemCount(allowedHostList); i++) {
707 hName = WMGetHostName(WMGetFromArray(allowedHostList, i));
708 printf("%s'%s'", i==0?" ":", ", hName);
710 printf(".\n");
711 } else {
712 printf(" any host.\n");
715 serverPtr = WMCreateConnectionAsServerAtAddress(ServerAddress, ServerPort,
716 NULL);
718 if (!serverPtr) {
719 wfatal("could not create server on `%s:%s`. Exiting.",
720 ServerAddress ? ServerAddress : "localhost", ServerPort);
721 exit(1);
724 WMSetConnectionDelegate(serverPtr, &socketDelegate);
726 WMAddNotificationObserver(removeConnection, NULL,
727 SEConnectionShouldBeRemovedNotification, NULL);
729 while (1) {
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. */
733 WHandleEvents();
736 return 0;