- Added example of using WMCOnnection for a server like program.
[wmaker-crm.git] / WINGs / Examples / server.c
blob5fde492e98c67e3cc09c1796c3e168e279ff9195
1 /*
2 * WINGs server.c: example how to create a network server using WMConnection
4 * Copyright (c) 2001 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, /* didCatchException */
46 connectionDidDie, /* didDie */
47 NULL, /* didInitialize */
48 didReceiveInput, /* didReceiveInput */
49 connectionDidTimeout /* didTimeout */
54 void
55 wAbort(Bool foo) /*FOLD00*/
57 exit(1);
61 static void
62 printHelp(char *progname) /*FOLD00*/
64 printf(_("usage: %s [options]\n\n"), progname);
65 puts(_(" --help print this message"));
66 puts(_(" --listen [address:]port only listen on the specified address/port"));
67 puts(_(" --allow host1[,host2...] only allow connections from listed hosts\n"));
68 puts(_(" By default server listens on all interfaces and port 34567, unless"
69 " something\nelse is specified with the --listen option. If address is"
70 " omitted or the keyword\n'Any' is used, it will listen on all interfaces else"
71 " only on the specified one.\n\nFor example --listen localhost: will"
72 " listen on the default port 34567, but only\non connections comming"
73 " in through the loopback interface.\n\n Also by default the server"
74 " listens to incoming connections from any host,\nunless a list of"
75 " hosts is given with the --allow option, in which case it will\nreject"
76 " connections not comming from those hosts.\nThe list of hosts is comma"
77 " separated and should NOT contain ANY spaces."));
81 static void
82 enqueueConnectionForRemoval(WMConnection *cPtr)
84 WMNotification *notif;
86 /*don't release notif here. it will be released by queue after processing */
87 notif = WMCreateNotification(SEConnectionShouldBeRemovedNotification,
88 cPtr, NULL);
89 WMEnqueueNotification(WMGetDefaultNotificationQueue(), notif, WMPostASAP);
93 static int
94 sendMessage(WMConnection *cPtr, char *message)
96 WMData *aData;
97 int res;
99 if (WMGetConnectionState(cPtr)!=WCConnected)
100 return -1;
102 aData = WMCreateDataWithBytes(message, strlen(message));
103 res = WMSendConnectionData(cPtr, aData);
104 WMReleaseData(aData);
106 return res;
110 static Bool
111 enqueueMessage(WMConnection *cPtr, char *message)
113 WMData *aData;
114 Bool res;
116 if (WMGetConnectionState(cPtr)!=WCConnected)
117 return False;
119 aData = WMCreateDataWithBytes(message, strlen(message));
120 res = WMEnqueueConnectionData(cPtr, aData);
121 WMReleaseData(aData);
123 return res;
127 static unsigned char*
128 findDelimiter(unsigned char *data, unsigned const char *endPtr)
130 wassertrv(data < endPtr, NULL);
132 while (data<endPtr && *data!='\n' && *data!='\r' && *data!=';' && *data!='\0')
133 data++;
135 if (data < endPtr)
136 return data;
138 return NULL;
142 static WMArray*
143 getAvailableMessages(WMConnection *cPtr)
145 char *ptr, *crtPos, *buffer;
146 const char *bytes, *endPtr;
147 WMData *aData, *receivedData, *holdData;
148 WMRange range;
149 WMArray *messages;
150 int length;
152 receivedData = WMGetConnectionAvailableData(cPtr);
153 if (!receivedData)
154 return NULL;
155 if ((length=WMGetDataLength(receivedData))==0) {
156 WMReleaseData(receivedData);
157 return NULL;
160 holdData = (WMData*)WMGetConnectionClientData(cPtr);
161 if (holdData) {
162 WMAppendData(holdData, receivedData);
163 WMReleaseData(receivedData);
164 WMSetConnectionClientData(cPtr, NULL);
165 aData = holdData;
166 } else {
167 aData = receivedData;
170 length = WMGetDataLength(aData);
171 bytes = (char*)WMDataBytes(aData);
172 endPtr = bytes + length;
174 messages = WMCreateArrayWithDestructor(1, wfree);
175 crtPos = (char*)bytes;
176 while (crtPos<endPtr && (ptr = findDelimiter(crtPos, endPtr))!=NULL) {
177 range.position = (crtPos - bytes);
178 range.count = (ptr - crtPos);
179 if (range.count > MAXCMD_SIZE) {
180 /* Hmmm... The message is too long. Possibly that someone is
181 * flooding us, or there is a dumb client which do not know
182 * who is talking to. */
183 sendMessage(cPtr, "Command too long\n\r");
184 WMFreeArray(messages);
185 WMReleaseData(aData);
186 WMCloseConnection(cPtr);
187 enqueueConnectionForRemoval(cPtr);
188 return NULL;
190 buffer = wmalloc(range.count+1);
191 WMGetDataBytesWithRange(aData, buffer, range);
192 buffer[range.count] = '\0';
193 WMAddToArray(messages, buffer);
194 crtPos = ptr;
195 while (crtPos<endPtr && (*crtPos=='\n' || *crtPos=='\r' ||
196 *crtPos=='\t' || *crtPos=='\0' ||
197 *crtPos==';' || *crtPos==' ')) {
198 crtPos++;
202 if (crtPos<endPtr) {
203 range.position = (crtPos - bytes);
204 range.count = (endPtr - crtPos);
205 if (range.count > MAXCMD_SIZE) {
206 /* Flooooooding!!!! */
207 sendMessage(cPtr, "Message too long\n\r");
208 WMFreeArray(messages);
209 WMReleaseData(aData);
210 WMCloseConnection(cPtr);
211 enqueueConnectionForRemoval(cPtr);
212 return NULL;
214 holdData = WMGetSubdataWithRange(aData, range);
215 WMSetConnectionClientData(cPtr, holdData);
217 WMReleaseData(aData);
219 if (WMGetArrayItemCount(messages)==0) {
220 WMFreeArray(messages);
221 messages = NULL;
223 return messages;
228 static void
229 complainAboutBadArgs(WMConnection *cPtr, char *cmdName, char *badArgs) /*FOLD00*/
231 char *buf = wmalloc(strlen(cmdName) + strlen(badArgs) + 100);
233 sprintf(buf, _("Invalid parameters '%s' for command %s. Use HELP for"
234 " a list of commands.\n"), badArgs, cmdName);
235 sendMessage(cPtr, buf);
236 wfree(buf);
240 static void
241 sendUpdateMessage(WMConnection *cPtr, char *id, int time) /*FOLD00*/
243 char *buf = wmalloc(strlen(id) + 100);
245 sprintf(buf, "%s has %i minutes left\n", id, time);
246 sendMessage(cPtr, buf);
247 wfree(buf);
251 static void
252 showId(WMConnection *cPtr)
254 sendMessage(cPtr, "Server example based on WMConnection\n");
258 static void
259 showHelp(WMConnection *cPtr) /*FOLD00*/
261 char *buf = wmalloc(strlen(WMGetApplicationName()) + 16);
263 sprintf(buf, _("%s commands:\n\n"), WMGetApplicationName());
265 enqueueMessage(cPtr, _("\n"));
266 enqueueMessage(cPtr, buf);
267 enqueueMessage(cPtr, _("GET <id>\t- return time left (in minutes) "
268 "for user with id <id>\n"));
269 enqueueMessage(cPtr, _("SET <id> <time>\t- set time limit to <time> "
270 "minutes for user with id <id>\n"));
271 enqueueMessage(cPtr, _("ADD <id> <time>\t- add <time> minutes "
272 "for user with id <id>\n"));
273 enqueueMessage(cPtr, _("SUB <id> <time>\t- subtract <time> minutes "
274 "for user with id <id>\n"));
275 enqueueMessage(cPtr, _("REMOVE <id>\t- remove time limitations for "
276 "user with id <id>\n"));
277 enqueueMessage(cPtr, _("LIST\t\t- list all users and their "
278 "corresponding time limit\n"));
279 enqueueMessage(cPtr, _("ID\t\t- returns the Time Manager "
280 "identification string\n"));
281 enqueueMessage(cPtr, _("EXIT\t\t- exits session\n"));
282 enqueueMessage(cPtr, _("QUIT\t\t- exits session\n"));
283 enqueueMessage(cPtr, _("HELP\t\t- show this message\n\n"));
284 /* Just flush the queue we made before */
285 WMFlushConnection(cPtr);
286 wfree(buf);
290 static void
291 listUsers(WMConnection *cPtr)
293 proplist_t userList;
294 char *id;
295 int i, time;
297 userList = WMGetUDAllKeys(timeDB);
299 for (i=0; i<PLGetNumberOfElements(userList); i++) {
300 id = PLGetString(PLGetArrayElement(userList, i));
301 time = WMGetUDIntegerForKey(timeDB, id);
302 sendUpdateMessage(cPtr, id, time);
305 PLRelease(userList);
309 static void
310 setTimeForUser(WMConnection *cPtr, char *cmdArgs) /*FOLD00*/
312 char *id;
313 int i, time;
315 id = wmalloc(strlen(cmdArgs));
316 if (sscanf(cmdArgs, "%s %d", id, &time)!=2) {
317 complainAboutBadArgs(cPtr, "SET", cmdArgs);
318 wfree(id);
319 return;
321 if (time<0)
322 time = 0;
324 WMSetUDIntegerForKey(timeDB, time, id);
326 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
327 cPtr = WMGetFromArray(clientConnections, i);
328 sendUpdateMessage(cPtr, id, time);
330 wfree(id);
334 static void
335 addTimeToUser(WMConnection *cPtr, char *cmdArgs) /*FOLD00*/
337 char *id;
338 int i, time, newTime;
340 id = wmalloc(strlen(cmdArgs));
341 if (sscanf(cmdArgs, "%s %d", id, &time)!=2) {
342 complainAboutBadArgs(cPtr, "ADD", cmdArgs);
343 wfree(id);
344 return;
347 newTime = WMGetUDIntegerForKey(timeDB, id) + time;
348 if (newTime<0)
349 newTime = 0;
351 WMSetUDIntegerForKey(timeDB, newTime, id);
353 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
354 cPtr = WMGetFromArray(clientConnections, i);
355 sendUpdateMessage(cPtr, id, newTime);
357 wfree(id);
361 static void
362 subTimeFromUser(WMConnection *cPtr, char *cmdArgs) /*FOLD00*/
364 char *id;
365 int i, time, newTime;
367 id = wmalloc(strlen(cmdArgs));
368 if (sscanf(cmdArgs, "%s %d", id, &time)!=2) {
369 complainAboutBadArgs(cPtr, "SUB", cmdArgs);
370 wfree(id);
371 return;
374 newTime = WMGetUDIntegerForKey(timeDB, id) - time;
375 if (newTime<0)
376 newTime = 0;
378 WMSetUDIntegerForKey(timeDB, newTime, id);
380 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
381 cPtr = WMGetFromArray(clientConnections, i);
382 sendUpdateMessage(cPtr, id, newTime);
384 wfree(id);
388 static void
389 removeTimeForUser(WMConnection *cPtr, char *cmdArgs) /*FOLD00*/
391 char *ptr;
392 int i;
394 if (cmdArgs[0]=='\0') {
395 sendMessage(cPtr, _("Missing parameter for command REMOVE."
396 " Use HELP for a list of commands.\n"));
397 return;
400 ptr = cmdArgs;
401 while (*ptr && *ptr!=' ' && *ptr!='\t')
402 ptr++;
403 *ptr = '\0';
405 WMRemoveUDObjectForKey(timeDB, cmdArgs);
407 for (i=0; i<WMGetArrayItemCount(clientConnections); i++) {
408 cPtr = WMGetFromArray(clientConnections, i);
409 sendUpdateMessage(cPtr, cmdArgs, -1);
414 static void
415 getTimeForUser(WMConnection *cPtr, char *cmdArgs) /*FOLD00*/
417 char *ptr;
418 int time;
420 if (cmdArgs[0]=='\0') {
421 sendMessage(cPtr, _("Missing parameter for command GET."
422 " Use HELP for a list of commands.\n"));
423 return;
426 ptr = cmdArgs;
427 while (*ptr && *ptr!=' ' && *ptr!='\t')
428 ptr++;
429 *ptr = '\0';
431 if (WMGetUDObjectForKey(timeDB, cmdArgs)!=NULL)
432 time = WMGetUDIntegerForKey(timeDB, cmdArgs);
433 else
434 time = -1;
436 sendUpdateMessage(cPtr, cmdArgs, time);
440 static void
441 handleConnection(WMConnection *cPtr)
443 char *command, *ptr, *cmdArgs, *buffer;
444 WMArray *commands;
445 int i;
447 commands = getAvailableMessages(cPtr);
448 if (!commands)
449 return;
451 for (i=0; i<WMGetArrayItemCount(commands); i++) {
452 command = WMGetFromArray(commands, i);
453 while (*command && (*command==' ' || *command=='\t'))
454 command++;
455 ptr = command;
456 while(*ptr && *ptr!=' ' && *ptr!='\t')
457 ptr++;
458 if (*ptr) {
459 *ptr = '\0';
460 ptr++;
462 while (*ptr && (*ptr==' ' || *ptr=='\t'))
463 ptr++;
465 cmdArgs = ptr;
467 fprintf(stderr, "Command: '%s', args: '%s'\n", command, cmdArgs);
469 if (strcasecmp(command, "quit")==0 || strcasecmp(command, "exit")==0) {
470 sendMessage(cPtr, "Bye\n");
471 WMCloseConnection(cPtr);
472 enqueueConnectionForRemoval(cPtr);
473 WMFreeArray(commands);
474 return;
475 } else if (strcasecmp(command, "id")==0) {
476 showId(cPtr);
477 } else if (strcasecmp(command, "help")==0) {
478 showHelp(cPtr);
479 } else if (strcasecmp(command, "list")==0) {
480 listUsers(cPtr);
481 } else if (strcasecmp(command, "set")==0) {
482 setTimeForUser(cPtr, cmdArgs);
483 } else if (strcasecmp(command, "add")==0) {
484 addTimeToUser(cPtr, cmdArgs);
485 } else if (strcasecmp(command, "sub")==0) {
486 subTimeFromUser(cPtr, cmdArgs);
487 } else if (strcasecmp(command, "remove")==0) {
488 removeTimeForUser(cPtr, cmdArgs);
489 } else if (strcasecmp(command, "get")==0) {
490 getTimeForUser(cPtr, cmdArgs);
491 } else {
492 buffer = wmalloc(strlen(command) + 100);
493 sprintf(buffer, _("Unknown command '%s'. Try HELP for"
494 " a list of commands.\n"), command);
495 sendMessage(cPtr, buffer);
496 wfree(buffer);
500 WMFreeArray(commands);
504 static Bool
505 isAllowedToConnect(WMConnection *cPtr)
507 WMHost *hPtr;
508 int i;
510 if (allowedHostList == NULL)
511 return True; /* No list. Allow all by default */
513 hPtr = WMGetHostWithAddress(WMGetConnectionAddress(cPtr));
514 for (i=0; i<WMGetArrayItemCount(allowedHostList); i++) {
515 if (WMIsHostEqualToHost(hPtr, WMGetFromArray(allowedHostList, i))) {
516 WMReleaseHost(hPtr);
517 return True;
521 WMReleaseHost(hPtr);
523 return False;
527 static void
528 didReceiveInput(ConnectionDelegate *self, WMConnection *cPtr) /*FOLD00*/
530 if (cPtr == serverPtr) {
531 WMConnection *newPtr = WMAcceptConnection(cPtr);
533 if (newPtr) {
534 if (isAllowedToConnect(newPtr)) {
535 WMSetConnectionDelegate(newPtr, &socketDelegate);
536 WMSetConnectionSendTimeout(newPtr, 120);
537 WMAddToArray(clientConnections, newPtr);
538 } else {
539 sendMessage(newPtr, "Sorry, you are not allowed to connect.\n");
540 WMDestroyConnection(newPtr);
543 } else {
544 /* Data arriving on an already-connected socket */
545 handleConnection(cPtr);
550 static void
551 connectionDidTimeout(ConnectionDelegate *self, WMConnection *cPtr) /*FOLD00*/
553 WMHost *hPtr;
555 if (cPtr == serverPtr) {
556 wfatal(_("The server listening socket did timeout. Exiting."));
557 exit(1);
560 hPtr = WMGetHostWithAddress(WMGetConnectionAddress(cPtr));
561 wwarning(_("Connection with %s did timeout. Closing connection."),
562 WMGetHostName(hPtr));
563 WMReleaseHost(hPtr);
565 enqueueConnectionForRemoval(cPtr);
569 static void
570 connectionDidDie(ConnectionDelegate *self, WMConnection *cPtr)
572 if (cPtr == serverPtr) {
573 /* trouble. server listening port itself died!!! */
574 wfatal(_("The server listening socket died. Exiting."));
575 exit(1);
578 enqueueConnectionForRemoval(cPtr);
582 static void
583 removeConnection(void *observer, WMNotification *notification)
585 WMConnection *cPtr = (WMConnection*)WMGetNotificationObject(notification);
586 WMData *data;
588 WMRemoveFromArray(clientConnections, cPtr);
589 if ((data = (WMData*)WMGetConnectionClientData(cPtr))!=NULL)
590 WMReleaseData(data);
591 WMDestroyConnection(cPtr);
595 static Bool
596 isDifferent(char *str1, char *str2) /*FOLD00*/
598 if ((!str1 && !str2) || (str1 && str2 && strcmp(str1, str2)==0))
599 return False;
601 return True;
606 main(int argc, char **argv) /*FOLD00*/
608 int i;
610 wsetabort(wAbort);
612 WMInitializeApplication("server", &argc, argv);
614 if (argc>1) {
615 for (i=1; i<argc; i++) {
616 if (strcmp(argv[i], "--help")==0) {
617 printHelp(argv[0]);
618 exit(0);
619 } else if (strcmp(argv[i], "--listen")==0) {
620 char *p;
622 if ((p = strchr(argv[++i], ':')) != NULL) {
623 *p = 0;
624 ServerAddress = wstrdup(argv[i]);
625 ServerPort = wstrdup(p+1);
626 *p = ':';
627 if (ServerAddress[0] == 0) {
628 wfree(ServerAddress);
629 ServerAddress = NULL;
631 if (ServerPort[0] == 0) {
632 wfree(ServerPort);
633 ServerPort = "34567";
635 } else if (argv[i][0]!=0) {
636 ServerPort = argv[i];
638 } else if (strcmp(argv[i], "--allow")==0) {
639 char *p, *ptr;
640 int done;
641 WMHost *hPtr;
643 ptr = argv[++i];
644 done = 0;
645 while (!done) {
646 if ((p = strchr(ptr, ',')) != NULL) {
647 *p = 0;
649 if (*ptr != 0) {
650 hPtr = WMGetHostWithName(ptr);
651 if (hPtr) {
652 if (!allowedHostList)
653 allowedHostList = WMCreateArray(4);
654 WMAddToArray(allowedHostList, hPtr);
655 } else {
656 wwarning(_("Unknown host '%s'. Ignored."), ptr);
660 if (p!=NULL) {
661 *p = ',';
662 ptr = p+1;
663 } else {
664 done = 1;
667 } else {
668 printf(_("%s: invalid argument '%s'\n"), argv[0], argv[i]);
669 printf(_("Try '%s --help' for more information\n"), argv[0]);
670 exit(1);
675 timeDB = WMGetDefaultsFromPath("./UserTime.plist");
677 clientConnections = WMCreateArray(4);
679 /* A NULL ServerAddress means to listen on any address the host has.
680 * Else if ServerAddress points to a specific address (like "localhost",
681 * "host.domain.com" or "192.168.1.1"), then it will only listen on that
682 * interface and ignore incoming connections on the others. */
683 if (ServerAddress && strcasecmp(ServerAddress, "Any")==0)
684 ServerAddress = NULL;
685 if (ServerPort==NULL)
686 ServerPort = "34567";
688 printf("Server will listen on '%s:%s'\n", ServerAddress?ServerAddress:"Any",
689 ServerPort);
690 printf("This server will allow connections from:");
691 if (allowedHostList) {
692 int i;
693 char *hName;
695 for (i=0; i<WMGetArrayItemCount(allowedHostList); i++) {
696 hName = WMGetHostName(WMGetFromArray(allowedHostList, i));
697 printf("%s'%s'", i==0?" ":", ", hName);
699 printf(".\n");
700 } else {
701 printf(" any host.\n");
704 serverPtr = WMCreateConnectionAsServerAtAddress(ServerAddress, ServerPort,
705 NULL);
707 if (!serverPtr) {
708 wfatal("could not create server on `%s:%s`. Exiting.",
709 ServerAddress ? ServerAddress : "localhost", ServerPort);
710 exit(1);
713 WMSetConnectionDelegate(serverPtr, &socketDelegate);
715 WMAddNotificationObserver(removeConnection, NULL,
716 SEConnectionShouldBeRemovedNotification, NULL);
718 while (1) {
719 /* The ASAP notification queue is called at the end of WHandleEvents()
720 * There's where died connections we get while running through
721 * WHandleEvents() get removed. */
722 WHandleEvents();
725 return 0;