diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake
index 06a7ab6d7586306e1a0d408f5ea3e24d11aa58ca..26604d4913beb17e5bb2d510b5acbaa744b2587e 100644
--- a/indra/cmake/Boost.cmake
+++ b/indra/cmake/Boost.cmake
@@ -103,28 +103,28 @@ else (USESYSTEMLIBS)
   elseif (DARWIN)
     set(BOOST_CONTEXT_LIBRARY
         optimized boost_context-mt${addrsfx}
-        debug boost_context-mt${addrsfx}-d)
+        debug boost_context-mt${addrsfx})
     set(BOOST_FIBER_LIBRARY
         optimized boost_fiber-mt${addrsfx}
-        debug boost_fiber-mt${addrsfx}-d)
+        debug boost_fiber-mt${addrsfx})
     set(BOOST_FILESYSTEM_LIBRARY
         optimized boost_filesystem-mt${addrsfx}
-        debug boost_filesystem-mt${addrsfx}-d)
+        debug boost_filesystem-mt${addrsfx})
     set(BOOST_PROGRAM_OPTIONS_LIBRARY
         optimized boost_program_options-mt${addrsfx}
-        debug boost_program_options-mt${addrsfx}-d)
+        debug boost_program_options-mt${addrsfx})
     set(BOOST_REGEX_LIBRARY
         optimized boost_regex-mt${addrsfx}
-        debug boost_regex-mt${addrsfx}-d)
+        debug boost_regex-mt${addrsfx})
     set(BOOST_SIGNALS_LIBRARY
         optimized boost_signals-mt${addrsfx}
-        debug boost_signals-mt${addrsfx}-d)
+        debug boost_signals-mt${addrsfx})
     set(BOOST_SYSTEM_LIBRARY
         optimized boost_system-mt${addrsfx}
-        debug boost_system-mt${addrsfx}-d)
+        debug boost_system-mt${addrsfx})
     set(BOOST_THREAD_LIBRARY
         optimized boost_thread-mt${addrsfx}
-        debug boost_thread-mt${addrsfx}-d)
+        debug boost_thread-mt${addrsfx})
   endif (WINDOWS)
 endif (USESYSTEMLIBS)
 
diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp
index 639d1fba32ab9794ca2fc95b86c16f843cd3ed3a..18168d1c3f02fa894c75f552c923e266a072dd5f 100644
--- a/indra/llrender/llgl.cpp
+++ b/indra/llrender/llgl.cpp
@@ -722,20 +722,23 @@ bool LLGLManager::initGL()
 	glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &num_tex_image_units);
 	mNumTextureImageUnits = llmin(num_tex_image_units, 32);
 
-	if (LLRender::sGLCoreProfile)
-	{
-		mNumTextureUnits = llmin(mNumTextureImageUnits, MAX_GL_TEXTURE_UNITS);
-	}
-	else if (mHasMultitexture)
-	{
-		GLint num_tex_units;		
-		glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &num_tex_units);
-		mNumTextureUnits = llmin(num_tex_units, (GLint)MAX_GL_TEXTURE_UNITS);
-		if (mIsIntel)
-		{
-			mNumTextureUnits = llmin(mNumTextureUnits, 2);
-		}
-	}
+    if (mHasMultitexture)
+    {
+        if (LLRender::sGLCoreProfile)
+        {
+            mNumTextureUnits = llmin(mNumTextureImageUnits, MAX_GL_TEXTURE_UNITS);
+        }
+        else
+        {
+            GLint num_tex_units;
+            glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &num_tex_units);
+            mNumTextureUnits = llmin(num_tex_units, (GLint)MAX_GL_TEXTURE_UNITS);
+            if (mIsIntel)
+            {
+                mNumTextureUnits = llmin(mNumTextureUnits, 2);
+            }
+        }
+    }
 	else
 	{
 		mHasRequirements = FALSE;
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index 33b42577064aaa4fcd8142a85ab91654a0f3c283..1f3823509c4ebddb77e64193a292fdfc77f3868a 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -907,6 +907,10 @@ void LLWindowWin32::close()
             }
 
         });
+    // Window thread might be waiting for a getMessage(), give it
+    // a push to enshure it will process destroy_window_handler
+    kickWindowThread();
+
     // Even though the above lambda might not yet have run, we've already
     // bound mWindowHandle into it by value, which should suffice for the
     // operations we're asking. That's the last time WE should touch it.
@@ -1251,9 +1255,9 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen& size, BO
 	if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR),
 		&pfd))
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBPixelFmtDescErr"),
 			mCallbacks->translateString("MBError"), OSMB_OK);
+        close();
 		return FALSE;
 	}
 
@@ -1290,42 +1294,42 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen& size, BO
 
 	if (pfd.cColorBits < 32)
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBTrueColorWindow"),
 			mCallbacks->translateString("MBError"), OSMB_OK);
+        close();
 		return FALSE;
 	}
 
 	if (pfd.cAlphaBits < 8)
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBAlpha"),
 			mCallbacks->translateString("MBError"), OSMB_OK);
+        close();
 		return FALSE;
 	}
 
 	if (!SetPixelFormat(mhDC, pixel_format, &pfd))
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBPixelFmtSetErr"),
 			mCallbacks->translateString("MBError"), OSMB_OK);
+        close();
 		return FALSE;
 	}
 
 
 	if (!(mhRC = SafeCreateContext(mhDC)))
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBGLContextErr"),
 			mCallbacks->translateString("MBError"), OSMB_OK);
