Пример #1
0
/**
	@brief Execute an SQL query and return a result set.
	@param ctx Pointer to the current method context.
	@return Zero if successful, or -1 if not.

	Method parameters:
	- query token, as previously returned by the .prepare method.

	Returns: A series of responses, each of them a row represented as an array of column values.
*/
int doExecute( osrfMethodContext* ctx ) {
	if(osrfMethodVerifyContext( ctx )) {
		osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
		return -1;
	}

	// Get the query token
	const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
	if( token_obj->type != JSON_STRING ) {
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Invalid parameter; query token must be a string" );
		return -1;
	}
	const char* token = jsonObjectGetString( token_obj );

	// Look up the query token in the session-level userData
	CachedQuery* query = search_token( ctx, token );
	if( !query ) {
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Invalid query token" );
		return -1;
	}

	osrfLogInfo( OSRF_LOG_MARK, "Executing query for token \"%s\"", token );
	if( query->state->error ) {
		osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
			"No valid prepared query available for query id # %d", query->query->id ));
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
							  ctx->request, "No valid prepared query available" );
		return -1;
	} else if( buildSQL( query->state, query->query )) {
		osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
			"Unable to build SQL statement for query id # %d", query->query->id ));
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Unable to build SQL statement" );
		return -1;
	}

	jsonObject* row = oilsFirstRow( query->state );
	while( row ) {
		osrfAppRespond( ctx, row );
		row = oilsNextRow( query->state );
	}

	if( query->state->error ) {
		osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
			"Unable to execute SQL statement for query id # %d", query->query->id ));
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Unable to execute SQL statement" );
		if( query->state->panic ) {
			osrfLogError( OSRF_LOG_MARK, sqlAddMsg( query->state,
				"Database connection isn't working" ));
			osrfAppSessionPanic( ctx->session );
		}
		return -1;
	}

	osrfAppRespondComplete( ctx, NULL );
	return 0;
}
Пример #2
0
/**
	@brief Load an XML configuration file into a jsonObject within an osrfConfig.
	@param configFile Name of the XML configuration file.
	@param configContext (Optional) Root of a subtree in the configuration file,
	@return If successful, a pointer to the resulting osrfConfig; otherwise NULL.

	If @a configContext is not NULL, save a copy of the string to which it points.  It is a
	tag identifying one or more subtrees within the XML; subsequent searches will examine
	only matching subtrees.  Otherwise they will search the tree from the root.

	(In practice a configContext is always supplied, so that different programs can share
	the same configuration file, partitioned by context tags.)

	The calling code is responsible for freeing the returned config object by calling
	osrfConfigFree().
*/
osrfConfig* osrfConfigInit(const char* configFile, const char* configContext) {
	if(!configFile) return NULL;

	// Load XML from the configuration file

	xmlDocPtr doc = xmlParseFile(configFile);
	if(!doc) {
		osrfLogWarning( OSRF_LOG_MARK, "Unable to parse XML config file %s", configFile);
		return NULL;
	}

	// Translate it into a jsonObject
	jsonObject* json_config = xmlDocToJSON(doc);
	xmlFreeDoc(doc);

	if(!json_config ) {
		osrfLogWarning( OSRF_LOG_MARK, "xmlDocToJSON failed for config %s", configFile);
		return NULL;
	}

	// Build an osrfConfig and return it by pointer
	osrfConfig* cfg = safe_malloc(sizeof(osrfConfig));

	if( configContext )
		cfg->configContext = strdup(configContext);
	else
		cfg->configContext = NULL;

    cfg->configFileName = strdup(configFile);
	cfg->config = json_config;

	return cfg;
}
Пример #3
0
/**
	@brief Register an application.
	@param appName Name of the application.
	@param soFile Name of the shared object file to be loaded for this application.
	@return Zero if successful, or -1 upon error.

	Open the shared object file and call its osrfAppInitialize() function, if it has one.
	Register the standard system methods for it.  Arrange for the application name to
	appear in subsequent log messages.
*/
int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
	if( !appName || ! soFile ) return -1;
	char* error;

	osrfLogSetAppname( appName );

	if( !_osrfAppHash ) {
		_osrfAppHash = osrfNewHash();
		osrfHashSetCallback( _osrfAppHash, osrfAppFree );
	}

	osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );

	// Open the shared object.
	void* handle = dlopen( soFile, RTLD_NOW );
	if( ! handle ) {
		const char* msg = dlerror();
		osrfLogError( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, msg );
		return -1;
	}

	// Construct the osrfApplication.
	osrfApplication* app = safe_malloc(sizeof(osrfApplication));
	app->handle = handle;
	app->methods = osrfNewHash();
	osrfHashSetCallback( app->methods, osrfMethodFree );
	app->onExit = NULL;

	// Add the newly-constructed app to the list.
	osrfHashSet( _osrfAppHash, app, appName );

	// Try to run the initialize method.  Typically it will register one or more
	// methods of the application.
	int (*init) (void);
	*(void **) (&init) = dlsym( handle, "osrfAppInitialize" );

	if( (error = dlerror()) != NULL ) {
		osrfLogWarning( OSRF_LOG_MARK,
			"! Unable to locate method symbol [osrfAppInitialize] for app %s: %s",
			appName, error );

	} else {

		/* run the method */
		int ret;
		if( (ret = (*init)()) ) {
			osrfLogWarning( OSRF_LOG_MARK, "Application %s returned non-zero value from "
				"'osrfAppInitialize', not registering...", appName );
			osrfHashRemove( _osrfAppHash, appName );
			return ret;
		}
	}

	register_system_methods( app );
	osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
	osrfAppSetOnExit( app, appName );

	return 0;
}
Пример #4
0
/**
	@brief Load the child queries subordinate to a UNION, INTERSECT, or EXCEPT query.
	@param state Pointer to the query-building context.
	@param parent ID of the UNION, INTERSECT, or EXCEPT query.
	@param type_str The type of the query ("UNION", "INTERSECT", or "EXCEPT").
	@return If successful, a pointer to a linked list of QSeq, each bearing a pointer to a
		StoredQ; otherwise NULL.

	The @a type_str parameter is used only for building error messages.
*/
static QSeq* loadChildQueries( BuildSQLState* state, int parent_id, const char* type_str ) {
	QSeq* child_list = NULL;
	
	// The ORDER BY is in descending order so that we can build the list by adding to
	// the head, and it will wind up in the right order.
	dbi_result result = dbi_conn_queryf( state->dbhandle,
		"SELECT id, parent_query, seq_no, child_query "
		"FROM query.query_sequence WHERE parent_query = %d ORDER BY seq_no DESC", parent_id );
	if( result ) {
		if( dbi_result_first_row( result ) ) {
			int count = 0;
			while( 1 ) {
				++count;
				QSeq* seq = constructQSeq( state, result );
				if( seq ) {
					PRINT( "Found a child query\n" );
					PRINT( "\tid: %d\n", seq->id );
					PRINT( "\tparent id: %d\n", seq->parent_query_id );
					PRINT( "\tseq_no: %d\n", seq->seq_no );
					// Add to the head of the list
					seq->next = child_list;
					child_list = seq;
				} else{
					freeQSeqList( child_list );
					return NULL;
				}
				if( !dbi_result_next_row( result ))
					break;
			}
			if( count < 2 ) {
				osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
					"%s query # %d has only one child query", type_str, parent_id ));
				state->error = 1;
				freeQSeqList( child_list );
				return NULL;
			}
		} else {
			osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
				"%s query # %d has no child queries within it", type_str, parent_id ));
			state->error = 1;
			return NULL;
		}
	} else {
		const char* msg;
		int errnum = dbi_conn_error( state->dbhandle, &msg );
		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
			"Unable to query query.query_sequence table: # %d %s",
			errnum, msg ? msg : "No description available" ));
		state->error = 1;
		return NULL;
	}

	return child_list;
}
Пример #5
0
static QSeq* constructQSeq( BuildSQLState* state, dbi_result result ) {
	int id = dbi_result_get_int_idx( result, 1 );
	int parent_query_id = dbi_result_get_int_idx( result, 2 );
	int seq_no = dbi_result_get_int_idx( result, 3 );
	int child_query_id = dbi_result_get_int_idx( result, 4 );

	StoredQ* child_query = getStoredQuery( state, child_query_id );
	if( !child_query ) {
		osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
			"Unable to load child query # %d for parent query %d",
			child_query_id, parent_query_id ));
		state->error = 1;
		return NULL;
	}

	// Allocate a QSeq; from the free list if possible, from the heap if necessary
	QSeq* seq = NULL;
	if( free_qseq_list ) {
		seq = free_qseq_list;
		free_qseq_list = free_qseq_list->next;
	} else
		seq = safe_malloc( sizeof( QSeq ));

	seq->next            = NULL;
	seq->id              = id;
	seq->parent_query_id = parent_query_id;
	seq->seq_no          = seq_no;
	seq->child_query     = child_query;

	return seq;
}
Пример #6
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;
}
Пример #7
0
static int handler(request_rec *r) {
    int stat = OK;
	if(strcmp(r->handler, MODULE_NAME)) return DECLINED;
    if(r->header_only) return stat;

	r->allowed |= (AP_METHOD_BIT << M_GET);
	r->allowed |= (AP_METHOD_BIT << M_POST);

	osrfLogSetAppname("osrf_http_translator");
	osrfAppSessionSetIngress(TRANSLATOR_INGRESS);
    testConnection(r);
    crossOriginHeaders(r, allowedOrigins);

	osrfLogMkXid();
    osrfHttpTranslator* trans = osrfNewHttpTranslator(r);
    if(trans->body) {
        stat = osrfHttpTranslatorProcess(trans);
        //osrfHttpTranslatorDebug(trans);
        osrfLogInfo(OSRF_LOG_MARK, "translator resulted in status %d", stat);
    } else {
        osrfLogWarning(OSRF_LOG_MARK, "no message body to process");
    }
    osrfHttpTranslatorFree(trans);
	return stat;
}
Пример #8
0
/**
	@brief Translate a JSON array into an array of osrfMessages.
	@param string The JSON string to be translated.
	@param msgs Pointer to an array of pointers to osrfMessage, to receive the results.
	@param count How many slots are available in the @a msgs array.
	@return The number of osrfMessages created.

	The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.

	If there are too many messages in the JSON array to fit into the pointer array, we
	silently ignore the excess.
*/
int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {

	if(!string || !msgs || count <= 0) return 0;
	int numparsed = 0;

	// Parse the JSON
	jsonObject* json = jsonParse(string);

	if(!json) {
		osrfLogWarning( OSRF_LOG_MARK,
			"osrf_message_deserialize() unable to parse data: \n%s\n", string);
		return 0;
	}

	// Traverse the JSON_ARRAY, turning each element into an osrfMessage
	int x;
	for( x = 0; x < json->size && x < count; x++ ) {

		const jsonObject* message = jsonObjectGetIndex( json, x );

		if( message && message->type != JSON_NULL &&
			message->classname && !strcmp(message->classname, "osrfMessage" )) {
			msgs[numparsed++] = deserialize_one_message( message );
		}
	}

	jsonObjectFree( json );
	return numparsed;
}
Пример #9
0
/**
	@brief Send a STATUS message to the client, notifying it of an error.
	@param ses Pointer to the current application session.
	@param request Request ID of the request.
	@param msg A printf-style format string defining an explanatory message to be sent to
	the client.  Subsequent parameters, if any, will be formatted and inserted into the
	resulting output string.
	@return -1 if the @a ses parameter is NULL; otherwise zero.
*/
int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... ) {
	if(!ses) return -1;
	if(!msg) msg = "";
	VA_LIST_TO_STRING(msg);
	osrfLogWarning( OSRF_LOG_MARK,  "Returning method exception with message: %s", VA_BUF );
	osrfAppSessionStatus( ses, OSRF_STATUS_NOTFOUND, "osrfMethodException", request,  VA_BUF );
	return 0;
}
Пример #10
0
/**
	@brief Construct an SQL query, but without executing it.
	@param ctx Pointer to the current method context.
	@return Zero if successful, or -1 if not.

	Method parameters:
	- query token, as previously returned by the .prepare method.

	Returns: A string containing an SQL query..
*/
int doSql( osrfMethodContext* ctx ) {
	if(osrfMethodVerifyContext( ctx )) {
		osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
		return -1;
	}

	// Get the query token
	const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
	if( token_obj->type != JSON_STRING ) {
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Invalid parameter; query token must be a string" );
		return -1;
	}
	const char* token = jsonObjectGetString( token_obj );

	// Look up the query token in the session-level userData
	CachedQuery* query = search_token( ctx, token );
	if( !query ) {
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Invalid query token" );
		return -1;
	}

	osrfLogInfo( OSRF_LOG_MARK, "Returning SQL for token \"%s\"", token );
	if( query->state->error ) {
		osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
			"No valid prepared query available for query id # %d", query->query->id ));
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "No valid prepared query available" );
		return -1;
	} else if( buildSQL( query->state, query->query )) {
		osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
			"Unable to build SQL statement for query id # %d", query->query->id ));
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Unable to build SQL statement" );
		return -1;
	}

	osrfAppRespondComplete( ctx, jsonNewObject( OSRF_BUFFER_C_STR( query->state->sql )));
	return 0;
}
Пример #11
0
/**
	@brief Load a specified query from the database query tables.
	@param ctx Pointer to the current method context.
	@return Zero if successful, or -1 if not.

	Method parameters:
	- query id (key of query.stored_query table)

	Returns: a hash with two entries:
	- "token": A character string serving as a token for future references to the query.
	- "bind_variables" A hash of bind variables; see notes for doParamList().
*/
int doPrepare( osrfMethodContext* ctx ) {
	if(osrfMethodVerifyContext( ctx )) {
		osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
		return -1;
	}

	// Get the query id from a method parameter
	const jsonObject* query_id_obj = jsonObjectGetIndex( ctx->params, 0 );
	if( query_id_obj->type != JSON_NUMBER ) {
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Invalid parameter; query id must be a number" );
		return -1;
	}

	int query_id = atoi( jsonObjectGetString( query_id_obj ));
	if( query_id <= 0 ) {
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Invalid parameter: query id must be greater than zero" );
		return -1;
	}

	osrfLogInfo( OSRF_LOG_MARK, "Loading query for id # %d", query_id );

	BuildSQLState* state = buildSQLStateNew( dbhandle );
	state->defaults_usable = 1;
	state->values_required = 0;
	StoredQ* query = getStoredQuery( state, query_id );
	if( state->error ) {
		osrfLogWarning( OSRF_LOG_MARK, "Unable to load stored query # %d", query_id );
		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
			ctx->request, "Unable to load stored query" );
		if( state->panic ) {
			osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state, 
				"Database connection isn't working" ));
			osrfAppSessionPanic( ctx->session );
		}
		return -1;
	}

	const char* token = save_query( ctx, state, query );

	osrfLogInfo( OSRF_LOG_MARK, "Token for query id # %d is \"%s\"", query_id, token );

	// Build an object to return.  It will be a hash containing the query token and a
	// list of bind variables.
	jsonObject* returned_obj = jsonNewObjectType( JSON_HASH );
	jsonObjectSetKey( returned_obj, "token", jsonNewObject( token ));
	jsonObjectSetKey( returned_obj, "bind_variables",
		oilsBindVarList( state->bindvar_list ));

	osrfAppRespondComplete( ctx, returned_obj );
	return 0;
}
Пример #12
0
/**
	@brief Convert a transport_message to XML and send it to Jabber.
	@param session Pointer to the transport_session.
	@param msg Pointer to a transport_message enclosing the message.
	@return 0 if successful, or -1 upon error.
*/
int session_send_msg(
		transport_session* session, transport_message* msg ) {

	if( ! session ) { return -1; }

	if( ! session->state_machine->connected ) {
		osrfLogWarning(OSRF_LOG_MARK, "State machine is not connected in send_msg()");
		return -1;
	}

	message_prepare_xml( msg );
	return socket_send( session->sock_id, msg->msg_xml );

}
Пример #13
0
/**
	@brief Set the effective output buffer size for a given method.
	@param appName Name of the application.
	@param methodName Name of the method.
	@param bufsize Desired size of the output buffer, in bytes.
	@return Zero if successful, or -1 if the specified method cannot be found.

	A smaller buffer size may result in a lower latency for the first response, since we don't
	wait for as many messages to accumulate before flushing the output buffer.  On the other
	hand a larger buffer size may result in higher throughput due to lower network overhead.

	Since the buffer size is not an absolute limit, it may be set to zero, in which case each
	output transport message will contain no more than one RESULT message.

	This function has no effect on atomic methods, because all responses are sent in a single
	message anyway.  Likewise it has no effect on a method that returns only a single response.
*/
int osrfMethodSetBufferSize( const char* appName, const char* methodName, size_t bufsize ) {
	osrfMethod* method = _osrfAppFindMethod( appName, methodName );
	if( method ) {
		osrfLogInfo( OSRF_LOG_MARK,
			"Setting outbuf buffer size to %lu for method %s of application %s",
			(unsigned long) bufsize, methodName, appName );
		method->bufsize = bufsize;
		return 0;
	} else {
		osrfLogWarning( OSRF_LOG_MARK,
			"Unable to set outbuf buffer size to %lu for method %s of application %s",
			(unsigned long) bufsize, methodName, appName );
		return -1;
	}
}
Пример #14
0
/**
	@brief Search for one or more subtrees of a configuration.
	@param cfg (Optional) The configuration to search, or NULL for the default configuration.
	@param path A printf-style format string representing the search path.  Subsequent
	parameters, if any, are inserted into the format string to form the search path.
	@return A pointer to a jsonObject representing a subset of the specified configuration,
	if found; otherwise NULL.

	Search for subtrees of the configuration, as specified by the search path.

	If the configuration includes a configContext, then prepend it to the path, like so:
	"//<configContext><path>".  Hence the path should begin with a slash to separate it from
	the context.  Search for the resulting effective path at any level within the
	configuration.

	If the configuration does @em not include a configContext, then start the search at the
	root of the configuration.  In this case the path should @em not begin with a slash.

	If any entries match the effective path, return copies of them all as elements of a
	jsonObject of type JSON_ARRAY.

	If no entries match the effective path, return a jsonObject of type JSON_NULL.

	The calling code is responsible for freeing the returned jsonObject by calling
	jsonObjectFree().
*/
jsonObject* osrfConfigGetValueObject(osrfConfig* cfg, const char* path, ...) {
	if(!path) return NULL;
	if(!cfg)
		cfg = osrfConfigDefault;
	if(!cfg) {
		osrfLogWarning( OSRF_LOG_MARK, "No Config object in osrfConfigGetValueObject()");
		return NULL;
	}

	VA_LIST_TO_STRING(path);
	if(cfg->configContext)
		return jsonObjectFindPath(cfg->config, "//%s%s", cfg->configContext, VA_BUF);
	else
		return jsonObjectFindPath(cfg->config, VA_BUF);
}
Пример #15
0
/**
	@brief Load a stored query.
	@param state Pointer to the query-building context.
	@param query_id ID of the query in query.stored_query.
	@return A pointer to the newly loaded StoredQ if successful, or NULL if not.

	The calling code is responsible for freeing the StoredQ by calling storedQFree().
*/
StoredQ* getStoredQuery( BuildSQLState* state, int query_id ) {
	if( !state )
		return NULL;

	// Check the stack to see if the current query is nested inside itself.  If it is, then
	// abort in order to avoid infinite recursion.  If it isn't, then add it to the stack.
	// (Make sure to pop it off the stack before returning.)
	if( searchIdStack( state->query_stack, query_id, NULL )) {
		osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
			"Infinite recursion detected; query # %d is nested within itself", query_id ));
		state->error = 1;
		return NULL;
	} else
		push_id( &state->query_stack, query_id, NULL );

	StoredQ* sq = NULL;
	dbi_result result = dbi_conn_queryf( state->dbhandle,
		"SELECT id, type, use_all, use_distinct, from_clause, where_clause, having_clause "
		"FROM query.stored_query WHERE id = %d;", query_id );
	if( result ) {
		if( dbi_result_first_row( result ) ) {
			sq = constructStoredQ( state, result );
			if( sq ) {
				PRINT( "Got a query row\n" );
				PRINT( "\tid: %d\n", sq->id );
				PRINT( "\ttype: %d\n", (int) sq->type );
				PRINT( "\tuse_all: %s\n", sq->use_all ? "true" : "false" );
				PRINT( "\tuse_distinct: %s\n", sq->use_distinct ? "true" : "false" );
			} else
				osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
					"Unable to build a query for id = %d", query_id ));
		} else {
			sqlAddMsg( state, "Stored query not found for id %d", query_id );
		}

		dbi_result_free( result );
	} else {
		const char* msg;
		int errnum = dbi_conn_error( state->dbhandle, &msg );
		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state, 
			"Unable to query query.stored_query table: #%d %s",
			errnum, msg ? msg : "No description available" ));
	}

	pop_id( &state->query_stack );
	return sq;
}
Пример #16
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;
}
Пример #17
0
/**
	@brief Translate a JSON array into an osrfList of osrfMessages.
	@param string The JSON string to be translated.
	@param list Pointer to an osrfList of osrfMessages (may be NULL)
	@return Pointer to an osrfList containing pointers to osrfMessages.

	The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.

	Translate each element of the JSON array into an osrfMessage, and store a pointer to the
	osrfMessage in an osrfList.

	If the @a list parameter is NULL, create a new osrfList (with osrfMessageFree() as the
	callback function for freeing items), populate it, and return a pointer to it.  Otherwise
	clear the osrfList provided and reuse it.

	When calling osrfMessageDeserialize repeatedly, a reasonable strategy is to pass a NULL
	for the @a list parameter on the first call, and pass the value returned from the first
	call on subsequent calls.

	The calling code is responsible for eventually freeing the returned osrfList by calling
	osrfListFree().
 */
