First test script. Still missing the environment setup.
[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    Conditions are not yes implemented except for a simple evaluation
44    which yields false for an empty string or the string "0".  The
45    result may be negated by prefixing with a '!'.
46
47    The general syntax of a command is:
48
49    [<name> =] <statement> [<args>]
50
51    If NAME is not specifed but the statement returns a value it is
52    assigned to the name "?" so that it can be referenced using "$?".
53    The following commands are implemented:
54
55    let <value>
56       Return VALUE.
57
58    echo <value>
59       Print VALUE.
60
61    openfile <filename>
62       Open file FILENAME for read access and retrun the file descriptor.
63
64    createfile <filename>
65       Create file FILENAME, open for write access and retrun the file
66       descriptor.
67
68    pipeserver [<path>]
69       Connect to an Assuan server with name PATH.  If PATH is not
70       specified the value ../sm/gpgsm is used.
71
72    send <line>
73       Send LINE to the server.
74
75    expect-ok
76       Expect an OK response from the server.  Status and data out put
77       is ignored.
78
79    expect-err
80       Expect an ERR response from the server.  Status and data out put
81       is ignored.
82
83    quit
84       Terminate the process.
85
86    quit-if <condition>
87       Terminate the process if CONDITION evaluates to true.
88
89    fail-if <condition>
90       Terminate the process with an exit code of 1 if CONDITION
91       evaluates to true.
92
93    cmpfiles <first> <second>
94       Returns true when the content of the files FIRST and SECOND match.
95
96 */
97
98 #include <stdio.h>
99 #include <stdlib.h>
100 #include <string.h>
101 #include <errno.h>
102 #include <stdarg.h>
103 #include <assert.h>
104 #include <unistd.h>
105 #include <fcntl.h>
106
107 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
108 # define ATTR_PRINTF(f,a)  __attribute__ ((format (printf,f,a)))
109 #else
110 # define ATTR_PRINTF(f,a)
111 #endif
112
113 #define spacep(p) (*(p) == ' ' || *(p) == '\t')
114
115 typedef enum {
116   LINE_OK = 0,
117   LINE_ERR,
118   LINE_STAT,
119   LINE_DATA,
120   LINE_END,
121 } LINETYPE;
122
123
124 struct variable_s {
125   struct variable_s *next;
126   int is_fd;
127   char *value;
128   char name[1];
129 };
130 typedef struct variable_s *VARIABLE;
131
132
133
134 static void die (const char *format, ...)  ATTR_PRINTF(1,2);
135
136
137 /* Name of this program to be printed in error messages. */
138 static const char *invocation_name;
139
140 /* Talk a bit about what is going on. */
141 static int opt_verbose;
142
143 /* File descriptors used to communicate with the current server. */
144 static int server_send_fd = -1;
145 static int server_recv_fd = -1;
146
147 /* The Assuan protocol limits the line length to 1024, so we can
148    safely use a (larger) buffer.  The buffer is filled using the
149    read_assuan(). */
150 static char recv_line[2048];
151 /* Tell the status of the current line. */
152 static LINETYPE recv_type;
153
154 /* This is our variable storage. */
155 static VARIABLE variable_list;
156
157 \f
158 static void
159 die (const char *format, ...)
160 {
161   va_list arg_ptr;
162
163   fflush (stdout);
164   fprintf (stderr, "%s: ", invocation_name);
165
166   va_start (arg_ptr, format);
167   vfprintf (stderr, format, arg_ptr);
168   va_end (arg_ptr);
169   putc ('\n', stderr);
170
171   exit (1);
172 }
173
174 static void
175 err (const char *format, ...)
176 {
177   va_list arg_ptr;
178
179   fflush (stdout);
180   fprintf (stderr, "%s: ", invocation_name);
181
182   va_start (arg_ptr, format);
183   vfprintf (stderr, format, arg_ptr);
184   va_end (arg_ptr);
185   putc ('\n', stderr);
186 }
187
188 static void *
189 xmalloc (size_t n)
190 {
191   void *p = malloc (n);
192   if (!p)
193     die ("out of core");
194   return p;
195 }
196
197 static void *
198 xcalloc (size_t n, size_t m)
199 {
200   void *p = calloc (n, m);
201   if (!p)
202     die ("out of core");
203   return p;
204 }
205
206 static char *
207 xstrdup (const char *s)
208 {
209   char *p = xmalloc (strlen (s)+1);
210   strcpy (p, s);
211   return p;
212 }
213
214
215 /* Write LENGTH bytes from BUFFER to FD. */
216 static int
217 writen (int fd, const char *buffer, size_t length)
218 {
219   while (length)
220     {
221       int nwritten = write (fd, buffer, length);
222       
223       if (nwritten < 0)
224         {
225           if (errno == EINTR)
226             continue;
227           return -1; /* write error */
228         }
229       length -= nwritten;
230       buffer += nwritten;
231     }
232   return 0;  /* okay */
233 }
234
235
236
237 \f
238 /* Assuan specific stuff. */
239
240 /* Read a line from FD, store it in the global recv_line, analyze the
241    type and store that in recv_type.  The function terminates on a
242    communication error.  Returns a pointer into the inputline to the
243    first byte of the arguments.  The parsing is very strict to match
244    excalty what we want to send. */
245 static char *
246 read_assuan (int fd)
247 {
248   size_t nleft = sizeof recv_line;
249   char *buf = recv_line;
250   char *p;
251   int nread = 0;
252
253   while (nleft > 0)
254     {
255       int n = read (fd, buf, nleft);
256       if (n < 0)
257         {
258           if (errno == EINTR)
259             continue;
260           die ("reading fd %d failed: %s", fd, strerror (errno));
261         }
262       else if (!n)
263         die ("received incomplete line on fd %d", fd);
264       p = buf;
265       nleft -= n;
266       buf += n;
267       nread += n;
268       
269       for (; n && *p != '\n'; n--, p++)
270         ;
271       if (n)
272         {
273           /* fixme: keep pending bytes for next read. */
274           break;
275         }
276     }
277   if (!nleft)
278     die ("received line too large");
279   assert (nread>0);
280   recv_line[nread-1] = 0;
281   
282   p = recv_line;
283   if (p[0] == 'O' && p[1] == 'K' && (p[2] == ' ' || !p[2]))
284     {
285       recv_type = LINE_OK;
286       p += 3;
287     }
288   else if (p[0] == 'E' && p[1] == 'R' && p[2] == 'R'
289            && (p[3] == ' ' || !p[3]))
290     {
291       recv_type = LINE_ERR;
292       p += 4;
293     }
294   else if (p[0] == 'S' && (p[1] == ' ' || !p[1]))
295     {
296       recv_type = LINE_STAT;
297       p += 2;
298     }
299   else if (p[0] == 'D' && p[1] == ' ')
300     {
301       recv_type = LINE_DATA;
302       p += 2;
303     }
304   else if (p[0] == 'E' && p[1] == 'N' &&  p[2] == 'D' && !p[3])
305     {
306       recv_type = LINE_END;
307       p += 3;
308     }
309   else 
310     die ("invalid line type (%.5s)", p);
311
312   return p;
313 }
314
315 /* Write LINE to the server using FD.  It is expected that the line
316    contains the terminating linefeed as last character. */
317 static void
318 write_assuan (int fd, const char *line)
319 {
320   char buffer[1026];
321   size_t n = strlen (line);
322
323   if (n > 1024)
324     die ("line too long for Assuan protocol");
325   strcpy (buffer, line);
326   if (!n || buffer[n-1] != '\n')
327     buffer[n++] = '\n';
328
329   if (writen (fd, buffer, n))
330       die ("sending line to %d failed: %s", fd, strerror (errno));
331 }
332
333
334 /* Start the server with path PGMNAME and connect its stdout and
335    strerr to a newly created pipes; the file descriptors are then
336    store in the gloabl variables SERVER_SEND_FD and
337    SERVER_RECV_FD. The initial handcheck is performed.*/
338 static void
339 start_server (const char *pgmname)
340 {
341   int rp[2];
342   int wp[2];
343   pid_t pid;
344
345   if (pipe (rp) < 0)
346     die ("pipe creation failed: %s", strerror (errno));
347   if (pipe (wp) < 0)
348     die ("pipe creation failed: %s", strerror (errno));
349
350   fflush (stdout);
351   fflush (stderr);
352   pid = fork ();
353   if (pid < 0)
354     die ("fork failed");
355
356   if (!pid)
357     {
358       const char *arg0;
359
360       arg0 = strrchr (pgmname, '/');
361       if (arg0)
362         arg0++;
363       else
364         arg0 = pgmname;
365
366       if (wp[0] != STDIN_FILENO)
367         {
368           if (dup2 (wp[0], STDIN_FILENO) == -1)
369               die ("dup2 failed in child: %s", strerror (errno));
370           close (wp[0]);
371         }
372       if (rp[1] != STDOUT_FILENO)
373         {
374           if (dup2 (rp[1], STDOUT_FILENO) == -1)
375               die ("dup2 failed in child: %s", strerror (errno));
376           close (rp[1]);
377         }
378
379       execl (pgmname, arg0, "--server", NULL); 
380       die ("exec failed for `%s': %s", pgmname, strerror (errno));
381     }
382   close (wp[0]);
383   close (rp[1]);
384   server_send_fd = wp[1];
385   server_recv_fd = rp[0];
386
387   read_assuan (server_recv_fd);
388   if (recv_type != LINE_OK)
389     die ("no greating message");
390 }
391
392
393
394
395 \f
396 /* Script intepreter. */
397
398 static void
399 unset_var (const char *name)
400 {
401   VARIABLE var;
402
403   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
404     ;
405   if (!var)
406     return;
407 /*    fprintf (stderr, "unsetting `%s'\n", name); */
408
409   if (var->is_fd && var->value)
410     {
411       int fd;
412
413       fd = atoi (var->value);
414       if (fd != -1 && fd != 0 && fd != 1 && fd != 2)
415           close (fd);
416     }
417
418   free (var->value);
419   var->value = NULL;
420   var->is_fd = 0;
421 }
422
423
424 static void
425 set_fd_var (const char *name, const char *value, int is_fd)
426 {
427   VARIABLE var;
428
429   if (!name)
430     name = "?"; 
431   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
432     ;
433   if (!var)
434     {
435       var = xcalloc (1, sizeof *var + strlen (name));
436       strcpy (var->name, name);
437       var->next = variable_list;
438       variable_list = var;
439     }
440   else
441     free (var->value);
442
443   if (var->is_fd && var->value)
444     {
445       int fd;
446
447       fd = atoi (var->value);
448       if (fd != -1 && fd != 0 && fd != 1 && fd != 2)
449           close (fd);
450     }
451   
452   var->is_fd = is_fd;
453   var->value = xstrdup (value);
454 /*    fprintf (stderr, "setting `%s' to `%s'\n", var->name, var->value); */
455
456 }
457
458 static void
459 set_var (const char *name, const char *value)
460 {
461   set_fd_var (name, value, 0);
462 }
463
464
465 static const char *
466 get_var (const char *name)
467 {
468   VARIABLE var;
469
470   for (var=variable_list; var && strcmp (var->name, name); var = var->next)
471     ;
472   return var? var->value:NULL;
473 }
474
475
476
477 /* Expand variables in LINE and return a new allocated buffer if
478    required.  The function might modify LINE if the expanded version
479    fits into it. */
480 static char *
481 expand_line (char *buffer)
482 {
483   char *line = buffer;
484   char *p, *pend;
485   const char *value;
486   size_t valuelen, n;
487   char *result = NULL;
488
489   while (*line)
490     {
491       p = strchr (line, '$');
492       if (!p)
493         return result; /* nothing more to expand */
494       
495       if (p[1] == '$') /* quoted */
496         {
497           memmove (p, p+1, strlen (p+1)+1);
498           line = p + 1;
499           continue;
500         }
501       for (pend=p+1; *pend && !spacep (pend) && *pend != '$'; pend++)
502         ;
503       if (*pend)
504         {
505           int save = *pend;
506           *pend = 0;
507           value = get_var (p+1);
508           *pend = save;
509         }
510       else
511         value = get_var (p+1);
512       if (!value)
513         value = "";
514       valuelen = strlen (value);
515       if (valuelen <= pend - p)
516         {
517           memcpy (p, value, valuelen);
518           p += valuelen;
519           n = pend - p;
520           if (n)
521             memmove (p, p+n, strlen (p+n)+1);
522           line = p;
523         }
524       else
525         {
526           char *src = result? result : buffer;
527           char *dst;
528
529           dst = xmalloc (strlen (src) + valuelen + 1);
530           n = p - src;
531           memcpy (dst, src, n);
532           memcpy (dst + n, value, valuelen);
533           n += valuelen;
534           strcpy (dst + n, pend);
535           line = dst + n;
536           free (result);
537           result = dst;
538         }
539     }
540   return result;
541 }
542
543
544 /* Evaluate COND and return the result. */
545 static int 
546 eval_boolean (const char *cond)
547 {
548   int true = 1;
549
550   for ( ; *cond == '!'; cond++)
551     true = !true;
552   if (!*cond || (*cond == '0' && !cond[1]))
553     return !true;
554   return true;
555 }
556
557
558
559
560 \f
561 static void
562 cmd_let (const char *assign_to, char *arg)
563 {
564   set_var (assign_to, arg);
565 }
566
567
568 static void
569 cmd_echo (const char *assign_to, char *arg)
570 {
571   printf ("%s\n", arg);
572 }
573
574 static void
575 cmd_send (const char *assign_to, char *arg)
576 {
577   if (opt_verbose)
578     fprintf (stderr, "sending `%s'\n", arg);
579   write_assuan (server_send_fd, arg); 
580 }
581
582 static void
583 cmd_expect_ok (const char *assign_to, char *arg)
584 {
585   if (opt_verbose)
586     fprintf (stderr, "expecting OK\n");
587   do
588     {
589       read_assuan (server_recv_fd);
590       if (opt_verbose)
591         fprintf (stderr, "got line `%s'\n", recv_line);
592     }
593   while (recv_type != LINE_OK && recv_type != LINE_ERR);
594   if (recv_type != LINE_OK)
595     die ("expected OK but got `%s'", recv_line);
596 }
597
598 static void
599 cmd_expect_err (const char *assign_to, char *arg)
600 {
601   if (opt_verbose)
602     fprintf (stderr, "expecting ERR\n");
603   do
604     {
605       read_assuan (server_recv_fd);
606       if (opt_verbose)
607         fprintf (stderr, "got line `%s'\n", recv_line);
608     }
609   while (recv_type != LINE_OK && recv_type != LINE_ERR);
610   if (recv_type != LINE_ERR)
611     die ("expected ERR but got `%s'", recv_line);
612 }
613
614 static void
615 cmd_openfile (const char *assign_to, char *arg)
616 {
617   int fd;
618   char numbuf[20];
619
620   do 
621     fd = open (arg, O_RDONLY);
622   while (fd == -1 && errno == EINTR);
623   if (fd == -1)
624     die ("error opening `%s': %s", arg, strerror (errno));
625   
626   sprintf (numbuf, "%d", fd);
627   set_fd_var (assign_to, numbuf, 1);
628 }
629
630 static void
631 cmd_createfile (const char *assign_to, char *arg)
632 {
633   int fd;
634   char numbuf[20];
635
636   do 
637     fd = open (arg, O_WRONLY|O_CREAT|O_TRUNC, 0666);
638   while (fd == -1 && errno == EINTR);
639   if (fd == -1)
640     die ("error creating `%s': %s", arg, strerror (errno));
641
642   sprintf (numbuf, "%d", fd);
643   set_fd_var (assign_to, numbuf, 1);
644 }
645
646
647 static void
648 cmd_pipeserver (const char *assign_to, char *arg)
649 {
650   if (!*arg)
651     arg = "../sm/gpgsm";
652
653   start_server (arg);
654 }
655
656
657 static void
658 cmd_quit_if(const char *assign_to, char *arg)
659 {
660   if (eval_boolean (arg))
661     exit (0);
662 }
663
664 static void
665 cmd_fail_if(const char *assign_to, char *arg)
666 {
667   if (eval_boolean (arg))
668     exit (1);
669 }
670
671
672 static void
673 cmd_cmpfiles (const char *assign_to, char *arg)
674 {
675   char *p = arg;
676   char *second;
677   FILE *fp1, *fp2;
678   char buffer1[2048]; /* note: both must be of equal size. */
679   char buffer2[2048];
680   size_t nread1, nread2;
681   int rc = 0;
682
683   set_var (assign_to, "0"); 
684   for (p=arg; *p && !spacep (p); p++)
685     ;
686   if (!*p)
687     die ("cmpfiles: syntax error");
688   for (*p++ = 0; spacep (p); p++)
689     ;
690   second = p;
691   for (; *p && !spacep (p); p++)
692     ;
693   if (*p)
694     {
695       for (*p++ = 0; spacep (p); p++)
696         ;
697       if (*p)
698         die ("cmpfiles: syntax error");
699     }
700   
701   fp1 = fopen (arg, "rb");
702   if (!fp1)
703     {
704       err ("can't open `%s': %s", arg, strerror (errno));
705       return;
706     }
707   fp2 = fopen (second, "rb");
708   if (!fp2)
709     {
710       err ("can't open `%s': %s", second, strerror (errno));
711       fclose (fp1);
712       return;
713     }
714   while ( (nread1 = fread (buffer1, 1, sizeof buffer1, fp1)))
715     {
716       if (ferror (fp1))
717         break;
718       nread2 = fread (buffer2, 1, sizeof buffer2, fp2);
719       if (ferror (fp2))
720         break;
721       if (nread1 != nread2 || memcmp (buffer1, buffer2, nread1))
722         {
723           rc = 1;
724           break;
725         }
726     }
727   if (feof (fp1) && feof (fp2) && !rc)
728     {
729       if (opt_verbose)
730         err ("files match");
731       set_var (assign_to, "1"); 
732     }
733   else if (!rc)
734     err ("cmpfiles: read error: %s", strerror (errno));
735   else
736     err ("cmpfiles: mismatch");
737   fclose (fp1);
738   fclose (fp2);
739 }
740
741 /* Process the current script line LINE. */
742 static int
743 interpreter (char *line)
744 {
745   static struct {
746     const char *name;
747     void (*fnc)(const char*, char*);
748   } cmdtbl[] = {
749     { "let"       , cmd_let },
750     { "echo"      , cmd_echo },
751     { "send"      , cmd_send },
752     { "expect-ok" , cmd_expect_ok },
753     { "expect-err", cmd_expect_err },
754     { "openfile"  , cmd_openfile },
755     { "createfile", cmd_createfile },
756     { "pipeserver", cmd_pipeserver },
757     { "quit"      , NULL },
758     { "quit-if"   , cmd_quit_if },
759     { "fail-if"   , cmd_fail_if },
760     { "cmpfiles"  , cmd_cmpfiles },
761     { NULL }
762   };
763   char *p, *save_p;
764   int i, save_c;
765   char *stmt = NULL;
766   char *assign_to = NULL;
767   char *must_free = NULL;
768
769   for ( ;spacep (line); line++)
770     ;
771   if (!*line || *line == '#')
772     return 0; /* empty or comment */
773   p = expand_line (line);
774   if (p)
775     {
776       must_free = p;
777       line = p;
778       for ( ;spacep (line); line++)
779         ;
780       if (!*line || *line == '#')
781         {
782           free (must_free);
783           return 0; /* empty or comment */
784         }
785     }
786
787   for (p=line; *p && !spacep (p) && *p != '='; p++)
788     ;
789   if (*p == '=')
790     {
791       *p = 0;
792       assign_to = line;
793     }
794   else if (*p)
795     {
796       for (*p++ = 0; spacep (p); p++)
797         ;
798       if (*p == '=')
799         assign_to = line;
800     }
801   if (!*line)
802     die ("syntax error");
803   stmt = line;
804   save_c = 0;
805   save_p = NULL;
806   if (assign_to)
807     { /* this is an assignment */
808       for (p++; spacep (p); p++)
809         ;
810       if (!*p)
811         {
812           unset_var (assign_to);
813           free (must_free);
814           return 0;
815         }
816       stmt = p;
817       for (; *p && !spacep (p); p++)
818         ;
819       if (*p)
820         {
821           save_p = p;
822           save_c = *p;
823           for (*p++ = 0; spacep (p);  p++)
824             ;
825         }
826     }
827   for (i=0; cmdtbl[i].name && strcmp (stmt, cmdtbl[i].name); i++)
828     ;
829   if (!cmdtbl[i].name)
830     {
831       if (!assign_to)
832         die ("invalid statement `%s'\n", stmt);
833       if (save_p)
834         *save_p = save_c;
835       set_var (assign_to, stmt);
836       free (must_free);
837       return 0;
838     }
839
840   if (cmdtbl[i].fnc)
841     cmdtbl[i].fnc (assign_to, p);
842   free (must_free);
843   return cmdtbl[i].fnc? 0:1;
844 }
845
846
847
848 int
849 main (int argc, char **argv)
850 {
851   char buffer[2048];
852   char *p, *pend;
853
854   if (!argc)
855     invocation_name = "asschk";
856   else
857     {
858       invocation_name = *argv++;
859       argc--;
860       p = strrchr (invocation_name, '/');
861       if (p)
862         invocation_name = p+1;
863     }
864
865
866   set_var ("?","1"); /* defaults to true */
867
868   for (; argc; argc--, argv++)
869     {
870       p = *argv;
871       if (*p != '-')
872         break;
873       if (!strcmp (p, "--verbose"))
874         opt_verbose = 1;
875       else if (*p == '-' && p[1] == 'D')
876         {
877           p += 2;
878           pend = strchr (p, '=');
879           if (pend)
880             {
881               int tmp = *pend;
882               *pend = 0;
883               set_var (p, pend+1);
884               *pend = tmp;
885             }
886           else
887             set_var (p, "1");
888         }
889       else if (*p == '-' && p[1] == '-' && !p[2])
890         {
891           argc--; argv++;
892           break;
893         }
894       else
895         break;
896     }
897   if (argc)
898     die ("usage: asschk [--verbose] {-D<name>[=<value>]}");
899
900
901   while (fgets (buffer, sizeof buffer, stdin))
902     {
903       p = strchr (buffer,'\n');
904       if (!p)
905         die ("incomplete script line");
906       *p = 0;
907       if (interpreter (buffer))
908         break;
909       fflush (stdout);
910     }
911   return 0;
912 }
913