Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/extensions/auth/nsAuthSambaNTLM.cpp
Line
Count
Source (jump to first uncovered line)
1
/* vim:set ts=4 sw=4 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 "nsAuth.h"
7
#include "nsAuthSambaNTLM.h"
8
#include "nsMemory.h"
9
#include "nspr.h"
10
#include "prenv.h"
11
#include "plbase64.h"
12
#include "prerror.h"
13
#include "mozilla/Telemetry.h"
14
15
#include <stdlib.h>
16
17
nsAuthSambaNTLM::nsAuthSambaNTLM()
18
    : mInitialMessage(nullptr), mChildPID(nullptr), mFromChildFD(nullptr),
19
      mToChildFD(nullptr)
20
0
{
21
0
}
22
23
nsAuthSambaNTLM::~nsAuthSambaNTLM()
24
0
{
25
0
    // ntlm_auth reads from stdin regularly so closing our file handles
26
0
    // should cause it to exit.
27
0
    Shutdown();
28
0
    PR_Free(mInitialMessage);
29
0
}
30
31
void
32
nsAuthSambaNTLM::Shutdown()
33
0
{
34
0
    if (mFromChildFD) {
35
0
        PR_Close(mFromChildFD);
36
0
        mFromChildFD = nullptr;
37
0
    }
38
0
    if (mToChildFD) {
39
0
        PR_Close(mToChildFD);
40
0
        mToChildFD = nullptr;
41
0
    }
42
0
    if (mChildPID) {
43
0
        int32_t exitCode;
44
0
        PR_WaitProcess(mChildPID, &exitCode);
45
0
        mChildPID = nullptr;
46
0
    }
47
0
}
48
49
NS_IMPL_ISUPPORTS(nsAuthSambaNTLM, nsIAuthModule)
50
51
static bool
52
SpawnIOChild(char* const* aArgs, PRProcess** aPID,
53
             PRFileDesc** aFromChildFD, PRFileDesc** aToChildFD)
54
0
{
55
0
    PRFileDesc* toChildPipeRead;
56
0
    PRFileDesc* toChildPipeWrite;
57
0
    if (PR_CreatePipe(&toChildPipeRead, &toChildPipeWrite) != PR_SUCCESS)
58
0
        return false;
59
0
    PR_SetFDInheritable(toChildPipeRead, true);
60
0
    PR_SetFDInheritable(toChildPipeWrite, false);
61
0
62
0
    PRFileDesc* fromChildPipeRead;
63
0
    PRFileDesc* fromChildPipeWrite;
64
0
    if (PR_CreatePipe(&fromChildPipeRead, &fromChildPipeWrite) != PR_SUCCESS) {
65
0
        PR_Close(toChildPipeRead);
66
0
        PR_Close(toChildPipeWrite);
67
0
        return false;
68
0
    }
69
0
    PR_SetFDInheritable(fromChildPipeRead, false);
70
0
    PR_SetFDInheritable(fromChildPipeWrite, true);
71
0
72
0
    PRProcessAttr* attr = PR_NewProcessAttr();
73
0
    if (!attr) {
74
0
        PR_Close(fromChildPipeRead);
75
0
        PR_Close(fromChildPipeWrite);
76
0
        PR_Close(toChildPipeRead);
77
0
        PR_Close(toChildPipeWrite);
78
0
        return false;
79
0
    }
80
0
81
0
    PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, toChildPipeRead);
82
0
    PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, fromChildPipeWrite);
83
0
84
0
    PRProcess* process = PR_CreateProcess(aArgs[0], aArgs, nullptr, attr);
85
0
    PR_DestroyProcessAttr(attr);
86
0
    PR_Close(fromChildPipeWrite);
87
0
    PR_Close(toChildPipeRead);
88
0
    if (!process) {
89
0
        LOG(("ntlm_auth exec failure [%d]", PR_GetError()));
90
0
        PR_Close(fromChildPipeRead);
91
0
        PR_Close(toChildPipeWrite);
92
0
        return false;
93
0
    }
94
0
95
0
    *aPID = process;
96
0
    *aFromChildFD = fromChildPipeRead;
97
0
    *aToChildFD = toChildPipeWrite;
98
0
    return true;
99
0
}
100
101
static bool WriteString(PRFileDesc* aFD, const nsACString& aString)
102
0
{
103
0
    int32_t length = aString.Length();
104
0
    const char* s = aString.BeginReading();
105
0
    LOG(("Writing to ntlm_auth: %s", s));
106
0
107
0
    while (length > 0) {
108
0
        int result = PR_Write(aFD, s, length);
109
0
        if (result <= 0)
110
0
            return false;
111
0
        s += result;
112
0
        length -= result;
113
0
    }
114
0
    return true;
115
0
}
116
117
static bool ReadLine(PRFileDesc* aFD, nsACString& aString)
118
0
{
119
0
    // ntlm_auth is defined to only send one line in response to each of our
120
0
    // input lines. So this simple unbuffered strategy works as long as we
121
0
    // read the response immediately after sending one request.
122
0
    aString.Truncate();
123
0
    for (;;) {
124
0
        char buf[1024];
125
0
        int result = PR_Read(aFD, buf, sizeof(buf));
126
0
        if (result <= 0)
127
0
            return false;
128
0
        aString.Append(buf, result);
129
0
        if (buf[result - 1] == '\n') {
130
0
            LOG(("Read from ntlm_auth: %s", nsPromiseFlatCString(aString).get()));
131
0
            return true;
132
0
        }
133
0
    }
134
0
}
135
136
/**
137
 * Returns a heap-allocated array of PRUint8s, and stores the length in aLen.
138
 * Returns nullptr if there's an error of any kind.
139
 */
