0c90c5014df0ebf667863d0678e7e56f5ec4e93f
[tpg/acess2.git] / Usermode / Applications / irc_src / main.c
1 /*\r
2  * Acess2 IRC Client\r
3  */\r
4 #include <acess/sys.h>\r
5 #include <stdlib.h>\r
6 #include <stdio.h>\r
7 #include <string.h>\r
8 #include <net.h>\r
9 #include <readline.h>\r
10 \r
11 #define BUFSIZ  1023\r
12 \r
13 // === TYPES ===\r
14 typedef struct sServer {\r
15         struct sServer  *Next;\r
16          int    FD;\r
17         char    InBuf[BUFSIZ+1];\r
18          int    ReadPos;\r
19         char    Name[];\r
20 } tServer;\r
21 \r
22 typedef struct sMessage\r
23 {\r
24         struct sMessage *Next;\r
25         time_t  Timestamp;\r
26         tServer *Server;\r
27          int    Type;\r
28         char    *Source;        // Pointer into `Data`\r
29         char    Data[];\r
30 }       tMessage;\r
31 \r
32 typedef struct sWindow\r
33 {\r
34         struct sWindow  *Next;\r
35         tMessage        *Messages;\r
36         tServer *Server;        //!< Canoical server (can be NULL)\r
37          int    ActivityLevel;\r
38         char    Name[]; // Channel name / remote user\r
39 }       tWindow;\r
40 \r
41 enum eMessageTypes\r
42 {\r
43         MSG_TYPE_NULL,\r
44         MSG_TYPE_SERVER,        // Server message\r
45         \r
46         MSG_TYPE_NOTICE,        // NOTICE command\r
47         MSG_TYPE_JOIN,  // JOIN command\r
48         MSG_TYPE_PART,  // PART command\r
49         MSG_TYPE_QUIT,  // QUIT command\r
50         \r
51         MSG_TYPE_STANDARD,      // Standard line\r
52         MSG_TYPE_ACTION,        // /me\r
53         \r
54         MSG_TYPE_UNK\r
55 };\r
56 \r
57 // === PROTOTYPES ===\r
58  int    ParseArguments(int argc, const char *argv[]);\r
59  int    ParseUserCommand(char *String);\r
60 // --- \r
61 tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber);\r
62 tMessage        *Message_Append(tServer *Server, int Type, const char *Source, const char *Dest, const char *Message);\r
63 tWindow *Window_Create(tServer *Server, const char *Name);\r
64 \r
65  int    ProcessIncoming(tServer *Server);\r
66 // --- Helpers\r
67  int    writef(int FD, const char *Format, ...);\r
68  int    OpenTCP(const char *AddressString, short PortNumber);\r
69 char    *GetValue(char *Str, int *Ofs);\r
70 static inline int       isdigit(int ch);\r
71 \r
72 // === GLOBALS ===\r
73 char    *gsUsername = "root";\r
74 char    *gsHostname = "acess";\r
75 char    *gsRealName = "Acess2 IRC Client";\r
76 char    *gsNickname = "acess";\r
77 tServer *gpServers;\r
78 tWindow gWindow_Status = {\r
79         NULL, NULL, NULL,       // No next, empty list, no server\r
80         0, ""   // No activity, empty name (rendered as status)\r
81 };\r
82 tWindow *gpWindows = &gWindow_Status;\r
83 tWindow *gpCurrentWindow = &gWindow_Status;\r
84 \r
85 // ==== CODE ====\r
86 int main(int argc, const char *argv[], const char *envp[])\r
87 {\r
88          int    tmp;\r
89         tReadline       *readline_info;\r
90         \r
91         // Parse Command line\r
92         if( (tmp = ParseArguments(argc, argv)) )        return tmp;\r
93         \r
94         // HACK: Static server entry\r
95         // UCC (University [of Western Australia] Computer Club) IRC Server\r
96         gWindow_Status.Server = Server_Connect( "UCC", "130.95.13.18", 6667 );\r
97         \r
98         if( !gWindow_Status.Server )\r
99                 return -1;\r
100         \r
101         readline_info = Readline_Init(1);\r
102         \r
103         for( ;; )\r
104         {\r
105                 fd_set  readfds, errorfds;\r
106                  int    rv, maxFD = 0;\r
107                 tServer *srv;\r
108                 \r
109                 FD_ZERO(&readfds);\r
110                 FD_ZERO(&errorfds);\r
111                 FD_SET(0, &readfds);    // stdin\r
112                 \r
113                 // Fill server FDs in fd_set\r
114                 for( srv = gpServers; srv; srv = srv->Next )\r
115                 {\r
116                         FD_SET(srv->FD, &readfds);\r
117                         FD_SET(srv->FD, &errorfds);\r
118                         if( srv->FD > maxFD )\r
119                                 maxFD = srv->FD;\r
120                 }\r
121                 \r
122                 rv = select(maxFD+1, &readfds, 0, &errorfds, NULL);\r
123                 if( rv == -1 )  break;\r
124                 \r
125                 if(FD_ISSET(0, &readfds))\r
126                 {\r
127                         // User input\r
128                         char    *cmd = Readline_NonBlock(readline_info);\r
129                         if( cmd )\r
130                         {\r
131                                 if( cmd[0] )\r
132                                 {\r
133                                         ParseUserCommand(cmd);\r
134                                 }\r
135                                 free(cmd);\r
136                         }\r
137                 }\r
138                 \r
139                 // Server response\r
140                 for( srv = gpServers; srv; srv = srv->Next )\r
141                 {\r
142                         if(FD_ISSET(srv->FD, &readfds))\r
143                         {\r
144                                 if( ProcessIncoming(srv) != 0 ) {\r
145                                         // Oops, error\r
146                                         break;\r
147                                 }\r
148                         }\r
149                         \r
150                         if(FD_ISSET(srv->FD, &errorfds))\r
151                         {\r
152                                 break;\r
153                         }\r
154                 }\r
155                 \r
156                 // Oops, an error\r
157                 if( srv )       break;\r
158         }\r
159         \r
160         {\r
161                 tServer *srv;\r
162                 for( srv = gpServers; srv; srv = srv->Next )\r
163                         close(srv->FD);\r
164         }\r
165         return 0;\r
166 }\r
167 \r
168 /**\r
169  * \todo Actually implement correctly :)\r
170  */\r
171 int ParseArguments(int argc, const char *argv[])\r
172 {\r
173         return 0;\r
174 }\r
175 \r
176 int ParseUserCommand(char *String)\r
177 {\r
178         if( String[0] == '/' )\r
179         {\r
180                 char    *command;\r
181                  int    pos = 0;\r
182                 \r
183                 command = GetValue(String, &pos);\r
184                 \r
185                 if( strcmp(command, "/join") == 0 )\r
186                 {\r
187                         char    *channel_name = GetValue(String, &pos);\r
188                         \r
189                         if( gpCurrentWindow->Server )\r
190                         {\r
191                                 writef(gpCurrentWindow->Server->FD, "JOIN %s\n",  channel_name);\r
192                         }\r
193                 }\r
194                 else if( strcmp(command, "/quit") == 0 )\r
195                 {\r
196                         char    *quit_message = GetValue(String, &pos);\r
197                         tServer *srv;\r
198                         \r
199                         if( !quit_message )\r
200                                 quit_message = "/quit - Acess2 IRC Client";\r
201                         \r
202                         for( srv = gpServers; srv; srv = srv->Next )\r
203                         {\r
204                                 writef(srv->FD, "QUIT %s\n", quit_message);\r
205                         }\r
206                 }\r
207                 else if( strcmp(command, "/window") == 0 || strcmp(command, "/win") == 0 || strcmp(command, "/w") == 0 )\r
208                 {\r
209                         char    *window_id = GetValue(String, &pos);\r
210                          int    window_num = atoi(window_id);\r
211                         \r
212                         if( window_num > 0 )\r
213                         {\r
214                                 tWindow *win;\r
215                                 window_num --;  // Move to base 0\r
216                                 // Get `window_num`th window\r
217                                 for( win = gpWindows; win && window_num--; win = win->Next );\r
218                                 if( win ) {\r
219                                         gpCurrentWindow = win;\r
220                                         if( win->Name[0] )\r
221                                                 printf("[%s:%s] ", win->Server->Name, win->Name);\r
222                                         else\r
223                                                 printf("[(status)] ", win->Server->Name, win->Name);\r
224                                 }\r
225                                 // Otherwise, silently ignore\r
226                         }\r
227                 }\r
228                 else\r
229                 {\r
230                          int    len = snprintf(NULL, 0, "Unknown command %s", command);\r
231                         char    buf[len+1];\r
232                         snprintf(buf, len+1, "Unknown command %s", command);\r
233                         Message_Append(NULL, MSG_TYPE_SERVER, "client", "", buf);\r
234                 }\r
235         }\r
236         else\r
237         {\r
238                 // Message\r
239                 // - Only send if server is valid and window name is non-empty\r
240                 if( gpCurrentWindow->Server && gpCurrentWindow->Name[0] )\r
241                 {\r
242                         writef(gpCurrentWindow->Server->FD,\r
243                                 "PRIVMSG %s :%s\n", gpCurrentWindow->Name,\r
244                                 String\r
245                                 );\r
246                 }\r
247         }\r
248         \r
249         return 0;\r
250 }\r
251 \r
252 /**\r
253  * \brief Connect to a server\r
254  */\r
255 tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber)\r
256 {\r
257         tServer *ret;\r
258         \r
259         ret = calloc(1, sizeof(tServer) + strlen(Name) + 1);\r
260         \r
261         strcpy(ret->Name, Name);\r
262         \r
263         // Connect to the remove server\r
264         ret->FD = OpenTCP( AddressString, PortNumber );\r
265         if( ret->FD == -1 ) {\r
266                 fprintf(stderr, "%s: Unable to create socket\n", Name);\r
267                 return NULL;\r
268         }\r
269         \r
270         // Append to open list\r
271         ret->Next = gpServers;\r
272         gpServers = ret;\r
273         \r
274         // Read some initial data\r
275         printf("%s: Connection opened\n", Name);\r
276         ProcessIncoming(ret);\r
277         \r
278         // Identify\r
279         writef(ret->FD, "USER %s %s %s : %s\n", gsUsername, gsHostname, AddressString, gsRealName);\r
280         writef(ret->FD, "NICK %s\n", gsNickname);\r
281         printf("%s: Identified\n", Name);\r
282         \r
283         return ret;\r
284 }\r
285 \r
286 tMessage *Message_Append(tServer *Server, int Type, const char *Source, const char *Dest, const char *Message)\r
287 {\r
288         tMessage        *ret;\r
289         tWindow *win = NULL;\r
290          int    msgLen = strlen(Message);\r
291         \r
292         // NULL servers are internal messages\r
293         if( Server == NULL )\r
294         {\r
295                 win = &gWindow_Status;\r
296         }\r
297         // Determine if it's a channel or PM message\r
298         else if( Dest[0] == '#' || Dest[0] == '&' )     // TODO: Better determining here\r
299         {\r
300                 tWindow *prev = NULL;\r
301                 for(win = gpWindows; win; prev = win, win = win->Next)\r
302                 {\r
303                         if( win->Server == Server && strcmp(win->Name, Dest) == 0 )\r
304                         {\r
305                                 break;\r
306                         }\r
307                 }\r
308                 if( !win ) {\r
309                         win = Window_Create(Server, Dest);\r
310                 }\r
311         }\r
312         #if 0\r
313         else if( strcmp(Dest, Server->Nick) != 0 )\r
314         {\r
315                 // Umm... message for someone who isn't us?\r
316                 win = &gWindow_Status;  // Stick it in the status window, just in case\r
317         }\r
318         #endif\r
319         // Server message?\r
320         else if( strchr(Source, '.') )  // TODO: And again, less hack please\r
321         {\r
322                 #if 1\r
323                 for(win = gpWindows; win; win = win->Next)\r
324                 {\r
325                         if( win->Server == Server && strcmp(win->Name, Source) == 0 )\r
326                         {\r
327                                 break;\r
328                         }\r
329                 }\r
330                 #endif\r
331                 if( !win ) {\r
332                         win = &gWindow_Status;\r
333                 }\r
334                 \r
335         }\r
336         // Private message\r
337         else\r
338         {\r
339                 for(win = gpWindows; win; win = win->Next)\r
340                 {\r
341                         if( win->Server == Server && strcmp(win->Name, Source) == 0 )\r
342                         {\r
343                                 break;\r
344                         }\r
345                 }\r
346                 if( !win ) {\r
347                         win = Window_Create(Server, Dest);\r
348                 }\r
349         }\r
350         \r
351         ret = malloc( sizeof(tMessage) + msgLen + 1 + strlen(Source) + 1 );\r
352         ret->Source = ret->Data + msgLen + 1;\r
353         strcpy(ret->Source, Source);\r
354         strcpy(ret->Data, Message);\r
355         ret->Type = Type;\r
356         ret->Server = Server;\r
357         \r
358         // TODO: Append to window message list\r
359         ret->Next = win->Messages;\r
360         win->Messages = ret;\r
361         \r
362         return ret;\r
363 }\r
364 \r
365 tWindow *Window_Create(tServer *Server, const char *Name)\r
366 {\r
367         tWindow *ret, *prev = NULL;\r
368          int    num = 1;\r
369         \r
370         // Get the end of the list\r
371         // TODO: Cache this instead\r
372         for( ret = gpCurrentWindow; ret; prev = ret, ret = ret->Next )\r
373                 num ++;\r
374         \r
375         ret = malloc(sizeof(tWindow) + strlen(Name) + 1);\r
376         ret->Messages = NULL;\r
377         ret->Server = Server;\r
378         ret->ActivityLevel = 1;\r
379         strcpy(ret->Name, Name);\r
380         \r
381         if( prev ) {\r
382                 ret->Next = prev->Next;\r
383                 prev->Next = ret;\r
384         }\r
385         else {  // Shouldn't happen really\r
386                 ret->Next = gpWindows;\r
387                 gpWindows = ret;\r
388         }\r
389         \r
390         printf("Win %i %s:%s created\n", num, Server->Name, Name);\r
391         \r
392         return ret;\r
393 }\r
394 \r
395 void Cmd_PRIVMSG(tServer *Server, const char *Dest, const char *Src, const char *Message)\r
396 {\r
397         printf("<%s:%s:%s> %s\n", Server->Name, Dest, Src, Message);\r
398 }\r
399 \r
400 /**\r
401  */\r
402 void ParseServerLine(tServer *Server, char *Line)\r
403 {\r
404          int    pos = 0;\r
405         char    *ident, *cmd;\r
406         \r
407         // Message?\r
408         if( *Line == ':' )\r
409         {\r
410                 ident = GetValue(Line, &pos);   // Ident (user or server)\r
411                 cmd = GetValue(Line, &pos);\r
412                 \r
413                 // Numeric command\r
414                 if( isdigit(cmd[0]) && isdigit(cmd[1]) && isdigit(cmd[2]) )\r
415                 {\r
416                         char    *user, *message;\r
417                          int    num;\r
418                         num  = (cmd[0] - '0') * 100;\r
419                         num += (cmd[1] - '0') * 10;\r
420                         num += (cmd[2] - '0') * 1;\r
421                         \r
422                         user = GetValue(Line, &pos);\r
423                         \r
424                         if( Line[pos] == ':' ) {\r
425                                 message = Line + pos + 1;\r
426                         }\r
427                         else {\r
428                                 message = GetValue(Line, &pos);\r
429                         }\r
430                         \r
431                         switch(num)\r
432                         {\r
433                         default:\r
434                                 printf("[%s] %i %s\n", Server->Name, num, message);\r
435                                 Message_Append(Server, MSG_TYPE_SERVER, ident, user, message);\r
436                                 break;\r
437                         }\r
438                 }\r
439                 else if( strcmp(cmd, "NOTICE") == 0 )\r
440                 {\r
441                         char    *class, *message;\r
442                         \r
443                         class = GetValue(Line, &pos);\r
444                         \r
445                         if( Line[pos] == ':' ) {\r
446                                 message = Line + pos + 1;\r
447                         }\r
448                         else {\r
449                                 message = GetValue(Line, &pos);\r
450                         }\r
451                         \r
452                         printf("[%s] NOTICE %s: %s\n", Server->Name, ident, message);\r
453                         Message_Append(Server, MSG_TYPE_NOTICE, ident, "", message);\r
454                 }\r
455                 else if( strcmp(cmd, "PRIVMSG") == 0 )\r
456                 {\r
457                         char    *dest, *message;\r
458                         dest = GetValue(Line, &pos);\r
459                         \r
460                         if( Line[pos] == ':' ) {\r
461                                 message = Line + pos + 1;\r
462                         }\r
463                         else {\r
464                                 message = GetValue(Line, &pos);\r
465                         }\r
466                         Cmd_PRIVMSG(Server, dest, ident, message);\r
467                         Message_Append(Server, MSG_TYPE_STANDARD, ident, dest, message);\r
468                 }\r
469                 else\r
470                 {\r
471                         printf("Unknown message %s (%s)\n", cmd, Line+pos);\r
472                 }\r
473         }\r
474         else {\r
475                 \r
476                 // Command to client\r
477                 printf("Client Command: %s", Line);\r
478         }\r
479 }\r
480 \r
481 /**\r
482  * \brief Process incoming lines from the server\r
483  */\r
484 int ProcessIncoming(tServer *Server)\r
485 {       \r
486         char    *ptr, *newline;\r
487          int    len;\r
488         \r
489         // While there is data in the buffer, read it into user memory and \r
490         // process it line by line\r
491         // ioctl#8 on a TCP client gets the number of bytes in the recieve buffer\r
492         // - Used to avoid blocking\r
493         #if NON_BLOCK_READ\r
494         while( (len = ioctl(Server->FD, 8, NULL)) > 0 )\r
495         {\r
496         #endif\r
497                 // Read data\r
498                 len = read(Server->FD, BUFSIZ - Server->ReadPos, &Server->InBuf[Server->ReadPos]);\r
499                 if( len == -1 ) {\r
500                         return -1;\r
501                 }\r
502                 Server->InBuf[Server->ReadPos + len] = '\0';\r
503                 \r
504                 // Break into lines\r
505                 ptr = Server->InBuf;\r
506                 while( (newline = strchr(ptr, '\n')) )\r
507                 {\r
508                         *newline = '\0';\r
509                         if( newline[-1] == '\r' )       newline[-1] = '\0';\r
510                         ParseServerLine(Server, ptr);\r
511                         ptr = newline + 1;\r
512                 }\r
513                 \r
514                 // Handle incomplete lines\r
515                 if( ptr - Server->InBuf < len + Server->ReadPos ) {\r
516                         // Update the read position\r
517                         // InBuf ReadPos    ptr          ReadPos+len\r
518                         // | old | new used | new unused |\r
519                         Server->ReadPos = len + Server->ReadPos - (ptr - Server->InBuf);\r
520                         // Copy stuff back (moving "new unused" to the start of the buffer)\r
521                         memcpy(Server->InBuf, ptr, Server->ReadPos);\r
522                 }\r
523                 else {\r
524                         Server->ReadPos = 0;\r
525                 }\r
526         #if NON_BLOCK_READ\r
527         }\r
528         #endif\r
529         \r
530         return 0;\r
531 }\r
532 \r
533 /**\r
534  * \brief Write a formatted string to a file descriptor\r
535  * \r
536  */\r
537 int writef(int FD, const char *Format, ...)\r
538 {\r
539         va_list args;\r
540          int    len;\r
541         \r
542         va_start(args, Format);\r
543         len = vsnprintf(NULL, 1000, Format, args);\r
544         va_end(args);\r
545         \r
546         {\r
547                 char    buf[len+1];\r
548                 va_start(args, Format);\r
549                 vsnprintf(buf, len+1, Format, args);\r
550                 va_end(args);\r
551                 \r
552                 return write(FD, len, buf);\r
553         }\r
554 }\r
555 \r
556 /**\r
557  * \brief Initialise a TCP connection to \a AddressString on port \a PortNumber\r
558  */\r
559 int OpenTCP(const char *AddressString, short PortNumber)\r
560 {\r
561          int    fd, addrType;\r
562         char    *iface;\r
563         char    addrBuffer[8];\r
564         \r
565         // Parse IP Address\r
566         addrType = Net_ParseAddress(AddressString, addrBuffer);\r
567         if( addrType == 0 ) {\r
568                 fprintf(stderr, "Unable to parse '%s' as an IP address\n", AddressString);\r
569                 return -1;\r
570         }\r
571         \r
572         // Finds the interface for the destination address\r
573         iface = Net_GetInterface(addrType, addrBuffer);\r
574         if( iface == NULL ) {\r
575                 fprintf(stderr, "Unable to find a route to '%s'\n", AddressString);\r
576                 return -1;\r
577         }\r
578         \r
579         printf("iface = '%s'\n", iface);\r
580         \r
581         // Open client socket\r
582         // TODO: Move this out to libnet?\r
583         {\r
584                  int    len = snprintf(NULL, 100, "/Devices/ip/%s/tcpc", iface);\r
585                 char    path[len+1];\r
586                 snprintf(path, 100, "/Devices/ip/%s/tcpc", iface);\r
587                 fd = open(path, OPENFLAG_READ|OPENFLAG_WRITE);\r
588         }\r
589         \r
590         free(iface);\r
591         \r
592         if( fd == -1 ) {\r
593                 fprintf(stderr, "Unable to open TCP Client for reading\n");\r
594                 return -1;\r
595         }\r
596         \r
597         // Set remote port and address\r
598         printf("Setting port and remote address\n");\r
599         ioctl(fd, 5, &PortNumber);\r
600         ioctl(fd, 6, addrBuffer);\r
601         \r
602         // Connect\r
603         printf("Initiating connection\n");\r
604         if( ioctl(fd, 7, NULL) == 0 ) {\r
605                 // Shouldn't happen :(\r
606                 fprintf(stderr, "Unable to start connection\n");\r
607                 return -1;\r
608         }\r
609         \r
610         // Return descriptor\r
611         return fd;\r
612 }\r
613 \r
614 /**\r
615  * \brief Read a space-separated value from a string\r
616  */\r
617 char *GetValue(char *Src, int *Ofs)\r
618 {\r
619          int    pos = *Ofs;\r
620         char    *ret = Src + pos;\r
621         char    *end;\r
622         \r
623         if( !Src )      return NULL;\r
624         \r
625         while( *ret == ' ' )    ret ++;\r
626         \r
627         end = strchr(ret, ' ');\r
628         if( end ) {\r
629                 *end = '\0';\r
630         }\r
631         else {\r
632                 end = ret + strlen(ret) - 1;\r
633         }\r
634         \r
635         end ++ ;\r
636         while( *ret == ' ' )    end ++;\r
637         *Ofs = end - Src;\r
638         \r
639         return ret;\r
640 }\r
641 \r
642 static inline int isdigit(int ch)\r
643 {\r
644         return '0' <= ch && ch < '9';\r
645 }\r

UCC git Repository :: git.ucc.asn.au