/** @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; }
int oilsAuthSessionDelete( osrfMethodContext* ctx ) { OSRF_METHOD_VERIFY_CONTEXT(ctx); const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) ); jsonObject* resp = NULL; if( authToken ) { osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken ); char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/ osrfCacheRemove(key); resp = jsonNewObject(authToken); /**/ free(key); } osrfAppRespondComplete( ctx, resp ); jsonObjectFree(resp); return 0; }
static int osrfHttpTranslatorCheckStatus(osrfHttpTranslator* trans, transport_message* msg) { osrfMessage* omsgList[MAX_MSGS_PER_PACKET]; int numMsgs = osrf_message_deserialize(msg->body, omsgList, MAX_MSGS_PER_PACKET); osrfLogDebug(OSRF_LOG_MARK, "parsed %d response messages", numMsgs); if(numMsgs == 0) return 0; osrfMessage* last = omsgList[numMsgs-1]; if(last->m_type == STATUS) { if(last->status_code == OSRF_STATUS_TIMEOUT) { osrfLogDebug(OSRF_LOG_MARK, "removing cached session on request timeout"); osrfCacheRemove(trans->thread); return 0; } // XXX hm, check for explicit status=COMPLETE message instead?? if(last->status_code != OSRF_STATUS_CONTINUE) trans->complete = 1; } return 1; }
static int osrfHttpTranslatorProcess(osrfHttpTranslator* trans) { if(trans->body == NULL) return HTTP_BAD_REQUEST; if(!osrfHttpTranslatorSetTo(trans)) return HTTP_BAD_REQUEST; char* jsonBody = osrfHttpTranslatorParseRequest(trans); if (NULL == jsonBody) return HTTP_BAD_REQUEST; while(client_recv(trans->handle, 0)) continue; // discard any old status messages in the recv queue // send the message to the recipient transport_message* tmsg = message_init( jsonBody, NULL, trans->thread, trans->recipient, NULL); message_set_osrf_xid(tmsg, osrfLogGetXid()); client_send_message(trans->handle, tmsg); message_free(tmsg); free(jsonBody); if(trans->disconnectOnly) { osrfLogDebug(OSRF_LOG_MARK, "exiting early on disconnect"); osrfCacheRemove(trans->thread); return OK; } // process the response from the opensrf service int firstWrite = 1; while(!trans->complete) { transport_message* msg = client_recv(trans->handle, trans->timeout); if(trans->handle->error) { osrfLogError(OSRF_LOG_MARK, "Transport error"); osrfCacheRemove(trans->thread); return HTTP_INTERNAL_SERVER_ERROR; } if(msg == NULL) return HTTP_GATEWAY_TIME_OUT; if(msg->is_error) { osrfLogError(OSRF_LOG_MARK, "XMPP message resulted in error code %d", msg->error_code); osrfCacheRemove(trans->thread); return HTTP_NOT_FOUND; } if(!osrfHttpTranslatorCheckStatus(trans, msg)) continue; if(firstWrite) { osrfHttpTranslatorInitHeaders(trans, msg); if(trans->connecting) osrfHttpTranslatorCacheSession(trans, msg->sender); firstWrite = 0; } if(trans->multipart) { osrfHttpTranslatorWriteChunk(trans, msg); if(trans->connectOnly) break; } else { if(!trans->messages) trans->messages = osrfNewList(); osrfListPush(trans->messages, msg->body); if(trans->complete || trans->connectOnly) { growing_buffer* buf = buffer_init(128); unsigned int i; OSRF_BUFFER_ADD(buf, osrfListGetIndex(trans->messages, 0)); for(i = 1; i < trans->messages->size; i++) { buffer_chomp(buf); // chomp off the closing array bracket char* body = osrfListGetIndex(trans->messages, i); char newbuf[strlen(body)]; sprintf(newbuf, "%s", body+1); // chomp off the opening array bracket OSRF_BUFFER_ADD_CHAR(buf, ','); OSRF_BUFFER_ADD(buf, newbuf); } ap_rputs(buf->buf, trans->apreq); buffer_free(buf); } } } if(trans->disconnecting) // DISCONNECT within a multi-message batch osrfCacheRemove(trans->thread); return OK; }
/** @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" - "agent" (what software/interface/3rd-party is making the request) - "nonce" optional login seed to differentiate logins using the same username. 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* identifier = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier")); 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* ewho = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent")); const char* nonce = jsonObjectGetString(jsonObjectGetKeyConst(args, "nonce")); const char* ws = (workstation) ? workstation : ""; if (!nonce) nonce = ""; // we no longer care how the identifier reaches us, // as long as we have one. if (!identifier) { if (uname) { identifier = uname; } else if (barcode) { identifier = barcode; } } if (!identifier) { return osrfAppRequestRespondException(ctx->session, ctx->request, "username/barcode and password required for method: %s", ctx->method->name); } osrfLogInfo(OSRF_LOG_MARK, "Patron completing authentication with identifer %s", identifier); /* Use __FILE__, harmless_line_number for creating * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid * giving away information about why an authentication attempt failed. */ int harmless_line_number = __LINE__; if( !type ) type = OILS_AUTH_STAFF; oilsEvent* response = NULL; // free jsonObject* userObj = NULL; // free char* cache_key = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce); jsonObject* cacheObj = osrfCacheGetObject(cache_key); // free if (!cacheObj) { return osrfAppRequestRespondException(ctx->session, ctx->request, "No authentication seed found. " "open-ils.auth.authenticate.init must be called first " " (check that memcached is running and can be connected to) " ); } int user_id = jsonObjectGetNumber( jsonObjectGetKeyConst(cacheObj, "user_id")); if (user_id == -1) { // User was not found during init. Clean up and exit early. response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED); osrfAppRespondComplete(ctx, oilsEventToJSON(response)); oilsEventFree(response); // frees event JSON osrfCacheRemove(cache_key); jsonObjectFree(cacheObj); return 0; } jsonObject* param = jsonNewNumberObject(user_id); // free userObj = oilsUtilsCStoreReqCtx( ctx, "open-ils.cstore.direct.actor.user.retrieve", param); jsonObjectFree(param); char* freeable_uname = NULL; if (!uname) { uname = freeable_uname = oilsFMGetString(userObj, "usrname"); } // See if the user is allowed to login. jsonObject* params = jsonNewObject(NULL); jsonObjectSetKey(params, "user_id", jsonNewNumberObject(oilsFMGetObjectId(userObj))); jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc)); jsonObjectSetKey(params, "login_type", jsonNewObject(type)); if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode)); jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test ctx, "open-ils.auth_internal", "open-ils.auth_internal.user.validate", params); jsonObjectFree(params); if (!authEvt) { // Something went seriously wrong. Get outta here before // we start segfaulting. jsonObjectFree(userObj); if(freeable_uname) free(freeable_uname); return -1; } const char* authEvtCode = jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode")); if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) { // Received the generic login failure event. osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", uname, (barcode ? barcode : "(none)"), ws); response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED); } int passOK = 0; if (!response) { // User exists and is not barred, etc. Test the password. passOK = oilsAuthVerifyPassword( ctx, user_id, identifier, password, nonce); if (!passOK) { // Password check failed. Return generic login failure. response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED); osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", uname, (barcode ? barcode : "(none)"), ws ); } } // Below here, we know the password check succeeded if no response // object is present. if (!response && ( !strcmp(authEvtCode, "PATRON_INACTIVE") || !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) { // Patron and/or card is inactive but the correct password // was provided. Alert the caller to the inactive-ness. response = oilsNewEvent2( OSRF_LOG_MARK, authEvtCode, jsonObjectGetKey(authEvt, "payload") // cloned within Event ); } if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) { // Validate API returned an unexpected non-success event. // To be safe, treat this as a generic login failure. response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED); } if (!response) { // password OK and no other events have prevented login completion. char* ewhat = "login"; if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) { response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS ); ewhat = "verify"; } else { response = oilsAuthHandleLoginOK( ctx, userObj, uname, type, orgloc, workstation); } oilsUtilsTrackUserActivity( ctx, oilsFMGetObjectId(userObj), ewho, ewhat, osrfAppSessionGetIngress() ); } // reply osrfAppRespondComplete(ctx, oilsEventToJSON(response)); // clean up oilsEventFree(response); jsonObjectFree(userObj); jsonObjectFree(authEvt); jsonObjectFree(cacheObj); if(freeable_uname) free(freeable_uname); return 0; }
/** @brief Verify the password received from the client. @param ctx The method context. @param userObj An object from the database, representing the user. @param password An obfuscated password received from the client. @return 1 if the password is valid; 0 if it isn't; or -1 upon error. (None of the so-called "passwords" used here are in plaintext. All have been passed through at least one layer of hashing to obfuscate them.) Take the password from the user object. Append it to the username seed from memcache, as stored previously by a call to the init method. Take an md5 hash of the result. Then compare this hash to the password received from the client. In order for the two to match, other than by dumb luck, the client had to construct the password it passed in the same way. That means it neded to know not only the original password (either hashed or plaintext), but also the seed. The latter requirement means that the client process needs either to be the same process that called the init method or to receive the seed from the process that did so. */ static int oilsAuthVerifyPassword( const osrfMethodContext* ctx, const jsonObject* userObj, const char* uname, const char* password, const char* nonce ) { // Get the username seed, as stored previously in memcache by the init method char* seed = osrfCacheGetString( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, nonce ); if(!seed) { return osrfAppRequestRespondException( ctx->session, ctx->request, "No authentication seed found. " "open-ils.auth.authenticate.init must be called first " " (check that memcached is running and can be connected to) " ); } // We won't be needing the seed again, remove it osrfCacheRemove( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, nonce ); // Get the hashed password from the user object char* realPassword = oilsFMGetString( userObj, "passwd" ); osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword); osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed ); // Concatenate them and take an MD5 hash of the result char* maskedPw = md5sum( "%s%s", seed, realPassword ); free(realPassword); free(seed); if( !maskedPw ) { // This happens only if md5sum() runs out of memory free( maskedPw ); return -1; // md5sum() ran out of memory } osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. " "Testing against provided password %s", maskedPw, password ); int ret = 0; if( !strcmp( maskedPw, password ) ) ret = 1; free(maskedPw); char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, OILS_AUTH_COUNT_SFFX ); jsonObject* countobject = osrfCacheGetObject( countkey ); if(countobject) { long failcount = (long) jsonObjectGetNumber( countobject ); if(failcount >= _oilsAuthBlockCount) { ret = 0; osrfLogInfo(OSRF_LOG_MARK, "oilsAuth found too many recent failures for '%s' : %i, forcing failure state.", uname, failcount); } if(ret == 0) { failcount += 1; } jsonObjectSetNumber( countobject, failcount ); osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout ); jsonObjectFree(countobject); } free(countkey); return ret; }