Coverage Report

Created: 2023-09-25 06:08

/src/dropbear/src/cli-auth.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Dropbear SSH
3
 * 
4
 * Copyright (c) 2002,2003 Matt Johnston
5
 * Copyright (c) 2004 by Mihnea Stoenescu
6
 * All rights reserved.
7
 * 
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated documentation files (the "Software"), to deal
10
 * in the Software without restriction, including without limitation the rights
11
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 * copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 * 
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 * 
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
 * SOFTWARE. */
25
26
#include "includes.h"
27
#include "session.h"
28
#include "auth.h"
29
#include "dbutil.h"
30
#include "buffer.h"
31
#include "ssh.h"
32
#include "packet.h"
33
#include "runopts.h"
34
35
/* Send a "none" auth request to get available methods */
36
612
void cli_auth_getmethods() {
37
612
  TRACE(("enter cli_auth_getmethods"))
38
612
  CHECKCLEARTOWRITE();
39
612
  buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST);
40
612
  buf_putstring(ses.writepayload, cli_opts.username,
41
612
      strlen(cli_opts.username));
42
612
  buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION,
43
612
      SSH_SERVICE_CONNECTION_LEN);
44
612
  buf_putstring(ses.writepayload, "none", 4); /* 'none' method */
45
46
612
  encrypt_packet();
47
48
#if DROPBEAR_CLI_IMMEDIATE_AUTH
49
  /* We can't haven't two auth requests in-flight with delayed zlib mode
50
  since if the first one succeeds then the remote side will 
51
  expect the second one to be compressed. 
52
  Race described at
53
  http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/zlib-openssh.html
54
  */
55
  if (ses.keys->trans.algo_comp != DROPBEAR_COMP_ZLIB_DELAY) {
56
    ses.authstate.authtypes = AUTH_TYPE_PUBKEY;
57
#if DROPBEAR_USE_PASSWORD_ENV
58
    if (getenv(DROPBEAR_PASSWORD_ENV)) {
59
      ses.authstate.authtypes |= AUTH_TYPE_PASSWORD | AUTH_TYPE_INTERACT;
60
    }
61
#endif
62
    if (cli_auth_try() == DROPBEAR_SUCCESS) {
63
      TRACE(("skipped initial none auth query"))
64
      /* Note that there will be two auth responses in-flight */
65
      cli_ses.ignore_next_auth_response = 1;
66
    }
67
  }
68
#endif
69
612
  TRACE(("leave cli_auth_getmethods"))
70
612
}
71
72
909
void recv_msg_userauth_banner() {
73
74
909
  char* banner = NULL;
75
909
  unsigned int bannerlen;
76
909
  unsigned int i, linecount;
77
909
  int truncated = 0;
78
79
909
  TRACE(("enter recv_msg_userauth_banner"))
80
909
  if (ses.authstate.authdone) {
81
195
    TRACE(("leave recv_msg_userauth_banner: banner after auth done"))
82
195
    return;
83
195
  }
84
85
714
  if (cli_opts.quiet) {
86
0
    TRACE(("not showing banner"))
87
0
    return;
88
0
  }
89
90
714
  banner = buf_getstring(ses.payload, &bannerlen);
91
714
  buf_eatstring(ses.payload); /* The language string */
92
93
714
  if (bannerlen > MAX_BANNER_SIZE) {
94
34
    TRACE(("recv_msg_userauth_banner: bannerlen too long: %d", bannerlen))
95
34
    truncated = 1;
96
680
  } else {
97
680
    cleantext(banner);
98
99
    /* Limit to 24 lines */
100
680
    linecount = 1;
101
23.4k
    for (i = 0; i < bannerlen; i++) {
102
23.0k
      if (banner[i] == '\n') {
103
4.42k
        if (linecount >= MAX_BANNER_LINES) {
104
209
          banner[i] = '\0';
105
209
          truncated = 1;
106
209
          break;
107
209
        }
108
4.21k
        linecount++;
109
4.21k
      }
110
23.0k
    }
111
680
    fprintf(stderr, "%s\n", banner);
112
680
  }
113
114
714
  if (truncated) {
115
243
    fprintf(stderr, "[Banner from the server is too long]\n");
116
243
  }
117
118
714
  m_free(banner);
119
714
  TRACE(("leave recv_msg_userauth_banner"))
120
714
}
121
122
/* This handles the message-specific types which
123
 * all have a value of 60. These are
124
 * SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
125
 * SSH_MSG_USERAUTH_PK_OK, &
126
 * SSH_MSG_USERAUTH_INFO_REQUEST. */
127
2
void recv_msg_userauth_specific_60() {
128
129
2
#if DROPBEAR_CLI_PUBKEY_AUTH
130
2
  if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) {
131
0
    recv_msg_userauth_pk_ok();
132
0
    return;
133
0
  }
134
2
#endif
135
136
2
#if DROPBEAR_CLI_INTERACT_AUTH
137
2
  if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT) {
138
0
    recv_msg_userauth_info_request();
139
0
    return;
140
0
  }
