c8bfea58cdf3bc5d15a230e704337efd61730174
[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 = "Acess2 IRC Client";
20 const char      *gsNickname = "acess";
21 tServer *gpServers;
22
23 // === CODE ===
24 void Servers_FillSelect(int *nfds, fd_set *rfds, fd_set *efds)
25 {
26         for( tServer *srv = gpServers; srv; srv = srv->Next )
27         {
28                 FD_SET(srv->FD, rfds);
29                 FD_SET(srv->FD, efds);
30                 if( srv->FD >= *nfds )
31                         *nfds = srv->FD+1;
32         }
33 }
34
35 void Servers_HandleSelect(int nfds, const fd_set *rfds, const fd_set *efds)
36 {
37         for( tServer *srv = gpServers; srv; srv = srv->Next )
38         {
39                 if(FD_ISSET(srv->FD, rfds))
40                 {
41                          int    rv = Server_HandleIncoming(srv);
42                         if(rv)
43                         {
44                                 // Oops, error
45                                 _SysDebug("ProcessIncoming failed on FD%i (Server %p %s)",
46                                         srv->FD, srv, srv->Name);
47                                 Exit("Processing error");
48                         }
49                 }
50                 
51                 if(FD_ISSET(srv->FD, efds))
52                 {
53                         _SysDebug("Error on FD%i (Server %p %s)",
54                                 srv->FD, srv, srv->Name);
55                         Exit("Socket error");
56                 }
57         }
58 }
59
60 void Servers_CloseAll(const char *QuitMessage)
61 {
62         while( gpServers )
63         {
64                 tServer *srv = gpServers;
65                 gpServers = srv->Next;
66         
67                 Server_SendCommand(srv, "QUIT :%s", QuitMessage);
68                 _SysClose(srv->FD);
69                 free(srv);
70         }
71 }
72
73 /**
74  * \brief Connect to a server
75  */
76 tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber)
77 {
78         tServer *ret;
79         
80         ret = calloc(1, sizeof(tServer) + strlen(Name) + 1);
81         
82         strcpy(ret->Name, Name);
83         
84         // Connect to the remove server
85         ret->FD = OpenTCP( AddressString, PortNumber );
86         if( ret->FD == -1 ) {
87                 free(ret);
88                 Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Unable to create socket");
89                 return NULL;
90         }
91         
92         ret->Nick = strdup(gsNickname);
93         
94         // Append to open list
95         ret->Next = gpServers;
96         gpServers = ret;
97         
98         // Read some initial data
99         Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Unable to create socket");
100         Server_HandleIncoming(ret);
101         
102         // Identify
103         Server_SendCommand(ret, "USER %s %s %s : %s", gsUsername, gsHostname, AddressString, gsRealName);
104         Server_SendCommand(ret, "NICK %s", ret->Nick);
105         Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Identified");
106         
107         return ret;
108 }
109
110 const char *Server_GetNick(const tServer *Server)
111 {
112         return Server->Nick;
113 }
114
115 void Server_SendCommand(tServer *Server, const char *Format, ...)
116 {
117         va_list args;
118          int    len;
119         
120         va_start(args, Format);
121         len = vsnprintf(NULL, 0, Format, args);
122         va_end(args);
123         
124         char    buf[len+1];
125         va_start(args, Format);
126         vsnprintf(buf, len+1, Format, args);
127         va_end(args);
128         
129         _SysWrite(Server->FD, buf, len);
130         _SysWrite(Server->FD, "\n", 1);
131 }
132
133 void Cmd_PRIVMSG(tServer *Server, const char *Dest, const char *Src, const char *Message)
134 {
135         tWindow *win;
136         if( strcmp(Dest, Server->Nick) == 0 ) {
137                 win = Windows_GetByName(Server, Src);
138                 if(!win)
139                         win = Window_Create(Server, Src);
140         }
141         else {
142                 win = Windows_GetByName(Server, Dest);
143                 if(!win)
144                         win = Window_Create(Server, Dest);
145         }
146         Window_AppendMessage(win, MSG_CLASS_MESSAGE, Src, "%s", Message);
147 }
148
149 /**
150  * \brief Process incoming lines from the server
151  */
152 int Server_HandleIncoming(tServer *Server)
153 {       
154         char    *ptr, *newline;
155          int    len;
156         
157         // While there is data in the buffer, read it into user memory and 
158         // process it line by line
159         // ioctl#8 on a TCP client gets the number of bytes in the recieve buffer
160         // - Used to avoid blocking
161         #if NON_BLOCK_READ
162         while( (len = _SysIOCtl(Server->FD, 8, NULL)) > 0 )
163         {
164         #endif
165                 // Read data
166                 len = _SysRead(Server->FD, &Server->InBuf[Server->ReadPos], sizeof(Server->InBuf) - Server->ReadPos);
167                 if( len == -1 ) {
168                         return -1;
169                 }
170                 Server->InBuf[Server->ReadPos + len] = '\0';
171                 
172                 // Break into lines
173                 ptr = Server->InBuf;
174                 while( (newline = strchr(ptr, '\n')) )
175                 {
176                         *newline = '\0';
177                         if( newline[-1] == '\r' )       newline[-1] = '\0';
178                         ParseServerLine(Server, ptr);
179                         ptr = newline + 1;
180                 }
181                 
182                 // Handle incomplete lines
183                 if( ptr - Server->InBuf < len + Server->ReadPos ) {
184                         // Update the read position
185                         // InBuf ReadPos    ptr          ReadPos+len
186                         // | old | new used | new unused |
187                         Server->ReadPos = len + Server->ReadPos - (ptr - Server->InBuf);
188                         // Copy stuff back (moving "new unused" to the start of the buffer)
189                         memcpy(Server->InBuf, ptr, Server->ReadPos);
190                 }
191                 else {
192                         Server->ReadPos = 0;
193                 }
194         #if NON_BLOCK_READ
195         }
196         #endif
197         
198         return 0;
199 }
200
201 void ParseServerLine_Numeric(tServer *Server, const char *ident, int Num, char *Line)
202 {
203          int    pos = 0;
204         const char *message;
205         const char *user = GetValue(Line, &pos);
206         const char      *timestamp;
207         
208         if( Line[pos] == ':' ) {
209                 message = Line + pos + 1;
210         }
211         else {
212                 message = GetValue(Line, &pos);
213         }
214         
215         switch(Num)
216         {
217         case 332:       // Topic
218                 user = message; // Channel
219                 message = Line + pos + 1;       // Topic
220                 Window_AppendMsg_Topic( Windows_GetByNameOrCreate(Server, user), message );
221                 break;
222         case 333:       // Topic set by
223                 user = message; // Channel
224                 message = GetValue(Line, &pos); // User
225                 timestamp = GetValue(Line, &pos);       // Timestamp
226                 Window_AppendMsg_TopicTime( Windows_GetByNameOrCreate(Server, user), message, timestamp );
227                 break;
228         case 353:       // /NAMES list
229                 // <user> = <channel> :list
230                 // '=' was eaten in and set to message
231                 user = GetValue(Line, &pos);    // Actually channel
232                 message = Line + pos + 1;       // List
233                 // TODO: parse and store
234                 Window_AppendMessage( Windows_GetByNameOrCreate(Server, user), MSG_CLASS_CLIENT, "NAMES", message );
235                 break;
236         case 366:       // end of /NAMES list
237                 // <user> <channel> :msg
238                 // - Ignored
239                 break;
240         case 372:       // MOTD Data
241         case 375:       // MOTD Start
242         case 376:       // MOTD End
243                 
244         default:
245                 //printf("[%s] %i %s\n", Server->Name, num, message);
246                 Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_WALL, Server->Name, "Unknown %i %s", Num, message);
247                 break;
248         }
249 }
250
251 void ParseServerLine_String(tServer *Server, const char *ident, const char *cmd, char *Line)
252 {
253          int    pos = 0;
254         _SysDebug("ident=%s,cmd=%s,Line=%s", ident, cmd, Line);
255         if( strcmp(cmd, "NOTICE") == 0 )
256         {
257                 const char *class = GetValue(Line, &pos);
258                 _SysDebug("NOTICE class='%s'", class);
259                 
260                 const char *message = (Line[pos] == ':') ? Line + pos + 1 : GetValue(Line, &pos);
261                 
262                 //printf("[%s] NOTICE %s: %s\n", Server->Name, ident, message);
263                 char *ident_bang = strchr(ident, '!');
264                 if( ident_bang ) {
265                         *ident_bang = '\0';
266                 }
267                 // TODO: Colour codes
268                 Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_WALL, Server->Name, "%s %s", ident, message);
269         }
270         else if( strcmp(cmd, "PRIVMSG") == 0 )
271         {
272                 const char *dest = GetValue(Line, &pos);
273                 const char *message = (Line[pos] == ':') ? Line + pos + 1 : GetValue(Line, &pos);
274
275                 char *ident_bang = strchr(ident, '!');
276                 if( ident_bang ) {
277                         *ident_bang = '\0';
278                 }
279                 Cmd_PRIVMSG(Server, dest, ident, message);
280         }
281         else if( strcmp(cmd, "JOIN" ) == 0 )
282         {
283                 const char      *channel = Line + pos + 1;
284                 
285                 Window_AppendMsg_Join( Windows_GetByNameOrCreate(Server, channel), ident );
286         }
287         else if( strcmp(cmd, "PART" ) == 0 )
288         {
289                 const char      *channel = Line + pos + 1;
290                 
291                 Window_AppendMsg_Part( Windows_GetByNameOrCreate(Server, channel), ident, "" );
292         }
293         else
294         {
295                 Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_BARE, Server->Name, "Unknown command '%s' %s", cmd, Line);
296         }
297 }
298
299 /**
300  */
301 void ParseServerLine(tServer *Server, char *Line)
302 {
303          int    pos = 0;
304
305         _SysDebug("[%s] %s", Server->Name, Line);       
306         
307         // Message?
308         if( *Line == ':' )
309         {
310                 pos ++;
311                 const char *ident = GetValue(Line, &pos);       // Ident (user or server)
312                 const char *cmd = GetValue(Line, &pos);
313                 
314                 // Numeric command
315                 if( isdigit(cmd[0]) && isdigit(cmd[1]) && isdigit(cmd[2]) )
316                 {
317                          int    num;
318                         num  = (cmd[0] - '0') * 100;
319                         num += (cmd[1] - '0') * 10;
320                         num += (cmd[2] - '0') * 1;
321
322                         ParseServerLine_Numeric(Server, ident, num, Line+pos);
323                 }
324                 else
325                 {
326                         ParseServerLine_String(Server, ident, cmd, Line+pos);
327                 }
328         }
329         else {
330                 const char *cmd = GetValue(Line, &pos);
331                 
332                 if( strcmp(cmd, "PING") == 0 ) {
333                         Server_SendCommand(Server, "PONG %s", Line+pos);
334                 }
335                 else {
336                         // Command to client
337                         Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Server->Name, "UNK Client Command: %s", Line);
338                 }
339         }
340 }

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