def signin_post_(request): form = request.web_input(username="", password="", referer="", sfwmode="nsfw") form.referer = form.referer or '/' logid, logerror = login.authenticate_bcrypt(form.username, form.password) if logid and logerror == 'unicode-failure': raise HTTPSeeOther(location='/signin/unicode-failure') elif logid and logerror is None: if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings. index.template_fields.invalidate(logid) raise HTTPSeeOther(location=form.referer) elif logerror == "invalid": return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer])) elif logerror == "banned": reason = moderation.get_ban_reason(logid) return Response(define.errorpage( request.userid, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact [email protected] for assistance." % (reason,))) elif logerror == "suspended": suspension = moderation.get_suspension(logid) return Response(define.errorpage( request.userid, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release)))) elif logerror == "address": return Response("IP ADDRESS TEMPORARILY BLOCKED") return Response(define.errorpage(request.userid))
def common_status_page(userid, status): """ Raise the redirect to the script returned by common_status_check() or render the appropriate site status error page. """ if status in ('banned', 'suspended'): from weasyl import moderation, login login.signout(get_current_request()) if status == 'banned': reason = moderation.get_ban_reason(userid) return errorpage( userid, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact %s for assistance." % (reason, MACRO_SUPPORT_ADDRESS)) elif status == 'suspended': suspension = moderation.get_suspension(userid) return errorpage( userid, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "%s for assistance." % (suspension.reason, convert_date(suspension.release), MACRO_SUPPORT_ADDRESS))
def POST(self): form = web.input(username="", password="", referer="", sfwmode="nsfw") form.referer = form.referer or '/index' logid, logerror = login.authenticate_bcrypt(form.username, form.password) if logid and logerror == 'unicode-failure': raise web.seeother('/signin/unicode-failure') elif logid and logerror is None: if form.sfwmode == "sfw": web.setcookie("sfwmode", "sfw", 31536000) raise web.seeother(form.referer) elif logerror == "invalid": return define.webpage(self.user_id, template.etc_signin, [True, form.referer]) elif logerror == "banned": reason = moderation.get_ban_reason(logid) return define.errorpage( self.user_id, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact [email protected] for assistance." % (reason,)) elif logerror == "suspended": suspension = moderation.get_suspension(logid) return define.errorpage( self.user_id, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release))) elif logerror == "address": return "IP ADDRESS TEMPORARILY BLOCKED" return define.errorpage(self.user_id)
def signin_post_(request): form = request.web_input(username="", password="", referer="", sfwmode="nsfw") form.referer = form.referer or '/' logid, logerror = login.authenticate_bcrypt(form.username, form.password, request=request, ip_address=request.client_addr, user_agent=request.user_agent) if logid and logerror is None: if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings. index.template_fields.invalidate(logid) raise HTTPSeeOther(location=form.referer) elif logid and logerror == "2fa": # Password authentication passed, but user has 2FA set, so verify second factor (Also set SFW mode now) if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) index.template_fields.invalidate(logid) # Check if out of recovery codes; this should *never* execute normally, save for crafted # webtests. However, check for it and log an error to Sentry if it happens. remaining_recovery_codes = two_factor_auth.get_number_of_recovery_codes(logid) if remaining_recovery_codes == 0: raise RuntimeError("Two-factor Authentication: Count of recovery codes for userid " + str(logid) + " was zero upon password authentication succeeding, " + "which should be impossible.") # Store the authenticated userid & password auth time to the session sess = define.get_weasyl_session() # The timestamp at which password authentication succeeded sess.additional_data['2fa_pwd_auth_timestamp'] = arrow.now().timestamp # The userid of the user attempting authentication sess.additional_data['2fa_pwd_auth_userid'] = logid # The number of times the user has attempted to authenticate via 2FA sess.additional_data['2fa_pwd_auth_attempts'] = 0 sess.save = True return Response(define.webpage( request.userid, "etc/signin_2fa_auth.html", [define.get_display_name(logid), form.referer, remaining_recovery_codes, None], title="Sign In - 2FA" )) elif logerror == "invalid": return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer])) elif logerror == "banned": reason = moderation.get_ban_reason(logid) return Response(define.errorpage( request.userid, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact %s for assistance." % (reason, MACRO_SUPPORT_ADDRESS))) elif logerror == "suspended": suspension = moderation.get_suspension(logid) return Response(define.errorpage( request.userid, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "%s for assistance." % (suspension.reason, define.convert_date(suspension.release), MACRO_SUPPORT_ADDRESS))) raise WeasylError("Unexpected") # pragma: no cover
def signin_post_(request): form = request.web_input(username="", password="", referer="", sfwmode="nsfw") form.referer = form.referer or '/' logid, logerror = login.authenticate_bcrypt(form.username, form.password) if logid and logerror == 'unicode-failure': raise HTTPSeeOther(location='/signin/unicode-failure') elif logid and logerror is None: if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings. index.template_fields.invalidate(logid) raise HTTPSeeOther(location=form.referer) elif logerror == "invalid": return Response( define.webpage(request.userid, "etc/signin.html", [True, form.referer])) elif logerror == "banned": reason = moderation.get_ban_reason(logid) return Response( define.errorpage( request.userid, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact [email protected] for assistance." % (reason, ))) elif logerror == "suspended": suspension = moderation.get_suspension(logid) return Response( define.errorpage( request.userid, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release)))) elif logerror == "address": return Response("IP ADDRESS TEMPORARILY BLOCKED") return Response(define.errorpage(request.userid))
def common_status_page(userid, status): """ Raise the redirect to the script returned by common_status_check() or render the appropriate site status error page. """ if status == "admin": return errorpage(0, errorcode.admin_mode) elif status == "local": return errorpage(0, errorcode.local_mode) elif status == "offline": return errorpage(0, errorcode.offline_mode) elif status == "address": return "IP ADDRESS TEMPORARILY REJECTED" elif status == "resetpassword": return webpage(userid, "force/resetpassword.html") elif status == "resetbirthday": return webpage(userid, "force/resetbirthday.html") elif status == "resetemail": return "reset email" # todo elif status in ('banned', 'suspended'): from weasyl import moderation, login login.signout(get_current_request()) if status == 'banned': reason = moderation.get_ban_reason(userid) return errorpage( userid, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact [email protected] for assistance." % (reason, )) elif status == 'suspended': suspension = moderation.get_suspension(userid) return errorpage( userid, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "[email protected] for assistance." % (suspension.reason, convert_date(suspension.release)))
def common_status_page(userid, status): """ Raise the redirect to the script returned by common_status_check() or render the appropriate site status error page. """ if status == "admin": return errorpage(0, errorcode.admin_mode) elif status == "local": return errorpage(0, errorcode.local_mode) elif status == "offline": return errorpage(0, errorcode.offline_mode) elif status == "address": return "IP ADDRESS TEMPORARILY REJECTED" elif status == "resetpassword": return webpage(userid, "force/resetpassword.html") elif status == "resetbirthday": return webpage(userid, "force/resetbirthday.html") elif status == "resetemail": return "reset email" # todo elif status in ('banned', 'suspended'): from weasyl import moderation, login login.signout(userid) if status == 'banned': reason = moderation.get_ban_reason(userid) return errorpage( userid, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact [email protected] for assistance." % (reason,)) elif status == 'suspended': suspension = moderation.get_suspension(userid) return errorpage( userid, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "[email protected] for assistance." % (suspension.reason, convert_date(suspension.release)))
def POST(self): form = web.input(username="", password="", referer="", sfwmode="nsfw") form.referer = form.referer or '/index' logid, logerror = login.authenticate_bcrypt(form.username, form.password) if logid and logerror == 'unicode-failure': raise web.seeother('/signin/unicode-failure') elif logid and logerror is None: if form.sfwmode == "sfw": web.setcookie("sfwmode", "sfw", 31536000) raise web.seeother(form.referer) elif logerror == "invalid": return define.webpage(self.user_id, template.etc_signin, [True, form.referer]) elif logerror == "banned": reason = moderation.get_ban_reason(logid) return define.errorpage( self.user_id, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact [email protected] for assistance." % (reason, )) elif logerror == "suspended": suspension = moderation.get_suspension(logid) return define.errorpage( self.user_id, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release))) elif logerror == "address": return "IP ADDRESS TEMPORARILY BLOCKED" return define.errorpage(self.user_id)
def authenticate_bcrypt(username, password, request, ip_address=None, user_agent=None): """ Return a result tuple of the form (userid, error); `error` is None if the login was successful. Pass None as the `request` to authenticate a user without creating a new session. :param username: The username of the user attempting authentication. :param password: The user's claimed password to check against the stored hash. :param request: The request, or None :param ip_address: The address requesting authentication. :param user_agent: The user agent string of the submitting client. Possible errors are: - "invalid" - "unexpected" - "banned" - "suspended" - "2fa" - Indicates the user has opted-in to 2FA. Additional authentication required. """ # Check that the user entered potentially valid values for `username` and # `password` before attempting to authenticate them if not username or not password: return 0, "invalid" # Select the authentication data necessary to check that the the user-entered # credentials are valid query = d.engine.execute( "SELECT ab.userid, ab.hashsum, lo.twofa_secret FROM authbcrypt ab" " RIGHT JOIN login lo USING (userid)" " WHERE lo.login_name = %(name)s", name=d.get_sysname(username), ).first() if not query: return 0, "invalid" USERID, HASHSUM, TWOFA = query HASHSUM = HASHSUM.encode('utf-8') _, IS_BANNED, IS_SUSPENDED = d.get_login_settings(USERID) d.metric('increment', 'attemptedlogins') if not bcrypt.checkpw(password.encode('utf-8'), HASHSUM): # Log the failed login attempt in a security log if the account the user # attempted to log into is a privileged account if USERID in staff.MODS: d.append_to_log('login.fail', userid=USERID, ip=d.get_address()) d.metric('increment', 'failedlogins') # Return a zero userid and an error code (indicating the entered password # was incorrect) return 0, "invalid" elif IS_BANNED: # Return the proper userid and an error code (indicating the user's account # has been banned) return USERID, "banned" elif IS_SUSPENDED: from weasyl import moderation suspension = moderation.get_suspension(USERID) if d.get_time() > suspension.release: d.execute("DELETE FROM suspension WHERE userid = %i", [USERID]) d._get_all_config.invalidate(USERID) else: # Return the proper userid and an error code (indicating the user's # account has been temporarily suspended) return USERID, "suspended" # Attempt to create a new session if this is a request to log in, then log the signin # if it succeeded. if request is not None: # If the user's record has ``login.twofa_secret`` set (not nulled), return that password authentication succeeded. if TWOFA: if not isinstance(request.weasyl_session, GuestSession): request.pg_connection.delete(request.weasyl_session) request.pg_connection.flush() request.weasyl_session = create_session(None) request.weasyl_session.additional_data = {} return USERID, "2fa" else: signin(request, USERID, ip_address=ip_address, user_agent=user_agent) # Either way, authentication succeeded, so return the userid and a status. return USERID, None
def signin_post_(request): form = request.web_input(username="", password="", referer="", sfwmode="nsfw") form.referer = form.referer or '/' logid, logerror = login.authenticate_bcrypt(form.username, form.password, request=request, ip_address=request.client_addr, user_agent=request.user_agent) if logid and logerror == 'unicode-failure': raise HTTPSeeOther(location='/signin/unicode-failure') elif logid and logerror is None: if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings. index.template_fields.invalidate(logid) raise HTTPSeeOther(location=form.referer) elif logid and logerror == "2fa": # Password authentication passed, but user has 2FA set, so verify second factor (Also set SFW mode now) if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) index.template_fields.invalidate(logid) # Check if out of recovery codes; this should *never* execute normally, save for crafted # webtests. However, check for it and log an error to Sentry if it happens. remaining_recovery_codes = two_factor_auth.get_number_of_recovery_codes(logid) if remaining_recovery_codes == 0: raise RuntimeError("Two-factor Authentication: Count of recovery codes for userid " + str(logid) + " was zero upon password authentication succeeding, " + "which should be impossible.") # Store the authenticated userid & password auth time to the session sess = define.get_weasyl_session() # The timestamp at which password authentication succeeded sess.additional_data['2fa_pwd_auth_timestamp'] = arrow.now().timestamp # The userid of the user attempting authentication sess.additional_data['2fa_pwd_auth_userid'] = logid # The number of times the user has attempted to authenticate via 2FA sess.additional_data['2fa_pwd_auth_attempts'] = 0 sess.save = True return Response(define.webpage( request.userid, "etc/signin_2fa_auth.html", [define.get_display_name(logid), form.referer, remaining_recovery_codes, None], title="Sign In - 2FA" )) elif logerror == "invalid": return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer])) elif logerror == "banned": reason = moderation.get_ban_reason(logid) return Response(define.errorpage( request.userid, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact [email protected] for assistance." % (reason,))) elif logerror == "suspended": suspension = moderation.get_suspension(logid) return Response(define.errorpage( request.userid, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release)))) elif logerror == "address": return Response("IP ADDRESS TEMPORARILY BLOCKED") return Response(define.errorpage(request.userid))
def authenticate_bcrypt(username, password, session=True): """ Return a result tuple of the form (userid, error); `error` is None if the login was successful. Pass `session` as False to authenticate a user without creating a new session. Possible errors are: - "invalid" - "unexpected" - "address" - "banned" - "suspended" """ # Check that the user entered potentially valid values for `username` and # `password` before attempting to authenticate them if not username or not password: return 0, "invalid" # Select the authentication data necessary to check that the the user-entered # credentials are valid query = d.execute("SELECT ab.userid, ab.hashsum, lo.settings FROM authbcrypt ab" " RIGHT JOIN login lo USING (userid)" " WHERE lo.login_name = '%s'", [d.get_sysname(username)], ["single"]) if not query: return 0, "invalid" USERID, HASHSUM, SETTINGS = query HASHSUM = HASHSUM.encode('utf-8') d.metric('increment', 'attemptedlogins') unicode_success = bcrypt.checkpw(password.encode('utf-8'), HASHSUM) if not unicode_success and not bcrypt.checkpw(d.plaintext(password).encode('utf-8'), HASHSUM): # Log the failed login attempt in a security log if the account the user # attempted to log into is a privileged account if USERID in staff.MODS: d.append_to_log('login.fail', userid=USERID, ip=d.get_address()) d.metric('increment', 'failedlogins') # Return a zero userid and an error code (indicating the entered password # was incorrect) return 0, "invalid" elif "b" in SETTINGS: # Return the proper userid and an error code (indicating the user's account # has been banned) return USERID, "banned" elif "s" in SETTINGS: suspension = moderation.get_suspension(USERID) if d.get_time() > suspension.release: d.execute("UPDATE login SET settings = REPLACE(settings, 's', '') WHERE userid = %i", [USERID]) d.execute("DELETE FROM suspension WHERE userid = %i", [USERID]) d.get_login_settings.invalidate(USERID) else: # Return the proper userid and an error code (indicating the user's # account has been temporarily suspended) return USERID, "suspended" # Attempt to create a new session if `session` is True, then log the signin # if it succeeded. if session: signin(USERID) d.append_to_log('login.success', userid=USERID, ip=d.get_address()) d.metric('increment', 'logins') status = None if not unicode_success: # Oops; the user's password was stored badly, but they did successfully authenticate. status = 'unicode-failure' # Either way, authentication succeeded, so return the userid and a status. return USERID, status
def authenticate_bcrypt(username, password, session=True): """ Return a result tuple of the form (userid, error); `error` is None if the login was successful. Pass `session` as False to authenticate a user without creating a new session. Possible errors are: - "invalid" - "unexpected" - "address" - "banned" - "suspended" - "2fa" - Indicates the user has opted-in to 2FA. Additional authentication required. """ # Check that the user entered potentially valid values for `username` and # `password` before attempting to authenticate them if not username or not password: return 0, "invalid" # Select the authentication data necessary to check that the the user-entered # credentials are valid query = d.execute( "SELECT ab.userid, ab.hashsum, lo.settings, lo.twofa_secret FROM authbcrypt ab" " RIGHT JOIN login lo USING (userid)" " WHERE lo.login_name = '%s'", [d.get_sysname(username)], ["single"]) if not query: return 0, "invalid" USERID, HASHSUM, SETTINGS, TWOFA = query HASHSUM = HASHSUM.encode('utf-8') d.metric('increment', 'attemptedlogins') unicode_success = bcrypt.checkpw(password.encode('utf-8'), HASHSUM) if not unicode_success and not bcrypt.checkpw( d.plaintext(password).encode('utf-8'), HASHSUM): # Log the failed login attempt in a security log if the account the user # attempted to log into is a privileged account if USERID in staff.MODS: d.append_to_log('login.fail', userid=USERID, ip=d.get_address()) d.metric('increment', 'failedlogins') # Return a zero userid and an error code (indicating the entered password # was incorrect) return 0, "invalid" elif "b" in SETTINGS: # Return the proper userid and an error code (indicating the user's account # has been banned) return USERID, "banned" elif "s" in SETTINGS: suspension = moderation.get_suspension(USERID) if d.get_time() > suspension.release: d.execute( "UPDATE login SET settings = REPLACE(settings, 's', '') WHERE userid = %i", [USERID]) d.execute("DELETE FROM suspension WHERE userid = %i", [USERID]) d.get_login_settings.invalidate(USERID) else: # Return the proper userid and an error code (indicating the user's # account has been temporarily suspended) return USERID, "suspended" # Attempt to create a new session if `session` is True, then log the signin # if it succeeded. if session: # If the user's record has ``login.twofa_secret`` set (not nulled), return that password authentication succeeded. if TWOFA: return USERID, "2fa" else: signin(USERID) status = None if not unicode_success: # Oops; the user's password was stored badly, but they did successfully authenticate. status = 'unicode-failure' # Either way, authentication succeeded, so return the userid and a status. return USERID, status
def authenticate_bcrypt(username, password, request, ip_address=None, user_agent=None): """ Return a result tuple of the form (userid, error); `error` is None if the login was successful. Pass None as the `request` to authenticate a user without creating a new session. :param username: The username of the user attempting authentication. :param password: The user's claimed password to check against the stored hash. :param ip_address: The address requesting authentication. :param user_agent: The user agent string of the submitting client. Possible errors are: - "invalid" - "unexpected" - "address" - "banned" - "suspended" - "2fa" - Indicates the user has opted-in to 2FA. Additional authentication required. """ # Check that the user entered potentially valid values for `username` and # `password` before attempting to authenticate them if not username or not password: return 0, "invalid" # Select the authentication data necessary to check that the the user-entered # credentials are valid query = d.execute("SELECT ab.userid, ab.hashsum, lo.settings, lo.twofa_secret FROM authbcrypt ab" " RIGHT JOIN login lo USING (userid)" " WHERE lo.login_name = '%s'", [d.get_sysname(username)], ["single"]) if not query: return 0, "invalid" USERID, HASHSUM, SETTINGS, TWOFA = query HASHSUM = HASHSUM.encode('utf-8') d.metric('increment', 'attemptedlogins') unicode_success = bcrypt.checkpw(password.encode('utf-8'), HASHSUM) if not unicode_success and not bcrypt.checkpw(d.plaintext(password).encode('utf-8'), HASHSUM): # Log the failed login attempt in a security log if the account the user # attempted to log into is a privileged account if USERID in staff.MODS: d.append_to_log('login.fail', userid=USERID, ip=d.get_address()) d.metric('increment', 'failedlogins') # Return a zero userid and an error code (indicating the entered password # was incorrect) return 0, "invalid" elif "b" in SETTINGS: # Return the proper userid and an error code (indicating the user's account # has been banned) return USERID, "banned" elif "s" in SETTINGS: suspension = moderation.get_suspension(USERID) if d.get_time() > suspension.release: d.execute("UPDATE login SET settings = REPLACE(settings, 's', '') WHERE userid = %i", [USERID]) d.execute("DELETE FROM suspension WHERE userid = %i", [USERID]) d.get_login_settings.invalidate(USERID) else: # Return the proper userid and an error code (indicating the user's # account has been temporarily suspended) return USERID, "suspended" # Attempt to create a new session if this is a request to log in, then log the signin # if it succeeded. if request is not None: # If the user's record has ``login.twofa_secret`` set (not nulled), return that password authentication succeeded. if TWOFA: if not isinstance(request.weasyl_session, GuestSession): request.pg_connection.delete(request.weasyl_session) request.pg_connection.flush() request.weasyl_session = create_session(None) request.weasyl_session.additional_data = {} return USERID, "2fa" else: signin(request, USERID, ip_address=ip_address, user_agent=user_agent) status = None if not unicode_success: # Oops; the user's password was stored badly, but they did successfully authenticate. status = 'unicode-failure' # Either way, authentication succeeded, so return the userid and a status. return USERID, status