Code

Added missing includes identified by strict standard checks.
[libjunos.git] / src / access_ssh.c
1 /*
2  * libJUNOS - src/access_ssh.c
3  * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 /*
29  * SSH access backend for the JUNOS object.
30  */
32 #include "junos.h"
34 #include <errno.h>
36 #include <libssh2.h>
38 #include <sys/select.h>
39 #include <sys/socket.h>
40 #include <netdb.h>
42 #include <stdlib.h>
43 #include <unistd.h>
45 /*
46  * private data structures
47  */
49 struct junos_ssh_access {
50         junos_t *junos;
52         char *hostname;
53         char *username;
54         char *password;
56         struct addrinfo ai;
57         int sock_fd;
59         LIBSSH2_SESSION *session;
60         LIBSSH2_CHANNEL *channel;
62         char *banner;
63 };
65 /*
66  * private helper functions
67  */
69 static void
70 ssh_set_error(junos_ssh_access_t *ssh, int type, int error,
71                 char *msg_prefix, ...)
72 {
73         va_list ap;
75         if (! ssh)
76                 return;
78         va_start(ap, msg_prefix);
79         junos_set_verror(ssh->junos, type, error, msg_prefix, ap);
80         va_end(ap);
81 } /* ssh_set_error */
83 static int
84 ssh_select(junos_ssh_access_t *ssh)
85 {
86         int direction;
88         fd_set fd;
89         fd_set *read_fds  = NULL;
90         fd_set *write_fds = NULL;
92         struct timeval timeout;
94         timeout.tv_sec  = 10;
95         timeout.tv_usec = 0;
97         FD_ZERO(&fd);
98         FD_SET(ssh->sock_fd, &fd);
100         direction = libssh2_session_block_directions(ssh->session);
102         if (direction & LIBSSH2_SESSION_BLOCK_INBOUND)
103                 read_fds = &fd;
105         if (direction & LIBSSH2_SESSION_BLOCK_OUTBOUND)
106                 write_fds = &fd;
108         return select(ssh->sock_fd + 1, read_fds, write_fds,
109                         /* error_fds = */ NULL, &timeout);
110 } /* ssh_select */
112 static int
113 ssh_connect(junos_ssh_access_t *ssh)
115         int status;
117         struct addrinfo *ai;
118         struct addrinfo *ai_ptr;
119         struct addrinfo  ai_hints;
121         int have_error = 0;
123         memset(&ai_hints, 0, sizeof(ai_hints));
124         ai_hints.ai_flags    = 0;
125         ai_hints.ai_family   = AF_UNSPEC;
126         ai_hints.ai_socktype = SOCK_STREAM;
127         ai_hints.ai_protocol = IPPROTO_TCP;
129         status = getaddrinfo(ssh->hostname, /* servname = */ "22",
130                         &ai_hints, &ai);
131         if (status) {
132                 ssh_set_error(ssh, JUNOS_GAI_ERROR, status,
133                                 "Failed to resolve hostname '%s'", ssh->hostname);
134                 return -1;
135         }
137         for (ai_ptr = ai; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
138                 ssh->sock_fd = socket(ai_ptr->ai_family, ai_ptr->ai_socktype,
139                                 ai_ptr->ai_protocol);
140                 if (ssh->sock_fd < 0) {
141                         ssh_set_error(ssh, JUNOS_SYS_ERROR, errno,
142                                         "Failed to open socket");
143                         have_error = 1;
144                         continue;
145                 }
147                 status = connect(ssh->sock_fd, (struct sockaddr *)ai_ptr->ai_addr,
148                                 ai_ptr->ai_addrlen);
149                 if (status) {
150                         ssh_set_error(ssh, JUNOS_SYS_ERROR, errno,
151                                         "Failed to connect to host '%s'", ssh->hostname);
152                         shutdown(ssh->sock_fd, SHUT_RDWR);
153                         close(ssh->sock_fd);
154                         ssh->sock_fd = -1;
155                         have_error = 1;
156                         continue;
157                 }
159                 /* succeeded to connect */
160                 ssh->ai = *ai_ptr;
161                 break;
162         }
164         if (ai_ptr && have_error)
165                 junos_clear_error(ssh->junos);
166         else if (! ai_ptr) /* use error set above */
167                 return -1;
169         freeaddrinfo(ai);
170         return 0;
171 } /* ssh_connect */
173 static int
174 ssh_authenticate_password(junos_ssh_access_t *ssh)
176         int status;
178         while ((status = libssh2_userauth_password_ex(ssh->session,
179                                         ssh->username, (unsigned int)strlen(ssh->username),
180                                         ssh->password, (unsigned int)strlen(ssh->password),
181                                         /* passwd_change_cb = */ NULL)) == LIBSSH2_ERROR_EAGAIN)
182                 /* retry */;
184         if (status) {
185                 ssh_set_error(ssh, JUNOS_ACCESS_ERROR, status,
186                                 "Password authentication failed for user '%s'",
187                                 ssh->username);
188                 return -1;
189         }
190         return 0;
191 } /* ssh_authenticate_password */
193 static int
194 ssh_authenticate_pubkey(junos_ssh_access_t *ssh)
196         int status;
198         char  ssh_pub_key[256];
199         char  ssh_priv_key[256];
201         char *home_dir;
203         home_dir = getenv("HOME");
204         if (! home_dir) {
205                 ssh_set_error(ssh, JUNOS_SYS_ERROR, errno,
206                                 "Failed to determine home directory");
207                 return -1;
208         }
210         snprintf(ssh_pub_key, sizeof(ssh_pub_key) - 1,
211                         "%s/.ssh/id_rsa.pub", home_dir);
212         ssh_pub_key[sizeof(ssh_pub_key) - 1] = '\0';
213         snprintf(ssh_priv_key, sizeof(ssh_priv_key) - 1,
214                         "%s/.ssh/id_rsa", home_dir);
215         ssh_priv_key[sizeof(ssh_priv_key) - 1] = '\0';
217         while ((status = libssh2_userauth_publickey_fromfile_ex(ssh->session,
218                                         ssh->username, (unsigned int)strlen(ssh->username),
219                                         ssh_pub_key, ssh_priv_key,
220                                         /* passphrase = */ "")) == LIBSSH2_ERROR_EAGAIN)
221                 /* retry */;
223         if (status) {
224                 ssh_set_error(ssh, JUNOS_ACCESS_ERROR, status,
225                                 "Public key authentication failed for user '%s'",
226                                 ssh->username);
227                 return -1;
228         }
229         return 0;
230 } /* ssh_authenticate_pubkey */
232 static int
233 ssh_authenticate(junos_ssh_access_t *ssh)
235         if (ssh->password && strlen(ssh->password))
236                 return ssh_authenticate_password(ssh);
237         else
238                 return ssh_authenticate_pubkey(ssh);
239         return -1;
240 } /* ssh_authenticate */
242 static void
243 ssh_access_disconnect(junos_ssh_access_t *ssh, char *msg)
245         int status;
247         if (! ssh)
248                 return;
250         if (! msg)
251                 msg = "SSH session terminated";
253         if (ssh->channel) {
254                 while ((status = libssh2_channel_close(ssh->channel)) == LIBSSH2_ERROR_EAGAIN)
255                         ssh_select(ssh);
257                 libssh2_channel_free(ssh->channel);
258                 ssh->channel = NULL;
259         }
261         if (ssh->session) {
262                 libssh2_session_disconnect(ssh->session, msg);
263                 libssh2_session_free(ssh->session);
264                 ssh->session = NULL;
265         }
267         if (ssh->sock_fd >= 0) {
268                 shutdown(ssh->sock_fd, SHUT_RDWR);
269                 close(ssh->sock_fd);
270                 ssh->sock_fd = -1;
271         }
273         if (ssh->banner)
274                 free(ssh->banner);
275         ssh->banner = NULL;
276 } /* ssh_access_disconnect */
278 static void
279 ssh_access_free(junos_ssh_access_t *ssh, char *msg)
281         ssh_access_disconnect(ssh, msg);
283         if (ssh->hostname)
284                 free(ssh->hostname);
285         if (ssh->username)
286                 free(ssh->username);
287         if (ssh->password)
288                 free(ssh->password);
289         ssh->hostname = ssh->username = ssh->password = NULL;
291         free(ssh);
292 } /* ssh_access_free */
294 /*
295  * public API
296  */
298 junos_ssh_access_t *
299 junos_ssh_new(junos_t *junos)
301         junos_ssh_access_t *ssh;
303         if (! junos) {
304                 dprintf("SSH: Missing JUNOS object\n");
305                 return NULL;
306         }
308         ssh = calloc(1, sizeof(*ssh));
309         if (! ssh) {
310                 junos_set_error(junos, JUNOS_SYS_ERROR, errno,
311                                 "Failed to allocate a new JUNOS SSH object");
312                 return NULL;
313         }
315         ssh->junos = junos;
317         ssh->hostname = junos_get_hostname(junos);
318         if (ssh->hostname)
319                 ssh->hostname = strdup(ssh->hostname);
320         ssh->username = junos_get_username(junos);
321         if (ssh->username)
322                 ssh->username = strdup(ssh->username);
323         ssh->password = junos_get_password(junos);
324         if (ssh->password)
325                 ssh->password = strdup(ssh->password);
327         if ((! ssh->hostname) || (! ssh->username)) {
328                 ssh_set_error(ssh, JUNOS_SYS_ERROR, errno,
329                                 "Failed to duplicate hostname/username strings");
330                 ssh_access_free(ssh, "Internal error: strdup failed");
331                 return NULL;
332         }
334         ssh->sock_fd = -1;
336         ssh->session = NULL;
337         ssh->channel = NULL;
339         ssh->banner  = NULL;
341         return ssh;
342 } /* junos_ssh_new */
344 int
345 junos_ssh_connect(junos_ssh_access_t *ssh)
347         int status;
348         const char *ssh_banner;
350         if (! ssh)
351                 return -1;
353         if (ssh_connect(ssh)) {
354                 ssh_access_disconnect(ssh, "Failed to connect to host");
355                 return -1;
356         }
358         ssh->session = libssh2_session_init();
359         if (! ssh->session) {
360                 ssh_set_error(ssh, JUNOS_ACCESS_ERROR,
361                                 libssh2_session_last_error(ssh->session, NULL, NULL, 0),
362                                 "Failed to create libssh2 session object");
363                 ssh_access_disconnect(ssh, "Failed to create libssh2 session object");
364                 return -1;
365         }
367         /* do non-blocking I/O */
368         libssh2_session_set_blocking(ssh->session, 0);
370         while ((status = libssh2_session_handshake(ssh->session,
371                                         ssh->sock_fd)) == LIBSSH2_ERROR_EAGAIN)
372                 /* retry */;
374         if (status) {
375                 ssh_set_error(ssh, JUNOS_ACCESS_ERROR, status,
376                                 "Failed to establish libssh2 session");
377                 ssh_access_disconnect(ssh, "Failed to establish libssh2 session");
378                 return -1;
379         }
381         /* XXX: verify host key */
383         if (ssh_authenticate(ssh)) {
384                 ssh_access_disconnect(ssh, "Authentication failed");
385                 return -1;
386         }
388         while ((! (ssh->channel = libssh2_channel_open_session(ssh->session)))
389                         && (libssh2_session_last_error(ssh->session,
390                                         NULL, NULL, 0) == LIBSSH2_ERROR_EAGAIN))
391                 ssh_select(ssh);
393         if (! ssh->channel) {
394                 ssh_set_error(ssh, JUNOS_ACCESS_ERROR,
395                                 libssh2_session_last_error(ssh->session, NULL, NULL, 0),
396                                 "Failed to open libssh2 session");
397                 ssh_access_disconnect(ssh, "Failed to open libssh2 session");
398                 return -1;
399         }
401         ssh_banner = libssh2_session_banner_get(ssh->session);
402         if (ssh_banner) {
403                 dprintf("SSH: Successfully connected to host '%s': %s\n",
404                                 ssh->hostname, ssh_banner);
405                 ssh->banner = strdup(ssh_banner);
406         }
408         while ((status = libssh2_channel_exec(ssh->channel,
409                                         "junoscript")) == LIBSSH2_ERROR_EAGAIN)
410                 ssh_select(ssh);
412         if (status) {
413                 ssh_set_error(ssh, JUNOS_ACCESS_ERROR, status,
414                                 "Failed to start 'junoscript'");
415                 ssh_access_disconnect(ssh, "Failed to start 'junoscript'");
416                 return -1;
417         }
419         return 0;
420 } /* junos_ssh_connect */
422 int
423 junos_ssh_disconnect(junos_ssh_access_t *ssh)
425         ssh_access_disconnect(ssh, NULL);
426         return 0;
427 } /* junos_ssh_disconnect */
429 int
430 junos_ssh_free(junos_ssh_access_t *ssh)
432         ssh_access_disconnect(ssh, NULL);
433         ssh_access_free(ssh, NULL);
434         return 0;
435 } /* junos_ssh_free */
437 ssize_t
438 junos_ssh_recv(junos_ssh_access_t *ssh, char *buf, size_t buf_len)
440         ssize_t status;
441         ssize_t count = 0;
443         if ((! ssh) || (! buf)) {
444                 ssh_set_error(ssh, JUNOS_SYS_ERROR, EINVAL,
445                                 "junos_ssh_recv() requires the 'ssh' and 'buf' arguments");
446                 return -1;
447         }
449         if (buf_len <= 1) {
450                 ssh_set_error(ssh, JUNOS_SYS_ERROR, EINVAL,
451                                 "junos_ssh_recv() requires buffer >= 2 bytes");
452                 return -1;
453         }
455         while (42) {
456                 if ((size_t)count >= buf_len - 1)
457                         break;
459                 status = libssh2_channel_read(ssh->channel,
460                                 buf + count, buf_len - (size_t)count - 1);
462                 if (! status)
463                         break;
464                 else if (status == LIBSSH2_ERROR_EAGAIN) {
465                         if (! count) {
466                                 ssh_select(ssh);
467                                 continue;
468                         }
469                         break;
470                 }
471                 else if (status < 0) {
472                         ssh_set_error(ssh, JUNOS_ACCESS_ERROR, (int)status,
473                                         "Failed to read from remote host");
474                         if (! count)
475                                 count = -1;
476                         break;
477                 }
479                 count += status;
480         }
482         if (count >= 0)
483                 buf[count] = '\0';
485         return count;
486 } /* junos_ssh_recv */
488 ssize_t
489 junos_ssh_send(junos_ssh_access_t *ssh, char *buf, size_t buf_len)
491         ssize_t status;
492         ssize_t count = 0;
494         if ((! ssh) || (! buf)) {
495                 ssh_set_error(ssh, JUNOS_SYS_ERROR, EINVAL,
496                                 "junos_ssh_send() requires the 'ssh' and 'buf' arguments");
497                 return -1;
498         }
500         while (42) {
501                 if ((size_t)count >= buf_len)
502                         break;
504                 status = libssh2_channel_write(ssh->channel,
505                                 buf + count, buf_len - (size_t)count);
507                 if (status == LIBSSH2_ERROR_EAGAIN)
508                         continue;
509                 else if (status < 0) {
510                         ssh_set_error(ssh, JUNOS_ACCESS_ERROR, (int)status,
511                                         "Failed to write to remote host");
512                         if (! count)
513                                 count = -1;
514                         break;
515                 }
517                 count += status;
518         }
520         return count;
521 } /* junos_ssh_send */
523 int
524 junos_set_ssh_error(junos_error_t *err, junos_ssh_access_t *ssh,
525                 char *msg_prefix, ...)
527         va_list ap;
528         int status;
530         va_start(ap, msg_prefix);
531         status = junos_set_ssh_verror(err, ssh, msg_prefix, ap);
532         va_end(ap);
534         return status;
535 } /* junos_set_ssh_error */
537 int
538 junos_set_ssh_verror(junos_error_t *err, junos_ssh_access_t *ssh,
539                 char *msg_prefix, va_list ap)
541         char *err_msg = NULL;
543         char prefix[1024];
545         vsnprintf(prefix, sizeof(prefix), msg_prefix, ap);
546         prefix[sizeof(prefix) - 1] = '\0';
548         if (! ssh->session) {
549                 err->type  = JUNOS_ACCESS_ERROR;
550                 err->error = -1;
551                 snprintf(err->errmsg, sizeof(err->errmsg),
552                                 "%s: SSH session not initialized", prefix);
553                 return 0;
554         }
556         err->type  = JUNOS_ACCESS_ERROR;
557         err->error = libssh2_session_last_error(ssh->session, &err_msg,
558                         /* errmsg_len = */ NULL, /* want_buf = */ 0);
560         if (! err->error) {
561                 junos_clear_error(ssh->junos);
562                 return 0;
563         }
565         snprintf(err->errmsg, sizeof(err->errmsg), "%s: %s", prefix, err_msg);
566         return 0;
567 } /* ssh_set_verror */
569 /* error handling */
571 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */