import edward_config
-langs = ["an", "de", "el", "en", "fr", "ja", "pt-br", "ro", "ru", "tr"]
+langs = ["de", "el", "en", "fr", "ja", "pt-br", "ro", "ru", "tr"]
+
+"""This list contains the abbreviated names of reply languages available to
+edward."""
+
match_types = [('clearsign',
'-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----'),
('detachedsig',
'-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----')]
+"""This list of tuples matches query names with re.search() queries used
+to find GPG data for edward to process."""
+
class EddyMsg (object):
- def __init__(self):
- self.multipart = False
- self.subparts = []
+ """
+ The EddyMsg class represents relevant parts of a mime message.
+
+ The represented message can be single-part or multi-part.
+
+ 'multipart' is set to True if there are multiple mime parts.
+
+ 'subparts' points to a list of mime sub-parts if it is a multi-part
+ message. Otherwise it points to an empty list.
+
+ 'payload_bytes' contains the raw mime-decoded bytes that haven't been
+ encoded into a character set.
+
+ 'payload_pieces' is a list of objects containing strings that when strung
+ together form the fully-decoded string representation of the mime part.
+
+ The 'charset' describes the character set of payload_bytes.
+
+ The 'filename', 'content_type' and 'description_list' come from the mime
+ part parameters.
+ """
+
+ multipart = False
+ subparts = []
- self.charset = None
- self.payload_bytes = None
- self.payload_pieces = []
+ payload_bytes = None
+ payload_pieces = []
- self.filename = None
- self.content_type = None
- self.description_list = None
+ charset = None
+ filename = None
+ content_type = None
+ description_list = None
class PayloadPiece (object):
- def __init__(self):
- self.piece_type = None
- self.string = None
- self.gpg_data = None
+ """
+ PayloadPiece represents a complte or sub-section of a mime part.
+
+ Instances of this class are often strung together within one or more arrays
+ pointed to by each instance of the EddyMsg class.
+
+ 'piece_type' refers to a string whose value describes the content of
+ 'string'. Examples include "pubkey", for public keys, and "message", for
+ encrypted data (or armored signatures until they are known to be such.) The
+ names derive from the header and footer of each of these ascii-encoded gpg
+ blocks.
+
+ 'string' contains some string of text, such as non-GPG text, an encrypted
+ block of text, a signature, or a public key.
+
+ 'gpg_data' points to any instances of GPGData that have been created based
+ on the contents of 'string'.
+ """
+
+ piece_type = None
+ string = None
+ gpg_data = None
class GPGData (object):
- def __init__(self):
- self.decrypted = False
+ """
+ GPGData holds info from decryption, sig. verification, and/or pub. keys.
+
+ Instances of this class contain decrypted information, signature
+ fingerprints and/or fingerprints of processed and imported public keys.
+
+ 'decrypted' is set to True if 'plainobj' was created from encrypted data.
+
+ 'plainobj' points to any decrypted, or signed part of, a GPG signature. It
+ is intended to be an instance of the EddyMsg class.
+
+ 'sigs' is a list of fingerprints of keys used to sign the data in plainobj.
+
+ 'keys' is a list of fingerprints of keys obtained in public key blocks.
+ """
+
+ decrypted = False
+
+ plainobj = None
+ sigs = []
+ keys = []
- self.plainobj = None
- self.sigs = []
- self.keys = []
class ReplyInfo (object):
- def __init__(self):
- self.replies = None
- self.msg_to_quote = ""
+ """
+ ReplyInfo contains details that edward uses in generating its reply.
+
+ Instances of this class contain information about whether a message was
+ successfully encrypted or signed, and whether a public key was attached, or
+ retrievable, from the local GPG store. It stores the fingerprints of
+ potential encryption key candidates and the message (if any at all) to
+ quote in edward's reply.
+
+ 'replies' points one of the dictionaries of translated replies.
+
+ 'target_key' refers to the fingerprint of a key used to sign encrypted
+ data. This is the preferred key, if it is set, and if is available.
+
+ 'fallback_target_key' referst to the fingerprint of a key used to sign
+ unencrypted data; alternatively it may be a public key attached to the
+ message.
+
+ 'msg_to_quote' refers to the part of a message which edward should quote in
+ his reply. This should remain as None if there was no encrypted and singed
+ part. This is to avoid making edward a service for decrypting other
+ people's messages to edward.
+
+ 'success_decrypt' is set to True if edward could decrypt part of the
+ message.
+
+ 'failed_decrypt' is set to True if edward failed to decrypt part of the
+ message.
+
+ 'publick_key_received' is set to True if edward successfully imported a
+ public key.
+
+ 'no_public_key' is set to True if edward doesn't have a key to encrypt to
+ when replying to the user.
+
+ 'sig_success' is set to True if edward could to some extent verify the
+ signature of a signed part of the message to edward.
- self.success_decrypt = False
- self.failed_decrypt = False
- self.public_key_received = False
- self.no_public_key = False
- self.sig_success = False
- self.sig_failure = False
+ 'sig_failure' is set to True if edward failed to some extent verify the
+ signature of a signed part of the message to edward.
+ """
+
+ replies = None
+
+ target_key = None
+ fallback_target_key = None
+ msg_to_quote = ""
+
+ success_decrypt = False
+ failed_decrypt = False
+ public_key_received = False
+ no_public_key = False
+ sig_success = False
+ sig_failure = False
def main ():
+ """
+ This is the main function for edward, a GPG reply bot.
+
+ Edward responds to GPG-encrypted and signed mail, encrypting and signing
+ the response if the user's public key is, or was, included in the message.
+
+ Args:
+ None
+
+ Returns:
+ Nothing
+
+ Pre:
+ Mime or plaintext email passing in through standard input. Portions of
+ the email may be encrypted. If the To: address contains the text
+ "edward-ja", then the reply will contain a reply written in the
+ Japanese language. There are other languages as well. The default
+ language is English.
+
+ Post:
+ A reply email will be printed to standard output. The contents of the
+ reply email depends on whether the original email was encrypted or not,
+ has or doesn't have a signature, whether a public key used in the
+ original message is provided or locally stored, and the language
+ implied by the To: address in the original email.
+ """
+
handle_args()
gpgme_ctx = get_gpg_context(edward_config.gnupghome,
replyinfo_obj.replies = lang.replies
prepare_for_reply(email_struct, replyinfo_obj)
+ encrypt_to_key = get_key_from_fp(replyinfo_obj, gpgme_ctx)
reply_plaintext = write_reply(replyinfo_obj)
- print(reply_plaintext)
+ reply_mime = generate_encrypted_mime(reply_plaintext, email_from, \
+ email_subject, encrypt_to_key,
+ gpgme_ctx)
-# encrypt_to_key = choose_reply_encryption_key(gpgme_ctx, fingerprints)
-#
-# reply_mime = generate_encrypted_mime(plaintext, email_from, \
-# email_subject, encrypt_to_key,
-# gpgme_ctx)
+ print(reply_mime)
def get_gpg_context (gnupghome, sign_with_key_fp):
+ """
+ This function returns the GPG context needed for encryption and signing.
+
+ The context is needed by other functions which use GPG functionality.
+
+ Args:
+ gnupghome: The path to "~/.gnupg/" or its alternative.
+ sign_with_key: The fingerprint of the key to sign with
+
+ Returns:
+ A gpgme context to be used for GPG functions.
+
+ Post:
+ the 'armor' flag is set to True and the list of signing keys contains
+ the single specified key
+ """
os.environ['GNUPGHOME'] = gnupghome
def parse_pgp_mime (email_text, gpgme_ctx):
+ """Parses the email for mime payloads and decrypts/verfies signatures.
+
+ This function creates a representation of a mime or plaintext email with
+ the EddyMsg class. It then splits each mime payload into one or more pieces
+ which may be plain text or GPG data. It then decrypts encrypted parts and
+ does some very basic signature verification on those parts.
+
+ Args:
+ email_text: an email message in string format
+ gpgme_ctx: a gpgme context
+
+ Returns:
+ A message as an instance of EddyMsg
+
+ Post:
+ the returned EddyMsg instance has split, decrypted, verified and pubkey
+ imported payloads
+ """
email_struct = email.parser.Parser().parsestr(email_text)
def parse_mime(msg_struct):
+ """Translates python's email.parser format into an EddyMsg format
+
+ If the message is multi-part, then a recursive object is created, where
+ each sub-part is also a EddyMsg instance.
+
+ Args:
+ msg_struct: an email parsed with email.parser.Parser(), which can be
+ multi-part
+
+ Returns:
+ an instance of EddyMsg, potentially a recursive one.
+ """
eddymsg_obj = EddyMsg()
def scan_and_split (payload_piece, match_type, pattern):
+ """This splits the payloads of an EddyMsg object into GPG and text parts.
+
+ An EddyMsg object's payload_pieces starts off as a list containing a single
+ PayloadPiece object. This function returns a list of these objects which
+ have been split into GPG data and regular text, if such splits need to be/
+ can be made.
+
+ Args:
+ payload_piece: a single payload or a split part of a payload
+ match_type: the type of data to try to spit out from the payload piece
+ pattern: the search pattern to be used for finding that type of data
+
+ Returns:
+ a list of objects of the PayloadPiece class, in the order that the
+ string part of payload_piece originally was, broken up according to
+ matches specified by 'pattern'.
+ """
# don't try to re-split pieces containing gpg data
if payload_piece.piece_type != "text":
def get_subpart_data (part):
+ """This function grabs information from a single part mime object.
+
+ It copies needed data from a single part email.parser.Parser() object over
+ to an EddyMsg object.
+
+ Args:
+ part: a non-multi-part mime.parser.Parser() object
+
+ Returns:
+ a single-part EddyMsg() object
+ """
obj = EddyMsg()
def do_to_eddys_pieces (function_to_do, eddymsg_obj, data):
+ """A function which maps another function onto a message's subparts.
+
+ This is a higer-order function which recursively performs a specified
+ function on each subpart of a multi-part message. Each single-part sub-part
+ has the function applied to it. This function also works if the part passed
+ in is single-part.
+
+ Args:
+ function_to_do: function to perform on sub-parts
+ eddymsg_obj: a single part or multi-part EddyMsg object
+ data: a second argument to pass to 'function_to_do'
+
+ Returns:
+ Nothing
+
+ Post:
+ The passed-in EddyMsg object is transformed recursively on its
+ sub-parts according to 'function_to_do'.
+ """
if eddymsg_obj.multipart == True:
for sub in eddymsg_obj.subparts:
def split_payloads (eddymsg_obj):
+ """Splits all (sub-)payloads of a message into GPG data and regular text.
+
+ Recursively performs payload splitting on all sub-parts of an EddyMsg
+ object into the various GPG data types, such as GPG messages, public key
+ blocks and signed text.
+
+ Args:
+ eddymsg_obj: an instance of EddyMsg
+
+ Returns:
+ Nothing
+
+ Pre:
+ The EddyMsg object has payloads that are unsplit (by may be split)..
+
+ Post:
+ The EddyMsg object's payloads are all split into GPG and non-GPG parts.
+ """
for match_type in match_types:
do_to_eddys_pieces(split_payload_pieces, eddymsg_obj, match_type)
def split_payload_pieces (eddymsg_obj, match_type):
+ """A helper function for split_payloads(); works on PayloadPiece objects.
+
+ This function splits up PayloadPiece objects into multipe PayloadPiece
+ objects and replaces the EddyMsg object's previous list of payload pieces
+ with the new split up one.
+
+ Args:
+ eddymsg_obj: a single-part EddyMsg object.
+ match_type: a tuple from the match_types list, which specifies a match
+ name and a match pattern.
+
+ Returns:
+ Nothing
+
+ Pre:
+ The payload piece(s) of an EddyMsg object may be already split or
+ unsplit.
+
+ Post:
+ The EddyMsg object's payload piece(s) are split into a list of pieces
+ if matches of the match_type are found.
+ """
(match_name, pattern) = match_type
def gpg_on_payloads (eddymsg_obj, gpgme_ctx, prev_parts=[]):
+ """Performs GPG operations on the GPG parts of the message
+
+ This function decrypts text, verifies signatures, and imports public keys
+ included in an email.
+
+ Args:
+ eddymsg_obj: an EddyMsg object with its payload_pieces split into GPG
+ and non-GPG sections by split_payloads()
+ gpgme_ctx: a gpgme context
+
+ prev_parts: a list of mime parts that occur before the eddymsg_obj
+ part, under the same multi-part mime part. This is used for
+ verifying detached signatures. For the root mime part, this should
+ be an empty list, which is the default value if this paramater is
+ omitted.
+
+ Return:
+ Nothing
+
+ Pre:
+ eddymsg_obj should have its payloads split into gpg and non-gpg pieces.
+
+ Post:
+ Decryption, verification and key imports occur. the gpg_data member of
+ PayloadPiece objects get filled in with GPGData objects.
+ """
if eddymsg_obj.multipart == True:
prev_parts=[]
pass
elif piece.piece_type == "message":
- (plaintext, sigs) = decrypt_block (piece.string, gpgme_ctx)
+ (plaintext, sigs) = decrypt_block(piece.string, gpgme_ctx)
+
+ if plaintext:
+ piece.gpg_data = GPGData()
+ piece.gpg_data.decrypted = True
+ piece.gpg_data.sigs = sigs
+ # recurse!
+ piece.gpg_data.plainobj = parse_pgp_mime(plaintext, gpgme_ctx)
+ continue
+
+ # if not encrypted, check to see if this is an armored signature.
+ (plaintext, sigs) = verify_sig_message(piece.string, gpgme_ctx)
if plaintext:
+ piece.piece_type = "signature"
piece.gpg_data = GPGData()
piece.gpg_data.sigs = sigs
# recurse!
piece.gpg_data.sigs = sig_fps
piece.gpg_data.plainobj = prev
break
+
else:
pass
def prepare_for_reply (eddymsg_obj, replyinfo_obj):
+ """Updates replyinfo_obj with info on the message's GPG success/failures
- do_to_eddys_pieces(prepare_for_reply_pieces, eddymsg_obj, replyinfo_obj)
+ This function marks replyinfo_obj with information about whether encrypted
+ text in eddymsg_obj was successfully decrypted, signatures were verified
+ and whether a public key was found or not.
+
+ Args:
+ eddymsg_obj: a message in the EddyMsg format
+ replyinfo_obj: an instance of ReplyInfo
+
+ Returns:
+ Nothing
+
+ Pre:
+ eddymsg_obj has had its gpg_data created by gpg_on_payloads
+ Post:
+ replyinfo_obj has been updated with info about decryption/sig
+ verififcation status, etc. However the desired key isn't imported until
+ later, so the success or failure of that updates the values set here.
+ """
+
+ do_to_eddys_pieces(prepare_for_reply_pieces, eddymsg_obj, replyinfo_obj)
def prepare_for_reply_pieces (eddymsg_obj, replyinfo_obj):
+ """A helper function for prepare_for_reply
+
+ It updates replyinfo_obj with GPG success/failure information, when
+ supplied a single-part EddyMsg object.
+
+ Args:
+ eddymsg_obj: a single-part message in the EddyMsg format
+ replyinfo_obj: an object which holds information about the message's
+ GPG status
+
+ Returns:
+ Nothing
+
+ Pre:
+ eddymsg_obj is a single-part message. (it may be a part of a multi-part
+ message.) It has had its gpg_data created by gpg_on_payloads if it has
+ gpg data.
+
+ Post:
+ replyinfo_obj has been updated with gpg success/failure information
+ """
for piece in eddymsg_obj.payload_pieces:
if piece.piece_type == "text":
pass
elif piece.piece_type == "message":
- if piece.gpg_data == None:
- replyinfo_obj.failed_decrypt = True
- else:
- replyinfo_obj.success_decrypt = True
- # TODO: only quote it if it is also signed by the encrypter.
- replyinfo_obj.msg_to_quote += flatten_payloads(piece.gpg_data.plainobj)
-
- prepare_for_reply(piece.gpg_data.plainobj, replyinfo_obj)
+ prepare_for_reply_message(piece, replyinfo_obj)
elif piece.piece_type == "pubkey":
- if piece.gpg_data == None:
- replyinfo_obj.no_public_key = True
- else:
- replyinfo_obj.public_key_received = True
+ prepare_for_reply_pubkey(piece, replyinfo_obj)
elif (piece.piece_type == "clearsign") \
- or (piece.piece_type == "detachedsig"):
- if piece.gpg_data == None:
- replyinfo_obj.sig_failure = True
- else:
- replyinfo_obj.sig_success = True
+ or (piece.piece_type == "detachedsig") \
+ or (piece.piece_type == "signature"):
+ prepare_for_reply_sig(piece, replyinfo_obj)
+
+
+def prepare_for_reply_message (piece, replyinfo_obj):
+ """Helper function for prepare_for_reply()
+
+ This function is called when the piece_type of a payload piece is
+ "message", or GPG Message block. This should be encrypted text. If the
+ encryted block is signed, a sig will be attached to .target_key unless
+ there is already one there.
+
+ Args:
+ piece: a PayloadPiece object.
+ replyinfo_obj: object which gets updated with decryption status, etc.
+
+
+ Returns:
+ Nothing
+
+ Pre:
+ the piece.payload_piece value should be "message".
+
+ Post:
+ replyinfo_obj gets updated with decryption status, signing status and a
+ potential signing key.
+ """
+
+ if piece.gpg_data == None:
+ replyinfo_obj.failed_decrypt = True
+ return
+
+ replyinfo_obj.success_decrypt = True
+
+ # we already have a key (and a message)
+ if replyinfo_obj.target_key != None:
+ return
+
+ if piece.gpg_data.sigs != []:
+ replyinfo_obj.target_key = piece.gpg_data.sigs[0]
+ get_signed_part = False
+ else:
+ # only include a signed message in the reply.
+ get_signed_part = True
+
+ replyinfo_obj.msg_to_quote = flatten_decrypted_payloads(piece.gpg_data.plainobj, get_signed_part)
+
+ # to catch public keys in encrypted blocks
+ prepare_for_reply(piece.gpg_data.plainobj, replyinfo_obj)
+
+
+def prepare_for_reply_pubkey (piece, replyinfo_obj):
+ """Helper function for prepare_for_reply(). Marks pubkey import status.
+
+ Marks replyinfo_obj with pub key import status.
+
+ Args:
+ piece: a PayloadPiece object
+ replyinfo_obj: a ReplyInfo object
+
+ Pre:
+ piece.piece_type should be set to "pubkey".
+
+ Post:
+ replyinfo_obj has its fields updated.
+ """
+
+ if piece.gpg_data == None or piece.gpg_data.keys == []:
+ replyinfo_obj.no_public_key = True
+ else:
+ replyinfo_obj.public_key_received = True
+
+ if replyinfo_obj.fallback_target_key == None:
+ replyinfo_obj.fallback_target_key = piece.gpg_data.keys[0]
+
+
+def prepare_for_reply_sig (piece, replyinfo_obj):
+ """Helper function for prepare_for_reply(). Marks sig verification status.
+
+ Marks replyinfo_obj with signature verification status.
+ Args:
+ piece: a PayloadPiece object
+ replyinfo_obj: a ReplyInfo object
-def flatten_payloads (eddymsg_obj):
+ Pre:
+ piece.piece_type should be set to "clearsign", "signature", or
+ "detachedsig".
+
+ Post:
+ replyinfo_obj has its fields updated.
+ """
+
+ if piece.gpg_data == None or piece.gpg_data.sigs == []:
+ replyinfo_obj.sig_failure = True
+ else:
+ replyinfo_obj.sig_success = True
+
+ if replyinfo_obj.fallback_target_key == None:
+ replyinfo_obj.fallback_target_key = piece.gpg_data.sigs[0]
+
+
+
+def flatten_decrypted_payloads (eddymsg_obj, get_signed_part):
flat_string = ""
+ if eddymsg_obj == None:
+ return ""
+
+ # recurse on multi-part mime
if eddymsg_obj.multipart == True:
for sub in eddymsg_obj.subparts:
- flat_string += flatten_payloads (sub)
+ flat_string += flatten_decrypted_payloads (sub, get_signed_part)
return flat_string
for piece in eddymsg_obj.payload_pieces:
- if piece.piece_type == "text":
- flat_string += piece.string
+ if (get_signed_part):
+ # don't include nested encryption
+ if ((piece.piece_type == "clearsign") \
+ or (piece.piece_type == "detachedsig") \
+ or (piece.piece_type == "signature")) \
+ and (piece.gpg_data != None):
+ # FIXME: the key used to sign this message needs to be the one that is used for the encrypted reply.
+ flat_string += flatten_decrypted_payloads (piece.gpg_data.plainobj, False)
+ break
+ else:
+ if piece.piece_type == "text":
+ flat_string += piece.string
return flat_string
+def get_key_from_fp (replyinfo_obj, gpgme_ctx):
+
+ if replyinfo_obj.target_key == None:
+ replyinfo_obj.target_key = replyinfo_obj.fallback_target_key
+
+ if replyinfo_obj.target_key != None:
+ try:
+ encrypt_to_key = gpgme_ctx.get_key(replyinfo_obj.target_key)
+ return encrypt_to_key
+
+ except:
+ pass
+
+ # no available key to use
+ replyinfo_obj.target_key = None
+ replyinfo_obj.fallback_target_key = None
+
+ replyinfo_obj.no_public_key = True
+ replyinfo_obj.public_key_received = False
+
+ return None
+
+
def write_reply (replyinfo_obj):
reply_plain = ""
if replyinfo_obj.success_decrypt == True:
- quoted_text = email_quote_text(replyinfo_obj.msg_to_quote)
reply_plain += replyinfo_obj.replies['success_decrypt']
- reply_plain += quoted_text
+
+ if replyinfo_obj.no_public_key == False:
+ quoted_text = email_quote_text(replyinfo_obj.msg_to_quote)
+ reply_plain += quoted_text
elif replyinfo_obj.failed_decrypt == True:
reply_plain += replyinfo_obj.replies['failed_decrypt']
return key_fingerprints
+def verify_sig_message (msg_block, gpgme_ctx):
+
+ block_b = io.BytesIO(msg_block.encode('ascii'))
+ plain_b = io.BytesIO()
+
+ try:
+ sigs = gpgme_ctx.verify(block_b, None, plain_b)
+ except:
+ return ("",[])
+
+ plaintext = plain_b.getvalue().decode('utf-8')
+
+ fingerprints = []
+ for sig in sigs:
+ fingerprints += [sig.fpr]
+ return (plaintext, fingerprints)
+
+
def verify_clear_signature (sig_block, gpgme_ctx):
# FIXME: this might require the un-decoded bytes
return ("",[])
plaintext = plain_b.getvalue().decode('utf-8')
- return (plaintext, sigs)
+
+ fingerprints = []
+ for sig in sigs:
+ fingerprints += [sig.fpr]
+ return (plaintext, fingerprints)
def choose_reply_encryption_key (gpgme_ctx, fingerprints):
def generate_encrypted_mime (plaintext, email_from, email_subject, encrypt_to_key,
gpgme_ctx):
+ # quoted printable encoding lets most ascii characters look normal
+ # before the mime message is decoded.
+ char_set = email.charset.Charset("utf-8")
+ char_set.body_encoding = email.charset.QP
- reply = "To: " + email_from + "\n"
- reply += "Subject: " + email_subject + "\n"
+ # MIMEText doesn't allow setting the text encoding
+ # so we use MIMENonMultipart.
+ plaintext_mime = MIMENonMultipart('text', 'plain')
+ plaintext_mime.set_payload(plaintext, charset=char_set)
if (encrypt_to_key != None):
- plaintext_reply = "thanks for the message!\n\n\n"
- plaintext_reply += email_quote_text(plaintext)
-
- # quoted printable encoding lets most ascii characters look normal
- # before the decrypted mime message is decoded.
- char_set = email.charset.Charset("utf-8")
- char_set.body_encoding = email.charset.QP
-
- # MIMEText doesn't allow setting the text encoding
- # so we use MIMENonMultipart.
- plaintext_mime = MIMENonMultipart('text', 'plain')
- plaintext_mime.set_payload(plaintext_reply, charset=char_set)
encrypted_text = encrypt_sign_message(plaintext_mime.as_string(),
encrypt_to_key,
message_mime.attach(encoded_mime)
message_mime['Content-Disposition'] = 'inline'
- reply += message_mime.as_string()
-
else:
- reply += "\n"
- reply += "Sorry, i couldn't find your key.\n"
- reply += "I'll need that to encrypt a message to you."
+ message_mime = plaintext_mime
+
+ message_mime['To'] = email_from
+ message_mime['Subject'] = email_subject
+
+ reply = message_mime.as_string()
return reply
def handle_args ():
- if __name__ == "__main__":
- global progname
- progname = sys.argv[0]
+ global progname
+ progname = sys.argv[0]
+
+ if len(sys.argv) > 1:
+ print(progname + ": error, this program doesn't " \
+ "need any arguments.", file=sys.stderr)
+ exit(1)
- if len(sys.argv) > 1:
- print(progname + ": error, this program doesn't " \
- "need any arguments.", file=sys.stderr)
- exit(1)
+if __name__ == "__main__":
-main()
+ main()