Coverage Report

Created: 2025-08-26 06:41

/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
0
void cli_auth_getmethods() {
37
0
  TRACE(("enter cli_auth_getmethods"))
38
0
  CHECKCLEARTOWRITE();
39
0
  buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST);
40
0
  buf_putstring(ses.writepayload, cli_opts.username,
41
0
      strlen(cli_opts.username));
42
0
  buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION,
43
0
      SSH_SERVICE_CONNECTION_LEN);
44
0
  buf_putstring(ses.writepayload, "none", 4); /* 'none' method */
45
46
0
  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
0
  TRACE(("leave cli_auth_getmethods"))
70
0
}
71
72
0
void recv_msg_userauth_banner() {
73
74
0
  char* banner = NULL;
75
0
  unsigned int bannerlen;
76
0
  unsigned int i, linecount;
77
0
  int truncated = 0;
78
79
0
  TRACE(("enter recv_msg_userauth_banner"))
80
0
  if (ses.authstate.authdone) {
81
0
    TRACE(("leave recv_msg_userauth_banner: banner after auth done"))
82
0
    return;
83
0
  }
84
85
0
  if (cli_opts.quiet) {
86
0
    TRACE(("not showing banner"))
87
0
    return;
88
0
  }
89
90
0
  banner = buf_getstring(ses.payload, &bannerlen);
91
0
  buf_eatstring(ses.payload); /* The language string */
92
93
0
  if (bannerlen > MAX_BANNER_SIZE) {
94
0
    TRACE(("recv_msg_userauth_banner: bannerlen too long: %d", bannerlen))
95
0
    truncated = 1;
96
0
  } else {
97
0
    cleantext(banner);
98
99
    /* Limit to 24 lines */
100
0
    linecount = 1;
101
0
    for (i = 0; i < bannerlen; i++) {
102
0
      if (banner[i] == '\n') {
103
0
        if (linecount >= MAX_BANNER_LINES) {
104
0
          banner[i] = '\0';
105
0
          truncated = 1;
106
0
          break;
107
0
        }
108
0
        linecount++;
109
0
      }
110
0
    }
111
0
    fprintf(stderr, "%s\n", banner);
112
0
  }
113
114
0
  if (truncated) {
115
0
    fprintf(stderr, "[Banner from the server is too long]\n");
116
0
  }
117
118
0
  m_free(banner);
119
0
  TRACE(("leave recv_msg_userauth_banner"))
120
0
}
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
0
void recv_msg_userauth_specific_60() {
128
129
0
#if DROPBEAR_CLI_PUBKEY_AUTH
130
0
  if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) {
131
0
    recv_msg_userauth_pk_ok();
132
0
    return;
133
0
  }
134
0
#endif
135
136
0
#if DROPBEAR_CLI_INTERACT_AUTH
137
0
  if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT) {
138
0
    recv_msg_userauth_info_request();
139
0
    return;
140
0
  }
141
0
#endif
142
143
0
#if DROPBEAR_CLI_PASSWORD_AUTH
144
0
  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
0
#endif
153
154
0
  dropbear_exit("Unexpected userauth packet");
155
0
}
156
157
0
void recv_msg_userauth_failure() {
158
159
0
  char * methods = NULL;
160
0
  char * tok = NULL;
161
0
  unsigned int methlen = 0;
162
0
  unsigned int partial = 0;
163
0
  unsigned int i = 0;
164
0
  int allow_pw_auth = 1;
165
166
0
  TRACE(("<- MSG_USERAUTH_FAILURE"))
167
0
  TRACE(("enter recv_msg_userauth_failure"))
168
169
0
  if (ses.authstate.authdone) {
170
0
    TRACE(("leave recv_msg_userauth_failure, already authdone."))
171
0
    return;
172
0
  }
173
174
0
  if (cli_ses.state != USERAUTH_REQ_SENT) {
175
    /* Perhaps we should be more fatal? */
176
0
    dropbear_exit("Unexpected userauth failure");
177
0
  }
178
179
  /* Password authentication is only allowed in batch mode
180
   * when a password can be provided non-interactively */
181
0
  if (cli_opts.batch_mode && !getenv(DROPBEAR_PASSWORD_ENV)) {
182
0
    allow_pw_auth = 0;
183
0
  }
184
0
  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
0
  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
0
  } else  {
195
0
#if DROPBEAR_CLI_PUBKEY_AUTH
196
    /* If it was a pubkey auth request, we should cross that key 
197
     * off the list. */
198
0
    if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) {
199
0
      cli_pubkeyfail();
200
0
    }
