2911 lines
95 KiB
C
2911 lines
95 KiB
C
/****************************************************************************
|
|
* <Novell-copyright>
|
|
* Copyright (c) 2005, 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 <bongoutil.h>
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
|
|
#include "stored.h"
|
|
#include "command-parsing.h"
|
|
#include "object-model.h"
|
|
#include "calendar.h"
|
|
|
|
#include "mail.h"
|
|
#include "messages.h"
|
|
#include "mime.h"
|
|
|
|
static uint32_t
|
|
CommandHash (const void *key)
|
|
{
|
|
char *s = (char *) key;
|
|
uint32_t b = 1;
|
|
uint32_t h = 0;
|
|
|
|
for (; *s && ' ' != *s; s++) {
|
|
h += b * tolower(*s);
|
|
b = (b << 5) - b;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
// internal prototypes
|
|
CCode ReceiveToFile(StoreClient *client, const char *path, uint64_t *size);
|
|
|
|
/* a strcmp() that ends on first space. (might be optimizable by word-compares) */
|
|
|
|
static int
|
|
CommandCmp(const void *cmda, const void *cmdb)
|
|
{
|
|
char *a = (char *) cmda;
|
|
char *b = (char *) cmdb;
|
|
|
|
while (*a) {
|
|
if (tolower(*a) != tolower(*b)) {
|
|
if (!*b && ' ' == *a) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
a++;
|
|
b++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define CHECK_NOT_READONLY(x) { CCode ret = IsStoreReadonly((x)); if (ret != 0) return ret; }
|
|
|
|
static CCode
|
|
IsStoreReadonly(StoreClient *client)
|
|
{
|
|
if (client->readonly) {
|
|
ConnWriteStr(client->conn, client->ro_reason);
|
|
return 4250;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static BongoHashtable *CommandTable;
|
|
|
|
|
|
/* returns 0 on success */
|
|
|
|
/* I probably should have used ProtocolCommandTree? The hashtable came
|
|
* about when I tried to do this with with lex yacc code */
|
|
|
|
int
|
|
StoreSetupCommands()
|
|
{
|
|
CommandTable = BongoHashtableCreate(64, CommandHash, CommandCmp);
|
|
|
|
if (!CommandTable ||
|
|
|
|
/* session commands */
|
|
BongoHashtablePutNoReplace(CommandTable, "AUTH", (void *) STORE_COMMAND_AUTH) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "COOKIE", (void *) STORE_COMMAND_COOKIE) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "QUIT", (void *) STORE_COMMAND_QUIT) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "STORE", (void *) STORE_COMMAND_STORE) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "TOKEN", (void *) STORE_COMMAND_TOKEN) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "TOKENS", (void *) STORE_COMMAND_TOKENS) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "USER", (void *) STORE_COMMAND_USER) ||
|
|
|
|
/* collection commands */
|
|
BongoHashtablePutNoReplace(CommandTable, "COLLECTIONS",
|
|
(void *) STORE_COMMAND_COLLECTIONS) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "CREATE", (void *) STORE_COMMAND_CREATE) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "LIST", (void *) STORE_COMMAND_LIST) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "MAILINGLISTS",
|
|
(void *) STORE_COMMAND_MAILINGLISTS) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "RENAME", (void *) STORE_COMMAND_RENAME) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "REMOVE", (void *) STORE_COMMAND_REMOVE) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "SEARCH", (void *) STORE_COMMAND_SEARCH) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "WATCH", (void *) STORE_COMMAND_WATCH) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "REPAIR", (void *) STORE_COMMAND_REPAIR) ||
|
|
|
|
/* document commands */
|
|
BongoHashtablePutNoReplace(CommandTable, "COPY", (void *) STORE_COMMAND_COPY) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "DELETE", (void *) STORE_COMMAND_DELETE) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "FLAG", (void *) STORE_COMMAND_FLAG) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "INFO", (void *) STORE_COMMAND_INFO) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "LINK", (void *) STORE_COMMAND_LINK) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "LINKS", (void *) STORE_COMMAND_LINKS) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "UNLINK", (void *) STORE_COMMAND_UNLINK) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "MESSAGES ", (void *) STORE_COMMAND_MESSAGES) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "MIME ", (void *) STORE_COMMAND_MIME) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "MOVE ", (void *) STORE_COMMAND_MOVE) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "PROPGET", (void *) STORE_COMMAND_PROPGET) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "PROPSET", (void *) STORE_COMMAND_PROPSET) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "PURGE", (void *) STORE_COMMAND_DELETE) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "READ", (void *) STORE_COMMAND_READ) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "REPLACE", (void *) STORE_COMMAND_REPLACE) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "WRITE", (void *) STORE_COMMAND_WRITE) ||
|
|
|
|
/* delivery command */
|
|
BongoHashtablePutNoReplace(CommandTable, "DELIVER", (void *) STORE_COMMAND_DELIVER) ||
|
|
|
|
/* conversation commands */
|
|
BongoHashtablePutNoReplace(CommandTable, "CONVERSATION",
|
|
(void *) STORE_COMMAND_CONVERSATION) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "CONVERSATIONS",
|
|
(void *) STORE_COMMAND_CONVERSATIONS) ||
|
|
|
|
/* index commands */
|
|
BongoHashtablePutNoReplace(CommandTable, "ISEARCH", (void *) STORE_COMMAND_ISEARCH) ||
|
|
|
|
/* calendar commands */
|
|
BongoHashtablePutNoReplace(CommandTable, "CALENDARS",
|
|
(void *) STORE_COMMAND_CALENDARS) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "EVENTS", (void *) STORE_COMMAND_EVENTS) ||
|
|
|
|
/* alarm commands */
|
|
|
|
BongoHashtablePutNoReplace(CommandTable, "ALARMS", (void *) STORE_COMMAND_ALARMS) ||
|
|
|
|
/* other */
|
|
BongoHashtablePutNoReplace(CommandTable, "ACL", (void *) STORE_COMMAND_ACL) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "NOOP", (void *) STORE_COMMAND_NOOP) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "TIMEOUT", (void *) STORE_COMMAND_TIMEOUT) ||
|
|
|
|
/* debugging commands */
|
|
|
|
BongoHashtablePutNoReplace(CommandTable, "REINDEX", (void *) STORE_COMMAND_REINDEX) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "RESET", (void *) STORE_COMMAND_RESET) ||
|
|
BongoHashtablePutNoReplace(CommandTable, "SHUTDOWN", (void *) STORE_COMMAND_SHUTDOWN) ||
|
|
|
|
0)
|
|
{
|
|
XplConsolePrintf(AGENT_NAME ": Couldn't setup NMAP command table.\r\n");
|
|
if (CommandTable) {
|
|
BongoHashtableDelete(CommandTable);
|
|
CommandTable = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static CCode
|
|
RequireUser(StoreClient *client)
|
|
{
|
|
return STORE_PRINCIPAL_USER == client->principal.type
|
|
? TOKEN_OK
|
|
: ConnWriteStr(client->conn, MSG3241NOUSER);
|
|
}
|
|
|
|
|
|
static CCode
|
|
RequireStore(StoreClient *client)
|
|
{
|
|
return client->storedb ? TOKEN_OK
|
|
: ConnWriteStr(client->conn, MSG3243NOSTORE);
|
|
}
|
|
|
|
|
|
static CCode
|
|
RequireNoStore(StoreClient *client)
|
|
{
|
|
return client->storedb ? ConnWriteStr(client->conn, MSG3247STORE)
|
|
: TOKEN_OK;
|
|
}
|
|
|
|
|
|
static CCode
|
|
RequireManager(StoreClient *client)
|
|
{
|
|
return IS_MANAGER(client) ? TOKEN_OK
|
|
: ConnWriteStr(client->conn, MSG3243MANAGERONLY);
|
|
}
|
|
|
|
|
|
static CCode
|
|
RequireIdentity(StoreClient *client)
|
|
{
|
|
return (IS_MANAGER(client) || HAS_IDENTITY(client))
|
|
? TOKEN_OK
|
|
: ConnWriteStr(client->conn, MSG3241NOUSER);
|
|
}
|
|
|
|
#define TOK_ARR_SZ 10
|
|
#define PROP_ARR_SZ 10
|
|
#define HDR_ARR_SZ 10
|
|
|
|
CCode
|
|
StoreCommandLoop(StoreClient *client)
|
|
{
|
|
int ccode = 0;
|
|
StoreCommand command;
|
|
|
|
while (-1 != ccode && BONGO_AGENT_STATE_RUNNING == StoreAgent.agent.state) {
|
|
char *tokens[TOK_ARR_SZ];
|
|
int n;
|
|
int i;
|
|
|
|
BongoMemStackReset(&client->memstack);
|
|
|
|
ccode = ConnReadAnswer(client->conn, client->buffer, CONN_BUFSIZE);
|
|
if (-1 == ccode || ccode >= CONN_BUFSIZE) {
|
|
break;
|
|
}
|
|
|
|
memset(tokens, 0, sizeof(tokens));
|
|
n = CommandSplit(client->buffer, tokens, TOK_ARR_SZ);
|
|
if (0 == n) {
|
|
command = STORE_COMMAND_NULL;
|
|
} else {
|
|
command = (StoreCommand) BongoHashtableGet(CommandTable, tokens[0]);
|
|
}
|
|
|
|
if (client->watch.collection) {
|
|
/* out of band events */
|
|
ccode = StoreShowWatcherEvents(client);
|
|
if (-1 == ccode) {
|
|
break;
|
|
}
|
|
|
|
/* FIXME: What if there were an event related to a document relevant
|
|
to the command we just parsed? (or any event?) Should we ignore
|
|
the command? */
|
|
}
|
|
|
|
switch (command) {
|
|
StoreDocumentType doctype;
|
|
StoreObject object;
|
|
StoreObject collection;
|
|
int int1;
|
|
int int2;
|
|
int int3;
|
|
int int4;
|
|
unsigned long ulong;
|
|
unsigned long ulong2;
|
|
uint64_t guid;
|
|
uint64_t guid2;
|
|
uint64_t timestamp;
|
|
uint64_t timestamp2;
|
|
const char *filename;
|
|
char *uid;
|
|
char *query;
|
|
StorePropInfo prop;
|
|
StorePropInfo props[PROP_ARR_SZ];
|
|
StoreHeaderInfo hdrs[HDR_ARR_SZ];
|
|
int hdrcnt;
|
|
char dt_start[BONGO_CAL_TIME_BUFSIZE];
|
|
char dt_end[BONGO_CAL_TIME_BUFSIZE];
|
|
|
|
case STORE_COMMAND_NULL:
|
|
ccode = ConnWriteStr(client->conn, MSG3000UNKNOWN);
|
|
break;
|
|
|
|
case STORE_COMMAND_ACL:
|
|
/* ACL GRANT <document> P<priv> T<principal> [W<who>] */
|
|
/* ACL DENY <document> P<priv> T<principal> [W<who>] */
|
|
/* ACL LIST [<document>] */
|
|
/* ACL REMOVE <document> P<priv> [T<principal>] [W<who>] */
|
|
|
|
int1 = 0; // priv
|
|
int2 = 0; // principal
|
|
query = NULL; // who
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 0, 6)))
|
|
{
|
|
StorePrincipalType type = 0;
|
|
StorePrivilege priv = 0;
|
|
char *who = NULL;
|
|
|
|
for (i = 3; TOKEN_OK == ccode && i < n; i++) {
|
|
if ('P' == *tokens[i]) {
|
|
ccode = ParsePrivilege(client, tokens[i] + 1, &priv);
|
|
} else if ('T' == *tokens[i]) {
|
|
ccode = ParsePrincipal(client, tokens[i] + 1, &type);
|
|
} else if ('W' == *tokens[i]) {
|
|
who = tokens[i] + 1;
|
|
ccode = TOKEN_OK;
|
|
} else {
|
|
// 'bad' token - something we don't understand.
|
|
ccode = 0;
|
|
}
|
|
if (ccode != TOKEN_OK) {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
}
|
|
}
|
|
if (ccode != TOKEN_OK) break;
|
|
|
|
if (!XplStrCaseCmp(tokens[1], "list")) {
|
|
if ((n == 1) && ParseDocument(client, tokens[2], &object) == TOKEN_OK) {
|
|
ccode = StoreObjectListACL(client, &object);
|
|
break;
|
|
} else {
|
|
ccode = StoreObjectListACL(client, NULL);
|
|
break;
|
|
}
|
|
} else {
|
|
if (n < 1) {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
} else if (ParseDocument(client, tokens[2], &object) != TOKEN_OK) {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
}
|
|
}
|
|
|
|
if (!XplStrCaseCmp(tokens[1], "remove")) {
|
|
if (priv == 0) {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
} else {
|
|
if (StoreObjectRemoveACL(client, &object, type, priv, who) == 0) {
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
}
|
|
break;
|
|
} else {
|
|
BOOL deny;
|
|
if (! XplStrCaseCmp(tokens[1], "grant")) {
|
|
deny = FALSE;
|
|
} else if (! XplStrCaseCmp(tokens[1], "deny")) {
|
|
deny = TRUE;
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
break;
|
|
}
|
|
if ((priv == 0) || (type == 0) || (who == NULL)) {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
break;
|
|
}
|
|
if (StoreObjectAddACL(client, &object, type, priv, deny, who) == 0) {
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case STORE_COMMAND_ALARMS:
|
|
/* ALARMS <starttime> <endtime> */
|
|
|
|
if (TOKEN_OK == (ccode = RequireManager(client)) &&
|
|
TOKEN_OK == (ccode = RequireNoStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 3, 3)) &&
|
|
TOKEN_OK == (ccode = ParseDateTimeToUint64(client, tokens[1],
|
|
×tamp)) &&
|
|
TOKEN_OK == (ccode = ParseDateTimeToUint64(client, tokens[2],
|
|
×tamp2)))
|
|
{
|
|
ccode = StoreCommandALARMS(client, timestamp, timestamp2);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_AUTH:
|
|
/* AUTH COOKIE <username> <token> [NOUSER] */
|
|
/* AUTH SYSTEM <md5 hash> */
|
|
/* AUTH USER <username> <password> [NOUSER] */
|
|
|
|
int1 = 0; /* "NOUSER" flag */
|
|
|
|
if (TOKEN_OK != (ccode = CheckTokC(client, n, 3, 5))) {
|
|
break;
|
|
}
|
|
if (5 == n) {
|
|
if (!XplStrCaseCmp(tokens[4], "NOUSER")) {
|
|
int1 = 1;
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
break;
|
|
}
|
|
}
|
|
if (!XplStrCaseCmp(tokens[1], "COOKIE")) {
|
|
if (TOKEN_OK == (ccode = CheckTokC(client, n, 4, 5)) &&
|
|
TOKEN_OK == (ccode = ParseUserName(client, tokens[2])))
|
|
{
|
|
ccode = StoreCommandAUTHCOOKIE(client,
|
|
tokens[2], tokens[3], int1);
|
|
}
|
|
} else if (0 == XplStrCaseCmp(tokens[1], "SYSTEM")) {
|
|
ccode = StoreCommandAUTHSYSTEM(client, tokens[2]);
|
|
} else if (0 == XplStrCaseCmp(tokens[1], "USER")) {
|
|
if (TOKEN_OK == (ccode = CheckTokC(client, n, 4, 5)) &&
|
|
TOKEN_OK == (ccode = ParseUserName(client, tokens[2])))
|
|
{
|
|
ccode = StoreCommandUSER(client, tokens[2], tokens[3], int1);
|
|
}
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3000UNKNOWN);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_CALENDARS:
|
|
/* FIXME: this command is no longer needed */
|
|
/* CALENDARS [F <flags>] [P <proplist>] */
|
|
|
|
int1 = 0; /* propcount */
|
|
ulong = ULONG_MAX; /* flags */
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 1, 3)))
|
|
{
|
|
break;
|
|
}
|
|
for (i = 1; TOKEN_OK == ccode && i < n; i++) {
|
|
if ('F' == *tokens[i]) {
|
|
ccode = ParseUnsignedLong(client, tokens[1] + 1, &ulong);
|
|
} else if ('P' == *tokens[i] && 0 == int3) {
|
|
ccode = ParsePropList(client, tokens[i] + 1,
|
|
props, PROP_ARR_SZ, &int1, 0);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
}
|
|
}
|
|
|
|
if (TOKEN_OK == ccode) {
|
|
ccode = StoreCommandCALENDARS(client, ulong, props, int1);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_COLLECTIONS:
|
|
/* COLLECTIONS [<collection>] */
|
|
|
|
if (1 == n) {
|
|
tokens[1] = "/";
|
|
}
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 1, 2)) &&
|
|
TOKEN_OK == (ccode = ParseCollection(client, tokens[1], &object)))
|
|
{
|
|
ccode = StoreCommandCOLLECTIONS(client, &object);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_CONVERSATIONS:
|
|
/* CONVERSATIONS [Ssource] [Rxx-xx] [Q<query>] [P<proplist>]
|
|
[C<center document>] [T] [F<flags>] [M<flags mask>]
|
|
[H<headerlist>]
|
|
*/
|
|
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 1, 10)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
filename = NULL; /* source */
|
|
query = NULL;
|
|
int1 = int2 = -1; /* range */
|
|
int3 = 0; /* propcount */
|
|
int4 = 0; /* display total */
|
|
guid = 0; /* center document */
|
|
ulong = 0; /* flags mask */
|
|
ulong2 = 0; /* flags */
|
|
hdrcnt = 0;
|
|
for (i = 1; i < n; i++) {
|
|
if ('S' == *tokens[i] && tokens[i][1] != '\0' && filename == NULL) {
|
|
filename = tokens[i] + 1;
|
|
} else if ('P' == *tokens[i] && 0 == int3) {
|
|
ccode = ParsePropList(client, tokens[i] + 1,
|
|
props, PROP_ARR_SZ, &int3, 0);
|
|
} else if ('R' == *tokens[i] && int1 == -1) {
|
|
ccode = ParseRange(client, tokens[i] + 1, &int1, &int2);
|
|
} else if ('Q' == *tokens[i]) {
|
|
query = tokens[i] + 1;
|
|
} else if ('C' == *tokens[i] && !guid) {
|
|
ccode = ParseDocument(client, tokens[i] + 1, &object);
|
|
} else if ('T' == *tokens[i] && !int4 && !tokens[i][1]) {
|
|
int4 = 1;
|
|
} else if ('M' == *tokens[i]) {
|
|
ccode = ParseUnsignedLong(client, tokens[i] + 1, &ulong);
|
|
} else if ('F' == *tokens[i]) {
|
|
ccode = ParseUnsignedLong(client, tokens[i] + 1, &ulong2);
|
|
} else if ('H' == *tokens[i]) {
|
|
ccode = ParseHeaderList(client, tokens[i] + 1,
|
|
hdrs, HDR_ARR_SZ, &hdrcnt);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (TOKEN_OK != ccode) {
|
|
break;
|
|
}
|
|
|
|
ccode = StoreCommandCONVERSATIONS(client, filename, query,
|
|
int1, int2, &object,
|
|
(uint32_t) ulong, (uint32_t) ulong2,
|
|
int4,
|
|
hdrs, hdrcnt,
|
|
props, int3);
|
|
break;
|
|
|
|
case STORE_COMMAND_CONVERSATION:
|
|
/* CONVERSATION <conversation> [P<proplist>] */
|
|
|
|
guid = 0;
|
|
int3 = 0; /* propcount */
|
|
if (TOKEN_OK != (ccode = RequireStore(client))) break;
|
|
if (TOKEN_OK != (ccode = CheckTokC(client, n, 2, 3))) break;
|
|
if (TOKEN_OK != (ccode = ParseDocument(client, tokens[1], &object))) break;
|
|
|
|
for (i = 2; i < n; i++) {
|
|
if ('P' == *tokens[i] && 0 == int3) {
|
|
ccode = ParsePropList(client, tokens[i] + 1,
|
|
props, PROP_ARR_SZ, &int3, 0);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (TOKEN_OK == ccode) {
|
|
ccode = StoreCommandCONVERSATION(client, &object, props, int3);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_COOKIE:
|
|
/* COOKIE [BAKE | NEW] <timeout> */
|
|
/* COOKIE [CRUMBLE | DELETE] [<token>] */
|
|
|
|
if (TOKEN_OK != (ccode = RequireUser(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 2, 3)))
|
|
{
|
|
break;
|
|
}
|
|
if (!XplStrCaseCmp(tokens[1], "BAKE") ||
|
|
!XplStrCaseCmp(tokens[1], "NEW"))
|
|
{
|
|
if (TOKEN_OK != (ccode = CheckTokC(client, n, 3, 3)) ||
|
|
TOKEN_OK != (ccode = ParseDateTimeToUint64(client, tokens[2],
|
|
×tamp)))
|
|
{
|
|
break;
|
|
}
|
|
ccode = StoreCommandCOOKIENEW(client, timestamp);
|
|
} else if (!XplStrCaseCmp(tokens[1], "CRUMBLE") ||
|
|
!XplStrCaseCmp(tokens[1], "DELETE"))
|
|
{
|
|
ccode = StoreCommandCOOKIEDELETE(client, 3 == n ? tokens[2] : NULL);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3000UNKNOWN);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_COPY:
|
|
/* COPY <document> <collection> */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 3, 3)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &object)) &&
|
|
TOKEN_OK == (ccode = ParseCollection(client, tokens[2], &collection)))
|
|
{
|
|
ccode = StoreCommandCOPY(client, &object, &collection);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_CREATE:
|
|
/* CREATE <collection name> [<guid>] */
|
|
|
|
guid = 0;
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 2, 3)) &&
|
|
(n <= 2 || TOKEN_OK == (ccode = ParseGUID(client, tokens[2], &guid))))
|
|
{
|
|
ccode = StoreCommandCREATE(client, tokens[1], guid);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_DELETE:
|
|
/* DELETE <document> */
|
|
/* PURGE <document> */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 2, 2)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &object)))
|
|
{
|
|
ccode = StoreCommandDELETE(client, &object);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_DELIVER:
|
|
/* DELIVER FILE <type> <sender> <authsender> <filename> */
|
|
/* DELIVER STREAM <type> <sender> <authsender> <count> */
|
|
|
|
if (TOKEN_OK != (ccode = RequireManager(client)) ||
|
|
TOKEN_OK != (ccode = RequireNoStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 6, 6)) ||
|
|
TOKEN_OK != (ccode = ParseDocType(client, tokens[2], &doctype)))
|
|
{
|
|
break;
|
|
}
|
|
if (0 == XplStrCaseCmp(tokens[1], "FILE")) {
|
|
ccode = StoreCommandDELIVER(client, tokens[3], tokens[4],
|
|
tokens[5], 0);
|
|
} else if (0 == XplStrCaseCmp(tokens[1], "STREAM")) {
|
|
if (TOKEN_OK != (ccode = ParseInt(client, tokens[5], &int1))) {
|
|
break;
|
|
}
|
|
ccode = StoreCommandDELIVER(client, tokens[3], tokens[4], NULL, int1);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3000UNKNOWN);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_EVENTS:
|
|
/* EVENTS [D<daterange>] [C<calendar> | U<uid>]
|
|
[F<mask>] [Q<query>] [P<proplist>] */
|
|
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 1, 6)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
dt_start[0] = '\0';
|
|
int1 = 0; /* proplist len */
|
|
int2 = 0; /* have coll */
|
|
int3 = int4 = -1; /* range */
|
|
int4 = -1;
|
|
uid = NULL;
|
|
query = NULL; /* uid */
|
|
ulong = ULONG_MAX;
|
|
for (i = 1; TOKEN_OK == ccode && i < n; i++) {
|
|
if ('C' == *tokens[i] && !int2 && !query) {
|
|
ccode = ParseDocumentInfo(client, tokens[i] + 1, &object);
|
|
int2++;
|
|
} else if ('D' == *tokens[i] && !dt_start[0]) {
|
|
ccode = ParseDateRange(client, tokens[i] + 1, dt_start, dt_end);
|
|
} else if ('F' == *tokens[i] && ULONG_MAX == ulong) {
|
|
ccode = ParseUnsignedLong(client, tokens[i] + 1, &ulong);
|
|
} else if ('P' == *tokens[i] && 0 == int1) {
|
|
ccode = ParsePropList(client, tokens[i] + 1,
|
|
props, PROP_ARR_SZ, &int1, 0);
|
|
} else if ('Q' == *tokens[i] && !query) {
|
|
query = tokens[i] + 1;
|
|
} else if ('R' == *tokens[i] && -1 == int3) {
|
|
ccode = ParseRange(client, tokens[i] + 1, &int3, &int4);
|
|
} else if ('U' == *tokens[i] && !uid && !int2) {
|
|
uid = tokens[i] + 1;
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
}
|
|
}
|
|
|
|
if (TOKEN_OK != ccode) {
|
|
break;
|
|
}
|
|
|
|
if (!dt_start[0]) {
|
|
strcpy(dt_start, "00000000T000000");
|
|
strcpy(dt_end, "99999999T999999");
|
|
}
|
|
ccode = StoreCommandEVENTS(client, dt_start, dt_end,
|
|
int2 ? &object : NULL,
|
|
ulong, uid,
|
|
query, int3, int4,
|
|
props, int1);
|
|
break;
|
|
|
|
case STORE_COMMAND_FLAG:
|
|
/* FLAG <document> [[+ | -]<value>] */
|
|
|
|
int2 = STORE_FLAG_SHOW;
|
|
guid = 0;
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 2, 3)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &object)) &&
|
|
(n < 3 || TOKEN_OK == (ccode = ParseFlag(client, tokens[2], &int1, &int2))))
|
|
|
|
{
|
|
ccode = StoreCommandFLAG(client, &object, int1, int2);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_INFO:
|
|
/* INFO <document> [P<proplist>] */
|
|
|
|
guid = 0;
|
|
int1 = 0; /* proplist len */
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 2, 3)) ||
|
|
TOKEN_OK != (ccode = ParseDocument(client, tokens[1], &object)))
|
|
{
|
|
break;
|
|
}
|
|
if (n > 2) {
|
|
if ('P' == *tokens[2]) {
|
|
ccode = ParsePropList(client, tokens[2] + 1,
|
|
props, PROP_ARR_SZ, &int1, 0);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
}
|
|
}
|
|
if (TOKEN_OK == ccode) {
|
|
ccode = StoreCommandINFO(client, &object, props, int1);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_ISEARCH:
|
|
/* ISEARCH R<range> <query> [P<proplist>] */
|
|
|
|
int1 = 0; /* range */
|
|
int2 = 0; /* range */
|
|
int3 = 0; /* proplist len */
|
|
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 3, 4)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ('R' != *tokens[1]) {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
break;
|
|
}
|
|
ccode = ParseRange(client, tokens[1] + 1, &int1, &int2);
|
|
|
|
for (i = 3; TOKEN_OK == ccode && i < n; i++) {
|
|
if ('P' == *tokens[i] && 0 == int3) {
|
|
ccode = ParsePropList(client, tokens[i] + 1,
|
|
props, PROP_ARR_SZ, &int3, 0);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
}
|
|
}
|
|
|
|
if (TOKEN_OK != ccode) {
|
|
break;
|
|
}
|
|
|
|
ccode = StoreCommandISEARCH(client, tokens[2], int1, int2,
|
|
props, int3);
|
|
break;
|
|
|
|
case STORE_COMMAND_LINK:
|
|
/* LINK <document> <document>
|
|
* abuses collection - first argument should be another document*/
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 3, 3)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &collection)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[2], &object)))
|
|
{
|
|
ccode = StoreCommandLINK(client, &collection, &object);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_LINKS:
|
|
/* LINKS <document> */
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 3, 3)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[2], &object)))
|
|
{
|
|
BOOL reverse_links;
|
|
if (!XplStrCaseCmp(tokens[1], "TO")) {
|
|
reverse_links = TRUE;
|
|
} else if (!XplStrCaseCmp(tokens[1], "FROM")) {
|
|
reverse_links = FALSE;
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
break;
|
|
}
|
|
|
|
ccode = StoreCommandLINKS(client, reverse_links, &object);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_LIST:
|
|
/* LIST <collection> [Rxx-xx] [P<proplist>] [M<flags mask>] [F<flags>] */
|
|
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 2, 6)) ||
|
|
TOKEN_OK != (ccode = ParseCollection(client, tokens[1], &object)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
int1 = int2 = -1; /* range */
|
|
int3 = 0; /* proplist len */
|
|
ulong = 0; /* flags mask */
|
|
ulong2 = 0; /* flags */
|
|
for (i = 2; TOKEN_OK == ccode && i < n; i++) {
|
|
if ('R' == *tokens[i] && -1 == int1) {
|
|
ccode = ParseRange(client, tokens[i] + 1, &int1, &int2);
|
|
} else if ('P' == *tokens[i] && 0 == int3) {
|
|
ccode = ParsePropList(client, tokens[i] + 1,
|
|
props, PROP_ARR_SZ, &int3, 0);
|
|
} else if ('M' == *tokens[i]) {
|
|
ccode = ParseUnsignedLong(client, tokens[i] + 1, &ulong);
|
|
} else if ('F' == *tokens[i]) {
|
|
ccode = ParseUnsignedLong(client, tokens[i] + 1, &ulong2);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
}
|
|
}
|
|
|
|
if (TOKEN_OK != ccode) {
|
|
break;
|
|
}
|
|
|
|
ccode = StoreCommandLIST(client, &object, int1, int2,
|
|
(uint32_t) ulong, (uint32_t) ulong2,
|
|
props, int3);
|
|
break;
|
|
|
|
case STORE_COMMAND_MAILINGLISTS:
|
|
/* MAILINGLISTS <source> */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 2, 2)))
|
|
{
|
|
ccode = StoreCommandMAILINGLISTS(client, tokens[1]);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_MESSAGES:
|
|
/* MESSAGES [Ssource] [H<headerlist>] [Q<query>]
|
|
[F<flags>] [M<flags mask>]
|
|
[Rxx-xx] [C<center document>] [T]
|
|
[P<proplist>]
|
|
*/
|
|
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 1, 10)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
filename = NULL; /* source */
|
|
query = NULL;
|
|
int1 = int2 = -1; /* range */
|
|
int3 = 0; /* propcount */
|
|
int4 = 0; /* display total */
|
|
guid = 0; /* center document */
|
|
ulong = 0; /* flags mask */
|
|
ulong2 = 0; /* flags */
|
|
hdrcnt = 0;
|
|
for (i = 1; i < n; i++) {
|
|
if ('S' == *tokens[i] && tokens[i][1] != '\0' && filename == NULL) {
|
|
filename = tokens[i] + 1;
|
|
} else if ('P' == *tokens[i] && 0 == int3) {
|
|
ccode = ParsePropList(client, tokens[i] + 1,
|
|
props, PROP_ARR_SZ, &int3, 0);
|
|
} else if ('R' == *tokens[i] && int1 == -1) {
|
|
ccode = ParseRange(client, tokens[i] + 1, &int1, &int2);
|
|
} else if ('Q' == *tokens[i]) {
|
|
query = tokens[i] + 1;
|
|
} else if ('C' == *tokens[i] && !guid) {
|
|
ccode = ParseDocument(client, tokens[i] + 1, &object);
|
|
} else if ('T' == *tokens[i] && !int4 && !tokens[i][1]) {
|
|
int4 = 1;
|
|
} else if ('M' == *tokens[i]) {
|
|
ccode = ParseUnsignedLong(client, tokens[i] + 1, &ulong);
|
|
} else if ('F' == *tokens[i]) {
|
|
ccode = ParseUnsignedLong(client, tokens[i] + 1, &ulong2);
|
|
} else if ('H' == *tokens[i]) {
|
|
ccode = ParseHeaderList(client, tokens[i] + 1,
|
|
hdrs, HDR_ARR_SZ, &hdrcnt);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (TOKEN_OK != ccode) {
|
|
break;
|
|
}
|
|
|
|
ccode = StoreCommandMESSAGES(client, filename, query,
|
|
int1, int2, &object,
|
|
(uint32_t) ulong, (uint32_t) ulong2,
|
|
int4,
|
|
hdrs, hdrcnt,
|
|
props, int3);
|
|
break;
|
|
|
|
case STORE_COMMAND_MIME:
|
|
/* MIME <document> */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 2, 2)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &object)))
|
|
{
|
|
ccode = StoreCommandMIME(client, &object);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_MOVE:
|
|
/* MOVE <document> <collection> [<filename>] */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 3, 4)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &object)) &&
|
|
TOKEN_OK == (ccode = ParseCollection(client, tokens[2], &collection)))
|
|
{
|
|
ccode = StoreCommandMOVE(client, &object, &collection,
|
|
4 == n ? tokens[3] : NULL);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_NOOP:
|
|
/* NOOP */
|
|
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
break;
|
|
|
|
case STORE_COMMAND_PROPGET:
|
|
/* PROPGET <document> [<proplist>] */
|
|
|
|
int3 = 0; /* propcount */
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 2, 3)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &object)) &&
|
|
(2 == n ||
|
|
TOKEN_OK == (ccode = ParsePropList(client, tokens[2],
|
|
props, PROP_ARR_SZ, &int3, 0))))
|
|
{
|
|
ccode = StoreCommandPROPGET(client, &object, props, int3);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_PROPSET:
|
|
/* PROPSET <document> <propname> <length> */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 4, 4)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &object)) &&
|
|
TOKEN_OK == (ccode = ParsePropName(client, tokens[2], &prop)) &&
|
|
TOKEN_OK == (ccode = ParseInt(client, tokens[3], &int1)))
|
|
|
|
{
|
|
prop.valueLen = int1;
|
|
ccode = StoreCommandPROPSET(client, &object, &prop, int1);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_QUIT:
|
|
/* QUIT */
|
|
|
|
ccode = StoreCommandQUIT(client);
|
|
break;
|
|
|
|
case STORE_COMMAND_READ:
|
|
/* READ <document> [<start> [<length>]] */
|
|
// FIXME: need to get signedness etc. right on these args
|
|
int1 = -1;
|
|
int2 = -1;
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 2, 4)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &object)) &&
|
|
(n < 3 || TOKEN_OK == (ccode = ParseInt(client, tokens[2], &int1))) &&
|
|
(n < 4 || TOKEN_OK == (ccode = ParseInt(client, tokens[3], &int2))))
|
|
{
|
|
ccode = StoreCommandREAD(client, &object, int1, int2);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_REINDEX:
|
|
/* REINDEX [<document>] */
|
|
|
|
guid = 0;
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 1, 2)) &&
|
|
(n < 2 ||
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &object))))
|
|
{
|
|
ccode = StoreCommandREINDEX(client, &object);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_RENAME:
|
|
/* RENAME <collection> <new name> */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 3, 3)) &&
|
|
TOKEN_OK == (ccode = ParseCollection(client, tokens[1], &object)))
|
|
{
|
|
ccode = StoreCommandRENAME(client, &object, tokens[2]);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_REMOVE:
|
|
/* REMOVE <collection> */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 2, 2)) &&
|
|
TOKEN_OK == (ccode = ParseCollection(client, tokens[1], &object)))
|
|
{
|
|
ccode = StoreCommandREMOVE(client, &object);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_REPAIR:
|
|
/* REPAIR */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)))
|
|
{
|
|
ccode = StoreCommandREPAIR(client);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_REPLACE:
|
|
/* REPLACE <document> <length> [R<xx-xx>] [V<version>]
|
|
[L<link document>]*/
|
|
|
|
int2 = 0; /* range start */
|
|
int3 = 0; /* range end */
|
|
int4 = -1; /* version */
|
|
guid2 = 0;
|
|
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 3, 6)) ||
|
|
TOKEN_OK != (ccode = ParseDocument(client, tokens[1], &object)) ||
|
|
TOKEN_OK != (ccode = ParseStreamLength(client, tokens[2], &int1)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
for (i = 3; TOKEN_OK == ccode && i < n; i++) {
|
|
if ('R' == *tokens[i] && -1 == int2) {
|
|
ccode = ParseRange(client, tokens[i] + 1 , &int2, &int3);
|
|
} else if ('V' == *tokens[i] && -1 == int4) {
|
|
ccode = ParseInt(client, tokens[i] + 1, &int4);
|
|
//} else if ('L' == *tokens[i] && !guid2) {
|
|
// ccode = ParseDocument(client, 1 + tokens[i], &guid2);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
}
|
|
}
|
|
if (TOKEN_OK == ccode) {
|
|
ccode = StoreCommandREPLACE(client, &object, int1, int2, int3, int4);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_RESET:
|
|
/* RESET */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 1, 1)))
|
|
{
|
|
ccode = StoreCommandRESET(client);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_SEARCH:
|
|
/* SEARCH <document or collection> TEXT <query>
|
|
SEARCH <document or collection> BODY <query>
|
|
SEARCH <document or collection> HEADER <header> <query>
|
|
SEARCH <document or collection> HEADERS <query>
|
|
*/
|
|
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 4, 5)) ||
|
|
TOKEN_OK != (ccode = ParseDocument(client, tokens[1], &object)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
{
|
|
StoreSearchInfo search;
|
|
|
|
if (0 == XplStrCaseCmp(tokens[2], "TEXT")) {
|
|
search.type = STORE_SEARCH_TEXT;
|
|
search.query = tokens[3];
|
|
} else if (0 == XplStrCaseCmp(tokens[2], "BODY")) {
|
|
search.type = STORE_SEARCH_BODY;
|
|
search.query = tokens[3];
|
|
} else if (0 == XplStrCaseCmp(tokens[2], "HEADER")) {
|
|
if (TOKEN_OK != (ccode = CheckTokC(client, n, 5, 5))) {
|
|
break;
|
|
}
|
|
search.type = STORE_SEARCH_HEADER;
|
|
search.header = tokens[3];
|
|
search.query = tokens[4];
|
|
} else if (0 == XplStrCaseCmp(tokens[2], "HEADERS")) {
|
|
search.type = STORE_SEARCH_HEADERS;
|
|
search.query = tokens[3];
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3000UNKNOWN);
|
|
break;
|
|
}
|
|
|
|
ccode = StoreCommandSEARCH(client, object.guid, &search);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_SHUTDOWN:
|
|
/* SHUTDOWN */
|
|
exit(0);
|
|
break;
|
|
|
|
case STORE_COMMAND_STORE:
|
|
/* STORE [<storename>] */
|
|
|
|
if (TOKEN_OK != (ccode = CheckTokC(client, n, 1, 2)) ||
|
|
TOKEN_OK != (ccode = RequireIdentity(client)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (2 == n) {
|
|
if (TOKEN_OK != (ccode = ParseStoreName(client, tokens[1]))) {
|
|
break;
|
|
}
|
|
} else {
|
|
tokens[1] = NULL;
|
|
}
|
|
|
|
ccode = StoreCommandSTORE(client, tokens[1]);
|
|
break;
|
|
|
|
case STORE_COMMAND_TIMEOUT:
|
|
/* TIMEOUT [<timeoutms>] */
|
|
|
|
int1 = -1;
|
|
|
|
if (TOKEN_OK == (ccode = CheckTokC(client, n, 1, 2)) &&
|
|
(1 == n || TOKEN_OK == (ccode = ParseInt(client, tokens[1], &int1))))
|
|
{
|
|
ccode = StoreCommandTIMEOUT(client, int1);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_TOKEN:
|
|
/* TOKEN <token> */
|
|
|
|
if (TOKEN_OK == (ccode = CheckTokC(client, n, 2, 2)) &&
|
|
TOKEN_OK == (ccode = RequireIdentity(client)))
|
|
{
|
|
ccode = StoreCommandTOKEN(client, tokens[1]);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_TOKENS:
|
|
/* TOKENS */
|
|
|
|
if (TOKEN_OK == (ccode = CheckTokC(client, n, 1, 1)) &&
|
|
TOKEN_OK == (ccode = RequireIdentity(client)))
|
|
{
|
|
ccode = StoreCommandTOKENS(client);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_UNLINK:
|
|
/* UNLINK <document> <document> */
|
|
|
|
if (TOKEN_OK == (ccode = RequireStore(client)) &&
|
|
TOKEN_OK == (ccode = CheckTokC(client, n, 3, 3)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[1], &collection)) &&
|
|
TOKEN_OK == (ccode = ParseDocument(client, tokens[2], &object)))
|
|
{
|
|
ccode = StoreCommandUNLINK(client, &collection, &object);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_USER:
|
|
/* USER [<username>] */
|
|
|
|
if (TOKEN_OK != (ccode = RequireManager(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 1, 2)))
|
|
{
|
|
break;
|
|
}
|
|
if (2 == n) {
|
|
if (TOKEN_OK != (ccode = ParseUserName(client, tokens[1]))) {
|
|
break;
|
|
}
|
|
} else {
|
|
tokens[1] = NULL;
|
|
}
|
|
|
|
ccode = StoreCommandUSER(client, tokens[1], NULL, 0);
|
|
break;
|
|
|
|
case STORE_COMMAND_WATCH:
|
|
/* WATCH <collection> [DELETED] [FLAGS] [MODIFIED] [NEW] */
|
|
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 2, 6)) ||
|
|
TOKEN_OK != (ccode = ParseCollection(client, tokens[1], &object)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
int1 = 0;
|
|
for (i = 2; i < n; i++) {
|
|
if (!XplStrCaseCmp("FLAGS", tokens[i])) {
|
|
int1 |= STORE_WATCH_EVENT_FLAGS;
|
|
} else if (!XplStrCaseCmp("MODIFIED", tokens[i])) {
|
|
int1 |= STORE_WATCH_EVENT_MODIFIED;
|
|
} else if (!XplStrCaseCmp("NEW", tokens[i])) {
|
|
int1 |= STORE_WATCH_EVENT_NEW;
|
|
} else if (!XplStrCaseCmp("DELETED", tokens[i])) {
|
|
int1 |= STORE_WATCH_EVENT_DELETED;
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (TOKEN_OK == ccode) {
|
|
ccode = StoreCommandWATCH(client, &object, int1);
|
|
}
|
|
break;
|
|
|
|
case STORE_COMMAND_WRITE:
|
|
/* WRITE <collection> <type> <length>
|
|
[F<filename>] [G<guid>] [T<timeCreated>]
|
|
[Z<flags>] [L<link guid>] [N]
|
|
*/
|
|
|
|
if (TOKEN_OK != (ccode = RequireStore(client)) ||
|
|
TOKEN_OK != (ccode = CheckTokC(client, n, 4, 9)) ||
|
|
TOKEN_OK != (ccode = ParseCollection(client, tokens[1], &object)) ||
|
|
TOKEN_OK != (ccode = ParseDocType(client, tokens[2], &doctype)) ||
|
|
TOKEN_OK != (ccode = ParseStreamLength(client, tokens[3], &int1)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
filename = NULL;
|
|
timestamp = 0;
|
|
ulong = 0; /* addflags */
|
|
guid = 0;
|
|
guid2 = 0; /* link guid */
|
|
int2 = 0; /* whether or not to process the document */
|
|
|
|
for (i = 4; i < n; i++) {
|
|
if ('G' == *tokens[i] && !guid) {
|
|
ccode = ParseGUID(client, 1 + tokens[i], &guid);
|
|
//} else if ('L' == *tokens[i] && !guid2) {
|
|
// ccode = ParseDocument(client, 1 + tokens[i], &guid2);
|
|
} else if ('F' == *tokens[i] && tokens[i][1] != '\0') {
|
|
filename = tokens[i] + 1;
|
|
} else if ('N' == *tokens[i] && tokens[i][1] == '\0') {
|
|
int2 = 1;
|
|
} else if ('T' == *tokens[i] && !timestamp) {
|
|
ccode = ParseDateTimeToUint64(client, 1 + tokens[i], ×tamp);
|
|
} else if ('Z' == *tokens[i] && !ulong) {
|
|
ccode = ParseUnsignedLong(client, tokens[i] + 1, &ulong);
|
|
} else {
|
|
ccode = ConnWriteStr(client->conn, MSG3022BADSYNTAX);
|
|
}
|
|
if (TOKEN_OK != ccode) {
|
|
break;
|
|
}
|
|
}
|
|
if (TOKEN_OK != ccode) {
|
|
break;
|
|
}
|
|
|
|
ccode = StoreCommandWRITE(client, &object, doctype, int1,
|
|
(uint32_t) ulong, guid, filename,
|
|
timestamp, guid2, int2);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (client->watchLock) {
|
|
StoreReleaseCollectionLock(client, &(client->watchLock));
|
|
client->watchLock = NULL;
|
|
}
|
|
|
|
if (ccode >= 0) {
|
|
ccode = ConnFlush(client->conn);
|
|
}
|
|
}
|
|
|
|
return ccode;
|
|
}
|
|
|
|
static CCode
|
|
ShowDocumentBody(StoreClient *client, StoreObject *document,
|
|
int64_t requestStart, uint64_t requestLength)
|
|
{
|
|
int ccode = 0;
|
|
char path[XPL_MAX_PATH + 1];
|
|
FILE *fh = NULL;
|
|
uint64_t start;
|
|
uint64_t length;
|
|
|
|
if (STORE_IS_FOLDER(document->type)) {
|
|
return ConnWriteStr(client->conn, MSG3015BADDOCTYPE);
|
|
} else {
|
|
FindPathToDocument(client, document->collection_guid, document->guid, path, sizeof(path));
|
|
fh = fopen(path, "rb");
|
|
if (!fh)
|
|
return ConnWriteStr(client->conn, MSG4224CANTREAD);
|
|
|
|
if (requestStart < 0) {
|
|
start = 0;
|
|
length = document->size;
|
|
} else {
|
|
if ((uint64_t)requestStart < document->size) {
|
|
start = requestStart;
|
|
length = document->size - start;
|
|
if ((requestLength < length) && (requestLength != 0)) {
|
|
length = requestLength;
|
|
}
|
|
} else {
|
|
start = document->size;
|
|
length = 0;
|
|
}
|
|
}
|
|
|
|
ccode = ConnWriteF(client->conn, "2001 nmap.document %ld\r\n", (long) length);
|
|
if (-1 == ccode) goto finish;
|
|
|
|
if (0 != XplFSeek64(fh, start, SEEK_SET)) {
|
|
ccode = ConnWriteStr(client->conn, MSG4224CANTREAD);
|
|
goto finish;
|
|
}
|
|
ccode = ConnWriteFromFile(client->conn, fh, length);
|
|
if (-1 == ccode) {
|
|
goto finish;
|
|
}
|
|
ccode = ConnWriteStr(client->conn, "\r\n");
|
|
}
|
|
|
|
finish:
|
|
if (fh) {
|
|
fclose(fh);
|
|
}
|
|
|
|
return ccode;
|
|
}
|
|
|
|
CCode
|
|
StoreCommandALARMS(StoreClient *client, uint64_t start, uint64_t end)
|
|
{
|
|
if (client) {} // FIXME compile error
|
|
if (start) {} // FIXME compile error
|
|
if (end) {} // FIXME compile error
|
|
/* FIXME - redo this completely
|
|
*
|
|
AlarmInfoStmt *stmt;
|
|
AlarmInfo info;
|
|
CCode ccode = 0;
|
|
|
|
stmt = AlarmDBListAlarms(client->system.alarmdb, start, end);
|
|
if (!stmt) {
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
do {
|
|
switch (AlarmInfoStmtStep(client->system.alarmdb, stmt, &info)) {
|
|
case 1:
|
|
ccode = ConnWriteF(client->conn,
|
|
"2001 " GUID_FMT " %lld\r\n"
|
|
"2001 nmap.alarm.summary %d\r\n%s\r\n",
|
|
info.guid, BongoCalTimeUtcAsUint64(info.time),
|
|
strlen(info.summary), info.summary);
|
|
if (-1 == ccode) {
|
|
break;
|
|
}
|
|
|
|
if (info.email) {
|
|
ccode = ConnWriteF(client->conn,
|
|
"2001 nmap.alarm.email %d\r\n%s\r\n",
|
|
strlen(info.email), info.email);
|
|
} else {
|
|
ccode = ConnWriteF(client->conn, MSG3245NOPROP, "nmap.alarm.email");
|
|
}
|
|
if (-1 == ccode) {
|
|
break;
|
|
}
|
|
|
|
if (info.sms) {
|
|
ccode = ConnWriteF(client->conn,
|
|
"2001 nmap.alarm.sms %d\r\n%s\r\n",
|
|
strlen(info.email), info.email);
|
|
} else {
|
|
ccode = ConnWriteF(client->conn, MSG3245NOPROP, "nmap.alarm.sms");
|
|
}
|
|
break;
|
|
case 0:
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
goto finish;
|
|
case -1:
|
|
default:
|
|
ccode = ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
goto finish;
|
|
}
|
|
} while (ccode >= 0);
|
|
|
|
finish:
|
|
AlarmStmtEnd(client->system.alarmdb, stmt);
|
|
*/
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandCALENDARS(StoreClient *client, unsigned long mask,
|
|
StorePropInfo *props, int propcount)
|
|
{
|
|
StoreObject calendar_collection;
|
|
CCode ccode;
|
|
|
|
if (mask) {
|
|
// FIXME : mask is unused in this function thus far
|
|
}
|
|
|
|
StoreObjectFind(client, STORE_CALENDARS_GUID, &calendar_collection);
|
|
ccode = StoreObjectCheckAuthorization(client, &calendar_collection, STORE_PRIV_LIST);
|
|
if (ccode) return ccode;
|
|
|
|
return StoreObjectIterCollectionContents(client, &calendar_collection, -1,
|
|
-1, 0, 0, props, propcount, "= nmap.type 6", NULL, FALSE);
|
|
}
|
|
|
|
// list the collections who are subcollections of container
|
|
CCode
|
|
StoreCommandCOLLECTIONS(StoreClient *client, StoreObject *container)
|
|
{
|
|
CCode ccode;
|
|
|
|
ccode = StoreObjectCheckAuthorization(client, container, STORE_PRIV_LIST);
|
|
if (ccode) return ConnWriteStr(client->conn, MSG4240NOPERMISSION);
|
|
|
|
return StoreObjectIterSubcollections(client, container);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandCONVERSATION(StoreClient *client, StoreObject *conversation,
|
|
StorePropInfo *props, int propcount)
|
|
{
|
|
CCode ccode = 0;
|
|
StoreObject conversation_collection;
|
|
|
|
StoreObjectFind(client, STORE_CONVERSATIONS_GUID, &conversation_collection);
|
|
ccode = StoreObjectCheckAuthorization(client, &conversation_collection, STORE_PRIV_READ);
|
|
if (ccode) return ConnWriteStr(client->conn, MSG4240NOPERMISSION);
|
|
|
|
return StoreObjectIterConversationMails(client, conversation, props, propcount);
|
|
}
|
|
|
|
// FIXME: Need to implement support for 'center' properly.
|
|
// The headers options can take a running jump, though.
|
|
CCode
|
|
StoreCommandCONVERSATIONS(StoreClient *client, const char *source, const char *query,
|
|
int start, int end, StoreObject *center,
|
|
uint32_t flagsmask, uint32_t flags,
|
|
int displayTotal,
|
|
StoreHeaderInfo *headers, int headercount,
|
|
StorePropInfo *props, int propcount)
|
|
{
|
|
int ccode;
|
|
StoreObject conversation_collection;
|
|
BOOL show_total;
|
|
|
|
if (headers || headercount || source || center) {
|
|
// FIXME : these are current unused
|
|
}
|
|
|
|
StoreObjectFind(client, STORE_CONVERSATIONS_GUID, &conversation_collection);
|
|
|
|
// FIXME: which priv should we really be checking? either/or/both?
|
|
ccode = StoreObjectCheckAuthorization(client, &conversation_collection, STORE_PRIV_LIST);
|
|
if (ccode) return ConnWriteStr(client->conn, MSG4240NOPERMISSION);
|
|
ccode = StoreObjectCheckAuthorization(client, &conversation_collection, STORE_PRIV_READ);
|
|
if (ccode) return ConnWriteStr(client->conn, MSG4240NOPERMISSION);
|
|
|
|
show_total = (displayTotal == 0)? FALSE : TRUE;
|
|
|
|
return StoreObjectIterCollectionContents(client, &conversation_collection, start,
|
|
end, flagsmask, flags, props, propcount, NULL, query, show_total);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandCOPY(StoreClient *client, StoreObject *object, StoreObject *collection)
|
|
{
|
|
CCode ccode;
|
|
FILE *src = NULL;
|
|
char srcpath[XPL_MAX_PATH + 1];
|
|
char dstpath[XPL_MAX_PATH + 1];
|
|
StoreObject newobject;
|
|
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
// check the parameters are ok and the document exists
|
|
if (STORE_IS_FOLDER(object->type)) return ConnWriteStr(client->conn, MSG3015BADDOCTYPE);
|
|
|
|
// check authorization on the parent collection
|
|
ccode = StoreObjectCheckAuthorization(client, collection, STORE_PRIV_BIND | STORE_PRIV_READ);
|
|
if (ccode) return ccode;
|
|
|
|
// copy the files on disk
|
|
FindPathToDocument(client, object->collection_guid, object->guid, srcpath, sizeof(srcpath));
|
|
src = fopen(srcpath, "r");
|
|
if (!src) {
|
|
ccode = ConnWriteStr(client->conn, MSG4224CANTREADMBOX);
|
|
goto finish;
|
|
}
|
|
fclose(src);
|
|
|
|
MaildirTempDocument(client, collection->guid, dstpath, sizeof(dstpath));
|
|
|
|
if (link(srcpath, dstpath) != 0) {
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
// TODO: how to detect MSG5220QUOTAERR ?
|
|
|
|
// make a copy of the storeobject
|
|
memcpy(&newobject, object, sizeof(StoreObject));
|
|
newobject.guid = 0;
|
|
newobject.filename[0] = '\0';
|
|
|
|
if (StoreObjectCreate(client, &newobject))
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
|
|
StoreObjectCopyInfo(client, object, &newobject);
|
|
|
|
// swap dest to src, and find new dest - move temp file into correct position
|
|
srcpath[0] = '\0';
|
|
strncpy(srcpath, dstpath, sizeof(srcpath));
|
|
FindPathToDocument(client, collection->guid, newobject.guid, dstpath, sizeof(dstpath));
|
|
if (link(srcpath, dstpath) != 0) {
|
|
// StoreObjectCreate saved the new object; if we can't create
|
|
// the data, we should remove that object
|
|
StoreObjectRemove(client, &newobject);
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
unlink(srcpath);
|
|
|
|
// update metadata
|
|
newobject.collection_guid = collection->guid;
|
|
newobject.time_created = newobject.time_modified = time(NULL);
|
|
|
|
StoreObjectFixUpFilename(collection, &newobject);
|
|
|
|
StoreObjectSave(client, &newobject);
|
|
|
|
// slight race here without store-level locking
|
|
StoreObjectUpdateImapUID(client, &newobject);
|
|
|
|
++client->stats.insertions;
|
|
StoreWatcherEvent(client, &newobject, STORE_WATCH_EVENT_NEW);
|
|
|
|
ccode = ConnWriteF(client->conn, "1000 " GUID_FMT " %d\r\n",
|
|
newobject.guid, newobject.version);
|
|
|
|
finish:
|
|
return ccode;
|
|
}
|
|
|
|
// FIXME: doesn't detect all error conditions
|
|
CCode
|
|
StoreCommandCREATE(StoreClient *client, char *name, uint64_t guid)
|
|
{
|
|
StoreObject object;
|
|
StoreObject container;
|
|
int ret;
|
|
char container_path[MAX_FILE_NAME+1];
|
|
char *container_final_sep = NULL;
|
|
char *ptr;
|
|
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
// look for existing name/guid first?
|
|
if (name != NULL) {
|
|
if (StoreObjectFindByFilename(client, name, &object) == 0) {
|
|
return ConnWriteStr(client->conn, MSG4226MBOXEXISTS);
|
|
}
|
|
}
|
|
if (guid != 0) {
|
|
if (StoreObjectFind(client, guid, &object) == 0) {
|
|
return ConnWriteStr(client->conn, MSG4226GUIDINUSE);
|
|
}
|
|
}
|
|
|
|
// first, find the name of the containing collection from the
|
|
// name given. This will end in a /, but we will want to remove
|
|
// that final / later unless the path is literally '/', so we
|
|
// save the pointer for later...
|
|
strncpy(container_path, name, MAX_FILE_NAME);
|
|
ptr = strrchr(container_path, '/');
|
|
if (ptr == NULL) {
|
|
return ConnWriteStr(client->conn, MSG3019BADCOLLNAME);
|
|
}
|
|
container_final_sep = ptr++;
|
|
*ptr = '\0';
|
|
|
|
// Create the object first to allocate our wanted guid
|
|
memset(&object, 0, sizeof(StoreObject));
|
|
object.type = STORE_DOCTYPE_FOLDER;
|
|
object.guid = guid;
|
|
strncpy(object.filename, name, MAX_FILE_NAME);
|
|
|
|
ret = StoreObjectCreate(client, &object);
|
|
switch (ret) {
|
|
case 0:
|
|
// if it's successful, that's ok.
|
|
break;
|
|
case -1:
|
|
return ConnWriteF(client->conn, "4226 Guid " GUID_FMT " already exists\r\n",
|
|
object.guid);
|
|
break;
|
|
default:
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
break;
|
|
}
|
|
/* FIXME - detect following errors?
|
|
return ConnWriteStr(client->conn, MSG4240NOPERMISSION);
|
|
return ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
*/
|
|
// now try to find the parent collection, creating any collections
|
|
// as needed.
|
|
if (container_path[1] != '\0') {
|
|
uint64_t parent_guid = 0;
|
|
|
|
ptr = container_path + 1;
|
|
while (*ptr != '\0') {
|
|
if (*ptr == '/') {
|
|
*ptr = '\0';
|
|
if (StoreObjectFindByFilename(client, container_path, &container)) {
|
|
// need to create this path
|
|
memset(&container, 0, sizeof(StoreObject));
|
|
container.type = STORE_DOCTYPE_FOLDER;
|
|
container.collection_guid = parent_guid;
|
|
strncpy(container.filename, container_path, MAX_FILE_NAME);
|
|
ret = StoreObjectCreate(client, &container);
|
|
if (ret != 0) {
|
|
StoreObjectRemove(client, &container);
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
// save the guid for later.
|
|
parent_guid = container.guid;
|
|
} else {
|
|
if (container.type != 4096) {
|
|
// this isn't a collection - erk!
|
|
return ConnWriteStr(client->conn, MSG3019BADCOLLNAME);
|
|
}
|
|
}
|
|
*ptr = '/';
|
|
}
|
|
ptr++;
|
|
}
|
|
}
|
|
|
|
// remove any trailing / from the path unless it's literally /
|
|
if (container_final_sep != container_path) {
|
|
*container_final_sep = '\0';
|
|
}
|
|
// now find the containing collection
|
|
if (StoreObjectFindByFilename(client, container_path, &container)) {
|
|
// no such collection - but it should have been created. Must be DB error
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
|
|
// check we can save this new object and if not, error out
|
|
object.collection_guid = container.guid;
|
|
StoreObjectFixUpFilename(&container, &object);
|
|
|
|
if (StoreObjectSave(client, &object)) {
|
|
StoreObjectRemove(client, &object);
|
|
return ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
}
|
|
|
|
// announce creation on parent container
|
|
++client->stats.insertions;
|
|
StoreWatcherEvent(client, &container, STORE_WATCH_EVENT_NEW);
|
|
|
|
return ConnWriteF(client->conn, "1000 " GUID_FMT " %d\r\n",
|
|
object.guid, object.version);
|
|
}
|
|
|
|
|
|
CCode
|
|
StoreCommandDELETE(StoreClient *client, StoreObject *object)
|
|
{
|
|
StoreObject collection;
|
|
char path[XPL_MAX_PATH];
|
|
int ccode;
|
|
|
|
// FIXME: do we want some kind of delete-only mode too?
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if (STORE_IS_FOLDER(object->type))
|
|
return ConnWriteStr(client->conn, MSG4231USEREMOVE);
|
|
|
|
// find the containing collection
|
|
ccode = StoreObjectFind(client, object->collection_guid, &collection);
|
|
if (ccode != 0) return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
|
|
// check we have the required permissions.
|
|
ccode = StoreObjectCheckAuthorization(client, &collection, STORE_PRIV_UNBIND);
|
|
if (ccode) return ConnWriteStr(client->conn, MSG5005DBLIBERR); // TODO : less generic errors
|
|
|
|
switch(object->type) {
|
|
case STORE_DOCTYPE_MAIL:
|
|
// check to see if we can remove this document from a conversation
|
|
break;
|
|
case STORE_DOCTYPE_CALENDAR:
|
|
// do we want to remove its events also?
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// TODO: check to see if we can unlink other documents from this document
|
|
|
|
// Remove the document from the store
|
|
StoreObjectRemove(client, object);
|
|
|
|
// Remove document from the search index
|
|
// IndexRemoveDocument(index, guid);
|
|
|
|
// Remove the file content from the filesystem
|
|
FindPathToDocument(client, object->collection_guid, object->guid, path, sizeof(path));
|
|
if (unlink(path) != 0) {
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
|
|
// update stats and fire any event
|
|
++client->stats.deletions;
|
|
StoreWatcherEvent(client, object, STORE_WATCH_EVENT_DELETED);
|
|
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
|
|
finish:
|
|
|
|
return ccode;
|
|
}
|
|
|
|
|
|
// one of filename and bytes must be set
|
|
// if DELIVER FILE - it's filename
|
|
// if DELIVER STREAM - it's bytes on a network socket
|
|
|
|
CCode
|
|
StoreCommandDELIVER(StoreClient *client, char *sender, char *authSender,
|
|
char *filename, size_t bytes)
|
|
{
|
|
CCode ccode;
|
|
FILE *msgfile = NULL;
|
|
char tmpFile[XPL_MAX_PATH+1];
|
|
|
|
tmpFile[0] = 0;
|
|
|
|
if (!filename) {
|
|
const char *work_dir = (char *)MsgGetDir(MSGAPI_DIR_WORK, NULL, 0);
|
|
|
|
if (bytes <= 0) {
|
|
return ConnWriteStr(client->conn, MSG3017INTARGRANGE);
|
|
}
|
|
msgfile = XplOpenTemp(work_dir, "w+b", tmpFile);
|
|
if (!msgfile) {
|
|
return ConnWriteStr(client->conn, MSG5202TMPWRITEERR);
|
|
}
|
|
ccode = ConnReadToFile(client->conn, msgfile, bytes);
|
|
if (-1 == ccode) {
|
|
unlink(tmpFile);
|
|
fclose (msgfile);
|
|
return ConnWriteStr(client->conn, MSG5202TMPWRITEERR);
|
|
}
|
|
} else {
|
|
struct stat sb;
|
|
char fullPath[XPL_MAX_PATH + 1];
|
|
|
|
snprintf(fullPath, sizeof(fullPath), "%s/%s", StoreAgent.store.spoolDir, filename);
|
|
|
|
|
|
if (stat(fullPath, &sb)) {
|
|
return ConnWriteStr(client->conn, MSG4201FILENOTFOUND);
|
|
}
|
|
bytes = sb.st_size;
|
|
|
|
msgfile = fopen(fullPath, "r");
|
|
if (!msgfile) {
|
|
return ConnWriteStr(client->conn, MSG5201FILEREADERR);
|
|
}
|
|
}
|
|
|
|
if (-1 == (ccode = ConnWriteStr(client->conn, MSG2053SENDRECIP)) ||
|
|
-1 == (ccode = ConnFlush(client->conn)))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
while (ccode >= 0) {
|
|
char *args[3];
|
|
char *recipient;
|
|
char *mbox;
|
|
unsigned long flags = 0;
|
|
int n;
|
|
int status;
|
|
char buffer[CONN_BUFSIZE];
|
|
|
|
ccode = ConnReadAnswer(client->conn, buffer, sizeof(buffer));
|
|
if (-1 == ccode) {
|
|
goto cleanup;
|
|
}
|
|
if (!buffer[0]) {
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
break;
|
|
}
|
|
|
|
/* <recipient> <mailbox> <flags> */
|
|
|
|
n = BongoStringSplit(buffer, ' ', args, 3);
|
|
if (TOKEN_OK != (ccode = CheckTokC(client, n, 3, 3)) ||
|
|
TOKEN_OK != (ccode = ParseUserName(client, args[0])) ||
|
|
TOKEN_OK != (ccode = ParseUnsignedLong(client, args[2], &flags)))
|
|
{
|
|
if (-1 == (ccode = ConnFlush(client->conn))) {
|
|
goto cleanup;
|
|
}
|
|
continue;
|
|
}
|
|
recipient = args[0];
|
|
mbox = args[1];
|
|
|
|
if (fseek (msgfile, 0, SEEK_SET)) {
|
|
ccode = ConnWriteStr(client->conn, MSG5201FILEREADERR);
|
|
goto cleanup;
|
|
}
|
|
SelectStore(client, recipient);
|
|
|
|
if (! client->readonly) {
|
|
status = DeliverMessageToMailbox(client, sender, authSender, recipient, mbox,
|
|
msgfile, 0, bytes, flags);
|
|
} else {
|
|
status = DELIVER_FAILURE;
|
|
}
|
|
UnselectStore(client);
|
|
switch (status) {
|
|
case DELIVER_SUCCESS:
|
|
ccode = ConnWriteStr(client->conn, MSG1000DELIVERYOK);
|
|
break;
|
|
default:
|
|
ccode = ConnWriteF(client->conn, MSG5260DELIVERYFAILED, status);
|
|
break;
|
|
}
|
|
if (-1 == ccode) {
|
|
goto cleanup;
|
|
}
|
|
if (-1 == (ccode = ConnFlush(client->conn))) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (msgfile) {
|
|
fclose (msgfile);
|
|
}
|
|
if (tmpFile[0]) {
|
|
unlink(tmpFile);
|
|
}
|
|
|
|
return ccode;
|
|
}
|
|
|
|
CCode
|
|
StoreCommandEVENTS(StoreClient *client,
|
|
char *startUTC, char *endUTC,
|
|
StoreObject *calendar,
|
|
unsigned int mask,
|
|
char *uid,
|
|
const char *query,
|
|
int start, int end,
|
|
StorePropInfo *props, int propcount)
|
|
{
|
|
// pretend we have no events for now.
|
|
if (client || startUTC || endUTC || calendar || mask ||
|
|
uid || query || start || end || props || propcount) {
|
|
// FIXME : compiler warnings in blank function
|
|
}
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
/*
|
|
CCode ccode = 0;
|
|
|
|
// FIXME - need to handle STORE_PRIV_READ_BUSY (bug 174023)
|
|
|
|
if (calendar) {
|
|
ccode = StoreCheckAuthorization(client, calendar,
|
|
STORE_PRIV_LIST);
|
|
if (ccode) {
|
|
return ccode;
|
|
}
|
|
}
|
|
|
|
if (query) {
|
|
IndexHandle *index;
|
|
uint64_t *guids = NULL;
|
|
uint64_t *guidsptr = NULL;
|
|
char newQuery[CONN_BUFSIZE + 64];
|
|
int total;
|
|
int i;
|
|
DStoreDocInfo *info;
|
|
int checkauth = !calendar && !IsOwnStoreSelected(client);
|
|
|
|
snprintf(newQuery, sizeof(newQuery) - 1, "(nmap.type:event) AND (%s)", query);
|
|
|
|
index = IndexOpen(client);
|
|
if (!index) {
|
|
return ConnWriteStr(client->conn, MSG4120INDEXLOCKED);
|
|
}
|
|
guids = IndexSearch(index, newQuery, TRUE, TRUE,
|
|
-1, -1, NULL, &total, INDEX_SORT_DATE_DESC);
|
|
IndexClose(index);
|
|
|
|
if (!guids) {
|
|
return ConnWriteStr(client->conn, MSG5007INDEXLIBERR);
|
|
}
|
|
|
|
// FIXME: This is a big allocation
|
|
info = BONGO_MEMSTACK_ALLOC(&client->memstack, total * sizeof(DStoreDocInfo));
|
|
|
|
for (guidsptr = guids, i = 0; *guidsptr; ++guidsptr) {
|
|
switch (DStoreGetDocInfoGuid(client->handle, *guidsptr, &info[i])) {
|
|
case 1:
|
|
if (checkauth &&
|
|
StoreCheckAuthorizationQuiet(client, info, STORE_PRIV_READ))
|
|
{
|
|
--total;
|
|
continue;
|
|
}
|
|
i++;
|
|
break;
|
|
case 0:
|
|
--total;
|
|
continue;
|
|
case -1:
|
|
MemFree(guids);
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
}
|
|
|
|
MemFree(guids);
|
|
|
|
if (-1 == start) {
|
|
start = 0;
|
|
end = total;
|
|
}
|
|
|
|
for (i = start; i < end && i < total && -1 != ccode; i++) {
|
|
ccode = ShowDocumentInfo(client, &info[i], props, propcount);
|
|
}
|
|
|
|
if (-1 != ccode) {
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
} else {
|
|
DStoreStmt *stmt;
|
|
AuthFilterStruct f;
|
|
|
|
stmt = DStoreFindEvents(client->handle,
|
|
startUTC, endUTC,
|
|
calendar ? calendar->guid : 0,
|
|
mask, uid);
|
|
|
|
if (!stmt) {
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
|
|
if (!calendar && !IsOwnStoreSelected(client)) {
|
|
f.client = client;
|
|
f.neededPrivileges = STORE_PRIV_READ;
|
|
DStoreInfoStmtAddFilter(stmt, AuthFilter, &f);
|
|
}
|
|
|
|
ccode = ShowStmtDocuments(client, stmt, props, propcount);
|
|
|
|
DStoreStmtEnd(client->handle, stmt);
|
|
}
|
|
|
|
return 0;
|
|
*/
|
|
}
|
|
|
|
CCode
|
|
StoreCommandFLAG(StoreClient *client, StoreObject *object, uint32_t change, int mode)
|
|
{
|
|
uint32_t old_flags;
|
|
int ccode;
|
|
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if (mode == STORE_FLAG_SHOW) {
|
|
ccode = StoreObjectCheckAuthorization(client, object, STORE_PRIV_READ_PROPS);
|
|
if (ccode) return ccode;
|
|
return ConnWriteF(client->conn, "1000 %u %u\r\n", object->flags, object->flags);
|
|
}
|
|
|
|
// just handle ADD / REMOVE / REPLACE now
|
|
old_flags = object->flags;
|
|
if (mode == STORE_FLAG_ADD) object->flags |= change;
|
|
if (mode == STORE_FLAG_REMOVE) object->flags &= ~change;
|
|
if (mode == STORE_FLAG_REPLACE) object->flags = change;
|
|
|
|
ccode = StoreObjectCheckAuthorization(client, object, STORE_PRIV_WRITE_PROPS);
|
|
if (ccode) return ccode;
|
|
|
|
// FIXME: need to propogate changes on mail documents to conversations?
|
|
if (object->type == STORE_DOCTYPE_MAIL) {
|
|
|
|
}
|
|
|
|
// save the changes
|
|
if (StoreObjectSave(client, object) != 0) {
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
|
|
++client->stats.updates;
|
|
StoreWatcherEvent(client, object, STORE_WATCH_EVENT_FLAGS);
|
|
|
|
return ConnWriteF(client->conn, "1000 %u %u\r\n", old_flags, object->flags);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandINFO(StoreClient *client, StoreObject *object,
|
|
StorePropInfo *props, int propcount)
|
|
{
|
|
return StoreObjectIterDocinfo(client, object, props, propcount, FALSE);
|
|
}
|
|
|
|
|
|
CCode
|
|
StoreCommandISEARCH(StoreClient *client, const char *query, int start, int end,
|
|
StorePropInfo *props, int propcount)
|
|
{
|
|
if (client || query || start || end || props || propcount) {
|
|
// FIXME : compiler warnings in blank function
|
|
}
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
/*
|
|
char buf[200];
|
|
int ccode = 0;
|
|
IndexHandle *index;
|
|
uint64_t *guids;
|
|
int displayed;
|
|
int total;
|
|
|
|
if (end - start > STORE_GUID_LIST_MAX) {
|
|
return ConnWriteStr(client->conn, MSG3021BADRANGESIZE);
|
|
}
|
|
|
|
index = IndexOpen(client);
|
|
if (!index) {
|
|
return ConnWriteStr(client->conn, MSG4120INDEXLOCKED);
|
|
}
|
|
|
|
guids = IndexSearch(index, query, FALSE, FALSE, start, end, &displayed, &total,
|
|
INDEX_SORT_RELEVANCE);
|
|
if (!guids) {
|
|
IndexClose(index);
|
|
return ConnWriteStr(client->conn, MSG5007INDEXLIBERR);
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), "1000 %d\r\n", total);
|
|
|
|
ccode = ShowGuidsDocumentsWithMessage(client, guids, NULL, NULL,
|
|
props, propcount, buf);
|
|
|
|
IndexClose(index);
|
|
|
|
MemFree(guids);
|
|
|
|
return ccode;
|
|
*/
|
|
}
|
|
|
|
CCode
|
|
StoreCommandLIST(StoreClient *client,
|
|
StoreObject *collection,
|
|
int start, int end,
|
|
uint32_t flagsmask, uint32_t flags,
|
|
StorePropInfo *props, int propcount)
|
|
{
|
|
int ccode;
|
|
|
|
ccode = StoreObjectCheckAuthorization(client, collection, STORE_PRIV_LIST);
|
|
if (ccode) return ccode;
|
|
|
|
return StoreObjectIterCollectionContents(client, collection, start,
|
|
end, flagsmask, flags, props, propcount, NULL, NULL, FALSE);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandLINK(StoreClient *client, StoreObject *document, StoreObject *related)
|
|
{
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if (StoreObjectLink(client, document, related) == 0) {
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
} else {
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
}
|
|
|
|
CCode
|
|
StoreCommandLINKS(StoreClient *client, BOOL reverse, StoreObject *document)
|
|
{
|
|
int ccode;
|
|
|
|
// FIXME: does this auth check make sense?
|
|
ccode = StoreObjectCheckAuthorization(client, document, STORE_PRIV_READ);
|
|
if (ccode) return ccode;
|
|
|
|
return StoreObjectIterLinks(client, document, reverse);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandUNLINK(StoreClient *client, StoreObject *document, StoreObject *related)
|
|
{
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if (StoreObjectUnlink(client, document, related) == 0) {
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
} else {
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
}
|
|
|
|
CCode
|
|
StoreCommandMAILINGLISTS(StoreClient *client, char *source)
|
|
{
|
|
// TODO
|
|
if (client || source) {
|
|
// FIXME : blank function
|
|
}
|
|
return ConnWriteStr(client->conn, MSG3000UNKNOWN);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandMESSAGES(StoreClient *client, const char *source, const char *query,
|
|
int start, int end, StoreObject *center,
|
|
uint32_t flagsmask, uint32_t flags,
|
|
int displayTotal,
|
|
StoreHeaderInfo *headers, int headercount,
|
|
StorePropInfo *props, int propcount)
|
|
{
|
|
// FIXME: obsolete? This looks very similar to LIST; just added
|
|
// source and query. Might as well make it one command...
|
|
if (client || source || query || start || end || center ||
|
|
flagsmask || flags || displayTotal || headers || headercount ||
|
|
props || propcount) {
|
|
// FIXME : stop compiler warning on empty function
|
|
}
|
|
return ConnWriteStr(client->conn, MSG3000UNKNOWN);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandMIME(StoreClient *client, StoreObject *document)
|
|
{
|
|
int ccode = 0;
|
|
MimeReport *report = NULL;
|
|
NLockStruct *lock = NULL;
|
|
|
|
ccode = StoreObjectCheckAuthorization(client, document, STORE_PRIV_READ);
|
|
if (ccode) return ccode;
|
|
|
|
if (STORE_IS_FOLDER(document->type)) return ConnWriteStr(client->conn, MSG3015BADDOCTYPE);
|
|
|
|
// ccode = StoreGetSharedFairLockQuiet(client, &lock, document->collection_guid, 3000);
|
|
// if (ccode) return ccode;
|
|
|
|
// FIXME: need to re-do the MimeGetInfo() API for store objects
|
|
switch (MimeGetInfo(client, document, &report)) {
|
|
case 1:
|
|
ccode = MimeReportSend(client, report);
|
|
MimeReportFree(report);
|
|
if (-1 != ccode) {
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
break;
|
|
case -1:
|
|
ccode = ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
break;
|
|
case -2:
|
|
ccode = ConnWriteStr(client->conn, MSG4120DBLOCKED);
|
|
break;
|
|
case -3:
|
|
ccode = ConnWriteF(client->conn, MSG4220NOGUID);
|
|
break;
|
|
case -4:
|
|
ccode = ConnWriteStr(client->conn, MSG4224CANTREAD);
|
|
break;
|
|
case -5:
|
|
case 0:
|
|
default:
|
|
ccode = ConnWriteStr(client->conn, MSG5004INTERNALERR);
|
|
break;
|
|
}
|
|
|
|
if (lock) {
|
|
// StoreReleaseSharedFairLockQuiet(&lock);
|
|
}
|
|
|
|
return ccode;
|
|
}
|
|
|
|
// FIXME: needs to move any store properties etc. related to this document
|
|
|
|
CCode
|
|
StoreCommandMOVE(StoreClient *client, StoreObject *object,
|
|
StoreObject *destination_collection, const char *newfilename)
|
|
{
|
|
int ccode = 0;
|
|
char src_path[XPL_MAX_PATH];
|
|
char dst_path[XPL_MAX_PATH];
|
|
StoreObject source_collection;
|
|
StoreObject original;
|
|
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if (STORE_IS_FOLDER(object->type)) return ConnWriteStr(client->conn, MSG3015BADDOCTYPE);
|
|
if (!STORE_IS_FOLDER(destination_collection->type))
|
|
return ConnWriteStr(client->conn, MSG3015BADDOCTYPE);
|
|
|
|
ccode = StoreObjectFind(client, object->collection_guid, &source_collection);
|
|
if (ccode != 0) return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
|
|
// check we have the rights to do this move
|
|
ccode = StoreObjectCheckAuthorization(client, &source_collection, STORE_PRIV_UNBIND);
|
|
if (ccode) goto finish;
|
|
ccode = StoreObjectCheckAuthorization(client, destination_collection, STORE_PRIV_BIND);
|
|
if (ccode) goto finish;
|
|
|
|
// copy the original object so we can fire events on it later
|
|
memcpy(&original, object, sizeof(StoreObject));
|
|
|
|
// move the file to a temporary location
|
|
FindPathToDocument(client, object->collection_guid, object->guid, src_path, sizeof(src_path));
|
|
MaildirTempDocument(client, destination_collection->guid, dst_path, sizeof(dst_path));
|
|
|
|
if (link(src_path, dst_path) != 0) {
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
|
|
if (unlink(src_path) != 0) {
|
|
// FIXME: try to remove the new link we just created?
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
|
|
// now change the information on this object and fix up the filename
|
|
object->collection_guid = destination_collection->guid;
|
|
if (newfilename != NULL)
|
|
strncpy(object->filename, newfilename, MAX_FILE_NAME);
|
|
StoreObjectFixUpFilename(destination_collection, object);
|
|
StoreObjectUpdateImapUID(client, object);
|
|
|
|
if (STORE_DOCTYPE_MAIL == object->type) {
|
|
// FIXME: do something with the conversations ??
|
|
}
|
|
|
|
// move the temp doc we created to the new location
|
|
src_path[0] = '\0';
|
|
strncpy(src_path, dst_path, sizeof(src_path));
|
|
FindPathToDocument(client, object->collection_guid, object->guid, dst_path, sizeof(dst_path));
|
|
|
|
if (link(src_path, dst_path) != 0) {
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
if (unlink(src_path) != 0) {
|
|
// FIXME: try to remove the new link we just created?
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
|
|
// save our changes
|
|
StoreObjectSave(client, object);
|
|
|
|
// fire off any events
|
|
StoreWatcherEvent(client, &original, STORE_WATCH_EVENT_DELETED);
|
|
StoreWatcherEvent(client, object, STORE_WATCH_EVENT_NEW);
|
|
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
|
|
finish:
|
|
|
|
return ccode;
|
|
}
|
|
|
|
CCode
|
|
StoreCommandPROPGET(StoreClient *client,
|
|
StoreObject *object,
|
|
StorePropInfo *props, int propcount)
|
|
{
|
|
if (propcount == 0) {
|
|
return StoreObjectIterProperties(client, object);
|
|
} else {
|
|
// output requested props for each document
|
|
return StoreObjectIterDocinfo(client, object, props, propcount, TRUE);
|
|
}
|
|
}
|
|
|
|
CCode
|
|
StoreCommandPROPSET(StoreClient *client,
|
|
StoreObject *object,
|
|
StorePropInfo *prop,
|
|
int size)
|
|
{
|
|
int ccode;
|
|
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if (size) {} // FIXME : unused variable
|
|
|
|
prop->value = BONGO_MEMSTACK_ALLOC(&client->memstack, prop->valueLen + 1);
|
|
if (!prop->value) {
|
|
return ConnWriteStr(client->conn, MSG5001NOMEMORY);
|
|
}
|
|
if (-1 == (ccode = ConnWriteStr(client->conn, MSG2002SENDVALUE)) ||
|
|
-1 == (ccode = ConnFlush(client->conn)) ||
|
|
-1 == (ccode = ConnReadCount(client->conn, prop->value, prop->valueLen)))
|
|
{
|
|
return ccode;
|
|
}
|
|
prop->value[prop->valueLen] = 0;
|
|
|
|
ccode = StoreObjectCheckAuthorization(client, object, STORE_PRIV_WRITE_PROPS);
|
|
if (ccode) return ccode;
|
|
switch (prop->type) {
|
|
case STORE_PROP_EVENT_ALARM:
|
|
// FIXME: this did cache alarms in a separate DB.
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
break;
|
|
case STORE_PROP_ACCESS_CONTROL:
|
|
// FIXME: this is how we used to set ACLs.
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
break;
|
|
default:
|
|
// this is a plain external property
|
|
ccode = StoreObjectSetProperty(client, object, prop);
|
|
if (ccode < -1) {
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
} else if (ccode == -1) {
|
|
// failed, but already returned a notice to the client
|
|
return -1;
|
|
} else {
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
break;
|
|
}
|
|
|
|
++client->stats.updates;
|
|
StoreWatcherEvent(client, object, STORE_WATCH_EVENT_MODIFIED);
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
|
|
return ccode;
|
|
}
|
|
|
|
|
|
/* returns: -1, causing storecommandloop to exit */
|
|
CCode
|
|
StoreCommandQUIT(StoreClient *client)
|
|
{
|
|
if (-1 != ConnWriteStr(client->conn, MSG1000BYE)) {
|
|
ConnFlush(client->conn);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
CCode
|
|
StoreCommandREAD(StoreClient *client, StoreObject *object,
|
|
int64_t requestStart, uint64_t requestLength)
|
|
{
|
|
int ccode = 0;
|
|
|
|
// check that we're allowed to read
|
|
ccode = StoreObjectCheckAuthorization(client, object, STORE_PRIV_READ);
|
|
if (ccode) goto finish;
|
|
|
|
if (STORE_IS_FOLDER(object->type)) {
|
|
// 2001 <guid> <version>
|
|
ccode = ConnWriteF(client->conn, "2001 " GUID_FMT " %d\r\n", object->guid, object->version);
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
} else if (STORE_IS_DBONLY(object->type)) {
|
|
ccode = ConnWriteStr(client->conn, MSG3016DOCTYPEUNREADABLE);
|
|
} else {
|
|
ccode = ShowDocumentBody(client, object, requestStart, requestLength);
|
|
}
|
|
|
|
finish:
|
|
return ccode;
|
|
}
|
|
|
|
|
|
CCode
|
|
StoreCommandREINDEX(StoreClient *client, StoreObject *document)
|
|
{
|
|
// want to re-do this completely.
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if (document) { } // FIXME : stop compiler whining
|
|
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
|
|
// FIXME: doesn't handle following errors
|
|
// MSG3019BADCOLLNAME
|
|
// MSG4240NOPERMISSION
|
|
// MSG4228CANTWRITEMBOX
|
|
// FIXME: doesn't create collections as needed to create destination tree
|
|
CCode
|
|
StoreCommandRENAME(StoreClient *client, StoreObject *collection,
|
|
char *newfilename)
|
|
{
|
|
int ccode = 0;
|
|
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
ccode = StoreObjectCheckAuthorization(client, collection, STORE_PRIV_UNBIND);
|
|
if (ccode) return ccode;
|
|
|
|
// locking: want to acquire first a lock on the source tree,
|
|
// then take a lock on the non-existant destination tree
|
|
|
|
if (StoreObjectRenameSubobjects(client, collection, newfilename))
|
|
goto dberror;
|
|
|
|
strncpy(collection->filename, newfilename, MAX_FILE_NAME);
|
|
if (StoreObjectSave(client, collection))
|
|
goto dberror;
|
|
|
|
// FIXME - need to fire an event for each collection renamed
|
|
StoreWatcherEvent(client, collection, STORE_WATCH_EVENT_COLL_RENAMED);
|
|
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
|
|
dberror:
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
|
|
CCode
|
|
ReceiveToFile(StoreClient *client, const char *path, uint64_t *size)
|
|
{
|
|
FILE *fh;
|
|
CCode ccode;
|
|
uint64_t receive_size;
|
|
|
|
fh = fopen(path, "w");
|
|
if (!fh) {
|
|
if (ENOSPC == errno)
|
|
return ConnWriteStr(client->conn, MSG5220QUOTAERR); // FIXME
|
|
else
|
|
return ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
}
|
|
if (-1 == (ccode = ConnWrite(client->conn, "2002 Send document.\r\n", 21)) ||
|
|
-1 == (ccode = ConnFlush(client->conn))) {
|
|
return ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
}
|
|
|
|
if (*size > 0) {
|
|
ccode = ConnReadToFile(client->conn, fh, *size);
|
|
receive_size = *size;
|
|
} else {
|
|
ccode = receive_size = ConnReadToFileUntilEOS(client->conn, fh);
|
|
}
|
|
if (-1 == ccode) {
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
*size = receive_size;
|
|
|
|
ccode = fsync(fileno(fh));
|
|
if (ccode) {
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
fclose(fh);
|
|
fh = NULL;
|
|
goto finish;
|
|
}
|
|
|
|
ccode = fclose(fh);
|
|
fh = NULL;
|
|
if (ccode) {
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
|
|
return 0;
|
|
|
|
finish:
|
|
return ccode;
|
|
}
|
|
|
|
// FIXME: rstart/rend range processing totally broken.
|
|
// FIXME: range processing needs to unlink and copy-on-write
|
|
// FIXME: we should probably be removing some/all document properties?
|
|
CCode
|
|
StoreCommandREPLACE(StoreClient *client, StoreObject *object, int size, uint64_t rstart, uint64_t rend, uint32_t version)
|
|
{
|
|
CCode ccode;
|
|
char path[XPL_MAX_PATH + 1];
|
|
char tmppath[XPL_MAX_PATH + 1];
|
|
uint64_t tmpsize;
|
|
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if ((rend <= rstart) && ( rstart > 0)) {
|
|
// given a range that makes no sense....!
|
|
return ConnWriteStr(client->conn, MSG3021BADRANGESIZE);
|
|
}
|
|
|
|
// check object type and permissions
|
|
if (STORE_IS_FOLDER(object->type) || STORE_IS_CONVERSATION(object->type)) {
|
|
return ConnWriteStr(client->conn, MSG3015BADDOCTYPE);
|
|
}
|
|
|
|
ccode = StoreObjectCheckAuthorization(client, object, STORE_PRIV_WRITE_CONTENT);
|
|
if (ccode) return ccode;
|
|
|
|
// write the waiting data to a temporary file
|
|
MaildirTempDocument(client, object->collection_guid, tmppath, sizeof(tmppath));
|
|
|
|
tmpsize = size;
|
|
if (size < 0) tmpsize = 0;
|
|
|
|
ccode = ReceiveToFile(client, tmppath, &tmpsize);
|
|
if (ccode) goto finish;
|
|
|
|
// update metadata
|
|
if ((rend != 0) && (rend > object->size)) {
|
|
// writing a range - new size must be maximum of old size and new end of range
|
|
tmpsize = rend;
|
|
}
|
|
|
|
StoreObjectUpdateImapUID(client, object);
|
|
StoreProcessDocument(client, object, tmppath);
|
|
|
|
// update the document
|
|
FindPathToDocument(client, object->collection_guid, object->guid, path, sizeof(path));
|
|
if (rend != 0) {
|
|
// we're writing a range, not replacing the whole file
|
|
// TODO
|
|
} else {
|
|
if (rename(tmppath, path) != 0) {
|
|
ccode = ConnWriteStr(client->conn, MSG4228CANTWRITEMBOX);
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
// update the version if required
|
|
if ((version > 0) && (version != object->version)) {
|
|
object->version = version;
|
|
}
|
|
// update other metadata
|
|
StoreObjectUpdateModifiedTime(object);
|
|
object->size = tmpsize;
|
|
|
|
if (StoreObjectSave(client, object)) {
|
|
ccode = ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
goto finish;
|
|
}
|
|
|
|
// now we're done, do watcher events
|
|
++client->stats.updates;
|
|
StoreWatcherEvent(client, object, STORE_WATCH_EVENT_MODIFIED);
|
|
|
|
ccode = ConnWriteF(client->conn, "1000 " GUID_FMT " %d\r\n",
|
|
object->guid, object->version);
|
|
|
|
finish:
|
|
return ccode;
|
|
}
|
|
|
|
// we want to check through this store and fix up any bad data
|
|
// FIXME: there is a race condition in here. When we turn on read-only
|
|
// mode, we should wait for any other users to finish up. In reality,
|
|
// this is going to be exceptionally hard to trigger though.
|
|
CCode
|
|
StoreCommandREPAIR(StoreClient *client)
|
|
{
|
|
StoreObject object;
|
|
int result;
|
|
char reason[200];
|
|
|
|
// put the store into read-only mode to prevent other people
|
|
// fiddling with it!
|
|
SetStoreReadonly(client, MSG4250READONLY);
|
|
|
|
// we want to loop through each document in the store and make
|
|
// sure that it's "sane"
|
|
object.guid = 0;
|
|
while ((result = StoreObjectFindNext(client, object.guid + 1, &object)) == 0) {
|
|
int test = StoreObjectRepair(client, &object);
|
|
|
|
switch (test) {
|
|
case 1:
|
|
// repaired
|
|
sprintf(reason, "2001 %s\r\n", object.filename);
|
|
break;
|
|
case -1:
|
|
// broken
|
|
sprintf(reason, "2011 %s\r\n", object.filename);
|
|
break;
|
|
case -2:
|
|
// removed
|
|
sprintf(reason, "2012 %s\r\n", object.filename);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (test != 0) {
|
|
ConnWriteStr(client->conn, reason);
|
|
}
|
|
}
|
|
|
|
// put it back in read-write mode
|
|
UnsetStoreReadonly(client);
|
|
|
|
if (result == -1) {
|
|
// no such document - therefore, we went through all ok
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
} else {
|
|
// result must be -2 or something - some error occured
|
|
// sad; DB repair shouldn't return a db error... :D
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
}
|
|
|
|
CCode
|
|
StoreCommandREMOVE(StoreClient *client, StoreObject *collection)
|
|
{
|
|
CCode ccode;
|
|
|
|
// FIXME: do we want some kind of 'can delete' state?
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if (! STORE_IS_FOLDER(collection->type)) {
|
|
return ConnWriteStr(client->conn, MSG4231USEPURGE);
|
|
} else {
|
|
ccode = StoreObjectCheckAuthorization(client, collection, STORE_PRIV_UNBIND);
|
|
if (ccode) return ccode;
|
|
|
|
if (StoreObjectRemoveCollection(client, collection) == 0) {
|
|
// finished with success!
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
} else {
|
|
// some error
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* N.B. This command does no locking. It is for debugging purposes only. */
|
|
|
|
CCode
|
|
StoreCommandRESET(StoreClient *client)
|
|
{
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
DeleteStore(client);
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
|
|
|
|
CCode
|
|
StoreCommandSTORE(StoreClient *client, char *user)
|
|
{
|
|
if (!user) {
|
|
UnselectStore(client);
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
|
|
if (StoreAgent.installMode || !strncmp(user, "_system", 7)) {
|
|
int ret = 0;
|
|
ret = SelectStore(client, user);
|
|
switch (ret) {
|
|
case 0:
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
break;
|
|
case -2:
|
|
return ConnWriteStr(client->conn, MSG4225BADVERSION);
|
|
case -1:
|
|
default:
|
|
return ConnWriteStr(client->conn, MSG4224BADSTORE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 != MsgAuthFindUser(user)) {
|
|
Log(LOG_INFO, "Couldn't find user object for %s\r\n", user);
|
|
/* the previous nmap returned some sort of user locked message? */
|
|
return ConnWriteStr(client->conn, MSG4100STORENOTFOUND);
|
|
}
|
|
|
|
if (SelectStore(client, user)) {
|
|
return ConnWriteStr(client->conn, MSG4224BADSTORE);
|
|
} else {
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
}
|
|
|
|
CCode
|
|
StoreCommandTIMEOUT(StoreClient *client, int lockTimeoutMs)
|
|
{
|
|
// should be obsolete really
|
|
if (client || lockTimeoutMs) {
|
|
// FIXME : unused parameters
|
|
}
|
|
return ConnWriteStr(client->conn, MSG3000UNKNOWN);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandTOKEN(StoreClient *client, char *token)
|
|
{
|
|
StoreToken *tok;
|
|
|
|
tok = MemMalloc(sizeof(StoreToken));
|
|
|
|
strncpy(tok->data, token, sizeof(tok->data));
|
|
tok->data[sizeof(tok->data)-1] = 0;
|
|
|
|
tok->next = client->principal.tokens;
|
|
client->principal.tokens = tok;
|
|
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
|
|
CCode
|
|
StoreCommandTOKENS(StoreClient *client)
|
|
{
|
|
StoreToken *tok;
|
|
CCode ccode = 0;
|
|
|
|
for (tok = client->principal.tokens;
|
|
tok && ccode >= 0;
|
|
tok = tok->next)
|
|
{
|
|
ccode = ConnWriteF(client->conn, "2001 %s\r\n", tok->data);
|
|
}
|
|
|
|
if (ccode >= 0) {
|
|
ccode = ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
return ccode;
|
|
}
|
|
|
|
CCode
|
|
StoreCommandUSER(StoreClient *client, char *user, char *password, int nouser)
|
|
{
|
|
if (user) {
|
|
return SelectUser(client, user, password, nouser);
|
|
} else {
|
|
UnselectUser(client);
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
}
|
|
|
|
// Set the watched flags for the specified collection. If we were watching
|
|
// something else before, stop watching it now.
|
|
|
|
CCode
|
|
StoreCommandWATCH(StoreClient *client, StoreObject *collection, int flags)
|
|
{
|
|
CCode ccode;
|
|
|
|
// stop watching whatever we were watching before
|
|
if (client->watch.collection) {
|
|
if (StoreWatcherRemove(client, client->watch.collection))
|
|
return ConnWriteStr(client->conn, MSG5004INTERNALERR);
|
|
|
|
client->watch.collection = 0;
|
|
client->watch.flags = 0;
|
|
}
|
|
|
|
// watch the new collection
|
|
if (flags) {
|
|
flags |= STORE_WATCH_EVENT_COLL_REMOVED;
|
|
flags |= STORE_WATCH_EVENT_COLL_RENAMED;
|
|
|
|
ccode = StoreObjectCheckAuthorization(client, collection, STORE_PRIV_READ);
|
|
if (ccode) {
|
|
return ccode;
|
|
}
|
|
|
|
if (StoreWatcherAdd(client, collection->guid))
|
|
return ConnWriteStr(client->conn, MSG5004INTERNALERR);
|
|
|
|
client->watch.collection = collection->guid;
|
|
client->watch.flags = flags;
|
|
}
|
|
return ConnWriteStr(client->conn, MSG1000OK);
|
|
}
|
|
|
|
// FIXME: do something with the link document
|
|
CCode
|
|
StoreCommandWRITE(StoreClient *client,
|
|
StoreObject *collection,
|
|
int doctype,
|
|
uint64_t size,
|
|
uint32_t addflags,
|
|
uint64_t guid,
|
|
const char *filename,
|
|
uint64_t timestamp,
|
|
uint64_t guid_link,
|
|
int no_process)
|
|
{
|
|
CCode ccode;
|
|
StoreObject newdocument;
|
|
uint64_t tmpsize;
|
|
char path[XPL_MAX_PATH+1];
|
|
char tmppath[XPL_MAX_PATH+1];
|
|
|
|
if (guid_link) {
|
|
// FIXME : should use this parameter to link to another doc
|
|
}
|
|
|
|
CHECK_NOT_READONLY(client)
|
|
|
|
if (STORE_IS_FOLDER(doctype) || STORE_IS_CONVERSATION(doctype)) {
|
|
return ConnWriteStr(client->conn, MSG3015BADDOCTYPE);
|
|
}
|
|
|
|
memset(&newdocument, 0, sizeof(StoreObject));
|
|
|
|
// try to create new object
|
|
newdocument.guid = guid;
|
|
newdocument.type = doctype;
|
|
|
|
if (StoreObjectCreate(client, &newdocument))
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
|
|
// read content to temporary file
|
|
MaildirTempDocument(client, collection->guid, tmppath, sizeof(tmppath));
|
|
|
|
tmpsize = size;
|
|
ccode = ReceiveToFile(client, tmppath, &tmpsize);
|
|
if (ccode) goto finish;
|
|
|
|
// update new object metadata
|
|
newdocument.collection_guid = collection->guid;
|
|
newdocument.size = tmpsize;
|
|
newdocument.time_created = timestamp;
|
|
newdocument.time_modified = timestamp;
|
|
newdocument.flags = addflags;
|
|
if (filename) strncpy(newdocument.filename, filename, MAX_FILE_NAME);
|
|
|
|
StoreObjectFixUpFilename(collection, &newdocument);
|
|
StoreObjectUpdateImapUID(client, &newdocument);
|
|
if (no_process == 0) {
|
|
// update metadata if we haven't been asked not to
|
|
StoreProcessDocument(client, &newdocument, tmppath);
|
|
}
|
|
|
|
// put content in place
|
|
FindPathToDocument(client, collection->guid, newdocument.guid, path, sizeof(path));
|
|
link(tmppath, path);
|
|
unlink(tmppath);
|
|
|
|
// save new object
|
|
if (StoreObjectSave(client, &newdocument)) {
|
|
StoreObjectRemove(client, &newdocument);
|
|
return ConnWriteStr(client->conn, MSG5005DBLIBERR);
|
|
}
|
|
|
|
// announce its creation
|
|
++client->stats.insertions;
|
|
StoreWatcherEvent(client, &newdocument, STORE_WATCH_EVENT_NEW);
|
|
|
|
return ConnWriteF(client->conn, "1000 " GUID_FMT " %d\r\n", newdocument.guid, newdocument.version);
|
|
|
|
finish:
|
|
return ccode;
|
|
}
|