/********************************************************************* msgboard.cc 連絡ボードのようなものです。 ご自由にお使い下さい。 g++ -Wall msgboard.cc -o msgboard.cgi -lcrypt のようにしてコンパイルし,suid root して使います。 詳しくは http://www.matsusaka-u.ac.jp/~okumura/html/msgboard.html をご覧ください。 Haruhiko Okumura *********************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include /* htonl, ntohl */ #include #include #include /* glibc ベースの Linux はこれが必要 */ using namespace std; map post; /** 日付を string として返す */ string datestr() { char buf[20]; time_t t = time(0); // 1970年元旦からの秒数 struct tm* lt = localtime(&t); // strftime(buf, sizeof(buf), "%Y-%m-%d %X", lt); strftime(buf, sizeof(buf), "%Y-%m-%d", lt); return string(buf); } /** HTML にする(タグは ... だけ許し, 他は > < & にする) */ void make_html(string& s) { string t; int level = 0; for (size_t i = 0; i < s.length(); i++) { if (s[i] == '<') { if (level == 0 && i+2 < s.length() && toupper(s[i+1]) == 'A' && isspace(s[i+2])) { level = 1; t += s[i++]; t += s[i++]; t += s[i]; } else if (level == 3 && i+3 < s.length() && s[i+1] == '/' && toupper(s[i+2]) == 'A' && s[i+3] == '>') { level = 0; t += s[i++]; t += s[i++]; t += s[i++]; t += s[i]; } else { t += "<"; } } else if (s[i] == '"') { if (level == 1) level = 2; else if (level == 2) level = 1; t += s[i]; } else if (s[i] == '>') { if (level == 1) { t += s[i]; level = 3; } else { t += ">"; } } else if (s[i] == '&') { if (level == 1 || level == 2) t += '&'; else t += "&"; } else { t += s[i]; } } if (level == 1) t += "\">"; else if (level == 2) t += ">"; else if (level == 3) t += ""; s = t; } /** 正しいEUC文字列でない場合は修正する */ void check_euc(string& s) { for (size_t i = 0; i < s.length(); ++i) { int c = (unsigned char)s[i]; if ((c >= 0x20 && c <= 0x7e) || c == '\t') { // OK } else if (c >= 0x80 && i+1 < s.length()) { int d = (unsigned char)s[++i]; if ((c >= 0xa1 && c <= 0xfe && d >= 0xa1 && d <= 0xfe) || (c == 0x8e && d >= 0xa0 && d <= 0xdf)) { // OK } else if ((d >= 0x20 && d <= 0x7e) || d == '\t' || d == '\n') { s[i-1] = ' '; } else { s[i-1] = s[i] = ' '; } } else { s[i] = ' '; } } } void putheader(const char* msg) { cout << "Content-type: text/html\n\n" "\n" "\n" "\n" "" << msg << "\n" "\n" "

" << msg << "

"; } void msgexit(const char* msg) { putheader(msg); cout << "[戻る]\n"; exit(0); } char* tmpname() // ユニークな名前を返す { static char buf[64]; sprintf(buf, "%d.tmp", getpid()); return buf; } /** ユーザ名 user, パスワード pass を検証する。 正しいユーザであれば gid を返し, そうでなければ負の値を返す。 スーパーユーザでなければならない。 */ int auth_user(const char *user, const char *pass) { struct passwd *pw; /* passwd entry */ struct spwd *spw; /* shadow entry */ char buf[14]; /* encrypted passwd */ int gid; /* gid of the user */ /* look for the user in the password file */ /* need not be a superuser */ if ((pw = getpwnam(user)) == NULL) { /* Not listed in passwd */ /* sleep(3); */ return -1; } /* uid = pw->pw_uid; */ gid = pw->pw_gid; /* must be a superuser */ setspent(); /* begin access to shadow */ /* Look for the user in the shadow password file */ if ((spw = getspnam(user)) == NULL) { /* Not listed in shadow (or you are not a superuser) */ /* sleep(3); */ return -2; } strncpy(buf, spw->sp_pwdp, sizeof(buf)); endspent(); /* end access to shadow */ if (strcmp(crypt(pass, buf), buf) != 0) { /* wrong passwd */ /* sleep(3); */ return -3; } return gid; } /* ロックファイルクラス */ class LockFile { int fd; long count; public: explicit LockFile(const char* filename); long getcount() const { return count; } void setcount(long n); ~LockFile() { close(fd); } // close() するとロックが解除される }; LockFile::LockFile(const char* filename) { fd = open(filename, O_RDWR | O_CREAT, 0666); if (fd < 0) { count = -1; return; } struct flock lock; lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_SET; lock.l_len = 0; // ロック対象はファイル全体 // ↓ 他のプログラムがロックしている間待つ if (fcntl(fd, F_SETLKW, &lock) < 0) { count = -1; return; } long ncount; if (read(fd, &ncount, sizeof(ncount)) == sizeof(ncount)) { count = ntohl(ncount); } else { count = 0; } } void LockFile::setcount(long n) { count = n; n = htonl(n); lseek(fd, 0, SEEK_SET); write(fd, &n, sizeof(n)); } void append(string& user, string& s) { check_euc(s); make_html(s); LockFile lck("count.dat"); int count = lck.getcount(); if (count < 0) throw "カウントファイルが開きません"; count = ++count % 10000; if (count == 0) count = 1; lck.setcount(count); ofstream out("index.html", ios_base::app); if (!out) throw "アペンドできません"; out << "

