Usermode/libaxwin4 - Handle demarshal failure
[tpg/acess2.git] / Usermode / Applications / irc_src / server.c
1 /*
2  */
3 #include <stdlib.h>
4 #include <string.h>
5 #include "server.h"
6 #include "window.h"
7 #include <acess/sys.h>
8 #include <ctype.h>      // isdigit
9 #include <stdio.h>      // vsnprintf
10
11 // === PROTOTYPES ===
12 tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber);
13  int    Server_HandleIncoming(tServer *Server);
14 void    ParseServerLine(tServer *Server, char *Line);
15
16 // === GLOBALS ===
17 const char      *gsUsername = "user";
18 const char      *gsHostname = "acess";
19 const char      *gsRealName = "An Acess User";
20 const char      *gsNickname = "acess";
21 const char      *gsVersionResponse = "Acess2 IRC Client / Running on some VM probably";
22 tServer *gpServers;
23
24 // === CODE ===
25 void Servers_FillSelect(int *nfds, fd_set *rfds, fd_set *efds)
26 {
27         for( tServer *srv = gpServers; srv; srv = srv->Next )
28         {
29                 FD_SET(srv->FD, rfds);
30                 FD_SET(srv->FD, efds);
31                 if( srv->FD >= *nfds )
32                         *nfds = srv->FD+1;
33         }
34 }
35
36 void Servers_HandleSelect(int nfds, const fd_set *rfds, const fd_set *efds)
37 {
38         for( tServer *srv = gpServers; srv; srv = srv->Next )
39         {
40                 if(FD_ISSET(srv->FD, rfds))
41                 {
42                          int    rv = Server_HandleIncoming(srv);
43                         if(rv)
44                         {
45                                 // Oops, error
46                                 _SysDebug("ProcessIncoming failed on FD%i (Server %p %s)",
47                                         srv->FD, srv, srv->Name);
48                                 Exit("Processing error");
49                         }
50                 }
51                 
52                 if(FD_ISSET(srv->FD, efds))
53                 {
54                         _SysDebug("Error on FD%i (Server %p %s)",
55                                 srv->FD, srv, srv->Name);
56                         Exit("Socket error");
57                 }
58         }
59 }
60
61 void Servers_CloseAll(const char *QuitMessage)
62 {
63         while( gpServers )
64         {
65                 tServer *srv = gpServers;
66                 gpServers = srv->Next;
67         
68                 Server_SendCommand(srv, "QUIT :%s", QuitMessage);
69                 _SysClose(srv->FD);
70                 free(srv);
71         }
72 }
73
74 /**
75  * \brief Connect to a server
76  */
77 tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber)
78 {
79         tServer *ret;
80         
81         ret = calloc(1, sizeof(tServer) + strlen(Name) + 1);
82         
83         strcpy(ret->Name, Name);
84         
85         // Connect to the remove server
86         ret->FD = OpenTCP( AddressString, PortNumber );
87         if( ret->FD == -1 ) {
88                 free(ret);
89                 Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Unable to create socket");
90                 return NULL;
91         }
92         
93         ret->Nick = strdup(gsNickname);
94         
95         // Append to open list
96         ret->Next = gpServers;
97         gpServers = ret;
98         
99         // Read some initial data
100         Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Unable to create socket");
101         Server_HandleIncoming(ret);
102         
103         // Identify
104         Server_SendCommand(ret, "USER %s %s %s : %s", gsUsername, gsHostname, AddressString, gsRealName);
105         Server_SendCommand(ret, "NICK %s", ret->Nick);
106         Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Identified");
107         
108         return ret;
109 }
110
111 const char *Server_GetNick(const tServer *Server)
112 {
113         return Server ? Server->Nick : "NULL";
114 }
115 const char *Server_GetName(const tServer *Server)
116 {
117         return Server ? Server->Name : "NULL";
118 }
119
120 void Server_SendCommand(tServer *Server, const char *Format, ...)
121 {
122         va_list args;
123          int    len;
124         
125         va_start(args, Format);
126         len = vsnprintf(NULL, 0, Format, args);
127         va_end(args);
128         
129         char    buf[len+1];
130         va_start(args, Format);
131         vsnprintf(buf, len+1, Format, args);
132         va_end(args);
133         
134         _SysWrite(Server->FD, buf, len);
135         _SysWrite(Server->FD, "\n", 1);
136 }
137
138 void Cmd_PRIVMSG(tServer *Server, const char *Dest, const char *Src, const char *Message)
139 {
140         tWindow *win;
141         if( strcmp(Dest, Server->Nick) == 0 ) {
142                 win = Windows_GetByNameOrCreate(Server, Src);
143         }
144         else {
145                 win = Windows_GetByNameOrCreate(Server, Dest);
146         }
147         
148         // Detect CTCP
149         if( Message[0] == '\1' && Message[strlen(Message)-1] == '\1' )
150         {
151                 Message += 1;
152                 // message is a CTCP command
153                 if( strcmp(Message, "VERSION\1") == 0 )
154                 {
155                         // Put a message in the status window, and reply
156                         Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Server->Name, "CTCP VERSION request from %s", Src);
157                         // - Always reply via NOTICE
158                         Server_SendCommand(Server, "NOTICE %s :\1VERSION %s\1", Src, gsVersionResponse);
159                 }
160                 else if( strncmp(Message, "ACTION ", 7) == 0 )
161                 {
162                         Message += 7;
163                         // Put a message in the status window, and reply
164                         Window_AppendMessage(win, MSG_CLASS_ACTION, Src, "%.*s", (int)(strlen(Message)-1), Message);
165                 }
166                 else
167                 {
168                         Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Server->Name, "Unknown CTCP '%s' from %s",
169                                 Message, Src);
170                 }
171         }
172         else
173         {
174                 Window_AppendMessage(win, MSG_CLASS_MESSAGE, Src, "%s", Message);
175         }
176 }
177
178 /**
179  * \brief Process incoming lines from the server
180  */
181 int Server_HandleIncoming(tServer *Server)
182 {       
183         char    *ptr, *newline;
184          int    len;
185         
186         // While there is data in the buffer, read it into user memory and 
187         // process it line by line
188         // ioctl#8 on a TCP client gets the number of bytes in the recieve buffer
189         // - Used to avoid blocking
190         #if NON_BLOCK_READ
191         while( (len = _SysIOCtl(Server->FD, 8, NULL)) > 0 )
192         {
193         #endif
194                 // Read data
195                 len = _SysRead(Server->FD, &Server->InBuf[Server->ReadPos], sizeof(Server->InBuf) - Server->ReadPos);
196                 if( len == -1 ) {
197                         return -1;
198                 }
199                 Server->InBuf[Server->ReadPos + len] = '\0';
200                 
201                 // Break into lines
202                 ptr = Server->InBuf;
203                 while( (newline = strchr(ptr, '\n')) )
204                 {
205                         *newline = '\0';
206                         if( newline[-1] == '\r' )       newline[-1] = '\0';
207                         ParseServerLine(Server, ptr);
208                         ptr = newline + 1;
209                 }
210                 
211                 // Handle incomplete lines
212                 if( ptr - Server->InBuf < len + Server->ReadPos ) {
213                         // Update the read position
214                         // InBuf ReadPos    ptr          ReadPos+len
215                         // | old | new used | new unused |
216                         Server->ReadPos = len + Server->ReadPos - (ptr - Server->InBuf);
217                         // Copy stuff back (moving "new unused" to the start of the buffer)
218                         memcpy(Server->InBuf, ptr, Server->ReadPos);
219                 }
220                 else {
221                         Server->ReadPos = 0;
222                 }
223         #if NON_BLOCK_READ
224         }
225         #endif
226         
227         return 0;
228 }
229
230 void ParseServerLine_Numeric(tServer *Server, const char *ident, int Num, char *Line)
231 {
232          int    pos = 0;
233         const char *message;
234         const char *user = GetValue(Line, &pos);
235         const char      *timestamp;
236         
237         if( Line[pos] == ':' ) {
238                 message = Line + pos + 1;
239         }
240         else {
241                 message = GetValue(Line, &pos);
242         }
243         
244         switch(Num)
245         {
246         case 332:       // Topic
247                 user = message; // Channel
248                 message = Line + pos + 1;       // Topic
249                 Window_AppendMsg_Topic( Windows_GetByNameOrCreate(Server, user), message );
250                 break;
251         case 333:       // Topic set by
252                 user = message; // Channel
253                 message = GetValue(Line, &pos); // User
254                 timestamp = GetValue(Line, &pos);       // Timestamp
255                 Window_AppendMsg_TopicTime( Windows_GetByNameOrCreate(Server, user), message, timestamp );
256                 break;
257         case 353:       // /NAMES list
258                 // TODO: Parse the /names list and store it locally, dump to window when end is seen
259                 // <user> = <channel> :list
260                 // '=' was eaten in and set to message
261                 user = GetValue(Line, &pos);    // Actually channel
262                 message = Line + pos + 1;       // List
263                 Window_AppendMessage( Windows_GetByNameOrCreate(Server, user), MSG_CLASS_CLIENT, "NAMES", message );
264                 break;
265         case 366:       // end of /NAMES list
266                 // <user> <channel> :msg
267                 // - Ignored
268                 break;
269
270         case   1:       // welcome
271         case   2:       // host name and version (text)
272         case   3:       // host uptime
273         case   4:       // host name, version, and signature
274         case   5:       // parameters
275         
276         case 250:       // Highest connection count
277         case 251:       // user count (network)
278         case 252:       // Operator count
279         case 253:       // Unidentified connections
280         case 254:       // Channel count
281         case 255:       // Server's stats
282         case 265:       // Local users -- min max :Text representation
283         case 266:       // Global users (same as above)
284         
285         case 372:       // MOTD Data
286         case 375:       // MOTD Start
287         case 376:       // MOTD End
288                 Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_WALL, Server->Name, "%s", message);
289                 break;
290                 
291         default:
292                 //printf("[%s] %i %s\n", Server->Name, num, message);
293                 Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_WALL, Server->Name, "Unknown %i %s", Num, message);
294                 break;
295         }
296 }
297
298 void ParseServerLine_String(tServer *Server, const char *ident, const char *cmd, char *Line)
299 {
300          int    pos = 0;
301         _SysDebug("ident=%s,cmd=%s,Line=%s", ident, cmd, Line);
302         if( strcmp(cmd, "NOTICE") == 0 )
303         {
304                 const char *class = GetValue(Line, &pos);
305                 const char *message = (Line[pos] == ':') ? Line + pos + 1 : GetValue(Line, &pos);
306                 _SysDebug("NOTICE class='%s'", class);
307                 
308                 char *ident_bang = strchr(ident, '!');
309                 if( ident_bang ) {
310                         *ident_bang = '\0';
311                 }
312                 // TODO: Colour codes
313                 Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_WALL, Server->Name, "%s %s", ident, message);
314         }
315         else if( strcmp(cmd, "PRIVMSG") == 0 )
316         {
317                 const char *dest = GetValue(Line, &pos);
318                 const char *message = (Line[pos] == ':') ? Line + pos + 1 : GetValue(Line, &pos);
319
320                 char *ident_bang = strchr(ident, '!');
321                 if( ident_bang ) {
322                         *ident_bang = '\0';
323                 }
324                 Cmd_PRIVMSG(Server, dest, ident, message);
325         }
326         else if( strcmp(cmd, "JOIN" ) == 0 )
327         {
328                 const char      *channel = Line + pos + 1;
329                 
330                 Window_AppendMsg_Join( Windows_GetByNameOrCreate(Server, channel), ident );
331         }
332         else if( strcmp(cmd, "PART" ) == 0 )
333         {
334                 const char      *channel = Line + pos + 1;
335                 
336                 Window_AppendMsg_Part( Windows_GetByNameOrCreate(Server, channel), ident, "" );
337         }
338         else if( strcmp(cmd, "MODE" ) == 0 )
339         {
340                 // ident MODE channel flags nick[ nick...]
341                 const char      *channel = GetValue(Line, &pos);
342                 const char      *flags = GetValue(Line, &pos);
343                 const char      *args = Line + pos;
344                 
345                 Window_AppendMsg_Mode( Windows_GetByNameOrCreate(Server, channel), ident, flags, args );
346         }
347         else if( strcmp(cmd, "KICK" ) == 0 )
348         {
349                 // ident KICK channel nick :reason
350                 const char      *channel = GetValue(Line, &pos);
351                 const char      *nick = GetValue(Line, &pos);
352                 const char      *message = Line + pos + 1;
353                 
354                 Window_AppendMsg_Kick( Windows_GetByNameOrCreate(Server, channel), ident, nick, message );
355                 if( strcmp(nick, Server->Nick) == 0 ) {
356                         // Oh, that was me :(
357                         // - what do?
358                 }
359         }
360         else
361         {
362                 Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_BARE, Server->Name, "Unknown command '%s' %s", cmd, Line);
363         }
364 }
365
366 /**
367  */
368 void ParseServerLine(tServer *Server, char *Line)
369 {
370          int    pos = 0;
371
372         _SysDebug("[%s] %s", Server->Name, Line);       
373         
374         // Message?
375         if( *Line == ':' )
376         {
377                 pos ++;
378                 const char *ident = GetValue(Line, &pos);       // Ident (user or server)
379                 const char *cmd = GetValue(Line, &pos);
380                 
381                 // Numeric command
382                 if( isdigit(cmd[0]) && isdigit(cmd[1]) && isdigit(cmd[2]) )
383                 {
384                          int    num;
385                         num  = (cmd[0] - '0') * 100;
386                         num += (cmd[1] - '0') * 10;
387                         num += (cmd[2] - '0') * 1;
388
389                         ParseServerLine_Numeric(Server, ident, num, Line+pos);
390                 }
391                 else
392                 {
393                         ParseServerLine_String(Server, ident, cmd, Line+pos);
394                 }
395         }
396         else {
397                 const char *cmd = GetValue(Line, &pos);
398                 
399                 if( strcmp(cmd, "PING") == 0 ) {
400                         Server_SendCommand(Server, "PONG %s", Line+pos);
401                 }
402                 else {
403                         // Command to client
404                         Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Server->Name, "UNK Client Command: %s", Line);
405                 }
406         }
407 }

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