/* Copyright (C) 2000-2003 Max Berger This file is part of libsyncml, written for the Opengroupware project (OGo) OGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. OGo 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // $Id$ //#import "common.h" #include #include #include "SyncML.h" #include "SyncMLSessionState.h" #include "SyncMLSessionStatesStore.h" #include "SyncMLTag.h" #include "SyncMLTagBehavior.h" #include "SyncMLTagSmarts.h" //#define DEBUG_MODEL static BOOL doDebug = NO; static BOOL logStream = NO; @implementation SyncML + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; static BOOL didInit = NO; if (didInit) return; didInit = YES; doDebug = [ud boolForKey:@"OGoDebugSyncMLConnection"]; logStream = [ud boolForKey:@"OGoDebugSyncMLConnectionLogStream"]; } + (void) cleanup { [SyncMLSessionStatesStore deleteSyncMLSessionStatesStore]; } - (id)initWithSessionHandler:(id)_handler; { [SyncML initialize]; self = [super init]; if ((self = [super init])) { self->sessionHandler = NULL; ASSIGN(self->sessionHandler,_handler); [sessionHandler setSyncml:self]; state = NULL; cmdIdCounter = 0; } return self; } - (void) dealloc { [sessionHandler release]; [super dealloc]; } - (NSString*)_getDBAnchor:(NSString *)anchor forDB:(NSString*)dbName { return [[[[state deviceInformation]objectForKey:dbName] objectForKey:@"Anchors"]objectForKey:anchor]; } - (void)_setDBAnchor:(NSString *)anchor forDB:(NSString*)dbName to:(NSString *)_new { NSMutableDictionary *deviceInformation = [state deviceInformation]; NSMutableDictionary *devanchors = [[NSMutableDictionary alloc]init]; NSMutableDictionary *dbAnchors = [[NSMutableDictionary alloc]init]; [devanchors addEntriesFromDictionary: [deviceInformation objectForKey:dbName]]; [dbAnchors addEntriesFromDictionary:[devanchors objectForKey:@"Anchors"]]; [dbAnchors setObject:_new forKey:anchor]; [devanchors setObject:dbAnchors forKey:@"Anchors"]; [dbAnchors release]; [deviceInformation setObject:devanchors forKey:dbName]; [devanchors release]; } - (void)_addChalTo:(SyncMLTagStatus*)_status { SyncMLTagChal *chal = [[SyncMLTagChal alloc]init]; SyncMLTagMeta *meta = [[SyncMLTagMeta alloc]init]; [meta setType:[sessionHandler requireAuthentication]]; [meta setFormat:SYNCML_FORMAT_BASE64]; [chal setMeta:meta]; [meta release]; [_status setChal:chal]; [chal release]; } - (void)_loadDeviceInformationIfNotLoaded { if ([state deviceInformation]==NULL) { [state setDeviceInformation: [sessionHandler loadDeviceInformationFor:[state remoteLocation]]]; [[state deviceInformation] setObject:[state remoteLocation] forKey:@"deviceName"]; } } - (int)_checkHeaderFromRequest:(SyncMLTagSyncHdr*)_hdr { /* TODO: Check for correct header, version, authentication, ... */ SyncMLTagCred *cred = [_hdr cred]; NSString *authType; NSString *decoded_data; NSArray *components; state = [[[SyncMLSessionStatesStore getSyncMLSessionStatesStore] getStateForID:[_hdr sessionid]] retain]; if (doDebug) NSLog(@"Loaded State for Session: %@",[state sessionID]); // FIXME: Check if [[_hdr target] locuri] == our local location [state setRemoteLocation:[[_hdr source] locuri]]; [self _loadDeviceInformationIfNotLoaded]; if ((![state otherIsAuthenticated]) && (authType=[sessionHandler requireAuthentication])) { if (([cred data]==NULL)||([[cred meta]type]==NULL)) { return SYNCML_MISSING_CREDENTIALS; } if ([[[cred meta]type]caseInsensitiveCompare:authType]!=NSOrderedSame) { return SYNCML_INVALID_CREDENTIALS; } if ([[[cred meta]format]caseInsensitiveCompare:SYNCML_FORMAT_BASE64] != NSOrderedSame) { return SYNCML_INVALID_CREDENTIALS; } decoded_data = [[[cred data]stringdata]stringByDecodingBase64]; components = [decoded_data componentsSeparatedByString:@":"]; if ([sessionHandler authenticateAs:[components objectAtIndex:0] withPass:[components objectAtIndex:1]]) { [state setOtherIsAuthenticated:YES]; return SYNCML_AUTHENTICATION_ACCEPTED; } else { return SYNCML_FORBIDDEN; } } return SYNCML_OK; } - (SyncMLTagSyncML*)_createEmptyReturnMessage { SyncMLTagSyncML *root = [[SyncMLTagSyncML alloc]init]; SyncMLTagSyncHdr *hdr = [[SyncMLTagSyncHdr alloc]init]; SyncMLTagSyncBody *body = [[SyncMLTagSyncBody alloc]init]; SyncMLTagSource *source = [[SyncMLTagSource alloc]init]; SyncMLTagTarget *target = [[SyncMLTagTarget alloc]init]; [hdr setVerdtd:@"1.1"]; [hdr setVerproto:@"SyncML/1.1"]; [hdr setSessionid:[state sessionID]]; [hdr setMsgid:[state messageID]]; [target setLocuri:[self->state remoteLocation]]; [hdr setTarget:target]; [target release]; [source setLocuri:[self->sessionHandler deviceIdentification]]; [hdr setSource:source]; [source release]; [root setSynchdr:hdr]; [hdr release]; [root setSyncbody:body]; [body release]; [state addOpenReq:root cmdID:@"0" msgID:[state messageID]]; return [root autorelease]; } - (NSString*) _nextCmdid { cmdIdCounter++; return [NSString stringWithFormat:@"%u",cmdIdCounter]; } - (void)_addCmd:(id)_command toMessage:(SyncMLTagSyncML*)_root reqStatus:(BOOL)_req { SyncMLTagSyncBody* body = [_root syncbody]; [_command setCmdid:[self _nextCmdid]]; [body setCommand:_command]; if (_req) [state addOpenReq:_command cmdID:[_command cmdid] msgID:[state messageID]]; } - (void) addAlert:(int)_alertNr to:(SyncMLTagSyncML*)_root withDatabase:(id)_db forTarget:(NSString *)_remoteLocation { SyncMLTagAlert *alert; SyncMLTagItem *item; SyncMLTagTarget* target; SyncMLTagSource* source; SyncMLTagMeta* meta; SyncMLTagAnchor *anchor; SyncMLTagData *data; NSString *nextSync = [SyncML extendedTimeStamp]; [state setSync:_alertNr forDB:[_db databaseLocation]]; alert = [[SyncMLTagAlert alloc]init]; data = [[SyncMLTagData alloc]init]; [data setStringdata:[NSString stringWithFormat:@"%u",_alertNr]]; [alert setData:data]; [data release]; item = [[SyncMLTagItemWithSmartEncode alloc]init]; target = [[SyncMLTagTarget alloc]init]; [target setLocuri:_remoteLocation]; [item setTarget:target]; [target release]; source = [[SyncMLTagSource alloc]init]; [source setLocuri:[_db databaseLocation]]; [source setLocname:[_db databaseDescription]]; [item setSource:source]; [source release]; meta = [[SyncMLTagMeta alloc]init]; anchor = [[SyncMLTagAnchor alloc]init]; [anchor setLast:[self _getDBAnchor:@"myLast" forDB:[_db databaseLocation]]]; [anchor setNext:nextSync]; [self _setDBAnchor:@"myNext" forDB:[_db databaseLocation] to:nextSync]; [meta setAnchor:anchor]; [anchor release]; [item setMeta:meta]; [meta release]; [alert setItem:item]; [item release]; [self _addCmd:alert toMessage:_root reqStatus:YES]; [alert release]; } - (void)_addInitialAlertsTo:(SyncMLTagSyncML *)root { id db; int i; if (![state sendInitialAlerts]) return; if ([sessionHandler server]) return; for (i=0;i<[sessionHandler databaseCount];i++) { db = [sessionHandler databaseForIndex:i]; if ([db syncAlert]!=SYNCML_SYNC_ALERT_NO) { [self addAlert:[db syncAlert] to:root withDatabase:db forTarget:[db syncWithLocation]]; } } [state setSendInitialAlerts:NO]; } - (void)_addCredentials:(SyncMLTagSyncHdr *)header { SyncMLTagCred *cred; SyncMLTagMeta *meta; SyncMLTagData* data; if ([state authenticationData]==NULL) return; if ([state iAmAuthenticated]) return; cred = [[SyncMLTagCred alloc]init]; meta = [[SyncMLTagMeta alloc]init]; [meta setType:[state authenticationType]]; [meta setFormat:[state authenticationFormat]]; data = [[SyncMLTagData alloc]init]; [data setStringdata: [state authenticationData]]; [cred setData:data]; [data release]; [cred setMeta:meta]; [meta release]; [header setCred:cred]; [cred release]; } - (void)_saveDeviceInformation { NSString *devName = [[state deviceInformation]objectForKey:@"deviceName"]; [[state deviceInformation]removeObjectForKey:@"deviceName"]; [sessionHandler saveDeviceInformation:[state deviceInformation] for:devName]; [[state deviceInformation]setObject:devName forKey:@"deviceName"]; } - (void)_processMappings { NSString *db; BOOL save = NO; while ((db=[[[state activeMappingDBs]anyObject] retain])) { // This DB has finished syncing! [self _setDBAnchor:@"hisLast" forDB:db to:[self _getDBAnchor:@"hisNext" forDB:db]]; [self _setDBAnchor:@"myLast" forDB:db to:[self _getDBAnchor:@"myNext" forDB:db]]; [self _setDBAnchor:@"hisNext" forDB:db to:@""]; [self _setDBAnchor:@"myNext" forDB:db to:@""]; save = YES; [[state activeMappingDBs]removeObject:db]; [db release]; } if (save) { [self _saveDeviceInformation]; } [state makeMappingsActive]; } - (SyncMLTagStatus*)addStatus:(int)statusCode msgRef:(NSString *)msgRef cmdRef:(NSString*)cmdRef cmd:(NSString *)cmd toMsg:(SyncMLTagSyncML*)root { SyncMLTagStatus* status = [[SyncMLTagStatus alloc]init]; SyncMLTagData* data = [[SyncMLTagData alloc]init]; [status setMsgref:msgRef]; [status setCmdref:cmdRef]; [status setCmd:cmd]; [data setStringdata:([NSString stringWithFormat:@"%u",statusCode])]; [status setData:data]; [data release]; [self _addCmd:status toMessage:root reqStatus:NO]; return [status autorelease]; } - (void)processCommandsFrom:(id)node forMsgRef:(NSString *)msgRef forMsg:(SyncMLTagSyncML*)msg { NSEnumerator* enumerator; id anObject; enumerator = [[node commands]objectEnumerator]; while ((anObject = [enumerator nextObject])) { if ([anObject conformsToProtocol:@protocol(SyncMLCommand)]) [anObject processCommand:self forMsgRef:msgRef forMsg:msg inContext:node]; } } - (void) _processDeferredCommandsForMsg:(SyncMLTagSyncML*)msg { NSArray *deferred = nil; while ((deferred = [self->state nextSyncLater])) { [self addSyncTarget:[deferred objectAtIndex:0] source:[deferred objectAtIndex:1] toMsg:msg]; } } - (NSData*)processSyncMessage:(NSData *)message messageForInvalid:(BOOL)invalid; { int headerReturnCode; SaxObjectDecoder *sax; id parser; SyncMLTagSyncML* rootnode; SyncMLTagSyncML* returnroot; SyncMLTagStatus *status; BOOL returnData = YES; #ifdef DEBUG_MODEL SaxObjectModel *model; #endif cmdIdCounter = 0; parser = [[SaxXMLReaderFactory standardXMLReaderFactory] createXMLReaderForMimeType:@"text/xml"]; #ifdef DEBUG_MODEL model = [SaxObjectModel modelWithName:@"SyncMLSaxMap"]; if (doDebug) NSLog(@"sax model: %@",model); sax = [[SaxObjectDecoder alloc] initWithMappingModel:model]; #else sax = [[SaxObjectDecoder alloc] initWithMappingNamed:@"SyncMLSaxMap"]; #endif [parser setContentHandler:sax]; [parser setErrorHandler:sax]; [parser parseFromSource:message]; if (doDebug) NSLog(@"sax: %@",sax); rootnode = [sax rootObject]; if (logStream) { NSLog(@"parsed object: %@", rootnode); if (rootnode == NULL) NSLog(@"null!"); } if ([rootnode synchdr] == NULL) headerReturnCode = SYNCML_INVALID_REQUEST; else headerReturnCode = [self _checkHeaderFromRequest:[rootnode synchdr]]; if ([rootnode syncbody] == NULL) headerReturnCode = SYNCML_INVALID_REQUEST; if (doDebug) NSLog(@"Header check done"); returnroot = [self _createEmptyReturnMessage]; status = [self addStatus:headerReturnCode msgRef:[[rootnode synchdr] msgid] cmdRef:@"0" cmd:@"SyncHdr" toMsg:returnroot]; if (headerReturnCode != SYNCML_INVALID_REQUEST) { [status setTargetref:[sessionHandler deviceIdentification]]; [status setSourceref:[state remoteLocation]]; if ((headerReturnCode == SYNCML_MISSING_CREDENTIALS)|| (headerReturnCode == SYNCML_INVALID_CREDENTIALS)) [self _addChalTo:status]; [self processCommandsFrom:[rootnode syncbody] forMsgRef:[[rootnode synchdr] msgid] forMsg:returnroot ]; if ([[rootnode syncbody]final]) { [self _processDeferredCommandsForMsg:returnroot]; [self _processMappings]; } [self _addCredentials:[returnroot synchdr]]; [self _addInitialAlertsTo:returnroot]; } [[returnroot syncbody] setFinal:NULL]; [sax release]; if ([returnroot syncbody]==NULL) returnData = NO; // Do not send if you don't have anything to send except the status // command for the header! else if (([[[returnroot syncbody]commands] count]<=1) && (![sessionHandler server])) { if ((headerReturnCode!=SYNCML_INVALID_REQUEST) || (invalid==NO)) returnData = NO; } if (returnData == NO) { [state closeReqCmdID:@"0" msgID:[state messageID]]; if (logStream) NSLog(@"No SyncML Message returned"); [state release]; state = NULL; return NULL; } [state increaseMessageID]; if (logStream) NSLog(@"Sending data: %@",[returnroot description]); [state release]; state = NULL; return [[returnroot description] dataUsingEncoding:NSUTF8StringEncoding]; } - (NSData*)createInitialSyncPackageTo:(NSString *)target { SyncMLTagSyncML* returnMessage; cmdIdCounter = 0; self->state = [[SyncMLSessionStatesStore getSyncMLSessionStatesStore] newState]; [self->state setRemoteLocation:target]; [self _loadDeviceInformationIfNotLoaded]; returnMessage= [self _createEmptyReturnMessage]; [self _addInitialAlertsTo:returnMessage]; [self->state increaseMessageID]; self->state = NULL; return [[returnMessage description] dataUsingEncoding:NSUTF8StringEncoding]; } - (id ) sessionHandler { return sessionHandler; } - (SyncMLSessionState *)state { return state; } - (id ) databaseHandlerForURI:(NSString *)uri { id db = NULL; id curdb = NULL; int i; int longmatch = 0; int len = 0; for (i=0;i<[sessionHandler databaseCount];i++) { curdb = [sessionHandler databaseForIndex:i]; if ([uri hasPrefix:[curdb databaseLocation]]) { len = [[curdb databaseLocation]length]; if (len > longmatch) { longmatch = len; db = curdb; } } } return db; } - (void) _addAdd:(id)uid to:(id)parent database:(id)db { SyncMLTagAdd *add = [[SyncMLTagAdd alloc]init]; SyncMLTagMeta *meta = [[SyncMLTagMeta alloc]init]; SyncMLTagItem *item = [[SyncMLTagItemWithSmartEncode alloc]init]; SyncMLTagSource *source = [[SyncMLTagSource alloc]init]; SyncMLTagData *data = [[SyncMLTagData alloc]init]; [add setCmdid:[self _nextCmdid]]; [state addOpenReq:add cmdID:[add cmdid] msgID:[state messageID]]; [state setExtraInfo:db forCmdID:[add cmdid] msgID:[state messageID]]; // FIXME: Fill in Meta Information [add setMeta:meta]; [meta release]; // FIXME: Might not be the right uri [source setLocuri:[uid description]]; [item setSource:source]; [source release]; [data setStringdata:[db representationForUID:uid]]; [item setData:data]; [data release]; [add setItem:item]; [item release]; [parent setAdd:add]; [add release]; } - (void) addSyncLaterForTarget:(NSString*)targetdb source:(NSString *)sourcedb { [self->state addSyncLaterTarget:targetdb source:sourcedb]; } - (void) saveSynched:(NSString *)what forUID:(id)UID inDB:(id)db to:(NSString *)_to { NSMutableDictionary *dbs; NSMutableDictionary *synched; NSMutableDictionary *synchedVersions; dbs = [[NSMutableDictionary alloc]init]; synched = [[NSMutableDictionary alloc]init]; synchedVersions = [[NSMutableDictionary alloc]init]; [dbs addEntriesFromDictionary: [[state deviceInformation]objectForKey:[db databaseLocation]]]; [synched addEntriesFromDictionary:[dbs objectForKey:@"Entries"]]; [synchedVersions addEntriesFromDictionary:[synched objectForKey:UID]]; [synchedVersions setObject:_to forKey:what]; [synched setObject:synchedVersions forKey:UID]; [dbs setObject:synched forKey:@"Entries"]; [[state deviceInformation]setObject:dbs forKey:[db databaseLocation]]; [synched release]; [synchedVersions release]; [dbs release]; } - (void) forgetSynchedWithDB:(id)db { NSMutableDictionary *dbs; dbs = [[NSMutableDictionary alloc]init]; [dbs addEntriesFromDictionary: [[state deviceInformation]objectForKey:[db databaseLocation]]]; [dbs removeObjectForKey:@"Entries"]; [[state deviceInformation]setObject:dbs forKey:[db databaseLocation]]; [dbs release]; } - (void) addSyncTarget:(NSString*)targetdb source:(NSString *)sourcedb toMsg:(SyncMLTagSyncML*)msg; { // Make sure we should actually send stuff NSEnumerator *enumerator; id object; id db; SyncMLTagSync *sync = [[SyncMLTagSync alloc]init]; SyncMLTagTarget *target = [[SyncMLTagTarget alloc]init]; SyncMLTagSource *source = [[SyncMLTagSource alloc]init]; BOOL slow = NO; [target setLocuri:targetdb]; [source setLocuri:sourcedb]; [sync setTarget:target]; [target release]; [sync setSource:source]; [source release]; db = [self databaseHandlerForURI:sourcedb]; // FIXME: Add meta/mem!!! // This sends out empty sync packages. This might required for servers to // detect one-way-syncs [self _addCmd:sync toMessage:msg reqStatus:YES]; if ( ((([state syncForDB:[db databaseLocation]] == SYNCML_SYNC_ALERT_ONE_WAY_FROM_CLIENT) || ([state syncForDB:[db databaseLocation]] == SYNCML_SYNC_ALERT_REFRESH_FROM_CLIENT))&& ([self->sessionHandler server])) || ((([state syncForDB:[db databaseLocation]] == SYNCML_SYNC_ALERT_ONE_WAY_FROM_SERVER) || ([state syncForDB:[db databaseLocation]] == SYNCML_SYNC_ALERT_REFRESH_FROM_SERVER)) && (![self->sessionHandler server])) ) return; slow = ([state syncForDB:[db databaseLocation]] == SYNCML_SYNC_ALERT_SLOW_SYNC) || ([state syncForDB:[db databaseLocation]] == SYNCML_SYNC_ALERT_REFRESH_FROM_CLIENT) || ([state syncForDB:[db databaseLocation]] == SYNCML_SYNC_ALERT_REFRESH_FROM_SERVER); if (slow) { enumerator= [[db allUIDs]objectEnumerator]; while ((object=[enumerator nextObject])!=NULL) { [self _addAdd:object to:sync database:db]; } } else { // FIXME!!! enumerator= [[db allUIDs]objectEnumerator]; while ((object=[enumerator nextObject])!=NULL) { } } [sync release]; } - (void)setBackendData:(id)data forKey:(id)key { [state setBackendData:data forKey:key]; } - (id)backendDataForKey:(id)key { return [state backendDataForKey:key]; } - (BOOL)compareAnchors:(SyncMLTagAnchor*)anchors forDB:(id)db { BOOL fallback = NO; if (![[anchors last] isEqual:[self _getDBAnchor:@"hisLast" forDB:[db databaseLocation]]]) { // Anchors mismatch fallback = YES; } if (![[self _getDBAnchor:@"hisNext" forDB:[db databaseLocation]] isEqual:@""]) { // Incomplete previous sync fallback = YES; } [self _setDBAnchor:@"hisNext" forDB:[db databaseLocation] to:[anchors next]]; [self _saveDeviceInformation]; return fallback; } + (NSString*) extendedTimeStamp { // Fixme: also add somekind of a random number return [NSString stringWithFormat:@"%@",[NSDate date]]; } - (void)addMapToMsg:(SyncMLTagSyncML*)_msg target:(NSString *)_target source:(NSString *)_source targetDB:(NSString *)_targetDB sourceDB:(NSString *)_sourceDB { SyncMLTagSyncBody* body = [_msg syncbody]; NSArray* commands = [body commands]; NSEnumerator *enumerator = [commands objectEnumerator]; id command = NULL; SyncMLTagMap *map = NULL; SyncMLTagMapItem *mapItem; SyncMLTagSource *source; SyncMLTagTarget *target; while ((command = [enumerator nextObject])) { if ([command respondsToSelector:@selector(setMapitem:)]) { map = command; if ( [[[map source]locuri]isEqual:_sourceDB] && [[[map target]locuri]isEqual:_targetDB]) break; map = NULL; } } if (map == NULL) { map = [[SyncMLTagMap alloc]init]; [map autorelease]; [self _addCmd:map toMessage:_msg reqStatus:YES]; target = [[SyncMLTagTarget alloc]init]; [target setLocuri:_targetDB]; [map setTarget:target]; [target release]; source = [[SyncMLTagSource alloc]init]; [source setLocuri:_sourceDB]; [map setSource:source]; [source release]; } mapItem = [[SyncMLTagMapItem alloc]init]; target = [[SyncMLTagTarget alloc]init]; [target setLocuri:_target]; [mapItem setTarget:target]; source = [[SyncMLTagSource alloc]init]; [source setLocuri:_source]; [mapItem setSource:source]; [source release]; [map setMapitem:mapItem]; [mapItem release]; } @end