Mercurial > hgrepos > hgweb.cgi > after5
view after5.rb @ 47:d65174b867aa draft
Add fromhack mode
author | HIROSE Yuuji <yuuji@gentei.org> |
---|---|
date | Mon, 11 Jun 2012 23:47:45 +0900 |
parents | 1b16890d61d6 |
children | 05de5274c110 |
line wrap: on
line source
#!/usr/local/bin/ruby19 # -*- coding: euc-jp -*- # # Associative Scheduling Table - after5 # (C)2003, 2004, 2006, 2008, 2012 by HIROSE Yuuji [yuuji<at>gentei.org] # $Id: after5.rb,v 1.19 2012/04/01 11:52:25 yuuji Exp yuuji $ # Last modified Mon Jun 11 23:37:31 2012 on firestorm # See http://www.gentei.org/~yuuji/software/after5/ # このスクリプトはEUCで保存してください。 $hgid = <<_HGID_.split[1..-2].join(" ") $HGid$ _HGID_ $myurl = "http://www.gentei.org/~yuuji/software/after5/" require 'kconv' require 'nkf' $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(name, range, selected=nil) #start = (b<e ? b : e) #last = (b>e ? b : e) c=0 "<select name=\"#{name}\">\n" + \ range.collect{|i| value = (i.is_a?(Array) ? i[1] : i).to_s sprintf "<option%s%s>%s%s</option>", (selected.to_s==value.to_s) ? " selected" : "", i.is_a?(Array) ? " value=\"%s\"" % value : '', i.is_a?(Array) ? i[0] : i.to_s, (c+=1)%6==0 ? "\n" : '' }.join + \ "\n</select>\n" end end class TEXTout def isBlock(elt) /\b(tr|[udo]l|p|div)\b/i =~ elt end def isEOC(elt) /\bt[dh]\b/i =~ elt end def eoelem(elt) r = "" r << "\n" if isBlock(elt) r << " " if isEOC(elt) r end def contenttype(type = "text/plain", 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 ===== [[[ %s ]]] ===== __EOS__ end def startelement(elt, attrs = {}, nl = true) attr = "" x = sprintf "%s", " "*@eltstack.length @eltstack.push(elt) x end def endelement(elt = nil, nl = true) if elt x = elt @eltstack.pop else x = @eltstack.pop end eoelem(x) end def element(elt, attrs = nil, nl = nil) attr = "" lf = nl ? "\n" : "" body = yield #sprintf "<%s%s>%s%s%s</%s>%s", elt, attr, lf, body, lf, elt, lf sprintf "%s%s", body, eoelem(elt) end def elementln(elt, attr=nil) body = yield sprintf "%s\n", body end def a(href, anchor = nil, attrs = {}) attr = attrs attr['href'] = href # sprintf "%s\n", href anchor end def p(msg, attrs=nil) element("p", attrs){msg} end def text(name, value='', size=nil, maxlength=nil) "" end def hidden(name, value='') "" end def radio(name, value, text='', checked=nil) "" end def checkbox(name, value, text='', checked=nil) "" end def submit(name, value, text='') "" end def reset(name, value, text='') "" end def submit_reset(name) "" end def select(name, range, selected=nil) "" 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.collect{|u| u.toeuc} # toeuc is for 1.9 :( 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 %r,[-A-Z0-9/+_.@],i =~ c } u = newu map[u] = {} d = File.join(@usermapdir, u).untaint next unless test(?d, d) Dir.foreach(d){|attr| next if /^\./ =~ attr attr.untaint if /^[A-Z_][-A-Z_0-9]*$/i =~ attr file = File.join(@usermapdir, u, attr).untaint next unless test(?s, file) && test(?r, file) map[u][attr] = IO.readlines(file).join.toeuc.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 unless @usermap[user] @usermap[user] = {} mkdir_p(d) unless test(?d, d) end @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].untaint 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 mailaddress(user, grp = nil) grp ? mail4grp(user, grp) : \ (getuserattr(user, 'email') || user) 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').untaint 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).untaint next unless test(?d, d) # get group name gnf = File.join(d, 'name').untaint if test(?r, gnf) && test(?s, gnf) n = IO.readlines(gnf)[0].to_s.toeuc.strip map[g]['name'] = if n > '' then n else g end else map[g]['name'] = g end # get administrators # gad = File.join(d, 'admin').untaint 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').untaint map[g]['members'] = [] if test(?d, memd) Dir.foreach(memd){|a| next if /^\./ =~ a map[g]['members'] << a.untaint } end # get other attributes Dir.foreach(d) {|attr| next if /^\./ =~ attr next unless /^[-_a-z]+$/i =~ attr next if attr == "name" # already collected attr.untaint file = File.join(d, attr) #.untaint next if test(?d, file) next unless test(?s, file) && test(?r, file) map[g][attr] = IO.readlines(file).join.toeuc.strip } } map end def putgroupattr(group, attr, value) d = File.join(@groupmapdir, group).untaint Dir.mkdir(d) unless test(?d, d) file = File.join(d, attr) begin unless @groupmap[group] @groupmap[group] = {} end @groupmap[group][attr] = value if value == nil File.unlink(file) else open(file, "w"){|w| w.puts @groupmap[group][attr]} end rescue return nil end return {attr => value} end def getgroupattr(group, attr) if @groupmap.has_key?(group) && @groupmap[group][attr].is_a?(String) && @groupmap[group][attr] > '' return @groupmap[group][attr].untaint else return nil end end def groupmap() @groupmap end def groups() @groupmap.keys end def addgroup(group, users, remove=nil, role='members') grp = groups.grep(group)[0] # group may be tainted, using kept name return nil unless grp for u in users m = nil u, m = u if u.is_a?(Array) # ["user", "mailto"] m = nil if mailaddress(u)==m || /@/ !~ m next unless account_exists(u) mdir = File.join(@groupmapdir, grp, role).untaint file = File.join(mdir, u).untaint 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|x.puts m if m} end end grp end def setgroupname(grp, name) return nil unless @groupmap[grp] mdir = File.join(@groupmapdir, grp).untaint nfile = File.join(mdir, 'name').untaint @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=[]) grpptnOK = /^[-A-Z0-9._:!$%,]+$/i return nil unless grpptnOK =~ grp gg = '' grp.split('').each{|c| gg << c[0].chr if c =~ grpptnOK} grp = gg 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 = File.join(@usermapdir, user).untaint test(?d, dir) || Dir.mkdir(dir) 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) path.untaint if (list = Dir.glob(path))[0] for p in list p.untaint system "/bin/rm -rf \"#{p}\"" end cleanup_files(list) end end def account_exists(instance) if /@/ =~ instance true else ! @groupmap.select{|k, v| k==instance}.empty? end end def mail4grp(usr, group) file = File.expand_path((group+"/members/"+usr).untaint, @groupmapdir) if test(?s, file.untaint) open(file, "r"){|f|f.gets.chomp}.untaint else mailaddress(usr) end end def ismember(user, grouporuser) return user if user==grouporuser if @groupmap[grouporuser] @groupmap[grouporuser]['members'].grep(user)[0] && mail4grp(user, grouporuser) end end def isuser(user) @usermap[user] && @usermap.keys.grep(user)[0] 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 membernames(grp) members(grp).collect{|u| nickname(u)} end def admins(grp) @groupmap[grp] and @groupmap[grp]['admin'] end def groupname(grp) @groupmap[grp] && @groupmap[grp]['name'] end def name2group(name) @groupmap.find{|g, v| v.is_a?(Hash) && v['name']==name} end def day_all(d, user=nil, personalonly = 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) ## next if personalonly && who != user #2004/1/16 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) && !personalonly # unneccessary if personal mode 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.toeuc.chomp! sched[time][who]['regtime'] = File.stat(file).mtime 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).untaint 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 path = path.untaint 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.toeuc 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| next unless /(\d\d\d\d+)-(\d+)-(\d+)-(\d\d\d\d)/ =~ datedir ##datedir = sprintf("%04d-%02d-%02d-%04d", ## $1.to_i, $2.to_i, $3.to_i, $4.to_i) datedir.untaint dd = File.join(@crondir, datedir) next unless test(?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) next if /^\./ =~ user if isgroup(user) user = @groupmap.keys.grep(user)[0] else user = @usermap.keys.grep(user)[0] end next unless 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 #date = sprintf("%04d-%02d-%02d-%04d", # $1.to_i, $2.to_i, $3.to_i, $4.to_i) date.untaint f = File.join(ud, date) if test(?s, f) ntl[user][date] = {} ntl[user][date]['file'] = f ntl[user][date]['text'] = IO.readlines(f).collect{|l| l.toeuc} # ...why? :-( 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 me = $0.dup.untaint scriptsuid = File.stat(me).uid for f in files if $SAFE > 0 f.untaint if test(?e, f) && File.stat(f).uid != scriptsuid f.taint end end printf "Removing %s\n", f if $DEBUG 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 append(str) @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 = line.toeuc.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] wday = wname[wday || Time.mktime(y, m, d).wday] holiday = @@holiday[sprintf("%d/%d/%d", y, m, d)] || @@holiday[sprintf("%d/%d", m, d)] unless holiday nthweek = (d-1)/7+1 holiday = @@holiday[sprintf("%d/w%d%s", m, nthweek, wday)] end if !holiday && wday == "mon" && d > 0 # d<1 when column is before 1th # holiday in lieu yesterday = Time.mktime(y, m, d)-3600*24 holiday = ["振替休日"] if isHoliday(yesterday.year, yesterday.mon, yesterday.day) 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) @mybase = @myname.sub(/\.\w+$/, '') @mydir.untaint @mybase.untaint Dir.chdir @mydir @myname='a5.cgi' if test(?f, "a5.cgi") @conf = nil @schedulearea = {'rows'=>'4', 'cols'=>'60', 'name'=>'schedule'} @oldagent = (%r,Mozilla/4, =~ ENV['HTTP_USER_AGENT']) @lang = 0 @mailmode = nil @saveprefsregexp = /^(display(mode|days)$|nt|headline)/ @opt = { 'conf' => @mybase+".cf", 'css' => @mybase+".css", 'logfile' => 's/'+@mybase+".log", "sendmail" => "/usr/sbin/sendmail", 'hostcmd' => '/usr/bin/host', 'nslookup' => '/usr/sbin/nsookup', 'bg' => 'ivory', 'name' => nil, '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' => 's/a5pswd', 'lang' => 'j', 'notifymail' => true, 'mailbracket' => '[%n-ML]', } @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" @wnames = %w[sun mon tue wed thu fri sat] @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() importcookie() @lang = (/^j/i =~ @opt['lang'] ? 0 : 1) p @cookie if $DEBUG p @params if $DEBUG ### @params['displaymode'] = @params['displaymode'] || @cookie['displaymode'] personal = /personal/i =~ @params['displaymode'] bodyclass = if personal then {'class'=>'personal'} end ## x = {"align"=>'center'} ## @H.element("p", x, "hoge", nil) ## @H.element("p", nil, "buha", nil) if nil if !@params['passwd'] && @cookie['passwd'] @params['passwd'] = @cookie['passwd'] end if !@params['user'] && @cookie['user'] @params['user'] = @cookie['user'] end end @params['user'] = safecopy(@params['user']) ######eval @job a5name = if @opt['name'] && @opt['name'] > '' sprintf("(%s)", @opt['name']) else "" end @O.append(@H.contenttype() + @H.head(a5name+"After 5"+@job.sub(/\s*/, ' '), @opt['css'])) @O.print @H.startelement("body", bodyclass, true) # @job should be here because its output shoud go after <body>. eval @job @O.print @H.endelement(nil, true) # body @O.print @H.endelement("html", true) # html setcookie() print @O.readlines end def msg(*keyword) unless defined?(@msg) @msg = { 'title' => ['みんなの予定表 <img src="after5.png" alt="「アフター5」">', 'Schedule table for us all <img src="after5.png" alt="After 5">'], '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'], 'forgotguide' => ['忘れた場合は %s と入力するよろし', "Put \`%s' when you forgot password."], 'fmtdaysschedule'=> ['%s〜の予定', 'Schedule from %s'], 'schedtable' => ['予定表', 'Schedule Table'], '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'], '24hourtxt' => ['24時間制4桁でね(0000〜2359), %sは時刻指定なし', 'in 24-hour(0000-2359), %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)'], 'daystodisplay' => ['日分表示', 'days to display'], 'before' => ['前', 'before'], 'precedingday' => ['前日', 'Preceding day'], 'theday' => ['当日朝', "the day's morning"], 'night' => ['(夜)', '(night)'], 'publicok' => ['メンバーに<br>見せてもええね?', 'visible from other members?'], 'public' => ['公', 'pub'], 'nonpublic' => ['非', 'sec'], 'through' => ['〜', '=>'], 'yes' => ['はいな', 'yes'], 'no' => ['やだ', 'nope'], 'wnames' => [%w[日 月 火 水 木 金 土], %w[sun mon tue wed thu fri sat]], 'whichday' => ['<small>(まとめ登録の場合)</small><br>期間中のどの日に?', '<small>(On multiple registration)</small><br>Which days in the term?'], 'singleday' => ['一日分だけ登録', '1day regist'], 'everyday' => ['毎日', 'everyday'], 'invaliddate' => ['日付指定が変みたい', 'Invalid time string'], 'past' => ['それはもう過去の話ね', 'It had Pasted'], 'putsomething' => ['何か書こうや', 'Write some message please'], 'appended' => ['既存の予定に追加しました', 'Appended'], 'append' => ['追加', 'append'], 'join' => ['参加', 'join'], 'regist' => ['登録', 'register'], 'remove' => ['削除', 'remove'], 'move' => ['移動', 'move'], 'newdate' => ['移動先時刻', 'New date'], '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'], 'fromhack' => ['ML配送時のFrom:を常にMLのアドレスにする', 'Set From: address of all ML messages to ML address.'], 'personal' => ['個人で', 'personal'], 'registas' => ['グループ予定として登録?', 'Register as group?'], 'headsched' => ['下の枠内に予定を記入: 1行以内で短めに。 長くなるときは2行目以降に詳細を。', 'Put shortest sentence as possible within 1 line. Or, put short subject in the first line, details in latter lines.'], 'joinquit' => ['入退', 'joining/quiting'], 'of' => ['の', "'s"], 'id' => ['ID(英単語かローマ字の分かりやすい1単語半角空白なしで)', 'ID(without spaces)'], 'name' => ['名前', 'name'], 'anystring' => ['(日本語OK)', '(any length, any characters)'], 'setto' => ['を設定 → ', 'set to '], 'dupname' => ['あー、%sってグループ名は既にあるん素。別のにして.', "Group name `%s' already exists, choose another name."], 'management' => ['管理', 'management'], 'administrator' => ['管理者', 'Administrator'], 'newgroup' => ['新規グループ作成', 'Create new group'], 'adminop' => ['管理<br>操作', "Administrative<br>operation"], 'sendall' => ['一斉送信', "write to members"], 'sendall_err' => ["%s ファイルで mailprefix と maildomain を定義しとかないと送れまへん。 例: mailprefix=yuuji-after5 maildomain=gentei.org さらに、.qmail-$mailprefix-default も以下のように用意しておこね。 | ./#{@myname} -list", "You should define `mailprefix' and `maildomain' in %s file before sending message to all. (ex.) mailprefix=yuuji-after5 maildomain=gentei.org And then prepare .qmail-$mailprefix-default file as below. | ./#{@myname} -list"], 'sendall_head' => ['「%s」宛のメイル送信', "Send message to `%s'"], 'sendall_note' => ['メンバーへの連絡だけでなく、グループ非加入者がこれから加入する旨の通知などにも有用。', "Send this message to all of group."], 'sendall_done' => ['送信完了', "sending message done"], 'body' => ['本文', 'Body'], 'member' => ['メンバー', 'Member'], 'personalmode' => ['自分のだけ表示モード', 'Display Personal Only'], 'normalmode' => ['全員分表示モード', "Display Everyone's"], 'display' => ['予定表示行: ', 'Display schedule of: '], 'nameonly' => ['名前のみ', 'Name Only'], 'head5char' => ['先頭5文字', 'Head 5 chars'], 'headline' => ['先頭1行', 'Headline only'], 'whole' => ['長くても全部', 'Whole text'], 'hldays' => ['最新X日分強調', 'Hilight Recent X-days'], '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.'], 'schedlist' => [' and %d days Schedule list', 'から%d日間の予定一覧'], 'nothing' => ['なんもないす', 'Nothing'], 'sessionpswd' => ['セッションパスワード(これはいじらないでね)', 'Session Password(Do not modify this)'], 'date' => ['日付', 'Date'], 'time' => ['時刻指定', 'Time'], 'publicp' => ['公開=yes、非公開=no', 'Public?'], 'neednotify' => ['通知メイル(要らないのは消してね)', 'Leave lines for notification timing'], 'schedulehere' => ['以下登録内容', 'Your Schedule below'] } end 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 importcookie() @cookie.keys.grep(@saveprefsregexp){|v| @params[v] = @params[v] || @cookie[v] } for v in %w[user passwd] @params[v] = @params[v] || @cookie[v] end end def setcookie() a = {} a['user'] = @params['user'] if @params['user'] a['passwd'] = @params['passwd'] if @params['passwd'] ac = gencookie("value", a, 3600*6*1) printf "Set-Cookie: %s\n", ac if ac p = {} @params.keys.grep(@saveprefsregexp){|v| p[v] = @params[v].to_s.strip if @params[v] && @params[v] > '' } c = gencookie("prefs", p, 3600*24*7) str = [ac, c].select{|x|x}.join("; ") # printf "Set-Cookie: %s\n", str if str>'' 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 purify(string) string.gsub(/[\040-\177]/) {encode($&)} end def decode!(string) string.gsub!(/\+/, ' ') string.gsub!(/%(..)/){[$1.hex].pack("c")} end def decode(string) string.gsub(/\+/, ' ').gsub(/%(..)/){[$1.hex].pack("c")} end def escape(string) string.gsub(/&/n, '&').gsub(/\"/n, '"'). gsub(/>/n, '>').gsub(/</n, '<') end def gencookie(name, 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 "%s=%s; expires=%s", name, 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, grp=nil) @sc.mailaddress(user, grp) end def webpage(user) @sc.getuserattr(user, "webpage") end def checkauth_mail() return true # temporary end def checkauth() if @mailmode && @params['sessionpw'] return checkauth_mail end 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" + @opt['url'] + "\n" + "#{@mybase} 用の #{user} さんのパスワードは\n" + (newp || "未定義") + "\nです。\n") @O.print @H.p("#{email} 宛に送信しておきました") throw :auth, nil else outputError(msg('pswderror')) @O.print @H.p(sprintf(msg('forgotguide'), @opt['forgot'])) throw :auth, nil end elsif passwd == '' # Create new user from Web-UI newp = pm.setnewpasswd(user, @opt['pswdlen']) @sc.createuser(user, user) sendMail(email, "#{@mybase} new account", sprintf(msg('accessfrom'), ENV['REMOTE_ADDR']) + sprintf(@opt['url']) + "\n" + 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 else outputError(msg('pswderror')) 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.untaint 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.toeuc 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 } 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 dospool(dir, outhandle) seq=1 seqfile=File.expand_path("seq", dir).untaint spooldir=File.expand_path("spool", dir).untaint test(?d, spooldir) or Dir.mkdir(spooldir) if test(?s, seqfile) seq=open(seqfile, "r"){|s|s.gets.to_i} end seq+=1 while test(?s, (newfile=sprintf("%s/%d", spooldir, seq))) open(newfile, "w") do |spoolfile| countdone = nil while line=STDIN.gets if !countdone && /^X-ML-Name: / =~ line line += sprintf("X-Mail-Count: %d\n", seq) coutndone=true end spoolfile.print line outhandle.print line end end open(seqfile, "w"){|s| s.puts seq.to_s} # update `seq' file end def mlseq(dir) open(dir+"/seq", "r"){|s|s.gets.to_i+1} end def sendMail(to, subject, body, from=nil, rcptto=nil, header={}, thru=nil, spoolto=false) body = NKF.nkf("-j", body) unless thru subject = NKF.nkf("-jM", subject.strip) to = safecopy(to) # cleanup tainted address subject.gsub!(/\n/, '') begin if (m=open("|-", "w")) header.each do |h, v| m.printf("%s: %s\n", h.strip, v.strip) end unless thru m.print "To: #{to}\n" from and m.print "From: #{from}\n" m.print "Subject: #{subject}\n" m.puts "Mime-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Type: Text/Plain; charset=iso-2022-jp" m.print "\n" end m.print body, "\n" m.close else # exec(@attr['mail'], "-s", subject, to) recipient = rcptto || [to] #p recipient if spoolto && spoolto.is_a?(String) && proc { require 'fileutils' begin test(?d, spoolto) or FileUtils.mkdir_p(spoolto) test(?w, spoolto) rescue nil end}.call && (tee=open("|-", "w")) # popen should be done in if-condition dospool(spoolto, tee) else if ENV['MAILCMD'] #exec("qmail-inject", "yuuji@gentei.org", "yuuji@koeki-u.ac.jp") open("/tmp/body", "w") {|w| w.print STDIN.readlines.join w.puts "---" w.puts recipient.join(",\n") } exit 0 else #recipient.unshift "-f"+header['return-path'] if header['return-path'] exec(ENV['MAILCMD'] || @opt['sendmail'], *recipient) end end exit 0; end putLog("Sent '#{subject.toeuc}' 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')) + delim + \ if /personal/i =~ @params['displaymode'] @H.a(me+'today_n', msg('normalmode')) else @H.a(me+'today_p', msg('personalmode')) end } 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 = @wnames today = Time.now todayy = today.year todaym = today.month todayd = today.day tdclass = {} tdclass["width"] = "64px" if @oldagent # workaround for NN4 personal = /personal/ =~ @params['displaymode'] headline = @params['headline'] headlinehl = @params['headlinehl'] hldays = headlinehl.to_i * 3600*24 recent = {'class'=>'recent'} monthstr = sprintf "%d/%d", day.year, day.month holiday = Holiday.new # create dayofweek header @O.print @H.elementln("h1", nil){monthstr} # which mode? @O.print @H.p(msg(personal ? 'personalmode' : 'normalmode')) # # display table @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.year==todayy && 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 thisday = first+(d-1)*3600*24 #date = "%d/%d/%d"%[day.year, day.month, d] date = "%d/%d/%d"%[thisday.year, thisday.month, thisday.day] @H.element("p", {'class'=>todayp ? 'todayline' : 'dayline'}){ ##@H.a(@myname+"?-show+"+date, "%4d"%d) @H.a(@myname+"?-show+"+date, "%4d"%thisday.day) } + \ # isHoliday? if hd @H.element("small"){hd.join("<br>")} end.to_s + \ @H.element("p", {'class'=>'topic'}){ s = @sc.day_all(date, @params['user'], personal) if !s.empty? s.keys.sort.collect{|time| s[time].keys.sort.collect{|who| text = escape(s[time][who]['sched']) topic = sprintf "%s%s", time == @opt['alldaydir'] ? '' : time+":", if personal (@params['user'] == who ? "" : nickname(who)+"=") + text ## .split("\n") ##[0] else nickname(who) + \ if headline == 'whole' '=' + text elsif headline == 'head5char' '=' + text.gsub(/\n/, '').sub(/(.{5}).*/, '\1') elsif headline == 'headline' '=' + text.split("\n")[0] end.to_s end if hldays > 0 && (today - s[time][who]['regtime']) < hldays topic = @H.element("span", recent){topic} end topic }.join("<br>") }.join("<br>\n") 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 @H.elementln("form", {'action'=>@myname+"?-month+#{monthstr}", 'method'=>'POST'}){ choice = [ [msg('nameonly'), 'name'], [msg('head5char'), 'head5char'], [msg('headline'), 'headline'], [msg('whole'), 'whole']] msg('display') + \ @H.select('headline', choice, headline) + "/" + \ msg('hldays') + \ @H.select('headlinehl', 0..30, headlinehl) + \ @H.submit("GO", "GO") } @O.print footer ##schedule.day_all("2003/12/22") # @O.print @H.endelement() end # # Put carrying values def hiddenvalues() h = %w[user displaymode].collect{|v| if @params[v] sprintf "<input type=\"hidden\" name=\"%s\" value=\"%s\">\n", v, @params[v] end } h.delete(nil) h.join end def date2ymd(date) %r,(\d\d\d\d+)/(\d\d?)/(\d\d?), =~ date and [$1.to_i, $2.to_i, $3.to_i] end # # Return the string of table def dayTableString(user, datestr, range, personal = nil) #s = @sc.day_all(date, user, personal) #return '' if s.empty? r = '' header = @H.startelement("table", {'border'=>'1'}, true) day = Time.mktime(*date2ymd(datestr)) i = -1 while (i+=1) < range d = Time.at(day+i*3600*24) date = sprintf("%04d/%02d/%02d", d.year, d.month, d.day) datewn = @H.element("span", {'class'=>@wnames[d.wday]}){ sprintf("%s(%s)", date, @msg['wnames'][@lang][d.wday]) } s = @sc.day_all(date, user, personal) next if s.empty? r << @H.element("tr", nil){ @H.element("th", {'class'=>'time'}){'TIME'} + \ @H.element("th", nil){'Who - '+datewn+' - What'} } for time in s.keys.sort 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"){ if editable @H.a(@myname+"?-move+#{date}/#{time}/#{who}", msg('move')) else @opt['tdskip'] end } + \ @H.element("td"){escape(s[time][who]['sched'])} } }.join("\n") } } r << @H.endelement() end end footer = @H.endelement() if r > '' header + r + footer else '' end end def dayTextString(user, datestr, range, personal = nil) r = '' cols = 20 header = "-" * cols + "\n" day = Time.mktime(*date2ymd(datestr)) i = -1 while (i+=1) < range d = Time.at(day+i*3600*24) date = sprintf("%04d/%02d/%02d", d.year, d.month, d.day) datewn = sprintf("%s(%s)", date, @msg['wnames'][@lang][d.wday]) s = @sc.day_all(date, user, personal) next if s.empty? r << sprintf("TIME Who %s - What\n", datewn) for time in s.keys.sort tstr = case time when @opt['alldaydir'] msg('allday') else sprintf "%02d:%02d", time.to_i/100, time.to_i%100 end r << s[time].keys.collect{|who| editable = (user==who || @sc.ismember(user, who)) groupp = grepgroup(who) sprintf("%-5s %-10s %s", tstr, nickname(who), escape(s[time][who]['sched'])) }.join("\n") + "\n" end r << "-" * cols + "\n" end footer = "That's all\n" if r > '' header + r + footer else '' end end # # new form def displayRegistForm(date, multiple = true) # # Link button to add new plan #now = Time.now+3600*24 thisyear, thismonth, thisday = date.scan(%r,(\d\d\d\d+)/(\d+)/(\d+),)[0] user = @params['user'] 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 nextweek = Time.at(now+3600*24*7) ey, em, ed = nextweek.year, nextweek.month, nextweek.day rcsp = (multiple ? {'colspan'=>'2'} : nil) wnames = @msg['wnames'][@lang] wnames << @msg['everyday'][@lang] @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'}){ border1 = {'border'=>'1'} border1c = {'border'=>'1', 'class'=>'c'} mygroup = @sc.groups().select{|g|@sc.ismember(user, g)} @H.elementln('table', border1){ @H.elementln('tr'){ @H.element('th'){'Name'} + \ @H.element('td', rcsp){ hiddenvalues() + @sc.nickname(user) } } + \ @H.elementln('tr'){ @H.element('th'){'Year'} + \ @H.element('td'){@H.select("year", y..y+5, y)} + \ if multiple @H.element('td'){ d1 = msg('singleday') msg('through')+@H.select("endyear", [d1]+(y..y+5).to_a, d1) } end } + \ @H.elementln('tr'){ @H.element('th'){'Month'} + \ @H.element('td'){@H.select("month", 1..12, m)} + \ if multiple @H.element('td'){ msg('through')+@H.select("endmonth", 1..12, em) } end } + \ @H.elementln('tr'){ @H.element('th'){'Day'} + \ @H.element('td'){@H.select("day", 1..31, d)} + \ if multiple @H.element('td'){ msg('through')+@H.select("endday", 1..31, ed) } end } + \ if multiple @H.elementln('tr'){ @H.element('th'){ msg('whichday') } + \ @H.element('td', rcsp){ @H.elementln('table', border1c){ @H.element('tr'){ i=-1 wnames.collect{|w| @H.element('td'){ i+=1 @H.radio('whichday', i.to_s, '', i==wnames.length-1) } }.join("\n") } + \ @H.element('tr'){ i=-1 wnames.collect{|w| @H.element('td'){w} }.join } } } } end + \ @H.elementln('tr'){ @H.element('th'){'Time<br>'+ \ sprintf(msg('24hour'), @opt['alldaydir'])} + \ @H.element('td', rcsp){ '<input type=text name="time" value="3000" size=8 maxlength="4">' } } + \ @H.elementln('tr'){ @H.element('th'){msg('publicok')} + \ @H.element('td', rcsp){ @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| # Actual variables of notifylist for submitting is "sub_"+n @H.checkbox("sub_"+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.radio('editmode', 'remove', 'Delete?') + " / " + \ @H.radio('editmode', 'modify', 'Overwrite?') + " / " + \ @H.radio('editmode', 'append', 'Append?', true) + "<br>\n" + \ @H.element("p"){msg('headsched') + "<br>\n" + \ @H.element("textarea", @schedulearea){}} + # textarea @H.submit_reset("GO") } #form end # # show the schedule list of specified date # def show(date) if !checkauth return nil end user = safecopy(@params['user']) personal = (/personal/i =~ @params['displaymode']) @params['displaydays'] = @params['displaydays'] || @cookie['displaydays'] days = @params['displaydays'].to_i days = (days > 0 ? days : 3) # str = @sc.day_all(date, user, personal) outstr = dayTableString(user, date, days, personal) @O.print @H.element("h1", nil){ sprintf msg('fmtdaysschedule'), date } @O.print @H.element("h2"){msg('schedtable')} ## @O.print @H.p() @O.print @H.elementln("form", {'action'=>@myname+"?-show+#{date}", 'method'=>'POST'}){ @H.elementln("p"){ msg(personal ? 'personalmode' : 'normalmode') + "<br>" + \ @H.select("displaydays", 1..30, days) + msg('daystodisplay') + \ @H.submit("GO", "GO") } } if outstr > '' @O.print outstr else @O.print @H.p(msg('noplan')) 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)) # # Display registration form displayRegistForm(date) @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 tday= Time.at(base-seconds) target = [tday.year, tday.month, tday.day, @opt['night'].to_i] targetnight = Time.mktime(*target) 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) return nil unless @opt['notifymail'] 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| # @params[k]s are always defined in cookies, so we use @params["sub_"+k] @params[k] = @params["sub_"+k] 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 def commit_schedule(who, y, m, d, timedir, text, repl, pub) end def regulate_time(y, m, d, tm) if tm > 2399 sh, smin = 23, 59 timedir=@opt['alldaydir'] 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(y, m, d, 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 [time, timedir, tmstr] 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) if (gr=grepgroup(as)) registerer = gr end 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 time, timedir, tmstr = regulate_time(sy, sm, sd, tm) # # Check continuous schedule registration wwday = @params['whichday'].to_i if @params['endyear'] && @params['endmonth'] && @params['endday'] && (ey=@params['endyear'].to_i) > 0 && (em=@params['endmonth'].to_i) > 0 && (ed=@params['endday'].to_i) > 0 daylist = [] endtime = Time.mktime(ey, em, ed, 23, 59) ti = time begin if wwday==7 || wwday==ti.wday daylist << [ti.year, ti.month, ti.day] end end while (ti=Time.at(ti+3600*24)) <= endtime else daylist = [[sy, sm, sd]] end if !remove && !(@params['schedule'] && @params['schedule'].strip > '') outputError msg('putsomething') return nil end for y, m, d in daylist # do remove or addition if remove cancel_notify(registerer, y, m, d, timedir) begin @sc.remove(registerer, y, m, d, 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'].toeuc.strip.gsub(/\r+\n/, $/)) << "\n" # text = purify(text) STDERR.print text replace = (/modify/i =~ @params['editmode']) rc = @sc.register(registerer, y, m, d, timedir, text, replace) if @params['pub'] && /yes/ =~ @params['pub'] @sc.putfile(registerer, y, m, d, timedir, 'pub', "1\n") else @sc.removefile(registerer, y, m, d, timedir, 'pub') end ######## @O.print @H.p(msg('appended')) if rc == 1 rescue outputError("Failed"+$!) end text = @sc.getschedule(registerer, y, m, d, timedir) reg_notify(registerer, y, m, d, timedir, text) end end if !remove && @params['rightnow'] && /yes/i =~ @params['rightnow'] header = sprintf("%s\n%s/%s/%s%s %s %s\n%s%s%s\n%s\n", @opt['url'], sy, sm, sd, if daylist.length > 1 "-%s/%s/%s" % daylist[-1] end, 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 unless @mailmode show(sprintf("%04d/%02d/%02d", sy, sm, sd)) @O.print "add_remove" if user == @author end end # add def addsched() if "move" == @params['editmode'] add_remove(:remove) for p in %w(year month day time) do @params[p] = @params["new"+p] end end add_remove(/^remove/i =~ @params['editmode']) end # # Display remove or modify screen def remove_modify(datetime, editmode) 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 ## text = decode(text) @O.print @H.elementln("h1"){ sprintf "%s %s", datetime, msg(editmode) } @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 + \ if editmode=="move" @H.elementln("table") { @H.elementln("tr", {"colspan" => "2"}) {msg('newdate')} + \ @H.elementln("tr") { @H.element("th"){"Year"} + \ @H.element("td"){@H.select("newyear", y.to_i..y.to_i+5, y)} } + \ @H.elementln("tr") { @H.element("th"){"Month"} + \ @H.element("td"){@H.select("newmonth", 1..12, m)} } + \ @H.elementln("tr") { @H.element("th"){"Day"} + \ @H.element("td"){@H.select("newday", 1..31, d)} } + \ @H.elementln("tr") { @H.element("th"){"Time"} + \ @H.element("td"){ "<input type=text name=\"newtime\" value=\"#{time}\" " + \ "size=\"8\" maxlength=\"4\">" } } } end.to_s + \ @H.elementln("div", {"style" => "visibility: " + (editmode=="move" ? "hidden" : "show") + "\""}) { 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'), editmode=="modify")+' / '+\ @H.radio("editmode", "remove", msg('remove'), editmode=="remove")+' / '+\ @H.radio("editmode", "move", msg('move'), editmode=="move") + ' / ' + \ "<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, "remove") end def modify(datetime) remove_modify(datetime, "modify") end def move(datetime) remove_modify(datetime, "move") 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 ddiff=(Time.mktime(y, m, d) \ - Time.mktime(now.year, now.month, now.day))/3600/24 if t > 2359 hhmm = msg('allday') if ddiff > 1 comment = "%d%s" % [ddiff, msg('days', 'before')] else comment = msg(now.hour > 18 ? 'precedingday' : 'theday') end else hhmm = sprintf "%02d:%02d", t/100, t%100 diff = Time.mktime(y, m, d, t/100, t%100) - now if diff < 7200 comment = "%d%s" % [diff/60, msg('minutes', 'before')] elsif (ddiff == 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 # ML functions def parseaddress(spec) # from catchup.rb # Return [email, comment] # nil if comment does not exitst. if /(.*)\s*<(.*)>/ =~ spec then [$2, $1.strip] elsif /(.*)\s*\((.*)\)/ =~ spec then [$1.strip, $2] else [spec.strip, nil] end end def rewritefrom(email, comment, newseed) # from catchup.rb # no need to setcomment here because if comment set, it's enough comment.sub!(/(\"?)(.*)\1/, '\2') comment += "/" if comment>"" return comment.gsub(/([^\x00-\x7f]+)/){NKF.nkf('-jM', $1)} + email.sub("@", "=")+" <"+newseed+">" # end end def tagify_subj(body, tag, removeregexp, fromhack = nil) # This method should be generic for other headers than `Subject'? hold = [] ret = [] skip = false while line = body.shift case line.toeuc when /^$/ hold << "\n" break when /^(subject|from): /i skip = $1.downcase ret += hold hold = [line] when /^\s/ # continued line hold << line else if skip=="subject" sj = hold.join.toeuc.sub("Subject: ", "").gsub(tag, "").strip sj.gsub!(removeregexp, "") sj = sj.sub(/^(re: *)+/i, "Re: ").gsub("\n", "") ret << "Subject: "+NKF.nkf('-jM', tag+" "+sj).strip+"\n" elsif skip=="from" && fromhack.is_a?(String) from = hold.join.toeuc.sub(/From: */i, "").strip email, comment = parseaddress(from) ret << "From: "+rewritefrom(email, comment, fromhack)+"\n" else ret += hold end skip = false hold = [line] end end ret + hold + body end def list() # For debug: # LOCAL=1 DEFAULT=name ./after5.rb -list # $DEFAULT is ML name viamail = ENV['LOCAL'] && ENV['DEFAULT'] # called via mail from = nil unless @opt['mailprefix'] && @opt['maildomain'] if viamail STDERR.print msg('sendall_err') % [@opt['conf']] exit 0 else @O.print @H.elementln("pre"){msg('sendall_err') % [@opt['conf']]} return true end end if viamail then prohibitviahttp() name = ENV['DEFAULT'] else # via http return nil unless checkauth name = @params['name'].untaint nick = @sc.nickname(@params['user']) from = sprintf("%s <%s>", nick, @params['user']) body = @params['body'].gsub("\r", "").untaint end fromhack = @sc.getgroupattr(name, 'fromhack') mldir = "ml/"+name prefix = (@opt['mailprefix'] || "") dash = prefix > '' ? "-" : "" to = sprintf("%s%s%s@%s", prefix, dash, name, @opt['maildomain']) subj = @params['subject'] || "Message from "+@myname sjtag = @opt['mailbracket'].gsub("%n", nickname(name)). gsub("%i", name). gsub(/%(\d*)c/){("%0"+$1+"d") % [mlseq(mldir)]} tagpt = Regexp.quote(@opt['mailbracket']). # compute bracket pattern gsub("%n", Regexp.quote(nickname(name))). gsub("%i", Regexp.quote(name)). gsub(/%(\d*)c/, '\d+') tagre = Regexp.new(tagpt) subj = sjtag.strip+" "+subj.gsub(Regexp.new(tagpt), "") if viamail then body = tagify_subj(STDIN.readlines, sjtag, tagre, fromhack ? to : nil).join elsif fromhack from = rewritefrom(@params['user'], nick, to) end header = { "Reply-to" => to, "X-ML-Driver" => ($hgid || @myname), "X-ML-Driver-URI" => $myurl, "X-ML-Name" => name, "Errors-to" => @opt['maintainer'], "Return-path" => @opt['maintainer']} Dir.chdir @mydir rcpts = if grepgroup(name) @sc.members(name) else [name] end.collect {|u| mailaddress(u, name)} # p rcpts # p to sendMail(to, subj, body, from, rcpts, header, ENV['SENDER'], @opt['mlspooling'] ? mldir : nil) if !viamail then @O.print @H.elementln("h1"){msg('sendall_done')} @O.print @H.p(sprintf(msg('sendall_head'), nickname(name))+" "+msg('done')) link2home() @O.print footer() return true end exit 0 end def listdraft(name) return nil unless checkauth return nil unless name unless @opt['mailprefix'] && @opt['maildomain'] @O.print @H.elementln("pre"){msg('sendall_err') % [@opt['conf']]} return true end user=@params['user'] nickname = @sc.nickname(user) @O.print @H.elementln("h1") { @mybase+' '+msg('sendall').sub("<br>", " ") } @O.print @H.elementln("h2") { sprintf(msg('sendall_head'), nickname(name)) } @O.print @H.p(sprintf("(%s: %s)", msg('member'), @sc.membernames(name).join(", "))) @O.print \ @H.elementln("form", {'action' => @myname+'?-list', 'method'=>"POST"}) { @H.elementln("table"){ @H.elementln("tr"){ @H.element("td"){"Subject"} + \ @H.element("td"){ @H.text("subject", "", 40, 128) } } + \ @H.elementln("tr"){ @H.element("td"){ msg('body') } + \ @H.element("td"){ @H.element("textarea", @schedulearea.merge({"name"=>"body"})){} } } } + # </table> @H.hidden("name", name) + @H.submit("send", "SEND") + @H.reset("clear", "Clear") } @O.print @H.p(msg('sendall_note')) end # put Link to home def link2home() @O.print @H.p("-> " + @H.a(@myname+"?-today", "Home")) 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 link2home 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.sort.collect{|g, ghash| memberp = @sc.ismember(user, g) @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"){ 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"){ @H.element("div", {'class'=>'memlist5'}){ memlist = ghash['members'] if memberp # move this user to the beginning of list memlist.delete(user) memlist.unshift(user) end memlist.collect{|u| @sc.nickname(u) + \ ((u == user) ? ("("+@H.text("mail4-#{g}", memberp, 30, 80)+")") : "") }.join(", ") } } + \ @H.element("td"){ @H.a(@myname+"?-listdraft+#{g}", msg('sendall')) } } }.join("\n") } + \ '' + \ @H.p(msg('groupwarn', 'shortnameplz')) + \ @H.submit_reset("GO") } # form end def groupnamesString() @H.elementln("p", {'class'=>'listup'}){ @sc.groups().collect{|g|@sc.groupname(g)}.join(", ") } 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 member, change group-specific mailto address. key = "mail4-#{grp}" if memberp && @params[key] && memberp != @params[key] @sc.addgroup(grp, [[user, @params[key]]]) newmemp = @sc.ismember(user, grp) @O.print @H.elementln("p") { sprintf("%s `%s' %s => %s%s", msg('group'), grp, msg('mailaddress'), @params[key], @params[key]==mailaddress(user) ? "(same)" : "") } end # # as a owner, change the name of group if @sc.isadmin(user, grp) && (newname = @params["groupname-#{grp}"]) && @sc.groupname(grp) != newname @O.printf "@sc.name2group=%s<br>\n", @sc.name2group(newname) if dupl=@sc.name2group(newname) @O.print @H.p(sprintf(msg('dupname'), newname)) @O.print groupnamesString() else @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 link2home 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.sort 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("div", {'class'=>'memlist'}){ @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().sort.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.checkbox("fromhack", "yes", msg('fromhack'), @sc.getgroupattr(group, 'fromhack')) + "<br>\n" + \ @H.submit_reset("GO") } # form @O.print @H.p(@H.element("span", warnclass){"(*)"}+ msg('recursewarn')) if warnp if group && (members = @sc.members(group))[0] @O.print @H.p(sprintf(msg('wholemembers'), group)) @O.print @H.elementln("p", {'class'=>'listup'}){ members.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') } resmsg = @sc.destroygroup(group) ? msg("done") : msg("failure") @O.print @H.p(resmsg) putLog("Delete group '#{group}' #{resmsg}\n") @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') } resmsg = deleteuser(user) ? msg("done") : msg("failure") @O.print @H.p(resmsg) putLog("Delete user '#{user}' #{resmsg}\n") @O.print @H.p(@H.a(@myname, msg('login'))) 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() u = @sc.isuser(u) # users() value is considered tainted. next unless u # Use registered value in @sc. 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"){ putLog(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"){ putLog(sprintf("%s [%s] %s %s", msg('group'), g, joinp ? msg('addedtogroup') : msg('removedfromgp'), group)) } end end end # groups # Change parameter(s) # To be more generic... fhsetp = (@params['fromhack'] && /^yes/i =~ @params['fromhack']) cusetp = (@sc.getgroupattr(group, 'fromhack')!=nil) if cusetp ^ fhsetp @sc.putgroupattr(group, 'fromhack', @params['fromhack']) @O.print @H.elementln("p") { putLog(sprintf("%s[fromhack] -> %s", group, @params['fromhack'].inspect)) } somethingdone = true end unless somethingdone # @O.print @H.p(msg('nothingtodo')) end # @O.print footer() link2home end def newgroupsub() return nil unless checkauth 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 if dupl=@sc.name2group(newgname) @O.print @H.p(sprintf(msg('dupname'), newgname)) @O.print groupnamesString() return nil end @sc.creategroup(newgroup, newgname, [user]) && putLog("New group '#{newgroup}'(#{newgname}) created\n") admgroupsub() end # # Methods Related to viaMail functions def gen_sessionpswd() end def viamail_registform() c = "# " nl = "\n" user = @params['user'] msg('addsched') + "-" * 20 + nl*2 + \ c + msg('user') + nl + \ "user=" + user + nl*2 + \ c + msg('sessionpswd') + nl + \ "sp=hoge" + nl*2 + \ c + msg('date') + nl + \ "date="+Time.now.strftime("%Y/%m/%d") + nl*2 + \ c + msg('time') + sprintf(msg('24hourtxt'), @opt['alldaydir']) + nl + \ "time=3000"+nl*2 + \ c + msg('publicp') + nl + \ "public=yes" + nl*2 + \ c + msg('neednotify') + nl + \ "nt10m=yes (%s) nttoday=yes (%s) nt1d=yes (%s) nt7d=yes (%s)" % ["10"+msg('minutes')+msg('before'), msg('theday'), msg('precedingday'), "7"+msg('days')+msg('before')] + nl*2 + \ c + msg('schedulehere') end def viamail_footer() viamail_registform() end def show_by_text(date, days) user = @params['user'] personal = true sched = dayTextString(user, date, days, personal) # @O.print outstr sendMail(mailaddress(user), "After5 Schedule", @opt['url'] + "\n" + \ Time.now.strftime("%Y/%m/%d") + \ sprintf(msg('schedlist'), days) + "\n\n" + \ if sched > '' sched else msg('noplan')+"\n" end + \ viamail_footer ) end def parseHeader contline=nil header=Hash.new text=Array.new field=nil # header while line=STDIN.gets text << line break if /^$/ =~ line if /^\s+/ =~ line if field header[field][-1] << line end else if /^([^:]+):\s*(.*)/ =~ line field=$1.downcase header[field] or header[field] = [] header[field] << $2 end end end header end def mail_regsched() @params = Hash.new # Reset reqparams = %w[user sp date time public] otherparams = %w[nt10m nttoday nt1d nt7d] setall = lambda{ reqparams.each{|key| return false unless @params.has_key?(key)} return true } stack = "" while line=gets # !setall.call && line=gets if /^(\S+)=(.*)/ =~ line next unless reqparams.index($1) || otherparams.index($1) @params[$1] = $2 #if reqparams.index($1) STDERR.print "Set #{$1} to #{$2}\n" #end buf = "" elsif /^\s*\#|^$/ =~ line # skip comments else buf += line end end unless setall.call STDERR.print "Insufficient variables\n" exit 1 end p buf y, m, d = date2ymd(@params["date"]) @params["year"] = y @params["month"] = m @params["day"] = d @params["schedule"] = buf @params["editmode"] = "modify" @params["sessionpw"] = @params["sp"] p @params add_remove() end def mail_getsched() user = nil while bline=gets if /(\S+@\S+)/ =~ bline break if user=@sc.isuser($1) end end unless user sendMail(@opt['maintainer'], "viaMail Request Error", "This is `#{@mybase}' in #{@mydir}\n" + "Invalid schedule request from #{ENV['SENDER']}.\n\n") exit 1 end today = Time.now.strftime("%Y/%m/%d") days = 7 if bline=gets if /\d+/ =~ bline days = bline.to_i end end # Send user to schedules of today and near future @params['user'] = user show_by_text(today, days) end def doMail() days = 7 # Confirm `via Mail' prohibitviahttp() @H = TEXTout.new unless ENV['RECIPIENT'] && ENV['SENDER'] STDERR.print "Call me via qmail\n" exit 1 end @mailmode = true header = parseHeader # is this necessary? if /regist/ =~ ENV["EXT"] mail_regsched() else mail_getsched() end end # # Password related Methos 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 email = nil if /(.*@.*)=(.*@.*)/ =~ user user, email = $1, $2 end newpwd = pm.setnewpasswd(user, 4) @sc.createuser(user, email) 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) @sc.deleteuser(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| line = line.toeuc 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|false|0|off)$/io @opt[key] = nil else @opt[key] = value.untaint end print "#{key} set to #{value}\n" if $DEBUG end } rescue STDERR.printf("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 "-stream" # ARGV.shift # @job = 'show_by_text "2005/1/18"' @job = 'doMail' when "-addsched" @job = "addsched" when "-today" @job = "today" when "-today_p" argument['displaymode'] = 'personal' @job = "today" when "-today_n" argument['displaymode'] = 'normal' @job = "today" when "-remove" ARGV.shift @job = 'remove "'+parsedate(ARGV[0])+'"' when "-move" ARGV.shift @job = 'move "'+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 "-list" @job = 'list' # + exit when "-newgroup" @job = 'newgroup' when /^-(admgroup|listdraft)$/ ARGV.shift gr = safecopy(grepgroup(ARGV[0])) ##gr.untaint @job = safecopy($1)+' "'+gr+'"' when "-admgroupsub" @job = 'admgroupsub' when "-newgroupsub" @job = 'newgroupsub' when "-delusersub" ARGV.shift usr = safecopy(users().grep(ARGV[0])[0]) @job = 'delusersub "'+usr+'"' when "-delgroupsub" ARGV.shift gr = safecopy(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 = {} return cookie unless ENV['HTTP_COOKIE'] #if /value=(.*)/ =~ ENV['HTTP_COOKIE'] for cv in ENV['HTTP_COOKIE'].split(/[\; ]+/).grep(/(value|prefs)=(.*)/) # value=$1.gsub!(/%(..)/){[$1.hex].pack("c")} next unless /\w+=(.*)/ =~ cv value=decode!($1) next unless value 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 $KCODE='e' if RUBY_VERSION < "1.9" After5.new.doit if __FILE__ == $0 end # Local variables: # buffer-file-coding-system: euc-jp # End: