2019-01-23 09:35:39 +01:00

599 lines
16 KiB
C

/* News module.
* Based on code by Andrew Kempe (TheShadow).
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "commands.h"
#include "databases.h"
#include "language.h"
#include "operserv.h"
#include "news.h"
/*************************************************************************/
static Module *module_operserv;
static void do_logonnews(User *u);
static void do_opernews(User *u);
static Command cmds[] = {
/* Anyone can use *NEWS LIST, but *NEWS {ADD,DEL} are reserved for
* Services operators. (The command routines check permissions.) */
{"LOGONNEWS", do_logonnews, NULL, NEWS_HELP_LOGON, -1,-1},
{"OPERNEWS", do_opernews, NULL, NEWS_HELP_OPER, -1,-1},
{NULL}
};
/*************************************************************************/
/* List of messages for each news type. This simplifies message sending. */
#define MSG_SYNTAX 0
#define MSG_LIST_HEADER 1
#define MSG_LIST_ENTRY 2
#define MSG_LIST_NONE 3
#define MSG_ADD_SYNTAX 4
#define MSG_ADD_FULL 5
#define MSG_ADDED 6
#define MSG_DEL_SYNTAX 7
#define MSG_DEL_NOT_FOUND 8
#define MSG_DELETED 9
#define MSG_DEL_NONE 10
#define MSG_DELETED_ALL 11
#define MSG_MAX 11
struct newsmsgs {
int16 type;
const char *name;
int msgs[MSG_MAX+1];
};
static struct newsmsgs msgarray[] = {
{ NEWS_LOGON, "LOGON",
{ NEWS_LOGON_SYNTAX,
NEWS_LOGON_LIST_HEADER,
NEWS_LOGON_LIST_ENTRY,
NEWS_LOGON_LIST_NONE,
NEWS_LOGON_ADD_SYNTAX,
NEWS_LOGON_ADD_FULL,
NEWS_LOGON_ADDED,
NEWS_LOGON_DEL_SYNTAX,
NEWS_LOGON_DEL_NOT_FOUND,
NEWS_LOGON_DELETED,
NEWS_LOGON_DEL_NONE,
NEWS_LOGON_DELETED_ALL
}
},
{ NEWS_OPER, "OPER",
{ NEWS_OPER_SYNTAX,
NEWS_OPER_LIST_HEADER,
NEWS_OPER_LIST_ENTRY,
NEWS_OPER_LIST_NONE,
NEWS_OPER_ADD_SYNTAX,
NEWS_OPER_ADD_FULL,
NEWS_OPER_ADDED,
NEWS_OPER_DEL_SYNTAX,
NEWS_OPER_DEL_NOT_FOUND,
NEWS_OPER_DELETED,
NEWS_OPER_DEL_NONE,
NEWS_OPER_DELETED_ALL
}
}
};
static int *findmsgs(int16 type, char **typename) {
int i;
for (i = 0; i < lenof(msgarray); i++) {
if (msgarray[i].type == type) {
if (typename)
*typename = (char *)msgarray[i].name;
return msgarray[i].msgs;
}
}
return NULL;
}
/*************************************************************************/
/* Main handler for NEWS commands. */
static void do_news(User *u, int16 type);
/* Lists all a certain type of news. */
static void do_news_list(User *u, int16 type, int *msgs);
/* Add news items. */
static void do_news_add(User *u, int16 type, int *msgs, const char *typename);
static int add_newsitem(User *u, const char *text, int16 type);
/* Delete news items. */
static void do_news_del(User *u, int16 type, int *msgs, const char *typename);
static int del_newsitem(int num, int16 type);
/*************************************************************************/
/**************************** Database stuff *****************************/
/*************************************************************************/
static NewsItem *newslist = NULL;
static int32 newslist_count = 0;
static int newslist_iterator;
/*************************************************************************/
static void *new_news(void)
{
return scalloc(1, sizeof(NewsItem));
}
/*************************************************************************/
static void free_news(void *record)
{
NewsItem *news = record;
if (news) {
free(news->text);
free(news);
}
}
/*************************************************************************/
NewsItem *add_news(NewsItem *newsitem)
{
if (newslist_count >= MAX_NEWS)
fatal("add_news(): too many news items!");
ARRAY_EXTEND(newslist);
memcpy(&newslist[newslist_count-1], newsitem, sizeof(NewsItem));
newslist[newslist_count-1].next =
(NewsItem *)(long)(newslist_count-1); /*index*/
free(newsitem);
return &newslist[newslist_count-1];
}
/*************************************************************************/
void del_news(NewsItem *newsitem)
{
int num = (int)(long)(newsitem->next);
if (num < 0 || num >= newslist_count) {
module_log("del_news(): invalid index %d in news item at %p",
num, newsitem);
return;
}
free(newsitem->text);
ARRAY_REMOVE(newslist, num);
if (num < newslist_iterator)
newslist_iterator--;
while (num < newslist_count) {
newslist[num].next = (NewsItem *)(long)num;
num++;
}
}
/*************************************************************************/
NewsItem *get_news(int16 type, int32 num)
{
int i;
ARRAY_FOREACH (i, newslist) {
if (newslist[i].type == type && newslist[i].num == num)
break;
}
return i<newslist_count ? &newslist[i] : NULL;
}
/*************************************************************************/
NewsItem *put_news(NewsItem *news)
{
return news;
}
/*************************************************************************/
NewsItem *first_news(void)
{
newslist_iterator = 0;
return next_news();
}
NewsItem *next_news(void)
{
if (newslist_iterator >= newslist_count)
return NULL;
return &newslist[newslist_iterator++];
}
/*************************************************************************/
int news_count(void)
{
return newslist_count;
}
/*************************************************************************/
/* Free all memory used by database tables. */
static void clean_dbtables(void)
{
int i;
ARRAY_FOREACH (i, newslist)
free(newslist[i].text);
free(newslist);
newslist = NULL;
newslist_count = 0;
}
/*************************************************************************/
/* News database table info */
static DBField news_dbfields[] = {
{ "type", DBTYPE_INT16, offsetof(NewsItem,type) },
{ "num", DBTYPE_INT32, offsetof(NewsItem,num) },
{ "text", DBTYPE_STRING, offsetof(NewsItem,text) },
{ "who", DBTYPE_BUFFER, offsetof(NewsItem,who), NICKMAX },
{ "time", DBTYPE_TIME, offsetof(NewsItem,time) },
{ NULL }
};
static DBTable news_dbtable = {
.name = "news",
.newrec = new_news,
.freerec = free_news,
.insert = (void *)add_news,
.first = (void *)first_news,
.next = (void *)next_news,
.fields = news_dbfields,
};
/*************************************************************************/
/***************************** News display ******************************/
/*************************************************************************/
static void display_news(User *u, int16 type, time_t min_time)
{
NewsItem *news, *disp[NEWS_DISPCOUNT];
int count = 0; /* Number we're going to show--not more than 3 */
int msg;
if (type == NEWS_LOGON) {
msg = NEWS_LOGON_TEXT;
} else if (type == NEWS_OPER) {
msg = NEWS_OPER_TEXT;
} else {
module_log("Invalid type (%d) to display_news()", type);
return;
}
for (news = first_news(); news; news = next_news()) {
if (count >= NEWS_DISPCOUNT)
break;
if (news->type == type && (!min_time || news->time > min_time)) {
disp[count] = news;
count++;
}
}
while (--count >= 0) {
char timebuf[BUFSIZE];
strftime_lang(timebuf, sizeof(timebuf), u->ngi,
STRFTIME_SHORT_DATE_FORMAT, disp[count]->time);
notice_lang(s_GlobalNoticer, u, msg, timebuf, disp[count]->text);
}
}
/*************************************************************************/
/***************************** News editing ******************************/
/*************************************************************************/
static void do_logonnews(User *u)
{
do_news(u, NEWS_LOGON);
}
static void do_opernews(User *u)
{
do_news(u, NEWS_OPER);
}
/*************************************************************************/
/* Main news command handling routine. */
static void do_news(User *u, int16 type)
{
const char *cmd = strtok(NULL, " ");
char *typename;
int *msgs;
msgs = findmsgs(type, &typename);
if (!msgs) {
module_log("Invalid type to do_news()");
return;
}
if (!cmd)
cmd = "";
if (stricmp(cmd, "LIST") == 0) {
do_news_list(u, type, msgs);
} else if (stricmp(cmd, "ADD") == 0) {
if (is_services_oper(u))
do_news_add(u, type, msgs, typename);
else
notice_lang(s_OperServ, u, PERMISSION_DENIED);
} else if (stricmp(cmd, "DEL") == 0) {
if (is_services_oper(u))
do_news_del(u, type, msgs, typename);
else
notice_lang(s_OperServ, u, PERMISSION_DENIED);
} else {
char buf[32];
snprintf(buf, sizeof(buf), "%sNEWS", typename);
syntax_error(s_OperServ, u, buf, msgs[MSG_SYNTAX]);
}
}
/*************************************************************************/
/* Handle a {LOGON,OPER}NEWS LIST command. */
static void do_news_list(User *u, int16 type, int *msgs)
{
NewsItem *news;
int count = 0;
char timebuf[64];
for (news = first_news(); news; news = next_news()) {
if (news->type == type) {
if (count == 0)
notice_lang(s_OperServ, u, msgs[MSG_LIST_HEADER]);
strftime_lang(timebuf, sizeof(timebuf), u->ngi,
STRFTIME_DATE_TIME_FORMAT, news->time);
notice_lang(s_OperServ, u, msgs[MSG_LIST_ENTRY],
news->num, timebuf,
*news->who ? news->who : "<unknown>",
news->text);
count++;
}
}
if (count == 0)
notice_lang(s_OperServ, u, msgs[MSG_LIST_NONE]);
}
/*************************************************************************/
/* Handle a {LOGON,OPER}NEWS ADD command. */
static void do_news_add(User *u, int16 type, int *msgs, const char *typename)
{
char *text = strtok_remaining();
if (!text) {
char buf[32];
snprintf(buf, sizeof(buf), "%sNEWS", typename);
syntax_error(s_OperServ, u, buf, msgs[MSG_ADD_SYNTAX]);
} else {
int n = add_newsitem(u, text, type);
if (n < 0)
notice_lang(s_OperServ, u, msgs[MSG_ADD_FULL]);
else
notice_lang(s_OperServ, u, msgs[MSG_ADDED], n);
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
}
}
/* Actually add a news item. Return the number assigned to the item, or -1
* if the news list is full (MAX_NEWS items).
*/
static int add_newsitem(User *u, const char *text, int16 type)
{
NewsItem *news;
int num;
if (news_count() >= MAX_NEWS)
return -1;
num = 0;
for (news = first_news(); news; news = next_news()) {
if (news->type == type && num < news->num)
num = news->num;
}
if (num+1 < num) {
module_log("BUG: add_newsitem(): news number overflow (MAX_NEWS"
" too small?)");
return -1;
}
news = scalloc(1, sizeof(*news));
news->type = type;
news->num = num+1;
news->text = sstrdup(text);
news->time = time(NULL);
strbcpy(news->who, u->nick);
add_news(news);
return num+1;
}
/*************************************************************************/
/* Handle a {LOGON,OPER}NEWS DEL command. */
static void do_news_del(User *u, int16 type, int *msgs, const char *typename)
{
char *text = strtok(NULL, " ");
if (!text) {
char buf[32];
snprintf(buf, sizeof(buf), "%sNEWS", typename);
syntax_error(s_OperServ, u, buf, msgs[MSG_DEL_SYNTAX]);
} else {
if (stricmp(text, "ALL") != 0) {
int num = atoi(text);
if (num > 0 && del_newsitem(num, type))
notice_lang(s_OperServ, u, msgs[MSG_DELETED], num);
else
notice_lang(s_OperServ, u, msgs[MSG_DEL_NOT_FOUND], num);
} else {
if (del_newsitem(0, type))
notice_lang(s_OperServ, u, msgs[MSG_DELETED_ALL]);
else
notice_lang(s_OperServ, u, msgs[MSG_DEL_NONE]);
}
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
}
}
/* Actually delete a news item. If `num' is 0, delete all news items of
* the given type. Returns the number of items deleted.
*/
static int del_newsitem(int num, int16 type)
{
NewsItem *news;
int count = 0;
for (news = first_news(); news; news = next_news()) {
if (news->type == type && (num == 0 || news->num == num)) {
del_news(news);
count++;
}
}
return count;
}
/*************************************************************************/
/*************************** Callback routines ***************************/
/*************************************************************************/
/* Callback for users logging on. */
static int new_user_callback(User *u, int ac, char **av, int reconnect)
{
display_news(u, NEWS_LOGON, reconnect ? u->signon : 0);
return 0;
}
/*************************************************************************/
/* Callback to watch for mode +o to send oper news. */
static int user_mode_callback(User *u, int modechar, int add)
{
if (modechar == 'o' && add)
display_news(u, NEWS_OPER, 0);
return 0;
}
/*************************************************************************/
/* OperServ STATS ALL callback. */
static int do_stats_all(User *user, const char *s_OperServ)
{
int32 count, mem;
NewsItem *news;
count = mem = 0;
for (news = first_news(); news; news = next_news()) {
count++;
mem += sizeof(*news);
if (news->text)
mem += strlen(news->text)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_NEWS_MEM,
count, (mem+512) / 1024);
return 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ NULL }
};
/*************************************************************************/
int init_module()
{
module_operserv = find_module("operserv/main");
if (!module_operserv) {
module_log("Main OperServ module not loaded");
return 0;
}
use_module(module_operserv);
if (!register_commands(module_operserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
if (!add_callback(NULL, "user create", new_user_callback)
|| !add_callback(NULL, "user MODE", user_mode_callback)
|| !add_callback(module_operserv, "STATS ALL", do_stats_all)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
if (!register_dbtable(&news_dbtable)) {
module_log("Unable to register database table");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
unregister_dbtable(&news_dbtable);
clean_dbtables();
remove_callback(NULL, "user create", new_user_callback);
remove_callback(NULL, "user MODE", user_mode_callback);
if (module_operserv) {
remove_callback(module_operserv, "STATS ALL", do_stats_all);
unregister_commands(module_operserv, cmds);
unuse_module(module_operserv);
module_operserv = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/