141
2
#endif
142
143
2
#if DROPBEAR_CLI_PASSWORD_AUTH
144
2
  if (cli_ses.lastauthtype == AUTH_TYPE_PASSWORD) {
145
    /* Eventually there could be proper password-changing
146
     * support. However currently few servers seem to
147
     * implement it, and password auth is last-resort
148
     * regardless - keyboard-interactive is more likely
149
     * to be used anyway. */
150
0
    dropbear_close("Your password has expired.");
151
0
  }
152
2
#endif
153
154
2
  dropbear_exit("Unexpected userauth packet");
155
2
}
156
157
383
void recv_msg_userauth_failure() {
158
159
383
  char * methods = NULL;
160
383
  char * tok = NULL;
161
383
  unsigned int methlen = 0;
162
383
  unsigned int partial = 0;
163
383
  unsigned int i = 0;
164
165
383
  TRACE(("<- MSG_USERAUTH_FAILURE"))
166
383
  TRACE(("enter recv_msg_userauth_failure"))
167
168
383
  if (ses.authstate.authdone) {
169
66
    TRACE(("leave recv_msg_userauth_failure, already authdone."))
170
66
    return;
171
66
  }
172
173
317
  if (cli_ses.state != USERAUTH_REQ_SENT) {
174
    /* Perhaps we should be more fatal? */
175
2
    dropbear_exit("Unexpected userauth failure");
176
2
  }
177
178
  /* When DROPBEAR_CLI_IMMEDIATE_AUTH is set there will be an initial response for 
179
  the "none" auth request, and then a response to the immediate auth request. 
180
  We need to be careful handling them. */
181
315
  if (cli_ses.ignore_next_auth_response) {
182
0
    cli_ses.state = USERAUTH_REQ_SENT;
183
0
    cli_ses.ignore_next_auth_response = 0;
184
0
    TRACE(("leave recv_msg_userauth_failure, ignored response, state set to USERAUTH_REQ_SENT"));
185
0
    return;
186
315
  } else  {
187
315
#if DROPBEAR_CLI_PUBKEY_AUTH
188
    /* If it was a pubkey auth request, we should cross that key 
189
     * off the list. */
190
315
    if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) {
191
0
      cli_pubkeyfail();
192
0
    }
193
315
#endif
194
195
315
#if DROPBEAR_CLI_INTERACT_AUTH
196
    /* If we get a failure message for keyboard interactive without
197
     * receiving any request info packet, then we don't bother trying
198
     * keyboard interactive again */
199
315
    if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT
200
315
        && !cli_ses.interact_request_received) {
201
0
      TRACE(("setting auth_interact_failed = 1"))
202
0
      cli_ses.auth_interact_failed = 1;
203
0
    }
204
315
#endif
205
315
    cli_ses.state = USERAUTH_FAIL_RCVD;
206
315
    cli_ses.lastauthtype = AUTH_TYPE_NONE;
207
315
  }
208
209
315
  methods = buf_getstring(ses.payload, &methlen);
210
211
315
  partial = buf_getbool(ses.payload);
212
213
315
  if (partial) {
214
248
    dropbear_log(LOG_INFO, "Authentication partially succeeded, more attempts required");
215
248
  } else {
216
67
    ses.authstate.failcount++;
217
67
  }
218
219
315
  TRACE(("Methods (len %d): '%s'", methlen, methods))
220
221
315
  ses.authstate.authdone=0;
222
315
  ses.authstate.authtypes=0;
223
224
  /* Split with nulls rather than commas */
225
54.0k
  for (i = 0; i < methlen; i++) {
226
53.7k
    if (methods[i] == ',') {
227
1.56k
      methods[i] = '\0';
228
1.56k
    }
229
53.7k
  }
230
231
315
  tok = methods; /* tok stores the next method we'll compare */
232
54.3k
  for (i = 0; i <= methlen; i++) {
233
54.0k
    if (methods[i] == '\0') {
234
17.6k
      TRACE(("auth method '%s'", tok))
235
17.6k
#if DROPBEAR_CLI_PUBKEY_AUTH
236
17.6k
      if (strncmp(AUTH_METHOD_PUBKEY, tok,
237
17.6k
        AUTH_METHOD_PUBKEY_LEN) == 0) {
238
212
        ses.authstate.authtypes |= AUTH_TYPE_PUBKEY;
239
212
      }
240
17.6k
#endif
241
17.6k
#if DROPBEAR_CLI_INTERACT_AUTH
242
17.6k
      if (strncmp(AUTH_METHOD_INTERACT, tok,
243
17.6k
        AUTH_METHOD_INTERACT_LEN) == 0) {
244
195
        ses.authstate.authtypes |= AUTH_TYPE_INTERACT;
245
195
      }
246
17.6k
#endif
247
17.6k
#if DROPBEAR_CLI_PASSWORD_AUTH
248
17.6k
      if (strncmp(AUTH_METHOD_PASSWORD, tok,
249
17.6k
        AUTH_METHOD_PASSWORD_LEN) == 0) {
250
268
        ses.authstate.authtypes |= AUTH_TYPE_PASSWORD;
251
268
      }
252
17.6k
#endif
253
17.6k
      tok = &methods[i+1]; /* Must make sure we don't use it after the
254
                  last loop, since it'll point to something
255
                  undefined */
256
17.6k
    }
257
54.0k
  }
