/** @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; }
/** @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; }
/** @brief Initialize the application by registering functions for method calls. @return Zero on success, 1 on error. */ int osrfAppInitialize() { osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Internal Server..."); /* load and parse the IDL */ /* return non-zero to indicate error */ if (!oilsInitIDL(NULL)) return 1; osrfAppRegisterMethod( MODULENAME, "open-ils.auth_internal.session.create", "oilsAuthInternalCreateSession", "Adds a user to the authentication cache to indicate " "the user is authenticated", 1, 0 ); osrfAppRegisterMethod( MODULENAME, "open-ils.auth_internal.user.validate", "oilsAuthInternalValidate", "Determines whether a user should be allowed to login. " "Returns SUCCESS oilsEvent when the user is valid, otherwise " "returns a non-SUCCESS oilsEvent object", 1, 0 ); return 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; }
osrfHash* oilsInitIDL(const char* idl_filename) { char* freeable_filename = NULL; const char* filename; if(idl_filename) filename = idl_filename; else { freeable_filename = osrf_settings_host_value("/IDL"); filename = freeable_filename; } if (!filename) { osrfLogError(OSRF_LOG_MARK, "No settings config for '/IDL'"); return NULL; } osrfLogInfo(OSRF_LOG_MARK, "Parsing IDL %s", filename); if (!oilsIDLInit( filename )) { osrfLogError(OSRF_LOG_MARK, "Problem loading IDL file [%s]!", filename); if(freeable_filename) free(freeable_filename); return NULL; } if(freeable_filename) free(freeable_filename); return oilsIDL(); }
/** @brief Given a barcode, fetch the corresponding row from the actor.usr table, if any. @param name The barcode 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. Look up the barcode in actor.card. Follow a foreign key from there to get a row in actor.usr. The calling code is responsible for freeing the returned object by calling jsonObjectFree(). */ jsonObject* oilsUtilsFetchUserByBarcode(const char* barcode) { if(!barcode) return NULL; osrfLogInfo(OSRF_LOG_MARK, "Fetching user by barcode %s", barcode); jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode); jsonObject* card = oilsUtilsQuickReq( "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params ); jsonObjectFree(params); if(!card) return NULL; // No such card // Get the user's id as a double char* usr = oilsFMGetString(card, "usr"); jsonObjectFree(card); if(!usr) return NULL; // No user id (shouldn't happen) double iusr = strtod(usr, NULL); free(usr); // Look up the user in actor.usr params = jsonParseFmt("[%f]", iusr); jsonObject* user = oilsUtilsQuickReq( "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params); jsonObjectFree(params); return user; }
/** @brief Implement the param_list method. @param ctx Pointer to the current method context. @return Zero if successful, or -1 if not. Provide a list of bind variables for a specified query, along with their various attributes. Method parameters: - query token, as previously returned by the .prepare method. Returns: A (possibly empty) JSON_HASH, keyed on the names of the bind variables. The data for each is another level of JSON_HASH with a fixed set of tags: - "label" - "type" - "description" - "default_value" (as a jsonObject) - "actual_value" (as a jsonObject) Any non-existent values are represented as JSON_NULLs. */ int doParamList( osrfMethodContext* ctx ) { if(osrfMethodVerifyContext( ctx )) { osrfLogError( OSRF_LOG_MARK, "Invalid method context" ); return -1; } // Get the query token from a method parameter 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 list of bind variables for token %s", token ); osrfAppRespondComplete( ctx, oilsBindVarList( query->state->bindvar_list ) ); return 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; }
/** @brief Implement the bind_param method. @param ctx Pointer to the current method context. @return Zero if successful, or -1 if not. Apply values to bind variables, overriding the defaults, if any. Method parameters: - query token, as previously returned by the .prepare method. - hash of bind variable values, keyed on bind variable names. Returns: Nothing. */ int doBindParam( osrfMethodContext* ctx ) { if(osrfMethodVerifyContext( ctx )) { osrfLogError( OSRF_LOG_MARK, "Invalid method context" ); return -1; } // Get the query token from a method parameter 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, "Binding parameter(s) for token %s", token ); jsonObject* bindings = jsonObjectGetIndex( ctx->params, 1 ); if( !bindings ) { osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, "No parameter provided for bind variable values" ); return -1; } else if( bindings->type != JSON_HASH ) { osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, "Invalid parameter for bind variable values: not a hash" ); return -1; } if( 0 == bindings->size ) { // No values to assign; we're done. osrfAppRespondComplete( ctx, NULL ); return 0; } osrfHash* bindvar_list = query->state->bindvar_list; if( !bindvar_list || osrfHashGetCount( bindvar_list ) == 0 ) { osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, "There are no bind variables to which to assign values" ); return -1; } if( oilsApplyBindValues( query->state, bindings )) { osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, "Unable to apply values to bind variables" ); return -1; } else { osrfAppRespondComplete( ctx, NULL ); return 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; }
/** @brief Initialize the application by registering functions for method calls. @return Zero in all cases. */ int osrfAppInitialize() { osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server..."); /* load and parse the IDL */ if (!oilsInitIDL(NULL)) return 1; /* return non-zero to indicate error */ osrfAppRegisterMethod( MODULENAME, "open-ils.auth.authenticate.init", "oilsAuthInit", "Start the authentication process and returns the intermediate authentication seed" " PARAMS( username )", 1, 0 ); osrfAppRegisterMethod( MODULENAME, "open-ils.auth.authenticate.complete", "oilsAuthComplete", "Completes the authentication process. Returns an object like so: " "{authtoken : <token>, authtime:<time>}, where authtoken is the login " "token and authtime is the number of seconds the session will be active" "PARAMS(username, md5sum( seed + md5sum( password ) ), type, org_id ) " "type can be one of 'opac','staff', or 'temp' and it defaults to 'staff' " "org_id is the location at which the login should be considered " "active for login timeout purposes", 1, 0 ); osrfAppRegisterMethod( MODULENAME, "open-ils.auth.session.retrieve", "oilsAuthSessionRetrieve", "Pass in the auth token and this retrieves the user object. The auth " "timeout is reset when this call is made " "Returns the user object (password blanked) for the given login session " "PARAMS( authToken )", 1, 0 ); osrfAppRegisterMethod( MODULENAME, "open-ils.auth.session.delete", "oilsAuthSessionDelete", "Destroys the given login session " "PARAMS( authToken )", 1, 0 ); osrfAppRegisterMethod( MODULENAME, "open-ils.auth.session.reset_timeout", "oilsAuthResetTimeout", "Resets the login timeout for the given session " "Returns an ILS Event with payload = session_timeout of session " "if found, otherwise returns the NO_SESSION event" "PARAMS( authToken )", 1, 0 ); return 0; }
/** @brief Call the exit handler for every application that has one. Normally a server's child process (a so-called "drone") calls this function just before shutting down. */ void osrfAppRunExitCode( void ) { osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash); osrfApplication* app; while( (app = osrfHashIteratorNext(itr)) ) { if( app->onExit ) { osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s", osrfHashIteratorKey(itr) ); app->onExit(); } } osrfHashIteratorFree(itr); }
static int oilsAuthLoginVerifyPassword(const osrfMethodContext* ctx, int user_id, const char* username, const char* password) { // build the cache key growing_buffer* gb = buffer_init(64); // free me buffer_add(gb, OILS_AUTH_CACHE_PRFX); buffer_add(gb, username); buffer_add(gb, OILS_AUTH_COUNT_SFFX); char* countkey = buffer_release(gb); // free me jsonObject* countobject = osrfCacheGetObject(countkey); // free me long failcount = 0; if (countobject) { failcount = (long) jsonObjectGetNumber(countobject); if (failcount >= _oilsAuthBlockCount) { // User is blocked. Don't waste any more CPU cycles on them. osrfLogInfo(OSRF_LOG_MARK, "oilsAuth found too many recent failures for '%s' : %i, " "forcing failure state.", username, failcount); jsonObjectFree(countobject); free(countkey); return 0; } } int verified = oilsAuthLoginCheckPassword(user_id, password); if (!verified) { // login failed. increment failure counter. failcount++; if (countobject) { // append to existing counter jsonObjectSetNumber(countobject, failcount); } else { // first failure, create a new counter countobject = jsonNewNumberObject((double) failcount); } osrfCachePutObject(countkey, countobject, _oilsAuthBlockTimeout); } jsonObjectFree(countobject); // NULL OK free(countkey); return verified; }
/** @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, int user_id, const char* identifier, const char* password, const char* nonce) { int verified = 0; // We won't be needing the seed again, remove it osrfCacheRemove("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce); // Ask the DB to verify the user's password. // Here, the password is md5(md5(password) + salt) jsonObject* params = jsonParseFmt( // free "{\"from\":[\"actor.verify_passwd\",%d,\"main\",\"%s\"]}", user_id, password); jsonObject* verify_obj = // free oilsUtilsCStoreReq("open-ils.cstore.json_query", params); jsonObjectFree(params); if (verify_obj) { verified = oilsUtilsIsDBTrue( jsonObjectGetString( jsonObjectGetKeyConst( verify_obj, "actor.verify_passwd"))); jsonObjectFree(verify_obj); } char* countkey = va_list_to_string("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, OILS_AUTH_COUNT_SFFX ); jsonObject* countobject = osrfCacheGetObject( countkey ); if(countobject) { long failcount = (long) jsonObjectGetNumber( countobject ); if(failcount >= _oilsAuthBlockCount) { verified = 0; osrfLogInfo(OSRF_LOG_MARK, "oilsAuth found too many recent failures for '%s' : %i, " "forcing failure state.", identifier, failcount); } if(verified == 0) { failcount += 1; } jsonObjectSetNumber( countobject, failcount ); osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout ); jsonObjectFree(countobject); } free(countkey); return verified; }
/** @brief Run the application-specific child initialization function for a given application. @param appname Name of the application. @return Zero if successful, or if the application has no child initialization function; -1 if the application is not registered, or if the function returns non-zero. The child initialization function must be named "osrfAppChildInit" within the shared object library. It initializes a drone process of a server. */ int osrfAppRunChildInit(const char* appname) { osrfApplication* app = _osrfAppFindApplication(appname); if(!app) return -1; char* error; int ret; int (*childInit) (void); *(void**) (&childInit) = dlsym(app->handle, "osrfAppChildInit"); if( (error = dlerror()) != NULL ) { osrfLogInfo( OSRF_LOG_MARK, "No child init defined for app %s : %s", appname, error); return 0; } if( (ret = (*childInit)()) ) { osrfLogError(OSRF_LOG_MARK, "App %s child init failed", appname); return -1; } osrfLogInfo(OSRF_LOG_MARK, "%s child init succeeded", appname); return 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; } }
/** @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); }
static oilsEvent* oilsAuthVerifyWorkstation( const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) { osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws); jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws); if(!workstation || workstation->type == JSON_NULL) { jsonObjectFree(workstation); return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND"); } long wsid = oilsFMGetObjectId(workstation); LONG_TO_STRING(wsid); char* orgid = oilsFMGetString(workstation, "owning_lib"); oilsFMSetString(userObj, "wsid", LONGSTR); oilsFMSetString(userObj, "ws_ou", orgid); free(orgid); jsonObjectFree(workstation); return NULL; }
/** * Clear the session cache, release the session pool */ void CALLBACK on_disconnect_handler( void *data, const WebSocketServer *server) { // if the threads wake up during disconnect, this tells // them to go back to sleep. trans->client_connected = 0; request_rec *r = server->request(server); osrfLogInfo(OSRF_LOG_MARK, "WS disconnect from %s", get_client_ip(r)); // Clear any lingering session data // NOTE: we could apr_pool_destroy the stateful_session_pool to truly free // the memory, but since there is a limit to the size of the pool // (max_concurrent_sessions), the memory cannot grow unbounded, // so there's no need. apr_hash_clear(trans->stateful_session_cache); apr_pool_clear(trans->stateful_session_pool); }
/** @brief Initialize a server drone. @return Zero if successful, -1 if not. Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * " query to get the datatype of each column. Record the datatypes in the loaded IDL. This function is called by a server drone shortly after it is spawned by the listener. */ int osrfAppChildInit( void ) { dbhandle = oilsConnectDB( modulename ); if( !dbhandle ) return -1; else { oilsSetDBConnection( dbhandle ); osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", modulename ); // Apply datatypes from database to the fields in the IDL //if( oilsExtendIDL() ) { // osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" ); // return -1; //} //else return 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; }
static void osrf_json_gateway_child_init(apr_pool_t *p, server_rec *s) { char* cfg = osrf_json_gateway_config_file; char buf[32]; int t = time(NULL); snprintf(buf, sizeof(buf), "%d", t); if( ! osrfSystemBootstrapClientResc( cfg, CONFIG_CONTEXT, buf ) ) { ap_log_error( APLOG_MARK, APLOG_ERR, 0, s, "Unable to Bootstrap OpenSRF Client with config %s..", cfg); return; } bootstrapped = 1; osrfLogInfo(OSRF_LOG_MARK, "Bootstrapping gateway child for requests"); // when this pool is cleaned up, it means the child // process is going away. register some cleanup code // XXX causes us to disconnect even for clone()'d process cleanup (as in mod_cgi) //apr_pool_cleanup_register(p, NULL, child_exit, apr_pool_cleanup_null); }
/** * Create the per-client translator */ void* CALLBACK on_connect_handler(const WebSocketServer *server) { request_rec *r = server->request(server); if (!trans) { // first connection // connect to opensrf if (child_init(server) != APR_SUCCESS) return NULL; // build pools, thread data, and the translator if (build_startup_data(server) != APR_SUCCESS) return NULL; } const char* client_ip = get_client_ip(r); osrfLogInfo(OSRF_LOG_MARK, "WS connect from %s", client_ip); last_activity_time = time(NULL); trans->client_connected = 1; return trans; }
/** @brief Return a list of column names for the SELECT list. @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: An array of column names; unavailable names are represented as nulls. */ int doColumns( osrfMethodContext* ctx ) { if(osrfMethodVerifyContext( ctx )) { osrfLogError( OSRF_LOG_MARK, "Invalid method context" ); return -1; } // Get the query token from a method parameter 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, "Listing column names for token %s", token ); jsonObject* col_list = oilsGetColNames( query->state, query->query ); if( query->state->error ) { osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException", ctx->request, "Unable to get column names" ); if( query->state->panic ) { osrfLogError( OSRF_LOG_MARK, sqlAddMsg( query->state, "Database connection isn't working" )); osrfAppSessionPanic( ctx->session ); } return -1; } else { osrfAppRespondComplete( ctx, col_list ); return 0; } }
static int oilsAuthInitBarcodeHandler( osrfMethodContext* ctx, const char* barcode, const char* nonce) { osrfLogInfo(OSRF_LOG_MARK, "User logging in with barcode %s", barcode); int user_id = -1; jsonObject* resp = NULL; // free jsonObject* user_obj = oilsUtilsFetchUserByBarcode(ctx, barcode); // free if (user_obj && user_obj->type != JSON_NULL) user_id = oilsFMGetObjectId(user_obj); jsonObjectFree(user_obj); // NULL OK char* seed = oilsAuthBuildInitCache(user_id, barcode, "barcode", nonce); resp = jsonNewObject(seed); free(seed); osrfAppRespondComplete(ctx, resp); jsonObjectFree(resp); return 0; }
/** @brief Return a list of previously generated error messages for a specified query. @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 (possibly empty) array of strings, each one an error message generated during previous operations in connection with the specified query. */ int doMessages( osrfMethodContext* ctx ) { if(osrfMethodVerifyContext( ctx )) { osrfLogError( OSRF_LOG_MARK, "Invalid method context" ); return -1; } // Get the query token from a method parameter 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 messages for token %s", token ); jsonObject* msgs = jsonNewObjectType( JSON_ARRAY ); const osrfStringArray* error_msgs = query->state->error_msgs; int i; for( i = 0; i < error_msgs->size; ++i ) { jsonObject* msg = jsonNewObject( osrfStringArrayGetString( error_msgs, i )); jsonObjectPush( msgs, msg ); } osrfAppRespondComplete( ctx, msgs ); return 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; }
/** @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; } } }
/** @brief Implement the "complete" method. @param ctx The method context. @return -1 upon error; zero if successful, and if a STATUS message has been sent to the client to indicate completion; a positive integer if successful but no such STATUS message has been sent. Method parameters: - a hash with some combination of the following elements: - "username" - "barcode" - "password" (hashed with the cached seed; not plaintext) - "type" - "org" - "workstation" The password is required. Either a username or a barcode must also be present. Return to client: Intermediate authentication seed. Validate the password, using the username if available, or the barcode if not. The user must be active, and not barred from logging on. The barcode, if used for authentication, must be active as well. The workstation, if specified, must be valid. Upon deciding whether to allow the logon, return a corresponding event to the client. */ int oilsAuthComplete( osrfMethodContext* ctx ) { OSRF_METHOD_VERIFY_CONTEXT(ctx); const jsonObject* args = jsonObjectGetIndex(ctx->params, 0); const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username")); const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password")); const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type")); int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org")); const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation")); const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode")); const char* ws = (workstation) ? workstation : ""; if( !type ) type = OILS_AUTH_STAFF; if( !( (uname || barcode) && password) ) { return osrfAppRequestRespondException( ctx->session, ctx->request, "username/barcode and password required for method: %s", ctx->method->name ); } oilsEvent* response = NULL; jsonObject* userObj = NULL; int card_active = 1; // boolean; assume active until proven otherwise // Fetch a row from the actor.usr table, by username if available, // or by barcode if not. if(uname) { userObj = oilsUtilsFetchUserByUsername( uname ); if( userObj && JSON_NULL == userObj->type ) { jsonObjectFree( userObj ); userObj = NULL; // username not found } } else if(barcode) { // Read from actor.card by barcode osrfLogInfo( OSRF_LOG_MARK, "Fetching user by barcode %s", barcode ); jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode); jsonObject* card = oilsUtilsQuickReq( "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params ); jsonObjectFree( params ); if( card && card->type != JSON_NULL ) { // Determine whether the card is active char* card_active_str = oilsFMGetString( card, "active" ); card_active = oilsUtilsIsDBTrue( card_active_str ); free( card_active_str ); // Look up the user who owns the card char* userid = oilsFMGetString( card, "usr" ); jsonObjectFree( card ); params = jsonParseFmt( "[%s]", userid ); free( userid ); userObj = oilsUtilsQuickReq( "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params ); jsonObjectFree( params ); if( userObj && JSON_NULL == userObj->type ) { // user not found (shouldn't happen, due to foreign key) jsonObjectFree( userObj ); userObj = NULL; } } } if(!userObj) { response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED ); osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", uname, (barcode ? barcode : "(none)"), ws ); osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); oilsEventFree(response); return 0; // No such user } // Such a user exists. Now see if he or she has the right credentials. int passOK = -1; if(uname) passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password ); else if (barcode) passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password ); if( passOK < 0 ) { jsonObjectFree(userObj); return passOK; } // See if the account is active char* active = oilsFMGetString(userObj, "active"); if( !oilsUtilsIsDBTrue(active) ) { if( passOK ) response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" ); else response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED ); osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); oilsEventFree(response); jsonObjectFree(userObj); free(active); return 0; } free(active); osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode ); if( !card_active ) { osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode ); response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" ); osrfAppRespondComplete( ctx, oilsEventToJSON( response ) ); oilsEventFree( response ); jsonObjectFree( userObj ); return 0; } // See if the user is even allowed to log in if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) { jsonObjectFree(userObj); return 0; } // If a workstation is defined, add the workstation info if( workstation != NULL ) { osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation); response = oilsAuthVerifyWorkstation( ctx, userObj, workstation ); if(response) { jsonObjectFree(userObj); osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); oilsEventFree(response); return 0; } } else { // Otherwise, use the home org as the workstation org on the user char* orgid = oilsFMGetString(userObj, "home_ou"); oilsFMSetString(userObj, "ws_ou", orgid); free(orgid); } char* freeable_uname = NULL; if(!uname) { uname = freeable_uname = oilsFMGetString( userObj, "usrname" ); } if( passOK ) { response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation ); } else { response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED ); osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", uname, (barcode ? barcode : "(none)"), ws ); } jsonObjectFree(userObj); osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); oilsEventFree(response); if(freeable_uname) free(freeable_uname); return 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; }