cli-auth.c 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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. #include "includes.h"
  26. #include "session.h"
  27. #include "auth.h"
  28. #include "dbutil.h"
  29. #include "buffer.h"
  30. #include "ssh.h"
  31. #include "packet.h"
  32. #include "runopts.h"
  33. /* Send a "none" auth request to get available methods */
  34. void cli_auth_getmethods() {
  35. TRACE(("enter cli_auth_getmethods"))
  36. CHECKCLEARTOWRITE();
  37. buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST);
  38. buf_putstring(ses.writepayload, cli_opts.username,
  39. strlen(cli_opts.username));
  40. buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION,
  41. SSH_SERVICE_CONNECTION_LEN);
  42. buf_putstring(ses.writepayload, "none", 4); /* 'none' method */
  43. encrypt_packet();
  44. #if DROPBEAR_CLI_IMMEDIATE_AUTH
  45. /* We can't haven't two auth requests in-flight with delayed zlib mode
  46. since if the first one succeeds then the remote side will
  47. expect the second one to be compressed.
  48. Race described at
  49. http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/zlib-openssh.html
  50. */
  51. if (ses.keys->trans.algo_comp != DROPBEAR_COMP_ZLIB_DELAY) {
  52. ses.authstate.authtypes = AUTH_TYPE_PUBKEY;
  53. #if DROPBEAR_USE_PASSWORD_ENV
  54. if (getenv(DROPBEAR_PASSWORD_ENV)) {
  55. ses.authstate.authtypes |= AUTH_TYPE_PASSWORD | AUTH_TYPE_INTERACT;
  56. }
  57. #endif
  58. if (cli_auth_try() == DROPBEAR_SUCCESS) {
  59. TRACE(("skipped initial none auth query"))
  60. /* Note that there will be two auth responses in-flight */
  61. cli_ses.ignore_next_auth_response = 1;
  62. }
  63. }
  64. #endif
  65. TRACE(("leave cli_auth_getmethods"))
  66. }
  67. void recv_msg_userauth_banner() {
  68. char* banner = NULL;
  69. unsigned int bannerlen;
  70. unsigned int i, linecount;
  71. int truncated = 0;
  72. TRACE(("enter recv_msg_userauth_banner"))
  73. if (ses.authstate.authdone) {
  74. TRACE(("leave recv_msg_userauth_banner: banner after auth done"))
  75. return;
  76. }
  77. if (cli_opts.quiet) {
  78. TRACE(("not showing banner"))
  79. return;
  80. }
  81. banner = buf_getstring(ses.payload, &bannerlen);
  82. buf_eatstring(ses.payload); /* The language string */
  83. if (bannerlen > MAX_BANNER_SIZE) {
  84. TRACE(("recv_msg_userauth_banner: bannerlen too long: %d", bannerlen))
  85. truncated = 1;
  86. } else {
  87. cleantext(banner);
  88. /* Limit to 24 lines */
  89. linecount = 1;
  90. for (i = 0; i < bannerlen; i++) {
  91. if (banner[i] == '\n') {
  92. if (linecount >= MAX_BANNER_LINES) {
  93. banner[i] = '\0';
  94. truncated = 1;
  95. break;
  96. }
  97. linecount++;
  98. }
  99. }
  100. fprintf(stderr, "%s\n", banner);
  101. }
  102. if (truncated) {
  103. fprintf(stderr, "[Banner from the server is too long]\n");
  104. }
  105. m_free(banner);
  106. TRACE(("leave recv_msg_userauth_banner"))
  107. }
  108. /* This handles the message-specific types which
  109. * all have a value of 60. These are
  110. * SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
  111. * SSH_MSG_USERAUTH_PK_OK, &
  112. * SSH_MSG_USERAUTH_INFO_REQUEST. */
  113. void recv_msg_userauth_specific_60() {
  114. #if DROPBEAR_CLI_PUBKEY_AUTH
  115. if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) {
  116. recv_msg_userauth_pk_ok();
  117. return;
  118. }
  119. #endif
  120. #if DROPBEAR_CLI_INTERACT_AUTH
  121. if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT) {
  122. recv_msg_userauth_info_request();
  123. return;
  124. }
  125. #endif
  126. #if DROPBEAR_CLI_PASSWORD_AUTH
  127. if (cli_ses.lastauthtype == AUTH_TYPE_PASSWORD) {
  128. /* Eventually there could be proper password-changing
  129. * support. However currently few servers seem to
  130. * implement it, and password auth is last-resort
  131. * regardless - keyboard-interactive is more likely
  132. * to be used anyway. */
  133. dropbear_close("Your password has expired.");
  134. }
  135. #endif
  136. dropbear_exit("Unexpected userauth packet");
  137. }
  138. void recv_msg_userauth_failure() {
  139. char * methods = NULL;
  140. char * tok = NULL;
  141. unsigned int methlen = 0;
  142. unsigned int partial = 0;
  143. unsigned int i = 0;
  144. TRACE(("<- MSG_USERAUTH_FAILURE"))
  145. TRACE(("enter recv_msg_userauth_failure"))
  146. if (ses.authstate.authdone) {
  147. TRACE(("leave recv_msg_userauth_failure, already authdone."))
  148. return;
  149. }
  150. if (cli_ses.state != USERAUTH_REQ_SENT) {
  151. /* Perhaps we should be more fatal? */
  152. dropbear_exit("Unexpected userauth failure");
  153. }
  154. /* When DROPBEAR_CLI_IMMEDIATE_AUTH is set there will be an initial response for
  155. the "none" auth request, and then a response to the immediate auth request.
  156. We need to be careful handling them. */
  157. if (cli_ses.ignore_next_auth_response) {
  158. cli_ses.state = USERAUTH_REQ_SENT;
  159. cli_ses.ignore_next_auth_response = 0;
  160. TRACE(("leave recv_msg_userauth_failure, ignored response, state set to USERAUTH_REQ_SENT"));
  161. return;
  162. } else {
  163. #if DROPBEAR_CLI_PUBKEY_AUTH
  164. /* If it was a pubkey auth request, we should cross that key
  165. * off the list. */
  166. if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) {
  167. cli_pubkeyfail();
  168. }
  169. #endif
  170. #if DROPBEAR_CLI_INTERACT_AUTH
  171. /* If we get a failure message for keyboard interactive without
  172. * receiving any request info packet, then we don't bother trying
  173. * keyboard interactive again */
  174. if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT
  175. && !cli_ses.interact_request_received) {
  176. TRACE(("setting auth_interact_failed = 1"))
  177. cli_ses.auth_interact_failed = 1;
  178. }
  179. #endif
  180. cli_ses.state = USERAUTH_FAIL_RCVD;
  181. cli_ses.lastauthtype = AUTH_TYPE_NONE;
  182. }
  183. methods = buf_getstring(ses.payload, &methlen);
  184. partial = buf_getbool(ses.payload);
  185. if (partial) {
  186. dropbear_log(LOG_INFO, "Authentication partially succeeded, more attempts required");
  187. } else {
  188. ses.authstate.failcount++;
  189. }
  190. TRACE(("Methods (len %d): '%s'", methlen, methods))
  191. ses.authstate.authdone=0;
  192. ses.authstate.authtypes=0;
  193. /* Split with nulls rather than commas */
  194. for (i = 0; i < methlen; i++) {
  195. if (methods[i] == ',') {
  196. methods[i] = '\0';
  197. }
  198. }
  199. tok = methods; /* tok stores the next method we'll compare */
  200. for (i = 0; i <= methlen; i++) {
  201. if (methods[i] == '\0') {
  202. TRACE(("auth method '%s'", tok))
  203. #if DROPBEAR_CLI_PUBKEY_AUTH
  204. if (strncmp(AUTH_METHOD_PUBKEY, tok,
  205. AUTH_METHOD_PUBKEY_LEN) == 0) {
  206. ses.authstate.authtypes |= AUTH_TYPE_PUBKEY;
  207. }
  208. #endif
  209. #if DROPBEAR_CLI_INTERACT_AUTH
  210. if (strncmp(AUTH_METHOD_INTERACT, tok,
  211. AUTH_METHOD_INTERACT_LEN) == 0) {
  212. ses.authstate.authtypes |= AUTH_TYPE_INTERACT;
  213. }
  214. #endif
  215. #if DROPBEAR_CLI_PASSWORD_AUTH
  216. if (strncmp(AUTH_METHOD_PASSWORD, tok,
  217. AUTH_METHOD_PASSWORD_LEN) == 0) {
  218. ses.authstate.authtypes |= AUTH_TYPE_PASSWORD;
  219. }
  220. #endif
  221. tok = &methods[i+1]; /* Must make sure we don't use it after the
  222. last loop, since it'll point to something
  223. undefined */
  224. }
  225. }
  226. m_free(methods);
  227. TRACE(("leave recv_msg_userauth_failure"))
  228. }
  229. void recv_msg_userauth_success() {
  230. /* This function can validly get called multiple times
  231. if DROPBEAR_CLI_IMMEDIATE_AUTH is set */
  232. DEBUG1(("received msg_userauth_success"))
  233. if (cli_opts.disable_trivial_auth && cli_ses.is_trivial_auth) {
  234. dropbear_exit("trivial authentication not allowed");
  235. }
  236. /* Note: in delayed-zlib mode, setting authdone here
  237. * will enable compression in the transport layer */
  238. ses.authstate.authdone = 1;
  239. cli_ses.state = USERAUTH_SUCCESS_RCVD;
  240. cli_ses.lastauthtype = AUTH_TYPE_NONE;
  241. #if DROPBEAR_CLI_PUBKEY_AUTH
  242. cli_auth_pubkey_cleanup();
  243. #endif
  244. }
  245. int cli_auth_try() {
  246. int finished = 0;
  247. TRACE(("enter cli_auth_try"))
  248. CHECKCLEARTOWRITE();
  249. /* Order to try is pubkey, interactive, password.
  250. * As soon as "finished" is set for one, we don't do any more. */
  251. #if DROPBEAR_CLI_PUBKEY_AUTH
  252. if (ses.authstate.authtypes & AUTH_TYPE_PUBKEY) {
  253. finished = cli_auth_pubkey();
  254. cli_ses.lastauthtype = AUTH_TYPE_PUBKEY;
  255. }
  256. #endif
  257. #if DROPBEAR_CLI_PASSWORD_AUTH
  258. if (!finished && (ses.authstate.authtypes & AUTH_TYPE_PASSWORD)) {
  259. if (ses.keys->trans.algo_crypt->cipherdesc == NULL) {
  260. fprintf(stderr, "Sorry, I won't let you use password auth unencrypted.\n");
  261. } else {
  262. cli_auth_password();
  263. finished = 1;
  264. cli_ses.lastauthtype = AUTH_TYPE_PASSWORD;
  265. }
  266. }
  267. #endif
  268. #if DROPBEAR_CLI_INTERACT_AUTH
  269. if (!finished && (ses.authstate.authtypes & AUTH_TYPE_INTERACT)) {
  270. if (ses.keys->trans.algo_crypt->cipherdesc == NULL) {
  271. fprintf(stderr, "Sorry, I won't let you use interactive auth unencrypted.\n");
  272. } else {
  273. if (!cli_ses.auth_interact_failed) {
  274. cli_auth_interactive();
  275. cli_ses.lastauthtype = AUTH_TYPE_INTERACT;
  276. finished = 1;
  277. }
  278. }
  279. }
  280. #endif
  281. TRACE(("cli_auth_try lastauthtype %d", cli_ses.lastauthtype))
  282. if (finished) {
  283. TRACE(("leave cli_auth_try success"))
  284. return DROPBEAR_SUCCESS;
  285. }
  286. TRACE(("leave cli_auth_try failure"))
  287. return DROPBEAR_FAILURE;
  288. }
  289. #if DROPBEAR_CLI_PASSWORD_AUTH || DROPBEAR_CLI_INTERACT_AUTH
  290. /* A helper for getpass() that exits if the user cancels. The returned
  291. * password is statically allocated by getpass() */
  292. char* getpass_or_cancel(const char* prompt)
  293. {
  294. char* password = NULL;
  295. #if DROPBEAR_USE_PASSWORD_ENV
  296. /* Password provided in an environment var */
  297. password = getenv(DROPBEAR_PASSWORD_ENV);
  298. if (password)
  299. {
  300. return password;
  301. }
  302. #endif
  303. password = getpass(prompt);
  304. /* 0x03 is a ctrl-c character in the buffer. */
  305. if (password == NULL || strchr(password, '\3') != NULL) {
  306. dropbear_close("Interrupted.");
  307. }
  308. return password;
  309. }
  310. #endif