(" << count << ") " << user << " [" << datestr() << "]

" << s << "
\n"; } void insmsg(const string& user, string& msg) { check_euc(msg); make_html(msg); LockFile lck("count.dat"); int count = lck.getcount(); if (count < 0) throw "カウントファイルが開きません"; count = ++count % 10000; if (count == 0) count = 1; lck.setcount(count); ifstream in("index.html"); if (!in) throw "index.html が開きません"; char* tmp = tmpname(); ofstream out(tmp); if (!out) throw "テンポラリファイルが作れません"; string s; while (getline(in, s)) { if (s.length() == 0) continue; out << s << '\n'; if (s == "
") { out << "

(" << count << ") " << user << " [" << datestr() << "]

" << msg << "
\n"; } } in.close(); out.close(); if (rename(tmp, "index.html") == 0) return; // rename が失敗した場合 unlink("index.html.bak"); // index.html.bak があれば消してから if (rename("index.html", "index.html.bak") == 0) { if (rename(tmp, "index.html") == 0) return; rename("index.html.bak", "index.html"); } msgexit("書き込みが失敗しました"); } void delmsg(const int n, const string& user) { bool deleted = false; LockFile lck("count.dat"); int count = lck.getcount(); if (count < 0) throw "カウントファイルが開きません"; ifstream in("index.html"); if (!in) throw "index.html が開きません"; char* tmp = tmpname(); ofstream out(tmp); if (!out) throw "テンポラリファイルが作れません"; string s; while (getline(in, s)) { const char* t = s.c_str(); while (*t != 0 && !isdigit(*t)) t++; if (atoi(t) == n) { while (isdigit(*t)) t++; while (*t != 0 && !isalnum(*t)) t++; string u; while (isalnum(*t)) { u += *t; t++; } if (u == user) { deleted = true; } else { out << s << '\n'; } } else if (s.length() != 0) { out << s << '\n'; } } in.close(); out.close(); if (deleted) { unlink("index.html"); // rename が失敗する場合 if (rename(tmp, "index.html") < 0) msgexit("rename が失敗しました"); } } void parse_post() { while (cin) { string s, t; int c; while ((c = cin.get()) != EOF && c != '=') { s += char(c); } if (c == EOF) break; while ((c = cin.get()) != EOF && c != '&') { if (c == '+') { t += ' '; } else if (c == '%') { int d = cin.get(); int e = cin.get(); d = (d >= 'A') ? (d & 0xdf) - 'A' + 10 : d - '0'; e = (e >= 'A') ? (e & 0xdf) - 'A' + 10 : e - '0'; if (d >= 0 && d < 16 && e >= 0 && e < 16) { t += char(16 * d + e); } else { t += char(c); t += char(d); t += char(e); } } else { t += char(c); } } post[s] = t; } } int main() { // 注意! root に setuid されている int delnum = 0; parse_post(); if (post["user"].length() < 3 || post["pass"].length() < 6 || (delnum = atoi(post["delnum"].c_str())) <= 0 && post["message"].length() < 2) { setuid(getuid()); msgexit("入力エラー"); } int gid = auth_user(post["user"].c_str(), post["pass"].c_str()); // できるだけ早く本来のユーザに戻っておく setuid(getuid()); // cout << "

user = " << post["user"]; // cout << "

pass = " << post["pass"]; // cout << "

message = " << post["message"]; // cout << "

gid = " << gid; // cout << "

delnum = " << delnum; if (gid == -1) msgexit("ユーザ名が登録されていません"); if (gid == -2) msgexit("シャドウファイルが読めません"); if (gid == -3) msgexit("パスワードが違います"); if (gid != 500) // 500 は staff の gid msgexit("書き込み権限がありません"); if (delnum > 0) { try { delmsg(delnum, post["user"]); } catch (const char* msg) { msgexit(msg); } } if (post["message"].length() >= 2) { try { insmsg(post["user"], post["message"]); } catch (const char* msg) { msgexit(msg); } } msgexit("処理完了"); }