diff --git a/Bot/Makefile b/Bot/Makefile index 039c1d1..d742ea1 100644 --- a/Bot/Makefile +++ b/Bot/Makefile @@ -5,7 +5,7 @@ include $(PWD)/Platform/$(PLATFORM).mk .PHONY: all clean .SUFFIXES: .c .o -OBJS = main.o ircfw.o +OBJS = main.o ircfw.o bot.o util.o news.o all: okuu$(EXEC) diff --git a/Bot/bot.c b/Bot/bot.c new file mode 100644 index 0000000..6fa9df3 --- /dev/null +++ b/Bot/bot.c @@ -0,0 +1,246 @@ +/* $Id$ */ + +#include "ok_bot.h" + +#include "ok_util.h" +#include "ok_news.h" + +#include "ircfw.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef __FreeBSD__ +#include +#endif + +#define OKUU_VERSION "1.00" + +extern char* nntpserver; +extern char* nntpuser; +extern char* nntppass; +extern char* nntppath; +extern char* nntpcount; +extern int nntpport; + +extern char* ircserver; +extern char* ircchan; +extern char* ircpass; +extern char* ircuser; +extern char* ircnick; +extern char* ircreal; +extern int ircport; + +int ok_sock; +struct sockaddr_in ok_addr; + +void ok_close(int sock){ + close(sock); +} + +void ok_bot_kill(int sig){ + fprintf(stderr, "Shutdown\n"); + ircfw_socket_send_cmd(ok_sock, NULL, "QUIT :Shutdown (Signal)"); + exit(1); +} + +bool ok_is_number(const char* str) { + int i; + for(i = 0; str[i] != 0; i++) { + if(!('0' <= str[i] && str[i] <= '9')) return false; + } + return true; +} + +char* ok_null(const char* str){ + return str == NULL ? "(null)" : (char*)str; +} + +int namesort(const struct dirent** a_, const struct dirent** b_){ + const struct dirent* a = *a_; + const struct dirent* b = *b_; + return atoi(a->d_name) - atoi(b->d_name); +} + +int nodots(const struct dirent* d){ + return (strcmp(d->d_name, "..") == 0 || strcmp(d->d_name, ".") == 0) ? 0 : 1; +} + +void ok_bot(void){ + if((ok_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0){ + fprintf(stderr, "Socket creation failure\n"); + return; + } + + bzero((char*)&ok_addr, sizeof(ok_addr)); + ok_addr.sin_family = PF_INET; + ok_addr.sin_addr.s_addr = inet_addr(ircserver); + ok_addr.sin_port = htons(ircport); + + if(connect(ok_sock, (struct sockaddr*)&ok_addr, sizeof(ok_addr)) < 0){ + fprintf(stderr, "Connection failure\n"); + ok_close(ok_sock); + return; + } + + signal(SIGINT, ok_bot_kill); + signal(SIGTERM, ok_bot_kill); + + char* construct = malloc(1025); + + if(ircpass != NULL && strlen(ircpass) > 0){ + sprintf(construct, "PASS :%s", ircpass); + ircfw_socket_send_cmd(ok_sock, NULL, construct); + } + + sprintf(construct, "USER %s %s %s :%s", ircuser, ircuser, ircuser, ircreal); + ircfw_socket_send_cmd(ok_sock, NULL, construct); + + sprintf(construct, "NICK :%s", ircnick); + ircfw_socket_send_cmd(ok_sock, NULL, construct); + + bool is_in = false; + + struct pollfd pollfds[1]; + pollfds[0].fd = ok_sock; + pollfds[0].events = POLLIN | POLLPRI; + + while(1){ + int r = poll(pollfds, 1, 100); + if(!(r > 0 && pollfds[0].revents & POLLIN)){ + /* 100ms sleep, technically */ + uint64_t count = 0; + FILE* f = fopen(nntpcount, "rb"); + if(f != NULL){ + fread(&count, sizeof(count), 1, f); + fclose(f); + } + struct dirent** list; + int n = scandir(nntppath, &list, nodots, namesort); + if(n >= 0){ + int i; + for(i = 0; i < n; i++){ + if(count <= atoi(list[i]->d_name)){ + sprintf(construct, "%s/%s", nntppath, list[i]->d_name); + if(ok_news_read(construct) == 0){ + char* tmp = ok_strcat3("PRIVMSG ", ircchan, " :\x03" "07[USENET] ~ "); + char* temp = ok_strcat3(tmp, news_entry.from, "\x03 "); + free(tmp); + int j; + int incr = 0; + for(j = 0;; j++){ + if(news_entry.content[j] == 0 || news_entry.content[j] == '\n'){ + char* line = malloc(j - incr + 1); + line[j - incr] = 0; + memcpy(line, news_entry.content + incr, j - incr); + + if(strlen(line) > 0){ + char* msg = ok_strcat(temp, line); + ircfw_socket_send_cmd(ok_sock, NULL, msg); + free(msg); + } + + free(line); + incr = j + 1; + if(news_entry.content[j] == 0) break; + } + } + free(temp); + }else{ + fprintf(stderr, "Could not read %s\n", construct); + } + } + free(list[i]); + } + count = atoi(list[i - 1]->d_name) + 1; + free(list); + } + f = fopen(nntpcount, "wb"); + if(f != NULL){ + fwrite(&count, sizeof(count), 1, f); + fclose(f); + } + continue; + } + int st = ircfw_socket_read_cmd(ok_sock); + if(st != 0){ + fprintf(stderr, "Bad response\n"); + return; + } + if(strlen(ircfw_message.command) == 3 && ok_is_number(ircfw_message.command)){ + int res = atoi(ircfw_message.command); + if(!is_in && 400 <= res && res <= 599){ + fprintf(stderr, "Bad response\n"); + return; + }else if(400 <= res && res <= 599){ + fprintf(stderr, "Ignored error: %d\n", res); + continue; + } + if(res == 376){ + is_in = true; + fprintf(stderr, "Login successful\n"); + sprintf(construct, "JOIN :%s", ircchan); + ircfw_socket_send_cmd(ok_sock, NULL, construct); + } + }else{ + if(strcasecmp(ircfw_message.command, "PING") == 0){ + fprintf(stderr, "Ping request\n"); + sprintf(construct, "PONG :%s", ok_null(ircfw_message.prefix)); + ircfw_socket_send_cmd(ok_sock, NULL, construct); + }else if(strcasecmp(ircfw_message.command, "PRIVMSG") == 0){ + char* prefix = ircfw_message.prefix; + char** params = ircfw_message.params; + + int len = 0; + if(params != NULL) { + int i; + for(i = 0; params[i] != NULL; i++); + len = i; + } + if(prefix != NULL && len == 2) { + char* sentin = params[0]; + char* msg = params[1]; + char* nick = ok_strdup(prefix); + int i; + for(i = 0; nick[i] != 0; i++) { + if(nick[i] == '!') { + nick[i] = 0; + break; + } + } + if(msg[0] == 1 && msg[strlen(msg) - 1] == 1){ + /* CTCP */ + if(strcasecmp(msg, "\x01VERSION\x01") == 0){ + sprintf(construct, "NOTICE %s :\x01VERSION Okuu %s / IRC Frameworks %s: http://nishi.boats/okuu\x01", nick, OKUU_VERSION, IRCFW_VERSION); + ircfw_socket_send_cmd(ok_sock, NULL, construct); + } + }else if(sentin[0] == '#'){ + /* This was sent in channel */ + } + + free(nick); + } + }else if(strcasecmp(ircfw_message.command, "ERROR") == 0){ + if(ircfw_message.params != NULL){ + int i; + for(i = 0; ircfw_message.params[i] != NULL; i++) fprintf(stderr, "ERROR: %s\n", ircfw_message.params[i]); + } + } + } + } + + ircfw_socket_send_cmd(ok_sock, NULL, "QUIT :Shutdown"); + + free(construct); +} diff --git a/Bot/main.c b/Bot/main.c index b1eefe9..9ff8e1e 100644 --- a/Bot/main.c +++ b/Bot/main.c @@ -1,5 +1,9 @@ /* $Id$ */ +#include "ok_bot.h" + +#include "ok_news.h" + #include #include #include @@ -7,10 +11,17 @@ char* nntpserver; char* nntpuser; char* nntppass; +char* nntppath; +char* nntpcount; int nntpport = 119; char* ircserver; -int ircport = 6669; +char* ircchan; +char* ircuser; +char* ircnick; +char* ircreal; +char* ircpass; +int ircport = 6667; int main(){ printf("Okuu starting up\n"); @@ -18,7 +29,14 @@ int main(){ nntpserver = getenv("NNTPSERVER"); nntpuser = getenv("NNTPUSER"); nntppass = getenv("NNTPPASS"); + nntppath = getenv("NNTPPATH"); + nntpcount = getenv("NNTPCOUNT"); ircserver = getenv("IRCSERVER"); + ircchan = getenv("IRCCHAN"); + ircuser = getenv("IRCUSER"); + ircnick = getenv("IRCNICK"); + ircreal = getenv("IRCREAL"); + ircpass = getenv("IRCPASS"); if(getenv("NNTPPORT") != NULL){ nntpport = atoi(getenv("NNTPPORT")); } @@ -30,11 +48,31 @@ int main(){ fprintf(stderr, "Set NNTPSERVER\n"); bad = true; } + if(nntppath == NULL){ + fprintf(stderr, "Set NNTPPATH\n"); + bad = true; + } + if(nntpcount == NULL){ + fprintf(stderr, "Set NNTPCOUNT\n"); + bad = true; + } if(ircserver == NULL){ fprintf(stderr, "Set IRCSERVER\n"); bad = true; } + if(ircchan == NULL){ + fprintf(stderr, "Set IRCCHAN\n"); + bad = true; + } + if(ircuser == NULL){ + fprintf(stderr, "Set IRCUSER\n"); + bad = true; + } + if(ircnick == NULL) ircnick = ircuser; + if(ircreal == NULL) ircreal = ircuser; if(bad){ return 1; } + ok_news_init(); + ok_bot(); } diff --git a/Bot/news.c b/Bot/news.c new file mode 100644 index 0000000..f5c018f --- /dev/null +++ b/Bot/news.c @@ -0,0 +1,205 @@ +/* $Id$ */ + +#define OK_NEWS_SRC +#include "ok_news.h" + +#include "ok_util.h" + +#include +#include +#include +#include +#include +#include + +struct news_entry news_entry; + +void ok_news_init(void){ + news_entry.from = NULL; + news_entry.content = NULL; +} + +int ok_news_read(const char* path){ + + if(news_entry.from != NULL){ + free(news_entry.from); + news_entry.from = NULL; + } + if(news_entry.content != NULL){ + free(news_entry.content); + news_entry.content = NULL; + } + + struct stat s; + if(stat(path, &s) == 0){ + char* boundary = NULL; + char* buffer = malloc(s.st_size + 1); + buffer[s.st_size] = 0; + FILE* f = fopen(path, "r"); + fread(buffer, s.st_size, 1, f); + + uint64_t i; + bool newline = false; + int incr = 0; + char* l = malloc(1); + l[0] = 0; + bool header = true; + bool ignore = false; + bool bheader = false; + for(i = 0; i < s.st_size; i++){ + if(buffer[i] == '\r'){ + if(buffer[i + 1] == '\n'){ + /* newline */ + i++; + if(!header){ + char* line = malloc(i - 1 - incr + 1); + line[i - 1 - incr] = 0; + memcpy(line, buffer + incr, i - 1 - incr); + + if(strcmp(line, ".") == 0){ + free(line); + break; + }else{ + char* ln = line; + if(line[0] == '.'){ + ln++; + } + + if(news_entry.content == NULL){ + news_entry.content = malloc(1); + news_entry.content[0] = 0; + } + + if(boundary != NULL && strcmp(ln, boundary) == 0){ + bheader = true; + ignore = true; + }else if(boundary != NULL && bheader && strlen(ln) == 0){ + bheader = false; + free(line); + incr = i + 1; + newline = true; + continue; + }else if(boundary != NULL && bheader){ + int j; + for(j = 0; j < strlen(ln); j++){ + if(ln[j] == ':'){ + ln[j] = 0; + if(strcasecmp(ln, "Content-Type") == 0){ + ignore = false; + j++; + for(; ln[j] != 0 && (ln[j] == ' ' || ln[j] == '\t'); j++); + if(ln[j] != 0){ + char* v = ln + j; + int k; + for(k = 0; v[k] != 0; k++){ + if(v[k] == ';'){ + v[k] = 0; + break; + } + } + if(strcasecmp(v, "text/plain") == 0){ + }else{ + ignore = true; + } + } + } + break; + } + } + } + + if(!ignore && !bheader){ + char* tmp = news_entry.content; + news_entry.content = ok_strcat3(tmp, ln, "\n"); + free(tmp); + } + } + + free(line); + }else if(newline){ + header = false; + }else{ + char* line = malloc(i - 1 - incr + 1); + line[i - 1 - incr] = 0; + memcpy(line, buffer + incr, i - 1 - incr); + + char* last = ok_strdup(l); + char* tmp = l; + l = ok_strcat(tmp, line); + free(tmp); + bool al = false; + if(('a' <= line[0] && line[0] <= 'z') || ('A' <= line[0] && line[0] <= 'Z')){ + free(l); + l = ok_strdup(line); + al = true; + } + if(al){ + char* ln = ok_strdup(l); + int j; + for(j = 0; ln[j] != 0; j++){ + if(ln[j] == ':'){ + char* key = ln; + char* value = ""; + ln[j] = 0; + j++; + for(; ln[j] != 0 && (ln[j] == '\t' || ln[j] == ' '); j++); + if(ln[j] != 0) value = ln + j; + if(strcasecmp(key, "From") == 0){ + if(news_entry.from != NULL) free(news_entry.from); + news_entry.from = ok_strdup(value); + }else if(strcasecmp(key, "Content-Type") == 0){ + int k = 0; + int incr2 = 0; + for(k = 0; k <= strlen(value); k++){ + if(value[k] == ';' || value[k] == 0){ + char* attr = malloc(k - incr2 + 1); + attr[k - incr2] = 0; + memcpy(attr, value + incr2, k - incr2); + + int in; + for(in = 0; attr[in] != 0; in++){ + if(attr[in] == '='){ + attr[in] = 0; + + if(strcasecmp(attr, "boundary") == 0){ + boundary = ok_strcat("--", attr + in + 1 + 1); + boundary[strlen(attr + in + 1 + 1) - 1 + 2] = 0; + ignore = true; + } + + break; + } + } + + free(attr); + k++; + for(; value[k] != 0 && (value[k] == ' ' || value[k] == '\t'); k++); + incr2 = k; + } + } + } + } + } + free(ln); + } + free(last); + free(line); + } + incr = i + 1; + newline = true; + }else{ + newline = false; + } + }else{ + newline = false; + } + } + free(l); + + free(buffer); + if(boundary != NULL) free(boundary); + return 0; + }else{ + return 1; + } +} diff --git a/Bot/ok_bot.h b/Bot/ok_bot.h new file mode 100644 index 0000000..66bbf35 --- /dev/null +++ b/Bot/ok_bot.h @@ -0,0 +1,8 @@ +/* $Id$ */ + +#ifndef __OK_BOT_H__ +#define __OK_BOT_H__ + +void ok_bot(void); + +#endif diff --git a/Bot/ok_news.h b/Bot/ok_news.h new file mode 100644 index 0000000..a3ed195 --- /dev/null +++ b/Bot/ok_news.h @@ -0,0 +1,18 @@ +/* $Id$ */ + +#ifndef __OK_NEWS_H__ +#define __OK_NEWS_H__ + +struct news_entry { + char* from; + char* content; +}; + +void ok_news_init(void); +int ok_news_read(const char* path); + +#ifndef OK_NEWS_SRC +extern struct news_entry news_entry; +#endif + +#endif diff --git a/Bot/ok_util.h b/Bot/ok_util.h new file mode 100644 index 0000000..6bee40b --- /dev/null +++ b/Bot/ok_util.h @@ -0,0 +1,10 @@ +/* $Id$ */ + +#ifndef __OK_UTIL_H__ +#define __OK_UTIL_H__ + +char* ok_strcat(const char* a, const char* b); +char* ok_strcat3(const char* a, const char* b, const char* c); +char* ok_strdup(const char* a); + +#endif diff --git a/Bot/util.c b/Bot/util.c new file mode 100644 index 0000000..7cf29fb --- /dev/null +++ b/Bot/util.c @@ -0,0 +1,25 @@ +/* $Id$ */ + +#include "ok_util.h" + +#include +#include + +char* ok_strcat(const char* a, const char* b){ + char* str = malloc(strlen(a) + strlen(b) + 1); + memcpy(str, a, strlen(a)); + memcpy(str + strlen(a), b, strlen(b)); + str[strlen(a) + strlen(b)] = 0; + return str; +} + +char* ok_strcat3(const char* a, const char* b, const char* c){ + char* tmp = ok_strcat(a, b); + char* str = ok_strcat(tmp, c); + free(tmp); + return str; +} + +char* ok_strdup(const char* a){ + return ok_strcat(a, ""); +} diff --git a/launch.sh b/launch.sh new file mode 100755 index 0000000..97f6c0e --- /dev/null +++ b/launch.sh @@ -0,0 +1,10 @@ +#!/bin/sh +export NNTPSERVER=127.0.0.1 +export NNTPPATH=/var/news/spool/articles/comp/os/netbsd +export NNTPCOUNT=netbsd-count +export IRCSERVER=192.168.0.161 +export IRCCHAN=#test +export IRCUSER=okuu +export IRCNICK=Okuu +export IRCREAL="Utsuho Reiuji" +./Bot/okuu