osrfList* osrfMessageDeserialize( const char* string, osrfList* list ) {

	if( list )
		osrfListClear( list );

	if( ! string  || ! *string ) {
		if( ! list ) {
			list = osrfNewList( 1 );
			list->freeItem = (void(*)(void*)) osrfMessageFree;
		}
		return list;                   // No string?  Return empty list.
	}
	
	// Parse the JSON
	jsonObject* json = jsonParse(string);
	if(!json) {
		osrfLogWarning( OSRF_LOG_MARK,
				"osrfMessageDeserialize() unable to parse data: \n%s\n", string);
		if( ! list ) {
			list = osrfNewList( 1 );
			list->freeItem = (void(*)(void*)) osrfMessageFree;
		}
		return list;                   // Bad JSON?  Return empty list.
	}

	const unsigned int count = (int) json->size;
	if( ! list ) {
		// Create a right-sized osrfList
		list = osrfNewList( count );
		list->freeItem = (void(*)(void*)) osrfMessageFree;
	}

	// Traverse the JSON_ARRAY, turning each element into an osrfMessage
	int i;
	for( i = 0; i < count; ++i ) {

		const jsonObject* message = jsonObjectGetIndex( json, i );
		if( message && message->type != JSON_NULL &&
				  message->classname && !strcmp(message->classname, "osrfMessage" )) {
			osrfListPush( list, deserialize_one_message( message ) );
		}
	}

	jsonObjectFree( json );
	return list;
}
Пример #18
0
static Expression* getExpression( BuildSQLState* state, int id ) {
	
	// Check the stack to see if the current expression is nested inside itself.  If it is,
	// then abort in order to avoid infinite recursion.  If it isn't, then add it to the
	// stack.  (Make sure to pop it off the stack before returning.)
	if( searchIdStack( state->expr_stack, id, NULL )) {
		osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
			"Infinite recursion detected; expression # %d is nested within itself", id ));
		state->error = 1;
		return NULL;
	} else
		push_id( &state->expr_stack, id, NULL );

		Expression* exp = NULL;
	dbi_result result = dbi_conn_queryf( state->dbhandle,
		"SELECT id, type, parenthesize, parent_expr, seq_no, literal, table_alias, "
		"column_name, left_operand, operator, right_operand, function_id, subquery, cast_type "
		"FROM query.expression WHERE id = %d;", id );
	if( result ) {
		if( dbi_result_first_row( result ) ) {
			exp = constructExpression( state, result );
			if( exp ) {
				PRINT( "Got an expression\n" );
				PRINT( "\tid = %d\n", exp->id );
				PRINT( "\ttype = %d\n", exp->type );
				PRINT( "\tparenthesize = %d\n", exp->parenthesize );
				PRINT( "\tcolumn_name = %s\n", exp->column_name ? exp->column_name : "(none)" );
			} else 
				osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
					"Unable to construct an Expression for id = %d", id ));
		}
	} else {
		const char* msg;
		int errnum = dbi_conn_error( state->dbhandle, &msg );
		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
			"Unable to query query.expression table: #%d %s",
			errnum, msg ? msg : "No description available" ));
	}

	pop_id( &state->expr_stack );
	return exp;
}
static int active_connection_count() {

    if (requests_in_flight) {

        time_t now = time(NULL);
        time_t difference = now - last_activity_time;

        if (difference >= max_request_wait_time) {
            osrfLogWarning(OSRF_LOG_MARK, 
                "%d In-flight request(s) took longer than %d seconds "
                "to complete.  Treating request as dead and moving on.",
                requests_in_flight, 
                max_request_wait_time
            );
            requests_in_flight = 0;
        }
    }

    return requests_in_flight;
}
Пример #20
0
/**
	@brief Copy XML text (outside of tags) into the appropriate buffer.
	@param session Pointer to the transport_session.
	@param ch Pointer to the text to be copied.
	@param len How many characters to be copied.

	The XML parser calls this as a callback when it finds text outside of a tag,  We check
	the state machine to figure out what kind of text it is, and then append it to the
	corresponding buffer.
*/
static void characterHandler(
		void *session, const xmlChar *ch, int len) {

	const char* p = (const char*) ch;

	transport_session* ses = (transport_session*) session;
	if( ! ses ) { return; }

	jabber_machine* machine = ses->state_machine;

	/* set the various message parts */
	if( machine->in_message ) {

		if( machine->in_message_body ) {
			buffer_add_n( ses->body_buffer, p, len );
		}

		if( machine->in_subject ) {
			buffer_add_n( ses->subject_buffer, p, len );
		}

		if( machine->in_thread ) {
			buffer_add_n( ses->thread_buffer, p, len );
		}
	}

	/* set the presence status */
	if( machine->in_presence && ses->state_machine->in_status ) {
		buffer_add_n( ses->status_buffer, p, len );
	}

	if( machine->in_error ) {
		char msg[ len + 1 ];
		strncpy( msg, p, len );
		msg[ len ] = '\0';
		osrfLogWarning( OSRF_LOG_MARK,
			"Text of error message received from Jabber: %s", msg );
	}
}
Пример #21
0
/**
	@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;
}
Пример #22
0
/**
	@brief Search for one or more values in a configuration, specified by a path.
	@param cfg (Optional) The configuration to search, or NULL for the default configuration.
	@param arr Pointer to an osrfStringArray to be populated with values.
	@param path A printf-style format string representing the search path.  Subsequent
	parameters, if any, are inserted into the format string to form the search path.
	@return The number of values loaded into the osrfStringArray.

	Search the configuration for values specified by the search path.  Load any values
	found (either strings or numbers) into an existing osrfStringArray supplied by the
	calling code.

	Make no effort to delete any strings that may already be in the osrfStringArray.
	Ordinarily the calling code should ensure that the osrfStringArray is empty when passed.

	If the configuration includes a configContext, then prepend it to the path, like so:
	"//<configContext><path>".  Hence the path should begin with a slash to separate it from
	the context.  Search for the resulting effective path at any level within the
	configuration.

	If the configuration does @em not include a configContext, then start the search at the
	root of the configuration.  In this case the path should @em not begin with a slash.
*/
int osrfConfigGetValueList(const osrfConfig* cfg, osrfStringArray* arr,
		const char* path, ...) {

	if(!arr || !path) return 0;
	if(!cfg)
		cfg = osrfConfigDefault;
	if(!cfg) {
		osrfLogWarning( OSRF_LOG_MARK, "No Config object!");
		return -1;
	}

	VA_LIST_TO_STRING(path);

	jsonObject* obj;
	if(cfg->configContext) {
		obj = jsonObjectFindPath( cfg->config, "//%s%s", cfg->configContext, VA_BUF);
	} else {
		obj = jsonObjectFindPath( cfg->config, VA_BUF);
	}

	int count = 0;

	if(obj && obj->type == JSON_ARRAY ) {

		int i;
		for( i = 0; i < obj->size; i++ ) {

			const char* val = jsonObjectGetString( jsonObjectGetIndex(obj, i) );
			if(val) {
				count++;
				osrfStringArrayAdd(arr, val);
			}
		}
	}

	jsonObjectFree(obj);
	return count;
}
Пример #23
0
/**
	@brief Search a configuration for a specified value.
	@param cfg (Optional) The configuration to search, or NULL for the default configuration.
	@param path A printf-style format string representing the search path.  Subsequent
	parameters, if any, are inserted into the format string to form the search path.
	@return A pointer to a newly allocated string containing the value, if found; otherwise
	NULL.

	Search for a value in the configuration, as specified by the search path.

	If cfg is NULL, search the default configuration; otherwise search the configuration
	specified.

	If the configuration includes a configContext, then prepend it to the path, like so:
	"//<configContext><path>".  Hence the path should begin with a slash to separate it from
	the context.  Search for the resulting effective path at any level within the
	configuration.

	If the configuration does @em not include a configContext, then start the search at the
	root of the configuration.  In this case the path should @em not begin with a slash.

	If the configuration contains more than one entry at locations matching the effective
	search path, return NULL.

	Return numeric values as numeric strings.

	The calling code is responsible for freeing the returned string by calling free().
*/
char* osrfConfigGetValue(const osrfConfig* cfg, const char* path, ...) {
	if(!path) return NULL;
	if(!cfg)
		cfg = osrfConfigDefault;
	if(!cfg) {
		osrfLogWarning( OSRF_LOG_MARK, "No Config object in osrfConfigGetValue()");
		return NULL;
	}

	VA_LIST_TO_STRING(path);
	jsonObject* obj;

	if(cfg->configContext) {
		jsonObject* outer_obj =
			jsonObjectFindPath(cfg->config, "//%s%s", cfg->configContext, VA_BUF);
		obj = jsonObjectExtractIndex( outer_obj, 0 );
		jsonObjectFree( outer_obj );
	} else
		obj = jsonObjectFindPath( cfg->config, VA_BUF );

	char* val = jsonObjectToSimpleString(obj);
	jsonObjectFree(obj);
	return val;
}
Пример #24
0
/**
 * 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;
}
Пример #25
0
/**
	@brief Log a warning from the XML parser.
	@param session Pointer to a transport_session, cast to a void pointer (not used).
	@param msg Pointer to a printf-style format string.  Subsequent messages, if any, are
		formatted and inserted into the expanded string.

	The XML parser calls this function when it wants to warn about something in the XML.
*/
static void  parseWarningHandler( void *session, const char* msg, ... ) {
	VA_LIST_TO_STRING(msg);
	osrfLogWarning( OSRF_LOG_MARK, VA_BUF );
}
Пример #26
0
/**
	@brief React to the closing of an XML tag.
	@param session Pointer to a transport_session, cast to a void pointer.
	@param name Pointer to the name of the tag that is closing.

	See what kind of tag is closing, and respond accordingly.
*/
static void endElementHandler( void *session, const xmlChar *name) {
	transport_session* ses = (transport_session*) session;
	if( ! ses ) { return; }

	// Bypass a level of indirection, since we'll examine the machine repeatedly:
	jabber_machine* machine = ses->state_machine;

	if( machine->in_message && strcmp( (char*) name, "message" ) == 0 ) {

		/* pass off the message info the callback */
		if( ses->message_callback ) {

			transport_message* msg =  message_init(
				OSRF_BUFFER_C_STR( ses->body_buffer ),
				OSRF_BUFFER_C_STR( ses->subject_buffer ),
				OSRF_BUFFER_C_STR( ses->thread_buffer ),
				OSRF_BUFFER_C_STR( ses->recipient_buffer ),
				OSRF_BUFFER_C_STR( ses->from_buffer ) );

			message_set_router_info( msg,
				ses->router_from_buffer->buf,
				ses->router_to_buffer->buf,
				ses->router_class_buffer->buf,
				ses->router_command_buffer->buf,
				ses->router_broadcast );

			message_set_osrf_xid( msg, ses->osrf_xid_buffer->buf );

			if( ses->message_error_type->n_used > 0 ) {
				set_msg_error( msg, ses->message_error_type->buf, ses->message_error_code );
			}

			if( msg == NULL ) { return; }
			ses->message_callback( ses->user_data, msg );
		}

		machine->in_message = 0;
		reset_session_buffers( session );
		return;
	}

	if( machine->in_message_body && strcmp( (const char*) name, "body" ) == 0 ) {
		machine->in_message_body = 0;
		return;
	}

	if( machine->in_subject && strcmp( (const char*) name, "subject" ) == 0 ) {
		machine->in_subject = 0;
		return;
	}

	if( machine->in_thread && strcmp( (const char*) name, "thread" ) == 0 ) {
		machine->in_thread = 0;
		return;
	}

	if( machine->in_iq && strcmp( (const char*) name, "iq" ) == 0 ) {
		machine->in_iq = 0;
		if( ses->message_error_code > 0 ) {
			if( 401 == ses->message_error_code )
				osrfLogWarning( OSRF_LOG_MARK, "Error 401 in IQ packet: not authorized" );
			else
				osrfLogWarning( OSRF_LOG_MARK, "Error in IQ packet: code %d",
						ses->message_error_code );
		}
		reset_session_buffers( session );
		return;
	}

	if( machine->in_presence && strcmp( (const char*) name, "presence" ) == 0 ) {
		machine->in_presence = 0;
		/*
		if( ses->presence_callback ) {
			// call the callback with the status, etc.
		}
		*/
		reset_session_buffers( session );
		return;
	}

	if( machine->in_status && strcmp( (const char*) name, "status" ) == 0 ) {
		machine->in_status = 0;
		return;
	}

	if( machine->in_message_error && strcmp( (const char*) name, "error" ) == 0 ) {
		machine->in_message_error = 0;
		return;
	}

	if( machine->in_error && strcmp( (const char*) name, "stream:error" ) == 0 ) {
		machine->in_error = 0;
		return;
	}
}
Пример #27
0
/**
	@brief Respond to the beginning of an XML element.
	@param session Pointer to the transport_session, cast to a void pointer.
	@param name Name of the XML element.
	@param atts Pointer to a ragged array containing attributes and values.

	The XML parser calls this when it sees the beginning of an XML element.  We note what
	element it is by setting the corresponding switch in the state machine, and grab whatever
	attributes we expect to find.
*/
static void startElementHandler(
	void *session, const xmlChar *name, const xmlChar **atts) {

	transport_session* ses = (transport_session*) session;
	if( ! ses ) { return; }


	if( strcmp( (char*) name, "message" ) == 0 ) {
		ses->state_machine->in_message = 1;
		buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
		buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
		buffer_add( ses->router_from_buffer, get_xml_attr( atts, "router_from" ) );
		buffer_add( ses->osrf_xid_buffer, get_xml_attr( atts, "osrf_xid" ) );
		buffer_add( ses->router_to_buffer, get_xml_attr( atts, "router_to" ) );
		buffer_add( ses->router_class_buffer, get_xml_attr( atts, "router_class" ) );
		buffer_add( ses->router_command_buffer, get_xml_attr( atts, "router_command" ) );
		const char* broadcast = get_xml_attr( atts, "broadcast" );
		if( broadcast )
			ses->router_broadcast = atoi( broadcast );

		return;
	}

	if( ses->state_machine->in_message ) {

		if( strcmp( (char*) name, "body" ) == 0 ) {
			ses->state_machine->in_message_body = 1;
			return;
		}

		if( strcmp( (char*) name, "subject" ) == 0 ) {
			ses->state_machine->in_subject = 1;
			return;
		}

		if( strcmp( (char*) name, "thread" ) == 0 ) {
			ses->state_machine->in_thread = 1;
			return;
		}

	}

	if( strcmp( (char*) name, "presence" ) == 0 ) {
		ses->state_machine->in_presence = 1;
		buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
		buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
		return;
	}

	if( strcmp( (char*) name, "status" ) == 0 ) {
		ses->state_machine->in_status = 1;
		return;
	}


	if( strcmp( (char*) name, "stream:error" ) == 0 ) {
		ses->state_machine->in_error = 1;
		ses->state_machine->connected = 0;
		osrfLogWarning(  OSRF_LOG_MARK, "Received <stream:error> message from Jabber server" );
		return;
	}


	/* first server response from a connect attempt */
	if( strcmp( (char*) name, "stream:stream" ) == 0 ) {
		if( ses->state_machine->connecting == CONNECTING_1 ) {
			ses->state_machine->connecting = CONNECTING_2;
			buffer_add( ses->session_id, get_xml_attr(atts, "id") );
		}
		return;
	}

	if( strcmp( (char*) name, "handshake" ) == 0 ) {
		ses->state_machine->connected = 1;
		ses->state_machine->connecting = 0;
		return;
	}


	if( strcmp( (char*) name, "error" ) == 0 ) {
		ses->state_machine->in_message_error = 1;
		buffer_add( ses->message_error_type, get_xml_attr( atts, "type" ) );
		ses->message_error_code = atoi( get_xml_attr( atts, "code" ) );
		osrfLogInfo( OSRF_LOG_MARK, "Received <error> message with type %s and code %d",
			OSRF_BUFFER_C_STR( ses->message_error_type ), ses->message_error_code );
		return;
	}

	if( strcmp( (char*) name, "iq" ) == 0 ) {
		ses->state_machine->in_iq = 1;

		const char* type = get_xml_attr(atts, "type");

		if( strcmp( type, "result") == 0
				&& ses->state_machine->connecting == CONNECTING_2 ) {
			ses->state_machine->connected = 1;
			ses->state_machine->connecting = 0;
			return;
		}

		if( strcmp( type, "error") == 0 ) {
			osrfLogWarning( OSRF_LOG_MARK,  "Error connecting to jabber" );
			return;
		}
	}
}
Пример #28
0
/**
	@brief Connect to the Jabber server as a client and open a Jabber session.
	@param session Pointer to a transport_session.
	@param username Jabber user name.
	@param password Jabber password.
	@param resource name of Jabber resource.
	@param connect_timeout Timeout interval, in seconds, for receiving data (see notes).
	@param auth_type An enum: either AUTH_PLAIN or AUTH_DIGEST (see notes).
	@return 1 if successful, or 0 upon error.

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

	The value of @a connect_timeout applies to each of two stages in the logon procedure.
	Hence the logon may take up to twice the amount of time indicated.

	If we connect as a Jabber component, we send the password as an SHA1 hash.  Otherwise
	we look at the @a auth_type.  If it's AUTH_PLAIN, we send the password as plaintext; if
	it's AUTH_DIGEST, we send it as a hash.

	At this writing, we only use AUTH_DIGEST.
*/
int session_connect( transport_session* session,
		const char* username, const char* password,
		const char* resource, int connect_timeout, enum TRANSPORT_AUTH_TYPE auth_type ) {

	// Sanity checks
	if( ! session ) {
		osrfLogWarning(OSRF_LOG_MARK, "session is null in session_connect()" );
		return 0;
	}

	if( session->sock_id != 0 ) {
		osrfLogWarning(OSRF_LOG_MARK, "transport session is already open, on socket %d",
			session->sock_id );
		return 0;
	}

	// Open a client socket connecting to the Jabber server
	if(session->port > 0) {   // use TCP
		session->sock_id = socket_open_tcp_client(
				session->sock_mgr, session->port, session->server );
		if( session->sock_id <= 0 ) {
			session->sock_id = 0;
			return 0;
		}
	} else if(session->unix_path != NULL) {  // use UNIX domain
		session->sock_id = socket_open_unix_client( session->sock_mgr, session->unix_path );
		if( session->sock_id <= 0 ) {
			session->sock_id = 0;
			return 0;
		}
	}
	else {
		osrfLogWarning( OSRF_LOG_MARK, "Can't open session: no port or unix path" );
		return 0;
	}

	const char* server = session->server;
	int size1 = 0;
	int size2 = 0;

	/*
	We establish the session in two stages.

	First we establish an XMPP stream with the Jabber server by sending an opening tag of
	stream:stream.  This is not a complete XML document.  We don't send the corresponding
	closing tag until we close the session.

	If the Jabber server responds by sending an opening stream:stream tag of its own, we can
	proceed to the second stage by sending a second stanza to log in.  This stanza is an XML
	element with the tag <handshake> (if we're a Jabber component) or <iq> (if we're not),
	enclosing the username, password, and resource.

	If all goes well, the Jabber server responds with a <handshake> or <iq> stanza of its own,
	and we're logged in.

	If authentication fails, the Jabber server returns a <stream:error> (if we used a <handshake>
	or an <iq> of type "error" (if we used an <iq>).
	*/
	if( session->component ) {

		/* the first Jabber connect stanza */
		char our_hostname[HOST_NAME_MAX + 1] = "";
		gethostname(our_hostname, sizeof(our_hostname) );
		our_hostname[HOST_NAME_MAX] = '\0';
		size1 = 150 + strlen( username ) + strlen( our_hostname );
		char stanza1[ size1 ];
		snprintf( stanza1, sizeof(stanza1),
				"<stream:stream version='1.0' xmlns:stream='http://etherx.jabber.org/streams' "
				"xmlns='jabber:component:accept' to='%s' from='%s' xml:lang='en'>",
				username, our_hostname );

		/* send the first stanze */
		session->state_machine->connecting = CONNECTING_1;

		if( socket_send( session->sock_id, stanza1 ) ) {
			osrfLogWarning(OSRF_LOG_MARK, "error sending");
			socket_disconnect( session->sock_mgr, session->sock_id );
			session->sock_id = 0;
			return 0;
		}

		/* wait for reply */
		socket_wait(session->sock_mgr, connect_timeout, session->sock_id);

		/* server acknowledges our existence, now see if we can login */
		if( session->state_machine->connecting == CONNECTING_2 ) {

			int ss = buffer_length( session->session_id ) + strlen( password ) + 5;
			char hashstuff[ss];
			snprintf( hashstuff, sizeof(hashstuff), "%s%s",
					OSRF_BUFFER_C_STR( session->session_id ), password );

			char* hash = shahash( hashstuff );
			size2 = 100 + strlen( hash );
			char stanza2[ size2 ];
			snprintf( stanza2, sizeof(stanza2), "<handshake>%s</handshake>", hash );

			if( socket_send( session->sock_id, stanza2 )  ) {
				osrfLogWarning(OSRF_LOG_MARK, "error sending");
				socket_disconnect( session->sock_mgr, session->sock_id );
				session->sock_id = 0;
				return 0;
			}
		}

	} else { /* we're not a component */

		/* the first Jabber connect stanza */
		size1 = 100 + strlen( server );
		char stanza1[ size1 ];
		snprintf( stanza1, sizeof(stanza1),
				"<stream:stream to='%s' xmlns='jabber:client' "
				"xmlns:stream='http://etherx.jabber.org/streams'>",
			server );

		/* send the first stanze */
		session->state_machine->connecting = CONNECTING_1;
		if( socket_send( session->sock_id, stanza1 ) ) {
			osrfLogWarning(OSRF_LOG_MARK, "error sending");
			socket_disconnect( session->sock_mgr, session->sock_id );
			session->sock_id = 0;
			return 0;
		}


		/* wait for reply */
		socket_wait( session->sock_mgr, connect_timeout, session->sock_id ); /* make the timeout smarter XXX */

		if( auth_type == AUTH_PLAIN ) {

			/* the second jabber connect stanza including login info*/
			size2 = 150 + strlen( username ) + strlen( password ) + strlen( resource );
			char stanza2[ size2 ];
			snprintf( stanza2, sizeof(stanza2),
					"<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'>"
					"<username>%s</username><password>%s</password><resource>%s</resource></query></iq>",
					username, password, resource );

			/* server acknowledges our existence, now see if we can login */
			if( session->state_machine->connecting == CONNECTING_2 ) {
				if( socket_send( session->sock_id, stanza2 )  ) {
					osrfLogWarning(OSRF_LOG_MARK, "error sending");
					socket_disconnect( session->sock_mgr, session->sock_id );
					session->sock_id = 0;
					return 0;
				}
			}

		} else if( auth_type == AUTH_DIGEST ) {

			int ss = buffer_length( session->session_id ) + strlen( password ) + 5;
			char hashstuff[ss];
			snprintf( hashstuff, sizeof(hashstuff), "%s%s", OSRF_BUFFER_C_STR( session->session_id ), password );

			char* hash = shahash( hashstuff );

			/* the second jabber connect stanza including login info */
			size2 = 150 + strlen( username ) + strlen( hash ) + strlen(resource);
			char stanza2[ size2 ];
			snprintf( stanza2, sizeof(stanza2),
					"<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'>"
					"<username>%s</username><digest>%s</digest><resource>%s</resource></query></iq>",
					username, hash, resource );

			/* server acknowledges our existence, now see if we can login */
			if( session->state_machine->connecting == CONNECTING_2 ) {
				if( socket_send( session->sock_id, stanza2 )  ) {
					osrfLogWarning(OSRF_LOG_MARK, "error sending");
					socket_disconnect( session->sock_mgr, session->sock_id );
					session->sock_id = 0;
					return 0;
				}
			}

		} else {
			osrfLogWarning(OSRF_LOG_MARK, "Invalid auth_type parameter: %d",
					(int) auth_type );
			socket_disconnect( session->sock_mgr, session->sock_id );
			session->sock_id = 0;
			return 0;
		}

	} // not component


	/* wait for reply to login request */
	socket_wait( session->sock_mgr, connect_timeout, session->sock_id );

	if( session->state_machine->connected ) {
		/* yar! */
		return 1;
	} else {
		socket_disconnect( session->sock_mgr, session->sock_id );
		session->sock_id = 0;
		return 0;
	}
}
Пример #29
0
/**
	@brief Determine the login timeout.
	@param userObj Pointer to an object describing the user.
	@param type Pointer to one of four possible character strings identifying the login type.
	@param orgloc Org unit to use for settings lookups (negative or zero means unspecified)
	@return The length of the timeout, in seconds.

	The default timeout value comes from the configuration file, and depends on the
	login type.

	The default may be overridden by a corresponding org unit setting.  The @a orgloc
	parameter says what org unit to use for the lookup.  If @a orgloc <= 0, or if the
	lookup for @a orgloc yields no result, we look up the setting for the user's home org unit
	instead (except that if it's the same as @a orgloc we don't bother repeating the lookup).

	Whether defined in the config file or in an org unit setting, a timeout value may be
	expressed as a raw number (i.e. all digits, possibly with leading and/or trailing white
	space) or as an interval string to be translated into seconds by PostgreSQL.
*/
static long oilsAuthGetTimeout( const jsonObject* userObj, const char* type, int orgloc ) {

	if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */

		jsonObject* value_obj;

		value_obj = osrf_settings_host_value_object(
			"/apps/open-ils.auth/app_settings/default_timeout/opac" );
		_oilsAuthOPACTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
		jsonObjectFree(value_obj);
		if( -1 == _oilsAuthOPACTimeout ) {
			osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for OPAC logins" );
			_oilsAuthOPACTimeout = 0;
		}

		value_obj = osrf_settings_host_value_object(
			"/apps/open-ils.auth/app_settings/default_timeout/staff" );
		_oilsAuthStaffTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
		jsonObjectFree(value_obj);
		if( -1 == _oilsAuthStaffTimeout ) {
			osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for staff logins" );
			_oilsAuthStaffTimeout = 0;
		}

		value_obj = osrf_settings_host_value_object(
			"/apps/open-ils.auth/app_settings/default_timeout/temp" );
		_oilsAuthOverrideTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
		jsonObjectFree(value_obj);
		if( -1 == _oilsAuthOverrideTimeout ) {
			osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for temp logins" );
			_oilsAuthOverrideTimeout = 0;
		}

		value_obj = osrf_settings_host_value_object(
			"/apps/open-ils.auth/app_settings/default_timeout/persist" );
		_oilsAuthPersistTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
		jsonObjectFree(value_obj);
		if( -1 == _oilsAuthPersistTimeout ) {
			osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for persist logins" );
			_oilsAuthPersistTimeout = 0;
		}

		osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: "
			"opac => %ld : staff => %ld : temp => %ld : persist => %ld",
			_oilsAuthOPACTimeout, _oilsAuthStaffTimeout,
			_oilsAuthOverrideTimeout, _oilsAuthPersistTimeout );
	}

	int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
	if(orgloc < 1)
		orgloc = home_ou;

	char* setting = NULL;
	long default_timeout = 0;

	if( !strcmp( type, OILS_AUTH_OPAC )) {
		setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
		default_timeout = _oilsAuthOPACTimeout;
	} else if( !strcmp( type, OILS_AUTH_STAFF )) {
		setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
		default_timeout = _oilsAuthStaffTimeout;
	} else if( !strcmp( type, OILS_AUTH_TEMP )) {
		setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
		default_timeout = _oilsAuthOverrideTimeout;
	} else if( !strcmp( type, OILS_AUTH_PERSIST )) {
		setting = OILS_ORG_SETTING_PERSIST_TIMEOUT;
		default_timeout = _oilsAuthPersistTimeout;
	}

	// Get the org unit setting, if there is one.
	char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
	if(!timeout) {
		if( orgloc != home_ou ) {
			osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
				"trying home_ou %d", orgloc, home_ou );
			timeout = oilsUtilsFetchOrgSetting( home_ou, setting );
		}
	}

	if(!timeout)
		return default_timeout;   // No override from org unit setting

	// Translate the org unit setting to a number
	long t;
	if( !*timeout ) {
		osrfLogWarning( OSRF_LOG_MARK,
			"Timeout org unit setting is an empty string for %s login; using default",
			timeout, type );
		t = default_timeout;
	} else {
		// Treat timeout string as an interval, and convert it to seconds
		t = oilsUtilsIntervalToSeconds( timeout );
		if( -1 == t ) {
			// Unable to convert; possibly an invalid interval string
			osrfLogError( OSRF_LOG_MARK,
				"Unable to convert timeout interval \"%s\" for %s login; using default",
				timeout, type );
			t = default_timeout;
		}
	}

	free(timeout);
	return t;
}
Пример #30
0
/**
	@brief Convert a string to a number representing a time interval in seconds.
	@param interval Pointer to string, e.g. "420" or "2 weeks".
	@return If successful, the number of seconds that the string represents; otherwise -1.

	If the string is all digits (apart from any leading or trailing white space), convert
	it directly.  Otherwise pass it to PostgreSQL for translation.

	The result is the same as if we were to pass every string to PostgreSQL, except that,
	depending on the value of LONG_MAX, we return values for some strings that represent
	intervals too long for PostgreSQL to represent (i.e. more than 2147483647 seconds).

	WARNING: a valid interval of -1 second will be indistinguishable from an error.  If
	such an interval is a plausible possibility, don't use this function.
*/
long oilsUtilsIntervalToSeconds( const char* s ) {

	if( !s ) {
		osrfLogWarning( OSRF_LOG_MARK, "String to be converted is NULL" );
		return -1;
	}

	// Skip leading white space
	while( isspace( (unsigned char) *s ))
		++s;

	if( '\0' == *s ) {
		osrfLogWarning( OSRF_LOG_MARK, "String to be converted is empty or all white space" );
		return -1;
	}

	// See if the string is a raw number, i.e. all digits
	// (apart from any leading or trailing white space)

	const char* p = s;   // For traversing and examining the remaining string
	if( isdigit( (unsigned char) *p )) {
		// Looks like a number so far...skip over the digits
		do {
			++p;
		} while( isdigit( (unsigned char) *p ));
		// Skip over any following white space
		while( isspace( (unsigned char) *p ))
			++p;
		if( '\0' == *p ) {
			// This string is a raw number.  Convert it directly.
			long n = strtol( s, NULL, 10 );
			if( LONG_MAX == n ) {
				// numeric overflow
				osrfLogWarning( OSRF_LOG_MARK,
					"String \"%s\"represents a number too big for a long", s );
				return -1;
			} else
				return n;
		}
	}

	// If we get to this point, the string is not all digits.  Pass it to PostgreSQL.

	// Build the query
	jsonObject* query_obj = jsonParseFmt(
		"{\"from\":[\"config.interval_to_seconds\",\"%s\"]}", s );

	// Execute the query
	jsonObject* result = oilsUtilsCStoreReq(
		"open-ils.cstore.json_query", query_obj );
	jsonObjectFree( query_obj );

	// Get the results
	const jsonObject* seconds_obj = jsonObjectGetKeyConst( result, "config.interval_to_seconds" );
	long seconds = -1;
	if( seconds_obj && JSON_NUMBER == seconds_obj->type )
		seconds = (long) jsonObjectGetNumber( seconds_obj );
	else
		osrfLogError( OSRF_LOG_MARK,
			"Error calling json_query to convert \"%s\" to seconds", s );

	jsonObjectFree( result );
	return seconds;
}