All Implemented Interfaces:
Serializable, Servlet, ServletConfig

public class LoginServletextends HttpServlet
Login servlet for console authentication.
See Also:
  • Field Details

    • _log

      private static final Log _log
      Logger for the login servlet.
    • PARAM_USERNAME

      private static final String PARAM_USERNAME
      Form parameter carrying the user name.
      See Also:
    • PARAM_PASSWORD

      private static final String PARAM_PASSWORD
      Form parameter carrying the password.
      See Also:
    • REALM

      private static final String REALM
      The authentication realm name.
      See Also:
    • DEFAULT_THEME

      private static final String DEFAULT_THEME
      Default login page theme.
      See Also:
    • PROP_PERSISTED_SESSIONS

      private static final String PROP_PERSISTED_SESSIONS
      Config key for the persisted session count.
      See Also:
    • _SESSION_LIST_LOCK

      private static final Object _SESSION_LIST_LOCK
      Serializes the read-modify-write of the persisted-session list. Two concurrent logins otherwise read the same list and the later saveConfig() overwrites the earlier one's entry, so a session that was already issued silently stops working.
    • MAX_PERSISTED_SESSIONS

      static final int MAX_PERSISTED_SESSIONS
      How many "remember me" sessions router.config holds. Every login used to append one, with no expiry pruning, so the value grew without bound.
      See Also:
    • PROP_TRUST_FORWARDED_FOR

      private static final String PROP_TRUST_FORWARDED_FOR
      Set to true only when the console really does sit behind a reverse proxy that rewrites the forwarding headers. Left false, the login blocklist keys on the peer address of the connection, which a client cannot forge.
      See Also:
    • COLON_SPLIT

      private static final Pattern COLON_SPLIT
      Splits a stored session record on colons.
    • PIPE_SPLIT

      private static final Pattern PIPE_SPLIT
      Splits the list of stored session records.
    • CSRF_RANDOM

      private static final SecureRandom CSRF_RANDOM
      Random source for the CSRF tokens.
    • ALLOWED_THEMES

      private static final Set<String> ALLOWED_THEMES
      Themes the login page accepts.
    • ALLOWED_LANGS

      private static final Set<String> ALLOWED_LANGS
      Languages offered by the login page flag bar (login.jsp langToCountry). Any addition there MUST be mirrored here, or the selection is silently discarded and falls back to "en".
  • Constructor Details

    • LoginServlet

      public LoginServlet()
      Constructor, used by the servlet container. The container calls init() afterwards, which is where the persisted sessions are loaded.
  • Method Details

    • init

      public void init() throws ServletException
      Overrides:
      init in class GenericServlet
      Throws:
      ServletException
    • doGet

      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
      Overrides:
      doGet in class HttpServlet
      Throws:
      ServletException
      IOException
    • isPasswordConfigured

      private boolean isPasswordConfigured()
      Checks whether any console password is configured.
      Returns:
      true if a password exists
    • hasAnyPassword

      private boolean hasAnyPassword(RouterContext ctx)
      Checks the password files for any usable password.
      Parameters:
      ctx - the router context
      Returns:
      true if a password exists
    • getLoginTheme

      private String getLoginTheme()
      Returns the configured login page theme.
      Returns:
      the theme name
    • sanitizeTheme

      private static String sanitizeTheme(String theme)
      Keeps only the known theme names.
      Parameters:
      theme - the theme from the request, may be null
      Returns:
      a safe theme name
    • sanitizeLang

      private static String sanitizeLang(String lang)
      Keeps only a well formed language tag.
      Parameters:
      lang - the language from the request, may be null
      Returns:
      a safe language tag
    • doPost

      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
      Overrides:
      doPost in class HttpServlet
      Throws:
      ServletException
      IOException
    • parseDuration

      private long parseDuration(String duration)
      Parses a session duration like 7d.
      Parameters:
      duration - the text from the form
      Returns:
      the duration, in ms
    • getClientIP

      private String getClientIP(HttpServletRequest req)
      Returns the client address of the request.
      Parameters:
      req - the request to read the client address from
      Returns:
      the address text
    • clientIP

      static String clientIP(String forwardedFor, String realIp, String remoteAddr, boolean trustForwarded)
      Choose the address the login blocklist keys on.

      The peer address of the connection is the default. The forwarding headers are attacker-controlled on a directly-reachable console, and honouring them let every attempt with a fresh X-Forwarded-For value look like a new client, so the blocklist never engaged and its map grew one entry per spoofed address. They are read only when an operator has opted in via PROP_TRUST_FORWARDED_FOR, having put a real proxy in front.

      That proxy must OVERWRITE the forwarding header with the address it saw - nginx X-Real-IP from $remote_addr, for example. A proxy that appends instead (nginx $proxy_add_x_forwarded_for, Apache's default) leaves the client's own text as the leftmost entry, which is exactly the fresh-identity bypass the header is not trusted for by default. A value that is not an IP literal is ignored outright, so a hostile header can neither key the blocklist on text of the sender's choosing nor reach the log line verbatim.

      Parameters:
      forwardedFor - the X-Forwarded-For header, a comma-separated chain, or null
      realIp - the X-Real-IP header, or null
      remoteAddr - the peer address of the connection, never null in practice
      trustForwarded - true only when a trusted reverse proxy fronts the console
      Returns:
      the address to rate-limit, never null or empty
    • firstForwarded

      static String firstForwarded(String header)
      The client address from a forwarding header: the leftmost entry of an X-Forwarded-For chain, since later entries are appended by proxies further along and the first is the one the nearest proxy saw.
      Parameters:
      header - the raw header value, or null
      Returns:
      the trimmed address, or null if the header is absent, blank, or the literal "unknown" some proxies send
    • verifyPassword

      private boolean verifyPassword(String username, String password)
      Checks the submitted password against the stored hashes.
      Parameters:
      username - the user name from the form
      password - the password from the form
      Returns:
      true if the password matches
    • getSessionCookie

      private String getSessionCookie(HttpServletRequest req)
      Returns the persisted session cookie value.
      Parameters:
      req - the request to read the cookie from
      Returns:
      the cookie value, null if absent
    • persistSession

      private void persistSession(String token, String username, long expiryMs)
      Saves a session to survive a restart.
      Parameters:
      token - the session token
      username - the user name
      expiryMs - the expiry time, in ms since epoch
    • addPersistedSession

      static String addPersistedSession(String existing, String token, String username, long expiresAt, long now)
      Rebuild the persisted-session list with one session added.

      The list used to be append-only: every login wrote a new entry, nothing ever removed one, and the whole value was rewritten each time - so a router left running accumulated a session token per login in router.config for ever, including ones that had expired and were never loadable again. This drops expired and unreadable entries, replaces an existing entry for the same token instead of adding a second, and caps the list so it cannot grow without bound.

      Pure and static so a test can drive a hostile stored value without a router.

      Parameters:
      existing - the stored value, or null
      token - the session token being added
      username - who the session belongs to
      expiresAt - when it expires in ms, or -1 for no expiry
      now - current time in ms
      Returns:
      the value to store, entries joined with '|'
    • loadPersistedSessions

      private void loadPersistedSessions()
      Loads the saved sessions from the config.
    • generateCSRFToken

      private String generateCSRFToken()
      Generates a random CSRF token.
      Returns:
      the token, hex
    • updatePreference

      private void updatePreference(String key, String value)
      Writes one console preference to the config.
      Parameters:
      key - the preference name
      value - the preference value