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

Last change on this file since 855 was 855, checked in by mabarracus, 4 years ago
  • Updated sources to 2.2.18
  • Ported code from patches to apply in 2.2.18
File size: 17.0 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 <crypt.h>
20#include <errno.h>
21
22#include "../../ldm.h"
23#include "../../ldmutils.h"
24#include "../../ldmgreetercomm.h"
25#include "../../logging.h"
26#include "../../plugin.h"
27#include "ssh.h"
28
29#define ERROR -1
30#define TIMED_OUT -2
31#define MAXEXP 4096
32#define SENTINEL "LTSPROCKS"
33
34LdmBackend *descriptor;
35SSHInfo *sshinfo;
36
37void __attribute__ ((constructor)) initialize()
38{
39    descriptor = (LdmBackend *) malloc(sizeof(LdmBackend));
40    bzero(descriptor, sizeof(LdmBackend));
41
42    descriptor->name = "ssh";
43    descriptor->description = "ssh plugin";
44    descriptor->auth_cb = get_auth;
45    descriptor->clean_cb = close_ssh;
46    descriptor->guest_cb = get_guest;
47    descriptor->init_cb = init_ssh;
48    descriptor->start_cb = start_ssh;
49    ldm_init_plugin(descriptor);
50}
51
52/*
53 * init_ssh
54 *  Callback function for initialization
55 */
56void
57init_ssh()
58{
59    sshinfo = (SSHInfo *) malloc(sizeof(SSHInfo));
60    bzero(sshinfo, sizeof(SSHInfo));
61
62    /* Get ENV Variables */
63    sshinfo->sshoptions = g_strdup(getenv("LDM_SSHOPTIONS"));
64    sshinfo->override_port = g_strdup(getenv("SSH_OVERRIDE_PORT"));
65}
66
67/*
68 * start_ssh
69 *  Start ssh session
70 */
71void
72start_ssh()
73{
74    gboolean error = FALSE;
75
76    /* Variable validation */
77    if (!(sshinfo->username)) {
78        log_entry("ssh", 3, "no username");
79        error = TRUE;
80    }
81
82    if (!(sshinfo->password)) {
83        log_entry("ssh", 3, "no password");
84        error = TRUE;
85    }
86
87    if (!(sshinfo->server)) {
88        log_entry("ssh", 3, "no server");
89        error = TRUE;
90    }
91
92    if (!(sshinfo->session))
93        sshinfo->session = g_strdup("default");
94
95    if (error) {
96        die("ssh", "missing mandatory information");
97    }
98
99    /* Getting Xsession */
100    get_Xsession(&(sshinfo->xsession), sshinfo->server);
101
102    /* Check if we are loadbalanced */
103    get_ltsp_cfg(&(sshinfo->server));
104
105    /*
106     * If we run multiple ldm sessions on multiply vty's we need separate
107     * control sockets.
108     */
109    sshinfo->ctl_socket =
110        g_strdup_printf("/var/run/ldm_socket_%d_%s", ldm.pid,
111                        sshinfo->server);
112
113    /* Setting ENV variables for plugin */
114    _set_env();
115
116    /* Execute any rc files */
117    log_entry("ssh", 6, "calling rc.d pressh scripts");
118    rc_files("pressh");
119
120    ssh_session();
121    log_entry("ssh", 6, "established ssh session on '%s' as '%s'",
122              sshinfo->server, sshinfo->username);
123
124    /* Greeter not needed anymore */
125    close_greeter();
126
127    log_entry("ssh", 6, "calling rc.d start scripts");
128    rc_files("start");                           /* Execute any rc files */
129
130    /* ssh_hashpass - Defaults to opt-in (Must set LDM_PASSWORD_HASH to true) */
131    if (ldm_getenv_bool_default("LDM_PASSWORD_HASH", FALSE))   
132    {   
133        ssh_hashpass();
134    } 
135    else
136    {
137        log_entry("hashpass", 6, "LDM_PASSWORD_HASH set to FALSE or unset, skipping hash function");
138    }       
139    log_entry("hashpass", 6, "Freeing password as promised.");
140    g_free(sshinfo->password);
141    sshinfo->password = NULL;
142
143    log_entry("ssh", 6, "starting X session");
144    set_session_env(sshinfo->xsession, sshinfo->session);
145}
146
147/*
148 * get_guest
149 *  Callback function for setting guest login
150 */
151void
152get_guest()
153{
154    log_entry("ssh", 6, "setting guest login");
155
156    /* Get credentials */
157    g_free(sshinfo->username);
158    g_free(sshinfo->password);
159
160    /* Get UserID */
161    sshinfo->username = g_strdup(getenv("LDM_USERNAME"));
162
163    /* Get password */
164    sshinfo->password = g_strdup(getenv("LDM_PASSWORD"));
165
166
167    /* Don't ask anything from the greeter when on autologin */
168    if (!ldm_getenv_bool("LDM_AUTOLOGIN")) {
169        /* Get hostname */
170        get_host(&(sshinfo->server));
171
172        /* Get Language */
173        get_language(&(sshinfo->lang));
174
175        /* Get Session */
176        get_session(&(sshinfo->session));
177    }
178
179    if (!sshinfo->username) {
180        gchar hostname[HOST_NAME_MAX + 1];      /* +1 for \0 terminator */
181        gethostname(hostname, sizeof hostname);
182
183        sshinfo->username = g_strdup(hostname);
184    }
185    if (!sshinfo->password)
186        sshinfo->password = g_strdup(sshinfo->username);
187
188    {
189        char **hosts_char = NULL;
190        gchar *autoservers = NULL;
191        gboolean good;
192        int i;
193
194        autoservers = g_strdup(getenv("LDM_GUEST_SERVER"));
195        if (!autoservers)
196            autoservers = g_strdup(getenv("LDM_AUTOLOGIN_SERVER"));
197
198        if (!autoservers)
199            autoservers = g_strdup(getenv("LDM_SERVER"));
200
201        hosts_char = g_strsplit(autoservers, " ", -1);
202
203        good = FALSE;
204        if (sshinfo->server) {
205            i = 0;
206            while (1) {
207                if (hosts_char[i] == NULL) {
208                    break;
209                }
210                if (!g_strcmp0(hosts_char[i], sshinfo->server)) {
211                    good = TRUE;
212                    break;
213                }
214                i++;
215            }
216        }
217
218        if (good == FALSE) {
219            sshinfo->server = g_strdup(hosts_char[0]);
220        }
221        g_strfreev(hosts_char);
222        g_free(autoservers);
223        return;
224    }
225}
226
227/*
228 * _set_env
229 *  Set environment variables used by LDM and Greeter
230 */
231void
232_set_env()
233{
234    setenv("LDM_SERVER", sshinfo->server, 1);
235    setenv("LDM_USERNAME", sshinfo->username, 1);
236    setenv("LDM_SOCKET", sshinfo->ctl_socket, 1);
237}
238
239/*
240 * get_auth
241 *  Callback function for authentication
242 */
243void
244get_auth()
245{
246    /* Get UserID */
247    get_userid(&(sshinfo->username));
248
249    /* Get password */
250    get_passwd(&(sshinfo->password));
251
252    /* Get hostname */
253    get_host(&(sshinfo->server));
254
255    /* Get Language */
256    get_language(&(sshinfo->lang));
257
258    /* Get Session */
259    get_session(&(sshinfo->session));
260}
261
262/*
263 * close_ssh
264 *  Callback function for closing the plugins
265 */
266void
267close_ssh()
268{
269    log_entry("ssh", 7, "closing ssh session");
270    ssh_endsession();
271
272    // leave no crumbs and free memory allocated for auth values
273    g_free(sshinfo->password);
274    g_free(sshinfo->username);
275    g_free(sshinfo->server);
276    g_free(sshinfo->lang);
277    g_free(sshinfo->session);
278    free(sshinfo);
279}
280
281int
282expect(int fd, char *p, int seconds, ...)
283{
284    fd_set set;
285    struct timeval timeout;
286    int i, st;
287    ssize_t size = 0;
288    size_t total = 0;
289    va_list ap;
290    char buffer[BUFSIZ];
291    gchar *arg;
292    GPtrArray *expects;
293    int loopcount = seconds * 5;
294    int loopend = 0;
295
296    bzero(p, MAXEXP);
297
298    expects = g_ptr_array_new();
299
300    va_start(ap, seconds);
301
302    while ((arg = va_arg(ap, char *)) != NULL) {
303        g_ptr_array_add(expects, (gpointer) arg);
304    }
305
306    va_end(ap);
307
308    /*
309     * Set our file descriptor to be watched.
310     */
311
312
313    /*
314     * Main loop.
315     */
316    log_entry("ssh",7,"expect: entering main loop");
317    while (1) {
318//        timeout.tv_sec = (long) 1;               /* one second timeout */
319//        timeout.tv_usec = 0;
320        timeout.tv_sec  = 0;
321        timeout.tv_usec = 200000; /* 200ms timeout */
322
323        FD_ZERO(&set);
324        FD_SET(fd, &set);
325        st = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
326
327        log_entry("ssh", 7, "expect: select returned %d", st);
328
329//        if (st == -1 && errno == EINTR)
330//        {
331//            continue;                            /* interrupted by signal -> retry */
332//        }
333
334//        if (st < 0) {                            /* bad thing */
335//            break;
336//        }
337
338//        if (loopcount == 0) {
339//            break;
340//        }
341
342//        if (!st) {                               /* timeout */
343//            loopcount--;                         /* We've not seen the data we want */
344//            continue;
345//        }
346
347        /* There is data available - we want to read it in even if
348         * the child process has exited - we want to get the error
349         * message
350         */
351        if (st > 0)
352        {
353                size = read(fd, buffer, sizeof buffer);
354
355                if (size < 0) {
356                    log_entry("ssh", 3, "expect: read returned error %d (%s)", errno, strerror(errno));
357                    break;
358                }
359                if (size == 0) {
360                        log_entry("ssh", 3, "expect: read returned 0 (EOF))");
361                        break;
362                }
363
364                if ((total + size) < MAXEXP) {
365                    strncpy(p + total, buffer, size);
366                    total += size;
367                    log_entry("ssh", 7, "expect: got %d bytes so far", total);
368                }
369                else
370                {
371                        log_entry("ssh", 3, "expect: buffer full");
372                        return ERROR;
373                }
374        }
375        else /* No data available */
376        {
377            /* select() was interrupted by a signal,
378             * most likely SIGCHLD. Just try again */
379            if (st == -1 && errno == EINTR)
380            {
381                log_entry("ssh", 7, "expect: retrying");
382                continue;
383            }
384            else if (st == -1) {        /* other errors */
385                log_entry("ssh", 3, "expect: select returned error %d (%s)", errno, strerror(errno));
386                break;
387            }
388            /* st == 0 and there was no data for 200ms */
389
390            if (child_exited) {         /* someone died on us */
391                log_entry("ssh", 3, "expect: ssh process has died");
392                break;
393            }
394
395            if (loopcount == 0) {
396                log_entry("ssh", 3, "expect: timed out after %d seconds", seconds);
397                break;
398            }
399     
400
401            for (i = 0; i < expects->len; i++) {
402                if (strstr(p, g_ptr_array_index(expects, i))) {
403                        log_entry("ssh", 7, "expect: found string %d \"%s\"", i, g_ptr_array_index(expects, i));
404                        loopend = TRUE;
405                        break;
406                }
407            }
408            loopcount--;            /* We've not seen the data we want */
409        }
410
411        if (loopend) {
412            break;
413        }
414    }
415
416    log_entry("ssh",7,"expect: saw: %s", p);
417
418    if (size < 0 || st < 0) {
419        return ERROR;                            /* error occured */
420    }
421    if (loopcount == 0) {
422        return TIMED_OUT;                        /* timed out */
423    }
424    /* Sleep a bit to make sure we notice if ssh died in the meantime */
425    usleep(100000);
426    if (child_exited)
427    {
428        return ERROR;
429    }
430
431    return i;                                    /* which expect did we see? */
432}
433
434void
435ssh_chat(gint fd)
436{
437    int seen;
438    size_t len;
439    gchar lastseen[MAXEXP];
440    int first_time = 1;
441
442    /* We've already got the password here from the mainline,  so there's
443     * no delay between asking for the userid, and the ssh session asking for a
444     * password.  That's why we need the "first_time" variable.  If a
445     * password expiry is in the works, then subsequent password prompts
446     * will cause us to go back to the greeter. */
447
448    child_exited = FALSE;
449
450    while (TRUE) {
451        /* ASSUMPTION: ssh will send out a string that ends in ": " for an expiry */
452        seen = expect(fd, lastseen, 30, SENTINEL, ": ", NULL);
453
454        /* We might have a : in the data, we're looking for :'s at the
455           end of the line */
456        if (seen == 0) {
457            return;
458        }
459
460        int i;
461        g_strdelimit(lastseen, "\r\n\t", ' ');
462        g_strchomp(lastseen);
463        i = strlen(lastseen);
464
465        if (seen == 1) {
466            /* If it's not the first time through, or the :'s not at the
467             * end of a line (password expiry or error), set the message */
468            if ((!first_time) || (lastseen[i - 1] != ':')) {
469                log_entry("ssh", 4, "ssh_chat: ssh returned \"%d\"", lastseen);
470                set_message(lastseen);
471            }
472            /* If ':' *IS* the last character on the line, we'll assume a
473             * password prompt is presented, and get a password */
474            if (lastseen[i - 1] == ':') {
475                log_entry("ssh", 7, "ssh_chat: writing password");
476                write(fd, sshinfo->password, strlen(sshinfo->password));
477                write(fd, "\n", 1);
478            }
479            first_time = 0;
480        } else if (seen < 0) {
481            log_entry("ssh", 3, "ssh_chat: expect returned error %d", seen);
482            g_strstrip(lastseen);
483            len = strlen(lastseen);
484            if (len > 0)
485            {
486                log_entry("ssh", 3, "ssh_chat: ssh returned \"%s\"", lastseen);
487                set_message(_(lastseen));
488            }
489            else
490            {
491                log_entry("ssh", 3, "ssh_chat: did not get an error message from ssh");
492                set_message(_("No response from server, restarting..."));
493            }
494            sleep(5);
495            close_greeter();
496            die("ssh","login failed, restarting");
497        }
498    }
499}
500
501void
502ssh_tty_init(void)
503{
504    (void) setsid();
505    if (login_tty(sshinfo->sshslavefd) < 0) {
506        die("ssh", "login_tty failed");
507    }
508}
509
510/*
511 * ssh_session()
512 * Start an ssh login to the server.
513 */
514void
515ssh_session(void)
516{
517    gchar *command;
518    gchar *port = NULL;
519    pthread_t pt;
520
521    /* Check for port Override */
522    if (sshinfo->override_port)
523        port = g_strconcat(" -p ", sshinfo->override_port, " ", NULL);
524
525    openpty(&(sshinfo->sshfd), &(sshinfo->sshslavefd), NULL, NULL, NULL);
526
527    command = g_strjoin(" ", "ssh", "-Y", "-t", "-M",
528                        "-S", sshinfo->ctl_socket,
529                        "-o", "NumberOfPasswordPrompts=1",
530                         /* ConnectTimeout should be less than the timeout ssh_chat
531                          * passes to expect, so we get the error message from ssh
532                          * before expect gives up
533                          */
534                        "-o", "ConnectTimeout=10",
535                        "-l", sshinfo->username,
536                        port ? port : "",
537                        sshinfo->sshoptions ? sshinfo->sshoptions : "",
538                        sshinfo->server,
539                        "echo " SENTINEL "; exec /bin/sh -", NULL);
540    log_entry("ssh", 6, "ssh_session: %s", command);
541
542    sshinfo->sshpid = ldm_spawn(command, NULL, NULL, ssh_tty_init);
543
544    ssh_chat(sshinfo->sshfd);
545
546    /*
547     * Spawn a thread to keep sshfd clean.
548     */
549    pthread_create(&pt, NULL, eater, NULL);
550
551    if (port)
552        g_free(port);
553}
554
555void
556ssh_endsession(void)
557{
558    GPid pid;
559    gchar *command;
560    struct stat stbuf;
561
562    if (!stat(sshinfo->ctl_socket, &stbuf)) {
563        /* socket still exists, so we need to shut down the ssh link */
564
565        command =
566            g_strjoin(" ", "ssh", "-S", sshinfo->ctl_socket, "-O", "exit",
567                      sshinfo->server, NULL);
568        log_entry("ssh", 6, "closing ssh session: %s"), command;
569        pid = ldm_spawn(command, NULL, NULL, NULL);
570        ldm_wait(pid);
571        close(sshinfo->sshfd);
572        ldm_wait(sshinfo->sshpid);
573        sshinfo->sshpid = 0;
574        g_free(command);
575    }
576}
577
578/*
579 * ssh_hashpass()
580 * Set up password hash for client /etc/shadow using /dev/urandom
581 * rather than g_rand() due to developer recommendations at:
582 * https://developer.gnome.org/glib/stable/glib-Random-Numbers.html
583 */
584void
585ssh_hashpass(void)
586{
587    FILE *rand_fp;
588    FILE *shad_fp;
589    gchar salt[] = "$6$...............$";
590    gchar buf[16];
591    const gchar seedchars[] =
592        "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
593    gchar *shadowentry;
594    const gchar hashloc[] = "/var/cache/ltsp/shadow.sed";
595    size_t i = 0;
596    log_entry("hashpass", 6, "LDM_PASSWORD_HASH set to true, setting hash");       
597    rand_fp = fopen("/dev/urandom", "r");
598    if (rand_fp == NULL) 
599    {
600        log_entry("hashpass", 7, "Unable to read from /dev/urandom - Skipping HASH function");
601    } 
602    else 
603    {     
604        fread(buf, sizeof buf, 1, rand_fp);
605        fclose(rand_fp);
606        for (; i < sizeof buf; i++) 
607        {
608            salt[3 + i] = seedchars[buf[i] % (sizeof seedchars - 1)];
609        }
610        shadowentry = crypt(sshinfo->password, salt);
611        log_entry("hashpass", 6, "hash created");
612        /* generate dynamic file for writing hash to.
613        * Will remove anything in its way.
614        * This will be removed during rc.d script run.
615        */
616        shad_fp = fopen(hashloc, "w");
617        if (shad_fp == NULL) 
618        {
619            log_entry("hashpass", 7, "Unable to open %s for hash entry.",
620                      hashloc);
621        }
622        else
623        {
624            fprintf(shad_fp,
625                    "# Generated by LTSP, for LDM rc.d script manipulation\n$s:!:%s:",
626                    shadowentry);
627            fclose(shad_fp);
628        }
629    } 
630}
631
632void *
633eater()
634{
635    fd_set set;
636    struct timeval timeout;
637    int st;
638    char buf[BUFSIZ];
639
640    while (1) {
641        if (sshinfo->sshfd == 0) {
642            pthread_exit(NULL);
643            break;
644        }
645
646        timeout.tv_sec = (long) 1;               /* one second timeout */
647        timeout.tv_usec = 0;
648        FD_ZERO(&set);
649        FD_SET(sshinfo->sshfd, &set);
650        st = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
651        if (st > 0) {
652            read(sshinfo->sshfd, buf, sizeof buf);
653        }
654    }
655}
Note: See TracBrowser for help on using the repository browser.