Subversion Repositories IRC-Archiver

Rev

Rev 17 | Rev 19 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
4 nishi 1
/* $Id: bot.c 18 2024-08-30 08:29:19Z nishi $ */
2
 
3
#include "ia_bot.h"
4
 
5 nishi 5
#include "ia_util.h"
8 nishi 6
#include "ia_db.h"
5 nishi 7
 
11 nishi 8
#include "web_html.h"
9
 
5 nishi 10
#include "ircfw.h"
11
 
12
#include <signal.h>
4 nishi 13
#include <stdbool.h>
5 nishi 14
#include <stdlib.h>
4 nishi 15
#include <stdio.h>
11 nishi 16
#include <time.h>
4 nishi 17
#include <string.h>
17 nishi 18
#include <strings.h>
5 nishi 19
#include <unistd.h>
4 nishi 20
 
21
#include <sys/socket.h>
22
#include <netinet/tcp.h>
23
#include <arpa/inet.h>
24
 
6 nishi 25
#define IRCARC_VERSION "1.00"
26
 
13 nishi 27
const char* ircarc_version = IRCARC_VERSION;
28
 
4 nishi 29
int ia_sock;
5 nishi 30
struct sockaddr_in ia_addr;
4 nishi 31
 
32
bool ia_do_log = false;
33
 
34
void ia_log(const char* txt) {
35
	if(!ia_do_log) return;
36
	fprintf(stderr, "%s\n", txt);
37
}
38
 
39
extern char* host;
6 nishi 40
extern char* admin;
5 nishi 41
extern char* realname;
42
extern char* nickname;
43
extern char* username;
44
extern char* password;
8 nishi 45
extern char* nickserv;
5 nishi 46
extern char* channels[];
4 nishi 47
extern int port;
48
 
5 nishi 49
void ia_close(int sock) { close(sock); }
50
 
51
const char* ia_null(const char* str) {
52
	if(str == NULL) return "(null)";
53
	return str;
54
}
55
 
56
bool ia_is_number(const char* str) {
57
	int i;
58
	for(i = 0; str[i] != 0; i++) {
59
		if(!('0' <= str[i] && str[i] <= '9')) return false;
60
	}
61
	return true;
62
}
63
 
64
bool loop = true;
65
 
66
void ia_bot_kill(int sig) {
67
	ia_log("Shutdown");
68
	ircfw_socket_send_cmd(ia_sock, NULL, "QUIT :Shutdown (Signal)");
69
	exit(1);
70
}
71
 
4 nishi 72
void ia_bot_loop(void) {
73
	if((ia_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
74
		ia_log("Socket creation failure");
75
		return;
76
	}
77
 
5 nishi 78
	loop = true;
79
 
4 nishi 80
	int yes = 1;
81
 
82
	if(setsockopt(ia_sock, IPPROTO_TCP, TCP_NODELAY, (char*)&yes, sizeof(yes)) < 0) {
83
		ia_log("setsockopt failure");
5 nishi 84
		ia_close(ia_sock);
4 nishi 85
		return;
86
	}
87
 
5 nishi 88
	bzero((char*)&ia_addr, sizeof(ia_addr));
89
	ia_addr.sin_family = PF_INET;
90
	ia_addr.sin_addr.s_addr = inet_addr(host);
91
	ia_addr.sin_port = htons(port);
4 nishi 92
 
93
	if(connect(ia_sock, (struct sockaddr*)&ia_addr, sizeof(ia_addr)) < 0) {
94
		ia_log("Connection failure");
5 nishi 95
		ia_close(ia_sock);
96
		return;
4 nishi 97
	}
98
 
5 nishi 99
	signal(SIGINT, ia_bot_kill);
100
	signal(SIGTERM, ia_bot_kill);
101
 
102
	char* construct = malloc(1025);
103
 
104
	if(password != NULL && strlen(password) > 0) {
105
		sprintf(construct, "PASS :%s", password);
106
		ircfw_socket_send_cmd(ia_sock, NULL, construct);
107
	}
108
 
109
	sprintf(construct, "USER %s %s %s :%s", username, username, username, realname);
110
	ircfw_socket_send_cmd(ia_sock, NULL, construct);
111
 
112
	sprintf(construct, "NICK :%s", nickname);
113
	ircfw_socket_send_cmd(ia_sock, NULL, construct);
114
 
115
	bool is_in = false;
116
 
7 nishi 117
	while(loop) {
5 nishi 118
		int st = ircfw_socket_read_cmd(ia_sock);
119
		if(st != 0) {
120
			ia_log("Bad response");
121
			return;
122
		}
123
		if(strlen(ircfw_message.command) == 3 && ia_is_number(ircfw_message.command)) {
124
			int res = atoi(ircfw_message.command);
125
			if(!is_in && 400 <= res && res <= 599) {
126
				ia_log("Bad response");
127
				return;
128
			} else if(400 <= res && res <= 599) {
129
				sprintf(construct, "Ignored error: %d", res);
130
				ia_log(construct);
131
				continue;
132
			}
133
			if(res == 376) {
134
				is_in = true;
135
				ia_log("Login successful");
136
				int i;
137
				for(i = 0; channels[i] != NULL; i++) {
138
					sprintf(construct, "JOIN :%s", channels[i]);
139
					ircfw_socket_send_cmd(ia_sock, NULL, construct);
140
				}
6 nishi 141
				sprintf(construct, "NOTICE %s :IRC-Archiver %s is ready to accept requests", admin, IRCARC_VERSION);
142
				ircfw_socket_send_cmd(ia_sock, NULL, construct);
8 nishi 143
				if(nickserv != NULL) {
144
					sprintf(construct, "PRIVMSG NickServ :%s", nickserv);
145
					ircfw_socket_send_cmd(ia_sock, NULL, construct);
146
				}
5 nishi 147
			}
148
		} else {
149
			if(strcasecmp(ircfw_message.command, "PING") == 0) {
150
				ia_log("Ping request");
151
				sprintf(construct, "PONG :%s", ia_null(ircfw_message.prefix));
152
				ircfw_socket_send_cmd(ia_sock, NULL, construct);
153
			} else if(strcasecmp(ircfw_message.command, "PRIVMSG") == 0) {
154
				char* prefix = ircfw_message.prefix;
155
				char** params = ircfw_message.params;
6 nishi 156
				int i;
157
				int len = 0;
158
				if(params != NULL) {
159
					for(i = 0; params[i] != NULL; i++)
160
						;
161
					len = i;
162
				}
163
				if(prefix != NULL && len == 2) {
164
					char* sentin = params[0];
165
					char* msg = params[1];
5 nishi 166
					char* nick = ia_strdup(prefix);
167
					for(i = 0; nick[i] != 0; i++) {
168
						if(nick[i] == '!') {
169
							nick[i] = 0;
170
							break;
171
						}
172
					}
173
 
6 nishi 174
					if(msg[0] == 1 && msg[strlen(msg) - 1] == 1) {
175
						/* CTCP */
176
						if(strcasecmp(msg, "\x01VERSION\x01") == 0) {
15 nishi 177
							sprintf(construct, "NOTICE %s :\x01VERSION IRC-Archiver %s / IRC Frameworks %s: http://nishi.boats/ircarc\x01", nick, IRCARC_VERSION, IRCFW_VERSION);
6 nishi 178
							ircfw_socket_send_cmd(ia_sock, NULL, construct);
179
						}
180
					} else if(sentin[0] == '#') {
181
						/* This was sent in channel ; log it */
8 nishi 182
						ia_db_put(nick, sentin, msg);
6 nishi 183
					} else {
184
						/* Command, I guess */
7 nishi 185
						int i;
11 nishi 186
 
187
						char* name = NULL;
188
						char* chn = NULL;
189
						char* frm = NULL;
190
						char* to = NULL;
191
 
7 nishi 192
						char* prm = ia_strdup(msg);
193
						int incr = 0;
11 nishi 194
						int t = 0;
7 nishi 195
						for(i = 0;; i++) {
196
							if(prm[i] == ' ' || prm[i] == 0) {
197
								char oldc = prm[i];
198
								prm[i] = 0;
199
 
200
								if(strcasecmp(prm, "help") == 0) {
11 nishi 201
									sprintf(construct, "PRIVMSG %s :HELP   Show this help", nick);
7 nishi 202
									ircfw_socket_send_cmd(ia_sock, NULL, construct);
203
									sprintf(construct, "PRIVMSG %s :SHUTDOWN   Shutdown the bot", nick);
204
									ircfw_socket_send_cmd(ia_sock, NULL, construct);
11 nishi 205
									sprintf(construct, "PRIVMSG %s :ARCHIVE [name] [channel] <yyyy-mm-dd-hh:mm:ss> <yyyy-mm-dd-hh:mm:ss>   Archive the log", nick);
206
									ircfw_socket_send_cmd(ia_sock, NULL, construct);
207
								} else if(strcasecmp(prm, "archive") == 0) {
208
									if(strcmp(admin, nick) == 0) {
209
										if(t <= 4 && t != 0) {
210
											char* p = prm + incr;
211
											if(t == 1) {
212
												name = p;
213
											} else if(t == 2) {
214
												chn = p;
215
											} else if(t == 3) {
216
												frm = p;
217
											} else if(t == 4) {
218
												to = p;
219
											}
220
										}
221
										if(oldc == 0) {
222
											if(chn == NULL) {
223
												sprintf(construct, "PRIVMSG %s :Insufficient arguments", nick);
224
												ircfw_socket_send_cmd(ia_sock, NULL, construct);
18 nishi 225
											} else if(strcmp(name, "index") == 0) {
226
												sprintf(construct, "PRIVMSG %s :You cannot use that name", nick);
227
												ircfw_socket_send_cmd(ia_sock, NULL, construct);
11 nishi 228
											} else {
229
												web_range_t range;
230
												struct tm from_tm;
231
												memset(&from_tm, 0, sizeof(from_tm));
232
												struct tm to_tm;
233
												memset(&to_tm, 0, sizeof(to_tm));
234
												time_t tfrom = 0;
235
												time_t tto = 0;
236
												if(frm != NULL) {
13 nishi 237
													if(strptime(frm, "%Y/%m/%d-%H:%M:%S", &from_tm) == NULL) {
11 nishi 238
														sprintf(construct, "PRIVMSG %s :Date parsing failure", nick);
239
														ircfw_socket_send_cmd(ia_sock, NULL, construct);
240
														break;
241
													}
242
													tfrom = mktime(&from_tm);
243
												}
244
												if(to != NULL) {
13 nishi 245
													if(strptime(to, "%Y/%m/%d-%H:%M:%S", &to_tm) == NULL) {
11 nishi 246
														sprintf(construct, "PRIVMSG %s :Date parsing failure", nick);
247
														ircfw_socket_send_cmd(ia_sock, NULL, construct);
248
														break;
249
													}
250
													tto = mktime(&to_tm);
251
												}
18 nishi 252
 
253
												char* hpath = ia_strcat4(webroot, "/", name, ".html");
254
												struct stat s;
255
												if(stat(hpath, &s) == 0) {
256
													sprintf(construct, "PRIVMSG %s :Archive which has the same name already exists", nick);
257
													ircfw_socket_send_cmd(ia_sock, NULL, construct);
258
													break;
259
												}
260
 
11 nishi 261
												int j;
262
												bool found = false;
263
												for(j = 0; channels[j] != NULL; j++) {
264
													if(strcmp(channels[j], chn) == 0) {
265
														found = true;
266
														break;
267
													}
268
												}
269
												if(!found) {
270
													sprintf(construct, "PRIVMSG %s :I do not know the channel", nick);
271
													ircfw_socket_send_cmd(ia_sock, NULL, construct);
272
													break;
273
												}
274
												range.from = tfrom;
275
												range.to = tto;
276
												range.channel = chn;
277
												web_html_generate(name, range);
278
											}
279
										}
280
									} else {
281
										sprintf(construct, "PRIVMSG %s :You must be an admin to use this", nick);
282
										ircfw_socket_send_cmd(ia_sock, NULL, construct);
283
										break;
284
									}
7 nishi 285
								} else if(strcasecmp(prm, "shutdown") == 0) {
286
									if(strcmp(admin, nick) == 0) {
287
										loop = false;
288
									} else {
289
										sprintf(construct, "PRIVMSG %s :You must be an admin to use this", nick);
290
										ircfw_socket_send_cmd(ia_sock, NULL, construct);
291
									}
292
									break;
293
								} else {
294
									sprintf(construct, "PRIVMSG %s :Unknown command `%s'. See HELP.", nick, prm);
295
									ircfw_socket_send_cmd(ia_sock, NULL, construct);
296
									break;
297
								}
298
 
299
								incr = i + 1;
11 nishi 300
								t++;
7 nishi 301
								if(oldc == 0) break;
302
							}
303
						}
304
						free(prm);
6 nishi 305
					}
306
 
5 nishi 307
					free(nick);
308
				}
309
			}
310
		}
4 nishi 311
	}
5 nishi 312
 
313
	ircfw_socket_send_cmd(ia_sock, NULL, "QUIT :Shutdown");
314
 
315
	free(construct);
4 nishi 316
}