Coverage Report

Created: 2023-03-06 09:27

/src/dropbear/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
165
0
  TRACE(("<- MSG_USERAUTH_FAILURE"))
166
0
  TRACE(("enter recv_msg_userauth_failure"))
167
168
0
  if (ses.authstate.authdone) {
169
0
    TRACE(("leave recv_msg_userauth_failure, already authdone."))
170
0
    return;
171
0
  }
172
173
0
  if (cli_ses.state != USERAUTH_REQ_SENT) {
174
    /* Perhaps we should be more fatal? */
175
0
    dropbear_exit("Unexpected userauth failure");
176
0
  }
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
0
  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
0
  } else  {
187
0
#if DROPBEAR_CLI_PUBKEY_AUTH
188
    /* If it was a pubkey auth request, we should cross that key 
189
     * off the list. */
190
0
    if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) {
191
0
      cli_pubkeyfail();
192
0
    }
193
0
#endif
194
195
0
#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
0
    if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT
200
0
        && !cli_ses.interact_request_received) {
201
0
      TRACE(("setting auth_interact_failed = 1"))
202
0
      cli_ses.auth_interact_failed = 1;
203
0
    }
204
0
#endif
205
0
    cli_ses.state = USERAUTH_FAIL_RCVD;
206
0
    cli_ses.lastauthtype = AUTH_TYPE_NONE;
207
0
  }
208
209
0
  methods = buf_getstring(ses.payload, &methlen);
210
211
0
  partial = buf_getbool(ses.payload);
212
213
0
  if (partial) {
214
0
    dropbear_log(LOG_INFO, "Authentication partially succeeded, more attempts required");
215
0
  } else {
216
0
    ses.authstate.failcount++;
217
0
  }
218
219
0
  TRACE(("Methods (len %d): '%s'", methlen, methods))
220
221
0
  ses.authstate.authdone=0;
222
0
  ses.authstate.authtypes=0;
223
224
  /* Split with nulls rather than commas */
225
0
  for (i = 0; i < methlen; i++) {
226
0
    if (methods[i] == ',') {
227
0
      methods[i] = '\0';
228
0
    }
229
0
  }
230
231
0
  tok = methods; /* tok stores the next method we'll compare */
232
0
  for (i = 0; i <= methlen; i++) {
233
0
    if (methods[i] == '\0') {
234
0
      TRACE(("auth method '%s'", tok))
235
0
#if DROPBEAR_CLI_PUBKEY_AUTH
236
0
      if (strncmp(AUTH_METHOD_PUBKEY, tok,
237
0
        AUTH_METHOD_PUBKEY_LEN) == 0) {
238
0
        ses.authstate.authtypes |= AUTH_TYPE_PUBKEY;
239
0
      }
240
0
#endif
241
0
#if DROPBEAR_CLI_INTERACT_AUTH
242
0
      if (strncmp(AUTH_METHOD_INTERACT, tok,
243
0
        AUTH_METHOD_INTERACT_LEN) == 0) {
244
0
        ses.authstate.authtypes |= AUTH_TYPE_INTERACT;
245
0
      }
246
0
#endif
247
0
#if DROPBEAR_CLI_PASSWORD_AUTH
248
0
      if (strncmp(AUTH_METHOD_PASSWORD, tok,
249
0
        AUTH_METHOD_PASSWORD_LEN) == 0) {
250
0
        ses.authstate.authtypes |= AUTH_TYPE_PASSWORD;
251
0
      }
252
0
#endif
253
0
      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
0
    }
257
0
  }
258
259
0
  m_free(methods);
260
    
261
0
  TRACE(("leave recv_msg_userauth_failure"))
262
0
}
263
264
0
void recv_msg_userauth_success() {
265
  /* This function can validly get called multiple times
266
  if DROPBEAR_CLI_IMMEDIATE_AUTH is set */
267
268
0
  DEBUG1(("received msg_userauth_success"))
269
0
  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
0
  ses.authstate.authdone = 1;
275
0
  cli_ses.state = USERAUTH_SUCCESS_RCVD;
276
0
  cli_ses.lastauthtype = AUTH_TYPE_NONE;
277
278
0
#if DROPBEAR_CLI_PUBKEY_AUTH
279
0
  cli_auth_pubkey_cleanup();
280
0
#endif
281
0
}
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