source: ldm/trunk/fuentes/src/plugins/ssh/ssh.c @ 516

Last change on this file since 516 was 516, checked in by mabarracus, 5 years ago

Copy trusty code

File size: 14.1 KB
Line 
1#include <config.h>
2#include <ctype.h>
3#include <fcntl.h>
4#include <glib.h>
5#include <libintl.h>
6#include <locale.h>
7#include <pthread.h>
8#include <pty.h>
9#include <signal.h>
10#include <stdarg.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <unistd.h>
15#include <sys/socket.h>
16#include <sys/ioctl.h>
17#include <sys/stat.h>
18#include <utmp.h>
19#include <errno.h>
20
21#include "../../ldm.h"
22#include "../../ldmutils.h"
23#include "../../ldmgreetercomm.h"
24#include "../../logging.h"
25#include "../../plugin.h"
26#include "ssh.h"
27
28#define ERROR -1
29#define TIMED_OUT -2
30#define MAXEXP 4096
31#define SENTINEL "LTSPROCKS"
32
33LdmBackend *descriptor;
34SSHInfo *sshinfo;
35
36void __attribute__((constructor)) initialize() {
37    descriptor = (LdmBackend*) malloc(sizeof(LdmBackend));
38    bzero(descriptor, sizeof(LdmBackend));
39
40    descriptor->name = "ssh";
41    descriptor->description = "ssh plugin";
42    descriptor->auth_cb = get_auth;
43    descriptor->clean_cb = close_ssh;
44    descriptor->guest_cb = get_guest;
45    descriptor->init_cb = init_ssh;
46    descriptor->start_cb = start_ssh;
47    ldm_init_plugin(descriptor);
48}
49
50/*
51 * init_ssh
52 *  Callback function for initialization
53 */
54void init_ssh() {
55    sshinfo = (SSHInfo*) malloc(sizeof(SSHInfo));
56    bzero(sshinfo, sizeof(SSHInfo));
57
58    /* Get ENV Variables */
59    sshinfo->sshoptions = g_strdup(getenv("LDM_SSHOPTIONS"));
60    sshinfo->override_port = g_strdup(getenv("SSH_OVERRIDE_PORT"));
61}
62
63/*
64 * start_ssh
65 *  Start ssh session
66 */
67void start_ssh() {
68    gboolean error = FALSE;
69
70    /* Variable validation */
71    if (!(sshinfo->username)) {
72        log_entry("ssh",3,"no username");
73        error = TRUE;
74    }
75
76    if (!(sshinfo->password)) {
77        log_entry("ssh",3,"no password");
78        error = TRUE;
79    }
80
81    if (!(sshinfo->server)) {
82        log_entry("ssh",3,"no server");
83        error = TRUE;
84    }
85
86    if (!(sshinfo->session))
87        sshinfo->session = g_strdup("default");
88
89    if (error) {
90        die("ssh","missing mandatory information");
91    }
92
93    /* Getting Xsession */
94    get_Xsession(&(sshinfo->xsession), sshinfo->server);
95
96    /* Check if we are loadbalanced */
97    get_ltsp_cfg(&(sshinfo->server));
98
99    /*
100     * If we run multiple ldm sessions on multiply vty's we need separate
101     * control sockets.
102     */
103    sshinfo->ctl_socket = g_strdup_printf("/var/run/ldm_socket_%d_%s", ldm.pid, sshinfo->server);
104
105    /* Setting ENV variables for plugin */
106    _set_env();
107
108    /* Execute any rc files */
109    log_entry("ssh",6,"calling rc.d pressh scripts");
110    rc_files("pressh");
111
112    ssh_session();
113    log_entry("ssh",6,"established ssh session on '%s' as '%s'",sshinfo->server,sshinfo->username);
114
115    /* Greeter not needed anymore */
116    close_greeter();
117
118    log_entry("ssh",6,"calling rc.d start scripts");
119    rc_files("start");                      /* Execute any rc files */
120
121    log_entry("ssh",6,"starting X session");
122    set_session_env(sshinfo->xsession, sshinfo->session);
123}
124
125/*
126 * get_guest
127 *  Callback function for setting guest login
128 */
129void get_guest() {
130    log_entry("ssh",6,"setting guest login");
131
132    /* Get credentials */
133    g_free(sshinfo->username);
134    g_free(sshinfo->password);
135
136    /* Get UserID */
137    sshinfo->username = g_strdup(getenv("LDM_USERNAME"));
138
139    /* Get password */
140    sshinfo->password = g_strdup(getenv("LDM_PASSWORD"));
141
142
143    /* Don't ask anything from the greeter when on autologin */
144    if (!ldm_getenv_bool("LDM_AUTOLOGIN")) {
145        /* Get hostname */
146        get_host(&(sshinfo->server));
147
148        /* Get Language */
149        get_language(&(sshinfo->lang));
150
151        /* Get Session */
152        get_session(&(sshinfo->session));
153    }
154
155    if (!sshinfo->username) {
156        gchar hostname[HOST_NAME_MAX + 1];      /* +1 for \0 terminator */
157        gethostname(hostname, sizeof hostname);
158
159        sshinfo->username = g_strdup(hostname);
160    }
161    if (!sshinfo->password)
162        sshinfo->password = g_strdup(sshinfo->username);
163
164    {
165        char **hosts_char = NULL;
166        gchar *autoservers = NULL;
167        gboolean good;
168        int i;
169
170        autoservers = g_strdup(getenv("LDM_GUEST_SERVER"));
171        if (!autoservers)
172            autoservers = g_strdup(getenv("LDM_AUTOLOGIN_SERVER"));
173
174        if (!autoservers)
175            autoservers = g_strdup(getenv("LDM_SERVER"));
176
177        hosts_char = g_strsplit(autoservers, " ", -1);
178
179        good = FALSE;
180        if (sshinfo->server) {
181            i = 0;
182            while(1) {
183                if(hosts_char[i] == NULL) {
184                    break;
185                }
186                if(!g_strcmp0(hosts_char[i], sshinfo->server)) {
187                    good = TRUE;
188                    break;
189                }
190                i++;
191            }
192        }
193
194        if (good == FALSE) {
195            sshinfo->server = g_strdup(hosts_char[0]);
196        }
197        g_strfreev(hosts_char);
198        g_free(autoservers);
199        return;
200    }
201}
202
203/*
204 * _set_env
205 *  Set environment variables used by LDM and Greeter
206 */
207void _set_env() {
208    setenv("LDM_SERVER", sshinfo->server, 1);
209    setenv("LDM_USERNAME", sshinfo->username, 1);
210    setenv("LDM_SOCKET", sshinfo->ctl_socket, 1);
211}
212
213/*
214 * get_auth
215 *  Callback function for authentication
216 */
217void get_auth() {
218    /* Get UserID */
219    get_userid(&(sshinfo->username));
220
221    /* Get password */
222    get_passwd(&(sshinfo->password));
223
224    /* Get hostname */
225    get_host(&(sshinfo->server));
226
227    /* Get Language */
228    get_language(&(sshinfo->lang));
229
230    /* Get Session */
231    get_session(&(sshinfo->session));
232}
233
234/*
235 * close_ssh
236 *  Callback function for closing the plugins
237 */
238void close_ssh() {
239    log_entry("ssh",7,"closing ssh session");
240    ssh_endsession();
241
242    // leave no crumbs and free memory allocated for auth values
243    g_free(sshinfo->password);
244    g_free(sshinfo->username);
245    g_free(sshinfo->server);
246    g_free(sshinfo->lang);
247    g_free(sshinfo->session);
248    free(sshinfo);
249}
250
251int expect(int fd, char *p, int seconds, ...) {
252    fd_set set;
253    struct timeval timeout;
254    int i, st;
255    ssize_t size = 0;
256    size_t total = 0;
257    va_list ap;
258    char buffer[BUFSIZ];
259    gchar *arg;
260    GPtrArray *expects;
261    int loopcount = seconds * 5;
262    int loopend = 0;
263
264    bzero(p, MAXEXP);
265
266    expects = g_ptr_array_new();
267
268    va_start(ap, seconds);
269
270    while ((arg = va_arg(ap, char *)) != NULL) {
271        g_ptr_array_add(expects, (gpointer) arg);
272    }
273
274    va_end(ap);
275
276    /*
277     * Set our file descriptor to be watched.
278     */
279
280
281    /*
282     * Main loop.
283     */
284
285    log_entry("ssh",7,"expect: entering main loop");
286
287    while(1) {
288        timeout.tv_sec  = 0;
289        timeout.tv_usec = 200000; /* 200ms timeout */
290
291        FD_ZERO(&set);
292        FD_SET(fd, &set);
293        st = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
294
295        log_entry("ssh", 7, "expect: select returned %d", st);
296
297        /* There is data available - we want to read it in even if
298         * the child process has exited - we want to get the error
299         * message
300         */
301        if (st > 0)
302        {
303            size = read(fd, buffer, sizeof buffer);
304            if (size == -1) {
305                log_entry("ssh", 3, "expect: read returned error %d (%s)", errno, strerror(errno));
306                break;
307            }
308            if (size == 0) {
309                log_entry("ssh", 3, "expect: read returned 0 (EOF))");
310                break;
311            }
312
313            if ((total + size) < MAXEXP) {
314                strncpy(p + total, buffer, size);
315                total += size;
316                log_entry("ssh", 7, "expect: got %d bytes so far", total);
317            }
318            else
319            {
320                log_entry("ssh", 3, "expect: buffer full");
321                return ERROR;
322            }
323        }
324        else /* No data available */
325        {
326            /* select() was interrupted by a signal,
327             * most likely SIGCHLD. Just try again */
328            if (st == -1 && errno == EINTR)
329            {
330                log_entry("ssh", 7, "expect: retrying");
331                continue;
332            }
333            else if (st == -1) {        /* other errors */
334                log_entry("ssh", 3, "expect: select returned error %d (%s)", errno, strerror(errno));
335                break;
336            }
337            /* st == 0 and there was no data for 200ms */
338
339            if (child_exited) {         /* someone died on us */
340                log_entry("ssh", 3, "expect: ssh process has died");
341                break;
342            }
343
344            if (loopcount == 0) {
345                log_entry("ssh", 3, "expect: timed out after %d seconds", seconds);
346                break;
347            }
348
349            for (i = 0; i < expects->len; i++) {
350                if (strstr(p, g_ptr_array_index(expects, i))) {
351                    log_entry("ssh", 7, "expect: found string %d \"%s\"", i, g_ptr_array_index(expects, i));
352                    loopend = TRUE;
353                    break;
354                }
355            }
356
357            loopcount--;            /* We've not seen the data we want */
358        }
359
360        if (loopend) {
361            break;
362        }
363    }
364
365    log_entry("ssh",7,"expect: saw: %s", p);
366
367    if (size < 0 || st < 0 || child_exited) {
368        return ERROR;               /* error occured */
369    }
370    if (loopcount == 0) {
371        return TIMED_OUT;           /* timed out */
372    } else {
373        return i;                   /* which expect did we see? */
374    }
375}
376
377void ssh_chat(gint fd) {
378    int seen;
379    size_t len;
380    gchar lastseen[MAXEXP];
381    int first_time = 1;
382
383    /* We've already got the password here from the mainline,  so there's
384     * no delay between asking for the userid, and the ssh session asking for a
385     * password.  That's why we need the "first_time" variable.  If a
386     * password expiry is in the works, then subsequent password prompts
387     * will cause us to go back to the greeter. */
388
389    child_exited = FALSE;
390
391    while (TRUE) {
392        /* ASSUMPTION: ssh will send out a string that ends in ": " for an expiry */
393        seen = expect(fd, lastseen, 30, SENTINEL, ": ", NULL);
394
395        /* We might have a : in the data, we're looking for :'s at the
396           end of the line */
397        if (seen == 0) {
398            g_free(sshinfo->password);
399            sshinfo->password = NULL;
400            return;
401        } else if (seen == 1) {
402            int i;
403            g_strdelimit(lastseen, "\r\n\t", ' ');
404            g_strchomp(lastseen);
405            i = strlen(lastseen);
406            /* If it's not the first time through, or the :'s not at the
407             * end of a line (password expiry or error), set the message */
408            if ((!first_time) || (lastseen[i - 1] != ':')) {
409                log_entry("ssh", 4, "ssh_chat: ssh returned \"%d\"", lastseen);
410                set_message(lastseen);
411            }
412            /* If ':' *IS* the last character on the line, we'll assume a
413             * password prompt is presented, and get a password */
414            if (lastseen[i - 1] == ':') {
415                log_entry("ssh", 7, "ssh_chat: writing password");
416                write(fd, sshinfo->password, strlen(sshinfo->password));
417                write(fd, "\n", 1);
418            }
419            first_time = 0;
420        } else if (seen < 0) {
421            log_entry("ssh", 3, "ssh_chat: expect returned error %d", seen);
422            g_strstrip(lastseen);
423            len = strlen(lastseen);
424            if (len > 0)
425            {
426                log_entry("ssh", 3, "ssh_chat: ssh returned \"%s\"", lastseen);
427                set_message(_(lastseen));
428            }
429            else
430            {
431                log_entry("ssh", 3, "ssh_chat: did not get an error message from ssh");
432                set_message(_("No response from server, restarting..."));
433            }
434            sleep(5);
435            close_greeter();
436            die("ssh","login failed, restarting");
437        }
438    }
439}
440
441void ssh_tty_init(void) {
442    (void) setsid();
443    if (login_tty(sshinfo->sshslavefd) < 0 ) {
444        die("ssh","login_tty failed");
445    }
446}
447
448/*
449 * ssh_session()
450 * Start an ssh login to the server.
451 */
452void ssh_session(void) {
453    gchar *command;
454    gchar *port = NULL;
455    pthread_t pt;
456
457    /* Check for port Override */
458    if(sshinfo->override_port)
459        port = g_strconcat(" -p ", sshinfo->override_port, " ",  NULL);
460
461    openpty(&(sshinfo->sshfd), &(sshinfo->sshslavefd), NULL, NULL, NULL);
462
463    command = g_strjoin(" ", "ssh", "-Y", "-t", "-M",
464            "-S", sshinfo->ctl_socket,
465            "-o", "NumberOfPasswordPrompts=1",
466             /* ConnectTimeout should be less than the timeout ssh_chat
467              * passes to expect, so we get the error message from ssh
468              * before expect gives up
469              */
470            "-o", "ConnectTimeout=10",
471            "-l", sshinfo->username,
472            port ? port : "",
473            sshinfo->sshoptions ? sshinfo->sshoptions : "",
474            sshinfo->server, "echo " SENTINEL "; exec /bin/sh -", NULL);
475    log_entry("ssh", 6, "ssh_session: %s", command);
476
477    sshinfo->sshpid = ldm_spawn(command, NULL, NULL, ssh_tty_init);
478
479    ssh_chat(sshinfo->sshfd);
480
481    /*
482     * Spawn a thread to keep sshfd clean.
483     */
484    pthread_create(&pt, NULL, eater, NULL);
485
486    if (port)
487        g_free(port);
488}
489
490void ssh_endsession(void) {
491    GPid pid;
492    gchar *command;
493    struct stat stbuf;
494
495    if (!stat(sshinfo->ctl_socket, &stbuf)) {
496        /* socket still exists, so we need to shut down the ssh link */
497
498        command = g_strjoin(" ", "ssh", "-S", sshinfo->ctl_socket, "-O", "exit", sshinfo->server, NULL);
499        log_entry("ssh", 6, "closing ssh session: %s", command);
500        pid = ldm_spawn(command, NULL, NULL, NULL);
501        ldm_wait(pid);
502        close(sshinfo->sshfd);
503        ldm_wait(sshinfo->sshpid);
504        sshinfo->sshpid = 0;
505        g_free(command);
506    }
507}
508
509void *eater() {
510    fd_set set;
511    struct timeval timeout;
512    int st;
513    char buf[BUFSIZ];
514
515    while (1) {
516        if (sshinfo->sshfd == 0) {
517            pthread_exit(NULL);
518            break;
519        }
520
521        timeout.tv_sec = (long)1;             /* one second timeout */
522        timeout.tv_usec = 0;
523        FD_ZERO(&set);
524        FD_SET(sshinfo->sshfd, &set);
525        st = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
526        if (st > 0) {
527            read(sshinfo->sshfd, buf, sizeof buf);
528        }
529    }
530}
Note: See TracBrowser for help on using the repository browser.