+        close();
 		return FALSE;
 	}
 		
 	if (!wglMakeCurrent(mhDC, mhRC))
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBGLContextActErr"),
 			mCallbacks->translateString("MBError"), OSMB_OK);
+        close();
 		return FALSE;
 	}
 
@@ -1532,16 +1536,16 @@ const	S32   max_format  = (S32)num_formats - 1;
 
 		if (!mhDC)
 		{
-			close();
 			OSMessageBox(mCallbacks->translateString("MBDevContextErr"), mCallbacks->translateString("MBError"), OSMB_OK);
+			close();
 			return FALSE;
 		}
 
 		if (!SetPixelFormat(mhDC, pixel_format, &pfd))
 		{
-			close();
 			OSMessageBox(mCallbacks->translateString("MBPixelFmtSetErr"),
 				mCallbacks->translateString("MBError"), OSMB_OK);
+			close();
 			return FALSE;
 		}
 
@@ -1577,8 +1581,8 @@ const	S32   max_format  = (S32)num_formats - 1;
 	if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR),
 		&pfd))
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBPixelFmtDescErr"), mCallbacks->translateString("MBError"), OSMB_OK);
+		close();
 		return FALSE;
 	}
 
@@ -1590,15 +1594,15 @@ const	S32   max_format  = (S32)num_formats - 1;
 	// make sure we have 32 bits per pixel
 	if (pfd.cColorBits < 32 || GetDeviceCaps(mhDC, BITSPIXEL) < 32)
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBTrueColorWindow"), mCallbacks->translateString("MBError"), OSMB_OK);
+		close();
 		return FALSE;
 	}
 
 	if (pfd.cAlphaBits < 8)
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBAlpha"), mCallbacks->translateString("MBError"), OSMB_OK);
+		close();
 		return FALSE;
 	}
 
@@ -1614,8 +1618,8 @@ const	S32   max_format  = (S32)num_formats - 1;
 
 	if (!wglMakeCurrent(mhDC, mhRC))
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBGLContextActErr"), mCallbacks->translateString("MBError"), OSMB_OK);
+        close();
 		return FALSE;
 	}
 
@@ -1623,8 +1627,8 @@ const	S32   max_format  = (S32)num_formats - 1;
 
 	if (!gGLManager.initGL())
 	{
-		close();
 		OSMessageBox(mCallbacks->translateString("MBVideoDrvErr"), mCallbacks->translateString("MBError"), OSMB_OK);
+        close();
 		return FALSE;
 	}
 	
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 5d775622a018d46b316b385aa906697632d5010a..bff7de6e289c77b420c15d750860c0215542e0e3 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -8110,9 +8110,9 @@
     <string>Color4</string>
     <key>Value</key>
     <array>
-      <real>1.0</real>
-      <real>1.0</real>
-      <real>1.0</real>
+      <real>0.33</real>
+      <real>0.33</real>
+      <real>0.33</real>
       <real>1.0</real>
     </array>
   </map>
@@ -16919,5 +16919,16 @@
     <key>Value</key>
     <integer>0</integer>
   </map>
+  <key>MFAHash</key>
+  <map>
+    <key>Comment</key>
+    <string>Override MFA state hash for authentication</string>
+    <key>Persist</key>
+    <integer>0</integer>
+    <key>Type</key>
+    <string>String</string>
+    <key>Value</key>
+    <string></string>
+  </map>
 </map>
 </llsd>
diff --git a/indra/newview/app_settings/shaders/class1/objects/previewV.glsl b/indra/newview/app_settings/shaders/class1/objects/previewV.glsl
index 4bb588335ad9809773dd08cf7d2f062a8cfe0adb..5886f47cbce323205f8e6725cc8e317f6d3762c4 100644
--- a/indra/newview/app_settings/shaders/class1/objects/previewV.glsl
+++ b/indra/newview/app_settings/shaders/class1/objects/previewV.glsl
@@ -51,30 +51,6 @@ float calcDirectionalLight(vec3 n, vec3 l)
 	return a;
 }
 
-
-float calcLocalLight(vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float is_pointlight)
-{
-	//get light vector
-	vec3 lv = lp.xyz-v;
-	
-	//get distance
-	float d = length(lv);
-	
-	//normalize light vector
-	lv *= 1.0/d;
-	
-	//distance attenuation
-	float da = clamp(1.0/(la * d), 0.0, 1.0);
-	
-	// spotlight coefficient.
-	float spot = max(dot(-ln, lv), is_pointlight);
-	da *= spot*spot; // GL_SPOT_EXPONENT=2
-
-	//angular attenuation
-	da *= calcDirectionalLight(n, lv);
-
-	return da;	
-}
 //====================================================================================================
 
 
@@ -91,7 +67,8 @@ void main()
 
 	// Collect normal lights (need to be divided by two, as we later multiply by 2)
 	col.rgb += light_diffuse[1].rgb * calcDirectionalLight(norm, light_position[1].xyz);
