8 #include <ctype.h> // isdigit
9 #include <stdio.h> // vsnprintf
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);
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";
25 void Servers_FillSelect(int *nfds, fd_set *rfds, fd_set *efds)
27 for( tServer *srv = gpServers; srv; srv = srv->Next )
29 FD_SET(srv->FD, rfds);
30 FD_SET(srv->FD, efds);
31 if( srv->FD >= *nfds )
36 void Servers_HandleSelect(int nfds, const fd_set *rfds, const fd_set *efds)
38 for( tServer *srv = gpServers; srv; srv = srv->Next )
40 if(FD_ISSET(srv->FD, rfds))
42 int rv = Server_HandleIncoming(srv);
46 _SysDebug("ProcessIncoming failed on FD%i (Server %p %s)",
47 srv->FD, srv, srv->Name);
48 Exit("Processing error");
52 if(FD_ISSET(srv->FD, efds))
54 _SysDebug("Error on FD%i (Server %p %s)",
55 srv->FD, srv, srv->Name);
61 void Servers_CloseAll(const char *QuitMessage)
65 tServer *srv = gpServers;
66 gpServers = srv->Next;
68 Server_SendCommand(srv, "QUIT :%s", QuitMessage);
75 * \brief Connect to a server
77 tServer *Server_Connect(const char *Name, const char *AddressString, short PortNumber)
81 ret = calloc(1, sizeof(tServer) + strlen(Name) + 1);
83 strcpy(ret->Name, Name);
85 // Connect to the remove server
86 ret->FD = OpenTCP( AddressString, PortNumber );
89 Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Unable to create socket");
93 ret->Nick = strdup(gsNickname);
95 // Append to open list
96 ret->Next = gpServers;
99 // Read some initial data
100 Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Name, "Unable to create socket");
101 Server_HandleIncoming(ret);
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");
111 const char *Server_GetNick(const tServer *Server)
113 return Server ? Server->Nick : "NULL";
115 const char *Server_GetName(const tServer *Server)
117 return Server ? Server->Name : "NULL";
120 void Server_SendCommand(tServer *Server, const char *Format, ...)
125 va_start(args, Format);
126 len = vsnprintf(NULL, 0, Format, args);
130 va_start(args, Format);
131 vsnprintf(buf, len+1, Format, args);
134 _SysWrite(Server->FD, buf, len);
135 _SysWrite(Server->FD, "\n", 1);
138 void Cmd_PRIVMSG(tServer *Server, const char *Dest, const char *Src, const char *Message)
141 if( strcmp(Dest, Server->Nick) == 0 ) {
142 win = Windows_GetByNameOrCreate(Server, Src);
145 win = Windows_GetByNameOrCreate(Server, Dest);
149 if( Message[0] == '\1' && Message[strlen(Message)-1] == '\1' )
152 // message is a CTCP command
153 if( strcmp(Message, "VERSION\1") == 0 )
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);
160 else if( strncmp(Message, "ACTION ", 7) == 0 )
163 // Put a message in the status window, and reply
164 Window_AppendMessage(win, MSG_CLASS_ACTION, Src, "%.*s", (int)(strlen(Message)-1), Message);
168 Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Server->Name, "Unknown CTCP '%s' from %s",
174 Window_AppendMessage(win, MSG_CLASS_MESSAGE, Src, "%s", Message);
179 * \brief Process incoming lines from the server
181 int Server_HandleIncoming(tServer *Server)
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
191 while( (len = _SysIOCtl(Server->FD, 8, NULL)) > 0 )
195 len = _SysRead(Server->FD, &Server->InBuf[Server->ReadPos], sizeof(Server->InBuf) - Server->ReadPos);
199 Server->InBuf[Server->ReadPos + len] = '\0';
203 while( (newline = strchr(ptr, '\n')) )
206 if( newline[-1] == '\r' ) newline[-1] = '\0';
207 ParseServerLine(Server, ptr);
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);
230 void ParseServerLine_Numeric(tServer *Server, const char *ident, int Num, char *Line)
234 const char *user = GetValue(Line, &pos);
235 const char *timestamp;
237 if( Line[pos] == ':' ) {
238 message = Line + pos + 1;
241 message = GetValue(Line, &pos);
247 user = message; // Channel
248 message = Line + pos + 1; // Topic
249 Window_AppendMsg_Topic( Windows_GetByNameOrCreate(Server, user), message );
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 );
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 );
265 case 366: // end of /NAMES list
266 // <user> <channel> :msg
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
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)
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);
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);
298 void ParseServerLine_String(tServer *Server, const char *ident, const char *cmd, char *Line)
301 _SysDebug("ident=%s,cmd=%s,Line=%s", ident, cmd, Line);
302 if( strcmp(cmd, "NOTICE") == 0 )
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);
308 char *ident_bang = strchr(ident, '!');
312 // TODO: Colour codes
313 Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_WALL, Server->Name, "%s %s", ident, message);
315 else if( strcmp(cmd, "PRIVMSG") == 0 )
317 const char *dest = GetValue(Line, &pos);
318 const char *message = (Line[pos] == ':') ? Line + pos + 1 : GetValue(Line, &pos);
320 char *ident_bang = strchr(ident, '!');
324 Cmd_PRIVMSG(Server, dest, ident, message);
326 else if( strcmp(cmd, "JOIN" ) == 0 )
328 const char *channel = Line + pos + 1;
330 Window_AppendMsg_Join( Windows_GetByNameOrCreate(Server, channel), ident );
332 else if( strcmp(cmd, "PART" ) == 0 )
334 const char *channel = Line + pos + 1;
336 Window_AppendMsg_Part( Windows_GetByNameOrCreate(Server, channel), ident, "" );
338 else if( strcmp(cmd, "MODE" ) == 0 )
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;
345 Window_AppendMsg_Mode( Windows_GetByNameOrCreate(Server, channel), ident, flags, args );
347 else if( strcmp(cmd, "KICK" ) == 0 )
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;
354 Window_AppendMsg_Kick( Windows_GetByNameOrCreate(Server, channel), ident, nick, message );
355 if( strcmp(nick, Server->Nick) == 0 ) {
356 // Oh, that was me :(
362 Window_AppendMessage( WINDOW_STATUS, MSG_CLASS_BARE, Server->Name, "Unknown command '%s' %s", cmd, Line);
368 void ParseServerLine(tServer *Server, char *Line)
372 _SysDebug("[%s] %s", Server->Name, Line);
378 const char *ident = GetValue(Line, &pos); // Ident (user or server)
379 const char *cmd = GetValue(Line, &pos);
382 if( isdigit(cmd[0]) && isdigit(cmd[1]) && isdigit(cmd[2]) )
385 num = (cmd[0] - '0') * 100;
386 num += (cmd[1] - '0') * 10;
387 num += (cmd[2] - '0') * 1;
389 ParseServerLine_Numeric(Server, ident, num, Line+pos);
393 ParseServerLine_String(Server, ident, cmd, Line+pos);
397 const char *cmd = GetValue(Line, &pos);
399 if( strcmp(cmd, "PING") == 0 ) {
400 Server_SendCommand(Server, "PONG %s", Line+pos);
404 Window_AppendMessage(WINDOW_STATUS, MSG_CLASS_CLIENT, Server->Name, "UNK Client Command: %s", Line);