精華區beta SetupBBS 關於我們 聯絡資訊
/*-------------------------------------------------------*/ /* util/bgopherd.c ( NTHU CS MapleBBS Ver 2.39 ) */ /*-------------------------------------------------------*/ /* target : simple gopher daemon for BBS */ /* create : 95/03/29 */ /* update : 96/10/07 */ /*-------------------------------------------------------*/ #include "bbs.h" #include <sys/wait.h> #include <netinet/in.h> #include <sys/socket.h> #include <netdb.h> #include <syslog.h> #define GOPHER_PORT 70 #define GOPHER_HOME (BBSHOME "/man") #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN 1024 #endif #define TCP_BUFSIZ 4096 #define TCP_QLEN 5 #define GOPHER_LOGFILE "/var/run/bgopherd.log" #define GOPHER_PIDFILE "/var/run/bgopherd.pid" static struct sockaddr_in xsin; static int rfds; /* read file descriptor set */ dashf(fname) char *fname; { struct stat st; return (stat(fname, &st) == 0 && S_ISREG(st.st_mode)); } /* ----------------------------------------------------- */ /* operation log and debug information */ /* ----------------------------------------------------- */ int flog; /* log file descriptor */ void logit(key, msg) char *key; char *msg; { time_t now; struct tm *p; char buf[256]; time(&now); p = localtime(&now); sprintf(buf, "%02d/%02d/%02d %02d:%02d:%02d %-7s%s\n", p->tm_year, p->tm_mon + 1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, key, msg); write(flog, buf, strlen(buf)); } void log_init() { flog = open(GOPHER_LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0644); logit("START", "gopher daemon"); } void log_close() { close(flog); } #if 0 /* ----------------------------------------------------- */ /* 取得 remote user name 以判定身份 */ /* ----------------------------------------------------- */ /* * rfc931() speaks a common subset of the RFC 931, AUTH, TAP, IDENT and RFC * 1413 protocols. It queries an RFC 931 etc. compatible daemon on a remote * host to look up the owner of a connection. The information should not be * used for authentication purposes. This routine intercepts alarm signals. * * Diagnostics are reported through syslog(3). * * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. */ #include <setjmp.h> #define STRN_CPY(d,s,l) { strncpy((d),(s),(l)); (d)[(l)-1] = 0; } #define STRING_LENGTH 60 #define RFC931_TIMEOUT 10 #define RFC931_PORT 113 /* Semi-well-known port */ #define ANY_PORT 0 /* Any old port will do */ /* ------------------------- */ /* timeout - handle timeouts */ /* ------------------------- */ static jmp_buf timebuf; static void timeout(sig) int sig; { longjmp(timebuf, sig); } void getremotename(from, rhost, rname) struct sockaddr_in *from; char *rhost; char *rname; { struct sockaddr_in our_sin; struct sockaddr_in rmt_sin; unsigned rmt_port, rmt_pt; unsigned our_port, our_pt; FILE *fp; char buffer[512], user[80], *cp; int s; struct hostent *hp; /* get remote host name */ hp = NULL; if (setjmp(timebuf) == 0) { signal(SIGALRM, timeout); alarm(3); hp = gethostbyaddr((char *) &from->sin_addr, sizeof(struct in_addr), from->sin_family); alarm(0); } strcpy(rhost, hp ? hp->h_name : (char *) inet_ntoa(from->sin_addr)); /* * Use one unbuffered stdio stream for writing to and for reading from the * RFC931 etc. server. This is done because of a bug in the SunOS 4.1.x * stdio library. The bug may live in other stdio implementations, too. * When we use a single, buffered, bidirectional stdio stream ("r+" or "w+" * mode) we read our own output. Such behaviour would make sense with * resources that support random-access operations, but not with sockets. */ *rname = '\0'; s = sizeof our_sin; if (getsockname(0, &our_sin, &s) < 0) return; if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { logit("ERROR", "socket in rfc931"); return; } if (!(fp = fdopen(s, "r+"))) { close(s); return; } /* * Set up a timer so we won't get stuck while waiting for the server. */ if (setjmp(timebuf) == 0) { signal(SIGALRM, timeout); alarm(RFC931_TIMEOUT); /* * Bind the local and remote ends of the query socket to the same IP * addresses as the connection under investigation. We go through all * this trouble because the local or remote system might have more than * one network address. The RFC931 etc. client sends only port numbers; * the server takes the IP addresses from the query socket. */ our_pt = ntohs(our_sin.sin_port); our_sin.sin_port = htons(ANY_PORT); rmt_sin = *from; rmt_pt = ntohs(rmt_sin.sin_port); rmt_sin.sin_port = htons(RFC931_PORT); setbuf(fp, (char *) 0); s = fileno(fp); if (bind(s, (struct sockaddr *) & our_sin, sizeof(our_sin)) >= 0 && connect(s, (struct sockaddr *) & rmt_sin, sizeof(rmt_sin)) >= 0) { /* * Send query to server. Neglect the risk that a 13-byte write would * have to be fragmented by the local system and cause trouble with * buggy System V stdio libraries. */ fprintf(fp, "%u,%u\r\n", rmt_pt, our_pt); fflush(fp); /* * Read response from server. Use fgets()/sscanf() so we can work * around System V stdio libraries that incorrectly assume EOF when a * read from a socket returns less than requested. */ if (fgets(buffer, sizeof(buffer), fp) && !ferror(fp) && !feof(fp) && sscanf(buffer, "%u , %u : USERID :%*[^:]:%79s", &rmt_port, &our_port, user) == 3 && rmt_pt == rmt_port && our_pt == our_port) { /* * Strip trailing carriage return. It is part of the protocol, not * part of the data. */ if (cp = strchr(user, '\r')) *cp = 0; strcpy(rname, user); } } alarm(0); } fclose(fp); } #endif /* ----------------------------------------------------- */ /* buffered TCP I/O routines */ /* ----------------------------------------------------- */ int tcp_pos; char tcp_pool[TCP_BUFSIZ + 512]; void tcp_init() { tcp_pos = 0; } int tcp_puts(sock, msg) int sock; char *msg; { int pos, len; char *head, *tail; len = strlen(msg); pos = tcp_pos; head = tcp_pool; tail = head + pos; memcpy(tail, msg, len); memcpy(tail + len, "\r\n", 2); pos += (len + 2); while (pos >= TCP_BUFSIZ) { len = write(sock, head, TCP_BUFSIZ); if (len <= 0) return 0; pos -= len; if (pos == 0) break; memcpy(head, head + len, pos); } tcp_pos = pos; return len; } int tcp_flush(sock, msg) int sock; char *msg; { int pos, len; if (msg != NULL) { len = tcp_puts(sock, msg); if (len == 0) return 0; } pos = tcp_pos; if (pos == 0) return 0; msg = tcp_pool; for (;;) { len = write(sock, msg, pos); if (len <= 0) return 0; pos -= len; if (pos == 0) { tcp_pos = 0; return len; } msg += len; } } /* ----------------------------------------------------- */ /* transform to real path & security check */ /* ----------------------------------------------------- */ int real_path(path) char *path; { int ch, level; char *source, *target; if (*path == '/') return 0; level = 1; source = target = path; for (;;) { ch = *source; if (ch == '/') { int next; next = source[1]; if (next == '/') { return 0; /* [//] */ } else if (next == '.') { next = source[2]; if (next == '/') return 0; /* [/./] */ if (next == '.' && source[3] == '/') { /* -------------------------- */ /* abc/xyz/../def ==> abc/def */ /* -------------------------- */ for (;;) { if (target <= path) return 0; target--; if (*target == '/') break; } source += 3; continue; } } level++; } *target = ch; if (ch == 0) return level; target++; source++; } } /* ----------------------------------------------------- */ /* send file out in text-mail format */ /* ----------------------------------------------------- */ void send_file(sock, fpath) int sock; char *fpath; { FILE *fp; struct stat st; if (!stat(fpath, &st) && S_ISREG(st.st_mode) && (fp = fopen(fpath, "r"))) { int ch; char *ip, *p, buf[512]; ip = buf; while (fgets(ip, 511, fp)) { for (p = ip; ch = *p; p++) { if (ch == '\n' || ch == '\r') { *p = '\0'; break; } } if (*ip == '.' && ip[1] == '\0') tcp_puts(sock, ".."); else tcp_puts(sock, ip); } fclose(fp); } } /* ----------------------------------------------------- */ /* transform .Names to gopher index format & send it out */ /* ----------------------------------------------------- */ a_timestamp(buf, time) char *buf; time_t *time; { struct tm *pt = localtime(time); sprintf(buf, "%02d/%02d/%02d", pt->tm_mon + 1, pt->tm_mday, pt->tm_year); } void send_index(int sock, char* fpath, char* plus) { int mode; FILE *fp; static char gtitle[80], gserver[80], gport[8]; char *ptr, buf[1024]; char outbuf[2048]; fileheader fhdr; time_t dtime; char date[20]; #ifdef DEBUG logit("index", fpath); #endif sprintf(outbuf, "%s.DIR", fpath); if (fp = fopen(outbuf, "r")) { while (fread(&fhdr, sizeof(fhdr), 1, fp) == 1) { strcpy(gtitle, fhdr.title); gethostname(gserver, MAXHOSTNAMELEN); strcpy(gport, "70"); sprintf(outbuf, "%s%s", fpath, fhdr.filename); mode = dashf(outbuf) ? 0 : 1; sprintf(outbuf, "%c/%s%s", mode + '0', fpath, fhdr.filename); if (!real_path(outbuf)) continue; ptr = buf; strcpy(ptr, outbuf); dtime = atoi(fhdr.filename + 2); a_timestamp(date, &dtime); sprintf(outbuf, "%s%c%-43.42s%-13s[%s]\t%s\t%s\t%s", plus, '0' + mode, gtitle, fhdr.owner, date, ptr, gserver, gport); tcp_puts(sock, outbuf); } fclose(fp); } } /* ----------------------------------------------------- */ /* parse client's command and serve it */ /* ----------------------------------------------------- */ void serve(sock) int sock; { register char *ptr; int n, ch; char buf[1024]; ptr = buf; n = read(sock, ptr, 1024); if (n < 0) return; ptr[n] = '\0'; while (ch = *ptr) { if (ch == '\r' || ch == '\n') { *ptr = '\0'; n = ptr - buf; break; } ptr++; } ptr = buf; ch = *ptr; if (ch == 0) ch = '1'; logit("cmd", ptr); tcp_init(); switch (ch) { case '0': ptr += 2; if (real_path(ptr)) send_file(sock, ptr); break; case '1': if (n <= 2) *ptr = '\0'; else { strcpy(ptr + n, "/"); ptr += 2; } send_index(sock, ptr, ""); break; case '\t': tcp_puts(sock, "+-1"); send_index(sock, "", "+INFO: "); break; default: return; } tcp_flush(sock, "."); } /* ----------------------------------------------------- */ /* daemon : initialize and other stuff */ /* ----------------------------------------------------- */ start_daemon() { int n, on; char buf[80]; struct sockaddr_in fsin; /* * More idiot speed-hacking --- the first time conversion makes the C * library open the files containing the locale definition and time zone. * If this hasn't happened in the parent process, it happens in the * children, once per connection --- and it does add up. */ time_t dummy = time(NULL); struct tm *dummy_time = localtime(&dummy); struct tm *other_dummy_time = gmtime(&dummy); strftime(buf, 80, "%d/%b/%Y:%H:%M:%S", dummy_time); n = getdtablesize(); if (fork()) exit(0); while (n) (void) close(--n); n = open(GOPHER_PIDFILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (n >= 0) { sprintf(buf, "%5d\n", getpid()); write(n, buf, 6); close(n); } (void) open("/dev/null", O_RDONLY); (void) dup2(0, 1); (void) dup2(0, 2); if ((n = open("/dev/tty", O_RDWR)) > 0) { ioctl(n, TIOCNOTTY, 0); close(n); } openlog("bgopherd", LOG_PID, LOG_AUTH); syslog(LOG_NOTICE, "start\n"); } int bind_port(port) int port; { int sock, on; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { syslog(LOG_NOTICE, "socket\n"); exit(1); } on = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0) syslog(LOG_ERR, "(SO_REUSEADDR): %m"); on = 0; #if 0 /* 0825 */ on = 4096; setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *) &on, sizeof(on)); setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *) &on, sizeof(on)); #endif xsin.sin_port = htons(port); if (bind(sock, (struct sockaddr *)&xsin, sizeof xsin) < 0) { syslog(LOG_INFO, "bgopherd bind_port can't bind to %d",port); exit(1); } if (listen(sock, TCP_QLEN) < 0) { syslog(LOG_INFO, "bgopherd bind_port can't listen to %d",port); exit(1); } FD_SET(sock, (fd_set *) & rfds); return sock; } #if 0 void reaper() { int state, pid; while ((pid = waitpid(-1, &state, WNOHANG | WUNTRACED)) > 0); } #endif void abort_server() { log_close(); exit(1); } void reapchild() { int state, pid; /* signal(SIGCHLD, reapchild); */ while ((pid = waitpid(-1, &state, WNOHANG | WUNTRACED)) > 0); } main(int argc, char** argv) { int msock, csock, ofds, nfds, sr; extern int errno; fd_set rset; struct timeval tv; pid_t pid, slot; start_daemon(); signal(SIGCHLD, reapchild); memset(&xsin, 0, sizeof(xsin)); xsin.sin_family = AF_INET; rfds = 0; log_init(); (void) signal(SIGHUP, abort_server); #if 0 (void) signal(SIGCHLD, reaper); #endif if (argc > 1) { msock = -1; for (nfds = 1; nfds < argc; nfds++) { csock = atoi(argv[nfds]); if (csock > 0) msock = bind_port(csock); else break; } if (msock < 0) { syslog(LOG_INFO, "bgopherd started with invalid arguments (no port)"); exit(1); } } else { static int ports[] = {70 /*, 4001, 4002, 4003, 4004, 4005 */ }; for (nfds = 0; nfds < sizeof(ports) / sizeof(int); nfds++) { msock = bind_port(ports[nfds]); } } setgid(BBSGID); setuid(BBSUID); chdir(GOPHER_HOME); /* main loop */ ofds = rfds; nfds = msock + 1; for (;;) { forever: rfds = ofds; tv.tv_sec = 60 * 30; tv.tv_usec = 0; msock = select(nfds, (fd_set *) & rfds, NULL, NULL, &tv); if (msock <= 0) continue; msock = 0; csock = 1; for (;;) { if (csock & rfds) break; if (++msock >= nfds) goto forever; csock <<= 1; } slot = sizeof(xsin); do { csock = accept(msock, (struct sockaddr *)&xsin, (int*)&slot); } while (csock < 0 && errno == EINTR); if (csock < 0) continue; pid = fork(); if (!pid) { while (--nfds >= 0) close(nfds); close(msock); serve(csock); /* shutdown(csock, 2); close(csock); */ exit(0); } else close(csock); } }