Coverage Report

Created: 2024-09-08 06:28

/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
854
void cli_auth_getmethods() {
37
854
  TRACE(("enter cli_auth_getmethods"))
38
854
  CHECKCLEARTOWRITE();
39
854
  buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST);
40
854
  buf_putstring(ses.writepayload, cli_opts.username,
41
854
      strlen(cli_opts.username));
42
854
  buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION,
43
854
      SSH_SERVICE_CONNECTION_LEN);
44
854
  buf_putstring(ses.writepayload, "none", 4); /* 'none' method */
45
46
854
  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
854
  TRACE(("leave cli_auth_getmethods"))
70
854
}
71
72
895
void recv_msg_userauth_banner() {
73
74
895
  char* banner = NULL;
75
895
  unsigned int bannerlen;
76
895
  unsigned int i, linecount;
77
895
  int truncated = 0;
78
79
895
  TRACE(("enter recv_msg_userauth_banner"))
80
895
  if (ses.authstate.authdone) {
81
197
    TRACE(("leave recv_msg_userauth_banner: banner after auth done"))
82
197
    return;
83
197
  }
84
85
698
  if (cli_opts.quiet) {
86
0
    TRACE(("not showing banner"))
87
0
    return;
88
0
  }
89
90
698
  banner = buf_getstring(ses.payload, &bannerlen);
91
698
  buf_eatstring(ses.payload); /* The language string */
92
93
698
  if (bannerlen > MAX_BANNER_SIZE) {
94
34
    TRACE(("recv_msg_userauth_banner: bannerlen too long: %d", bannerlen))
95
34
    truncated = 1;
96
664
  } else {
97
664
    cleantext(banner);
98
99
    /* Limit to 24 lines */
100
664
    linecount = 1;
101
19.3k
    for (i = 0; i < bannerlen; i++) {
102
18.8k
      if (banner[i] == '\n') {
103
4.23k
        if (linecount >= MAX_BANNER_LINES) {
104
204
          banner[i] = '\0';
105
204
          truncated = 1;
106
204
          break;
107
204
        }
108
4.03k
        linecount++;
109
4.03k
      }
110
18.8k
    }
111
664
    fprintf(stderr, "%s\n", banner);
112
664
  }
113
114
698
  if (truncated) {
115
238
    fprintf(stderr, "[Banner from the server is too long]\n");
116
238
  }
117
118
698
  m_free(banner);
119
698
  TRACE(("leave recv_msg_userauth_banner"))
120
698
}
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
513
void recv_msg_userauth_failure() {
158
159
513
  char * methods = NULL;
160
513
  char * tok = NULL;
161
513
  unsigned int methlen = 0;
162
513
  unsigned int partial = 0;
163
513
  unsigned int i = 0;
164
513
  int allow_pw_auth = 1;
165
166
513
  TRACE(("<- MSG_USERAUTH_FAILURE"))
167
513
  TRACE(("enter recv_msg_userauth_failure"))
168
169
513
  if (ses.authstate.authdone) {
170
197
    TRACE(("leave recv_msg_userauth_failure, already authdone."))
171
197
    return;
172
197
  }
173
174
316
  if (cli_ses.state != USERAUTH_REQ_SENT) {
175
    /* Perhaps we should be more fatal? */
176
1
    dropbear_exit("Unexpected userauth failure");
177
1
  }
178
179
  /* Password authentication is only allowed in batch mode
180
   * when a password can be provided non-interactively */
181
315
  if (cli_opts.batch_mode && !getenv(DROPBEAR_PASSWORD_ENV)) {
182
0
    allow_pw_auth = 0;
183
0
  }
184
315
  allow_pw_auth &= cli_opts.password_authentication;
185
186
  /* When DROPBEAR_CLI_IMMEDIATE_AUTH is set there will be an initial response for 
187
  the "none" auth request, and then a response to the immediate auth request. 
188
  We need to be careful handling them. */
189
315
  if (cli_ses.ignore_next_auth_response) {
190
0
    cli_ses.state = USERAUTH_REQ_SENT;
191
0
    cli_ses.ignore_next_auth_response = 0;
192
0
    TRACE(("leave recv_msg_userauth_failure, ignored response, state set to USERAUTH_REQ_SENT"));
193
0
    return;
194
315
  } else  {
195
315
#if DROPBEAR_CLI_PUBKEY_AUTH
196
    /* If it was a pubkey auth request, we should cross that key 
197
     * off the list. */
198
315
    if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) {
199
0
      cli_pubkeyfail();
200
0
    }
201
315
#endif
202
203
315
#if DROPBEAR_CLI_INTERACT_AUTH
204
    /* If we get a failure message for keyboard interactive without
205
     * receiving any request info packet, then we don't bother trying
206
     * keyboard interactive again */
207
315
    if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT
208
315
        && !cli_ses.interact_request_received) {
209
0
      TRACE(("setting auth_interact_failed = 1"))
210
0
      cli_ses.auth_interact_failed = 1;
211
0
    }
