| @@ -27,6 +27,6 @@ xcuserdata | |||
| *.hmap | |||
| *.ipa | |||
| libirc* | |||
| .DS_Store | |||
| *~ | |||
| @@ -0,0 +1 @@ | |||
| Subproject commit 04f57f59ef78d2605d4835eda60a0a54c88ab088 | |||
| @@ -1,95 +0,0 @@ | |||
| // | |||
| // NSData+SA_NSDataExtensions.h | |||
| // | |||
| // Copyright (c) 2015 Said Achmiz. | |||
| // | |||
| // Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| // of this software and associated documentation files (the "Software"), to deal | |||
| // in the Software without restriction, including without limitation the rights | |||
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| // copies of the Software, and to permit persons to whom the Software is | |||
| // furnished to do so, subject to the following conditions: | |||
| // | |||
| // The above copyright notice and this permission notice shall be included in all | |||
| // copies or substantial portions of the Software. | |||
| // | |||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| // SOFTWARE. | |||
| #import <Foundation/Foundation.h> | |||
| /** \category NSData+SA_NSDataExtensions | |||
| * @brief Adds several utility methods to NSData. | |||
| */ | |||
| @interface NSData (SA_NSDataExtensions) | |||
| // NOTE on stripping nulls from the ends of byte arrays. | |||
| // | |||
| // If you strip a null from the end of an array which is something other than | |||
| // a null-terminated C string (such as, for example, the bytes representing a | |||
| // UTF-16 string), and thereby cause yourself difficulties, you have only | |||
| // yourself to blame. Be sure that you know what your NSData objects are | |||
| // supposed to contain! | |||
| /** Returns YES if the last byte of the stored data is null, NO otherwise. | |||
| */ | |||
| @property (readonly, getter=isNullTerminated) BOOL nullTerminated; | |||
| /** Returns the stored bytes as a null-terminated C string (byte array). | |||
| If the stored data is already null-terminated, the returned pointer will | |||
| be a pointer to the bytes managed by the receiver. If it is not already | |||
| null-terminated, the returned pointer will point to bytes managed by a | |||
| copy of the receiver (and the bytes of the copy will be null-terminated). | |||
| */ | |||
| @property (readonly) const char *SA_terminatedCString; | |||
| /** Returns data containing the stored bytes as a null-terminated C string | |||
| (byte array). | |||
| If the stored data is already null-terminated, this method simply returns | |||
| the receiver. If it is not already null-terminated, this method returns a | |||
| reference to a fresh copy of the receiver (a copy that contains a | |||
| null-terminated byte array, of course). | |||
| */ | |||
| @property (readonly) NSData *SA_dataWithTerminatedCString; | |||
| /** Returns the stored bytes as an non-null-terminated byte array. | |||
| If the stored data was not null-terminated to begin with, the returned | |||
| pointer will be a pointer to the bytes managed by the receiver. If the | |||
| stored data was null-terminated, the returned pointer will point to bytes | |||
| managed by a copy of the receiver (and the bytes of the copy will not be | |||
| null-terminated; but see NOTE). | |||
| NOTE: If the receiver's *last* byte is null, the bytes pointed to by the | |||
| returned pointer will have that null stripped; but if there are any more | |||
| null bytes prior to that last null, they will remain untouched! | |||
| */ | |||
| @property (readonly) const char *SA_unterminatedByteString; | |||
| /** Returns data containing the stored bytes as a non-null-terminated byte | |||
| array. | |||
| If the stored data was not null-terminated to begin with, this method simply | |||
| returns the receiver. If the stored data was null-terminated, this method | |||
| returns a reference to a fresh copy of the receiver (a copy that contains a | |||
| non-null-terminated byte array, of course; but see NOTE). | |||
| NOTE: If the receiver's *last* byte is null, the bytes managed by the | |||
| returned object will have that null stripped; but if there are any more | |||
| null bytes prior to that last null, they will remain untouched! | |||
| */ | |||
| @property (readonly) NSData *SA_dataWithUnterminatedByteString; | |||
| /** Returns an NSData object containing a blank C string (i.e. a byte sequence | |||
| of length 1, containing the null character '\0'). | |||
| */ | |||
| +(NSData *)dataWithBlankCString; | |||
| @end | |||
| @@ -1,83 +0,0 @@ | |||
| // | |||
| // NSData+SA_NSDataExtensions.m | |||
| // | |||
| // Copyright (c) 2015 Said Achmiz. | |||
| // | |||
| // Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| // of this software and associated documentation files (the "Software"), to deal | |||
| // in the Software without restriction, including without limitation the rights | |||
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| // copies of the Software, and to permit persons to whom the Software is | |||
| // furnished to do so, subject to the following conditions: | |||
| // | |||
| // The above copyright notice and this permission notice shall be included in all | |||
| // copies or substantial portions of the Software. | |||
| // | |||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| // SOFTWARE. | |||
| #import "NSData+SA_NSDataExtensions.h" | |||
| @implementation NSData (SA_NSDataExtensions) | |||
| -(BOOL)isNullTerminated | |||
| { | |||
| return (((char*) self.bytes)[self.length - 1] == '\0'); | |||
| } | |||
| -(const char *)SA_terminatedCString | |||
| { | |||
| return self.SA_dataWithTerminatedCString.bytes; | |||
| } | |||
| -(NSData *)SA_dataWithTerminatedCString | |||
| { | |||
| if(self.length == 0) | |||
| { | |||
| return [NSData dataWithBytes:"\0" length:1]; | |||
| } | |||
| else if(self.isNullTerminated) | |||
| { | |||
| return self; | |||
| } | |||
| else | |||
| { | |||
| char* terminated_string_buffer = malloc(self.length + 1); | |||
| [self getBytes:terminated_string_buffer length:self.length]; | |||
| terminated_string_buffer[self.length] = '\0'; | |||
| return [NSData dataWithBytesNoCopy:terminated_string_buffer length:(self.length + 1) freeWhenDone:YES]; | |||
| } | |||
| } | |||
| -(const char *)SA_unterminatedByteString | |||
| { | |||
| return self.SA_dataWithUnterminatedByteString.bytes; | |||
| } | |||
| -(NSData *)SA_dataWithUnterminatedByteString | |||
| { | |||
| if(self.length == 0 || self.isNullTerminated == NO) | |||
| { | |||
| return self; | |||
| } | |||
| else | |||
| { | |||
| char* unterminated_string_buffer = malloc(self.length - 1); | |||
| [self getBytes:unterminated_string_buffer length:self.length - 1]; | |||
| return [NSData dataWithBytesNoCopy:unterminated_string_buffer length:(self.length - 1) freeWhenDone:YES]; | |||
| } | |||
| } | |||
| +(NSData *)dataWithBlankCString | |||
| { | |||
| return [NSData dataWithBytes:"\0" length:1]; | |||
| } | |||
| @end | |||
| @@ -1,7 +0,0 @@ | |||
| NSData+SA_NSDataExtensions | |||
| Adds utility functions to NSData, that help deal with null termination of C strings. | |||
| This category on NSData adds properties that allow you to get the null-terminated or non-null-terminated versions of byte arrays stored as NSData objects, and to easily check whether an NSData's byte array is, or is not, null-terminated (that is, whether its last byte is a null). | |||
| Copyright (c) 2015 Said Achmiz. | |||
| @@ -0,0 +1,110 @@ | |||
| /* src/config.h. Generated from config.h.in by configure. */ | |||
| /* include/config.h.in. Generated from configure.in by autoheader. */ | |||
| /* Define to 1 if you have the `getaddrinfo' function. */ | |||
| /* #undef HAVE_GETADDRINFO */ | |||
| /* Define to 1 if you have the `gethostbyname_r' function. */ | |||
| /* #undef HAVE_GETHOSTBYNAME_R */ | |||
| /* Define to 1 if you have the `inet_ntoa' function. */ | |||
| /* #undef HAVE_INET_NTOA */ | |||
| /* Define to 1 if you have the `inet_pton' function. */ | |||
| /* #undef HAVE_INET_PTON */ | |||
| /* Define to 1 if you have the <inttypes.h> header file. */ | |||
| /* #undef HAVE_INTTYPES_H */ | |||
| /* Define to 1 if you have the `localtime_r' function. */ | |||
| /* #undef HAVE_LOCALTIME_R */ | |||
| /* Define to 1 if your system has a GNU libc compatible `malloc' function, and | |||
| to 0 otherwise. */ | |||
| #define HAVE_MALLOC 0 | |||
| /* Define to 1 if you have the <memory.h> header file. */ | |||
| /* #undef HAVE_MEMORY_H */ | |||
| /* Define to 1 if you have the `socket' function. */ | |||
| /* #undef HAVE_SOCKET */ | |||
| /* Define to 1 if `stat' has the bug that it succeeds when given the | |||
| zero-length file name argument. */ | |||
| #define HAVE_STAT_EMPTY_STRING_BUG 1 | |||
| /* Define to 1 if stdbool.h conforms to C99. */ | |||
| #define HAVE_STDBOOL_H 1 | |||
| /* Define to 1 if you have the <stdint.h> header file. */ | |||
| /* #undef HAVE_STDINT_H */ | |||
| /* Define to 1 if you have the <stdlib.h> header file. */ | |||
| /* #undef HAVE_STDLIB_H */ | |||
| /* Define to 1 if you have the <strings.h> header file. */ | |||
| /* #undef HAVE_STRINGS_H */ | |||
| /* Define to 1 if you have the <string.h> header file. */ | |||
| /* #undef HAVE_STRING_H */ | |||
| /* Define to 1 if you have the <sys/select.h> header file. */ | |||
| /* #undef HAVE_SYS_SELECT_H */ | |||
| /* Define to 1 if you have the <sys/socket.h> header file. */ | |||
| /* #undef HAVE_SYS_SOCKET_H */ | |||
| /* Define to 1 if you have the <sys/stat.h> header file. */ | |||
| /* #undef HAVE_SYS_STAT_H */ | |||
| /* Define to 1 if you have the <sys/types.h> header file. */ | |||
| /* #undef HAVE_SYS_TYPES_H */ | |||
| /* Define to 1 if you have the <unistd.h> header file. */ | |||
| /* #undef HAVE_UNISTD_H */ | |||
| /* Define to 1 if the system has the type `_Bool'. */ | |||
| /* #undef HAVE__BOOL */ | |||
| /* Define to 1 if `lstat' dereferences a symlink specified with a trailing | |||
| slash. */ | |||
| /* #undef LSTAT_FOLLOWS_SLASHED_SYMLINK */ | |||
| /* Define to the address where bug reports for this package should be sent. */ | |||
| #define PACKAGE_BUGREPORT "gyunaev@ulduzsoft.com" | |||
| /* Define to the full name of this package. */ | |||
| #define PACKAGE_NAME "libircclient" | |||
| /* Define to the full name and version of this package. */ | |||
| #define PACKAGE_STRING "libircclient 1.8" | |||
| /* Define to the one symbol short name of this package. */ | |||
| #define PACKAGE_TARNAME "libircclient" | |||
| /* Define to the version of this package. */ | |||
| #define PACKAGE_VERSION "1.8" | |||
| /* Define to the type of arg 1 for `select'. */ | |||
| #define SELECT_TYPE_ARG1 int | |||
| /* Define to the type of args 2, 3 and 4 for `select'. */ | |||
| #define SELECT_TYPE_ARG234 (int *) | |||
| /* Define to the type of arg 5 for `select'. */ | |||
| #define SELECT_TYPE_ARG5 (struct timeval *) | |||
| /* Define to 1 if you have the ANSI C header files. */ | |||
| /* #undef STDC_HEADERS */ | |||
| /* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */ | |||
| /* #undef TIME_WITH_SYS_TIME */ | |||
| /* Define to empty if `const' does not conform to ANSI C. */ | |||
| /* #undef const */ | |||
| /* Define to rpl_malloc if the replacement function should be used. */ | |||
| /* #undef malloc */ | |||
| /* Define to `unsigned int' if <sys/types.h> does not define. */ | |||
| /* #undef size_t */ | |||
| @@ -0,0 +1,54 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #ifndef INCLUDE_IRC_DCC_H | |||
| #define INCLUDE_IRC_DCC_H | |||
| /* | |||
| * This structure keeps the state of a single DCC connection. | |||
| */ | |||
| struct irc_dcc_session_s | |||
| { | |||
| irc_dcc_session_t * next; | |||
| irc_dcc_t id; | |||
| void * ctx; | |||
| socket_t sock; /*!< DCC socket */ | |||
| int dccmode; /*!< Boolean value to differ chat vs send | |||
| requests. Changes the cb behavior - when | |||
| it is chat, data is sent by lines with | |||
| stripped CRLFs. In file mode, the data | |||
| is sent as-is */ | |||
| int state; | |||
| time_t timeout; | |||
| FILE * dccsend_file_fp; | |||
| unsigned int received_file_size; | |||
| unsigned int file_confirm_offset; | |||
| struct sockaddr_in remote_addr; | |||
| char incoming_buf[LIBIRC_DCC_BUFFER_SIZE]; | |||
| unsigned int incoming_offset; | |||
| char outgoing_buf[LIBIRC_DCC_BUFFER_SIZE]; | |||
| unsigned int outgoing_offset; | |||
| port_mutex_t mutex_outbuf; | |||
| irc_dcc_callback_t cb; | |||
| }; | |||
| #endif /* INCLUDE_IRC_DCC_H */ | |||
| @@ -0,0 +1,235 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #ifndef INCLUDE_IRC_ERRORS_H | |||
| #define INCLUDE_IRC_ERRORS_H | |||
| #ifndef IN_INCLUDE_LIBIRC_H | |||
| #error This file should not be included directly, include just libircclient.h | |||
| #endif | |||
| /*! brief No error | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_OK 0 | |||
| /*! \brief Invalid argument | |||
| * | |||
| * An invalid value was given for one of the arguments to a function. | |||
| * For example, supplying the NULL value for \a channel argument of | |||
| * irc_cmd_join() produces LIBIRC_ERR_INVAL error. You should fix the code. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_INVAL 1 | |||
| /*! \brief Could not resolve host. | |||
| * | |||
| * The host name supplied for irc_connect() function could not be resolved | |||
| * into valid IP address. Usually means that host name is invalid. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_RESOLV 2 | |||
| /*! \brief Could not create socket. | |||
| * | |||
| * The new socket could not be created or made non-blocking. Usually means | |||
| * that the server is out of resources, or (rarely :) a bug in libircclient. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_SOCKET 3 | |||
| /*! \brief Could not connect. | |||
| * | |||
| * The socket could not connect to the IRC server, or to the destination DCC | |||
| * part. Usually means that either the IRC server is down or its address is | |||
| * invalid. For DCC the reason usually is the firewall on your or destination | |||
| * computer, which refuses DCC transfer. | |||
| * | |||
| * \sa irc_run irc_connect | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_CONNECT 4 | |||
| /*! \brief Connection closed by remote peer. | |||
| * | |||
| * The IRC connection was closed by the IRC server (which could mean that an | |||
| * IRC operator just have banned you from the server :)), or the DCC connection | |||
| * was closed by remote peer - for example, the other side just quits his mIrc. | |||
| * Usually it is not an error. | |||
| * | |||
| * \sa irc_run irc_connect irc_dcc_callback_t | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_CLOSED 5 | |||
| /*! \brief Out of memory | |||
| * | |||
| * There are two possible reasons for this error. First is that memory could | |||
| * not be allocated for libircclient use, and this error usually is fatal. | |||
| * Second reason is that the command queue (which keeps command ready to be | |||
| * sent to the IRC server) is full, and could not accept more commands yet. | |||
| * In this case you should just wait, and repeat the command later. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_NOMEM 6 | |||
| /*! \brief Could not accept new connection | |||
| * | |||
| * A DCC chat/send connection from the remote peer could not be accepted. | |||
| * Either the connection was just terminated before it is accepted, or there | |||
| * is a bug in libircclient. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_ACCEPT 7 | |||
| /*! \brief Could not send this | |||
| * | |||
| * A \a filename supplied to irc_dcc_sendfile() could not be sent. Either is | |||
| * is not a file (a directory or a socket, for example), or it is not readable. * | |||
| * | |||
| * \sa LIBIRC_ERR_OPENFILE | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_NODCCSEND 9 | |||
| /*! \brief Could not read DCC file or socket | |||
| * | |||
| * Either a DCC file could not be read (for example, was truncated during | |||
| * sending), or a DCC socket returns a read error, which usually means that | |||
| * the network connection is terminated. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_READ 10 | |||
| /*! \brief Could not write DCC file or socket | |||
| * | |||
| * Either a DCC file could not be written (for example, there is no free space | |||
| * on disk), or a DCC socket returns a write error, which usually means that | |||
| * the network connection is terminated. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_WRITE 11 | |||
| /*! \brief Invalid state | |||
| * | |||
| * The function is called when it is not allowed to be called. For example, | |||
| * irc_cmd_join() was called before the connection to IRC server succeed, and | |||
| * ::event_connect is called. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_STATE 12 | |||
| /*! \brief Operation timed out | |||
| * | |||
| * The DCC request is timed out. | |||
| * There is a timer for each DCC request, which tracks connecting, accepting | |||
| * and non-accepted/declined DCC requests. For every request this timer | |||
| * is currently 60 seconds. If the DCC request was not connected, accepted | |||
| * or declined during this time, it will be terminated with this error. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_TIMEOUT 13 | |||
| /*! \brief Could not open file for DCC send | |||
| * | |||
| * The file specified in irc_dcc_sendfile() could not be opened. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_OPENFILE 14 | |||
| /*! \brief IRC server connection terminated | |||
| * | |||
| * The connection to the IRC server was terminated - possibly, by network | |||
| * error. Try to irc_connect() again. | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_TERMINATED 15 | |||
| /*! \brief IPv6 not supported | |||
| * | |||
| * The function which requires IPv6 support was called, but the IPv6 support was not compiled | |||
| * into the application | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_NOIPV6 16 | |||
| /*! \brief SSL not supported | |||
| * | |||
| * The SSL connection was required but the library was not compiled with SSL support | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_SSL_NOT_SUPPORTED 17 | |||
| /*! \brief SSL initialization failed | |||
| * | |||
| * The SSL connection was required but the library was not compiled with SSL support | |||
| * | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_SSL_INIT_FAILED 18 | |||
| /*! \brief SSL connection failed | |||
| * | |||
| * SSL handshare failed when attempting to connect to the server. Typically this means you're trying | |||
| * to use SSL but attempting to connect to a non-SSL port. | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_CONNECT_SSL_FAILED 19 | |||
| /*! \brief SSL certificate verify failed | |||
| * | |||
| * The server is using the self-signed certificate. Use LIBIRC_OPTION_SSL_NO_VERIFY option to connect to it. | |||
| * \ingroup errorcodes | |||
| */ | |||
| #define LIBIRC_ERR_SSL_CERT_VERIFY_FAILED 20 | |||
| // Internal max error value count. | |||
| // If you added more errors, add them to errors.c too! | |||
| #define LIBIRC_ERR_MAX 21 | |||
| #endif /* INCLUDE_IRC_ERRORS_H */ | |||
| @@ -0,0 +1,415 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #ifndef INCLUDE_IRC_EVENTS_H | |||
| #define INCLUDE_IRC_EVENTS_H | |||
| #ifndef IN_INCLUDE_LIBIRC_H | |||
| #error This file should not be included directly, include just libircclient.h | |||
| #endif | |||
| /*! | |||
| * \fn typedef void (*irc_event_callback_t) (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count) | |||
| * \brief A most common event callback | |||
| * | |||
| * \param session the session, which generates an event | |||
| * \param event the text name of the event. Useful in case you use a single | |||
| * event handler for several events simultaneously. | |||
| * \param origin the originator of the event. See the note below. | |||
| * \param params a list of event params. Depending on the event nature, it | |||
| * could have zero or more params. The actual number of params | |||
| * is specified in count. None of the params can be NULL, but | |||
| * 'params' pointer itself could be NULL for some events. | |||
| * \param count the total number of params supplied. | |||
| * | |||
| * Every event generates a callback. This callback is generated by most events. | |||
| * Depending on the event nature, it can provide zero or more params. For each | |||
| * event, the number of provided params is fixed, and their meaning is | |||
| * described. | |||
| * | |||
| * Every event has origin, though the \a origin variable may be NULL, which | |||
| * means that event origin is unknown. The origin usually looks like | |||
| * nick!host\@ircserver, i.e. like tim!home\@irc.krasnogorsk.ru. Such origins | |||
| * can not be used in IRC commands, and need to be stripped (i.e. host and | |||
| * server part should be cut off) before using. This can be done either | |||
| * explicitly, by calling irc_target_get_nick(), or implicitly for all the | |||
| * events - by setting the #LIBIRC_OPTION_STRIPNICKS option with irc_option_set(). | |||
| * | |||
| * \ingroup events | |||
| */ | |||
| typedef void (*irc_event_callback_t) (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count); | |||
| /*! | |||
| * \fn typedef void (*irc_eventcode_callback_t) (irc_session_t * session, unsigned int event, const char * origin, const char ** params, unsigned int count) | |||
| * \brief A numeric event callback | |||
| * | |||
| * \param session the session, which generates an event | |||
| * \param event the numeric code of the event. Useful in case you use a | |||
| * single event handler for several events simultaneously. | |||
| * \param origin the originator of the event. See the note below. | |||
| * \param params a list of event params. Depending on the event nature, it | |||
| * could have zero or more params. The actual number of params | |||
| * is specified in count. None of the params can be NULL, but | |||
| * 'params' pointer itself could be NULL for some events. | |||
| * \param count the total number of params supplied. | |||
| * | |||
| * Most times in reply to your actions the IRC server generates numeric | |||
| * callbacks. Most of them are error codes, and some of them mark list start | |||
| * and list stop markers. Every code has its own set of params; for details | |||
| * you can either experiment, or read RFC 1459. | |||
| * | |||
| * Every event has origin, though the \a origin variable may be NULL, which | |||
| * means that event origin is unknown. The origin usually looks like | |||
| * nick!host\@ircserver, i.e. like tim!home\@irc.krasnogorsk.ru. Such origins | |||
| * can not be used in IRC commands, and need to be stripped (i.e. host and | |||
| * server part should be cut off) before using. This can be done either | |||
| * explicitly, by calling irc_target_get_nick(), or implicitly for all the | |||
| * events - by setting the #LIBIRC_OPTION_STRIPNICKS option with irc_option_set(). | |||
| * | |||
| * \ingroup events | |||
| */ | |||
| typedef void (*irc_eventcode_callback_t) (irc_session_t * session, unsigned int event, const char * origin, const char ** params, unsigned int count); | |||
| /*! | |||
| * \fn typedef void (*irc_event_dcc_chat_t) (irc_session_t * session, const char * nick, const char * addr, irc_dcc_t dccid) | |||
| * \brief A remote DCC CHAT request callback | |||
| * | |||
| * \param session the session, which generates an event | |||
| * \param nick the person who requested DCC CHAT with you. | |||
| * \param addr the person's IP address in decimal-dot notation. | |||
| * \param dccid an id associated with this request. Use it in calls to | |||
| * irc_dcc_accept() or irc_dcc_decline(). | |||
| * | |||
| * This callback is called when someone requests DCC CHAT with you. In respond | |||
| * you should call either irc_dcc_accept() to accept chat request, or | |||
| * irc_dcc_decline() to decline chat request. | |||
| * | |||
| * \sa irc_dcc_accept or irc_dcc_decline | |||
| * \ingroup events | |||
| */ | |||
| typedef void (*irc_event_dcc_chat_t) (irc_session_t * session, const char * nick, const char * addr, irc_dcc_t dccid); | |||
| /*! | |||
| * \fn typedef void (*irc_event_dcc_send_t) (irc_session_t * session, const char * nick, const char * addr, const char * filename, unsigned long size, irc_dcc_t dccid) | |||
| * \brief A remote DCC CHAT request callback | |||
| * | |||
| * \param session the session, which generates an event | |||
| * \param nick the person who requested DCC CHAT with you. | |||
| * \param addr the person's IP address in decimal-dot notation. | |||
| * \param filename the sent filename. | |||
| * \param size the filename size. | |||
| * \param dccid an id associated with this request. Use it in calls to | |||
| * irc_dcc_accept() or irc_dcc_decline(). | |||
| * | |||
| * This callback is called when someone wants to send a file to you using | |||
| * DCC SEND. As with chat, in respond you should call either irc_dcc_accept() | |||
| * to accept this request and receive the file, or irc_dcc_decline() to | |||
| * decline this request. | |||
| * | |||
| * \sa irc_dcc_accept or irc_dcc_decline | |||
| * \ingroup events | |||
| */ | |||
| typedef void (*irc_event_dcc_send_t) (irc_session_t * session, const char * nick, const char * addr, const char * filename, unsigned long size, irc_dcc_t dccid); | |||
| /*! \brief Event callbacks structure. | |||
| * | |||
| * All the communication with the IRC network is based on events. Generally | |||
| * speaking, event is anything generated by someone else in the network, | |||
| * or by the IRC server itself. "Someone sends you a message", "Someone | |||
| * has joined the channel", "Someone has quits IRC" - all these messages | |||
| * are events. | |||
| * | |||
| * Every event has its own event handler, which is called when the | |||
| * appropriate event is received. You don't have to define all the event | |||
| * handlers; define only the handlers for the events you need to intercept. | |||
| * | |||
| * Most event callbacks are the types of ::irc_event_callback_t. There are | |||
| * also events, which generate ::irc_eventcode_callback_t, | |||
| * ::irc_event_dcc_chat_t and ::irc_event_dcc_send_t callbacks. | |||
| * | |||
| * \ingroup events | |||
| */ | |||
| typedef struct | |||
| { | |||
| /*! | |||
| * The "on_connect" event is triggered when the client successfully | |||
| * connects to the server, and could send commands to the server. | |||
| * No extra params supplied; \a params is 0. | |||
| */ | |||
| irc_event_callback_t event_connect; | |||
| /*! | |||
| * The "nick" event is triggered when the client receives a NICK message, | |||
| * meaning that someone (including you) on a channel with the client has | |||
| * changed their nickname. | |||
| * | |||
| * \param origin the person, who changes the nick. Note that it can be you! | |||
| * \param params[0] mandatory, contains the new nick. | |||
| */ | |||
| irc_event_callback_t event_nick; | |||
| /*! | |||
| * The "quit" event is triggered upon receipt of a QUIT message, which | |||
| * means that someone on a channel with the client has disconnected. | |||
| * | |||
| * \param origin the person, who is disconnected | |||
| * \param params[0] optional, contains the reason message (user-specified). | |||
| */ | |||
| irc_event_callback_t event_quit; | |||
| /*! | |||
| * The "join" event is triggered upon receipt of a JOIN message, which | |||
| * means that someone has entered a channel that the client is on. | |||
| * | |||
| * \param origin the person, who joins the channel. By comparing it with | |||
| * your own nickname, you can check whether your JOIN | |||
| * command succeed. | |||
| * \param params[0] mandatory, contains the channel name. | |||
| */ | |||
| irc_event_callback_t event_join; | |||
| /*! | |||
| * The "part" event is triggered upon receipt of a PART message, which | |||
| * means that someone has left a channel that the client is on. | |||
| * | |||
| * \param origin the person, who leaves the channel. By comparing it with | |||
| * your own nickname, you can check whether your PART | |||
| * command succeed. | |||
| * \param params[0] mandatory, contains the channel name. | |||
| * \param params[1] optional, contains the reason message (user-defined). | |||
| */ | |||
| irc_event_callback_t event_part; | |||
| /*! | |||
| * The "mode" event is triggered upon receipt of a channel MODE message, | |||
| * which means that someone on a channel with the client has changed the | |||
| * channel's parameters. | |||
| * | |||
| * \param origin the person, who changed the channel mode. | |||
| * \param params[0] mandatory, contains the channel name. | |||
| * \param params[1] mandatory, contains the changed channel mode, like | |||
| * '+t', '-i' and so on. | |||
| * \param params[2] optional, contains the mode argument (for example, a | |||
| * key for +k mode, or user who got the channel operator status for | |||
| * +o mode) | |||
| */ | |||
| irc_event_callback_t event_mode; | |||
| /*! | |||
| * The "umode" event is triggered upon receipt of a user MODE message, | |||
| * which means that your user mode has been changed. | |||
| * | |||
| * \param origin the person, who changed the channel mode. | |||
| * \param params[0] mandatory, contains the user changed mode, like | |||
| * '+t', '-i' and so on. | |||
| */ | |||
| irc_event_callback_t event_umode; | |||
| /*! | |||
| * The "topic" event is triggered upon receipt of a TOPIC message, which | |||
| * means that someone on a channel with the client has changed the | |||
| * channel's topic. | |||
| * | |||
| * \param origin the person, who changes the channel topic. | |||
| * \param params[0] mandatory, contains the channel name. | |||
| * \param params[1] optional, contains the new topic. | |||
| */ | |||
| irc_event_callback_t event_topic; | |||
| /*! | |||
| * The "kick" event is triggered upon receipt of a KICK message, which | |||
| * means that someone on a channel with the client (or possibly the | |||
| * client itself!) has been forcibly ejected. | |||
| * | |||
| * \param origin the person, who kicked the poor. | |||
| * \param params[0] mandatory, contains the channel name. | |||
| * \param params[0] optional, contains the nick of kicked person. | |||
| * \param params[1] optional, contains the kick text | |||
| */ | |||
| irc_event_callback_t event_kick; | |||
| /*! | |||
| * The "channel" event is triggered upon receipt of a PRIVMSG message | |||
| * to an entire channel, which means that someone on a channel with | |||
| * the client has said something aloud. Your own messages don't trigger | |||
| * PRIVMSG event. | |||
| * | |||
| * \param origin the person, who generates the message. | |||
| * \param params[0] mandatory, contains the channel name. | |||
| * \param params[1] optional, contains the message text | |||
| */ | |||
| irc_event_callback_t event_channel; | |||
| /*! | |||
| * The "privmsg" event is triggered upon receipt of a PRIVMSG message | |||
| * which is addressed to one or more clients, which means that someone | |||
| * is sending the client a private message. | |||
| * | |||
| * \param origin the person, who generates the message. | |||
| * \param params[0] mandatory, contains your nick. | |||
| * \param params[1] optional, contains the message text | |||
| */ | |||
| irc_event_callback_t event_privmsg; | |||
| /*! | |||
| * The "privmsg" event is triggered upon receipt of a PRIVMSG message | |||
| * which is addressed to no one in particular, but it sent to the client | |||
| * anyway. | |||
| * | |||
| * \param origin the person, who generates the message. | |||
| * \param params optional, contains who knows what. | |||
| */ | |||
| irc_event_callback_t event_server_msg; | |||
| /*! | |||
| * The "notice" event is triggered upon receipt of a NOTICE message | |||
| * which means that someone has sent the client a public or private | |||
| * notice. According to RFC 1459, the only difference between NOTICE | |||
| * and PRIVMSG is that you should NEVER automatically reply to NOTICE | |||
| * messages. Unfortunately, this rule is frequently violated by IRC | |||
| * servers itself - for example, NICKSERV messages require reply, and | |||
| * are NOTICEs. | |||
| * | |||
| * \param origin the person, who generates the message. | |||
| * \param params[0] mandatory, contains the target nick name. | |||
| * \param params[1] optional, contains the message text | |||
| */ | |||
| irc_event_callback_t event_notice; | |||
| /*! | |||
| * The "channel_notice" event is triggered upon receipt of a NOTICE | |||
| * message which means that someone has sent the client a public | |||
| * notice. According to RFC 1459, the only difference between NOTICE | |||
| * and PRIVMSG is that you should NEVER automatically reply to NOTICE | |||
| * messages. Unfortunately, this rule is frequently violated by IRC | |||
| * servers itself - for example, NICKSERV messages require reply, and | |||
| * are NOTICEs. | |||
| * | |||
| * \param origin the person, who generates the message. | |||
| * \param params[0] mandatory, contains the channel name. | |||
| * \param params[1] optional, contains the message text | |||
| */ | |||
| irc_event_callback_t event_channel_notice; | |||
| /*! | |||
| * The "server_notice" event is triggered upon receipt of a NOTICE | |||
| * message which means that the server has sent the client a notice. | |||
| * This notice is not necessarily addressed to the client's nick | |||
| * (for example, AUTH notices, sent before the client's nick is known). | |||
| * According to RFC 1459, the only difference between NOTICE | |||
| * and PRIVMSG is that you should NEVER automatically reply to NOTICE | |||
| * messages. Unfortunately, this rule is frequently violated by IRC | |||
| * servers itself - for example, NICKSERV messages require reply, and | |||
| * are NOTICEs. | |||
| * | |||
| * \param origin the person, who generates the message. | |||
| * \param params optional, contains who knows what. | |||
| */ | |||
| irc_event_callback_t event_server_notice; | |||
| /*! | |||
| * The "invite" event is triggered upon receipt of an INVITE message, | |||
| * which means that someone is permitting the client's entry into a +i | |||
| * channel. | |||
| * | |||
| * \param origin the person, who INVITEs you. | |||
| * \param params[0] mandatory, contains your nick. | |||
| * \param params[1] mandatory, contains the channel name you're invited into. | |||
| * | |||
| * \sa irc_cmd_invite irc_cmd_chanmode_invite | |||
| */ | |||
| irc_event_callback_t event_invite; | |||
| /*! | |||
| * The "ctcp" event is triggered when the client receives the CTCP | |||
| * request. By default, the built-in CTCP request handler is used. The | |||
| * build-in handler automatically replies on most CTCP messages, so you | |||
| * will rarely need to override it. | |||
| * | |||
| * \param origin the person, who generates the message. | |||
| * \param params[0] mandatory, the complete CTCP message, including its | |||
| * arguments. | |||
| * | |||
| * Mirc generates PING, FINGER, VERSION, TIME and ACTION messages, | |||
| * check the source code of \c libirc_event_ctcp_internal function to | |||
| * see how to write your own CTCP request handler. Also you may find | |||
| * useful this question in FAQ: \ref faq4 | |||
| */ | |||
| irc_event_callback_t event_ctcp_req; | |||
| /*! | |||
| * The "ctcp" event is triggered when the client receives the CTCP reply. | |||
| * | |||
| * \param origin the person, who generates the message. | |||
| * \param params[0] mandatory, the CTCP message itself with its arguments. | |||
| */ | |||
| irc_event_callback_t event_ctcp_rep; | |||
| /*! | |||
| * The "action" event is triggered when the client receives the CTCP | |||
| * ACTION message. These messages usually looks like:\n | |||
| * \code | |||
| * [23:32:55] * Tim gonna sleep. | |||
| * \endcode | |||
| * | |||
| * \param origin the person, who generates the message. | |||
| * \param params[0] mandatory, the ACTION message. | |||
| */ | |||
| irc_event_callback_t event_ctcp_action; | |||
| /*! | |||
| * The "unknown" event is triggered upon receipt of any number of | |||
| * unclassifiable miscellaneous messages, which aren't handled by the | |||
| * library. | |||
| */ | |||
| irc_event_callback_t event_unknown; | |||
| /*! | |||
| * The "numeric" event is triggered upon receipt of any numeric response | |||
| * from the server. There is a lot of such responses, see the full list | |||
| * here: \ref rfcnumbers. | |||
| * | |||
| * See the params in ::irc_eventcode_callback_t specification. | |||
| */ | |||
| irc_eventcode_callback_t event_numeric; | |||
| /*! | |||
| * The "dcc chat" event is triggered when someone requests a DCC CHAT from | |||
| * you. | |||
| * | |||
| * See the params in ::irc_event_dcc_chat_t specification. | |||
| */ | |||
| irc_event_dcc_chat_t event_dcc_chat_req; | |||
| /*! | |||
| * The "dcc chat" event is triggered when someone wants to send a file | |||
| * to you via DCC SEND request. | |||
| * | |||
| * See the params in ::irc_event_dcc_send_t specification. | |||
| */ | |||
| irc_event_dcc_send_t event_dcc_send_req; | |||
| } irc_callbacks_t; | |||
| #endif /* INCLUDE_IRC_EVENTS_H */ | |||
| @@ -0,0 +1,56 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #ifndef INCLUDE_IRC_OPTIONS_H | |||
| #define INCLUDE_IRC_OPTIONS_H | |||
| #ifndef IN_INCLUDE_LIBIRC_H | |||
| #error This file should not be included directly, include just libircclient.h | |||
| #endif | |||
| /*! | |||
| * enables additional debug output | |||
| * \ingroup options | |||
| */ | |||
| #define LIBIRC_OPTION_DEBUG (1 << 1) | |||
| /*! \brief allows to strip origins automatically. | |||
| * | |||
| * For every IRC server event, the event origin is sent in standard form: | |||
| * nick!host\@ircserver, i.e. like tim!home\@irc.freenet.org. Such origins | |||
| * can not be used in IRC commands, and need to be stripped (i.e. host and | |||
| * server part should be cut off) before using. This can be done either | |||
| * explicitly, by calling irc_target_get_nick(), or implicitly for all the | |||
| * events - by setting this option with irc_option_set(). | |||
| * \ingroup options | |||
| */ | |||
| #define LIBIRC_OPTION_STRIPNICKS (1 << 2) | |||
| /*! \brief Disables the certificate verification for SSL connections | |||
| * | |||
| * By default the SSL connection authenticy is ensured by verifying that the certificate | |||
| * presented by the server is signed by a known trusted certificate authority. Since those | |||
| * typically cost money, some IRC servers use the self-signed certificates. They provide the | |||
| * benefits of the SSL connection but since they are not signed by the Certificate Authority, | |||
| * their authencity cannot be verified. This option, if set, disables the certificate | |||
| * verification - the library will accept any certificate presented by the server. | |||
| * | |||
| * This option must be set before the irc_connect function is called. | |||
| * \ingroup options | |||
| */ | |||
| #define LIBIRC_OPTION_SSL_NO_VERIFY (1 << 3) | |||
| #endif /* INCLUDE_IRC_OPTIONS_H */ | |||
| @@ -0,0 +1,36 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #ifndef INCLUDE_IRC_PARAMS_H | |||
| #define INCLUDE_IRC_PARAMS_H | |||
| #define LIBIRC_VERSION_HIGH 1 | |||
| #define LIBIRC_VERSION_LOW 8 | |||
| #define LIBIRC_BUFFER_SIZE 1024 | |||
| #define LIBIRC_DCC_BUFFER_SIZE 1024 | |||
| #define LIBIRC_STATE_INIT 0 | |||
| #define LIBIRC_STATE_LISTENING 1 | |||
| #define LIBIRC_STATE_CONNECTING 2 | |||
| #define LIBIRC_STATE_CONNECTED 3 | |||
| #define LIBIRC_STATE_DISCONNECTED 4 | |||
| #define LIBIRC_STATE_CONFIRM_SIZE 5 // Used only by DCC send to confirm the amount of sent data | |||
| #define LIBIRC_STATE_REMOVED 10 // this state is used only in DCC | |||
| #define SSL_PREFIX '#' | |||
| #endif /* INCLUDE_IRC_PARAMS_H */ | |||
| @@ -0,0 +1,79 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #ifndef INCLUDE_IRC_SESSION_H | |||
| #define INCLUDE_IRC_SESSION_H | |||
| #include "params.h" | |||
| #include "dcc.h" | |||
| #include "libirc_events.h" | |||
| // Session flags | |||
| #define SESSIONFL_MOTD_RECEIVED (0x00000001) | |||
| #define SESSIONFL_SSL_CONNECTION (0x00000002) | |||
| #define SESSIONFL_SSL_WRITE_WANTS_READ (0x00000004) | |||
| #define SESSIONFL_SSL_READ_WANTS_WRITE (0x00000008) | |||
| #define SESSIONFL_USES_IPV6 (0x00000010) | |||
| struct irc_session_s | |||
| { | |||
| void * ctx; | |||
| int dcc_timeout; | |||
| int options; | |||
| int lasterror; | |||
| char incoming_buf[LIBIRC_BUFFER_SIZE]; | |||
| unsigned int incoming_offset; | |||
| char outgoing_buf[LIBIRC_BUFFER_SIZE]; | |||
| unsigned int outgoing_offset; | |||
| port_mutex_t mutex_session; | |||
| socket_t sock; | |||
| int state; | |||
| int flags; | |||
| char * server; | |||
| char * server_password; | |||
| char * realname; | |||
| char * username; | |||
| char * nick; | |||
| char * ctcp_version; | |||
| #if defined( ENABLE_IPV6 ) | |||
| struct in6_addr local_addr6; | |||
| #endif | |||
| struct in_addr local_addr; | |||
| irc_dcc_t dcc_last_id; | |||
| irc_dcc_session_t * dcc_sessions; | |||
| port_mutex_t mutex_dcc; | |||
| irc_callbacks_t callbacks; | |||
| #if defined (ENABLE_SSL) | |||
| SSL * ssl; | |||
| #endif | |||
| }; | |||
| #endif /* INCLUDE_IRC_SESSION_H */ | |||
| @@ -0,0 +1,388 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #include <ctype.h> | |||
| #define LIBIRC_COLORPARSER_BOLD (1<<1) | |||
| #define LIBIRC_COLORPARSER_UNDERLINE (1<<2) | |||
| #define LIBIRC_COLORPARSER_REVERSE (1<<3) | |||
| #define LIBIRC_COLORPARSER_COLOR (1<<4) | |||
| #define LIBIRC_COLORPARSER_MAXCOLORS 15 | |||
| static const char * color_replacement_table[] = | |||
| { | |||
| "WHITE", | |||
| "BLACK", | |||
| "DARKBLUE", | |||
| "DARKGREEN", | |||
| "RED", | |||
| "BROWN", | |||
| "PURPLE", | |||
| "OLIVE", | |||
| "YELLOW", | |||
| "GREEN", | |||
| "TEAL", | |||
| "CYAN", | |||
| "BLUE", | |||
| "MAGENTA", | |||
| "DARKGRAY", | |||
| "LIGHTGRAY", | |||
| 0 | |||
| }; | |||
| static inline void libirc_colorparser_addorcat (char ** destline, unsigned int * destlen, const char * str) | |||
| { | |||
| unsigned int len = strlen(str); | |||
| if ( *destline ) | |||
| { | |||
| strcpy (*destline, str); | |||
| *destline += len; | |||
| } | |||
| else | |||
| *destlen += len; | |||
| } | |||
| static void libirc_colorparser_applymask (unsigned int * mask, | |||
| char ** destline, unsigned int * destlen, | |||
| unsigned int bitmask, const char * start, const char * end) | |||
| { | |||
| if ( (*mask & bitmask) != 0 ) | |||
| { | |||
| *mask &= ~bitmask; | |||
| libirc_colorparser_addorcat (destline, destlen, end); | |||
| } | |||
| else | |||
| { | |||
| *mask |= bitmask; | |||
| libirc_colorparser_addorcat (destline, destlen, start); | |||
| } | |||
| } | |||
| static void libirc_colorparser_applycolor (unsigned int * mask, | |||
| char ** destline, unsigned int * destlen, | |||
| unsigned int colorid, unsigned int bgcolorid) | |||
| { | |||
| const char * end = "[/COLOR]"; | |||
| char startbuf[64]; | |||
| if ( bgcolorid != 0 ) | |||
| sprintf (startbuf, "[COLOR=%s/%s]", color_replacement_table[colorid], color_replacement_table[bgcolorid]); | |||
| else | |||
| sprintf (startbuf, "[COLOR=%s]", color_replacement_table[colorid]); | |||
| if ( (*mask & LIBIRC_COLORPARSER_COLOR) != 0 ) | |||
| libirc_colorparser_addorcat (destline, destlen, end); | |||
| *mask |= LIBIRC_COLORPARSER_COLOR; | |||
| libirc_colorparser_addorcat (destline, destlen, startbuf); | |||
| } | |||
| static void libirc_colorparser_closetags (unsigned int * mask, | |||
| char ** destline, unsigned int * destlen) | |||
| { | |||
| if ( *mask & LIBIRC_COLORPARSER_BOLD ) | |||
| libirc_colorparser_applymask (mask, destline, destlen, LIBIRC_COLORPARSER_BOLD, 0, "[/B]"); | |||
| if ( *mask & LIBIRC_COLORPARSER_UNDERLINE ) | |||
| libirc_colorparser_applymask (mask, destline, destlen, LIBIRC_COLORPARSER_UNDERLINE, 0, "[/U]"); | |||
| if ( *mask & LIBIRC_COLORPARSER_REVERSE ) | |||
| libirc_colorparser_applymask (mask, destline, destlen, LIBIRC_COLORPARSER_REVERSE, 0, "[/I]"); | |||
| if ( *mask & LIBIRC_COLORPARSER_COLOR ) | |||
| libirc_colorparser_applymask (mask, destline, destlen, LIBIRC_COLORPARSER_COLOR, 0, "[/COLOR]"); | |||
| } | |||
| /* | |||
| * IRC to [code] color conversion. Or strip. | |||
| */ | |||
| static char * libirc_colorparser_irc2code (const char * source, int strip) | |||
| { | |||
| unsigned int mask = 0, destlen = 0; | |||
| char * destline = 0, *d = 0; | |||
| const char *p; | |||
| int current_bg = 0; | |||
| /* | |||
| * There will be two passes. First pass calculates the total length of | |||
| * the destination string. The second pass allocates memory for the string, | |||
| * and fills it. | |||
| */ | |||
| while ( destline == 0 ) // destline will be set after the 2nd pass | |||
| { | |||
| if ( destlen > 0 ) | |||
| { | |||
| // This is the 2nd pass; allocate memory. | |||
| if ( (destline = malloc (destlen)) == 0 ) | |||
| return 0; | |||
| d = destline; | |||
| } | |||
| for ( p = source; *p; p++ ) | |||
| { | |||
| switch (*p) | |||
| { | |||
| case 0x02: // bold | |||
| if ( strip ) | |||
| continue; | |||
| libirc_colorparser_applymask (&mask, &d, &destlen, LIBIRC_COLORPARSER_BOLD, "[B]", "[/B]"); | |||
| break; | |||
| case 0x1F: // underline | |||
| if ( strip ) | |||
| continue; | |||
| libirc_colorparser_applymask (&mask, &d, &destlen, LIBIRC_COLORPARSER_UNDERLINE, "[U]", "[/U]"); | |||
| break; | |||
| case 0x16: // reverse | |||
| if ( strip ) | |||
| continue; | |||
| libirc_colorparser_applymask (&mask, &d, &destlen, LIBIRC_COLORPARSER_REVERSE, "[I]", "[/I]"); | |||
| break; | |||
| case 0x0F: // reset colors | |||
| if ( strip ) | |||
| continue; | |||
| libirc_colorparser_closetags (&mask, &d, &destlen); | |||
| break; | |||
| case 0x03: // set color | |||
| if ( isdigit (p[1]) ) | |||
| { | |||
| // Parse | |||
| int bgcolor = -1, color = p[1] - 0x30; | |||
| p++; | |||
| if ( isdigit (p[1]) ) | |||
| { | |||
| color = color * 10 + (p[1] - 0x30); | |||
| p++; | |||
| } | |||
| // If there is a comma, search for the following | |||
| // background color | |||
| if ( p[1] == ',' && isdigit (p[2]) ) | |||
| { | |||
| bgcolor = p[2] - 0x30; | |||
| p += 2; | |||
| if ( isdigit (p[1]) ) | |||
| { | |||
| bgcolor = bgcolor * 10 + (p[1] - 0x30); | |||
| p++; | |||
| } | |||
| } | |||
| // Check for range | |||
| if ( color <= LIBIRC_COLORPARSER_MAXCOLORS | |||
| && bgcolor <= LIBIRC_COLORPARSER_MAXCOLORS ) | |||
| { | |||
| if ( strip ) | |||
| continue; | |||
| if ( bgcolor != -1 ) | |||
| current_bg = bgcolor; | |||
| libirc_colorparser_applycolor (&mask, &d, &destlen, color, current_bg); | |||
| } | |||
| } | |||
| break; | |||
| default: | |||
| if ( destline ) | |||
| *d++ = *p; | |||
| else | |||
| destlen++; | |||
| break; | |||
| } | |||
| } | |||
| // Close all the opened tags | |||
| libirc_colorparser_closetags (&mask, &d, &destlen); | |||
| destlen++; // for 0-terminator | |||
| } | |||
| *d = '\0'; | |||
| return destline; | |||
| } | |||
| static int libirc_colorparser_colorlookup (const char * color) | |||
| { | |||
| int i; | |||
| for ( i = 0; color_replacement_table[i]; i++ ) | |||
| if ( !strcmp (color, color_replacement_table[i]) ) | |||
| return i; | |||
| return -1; | |||
| } | |||
| /* | |||
| * [code] to IRC color conversion. | |||
| */ | |||
| char * irc_color_convert_to_mirc (const char * source) | |||
| { | |||
| unsigned int destlen = 0; | |||
| char * destline = 0, *d = 0; | |||
| const char *p1, *p2, *cur; | |||
| /* | |||
| * There will be two passes. First pass calculates the total length of | |||
| * the destination string. The second pass allocates memory for the string, | |||
| * and fills it. | |||
| */ | |||
| while ( destline == 0 ) // destline will be set after the 2nd pass | |||
| { | |||
| if ( destlen > 0 ) | |||
| { | |||
| // This is the 2nd pass; allocate memory. | |||
| if ( (destline = malloc (destlen)) == 0 ) | |||
| return 0; | |||
| d = destline; | |||
| } | |||
| cur = source; | |||
| while ( (p1 = strchr (cur, '[')) != 0 ) | |||
| { | |||
| const char * replacedval = 0; | |||
| p2 = 0; | |||
| // Check if the closing bracket is available after p1 | |||
| // and the tag length is suitable | |||
| if ( p1[1] != '\0' | |||
| && (p2 = strchr (p1, ']')) != 0 | |||
| && (p2 - p1) > 1 | |||
| && (p2 - p1) < 31 ) | |||
| { | |||
| // Get the tag | |||
| char tagbuf[32]; | |||
| int taglen = p2 - p1 - 1; | |||
| memcpy (tagbuf, p1 + 1, taglen); | |||
| tagbuf[taglen] = '\0'; | |||
| if ( !strcmp (tagbuf, "/COLOR") ) | |||
| replacedval = "\x0F"; | |||
| else if ( strstr (tagbuf, "COLOR=") == tagbuf ) | |||
| { | |||
| int color, bgcolor = -2; | |||
| char * bcol; | |||
| bcol = strchr (tagbuf + 6, '/'); | |||
| if ( bcol ) | |||
| { | |||
| *bcol++ = '\0'; | |||
| bgcolor = libirc_colorparser_colorlookup (bcol); | |||
| } | |||
| color = libirc_colorparser_colorlookup (tagbuf + 6); | |||
| if ( color != -1 && bgcolor == -2 ) | |||
| { | |||
| sprintf (tagbuf, "\x03%02d", color); | |||
| replacedval = tagbuf; | |||
| } | |||
| else if ( color != -1 && bgcolor >= 0 ) | |||
| { | |||
| sprintf (tagbuf, "\x03%02d,%02d", color, bgcolor); | |||
| replacedval = tagbuf; | |||
| } | |||
| } | |||
| else if ( !strcmp (tagbuf, "B") || !strcmp (tagbuf, "/B") ) | |||
| replacedval = "\x02"; | |||
| else if ( !strcmp (tagbuf, "U") || !strcmp (tagbuf, "/U") ) | |||
| replacedval = "\x1F"; | |||
| else if ( !strcmp (tagbuf, "I") || !strcmp (tagbuf, "/I") ) | |||
| replacedval = "\x16"; | |||
| } | |||
| if ( replacedval ) | |||
| { | |||
| // add a part before the tag | |||
| int partlen = p1 - cur; | |||
| if ( destline ) | |||
| { | |||
| memcpy (d, cur, partlen); | |||
| d += partlen; | |||
| } | |||
| else | |||
| destlen += partlen; | |||
| // Add the replacement | |||
| libirc_colorparser_addorcat (&d, &destlen, replacedval); | |||
| // And move the pointer | |||
| cur = p2 + 1; | |||
| } | |||
| else | |||
| { | |||
| // add a whole part before the end tag | |||
| int partlen; | |||
| if ( !p2 ) | |||
| p2 = cur + strlen(cur); | |||
| partlen = p2 - cur + 1; | |||
| if ( destline ) | |||
| { | |||
| memcpy (d, cur, partlen); | |||
| d += partlen; | |||
| } | |||
| else | |||
| destlen += partlen; | |||
| // And move the pointer | |||
| cur = p2 + 1; | |||
| } | |||
| } | |||
| // Add the rest of string | |||
| libirc_colorparser_addorcat (&d, &destlen, cur); | |||
| destlen++; // for 0-terminator | |||
| } | |||
| *d = '\0'; | |||
| return destline; | |||
| } | |||
| char * irc_color_strip_from_mirc (const char * message) | |||
| { | |||
| return libirc_colorparser_irc2code (message, 1); | |||
| } | |||
| char * irc_color_convert_from_mirc (const char * message) | |||
| { | |||
| return libirc_colorparser_irc2code (message, 0); | |||
| } | |||
| @@ -0,0 +1,892 @@ | |||
| /* | |||
| * 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 LIBIRC_DCC_CHAT 1 | |||
| #define LIBIRC_DCC_SENDFILE 2 | |||
| #define LIBIRC_DCC_RECVFILE 3 | |||
| static irc_dcc_session_t * libirc_find_dcc_session (irc_session_t * session, irc_dcc_t dccid, int lock_list) | |||
| { | |||
| irc_dcc_session_t * s, *found = 0; | |||
| if ( lock_list ) | |||
| libirc_mutex_lock (&session->mutex_dcc); | |||
| for ( s = session->dcc_sessions; s; s = s->next ) | |||
| { | |||
| if ( s->id == dccid ) | |||
| { | |||
| found = s; | |||
| break; | |||
| } | |||
| } | |||
| if ( found == 0 && lock_list ) | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| return found; | |||
| } | |||
| static void libirc_dcc_destroy_nolock (irc_session_t * session, irc_dcc_t dccid) | |||
| { | |||
| irc_dcc_session_t * dcc = libirc_find_dcc_session (session, dccid, 0); | |||
| if ( dcc ) | |||
| { | |||
| if ( dcc->sock >= 0 ) | |||
| socket_close (&dcc->sock); | |||
| dcc->state = LIBIRC_STATE_REMOVED; | |||
| } | |||
| } | |||
| static void libirc_remove_dcc_session (irc_session_t * session, irc_dcc_session_t * dcc, int lock_list) | |||
| { | |||
| if ( dcc->sock >= 0 ) | |||
| socket_close (&dcc->sock); | |||
| if ( dcc->dccsend_file_fp ) | |||
| fclose (dcc->dccsend_file_fp); | |||
| dcc->dccsend_file_fp = 0; | |||
| libirc_mutex_destroy (&dcc->mutex_outbuf); | |||
| if ( lock_list ) | |||
| libirc_mutex_lock (&session->mutex_dcc); | |||
| if ( session->dcc_sessions != dcc ) | |||
| { | |||
| irc_dcc_session_t * s; | |||
| for ( s = session->dcc_sessions; s; s = s->next ) | |||
| { | |||
| if ( s->next == dcc ) | |||
| { | |||
| s->next = dcc->next; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| else | |||
| session->dcc_sessions = dcc->next; | |||
| if ( lock_list ) | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| free (dcc); | |||
| } | |||
| static void libirc_dcc_add_descriptors (irc_session_t * ircsession, fd_set *in_set, fd_set *out_set, int * maxfd) | |||
| { | |||
| irc_dcc_session_t * dcc, *dcc_next; | |||
| time_t now = time (0); | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| // Preprocessing DCC list: | |||
| // - ask DCC send callbacks for data; | |||
| // - remove unused DCC structures | |||
| for ( dcc = ircsession->dcc_sessions; dcc; dcc = dcc_next ) | |||
| { | |||
| dcc_next = dcc->next; | |||
| // Remove timed-out sessions | |||
| if ( (dcc->state == LIBIRC_STATE_CONNECTING | |||
| || dcc->state == LIBIRC_STATE_INIT | |||
| || dcc->state == LIBIRC_STATE_LISTENING) | |||
| && now - dcc->timeout > ircsession->dcc_timeout ) | |||
| { | |||
| // Inform the caller about DCC timeout. | |||
| // Do not inform when state is LIBIRC_STATE_INIT - session | |||
| // was initiated from someone else, and callbacks aren't set yet. | |||
| if ( dcc->state != LIBIRC_STATE_INIT ) | |||
| { | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| if ( dcc->cb ) | |||
| (*dcc->cb)(ircsession, dcc->id, LIBIRC_ERR_TIMEOUT, dcc->ctx, 0, 0); | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| } | |||
| libirc_remove_dcc_session (ircsession, dcc, 0); | |||
| } | |||
| /* | |||
| * If we're sending file, and the output buffer is empty, we need | |||
| * to provide some data. | |||
| */ | |||
| if ( dcc->state == LIBIRC_STATE_CONNECTED | |||
| && dcc->dccmode == LIBIRC_DCC_SENDFILE | |||
| && dcc->dccsend_file_fp | |||
| && dcc->outgoing_offset == 0 ) | |||
| { | |||
| int len = fread (dcc->outgoing_buf, 1, sizeof (dcc->outgoing_buf), dcc->dccsend_file_fp); | |||
| if ( len <= 0 ) | |||
| { | |||
| int err = (len < 0 ? LIBIRC_ERR_READ : 0); | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| (*dcc->cb)(ircsession, dcc->id, err, dcc->ctx, 0, 0); | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| libirc_dcc_destroy_nolock (ircsession, dcc->id); | |||
| } | |||
| else | |||
| dcc->outgoing_offset = len; | |||
| } | |||
| // Clean up unused sessions | |||
| if ( dcc->state == LIBIRC_STATE_REMOVED ) | |||
| libirc_remove_dcc_session (ircsession, dcc, 0); | |||
| } | |||
| for ( dcc = ircsession->dcc_sessions; dcc; dcc = dcc->next ) | |||
| { | |||
| switch (dcc->state) | |||
| { | |||
| case LIBIRC_STATE_LISTENING: | |||
| // While listening, only in_set descriptor should be set | |||
| libirc_add_to_set (dcc->sock, in_set, maxfd); | |||
| break; | |||
| case LIBIRC_STATE_CONNECTING: | |||
| // While connection, only out_set descriptor should be set | |||
| libirc_add_to_set (dcc->sock, out_set, maxfd); | |||
| break; | |||
| case LIBIRC_STATE_CONNECTED: | |||
| // Add input descriptor if there is space in input buffer | |||
| // and it is DCC chat (during DCC send, there is nothing to recv) | |||
| if ( dcc->incoming_offset < sizeof(dcc->incoming_buf) - 1 ) | |||
| libirc_add_to_set (dcc->sock, in_set, maxfd); | |||
| // Add output descriptor if there is something in output buffer | |||
| libirc_mutex_lock (&dcc->mutex_outbuf); | |||
| if ( dcc->outgoing_offset > 0 ) | |||
| libirc_add_to_set (dcc->sock, out_set, maxfd); | |||
| libirc_mutex_unlock (&dcc->mutex_outbuf); | |||
| break; | |||
| case LIBIRC_STATE_CONFIRM_SIZE: | |||
| /* | |||
| * If we're receiving file, then WE should confirm the transferred | |||
| * part (so we have to sent data). But if we're sending the file, | |||
| * then RECEIVER should confirm the packet, so we have to receive | |||
| * data. | |||
| * | |||
| * We don't need to LOCK_DCC_OUTBUF - during file transfer, buffers | |||
| * can't change asynchronously. | |||
| */ | |||
| if ( dcc->dccmode == LIBIRC_DCC_RECVFILE && dcc->outgoing_offset > 0 ) | |||
| libirc_add_to_set (dcc->sock, out_set, maxfd); | |||
| if ( dcc->dccmode == LIBIRC_DCC_SENDFILE && dcc->incoming_offset < 4 ) | |||
| libirc_add_to_set (dcc->sock, in_set, maxfd); | |||
| } | |||
| } | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| } | |||
| static void libirc_dcc_process_descriptors (irc_session_t * ircsession, fd_set *in_set, fd_set *out_set) | |||
| { | |||
| irc_dcc_session_t * dcc; | |||
| /* | |||
| * We need to use such a complex scheme here, because on every callback | |||
| * a number of DCC sessions could be destroyed. | |||
| */ | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| for ( dcc = ircsession->dcc_sessions; dcc; dcc = dcc->next ) | |||
| { | |||
| if ( dcc->state == LIBIRC_STATE_LISTENING | |||
| && FD_ISSET (dcc->sock, in_set) ) | |||
| { | |||
| socklen_t len = sizeof(dcc->remote_addr); | |||
| int nsock, err = 0; | |||
| // New connection is available; accept it. | |||
| if ( socket_accept (&dcc->sock, &nsock, (struct sockaddr *) &dcc->remote_addr, &len) ) | |||
| err = LIBIRC_ERR_ACCEPT; | |||
| // On success, change the active socket and change the state | |||
| if ( err == 0 ) | |||
| { | |||
| // close the listen socket, and replace it by a newly | |||
| // accepted | |||
| socket_close (&dcc->sock); | |||
| dcc->sock = nsock; | |||
| dcc->state = LIBIRC_STATE_CONNECTED; | |||
| } | |||
| // If this is DCC chat, inform the caller about accept() | |||
| // success or failure. | |||
| // Otherwise (DCC send) there is no reason. | |||
| if ( dcc->dccmode == LIBIRC_DCC_CHAT ) | |||
| { | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| (*dcc->cb)(ircsession, dcc->id, err, dcc->ctx, 0, 0); | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| } | |||
| if ( err ) | |||
| libirc_dcc_destroy_nolock (ircsession, dcc->id); | |||
| } | |||
| if ( dcc->state == LIBIRC_STATE_CONNECTING | |||
| && FD_ISSET (dcc->sock, out_set) ) | |||
| { | |||
| // Now we have to determine whether the socket is connected | |||
| // or the connect is failed | |||
| struct sockaddr_in saddr; | |||
| socklen_t slen = sizeof(saddr); | |||
| int err = 0; | |||
| if ( getpeername (dcc->sock, (struct sockaddr*)&saddr, &slen) < 0 ) | |||
| err = LIBIRC_ERR_CONNECT; | |||
| // On success, change the state | |||
| if ( err == 0 ) | |||
| dcc->state = LIBIRC_STATE_CONNECTED; | |||
| // If this is DCC chat, inform the caller about connect() | |||
| // success or failure. | |||
| // Otherwise (DCC send) there is no reason. | |||
| if ( dcc->dccmode == LIBIRC_DCC_CHAT ) | |||
| { | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| (*dcc->cb)(ircsession, dcc->id, err, dcc->ctx, 0, 0); | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| } | |||
| if ( err ) | |||
| libirc_dcc_destroy_nolock (ircsession, dcc->id); | |||
| } | |||
| if ( dcc->state == LIBIRC_STATE_CONNECTED | |||
| || dcc->state == LIBIRC_STATE_CONFIRM_SIZE ) | |||
| { | |||
| if ( FD_ISSET (dcc->sock, in_set) ) | |||
| { | |||
| int length, offset = 0, err = 0; | |||
| unsigned int amount = sizeof (dcc->incoming_buf) - dcc->incoming_offset; | |||
| length = socket_recv (&dcc->sock, dcc->incoming_buf + dcc->incoming_offset, amount); | |||
| if ( length < 0 ) | |||
| { | |||
| err = LIBIRC_ERR_READ; | |||
| } | |||
| else if ( length == 0 ) | |||
| { | |||
| err = LIBIRC_ERR_CLOSED; | |||
| if ( dcc->dccsend_file_fp ) | |||
| { | |||
| fclose (dcc->dccsend_file_fp); | |||
| dcc->dccsend_file_fp = 0; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| dcc->incoming_offset += length; | |||
| if ( dcc->dccmode != LIBIRC_DCC_CHAT ) | |||
| offset = dcc->incoming_offset; | |||
| else | |||
| offset = libirc_findcrorlf (dcc->incoming_buf, dcc->incoming_offset); | |||
| /* | |||
| * In LIBIRC_STATE_CONFIRM_SIZE state we don't call any | |||
| * callbacks (except there is an error). We just receive | |||
| * the data, and compare it with the amount sent. | |||
| */ | |||
| if ( dcc->state == LIBIRC_STATE_CONFIRM_SIZE ) | |||
| { | |||
| if ( dcc->dccmode != LIBIRC_DCC_SENDFILE ) | |||
| abort(); | |||
| if ( dcc->incoming_offset == 4 ) | |||
| { | |||
| // The order is big-endian | |||
| const unsigned char * bptr = (const unsigned char *) dcc->incoming_buf; | |||
| unsigned int received_size = (bptr[0] << 24) | (bptr[1] << 16) | (bptr[2] << 8) | bptr[3]; | |||
| // Sent size confirmed | |||
| if ( dcc->file_confirm_offset == received_size ) | |||
| { | |||
| dcc->state = LIBIRC_STATE_CONNECTED; | |||
| dcc->incoming_offset = 0; | |||
| } | |||
| else | |||
| err = LIBIRC_ERR_WRITE; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| /* | |||
| * If it is DCC_CHAT, we send a 0-terminated string | |||
| * (which is smaller than offset). Otherwise we send | |||
| * a full buffer. | |||
| */ | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| if ( dcc->dccmode != LIBIRC_DCC_CHAT ) | |||
| { | |||
| if ( dcc->dccmode != LIBIRC_DCC_RECVFILE ) | |||
| abort(); | |||
| (*dcc->cb)(ircsession, dcc->id, err, dcc->ctx, dcc->incoming_buf, offset); | |||
| /* | |||
| * If the session is not terminated in callback, | |||
| * put the sent amount into the sent_packet_size_net_byteorder | |||
| */ | |||
| if ( dcc->state != LIBIRC_STATE_REMOVED ) | |||
| { | |||
| dcc->state = LIBIRC_STATE_CONFIRM_SIZE; | |||
| dcc->file_confirm_offset += offset; | |||
| // Store as big endian | |||
| dcc->outgoing_buf[0] = (char) dcc->file_confirm_offset >> 24; | |||
| dcc->outgoing_buf[1] = (char) dcc->file_confirm_offset >> 16; | |||
| dcc->outgoing_buf[2] = (char) dcc->file_confirm_offset >> 8; | |||
| dcc->outgoing_buf[3] = (char) dcc->file_confirm_offset; | |||
| dcc->outgoing_offset = 4; | |||
| } | |||
| } | |||
| else | |||
| (*dcc->cb)(ircsession, dcc->id, err, dcc->ctx, dcc->incoming_buf, strlen(dcc->incoming_buf)); | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| if ( dcc->incoming_offset - offset > 0 ) | |||
| memmove (dcc->incoming_buf, dcc->incoming_buf + offset, dcc->incoming_offset - offset); | |||
| dcc->incoming_offset -= offset; | |||
| } | |||
| } | |||
| /* | |||
| * If error arises somewhere above, we inform the caller | |||
| * of failure, and destroy this session. | |||
| */ | |||
| if ( err ) | |||
| { | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| (*dcc->cb)(ircsession, dcc->id, err, dcc->ctx, 0, 0); | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| libirc_dcc_destroy_nolock (ircsession, dcc->id); | |||
| } | |||
| } | |||
| /* | |||
| * Session might be closed (with sock = -1) after the in_set | |||
| * processing, so before out_set processing we should check | |||
| * for this case | |||
| */ | |||
| if ( dcc->state == LIBIRC_STATE_REMOVED ) | |||
| continue; | |||
| /* | |||
| * Write bit set - we can send() something, and it won't block. | |||
| */ | |||
| if ( FD_ISSET (dcc->sock, out_set) ) | |||
| { | |||
| int length, offset, err = 0; | |||
| /* | |||
| * Because in some cases outgoing_buf could be changed | |||
| * asynchronously (by another thread), we should lock | |||
| * it. | |||
| */ | |||
| libirc_mutex_lock (&dcc->mutex_outbuf); | |||
| offset = dcc->outgoing_offset; | |||
| if ( offset > 0 ) | |||
| { | |||
| length = socket_send (&dcc->sock, dcc->outgoing_buf, offset); | |||
| if ( length < 0 ) | |||
| err = LIBIRC_ERR_WRITE; | |||
| else if ( length == 0 ) | |||
| err = LIBIRC_ERR_CLOSED; | |||
| else | |||
| { | |||
| /* | |||
| * If this was DCC_SENDFILE, and we just sent a packet, | |||
| * change the state to wait for confirmation (and store | |||
| * sent packet size) | |||
| */ | |||
| if ( dcc->state == LIBIRC_STATE_CONNECTED | |||
| && dcc->dccmode == LIBIRC_DCC_SENDFILE ) | |||
| { | |||
| dcc->file_confirm_offset += offset; | |||
| dcc->state = LIBIRC_STATE_CONFIRM_SIZE; | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| libirc_mutex_unlock (&dcc->mutex_outbuf); | |||
| (*dcc->cb)(ircsession, dcc->id, err, dcc->ctx, 0, offset); | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| libirc_mutex_lock (&dcc->mutex_outbuf); | |||
| } | |||
| if ( dcc->outgoing_offset - length > 0 ) | |||
| memmove (dcc->outgoing_buf, dcc->outgoing_buf + length, dcc->outgoing_offset - length); | |||
| dcc->outgoing_offset -= length; | |||
| /* | |||
| * If we just sent the confirmation data, change state | |||
| * back. | |||
| */ | |||
| if ( dcc->state == LIBIRC_STATE_CONFIRM_SIZE | |||
| && dcc->dccmode == LIBIRC_DCC_RECVFILE | |||
| && dcc->outgoing_offset == 0 ) | |||
| { | |||
| /* | |||
| * If the file is already received, we should inform | |||
| * the caller, and close the session. | |||
| */ | |||
| if ( dcc->received_file_size == dcc->file_confirm_offset ) | |||
| { | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| libirc_mutex_unlock (&dcc->mutex_outbuf); | |||
| (*dcc->cb)(ircsession, dcc->id, 0, dcc->ctx, 0, 0); | |||
| libirc_dcc_destroy_nolock (ircsession, dcc->id); | |||
| } | |||
| else | |||
| { | |||
| /* Continue to receive the file */ | |||
| dcc->state = LIBIRC_STATE_CONNECTED; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| libirc_mutex_unlock (&dcc->mutex_outbuf); | |||
| /* | |||
| * If error arises somewhere above, we inform the caller | |||
| * of failure, and destroy this session. | |||
| */ | |||
| if ( err ) | |||
| { | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| (*dcc->cb)(ircsession, dcc->id, err, dcc->ctx, 0, 0); | |||
| libirc_mutex_lock (&ircsession->mutex_dcc); | |||
| libirc_dcc_destroy_nolock (ircsession, dcc->id); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| libirc_mutex_unlock (&ircsession->mutex_dcc); | |||
| } | |||
| static int libirc_new_dcc_session (irc_session_t * session, unsigned long ip, unsigned short port, int dccmode, void * ctx, irc_dcc_session_t ** pdcc) | |||
| { | |||
| irc_dcc_session_t * dcc = malloc (sizeof(irc_dcc_session_t)); | |||
| if ( !dcc ) | |||
| return LIBIRC_ERR_NOMEM; | |||
| // setup | |||
| memset (dcc, 0, sizeof(irc_dcc_session_t)); | |||
| dcc->dccsend_file_fp = 0; | |||
| if ( libirc_mutex_init (&dcc->mutex_outbuf) ) | |||
| goto cleanup_exit_error; | |||
| if ( socket_create (PF_INET, SOCK_STREAM, &dcc->sock) ) | |||
| goto cleanup_exit_error; | |||
| if ( !ip ) | |||
| { | |||
| unsigned long arg = 1; | |||
| setsockopt (dcc->sock, SOL_SOCKET, SO_REUSEADDR, (char*)&arg, sizeof(arg)); | |||
| #if defined (ENABLE_IPV6) | |||
| if ( session->flags & SESSIONFL_USES_IPV6 ) | |||
| { | |||
| struct sockaddr_in6 saddr6; | |||
| memset (&saddr6, 0, sizeof(saddr6)); | |||
| saddr6.sin6_family = AF_INET6; | |||
| memcpy (&saddr6.sin6_addr, &session->local_addr6, sizeof(session->local_addr6)); | |||
| saddr6.sin6_port = htons (0); | |||
| if ( bind (dcc->sock, (struct sockaddr *) &saddr6, sizeof(saddr6)) < 0 ) | |||
| goto cleanup_exit_error; | |||
| } | |||
| else | |||
| #endif | |||
| { | |||
| struct sockaddr_in saddr; | |||
| memset (&saddr, 0, sizeof(saddr)); | |||
| saddr.sin_family = AF_INET; | |||
| memcpy (&saddr.sin_addr, &session->local_addr, sizeof(session->local_addr)); | |||
| saddr.sin_port = htons (0); | |||
| if ( bind (dcc->sock, (struct sockaddr *) &saddr, sizeof(saddr)) < 0 ) | |||
| goto cleanup_exit_error; | |||
| } | |||
| if ( listen (dcc->sock, 5) < 0 ) | |||
| goto cleanup_exit_error; | |||
| dcc->state = LIBIRC_STATE_LISTENING; | |||
| } | |||
| else | |||
| { | |||
| // make socket non-blocking, so connect() call won't block | |||
| if ( socket_make_nonblocking (&dcc->sock) ) | |||
| goto cleanup_exit_error; | |||
| memset (&dcc->remote_addr, 0, sizeof(dcc->remote_addr)); | |||
| dcc->remote_addr.sin_family = AF_INET; | |||
| dcc->remote_addr.sin_addr.s_addr = htonl (ip); // what idiot came up with idea to send IP address in host-byteorder? | |||
| dcc->remote_addr.sin_port = htons(port); | |||
| dcc->state = LIBIRC_STATE_INIT; | |||
| } | |||
| dcc->dccmode = dccmode; | |||
| dcc->ctx = ctx; | |||
| time (&dcc->timeout); | |||
| // and store it | |||
| libirc_mutex_lock (&session->mutex_dcc); | |||
| dcc->id = session->dcc_last_id++; | |||
| dcc->next = session->dcc_sessions; | |||
| session->dcc_sessions = dcc; | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| *pdcc = dcc; | |||
| return 0; | |||
| cleanup_exit_error: | |||
| if ( dcc->sock >= 0 ) | |||
| socket_close (&dcc->sock); | |||
| free (dcc); | |||
| return LIBIRC_ERR_SOCKET; | |||
| } | |||
| int irc_dcc_destroy (irc_session_t * session, irc_dcc_t dccid) | |||
| { | |||
| // This function doesn't actually destroy the session; it just changes | |||
| // its state to "removed" and closes the socket. The memory is actually | |||
| // freed after the processing loop. | |||
| irc_dcc_session_t * dcc = libirc_find_dcc_session (session, dccid, 1); | |||
| if ( !dcc ) | |||
| return 1; | |||
| if ( dcc->sock >= 0 ) | |||
| socket_close (&dcc->sock); | |||
| dcc->state = LIBIRC_STATE_REMOVED; | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| return 0; | |||
| } | |||
| int irc_dcc_chat (irc_session_t * session, void * ctx, const char * nick, irc_dcc_callback_t callback, irc_dcc_t * dccid) | |||
| { | |||
| struct sockaddr_in saddr; | |||
| socklen_t len = sizeof(saddr); | |||
| char cmdbuf[128], notbuf[128]; | |||
| irc_dcc_session_t * dcc; | |||
| int err; | |||
| if ( session->state != LIBIRC_STATE_CONNECTED ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_STATE; | |||
| return 1; | |||
| } | |||
| err = libirc_new_dcc_session (session, 0, 0, LIBIRC_DCC_CHAT, ctx, &dcc); | |||
| if ( err ) | |||
| { | |||
| session->lasterror = err; | |||
| return 1; | |||
| } | |||
| if ( getsockname (dcc->sock, (struct sockaddr*) &saddr, &len) < 0 ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_SOCKET; | |||
| libirc_remove_dcc_session (session, dcc, 1); | |||
| return 1; | |||
| } | |||
| sprintf (notbuf, "DCC Chat (%s)", inet_ntoa (saddr.sin_addr)); | |||
| sprintf (cmdbuf, "DCC CHAT chat %lu %u", (unsigned long) ntohl (saddr.sin_addr.s_addr), ntohs (saddr.sin_port)); | |||
| if ( irc_cmd_notice (session, nick, notbuf) | |||
| || irc_cmd_ctcp_request (session, nick, cmdbuf) ) | |||
| { | |||
| libirc_remove_dcc_session (session, dcc, 1); | |||
| return 1; | |||
| } | |||
| *dccid = dcc->id; | |||
| dcc->cb = callback; | |||
| dcc->dccmode = LIBIRC_DCC_CHAT; | |||
| return 0; | |||
| } | |||
| int irc_dcc_msg (irc_session_t * session, irc_dcc_t dccid, const char * text) | |||
| { | |||
| irc_dcc_session_t * dcc = libirc_find_dcc_session (session, dccid, 1); | |||
| if ( !dcc ) | |||
| return 1; | |||
| if ( dcc->dccmode != LIBIRC_DCC_CHAT ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_INVAL; | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| return 1; | |||
| } | |||
| if ( (strlen(text) + 2) >= (sizeof(dcc->outgoing_buf) - dcc->outgoing_offset) ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_NOMEM; | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| return 1; | |||
| } | |||
| libirc_mutex_lock (&dcc->mutex_outbuf); | |||
| strcpy (dcc->outgoing_buf + dcc->outgoing_offset, text); | |||
| dcc->outgoing_offset += strlen (text); | |||
| dcc->outgoing_buf[dcc->outgoing_offset++] = 0x0D; | |||
| dcc->outgoing_buf[dcc->outgoing_offset++] = 0x0A; | |||
| libirc_mutex_unlock (&dcc->mutex_outbuf); | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| return 0; | |||
| } | |||
| static void libirc_dcc_request (irc_session_t * session, const char * nick, const char * req) | |||
| { | |||
| char filenamebuf[256]; | |||
| unsigned long ip, size; | |||
| unsigned short port; | |||
| if ( sscanf (req, "DCC CHAT chat %lu %hu", &ip, &port) == 2 ) | |||
| { | |||
| if ( session->callbacks.event_dcc_chat_req ) | |||
| { | |||
| irc_dcc_session_t * dcc; | |||
| int err = libirc_new_dcc_session (session, ip, port, LIBIRC_DCC_CHAT, 0, &dcc); | |||
| if ( err ) | |||
| { | |||
| session->lasterror = err; | |||
| return; | |||
| } | |||
| (*session->callbacks.event_dcc_chat_req) (session, | |||
| nick, | |||
| inet_ntoa (dcc->remote_addr.sin_addr), | |||
| dcc->id); | |||
| } | |||
| return; | |||
| } | |||
| else if ( sscanf (req, "DCC SEND %s %lu %hu %lu", filenamebuf, &ip, &port, &size) == 4 ) | |||
| { | |||
| if ( session->callbacks.event_dcc_send_req ) | |||
| { | |||
| irc_dcc_session_t * dcc; | |||
| int err = libirc_new_dcc_session (session, ip, port, LIBIRC_DCC_RECVFILE, 0, &dcc); | |||
| if ( err ) | |||
| { | |||
| session->lasterror = err; | |||
| return; | |||
| } | |||
| (*session->callbacks.event_dcc_send_req) (session, | |||
| nick, | |||
| inet_ntoa (dcc->remote_addr.sin_addr), | |||
| filenamebuf, | |||
| size, | |||
| dcc->id); | |||
| dcc->received_file_size = size; | |||
| } | |||
| return; | |||
| } | |||
| #if defined (ENABLE_DEBUG) | |||
| fprintf (stderr, "BUG: Unhandled DCC message: %s\n", req); | |||
| abort(); | |||
| #endif | |||
| } | |||
| int irc_dcc_accept (irc_session_t * session, irc_dcc_t dccid, void * ctx, irc_dcc_callback_t callback) | |||
| { | |||
| irc_dcc_session_t * dcc = libirc_find_dcc_session (session, dccid, 1); | |||
| if ( !dcc ) | |||
| return 1; | |||
| if ( dcc->state != LIBIRC_STATE_INIT ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_STATE; | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| return 1; | |||
| } | |||
| dcc->cb = callback; | |||
| dcc->ctx = ctx; | |||
| // Initiate the connect | |||
| if ( socket_connect (&dcc->sock, (struct sockaddr *) &dcc->remote_addr, sizeof(dcc->remote_addr)) ) | |||
| { | |||
| libirc_dcc_destroy_nolock (session, dccid); | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| session->lasterror = LIBIRC_ERR_CONNECT; | |||
| return 1; | |||
| } | |||
| dcc->state = LIBIRC_STATE_CONNECTING; | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| return 0; | |||
| } | |||
| int irc_dcc_decline (irc_session_t * session, irc_dcc_t dccid) | |||
| { | |||
| irc_dcc_session_t * dcc = libirc_find_dcc_session (session, dccid, 1); | |||
| if ( !dcc ) | |||
| return 1; | |||
| if ( dcc->state != LIBIRC_STATE_INIT ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_STATE; | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| return 1; | |||
| } | |||
| libirc_dcc_destroy_nolock (session, dccid); | |||
| libirc_mutex_unlock (&session->mutex_dcc); | |||
| return 0; | |||
| } | |||
| int irc_dcc_sendfile (irc_session_t * session, void * ctx, const char * nick, const char * filename, irc_dcc_callback_t callback, irc_dcc_t * dccid) | |||
| { | |||
| struct sockaddr_in saddr; | |||
| socklen_t len = sizeof(saddr); | |||
| char cmdbuf[128], notbuf[128]; | |||
| irc_dcc_session_t * dcc; | |||
| const char * p; | |||
| int err; | |||
| long filesize; | |||
| if ( !session || !dccid || !filename || !callback ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_INVAL; | |||
| return 1; | |||
| } | |||
| if ( session->state != LIBIRC_STATE_CONNECTED ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_STATE; | |||
| return 1; | |||
| } | |||
| if ( (err = libirc_new_dcc_session (session, 0, 0, LIBIRC_DCC_SENDFILE, ctx, &dcc)) != 0 ) | |||
| { | |||
| session->lasterror = err; | |||
| return 1; | |||
| } | |||
| if ( (dcc->dccsend_file_fp = fopen (filename, "rb")) == 0 ) | |||
| { | |||
| libirc_remove_dcc_session (session, dcc, 1); | |||
| session->lasterror = LIBIRC_ERR_OPENFILE; | |||
| return 1; | |||
| } | |||
| /* Get file length */ | |||
| if ( fseek (dcc->dccsend_file_fp, 0, SEEK_END) | |||
| || (filesize = ftell (dcc->dccsend_file_fp)) == -1 | |||
| || fseek (dcc->dccsend_file_fp, 0, SEEK_SET) ) | |||
| { | |||
| libirc_remove_dcc_session (session, dcc, 1); | |||
| session->lasterror = LIBIRC_ERR_NODCCSEND; | |||
| return 1; | |||
| } | |||
| if ( getsockname (dcc->sock, (struct sockaddr*) &saddr, &len) < 0 ) | |||
| { | |||
| libirc_remove_dcc_session (session, dcc, 1); | |||
| session->lasterror = LIBIRC_ERR_SOCKET; | |||
| return 1; | |||
| } | |||
| // Remove path from the filename | |||
| if ( (p = strrchr (filename, '\\')) == 0 | |||
| && (p = strrchr (filename, '/')) == 0 ) | |||
| p = filename; | |||
| else | |||
| p++; // skip directory slash | |||
| sprintf (notbuf, "DCC Send %s (%s)", p, inet_ntoa (saddr.sin_addr)); | |||
| sprintf (cmdbuf, "DCC SEND %s %lu %u %ld", p, (unsigned long) ntohl (saddr.sin_addr.s_addr), ntohs (saddr.sin_port), filesize); | |||
| if ( irc_cmd_notice (session, nick, notbuf) | |||
| || irc_cmd_ctcp_request (session, nick, cmdbuf) ) | |||
| { | |||
| libirc_remove_dcc_session (session, dcc, 1); | |||
| return 1; | |||
| } | |||
| *dccid = dcc->id; | |||
| dcc->cb = callback; | |||
| return 0; | |||
| } | |||
| @@ -0,0 +1,54 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| static const char * libirc_strerror[LIBIRC_ERR_MAX] = | |||
| { | |||
| "No error", | |||
| "Invalid argument", | |||
| "Host not resolved", | |||
| "Socket error", | |||
| "Could not connect", | |||
| "Remote connection closed", | |||
| "Out of memory", | |||
| "Could not accept new connection", | |||
| "Object not found", | |||
| "Could not DCC send this object", | |||
| "Read error", | |||
| "Write error", | |||
| "Illegal operation for this state", | |||
| "Timeout error", | |||
| "Could not open file", | |||
| "IRC session terminated", | |||
| "IPv6 not supported", | |||
| "SSL not supported", | |||
| "SSL initialization failed", | |||
| "SSL connection failed", | |||
| "SSL certificate verify failed", | |||
| }; | |||
| int irc_errno (irc_session_t * session) | |||
| { | |||
| return session->lasterror; | |||
| } | |||
| const char * irc_strerror (int ircerrno) | |||
| { | |||
| if ( ircerrno >= 0 && ircerrno < LIBIRC_ERR_MAX ) | |||
| return libirc_strerror[ircerrno]; | |||
| else | |||
| return "Invalid irc_errno value"; | |||
| } | |||
| @@ -0,0 +1,156 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #if !defined (_WIN32) | |||
| #include "config.h" | |||
| #include <stdio.h> | |||
| #include <stdarg.h> | |||
| #include <unistd.h> | |||
| #include <string.h> | |||
| #include <stdlib.h> | |||
| #include <sys/stat.h> | |||
| #include <sys/types.h> | |||
| #include <sys/socket.h> | |||
| #include <netdb.h> | |||
| #include <arpa/inet.h> | |||
| #include <netinet/in.h> | |||
| #include <fcntl.h> | |||
| #include <errno.h> | |||
| #include <ctype.h> | |||
| #include <time.h> | |||
| #if defined (ENABLE_THREADS) | |||
| #include <pthread.h> | |||
| typedef pthread_mutex_t port_mutex_t; | |||
| #if !defined (PTHREAD_MUTEX_RECURSIVE) && defined (PTHREAD_MUTEX_RECURSIVE_NP) | |||
| #define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP | |||
| #endif | |||
| #endif | |||
| #else | |||
| #include <winsock2.h> | |||
| #include <ws2tcpip.h> | |||
| #include <windows.h> | |||
| #include <time.h> | |||
| #include <stdio.h> | |||
| #include <stdarg.h> | |||
| #include <string.h> | |||
| #include <stdlib.h> | |||
| #include <sys/stat.h> | |||
| #if defined (ENABLE_THREADS) | |||
| typedef CRITICAL_SECTION port_mutex_t; | |||
| #endif | |||
| #define inline | |||
| #define snprintf _snprintf | |||
| #define vsnprintf _vsnprintf | |||
| #define strncasecmp _strnicmp | |||
| #endif | |||
| #if defined (ENABLE_SSL) | |||
| #include <openssl/ssl.h> | |||
| #include <openssl/err.h> | |||
| #include <openssl/rand.h> | |||
| #endif | |||
| #if defined (ENABLE_THREADS) | |||
| static inline int libirc_mutex_init (port_mutex_t * mutex) | |||
| { | |||
| #if defined (_WIN32) | |||
| InitializeCriticalSection (mutex); | |||
| return 0; | |||
| #elif defined (PTHREAD_MUTEX_RECURSIVE) | |||
| pthread_mutexattr_t attr; | |||
| return (pthread_mutexattr_init (&attr) | |||
| || pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE) | |||
| || pthread_mutex_init (mutex, &attr)); | |||
| #else /* !defined (PTHREAD_MUTEX_RECURSIVE) */ | |||
| return pthread_mutex_init (mutex, 0); | |||
| #endif /* defined (_WIN32) */ | |||
| } | |||
| static inline void libirc_mutex_destroy (port_mutex_t * mutex) | |||
| { | |||
| #if defined (_WIN32) | |||
| DeleteCriticalSection (mutex); | |||
| #else | |||
| pthread_mutex_destroy (mutex); | |||
| #endif | |||
| } | |||
| static inline void libirc_mutex_lock (port_mutex_t * mutex) | |||
| { | |||
| #if defined (_WIN32) | |||
| EnterCriticalSection (mutex); | |||
| #else | |||
| pthread_mutex_lock (mutex); | |||
| #endif | |||
| } | |||
| static inline void libirc_mutex_unlock (port_mutex_t * mutex) | |||
| { | |||
| #if defined (_WIN32) | |||
| LeaveCriticalSection (mutex); | |||
| #else | |||
| pthread_mutex_unlock (mutex); | |||
| #endif | |||
| } | |||
| #else | |||
| typedef void * port_mutex_t; | |||
| static inline int libirc_mutex_init (port_mutex_t * mutex) { return 0; } | |||
| static inline void libirc_mutex_destroy (port_mutex_t * mutex) {} | |||
| static inline void libirc_mutex_lock (port_mutex_t * mutex) {} | |||
| static inline void libirc_mutex_unlock (port_mutex_t * mutex) {} | |||
| #endif | |||
| /* | |||
| * Stub for WIN32 dll to initialize winsock API | |||
| */ | |||
| #if defined (WIN32_DLL) | |||
| BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved) | |||
| { | |||
| WORD wVersionRequested = MAKEWORD (1, 1); | |||
| WSADATA wsaData; | |||
| switch(fdwReason) | |||
| { | |||
| case DLL_PROCESS_ATTACH: | |||
| if ( WSAStartup (wVersionRequested, &wsaData) != 0 ) | |||
| return FALSE; | |||
| DisableThreadLibraryCalls (hinstDll); | |||
| break; | |||
| case DLL_PROCESS_DETACH: | |||
| WSACleanup(); | |||
| break; | |||
| } | |||
| return TRUE; | |||
| } | |||
| #endif | |||
| @@ -0,0 +1,159 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| /* | |||
| * The sockets interface was moved out to simplify going OpenSSL integration. | |||
| */ | |||
| #if !defined (_WIN32) | |||
| #include <sys/socket.h> | |||
| #include <netdb.h> | |||
| #include <arpa/inet.h> | |||
| #include <netinet/in.h> | |||
| #include <fcntl.h> | |||
| #define IS_SOCKET_ERROR(a) ((a)<0) | |||
| typedef int socket_t; | |||
| #else | |||
| #include <winsock2.h> | |||
| #include <ws2tcpip.h> | |||
| #include <windows.h> | |||
| #define IS_SOCKET_ERROR(a) ((a)==SOCKET_ERROR) | |||
| #if !defined(EWOULDBLOCK) | |||
| #define EWOULDBLOCK WSAEWOULDBLOCK | |||
| #endif | |||
| #if !defined(EINPROGRESS) | |||
| #define EINPROGRESS WSAEINPROGRESS | |||
| #endif | |||
| #if !defined(EINTR) | |||
| #define EINTR WSAEINTR | |||
| #endif | |||
| #if !defined(EAGAIN) | |||
| #define EAGAIN EWOULDBLOCK | |||
| #endif | |||
| typedef SOCKET socket_t; | |||
| #endif | |||
| #ifndef INADDR_NONE | |||
| #define INADDR_NONE 0xFFFFFFFF | |||
| #endif | |||
| static int socket_error() | |||
| { | |||
| #if !defined (_WIN32) | |||
| return errno; | |||
| #else | |||
| return WSAGetLastError(); | |||
| #endif | |||
| } | |||
| static int socket_create (int domain, int type, socket_t * sock) | |||
| { | |||
| *sock = socket (domain, type, 0); | |||
| return IS_SOCKET_ERROR(*sock) ? 1 : 0; | |||
| } | |||
| static int socket_make_nonblocking (socket_t * sock) | |||
| { | |||
| #if !defined (_WIN32) | |||
| return fcntl (*sock, F_SETFL, fcntl (*sock, F_GETFL,0 ) | O_NONBLOCK) != 0; | |||
| #else | |||
| unsigned long mode = 0; | |||
| return ioctlsocket (*sock, FIONBIO, &mode) == SOCKET_ERROR; | |||
| #endif | |||
| } | |||
| static int socket_close (socket_t * sock) | |||
| { | |||
| #if !defined (_WIN32) | |||
| close (*sock); | |||
| #else | |||
| closesocket (*sock); | |||
| #endif | |||
| *sock = -1; | |||
| return 0; | |||
| } | |||
| static int socket_connect (socket_t * sock, const struct sockaddr *saddr, socklen_t len) | |||
| { | |||
| while ( 1 ) | |||
| { | |||
| if ( connect (*sock, saddr, len) < 0 ) | |||
| { | |||
| if ( socket_error() == EINTR ) | |||
| continue; | |||
| if ( socket_error() != EINPROGRESS && socket_error() != EWOULDBLOCK ) | |||
| return 1; | |||
| } | |||
| return 0; | |||
| } | |||
| } | |||
| static int socket_accept (socket_t * sock, socket_t * newsock, struct sockaddr *saddr, socklen_t * len) | |||
| { | |||
| while ( IS_SOCKET_ERROR(*newsock = accept (*sock, saddr, len)) ) | |||
| { | |||
| if ( socket_error() == EINTR ) | |||
| continue; | |||
| return 1; | |||
| } | |||
| return 0; | |||
| } | |||
| static int socket_recv (socket_t * sock, void * buf, size_t len) | |||
| { | |||
| int length; | |||
| while ( (length = recv (*sock, buf, len, 0)) < 0 ) | |||
| { | |||
| int err = socket_error(); | |||
| if ( err != EINTR && err != EAGAIN ) | |||
| break; | |||
| } | |||
| return length; | |||
| } | |||
| static int socket_send (socket_t * sock, const void *buf, size_t len) | |||
| { | |||
| int length; | |||
| while ( (length = send (*sock, buf, len, 0)) < 0 ) | |||
| { | |||
| int err = socket_error(); | |||
| if ( err != EINTR && err != EAGAIN ) | |||
| break; | |||
| } | |||
| return length; | |||
| } | |||
| @@ -0,0 +1,390 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #if defined (ENABLE_SSL) | |||
| // Nonzero if OpenSSL has been initialized | |||
| static SSL_CTX * ssl_context = 0; | |||
| #if defined (_WIN32) | |||
| #include <windows.h> | |||
| // This array will store all of the mutexes available to OpenSSL | |||
| static CRITICAL_SECTION * mutex_buf = 0; | |||
| // OpenSSL callback to utilize static locks | |||
| static void cb_openssl_locking_function( int mode, int n, const char * file, int line ) | |||
| { | |||
| if ( mode & CRYPTO_LOCK) | |||
| EnterCriticalSection( &mutex_buf[n] ); | |||
| else | |||
| LeaveCriticalSection( &mutex_buf[n] ); | |||
| } | |||
| // OpenSSL callback to get the thread ID | |||
| static unsigned long cb_openssl_id_function(void) | |||
| { | |||
| return ((unsigned long) GetCurrentThreadId() ); | |||
| } | |||
| static int alloc_mutexes( unsigned int total ) | |||
| { | |||
| unsigned int i; | |||
| // Enable thread safety in OpenSSL | |||
| mutex_buf = (CRITICAL_SECTION*) malloc( total * sizeof(CRITICAL_SECTION) ); | |||
| if ( !mutex_buf ) | |||
| return -1; | |||
| for ( i = 0; i < total; i++) | |||
| InitializeCriticalSection( &(mutex_buf[i]) ); | |||
| return 0; | |||
| } | |||
| #else | |||
| // This array will store all of the mutexes available to OpenSSL | |||
| static pthread_mutex_t * mutex_buf = 0; | |||
| // OpenSSL callback to utilize static locks | |||
| static void cb_openssl_locking_function( int mode, int n, const char * file, int line ) | |||
| { | |||
| (void)file; | |||
| (void)line; | |||
| if ( mode & CRYPTO_LOCK) | |||
| pthread_mutex_lock( &mutex_buf[n] ); | |||
| else | |||
| pthread_mutex_unlock( &mutex_buf[n] ); | |||
| } | |||
| // OpenSSL callback to get the thread ID | |||
| static unsigned long cb_openssl_id_function() | |||
| { | |||
| return ((unsigned long) pthread_self() ); | |||
| } | |||
| static int alloc_mutexes( unsigned int total ) | |||
| { | |||
| unsigned i; | |||
| // Enable thread safety in OpenSSL | |||
| mutex_buf = (pthread_mutex_t*) malloc( total * sizeof(pthread_mutex_t) ); | |||
| if ( !mutex_buf ) | |||
| return -1; | |||
| for ( i = 0; i < total; i++) | |||
| pthread_mutex_init( &(mutex_buf[i]), 0 ); | |||
| return 0; | |||
| } | |||
| #endif | |||
| static int ssl_init_context( irc_session_t * session ) | |||
| { | |||
| // Load the strings and init the library | |||
| SSL_load_error_strings(); | |||
| // Enable thread safety in OpenSSL | |||
| if ( alloc_mutexes( CRYPTO_num_locks() ) ) | |||
| return LIBIRC_ERR_NOMEM; | |||
| // Register our callbacks | |||
| CRYPTO_set_id_callback( cb_openssl_id_function ); | |||
| CRYPTO_set_locking_callback( cb_openssl_locking_function ); | |||
| // Init it | |||
| if ( !SSL_library_init() ) | |||
| return LIBIRC_ERR_SSL_INIT_FAILED; | |||
| if ( RAND_status() == 0 ) | |||
| return LIBIRC_ERR_SSL_INIT_FAILED; | |||
| // Create an SSL context; currently a single context is used for all connections | |||
| ssl_context = SSL_CTX_new( SSLv23_method() ); | |||
| if ( !ssl_context ) | |||
| return LIBIRC_ERR_SSL_INIT_FAILED; | |||
| // Disable SSLv2 as it is unsecure | |||
| if ( (SSL_CTX_set_options( ssl_context, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2) == 0 ) | |||
| return LIBIRC_ERR_SSL_INIT_FAILED; | |||
| // Enable only strong ciphers | |||
| if ( SSL_CTX_set_cipher_list( ssl_context, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" ) != 1 ) | |||
| return LIBIRC_ERR_SSL_INIT_FAILED; | |||
| // Set the verification | |||
| if ( session->options & LIBIRC_OPTION_SSL_NO_VERIFY ) | |||
| SSL_CTX_set_verify( ssl_context, SSL_VERIFY_NONE, 0 ); | |||
| else | |||
| SSL_CTX_set_verify( ssl_context, SSL_VERIFY_PEER, 0 ); | |||
| // Disable session caching | |||
| SSL_CTX_set_session_cache_mode( ssl_context, SSL_SESS_CACHE_OFF ); | |||
| // Enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER so we can move the buffer during sending | |||
| SSL_CTX_set_mode( ssl_context, SSL_CTX_get_mode(ssl_context) | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE ); | |||
| return 0; | |||
| } | |||
| #if defined (_WIN32) | |||
| #define SSLINIT_LOCK_MUTEX(a) WaitForSingleObject( a, INFINITE ) | |||
| #define SSLINIT_UNLOCK_MUTEX(a) ReleaseMutex( a ) | |||
| #else | |||
| #define SSLINIT_LOCK_MUTEX(a) pthread_mutex_lock( &a ) | |||
| #define SSLINIT_UNLOCK_MUTEX(a) pthread_mutex_unlock( &a ) | |||
| #endif | |||
| // Initializes the SSL context. Must be called after the socket is created. | |||
| static int ssl_init( irc_session_t * session ) | |||
| { | |||
| static int ssl_context_initialized = 0; | |||
| #if defined (_WIN32) | |||
| static HANDLE initmutex = 0; | |||
| // First time run? Create the mutex | |||
| if ( initmutex == 0 ) | |||
| { | |||
| HANDLE m = CreateMutex( 0, FALSE, 0 ); | |||
| // Now we check if the mutex has already been created by another thread performing the init concurrently. | |||
| // If it was, we close our mutex and use the original one. This could be done synchronously by using the | |||
| // InterlockedCompareExchangePointer function. | |||
| if ( InterlockedCompareExchangePointer( &m, m, 0 ) != 0 ) | |||
| CloseHandle( m ); | |||
| } | |||
| #else | |||
| static pthread_mutex_t initmutex = PTHREAD_MUTEX_INITIALIZER; | |||
| #endif | |||
| // This initialization needs to be performed only once. The problem is that it is called from | |||
| // irc_connect() and this function may be called simultaneously from different threads. So we have | |||
| // to use mutex on Linux because it allows static mutex initialization. Windows doesn't, so here | |||
| // we do the sabre dance around it. | |||
| SSLINIT_LOCK_MUTEX( initmutex ); | |||
| if ( ssl_context_initialized == 0 ) | |||
| { | |||
| int res = ssl_init_context( session ); | |||
| if ( res ) | |||
| { | |||
| SSLINIT_UNLOCK_MUTEX( initmutex ); | |||
| return res; | |||
| } | |||
| ssl_context_initialized = 1; | |||
| } | |||
| SSLINIT_UNLOCK_MUTEX( initmutex ); | |||
| // Get the SSL context | |||
| session->ssl = SSL_new( ssl_context ); | |||
| if ( !session->ssl ) | |||
| return LIBIRC_ERR_SSL_INIT_FAILED; | |||
| // Let OpenSSL use our socket | |||
| if ( SSL_set_fd( session->ssl, session->sock) != 1 ) | |||
| return LIBIRC_ERR_SSL_INIT_FAILED; | |||
| // Since we're connecting on our own, tell openssl about it | |||
| SSL_set_connect_state( session->ssl ); | |||
| return 0; | |||
| } | |||
| static void ssl_handle_error( irc_session_t * session, int ssl_error ) | |||
| { | |||
| if ( ERR_GET_LIB(ssl_error) == ERR_LIB_SSL ) | |||
| { | |||
| if ( ERR_GET_REASON(ssl_error) == SSL_R_CERTIFICATE_VERIFY_FAILED ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_SSL_CERT_VERIFY_FAILED; | |||
| return; | |||
| } | |||
| if ( ERR_GET_REASON(ssl_error) == SSL_R_UNKNOWN_PROTOCOL ) | |||
| { | |||
| session->lasterror = LIBIRC_ERR_CONNECT_SSL_FAILED; | |||
| return; | |||
| } | |||
| } | |||
| #if defined (ENABLE_DEBUG) | |||
| if ( IS_DEBUG_ENABLED(session) ) | |||
| fprintf (stderr, "[DEBUG] SSL error: %s\n\t(%d, %d)\n", | |||
| ERR_error_string( ssl_error, NULL), ERR_GET_LIB( ssl_error), ERR_GET_REASON(ssl_error) ); | |||
| #endif | |||
| } | |||
| static int ssl_recv( irc_session_t * session ) | |||
| { | |||
| int count; | |||
| unsigned int amount = (sizeof (session->incoming_buf) - 1) - session->incoming_offset; | |||
| ERR_clear_error(); | |||
| // Read up to m_bufferLength bytes | |||
| count = SSL_read( session->ssl, session->incoming_buf + session->incoming_offset, amount ); | |||
| if ( count > 0 ) | |||
| return count; | |||
| else if ( count == 0 ) | |||
| return -1; // remote connection closed | |||
| else | |||
| { | |||
| int ssl_error = SSL_get_error( session->ssl, count ); | |||
| // Handle SSL error since not all of them are actually errors | |||
| switch ( ssl_error ) | |||
| { | |||
| case SSL_ERROR_WANT_READ: | |||
| // This is not really an error. We received something, but | |||
| // OpenSSL gave nothing to us because all it read was | |||
| // internal data. Repeat the same read. | |||
| return 0; | |||
| case SSL_ERROR_WANT_WRITE: | |||
| // This is not really an error. We received something, but | |||
| // now OpenSSL needs to send the data before returning any | |||
| // data to us (like negotiations). This means we'd need | |||
| // to wait for WRITE event, but call SSL_read() again. | |||
| session->flags |= SESSIONFL_SSL_READ_WANTS_WRITE; | |||
| return 0; | |||
| } | |||
| // This is an SSL error, handle it | |||
| ssl_handle_error( session, ERR_get_error() ); | |||
| } | |||
| return -1; | |||
| } | |||
| static int ssl_send( irc_session_t * session ) | |||
| { | |||
| int count; | |||
| ERR_clear_error(); | |||
| count = SSL_write( session->ssl, session->outgoing_buf, session->outgoing_offset ); | |||
| if ( count > 0 ) | |||
| return count; | |||
| else if ( count == 0 ) | |||
| return -1; | |||
| else | |||
| { | |||
| int ssl_error = SSL_get_error( session->ssl, count ); | |||
| switch ( ssl_error ) | |||
| { | |||
| case SSL_ERROR_WANT_READ: | |||
| // This is not really an error. We sent some internal OpenSSL data, | |||
| // but now it needs to read more data before it can send anything. | |||
| // Thus we wait for READ event, but will call SSL_write() again. | |||
| session->flags |= SESSIONFL_SSL_WRITE_WANTS_READ; | |||
| return 0; | |||
| case SSL_ERROR_WANT_WRITE: | |||
| // This is not really an error. We sent some data, but now OpenSSL | |||
| // wants to send some internal data before sending ours. | |||
| // Repeat the same write. | |||
| return 0; | |||
| } | |||
| // This is an SSL error, handle it | |||
| ssl_handle_error( session, ERR_get_error() ); | |||
| } | |||
| return -1; | |||
| } | |||
| #endif | |||
| // Handles both SSL and non-SSL reads. | |||
| // Returns -1 in case there is an error and socket should be closed/connection terminated | |||
| // Returns 0 in case there is a temporary error and the call should be retried (SSL_WANTS_WRITE case) | |||
| // Returns a positive number if we actually read something | |||
| static int session_socket_read( irc_session_t * session ) | |||
| { | |||
| int length; | |||
| #if defined (ENABLE_SSL) | |||
| if ( session->ssl ) | |||
| { | |||
| // Yes, I know this is tricky | |||
| if ( session->flags & SESSIONFL_SSL_READ_WANTS_WRITE ) | |||
| { | |||
| session->flags &= ~SESSIONFL_SSL_READ_WANTS_WRITE; | |||
| ssl_send( session ); | |||
| return 0; | |||
| } | |||
| return ssl_recv( session ); | |||
| } | |||
| #endif | |||
| length = socket_recv( &session->sock, | |||
| session->incoming_buf + session->incoming_offset, | |||
| (sizeof (session->incoming_buf) - 1) - session->incoming_offset ); | |||
| // There is no "retry" errors for regular sockets | |||
| if ( length <= 0 ) | |||
| return -1; | |||
| return length; | |||
| } | |||
| // Handles both SSL and non-SSL writes. | |||
| // Returns -1 in case there is an error and socket should be closed/connection terminated | |||
| // Returns 0 in case there is a temporary error and the call should be retried (SSL_WANTS_WRITE case) | |||
| // Returns a positive number if we actually sent something | |||
| static int session_socket_write( irc_session_t * session ) | |||
| { | |||
| int length; | |||
| #if defined (ENABLE_SSL) | |||
| if ( session->ssl ) | |||
| { | |||
| // Yep | |||
| if ( session->flags & SESSIONFL_SSL_WRITE_WANTS_READ ) | |||
| { | |||
| session->flags &= ~SESSIONFL_SSL_WRITE_WANTS_READ; | |||
| ssl_recv( session ); | |||
| return 0; | |||
| } | |||
| return ssl_send( session ); | |||
| } | |||
| #endif | |||
| length = socket_send (&session->sock, session->outgoing_buf, session->outgoing_offset); | |||
| // There is no "retry" errors for regular sockets | |||
| if ( length <= 0 ) | |||
| return -1; | |||
| return length; | |||
| } | |||
| @@ -0,0 +1,130 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| static void libirc_add_to_set (int fd, fd_set *set, int * maxfd) | |||
| { | |||
| FD_SET (fd, set); | |||
| if ( *maxfd < fd ) | |||
| *maxfd = fd; | |||
| } | |||
| #if defined (ENABLE_DEBUG) | |||
| static void libirc_dump_data (const char * prefix, const char * buf, unsigned int length) | |||
| { | |||
| printf ("%s: ", prefix); | |||
| for ( ; length > 0; length -- ) | |||
| printf ("%c", *buf++); | |||
| } | |||
| #endif | |||
| /* | |||
| * Finds a separator (\x0D\x0A), which separates two lines. | |||
| */ | |||
| static int libirc_findcrlf (const char * buf, int length) | |||
| { | |||
| int offset = 0; | |||
| for ( ; offset < length; offset++ ) | |||
| { | |||
| if ( buf[offset] == 0x0D && offset < length - 1 && buf[offset+1] == 0x0A ) | |||
| return offset; | |||
| if ( buf[offset] == 0x0A) | |||
| return offset; | |||
| } | |||
| return 0; | |||
| } | |||
| static int libirc_findcrlf_offset(const char *buf, int offset, const int length) | |||
| { | |||
| for(; offset < length; offset++) | |||
| { | |||
| if(buf[offset] != 0x0D && buf[offset] != 0x0A) | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| return offset; | |||
| } | |||
| static int libirc_findcrorlf (char * buf, int length) | |||
| { | |||
| int offset = 0; | |||
| for ( ; offset < length; offset++ ) | |||
| { | |||
| if ( buf[offset] == 0x0D || buf[offset] == 0x0A ) | |||
| { | |||
| buf[offset++] = '\0'; | |||
| if ( offset < (length - 1) | |||
| && (buf[offset] == 0x0D || buf[offset] == 0x0A) ) | |||
| offset++; | |||
| return offset; | |||
| } | |||
| } | |||
| return 0; | |||
| } | |||
| static void libirc_event_ctcp_internal (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count) | |||
| { | |||
| (void)event; | |||
| (void)count; | |||
| if ( origin ) | |||
| { | |||
| char nickbuf[128], textbuf[256]; | |||
| irc_target_get_nick (origin, nickbuf, sizeof(nickbuf)); | |||
| if ( strstr (params[0], "PING") == params[0] ) | |||
| irc_cmd_ctcp_reply (session, nickbuf, params[0]); | |||
| else if ( !strcmp (params[0], "VERSION") ) | |||
| { | |||
| if ( !session->ctcp_version ) | |||
| { | |||
| unsigned int high, low; | |||
| irc_get_version (&high, &low); | |||
| snprintf (textbuf, sizeof (textbuf), "VERSION libircclient by Georgy Yunaev ver.%d.%d", high, low); | |||
| } | |||
| else | |||
| snprintf (textbuf, sizeof (textbuf), "VERSION %s", session->ctcp_version); | |||
| irc_cmd_ctcp_reply (session, nickbuf, textbuf); | |||
| } | |||
| else if ( !strcmp (params[0], "FINGER") ) | |||
| { | |||
| sprintf (textbuf, "FINGER %s (%s) Idle 0 seconds", | |||
| session->username ? session->username : "nobody", | |||
| session->realname ? session->realname : "noname"); | |||
| irc_cmd_ctcp_reply (session, nickbuf, textbuf); | |||
| } | |||
| else if ( !strcmp (params[0], "TIME") ) | |||
| { | |||
| time_t now = time(0); | |||
| #if defined (ENABLE_THREADS) && defined (HAVE_LOCALTIME_R) | |||
| struct tm tmtmp, *ltime = localtime_r (&now, &tmtmp); | |||
| #else | |||
| struct tm * ltime = localtime (&now); | |||
| #endif | |||
| strftime (textbuf, sizeof(textbuf), "%a %b %d %H:%M:%S %Z %Y", ltime); | |||
| irc_cmd_ctcp_reply (session, nickbuf, textbuf); | |||
| } | |||
| } | |||
| } | |||