/* News functions. * * (C) 2003 Anope Team * Contact us at info@anope.org * * Please read COPYING and README for furhter details. * * Based on the original code of Epona by Lara. * Based on the original code of Services by Andy Church. * * $Id: news.c 387 2004-10-10 15:21:08Z geniusdex $ * */ #include "services.h" #include "pseudo.h" /*************************************************************************/ static int32 nnews = 0; static int32 news_size = 0; static NewsItem *news = 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; char *name; int msgs[MSG_MAX + 1]; }; /* *INDENT-OFF* */ 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 } }, { NEWS_RANDOM, "RANDOM", { NEWS_RANDOM_SYNTAX, NEWS_RANDOM_LIST_HEADER, NEWS_RANDOM_LIST_ENTRY, NEWS_RANDOM_LIST_NONE, NEWS_RANDOM_ADD_SYNTAX, NEWS_RANDOM_ADD_FULL, NEWS_RANDOM_ADDED, NEWS_RANDOM_DEL_SYNTAX, NEWS_RANDOM_DEL_NOT_FOUND, NEWS_RANDOM_DELETED, NEWS_RANDOM_DEL_NONE, NEWS_RANDOM_DELETED_ALL } } }; /* *INDENT-ON* */ static int *findmsgs(int16 type, char **typename) { int i; for (i = 0; i < lenof(msgarray); i++) { if (msgarray[i].type == type) { if (typename) *typename = msgarray[i].name; return msgarray[i].msgs; } } return NULL; } /*************************************************************************/ /* Called by the main OperServ routine in response to a NEWS command. */ 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); /*************************************************************************/ /****************************** Statistics *******************************/ /*************************************************************************/ void get_news_stats(long *nrec, long *memuse) { long mem; int i; mem = sizeof(NewsItem) * news_size; for (i = 0; i < nnews; i++) mem += strlen(news[i].text) + 1; *nrec = nnews; *memuse = mem; } /*************************************************************************/ /*********************** News item loading/saving ************************/ /*************************************************************************/ #define SAFE(x) do { \ if ((x) < 0) { \ if (!forceload) \ fatal("Read error on %s", NewsDBName); \ nnews = i; \ break; \ } \ } while (0) void load_news() { dbFILE *f; int i; int16 n; int32 tmp32; if (!(f = open_db(s_OperServ, NewsDBName, "r", NEWS_VERSION))) return; switch (i = get_file_version(f)) { case 9: case 8: case 7: SAFE(read_int16(&n, f)); nnews = n; if (nnews < 8) news_size = 16; else if (nnews >= 16384) news_size = 32767; else news_size = 2 * nnews; news = scalloc(sizeof(*news) * news_size, 1); if (!nnews) { close_db(f); return; } for (i = 0; i < nnews; i++) { SAFE(read_int16(&news[i].type, f)); SAFE(read_int32(&news[i].num, f)); SAFE(read_string(&news[i].text, f)); SAFE(read_buffer(news[i].who, f)); SAFE(read_int32(&tmp32, f)); news[i].time = tmp32; } break; default: fatal("Unsupported version (%d) on %s", i, NewsDBName); } /* switch (ver) */ close_db(f); } #undef SAFE /*************************************************************************/ #define SAFE(x) do { \ if ((x) < 0) { \ restore_db(f); \ log_perror("Write error on %s", NewsDBName); \ if (time(NULL) - lastwarn > WarningTimeout) { \ wallops(NULL, "Write error on %s: %s", NewsDBName, \ strerror(errno)); \ lastwarn = time(NULL); \ } \ return; \ } \ } while (0) void save_news() { dbFILE *f; int i; static time_t lastwarn = 0; if (!(f = open_db(s_OperServ, NewsDBName, "w", NEWS_VERSION))) return; SAFE(write_int16(nnews, f)); for (i = 0; i < nnews; i++) { SAFE(write_int16(news[i].type, f)); SAFE(write_int32(news[i].num, f)); SAFE(write_string(news[i].text, f)); SAFE(write_buffer(news[i].who, f)); SAFE(write_int32(news[i].time, f)); } close_db(f); } #undef SAFE void save_rdb_news() { #ifdef USE_RDB int i; NewsItem *ni; if (!rdb_open()) return; rdb_clear_table("anope_os_news"); for (i = 0; i < nnews; i++) { ni = &news[i]; rdb_save_news(ni); } rdb_close(); #endif } /*************************************************************************/ /***************************** News display ******************************/ /*************************************************************************/ void display_news(User * u, int16 type) { int msg; if (type == NEWS_LOGON) { msg = NEWS_LOGON_TEXT; } else if (type == NEWS_OPER) { msg = NEWS_OPER_TEXT; } else if (type == NEWS_RANDOM) { msg = NEWS_RANDOM_TEXT; } else { alog("news: Invalid type (%d) to display_news()", type); return; } if (type == NEWS_RANDOM) { static int current_news = -1; int count = 0; if (!nnews) return; while (count++ < nnews) { if (++current_news >= nnews) current_news = 0; if (news[current_news].type == type) { struct tm *tm; char timebuf[64]; tm = localtime(&news[current_news].time); strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT, tm); notice_lang(s_GlobalNoticer, u, msg, timebuf, news[current_news].text); return; } } } else { int i, count = 0; /* Number we're going to show--not more than 3 */ for (i = nnews - 1; i >= 0; i--) { if (count >= 3) break; if (news[i].type == type) count++; } while (++i < nnews) { if (news[i].type == type) { struct tm *tm; char timebuf[64]; tm = localtime(&news[i].time); strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT, tm); notice_lang(s_GlobalNoticer, u, msg, timebuf, news[i].text); } } } } /*************************************************************************/ /***************************** News editing ******************************/ /*************************************************************************/ /* Declared in extern.h */ int do_logonnews(User * u) { do_news(u, NEWS_LOGON); return MOD_CONT; } /* Declared in extern.h */ int do_opernews(User * u) { do_news(u, NEWS_OPER); return MOD_CONT; } /* Declared in extern.h */ int do_randomnews(User * u) { do_news(u, NEWS_RANDOM); return MOD_CONT; } /*************************************************************************/ /* Main news command handling routine. */ void do_news(User * u, short type) { int is_servadmin = is_services_admin(u); char *cmd = strtok(NULL, " "); char *typename; int *msgs; msgs = findmsgs(type, &typename); if (!msgs) { alog("news: 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_servadmin) do_news_add(u, type, msgs, typename); else notice_lang(s_OperServ, u, PERMISSION_DENIED); } else if (stricmp(cmd, "DEL") == 0) { if (is_servadmin) 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) { int i, count = 0; char timebuf[64]; struct tm *tm; for (i = 0; i < nnews; i++) { if (news[i].type == type) { if (count == 0) notice_lang(s_OperServ, u, msgs[MSG_LIST_HEADER]); tm = localtime(&news[i].time); strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_DATE_TIME_FORMAT, tm); notice_lang(s_OperServ, u, msgs[MSG_LIST_ENTRY], news[i].num, timebuf, *news[i].who ? news[i].who : "", news[i].text); count++; } } if (count == 0) notice_lang(s_OperServ, u, msgs[MSG_LIST_NONE]); else { notice_lang(s_OperServ, u, END_OF_ANY_LIST, "News"); } } /*************************************************************************/ /* Handle a {LOGON,OPER}NEWS ADD command. */ static void do_news_add(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_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 (32767 items). */ static int add_newsitem(User * u, const char *text, short type) { int i, num; if (nnews >= 32767) return -1; if (nnews >= news_size) { if (news_size < 8) news_size = 8; else news_size *= 2; news = srealloc(news, sizeof(*news) * news_size); } num = 0; for (i = nnews - 1; i >= 0; i--) { if (news[i].type == type) { num = news[i].num; break; } } news[nnews].type = type; news[nnews].num = num + 1; news[nnews].text = sstrdup(text); news[nnews].time = time(NULL); strscpy(news[nnews].who, u->nick, NICKMAX); nnews++; 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, " "); int i; 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); /* Reset the order - only the type where we are working on */ for (i = 0; i < nnews; i++) { if ((news[i].type == type) && (news[i].num > num)) news[i].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, short type) { int i; int count = 0; for (i = 0; i < nnews; i++) { if (news[i].type == type && (num == 0 || news[i].num == num)) { free(news[i].text); count++; nnews--; if (i < nnews) memcpy(news + i, news + i + 1, sizeof(*news) * (nnews - i)); i--; } } return count; } /*************************************************************************/