-	col.rgb += light_diffuse[2].rgb*calcLocalLight(pos.xyz, norm, light_position[2], light_direction[2], light_attenuation[2].x, light_attenuation[2].z);
-	col.rgb += light_diffuse[3].rgb*calcLocalLight(pos.xyz, norm, light_position[3], light_direction[3], light_attenuation[3].x, light_attenuation[3].z);
+    col.rgb += light_diffuse[2].rgb * calcDirectionalLight(norm, light_position[2].xyz);
+    col.rgb += light_diffuse[3].rgb * calcDirectionalLight(norm, light_position[3].xyz);
+
 	vertex_color = col*color;
 }
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index a63203f3cba5d2f2fdf6c68392dc5d1c749592be..3bcd4f9a497586ee2c669dbb725be88f5f32311f 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -957,13 +957,7 @@ bool LLAppViewer::init()
 	// If we don't have the right GL requirements, exit.
 	if (!gGLManager.mHasRequirements)
 	{
-		// can't use an alert here since we're exiting and
-		// all hell breaks lose.
-		LLUIString details = LLNotifications::instance().getGlobalString("UnsupportedGLRequirements");
-		OSMessageBox(
-			details.getString(),
-			LLStringUtil::null,
-			OSMB_OK);
+        // already handled with a MBVideoDrvErr
 		return 0;
 	}
 
@@ -3138,6 +3132,11 @@ bool LLAppViewer::initWindow()
 	return true;
 }
 
+bool LLAppViewer::isUpdaterMissing()
+{
+    return mUpdaterNotFound;
+}
+
 void LLAppViewer::writeDebugInfo(bool isStatic)
 {
 #if LL_WINDOWS && LL_BUGSPLAT
diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h
index a86fa7d8733de1e5b2347db24b78d772e3378025..68c04d450b973139e7cd63b0b34be1ef9399e184 100644
--- a/indra/newview/llappviewer.h
+++ b/indra/newview/llappviewer.h
@@ -105,7 +105,7 @@ class LLAppViewer : public LLApp
 	bool quitRequested() { return mQuitRequested; }
 	bool logoutRequestSent() { return mLogoutRequestSent; }
 	bool isSecondInstance() { return mSecondInstance; }
-	bool isUpdaterMissing() { return mUpdaterNotFound; }
+    bool isUpdaterMissing(); // In use by tests
 
 	void writeDebugInfo(bool isStatic=true);
 
diff --git a/indra/newview/llcallingcard.cpp b/indra/newview/llcallingcard.cpp
index bddbc79df4f71207e51a68dbde4b13c2c4702577..8d1e9a438ec5a2d054c9f0cd07df41de57a10ca3 100644
--- a/indra/newview/llcallingcard.cpp
+++ b/indra/newview/llcallingcard.cpp
@@ -257,6 +257,7 @@ S32 LLAvatarTracker::addBuddyList(const LLAvatarTracker::buddy_map_t& buds)
 			LLAvatarName av_name;
 			LLAvatarNameCache::get(agent_id, &av_name);
 
+			addChangedMask(LLFriendObserver::ADD, agent_id);
 			LL_DEBUGS() << "Added buddy " << agent_id
 					<< ", " << (mBuddyInfo[agent_id]->isOnline() ? "Online" : "Offline")
 					<< ", TO: " << mBuddyInfo[agent_id]->getRightsGrantedTo()
diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp
index eebd89f77fd39eadd5386c34aff34e74cf20c97a..e674707c011dc412a8321056b802a58bfd837130 100644
--- a/indra/newview/lldrawpoolalpha.cpp
+++ b/indra/newview/lldrawpoolalpha.cpp
@@ -717,6 +717,16 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
 					LLGLEnableFunc stencil_test(GL_STENCIL_TEST, params.mSelected, &LLGLCommonFunc::selected_stencil_test);
 
 					gGL.blendFunc((LLRender::eBlendFactor) params.mBlendFuncSrc, (LLRender::eBlendFactor) params.mBlendFuncDst, mAlphaSFactor, mAlphaDFactor);
+
+                    bool reset_minimum_alpha = false;
+                    if (!LLPipeline::sImpostorRender &&
+                        params.mBlendFuncDst != LLRender::BF_SOURCE_ALPHA &&
+                        params.mBlendFuncSrc != LLRender::BF_SOURCE_ALPHA)
+                    { // this draw call has a custom blend function that may require rendering of "invisible" fragments
+                        current_shader->setMinimumAlpha(0.f);
+                        reset_minimum_alpha = true;
+                    }
+                    
                     U32 drawMask = mask;
                     if (params.mFullbright)
                     {
@@ -729,6 +739,11 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
 
                     params.mVertexBuffer->setBufferFast(drawMask);
                     params.mVertexBuffer->drawRangeFast(params.mDrawMode, params.mStart, params.mEnd, params.mCount, params.mOffset);
+
+                    if (reset_minimum_alpha)
+                    {
+                        current_shader->setMinimumAlpha(MINIMUM_ALPHA);
+                    }
 				}
 
 				// If this alpha mesh has glow, then draw it a second time to add the destination-alpha (=glow).  Interleaving these state-changing calls is expensive, but glow must be drawn Z-sorted with alpha.
diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp
index 531f0b172d126c9234281c18fea9b13ac7f59430..a3d0eb579624e18e65e5dcc6cab6c6463fcc6545 100644
--- a/indra/newview/lllogininstance.cpp
+++ b/indra/newview/lllogininstance.cpp
@@ -61,6 +61,7 @@
 #include "lltrans.h"
 
 #include <boost/scoped_ptr.hpp>
+#include <boost/regex.hpp>
 #include <sstream>
 
 const S32 LOGIN_MAX_RETRIES = 0; // Viewer should not autmatically retry login
@@ -224,8 +225,9 @@ void LLLoginInstance::constructAuthParams(LLPointer<LLCredential> user_credentia
 	request_params["id0"] = mSerialNumber;
 	request_params["host_id"] = gSavedSettings.getString("HostID");
 	request_params["extended_errors"] = true; // request message_id and message_args
+	request_params["token"] = "";
 
-    // log request_params _before_ adding the credentials   
+    // log request_params _before_ adding the credentials or sensitive MFA hash data
     LL_DEBUGS("LLLogin") << "Login parameters: " << LLSDOStreamer<LLSDNotationFormatter>(request_params) << LL_ENDL;
 
     // Copy the credentials into the request after logging the rest
@@ -238,6 +240,33 @@ void LLLoginInstance::constructAuthParams(LLPointer<LLCredential> user_credentia
         request_params[it->first] = it->second;
     }
 
+    std::string mfa_hash = gSavedSettings.getString("MFAHash"); //non-persistent to enable testing
+    std::string grid(LLGridManager::getInstance()->getGridId());
+    std::string user_id = user_credential->userID();
+    if (gSecAPIHandler)
+    {
+        if (mfa_hash.empty())
+        {
+            // normal execution, mfa_hash was not set from debug setting so load from protected store
+            LLSD data_map = gSecAPIHandler->getProtectedData("mfa_hash", grid);
+            if (data_map.isMap() && data_map.has(user_id))
+            {
+                mfa_hash = data_map[user_id].asString();
+            }
+        }
+        else
+        {
+            // SL-16888 the mfa_hash is being overridden for testing so save it for consistency for future login requests
+            gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, mfa_hash);
+        }
+    }
+    else
+    {
+        LL_WARNS() << "unable to access protected store for mfa_hash" << LL_ENDL;
+    }
+
+    request_params["mfa_hash"] = mfa_hash;
+
 	// Specify desired timeout/retry options
 	LLSD http_params;
 	F32 srv_timeout = llclamp(gSavedSettings.getF32("LoginSRVTimeout"), LOGIN_SRV_TIMEOUT_MIN, LOGIN_SRV_TIMEOUT_MAX);
@@ -250,6 +279,11 @@ void LLLoginInstance::constructAuthParams(LLPointer<LLCredential> user_credentia
 	mRequestData["params"] = request_params;
 	mRequestData["options"] = requested_options;
 	mRequestData["http_params"] = http_params;
+#if LL_RELEASE_FOR_DOWNLOAD
+    mRequestData["wait_for_updater"] = !gSavedSettings.getBOOL("CmdLineSkipUpdater") && !LLAppViewer::instance()->isUpdaterMissing();
+#else
+    mRequestData["wait_for_updater"] = false;
+#endif
 }
 
 bool LLLoginInstance::handleLoginEvent(const LLSD& event)
@@ -406,6 +440,38 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)
                 boost::bind(&LLLoginInstance::syncWithUpdater, this, resp, _1, _2));
         }
     }
