3b9e903d4c92eb29d83f8cabe6f606e3f1648e7b
[ngircd-alex.git] / src / ngircd / irc.c
1 /*
2  * ngIRCd -- The Next Generation IRC Daemon
3  * Copyright (c)2001,2002 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 ngIRCd beteiligten Autoren finden Sie in der Datei AUTHORS.
11  *
12  * $Id: irc.c,v 1.83 2002/02/28 00:48:26 alex Exp $
13  *
14  * irc.c: IRC-Befehle
15  *
16  * $Log: irc.c,v $
17  * Revision 1.83  2002/02/28 00:48:26  alex
18  * - Forwarding von TOPIC an andere Server gefixed. Hoffentlich ;-)
19  *
20  * Revision 1.82  2002/02/27 23:26:36  alex
21  * - einige Funktionen in irc-xxx-Module ausgegliedert.
22  *
23  * Revision 1.81  2002/02/27 20:55:44  alex
24  * - Channel-Topics werden nun auch korrekt von anderen Server angenommen.
25  *
26  * Revision 1.80  2002/02/27 20:33:13  alex
27  * - Channel-Topics implementiert.
28  *
29  * Revision 1.79  2002/02/27 18:57:21  alex
30  * - PRIVMSG zeugt nun bei Texten an User an, wenn diese "away" sind.
31  *
32  * Revision 1.78  2002/02/27 18:23:45  alex
33  * - IRC-Befehl "AWAY" implementert.
34  *
35  * Revision 1.77  2002/02/27 17:05:41  alex
36  * - PRIVMSG beachtet nun die Channel-Modes "n" und "m".
37  *
38  * Revision 1.76  2002/02/27 16:04:14  alex
39  * - Bug bei belegtem Nickname bei User-Registrierung (NICK-Befehl) behoben.
40  *
41  * Revision 1.75  2002/02/27 15:23:27  alex
42  * - NAMES beachtet nun das "invisible" Flag ("i") von Usern.
43  *
44  * Revision 1.74  2002/02/27 03:44:53  alex
45  * - gerade eben in SQUIT eingefuehrten Bug behoben: entfernte Server werden nun
46  *   nur noch geloescht, die Verbindung, von der SQUIT kam, bleibt wieder offen.
47  *
48  * Revision 1.73  2002/02/27 03:08:05  alex
49  * - Log-Meldungen bei SQUIT erneut ueberarbeitet ...
50  *
51  * Revision 1.72  2002/02/27 02:26:58  alex
52  * - SQUIT wird auf jeden Fall geforwarded, zudem besseres Logging.
53  *
54  * Revision 1.71  2002/02/27 00:50:05  alex
55  * - einige unnoetige Client_NextHop()-Aufrufe entfernt.
56  * - NAMES korrigiert und komplett implementiert.
57  *
58  * Revision 1.70  2002/02/26 22:06:40  alex
59  * - Nick-Aenderungen werden nun wieder korrekt ins Logfile geschrieben.
60  */
61
62
63 #include <portab.h>
64 #include "global.h"
65
66 #include <imp.h>
67 #include <assert.h>
68 #include <errno.h>
69 #include <stdio.h>
70 #include <stdlib.h>
71 #include <string.h>
72
73 #include "ngircd.h"
74 #include "channel.h"
75 #include "client.h"
76 #include "conf.h"
77 #include "conn.h"
78 #include "irc-write.h"
79 #include "log.h"
80 #include "messages.h"
81 #include "tool.h"
82
83 #include <exp.h>
84 #include "irc.h"
85
86
87 GLOBAL BOOLEAN IRC_MOTD( CLIENT *Client, REQUEST *Req )
88 {
89         assert( Client != NULL );
90         assert( Req != NULL );
91
92         if( Client_Type( Client ) != CLIENT_USER ) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
93
94         /* Falsche Anzahl Parameter? */
95         if( Req->argc != 0 ) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
96
97         return IRC_Show_MOTD( Client );
98 } /* IRC_MOTD */
99
100
101 GLOBAL BOOLEAN IRC_PRIVMSG( CLIENT *Client, REQUEST *Req )
102 {
103         BOOLEAN is_member, has_voice, is_op, ok;
104         CLIENT *cl, *from;
105         CHANNEL *chan;
106         
107         assert( Client != NULL );
108         assert( Req != NULL );
109
110         if(( Client_Type( Client ) != CLIENT_USER ) && ( Client_Type( Client ) != CLIENT_SERVER )) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
111
112         /* Falsche Anzahl Parameter? */
113         if( Req->argc == 0 ) return IRC_WriteStrClient( Client, ERR_NORECIPIENT_MSG, Client_ID( Client ), Req->command );
114         if( Req->argc == 1 ) return IRC_WriteStrClient( Client, ERR_NOTEXTTOSEND_MSG, Client_ID( Client ));
115         if( Req->argc > 2 ) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
116
117         if( Client_Type( Client ) == CLIENT_SERVER ) from = Client_GetFromID( Req->prefix );
118         else from = Client;
119         if( ! from ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
120
121         cl = Client_Search( Req->argv[0] );
122         if( cl )
123         {
124                 /* Okay, Ziel ist ein User */
125                 if(( Client_Type( Client ) != CLIENT_SERVER ) && ( strchr( Client_Modes( cl ), 'a' )))
126                 {
127                         /* Ziel-User ist AWAY: Meldung verschicken */
128                         if( ! IRC_WriteStrClient( from, RPL_AWAY_MSG, Client_ID( from ), Client_ID( cl ), Client_Away( cl ))) return DISCONNECTED;
129                 }
130
131                 /* Text senden */
132                 if( Client_Conn( from ) > NONE ) Conn_UpdateIdle( Client_Conn( from ));
133                 return IRC_WriteStrClientPrefix( cl, from, "PRIVMSG %s :%s", Client_ID( cl ), Req->argv[1] );
134         }
135
136         chan = Channel_Search( Req->argv[0] );
137         if( chan )
138         {
139                 /* Okay, Ziel ist ein Channel */
140                 is_member = has_voice = is_op = FALSE;
141                 if( Channel_IsMemberOf( chan, from ))
142                 {
143                         is_member = TRUE;
144                         if( strchr( Channel_UserModes( chan, from ), 'v' )) has_voice = TRUE;
145                         if( strchr( Channel_UserModes( chan, from ), 'o' )) is_op = TRUE;
146                 }
147                 
148                 /* pruefen, ob Client in Channel schreiben darf */
149                 ok = TRUE;
150                 if( strchr( Channel_Modes( chan ), 'n' ) && ( ! is_member )) ok = FALSE;
151                 if( strchr( Channel_Modes( chan ), 'm' ) && ( ! is_op ) && ( ! has_voice )) ok = FALSE;
152
153                 if( ! ok ) return IRC_WriteStrClient( from, ERR_CANNOTSENDTOCHAN_MSG, Client_ID( from ), Req->argv[0] );
154
155                 /* Text senden */
156                 if( Client_Conn( from ) > NONE ) Conn_UpdateIdle( Client_Conn( from ));
157                 return IRC_WriteStrChannelPrefix( Client, chan, from, TRUE, "PRIVMSG %s :%s", Req->argv[0], Req->argv[1] );
158         }
159
160         return IRC_WriteStrClient( from, ERR_NOSUCHNICK_MSG, Client_ID( from ), Req->argv[0] );
161 } /* IRC_PRIVMSG */
162
163
164 GLOBAL BOOLEAN IRC_NOTICE( CLIENT *Client, REQUEST *Req )
165 {
166         CLIENT *to, *from;
167
168         assert( Client != NULL );
169         assert( Req != NULL );
170
171         if(( Client_Type( Client ) != CLIENT_USER ) && ( Client_Type( Client ) != CLIENT_SERVER )) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
172
173         /* Falsche Anzahl Parameter? */
174         if( Req->argc != 2 ) return CONNECTED;
175
176         if( Client_Type( Client ) == CLIENT_SERVER ) from = Client_GetFromID( Req->prefix );
177         else from = Client;
178         if( ! from ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
179
180         to = Client_Search( Req->argv[0] );
181         if( to )
182         {
183                 /* Okay, Ziel ist ein User */
184                 return IRC_WriteStrClientPrefix( to, from, "NOTICE %s :%s", Client_ID( to ), Req->argv[1] );
185         }
186         else return CONNECTED;
187 } /* IRC_NOTICE */
188
189
190 GLOBAL BOOLEAN IRC_OPER( CLIENT *Client, REQUEST *Req )
191 {
192         INT i;
193         
194         assert( Client != NULL );
195         assert( Req != NULL );
196
197         if( Client_Type( Client ) != CLIENT_USER ) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
198         
199         /* Falsche Anzahl Parameter? */
200         if( Req->argc != 2 ) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
201
202         /* Operator suchen */
203         for( i = 0; i < Conf_Oper_Count; i++)
204         {
205                 if( Conf_Oper[i].name[0] && Conf_Oper[i].pwd[0] && ( strcmp( Conf_Oper[i].name, Req->argv[0] ) == 0 )) break;
206         }
207         if( i >= Conf_Oper_Count )
208         {
209                 Log( LOG_WARNING, "Got invalid OPER from \"%s\": Name \"%s\" not configured!", Client_Mask( Client ), Req->argv[0] );
210                 return IRC_WriteStrClient( Client, ERR_PASSWDMISMATCH_MSG, Client_ID( Client ));
211         }
212
213         /* Stimmt das Passwort? */
214         if( strcmp( Conf_Oper[i].pwd, Req->argv[1] ) != 0 )
215         {
216                 Log( LOG_WARNING, "Got invalid OPER from \"%s\": Bad password for \"%s\"!", Client_Mask( Client ), Conf_Oper[i].name );
217                 return IRC_WriteStrClient( Client, ERR_PASSWDMISMATCH_MSG, Client_ID( Client ));
218         }
219         
220         if( ! Client_HasMode( Client, 'o' ))
221         {
222                 /* noch kein o-Mode gesetzt */
223                 Client_ModeAdd( Client, 'o' );
224                 if( ! IRC_WriteStrClient( Client, "MODE %s :+o", Client_ID( Client ))) return DISCONNECTED;
225                 IRC_WriteStrServersPrefix( NULL, Client, "MODE %s :+o", Client_ID( Client ));
226         }
227
228         if( ! Client_OperByMe( Client )) Log( LOG_NOTICE, "Got valid OPER from \"%s\", user is an IRC operator now.", Client_Mask( Client ));
229
230         Client_SetOperByMe( Client, TRUE );
231         return IRC_WriteStrClient( Client, RPL_YOUREOPER_MSG, Client_ID( Client ));
232 } /* IRC_OPER */
233
234
235 GLOBAL BOOLEAN IRC_DIE( CLIENT *Client, REQUEST *Req )
236 {
237         assert( Client != NULL );
238         assert( Req != NULL );
239
240         if( Client_Type( Client ) != CLIENT_USER ) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
241
242         /* Falsche Anzahl Parameter? */
243         if( Req->argc != 0 ) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
244
245         if(( ! Client_HasMode( Client, 'o' )) || ( ! Client_OperByMe( Client ))) return IRC_WriteStrClient( Client, ERR_NOPRIVILEGES_MSG, Client_ID( Client ));
246
247         Log( LOG_NOTICE, "Got DIE command from \"%s\", going down!", Client_Mask( Client ));
248         NGIRCd_Quit = TRUE;
249         return CONNECTED;
250 } /* IRC_DIE */
251
252
253 GLOBAL BOOLEAN IRC_RESTART( CLIENT *Client, REQUEST *Req )
254 {
255         assert( Client != NULL );
256         assert( Req != NULL );
257
258         if( Client_Type( Client ) != CLIENT_USER ) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
259
260         /* Falsche Anzahl Parameter? */
261         if( Req->argc != 0 ) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
262
263         if(( ! Client_HasMode( Client, 'o' )) || ( ! Client_OperByMe( Client ))) return IRC_WriteStrClient( Client, ERR_NOPRIVILEGES_MSG, Client_ID( Client ));
264
265         Log( LOG_NOTICE, "Got RESTART command from \"%s\", going down!", Client_Mask( Client ));
266         NGIRCd_Restart = TRUE;
267         return CONNECTED;
268 } /* IRC_RESTART */
269
270
271 GLOBAL BOOLEAN IRC_NAMES( CLIENT *Client, REQUEST *Req )
272 {
273         CHAR rpl[COMMAND_LEN], *ptr;
274         CLIENT *target, *from, *c;
275         CHANNEL *chan;
276         
277         assert( Client != NULL );
278         assert( Req != NULL );
279
280         if(( Client_Type( Client ) != CLIENT_USER ) && ( Client_Type( Client ) != CLIENT_SERVER )) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
281
282         /* Falsche Anzahl Parameter? */
283         if( Req->argc > 2 ) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
284
285         /* From aus Prefix ermitteln */
286         if( Client_Type( Client ) == CLIENT_SERVER ) from = Client_GetFromID( Req->prefix );
287         else from = Client;
288         if( ! from ) return IRC_WriteStrClient( Client, ERR_NOSUCHSERVER_MSG, Client_ID( Client ), Req->prefix );
289         
290         if( Req->argc == 2 )
291         {
292                 /* an anderen Server forwarden */
293                 target = Client_GetFromID( Req->argv[1] );
294                 if( ! target ) return IRC_WriteStrClient( Client, ERR_NOSUCHSERVER_MSG, Client_ID( Client ), Req->argv[1] );
295
296                 if( target != Client_ThisServer( ))
297                 {
298                         /* Ok, anderer Server ist das Ziel: forwarden */
299                         return IRC_WriteStrClientPrefix( target, from, "NAMES %s :%s", Req->argv[0], Req->argv[1] );
300                 }
301         }
302
303         if( Req->argc > 0 )
304         {
305                 /* bestimmte Channels durchgehen */
306                 ptr = strtok( Req->argv[0], "," );
307                 while( ptr )
308                 {
309                         chan = Channel_Search( ptr );
310                         if( chan )
311                         {
312                                 /* Namen ausgeben */
313                                 if( ! IRC_Send_NAMES( from, chan )) return DISCONNECTED;
314                         }
315                         if( ! IRC_WriteStrClient( from, RPL_ENDOFNAMES_MSG, Client_ID( from ), ptr )) return DISCONNECTED;
316                         
317                         /* naechsten Namen ermitteln */
318                         ptr = strtok( NULL, "," );
319                 }
320                 return CONNECTED;
321         }
322         
323         /* alle Channels durchgehen */
324         chan = Channel_First( );
325         while( chan )
326         {
327                 /* Namen ausgeben */
328                 if( ! IRC_Send_NAMES( from, chan )) return DISCONNECTED;
329
330                 /* naechster Channel */
331                 chan = Channel_Next( chan );
332         }
333
334         /* Nun noch alle Clients ausgeben, die in keinem Channel sind */
335         c = Client_First( );
336         sprintf( rpl, RPL_NAMREPLY_MSG, Client_ID( from ), "*", "*" );
337         while( c )
338         {
339                 if(( Client_Type( c ) == CLIENT_USER ) && ( Channel_FirstChannelOf( c ) == NULL ) && ( ! strchr( Client_Modes( c ), 'i' )))
340                 {
341                         /* Okay, das ist ein User: anhaengen */
342                         if( rpl[strlen( rpl ) - 1] != ':' ) strcat( rpl, " " );
343                         strcat( rpl, Client_ID( c ));
344
345                         if( strlen( rpl ) > ( LINE_LEN - CLIENT_NICK_LEN - 4 ))
346                         {
347                                 /* Zeile wird zu lang: senden! */
348                                 if( ! IRC_WriteStrClient( from, rpl )) return DISCONNECTED;
349                                 sprintf( rpl, RPL_NAMREPLY_MSG, Client_ID( from ), "*", "*" );
350                         }
351                 }
352
353                 /* naechster Client */
354                 c = Client_Next( c );
355         }
356         if( rpl[strlen( rpl ) - 1] != ':')
357         {
358                 /* es wurden User gefunden */
359                 if( ! IRC_WriteStrClient( from, rpl )) return DISCONNECTED;
360         }
361         
362         return IRC_WriteStrClient( from, RPL_ENDOFNAMES_MSG, Client_ID( from ), "*" );
363 } /* IRC_NAMES */
364
365
366 GLOBAL BOOLEAN IRC_ISON( CLIENT *Client, REQUEST *Req )
367 {
368         CHAR rpl[COMMAND_LEN];
369         CLIENT *c;
370         CHAR *ptr;
371         INT i;
372         
373         assert( Client != NULL );
374         assert( Req != NULL );
375
376         if( Client_Type( Client ) != CLIENT_USER ) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
377
378         /* Falsche Anzahl Parameter? */
379         if(( Req->argc < 1 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
380
381         strcpy( rpl, RPL_ISON_MSG );
382         for( i = 0; i < Req->argc; i++ )
383         {
384                 ptr = strtok( Req->argv[i], " " );
385                 while( ptr )
386                 {
387                         ngt_TrimStr( ptr );
388                         c = Client_GetFromID( ptr );
389                         if( c && ( Client_Type( c ) == CLIENT_USER ))
390                         {
391                                 /* Dieser Nick ist "online" */
392                                 strcat( rpl, ptr );
393                                 strcat( rpl, " " );
394                         }
395                         ptr = strtok( NULL, " " );
396                 }
397         }
398         if( rpl[strlen( rpl ) - 1] == ' ' ) rpl[strlen( rpl ) - 1] = '\0';
399
400         return IRC_WriteStrClient( Client, rpl, Client_ID( Client ) );
401 } /* IRC_ISON */
402
403
404 GLOBAL BOOLEAN IRC_WHOIS( CLIENT *Client, REQUEST *Req )
405 {
406         CLIENT *from, *target, *c;
407         CHAR str[LINE_LEN + 1], *ptr = NULL;
408         CL2CHAN *cl2chan;
409         CHANNEL *chan;
410         
411         assert( Client != NULL );
412         assert( Req != NULL );
413
414         if(( Client_Type( Client ) != CLIENT_USER ) && ( Client_Type( Client ) != CLIENT_SERVER )) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
415
416         /* Falsche Anzahl Parameter? */
417         if(( Req->argc < 1 ) || ( Req->argc > 2 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
418
419         /* Client suchen */
420         c = Client_GetFromID( Req->argv[Req->argc - 1] );
421         if(( ! c ) || ( Client_Type( c ) != CLIENT_USER )) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->argv[Req->argc - 1] );
422
423         /* Empfaenger des WHOIS suchen */
424         if( Client_Type( Client ) == CLIENT_SERVER ) from = Client_GetFromID( Req->prefix );
425         else from = Client;
426         if( ! from ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
427         
428         /* Forwarden an anderen Server? */
429         if( Req->argc > 1 )
430         {
431                 /* angegebenen Ziel-Server suchen */
432                 target = Client_GetFromID( Req->argv[1] );
433                 if( ! target ) return IRC_WriteStrClient( from, ERR_NOSUCHSERVER_MSG, Client_ID( from ), Req->argv[1] );
434                 ptr = Req->argv[1];
435         }
436         else target = Client_ThisServer( );
437
438         assert( target != NULL );
439         
440         if(( Client_NextHop( target ) != Client_ThisServer( )) && ( Client_Type( Client_NextHop( target )) == CLIENT_SERVER )) return IRC_WriteStrClientPrefix( target, from, "WHOIS %s :%s", Req->argv[0], ptr );
441         
442         /* Nick, User und Name */
443         if( ! IRC_WriteStrClient( from, RPL_WHOISUSER_MSG, Client_ID( from ), Client_ID( c ), Client_User( c ), Client_Hostname( c ), Client_Info( c ))) return DISCONNECTED;
444
445         /* Server */
446         if( ! IRC_WriteStrClient( from, RPL_WHOISSERVER_MSG, Client_ID( from ), Client_ID( c ), Client_ID( Client_Introducer( c )), Client_Info( Client_Introducer( c )))) return DISCONNECTED;
447
448         /* Channels */
449         sprintf( str, RPL_WHOISCHANNELS_MSG, Client_ID( from ), Client_ID( c ));
450         cl2chan = Channel_FirstChannelOf( c );
451         while( cl2chan )
452         {
453                 chan = Channel_GetChannel( cl2chan );
454                 assert( chan != NULL );
455                 
456                 /* Channel-Name anhaengen */
457                 if( str[strlen( str ) - 1] != ':' ) strcat( str, " " );
458                 if( strchr( Channel_UserModes( chan, c ), 'v' )) strcat( str, "+" );
459                 if( strchr( Channel_UserModes( chan, c ), 'o' )) strcat( str, "@" );
460                 strcat( str, Channel_Name( chan ));
461
462                 if( strlen( str ) > ( LINE_LEN - CHANNEL_NAME_LEN - 4 ))
463                 {
464                         /* Zeile wird zu lang: senden! */
465                         if( ! IRC_WriteStrClient( Client, str )) return DISCONNECTED;
466                         sprintf( str, RPL_WHOISCHANNELS_MSG, Client_ID( from ), Client_ID( c ));
467                 }
468
469                 /* naechstes Mitglied suchen */
470                 cl2chan = Channel_NextChannelOf( c, cl2chan );
471         }
472         if( str[strlen( str ) - 1] != ':')
473         {
474                 /* Es sind noch Daten da, die gesendet werden muessen */
475                 if( ! IRC_WriteStrClient( Client, str )) return DISCONNECTED;
476         }
477         
478         /* IRC-Operator? */
479         if( Client_HasMode( c, 'o' ))
480         {
481                 if( ! IRC_WriteStrClient( from, RPL_WHOISOPERATOR_MSG, Client_ID( from ), Client_ID( c ))) return DISCONNECTED;
482         }
483
484         /* Idle (nur lokale Clients) */
485         if( Client_Conn( c ) > NONE )
486         {
487                 if( ! IRC_WriteStrClient( from, RPL_WHOISIDLE_MSG, Client_ID( from ), Client_ID( c ), Conn_GetIdle( Client_Conn ( c )))) return DISCONNECTED;
488         }
489
490         /* Away? */
491         if( Client_HasMode( c, 'a' ))
492         {
493                 if( ! IRC_WriteStrClient( from, RPL_AWAY_MSG, Client_ID( from ), Client_ID( c ), Client_Away( c ))) return DISCONNECTED;
494         }
495
496         /* End of Whois */
497         return IRC_WriteStrClient( from, RPL_ENDOFWHOIS_MSG, Client_ID( from ), Client_ID( c ));
498 } /* IRC_WHOIS */
499
500
501 GLOBAL BOOLEAN IRC_USERHOST( CLIENT *Client, REQUEST *Req )
502 {
503         CHAR rpl[COMMAND_LEN];
504         CLIENT *c;
505         INT max, i;
506
507         assert( Client != NULL );
508         assert( Req != NULL );
509
510         if( Client_Type( Client ) != CLIENT_USER ) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
511
512         /* Falsche Anzahl Parameter? */
513         if(( Req->argc < 1 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
514
515         if( Req->argc > 5 ) max = 5;
516         else max = Req->argc;
517         
518         strcpy( rpl, RPL_USERHOST_MSG );
519         for( i = 0; i < max; i++ )
520         {
521                 c = Client_GetFromID( Req->argv[i] );
522                 if( c && ( Client_Type( c ) == CLIENT_USER ))
523                 {
524                         /* Dieser Nick ist "online" */
525                         strcat( rpl, Client_ID( c ));
526                         if( Client_HasMode( c, 'o' )) strcat( rpl, "*" );
527                         strcat( rpl, "=" );
528                         if( Client_HasMode( c, 'a' )) strcat( rpl, "-" );
529                         else strcat( rpl, "+" );
530                         strcat( rpl, Client_User( c ));
531                         strcat( rpl, "@" );
532                         strcat( rpl, Client_Hostname( c ));
533                         strcat( rpl, " " );
534                 }
535         }
536         if( rpl[strlen( rpl ) - 1] == ' ' ) rpl[strlen( rpl ) - 1] = '\0';
537
538         return IRC_WriteStrClient( Client, rpl, Client_ID( Client ) );
539 } /* IRC_USERHOST */
540
541
542 GLOBAL BOOLEAN IRC_ERROR( CLIENT *Client, REQUEST *Req )
543 {
544         assert( Client != NULL );
545         assert( Req != NULL );
546
547         if( Req->argc < 1 ) Log( LOG_NOTICE, "Got ERROR from \"%s\"!", Client_Mask( Client ));
548         else Log( LOG_NOTICE, "Got ERROR from \"%s\": %s!", Client_Mask( Client ), Req->argv[0] );
549
550         return CONNECTED;
551 } /* IRC_ERROR */
552
553
554 GLOBAL BOOLEAN IRC_LUSERS( CLIENT *Client, REQUEST *Req )
555 {
556         CLIENT *target, *from;
557         
558         assert( Client != NULL );
559         assert( Req != NULL );
560
561         if(( Client_Type( Client ) != CLIENT_USER ) && ( Client_Type( Client ) != CLIENT_SERVER )) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
562
563         /* Falsche Anzahl Parameter? */
564         if(( Req->argc > 2 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
565
566         /* Absender ermitteln */
567         if( Client_Type( Client ) == CLIENT_SERVER ) from = Client_GetFromID( Req->prefix );
568         else from = Client;
569         if( ! from ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
570
571         /* An anderen Server forwarden? */
572         if( Req->argc == 2 )
573         {
574                 target = Client_GetFromID( Req->argv[1] );
575                 if( ! target ) return IRC_WriteStrClient( Client, ERR_NOSUCHSERVER_MSG, Client_ID( Client ), Req->argv[1] );
576                 else if( target != Client_ThisServer( )) return IRC_WriteStrClientPrefix( target, from, "LUSERS %s %s", Req->argv[0], Req->argv[1] );
577         }
578
579         /* Wer ist der Absender? */
580         if( Client_Type( Client ) == CLIENT_SERVER ) target = Client_GetFromID( Req->prefix );
581         else target = Client;
582         if( ! target ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
583         
584         IRC_Send_LUSERS( target );
585
586         return CONNECTED;
587 } /* IRC_LUSERS */
588
589
590 GLOBAL BOOLEAN IRC_LINKS( CLIENT *Client, REQUEST *Req )
591 {
592         CLIENT *target, *from, *c;
593         CHAR *mask;
594         
595         assert( Client != NULL );
596         assert( Req != NULL );
597
598         if(( Client_Type( Client ) != CLIENT_USER ) && ( Client_Type( Client ) != CLIENT_SERVER )) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
599
600         /* Falsche Anzahl Parameter? */
601         if(( Req->argc > 2 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
602
603         /* Server-Mask ermitteln */
604         if( Req->argc > 0 ) mask = Req->argv[Req->argc - 1];
605         else mask = "*";
606
607         /* Absender ermitteln */
608         if( Client_Type( Client ) == CLIENT_SERVER ) from = Client_GetFromID( Req->prefix );
609         else from = Client;
610         if( ! from ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
611         
612         /* An anderen Server forwarden? */
613         if( Req->argc == 2 )
614         {
615                 target = Client_GetFromID( Req->argv[0] );
616                 if( ! target ) return IRC_WriteStrClient( Client, ERR_NOSUCHSERVER_MSG, Client_ID( Client ), Req->argv[0] );
617                 else if( target != Client_ThisServer( )) return IRC_WriteStrClientPrefix( target, from, "LINKS %s %s", Req->argv[0], Req->argv[1] );
618         }
619
620         /* Wer ist der Absender? */
621         if( Client_Type( Client ) == CLIENT_SERVER ) target = Client_GetFromID( Req->prefix );
622         else target = Client;
623         if( ! target ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
624         
625         c = Client_First( );
626         while( c )
627         {
628                 if( Client_Type( c ) == CLIENT_SERVER )
629                 {
630                         if( ! IRC_WriteStrClient( target, RPL_LINKS_MSG, Client_ID( target ), Client_ID( c ), Client_ID( Client_TopServer( c ) ? Client_TopServer( c ) : Client_ThisServer( )), Client_Hops( c ), Client_Info( c ))) return DISCONNECTED;
631                 }
632                 c = Client_Next( c );
633         }
634         
635         return IRC_WriteStrClient( target, RPL_ENDOFLINKS_MSG, Client_ID( target ), mask );
636 } /* IRC_LINKS */
637
638
639 GLOBAL BOOLEAN IRC_JOIN( CLIENT *Client, REQUEST *Req )
640 {
641         CHAR *channame, *flags, *topic, modes[8];
642         BOOLEAN is_new_chan;
643         CLIENT *target;
644         CHANNEL *chan;
645         
646         assert( Client != NULL );
647         assert( Req != NULL );
648
649         if(( Client_Type( Client ) != CLIENT_USER ) && ( Client_Type( Client ) != CLIENT_SERVER )) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
650
651         /* Falsche Anzahl Parameter? */
652         if(( Req->argc > 1 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
653
654         /* Wer ist der Absender? */
655         if( Client_Type( Client ) == CLIENT_SERVER ) target = Client_GetFromID( Req->prefix );
656         else target = Client;
657         if( ! target ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
658         
659         /* Channel-Namen durchgehen */
660         channame = strtok( Req->argv[0], "," );
661         while( channame )
662         {
663                 /* wird der Channel neu angelegt? */
664                 flags = NULL;
665
666                 if( Channel_Search( channame )) is_new_chan = FALSE;
667                 else is_new_chan = TRUE;
668
669                 /* Hat ein Server Channel-User-Modes uebergeben? */
670                 if( Client_Type( Client ) == CLIENT_SERVER )
671                 {
672                         /* Channel-Flags extrahieren */
673                         flags = strchr( channame, 0x7 );
674                         if( flags ) *flags++ = '\0';
675                 }
676
677                 /* neuer Channel udn lokaler Client? */
678                 if( is_new_chan && ( Client_Type( Client ) == CLIENT_USER ))
679                 {
680                         /* Dann soll der Client Channel-Operator werden! */
681                         flags = "o";
682                 }
683
684                 /* Channel joinen (und ggf. anlegen) */
685                 if( ! Channel_Join( target, channame ))
686                 {
687                         /* naechsten Namen ermitteln */
688                         channame = strtok( NULL, "," );
689                         continue;
690                 }
691                 chan = Channel_Search( channame );
692                 assert( chan != NULL );
693
694                 /* Modes setzen (wenn vorhanden) */
695                 while( flags && *flags )
696                 {
697                         Channel_UserModeAdd( chan, target, *flags );
698                         flags++;
699                 }
700
701                 /* Muessen Modes an andere Server gemeldet werden? */
702                 strcpy( &modes[1], Channel_UserModes( chan, target ));
703                 if( modes[1] ) modes[0] = 0x7;
704                 else modes[0] = '\0';
705
706                 /* An andere Server weiterleiten */
707                 IRC_WriteStrServersPrefix( Client, target, "JOIN :%s%s", channame, modes );
708
709                 /* im Channel bekannt machen */
710                 IRC_WriteStrChannelPrefix( Client, chan, target, FALSE, "JOIN :%s", channame );
711                 if( modes[1] )
712                 {
713                         /* Modes im Channel bekannt machen */
714                         IRC_WriteStrChannelPrefix( Client, chan, target, FALSE, "MODE %s %s :%s", channame, modes, Client_ID( target ));
715                 }
716
717                 if( Client_Type( Client ) == CLIENT_USER )
718                 {
719                         /* an Client bestaetigen */
720                         IRC_WriteStrClientPrefix( Client, target, "JOIN :%s", channame );
721
722                         /* Topic an Client schicken */
723                         topic = Channel_Topic( chan );
724                         if( *topic ) IRC_WriteStrClient( Client, RPL_TOPIC_MSG, Client_ID( Client ), channame, topic );
725
726                         /* Mitglieder an Client Melden */
727                         IRC_Send_NAMES( Client, chan );
728                         IRC_WriteStrClient( Client, RPL_ENDOFNAMES_MSG, Client_ID( Client ), Channel_Name( chan ));
729                 }
730                 
731                 /* naechsten Namen ermitteln */
732                 channame = strtok( NULL, "," );
733         }
734         return CONNECTED;
735 } /* IRC_JOIN */
736
737
738 GLOBAL BOOLEAN IRC_PART( CLIENT *Client, REQUEST *Req )
739 {
740         CLIENT *target;
741         CHAR *chan;
742
743         assert( Client != NULL );
744         assert( Req != NULL );
745
746         if(( Client_Type( Client ) != CLIENT_USER ) && ( Client_Type( Client ) != CLIENT_SERVER )) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
747
748         /* Falsche Anzahl Parameter? */
749         if(( Req->argc > 2 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
750
751         /* Wer ist der Absender? */
752         if( Client_Type( Client ) == CLIENT_SERVER ) target = Client_GetFromID( Req->prefix );
753         else target = Client;
754         if( ! target ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
755
756         /* Channel-Namen durchgehen */
757         chan = strtok( Req->argv[0], "," );
758         while( chan )
759         {
760                 if( ! Channel_Part( target, Client, chan, Req->argc > 1 ? Req->argv[1] : Client_ID( target )))
761                 {
762                         /* naechsten Namen ermitteln */
763                         chan = strtok( NULL, "," );
764                         continue;
765                 }
766
767                 /* naechsten Namen ermitteln */
768                 chan = strtok( NULL, "," );
769         }
770         return CONNECTED;
771 } /* IRC_PART */
772
773
774 GLOBAL BOOLEAN IRC_TOPIC( CLIENT *Client, REQUEST *Req )
775 {
776         CHANNEL *chan;
777         CLIENT *from;
778         CHAR *topic;
779         
780         assert( Client != NULL );
781         assert( Req != NULL );
782
783         if(( Client_Type( Client ) != CLIENT_USER ) && ( Client_Type( Client ) != CLIENT_SERVER )) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
784
785         /* Falsche Anzahl Parameter? */
786         if(( Req->argc < 1 ) || ( Req->argc > 2 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
787
788         if( Client_Type( Client ) == CLIENT_SERVER ) from = Client_GetFromID( Req->prefix );
789         else from = Client;
790         if( ! from ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix );
791
792         /* Welcher Channel? */
793         chan = Channel_Search( Req->argv[0] );
794         if( ! chan ) return IRC_WriteStrClient( from, ERR_NOTONCHANNEL_MSG, Client_ID( from ), Req->argv[0] );
795
796         /* Ist der User Mitglied in dem Channel? */
797         if( ! Channel_IsMemberOf( chan, from )) return IRC_WriteStrClient( from, ERR_NOTONCHANNEL_MSG, Client_ID( from ), Req->argv[0] );
798
799         if( Req->argc == 1 )
800         {
801                 /* Topic erfragen */
802                 topic = Channel_Topic( chan );
803                 if( *topic ) return IRC_WriteStrClient( from, RPL_TOPIC_MSG, Client_ID( from ), Channel_Name( chan ), topic );
804                 else return IRC_WriteStrClient( from, RPL_NOTOPIC_MSG, Client_ID( from ), Channel_Name( chan ));
805         }
806
807         if( strchr( Channel_Modes( chan ), 't' ))
808         {
809                 /* Topic Lock. Ist der User ein Channel Operator? */
810                 if( ! strchr( Channel_UserModes( chan, from ), 'o' )) return IRC_WriteStrClient( from, ERR_CHANOPRIVSNEEDED_MSG, Client_ID( from ), Channel_Name( chan ));
811         }
812
813         /* Topic setzen */
814         Channel_SetTopic( chan, Req->argv[1] );
815         Log( LOG_DEBUG, "User \"%s\" set topic on \"%s\": %s", Client_Mask( from ), Channel_Name( chan ), Req->argv[1][0] ? Req->argv[1] : "<none>" );
816
817         /* im Channel bekannt machen und an Server weiterleiten */
818         IRC_WriteStrServersPrefix( Client, from, "TOPIC %s :%s", Req->argv[0], Req->argv[1] );
819         IRC_WriteStrChannelPrefix( Client, chan, from, FALSE, "TOPIC %s :%s", Req->argv[0], Req->argv[1] );
820
821         if( Client_Type( Client ) == CLIENT_USER ) return IRC_WriteStrClientPrefix( Client, Client, "TOPIC %s :%s", Req->argv[0], Req->argv[1] );
822         else return CONNECTED;
823 } /* IRC_TOPIC */
824
825
826 GLOBAL BOOLEAN IRC_VERSION( CLIENT *Client, REQUEST *Req )
827 {
828         CLIENT *target, *prefix;
829         
830         assert( Client != NULL );
831         assert( Req != NULL );
832
833         /* Falsche Anzahl Parameter? */
834         if(( Req->argc > 1 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
835
836         /* Ziel suchen */
837         if( Req->argc == 1 ) target = Client_GetFromID( Req->argv[0] );
838         else target = Client_ThisServer( );
839
840         /* Prefix ermitteln */
841         if( Client_Type( Client ) == CLIENT_SERVER ) prefix = Client_GetFromID( Req->prefix );
842         else prefix = Client;
843         if( ! prefix ) return IRC_WriteStrClient( Client, ERR_NOSUCHSERVER_MSG, Client_ID( Client ), Req->prefix );
844         
845         /* An anderen Server weiterleiten? */
846         if( target != Client_ThisServer( ))
847         {
848                 if( ! target ) return IRC_WriteStrClient( Client, ERR_NOSUCHSERVER_MSG, Client_ID( Client ), Req->argv[0] );
849
850                 /* forwarden */
851                 IRC_WriteStrClientPrefix( target, prefix, "VERSION %s", Req->argv[0] );
852                 return CONNECTED;
853         }
854
855         /* mit Versionsinfo antworten */
856         return IRC_WriteStrClient( Client, RPL_VERSION_MSG, Client_ID( prefix ), NGIRCd_DebugLevel, Conf_ServerName, NGIRCd_VersionAddition( ));
857 } /* IRC_VERSION */
858
859
860 GLOBAL BOOLEAN IRC_KILL( CLIENT *Client, REQUEST *Req )
861 {
862         CLIENT *prefix, *c;
863         
864         assert( Client != NULL );
865         assert( Req != NULL );
866
867         if( Client_Type( Client ) != CLIENT_SERVER ) return IRC_WriteStrClient( Client, ERR_NOTREGISTERED_MSG, Client_ID( Client ));
868
869         /* Falsche Anzahl Parameter? */
870         if(( Req->argc != 2 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command );
871
872         prefix = Client_GetFromID( Req->prefix );
873         if( ! prefix )
874         {
875                 Log( LOG_WARNING, "Got KILL with invalid prefix: \"%s\"!", Req->prefix );
876                 prefix = Client_ThisServer( );
877         }
878         
879         Log( LOG_NOTICE, "Got KILL command from \"%s\" for \"%s\": %s", Client_Mask( prefix ), Req->argv[0], Req->argv[1] );
880         
881         /* andere Server benachrichtigen */
882         IRC_WriteStrServersPrefix( Client, prefix, "KILL %s :%s", Req->argv[0], Req->argv[1] );
883
884         /* haben wir selber einen solchen Client? */
885         c = Client_GetFromID( Req->argv[0] );
886         if( c && ( Client_Conn( c ) != NONE )) Conn_Close( Client_Conn( c ), NULL, Req->argv[1], TRUE );
887         
888         return CONNECTED;
889 } /* IRC_KILL */
890
891
892 GLOBAL BOOLEAN IRC_Show_MOTD( CLIENT *Client )
893 {
894         BOOLEAN ok;
895         CHAR line[127];
896         FILE *fd;
897         
898         assert( Client != NULL );
899
900         fd = fopen( Conf_MotdFile, "r" );
901         if( ! fd )
902         {
903                 Log( LOG_WARNING, "Can't read MOTD file \"%s\": %s", Conf_MotdFile, strerror( errno ));
904                 return IRC_WriteStrClient( Client, ERR_NOMOTD_MSG, Client_ID( Client ) );
905         }
906         
907         IRC_WriteStrClient( Client, RPL_MOTDSTART_MSG, Client_ID( Client ), Client_ID( Client_ThisServer( )));
908         while( TRUE )
909         {
910                 if( ! fgets( line, 126, fd )) break;
911                 if( line[strlen( line ) - 1] == '\n' ) line[strlen( line ) - 1] = '\0';
912                 if( ! IRC_WriteStrClient( Client, RPL_MOTD_MSG, Client_ID( Client ), line ))
913                 {
914                         fclose( fd );
915                         return FALSE;
916                 }
917         }
918         ok = IRC_WriteStrClient( Client, RPL_ENDOFMOTD_MSG, Client_ID( Client ) );
919
920         fclose( fd );
921         
922         return ok;
923 } /* IRC_Show_MOTD */
924
925
926 GLOBAL BOOLEAN IRC_Send_NAMES( CLIENT *Client, CHANNEL *Chan )
927 {
928         BOOLEAN is_visible, is_member;
929         CHAR str[LINE_LEN + 1];
930         CL2CHAN *cl2chan;
931         CLIENT *cl;
932         
933         assert( Client != NULL );
934         assert( Chan != NULL );
935
936         if( Channel_IsMemberOf( Chan, Client )) is_member = TRUE;
937         else is_member = FALSE;
938                          
939         /* Alle Mitglieder suchen */
940         sprintf( str, RPL_NAMREPLY_MSG, Client_ID( Client ), "=", Channel_Name( Chan ));
941         cl2chan = Channel_FirstMember( Chan );
942         while( cl2chan )
943         {
944                 cl = Channel_GetClient( cl2chan );
945
946                 if( strchr( Client_Modes( cl ), 'i' )) is_visible = FALSE;
947                 else is_visible = TRUE;
948
949                 if( is_member || is_visible )
950                 {
951                         /* Nick anhaengen */
952                         if( str[strlen( str ) - 1] != ':' ) strcat( str, " " );
953                         if( strchr( Channel_UserModes( Chan, cl ), 'v' )) strcat( str, "+" );
954                         if( strchr( Channel_UserModes( Chan, cl ), 'o' )) strcat( str, "@" );
955                         strcat( str, Client_ID( cl ));
956         
957                         if( strlen( str ) > ( LINE_LEN - CLIENT_NICK_LEN - 4 ))
958                         {
959                                 /* Zeile wird zu lang: senden! */
960                                 if( ! IRC_WriteStrClient( Client, str )) return DISCONNECTED;
961                                 sprintf( str, RPL_NAMREPLY_MSG, Client_ID( Client ), "=", Channel_Name( Chan ));
962                         }
963                 }
964
965                 /* naechstes Mitglied suchen */
966                 cl2chan = Channel_NextMember( Chan, cl2chan );
967         }
968         if( str[strlen( str ) - 1] != ':')
969         {
970                 /* Es sind noch Daten da, die gesendet werden muessen */
971                 if( ! IRC_WriteStrClient( Client, str )) return DISCONNECTED;
972         }
973
974         return CONNECTED;
975 } /* IRC_Send_NAMES */
976
977
978 GLOBAL BOOLEAN IRC_Send_LUSERS( CLIENT *Client )
979 {
980         INT cnt;
981
982         assert( Client != NULL );
983
984         /* Users, Services und Serevr im Netz */
985         if( ! IRC_WriteStrClient( Client, RPL_LUSERCLIENT_MSG, Client_ID( Client ), Client_UserCount( ), Client_ServiceCount( ), Client_ServerCount( ))) return DISCONNECTED;
986
987         /* IRC-Operatoren im Netz */
988         cnt = Client_OperCount( );
989         if( cnt > 0 )
990         {
991                 if( ! IRC_WriteStrClient( Client, RPL_LUSEROP_MSG, Client_ID( Client ), cnt )) return DISCONNECTED;
992         }
993
994         /* Unbekannt Verbindungen */
995         cnt = Client_UnknownCount( );
996         if( cnt > 0 )
997         {
998                 if( ! IRC_WriteStrClient( Client, RPL_LUSERUNKNOWN_MSG, Client_ID( Client ), cnt )) return DISCONNECTED;
999         }
1000
1001         /* Channels im Netz */
1002         if( ! IRC_WriteStrClient( Client, RPL_LUSERCHANNELS_MSG, Client_ID( Client ), Channel_Count( ))) return DISCONNECTED;
1003
1004         /* Channels im Netz */
1005         if( ! IRC_WriteStrClient( Client, RPL_LUSERME_MSG, Client_ID( Client ), Client_MyUserCount( ), Client_MyServiceCount( ), Client_MyServerCount( ))) return DISCONNECTED;
1006         
1007         return CONNECTED;
1008 } /* IRC_Send_LUSERS */
1009
1010
1011 /* -eof- */