#include "cgi-private.h"
#include <errno.h>
#include <cups/http.h>
#include <cups/md5.h>
#define CUPS_SID "org.cups.sid"
typedef struct
{
const char *name;
int nvalues,
avalues;
const char **values;
} _cgi_var_t;
static int num_cookies = 0;
static cups_option_t *cookies = NULL;
static int form_count = 0,
form_alloc = 0;
static _cgi_var_t *form_vars = NULL;
static cgi_file_t *form_file = NULL;
static void cgi_add_variable(const char *name, int element,
const char *value);
static int cgi_compare_variables(const _cgi_var_t *v1,
const _cgi_var_t *v2);
static _cgi_var_t *cgi_find_variable(const char *name);
static void cgi_initialize_cookies(void);
static int cgi_initialize_get(void);
static int cgi_initialize_multipart(const char *boundary);
static int cgi_initialize_post(void);
static int cgi_initialize_string(const char *data);
static const char *cgi_passwd(const char *prompt);
static const char *cgi_set_sid(void);
static void cgi_sort_variables(void);
static void cgi_unlink_file(void);
int
cgiCheckVariables(const char *names)
{
char name[255],
*s;
const char *val;
int element;
if (names == NULL)
return (1);
while (*names != '\0')
{
while (*names == ' ' || *names == ',')
names ++;
for (s = name; *names != '\0' && *names != ' ' && *names != ','; s ++, names ++)
*s = *names;
*s = 0;
if (name[0] == '\0')
break;
if ((s = strrchr(name, '-')) != NULL)
{
*s = '\0';
element = atoi(s + 1) - 1;
val = cgiGetArray(name, element);
}
else
val = cgiGetVariable(name);
if (val == NULL)
return (0);
if (*val == '\0')
return (0);
}
return (1);
}
void
cgiClearVariables(void)
{
int i, j;
_cgi_var_t *v;
for (v = form_vars, i = form_count; i > 0; v ++, i --)
{
_cupsStrFree(v->name);
for (j = 0; j < v->nvalues; j ++)
if (v->values[j])
_cupsStrFree(v->values[j]);
}
form_count = 0;
cgi_unlink_file();
}
const char *
cgiGetArray(const char *name,
int element)
{
_cgi_var_t *var;
if ((var = cgi_find_variable(name)) == NULL)
return (NULL);
if (element < 0 || element >= var->nvalues)
return (NULL);
return (_cupsStrRetain(var->values[element]));
}
const char *
cgiGetCookie(const char *name)
{
return (cupsGetOption(name, num_cookies, cookies));
}
const cgi_file_t *
cgiGetFile(void)
{
return (form_file);
}
int
cgiGetSize(const char *name)
{
_cgi_var_t *var;
if ((var = cgi_find_variable(name)) == NULL)
return (0);
return (var->nvalues);
}
const char *
cgiGetVariable(const char *name)
{
const _cgi_var_t *var;
var = cgi_find_variable(name);
#ifdef DEBUG
if (var == NULL)
DEBUG_printf(("cgiGetVariable(\"%s\") is returning NULL...\n", name));
else
DEBUG_printf(("cgiGetVariable(\"%s\") is returning \"%s\"...\n", name,
var->values[var->nvalues - 1]));
#endif
return ((var == NULL) ? NULL : _cupsStrRetain(var->values[var->nvalues - 1]));
}
int
cgiInitialize(void)
{
const char *method,
*content_type,
*cups_sid_cookie,
*cups_sid_form;
cupsSetPasswordCB(cgi_passwd);
setlocale(LC_ALL, "");
#ifdef DEBUG
setbuf(stdout, NULL);
#endif
cgi_initialize_cookies();
if ((cups_sid_cookie = cgiGetCookie(CUPS_SID)) == NULL)
{
fputs("DEBUG: " CUPS_SID " cookie not found, initializing!\n", stderr);
cups_sid_cookie = cgi_set_sid();
}
fprintf(stderr, "DEBUG: " CUPS_SID " cookie is \"%s\"\n", cups_sid_cookie);
method = getenv("REQUEST_METHOD");
content_type = getenv("CONTENT_TYPE");
if (!method)
return (0);
if (!strcasecmp(method, "GET"))
return (cgi_initialize_get());
else if (!strcasecmp(method, "POST") && content_type)
{
const char *boundary = strstr(content_type, "boundary=");
if (boundary)
boundary += 9;
if (content_type && !strncmp(content_type, "multipart/form-data; ", 21))
{
if (!cgi_initialize_multipart(boundary))
return (0);
}
else if (!cgi_initialize_post())
return (0);
if ((cups_sid_form = cgiGetVariable(CUPS_SID)) == NULL ||
strcmp(cups_sid_cookie, cups_sid_form))
{
if (cups_sid_form)
fprintf(stderr, "DEBUG: " CUPS_SID " form variable is \"%s\"\n",
cups_sid_form);
else
fputs("DEBUG: " CUPS_SID " form variable is not present.\n", stderr);
cgiClearVariables();
return (0);
}
else
return (1);
}
else
return (0);
}
int
cgiIsPOST(void)
{
const char *method;
if ((method = getenv("REQUEST_METHOD")) == NULL)
return (0);
else
return (!strcmp(method, "POST"));
}
void
cgiSetArray(const char *name,
int element,
const char *value)
{
int i;
_cgi_var_t *var;
if (name == NULL || value == NULL || element < 0 || element > 100000)
return;
if ((var = cgi_find_variable(name)) == NULL)
{
cgi_add_variable(name, element, value);
cgi_sort_variables();
}
else
{
if (element >= var->avalues)
{
const char **temp;
temp = (const char **)realloc((void *)(var->values),
sizeof(char *) * (element + 16));
if (!temp)
return;
var->avalues = element + 16;
var->values = temp;
}
if (element >= var->nvalues)
{
for (i = var->nvalues; i < element; i ++)
var->values[i] = NULL;
var->nvalues = element + 1;
}
else if (var->values[element])
_cupsStrFree((char *)var->values[element]);
var->values[element] = _cupsStrAlloc(value);
}
}
void
cgiSetCookie(const char *name,
const char *value,
const char *path,
const char *domain,
time_t expires,
int secure)
{
num_cookies = cupsAddOption(name, value, num_cookies, &cookies);
printf("Set-Cookie: %s=%s;", name, value);
if (path)
printf("; path=%s", path);
if (domain)
printf("; domain=%s", domain);
if (expires)
{
char date[256];
printf("; expires=%s", httpGetDateString2(expires, date, sizeof(date)));
}
if (secure)
puts("; secure;");
else
puts(";");
}
void
cgiSetSize(const char *name,
int size)
{
int i;
_cgi_var_t *var;
if (name == NULL || size < 0 || size > 100000)
return;
if ((var = cgi_find_variable(name)) == NULL)
return;
if (size >= var->avalues)
{
const char **temp;
temp = (const char **)realloc((void *)(var->values),
sizeof(char *) * (size + 16));
if (!temp)
return;
var->avalues = size + 16;
var->values = temp;
}
if (size > var->nvalues)
{
for (i = var->nvalues; i < size; i ++)
var->values[i] = NULL;
}
else if (size < var->nvalues)
{
for (i = size; i < var->nvalues; i ++)
if (var->values[i])
_cupsStrFree((void *)(var->values[i]));
}
var->nvalues = size;
}
void
cgiSetVariable(const char *name,
const char *value)
{
int i;
_cgi_var_t *var;
if (name == NULL || value == NULL)
return;
if ((var = cgi_find_variable(name)) == NULL)
{
cgi_add_variable(name, 0, value);
cgi_sort_variables();
}
else
{
for (i = 0; i < var->nvalues; i ++)
if (var->values[i])
_cupsStrFree((char *)var->values[i]);
var->values[0] = _cupsStrAlloc(value);
var->nvalues = 1;
}
}
static void
cgi_add_variable(const char *name,
int element,
const char *value)
{
_cgi_var_t *var;
if (name == NULL || value == NULL || element < 0 || element > 100000)
return;
DEBUG_printf(("cgi_add_variable: Adding variable \'%s\' with value "
"\'%s\'...\n", name, value));
if (form_count >= form_alloc)
{
_cgi_var_t *temp_vars;
if (form_alloc == 0)
temp_vars = malloc(sizeof(_cgi_var_t) * 16);
else
temp_vars = realloc(form_vars, (form_alloc + 16) * sizeof(_cgi_var_t));
if (!temp_vars)
return;
form_vars = temp_vars;
form_alloc += 16;
}
var = form_vars + form_count;
if ((var->values = calloc(element + 1, sizeof(char *))) == NULL)
return;
var->name = _cupsStrAlloc(name);
var->nvalues = element + 1;
var->avalues = element + 1;
var->values[element] = _cupsStrAlloc(value);
form_count ++;
}
static int
cgi_compare_variables(
const _cgi_var_t *v1,
const _cgi_var_t *v2)
{
return (strcasecmp(v1->name, v2->name));
}
static _cgi_var_t *
cgi_find_variable(const char *name)
{
_cgi_var_t key;
if (form_count < 1 || name == NULL)
return (NULL);
key.name = name;
return ((_cgi_var_t *)bsearch(&key, form_vars, form_count, sizeof(_cgi_var_t),
(int (*)(const void *, const void *))cgi_compare_variables));
}
static void
cgi_initialize_cookies(void)
{
const char *cookie;
char name[128],
value[512],
*ptr;
if ((cookie = getenv("HTTP_COOKIE")) == NULL)
return;
while (*cookie)
{
while (isspace(*cookie & 255))
cookie ++;
if (!*cookie)
break;
for (ptr = name; *cookie && *cookie != '=';)
if (ptr < (name + sizeof(name) - 1))
*ptr++ = *cookie++;
else
break;
if (*cookie != '=')
break;
*ptr = '\0';
cookie ++;
if (*cookie == '\"')
{
for (cookie ++, ptr = value; *cookie && *cookie != '\"';)
if (ptr < (value + sizeof(value) - 1))
*ptr++ = *cookie++;
else
break;
if (*cookie == '\"')
cookie ++;
}
else
{
for (ptr = value; *cookie && *cookie != ';';)
if (ptr < (value + sizeof(value) - 1))
*ptr++ = *cookie++;
else
break;
}
if (*cookie == ';')
cookie ++;
else if (*cookie)
break;
*ptr = '\0';
if (name[0] != '$')
num_cookies = cupsAddOption(name, value, num_cookies, &cookies);
}
}
static int
cgi_initialize_get(void)
{
char *data;
DEBUG_puts("cgi_initialize_get: Initializing variables using GET method...");
data = getenv("QUERY_STRING");
if (data == NULL || strlen(data) == 0)
return (0);
return (cgi_initialize_string(data));
}
static int
cgi_initialize_multipart(
const char *boundary)
{
char line[10240],
name[1024],
filename[1024],
mimetype[1024],
bstring[256],
*ptr,
*end;
int ch,
fd,
blen;
DEBUG_printf(("cgi_initialize_multipart(boundary=\"%s\")\n", boundary));
name[0] = '\0';
filename[0] = '\0';
mimetype[0] = '\0';
snprintf(bstring, sizeof(bstring), "\r\n--%s", boundary);
blen = strlen(bstring);
while (fgets(line, sizeof(line), stdin))
{
if (!strcmp(line, "\r\n"))
{
if (filename[0])
{
if (form_file)
{
cgi_unlink_file();
}
if ((form_file = calloc(1, sizeof(cgi_file_t))) == NULL)
return (0);
form_file->name = strdup(name);
form_file->filename = strdup(filename);
form_file->mimetype = strdup(mimetype);
fd = cupsTempFd(form_file->tempfile, sizeof(form_file->tempfile));
if (fd < 0)
return (0);
atexit(cgi_unlink_file);
ptr = line;
while ((ch = getchar()) != EOF)
{
*ptr++ = ch;
if ((ptr - line) >= blen && !memcmp(ptr - blen, bstring, blen))
{
ptr -= blen;
break;
}
if ((ptr - line - blen) >= 8192)
{
write(fd, line, 8192);
memmove(line, line + 8192, ptr - line - 8192);
ptr -= 8192;
}
}
if (ptr > line)
write(fd, line, ptr - line);
close(fd);
}
else
{
ptr = line;
end = line + sizeof(line) - 1;
while ((ch = getchar()) != EOF)
{
if (ptr < end)
*ptr++ = ch;
if ((ptr - line) >= blen && !memcmp(ptr - blen, bstring, blen))
{
ptr -= blen;
break;
}
}
*ptr = '\0';
if ((ptr = strrchr(name, '-')) != NULL && isdigit(ptr[1] & 255))
{
*ptr++ = '\0';
if (line[0])
cgiSetArray(name, atoi(ptr) - 1, line);
}
else if (cgiGetVariable(name))
{
cgiSetArray(name, cgiGetSize(name), line);
}
else
{
cgiSetVariable(name, line);
}
}
fgets(line, sizeof(line), stdin);
name[0] = '\0';
filename[0] = '\0';
mimetype[0] = '\0';
}
else if (!strncasecmp(line, "Content-Disposition:", 20))
{
if ((ptr = strstr(line + 20, " name=\"")) != NULL)
{
strlcpy(name, ptr + 7, sizeof(name));
if ((ptr = strchr(name, '\"')) != NULL)
*ptr = '\0';
}
if ((ptr = strstr(line + 20, " filename=\"")) != NULL)
{
strlcpy(filename, ptr + 11, sizeof(filename));
if ((ptr = strchr(filename, '\"')) != NULL)
*ptr = '\0';
}
}
else if (!strncasecmp(line, "Content-Type:", 13))
{
for (ptr = line + 13; isspace(*ptr & 255); ptr ++);
strlcpy(mimetype, ptr, sizeof(mimetype));
for (ptr = mimetype + strlen(mimetype) - 1;
ptr > mimetype && isspace(*ptr & 255);
*ptr-- = '\0');
}
}
return (1);
}
static int
cgi_initialize_post(void)
{
char *content_length,
*data;
int length,
nbytes,
tbytes,
status;
DEBUG_puts("cgi_initialize_post: Initializing variables using POST method...");
content_length = getenv("CONTENT_LENGTH");
if (content_length == NULL || atoi(content_length) <= 0)
return (0);
length = atoi(content_length);
data = malloc(length + 1);
if (data == NULL)
return (0);
for (tbytes = 0; tbytes < length; tbytes += nbytes)
if ((nbytes = read(0, data + tbytes, length - tbytes)) < 0)
{
if (errno != EAGAIN)
{
free(data);
return (0);
}
else
nbytes = 0;
}
else if (nbytes == 0)
{
free(data);
return (0);
}
data[length] = '\0';
status = cgi_initialize_string(data);
free(data);
return (status);
}
static int
cgi_initialize_string(const char *data)
{
int done;
char *s,
ch,
name[255],
value[65536];
if (data == NULL)
return (0);
while (*data != '\0')
{
for (s = name; *data != '\0'; data ++)
if (*data == '=')
break;
else if (*data >= ' ' && s < (name + sizeof(name) - 1))
*s++ = *data;
*s = '\0';
if (*data == '=')
data ++;
else
return (0);
for (s = value, done = 0; !done && *data != '\0'; data ++)
switch (*data)
{
case '&' :
done = 1;
break;
case '+' :
if (s < (value + sizeof(value) - 1))
*s++ = ' ';
break;
case '%' :
if (!isxdigit(data[1] & 255) || !isxdigit(data[2] & 255))
return (0);
if (s < (value + sizeof(value) - 1))
{
data ++;
ch = *data - '0';
if (ch > 9)
ch -= 7;
*s = ch << 4;
data ++;
ch = *data - '0';
if (ch > 9)
ch -= 7;
*s++ |= ch;
}
else
data += 2;
break;
default :
if (*data >= ' ' && s < (value + sizeof(value) - 1))
*s++ = *data;
break;
}
*s = '\0';
if (s > value)
s --;
while (s >= value && isspace(*s & 255))
*s-- = '\0';
if ((s = strrchr(name, '-')) != NULL && isdigit(s[1] & 255))
{
*s++ = '\0';
if (value[0])
cgiSetArray(name, atoi(s) - 1, value);
}
else if (cgiGetVariable(name) != NULL)
cgiSetArray(name, cgiGetSize(name), value);
else
cgiSetVariable(name, value);
}
return (1);
}
static const char *
cgi_passwd(const char *prompt)
{
(void)prompt;
fprintf(stderr, "DEBUG: cgi_passwd(prompt=\"%s\") called!\n",
prompt ? prompt : "(null)");
puts("Status: 401\n");
exit(0);
return (NULL);
}
static const char *
cgi_set_sid(void)
{
char buffer[512],
sid[33];
_cups_md5_state_t md5;
unsigned char sum[16];
const char *remote_addr,
*server_name,
*server_port;
if ((remote_addr = getenv("REMOTE_ADDR")) == NULL)
remote_addr = "REMOTE_ADDR";
if ((server_name = getenv("SERVER_NAME")) == NULL)
server_name = "SERVER_NAME";
if ((server_port = getenv("SERVER_PORT")) == NULL)
server_port = "SERVER_PORT";
CUPS_SRAND(time(NULL));
snprintf(buffer, sizeof(buffer), "%s:%s:%s:%02X%02X%02X%02X%02X%02X%02X%02X",
remote_addr, server_name, server_port,
(unsigned)CUPS_RAND() & 255, (unsigned)CUPS_RAND() & 255,
(unsigned)CUPS_RAND() & 255, (unsigned)CUPS_RAND() & 255,
(unsigned)CUPS_RAND() & 255, (unsigned)CUPS_RAND() & 255,
(unsigned)CUPS_RAND() & 255, (unsigned)CUPS_RAND() & 255);
_cupsMD5Init(&md5);
_cupsMD5Append(&md5, (unsigned char *)buffer, (int)strlen(buffer));
_cupsMD5Finish(&md5, sum);
cgiSetCookie(CUPS_SID, httpMD5String(sum, sid), "/", server_name, 0, 0);
return (cupsGetOption(CUPS_SID, num_cookies, cookies));
}
static void
cgi_sort_variables(void)
{
#ifdef DEBUG
int i;
DEBUG_puts("cgi_sort_variables: Sorting variables...");
#endif
if (form_count < 2)
return;
qsort(form_vars, form_count, sizeof(_cgi_var_t),
(int (*)(const void *, const void *))cgi_compare_variables);
#ifdef DEBUG
DEBUG_puts("cgi_sort_variables: Sorted variable list is:");
for (i = 0; i < form_count; i ++)
DEBUG_printf(("cgi_sort_variables: %d: %s (%d) = \"%s\" ...\n", i,
form_vars[i].name, form_vars[i].nvalues,
form_vars[i].values[0]));
#endif
}
static void
cgi_unlink_file(void)
{
if (form_file)
{
unlink(form_file->tempfile);
free(form_file->name);
free(form_file->filename);
free(form_file->mimetype);
free(form_file);
form_file = NULL;
}
}