#include #include #include #include #include #include #include #include #ifdef USE_THREADS #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_BINDADDR "127.0.0.1" #define DEFAULT_PORT "2222" #define DEFAULT_DOCROOT "/usr/local/apache2/htdocs" #define DEFAULT_SERVERNAME "www.example.com" #define DEFAULT_SUBJECT_TXT_INTERVAL 5 #define DEFAULT_SETTING_INTERVAL 60 #define DEFAULT_NTHR_MAX 32 #define DEFAULT_RCVBUFSIZE 2097152 #define RES_MAX 1000 #define DATSIZE_MAX 512000 #define RESMAX_WIDEBUFSIZE 9 #define MD5SEED_LENGTH 16 #define CHKID_AGE 3600 #define NEWTHR_RETRY 16 #define SYSLOG_FACILITY LOG_USER #define UMASK 022 #define READBUFSIZE 65536 #define NTHR_RANGE_RATIO 8 #define HASH_FACTOR 33 #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE !FALSE #endif #if !defined SIGPOLL && defined SIGIO #define SIGPOLL SIGIO #endif #if !defined CLOCK_HIGHRES && defined CLOCK_MONOTONIC #define CLOCK_HIGHRES CLOCK_MONOTONIC #endif #ifndef USE_THREADS #if 0 typedef void *pthread_cond_t; typedef void *pthread_mutex_t; typedef void *pthread_rwlock_t; #else #define pthread_cond_t int #define pthread_mutex_t int #define pthread_rwlock_t int #endif #define pthread_cond_init(cond, attr) 0 #define pthread_mutex_init(mutex, attr) 0 #define pthread_mutex_destroy(mutex) (void)0 #define pthread_rwlock_init(rwlock, attr) 0 #define pthread_rwlock_destroy(rwlock) (void)0 #define pthread_mutex_lock(mutex) (void)0 #define pthread_mutex_unlock(mutex) (void)0 #define pthread_rwlock_rdlock(rwlock) (void)0 #define pthread_rwlock_wrlock(rwlock) (void)0 #define pthread_rwlock_unlock(rwlock) (void)0 #else /* !USE_THREADS */ #define sigprocmask pthread_sigmask #define _pthread_abort_on_error(func, ...) do { \ if (pthread_ ## func(__VA_ARGS__)) \ abort(); \ } while(FALSE) #define pthread_cond_signal(cond) _pthread_abort_on_error(cond_signal, cond) #define pthread_cond_broadcast(cond) _pthread_abort_on_error(cond_broadcast, cond) #define pthread_cond_wait(cond, mutex) _pthread_abort_on_error(cond_wait, cond, mutex) #define pthread_mutex_lock(mutex) _pthread_abort_on_error(mutex_lock, mutex) #define pthread_mutex_unlock(mutex) _pthread_abort_on_error(mutex_unlock, mutex) #define pthread_rwlock_rdlock(rwlock) _pthread_abort_on_error(rwlock_rdlock, rwlock) #define pthread_rwlock_wrlock(rwlock) _pthread_abort_on_error(rwlock_wrlock, rwlock) #define pthread_rwlock_unlock(rwlock) _pthread_abort_on_error(rwlock_unlock, rwlock) #endif /* !USE_THREADS */ #define arraylen(a) (sizeof a / sizeof *a) #define syslogx(priority, ...) do { \ sigset_t oset; \ sigprocmask(SIG_SETMASK, &filledsigset, &oset); \ syslog(priority, __VA_ARGS__); \ sigprocmask(SIG_SETMASK, &oset, NULL); \ } while(FALSE) enum { NOTE_footnote, NOTE_adfile1, NOTE_adfile2, NOTE_adfile3, NOTE_adline, NUM_NOTES }; typedef struct subject { struct subject *next; struct chktimecount *chktimecounts; uint64_t key; unsigned n; pthread_mutex_t mutex, chktimecounts_mutex; char title[1]; } subject_t; typedef struct setting { struct setting *next; unsigned hash; size_t key_len; char key_value[1]; } setting_t; typedef struct chkthr { struct chkthr *next; int key; char value[1]; } chkthr_t; typedef struct chktimecount { struct chktimecount *next; unsigned hash; size_t len; time_t time; char id[1]; } chktimecount_t; typedef struct chkid { struct chkid *next; unsigned hash; size_t n, len; time_t time; char id[1]; } chkid_t; typedef struct chkiddb { struct chkiddb *next; chkid_t *ids; unsigned name_hash; size_t name_len; pthread_mutex_t mutex; char name[1]; } chkiddb_t; typedef struct board { struct board *next; char *md5seed, *notes[NUM_NOTES]; subject_t *subjects; setting_t *settings; chkthr_t *chkthrs; chktimecount_t *chktimecounts; chkiddb_t *chkiddbs; volatile int subjects_isdirty:1; unsigned name_hash; size_t name_len; volatile time_t subject_txt_mtime; time_t subject_html_mtime, settings_mtime, settings_lastchk, notes_lastchk; pthread_mutex_t subjects_mutex, chkthrs_mutex, chktimecounts_mutex, chkiddbs_mutex; pthread_rwlock_t settings_rwlock, md5seed_rwlock, notes_rwlock; char name[1]; } board_t; typedef struct { char *bbs, *datline, *title; board_t *board; int issage:1; uint64_t key; } request_t; typedef struct { char *writebuf; pthread_rwlock_t *writebuf_rwlock; time_t now; size_t writesize; ssize_t readsize; char readbuf[READBUFSIZE], paths[2][PATH_MAX + 1]; } worker_t; typedef struct { const char *argv0, *docroot, *name; board_t *boards; struct addrinfo *addrinfo; int sock, rcvbufsize, sndbufsize, ischkseq:1, isforeground:1, isbbspink:1, isrecvok:1; volatile int isterminate:1; size_t nthr_max, nthr_min, nthr_cur, nthr_idle; time_t subject_txt_interval, subject_html_interval, settings_interval; pthread_cond_t sock_cond, thr_cond; pthread_mutex_t cond_mutex, boards_mutex; struct timespec starttime; } server_t; typedef struct { const char *name; char delim; unsigned hash; size_t len; } command_t; extern char *optarg; extern int optind, opterr, optopt; static volatile int isterminate = FALSE; static sigset_t filledsigset; static const int ignoresignals[] = { SIGPIPE, SIGALRM, SIGUSR1, SIGUSR2, SIGPOLL, SIGVTALRM, SIGPROF }, coredumpsignals[] = { SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGEMT, SIGFPE, SIGBUS, SIGSEGV, SIGSYS, SIGXCPU, SIGXFSZ }; enum { COMMAND_chkid, COMMAND_peekid, COMMAND_clearids, COMMAND_countids, COMMAND_chktimecount, COMMAND_chkthrtimecount, COMMAND_chkthr, COMMAND_getndats, COMMAND_getmd5seed, COMMAND_get1, COMMAND_raise, COMMAND_touch, COMMAND_cp, COMMAND_getfilesize, COMMAND_stat, COMMAND_repair, COMMAND_makehtml, COMMAND_delete, COMMAND_tdelete, COMMAND_move, COMMAND_stop, COMMAND_restart, COMMAND_inject, COMMAND_purge, COMMAND_autopurge, COMMAND_unload }; static command_t commands[] = { {"chkid", '\x8'}, {"peekid", '\x8'}, {"clearids", '\x8'}, {"countids", '\x8'}, {"chktimecount", '\x8'}, {"chkthrtimecount", '\x8'}, {"chkthr", '\x8'}, {"getndats", 0}, {"getmd5seed", 0}, {"get1", '\x8'}, {"raise", '\x8'}, {"touch", '\x8'}, {"cp", '\x8'}, {"getfilesize", 0}, {"stat", 0}, {"repair", 0}, {"makehtml", 0}, {"delete", ':'}, {"tdelete", ':'}, {"move", ':'}, {"stop", ':'}, {"restart", ':'}, {"inject", ':'}, {"purge", '\x8'}, {"autopurge", 0}, {"unload", 0} }; void printusage(const server_t *server) { fprintf(stderr, "usage: %s [-cfh] [-b bindaddr] [-B rcvbuf[:sndbuf]] [-d docroot] [-i subject_txt_interval[:subject_html_interval]] [-I settings_interval] " #ifdef USE_THREADS "[-n [nthr_min-]nthr_max] [-p port] [-s servername]\n" #else "[-p port] [-s servername]\n" #endif "\t-b bindaddr: bind UDP socket to this address [%s]\n" "\t-B rcvbuf[:sndbuf]: receive / send buffer size of UDP socket [rcv = %u, snd = rcv]\n" "\t-c: check byte sequence of inputs (dependent on envvar LANG/LC_CTYPE/LC_ALL)\n" "\t-d docroot: document root of httpd [%s]\n" "\t-f: run in foreground\n" "\t-h: this help\n" "\t-i subject_txt_interval[:subject_html_interval]: interval(sec) of subject file modification [txt = %u, html = txt]\n" "\t-I settings_interval: interval(sec) of setting file check [%u]\n" #ifdef USE_THREADS "\t-n [nthr_min-]nthr_max: number of worker threads [max = %u, min = ceil(max / %u)]\n" #endif "\t-p port: port number of UDP socket [%s]\n" "\t-s servername: server name of httpd [%s]\n", server->argv0, DEFAULT_BINDADDR, DEFAULT_RCVBUFSIZE, DEFAULT_DOCROOT, DEFAULT_SUBJECT_TXT_INTERVAL, DEFAULT_SETTING_INTERVAL, #ifdef USE_THREADS DEFAULT_NTHR_MAX, NTHR_RANGE_RATIO, DEFAULT_PORT, DEFAULT_SERVERNAME); #else DEFAULT_PORT, DEFAULT_SERVERNAME); #endif } static int isinvalidseq(const char *s, size_t n) { size_t len; mbstate_t mbs; memset(&mbs, 0, sizeof mbs); for (; n; s += len, n -= len) if ((len = mbrlen(s, n, &mbs)) == (size_t)-1) return TRUE; else if (len == (size_t)-2) { errno = EILSEQ; return TRUE; } return FALSE; } static void *_memchrmb(const void *s, int c, size_t n) { const char *_s = s; size_t len; mbstate_t mbs; memset(&mbs, 0, sizeof mbs); for (; n; _s += len, n -= len) if (*_s == c) return (void *)_s; else if ((len = mbrlen(_s, n, &mbs)) == (size_t)-1) break; else if (len == (size_t)-2) { errno = EILSEQ; break; } return NULL; } static const void *memstrmb(const void *s1, const char *s2, size_t n, int ismb) { const char *p1, *p2; size_t s2len = strlen(s2); void *(*_memchrx)(const void *, int, size_t) = ismb ? _memchrmb : memchr; if (n < s2len) return NULL; else if (!s2len) return s1; else n -= --s2len; for (p1 = s1; (p2 = _memchrx(p1, *s2, n)) && memcmp(++p2, s2 + 1, s2len); n -= p2 - p1, p1 = p2) ; return p2 ? --p2 : NULL; } #define memstr(s1, s2, n) memstrmb(s1, s2, n, FALSE) void sighandler(int sig) { switch (sig) { case SIGHUP: /* nothing to do... */ break; case SIGINT: case SIGTERM: syslogx(LOG_INFO, "Received %s. Terminating...", strsignal(sig)); isterminate = TRUE; break; default: syslogx(LOG_CRIT, "%s occurred. Core dumping to /tmp...", strsignal(sig)); chdir("/tmp"); kill(getpid(), sig); } } int parse_argv(server_t *server, int argc, char *const *argv) { const char *p, *bindaddr, *port; int c; struct stat st; struct addrinfo ai_hints; server->argv0 = (p = strrchr(*argv, '/')) ? ++p : *argv; bindaddr = DEFAULT_BINDADDR; port = DEFAULT_PORT; server->rcvbufsize = server->sndbufsize = DEFAULT_RCVBUFSIZE; server->ischkseq = server->isforeground = FALSE; server->docroot = DEFAULT_DOCROOT; server->subject_txt_interval = server->subject_html_interval = DEFAULT_SUBJECT_TXT_INTERVAL; server->settings_interval = DEFAULT_SETTING_INTERVAL; server->nthr_max = DEFAULT_NTHR_MAX; server->nthr_min = (server->nthr_max + NTHR_RANGE_RATIO - 1) / NTHR_RANGE_RATIO; server->name = DEFAULT_SERVERNAME; #ifdef USE_THREADS while ((c = getopt(argc, argv, "b:B:cd:fi:I:n:p:s:h")) != -1) #else while ((c = getopt(argc, argv, "b:B:cd:fi:I:p:s:h")) != -1) #endif switch(c) { case 'b': bindaddr = optarg; break; case 'B': if ((p = strchr(optarg, ':'))) { server->rcvbufsize = atoi(optarg); server->sndbufsize = atoi(++p); } else server->rcvbufsize = server->sndbufsize = atoi(optarg); break; case 'c': server->ischkseq = TRUE; break; case 'd': if (*optarg != '/') { fprintf(stderr, "-%c: %s: Absolute path required.\n", c, optarg); return EINVAL; } else if (stat(optarg, &st) || (!S_ISDIR(st.st_mode) && (errno = ENOTDIR))) { fprintf(stderr, "-%c: %s: %s\n", c, optarg, strerror(errno)); return errno; } server->docroot = optarg; break; case 'f': server->isforeground = TRUE; break; case 'i': if ((p = strchr(optarg, ':'))) { server->subject_txt_interval = atol(optarg); server->subject_html_interval = atol(++p); if (server->subject_txt_interval > server->subject_html_interval && (errno = EINVAL)) { fprintf(stderr, "-%c: %s: %s\n", c, optarg, strerror(errno)); return errno; } } else server->subject_txt_interval = server->subject_html_interval = atol(optarg); break; case 'I': server->settings_interval = atol(optarg); break; case 'n': if ((p = strchr(optarg, '-'))) { if ((!(server->nthr_max = strtoul(++p, NULL, 10)) || !(server->nthr_min = strtoul(optarg, NULL, 10)) || server->nthr_min > server->nthr_max) && (errno = EINVAL)) { fprintf(stderr, "-%c: %s: %s\n", c, optarg, strerror(errno)); return errno; } } else { if (!(server->nthr_max = strtoul(optarg, NULL, 10)) && (errno = EINVAL)) { fprintf(stderr, "-%c: %s: %s\n", c, optarg, strerror(errno)); return errno; } server->nthr_min = (server->nthr_max + NTHR_RANGE_RATIO - 1) / NTHR_RANGE_RATIO; } break; case 'p': port = optarg; break; case 's': server->name = optarg; break; case 'h': case '?': printusage(server); return -1; } memset(&ai_hints, 0, sizeof ai_hints); ai_hints.ai_family = PF_UNSPEC; ai_hints.ai_socktype = SOCK_DGRAM; if ((c = getaddrinfo(bindaddr, port, &ai_hints, &server->addrinfo))) { fprintf(stderr, "error: getaddrinfo: %s.%s: %s\n", bindaddr, port, c == EAI_SYSTEM ? strerror(errno) : gai_strerror(c)); return c == EAI_SYSTEM ? errno : c; } return 0; } int initialize(server_t *server) { int i; struct sigaction sa; if (clock_gettime(CLOCK_HIGHRES, &server->starttime)) { perror("error: clock_gettime"); return errno; } for (i = 0; i < arraylen(commands); i++) { const char *p; for (p = commands[i].name; *p; p++) commands[i].hash = HASH_FACTOR * commands[i].hash + (unsigned char)*p; commands[i].len = p - commands[i].name; } server->nthr_cur = server->nthr_min; server->nthr_idle = 0; server->boards = NULL; sigfillset(&filledsigset); if (!setlocale(LC_ALL, "") && (errno = EINVAL)) perror("warning: setlocale"); openlog(server->argv0, LOG_PID, SYSLOG_FACILITY); umask(UMASK); server->isbbspink = strstr(server->name, "bbspink.com") != NULL; if ((errno = pthread_cond_init(&server->sock_cond, NULL)) || (errno = pthread_cond_init(&server->thr_cond, NULL))) { perror("error: pthread_cond_init"); return errno; } if ((errno = pthread_mutex_init(&server->cond_mutex, NULL)) || (errno = pthread_mutex_init(&server->boards_mutex, NULL))) { perror("error: pthread_mutex_init"); return errno; } sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; for (i = 0; i < arraylen(ignoresignals); i++) sigaction(ignoresignals[i], &sa, NULL); sa.sa_handler = sighandler; sigaction(SIGHUP, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); for (i = 0; i < arraylen(coredumpsignals); i++) sigaddset(&sa.sa_mask, coredumpsignals[i]); sa.sa_flags = SA_RESETHAND|SA_RESTART; for (i = 0; i < arraylen(coredumpsignals); i++) sigaction(coredumpsignals[i], &sa, NULL); if ((server->sock = socket(server->addrinfo->ai_family, server->addrinfo->ai_socktype, server->addrinfo->ai_protocol)) == -1) { perror("error: socket"); return errno; } if (setsockopt(server->sock, SOL_SOCKET, SO_RCVBUF, &server->rcvbufsize, sizeof server->rcvbufsize)) perror("warning: setsockopt(SO_RCVBUF)"); if (setsockopt(server->sock, SOL_SOCKET, SO_SNDBUF, &server->sndbufsize, sizeof server->sndbufsize)) perror("warning: setsockopt(SO_SNDBUF)"); if (bind(server->sock, server->addrinfo->ai_addr, server->addrinfo->ai_addrlen)) { perror("error: bind"); return errno; } return 0; } int daemonize(void) { switch (fork()) { case -1: perror("error: fork"); return errno; case 0: break; default: _exit(0); } freopen("/dev/null", "r+", stdin); freopen("/dev/null", "r+", stdout); freopen("/dev/null", "r+", stderr); setsid(); chdir("/"); return 0; } int read_setting(const server_t *server, worker_t *worker, board_t *board) { const char *p, *endp, *eol; char *buf; int fd; struct stat st; setting_t *setting, **settingp; pthread_rwlock_rdlock(&board->settings_rwlock); if (worker->now - board->settings_lastchk < server->settings_interval) { pthread_rwlock_unlock(&board->settings_rwlock); return 0; } pthread_rwlock_unlock(&board->settings_rwlock); pthread_rwlock_wrlock(&board->settings_rwlock); board->settings_lastchk = worker->now; snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/SETTING.TXT", server->docroot, (int)board->name_len, board->name); if (stat(*worker->paths, &st)) { pthread_rwlock_unlock(&board->settings_rwlock); return errno; } if (st.st_mtime == board->settings_mtime) { pthread_rwlock_unlock(&board->settings_rwlock); return 0; } for (setting = board->settings; setting; ) { setting_t *setting_next = setting->next; free(setting); setting = setting_next; } board->settings = NULL; if ((fd = open(*worker->paths, O_RDONLY)) == -1 || fstat(fd, &st) || (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { if (fd != -1) close(fd); pthread_rwlock_unlock(&board->settings_rwlock); return errno; } close(fd); for (endp = (p = buf) + st.st_size, settingp = &board->settings; p < endp; p = ++eol, settingp = &(*settingp)->next) { const char *p2; (eol = memchr(p, '\n', endp - p)) || (eol = endp); if (!(*settingp = malloc(sizeof **settingp + eol - p))) { for (setting = board->settings; setting; ) { setting_t *setting_next = setting->next; free(setting); setting = setting_next; } board->settings = NULL; munmap(buf, st.st_size); pthread_rwlock_unlock(&board->settings_rwlock); return errno; } for (p2 = p, (*settingp)->hash = 0; p2 < eol && *p2 != '='; p2++) (*settingp)->hash = HASH_FACTOR * (*settingp)->hash + (unsigned char)*p2; (*settingp)->key_len = p2 - p; sprintf((*settingp)->key_value, "%.*s", (int)(eol - p), p); } *settingp = NULL; munmap(buf, st.st_size); board->settings_mtime = st.st_mtime; pthread_rwlock_unlock(&board->settings_rwlock); return 0; } const char *get_setting(const board_t *board, const char *key) { const char *p; unsigned hash; size_t key_len; setting_t *setting; for (p = key, hash = 0; *p; p++) hash = HASH_FACTOR * hash + (unsigned char)*p; key_len = p - key; for (setting = board->settings; setting && (setting->hash != hash || setting->key_len != key_len || memcmp(setting->key_value, key, key_len) || setting->key_value[key_len] != '='); setting = setting->next) ; return setting ? setting->key_value + key_len + 1 : ""; } subject_t *alloc_subject(uint64_t key, const char *title, size_t title_len) { subject_t *subject; if (!(subject = malloc(sizeof *subject + title_len)) || (errno = pthread_mutex_init(&subject->mutex, NULL))) { free(subject); return NULL; } if ((errno = pthread_mutex_init(&subject->chktimecounts_mutex, NULL))) { pthread_mutex_destroy(&subject->mutex); free(subject); return NULL; } subject->key = key; sprintf(subject->title, "%.*s", (int)title_len, title); subject->chktimecounts = NULL; return subject; } void free_subject(subject_t *subject) { chktimecount_t *chktimecount, *chktimecount_next; pthread_mutex_lock(&subject->chktimecounts_mutex); for (chktimecount = subject->chktimecounts; chktimecount; chktimecount = chktimecount_next) { chktimecount_next = chktimecount->next; free(chktimecount); } pthread_mutex_unlock(&subject->chktimecounts_mutex); pthread_mutex_destroy(&subject->chktimecounts_mutex); pthread_mutex_lock(&subject->mutex); pthread_mutex_unlock(&subject->mutex); pthread_mutex_destroy(&subject->mutex); free(subject); } board_t *alloc_board(const char *bbs, unsigned bbs_hash, size_t bbs_len) { int i; board_t *board; if (!(board = malloc(sizeof *board + bbs_len - 1)) || (errno = pthread_mutex_init(&board->subjects_mutex, NULL))) { free(board); return NULL; } if ((errno = pthread_rwlock_init(&board->settings_rwlock, NULL))) { pthread_mutex_destroy(&board->subjects_mutex); free(board); return NULL; } if ((errno = pthread_rwlock_init(&board->md5seed_rwlock, NULL))) { pthread_rwlock_destroy(&board->settings_rwlock); pthread_mutex_destroy(&board->subjects_mutex); free(board); return NULL; } if ((errno = pthread_mutex_init(&board->chkthrs_mutex, NULL))) { pthread_rwlock_destroy(&board->md5seed_rwlock); pthread_rwlock_destroy(&board->settings_rwlock); pthread_mutex_destroy(&board->subjects_mutex); free(board); return NULL; } if ((errno = pthread_mutex_init(&board->chktimecounts_mutex, NULL))) { pthread_mutex_destroy(&board->chkthrs_mutex); pthread_rwlock_destroy(&board->md5seed_rwlock); pthread_rwlock_destroy(&board->settings_rwlock); pthread_mutex_destroy(&board->subjects_mutex); free(board); return NULL; } if ((errno = pthread_mutex_init(&board->chkiddbs_mutex, NULL))) { pthread_mutex_destroy(&board->chktimecounts_mutex); pthread_mutex_destroy(&board->chkthrs_mutex); pthread_rwlock_destroy(&board->md5seed_rwlock); pthread_rwlock_destroy(&board->settings_rwlock); pthread_mutex_destroy(&board->subjects_mutex); free(board); return NULL; } if ((errno = pthread_rwlock_init(&board->notes_rwlock, NULL))) { pthread_mutex_destroy(&board->chkiddbs_mutex); pthread_mutex_destroy(&board->chktimecounts_mutex); pthread_mutex_destroy(&board->chkthrs_mutex); pthread_rwlock_destroy(&board->md5seed_rwlock); pthread_rwlock_destroy(&board->settings_rwlock); pthread_mutex_destroy(&board->subjects_mutex); free(board); return NULL; } memcpy(board->name, bbs, board->name_len = bbs_len); board->name_hash = bbs_hash; board->subjects = NULL; board->settings = NULL; board->md5seed = MAP_FAILED; board->chkthrs = NULL; board->chktimecounts = NULL; board->chkiddbs = NULL; board->settings_mtime = board->settings_lastchk = board->notes_lastchk = 0; for (i = 0; i < arraylen(board->notes); i++) board->notes[i] = NULL; return board; } void free_board(board_t *board) { union { int i; subject_t *subject; setting_t *setting; chkthr_t *chkthr; chktimecount_t *chktimecount; chkiddb_t *chkiddb; } p, p_next; pthread_rwlock_wrlock(&board->notes_rwlock); for (p.i = 0; p.i < arraylen(board->notes); p.i++) free(board->notes[p.i]); pthread_rwlock_unlock(&board->notes_rwlock); pthread_rwlock_destroy(&board->notes_rwlock); pthread_mutex_lock(&board->chkiddbs_mutex); for (p.chkiddb = board->chkiddbs; p.chkiddb; p.chkiddb = p_next.chkiddb) { chkid_t *chkid, *chkid_next; pthread_mutex_lock(&p.chkiddb->mutex); for (chkid = p.chkiddb->ids; chkid; chkid = chkid_next) { chkid_next = chkid->next; free(chkid); } pthread_mutex_unlock(&p.chkiddb->mutex); pthread_mutex_destroy(&p.chkiddb->mutex); p_next.chkiddb = p.chkiddb->next; free(p.chkiddb); } pthread_mutex_unlock(&board->chkiddbs_mutex); pthread_mutex_destroy(&board->chkiddbs_mutex); pthread_mutex_lock(&board->chktimecounts_mutex); for (p.chktimecount = board->chktimecounts; p.chktimecount; p.chktimecount = p_next.chktimecount) { p_next.chktimecount = p.chktimecount->next; free(p.chktimecount); } pthread_mutex_unlock(&board->chktimecounts_mutex); pthread_mutex_destroy(&board->chktimecounts_mutex); pthread_mutex_lock(&board->chkthrs_mutex); for (p.chkthr = board->chkthrs; p.chkthr; p.chkthr = p_next.chkthr) { p_next.chkthr = p.chkthr->next; free(p.chkthr); } pthread_mutex_unlock(&board->chkthrs_mutex); pthread_mutex_destroy(&board->chkthrs_mutex); pthread_rwlock_wrlock(&board->md5seed_rwlock); if (board->md5seed != MAP_FAILED) munmap(board->md5seed, 10 + 2 + MD5SEED_LENGTH); pthread_rwlock_unlock(&board->md5seed_rwlock); pthread_rwlock_destroy(&board->md5seed_rwlock); pthread_rwlock_wrlock(&board->settings_rwlock); for (p.setting = board->settings; p.setting; p.setting = p_next.setting) { p_next.setting = p.setting->next; free(p.setting); } pthread_rwlock_unlock(&board->settings_rwlock); pthread_rwlock_destroy(&board->settings_rwlock); pthread_mutex_lock(&board->subjects_mutex); for (p.subject = board->subjects; p.subject; p.subject = p_next.subject) { p_next.subject = p.subject->next; free_subject(p.subject); } pthread_mutex_unlock(&board->subjects_mutex); pthread_mutex_destroy(&board->subjects_mutex); free(board); } int read_subjects(server_t *server, worker_t *worker, request_t *request) { const char *p, *endp, *eol; char *buf; int fd; unsigned bbs_hash; size_t bbs_len; subject_t **subjp; struct stat st; for (p = request->bbs, bbs_hash = 0; *p; p++) bbs_hash = HASH_FACTOR * bbs_hash + (unsigned char)*p; bbs_len = p - request->bbs; pthread_mutex_lock(&server->boards_mutex); for (request->board = server->boards; request->board && (request->board->name_hash != bbs_hash || request->board->name_len != bbs_len || memcmp(request->board->name, request->bbs, bbs_len)); request->board = request->board->next) ; if (request->board) { pthread_mutex_unlock(&server->boards_mutex); return 0; } snprintf(*worker->paths, sizeof *worker->paths, "%s/%s/subject.txt", server->docroot, request->bbs); if ((fd = open(*worker->paths, O_RDONLY)) == -1) { if (errno != ENOENT) { pthread_mutex_unlock(&server->boards_mutex); return errno; } st.st_mtime = 0; st.st_size = 0; buf = MAP_FAILED; } else if (fstat(fd, &st) || (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { close(fd); pthread_mutex_unlock(&server->boards_mutex); return errno; } else close(fd); if (!(request->board = alloc_board(request->bbs, bbs_hash, bbs_len))) { if (buf != MAP_FAILED) munmap(buf, st.st_size); pthread_mutex_unlock(&server->boards_mutex); return errno; } request->board->next = server->boards; server->boards = request->board; pthread_mutex_lock(&request->board->subjects_mutex); pthread_mutex_unlock(&server->boards_mutex); request->board->subjects_isdirty = FALSE; request->board->subject_txt_mtime = request->board->subject_html_mtime = st.st_mtime; for (endp = (p = buf) + st.st_size, subjp = &request->board->subjects; p < endp; p = ++eol, subjp = &(*subjp)->next) { const char *p1, *p2; (eol = memchr(p, '\n', endp - p)) || (eol = endp); (p1 = memstr(p, "<>", eol - p)) && (p1 += 2); for (p2 = eol; p2-- > p && *p2 != '('; ) ; if (!p1 || p2 < p || p1 > p2 - 1 || p == eol || eol[-1] != ')') { p = p1 = "[ここ壊れてます] ("; p2 = p1 + arraylen("[ここ壊れてます] (") - 2; } if (!(*subjp = alloc_subject(strtoull(p, NULL, 10), p1, p2 - 1 - p1))) { subject_t *subject, *subject_next; for (subject = request->board->subjects; subject; subject = subject_next) { subject_next = subject->next; free_subject(subject); } request->board->subjects = NULL; munmap(buf, st.st_size); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } (*subjp)->n = strtoul(++p2, NULL, 10); } *subjp = NULL; if (buf != MAP_FAILED) munmap(buf, st.st_size); pthread_mutex_unlock(&request->board->subjects_mutex); return 0; } #define _null2str(s) (s ? s : "") int write_index(const server_t *server, worker_t *worker, board_t *board, const char *footnote, int isreadjs) { const char *bbs_title, *bbs_title_picture; char *buf; int i, fd, ispicjs; size_t nthr_disp, nthr_menu; FILE *fp; subject_t *subject; struct stat st; pthread_mutex_lock(&board->subjects_mutex); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/index.css", server->docroot, (int)board->name_len, board->name); if (stat(worker->paths[1], &st) || st.st_mtime < board->settings_mtime) { static const char *csscfgs[] = { "BBS_BG_COLOR", "BBS_BG_PICTURE", "BBS_TEXT_COLOR", "BBS_LINK_COLOR", "BBS_VLINK_COLOR", "BBS_ALINK_COLOR", "BBS_MENU_COLOR", "BBS_MAKETHREAD_COLOR", "BBS_TITLE_COLOR", "BBS_SUBJECT_COLOR", "BBS_THREAD_COLOR", "BBS_NAME_COLOR" }; const char *cfgs[arraylen(csscfgs)]; snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/.index.css", server->docroot, (int)board->name_len, board->name); if (!(fp = fopen(*worker->paths, "w"))) { pthread_mutex_unlock(&board->subjects_mutex); return errno; } pthread_rwlock_rdlock(&board->settings_rwlock); for (i = 0; i < arraylen(csscfgs); i++) cfgs[i] = get_setting(board, csscfgs[i]); fprintf(fp, "body { margin: .5em 2.5%%; background: %s%s%s%s; color: %s; }\n" "a:link { color: %s; }\n" "a:visited { color: %s; }\n" "a:active { color: %s; }\n" "a.js, a.js:visited, a.js:active { background-color: dimgray; border: 1px outset dimgray; color: palegreen; text-decoration: none; }\n" "a img { border: none; }\n" "p.center { margin: 0; text-align: center; }\n" "p.right { margin: 0; text-align: right; }\n" "#head, #ad, #menu, .thr, #post { border: 4px ridge; padding: .5em; }\n" ".ad { border: 4px ridge; }\n" "#head, #menu, #adv, .thr, #post { margin-bottom: 1em; }\n" "#head, #ad, .ad, #menu { background: %s; border-color: %s; }\n" "#post { background: %s; border-color: %s; }\n" "h1 { color: %s; font-size: larger; float: left; margin: 0; }\n" "h2 { color: %s; display: inline; }\n" "#head br { clear: left; }\n" "#head hr { height: .5em; }\n" ".ad p { display: inline; }\n" ".ad table { margin: 0; padding: 0; border: 0; width: 100%%; }\n" ".ad table td { border: 0; }\n" ".thr { background: %s; border-color: %s; padding-bottom: 1.5em; }\n" ".thr dd { padding-bottom: 1em; }\n" ".thr dt span, .thr dd .truncated { color: %s; }\n" ".thr form, .thr textarea { margin: 1px 0 0 3em; }\n" ".thr p { margin: 1px 0 0 6em; }\n" "textarea { white-space: pre; }\n", *cfgs, *cfgs[1] ? " url(\"" : "", cfgs[1], *cfgs[1] ? "\")" : "", cfgs[2], cfgs[3], cfgs[4], cfgs[5], cfgs[6], cfgs[6], cfgs[7], cfgs[7], cfgs[8], cfgs[9], cfgs[10], cfgs[10], cfgs[11]); pthread_rwlock_unlock(&board->settings_rwlock); fclose(fp); if (rename(*worker->paths, worker->paths[1])) { unlink(*worker->paths); pthread_mutex_unlock(&board->subjects_mutex); return errno; } } snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/.index.html", server->docroot, (int)board->name_len, board->name); if (!(fp = fopen(*worker->paths, "w"))) { pthread_mutex_unlock(&board->subjects_mutex); return errno; } pthread_rwlock_rdlock(&board->settings_rwlock); nthr_disp = strtoul(get_setting(board, "BBS_THREAD_NUMBER"), NULL, 10); nthr_menu = strtoul(get_setting(board, "BBS_MAX_MENU_THREAD"), NULL, 10); bbs_title_picture = get_setting(board, "BBS_TITLE_PICTURE"); for (bbs_title = bbs_title_picture; *bbs_title; bbs_title++) ; ispicjs = (bbs_title -= 3) > bbs_title_picture && !strcmp(bbs_title, ".js"); bbs_title = get_setting(board, "BBS_TITLE"); fprintf(fp, "\n" "\n" "\n" "\n" "%s\n" "\n%s" "\n\n" "

