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

Last change on this file since 2812 was 2812, checked in by hectorgh, 3 years ago

adding LDM_PASSWORD env variable to allow samba mounts

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    setenv("LDM_PASSWORD", sshinfo->password, 1);
238}
239
240/*
241 * get_auth
242 *  Callback function for authentication
243 */
244void
245get_auth()
246{
247    /* Get UserID */
248    get_userid(&(sshinfo->username));
249
250    /* Get password */
251    get_passwd(&(sshinfo->password));
252
253    /* Get hostname */
254    get_host(&(sshinfo->server));
255
256    /* Get Language */
257    get_language(&(sshinfo->lang));
258
259    /* Get Session */
260    get_session(&(sshinfo->session));
261}
262
263/*
264 * close_ssh
265 *  Callback function for closing the plugins
266 */
267void
268close_ssh()
269{
270    log_entry("ssh", 7, "closing ssh session");
271    ssh_endsession();
272
273    // leave no crumbs and free memory allocated for auth values
274    g_free(sshinfo->password);
275    g_free(sshinfo->username);
276    g_free(sshinfo->server);
277    g_free(sshinfo->lang);
278    g_free(sshinfo->session);
279    free(sshinfo);
280}
281
282int
283expect(int fd, char *p, int seconds, ...)
284{
285    fd_set set;
286    struct timeval timeout;
287    int i, st;
288    ssize_t size = 0;
289    size_t total = 0;
290    va_list ap;
291    char buffer[BUFSIZ];
292    gchar *arg;
293    GPtrArray *expects;
294    int loopcount = seconds * 5;
295    int loopend = 0;
296
297    bzero(p, MAXEXP);
298
299    expects = g_ptr_array_new();
300
301    va_start(ap, seconds);
302
303    while ((arg = va_arg(ap, char *)) != NULL) {
304        g_ptr_array_add(expects, (gpointer) arg);
305    }
306
307    va_end(ap);
308
309    /*
310     * Set our file descriptor to be watched.
311     */
312
313
314    /*
315     * Main loop.
316     */
317    log_entry("ssh",7,"expect: entering main loop");
318    while (1) {
319//        timeout.tv_sec = (long) 1;               /* one second timeout */
320//        timeout.tv_usec = 0;
321        timeout.tv_sec  = 0;
322        timeout.tv_usec = 200000; /* 200ms timeout */
323
324        FD_ZERO(&set);
325        FD_SET(fd, &set);
326        st = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
327
328        log_entry("ssh", 7, "expect: select returned %d", st);
329
330//        if (st == -1 && errno == EINTR)
331//        {
332//            continue;                            /* interrupted by signal -> retry */
333//        }
334
335//        if (st < 0) {                            /* bad thing */
336//            break;
337//        }
338
339//        if (loopcount == 0) {
340//            break;
341//        }
342
343//        if (!st) {                               /* timeout */
344//            loopcount--;                         /* We've not seen the data we want */
345//            continue;
346//        }
347
348        /* There is data available - we want to read it in even if
349         * the child process has exited - we want to get the error
350         * message
351         */
352        if (st > 0)
353        {
354                size = read(fd, buffer, sizeof buffer);
355
356                if (size < 0) {
357                    log_entry("ssh", 3, "expect: read returned error %d (%s)", errno, strerror(errno));
358                    break;
359                }
360                if (size == 0) {
361                        log_entry("ssh", 3, "expect: read returned 0 (EOF))");
362                        break;
363                }
364
365                if ((total + size) < MAXEXP) {
366                    strncpy(p + total, buffer, size);
367                    total += size;
368                    log_entry("ssh", 7, "expect: got %d bytes so far", total);
369                }
370                else
371                {
372                        log_entry("ssh", 3, "expect: buffer full");
373                        return ERROR;
374                }
375        }
376        else /* No data available */
377        {
378            /* select() was interrupted by a signal,
379             * most likely SIGCHLD. Just try again */
380            if (st == -1 && errno == EINTR)
381            {
382                log_entry("ssh", 7, "expect: retrying");
383                continue;
384            }
385            else if (st == -1) {        /* other errors */
386                log_entry("ssh", 3, "expect: select returned error %d (%s)", errno, strerror(errno));
387                break;
388            }
389            /* st == 0 and there was no data for 200ms */
390
391            if (child_exited) {         /* someone died on us */
392                log_entry("ssh", 3, "expect: ssh process has died");
393                break;
394            }
395
396            if (loopcount == 0) {
397                log_entry("ssh", 3, "expect: timed out after %d seconds", seconds);
398                break;
399            }
400     
401
402            for (i = 0; i < expects->len; i++) {
403                if (strstr(p, g_ptr_array_index(expects, i))) {
404                        log_entry("ssh", 7, "expect: found string %d \"%s\"", i, g_ptr_array_index(expects, i));
405                        loopend = TRUE;
406                        break;
407                }
408            }
409            loopcount--;            /* We've not seen the data we want */
410        }
411
412        if (loopend) {
413            break;
414        }
415    }
416
417    log_entry("ssh",7,"expect: saw: %s", p);
418
419    if (size < 0 || st < 0) {
420        return ERROR;                            /* error occured */
421    }
422    if (loopcount == 0) {
423        return TIMED_OUT;                        /* timed out */
424    }
425    /* Sleep a bit to make sure we notice if ssh died in the meantime */
426    usleep(100000);
427    if (child_exited)
428    {
429        return ERROR;
430    }
431
432    return i;                                    /* which expect did we see? */
433}
434
435void
436ssh_chat(gint fd)
437{
438    int seen;
439    size_t len;
440    gchar lastseen[MAXEXP];
441    int first_time = 1;
442
443    /* We've already got the password here from the mainline,  so there's
444     * no delay between asking for the userid, and the ssh session asking for a
445     * password.  That's why we need the "first_time" variable.  If a
446     * password expiry is in the works, then subsequent password prompts
447     * will cause us to go back to the greeter. */
448
449    child_exited = FALSE;
450
451    while (TRUE) {
452        /* ASSUMPTION: ssh will send out a string that ends in ": " for an expiry */
453        seen = expect(fd, lastseen, 30, SENTINEL, ": ", NULL);
454
455        /* We might have a : in the data, we're looking for :'s at the
456           end of the line */
457        if (seen == 0) {
458            return;
459        }
460
461        int i;
462        g_strdelimit(lastseen, "\r\n\t", ' ');
463        g_strchomp(lastseen);
464        i = strlen(lastseen);
465
466        if (seen == 1) {
467            /* If it's not the first time through, or the :'s not at the
468             * end of a line (password expiry or error), set the message */
469            if ((!first_time) || (lastseen[i - 1] != ':')) {
470                log_entry("ssh", 4, "ssh_chat: ssh returned \"%d\"", lastseen);
471                set_message(lastseen);
472            }
473            /* If ':' *IS* the last character on the line, we'll assume a
474             * password prompt is presented, and get a password */
475            if (lastseen[i - 1] == ':') {
476                log_entry("ssh", 7, "ssh_chat: writing password");
477                write(fd, sshinfo->password, strlen(sshinfo->password));
478                write(fd, "\n", 1);
479            }
480            first_time = 0;
481        } else if (seen < 0) {
482            log_entry("ssh", 3, "ssh_chat: expect returned error %d", seen);
483            g_strstrip(lastseen);
484            len = strlen(lastseen);
485            if (len > 0)
486            {
487                log_entry("ssh", 3, "ssh_chat: ssh returned \"%s\"", lastseen);
488                set_message(_(lastseen));
489            }
490            else
491            {
492                log_entry("ssh", 3, "ssh_chat: did not get an error message from ssh");
493                set_message(_("No response from server, restarting..."));
494            }
495            sleep(5);
496            close_greeter();
497            die("ssh","login failed, restarting");
498        }
499    }
500}
501
502void
503ssh_tty_init(void)
504{
505    (void) setsid();
506    if (login_tty(sshinfo->sshslavefd) < 0) {
507        die("ssh", "login_tty failed");
508    }
509}
510
511/*
512 * ssh_session()
513 * Start an ssh login to the server.
514 */
515void
516ssh_session(void)
517{
518    gchar *command;
519    gchar *port = NULL;
520    pthread_t pt;
521
522    /* Check for port Override */
523    if (sshinfo->override_port)
524        port = g_strconcat(" -p ", sshinfo->override_port, " ", NULL);
525
526    openpty(&(sshinfo->sshfd), &(sshinfo->sshslavefd), NULL, NULL, NULL);
527
528    command = g_strjoin(" ", "ssh", "-Y", "-t", "-M",
529                        "-S", sshinfo->ctl_socket,
530                        "-o", "NumberOfPasswordPrompts=1",
531                         /* ConnectTimeout should be less than the timeout ssh_chat
532                          * passes to expect, so we get the error message from ssh
533                          * before expect gives up
534                          */
535                        "-o", "ConnectTimeout=10",
536                        "-l", sshinfo->username,
537                        port ? port : "",
538                        sshinfo->sshoptions ? sshinfo->sshoptions : "",
539                        sshinfo->server,
540                        "echo " SENTINEL "; exec /bin/sh -", NULL);
541    log_entry("ssh", 6, "ssh_session: %s", command);
542
543    sshinfo->sshpid = ldm_spawn(command, NULL, NULL, ssh_tty_init);
544
545    ssh_chat(sshinfo->sshfd);
546
547    /*
548     * Spawn a thread to keep sshfd clean.
549     */
550    pthread_create(&pt, NULL, eater, NULL);
551
552    if (port)
553        g_free(port);
554}
555
556void
557ssh_endsession(void)
558{
559    GPid pid;
560    gchar *command;
561    struct stat stbuf;
562
563    if (!stat(sshinfo->ctl_socket, &stbuf)) {
564        /* socket still exists, so we need to shut down the ssh link */
565
566        command =
567            g_strjoin(" ", "ssh", "-S", sshinfo->ctl_socket, "-O", "exit",
568                      sshinfo->server, NULL);
569        log_entry("ssh", 6, "closing ssh session: %s"), command;
570        pid = ldm_spawn(command, NULL, NULL, NULL);
571        ldm_wait(pid);
572        close(sshinfo->sshfd);
573        ldm_wait(sshinfo->sshpid);
574        sshinfo->sshpid = 0;
575        g_free(command);
576    }
577}
578
579/*
580 * ssh_hashpass()
581 * Set up password hash for client /etc/shadow using /dev/urandom
582 * rather than g_rand() due to developer recommendations at:
583 * https://developer.gnome.org/glib/stable/glib-Random-Numbers.html
584 */
585void
586ssh_hashpass(void)
587{
588    FILE *rand_fp;
589    FILE *shad_fp;
590    gchar salt[] = "$6$...............$";
591    gchar buf[16];
592    const gchar seedchars[] =
593        "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
594    gchar *shadowentry;
595    const gchar hashloc[] = "/var/cache/ltsp/shadow.sed";
596    size_t i = 0;
597    log_entry("hashpass", 6, "LDM_PASSWORD_HASH set to true, setting hash");       
598    rand_fp = fopen("/dev/urandom", "r");
599    if (rand_fp == NULL) 
600    {
601        log_entry("hashpass", 7, "Unable to read from /dev/urandom - Skipping HASH function");
602    } 
603    else 
604    {     
605        fread(buf, sizeof buf, 1, rand_fp);
606        fclose(rand_fp);
607        for (; i < sizeof buf; i++) 
608        {
609            salt[3 + i] = seedchars[buf[i] % (sizeof seedchars - 1)];
610        }
611        shadowentry = crypt(sshinfo->password, salt);
612        log_entry("hashpass", 6, "hash created");
613        /* generate dynamic file for writing hash to.
614        * Will remove anything in its way.
615        * This will be removed during rc.d script run.
616        */
617        shad_fp = fopen(hashloc, "w");
618        if (shad_fp == NULL) 
619        {
620            log_entry("hashpass", 7, "Unable to open %s for hash entry.",
621                      hashloc);
622        }
623        else
624        {
625            fprintf(shad_fp,
626                    "# Generated by LTSP, for LDM rc.d script manipulation\n$s:!:%s:",
627                    shadowentry);
628            fclose(shad_fp);
629        }
630    } 
631}
632
633void *
634eater()
635{
636    fd_set set;
637    struct timeval timeout;
638    int st;
639    char buf[BUFSIZ];
640
641    while (1) {
642        if (sshinfo->sshfd == 0) {
643            pthread_exit(NULL);
644            break;
645        }
646
647        timeout.tv_sec = (long) 1;               /* one second timeout */
648        timeout.tv_usec = 0;
649        FD_ZERO(&set);
650        FD_SET(sshinfo->sshfd, &set);
651        st = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
652        if (st > 0) {
653            read(sshinfo->sshfd, buf, sizeof buf);
654        }
655    }
656}
Note: See TracBrowser for help on using the repository browser.