예제 #1
0
 /**
  * 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());
     }
   }
 }
예제 #2
0
  /** 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);
  }
예제 #3
0
  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;
  }
예제 #4
0
 /**
  * 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);
   }
 }
예제 #5
0
  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;
  }
예제 #6
0
  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);
    }
  }
예제 #7
0
  /** 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);
    }
  }
예제 #8
0
  // 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);
  }
예제 #9
0
  // 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");
  }