PageRenderTime 69ms CodeModel.GetById 40ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/modules/mcmd.c

https://code.google.com/
C | 671 lines | 375 code | 91 blank | 205 comment | 68 complexity | 0574e7ac19a3033091aebcb4ffc40c54 MD5 | raw file
  1/*****************************************************************************\
  2 *  $Id$
  3 *****************************************************************************
  4 *  Copyright (C) 2001-2006 The Regents of the University of California.
  5 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
  6 *  Written by Jim Garlick <garlick@llnl.gov>.
  7 *  UCRL-CODE-2003-005.
  8 *  
  9 *  This file is part of Pdsh, a parallel remote shell program.
 10 *  For details, see <http://www.llnl.gov/linux/pdsh/>.
 11 *  
 12 *  Pdsh is free software; you can redistribute it and/or modify it under
 13 *  the terms of the GNU General Public License as published by the Free
 14 *  Software Foundation; either version 2 of the License, or (at your option)
 15 *  any later version.
 16 *  
 17 *  Pdsh is distributed in the hope that it will be useful, but WITHOUT ANY
 18 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 19 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 20 *  details.
 21 *  
 22 *  You should have received a copy of the GNU General Public License along
 23 *  with Pdsh; if not, write to the Free Software Foundation, Inc.,
 24 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 25\*****************************************************************************/
 26
 27/*
 28 * Started with BSD mcmd.c which is:
 29 * 
 30 * Copyright (c) 1983, 1993, 1994, 2003
 31 *      The Regents of the University of California.  All rights reserved.
 32 *
 33 * Redistribution and use in source and binary forms, with or without
 34 * modification, are permitted provided that the following conditions
 35 * are met:
 36 * 1. Redistributions of source code must retain the above copyright
 37 *    notice, this list of conditions and the following disclaimer.
 38 *
 39 * 2. Redistributions in binary form must reproduce the above copyright
 40 *    notice, this list of conditions and the following disclaimer in the
 41 *    documentation and/or other materials provided with the distribution.
 42 *
 43 * 3. All advertising materials mentioning features or use of this software
 44 *    must display the following acknowledgement:
 45 *      This product includes software developed by the University of
 46 *      California, Berkeley and its contributors.
 47 *
 48 * 4. Neither the name of the University nor the names of its contributors
 49 *    may be used to endorse or promote products derived from this software
 50 *    without specific prior written permission.
 51 *
 52 * 5. This is free software; you can redistribute it and/or modify it
 53 *    under the terms of the GNU General Public License as published
 54 *    by the Free Software Foundation; either version 2 of the
 55 *    License, or (at your option) any later version.
 56 *                              
 57 * 6. This is distributed in the hope that it will be useful, but
 58 *    WITHOUT ANY WARRANTY; without even the implied warranty of
 59 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 60 *    GNU General Public License for more details.
 61 *                                                           
 62 * 7. You should have received a copy of the GNU General Public License;
 63 *    if not, write to the Free Software Foundation, Inc., 59 Temple
 64 *    Place, Suite 330, Boston, MA  02111-1307  USA.
 65 *
 66 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 67 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 68 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 69 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 70 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 71 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 72 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 73 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 74 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 75 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 76 * SUCH DAMAGE.
 77 */
 78
 79#if defined(LIBC_SCCS) && !defined(lint)
 80static char sccsid[] = "@(#)mcmd.c      Based from: 8.3 (Berkeley) 3/26/94";
 81#endif /* LIBC_SCCS and not lint */
 82
 83#if     HAVE_CONFIG_H
 84#include "config.h"
 85#endif
 86
 87#include <sys/param.h>
 88#include <sys/types.h>
 89#include <sys/time.h>
 90#include <sys/socket.h>
 91#include <sys/stat.h>
 92
 93#ifdef HAVE_PTHREAD
 94#include <pthread.h>
 95#endif
 96
 97#include <netinet/in.h>
 98#include <arpa/inet.h>
 99#include <signal.h>
