- Unterstuetzung fuer die Konfigurationsdatei eingebaut.
[ngircd-alex.git] / src / ngircd / irc.c
1 /*
2  * ngIRCd -- The Next Generation IRC Daemon
3  * Copyright (c)2001 by Alexander Barton (alex@barton.de)
4  *
5  * Dieses Programm ist freie Software. Sie koennen es unter den Bedingungen
6  * der GNU General Public License (GPL), wie von der Free Software Foundation
7  * herausgegeben, weitergeben und/oder modifizieren, entweder unter Version 2
8  * der Lizenz oder (wenn Sie es wuenschen) jeder spaeteren Version.
9  * Naehere Informationen entnehmen Sie bitter der Datei COPYING. Eine Liste
10  * der an comBase beteiligten Autoren finden Sie in der Datei AUTHORS.
11  *
12  * $Id: irc.c,v 1.15 2001/12/30 19:26:11 alex Exp $
13  *
14  * irc.c: IRC-Befehle
15  *
16  * $Log: irc.c,v $
17  * Revision 1.15  2001/12/30 19:26:11  alex
18  * - Unterstuetzung fuer die Konfigurationsdatei eingebaut.
19  *
20  * Revision 1.14  2001/12/30 11:42:00  alex
21  * - der Server meldet nun eine ordentliche "Start-Zeit".
22  *
23  * Revision 1.13  2001/12/29 03:10:06  alex
24  * - Neue Funktion IRC_MODE() implementiert, div. Aenderungen.
25  * - neue configure-Optione "--enable-strict-rfc".
26  *
27  * Revision 1.12  2001/12/27 19:17:26  alex
28  * - neue Befehle PRIVMSG, NOTICE, PING.
29  *
30  * Revision 1.11  2001/12/27 16:55:41  alex
31  * - neu: IRC_WriteStrRelated(), Aenderungen auch in IRC_WriteStrClient().
32  *
33  * Revision 1.10  2001/12/26 22:48:53  alex
34  * - MOTD-Datei ist nun konfigurierbar und wird gelesen.
35  *
36  * Revision 1.9  2001/12/26 14:45:37  alex
37  * - "Code Cleanups".
38  *
39  * Revision 1.8  2001/12/26 03:21:46  alex
40  * - PING/PONG-Befehle implementiert,
41  * - Meldungen ueberarbeitet: enthalten nun (fast) immer den Nick.
42  *
43  * Revision 1.7  2001/12/25 23:25:18  alex
44  * - und nochmal Aenderungen am Logging ;-)
45  *
46  * Revision 1.6  2001/12/25 23:13:33  alex
47  * - Debug-Meldungen angepasst.
48  *
49  * Revision 1.5  2001/12/25 22:02:42  alex
50  * - neuer IRC-Befehl "/QUIT". Verbessertes Logging & Debug-Ausgaben.
51  *
52  * Revision 1.4  2001/12/25 19:19:30  alex
53  * - bessere Fehler-Abfragen, diverse Bugfixes.
54  * - Nicks werden nur einmal vergeben :-)
55  * - /MOTD wird unterstuetzt.
56  *
57  * Revision 1.3  2001/12/24 01:34:06  alex
58  * - USER und NICK wird nun in beliebiger Reihenfolge akzeptiert (wg. BitchX)
59  * - MOTD-Ausgabe begonnen zu implementieren.
60  *
61  * Revision 1.2  2001/12/23 21:57:16  alex
62  * - erste IRC-Befehle zu implementieren begonnen.
63  *
64  * Revision 1.1  2001/12/14 08:13:43  alex
65  * - neues Modul begonnen :-)
66  */
67
68
69 #include <portab.h>
70 #include "global.h"
71
72 #include <imp.h>
73 #include <assert.h>
74 #include <errno.h>
75 #include <stdarg.h>
76 #include <stdio.h>
77 #include <string.h>
78
79 #include "ngircd.h"
80 #include "client.h"
81 #include "conf.h"
82 #include "log.h"
83 #include "messages.h"
84 #include "parse.h"
85
86 #include <exp.h>
87 #include "irc.h"
88
89
90 #define CONNECTED TRUE
91 #define DISCONNECTED FALSE
92
93
94 LOCAL BOOLEAN Check_Valid_User( CLIENT *Client );
95
96 LOCAL BOOLEAN Hello_User( CLIENT *Client );
97 LOCAL BOOLEAN Show_MOTD( CLIENT *Client );
98
99
100 GLOBAL VOID IRC_Init( VOID )
101 {
102 } /* IRC_Init */
103
104
105 GLOBAL VOID IRC_Exit( VOID )
106 {
107 } /* IRC_Exit */
108
109
110 GLOBAL BOOLEAN IRC_WriteStrClient( CLIENT *Client, CLIENT *Prefix, CHAR *Format, ... )
111 {
112         /* Text an Clients, lokal bzw. remote, senden. */
113
114         CHAR buffer[1000];
115         BOOLEAN ok = CONNECTED;
116         CONN_ID send_to;
117         va_list ap;
118
119         assert( Client != NULL );
120         assert( Format != NULL );
121
122         va_start( ap, Format );
123         vsnprintf( buffer, 1000, Format, ap );
124         va_end( ap );
125
126         if( Client->conn_id != NONE ) send_to = Client->conn_id;
127         else send_to = Client->introducer->conn_id;
128
129         if( Prefix ) ok = Conn_WriteStr( Client->conn_id, ":%s %s", Client_GetID( Prefix ), buffer );
130         else ok = Conn_WriteStr( Client->conn_id, buffer );
131
132         return ok;
133 } /* IRC_WriteStrClient */
134
135
136 GLOBAL BOOLEAN IRC_WriteStrRelated( CLIENT *Client, CHAR *Format, ... )
137 {
138         CHAR buffer[1000];
139         BOOLEAN ok = CONNECTED;
140         va_list ap;
141
142         assert( Client != NULL );
143         assert( Format != NULL );
144
145         va_start( ap, Format );
146         vsnprintf( buffer, 1000, Format, ap );
147         va_end( ap );
148
149         /* an den Client selber */
150         ok = IRC_WriteStrClient( Client, Client, buffer );
151
152         return ok;
153 } /* IRC_WriteStrRelated */
154
155
156 GLOBAL BOOLEAN IRC_PASS( CLIENT *Client, REQUEST *Req )
157 {
158         assert( Client != NULL );
159         assert( Req != NULL );
160
161         if( Client->type == CLIENT_UNKNOWN )
162         {
163                 Log( LOG_DEBUG, "Connection %d: got PASS command ...", Client->conn_id );
164                 return IRC_WriteStrClient( Client, This_Server, ERR_UNKNOWNCOMMAND_MSG, Client_Name( Client ), Req->command );
165         }
166         else return IRC_WriteStrClient( Client, This_Server, ERR_ALREADYREGISTRED_MSG, Client_Name( Client ));
167 } /* IRC_PASS */
168
169
170 GLOBAL BOOLEAN IRC_NICK( CLIENT *Client, REQUEST *Req )
171 {
172         assert( Client != NULL );
173         assert( Req != NULL );
174
175         /* Zumindest BitchX sendet NICK-USER in der falschen Reihenfolge. */
176 #ifndef STRICT_RFC
177         if( Client->type == CLIENT_UNKNOWN || Client->type == CLIENT_GOTPASS || Client->type == CLIENT_GOTNICK || Client->type == CLIENT_GOTUSER || Client->type == CLIENT_USER )
178 #else
179         if( Client->type == CLIENT_UNKNOWN || Client->type == CLIENT_GOTPASS || Client->type == CLIENT_GOTNICK || Client->type == CLIENT_USER  )
180 #endif
181         {
182                 /* Falsche Anzahl Parameter? */
183                 if( Req->argc != 1 ) return IRC_WriteStrClient( Client, This_Server, ERR_NEEDMOREPARAMS_MSG, Client_Name( Client ), Req->command );
184
185                 /* Ist der Client "restricted"? */
186                 if( strchr( Client->modes, 'r' )) return IRC_WriteStrClient( Client, This_Server, ERR_RESTRICTED_MSG, Client_Name( Client ));
187
188                 /* Wenn der Client zu seinem eigenen Nick wechseln will, so machen
189                  * wir nichts. So macht es das Original und mind. Snak hat probleme,
190                  * wenn wir es nicht so machen. Ob es so okay ist? Hm ... */
191 #ifndef STRICT_RFC
192                 if( strcasecmp( Client->nick, Req->argv[0] ) == 0 ) return CONNECTED;
193 #endif
194                 
195                 /* pruefen, ob Nick bereits vergeben */
196                 if( ! Client_CheckNick( Client, Req->argv[0] )) return CONNECTED;
197
198                 if( Client->type == CLIENT_USER )
199                 {
200                         /* Nick-Aenderung: allen mitteilen! */
201                         Log( LOG_INFO, "User \"%s!%s@%s\" changed nick: \"%s\" -> \"%s\".", Client->nick, Client->user, Client->host, Client->nick, Req->argv[0] );
202                         IRC_WriteStrRelated( Client, "NICK :%s", Req->argv[0] );
203                 }
204                 
205                 /* Client-Nick registrieren */
206                 strcpy( Client->nick, Req->argv[0] );
207
208                 if( Client->type != CLIENT_USER )
209                 {
210                         /* Neuer Client */
211                         Log( LOG_DEBUG, "Connection %d: got NICK command ...", Client->conn_id );
212                         if( Client->type == CLIENT_GOTUSER ) return Hello_User( Client );
213                         else Client->type = CLIENT_GOTNICK;
214                 }
215                 return CONNECTED;
216         }
217         else return IRC_WriteStrClient( Client, This_Server, ERR_ALREADYREGISTRED_MSG, Client_Name( Client ));
218 } /* IRC_NICK */
219
220
221 GLOBAL BOOLEAN IRC_USER( CLIENT *Client, REQUEST *Req )
222 {
223         assert( Client != NULL );
224         assert( Req != NULL );
225
226 #ifndef STRICT_RFC
227         if( Client->type == CLIENT_GOTNICK || Client->type == CLIENT_GOTPASS || Client->type == CLIENT_UNKNOWN )
228 #else
229         if( Client->type == CLIENT_GOTNICK || Client->type == CLIENT_GOTPASS )
230 #endif
231         {
232                 /* Falsche Anzahl Parameter? */
233                 if( Req->argc != 4 ) return IRC_WriteStrClient( Client, This_Server, ERR_NEEDMOREPARAMS_MSG, Client_Name( Client ), Req->command );
234
235                 strncpy( Client->user, Req->argv[0], CLIENT_USER_LEN );
236                 Client->user[CLIENT_USER_LEN] = '\0';
237                 strncpy( Client->name, Req->argv[3], CLIENT_NAME_LEN );
238                 Client->name[CLIENT_NAME_LEN] = '\0';
239
240                 Log( LOG_DEBUG, "Connection %d: got USER command ...", Client->conn_id );
241                 if( Client->type == CLIENT_GOTNICK ) return Hello_User( Client );
242                 else Client->type = CLIENT_GOTUSER;
243                 return CONNECTED;
244         }
245         else if( Client->type == CLIENT_USER || Client->type == CLIENT_SERVER || Client->type == CLIENT_SERVICE )
246         {
247                 return IRC_WriteStrClient( Client, This_Server, ERR_ALREADYREGISTRED_MSG, Client_Name( Client ));
248         }
249         else return IRC_WriteStrClient( Client, This_Server, ERR_NOTREGISTERED_MSG, Client_Name( Client ));
250 } /* IRC_USER */
251
252
253 GLOBAL BOOLEAN IRC_QUIT( CLIENT *Client, REQUEST *Req )
254 {
255         assert( Client != NULL );
256         assert( Req != NULL );
257
258         if( Client->type != CLIENT_SERVER && Client->type != CLIENT_SERVICE )
259         {
260                 /* Falsche Anzahl Parameter? */
261                 if( Req->argc > 1 ) return IRC_WriteStrClient( Client, This_Server, ERR_NEEDMOREPARAMS_MSG, Client_Name( Client ), Req->command );
262
263                 Conn_Close( Client->conn_id, "Client wants to quit." );
264                 return DISCONNECTED;
265         }
266         else return IRC_WriteStrClient( Client, This_Server, ERR_NOTREGISTERED_MSG, Client_Name( Client ));
267 } /* IRC_QUIT */
268
269
270 GLOBAL BOOLEAN IRC_PING( CLIENT *Client, REQUEST *Req )
271 {
272         CLIENT *to;
273         
274         assert( Client != NULL );
275         assert( Req != NULL );
276
277         if( ! Check_Valid_User( Client )) return CONNECTED;
278
279         /* Falsche Anzahl Parameter? */
280         if( Req->argc < 1 ) return IRC_WriteStrClient( Client, This_Server, ERR_NOORIGIN_MSG, Client_Name( Client ));
281         if( Req->argc > 1 ) return IRC_WriteStrClient( Client, This_Server, ERR_NEEDMOREPARAMS_MSG, Client_Name( Client ), Req->command );
282
283         to = Client_Search( Req->argv[0] );
284         
285         if( to ) return IRC_WriteStrClient( Client, This_Server, "PONG :%s", Client_Name( Client ));
286         else return IRC_WriteStrClient( Client, This_Server, ERR_NOSUCHNICK_MSG, Client_Name( Client ), Req->argv[0] );
287 } /* IRC_PING */
288
289
290 GLOBAL BOOLEAN IRC_PONG( CLIENT *Client, REQUEST *Req )
291 {
292         assert( Client != NULL );
293         assert( Req != NULL );
294
295         if( ! Check_Valid_User( Client )) return CONNECTED;
296
297         /* Falsche Anzahl Parameter? */
298         if( Req->argc < 1 ) return IRC_WriteStrClient( Client, This_Server, ERR_NOORIGIN_MSG, Client_Name( Client ));
299         if( Req->argc > 1 ) return IRC_WriteStrClient( Client, This_Server, ERR_NEEDMOREPARAMS_MSG, Client_Name( Client ), Req->command );
300
301         /* Der Connection-Timestamp wurde schon beim Lesen aus dem Socket
302          * aktualisiert, daher muss das hier nicht mehr gemacht werden. */
303
304         Log( LOG_DEBUG, "Connection %d: received PONG.", Client->conn_id );
305         return CONNECTED;
306 } /* IRC_PONG */
307
308
309 GLOBAL BOOLEAN IRC_MOTD( CLIENT *Client, REQUEST *Req )
310 {
311         assert( Client != NULL );
312         assert( Req != NULL );
313
314         if( ! Check_Valid_User( Client )) return CONNECTED;
315
316         /* Falsche Anzahl Parameter? */
317         if( Req->argc != 0 ) return IRC_WriteStrClient( Client, This_Server, ERR_NEEDMOREPARAMS_MSG, Client_Name( Client ), Req->command );
318
319         return Show_MOTD( Client );
320 } /* IRC_MOTD */
321
322
323 GLOBAL BOOLEAN IRC_PRIVMSG( CLIENT *Client, REQUEST *Req )
324 {
325         CLIENT *to;
326         
327         assert( Client != NULL );
328         assert( Req != NULL );
329
330         if( ! Check_Valid_User( Client )) return CONNECTED;
331
332         /* Falsche Anzahl Parameter? */
333         if( Req->argc == 0 ) return IRC_WriteStrClient( Client, This_Server, ERR_NORECIPIENT_MSG, Client_Name( Client ), Req->command );
334         if( Req->argc == 1 ) return IRC_WriteStrClient( Client, This_Server, ERR_NOTEXTTOSEND_MSG, Client_Name( Client ));
335         if( Req->argc > 2 ) return IRC_WriteStrClient( Client, This_Server, ERR_NEEDMOREPARAMS_MSG, Client_Name( Client ), Req->command );
336
337         to = Client_Search( Req->argv[0] );
338         if( to )
339         {
340                 /* Okay, Ziel ist ein User */
341                 return IRC_WriteStrClient( to, Client, "PRIVMSG %s :%s", to->nick, Req->argv[1] );
342         }
343
344         return IRC_WriteStrClient( Client, This_Server, ERR_NOSUCHNICK_MSG, Client_Name( Client ), Req->argv[0] );
345 } /* IRC_PRIVMSG */
346
347
348 GLOBAL BOOLEAN IRC_NOTICE( CLIENT *Client, REQUEST *Req )
349 {
350         CLIENT *to;
351
352         assert( Client != NULL );
353         assert( Req != NULL );
354
355         if( ! Check_Valid_User( Client )) return CONNECTED;
356
357         /* Falsche Anzahl Parameter? */
358         if( Req->argc != 2 ) return CONNECTED;
359
360         to = Client_Search( Req->argv[0] );
361         if( to )
362         {
363                 /* Okay, Ziel ist ein User */
364                 return IRC_WriteStrClient( to, Client, "NOTICE %s :%s", to->nick, Req->argv[1] );
365         }
366
367         return CONNECTED;
368 } /* IRC_NOTICE */
369
370
371 GLOBAL BOOLEAN IRC_MODE( CLIENT *Client, REQUEST *Req )
372 {
373         CHAR x[2], new_modes[CLIENT_MODE_LEN], *ptr, *p;
374         BOOLEAN set, ok;
375         
376         assert( Client != NULL );
377         assert( Req != NULL );
378
379         if( ! Check_Valid_User( Client )) return CONNECTED;
380
381         /* Falsche Anzahl Parameter? */
382         if(( Req->argc > 2 ) || ( Req->argc < 1 )) return IRC_WriteStrClient( Client, This_Server, ERR_NEEDMOREPARAMS_MSG, Client_Name( Client ), Req->command );
383
384         /* MODE ist nur fuer sich selber zulaessig */
385         if( Client_Search( Req->argv[0] ) != Client ) return IRC_WriteStrClient( Client, This_Server, ERR_USERSDONTMATCH_MSG, Client_Name( Client ));
386
387         /* Werden die Modes erfragt? */
388         if( Req->argc == 1 ) return IRC_WriteStrClient( Client, This_Server, RPL_UMODEIS_MSG, Client_Name( Client ), Client->modes );
389
390         ptr = Req->argv[1];
391
392         /* Sollen Modes gesetzt oder geloescht werden? */
393         if( *ptr == '+' ) set = TRUE;
394         else if( *ptr == '-' ) set = FALSE;
395         else return IRC_WriteStrClient( Client, This_Server, ERR_UMODEUNKNOWNFLAG_MSG, Client_Name( Client ));
396
397         /* Reply-String mit Aenderungen vorbereiten */
398         if( set ) strcpy( new_modes, "+" );
399         else strcpy( new_modes, "-" );
400
401         ptr++;
402         ok = TRUE;
403         x[1] = '\0';
404         while( *ptr )
405         {
406                 x[0] = '\0';
407                 switch( *ptr )
408                 {
409                         case 'i':
410                                 /* invisible */
411                                 x[0] = 'i';
412                                 break;
413                         case 'r':
414                                 /* restricted (kann nur gesetzt werden) */
415                                 if( set ) x[0] = 'r';
416                                 else ok = IRC_WriteStrClient( Client, This_Server, ERR_RESTRICTED_MSG, Client_Name( Client ));
417                                 break;
418                         default:
419                                 ok = IRC_WriteStrClient( Client, This_Server, ERR_UMODEUNKNOWNFLAG_MSG, Client_Name( Client ));
420                                 x[0] = '\0';
421                 }
422                 if( ! ok ) break;
423
424                 ptr++;
425                 if( ! x[0] ) continue;
426
427                 /* Okay, gueltigen Mode gefunden */
428                 if( set )
429                 {
430                         /* Modes sollen gesetzt werden */
431                         if( ! strchr( Client->modes, x[0] ))
432                         {
433                                 /* Client hat den Mode noch nicht -> setzen */
434                                 strcat( Client->modes, x );
435                                 strcat( new_modes, x );
436                         }
437                 }
438                 else
439                 {
440                         /* Modes sollen geloescht werden */
441                         p = strchr( Client->modes, x[0] );
442                         if( p )
443                         {
444                                 /* Client hat den Mode -> loeschen */
445                                 while( *p )
446                                 {
447                                         *p = *(p + 1);
448                                         p++;
449                                 }
450                                 strcat( new_modes, x );
451                         }
452                 }
453         }
454         
455         /* Geanderte Modes? */
456         if( new_modes[1] && ok )
457         {
458                 ok = IRC_WriteStrRelated( Client, "MODE %s :%s", Client->nick, new_modes );
459                 Log( LOG_DEBUG, "User \"%s!%s@%s\": Mode change, now \"%s\".", Client->nick, Client->user, Client->host, Client->modes );
460         }
461         return ok;
462 } /* IRC_MODE */
463
464
465 LOCAL BOOLEAN Check_Valid_User( CLIENT *Client )
466 {
467         assert( Client != NULL );
468
469         if( Client->type != CLIENT_USER )
470         {
471                 IRC_WriteStrClient( Client, This_Server, ERR_NOTREGISTERED_MSG, Client_Name( Client ));
472                 return FALSE;
473         }
474         else return TRUE;
475 } /* Check_Valid_User */
476
477
478 LOCAL BOOLEAN Hello_User( CLIENT *Client )
479 {
480         assert( Client != NULL );
481         assert( Client->nick[0] );
482         
483         Log( LOG_NOTICE, "User \"%s!%s@%s\" (%s) registered (connection %d).", Client->nick, Client->user, Client->host, Client->name, Client->conn_id );
484
485         IRC_WriteStrClient( Client, This_Server, RPL_WELCOME_MSG, Client->nick, Client_GetID( Client ));
486         IRC_WriteStrClient( Client, This_Server, RPL_YOURHOST_MSG, Client->nick, This_Server->nick );
487         IRC_WriteStrClient( Client, This_Server, RPL_CREATED_MSG, Client->nick, NGIRCd_StartStr );
488         IRC_WriteStrClient( Client, This_Server, RPL_MYINFO_MSG, Client->nick, This_Server->nick );
489
490         Client->type = CLIENT_USER;
491
492         return Show_MOTD( Client );
493 } /* Hello_User */
494
495
496 LOCAL BOOLEAN Show_MOTD( CLIENT *Client )
497 {
498         BOOLEAN ok;
499         CHAR line[127];
500         FILE *fd;
501         
502         assert( Client != NULL );
503         assert( Client->nick[0] );
504
505         fd = fopen( Conf_MotdFile, "r" );
506         if( ! fd )
507         {
508                 Log( LOG_WARNING, "Can't read MOTD file \"%s\": %s", Conf_MotdFile, strerror( errno ));
509                 return IRC_WriteStrClient( Client, This_Server, ERR_NOMOTD_MSG, Client->nick );
510         }
511         
512         IRC_WriteStrClient( Client, This_Server, RPL_MOTDSTART_MSG, Client->nick, This_Server->nick );
513         while( TRUE )
514         {
515                 if( ! fgets( line, 126, fd )) break;
516                 if( line[strlen( line ) - 1] == '\n' ) line[strlen( line ) - 1] = '\0';
517                 IRC_WriteStrClient( Client, This_Server, RPL_MOTD_MSG, Client->nick, line );
518         }
519         ok = IRC_WriteStrClient( Client, This_Server, RPL_ENDOFMOTD_MSG, Client->nick );
520
521         fclose( fd );
522         
523         return ok;
524 } /* Show_MOTD */
525
526
527 /* -eof- */