├── debian ├── compat ├── docs ├── mpssh.manpages ├── mpssh.dirs ├── changelog ├── rules ├── control └── copyright ├── Makefile ├── hosts.sample ├── README ├── host.h ├── pslot.h ├── mpssh.h ├── mpssh.1 ├── host.c ├── pslot.c └── mpssh.c /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README 2 | -------------------------------------------------------------------------------- /debian/mpssh.manpages: -------------------------------------------------------------------------------- 1 | mpssh.1 -------------------------------------------------------------------------------- /debian/mpssh.dirs: -------------------------------------------------------------------------------- 1 | usr/bin 2 | usr/share/man/man1 3 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | mpssh (1.4-dev) unstable; urgency=low 2 | 3 | * Initial Release. 4 | 5 | -- Eduardo Ferro Aldama Thu, 12 Jun 2014 17:40:10 +0200 6 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | 7 | %: 8 | dh $@ 9 | 10 | override_dh_auto_install: 11 | $(MAKE) DESTDIR=$$(pwd)/debian/mpssh BIN=$$(pwd)/debian/mpssh/usr/bin install 12 | 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | LD = gcc 3 | SSHPATH = `which ssh` 4 | SCPPATH = `which scp` 5 | CFLAGS = -Wall -DSSHPATH=\"$(SSHPATH)\" -DSCPPATH=\"$(SCPPATH)\" 6 | LDFLAGS = 7 | RM = /bin/rm -f 8 | BIN=/usr/local/bin 9 | 10 | LIBS = 11 | 12 | OBJS = pslot.o host.o mpssh.o 13 | PROG = mpssh 14 | 15 | all: $(PROG) 16 | 17 | $(PROG): $(OBJS) 18 | $(LD) $(LDFLAGS) $(OBJS) $(LIBS) $(FLAGS) -o $(PROG) 19 | 20 | %.o: %.c 21 | $(CC) $(CFLAGS) $(FLAGS) -c $< 22 | 23 | clean: 24 | $(RM) $(PROG) $(OBJS) $(PROG).core 25 | 26 | install: all 27 | strip $(PROG) 28 | install -m 775 -d $(BIN) 29 | install -m 751 $(PROG) $(BIN) 30 | 31 | -------------------------------------------------------------------------------- /hosts.sample: -------------------------------------------------------------------------------- 1 | # 2 | # This is a sample "hosts" file for mpssh. 3 | # Either install similar file as ~/.mpssh/hosts or specify it 4 | # with the -f command line option. 5 | # Lines starting with # are comments and are ignored. 6 | # Lines starting with % are group labels. 7 | # All other lines are parsed as hosts definitions 8 | # You can specify username with prepending it to the hostname separated by the @ char. 9 | # You can spefify alternative port name by appending colon and the port number after the hostname. 10 | # By default port 22 is used, and the username of the current user is used. 11 | 12 | %localhost 13 | root@localhost:22 14 | %routers 15 | mgmt@router1.datacenter1:2222 16 | mgmt@router2.datacenter1:2222 17 | mgmt@router1.datacenter2:2222 18 | mgmt@router2.datacenter2:2222 19 | %app 20 | jboss@app-srv1.datacenter1 21 | jboss@app-srv2.datacenter1 22 | 23 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: mpssh 2 | Section: admin 3 | Priority: extra 4 | Maintainer: Eduardo Ferro Aldama 5 | Build-Depends: debhelper (>= 8.0.0) 6 | Standards-Version: 3.9.4 7 | Homepage: https://github.com/ndenev/mpssh 8 | #Vcs-Git: git://git.debian.org/collab-maint/mpssh.git 9 | #Vcs-Browser: http://git.debian.org/?p=collab-maint/mpssh.git;a=summary 10 | 11 | Package: mpssh 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: Mass Parallel Secure Shell 15 | mpssh is a parallel ssh tool. What it does is connecting to a number of hosts 16 | specified in the hosts file and execute the same command on all of them, 17 | showing a nicely formatted output with each line prepended with the hostname 18 | that produced the line. It is also possible to specify a script on the local 19 | filesystem that will be first scp copied to the remote host and then executed. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | MPSSH - Mass Parallel Secure Shell 3 | (c) 2005-2015 Nikolay Denev 4 | 5 | mpssh is a parallel ssh tool. What it does is connecting to a number of hosts 6 | specified in the hosts file and execute the same command on all of them, 7 | showing a nicely formatted output with each line prepended with the hostname 8 | that produced the line. It is also possible to specify a script on the local filesystem 9 | that will be first scp copied to the remote host and then executed. 10 | 11 | The hosts file allows to specify different usernames and ports for each host, and also 12 | to group hosts under different "labels" and then execute mpssh against certain label. 13 | 14 | The ssh fork rate and parallelism can be controlled to keep the load on the machine 15 | that executes mpssh under control. 16 | 17 | mpssh uses the ssh binary from the openssh package, and executes it directly. 18 | There are no other external dependancies. 19 | mpssh depends on preexisting passwordless authentication method such as 20 | pubkey or kerberos to work. 21 | 22 | -------------------------------------------------------------------------------- /host.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2005-2015 Nikolay Denev 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #define MAXNAME 255 /* max hostname len */ 29 | #define NON_DEFINED_PORT 0 30 | #define DEFAULT_PORT 22 31 | 32 | /* host list structs */ 33 | struct 34 | host { 35 | char *user; 36 | char *host; 37 | uint16_t port; 38 | struct host *next; 39 | }; 40 | -------------------------------------------------------------------------------- /pslot.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2005-2015 Nikolay Denev 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #define LINEBUF 1024 /* max output line len */ 29 | 30 | 31 | /* stdout/err structure for struct procslot */ 32 | struct 33 | stdio_pipe { 34 | int out[2]; 35 | int err[2]; 36 | }; 37 | 38 | /* stdout/stderr output filenames and filehandles */ 39 | struct 40 | out_files { 41 | char *name; 42 | FILE *fh; 43 | }; 44 | 45 | /* process slot structure */ 46 | struct 47 | procslot { 48 | int pid; 49 | struct host *hst; 50 | char out_buf[LINEBUF]; 51 | char err_buf[LINEBUF]; 52 | struct out_files outf[2]; 53 | int used; 54 | int ret; 55 | struct stdio_pipe io; 56 | struct procslot *prev; 57 | struct procslot *next; 58 | }; 59 | 60 | /* global process slot var */ 61 | extern struct procslot *ps; 62 | 63 | /* other global vars */ 64 | extern int pslots; 65 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: mpssh 3 | Source: https://github.com/ndenev/mpssh 4 | 5 | Files: * 6 | Copyright: 2005-2015 Nikolay Denev 7 | 8 | License: BSD-3-clause 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions 13 | are met: 14 | 1. Redistributions of source code must retain the above copyright 15 | notice, this list of conditions and the following disclaimer. 16 | 2. Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 3. The name of the author may not be used to endorse or promote products 20 | derived from this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 23 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 27 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 31 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.License: BSD-3-clause 32 | 33 | Files: debian/* 34 | Copyright: 2014 Eduardo Ferro Aldama 35 | License: BSD-3-clause 36 | All rights reserved. 37 | 38 | Redistribution and use in source and binary forms, with or without 39 | modification, are permitted provided that the following conditions 40 | are met: 41 | 1. Redistributions of source code must retain the above copyright 42 | notice, this list of conditions and the following disclaimer. 43 | 2. Redistributions in binary form must reproduce the above copyright 44 | notice, this list of conditions and the following disclaimer in the 45 | documentation and/or other materials provided with the distribution. 46 | 3. The name of the author may not be used to endorse or promote products 47 | derived from this software without specific prior written permission. 48 | 49 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 50 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 51 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 52 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 53 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 54 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 55 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 56 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 57 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 58 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59 | -------------------------------------------------------------------------------- /mpssh.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2005-2015 Nikolay Denev 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #ifndef SSHPATH 46 | #define SSHPATH "/usr/bin/ssh" 47 | #endif 48 | 49 | #ifndef SCPPATH 50 | #define SCPPATH "/usr/bin/scp" 51 | #endif 52 | 53 | /* Default hosts filename, relative to users homedir */ 54 | #define HSTLIST ".mpssh/hosts" 55 | #define MAXCMD 1024 /* max command len */ 56 | #define MAXUSER 30 /* max username len */ 57 | #define MAXCHLD 1024 /* max child procs */ 58 | #define DEFCHLD 100 /* default child procs */ 59 | #define OUT 1 60 | #define ERR 2 61 | #define MAXFD 1024 /* max filedesc number */ 62 | 63 | /* block/unblck SIGCHLD macros. */ 64 | #define BLOCK_SIGCHLD \ 65 | sigemptyset(&sigmask); \ 66 | sigaddset(&sigmask, SIGCHLD); \ 67 | sigprocmask(SIG_BLOCK, &sigmask, &osigmask) 68 | 69 | #define UNBLOCK_SIGCHLD \ 70 | sigprocmask(SIG_SETMASK, &osigmask, NULL) 71 | 72 | #define perr(...) fprintf(stderr, __VA_ARGS__) 73 | 74 | /* some global vars */ 75 | extern int maxchld; 76 | extern const char Rev[]; 77 | extern int user_len_max; 78 | extern int host_len_max; 79 | extern int children; 80 | extern int verbose; 81 | extern int done; 82 | extern int print_exit; 83 | extern int hostcount; 84 | extern int blind; 85 | extern char *outdir; 86 | extern char *user; 87 | extern char *label; 88 | extern int no_err; 89 | extern int no_out; 90 | -------------------------------------------------------------------------------- /mpssh.1: -------------------------------------------------------------------------------- 1 | .Dd 08/03/2013 2 | .Dt mpssh 3 | .Sh NAME 4 | .Nm mpssh 5 | .Sh SYNOPSIS 6 | .Nm 7 | 8 | .Op Fl besvV 9 | .Op Fl o Ar directory 10 | .Op Fl u Ar username 11 | .Op Fl f Ar hosts 12 | .Op Fl p Ar procs 13 | .Ar 14 | .Sh DESCRIPTION 15 | 16 | -b, --blind enable blind mode (no remote output) 17 | -d, --delay delay between each ssh fork (default 10 msec) 18 | -e, --exit print the remote command return code 19 | -f, --file=FILE name of the file with the list of hosts 20 | -h, --help this screen 21 | -l, --label=LABEL connect only to hosts under label LABEL 22 | -o, --outdir=DIR save the remote output in this directory 23 | -p, --procs=NPROC number of parallel ssh processes (default 100) 24 | -s, --nokeychk disable ssh strict host key check 25 | -t, --conntmout ssh connect timeout (default 30 sec) 26 | -u, --user=USER ssh login as this username 27 | -v, --verbose be more verbose (i.e. show usernames used) 28 | -V, --version show program version 29 | 30 | The 31 | .Nm 32 | utility executes multiple parallel ssh binary instances in order to connect to a list of hosts (specified in the hosts file) and execute the given on each of them. 33 | 34 | A list of flags and arguments with description: 35 | .Bl -tag -width -indent 36 | .It Fl b 37 | This flag enables "blind" mode, in which no output from the remote hosts is output to the screen. This mode is normally used with the 38 | .Fl o 39 | flag, so the output is saved to disk. 40 | .It Fl d 41 | This flag sets some delay in msecs between each fork()/exec() of the ssh process. 42 | .It Fl e 43 | With this flag 44 | .Nm 45 | prints the return codes of the remotely executed commands. 46 | .It Fl l LABEL 47 | Only connect to the hosts under the given label. 48 | .It Fl s 49 | This flag disables the ssh(1)'s strict host key checking. For more info see the ssh(1) manual page. 50 | .It Fl v 51 | This flag makes the output more verbose. 52 | .It Fl o Ar directory 53 | This option creates files in the specified directory named after each host name listed in the "hosts" file and saves the output received from the remotely executed command there. If the directory does not exists and attempt is made to be created. 54 | .It Fl u Ar username 55 | This forces ssh to use the supplied username instead of the username of the current user. 56 | .It Fl f Ar hosts 57 | A file containing a list of hosts to whom we are going to connect. One host per line. Lines starting with # are skipped. If not specified $HOME/.mpssh/hosts will be used. 58 | .It Fl p Ar procs 59 | Spawn up-to "procs" number of ssh processes in parallel. 60 | .El 61 | .Pp 62 | .\" .Sh ENVIRONMENT \" May not be needed 63 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 64 | .\" .It Ev ENV_VAR_1 65 | .\" Description of ENV_VAR_1 66 | .\" .It Ev ENV_VAR_2 67 | .\" Description of ENV_VAR_2 68 | .\" .El 69 | .Sh FILES 70 | .It Pa /usr/local/bin/mpssh 71 | /usr/local/bin/mpssh The 72 | .Nm 73 | binary 74 | .El 75 | .\" .Sh DIAGNOSTICS \" May not be needed 76 | .\" .Bl -diag 77 | .\" .It Diagnostic Tag 78 | .\" Diagnostic informtion here. 79 | .\" .It Diagnostic Tag 80 | .\" Diagnostic informtion here. 81 | .\" .El 82 | .Sh SEE ALSO 83 | .\" List links in ascending order by section, alphabetically within a section. 84 | .\" Please do not reference files that do not exist without filing a bug report 85 | .Xr ssh 1 , 86 | .Xr ssh-keygen 1 , 87 | .Xr ssh-agent 1 88 | .\" .Sh BUGS \" Document known, unremedied bugs 89 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner 90 | -------------------------------------------------------------------------------- /host.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2005-2015 Nikolay Denev 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mpssh.h" 29 | #include "host.h" 30 | 31 | /* 32 | * routine for allocating a new host element in the 33 | * linked list containing the hosts read from the file. 34 | * it is used internally by host_add(). 35 | */ 36 | static struct host* 37 | host_new(char *user, char *host, uint16_t port) 38 | { 39 | static struct host *hst; 40 | 41 | if (!(hst = calloc(1, sizeof(struct host)))) goto fail; 42 | 43 | if (user) { 44 | hst->user = calloc(1, strlen(user)+1); 45 | if (hst->user == NULL) 46 | goto fail; 47 | strncpy(hst->user, user, strlen(user)); 48 | } else { 49 | hst->user = NULL; 50 | } 51 | 52 | if (host) { 53 | hst->host = calloc(1, strlen(host)+1); 54 | if (hst->host == NULL) 55 | goto fail; 56 | strncpy(hst->host, host, strlen(host)); 57 | } else { 58 | hst->host = NULL; 59 | } 60 | 61 | hst->port = port; 62 | 63 | hst->next = NULL; 64 | 65 | return(hst); 66 | fail: 67 | perr("Can't alloc mem in %s\n", __func__); 68 | 69 | if (hst != NULL) { 70 | if (hst->user != NULL) 71 | free(hst->user); 72 | if (hst->host != NULL) 73 | free(hst->host); 74 | free(hst); 75 | } 76 | 77 | exit(1); 78 | } 79 | 80 | /* 81 | * routine for adding elements in the existing hostlist 82 | * linked list. 83 | */ 84 | static struct host* 85 | host_add(struct host *hst, char *user, char *host, uint16_t port) 86 | { 87 | if (hst == NULL) 88 | return(host_new(user, host, port)); 89 | 90 | hst->next = host_add(hst->next, user, host, port); 91 | return(hst->next); 92 | } 93 | 94 | static FILE* 95 | host_openfile(char *fname) 96 | { 97 | int fnamelen; 98 | char *home; 99 | FILE *hstlist; 100 | 101 | if (fname == NULL) { 102 | home = getenv("HOME"); 103 | if (!home) { 104 | perr("Can't get HOME env var in %s\n", __func__); 105 | return NULL; 106 | } 107 | 108 | fnamelen = strlen(home) + strlen("/"HSTLIST) + 1; 109 | 110 | fname = calloc(1, fnamelen); 111 | 112 | if (!fname) { 113 | perr("Can't alloc mem in %s\n", __func__); 114 | return NULL; 115 | } 116 | 117 | sprintf(fname, "%s/"HSTLIST, home); 118 | 119 | } else if (strcmp(fname, "-") == 0) { 120 | 121 | fname = calloc(1, strlen("stdin")+1); 122 | 123 | if (!fname) { 124 | perr("Can't alloc mem in %s\n", __func__); 125 | return NULL; 126 | } 127 | 128 | sprintf(fname, "stdin"); 129 | 130 | hstlist = stdin; 131 | 132 | if (verbose) 133 | fprintf(stdout, "Reading hosts from : stdin\n"); 134 | 135 | goto out; 136 | } 137 | 138 | if (verbose) 139 | fprintf(stdout, "Reading hosts from : %s\n", fname); 140 | 141 | hstlist = fopen(fname, "r"); 142 | 143 | if (!hstlist) 144 | perr("Can't open file: %s (%s) in %s\n", 145 | fname, strerror(errno), __func__); 146 | out: 147 | return hstlist; 148 | } 149 | 150 | /* 151 | * routine that reads the host from a file and puts them 152 | * in the hostlist linked list using the above two routines 153 | */ 154 | struct host* 155 | host_readlist(char *fname) 156 | { 157 | FILE *hstlist; 158 | struct host *hst; 159 | struct host *hst_head; 160 | char line[MAXNAME*3]; 161 | int i; 162 | int linelen; 163 | u_long port; 164 | char *login = NULL; 165 | char *hostname = NULL; 166 | char *llabel = NULL; 167 | 168 | hstlist = host_openfile(fname); 169 | 170 | if (hstlist == NULL) 171 | exit(1); 172 | 173 | hst_head = hst = host_add(NULL, NULL, NULL, 0); 174 | 175 | if (hst_head == NULL) { 176 | perr("Unable to add host structure head in %s\n", __func__); 177 | exit(1); 178 | } 179 | 180 | while (fgets(line, sizeof(line), hstlist)) { 181 | 182 | if (sscanf(line, "%[A-Za-z0-9-.@:%]", line) != 1) 183 | continue; 184 | 185 | linelen = strlen(line); 186 | 187 | /* label support */ 188 | if (line[0] == '%') { 189 | if (llabel) 190 | free(llabel); 191 | llabel = calloc(1, linelen + 1); 192 | if (llabel == NULL) { 193 | perr("Can't alloc mem in %s\n", __func__); 194 | exit(1); 195 | } 196 | strncpy(llabel, &line[1], linelen); 197 | continue; 198 | } 199 | 200 | hostname = line; 201 | login = NULL; 202 | port = NON_DEFINED_PORT; 203 | 204 | /* XXX: This is ugly */ 205 | for (i=0; i < linelen; i++) { 206 | switch (line[i]) { 207 | case '@': 208 | if (login) 209 | break; 210 | line[i] = '\0'; 211 | if (strlen(line)) 212 | login = line; 213 | else 214 | break; 215 | hostname = &line[i+1]; 216 | break; 217 | case ':': 218 | line[i] = '\0'; 219 | port = strtol(&line[i+1], 220 | (char **)NULL, 10); 221 | break; 222 | default: 223 | break; 224 | } 225 | } 226 | 227 | if ((hostname == NULL) || (strlen(hostname) == 0)) 228 | continue; 229 | 230 | errno = 0; 231 | 232 | if (!login) 233 | login = user; 234 | 235 | /* check if labels match */ 236 | if (label && llabel) { 237 | if (strcmp(llabel, label)) 238 | continue; 239 | } 240 | 241 | /* add the host record */ 242 | hst = host_add(hst, login, hostname, (uint16_t)port); 243 | 244 | if (hst == NULL) { 245 | perr("Unable to add member to " 246 | "the host struct in %s\n", __func__); 247 | exit(1); 248 | } 249 | 250 | /* keep track of the longest username */ 251 | if (login && strlen(login) > user_len_max) 252 | user_len_max = strlen(login); 253 | 254 | /* keep track of the longest hostname */ 255 | if (strlen(hostname) > host_len_max) 256 | host_len_max = strlen(hostname); 257 | 258 | hostcount++; 259 | } 260 | 261 | if (llabel) 262 | free(llabel); 263 | 264 | fclose(hstlist); 265 | 266 | if (maxchld > hostcount) 267 | maxchld = hostcount; 268 | 269 | hst = hst_head->next; 270 | 271 | free(hst_head); 272 | 273 | return(hst); 274 | } 275 | 276 | void 277 | host_free(struct host *hst) 278 | { 279 | struct host *next = NULL; 280 | 281 | if (hst == NULL) 282 | return; 283 | 284 | next = hst; 285 | 286 | while (next != NULL) { 287 | hst = hst->next; 288 | 289 | if (next->host != NULL) 290 | free(next->host); 291 | if (next->user != NULL) 292 | free(next->user); 293 | 294 | free(next); 295 | next = hst; 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /pslot.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2005-2015 Nikolay Denev 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mpssh.h" 29 | #include "pslot.h" 30 | #include "host.h" 31 | 32 | /* 33 | * process slot initialization routine 34 | */ 35 | static struct procslot* 36 | pslot_new(int pid, struct host *hst) 37 | { 38 | struct procslot *pslot_tmp; 39 | 40 | pslot_tmp = calloc(1, sizeof(struct procslot)); 41 | if (pslot_tmp == NULL) { 42 | perr("%s\n", strerror(errno)); 43 | exit(1); 44 | } 45 | pslot_tmp->pid = pid; 46 | pslot_tmp->hst = hst; 47 | pslot_tmp->outf[0].name = NULL; 48 | pslot_tmp->outf[0].fh = NULL; 49 | pslot_tmp->outf[1].name = NULL; 50 | pslot_tmp->outf[1].fh = NULL; 51 | pslot_tmp->used = 0; 52 | pipe(pslot_tmp->io.out); 53 | pipe(pslot_tmp->io.err); 54 | fcntl(pslot_tmp->io.out[0], F_SETFL, O_NONBLOCK); 55 | fcntl(pslot_tmp->io.err[0], F_SETFL, O_NONBLOCK); 56 | return(pslot_tmp); 57 | } 58 | 59 | 60 | /* 61 | * routine for adding new process slot in the 62 | * circular doubly linked list of proccess slots 63 | */ 64 | struct procslot* 65 | pslot_add(struct procslot *pslot, int pid, struct host *hst) 66 | { 67 | struct procslot *pslot_tmp; 68 | 69 | if (!pslot) { 70 | /* first entry special case */ 71 | pslot = pslot_new(pid, hst); 72 | pslot->prev = pslot; 73 | pslot->next = pslot; 74 | pslot_tmp = pslot; 75 | } else { 76 | pslot_tmp = pslot_new(pid, hst); 77 | pslot_tmp->next = pslot->next; 78 | pslot_tmp->prev = pslot; 79 | pslot->next->prev = pslot_tmp; 80 | pslot->next = pslot_tmp; 81 | } 82 | pslots++; 83 | return(pslot_tmp); 84 | } 85 | 86 | /* 87 | * routine for deleting process slot from the ring list 88 | * it adjusts the links of the neighbor pslots and free()'s 89 | * the slot, closing it's piped descriptors 90 | */ 91 | struct procslot* 92 | pslot_del(struct procslot *pslot) 93 | { 94 | struct procslot *pslot_todel; 95 | int is_last = 0; 96 | 97 | if (!pslot) return(NULL); 98 | 99 | if (pslot == pslot->next) 100 | is_last = 1; 101 | 102 | /* these shouldn't do anything if we are the last element */ 103 | pslot_todel = pslot; 104 | pslot->prev->next = pslot_todel->next; 105 | pslot->next->prev = pslot_todel->prev; 106 | pslot = pslot_todel->next; 107 | close(pslot_todel->io.out[0]); 108 | close(pslot_todel->io.err[0]); 109 | 110 | /* 111 | * close the stdout and stderr filehandles, 112 | * unlink the output file if we have not written anything to it, 113 | * and finally free the memory containing the filename 114 | */ 115 | if (pslot_todel->outf[0].fh) { 116 | if (ftell(pslot_todel->outf[0].fh) == 0) 117 | unlink(pslot_todel->outf[0].name); 118 | fclose(pslot_todel->outf[0].fh); 119 | free(pslot_todel->outf[0].name); 120 | } 121 | if (pslot_todel->outf[1].fh) { 122 | if (ftell(pslot_todel->outf[1].fh) == 0) 123 | unlink(pslot_todel->outf[1].name); 124 | fclose(pslot_todel->outf[1].fh); 125 | free(pslot_todel->outf[1].name); 126 | } 127 | 128 | free(pslot_todel); 129 | 130 | if (is_last) { 131 | pslots++; 132 | return(NULL); 133 | } 134 | 135 | pslots--; 136 | return(pslot); 137 | } 138 | 139 | /* 140 | * routine used by the child reaper routine installed as 141 | * signal handler for SIGCHLD. 142 | * it iterates thru the process slots and finds the slot 143 | * with the pid that we have supplied as argument. 144 | */ 145 | struct procslot* 146 | pslot_bypid(struct procslot *pslot, int pid) 147 | { 148 | int i; 149 | 150 | for (i=0; i <= children; i++) { 151 | if (pslot->pid == pid) 152 | return(pslot); 153 | pslot = pslot->next; 154 | } 155 | return(NULL); 156 | } 157 | 158 | int 159 | pslot_readbuf(struct procslot *pslot, int outfd) 160 | { 161 | int i; 162 | int fd; 163 | char buf; 164 | char *bufp; 165 | 166 | switch (outfd) { 167 | case OUT: 168 | fd = pslot->io.out[0]; 169 | bufp = pslot->out_buf; 170 | break; 171 | case ERR: 172 | fd = pslot->io.err[0]; 173 | bufp = pslot->err_buf; 174 | break; 175 | default: 176 | return 0; 177 | } 178 | 179 | for (;;) { 180 | i = read(fd, &buf, sizeof(buf)); 181 | if (i == 0) return 0; 182 | if (i < 0) { 183 | if (errno == EINTR) continue; 184 | return 0; 185 | } 186 | if (buf == '\n') return 1; 187 | strncat(bufp, &buf, 1); 188 | if (strlen(bufp) >= (LINEBUF-1)) return 1; 189 | } 190 | 191 | } 192 | 193 | void 194 | pslot_printbuf(struct procslot *pslot, int outfd) 195 | { 196 | char *bufp; 197 | FILE *stream; 198 | char progress[9]; 199 | char **stream_pfx; 200 | char *pfx_out[] = { "OUT:", "->", "\033[1;32m->\033[0;39m", NULL }; 201 | char *pfx_err[] = { "ERR:", "=>", "\033[1;31m=>\033[0;39m", NULL }; 202 | char *pfx_ret[] = { "=:", "\033[1;32m=:\033[0;39m", "\033[1;31m=:\033[0;39m", NULL }; 203 | char *pfx_crt[] = { "!!!", "\033[1;33m!!!\033[0;39m", NULL }; 204 | 205 | switch (outfd) { 206 | case OUT: 207 | if (no_out) 208 | return; 209 | bufp = pslot->out_buf; 210 | stream_pfx = pfx_out; 211 | stream = stdout; 212 | break; 213 | case ERR: 214 | if (no_err) 215 | return; 216 | bufp = pslot->err_buf; 217 | stream_pfx = pfx_err; 218 | stream = stderr; 219 | break; 220 | default: 221 | return; 222 | } 223 | 224 | /* 225 | if (verbose) { 226 | //snprintf(progress, sizeof(progress), "[%d]", 100 / ( hostcount - done ) ); 227 | progress[0] = '\0'; 228 | } else { 229 | progress[0] = '\0'; 230 | } 231 | */ 232 | progress[0] = '\0'; 233 | 234 | if (strlen(bufp)) { 235 | if (outdir) { 236 | /* print to file */ 237 | fprintf(pslot->outf[outfd - 1].fh, "%s\n", bufp); 238 | fflush(pslot->outf[outfd - 1].fh); 239 | pslot->used++; 240 | } 241 | if (!blind) { 242 | /* print to console */ 243 | if (verbose) { 244 | fprintf(stream, "%*s@%*s %s%s %s\n", 245 | user_len_max, pslot->hst->user, 246 | host_len_max, pslot->hst->host, 247 | progress, 248 | stream_pfx[isatty(fileno(stream))+1], 249 | bufp); 250 | } else { 251 | fprintf(stream, "%*s %s %s\n", 252 | host_len_max, pslot->hst->host, 253 | stream_pfx[isatty(fileno(stream))+1], 254 | bufp); 255 | } 256 | fflush(stream); 257 | pslot->used++; 258 | } 259 | memset(bufp, 0, LINEBUF); 260 | /* 261 | * the child is dead and we are going to print exit code if reqested 262 | * so, make sure that we print it only when we are called for OUT fd, 263 | * because we want it printed only once 264 | */ 265 | } else if (!pslot->pid && (outfd == OUT)) { 266 | if (pslot->ret == 255) { 267 | if (verbose) { 268 | printf("%*s@%*s %s ssh failure\n", 269 | user_len_max, pslot->hst->user, 270 | host_len_max, pslot->hst->host, 271 | pfx_crt[isatty(fileno(stdout))]); 272 | } else { 273 | printf("%*s %s ssh failure\n", 274 | host_len_max, pslot->hst->host, 275 | pfx_crt[isatty(fileno(stdout))]); 276 | } 277 | fflush(stdout); 278 | return; 279 | } 280 | if (print_exit) { 281 | /* 282 | * print exit code prefix "=:", bw if we are not on a tty, 283 | * green if return code is zero and red if differs from zero 284 | * pfx_ret[isatty(fileno(stdout))?(pslot->ret?2:1):0] 285 | */ 286 | if (verbose) { 287 | printf("%*s@%*s %s%s %d\n", 288 | user_len_max, pslot->hst->user, 289 | host_len_max, pslot->hst->host, 290 | progress, 291 | pfx_ret[isatty(fileno(stdout))?(pslot->ret?2:1):0], 292 | pslot->ret); 293 | } else { 294 | printf("%*s %s %d\n", 295 | host_len_max, pslot->hst->host, 296 | pfx_ret[isatty(fileno(stdout))?(pslot->ret?2:1):0], 297 | pslot->ret); 298 | } 299 | fflush(stdout); 300 | } else if (!pslot->used && !blind && verbose) { 301 | printf("%*s@%*s %s\n", 302 | user_len_max, pslot->hst->user, 303 | host_len_max, pslot->hst->host, 304 | progress); 305 | } 306 | } 307 | 308 | } 309 | -------------------------------------------------------------------------------- /mpssh.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2005-2015 Nikolay Denev 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mpssh.h" 29 | #include "host.h" 30 | #include "pslot.h" 31 | 32 | const char Ver[] = "1.4-dev"; 33 | 34 | /* global vars */ 35 | struct procslot *ps = NULL; 36 | 37 | char *cmd = NULL; 38 | char *user = NULL; 39 | char *fname = NULL; 40 | char *outdir = NULL; 41 | char *label = NULL; 42 | char *script = NULL; 43 | char *base_script = NULL; 44 | char *ident_file = NULL; 45 | 46 | int children = 0; 47 | int maxchld = 0; 48 | int blind = 0; 49 | int done = 0; 50 | int delay = 10; 51 | int hostcount = 0; 52 | int pslots = 0; 53 | int user_len_max = 0; 54 | int host_len_max = 0; 55 | int print_exit = 0; 56 | int local_command = 0; 57 | int ssh_hkey_check = 1; 58 | int ssh_quiet = 0; 59 | int ssh_conn_tmout = 30; 60 | int verbose = 0; 61 | int no_err = 0; 62 | int no_out = 0; 63 | 64 | sigset_t sigmask; 65 | sigset_t osigmask; 66 | 67 | /* function declarations */ 68 | struct host *host_readlist(char *); 69 | void host_free(struct host *); 70 | struct procslot *pslot_add(struct procslot *, int, struct host *); 71 | struct procslot *pslot_del(struct procslot *); 72 | struct procslot *pslot_bypid(struct procslot *, int); 73 | void pslot_printbuf(struct procslot *, int); 74 | int pslot_readbuf(struct procslot *, int); 75 | 76 | /* 77 | * child reaping routine. it is installed as signal 78 | * hanler for the SIG_CHLD signal. 79 | */ 80 | void 81 | reap_child() 82 | { 83 | int pid; 84 | int ret; 85 | 86 | while ((pid = waitpid(-1, &ret, WNOHANG)) > 0) { 87 | done++; 88 | ps = pslot_bypid(ps, pid); 89 | ps->pid = 0; 90 | 91 | if (WIFEXITED(ret)) 92 | ps->ret = WEXITSTATUS(ret); 93 | else 94 | ps->ret = 255; 95 | 96 | while (pslot_readbuf(ps, OUT)) 97 | pslot_printbuf(ps, OUT); 98 | while (pslot_readbuf(ps, ERR)) 99 | pslot_printbuf(ps, ERR); 100 | /* 101 | * make sure that we print some output in verbose mode 102 | * even if there is no data in the buffer 103 | */ 104 | pslot_printbuf(ps, OUT); 105 | ps = pslot_del(ps); 106 | /* decrement this last, its used in pslot_bypid */ 107 | children--; 108 | } 109 | return; 110 | } 111 | 112 | void 113 | child() 114 | { 115 | char *ssh_argv[19]; 116 | int sap; 117 | 118 | char *lcmd; 119 | int len_u; 120 | char *user_arg; 121 | /* enough for -p65535\0 */ 122 | char port_arg[8]; 123 | char tmo_arg[32]; 124 | 125 | ps->pid = 0; 126 | sap = 0; 127 | 128 | /* close stdin of the child, so it won't accept input */ 129 | close(0); 130 | 131 | /* close the parent end of the pipes */ 132 | close(ps->io.out[0]); 133 | close(ps->io.err[0]); 134 | 135 | if (dup2(ps->io.out[1], 1) == -1) 136 | perr("stdout dup fail %s\n", 137 | strerror(errno)); 138 | 139 | if (dup2(ps->io.err[1], 2) == -1) 140 | perr("stderr dup fail %s\n", 141 | strerror(errno)); 142 | #ifdef TESTING 143 | ssh_argv[sap++] = "/bin/echo"; 144 | #endif 145 | 146 | ssh_argv[sap++] = SSHPATH; 147 | 148 | ssh_argv[sap++] = "-oNumberOfPasswordPrompts=0"; 149 | 150 | if (ssh_quiet) 151 | ssh_argv[sap++] = "-q"; 152 | 153 | /* space for -l and \0 */ 154 | len_u = strlen(ps->hst->user) + 3; 155 | user_arg = calloc(1, len_u); 156 | if (user_arg == NULL) { 157 | exit(1); 158 | } 159 | snprintf(user_arg, len_u, "-l%s", ps->hst->user); 160 | ssh_argv[sap++] = user_arg; 161 | 162 | if (ps->hst->port != NON_DEFINED_PORT) { 163 | snprintf(port_arg, sizeof(port_arg), "-p%d", ps->hst->port); 164 | ssh_argv[sap++] = port_arg; 165 | } 166 | 167 | if (ssh_hkey_check) 168 | ssh_argv[sap++] = "-oStrictHostKeyChecking=yes"; 169 | else 170 | ssh_argv[sap++] = "-oStrictHostKeyChecking=no"; 171 | 172 | snprintf(tmo_arg,sizeof(tmo_arg), "-oConnectTimeout=%d", 173 | ssh_conn_tmout); 174 | ssh_argv[sap++] = tmo_arg; 175 | 176 | 177 | if (local_command) { 178 | snprintf(port_arg, sizeof(port_arg), "-P%d", (ps->hst->port 179 | != NON_DEFINED_PORT ? ps->hst->port : DEFAULT_PORT)); 180 | ssh_argv[sap++] = "-oPermitLocalCommand=yes"; 181 | lcmd = calloc(1, 2048); 182 | snprintf(lcmd, 2048, "-oLocalCommand=%s %s -p %s %s@%s:%s", 183 | SCPPATH, 184 | port_arg, 185 | script, 186 | ps->hst->user, 187 | ps->hst->host, 188 | base_script); 189 | ssh_argv[sap++] = lcmd; 190 | } 191 | 192 | if (ident_file) { 193 | ssh_argv[sap++] = "-i"; 194 | ssh_argv[sap++] = ident_file; 195 | } 196 | 197 | ssh_argv[sap++] = ps->hst->host; 198 | 199 | if (local_command) { 200 | char *remexec; 201 | remexec = calloc(1, strlen(base_script)+3); 202 | snprintf(remexec, strlen(base_script)+3, "./%s", base_script); 203 | ssh_argv[sap++] = remexec; 204 | } else { 205 | ssh_argv[sap++] = cmd; 206 | } 207 | 208 | ssh_argv[sap++] = NULL; 209 | 210 | #ifdef TESTING 211 | execv("/bin/echo", ssh_argv); 212 | #else 213 | execv(SSHPATH, ssh_argv); 214 | #endif 215 | 216 | perr("failed to exec the ssh binary"); 217 | exit(1); 218 | } 219 | 220 | /* 221 | * print program version and exit 222 | */ 223 | void 224 | show_ver() 225 | { 226 | printf("mpssh-%s\n", Ver); 227 | exit(0); 228 | } 229 | 230 | /* 231 | * routine displaing the usage, and various error messages 232 | * supplied from the main() routine. 233 | */ 234 | void 235 | usage(char *msg) 236 | { 237 | if (!msg) { 238 | printf("\n Usage: mpssh [-u username] [-p numprocs] [-f hostlist]\n" 239 | " [-e] [-b] [-o /some/dir] [-s] [-v] \n\n" 240 | " -b, --blind enable blind mode (no remote output)\n" 241 | " -d, --delay delay between each ssh fork (default %d msec)\n" 242 | " -e, --exit print the remote command return code\n" 243 | " -E, --no-err suppress stderr output\n" 244 | " -f, --file=FILE file with the list of hosts or - for stdin\n" 245 | " -h, --help this screen\n" 246 | " -l, --label=LABEL connect only to hosts under label LABEL\n" 247 | " -i, --identity=FILE use the private key in FILE to connect to hosts\n" 248 | " -o, --outdir=DIR save the remote output in this directory\n" 249 | " -O, --no-out suppress stdout output\n" 250 | " -p, --procs=NPROC number of parallel ssh processes (default %d)\n" 251 | " -q, --quiet run ssh with -q\n" 252 | " -r, --script copy local script to remote host and execute it\n" 253 | " -s, --nokeychk disable ssh strict host key check\n" 254 | " -t, --conntmout ssh connect timeout (default %d sec)\n" 255 | " -u, --user=USER ssh login as this username\n" 256 | " -v, --verbose be more verbose (i.e. show usernames used)\n" 257 | " -V, --version show program version\n" 258 | "\n", delay, DEFCHLD, ssh_conn_tmout); 259 | } else { 260 | printf("\n *** %s\n\n", msg); 261 | } 262 | 263 | exit(0); 264 | } 265 | 266 | void 267 | parse_opts(int *argc, char ***argv) 268 | { 269 | int opt; 270 | struct stat scstat; 271 | 272 | static struct option longopts[] = { 273 | { "blind", no_argument, NULL, 'b' }, 274 | { "exit", no_argument, NULL, 'e' }, 275 | { "file", required_argument, NULL, 'f' }, 276 | { "help", no_argument, NULL, 'h' }, 277 | { "identity", required_argument, NULL, 'i' }, 278 | { "label", required_argument, NULL, 'l' }, 279 | { "outdir", required_argument, NULL, 'o' }, 280 | { "procs", required_argument, NULL, 'p' }, 281 | { "quiet", no_argument, NULL, 'q' }, 282 | { "script", required_argument, NULL, 'r' }, 283 | { "nokeychk", no_argument, NULL, 's' }, 284 | { "no-err", no_argument, NULL, 'E' }, 285 | { "no-out", no_argument, NULL, 'O' }, 286 | { "conntmout", required_argument, NULL, 't' }, 287 | { "user", required_argument, NULL, 'u' }, 288 | { "verbose", no_argument, NULL, 'v' }, 289 | { "version", no_argument, NULL, 'V' }, 290 | { NULL, 0, NULL, 0}, 291 | }; 292 | 293 | while ((opt = getopt_long(*argc, *argv, 294 | "bd:eEf:hi:l:o:Op:qr:u:t:svV", longopts, NULL)) != -1) { 295 | switch (opt) { 296 | case 'b': 297 | blind = 1; 298 | break; 299 | case 'd': 300 | delay = (int)strtol(optarg,(char **)NULL,10); 301 | if (delay == 0 && errno == EINVAL) 302 | usage("invalid delay value"); 303 | if (delay < 0) usage("delay can't be negative"); 304 | break; 305 | case 'e': 306 | print_exit = 1; 307 | break; 308 | case 'E': 309 | no_err = 1; 310 | break; 311 | case 'f': 312 | if (fname) 313 | usage("one filename allowed"); 314 | fname = optarg; 315 | break; 316 | case 'h': 317 | usage(NULL); 318 | break; 319 | case 'i': 320 | ident_file = optarg; 321 | break; 322 | case 'l': 323 | label = optarg; 324 | break; 325 | case 'o': 326 | if (outdir) 327 | usage("one output dir allowed"); 328 | outdir = optarg; 329 | break; 330 | case 'O': 331 | no_out = 1; 332 | break; 333 | case 'p': 334 | maxchld = (int)strtol(optarg,(char **)NULL,10); 335 | if (maxchld < 0) usage("bad numproc"); 336 | if (maxchld > MAXCHLD) maxchld = MAXCHLD; 337 | break; 338 | case 'q': 339 | ssh_quiet = 1; 340 | break; 341 | case 'r': 342 | local_command = 1; 343 | script = optarg; 344 | if (stat(script, &scstat) < 0) { 345 | usage("can't stat script file"); 346 | } 347 | if (!(S_ISREG(scstat.st_mode) && scstat.st_mode & 0111)) { 348 | usage("script file is not executable"); 349 | } 350 | base_script = basename(script); 351 | break; 352 | case 's': 353 | ssh_hkey_check = 0; 354 | break; 355 | case 't': 356 | ssh_conn_tmout = (int)strtol(optarg,(char **)NULL,10); 357 | break; 358 | case 'u': 359 | if (user) 360 | usage("one username allowed"); 361 | user = optarg; 362 | if (user && strlen(user) > MAXUSER) 363 | usage("username too long"); 364 | break; 365 | case 'v': 366 | verbose = 1; 367 | break; 368 | case 'V': 369 | show_ver(); 370 | break; 371 | case '?': 372 | usage("unrecognized option"); 373 | break; 374 | default: 375 | usage(NULL); 376 | } 377 | } 378 | *argc -= optind; 379 | *argv += optind; 380 | 381 | if (!maxchld) 382 | maxchld = DEFCHLD; 383 | 384 | if (local_command) { 385 | if(*argc) 386 | usage("can't use remote command when executing local script"); 387 | return; 388 | } 389 | 390 | if (*argc > 1) 391 | usage("too many arguments"); 392 | if (*argc < 1) 393 | usage("command missing, use -h for help"); 394 | 395 | cmd = *argv[0]; 396 | if (strlen(cmd) > MAXCMD) 397 | usage("command too long"); 398 | 399 | return; 400 | } 401 | 402 | /* 403 | * Routine to handle stdout and stderr 404 | * output file creation and opening 405 | * when output to file mode is enabled. 406 | */ 407 | int 408 | setupoutdirfiles(struct procslot *p) 409 | { 410 | int i; 411 | 412 | /* 413 | * alloc enough space for the string consisting 414 | * of a directoryname, slash, username, @ sign, 415 | * hostname, a dot and a three letter file 416 | * extension (out/err) and the terminating null 417 | */ 418 | i = strlen(outdir); 419 | i += strlen(p->hst->user); 420 | i += strlen(p->hst->host); 421 | i += 7; 422 | 423 | /* setup the stdout output file */ 424 | p->outf[0].name = calloc(1, i); 425 | if (!p->outf[0].name) { 426 | perr("unable to malloc memory for filename\n"); 427 | return(1); 428 | } 429 | sprintf(p->outf[0].name, "%s/%s@%s.out", 430 | outdir, p->hst->user, p->hst->host); 431 | p->outf[0].fh = fopen(p->outf[0].name, "w"); 432 | if (!p->outf[0].fh) { 433 | perr("unable to open : %s\n", p->outf[0].name); 434 | return(1); 435 | } 436 | 437 | /* setup the stderr output file */ 438 | p->outf[1].name = calloc(1, i); 439 | if (!p->outf[1].name) { 440 | perr("unable to malloc memory for filename\n"); 441 | return(1); 442 | } 443 | sprintf(p->outf[1].name, "%s/%s@%s.err", 444 | outdir, p->hst->user, p->hst->host); 445 | 446 | p->outf[1].fh = fopen(p->outf[1].name, "w"); 447 | if (!p->outf[1].fh) { 448 | perr("unable to open : %s\n", p->outf[1].name); 449 | return(1); 450 | } 451 | return(0); 452 | } 453 | 454 | /* 455 | * Main routine 456 | */ 457 | int 458 | main(int argc, char *argv[]) 459 | { 460 | struct host *hst, *tofree; 461 | int i; 462 | int pid; 463 | int tty; 464 | fd_set readfds; 465 | int children_fds; 466 | struct timeval *timeout; 467 | struct timeval notimeout; 468 | struct passwd *pw; 469 | 470 | parse_opts(&argc, &argv); 471 | 472 | if (!user) { 473 | pw = getpwuid(getuid()); 474 | user = pw->pw_name; 475 | } 476 | 477 | hst = host_readlist(fname); 478 | 479 | if (hst == NULL) { 480 | perr("host list file empty, " 481 | "does not exist or no valid entries\n"); 482 | exit(1); 483 | } 484 | 485 | tofree = hst; 486 | 487 | /* Console Printf if we are running on tty */ 488 | tty = isatty(fileno(stdout)); 489 | #define tty_printf(...) if (tty) fprintf(stdout, __VA_ARGS__) 490 | 491 | tty_printf( "MPSSH - Mass Parallel Ssh Ver.%s\n" 492 | "(c)2005-2013 Nikolay Denev \n\n" 493 | " [*] read (%d) hosts from the list\n", 494 | Ver, hostcount); 495 | 496 | if (local_command) { 497 | tty_printf( " [*] uploading and executing the script \"%s\" as user \"%s\"\n", 498 | script, user); 499 | } else { 500 | tty_printf( " [*] executing \"%s\" as user \"%s\"\n", cmd, user); 501 | } 502 | 503 | if (label) 504 | tty_printf(" [*] only on hosts labeled \"%s\"\n", label); 505 | 506 | if (!ssh_hkey_check) 507 | tty_printf(" [*] strict host key check disabled\n"); 508 | 509 | if (blind) 510 | tty_printf(" [*] blind mode enabled\n"); 511 | 512 | if (verbose) 513 | tty_printf(" [*] verbose mode enabled\n"); 514 | 515 | if (outdir) { 516 | if (!access(outdir, R_OK | W_OK | X_OK)) { 517 | tty_printf(" [*] using output directory : %s\n", outdir); 518 | } else { 519 | tty_printf(" [*] creating output directory : %s\n", outdir); 520 | if (mkdir(outdir, 0755)) { 521 | perr("\n *** can't create output dir : "); 522 | perror(outdir); 523 | exit(1); 524 | } 525 | } 526 | } 527 | tty_printf(" [*] spawning %d parallel ssh sessions\n\n", 528 | maxchld); 529 | fflush(NULL); 530 | 531 | /* install the signal handler for SIGCHLD */ 532 | signal(SIGCHLD, reap_child); 533 | 534 | if (outdir) 535 | umask(022); 536 | 537 | while (hst || children) { 538 | BLOCK_SIGCHLD; 539 | if (hst && (children < maxchld)) { 540 | ps = pslot_add(ps, 0, hst); 541 | if (outdir) 542 | setupoutdirfiles(ps); 543 | switch (pid = fork()) { 544 | case 0: 545 | /* child, does not return */ 546 | child(); 547 | break; 548 | case -1: 549 | /* error */ 550 | perr("unable to fork: %s\n", 551 | strerror(errno)); 552 | break; 553 | default: 554 | /* parent */ 555 | ps->pid = pid; 556 | /* close the child's end of the pipes */ 557 | close(ps->io.out[1]); 558 | close(ps->io.err[1]); 559 | children++; 560 | break; 561 | } 562 | /* delay between each sshd fork */ 563 | if (delay) 564 | usleep(delay * 1000); 565 | 566 | hst = hst->next; 567 | } 568 | FD_ZERO(&readfds); 569 | children_fds = children; 570 | for (i=0; i <= children_fds; i++) { 571 | FD_SET(ps->io.out[0], &readfds); 572 | FD_SET(ps->io.err[0], &readfds); 573 | if (ps->next) 574 | ps = ps->next; 575 | } 576 | if (children == maxchld || !hst) { 577 | timeout = NULL; 578 | } else { 579 | memset(¬imeout, 0, sizeof(struct timeval)); 580 | timeout = ¬imeout; 581 | } 582 | UNBLOCK_SIGCHLD; 583 | if (select(MAXFD , &readfds, NULL, NULL, timeout) > 0 ) { 584 | BLOCK_SIGCHLD; 585 | if (ps) { 586 | for (i=0; i <= children_fds; i++) { 587 | if (FD_ISSET(ps->io.out[0], &readfds)) { 588 | while (pslot_readbuf(ps, OUT)) 589 | pslot_printbuf(ps, OUT); 590 | } 591 | if (FD_ISSET(ps->io.err[0], &readfds)) { 592 | while (pslot_readbuf(ps, ERR)) 593 | pslot_printbuf(ps, ERR); 594 | } 595 | ps = ps->next; 596 | } 597 | } 598 | UNBLOCK_SIGCHLD; 599 | } 600 | } 601 | tty_printf("\n Done. %d hosts processed.\n", done); 602 | 603 | host_free(tofree); 604 | 605 | return(0); 606 | } 607 | --------------------------------------------------------------------------------