Mercurial > hgrepos > hgweb.cgi > after5
view after5.rb @ 84:f67f5304baac draft
Add feature of sending message to a user
author | HIROSE Yuuji <yuuji@gentei.org> |
---|---|
date | Thu, 12 Dec 2013 19:57:20 +0900 |
parents | 54d85f7d5676 |
children | e2b6a2e8b5c7 |
line wrap: on
line source
#!/usr/local/bin/ruby19 # -*- coding: euc-jp -*- # # Associative Scheduling Table - after5 # (C)2003, 2004, 2006, 2008, 2012, 2013 by HIROSE Yuuji [yuuji<at>gentei.org] # $Id: after5.rb,v 1.20 2012/12/03 15:54:20 yuuji Exp $ # Last modified Thu Dec 12 18:09:01 2013 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() 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) length = length.to_i length = 4 if length < 4 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, 0750) 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 ismembersemail(email) @usermap.keys.each {|u| return u if u==email return u if mailaddress(u).split(/,\s*|\s+/).grep(email)[0] } nil 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.uniq 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) if isgroup(grp) members(grp).collect{|u| nickname(u)} else [nickname(grp)] end 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).untaint next unless test(?d, ud) ntl[user] = {} unless ntl.has_key?(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 @mailadmdelimiter = "/" @mailadmsuffix = @mailadmdelimiter + "adm" @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]', } @subjtags = [['[GroupID:#]', "[%i:%c]"], ['[GroupID:#####]', "[%i:%5c]"], ['[GroupName:#]', "[%n:%c]"], ['[GroupName:#####]', "[%n:%5c]"], ['(GroupID:#)', "(%i:%c)"], ['(GroupID:#####)', "(%i:%5c)"], ['(GroupName:#)', "(%n:%c)"], ['(GroupName:#####)', "(%n:%5c)"], ['NONE', "NONE"]] ##@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) @ntlist = [ # this shoud be set after @lang ['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')], ] 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 to anyone who has account of this board?'], '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' => ["身に覚えのない場合はMLへの代理登録の可能性があります。 上記URLが見慣れたものならばアクセスしてみるか、 このURLの管理人(%s さん)に問い合わせてみて下さい。 それらも心当たりのない場合はいたずらです。対処しますので管理人まで御連絡下さい。", 'If you have no idea of the reason for getting this message, '+ 'it may be a invitation of mailing list from your friend. Please try to access URL above if it is familiar one or contact to the administrator of the site(is %s). If you have completely no clue for this invitation, it might be a mischief by someone else. Please notice the fact to the administrator. Thank you.'], 'user' => ['ユーザ', 'user'], 'group' => ['グループ', 'group'], 'mladdress' => ['公開MLアドレス(%s以外にしたい場合設定する)', "Public ML address(if you set to diffrent address than `%s')"], 'fromhack' => ['ML配送時のFrom:を常にMLのアドレスにする (From:にしか返さないケータイ参加者が多いときにオススメ)', 'Set From: address of all ML messages to ML address, which is convenient to keep responses from cellular phones surely to ML address. Most cellular phones tend to return only from: address.'], 'inviteonly' => ['管理者のみ加入操作可能', 'Only administrators can add new members.'], 'invite-error' => ['%s への加入はグループ管理者のみが操作できます。', "Only administrator of this group(`%s') can add you."], 'limitsender' => ['アカウント保持者のみ送信可能', 'Allow only account holders to post to ML'], 'xmlname' => ['X-ML-Nameヘッダの値("%s" 以外にしたい場合設定する)', 'Value of X-ML-Name header ("%s" for default)'], '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'], 'operation' => ['操作', 'operation'], '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'], 'multipleok' => ['<br>(スペースで区切って複数指定可)', '<br>(Two or more addresses are OK by delimiting with space.)'], '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'], 'aboutgroup' => ['グループ %s の操作', "Operations on group `%s'"], '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.'], 'address2send' => ['自分が参加しているグループのメンバーリストの先頭が自分。その直後にある入力欄には、そのML宛メッセージをどの宛先に配送するかを入れられる。そう、MLごとに自分への配送先を変えられるよ。', 'The first entry of member list of a group to which you belongs, is you. Entry box just after your name is for address list you want to deliver messages to that ML. Thus, you can define different addresses for each ML.'], '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."], '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 quoted(string) NKF.nkf('-eMQ', string) end def unquoted(string) NKF.nkf('-emQ', string) 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(altaction = nil) @O.print @H.elementln("h1", nil){msg('title')} @O.print @H.elementln("h2", nil){msg('login')} format = {'method'=>'POST', 'action'=> @myname+"?" +(altaction || "-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'))) login(@oldargv.join('+')) 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'].to_i) 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) putLog("New user [#{user}] created\n") sendMail(@opt['maintainer'], "After5 New User", sprintf("URL=%s\nREMOTE_ADDR=%s\nuser=%s", @opt['url'], ENV['REMOTE_ADDR'], 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.toeuc 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) test(?s, (seqfile = dir+"/seq")) ? open(seqfile, "r"){|s|s.gets.to_i+1} : 1 end def sendMail(to, subject, body, from=nil, rcptto=nil, header={}, thru=nil, spoolto=false) # rcptto should be an Array 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.puts "Date: #{Time.now.strftime("%a, %d %b %Y %T %z")}" m.print "\n" end m.print body, "\n" m.close else # exec(@attr['mail'], "-s", subject, to) recipient = rcptto || to.split(/,\s*|\s+/) #p recipient File.umask(027) if spoolto && spoolto.is_a?(String) && proc { require 'fileutils' begin test(?d, spoolto) or FileUtils.mkdir_p(spoolto, :mode => 0750) 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'].gsub(".", "<span>.</span>"). sub('@', "@") + \ '<span style="display: none;">.cut.here</span>' + \ "." } 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 thisday = first+(d-1)*3600*24 hd = holiday.isHoliday(thisday.year, thisday.month, thisday.day, wd) tdclass['class'] = (hd ? 'holiday' : wname[wd]) @H.element("td", tdclass){ if d>0 #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 ntlist = @sc.notify_list(now) p "notifylist", ntlist if $DEBUG ntlist.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 when /^(\S+): /i # if new header comes if /^subject:/i =~ hold[0] # check previous header in hold space sj = hold.join.toeuc.sub("Subject: ", "").gsub(tag, "").strip removeregexp && sj && sj.gsub!(removeregexp, "") sj = sj.sub(/^(re: *)+/i, "Re: ").gsub("\n", "") hold = ["Subject: "+NKF.nkf('-jM', tag+" "+sj).strip+"\n"] elsif /^from/i =~ hold[0] && fromhack.is_a?(String) from = hold.join.toeuc.sub(/From: */i, "").strip email, comment = parseaddress(from) if (!comment || comment=="") && comment = @sc.ismembersemail(email) # Reverse conversion of uname<->email comment = @sc.nickname(comment) || "" end hold = ["From: "+rewritefrom(email, comment, fromhack)+"\n"] end ret += hold hold = [line] when /^\s/ # continued line hold << line end end ret + hold + body end def defaultmladdress(name) prefix = (@opt['mailprefix'] || "") dash = prefix > '' ? "-" : "" sprintf("%s%s%s@%s", prefix, dash, name, @opt['maildomain']) 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 = toadmin = groupmode = fromhack = 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 = unquoted(ENV['DEFAULT']) if Regexp.new("(.*)("+Regexp.quote(@mailadmsuffix)+")") =~ name # To: GROUP/adm*@domain # -> Forward to group administrator(s) name, toadmin = $1, $2 sendMail("dummy", 'dummy', # Original To: and Subject: go through STDIN.readlines.join, nil, @sc.admins(name), {"Return-path" => @opt['maintainer']}, :thru) exit 0 end if @sc.isuser(name) # groupmode = nil # First, compare with username # groupmode = nil # Then, check group name elsif grepgroup(name) groupmode = true else # not found sendMail(@opt['maintainer'], "no group", sprintf("Invalid group address: %s(%s@%s)\nSent by %s\n" + "URL: %s\n------------\n", name, ENV['LOCAL'], ENV['HOST'], ENV['SENDER'], @opt['url']) + "> "+STDIN.readlines.join("> ")) exit 0 # should exit 0 in mail mode end else # via http return nil unless checkauth name = unquoted(@params['name'].untaint) if @sc.isuser(name) # groupmode = nil elsif grepgroup(name) groupmode = true else @O.print @H.p("No such group: #{name}") return true end nick = @sc.nickname(@params['user']) from = sprintf("%s <%s>", nick, @params['user']) body = @params['body'].gsub("\r", "").untaint end # Set values for header rewriting if groupmode # Run as ML bracket = @sc.getgroupattr(name, 'subjtag') || @opt['mailbracket'] xmlname = @sc.getgroupattr(name, 'xmlname') || name mldir = "ml/"+name to = @sc.getgroupattr(name, 'mladdress') || defaultmladdress(name) if @sc.getgroupattr(name, 'fromhack') fromhack = to end spooling = @opt['mlspooling'] else # Run as p2p mail bracket = "NONE" # Throught Subject user = @params['user'] || ENV['SENDER'] if @sc.ismembersemail(user) sender = defaultmladdress(quoted(user)) else sender = user end ###fromhack = sprintf("%s <%s>", nick, sender) fromhack = sender xmlname = name to = name spooling = mldir = nil end returnpath = to.sub("@", @mailadmsuffix+"-@") adminaddr = to.sub("@", @mailadmsuffix+"@") subj = @params['subject'] || "Message from "+@myname if bracket == "NONE" sjtag = "" tagre = nil else sjtag = bracket.gsub("%n", nickname(name)). gsub("%i", name). gsub(/%(\d*)c/){("%0"+$1+"d") % [mlseq(mldir)]} tagpt = Regexp.quote(bracket). # 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), "") end if viamail then body = tagify_subj(STDIN.readlines, sjtag, tagre, fromhack).join elsif fromhack # via http from = rewritefrom(@params['user'], nick, groupmode ? to : sender) end header = { "X-ML-Driver" => ($hgid || @myname), "X-ML-Driver-URI" => $myurl, } if groupmode header["Reply-to"] = to header["X-ML-Name"] = xmlname header["X-ML-URI"] = sprintf("%s?-groupman+%s", @opt['url'], name) header["Return-path"] = returnpath end Dir.chdir @mydir if groupmode rcpts = if grepgroup(name) @sc.members(name) else [name] end.collect {|u| mailaddress(u, name).split(/,\s*|\s+/)}.flatten else rcpts = @sc.mailaddress(name).split(/,\s*|\s+/).flatten rcpts += @sc.mailaddress(user).split(/,\s*|\s+/).flatten # +sender end ENV["QMAILINJECT"] = "r" # for ML mode, use verp # # On mail mode, check if sender can send message to list. if viamail && @sc.getgroupattr(name, 'limitsender') s = ENV['SENDER'] if !catch(:senderok) { throw :senderok, true if rcpts.grep(s)[0] throw :senderok, true if @sc.ismembersemail(s) } # sender is not allowed to send to ML sendMail(s, "You are not allowed to send to this ML", ("Before posting to this list(%s),\n"+ "subscribe to %s") % [to, @opt['url']], adminaddr, nil, {"Return-path" => returnpath}) exit 0 end end # # OK to send, go ahead sendMail(to, subj, body, from, rcpts, header, ENV['SENDER'], spooling ? 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) groupmode = @sc.isgroup(name) @O.print @H.elementln("h1") { @mybase+' '+msg('sendall').sub("<br>", " ") } @O.print @H.elementln("h2") { sprintf(msg('sendall_head'), nickname(name)) } list = groupmode ? @sc.members(name) : [name, user] @O.print @H.p(sprintf("%s: %s", msg('member'), list.collect {|u| @H.element("abbr", "title"=>u){ @sc.nickname(u) } }.join(",\n"))) @O.print @H.p(sprintf("(total %d)", list.length))+"\n" @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", {"class" => "border"}){ @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', 'multipleok')} + \ @H.element("td") { @H.text("newmail", mailaddress(user), @opt['size'], 180) } } + \ @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>' + \ @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(grp = nil) 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 if grp && group = grepgroup(grp) @O.print @H.elementln("h2"){ sprintf(msg('aboutgroup'), group) } grmap = {group => grmap[group]} end @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", {'class'=>'border'}){ grmap.sort.collect{|g, ghash| memberp = @sc.ismember(user, g) adminp = @sc.isadmin(user, g) @H.elementln("tr"){ @H.element("td", adminp ? admclass : nil){ g + "<br>("+@sc.members(g).length.to_s+")" } + \ @H.element("td"){ @H.element("div", {'class'=>'c'}) { if adminp @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, 12) else ghash['name'] end + '<br>' + \ # If this group is inviteonly and the user is not a member, # one cannot join. if memberp && adminp || !@sc.getgroupattr(g, 'inviteonly') @H.radio("groupadd-#{g}", "yes", "IN", memberp) + " / " + \ @H.radio("groupadd-#{g}", "no", "OUT", !memberp) else @H.element("small"){"("+msg('inviteonly')+")"} end } + \ @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| if u == user @sc.nickname(u) + \ "("+@H.text("mail4-#{g}", memberp, 30, 180)+")" else @H.a(@myname+"?-listdraft+#{u}", @H.element("abbr", "title"=>u) { @sc.nickname(u) }) end }.join(", ") } } + \ @H.element("td"){ @H.a(@myname+"?-listdraft+#{g}", msg('sendall')) } } }.join("\n") } + \ '' + \ @H.p(msg('address2send')) + \ @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}" memberp = @sc.ismember(user, grp) removep = (/no/i =~ @params[key]) if @params[key] # # Check the group is invitation-only mode. if !removep && !memberp && !@sc.isadmin(user, grp) @O.print @H.elementln("p") { sprintf(msg('invite-error'), grp) + "<br>\n" + \ @sc.admins(grp).join(", ") } sendMail(defaultmladdress(grp).sub("@", @mailadmsuffix+"@"), "Group paticipation attempt to #{grp}", putLog("User `%s' tried to join `%s' from %s" % [user, grp, ENV['REMOTE_ADDR']]), nil, @sc.admins(grp)) next end # # OK to join/retire 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 an 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'} yesclass = {'class' => 'yes'} colspan2 = {'colspan'=>'2'} colspan3 = {'colspan'=>'3'} warnclass = {'class'=>'warn'} warnp = nil @O.print @H.elementln("form", actionmethod){ @H.hidden('group', group) + "\n" + \ if group # Non symmetric job: Move the current users above. gmemlist = userlist.select{|u| @sc.ismember(u, group)} userlist = (gmemlist + userlist).uniq # In this context, should return simply "". "" 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.elementln("th", colspan3) { msg('member', 'of', 'joinquit', 'operation')} } + \ @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", memberp && yesclass){ yes = memberp ? 'YES' : 'yes' @H.radio('mem-'+u, 'yes', yes+' / ', memberp) + \ @H.radio('mem-'+u, 'no', 'no', !memberp) } + \ @H.element("td"){ @H.radio('adm-'+u, 'yes', 'Admin / ', adminp) + \ @H.radio('adm-'+u, 'no', 'no', !adminp) } + \ @H.element("td"){ @H.element("abbr", "title"=>mailaddress(u)) { @sc.nickname(u) } + \ if recursememp warnp = true @H.element("span", warnclass){"(*)"} end.to_s } } }.join + \ # group names @H.elementln("tr") { @H.elementln("th", colspan3) { msg('group', 'of', 'joinquit', 'operation')} } + \ @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 } } + \ ["fromhack", "inviteonly", "limitsender"].collect do |param| @H.checkbox(param, "yes", msg(param), @sc.getgroupattr(group, param)) + "<br>\n" end.join + \ (group ? @H.elementln("p") { sprintf(msg('mladdress'), defaultmladdress(group)) + \ @H.text("mladdress", @sc.getgroupattr(group, 'mladdress'), @opt['size'], 80) } + \ @H.elementln('p') { sprintf(msg('xmlname'), group) + \ @H.text("xmlname", @sc.getgroupattr(group, 'xmlname'), @opt['size'], 80) } : "") + \ @H.elementln('p') { n = -1 curtag = @sc.getgroupattr(group, 'subjtag') values = @subjtags.collect {|x| sprintf(' <option value="%d"%s>%s</option>', n+=1, curtag==@subjtags[n][1] ? ' selected' : "", x[0]) }.join("\n") "Subject tag: " + \ <<-_EOF_ <select name="subjtag"> <option value="">DEFAULT</option> #{values} </select> _EOF_ } + \ @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 = safecopy(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') } if @sc.destroygroup(group) system(sprintf("rm -r ml/%s", group)) resmsg = msg("done") else resmsg = Omsg("failure") end @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... ["fromhack", "inviteonly", "limitsender"].each {|param| parsetp = (@params[param] && /^yes/i =~ @params[param]) cursetp = (@sc.getgroupattr(group, param)!=nil) if cursetp ^ parsetp @sc.putgroupattr(group, param, @params[param]) @O.print @H.elementln("p") { putLog(sprintf("group: %s[%s] -> %s", group, param, @params[param].inspect)) } somethingdone = true end } # mladdress newmladdress = @params['mladdress'] newmladdress = nil if newmladdress == "" curmladdress = @sc.getgroupattr(group, 'mladdress') if newmladdress != curmladdress defmladdress = defaultmladdress(group) @sc.putgroupattr(group, 'mladdress', newmladdress) @O.print @H.elementln("p") { putLog(sprintf("group: %s[mladdress] <%s> -> <%s>", group, curmladdress || defmladdress, newmladdress || defmladdress)) } somethingdone = true end # Subject tag bracket newtag = @params['subjtag'] if newtag == '' newtag = nil else newtag = @subjtags[newtag.to_i % @subjtags.length][1] end @sc.putgroupattr(group, 'subjtag', newtag) if newtag && newtag > "" @O.print @H.elementln("p") { putLog(sprintf("group: %s[subjtag] set to '%s'", group, newtag)) } somethingdone = true end # X-ML-Name: Header value xmlname = @params['xmlname'] xmlname = nil if xmlname == "" curxmlname = @sc.getgroupattr(group, 'xmlname') if xmlname != curxmlname && /^[-A-Z_a-z\/0-9+@(),.<>]+$/ =~ xmlname @sc.putgroupattr(group, 'xmlname', xmlname) @O.print @H.elementln("p") { putLog(sprintf("X-ML-Name: Set to %s", xmlname)) } 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 if pm.userexist?(user) && !ENV['PWRESET'] printf("User %s already exists. Skip.\n", user) printf("If you reset passwd to new one, PWRESET=1 #{$0} ...\n") exit 1 end 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 = {} @oldargv = ARGV.dup 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" ARGV.shift x=ARGV[0] @job = "groupman("+(x ? "'#{x.dup.untaint}'" : "") + ")" 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 = safecopy(ARGV[0]) # -listdraft can be called with user 2013/12/12 ##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: