Example #1
0
/*
	Adds the authentication token to the user cache.  The timeout for the
	auth token is based on the type of login as well as (if type=='opac')
	the org location id.
	Returns the event that should be returned to the user.
	Event must be freed
*/
static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
		const char* type, int orgloc, const char* workstation ) {

	oilsEvent* response;

	long timeout;
	char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
	if(wsorg) { /* if there is a workstation, use it for the timeout */
		osrfLogDebug( OSRF_LOG_MARK,
				"Auth session trying workstation id %d for auth timeout", atoi(wsorg));
		timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
		free(wsorg);
	} else {
		osrfLogDebug( OSRF_LOG_MARK,
				"Auth session trying org from param [%d] for auth timeout", orgloc );
		timeout = oilsAuthGetTimeout( userObj, type, orgloc );
	}
	osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %ld", uname, timeout );

	char* string = va_list_to_string(
			"%d.%ld.%s", (long) getpid(), time(NULL), uname );
	char* authToken = md5sum(string);
	char* authKey = va_list_to_string(
			"%s%s", OILS_AUTH_CACHE_PRFX, authToken );

	const char* ws = (workstation) ? workstation : "";
	osrfLogActivity(OSRF_LOG_MARK,
		"successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );

	oilsFMSetString( userObj, "passwd", "" );
	jsonObject* cacheObj = jsonParseFmt( "{\"authtime\": %ld}", timeout );
	jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));

	if( !strcmp( type, OILS_AUTH_PERSIST )) {
		// Add entries for endtime and reset_interval, so that we can gracefully
		// extend the session a bit if the user is active toward the end of the 
		// timeout originally specified.
		time_t endtime = time( NULL ) + timeout;
		jsonObjectSetKey( cacheObj, "endtime", jsonNewNumberObject( (double) endtime ) );

		// Reset interval is hard-coded for now, but if we ever want to make it
		// configurable, this is the place to do it:
		jsonObjectSetKey( cacheObj, "reset_interval",
			jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL ));
	}

	osrfCachePutObject( authKey, cacheObj, (time_t) timeout );
	jsonObjectFree(cacheObj);
	osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
	jsonObject* payload = jsonParseFmt(
		"{ \"authtoken\": \"%s\", \"authtime\": %ld }", authToken, timeout );

	response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
	free(string); free(authToken); free(authKey);
	jsonObjectFree(payload);

	return response;
}
/**
 * Determines the correct recipient address based on the requested 
 * service or recipient address.  
 */
