Changes in / [4c266f2:c1826c6]


Ignore:
Files:
3 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • Makefile

    r4c266f2 rc1826c6  
    1010
    1111# Program variables
    12 objects = account.o bitlbee.o commands.o conf.o crypting.o help.o ini.o ipc.o irc.o irc_commands.o log.o nick.o query.o set.o storage.o storage_text.o unix.o url.o user.o util.o
     12objects = account.o bitlbee.o commands.o conf.o crypting.o help.o ini.o irc.o log.o nick.o query.o set.o storage.o storage_text.o unix.o url.o user.o util.o
    1313subdirs = protocols
    1414
  • bitlbee.c

    r4c266f2 rc1826c6  
    2929#include "protocols/nogaim.h"
    3030#include "help.h"
    31 #include "ipc.h"
    3231#include <signal.h>
    3332#include <stdio.h>
    3433#include <errno.h>
    3534
    36 gboolean bitlbee_io_new_client( GIOChannel *source, GIOCondition condition, gpointer data );
     35gboolean bitlbee_io_new_client( GIOChannel *source, GIOCondition condition, gpointer data )
     36{
     37        size_t size = sizeof( struct sockaddr_in );
     38        struct sockaddr_in conn_info;
     39        int new_socket = accept( global.listen_socket, (struct sockaddr *) &conn_info, &size );
     40        pid_t client_pid = 0;
     41       
     42        if( global.conf->runmode == RUNMODE_FORKDAEMON )
     43                client_pid = fork();
     44       
     45        if( client_pid == 0 )
     46        {
     47                log_message( LOGLVL_INFO, "Creating new connection with fd %d.", new_socket );
     48                irc_new( new_socket );
     49               
     50                if( global.conf->runmode == RUNMODE_FORKDAEMON )
     51                {
     52                        /* Close the listening socket, we're a client. */
     53                        close( global.listen_socket );
     54                        g_source_remove( global.listen_watch_source_id );
     55                }
     56        }
     57        else
     58        {
     59                /* We don't need this one, only the client does. */
     60                close( new_socket );
     61               
     62                /* Or maybe we didn't even get a child process... */
     63                if( client_pid == -1 )
     64                        log_message( LOGLVL_ERROR, "Failed to fork() subprocess for client: %s", strerror( errno ) );
     65        }
     66       
     67        return TRUE;
     68}
     69 
     70
    3771
    3872int bitlbee_daemon_init()
     
    5589                return( -1 );
    5690        }
    57        
    58         /* TIME_WAIT (?) sucks.. */
    59         i = 1;
    60         setsockopt( global.listen_socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) );
    6191       
    6292#ifdef IPV6
     
    228258}
    229259
    230 gboolean bitlbee_io_new_client( GIOChannel *source, GIOCondition condition, gpointer data )
    231 {
    232         size_t size = sizeof( struct sockaddr_in );
    233         struct sockaddr_in conn_info;
    234         int new_socket = accept( global.listen_socket, (struct sockaddr *) &conn_info, &size );
    235         pid_t client_pid = 0;
    236        
    237         if( global.conf->runmode == RUNMODE_FORKDAEMON )
    238         {
    239                 int fds[2];
    240                
    241                 if( socketpair( AF_UNIX, SOCK_STREAM, 0, fds ) == -1 )
    242                 {
    243                         log_message( LOGLVL_WARNING, "Could not create IPC socket for client: %s", strerror( errno ) );
    244                         fds[0] = fds[1] = -1;
    245                 }
    246                
    247                 sock_make_nonblocking( fds[0] );
    248                 sock_make_nonblocking( fds[1] );
    249                
    250                 client_pid = fork();
    251                
    252                 if( client_pid > 0 && fds[0] != -1 )
    253                 {
    254                         struct bitlbee_child *child;
    255                        
    256                         child = g_new0( struct bitlbee_child, 1 );
    257                         child->pid = client_pid;
    258                         child->ipc_fd = fds[0];
    259                         child->ipc_inpa = gaim_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
    260                         child_list = g_slist_append( child_list, child );
    261                        
    262                         log_message( LOGLVL_INFO, "Creating new subprocess with pid %d.", client_pid );
    263                        
    264                         /* Close some things we don't need in the parent process. */
    265                         close( new_socket );
    266                         close( fds[1] );
    267                 }
    268                 else if( client_pid == 0 )
    269                 {
    270                         irc_t *irc;
    271                        
    272                         /* Close the listening socket, we're a client. */
    273                         close( global.listen_socket );
    274                         g_source_remove( global.listen_watch_source_id );
    275                        
    276                         /* Make the connection. */
    277                         irc = irc_new( new_socket );
    278                        
    279                         /* We can store the IPC fd there now. */
    280                         global.listen_socket = fds[1];
    281                         global.listen_watch_source_id = gaim_input_add( fds[1], GAIM_INPUT_READ, ipc_child_read, irc );
    282                        
    283                         close( fds[0] );
    284                 }
    285         }
    286         else
    287         {
    288                 log_message( LOGLVL_INFO, "Creating new connection with fd %d.", new_socket );
    289                 irc_new( new_socket );
    290         }
    291        
    292         return TRUE;
    293 }
    294 
    295260void bitlbee_shutdown( gpointer data )
    296261{
  • bitlbee.h

    r4c266f2 rc1826c6  
    112112#include "sock.h"
    113113
    114 typedef struct global {
    115         /* In forked mode, child processes store the fd of the IPC socket here. */
     114typedef struct global_t {
    116115        int listen_socket;
    117116        gint listen_watch_source_id;
  • commands.c

    r4c266f2 rc1826c6  
    3232#include <string.h>
    3333
     34const command_t commands[] = {
     35        { "help",           0, cmd_help },
     36        { "identify",       1, cmd_identify },
     37        { "register",       1, cmd_register },
     38        { "drop",           1, cmd_drop },
     39        { "account",        1, cmd_account },
     40        { "add",            2, cmd_add },
     41        { "info",           1, cmd_info },
     42        { "rename",         2, cmd_rename },
     43        { "remove",         1, cmd_remove },
     44        { "block",          1, cmd_block },
     45        { "allow",          1, cmd_allow },
     46        { "save",           0, cmd_save },
     47        { "set",            0, cmd_set },
     48        { "yes",            0, cmd_yesno },
     49        { "no",             0, cmd_yesno },
     50        { "blist",          0, cmd_blist },
     51        { "nick",           1, cmd_nick },
     52        { "import_buddies", 1, cmd_import_buddies },
     53        { "qlist",          0, cmd_qlist },
     54        { NULL }
     55};
     56
    3457int root_command_string( irc_t *irc, user_t *u, char *command, int flags )
    3558{
     
    91114}
    92115
    93 static int cmd_help( irc_t *irc, char **cmd )
     116int cmd_help( irc_t *irc, char **cmd )
    94117{
    95118        char param[80];
     
    120143}
    121144
    122 static int cmd_identify( irc_t *irc, char **cmd )
     145int cmd_identify( irc_t *irc, char **cmd )
    123146{
    124147        storage_status_t status = storage_load( irc->nick, cmd[1], irc );
     
    143166}
    144167
    145 static int cmd_register( irc_t *irc, char **cmd )
     168int cmd_register( irc_t *irc, char **cmd )
    146169{
    147170        if( global.conf->authmode == AUTHMODE_REGISTERED )
     
    170193}
    171194
    172 static int cmd_drop( irc_t *irc, char **cmd )
     195int cmd_drop( irc_t *irc, char **cmd )
    173196{
    174197        storage_status_t status;
     
    194217}
    195218
    196 static int cmd_account( irc_t *irc, char **cmd )
     219int cmd_account( irc_t *irc, char **cmd )
    197220{
    198221        account_t *a;
     
    354377}
    355378
    356 static int cmd_add( irc_t *irc, char **cmd )
     379int cmd_add( irc_t *irc, char **cmd )
    357380{
    358381        account_t *a;
     
    394417}
    395418
    396 static int cmd_info( irc_t *irc, char **cmd )
     419int cmd_info( irc_t *irc, char **cmd )
    397420{
    398421        struct gaim_connection *gc;
     
    431454}
    432455
    433 static int cmd_rename( irc_t *irc, char **cmd )
     456int cmd_rename( irc_t *irc, char **cmd )
    434457{
    435458        user_t *u;
     
    472495}
    473496
    474 static int cmd_remove( irc_t *irc, char **cmd )
     497int cmd_remove( irc_t *irc, char **cmd )
    475498{
    476499        user_t *u;
     
    494517}
    495518
    496 static int cmd_block( irc_t *irc, char **cmd )
     519int cmd_block( irc_t *irc, char **cmd )
    497520{
    498521        struct gaim_connection *gc;
     
    535558}
    536559
    537 static int cmd_allow( irc_t *irc, char **cmd )
     560int cmd_allow( irc_t *irc, char **cmd )
    538561{
    539562        struct gaim_connection *gc;
     
    577600}
    578601
    579 static int cmd_yesno( irc_t *irc, char **cmd )
     602int cmd_yesno( irc_t *irc, char **cmd )
    580603{
    581604        query_t *q = NULL;
     
    617640}
    618641
    619 static int cmd_set( irc_t *irc, char **cmd )
     642int cmd_set( irc_t *irc, char **cmd )
    620643{
    621644        if( cmd[1] && cmd[2] )
     
    643666}
    644667
    645 static int cmd_save( irc_t *irc, char **cmd )
     668int cmd_save( irc_t *irc, char **cmd )
    646669{
    647670        if( storage_save( irc, TRUE ) == STORAGE_OK )
     
    653676}
    654677
    655 static int cmd_blist( irc_t *irc, char **cmd )
     678int cmd_blist( irc_t *irc, char **cmd )
    656679{
    657680        int online = 0, away = 0, offline = 0;
     
    699722}
    700723
    701 static int cmd_nick( irc_t *irc, char **cmd )
     724int cmd_nick( irc_t *irc, char **cmd )
    702725{
    703726        account_t *a;
     
    735758}
    736759
    737 static int cmd_qlist( irc_t *irc, char **cmd )
     760int cmd_qlist( irc_t *irc, char **cmd )
    738761{
    739762        query_t *q = irc->queries;
     
    757780}
    758781
    759 static int cmd_import_buddies( irc_t *irc, char **cmd )
     782int cmd_import_buddies( irc_t *irc, char **cmd )
    760783{
    761784        struct gaim_connection *gc;
     
    809832        return( 0 );
    810833}
    811 
    812 const command_t commands[] = {
    813         { "help",           0, cmd_help,           0 },
    814         { "identify",       1, cmd_identify,       0 },
    815         { "register",       1, cmd_register,       0 },
    816         { "drop",           1, cmd_drop,           0 },
    817         { "account",        1, cmd_account,        0 },
    818         { "add",            2, cmd_add,            0 },
    819         { "info",           1, cmd_info,           0 },
    820         { "rename",         2, cmd_rename,         0 },
    821         { "remove",         1, cmd_remove,         0 },
    822         { "block",          1, cmd_block,          0 },
    823         { "allow",          1, cmd_allow,          0 },
    824         { "save",           0, cmd_save,           0 },
    825         { "set",            0, cmd_set,            0 },
    826         { "yes",            0, cmd_yesno,          0 },
    827         { "no",             0, cmd_yesno,          0 },
    828         { "blist",          0, cmd_blist,          0 },
    829         { "nick",           1, cmd_nick,           0 },
    830         { "import_buddies", 1, cmd_import_buddies, 0 },
    831         { "qlist",          0, cmd_qlist,          0 },
    832         { NULL }
    833 };
  • commands.h

    r4c266f2 rc1826c6  
    2929#include "bitlbee.h"
    3030
    31 typedef struct command
     31typedef struct command_t
    3232{
    3333        char *command;
    3434        int required_parameters;
    3535        int (*execute)(irc_t *, char **args);
    36         int flags;
    3736} command_t;
     37
     38int cmd_account( irc_t *irc, char **cmd );
     39int cmd_help( irc_t *irc, char **args);
     40int cmd_info( irc_t *irc, char **args);
     41int cmd_add( irc_t *irc, char **args) ;
     42int cmd_rename( irc_t *irc, char **args );
     43int cmd_remove( irc_t *irc, char **args );
     44int cmd_block( irc_t *irc, char **args );
     45int cmd_allow( irc_t *irc, char **args );
     46int cmd_save( irc_t *irc, char **args );
     47int cmd_set( irc_t *irc, char **args );
     48int cmd_yesno( irc_t *irc, char **args );
     49int cmd_identify( irc_t *irc, char **args );
     50int cmd_register( irc_t *irc, char **args );
     51int cmd_drop( irc_t *irc, char **args );
     52int cmd_blist( irc_t *irc, char **cmd );
     53int cmd_nick( irc_t *irc, char **cmd );
     54int cmd_qlist( irc_t *irc, char **cmd );
     55int cmd_import_buddies( irc_t *irc, char **cmd );
     56int cmd_dump( irc_t *irc, char **cmd );
    3857
    3958extern const command_t commands[];
    4059
    41 #define IRC_CMD_PRE_LOGIN       1
    42 #define IRC_CMD_LOGGED_IN       2
    43 #define IRC_CMD_OPER_ONLY       4
    44 #define IRC_CMD_TO_MASTER       8
    45 
    46 #define IPC_CMD_TO_CHILDREN     1
    47 
    4860#endif
  • conf.c

    r4c266f2 rc1826c6  
    6464        conf->ping_interval = 180;
    6565        conf->ping_timeout = 300;
    66         proxytype = 0;
    6766       
    6867        i = conf_loadini( conf, CONF_FILE );
     
    7776        }
    7877       
    79         while( argc > 0 && ( opt = getopt( argc, argv, "i:p:nvIDFc:d:h" ) ) >= 0 )
    80         /*     ^^^^ Just to make sure we skip this step from the REHASH handler. */
     78        while( ( opt = getopt( argc, argv, "i:p:nvIDFc:d:h" ) ) >= 0 )
    8179        {
    8280                if( opt == 'i' )
     
    9492                }
    9593                else if( opt == 'n' )
    96                         conf->nofork = 1;
     94                        conf->nofork=1;
    9795                else if( opt == 'v' )
    98                         conf->verbose = 1;
     96                        conf->verbose=1;
    9997                else if( opt == 'I' )
    100                         conf->runmode = RUNMODE_INETD;
     98                        conf->runmode=RUNMODE_INETD;
    10199                else if( opt == 'D' )
    102                         conf->runmode = RUNMODE_DAEMON;
     100                        conf->runmode=RUNMODE_DAEMON;
    103101                else if( opt == 'F' )
    104                         conf->runmode = RUNMODE_FORKDAEMON;
     102                        conf->runmode=RUNMODE_FORKDAEMON;
    105103                else if( opt == 'c' )
    106104                {
     
    110108                                CONF_FILE = g_strdup( optarg );
    111109                                g_free( conf );
    112                                 /* Re-evaluate arguments. Don't use this option twice,
    113                                    you'll end up in an infinite loop! Hope this trick
    114                                    works with all libcs BTW.. */
    115                                 optind = 1;
    116110                                return( conf_load( argc, argv ) );
    117111                        }
  • irc.c

    r4c266f2 rc1826c6  
    173173
    174174/* Because we have no garbage collection, this is quite annoying */
    175 void irc_free(irc_t * irc)
     175void irc_free( irc_t * irc )
    176176{
    177177        account_t *account, *accounttmp;
     
    307307int irc_process( irc_t *irc )
    308308{
    309         char **lines, *temp, **cmd;
     309        char **lines, *temp;   
    310310        int i;
    311311
    312         if( irc->readbuffer != NULL )
    313         {
    314                 lines = irc_tokenize( irc->readbuffer );
    315                
    316                 for( i = 0; *lines[i] != '\0'; i ++ )
    317                 {
    318                         if( lines[i+1] == NULL )
    319                         {
     312        if( irc->readbuffer != NULL ) {
     313                lines = irc_tokenize(irc->readbuffer );
     314                for( i = 0; *lines[i] != '\0'; i++ ) {
     315                        if( lines[i+1] == NULL ) {
    320316                                temp = g_strdup( lines[i] );
    321317                                g_free( irc->readbuffer );
    322318                                irc->readbuffer = temp;
    323                                 i ++;
     319                                i++;
    324320                                break;
    325321                        }                       
    326                        
    327                         if( ( cmd = irc_parse_line( lines[i] ) ) == NULL )
    328                                 continue;
    329                         if( !irc_exec( irc, cmd ) )
    330                         {
    331                                 g_free( cmd );
     322                        if (!irc_process_line(irc, lines[i])) {
    332323                                g_free( lines );
    333324                                return 0;
    334325                        }
    335                        
    336                         g_free( cmd );
    337                 }
    338                
    339                 if( lines[i] != NULL )
    340                 {
    341                         g_free( irc->readbuffer );
    342                         irc->readbuffer = NULL;
    343                 }
    344                
     326                }
     327                if(lines[i]!=NULL) {
     328                        g_free(irc->readbuffer);
     329                        irc->readbuffer=NULL;   
     330                }
    345331                g_free( lines );
    346332        }
    347        
    348333        return 1;       
    349334}
     
    355340
    356341        /* Count the number of elements we're gonna need. */
    357         for( i = 0, j = 1; buffer[i] != '\0'; i ++ )
    358         {
    359                 if( buffer[i] == '\n' )
    360                         if( buffer[i+1] != '\r' && buffer[i+1] != '\n' )
    361                                 j ++;
     342        for(i=0, j=1; buffer[i]!='\0'; i++ ) {
     343                if(buffer[i]=='\n' )
     344                        if(buffer[i+1]!='\r' && buffer[i+1]!='\n')
     345                                j++;
    362346        }
    363347       
    364348        /* Allocate j+1 elements. */
    365         lines = g_new( char *, j + 1 );
     349        lines=g_new (char *, j+1);
    366350       
    367351        /* NULL terminate our list. */
    368         lines[j] = NULL;
    369        
    370         lines[0] = buffer;
     352        lines[j]=NULL;
     353       
     354        lines[0]=buffer;
    371355       
    372356        /* Split the buffer in several strings, using \r\n as our seperator, where \r is optional.
    373357         * Although this is not in the RFC, some braindead ircds (newnet's) use this, so some clients might too.
    374358         */
    375         for( i = 0, j = 0; buffer[i] != '\0'; i ++)
    376         {
    377                 if( buffer[i] == '\n' )
    378                 {
    379                         buffer[i] = '\0';
    380                        
    381                         if( i > 0 && buffer[i-1] == '\r' )
    382                                 buffer[i-1] = '\0';
    383                         if( buffer[i+1] != '\r' && buffer[i+1] != '\n' )
    384                                 lines[++j] = buffer + i + 1;
    385                 }
    386         }
    387        
    388         return( lines );
    389 }
    390 
    391 char **irc_parse_line( char *line )
     359        for( i=0, j=0; buffer[i]!='\0'; i++) {
     360                if(buffer[i]=='\n') {
     361                        buffer[i]='\0';
     362
     363                        /* We dont want to read 1 byte before our buffer
     364                         * and (in rare cases) generate a SIGSEGV.
     365                         */
     366                        if(i!=0)
     367                                if(buffer[i-1]=='\r')
     368                                        buffer[i-1]='\0';
     369                        if(buffer[i+1]!='\r'&&buffer[i+1]!='\n')
     370                                lines[++j]=buffer+i+1;
     371                }
     372        }
     373
     374        return(lines);
     375}
     376
     377int irc_process_line( irc_t *irc, char *line )
    392378{
    393379        int i, j;
     
    395381       
    396382        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
    397         if( line[0] == ':' )
    398         {
    399                 for( i = 0; line[i] != ' '; i ++ );
    400                 line = line + i;
    401         }
    402         for( i = 0; line[i] == ' '; i ++ );
    403         line = line + i;
    404        
     383        if(line[0]==':') {
     384                for(i=0; line[i]!=32; i++);
     385                line=line+i;
     386        }
     387        for(i=0; line[i]==32; i++);
     388        line=line+i;
     389
    405390        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
    406         if( line[0] == '\0')
    407                 return NULL;
    408        
    409         /* Count the number of char **cmd elements we're going to need. */
    410         j = 1;
    411         for( i = 0; line[i] != '\0'; i ++ )
    412         {
    413                 if( line[i] == ' ' )
    414                 {
    415                         j ++;
     391        if(line[0]=='\0')
     392                return 1;
     393        else
     394                j=1;   
     395       
     396        /* Count the number of char **cmd elements we're going to need. */     
     397        for(i=0; line[i]!='\0'; i++) {
     398                if((line[i]==32) && (line[i+1]!=32) && (line[i+1]!='\0') && (line[i+1]!=':'))           
     399                        j++;
     400                else if((line[i]==':') && (line[i+1]!='\0') && (line[i-1]==32)) {
     401                        j++;
     402                        break;
     403                }
    416404                       
    417                         if( line[i+1] == ':' )
    418                                 break;
    419                 }
    420405        }       
    421406
    422407        /* Allocate the space we need. */
    423         cmd = g_new( char *, j + 1 );
    424         cmd[j] = NULL;
     408        cmd=g_new(char *, j+1);
     409        cmd[j]=NULL;
    425410       
    426411        /* Do the actual line splitting, format is:
     
    429414         */
    430415
    431         cmd[0] = line;
    432         for( i = 0, j = 0; line[i] != '\0'; i ++ )
    433         {
    434                 if( line[i] == ' ' )
    435                 {
    436                         line[i] = '\0';
    437                         cmd[++j] = line + i + 1;
     416        cmd[0]=line;
     417        for(i=0, j=0; line[i]!='\0'; i++) {
     418                if((line[i]==32)) {
     419                        line[i]='\0';
     420                        if((line[i+1]!=32) && (line[i+1]!='\0') && (line[i+1]!=':'))           
     421                                cmd[++j]=line+i+1;
     422                }
     423                else if((line[i]==':') && (line[i+1]!='\0') && (line[i-1]=='\0')) {
     424                        cmd[++j]=line+i+1;
     425                        break;
     426                }
     427        }
     428       
     429        i=irc_exec(irc, cmd);
     430        g_free(cmd);
     431
     432        return(i);     
     433}
     434
     435int irc_exec( irc_t *irc, char **cmd )
     436{       
     437        int i;
     438
     439        if( (global.conf)->authmode == AUTHMODE_CLOSED && irc->status < USTATUS_AUTHORIZED )
     440        {
     441                if( g_strcasecmp( cmd[0], "PASS" ) == 0 )
     442                {
     443                        if( !cmd[1] )
     444                        {
     445                                irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     446                        }
     447                        else if( strcmp( cmd[1], (global.conf)->auth_pass ) == 0 )
     448                        {
     449                                irc->status = USTATUS_AUTHORIZED;
     450                        }
     451                        else
     452                        {
     453                                irc_reply( irc, 464, ":Nope, maybe you should try it again..." );
     454                        }
     455                }
     456                else
     457                {
     458                        irc_reply( irc, 464, ":Uhh, fine, but I want the password first." );
     459                }
     460               
     461                return( 1 );
     462        }
     463       
     464        if( g_strcasecmp( cmd[0], "USER" ) == 0 )
     465        {
     466                if( !( cmd[1] && cmd[2] && cmd[3] && cmd[4] ) )
     467                {
     468                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     469                }
     470                else if( irc->user )
     471                {
     472                        irc_reply( irc, 462, ":You can't change your nick/userinfo" );
     473                }
     474                else
     475                {
     476                        irc->user = g_strdup( cmd[1] );
     477                        irc->realname = g_strdup( cmd[4] );
     478                        if( irc->nick ) irc_login( irc );
     479                }
     480                return( 1 );
     481        }
     482        else if( g_strcasecmp( cmd[0], "NICK" ) == 0 )
     483        {
     484                if( !cmd[1] )
     485                {
     486                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     487                }
     488                else if( irc->nick )
     489                {
     490                        irc_reply( irc, 438, ":The hand of the deity is upon thee, thy nick may not change" );
     491                }
     492                /* This is not clean, but for now it'll have to be like this... */
     493                else if( ( nick_cmp( cmd[1], irc->mynick ) == 0 ) || ( nick_cmp( cmd[1], NS_NICK ) == 0 ) )
     494                {
     495                        irc_reply( irc, 433, ":This nick is already in use" );
     496                }
     497                else if( !nick_ok( cmd[1] ) )
     498                {
     499                        /* [SH] Invalid characters. */
     500                        irc_reply( irc, 432, ":This nick contains invalid characters" );
     501                }
     502                else
     503                {
     504                        irc->nick = g_strdup( cmd[1] );
     505                        if( irc->user ) irc_login( irc );
     506                }
     507                return( 1 );
     508        }
     509        else if( g_strcasecmp( cmd[0], "QUIT" ) == 0 )
     510        {
     511                irc_write( irc, "ERROR :%s%s", cmd[1]?"Quit: ":"", cmd[1]?cmd[1]:"Client Quit" );
     512                /* g_io_channel_close( irc->io_channel ); */
     513                return( 0 );
     514        }
     515       
     516        if( !irc->user || !irc->nick )
     517        {
     518                irc_reply( irc, 451, ":Register first" );
     519                return( 1 );
     520        }
     521       
     522        if( g_strcasecmp( cmd[0], "PING" ) == 0 )
     523        {
     524                irc_write( irc, ":%s PONG %s :%s", irc->myhost, irc->myhost, cmd[1]?cmd[1]:irc->myhost );
     525        }
     526        else if( g_strcasecmp( cmd[0], "OPER" ) == 0 )
     527        {
     528                if( !cmd[2] )
     529                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     530                else if( strcmp( cmd[2], global.conf->oper_pass ) == 0 )
     531                        irc_umode_set( irc, "+o", 1 );
     532                // else
     533                        /* FIXME/TODO: Find out which reply to send now. */
     534        }
     535        else if( g_strcasecmp( cmd[0], "MODE" ) == 0 )
     536        {
     537                if( !cmd[1] )
     538                {
     539                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     540                }
     541                else if( *cmd[1] == '#' || *cmd[1] == '&' )
     542                {
     543                        if( cmd[2] )
     544                        {
     545                                if( *cmd[2] == '+' || *cmd[2] == '-' )
     546                                        irc_reply( irc, 477, "%s :Can't change channel modes", cmd[1] );
     547                                else if( *cmd[2] == 'b' )
     548                                        irc_reply( irc, 368, "%s :No bans possible", cmd[1] );
     549                        }
     550                        else
     551                                irc_reply( irc, 324, "%s +%s", cmd[1], CMODE );
     552                }
     553                else
     554                {
     555                        if( nick_cmp( cmd[1], irc->nick ) == 0 )
     556                        {
     557                                if( cmd[2] )
     558                                        irc_umode_set( irc, cmd[2], 0 );
     559                        }
     560                        else
     561                                irc_reply( irc, 502, ":Don't touch their modes" );
     562                }
     563        }
     564        else if( g_strcasecmp( cmd[0], "NAMES" ) == 0 )
     565        {
     566                irc_names( irc, cmd[1]?cmd[1]:irc->channel );
     567        }
     568        else if( g_strcasecmp( cmd[0], "PART" ) == 0 )
     569        {
     570                struct conversation *c;
     571               
     572                if( !cmd[1] )
     573                {
     574                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     575                }
     576                else if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
     577                {
     578                        user_t *u = user_find( irc, irc->nick );
    438579                       
    439                         if( line[i+1] == ':' )
    440                         {
    441                                 cmd[j] ++;
     580                        /* Not allowed to leave control channel */
     581                        irc_part( irc, u, irc->channel );
     582                        irc_join( irc, u, irc->channel );
     583                }
     584                else if( ( c = conv_findchannel( cmd[1] ) ) )
     585                {
     586                        user_t *u = user_find( irc, irc->nick );
     587                       
     588                        irc_part( irc, u, c->channel );
     589                       
     590                        if( c->gc && c->gc->prpl )
     591                        {
     592                                c->joined = 0;
     593                                c->gc->prpl->chat_leave( c->gc, c->id );
     594                        }
     595                }
     596                else
     597                {
     598                        irc_reply( irc, 403, "%s :No such channel", cmd[1] );
     599                }
     600        }
     601        else if( g_strcasecmp( cmd[0], "JOIN" ) == 0 )
     602        {
     603                if( !cmd[1] )
     604                {
     605                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     606                }
     607                else if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
     608                        ; /* Dude, you're already there...
     609                             RFC doesn't have any reply for that though? */
     610                else if( cmd[1] )
     611                {
     612                        if( ( cmd[1][0] == '#' || cmd[1][0] == '&' ) && cmd[1][1] )
     613                        {
     614                                user_t *u = user_find( irc, cmd[1] + 1 );
     615                               
     616                                if( u && u->gc && u->gc->prpl && u->gc->prpl->chat_open )
     617                                {
     618                                        irc_reply( irc, 403, "%s :Initializing groupchat in a different channel", cmd[1] );
     619                                       
     620                                        if( !u->gc->prpl->chat_open( u->gc, u->handle ) )
     621                                        {
     622                                                irc_usermsg( irc, "Could not open a groupchat with %s, maybe you don't have a connection to him/her yet?", u->nick );
     623                                        }
     624                                }
     625                                else
     626                                {
     627                                        irc_reply( irc, 403, "%s :Groupchats are not possible with %s", cmd[1], cmd[1]+1 );
     628                                }
     629                        }
     630                        else
     631                        {
     632                                irc_reply( irc, 403, "%s :No such channel", cmd[1] );
     633                        }
     634                }
     635        }
     636        else if( g_strcasecmp( cmd[0], "INVITE" ) == 0 )
     637        {
     638                if( cmd[1] && cmd[2] )
     639                        irc_invite( irc, cmd[1], cmd[2] );
     640                else
     641                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     642        }
     643        else if( g_strcasecmp( cmd[0], "PRIVMSG" ) == 0 || g_strcasecmp( cmd[0], "NOTICE" ) == 0 )
     644        {
     645                if( !cmd[1] )
     646                {
     647                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     648                }
     649                else if ( !cmd[2] )
     650                {
     651                        irc_reply( irc, 412, ":No text to send" );
     652                }
     653                else if ( irc->nick && g_strcasecmp( cmd[1], irc->nick ) == 0 )
     654                {
     655                        irc_write( irc, ":%s!%s@%s %s %s :%s", irc->nick, irc->user, irc->host, cmd[0], cmd[1], cmd[2] );
     656                }
     657                else
     658                {
     659                        if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
     660                        {
     661                                unsigned int i;
     662                                char *t = set_getstr( irc, "default_target" );
     663                               
     664                                if( g_strcasecmp( t, "last" ) == 0 && irc->last_target )
     665                                        cmd[1] = irc->last_target;
     666                                else if( g_strcasecmp( t, "root" ) == 0 )
     667                                        cmd[1] = irc->mynick;
     668                               
     669                                for( i = 0; i < strlen( cmd[2] ); i ++ )
     670                                {
     671                                        if( cmd[2][i] == ' ' ) break;
     672                                        if( cmd[2][i] == ':' || cmd[2][i] == ',' )
     673                                        {
     674                                                cmd[1] = cmd[2];
     675                                                cmd[2] += i;
     676                                                *cmd[2] = 0;
     677                                                while( *(++cmd[2]) == ' ' );
     678                                                break;
     679                                        }
     680                                }
     681                               
     682                                irc->is_private = 0;
     683                               
     684                                if( cmd[1] != irc->last_target )
     685                                {
     686                                        if( irc->last_target )
     687                                                g_free( irc->last_target );
     688                                        irc->last_target = g_strdup( cmd[1] );
     689                                }
     690                        }
     691                        else
     692                        {
     693                                irc->is_private = 1;
     694                        }
     695                        irc_send( irc, cmd[1], cmd[2], ( g_strcasecmp( cmd[0], "NOTICE" ) == 0 ) ? IM_FLAG_AWAY : 0 );
     696                }
     697        }
     698        else if( g_strcasecmp( cmd[0], "WHO" ) == 0 )
     699        {
     700                irc_who( irc, cmd[1] );
     701        }
     702        else if( g_strcasecmp( cmd[0], "USERHOST" ) == 0 )
     703        {
     704                user_t *u;
     705               
     706                if( !cmd[1] )
     707                {
     708                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     709                }
     710                /* [TV] Usable USERHOST-implementation according to
     711                        RFC1459. Without this, mIRC shows an error
     712                        while connecting, and the used way of rejecting
     713                        breaks standards.
     714                */
     715               
     716                for( i = 1; cmd[i]; i ++ )
     717                        if( ( u = user_find( irc, cmd[i] ) ) )
     718                        {
     719                                if( u->online && u->away )
     720                                        irc_reply( irc, 302, ":%s=-%s@%s", u->nick, u->user, u->host );
     721                                else
     722                                        irc_reply( irc, 302, ":%s=+%s@%s", u->nick, u->user, u->host );
     723                        }
     724        }
     725        else if( g_strcasecmp( cmd[0], "ISON" ) == 0 )
     726        {
     727                user_t *u;
     728                char buff[IRC_MAX_LINE];
     729                int lenleft;
     730               
     731                buff[0] = '\0';
     732               
     733                /* [SH] Leave room for : and \0 */
     734                lenleft = IRC_MAX_LINE - 2;
     735               
     736                for( i = 1; cmd[i]; i ++ )
     737                {
     738                        if( ( u = user_find( irc, cmd[i] ) ) && u->online )
     739                        {
     740                                /* [SH] Make sure we don't use too much buffer space. */
     741                                lenleft -= strlen( u->nick ) + 1;
     742                               
     743                                if( lenleft < 0 )
     744                                {
     745                                        break;
     746                                }
     747                               
     748                                /* [SH] Add the nick to the buffer. Note
     749                                 * that an extra space is always added. Even
     750                                 * if it's the last nick in the list. Who
     751                                 * cares?
     752                                 */
     753                               
     754                                strcat( buff, u->nick );
     755                                strcat( buff, " " );
     756                        }
     757                }
     758               
     759                /* [WvG] Well, maybe someone cares, so why not remove it? */
     760                if( strlen( buff ) > 0 )
     761                        buff[strlen(buff)-1] = '\0';
     762               
     763                /* [SH] By the way, that really *was* WvG talking. */
     764                /* [WvG] Really? */
     765                /* [SH] Yeah... But *this* is WvG talking too. ;-P */
     766                /* [WvG] *sigh* */
     767               
     768                irc_reply( irc, 303, ":%s", buff );
     769        }
     770        else if( g_strcasecmp( cmd[0], "WATCH" ) == 0 )
     771        {
     772                /* Obviously we could also mark a user structure as being
     773                   watched, but what if the WATCH command is sent right
     774                   after connecting? The user won't exist yet then... */
     775                for( i = 1; cmd[i]; i ++ )
     776                {
     777                        char *nick;
     778                        user_t *u;
     779                       
     780                        if( !cmd[i][0] || !cmd[i][1] )
    442781                                break;
    443                         }
    444                 }
    445         }
    446        
    447         return cmd;
    448 }
    449 
    450 char *irc_build_line( char **cmd )
    451 {
    452         int i, len;
    453         char *s;
    454        
    455         if( cmd[0] == NULL )
    456                 return NULL;
    457        
    458         len = 1;
    459         for( i = 0; cmd[i]; i ++ )
    460                 len += strlen( cmd[i] ) + 1;
    461        
    462         if( strchr( cmd[i-1], ' ' ) != NULL )
    463                 len ++;
    464        
    465         s = g_new0( char, len + 1 );
    466         for( i = 0; cmd[i]; i ++ )
    467         {
    468                 if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL )
    469                         strcat( s, ":" );
    470                
    471                 strcat( s, cmd[i] );
    472                
    473                 if( cmd[i+1] )
    474                         strcat( s, " " );
    475         }
    476         strcat( s, "\r\n" );
    477        
    478         return s;
     782                       
     783                        nick = g_strdup( cmd[i] + 1 );
     784                        nick_lc( nick );
     785                       
     786                        u = user_find( irc, nick );
     787                       
     788                        if( cmd[i][0] == '+' )
     789                        {
     790                                if( !g_hash_table_lookup( irc->watches, nick ) )
     791                                        g_hash_table_insert( irc->watches, nick, nick );
     792                               
     793                                if( u && u->online )
     794                                        irc_reply( irc, 604, "%s %s %s %d :%s", u->nick, u->user, u->host, time( NULL ), "is online" );
     795                                else
     796                                        irc_reply( irc, 605, "%s %s %s %d :%s", nick, "*", "*", time( NULL ), "is offline" );
     797                        }
     798                        else if( cmd[i][0] == '-' )
     799                        {
     800                                gpointer okey, ovalue;
     801                               
     802                                if( g_hash_table_lookup_extended( irc->watches, nick, &okey, &ovalue ) )
     803                                {
     804                                        g_free( okey );
     805                                        g_hash_table_remove( irc->watches, okey );
     806                                       
     807                                        irc_reply( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" );
     808                                }
     809                        }
     810                }
     811        }
     812        else if( g_strcasecmp( cmd[0], "TOPIC" ) == 0 )
     813        {
     814                if( cmd[1] && cmd[2] )
     815                        irc_reply( irc, 482, "%s :Cannot change topic", cmd[1] );
     816                else if( cmd[1] )
     817                        irc_topic( irc, cmd[1] );
     818                else
     819                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     820        }
     821        else if( g_strcasecmp( cmd[0], "AWAY" ) == 0 )
     822        {
     823                irc_away( irc, cmd[1] );
     824        }
     825        else if( g_strcasecmp( cmd[0], "WHOIS" ) == 0 )
     826        {
     827                if( cmd[1] )
     828                {
     829                        irc_whois( irc, cmd[1] );
     830                }
     831                else
     832                {
     833                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     834                }
     835        }
     836        else if( g_strcasecmp( cmd[0], "WHOWAS" ) == 0 )
     837        {
     838                /* For some reason irssi tries a whowas when whois fails. We can
     839                   ignore this, but then the user never gets a "user not found"
     840                   message from irssi which is a bit annoying. So just respond
     841                   with not-found and irssi users will get better error messages */
     842               
     843                if( cmd[1] )
     844                {
     845                        irc_reply( irc, 406, "%s :Nick does not exist", cmd[1] );
     846                        irc_reply( irc, 369, "%s :End of WHOWAS", cmd[1] );
     847                }
     848                else
     849                {
     850                        irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
     851                }
     852        }
     853        else if( ( g_strcasecmp( cmd[0], "NICKSERV" ) == 0 ) || ( g_strcasecmp( cmd[0], "NS" ) == 0 ) )
     854        {
     855                /* [SH] This aliases the NickServ command to PRIVMSG root */
     856                /* [TV] This aliases the NS command to PRIVMSG root as well */
     857                root_command( irc, cmd + 1 );
     858        }
     859        else if( g_strcasecmp( cmd[0], "MOTD" ) == 0 )
     860        {
     861                irc_motd( irc );
     862        }
     863        else if( g_strcasecmp( cmd[0], "PONG" ) == 0 )
     864        {
     865                /* We could check the value we get back from the user, but in
     866                   fact we don't care, we're just happy he's still alive. */
     867                irc->last_pong = gettime();
     868                irc->pinging = 0;
     869        }
     870        else if( g_strcasecmp( cmd[0], "COMPLETIONS" ) == 0 )
     871        {
     872                user_t *u = user_find( irc, irc->mynick );
     873                help_t *h;
     874                set_t *s;
     875                int i;
     876               
     877                irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "OK" );
     878               
     879                for( i = 0; commands[i].command; i ++ )
     880                        irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", commands[i].command );
     881               
     882                for( h = global.help; h; h = h->next )
     883                        irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS help ", h->string );
     884               
     885                for( s = irc->set; s; s = s->next )
     886                        irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS set ", s->key );
     887               
     888                irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "END" );
     889        }
     890        else if( set_getint( irc, "debug" ) )
     891        {
     892                irc_usermsg( irc, "\002--- Unknown command:" );
     893                for( i = 0; cmd[i]; i ++ ) irc_usermsg( irc, "%s", cmd[i] );
     894                irc_usermsg( irc, "\002--------------------" );
     895        }
     896       
     897        return( 1 );
    479898}
    480899
     
    6351054}
    6361055
    637 int irc_check_login( irc_t *irc )
    638 {
    639         if( irc->user && irc->nick )
    640         {
    641                 if( global.conf->authmode == AUTHMODE_CLOSED && irc->status < USTATUS_AUTHORIZED )
    642                 {
    643                         irc_reply( irc, 464, ":This server is password-protected." );
    644                         return 0;
    645                 }
    646                 else
    647                 {
    648                         irc_login( irc );
    649                         return 1;
    650                 }
    651         }
    652         else
    653         {
    654                 /* More information needed. */
    655                 return 0;
    656         }
     1056void irc_who( irc_t *irc, char *channel )
     1057{
     1058        user_t *u = irc->users;
     1059        struct conversation *c;
     1060        GList *l;
     1061       
     1062        if( !channel || *channel == '0' || *channel == '*' || !*channel )
     1063                while( u )
     1064                {
     1065                        irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", u->online ? irc->channel : "*", u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname );
     1066                        u = u->next;
     1067                }
     1068        else if( g_strcasecmp( channel, irc->channel ) == 0 )
     1069                while( u )
     1070                {
     1071                        if( u->online )
     1072                                irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );
     1073                        u = u->next;
     1074                }
     1075        else if( ( c = conv_findchannel( channel ) ) )
     1076                for( l = c->in_room; l; l = l->next )
     1077                {
     1078                        if( ( u = user_findhandle( c->gc, l->data ) ) )
     1079                                irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );
     1080                }
     1081        else if( ( u = user_find( irc, channel ) ) )
     1082                irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname );
     1083       
     1084        irc_reply( irc, 315, "%s :End of /WHO list.", channel?channel:"**" );
    6571085}
    6581086
     
    7661194}
    7671195
     1196void irc_whois( irc_t *irc, char *nick )
     1197{
     1198        user_t *u = user_find( irc, nick );
     1199       
     1200        if( u )
     1201        {
     1202                irc_reply( irc, 311, "%s %s %s * :%s", u->nick, u->user, u->host, u->realname );
     1203               
     1204                if( u->gc )
     1205                        irc_reply( irc, 312, "%s %s.%s :%s network", u->nick, u->gc->user->username,
     1206                                   *u->gc->user->proto_opt[0] ? u->gc->user->proto_opt[0] : "", u->gc->prpl->name );
     1207                else
     1208                        irc_reply( irc, 312, "%s %s :%s", u->nick, irc->myhost, IRCD_INFO );
     1209               
     1210                if( !u->online )
     1211                        irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
     1212                else if( u->away )
     1213                        irc_reply( irc, 301, "%s :%s", u->nick, u->away );
     1214               
     1215                irc_reply( irc, 318, "%s :End of /WHOIS list", nick );
     1216        }
     1217        else
     1218        {
     1219                irc_reply( irc, 401, "%s :Nick does not exist", nick );
     1220        }
     1221}
     1222
     1223
    7681224void irc_umode_set( irc_t *irc, char *s, int allow_priv )
    7691225{
     
    7951251}
    7961252
     1253int irc_away( irc_t *irc, char *away )
     1254{
     1255        user_t *u = user_find( irc, irc->nick );
     1256        GSList *c = get_connections();
     1257       
     1258        if( !u ) return( 0 );
     1259       
     1260        if( away && *away )
     1261        {
     1262                int i, j;
     1263               
     1264                /* Copy away string, but skip control chars. Mainly because
     1265                   Jabber really doesn't like them. */
     1266                u->away = g_malloc( strlen( away ) + 1 );
     1267                for( i = j = 0; away[i]; i ++ )
     1268                        if( ( u->away[j] = away[i] ) >= ' ' )
     1269                                j ++;
     1270                u->away[j] = 0;
     1271               
     1272                irc_reply( irc, 306, ":You're now away: %s", u->away );
     1273                /* irc_umode_set( irc, irc->myhost, "+a" ); */
     1274        }
     1275        else
     1276        {
     1277                if( u->away ) g_free( u->away );
     1278                u->away = NULL;
     1279                /* irc_umode_set( irc, irc->myhost, "-a" ); */
     1280                irc_reply( irc, 305, ":Welcome back" );
     1281        }
     1282       
     1283        while( c )
     1284        {
     1285                if( ((struct gaim_connection *)c->data)->flags & OPT_LOGGED_IN )
     1286                        proto_away( c->data, u->away );
     1287               
     1288                c = c->next;
     1289        }
     1290       
     1291        return( 1 );
     1292}
     1293
    7971294void irc_spawn( irc_t *irc, user_t *u )
    7981295{
     
    8461343        }
    8471344        g_free( nick );
     1345}
     1346
     1347void irc_invite( irc_t *irc, char *nick, char *channel )
     1348{
     1349        struct conversation *c = conv_findchannel( channel );
     1350        user_t *u = user_find( irc, nick );
     1351       
     1352        if( u && c && ( u->gc == c->gc ) )
     1353                if( c->gc && c->gc->prpl && c->gc->prpl->chat_invite )
     1354                {
     1355                        c->gc->prpl->chat_invite( c->gc, c->id, "", u->handle );
     1356                        irc_reply( irc, 341, "%s %s", nick, channel );
     1357                        return;
     1358                }
     1359       
     1360        irc_reply( irc, 482, "%s :Invite impossible; User/Channel non-existent or incompatible", channel );
    8481361}
    8491362
  • irc.h

    r4c266f2 rc1826c6  
    3333#define IRC_PING_STRING "PinglBee"
    3434
    35 #define UMODES "iasw"
     35#define UMODES "ias"
    3636#define UMODES_PRIV "Ro"
    3737#define CMODES "nt"
     
    110110int irc_exec( irc_t *irc, char **cmd );
    111111int irc_process( irc_t *irc );
    112 char **irc_parse_line( char *line );
    113 char *irc_build_line( char **cmd );
     112int irc_process_line( irc_t *irc, char *line );
    114113
    115114void irc_vawrite( irc_t *irc, char *format, va_list params );
     
    121120
    122121void irc_login( irc_t *irc );
    123 int irc_check_login( irc_t *irc );
    124122void irc_motd( irc_t *irc );
    125123void irc_names( irc_t *irc, char *channel );
  • url.c

    r4c266f2 rc1826c6  
    4040        else
    4141        {
    42                 if( g_strncasecmp( set_url, "http", i - set_url ) == 0 )
     42                if( g_strncasecmp( set_url, "https", i - set_url ) == 0 )
     43                        url->proto = PROTO_HTTPS;
     44                else if( g_strncasecmp( set_url, "http", i - set_url ) == 0 )
    4345                        url->proto = PROTO_HTTP;
    44                 else if( g_strncasecmp( set_url, "https", i - set_url ) == 0 )
    45                         url->proto = PROTO_HTTPS;
    4646                else if( g_strncasecmp( set_url, "socks4", i - set_url ) == 0 )
    4747                        url->proto = PROTO_SOCKS4;
Note: See TracChangeset for help on using the changeset viewer.