Mercurial > hgrepos > hgweb.cgi > after5
diff after5.rb @ 0:8f811f47ac60 draft
RCS-revision 1.1
date: 2003/12/29 16:05:57; author: yuuji; state: Exp;
Initial revision
=============================================================================
author | HIROSE Yuuji <yuuji@gentei.org> |
---|---|
date | Mon, 29 Dec 2003 16:05:57 +0859 |
parents | |
children | 8145b15d3d6f |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/after5.rb Mon Dec 29 16:05:57 2003 +0859 @@ -0,0 +1,2760 @@ +#!/usr/local/bin/ruby +# +# Associative Scheduling Table - after5 +# (C)2003 by HIROSE Yuuji [yuuji@gentei.org] +# $Id: after5.rb,v 1.1 2003/12/29 16:05:57 yuuji Exp $ +# Last modified Tue Dec 30 00:46:49 2003 on firestorm +# See http://www.gentei.org/~yuuji/software/after5/ +# このスクリプトはEUCで保存してください。 + +require 'kconv' + +$charset = 'EUC-JP' + +class HTMLout + def contenttype(type = "text/html", charset = $charset) + sprintf "Content-type: %s; charset=%s\n\n", type, charset + end + def initialize(title = "Document") + @title = title + @eltstack = [] + end + def resetstack() + @eltstack = [] + end + def head(title = @title, css = "style.css") + sprintf <<__EOS__, title, css +<html> +<head> +<title>%s</title> +<link rel="stylesheet" type="text/css" href="%s"> +</head> +__EOS__ + end + + def startelement(elt, attrs = {}, nl = true) + attr = "" + if attrs.is_a?(Hash) + for k in attrs.keys + attr += " %s=\"%s\"" % [k, attrs[k]] + end + end + @eltstack.push(elt) + sprintf "<%s%s>%s", elt, attr, nl ? "\n" : "" + end + def endelement(elt = nil, nl = true) + if elt + x = elt + @eltstack.pop + else + x = @eltstack.pop + end + sprintf "</%s>%s", x, nl ? "\n" : "" + end + def element(elt, attrs = nil, nl = nil) + attr = "" + lf = nl ? "\n" : "" + if attrs.is_a?(Hash) + for k in attrs.keys + attr += " %s=\"%s\"" % [k, attrs[k]] + end + end + body = yield + sprintf "<%s%s>%s%s%s</%s>%s", elt, attr, lf, body, lf, elt, lf + end + def elementln(elt, attr=nil) + body = yield + element(elt, attr, true){body} + end + def a(href, anchor = nil, attrs = {}) + attr = attrs + attr['href'] = href + element("a", attr){ + anchor or href + } + end + def p(msg, attrs=nil) + element("p", attrs){msg} + end + def text(name, value='', size=nil, maxlength=nil) + sprintf "<input type=\"text\" name=\"%s\" value=\"%s\"%s%s>", + name, value, + size ? " size=\"%s\""%size.to_s : '', + maxlength ? " maxlength=\"%s\""%maxlength.to_s : '' + end + def hidden(name, value='') + sprintf "<input type=\"hidden\" name=\"%s\" value=\"%s\">", name, value + end + def radio(name, value, text='', checked=nil) + sprintf "<input type=\"radio\" name=\"%s\" value=\"%s\"%s>%s", + name, value, checked ? " checked" : "", text + end + def checkbox(name, value, text='', checked=nil) + sprintf "<input type=\"checkbox\" name=\"%s\" value=\"%s\"%s>%s", + name, value, checked ? " checked" : "", text + end + def submit(name, value, text='') + sprintf "<input type=\"submit\" name=\"%s\" value=\"%s\">%s\n", + name, value, text + end + def reset(name, value, text='') + sprintf "<input type=\"reset\" name=\"%s\" value=\"%s\">\n", + name, value, text + end + def submit_reset(name) + submit(name, "GO")+reset(name, "Reset") + end + + def select_integer(name, b, e, selected=nil) + start = (b<e ? b : e) + last = (b>e ? b : e) + "<select name=\"#{name}\">\n" + \ + (start..last).collect{|i| + sprintf "<option%s>%d%s", + (selected.to_s==i.to_s) ? " selected" : "", + i, + i%6==0 ? "\n" : '' + }.join + \ + "\n</select>\n" + end +end + +class PasswdMgr + def initialize(name, mode=0640) + require 'dbm' + @pdb = DBM.open(name, mode) + end + def checkpasswd(user, passwd) + if @pdb[user] then + @pdb[user] == passwd.crypt(@pdb[user]) + end + end + def setpasswd(user, passwd) + salt = [rand(64),rand(64)].pack("C*").tr("\x00-\x3f","A-Za-z0-9./") + @pdb[user] = passwd.crypt(salt) + end + def userexist?(user) + @pdb[user] ? true : false + end + def getpasswd(user) + @pdb[user] + end + def delete(user) + @pdb.delete(user) + end + def close() + @pdb.close() + end + def newpasswd(length) + srand(Time.now.to_i) + left = "qazxswedcvfrtgb12345" + right = "yhnmjuik.lop;/67890-" + array = [left, right] + (1..length).collect{|i| + a = array[i%array.length] + a[rand(a.length), 1] + }.join('') + end + def users() + @pdb.keys + end + private :newpasswd + def setnewpasswd(user, length=8) + newp = newpasswd(length) + setpasswd(user, newp) + newp + end +end + +class ScheduleDir + def initialize(dir = "s") + @dir = dir + @schedulefile = "sched" + @usermapdir = File.join(@dir, "usermap") + @usermap = mkusermap() + @groupmapdir = File.join(@dir, "groupmap") + @groupmap = mkgroupmap() + @crondir = File.join(@dir, "crondir") + + end + def mkusermap() + map = {} + unless test(?d, @usermapdir) + mkdir_p(@usermapdir) + end + Dir.foreach(@usermapdir){|u| + next if /^\./ =~ u + newu = '' + u.split('').each{|c| # for security wrapping + newu << c[0].chr if /[-A-Z_.@]/i =~ c + } + u = newu + map[u] = {} + d = File.join(@usermapdir, u) + next unless test(?d, d) + Dir.foreach(d){|attr| + next if /^\./ =~ attr + attr.untaint if /^[A-Za-z]+$/ =~ attr + file = File.join(@usermapdir, u, attr) + next unless test(?s, file) && test(?r, file) + map[u][attr] = IO.readlines(file).join().strip + } + } + map + end + def putuserattr(user, attr, text) + # if text==nil, remove it + d = File.join(@usermapdir, user) + Dir.mkdir(d) unless test(?d, d) + file = File.join(d, attr) + begin + @usermap[user][attr] = text + if text==nil + File.unlink(file) + else + open(file, "w"){|w| w.puts @usermap[user][attr]} + end + rescue + return nil + end + return {attr => text} + end + def getuserattr(user, attr) + # Should we distinguish between attribute is nil and "" ? + if @usermap.has_key?(user) && @usermap[user][attr].is_a?(String) && + @usermap[user][attr] > '' + return @usermap[user][attr] + else + return nil + end + end + + def nickname(user) + if @usermap.has_key?(user) && @usermap[user]['name'].is_a?(String) && + @usermap[user]['name'] > '' + return @usermap[user]['name'] + else + return user.sub(/@.*/, '') + end + end + def setnickname(user, nickname) + putuserattr(user, 'name', nickname) + end + + # + # make group map + def collectmembers(gname) + @visitedgroup=[] unless @visitedgroup + return [] unless @visitedgroup.grep(gname).empty? + @visitedgroup.push(gname) + mdir = File.join(@groupmapdir, gname, 'members') + return [] unless test(?d, mdir) + members = [] + Dir.foreach(mdir){|item| + next if /^\./ =~ item + item.untaint + next unless test(?f, File.join(mdir, item)) + if /.+@.+/ =~ item + members << item + else + members += collectmembers(item) + end + } + @visitedgroup.pop + members + end + def mkgroupmap() + map = {} + return map unless test(?d, @groupmapdir) + @visitedgroup = [] + Dir.foreach(@groupmapdir){|g| + next if /^\./ =~ g + newg = '' + next unless /^[-a-z0-9_.]+$/i =~ g + #g.untaint ## untaintじゃだめだ。map{g} のkeyがtaintedになっちゃうよ + gg = '' # for security wrapping + g.split('').each{|c| gg << c[0].chr if c != '`'} + g = gg + map[gg] = {} + d = File.join(@groupmapdir, g) + next unless test(?d, d) + # get group name + gnf = File.join(d, 'name') + if test(?r, gnf) && test(?s, gnf) + n = IO.readlines(gnf)[0].to_s.strip + map[g]['name'] = if n > '' then n else g end + else + map[g]['name'] = g + end + # get administrators + # + gad = File.join(d, 'admin') + map[g]['admin'] = [] + if test(?d, gad) + Dir.foreach(gad){|a| + # administrator should be a person (not group) + next unless /@/ =~ a + map[g]['admin'] << a + } + end + # collect members + #map[g]['members'] = collectmembers(g) + memd = File.join(d, 'members') + map[g]['members'] = [] + if test(?d, memd) + Dir.foreach(memd){|a| + next if /^\./ =~ a + map[g]['members'] << a + } + end + } + map + end + def groupmap() + @groupmap + end + def groups() + @groupmap.keys + end + def addgroup(grp, users, remove=nil, role='members') + return nil unless @groupmap[grp] + for u in users + next unless account_exists(u) + mdir = File.join(@groupmapdir, grp, role) + file = File.join(mdir, u) + if remove + @groupmap[grp][role].delete(u) + File.unlink(file) if test(?e, file) + else + @groupmap[grp][role] << u + @groupmap[grp][role].uniq + Dir.mkdir(file) unless test(?d, mdir) + open(file, "w"){|x|} # Touch it + end + end + grp + end + def setgroupname(grp, name) + return nil unless @groupmap[grp] + mdir = File.join(@groupmapdir, grp) + nfile = File.join(mdir, 'name') + @groupmap[grp]['name'] = name + if grp == name + # remove the name file because it is default name + File.unlink(nfile) if test(?e, nfile) + else + Dir.mkdir(mdir) unless test(?d, mdir) + open(nfile, "w"){|n| n.puts name.to_s.strip} + end + name + end + def creategroup(grp, grpname="", admin=[]) + return nil unless /^[-A-Z0-9._:!$%,]+$/i =~ grp + grp.untaint + gdir = File.join(@groupmapdir, grp) + mkdir_p(gdir) # Should not care errors here + Dir.mkdir(File.join(gdir, "admin")) + Dir.mkdir(File.join(gdir, "members")) + @groupmap[grp] = {} + if grpname == '' + @groupmap[grp]['name'] = grp + else + setgroupname(grp, grpname) + end + @groupmap[grp]['members'] = [] + @groupmap[grp]['admin'] = [] + addgroup(grp, admin) + addgroup(grp, admin, nil, 'admin') + return @groupmap[grp] + end + def createuser(user, email = nil) + return nil unless /@/ =~ user + return nil if %r@[\/()\;|,$\%^!\#&\'\"]@ =~ user + email = email || user + @usermap[user] = {} + Dir.mkdir(File.join(@usermapdir, user)) + putuserattr(user, 'email', email) + end + def deleteuser(user) + return nil unless @usermap[user] + begin + @usermap[user] # return value + ensure + @usermap.delete(user) + rm_rf(File.join(@usermapdir, user)) + rm_rf(File.join(@groupmapdir, "*/members/#{user}")) + rm_rf(File.join(@crondir, "[1-9]*-*-*/#{user}")) + rm_rf(File.join(@dir, "[1-9]*/[0-9][0-9]/[0-9][0-9]/[0-9]???/#{user}")) + end + end + def destroygroup(grp) + return nil unless @groupmap[grp] + begin + @groupmap[grp] # return value + ensure + @groupmap.delete(grp) + rm_rf(File.join(@groupmapdir, grp)) + rm_rf(File.join(@groupmapdir, "*/members/#{grp}")) + rm_rf(File.join(@crondir, "[1-9]*-*-*/#{grp}")) + rm_rf(File.join(@dir, "[1-9]*/[0-9][0-9]/[0-9][0-9]/[0-9]???/#{grp}")) + end + end + def rm_rf(path) + if (list = Dir.glob(path))[0] + for p in list + system "/bin/rm -rf \"#{p}\"" + end + cleanup_files(list[0]) + end + end + def account_exists(instance) + if /@/ =~ instance + true + else + ! @groupmap.select{|k, v| k==instance}.empty? + end + end + def ismember(user, grouporuser) + return true if user==grouporuser + if @groupmap[grouporuser] + @groupmap[grouporuser]['members'].grep(user)[0] + end + end + def isgroup(grp) + @groupmap[grp] + end + def isadmin(user, group) + @groupmap[group] and @groupmap[group]['admin'].grep(user)[0] + end + def members(grp) + @groupmap[grp] and ####################@groupmap[grp]['members'] + collectmembers(grp) + end + def admins(grp) + @groupmap[grp] and @groupmap[grp]['admin'] + end + def groupname(grp) + @groupmap[grp] && @groupmap[grp]['name'] + end + def day_all(d, user=nil) + y, m, d = d.scan(%r,(\d\d\d\d+)/(\d+)/(\d+),)[0] + #daydir = File.join(@dir, "%04d"%y.to_i, "%02d"%m.to_i, "%02d"%d.to_i) + daydir = File.join("s", "%04d"%y.to_i, "%02d"%m.to_i, "%02d"%d.to_i) + sched = {} + return sched unless test(?d, daydir) + Dir.foreach(daydir) {|time| + next if /^\./ =~ time + next unless /^\d\d\d\d$/ =~ time + time.untaint + t = File.join(daydir, time) + next unless test(?d, t) + sched[time] = {} + Dir.foreach(t){|who| + next if /^\./ =~ who + + visible = false + #next unless /@/ =~ who # user must be as user@do.ma.in + next unless account_exists(who) + who.untaint + dir = File.join(t, who) + next unless test(?d, dir) && test(?x, dir) + pub = File.join(dir, 'pub') + if test(?f, pub) && test(?r, pub) && test(?s, pub) + if IO.readlines(pub)[0].to_i > 0 + visible = true + end + end + + + if ismember(user, who) || visible + sched[time][who] = {} + file = File.join(dir, @schedulefile) + if test(?s, file) && test(?r, file) && test(?s, file) + sched[time][who]['sched'] = IO.readlines(file).join().chomp! + end + sched[time][who]['pub'] = visible + end + } #|who| + sched.delete(time) if sched[time].empty? + } + sched + end + + def scheduledir(user, y, m, d, time) + sprintf("%s/%04d/%02d/%02d/%04d/%s", + @dir, y.to_i, m.to_i, d.to_i, time.to_i, user) + end + def schedulefile(user, y, m, d, time) + File.join(scheduledir(user, y, m, d, time), @schedulefile) + end + def mkdir_p(path, mode=0777) + # Do not mkdir `path' for + # absolute paths + # those paths which contains `../' + # for the sake of security reason + return false if %r,\.\./|^/, =~ path + p = 0 + i=0 + while p=path.index("/", p) + dir = path[0..p].chop + p += 1 + break if i > 10 # overprotecting + next if test(?d, dir) + Dir.mkdir(dir, mode) + i += 1 + end + Dir.mkdir(path, mode) unless test(?d, path) + end + + # + # register schedule for user + # + def register(user, year, month, day, time, text, replace=nil) + # return code: 0 = succesfull new registration + # 1 = succesfull appending registration + dir = scheduledir(user, year, month, day, time) + file = schedulefile(user, year, month, day, time) + ret = 0 + um = File.umask(027) + begin + if !replace && test(?s, file) + ret = 1 + else + mkdir_p(dir, 0777) + end + ensure + File.umask(um) + end + open(file, replace ? "w" : "a"){|out|out.print text} + return ret + end + def getschedule(user, year, month, day, time) + file = schedulefile(user, year, month, day, time) + if test(?r, file) && test(?s, file) + return IO.readlines(file).join + end + return nil + end + def remove(user, year, month, day, time) + file = schedulefile(user, year, month, day, time) + dir = File.dirname(file) + if test(?r, file) && test(?s, file) + File.unlink(file) + end + for f in Dir.glob(File.join(dir, "*")) + f.untaint + File.unlink(f) + end + Dir.rmdir(dir) if test(?d, dir) + begin + Dir.rmdir(File.dirname(dir)) + rescue + end + end + # + # register file + # + def putfile(user, year, month, day, time, file, contents) + scback = @schedulefile + begin + @schedulefile = File.basename(file) + register(user, year, month, day, time, contents, true) + ensure + @schedulefile = scback + end + end + def getfile(user, year, month, day, time, file) + scback = @schedulefile + begin + @schedulefile = File.basename(file) + getschedule(user, year, month, day, time) + ensure + @schedulefile = scback + end + end + def removefile(user, year, month, day, time, file) + dir = scheduledir(user, year, month, day, time) + file = File.join(dir, file) + if test(?e, file) + File.unlink(file) + end + end + # + # registration to crondir + # + def cronlink_file(nt_time, user, y, m, d, time) + subdir = nt_time.strftime("%Y-%m-%d-%H%M/#{user}") + cdir = File.join(@crondir, subdir) + File.join(cdir, sprintf("%04d-%02d-%02d-%04d", y, m, d, time)) + end + def register_crondir(nt_time, user, y, m, d, time) + linkfile = cronlink_file(nt_time, user, y, m, d, time) + mkdir_p(File.dirname(linkfile)) + scfile = schedulefile(user, y, m, d, time) + if test(?s, scfile) + sclink = File.join("../../..", scfile.sub!(Regexp.quote(@dir+'/'), '')) + File.symlink(sclink, linkfile) unless test(?e, linkfile) + return linkfile + end + return false + end + def remove_crondir(nt_time, user, y, m, d, time) + linkfile = cronlink_file(nt_time, user, y, m, d, time) + scfile = schedulefile(user, y, m, d, time) + if test(?e, linkfile) + File.unlink(linkfile) + begin + dir = linkfile + 2.times {|x| + dir = File.dirname(dir) + if Dir.open(dir).collect.length <= 2 # is empty dir + Dir.rmdir(dir) + else + break + end + } + rescue + end + return linkfile + end + return false + end + + # + # return the Hash of crondir {user => files} + def notify_list(asof) + slack = 5*60 + gomifiles = [] + ntl = {} + return ntl unless test(?d, @crondir) + Dir.foreach(@crondir){|datedir| + dd = File.join(@crondir, datedir) + next unless test(?d, dd) + next unless /(\d\d\d\d+)-(\d+)-(\d+)-(\d\d\d\d)/ =~ dd + y, m, d, hm = $1.to_i, $2.to_i, $3.to_i, $4.to_i + hh = hm/100 % 60 + mm = (hm%100) % 60 + t = Time.mktime(y, m, d, hh, mm) + next if t-slack > asof + # + # collect them + Dir.foreach(dd){|user| + next unless /@/ =~ user || isgroup(user) + ud = File.join(dd, user) + next unless test(?d, ud) + ntl[user] = {} + Dir.foreach(ud){|date| + next if /^\./ =~ date + unless /(\d\d\d\d+)-(\d+)-(\d+)-(\d\d\d\d)/ =~ date + gomifiles << File.join(ud, date) + next + end + f = File.join(ud, date) + if test(?s, f) + ntl[user][date] = {} + ntl[user][date]['file'] = f + ntl[user][date]['text'] = IO.readlines(f) + else + File.unlink(f) # symlink points to nonexistent file + end + } + if ntl[user].empty? + # if ud does not contain valid cron symlinks, + # ud had been left badly. Remove it. + ntl.delete(user) + cleanup_files(gomifiles) + end + } + } + ntl + end + # + # cleanup file and directories + def cleanup_crondir(time) + Dir.foreach(@crnondir){|datedir| + dd = File.join(@crondir, datedir) + next unless test(?d, dd) + next unless /(\d\d\d\d+)-(\d+)-(\d+)-(\d\d\d\d)/ =~ dd + y, m, d, hm = $1.to_i, $2.to_i, $3.to_i, $4.to_i + hh = hm/100 % 60 + mm = (hm%100) % 60 + t = Time.mktime(y, m, d, hh, mm) + if t < time + system "rm -rf #{dd}" + end + } + end + # + # remove files in FILES, and remove parent directory if possible + def cleanup_files(files) + sentinel = File.stat(@dir).ino + for f in files + File.unlink(f) if test(?e, f) + d = f + loop { + d = File.dirname(d) + break if d.length < 2 + break if File.stat(d).ino == sentinel + begin + puts "rmdir #{d}" if $DEBUG + Dir.rmdir(d) + rescue + break + end + } + end + end +end + +class StringIO<IO + def initialize() + @str="" + end + def foo=(str) + @str = str + end + def print(str) + @str << str + end + def puts(str) + @str << str+"\n" + end + def printf(*args) + @str << sprintf(*args) + end + def write(bytes) + print(bytes) + end + def gets() + return nil if @str == '' + p = @str.index(?\n) + if p + r = @str[0..p] + @str=@str[p+1..-1] + else + r = @str + end + return r + end + def readline() + this.gets() + end + def readlines() + r = @str + @str='' + r + end + + def p(*obj) + STDOUT.p(*obj) + end +end + +class CMDTimeout < Exception + def initialize() + @pw = IO.pipe + @pr = IO.pipe + @pe = IO.pipe + @timeout = false + end + def start(cmd, timeout, mixstderr=false) + if @pid=fork + @pw[0].close + @pr[1].close + @pe[1].close + # puts "parent!" + if @tk=fork + # main + else + @pw[1].close + @pr[0].close + @pe[0].close + trap(:INT){exit 0} + sleep timeout + begin + @timeout = true + STDERR.puts "TIMEOUT" + Process.kill :INT, @pid + rescue + #puts "Already done" + end + exit 0 + end + else + # Running this block with pid=@pid + trap(:INT){@timeout = true; exit 0} + @pw[1].close + STDIN.reopen(@pw[0]) + @pw[0].close + + @pr[0].close + STDOUT.reopen(@pr[1]) + if mixstderr + STDERR.reopen(@pr[1]) + else + STDERR.reopen(@pe[1]) + end + @pr[1].close + @pe[0].close + @pe[1].close + + exec *cmd + exit 0 + end + return [@pw[1], @pr[0], @pe[0]] + end + def wait() + Process.waitpid(@pid, nil) + end + def close() + @pr.each{|p| p.close unless p.closed?} + @pw.each{|p| p.close unless p.closed?} + @pe.each{|p| p.close unless p.closed?} + begin + Process.kill :INT, @tk + rescue + end + end + def timeout() + @timeout + end +end + +class Holiday + def initialize(dir = ".") + @@dir = dir + defined?(@@holiday) || setupHoliday + end + def setupHoliday(file = "holiday") + @@holiday = {} + return unless test(?f, file) && test(?s, file) + IO.foreach(file){|line| + line.strip + next if /^#/ =~ line + date, what = line.scan(/(\S+)\s+(.*)/)[0] + if %r,(\d+)/(\d+)/(\d+), =~ date + cdate = sprintf("%d/%d/%d", $1.to_i, $2.to_i, $3.to_i) + @@holiday[cdate] || @@holiday[cdate] = [] + @@holiday[cdate] << what + elsif %r,(\d+)/(\d+), =~ date + cdate = sprintf("%d/%d", $1.to_i, $2.to_i) + @@holiday[cdate] || @@holiday[cdate] = [] + @@holiday[cdate] << what + elsif %r,(\d+)/(\w+), =~ date + cdate = sprintf("%d/%s", $1.to_i, $2.downcase) + @@holiday[cdate] || @@holiday[cdate] = [] + @@holiday[cdate] << what + end + } + end + def isHoliday(y, m, d, wday=nil) + y, m, d = y.to_i, m.to_i, d.to_i + wname = %w[sun mon tue wed thu fri sat] + holiday = @@holiday[sprintf("%d/%d/%d", y, m, d)] || + @@holiday[sprintf("%d/%d", m, d)] + unless holiday + wday = wname[wday || Time.mktime(y, m, d).wday] + nthweek = (d-1)/7+1 + holiday = @@holiday[sprintf("%d/w%d%s", m, nthweek, wday)] + end + holiday + end + def holidays() + @@holiday + end +end + +class After5 + def initialize() + @me = File.expand_path($0) + @mydir, @myname = File.dirname(@me), File.basename(@me) + @mydir.untaint + Dir.chdir @mydir + # @mybase = @myname.sub(/\.\w+$/, '') + @mybase = "after5" ########################################### secure? + @myname='a5.cgi' # if test(?f, File.join(@mydir, "a5.cgi")) + @conf = nil + @schedulearea = {'rows'=>'4', 'cols'=>'60', 'name'=>'schedule'} + @oldagent = (%r,Mozilla/4, =~ ENV['HTTP_USER_AGENT']) + @opt = { + 'conf' => @mybase+".cf", + 'css' => @mybase+".css", + 'logfile' => @mybase+".log", + "sendmail" => "/usr/sbin/sendmail", + 'hostcmd' => '/usr/bin/host', + 'nslookup' => '/usr/sbin/nsookup', + 'bg' => 'ivory', + 'at_bsd' => '%H:%M %b %d %Y', + 'at_solaris' => '%H:%M %b %d,%Y', + 'schedir' => 's', + 'tdskip' => '<br>', + 'forgot' => 'wasureta', + 'size' => @oldagent ? '15' : '40', + 'morning' => '6', + 'night' => '22', + 'alldaydir' => '3000', + 'pswdlen' => 4, + 'pswddb' => 'a5pswd', + } + @ntlist = [ + ['nt10m', "10"+msg('minutes', 'before')], + ['nt30m', "30"+msg('minutes', 'before')], + ['nt60m', "60"+msg('minutes', 'before')], + ['nttoday', msg('theday')], + ['nt1d', "1"+msg('days', 'before')], + ['nt2d', "2"+msg('days', 'before')], + ['nt3d', "3"+msg('days', 'before')], + ['nt7d', "7"+msg('days', 'before')], + ['nt30d', "30"+msg('days', 'before')], + ] + ##@job = "today" + @job = "login" + @sc = ScheduleDir.new + @O = StringIO.new + @H = HTMLout.new() + @umback = File.umask + @author = 'yuuji@gentei.org' + @after5url = 'http://www.gentei.org/~yuuji/software/after5/' + File.umask(007) + end + def doit() + @params = getarg() + @cookie = getcookie() + p @cookie if $DEBUG + p @params if $DEBUG + + @O.print @H.contenttype() + @O.print @H.head("After 5", @opt['css']) + @O.print @H.startelement("body", true) + + ######### @O.puts @H.p(@cookie.inspect) #cookie check! + + ## x = {"align"=>'center'} + ## @H.element("p", x, "hoge", nil) + ## @H.element("p", nil, "buha", nil) + + if !@params['passwd'] && @cookie['passwd'] + @params['passwd'] = @cookie['passwd'] + end + if !@params['user'] && @cookie['user'] + @params['user'] = @cookie['user'] + end + @params['user'] = safecopy(@params['user']) + eval @job + # @job.call + @O.print @H.endelement(nil, true) # body + @O.print "</html>\n" # html + setcookie() + + print @O.readlines + end + def msg(*keyword) + unless defined?(@msg) + @msg = { + 'title' => ['みんなの予定表 <img src="after5.png" alt="「アフター5」" width="107" height="53">', 'Schedule table for us all <img src="after5.png" alt="After 5" width="107" height="53">'], + 'login' => ['ログイン', 'Login'], + 'loginfirst' => ['最初にログインすべし', 'Login first'], + 'autherror' => ['認証エラーがあったと管理者に伝えてくれっす', + 'Unexpected authentication error. Please tell this to the administrator'], + 'yourmail' => ['あなたのメイルアドレス', 'Your email address'], + 'passwd' => ['パスワード<br>(初回時は空欄)', + 'Passowrd<br>Left blank, first time'], + 'error' => ['エラー:', 'Error: '], + 'mailerror' => ['メイルアドレスが違います', 'Invalid email address'], + 'pswderror' => ['パスワードが違います', 'Password incorrect'], + 'fmtdaysschedule'=> ['%sの予定', 'Schedule on %s'], + 'noplan' => ['登録されている予定はありません', 'No plans'], + 'allday' => ['全日', 'whole day'], + 'addsched' => ['新規予定項目の登録', 'Register new schedule'], + 'defthisday' => ['デフォルトの日付はこの日になってま', ''], + '24hour' => ['24時間制4桁でね<br>(0000〜2359)<br>%sは時刻指定なし', 'in 24-hour<br>(0000-2359)<br>%s for whole day'], + 'reqnotify' => ['通知メイルいるけ?', 'Previous notification'], + 'rightnow' => ['登録時にすぐ', 'Right now on registration'], + 'immediatenote' => ['に以下の予定を登録しました', + ", Your schedule has been registered as follows;"], + 'registerer_is' => ['登録名義: ', 'Register as '], + 'registerer' => ['登録者: ', 'registerer: '], + 'about' => ['約', 'about'], + 'minutes' => ['分', 'minutes'], + 'hours' => ['時間', 'hour(s)'], + 'days' => ['日', 'day(s)'], + 'before' => ['前', 'before'], + 'theday' => ['当日朝', "the day's morning"], + 'night' => ['(夜)', '(night)'], + 'publicok' => ['メンバーに<br>見せてもええね?', + 'visible from other members?'], + 'public' => ['公', 'pub'], + 'nonpublic' => ['非', 'sec'], + 'yes' => ['はいな', 'yes'], + 'no' => ['やだ', 'nope'], + 'invaliddate' => ['日付指定が変みたい', 'Invalid time string'], + 'past' => ['それはもう過去の話ね', 'It had Pasted'], + 'putsomething' => ['何か書こうや', 'Write some message please'], + 'appended' => ['既存の予定に追加しました', 'Appended'], + 'append' => ['追加', 'append'], + 'join' => ['参加', 'join'], + 'regist' => ['登録', 'register'], + 'remove' => ['削除', 'remove'], + 'deletion' => ['完全消去', 'deletion'], + 'deletionwarn' => ['OK押したら即消去。確認とらないぞ', + 'Hitting OK immediately delets this group, be carefully!'], + 'deluser' => ['%s ユーザ消してええかの?', "Delete the user `%s'"], + 'delgroup' => ['%s グループ消してええかの?', "Delete the group `%s'"], + 'really?' => ['ほんまにええけ?', 'Really?'], + 'chicken' => ['ふっ、腰抜けめ', 'Hey, chicken boy'], + 'modify' => ['修正', 'modify'], + 'done' => ['完了', 'done'], + 'success' => ['成功', 'success'], + 'failure' => ['失敗', 'failure'], + 'tomonthlist' => ['%s の一覧', 'all %s table'], + 'notifysubj' => @mybase+"'s reminder for your plan", + 'introduce' => ['はいこんにちは、'+@mybase+'ですよ〜。', + "Hi, this is #{@mybase}'s notification."], + 'notifymail' => ['こんな予定がありまっせ。', + "You have some eschedule below;"], + 'notification' => ['の通知', 'notification'], + 'newaccount' => ["新しいアカウントを作りました。\n"+ + "パスワードは %s さん宛に送信しておきました。\n", + "You got new account for #{@mybase}\n" + + "Password was sent to %s.\nThank you.\n"], + 'accessfrom' => ["%s からのアクセスによる送信\n", + "This mail was sent by the access from %s\n"], + 'newpassword' => ["%s さんのパスワードは %s です。\n", + "The password of %s is %s\n"], + 'mischief' => ["身に覚えのない場合はいたずらです。どうしましょ。", + 'If you have no idea for getting this message, '+ + 'it is mischief by someone else'], + 'user' => ['ユーザ', 'user'], + 'group' => ['グループ', 'group'], + 'personal' => ['個人で', 'personal'], + 'registas' => ['グループ予定として登録', 'Register as group'], + 'joinquit' => ['入退', 'joining/quiting'], + 'of' => ['の', "'s"], + 'id' => ['ID(ローマ字1単語空白なしで)', 'ID(without spaces)'], + 'name' => ['名前', 'name'], + 'anystring' => ['(日本語OK)', '(any length, any characters)'], + 'setto' => ['を設定 → ', 'set to '], + 'management' => ['管理', 'management'], + 'administrator' => ['管理者', 'Administrator'], + 'newgroup' => ['新規グループ作成', 'Create new group'], + 'adminop' => ['管理<br>操作', "Administrative<br>operation"], + 'member' => ['メンバー', 'Member'], + 'addedtogroup' => ['をグループに追加 →', 'added to the group:'], + 'removedfromgp' => ['をグループから削除:', 'removed from the group:'], + 'soleadmin' => ['%s は %s の唯一の管理者なのでやめられないのだ', + "%s is sole administrator of %s. Cannot retire."], + 'recursewarn' => ['個人では加入してないが、別の加入グループがこのグループに入っているので実質参加していることになっている。', + 'Though this member does not join to this group, it is assumed to be joining this group because other group where one joins is joined to this group.'], + 'regaddress' => ['登録アカウント名', 'Account id'], + 'existent' => ['既にあるんすよ → ', 'Already exists: '], + 'mailaddress' => ['通知送付先アドレス', 'Notification email address'], + 'weburl' => ['ゲストブックとかURL<br><small>(予定への反応を書いて欲しい場所)</small>', 'Your guest book URL'], + 'usermodwarn' => ['いちいち yes/no とか確認取らないから押したら最後、気いつけて。', + 'This is the final decision. Make sure and sure.'], + 'joinmyself' => ['自分自身が既存のグループに対して入る(IN)か出る(OUT)かを決めるのがここ。自分管理のグループに誰かを足すなら「管理操作」、新たにグループを作るなら', + 'In this page, you can decide put yourself IN or OUT of the existing groups. If you want to manage the member of your own group, go to'], + 'groupwarn' => ['自分が参加してないグループAに、自分が参加しているグループBが含まれている場合、グループAにも加入していると見なされるので気をつけよう。管理者はグループのニックネームを変えられるよ。', + 'Though you are not member of group A, you are treated as a member of A, if you join to the group B, which is a member of A. Think the nesting of groups carefully, please. Group administrator can change the group nickname.'], + 'wholemembers' => ['グループ内グループを考慮した上で、現在グループ %s への通知は以下のメンバーに送られる。', + "Consiering the groups registered in another group, notification to the group `%s' is send to members as follows."], + 'noadmingroup' => ['管理できるグループはないっす', + "'There's no groups under your administration."], + 'multiplemail' => ['複数の宛先に通知したいなら..', + 'Wanna send notify to multiple address...'], + 'nickname' => ['ニックネーム', 'nickname'], + 'shortnameplz' => ['表が崩れるほど長すぎるニックネームは嫌われるよ。短めにね。', + 'Because nickname is displayed many times in table, shorter name is prefered.'], + 'nicknamenote' => ['ニックネームを消去するとデフォルト名になりんす.', + 'Default name is displayed if you remove nickname.'], + 'nothingtodo' => ['って何もやることあらへんかったで', + 'Nothing to do for this transaction.'] + } + end + lang=0 + keyword.collect{|k| + if @msg[k].is_a?(Array) + @msg[k][lang] + elsif @msg[k].is_a?(String) + @msg[k] + else + '' + end + }.join(['', ' '][lang]) + end + + def setcookie() + cookievals = %w[user passwd ^nt] + p = {} + @params.keys.grep(/^(user$|passwd$|nt)/){|v| + p[v] = @params[v].to_s.strip + } + c = gencookie(p, 3600*6*1) + printf "Set-Cookie: %s\n", c if c + end + + def encode(string) # borrowed from cgi.rb + string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do + '%' + $1.unpack('H2' * $1.size).join('%').upcase + end.tr(' ', '+') + end + def decode!(string) + string.gsub!(/\+/, ' ') + string.gsub!(/%(..)/){[$1.hex].pack("c")} + end + + def gencookie(a, expire) + x = a.collect{|k, v| + sprintf("%s=%s", k, encode(v)) if v + } + x.delete(nil) + return nil if x.empty? + str = x.join('&') + ex = (Time.new+expire).to_s + sprintf "value=%s; expires=%s", encode(str), ex + end + + def login() + @O.print @H.elementln("h1", nil){msg('title')} + @O.print @H.elementln("h2", nil){msg('login')} + format = {'method'=>'POST', 'action'=>@myname+"?-today"} + @O.print @H.elementln("form", format){ + @H.elementln("table", nil){ + @H.elementln("tr", nil){ + @H.element("td", nil){msg('yourmail')} + \ + @H.element("td", nil){ + sprintf '<input type="text" size="%s" name="user">', @opt['size'] + } + } + \ + @H.elementln("tr", nil){ + @H.element("td", nil){msg('passwd')} + \ + @H.element("td", nil){ + sprintf '<input type="password" size="%s" name="passwd">', @opt['size'] + } + } + } + '<input type="submit" value="LOGIN">' + } + @O.print footer2() + end + def open_pm() + begin + PasswdMgr.new(@opt['pswddb']) + rescue + STDERR.printf "Cannot open pswd file [%s]\n", @opt['pswddb'] + STDERR.printf "euid=#{Process.euid}, uid=#{Process.uid}\n", @opt['pswddb'] + nil + end + end + def outputError(*msg) + @O.print @H.p(msg('error')+sprintf(*msg)) + end + def mailaddress(user) + email = @sc.getuserattr(user, "email") + email || user + end + def webpage(user) + @sc.getuserattr(user, "webpage") + end + def checkauth() + auth = catch (:auth) { + unless @params['user'] + outputError(@H.a(@myname, msg('loginfirst'))) + throw :auth, nil + end + unless pm=open_pm() + outputError(msg('autherror')) + throw :auth, nil + end + user, passwd = @params['user'], @params['passwd'] + email = mailaddress(user) + if !checkmail(user) + outputError(msg('mailerror')) + throw :auth, nil + end + if pm.userexist?(user) + if pm.checkpasswd(user, passwd) + throw :auth, true + elsif passwd == @opt['forgot'] + newp = pm.setnewpasswd(user, @opt['pswdlen']) + sendMail(email, "#{@mybase} password", + "(#{ENV['REMOTE_ADDR']} からのアクセスによる送信)\n" + + "#{@mybase} 用の #{user} さんのパスワードは\n" + + (newp || "未定義") + "\nです。\n") + @O.print @H.p("#{email} 宛に送信しておきました") + throw :auth, nil + else + outputError(msg('pswderror')) + throw :auth, nil + end + else + newp = pm.setnewpasswd(user, @opt['pswdlen']) + @sc.createuser(user, user) + sendMail(email, "#{@mybase} new account", + sprintf(msg('accessfrom'), ENV['REMOTE_ADDR']) + + sprintf(msg('newpassword'), user, newp) + + sprintf(msg('mischief'))) + @O.print @H.p(sprintf(msg('newaccount'), user)) + @O.print @H.p(@H.a(@myname, msg('login'))) + throw :auth, nil + end + } + if auth + return true + else + return false + end + end + def safecopy(string) + return nil unless string + if $SAFE > 0 + cpy='' + string.split('').each{|c| + cpy << c[0].chr if c[0] != ?` # ` + } + cpy + else + string + end + end + def checkmail(mail) + account, domain = mail.scan(/(.*)@(.*)/)[0] + return false unless account != nil && domain != nil + return false unless /^[-0-9a-z_.]+$/oi =~ domain + domain = safecopy(domain) + require 'socket' + begin + TCPSocket.gethostbyname(domain) + return true + rescue + if test(?x, @opt["hostcmd"]) + open("| #{@opt['hostcmd']} -t mx #{domain}.", "r") {|ns| + #p ns.readlines.grep(/\d,\s*mail exchanger/) + return ! ns.readlines.grep(/is handled .*(by |=)\d+/).empty? + } + elsif test(?x, @opt["nslookup"]) + open("| #{@opt['nslookup']} -type=mx #{domain}.", "r") {|ns| + #p ns.readlines.grep(/\d,\s*mail exchanger/) + return ! ns.readlines.grep(/\d,\s*mail exchanger/).empty? + } + end + return false + end + end # checkmail + + # Logging + # + def putLog(msg) + msg += "\n" unless /\n/ =~ msg + open(@opt["logfile"], "a+") {|lp| + lp.print Time.now.to_s + " " + msg + } + end + + def sendnotify(whom, subj, body) + users = users() + if grepgroup(whom) + recipients = @sc.members(whom) + else + recipients=[whom] + end + for u in recipients + if users.grep(u)[0] + sendMail(mailaddress(u), subj, body) + end + end + end + + def sendMail(to, subject, body) + body = Kconv::tojis(body) + subject = Kconv.tojis(subject) + to = safecopy(to) # cleanup tainted address + if /\e/ =~ subject # If contains JIS chars... + subject = subject.split(//,1).pack('m') + subject = "=?iso-2022-jp?B?#{subject}?=" + end + subject.gsub!(/\n/, '') + begin + if (m=open("|-", "w")) + m.print "To: #{to}\n" + m.print "Subject: #{subject}\n" + m.print "Mime-Version: 1.0 +Content-Transfer-Encoding: 7bit +Content-Type: Text/Plain; charset=iso-2022-jp + +" + m.print body, "\n" + m.close + else + # exec(@attr['mail'], "-s", subject, to) + exec(@opt['sendmail'], to) + exit 0; + end + putLog("Sent '#{subject}' to #{to}\n") + return true + rescue + putLog("FAILED! - Sent '#{subject}' to #{to}\n") + return nil + end + end # sendMail + + def today() + today = Time.now + showtable(today) + end + def isleap?(y) + if y%400 == 0 + true + elsif y%100 == 0 || y%4 != 0 + false + else + true + end + end + def daysofmonth(year, month) + dl = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + if month != 2 || !isleap?(year) + dl[month-1] + else + 29 + end + end + # + # Return the Time object at the last day of last month + def lastmonth(today) + Time.at(Time.mktime(today.year, today.month)-3600*24) + end + # + # Return the Time object at the first day of next month + def nextmonth(today) + y, m = today.year, today.month + Time.at(Time.mktime(y, m, daysofmonth(y, m))+3600*24) + end + + def month(month) + y, m = month.scan(%r,(\d\d\d\d+)/(\d+),)[0] + if y && m + showtable(Time.mktime(y, m, 1)) + else + outputError "%s %s", msg('invaliddate'), month + return nil + end + end + def footer1() + "<br>" + \ + @H.element("p"){ + me = @myname+"?-"; delim = " / " + @H.a(me+'userman', msg('user', 'management')) + delim + \ + @H.a(me+'groupman', msg('group', 'management')) + } + end + + def footer2() + "<hr>" + \ + @H.element("code") { + "This " + \ + @H.a(@after5url, "After5") + \ + " board is maintained by " + \ + @opt['maintainer'].sub('@', "@") + "." + } + end + def footer() + footer1+footer2 + end + def nickname(userORgroup) + if grepgroup(userORgroup) + @sc.groupname(userORgroup) + else + @sc.nickname(userORgroup) + end + end + # + # show specified month's calendar + def showtable(day) + if !checkauth + return nil + end + + month = day.month.to_s + first = Time.mktime(day.year, day.month, 1) + last = daysofmonth(day.year, day.month) + wday1 = first.wday + start = 1-wday1 + wname = %w[sun mon tue wed thu fri sat] + today = Time.now + todaym = today.month + todayd = today.day + tdclass = {} + tdclass["width"] = "64px" if @oldagent # workaround for NN4 + + holiday = Holiday.new + # create dayofweek header + @O.print @H.elementln("h1", nil){sprintf "%d/%d", day.year, day.month} + @O.print @H.startelement("table", {'border'=>"1", 'class'=>'main'}) + + # day of week + @O.print @H.startelement("tr") + for w in wname + @O.print @H.element("th", {'class'=>w}){w.capitalize} + end + @O.print "\n"+@H.endelement(nil, true) + + # create day table + column = start + ## p day, last + while column <= last + @O.print @H.elementln("tr", nil){ + (column..column+6).collect{|d| + todayp = (day.month==todaym && d==todayd) + wd=d-column + hd = holiday.isHoliday(day.year, day.month, d, wd) + tdclass['class'] = (hd ? 'holiday' : wname[wd]) + @H.element("td", tdclass){ + if d>0 && d <= last + date = "%d/%d/%d"%[day.year, day.month, d] + @H.element("p", {'class'=>todayp ? 'todayline' : 'dayline'}){ + @H.a(@myname+"?-show+"+date, d.to_s) + } + \ + # isHoliday? + if hd + @H.element("small"){hd.join("<br>")} + end.to_s + \ + @H.element("p", {'class'=>'topic'}){ + s = @sc.day_all(date, @params['user']) + if !s.empty? + s.keys.sort.collect{|time| + s[time].keys.sort.collect{|who| + sprintf "%04s:%s", + time == @opt['alldaydir'] ? msg('allday') : time, + nickname(who) + }.join + }.join("<br>") + else + @opt['tdskip'] + end + } + else + @opt['tdskip'] + end + } + }.join + } + # ここ活かしてない + @H.elementln("tr", nil){ + (column..column+6).collect{|d| + wd=d%7 + @H.element("td", {'class'=>wname[wd]}){ + @H.element("div", {'class'=>'scline'}){ + if d>0 && d <= last + s = @sc.day_all("%d/%d/%d"%[day.year, day.month, d]) + unless s.empty? + s.keys.sort.collect{|time| + s[time].keys.sort.collect{|who| + sprintf "%4s:%s", time, who.sub(/@.*/, '') + } + }.join("<br>") + else + @opt['tdskip'] + end + else + @opt['tdskip'] + end + } + } + }.join + } + column += 7 + end + + # month-link + @O.print @H.elementln("tr", {'class'=>'monthlink'}){ + lm1 = lastmonth(day) + lm2 = lastmonth(lm1) + lm3 = lastmonth(lm2) + nm1 = nextmonth(day) + nm2 = nextmonth(nm1) + nm3 = nextmonth(nm2) + [lm3, lm2, lm1, nil, nm1, nm2, nm3].collect{|t| + @H.element("td"){ + if t.is_a?(Time) + ym = sprintf("%d/%d", t.year, t.month) + @H.a(sprintf("%s?-month+%s", @myname, ym), ym) + else + sprintf "%d/%d", day.year, day.month + end + } + }.join("\n") + } + @O.print "\n"+@H.endelement(nil, true) + + @O.print "showtable" if @params['user'] == @author + @O.print footer + ##schedule.day_all("2003/12/22") + # @O.print @H.endelement() + end + + # + # Put carrying values + def hiddenvalues() + h = %w[user].collect{|v| + if @params[v] + sprintf "<input type=\"hidden\" name=\"%s\" value=\"%s\">\n", + v, @params[v] + end + } + h.delete(nil) + h.join + end + # + # Return the string of table + def dayTableString(s, user, date) + r = '' + r << @H.startelement("table", {'border'=>'1'}, true) + r << @H.element("tr", nil){ + @H.element("th", {'class'=>'time'}){'TIME'} + \ + @H.element("th", nil){'Schedule'} + } + for time in s.keys + tstr = case time + when @opt['alldaydir'] + msg('allday') + else + sprintf "%02d:%02d", time.to_i/100, time.to_i%100 + end + r << @H.startelement("tr", nil, true) + r << @H.element("th", {'class'=>'time'}){tstr} + r << @H.element("td"){ + @H.elementln("table"){ + s[time].keys.collect{|who| + editable = (user==who || @sc.ismember(user, who)) + groupp = grepgroup(who) + @H.element("tr"){ + @H.element("td", {'class'=>groupp ? 'group' : 'who'}){ + if !groupp && webpage(who) + @H.a(webpage(who), nickname(who)) + else + nickname(who) + end + } + \ + @H.element("td"){ + if editable + s[time][who]['pub'] ? msg('public') : + msg('nonpublic') + else + @opt['tdskip'] + end + } + \ + @H.element("td"){ + if editable + @H.a(@myname+"?-modify+#{date}/#{time}/#{who}", + msg('modify')) + else + @opt['tdskip'] + end + } + \ + @H.element("td"){ + if editable + @H.a(@myname+"?-remove+#{date}/#{time}/#{who}", + msg('remove')) + else + @opt['tdskip'] + end + } + \ + @H.element("td"){s[time][who]['sched']} + } + }.join("\n") + } + } + r << @H.endelement() + end + r << @H.endelement() + r + end + # + # show the schedule list of specified date + # + def show(date) + if !checkauth + return nil + end + border1 = {'border'=>'1'} + user = safecopy(@params['user']) + s = @sc.day_all(date, user) + mygroup = @sc.groups().select{|g|@sc.ismember(user, g)} + + @O.print @H.element("h1", nil){ + sprintf msg('fmtdaysschedule'), date + } + if s.empty? + @O.print @H.p(msg('noplan')) + else + @O.print dayTableString(s, user, date) + end #is_empty? + thisyear, thismonth, thisday = date.scan(%r,(\d\d\d\d+)/(\d+)/(\d+),)[0] + mstr = sprintf "%04d/%02d", thisyear.to_i, thismonth.to_i + @O.print @H.a(@myname+"?-month+"+mstr, + sprintf(msg('tomonthlist'), mstr)) + # + # Link button to add new plan + #now = Time.now+3600*24 + now = Time.mktime(thisyear, thismonth, thisday.to_i, Time.now.hour) + y, m, d, h, min = now.year, now.month, now.day, now.hour, now.min + @O.print @H.element('h2', nil, true){msg('addsched')} + @O.print @H.element('p', nil){msg('defthisday')} + @O.print @H.element("form", {'action'=>@myname+"?-addsched", 'method'=>'POST'}){ + @H.elementln('table', border1){ + @H.elementln('tr'){ + @H.element('th'){'Name'} + \ + @H.element('td'){ + hiddenvalues() + @sc.nickname(user) + } + } + \ + @H.elementln('tr'){ + @H.element('th'){'Year'} + \ + @H.element('td'){@H.select_integer("year", y, y+5, y)} + } + \ + @H.elementln('tr'){ + @H.element('th'){'Month'} + \ + @H.element('td'){@H.select_integer("month", 1, 12, m)} + } + \ + @H.elementln('tr'){ + @H.element('th'){'Day'} + \ + @H.element('td'){@H.select_integer("day", 1, 31, d)} + } + \ + @H.elementln('tr'){ + @H.element('th'){'Time<br>'+ \ + sprintf(msg('24hour'), @opt['alldaydir'])} + \ + @H.element('td'){ + '<input type=text name="time" value="3000" size=8 maxlength="4">' + } + } + \ + @H.elementln('tr'){ + @H.element('th'){msg('publicok')} + \ + @H.element('td'){ + @H.radio('pub', 'yes', msg('yes')+'<br>', true) + \ + @H.radio('pub', 'no', msg('no')) + } + } + ## table + } + \ + @H.elementln("p"){ # put notify mail checkbox + msg('reqnotify') + '<br>' + \ + @ntlist.collect{|n, v| + @H.checkbox(n, 'yes', v, @params[n]) + }.join("\n") + \ + " " + @H.checkbox('rightnow', 'yes', msg('rightnow'), true) + \ + "\n" + } + \ + if mygroup[0] + @H.elementln("p"){ # put "register as" + msg('registas') + "<br>\n" + \ + mygroup.collect{|g| + @H.radio('registas', g, @sc.groupname(g)) + }.join(' ') + "\n/ " + \ + @H.radio('registas', 'no', msg('personal')) + } + end.to_s + "\n" + \ + @H.element("textarea", @schedulearea){} + "<br>\n" + \ + @H.submit_reset("GO") + } #form + @O.print "show" if user == @author + end + + # + # call process + def call_process(cmd, input=nil, timeout=10) + prc = CMDTimeout.new + fds = prc.start(cmd, timeout, true) + if input + Thread.start { + fds[0].sync = true + fds[0].print.input + fds[0] + } + end + begin + fds[1].readlines + ensure + prc.close() + end + end + # + # notification registerer + def notify_time(year, month, day, time, symbol) + if (t = time.to_i) > 2359 + hh = mm = 0 + else + hh, mm = t/100, t%100 + end + base = Time.mktime(year.to_i, month.to_i, day.to_i, hh, mm) + if /nt(\d+)([mh])$/ =~ symbol + return nil if t > 2359 + num, unit = $1.to_i, $2.downcase + rate = {'h'=>3600, 'm'=>60}[unit] || 3600 + return Time.at(base-rate*num) + elsif /nt(\d+)d/ =~ symbol + seconds = $1.to_i*3600*24 + targetday= Time.at(base-seconds).to_a + targetnight = + Time.mktime(*(targetday.indexes(5,4,3)+[@opt['night'].to_i])) + elsif "nttoday" == symbol + Time.mktime(year.to_i, month.to_i, day.to_i, @opt['morning']) + end + end + def reg_notify(user, year, month, day, time, text, cancelall = nil) + threshold = 5*60 # Omit notifycation within 30min future + + y, m, d, t, = year.to_i, month.to_i, day.to_i, time.to_i + if t > 2359 + hh = mm = 0 + else + hh = t/100 + mm = t%100 + end + now = Time.now + + filearg = [user, year, month, day, t] + @ntlist.each{|k, v| + nt_time = notify_time(year, month, day, t, k) + if !nt_time + # do nothing for allday schedule's notification before some minutes + elsif cancelall || nt_time < now+threshold || + /yes|on|true|1/ !~ @params[k] || !@params[k] + # cancel + uf = @sc.remove_crondir(nt_time, user, year, month, day, t) + @sc.removefile(*(filearg+[k])) + else + # register + lf = @sc.register_crondir(nt_time, user, year, month, day, t) + @sc.putfile(*(filearg+[k, lf])) + end + } + end + def cancel_notify(user, year, month, day, time) + reg_notify(user, year, month, day, time, 'dummy', true) + end + # + # add or remove a schedule + # + def add_remove(remove = nil) + if !checkauth + return nil + end + user = registerer = @params['user'] + as = @params['registas'] + if as && as > '' && /^no$/ !~ as && @sc.ismember(user, as) + registerer = as + end + now = Time.now + y, m, d, h, min = now.year, now.month, now.day, now.hour, now.min + + $KCODE='e' if $DEBUG + @O.print @params.inspect if $DEBUG + # + # Check the validity of specified time + sy = @params['year'].to_i + sm = @params['month'].to_i + sd = @params['day'].to_i + tm = @params['time'].to_i + if tm > 2399 + timedir=@opt['alldaydir'] + sh, smin = 23, 59 + tmstr = msg('allday') + else + sh = (tm/100).to_i + smin = (tm%100).to_i + timedir = sprintf("%04d", tm) + tmstr = sprintf("%d:%02d", sh, smin) + end + time = nil + begin + time = Time.mktime(sy, sm, sd, sh, smin) + rescue + outputError "%s<br>\nyear=%s<br>month=%s<br>day=%s<br>time=%s\n", + msg('invaliddate'), + @params['year'], @params['month'], @params['day'], @params['time'] + return nil + end + unless @params['schedule'] && @params['schedule'].strip > '' + outputError msg('putsomething') + return nil + end + + # do remove or addition + if remove + cancel_notify(registerer, sy, sm, sd, timedir) + begin + @sc.remove(registerer, sy, sm, sd, timedir) + #########@O.print @H.p(msg('remove')+msg('done')) + rescue + outputError("Failed"+$!) + end + else + if time < now + outputError(msg('past')) + return nil + end + begin + (text = @params['schedule'].strip.gsub(/\r+\n/, $/)) << "\n" + replace = (/modify/i =~ @params['editmode']) + rc = @sc.register(registerer, sy, sm, sd, timedir, text, replace) + if @params['pub'] && /yes/ =~ @params['pub'] + @sc.putfile(registerer, sy, sm, sd, timedir, 'pub', "1\n") + else + @sc.removefile(registerer, sy, sm, sd, timedir, 'pub') + end + ######## @O.print @H.p(msg('appended')) if rc == 1 + rescue + outputError("Failed"+$!) + end + text = @sc.getschedule(registerer, sy, sm, sd, timedir) + reg_notify(registerer, sy, sm, sd, timedir, text) + if @params['rightnow'] && /yes/i =~ @params['rightnow'] + header = sprintf("%s/%s/%s %s %s\n%s%s%s\n%s\n", + sy, sm, sd, tmstr, msg('immediatenote'), + msg('registerer_is'), nickname(registerer), + if user!=registerer + sprintf(" (%s%s)", + msg('registerer'), nickname(user)) + else + "" + end, + "-"*70) + sendnotify(registerer, "Registration completed", header+text) + end + end + show(sprintf("%04d/%02d/%02d", sy, sm, sd)) + @O.print "add_remove" if user == @author + end + + # add + def addsched() + add_remove(/remove/i =~ @params['editmode']) + end + + # + # Display remove or modify screen + def remove_modify(datetime, remove) + if !checkauth + return nil + end + + user = @params['user'] + y, m, d, time, dummy, as = + datetime.scan(%r,(\d\d\d\d+)/(\d+)/(\d+)/(\d+)(/(.+))?,)[0] + # datetime always contains trailing slash generated by parsedate + # but if the trailing part is a user(not a group), it is removed + # because it filtered out by grepgroup() function + if ! (y && m && d && time) + outputError "Invalid time specification" + return nil + elsif as && as > '' + unless @sc.ismember(user, as) + outputError "You have no permission to edit group %s's schedule", as + return nil + end + user = as + end + unless text=@sc.getschedule(user, y, m, d, time) + outputError "%s %s", datetime, msg('noplan') + return nil + end + @O.print @H.elementln("h1"){ + sprintf "%s %s", datetime, remove ? msg('remove') : msg('modify') + } + @O.print @H.elementln("form", {'action'=>@myname+"?-addsched", 'method'=>'POST'}){ + pubp=(@sc.getfile(user, y, m, d, time, 'pub').to_i > 0) + if as + @H.hidden("registas", as) + end.to_s + \ + "<input type=\"hidden\" name=\"year\" value=\"%04d\">\n" % y.to_i + \ + "<input type=\"hidden\" name=\"month\" value=\"%02d\">\n" % m.to_i + \ + "<input type=\"hidden\" name=\"day\" value=\"%02d\">\n" % d.to_i + \ + "<input type=\"hidden\" name=\"time\" value=\"%04d\">\n" % time.to_i + \ + msg('reqnotify') + "<br>\n" + \ + @ntlist.collect{|nt, v| + cronp = @sc.getfile(user, y, m, d, time, nt) + sprintf "<input type=\"checkbox\" name=\"%s\"%s>%s \n", + nt, (cronp ? " checked" : ""), v + }.join + "<br>" + \ + @H.element("textarea", @schedulearea) {text} + "<br>" + \ + @H.radio("editmode", "append", msg('append')) + ' / ' + \ + @H.radio("editmode", "modify", msg('modify'), !remove) + ' / ' + \ + @H.radio("editmode", "remove", msg('remove'), remove) + ' / ' + \ + "<br>\n" + \ + msg('publicok') + \ + @H.radio("pub", "yes", msg('yes'), pubp) + \ + @H.radio("pub", "no", msg('no'), !pubp) + \ + '<br>' + \ + @H.submit_reset("GO") + } + @O.print "remove_modify" if user == @author + end + def remove(datetime) + remove_modify(datetime, true) + end + def modify(datetime) + remove_modify(datetime, false) + end + + def prohibitviahttp() + %w[REMOTE_ADDR REMOTE_HOST SERVER_NAME].each{|v| + if ENV[v] + print "Content-type: text/plain\n\n" + print "Do not call this via CGI" + exit 0 + end + } + end + # + # notify: call via cron + def notify() + prohibitviahttp() + unless @opt['maintainer'] + STDERR.printf "Set maintainer(email-address) in %s\n", @opt['conf'] + STDERR.print "(ex) maintainer=yuuji@gentei.org\n" + exit 0 + end + Dir.chdir @mydir + line = "-"*25 + indent = " " + now = Time.now + p "notifylist", @sc.notify_list(now) if $DEBUG + @sc.notify_list(now).each{|u, datehash| + dellist = [] + content = datehash.sort.collect{|date, filehash| + next unless /(\d\d\d\d+)-(\d+)-(\d+)-(\d\d\d\d)/ =~ date + y, m, d, t = $1.to_i, $2.to_i, $3.to_i, $4.to_i + if t > 2359 + hhmm = msg('allday') + comment = msg('theday') + else + hhmm = sprintf "%02d:%02d", t/100, t%100 + diff = Time.mktime(y, m, d, t/100, 5%100) - now + if diff < 7200 + comment = "%d%s" % [diff/60, msg('minutes', 'before')] + elsif (ddiff=(Time.mktime(y, m, d)-Time.mktime(now.year, now.month, now.day))/3600/24) == 0 + comment = "%s%d%s" % + [msg('about'), diff/3600, msg('hours', 'before')] + else + comment = "%d%s" % [ddiff, msg('days', 'before')] + end + end + dellist << filehash['file'] + sprintf("%s[[[%d/%d/%d %s]]]%s\n", line, y, m, d, hhmm, line) + \ + sprintf("(%s %s)\n", comment, msg('notification')) + \ + indent+filehash['text'].join(indent) + "\n\n" + } + # content.delete(nil) + if content + if $DEBUG + print content + else + content.unshift(msg('introduce')+"\n"+msg('notifymail')+"\n") + content.unshift(@opt['url'].to_s+"\n") + if sendnotify(u, msg('notifysubj'), content.join) + # send mail completed + begin + @sc.cleanup_files(dellist) + rescue + end + end + end + end + } + if !(list=@sc.notify_list(now)).empty? + subj = @mybase+": Undeleted old cron files detected" + files = list.collect{|who, whash| + whash.sort.collect{|date, fhash| fhash['file']}.join("\n") + }.join("\n") + sendMail(@opt['maintainer'], subj, + "This is `#{@mybase}' in #{@mydir}\n" + + "You'd better check and remove these files.\n\n"+files) + end + + exit 0 + end + + # + # user management + def userman() + if !checkauth + return nil + end + user=@params['user'] + nickname = @sc.nickname(user) + tdclass = {} + tdclass["width"] = "80px" if @oldagent # workaround for NN4 + + @O.print @H.elementln("h1"){ + @mybase+' '+msg('user', 'management') + } + @O.print @H.p(@sc.mkusermap.inspect) if $DEBUG + @O.print @H.p(msg('usermodwarn')) + @O.print \ + @H.elementln("form", {'action'=>@myname+"?-usermod", 'method'=>'POST'}){ + @H.elementln("table"){ + @H.elementln("tr"){ + @H.element("td", tdclass) {msg('regaddress')} + \ + @H.element("td") { + @H.element("code"){user} + } + } + \ + @H.elementln("tr"){ + @H.element("td", tdclass) {msg('mailaddress')} + \ + @H.element("td") { + @H.text("newmail", mailaddress(user), @opt['size'], 80) + } + } + \ + @H.elementln("tr"){ + @H.element("td", tdclass) {msg('weburl')} + \ + @H.element("td") { + @H.text("webpage", webpage(user), @opt['size'], 80) + } + } + \ + @H.elementln("tr"){ + @H.element("td") {msg('nickname')} + \ + @H.element("td") { + @H.text("nickname", nickname, @opt['size'], 10) + } + } + } + \ + @H.elementln("p"){ + msg('shortnameplz') + "<br>\n" + \ + @H.a(@after5url+"multiplenotify.html", msg('multiplemail')) + } + \ + '<br>' + \ + @H.submit_reset("GO") + } # form + + # + # Next section, REMOVE USER! + @O.print @H.elementln("h2"){ + sprintf "%s %s %s", msg('user'), user, msg('deletion') + } + @O.print @H.p(msg('deletionwarn'))+"\n" + @O.print @H.elementln("form", {'action'=>@myname+"?-delusersub+#{user}", 'method'=>'POST'}){ + @H.hidden("user", user) + "\n" + \ + @H.elementln("table"){ + @H.elementln("tr"){ + @H.elementln("td"){ + sprintf msg('deluser'), user + } + \ + @H.elementln("td"){ + @H.radio("delete", "yes", msg('yes')) + ' ' + \ + @H.radio("delete", "no", msg('no'), true) + } + } + \ + @H.elementln("tr"){ + @H.elementln("td"){ + sprintf msg('really?'), user + } + \ + @H.elementln("td"){ + @H.radio("delete2", "yes", msg('yes')) + ' ' + \ + @H.radio("delete2", "no", msg('no'), true) + } + } + } + \ + "<br>\n" + @H.submit_reset("GO") + } + + + end + def usermod() + if !checkauth + return nil + end + @O.print @H.elementln("h1"){ + msg('user', 'management')+" "+msg('done') + } + user=@params['user'] + email = mailaddress(user) + newmail = @params['newmail'] + nickname = @sc.nickname(user) + newnn = @params['nickname'].to_s.strip + webpage = webpage(user) + newweb = @params['webpage'] + if email != newmail + # change user's address + if newmail == user + newvalue = nil + elsif checkmail(newmail) + newvalue = newmail + else + @O.print @H.elementln("pre"){"Invalid mail address"} + end + @O.print @H.elementln("pre"){ + if @sc.putuserattr(user, 'email', newvalue) + sprintf "new mail address=\"%s\"", mailaddress(user) + else + sprintf "Setting new mail address to \"%s\" failed", newvalue + end + } + end + if nickname != newnn + if @sc.setnickname(user, newnn) + @O.print @H.p(msg('success')) + @O.print @H.elementln("pre"){ + sprintf "user=\"%s\"\nnickname=\"%s\"", user, @sc.nickname(user) + } + @O.print @H.p(msg('nicknamenote')) if newnn == '' + else + @O.print @H.p(msg('failure')) + end + end + if newweb > '' && webpage != newweb + if @sc.putuserattr(user, "webpage", newweb) + @O.print @H.p(msg('success')) + @O.print @H.elementln("pre"){ + sprintf "user=\"%s\"\nwebpage=\"%s\"", user, webpage(user) + } + else + @O.print @H.p("Update webpage"+msg('failure')) + end + end + end + # + # Display form of group management + def groupman() + if !checkauth + return nil + end + user=@params['user'] + nickname = @sc.nickname(user) + tdclass = {} + tdclass["width"] = "80px" if @oldagent # workaround for NN4 + admclass = {'class'=>'admin'} + grmap = @sc.groupmap + + @O.print @H.elementln("h1"){ + @mybase+' '+msg('group', 'management') + } + $KCODE='e' if $DEBUG + @O.print grmap.inspect if $DEBUG + @O.print @H.p(msg('joinmyself')+ + @H.a(@myname+"?-newgroup", msg('newgroup'))) + @O.print @H.p(msg('usermodwarn')) + @O.print \ + @H.elementln("form", {'action'=>@myname+"?-groupmod", 'method'=>'POST'}){ + @H.elementln("table", {'border'=>'1', 'vertical-align'=>'top'}){ + grmap.collect{|g, ghash| + @H.elementln("tr"){ + @H.element("td", @sc.isadmin(user, g) ? admclass : nil){ + g + } + \ + @H.element("td"){ + @H.element("div", {'class'=>'c'}) { + if @sc.isadmin(user, g) + @H.a(@myname+"?-admgroup+#{g}", msg('adminop')) + else + '--' + end + } + } + \ + @H.element("td"){ + memberp = @sc.ismember(user, g) + if ghash['admin'].grep(user)[0] + @H.text("groupname-#{g}", ghash['name'], nil, 20) + else + ghash['name'] + end + '<br>' + \ + @H.radio("groupadd-#{g}", "yes", "IN", memberp) + " / " + \ + @H.radio("groupadd-#{g}", "no", "OUT", !memberp) + } + \ + @H.element("td"){ + ghash['members'].collect{|u| + @sc.nickname(u) + }.join(", ") + } + } + } + } + \ + '' + \ + @H.p(msg('groupwarn', 'shortnameplz')) + \ + @H.submit_reset("GO") + } # form + end + def groupmod() + if !checkauth + return nil + end + @O.print @H.elementln("h1"){ + msg('group', 'management')+" "+msg('done') + } + user=@params['user'] + @O.print @params.inspect if $DEBUG + + for grp in @sc.groups() + # + # as a member, participate or retire + key = "groupadd-#{grp}" + removep = (/no/i =~ @params[key]) + memberp = @sc.ismember(user, grp) + if @params[key] + if (!removep) ^ memberp + @sc.addgroup(grp, [user], removep) + @O.print @H.elementln("p"){ + sprintf "%s [%s] %s %s", msg('user'), user, + removep ? msg('removedfromgp') : msg('addedtogroup'), grp + } + end + end + # + # as a owner, change the name of group + if @sc.isadmin(user, grp) && + (newname = @params["groupname-#{grp}"]) && + @sc.groupname(grp) != newname + @sc.setgroupname(grp, newname) + @O.print @H.elementln("p"){ + sprintf "%s %s%s %s", + msg('group'), grp, msg('of', 'name', 'setto'), newname + } + end + end + end + def users() + unless pm=open_pm() + outputError(msg('autherror')) + return nil + end + pm.users + end + def grepgroup(gname) + gr = @sc.groups.grep(gname)[0] + end + def admgroup(group = nil) + # if group==nil, create new + if !checkauth + return nil + end + @O.print @H.elementln("h1"){ + msg('group', 'management') + } + user=@params['user'] + + # Check the existent group's validity + if group + unless (gr=grepgroup(group)) + @O.print @H.p("No such group #{group}") + return nil + end + group = gr + unless @sc.isadmin(user, group) + @O.print @H.p("You are not administrator of #{group}.") + return nil + end + @O.print @H.elementln("h2"){ + msg('group')+" #{group}" + + if group != @sc.groupname(group) + " (#{@sc.groupname(group)})" + end.to_s + } + actionmethod={'action'=>@myname+"?-admgroupsub", 'method'=>'POST'} + else + # New group creation + @O.print @H.elementln("h2"){ + msg('newgroup') + } + actionmethod={'action'=>@myname+"?-newgroupsub", 'method'=>'POST'} + end + + userlist = ([user] + users()).uniq + myselfclass = {'class'=>'admin'} + colspan2 = {'colspan'=>'2'} + warnclass = {'class'=>'warn'} + warnp = nil + + @O.print @H.elementln("form", actionmethod){ + @H.hidden('group', group) + "\n" + \ + if group + "" + else + # new group creation + grps = @sc.groups() + i=1 + defname = "group%03d"%i + while grps.grep(defname)[0] + defname = "group%03d"%(i+=1) + end + @H.element("pre"){ + msg('group', 'of', 'id')+"\n"+@H.text("group", defname) + "\n" + \ + msg('group', 'of', 'name', 'anystring')+"\n"+ \ + @H.text("gname", '') + "\n" + } + end + \ + @H.elementln("table", {'border'=>'1'}){ + @H.elementln("tr"){ + @H.element("th"){msg('join')} + \ + @H.element("th"){msg('administrator')} + \ + @H.element("th"){msg('member')} + } + \ + userlist.collect{|u| + recursememp = nil + if group + memberp = (@sc.ismember(u, group) && true) + adminp = (@sc.isadmin(u, group) && true) + if !memberp && @sc.members(group).grep(u)[0] + recursememp = true + end + else + memberp = adminp = (u == user) + end + @H.elementln("tr", (u==user ? myselfclass : nil)){ + @H.element("td"){ + @H.radio('mem-'+u, 'yes', 'YES / ', memberp) + \ + @H.radio('mem-'+u, 'no', 'NO', !memberp) + } + \ + @H.element("td"){ + @H.radio('adm-'+u, 'yes', 'YES / ', adminp) + \ + @H.radio('adm-'+u, 'no', 'NO', !adminp) + } + \ + @H.element("td"){ + @sc.nickname(u) + \ + if recursememp + warnp = true + @H.element("span", warnclass){"(*)"} + end.to_s + } + } + }.join + \ + # group names + @H.elementln("tr"){ + @H.element("th", colspan2){msg('join')} + \ + @H.element("th"){msg('group')} + } + \ + @sc.groups().collect{|g| + next if group == g + memberp = @sc.ismember(g, group) + @H.element("tr"){ + @H.element("td", colspan2){ + @H.radio('mem-'+g, 'yes', 'YES / ', memberp) + \ + @H.radio('mem-'+g, 'no', 'NO', !memberp) + } + \ + @H.element("td"){ + if @sc.isadmin(user, g) + @H.a(@myname+"?-admgroup+#{g}", @sc.groupname(g)) + else + @sc.groupname(g) + end + } + } + }.join + } + "<br>\n" + \ + @H.submit_reset("GO") + } # form + @O.print @H.p(@H.element("span", warnclass){"(*)"}+ + msg('recursewarn')) if warnp + if group + @O.print @H.p(sprintf(msg('wholemembers'), group)) + @O.print @H.elementln("p", {'class'=>'listup'}){ + @sc.members(group).collect{|u|@sc.nickname(u)}.join(", ")} + end + + # + # Next section, REMOVE GROUP! + return nil unless group + @O.print @H.elementln("h2"){ + sprintf "%s %s %s", msg('group'), group, msg('deletion') + } + @O.print @H.p(msg('deletionwarn'))+"\n" + @O.print @H.elementln("form", {'action'=>@myname+"?-delgroupsub+#{group}", 'method'=>'POST'}){ + @H.hidden("group", group) + "\n" + \ + @H.elementln("table"){ + @H.elementln("tr"){ + @H.elementln("td"){ + sprintf msg('delgroup'), group + } + \ + @H.elementln("td"){ + @H.radio("delete", "yes", msg('yes')) + ' ' + \ + @H.radio("delete", "no", msg('no'), true) + } + } + \ + @H.elementln("tr"){ + @H.elementln("td"){ + sprintf msg('really?'), group + } + \ + @H.elementln("td"){ + @H.radio("delete2", "yes", msg('yes')) + ' ' + \ + @H.radio("delete2", "no", msg('no'), true) + } + } + } + \ + "<br>\n" + @H.submit_reset("GO") + } + + @O.print footer() + end + def newgroup() + admgroup(nil) + end + + def delgroupsub(group) + if !checkauth + return nil + end + user = @params['user'] + if group != @params['group'] + @O.print @H.p("Group mismatch") + return nil + end + unless (gr=grepgroup(group)) + @O.print @H.p("No such group #{group}") + return nil + end + group = gr + unless @sc.isadmin(user, group) + @O.print @H.p("You are not administrator of #{group}.") + return nil + end + unless @params['delete'] && /yes/i =~ @params['delete'] \ + && @params['delete2'] && /yes/i =~ @params['delete2'] + @O.print @H.p(msg('chicken')) + return nil + end + @O.print @H.elementln("h1"){ + msg('group')+" #{group} "+msg('deletion') + } + @O.print @H.p(@sc.destroygroup(group) ? msg("done") : msg("failure")) + + @O.print footer() + end + + def deleteuser(user) + @sc.deleteuser(user) && + begin + pm = open_pm + pm.delete(user) + pm.close() + true + rescue + nil + end + end + def delusersub(user) + if !checkauth + return nil + end + user = @params['user'] + if user != @params['user'] + @O.print @H.p("User mismatch") + return nil + end + unless (us=users().grep(user)[0]) + @O.print @H.p("No such user #{user}") + return nil + end + user = us + unless @params['delete'] && /yes/i =~ @params['delete'] \ + && @params['delete2'] && /yes/i =~ @params['delete2'] + @O.print @H.p(msg('chicken')) + return nil + end + @O.print @H.elementln("h1"){ + msg('user')+" #{user} "+msg('deletion') + } + @O.print @H.p(deleteuser(user) ? msg("done") : msg("failure")) + + @O.print footer() + end + + def admgroupsub() + if !checkauth + return nil + end + user = @params['user'] + group = @params['group'] + unless (gr=grepgroup(group)) + @O.print @H.element("pre"){"No such group #{group.inspect}"} + return nil + end + unless @sc.isadmin(user, group) + @O.print @H.p("You are not administrator of #{group}.") + return nil + end + gorup = gr + @O.print @H.elementln("h1"){ + msg('group', 'management', 'done') + } + @O.print @H.elementln("h2"){ + msg('group')+" #{group}" + + if group != @sc.groupname(group) + " (#{@sc.groupname(group)})" + end.to_s + } + somethingdone = nil + for u in users() + for var, kind in { + "mem"=>['members', 'member'], 'adm'=>['admin', 'administrator']} + memv = "#{var}-#{u}" + if @params[memv] + joinp = ((/^yes/i =~ @params[memv]) && true) + membp = if var=='mem' + @sc.ismember(u, group) + else # admin + @sc.isadmin(u, group) + end && true + if var=='adm' && @sc.admins(group).length == 1 && membp && !joinp + @O.print @H.p(sprintf(msg('soleadmin'), u, group)) + elsif joinp ^ membp + somethingdone = true + @sc.addgroup(group, [u], !joinp, kind[0]) + @O.print @H.elementln("p"){ + sprintf "%s [%s](%s) %s %s", msg('user'), u, + msg(kind[1]), + joinp ? msg('addedtogroup'): msg('removedfromgp'), group + } + end + end + end + end # users() + + # add or remove for group in groups + for g in @sc.groups() + next if g == group + memv = "mem-#{g}" + if @params[memv] + joinp = ((/^yes/i =~ @params[memv]) && true) + membp = (@sc.ismember(g, group) && true) + if joinp ^ membp + somethingdone = true + @sc.addgroup(group, [g], !joinp) + @O.print @H.elementln("p"){ + sprintf "%s [%s] %s %s", msg('group'), g, + joinp ? msg('addedtogroup'): msg('removedfromgp'), group + } + end + end + end # groups + unless somethingdone + # @O.print @H.p(msg('nothingtodo')) + end + @O.print footer() + end + def newgroupsub() + if !checkauth + nil + end + user = @params['user'] + newgroup = @params['group'] + newgname = @params['gname'] + + + if @sc.groups.grep(newgroup)[0] + @O.print @H.p(msg('existent')+newgroup) + return nil + end + @sc.creategroup(newgroup, newgname, [user]) + admgroup(newgroup) + end + + def setpasswd(user) + prohibitviahttp() + pm = open_pm() + exit 1 unless pm + if pm.userexist?(user) then + begin + system "stty -echo" + STDERR.print "New passwd: " + p1 = STDIN.gets + STDERR.print "\nAgain: " + p2 = STDIN.gets + ensure + system "stty echo" + end + if (p1 == p2) then + pm.setpasswd(user, p1.chop!) + end + STDERR.print "New password for #{user} set successfully\n" + else + STDERR.print "User #{user} not found\n" + end + pm.close() + exit 0 + end + def adduser(user) + prohibitviahttp() + pm = open_pm() + exit 1 unless pm + newpwd = pm.setnewpasswd(user, 4) + print "#{user}'s password is #{newpwd}\n" + pm.close() + exit 0 + end + def deluser(user) + prohibitviahttp() + pm = open_pm() + exit 1 unless pm + pm.delete(user) + pm.close() + exit 0 + end + + # read configuratoin file + def readconf(conf) + cf = "after5.cf" #conf # || @opt['conf'] + cf = File.join(@mydir, cf) unless test(?s, cf) + cf = File.join(ENV["HOME"], cf) unless test(?s, cf) + return unless test(?s, cf) + begin + IO.foreach(cf){|line| + next if /^\s *#/ =~ line + line.chop! + line.sub!(/^\s*#.*/, '') + next if /^$/ =~ line + case line + # title, type = line.split(/\t+/) + when /^([a-z]+)=(.*)/oi + key, value = $1, $2 + case value + when /^(no|none|null|nil)$/io + @opt[key] = nil + else + @opt[key] = value + end + print "#{key} set to #{value}\n" if $DEBUG + end + } + rescue + STDERR.print "Configuration file %s not readable\n", cf + end + end + + def parsedate(string) + if %r,^(\d\d\d\d+)/(\d+)/(\d+)/(\d+)/([^/]+)$, =~ string + sprintf "%04d/%02d/%02d/%04d/%s", $1.to_i, $2.to_i, $3.to_i, $4.to_i, + grepgroup($5) + elsif %r,^(\d\d\d\d+)/(\d+)/(\d+)/(\d+), =~ string + sprintf "%04d/%02d/%02d/%04d", $1.to_i, $2.to_i, $3.to_i, $4.to_i + elsif %r,^(\d\d\d\d+)/(\d+)/(\d+), =~ string + sprintf "%04d/%02d/%02d", $1.to_i, $2.to_i, $3.to_i + elsif %r,^(\d\d\d\d+)/(\d+), =~ string + sprintf "%04d/%02d", $1.to_i, $2.to_i + elsif %r,^(\d\d\d\d+)/(\d+), =~ string + sprintf "%04d", $1.to_i + end + end + + def getarg() + argument = {} + + while /^-/ =~ ARGV[0] + case ARGV[0] + when '-f' + conf = ARGV[1] + ARGV.shift + when "-d" + $DEBUG = true + when "-install" + when "-addsched" + @job = "addsched" + when "-today" + @job = "today" + when "-remove" + ARGV.shift + @job = 'remove "'+parsedate(ARGV[0])+'"' + when "-modify" + ARGV.shift + @job = 'modify "'+parsedate(ARGV[0])+'"' + when "-month" + ARGV.shift + @job = 'month "'+parsedate(ARGV[0])+'"' + when "-show" + ARGV.shift + # @job = "show '"+ARGV[0]+"'" + @job = "show '"+parsedate(ARGV[0])+"'" + when "-login" + @job = "login" + when "-userman" + @job = "userman" + when "-usermod" + @job = "usermod" + when "-groupinout" + @job = "groupinout" + when "-groupsubmit" + @job = "groupsubmit" + when "-groupman" + @job = "groupman" + when "-groupmod" + @job = "groupmod" + when "-notify" + @job = 'notify' # + exit + when "-newgroup" + @job = 'newgroup' + when "-admgroup" + ARGV.shift + gr = safecopy(grepgroup(ARGV[0])) + ##gr.untaint + @job = 'admgroup "'+gr+'"' + when "-admgroupsub" + @job = 'admgroupsub' + when "-newgroupsub" + @job = 'newgroupsub' + when "-delusersub" + ARGV.shift + usr = users().grep(ARGV[0])[0] + @job = 'delusersub "'+usr+'"' + when "-delgroupsub" + ARGV.shift + gr = grepgroup(ARGV[0]) + @job = 'delgroupsub "'+gr+'"' + when /-(setpasswd|deluser|adduser)$/ + ARGV.shift + @job = $1+ " '#{ARGV[0]}'" # + exit + when "" + end + ARGV.shift + end + + readconf(@conf) + + query = '' + method = ENV["REQUEST_METHOD"] + if /POST/i =~ method then + length = ENV["CONTENT_LENGTH"].to_i + query = STDIN.read(length) + elsif /GET/i =~ method then + query = ENV["QUERY_STRING"] + else # executed from command line + query = ARGV.join("&") + end + + for unit in query.split(/\&/) + if /^([a-z][-_0-9@%a-z.]*)=(.*)/i =~ unit + key, value = $1, $2 + #value.gsub!(/%(..)/){[$1.hex].pack("c")} # これでURLデコードが一発 + decode!(value) + decode!(key) + value = Kconv::toeuc(value) # EUCに変換 + printf "[%s] = %s\n", key, value if $DEBUG + argument[key] = value + end + end + argument + end + def getcookie() + cookie = {} + if /value=(.*)/ =~ ENV['HTTP_COOKIE'] + # value=$1.gsub!(/%(..)/){[$1.hex].pack("c")} + value=decode!($1) + for line in value.split("&") + if /(\w+)=(.*)/ =~ line + key, value = $1, $2 + #value.gsub!(/%(..)/){[$1.hex].pack("c")} # これでURLデコードが一発 + decode!(value) + value = Kconv::toeuc(value) # EUCに変換 + printf "cookie[%s] = %s\n", key, value if $DEBUG + cookie[key] = value + end + end + end + cookie + end +end + +After5.new.doit + +if __FILE__ == $0 +end + + +# Local variables: +# buffer-file-coding-system: euc-jp +# End: