1 |
/* News module. |
2 |
* Based on code by Andrew Kempe (TheShadow). |
3 |
* |
4 |
* IRC Services is copyright (c) 1996-2009 Andrew Church. |
5 |
* E-mail: <achurch@achurch.org> |
6 |
* Parts written by Andrew Kempe and others. |
7 |
* This program is free but copyrighted software; see the file GPL.txt for |
8 |
* details. |
9 |
*/ |
10 |
|
11 |
#include "services.h" |
12 |
#include "modules.h" |
13 |
#include "conffile.h" |
14 |
#include "commands.h" |
15 |
#include "databases.h" |
16 |
#include "language.h" |
17 |
|
18 |
#include "operserv.h" |
19 |
#include "news.h" |
20 |
|
21 |
/*************************************************************************/ |
22 |
|
23 |
static Module *module_operserv; |
24 |
|
25 |
static void do_logonnews(User *u); |
26 |
static void do_opernews(User *u); |
27 |
|
28 |
static Command cmds[] = { |
29 |
/* Anyone can use *NEWS LIST, but *NEWS {ADD,DEL} are reserved for |
30 |
* Services operators. (The command routines check permissions.) */ |
31 |
{"LOGONNEWS", do_logonnews, NULL, NEWS_HELP_LOGON, -1,-1}, |
32 |
{"OPERNEWS", do_opernews, NULL, NEWS_HELP_OPER, -1,-1}, |
33 |
{NULL} |
34 |
}; |
35 |
|
36 |
/*************************************************************************/ |
37 |
|
38 |
/* List of messages for each news type. This simplifies message sending. */ |
39 |
|
40 |
#define MSG_SYNTAX 0 |
41 |
#define MSG_LIST_HEADER 1 |
42 |
#define MSG_LIST_ENTRY 2 |
43 |
#define MSG_LIST_NONE 3 |
44 |
#define MSG_ADD_SYNTAX 4 |
45 |
#define MSG_ADD_FULL 5 |
46 |
#define MSG_ADDED 6 |
47 |
#define MSG_DEL_SYNTAX 7 |
48 |
#define MSG_DEL_NOT_FOUND 8 |
49 |
#define MSG_DELETED 9 |
50 |
#define MSG_DEL_NONE 10 |
51 |
#define MSG_DELETED_ALL 11 |
52 |
#define MSG_MAX 11 |
53 |
|
54 |
struct newsmsgs { |
55 |
int16 type; |
56 |
const char *name; |
57 |
int msgs[MSG_MAX+1]; |
58 |
}; |
59 |
static struct newsmsgs msgarray[] = { |
60 |
{ NEWS_LOGON, "LOGON", |
61 |
{ NEWS_LOGON_SYNTAX, |
62 |
NEWS_LOGON_LIST_HEADER, |
63 |
NEWS_LOGON_LIST_ENTRY, |
64 |
NEWS_LOGON_LIST_NONE, |
65 |
NEWS_LOGON_ADD_SYNTAX, |
66 |
NEWS_LOGON_ADD_FULL, |
67 |
NEWS_LOGON_ADDED, |
68 |
NEWS_LOGON_DEL_SYNTAX, |
69 |
NEWS_LOGON_DEL_NOT_FOUND, |
70 |
NEWS_LOGON_DELETED, |
71 |
NEWS_LOGON_DEL_NONE, |
72 |
NEWS_LOGON_DELETED_ALL |
73 |
} |
74 |
}, |
75 |
{ NEWS_OPER, "OPER", |
76 |
{ NEWS_OPER_SYNTAX, |
77 |
NEWS_OPER_LIST_HEADER, |
78 |
NEWS_OPER_LIST_ENTRY, |
79 |
NEWS_OPER_LIST_NONE, |
80 |
NEWS_OPER_ADD_SYNTAX, |
81 |
NEWS_OPER_ADD_FULL, |
82 |
NEWS_OPER_ADDED, |
83 |
NEWS_OPER_DEL_SYNTAX, |
84 |
NEWS_OPER_DEL_NOT_FOUND, |
85 |
NEWS_OPER_DELETED, |
86 |
NEWS_OPER_DEL_NONE, |
87 |
NEWS_OPER_DELETED_ALL |
88 |
} |
89 |
} |
90 |
}; |
91 |
|
92 |
static int *findmsgs(int16 type, char **typename) { |
93 |
int i; |
94 |
for (i = 0; i < lenof(msgarray); i++) { |
95 |
if (msgarray[i].type == type) { |
96 |
if (typename) |
97 |
*typename = (char *)msgarray[i].name; |
98 |
return msgarray[i].msgs; |
99 |
} |
100 |
} |
101 |
return NULL; |
102 |
} |
103 |
|
104 |
/*************************************************************************/ |
105 |
|
106 |
/* Main handler for NEWS commands. */ |
107 |
static void do_news(User *u, int16 type); |
108 |
|
109 |
/* Lists all a certain type of news. */ |
110 |
static void do_news_list(User *u, int16 type, int *msgs); |
111 |
|
112 |
/* Add news items. */ |
113 |
static void do_news_add(User *u, int16 type, int *msgs, const char *typename); |
114 |
static int add_newsitem(User *u, const char *text, int16 type); |
115 |
|
116 |
/* Delete news items. */ |
117 |
static void do_news_del(User *u, int16 type, int *msgs, const char *typename); |
118 |
static int del_newsitem(int num, int16 type); |
119 |
|
120 |
/*************************************************************************/ |
121 |
/**************************** Database stuff *****************************/ |
122 |
/*************************************************************************/ |
123 |
|
124 |
static NewsItem *newslist = NULL; |
125 |
static int32 newslist_count = 0; |
126 |
static int newslist_iterator; |
127 |
|
128 |
/*************************************************************************/ |
129 |
|
130 |
static void *new_news(void) |
131 |
{ |
132 |
return scalloc(1, sizeof(NewsItem)); |
133 |
} |
134 |
|
135 |
/*************************************************************************/ |
136 |
|
137 |
static void free_news(void *record) |
138 |
{ |
139 |
NewsItem *news = record; |
140 |
if (news) { |
141 |
free(news->text); |
142 |
free(news); |
143 |
} |
144 |
} |
145 |
|
146 |
/*************************************************************************/ |
147 |
|
148 |
NewsItem *add_news(NewsItem *newsitem) |
149 |
{ |
150 |
if (newslist_count >= MAX_NEWS) |
151 |
fatal("add_news(): too many news items!"); |
152 |
ARRAY_EXTEND(newslist); |
153 |
memcpy(&newslist[newslist_count-1], newsitem, sizeof(NewsItem)); |
154 |
newslist[newslist_count-1].next = |
155 |
(NewsItem *)(long)(newslist_count-1); /*index*/ |
156 |
free(newsitem); |
157 |
return &newslist[newslist_count-1]; |
158 |
} |
159 |
|
160 |
/*************************************************************************/ |
161 |
|
162 |
void del_news(NewsItem *newsitem) |
163 |
{ |
164 |
int num = (int)(long)(newsitem->next); |
165 |
if (num < 0 || num >= newslist_count) { |
166 |
module_log("del_news(): invalid index %d in news item at %p", |
167 |
num, newsitem); |
168 |
return; |
169 |
} |
170 |
free(newsitem->text); |
171 |
ARRAY_REMOVE(newslist, num); |
172 |
if (num < newslist_iterator) |
173 |
newslist_iterator--; |
174 |
while (num < newslist_count) { |
175 |
newslist[num].next = (NewsItem *)(long)num; |
176 |
num++; |
177 |
} |
178 |
} |
179 |
|
180 |
/*************************************************************************/ |
181 |
|
182 |
NewsItem *get_news(int16 type, int32 num) |
183 |
{ |
184 |
int i; |
185 |
|
186 |
ARRAY_FOREACH (i, newslist) { |
187 |
if (newslist[i].type == type && newslist[i].num == num) |
188 |
break; |
189 |
} |
190 |
return i<newslist_count ? &newslist[i] : NULL; |
191 |
} |
192 |
|
193 |
/*************************************************************************/ |
194 |
|
195 |
NewsItem *put_news(NewsItem *news) |
196 |
{ |
197 |
return news; |
198 |
} |
199 |
|
200 |
/*************************************************************************/ |
201 |
|
202 |
NewsItem *first_news(void) |
203 |
{ |
204 |
newslist_iterator = 0; |
205 |
return next_news(); |
206 |
} |
207 |
|
208 |
NewsItem *next_news(void) |
209 |
{ |
210 |
if (newslist_iterator >= newslist_count) |
211 |
return NULL; |
212 |
return &newslist[newslist_iterator++]; |
213 |
} |
214 |
|
215 |
/*************************************************************************/ |
216 |
|
217 |
int news_count(void) |
218 |
{ |
219 |
return newslist_count; |
220 |
} |
221 |
|
222 |
/*************************************************************************/ |
223 |
|
224 |
/* Free all memory used by database tables. */ |
225 |
|
226 |
static void clean_dbtables(void) |
227 |
{ |
228 |
int i; |
229 |
|
230 |
ARRAY_FOREACH (i, newslist) |
231 |
free(newslist[i].text); |
232 |
free(newslist); |
233 |
newslist = NULL; |
234 |
newslist_count = 0; |
235 |
} |
236 |
|
237 |
/*************************************************************************/ |
238 |
|
239 |
/* News database table info */ |
240 |
static DBField news_dbfields[] = { |
241 |
{ "type", DBTYPE_INT16, offsetof(NewsItem,type) }, |
242 |
{ "num", DBTYPE_INT32, offsetof(NewsItem,num) }, |
243 |
{ "text", DBTYPE_STRING, offsetof(NewsItem,text) }, |
244 |
{ "who", DBTYPE_BUFFER, offsetof(NewsItem,who), NICKMAX }, |
245 |
{ "time", DBTYPE_TIME, offsetof(NewsItem,time) }, |
246 |
{ NULL } |
247 |
}; |
248 |
static DBTable news_dbtable = { |
249 |
.name = "news", |
250 |
.newrec = new_news, |
251 |
.freerec = free_news, |
252 |
.insert = (void *)add_news, |
253 |
.first = (void *)first_news, |
254 |
.next = (void *)next_news, |
255 |
.fields = news_dbfields, |
256 |
}; |
257 |
|
258 |
/*************************************************************************/ |
259 |
/***************************** News display ******************************/ |
260 |
/*************************************************************************/ |
261 |
|
262 |
static void display_news(User *u, int16 type, time_t min_time) |
263 |
{ |
264 |
NewsItem *news, *disp[NEWS_DISPCOUNT]; |
265 |
int count = 0; /* Number we're going to show--not more than 3 */ |
266 |
int msg; |
267 |
|
268 |
if (type == NEWS_LOGON) { |
269 |
msg = NEWS_LOGON_TEXT; |
270 |
} else if (type == NEWS_OPER) { |
271 |
msg = NEWS_OPER_TEXT; |
272 |
} else { |
273 |
module_log("Invalid type (%d) to display_news()", type); |
274 |
return; |
275 |
} |
276 |
|
277 |
for (news = first_news(); news; news = next_news()) { |
278 |
if (count >= NEWS_DISPCOUNT) |
279 |
break; |
280 |
if (news->type == type && (!min_time || news->time > min_time)) { |
281 |
disp[count] = news; |
282 |
count++; |
283 |
} |
284 |
} |
285 |
while (--count >= 0) { |
286 |
char timebuf[BUFSIZE]; |
287 |
|
288 |
strftime_lang(timebuf, sizeof(timebuf), u->ngi, |
289 |
STRFTIME_SHORT_DATE_FORMAT, disp[count]->time); |
290 |
notice_lang(s_GlobalNoticer, u, msg, timebuf, disp[count]->text); |
291 |
} |
292 |
} |
293 |
|
294 |
/*************************************************************************/ |
295 |
/***************************** News editing ******************************/ |
296 |
/*************************************************************************/ |
297 |
|
298 |
static void do_logonnews(User *u) |
299 |
{ |
300 |
do_news(u, NEWS_LOGON); |
301 |
} |
302 |
|
303 |
static void do_opernews(User *u) |
304 |
{ |
305 |
do_news(u, NEWS_OPER); |
306 |
} |
307 |
|
308 |
/*************************************************************************/ |
309 |
|
310 |
/* Main news command handling routine. */ |
311 |
static void do_news(User *u, int16 type) |
312 |
{ |
313 |
const char *cmd = strtok(NULL, " "); |
314 |
char *typename; |
315 |
int *msgs; |
316 |
|
317 |
msgs = findmsgs(type, &typename); |
318 |
if (!msgs) { |
319 |
module_log("Invalid type to do_news()"); |
320 |
return; |
321 |
} |
322 |
|
323 |
if (!cmd) |
324 |
cmd = ""; |
325 |
|
326 |
if (stricmp(cmd, "LIST") == 0) { |
327 |
do_news_list(u, type, msgs); |
328 |
|
329 |
} else if (stricmp(cmd, "ADD") == 0) { |
330 |
if (is_services_oper(u)) |
331 |
do_news_add(u, type, msgs, typename); |
332 |
else |
333 |
notice_lang(s_OperServ, u, PERMISSION_DENIED); |
334 |
|
335 |
} else if (stricmp(cmd, "DEL") == 0) { |
336 |
if (is_services_oper(u)) |
337 |
do_news_del(u, type, msgs, typename); |
338 |
else |
339 |
notice_lang(s_OperServ, u, PERMISSION_DENIED); |
340 |
|
341 |
} else { |
342 |
char buf[32]; |
343 |
snprintf(buf, sizeof(buf), "%sNEWS", typename); |
344 |
syntax_error(s_OperServ, u, buf, msgs[MSG_SYNTAX]); |
345 |
} |
346 |
} |
347 |
|
348 |
/*************************************************************************/ |
349 |
|
350 |
/* Handle a {LOGON,OPER}NEWS LIST command. */ |
351 |
|
352 |
static void do_news_list(User *u, int16 type, int *msgs) |
353 |
{ |
354 |
NewsItem *news; |
355 |
int count = 0; |
356 |
char timebuf[64]; |
357 |
|
358 |
for (news = first_news(); news; news = next_news()) { |
359 |
if (news->type == type) { |
360 |
if (count == 0) |
361 |
notice_lang(s_OperServ, u, msgs[MSG_LIST_HEADER]); |
362 |
strftime_lang(timebuf, sizeof(timebuf), u->ngi, |
363 |
STRFTIME_DATE_TIME_FORMAT, news->time); |
364 |
notice_lang(s_OperServ, u, msgs[MSG_LIST_ENTRY], |
365 |
news->num, timebuf, |
366 |
*news->who ? news->who : "<unknown>", |
367 |
news->text); |
368 |
count++; |
369 |
} |
370 |
} |
371 |
if (count == 0) |
372 |
notice_lang(s_OperServ, u, msgs[MSG_LIST_NONE]); |
373 |
} |
374 |
|
375 |
/*************************************************************************/ |
376 |
|
377 |
/* Handle a {LOGON,OPER}NEWS ADD command. */ |
378 |
|
379 |
static void do_news_add(User *u, int16 type, int *msgs, const char *typename) |
380 |
{ |
381 |
char *text = strtok_remaining(); |
382 |
|
383 |
if (!text) { |
384 |
char buf[32]; |
385 |
snprintf(buf, sizeof(buf), "%sNEWS", typename); |
386 |
syntax_error(s_OperServ, u, buf, msgs[MSG_ADD_SYNTAX]); |
387 |
} else { |
388 |
int n = add_newsitem(u, text, type); |
389 |
if (n < 0) |
390 |
notice_lang(s_OperServ, u, msgs[MSG_ADD_FULL]); |
391 |
else |
392 |
notice_lang(s_OperServ, u, msgs[MSG_ADDED], n); |
393 |
if (readonly) |
394 |
notice_lang(s_OperServ, u, READ_ONLY_MODE); |
395 |
} |
396 |
} |
397 |
|
398 |
|
399 |
/* Actually add a news item. Return the number assigned to the item, or -1 |
400 |
* if the news list is full (MAX_NEWS items). |
401 |
*/ |
402 |
|
403 |
static int add_newsitem(User *u, const char *text, int16 type) |
404 |
{ |
405 |
NewsItem *news; |
406 |
int num; |
407 |
|
408 |
if (news_count() >= MAX_NEWS) |
409 |
return -1; |
410 |
|
411 |
num = 0; |
412 |
for (news = first_news(); news; news = next_news()) { |
413 |
if (news->type == type && num < news->num) |
414 |
num = news->num; |
415 |
} |
416 |
if (num+1 < num) { |
417 |
module_log("BUG: add_newsitem(): news number overflow (MAX_NEWS" |
418 |
" too small?)"); |
419 |
return -1; |
420 |
} |
421 |
news = scalloc(1, sizeof(*news)); |
422 |
news->type = type; |
423 |
news->num = num+1; |
424 |
news->text = sstrdup(text); |
425 |
news->time = time(NULL); |
426 |
strbcpy(news->who, u->nick); |
427 |
add_news(news); |
428 |
return num+1; |
429 |
} |
430 |
|
431 |
/*************************************************************************/ |
432 |
|
433 |
/* Handle a {LOGON,OPER}NEWS DEL command. */ |
434 |
|
435 |
static void do_news_del(User *u, int16 type, int *msgs, const char *typename) |
436 |
{ |
437 |
char *text = strtok(NULL, " "); |
438 |
|
439 |
if (!text) { |
440 |
char buf[32]; |
441 |
snprintf(buf, sizeof(buf), "%sNEWS", typename); |
442 |
syntax_error(s_OperServ, u, buf, msgs[MSG_DEL_SYNTAX]); |
443 |
} else { |
444 |
if (stricmp(text, "ALL") != 0) { |
445 |
int num = atoi(text); |
446 |
if (num > 0 && del_newsitem(num, type)) |
447 |
notice_lang(s_OperServ, u, msgs[MSG_DELETED], num); |
448 |
else |
449 |
notice_lang(s_OperServ, u, msgs[MSG_DEL_NOT_FOUND], num); |
450 |
} else { |
451 |
if (del_newsitem(0, type)) |
452 |
notice_lang(s_OperServ, u, msgs[MSG_DELETED_ALL]); |
453 |
else |
454 |
notice_lang(s_OperServ, u, msgs[MSG_DEL_NONE]); |
455 |
} |
456 |
if (readonly) |
457 |
notice_lang(s_OperServ, u, READ_ONLY_MODE); |
458 |
} |
459 |
} |
460 |
|
461 |
|
462 |
/* Actually delete a news item. If `num' is 0, delete all news items of |
463 |
* the given type. Returns the number of items deleted. |
464 |
*/ |
465 |
|
466 |
static int del_newsitem(int num, int16 type) |
467 |
{ |
468 |
NewsItem *news; |
469 |
int count = 0; |
470 |
|
471 |
for (news = first_news(); news; news = next_news()) { |
472 |
if (news->type == type && (num == 0 || news->num == num)) { |
473 |
del_news(news); |
474 |
count++; |
475 |
} |
476 |
} |
477 |
return count; |
478 |
} |
479 |
|
480 |
/*************************************************************************/ |
481 |
/*************************** Callback routines ***************************/ |
482 |
/*************************************************************************/ |
483 |
|
484 |
/* Callback for users logging on. */ |
485 |
|
486 |
static int new_user_callback(User *u, int ac, char **av, int reconnect) |
487 |
{ |
488 |
display_news(u, NEWS_LOGON, reconnect ? u->signon : 0); |
489 |
return 0; |
490 |
} |
491 |
|
492 |
/*************************************************************************/ |
493 |
|
494 |
/* Callback to watch for mode +o to send oper news. */ |
495 |
|
496 |
static int user_mode_callback(User *u, int modechar, int add) |
497 |
{ |
498 |
if (modechar == 'o' && add) |
499 |
display_news(u, NEWS_OPER, 0); |
500 |
return 0; |
501 |
} |
502 |
|
503 |
/*************************************************************************/ |
504 |
|
505 |
/* OperServ STATS ALL callback. */ |
506 |
|
507 |
static int do_stats_all(User *user, const char *s_OperServ) |
508 |
{ |
509 |
int32 count, mem; |
510 |
NewsItem *news; |
511 |
|
512 |
count = mem = 0; |
513 |
for (news = first_news(); news; news = next_news()) { |
514 |
count++; |
515 |
mem += sizeof(*news); |
516 |
if (news->text) |
517 |
mem += strlen(news->text)+1; |
518 |
} |
519 |
notice_lang(s_OperServ, user, OPER_STATS_ALL_NEWS_MEM, |
520 |
count, (mem+512) / 1024); |
521 |
|
522 |
return 0; |
523 |
} |
524 |
|
525 |
/*************************************************************************/ |
526 |
/***************************** Module stuff ******************************/ |
527 |
/*************************************************************************/ |
528 |
|
529 |
ConfigDirective module_config[] = { |
530 |
{ NULL } |
531 |
}; |
532 |
|
533 |
/*************************************************************************/ |
534 |
|
535 |
int init_module() |
536 |
{ |
537 |
module_operserv = find_module("operserv/main"); |
538 |
if (!module_operserv) { |
539 |
module_log("Main OperServ module not loaded"); |
540 |
return 0; |
541 |
} |
542 |
use_module(module_operserv); |
543 |
|
544 |
if (!register_commands(module_operserv, cmds)) { |
545 |
module_log("Unable to register commands"); |
546 |
exit_module(0); |
547 |
return 0; |
548 |
} |
549 |
|
550 |
if (!add_callback(NULL, "user create", new_user_callback) |
551 |
|| !add_callback(NULL, "user MODE", user_mode_callback) |
552 |
|| !add_callback(module_operserv, "STATS ALL", do_stats_all) |
553 |
) { |
554 |
module_log("Unable to add callbacks"); |
555 |
exit_module(0); |
556 |
return 0; |
557 |
} |
558 |
|
559 |
if (!register_dbtable(&news_dbtable)) { |
560 |
module_log("Unable to register database table"); |
561 |
exit_module(0); |
562 |
return 0; |
563 |
} |
564 |
|
565 |
return 1; |
566 |
} |
567 |
|
568 |
/*************************************************************************/ |
569 |
|
570 |
int exit_module(int shutdown_unused) |
571 |
{ |
572 |
unregister_dbtable(&news_dbtable); |
573 |
clean_dbtables(); |
574 |
|
575 |
remove_callback(NULL, "user create", new_user_callback); |
576 |
remove_callback(NULL, "user MODE", user_mode_callback); |
577 |
|
578 |
if (module_operserv) { |
579 |
remove_callback(module_operserv, "STATS ALL", do_stats_all); |
580 |
unregister_commands(module_operserv, cmds); |
581 |
unuse_module(module_operserv); |
582 |
module_operserv = NULL; |
583 |
} |
584 |
|
585 |
return 1; |
586 |
} |
587 |
|
588 |
/*************************************************************************/ |
589 |
|
590 |
/* |
591 |
* Local variables: |
592 |
* c-file-style: "stroustrup" |
593 |
* c-file-offsets: ((case-label . *) (statement-case-intro . *)) |
594 |
* indent-tabs-mode: nil |
595 |
* End: |
596 |
* |
597 |
* vim: expandtab shiftwidth=4: |
598 |
*/ |