212
315
#endif
213
315
    cli_ses.state = USERAUTH_FAIL_RCVD;
214
315
    cli_ses.lastauthtype = AUTH_TYPE_NONE;
215
315
  }
216
217
315
  methods = buf_getstring(ses.payload, &methlen);
218
219
315
  partial = buf_getbool(ses.payload);
220
221
315
  if (partial) {
222
243
    dropbear_log(LOG_INFO, "Authentication partially succeeded, more attempts required");
223
243
  } else {
224
72
    ses.authstate.failcount++;
225
72
  }
226
227
315
  TRACE(("Methods (len %d): '%s'", methlen, methods))
228
229
315
  ses.authstate.authdone=0;
230
315
  ses.authstate.authtypes=0;
231
232
  /* Split with nulls rather than commas */
233
54.7k
  for (i = 0; i < methlen; i++) {
234
54.4k
    if (methods[i] == ',') {
235
1.12k
      methods[i] = '\0';
236
1.12k
    }
237
54.4k
  }
238
239
315
  tok = methods; /* tok stores the next method we'll compare */
240
55.0k
  for (i = 0; i <= methlen; i++) {
241
54.7k
    if (methods[i] == '\0') {
242
14.3k
      TRACE(("auth method '%s'", tok))
243
14.3k
#if DROPBEAR_CLI_PUBKEY_AUTH
244
14.3k
      if (strncmp(AUTH_METHOD_PUBKEY, tok,
245
14.3k
        AUTH_METHOD_PUBKEY_LEN) == 0) {
246
200
        ses.authstate.authtypes |= AUTH_TYPE_PUBKEY;
247
200
      }
248
14.3k
#endif
249
14.3k
#if DROPBEAR_CLI_INTERACT_AUTH
250
14.3k
      if (allow_pw_auth
251
14.3k
        && strncmp(AUTH_METHOD_INTERACT, tok, AUTH_METHOD_INTERACT_LEN) == 0) {
252
195
        ses.authstate.authtypes |= AUTH_TYPE_INTERACT;
253
195
      }
254
14.3k
#endif
255
14.3k
#if DROPBEAR_CLI_PASSWORD_AUTH
256
14.3k
      if (allow_pw_auth
257
14.3k
        && strncmp(AUTH_METHOD_PASSWORD, tok, AUTH_METHOD_PASSWORD_LEN) == 0) {
258
210
        ses.authstate.authtypes |= AUTH_TYPE_PASSWORD;
259
210
      }
260
14.3k
#endif
261
14.3k
      tok = &methods[i+1]; /* Must make sure we don't use it after the
262
                  last loop, since it'll point to something
263
                  undefined */
264
14.3k
    }
265
54.7k
  }
266
267
315
  m_free(methods);
268
    
269
315
  TRACE(("leave recv_msg_userauth_failure"))
