This repository has been archived on 2025-02-12. You can view files and clone it, but cannot push or open issues or pull requests.
NeoStats/modules/update/update.c

528 lines
13 KiB
C

/* NeoStats - IRC Statistical Services
** Copyright (c) 1999-2008 Adam Rutter, Justin Hammond, Mark Hetherington
** http://www.neostats.net/
**
** Portions Copyright (c) 2000-2008 ^Enigma^
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
** USA
**
** NeoStats CVS Identification
** $Id: update.c 3198 2007-08-19 06:23:08Z Fish $
*/
/* TODO:
* - Nothing at present
*/
#include "neostats.h"
#include "services.h"
#include "namedvars.h"
#include "mrss.h"
#include "update.h"
/** local structures */
static int update_set_updateenabled_cb( const CmdParams *cmdparams, SET_REASON reason );
static int update_set_updateurl_cb( const CmdParams *cmdparams, SET_REASON reason );
static int update_check_umode( const CmdParams *cmdparams );
static int update_checkupdate(const CmdParams *cmdparams);
static int update_report_update( const CmdParams *cmdparams);
/** Configuration structure */
static struct update_cfg
{
char updateurl[BUFSIZE];
unsigned int enabled;
} update_cfg;
/** Copyright info */
static const char *update_copyright[] =
{
"Copyright (c) 1999-2008, NeoStats",
"http://www.neostats.net/",
NULL
};
static const char *update_about[] =
{
"Automatically notify operators of New Versions"
"of NeoStats and its Modules",
NULL
};
static const char *update_help_updateinfo[] =
{
"Shows any available updates for NeoStats and its modules",
"Syntax: \2UPDATEINFO\2",
"",
"Will display any newer versions of modules or the Core NeoStats",
"that is available from the NeoStats website",
NULL
};
static const char *update_help_checkupdate[] =
{
"Checkers for new versions of NeoStats or the loaded modules",
"Syntax: \2CHECKUPDATE\2",
"",
"Will check for any new versions of NeoStats or the modules that are",
"currently loaded",
NULL
};
typedef struct updateinfo {
char *mod;
char *title;
char *ver;
char *description;
char *url;
} updateinfo;
hash_t *availableupdates;
nv_struct nv_updateinfo[] = {
{"mod", NV_PSTR, offsetof(updateinfo, mod), NV_FLG_RO, -1, -1},
{"ver", NV_PSTR, offsetof(updateinfo, ver), NV_FLG_RO, -1, -1},
{"description", NV_PSTR, offsetof(updateinfo, description), NV_FLG_RO, -1, -1},
{"url", NV_PSTR, offsetof(updateinfo, url), NV_FLG_RO, -1, -1},
NV_STRUCT_END()
};
/** Module info */
ModuleInfo module_info =
{
"Update",
"Update Notification Module",
update_copyright,
update_about,
NEOSTATS_VERSION,
CORE_MODULE_VERSION,
__DATE__,
__TIME__,
0,
0,
0,
};
/** Bot setting table */
static bot_setting update_settings[] =
{
{"UPDATEURL", &update_cfg.updateurl, SET_TYPE_STRING, 0, BUFSIZE, NS_ULEVEL_ADMIN, NULL, update_help_set_updateurl, update_set_updateurl_cb, ( void *)"http://www.neostats.net/feed.php" },
{"UPDATEENABLED", &update_cfg.enabled, SET_TYPE_BOOLEAN, 0, 0, NS_ULEVEL_ADMIN, NULL, update_help_set_updateenabled, update_set_updateenabled_cb, ( void* )1 },
NS_SETTING_END()
};
ModuleEvent module_events[] =
{
{EVENT_UMODE, update_check_umode, EVENT_FLAG_EXCLUDE_ME},
NS_EVENT_END()
};
static bot_cmd update_cmds[] = {
{"UPDATEINFO", update_report_update, 0, NS_ULEVEL_LOCOPER, update_help_updateinfo, 0, NULL, NULL},
{"CHECKUPDATE", update_checkupdate, 0, NS_ULEVEL_LOCOPER, update_help_checkupdate, 0, NULL, NULL},
NS_CMD_END()
};
unsigned int string_to_ver(const char *str)
{
static char lookup[] = "abcdef0123456789";
unsigned int maj = 0, min = 0, rev = 0, ver;
unsigned char *cptr, *idx;
int bits;
// do the major number
cptr = (unsigned char *)str;
for (; *cptr; cptr++)
{
if (*cptr == '.' || *cptr == '_')
{
cptr++;
break;
}
idx = (unsigned char *)strchr(lookup, tolower(*cptr));
if (!idx)
continue;
maj = (maj << 4) | ((char *)idx - lookup);
}
// do the minor number
for (bits = 2; *cptr && *cptr != '.' && *cptr != '_' && bits > 0; cptr++)
{
idx = (unsigned char *)strchr(lookup, tolower(*cptr));
if (!idx)
continue;
min = (min << 4) | ((char *)idx - lookup);
bits--;
}
// do the revision number
for (bits = 4; *cptr && bits > 0; cptr++)
{
idx = (unsigned char *)strchr(lookup, tolower(*cptr));
if (!idx)
continue;
rev = (rev << 4) | ((char *)idx - lookup);
bits--;
}
ver = (maj << 24) | (min << 16) | (rev << (4*bits));
return ver;
}
static int BuildMods( Module *mod_ptr, void *v) {
hash_t *loadedmods = (hash_t *)v;
hnode_create_insert(loadedmods, mod_ptr, mod_ptr->info->name);
return NS_FALSE;
}
static void StoreReport(mrss_item_t *item, const char *mod, const char *version) {
updateinfo *newitem;
/* regardless if this is already known or not, announce it to the world every time we check */
irc_chanalert(ns_botptr, "A new version of %s is available! /msg %s UPDATEINFO for more information", item->title, ns_botptr->u->name);
if ((newitem = hnode_find(availableupdates, mod)) != NULL) {
if (string_to_ver(version) <= string_to_ver(newitem->ver))
return;
/* else free the descriptions, versions, url structs */
ns_free(newitem->ver);
ns_free(newitem->description);
ns_free(newitem->url);
} else {
newitem = ns_malloc(sizeof(updateinfo));
newitem->mod = strdup(mod);
newitem->title = strdup(item->title);
hnode_create_insert(availableupdates, newitem, newitem->mod);
}
/* if we get here, we have a newitem ready for new vals */
newitem->ver = strdup(version);
newitem->description = strdup(item->description);
newitem->url = strdup(item->link);
/* ok, its stored.... now we just wait for new clients to signon in our umode and smode handlers */
return;
}
static void CheckModVersions(mrss_item_t *item, char *modname, Module *mod) {
mrss_tag_t *othertags;
char **av;
int ac = 0;
othertags = item->other_tags;
while (othertags) {
if (!ircstrcasecmp(othertags->name, "Version")) {
if (mod) {
/* av[0] - Main Version av[2] - SVN revison if ac > 1 */
ac = split_buf((char *)mod->info->version, &av);
} else {
ac = split_buf(me.version, &av);
}
if (string_to_ver(othertags->value) > string_to_ver(av[0])) {
StoreReport(item, modname, othertags->value);
}
ns_free(av);
}
othertags = othertags->next;
}
}
static void UpdateRSSHandler(void *userptr, int status, char *data, int datasize)
{
mrss_error_t ret;
mrss_t *mrss;
mrss_item_t *item;
mrss_tag_t *othertags;
hash_t *loadedmods;
Module *mod;
SET_SEGV_LOCATION();
if (status != NS_SUCCESS) {
nlog(LOG_WARNING, "RSS Update Feed download failed: %s", data);
return;
}
mrss = NULL;
mrss_new(&mrss);
ret = mrss_parse_buffer(data, datasize, &mrss);
if (ret != MRSS_OK) {
nlog(LOG_WARNING, "RSS Update Feed Parse failed: %s", mrss_strerror(ret));
mrss_free(mrss);
return;
}
/* build a hash of modules
* we do this every check rather than at Init/Sych time
* as we might have loaded/unloaded modules
*/
loadedmods = hash_create(HASHCOUNT_T_MAX, 0, 0);
ProcessModuleList(BuildMods, loadedmods);
item = mrss->item;
while (item)
{
othertags = item->other_tags;
while (othertags) {
if (!ircstrcasecmp(othertags->name, "Module")) {
mod = (Module *)hnode_find(loadedmods, othertags->value);
if (mod) {
CheckModVersions(item, othertags->value, mod);
} else if (!ircstrcasecmp(othertags->value, "NeoStats")) {
CheckModVersions(item, "NeoStats", NULL);
}
}
othertags = othertags->next;
}
item = item->next;
}
mrss_free(mrss);
hash_free_nodes(loadedmods);
hash_destroy(loadedmods);
}
static int update_check_updates(void *unused)
{
if (new_transfer(update_cfg.updateurl, NULL, NS_MEMORY, "", NULL, UpdateRSSHandler) != NS_SUCCESS) {
nlog(LOG_WARNING, "Download Update RSS Feed Failed");
}
return NS_SUCCESS;
}
/** @brief ModInit
*
* Init handler
* Loads connectserv configuration
*
* @param none
*
* @return NS_SUCCESS if suceeds else NS_FAILURE
*/
int ModInit( void )
{
SET_SEGV_LOCATION();
/* Load stored configuration */
ModuleConfig( update_settings );
return NS_SUCCESS;
}
/** @brief ModSynch
*
* Startup handler
* Introduce bot onto network
*
* @param none
*
* @return NS_SUCCESS if suceeds else NS_FAILURE
*/
int ModSynch( void )
{
SET_SEGV_LOCATION();
if (add_services_set_list(update_settings) != NS_SUCCESS) {
return NS_FAILURE;
}
if (add_services_cmd_list(update_cmds) != NS_SUCCESS) {
return NS_FAILURE;
}
if (update_cfg.enabled == 1) {
/* updates are enabled. Setup Timer and load first update */
AddTimer(TIMER_TYPE_INTERVAL, update_check_updates, "CheckUpdates", 86400, NULL);
update_check_updates(NULL);
}
availableupdates = nv_hash_create(HASHCOUNT_T_MAX,0, 0, "Update", nv_updateinfo, NV_FLAGS_RO, NULL);
return NS_SUCCESS;
}
/** @brief ModFini
*
* Fini handler
*
* @param none
*
* @return NS_SUCCESS if suceeds else NS_FAILURE
*/
int ModFini( void )
{
hscan_t scan;
hnode_t *node;
updateinfo *item;
SET_SEGV_LOCATION();
del_services_set_list(update_settings);
del_services_cmd_list(update_cmds);
hash_scan_begin(&scan, availableupdates);
while ( ( node = hash_scan_next (&scan)) != NULL) {
item = hnode_get(node);
ns_free(item->mod);
ns_free(item->title);
ns_free(item->ver);
ns_free(item->description);
ns_free(item->url);
ns_free(item);
hash_scan_delete_destroy_node(availableupdates, node);
}
nv_hash_destroy(availableupdates, "Update");
return NS_SUCCESS;
}
static int update_report_update( const CmdParams *cmdparams) {
hscan_t scan;
hnode_t *node;
updateinfo *item;
char *descline;
char *freeme;
char *line;
int updates;
updates = hash_count(availableupdates);
if (updates <= 0)
return NS_SUCCESS;
if (cmdparams->eventid != EVENT_UMODE)
irc_prefmsg(ns_botptr, cmdparams->source, "There are %d updates available\n", updates);
hash_scan_begin(&scan, availableupdates);
while ( ( node = hash_scan_next (&scan)) != NULL) {
item = hnode_get(node);
irc_prefmsg(ns_botptr, cmdparams->source, "A new update for %s is available", item->title);
irc_prefmsg(ns_botptr, cmdparams->source, "The new version is %s and can be downloaded from %s", item->ver, item->url);
irc_prefmsg(ns_botptr, cmdparams->source, "Short ChangeLog:");
/* we have to deal with newlines in the descrition here */
descline = strdup(item->description);
freeme = descline;
while ((line = strsep(&descline, "\n")))
irc_prefmsg(ns_botptr, cmdparams->source, "%s", line);
ns_free(freeme);
}
return NS_SUCCESS;
}
static int update_checkupdate(const CmdParams *cmdparams) {
if (new_transfer(update_cfg.updateurl, NULL, NS_MEMORY, "", NULL, UpdateRSSHandler) != NS_SUCCESS) {
nlog(LOG_WARNING, "Download Update RSS Feed Failed");
irc_prefmsg(ns_botptr, cmdparams->source, "Update Checking Failed");
return NS_SUCCESS;
}
irc_prefmsg(ns_botptr, cmdparams->source, "Checking for new updates. If there is a update available, it will be announced in the services channel");
return NS_SUCCESS;
}
/** @brief update_check_uoper
*
* umode event handler
* report umode changes
*
* @params cmdparams pointer to commands param struct
*
* @return NS_SUCCESS if suceeds else NS_FAILURE
*/
static int update_check_umode( const CmdParams *cmdparams )
{
/* Mask of modes we will handle */
static const unsigned int OperUmodes =
UMODE_NETADMIN |
UMODE_TECHADMIN |
UMODE_ADMIN |
UMODE_COADMIN |
UMODE_SADMIN |
UMODE_OPER |
UMODE_LOCOP |
UMODE_SERVICES;
unsigned int mask;
int add = 1;
const char *modes;
SET_SEGV_LOCATION();
/* if no updates, then ignore this */
if (hash_count(availableupdates) == 0)
return NS_SUCCESS;
modes = cmdparams->param;
while( *modes != '\0' )
{
switch( *modes )
{
case '+':
add = 1;
break;
case '-':
add = 0;
break;
default:
mask = UmodeCharToMask( *modes );
if( (OperUmodes & mask) && (add == 1))
update_report_update(cmdparams);
break;
}
modes++;
}
return NS_SUCCESS;
}
/** @brief update_set_updateurl_cb
*
* Set callback for updateurl
* Check the supplied URL is valid
*
* @params cmdparams pointer to commands param struct
* @params reason for SET
*
* @return NS_SUCCESS if suceeds else NS_FAILURE
*/
static int update_set_updateurl_cb( const CmdParams *cmdparams, SET_REASON reason )
{
if( reason == SET_VALIDATE )
{
}
return NS_SUCCESS;
}
/** @brief update_set_updateenabled_cb
*
* Set callback for updateenabled
* Turn on/off the timer
*
* @params cmdparams pointer to commands param struct
* @params reason for SET
*
* @return NS_SUCCESS if suceeds else NS_FAILURE
*/
static int update_set_updateenabled_cb( const CmdParams *cmdparams, SET_REASON reason )
{
if( reason == SET_VALIDATE )
{
}
return NS_SUCCESS;
}