/*
* Downloads server (chat version).
*
* NOTE: A simple downloads dpi that illustrates how to make a dpi-server.
*
* It uses wget to download a link. This has been tested with wget 1.8.1
* The server accepts multiple connections once it has been started.
* If there are no requests within 5 minutes it waits for all child processes
* to finish and then it exits.
*
* Copyright 2002-2004 Jorge Arellano Cid <jcid@dillo.org>
*
* This program 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.
*
*/
#include <config.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <sys/time.h>
#include <glib.h>
#include "../dpip/dpip.h"
#include "dpiutil.h"
/*
* Debugging macros
*/
#define _MSG(fmt...)
#define _CMSG(fmt...)
#define MSG(fmt...) g_print("[downloads dpi]: " fmt)
#define CMSG(fmt...) g_print("[downloads (child)]: " fmt)
pid_t origpid, fpid;
/*---------------------------------------------------------------------------*/
/*
* Make a new name and place it in 'dl_dest'.
*/
static void make_new_name(gchar **dl_dest, const gchar *url)
{
GString *gstr = g_string_new(*dl_dest);
gint idx = gstr->len;
if (gstr->str[idx - 1] != '/'){
g_string_append_c(gstr, '/');
++idx;
}
/* Use a mangled url as name */
g_string_append(gstr, url);
for ( ; idx < gstr->len; ++idx)
if (!isalnum(gstr->str[idx]))
gstr->str[idx] = '_';
/* free memory */
g_free(*dl_dest);
*dl_dest = gstr->str;
g_string_free(gstr, FALSE);
}
/*---------------------------------------------------------------------------*/
/*
* SIGCHLD handler
*/
static void sigchld(int sig)
{
MSG("received sigchld, pid=%d\n", origpid);
fflush(stderr);
while (waitpid(0, NULL, WNOHANG) > 0) {
}
}
/*
* Establish SIGCHLD handler
*/
static void est_sigchld(void)
{
struct sigaction act;
sigset_t block;
sigemptyset(&block);
sigaddset(&block, SIGCHLD);
act.sa_handler = sigchld;
act.sa_mask = block;
act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, NULL);
}
/*
* Read a single line from a socket and store it in a GString.
*/
static ssize_t readline(int socket, GString ** msg)
{
ssize_t st;
gchar buf[16384], *aux;
/* can't use fread() */
do
st = read(socket, buf, 16384);
while (st < 0 && errno == EINTR);
if (st == -1)
MSG("readline, %s\n", strerror(errno));
if (st > 0) {
aux = g_strndup(buf, (guint)st);
g_string_assign(*msg, aux);
g_free(aux);
} else {
g_string_assign(*msg, "");
}
return st;
}
/*!
* Main download process.
*/
int main(void)
{
int new_socket, ns;
socklen_t csz;
ssize_t rdlen;
struct sockaddr_un clnt_addr;
//char *wget_cmd = "wget --no-parent -t 1 -nc -k -nH --cut-dirs=30 -P";
//char *wget_cmd = "wget -t 1 -nH -P"; --doesn't rename, +other problems
char *wget_cmd = "wget -O - --load-cookies $HOME/.dillo/cookies.txt ";
char *url = NULL, *esc_url = NULL, *dl_dest = NULL, *cmd = NULL;
GString *gs_dl_cmd, *tag;
fd_set active_set, selected_set;
struct timeval tout;
sigset_t blockSC;
origpid = getpid();
fpid = origpid;
MSG("v1.1 started (pid=%u)\n", origpid);
fflush(stdout);
sigemptyset(&blockSC);
sigaddset(&blockSC, SIGCHLD);
est_sigchld();
csz = (socklen_t) sizeof(clnt_addr);
FD_ZERO(&active_set);
FD_SET(STDIN_FILENO, &active_set);
while (1) {
MSG("before select\n");
do {
/* exit if there are no download requests after this time */
tout.tv_sec = TOUT;
tout.tv_usec = 0;
selected_set = active_set;
ns = select(STDIN_FILENO + 1, &selected_set, NULL, NULL, &tout);
} while (ns == -1 && errno == EINTR);
MSG("after select\n");
if (ns == -1) {
MSG("select, %s\n", strerror(errno));
exit(1);
} else if (ns == 0) { /* exit if no download requests */
close(STDIN_FILENO);
printf("downloads server %d:Terminating.\n"
"Waiting for children to finish\n", origpid);
fflush(stdout);
/* BUG? Any further calls to downloads server will be queued by dpid
* until all the children have finished. This could be a long time */
while (waitpid(-1, NULL, 0) >= 0) {
}
printf("\n\nDL_SRV %d: EXITING\n", origpid);
fflush(stdout);
exit(0);
} else {
/* accept the request */
do {
new_socket = accept(STDIN_FILENO, (struct sockaddr *) &clnt_addr,
&csz);
} while (new_socket == -1 && errno == EINTR);
if (new_socket == -1) {
MSG("accept, %s\n", strerror(errno));
exit(1);
}
}
sigprocmask(SIG_BLOCK, &blockSC, NULL);
tag = g_string_new(NULL);
MSG("before readline\n");
rdlen = readline(new_socket, &tag);
MSG("after readline\n");
MSG("[%s]\n", tag->str);
if ((cmd = a_Dpip_get_attr(tag->str, (size_t)tag->len, "cmd")) == NULL) {
MSG("Failed to parse 'cmd' in %s\n", tag->str);
exit(1);
}
if (strcmp(cmd, "DpiBye") == 0) {
MSG("got DpiBye, terminating.\n");
exit(0);
}
if (strcmp(cmd, "download") != 0) {
MSG("unknown command: '%s'. Aborting.\n", cmd);
exit(1);
}
g_free(cmd);
fpid = fork();
if (fpid == 0) {
pid_t ppid, cpid;
FILE *in_stream, *out_stream;
gchar buf[4096];
struct stat sb;
size_t n;
origpid = cpid = getpid();
ppid = getppid();
CMSG("pid=%u, from parent=%u\n", (unsigned)cpid, (unsigned)ppid);
if (!(url = a_Dpip_get_attr(tag->str,(size_t)tag->len, "url"))){
CMSG("Failed to parse 'url' in %s\n", tag->str);
exit(1);
}
dl_dest = a_Dpip_get_attr(tag->str, (size_t)tag->len, "destination");
if (dl_dest == NULL) {
CMSG("Failed to parse 'destination' in %s\n", tag->str);
exit(1);
}
CMSG("url=%s, dl_dest=%s\n", url, dl_dest);
/* 'dl_dest' may be a directory */
if (stat(dl_dest, &sb) == 0 && S_ISDIR(sb.st_mode))
make_new_name(&dl_dest, url);
/* open the target stream */
if ((out_stream = fopen(dl_dest, "w")) == NULL) {
CMSG("%s\n", strerror(errno));
exit(1);
}
/* make the download command string */
gs_dl_cmd = g_string_new(NULL);
/* escape "'" character for the shell */
esc_url = Escape_uri_str(url, "'");
/* avoid malicious SMTP relaying with FTP urls */
if (g_strncasecmp(esc_url, "ftp:/", 5) == 0)
Filter_smtp_hack(esc_url);
g_string_sprintf(gs_dl_cmd, "%s '%s'", wget_cmd, esc_url);
CMSG(" cmd: %s\n", gs_dl_cmd->str);
CMSG(" to: %s\n", dl_dest);
g_free(dl_dest);
g_free(esc_url);
g_free(url);
CMSG("pid=%u, Running: %s\n", cpid, gs_dl_cmd->str);
/* fork through popen */
if ((in_stream = popen(gs_dl_cmd->str, "r")) == NULL) {
CMSG("popen, %s\n", strerror(errno));
exit(1);
}
/* do the file transfer */
while ((n = fread (buf, 1, 4096, in_stream)) > 0)
fwrite(buf, 1, n, out_stream);
/* close transfer */
if (pclose(in_stream) != 0)
CMSG("pclose, %s\n", strerror(errno));
if (fclose(out_stream) != 0)
CMSG("fclose, %s\n", strerror(errno));
g_string_free(gs_dl_cmd, TRUE);
if (close(new_socket) == -1) {
CMSG("close, %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
CMSG("pid=%u, done!\n", cpid);
exit(0);
}
g_string_free(tag, TRUE);
sigprocmask(SIG_UNBLOCK, &blockSC, NULL);
close(new_socket);
}
}