270
315
}
271
272
1.85k
void recv_msg_userauth_success() {
273
  /* This function can validly get called multiple times
274
  if DROPBEAR_CLI_IMMEDIATE_AUTH is set */
275
276
1.85k
  DEBUG1(("received msg_userauth_success"))
277
1.85k
  if (cli_opts.disable_trivial_auth && cli_ses.is_trivial_auth) {
278
0
    dropbear_exit("trivial authentication not allowed");
279
0
  }
280
  /* Note: in delayed-zlib mode, setting authdone here 
281
   * will enable compression in the transport layer */
282
1.85k
  ses.authstate.authdone = 1;
283
1.85k
  cli_ses.state = USERAUTH_SUCCESS_RCVD;
284
1.85k
  cli_ses.lastauthtype = AUTH_TYPE_NONE;
285
286
1.85k
#if DROPBEAR_CLI_PUBKEY_AUTH
287
1.85k
  cli_auth_pubkey_cleanup();
288
1.85k
#endif
289
1.85k
}
290
291
0
int cli_auth_try() {
292
293
0
  int finished = 0;
294
0
  TRACE(("enter cli_auth_try"))
295
296
0
  CHECKCLEARTOWRITE();
297
  
298
  /* Order to try is pubkey, interactive, password.
299
   * As soon as "finished" is set for one, we don't do any more. */
300
0
#if DROPBEAR_CLI_PUBKEY_AUTH
301
0
  if (ses.authstate.authtypes & AUTH_TYPE_PUBKEY) {
302
0
    finished = cli_auth_pubkey();
303
0
    cli_ses.lastauthtype = AUTH_TYPE_PUBKEY;
304
0
  }
305
0
#endif
306
307
0
#if DROPBEAR_CLI_INTERACT_AUTH
308
0
  if (!finished && cli_opts.password_authentication && (ses.authstate.authtypes & AUTH_TYPE_INTERACT)) {
309
0
    if (ses.keys->trans.algo_crypt->cipherdesc == NULL) {
310
0
      fprintf(stderr, "Sorry, I won't let you use interactive auth unencrypted.\n");
311
0
    } else {
312
0
      if (!cli_ses.auth_interact_failed) {
313
0
        cli_auth_interactive();
314
0
        cli_ses.lastauthtype = AUTH_TYPE_INTERACT;
315
0
        finished = 1;
316
0
      }
317
0
    }
318
0
  }
319
0
#endif
320
321
0
#if DROPBEAR_CLI_PASSWORD_AUTH
322
0
  if (!finished && cli_opts.password_authentication && (ses.authstate.authtypes & AUTH_TYPE_PASSWORD)) {
323
0
    if (ses.keys->trans.algo_crypt->cipherdesc == NULL) {
324
0
      fprintf(stderr, "Sorry, I won't let you use password auth unencrypted.\n");
325
0
    } else {
326
0
      cli_auth_password();
327
0
      finished = 1;
328
0
      cli_ses.lastauthtype = AUTH_TYPE_PASSWORD;
329
0
    }
330
0
  }
331
0
#endif
332
333
0
  TRACE(("cli_auth_try lastauthtype %d", cli_ses.lastauthtype))
334
335
0
  if (finished) {
336
0
    TRACE(("leave cli_auth_try success"))
337
0
    return DROPBEAR_SUCCESS;
338
0
  }
339
0
  TRACE(("leave cli_auth_try failure"))
340
0
  return DROPBEAR_FAILURE;
341
0
}
342
343
#if DROPBEAR_CLI_PASSWORD_AUTH || DROPBEAR_CLI_INTERACT_AUTH
344
/* A helper for getpass() that exits if the user cancels. The returned
345
 * password is statically allocated by getpass() */
346
char* getpass_or_cancel(const char* prompt)
347
0
{
348
0
  char* password = NULL;
349
  
350
0
#if DROPBEAR_USE_PASSWORD_ENV
351
  /* Password provided in an environment var */
352
0
  password = getenv(DROPBEAR_PASSWORD_ENV);
353
0
  if (password)
354
0
  {
355
0
    return password;
356
0
  }
357
0
#endif
358
0
  if (cli_opts.batch_mode) {
359
0
    dropbear_close("BatchMode active, no interactive session possible.");
360
0
  }
361
362
0
  if (!cli_opts.batch_mode) {
363
0
    password = getpass(prompt);
364
0
  }
365
366
  /* 0x03 is a ctrl-c character in the buffer. */
367
0
  if (password == NULL || strchr(password, '\3') != NULL) {
368
0
    dropbear_close("Interrupted.");
369
0
  }
370
0
  return password;
371
0
}
372
#endif