Port to pcre2
[miniexpect.git] / example-sshpass.c
1 /* miniexpect example.
2  * Copyright (C) 2014-2022 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18
19 /* There is a program called 'sshpass' which does roughly the same
20  * as this simplified example.  We run ssh as a subprocess, and log
21  * in using the given password from the command line.
22  *
23  * The first argument is the password to send at the password prompt.
24  * The remaining arguments are passed to the ssh subprocess.
25  *
26  * For example:
27  *   sshpass [-d] 123456 ssh remote.example.com
28  *   sshpass [-d] 123456 ssh -l root remote.example.com
29  *
30  * Use the -d flag to enable debugging to stderr.
31  */
32
33 #include <config.h>
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <assert.h>
40
41 #define PCRE2_CODE_UNIT_WIDTH 8
42 #include <pcre2.h>
43
44 #include "miniexpect.h"
45
46 static pcre2_code *compile_re (const char *rex);
47
48 static void
49 usage (void)
50 {
51   fprintf (stderr, "usage: sshpass [-d] PASSWORD ssh [SSH-ARGS...] HOST\n");
52   exit (EXIT_FAILURE);
53 }
54
55 int
56 main (int argc, char *argv[])
57 {
58   int opt;
59   int debug = 0;
60   mexp_h *h;
61   const char *password;
62   int status;
63   pcre2_code *password_re, *prompt_re, *hello_re;
64   pcre2_match_data *match_data;
65
66   match_data = pcre2_match_data_create (4, NULL);
67
68   while ((opt = getopt (argc, argv, "d")) != -1) {
69     switch (opt) {
70     case 'd':
71       debug = 1;
72       break;
73     default:
74       usage ();
75     }
76   }
77
78   if (argc-optind <= 2)
79     usage ();
80
81   password = argv[optind];
82   optind++;
83
84   printf ("starting ssh command ...\n");
85
86   h = mexp_spawnv (argv[optind], &argv[optind]);
87   if (h == NULL) {
88     perror ("mexp_spawnv: ssh");
89     exit (EXIT_FAILURE);
90   }
91   if (debug)
92     mexp_set_debug_file (h, stderr);
93
94   /* Wait for the password prompt. */
95   password_re = compile_re ("assword");
96   switch (mexp_expect (h,
97                        (mexp_regexp[]) {
98                          { 100, .re = password_re },
99                          { 0 }
100                        },
101                        match_data)) {
102   case 100:
103     break;
104   case MEXP_EOF:
105     fprintf (stderr, "error: ssh closed the connection unexpectedly\n");
106     goto error;
107   case MEXP_TIMEOUT:
108     fprintf (stderr, "error: timeout before reaching the password prompt\n");
109     goto error;
110   case MEXP_ERROR:
111     perror ("mexp_expect");
112     goto error;
113   case MEXP_PCRE_ERROR:
114     fprintf (stderr, "error: PCRE error: %d\n", mexp_get_pcre_error (h));
115     goto error;
116   }
117
118   /* Got the password prompt, so send a password.
119    *
120    * Note use of mexp_printf_password here which is identical to
121    * mexp_printf except that it hides the password in debugging
122    * output.
123    */
124   printf ("sending the password ...\n");
125
126   if (mexp_printf_password (h, "%s", password) == -1 ||
127       mexp_printf (h, "\n") == -1) {
128     perror ("mexp_printf");
129     goto error;
130   }
131
132   /* We have to wait for the prompt before we can send commands
133    * (because the ssh connection may not be fully established).  If we
134    * get "password" again, then probably the password was wrong.  This
135    * expect checks all these possibilities.  Unfortunately since all
136    * prompts are a little bit different, we have to guess here.
137    */
138   prompt_re = compile_re ("[#$]");
139   switch (mexp_expect (h,
140                        (mexp_regexp[]) {
141                          { 100, .re = password_re },
142                          { 101, .re = prompt_re },
143                          { 0 },
144                        },
145                        match_data)) {
146   case 100:                     /* Password. */
147     fprintf (stderr, "error: ssh asked for password again, probably the password supplied is wrong\n");
148     goto error;
149   case 101:                     /* Prompt. */
150     break;
151   case MEXP_EOF:
152     fprintf (stderr, "error: ssh closed the connection unexpectedly\n");
153     goto error;
154   case MEXP_TIMEOUT:
155     fprintf (stderr, "error: timeout before reaching the prompt\n");
156     goto error;
157   case MEXP_ERROR:
158     perror ("mexp_expect");
159     goto error;
160   case MEXP_PCRE_ERROR:
161     fprintf (stderr, "error: PCRE error: %d\n", mexp_get_pcre_error (h));
162     goto error;
163   }
164
165   /* Send a command which will have expected output. */
166   printf ("sending a test command ...\n");
167
168   if (mexp_printf (h, "echo h''ello\n") == -1) {
169     perror ("mexp_printf");
170     goto error;
171   }
172
173   /* Wait for expected output from echo hello command. */
174   hello_re = compile_re ("hello");
175   switch (mexp_expect (h,
176                        (mexp_regexp[]) {
177                          { 100, .re = hello_re },
178                          { 0 },
179                        },
180                        match_data)) {
181   case 100:
182     break;
183   case MEXP_EOF:
184     fprintf (stderr, "error: ssh closed the connection unexpectedly\n");
185     goto error;
186   case MEXP_TIMEOUT:
187     fprintf (stderr, "error: timeout before reading command output\n");
188     goto error;
189   case MEXP_ERROR:
190     perror ("mexp_expect");
191     goto error;
192   case MEXP_PCRE_ERROR:
193     fprintf (stderr, "error: PCRE error: %d\n", mexp_get_pcre_error (h));
194     goto error;
195   }
196
197   /* Send exit command and wait for ssh to exit. */
198   printf ("sending the exit command ...\n");
199
200   if (mexp_printf (h, "exit\n") == -1) {
201     perror ("mexp_printf");
202     goto error;
203   }
204
205   switch (mexp_expect (h, NULL, NULL)) {
206   case MEXP_EOF:
207     /* This is what we're expecting: ssh will close the connection. */
208     break;
209   case MEXP_TIMEOUT:
210     fprintf (stderr, "error: timeout before ssh closed the connection\n");
211     goto error;
212   case MEXP_ERROR:
213     perror ("mexp_expect");
214     goto error;
215   case MEXP_PCRE_ERROR:
216     fprintf (stderr, "error: unexpected return value from mexp_expect\n");
217     goto error;
218   }
219
220   /* Close the ssh connection. */
221   status = mexp_close (h);
222   if (status != 0) {
223     fprintf (stderr, "error: bad exit status from ssh subprocess (status=%d)\n",
224              status);
225     exit (EXIT_FAILURE);
226   }
227
228   printf ("test was successful\n");
229
230   pcre2_match_data_free (match_data);
231   exit (EXIT_SUCCESS);
232
233  error:
234   mexp_close (h);
235   exit (EXIT_FAILURE);
236 }
237
238 /* Helper function to compile a PCRE regexp. */
239 static pcre2_code *
240 compile_re (const char *rex)
241 {
242   int errorcode;
243   PCRE2_SIZE erroroffset;
244   char errormsg[256];
245   pcre2_code *ret;
246
247   ret = pcre2_compile ((PCRE2_SPTR) rex, PCRE2_ZERO_TERMINATED,
248                        0, &errorcode, &erroroffset, NULL);
249   if (ret == NULL) {
250     pcre2_get_error_message (errorcode,
251                              (PCRE2_UCHAR *) errormsg, sizeof errormsg);
252     fprintf (stderr, "error: "
253              "failed to compile regular expression '%s': "
254              "%s at offset %zu\n",
255              rex, errormsg, erroroffset);
256     exit (EXIT_FAILURE);
257   }
258   return ret;
259 }