100#if HAVE_FCNTL_H
101#include <fcntl.h>
102#endif
103#include <netdb.h>
104#if HAVE_UNISTD_H
105#include <unistd.h>
106#endif
107#include <pwd.h>
108#include <errno.h>
109#include <ctype.h>
110#include <string.h>
111
112#include <stdio.h>
113#include <string.h>
114#include <stdlib.h>
115
116#include <munge.h>
117
118#include "src/common/macros.h"       /* LINEBUFSIZE && IP_ADDR_LEN */
119#include "src/common/err.h"
120#include "src/common/fd.h"
121#include "src/common/xpoll.h"
122#include "src/pdsh/mod.h"
123
124#define MRSH_PROTOCOL_VERSION    "2.1"
125
126#define MRSH_PORT                21212
127
128#ifndef MAXHOSTNAMELEN
129#define MAXHOSTNAMELEN          64
130#endif
131
132#define MRSH_LOCALHOST_KEY      "LHOST"
133#define MRSH_LOCALHOST_KEYLEN   5
134
135#ifdef HAVE_PTHREAD
136#define SET_PTHREAD()           pthread_sigmask(SIG_BLOCK, &blockme, &oldset)
137#define RESTORE_PTHREAD()       pthread_sigmask(SIG_SETMASK, &oldset, NULL)
138#define EXIT_PTHREAD()          RESTORE_PTHREAD(); \
139                                return -1
140#else
141#define SET_PTHREAD()
142#define RESTORE_PTHREAD()
143#define EXIT_PTHREAD()          return -1
144#endif
145
146#if STATIC_MODULES
147#  define pdsh_module_info mcmd_module_info
148#  define pdsh_module_priority mcmd_module_priority
149#endif    
150
151int pdsh_module_priority = DEFAULT_MODULE_PRIORITY;
152
153static int mcmd_init(opt_t *);
154static int mcmd_signal(int, void *, int);
155static int mcmd(char *, char *, char *, char *, char *, int, int *, void **); 
156
157/* random num for all jobs in this group */
158static unsigned int randy = -1;
159
160/* 
161 * Export pdsh module operations structure
162 */
163struct pdsh_module_operations mcmd_module_ops = {
164    (ModInitF)       NULL, 
165    (ModExitF)       NULL, 
166    (ModReadWcollF)  NULL, 
167    (ModPostOpF)     NULL,
168};
169
170/*
171 *  Export rcmd module operations
172 */
173struct pdsh_rcmd_operations mcmd_rcmd_ops = {
174    (RcmdInitF)    mcmd_init,
175    (RcmdSigF)     mcmd_signal,
176    (RcmdF)        mcmd,
177};
178
179/* 
180 * Export module options
181 */
182struct pdsh_module_option mcmd_module_options[] = 
183{ 
184    PDSH_OPT_TABLE_END
185};
186
187/* 
188 * Mcmd module info 
189 */
190struct pdsh_module pdsh_module_info = {
191    "rcmd",
192    "mrsh",
193    "Al Chu <chu11@llnl.gov>",
194    "mrsh rcmd connect method",
195    DSH | PCP, 
196
197    &mcmd_module_ops,
198    &mcmd_rcmd_ops,
199    &mcmd_module_options[0],
200};
201
202static int
203mcmd_init(opt_t * opt)
204{
205    int rv, rand_fd;
206
207    /*
208     * Drop privileges if running setuid root
209     */
210    if ((geteuid() == 0) && (getuid() != 0))
211        setuid (getuid ());
212
213    /*
214     * Generate a random number to send in our package to the 
215     * server.  We will see it again and compare it when the
216     * server sets up the stderr socket and sends it to us.
217     * We need to loop for the tiny possibility we read 0 :P
218     */
219    if ((rand_fd = open ("/dev/urandom", O_RDONLY | O_NONBLOCK)) < 0 ) {
220        err("%p: mcmd: Open of /dev/urandom failed\n");
221        return -1;
222    }
223
224    do {
225        if ((rv = read (rand_fd, &randy, sizeof(uint32_t))) < 0) {
226            close(rand_fd);
227            err("%p: mcmd: Read of /dev/urandom failed\n");
228            return -1;
229        }
230
231        if (rv < (int) (sizeof(uint32_t))) {
232            close(rand_fd);
233            err("%p: mcmd: Read returned too few bytes\n");
234            return -1;
235        }
236    } while (randy == 0);
237
238    close(rand_fd);
239
240    return 0;
241}
242
243static int
244mcmd_signal(int fd, void *arg, int signum)
245{
246    char c;
247
248    if (fd >= 0) {
249        /* set non-blocking mode for write - just take our best shot */
250        if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
251            err("%p: fcntl: %m\n");
252        c = (char) signum;
253        write(fd, &c, 1);
254    }
255    return 0;
256}
257
258/*
259 * If `host' corresponds to a standard "locahost" target, then
260 *  encode mrsh localhost key and hostname into str, returning
261 *  number of bytes written into str. Otherwise do nothing and return 0.
262 */
263static int
264encode_localhost_string (const char *host, char *str, int maxlen)
265{
266    char hostname[MAXHOSTNAMELEN+1];
267
268    if (strcmp (host, "localhost") && strcmp (host, "127.0.0.1"))
269        return (0);
270
271    if (maxlen < MRSH_LOCALHOST_KEYLEN)
272        return (-1);
273
274    memset (hostname, '\0', MAXHOSTNAMELEN + 1);
275
276    if (gethostname (hostname, MAXHOSTNAMELEN) < 0)
277        errx ("mcmd: gethostname: %m\n");
278
279    strncpy (str, MRSH_LOCALHOST_KEY, MRSH_LOCALHOST_KEYLEN);
280    strncat (str, hostname, maxlen - MRSH_LOCALHOST_KEYLEN - 1);
281
282    return (strlen (str));
283}
284
285/*
286 * Derived from the mcmd() libc call, with modified interface.
287 * This version is MT-safe.  Errors are displayed in pdsh-compat format.
288 * Connection can time out.
289 *      ahost (IN)              target hostname
290 *      addr (IN)               4 byte internet address
291 *      locuser (IN)            local username
292 *      remuser (IN)            remote username
293 *      cmd (IN)                remote command to execute under shell
294 *      rank (IN)               not used 
295 *      fd2p (IN)               if non NULL, return stderr file descriptor here
296 *      int (RETURN)            -1 on error, socket for I/O on success
297 *
298 * Originally by Mike Haskell for mrsh, modified slightly to work with pdsh by:
299 * - making mcmd always thread safe
300 * - using "err" function output errors.
301 * - passing in address as addr intead of calling gethostbyname
302 * - using default mshell port instead of calling getservbyname
303 * 
304 */
305static int 
306mcmd(char *ahost, char *addr, char *locuser, char *remuser, char *cmd, 
307        int rank, int *fd2p, void **argp)
308{
309    struct sockaddr m_socket;
310    struct sockaddr_in *getp;
311    struct sockaddr_in sin, from;
312    struct sockaddr_storage ss;
313    struct in_addr m_in;
314    unsigned int rand, randl;
315    unsigned char *hptr;
316    int s, s2, rv, mcount, lport;
317    char c;
318    char num[6] = {0};
319    char *mptr;
320    char *mbuf;
321    char *tmbuf;
322    char *m;
323    char *mpvers;
324    char num_seq[12] = {0};
325    socklen_t len;
326    sigset_t blockme;
327    sigset_t oldset;
328    char haddrdot[MAXHOSTNAMELEN + MRSH_LOCALHOST_KEYLEN + 1] = {0};
329    munge_ctx_t ctx;
330    struct xpollfd xpfds[2];
331
332    memset (xpfds, 0, sizeof (xpfds));
333    memset (&sin, 0, sizeof (sin));
334
335    sigemptyset(&blockme);
336    sigaddset(&blockme, SIGURG);
337    sigaddset(&blockme, SIGPIPE);
338    SET_PTHREAD();
339
340    /* Convert randy to decimal string, 0 if we dont' want stderr */
341    if (fd2p != NULL)
342        snprintf(num_seq, sizeof(num_seq),"%d",randy);
343    else
344        snprintf(num_seq, sizeof(num_seq),"%d",0);
345
346    /*
347     * Start setup of the stdin/stdout socket...
348     */
349    lport = 0;
350    len = sizeof(struct sockaddr_in);
351
352    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
353        err("%p: %S: mcmd: socket call stdout failed: %m\n", ahost);
354        EXIT_PTHREAD();
355    }
356
357    memset (&ss, '\0', sizeof(ss));
358    ss.ss_family = AF_INET;
359
360    if (bind(s, (struct sockaddr *)&ss, len) < 0) {
361        err("%p: %S: mcmd: bind failed: %m\n", ahost);
362        goto bad;
363    }
364
365    sin.sin_family = AF_INET;
366
367    memcpy(&sin.sin_addr.s_addr, addr, IP_ADDR_LEN); 
368
369    sin.sin_port = htons(MRSH_PORT);
370    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
371        err("%p: %S: mcmd: connect failed: %m\n", ahost);
372        goto bad;
373    }
374
375    lport = 0;
376    s2 = -1;
377    if (fd2p != NULL) {
378        /*
379         * Start the socket setup for the stderr.
380         */
381        struct sockaddr_in sin2;
382
383        if ((s2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
384            err("%p: %S: mcmd: socket call for stderr failed: %m\n", ahost);
385            goto bad;
386        }
387
388        memset (&sin2, 0, sizeof(sin2));
389        sin2.sin_family = AF_INET;
390        sin2.sin_addr.s_addr = htonl(INADDR_ANY);
391        sin2.sin_port = 0;
392        if (bind(s2,(struct sockaddr *)&sin2, sizeof(sin2)) < 0) {
393            err("%p: %S: mcmd: bind failed: %m\n", ahost);
394            close(s2);
395            goto bad;
396        }
397
398        len = sizeof(struct sockaddr);
399
400        /*
401         * Retrieve our port number so we can hand it to the server
402         * for the return (stderr) connection...
403         */
404
405        /* getsockname is thread safe */
406        if (getsockname(s2,&m_socket,&len) < 0) {
407            err("%p: %S: mcmd: getsockname failed: %m\n", ahost);
408            close(s2);
409            goto bad;
410        }
411
412        getp = (struct sockaddr_in *)&m_socket;
413        lport = ntohs(getp->sin_port);
414
415        if (listen(s2, 5) < 0) {
416            err("%p: %S: mcmd: listen() failed: %m\n", ahost);
417            close(s2);
418            goto bad;
419        }
420    }
421
422    /* put port in buffer. will be 0 if user didn't want stderr */
423    snprintf(num,sizeof(num),"%d",lport);
424
425    /* 
426     * Use special keyed string if target is localhost, otherwise,
427     *  encode the IP addr string.
428     */
429    if (!encode_localhost_string (ahost, haddrdot, sizeof (haddrdot))) {
430        /* inet_ntoa is not thread safe, so we use the following, 
431         * which is more or less ripped from glibc
432         */
433        memcpy(&m_in.s_addr, addr, IP_ADDR_LEN);
434        hptr = (unsigned char *)&m_in;
435        sprintf(haddrdot, "%u.%u.%u.%u", hptr[0], hptr[1], hptr[2], hptr[3]);
436    }
437
438    /*
439     * We call munge_encode which will take what we write in and return a
440     * pointer to an munged buffer.  What we get back is a null terminated
441     * string of encrypted characters.
442     * 
443     * The format of the unmunged buffer is as follows (each a string terminated 
444     * with a '\0' (null):
445     *
446     * stderr_port_number & /dev/urandom_client_produce_number are 0
447     * if user did not request stderr socket
448     *                                            SIZE            EXAMPLE
449     *                                            ==========      =============
450     * remote_user_name                           variable        "mhaskell"
451     * '\0'
452     * protocol version                           < 12 bytes      "1.2"
453     * '\0'
454     * dotted_decimal_address_of_this_server      7-15 bytes      "134.9.11.155"
455     * '\0'
456     * stderr_port_number                         4-8 bytes       "50111"
457     * '\0'
458     * /dev/urandom_client_produced_number        1-8 bytes       "1f79ca0e"
459     * '\0'
460     * users_command                              variable        "ls -al"
461     * '\0' '\0'
462     *
463     * (The last extra null is accounted for in the following line's 
464     *  last strlen() call.)
465     *
466     */
467
468    mpvers = MRSH_PROTOCOL_VERSION;
469
470    mcount = ((strlen(remuser)+1) + (strlen(mpvers)+1) + 
471              (strlen(haddrdot)+1) + (strlen(num)+1) + 
472              (strlen(num_seq)+1) + strlen(cmd)+2);
473
474    tmbuf = mbuf = malloc(mcount);
475    if (tmbuf == NULL) {
476        err("%p: %S: mcmd: Error from malloc\n", ahost);
477        close(s2);
478        goto bad;
479    }
480
481    /*
482     * The following memset() call takes the extra trailing null as
483     * part of its count as well.
484     */
485    memset(mbuf,0,mcount);
486
487    mptr = strcpy(mbuf, remuser);
488    mptr += strlen(remuser)+1;
489    mptr = strcpy(mptr, mpvers);
490    mptr += strlen(mpvers)+1;
491    mptr = strcpy(mptr, haddrdot);
492    mptr += strlen(haddrdot)+1;
493    mptr = strcpy(mptr, num);
494    mptr += strlen(num)+1;
495    mptr = strcpy(mptr, num_seq);
496    mptr += strlen(num_seq)+1;
497    mptr = strcpy(mptr, cmd);
498
499    ctx = munge_ctx_create();
500
501    if ((rv = munge_encode(&m,ctx,mbuf,mcount)) != EMUNGE_SUCCESS) {
502        err("%p: %S: mcmd: munge_encode: %s\n", ahost, munge_ctx_strerror(ctx));
503        munge_ctx_destroy(ctx);
504        if (s2 >= 0) 
505            close(s2);
506        free(tmbuf);
507        goto bad;
508    }
509
510    munge_ctx_destroy(ctx);
511
512    mcount = (strlen(m)+1);
513
514    /*
515     * Write stderr port in the clear in case we can't decode for
516     * some reason (i.e. bad credentials).  May be 0 if user 
517     * doesn't want stderr
518     */
519    if (fd2p != NULL) {
520        rv = fd_write_n(s, num, strlen(num)+1);
521        if (rv != (strlen(num) + 1)) {
522            free(m);
523            free(tmbuf);
524            if (errno == EPIPE)
525                err("%p: %S: mcmd: Lost connection (EPIPE): %m", ahost);
526            else
527                err("%p: %S: mcmd: Write of stderr port failed: %m\n", ahost);
528            close(s2);
529            goto bad;
530        }
531    } else {
532        write(s, "", 1);
533        lport = 0;
534    }
535
536    /*
537     * Write the munge_encoded blob to the socket.
538     */
539    rv = fd_write_n(s, m, mcount);
540    if (rv != mcount) {
541        free(m);
542        free(tmbuf);
543        if (errno == EPIPE)
544            err("%p: %S: mcmd: Lost connection: %m\n", ahost);
545        else
546            err("%p: %S: mcmd: Write to socket failed: %m\n", ahost);
547        close(s2);
548        goto bad;
549    }
550
551    free(m);
552    free(tmbuf);
553
554    if (fd2p != NULL) {
555        /*
556         * Wait for stderr connection from daemon.
557         */
558        int s3;
559
560        errno = 0;
561        xpfds[0].fd = s;
562        xpfds[1].fd = s2;
563        xpfds[0].events = xpfds[1].events = XPOLLREAD;
564        if (  ((rv = xpoll(xpfds, 2, -1)) < 0) 
565            || rv != 1 
566            || (xpfds[0].revents > 0)) {
567            if (errno != 0)
568                err("%p: %S: mcmd: xpoll (setting up stderr): %m\n", ahost);
569            else
570                err("%p: %S: mcmd: xpoll: protocol failure in circuit setup\n",
571                     ahost);
572            (void) close(s2);
573            goto bad;
574        }
575
576        errno = 0;
577        len = sizeof(from); /* arg to accept */
578
579        if ((s3 = accept(s2, (struct sockaddr *)&from, &len)) < 0) {
580            err("%p: %S: mcmd: accept (stderr) failed: %m\n", ahost);
581            close(s2);
582            goto bad;
583        }
584
585        if (from.sin_family != AF_INET) {
586            err("%p: %S: mcmd: bad family type: %d\n", ahost, from.sin_family);
587            goto bad2;
588        }
589
590        close(s2);
591
592        /*
593         * The following fixes a race condition between the daemon
594         * and the client.  The daemon is waiting for a null to
595         * proceed.  We do this to make sure that we have our
596         * socket is up prior to the daemon running the command.
597         */
598        if (write(s,"",1) < 0) {
599            err("%p: %S: mcmd: Could not communicate to daemon to proceed: %m\n", ahost);
600            close(s3);
601            goto bad;
602        }
603
604        /*
605         * Read from our stderr.  The server should have placed our
606         * random number we generated onto this socket.
607         */
608        rv = fd_read_n(s3, &rand, sizeof(rand));
609        if (rv != (ssize_t) (sizeof(rand))) {
610            err("%p: %S: mcmd: Bad read of expected verification "
611                    "number off of stderr socket: %m\n", ahost);
612            close(s3);
613            goto bad;
614        }
615
616        randl = ntohl(rand);
617        if (randl != randy) {
618            char tmpbuf[LINEBUFSIZE] = {0};
619            char *tptr = &tmpbuf[0];
620
621            memcpy(tptr,(char *) &rand,sizeof(rand));
622            tptr += sizeof(rand);
623            if (fd_read_line (s3, tptr, LINEBUFSIZE) < 0)
624                err("%p: %S: mcmd: Read error from remote host: %m\n", ahost);
625            else
626                err("%p: %S: mcmd: Error: %s\n", ahost, &tmpbuf[0]);
627            close(s3);
628            goto bad;
629        }
630
631        /*
632         * Set the stderr file descriptor for the user...
633         */
634        *fd2p = s3;
635    }
636
637    if ((rv = read(s, &c, 1)) < 0) {
638        err("%p: %S: mcmd: read: protocol failure: %m\n", ahost);
639        goto bad2;
640    }
641
642    if (rv != 1) {
643        err("%p: %S: mcmd: read: protocol failure: invalid response\n", ahost);
644        goto bad2;
645    }
646
647    if (c != '\0') {
648        /* retrieve error string from remote server */
649        char tmpbuf[LINEBUFSIZE];
650
651        if (fd_read_line (s, &tmpbuf[0], LINEBUFSIZE) < 0)
652            err("%p: %S: mcmd: Error from remote host\n", ahost);
653        else
654            err("%p: %S: mcmd: Error: %s\n", ahost, tmpbuf);
655        goto bad2;
656    }
657    RESTORE_PTHREAD();
658
659    return (s);
660
661bad2:
662    if (lport)
663        close(*fd2p);
664bad:
665    close(s);
666    EXIT_PTHREAD();
667}
668
669/*
670 * vi: tabstop=4 shiftwidth=4 expandtab
671 */