201
0
#endif
202
203
0
#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
0
    if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT
208
0
        && !cli_ses.interact_request_received) {
209
0
      TRACE(("setting auth_interact_failed = 1"))
210
0
      cli_ses.auth_interact_failed = 1;
211
0
    }
212
0
#endif
213
0
    cli_ses.state = USERAUTH_FAIL_RCVD;
214
0
    cli_ses.lastauthtype = AUTH_TYPE_NONE;
215
0
  }
216
217
0
  methods = buf_getstring(ses.payload, &methlen);
218
219
0
  partial = buf_getbool(ses.payload);
220
221
0
  if (partial) {
222
0
    dropbear_log(LOG_INFO, "Authentication partially succeeded, more attempts required");
223
0
  } else {
224
0
    ses.authstate.failcount++;
225
0
  }
226
227
0
  TRACE(("Methods (len %d): '%s'", methlen, methods))
228
229
0
  ses.authstate.authdone=0;
230
0
  ses.authstate.authtypes=0;
231
232
  /* Split with nulls rather than commas */
233
0
  for (i = 0; i < methlen; i++) {
234
0
    if (methods[i] == ',') {
235
0
      methods[i] = '\0';
236
0
    }
237
0
  }
238
239
0
  tok = methods; /* tok stores the next method we'll compare */
240
0
  for (i = 0; i <= methlen; i++) {
241
0
    if (methods[i] == '\0') {
242
0
      TRACE(("auth method '%s'", tok))
243
0
#if DROPBEAR_CLI_PUBKEY_AUTH
244
0
      if (strncmp(AUTH_METHOD_PUBKEY, tok,
245
0
        AUTH_METHOD_PUBKEY_LEN) == 0) {
246
0
        ses.authstate.authtypes |= AUTH_TYPE_PUBKEY;
247
0
      }
248
0
#endif
249
0
#if DROPBEAR_CLI_INTERACT_AUTH
250
0
      if (allow_pw_auth
251
0
        && strncmp(AUTH_METHOD_INTERACT, tok, AUTH_METHOD_INTERACT_LEN) == 0) {
252
0
        ses.authstate.authtypes |= AUTH_TYPE_INTERACT;
253
0
      }
254
0
#endif
255
0
#if DROPBEAR_CLI_PASSWORD_AUTH
256
0
      if (allow_pw_auth
257
0
        && strncmp(AUTH_METHOD_PASSWORD, tok, AUTH_METHOD_PASSWORD_LEN) == 0) {
258
0
        ses.authstate.authtypes |= AUTH_TYPE_PASSWORD;
259
0
      }
260
0
#endif
261
0
      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
0
    }
265
0
  }
266
267
0
  m_free(methods);
268
    
269
0
  TRACE(("leave recv_msg_userauth_failure"))
270
0
}
271
272
0
void recv_msg_userauth_success() {
273
  /* This function can validly get called multiple times
274
  if DROPBEAR_CLI_IMMEDIATE_AUTH is set */
275
276
0
  DEBUG1(("received msg_userauth_success"))
277
0
  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
0
  ses.authstate.authdone = 1;
283
0
  cli_ses.state = USERAUTH_SUCCESS_RCVD;
284
0
  cli_ses.lastauthtype = AUTH_TYPE_NONE;
285
286
0
#if DROPBEAR_CLI_PUBKEY_AUTH
287
0
  cli_auth_pubkey_cleanup();
288
0
#endif
289
0
}
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