835 lines
27 KiB
C
835 lines
27 KiB
C
/***********************************************************************************
|
|
* Author: Dmitry Isaenko *
|
|
* License: GNU GPL v.3 *
|
|
* Version: 1.2.2 *
|
|
* Site: https://developersu.blogspot.com/ *
|
|
* 2017, Russia *
|
|
***********************************************************************************/
|
|
|
|
#include "libircclient/libircclient.h"
|
|
#include "libircclient/libirc_rfcnumeric.h"
|
|
#include "stdio.h"
|
|
#include "string.h"
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
// only to get PATH_MAX
|
|
#include <limits.h>
|
|
// for using setsid
|
|
#include <unistd.h>
|
|
|
|
//define period before sending PING to server (5 min - 300).
|
|
#define TIME_TO_PING 300
|
|
|
|
// set if you want to turn mode +x on while connecting
|
|
#define X_MODE
|
|
|
|
// Current version of the program
|
|
#define __CUR_VER__ "1.2.2"
|
|
|
|
#define NPING_DEBUG
|
|
#define NDEBUG
|
|
#define NDEBUG_LOG
|
|
|
|
#ifndef __TIME__
|
|
#define __TIME__ "-"
|
|
#endif
|
|
#ifndef __DATE__
|
|
#define __DATE__ "-"
|
|
#endif
|
|
|
|
|
|
// TODO normal deamon-mode functionality
|
|
// TODO make it multi-channel
|
|
// TODO-feachure make an ability to define log path for the bot.conf? Or move it to /etc/
|
|
|
|
typedef struct conf_sruct {
|
|
int status;
|
|
char server[2048];
|
|
char channel[2048];
|
|
int port;
|
|
char nick[2048];
|
|
char username[2048];
|
|
char realname[2048];
|
|
char password[2048];
|
|
int maxNickLength;
|
|
char logPath[PATH_MAX];
|
|
char link[2048]; // should be enough to store link
|
|
int reJoin; // 1 - yes, 0 - no
|
|
} configuration;
|
|
|
|
|
|
configuration load_config(int run, char * nick_or_path) {
|
|
/*
|
|
* if run = 0 we load config from the file
|
|
* if run = 1 we return configuration structure stored in this function
|
|
* if run = 2 we overrite 'nick' at the configuration structure
|
|
*/
|
|
int i;
|
|
|
|
FILE * conf;
|
|
|
|
static char fileArray[15][2048]; // user setup stored here and avaliable by any call after initial (by passing 0 to function)
|
|
char * st;
|
|
char sta[15][2][2048]; // should be 3D
|
|
static configuration C; //returning structure
|
|
|
|
char confFilePath[PATH_MAX];
|
|
|
|
int checksum = 0;
|
|
|
|
if (run == 0) {
|
|
// the first call
|
|
strcpy(confFilePath, nick_or_path);
|
|
strcat(confFilePath, "bot.conf"); // add bot.conf to path-to-executable
|
|
|
|
if ( (conf = fopen(confFilePath,"r")) != NULL) {
|
|
for (i=0;(!feof(conf));i++){
|
|
fgets(fileArray[i], sizeof(fileArray[i]), conf);
|
|
if (i>=14) // 15 lines is max size for config file
|
|
break;
|
|
}
|
|
|
|
fclose(conf);
|
|
|
|
#ifdef DEBUG_LOG
|
|
printf("___Content of the file___\n");
|
|
for (i=0;i<15; i++){
|
|
printf("%s", fileArray[i]);
|
|
}
|
|
printf("______________________\n");
|
|
#endif
|
|
|
|
// Parsing configuration file - IF STRING DOESN'T HAVE ":" - SKIP IT
|
|
for (i=0;i<15;i++){
|
|
if ( strstr(fileArray[i], ":") != NULL ) {
|
|
st = strtok(fileArray[i], ": \t\n"); // if we can't get ANY parameter at the string, that have ":" inside, then skip this string
|
|
if (st != NULL){
|
|
strcpy(sta[i][0],st);
|
|
|
|
st = strtok(NULL, ": \t\n");
|
|
|
|
if (st == NULL){ // if we had a first parameter in string, (no matter was it set before ":" or after) and don't have the second one,
|
|
C.status = -1; // then we ruin the chain and returning error. 'Status' of incorrect config-file = "-1"
|
|
return C;
|
|
}
|
|
else {
|
|
strcpy(sta[i][1], st);
|
|
|
|
st = strtok(NULL, ": \t\n"); // generally to parse links
|
|
while (st != NULL){
|
|
strcat(sta[i][1], ":");
|
|
strcat(sta[i][1], st);
|
|
|
|
st = strtok(NULL, ": \t\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for (i=0;i<15;i++){
|
|
if ( strstr(sta[i][0], "server") != NULL )
|
|
checksum |= 1<<0;
|
|
else if ( strstr(sta[i][0], "channel") != NULL )
|
|
checksum |= 1<<1;
|
|
else if ( strstr(sta[i][0], "port") != NULL )
|
|
checksum |= 1<<2;
|
|
else if ( strstr(sta[i][0], "nick") != NULL )
|
|
checksum |= 1<<3;
|
|
else if ( strstr(sta[i][0], "username") != NULL )
|
|
checksum |= 1<<4;
|
|
else if ( strstr(sta[i][0], "realname") != NULL )
|
|
checksum |= 1<<5;
|
|
else if ( strstr(sta[i][0], "password") != NULL )
|
|
checksum |= 1<<6;
|
|
else if ( strstr(sta[i][0], "maxNickLength") != NULL )
|
|
checksum |= 1<<7;
|
|
else if ( strstr(sta[i][0], "logPath") != NULL )
|
|
checksum |= 1<<8;
|
|
else if ( strstr(sta[i][0], "link") != NULL )
|
|
checksum |= 1<<9;
|
|
else if ( strstr(sta[i][0], "reJoin") != NULL )
|
|
checksum |= 1<<10;
|
|
|
|
}
|
|
|
|
if (checksum != 0b11111111111){
|
|
C.status = checksum; // incorrect number of settings defined
|
|
return C;
|
|
}
|
|
else {
|
|
// Format array for return in case we're all good
|
|
|
|
C.status = 0; // OK = 0.
|
|
|
|
for (i=0; i<15; i++){
|
|
if ((strcmp(sta[i][0], "server")) == 0)
|
|
strcpy(C.server, sta[i][1]);
|
|
else if ( (strcmp(sta[i][0], "port")) == 0)
|
|
C.port = atoi(sta[i][1]);
|
|
else if ( (strcmp(sta[i][0], "channel")) == 0)
|
|
strcpy(C.channel, sta[i][1]);
|
|
else if ( (strcmp(sta[i][0], "nick")) == 0)
|
|
strcpy(C.nick, sta[i][1]);
|
|
else if ( (strcmp(sta[i][0], "username")) == 0)
|
|
strcpy(C.username, sta[i][1]);
|
|
else if ( (strcmp(sta[i][0], "realname")) == 0)
|
|
strcpy(C.realname, sta[i][1]);
|
|
else if ( (strcmp(sta[i][0], "password")) == 0)
|
|
strcpy(C.password, sta[i][1]);
|
|
else if ( (strcmp(sta[i][0], "maxNickLength")) == 0){
|
|
C.maxNickLength = atoi(sta[i][1]);
|
|
if (C.maxNickLength > 128){
|
|
C.maxNickLength = 128; // now idiots could feel themselfs protected. Libircclient restriction IIRC set to 128 chars
|
|
}
|
|
}
|
|
else if ( (strcmp(sta[i][0], "logPath")) == 0){
|
|
if (strcmp(sta[i][1], "0") == 0)
|
|
strcpy(C.logPath, nick_or_path);
|
|
else {
|
|
if (sta[i][1][0] != '/'){
|
|
C.status = -3;
|
|
return C;
|
|
}
|
|
else{
|
|
strcpy(C.logPath, sta[i][1]);
|
|
if ( C.logPath[strlen(C.logPath)] != '/' )
|
|
strcat (C.logPath, "/");
|
|
}
|
|
}
|
|
}
|
|
else if ( (strcmp(sta[i][0], "link")) == 0){
|
|
strcpy(C.link, "Logs: ");
|
|
strcat(C.link, sta[i][1]);
|
|
}
|
|
else if ( (strcmp(sta[i][0], "reJoin")) == 0){
|
|
if (strcmp(sta[i][1], "yes") == 0 || strcmp(sta[i][1], "Yes") == 0 )
|
|
C.reJoin = 1;
|
|
else
|
|
C.reJoin = 0;
|
|
}
|
|
}
|
|
if (strlen(C.nick) > C.maxNickLength)
|
|
C.nick[C.maxNickLength] = '\0'; // yeah, they will love it, before set nick name longer then 128 char =(
|
|
}
|
|
// ++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
}
|
|
else {
|
|
|
|
C.status = -2; //unable to open file = -2
|
|
return C;
|
|
}
|
|
|
|
#ifdef DEBUG_LOG
|
|
printf("___Recieved keys from the file___\n");
|
|
for (i=0;i<15; i++){
|
|
printf("%s - %s\n", sta[i][0], sta[i][1]);
|
|
}
|
|
printf("______________________\n");
|
|
#endif
|
|
|
|
return C;
|
|
}
|
|
else if ( run == 1 ){
|
|
return C; // just return already loaded structure by request
|
|
}
|
|
else if ( run == 2){ // save nick recieved
|
|
strcpy(C.nick, nick_or_path);
|
|
return C;
|
|
}
|
|
}
|
|
|
|
int set_log_dir(char * logdir){
|
|
struct stat st = {0};
|
|
|
|
strcat(logdir, "logs");
|
|
|
|
if (stat(logdir, &st) == -1) {
|
|
if (mkdir(logdir, 0777) != 0 )
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Function to print time stamps when writing to console
|
|
void printTimeStamp(){
|
|
char timeStamp[22];
|
|
time_t now = time(NULL);
|
|
|
|
strftime(timeStamp, 22, "%d %b %Y %X ", localtime(&now));
|
|
printf("%s", timeStamp);
|
|
}
|
|
|
|
// Mainly used to update is_alive
|
|
void event_ctcp_rep(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count){ // handle own ctcp requests - get recieved data and do something with it
|
|
configuration C = load_config(1, ""); //load config to get current nickname
|
|
|
|
#ifdef PING_DEBUG
|
|
printf("\nReply from my CTCP, where USER = %s\n", origin);
|
|
#endif
|
|
if (strncmp(origin, C.nick, strlen(C.nick)) == 0){ // if bot generated response, then it's ping for internal use. (Ok, documentation is 80% clear on this, it will work but in future should be checked)
|
|
is_alive(1, time(NULL), session);
|
|
}
|
|
}
|
|
|
|
/* Handle timeouts before re-connect (rewrite in January 2038)
|
|
state = 0 - get status
|
|
state = 1 - write status
|
|
|
|
returns 0 - OK
|
|
returns 1 - not OK
|
|
*/
|
|
int is_alive( int state, time_t timeGot, irc_session_t * session){ // in future, it should be stand-alone thread
|
|
static int defaultTimeout = TIME_TO_PING;
|
|
static time_t timeSav;
|
|
static int pingRequestSent = 0; // 0 - not sent, 1 - already sent
|
|
configuration C = load_config(1, ""); //load config to get current nickname
|
|
|
|
#ifdef PING_DEBUG
|
|
printf("is_alive:ping = %d\n", pingRequestSent);
|
|
#endif
|
|
|
|
if (state == 0) {
|
|
if ( ((long) timeGot - timeSav) > defaultTimeout ) { // ok, it's time to send PING to yourself or probably we have problems
|
|
if (pingRequestSent == 0){ // if we didn't sent PING request before, let's do it and change pingRequestSent
|
|
#ifdef PING_DEBUG
|
|
printf("PANIC - Request sent\n");
|
|
printf(" stored time = %ld\n",timeSav);
|
|
printf(" recieved time = %ld\n",timeGot);
|
|
#endif
|
|
irc_cmd_ctcp_request(session, C.nick, "PING");
|
|
pingRequestSent = 1;
|
|
return 0;
|
|
}
|
|
else{ // we already sent PING
|
|
if (((long) timeGot - timeSav) > (defaultTimeout+180)){ // we know, that the time is greater than stored one, so we add 3min(180) and if it's greater, then re-connect
|
|
#ifdef PING_DEBUG
|
|
printf("Now we have problems\n");
|
|
#endif
|
|
return 1;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
else if ( state == 1 ){
|
|
timeSav = timeGot; // re-write stored time
|
|
pingRequestSent = 0; // reset pingRequestSent
|
|
#ifdef PING_DEBUG
|
|
printf("\n1-WRITE event\nTime Saved (re-written) = %ld\n", timeSav); //debug
|
|
printf("defaultTimeout = %d\n", defaultTimeout); //debug
|
|
#endif
|
|
return 0;
|
|
}
|
|
return 0; // how it could be possible to come here? ok, whatever
|
|
}
|
|
void event_connect (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)
|
|
{
|
|
is_alive(1, time(NULL), session ); // this time really 'initial' timestamp written into the watchdog function
|
|
|
|
// dump_event (session, event, origin, params, count);
|
|
configuration C = load_config(1, "");
|
|
#ifdef X_MODE
|
|
irc_cmd_user_mode (session, "+x"); // TODO clarify this shit
|
|
#endif
|
|
irc_cmd_join (session, C.channel, 0);
|
|
}
|
|
|
|
void dump_event (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)
|
|
{
|
|
#ifdef DEBUG
|
|
char buf[512];
|
|
int cnt;
|
|
#endif
|
|
char nowTime[20];
|
|
|
|
char nickBuf[128];
|
|
char hostBuf[1024];
|
|
|
|
time_t now = time(NULL);
|
|
|
|
#ifdef DEBUG
|
|
buf[0] = '\0';
|
|
#endif
|
|
FILE * fp; // set FD for the file for logs
|
|
|
|
configuration C = load_config(1, "");
|
|
|
|
#ifdef DEBUG
|
|
for ( cnt = 0; cnt < count; cnt++ ) {
|
|
if ( cnt )
|
|
strcat (buf, "|");
|
|
strcat (buf, params[cnt]);
|
|
|
|
}
|
|
#endif
|
|
// Set name for the log file
|
|
|
|
irc_target_get_nick (origin, nickBuf, 128);
|
|
irc_target_get_host (origin, hostBuf, 1024);
|
|
|
|
strftime(nowTime, 20, "logs/%Y-%m-%d.txt", localtime(&now));
|
|
strcat(C.logPath, nowTime); // now C.logPath have the correct name of the file
|
|
|
|
if ( (fp = fopen (C.logPath, "ab")) != NULL )
|
|
{
|
|
strftime(nowTime,20, "[%H:%M:%S] ", localtime(&now));
|
|
|
|
!(strcmp(event,"CHANNEL")) ? fprintf(fp,"%s <%s>: %s\n",nowTime, nickBuf, params[1]):
|
|
!(strcmp(event,"ACTION")) ? fprintf(fp,"%s %s %s\n",nowTime, nickBuf, params[1]):
|
|
!(strcmp(event,"JOIN")) ? fprintf(fp,"%s >> %s [%s] joined %s\n",nowTime, nickBuf, hostBuf, params[0]):
|
|
!(strcmp(event,"INVITE")) ? fprintf(fp,"%s %s invites %s to %s\n",nowTime, nickBuf, params[0], params[1]):
|
|
!(strcmp(event,"KICK")) ? fprintf(fp,"%s !<< %s kicked by %s [%s] with reason: %s\n",nowTime, params[1], nickBuf, hostBuf, params[2]):
|
|
!(strcmp(event,"MODE")) ? strcmp(nickBuf, hostBuf) == 0 ? 0: fprintf(fp,"%s -!- %s [%s] set %s %s\n",nowTime, nickBuf, hostBuf, params[1], params[2] ? params[2]:""):
|
|
!(strcmp(event,"PART")) ? fprintf(fp,"%s << %s [%s] parted: %s \n",nowTime, nickBuf, hostBuf, params[1]):
|
|
!(strcmp(event,"TOPIC")) ? fprintf(fp,"%s -!- %s [%s] has changed topic to: %s \n",nowTime, nickBuf, hostBuf, params[1]):
|
|
!(strcmp(event,"QUIT")) ? fprintf(fp,"%s << %s [%s] quit: %s \n",nowTime, nickBuf, hostBuf, params[0]):
|
|
!(strcmp(event,"NICK")) ? fprintf(fp,"%s -!- %s [%s] changed nick to: %s \n",nowTime, nickBuf, hostBuf, params[0]): 0;
|
|
|
|
if (!strcmp(event,"KICK")){
|
|
// Reloading configuration in case our nick has been changed recently
|
|
if ( !(strcmp(params[1], C.nick)) && C.reJoin == 1 ){
|
|
event_connect (session, event, origin, params, count);
|
|
}
|
|
}
|
|
else if (!strcmp(event, "CHANNEL"))
|
|
if (strncmp(params[1], C.nick, strlen(C.nick)) == 0 )
|
|
strlen(C.link) == 7 ? irc_cmd_msg(session, params[0], ";)") : irc_cmd_msg(session, params[0], C.link);
|
|
|
|
fclose (fp);
|
|
}
|
|
else {
|
|
printf("Unable to open/create log file: check folder permissions\n"); // and die, but it doesn't die.. well. Ok for now.
|
|
}
|
|
|
|
#if defined (DEBUG)
|
|
printf ("Event \"%s\", origin: \"%s\", params: %d [%s]\n", event, origin ? origin : "NULL", cnt, buf);
|
|
#endif
|
|
}
|
|
|
|
void event_ctcp_req(irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)
|
|
{
|
|
dump_event (session, event, origin, params, count);
|
|
|
|
time_t now = time(NULL);
|
|
char curTime[138];
|
|
strftime(curTime, 138, "TIME %a %b %d %H:%M:%S %Z %Y", localtime(&now));
|
|
|
|
if( strcmp (params[0], "VERSION") == 0 ){
|
|
dump_event (session, event, origin, params, count);
|
|
irc_cmd_ctcp_reply(session, origin, "VERSION loperIRCLogBot v."__CUR_VER__);
|
|
}
|
|
else if( strcmp (params[0], "SOURCE") == 0 ){
|
|
dump_event (session, event, origin, params, count);
|
|
irc_cmd_ctcp_reply(session, origin, "SOURCE loperIRCLogBot v."__CUR_VER__);
|
|
}
|
|
else if( strcmp (params[0], "TIME") == 0 ){
|
|
dump_event (session, event, origin, params, count);
|
|
irc_cmd_ctcp_reply(session, origin, curTime);
|
|
}
|
|
else if( strncmp(params[0], "PING", 4) == 0 ){
|
|
dump_event (session, event, origin, params, count);
|
|
irc_cmd_ctcp_reply (session, origin, params[0]);
|
|
}
|
|
else if( strcmp (params[0], "CLIENTINFO") == 0 ){
|
|
dump_event (session, event, origin, params, count);
|
|
irc_cmd_ctcp_reply(session, origin, "CLIENTINFO loperIRCLogBot v."__CUR_VER__" - Supported tags: VERSION, SOURCE, TIME, PING, CLIENTINFO");
|
|
}
|
|
}
|
|
|
|
void event_join (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)
|
|
{
|
|
dump_event (session, event, origin, params, count);
|
|
irc_cmd_user_mode (session, "+i");
|
|
}
|
|
|
|
void nick_change(irc_session_t * session){
|
|
static char append[] = "|0";
|
|
configuration C = load_config(1, "");
|
|
|
|
if ( append[1] > '9') // Refactoring request
|
|
append[1] = '0'; // if all your nicknames with |0 |2 ... |9 are already occupied, we're fucked up. Really, there is no good solution in this code.
|
|
|
|
// Let's say that we have string like 1234567|0, and maxNickLength = 10, then we see, that string length is greater (9) then maxNickLength-2, but also we've already appended something like "|0" into it, then
|
|
// we could check it and wipe. Otherwise we wipe 2 last chars.
|
|
if ( strlen(C.nick) > (C.maxNickLength - 2) ){
|
|
if ( (C.nick[C.maxNickLength-3] == '|') && (C.nick[C.maxNickLength-2] >= '0') && (C.nick[C.maxNickLength-2] <= '9') )
|
|
C.nick[C.maxNickLength-3] = '\0';
|
|
else
|
|
C.nick[C.maxNickLength-2] = '\0';
|
|
}
|
|
if ( C.nick[strlen(C.nick)-2] == '|' )
|
|
C.nick[strlen(C.nick)-1] = append[1];
|
|
else
|
|
strncat(C.nick, append, 2);
|
|
|
|
append[1]++;
|
|
|
|
C = load_config(2, C.nick);
|
|
|
|
irc_cmd_nick(session, C.nick);
|
|
}
|
|
|
|
void event_numeric (irc_session_t * session, unsigned int event, const char * origin, const char ** params, unsigned int count)
|
|
{
|
|
char buf[24];
|
|
sprintf (buf, "%d", event);
|
|
|
|
if (event == 433){ // 433 Nickname is already in use
|
|
nick_change(session);
|
|
}
|
|
|
|
dump_event (session, buf, origin, params, count);
|
|
}
|
|
static void event_notice (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)
|
|
{
|
|
char nickBuf[1024]; // too big??
|
|
configuration C = load_config(1,"");
|
|
|
|
dump_event (session, event, origin, params, count);
|
|
|
|
|
|
if ( !origin )
|
|
return;
|
|
else
|
|
irc_target_get_nick (origin, nickBuf, 1024);
|
|
|
|
if ( strcasecmp (nickBuf, "nickserv") )
|
|
return;
|
|
if ( (strstr (params[1], "This nickname is registered") != NULL) || (strstr (params[1], "This nickname is owned by someone else") != NULL) ){
|
|
irc_send_raw(session, "nickserv IDENTIFY %s", C.password);
|
|
}
|
|
else if( strstr (params[1], "Password accepted") != NULL )
|
|
printf ("Nickserv authentication succeeded\n");
|
|
|
|
}
|
|
|
|
int make_template(char * folder){
|
|
FILE * templ;
|
|
strcat (folder, "bot.conf");
|
|
|
|
if ( (templ = fopen(folder,"w")) != NULL) {
|
|
fprintf(templ, "server: \n");
|
|
fprintf(templ, "channel: \n");
|
|
fprintf(templ, "port: \n");
|
|
fprintf(templ, "nick: \n");
|
|
fprintf(templ, "username: \n");
|
|
fprintf(templ, "realname: \n");
|
|
fprintf(templ, "password: 0\n");
|
|
fprintf(templ, "maxNickLength: 30\n");
|
|
fprintf(templ, "logPath: 0\n");
|
|
fprintf(templ, "link: 0\n");
|
|
fprintf(templ, "reJoin: yes\n");
|
|
fclose(templ);
|
|
printf("Configuratation template created.\n");
|
|
return 0;
|
|
}
|
|
else {
|
|
printf("Unable to create configuration template file\nCheck folder permissions\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
void reportSettingsIssues(int sum){
|
|
char *setLst[] = {"server",
|
|
"channel",
|
|
"port",
|
|
"nick",
|
|
"username",
|
|
"realname",
|
|
"password",
|
|
"maxNickLenght",
|
|
"logPath",
|
|
"link",
|
|
"reJoin"};
|
|
int i;
|
|
|
|
switch (sum){
|
|
case -1:
|
|
printf("Configuration file issue: value or attribute missed or redundant\n");
|
|
break;
|
|
case -2:
|
|
printf ("Unable to open configuration file\n");
|
|
break;
|
|
case -3:
|
|
printf("Configuration file issue: 'logPath' is not defined as absolute path\n");
|
|
break;
|
|
default:
|
|
printf("Configuration file issue. Next field(s) not found:\n");
|
|
for (i=0; i<11; i++)
|
|
if ((sum & (1<<i)) != (1<<i))
|
|
printf("\t'%s'\n", setLst[i]);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
configuration config;
|
|
|
|
irc_callbacks_t callbacks; // The IRC callbacks structure
|
|
|
|
irc_session_t * session;
|
|
|
|
struct timeval tv;
|
|
fd_set in_set, out_set;
|
|
int maxfd = 0;
|
|
|
|
// Let's find out the path to executable file! It's needed, because when we create config file like ../executable -g
|
|
// it creates it on the same folder where user located at, not at the folder where the executable is.
|
|
// Same for logs.
|
|
char dest[PATH_MAX];
|
|
struct stat info;
|
|
if (readlink("/proc/self/exe", dest, PATH_MAX) == -1){
|
|
printf("Unable to get the path to this executable\n");
|
|
return 1;
|
|
}
|
|
else {
|
|
dest[strlen(dest)-strlen(strrchr(dest,'/')+1)] = '\0'; // looks for the last "/" at the path etc.
|
|
}
|
|
// end detection
|
|
if (argc == 2){
|
|
if ( strcmp("--help", argv[1]) == 0 ){
|
|
printf("Avaliable options:\n\n"
|
|
" -d, --daemon Start application as daemon (experimental)\n"
|
|
" -g, --genconf Create configuration file template. Attention! It will overrite your existing configuration file.\n"
|
|
" -s, --silent Silent mode. All program messages stores to the 'output.txt' file\n"
|
|
" -n, --nomessage Don't print any messages anywhere\n"
|
|
" -v, --version Application version\n\n"
|
|
" --help Show this message and terminate application\n\n"
|
|
"Configuration stores at the ./bot.conf. Please pay attention: maximum lenght of the file is 10 strings. In case if you don't use password for your nickname set it to 0.\n"
|
|
"Log files stores at the \"./logs\" folder, if in \"bot.conf\" you defined '0' for this setting, otherwise it's stores at \"YOUR_PATH/logs\". Files have next format: YYY-MM-DD.txt\n"
|
|
"Password for the nickname is taken form the configuration file. '0' stands for 'no password'\n"
|
|
"'Link' is the link that returns when someone says \"bot_name something-something\". '0' stands for no link\n"
|
|
"Be smart! Don't use too long nicknames. Usually they used should 30 characters long but may vary from server to server. Don't worry if your nickname shorter, but don't let it be greater then 128 characters.\n");
|
|
return 0;
|
|
}
|
|
// deamon mode here
|
|
else if( (strcmp("-d", argv[1]) == 0) || (strcmp("--daemon", argv[1]) == 0)) {
|
|
pid_t pid, sid;
|
|
// fork
|
|
pid = fork();
|
|
if (pid < 0)
|
|
// TODO implement notices to syslog to track failure
|
|
exit(EXIT_FAILURE); // report to log
|
|
// Exit parent process. pid of is not 0, then it's parent. pid = 0 then it's child.
|
|
if (pid > 0)
|
|
exit(EXIT_SUCCESS); // report to log
|
|
// set umask for using filesystem as we want
|
|
umask(0);
|
|
// create sid
|
|
sid = setsid();
|
|
if (sid < 0)
|
|
exit(EXIT_FAILURE); // report to log
|
|
// change working directory of the process to /
|
|
if (chdir("/") < 0)
|
|
exit(EXIT_FAILURE); // it's safe to replace to 'return' statement
|
|
|
|
freopen("/dev/null", "rb", stdout); //doing it using guidelines form man daemon
|
|
freopen("/dev/null", "rb", stdin);
|
|
freopen("/dev/null", "rb", stderr);
|
|
}
|
|
else if( (strcmp("-g", argv[1]) == 0) || (strcmp("--genconf", argv[1]) == 0)) {
|
|
printf("Attention! This action will remove your current bot.conf file\nDo you really want to generate template? (Y/N):\n");
|
|
do{
|
|
switch (getchar()){
|
|
case 'y': case 'Y':
|
|
return make_template(dest);
|
|
case 'n': case 'N':
|
|
printf("No changes applied\n");
|
|
return 0;
|
|
case '\n': // ,__o
|
|
break; // _-\_<,
|
|
default: // (*)/'(*)
|
|
while ( getchar() != '\n' );
|
|
}
|
|
|
|
printf("\033[A\033[2K"); // VT100 escape codes
|
|
|
|
} while(1);
|
|
return 0;
|
|
}
|
|
else if( (strcmp("-s", argv[1]) == 0) || (strcmp("--silent", argv[1]) == 0)) {
|
|
// close stdin, redirect stdout & stderr
|
|
if ( (freopen ("output.txt", "a", stdout)) == NULL ){ // todo - add validation for errno? Write to syslog?
|
|
return 1;
|
|
}
|
|
setlinebuf(stdout); // line buffer
|
|
|
|
if ( (freopen ("output.txt", "a", stderr)) == NULL ){ // todo - add validation for errno?
|
|
return 1;
|
|
}
|
|
setvbuf(stderr, NULL, _IONBF, 0); // no buffering for srderr
|
|
freopen("/dev/null", "rb", stdin);
|
|
}
|
|
else if( (strcmp("-n", argv[1]) == 0) || (strcmp("--nomessage", argv[1]) == 0)) {
|
|
freopen("/dev/null", "rb", stdout); //doing it using guidelines form man daemon
|
|
freopen("/dev/null", "rb", stdin);
|
|
freopen("/dev/null", "rb", stderr);
|
|
}
|
|
else if ( (strcmp("-v", argv[1]) == 0) || (strcmp("--version", argv[1]) == 0)) {
|
|
printf("loperIRCLogBot: "__CUR_VER__" - build %s %s\n", __TIME__, __DATE__);
|
|
return 0;
|
|
}
|
|
else {
|
|
printf("Incorrect arguments.\n"
|
|
"Use --help for information.\n");
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
else if (argc > 2){
|
|
printf("Too many arguments.\n"
|
|
"Use --help for information.\n");
|
|
return 0;
|
|
}
|
|
// -================================================================================================-
|
|
config = load_config(0, dest);
|
|
if (config.status == 0) {
|
|
printf("\t--------\n"
|
|
"\tSETTINGS\n"
|
|
"\t--------\n"
|
|
"server - %s\n"
|
|
"channel - %s\n"
|
|
"port - %d\n"
|
|
"nick - %s\n"
|
|
"username - %s\n"
|
|
"realname - %s\n"
|
|
"password - %s\n"
|
|
"maxNickLength - %d\n"
|
|
"logPath - %s\n"
|
|
"link - %s\n"
|
|
"reJoin - %s\n"
|
|
"\t--------\n",
|
|
config.server,
|
|
config.channel,
|
|
config.port,
|
|
config.nick,
|
|
config.username,
|
|
config.realname,
|
|
config.password,
|
|
config.maxNickLength,
|
|
config.logPath,
|
|
config.link,
|
|
config.reJoin == 0?"No":"Yes");
|
|
|
|
if ( set_log_dir(config.logPath) != 0){ // set logs directory
|
|
printf ("Unable to create directory for log files (%s)\n"
|
|
"Please make sure that you have premissions to write in this directory\n",
|
|
config.logPath);
|
|
return 1;
|
|
}
|
|
else {
|
|
// Init callbacks
|
|
memset ( &callbacks, 0, sizeof(callbacks) );
|
|
|
|
// Set up the mandatory events
|
|
callbacks.event_connect = event_connect;
|
|
callbacks.event_numeric = event_numeric;
|
|
callbacks.event_join = event_join;
|
|
|
|
// Set up the rest of events
|
|
|
|
callbacks.event_ctcp_req = event_ctcp_req;
|
|
callbacks.event_notice = event_notice;
|
|
callbacks.event_ctcp_rep = event_ctcp_rep; // & dump_event? Now no need to dump. This event is triggered upon receipt of an CTCP response. Thus if you generate the CTCP message and the remote user responded, this event handler will be called.
|
|
// callbacks.event_unknown = event_unknown; // this event will be triggered when app recieve PING request from server. And any other unknown event, and this is buggy moment, and should be fixed.
|
|
|
|
callbacks.event_channel = dump_event;
|
|
callbacks.event_nick = dump_event;
|
|
callbacks.event_quit = dump_event;
|
|
callbacks.event_part = dump_event;
|
|
callbacks.event_mode = dump_event;
|
|
callbacks.event_topic = dump_event;
|
|
callbacks.event_kick = dump_event;
|
|
callbacks.event_invite = dump_event;
|
|
callbacks.event_umode = dump_event;
|
|
callbacks.event_ctcp_action = dump_event; // "ACTION" handler
|
|
|
|
|
|
// Now create the session
|
|
while (1) {
|
|
printTimeStamp();
|
|
printf("Connecting...\n");
|
|
session = irc_create_session( &callbacks );
|
|
|
|
if ( !session ){
|
|
printTimeStamp();
|
|
printf("Unable to create session\n");
|
|
// return 1; // give a try once again
|
|
}
|
|
else {
|
|
irc_option_set( session, LIBIRC_OPTION_SSL_NO_VERIFY );
|
|
|
|
// Connect to a regular IRC server
|
|
if ( irc_connect (session, config.server, config.port, 0, config.nick, config.username, config.realname ) ){
|
|
printTimeStamp();
|
|
printf ("Could not connect: %s\n", irc_strerror (irc_errno(session)));
|
|
// return 1; //give a try once again
|
|
// TODO add a counter for few reply and die / do something with this
|
|
}
|
|
else {
|
|
|
|
is_alive(1, time(NULL), session); //initialize is_alive internal start time
|
|
printTimeStamp();
|
|
printf("Connection established\n");
|
|
|
|
///////////////////// irc_run() replacement ///////////////////
|
|
while ( irc_is_connected(session) )
|
|
{
|
|
tv.tv_usec = 250000;
|
|
tv.tv_sec = 0;
|
|
|
|
// Init sets
|
|
FD_ZERO (&in_set);
|
|
FD_ZERO (&out_set);
|
|
|
|
// +1 chk
|
|
if ( is_alive(0, time(NULL), session) != 0 ){
|
|
printf("\n\t-----------------------------------\n\t");
|
|
printTimeStamp();
|
|
printf("PING timeout\n"
|
|
"\t-----------------------------------\n");
|
|
break; // 1
|
|
}
|
|
// end +1 chk
|
|
|
|
|
|
irc_add_select_descriptors (session, &in_set, &out_set, &maxfd);
|
|
|
|
if ( select (maxfd + 1, &in_set, &out_set, 0, &tv) < 0 )
|
|
{
|
|
printTimeStamp();
|
|
printf ("Could not connect or I/O error: LIBIRC_ERR_TERMINATED\n");
|
|
break; // 1
|
|
}
|
|
|
|
if ( irc_process_select_descriptors (session, &in_set, &out_set) ){
|
|
printTimeStamp();
|
|
printf ("Could not connect or I/O error\n" );
|
|
break; // 1
|
|
}
|
|
|
|
}
|
|
irc_disconnect(session);
|
|
printTimeStamp();
|
|
printf("Disconnected after successful connection\n");
|
|
//return 0;
|
|
////////////////////////////////////////////////////////////////////////////
|
|
}
|
|
}
|
|
printTimeStamp();
|
|
printf ("Not connected: will retry in 5 sec\n");
|
|
sleep (5);
|
|
} // while end
|
|
}
|
|
return 0; // never happens
|
|
}
|
|
else
|
|
reportSettingsIssues(config.status);
|
|
|
|
return 0;
|
|
}
|