+    else if(reason_response == "mfa_challenge")
+    {
+        LL_DEBUGS("LLLogin") << " MFA challenge" << LL_ENDL;
+
+        if (gViewerWindow)
+        {
+            gViewerWindow->setShowProgress(FALSE);
+        }
+
+        LLSD args(llsd::map( "MESSAGE", LLTrans::getString(response["message_id"]) ));
+        LLSD payload;
+        LLNotificationsUtil::add("PromptMFAToken", args, payload, [=](LLSD const & notif, LLSD const & response) {
+            bool continue_clicked = response["continue"].asBoolean();
+            std::string token = response["token"].asString();
+            LL_DEBUGS("LLLogin") << "PromptMFAToken: response: " << response << " continue_clicked" << continue_clicked << LL_ENDL;
+
+            // strip out whitespace - SL-17034/BUG-231938
+            token = boost::regex_replace(token, boost::regex("\\s"), "");
+
+            if (continue_clicked && !token.empty())
+            {
+                LL_INFOS("LLLogin") << "PromptMFAToken: token submitted" << LL_ENDL;
+
+                // Set the request data to true and retry login.
+                mRequestData["params"]["token"] = token;
+                reconnect();
+            } else {
+                LL_INFOS("LLLogin") << "PromptMFAToken: no token, attemptComplete" << LL_ENDL;
+                attemptComplete();
+            }
+        });
+    }
     else if(   reason_response == "key"
             || reason_response == "presence"
             || reason_response == "connect"
diff --git a/indra/newview/llsecapi.h b/indra/newview/llsecapi.h
index e1320375abef84f7d3817c6bcbb981e19484417c..d8831fee93f2494d9439981ef5313f36ed1bf838 100644
--- a/indra/newview/llsecapi.h
+++ b/indra/newview/llsecapi.h
@@ -485,6 +485,9 @@ class LLSecAPIHandler : public LLThreadSafeRefCount
 										const std::string& data_id,
 										const std::string& map_elem)=0;
 
+	// ensure protected store's map is written to storage
+	virtual void syncProtectedMap() = 0;
+
 public:
 	virtual LLPointer<LLCredential> createCredential(const std::string& grid,
 													 const LLSD& identifier, 
diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp
index 6b06abaf999d3b7b8fc3de693d93708c6cc9081f..d0da3387ec3c810a96a9a4c371cc32ca83ed27d9 100644
--- a/indra/newview/llsechandler_basic.cpp
+++ b/indra/newview/llsechandler_basic.cpp
@@ -1608,6 +1608,11 @@ void LLSecAPIBasicHandler::removeFromProtectedMap(const std::string& data_type,
     }
 }
 
+void LLSecAPIBasicHandler::syncProtectedMap()
+{
+    // TODO - consider unifing these functions
+    _writeProtectedData();
+}
 //
 // Create a credential object from an identifier and authenticator.  credentials are
 // per grid.
