Files
bongo/src/libs/msgapi/msgcollector.c
T
2009-08-18 14:08:17 +00:00

806 lines
22 KiB
C

/****************************************************************************
* <Novell-copyright>
* Copyright (c) 2006 Novell, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*
* 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, contact Novell, Inc.
*
* To contact Novell about this file by physical or electronic mail, you
* may find current contact information at www.novell.com.
* </Novell-copyright>
****************************************************************************/
#include <config.h>
#include <xpl.h>
#include <memmgr.h>
#include <logger.h>
#include <bongoutil.h>
#include <bongoagent.h>
#include <nmap.h>
#include <bongostore.h>
#include <msgapi.h>
#include <connio.h>
#include <bongojson.h>
#include <bongocal.h>
#include <nmlib.h>
#include <bongocal-raw.h>
#include <ical-wrapper.h>
#include <curl/curl.h>
#include "msghttp.h"
static size_t write_data(void *buffer, size_t size, size_t nmemb, void *stream);
BongoList *GetSubscribedCals(Connection *conn);
typedef struct _SubscribedCal {
int64_t guid;
char *name;
char *color;
char *url;
char *urlUsername;
char *urlPassword;
long lastImportTime;
} SubscribedCal;
static SubscribedCal *
SubscribedCalNew(int64_t guid,
const char *name,
const char *color,
const char *url,
const char *urlUsername,
const char *urlPassword,
long lastImportTime)
{
SubscribedCal *cal = MemMalloc(sizeof(SubscribedCal));
cal->guid = guid;
cal->name = MemStrdup(name);
cal->color = color ? MemStrdup(color) : NULL;
cal->url = MemStrdup(url);
cal->urlUsername = urlUsername ? MemStrdup(urlUsername) : NULL;
cal->urlPassword = urlPassword ? MemStrdup(urlPassword) : NULL;
cal->lastImportTime = lastImportTime;
return cal;
}
static void
SubscribedCalFree(SubscribedCal *cal)
{
MemFree(cal->name);
MemFree(cal->url);
MemFree(cal->urlUsername);
MemFree(cal->urlPassword);
MemFree(cal->color);
MemFree(cal);
}
static void
BongoSubscribedCalListFree(BongoList *list)
{
BongoList *cur = list;
while (cur) {
SubscribedCalFree(cur->data);
cur = cur->next;
}
BongoListFree(list);
}
static char *
ReadStream(char *s, size_t size, void *d)
{
char *c = fgets(s, size, (FILE*)d);
return c;
}
static icalcomponent *
ParseIcs(FILE *fh)
{
icalparser *parser;
icalcomponent *component;
parser = icalparser_new();
icalparser_set_gen_data(parser, fh);
component = icalparser_parse(parser, ReadStream);
if (component) {
icalcomponent_strip_errors(component);
}
icalparser_free(parser);
return component;
}
static Connection *
ConnectToStore(const char *user)
{
char buffer[CONN_BUFSIZE + 1];
struct sockaddr_in addr;
Connection *conn;
int ret;
if ((conn = NMAPConnect("127.0.0.1", NULL)) == NULL) {
printf("bongocollector: couldn't connect to nmap\n");
return NULL;
}
if (!NMAPAuthenticateToStore(conn, buffer, sizeof(buffer))) {
printf("bongocollector: couldn't authenticate\n");
ConnClose(conn);
return NULL;
}
ret = NMAPRunCommandF(conn, buffer, sizeof(buffer), "USER %s\r\n", user);
if (ret != 1000) {
printf("bongocollector: couldn't select user '%s'\n", user);
ConnClose(conn);
return NULL;
}
ret = NMAPRunCommandF(conn, buffer, sizeof(buffer), "STORE %s\r\n", user);
if (ret != 1000) {
printf("bongocollector: couldn't change store '%s'\n", user);
ConnClose(conn);
return NULL;
}
return conn;
}
static int
ImportObject(Connection *conn, BongoJsonObject *obj, const char *calendar, const char *guid)
{
char *doc;
char buffer[CONN_BUFSIZE + 1];
int len;
doc = BongoJsonObjectToString(obj);
len = strlen(doc);
if (guid) {
NMAPSendCommandF(conn, "REPLACE %s %d \"L/calendars/%s\"\r\n", guid, len, calendar);
} else {
NMAPSendCommandF(conn, "WRITE /events 3 %d \"L/calendars/%s\"\r\n", len, calendar);
}
if (NMAPReadAnswer(conn, buffer, sizeof(buffer), TRUE) == 2002) {
ConnWrite(conn, doc, len);
ConnFlush(conn);
if (NMAPReadAnswer(conn, buffer, sizeof(buffer), TRUE) == 1000) {
MemFree(doc);
return MSG_COLLECT_OK;
}
}
MemFree(doc);
return MSG_COLLECT_ERROR_STORE;
}
typedef struct {
char *guid;
char *dtstamp;
} EventData;
static void
FreeEventData (void *data)
{
if (data) {
MemFree (((EventData *)data)->guid);
MemFree (((EventData *)data)->dtstamp);
MemFree (data);
}
}
static BongoHashtable *
ReadCalendar(Connection *conn, const char *calendar)
{
int ccode;
char buffer[CONN_BUFSIZE + 1];
BongoHashtable *guids;
GArray *oldDuplicates;
unsigned int i;
char *guid;
oldDuplicates = g_array_sized_new(FALSE, FALSE, sizeof(char *), 8);
guids = BongoHashtableCreateFull(16,
(HashFunction)BongoStringHash,
(CompareFunction)strcmp,
g_free,
FreeEventData);
ccode = NMAPRunCommandF(conn, buffer, CONN_BUFSIZE, "EVENTS \"C/calendars/%s\" Pnmap.event.uid,nmap.event.stamp\r\n", calendar);
while (ccode == 2001) {
char *p;
p = strchr(buffer, ' ');
if (p) {
char *uid = NULL;
char *dtstamp = NULL;
*p = '\0';
guid = MemStrdup(buffer);
if (NMAPReadTextPropertyResponse(conn, "nmap.event.uid", buffer, CONN_BUFSIZE) == 2001) {
EventData *oldData;
oldData = BongoHashtableGet(guids, buffer);
if (oldData) {
/*fprintf (stderr, "Found duplicate of ical uid %s in guid %s\n", buffer, guid);*/
uid = MemStrdup(guid);
g_array_append_val(oldDuplicates, uid);
uid = NULL;
} else {
uid = MemStrdup(buffer);
}
}
if (NMAPReadTextPropertyResponse(conn, "nmap.event.stamp", buffer, CONN_BUFSIZE) == 2001 &&
uid != NULL) {
dtstamp = MemStrdup(buffer);
}
if (uid) {
EventData *data;
data = MemNew(EventData, 1);
data->guid = guid;
data->dtstamp = dtstamp;
BongoHashtablePut(guids, uid, data);
} else {
MemFree(guid);
}
}
ccode = NMAPReadAnswer(conn, buffer, CONN_BUFSIZE, TRUE);
}
for (i = 0; i < oldDuplicates->len; i++) {
guid = g_array_index(oldDuplicates, char *, i);
if (ccode == 1000) {
NMAPSendCommandF(conn, "UNLINK /calendars/%s %s\r\n", calendar, guid);
NMAPReadAnswer(conn, buffer, sizeof(buffer), TRUE);
}
MemFree(guid);
}
g_array_free(oldDuplicates, TRUE);
if (ccode == 1000) {
return guids;
} else {
BongoHashtableDelete(guids);
return NULL;
}
}
typedef struct {
Connection *conn;
const char *calendarName;
char *buffer;
int bufferLen;
} RemoveGuidForeachData;
static void
RemoveGuidForeach (void *key, void *value, void *data)
{
RemoveGuidForeachData *rgfd = data;
EventData *evtData = value;
int ccode;
NMAPSendCommandF(rgfd->conn, "UNLINK /calendars/%s %s\r\n", rgfd->calendarName, evtData->guid);
ccode = NMAPReadAnswer(rgfd->conn, rgfd->buffer, rgfd->bufferLen, TRUE);
}
static int
ImportJson(GArray *objects,
const char *user,
const char *calendarName,
const char *icsName,
Connection *existingConn)
{
char buffer[CONN_BUFSIZE + 1];
Connection *conn;
int ret;
unsigned int i;
BongoHashtable *guids;
if (existingConn) {
conn = existingConn;
} else {
conn = ConnectToStore(user);
if (!conn) {
return MSG_COLLECT_ERROR_STORE;
}
}
if (icsName) {
snprintf(buffer, sizeof(buffer), "/calendars/%s", calendarName);
if (NMAPSetTextPropertyFilename(conn, buffer, "bongo.calendar.icsname", icsName, strlen(icsName)) < 0) {
ret = MSG_COLLECT_ERROR_STORE;
goto done;
}
}
guids = ReadCalendar(conn, calendarName);
ret = MSG_COLLECT_OK;
for (i = 0; i < objects->len; i++) {
BongoJsonObject *event;
if (BongoJsonArrayGetObject(objects, i, &event) == BONGO_JSON_OK) {
BongoCalObject *cal;
EventData *evtData = NULL;
int res = MSG_COLLECT_OK;
char *guid = NULL;
BOOL import = TRUE;
cal = BongoCalObjectNew(event);
assert (cal != NULL);
if (guids) {
const char *uid;
const char *stamp;
uid = BongoCalObjectGetUid(cal);
if (uid) {
evtData = BongoHashtableRemoveFull(guids, uid, TRUE, FALSE);
}
if (evtData) {
guid = evtData->guid;
if (evtData->dtstamp) {
stamp = BongoCalObjectGetStamp(cal);
if (stamp && !strcmp (stamp, evtData->dtstamp)) {
/* fprintf (stderr, "Skipping guid: %s uid: %s stamp: %s\n",
guid, uid, stamp); */
import = FALSE;
}
}
}
}
if (import) {
res = ImportObject(conn, event, calendarName, guid);
}
FreeEventData (evtData);
if (res != MSG_COLLECT_OK) {
ret = res;
}
BongoCalObjectFree(cal, FALSE);
}
}
/* remove guids that are no longer in the ics file */
if (guids) {
RemoveGuidForeachData rtfd;
rtfd.conn = conn;
rtfd.calendarName = calendarName;
rtfd.buffer = buffer;
rtfd.bufferLen = sizeof (buffer);
BongoHashtableForeach(guids, RemoveGuidForeach, &rtfd);
BongoHashtableDelete(guids);
}
done:
if (!existingConn) {
ConnClose(conn);
ConnFree(conn);
}
return ret;
}
int
MsgImportIcs(FILE *fh,
const char *user,
const char *calendarName,
const char *calendarColor,
Connection *conn)
{
icalcomponent *component;
BongoJsonObject *obj;
BongoCalObject *cal;
GArray *objects;
int ret;
const char *icsName;
char *icsNameDup = NULL;
component = ParseIcs(fh);
if (!component) {
return MSG_COLLECT_ERROR_PARSE;
}
obj = BongoCalIcalToJson(component);
icalcomponent_free(component);
if (!obj) {
return MSG_COLLECT_ERROR_PARSE;
}
cal = BongoCalObjectNew(obj);
icsName = (char *)BongoCalObjectGetCalendarName(cal);
if (icsName) {
icsNameDup = MemStrdup(icsName);
}
BongoCalObjectStripSystemTimezones(cal);
BongoCalObjectFree(cal, FALSE);
objects = BongoCalSplit(obj);
if (!objects) {
return MSG_COLLECT_ERROR_PARSE;
}
ret = ImportJson(objects, user, calendarName, icsNameDup, conn);
if (icsNameDup) {
MemFree(icsNameDup);
}
BongoJsonArrayFree(objects);
return ret;
}
BongoList *
GetSubscribedCals(Connection *conn)
{
char infoBuffer[CONN_BUFSIZE + 1];
char urlBuffer[CONN_BUFSIZE + 1];
char urlUsernameBuffer[CONN_BUFSIZE + 1];
char urlPasswordBuffer[CONN_BUFSIZE + 1];
char ownerBuffer[CONN_BUFSIZE + 1];
char *info[7];
NMAPSendCommandF(conn, "LIST /calendars Pbongo.calendar.url,bongo.calendar.username,bongo.calendar.password,bongo.calendar.owner,bongo.calendar.date\r\n");
BongoList *ret = NULL;
while (NMAPReadAnswer(conn, infoBuffer, sizeof(infoBuffer), TRUE) == 2001) {
uint64_t guid;
char *endp;
long date;
int urlccode;
int urlusernameccode;
int urlpasswordccode;
int ownerccode;
int dateccode;
urlccode = NMAPReadTextPropertyResponse(conn, "bongo.calendar.url",
urlBuffer, sizeof(urlBuffer));
urlusernameccode = NMAPReadTextPropertyResponse(conn, "bongo.calendar.username",
urlUsernameBuffer, sizeof(urlUsernameBuffer));
urlpasswordccode = NMAPReadTextPropertyResponse(conn, "bongo.calendar.password",
urlPasswordBuffer, sizeof(urlPasswordBuffer));
ownerccode = NMAPReadTextPropertyResponse(conn, "bongo.calendar.owner",
ownerBuffer, sizeof(ownerBuffer));
dateccode = NMAPReadDecimalPropertyResponse(conn, "bongo.calendar.date",
&date);
if (urlccode == 3245) {
/* The current calendar is not a subscription */
continue;
}
if (ownerccode != 3245) {
/* The current calendar is a local subscription, it won't be collected */
continue;
}
if (dateccode == 3245) {
date = 0;
}
if (urlccode != 2001) {
continue;
}
CommandSplit(infoBuffer, info, 7);
guid = HexToUInt64(info[0], &endp);
SubscribedCal *cal = SubscribedCalNew(guid, info[6], NULL, urlBuffer,
urlusernameccode == 2001 ? urlUsernameBuffer : NULL,
urlpasswordccode == 2001 ? urlPasswordBuffer : NULL,
date);
ret = BongoListPrepend(ret, cal);
}
return ret;
}
static int
MsgImportSubscribedCal(const char *user, SubscribedCal *cal, Connection *conn, uint64_t *guidOut)
{
char buffer[CONN_BUFSIZE + 1];
int ccode;
char *info[7];
char *endp;
uint64_t guid = 0;
ccode = NMAPRunCommandF(conn, buffer, sizeof(buffer),
"INFO \"/calendars/%s\"\r\n", cal->name);
if (ccode == 4224) {
/* create calendar */
ccode = NMAPRunCommandF(conn, buffer, sizeof(buffer),
"WRITE /calendars %d 0 \"F%s\"\r\n",
STORE_DOCTYPE_CALENDAR, cal->name);
ccode = NMAPReadAnswer(conn, buffer, sizeof(buffer), TRUE);
if (ccode == 1000) {
CommandSplit(buffer, info, 2);
guid = HexToUInt64(info[0], &endp);
NMAPSetTextProperty(conn, guid, "bongo.calendar.url",
cal->url, strlen(cal->url));
NMAPSetTextProperty(conn, guid, "bongo.calendar.type",
"web", strlen("web"));
if (cal->urlUsername) {
NMAPSetTextProperty(conn, guid, "bongo.calendar.urlusername",
cal->urlUsername, strlen(cal->urlUsername));
}
if (cal->urlPassword) {
NMAPSetTextProperty(conn, guid, "bongo.calendar.urlpassword",
cal->urlPassword, strlen(cal->urlPassword));
}
if (cal->color) {
NMAPSetTextProperty(conn, guid, "bongo.calendar.color",
cal->color, strlen(cal->color));
}
}
} else if (ccode == 2001) {
CommandSplit(buffer, info, 7);
guid = HexToUInt64(info[0], &endp);
/* eat the 1000 OK line */
NMAPReadAnswer(conn, buffer, sizeof(buffer), TRUE);
}
ccode = MsgImportIcsUrl(user, cal->name, cal->color, cal->url, cal->urlUsername, cal->urlPassword, conn);
*guidOut = guid;
return ccode;
}
static int
MsgImportCal(const char *user, SubscribedCal *cal, Connection *conn, uint64_t *guidOut)
{
char buffer[CONN_BUFSIZE + 1];
int ccode;
char *info[7];
char *endp;
uint64_t guid = 0;
ccode = NMAPRunCommandF(conn, buffer, sizeof(buffer),
"INFO \"/calendars/%s\"\r\n", cal->name);
if (ccode == 4224) {
/* create calendar */
ccode = NMAPRunCommandF(conn, buffer, sizeof(buffer),
"WRITE /calendars %d 0 \"F%s\"\r\n",
STORE_DOCTYPE_CALENDAR, cal->name);
ccode = NMAPReadAnswer(conn, buffer, sizeof(buffer), TRUE);
if (ccode == 1000 && cal->color) {
CommandSplit(buffer, info, 2);
guid = HexToUInt64(info[0], &endp);
NMAPSetTextProperty(conn, guid, "bongo.calendar.color",
cal->color, strlen(cal->color));
}
} else if (ccode == 2001) {
CommandSplit(buffer, info, 7);
guid = HexToUInt64(info[0], &endp);
/* eat the 1000 OK line */
NMAPReadAnswer(conn, buffer, sizeof(buffer), TRUE);
}
ccode = MsgImportIcsUrl(user, cal->name, cal->color, cal->url, cal->urlUsername, cal->urlPassword, conn);
*guidOut = guid;
return ccode;
}
int
MsgCollectUser(const char *user)
{
Connection *conn = ConnectToStore(user);
BongoList *cals;
BongoList *cur;
if (!conn) {
return MSG_COLLECT_ERROR_STORE;
}
cals = GetSubscribedCals(conn);
cur = cals;
while (cur) {
uint64_t guid;
MsgImportSubscribedCal(user, (SubscribedCal*)cur->data, conn, &guid);
cur = cur->next;
}
ConnClose(conn);
ConnFree(conn);
BongoSubscribedCalListFree(cals);
return MSG_COLLECT_OK;
}
void
MsgCollectAllUsers(void)
{
char **userlist;
int result, i;
result = MsgAuthUserList(&userlist);
if (! result) return;
for (i = 0; userlist[i] != 0; i++) {
MsgCollectUser(userlist[i]);
}
MsgAuthUserListFree(&userlist);
}
size_t
write_data(void *buffer, size_t size, size_t nmemb, void *stream)
{
fwrite(buffer, size, nmemb, stream);
return nmemb*size;
}
int
MsgImportIcsUrl(const char *user,
const char *calendarName,
const char *calendarColor,
const char *url,
const char *urlUsername,
const char *urlPassword,
Connection *existingConn)
{
CURL *curl;
CURLcode res;
char filename[XPL_MAX_PATH];
char *aliasedUrl = NULL;
int ret = 0;
/* TODO: fix up url.
* Protocol could be webcal://, feed://, dav://, ical://, etc.
* Need to replace with http:// for curl to understand it.
*/
curl = curl_easy_init();
if (curl) {
int ccode;
FILE *fh = XplOpenTemp("/tmp", "w+", filename);
char cred[1024];
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);
if (aliasedUrl) {
curl_easy_setopt(curl, CURLOPT_URL, aliasedUrl);
} else {
curl_easy_setopt(curl, CURLOPT_URL, url);
}
#if 0
curl_easy_setopt(curl, CURLOPT_VERBOSE, TRUE);
#endif
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, *write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fh);
if (urlUsername && urlPassword) {
snprintf(cred, 1024, "%s:%s", urlUsername, urlPassword);
curl_easy_setopt(curl, CURLOPT_USERPWD, cred);
}
res = msg_curl_easy_perform_cached(curl);
curl_easy_cleanup(curl);
if (res == CURLE_OK) {
/* import the ics */
fseek(fh, 0, SEEK_SET);
ccode = MsgImportIcs(fh, user, calendarName, calendarColor, existingConn);
} else {
switch (res) {
case CURLE_UNSUPPORTED_PROTOCOL :
case CURLE_URL_MALFORMAT :
ccode = MSG_COLLECT_ERROR_URL;
break;
case CURLE_FTP_ACCESS_DENIED :
case CURLE_BAD_PASSWORD_ENTERED :
#if LIBCURL_VERSION_NUM >= 0x070f02
// our import curl is the above version. if it's older, it might not have
// this error defined. Needed for Centos 4.4 (curl 7.12)
case CURLE_LOGIN_DENIED :
#endif
ccode = MSG_COLLECT_ERROR_AUTH;
break;
default :
ccode = MSG_COLLECT_ERROR_DOWNLOAD;
break;
}
}
fclose(fh);
unlink(filename);
ret = ccode;
}
if (aliasedUrl) {
MemFree(aliasedUrl);
}
return ret;
}
int
MsgIcsSubscribe(const char *user, const char *name, const char *color, const char *url, const char *urlUsername, const char *urlPassword, uint64_t *guidOut)
{
Connection *conn = ConnectToStore(user);
SubscribedCal *cal;
int ret;
if (!conn) {
return MSG_COLLECT_ERROR_STORE;
}
cal = SubscribedCalNew(0, name, color, url, urlUsername, urlPassword, 0);
ret = MsgImportSubscribedCal(user, cal, conn, guidOut);
ConnClose(conn);
ConnFree(conn);
SubscribedCalFree(cal);
return ret;
}
int
MsgIcsImport(const char *user, const char *name, const char *color, const char *url, const char *urlUsername, const char *urlPassword, uint64_t *guidOut)
{
Connection *conn = ConnectToStore(user);
SubscribedCal *cal;
int ret;
if (!conn) {
return MSG_COLLECT_ERROR_STORE;
}
cal = SubscribedCalNew(0, name, color, url, urlUsername, urlPassword, 0);
ret = MsgImportCal(user, cal, conn, guidOut);
ConnClose(conn);
ConnFree(conn);
SubscribedCalFree(cal);
return ret;
}