258
259
315
  m_free(methods);
260
    
261
315
  TRACE(("leave recv_msg_userauth_failure"))
262
315
}
263
264
1.84k
void recv_msg_userauth_success() {
265
  /* This function can validly get called multiple times
266
  if DROPBEAR_CLI_IMMEDIATE_AUTH is set */
267
268
1.84k
  DEBUG1(("received msg_userauth_success"))
269
1.84k
  if (cli_opts.disable_trivial_auth && cli_ses.is_trivial_auth) {
270
0
    dropbear_exit("trivial authentication not allowed");
271
0
  }
272
  /* Note: in delayed-zlib mode, setting authdone here 
273
   * will enable compression in the transport layer */
274
1.84k
  ses.authstate.authdone = 1;
275
1.84k
  cli_ses.state = USERAUTH_SUCCESS_RCVD;
276
1.84k
  cli_ses.lastauthtype = AUTH_TYPE_NONE;
277
278
1.84k
#if DROPBEAR_CLI_PUBKEY_AUTH
279
1.84k
  cli_auth_pubkey_cleanup();
280
1.84k
#endif
281
1.84k
}
282
283
0
int cli_auth_try() {
284
285
0
  int finished = 0;
286
0
  TRACE(("enter cli_auth_try"))
287
288
0
  CHECKCLEARTOWRITE();
289
  
290
  /* Order to try is pubkey, interactive, password.
291
   * As soon as "finished" is set for one, we don't do any more. */
292
0
#if DROPBEAR_CLI_PUBKEY_AUTH
293
0
  if (ses.authstate.authtypes & AUTH_TYPE_PUBKEY) {
294
0
    finished = cli_auth_pubkey();
295
0
    cli_ses.lastauthtype = AUTH_TYPE_PUBKEY;
296
0
  }
297
0
#endif
298
299
0
#if DROPBEAR_CLI_INTERACT_AUTH
300
0
  if (!finished && (ses.authstate.authtypes & AUTH_TYPE_INTERACT)) {
301
0
    if (ses.keys->trans.algo_crypt->cipherdesc == NULL) {
302
0
      fprintf(stderr, "Sorry, I won't let you use interactive auth unencrypted.\n");
303
0
    } else {
304
0
      if (!cli_ses.auth_interact_failed) {
305
0
        cli_auth_interactive();
306
0
        cli_ses.lastauthtype = AUTH_TYPE_INTERACT;
307
0
        finished = 1;
308
0
      }
309
0
    }
310
0
  }
311
0
#endif
312
313
0
#if DROPBEAR_CLI_PASSWORD_AUTH
314
0
  if (!finished && (ses.authstate.authtypes & AUTH_TYPE_PASSWORD)) {
315
0
    if (ses.keys->trans.algo_crypt->cipherdesc == NULL) {
316
0
      fprintf(stderr, "Sorry, I won't let you use password auth unencrypted.\n");
317
0
    } else {
318
0
      cli_auth_password();
319
0
      finished = 1;
320
0
      cli_ses.lastauthtype = AUTH_TYPE_PASSWORD;
321
0
    }
322
0
  }
323
0
#endif
324
325
0
  TRACE(("cli_auth_try lastauthtype %d", cli_ses.lastauthtype))
326
327
0
  if (finished) {
328
0
    TRACE(("leave cli_auth_try success"))
329
0
    return DROPBEAR_SUCCESS;
330
0
  }
331
0
  TRACE(("leave cli_auth_try failure"))
332
0
  return DROPBEAR_FAILURE;
333
0
}
334
335
#if DROPBEAR_CLI_PASSWORD_AUTH || DROPBEAR_CLI_INTERACT_AUTH
336
/* A helper for getpass() that exits if the user cancels. The returned
337
 * password is statically allocated by getpass() */
338
char* getpass_or_cancel(const char* prompt)
339
0
{
340
0
  char* password = NULL;
341
  
342
0
#if DROPBEAR_USE_PASSWORD_ENV
343
  /* Password provided in an environment var */
344
0
  password = getenv(DROPBEAR_PASSWORD_ENV);
345
0
  if (password)
346
0
  {
347
0
    return password;
348
0
  }
349
0
#endif
350
351
0
  password = getpass(prompt);
352
353
  /* 0x03 is a ctrl-c character in the buffer. */
354
0
  if (password == NULL || strchr(password, '\3') != NULL) {
355
0
    dropbear_close("Interrupted.");
356
0
  }
357
0
  return password;
358
0
}
359
#endif