gnupg/tools/sockprox.c

552 lines
10 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* sockprox - Proxy for local sockets with logging facilities
* Copyright (C) 2007 g10 Code GmbH.
*
* sockprox 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 3 of the License, or
* (at your option) any later version.
*
* sockprox 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, see <https://www.gnu.org/licenses/>.
*/
/* Hacked by Moritz Schulte <moritz@g10code.com>.
Usage example:
Run a server which binds to a local socket. For example,
gpg-agent. gpg-agent's local socket is specified with --server.
sockprox opens a new local socket (here "mysock"); the whole
traffic between server and client is written to "/tmp/prot" in this
case.
./sockprox --server /tmp/gpg-PKdD8r/S.gpg-agent.ssh \
--listen mysock --protocol /tmp/prot
Then, redirect your ssh-agent client to sockprox by setting
SSH_AUTH_SOCK to "mysock".
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <assert.h>
#include <pthread.h>
struct opt
{
char *protocol_file;
char *server_spec;
char *listen_spec;
int verbose;
};
struct opt opt = { NULL, NULL, NULL, 0 };
struct thread_data
{
int client_sock;
FILE *protocol_file;
};
static int
create_server_socket (const char *filename, int *new_sock)
{
struct sockaddr_un name;
size_t size;
int sock;
int ret;
int err;
/* Create the socket. */
sock = socket (PF_LOCAL, SOCK_STREAM, 0);
if (sock < 0)
{
err = errno;
goto out;
}
/* Bind a name to the socket. */
name.sun_family = AF_LOCAL;
strncpy (name.sun_path, filename, sizeof (name.sun_path));
name.sun_path[sizeof (name.sun_path) - 1] = '\0';
size = SUN_LEN (&name);
remove (filename);
ret = bind (sock, (struct sockaddr *) &name, size);
if (ret < 0)
{
err = errno;
goto out;
}
ret = listen (sock, 2);
if (ret < 0)
{
err = errno;
goto out;
}
*new_sock = sock;
err = 0;
out:
return err;
}
static int
connect_to_socket (const char *filename, int *new_sock)
{
struct sockaddr_un srvr_addr;
size_t len;
int sock;
int ret;
int err;
sock = socket (PF_LOCAL, SOCK_STREAM, 0);
if (sock == -1)
{
err = errno;
goto out;
}
memset (&srvr_addr, 0, sizeof srvr_addr);
srvr_addr.sun_family = AF_LOCAL;
strncpy (srvr_addr.sun_path, filename, sizeof (srvr_addr.sun_path) - 1);
srvr_addr.sun_path[sizeof (srvr_addr.sun_path) - 1] = 0;
len = SUN_LEN (&srvr_addr);
ret = connect (sock, (struct sockaddr *) &srvr_addr, len);
if (ret == -1)
{
close (sock);
err = errno;
goto out;
}
*new_sock = sock;
err = 0;
out:
return err;
}
static int
log_data (unsigned char *data, size_t length,
FILE *from, FILE *to, FILE *protocol)
{
unsigned int i;
int ret;
int err;
flockfile (protocol);
fprintf (protocol, "%i -> %i: ", fileno (from), fileno (to));
for (i = 0; i < length; i++)
fprintf (protocol, "%02X", data[i]);
fprintf (protocol, "\n");
funlockfile (protocol);
ret = fflush (protocol);
if (ret == EOF)
err = errno;
else
err = 0;
return err;
}
static int
transfer_data (FILE *from, FILE *to, FILE *protocol)
{
unsigned char buffer[BUFSIZ];
size_t len, written;
int err;
int ret;
err = 0;
while (1)
{
len = fread (buffer, 1, sizeof (buffer), from);
if (len == 0)
break;
err = log_data (buffer, len, from, to, protocol);
if (err)
break;
written = fwrite (buffer, 1, len, to);
if (written != len)
{
err = errno;
break;
}
ret = fflush (to);
if (ret == EOF)
{
err = errno;
break;
}
if (ferror (from))
break;
}
return err;
}
static int
io_loop (FILE *client, FILE *server, FILE *protocol)
{
fd_set active_fd_set, read_fd_set;
int ret;
int err;
FD_ZERO (&active_fd_set);
FD_SET (fileno (client), &active_fd_set);
FD_SET (fileno (server), &active_fd_set);
err = 0;
while (1)
{
read_fd_set = active_fd_set;
/* FIXME: eof? */
ret = select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL);
if (ret < 0)
{
err = errno;
break;
}
if (FD_ISSET (fileno (client), &read_fd_set))
{
if (feof (client))
break;
/* Forward data from client to server. */
err = transfer_data (client, server, protocol);
}
else if (FD_ISSET (fileno (server), &read_fd_set))
{
if (feof (server))
break;
/* Forward data from server to client. */
err = transfer_data (server, client, protocol);
}
if (err)
break;
}
return err;
}
/* Set the 'O_NONBLOCK' flag of DESC if VALUE is nonzero,
or clear the flag if VALUE is 0.
Return 0 on success, or -1 on error with 'errno' set. */
int
set_nonblock_flag (int desc, int value)
{
int oldflags = fcntl (desc, F_GETFL, 0);
int err;
int ret;
/* If reading the flags failed, return error indication now. */
if (oldflags == -1)
return -1;
/* Set just the flag we want to set. */
if (value != 0)
oldflags |= O_NONBLOCK;
else
oldflags &= ~O_NONBLOCK;
/* Store modified flag word in the descriptor. */
ret = fcntl (desc, F_SETFL, oldflags);
if (ret == -1)
err = errno;
else
err = 0;
return err;
}
void *
serve_client (void *data)
{
struct thread_data *thread_data = data;
int client_sock = thread_data->client_sock;
int server_sock;
FILE *protocol = thread_data->protocol_file;
FILE *client;
FILE *server;
int err;
client = NULL;
server = NULL;
/* Connect to server. */
err = connect_to_socket (opt.server_spec, &server_sock);
if (err)
goto out;
/* Set IO mode to nonblicking. */
err = set_nonblock_flag (server_sock, 1);
if (err)
goto out;
client = fdopen (client_sock, "r+");
if (! client)
{
err = errno;
goto out;
}
server = fdopen (server_sock, "r+");
if (! server)
{
err = errno;
goto out;
}
err = io_loop (client, server, protocol);
out:
if (client)
fclose (client);
else
close (client_sock);
if (server)
fclose (server);
else
close (server_sock);
free (data);
return NULL;
}
static int
run_proxy (void)
{
int client_sock;
int my_sock;
int err;
struct sockaddr_un clientname;
size_t size;
pthread_t mythread;
struct thread_data *thread_data;
FILE *protocol_file;
pthread_attr_t thread_attr;
protocol_file = NULL;
err = pthread_attr_init (&thread_attr);
if (err)
goto out;
err = pthread_attr_setdetachstate (&thread_attr, PTHREAD_CREATE_DETACHED);
if (err)
goto out;
if (opt.protocol_file)
{
protocol_file = fopen (opt.protocol_file, "a");
if (! protocol_file)
{
err = errno;
goto out;
}
}
else
protocol_file = stdout;
err = create_server_socket (opt.listen_spec, &my_sock);
if (err)
goto out;
while (1)
{
/* Accept new client. */
size = sizeof (clientname);
client_sock = accept (my_sock,
(struct sockaddr *) &clientname,
&size);
if (client_sock < 0)
{
err = errno;
break;
}
/* Set IO mode to nonblicking. */
err = set_nonblock_flag (client_sock, 1);
if (err)
{
close (client_sock);
break;
}
/* Got new client -> handle in new process. */
thread_data = malloc (sizeof (*thread_data));
if (! thread_data)
{
err = errno;
break;
}
thread_data->client_sock = client_sock;
thread_data->protocol_file = protocol_file;
err = pthread_create (&mythread, &thread_attr, serve_client, thread_data);
if (err)
break;
}
if (err)
goto out;
/* ? */
out:
pthread_attr_destroy (&thread_attr);
if (protocol_file)
fclose (protocol_file); /* FIXME, err checking. */
return err;
}
static int
print_help (int ret)
{
printf ("Usage: sockprox [options] "
"--server SERVER-SOCKET --listen PROXY-SOCKET\n");
exit (ret);
}
int
main (int argc, char **argv)
{
struct option long_options[] =
{
{ "help", no_argument, 0, 'h' },
{ "verbose", no_argument, &opt.verbose, 1 },
{ "protocol", required_argument, 0, 'p' },
{ "server", required_argument, 0, 's' },
{ "listen", required_argument, 0, 'l' },
{ 0, 0, 0, 0 }
};
int ret;
int err;
int c;
while (1)
{
int opt_idx = 0;
c = getopt_long (argc, argv, "hvp:s:l:",
long_options, &opt_idx);
if (c == -1)
break;
switch (c)
{
case 0:
if (long_options[opt_idx].flag)
break;
printf ("option %s", long_options[opt_idx].name);
if (optarg)
printf (" with arg %s", optarg);
printf ("\n");
break;
case 'p':
opt.protocol_file = optarg;
break;
case 's':
opt.server_spec = optarg;
break;
case 'l':
opt.listen_spec = optarg;
break;
case 'v':
opt.verbose = 1;
break;
case 'h':
print_help (EXIT_SUCCESS);
break;
default:
abort ();
}
}
if (opt.verbose)
{
printf ("server: %s\n", opt.server_spec ? opt.server_spec : "");
printf ("listen: %s\n", opt.listen_spec ? opt.listen_spec : "");
printf ("protocol: %s\n", opt.protocol_file ? opt.protocol_file : "");
}
if (! (opt.server_spec && opt.listen_spec))
print_help (EXIT_FAILURE);
err = run_proxy ();
if (err)
{
fprintf (stderr, "run_proxy() failed: %s\n", strerror (err));
ret = EXIT_FAILURE;
}
else
/* ? */
ret = EXIT_SUCCESS;
return ret;
}
/*
Local Variables:
compile-command: "cc -Wall -g -o sockprox sockprox.c -lpthread"
End:
*/