140
static uint8_t* ExtractMessage(const nsACString& aLine, uint32_t* aLen)
141
0
{
142
0
    // ntlm_auth sends blobs to us as base64-encoded strings after the "xx "
143
0
    // preamble on the response line.
144
0
    int32_t length = aLine.Length();
145
0
    // The caller should verify there is a valid "xx " prefix and the line
146
0
    // is terminated with a \n
147
0
    NS_ASSERTION(length >= 4, "Line too short...");
148
0
    const char* line = aLine.BeginReading();
149
0
    const char* s = line + 3;
150
0
    length -= 4; // lose first 3 chars plus trailing \n
151
0
    NS_ASSERTION(s[length] == '\n', "aLine not newline-terminated");
152
0
153
0
    if (length & 3) {
154
0
        // The base64 encoded block must be multiple of 4. If not, something
155
0
        // screwed up.
156
0
        NS_WARNING("Base64 encoded block should be a multiple of 4 chars");
157
0
        return nullptr;
158
0
    }
159
0
160
0
    // Calculate the exact length. I wonder why there isn't a function for this
161
0
    // in plbase64.
162
0
    int32_t numEquals;
163
0
    for (numEquals = 0; numEquals < length; ++numEquals) {
164
0
        if (s[length - 1 - numEquals] != '=')
165
0
            break;
166
0
    }
167
0
    *aLen = (length/4)*3 - numEquals;
168
0
    return reinterpret_cast<uint8_t*>(PL_Base64Decode(s, length, nullptr));
169
0
}
170
171
nsresult
172
nsAuthSambaNTLM::SpawnNTLMAuthHelper()
173
0
{
174
0
    const char* username = PR_GetEnv("USER");
175
0
    if (!username)
176
0
        return NS_ERROR_FAILURE;
177
0
178
0
    const char* const args[] = {
179
0
        "ntlm_auth",
180
0
        "--helper-protocol", "ntlmssp-client-1",
181
0
        "--use-cached-creds",
182
0
        "--username", username,
183
0
        nullptr
184
0
    };
185
0
186
0
    bool isOK = SpawnIOChild(const_cast<char* const*>(args), &mChildPID, &mFromChildFD, &mToChildFD);
187
0
    if (!isOK)
188
0
        return NS_ERROR_FAILURE;
189
0
190
0
    if (!WriteString(mToChildFD, NS_LITERAL_CSTRING("YR\n")))
191
0
        return NS_ERROR_FAILURE;
192
0
    nsCString line;
193
0
    if (!ReadLine(mFromChildFD, line))
194
0
        return NS_ERROR_FAILURE;
195
0
    if (!StringBeginsWith(line, NS_LITERAL_CSTRING("YR "))) {
196
0
        // Something went wrong. Perhaps no credentials are accessible.
197
0
        return NS_ERROR_FAILURE;
198
0
    }
199
0
200
0
    // It gave us an initial client-to-server request packet. Save that
201
0
    // because we'll need it later.
202
0
    mInitialMessage = ExtractMessage(line, &mInitialMessageLen);
203
0
    if (!mInitialMessage)
204
0
        return NS_ERROR_FAILURE;
205
0
    return NS_OK;
206
0
}
207
208
NS_IMETHODIMP
209
nsAuthSambaNTLM::Init(const char *serviceName,
210
                      uint32_t    serviceFlags,
211
                      const char16_t *domain,
212
                      const char16_t *username,
213
                      const char16_t *password)
