/* --------------------------------------------------------------------------
* Copyright 2003-2011 (inclusive) Nathan Angelacos
* (nangel@users.sourceforge.net)
*
* This file is part of haserl.
*
* Haserl is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* Haserl 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 haserl. If not, see .
*
* ------------------------------------------------------------------------ */
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if HAVE_SIGNAL_H
#include
#endif
#include "common.h"
#include "h_error.h"
#include "h_script.h"
#include "h_bash.h"
#include "sliding_buffer.h"
#include "rfc2388.h"
#include "haserl.h"
void
empty_stdin (void)
{
char c[2000];
while (read (STDIN_FILENO, &c, 2000))
{
};
}
void
mime_var_init (mime_var_t * obj)
{
obj->name = NULL;
obj->filename = NULL;
obj->type = NULL;
obj->tempname = NULL;
haserl_buffer_init (&(obj->value));
obj->fh = 0;
}
void
mime_var_destroy (mime_var_t * obj)
{
int status;
struct sigaction new_action;
if (obj->name)
{
free (obj->name);
obj->name = NULL;
}
if (obj->filename)
{
free (obj->filename);
obj->filename = NULL;
}
if (obj->type)
{
free (obj->type);
obj->type = NULL;
}
if (obj->tempname)
{
free (obj->tempname);
obj->tempname = NULL;
}
buffer_destroy (&(obj->value));
if (obj->fh)
{
close (abs (obj->fh));
if (global.uploadhandler)
{
wait (&status);
new_action.sa_handler = SIG_DFL;
sigemptyset (&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction (SIGPIPE, &new_action, NULL);
}
obj->fh = 0;
}
}
char *
mime_substr (char *start, int len)
{
char *ptr;
if (!start)
return NULL;
if (len < 0)
return NULL;
ptr = xmalloc (len + 2);
memcpy (ptr, start, len);
return ptr;
}
void
mime_tag_add (mime_var_t * obj, char *str)
{
char *a = NULL;
char *b = NULL;
static char *tag[] = { "name=\"", "filename=\"", "Content-Type: " };
a = strcasestr (str, tag[0]);
if (a)
{
a += strlen (tag[0]);
b = strchr (a, '"');
if (!obj->name)
obj->name = mime_substr (a, b - a);
}
a = strcasestr (str, tag[1]);
if (a)
{
a += strlen (tag[1]);
b = strchr (a, '"');
if (!obj->filename)
obj->filename = mime_substr (a, b - a);
}
a = strcasestr (str, tag[2]);
if (a)
{
a += strlen (tag[2]);
b = a + strlen (a);
if (!obj->type)
obj->type = mime_substr (a, b - a);
}
}
void
mime_var_putenv (list_t * env, mime_var_t * obj)
{
buffer_t buf;
haserl_buffer_init (&buf);
if (obj->name)
{
/* For file uploads, this creates FORM_foo=tempfile_pathspec.
That name can be overwritten by a subsequent foo=/etc/passwd,
for instance. This code block is depricated for FILE uploads
only. (it is still valid for non-form uploads */
buffer_add (&(obj->value), "", 1);
buffer_add (&buf, obj->name, strlen (obj->name));
buffer_add (&buf, "=", 1);
buffer_add (&buf, (char *) obj->value.data,
strlen ((char *) obj->value.data) + 1);
myputenv (env, (char *) buf.data, global.var_prefix);
myputenv (env, (char *) buf.data, global.post_prefix);
buffer_reset (&buf);
}
if (obj->filename)
{
/* This creates HASERL_foo_path=tempfile_pathspec. */
buffer_add (&buf, obj->name, strlen (obj->name));
buffer_add (&buf, "_path=", 6);
buffer_add (&buf, (char *) obj->value.data,
strlen ((char *) obj->value.data) + 1);
myputenv (env, (char *) buf.data, global.haserl_prefix);
myputenv (env, (char *) buf.data, global.var_prefix);
myputenv (env, (char *) buf.data, global.post_prefix);
buffer_reset (&buf);
/* this saves the name of the file the client supplied */
buffer_add (&buf, obj->name, strlen (obj->name));
buffer_add (&buf, "_name=", 6);
buffer_add (&buf, obj->filename, strlen (obj->filename) + 1);
myputenv (env, (char *) buf.data, global.var_prefix);
myputenv (env, (char *) buf.data, global.post_prefix);
buffer_reset (&buf);
}
buffer_destroy (&buf);
}
void
mime_exec (mime_var_t * obj, char *fifo)
{
int pid;
char *av[4];
char *type, *filename, *name;
char *c;
int fh;
struct sigaction new_action;
pid = fork ();
if (pid == -1)
{
empty_stdin ();
die_with_message (NULL, NULL, g_err_msg[E_SUBSHELL_FAIL]);
}
if (pid == 0)
{
/* store the content type, filename, and form name */
/* we do not use global.var_prefix because it could be lua or shell
* or something else, and we are only shell here */
if (obj->type)
{
type = xmalloc (13 + strlen (obj->type) + 1);
sprintf (type, "CONTENT_TYPE=%s", obj->type);
putenv (type);
}
if (obj->filename)
{
filename = xmalloc (9 + strlen (obj->filename) + 1);
sprintf (filename, "FILENAME=%s", obj->filename);
putenv (filename);
}
if (obj->name)
{
name = xmalloc (5 + strlen (obj->name) + 1);
sprintf (name, "NAME=%s", obj->name);
putenv (name);
}
av[0] = global.uploadhandler;
av[1] = fifo;
av[2] = NULL;
execv (av[0], av);
/* if we get here, we had a failure. Not much we can do.
* We are the child, so we can't even warn the parent */
fh = open (fifo, O_RDONLY);
while (read (fh, &c, 1))
{
}
exit (-1);
}
else
{
/* I'm parent - ignore SIGPIPE from the child */
new_action.sa_handler = SIG_IGN;
sigemptyset (&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction (SIGPIPE, &new_action, NULL);
}
/* control should get to this point only in the parent.
*/
} /* end mime_exec */
void
mime_var_open_target (mime_var_t * obj)
{
char *tmpname;
token_t *curtoken;
curtoken = global.uploadlist;
int ok;
/* if upload_limit is zero, we die right here */
if (global.uploadkb == 0)
{
empty_stdin ();
die_with_message (NULL, NULL, "File uploads are not allowed.");
}
ok = -1;
tmpname = xmalloc (strlen (global.uploaddir) + 8);
strcpy (tmpname, global.uploaddir);
strcat (tmpname, "/XXXXXX");
obj->fh = mkstemp (tmpname);
if (obj->fh == -1)
{
ok = 0;
}
/* reuse the name as a fifo if we have a handler. We do this
* because tempnam uses TEMPDIR if defined, among other bugs
*/
if ((ok) && global.uploadhandler)
{
/* I have a handler */
close (obj->fh);
unlink (tmpname);
if (mkfifo (tmpname, 0600))
{
ok = 0;
}
/* you must open the fifo for reading before writing
* on non linux systems
*/
if (ok)
{
mime_exec (obj, tmpname);
obj->fh = open (tmpname, O_WRONLY);
}
if (obj->fh == -1)
ok = 0;
}
else
{
buffer_add (&(obj->value), tmpname, strlen (tmpname));
}
if (!ok)
{
empty_stdin ();
die_with_message (NULL, NULL, g_err_msg[E_FILE_OPEN_FAIL], tmpname);
}
curtoken =
push_token_on_list (curtoken, NULL, tmpname, strlen (tmpname) + 1);
if (global.uploadlist == NULL)
{
global.uploadlist = curtoken;
}
}
void
mime_var_writer (mime_var_t * obj, char *str, int len)
{
int err;
/* if not a file upload, then just a normal variable */
if (!obj->filename)
{
buffer_add (&(obj->value), str, len);
}
/* if a file upload, but don't have an open filehandle, open one */
if ((!obj->fh) && (obj->filename))
mime_var_open_target (obj);
/* if we have an open file, write the chunk */
if (obj->fh > 0)
{
err = write (obj->fh, str, len);
/* if there was an error, invert the filehandle; we need the
handle for later when we close it */
if (err == -1)
{
obj->fh = abs (obj->fh) * -1;
}
}
}
/*
* Read multipart/form-data input (RFC2388), typically used when
* uploading a file.
*/
int
rfc2388_handler (list_t * env)
{
enum mime_state_t
{ DISCARD, BOUNDARY, HEADER, CONTENT };
int state;
int i, x;
unsigned long max_len, content_length;
sliding_buffer_t sbuf;
char *crlf = "\r\n";
char *boundary;
char *str;
buffer_t buf;
mime_var_t var;
/* get the boundary info */
str = getenv ("CONTENT_TYPE");
i = strlen (str) - 9;
while ((i >= 0) && (memcmp ("boundary=", str + i, 9)))
{
i--;
}
if (i == -1)
{
empty_stdin ();
die_with_message (NULL, NULL, "No Mime Boundary Information Found");
}
i = i + 9;
if (str[i] == '"')
i++;
boundary = xmalloc (strlen (str + i) + 5); /* \r\n-- + NULL */
memcpy (boundary, crlf, 2);
memcpy (boundary + 2, "--", 2);
memcpy (boundary + 4, str + i, strlen (str + i) + 1);
if ((i > 0) && (str[i - 1] == '"'))
{
while ((boundary[i]) && (boundary[i] != '"'))
i++;
boundary[i] = '\0';
}
/* Allow 2MB content, unless they have a global upload set */
max_len = ((global.uploadkb == 0) ? 2048 : global.uploadkb) *1024;
content_length = 0;
/* initialize a 128K sliding buffer */
s_buffer_init (&sbuf, 1024 * 128);
sbuf.fh = STDIN;
if (getenv ("CONTENT_LENGTH"))
{
sbuf.maxread = strtoul (getenv ("CONTENT_LENGTH"), NULL, 10);
}
/* initialize the buffer, and make sure it doesn't point to null */
haserl_buffer_init (&buf);
buffer_add (&buf, "", 1);
buffer_reset (&buf);
state = DISCARD;
str = boundary + 2; /* skip the leading crlf */
do
{
/* x is true if this token ends with a matchstr or is at the end of stream */
x = s_buffer_read (&sbuf, str);
content_length += sbuf.len;
if (content_length >= max_len)
{
empty_stdin ();
free (boundary);
s_buffer_destroy (&sbuf);
buffer_destroy (&buf);
if (var.name)
{
mime_var_destroy (&var);
}
die_with_message (NULL, NULL,
"Attempted to send content larger than allowed limits.");
}
switch (state)
{
case DISCARD:
/* discard any text - used for first mime boundary */
if (x)
{
state = BOUNDARY;
str = crlf;
buffer_reset (&buf); /* reinitializes the buffer */
}
break;
case BOUNDARY:
if (!x)
{
buffer_add (&buf, sbuf.segment, sbuf.len);
}
if (x)
{
buffer_add (&buf, sbuf.segment, sbuf.len);
if (!memcmp (buf.data, boundary + 2, 2))
{ /* "--" */
/* all done... what does that mean? */
str = boundary + 2;
state = DISCARD;
}
else
{
buffer_reset (&buf);
mime_var_init (&var);
state = HEADER;
str = crlf;
}
}
break;
case HEADER:
buffer_add (&buf, sbuf.segment, sbuf.len);
if (x)
{
if (sbuf.len == 0)
{ /* blank line */
buffer_reset (&buf);
state = CONTENT;
str = boundary;
}
else
{
buffer_add (&buf, "", 1);
mime_tag_add (&var, (char *) buf.data);
buffer_reset (&buf);
}
}
break;
case CONTENT:
/* write to writer process, regardless */
mime_var_writer (&var, (char *) sbuf.segment, sbuf.len);
if (x)
{
buffer_reset (&buf);
mime_var_putenv (env, &var);
mime_var_destroy (&var);
state = BOUNDARY;
str = crlf;
}
break;
} /* end switch */
}
while (!sbuf.eof);
free (boundary);
s_buffer_destroy (&sbuf);
buffer_destroy (&buf);
return (0);
}