diff --git a/indra/newview/llsechandler_basic.h b/indra/newview/llsechandler_basic.h
index 17e9f72f076cd3e6a085127604006bd5cad69086..bd1a8f640c249eeca85feb09059c0159fb484425 100644
--- a/indra/newview/llsechandler_basic.h
+++ b/indra/newview/llsechandler_basic.h
@@ -278,6 +278,9 @@ class LLSecAPIBasicHandler : public LLSecAPIHandler
 										const std::string& data_id,
 										const std::string& map_elem);
 
+	// ensure protected store's map is written to storage
+	virtual void syncProtectedMap();
+
 	// credential management routines
 	
 	virtual LLPointer<LLCredential> createCredential(const std::string& grid,
diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp
index 5c648c11e1e7e9ce8d48402478db15562098c1f2..0d53950889dd4c22f015b1148d6f2e46df23b3a3 100644
--- a/indra/newview/llspatialpartition.cpp
+++ b/indra/newview/llspatialpartition.cpp
@@ -852,6 +852,7 @@ LLSpatialPartition::LLSpatialPartition(U32 data_mask, BOOL render_by_group, U32
 
 LLSpatialPartition::~LLSpatialPartition()
 {
+    cleanup();
 }
 
 LLSpatialGroup *LLSpatialPartition::put(LLDrawable *drawablep, BOOL was_visible)
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index a42b6562b8e335a17a205522a04a70132c5b6313..6c074dfa08ea704d4428c108da89dbefa928334b 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -134,6 +134,7 @@
 #include "llproxy.h"
 #include "llproductinforequest.h"
 #include "llqueryflags.h"
+#include "llsecapi.h"
 #include "llselectmgr.h"
 #include "llsky.h"
 #include "llstatview.h"
@@ -1116,10 +1117,10 @@ bool idle_startup()
 			}
 			else 
 			{
-				if (reason_response != "tos") 
+				if (reason_response != "tos"  && reason_response != "mfa_challenge")
 				{
-					// Don't pop up a notification in the TOS case because
-					// LLFloaterTOS::onCancel() already scolded the user.
+					// Don't pop up a notification in the TOS or MFA cases because
+					// the specialized floater has already scolded the user.
 					std::string error_code;
 					if(response.has("errorcode"))
 					{
@@ -2367,8 +2368,31 @@ void show_release_notes_if_required()
         && gSavedSettings.getBOOL("UpdaterShowReleaseNotes")
         && !gSavedSettings.getBOOL("FirstLoginThisInstall"))
     {
-        LLSD info(LLAppViewer::instance()->getViewerInfo());
-        LLWeb::loadURLInternal(info["VIEWER_RELEASE_NOTES_URL"]);
+
+#if LL_RELEASE_FOR_DOWNLOAD
+        if (!gSavedSettings.getBOOL("CmdLineSkipUpdater")
+            && !LLAppViewer::instance()->isUpdaterMissing())
+        {
+            // Instantiate a "relnotes" listener which assumes any arriving event
+            // is the release notes URL string. Since "relnotes" is an
+            // LLEventMailDrop, this listener will be invoked whether or not the
+            // URL has already been posted. If so, it will fire immediately;
+            // otherwise it will fire whenever the URL is (later) posted. Either
+            // way, it will display the release notes as soon as the URL becomes
+            // available.
+            LLEventPumps::instance().obtain("relnotes").listen(
+                "showrelnotes",
+                [](const LLSD& url) {
+                LLWeb::loadURLInternal(url.asString());
+                return false;
+            });
+        }
+        else
+#endif // LL_RELEASE_FOR_DOWNLOAD
+        {
+            LLSD info(LLAppViewer::instance()->getViewerInfo());
+            LLWeb::loadURLInternal(info["VIEWER_RELEASE_NOTES_URL"]);
+        }
         release_notes_shown = true;
     }
 }
@@ -3658,6 +3682,17 @@ bool process_login_success_response()
 		LLViewerMedia::getInstance()->openIDSetup(openid_url, openid_token);
 	}
 
+
+	// Only save mfa_hash for future logins if the user wants their info remembered.
+	if(response.has("mfa_hash") && gSavedSettings.getBOOL("RememberUser") && gSavedSettings.getBOOL("RememberPassword"))
+	{
+		std::string grid(LLGridManager::getInstance()->getGridId());
+		std::string user_id(gUserCredential->userID());
+		gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, response["mfa_hash"]);
+		// TODO(brad) - related to SL-17223 consider building a better interface that sync's automatically
+		gSecAPIHandler->syncProtectedMap();
+	}
+
 	bool success = false;
 	// JC: gesture loading done below, when we have an asset system
 	// in place.  Don't delete/clear gUserCredentials until then.
diff --git a/indra/newview/llvieweroctree.cpp b/indra/newview/llvieweroctree.cpp
index 5eda75753e5f3cfb3403fd1f70f0c156af30fa24..87e296012d3249fab20fca7249d274761afb02a6 100644
--- a/indra/newview/llvieweroctree.cpp
+++ b/indra/newview/llvieweroctree.cpp
@@ -1325,8 +1325,13 @@ LLViewerOctreePartition::LLViewerOctreePartition() :
 	
 LLViewerOctreePartition::~LLViewerOctreePartition()
 {
-	delete mOctree;
-	mOctree = NULL;
+    cleanup();
+}
+
+void LLViewerOctreePartition::cleanup()
+{
+    delete mOctree;
+    mOctree = nullptr;
 }
 
 BOOL LLViewerOctreePartition::isOcclusionEnabled()
