def Show(): "Show an e-mail in HTML." global Allow, Remove, Attachment, Divider, PartTemplate, T # Deal with a particular message? if Form.has_key("msgid"): PVars["MsgID"] = Form["msgid"].value PVars.Save() # Check to make sure they're not trying to access anything other than email if not re.compile("^\d+\.\d+\.msg$").search(PVars["MsgID"]): CgiUtil.TermError( "<tt>%s</tt> is not a valid message ID." % PVars["MsgID"], "Program error / corrupted link.", "retrieve pending e-mail", "", "Recheck link or contact TMDA programmers.") # Fetch the queue Queue = Pending.Queue(descending=1, cache=1) Queue.initQueue() Queue._loadCache() # Get e-mail template T = Template.Template("view.html") T["EmailClass"] = PVars[("ViewPending", "EmailClass")] # Locate messages in pending dir Msgs = Queue.listPendingIds() try: MsgIdx = Msgs.index(PVars["MsgID"]) except ValueError: # Oops. Perhaps they released the message? Get the list! raise Errors.MessageError # Any subcommands? if Form.has_key("subcmd"): # first/prev/next/last subcommands if Form["subcmd"].value == "first": MsgIdx = 0 PVars["Pager"] = 0 elif Form["subcmd"].value == "prev": if MsgIdx > 0: MsgIdx -= 1 PVars["Pager"] -= 1 elif Form["subcmd"].value == "next": if MsgIdx < (len(Msgs) - 1): MsgIdx += 1 PVars["Pager"] += 1 elif Form["subcmd"].value == "last": MsgIdx = len(Msgs) - 1 PVars["Pager"] = len(Msgs) # Toggle headers? elif Form["subcmd"].value == "headers": if PVars[("ViewPending", "Headers")] == "short": PVars[("ViewPending", "Headers")] = "all" else: PVars[("ViewPending", "Headers")] = "short" else: # Read in e-mail try: MsgObj = Pending.Message(PVars["MsgID"]) if Form["subcmd"].value == "pass": pass if Form["subcmd"].value == "delete": MsgObj.delete() elif Form["subcmd"].value == "release": MsgObj.release() PVars["InProcess"][PVars["MsgID"]] = 1 elif Form["subcmd"].value == "white": MsgObj.whitelist() MsgObj.release() PVars["InProcess"][PVars["MsgID"]] = 1 elif Form["subcmd"].value == "black": MsgObj.blacklist() MsgObj.delete() elif Form["subcmd"].value == "spamcop": CgiUtil.ReportToSpamCop(MsgObj) MsgObj.delete() # TODO: Check if subcmd is a custom filter and process accordingly del Msgs[MsgIdx] except IOError: pass # So which message are we on now? if len(Msgs) == 0: # Oops! None left! PVars.Save() raise Errors.MessageError if MsgIdx >= len(Msgs): MsgIdx = len(Msgs) - 1 PVars["MsgID"] = Msgs[MsgIdx] # Save session PVars.Save() # Get message ID T["MsgID"] = Msgs[MsgIdx] PVars["MsgID"] = Msgs[MsgIdx] # Grey out the first & prev buttons? if MsgIdx == 0: T["FirstButton1Active"] T["PrevButton1Active"] T["FirstButton2Active"] T["PrevButton2Active"] else: T["FirstButton1Inactive"] T["PrevButton1Inactive"] T["FirstButton2Inactive"] T["PrevButton2Inactive"] # Grey out the next & last buttons? if MsgIdx == (len(Msgs) - 1): T["NextButton1Active"] T["LastButton1Active"] T["NextButton2Active"] T["LastButton2Active"] else: T["NextButton1Inactive"] T["LastButton1Inactive"] T["NextButton2Inactive"] T["LastButton2Inactive"] # Use Javascript confirmation? if PVars[("General", "UseJSConfirm")] == "Yes": T["OnSubmit"] = "onSubmit=\"return TestConfirm()\"" T["DeleteURL"] = "javascript:ConfirmDelete()" T["BlacklistURL"] = "javascript:ConfirmBlacklist()" T["SpamCopURL"] = "javascript:ConfirmSpamCop()" else: T["OnSubmit"] = "" T["DeleteURL"] = "%s?cmd=view&subcmd=delete&SID=%s" % \ (os.environ["SCRIPT_NAME"], PVars.SID) T["BlacklistURL"] = "%s?cmd=view&subcmd=black&SID=%s" % \ (os.environ["SCRIPT_NAME"], PVars.SID) T["SpamCopURL"] = "%s?cmd=view&subcmd=spamcop&SID=%s" % \ (os.environ["SCRIPT_NAME"], PVars.SID) T["DispRange"] = "%d of %d" % (MsgIdx + 1, len(Msgs)) # Read in e-mail MsgObj = Pending.Message(PVars["MsgID"]) Queue._addCache(PVars["MsgID"]) Queue._saveCache() # Extract header row HeaderRow = T["HeaderRow"] if PVars[("ViewPending", "Headers")] == "all": # Remove header table T["ShortHeaders"] # Generate all headers Headers = "" for Line in CgiUtil.Escape(MsgObj.show()).split("\n"): if Line == "": break # Decode internationalized headers for decoded in email.Header.decode_header(Line): Headers += decoded[0] + " " if decoded[1]: cset = email.Charset.Charset( decoded[1]).input_charset.split() T["CharSet"] = cset[0] Headers += "\n" T["Headers"] = '<pre class="Headers">%s</pre>' % Headers else: # Remove all header block T["AllHeaders"] # Generate short headers for Header in Defaults.SUMMARY_HEADERS: T["Name"] = Header.capitalize() value = "" # Decode internationalazed headers for decoded in email.Header.decode_header(MsgObj.msgobj[Header]): value += decoded[0] + " " if decoded[1]: cset = email.Charset.Charset( decoded[1]).input_charset.split() T["CharSet"] = cset[0] T["Value"] = CgiUtil.Escape(value) HeaderRow.Add() # Go through each part and generate HTML Allow = re.split("[,\s]+", PVars[("ViewPending", "AllowTags")]) Remove = re.split("[,\s]+", PVars[("ViewPending", "BlockRemove")]) Attachment = T["Attachment"] Divider = T["Divider"] PartTemplate = T["Part"] ShowPart(MsgObj.msgobj) # Remove unneeded bits? NumCols = int(T["NumCols"]) # TODO: Programatically check a setting to see which are allowed, # and which should be shown. # For now, allow and show everything RlAllowed = 1 DlAllowed = 1 WhAllowed = 1 and Defaults.PENDING_WHITELIST_APPEND BlAllowed = 1 and Defaults.PENDING_BLACKLIST_APPEND ScAllowed = 1 and PVars[("General", "SpamCopAddr")] FltAllowed = 1 RlShow = RlAllowed and 1 DlShow = DlAllowed and 1 WhShow = WhAllowed and 1 BlShow = BlAllowed and 1 ScShow = ScAllowed and 1 if not RlAllowed: T["RlAction"] if not RlShow: NumCols -= 1 T["RlIcon1"] T["RlIcon2"] if not DlAllowed: T["DlAction"] if not DlShow: NumCols -= 1 T["DlIcon1"] T["DlIcon2"] if not BlAllowed: T["BlAction"] if not BlShow: NumCols -= 1 T["BlIcon1"] T["BlIcon2"] if not WhAllowed: T["WhAction"] if not WhShow: NumCols -= 1 T["WhIcon1"] T["WhIcon2"] if not ScAllowed: T["ScAction"] if not ScShow: NumCols -= 1 T["SCIcon1"] T["SCIcon2"] if FltAllowed: T["FilterOptions"] = CgiUtil.getFilterOptions() else: T["FilterOptions"] = "" T["NumCols"] = NumCols if len(Attachment.HTML) == 0: T["NoAttachments"] # Display HTML page with email included. print T
def Release(QueryString): """Release the message represented in the QueryString. QueryString is in one of two formats, real users MAY confirm with: <UID>.<confirm_cookie> Virtual users MUST confirm with: <UID>&<recipient_address>&<confirm_cookie> Where <UID> is the UID of the TMDA account, <recipient_address> is the untagged address of the original message recipient, and <confirm_cookie> is used to find and validate the pending email in question.""" # Prepare the traceback in case of uncaught exception MyCgiTb.ErrTemplate = "prog_err2.html" CgiUtil.ErrTemplate = "error2.html" try: UID, Recipient, Cookie = QueryString.split("&") UID = int(UID) OldStyle = 0 # Get base address from Recipient RecipUser, RecipDomain = Recipient.split("@") User = RecipUser.split('-')[0] + "@" + RecipDomain except (ValueError, KeyError): try: # Check for old-style format UID, Cookie = QueryString.split(".", 1) UID = int(UID) User = pwd.getpwuid(UID)[0] OldStyle = 1 except (ValueError, KeyError): CgiUtil.TermError( "Unable to parse query string.", "Program error / corrupted link.", "locate pending e-mail", "", """Please check the link you followed and make sure that it is typed in exactly as it was sent to you.""") try: # Get real user from UID Timestamp, PID, HMAC = Cookie.split(".") except ValueError: CgiUtil.TermError( "Unable to parse query string.", "Program error / corrupted link.", "locate pending e-mail", "", """Please check the link you followed and make sure that it is typed in exactly as it was sent to you.""") MsgID = "%s.%s.msg" % (Timestamp, PID) # Check to make sure they're not trying to access anything other than email if not re.compile("^\d+\.\d+\.msg$").search(MsgID): CgiUtil.TermError("<tt>%s.%s.%s</tt> is not a valid message ID." % \ (Timestamp, PID, HMAC), "Program error / corrupted link.", "retrieve pending e-mail", "", """Please check the link you followed and make sure that it is typed in exactly as it was sent to you.""") # Set up the user's home directory. try: os.seteuid(0) os.setegid(0) os.setuid(0) except OSError: pass try: if os.environ.has_key("TMDA_VLOOKUP") and not OldStyle: VLookup = \ CgiUtil.ParseString(os.environ["TMDA_VLOOKUP"], User ) List = Util.RunTask(VLookup[1:]) Sandbox = {"User": User} Filename = os.path.join("stubs", "%s.py" % VLookup[0]) try: execfile(Filename, Sandbox) except IOError: CgiUtil.TermError( "Can't load virtual user stub.", "Cannot execute %s" % Filename, "execute stub", "TMDA_VLOOKUP = %s" % os.environ["TMDA_VLOOKUP"], """Contact this message's sender by an alternate means and inform them of this error, or try confirming your message using an alternate method.""") Home, UID, GID = Sandbox["getuserparams"](List)[0:3] else: Home, UID, GID = Util.getuserparams(pwd.getpwuid(UID)[0]) except KeyError: CgiUtil.TermError( "No such user", "User %s not found" % User, "find user %s" % User, "", """Contact this message's sender by an alternate means and inform them of this error, or try confirming your message using an alternate method.""") if UID < 2: PasswordRecord = pwd.getpwnam(os.environ["TMDA_VUSER"]) UID = PasswordRecord[2] GID = PasswordRecord[3] if not int(UID): CgiUtil.TermError( "TMDA_VUSER is UID 0.", "It is not safe to run " "tmda-cgi as root.", "set euid", "TMDA_VUSER = %s" % os.environ["TMDA_VUSER"], """Contact this message's sender by an alternate means and inform them of this error, or try confirming your message using an alternate method.""") # We now have the home directory and the User. Set this in the environment. os.environ["USER"] = User os.environ["LOGNAME"] = User os.environ["HOME"] = Home # Is there a TMDARC variable? if os.environ.has_key("TMDARC"): # Yes, replace it os.environ["TMDARC"] = os.environ["TMDARC"].replace( "/~/", "/%s/" % User) # Try to change users try: os.seteuid(0) os.setegid(0) os.setgid(int(GID)) os.setuid(int(UID)) except OSError: pass # Now that we know who we are, get our defaults try: from TMDA import Defaults except Errors.ConfigError: CgiUtil.TermError( "Confirm Failed", "Old-style URL is not compatible with virtual users", "use incompatible URL", "", """Contact this message's sender by an alternate means and inform them of this error, or try confirming your message using an alternate method.""") from TMDA import Pending from TMDA import Cookie try: Defaults.CRYPT_KEY except AttributeError: CgiUtil.TermError( "Could not read CRYPT_KEY.", "CRYPT_KEY can not be read by group %d." % os.getegid(), "read CRYPT_KEY", "ALLOW_MODE_640 = %d<br>%s" % (Defaults.ALLOW_MODE_640, CgiUtil.FileDetails("Cryptography key", Defaults.CRYPT_KEY_FILE)), """Any of the following solutions:<br> 1. Place <tt>%s</tt> in any of the groups that user %d belongs to.<br> 2. Do all three of the following:<br> • Place <tt>%s</tt> in group %d.<br> • Assign permissions 640 to <tt>%s</tt>.<br> • Set ALLOW_MODE_640 = 1 in your configuration file.<br> 3. Disable URL confirmation in your confirmation template.""" % (Defaults.CRYPT_KEY_FILE, os.geteuid(), Defaults.CRYPT_KEY_FILE, os.getegid(), Defaults.CRYPT_KEY_FILE)) # Validate the HMAC if Cookie.confirmationmac(Timestamp, PID, "accept") != HMAC: CgiUtil.TermError("<tt>%s.%s.%s</tt> is not a valid message ID." % \ (Timestamp, PID, HMAC), "Program error / corrupted link.", "retrieve pending e-mail", "", "Recheck link or contact TMDA programmers.") # Read in e-mail try: MsgObj = Pending.Message(MsgID) except Errors.MessageError: CgiUtil.TermError("Message could not be fetched.", "Message has already been released or deleted.", "retrieve pending e-mail", "", "Inquire with recipient about e-mail.") T = Template.Template("released.html") # Fetch row Row = T["Row"] # Generate header rows for Header in Defaults.SUMMARY_HEADERS: T["Name"] = Header.capitalize() T["Value"] = CgiUtil.Escape(MsgObj.msgobj[Header]) Row.Add() # Can we add this address to a do-not-confirm-again list? if Defaults.CONFIRM_APPEND: ConfirmAddr = Util.confirm_append_address \ ( parseaddr(MsgObj.msgobj["x-primary-address"])[1], parseaddr(MsgObj.msgobj["return-path"])[1] ) if ConfirmAddr: Util.append_to_file(ConfirmAddr, Defaults.CONFIRM_APPEND) T["Address"] = ConfirmAddr else: T["Future"] else: T["Future"] print T # Make sure release does not write to PENDING_RELEASE_APPEND Defaults.PENDING_RELEASE_APPEND = None MsgObj.release()
def ShowPart(Part): "Analyze message part and display it as best possible." # Each part is one of five things and must be handled accordingly # multipart/alternative - pick one and display it # message or multipart - recurse through each # text/plain - escape & display # text/html - sterilize & display # other - show as an attachment # Display this part if Part.is_multipart(): if Part.get_type("multipart/mixed") == "multipart/alternative": # Pick preferred alternative PrefPart = None PrefRating = -1 for SubPart in Part.get_payload(): Type = SubPart.get_type("text/plain") Rating = PVars[("ViewPending", "AltPref")].find(Type) # Is this part preferred? if (not PrefPart) or ((PrefRating == -1) and (Rating >= 0)) \ or ((Rating >= 0) and (Rating < PrefRating)): PrefPart = SubPart PrefRating = Rating if PrefPart: ShowPart(PrefPart) else: # Recurse through all subparts for SubPart in Part.get_payload(): ShowPart(SubPart) else: Type = Part.get_type("text/plain") # Display the easily display-able parts if Type == "text/plain": # Check if there's a character set for this part. if Part.get_content_charset(): cset = email.Charset.Charset( Part.get_content_charset()).input_charset.split() T["CharSet"] = cset[0] # Escape & display try: Str = Part.get_payload(decode=1).strip() T["Content"] = CgiUtil.Escape(Str).replace("\n", " <br>") if len(PartTemplate.HTML) == 1: Divider.Add() PartTemplate.Add() except AttributeError: pass elif Type == "text/html": # Sterilize & display # Check if there's a character set for this part. if Part.get_content_charset(): cset = email.Charset.Charset( Part.get_content_charset()).input_charset.split() T["CharSet"] = cset[0] try: T["Content"] = \ CgiUtil.Sterilize(Part.get_payload(decode=1), Allow, Remove) if len(PartTemplate.HTML) == 1: Divider.Add() PartTemplate.Add() except AttributeError: pass else: # Display as an attachment AddIcon(Part)