diff --git a/tests/ChangeLog b/tests/ChangeLog index e69de29bb..f1a9d87cb 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -0,0 +1,16 @@ +2002-08-06 Werner Koch + + * Makefile.am, asschk.c: New. + + + Copyright 2002 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 000000000..59740ef9f --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,33 @@ +# Makefile.am -tests makefile for libxtime +# Copyright (C) 2002 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +## Process this file with automake to produce Makefile.in + +TESTS_ENVIRONMENT = + +EXTRA_DIST = + +TESTS = + +noinst_PROGRAMS = asschk $(TESTS) + +asschk_SOURCES = asschk.c + + + diff --git a/tests/asschk.c b/tests/asschk.c new file mode 100644 index 000000000..666c637c9 --- /dev/null +++ b/tests/asschk.c @@ -0,0 +1,689 @@ +/* asschk.c - Assuan Server Checker + * Copyright (C) 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* This is a simple stand-alone Assuan server test program. We don't + want to use the assuan library because we don't want to hide errors + in that library. + + The script language is line based. Empty lines or lines containing + only white spaces are ignored, line with a hash sign as first non + white space character are treated as comments. + + A simple macro mechanism is implemnted. Macros are expanded before + a line is processed but after comment processing. Macros are only + expanded once and non existing macros expand to the empty string. + A macro is dereferenced by prefixing its name with a dollar sign; + the end of the name is currently indicated by a white space. To + use a dollor sign verbatim, double it. + + A macro is assigned by prefixing a statement with the macro name + and an equal sign. The value is assigned verbatim if it does not + resemble a command, otherwise the return value of the command will + get assigned. The command "let" may be used to assign values + unambigiously and it should be used if the value starts with a + letter. + + The following commands are available: + + [ =] let + Assign VALUE to the macro named NAME. + + [ =] openfile + Open file FILENAME for read access and store the file descriptor + in NAME. + + [ =] createfile + Create file FILENAME and open for write access, store the file + descriptor in NAME. + + pipeserver [] + Connect to an Assuan server with name PATH. If PATH is not + specified the value ../sm/gpgsm is used. + + send + Send LINE to the server. + + expect-ok + Expect an OK response from the server. Status and data out put + is ignored. + + expect-err + Expect an ERR response from the server. Status and data out put + is ignored. + + quit + Terminate the program + + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PGMNAME "asschk" + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) +# define ATTR_PRINTF(f,a) __attribute__ ((format (printf,f,a))) +#else +# define ATTR_PRINTF(f,a) +#endif + +#define spacep(p) (*(p) == ' ' || *(p) == '\t') + +typedef enum { + LINE_OK = 0, + LINE_ERR, + LINE_STAT, + LINE_DATA, + LINE_END, +} LINETYPE; + + +struct variable_s { + struct variable_s *next; + char *value; + char name[1]; +}; +typedef struct variable_s *VARIABLE; + + + +static void die (const char *format, ...) ATTR_PRINTF(1,2); + + +/* File descriptors used to communicate with the current server. */ +static int server_send_fd = -1; +static int server_recv_fd = -1; + +/* The Assuan protocol limits the line length to 1024, so we can + safely use a (larger) buffer. The buffer is filled using the + read_assuan(). */ +static char recv_line[2048]; +/* Tell the status of the current line. */ +static LINETYPE recv_type; + +/* This is our variable storage. */ +static VARIABLE variable_list; + + +static void +die (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); + fprintf (stderr, "%s: ", PGMNAME); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + putc ('\n', stderr); + + exit (1); +} + +static void * +xmalloc (size_t n) +{ + void *p = malloc (n); + if (!p) + die ("out of core"); + return p; +} + +static void * +xcalloc (size_t n, size_t m) +{ + void *p = calloc (n, m); + if (!p) + die ("out of core"); + return p; +} + +static char * +xstrdup (const char *s) +{ + char *p = xmalloc (strlen (s)+1); + strcpy (p, s); + return p; +} + + +/* Write LENGTH bytes from BUFFER to FD. */ +static int +writen (int fd, const char *buffer, size_t length) +{ + while (length) + { + int nwritten = write (fd, buffer, length); + + if (nwritten < 0) + { + if (errno == EINTR) + continue; + return -1; /* write error */ + } + length -= nwritten; + buffer += nwritten; + } + return 0; /* okay */ +} + + + + +/* Assuan specific stuff. */ + +/* Read a line from FD, store it in the global recv_line, analyze the + type and store that in recv_type. The function terminates on a + communication error. Returns a pointer into the inputline to the + first byte of the arguments. The parsing is very strict to match + excalty what we want to send. */ +static char * +read_assuan (int fd) +{ + size_t nleft = sizeof recv_line; + char *buf = recv_line; + char *p; + int nread = 0; + + while (nleft > 0) + { + int n = read (fd, buf, nleft); + if (n < 0) + { + if (errno == EINTR) + continue; + die ("reading fd %d failed: %s", fd, strerror (errno)); + } + else if (!n) + die ("received incomplete line on fd %d", fd); + p = buf; + nleft -= n; + buf += n; + nread += n; + + for (; n && *p != '\n'; n--, p++) + ; + if (n) + { + /* fixme: keep pending bytes for next read. */ + break; + } + } + if (!nleft) + die ("received line too large"); + assert (nread>0); + recv_line[nread-1] = 0; + + p = recv_line; + if (p[0] == 'O' && p[1] == 'K' && (p[2] == ' ' || !p[2])) + { + recv_type = LINE_OK; + p += 3; + } + else if (p[0] == 'E' && p[1] == 'R' && p[2] == 'R' + && (p[3] == ' ' || !p[3])) + { + recv_type = LINE_ERR; + p += 4; + } + else if (p[0] == 'S' && (p[1] == ' ' || !p[1])) + { + recv_type = LINE_STAT; + p += 2; + } + else if (p[0] == 'D' && p[1] == ' ') + { + recv_type = LINE_DATA; + p += 2; + } + else if (p[0] == 'E' && p[1] == 'N' && p[2] == 'D' && !p[3]) + { + recv_type = LINE_END; + p += 3; + } + else + die ("invalid line type (%.5s)", p); + + return p; +} + +/* Write LINE to the server using FD. It is expected that the line + contains the terminating linefeed as last character. */ +static void +write_assuan (int fd, const char *line) +{ + char buffer[1026]; + size_t n = strlen (line); + + if (n > 1024) + die ("line too long for Assuan protocol"); + strcpy (buffer, line); + if (!n || buffer[n-1] != '\n') + buffer[n++] = '\n'; + + if (writen (fd, buffer, n)) + die ("sending line to %d failed: %s", fd, strerror (errno)); +} + + +/* Start the server with path PGMNAME and connect its stdout and + strerr to a newly created pipes; the file descriptors are then + store in the gloabl variables SERVER_SEND_FD and + SERVER_RECV_FD. The initial handcheck is performed.*/ +static void +start_server (const char *pgmname) +{ + int rp[2]; + int wp[2]; + pid_t pid; + + if (pipe (rp) < 0) + die ("pipe creation failed: %s", strerror (errno)); + if (pipe (wp) < 0) + die ("pipe creation failed: %s", strerror (errno)); + + fflush (stdout); + fflush (stderr); + pid = fork (); + if (pid < 0) + die ("fork failed"); + + if (!pid) + { + const char *arg0; + + arg0 = strrchr (pgmname, '/'); + if (!arg0) + arg0 = pgmname; + + if (wp[0] != STDIN_FILENO) + { + if (dup2 (wp[0], STDIN_FILENO) == -1) + die ("dup2 failed in child: %s", strerror (errno)); + close (wp[0]); + } + if (rp[1] != STDOUT_FILENO) + { + if (dup2 (rp[1], STDOUT_FILENO) == -1) + die ("dup2 failed in child: %s", strerror (errno)); + close (rp[1]); + } + + execl (pgmname, arg0, "--server", NULL); + die ("exec failed for `%s': %s", pgmname, strerror (errno)); + } + close (wp[0]); + close (rp[1]); + server_send_fd = wp[1]; + server_recv_fd = rp[0]; + + read_assuan (server_recv_fd); + if (recv_type != LINE_OK) + die ("no greating message"); +} + + + + + +/* Script intepreter. */ + +static void +unset_var (const char *name) +{ + VARIABLE var; + + for (var=variable_list; var && strcmp (var->name, name); var = var->next) + ; + if (!var) + return; + fprintf (stderr, "unsetting `%s'\n", name); + + free (var->value); + var->value = NULL; +} + + +static void +set_var (const char *name, const char *value) +{ + VARIABLE var; + + for (var=variable_list; var && strcmp (var->name, name); var = var->next) + ; + if (!var) + { + var = xcalloc (1, sizeof *var + strlen (name)); + strcpy (var->name, name); + var->next = variable_list; + variable_list = var; + } + else + free (var->value); + + var->value = xstrdup (value); + fprintf (stderr, "setting `%s' to `%s'\n", var->name, var->value); + +} + +static const char * +get_var (const char *name) +{ + VARIABLE var; + + for (var=variable_list; var && strcmp (var->name, name); var = var->next) + ; + return var? var->value:NULL; +} + + + +/* Expand variables in LINE and return a new allocated buffer if + required. The function might modify LINE if the expanded version + fits into it. */ +static char * +expand_line (char *buffer) +{ + char *line = buffer; + char *p, *pend; + const char *value; + size_t valuelen, n; + char *result = NULL; + + while (*line) + { + p = strchr (line, '$'); + if (!p) + return result; /* nothing more to expand */ + + if (p[1] == '$') /* quoted */ + { + memmove (p, p+1, strlen (p+1)+1); + line = p + 1; + continue; + } + for (pend=p+1; *pend && !spacep (pend) && *pend != '$'; pend++) + ; + if (*pend) + { + int save = *pend; + *pend = 0; + value = get_var (p+1); + *pend = save; + } + else + value = get_var (p+1); + if (!value) + value = ""; + valuelen = strlen (value); + if (valuelen <= pend - p) + { + memcpy (p, value, valuelen); + p += valuelen; + n = pend - p; + if (n) + memmove (p, p+n, strlen (p+n)+1); + line = p; + } + else + { + char *src = result? result : buffer; + char *dst; + + dst = xmalloc (strlen (src) + valuelen + 1); + n = p - src; + memcpy (dst, src, n); + memcpy (dst + n, value, valuelen); + n += valuelen; + strcpy (dst + n, pend); + line = dst + n; + free (result); + result = dst; + } + } + return result; +} + +static void +cmd_let (const char *assign_to, char *arg) +{ + if (!assign_to) + die ("syntax error: \"let\" needs an assignment"); + set_var (assign_to, arg); +} + + +static void +cmd_send (const char *assign_to, char *arg) +{ + fprintf (stderr, "sending `%s'\n", arg); + write_assuan (server_send_fd, arg); +} + +static void +cmd_expect_ok (const char *assign_to, char *arg) +{ + fprintf (stderr, "expecting OK\n"); + do + { + read_assuan (server_recv_fd); + fprintf (stderr, "got line `%s'\n", recv_line); + } + while (recv_type != LINE_OK && recv_type != LINE_ERR); + if (recv_type != LINE_OK) + die ("expected OK but got `%s'", recv_line); +} + +static void +cmd_expect_err (const char *assign_to, char *arg) +{ + do + { + read_assuan (server_recv_fd); + fprintf (stderr, "got line `%s'\n", recv_line); + } + while (recv_type != LINE_OK && recv_type != LINE_ERR); + if (recv_type != LINE_ERR) + die ("expected ERR but got `%s'", recv_line); +} + +static void +cmd_openfile (const char *assign_to, char *arg) +{ + int fd; + + do + fd = open (arg, O_RDONLY); + while (fd == -1 && errno == EINTR); + if (fd == -1) + die ("error opening `%s': %s", arg, strerror (errno)); + if (assign_to) + { + char numbuf[20]; + + sprintf (numbuf, "%d", fd); + set_var (assign_to, numbuf); + } +} + +static void +cmd_createfile (const char *assign_to, char *arg) +{ + int fd; + + do + fd = open (arg, O_WRONLY|O_CREAT|O_TRUNC, 0666); + while (fd == -1 && errno == EINTR); + if (fd == -1) + die ("error creating `%s': %s", arg, strerror (errno)); + if (assign_to) + { + char numbuf[20]; + + sprintf (numbuf, "%d", fd); + set_var (assign_to, numbuf); + } +} + + +static void +cmd_pipeserver (const char *assign_to, char *arg) +{ + if (!*arg) + arg = "../sm/gpgsm"; + + start_server (arg); +} + + +/* Process the current script line LINE. */ +static int +interpreter (char *line) +{ + static struct { + const char *name; + void (*fnc)(const char*, char*); + } cmdtbl[] = { + { "let" , cmd_let }, + { "send" , cmd_send }, + { "expect-ok" , cmd_expect_ok }, + { "expect-err", cmd_expect_err }, + { "openfile" , cmd_openfile }, + { "createfile", cmd_createfile }, + { "pipeserver", cmd_pipeserver }, + { "quit" , NULL }, + { NULL } + }; + char *p, *save_p; + int i, save_c; + char *stmt = NULL; + char *assign_to = NULL; + char *must_free = NULL; + + for ( ;spacep (line); line++) + ; + if (!*line || *line == '#') + return 0; /* empty or comment */ + p = expand_line (line); + if (p) + { + must_free = p; + line = p; + for ( ;spacep (line); line++) + ; + if (!*line || *line == '#') + { + free (must_free); + return 0; /* empty or comment */ + } + } + + for (p=line; *p && !spacep (p) && *p != '='; p++) + ; + if (*p == '=') + { + *p = 0; + assign_to = line; + } + else if (*p) + { + for (*p++ = 0; spacep (p); p++) + ; + if (*p == '=') + assign_to = line; + } + if (!*line) + die ("syntax error"); + stmt = line; + save_c = 0; + save_p = NULL; + if (assign_to) + { /* this is an assignment */ + for (p++; spacep (p); p++) + ; + if (!*p) + { + unset_var (assign_to); + free (must_free); + return 0; + } + stmt = p; + for (; *p && !spacep (p); p++) + ; + if (*p) + { + save_p = p; + save_c = *p; + for (*p++ = 0; spacep (p); p++) + ; + } + } + for (i=0; cmdtbl[i].name && strcmp (stmt, cmdtbl[i].name); i++) + ; + if (!cmdtbl[i].name) + { + if (!assign_to) + die ("invalid statement `%s'\n", stmt); + if (save_p) + *save_p = save_c; + set_var (assign_to, stmt); + free (must_free); + return 0; + } + + if (cmdtbl[i].fnc) + cmdtbl[i].fnc (assign_to, p); + free (must_free); + return cmdtbl[i].fnc? 0:1; +} + + + +int +main (int argc, char **argv) +{ + char buffer[1025]; + char *p; + + if (argc) + argv++, argc--; + if (argc) + die ("usage: asschk