nsAuthSambaNTLM.cpp (7904B)
1 /* vim:set ts=4 sw=2 et cindent: */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "base/process_util.h" 7 #include "nsAuth.h" 8 #include "nsAuthSambaNTLM.h" 9 #include "nspr.h" 10 #include "prenv.h" 11 #include "prerror.h" 12 #include "mozilla/glean/SecurityManagerSslMetrics.h" 13 #include "mozilla/Base64.h" 14 15 #include <stdlib.h> 16 #include <sys/wait.h> 17 18 nsAuthSambaNTLM::nsAuthSambaNTLM() = default; 19 20 nsAuthSambaNTLM::~nsAuthSambaNTLM() { 21 // ntlm_auth reads from stdin regularly so closing our file handles 22 // should cause it to exit. 23 Shutdown(); 24 PR_Free(mInitialMessage); 25 } 26 27 void nsAuthSambaNTLM::Shutdown() { 28 mFromChildFD = nullptr; 29 mToChildFD = nullptr; 30 31 if (mChildPID != -1) { 32 // Kill and wait for the process to exit. 33 kill(mChildPID, SIGKILL); 34 35 int status = 0; 36 pid_t result; 37 do { 38 result = waitpid(mChildPID, &status, 0); 39 } while (result == -1 && errno == EINTR); 40 41 mChildPID = -1; 42 } 43 } 44 45 NS_IMPL_ISUPPORTS(nsAuthSambaNTLM, nsIAuthModule) 46 47 [[nodiscard]] static bool CreatePipe(mozilla::UniqueFileHandle* aReadPipe, 48 mozilla::UniqueFileHandle* aWritePipe) { 49 int fds[2]; 50 if (pipe(fds) == -1) { 51 return false; 52 } 53 54 aReadPipe->reset(fds[0]); 55 aWritePipe->reset(fds[1]); 56 return true; 57 } 58 59 static bool WriteString(const mozilla::UniqueFileHandle& aFD, 60 const nsACString& aString) { 61 size_t length = aString.Length(); 62 const char* s = aString.BeginReading(); 63 LOG(("Writing to ntlm_auth: %s", s)); 64 65 while (length > 0) { 66 ssize_t result; 67 do { 68 result = write(aFD.get(), s, length); 69 } while (result == -1 && errno == EINTR); 70 if (result <= 0) return false; 71 s += result; 72 length -= result; 73 } 74 return true; 75 } 76 77 static bool ReadLine(const mozilla::UniqueFileHandle& aFD, 78 nsACString& aString) { 79 // ntlm_auth is defined to only send one line in response to each of our 80 // input lines. So this simple unbuffered strategy works as long as we 81 // read the response immediately after sending one request. 82 aString.Truncate(); 83 for (;;) { 84 char buf[1024]; 85 ssize_t result; 86 do { 87 result = read(aFD.get(), buf, sizeof(buf)); 88 } while (result == -1 && errno == EINTR); 89 if (result <= 0) return false; 90 aString.Append(buf, result); 91 if (buf[result - 1] == '\n') { 92 LOG(("Read from ntlm_auth: %s", nsPromiseFlatCString(aString).get())); 93 return true; 94 } 95 } 96 } 97 98 /** 99 * Returns a heap-allocated array of PRUint8s, and stores the length in aLen. 100 * Returns nullptr if there's an error of any kind. 101 */ 102 static uint8_t* ExtractMessage(const nsACString& aLine, uint32_t* aLen) { 103 // ntlm_auth sends blobs to us as base64-encoded strings after the "xx " 104 // preamble on the response line. 105 int32_t length = aLine.Length(); 106 // The caller should verify there is a valid "xx " prefix and the line 107 // is terminated with a \n 108 NS_ASSERTION(length >= 4, "Line too short..."); 109 const char* line = aLine.BeginReading(); 110 const char* s = line + 3; 111 length -= 4; // lose first 3 chars plus trailing \n 112 NS_ASSERTION(s[length] == '\n', "aLine not newline-terminated"); 113 114 if (length & 3) { 115 // The base64 encoded block must be multiple of 4. If not, something 116 // screwed up. 117 NS_WARNING("Base64 encoded block should be a multiple of 4 chars"); 118 return nullptr; 119 } 120 121 char* base64; 122 if (NS_FAILED(mozilla::Base64Decode(s, length, &base64, aLen))) { 123 return nullptr; 124 } 125 return (uint8_t*)base64; 126 } 127 128 nsresult nsAuthSambaNTLM::SpawnNTLMAuthHelper() { 129 const char* username = PR_GetEnv("USER"); 130 if (!username) return NS_ERROR_FAILURE; 131 132 // Use base::LaunchApp to run the child process. This code is posix-only, as 133 // this will not be used on Windows. 134 { 135 mozilla::UniqueFileHandle toChildPipeRead; 136 mozilla::UniqueFileHandle toChildPipeWrite; 137 if (!CreatePipe(&toChildPipeRead, &toChildPipeWrite)) { 138 return NS_ERROR_FAILURE; 139 } 140 141 mozilla::UniqueFileHandle fromChildPipeRead; 142 mozilla::UniqueFileHandle fromChildPipeWrite; 143 if (!CreatePipe(&fromChildPipeRead, &fromChildPipeWrite)) { 144 return NS_ERROR_FAILURE; 145 } 146 147 base::LaunchOptions options; 148 options.fds_to_remap.push_back( 149 std::pair{toChildPipeRead.get(), STDIN_FILENO}); 150 options.fds_to_remap.push_back( 151 std::pair{fromChildPipeWrite.get(), STDOUT_FILENO}); 152 153 std::vector<std::string> argvVec{"ntlm_auth", "--helper-protocol", 154 "ntlmssp-client-1", "--use-cached-creds", 155 "--username", username}; 156 157 auto result = base::LaunchApp(argvVec, std::move(options), &mChildPID); 158 if (result.isErr()) { 159 return NS_ERROR_FAILURE; 160 } 161 162 mToChildFD = std::move(toChildPipeWrite); 163 mFromChildFD = std::move(fromChildPipeRead); 164 } 165 166 if (!WriteString(mToChildFD, "YR\n"_ns)) return NS_ERROR_FAILURE; 167 nsCString line; 168 if (!ReadLine(mFromChildFD, line)) return NS_ERROR_FAILURE; 169 if (!StringBeginsWith(line, "YR "_ns)) { 170 // Something went wrong. Perhaps no credentials are accessible. 171 return NS_ERROR_FAILURE; 172 } 173 174 // It gave us an initial client-to-server request packet. Save that 175 // because we'll need it later. 176 mInitialMessage = ExtractMessage(line, &mInitialMessageLen); 177 if (!mInitialMessage) return NS_ERROR_FAILURE; 178 return NS_OK; 179 } 180 181 NS_IMETHODIMP 182 nsAuthSambaNTLM::Init(const nsACString& serviceName, uint32_t serviceFlags, 183 const nsAString& domain, const nsAString& username, 184 const nsAString& password) { 185 NS_ASSERTION(username.IsEmpty() && domain.IsEmpty() && password.IsEmpty(), 186 "unexpected credentials"); 187 188 static bool sTelemetrySent = false; 189 if (!sTelemetrySent) { 190 mozilla::glean::security::ntlm_module_used.AccumulateSingleSample( 191 serviceFlags & nsIAuthModule::REQ_PROXY_AUTH 192 ? NTLM_MODULE_SAMBA_AUTH_PROXY 193 : NTLM_MODULE_SAMBA_AUTH_DIRECT); 194 sTelemetrySent = true; 195 } 196 197 return NS_OK; 198 } 199 200 NS_IMETHODIMP 201 nsAuthSambaNTLM::GetNextToken(const void* inToken, uint32_t inTokenLen, 202 void** outToken, uint32_t* outTokenLen) { 203 if (!inToken) { 204 /* someone wants our initial message */ 205 *outToken = moz_xmemdup(mInitialMessage, mInitialMessageLen); 206 *outTokenLen = mInitialMessageLen; 207 return NS_OK; 208 } 209 210 /* inToken must be a type 2 message. Get ntlm_auth to generate our response */ 211 nsCString request; 212 request.AssignLiteral("TT "); 213 if (NS_FAILED(mozilla::Base64EncodeAppend(static_cast<const char*>(inToken), 214 inTokenLen, request))) { 215 return NS_ERROR_OUT_OF_MEMORY; 216 } 217 request.Append('\n'); 218 219 if (!WriteString(mToChildFD, request)) return NS_ERROR_FAILURE; 220 nsCString line; 221 if (!ReadLine(mFromChildFD, line)) return NS_ERROR_FAILURE; 222 if (!StringBeginsWith(line, "KK "_ns) && !StringBeginsWith(line, "AF "_ns)) { 223 // Something went wrong. Perhaps no credentials are accessible. 224 return NS_ERROR_FAILURE; 225 } 226 uint8_t* buf = ExtractMessage(line, outTokenLen); 227 if (!buf) return NS_ERROR_FAILURE; 228 *outToken = moz_xmemdup(buf, *outTokenLen); 229 PR_Free(buf); 230 231 // We're done. Close our file descriptors now and reap the helper 232 // process. 233 Shutdown(); 234 return NS_SUCCESS_AUTH_FINISHED; 235 } 236 237 NS_IMETHODIMP 238 nsAuthSambaNTLM::Unwrap(const void* inToken, uint32_t inTokenLen, 239 void** outToken, uint32_t* outTokenLen) { 240 return NS_ERROR_NOT_IMPLEMENTED; 241 } 242 243 NS_IMETHODIMP 244 nsAuthSambaNTLM::Wrap(const void* inToken, uint32_t inTokenLen, 245 bool confidential, void** outToken, 246 uint32_t* outTokenLen) { 247 return NS_ERROR_NOT_IMPLEMENTED; 248 }