blob: 3824c52bea75e246c94d06f88f491240350a7b43 [file] [log] [blame]
Michal Vasko086311b2016-01-08 09:53:11 +01001/**
2 * \file session_server_ssh.c
3 * \author Michal Vasko <mvasko@cesnet.cz>
4 * \brief libnetconf2 SSH server session manipulation functions
5 *
6 * Copyright (c) 2015 CESNET, z.s.p.o.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
17 * 3. Neither the name of the Company nor the names of its contributors
18 * may be used to endorse or promote products derived from this
19 * software without specific prior written permission.
20 *
21 */
22
23#define _GNU_SOURCE
24
25#include <stdlib.h>
26#include <string.h>
27#include <sys/types.h>
28#include <pwd.h>
29#include <shadow.h>
30#include <crypt.h>
31#include <errno.h>
32
Michal Vasko1a38c862016-01-15 15:50:07 +010033#include "libnetconf.h"
Michal Vasko11d142a2016-01-19 15:58:24 +010034#include "session_server.h"
Michal Vasko086311b2016-01-08 09:53:11 +010035
36extern struct nc_server_opts server_opts;
Michal Vaskob48aa812016-01-18 14:13:09 +010037struct nc_ssh_server_opts ssh_opts = {
38 .sshbind_lock = PTHREAD_MUTEX_INITIALIZER,
39 .authkey_lock = PTHREAD_MUTEX_INITIALIZER,
Michal Vasko086311b2016-01-08 09:53:11 +010040 .auth_methods = NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE,
41 .auth_attempts = 3,
42 .auth_timeout = 10
43};
44
45API int
Michal Vasko1a38c862016-01-15 15:50:07 +010046nc_ssh_server_set_hostkey(const char *privkey_path)
Michal Vasko086311b2016-01-08 09:53:11 +010047{
Michal Vasko1a38c862016-01-15 15:50:07 +010048 if (!privkey_path) {
Michal Vasko086311b2016-01-08 09:53:11 +010049 ERRARG;
50 return -1;
51 }
52
Michal Vaskob48aa812016-01-18 14:13:09 +010053 /* LOCK */
54 pthread_mutex_lock(&ssh_opts.sshbind_lock);
55
Michal Vasko086311b2016-01-08 09:53:11 +010056 if (!ssh_opts.sshbind) {
57 ssh_opts.sshbind = ssh_bind_new();
58 if (!ssh_opts.sshbind) {
Michal Vaskod083db62016-01-19 10:31:29 +010059 ERR("Failed to create a new ssh_bind.");
Michal Vaskob48aa812016-01-18 14:13:09 +010060 goto fail;
Michal Vasko086311b2016-01-08 09:53:11 +010061 }
62 }
63
Michal Vasko1a38c862016-01-15 15:50:07 +010064 if (ssh_bind_options_set(ssh_opts.sshbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +010065 ERR("Failed to set host key (%s).", ssh_get_error(ssh_opts.sshbind));
Michal Vaskob48aa812016-01-18 14:13:09 +010066 goto fail;
Michal Vasko086311b2016-01-08 09:53:11 +010067 }
Michal Vaskod45e25a2016-01-08 15:48:44 +010068
Michal Vaskob48aa812016-01-18 14:13:09 +010069 /* UNLOCK */
70 pthread_mutex_unlock(&ssh_opts.sshbind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +010071 return 0;
Michal Vaskob48aa812016-01-18 14:13:09 +010072
73fail:
74 /* UNLOCK */
75 pthread_mutex_unlock(&ssh_opts.sshbind_lock);
76 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +010077}
78
79API int
80nc_ssh_server_set_banner(const char *banner)
81{
82 if (!banner) {
83 ERRARG;
84 return -1;
85 }
86
Michal Vaskob48aa812016-01-18 14:13:09 +010087 /* LOCK */
88 pthread_mutex_lock(&ssh_opts.sshbind_lock);
89
Michal Vasko086311b2016-01-08 09:53:11 +010090 if (!ssh_opts.sshbind) {
91 ssh_opts.sshbind = ssh_bind_new();
92 if (!ssh_opts.sshbind) {
Michal Vaskod083db62016-01-19 10:31:29 +010093 ERR("Failed to create a new ssh_bind.");
Michal Vaskob48aa812016-01-18 14:13:09 +010094 goto fail;
Michal Vasko086311b2016-01-08 09:53:11 +010095 }
96 }
97
98 ssh_bind_options_set(ssh_opts.sshbind, SSH_BIND_OPTIONS_BANNER, banner);
Michal Vaskob48aa812016-01-18 14:13:09 +010099
100 /* UNLOCK */
101 pthread_mutex_unlock(&ssh_opts.sshbind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100102 return 0;
Michal Vaskob48aa812016-01-18 14:13:09 +0100103
104fail:
105 /* UNLOCK */
106 pthread_mutex_unlock(&ssh_opts.sshbind_lock);
107 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100108}
109
110API int
111nc_ssh_server_set_auth_methods(int auth_methods)
112{
113 if (!(auth_methods & NC_SSH_AUTH_PUBLICKEY) && !(auth_methods & NC_SSH_AUTH_PASSWORD)
114 && !(auth_methods & NC_SSH_AUTH_INTERACTIVE)) {
115 ERRARG;
116 return -1;
117 }
118
119 ssh_opts.auth_methods = auth_methods;
120 return 0;
121}
122
123API int
124nc_ssh_server_set_auth_attempts(uint16_t auth_attempts)
125{
126 if (!auth_attempts) {
127 ERRARG;
128 return -1;
129 }
130
131 ssh_opts.auth_attempts = auth_attempts;
132 return 0;
133}
134
135API int
136nc_ssh_server_set_auth_timeout(uint16_t auth_timeout)
137{
138 if (!auth_timeout) {
139 ERRARG;
140 return -1;
141 }
142
143 ssh_opts.auth_timeout = auth_timeout;
144 return 0;
145}
146
147API int
Michal Vasko1a38c862016-01-15 15:50:07 +0100148nc_ssh_server_add_authkey(const char *pubkey_path, const char *username)
Michal Vasko086311b2016-01-08 09:53:11 +0100149{
Michal Vasko1a38c862016-01-15 15:50:07 +0100150 if (!pubkey_path || !username) {
Michal Vasko086311b2016-01-08 09:53:11 +0100151 ERRARG;
152 return -1;
153 }
154
Michal Vaskob48aa812016-01-18 14:13:09 +0100155 /* LOCK */
156 pthread_mutex_lock(&ssh_opts.authkey_lock);
157
Michal Vasko086311b2016-01-08 09:53:11 +0100158 ++ssh_opts.authkey_count;
159 ssh_opts.authkeys = realloc(ssh_opts.authkeys, ssh_opts.authkey_count * sizeof *ssh_opts.authkeys);
160
Michal Vasko11d142a2016-01-19 15:58:24 +0100161 nc_ctx_lock(-1, NULL);
Michal Vasko76e3a352016-01-18 09:07:00 +0100162 ssh_opts.authkeys[ssh_opts.authkey_count - 1].path = lydict_insert(server_opts.ctx, pubkey_path, 0);
163 ssh_opts.authkeys[ssh_opts.authkey_count - 1].username = lydict_insert(server_opts.ctx, username, 0);
Michal Vasko11d142a2016-01-19 15:58:24 +0100164 nc_ctx_unlock();
Michal Vasko086311b2016-01-08 09:53:11 +0100165
Michal Vaskob48aa812016-01-18 14:13:09 +0100166 /* UNLOCK */
167 pthread_mutex_unlock(&ssh_opts.authkey_lock);
168
Michal Vasko086311b2016-01-08 09:53:11 +0100169 return 0;
170}
171
172API int
Michal Vasko1a38c862016-01-15 15:50:07 +0100173nc_ssh_server_del_authkey(const char *pubkey_path, const char *username)
Michal Vasko086311b2016-01-08 09:53:11 +0100174{
175 uint32_t i;
176 int ret = -1;
177
Michal Vaskob48aa812016-01-18 14:13:09 +0100178 /* LOCK */
179 pthread_mutex_lock(&ssh_opts.authkey_lock);
180
Michal Vasko1a38c862016-01-15 15:50:07 +0100181 if (!pubkey_path && !username) {
Michal Vasko11d142a2016-01-19 15:58:24 +0100182 nc_ctx_lock(-1, NULL);
Michal Vasko1a38c862016-01-15 15:50:07 +0100183 for (i = 0; i < ssh_opts.authkey_count; ++i) {
Michal Vasko76e3a352016-01-18 09:07:00 +0100184 lydict_remove(server_opts.ctx, ssh_opts.authkeys[i].path);
185 lydict_remove(server_opts.ctx, ssh_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100186
Michal Vasko086311b2016-01-08 09:53:11 +0100187 ret = 0;
188 }
Michal Vasko11d142a2016-01-19 15:58:24 +0100189 nc_ctx_unlock();
Michal Vasko1a38c862016-01-15 15:50:07 +0100190 free(ssh_opts.authkeys);
191 ssh_opts.authkeys = NULL;
192 ssh_opts.authkey_count = 0;
193 } else {
194 for (i = 0; i < ssh_opts.authkey_count; ++i) {
195 if ((!pubkey_path || !strcmp(ssh_opts.authkeys[i].path, pubkey_path))
196 && (!username || !strcmp(ssh_opts.authkeys[i].username, username))) {
Michal Vasko11d142a2016-01-19 15:58:24 +0100197 nc_ctx_lock(-1, NULL);
Michal Vasko76e3a352016-01-18 09:07:00 +0100198 lydict_remove(server_opts.ctx, ssh_opts.authkeys[i].path);
199 lydict_remove(server_opts.ctx, ssh_opts.authkeys[i].username);
Michal Vasko11d142a2016-01-19 15:58:24 +0100200 nc_ctx_unlock();
Michal Vasko1a38c862016-01-15 15:50:07 +0100201
202 --ssh_opts.authkey_count;
Michal Vasko5b003bf2016-01-19 10:56:19 +0100203 memcpy(&ssh_opts.authkeys[i], &ssh_opts.authkeys[ssh_opts.authkey_count], sizeof *ssh_opts.authkeys);
Michal Vasko1a38c862016-01-15 15:50:07 +0100204
205 ret = 0;
206 }
207 }
Michal Vasko086311b2016-01-08 09:53:11 +0100208 }
209
Michal Vaskob48aa812016-01-18 14:13:09 +0100210 /* UNLOCK */
211 pthread_mutex_unlock(&ssh_opts.authkey_lock);
212
Michal Vasko086311b2016-01-08 09:53:11 +0100213 return ret;
214}
215
Michal Vasko086311b2016-01-08 09:53:11 +0100216API void
Michal Vasko9e036d52016-01-08 10:49:26 +0100217nc_ssh_server_free_opts(void)
Michal Vasko086311b2016-01-08 09:53:11 +0100218{
Michal Vaskob48aa812016-01-18 14:13:09 +0100219 /* LOCK */
220 pthread_mutex_lock(&ssh_opts.sshbind_lock);
221
Michal Vasko086311b2016-01-08 09:53:11 +0100222 if (ssh_opts.sshbind) {
223 ssh_bind_free(ssh_opts.sshbind);
Michal Vaskob48aa812016-01-18 14:13:09 +0100224 ssh_opts.sshbind = NULL;
Michal Vasko086311b2016-01-08 09:53:11 +0100225 }
226
Michal Vaskob48aa812016-01-18 14:13:09 +0100227 /* UNLOCK */
228 pthread_mutex_unlock(&ssh_opts.sshbind_lock);
229
Michal Vasko1a38c862016-01-15 15:50:07 +0100230 nc_ssh_server_del_authkey(NULL, NULL);
Michal Vasko086311b2016-01-08 09:53:11 +0100231}
232
233static char *
234auth_password_get_pwd_hash(const char *username)
235{
236 struct passwd *pwd, pwd_buf;
237 struct spwd *spwd, spwd_buf;
238 char *pass_hash = NULL, buf[256];
239
240 getpwnam_r(username, &pwd_buf, buf, 256, &pwd);
241 if (!pwd) {
Michal Vasko11d142a2016-01-19 15:58:24 +0100242 VRB("User \"%s\" not found locally.", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100243 return NULL;
244 }
245
246 if (!strcmp(pwd->pw_passwd, "x")) {
247 getspnam_r(username, &spwd_buf, buf, 256, &spwd);
248 if (!spwd) {
249 VRB("Failed to retrieve the shadow entry for \"%s\".", username);
250 return NULL;
251 }
252
253 pass_hash = spwd->sp_pwdp;
254 } else {
255 pass_hash = pwd->pw_passwd;
256 }
257
258 if (!pass_hash) {
Michal Vaskod083db62016-01-19 10:31:29 +0100259 ERR("No password could be retrieved for \"%s\".", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100260 return NULL;
261 }
262
263 /* check the hash structure for special meaning */
264 if (!strcmp(pass_hash, "*") || !strcmp(pass_hash, "!")) {
265 VRB("User \"%s\" is not allowed to authenticate using a password.", username);
266 return NULL;
267 }
268 if (!strcmp(pass_hash, "*NP*")) {
269 VRB("Retrieving password for \"%s\" from a NIS+ server not supported.", username);
270 return NULL;
271 }
272
273 return strdup(pass_hash);
274}
275
276static int
277auth_password_compare_pwd(const char *pass_hash, const char *pass_clear)
278{
279 char *new_pass_hash;
280 struct crypt_data cdata;
281
282 if (!pass_hash[0]) {
283 if (!pass_clear[0]) {
284 WRN("User authentication successful with an empty password!");
285 return 0;
286 } else {
287 /* the user did now know he does not need any password,
288 * (which should not be used) so deny authentication */
289 return 1;
290 }
291 }
292
293 cdata.initialized = 0;
294 new_pass_hash = crypt_r(pass_clear, pass_hash, &cdata);
295 return strcmp(new_pass_hash, pass_hash);
296}
297
298static void
299nc_sshcb_auth_password(struct nc_session *session, ssh_message msg)
300{
301 char *pass_hash;
302
303 pass_hash = auth_password_get_pwd_hash(session->username);
304 if (pass_hash && !auth_password_compare_pwd(pass_hash, ssh_message_auth_password(msg))) {
Michal Vaskod083db62016-01-19 10:31:29 +0100305 VRB("User \"%s\" authenticated.", session->username);
Michal Vasko086311b2016-01-08 09:53:11 +0100306 ssh_message_auth_reply_success(msg, 0);
307 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
308 free(pass_hash);
309 return;
310 }
311
312 free(pass_hash);
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100313 ++session->ssh_auth_attempts;
Michal Vaskod083db62016-01-19 10:31:29 +0100314 VRB("Failed user \"'%s\" authentication attempt (#%d).", session->username, session->ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +0100315 ssh_message_reply_default(msg);
316}
317
318static void
319nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg)
320{
321 char *pass_hash;
322
323 if (!ssh_message_auth_kbdint_is_response(msg)) {
324 const char *prompts[] = {"Password: "};
325 char echo[] = {0};
326
327 ssh_message_auth_interactive_request(msg, "Interactive SSH Authentication", "Type your password:", 1, prompts, echo);
328 } else {
329 if (ssh_userauth_kbdint_getnanswers(session->ti.libssh.session) != 1) {
330 ssh_message_reply_default(msg);
331 return;
332 }
333 pass_hash = auth_password_get_pwd_hash(session->username);
334 if (!pass_hash) {
335 ssh_message_reply_default(msg);
336 return;
337 }
338 if (!auth_password_compare_pwd(pass_hash, ssh_userauth_kbdint_getanswer(session->ti.libssh.session, 0))) {
339 VRB("User \"%s\" authenticated.", session->username);
340 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
341 ssh_message_auth_reply_success(msg, 0);
342 } else {
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100343 ++session->ssh_auth_attempts;
344 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +0100345 ssh_message_reply_default(msg);
346 }
347 }
348}
349
350static const char *
351auth_pubkey_compare_key(ssh_key key)
352{
353 uint32_t i;
354 ssh_key pub_key;
Michal Vasko76e3a352016-01-18 09:07:00 +0100355 const char *username = NULL;
Michal Vasko086311b2016-01-08 09:53:11 +0100356
Michal Vaskob48aa812016-01-18 14:13:09 +0100357 /* LOCK */
358 pthread_mutex_lock(&ssh_opts.authkey_lock);
359
Michal Vasko086311b2016-01-08 09:53:11 +0100360 for (i = 0; i < ssh_opts.authkey_count; ++i) {
361 if (ssh_pki_import_pubkey_file(ssh_opts.authkeys[i].path, &pub_key) != SSH_OK) {
362 if (eaccess(ssh_opts.authkeys[i].path, R_OK)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100363 WRN("Failed to import the public key \"%s\" (%s).", ssh_opts.authkeys[i].path, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100364 } else {
Michal Vaskod083db62016-01-19 10:31:29 +0100365 WRN("Failed to import the public key \"%s\" (%s).", __func__, ssh_opts.authkeys[i].path, ssh_get_error(pub_key));
Michal Vasko086311b2016-01-08 09:53:11 +0100366 }
367 continue;
368 }
369
370 if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) {
371 ssh_key_free(pub_key);
372 break;
373 }
374
375 ssh_key_free(pub_key);
376 }
377
378 if (i < ssh_opts.authkey_count) {
379 username = ssh_opts.authkeys[i].username;
380 }
381
Michal Vaskob48aa812016-01-18 14:13:09 +0100382 /* UNLOCK */
383 pthread_mutex_unlock(&ssh_opts.authkey_lock);
384
Michal Vasko086311b2016-01-08 09:53:11 +0100385 return username;
386}
387
388static void
389nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg)
390{
391 const char *username;
392 int signature_state;
393
394 signature_state = ssh_message_auth_publickey_state(msg);
395 if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
396 VRB("User \"%s\" authenticated.", session->username);
397 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
398 ssh_message_auth_reply_success(msg, 0);
399 return;
400
401 } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
402 if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
403 VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
404
405 } else if (strcmp(session->username, username)) {
406 VRB("User \"%s\" is not the username identified with the presented public key.", session->username);
407
408 } else {
409 /* accepting only the use of a public key */
410 ssh_message_auth_reply_pk_ok_simple(msg);
411 return;
412 }
413 }
414
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100415 ++session->ssh_auth_attempts;
416 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +0100417 ssh_message_reply_default(msg);
418}
419
420static int
Michal Vasko96164bf2016-01-21 15:41:58 +0100421nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100422{
Michal Vasko96164bf2016-01-21 15:41:58 +0100423 ssh_channel chan;
424
425 /* first channel request */
426 if (!session->ti.libssh.channel) {
427 if (session->status != NC_STATUS_STARTING) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100428 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100429 return -1;
430 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100431 chan = ssh_message_channel_request_open_reply_accept(msg);
432 if (!chan) {
433 ERR("Failed to create a new SSH channel.");
434 return -1;
435 }
436 session->ti.libssh.channel = chan;
Michal Vasko086311b2016-01-08 09:53:11 +0100437
Michal Vasko96164bf2016-01-21 15:41:58 +0100438 /* additional channel request */
439 } else {
440 chan = ssh_message_channel_request_open_reply_accept(msg);
441 if (!chan) {
442 ERR("Session %u: failed to create a new SSH channel.", session->id);
443 return -1;
444 }
445 /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
Michal Vasko086311b2016-01-08 09:53:11 +0100446 }
447
Michal Vasko086311b2016-01-08 09:53:11 +0100448 return 0;
449}
450
451static int
452nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
453{
Michal Vasko96164bf2016-01-21 15:41:58 +0100454 struct nc_session *new_session;
Michal Vasko086311b2016-01-08 09:53:11 +0100455
Michal Vasko96164bf2016-01-21 15:41:58 +0100456 if (strcmp(subsystem, "netconf")) {
457 WRN("Received an unknown subsystem \"%s\" request.", subsystem);
Michal Vasko086311b2016-01-08 09:53:11 +0100458 return -1;
459 }
460
Michal Vasko96164bf2016-01-21 15:41:58 +0100461 if (session->ti.libssh.channel == channel) {
462 /* first channel requested */
463 if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
464 ERRINT;
465 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100466 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100467 if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
468 ERR("Session %u: subsystem \"netconf\" requested for the second time.", session->id);
469 return -1;
470 }
471
472 session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
Michal Vasko086311b2016-01-08 09:53:11 +0100473 } else {
Michal Vasko96164bf2016-01-21 15:41:58 +0100474 /* additional channel subsystem request, new session is ready as far as SSH is concerned */
475 new_session = calloc(1, sizeof *new_session);
476
477 /* insert the new session */
478 if (!session->ti.libssh.next) {
479 new_session->ti.libssh.next = session;
480 } else {
481 new_session->ti.libssh.next = session->ti.libssh.next;
482 }
483 session->ti.libssh.next = new_session;
484
485 new_session->status = NC_STATUS_STARTING;
486 new_session->side = NC_SERVER;
487 new_session->ti_type = NC_TI_LIBSSH;
488 new_session->ti_lock = session->ti_lock;
489 new_session->ti.libssh.channel = channel;
490 new_session->ti.libssh.session = session->ti.libssh.session;
491 new_session->username = lydict_insert(server_opts.ctx, session->username, 0);
492 new_session->host = lydict_insert(server_opts.ctx, session->host, 0);
493 new_session->port = session->port;
494 new_session->ctx = server_opts.ctx;
495 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX;
Michal Vasko086311b2016-01-08 09:53:11 +0100496 }
497
498 return 0;
499}
500
Michal Vasko96164bf2016-01-21 15:41:58 +0100501int
Michal Vaskob48aa812016-01-18 14:13:09 +0100502nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
Michal Vasko086311b2016-01-08 09:53:11 +0100503{
504 const char *str_type, *str_subtype = NULL, *username;
505 int subtype, type;
506 struct nc_session *session = (struct nc_session *)data;
Michal Vasko086311b2016-01-08 09:53:11 +0100507
508 type = ssh_message_type(msg);
509 subtype = ssh_message_subtype(msg);
510
511 switch (type) {
512 case SSH_REQUEST_AUTH:
513 str_type = "request-auth";
514 switch (subtype) {
515 case SSH_AUTH_METHOD_NONE:
516 str_subtype = "none";
517 break;
518 case SSH_AUTH_METHOD_PASSWORD:
519 str_subtype = "password";
520 break;
521 case SSH_AUTH_METHOD_PUBLICKEY:
522 str_subtype = "publickey";
523 break;
524 case SSH_AUTH_METHOD_HOSTBASED:
525 str_subtype = "hostbased";
526 break;
527 case SSH_AUTH_METHOD_INTERACTIVE:
528 str_subtype = "interactive";
529 break;
530 case SSH_AUTH_METHOD_GSSAPI_MIC:
531 str_subtype = "gssapi-mic";
532 break;
533 }
534 break;
535
536 case SSH_REQUEST_CHANNEL_OPEN:
537 str_type = "request-channel-open";
538 switch (subtype) {
539 case SSH_CHANNEL_SESSION:
540 str_subtype = "session";
541 break;
542 case SSH_CHANNEL_DIRECT_TCPIP:
543 str_subtype = "direct-tcpip";
544 break;
545 case SSH_CHANNEL_FORWARDED_TCPIP:
546 str_subtype = "forwarded-tcpip";
547 break;
548 case (int)SSH_CHANNEL_X11:
549 str_subtype = "channel-x11";
550 break;
551 case SSH_CHANNEL_UNKNOWN:
552 /* fallthrough */
553 default:
554 str_subtype = "unknown";
555 break;
556 }
557 break;
558
559 case SSH_REQUEST_CHANNEL:
560 str_type = "request-channel";
561 switch (subtype) {
562 case SSH_CHANNEL_REQUEST_PTY:
563 str_subtype = "pty";
564 break;
565 case SSH_CHANNEL_REQUEST_EXEC:
566 str_subtype = "exec";
567 break;
568 case SSH_CHANNEL_REQUEST_SHELL:
569 str_subtype = "shell";
570 break;
571 case SSH_CHANNEL_REQUEST_ENV:
572 str_subtype = "env";
573 break;
574 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
575 str_subtype = "subsystem";
576 break;
577 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
578 str_subtype = "window-change";
579 break;
580 case SSH_CHANNEL_REQUEST_X11:
581 str_subtype = "x11";
582 break;
583 case SSH_CHANNEL_REQUEST_UNKNOWN:
584 /* fallthrough */
585 default:
586 str_subtype = "unknown";
587 break;
588 }
589 break;
590
591 case SSH_REQUEST_SERVICE:
592 str_type = "request-service";
593 str_subtype = ssh_message_service_service(msg);
594 break;
595
596 case SSH_REQUEST_GLOBAL:
597 str_type = "request-global";
598 switch (subtype) {
599 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
600 str_subtype = "tcpip-forward";
601 break;
602 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
603 str_subtype = "cancel-tcpip-forward";
604 break;
605 case SSH_GLOBAL_REQUEST_UNKNOWN:
606 /* fallthrough */
607 default:
608 str_subtype = "unknown";
609 break;
610 }
611 break;
612
613 default:
614 str_type = "unknown";
615 str_subtype = "unknown";
616 break;
617 }
618
619 VRB("Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vasko96164bf2016-01-21 15:41:58 +0100620 session->flags |= NC_SESSION_SSH_NEW_MSG;
Michal Vasko086311b2016-01-08 09:53:11 +0100621
622 /*
623 * process known messages
624 */
625 if (type == SSH_REQUEST_AUTH) {
626 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
627 ERR("User \"%s\" authenticated, but requested another authentication.", session->username);
628 ssh_message_reply_default(msg);
629 return 0;
630 }
631
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100632 if (session->ssh_auth_attempts >= ssh_opts.auth_attempts) {
Michal Vasko086311b2016-01-08 09:53:11 +0100633 /* too many failed attempts */
634 ssh_message_reply_default(msg);
635 return 0;
636 }
637
638 /* save the username, do not let the client change it */
639 username = ssh_message_auth_user(msg);
640 if (!session->username) {
641 if (!username) {
642 ERR("Denying an auth request without a username.");
643 return 1;
644 }
645
Michal Vasko05ba9df2016-01-13 14:40:27 +0100646 session->username = lydict_insert(server_opts.ctx, username, 0);
Michal Vasko086311b2016-01-08 09:53:11 +0100647 } else if (username) {
648 if (strcmp(username, session->username)) {
649 ERR("User \"%s\" changed its username to \"%s\".", session->username, username);
650 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +0100651 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +0100652 return 1;
653 }
654 }
655
656 if (subtype == SSH_AUTH_METHOD_NONE) {
657 /* libssh will return the supported auth methods */
658 return 1;
659 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
660 nc_sshcb_auth_password(session, msg);
661 return 0;
662 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
663 nc_sshcb_auth_pubkey(session, msg);
664 return 0;
665 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
666 nc_sshcb_auth_kbdint(session, msg);
667 return 0;
668 }
669 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
670 if ((type == SSH_REQUEST_CHANNEL_OPEN) && (subtype == (int)SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +0100671 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +0100672 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100673 }
Michal Vasko086311b2016-01-08 09:53:11 +0100674 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +0100675
Michal Vasko086311b2016-01-08 09:53:11 +0100676 } else if ((type == SSH_REQUEST_CHANNEL) && (subtype == (int)SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +0100677 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
678 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +0100679 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +0100680 } else {
681 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100682 }
683 return 0;
684 }
685 }
686
687 /* we did not process it */
688 return 1;
689}
690
Michal Vasko1a38c862016-01-15 15:50:07 +0100691/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +0100692static int
693nc_open_netconf_channel(struct nc_session *session, int timeout)
694{
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100695 int elapsed = 0, ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100696
697 /* message callback is executed twice to give chance for the channel to be
698 * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100699 if (!timeout) {
Michal Vasko2a7d4732016-01-15 09:24:46 +0100700 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100701 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +0100702 return -1;
703 }
704
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100705 ret = nc_timedlock(session->ti_lock, timeout, NULL);
706 if (ret != 1) {
707 return ret;
708 }
709
710 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
711 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +0100712 ERR("Failed to receive SSH messages on a session (%s).",
713 ssh_get_error(session->ti.libssh.session));
Michal Vasko11d142a2016-01-19 15:58:24 +0100714 pthread_mutex_unlock(session->ti_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100715 return -1;
716 }
717
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100718 if (!session->ti.libssh.channel) {
719 /* we did not receive channel-open, timeout */
720 pthread_mutex_unlock(session->ti_lock);
721 return 0;
722 }
723
724 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
725 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +0100726 ERR("Failed to receive SSH messages on a session (%s).",
727 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100728 pthread_mutex_unlock(session->ti_lock);
729 return -1;
730 }
731 pthread_mutex_unlock(session->ti_lock);
732
733 if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
734 /* we did not receive subsystem-request, timeout */
735 return 0;
736 }
737
738 return 1;
739 }
740
741 while (1) {
742 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100743 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100744 return -1;
745 }
746
747 ret = nc_timedlock(session->ti_lock, timeout, &elapsed);
748 if (ret != 1) {
749 return ret;
750 }
751
752 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
753 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +0100754 ERR("Failed to receive SSH messages on a session (%s).",
755 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100756 pthread_mutex_unlock(session->ti_lock);
757 return -1;
758 }
759
760 pthread_mutex_unlock(session->ti_lock);
761
Michal Vasko086311b2016-01-08 09:53:11 +0100762 if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100763 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100764 }
765
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100766 if ((timeout != -1) && (timeout >= elapsed)) {
767 /* timeout */
Michal Vasko086311b2016-01-08 09:53:11 +0100768 break;
769 }
770
Michal Vasko086311b2016-01-08 09:53:11 +0100771 usleep(NC_TIMEOUT_STEP);
772 elapsed += NC_TIMEOUT_STEP;
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100773 }
Michal Vasko086311b2016-01-08 09:53:11 +0100774
Michal Vasko1a38c862016-01-15 15:50:07 +0100775 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100776}
777
Michal Vasko96164bf2016-01-21 15:41:58 +0100778/* ret 0 - timeout, 1 channel has data, 2 some other channel has data,
779 * 3 status change, 4 new SSH message, 5 new NETCONF SSH channel, -1 error */
780int
781nc_ssh_pollin(struct nc_session *session, int *timeout)
782{
783 int ret, elapsed = 0;
784 struct nc_session *new;
785
786 ret = nc_timedlock(session->ti_lock, *timeout, &elapsed);
787 if (*timeout > 0) {
788 *timeout -= elapsed;
789 }
790
791 if (ret != 1) {
792 return ret;
793 }
794
795 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
796 pthread_mutex_unlock(session->ti_lock);
797
798 if (ret != SSH_OK) {
799 ERR("Session %u: failed to receive SSH messages (%s).", session->id,
800 ssh_get_error(session->ti.libssh.session));
801 session->status = NC_STATUS_INVALID;
802 session->term_reason = NC_SESSION_TERM_OTHER;
803 return 3;
804 }
805
806 /* new SSH message */
807 if (session->flags & NC_SESSION_SSH_NEW_MSG) {
808 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
809 if (session->ti.libssh.next) {
810 for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
811 if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel
812 && (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
813 /* new NETCONF SSH channel */
814 return 5;
815 }
816 }
817 }
818
819 /* just some SSH message */
820 return 4;
821 }
822
823 /* no new SSH message, maybe NETCONF data? */
824 ret = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
825 /* not this one */
826 if (!ret) {
827 return 2;
828 } else if (ret == SSH_ERROR) {
829 ERR("Session %u: SSH channel error (%s).", session->id,
830 ssh_get_error(session->ti.libssh.session));
831 session->status = NC_STATUS_INVALID;
832 session->term_reason = NC_SESSION_TERM_OTHER;
833 return 3;
834 } else if (ret == SSH_EOF) {
835 ERR("Session %u: communication channel unexpectedly closed (libssh).",
836 session->id);
837 session->status = NC_STATUS_INVALID;
838 session->term_reason = NC_SESSION_TERM_DROPPED;
839 return 3;
840 }
841
842 return 1;
843}
844
Michal Vasko9e036d52016-01-08 10:49:26 +0100845int
846nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
Michal Vasko086311b2016-01-08 09:53:11 +0100847{
Michal Vasko1a38c862016-01-15 15:50:07 +0100848 int libssh_auth_methods = 0, elapsed = 0, ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100849
850 /* other transport-specific data */
851 session->ti_type = NC_TI_LIBSSH;
852 session->ti.libssh.session = ssh_new();
853 if (!session->ti.libssh.session) {
Michal Vaskod083db62016-01-19 10:31:29 +0100854 ERR("Failed to initialize a new SSH session.");
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100855 close(sock);
Michal Vasko9e036d52016-01-08 10:49:26 +0100856 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100857 }
858
859 if (ssh_opts.auth_methods & NC_SSH_AUTH_PUBLICKEY) {
860 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
861 }
862 if (ssh_opts.auth_methods & NC_SSH_AUTH_PASSWORD) {
863 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
864 }
865 if (ssh_opts.auth_methods & NC_SSH_AUTH_INTERACTIVE) {
866 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
867 }
868 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
869
870 ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
Michal Vasko96164bf2016-01-21 15:41:58 +0100871 session->flags |= NC_SESSION_SSH_MSG_CB;
Michal Vasko086311b2016-01-08 09:53:11 +0100872
Michal Vaskob48aa812016-01-18 14:13:09 +0100873 /* LOCK */
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100874 ret = nc_timedlock(&ssh_opts.sshbind_lock, timeout, &elapsed);
875 if (ret < 1) {
876 return ret;
877 }
Michal Vaskob48aa812016-01-18 14:13:09 +0100878
Michal Vasko086311b2016-01-08 09:53:11 +0100879 if (ssh_bind_accept_fd(ssh_opts.sshbind, session->ti.libssh.session, sock) == SSH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100880 ERR("SSH failed to accept a new connection (%s).", ssh_get_error(ssh_opts.sshbind));
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100881 close(sock);
Michal Vaskob48aa812016-01-18 14:13:09 +0100882 /* UNLOCK */
883 pthread_mutex_unlock(&ssh_opts.sshbind_lock);
Michal Vasko9e036d52016-01-08 10:49:26 +0100884 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100885 }
886
Michal Vaskob48aa812016-01-18 14:13:09 +0100887 /* UNLOCK */
888 pthread_mutex_unlock(&ssh_opts.sshbind_lock);
889
Michal Vasko086311b2016-01-08 09:53:11 +0100890 if (ssh_handle_key_exchange(session->ti.libssh.session) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +0100891 ERR("SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +0100892 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100893 }
894
895 /* authenticate */
896 do {
Michal Vasko2a7d4732016-01-15 09:24:46 +0100897 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100898 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +0100899 return -1;
900 }
901
Michal Vasko086311b2016-01-08 09:53:11 +0100902 if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +0100903 ERR("Failed to receive SSH messages on a session (%s).",
904 ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +0100905 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100906 }
907
908 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
909 break;
910 }
911
912 usleep(NC_TIMEOUT_STEP);
913 elapsed += NC_TIMEOUT_STEP;
914 } while ((timeout == -1) || (timeout && (elapsed < timeout)));
915
916 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
917 /* timeout */
Michal Vasko1a38c862016-01-15 15:50:07 +0100918 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100919 }
920
921 if (timeout > 0) {
922 timeout -= elapsed;
923 }
924
925 /* open channel */
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100926 ret = nc_open_netconf_channel(session, timeout);
Michal Vasko1a38c862016-01-15 15:50:07 +0100927 if (ret < 1) {
928 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100929 }
930
Michal Vasko96164bf2016-01-21 15:41:58 +0100931 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
932
Michal Vasko1a38c862016-01-15 15:50:07 +0100933 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100934}
935
Michal Vasko96164bf2016-01-21 15:41:58 +0100936API int
937nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +0100938{
Michal Vasko96164bf2016-01-21 15:41:58 +0100939 uint16_t i;
940 struct nc_session *new_session = NULL;
Michal Vasko086311b2016-01-08 09:53:11 +0100941
Michal Vasko96164bf2016-01-21 15:41:58 +0100942 if (!ps || !session) {
943 ERRARG;
944 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100945 }
946
Michal Vasko96164bf2016-01-21 15:41:58 +0100947 for (i = 0; i < ps->session_count; ++i) {
948 if ((ps->sessions[i]->status == NC_STATUS_RUNNING) && (ps->sessions[i]->ti_type == NC_TI_LIBSSH)
949 && ps->sessions[i]->ti.libssh.next) {
950 /* an SSH session with more channels */
951 for (new_session = ps->sessions[i]->ti.libssh.next;
952 new_session != ps->sessions[i];
953 new_session = new_session->ti.libssh.next) {
954 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
955 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
956 /* we found our session */
957 break;
958 }
959 }
960 if (new_session != ps->sessions[i]) {
961 break;
962 }
Michal Vaskofb89d772016-01-08 12:25:35 +0100963
Michal Vasko96164bf2016-01-21 15:41:58 +0100964 new_session = NULL;
965 }
966 }
Michal Vaskofb89d772016-01-08 12:25:35 +0100967
Michal Vasko96164bf2016-01-21 15:41:58 +0100968 if (!new_session) {
969 ERR("No session with a NETCONF SSH channel ready was found.");
970 return -1;
971 }
972
973 /* assign new SID atomically */
974 pthread_spin_lock(&server_opts.sid_lock);
975 new_session->id = server_opts.new_session_id++;
976 pthread_spin_unlock(&server_opts.sid_lock);
Michal Vaskofb89d772016-01-08 12:25:35 +0100977
Michal Vasko086311b2016-01-08 09:53:11 +0100978 /* NETCONF handshake */
979 if (nc_handshake(new_session)) {
Michal Vasko96164bf2016-01-21 15:41:58 +0100980 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100981 }
982 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +0100983 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +0100984
Michal Vasko96164bf2016-01-21 15:41:58 +0100985 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100986}