static int osrfHttpTranslatorSetTo(osrfHttpTranslator* trans) {
    int stat = 0;
    jsonObject* sessionCache = NULL;

    if(trans->service) {
        if(trans->recipient) {
            osrfLogError(OSRF_LOG_MARK, "Specifying both SERVICE and TO are not allowed");

        } else {
            // service is specified, build a recipient address 
            // from the router, domain, and service
            int size = snprintf(recipientBuf, 128, "%s@%s/%s", routerName,
                domainName, trans->service);
            recipientBuf[size] = '\0';
            osrfLogDebug(OSRF_LOG_MARK, "Set recipient to %s", recipientBuf);
            trans->recipient = recipientBuf;
            stat = 1;
        }

    } else {

        if(trans->recipient) {
            sessionCache = osrfCacheGetObject(trans->thread);

            if(sessionCache) {
                const char* ipAddr = jsonObjectGetString(
                    jsonObjectGetKeyConst( sessionCache, "ip" ));
                const char* recipient = jsonObjectGetString(
                    jsonObjectGetKeyConst( sessionCache, "jid" ));

                // choosing a specific recipient address requires that the recipient and 
                // thread be cached on the server (so drone processes cannot be hijacked)
                if(!strcmp(ipAddr, trans->remoteHost) && !strcmp(recipient, trans->recipient)) {
                    osrfLogDebug( OSRF_LOG_MARK,
                        "Found cached session from host %s and recipient %s",
                        trans->remoteHost, trans->recipient);
                    stat = 1;
                    trans->service = apr_pstrdup(
                        trans->apreq->pool, jsonObjectGetString(
                            jsonObjectGetKeyConst( sessionCache, "service" )));

                } else {
                    osrfLogError(OSRF_LOG_MARK, 
                        "Session cache for thread %s does not match request", trans->thread);
                }
            }  else {
                osrfLogError(OSRF_LOG_MARK, 
                    "attempt to send directly to %s without a session", trans->recipient);
            }
        } else {
            osrfLogError(OSRF_LOG_MARK, "No SERVICE or RECIPIENT defined");
        } 
    }

    jsonObjectFree(sessionCache);
    return stat;
}
Example #3
0
/**
	@brief Register an extended method for a specified application.

	@param appName Name of the application that implements the method.
	@param methodName The fully qualified name of the method.
	@param symbolName The symbol name (function name) that implements the method.
	@param notes Public documentation for this method.
	@param argc How many arguments this method expects.
	@param options Bit switches setting various options.
	@param user_data Opaque pointer to be passed to the dynamically called function.
	@return Zero if successful, or -1 upon error.

	This function is identical to osrfAppRegisterMethod(), except that it also installs
	a method-specific opaque pointer.  When we call the corresponding function at
	run time, this pointer will be available to the function via the method context.
*/
int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
	const char* symbolName, const char* notes, int argc, int options, void * user_data ) {

	if( !appName || ! methodName ) return -1;

	osrfApplication* app = _osrfAppFindApplication(appName);
	if(!app) {
		osrfLogWarning( OSRF_LOG_MARK, "Unable to locate application %s", appName );
		return -1;
	}

	osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );

	// Extract the only valid option bits, and ignore the rest.
	int opts = options & ( OSRF_METHOD_STREAMING | OSRF_METHOD_CACHABLE );

	// Build and install a non-atomic method.
	register_method(
		app, methodName, symbolName, notes, argc, opts, user_data );

	if( opts & OSRF_METHOD_STREAMING ) {
		// Build and install an atomic version of the same method.
		register_method(
			app, methodName, symbolName, notes, argc, opts | OSRF_METHOD_ATOMIC, user_data );
	}

	return 0;
}
/**
	@brief Acting as a server, process an incoming osrfMessage.
	@param session Pointer to the osrfAppSession to which the message pertains.
	@param msg Pointer to the osrfMessage.

	Branch on the message type.  In particular, if it's a REQUEST, call the requested method.
*/
static void _do_server( osrfAppSession* session, osrfMessage* msg ) {

	if(session == NULL || msg == NULL) return;

	osrfLogDebug( OSRF_LOG_MARK, "Server received message of type %d", msg->m_type );

        osrf_app_session_set_tz(session, msg->sender_tz);
        osrf_app_session_set_locale(session, msg->sender_locale);

	osrfLogDebug( OSRF_LOG_MARK, "Message has locale %s and tz %s", session->session_locale, session->session_tz );

	switch( msg->m_type ) {

		case STATUS:
			break;

		case DISCONNECT:
			/* session will be freed by the forker */
			osrfLogDebug(OSRF_LOG_MARK, "Client sent explicit disconnect");
			session->state = OSRF_SESSION_DISCONNECTED;
			break;

		case CONNECT:
			osrfAppSessionStatus( session, OSRF_STATUS_OK,
					"osrfConnectStatus", msg->thread_trace, "Connection Successful" );
			session->state = OSRF_SESSION_CONNECTED;
			break;

		case REQUEST:
			osrfLogDebug( OSRF_LOG_MARK, "server passing message %d to application handler "
					"for session %s", msg->thread_trace, session->session_id );

			osrfAppRunMethod( session->remote_service, msg->method_name,
				session, msg->thread_trace, msg->_params );

			break;

		default:
			osrfLogWarning( OSRF_LOG_MARK,
					"Server cannot handle message of type %d", msg->m_type );
			session->state = OSRF_SESSION_DISCONNECTED;
			break;
	}

	osrfMessageFree(msg);
	return;
}
Example #5
0
int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
	OSRF_METHOD_VERIFY_CONTEXT(ctx);
    bool returnFull = false;

	const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));

    if(ctx->params->size > 1) {
        // caller wants full cached object, with authtime, etc.
        const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
        if(rt && strcmp(rt, "0") != 0) 
            returnFull = true;
    }

	jsonObject* cacheObj = NULL;
	oilsEvent* evt = NULL;

	if( authToken ){

		// Reset the timeout to keep the session alive
		evt = _oilsAuthResetTimeout(authToken, 0);

		if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
			osrfAppRespondComplete( ctx, oilsEventToJSON( evt ));    // can't reset timeout

		} else {

			// Retrieve the cached session object
			osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
			char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
			cacheObj = osrfCacheGetObject( key );
			if(cacheObj) {
				// Return a copy of the cached user object
                if(returnFull)
				    osrfAppRespondComplete( ctx, cacheObj);
                else
				    osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
				jsonObjectFree(cacheObj);
			} else {
				// Auth token is invalid or expired
				oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
				osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
				oilsEventFree(evt2);
			}
			free(key);
		}

	} else {

		// No session
		evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
		osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
	}

	if(evt)
		oilsEventFree(evt);

	return 0;
}
Example #6
0
osrfStringArray* apacheGetParamKeys(osrfStringArray* params) {
	if(params == NULL) return NULL;	
	osrfStringArray* sarray = osrfNewStringArray(12);
	int i;
	osrfLogDebug(OSRF_LOG_MARK, "Fetching URL param keys");
	for( i = 0; i < params->size; i++ ) 
		osrfStringArrayAdd(sarray, osrfStringArrayGetString(params, i++));
	return sarray;
}
Example #7
0
/**
	@brief Disconnect from the database.

	This function is called when the server drone is about to terminate.
*/
void osrfAppChildExit() {
	osrfLogDebug( OSRF_LOG_MARK, "Child is exiting, disconnecting from database..." );

	if ( dbhandle ) {
		dbi_conn_query( dbhandle, "ROLLBACK;" );
		dbi_conn_close( dbhandle );
		dbhandle = NULL;
	}
}
Example #8
0
/**
	Resets the auth login timeout
	@return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
*/
static oilsEvent*  _oilsAuthResetTimeout( const char* authToken ) {
	if(!authToken) return NULL;

	oilsEvent* evt = NULL;
	time_t timeout;

	osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
	char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
	jsonObject* cacheObj = osrfCacheGetObject( key );

	if(!cacheObj) {
		osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
		evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);

	} else {
		// Determine a new timeout value
		jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
		if( endtime_obj ) {
			// Extend the current endtime by a fixed amount
			time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
			int reset_interval = DEFAULT_RESET_INTERVAL;
			const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
				cacheObj, "reset_interval" );
			if( reset_interval_obj ) {
				reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
				if( reset_interval <= 0 )
					reset_interval = DEFAULT_RESET_INTERVAL;
			}

			time_t now = time( NULL );
			time_t new_endtime = now + reset_interval;
			if( new_endtime > endtime ) {
				// Keep the session alive a little longer
				jsonObjectSetNumber( endtime_obj, (double) new_endtime );
				timeout = reset_interval;
				osrfCachePutObject( key, cacheObj, timeout );
			} else {
				// The session isn't close to expiring, so don't reset anything.
				// Just report the time remaining.
				timeout = endtime - now;
			}
		} else {
			// Reapply the existing timeout from the current time
			timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
			osrfCachePutObject( key, cacheObj, timeout );
		}

		jsonObject* payload = jsonNewNumberObject( (double) timeout );
		evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
		jsonObjectFree(payload);
		jsonObjectFree(cacheObj);
	}

	free(key);
	return evt;
}
Example #9
0
/**
	@brief Given a username, fetch the corresponding row from the actor.usr table, if any.
	@param name The username for which to search.
	@return A Fieldmapper object for the relevant row in the actor.usr table, if it exists;
	or a JSON_NULL if it doesn't.

	The calling code is responsible for freeing the returned object by calling jsonObjectFree().
*/
jsonObject* oilsUtilsFetchUserByUsername( const char* name ) {
	if(!name) return NULL;
	jsonObject* params = jsonParseFmt("{\"usrname\":\"%s\"}", name);
	jsonObject* user = oilsUtilsQuickReq(
		"open-ils.cstore", "open-ils.cstore.direct.actor.user.search", params );

	jsonObjectFree(params);
	long id = oilsFMGetObjectId(user);
	osrfLogDebug(OSRF_LOG_MARK, "Fetched user %s:%ld", name, id);
	return user;
}
static int osrfHttpTranslatorCheckStatus(osrfHttpTranslator* trans, transport_message* msg) {
    osrfMessage* omsgList[MAX_MSGS_PER_PACKET];
    int numMsgs = osrf_message_deserialize(msg->body, omsgList, MAX_MSGS_PER_PACKET);
    osrfLogDebug(OSRF_LOG_MARK, "parsed %d response messages", numMsgs);
    if(numMsgs == 0) return 0;

    osrfMessage* last = omsgList[numMsgs-1];
    if(last->m_type == STATUS) {
        if(last->status_code == OSRF_STATUS_TIMEOUT) {
            osrfLogDebug(OSRF_LOG_MARK, "removing cached session on request timeout");
            osrfCacheRemove(trans->thread);
            return 0;
        }
        // XXX hm, check for explicit status=COMPLETE message instead??
        if(last->status_code != OSRF_STATUS_CONTINUE)
            trans->complete = 1;
    }

    return 1;
}
Example #11
0
/**
	@brief Wait on the client socket connected to Jabber, and process any resulting input.
	@param session Pointer to the transport_session.
	@param timeout How seconds to wait before timing out (see notes).
	@return 0 if successful, or -1 if a timeout or other error occurs, or if the server
		closes the connection at the other end.

	If @a timeout is -1, wait indefinitely for input activity to appear.  If @a timeout is
	zero, don't wait at all.  If @a timeout is positive, wait that number of seconds
	before timing out.  If @a timeout has a negative value other than -1, the results are not
	well defined.

	Read all available input from the socket and pass it through grab_incoming() (a
	callback function previously installed in the socket_manager).

	There is no guarantee that we will get a complete message from a single call.  As a
	result, the calling code should call this function in a loop until it gets a complete
	message, or until an error occurs.
*/
int session_wait( transport_session* session, int timeout ) {
	if( ! session || ! session->sock_mgr ) {
		return 0;
	}

	int ret =  socket_wait( session->sock_mgr, timeout, session->sock_id );

	if( ret ) {
		osrfLogDebug(OSRF_LOG_MARK, "socket_wait returned error code %d", ret);
	}
	return ret;
}
Example #12
0
/**
	@brief Verify the password received from the client.
	@param ctx The method context.
	@param userObj An object from the database, representing the user.
	@param password An obfuscated password received from the client.
	@return 1 if the password is valid; 0 if it isn't; or -1 upon error.

	(None of the so-called "passwords" used here are in plaintext.  All have been passed
	through at least one layer of hashing to obfuscate them.)

	Take the password from the user object.  Append it to the username seed from memcache,
	as stored previously by a call to the init method.  Take an md5 hash of the result.
	Then compare this hash to the password received from the client.

	In order for the two to match, other than by dumb luck, the client had to construct
	the password it passed in the same way.  That means it neded to know not only the
	original password (either hashed or plaintext), but also the seed.  The latter requirement
	means that the client process needs either to be the same process that called the init
	method or to receive the seed from the process that did so.
*/
static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
		const jsonObject* userObj, const char* uname, const char* password ) {

	// Get the username seed, as stored previously in memcache by the init method
	char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
	if(!seed) {
		return osrfAppRequestRespondException( ctx->session,
			ctx->request, "No authentication seed found. "
			"open-ils.auth.authenticate.init must be called first");
	}

	// Get the hashed password from the user object
	char* realPassword = oilsFMGetString( userObj, "passwd" );

	osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
	osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );

	// Concatenate them and take an MD5 hash of the result
	char* maskedPw = md5sum( "%s%s", seed, realPassword );

	free(realPassword);
	free(seed);

	if( !maskedPw ) {
		// This happens only if md5sum() runs out of memory
		free( maskedPw );
		return -1;  // md5sum() ran out of memory
	}

	osrfLogDebug(OSRF_LOG_MARK,  "oilsAuth generated masked password %s. "
			"Testing against provided password %s", maskedPw, password );

	int ret = 0;
	if( !strcmp( maskedPw, password ) )
		ret = 1;

	free(maskedPw);

	return ret;
}
Example #13
0
char* apacheGetFirstParamValue(osrfStringArray* params, char* key) {
	if(params == NULL || key == NULL) return NULL;	

	int i;
	osrfLogDebug(OSRF_LOG_MARK, "Fetching first URL value for key %s", key);
	for( i = 0; i < params->size; i++ ) {
		const char* nkey = osrfStringArrayGetString(params, i++);
		if(nkey && !strcmp(nkey, key)) 
			return strdup(osrfStringArrayGetString(params, i));
	}

	return NULL;
}
Example #14
0
/**
	@brief Implement the "init" method.
	@param ctx The method context.
	@return Zero if successful, or -1 if not.

	Method parameters:
	- username
	- nonce : optional login seed (string) provided by the caller which
		is added to the auth init cache to differentiate between logins
		using the same username and thus avoiding cache collisions for
		near-simultaneous logins.

	Return to client: Intermediate authentication seed.

	Combine the username with a timestamp and process ID, and take an md5 hash of the result.
	Store the hash in memcache, with a key based on the username.  Then return the hash to
	the client.

	However: if the username includes one or more embedded blank spaces, return a dummy
	hash without storing anything in memcache.  The dummy will never match a stored hash, so
	any attempt to authenticate with it will fail.
*/
int oilsAuthInit( osrfMethodContext* ctx ) {
	OSRF_METHOD_VERIFY_CONTEXT(ctx);

	char* username  = jsonObjectToSimpleString( jsonObjectGetIndex(ctx->params, 0) );
	const char* nonce = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
	if (!nonce) nonce = "";

	if( username ) {

		jsonObject* resp;

		if( strchr( username, ' ' ) ) {

			// Embedded spaces are not allowed in a username.  Use "x" as a dummy
			// seed.  It will never be a valid seed because 'x' is not a hex digit.
			resp = jsonNewObject( "x" );

		} else {

			// Build a key and a seed; store them in memcache.
			char* key  = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, nonce );
			char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, OILS_AUTH_COUNT_SFFX );
			char* seed = md5sum( "%d.%ld.%s.%s", (int) time(NULL), (long) getpid(), username, nonce );
			jsonObject* countobject = osrfCacheGetObject( countkey );
			if(!countobject) {
				countobject = jsonNewNumberObject( (double) 0 );
			}
			osrfCachePutString( key, seed, _oilsAuthSeedTimeout );
			osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );

			osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key );

			// Build a returnable object containing the seed.
			resp = jsonNewObject( seed );

			free( seed );
			free( key );
			free( countkey );
			jsonObjectFree( countobject );
		}

		// Return the seed to the client.
		osrfAppRespondComplete( ctx, resp );

		jsonObjectFree(resp);
		free(username);
		return 0;
	}

	return -1;  // Error: no username parameter
}
Example #15
0
osrfStringArray* apacheGetParamValues(osrfStringArray* params, char* key) {

	if(params == NULL || key == NULL) return NULL;	
	osrfStringArray* sarray	= osrfNewStringArray(12);

	osrfLogDebug(OSRF_LOG_MARK, "Fetching URL values for key %s", key);
	int i;
	for( i = 0; i < params->size; i++ ) {
		const char* nkey = osrfStringArrayGetString(params, i++);
		if(nkey && !strcmp(nkey, key)) 
			osrfStringArrayAdd(sarray, osrfStringArrayGetString(params, i));
	}
	return sarray;
}
static void osrfHttpTranslatorInitHeaders(osrfHttpTranslator* trans, transport_message* msg) {
    apr_table_set(trans->apreq->headers_out, OSRF_HTTP_HEADER_FROM, msg->sender);
    apr_table_set(trans->apreq->headers_out, OSRF_HTTP_HEADER_THREAD, trans->thread);
    if(trans->multipart) {
        sprintf(contentTypeBuf, MULTIPART_CONTENT_TYPE, trans->delim);
        contentTypeBuf[79] = '\0';
        osrfLogDebug(OSRF_LOG_MARK, "content type %s : %s : %s", MULTIPART_CONTENT_TYPE,
        trans->delim, contentTypeBuf);
        ap_set_content_type(trans->apreq, contentTypeBuf);
        ap_rprintf(trans->apreq, "--%s\n", trans->delim);
    } else {
        ap_set_content_type(trans->apreq, JSON_CONTENT_TYPE);
    }
}
static void clear_cached_recipient(const char* thread) {
    apr_pool_t *pool = NULL;                                                
    request_rec *r = trans->server->request(trans->server);

    if (apr_hash_get(trans->stateful_session_cache, thread, APR_HASH_KEY_STRING)) {

        osrfLogDebug(OSRF_LOG_MARK, "WS removing cached recipient on disconnect");

        // remove it from the hash
        apr_hash_set(trans->stateful_session_cache, thread, APR_HASH_KEY_STRING, NULL);

        if (apr_hash_count(trans->stateful_session_cache) == 0) {
            osrfLogDebug(OSRF_LOG_MARK, "WS re-setting stateful_session_pool");

            // memory accumulates in the stateful_session_pool as
            // sessions are cached then un-cached.  Un-caching removes
            // strings from the hash, but not from the pool.  Clear the
            // pool here. note: apr_pool_clear does not free memory, it
            // reclaims it for use again within the pool.  This is more
            // effecient than freeing and allocating every time.
            apr_pool_clear(trans->stateful_session_pool);
        }
    }
}
Example #18
0
char* oilsUtilsFetchOrgSetting( int orgid, const char* setting ) {
	if(!setting) return NULL;

	jsonObject* params = jsonParseFmt("[%d, \"%s\"]", orgid, setting );

	jsonObject* set = oilsUtilsQuickReq(
		"open-ils.actor",
		"open-ils.actor.ou_setting.ancestor_default", params);

	char* value = jsonObjectToSimpleString( jsonObjectGetKeyConst( set, "value" ));
	jsonObjectFree(params);
	jsonObjectFree(set);
	osrfLogDebug(OSRF_LOG_MARK, "Fetched org [%d] setting: %s => %s", orgid, setting, value);
	return value;
}
Example #19
0
/**
	@brief Save a pointer to the application's exit function.
	@param app Pointer to the osrfApplication.
	@param appName Application name (used only for log messages).

	Look in the shared object for a symbol named "osrfAppChildExit".  If you find one, save
	it as a pointer to the application's exit function.  If present, this function will be
	called when a server's child process (a so-called "drone") is shutting down.
*/
static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
	if(!(app && appName)) return;

	/* see if we can run the initialize method */
	char* error;
	void (*onExit) (void);
	*(void **) (&onExit) = dlsym(app->handle, "osrfAppChildExit");

	if( (error = dlerror()) != NULL ) {
		osrfLogDebug(OSRF_LOG_MARK, "No exit handler defined for %s", appName);
		return;
	}

	osrfLogInfo(OSRF_LOG_MARK, "registering exit handler for %s", appName);
	app->onExit = (*onExit);
}
Example #20
0
/**
	@brief Populate the params member of an osrfMessage.
	@param msg Pointer to the osrfMessage.
	@param o Pointer to a jsonObject representing the parameter(s) to a method.

	Make a copy of a jsonObject and install it as the parameter list for a method call.

	If the osrfMessage already has any parameters, discard them.

	The @a o parameter should point to a jsonObject of type JSON_ARRAY, with each element
	of the array being a parameter.  If @a o points to any other type of jsonObject, create
	a JSON_ARRAY as a wrapper for it, and install a copy of @a o as its only element.

	Used for a REQUEST message, to pass parameters to a method.  The alternative is to call
	osrf_message_add_param() or osrf_message_add_object_param() repeatedly as needed to add
	one parameter at a time.
*/
void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
	if(!msg || !o) return;

	if(msg->_params)
		jsonObjectFree(msg->_params);

	if(o->type == JSON_ARRAY) {
		msg->_params = jsonObjectClone(o);
	} else {
		osrfLogDebug( OSRF_LOG_MARK, "passing non-array to osrf_message_set_params(), fixing...");
		jsonObject* clone = jsonObjectClone(o);
		msg->_params = jsonNewObjectType( JSON_ARRAY );
		jsonObjectPush(msg->_params, clone);
		return;
	}
}
Example #21
0
int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
	OSRF_METHOD_VERIFY_CONTEXT(ctx);

	const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
	jsonObject* resp = NULL;

	if( authToken ) {
		osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
		char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
		osrfCacheRemove(key);
		resp = jsonNewObject(authToken); /**/
		free(key);
	}

	osrfAppRespondComplete( ctx, resp );
	jsonObjectFree(resp);
	return 0;
}
Example #22
0
// ident is either a username or barcode
// Returns the init seed -> requires free();
static char* oilsAuthBuildInitCache(
    int user_id, const char* ident, const char* ident_type, const char* nonce) {

    char* cache_key  = va_list_to_string(
        "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, nonce);

    char* count_key = va_list_to_string(
        "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, OILS_AUTH_COUNT_SFFX);

    char* auth_seed;
    if (user_id == -1) {
        // user does not exist.  Use a dummy seed
        auth_seed = strdup("x");
    } else {
        auth_seed = oilsAuthGetSalt(user_id);
    }

    jsonObject* seed_object = jsonParseFmt(
        "{\"%s\":\"%s\",\"user_id\":%d,\"seed\":\"%s\"}",
        ident_type, ident, user_id, auth_seed);

    jsonObject* count_object = osrfCacheGetObject(count_key);
    if(!count_object) {
        count_object = jsonNewNumberObject((double) 0);
    }

    osrfCachePutObject(cache_key, seed_object, _oilsAuthSeedTimeout);

    if (user_id != -1) {
        // Only track login counts for existing users, since a 
        // login for a nonexistent user will never succeed anyway.
        osrfCachePutObject(count_key, count_object, _oilsAuthBlockTimeout);
    }

    osrfLogDebug(OSRF_LOG_MARK, 
        "oilsAuthInit(): has seed %s and key %s", auth_seed, cache_key);

    free(cache_key);
    free(count_key);
    jsonObjectFree(count_object);
    jsonObjectFree(seed_object);

    return auth_seed;
}
Example #23
0
/**
	@brief Disconnect from the database.

	This function is called when the server drone is about to terminate.
*/
void osrfAppChildExit( void ) {
	osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");

	int same = 0;
	if (writehandle == dbhandle)
		same = 1;

	if (writehandle) {
		dbi_conn_query(writehandle, "ROLLBACK;");
		dbi_conn_close(writehandle);
		writehandle = NULL;
	}
	if (dbhandle && !same)
		dbi_conn_close(dbhandle);

	// XXX add cleanup of readHandles whenever that gets used

	return;
}
Example #24
0
/**
	@brief Perform a remote procedure call.
	@param service The name of the service to invoke.
	@param method The name of the method to call.
	@param params The parameters to be passed to the method, if any.
	@return A copy of whatever the method returns as a result, or a JSON_NULL if the method
	doesn't return anything.

	If the @a params parameter points to a JSON_ARRAY, pass each element of the array
	as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
	single parameter.  If it is NULL, pass no parameters.

	The calling code is responsible for freeing the returned object by calling jsonObjectFree().
*/
jsonObject* oilsUtilsQuickReq( const char* service, const char* method,
		const jsonObject* params ) {
	if(!(service && method)) return NULL;

	osrfLogDebug(OSRF_LOG_MARK, "oilsUtilsQuickReq(): %s - %s", service, method );

	// Open an application session with the service, and send the request
	osrfAppSession* session = osrfAppSessionClientInit( service );
	int reqid = osrfAppSessionSendRequest( session, params, method, 1 );

	// Get the response
	osrfMessage* omsg = osrfAppSessionRequestRecv( session, reqid, 60 );
	jsonObject* result = jsonObjectClone( osrfMessageGetResult(omsg) );

	// Clean up
	osrfMessageFree(omsg);
	osrfAppSessionFree(session);
	return result;
}
/**
	@brief Read and process available transport_messages for a transport_client.
	@param client Pointer to the transport_client whose socket is to be read.
	@param timeout How many seconds to wait for the first message.
	@param msg_received A pointer through which to report whether a message was received.
	@return 0 upon success (even if a timeout occurs), or -1 upon failure.

	Read and process all available transport_messages from the socket of the specified
	transport_client.  Pass each one through osrf_stack_transport().

	The timeout applies only to the first message.  Any subsequent messages must be
	available immediately.  Don't wait for them, even if the timeout has not expired.  In
	theory, a sufficiently large backlog of input messages could keep you working past the
	nominal expiration of the timeout.

	The @a msg_received parameter points to an int owned by the calling code and used as
	a boolean.  Set it to true if you receive at least one transport_message, or to false
	if you don't.  A timeout is not treated as an error; it just means you must set that
	boolean to false.
*/
int osrf_stack_process( transport_client* client, int timeout, int* msg_received ) {
	if( !client ) return -1;
	transport_message* msg = NULL;
	if(msg_received) *msg_received = 0;

	// Loop through the available input messages
	while( (msg = client_recv( client, timeout )) ) {
		if(msg_received) *msg_received = 1;
		osrfLogDebug( OSRF_LOG_MARK, "Received message from transport code from %s", msg->sender );
		osrf_stack_transport_handler( msg, NULL );
		timeout = 0;
	}

	if( client->error ) {
		osrfLogWarning(OSRF_LOG_MARK, "transport_client had trouble reading from the socket..");
		return -1;
	}

	if( ! client_connected( client ) ) return -1;

	return 0;
}
Example #26
0
char* oilsUtilsLogin( const char* uname, const char* passwd, const char* type, int orgId ) {
	if(!(uname && passwd)) return NULL;

	osrfLogDebug(OSRF_LOG_MARK, "Logging in with username %s", uname );
	char* token = NULL;

	jsonObject* params = jsonParseFmt("[\"%s\"]", uname);

	jsonObject* o = oilsUtilsQuickReq(
		"open-ils.auth", "open-ils.auth.authenticate.init", params );

	const char* seed = jsonObjectGetString(o);
	char* passhash = md5sum(passwd);
	char buf[256];
	snprintf(buf, sizeof(buf), "%s%s", seed, passhash);
	char* fullhash = md5sum(buf);

	jsonObjectFree(o);
	jsonObjectFree(params);
	free(passhash);

	params = jsonParseFmt( "[\"%s\", \"%s\", \"%s\", \"%d\"]", uname, fullhash, type, orgId );
	o = oilsUtilsQuickReq( "open-ils.auth",
		"open-ils.auth.authenticate.complete", params );

	if(o) {
		const char* tok = jsonObjectGetString(
			jsonObjectGetKeyConst( jsonObjectGetKey( o,"payload" ), "authtoken" ));
		if( tok )
			token = strdup( tok );
	}

	free(fullhash);
	jsonObjectFree(params);
	jsonObjectFree(o);

	return token;
}
Example #27
0
/**
	@brief Finish up the processing of a request.
	@param ctx Pointer to the method context.
	@param retcode The return code from the method's function.
	@return 0 if successfull, or -1 upon error.

	For an atomic method: send whatever responses we have been saving up, together with a
	STATUS message to say that we're finished.

	For a non-atomic method: if the return code from the method is greater than zero, just
	send the STATUS message.  If the return code is zero, do nothing; the method presumably
	sent the STATUS message on its own.
*/
static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
	if(!(ctx && ctx->method)) return -1;

	osrfLogDebug( OSRF_LOG_MARK, "Postprocessing method %s with retcode %d",
			ctx->method->name, retcode );

	if(ctx->responses) {
		// We have cached atomic responses to return, collected in a JSON ARRAY (we
		// haven't sent any responses yet).  Now send them all at once, followed by
		// a STATUS message to say that we're finished.
		osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );

	} else {
		// We have no cached atomic responses to return, but we may have some
		// non-atomic messages waiting in the buffer.
		if( retcode > 0 )
			// Send a STATUS message to say that we're finished, and to force a
			// final flush of the buffer.
			osrfAppRespondComplete( ctx, NULL );
	}

	return 0;
}
static int osrfHttpTranslatorProcess(osrfHttpTranslator* trans) {
    if(trans->body == NULL)
        return HTTP_BAD_REQUEST;

    if(!osrfHttpTranslatorSetTo(trans))
        return HTTP_BAD_REQUEST;

    char* jsonBody = osrfHttpTranslatorParseRequest(trans);
    if (NULL == jsonBody)
        return HTTP_BAD_REQUEST;

    while(client_recv(trans->handle, 0))
        continue; // discard any old status messages in the recv queue

    // send the message to the recipient
    transport_message* tmsg = message_init(
        jsonBody, NULL, trans->thread, trans->recipient, NULL);
    message_set_osrf_xid(tmsg, osrfLogGetXid());
    client_send_message(trans->handle, tmsg);
    message_free(tmsg); 
    free(jsonBody);

    if(trans->disconnectOnly) {
        osrfLogDebug(OSRF_LOG_MARK, "exiting early on disconnect");
        osrfCacheRemove(trans->thread);
        return OK;
    }

    // process the response from the opensrf service
    int firstWrite = 1;
    while(!trans->complete) {
        transport_message* msg = client_recv(trans->handle, trans->timeout);

        if(trans->handle->error) {
            osrfLogError(OSRF_LOG_MARK, "Transport error");
            osrfCacheRemove(trans->thread);
            return HTTP_INTERNAL_SERVER_ERROR;
        }

        if(msg == NULL)
            return HTTP_GATEWAY_TIME_OUT;

        if(msg->is_error) {
            osrfLogError(OSRF_LOG_MARK, "XMPP message resulted in error code %d", msg->error_code);
            osrfCacheRemove(trans->thread);
            return HTTP_NOT_FOUND;
        }

        if(!osrfHttpTranslatorCheckStatus(trans, msg))
            continue;

        if(firstWrite) {
            osrfHttpTranslatorInitHeaders(trans, msg);
            if(trans->connecting)
                osrfHttpTranslatorCacheSession(trans, msg->sender);
            firstWrite = 0;
        }

        if(trans->multipart) {
            osrfHttpTranslatorWriteChunk(trans, msg);
            if(trans->connectOnly)
                break;
        } else {
            if(!trans->messages)
                trans->messages = osrfNewList();
            osrfListPush(trans->messages, msg->body);

            if(trans->complete || trans->connectOnly) {
                growing_buffer* buf = buffer_init(128);
                unsigned int i;
                OSRF_BUFFER_ADD(buf, osrfListGetIndex(trans->messages, 0));
                for(i = 1; i < trans->messages->size; i++) {
                    buffer_chomp(buf); // chomp off the closing array bracket
                    char* body = osrfListGetIndex(trans->messages, i);
                    char newbuf[strlen(body)];
                    sprintf(newbuf, "%s", body+1); // chomp off the opening array bracket
                    OSRF_BUFFER_ADD_CHAR(buf, ',');
                    OSRF_BUFFER_ADD(buf, newbuf);
                }

                ap_rputs(buf->buf, trans->apreq);
                buffer_free(buf);
            }
        }
    }

    if(trans->disconnecting) // DISCONNECT within a multi-message batch
        osrfCacheRemove(trans->thread);

    return OK;
}
/**
 * Parses the request body, logs any REQUEST messages to the activity log, 
 * stamps the translator ingress on each message, and returns the updated 
 * messages as a JSON string.
 */
