2 * Dropbear - a SSH2 server
4 * Copyright (c) 2002,2003 Matt Johnston
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 /* This file (auth.c) handles authentication requests, passing it to the
26 * particular type (auth-passwd, auth-pubkey). */
38 static void authclear();
39 static int checkusername(char *username
, unsigned int userlen
);
41 /* initialise the first time for a session, resetting all parameters */
42 void svr_authinitialise() {
44 ses
.authstate
.failcount
= 0;
45 ses
.authstate
.pw_name
= NULL
;
46 ses
.authstate
.pw_dir
= NULL
;
47 ses
.authstate
.pw_shell
= NULL
;
48 ses
.authstate
.pw_passwd
= NULL
;
53 /* Reset the auth state, but don't reset the failcount. This is for if the
54 * user decides to try with a different username etc, and is also invoked
55 * on initialisation */
56 static void authclear() {
58 memset(&ses
.authstate
, 0, sizeof(ses
.authstate
));
59 #ifdef ENABLE_SVR_PUBKEY_AUTH
60 ses
.authstate
.authtypes
|= AUTH_TYPE_PUBKEY
;
62 #if defined(ENABLE_SVR_PASSWORD_AUTH) || defined(ENABLE_SVR_PAM_AUTH)
63 if (!svr_opts
.noauthpass
) {
64 ses
.authstate
.authtypes
|= AUTH_TYPE_PASSWORD
;
67 if (ses
.authstate
.pw_name
) {
68 m_free(ses
.authstate
.pw_name
);
70 if (ses
.authstate
.pw_shell
) {
71 m_free(ses
.authstate
.pw_shell
);
73 if (ses
.authstate
.pw_dir
) {
74 m_free(ses
.authstate
.pw_dir
);
76 if (ses
.authstate
.pw_passwd
) {
77 m_free(ses
.authstate
.pw_passwd
);
82 /* Send a banner message if specified to the client. The client might
83 * ignore this, but possibly serves as a legal "no trespassing" sign */
84 void send_msg_userauth_banner(buffer
*banner
) {
86 TRACE(("enter send_msg_userauth_banner"))
90 buf_putbyte(ses
.writepayload
, SSH_MSG_USERAUTH_BANNER
);
91 buf_putbufstring(ses
.writepayload
, banner
);
92 buf_putstring(ses
.writepayload
, "en", 2);
96 TRACE(("leave send_msg_userauth_banner"))
99 /* handle a userauth request, check validity, pass to password or pubkey
100 * checking, and handle success or failure */
101 void recv_msg_userauth_request() {
103 char *username
= NULL
, *servicename
= NULL
, *methodname
= NULL
;
104 unsigned int userlen
, servicelen
, methodlen
;
107 TRACE(("enter recv_msg_userauth_request"))
109 /* ignore packets if auth is already done */
110 if (ses
.authstate
.authdone
== 1) {
111 TRACE(("leave recv_msg_userauth_request: authdone already"))
115 /* send the banner if it exists, it will only exist once */
116 if (svr_opts
.banner
) {
117 send_msg_userauth_banner(svr_opts
.banner
);
118 buf_free(svr_opts
.banner
);
119 svr_opts
.banner
= NULL
;
122 username
= buf_getstring(ses
.payload
, &userlen
);
123 servicename
= buf_getstring(ses
.payload
, &servicelen
);
124 methodname
= buf_getstring(ses
.payload
, &methodlen
);
126 /* only handle 'ssh-connection' currently */
127 if (servicelen
!= SSH_SERVICE_CONNECTION_LEN
128 && (strncmp(servicename
, SSH_SERVICE_CONNECTION
,
129 SSH_SERVICE_CONNECTION_LEN
) != 0)) {
131 /* TODO - disconnect here */
135 dropbear_exit("unknown service in auth");
138 /* check username is good before continuing.
139 * the 'incrfail' varies depending on the auth method to
140 * avoid giving away which users exist on the system through
142 if (checkusername(username
, userlen
) == DROPBEAR_SUCCESS
) {
146 /* user wants to know what methods are supported */
147 if (methodlen
== AUTH_METHOD_NONE_LEN
&&
148 strncmp(methodname
, AUTH_METHOD_NONE
,
149 AUTH_METHOD_NONE_LEN
) == 0) {
150 TRACE(("recv_msg_userauth_request: 'none' request"))
152 && svr_opts
.allowblankpass
153 && !svr_opts
.noauthpass
154 && !(svr_opts
.norootpass
&& ses
.authstate
.pw_uid
== 0)
155 && ses
.authstate
.pw_passwd
[0] == '\0')
157 dropbear_log(LOG_NOTICE
,
158 "Auth succeeded with blank password for '%s' from %s",
159 ses
.authstate
.pw_name
,
161 send_msg_userauth_success();
166 /* 'none' has no failure delay */
167 send_msg_userauth_failure(0, 0);
172 #ifdef ENABLE_SVR_PASSWORD_AUTH
173 if (!svr_opts
.noauthpass
&&
174 !(svr_opts
.norootpass
&& ses
.authstate
.pw_uid
== 0) ) {
175 /* user wants to try password auth */
176 if (methodlen
== AUTH_METHOD_PASSWORD_LEN
&&
177 strncmp(methodname
, AUTH_METHOD_PASSWORD
,
178 AUTH_METHOD_PASSWORD_LEN
) == 0) {
187 #ifdef ENABLE_SVR_PAM_AUTH
188 if (!svr_opts
.noauthpass
&&
189 !(svr_opts
.norootpass
&& ses
.authstate
.pw_uid
== 0) ) {
190 /* user wants to try password auth */
191 if (methodlen
== AUTH_METHOD_PASSWORD_LEN
&&
192 strncmp(methodname
, AUTH_METHOD_PASSWORD
,
193 AUTH_METHOD_PASSWORD_LEN
) == 0) {
202 #ifdef ENABLE_SVR_PUBKEY_AUTH
203 /* user wants to try pubkey auth */
204 if (methodlen
== AUTH_METHOD_PUBKEY_LEN
&&
205 strncmp(methodname
, AUTH_METHOD_PUBKEY
,
206 AUTH_METHOD_PUBKEY_LEN
) == 0) {
210 /* pubkey has no failure delay */
211 send_msg_userauth_failure(0, 0);
217 /* nothing matched, we just fail with a delay */
218 send_msg_userauth_failure(0, 1);
228 /* Check that the username exists and isn't disallowed (root), and has a valid shell.
229 * returns DROPBEAR_SUCCESS on valid username, DROPBEAR_FAILURE on failure */
230 static int checkusername(char *username
, unsigned int userlen
) {
232 char* listshell
= NULL
;
233 char* usershell
= NULL
;
235 TRACE(("enter checkusername"))
236 if (userlen
> MAX_USERNAME_LEN
) {
237 return DROPBEAR_FAILURE
;
240 /* new user or username has changed */
241 if (ses
.authstate
.username
== NULL
||
242 strcmp(username
, ses
.authstate
.username
) != 0) {
243 /* the username needs resetting */
244 if (ses
.authstate
.username
!= NULL
) {
245 dropbear_log(LOG_WARNING
, "Client trying multiple usernames from %s",
247 m_free(ses
.authstate
.username
);
250 fill_passwd(username
);
251 ses
.authstate
.username
= m_strdup(username
);
254 /* check that user exists */
255 if (!ses
.authstate
.pw_name
) {
256 TRACE(("leave checkusername: user '%s' doesn't exist", username
))
257 dropbear_log(LOG_WARNING
,
258 "Login attempt for nonexistent user from %s",
260 return DROPBEAR_FAILURE
;
263 /* check if we are running as non-root, and login user is different from the server */
265 if (uid
!= 0 && uid
!= ses
.authstate
.pw_uid
) {
266 TRACE(("running as nonroot, only server uid is allowed"))
267 dropbear_log(LOG_WARNING
,
268 "Login attempt with wrong user %s from %s",
269 ses
.authstate
.pw_name
,
271 return DROPBEAR_FAILURE
;
274 /* check for non-root if desired */
275 if (svr_opts
.norootlogin
&& ses
.authstate
.pw_uid
== 0) {
276 TRACE(("leave checkusername: root login disabled"))
277 dropbear_log(LOG_WARNING
, "root login rejected");
278 return DROPBEAR_FAILURE
;
281 TRACE(("shell is %s", ses
.authstate
.pw_shell
))
283 /* check that the shell is set */
284 usershell
= ses
.authstate
.pw_shell
;
285 if (usershell
[0] == '\0') {
286 /* empty shell in /etc/passwd means /bin/sh according to passwd(5) */
287 usershell
= "/bin/sh";
290 /* check the shell is valid. If /etc/shells doesn't exist, getusershell()
291 * should return some standard shells like "/bin/sh" and "/bin/csh" (this
292 * is platform-specific) */
294 while ((listshell
= getusershell()) != NULL
) {
295 TRACE(("test shell is '%s'", listshell
))
296 if (strcmp(listshell
, usershell
) == 0) {
301 /* no matching shell */
303 TRACE(("no matching shell"))
304 dropbear_log(LOG_WARNING
, "User '%s' has invalid shell, rejected",
305 ses
.authstate
.pw_name
);
306 return DROPBEAR_FAILURE
;
310 TRACE(("matching shell"))
312 TRACE(("uid = %d", ses
.authstate
.pw_uid
))
313 TRACE(("leave checkusername"))
314 return DROPBEAR_SUCCESS
;
317 /* Send a failure message to the client, in responds to a userauth_request.
318 * Partial indicates whether to set the "partial success" flag,
319 * incrfail is whether to count this failure in the failure count (which
320 * is limited. This function also handles disconnection after too many
322 void send_msg_userauth_failure(int partial
, int incrfail
) {
324 buffer
*typebuf
= NULL
;
326 TRACE(("enter send_msg_userauth_failure"))
330 buf_putbyte(ses
.writepayload
, SSH_MSG_USERAUTH_FAILURE
);
332 /* put a list of allowed types */
333 typebuf
= buf_new(30); /* long enough for PUBKEY and PASSWORD */
335 if (ses
.authstate
.authtypes
& AUTH_TYPE_PUBKEY
) {
336 buf_putbytes(typebuf
, (const unsigned char *)AUTH_METHOD_PUBKEY
, AUTH_METHOD_PUBKEY_LEN
);
337 if (ses
.authstate
.authtypes
& AUTH_TYPE_PASSWORD
) {
338 buf_putbyte(typebuf
, ',');
342 if (ses
.authstate
.authtypes
& AUTH_TYPE_PASSWORD
) {
343 buf_putbytes(typebuf
, (const unsigned char *)AUTH_METHOD_PASSWORD
, AUTH_METHOD_PASSWORD_LEN
);
346 buf_putbufstring(ses
.writepayload
, typebuf
);
348 TRACE(("auth fail: methods %d, '%.*s'", ses
.authstate
.authtypes
,
349 typebuf
->len
, typebuf
->data
))
353 buf_putbyte(ses
.writepayload
, partial
? 1 : 0);
358 genrandom((unsigned char*)&delay
, sizeof(delay
));
359 /* We delay for 300ms +- 50ms */
360 delay
= 250000 + (delay
% 100000);
362 ses
.authstate
.failcount
++;
365 if (ses
.authstate
.failcount
>= MAX_AUTH_TRIES
) {
367 /* XXX - send disconnect ? */
368 TRACE(("Max auth tries reached, exiting"))
370 if (ses
.authstate
.pw_name
== NULL
) {
371 userstr
= "is invalid";
373 userstr
= ses
.authstate
.pw_name
;
375 dropbear_exit("Max auth tries reached - user '%s' from %s",
376 userstr
, svr_ses
.addrstring
);
379 TRACE(("leave send_msg_userauth_failure"))
382 /* Send a success message to the user, and set the "authdone" flag */
383 void send_msg_userauth_success() {
385 TRACE(("enter send_msg_userauth_success"))
389 buf_putbyte(ses
.writepayload
, SSH_MSG_USERAUTH_SUCCESS
);
392 /* authdone must be set after encrypt_packet() for
393 * delayed-zlib mode */
394 ses
.authstate
.authdone
= 1;
395 ses
.connect_time
= 0;
398 if (ses
.authstate
.pw_uid
== 0) {
399 ses
.allowprivport
= 1;
402 /* Remove from the list of pre-auth sockets. Should be m_close(), since if
403 * we fail, we might end up leaking connection slots, and disallow new
404 * logins - a nasty situation. */
405 m_close(svr_ses
.childpipe
);
407 TRACE(("leave send_msg_userauth_success"))