2016-04-02 23:26:17 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <libgen.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <errno.h>
|
2016-08-07 06:37:55 +00:00
|
|
|
#include <termios.h>
|
2016-04-02 23:26:17 +00:00
|
|
|
#include "Xmodem/zmodem.h"
|
|
|
|
#include "bbs.h"
|
2016-04-12 05:19:56 +00:00
|
|
|
#include "lua/lua.h"
|
|
|
|
#include "lua/lualib.h"
|
|
|
|
#include "lua/lauxlib.h"
|
2016-04-02 23:26:17 +00:00
|
|
|
extern struct bbs_config conf;
|
2016-08-06 04:36:36 +00:00
|
|
|
extern int gSocket;
|
2016-08-07 03:45:52 +00:00
|
|
|
extern int sshBBS;
|
2016-04-02 23:26:17 +00:00
|
|
|
|
|
|
|
struct file_entry {
|
|
|
|
char *filename;
|
|
|
|
char *description;
|
|
|
|
int size;
|
|
|
|
int dlcount;
|
|
|
|
};
|
|
|
|
|
|
|
|
char **tagged_files;
|
|
|
|
int tagged_count = 0;
|
|
|
|
|
2016-08-07 06:37:55 +00:00
|
|
|
int ttySetRaw(int fd, struct termios *prevTermios) {
|
|
|
|
struct termios t;
|
|
|
|
|
|
|
|
if (tcgetattr(fd, &t) == -1)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (prevTermios != NULL)
|
|
|
|
*prevTermios = t;
|
|
|
|
|
|
|
|
t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
|
|
|
|
t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK);
|
|
|
|
t.c_oflag &= ~OPOST;
|
|
|
|
t.c_cc[VMIN] = 1;
|
|
|
|
t.c_cc[VTIME] = 0;
|
|
|
|
|
|
|
|
if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
int ZXmitStr(u_char *str, int len, ZModem *info) {
|
|
|
|
int i;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
for (i=0;i<len;i++) {
|
2016-08-07 03:45:52 +00:00
|
|
|
if (str[i] == 255 && !sshBBS) {
|
2016-04-02 23:26:17 +00:00
|
|
|
if (write(info->ofd, &str[i], 1) == 0) {
|
|
|
|
return ZmErrSys;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (write(info->ofd, &str[i], 1) == 0) {
|
|
|
|
return ZmErrSys;
|
|
|
|
}
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ZIFlush(ZModem *info) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void ZOFlush(ZModem *info) {
|
|
|
|
}
|
|
|
|
|
|
|
|
int ZAttn(ZModem *info) {
|
|
|
|
char *ptr ;
|
|
|
|
|
|
|
|
if( info->attn == NULL )
|
|
|
|
return 0 ;
|
|
|
|
|
|
|
|
for(ptr = info->attn; *ptr != '\0'; ++ptr) {
|
|
|
|
if( *ptr == ATTNBRK ) {
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
} else if( *ptr == ATTNPSE ) {
|
|
|
|
sleep(1);
|
|
|
|
} else {
|
|
|
|
write(info->ifd, ptr, 1) ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ZFlowControl(int onoff, ZModem *info) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void ZStatus(int type, int value, char *status) {
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
char *upload_path;
|
|
|
|
char upload_filename[1024];
|
|
|
|
|
|
|
|
FILE *ZOpenFile(char *name, u_long crc, ZModem *info) {
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
FILE *fptr;
|
|
|
|
struct stat s;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
snprintf(upload_filename, 1023, "%s/%s", upload_path, basename(name));
|
|
|
|
if (stat(upload_filename, &s) == 0) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
fptr = fopen(upload_filename, "wb");
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
return fptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ZWriteFile(u_char *buffer, int len, FILE *file, ZModem *info) {
|
|
|
|
return fwrite(buffer, 1, len, file) == len ? 0 : ZmErrSys;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ZCloseFile(ZModem *info) {
|
|
|
|
fclose(info->file);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ZIdleStr(u_char *buffer, int len, ZModem *info) {
|
|
|
|
}
|
|
|
|
|
|
|
|
int doIO(ZModem *zm) {
|
|
|
|
fd_set readfds;
|
|
|
|
struct timeval timeout;
|
|
|
|
u_char buffer[2048];
|
|
|
|
u_char buffer2[1024];
|
|
|
|
int len;
|
|
|
|
int pos;
|
|
|
|
int done = 0;
|
|
|
|
int i;
|
|
|
|
int j;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
while(!done) {
|
|
|
|
FD_ZERO(&readfds);
|
|
|
|
FD_SET(zm->ifd, &readfds) ;
|
|
|
|
timeout.tv_sec = zm->timeout ;
|
|
|
|
timeout.tv_usec = 0 ;
|
|
|
|
i = select(zm->ifd+1, &readfds,NULL,NULL, &timeout) ;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
if( i==0 ) {
|
|
|
|
done = ZmodemTimeout(zm) ;
|
|
|
|
} else if (i > 0) {
|
|
|
|
len = read(zm->ifd, buffer, 2048);
|
|
|
|
if (len == 0) {
|
2016-08-06 04:36:36 +00:00
|
|
|
disconnect("Socket closed");
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
pos = 0;
|
|
|
|
for (j=0;j<len;j++) {
|
2016-08-07 06:49:52 +00:00
|
|
|
if (buffer[j] == 255 && !sshBBS) {
|
2016-04-02 23:26:17 +00:00
|
|
|
if (buffer[j+1] == 255) {
|
|
|
|
buffer2[pos] = 255;
|
|
|
|
pos++;
|
|
|
|
j++;
|
|
|
|
} else {
|
|
|
|
j++;
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
buffer2[pos] = buffer[j];
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pos > 0) {
|
|
|
|
done = ZmodemRcv(buffer2, pos, zm) ;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// SIG INT catch
|
|
|
|
if (errno != EINTR) {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("SELECT ERROR %s", strerror(errno));
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return done;
|
|
|
|
}
|
|
|
|
|
2016-08-06 04:36:36 +00:00
|
|
|
void upload_zmodem(struct user_record *user) {
|
2016-04-02 23:26:17 +00:00
|
|
|
ZModem zm;
|
|
|
|
|
|
|
|
|
|
|
|
upload_path = conf.file_directories[user->cur_file_dir]->file_subs[user->cur_file_sub]->upload_path;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
zm.attn = NULL;
|
|
|
|
zm.windowsize = 0;
|
|
|
|
zm.bufsize = 0;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-08-07 03:45:52 +00:00
|
|
|
if (!sshBBS) {
|
|
|
|
zm.ifd = gSocket;
|
|
|
|
zm.ofd = gSocket;
|
|
|
|
} else {
|
|
|
|
zm.ifd = STDIN_FILENO;
|
|
|
|
zm.ofd = STDOUT_FILENO;
|
|
|
|
}
|
2016-04-02 23:26:17 +00:00
|
|
|
zm.zrinitflags = 0;
|
|
|
|
zm.zsinitflags = 0;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
zm.packetsize = 1024;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-08-06 04:55:18 +00:00
|
|
|
ZmodemRInit(&zm);
|
2016-04-02 23:26:17 +00:00
|
|
|
|
|
|
|
doIO(&zm);
|
|
|
|
}
|
|
|
|
|
2016-08-06 04:36:36 +00:00
|
|
|
void upload(struct user_record *user) {
|
2016-04-02 23:26:17 +00:00
|
|
|
char buffer[331];
|
|
|
|
char buffer2[66];
|
|
|
|
char buffer3[256];
|
|
|
|
int i;
|
|
|
|
char *create_sql = "CREATE TABLE IF NOT EXISTS files ("
|
|
|
|
"Id INTEGER PRIMARY KEY,"
|
|
|
|
"filename TEXT,"
|
|
|
|
"description TEXT,"
|
|
|
|
"size INTEGER,"
|
|
|
|
"dlcount INTEGER,"
|
|
|
|
"approved INTEGER);";
|
|
|
|
char *sql = "INSERT INTO files (filename, description, size, dlcount, approved) VALUES(?, ?, ?, 0, 0)";
|
|
|
|
sqlite3 *db;
|
2016-08-06 04:36:36 +00:00
|
|
|
sqlite3_stmt *res;
|
|
|
|
int rc;
|
|
|
|
struct stat s;
|
|
|
|
char *err_msg = NULL;
|
2016-08-07 06:43:32 +00:00
|
|
|
struct termios oldit;
|
2016-08-07 06:49:52 +00:00
|
|
|
struct termios oldot;
|
2016-08-07 06:37:55 +00:00
|
|
|
if (sshBBS) {
|
2016-08-07 06:43:32 +00:00
|
|
|
ttySetRaw(STDIN_FILENO, &oldit);
|
|
|
|
ttySetRaw(STDOUT_FILENO, &oldot);
|
2016-08-07 06:37:55 +00:00
|
|
|
}
|
2016-08-06 04:36:36 +00:00
|
|
|
upload_zmodem(user);
|
2016-08-07 06:37:55 +00:00
|
|
|
if (sshBBS) {
|
2016-08-07 06:43:32 +00:00
|
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &oldit);
|
|
|
|
tcsetattr(STDOUT_FILENO, TCSANOW, &oldot);
|
2016-08-07 06:37:55 +00:00
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf("\r\nPlease enter a description:\r\n");
|
2016-04-02 23:26:17 +00:00
|
|
|
buffer[0] = '\0';
|
|
|
|
for (i=0;i<5;i++) {
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf("\r\n%d: ", i);
|
|
|
|
s_readstring(buffer2, 65);
|
2016-04-02 23:26:17 +00:00
|
|
|
if (strlen(buffer2) == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
strcat(buffer, buffer2);
|
|
|
|
strcat(buffer, "\n");
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
sprintf(buffer3, "%s/%s.sq3", conf.bbs_path, conf.file_directories[user->cur_file_dir]->file_subs[user->cur_file_sub]->database);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
rc = sqlite3_open(buffer3, &db);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
if (rc != SQLITE_OK) {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("Cannot open database: %s", sqlite3_errmsg(db));
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_close(db);
|
|
|
|
exit(1);
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
rc = sqlite3_exec(db, create_sql, 0, 0, &err_msg);
|
2016-08-04 09:25:49 +00:00
|
|
|
if (rc != SQLITE_OK ) {
|
|
|
|
dolog("SQL error: %s", err_msg);
|
|
|
|
sqlite3_free(err_msg);
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_close(db);
|
|
|
|
return;
|
2016-08-04 09:25:49 +00:00
|
|
|
}
|
2016-04-02 23:26:17 +00:00
|
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
|
|
|
if (rc == SQLITE_OK) {
|
2016-04-02 23:26:17 +00:00
|
|
|
stat(upload_filename, &s);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_bind_text(res, 1, upload_filename, -1, 0);
|
|
|
|
sqlite3_bind_text(res, 2, buffer, -1, 0);
|
2016-08-04 09:25:49 +00:00
|
|
|
sqlite3_bind_int(res, 3, s.st_size);
|
2016-04-02 23:26:17 +00:00
|
|
|
} else {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("Failed to execute statement: %s", sqlite3_errmsg(db));
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_finalize(res);
|
2016-08-04 09:25:49 +00:00
|
|
|
sqlite3_close(db);
|
|
|
|
return;
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
rc = sqlite3_step(res);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
if (rc != SQLITE_DONE) {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("execution failed: %s", sqlite3_errmsg(db));
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_finalize(res);
|
2016-08-04 09:25:49 +00:00
|
|
|
sqlite3_close(db);
|
2016-04-02 23:26:17 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
sqlite3_finalize(res);
|
|
|
|
sqlite3_close(db);
|
|
|
|
}
|
|
|
|
|
2016-08-06 04:36:36 +00:00
|
|
|
void download_zmodem(struct user_record *user, char *filename) {
|
2016-04-02 23:26:17 +00:00
|
|
|
ZModem zm;
|
|
|
|
int done ;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
|
|
|
dolog("Attempting to upload %s", filename);
|
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
zm.attn = NULL;
|
|
|
|
zm.windowsize = 0;
|
|
|
|
zm.bufsize = 0;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-08-07 03:45:52 +00:00
|
|
|
if (!sshBBS) {
|
|
|
|
zm.ifd = gSocket;
|
|
|
|
zm.ofd = gSocket;
|
|
|
|
} else {
|
|
|
|
zm.ifd = STDIN_FILENO;
|
|
|
|
zm.ofd = STDOUT_FILENO;
|
|
|
|
}
|
2016-04-02 23:26:17 +00:00
|
|
|
zm.zrinitflags = 0;
|
|
|
|
zm.zsinitflags = 0;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
zm.packetsize = 1024;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
ZmodemTInit(&zm) ;
|
|
|
|
done = doIO(&zm);
|
|
|
|
if ( done != ZmDone ) {
|
|
|
|
return;
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
done = ZmodemTFile(filename, basename(filename), ZCBIN,0,0,0,0,0, &zm) ;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
switch( done ) {
|
2016-08-04 09:25:49 +00:00
|
|
|
case 0:
|
2016-04-02 23:26:17 +00:00
|
|
|
break ;
|
|
|
|
|
|
|
|
case ZmErrCantOpen:
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("cannot open file \"%s\": %s\n", filename, strerror(errno)) ;
|
2016-04-02 23:26:17 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
case ZmFileTooLong:
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("filename \"%s\" too long, skipping...\n", filename) ;
|
2016-04-02 23:26:17 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
case ZmDone:
|
|
|
|
return;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
if (!done) {
|
|
|
|
done = doIO(&zm);
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
if ( done != ZmDone ) {
|
|
|
|
return;
|
2016-08-04 09:25:49 +00:00
|
|
|
}
|
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
done = ZmodemTFinish(&zm);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
if (!done) {
|
|
|
|
done = doIO(&zm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-06 04:36:36 +00:00
|
|
|
void download(struct user_record *user) {
|
2016-04-02 23:26:17 +00:00
|
|
|
int i;
|
|
|
|
char *ssql = "select dlcount from files where filename like ?";
|
|
|
|
char *usql = "update files set dlcount=? where filename like ?";
|
|
|
|
char buffer[256];
|
|
|
|
int dloads;
|
|
|
|
sqlite3 *db;
|
2016-08-06 04:55:18 +00:00
|
|
|
sqlite3_stmt *res;
|
|
|
|
int rc;
|
2016-08-07 06:53:28 +00:00
|
|
|
struct termios oldit;
|
|
|
|
struct termios oldot;
|
|
|
|
if (sshBBS) {
|
|
|
|
ttySetRaw(STDIN_FILENO, &oldit);
|
|
|
|
ttySetRaw(STDOUT_FILENO, &oldot);
|
|
|
|
}
|
2016-04-02 23:26:17 +00:00
|
|
|
for (i=0;i<tagged_count;i++) {
|
2016-08-07 06:37:55 +00:00
|
|
|
|
2016-08-06 04:36:36 +00:00
|
|
|
download_zmodem(user, tagged_files[i]);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
sprintf(buffer, "%s/%s.sq3", conf.bbs_path, conf.file_directories[user->cur_file_dir]->file_subs[user->cur_file_sub]->database);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
rc = sqlite3_open(buffer, &db);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
if (rc != SQLITE_OK) {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("Cannot open database: %s", sqlite3_errmsg(db));
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_close(db);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(db, ssql, -1, &res, 0);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
|
|
|
if (rc == SQLITE_OK) {
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_bind_text(res, 1, tagged_files[i], -1, 0);
|
|
|
|
} else {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("Failed to execute statement: %s", sqlite3_errmsg(db));
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
|
|
|
rc = sqlite3_step(res);
|
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
if (rc != SQLITE_ROW) {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("Downloaded a file not in database!!!!!");
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_finalize(res);
|
|
|
|
sqlite3_close(db);
|
|
|
|
exit(1);
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
dloads = sqlite3_column_int(res, 0);
|
|
|
|
dloads++;
|
|
|
|
sqlite3_finalize(res);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
rc = sqlite3_prepare_v2(db, usql, -1, &res, 0);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
|
|
|
if (rc == SQLITE_OK) {
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_bind_int(res, 1, dloads);
|
|
|
|
sqlite3_bind_text(res, 2, tagged_files[i], -1, 0);
|
|
|
|
} else {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("Failed to execute statement: %s", sqlite3_errmsg(db));
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
|
|
|
rc = sqlite3_step(res);
|
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_finalize(res);
|
|
|
|
sqlite3_close(db);
|
|
|
|
}
|
2016-08-08 07:21:04 +00:00
|
|
|
|
2016-08-07 06:53:28 +00:00
|
|
|
if (sshBBS) {
|
|
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &oldit);
|
|
|
|
tcsetattr(STDOUT_FILENO, TCSANOW, &oldot);
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
for (i=0;i<tagged_count;i++) {
|
|
|
|
free(tagged_files[i]);
|
|
|
|
}
|
|
|
|
free(tagged_files);
|
|
|
|
tagged_count = 0;
|
|
|
|
}
|
|
|
|
|
2016-08-06 04:36:36 +00:00
|
|
|
void list_files(struct user_record *user) {
|
2016-04-02 23:26:17 +00:00
|
|
|
char *sql = "select filename, description, size, dlcount from files where approved=1";
|
|
|
|
char buffer[256];
|
|
|
|
sqlite3 *db;
|
|
|
|
sqlite3_stmt *res;
|
|
|
|
int rc;
|
|
|
|
int files_c;
|
|
|
|
int file_size;
|
|
|
|
char file_unit;
|
|
|
|
int lines = 0;
|
|
|
|
int i;
|
|
|
|
int j;
|
|
|
|
int z;
|
|
|
|
int k;
|
|
|
|
int match;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
struct file_entry **files_e;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
sprintf(buffer, "%s/%s.sq3", conf.bbs_path, conf.file_directories[user->cur_file_dir]->file_subs[user->cur_file_sub]->database);
|
|
|
|
|
|
|
|
rc = sqlite3_open(buffer, &db);
|
|
|
|
if (rc != SQLITE_OK) {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("Cannot open database: %s", sqlite3_errmsg(db));
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_close(db);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
|
|
|
if (rc != SQLITE_OK) {
|
2016-04-02 23:26:17 +00:00
|
|
|
sqlite3_finalize(res);
|
|
|
|
sqlite3_close(db);
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(68));
|
2016-08-04 09:25:49 +00:00
|
|
|
return;
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
files_c = 0;
|
|
|
|
|
|
|
|
while (sqlite3_step(res) == SQLITE_ROW) {
|
|
|
|
if (files_c == 0) {
|
|
|
|
files_e = (struct file_entry **)malloc(sizeof(struct file_entry *));
|
|
|
|
} else {
|
|
|
|
files_e = (struct file_entry **)realloc(files_e, sizeof(struct file_entry *) * (files_c + 1));
|
|
|
|
}
|
|
|
|
files_e[files_c] = (struct file_entry *)malloc(sizeof(struct file_entry));
|
|
|
|
files_e[files_c]->filename = strdup((char *)sqlite3_column_text(res, 0));
|
|
|
|
files_e[files_c]->description = strdup((char *)sqlite3_column_text(res, 1));
|
|
|
|
files_e[files_c]->size = sqlite3_column_int(res, 2);
|
|
|
|
files_e[files_c]->dlcount = sqlite3_column_int(res, 3);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
files_c++;
|
|
|
|
}
|
|
|
|
sqlite3_finalize(res);
|
2016-08-04 09:25:49 +00:00
|
|
|
sqlite3_close(db);
|
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
if (files_c == 0) {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(68));
|
2016-08-04 09:25:49 +00:00
|
|
|
return;
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf("\r\n");
|
2016-04-02 23:26:17 +00:00
|
|
|
for (i=0;i<files_c;i++) {
|
|
|
|
file_size = files_e[i]->size;
|
|
|
|
if (file_size > 1024 * 1024 * 1024) {
|
|
|
|
file_size = file_size / 1024 / 1024 / 1024;
|
|
|
|
file_unit = 'G';
|
|
|
|
} else if (file_size > 1024 * 1024) {
|
|
|
|
file_size = file_size / 1024 / 1024;
|
|
|
|
file_unit = 'M';
|
|
|
|
} else if (file_size > 1024) {
|
|
|
|
file_size = file_size / 1024;
|
|
|
|
file_unit = 'K';
|
|
|
|
} else {
|
|
|
|
file_unit = 'b';
|
|
|
|
}
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(69), i, files_e[i]->dlcount, file_size, file_unit, basename(files_e[i]->filename));
|
2016-04-03 05:26:25 +00:00
|
|
|
lines+=3;
|
2016-04-02 23:26:17 +00:00
|
|
|
for (j=0;j<strlen(files_e[i]->description);j++) {
|
|
|
|
if (files_e[i]->description[j] == '\n') {
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf("\r\n");
|
2016-04-02 23:26:17 +00:00
|
|
|
lines++;
|
2016-04-03 05:26:25 +00:00
|
|
|
if (lines >= 18) {
|
|
|
|
lines = 0;
|
2016-04-02 23:26:17 +00:00
|
|
|
while (1) {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(70));
|
2016-08-06 04:36:36 +00:00
|
|
|
s_readstring(buffer, 5);
|
2016-04-02 23:26:17 +00:00
|
|
|
if (strlen(buffer) == 0) {
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf("\r\n");
|
2016-04-02 23:26:17 +00:00
|
|
|
break;
|
|
|
|
} else if (tolower(buffer[0]) == 'q') {
|
|
|
|
for (z=0;z<files_c;z++) {
|
|
|
|
free(files_e[z]->filename);
|
|
|
|
free(files_e[z]->description);
|
|
|
|
free(files_e[z]);
|
|
|
|
}
|
|
|
|
free(files_e);
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf("\r\n");
|
2016-04-02 23:26:17 +00:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
z = atoi(buffer);
|
|
|
|
if (z >= 0 && z < files_c) {
|
|
|
|
if (conf.file_directories[user->cur_file_dir]->file_subs[user->cur_file_sub]->download_sec_level <= user->sec_level) {
|
|
|
|
match = 0;
|
|
|
|
for (k=0;k<tagged_count;k++) {
|
|
|
|
if (strcmp(tagged_files[k], files_e[z]->filename) == 0) {
|
|
|
|
match = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (match == 0) {
|
|
|
|
if (tagged_count == 0) {
|
|
|
|
tagged_files = (char **)malloc(sizeof(char *));
|
|
|
|
} else {
|
|
|
|
tagged_files = (char **)realloc(tagged_files, sizeof(char *) * (tagged_count + 1));
|
|
|
|
}
|
|
|
|
tagged_files[tagged_count] = strdup(files_e[z]->filename);
|
|
|
|
tagged_count++;
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(71), basename(files_e[z]->filename));
|
2016-04-02 23:26:17 +00:00
|
|
|
} else {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(72));
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
} else {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(73));
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(74));
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
} else {
|
2016-08-06 04:36:36 +00:00
|
|
|
s_putchar(files_e[i]->description[j]);
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
}
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
while (1) {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(75));
|
2016-08-06 04:36:36 +00:00
|
|
|
s_readstring(buffer, 5);
|
2016-04-02 23:26:17 +00:00
|
|
|
if (strlen(buffer) == 0) {
|
|
|
|
for (z=0;z<files_c;z++) {
|
|
|
|
free(files_e[z]->filename);
|
|
|
|
free(files_e[z]->description);
|
|
|
|
free(files_e[z]);
|
|
|
|
}
|
|
|
|
free(files_e);
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf("\r\n");
|
2016-04-02 23:26:17 +00:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
z = atoi(buffer);
|
|
|
|
if (z >= 0 && z < files_c) {
|
|
|
|
if (conf.file_directories[user->cur_file_dir]->file_subs[user->cur_file_sub]->download_sec_level <= user->sec_level) {
|
|
|
|
match = 0;
|
|
|
|
for (k=0;k<tagged_count;k++) {
|
|
|
|
if (strcmp(tagged_files[k], files_e[z]->filename) == 0) {
|
|
|
|
match = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (match == 0) {
|
|
|
|
if (tagged_count == 0) {
|
|
|
|
tagged_files = (char **)malloc(sizeof(char *));
|
|
|
|
} else {
|
|
|
|
tagged_files = (char **)realloc(tagged_files, sizeof(char *) * (tagged_count + 1));
|
|
|
|
}
|
|
|
|
tagged_files[tagged_count] = strdup(files_e[z]->filename);
|
|
|
|
tagged_count++;
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(71), basename(files_e[z]->filename));
|
2016-04-02 23:26:17 +00:00
|
|
|
} else {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(72));
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
} else {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(73));
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
}
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-06 04:36:36 +00:00
|
|
|
int file_menu(struct user_record *user) {
|
2016-04-02 23:26:17 +00:00
|
|
|
int doquit = 0;
|
|
|
|
int dofiles = 0;
|
|
|
|
char c;
|
|
|
|
int i;
|
|
|
|
int j;
|
|
|
|
char prompt[256];
|
2016-04-12 05:19:56 +00:00
|
|
|
struct stat s;
|
|
|
|
int do_internal_menu = 0;
|
|
|
|
char *lRet;
|
|
|
|
lua_State *L;
|
|
|
|
int result;
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-12 05:19:56 +00:00
|
|
|
if (conf.script_path != NULL) {
|
|
|
|
sprintf(prompt, "%s/filemenu.lua", conf.script_path);
|
|
|
|
if (stat(prompt, &s) == 0) {
|
|
|
|
L = luaL_newstate();
|
|
|
|
luaL_openlibs(L);
|
|
|
|
lua_push_cfunctions(L);
|
|
|
|
luaL_loadfile(L, prompt);
|
|
|
|
do_internal_menu = 0;
|
|
|
|
result = lua_pcall(L, 0, 1, 0);
|
|
|
|
if (result) {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("Failed to run script: %s", lua_tostring(L, -1));
|
2016-04-12 05:19:56 +00:00
|
|
|
do_internal_menu = 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_internal_menu = 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_internal_menu = 1;
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
while (!dofiles) {
|
2016-04-12 05:19:56 +00:00
|
|
|
if (do_internal_menu == 1) {
|
2016-08-06 04:36:36 +00:00
|
|
|
s_displayansi("filemenu");
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(76), user->cur_file_dir, conf.file_directories[user->cur_file_dir]->name, user->cur_file_sub, conf.file_directories[user->cur_file_dir]->file_subs[user->cur_file_sub]->name, user->timeleft);
|
2016-08-04 09:25:49 +00:00
|
|
|
|
2016-08-06 04:36:36 +00:00
|
|
|
c = s_getc();
|
2016-04-12 05:19:56 +00:00
|
|
|
} else {
|
|
|
|
lua_getglobal(L, "menu");
|
|
|
|
result = lua_pcall(L, 0, 1, 0);
|
|
|
|
if (result) {
|
2016-08-04 09:25:49 +00:00
|
|
|
dolog("Failed to run script: %s", lua_tostring(L, -1));
|
2016-04-12 05:19:56 +00:00
|
|
|
do_internal_menu = 1;
|
|
|
|
lua_close(L);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
lRet = (char *)lua_tostring(L, -1);
|
|
|
|
lua_pop(L, 1);
|
2016-08-04 09:25:49 +00:00
|
|
|
c = lRet[0];
|
2016-04-12 05:19:56 +00:00
|
|
|
}
|
2016-04-02 23:26:17 +00:00
|
|
|
switch(tolower(c)) {
|
|
|
|
case 'i':
|
|
|
|
{
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(77));
|
2016-04-02 23:26:17 +00:00
|
|
|
for (i=0;i<conf.file_directory_count;i++) {
|
|
|
|
if (conf.file_directories[i]->sec_level <= user->sec_level) {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(78), i, conf.file_directories[i]->name);
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
2016-04-03 05:26:25 +00:00
|
|
|
if (i != 0 && i % 20 == 0) {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(6));
|
2016-08-06 04:36:36 +00:00
|
|
|
c = s_getc();
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
}
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(79));
|
2016-08-06 04:36:36 +00:00
|
|
|
s_readstring(prompt, 5);
|
2016-04-02 23:26:17 +00:00
|
|
|
if (tolower(prompt[0]) != 'q') {
|
|
|
|
j = atoi(prompt);
|
|
|
|
if (j < 0 || j >= conf.file_directory_count || conf.file_directories[j]->sec_level > user->sec_level) {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(80));
|
2016-04-02 23:26:17 +00:00
|
|
|
} else {
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf("\r\n");
|
2016-04-02 23:26:17 +00:00
|
|
|
user->cur_file_dir = j;
|
|
|
|
user->cur_file_sub = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
{
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(81));
|
2016-04-02 23:26:17 +00:00
|
|
|
for (i=0;i<conf.file_directories[user->cur_file_dir]->file_sub_count;i++) {
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf(" %d. %s\r\n", i, conf.file_directories[user->cur_file_dir]->file_subs[i]->name);
|
2016-04-02 23:26:17 +00:00
|
|
|
|
2016-04-03 05:26:25 +00:00
|
|
|
if (i != 0 && i % 20 == 0) {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(6));
|
2016-08-06 04:36:36 +00:00
|
|
|
c = s_getc();
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
}
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(82));
|
2016-08-06 04:36:36 +00:00
|
|
|
s_readstring(prompt, 5);
|
2016-04-02 23:26:17 +00:00
|
|
|
if (tolower(prompt[0]) != 'q') {
|
|
|
|
j = atoi(prompt);
|
|
|
|
if (j < 0 || j >= conf.file_directories[user->cur_file_dir]->file_sub_count) {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(83));
|
2016-04-02 23:26:17 +00:00
|
|
|
} else {
|
2016-08-06 04:36:36 +00:00
|
|
|
s_printf("\r\n");
|
2016-04-02 23:26:17 +00:00
|
|
|
user->cur_file_sub = j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-04 09:25:49 +00:00
|
|
|
break;
|
2016-04-02 23:26:17 +00:00
|
|
|
case 'l':
|
2016-08-06 04:36:36 +00:00
|
|
|
list_files(user);
|
2016-04-02 23:26:17 +00:00
|
|
|
break;
|
|
|
|
case 'u':
|
|
|
|
{
|
|
|
|
if (user->sec_level >= conf.file_directories[user->cur_file_dir]->file_subs[user->cur_file_sub]->upload_sec_level) {
|
2016-08-06 04:36:36 +00:00
|
|
|
upload(user);
|
2016-04-02 23:26:17 +00:00
|
|
|
} else {
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(84));
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'd':
|
2016-08-06 04:36:36 +00:00
|
|
|
download(user);
|
2016-04-02 23:26:17 +00:00
|
|
|
break;
|
|
|
|
case 'c':
|
|
|
|
{
|
2016-08-04 09:25:49 +00:00
|
|
|
// Clear tagged files
|
2016-04-02 23:26:17 +00:00
|
|
|
if (tagged_count > 0) {
|
|
|
|
for (i=0;i<tagged_count;i++) {
|
|
|
|
free(tagged_files[i]);
|
|
|
|
}
|
|
|
|
free(tagged_files);
|
|
|
|
tagged_count = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '}':
|
|
|
|
{
|
2016-08-08 07:21:04 +00:00
|
|
|
|
2016-04-02 23:26:17 +00:00
|
|
|
for (i=user->cur_file_dir;i<conf.file_directory_count;i++) {
|
|
|
|
if (i + 1 == conf.file_directory_count) {
|
|
|
|
i = -1;
|
|
|
|
}
|
|
|
|
if (conf.file_directories[i+1]->sec_level <= user->sec_level) {
|
|
|
|
user->cur_file_dir = i + 1;
|
|
|
|
user->cur_file_sub = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '{':
|
|
|
|
{
|
|
|
|
for (i=user->cur_file_dir;i>=0;i--) {
|
|
|
|
if (i - 1 == -1) {
|
|
|
|
i = conf.file_directory_count;
|
|
|
|
}
|
|
|
|
if (conf.file_directories[i-1]->sec_level <= user->sec_level) {
|
|
|
|
user->cur_file_dir = i - 1;
|
|
|
|
user->cur_file_sub = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ']':
|
|
|
|
{
|
|
|
|
i=user->cur_file_sub;
|
|
|
|
if (i + 1 == conf.file_directories[user->cur_file_dir]->file_sub_count) {
|
|
|
|
i = -1;
|
|
|
|
}
|
2016-04-03 05:32:41 +00:00
|
|
|
user->cur_file_sub = i + 1;
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '[':
|
|
|
|
{
|
|
|
|
i=user->cur_file_sub;
|
|
|
|
if (i - 1 == -1) {
|
|
|
|
i = conf.file_directories[user->cur_file_dir]->file_sub_count;
|
|
|
|
}
|
2016-04-03 05:32:41 +00:00
|
|
|
user->cur_file_sub = i - 1;
|
2016-04-02 23:26:17 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'q':
|
|
|
|
dofiles = 1;
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
|
|
{
|
2016-08-14 09:56:15 +00:00
|
|
|
s_printf(get_string(53));
|
2016-08-06 04:36:36 +00:00
|
|
|
c = s_getc();
|
2016-04-02 23:26:17 +00:00
|
|
|
if (tolower(c) == 'y') {
|
|
|
|
dofiles = 1;
|
|
|
|
doquit = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-04-12 05:19:56 +00:00
|
|
|
if (do_internal_menu == 0) {
|
|
|
|
lua_close(L);
|
|
|
|
}
|
2016-04-02 23:26:17 +00:00
|
|
|
return doquit;
|
|
|
|
}
|