static char* osrfHttpTranslatorParseRequest(osrfHttpTranslator* trans) {
    osrfMessage* msg;
    osrfMessage* msgList[MAX_MSGS_PER_PACKET];
    int numMsgs = osrf_message_deserialize(trans->body, msgList, MAX_MSGS_PER_PACKET);
    osrfLogDebug(OSRF_LOG_MARK, "parsed %d opensrf messages in this packet", numMsgs);

    if(numMsgs == 0)
        return NULL;

    // log request messages to the activity log
    int i;
    for(i = 0; i < numMsgs; i++) {
        msg = msgList[i];
        osrfMessageSetIngress(msg, TRANSLATOR_INGRESS);

        switch(msg->m_type) {

            case REQUEST: {
                const jsonObject* params = msg->_params;
                growing_buffer* act = buffer_init(128);	
                char* method = msg->method_name;
                buffer_fadd(act, "[%s] [%s] %s %s", trans->remoteHost, "",
                    trans->service, method);

                const jsonObject* obj = NULL;
                int i = 0;
                const char* str;
                int redactParams = 0;
                while( (str = osrfStringArrayGetString(log_protect_arr, i++)) ) {
                    //osrfLogInternal(OSRF_LOG_MARK, "Checking for log protection [%s]", str);
                    if(!strncmp(method, str, strlen(str))) {
                        redactParams = 1;
                        break;
                    }
                }
                if(redactParams) {
                    OSRF_BUFFER_ADD(act, " **PARAMS REDACTED**");
                } else {
                    i = 0;
                    while((obj = jsonObjectGetIndex(params, i++))) {
                        str = jsonObjectToJSON(obj);
                        if( i == 1 )
                            OSRF_BUFFER_ADD(act, " ");
                        else
                            OSRF_BUFFER_ADD(act, ", ");
                        OSRF_BUFFER_ADD(act, str);
                        free(str);
                    }
                }
                osrfLogActivity(OSRF_LOG_MARK, "%s", act->buf);
                buffer_free(act);
                break;
            }

            case CONNECT:
                trans->connecting = 1;
                if (numMsgs == 1) 
                    trans->connectOnly = 1;
                break;

            case DISCONNECT:
                trans->disconnecting = 1;
                if (numMsgs == 1) 
                    trans->disconnectOnly = 1;
                break;

            case RESULT:
                osrfLogWarning( OSRF_LOG_MARK, "Unexpected RESULT message received" );
                break;

            case STATUS:
                osrfLogWarning( OSRF_LOG_MARK, "Unexpected STATUS message received" );
                break;

            default:
                osrfLogWarning( OSRF_LOG_MARK, "Invalid message type %d received",
                    msg->m_type );
                break;
        }
    }

    char* jsonString = osrfMessageSerializeBatch(msgList, numMsgs);
    for(i = 0; i < numMsgs; i++) {
        osrfMessageFree(msgList[i]);
    }
    return jsonString;
}
Example #30
0
osrfHash* oilsIDLInit( const char* idl_filename ) {

	if (idlHash) return idlHash;

	char* prop_str = NULL;

	idlHash = osrfNewHash();
	osrfHash* class_def_hash = NULL;

	osrfLogInfo(OSRF_LOG_MARK, "Parsing the IDL XML...");
	idlDoc = xmlReadFile( idl_filename, NULL, XML_PARSE_XINCLUDE );
	
	if (!idlDoc) {
		osrfLogError(OSRF_LOG_MARK, "Could not load or parse the IDL XML file!");
		return NULL;
	}

	osrfLogDebug(OSRF_LOG_MARK, "Initializing the Fieldmapper IDL...");

	xmlNodePtr docRoot = xmlDocGetRootElement(idlDoc);
	xmlNodePtr kid = docRoot->children;
	while (kid) {
		if (!strcmp( (char*)kid->name, "class" )) {

			class_def_hash = osrfNewHash();
			char* current_class_name = (char*) xmlGetProp(kid, BAD_CAST "id");
			
			osrfHashSet( class_def_hash, current_class_name, "classname" );
			osrfHashSet( class_def_hash, xmlGetNsProp(kid, BAD_CAST "fieldmapper", BAD_CAST OBJECT_NS), "fieldmapper" );
			osrfHashSet( class_def_hash, xmlGetNsProp(kid, BAD_CAST "readonly", BAD_CAST PERSIST_NS), "readonly" );

			osrfHashSet( idlHash, class_def_hash, current_class_name );

			if ((prop_str = (char*)xmlGetNsProp(kid, BAD_CAST "tablename", BAD_CAST PERSIST_NS))) {
				osrfLogDebug(OSRF_LOG_MARK, "Using table '%s' for class %s", prop_str, current_class_name );
				osrfHashSet(
					class_def_hash,
					prop_str,
					"tablename"
				);
			}

			if ((prop_str = (char*)xmlGetNsProp(kid, BAD_CAST "restrict_primary", BAD_CAST PERSIST_NS))) {
				osrfLogDebug(OSRF_LOG_MARK, "Delete restriction policy set at '%s' for pkey of class %s", prop_str, current_class_name );
				osrfHashSet(
					class_def_hash,
					prop_str,
					"restrict_primary"
				);
			}

			if ((prop_str = (char*)xmlGetNsProp(kid, BAD_CAST "virtual", BAD_CAST PERSIST_NS))) {
				osrfHashSet(
					class_def_hash,
					prop_str,
					"virtual"
				);
			}

			// Tokenize controller attribute into an osrfStringArray
			prop_str = (char*) xmlGetProp(kid, BAD_CAST "controller");
			if( prop_str )
				osrfLogDebug(OSRF_LOG_MARK, "Controller list is %s", prop_str );
			osrfStringArray* controller = osrfStringArrayTokenize( prop_str, ' ' );
			xmlFree( prop_str );
			osrfHashSet( class_def_hash, controller, "controller");

			osrfHash* current_links_hash = osrfNewHash();
			osrfHash* current_fields_hash = osrfNewHash();

			osrfHashSet( class_def_hash, current_fields_hash, "fields" );
			osrfHashSet( class_def_hash, current_links_hash, "links" );

			xmlNodePtr _cur = kid->children;

			while (_cur) {

				if (!strcmp( (char*)_cur->name, "fields" )) {

					if( (prop_str = (char*)xmlGetNsProp(_cur, BAD_CAST "primary", BAD_CAST PERSIST_NS)) ) {
						osrfHashSet(
							class_def_hash,
							prop_str,
							"primarykey"
						);
					}

					if( (prop_str = (char*)xmlGetNsProp(_cur, BAD_CAST "sequence", BAD_CAST PERSIST_NS)) ) {
						osrfHashSet(
							class_def_hash,
							prop_str,
							"sequence"
						);
					}

					unsigned int array_pos = 0;
					char array_pos_buf[ 7 ];  // For up to 1,000,000 fields per class

					xmlNodePtr _f = _cur->children;
					while(_f) {
						if (strcmp( (char*)_f->name, "field" )) {
							_f = _f->next;
							continue;
						}

						// Get the field name.  If it's one of the three standard
						// fields that we always generate, ignore it.
						char* field_name = (char*)xmlGetProp(_f, BAD_CAST "name");
						if( field_name ) {
							osrfLogDebug(OSRF_LOG_MARK, 
									"Found field %s for class %s", field_name, current_class_name );
							if(    !strcmp( field_name, "isnew" )
								|| !strcmp( field_name, "ischanged" )
								|| !strcmp( field_name, "isdeleted" ) ) {
								free( field_name );
								_f = _f->next;
								continue;
							}
						} else {
							osrfLogDebug(OSRF_LOG_MARK,
									"Found field with no name for class %s", current_class_name );
							_f = _f->next;
							continue;
						}
 
						osrfHash* field_def_hash = osrfNewHash();

						// Insert array_position
						snprintf( array_pos_buf, sizeof( array_pos_buf ), "%u", array_pos++ );
						osrfHashSet( field_def_hash, strdup( array_pos_buf ), "array_position" );

						if( (prop_str = (char*)xmlGetNsProp(_f, BAD_CAST "i18n", BAD_CAST PERSIST_NS)) ) {
							osrfHashSet(
								field_def_hash,
								prop_str,
								"i18n"
							);
						}

						if( (prop_str = (char*)xmlGetNsProp(_f, BAD_CAST "virtual", BAD_CAST PERSIST_NS)) ) {
							osrfHashSet(
								field_def_hash,
								prop_str,
								"virtual"
							);
						} else {   // default to virtual
							osrfHashSet(
								field_def_hash,
								"false",
								"virtual"
							);
						}

						if( (prop_str = (char*)xmlGetNsProp(_f, BAD_CAST "primitive", BAD_CAST PERSIST_NS)) ) {
							osrfHashSet(
								field_def_hash,
								prop_str,
								"primitive"
							);
						}

						osrfHashSet( field_def_hash, field_name, "name" );
						osrfHashSet(
							current_fields_hash,
							field_def_hash,
							field_name
						);
						_f = _f->next;
					}

					// Create three standard, stereotyped virtual fields for every class
					add_std_fld( current_fields_hash, "isnew",     array_pos++ );
					add_std_fld( current_fields_hash, "ischanged", array_pos++ );
					add_std_fld( current_fields_hash, "isdeleted", array_pos   );

				}

				if (!strcmp( (char*)_cur->name, "links" )) {
					xmlNodePtr _l = _cur->children;

					while(_l) {
						if (strcmp( (char*)_l->name, "link" )) {
							_l = _l->next;
							continue;
						}

						osrfHash* link_def_hash = osrfNewHash();

						if( (prop_str = (char*)xmlGetProp(_l, BAD_CAST "reltype")) ) {
							osrfHashSet(
								link_def_hash,
								prop_str,
								"reltype"
							);
							osrfLogDebug(OSRF_LOG_MARK, "Adding link with reltype %s", prop_str );
						} else
							osrfLogDebug(OSRF_LOG_MARK, "Adding link with no reltype" );

						if( (prop_str = (char*)xmlGetProp(_l, BAD_CAST "key")) ) {
							osrfHashSet(
								link_def_hash,
								prop_str,
								"key"
							);
							osrfLogDebug(OSRF_LOG_MARK, "Link fkey is %s", prop_str );
						} else
							osrfLogDebug(OSRF_LOG_MARK, "Link with no fkey" );

						if( (prop_str = (char*)xmlGetProp(_l, BAD_CAST "class")) ) {
							osrfHashSet(
								link_def_hash,
								prop_str,
								"class"
							);
							osrfLogDebug(OSRF_LOG_MARK, "Link fclass is %s", prop_str );
						} else
							osrfLogDebug(OSRF_LOG_MARK, "Link with no fclass" );

						// Tokenize map attribute into an osrfStringArray
						prop_str = (char*) xmlGetProp(_l, BAD_CAST "map");
						if( prop_str )
							osrfLogDebug(OSRF_LOG_MARK, "Link mapping list is %s", prop_str );
						osrfStringArray* map = osrfStringArrayTokenize( prop_str, ' ' );
						osrfHashSet( link_def_hash, map, "map");
						xmlFree( prop_str );

						if( (prop_str = (char*)xmlGetProp(_l, BAD_CAST "field")) ) {
							osrfHashSet(
								link_def_hash,
								prop_str,
								"field"
							);
							osrfLogDebug(OSRF_LOG_MARK, "Link fclass is %s", prop_str );
						} else
							osrfLogDebug(OSRF_LOG_MARK, "Link with no fclass" );

						osrfHashSet(
							current_links_hash,
							link_def_hash,
							prop_str
						);

						_l = _l->next;
					}
				}
/**** Structure of permacrud in memory ****

{ create :
    { permission : [ x, y, z ],
      global_required : "true", -- anything else, or missing, is false
      local_context : [ f1, f2 ],
      foreign_context : { class1 : { fkey : local_class_key, field : class1_field, context : [ a, b, c ] }, ...}
    },
  retrieve : null, -- no perm check, or structure similar to the others
  update : -- like create
    ...
  delete : -- like create
    ...
}   

**** Structure of permacrud in memory ****/

				if (!strcmp( (char*)_cur->name, "permacrud" )) {
					osrfHash* pcrud = osrfNewHash();
					osrfHashSet( class_def_hash, pcrud, "permacrud" );
					xmlNodePtr _l = _cur->children;

					while(_l) {
						if (strcmp( (char*)_l->name, "actions" )) {
							_l = _l->next;
							continue;
						}

						xmlNodePtr _a = _l->children;

						while(_a) {
							const char* action_name = (const char*) _a->name;
							if (
								strcmp( action_name, "create" ) &&
								strcmp( action_name, "retrieve" ) &&
								strcmp( action_name, "update" ) &&
								strcmp( action_name, "delete" )
							) {
								_a = _a->next;
								continue;
							}

							osrfLogDebug(OSRF_LOG_MARK, "Found Permacrud action %s for class %s",
								action_name, current_class_name );

							osrfHash* action_def_hash = osrfNewHash();
							osrfHashSet( pcrud, action_def_hash, action_name );

							// Tokenize permission attribute into an osrfStringArray
							prop_str = (char*) xmlGetProp(_a, BAD_CAST "permission");
							if( prop_str )
								osrfLogDebug(OSRF_LOG_MARK,
									"Permacrud permission list is %s", prop_str );
							osrfStringArray* map = osrfStringArrayTokenize( prop_str, ' ' );
							osrfHashSet( action_def_hash, map, "permission");
							xmlFree( prop_str );

					    	osrfHashSet( action_def_hash,
								(char*)xmlGetNoNsProp(_a, BAD_CAST "global_required"), "global_required");

							// Tokenize context_field attribute into an osrfStringArray
							prop_str = (char*) xmlGetProp(_a, BAD_CAST "context_field");
							if( prop_str )
								osrfLogDebug(OSRF_LOG_MARK,
									"Permacrud context_field list is %s", prop_str );
							map = osrfStringArrayTokenize( prop_str, ' ' );
							osrfHashSet( action_def_hash, map, "local_context");
							xmlFree( prop_str );

							osrfHash* foreign_context = osrfNewHash();
							osrfHashSet( action_def_hash, foreign_context, "foreign_context");

							xmlNodePtr _f = _a->children;

							while(_f) {
								if ( strcmp( (char*)_f->name, "context" ) ) {
									_f = _f->next;
									continue;
								}

								if( (prop_str = (char*)xmlGetNoNsProp(_f, BAD_CAST "link")) ) {
									osrfLogDebug(OSRF_LOG_MARK,
										"Permacrud context link definition is %s", prop_str );

									osrfHash* _tmp_fcontext = osrfNewHash();

									// Store pointers to elements already stored
									// from the <link> aggregate
									osrfHash* _flink = osrfHashGet( current_links_hash, prop_str );
									osrfHashSet( _tmp_fcontext, osrfHashGet(_flink, "field"), "fkey" );
									osrfHashSet( _tmp_fcontext, osrfHashGet(_flink, "key"), "field" );
									xmlFree( prop_str );

								    if( (prop_str = (char*)xmlGetNoNsProp(_f, BAD_CAST "jump")) )
									    osrfHashSet( _tmp_fcontext, osrfStringArrayTokenize( prop_str, '.' ), "jump" );
									xmlFree( prop_str );

									// Tokenize field attribute into an osrfStringArray
									char * field_list = (char*) xmlGetProp(_f, BAD_CAST "field");
									if( field_list )
										osrfLogDebug(OSRF_LOG_MARK,
											"Permacrud foreign context field list is %s", field_list );
									map = osrfStringArrayTokenize( field_list, ' ' );
									osrfHashSet( _tmp_fcontext, map, "context");
									xmlFree( field_list );

									// Insert the new hash into a hash attached to the parent node
									osrfHashSet( foreign_context, _tmp_fcontext, osrfHashGet( _flink, "class" ) );

								} else {

									if( (prop_str = (char*)xmlGetNoNsProp(_f, BAD_CAST "field") )) {
										char* map_list = prop_str;
										osrfLogDebug(OSRF_LOG_MARK,
											"Permacrud local context field list is %s", prop_str );
			
										if (strlen( map_list ) > 0) {
											char* st_tmp = NULL;
											char* _map_class = strtok_r(map_list, " ", &st_tmp);
											osrfStringArrayAdd(
												osrfHashGet( action_def_hash, "local_context"), _map_class);
									
											while ((_map_class = strtok_r(NULL, " ", &st_tmp))) {
												osrfStringArrayAdd(
													osrfHashGet( action_def_hash, "local_context"), _map_class);
											}
										}
										xmlFree(map_list);
									}

								}
								_f = _f->next;
							}
							_a = _a->next;
						}
						_l = _l->next;
					}
				}

				if (!strcmp( (char*)_cur->name, "source_definition" )) {
					char* content_str;
					if( (content_str = (char*)xmlNodeGetContent(_cur)) ) {
						osrfLogDebug(OSRF_LOG_MARK, "Using source definition '%s' for class %s",
							content_str, current_class_name );
						osrfHashSet(
							class_def_hash,
							content_str,
							"source_definition"
						);
					}

				}

				_cur = _cur->next;
			} // end while
		}

		kid = kid->next;
	} // end while

	osrfLogInfo(OSRF_LOG_MARK, "...IDL XML parsed");

	return idlHash;
}