├── .gitignore
├── mail.ico
├── screenshot
└── main_eng.png
├── locale
└── zh_CN
│ └── LC_MESSAGES
│ ├── lang_zh_CN.mo
│ └── lang_zh_CN.po
├── .idea
├── vcs.xml
├── .gitignore
├── inspectionProfiles
│ └── profiles_settings.xml
├── modules.xml
├── misc.xml
└── SampleMailSubmitter.iml
├── make.bat
├── README.md
├── LICENSE.md
├── Sender.py
├── GUI.py
├── Main.py
└── GUI.fbp
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist/
2 | /build/
3 |
--------------------------------------------------------------------------------
/mail.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryLinLinLin/SampleMailSubmitter/HEAD/mail.ico
--------------------------------------------------------------------------------
/screenshot/main_eng.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryLinLinLin/SampleMailSubmitter/HEAD/screenshot/main_eng.png
--------------------------------------------------------------------------------
/locale/zh_CN/LC_MESSAGES/lang_zh_CN.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JerryLinLinLin/SampleMailSubmitter/HEAD/locale/zh_CN/LC_MESSAGES/lang_zh_CN.mo
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/make.bat:
--------------------------------------------------------------------------------
1 | pyinstaller --noconfirm --log-level=WARN ^
2 | -F -w -n "SampleMailSubmitter" ^
3 | --add-data="locale;locale" ^
4 | --add-data="mail.ico;." ^
5 | --icon="mail.ico" ^
6 | --upx-exclude=vcruntime140.dll ^
7 | Main.py
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/SampleMailSubmitter.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SampleMailSubmitter
2 | A tool for automatically sending suspicious/false-flag files via email to Antivirus vendors
3 | ## Screenshot
4 |
5 |
6 | ## Features
7 |
8 | - Automatically compress all files into a single encrypted zip.
9 | - Customizable Zip password and Email content.
10 | - Add/Remove items from Antivirus vendor list
11 | - Automatically save login info
12 | - Send with one click
13 | - Multi-language support (English or Chinese Simplified)
14 |
15 | ## How to use
16 |
17 | 1. Drag files you want to send to the text window.
18 | 2. Input your email info, SMTP and port info.
19 | 3. Select the Antivirus vendors you want to send.
20 | 4. Click Submit button and done.
21 |
22 | ## Licence
23 |
24 | See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).
25 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2021] [JerryLinLinLin]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/locale/zh_CN/LC_MESSAGES/lang_zh_CN.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: \n"
4 | "POT-Creation-Date: 2020-07-28 10:22-0500\n"
5 | "PO-Revision-Date: 2020-07-28 10:23-0500\n"
6 | "Last-Translator: \n"
7 | "Language-Team: \n"
8 | "Language: zh_CN\n"
9 | "MIME-Version: 1.0\n"
10 | "Content-Type: text/plain; charset=UTF-8\n"
11 | "Content-Transfer-Encoding: 8bit\n"
12 | "X-Generator: Poedit 2.4\n"
13 | "X-Poedit-Basepath: ../../..\n"
14 | "Plural-Forms: nplurals=1; plural=0;\n"
15 | "X-Poedit-SearchPath-0: GUI.py\n"
16 | "X-Poedit-SearchPath-1: Main.py\n"
17 | "X-Poedit-SearchPath-2: Sender.py\n"
18 |
19 | #: GUI.py:23
20 | msgid "Sample Mail Submitter"
21 | msgstr "上报可疑文件"
22 |
23 | #: GUI.py:30
24 | msgid "Choose File(s)"
25 | msgstr "选择单个/多个文件"
26 |
27 | #: GUI.py:32 Sender.py:209
28 | msgid "#Drag all file(s) here. One line per file."
29 | msgstr "#拖拽文件至此,每行单个文件。"
30 |
31 | #: GUI.py:38
32 | msgid "EMail Account"
33 | msgstr "Email 信息"
34 |
35 | #: GUI.py:42
36 | msgid "Email Address: "
37 | msgstr "邮箱地址: "
38 |
39 | #: GUI.py:50
40 | msgid "Password: "
41 | msgstr "邮箱密码: "
42 |
43 | #: GUI.py:58
44 | msgid "Remember"
45 | msgstr "记住"
46 |
47 | #: GUI.py:61
48 | msgid "SMTP Address: "
49 | msgstr "SMTP地址: "
50 |
51 | #: GUI.py:69
52 | msgid "Port: "
53 | msgstr "端口: "
54 |
55 | #: GUI.py:83
56 | msgid "Submission Type"
57 | msgstr "上报类型"
58 |
59 | #: GUI.py:87
60 | msgid "False Negative"
61 | msgstr "漏报文件"
62 |
63 | #: GUI.py:91
64 | msgid "False Positive"
65 | msgstr "误报文件"
66 |
67 | #: GUI.py:94
68 | msgid "Zip Password"
69 | msgstr "压缩密码"
70 |
71 | #: GUI.py:97
72 | msgid "Customize..."
73 | msgstr "自定义..."
74 |
75 | #: GUI.py:108
76 | msgid "Vendors"
77 | msgstr "厂商"
78 |
79 | #: GUI.py:112
80 | msgid "Selected Vendors: "
81 | msgstr "已选厂商: "
82 |
83 | #: GUI.py:117
84 | msgid "Vendor List: "
85 | msgstr "厂商列表: "
86 |
87 | #: GUI.py:128
88 | msgid "<<"
89 | msgstr "<<"
90 |
91 | #: GUI.py:131
92 | msgid ">>"
93 | msgstr ">>"
94 |
95 | #: GUI.py:134
96 | msgid "..."
97 | msgstr "..."
98 |
99 | #: GUI.py:140
100 | msgid "Avira;virus@avira.com"
101 | msgstr ""
102 |
103 | #: GUI.py:140
104 | msgid "Avira;novirus@avira.com"
105 | msgstr ""
106 |
107 | #: GUI.py:140
108 | msgid "Kaspersky;newvirus@kaspersky.com"
109 | msgstr ""
110 |
111 | #: GUI.py:140
112 | msgid "Kaspersky;apac-virussample@kaspersky.com"
113 | msgstr ""
114 |
115 | #: GUI.py:140
116 | msgid "ESET;samples@eset.sk"
117 | msgstr ""
118 |
119 | #: GUI.py:140
120 | msgid "ESET;samples@eset.com"
121 | msgstr ""
122 |
123 | #: GUI.py:140
124 | msgid "Huorong;seclab@huorong.cn"
125 | msgstr ""
126 |
127 | #: GUI.py:140
128 | msgid "BitDefender;oemsamples@bitdefender.com"
129 | msgstr ""
130 |
131 | #: GUI.py:140
132 | msgid "BitDefender;virus_submission@bitdefender.com"
133 | msgstr ""
134 |
135 | #: GUI.py:152
136 | msgid "Other Settings: "
137 | msgstr "其他设置: "
138 |
139 | #: GUI.py:154
140 | msgid "Program Language: "
141 | msgstr "程序语言: "
142 |
143 | #: GUI.py:159
144 | msgid "English"
145 | msgstr "English"
146 |
147 | #: GUI.py:159
148 | msgid "Chinese Simplified"
149 | msgstr "简体中文"
150 |
151 | #: GUI.py:169
152 | msgid "Pack All to Desktop"
153 | msgstr "打包至桌面"
154 |
155 | #: GUI.py:172
156 | msgid "Pack and Submit All"
157 | msgstr "打包+提交全部"
158 |
159 | #: GUI.py:243
160 | msgid "Custom Mail Content"
161 | msgstr "自定义邮件内容"
162 |
163 | #: GUI.py:252
164 | msgid "False Negative Text"
165 | msgstr "漏报邮件内容"
166 |
167 | #: GUI.py:257
168 | msgid "False Positive Text"
169 | msgstr "误报邮件内容"
170 |
171 | #: GUI.py:278
172 | msgid "OK"
173 | msgstr "确定"
174 |
175 | #: GUI.py:281
176 | msgid "Cancel"
177 | msgstr "取消"
178 |
179 | #: Main.py:42
180 | msgid "Enter password for compressing"
181 | msgstr "输入压缩密码"
182 |
183 | #: Main.py:43
184 | msgid "Set Zip Password"
185 | msgstr "设置压缩密码"
186 |
187 | #: Main.py:50
188 | msgid "Password Cannot be Empty"
189 | msgstr "密码不能为空"
190 |
191 | #: Main.py:51 Main.py:98 Main.py:127 Sender.py:146 Sender.py:160 Sender.py:197
192 | msgid "ERROR"
193 | msgstr "错误"
194 |
195 | #: Main.py:62
196 | msgid "Password will be stored in the local config file. Continue?"
197 | msgstr "邮箱密码将会保存到本地配置文件。是否继续?"
198 |
199 | #: Main.py:63
200 | msgid "WARNING"
201 | msgstr "警告"
202 |
203 | #: Main.py:86
204 | msgid "Use \";\" to separate name and email."
205 | msgstr "使用“;”区分厂商名称和邮件地址。"
206 |
207 | #: Main.py:87
208 | msgid "Edit Vendor List"
209 | msgstr "编辑厂商列表"
210 |
211 | #: Main.py:97
212 | msgid "Incorrect Data Format"
213 | msgstr "数据格式错误"
214 |
215 | #: Main.py:112
216 | msgid "Sending Samples..."
217 | msgstr "发送样本..."
218 |
219 | #: Main.py:125 Sender.py:158
220 | msgid "Cannot Access Sample File(s). Check paths.\n"
221 | msgstr "无法读取样本文件。请检查路径。\n"
222 |
223 | #: Main.py:132
224 | msgid "Pack to Desktop succeed!"
225 | msgstr "成功打包样本至桌面!"
226 |
227 | #: Main.py:133 Sender.py:206
228 | msgid "INFO"
229 | msgstr "信息"
230 |
231 | #: Main.py:187
232 | #, python-brace-format
233 | msgid ""
234 | "Hello,\n"
235 | "\n"
236 | "The attached files may contain threats. Require for the further analysis.\n"
237 | "\n"
238 | "Password: {password} \n"
239 | "\n"
240 | "Thanks! "
241 | msgstr ""
242 |
243 | #: Main.py:189
244 | #, python-brace-format
245 | msgid ""
246 | "Hello,\n"
247 | "\n"
248 | "The attached files may be clean. Require for the further analysis.\n"
249 | "\n"
250 | "Password: {password} \n"
251 | "\n"
252 | "Thanks! "
253 | msgstr ""
254 |
255 | #: Sender.py:145
256 | msgid "Missing or incorrect parameters. Check if all info filled correctly."
257 | msgstr "丢失参数或参数错误。请检查所有设置。"
258 |
259 | #: Sender.py:153
260 | msgid "Compressing Files..."
261 | msgstr "压缩文件中..."
262 |
263 | #: Sender.py:168
264 | msgid "Composing Mail..."
265 | msgstr "生成邮件中..."
266 |
267 | #: Sender.py:177
268 | msgid "Login To Your Email..."
269 | msgstr "登录邮箱中..."
270 |
271 | #: Sender.py:192
272 | msgid "Sending Email..."
273 | msgstr "发送邮件中..."
274 |
275 | #: Sender.py:195
276 | msgid "Login Fail. Check Internet connection, your login info, or other config.\n"
277 | msgstr "登录邮箱失败。请检查网络连接,登录信息,或其他设置\n"
278 |
279 | #: Sender.py:204
280 | msgid "SUCCEED!"
281 | msgstr "成功!"
282 |
283 | #: Sender.py:205
284 | msgid "Email Sent. You may login your email to check the status."
285 | msgstr "邮件已发送,请登录查看发送状态。"
286 |
287 | #~ msgid "Cannot Access Sample File(s). Check paths."
288 | #~ msgstr "无法读取样本文件。请检查路径。"
289 |
290 | #, python-brace-format
291 | #~ msgid ""
292 | #~ "Cannot Access Sample File(s). Check paths.\n"
293 | #~ "Error: {error}\n"
294 | #~ "Info: {info}"
295 | #~ msgstr ""
296 | #~ "无法读取样本文件。请检查路径\n"
297 | #~ "错误: {error}\n"
298 | #~ "信息: {info}"
299 |
--------------------------------------------------------------------------------
/Sender.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import pyminizip
3 | import os
4 | import datetime
5 | import smtplib
6 | import ssl
7 | import Main
8 | import wx
9 | from email.mime.text import MIMEText
10 | from email.mime.multipart import MIMEMultipart
11 | from email.mime.application import MIMEApplication
12 |
13 | # for i18n support
14 | _ = wx.GetTranslation
15 |
16 |
17 | class SampleFiles:
18 | """
19 | Class represent sample Files for compressing
20 | """
21 | file_list: list = None # All file paths
22 | password: str = None # password for compressing
23 | output_path: str = None # output path
24 |
25 | def __init__(self, file_list: str, password: str):
26 | """
27 | Initialize compressing
28 | :param file_list: file paths
29 | :param password: compress password
30 | """
31 | self.file_list = file_list.split('\n')
32 | self.password = password
33 |
34 | def compress(self):
35 | """
36 | Compress files to user dir
37 | :return: output path
38 | """
39 | cur_time = str(datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d-%H-%M-%S'))
40 | user_path = os.getenv('APPDATA') + "\\VirusSampleSubmitter"
41 | output_path = '{user_path}\\SamplePack[{time}].zip'.format(user_path=user_path, time=cur_time)
42 | self.output_path = output_path
43 | pyminizip.compress_multiple(self.file_list, [], output_path, self.password, 8)
44 | return output_path
45 |
46 | def delete_zip(self):
47 | """Delete self after compressing"""
48 | if self.output_path is not None:
49 | os.remove(self.output_path)
50 |
51 | def compress_to_desktop(self):
52 | """
53 | Compress files to user desktop
54 | :return: output path
55 | """
56 | cur_time = str(datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d-%H-%M-%S'))
57 | output_path = '{desktop_path}\\SamplePack[{time}].zip'.format(desktop_path=os.path.expanduser("~\\Desktop"),
58 | time=cur_time)
59 | self.output_path = output_path
60 | pyminizip.compress_multiple(self.file_list, [], output_path, self.password, 8)
61 | return output_path
62 |
63 |
64 | class Mail:
65 | """
66 | Class represent email body
67 | """
68 | mail: MIMEMultipart = None # mail body
69 | mail_dst_list: list = None # vendor list
70 |
71 | def __init__(self, mail_src, mail_dst, mail_type, mail_content, attach_path):
72 | """
73 | Initialize mail obj
74 | :param mail_src: sender
75 | :param mail_dst: receivers
76 | :param mail_type: false negative or positive
77 | :param mail_content: content
78 | :param attach_path: sample pack path
79 | """
80 | mail = MIMEMultipart()
81 | mail['From'] = mail_src
82 | mail['To'] = ';'.join(self._extract_mails(mail_dst))
83 | mail['Subject'] = self._get_mail_title(mail_type)
84 | mail.attach(MIMEText(mail_content, 'plain', 'utf-8'))
85 | with open(attach_path, 'rb') as f:
86 | attachment = MIMEApplication(f.read())
87 | attachment.add_header('Content-Disposition', 'attachment',
88 | filename=str(attach_path)[str(attach_path).rfind("\\") + 1:len(attach_path)])
89 | mail.attach(attachment)
90 | self.mail = mail
91 | self.mail_dst_list = self._extract_mails(mail_dst)
92 | return
93 |
94 | def _extract_mails(self, mails_string_array):
95 | """
96 | Extract email address from vendor list
97 | :param mails_string_array:
98 | :return:
99 | """
100 | result_list: list = []
101 | for mail_string in mails_string_array:
102 | address = mail_string[mail_string.find(";") + 1: len(mail_string)]
103 | result_list.append(address)
104 | return result_list
105 |
106 | def _get_mail_title(self, mail_type):
107 | """
108 | Get the title of mail based on type
109 | :param mail_type: fn or fp
110 | :return:
111 | """
112 | cur_time = str(datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d-%H-%M-%S'))
113 | if mail_type == 0:
114 | return '[Malware]{time}'.format(time=cur_time)
115 | else:
116 | return '[False Positive]{time}'.format(time=cur_time)
117 |
118 | def get_mail(self):
119 | return self.mail
120 |
121 | def get_mail_dst_list(self):
122 | return self.mail_dst_list
123 |
124 |
125 | class SendingThread(threading.Thread):
126 | """
127 | Class for sending sample file
128 | """
129 | frame: Main.AppMainFrame = None # main frame
130 |
131 | def __init__(self, frame):
132 | """
133 | Start thread
134 | :param frame: main frame
135 | """
136 | threading.Thread.__init__(self)
137 | self.frame = frame
138 | self.start()
139 |
140 | def run(self):
141 | """Sending sample"""
142 | status = self.frame.progress_bar
143 |
144 | if not self._basic_check(): # check if parameters are valid
145 | wx.MessageBox(message=_('Missing or incorrect parameters. Check if all info filled correctly.'),
146 | caption=_('ERROR'),
147 | style=wx.OK | wx.ICON_ERROR)
148 | status.Destroy()
149 | return
150 |
151 | # Compressing files
152 | status.Update(value=25,
153 | newmsg=_('Compressing Files...'))
154 | sample = SampleFiles(self.frame.file_input.GetValue(), self.frame.zip_password)
155 | try:
156 | output_path = sample.compress()
157 | except Exception as e:
158 | wx.MessageBox(message=_('Cannot Access Sample File(s). Check paths.\n') +
159 | 'Error: {error}\nInfo: {info}'.format(error=e.__class__.__name__, info=str(e)),
160 | caption=_('ERROR'),
161 | style=wx.OK | wx.ICON_ERROR)
162 | sample.delete_zip()
163 | status.Destroy()
164 | return
165 |
166 | # Build email body
167 | status.Update(value=50,
168 | newmsg=_('Composing Mail...'))
169 | mail = Mail(mail_src=self.frame.email_account.GetValue(),
170 | mail_dst=self.frame.selected_vendors.GetStrings(),
171 | mail_type=self._get_mail_type(),
172 | mail_content=self._get_mail_content(),
173 | attach_path=output_path)
174 |
175 | # Login to email account and send email
176 | status.Update(value=75,
177 | newmsg=_('Login To Your Email...'))
178 | mail_body = mail.get_mail()
179 | mail_src = self.frame.email_account.GetValue()
180 | mail_password = self.frame.password_input.GetValue()
181 | mail_smtp = self.frame.smtp_input.GetValue()
182 | mail_port = self.frame.port_input.GetValue()
183 |
184 | try:
185 | context = ssl.SSLContext(ssl.PROTOCOL_TLS) # use ssl
186 | mail_main = smtplib.SMTP(host=mail_smtp, port=mail_port)
187 | mail_main.ehlo()
188 | mail_main.starttls(context=context)
189 | mail_main.ehlo()
190 | mail_main.login(user=mail_src, password=mail_password)
191 | status.Update(value=90,
192 | newmsg=_('Sending Email...'))
193 | mail_main.sendmail(from_addr=mail_src, to_addrs=mail.get_mail_dst_list(), msg=mail_body.as_string())
194 | except Exception as e:
195 | wx.MessageBox(message=_('Login Fail. Check Internet connection, your login info, or other config.\n') +
196 | 'Error: {error}\nInfo: {info}'.format(error=e.__class__.__name__, info=str(e)),
197 | caption=_('ERROR'),
198 | style=wx.OK | wx.ICON_ERROR)
199 | status.Destroy()
200 | sample.delete_zip()
201 | return
202 |
203 | status.Update(value=100,
204 | newmsg=_('SUCCEED!'))
205 | wx.MessageBox(message=_('Email Sent. You may login your email to check the status.'),
206 | caption=_('INFO'),
207 | style=wx.OK | wx.ICON_INFORMATION)
208 | status.Destroy()
209 | self.frame.file_input.SetValue(_(u"#Drag all file(s) here. One line per file."))
210 | sample.delete_zip()
211 | return
212 |
213 | def _get_mail_type(self):
214 | """Get mail type; 0 for fn, 1 for fp"""
215 | if self.frame.false_neg_select.GetValue():
216 | return 0
217 | if self.frame.false_positive_select.GetValue():
218 | return 1
219 |
220 | def _get_mail_content(self):
221 | """Get mail content based on type"""
222 | if self._get_mail_type() == 0:
223 | return self.frame.false_negative_content.format(password=self.frame.zip_password)
224 | if self._get_mail_type() == 1:
225 | return self.frame.false_positive_content.format(password=self.frame.zip_password)
226 |
227 | def _basic_check(self):
228 | """Check if parameters are valid"""
229 | f = self.frame
230 | if (f.email_account.GetValue() == '' or
231 | f.password_input.GetValue() == '' or
232 | f.smtp_input.GetValue() == '' or
233 | f.port_input.GetValue() == '' or
234 | f.zip_password == '' or
235 | f.selected_vendors.GetStrings() == []):
236 | return False
237 | return True
238 |
--------------------------------------------------------------------------------
/GUI.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ###########################################################################
4 | ## Python code generated with wxFormBuilder (version Oct 26 2018)
5 | ## http://www.wxformbuilder.org/
6 | ##
7 | ## PLEASE DO *NOT* EDIT THIS FILE!
8 | ###########################################################################
9 |
10 | import wx
11 | import wx.xrc
12 |
13 | # For i18n support
14 | _ = wx.GetTranslation
15 |
16 | ###########################################################################
17 | ## Class main_frame
18 | ###########################################################################
19 |
20 | class main_frame ( wx.Frame ):
21 |
22 | def __init__( self, parent ):
23 | wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Sample Mail Submitter"), pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.CAPTION|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.TAB_TRAVERSAL )
24 |
25 | self.SetSizeHints( wx.DefaultSize, wx.Size( 500,600 ) )
26 | self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) )
27 |
28 | bSizer2 = wx.BoxSizer( wx.VERTICAL )
29 |
30 | sbSizer1 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, _(u"Choose File(s)") ), wx.VERTICAL )
31 |
32 | self.file_input = wx.TextCtrl( sbSizer1.GetStaticBox(), wx.ID_ANY, _(u"#Drag all file(s) here. One line per file."), wx.DefaultPosition, wx.Size( 500,100 ), wx.TE_MULTILINE|wx.HSCROLL|wx.VSCROLL )
33 | sbSizer1.Add( self.file_input, 0, wx.ALL, 5 )
34 |
35 |
36 | bSizer2.Add( sbSizer1, 1, 0, 5 )
37 |
38 | sbSizer2 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, _(u"EMail Account") ), wx.VERTICAL )
39 |
40 | wSizer1 = wx.WrapSizer( wx.HORIZONTAL, wx.WRAPSIZER_DEFAULT_FLAGS )
41 |
42 | self.m_staticText2 = wx.StaticText( sbSizer2.GetStaticBox(), wx.ID_ANY, _(u"Email Address: "), wx.DefaultPosition, wx.Size( 80,-1 ), wx.ALIGN_LEFT )
43 | self.m_staticText2.Wrap( -1 )
44 |
45 | wSizer1.Add( self.m_staticText2, 0, wx.ALL, 8 )
46 |
47 | self.email_account = wx.TextCtrl( sbSizer2.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( 360,-1 ), 0 )
48 | wSizer1.Add( self.email_account, 0, wx.ALL, 5 )
49 |
50 | self.m_staticText3 = wx.StaticText( sbSizer2.GetStaticBox(), wx.ID_ANY, _(u"Password: "), wx.DefaultPosition, wx.Size( 80,-1 ), wx.ALIGN_LEFT )
51 | self.m_staticText3.Wrap( -1 )
52 |
53 | wSizer1.Add( self.m_staticText3, 0, wx.ALL, 8 )
54 |
55 | self.password_input = wx.TextCtrl( sbSizer2.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( 260,-1 ), wx.TE_PASSWORD )
56 | wSizer1.Add( self.password_input, 0, wx.ALL, 5 )
57 |
58 | self.reme_ps_check = wx.CheckBox( sbSizer2.GetStaticBox(), wx.ID_ANY, _(u"Remember"), wx.DefaultPosition, wx.DefaultSize, 0 )
59 | wSizer1.Add( self.reme_ps_check, 0, wx.ALL, 8 )
60 |
61 | self.m_staticText4 = wx.StaticText( sbSizer2.GetStaticBox(), wx.ID_ANY, _(u"SMTP Address: "), wx.DefaultPosition, wx.Size( 80,-1 ), wx.ALIGN_LEFT )
62 | self.m_staticText4.Wrap( -1 )
63 |
64 | wSizer1.Add( self.m_staticText4, 0, wx.ALL, 8 )
65 |
66 | self.smtp_input = wx.TextCtrl( sbSizer2.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( 170,-1 ), 0 )
67 | wSizer1.Add( self.smtp_input, 0, wx.ALL, 5 )
68 |
69 | self.m_staticText5 = wx.StaticText( sbSizer2.GetStaticBox(), wx.ID_ANY, _(u"Port: "), wx.DefaultPosition, wx.Size( 50,-1 ), wx.ALIGN_LEFT )
70 | self.m_staticText5.Wrap( -1 )
71 |
72 | wSizer1.Add( self.m_staticText5, 0, wx.ALL, 8 )
73 |
74 | self.port_input = wx.TextCtrl( sbSizer2.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( 113,-1 ), 0 )
75 | wSizer1.Add( self.port_input, 0, wx.ALL, 5 )
76 |
77 |
78 | sbSizer2.Add( wSizer1, 1, wx.EXPAND, 5 )
79 |
80 |
81 | bSizer2.Add( sbSizer2, 1, wx.EXPAND, 5 )
82 |
83 | sbSizer3 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, _(u"Submission Type") ), wx.VERTICAL )
84 |
85 | wSizer3 = wx.WrapSizer( wx.HORIZONTAL, wx.WRAPSIZER_DEFAULT_FLAGS )
86 |
87 | self.false_neg_select = wx.RadioButton( sbSizer3.GetStaticBox(), wx.ID_ANY, _(u"False Negative"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 )
88 | self.false_neg_select.SetValue( True )
89 | wSizer3.Add( self.false_neg_select, 0, wx.ALL, 5 )
90 |
91 | self.false_positive_select = wx.RadioButton( sbSizer3.GetStaticBox(), wx.ID_ANY, _(u"False Positive"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 )
92 | wSizer3.Add( self.false_positive_select, 0, wx.ALL, 5 )
93 |
94 | self.password_but = wx.Button( sbSizer3.GetStaticBox(), wx.ID_ANY, _(u"Zip Password"), wx.Point( -1,-1 ), wx.Size( 120,25 ), 0 )
95 | wSizer3.Add( self.password_but, 0, wx.ALL, 0 )
96 |
97 | self.email_content = wx.Button( sbSizer3.GetStaticBox(), wx.ID_ANY, _(u"Customize..."), wx.Point( -1,-1 ), wx.Size( 100,-1 ), 0 )
98 | self.email_content.SetMinSize( wx.Size( 120,25 ) )
99 |
100 | wSizer3.Add( self.email_content, 0, wx.ALL, 0 )
101 |
102 |
103 | sbSizer3.Add( wSizer3, 1, wx.EXPAND, 0 )
104 |
105 |
106 | bSizer2.Add( sbSizer3, 1, wx.EXPAND, 5 )
107 |
108 | sbSizer4 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, _(u"Vendors") ), wx.VERTICAL )
109 |
110 | wSizer4 = wx.WrapSizer( wx.HORIZONTAL, wx.WRAPSIZER_DEFAULT_FLAGS )
111 |
112 | self.m_staticText6 = wx.StaticText( sbSizer4.GetStaticBox(), wx.ID_ANY, _(u"Selected Vendors: "), wx.DefaultPosition, wx.Size( 250,-1 ), 0 )
113 | self.m_staticText6.Wrap( -1 )
114 |
115 | wSizer4.Add( self.m_staticText6, 0, wx.ALL, 5 )
116 |
117 | self.m_staticText7 = wx.StaticText( sbSizer4.GetStaticBox(), wx.ID_ANY, _(u"Vendor List: "), wx.DefaultPosition, wx.Size( 150,-1 ), 0 )
118 | self.m_staticText7.Wrap( -1 )
119 |
120 | wSizer4.Add( self.m_staticText7, 0, wx.ALL, 5 )
121 |
122 | selected_vendorsChoices = []
123 | self.selected_vendors = wx.ListBox( sbSizer4.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.Size( 190,100 ), selected_vendorsChoices, 0|wx.VSCROLL )
124 | wSizer4.Add( self.selected_vendors, 0, wx.ALL, 5 )
125 |
126 | bSizer5 = wx.BoxSizer( wx.VERTICAL )
127 |
128 | self.add_but = wx.Button( sbSizer4.GetStaticBox(), wx.ID_ANY, _(u"<<"), wx.DefaultPosition, wx.Size( 50,-1 ), 0 )
129 | bSizer5.Add( self.add_but, 0, wx.ALL, 5 )
130 |
131 | self.remove_but = wx.Button( sbSizer4.GetStaticBox(), wx.ID_ANY, _(u">>"), wx.DefaultPosition, wx.Size( 50,-1 ), 0 )
132 | bSizer5.Add( self.remove_but, 0, wx.ALL, 5 )
133 |
134 | self.vendor_edit_but = wx.Button( sbSizer4.GetStaticBox(), wx.ID_ANY, _(u"..."), wx.DefaultPosition, wx.Size( 50,-1 ), 0 )
135 | bSizer5.Add( self.vendor_edit_but, 0, wx.ALL, 5 )
136 |
137 |
138 | wSizer4.Add( bSizer5, 1, wx.EXPAND, 5 )
139 |
140 | vendor_listChoices = [ _(u"Avira;virus@avira.com"), _(u"Avira;novirus@avira.com"), _(u"Kaspersky;newvirus@kaspersky.com"), _(u"Kaspersky;apac-virussample@kaspersky.com"), _(u"ESET;samples@eset.sk"), _(u"ESET;samples@eset.com"), _(u"Huorong;seclab@huorong.cn"), _(u"BitDefender;oemsamples@bitdefender.com"), _(u"BitDefender;virus_submission@bitdefender.com") ]
141 | self.vendor_list = wx.ListBox( sbSizer4.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.Size( 200,100 ), vendor_listChoices, 0|wx.VSCROLL )
142 | wSizer4.Add( self.vendor_list, 0, wx.ALL, 5 )
143 |
144 |
145 | sbSizer4.Add( wSizer4, 1, wx.EXPAND, 5 )
146 |
147 |
148 | bSizer2.Add( sbSizer4, 1, wx.EXPAND, 5 )
149 |
150 | wSizer5 = wx.WrapSizer( wx.HORIZONTAL, wx.WRAPSIZER_DEFAULT_FLAGS )
151 |
152 | sbSizer5 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, _(u"Other Settings: ") ), wx.VERTICAL )
153 |
154 | self.m_staticText8 = wx.StaticText( sbSizer5.GetStaticBox(), wx.ID_ANY, _(u"Program Language: "), wx.DefaultPosition, wx.Size( 200,-1 ), 0 )
155 | self.m_staticText8.Wrap( -1 )
156 |
157 | sbSizer5.Add( self.m_staticText8, 0, wx.ALL, 5 )
158 |
159 | language_choiceChoices = [ _(u"English"), _(u"Chinese Simplified") ]
160 | self.language_choice = wx.Choice( sbSizer5.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.Size( 200,-1 ), language_choiceChoices, 0 )
161 | self.language_choice.SetSelection( 0 )
162 | sbSizer5.Add( self.language_choice, 0, wx.ALL, 5 )
163 |
164 |
165 | wSizer5.Add( sbSizer5, 1, wx.EXPAND, 5 )
166 |
167 | bSizer7 = wx.BoxSizer( wx.VERTICAL )
168 |
169 | self.pack_but = wx.Button( self, wx.ID_ANY, _(u"Pack All to Desktop"), wx.DefaultPosition, wx.Size( 250,30 ), 0 )
170 | bSizer7.Add( self.pack_but, 0, wx.ALL, 3 )
171 |
172 | self.submit_but = wx.Button( self, wx.ID_ANY, _(u"Pack and Submit All"), wx.DefaultPosition, wx.Size( 250,50 ), 0 )
173 | bSizer7.Add( self.submit_but, 0, wx.ALL, 3 )
174 |
175 |
176 | wSizer5.Add( bSizer7, 1, wx.EXPAND, 5 )
177 |
178 |
179 | bSizer2.Add( wSizer5, 1, wx.EXPAND, 5 )
180 |
181 |
182 | self.SetSizer( bSizer2 )
183 | self.Layout()
184 | bSizer2.Fit( self )
185 |
186 | self.Centre( wx.BOTH )
187 |
188 | # Connect Events
189 | self.Bind( wx.EVT_CLOSE, self.close_but_click )
190 | self.reme_ps_check.Bind( wx.EVT_CHECKBOX, self.reme_ps_check_click )
191 | self.password_but.Bind( wx.EVT_BUTTON, self.password_but_click )
192 | self.email_content.Bind( wx.EVT_BUTTON, self.content_but_click )
193 | self.add_but.Bind( wx.EVT_BUTTON, self.add_but_click )
194 | self.remove_but.Bind( wx.EVT_BUTTON, self.remove_but_click )
195 | self.vendor_edit_but.Bind( wx.EVT_BUTTON, self.vendor_edit_but_click )
196 | self.language_choice.Bind( wx.EVT_CHOICE, self.lang_select )
197 | self.pack_but.Bind( wx.EVT_BUTTON, self.pack_but_click )
198 | self.submit_but.Bind( wx.EVT_BUTTON, self.submit_but_click )
199 |
200 | def __del__( self ):
201 | pass
202 |
203 |
204 | # Virtual event handlers, overide them in your derived class
205 | def close_but_click( self, event ):
206 | event.Skip()
207 |
208 | def reme_ps_check_click( self, event ):
209 | event.Skip()
210 |
211 | def password_but_click( self, event ):
212 | event.Skip()
213 |
214 | def content_but_click( self, event ):
215 | event.Skip()
216 |
217 | def add_but_click( self, event ):
218 | event.Skip()
219 |
220 | def remove_but_click( self, event ):
221 | event.Skip()
222 |
223 | def vendor_edit_but_click( self, event ):
224 | event.Skip()
225 |
226 | def lang_select( self, event ):
227 | event.Skip()
228 |
229 | def pack_but_click( self, event ):
230 | event.Skip()
231 |
232 | def submit_but_click( self, event ):
233 | event.Skip()
234 |
235 |
236 | ###########################################################################
237 | ## Class mail_content_frame
238 | ###########################################################################
239 |
240 | class mail_content_frame ( wx.Frame ):
241 |
242 | def __init__( self, parent ):
243 | wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Custom Mail Content"), pos = wx.DefaultPosition, size = wx.Size( 500,270 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
244 |
245 | self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
246 | self.SetBackgroundColour( wx.Colour( 255, 255, 255 ) )
247 |
248 | bSizer4 = wx.BoxSizer( wx.VERTICAL )
249 |
250 | wSizer6 = wx.WrapSizer( wx.HORIZONTAL, wx.WRAPSIZER_DEFAULT_FLAGS )
251 |
252 | self.m_staticText10 = wx.StaticText( self, wx.ID_ANY, _(u"False Negative Text"), wx.DefaultPosition, wx.Size( 250,20 ), 0 )
253 | self.m_staticText10.Wrap( -1 )
254 |
255 | wSizer6.Add( self.m_staticText10, 0, wx.ALL, 5 )
256 |
257 | self.m_staticText11 = wx.StaticText( self, wx.ID_ANY, _(u"False Positive Text"), wx.DefaultPosition, wx.Size( -1,20 ), 0 )
258 | self.m_staticText11.Wrap( -1 )
259 |
260 | wSizer6.Add( self.m_staticText11, 0, wx.ALL, 5 )
261 |
262 |
263 | bSizer4.Add( wSizer6, 1, wx.EXPAND, 5 )
264 |
265 | wSizer7 = wx.WrapSizer( wx.HORIZONTAL, wx.WRAPSIZER_DEFAULT_FLAGS )
266 |
267 | self.false_negative_content = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( 230,150 ), wx.TE_MULTILINE|wx.VSCROLL )
268 | wSizer7.Add( self.false_negative_content, 0, wx.ALL, 5 )
269 |
270 | self.false_positive_content = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( 230,150 ), wx.TE_MULTILINE|wx.VSCROLL )
271 | wSizer7.Add( self.false_positive_content, 0, wx.ALL, 5 )
272 |
273 |
274 | bSizer4.Add( wSizer7, 1, wx.EXPAND, 5 )
275 |
276 | wSizer8 = wx.WrapSizer( wx.HORIZONTAL, wx.WRAPSIZER_DEFAULT_FLAGS )
277 |
278 | self.ok_but = wx.Button( self, wx.ID_ANY, _(u"OK"), wx.DefaultPosition, wx.DefaultSize, 0 )
279 | wSizer8.Add( self.ok_but, 0, wx.ALL, 5 )
280 |
281 | self.cancel_but = wx.Button( self, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 )
282 | wSizer8.Add( self.cancel_but, 0, wx.ALL, 5 )
283 |
284 |
285 | bSizer4.Add( wSizer8, 1, wx.ALIGN_RIGHT, 5 )
286 |
287 |
288 | self.SetSizer( bSizer4 )
289 | self.Layout()
290 |
291 | self.Centre( wx.BOTH )
292 |
293 | # Connect Events
294 | self.Bind( wx.EVT_CLOSE, self.close_but_click )
295 | self.ok_but.Bind( wx.EVT_BUTTON, self.ok_but_click )
296 | self.cancel_but.Bind( wx.EVT_BUTTON, self.cancel_but_click )
297 |
298 | def __del__( self ):
299 | pass
300 |
301 |
302 | # Virtual event handlers, overide them in your derived class
303 | def close_but_click( self, event ):
304 | event.Skip()
305 |
306 | def ok_but_click( self, event ):
307 | event.Skip()
308 |
309 | def cancel_but_click( self, event ):
310 | event.Skip()
311 |
312 |
313 |
--------------------------------------------------------------------------------
/Main.py:
--------------------------------------------------------------------------------
1 | import GUI
2 | import wx
3 | import configparser
4 | import os
5 | import sys
6 | import Sender
7 | from distutils.util import strtobool
8 | from base64 import urlsafe_b64encode, urlsafe_b64decode
9 |
10 | # For i18n support
11 | _ = wx.GetTranslation
12 |
13 |
14 | class AppMainFrame(GUI.main_frame):
15 | """
16 | Class for handling main_frame GUI event
17 | """
18 | config: configparser.ConfigParser = None # config obj
19 | lists = None # vendor list
20 | zip_password: str = None # password for compressing
21 | false_negative_content: str = None # mail content for fn
22 | false_positive_content: str = None # main content for fp
23 | progress_bar: wx.ProgressDialog = None # sending progress dialog
24 |
25 | def __init__(self, parent, config, lists):
26 | """
27 | Initialize the main window
28 | :param parent: wxframe
29 | :param config: config file
30 | :param lists: vendor list
31 | """
32 | GUI.main_frame.__init__(self, parent)
33 | self.SetIcon(wx.Icon(app_path('mail.ico'), wx.BITMAP_TYPE_ICO))
34 | self.config = config
35 | self.lists = lists
36 | self._ini_setting()
37 | self._ini_lists()
38 | self._ini_file_drop()
39 |
40 | def password_but_click(self, event):
41 | """Config Zip password"""
42 | ps_input = wx.TextEntryDialog(self, _('Enter password for compressing'),
43 | caption=_('Set Zip Password'),
44 | value=self.zip_password,
45 | style=wx.OK | wx.CANCEL)
46 | while True:
47 | if ps_input.ShowModal() == wx.ID_OK:
48 | ps = ps_input.GetValue()
49 | if ps == '':
50 | wx.MessageBox(message=_('Password Cannot be Empty'),
51 | caption=_('ERROR'),
52 | style=wx.OK | wx.ICON_ERROR)
53 | continue
54 | self.zip_password = ps_input.GetValue()
55 | break
56 | break
57 | ps_input.Destroy()
58 |
59 | def reme_ps_check_click(self, event):
60 | """Save email password to local file"""
61 | if self.reme_ps_check.GetValue():
62 | result = wx.MessageBox(message=_('Password will be stored in the local config file. Continue?'),
63 | caption=_('WARNING'),
64 | style=wx.OK | wx.CANCEL | wx.ICON_WARNING)
65 | if result != wx.OK:
66 | self.reme_ps_check.SetValue(False)
67 |
68 | def add_but_click(self, event):
69 | """Add item to selected list from vendor list"""
70 | cur_index = self.vendor_list.GetSelection()
71 | if cur_index is wx.NOT_FOUND:
72 | return
73 | self.selected_vendors.Append(self.vendor_list.GetString(cur_index))
74 | self.vendor_list.Delete(cur_index)
75 |
76 | def remove_but_click(self, event):
77 | """Remove item from selected list and send back to vendor list"""
78 | cur_index = self.selected_vendors.GetSelection()
79 | if cur_index is wx.NOT_FOUND:
80 | return
81 | self.vendor_list.Append(self.selected_vendors.GetString(cur_index))
82 | self.selected_vendors.Delete(cur_index)
83 |
84 | def vendor_edit_but_click(self, event):
85 | """Edit vendor list"""
86 | vendor_input = wx.TextEntryDialog(self, _('Use ";" to separate name and email.'),
87 | caption=_('Edit Vendor List'),
88 | value=str(self.selected_vendors.GetStrings() + self.vendor_list.GetStrings()),
89 | style=wx.OK | wx.CANCEL | wx.TE_MULTILINE)
90 | while True:
91 | if vendor_input.ShowModal() == wx.ID_OK:
92 | try:
93 | self.vendor_list.Set(eval(vendor_input.GetValue())) # transfer str to dict
94 | self.selected_vendors.Clear() # reset vendor list
95 | break
96 | except:
97 | wx.MessageBox(message=_('Incorrect Data Format'),
98 | caption=_('ERROR'),
99 | style=wx.OK | wx.ICON_ERROR)
100 | continue
101 | else:
102 | break
103 | vendor_input.Destroy()
104 |
105 | def content_but_click(self, event):
106 | """Build mail content frame"""
107 | AppEmailContentFrame(parent=self).Show(True)
108 | return
109 |
110 | def submit_but_click(self, event):
111 | """Submit sample"""
112 | self.progress_bar = wx.ProgressDialog(title=_('Sending Samples...'),
113 | message='N/A',
114 | maximum=100,
115 | parent=self,
116 | style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE)
117 | Sender.SendingThread(self) # initialize sending thread
118 |
119 | def pack_but_click(self, event):
120 | """Pack samples to desktop path"""
121 | pack = Sender.SampleFiles(self.file_input.GetValue(), self.zip_password) # compressing samples
122 | try:
123 | pack.compress_to_desktop()
124 | except Exception as e:
125 | wx.MessageBox(message=_('Cannot Access Sample File(s). Check paths.\n') +
126 | 'Error: {error}\nInfo: {info}'.format(error=e.__class__.__name__, info=str(e)),
127 | caption=_('ERROR'),
128 | style=wx.OK | wx.ICON_ERROR)
129 | pack.delete_zip()
130 | return
131 |
132 | wx.MessageBox(message=_('Pack to Desktop succeed!'),
133 | caption=_('INFO'),
134 | style=wx.OK | wx.ICON_INFORMATION)
135 |
136 | def close_but_click(self, event):
137 | """Operation before main_frame close"""
138 | self._save_setting()
139 | self._save_list()
140 | self.Destroy()
141 |
142 | def lang_select(self, event):
143 | """Select language and restart App"""
144 | self._save_setting()
145 | self._save_list()
146 | os.startfile(sys.argv[0]) # restart
147 | self.Destroy()
148 |
149 | def _ini_file_drop(self):
150 | """Set file drop target"""
151 | drop_target = FileDropTarget(self)
152 | self.file_input.SetDropTarget(drop_target)
153 |
154 | def _ini_lists(self):
155 | """Initialize vendor list"""
156 | if self.lists is not None:
157 | self.vendor_list.Set(self.lists['vendor_list'])
158 | self.selected_vendors.Set(self.lists['selected_list'])
159 |
160 | def _ini_setting(self):
161 | """parse all settings to GUI """
162 | cf = self.config
163 | sec_str = 'main_frame_string'
164 | sec_bool = 'main_frame_boolean'
165 | sec_int = 'main_frame_int'
166 | sec_txt = 'main_frame_txt'
167 | # iterate to apply all settings
168 | if cf.has_section(sec_str):
169 | for key in cf[sec_str]:
170 | getattr(self, key).SetValue(cf[sec_str][key])
171 | for key in cf[sec_bool]:
172 | getattr(self, key).SetValue(bool(strtobool(cf[sec_bool][key])))
173 | for key in cf[sec_int]:
174 | getattr(self, key).SetSelection(int(cf[sec_int][key]))
175 | for key in cf[sec_txt]:
176 | setattr(self, key, cf[sec_txt][key])
177 | data = self.password_input.GetValue()
178 | # decode email password
179 | self.password_input.SetValue(urlsafe_b64decode(data[2:len(data) - 1]).decode('utf-8'))
180 | else:
181 | # Ini setting if no previous setting found
182 | cf.add_section(sec_str)
183 | cf.add_section(sec_bool)
184 | cf.add_section(sec_int)
185 | cf.add_section(sec_txt)
186 | self.zip_password = 'infected'
187 | self.false_negative_content = _("Hello,\n\nThe attached files may contain threats. Require for the further "
188 | "analysis.\n\nPassword: {password} \n\nThanks! ")
189 | self.false_positive_content = _("Hello,\n\nThe attached files may be clean. Require for the further "
190 | "analysis.\n\nPassword: {password} \n\nThanks! ")
191 |
192 | def _save_list(self):
193 | """Save vendor list"""
194 | self.lists = {
195 | 'vendor_list': self.vendor_list.GetStrings(),
196 | 'selected_list': self.selected_vendors.GetStrings()
197 | }
198 |
199 | def _save_setting(self):
200 | """Save GUI settings to config obj"""
201 | cf = self.config
202 | sec = 'main_frame_string'
203 | cf.set(sec, 'email_account', self.email_account.GetValue())
204 | # convert password to base64 for basic obfuscation
205 | cf.set(sec, 'password_input', str(urlsafe_b64encode(bytes(self.password_input.GetValue(), 'utf-8'))))
206 | cf.set(sec, 'smtp_input', self.smtp_input.GetValue())
207 | cf.set(sec, 'port_input', self.port_input.GetValue())
208 | if self.reme_ps_check.GetValue() is False:
209 | cf.set(sec, 'password_input', '')
210 |
211 | sec = 'main_frame_boolean'
212 | cf.set(sec, 'reme_ps_check', str(self.reme_ps_check.GetValue()))
213 | cf.set(sec, 'false_neg_select', str(self.false_neg_select.GetValue()))
214 | cf.set(sec, 'false_positive_select', str(self.false_positive_select.GetValue()))
215 |
216 | sec = 'main_frame_int'
217 | cf.set(sec, 'language_choice', str(self.language_choice.GetCurrentSelection()))
218 |
219 | sec = 'main_frame_txt'
220 | cf.set(sec, 'zip_password', str(self.zip_password))
221 | cf.set(sec, 'false_negative_content', str(self.false_negative_content))
222 | cf.set(sec, 'false_positive_content', str(self.false_positive_content))
223 |
224 |
225 | class AppEmailContentFrame(GUI.mail_content_frame):
226 | """
227 | Class for handing mail_content_frame event
228 | """
229 | parent_f: AppMainFrame = None # parent frame
230 |
231 | def __init__(self, parent):
232 | """
233 | Initialize frame window
234 | :param parent: wxframe
235 | """
236 | GUI.mail_content_frame.__init__(self, parent)
237 | self.parent_f = parent
238 | self.parent_f.Disable()
239 | self.false_negative_content.SetValue(self.parent_f.false_negative_content)
240 | self.false_positive_content.SetValue(self.parent_f.false_positive_content)
241 |
242 | def return_to_parent(self):
243 | """Operation before closing this frame"""
244 | self.parent_f.Enable()
245 | self.parent_f.SetFocus()
246 | self.Destroy()
247 |
248 | def ok_but_click(self, event):
249 | self.parent_f.false_negative_content = self.false_negative_content.GetValue()
250 | self.parent_f.false_positive_content = self.false_positive_content.GetValue()
251 | self.return_to_parent()
252 |
253 | def cancel_but_click(self, event):
254 | self.return_to_parent()
255 |
256 | def close_but_click(self, event):
257 | self.return_to_parent()
258 |
259 |
260 | class FileDropTarget(wx.FileDropTarget):
261 | """
262 | Config drop target obj
263 | """
264 | frame_p: AppMainFrame = None # frame parameter
265 |
266 | def __init__(self, frame_p):
267 | wx.FileDropTarget.__init__(self)
268 | self.frame_p = frame_p
269 |
270 | def OnDropFiles(self, x, y, data: list):
271 | previous_txt = self.frame_p.file_input.GetValue()
272 | # Check if input textCtrl is empty or start with '#' (Default)
273 | if previous_txt == '' or previous_txt[0] == "#":
274 | previous_txt = ''
275 | self.frame_p.file_input.SetValue(previous_txt)
276 | else:
277 | previous_txt = previous_txt + '\n'
278 | self.frame_p.file_input.SetValue(previous_txt + '\n'.join(data))
279 | return True
280 |
281 |
282 | def app_path(path):
283 | """
284 | Static method to get the abs path special for pyinstaller packing
285 | :param path: relative path
286 | :return: abs path
287 | """
288 | if getattr(sys, 'frozen', False):
289 | app_dir = sys._MEIPASS
290 | else:
291 | app_dir = os.path.dirname(os.path.abspath(__file__))
292 | return os.path.join(app_dir, path)
293 |
294 |
295 | class BaseApp(wx.App):
296 | """
297 | Class for config wxApp; Loading config and language file
298 | """
299 | config_path = os.getenv('APPDATA') + "\\VirusSampleSubmitter" # config file path
300 | config = configparser.ConfigParser() # config obj
301 | lists = None # vendor list
302 |
303 | def OnInit(self):
304 | """Load config and list file"""
305 | if os.path.exists(self.config_path) is False:
306 | os.mkdir(self.config_path)
307 | self.config.read(self.config_path + "\\config.ini")
308 | self._ini_language()
309 | if os.path.exists(self.config_path + "\\list.db") is False:
310 | return True
311 | with open(self.config_path + "\\list.db") as f:
312 | self.lists = eval(f.read())
313 | f.close()
314 | return True
315 |
316 | def OnExit(self):
317 | """Save config and list file"""
318 | self.lists = frame.lists
319 | with open(self.config_path + "\\config.ini", 'w') as config_w:
320 | self.config.write(config_w)
321 | config_w.close()
322 | with open(self.config_path + "\\list.db", "w") as f:
323 | f.write(str(self.lists))
324 | f.close()
325 | return True
326 |
327 | def _ini_language(self):
328 | """Initialize program language"""
329 | self.locale = None
330 | if not self.config.has_section('main_frame_int'):
331 | return
332 | lang = int(self.config['main_frame_int']['language_choice'])
333 | if lang == 1: # Change to zh-cn
334 | self.locale = wx.Locale(wx.LANGUAGE_CHINESE_SIMPLIFIED)
335 | if self.locale.IsOk():
336 | self.locale.AddCatalogLookupPathPrefix(app_path('locale'))
337 | self.locale.AddCatalog('lang_zh_CN')
338 | else:
339 | self.locale = None
340 |
341 |
342 | if __name__ == '__main__':
343 | app = BaseApp(redirect=False)
344 | frame = AppMainFrame(None, app.config, app.lists)
345 | frame.Show(True)
346 | app.MainLoop()
347 |
--------------------------------------------------------------------------------
/GUI.fbp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
2315 |
2316 |
--------------------------------------------------------------------------------