+/*
+ * $Id$
+ *
+ * Session proxy for Citadel PHP bindings
+ *
+ * This is an unfinished session proxy ... it is a C version of the
+ * session proxy implemented in sessionproxy.php ... that version is pure PHP
+ * so we should probably stick with it as long as it works.
+ *
+ * Copyright (c) 2003 by Art Cancro <ajc@uncensored.citadel.org>
+ * This program is released under the terms of the GNU General Public License.
+ */
+
+#define SIZ 4096
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
+#include <string.h>
+#include <pwd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <pthread.h>
+#include <signal.h>
+
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+
+void timeout(int signum)
+{
+ fprintf(stderr, "Connection timed out.\n");
+ exit(3);
+}
+
+
+/*
+ * Connect a unix domain socket
+ */
+int uds_connectsock(char *sockpath)
+{
+ struct sockaddr_un addr;
+ int s;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
+
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s < 0) {
+ fprintf(stderr, "Can't create socket: %s\n",
+ strerror(errno));
+ return(-1);
+ }
+
+ if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ fprintf(stderr, "Can't connect: %s\n",
+ strerror(errno));
+ close(s);
+ return(-1);
+ }
+
+ return s;
+}
+
+
+/*
+ * Connect a TCP/IP socket
+ */
+int tcp_connectsock(char *host, char *service)
+{
+ struct hostent *phe;
+ struct servent *pse;
+ struct protoent *ppe;
+ struct sockaddr_in sin;
+ int s;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+
+ pse = getservbyname(service, "tcp");
+ if (pse) {
+ sin.sin_port = pse->s_port;
+ } else if ((sin.sin_port = htons((u_short) atoi(service))) == 0) {
+ fprintf(stderr, "Can't get %s service entry\n", service);
+ return (-1);
+ }
+ phe = gethostbyname(host);
+ if (phe) {
+ memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
+ } else if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
+ fprintf(stderr, "Can't get %s host entry: %s\n",
+ host, strerror(errno));
+ return (-1);
+ }
+ if ((ppe = getprotobyname("tcp")) == 0) {
+ fprintf(stderr, "Can't get TCP protocol entry: %s\n",
+ strerror(errno));
+ return (-1);
+ }
+
+ s = socket(PF_INET, SOCK_STREAM, ppe->p_proto);
+ if (s < 0) {
+ fprintf(stderr, "Can't create socket: %s\n", strerror(errno));
+ return (-1);
+ }
+ signal(SIGALRM, timeout);
+ alarm(30);
+
+ if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
+ fprintf(stderr, "Can't connect to %s.%s: %s\n",
+ host, service, strerror(errno));
+ close(s);
+ return (-1);
+ }
+ alarm(0);
+ signal(SIGALRM, SIG_IGN);
+
+ return (s);
+}
+
+
+
+
+/*
+ * Input binary data from socket
+ */
+int sock_read(int sock, char *buf, int bytes)
+{
+ int len, rlen;
+
+ len = 0;
+ while (len < bytes) {
+ rlen = read(sock, &buf[len], bytes - len);
+ if (rlen < 1) {
+ fprintf(stderr, "Server connection broken: %s\n",
+ strerror(errno));
+ return(-1);
+ }
+ len = len + rlen;
+ }
+ return(len);
+}
+
+
+/*
+ * input string from pipe
+ */
+int sock_gets(int sock, char *strbuf)
+{
+ int ch, len;
+ char buf[2];
+
+ len = 0;
+ strcpy(strbuf, "");
+ do {
+ if (sock_read(sock, &buf[0], 1) < 0) return(-1);
+ ch = buf[0];
+ strbuf[len++] = ch;
+ } while ((ch != 10) && (ch != 0) && (len < (SIZ-1)));
+ if (strbuf[len-1] == 10) strbuf[--len] = 0;
+ if (strbuf[len-1] == 13) strbuf[--len] = 0;
+ return(len);
+}
+
+
+
+/*
+ * send binary to server
+ */
+int sock_write(int sock, char *buf, int nbytes)
+{
+ int bytes_written = 0;
+ int retval;
+ while (bytes_written < nbytes) {
+ retval = write(sock, &buf[bytes_written],
+ nbytes - bytes_written);
+ if (retval < 1) {
+ fprintf(stderr, "Server connection broken: %s\n",
+ strerror(errno));
+ return(-1);
+ }
+ bytes_written = bytes_written + retval;
+ }
+ return(bytes_written);
+}
+
+
+/*
+ * send line to server
+ */
+int sock_puts(int sock, char *string)
+{
+ char buf[SIZ];
+
+ sprintf(buf, "%s\n", string);
+ return sock_write(sock, buf, strlen(buf));
+}
+
+
+/*
+ * Create a Unix domain socket and listen on it
+ */
+int ig_uds_server(char *sockpath, int queue_len)
+{
+ struct sockaddr_un addr;
+ int s;
+ int i;
+ int actual_queue_len;
+
+ actual_queue_len = queue_len;
+ if (actual_queue_len < 5) actual_queue_len = 5;
+
+ i = unlink(sockpath);
+ if (i != 0) if (errno != ENOENT) {
+ fprintf(stderr, "can't unlink %s: %s\n",
+ sockpath, strerror(errno));
+ return(-1);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
+
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s < 0) {
+ fprintf(stderr, "Can't create a socket: %s\n",
+ strerror(errno));
+ return(-1);
+ }
+
+ if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ fprintf(stderr, "Can't bind: %s\n",
+ strerror(errno));
+ return(-1);
+ }
+
+ if (listen(s, actual_queue_len) < 0) {
+ fprintf(stderr, "Can't listen: %s\n", strerror(errno));
+ return(-1);
+ }
+
+ chmod(sockpath, 0700); /* only me me me can talk to this */
+ return(s);
+}
+
+
+
+/*
+ * main loop
+ */
+int main(int argc, char **argv) {
+
+ char buf[SIZ];
+ char dbuf[SIZ];
+ int i, f;
+ int ctdl_sock;
+ int listen_sock;
+ int cmd_sock;
+
+ /* Fail if we weren't supplied with the right number of arguments
+ */
+ if (argc != 2) {
+ exit(1);
+ }
+
+ /* Fail if we can't connect to Citadel
+ */
+ ctdl_sock = uds_connectsock("/appl/citadel/citadel.socket");
+ if (ctdl_sock < 0) {
+ exit(2);
+ }
+
+ /* Fail if we can't read the server greeting message
+ */
+ if (sock_gets(ctdl_sock, buf) < 0) {
+ exit(3);
+ }
+
+ /* Fail if the server isn't giving us an error-free startup
+ */
+ if (buf[0] != '2') {
+ exit(4);
+ }
+
+ /* Now we're solid with the Citadel server. Nice. It's time to
+ * open our proxy socket so PHP can talk to us. Fail if we can't
+ * set this up.
+ */
+ listen_sock = ig_uds_server(argv[1], 5);
+ if (listen_sock < 0) {
+ close(ctdl_sock);
+ exit(5);
+ }
+
+ /* The socket is ready to listen for connections, so it's time for
+ * this program to go into the background. Fork, then close all file
+ * descriptors so that the PHP script that called us can continue
+ * its processing.
+ */
+ f = fork();
+ if (f < 0) {
+ close(ctdl_sock);
+ close(listen_sock);
+ unlink(argv[1]);
+ exit(6);
+ }
+ if (f != 0) {
+ exit(0);
+ }
+ if (f == 0) {
+ setpgrp();
+ for (i=0; i<256; ++i) {
+ /* Close fd's so PHP doesn't get all, like, whatever */
+ if ( (i != ctdl_sock) && (i != listen_sock) ) {
+ close(i);
+ }
+ }
+ }
+
+ /* Listen for connections. */
+
+ signal(SIGPIPE, SIG_IGN);
+ while (cmd_sock = accept(listen_sock, NULL, 0), cmd_sock >= 0) {
+
+ while (sock_gets(cmd_sock, buf) >= 0) {
+ if (sock_puts(ctdl_sock, buf) < 0) goto CTDL_BAIL;
+ if (sock_gets(ctdl_sock, buf) < 0) goto CTDL_BAIL;
+ sock_puts(cmd_sock, buf);
+
+ if (buf[0] == '1') do {
+ if (sock_gets(ctdl_sock, dbuf) < 0) {
+ goto CTDL_BAIL;
+ }
+ sock_puts(cmd_sock, dbuf);
+ } while (strcmp(dbuf, "000"));
+
+ else if (buf[0] == '4') do {
+ sock_gets(cmd_sock, dbuf);
+ if (sock_puts(ctdl_sock, dbuf) < 0) {
+ goto CTDL_BAIL;
+ }
+ } while (strcmp(dbuf, "000"));
+
+ }
+ close(cmd_sock);
+ }
+
+CTDL_BAIL:
+ /* Clean up and go away.
+ */
+ close(ctdl_sock);
+ close(listen_sock);
+ unlink(argv[1]);
+ exit(0);
+}