2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1999 University of Maryland
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation, and that the name of U.M. not be used in advertising or
11 * publicity pertaining to distribution of the software without specific,
12 * written prior permission. U.M. makes no representations about the
13 * suitability of this software for any purpose. It is provided "as is"
14 * without express or implied warranty.
16 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * Authors: the Amanda Development Team. Its members are listed in a
24 * file named AUTHORS, in the root directory of this distribution.
28 * $Id: ssh-security.c,v 1.23 2006/08/21 20:17:10 martinea Exp $
30 * ssh-security.c - security and transport over ssh or a ssh-like command.
32 * XXX still need to check for initial keyword on connect so we can skip
33 * over shell garbage and other stuff that ssh might want to spew out.
41 #include "security-util.h"
42 #include "sockaddr-util.h"
46 * Number of seconds ssh has to start up
48 #define CONNECT_TIMEOUT 20
51 * Magic values for ssh_conn->handle
53 #define H_TAKEN -1 /* ssh_conn->tok was already read */
54 #define H_EOF -2 /* this connection has been shut down */
59 static void ssh_connect(const char *, char *(*)(char *, void *),
60 void (*)(void *, security_handle_t *, security_status_t),
62 static void ssh_accept(const security_driver_t *driver, char *(*conf_fn)(char *, void *),
64 void (*fn)(security_handle_t *, pkt_t *),
68 * This is our interface to the outside world.
70 const security_driver_t ssh_security_driver = {
74 sec_get_authenticated_peer_name_hostname,
78 stream_recvpkt_cancel,
87 tcpm_stream_read_sync,
88 tcpm_stream_read_cancel,
89 tcpm_close_connection,
94 static int newhandle = 1;
99 static int runssh(struct tcp_conn *, const char *, const char *, const char *,
103 * ssh version of a security handle allocator. Logically sets
104 * up a network "connection".
108 const char * hostname,
109 char * (*conf_fn)(char *, void *),
110 void (*fn)(void *, security_handle_t *, security_status_t),
115 struct sec_handle *rh;
116 char *amandad_path=NULL, *client_username=NULL, *ssh_keys=NULL;
117 char *client_port = NULL;
120 assert(hostname != NULL);
122 auth_debug(1, "ssh_connect: %s\n", hostname);
124 rh = g_new0(struct sec_handle, 1);
125 security_handleinit(&rh->sech, &ssh_security_driver);
128 rh->ev_timeout = NULL;
131 /* get the canonical hostname */
133 if ((result = resolve_hostname(hostname, 0, NULL, &rh->hostname)) != 0
134 || rh->hostname == NULL) {
135 security_seterror(&rh->sech,
136 _("ssh_security could not find canonical name for '%s': %s"),
137 hostname, gai_strerror(result));
138 (*fn)(arg, &rh->sech, S_ERROR);
141 rh->rs = tcpma_stream_client(rh, newhandle++);
142 rh->rc->conf_fn = conf_fn;
143 rh->rc->datap = datap;
148 amfree(rh->hostname);
149 rh->hostname = stralloc(rh->rs->rc->hostname);
152 * We need to open a new connection.
154 * XXX need to eventually limit number of outgoing connections here.
158 amandad_path = conf_fn("amandad_path", datap);
159 client_username = conf_fn("client_username", datap);
160 ssh_keys = conf_fn("ssh_keys", datap);
161 port_str = conf_fn("client_port", datap);
162 if (port_str && strlen(port_str) >= 1) {
163 client_port = port_str;
166 if(rh->rc->read == -1) {
167 if (runssh(rh->rs->rc, amandad_path, client_username, ssh_keys,
169 security_seterror(&rh->sech, _("can't connect to %s: %s"),
170 hostname, rh->rs->rc->errmsg);
177 * The socket will be opened async so hosts that are down won't
178 * block everything. We need to register a write event
179 * so we will know when the socket comes alive.
181 * Overload rh->rs->ev_read to provide a write event handle.
182 * We also register a timeout.
186 rh->rs->ev_read = event_register((event_id_t)rh->rs->rc->write, EV_WRITEFD,
187 sec_connect_callback, rh);
188 rh->ev_timeout = event_register((event_id_t)CONNECT_TIMEOUT, EV_TIME,
189 sec_connect_timeout, rh);
194 (*fn)(arg, &rh->sech, S_ERROR);
197 /* like sec_accept, but first it gets the remote system's hostname */
200 const security_driver_t *driver,
201 char *(*conf_fn)(char *, void *),
204 void (*fn)(security_handle_t *, pkt_t *),
207 struct sec_handle *rh;
208 struct tcp_conn *rc = sec_tcp_conn_get("", 0);
209 char *ssh_connection, *p;
214 /* "Accepting" an SSH connection means that amandad was invoked via sshd, so
215 * we should have anSSH_CONNECTION env var. If not, then this probably isn't
216 * a real SSH connection and we should bail out. */
217 ssh_connection = getenv("SSH_CONNECTION");
218 if (!ssh_connection) {
219 errmsg = g_strdup("$SSH_CONNECTION not set - was amandad started by sshd?");
223 /* make a local copy, to munge */
224 ssh_connection = g_strdup(ssh_connection);
226 /* strip off the first component - the ASCII IP address */
227 if ((p = strchr(ssh_connection, ' ')) == NULL) {
228 errmsg = g_strdup("$SSH_CONNECTION malformed");
233 /* ---- everything from here on is just a warning, leaving hostname at "" */
235 SU_INIT(&addr, AF_INET);
237 /* turn the string address into a sockaddr */
238 if ((result = str_to_sockaddr(ssh_connection, &addr)) != 1) {
240 g_warning("Could not parse peer address %s", ssh_connection);
242 g_warning("Parsing peer address %s: %s", ssh_connection, gai_strerror(result));
247 /* find the hostname */
248 result = getnameinfo((struct sockaddr *)&addr, SS_LEN(&addr),
249 rc->hostname, sizeof(rc->hostname), NULL, 0, 0);
251 g_warning("Could not get hostname for SSH client %s: %s", ssh_connection,
252 gai_strerror(result));
257 if (check_name_give_sockaddr(rc->hostname,
258 (struct sockaddr *)&addr, &errmsg) < 0) {
259 rc->hostname[0] = '\0'; /* null out the bad hostname */
260 g_warning("Checking SSH client DNS: %s", errmsg);
267 g_free(ssh_connection);
273 rc->conf_fn = conf_fn;
275 sec_tcp_conn_read(rc);
280 g_free(ssh_connection);
282 /* make up a fake handle for the error */
283 rh = g_new0(struct sec_handle, 1);
284 security_handleinit(&rh->sech, driver);
285 security_seterror((security_handle_t*)rh, "ssh_accept: %s", errmsg);
287 (*fn)(&rh->sech, NULL);
291 * Forks a ssh to the host listed in rc->hostname
292 * Returns negative on error, with an errmsg in rc->errmsg.
296 struct tcp_conn * rc,
297 const char * amandad_path,
298 const char * client_username,
299 const char * ssh_keys,
300 const char * client_port)
302 int rpipe[2], wpipe[2];
303 char *xamandad_path = (char *)amandad_path;
304 char *xclient_username = (char *)client_username;
305 char *xssh_keys = (char *)ssh_keys;
306 char *xclient_port = (char *)client_port;
308 gchar *ssh_options[100] = {SSH_OPTIONS, NULL};
312 memset(rpipe, -1, SIZEOF(rpipe));
313 memset(wpipe, -1, SIZEOF(wpipe));
314 if (pipe(rpipe) < 0 || pipe(wpipe) < 0) {
315 rc->errmsg = newvstrallocf(rc->errmsg, _("pipe: %s"), strerror(errno));
319 if(!xamandad_path || strlen(xamandad_path) <= 1)
320 xamandad_path = vstralloc(amlibexecdir, "/", "amandad", NULL);
321 if(!xclient_username || strlen(xclient_username) <= 1)
322 xclient_username = CLIENT_LOGIN;
323 if(!xclient_port || strlen(xclient_port) <= 1)
326 myargs = g_ptr_array_sized_new(20);
327 g_ptr_array_add(myargs, SSH);
328 for (ssh_option = ssh_options; *ssh_option != NULL; ssh_option++) {
329 g_ptr_array_add(myargs, *ssh_option);
331 g_ptr_array_add(myargs, "-l");
332 g_ptr_array_add(myargs, xclient_username);
334 g_ptr_array_add(myargs, "-p");
335 g_ptr_array_add(myargs, xclient_port);
337 if (ssh_keys && strlen(ssh_keys) > 1) {
338 g_ptr_array_add(myargs, "-i");
339 g_ptr_array_add(myargs, xssh_keys);
341 g_ptr_array_add(myargs, rc->hostname);
342 g_ptr_array_add(myargs, xamandad_path);
343 g_ptr_array_add(myargs, "-auth=ssh");
344 g_ptr_array_add(myargs, NULL);
346 cmd = g_strjoinv(" ", (gchar **)myargs->pdata);
347 g_debug("exec: %s", cmd);
350 switch (rc->pid = fork()) {
352 rc->errmsg = newvstrallocf(rc->errmsg, _("fork: %s"), strerror(errno));
365 rc->write = wpipe[1];
370 /* drop root privs for good */
375 execvp(SSH, (gchar **)myargs->pdata);
377 error("error: couldn't exec %s: %s", SSH, strerror(errno));
379 /* should never go here, shut up compiler warning */