@@ -1334,6 +1339,7 @@ BOOL LLViewerOctreePartition::isOcclusionEnabled()
 	return mOcclusionEnabled || LLPipeline::sUseOcclusion > 2;
 }
 
+
 //-----------------------------------------------------------------------------------
 //class LLViewerOctreeCull definitions
 //-----------------------------------------------------------------------------------
diff --git a/indra/newview/llvieweroctree.h b/indra/newview/llvieweroctree.h
index 11ba7e4f1ec026163fa4d7f2fed6c3ebdddc16a2..e6974b0f84ef715c1fc26b4f819a5ac6c051908b 100644
--- a/indra/newview/llvieweroctree.h
+++ b/indra/newview/llvieweroctree.h
@@ -352,6 +352,10 @@ class LLViewerOctreePartition
 	virtual S32 cull(LLCamera &camera, bool do_occlusion) = 0;
 	BOOL isOcclusionEnabled();
 
+protected:
+    // MUST call from destructor of any derived classes (SL-17276)
+    void cleanup();
+
 public:	
 	U32              mPartitionType;
 	U32              mDrawableType;
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index 1cb2c6b9eee1b88c7b83ab47b1a67329d2c88a25..086b433c722198a10f911ad9f900561217386b92 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -426,6 +426,13 @@ void LLViewerShaderMgr::setShaders()
         return;
     }
 
+    if (!gGLManager.mHasRequirements)
+    {
+        // Viewer will show 'hardware requirements' warning later
+        LL_INFOS("ShaderLoading") << "Not supported hardware/software" << LL_ENDL;
+        return;
+    }
+
     static LLCachedControl<U32> max_texture_index(gSavedSettings, "RenderMaxTextureIndex", 16);
     LLGLSLShader::sIndexedTextureChannels = llmax(llmin(gGLManager.mNumTextureImageUnits, (S32) max_texture_index), 1);
 
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 42e21f7130457987d9679947ae56fb691f3fc523..8e565bbdca10e4c1fbcdc560fbc6886bfdf5303a 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -1914,12 +1914,6 @@ LLViewerWindow::LLViewerWindow(const Params& p)
 		p.ignore_pixel_depth,
 		gSavedSettings.getBOOL("RenderDeferred") ? 0 : gSavedSettings.getU32("RenderFSAASamples")); //don't use window level anti-aliasing if FBOs are enabled
 
-	if (!LLViewerShaderMgr::sInitialized)
-	{ //immediately initialize shaders
-		LLViewerShaderMgr::sInitialized = TRUE;
-		LLViewerShaderMgr::instance()->setShaders();
-	}
-
 	if (NULL == mWindow)
 	{
 		LLSplashScreen::update(LLTrans::getString("StartupRequireDriverUpdate"));
@@ -1938,6 +1932,12 @@ LLViewerWindow::LLViewerWindow(const Params& p)
 #endif
         LLAppViewer::instance()->fastQuit(1);
 	}
+    else if (!LLViewerShaderMgr::sInitialized)
+    {
+        //immediately initialize shaders
+        LLViewerShaderMgr::sInitialized = TRUE;
+        LLViewerShaderMgr::instance()->setShaders();
+    }
 	
 	if (!LLAppViewer::instance()->restoreErrorTrap())
 	{
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 33f2ec81ab33d9b5c7eb0bea209f2bff1a809c66..625fdf1977e143c8d28c519e0931a4a796ae7f43 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -2823,22 +2823,22 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update)
 				 attachment_iter != attachment->mAttachedObjects.end();
 				 ++attachment_iter)
 			{
-				LLViewerObject* attached_object = attachment_iter->get();
-				BOOL visibleAttachment = visible || (attached_object && attached_object->mDrawable.notNull() &&
-													 !(attached_object->mDrawable->getSpatialBridge() &&
-													   attached_object->mDrawable->getSpatialBridge()->getRadius() < 2.0));
+                LLViewerObject* attached_object = attachment_iter->get();
+                if (!attached_object
+                    || attached_object->isDead()
+                    || !attachment->getValid()
+                    || attached_object->mDrawable.isNull())
+                {
+                    continue;
+                }
+
+                LLSpatialBridge* bridge = attached_object->mDrawable->getSpatialBridge();
 				
-				if (visibleAttachment
-                    && attached_object
-                    && !attached_object->isDead()
-                    && attachment->getValid()
-                    && attached_object->mDrawable.notNull())
+				if (visible || !(bridge && bridge->getRadius() < 2.0))
 				{
-
                     //override rigged attachments' octree spatial extents with this avatar's bounding box
-                    LLSpatialBridge* bridge = attached_object->mDrawable->getSpatialBridge();
                     bool rigged = false;
-                    if (bridge && !bridge->isDead())
+                    if (bridge)
                     {
                         //transform avatar bounding box into attachment's coordinate frame
                         LLVector4a extents[2];
@@ -2854,8 +2854,12 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update)
                     
                     attached_object->mDrawable->makeActive();
                     attached_object->mDrawable->updateXform(TRUE);
-                    
-                    if (bridge && !bridge->isDead())
+
+                    // override_bbox calls movePartition() and getSpatialPartition(),
+                    // so bridge might no longer be valid, get it again.
+                    // ex: animesh stops being an animesh
+                    bridge = attached_object->mDrawable->getSpatialBridge();
+                    if (bridge)
                     {
                         if (!rigged)
                         {
diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp
index e10a9f9bcb5b8a20a672c724b3b7fec67fb5e542..db8ad183f0ad1048468f81ba82f1587a59cdd687 100644
--- a/indra/newview/llvocache.cpp
+++ b/indra/newview/llvocache.cpp
@@ -632,6 +632,13 @@ LLVOCachePartition::LLVOCachePartition(LLViewerRegion* regionp)
 	new LLVOCacheGroup(mOctree, this);
 }
 
+LLVOCachePartition::~LLVOCachePartition()
+{
+    // SL-17276 make sure to do base class cleanup while this instance
+    // can still be treated as an LLVOCachePartition 
+    cleanup();
+}
+
 bool LLVOCachePartition::addEntry(LLViewerOctreeEntry* entry)
 {
 	llassert(entry->hasVOCacheEntry());
diff --git a/indra/newview/llvocache.h b/indra/newview/llvocache.h
index c510ff77fcf0b6e73594e0a1aa0eb2ffd5c991a7..55a13d934dd01f4652c6943be6047250bb5f179b 100644
--- a/indra/newview/llvocache.h
+++ b/indra/newview/llvocache.h
@@ -189,6 +189,7 @@ class LLVOCachePartition : public LLViewerOctreePartition
 {
 public:
 	LLVOCachePartition(LLViewerRegion* regionp);
+    virtual ~LLVOCachePartition();
 
 	bool addEntry(LLViewerOctreeEntry* entry);
 	void removeEntry(LLViewerOctreeEntry* entry);
diff --git a/indra/newview/skins/default/xui/en/floater_mfa.xml b/indra/newview/skins/default/xui/en/floater_mfa.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a649cc6d471101fd6e9b5731c0db7c555ab47d3a
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_mfa.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ title="MFA Token Requred"
+ legacy_header_height="18"
+ can_minimize="false"
+ can_close="false"
+ height="110"
+ layout="topleft"
+ name="mfa_challenge"
+ help_topic="mfa_challenge"
+ width="550">
+    <text
+     type="string"
+     word_wrap="true"
+     length="1"
+     follows="top|left"
+     height="15"
+     layout="topleft"
+     left="10"
+     name="token_prompt_text"
+     top="20">
+        token prompt
+    </text>
+    <line_editor
+     follows="left|top|right"
+     height="19"
+     layout="topleft"
+     bottom_delta="40"
+     name="token_edit"
+     width="100" />
+    <button
+     follows="top|left"
+     height="20"
+     label="Continue"
+     layout="topleft"
+     left="10"
+     name="continue_btn"
+     bottom_delta="30"
+     width="64" />
+    <button
+     follows="top|left"
+     height="20"
+     label="Cancel"
+     layout="topleft"
+     left_pad="5"
+     name="cancel_btn"
+     bottom_delta="0"
+     width="64" />
+</floater>
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index ed6683ce2aaac367563080f99fb9ea76c8aab225..43f2d1ffa619ab0e01f18dd886283fdaa6968f71 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -9669,18 +9669,6 @@ Do you wish to continue?
      yestext="OK"/>
   </notification>
 
-  <global name="UnsupportedShaderRequirements">
-You do not appear to meet the hardware requirements for [APP_NAME]. [APP_NAME] requires OpenGL 2.0 or later shader support. If this is the case, you may want to make sure that you have the latest drivers for your graphics card, and service packs and patches for your operating system.
-
-If you continue to have problems, please visit the [SUPPORT_SITE].
-  </global>
-
-  <global name="UnsupportedGLRequirements">
-You do not appear to have the proper hardware requirements for [APP_NAME]. [APP_NAME] requires an OpenGL graphics card that has multitexture support. If this is the case, you may want to make sure that you have the latest drivers for your graphics card, and service packs and patches for your operating system.
-
-If you continue to have problems, please visit the [SUPPORT_SITE].
-  </global>
-
   <global name="UnsupportedIntelDriver">
 The installed Intel graphics driver for [GPUNAME], version [VERSION], is significantly out of date and is known to cause excessive rates of program crashes. You are strongly advised to update to a current Intel driver
 
@@ -11845,4 +11833,25 @@ Unpacking: [UNPACK_TIME]s [USIZE]KB
     <tag>fail</tag>
   </notification>
   
+  <notification
+   icon="alertmodal.tga"
+   label="Prompt for MFA Token"
+   name="PromptMFAToken"
+   type="alertmodal">
+    [MESSAGE]
+    <tag>confirm</tag>
+    <form name="form">
+      <input name="token" type="text" width="400" />
+      <button
+       default="true"
+       index="0"
+       name="continue"
+       text="Continue"/>
+      <button
+       index="1"
+       name="cancel"
+       text="Cancel"/>
+    </form>
+  </notification>
+
 </notifications>
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 1ae5b873f77609be99da3f29f60760c68193333f..6ad49764103d4be9785d79ad1010ff6e418d53f6 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -133,6 +133,7 @@ http://secondlife.com/viewer-access-faq</string>
 Please check to make sure you entered the right
     * Username (like bobsmith12 or steller.sunshine)
     * Password
+    * Second Factor Token (if enabled)
 Also, please make sure your Caps Lock key is off.</string>
 	<string name="LoginFailedPasswordChanged">As a security precaution your password has been changed.
 Please go to your account page at http://secondlife.com/password
@@ -192,7 +193,8 @@ Please try logging in again in a minute.</string>
 Please try logging in again in a minute.</string>
 	<string name="LoginFailedLoggingOutSession">The system has begun logging out your last session.
 Please try logging in again in a minute.</string>
-
+        <string name="LoginFailedAuthenticationMFARequired">To continue logging in, enter a new token from your multifactor authentication app.
+If you feel this is an error, please contact support@secondlife.com</string>
 
 	<!-- Disconnection -->
 	<string name="AgentLostConnection">This region may be experiencing trouble.  Please check your connection to the Internet.</string>
diff --git a/indra/newview/tests/lllogininstance_test.cpp b/indra/newview/tests/lllogininstance_test.cpp
index 8d1956957c7ac72a135d7aa41e018e11e793ce3e..a52c3dcef9b8923ae8a1bd317f9cf4389f71f14c 100644
--- a/indra/newview/tests/lllogininstance_test.cpp
+++ b/indra/newview/tests/lllogininstance_test.cpp
@@ -186,6 +186,10 @@ std::string LLGridManager::getAppSLURLBase(const std::string& grid_name)
 {
 	return "myappslurl";
 }
+std::string LLGridManager::getGridId(const std::string& grid)
+{
+    return std::string();
+}
 
 //-----------------------------------------------------------------------------
 #include "../llviewercontrol.h"
@@ -218,6 +222,7 @@ bool llHashedUniqueID(unsigned char* id)
 //-----------------------------------------------------------------------------
 #include "../llappviewer.h"
 void LLAppViewer::forceQuit(void) {}
+bool LLAppViewer::isUpdaterMissing() { return true; }
 LLAppViewer * LLAppViewer::sInstance = 0;
 
 //-----------------------------------------------------------------------------
@@ -226,6 +231,8 @@ LLAppViewer * LLAppViewer::sInstance = 0;
 static std::string gTOSType;
 static LLEventPump * gTOSReplyPump = NULL;
 
+LLPointer<LLSecAPIHandler> gSecAPIHandler;
+
 //static
 LLFloater* LLFloaterReg::showInstance(const std::string& name, const LLSD& key, BOOL focus)
 {
@@ -343,6 +350,7 @@ namespace tut
 			gSavedSettings.declareString("ClientSettingsFile", "test_settings.xml", "", LLControlVariable::PERSIST_NO);
 			gSavedSettings.declareString("NextLoginLocation", "", "", LLControlVariable::PERSIST_NO);
 			gSavedSettings.declareBOOL("LoginLastLocation", FALSE, "", LLControlVariable::PERSIST_NO);
+            gSavedSettings.declareBOOL("CmdLineSkipUpdater", TRUE, "", LLControlVariable::PERSIST_NO);
 
 			LLSD authenticator = LLSD::emptyMap();
 			LLSD identifier = LLSD::emptyMap();
diff --git a/indra/newview/tests/llsecapi_test.cpp b/indra/newview/tests/llsecapi_test.cpp
index 37fbbb449be47c8101593331412b1a527256fe20..7d2a9a436f5c567759492ff542e0522b27706fd4 100644
--- a/indra/newview/tests/llsecapi_test.cpp
+++ b/indra/newview/tests/llsecapi_test.cpp
@@ -62,6 +62,7 @@ LLPointer<LLCertificateStore> LLSecAPIBasicHandler::getCertificateStore(const st
 void LLSecAPIBasicHandler::setProtectedData(const std::string& data_type, const std::string& data_id, const LLSD& data) {}
 void LLSecAPIBasicHandler::addToProtectedMap(const std::string& data_type, const std::string& data_id, const std::string& map_elem, const LLSD& data) {}
 void LLSecAPIBasicHandler::removeFromProtectedMap(const std::string& data_type, const std::string& data_id, const std::string& map_elem) {}
+void LLSecAPIBasicHandler::syncProtectedMap() {}
 LLSD LLSecAPIBasicHandler::getProtectedData(const std::string& data_type, const std::string& data_id) { return LLSD(); }
 void LLSecAPIBasicHandler::deleteProtectedData(const std::string& data_type, const std::string& data_id) {}
 LLPointer<LLCredential> LLSecAPIBasicHandler::createCredential(const std::string& grid, const LLSD& identifier, const LLSD& authenticator) { return NULL; }
diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp
index 168880dc12d8e216000b703ebcb5236288f67ca8..8a7e6407ed10dda0542a25e0082686f96efeabed 100644
--- a/indra/viewer_components/login/lllogin.cpp
+++ b/indra/viewer_components/login/lllogin.cpp
@@ -251,20 +251,31 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)
                 // Since sSyncPoint is an LLEventMailDrop, we DEFINITELY want to
                 // consume the posted event.
                 LLCoros::OverrideConsuming oc(true);
-                // Timeout should produce the isUndefined() object passed here.
-                LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL;
-                LLSD updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD());
-                if (updater.isUndefined())
-                {
-                    LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login"
-                                        << LL_ENDL;
-                }
-                else
+                LLSD responses(mAuthResponse["responses"]);
+                LLSD updater;
+
+                if (printable_params["wait_for_updater"].asBoolean())
                 {
-                    LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL;
+                    std::string reason_response = responses["data"]["reason"].asString();
+                    if (reason_response == "update") // No point waiting if not an update
+                    {
+                        // Timeout should produce the isUndefined() object passed here.
+                        LL_INFOS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL;
+                        updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD());
+
+                        if (updater.isUndefined())
+                        {
+                            LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login"
+                                << LL_ENDL;
+                        }
+                        else
+                        {
+                            LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL;
+                        }
+                    }
                 }
+
                 // Let the fail.login handler deal with empty updater response.
-                LLSD responses(mAuthResponse["responses"]);
                 responses["updater"] = updater;
                 sendProgressEvent("offline", "fail.login", responses);
             }