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