/** * If 'file' exists, attempt to delete it. Logs, but doesn't throw upon failure. * * @param file * @param userId only for logging. */ private void deleteFileIfNeeded(File file, int userId) { if (file.exists()) { boolean ok = file.delete(); if (!ok) { // This is a problem because it may delay informing a client to leave // activation. LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Unable to delete file"); lmg.param(LoggingConsts.USER_ID, userId); lmg.param(LoggingConsts.FILENAME, file.getAbsolutePath()); m_logCategory.error(lmg.toString()); } } }
/** Return the user's secure_hash_key. If one wasn't found create one on the fly. */ private SecureHashKeyResult getSecureHashKey(UserAccount user, Connection c) throws SQLException { PreparedStatement select = null; // Create lazily. PreparedStatement update = null; int userId = user.getUserID(); boolean justCreated = false; byte[] key = null; try { // TODO: consider having UserManager returning secure_hash_key. // TODO: We have similar logic in several places for creating secure_hash_key just-in-time. // D.R.Y. this out. Sorry I couldn't resist using this cliche :) select = c.prepareStatement("SELECT secure_hash_key FROM dat_user_account WHERE object_id=?"); select.setInt(1, userId); ResultSet rs = select.executeQuery(); if (!rs.next()) { LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("dat_user_account row not found"); lmg.param(LoggingConsts.USER_ID, userId); // possible that the user simply disappeared by the time we got here. m_logCategory.warn(lmg.toString()); } else { key = rs.getBytes(1); if (key == null || key.length == 0) { // hash key not found; create one on the fly. update = c.prepareStatement("UPDATE dat_user_account SET secure_hash_key=? WHERE object_id=?"); key = createNewRandomKey(); update.setBytes(1, key); update.setInt(2, userId); int ct = update.executeUpdate(); if (ct != 1) { LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Unable to update dat_user_account.secure_hash_key"); lmg.param(LoggingConsts.USER_ID, userId); m_logCategory.error(lmg.toString()); } else { justCreated = true; } } // needed to set key. } // user found } finally { DbUtils.safeClose(select); DbUtils.safeClose(update); } return new SecureHashKeyResult(key, justCreated); }
public ClientSession getClientSession(String activationToken) { if (!StringUtils.hasValue(activationToken)) { m_logCategory.warn("activationToken is required"); return null; } File file = getSessionFile(activationToken); if (!file.exists()) { // Can't assume it's an authentication problem -- session file is removed // during active->recovery transition. // If this occurs, the client gets a 401 and will call into 'transition' or 'checkin'. LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Session file not found"); lmg.param(LoggingConsts.FILENAME, file.getAbsolutePath()); m_logCategory.info(lmg.toString()); return null; } try { ClientSession clientSession = null; clientSession = new ClientSession(); clientSession.read(file); clientSession.setActivationToken(activationToken); return clientSession; } catch (IOException ioe) { // Possible if the state file was removed after the file.exists() above, // or some other problem. // info, not warning because a client could ask for this file right when // the session is being removed. The client will then do a 'checkin', // same as described above. LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Unable to read session file"); lmg.param(LoggingConsts.FILENAME, file.getAbsolutePath()); m_logCategory.info(lmg.toString(), ioe); } return null; }
/** * Doesn't write the file atomically. Logs, but doesn't throw upon failure. * * @param file can't be null. * @param userId only for logging. * @param content * @param errorLevel if true log an error level message; otherwise warning level. */ private void touchFile(File file, byte[] content, int userId, boolean errorLevel) { OutputStream out = null; try { file.getParentFile().mkdirs(); out = new FileOutputStream(file); out.write(content); out.close(); out = null; } catch (IOException ioe) { LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Unable to touch cache file"); lmg.param(LoggingConsts.USER_ID, userId); lmg.param(LoggingConsts.FILENAME, file.getAbsolutePath()); lmg.param(LoggingConsts.CONTENT, content); if (errorLevel) { m_logCategory.error(lmg.toString(), ioe); } else { m_logCategory.warn(lmg.toString(), ioe); } } finally { // normally closed above; just for leak prevention. IOUtils.safeClose(out, false, m_logCategory); } }
public ClientSession createClientSession(IClientInfo client, String clientType) { // Both OE and BB share the session file. Don't stomp an existing file. if (client == null || !client.isConfigured()) { throw new IllegalStateException("Client not configured."); } clientType = clientType == null ? "" : clientType; ClientSession session = null; String tmpName = null; String targetName = null; try { File targetFile = getSessionFile(client.getLastActivationToken()); targetName = targetFile.getAbsolutePath(); if (targetFile.exists()) { session = new ClientSession(); session.read(targetFile); if (session.getLastActivationId() != client.getLastActivationId() || session.getUserId() != client.getUserId()) { // Existing session file found but it's clearly not ours. // A hash collision is improbable. If this occurs, its more likely to be a // bug. We can't safely rewrite the session file because another client // might later call getClientSession() and read our email. // This is a support concern. LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Found existing session, but the content is unexpected."); lmg.param(LoggingConsts.FILENAME, targetFile.getAbsolutePath()); m_logCategory.error(lmg.toString()); throw new ClientServiceException(""); } session.setActivationToken(client.getLastActivationToken()); } // targetFile already found. if (session == null) { UserAccount user = m_userService.getUserAccount(client.getCustomerId(), client.getUserId()); session = client.createSession(user); // What if the BB and OE create a session at the same time? // This ensures a unique filename. // The client who does the last renameTo() below wins the race // and that becomes the common session file. tmpName = targetFile.getAbsolutePath() + "_tmp_" + clientType; File tmpFile = new File(tmpName); session.write(tmpFile); // If 'targetFile' already exists it'll get clobbered. // Makes the session targetFile generation atomic. boolean ok = FileUtils.clobberingRename(tmpFile, targetFile); if (!ok) { throw new IOException("Rename failed"); } } // need to create session. } catch (Exception ex) { if (ex instanceof ClientServiceException) { // We already logged it. throw (ClientServiceException) ex; } LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Unable to generate session file."); lmg.param(LoggingConsts.USER_ID, client.getUserId()); lmg.param(LoggingConsts.TEMP_NAME, tmpName); lmg.param(LoggingConsts.FILENAME, targetName); m_logCategory.error(lmg.toString(), ex); throw new ClientServiceException(""); } return session; }
public void setKeyCreatingIfNeeded( List<? extends IClientInfo> clients, List<IClientInfo> clientsNeedingSignalUpdate, Connection c) throws SQLException { PreparedStatement select = null; // Create lazily. PreparedStatement update = null; try { select = c.prepareStatement("SELECT secure_hash_key FROM dat_user_account WHERE object_id=?"); for (IClientInfo client : clients) { int userId = client.getUserId(); // ensure dat_user_account.secure_hash_key is filled. select.setInt(1, userId); ResultSet rs = select.executeQuery(); if (!rs.next()) { LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("dat_user_account row not found"); lmg.param(LoggingConsts.USER_ID, userId); // possible that the user simply disappeared by the time we got here. m_logCategory.warn(lmg.toString()); continue; } boolean firstTimeCreate = false; byte[] key = rs.getBytes(1); if (key == null || key.length == 0) { if (update == null) { update = c.prepareStatement( "UPDATE dat_user_account SET secure_hash_key=? WHERE object_id=?"); } key = createNewRandomKey(); update.setBytes(1, key); update.setInt(2, userId); int ct = update.executeUpdate(); if (ct != 1) { LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Unable to update dat_user_account.secure_hash_key"); lmg.param(LoggingConsts.USER_ID, userId); m_logCategory.error(lmg.toString()); continue; } else { firstTimeCreate = true; } } // no existing key. client.getHashSupport().setHashKey(key); if (firstTimeCreate) { if (clientsNeedingSignalUpdate != null) { // EMSDEV-7854. Don't actually do // updateSignalFiles(client) right here; we want to avoid nfs usage in the middle of a // db // transaction. clientsNeedingSignalUpdate.add(client); } } } // each client. } finally { DbUtils.safeClose(select); DbUtils.safeClose(update); } }
/** Writes user state information to the shared filesystem */ private void updateStateCache( Customer customer, int transitionId, CustomerState newState, Connection c) throws SQLException { PreparedStatement pst = null; ResultSet rs = null; try { pst = c.prepareStatement( "SELECT u.secure_hash_key, u.object_id, u.user_state, u.is_deleted, u.last_activation_id " + "FROM dat_user_account u, dat_transition_users tr " + "WHERE u.object_id = tr.user_id AND u.customer_id=? " + "AND tr.transition_id=?;"); // The state files are used by Outlook, BB, and maybe future clients. pst.setInt(1, customer.getCustID()); pst.setInt(2, transitionId); rs = pst.executeQuery(); while (rs.next()) { int i = 0; byte[] key = rs.getBytes(++i); int userId = rs.getInt(++i); CustomerState state = CustomerState.fromInt(rs.getInt(++i)); int isDeletedInt = rs.getInt(++i); int lastActivationId = rs.getInt(++i); // If the user is marked deleted but has a key, we'll try cleaning // up state files. boolean isDeleted = isDeletedInt != IUserManager.USER_NOT_DELETED; if (key == null || key.length == 0) { LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("dat_user_account.secure_hash_key not set"); lmg.param(LoggingConsts.USER_ID, userId); m_logCategory.info(lmg.toString()); // Without a key, we can't determine the signal filenames // so no cleanup is possible. continue; } ClientHashSupport hash = null; hash = new ClientHashSupport(); hash.setCustomerId(customer.getCustID()); hash.setUserId(userId); hash.setHashKey(key); hash.setLastActivationId(lastActivationId); if (m_logCategory.isInfoEnabled()) { LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Updating signal files"); lmg.param(LoggingConsts.USER_ID, userId); m_logCategory.info(lmg.toString()); } // wrt EMSDEV-7854 nfs calls and database transactions. // Above is only doing a select; no locks are taken and this // method is private. updateSignalFiles(hash, state, isDeleted); } // each row. } finally { DbUtils.safeClose(rs); DbUtils.safeClose(pst); } }
// Override the execute function because there will not be an ACTIVATION ID public ActionForward execute( ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws IOException { // If the userName parameter is not present don't bother validating the backend. This is to // support older clients that do not pass the userName parameter. String userName = request.getParameter(EMS_RIM_PARAM_USER_NAME); String userPin = request.getParameter(EMS_RIM_PARAM_USER_PIN); if (!StringUtils.isNullOrEmptyIgnoreWS(userName)) { IDomainXrefManager dxm = ManagementContainer.getInstance().getDomainXrefManager(); String backend = request.getParameter(EMS_RIM_PARAM_BACKEND_LOCATION); if (StringUtils.isNullOrEmptyIgnoreWS(backend)) { backend = IDomainXrefManager.Location.NONE.toString(); } dxm.getUserBackend(userName, DomainXrefManager.getLocationFromString(backend)); if (StringUtils.isNullOrEmptyIgnoreWS(backend)) { LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Cannot determine backend for user"); lmg.param(LoggingConsts.USERNAME, userName); lmg.param(LoggingConsts.PIN, userPin); m_logCategory.warn(lmg.toString()); return null; } if (!isLocalBackend(backend)) { // We are on the wrong backend. Redirect. // Note that Global URL isn't supported for old agents. // Pre-pancit agents don't have requisite logic for handling the // redirects. So, this code is somewhat moot. response.setHeader(EMS_RIM_BACKEND_HEADER, backend); response.setHeader(EMS_RIM_ERROR_CODE_HEADER, HTTP_STATUS_REDIRECT); response.sendError(HttpServletResponse.SC_MOVED_TEMPORARILY, "Redirect to backend"); return null; } } // has username IDeviceManager deviceManager = ManagementContainer.getInstance().getDeviceManager(); if (!validatePin(userPin, null)) { return null; } // Preload the user and device to prevent multiple loads later. Device device = getDevice(userPin, null); if (device == null) { return null; } if (!assertLegacyApiUseAllowed(device)) { return null; } UserAccount user = getUser(null, device); if (user == null) { return null; } String currVersion = device.getAgentVersion(); currVersion = currVersion == null ? "" : currVersion; String agentVersion = request.getParameter(EMS_RIM_PARAM_AGENT_VERSIN); // If currVersion is empty it means an agent was never installed. // EMSDEV-4679 if (!"".equals(currVersion) && (!Device.usesOldRimBackendApi(agentVersion) || !Device.usesOldRimBackendApi(currVersion))) { // This helps close a security hole. // The 'outlook' BB API has stronger authentication. // The old service would allow clients to get in w/o a valid // auth-token. // This service returns the 'activation-id' with no other authentication // than a valid pin. That activation-id may then be used to access // GetRimMailAction. // We need to prevent this. This is the only service that sends // activation-id's. The other way id-s are delivered to old agents is via // push messages. We don't send old-style push messages to new agents. LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Mixing legacy/current agent APIs not permitted"); lmg.param(LoggingConsts.PIN, userPin); lmg.param(LoggingConsts.USER_ID, device.getUserId()); lmg.param("attemptedVersion", agentVersion); lmg.param("currVersion", currVersion); m_logCategory.warn(lmg.toString()); return null; } // 'phone' isn't known by agents 6.1 and earlier so just send a blank. deviceManager.setAgentVersionForInit(device.getPin(), agentVersion, ""); // The executeAction method must set any header to be returned in the response return executeAction(mapping, actionForm, request, response, user, device); }
// Use this to set headers that are required on all responses. protected void setResponseHeaders( ActionForm actionForm, HttpServletResponse response, UserAccount user, Device device) throws IOException { // The url is static across all devices in a customer. ICustomerManager customerManager = ManagementContainer.getInstance().getCustomerManager(); Customer cust = customerManager.getCustomer(user.getCustomerID()); IRimManager rimManager = ManagementContainer.getInstance().getRimManager(); String cappUrl = cust.getCappUrl(); if (m_devDebug) { // Our dev boxes use http and specifcy a port, this conflicts with the https // assumption of getCappUrl() cappUrl = "http://turbodog.austin.messageone.com:8080/"; } URL url = null; try { url = new URL(cappUrl); } catch (MalformedURLException e) { LogMessageGen lmg = new LogMessageGen(); lmg.setSubject("Bad URL"); lmg.param(LoggingConsts.URL, cappUrl); lmg.param(LoggingConsts.USER_ID, device.getUserId()); lmg.param(LoggingConsts.CUSTOMER_ID, device.getCustomerId()); lmg.param(LoggingConsts.PIN, device.getPin()); // This would come from the customer config, so it's bad. m_logCategory.error(lmg.toString(), e); response.setHeader(SUCCESS, "false"); return; } int deviceCheckin = cust.getCapabilities().getIntCapability(Capabilities.CAP_RIM_DEVICE_PERIODIC_CHECKIN, 30); response.setHeader("rim-access-url-protocol", url.getProtocol()); response.setHeader("rim-access-url-hostname", url.getHost()); if (url.getPort() > 0) { response.setHeader("rim-access-url-port", Integer.toString(url.getPort())); } response.setHeader(EMS_RIM_DISPLAY_NAME, device.getDisplayName()); response.setHeader("get-mail-path", "wfe/getRimMail.do"); response.setHeader("send-mail-path", "wfe/sendRimMail.do"); response.setHeader("get-display-name-path", "wfe/rimGetDisplayName.do"); response.setHeader("checkin-path", "wfe/rimCheckin.do"); response.setHeader("periodic-checkin", Integer.toString(deviceCheckin)); CustomerState state = user.getUserState(); String stateString = "ready"; if (CustomerState.ACTIVE.equals(state) || CustomerState.TEST.equals(state)) { stateString = "active"; String activationId = rimManager.getActivationId(device.getPin()); if (StringUtils.isEmptyOrNull(activationId)) { // EMSDEV-4722 // If an old agent is installed during activation, the dat_rim_user_connection_status // won't exist and thus no activation ID. If needed, create one on the fly. // Incidently, this will fix most of the problems we've had with activationId's // disappearing for various reasons. LogMessageGen.log( "Old agent activationId not found; will create one on the fly", m_logCategory) .param(LoggingConsts.PIN, device.getPin()) .param(LoggingConsts.USER_ID, device.getUserId()) .info(); activationId = rimManager.activateOldAgent(device.getPin(), device.getCustomerId()); // Try again. if (StringUtils.isEmptyOrNull(activationId)) { LogMessageGen.log("Unable to create activationId for old agent", m_logCategory) .param(LoggingConsts.PIN, device.getPin()) .param(LoggingConsts.USER_ID, device.getUserId()) .error(); } } // no activationId response.setHeader("activation-id", activationId); } // active. response.setHeader("application-state", stateString); response.setHeader(SUCCESS, "true"); }