New Assuan testing tool.
[gnupg.git] / tests / asschk.c
1 /* asschk.c - Assuan Server Checker
2  *      Copyright (C) 2002 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 /* This is a simple stand-alone Assuan server test program.  We don't
22    want to use the assuan library because we don't want to hide errors
23    in that library. 
24
25    The script language is line based.  Empty lines or lines containing
26    only white spaces are ignored, line with a hash sign as first non
27    white space character are treated as comments.
28    
29    A simple macro mechanism is implemnted.  Macros are expanded before
30    a line is processed but after comment processing.  Macros are only
31    expanded once and non existing macros expand to the empty string.
32    A macro is dereferenced by prefixing its name with a dollar sign;
33    the end of the name is currently indicated by a white space.  To
34    use a dollor sign verbatim, double it.
35
36    A macro is assigned by prefixing a statement with the macro name
37    and an equal sign.  The value is assigned verbatim if it does not
38    resemble a command, otherwise the return value of the command will
39    get assigned.  The command "let" may be used to assign values
40    unambigiously and it should be used if the value starts with a
41    letter.
42
43    The following commands are available:
44
45    [<name> =] let <value>
46       Assign VALUE to the macro named NAME.
47
48    [<name> =] openfile <filename>
49       Open file FILENAME for read access and store the file descriptor
50       in NAME.
51
52    [<name> =] createfile <filename>
53       Create file FILENAME and open for write access, store the file
54       descriptor in NAME.
55
56    pipeserver [<path>]
57       Connect to an Assuan server with name PATH.  If PATH is not
58       specified the value ../sm/gpgsm is used.
59
60    send <line>
61       Send LINE to the server.
62
63    expect-ok
64       Expect an OK response from the server.  Status and data out put
65       is ignored.
66
67    expect-err
68       Expect an ERR response from the server.  Status and data out put
69       is ignored.
70
71    quit
72       Terminate the program
73
74
75 */
76
77 #include <stdio.h>
78 #include <stdlib.h>
79 #include <string.h>
80 #include <errno.h>
81 #include <stdarg.h>
82 #include <assert.h>
83 #include <unistd.h>
84 #include <fcntl.h>
85
86 #define PGMNAME "asschk"
87
88 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
89 # define ATTR_PRINTF(f,a)  __attribute__ ((format (printf,f,a)))
90 #else
91 # define ATTR_PRINTF(f,a)
92 #endif
93
94 #define spacep(p) (*(p) == ' ' || *(p) == '\t')
95
96 typedef enum {
97   LINE_OK = 0,
98   LINE_ERR,
99   LINE_STAT,
100   LINE_DATA,
101   LINE_END,
102 } LINETYPE;
103
104
105 struct variable_s {
106   struct variable_s *next;
107   char *value;
108   char name[1];
109 };
110 typedef struct variable_s *VARIABLE;
111
112
113
114 static void die (const char *format, ...)  ATTR_PRINTF(1,2);
115
116
117 /* File descriptors used to communicate with the current server. */
118 static int server_send_fd = -1;
119 static int server_recv_fd = -1;
120
121 /* The Assuan protocol limits the line length to 1024, so we can
122    safely use a (larger) buffer.  The buffer is filled using the
123    read_assuan(). */
124 static char recv_line[2048];
125 /* Tell the status of the current line. */
126 static LINETYPE recv_type;
127
128 /* This is our variable storage. */
129 static VARIABLE variable_list;
130
131 \f
132 static void
133 die (const char *format, ...)
134 {
135   va_list arg_ptr;
136
137   fflush (stdout);
138   fprintf (stderr, "%s: ", PGMNAME);
139
140   va_start (arg_ptr, format);
141   vfprintf (stderr, format, arg_ptr);
142   va_end (arg_ptr);
143   putc ('\n', stderr);
144
145   exit (1);
146 }
147
148 static void *
149 xmalloc (size_t n)
150 {
151   void *p = malloc (n);
152   if (!p)
153     die ("out of core");
154   return p;
155 }
156
157 static void *
158 xcalloc (size_t n, size_t m)
159 {
160   void *p = calloc (n, m);
161   if (!p)
162     die ("out of core");
163   return p;
164 }
165
166 static char *
167 xstrdup (const char *s)
168 {
169   char *p = xmalloc (strlen (s)+1);
170   strcpy (p, s);
171   return p;
172 }
173
174
175 /* Write LENGTH bytes from BUFFER to FD. */
176 static int
177 writen (int fd, const char *buffer, size_t length)
178 {
179   while (length)
180     {
181       int nwritten = write (fd, buffer, length);
182       
183       if (nwritten < 0)
184         {
185           if (errno == EINTR)
186             continue;
187           return -1; /* write error */
188         }
189       length -= nwritten;
190       buffer += nwritten;
191     }
192   return 0;  /* okay */
193 }
194
195
196
197 \f
198 /* Assuan specific stuff. */
199
200 /* Read a line from FD, store it in the global recv_line, analyze the
201    type and store that in recv_type.  The function terminates on a
202    communication error.  Returns a pointer into the inputline to the
203    first byte of the arguments.  The parsing is very strict to match
204    excalty what we want to send. */
205 static char *
206 read_assuan (int fd)
207 {
208   size_t nleft = sizeof recv_line;
209   char *buf = recv_line;
210   char *p;
211   int nread = 0;
212
213   while (nleft > 0)
214     {
215       int n = read (fd, buf, nleft);
216       if (n < 0)
217         {
218           if (errno == EINTR)
219             continue;
220           die ("reading fd %d failed: %s", fd, strerror (errno));
221         }
222       else if (!n)
223         die ("received incomplete line on fd %d", fd);
224       p = buf;
225       nleft -= n;
226       buf += n;
227       nread += n;
228       
229       for (; n && *p != '\n'; n--, p++)
230         ;
231       if (n)
232         {
233           /* fixme: keep pending bytes for next read. */
234           break;
235         }
236     }
237   if (!nleft)
238     die ("received line too large");
239   assert (nread>0);
240   recv_line[nread-1] = 0;
241   
242   p = recv_line;
243   if (p[0] == 'O' && p[1] == 'K' && (p[2] == ' ' || !p[2]))
244     {
245       recv_type = LINE_OK;
246       p += 3;
247     }
248   else if (p[0] == 'E' && p[1] == 'R' && p[2] == 'R'
249            && (p[3] == ' ' || !p[3]))
250     {
251       recv_type = LINE_ERR;
252       p += 4;
253     }
254   else if (p[0] == 'S' && (p[1] == ' ' || !p[1]))
255     {
256       recv_type = LINE_STAT;
257       p += 2;
258     }
259   else if (p[0] == 'D' && p[1] == ' ')
260     {
261       recv_type = LINE_DATA;
262       p += 2;
263     }
264   else if (p[0] == 'E' && p[1] == 'N' &&  p[2] == 'D' && !p[3])
265     {
266       recv_type = LINE_END;
267       p += 3;
268     }
269   else 
270     die ("invalid line type (%.5s)", p);
271
272   return p;
273 }
274
275 /* Write LINE to the server using FD.  It is expected that the line
276    contains the terminating linefeed as last character. */
277 static void
278 write_assuan (int fd, const char *line)
279 {
280   char buffer[1026];
281   size_t n = strlen (line);
282
283   if (n > 1024)
284     die ("line too long for Assuan protocol");
285   strcpy (buffer, line);
286   if (!n || buffer[n-1] != '\n')
287     buffer[n++] = '\n';
288
289   if (writen (fd, buffer, n))
290       die ("sending line to %d failed: %s", fd, strerror (errno));
291 }
292
293
294 /* Start the server with path PGMNAME and connect its stdout and
295    strerr to a newly created pipes; the file descriptors are then
296    store in the gloabl variables SERVER_SEND_FD and
297    SERVER_RECV_FD. The initial handcheck is performed.*/
298 static void
299 start_server (const char *pgmname)
300 {
301   int rp[2];
302   int wp[2];
303   pid_t pid;
304
305   if (pipe (rp) < 0)
306     die ("pipe creation failed: %s", strerror (errno));
307   if (pipe (wp) < 0)
308     die ("pipe creation failed: %s", strerror (errno));
309
310   fflush (stdout);
311   fflush (stderr);
312   pid = fork ();
313   if (pid < 0)
314     die ("fork failed");
315
316   if (!pid)
317     {
318       const char *arg0;
319
320       arg0 = strrchr (pgmname, '/');
321       if (!arg0)
322         arg0 = pgmname;
323
324       if (wp[0] != STDIN_FILENO)
325         {
326           if (dup2 (wp[0], STDIN_FILENO) == -1)
327               die ("dup2 failed in child: %s", strerror (errno));
328           close (wp[0]);
329         }
330       if (rp[1] != STDOUT_FILENO)
331         {
332           if (dup2 (rp[1], STDOUT_FILENO) == -1)
333               die ("dup2 failed in child: %s", strerror (errno));
334           close (rp[1]);
335         }
336
337       execl (pgmname, arg0, "--server", NULL); 
338       die ("exec failed for `%s': %s", pgmname, strerror (errno));
339     }
340   close (wp[0]);
341   close (rp[1]);
342   server_send_fd = wp[1];
343   server_recv_fd = rp[0];
344
345   read_assuan (server_recv_fd);
346   if (recv_type != LINE_OK)
347     die ("no greating message");
348 }
349
350
351
352
353 \f
354 /* Script intepreter. */
355
356 static void
357 unset_var (const char *name)
358 {
359   VARIABLE var;
360
361   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
362     ;
363   if (!var)
364     return;
365   fprintf (stderr, "unsetting `%s'\n", name);
366
367   free (var->value);
368   var->value = NULL;
369 }
370
371
372 static void
373 set_var (const char *name, const char *value)
374 {
375   VARIABLE var;
376
377   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
378     ;
379   if (!var)
380     {
381       var = xcalloc (1, sizeof *var + strlen (name));
382       strcpy (var->name, name);
383       var->next = variable_list;
384       variable_list = var;
385     }
386   else
387     free (var->value);
388
389   var->value = xstrdup (value);
390   fprintf (stderr, "setting `%s' to `%s'\n", var->name, var->value);
391
392 }
393
394 static const char *
395 get_var (const char *name)
396 {
397   VARIABLE var;
398
399   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
400     ;
401   return var? var->value:NULL;
402 }
403
404
405
406 /* Expand variables in LINE and return a new allocated buffer if
407    required.  The function might modify LINE if the expanded version
408    fits into it. */
409 static char *
410 expand_line (char *buffer)
411 {
412   char *line = buffer;
413   char *p, *pend;
414   const char *value;
415   size_t valuelen, n;
416   char *result = NULL;
417
418   while (*line)
419     {
420       p = strchr (line, '$');
421       if (!p)
422         return result; /* nothing more to expand */
423       
424       if (p[1] == '$') /* quoted */
425         {
426           memmove (p, p+1, strlen (p+1)+1);
427           line = p + 1;
428           continue;
429         }
430       for (pend=p+1; *pend && !spacep (pend) && *pend != '$'; pend++)
431         ;
432       if (*pend)
433         {
434           int save = *pend;
435           *pend = 0;
436           value = get_var (p+1);
437           *pend = save;
438         }
439       else
440         value = get_var (p+1);
441       if (!value)
442         value = "";
443       valuelen = strlen (value);
444       if (valuelen <= pend - p)
445         {
446           memcpy (p, value, valuelen);
447           p += valuelen;
448           n = pend - p;
449           if (n)
450             memmove (p, p+n, strlen (p+n)+1);
451           line = p;
452         }
453       else
454         {
455           char *src = result? result : buffer;
456           char *dst;
457
458           dst = xmalloc (strlen (src) + valuelen + 1);
459           n = p - src;
460           memcpy (dst, src, n);
461           memcpy (dst + n, value, valuelen);
462           n += valuelen;
463           strcpy (dst + n, pend);
464           line = dst + n;
465           free (result);
466           result = dst;
467         }
468     }
469   return result;
470 }
471
472 static void
473 cmd_let (const char *assign_to, char *arg)
474 {
475   if (!assign_to)
476     die ("syntax error: \"let\" needs an assignment");
477   set_var (assign_to, arg);
478 }
479
480
481 static void
482 cmd_send (const char *assign_to, char *arg)
483 {
484   fprintf (stderr, "sending `%s'\n", arg);
485   write_assuan (server_send_fd, arg); 
486 }
487
488 static void
489 cmd_expect_ok (const char *assign_to, char *arg)
490 {
491   fprintf (stderr, "expecting OK\n");
492   do
493     {
494       read_assuan (server_recv_fd);
495       fprintf (stderr, "got line `%s'\n", recv_line);
496     }
497   while (recv_type != LINE_OK && recv_type != LINE_ERR);
498   if (recv_type != LINE_OK)
499     die ("expected OK but got `%s'", recv_line);
500 }
501
502 static void
503 cmd_expect_err (const char *assign_to, char *arg)
504 {
505   do
506     {
507       read_assuan (server_recv_fd);
508       fprintf (stderr, "got line `%s'\n", recv_line);
509     }
510   while (recv_type != LINE_OK && recv_type != LINE_ERR);
511   if (recv_type != LINE_ERR)
512     die ("expected ERR but got `%s'", recv_line);
513 }
514
515 static void
516 cmd_openfile (const char *assign_to, char *arg)
517 {
518   int fd;
519
520   do 
521     fd = open (arg, O_RDONLY);
522   while (fd == -1 && errno == EINTR);
523   if (fd == -1)
524     die ("error opening `%s': %s", arg, strerror (errno));
525   if (assign_to)
526     {
527       char numbuf[20];
528
529       sprintf (numbuf, "%d", fd);
530       set_var (assign_to, numbuf);
531     }
532 }
533
534 static void
535 cmd_createfile (const char *assign_to, char *arg)
536 {
537   int fd;
538
539   do 
540     fd = open (arg, O_WRONLY|O_CREAT|O_TRUNC, 0666);
541   while (fd == -1 && errno == EINTR);
542   if (fd == -1)
543     die ("error creating `%s': %s", arg, strerror (errno));
544   if (assign_to)
545     {
546       char numbuf[20];
547
548       sprintf (numbuf, "%d", fd);
549       set_var (assign_to, numbuf);
550     }
551 }
552
553
554 static void
555 cmd_pipeserver (const char *assign_to, char *arg)
556 {
557   if (!*arg)
558     arg = "../sm/gpgsm";
559
560   start_server (arg);
561 }
562
563
564 /* Process the current script line LINE. */
565 static int
566 interpreter (char *line)
567 {
568   static struct {
569     const char *name;
570     void (*fnc)(const char*, char*);
571   } cmdtbl[] = {
572     { "let"       , cmd_let },
573     { "send"      , cmd_send },
574     { "expect-ok" , cmd_expect_ok },
575     { "expect-err", cmd_expect_err },
576     { "openfile"  , cmd_openfile },
577     { "createfile", cmd_createfile },
578     { "pipeserver", cmd_pipeserver },
579     { "quit"      , NULL },
580     { NULL }
581   };
582   char *p, *save_p;
583   int i, save_c;
584   char *stmt = NULL;
585   char *assign_to = NULL;
586   char *must_free = NULL;
587
588   for ( ;spacep (line); line++)
589     ;
590   if (!*line || *line == '#')
591     return 0; /* empty or comment */
592   p = expand_line (line);
593   if (p)
594     {
595       must_free = p;
596       line = p;
597       for ( ;spacep (line); line++)
598         ;
599       if (!*line || *line == '#')
600         {
601           free (must_free);
602           return 0; /* empty or comment */
603         }
604     }
605
606   for (p=line; *p && !spacep (p) && *p != '='; p++)
607     ;
608   if (*p == '=')
609     {
610       *p = 0;
611       assign_to = line;
612     }
613   else if (*p)
614     {
615       for (*p++ = 0; spacep (p); p++)
616         ;
617       if (*p == '=')
618         assign_to = line;
619     }
620   if (!*line)
621     die ("syntax error");
622   stmt = line;
623   save_c = 0;
624   save_p = NULL;
625   if (assign_to)
626     { /* this is an assignment */
627       for (p++; spacep (p); p++)
628         ;
629       if (!*p)
630         {
631           unset_var (assign_to);
632           free (must_free);
633           return 0;
634         }
635       stmt = p;
636       for (; *p && !spacep (p); p++)
637         ;
638       if (*p)
639         {
640           save_p = p;
641           save_c = *p;
642           for (*p++ = 0; spacep (p);  p++)
643             ;
644         }
645     }
646   for (i=0; cmdtbl[i].name && strcmp (stmt, cmdtbl[i].name); i++)
647     ;
648   if (!cmdtbl[i].name)
649     {
650       if (!assign_to)
651         die ("invalid statement `%s'\n", stmt);
652       if (save_p)
653         *save_p = save_c;
654       set_var (assign_to, stmt);
655       free (must_free);
656       return 0;
657     }
658
659   if (cmdtbl[i].fnc)
660     cmdtbl[i].fnc (assign_to, p);
661   free (must_free);
662   return cmdtbl[i].fnc? 0:1;
663 }
664
665
666
667 int
668 main (int argc, char **argv)
669 {
670   char buffer[1025];
671   char *p;
672
673   if (argc)
674     argv++, argc--;
675   if (argc)
676     die ("usage: asschk <script");
677   
678   while (fgets (buffer, sizeof buffer, stdin))
679     {
680       p = strchr (buffer,'\n');
681       if (!p)
682         die ("incomplete script line");
683       *p = 0;
684       if (interpreter (buffer))
685         break;
686       fflush (stdout);
687     }
688   return 0;
689 }