214
0
{
215
0
    NS_ASSERTION(!username && !domain && !password, "unexpected credentials");
216
0
217
0
    static bool sTelemetrySent = false;
218
0
    if (!sTelemetrySent) {
219
0
        mozilla::Telemetry::Accumulate(
220
0
            mozilla::Telemetry::NTLM_MODULE_USED_2,
221
0
            serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
222
0
                ? NTLM_MODULE_SAMBA_AUTH_PROXY
223
0
                : NTLM_MODULE_SAMBA_AUTH_DIRECT);
224
0
        sTelemetrySent = true;
225
0
    }
226
0
227
0
    return NS_OK;
228
0
}
229
230
NS_IMETHODIMP
231
nsAuthSambaNTLM::GetNextToken(const void *inToken,
232
                              uint32_t    inTokenLen,
233
                              void      **outToken,
234
                              uint32_t   *outTokenLen)
235
0
{
236
0
    if (!inToken) {
237
0
        /* someone wants our initial message */
238
0
        *outToken = moz_xmemdup(mInitialMessage, mInitialMessageLen);
239
0
        *outTokenLen = mInitialMessageLen;
240
0
        return NS_OK;
241
0
    }
242
0
243
0
    /* inToken must be a type 2 message. Get ntlm_auth to generate our response */
244
0
    char* encoded = PL_Base64Encode(static_cast<const char*>(inToken), inTokenLen, nullptr);
245
0
    if (!encoded)
246
0
        return NS_ERROR_OUT_OF_MEMORY;
247
0
248
0
    nsCString request;
249
0
    request.AssignLiteral("TT ");
250
0
    request.Append(encoded);
251
0
    PR_Free(encoded);
252
0
    request.Append('\n');
253
0
254
0
    if (!WriteString(mToChildFD, request))
255
0
        return NS_ERROR_FAILURE;
256
0
    nsCString line;
257
0
    if (!ReadLine(mFromChildFD, line))
258
0
        return NS_ERROR_FAILURE;
259
0
    if (!StringBeginsWith(line, NS_LITERAL_CSTRING("KK ")) &&
260
0
        !StringBeginsWith(line, NS_LITERAL_CSTRING("AF "))) {
261
0
        // Something went wrong. Perhaps no credentials are accessible.
262
0
        return NS_ERROR_FAILURE;
263
0
    }
264
0
    uint8_t* buf = ExtractMessage(line, outTokenLen);
265
0
    if (!buf)
266
0
        return NS_ERROR_FAILURE;
267
0
    *outToken = moz_xmemdup(buf, *outTokenLen);
268
0
    PR_Free(buf);
269
0
270
0
    // We're done. Close our file descriptors now and reap the helper
271
0
    // process.
272
0
    Shutdown();
273
0
    return NS_SUCCESS_AUTH_FINISHED;
274
0
}
275
276
NS_IMETHODIMP
277
nsAuthSambaNTLM::Unwrap(const void *inToken,
278
                        uint32_t    inTokenLen,
279
                        void      **outToken,
280
                        uint32_t   *outTokenLen)
281
0
{
282
0
    return NS_ERROR_NOT_IMPLEMENTED;
283
0
}
284
285
NS_IMETHODIMP
286
nsAuthSambaNTLM::Wrap(const void *inToken,
287
                      uint32_t    inTokenLen,
288
                      bool        confidential,
289
                      void      **outToken,
290
                      uint32_t   *outTokenLen)
291
0
{
292
0
    return NS_ERROR_NOT_IMPLEMENTED;
293
0
}