loperIRCLogBot/src/libircclient-1.9/src/libircclient.c

1288 lines
30 KiB
C

/*
* Copyright (C) 2004-2012 George Yunaev gyunaev@ulduzsoft.com
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*/
#define IS_DEBUG_ENABLED(s) ((s)->options & LIBIRC_OPTION_DEBUG)
#include "portable.c"
#include "sockets.c"
#include "libircclient.h"
#include "session.h"
#include "utils.c"
#include "errors.c"
#include "colors.c"
#include "dcc.c"
#include "ssl.c"
#ifdef _MSC_VER
/*
* The debugger of MSVC 2005 does not like strdup.
* It complains about heap corruption when free is called.
* Use _strdup instead.
*/
#undef strdup
#define strdup _strdup
#endif
#if defined (WIN32_DLL)
static int winsock_refcount = 0;
#endif
irc_session_t * irc_create_session (irc_callbacks_t * callbacks)
{
irc_session_t * session;
#if defined (WIN32_DLL)
// From MSDN: The WSAStartup function typically leads to protocol-specific helper
// DLLs being loaded. As a result, the WSAStartup function should not be called
// from the DllMain function in a application DLL. This can potentially cause deadlocks.
if ( winsock_refcount == 0 )
{
WORD wVersionRequested = MAKEWORD (1, 1);
WSADATA wsaData;
if ( WSAStartup (wVersionRequested, &wsaData) != 0 )
return 0;
winsock_refcount++;
}
#endif
session = malloc (sizeof(irc_session_t));
if ( !session )
return 0;
memset (session, 0, sizeof(irc_session_t));
session->sock = -1;
if ( libirc_mutex_init (&session->mutex_session)
|| libirc_mutex_init (&session->mutex_dcc) )
{
free (session);
return 0;
}
session->dcc_last_id = 1;
session->dcc_timeout = 60;
memcpy (&session->callbacks, callbacks, sizeof(irc_callbacks_t));
if ( !session->callbacks.event_ctcp_req )
session->callbacks.event_ctcp_req = libirc_event_ctcp_internal;
return session;
}
static void free_ircsession_strings (irc_session_t * session)
{
if ( session->realname )
free (session->realname);
if ( session->username )
free (session->username);
if ( session->nick )
free (session->nick);
if ( session->server )
free (session->server);
if ( session->server_password )
free (session->server_password);
session->realname = 0;
session->username = 0;
session->nick = 0;
session->server = 0;
session->server_password = 0;
}
void irc_destroy_session (irc_session_t * session)
{
free_ircsession_strings( session );
// The CTCP VERSION must be freed only now
if ( session->ctcp_version )
free (session->ctcp_version);
if ( session->sock >= 0 )
socket_close (&session->sock);
#if defined (ENABLE_THREADS)
libirc_mutex_destroy (&session->mutex_session);
#endif
#if defined (ENABLE_SSL)
if ( session->ssl )
SSL_free( session->ssl );
#endif
/*
* delete DCC data
* libirc_remove_dcc_session removes the DCC session from the list.
*/
while ( session->dcc_sessions )
libirc_remove_dcc_session (session, session->dcc_sessions, 0);
libirc_mutex_destroy (&session->mutex_dcc);
free (session);
#if defined (WIN32_DLL)
if ( --winsock_refcount == 0 )
WSACleanup();
#endif
}
int irc_connect (irc_session_t * session,
const char * server,
unsigned short port,
const char * server_password,
const char * nick,
const char * username,
const char * realname)
{
struct sockaddr_in saddr;
char * p;
// Check and copy all the specified fields
if ( !server || !nick )
{
session->lasterror = LIBIRC_ERR_INVAL;
return 1;
}
if ( session->state != LIBIRC_STATE_INIT )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
// Free the strings if defined; may be the case when the session is reused after the connection fails
free_ircsession_strings( session );
// Handle the server # prefix (SSL)
if ( server[0] == SSL_PREFIX )
{
#if defined (ENABLE_SSL)
server++;
session->flags |= SESSIONFL_SSL_CONNECTION;
#else
session->lasterror = LIBIRC_ERR_SSL_NOT_SUPPORTED;
return 1;
#endif
}
if ( username )
session->username = strdup (username);
if ( server_password )
session->server_password = strdup (server_password);
if ( realname )
session->realname = strdup (realname);
session->nick = strdup (nick);
session->server = strdup (server);
// If port number is zero and server contains the port, parse it
if ( port == 0 && (p = strchr( session->server, ':' )) != 0 )
{
// Terminate the string and parse the port number
*p++ = '\0';
port = atoi( p );
}
// IPv4 address resolving
memset( &saddr, 0, sizeof(saddr) );
saddr.sin_family = AF_INET;
saddr.sin_port = htons (port);
saddr.sin_addr.s_addr = inet_addr( session->server );
if ( saddr.sin_addr.s_addr == INADDR_NONE )
{
struct hostent *hp;
#if defined HAVE_GETHOSTBYNAME_R
int tmp_errno;
struct hostent tmp_hostent;
char buf[2048];
if ( gethostbyname_r (session->server, &tmp_hostent, buf, sizeof(buf), &hp, &tmp_errno) )
hp = 0;
#else
hp = gethostbyname (session->server);
#endif // HAVE_GETHOSTBYNAME_R
if ( !hp )
{
session->lasterror = LIBIRC_ERR_RESOLV;
return 1;
}
memcpy (&saddr.sin_addr, hp->h_addr, (size_t) hp->h_length);
}
// create the IRC server socket
if ( socket_create( PF_INET, SOCK_STREAM, &session->sock)
|| socket_make_nonblocking (&session->sock) )
{
session->lasterror = LIBIRC_ERR_SOCKET;
return 1;
}
#if defined (ENABLE_SSL)
// Init the SSL stuff
if ( session->flags & SESSIONFL_SSL_CONNECTION )
{
int rc = ssl_init( session );
if ( rc != 0 )
{
session->lasterror = rc;
return 1;
}
}
#endif
// and connect to the IRC server
if ( socket_connect (&session->sock, (struct sockaddr *) &saddr, sizeof(saddr)) )
{
session->lasterror = LIBIRC_ERR_CONNECT;
return 1;
}
session->state = LIBIRC_STATE_CONNECTING;
session->flags = SESSIONFL_USES_IPV6; // reset in case of reconnect
return 0;
}
int irc_connect6 (irc_session_t * session,
const char * server,
unsigned short port,
const char * server_password,
const char * nick,
const char * username,
const char * realname)
{
#if defined (ENABLE_IPV6)
struct sockaddr_in6 saddr;
struct addrinfo ainfo, *res = NULL;
char portStr[32], *p;
#if defined (_WIN32)
int addrlen = sizeof(saddr);
HMODULE hWsock;
getaddrinfo_ptr_t getaddrinfo_ptr;
freeaddrinfo_ptr_t freeaddrinfo_ptr;
int resolvesuccess = 0;
#endif
// Check and copy all the specified fields
if ( !server || !nick )
{
session->lasterror = LIBIRC_ERR_INVAL;
return 1;
}
if ( session->state != LIBIRC_STATE_INIT )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
// Free the strings if defined; may be the case when the session is reused after the connection fails
free_ircsession_strings( session );
// Handle the server # prefix (SSL)
if ( server[0] == SSL_PREFIX )
{
#if defined (ENABLE_SSL)
server++;
session->flags |= SESSIONFL_SSL_CONNECTION;
#else
session->lasterror = LIBIRC_ERR_SSL_NOT_SUPPORTED;
return 1;
#endif
}
if ( username )
session->username = strdup (username);
if ( server_password )
session->server_password = strdup (server_password);
if ( realname )
session->realname = strdup (realname);
session->nick = strdup (nick);
session->server = strdup (server);
// If port number is zero and server contains the port, parse it
if ( port == 0 && (p = strchr( session->server, ':' )) != 0 )
{
// Terminate the string and parse the port number
*p++ = '\0';
port = atoi( p );
}
memset( &saddr, 0, sizeof(saddr) );
saddr.sin6_family = AF_INET6;
saddr.sin6_port = htons (port);
sprintf( portStr, "%u", (unsigned)port );
#if defined (_WIN32)
if ( WSAStringToAddressA( (LPSTR)session->server, AF_INET6, NULL, (struct sockaddr *)&saddr, &addrlen ) == SOCKET_ERROR )
{
hWsock = LoadLibraryA("ws2_32");
if (hWsock)
{
/* Determine functions at runtime, because windows systems < XP do not
* support getaddrinfo. */
getaddrinfo_ptr = (getaddrinfo_ptr_t)GetProcAddress(hWsock, "getaddrinfo");
freeaddrinfo_ptr = (freeaddrinfo_ptr_t)GetProcAddress(hWsock, "freeaddrinfo");
if (getaddrinfo_ptr && freeaddrinfo_ptr)
{
memset(&ainfo, 0, sizeof(ainfo));
ainfo.ai_family = AF_INET6;
ainfo.ai_socktype = SOCK_STREAM;
ainfo.ai_protocol = 0;
if ( getaddrinfo_ptr(session->server, portStr, &ainfo, &res) == 0 && res )
{
resolvesuccess = 1;
memcpy( &saddr, res->ai_addr, res->ai_addrlen );
freeaddrinfo_ptr( res );
}
}
FreeLibrary(hWsock);
}
if (!resolvesuccess)
{
session->lasterror = LIBIRC_ERR_RESOLV;
return 1;
}
}
#else
if ( inet_pton( AF_INET6, session->server, (void*) &saddr.sin6_addr ) <= 0 )
{
memset( &ainfo, 0, sizeof(ainfo) );
ainfo.ai_family = AF_INET6;
ainfo.ai_socktype = SOCK_STREAM;
ainfo.ai_protocol = 0;
if ( getaddrinfo( session->server, portStr, &ainfo, &res ) || !res )
{
session->lasterror = LIBIRC_ERR_RESOLV;
return 1;
}
memcpy( &saddr, res->ai_addr, res->ai_addrlen );
freeaddrinfo( res );
}
#endif
// create the IRC server socket
if ( socket_create( PF_INET6, SOCK_STREAM, &session->sock)
|| socket_make_nonblocking (&session->sock) )
{
session->lasterror = LIBIRC_ERR_SOCKET;
return 1;
}
#if defined (ENABLE_SSL)
// Init the SSL stuff
if ( session->flags & SESSIONFL_SSL_CONNECTION )
{
int rc = ssl_init( session );
if ( rc != 0 )
return rc;
}
#endif
// and connect to the IRC server
if ( socket_connect (&session->sock, (struct sockaddr *) &saddr, sizeof(saddr)) )
{
session->lasterror = LIBIRC_ERR_CONNECT;
return 1;
}
session->state = LIBIRC_STATE_CONNECTING;
session->flags = 0; // reset in case of reconnect
return 0;
#else
session->lasterror = LIBIRC_ERR_NOIPV6;
return 1;
#endif
}
int irc_is_connected (irc_session_t * session)
{
return (session->state == LIBIRC_STATE_CONNECTED
|| session->state == LIBIRC_STATE_CONNECTING) ? 1 : 0;
}
int irc_run (irc_session_t * session)
{
if ( session->state != LIBIRC_STATE_CONNECTING )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
while ( irc_is_connected(session) )
{
struct timeval tv;
fd_set in_set, out_set;
int maxfd = 0;
tv.tv_usec = 250000;
tv.tv_sec = 0;
// Init sets
FD_ZERO (&in_set);
FD_ZERO (&out_set);
irc_add_select_descriptors (session, &in_set, &out_set, &maxfd);
if ( select (maxfd + 1, &in_set, &out_set, 0, &tv) < 0 )
{
if ( socket_error() == EINTR )
continue;
session->lasterror = LIBIRC_ERR_TERMINATED;
return 1;
}
if ( irc_process_select_descriptors (session, &in_set, &out_set) )
return 1;
}
return 0;
}
int irc_add_select_descriptors (irc_session_t * session, fd_set *in_set, fd_set *out_set, int * maxfd)
{
if ( session->sock < 0
|| session->state == LIBIRC_STATE_INIT
|| session->state == LIBIRC_STATE_DISCONNECTED )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
libirc_mutex_lock (&session->mutex_session);
switch (session->state)
{
case LIBIRC_STATE_CONNECTING:
// While connection, only out_set descriptor should be set
libirc_add_to_set (session->sock, out_set, maxfd);
break;
case LIBIRC_STATE_CONNECTED:
// Add input descriptor if there is space in input buffer
if ( session->incoming_offset < (sizeof (session->incoming_buf) - 1)
|| (session->flags & SESSIONFL_SSL_WRITE_WANTS_READ) != 0 )
libirc_add_to_set (session->sock, in_set, maxfd);
// Add output descriptor if there is something in output buffer
if ( libirc_findcrlf (session->outgoing_buf, session->outgoing_offset) > 0
|| (session->flags & SESSIONFL_SSL_READ_WANTS_WRITE) != 0 )
libirc_add_to_set (session->sock, out_set, maxfd);
break;
}
libirc_mutex_unlock (&session->mutex_session);
libirc_dcc_add_descriptors (session, in_set, out_set, maxfd);
return 0;
}
static void libirc_process_incoming_data (irc_session_t * session, size_t process_length)
{
#define MAX_PARAMS_ALLOWED 10
char buf[2*512], *p, *s;
const char * command = 0, *prefix = 0, *params[MAX_PARAMS_ALLOWED+1];
int code = 0, paramindex = 0;
char *buf_end = buf + process_length;
if ( process_length > sizeof(buf) )
abort(); // should be impossible
memcpy (buf, session->incoming_buf, process_length);
buf[process_length] = '\0';
memset ((char *)params, 0, sizeof(params));
p = buf;
/*
* From RFC 1459:
* <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
* <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
* <command> ::= <letter> { <letter> } | <number> <number> <number>
* <SPACE> ::= ' ' { ' ' }
* <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
* <middle> ::= <Any *non-empty* sequence of octets not including SPACE
* or NUL or CR or LF, the first of which may not be ':'>
* <trailing> ::= <Any, possibly *empty*, sequence of octets not including
* NUL or CR or LF>
*/
// Parse <prefix>
if ( buf[0] == ':' )
{
while ( *p && *p != ' ')
p++;
*p++ = '\0';
// we use buf+1 to skip the leading colon
prefix = buf + 1;
// If LIBIRC_OPTION_STRIPNICKS is set, we should 'clean up' nick
// right here
if ( session->options & LIBIRC_OPTION_STRIPNICKS )
{
for ( s = buf + 1; *s; s++ )
{
if ( *s == '@' || *s == '!' )
{
*s = '\0';
break;
}
}
}
}
// Parse <command>
if ( isdigit (p[0]) && isdigit (p[1]) && isdigit (p[2]) )
{
p[3] = '\0';
code = atoi (p);
p += 4;
}
else
{
s = p;
while ( *p && *p != ' ')
p++;
*p++ = '\0';
command = s;
}
// Parse middle/params
while ( *p && paramindex < MAX_PARAMS_ALLOWED )
{
// beginning from ':', this is the last param
if ( *p == ':' )
{
params[paramindex++] = p + 1; // skip :
break;
}
// Just a param
for ( s = p; *p && *p != ' '; p++ )
;
params[paramindex++] = s;
if ( !*p )
break;
*p++ = '\0';
}
// Handle PING/PONG
if ( command && !strncmp (command, "PING", buf_end - command) && params[0] )
{
irc_send_raw (session, "PONG %s", params[0]);
return;
}
// and dump
if ( code )
{
// We use SESSIONFL_MOTD_RECEIVED flag to check whether it is the first
// RPL_ENDOFMOTD or ERR_NOMOTD after the connection.
if ( (code == 1 || code == 376 || code == 422) && !(session->flags & SESSIONFL_MOTD_RECEIVED ) )
{
session->flags |= SESSIONFL_MOTD_RECEIVED;
if ( session->callbacks.event_connect )
(*session->callbacks.event_connect) (session, "CONNECT", prefix, params, paramindex);
}
if ( session->callbacks.event_numeric )
(*session->callbacks.event_numeric) (session, code, prefix, params, paramindex);
}
else
{
if ( !strncmp (command, "NICK", buf_end - command) )
{
/*
* If we're changed our nick, we should save it.
*/
char nickbuf[256];
irc_target_get_nick (prefix, nickbuf, sizeof(nickbuf));
if ( !strncmp (nickbuf, session->nick, strlen(session->nick)) && paramindex > 0 )
{
free (session->nick);
session->nick = strdup (params[0]);
}
if ( session->callbacks.event_nick )
(*session->callbacks.event_nick) (session, command, prefix, params, paramindex);
}
else if ( !strncmp (command, "QUIT", buf_end - command) )
{
if ( session->callbacks.event_quit )
(*session->callbacks.event_quit) (session, command, prefix, params, paramindex);
}
else if ( !strncmp (command, "JOIN", buf_end - command) )
{
if ( session->callbacks.event_join )
(*session->callbacks.event_join) (session, command, prefix, params, paramindex);
}
else if ( !strncmp (command, "PART", buf_end - command) )
{
if ( session->callbacks.event_part )
(*session->callbacks.event_part) (session, command, prefix, params, paramindex);
}
else if ( !strncmp (command, "MODE", buf_end - command) )
{
if ( paramindex > 0 && !strncmp (params[0], session->nick, strlen(session->nick)) )
{
params[0] = params[1];
paramindex = 1;
if ( session->callbacks.event_umode )
(*session->callbacks.event_umode) (session, command, prefix, params, paramindex);
}
else
{
if ( session->callbacks.event_mode )
(*session->callbacks.event_mode) (session, command, prefix, params, paramindex);
}
}
else if ( !strncmp (command, "TOPIC", buf_end - command) )
{
if ( session->callbacks.event_topic )
(*session->callbacks.event_topic) (session, command, prefix, params, paramindex);
}
else if ( !strncmp (command, "KICK", buf_end - command) )
{
if ( session->callbacks.event_kick )
(*session->callbacks.event_kick) (session, command, prefix, params, paramindex);
}
else if ( !strncmp (command, "PRIVMSG", buf_end - command) )
{
if ( paramindex > 1 )
{
size_t msglen = strlen (params[1]);
/*
* Check for CTCP request (a CTCP message starts from 0x01
* and ends by 0x01
*/
if ( params[1][0] == 0x01 && params[1][msglen-1] == 0x01 )
{
char ctcp_buf[128];
msglen -= 2;
if ( msglen > sizeof(ctcp_buf) - 1 )
msglen = sizeof(ctcp_buf) - 1;
memcpy (ctcp_buf, params[1] + 1, msglen);
ctcp_buf[msglen] = '\0';
if ( !strncasecmp(ctcp_buf, "DCC ", 4) )
libirc_dcc_request (session, prefix, ctcp_buf);
else if ( !strncasecmp( ctcp_buf, "ACTION ", 7)
&& session->callbacks.event_ctcp_action )
{
params[1] = ctcp_buf + 7; // the length of "ACTION "
paramindex = 2;
(*session->callbacks.event_ctcp_action) (session, "ACTION", prefix, params, paramindex);
}
else
{
params[0] = ctcp_buf;
paramindex = 1;
if ( session->callbacks.event_ctcp_req )
(*session->callbacks.event_ctcp_req) (session, "CTCP", prefix, params, paramindex);
}
}
else if ( !strncasecmp (params[0], session->nick, strlen(session->nick) ) )
{
if ( session->callbacks.event_privmsg )
(*session->callbacks.event_privmsg) (session, "PRIVMSG", prefix, params, paramindex);
}
else
{
if ( session->callbacks.event_channel )
(*session->callbacks.event_channel) (session, "CHANNEL", prefix, params, paramindex);
}
}
}
else if ( !strncmp (command, "NOTICE", buf_end - command) )
{
size_t msglen = strlen (params[1]);
/*
* Check for CTCP request (a CTCP message starts from 0x01
* and ends by 0x01
*/
if ( paramindex > 1 && params[1][0] == 0x01 && params[1][msglen-1] == 0x01 )
{
char ctcp_buf[512];
msglen -= 2;
if ( msglen > sizeof(ctcp_buf) - 1 )
msglen = sizeof(ctcp_buf) - 1;
memcpy (ctcp_buf, params[1] + 1, msglen);
ctcp_buf[msglen] = '\0';
params[0] = ctcp_buf;
paramindex = 1;
if ( session->callbacks.event_ctcp_rep )
(*session->callbacks.event_ctcp_rep) (session, "CTCP", prefix, params, paramindex);
}
else if ( !strncasecmp (params[0], session->nick, strlen(session->nick) ) )
{
if ( session->callbacks.event_notice )
(*session->callbacks.event_notice) (session, command, prefix, params, paramindex);
} else {
if ( session->callbacks.event_channel_notice )
(*session->callbacks.event_channel_notice) (session, command, prefix, params, paramindex);
}
}
else if ( !strncmp (command, "INVITE", buf_end - command) )
{
if ( session->callbacks.event_invite )
(*session->callbacks.event_invite) (session, command, prefix, params, paramindex);
}
else if ( !strncmp (command, "KILL", buf_end - command) )
{
; /* ignore this event - not all servers generate this */
}
else
{
/*
* The "unknown" event is triggered upon receipt of any number of
* unclassifiable miscellaneous messages, which aren't handled by
* the library.
*/
if ( session->callbacks.event_unknown )
(*session->callbacks.event_unknown) (session, command, prefix, params, paramindex);
}
}
}
int irc_process_select_descriptors (irc_session_t * session, fd_set *in_set, fd_set *out_set)
{
char buf[256], hname[256];
if ( session->sock < 0
|| session->state == LIBIRC_STATE_INIT
|| session->state == LIBIRC_STATE_DISCONNECTED )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
session->lasterror = 0;
libirc_dcc_process_descriptors (session, in_set, out_set);
// Handle "connection succeed" / "connection failed"
if ( session->state == LIBIRC_STATE_CONNECTING )
{
// If the socket is not connected yet, wait longer - it is not an error
if ( !FD_ISSET (session->sock, out_set) )
return 0;
// Now we have to determine whether the socket is connected
// or the connect is failed
struct sockaddr_storage saddr, laddr;
socklen_t slen = sizeof(saddr);
socklen_t llen = sizeof(laddr);
if ( getsockname (session->sock, (struct sockaddr*)&laddr, &llen) < 0
|| getpeername (session->sock, (struct sockaddr*)&saddr, &slen) < 0 )
{
// connection failed
session->lasterror = LIBIRC_ERR_CONNECT;
session->state = LIBIRC_STATE_DISCONNECTED;
return 1;
}
if (saddr.ss_family == AF_INET)
memcpy (&session->local_addr, &((struct sockaddr_in *)&laddr)->sin_addr, sizeof(struct in_addr));
else
memcpy (&session->local_addr, &((struct sockaddr_in6 *)&laddr)->sin6_addr, sizeof(struct in6_addr));
#if defined (ENABLE_DEBUG)
if ( IS_DEBUG_ENABLED(session) )
fprintf (stderr, "[DEBUG] Detected local address: %s\n", inet_ntoa(session->local_addr));
#endif
session->state = LIBIRC_STATE_CONNECTED;
// Get the hostname
if ( gethostname (hname, sizeof(hname)) < 0 )
strcpy (hname, "unknown");
// Prepare the data, which should be sent to the server
if ( session->server_password )
{
snprintf (buf, sizeof(buf), "PASS %s", session->server_password);
irc_send_raw (session, buf);
}
snprintf (buf, sizeof(buf), "NICK %s", session->nick);
irc_send_raw (session, buf);
/*
* RFC 1459 states that "hostname and servername are normally
* ignored by the IRC server when the USER command comes from
* a directly connected client (for security reasons)", therefore
* we don't need them.
*/
snprintf (buf, sizeof(buf), "USER %s unknown unknown :%s",
session->username ? session->username : "nobody",
session->realname ? session->realname : "noname");
irc_send_raw (session, buf);
return 0;
}
if ( session->state != LIBIRC_STATE_CONNECTED )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
// Hey, we've got something to read!
if ( FD_ISSET (session->sock, in_set) )
{
int offset, length = session_socket_read( session );
if ( length < 0 )
{
if ( session->lasterror == 0 )
session->lasterror = (length == 0 ? LIBIRC_ERR_CLOSED : LIBIRC_ERR_TERMINATED);
session->state = LIBIRC_STATE_DISCONNECTED;
return 1;
}
session->incoming_offset += length;
// process the incoming data
while ( (offset = libirc_findcrlf (session->incoming_buf, session->incoming_offset)) > 0 )
{
#if defined (ENABLE_DEBUG)
if ( IS_DEBUG_ENABLED(session) )
libirc_dump_data ("RECV", session->incoming_buf, offset);
#endif
// parse the string
libirc_process_incoming_data (session, offset);
offset = libirc_findcrlf_offset(session->incoming_buf, offset, session->incoming_offset);
if ( session->incoming_offset - offset > 0 )
memmove (session->incoming_buf, session->incoming_buf + offset, session->incoming_offset - offset);
session->incoming_offset -= offset;
}
}
// We can write a stored buffer
if ( FD_ISSET (session->sock, out_set) )
{
int length;
// Because outgoing_buf could be changed asynchronously, we should lock any change
libirc_mutex_lock (&session->mutex_session);
length = session_socket_write( session );
if ( length < 0 )
{
if ( session->lasterror == 0 )
session->lasterror = (length == 0 ? LIBIRC_ERR_CLOSED : LIBIRC_ERR_TERMINATED);
session->state = LIBIRC_STATE_DISCONNECTED;
libirc_mutex_unlock (&session->mutex_session);
return 1;
}
#if defined (ENABLE_DEBUG)
if ( IS_DEBUG_ENABLED(session) )
libirc_dump_data ("SEND", session->outgoing_buf, length);
#endif
if ( length > 0 && session->outgoing_offset - length > 0 )
memmove (session->outgoing_buf, session->outgoing_buf + length, session->outgoing_offset - length);
session->outgoing_offset -= length;
libirc_mutex_unlock (&session->mutex_session);
}
return 0;
}
int irc_send_raw (irc_session_t * session, const char * format, ...)
{
char buf[1024];
va_list va_alist;
if ( session->state != LIBIRC_STATE_CONNECTED )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
va_start (va_alist, format);
vsnprintf (buf, sizeof(buf), format, va_alist);
va_end (va_alist);
libirc_mutex_lock (&session->mutex_session);
if ( (strlen(buf) + 2) >= (sizeof(session->outgoing_buf) - session->outgoing_offset) )
{
libirc_mutex_unlock (&session->mutex_session);
session->lasterror = LIBIRC_ERR_NOMEM;
return 1;
}
strcpy (session->outgoing_buf + session->outgoing_offset, buf);
session->outgoing_offset += strlen (buf);
session->outgoing_buf[session->outgoing_offset++] = 0x0D;
session->outgoing_buf[session->outgoing_offset++] = 0x0A;
libirc_mutex_unlock (&session->mutex_session);
return 0;
}
int irc_cmd_quit (irc_session_t * session, const char * reason)
{
return irc_send_raw (session, "QUIT :%s", reason ? reason : "quit");
}
int irc_cmd_join (irc_session_t * session, const char * channel, const char * key)
{
if ( !channel )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
if ( key )
return irc_send_raw (session, "JOIN %s :%s", channel, key);
else
return irc_send_raw (session, "JOIN %s", channel);
}
int irc_cmd_part (irc_session_t * session, const char * channel)
{
if ( !channel )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
return irc_send_raw (session, "PART %s", channel);
}
int irc_cmd_topic (irc_session_t * session, const char * channel, const char * topic)
{
if ( !channel )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
if ( topic )
return irc_send_raw (session, "TOPIC %s :%s", channel, topic);
else
return irc_send_raw (session, "TOPIC %s", channel);
}
int irc_cmd_names (irc_session_t * session, const char * channel)
{
if ( !channel )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
return irc_send_raw (session, "NAMES %s", channel);
}
int irc_cmd_list (irc_session_t * session, const char * channel)
{
if ( channel )
return irc_send_raw (session, "LIST %s", channel);
else
return irc_send_raw (session, "LIST");
}
int irc_cmd_invite (irc_session_t * session, const char * nick, const char * channel)
{
if ( !channel || !nick )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
return irc_send_raw (session, "INVITE %s %s", nick, channel);
}
int irc_cmd_kick (irc_session_t * session, const char * nick, const char * channel, const char * comment)
{
if ( !channel || !nick )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
if ( comment )
return irc_send_raw (session, "KICK %s %s :%s", channel, nick, comment);
else
return irc_send_raw (session, "KICK %s %s", channel, nick);
}
int irc_cmd_msg (irc_session_t * session, const char * nch, const char * text)
{
if ( !nch || !text )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
return irc_send_raw (session, "PRIVMSG %s :%s", nch, text);
}
int irc_cmd_notice (irc_session_t * session, const char * nch, const char * text)
{
if ( !nch || !text )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
return irc_send_raw (session, "NOTICE %s :%s", nch, text);
}
void irc_target_get_nick (const char * target, char *nick, size_t size)
{
char *p = strstr (target, "!");
unsigned int len;
if ( p )
len = p - target;
else
len = strlen (target);
if ( len > size-1 )
len = size - 1;
memcpy (nick, target, len);
nick[len] = '\0';
}
void irc_target_get_host (const char * target, char *host, size_t size)
{
unsigned int len;
const char *p = strstr (target, "!");
if ( !p )
p = target;
len = strlen (p);
if ( len > size-1 )
len = size - 1;
memcpy (host, p, len);
host[len] = '\0';
}
int irc_cmd_ctcp_request (irc_session_t * session, const char * nick, const char * reply)
{
if ( !nick || !reply )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
return irc_send_raw (session, "PRIVMSG %s :\x01%s\x01", nick, reply);
}
int irc_cmd_ctcp_reply (irc_session_t * session, const char * nick, const char * reply)
{
if ( !nick || !reply )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
return irc_send_raw (session, "NOTICE %s :\x01%s\x01", nick, reply);
}
void irc_get_version (unsigned int * high, unsigned int * low)
{
*high = LIBIRC_VERSION_HIGH;
*low = LIBIRC_VERSION_LOW;
}
void irc_set_ctx (irc_session_t * session, void * ctx)
{
session->ctx = ctx;
}
void * irc_get_ctx (irc_session_t * session)
{
return session->ctx;
}
void irc_set_ctcp_version (irc_session_t * session, const char * version)
{
if ( session->ctcp_version )
free(session->ctcp_version);
session->ctcp_version = strdup(version);
}
void irc_disconnect (irc_session_t * session)
{
if ( session->sock >= 0 )
socket_close (&session->sock);
session->sock = -1;
session->state = LIBIRC_STATE_INIT;
}
int irc_cmd_me (irc_session_t * session, const char * nch, const char * text)
{
if ( !nch || !text )
{
session->lasterror = LIBIRC_ERR_STATE;
return 1;
}
return irc_send_raw (session, "PRIVMSG %s :\x01" "ACTION %s\x01", nch, text);
}
void irc_option_set (irc_session_t * session, unsigned int option)
{
session->options |= option;
}
void irc_option_reset (irc_session_t * session, unsigned int option)
{
session->options &= ~option;
}
int irc_cmd_channel_mode (irc_session_t * session, const char * channel, const char * mode)
{
if ( !channel )
{
session->lasterror = LIBIRC_ERR_INVAL;
return 1;
}
if ( mode )
return irc_send_raw (session, "MODE %s %s", channel, mode);
else
return irc_send_raw (session, "MODE %s", channel);
}
int irc_cmd_user_mode (irc_session_t * session, const char * mode)
{
if ( mode )
return irc_send_raw (session, "MODE %s %s", session->nick, mode);
else
return irc_send_raw (session, "MODE %s", session->nick);
}
int irc_cmd_nick (irc_session_t * session, const char * newnick)
{
if ( !newnick )
{
session->lasterror = LIBIRC_ERR_INVAL;
return 1;
}
return irc_send_raw (session, "NICK %s", newnick);
}
int irc_cmd_whois (irc_session_t * session, const char * nick)
{
if ( !nick )
{
session->lasterror = LIBIRC_ERR_INVAL;
return 1;
}
return irc_send_raw (session, "WHOIS %s %s", nick, nick);
}