<%s src=\"%s\"%s%s%s>\n\n" "

\n\n" "

%s

%s


\n\n", bbs_title, isreadjs ? "\n" : "", get_setting(board, "BBS_TITLE_LINK"), ispicjs ? "script type=\"text/javascript\"" : "img", bbs_title_picture, ispicjs ? ">read.cgi モード切替 \n" : ""); pthread_rwlock_unlock(&board->settings_rwlock); snprintf(worker->paths[1], sizeof *worker->paths, "%s/test/flash.txt", server->docroot); buf = MAP_FAILED; if ((fd = open(worker->paths[1], O_RDONLY)) != -1 && !fstat(fd, &st) && st.st_size && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) != MAP_FAILED) fprintf(fp, "%.*s\n", (int)st.st_size, buf); if (buf != MAP_FAILED) munmap(buf, st.st_size); if (fd != -1) close(fd); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/head.txt", server->docroot, (int)board->name_len, board->name); buf = MAP_FAILED; if ((fd = open(worker->paths[1], O_RDONLY)) != -1 && !fstat(fd, &st) && st.st_size && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) != MAP_FAILED) fprintf(fp, "%.*s\n", (int)st.st_size, buf); if (buf != MAP_FAILED) munmap(buf, st.st_size); if (fd != -1) close(fd); fputs("

書き込む前に読んでね" "|2ちゃんねるガイド" "|FAQ" "|チャット\n" "


\n" "

掲示板一覧\n\n" "

\n\n", fp); pthread_rwlock_rdlock(&board->notes_rwlock); if (board->notes[NOTE_adfile1]) { snprintf(worker->paths[1], sizeof *worker->paths, "%s/test/%s", server->docroot, board->notes[NOTE_adfile1]); buf = MAP_FAILED; if ((fd = open(worker->paths[1], O_RDONLY)) != -1 && !fstat(fd, &st) && st.st_size && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) != MAP_FAILED) fprintf(fp, "
\n\n%.*s\n
\n\n", (int)st.st_size, buf); if (buf != MAP_FAILED) munmap(buf, st.st_size); if (fd != -1) close(fd); } if (board->notes[NOTE_adfile2]) { snprintf(worker->paths[1], sizeof *worker->paths, "%s/test/%s", server->docroot, board->notes[NOTE_adfile2]); buf = MAP_FAILED; if ((fd = open(worker->paths[1], O_RDONLY)) != -1 && !fstat(fd, &st) && st.st_size && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) != MAP_FAILED) fprintf(fp, "
\n\n%.*s\n
\n\n", (int)st.st_size, buf); if (buf != MAP_FAILED) munmap(buf, st.st_size); if (fd != -1) close(fd); } pthread_rwlock_unlock(&board->notes_rwlock); fputs("
\n\n" "\n", fp); for (subject = board->subjects, i = 1; subject && i <= nthr_disp; subject = subject->next, i++) fprintf(fp, "%i: %s (%u)\n", (int)board->name_len, board->name, subject->key, i, i, subject->title, subject->n); if (i <= nthr_disp) nthr_disp = i - 1; for (; subject && i <= nthr_menu; subject = subject->next, i++) fprintf(fp, "%i: %s (%u)\n", (int)board->name_len, board->name, subject->key, i, subject->title, subject->n); fputs("\n" "

スレッド一覧はこちら\n\n" "

\n\n", fp); pthread_rwlock_rdlock(&board->notes_rwlock); if (board->notes[NOTE_adfile3]) { snprintf(worker->paths[1], sizeof *worker->paths, "%s/test/%s", server->docroot, board->notes[NOTE_adfile3]); buf = MAP_FAILED; if ((fd = open(worker->paths[1], O_RDONLY)) != -1 && !fstat(fd, &st) && st.st_size && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) != MAP_FAILED) fprintf(fp, "
\n\n%.*s\n
\n\n", (int)st.st_size, buf); if (buf != MAP_FAILED) munmap(buf, st.st_size); if (fd != -1) close(fd); } pthread_rwlock_unlock(&board->notes_rwlock); for (subject = board->subjects, i = 1; subject && i <= nthr_disp; subject = subject->next, i++) { fprintf(fp, "
\n\n" "

