-
Rob Swindell authored
Handles integer overflow in the summing of user's credits and remaining daily free credits.
Rob Swindell authoredHandles integer overflow in the summing of user's credits and remaining daily free credits.
filedat.c 39.45 KiB
/* Synchronet file database-related exported functions */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* 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. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include "filedat.h"
#include "userdat.h"
#include "dat_rec.h"
#include "datewrap.h" // time32()
#include "str_util.h"
#include "nopen.h"
#include "smblib.h"
#include "load_cfg.h" // smb_open_dir()
#include "scfglib.h"
#include "sauce.h"
#include "crc32.h"
#include "utf8.h"
/* libarchive: */
#include <archive.h>
#include <archive_entry.h>
/****************************************************************************/
/****************************************************************************/
bool findfile(scfg_t* cfg, uint dirnum, const char *filename, file_t* file)
{
smb_t smb;
if(cfg == NULL || filename == NULL)
return false;
if(!smb_init_dir(cfg, &smb, dirnum))
return false;
if(smb_open_index(&smb) != SMB_SUCCESS)
return false;
int result = smb_findfile(&smb, filename, file);
smb_close(&smb);
return result == SMB_SUCCESS;
}
// This function may be called without opening the file base (for fast new-scans)
time_t newfiletime(smb_t* smb)
{
char str[MAX_PATH + 1];
SAFEPRINTF(str, "%s.sid", smb->file);
return fdate(str);
}
time_t dir_newfiletime(scfg_t* cfg, uint dirnum)
{
smb_t smb;
if(!smb_init_dir(cfg, &smb, dirnum))
return -1;
return newfiletime(&smb);
}
// This function may be called without opening the file base (for fast new-scans)
bool newfiles(smb_t* smb, time_t t)
{
return newfiletime(smb) > t;
}
// This function *must* be called with file base closed
bool update_newfiletime(smb_t* smb, time_t t)
{
char path[MAX_PATH + 1];
SAFEPRINTF(path, "%s.sid", smb->file);
return setfdate(path, t) == 0;
}
time_t lastfiletime(smb_t* smb)
{
idxrec_t idx;
if(smb_getlastidx(smb, &idx) != SMB_SUCCESS)
return (time_t)-1;
return idx.time;
}
static int filename_compare_a(const void *arg1, const void *arg2)
{
return stricmp(*(char**) arg1, *(char**) arg2);
}
static int filename_compare_d(const void *arg1, const void *arg2)
{
return stricmp(*(char**) arg2, *(char**) arg1);
}
static int filename_compare_ac(const void *arg1, const void *arg2)
{
return strcmp(*(char**) arg1, *(char**) arg2);
}
static int filename_compare_dc(const void *arg1, const void *arg2)
{
return strcmp(*(char**) arg2, *(char**) arg1);
}
// Note: does not support sorting by date
void sortfilenames(str_list_t filelist, size_t count, enum file_sort order)
{
switch(order) {
case FILE_SORT_NAME_A:
qsort(filelist, count, sizeof(*filelist), filename_compare_a);
break;
case FILE_SORT_NAME_D:
qsort(filelist, count, sizeof(*filelist), filename_compare_d);
break;
case FILE_SORT_NAME_AC:
qsort(filelist, count, sizeof(*filelist), filename_compare_ac);
break;
case FILE_SORT_NAME_DC:
qsort(filelist, count, sizeof(*filelist), filename_compare_dc);
break;
default:
break;
}
}
// Return an optionally-sorted dynamically-allocated string list of filenames added since date/time (t)
// Note: does not support sorting by date (exception: natural sort order of date-ascending)
str_list_t loadfilenames(smb_t* smb, const char* filespec, time_t t, enum file_sort order, size_t* count)
{
size_t count_;
if(count == NULL)
count = &count_;
*count = 0;
long start = 0;
if(t > 0) {
idxrec_t idx;
start = smb_getmsgidx_by_time(smb, &idx, t);
if(start < 0)
return NULL;
}
if(fseek(smb->sid_fp, start * sizeof(fileidxrec_t), SEEK_SET) != 0)
return NULL;
char** file_list = calloc(smb->status.total_files + 1, sizeof(char*));
if(file_list == NULL)
return NULL;
size_t offset = start;
while(!feof(smb->sid_fp) && offset < smb->status.total_files) {
fileidxrec_t fidx;
if(smb_fread(smb, &fidx, sizeof(fidx), smb->sid_fp) != sizeof(fidx))
break;
offset++;
if(fidx.idx.number == 0) /* invalid message number, ignore */
continue;
TERMINATE(fidx.name);
if(filespec != NULL && *filespec != '\0') {
if(!wildmatch(fidx.name, filespec, /* path: */false, /* case-sensitive: */false))
continue;
}
file_list[*count] = strdup(fidx.name);
(*count)++;
}
if(order != FILE_SORT_NATURAL)
sortfilenames(file_list, *count, order);
return file_list;
}
// Load and optionally-sort files from an open filebase into a dynamically-allocated list of "objects"
file_t* loadfiles(smb_t* smb, const char* filespec, time_t t, enum file_detail detail, enum file_sort order, size_t* count)
{
// Liberal filespec matching when filespec does not contain wildcards and is at least 12 chars in length
char newfilespec[SMB_FILEIDX_NAMELEN + 1] = "";
if(filespec != NULL) {
size_t len = strlen(filespec);
if(len >= 12 && strcspn(filespec, "*?") == len) {
SAFECOPY(newfilespec, filespec);
char* ext = getfext(filespec);
char* newext = getfext(newfilespec);
if(ext != NULL && newext != NULL) {
*newext = 0;
SAFECAT(newfilespec, "*");
SAFECAT(newfilespec, ext);
} else
SAFECAT(newfilespec, "*");
filespec = newfilespec;
}
}
*count = 0;
long start = 0;
if(t) {
idxrec_t idx;
start = smb_getmsgidx_by_time(smb, &idx, t);
if(start < 0)
return NULL;
}
if(fseek(smb->sid_fp, start * sizeof(fileidxrec_t), SEEK_SET) != 0)
return NULL;
file_t* file_list = calloc(smb->status.total_files, sizeof(file_t));
if(file_list == NULL)
return NULL;
size_t offset = start;
size_t cnt = 0;
while(!feof(smb->sid_fp) && offset < smb->status.total_files) {
file_t* f = &file_list[cnt];
if(smb_fread(smb, &f->file_idx, sizeof(f->file_idx), smb->sid_fp) != sizeof(f->file_idx))
break;
f->idx_offset = offset++;
if(f->idx.number == 0) /* invalid message number, ignore */
continue;
TERMINATE(f->file_idx.name);
if(filespec != NULL && *filespec != '\0') {
if(!wildmatch(f->file_idx.name, filespec, /* path: */false, /* case-sensitive: */false))
continue;
}
cnt++;
}
if(order != FILE_SORT_NATURAL)
sortfiles(file_list, cnt, order);
// Read (and set convenience pointers) *after* sorting the list
for((*count) = 0; (*count) < cnt; (*count)++) {
int result = smb_getfile(smb, &file_list[*count], detail);
if(result != SMB_SUCCESS)
break;
}
return file_list;
}
static int file_compare_name_a(const void* v1, const void* v2)
{
file_t* f1 = (file_t*)v1;
file_t* f2 = (file_t*)v2;
return stricmp(f1->file_idx.name, f2->file_idx.name);
}
static int file_compare_name_ac(const void* v1, const void* v2)
{
file_t* f1 = (file_t*)v1;
file_t* f2 = (file_t*)v2;
return strcmp(f1->file_idx.name, f2->file_idx.name);
}
static int file_compare_name_d(const void* v1, const void* v2)
{
file_t* f1 = (file_t*)v1;
file_t* f2 = (file_t*)v2;
return stricmp(f2->file_idx.name, f1->file_idx.name);
}
static int file_compare_name_dc(const void* v1, const void* v2)
{
file_t* f1 = (file_t*)v1;
file_t* f2 = (file_t*)v2;
return strcmp(f2->file_idx.name, f1->file_idx.name);
}
static int file_compare_date_a(const void* v1, const void* v2)
{
file_t* f1 = (file_t*)v1;
file_t* f2 = (file_t*)v2;
return f1->idx.time - f2->idx.time;
}
static int file_compare_date_d(const void* v1, const void* v2)
{
file_t* f1 = (file_t*)v1;
file_t* f2 = (file_t*)v2;
return f2->idx.time - f1->idx.time;
}
static int file_compare_size_a(const void* v1, const void* v2)
{
file_t* f1 = (file_t*)v1;
file_t* f2 = (file_t*)v2;
int64_t diff = smb_getfilesize(&f1->idx) - smb_getfilesize(&f2->idx);
if(diff < 0)
return -1;
if(diff > 0)
return 1;
return 0;
}
static int file_compare_size_d(const void* v1, const void* v2)
{
file_t* f1 = (file_t*)v1;
file_t* f2 = (file_t*)v2;
int64_t diff = smb_getfilesize(&f2->idx) - smb_getfilesize(&f1->idx);
if(diff < 0)
return -1;
if(diff > 0)
return 1;
return 0;
}
void sortfiles(file_t* filelist, size_t count, enum file_sort order)
{
switch(order) {
case FILE_SORT_NAME_A:
qsort(filelist, count, sizeof(*filelist), file_compare_name_a);
break;
case FILE_SORT_NAME_D:
qsort(filelist, count, sizeof(*filelist), file_compare_name_d);
break;
case FILE_SORT_NAME_AC:
qsort(filelist, count, sizeof(*filelist), file_compare_name_ac);
break;
case FILE_SORT_NAME_DC:
qsort(filelist, count, sizeof(*filelist), file_compare_name_dc);
break;
case FILE_SORT_DATE_A:
qsort(filelist, count, sizeof(*filelist), file_compare_date_a);
break;
case FILE_SORT_DATE_D:
qsort(filelist, count, sizeof(*filelist), file_compare_date_d);
break;
case FILE_SORT_SIZE_A:
qsort(filelist, count, sizeof(*filelist), file_compare_size_a);
break;
case FILE_SORT_SIZE_D:
qsort(filelist, count, sizeof(*filelist), file_compare_size_d);
break;
}
}
void freefiles(file_t* filelist, size_t count)
{
for(size_t i = 0; i < count; i++)
smb_freefilemem(&filelist[i]);
free(filelist);
}
bool loadfile(scfg_t* cfg, uint dirnum, const char* filename, file_t* file, enum file_detail detail)
{
smb_t smb;
if(smb_open_dir(cfg, &smb, dirnum) != SMB_SUCCESS)
return false;
int result = smb_loadfile(&smb, filename, file, detail);
smb_close(&smb);
if(cfg->dir[dirnum]->misc & DIR_FREE)
file->cost = 0;
return result == SMB_SUCCESS;
}
char* batch_list_name(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, char* fname, size_t size)
{
safe_snprintf(fname, size, "%suser/%04u.%sload", cfg->data_dir, usernumber
,(type == XFER_UPLOAD || type == XFER_BATCH_UPLOAD) ? "up" : "dn");
return fname;
}
FILE* batch_list_open(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, bool for_modify)
{
char path[MAX_PATH + 1];
return iniOpenFile(batch_list_name(cfg, usernumber, type, path, sizeof(path)), for_modify);
}
str_list_t batch_list_read(scfg_t* cfg, uint usernumber, enum XFER_TYPE type)
{
FILE* fp = batch_list_open(cfg, usernumber, type, /* for_modify: */false);
if(fp == NULL)
return NULL;
str_list_t ini = iniReadFile(fp);
iniCloseFile(fp);
return ini;
}
bool batch_list_write(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, str_list_t list)
{
FILE* fp = batch_list_open(cfg, usernumber, type, /* for_modify: */true);
if(fp == NULL)
return false;
bool result = iniWriteFile(fp, list);
iniCloseFile(fp);
return result;
}
bool batch_list_clear(scfg_t* cfg, uint usernumber, enum XFER_TYPE type)
{
char path[MAX_PATH + 1];
return remove(batch_list_name(cfg, usernumber, type, path, sizeof(path))) == 0;
}
bool batch_list_sort(scfg_t* cfg, uint usernumber, enum XFER_TYPE type)
{
str_list_t ini = batch_list_read(cfg, usernumber, type);
if(ini == NULL)
return true;
bool result = iniSortSections(&ini, /* prefix: */NULL, /* sort_keys: */FALSE);
if(result)
result = batch_list_write(cfg, usernumber, type, ini);
iniFreeStringList(ini);
return result;
}
size_t batch_file_count(scfg_t* cfg, uint usernumber, enum XFER_TYPE type)
{
FILE* fp = batch_list_open(cfg, usernumber, type, /* for_modify: */false);
if(fp == NULL)
return 0;
size_t result = iniReadSectionCount(fp, /* prefix: */NULL);
iniCloseFile(fp);
return result;
}
bool batch_file_remove(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, const char* filename)
{
FILE* fp = batch_list_open(cfg, usernumber, type, /* for_modify: */true);
if(fp == NULL)
return false;
str_list_t ini = iniReadFile(fp);
bool result = iniRemoveSection(&ini, filename);
iniWriteFile(fp, ini);
iniCloseFile(fp);
iniFreeStringList(ini);
return result;
}
// 'n' is zero-based index of file to remove
bool batch_file_remove_n(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, uint n)
{
FILE* fp = batch_list_open(cfg, usernumber, type, /* for_modify: */true);
if(fp == NULL)
return false;
str_list_t ini = iniReadFile(fp);
str_list_t files = iniGetSectionList(ini, NULL);
bool result = false;
if(n < strListCount(files))
result = iniRemoveSection(&ini, files[n]);
iniWriteFile(fp, ini);
iniCloseFile(fp);
iniFreeStringList(ini);
iniFreeStringList(files);
return result;
}
bool batch_file_exists(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, const char* filename)
{
FILE* fp = batch_list_open(cfg, usernumber, type, /* for_modify: */false);
if(fp == NULL)
return false;
str_list_t ini = iniReadFile(fp);
bool result = iniSectionExists(ini, filename);
iniCloseFile(fp);
iniFreeStringList(ini);
return result;
}
bool batch_file_add(scfg_t* cfg, uint usernumber, enum XFER_TYPE type, file_t* f)
{
FILE* fp = batch_list_open(cfg, usernumber, type, /* for_modify: */true);
if(fp == NULL)
return false;
if(fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return false;
}
fprintf(fp, "\n[%s]\n", f->name);
if(f->dir >= 0 && f->dir < cfg->total_dirs)
fprintf(fp, "dir=%s\n", cfg->dir[f->dir]->code);
if(f->desc != NULL)
fprintf(fp, "desc=%s\n", f->desc);
if(f->tags != NULL)
fprintf(fp, "tags=%s\n", f->tags);
fclose(fp);
return true;
}
bool batch_file_get(scfg_t* cfg, str_list_t ini, const char* filename, file_t* f)
{
char* p;
char value[INI_MAX_VALUE_LEN + 1];
if(!iniSectionExists(ini, filename))
return false;
f->dir = batch_file_dir(cfg, ini, filename);
if(f->dir < 0 || f->dir >= cfg->total_dirs)
return false;
smb_hfield_str(f, SMB_FILENAME, filename);
if((p = iniGetString(ini, filename, "desc", NULL, value)) != NULL)
smb_hfield_str(f, SMB_FILEDESC, p);
if((p = iniGetString(ini, filename, "tags", NULL, value)) != NULL)
smb_hfield_str(f, SMB_TAGS, p);
return true;
}
int batch_file_dir(scfg_t* cfg, str_list_t ini, const char* filename)
{
char value[INI_MAX_VALUE_LEN + 1];
return getdirnum(cfg, iniGetString(ini, filename, "dir", NULL, value));
}
bool batch_file_load(scfg_t* cfg, str_list_t ini, const char* filename, file_t* f)
{
if(!iniSectionExists(ini, filename))
return false;
f->dir = batch_file_dir(cfg, ini, filename);
if(f->dir < 0)
return false;
return loadfile(cfg, f->dir, filename, f, file_detail_normal);
}
bool updatefile(scfg_t* cfg, file_t* file)
{
smb_t smb;
if(smb_open_dir(cfg, &smb, file->dir) != SMB_SUCCESS)
return false;
int result = smb_updatemsg(&smb, file);
smb_close(&smb);
return result == SMB_SUCCESS;
}
bool removefile(scfg_t* cfg, uint dirnum, const char* filename)
{
smb_t smb;
if(smb_open_dir(cfg, &smb, dirnum) != SMB_SUCCESS)
return false;
int result;
file_t file;
if((result = smb_loadfile(&smb, filename, &file, file_detail_normal)) == SMB_SUCCESS) {
result = smb_removefile(&smb, &file);
smb_freefilemem(&file);
}
smb_close(&smb);
return result == SMB_SUCCESS;
}
ulong getuserxfers(scfg_t* cfg, const char* from, uint to)
{
smb_t smb;
ulong found = 0;
if(cfg == NULL)
return 0;
if(cfg->user_dir >= cfg->total_dirs)
return 0;
if(smb_open_dir(cfg, &smb, cfg->user_dir) != SMB_SUCCESS)
return 0;
char usernum[16];
SAFEPRINTF(usernum, "%u", to);
size_t count = 0;
file_t* list = loadfiles(&smb, /* filespec */ NULL, /* since: */0, file_detail_normal, FILE_SORT_NATURAL, &count);
for(size_t i = 0; i < count; i++) {
file_t* f = &list[i];
if(from != NULL && (f->from == NULL || stricmp(f->from, from) != 0))
continue;
if(to != 0) {
str_list_t dest_user_list = strListSplitCopy(NULL, f->to_list, ",");
int dest_user = strListFind(dest_user_list, usernum, /* case-sensitive: */true);
strListFree(&dest_user_list);
if(dest_user < 0)
continue;
}
found++;
}
smb_close(&smb);
freefiles(list, count);
return found;
}
/****************************************************************************/
/* Returns full (case-corrected) path to specified file */
/* 'path' should be MAX_PATH + 1 chars in size */
/* If the directory is configured to allow file-checking and the file does */
/* not exist, the size element is set to -1. */
/****************************************************************************/
char* getfilepath(scfg_t* cfg, file_t* f, char* path)
{
bool fchk = true;
const char* name = f->name == NULL ? f->file_idx.name : f->name;
if(f->dir >= cfg->total_dirs)
safe_snprintf(path, MAX_PATH, "%s%s", cfg->temp_dir, name);
else {
safe_snprintf(path, MAX_PATH, "%s%s", cfg->dir[f->dir]->path, name);
fchk = (cfg->dir[f->dir]->misc & DIR_FCHK) != 0;
}
if(f->size == 0 && fchk && !fexistcase(path))
f->size = -1;
return path;
}
char* getfilevpath(scfg_t* cfg, file_t* f, char* path)
{
const char* name = f->name == NULL ? f->file_idx.name : f->name;
if(f->dir >= cfg->total_dirs)
return "";
safe_snprintf(path, MAX_PATH, "%s/%s/%s"
,cfg->lib[cfg->dir[f->dir]->lib]->vdir, cfg->dir[f->dir]->vdir, name);
return path;
}
off_t getfilesize(scfg_t* cfg, file_t* f)
{
char fpath[MAX_PATH + 1];
if(f->size > 0)
return f->size;
f->size = flength(getfilepath(cfg, f, fpath));
return f->size;
}
time_t getfiletime(scfg_t* cfg, file_t* f)
{
char fpath[MAX_PATH + 1];
if(f->time > 0)
return f->time;
f->time = fdate(getfilepath(cfg, f, fpath));
return f->time;
}
// Return estimated time to transfer file, in seconds
ulong gettimetodl(scfg_t* cfg, file_t* f, uint rate_cps)
{
if(getfilesize(cfg, f) < 1)
return 0;
if(f->size <= (off_t)rate_cps)
return 1;
if(rate_cps < 1)
rate_cps = 100000;
return (ulong)(f->size / rate_cps);
}
bool hashfile(scfg_t* cfg, file_t* f)
{
bool result = false;
smb_t smb;
if(cfg->dir[f->dir]->misc & DIR_NOHASH)
return false;
if(smb_open_dir(cfg, &smb, f->dir) != SMB_SUCCESS)
return false;
if(!(smb.status.attr & SMB_NOHASH)) {
char fpath[MAX_PATH + 1];
getfilepath(cfg, f, fpath);
if((f->file_idx.hash.flags = smb_hashfile(fpath, getfilesize(cfg, f), &f->file_idx.hash.data)) != 0)
result = true;
}
smb_close(&smb);
return result;
}
int file_client_hfields(file_t* f, client_t* client)
{
int i;
if(client == NULL)
return -1;
if(*client->addr && (i = smb_hfield_str(f, SENDERIPADDR, client->addr)) != SMB_SUCCESS)
return i;
if(*client->host && (i = smb_hfield_str(f, SENDERHOSTNAME, client->host)) != SMB_SUCCESS)
return i;
if(client->protocol != NULL && (i = smb_hfield_str(f, SENDERPROTOCOL, client->protocol)) != SMB_SUCCESS)
return i;
if(client->port) {
char port[16];
SAFEPRINTF(port,"%u",client->port);
return smb_hfield_str(f, SENDERPORT, port);
}
return SMB_SUCCESS;
}
int file_sauce_hfields(file_t* f, struct sauce_charinfo* info)
{
int i;
if(info == NULL)
return -1;
if(*info->author && (i = smb_new_hfield_str(f, SMB_AUTHOR, info->author)) != SMB_SUCCESS)
return i;
if(*info->group && (i = smb_new_hfield_str(f, SMB_AUTHOR_ORG, info->group)) != SMB_SUCCESS)
return i;
if(f->desc == NULL && *info->title && (i = smb_new_hfield_str(f, SMB_FILEDESC, info->title)) != SMB_SUCCESS)
return i;
return SMB_SUCCESS;
}
bool addfile(scfg_t* cfg, file_t* f, const char* extdesc, const char* metadata, client_t* client)
{
char fpath[MAX_PATH + 1];
smb_t smb;
if(smb_open_dir(cfg, &smb, f->dir) != SMB_SUCCESS)
return false;
getfilepath(cfg, f, fpath);
if(f->from_ip == NULL)
file_client_hfields(f, client);
int result = smb_addfile(&smb, f, SMB_SELFPACK, extdesc, metadata, fpath);
smb_close(&smb);
return result == SMB_SUCCESS;
}
/* 'size' does not include the NUL-terminator */
char* format_filename(const char* fname, char* buf, size_t size, bool pad)
{
size_t fnlen = strlen(fname);
char* ext = getfext(fname);
if(ext != NULL) {
size_t extlen = strlen(ext);
if(extlen >= size)
safe_snprintf(buf, size + 1, "%s", fname);
else {
fnlen -= extlen;
if(fnlen > size - extlen)
fnlen = size - extlen;
safe_snprintf(buf, size + 1, "%-*.*s%s", (int)(pad ? (size - extlen) : 0), (int)fnlen, fname, ext);
}
} else /* no extension */
snprintf(buf, size + 1, "%s", fname);
return buf;
}
int archive_type(const char* archive, char* str, size_t size)
{
int result;
struct archive *ar;
struct archive_entry *entry;
if((ar = archive_read_new()) == NULL) {
safe_snprintf(str, size, "archive_read_new returned NULL");
return -1;
}
archive_read_support_filter_all(ar);
archive_read_support_format_all(ar);
if((result = archive_read_open_filename(ar, archive, 10240)) != ARCHIVE_OK) {
safe_snprintf(str, size, "archive_read_open_filename returned %d: %s"
,result, archive_error_string(ar));
archive_read_free(ar);
return result;
}
result = archive_filter_code(ar, 0);
if(result >= 0) {
int comp = result;
result = archive_read_next_header(ar, &entry);
if(result != ARCHIVE_OK)
safe_snprintf(str, size, "archive_read_next_header returned %d: %s"
,result, archive_error_string(ar));
else {
result = archive_format(ar);
if(comp > 0)
safe_snprintf(str, size, "%s/%s", archive_filter_name(ar, 0), archive_format_name(ar));
else
safe_snprintf(str, size, "%s", archive_format_name(ar));
}
}
archive_read_free(ar);
return result;
}
bool file_type_match(const char* filename, const char* type)
{
char filespec[MAX_PATH + 1];
SAFEPRINTF(filespec, "*.%s", type);
return wildmatch(filename, filespec, /* path: */false, /* case-sensitive: */false);
}
str_list_t directory(const char* path)
{
int flags = GLOB_MARK;
glob_t g;
if(glob(path, flags, NULL, &g) != 0)
return NULL;
str_list_t list = strListInit();
for(size_t i = 0; i < g.gl_pathc; i++) {
strListPush(&list, g.gl_pathv[i]);
}
globfree(&g);
return list;
}
const char* supported_archive_formats[] = { "zip", "7z", "tgz", NULL };
// Returns total number of successfully archived files
// Returns negative on error
long create_archive(const char* archive, const char* format
,bool with_path, str_list_t file_list, char* error, size_t maxerrlen)
{
int result;
struct archive *ar;
if(file_list == NULL || *file_list == NULL)
return 0;
if((ar = archive_write_new()) == NULL) {
safe_snprintf(error, maxerrlen, "archive_write_new returned NULL");
return -1;
}
if(stricmp(format, "tgz") == 0) {
archive_write_add_filter_gzip(ar);
archive_write_set_format_pax_restricted(ar);
} else if(stricmp(format, "tbz") == 0) {
archive_write_add_filter_bzip2(ar);
archive_write_set_format_pax_restricted(ar);
} else if(stricmp(format, "zip") == 0) {
archive_write_set_format_zip(ar);
} else if(stricmp(format, "7z") == 0) {
archive_write_set_format_7zip(ar);
} else {
safe_snprintf(error, maxerrlen, "unsupported format: %s", format);
return -2;
}
if((result = archive_write_open_filename(ar, archive)) != ARCHIVE_OK) {
safe_snprintf(error, maxerrlen, "archive_write_open_filename(%s) returned %d: %s"
,archive, result, archive_error_string(ar));
archive_write_free(ar);
return result;
}
ulong file_count = 0;
ulong archived = 0;
for(;file_list[file_count] != NULL; file_count++) {
struct archive_entry* entry;
struct stat st;
const char* filename = file_list[file_count];
if(isdir(filename))
continue;
FILE* fp = fopen(filename, "rb");
if(fp == NULL) {
safe_snprintf(error, maxerrlen, "%d opening %s", errno, filename);
break;
}
if(fstat(fileno(fp), &st) != 0) {
safe_snprintf(error, maxerrlen, "%d fstat %s", errno, filename);
fclose(fp);
break;
}
if((entry = archive_entry_new()) == NULL) {
safe_snprintf(error, maxerrlen, "archive_entry_new returned NULL");
fclose(fp);
break;
}
if(with_path)
archive_entry_set_pathname(entry, filename);
else
archive_entry_set_pathname(entry, getfname(filename));
archive_entry_set_size(entry, st.st_size);
archive_entry_set_mtime(entry, st.st_mtime, 0);
archive_entry_set_filetype(entry, AE_IFREG);
archive_entry_set_perm(entry, 0644);
if((result = archive_write_header(ar, entry)) != ARCHIVE_OK)
safe_snprintf(error, maxerrlen, "archive_write_header returned %d: %s", result, archive_error_string(ar));
else while(!feof(fp)) {
char buf[256 * 1024];
size_t len = fread(buf, 1, sizeof(buf), fp);
if((result = archive_write_data(ar, buf, len)) != len) {
safe_snprintf(error, maxerrlen, "archive_write_data returned %d instead of %d", result, (int)len);
break;
} else
result = ARCHIVE_OK;
}
fclose(fp);
archive_entry_free(entry);
if(result != ARCHIVE_OK)
break;
archived++;
}
archive_write_close(ar);
archive_write_free(ar);
if(file_list[file_count] != NULL)
return result < 0 ? result : -1;
return archived;
}
long extract_files_from_archive(const char* archive, const char* outdir, const char* allowed_filename_chars
,bool with_path, bool overwrite, long max_files, str_list_t file_list, char* error, size_t maxerrlen)
{
int result;
struct archive *ar;
struct archive_entry *entry;
long extracted = 0;
char fpath[MAX_PATH + 1];
if(error == NULL)
maxerrlen = 0;
if(maxerrlen >= 1)
*error = '\0';
if((ar = archive_read_new()) == NULL) {
safe_snprintf(error, maxerrlen, "archive_read_new returned NULL");
return -1;
}
archive_read_support_filter_all(ar);
archive_read_support_format_all(ar);
if((result = archive_read_open_filename(ar, archive, 10240)) != ARCHIVE_OK) {
safe_snprintf(error, maxerrlen, "archive_read_open_filename returned %d: %s"
,result, archive_error_string(ar));
archive_read_free(ar);
return result >= 0 ? -1 : result;
}
while(1) {
result = archive_read_next_header(ar, &entry);
if(result != ARCHIVE_OK) {
if(result != ARCHIVE_EOF)
safe_snprintf(error, maxerrlen, "archive_read_next_header returned %d: %s"
,result, archive_error_string(ar));
break;
}
const char* pathname = archive_entry_pathname(entry);
if(pathname == NULL)
continue;
int filetype = archive_entry_filetype(entry);
if(filetype == AE_IFDIR) {
if(!with_path)
continue;
if(strstr(pathname, "..") != NULL) {
safe_snprintf(error, maxerrlen, "Illegal double-dots in path '%s'", pathname);
break;
}
SAFECOPY(fpath, outdir);
backslash(fpath);
SAFECAT(fpath, pathname);
if(mkpath(fpath) != 0) {
char err[256];
safe_snprintf(error, maxerrlen, "%d (%s) creating path '%s'", errno, safe_strerror(errno, err, sizeof(err)), fpath);
break;
}
continue;
}
if(filetype != AE_IFREG)
continue;
char* filename = getfname(pathname);
if(!with_path)
pathname = filename;
if(file_list != NULL) {
int i;
for (i = 0; file_list[i] != NULL; i++)
if(wildmatch(pathname, file_list[i], with_path, /* case-sensitive: */false))
break;
if(file_list[i] == NULL)
continue;
}
if(allowed_filename_chars != NULL
&& *allowed_filename_chars != '\0'
&& strspn(filename, allowed_filename_chars) != strlen(filename)) {
safe_snprintf(error, maxerrlen, "disallowed filename '%s'", pathname);
break;
}
SAFECOPY(fpath, outdir);
backslash(fpath);
SAFECAT(fpath, pathname);
if(!overwrite && fexist(fpath))
continue;
if(max_files && extracted >= max_files) {
safe_snprintf(error, maxerrlen, "maximum number of files (%lu) extracted", max_files);
break;
}
FILE* fp = fopen(fpath, "wb");
if(fp == NULL) {
char err[256];
safe_snprintf(error, maxerrlen, "%d (%s) opening/creating '%s'", errno, safe_strerror(errno, err, sizeof(err)), fpath);
break;
}
const void *buff;
size_t size;
int64_t offset;
for(;;) {
result = archive_read_data_block(ar, &buff, &size, &offset);
if(result == ARCHIVE_EOF) {
extracted++;
break;
}
if(result < ARCHIVE_OK) {
safe_snprintf(error, maxerrlen, "archive_read_data_block returned %d: %s"
,result, archive_error_string(ar));
break;
}
if(fwrite(buff, 1, size, fp) != size)
break;
}
fclose(fp);
if(result != ARCHIVE_EOF)
(void)remove(fpath);
}
archive_read_free(ar);
return extracted;
}
bool extract_diz(scfg_t* cfg, file_t* f, str_list_t diz_fnames, char* path, size_t maxlen)
{
int i;
char archive[MAX_PATH + 1];
char* default_diz_fnames[] = { "FILE_ID.ANS", "FILE_ID.DIZ", "DESC.SDI", NULL };
getfilepath(cfg, f, archive);
if(diz_fnames == NULL)
diz_fnames = default_diz_fnames;
if(!fexistcase(archive))
return false;
for(i = 0; diz_fnames[i] != NULL; i++) {
safe_snprintf(path, maxlen, "%s%s", cfg->temp_dir, diz_fnames[i]); // no slash
removecase(path);
if(fexistcase(path)) // failed to delete?
return false;
}
for(int nested = 0; nested <= 1; nested++) {
if(extract_files_from_archive(archive
,/* outdir: */cfg->temp_dir
,/* allowed_filename_chars: */NULL /* any */
,/* with_path: */!nested
,/* overwrite: */false
,/* max_files: */strListCount(diz_fnames)
,/* file_list: */diz_fnames
,/* error: */NULL, 0) >= 0) {
for(i = 0; diz_fnames[i] != NULL; i++) {
safe_snprintf(path, maxlen, "%s%s", cfg->temp_dir, diz_fnames[i]); // no slash
if(fexistcase(path))
return true;
}
if(nested)
return false;
}
}
for(i = 0; i < cfg->total_fextrs; i++)
if(file_type_match(f->name, cfg->fextr[i]->ext) && chk_ar(cfg, cfg->fextr[i]->ar, /* user: */NULL, /* client: */NULL))
break;
if(i >= cfg->total_fextrs)
return false;
fextr_t* fextr = cfg->fextr[i];
char cmd[512];
for(i = 0; diz_fnames[i] != NULL; i++) {
safe_snprintf(path, maxlen, "%s%s", cfg->temp_dir, diz_fnames[i]);
system(cmdstr(cfg, /* user: */NULL, fextr->cmd, archive, diz_fnames[i], cmd, sizeof(cmd)));
if(fexistcase(path))
return true;
}
return false;
}
char* read_diz(const char* path, struct sauce_charinfo* sauce)
{
if(sauce != NULL)
memset(sauce, 0, sizeof(*sauce));
off_t len = flength(path);
if(len < 1)
return NULL;
FILE* fp = fopen(path, "rb");
if(fp == NULL)
return NULL;
if(sauce != NULL)
sauce_fread_charinfo(fp, /* type: */NULL, sauce);
if(len > LEN_EXTDESC)
len = LEN_EXTDESC;
char* buf = calloc((size_t)len + 1, 1);
if(buf != NULL) {
size_t rd = fread(buf, 1, (size_t)len, fp);
buf[rd] = 0;
char* eof = strchr(buf, CTRL_Z); // CP/M EOF
if(eof != NULL)
*eof = '\0';
}
fclose(fp);
return buf;
}
void free_diz(char* buf)
{
free(buf);
}
char* format_diz(const char* src, char* dest, size_t maxlen, int width, bool ice)
{
if(src == NULL) {
*dest = '\0';
return dest;
}
convert_ansi(src, dest, maxlen - 1, width, ice);
return dest;
}
// Take a verbose extended description (e.g. FILE_ID.DIZ)
// and convert to suitable short description
char* prep_file_desc(const char* ext, char* dest)
{
int out;
char* src;
char* buf = strdup(ext);
if(buf == NULL)
return NULL;
src = buf;
strip_ctrl(src, src);
if(!str_is_ascii(src)) {
if(utf8_str_is_valid(src))
utf8_to_cp437_inplace(src);
strip_cp437_graphics(src, src);
}
FIND_ALPHANUMERIC(src);
for(out = 0; *src != '\0' && out < LEN_FDESC; src++) {
if(IS_WHITESPACE(*src) && out && IS_WHITESPACE(dest[out - 1]))
continue;
if(!IS_ALPHANUMERIC(*src) && out && *src == dest[out - 1])
continue;
if(*src == '\n') {
if(out && !IS_WHITESPACE(dest[out - 1]))
dest[out++] = ' ';
continue;
}
if(IS_CONTROL(*src))
continue;
dest[out++] = *src;
}
dest[out] = '\0';
free(buf);
return dest;
}
static const char* quoted_string(const char* str, char* buf, size_t maxlen)
{
if(strchr(str,' ')==NULL)
return(str);
safe_snprintf(buf,maxlen,"\"%s\"",str);
return(buf);
}
#define QUOTED_STRING(ch, str, buf, maxlen) \
((IS_ALPHA(ch) && IS_UPPERCASE(ch)) ? str : quoted_string(str,buf,maxlen))
/****************************************************************************/
/* Returns command line generated from instr with %c replacements */
/* This is the C-exported version of sbbs_t::cmdstr() */
/****************************************************************************/
char* cmdstr(scfg_t* cfg, user_t* user, const char* instr, const char* fpath
,const char* fspec, char* cmd, size_t maxlen)
{
char str[MAX_PATH+1];
int i,j,len;
len=strlen(instr);
for(i=j=0; i < len && j < (int)maxlen; i++) {
if(instr[i]=='%') {
i++;
cmd[j]=0;
int avail = maxlen - j;
char ch=instr[i];
if(IS_ALPHA(ch))
ch=toupper(ch);
switch(ch) {
case 'A': /* User alias */
if(user!=NULL)
strncat(cmd,QUOTED_STRING(instr[i],user->alias,str,sizeof(str)), avail);
break;
case 'B': /* Baud (DTE) Rate */
break;
case 'C': /* Connect Description */
if(user!=NULL)
strncat(cmd,user->modem, avail);
break;
case 'D': /* Connect (DCE) Rate */
break;
case 'E': /* Estimated Rate */
break;
case 'F': /* File path */
strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
break;
case 'G': /* Temp directory */
strncat(cmd,cfg->temp_dir, avail);
break;
case 'H': /* Port Handle or Hardware Flow Control */
break;
case 'I': /* IP address */
if(user!=NULL)
strncat(cmd,user->note, avail);
break;
case 'J':
strncat(cmd,cfg->data_dir, avail);
break;
case 'K':
strncat(cmd,cfg->ctrl_dir, avail);
break;
case 'L': /* Lines per message */
if(user!=NULL)
strncat(cmd,ultoa(cfg->level_linespermsg[user->level],str,10), avail);
break;
case 'M': /* Minutes (credits) for user */
if(user!=NULL)
strncat(cmd,ultoa(user->min,str,10), avail);
break;
case 'N': /* Node Directory (same as SBBSNODE environment var) */
strncat(cmd,cfg->node_dir, avail);
break;
case 'O': /* SysOp */
strncat(cmd,QUOTED_STRING(instr[i],cfg->sys_op,str,sizeof(str)), avail);
break;
case 'P': /* Client protocol */
break;
case 'Q': /* QWK ID */
strncat(cmd,cfg->sys_id, avail);
break;
case 'R': /* Rows */
if(user!=NULL)
strncat(cmd,ultoa(user->rows,str,10), avail);
break;
case 'S': /* File Spec */
strncat(cmd, fspec, avail);
break;
case 'T': /* Time left in seconds */
break;
case 'U': /* UART I/O Address (in hex) */
strncat(cmd,ultoa(cfg->com_base,str,16), avail);
break;
case 'V': /* Synchronet Version */
sprintf(str,"%s%c",VERSION,REVISION);
strncat(cmd,str, avail);
break;
case 'W': /* Columns/width */
break;
case 'X':
if(user!=NULL)
strncat(cmd,cfg->shell[user->shell]->code, avail);
break;
case '&': /* Address of msr */
break;
case 'Y':
break;
case 'Z':
strncat(cmd,cfg->text_dir, avail);
break;
case '~': /* DOS-compatible (8.3) filename */
{
#ifdef _WIN32
char sfpath[MAX_PATH+1];
SAFECOPY(sfpath,fpath);
GetShortPathName(fpath,sfpath,sizeof(sfpath));
strncat(cmd,sfpath, avail);
#else
strncat(cmd,QUOTED_STRING(instr[i],fpath,str,sizeof(str)), avail);
#endif
break;
}
case '!': /* EXEC Directory */
strncat(cmd,cfg->exec_dir, avail);
break;
case '@': /* EXEC Directory for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
strncat(cmd,cfg->exec_dir, avail);
#endif
break;
case '#': /* Node number (same as SBBSNNUM environment var) */
sprintf(str,"%d",cfg->node_num);
strncat(cmd,str, avail);
break;
case '*':
sprintf(str,"%03d",cfg->node_num);
strncat(cmd,str, avail);
break;
case '$': /* Credits */
if(user!=NULL)
strncat(cmd,_ui64toa(user_available_credits(user),str,10), avail);
break;
case '%': /* %% for percent sign */
strncat(cmd,"%", avail);
break;
case '.': /* .exe for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
strncat(cmd,".exe", avail);
#endif
break;
case '?': /* Platform */
#ifdef __OS2__
strcpy(str,"OS2");
#else
strcpy(str,PLATFORM_DESC);
#endif
strlwr(str);
strncat(cmd,str, avail);
break;
case '^': /* Architecture */
strncat(cmd, ARCHITECTURE_DESC, avail);
break;
case '+': /* Real name */
if(user != NULL)
strncat(cmd, quoted_string(user->name, str, sizeof(str)), avail);
break;
default: /* unknown specification */
if(IS_DIGIT(instr[i]) && user!=NULL) {
sprintf(str,"%0*d",instr[i]&0xf,user->number);
strncat(cmd,str, avail);
}
break;
}
j=strlen(cmd);
}
else
cmd[j++]=instr[i];
}
cmd[j]=0;
return(cmd);
}
/****************************************************************************/
/* Is this filename made up of only the "safest" characters? */
/****************************************************************************/
bool safest_filename(const char* fname)
{
return (fname != NULL) && (strspn(fname, SAFEST_FILENAME_CHARS) == strlen(fname));
}
/****************************************************************************/
/* Is this filename highly-suspicious? */
/****************************************************************************/
bool illegal_filename(const char *fname)
{
if(fname == NULL)
return false;
size_t len = strlen(fname);
if(len < 1)
return true;
if(strcspn(fname, ILLEGAL_FILENAME_CHARS) != len)
return true;
if(strstr(fname, "..") != NULL)
return true;
if(*fname == '-') // leading dash is a problem for argument parsing
return true;
if(*fname == '.') // leading dot hides files on *nix
return true;
if(*fname == ' ') // leading space is a problem for argument parsing (shells)
return true;
if(fname[len - 1] == '.') // a trailing dot is a problem for Windows
return true;
if(fname[len - 1] == ' ') // a trailing space is a problem for argument parsing (shells)
return true;
for(size_t i = 0; i < len; i++) {
if(IS_CONTROL(fname[i])) // control characters in filenames are evil
return true;
}
return false;
}
/****************************************************************************/
/* Checks if the filename chars meet the system requirements for upload */
/* Assumes the filename has already been checked with illegal_filename() */
/****************************************************************************/
bool allowed_filename(scfg_t* cfg, const char *fname)
{
if(fname == NULL)
return false;
size_t len = strlen(fname);
if(len < 1 || (cfg->filename_maxlen > 1 && len > cfg->filename_maxlen))
return false;
if(cfg->file_misc & FM_SAFEST)
return safest_filename(fname);
uchar min = (cfg->file_misc & FM_SPACES) ? ' ' : '!';
uchar max = (cfg->file_misc & FM_EXASCII) ? 0xff : 0x7f;
for(size_t i = 0; i < len; i++) {
if((uchar)fname[i] < min || (uchar)fname[i] > max)
return false;
}
return true;
}