Coverage Report

Created: 2023-12-09 06:14

/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
886
void cli_auth_getmethods() {
37
886
  TRACE(("enter cli_auth_getmethods"))
38
886
  CHECKCLEARTOWRITE();
39
886
  buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST);
40
886
  buf_putstring(ses.writepayload, cli_opts.username,
41
886
      strlen(cli_opts.username));
42
886
  buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION,
43
886
      SSH_SERVICE_CONNECTION_LEN);
44
886
  buf_putstring(ses.writepayload, "none", 4); /* 'none' method */
45
46
886
  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
886
  TRACE(("leave cli_auth_getmethods"))
70
886
}
71
72
1.11k
void recv_msg_userauth_banner() {
73
74
1.11k
  char* banner = NULL;
75
1.11k
  unsigned int bannerlen;
76
1.11k
  unsigned int i, linecount;
77
1.11k
  int truncated = 0;
78
79
1.11k
  TRACE(("enter recv_msg_userauth_banner"))
80
1.11k
  if (ses.authstate.authdone) {
81
200
    TRACE(("leave recv_msg_userauth_banner: banner after auth done"))
82
200
    return;
83
200
  }
84
85
917
  if (cli_opts.quiet) {
86
0
    TRACE(("not showing banner"))
87
0
    return;
88
0
  }
89
90
917
  banner = buf_getstring(ses.payload, &bannerlen);
91
917
  buf_eatstring(ses.payload); /* The language string */
92
93
917
  if (bannerlen > MAX_BANNER_SIZE) {
94
49
    TRACE(("recv_msg_userauth_banner: bannerlen too long: %d", bannerlen))
95
49
    truncated = 1;
96
868
  } else {
97
868
    cleantext(banner);
98
99
    /* Limit to 24 lines */
100
868
    linecount = 1;
101
28.9k
    for (i = 0; i < bannerlen; i++) {
102
28.3k
      if (banner[i] == '\n') {
103
4.41k
        if (linecount >= MAX_BANNER_LINES) {
104
209
          banner[i] = '\0';
105
209
          truncated = 1;
106
209
          break;
107
209
        }
108
4.20k
        linecount++;
109
4.20k
      }
110
28.3k
    }
111
868
    fprintf(stderr, "%s\n", banner);
112
868
  }
113
114
917
  if (truncated) {
115
258
    fprintf(stderr, "[Banner from the server is too long]\n");
116
258
  }
117
118
917
  m_free(banner);
119
917
  TRACE(("leave recv_msg_userauth_banner"))
120
917
}
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
606
void recv_msg_userauth_failure() {
158
159
606
  char * methods = NULL;
160
606
  char * tok = NULL;
161
606
  unsigned int methlen = 0;
162
606
  unsigned int partial = 0;
163
606
  unsigned int i = 0;
164
165
606
  TRACE(("<- MSG_USERAUTH_FAILURE"))
166
606
  TRACE(("enter recv_msg_userauth_failure"))
167
168
606
  if (ses.authstate.authdone) {
169
291
    TRACE(("leave recv_msg_userauth_failure, already authdone."))
170
291
    return;
171
291
  }
172
173
315
  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
313
  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
313
  } else  {
187
313
#if DROPBEAR_CLI_PUBKEY_AUTH
188
    /* If it was a pubkey auth request, we should cross that key 
189
     * off the list. */
190
313
    if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) {
191
0
      cli_pubkeyfail();
192
0
    }
193
313
#endif
194
195
313
#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
313
    if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT
200
313
        && !cli_ses.interact_request_received) {
201
0
      TRACE(("setting auth_interact_failed = 1"))
202
0
      cli_ses.auth_interact_failed = 1;
203
0
    }
204
313
#endif
205
313
    cli_ses.state = USERAUTH_FAIL_RCVD;
206
313
    cli_ses.lastauthtype = AUTH_TYPE_NONE;
207
313
  }
208
209
313
  methods = buf_getstring(ses.payload, &methlen);
210
211
313
  partial = buf_getbool(ses.payload);
212
213
313
  if (partial) {
214
227
    dropbear_log(LOG_INFO, "Authentication partially succeeded, more attempts required");
215
227
  } else {
216
86
    ses.authstate.failcount++;
217
86
  }
218
219
313
  TRACE(("Methods (len %d): '%s'", methlen, methods))
220
221
313
  ses.authstate.authdone=0;
222
313
  ses.authstate.authtypes=0;
223
224
  /* Split with nulls rather than commas */
225
54.1k
  for (i = 0; i < methlen; i++) {
226
53.8k
    if (methods[i] == ',') {
227
1.28k
      methods[i] = '\0';
228
1.28k
    }
229
53.8k
  }
230
231
313
  tok = methods; /* tok stores the next method we'll compare */
232
54.4k
  for (i = 0; i <= methlen; i++) {
233
54.1k
    if (methods[i] == '\0') {
234
18.9k
      TRACE(("auth method '%s'", tok))
235
18.9k
#if DROPBEAR_CLI_PUBKEY_AUTH
236
18.9k
      if (strncmp(AUTH_METHOD_PUBKEY, tok,
237
18.9k
        AUTH_METHOD_PUBKEY_LEN) == 0) {
238
198
        ses.authstate.authtypes |= AUTH_TYPE_PUBKEY;
239
198
      }
240
18.9k
#endif
241
18.9k
#if DROPBEAR_CLI_INTERACT_AUTH
242
18.9k
      if (strncmp(AUTH_METHOD_INTERACT, tok,
243
18.9k
        AUTH_METHOD_INTERACT_LEN) == 0) {
244
198
        ses.authstate.authtypes |= AUTH_TYPE_INTERACT;
245
198
      }
246
18.9k
#endif
247
18.9k
#if DROPBEAR_CLI_PASSWORD_AUTH
248
18.9k
      if (strncmp(AUTH_METHOD_PASSWORD, tok,
249
18.9k
        AUTH_METHOD_PASSWORD_LEN) == 0) {
250
363
        ses.authstate.authtypes |= AUTH_TYPE_PASSWORD;
251
363
      }
252
18.9k
#endif
253
18.9k
      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
18.9k
    }
257
54.1k
  }
258
259
313
  m_free(methods);
260
    
261
313
  TRACE(("leave recv_msg_userauth_failure"))
262
313
}
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