\n" "【%i:%u】 ", i, i == 1 ? (int)nthr_disp : i - 1, i == nthr_disp ? 1 : i + 1, i, subject->n); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/html/%"PRIu64".html", server->docroot, (int)board->name_len, board->name, subject->key); buf = MAP_FAILED; pthread_mutex_lock(&subject->mutex); if ((fd = open(worker->paths[1], O_RDONLY)) != -1 && !fstat(fd, &st) && st.st_size && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) != MAP_FAILED) fprintf(fp, "%.*s", (int)st.st_size, buf); if (buf != MAP_FAILED) munmap(buf, st.st_size); if (fd != -1) close(fd); pthread_mutex_unlock(&subject->mutex); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)board->name_len, board->name, subject->key); if (!stat(worker->paths[1], &st) && st.st_mode & S_IWUSR) fprintf(fp, "
\n" "\n" "\n" "\n" "\n" "名前: E-mail:
\n" "\n" "
", (int)board->name_len, board->name, subject->key, (long)worker->now); fprintf(fp, "

全部読む\n" "最新50\n" "1-100\n" "板のトップ リロード\n\n" "

\n\n", (int)board->name_len, board->name, subject->key, (int)board->name_len, board->name, subject->key, (int)board->name_len, board->name, subject->key); } pthread_rwlock_rdlock(&board->settings_rwlock); i = *get_setting(board, "BBS_PASSWORD_CHECK") < ' '; pthread_rwlock_unlock(&board->settings_rwlock); pthread_rwlock_rdlock(&board->notes_rwlock); fprintf(fp, "
\n" "%s\n" "\n" "
\n\n" "

\"\"
\n" "どのような形の削除依頼であれ公開させていただきます\n\n" "

%s%s%s
\n" "Snowman system / %s ("__DATE__" "__TIME__")
\n" "%s\n\n" "\n", i ? "タイトル:
\n" "名前: E-mail:
\n" "内容:\n" : "\n", (int)board->name_len, board->name, (long)worker->now, (int)board->name_len, board->name, (int)board->name_len, board->name, _null2str(board->notes[NOTE_footnote]), footnote ? " # " : "", _null2str(footnote), server->argv0, _null2str(board->notes[NOTE_adline])); pthread_rwlock_unlock(&board->notes_rwlock); fclose(fp); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/index.html", server->docroot, (int)board->name_len, board->name); if (rename(*worker->paths, worker->paths[1])) { unlink(*worker->paths); pthread_mutex_unlock(&board->subjects_mutex); return errno; } pthread_mutex_unlock(&board->subjects_mutex); return 0; } int write_subjects(const server_t *server, worker_t *worker, board_t *board, const char *footnote, int isforce) { int i, isreadjs; FILE *fp; subject_t *subject; struct stat st; if (!board->subjects_isdirty || (!isforce && worker->now - board->subject_txt_mtime < server->subject_txt_interval)) return 0; pthread_mutex_lock(&board->subjects_mutex); if (!board->subjects_isdirty || (!isforce && worker->now - board->subject_txt_mtime < server->subject_txt_interval)) { pthread_mutex_unlock(&board->subjects_mutex); return 0; } snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/.subject.txt", server->docroot, (int)board->name_len, board->name); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/subject.txt", server->docroot, (int)board->name_len, board->name); if (!(fp = fopen(*worker->paths, "w"))) { pthread_mutex_unlock(&board->subjects_mutex); return errno; } for (subject = board->subjects; subject; subject = subject->next) fprintf(fp, "%"PRIu64".dat<>%s (%u)\n", subject->key, subject->title, subject->n); fclose(fp); if (rename(*worker->paths, worker->paths[1])) { unlink(*worker->paths); pthread_mutex_unlock(&board->subjects_mutex); return errno; } board->subjects_isdirty = FALSE; board->subject_txt_mtime = worker->now; if (!isforce && worker->now - board->subject_html_mtime < server->subject_html_interval) { pthread_mutex_unlock(&board->subjects_mutex); return 0; } snprintf(*worker->paths, sizeof *worker->paths, "%s/test/read.html", server->docroot); isreadjs = !stat(*worker->paths, &st); snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/.subback.html", server->docroot, (int)board->name_len, board->name); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/subback.html", server->docroot, (int)board->name_len, board->name); if (!(fp = fopen(*worker->paths, "w"))) { pthread_mutex_unlock(&board->subjects_mutex); return errno; } pthread_rwlock_rdlock(&board->settings_rwlock); fprintf(fp, "\n" "\n" "\n" "\n" "%s@スレッド一覧\n" "\n" "\n" "\n" "\n" "

\n", get_setting(board, "BBS_TITLE"), server->name, (int)board->name_len, board->name); pthread_rwlock_unlock(&board->settings_rwlock); for (subject = board->subjects, i = 1; subject; subject = subject->next, i++) fprintf(fp, "%i: %s (%u)\n", subject->key, i, subject->title, subject->n); fprintf(fp, "
\n" "\n" "\n", isreadjs ? "read.cgi モード切替 \n" : "", (int)board->name_len, board->name); fclose(fp); if (rename(*worker->paths, worker->paths[1])) { unlink(*worker->paths); pthread_mutex_unlock(&board->subjects_mutex); return errno; } board->subject_html_mtime = worker->now; pthread_mutex_unlock(&board->subjects_mutex); return write_index(server, worker, board, footnote, isreadjs); } int create_html(const server_t *server, worker_t *worker, board_t *board, const subject_t *subject, int fd) { static const char *linkablehosts[] = {"2ch.net", "bbspink.com", "machi.to"}; const char *const urlschemes[][2] = {{"http://", server->isbbspink ? "pinktower.com/" : "ime.nu/"}, {"https://", ""}, {"ftp://", ""}}; const char *p, *endp, *eol, *strpad = server->ischkseq ? "" : " "; char *dat; int i; size_t nlines, nres; FILE *fp; struct stat st; pthread_rwlock_rdlock(&board->settings_rwlock); nlines = strtoul(get_setting(board, "BBS_LINE_NUMBER"), NULL, 10); nres = strtoul(get_setting(board, "BBS_CONTENTS_NUMBER"), NULL, 10); pthread_rwlock_unlock(&board->settings_rwlock); snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/html/.%"PRIu64".html", server->docroot, (int)board->name_len, board->name, subject->key); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/html/%"PRIu64".html", server->docroot, (int)board->name_len, board->name, subject->key); if (!(fp = fopen(*worker->paths, "w")) || fstat(fd, &st) || (dat = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { if (fp) fclose(fp); return errno; } fprintf(fp, "

%s%s

\n", subject->title, strpad); for (i = 1, endp = (p = dat) + st.st_size; i <= subject->n && p < endp; i++, p = ++eol) { const char *cols[5], *p2, *_eol; int j; if (!(eol = memchr(p, '\n', endp - p))) break; if (i > 1 && subject->n > nres && i <= subject->n - nres) continue; /* "name<>email<>date etc.<>message<>[title]" */ for (j = 1, *cols = p2 = p, cols[4] = NULL; j <= 4; j++) { while ((p2 = memchr(p2, '<', eol - 1 - p2)) && *++p2 != '>') ; if (!p2++) break; cols[j] = p2; } if (!cols[4]) { fprintf(fp, "
%i :[ここ壊れてます]:[ここ壊れてます]
[ここ壊れてます]\n", i); continue; } else if (cols[1] == cols[2] - 2) fprintf(fp, "
%i :%.*s%s:", i, (int)(cols[1] - 2 - *cols), *cols, strpad); else fprintf(fp, "
%i :%.*s%s:", i, (int)(cols[2] - 2 - cols[1]), cols[1], strpad, (int)(cols[1] - 2 - *cols), *cols, strpad); /* BE:nnnnnnnn-## */ if ((p2 = memstr(cols[2], "BE:", cols[3] - 2 - cols[2]))) { const char *p3; fprintf(fp, "%.*s", (int)(p2 - cols[2]), cols[2]); p2 += 3; (p3 = memchr(p2, '-', cols[3] - 2 - p2)) || (p3 = cols[3] - 2 - 1); fprintf(fp, "?%.*s
", (int)(p3 - p2), p2, (int)(cols[3] - 2 - (p3 + 1)), p3 + 1); } else fprintf(fp, "%.*s
", (int)(cols[3] - 2 - cols[2]), cols[2]); for (j = 0, p2 = cols[3]; (i == 1 || j < nlines) && p2 < cols[4] - 2; j++, p2 = _eol) { (_eol = memstr(p2, "
", cols[4] - 2 - p2)) || (_eol = cols[4] - 2); do { const char *p3, *p4, *const *scheme; int k; for (k = 0, p3 = _eol, scheme = NULL; k < arraylen(urlschemes); k++) if ((p4 = memstrmb(p2, *urlschemes[k], _eol - p2, server->ischkseq)) && p4 < p3) { p3 = p4; scheme = urlschemes[k]; } fprintf(fp, "%.*s", (int)(p3 - p2), p2); if (scheme) { size_t slen = strlen(*scheme); for (p2 = p3; p2 < _eol && *p2 >= '\x21' && *p2 <= '\x7E' && *p2 != '"' && *p2 != '<'; p2++) ; (p4 = memchr(p3 + slen, '/', p2 - (p3 + slen))) || (p4 = p2); for (k = 0; k < arraylen(linkablehosts); k++) { size_t llen = strlen(linkablehosts[k]); if ((p4 - llen == p3 + slen || (p4 - llen > p3 + slen && p4[-llen - 1] == '.')) && !memcmp(p4 - llen, linkablehosts[k], llen)) break; } fprintf(fp, "%s%.*s", strpad, *scheme, k < arraylen(linkablehosts) ? "" : scheme[1], (int)(p2 - (p3 + slen)), p3 + slen, (int)(p2 - p3), p3); } else p2 = p3; } while (p2 < _eol); if (_eol < cols[4] - 2) { fputs("
", fp); _eol += 4; } } if (i > 1 && j == nlines && p2 < cols[4] - 2) fprintf(fp, "
(省略されました・・全てを読むには" "ここを押してください)", (int)board->name_len, board->name, subject->key, i); fputc('\n', fp); } fputs("
", fp); munmap(dat, st.st_size); fclose(fp); if (rename(*worker->paths, worker->paths[1])) { unlink(*worker->paths); return errno; } return 0; } static char *_widenumstr(unsigned n, char *buf, size_t len) { union { char *c; uint16_t *w; } p = {buf + len}; if (--p.c >= buf) *p.c = 0; while (n && --p.w >= (uint16_t *)buf) { *p.w = htons(0x824F + n % 10); n /= 10; } return p.c; } int append_dat(const server_t *server, worker_t *worker, request_t *request) { int fd; size_t datlinelen = strlen(request->datline); subject_t *subject; if (request->title) { int i; if (!(subject = alloc_subject(request->key, request->title, strlen(request->title)))) return errno; subject->n = 1; i = 0; do { snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); fd = open(*worker->paths, O_RDWR|O_CREAT|O_EXCL, 0644); } while (fd == -1 && errno == EEXIST && i++ < NEWTHR_RETRY && (request->key = ++subject->key)); if (fd == -1) { free_subject(subject); return errno; } request->datline[datlinelen++] = '\n'; write(fd, request->datline, datlinelen); fchmod(fd, 0666); create_html(server, worker, request->board, subject, fd); close(fd); pthread_mutex_lock(&request->board->subjects_mutex); subject->next = request->board->subjects; request->board->subjects = subject; request->board->subjects_isdirty = TRUE; pthread_mutex_unlock(&request->board->subjects_mutex); } else { FILE *fp; subject_t **subjp; struct stat st; pthread_mutex_lock(&request->board->subjects_mutex); for (subjp = &request->board->subjects; *subjp && (*subjp)->key != request->key; subjp = &(*subjp)->next) ; if (!(subject = *subjp)) { pthread_mutex_unlock(&request->board->subjects_mutex); return ENOENT; } if (subject->n >= RES_MAX) { pthread_mutex_unlock(&request->board->subjects_mutex); return EDQUOT; } snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); pthread_mutex_lock(&subject->mutex); if (stat(*worker->paths, &st) || (st.st_size >= DATSIZE_MAX && (errno = EDQUOT)) || (fd = open(*worker->paths, O_RDWR|O_APPEND)) == -1) { pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } request->datline[datlinelen++] = '\n'; if (!request->issage && subjp != &request->board->subjects) { *subjp = subject->next; subject->next = request->board->subjects; request->board->subjects = subject; } request->board->subjects_isdirty = TRUE; if (++subject->n == RES_MAX && (fp = fdopen(fd, "a+"))) { char *buf, widebuf[RESMAX_WIDEBUFSIZE]; subject->n++; pthread_mutex_unlock(&request->board->subjects_mutex); fprintf(fp, "%.*s%s<><>Over %u Thread<>", (int)datlinelen, request->datline, _widenumstr(RES_MAX + 1, widebuf, sizeof widebuf), RES_MAX); snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/%u.txt", server->docroot, (int)request->board->name_len, request->board->name, RES_MAX); buf = MAP_FAILED; if ((fd = open(*worker->paths, O_RDONLY)) != -1 && !fstat(fd, &st) && st.st_size && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) != MAP_FAILED) { const char *p, *p2, *endp; for (endp = (p = buf) + st.st_size; p && p < endp; p = p2) fprintf(fp, "%.*s", (int)((p2 = memchr(p, '\n', endp - p)) ? p2++ - p : endp - p), p); fputs("<>\n", fp); } else fprintf(fp, "このスレッドは%sを超えました。
もう書けないので、新しいスレッドを立ててくださいです。。。<>\n", _widenumstr(RES_MAX, widebuf, sizeof widebuf)); if (buf != MAP_FAILED) munmap(buf, st.st_size); if (fd != -1) close(fd); fflush(fp); fchmod(fileno(fp), 0555); create_html(server, worker, request->board, subject, fileno(fp)); fclose(fp); } else { pthread_mutex_unlock(&request->board->subjects_mutex); write(fd, request->datline, datlinelen); if (st.st_size + datlinelen >= DATSIZE_MAX) fchmod(fd, 0555); create_html(server, worker, request->board, subject, fd); close(fd); } pthread_mutex_unlock(&subject->mutex); } return 0; } typedef struct { uint64_t key; time_t mtime; } _dat_t; static int _datcmp(const void *_d1, const void *_d2) { return ((const _dat_t *)_d2)->mtime - ((const _dat_t *)_d1)->mtime; } int repair_board(server_t *server, worker_t *worker, const char *bbs) { const char *p; int i, n; unsigned bbs_hash; long name_max; size_t bbs_len; DIR *dp; board_t *board; subject_t *subject, **subjp; _dat_t *_dats; struct dirent *de, *dresult; struct stat st; for (p = bbs, bbs_hash = 0; *p; p++) bbs_hash = HASH_FACTOR * bbs_hash + (unsigned char)*p; bbs_len = p - bbs; snprintf(*worker->paths, sizeof *worker->paths, "%s/%s/dat", server->docroot, bbs); pthread_mutex_lock(&server->boards_mutex); for (board = server->boards; board && (board->name_hash != bbs_hash || board->name_len != bbs_len || memcmp(board->name, bbs, bbs_len)); board = board->next) ; if (!board) { if (stat(*worker->paths, &st) || (!S_ISDIR(st.st_mode) && (errno = ENOTDIR)) || !(board = alloc_board(bbs, bbs_hash, bbs_len))) { pthread_mutex_unlock(&server->boards_mutex); return errno; } board->next = server->boards; server->boards = board; } pthread_mutex_lock(&board->subjects_mutex); pthread_mutex_unlock(&server->boards_mutex); for (subject = board->subjects; subject; ) { subject_t *subject_next = subject->next; free_subject(subject); subject = subject_next; } board->subjects = NULL; board->subjects_isdirty = TRUE; board->subject_txt_mtime = board->subject_html_mtime = 0; if (!(dp = opendir(*worker->paths))) { pthread_mutex_unlock(&board->subjects_mutex); return errno; } name_max = pathconf(*worker->paths, _PC_NAME_MAX); if (!(de = malloc(sizeof *de + (name_max != -1 ? name_max : PATH_MAX)))) { closedir(dp); pthread_mutex_unlock(&board->subjects_mutex); return errno; } for (n = 0, _dats = NULL; !(errno = readdir_r(dp, de, &dresult)) && dresult; n++) { if (!(n % 128)) { _dat_t *__dats; if (!(__dats = realloc(_dats, 128 * (1 + n / 128) * sizeof *_dats))) break; else _dats = __dats; } if (*de->d_name == '.' || !(_dats[n].key = strtoull(de->d_name, NULL, 10)) || !snprintf(worker->paths[1], sizeof *worker->paths, "%s/%"PRIu64".dat", *worker->paths, _dats[n].key) || stat(worker->paths[1], &st) || !st.st_size) n--; else _dats[n].mtime = st.st_mtime; } free(de); closedir(dp); if (errno) { free(_dats); pthread_mutex_unlock(&board->subjects_mutex); return errno; } if (n) qsort(_dats, n, sizeof *_dats, _datcmp); for (i = 0, subjp = &board->subjects; i < n; i++, subjp = &(*subjp)->next) { int j; const char *p2, *endp; char *buf; snprintf(worker->paths[1], sizeof *worker->paths, "%s/%"PRIu64".dat", *worker->paths, _dats[i].key); if ((j = open(worker->paths[1], O_RDONLY)) == -1 || fstat(j, &st) || (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, j, 0)) == MAP_FAILED) { if (j != -1) close(j); endp = buf = MAP_FAILED; p = strerror(errno); p2 = p + strlen(p); } else { close(j); endp = buf + st.st_size; for (j = 0, p = buf; j < 4 && p; j++) while ((p = memchr(p, '<', endp - 1 - p)) && *++p != '>') ; if (!p || ++p >= endp || !(p2 = memchr(p, '\n', endp - p)) || p > p2) { p = "[ここ壊れてます]"; p2 = p + arraylen("[ここ壊れてます]") - 1; } } if (!(*subjp = alloc_subject(_dats[i].key, p, p2 - p))) { subject_t *subject_next; if (buf != MAP_FAILED) munmap(buf, st.st_size); for (subject = board->subjects; subject; subject = subject_next) { subject_next = subject->next; free_subject(subject); } board->subjects = NULL; free(_dats); pthread_mutex_unlock(&board->subjects_mutex); return errno; } (*subjp)->n = 0; if (buf != MAP_FAILED) { for (p = buf; p; (p = memchr(p, '\n', endp - p)) && p++ && (*subjp)->n++) ; munmap(buf, st.st_size); } } *subjp = NULL; free(_dats); pthread_mutex_unlock(&board->subjects_mutex); if ((i = read_setting(server, worker, board)) || (i = write_subjects(server, worker, board, "repair...", TRUE))) return i; return 0; } int make_html(server_t *server, worker_t *worker, request_t *request) { int i; subject_t *subject; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; pthread_mutex_lock(&request->board->subjects_mutex); for (subject = request->board->subjects; subject; subject = subject->next) { int fd; snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); pthread_mutex_lock(&subject->mutex); if ((fd = open(*worker->paths, O_RDONLY)) == -1) { pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } create_html(server, worker, request->board, subject, fd); close(fd); pthread_mutex_unlock(&subject->mutex); } pthread_mutex_unlock(&request->board->subjects_mutex); return write_index(server, worker, request->board, "makehtml...", FALSE); } static int _copy_rename(const char *old, const char *new, int isunlinkold) { char *buf; int fd_old, fd_new; struct stat st; struct timeval tvs[2]; fd_new = -1; buf = MAP_FAILED; if ((fd_old = open(old, O_RDONLY)) == -1 || (fd_new = open(new, O_WRONLY|O_CREAT|O_EXCL, 0644)) == -1 || fstat(fd_old, &st) || (st.st_size && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd_old, 0)) == MAP_FAILED)) { if (fd_new != -1) { close(fd_new); unlink(new); } if (fd_old != -1) close(fd_old); return errno; } if (buf != MAP_FAILED) { write(fd_new, buf, st.st_size); munmap(buf, st.st_size); } fchmod(fd_new, st.st_mode); close(fd_new); close(fd_old); if (isunlinkold) unlink(old); tvs->tv_sec = st.st_atime; tvs[1].tv_sec = st.st_mtime; tvs->tv_usec = tvs[1].tv_usec = 0; utimes(new, tvs); return 0; } #define _rename_xdev(old, new) _copy_rename(old, new, TRUE) #define _copy_file(src, dst) _copy_rename(src, dst, FALSE) typedef struct { uint64_t _resbits[(RES_MAX + 63) / 64]; } _resset_t; #define _resbit(n) (UINT64_C(1) << ((n - 1) & 63)) #define _resindex(n) ((n - 1) >> 6) #define _resemptyset(s) memset(&s, 0, sizeof s) #define _resaddset(s, n) (s._resbits[_resindex(n)] |= _resbit(n)) #define _resismember(s, n) (s._resbits[_resindex(n)] & _resbit(n)) int delete_dat(server_t *server, worker_t *worker, request_t *request, int istransparent) { char *delete_name; int i; subject_t *subject, **subjp; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; if (!istransparent) { if (!(delete_name = strchr(request->datline, '\x8'))) return EINVAL; *delete_name++ = 0; } else /* to avoid warning... */ delete_name = NULL; pthread_mutex_lock(&request->board->subjects_mutex); for (subjp = &request->board->subjects; *subjp && (*subjp)->key != request->key; subjp = &(*subjp)->next) ; if (!(subject = *subjp)) { pthread_mutex_unlock(&request->board->subjects_mutex); return ENOENT; } if (*request->datline == '*' && !request->datline[1]) { snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); if (!istransparent) snprintf(worker->paths[1], sizeof *worker->paths, "%s/test/%s", server->docroot, delete_name); if (istransparent ? unlink(*worker->paths) : _rename_xdev(*worker->paths, worker->paths[1])) { pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/html/%"PRIu64".html", server->docroot, (int)request->board->name_len, request->board->name, subject->key); unlink(*worker->paths); *subjp = subject->next; request->board->subjects_isdirty = TRUE; pthread_mutex_unlock(&request->board->subjects_mutex); free_subject(subject); } else { const char *endp; char *buf, *savepath, *p, *p2; int fd, ndel; FILE *fp; struct stat st; _resset_t _resset; if (!(savepath = strchr(istransparent ? request->datline : delete_name, '\x8'))) return EINVAL; *savepath++ = 0; _resemptyset(_resset); for (p = request->datline, ndel = 0; *p; *p == ',' && p++) { int j; if (!(i = *p == '-' ? 1 : strtoul(p, &p, 10)) || i > subject->n) { pthread_mutex_unlock(&request->board->subjects_mutex); return EINVAL; } j = *p != '-' ? i : *++p && *p != ',' ? strtoul(p, &p, 10) : subject->n; if (!j || j > subject->n || j < i || (i == 1 && j == subject->n)) { pthread_mutex_unlock(&request->board->subjects_mutex); return EINVAL; } if (istransparent) { for (; i <= j; i++) if (!_resismember(_resset, i)) { _resaddset(_resset, i); ndel++; } } else for (; i <= j; i++) _resaddset(_resset, i); } pthread_mutex_lock(&subject->mutex); snprintf(*worker->paths, sizeof *worker->paths, "%s/test/%s", server->docroot, savepath); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); if (_copy_file(worker->paths[1], *worker->paths)) { pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/dat/.%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); fd = -1; if (!(fp = fopen(*worker->paths, "w+")) || (fd = open(worker->paths[1], O_RDONLY)) == -1 || fstat(fd, &st) || (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { if (fd != -1) close(fd); if (fp) fclose(fp); pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } if (istransparent) { subject->n -= ndel; request->board->subjects_isdirty = TRUE; } if (_resismember(_resset, 1)) { subject_t *_subject; if (istransparent) { if ((_subject = realloc(subject, sizeof *subject))) *subjp = subject = _subject; *subject->title = 0; } else { if ((_subject = realloc(subject, sizeof *subject + strlen(delete_name)))) { *subjp = subject = _subject; strcpy(subject->title, delete_name); } else if (strlen(subject->title) >= strlen(delete_name)) strcpy(subject->title, delete_name); else *subject->title = 0; request->board->subjects_isdirty = TRUE; } } pthread_mutex_unlock(&request->board->subjects_mutex); for (endp = (p = buf) + st.st_size, i = 1; p && p < endp; p = p2, i++) { (p2 = memchr(p, '\n', endp - p)) && p2++; if (_resismember(_resset, i)) { if (!istransparent) fprintf(fp, "%s<>%s<>%s<>%s<>%s\n", delete_name, delete_name, delete_name, delete_name, i > 1 ? "" : delete_name); } else fprintf(fp, "%.*s", (int)(p2 ? p2 - p : endp - p), p); } munmap(buf, st.st_size); close(fd); fflush(fp); fchmod(fileno(fp), st.st_mode); if (rename(*worker->paths, worker->paths[1])) { unlink(*worker->paths); fclose(fp); pthread_mutex_unlock(&subject->mutex); return errno; } create_html(server, worker, request->board, subject, fileno(fp)); fclose(fp); pthread_mutex_unlock(&subject->mutex); } return write_subjects(server, worker, request->board, "delete...", *request->datline == '*'); } int move_dat(server_t *server, worker_t *worker, request_t *request) { static const char moved_title[] = "移転したよ。。。"; char *host, *moved_msg; int i; FILE *fp; subject_t *subject, **subjp, *_subject, *new_subject; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; if (!(moved_msg = strchr(request->datline, '\x8'))) return EINVAL; *moved_msg++ = 0; if (strchr(moved_msg, '\x8')) return EINVAL; pthread_mutex_lock(&request->board->subjects_mutex); for (subjp = &request->board->subjects; *subjp && (*subjp)->key != request->key; subjp = &(*subjp)->next) ; if (!(subject = *subjp)) { pthread_mutex_unlock(&request->board->subjects_mutex); return ENOENT; } snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); if ((host = strchr(request->datline, '@'))) { pthread_mutex_unlock(&request->board->subjects_mutex); return ENOTSUP; /* XXX: move to an external host */ } else { char *buf; int fd; struct stat st; struct timeval tvs[2]; pthread_mutex_lock(&subject->mutex); if (!(new_subject = alloc_subject(subject->key, subject->title, strlen(subject->title)))) { pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } new_subject->n = subject->n; new_subject->next = NULL; i = 0; do { snprintf(*worker->paths, sizeof *worker->paths, "%s/%s/dat/%"PRIu64".dat", server->docroot, request->datline, new_subject->key); fd = open(*worker->paths, O_WRONLY|O_CREAT|O_EXCL, 0644); } while (fd == -1 && errno == EEXIST && i++ < NEWTHR_RETRY && new_subject->key++); if (fd == -1) { free_subject(new_subject); pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } if ((i = open(worker->paths[1], O_RDONLY)) == -1 || fstat(i, &st) || (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, i, 0)) == MAP_FAILED) { if (i != -1) close(i); close(fd); unlink(*worker->paths); free_subject(new_subject); pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } write(fd, buf, st.st_size); munmap(buf, st.st_size); close(i); fchmod(fd, st.st_mode); close(fd); tvs->tv_sec = st.st_atime; tvs[1].tv_sec = st.st_mtime; tvs->tv_usec = tvs[1].tv_usec = 0; utimes(*worker->paths, tvs); } snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/dat/.%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); if (!(fp = fopen(*worker->paths, "w+"))) { if (!host) free_subject(new_subject); pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } if ((_subject = realloc(subject, sizeof *subject + arraylen(moved_title) - 1))) { *subjp = subject = _subject; strcpy(subject->title, moved_title); } else if (strlen(subject->title) >= arraylen(moved_title) - 1) strcpy(subject->title, moved_title); else *subject->title = 0; subject->n = 1; request->board->subjects_isdirty = TRUE; pthread_mutex_unlock(&request->board->subjects_mutex); fprintf(fp, "移転したよ。。。<>移転<>移転<>%shttp://%s/test/read.cgi/%s/%"PRIu64"/<>%s\n", moved_msg, host ? host : server->name, request->datline, new_subject->key, moved_title); fflush(fp); fchmod(fileno(fp), 0555); if (rename(*worker->paths, worker->paths[1])) { unlink(*worker->paths); fclose(fp); if (!host) free_subject(new_subject); pthread_mutex_unlock(&subject->mutex); return errno; } if (!host) { snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/html/%"PRIu64".html", server->docroot, (int)request->board->name_len, request->board->name, subject->key); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%s/html/%"PRIu64".html", server->docroot, request->datline, new_subject->key); _rename_xdev(*worker->paths, worker->paths[1]) && unlink(*worker->paths); } create_html(server, worker, request->board, subject, fileno(fp)); fclose(fp); pthread_mutex_unlock(&subject->mutex); if (host) worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "%"PRIu64, UINT64_C(0)); /* XXX: ENOTSUP */ else { request_t new_request; new_request.bbs = request->datline; if ((i = read_subjects(server, worker, &new_request)) || (i = read_setting(server, worker, new_request.board))) { free_subject(new_subject); return i; } pthread_mutex_lock(&new_request.board->subjects_mutex); for (subjp = &new_request.board->subjects; *subjp; subjp = &(*subjp)->next) ; *subjp = new_subject; new_request.board->subjects_isdirty = TRUE; worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "%"PRIu64, new_subject->key); pthread_mutex_unlock(&new_request.board->subjects_mutex); write_subjects(server, worker, new_request.board, "move...", TRUE); } if ((i = write_subjects(server, worker, request->board, "move...", TRUE))) return i; worker->writebuf_rwlock = NULL; return -1; } int stop_restart_dat(server_t *server, worker_t *worker, request_t *request, int isrestart) { int i; size_t datlinelen = strlen(request->datline); subject_t *subject; struct stat st; if (datlinelen) { char *p; for (i = 0, p = request->datline; i < 4 && p; i++, (p = strstr(p, "<>")) && (p += 2)) ; if (!p) return EINVAL; } if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; pthread_mutex_lock(&request->board->subjects_mutex); for (subject = request->board->subjects; subject && subject->key != request->key; subject = subject->next) ; if (!subject) { pthread_mutex_unlock(&request->board->subjects_mutex); return ENOENT; } snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); pthread_mutex_lock(&subject->mutex); if (stat(*worker->paths, &st)) { pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } if (isrestart ? st.st_mode & S_IWUSR : !(st.st_mode & S_IWUSR)) { pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return EALREADY; } if (isrestart && chmod(*worker->paths, 0666)) { pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } if (datlinelen) { if ((i = open(*worker->paths, O_RDWR|O_APPEND)) == -1) { pthread_mutex_unlock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } subject->n++; request->board->subjects_isdirty = TRUE; pthread_mutex_unlock(&request->board->subjects_mutex); request->datline[datlinelen++] = '\n'; write(i, request->datline, datlinelen); if (!isrestart && fchmod(i, 0555)) { close(i); pthread_mutex_unlock(&subject->mutex); return errno; } create_html(server, worker, request->board, subject, i); close(i); } else { pthread_mutex_unlock(&request->board->subjects_mutex); if (!isrestart && chmod(*worker->paths, 0555)) { pthread_mutex_unlock(&subject->mutex); return errno; } } pthread_mutex_unlock(&subject->mutex); return write_subjects(server, worker, request->board, isrestart ? "restart..." : "stop...", FALSE); } int inject_dat(server_t *server, worker_t *worker, request_t *request) { int i; const char *p, *p2, *endp; char *buf; subject_t **subjp; struct stat st; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; snprintf(*worker->paths, sizeof *worker->paths, "%s/test/%s", server->docroot, request->datline); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, request->key); if ((i = open(*worker->paths, O_RDONLY)) == -1 || fstat(i, &st) || (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, i, 0)) == MAP_FAILED) { if (i != -1) close(i); return errno; } close(i); endp = buf + st.st_size; for (i = 0, p = buf; i < 4 && p; i++) while ((p = memchr(p, '<', endp - 1 - p)) && *++p != '>') ; if (!p || ++p >= endp || !(p2 = memchr(p, '\n', endp - p)) || p > p2) { p = "[ここ壊れてます]"; p2 = p + arraylen("[ここ壊れてます]") - 1; } pthread_mutex_lock(&request->board->subjects_mutex); for (subjp = &request->board->subjects; *subjp && (*subjp)->key != request->key; subjp = &(*subjp)->next) ; if (*subjp) { subject_t *subject; pthread_mutex_lock(&(*subjp)->mutex); if ((subject = realloc(*subjp, sizeof **subjp + p2 - p))) { *subjp = subject; sprintf((*subjp)->title, "%.*s", (int)(p2 - p), p); } else if (strlen((*subjp)->title) >= p2 - p) sprintf((*subjp)->title, "%.*s", (int)(p2 - p), p); else *(*subjp)->title = 0; } else if (!(*subjp = alloc_subject(request->key, p, p2 - p))) { munmap(buf, st.st_size); pthread_mutex_unlock(&request->board->subjects_mutex); return errno; } else { (*subjp)->next = NULL; pthread_mutex_lock(&(*subjp)->mutex); } for (p = buf, (*subjp)->n = 0; p; (p = memchr(p, '\n', endp - p)) && p++ && (*subjp)->n++) ; request->board->subjects_isdirty = TRUE; pthread_mutex_unlock(&request->board->subjects_mutex); if (rename(*worker->paths, worker->paths[1])) { struct timeval tvs[2]; if (errno != EXDEV || (i = open(worker->paths[1], O_RDWR|O_CREAT|O_TRUNC, 0644)) == -1) { munmap(buf, st.st_size); pthread_mutex_unlock(&(*subjp)->mutex); return errno; } write(i, buf, st.st_size); fchmod(i, st.st_mode); unlink(*worker->paths); tvs->tv_sec = st.st_atime; tvs[1].tv_sec = st.st_mtime; tvs->tv_usec = tvs[1].tv_usec = 0; utimes(worker->paths[1], tvs); } else i = open(worker->paths[1], O_RDONLY); munmap(buf, st.st_size); if (i != -1) { create_html(server, worker, request->board, *subjp, i); close(i); } pthread_mutex_unlock(&(*subjp)->mutex); return write_subjects(server, worker, request->board, "inject...", TRUE); } int purge_dats(server_t *server, worker_t *worker, request_t *request) { char *p; int i; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; for (p = request->datline; *p; *p == ',' && p++) { uint64_t key; subject_t *subject, **subjp; if (!(key = strtoull(p, &p, 10))) return EINVAL; pthread_mutex_lock(&request->board->subjects_mutex); for (subjp = &request->board->subjects; *subjp && (*subjp)->key != key; subjp = &(*subjp)->next) ; if (!(subject = *subjp)) { pthread_mutex_unlock(&request->board->subjects_mutex); return ENOENT; } *subjp = subject->next; request->board->subjects_isdirty = TRUE; pthread_mutex_unlock(&request->board->subjects_mutex); free_subject(subject); } return write_subjects(server, worker, request->board, "purge...", TRUE); } int autopurge_dats(server_t *server, worker_t *worker, request_t *request) { int i; subject_t **subjp; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; pthread_mutex_lock(&request->board->subjects_mutex); for (subjp = &request->board->subjects; *subjp; ) { struct stat st; snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, (*subjp)->key); if (stat(*worker->paths, &st)) { if (errno == ENOENT) { subject_t *subject = *subjp; *subjp = subject->next; request->board->subjects_isdirty = TRUE; free_subject(subject); } else { syslogx(LOG_ERR, "autopurge_dats: stat: %s: %m", *worker->paths); subjp = &(*subjp)->next; /* pthread_mutex_unlock(&request->board->subjects_mutex); return errno; */ } } else subjp = &(*subjp)->next; } pthread_mutex_unlock(&request->board->subjects_mutex); return write_subjects(server, worker, request->board, "autopurge...", TRUE); } int unload_board(server_t *server, worker_t *worker, const char *bbs) { const char *p; unsigned bbs_hash; size_t bbs_len; board_t *board, **boardp; for (p = bbs, bbs_hash = 0; *p; p++) bbs_hash = HASH_FACTOR * bbs_hash + (unsigned char)*p; bbs_len = p - bbs; pthread_mutex_lock(&server->boards_mutex); for (boardp = &server->boards; *boardp && ((*boardp)->name_hash != bbs_hash || (*boardp)->name_len != bbs_len || memcmp((*boardp)->name, bbs, bbs_len)); boardp = &(*boardp)->next) ; if (!(board = *boardp)) { pthread_mutex_unlock(&server->boards_mutex); return 0; } *boardp = board->next; pthread_mutex_unlock(&server->boards_mutex); read_setting(server, worker, board); write_subjects(server, worker, board, "unload...", TRUE); free_board(board); return 0; } int get_1(server_t *server, worker_t *worker, request_t *request) { char *p, *buf; int i; subject_t *subject; struct stat st; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; pthread_mutex_lock(&request->board->subjects_mutex); for (subject = request->board->subjects; subject && subject->key != request->key; subject = subject->next) ; if (!subject) { pthread_mutex_unlock(&request->board->subjects_mutex); return ENOENT; } pthread_mutex_lock(&subject->mutex); pthread_mutex_unlock(&request->board->subjects_mutex); snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/dat/%"PRIu64".dat", server->docroot, (int)request->board->name_len, request->board->name, subject->key); if ((i = open(*worker->paths, O_RDONLY)) == -1 || fstat(i, &st) || (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, i, 0)) == MAP_FAILED) { if (i != -1) close(i); pthread_mutex_unlock(&subject->mutex); return errno; } worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "%.*s", (int)((p = memchr(buf, '\n', st.st_size)) ? p - buf : st.st_size), buf); if (worker->writesize >= sizeof worker->readbuf) worker->writesize = sizeof worker->readbuf - 1; worker->writebuf_rwlock = NULL; munmap(buf, st.st_size); close(i); pthread_mutex_unlock(&subject->mutex); return -1; } int raise_dat(server_t *server, worker_t *worker, request_t *request) { int i; subject_t *subject, **subjp; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; pthread_mutex_lock(&request->board->subjects_mutex); for (subjp = &request->board->subjects; *subjp && (*subjp)->key != request->key; subjp = &(*subjp)->next) ; if (!(subject = *subjp)) { pthread_mutex_unlock(&request->board->subjects_mutex); return ENOENT; } if (subjp != &request->board->subjects) { *subjp = subject->next; subject->next = request->board->subjects; request->board->subjects = subject; request->board->subjects_isdirty = TRUE; } pthread_mutex_unlock(&request->board->subjects_mutex); return write_subjects(server, worker, request->board, "raise...", FALSE); } int touch(const server_t *server, worker_t *worker, const request_t *request) { struct timeval tvs[2]; snprintf(*worker->paths, sizeof *worker->paths, "%s/test/%s", server->docroot, request->bbs); tvs->tv_sec = tvs[1].tv_sec = request->key; tvs->tv_usec = tvs[1].tv_usec = 0; return utimes(*worker->paths, request->key ? tvs : NULL) ? errno : 0; } int cp(const server_t *server, worker_t *worker, const request_t *request) { snprintf(*worker->paths, sizeof *worker->paths, "%s/test/%s", server->docroot, request->bbs); snprintf(worker->paths[1], sizeof *worker->paths, "%s/test/%s", server->docroot, request->datline); return _copy_file(*worker->paths, worker->paths[1]); } int get_filesize(const server_t *server, worker_t *worker, const char *path) { struct stat st; snprintf(*worker->paths, sizeof *worker->paths, "%s/test/%s", server->docroot, path); if (stat(*worker->paths, &st)) return errno; worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "%"PRIu64, (uint64_t)st.st_size); worker->writebuf_rwlock = NULL; return -1; } #define _time_hour(t) (size_t)(t / (60 * 60)) #define _time_min(t) (unsigned)(t / 60 % 60) #define _time_sec(t) (unsigned)(t % 60) int stat_board(server_t *server, worker_t *worker, const char *bbs) { const char *p, *notyet, *bufferis, *cleandirty; char *_bbs; unsigned bbs_hash; size_t bbs_len; board_t *board; struct timespec ts; struct rusage ru; if (clock_gettime(CLOCK_HIGHRES, &ts) || getrusage(RUSAGE_SELF, &ru)) return errno; ts.tv_sec -= server->starttime.tv_sec + ((ts.tv_nsec -= server->starttime.tv_nsec) < 0 ? (ts.tv_nsec += 1000000000, 1) : 0); if (!ts.tv_sec && ts.tv_nsec < 499999) ts.tv_nsec = 499999; /* to avoid SIGFPE */ for (p = bbs, bbs_hash = 0; *p; p++) bbs_hash = HASH_FACTOR * bbs_hash + (unsigned char)*p; bbs_len = p - bbs; pthread_mutex_lock(&server->boards_mutex); for (board = server->boards; board && (board->name_hash != bbs_hash || board->name_len != bbs_len || memcmp(board->name, bbs, bbs_len)); board = board->next) ; pthread_mutex_unlock(&server->boards_mutex); if (board) { _bbs = NULL; bbs = board->name; notyet = ""; bufferis = "Its subject buffer is "; cleandirty = board->subjects_isdirty ? "dirty.\n" : "clean.\n"; } else { if (!(_bbs = malloc(bbs_len))) return errno; memcpy(_bbs, bbs, bbs_len); bbs = _bbs; notyet = "not yet "; bufferis = cleandirty = ""; } worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "\"%.*s\" is %sloaded.\n%s%s\n" "user CPU time = %zu:%.2u:%.2u.%.3u, system CPU time = %zu:%.2u:%.2u.%.3u\n" "elapsed time = %zu:%.2u:%.2u.%.3u, CPU load = %.2f%%\n\n" #ifdef USE_THREADS "total worker threads = %zu, idle worker threads = %zu\n\n" #endif "minor page faults = %li, major page faults = %li, swaps = %li\n" "block inputs = %li, block outputs = %li\n" "messages sent = %li, messages received = %li\n" "signals = %li, vol ctx switches = %li, invol ctx switches = %li\n", (int)bbs_len, bbs, notyet, bufferis, cleandirty, _time_hour(ru.ru_utime.tv_sec), _time_min(ru.ru_utime.tv_sec), _time_sec(ru.ru_utime.tv_sec), (unsigned)((ru.ru_utime.tv_usec + 500) / 1000), _time_hour(ru.ru_stime.tv_sec), _time_min(ru.ru_stime.tv_sec), _time_sec(ru.ru_stime.tv_sec), (unsigned)((ru.ru_stime.tv_usec + 500) / 1000), _time_hour(ts.tv_sec), _time_min(ts.tv_sec), _time_sec(ts.tv_sec), (unsigned)((ts.tv_nsec + 500000) / 1000000), 100.0 * (ru.ru_utime.tv_sec + ru.ru_stime.tv_sec + (ru.ru_utime.tv_usec + ru.ru_stime.tv_usec) / 1000000.0) / (ts.tv_sec + ts.tv_nsec / 1000000000.0), #ifdef USE_THREADS (volatile size_t)server->nthr_cur, (volatile size_t)server->nthr_idle, #endif ru.ru_minflt, ru.ru_majflt, ru.ru_nswap, ru.ru_inblock, ru.ru_oublock, ru.ru_msgsnd, ru.ru_msgrcv, ru.ru_nsignals, ru.ru_nvcsw, ru.ru_nivcsw); if (worker->writesize >= sizeof worker->readbuf) worker->writesize = sizeof worker->readbuf - 1; free(_bbs); worker->writebuf_rwlock = NULL; return -1; } int check_id(server_t *server, worker_t *worker, request_t *request, int command) { const char *id; char *p; int i; unsigned hash, db_hash; size_t len, db_len, nwarn, nkick; time_t seconds; FILE *fp; chkiddb_t **chkiddbp; chkid_t **chkidp; for (p = request->datline, db_hash = 0; *p && *p != '\x8'; p++) db_hash = HASH_FACTOR * db_hash + (unsigned char)*p; db_len = p - request->datline; if (command == COMMAND_clearids || command == COMMAND_countids) { if (*p) return EINVAL; id = NULL; /* to avoid warning... */ hash = 0; len = nwarn = nkick = 0; seconds = 0; } else { if (!*p++) return EINVAL; for (id = p, hash = 0; *p && *p != '\x8'; p++) hash = HASH_FACTOR * hash + (unsigned char)*p; len = p - id; if (!*p++) return EINVAL; seconds = strtoul(p, &p, 10); if (*p++ != '\x8') return EINVAL; nwarn = strtoul(p, &p, 10); if (*p++ != '\x8') return EINVAL; nkick = strtoul(p, &p, 10); if (*p) return EINVAL; } if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; pthread_mutex_lock(&request->board->chkiddbs_mutex); if (!request->board->chkiddbs) { struct stat st; snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/%s_dbs", server->docroot, (int)request->board->name_len, request->board->name, server->argv0); if (stat(*worker->paths, &st) || !S_ISDIR(st.st_mode)) { static const char denyfromall[] = "Deny from all\n"; unlink(*worker->paths) && rmdir(*worker->paths); snprintf(worker->paths[1], sizeof *worker->paths, "%s/.htaccess", *worker->paths); if (mkdir(*worker->paths, 0700) || (i = open(worker->paths[1], O_WRONLY|O_CREAT, 0644)) == -1) { rmdir(*worker->paths); pthread_mutex_unlock(&request->board->chkiddbs_mutex); return errno; } write(i, denyfromall, arraylen(denyfromall) - 1); close(i); } } for (chkiddbp = &request->board->chkiddbs; *chkiddbp && ((*chkiddbp)->name_hash != db_hash || (*chkiddbp)->name_len != db_len || memcmp((*chkiddbp)->name, request->datline, db_len)); chkiddbp = &(*chkiddbp)->next) ; if (!*chkiddbp) { if (!(*chkiddbp = malloc(sizeof **chkiddbp + db_len - 1)) || (errno = pthread_mutex_init(&(*chkiddbp)->mutex, NULL))) { free(*chkiddbp); *chkiddbp = NULL; pthread_mutex_unlock(&request->board->chkiddbs_mutex); return errno; } memcpy((*chkiddbp)->name, request->datline, (*chkiddbp)->name_len = db_len); (*chkiddbp)->name_hash = db_hash; (*chkiddbp)->ids = NULL; (*chkiddbp)->next = NULL; } pthread_mutex_lock(&(*chkiddbp)->mutex); pthread_mutex_unlock(&request->board->chkiddbs_mutex); snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/%s_dbs/.%.*s", server->docroot, (int)request->board->name_len, request->board->name, server->argv0, (int)(*chkiddbp)->name_len, (*chkiddbp)->name); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/%s_dbs/%.*s", server->docroot, (int)request->board->name_len, request->board->name, server->argv0, (int)(*chkiddbp)->name_len, (*chkiddbp)->name); if (!(*chkiddbp)->ids && (i = open(worker->paths[1], O_RDONLY)) != -1) { char *buf; struct stat st; if (!fstat(i, &st) && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, i, 0)) != MAP_FAILED) { const char *endp; char *p2, *eol; unsigned hash; for (endp = (p = buf) + st.st_size, chkidp = &(*chkiddbp)->ids; p < endp; p = ++eol, chkidp = &(*chkidp)->next) { if (!(eol = memchr(p, '\n', endp - p))) break; for (p2 = p, hash = 0; p2 < eol && *p2 != ','; p2++) hash = HASH_FACTOR * hash + (unsigned char)*p2; if (!(*chkidp = malloc(sizeof **chkidp + p2 - p - 1))) { chkid_t *chkid, *chkid_next; for (chkid = (*chkiddbp)->ids; chkid; chkid = chkid_next) { chkid_next = chkid->next; free(chkid); } (*chkiddbp)->ids = NULL; munmap(buf, st.st_size); close(i); pthread_mutex_unlock(&(*chkiddbp)->mutex); return errno; } (*chkidp)->hash = hash; memcpy((*chkidp)->id, p, (*chkidp)->len = p2 - p); (*chkidp)->n = *p2++ == ',' ? strtoul(p2, &p2, 10) : (p2--, 0); (*chkidp)->time = *p2++ == ',' ? atol(p2) : 0; } *chkidp = NULL; munmap(buf, st.st_size); } close(i); } if (command == COMMAND_clearids) { chkid_t *chkid, *chkid_next; for (chkid = (*chkiddbp)->ids; chkid; chkid = chkid_next) { chkid_next = chkid->next; free(chkid); } (*chkiddbp)->ids = NULL; truncate(worker->paths[1], 0); pthread_mutex_unlock(&(*chkiddbp)->mutex); return 0; } if (command == COMMAND_countids) { for (chkidp = &(*chkiddbp)->ids, i = 0; *chkidp; chkidp = &(*chkidp)->next, i++) ; pthread_mutex_unlock(&(*chkiddbp)->mutex); worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "%i", i); worker->writebuf_rwlock = NULL; return -1; } for (chkidp = &(*chkiddbp)->ids; *chkidp; ) if (worker->now - (*chkidp)->time >= CHKID_AGE) { chkid_t *chkid = *chkidp; *chkidp = chkid->next; free(chkid); } else if ((*chkidp)->hash == hash && (*chkidp)->len == len && !memcmp((*chkidp)->id, id, len)) { if ((*chkidp)->n == (size_t)-1) i = 3; else if (worker->now - (*chkidp)->time >= seconds) { if (command != COMMAND_peekid) (*chkidp)->n = 1; i = 0; } else if ((command == COMMAND_peekid ? (*chkidp)->n : ++(*chkidp)->n) <= nwarn) i = 1; else if ((*chkidp)->n <= nkick) i = 2; else { (*chkidp)->n = -1; i = 3; } worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "%i,%zu,%li", i, (*chkidp)->n, (long)(worker->now - (*chkidp)->time)); worker->writebuf_rwlock = NULL; if (command != COMMAND_peekid) { (*chkidp)->time = worker->now; if ((fp = fopen(*worker->paths, "w"))) { for (chkidp = &(*chkiddbp)->ids; *chkidp; chkidp = &(*chkidp)->next) fprintf(fp, "%.*s,%zu,%li\n", (int)(*chkidp)->len, (*chkidp)->id, (*chkidp)->n, (long)(*chkidp)->time); fclose(fp); if (rename(*worker->paths, worker->paths[1])) unlink(*worker->paths); } } pthread_mutex_unlock(&(*chkiddbp)->mutex); return -1; } else chkidp = &(*chkidp)->next; if (command != COMMAND_peekid) { if (!(*chkidp = malloc(sizeof **chkidp + len - 1))) { pthread_mutex_unlock(&(*chkiddbp)->mutex); return errno; } (*chkidp)->hash = hash; memcpy((*chkidp)->id, id, (*chkidp)->len = len); (*chkidp)->n = 1; (*chkidp)->time = worker->now; (*chkidp)->next = NULL; if ((fp = fopen(*worker->paths, "w"))) { for (chkidp = &(*chkiddbp)->ids; *chkidp; chkidp = &(*chkidp)->next) fprintf(fp, "%.*s,%zu,%li\n", (int)(*chkidp)->len, (*chkidp)->id, (*chkidp)->n, (long)(*chkidp)->time); fclose(fp); if (rename(*worker->paths, worker->paths[1])) unlink(*worker->paths); } } pthread_mutex_unlock(&(*chkiddbp)->mutex); worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "0,%u,0", command != COMMAND_peekid ? 1 : 0); worker->writebuf_rwlock = NULL; return -1; } int check_timecount(server_t *server, worker_t *worker, request_t *request) { const char *p; int i, n; unsigned hash; size_t len, timeclose, timecount; time_t age; pthread_mutex_t *mutex; chktimecount_t **chktimecountp, **chktimecountsp; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; if (request->key) { age = strtoul(request->datline, &request->datline, 10); if (*request->datline++ != '\x8') return EINVAL; timecount = strtoul(request->datline, &request->datline, 10); if (*request->datline++ != '\x8') return EINVAL; timeclose = strtoul(request->datline, &request->datline, 10); if (*request->datline++ != '\x8' || strchr(request->datline, '\x8')) return EINVAL; } else { age = INT32_MAX; pthread_rwlock_rdlock(&request->board->settings_rwlock); timecount = strtoul(get_setting(request->board, "timecount"), NULL, 10); timeclose = strtoul(get_setting(request->board, "timeclose"), NULL, 10); pthread_rwlock_unlock(&request->board->settings_rwlock); } for (p = request->datline, hash = 0; *p; p++) hash = HASH_FACTOR * hash + (unsigned char)*p; len = p - request->datline; if (request->key) { subject_t *subject; pthread_mutex_lock(&request->board->subjects_mutex); for (subject = request->board->subjects; subject && subject->key != request->key; subject = subject->next) ; if (!subject) { pthread_mutex_unlock(&request->board->subjects_mutex); return ENOENT; } pthread_mutex_lock(mutex = &subject->chktimecounts_mutex); pthread_mutex_unlock(&request->board->subjects_mutex); chktimecountsp = &subject->chktimecounts; } else { pthread_mutex_lock(mutex = &request->board->chktimecounts_mutex); chktimecountsp = &request->board->chktimecounts; } for (chktimecountp = chktimecountsp, i = n = 0; *chktimecountp; ) if (worker->now - (*chktimecountp)->time >= age) { chktimecount_t *chktimecount = *chktimecountp; *chktimecountp = chktimecount->next; free(chktimecount); } else if ((*chktimecountp)->hash == hash && (*chktimecountp)->len == len && !memcmp((*chktimecountp)->id, request->datline, len) && ++n >= timeclose) { pthread_mutex_unlock(mutex); worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "%i", n); worker->writebuf_rwlock = NULL; return -1; } else chktimecountp = &(*chktimecountp)->next, i++; if (!(*chktimecountp = malloc(sizeof **chktimecountp + len - 1))) { pthread_mutex_unlock(mutex); return errno; } (*chktimecountp)->hash = hash; memcpy((*chktimecountp)->id, request->datline, (*chktimecountp)->len = len); (*chktimecountp)->time = worker->now; (*chktimecountp)->next = NULL; for (; i >= timecount; i--) { chktimecount_t *chktimecount = *chktimecountsp; if (!chktimecount) break; *chktimecountsp = chktimecount->next; free(chktimecount); } pthread_mutex_unlock(mutex); *(worker->writebuf = worker->readbuf) = '0'; worker->writesize = 1; worker->writebuf_rwlock = NULL; return -1; } int check_thread(server_t *server, worker_t *worker, request_t *request) { char *p; int i, key; size_t n; FILE *fp; chkthr_t **chkthrp; if ((i = read_subjects(server, worker, request)) || (i = read_setting(server, worker, request->board))) return i; if (!(p = strchr(request->datline, '\x8'))) return EINVAL; snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/.%.*s", server->docroot, (int)request->board->name_len, request->board->name, (int)(p - request->datline), request->datline); snprintf(worker->paths[1], sizeof *worker->paths, "%s/%.*s/%.*s", server->docroot, (int)request->board->name_len, request->board->name, (int)(p++ - request->datline), request->datline); key = strtol(p, &p, 10); if (*p++ != '\x8' || strchr(p, '\x8')) return EINVAL; pthread_rwlock_rdlock(&request->board->settings_rwlock); n = strtoul(get_setting(request->board, "BBS_THREAD_TATESUGI"), NULL, 10); pthread_rwlock_unlock(&request->board->settings_rwlock); pthread_mutex_lock(&request->board->chkthrs_mutex); if (!request->board->chkthrs && (i = open(worker->paths[1], O_RDONLY)) != -1) { char *buf; struct stat st; if (!fstat(i, &st) && (buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, i, 0)) != MAP_FAILED) { const char *p, *p2, *endp, *eol; for (endp = (p = buf) + st.st_size, chkthrp = &request->board->chkthrs; p < endp; p = ++eol, chkthrp = &(*chkthrp)->next) { (eol = memchr(p, '\n', endp - p)) || (eol = endp); if (!(p2 = memchr(p, ',', eol - p))) { p2 = p - 1; p = "0"; } if (!(*chkthrp = malloc(sizeof **chkthrp + eol - ++p2))) { chkthr_t *chkthr, *chkthr_next; for (chkthr = request->board->chkthrs; chkthr; chkthr = chkthr_next) { chkthr_next = chkthr->next; free(chkthr); } request->board->chkthrs = NULL; munmap(buf, st.st_size); close(i); pthread_mutex_unlock(&request->board->chkthrs_mutex); return errno; } (*chkthrp)->key = atoi(p); sprintf((*chkthrp)->value, "%.*s", (int)(eol - p2), p2); } *chkthrp = NULL; munmap(buf, st.st_size); } close(i); } for (chkthrp = &request->board->chkthrs, i = 0; *chkthrp; chkthrp = &(*chkthrp)->next, i++) if ((*chkthrp)->key == key) { worker->writesize = strlcpy(worker->writebuf = worker->readbuf, (*chkthrp)->value, sizeof worker->readbuf); if (worker->writesize >= sizeof worker->readbuf) worker->writesize = sizeof worker->readbuf - 1; worker->writebuf_rwlock = NULL; pthread_mutex_unlock(&request->board->chkthrs_mutex); return -1; } if (!(*chkthrp = malloc(sizeof **chkthrp + strlen(p)))) { pthread_mutex_unlock(&request->board->chkthrs_mutex); return errno; } (*chkthrp)->key = key; strcpy((*chkthrp)->value, p); (*chkthrp)->next = NULL; for (; i >= n; i--) { chkthr_t *chkthr = request->board->chkthrs; if (!chkthr) break; request->board->chkthrs = chkthr->next; free(chkthr); } if ((fp = fopen(*worker->paths, "w"))) { for (chkthrp = &request->board->chkthrs; *chkthrp; chkthrp = &(*chkthrp)->next) fprintf(fp, "%i,%s\n", (*chkthrp)->key, (*chkthrp)->value); fclose(fp); if (rename(*worker->paths, worker->paths[1])) unlink(*worker->paths); } pthread_mutex_unlock(&request->board->chkthrs_mutex); return 0; } int get_ndats(server_t *server, worker_t *worker, request_t *request) { int i; subject_t *subject; if ((i = read_subjects(server, worker, request))) return i; pthread_mutex_lock(&request->board->subjects_mutex); for (subject = request->board->subjects, i = 0; subject; subject = subject->next, i++) ; pthread_mutex_unlock(&request->board->subjects_mutex); worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "%i", i); worker->writebuf_rwlock = NULL; return -1; } int get_md5seed(server_t *server, worker_t *worker, request_t *request) { char md5date[10 + 2 + 1]; /* yyyy_mm_dd<> */ int i; struct tm tm; if ((i = read_subjects(server, worker, request))) return i; strftime(md5date, sizeof md5date, "%Y_%m_%d<>", localtime_r(&worker->now, &tm)); pthread_rwlock_rdlock(&request->board->md5seed_rwlock); if (request->board->md5seed == MAP_FAILED) { pthread_rwlock_unlock(&request->board->md5seed_rwlock); pthread_rwlock_wrlock(&request->board->md5seed_rwlock); if (request->board->md5seed == MAP_FAILED) { struct stat st; snprintf(*worker->paths, sizeof *worker->paths, "%s/%.*s/md5.cgi", server->docroot, (int)request->board->name_len, request->board->name); if ((i = open(*worker->paths, O_RDWR|O_CREAT, 0600)) == -1 || fstat(i, &st) || (st.st_size != 10 + 2 + MD5SEED_LENGTH && ftruncate(i, 10 + 2 + MD5SEED_LENGTH)) /* yyyy_mm_dd<>md5seed */ || (request->board->md5seed = mmap(NULL, 10 + 2 + MD5SEED_LENGTH, PROT_READ|PROT_WRITE, MAP_SHARED, i, 0)) == MAP_FAILED) { if (i != -1) close(i); pthread_rwlock_unlock(&request->board->md5seed_rwlock); return errno; } close(i); } pthread_rwlock_unlock(&request->board->md5seed_rwlock); pthread_rwlock_rdlock(&request->board->md5seed_rwlock); } if (memcmp(request->board->md5seed, md5date, 10 + 2)) { pthread_rwlock_unlock(&request->board->md5seed_rwlock); pthread_rwlock_wrlock(&request->board->md5seed_rwlock); if (memcmp(request->board->md5seed, md5date, 10 + 2)) { strlcpy(request->board->md5seed, md5date, 10 + 2 + MD5SEED_LENGTH); if ((i = open("/dev/urandom", O_RDONLY)) != -1) { read(i, request->board->md5seed + 10 + 2, MD5SEED_LENGTH); close(i); } else { unsigned short xi[3] = {worker->now, getpid(), getuid()}; for (i = 0; i < MD5SEED_LENGTH; i++) request->board->md5seed[10 + 2 + i] = nrand48(xi); } } } pthread_rwlock_unlock(&request->board->md5seed_rwlock); worker->writebuf = request->board->md5seed; worker->writesize = 10 + 2 + MD5SEED_LENGTH; worker->writebuf_rwlock = &request->board->md5seed_rwlock; return -1; } void append_log(const server_t *server, worker_t *worker, request_t *request) { char *p; int fd; size_t len; if (!(p = strchr(request->datline, ':')) || (*p++ = 0) || !snprintf(*worker->paths, sizeof *worker->paths, "%s/test/%s", server->docroot, request->datline) || (fd = open(*worker->paths, O_WRONLY|O_APPEND|O_CREAT, 0600)) == -1) return; len = strlen(p); p[len++] = '\n'; write(fd, p, len); close(fd); } int process_command(server_t *server, worker_t *worker, request_t *request, char *p) { const char *_p; int i; unsigned hash; size_t len; for (_p = p, hash = 0; *_p && *_p != '\x8' && *_p != ':'; _p++) hash = HASH_FACTOR * hash + (unsigned char)*_p; len = _p - p; for (i = 0; i < arraylen(commands) && (commands[i].hash != hash || commands[i].len != len || commands[i].delim != p[len] || memcmp(commands[i].name, p, len)); i++) ; switch (i) { case COMMAND_chkid: case COMMAND_peekid: case COMMAND_clearids: case COMMAND_countids: request->datline = p + len + 1; return check_id(server, worker, request, i); case COMMAND_chktimecount: request->key = 0; request->datline = p + len + 1; if (strchr(request->datline, '\x8')) return EINVAL; return check_timecount(server, worker, request); case COMMAND_chkthrtimecount: request->key = strtoull(p + len + 1, &request->datline, 10); if (*request->datline != '\x8') return EINVAL; *request->datline++ = 0; return check_timecount(server, worker, request); case COMMAND_chkthr: request->datline = p + len + 1; return check_thread(server, worker, request); case COMMAND_getndats: return get_ndats(server, worker, request); case COMMAND_getmd5seed: return get_md5seed(server, worker, request); case COMMAND_get1: request->key = strtoull(p + len + 1, &request->datline, 10); if (*request->datline) return EINVAL; return get_1(server, worker, request); case COMMAND_raise: request->key = strtoull(p + len + 1, &request->datline, 10); if (*request->datline) return EINVAL; return raise_dat(server, worker, request); case COMMAND_touch: request->key = strtoull(p + len + 1, &request->datline, 10); if (*request->datline) return EINVAL; return touch(server, worker, request); case COMMAND_cp: request->datline = p + len + 1; if (strchr(request->datline, '\x8')) return EINVAL; return cp(server, worker, request); case COMMAND_getfilesize: return get_filesize(server, worker, request->bbs); case COMMAND_stat: return stat_board(server, worker, request->bbs); case COMMAND_repair: return repair_board(server, worker, request->bbs); case COMMAND_makehtml: return make_html(server, worker, request); case COMMAND_delete: case COMMAND_tdelete: request->key = strtoull(p + len + 1, &request->datline, 10); if (*request->datline != '\x8') return EINVAL; *request->datline++ = 0; return delete_dat(server, worker, request, *p == 't'); case COMMAND_move: request->key = strtoull(p + len + 1, &request->datline, 10); if (*request->datline != '\x8') return EINVAL; *request->datline++ = 0; return move_dat(server, worker, request); case COMMAND_stop: case COMMAND_restart: request->key = strtoull(p + len + 1, &request->datline, 10); if (*request->datline != '\x8') return EINVAL; *request->datline++ = 0; if (strchr(request->datline, '\x8')) return EINVAL; return stop_restart_dat(server, worker, request, *p == 'r'); case COMMAND_inject: request->key = strtoull(p + len + 1, &request->datline, 10); if (*request->datline != '\x8') return EINVAL; *request->datline++ = 0; if (strchr(request->datline, '\x8')) return EINVAL; return inject_dat(server, worker, request); case COMMAND_purge: request->datline = p + len + 1; return purge_dats(server, worker, request); case COMMAND_autopurge: return autopurge_dats(server, worker, request); case COMMAND_unload: return unload_board(server, worker, request->bbs); } return EINVAL; } int process_request(server_t *server, worker_t *worker) { char *p, *email = NULL, *notes[NUM_NOTES]; int i; request_t request; time(&worker->now); p = worker->readbuf + worker->readsize - 1; p[p >= worker->readbuf && *p == '\n' ? 0 : 1] = 0; /* "bbs\x8key\x8datline\x8footnote\x8adfile1\x8adfile2\x8adfile3\x8adline\x8logline" or "bbs\x8cmd[\x8param]\x8logline" */ for (; p >= worker->readbuf && *p != '\x8'; p--) ; if ((request.datline = p) < worker->readbuf) return EINVAL; *request.datline++ = 0; if (request.datline - 2 >= worker->readbuf && request.datline[-2] == '\n') request.datline[-2] = 0; if (!(p = strchr(request.bbs = worker->readbuf, '\x8'))) return EINVAL; *p++ = 0; append_log(server, worker, &request); if (!isdigit((unsigned char)*p)) return process_command(server, worker, &request, p); request.key = strtoull(p, &request.datline, 10); if (*request.datline != '\x8') return EINVAL; *request.datline++ = 0; for (i = 0, p = request.datline; i < arraylen(notes); p = notes[i++]) { if (!(notes[i] = strchr(p, '\x8'))) return EINVAL; *notes[i]++ = 0; if (notes[i] - 2 >= p && notes[i][-2] == '\n') notes[i][-2] = 0; } if (strchr(p, '\x8')) return EINVAL; /* "name<>email<>date etc.<>message<>[title]" */ for (i = 0, p = request.datline; i < 4; i++) { if (!(p = strstr(p, "<>"))) return EINVAL; p += 2; switch (i) { case 0: email = p; break; case 1: request.issage = memstrmb(email, "sage", p - 2 - email, server->ischkseq) != NULL; break; case 3: request.title = *p ? p : NULL; } } if ((i = read_subjects(server, worker, &request)) || (i = read_setting(server, worker, request.board))) return i; if (server->ischkseq) /* hack: FreeBSD's mbstowcs() doesn't treat "\x81<" etc. as EILSEQ... */ for (p = request.datline; p; (p = email) && p++) { email = strchr(p, '<'); /* not "email", of course:-) */ if (isinvalidseq(p, (email ? email : request.datline + strlen(request.datline)) - p)) return errno; } pthread_rwlock_rdlock(&request.board->notes_rwlock); if (worker->now - request.board->notes_lastchk >= server->subject_html_interval) { pthread_rwlock_unlock(&request.board->notes_rwlock); pthread_rwlock_wrlock(&request.board->notes_rwlock); request.board->notes_lastchk = worker->now; for (i = 0; i < arraylen(notes); i++) if (!request.board->notes[i] || strcmp(request.board->notes[i], notes[i])) { free(request.board->notes[i]); if (!*notes[i]) request.board->notes[i] = NULL; else if (!(request.board->notes[i] = strdup(notes[i]))) { pthread_rwlock_unlock(&request.board->notes_rwlock); return errno; } } } pthread_rwlock_unlock(&request.board->notes_rwlock); if ((i = append_dat(server, worker, &request)) || (i = write_subjects(server, worker, request.board, NULL, request.title != NULL))) return i; if (request.title) { worker->writesize = snprintf(worker->writebuf = worker->readbuf, sizeof worker->readbuf, "%"PRIu64, request.key); worker->writebuf_rwlock = NULL; return -1; } return 0; } #ifdef USE_THREADS void *workerloop(void *server) { #define server ((server_t *)server) worker_t *worker; if (!(worker = malloc(sizeof *worker))) { syslog(LOG_CRIT, "malloc: %m"); pthread_mutex_lock(&server->cond_mutex); if (!--server->nthr_cur) pthread_cond_signal(&server->thr_cond); pthread_mutex_unlock(&server->cond_mutex); return (void *)(intptr_t)errno; } while (!server->isterminate) { int status; const char *str; struct sockaddr_storage recvaddr; socklen_t addrlen = sizeof recvaddr; pthread_mutex_lock(&server->cond_mutex); if (!server->nthr_idle++) pthread_cond_signal(&server->thr_cond); else if (server->nthr_idle > server->nthr_min) { server->nthr_idle--; pthread_mutex_unlock(&server->cond_mutex); break; } while (!server->isterminate && !server->isrecvok) pthread_cond_wait(&server->sock_cond, &server->cond_mutex); server->nthr_idle--; if (server->isterminate) { pthread_mutex_unlock(&server->cond_mutex); break; } worker->readsize = recvfrom(server->sock, worker->readbuf, sizeof worker->readbuf - 1, 0, (struct sockaddr *)&recvaddr, &addrlen); server->isrecvok = FALSE; pthread_cond_signal(&server->thr_cond); pthread_mutex_unlock(&server->cond_mutex); if (worker->readsize == -1) { syslog(LOG_ERR, "recvfrom: %m"); continue; } if ((status = process_request(server, worker)) == -1) { if (worker->writebuf_rwlock) pthread_rwlock_rdlock(worker->writebuf_rwlock); sendto(server->sock, worker->writebuf, worker->writesize, 0, (struct sockaddr *)&recvaddr, addrlen); if (worker->writebuf_rwlock) pthread_rwlock_unlock(worker->writebuf_rwlock); } else { str = status ? strerror(status) : ""; sendto(server->sock, str, strlen(str), 0, (struct sockaddr *)&recvaddr, addrlen); } } free(worker); pthread_mutex_lock(&server->cond_mutex); if (!--server->nthr_cur) pthread_cond_signal(&server->thr_cond); pthread_mutex_unlock(&server->cond_mutex); return NULL; #undef server } #endif /* USE_THREADS */ int serverloop(server_t *server) { #ifdef USE_THREADS int i; pthread_t thr; pthread_attr_t thr_attr; sigset_t oset; struct pollfd pollfd; #endif worker_t *worker; board_t *board; if (!(worker = malloc(sizeof *worker))) { syslogx(LOG_CRIT, "malloc: %m"); return errno; } else { char addr[INET6_ADDRSTRLEN], port[NI_MAXSERV]; #ifndef USE_THREADS int i; #endif if ((i = getnameinfo(server->addrinfo->ai_addr, server->addrinfo->ai_addrlen, addr, sizeof addr, port, sizeof port, NI_NUMERICHOST|NI_NUMERICSERV))) syslogx(LOG_WARNING, "getnameinfo: %s", i == EAI_SYSTEM ? strerror(errno) : gai_strerror(i)); else syslogx(LOG_INFO, "Started. [address = %s, port = %s, servername = \"%s\", docroot = \"%s\"]", addr, port, server->name, server->docroot); } #ifdef USE_THREADS if ((errno = pthread_attr_init(&thr_attr))) { syslogx(LOG_CRIT, "pthread_attr_init: %m"); free(worker); return errno; } if ((errno = pthread_attr_setdetachstate(&thr_attr, PTHREAD_CREATE_DETACHED))) { syslogx(LOG_CRIT, "pthread_attr_setdetachstate: %m"); pthread_attr_destroy(&thr_attr); free(worker); return errno; } server->isterminate = server->isrecvok = FALSE; pthread_sigmask(SIG_SETMASK, &filledsigset, &oset); for (i = 0; i < server->nthr_cur; i++) if ((errno = pthread_create(&thr, &thr_attr, workerloop, server))) { pthread_sigmask(SIG_SETMASK, &oset, NULL); syslogx(LOG_CRIT, "pthread_create: %m"); pthread_mutex_lock(&server->cond_mutex); server->isterminate = TRUE; pthread_cond_broadcast(&server->sock_cond); pthread_mutex_unlock(&server->cond_mutex); pthread_attr_destroy(&thr_attr); free(worker); return errno; } pthread_sigmask(SIG_SETMASK, &oset, NULL); pollfd.fd = server->sock; pollfd.events = POLLRDNORM; while (!isterminate) { pthread_mutex_lock(&server->cond_mutex); if (!server->nthr_idle && server->nthr_cur < server->nthr_max) { int nthr = server->nthr_min < NTHR_RANGE_RATIO ? server->nthr_min : NTHR_RANGE_RATIO; if (nthr > server->nthr_max - server->nthr_cur) nthr = server->nthr_max - server->nthr_cur; pthread_sigmask(SIG_SETMASK, &filledsigset, &oset); for (i = 0; i < nthr; i++) if ((errno = pthread_create(&thr, &thr_attr, workerloop, server))) syslogx(LOG_ERR, "pthread_create: %m"); else server->nthr_cur++; pthread_sigmask(SIG_SETMASK, &oset, NULL); } pthread_mutex_unlock(&server->cond_mutex); switch (poll(&pollfd, 1, -1)) { case -1: if (errno != EINTR) syslogx(LOG_ERR, "poll: %m"); case 0: break; default: pthread_mutex_lock(&server->cond_mutex); while (!server->nthr_idle || server->isrecvok) pthread_cond_wait(&server->thr_cond, &server->cond_mutex); if (poll(&pollfd, 1, 0) > 0) { server->isrecvok = TRUE; pthread_cond_signal(&server->sock_cond); } pthread_mutex_unlock(&server->cond_mutex); } } pthread_mutex_lock(&server->cond_mutex); server->isterminate = TRUE; pthread_cond_broadcast(&server->sock_cond); while (server->nthr_cur) pthread_cond_wait(&server->thr_cond, &server->cond_mutex); pthread_mutex_unlock(&server->cond_mutex); #else /* USE_THREADS */ while (!isterminate) { int status; const char *str; struct sockaddr_storage recvaddr; socklen_t addrlen = sizeof recvaddr; if ((worker->readsize = recvfrom(server->sock, worker->readbuf, sizeof worker->readbuf - 1, 0, (struct sockaddr *)&recvaddr, &addrlen)) == -1) { if (errno != EINTR) syslogx(LOG_ERR, "recvfrom: %m"); continue; } if ((status = process_request(server, worker)) == -1) sendto(server->sock, worker->writebuf, worker->writesize, 0, (struct sockaddr *)&recvaddr, addrlen); else { str = status ? strerror(status) : ""; sendto(server->sock, str, strlen(str), 0, (struct sockaddr *)&recvaddr, addrlen); } } #endif /* USE_THREADS */ time(&worker->now); for (board = server->boards; board; board = board->next) { read_setting(server, worker, board); write_subjects(server, worker, board, "shutdown...", TRUE); free_board(board); } server->boards = NULL; #ifdef USE_THREADS pthread_attr_destroy(&thr_attr); #endif free(worker); return 0; } int main(int argc, char *const *argv) { int status; server_t server; if ((status = parse_argv(&server, argc, argv)) || (status = initialize(&server)) || (!server.isforeground && (status = daemonize())) || (status = serverloop(&server))) return status; return 0; } #if 0 operate@opMDZWUYPwr8Y BBS_TITLE=運用情報@2ch掲示板 BBS_TITLE_PICTURE=http://img.2ch.net/img/operate_a.gif BBS_TITLE_COLOR=#000000 BBS_TITLE_LINK=http://info.2ch.net/wiki/ BBS_BG_COLOR=#FFFFFF BBS_BG_SOUND= BBS_BG_PICTURE=http://www2.2ch.net/ba.gif BBS_NONAME_NAME=動け動けウゴウゴ2ちゃんねる BBS_MAKETHREAD_COLOR=#CCFFCC BBS_MENU_COLOR=#CCFFCC BBS_THREAD_COLOR=#EFEFEF BBS_TEXT_COLOR=#000000 BBS_NAME_COLOR=green BBS_LINK_COLOR=#0000FF BBS_ALINK_COLOR=#FF0000 BBS_VLINK_COLOR=#660099 BBS_THREAD_NUMBER=10 BBS_CONTENTS_NUMBER=10 BBS_LINE_NUMBER=30 BBS_MAX_MENU_THREAD=40 BBS_SUBJECT_COLOR=#FF0000 BBS_PASSWORD_CHECK= BBS_UNICODE=change BBS_DELETE_NAME=あぼーん! BBS_NAMECOOKIE_CHECK=checked BBS_MAILCOOKIE_CHECK=checked BBS_SUBJECT_COUNT=64 BBS_NAME_COUNT=96 BBS_MAIL_COUNT=64 BBS_MESSAGE_COUNT=4096 BBS_NEWSUBJECT= BBS_THREAD_TATESUGI=256 BBS_AD2= SUBBBS_CGI_ON= NANASHI_CHECK= timecount=100 timeclose=10 BBS_PROXY_CHECK=checked BBS_OVERSEA_THREAD=checked BBS_OVERSEA_PROXY= BBS_RAWIP_CHECK= BBS_SLIP=checked BBS_DISP_IP= BBS_FORCE_ID=checked BBS_BE_ID= BBS_BE_TYPE2=checked BBS_NO_ID= BBS_JP_CHECK= BBS_VIP931=checked BBS_YMD_WEEKS= BBS_YMD_OFFSET= BBS_YMD_NAME= #endif