Change to the linux kernel coding style
[wmaker-crm.git] / WINGs / Examples / server.c
1 /*
2  *  WINGs server.c: example how to create a network server using WMConnection
3  *
4  *  Copyright (c) 2001-2003 Dan Pascu
5  *
6  */
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <string.h>
12
13 #include <WINGs/WINGs.h>
14
15 #define _(P)         P
16 #define MAXCMD_SIZE  512
17
18 char *SEConnectionShouldBeRemovedNotification = "SEConnectionShouldBeRemovedNotification";
19
20 static void didReceiveInput(ConnectionDelegate * self, WMConnection * cPtr);
21
22 static void connectionDidDie(ConnectionDelegate * self, WMConnection * cPtr);
23
24 static void connectionDidTimeout(ConnectionDelegate * self, WMConnection * cPtr);
25
26 extern char *SEConnectionShouldBeRemovedNotification;
27
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;
34
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 */
43 };
44
45 void wAbort(Bool foo)
46 {
47         exit(1);
48 }
49
50 static void printHelp(char *progname)
51 {
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."));
66 }
67
68 static void enqueueConnectionForRemoval(WMConnection * cPtr)
69 {
70         WMNotification *notif;
71
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);
75 }
76
77 static int sendMessage(WMConnection * cPtr, char *message)
78 {
79         WMData *aData;
80         int res;
81
82         if (WMGetConnectionState(cPtr) != WCConnected)
83                 return -1;
84
85         aData = WMCreateDataWithBytes(message, strlen(message));
86         res = WMSendConnectionData(cPtr, aData);
87         WMReleaseData(aData);
88
89         return res;
90 }
91
92 static Bool enqueueMessage(WMConnection * cPtr, char *message)
93 {
94         WMData *aData;
95         Bool res;
96
97         if (WMGetConnectionState(cPtr) != WCConnected)
98                 return False;
99
100         aData = WMCreateDataWithBytes(message, strlen(message));
101         res = WMEnqueueConnectionData(cPtr, aData);
102         WMReleaseData(aData);
103
104         return res;
105 }
106
107 static char *findDelimiter(char *data, const char *endPtr)
108 {
109         wassertrv(data < endPtr, NULL);
110
111         while (data < endPtr && *data != '\n' && *data != '\r' && *data != ';' && *data != '\0')
112                 data++;
113
114         if (data < endPtr)
115                 return data;
116
117         return NULL;
118 }
119
120 static WMArray *getAvailableMessages(WMConnection * cPtr)
121 {
122         char *ptr, *crtPos, *buffer;
123         const char *bytes, *endPtr;
124         WMData *aData, *receivedData, *holdData;
125         WMRange range;
126         WMArray *messages;
127         int length;
128
129         receivedData = WMGetConnectionAvailableData(cPtr);
130         if (!receivedData)
131                 return NULL;
132         if ((length = WMGetDataLength(receivedData)) == 0) {
133                 WMReleaseData(receivedData);
134                 return NULL;
135         }
136
137         holdData = (WMData *) WMGetConnectionClientData(cPtr);
138         if (holdData) {
139                 WMAppendData(holdData, receivedData);
140                 WMReleaseData(receivedData);
141                 WMSetConnectionClientData(cPtr, NULL);
142                 aData = holdData;
143         } else {
144                 aData = receivedData;
145         }
146
147         length = WMGetDataLength(aData);
148         bytes = (char *)WMDataBytes(aData);
149         endPtr = bytes + length;
150
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);
165                         return NULL;
166                 }
167                 buffer = wmalloc(range.count + 1);
168                 WMGetDataBytesWithRange(aData, buffer, range);
169                 buffer[range.count] = '\0';
170                 WMAddToArray(messages, buffer);
171                 crtPos = ptr;
172                 while (crtPos < endPtr && (*crtPos == '\n' || *crtPos == '\r' ||
173                                            *crtPos == '\t' || *crtPos == '\0' ||
174                                            *crtPos == ';' || *crtPos == ' ')) {
175                         crtPos++;
176                 }
177         }
178
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);
189                         return NULL;
190                 }
191                 holdData = WMGetSubdataWithRange(aData, range);
192                 WMSetConnectionClientData(cPtr, holdData);
193         }
194         WMReleaseData(aData);
195
196         if (WMGetArrayItemCount(messages) == 0) {
197                 WMFreeArray(messages);
198                 messages = NULL;
199         }
200         return messages;
201 }
202
203 static void complainAboutBadArgs(WMConnection * cPtr, char *cmdName, char *badArgs)
204 {
205         char *buf = wmalloc(strlen(cmdName) + strlen(badArgs) + 100);
206
207         sprintf(buf, _("Invalid parameters '%s' for command %s. Use HELP for"
208                        " a list of commands.\n"), badArgs, cmdName);
209         sendMessage(cPtr, buf);
210         wfree(buf);
211 }
212
213 static void sendUpdateMessage(WMConnection * cPtr, char *id, int time)
214 {
215         char *buf = wmalloc(strlen(id) + 100);
216
217         sprintf(buf, "%s has %i minutes left\n", id, time);
218         sendMessage(cPtr, buf);
219         wfree(buf);
220 }
221
222 static void showId(WMConnection * cPtr)
223 {
224         sendMessage(cPtr, "Server example based on WMConnection\n");
225 }
226
227 static void showHelp(WMConnection * cPtr)
228 {
229         char *buf = wmalloc(strlen(WMGetApplicationName()) + 16);
230
231         sprintf(buf, _("%s commands:\n\n"), WMGetApplicationName());
232
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);
247         wfree(buf);
248 }
249
250 static void listUsers(WMConnection * cPtr)
251 {
252         WMPropList *userList;
253         char *id;
254         int i, time;
255
256         userList = WMGetUDKeys(timeDB);
257
258         for (i = 0; i < WMGetPropListItemCount(userList); i++) {
259                 id = WMGetFromPLString(WMGetFromPLArray(userList, i));
260                 time = WMGetUDIntegerForKey(timeDB, id);
261                 sendUpdateMessage(cPtr, id, time);
262         }
263
264         WMReleasePropList(userList);
265 }
266
267 static void setTimeForUser(WMConnection * cPtr, char *cmdArgs)
268 {
269         char *id;
270         int i, time;
271
272         id = wmalloc(strlen(cmdArgs));
273         if (sscanf(cmdArgs, "%s %d", id, &time) != 2) {
274                 complainAboutBadArgs(cPtr, "SET", cmdArgs);
275                 wfree(id);
276                 return;
277         }
278         if (time < 0)
279                 time = 0;
280
281         WMSetUDIntegerForKey(timeDB, time, id);
282
283         for (i = 0; i < WMGetArrayItemCount(clientConnections); i++) {
284                 cPtr = WMGetFromArray(clientConnections, i);
285                 sendUpdateMessage(cPtr, id, time);
286         }
287         wfree(id);
288 }
289
290 static void addTimeToUser(WMConnection * cPtr, char *cmdArgs)
291 {
292         char *id;
293         int i, time, newTime;
294
295         id = wmalloc(strlen(cmdArgs));
296         if (sscanf(cmdArgs, "%s %d", id, &time) != 2) {
297                 complainAboutBadArgs(cPtr, "ADD", cmdArgs);
298                 wfree(id);
299                 return;
300         }
301
302         newTime = WMGetUDIntegerForKey(timeDB, id) + time;
303         if (newTime < 0)
304                 newTime = 0;
305
306         WMSetUDIntegerForKey(timeDB, newTime, id);
307
308         for (i = 0; i < WMGetArrayItemCount(clientConnections); i++) {
309                 cPtr = WMGetFromArray(clientConnections, i);
310                 sendUpdateMessage(cPtr, id, newTime);
311         }
312         wfree(id);
313 }
314
315 static void subTimeFromUser(WMConnection * cPtr, char *cmdArgs)
316 {
317         char *id;
318         int i, time, newTime;
319
320         id = wmalloc(strlen(cmdArgs));
321         if (sscanf(cmdArgs, "%s %d", id, &time) != 2) {
322                 complainAboutBadArgs(cPtr, "SUB", cmdArgs);
323                 wfree(id);
324                 return;
325         }
326
327         newTime = WMGetUDIntegerForKey(timeDB, id) - time;
328         if (newTime < 0)
329                 newTime = 0;
330
331         WMSetUDIntegerForKey(timeDB, newTime, id);
332
333         for (i = 0; i < WMGetArrayItemCount(clientConnections); i++) {
334                 cPtr = WMGetFromArray(clientConnections, i);
335                 sendUpdateMessage(cPtr, id, newTime);
336         }
337         wfree(id);
338 }
339
340 static void removeTimeForUser(WMConnection * cPtr, char *cmdArgs)
341 {
342         char *ptr;
343         int i;
344
345         if (cmdArgs[0] == '\0') {
346                 sendMessage(cPtr, _("Missing parameter for command REMOVE."
347                                     " Use HELP for a list of commands.\n"));
348                 return;
349         }
350
351         ptr = cmdArgs;
352         while (*ptr && *ptr != ' ' && *ptr != '\t')
353                 ptr++;
354         *ptr = '\0';
355
356         WMRemoveUDObjectForKey(timeDB, cmdArgs);
357
358         for (i = 0; i < WMGetArrayItemCount(clientConnections); i++) {
359                 cPtr = WMGetFromArray(clientConnections, i);
360                 sendUpdateMessage(cPtr, cmdArgs, -1);
361         }
362 }
363
364 static void getTimeForUser(WMConnection * cPtr, char *cmdArgs)
365 {
366         char *ptr;
367         int time;
368
369         if (cmdArgs[0] == '\0') {
370                 sendMessage(cPtr, _("Missing parameter for command GET." " Use HELP for a list of commands.\n"));
371                 return;
372         }
373
374         ptr = cmdArgs;
375         while (*ptr && *ptr != ' ' && *ptr != '\t')
376                 ptr++;
377         *ptr = '\0';
378
379         if (WMGetUDObjectForKey(timeDB, cmdArgs) != NULL)
380                 time = WMGetUDIntegerForKey(timeDB, cmdArgs);
381         else
382                 time = -1;
383
384         sendUpdateMessage(cPtr, cmdArgs, time);
385 }
386
387 static void handleConnection(WMConnection * cPtr)
388 {
389         char *command, *ptr, *cmdArgs, *buffer;
390         WMArray *commands;
391         int i;
392
393         commands = getAvailableMessages(cPtr);
394         if (!commands)
395                 return;
396
397         for (i = 0; i < WMGetArrayItemCount(commands); i++) {
398                 command = WMGetFromArray(commands, i);
399                 while (*command && (*command == ' ' || *command == '\t'))
400                         command++;
401                 ptr = command;
402                 while (*ptr && *ptr != ' ' && *ptr != '\t')
403                         ptr++;
404                 if (*ptr) {
405                         *ptr = '\0';
406                         ptr++;
407                 }
408                 while (*ptr && (*ptr == ' ' || *ptr == '\t'))
409                         ptr++;
410
411                 cmdArgs = ptr;
412
413                 fprintf(stderr, "Command: '%s', args: '%s'\n", command, cmdArgs);
414
415                 if (strcasecmp(command, "quit") == 0 || strcasecmp(command, "exit") == 0) {
416                         sendMessage(cPtr, "Bye\n");
417                         WMCloseConnection(cPtr);
418                         enqueueConnectionForRemoval(cPtr);
419                         WMFreeArray(commands);
420                         return;
421                 } else if (strcasecmp(command, "id") == 0) {
422                         showId(cPtr);
423                 } else if (strcasecmp(command, "help") == 0) {
424                         showHelp(cPtr);
425                 } else if (strcasecmp(command, "list") == 0) {
426                         listUsers(cPtr);
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);
437                 } else {
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);
441                         wfree(buffer);
442                 }
443         }
444
445         WMFreeArray(commands);
446 }
447
448 static Bool isAllowedToConnect(WMConnection * cPtr)
449 {
450         WMHost *hPtr;
451         int i;
452
453         if (allowedHostList == NULL)
454                 return True;    /* No list. Allow all by default */
455
456         hPtr = WMGetHostWithAddress(WMGetConnectionAddress(cPtr));
457         for (i = 0; i < WMGetArrayItemCount(allowedHostList); i++) {
458                 if (WMIsHostEqualToHost(hPtr, WMGetFromArray(allowedHostList, i))) {
459                         WMReleaseHost(hPtr);
460                         return True;
461                 }
462         }
463
464         WMReleaseHost(hPtr);
465
466         return False;
467 }
468
469 static void didReceiveInput(ConnectionDelegate * self, WMConnection * cPtr)
470 {
471         if (cPtr == serverPtr) {
472                 WMConnection *newPtr = WMAcceptConnection(cPtr);
473
474                 if (newPtr) {
475                         if (isAllowedToConnect(newPtr)) {
476                                 WMSetConnectionDelegate(newPtr, &socketDelegate);
477                                 WMSetConnectionSendTimeout(newPtr, 120);
478                                 WMAddToArray(clientConnections, newPtr);
479                         } else {
480                                 sendMessage(newPtr, "Sorry, you are not allowed to connect.\n");
481                                 WMDestroyConnection(newPtr);
482                         }
483                 }
484         } else {
485                 /* Data arriving on an already-connected socket */
486                 handleConnection(cPtr);
487         }
488 }
489
490 static void connectionDidTimeout(ConnectionDelegate * self, WMConnection * cPtr)
491 {
492         WMHost *hPtr;
493
494         if (cPtr == serverPtr) {
495                 wfatal(_("The server listening socket did timeout. Exiting."));
496                 exit(1);
497         }
498
499         hPtr = WMGetHostWithAddress(WMGetConnectionAddress(cPtr));
500         wwarning(_("Connection with %s did timeout. Closing connection."), WMGetHostName(hPtr));
501         WMReleaseHost(hPtr);
502
503         enqueueConnectionForRemoval(cPtr);
504 }
505
506 static void connectionDidDie(ConnectionDelegate * self, WMConnection * cPtr)
507 {
508         if (cPtr == serverPtr) {
509                 /* trouble. server listening port itself died!!! */
510                 wfatal(_("The server listening socket died. Exiting."));
511                 exit(1);
512         }
513
514         enqueueConnectionForRemoval(cPtr);
515 }
516
517 static void removeConnection(void *observer, WMNotification * notification)
518 {
519         WMConnection *cPtr = (WMConnection *) WMGetNotificationObject(notification);
520         WMData *data;
521
522         WMRemoveFromArray(clientConnections, cPtr);
523         if ((data = (WMData *) WMGetConnectionClientData(cPtr)) != NULL)
524                 WMReleaseData(data);
525         WMDestroyConnection(cPtr);
526 }
527
528 static void updatedDomain(void *observer, WMNotification * notification)
529 {
530         wmessage("defaults domain file changed on disk. synchronizing.");
531 }
532
533 #if 0
534 static Bool isDifferent(char *str1, char *str2)
535 {
536         if ((!str1 && !str2) || (str1 && str2 && strcmp(str1, str2) == 0))
537                 return False;
538
539         return True;
540 }
541 #endif
542
543 int main(int argc, char **argv)
544 {
545         int i;
546
547         wsetabort(wAbort);
548
549         WMInitializeApplication("server", &argc, argv);
550
551         if (argc > 1) {
552                 for (i = 1; i < argc; i++) {
553                         if (strcmp(argv[i], "--help") == 0) {
554                                 printHelp(argv[0]);
555                                 exit(0);
556                         } else if (strcmp(argv[i], "--listen") == 0) {
557                                 char *p;
558
559                                 if ((p = strchr(argv[++i], ':')) != NULL) {
560                                         *p = 0;
561                                         ServerAddress = wstrdup(argv[i]);
562                                         ServerPort = wstrdup(p + 1);
563                                         *p = ':';
564                                         if (ServerAddress[0] == 0) {
565                                                 wfree(ServerAddress);
566                                                 ServerAddress = NULL;
567                                         }
568                                         if (ServerPort[0] == 0) {
569                                                 wfree(ServerPort);
570                                                 ServerPort = "34567";
571                                         }
572                                 } else if (argv[i][0] != 0) {
573                                         ServerPort = argv[i];
574                                 }
575                         } else if (strcmp(argv[i], "--allow") == 0) {
576                                 char *p, *ptr;
577                                 int done;
578                                 WMHost *hPtr;
579
580                                 ptr = argv[++i];
581                                 done = 0;
582                                 while (!done) {
583                                         if ((p = strchr(ptr, ',')) != NULL) {
584                                                 *p = 0;
585                                         }
586                                         if (*ptr != 0) {
587                                                 hPtr = WMGetHostWithName(ptr);
588                                                 if (hPtr) {
589                                                         if (!allowedHostList)
590                                                                 allowedHostList = WMCreateArray(4);
591                                                         WMAddToArray(allowedHostList, hPtr);
592                                                 } else {
593                                                         wwarning(_("Unknown host '%s'. Ignored."), ptr);
594                                                 }
595                                         }
596
597                                         if (p != NULL) {
598                                                 *p = ',';
599                                                 ptr = p + 1;
600                                         } else {
601                                                 done = 1;
602                                         }
603                                 }
604                         } else {
605                                 printf(_("%s: invalid argument '%s'\n"), argv[0], argv[i]);
606                                 printf(_("Try '%s --help' for more information\n"), argv[0]);
607                                 exit(1);
608                         }
609                 }
610         }
611
612         timeDB = WMGetDefaultsFromPath("./UserTime.plist");
613         WMAddNotificationObserver(updatedDomain, NULL, WMUserDefaultsDidChangeNotification, NULL);
614
615         clientConnections = WMCreateArray(4);
616
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";
625
626         printf("Server will listen on '%s:%s'\n", ServerAddress ? ServerAddress : "Any", ServerPort);
627         printf("This server will allow connections from:");
628         if (allowedHostList) {
629                 int i;
630                 char *hName;
631
632                 for (i = 0; i < WMGetArrayItemCount(allowedHostList); i++) {
633                         hName = WMGetHostName(WMGetFromArray(allowedHostList, i));
634                         printf("%s'%s'", i == 0 ? " " : ", ", hName);
635                 }
636                 printf(".\n");
637         } else {
638                 printf(" any host.\n");
639         }
640
641         serverPtr = WMCreateConnectionAsServerAtAddress(ServerAddress, ServerPort, NULL);
642
643         if (!serverPtr) {
644                 wfatal("could not create server on `%s:%s`. Exiting.",
645                        ServerAddress ? ServerAddress : "localhost", ServerPort);
646                 exit(1);
647         }
648
649         WMSetConnectionDelegate(serverPtr, &socketDelegate);
650
651         WMAddNotificationObserver(removeConnection, NULL, SEConnectionShouldBeRemovedNotification, NULL);
652
653         while (1) {
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. */
657                 WHandleEvents();
658         }
659
660         return 0;
661 }