最近睡前把《Ruby元编程》作为枕边书,复习一些元编程技巧。顺手整理记录下这些技巧的同时,也为了这门语言能更广泛地传播,希望有更多的人喜欢Ruby这个神器。

一切都是对象

来到Ruby的世界,请你首先不要被这一切搞晕,实际上这些反而让Ruby的对象模型概念变得更加清晰。一切都是对象,对象是一个类,类也是一个对象。而实际上类是一个带有特殊功能的模块。

1
2
3
4
5
6
7
8
9
10
11
2.5.1 :001 > Class.superclass
=> Module
2.5.1 :002 > Module.superclass
=> Object
2.5.1 :003 > Object.superclass
=> BasicObject
2.5.1 :004 > BasicObject.superclass
=> nil
2.5.1 :005 > Kernel.class
=> Module

模块的超类是BasicObject,BasicObject才是Ruby类系统的根。BasicObject派生了Objce和Kernel,Object又派生出了Module,Module派生出Class。
这像不像道德经里所说的“道生一,一生二,二生三,三生万物”? 再说下去更迷糊了,去Ruby的世界里自己领悟吧。Ruby诞生自亚洲,一直觉得它是一门充满东方哲学的编程语言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2.5.1 :001 > Class.ancestors
=> [Class, Module, Object, Kernel, BasicObject]
2.5.1 :002 > Class.superclass
=> Module
2.5.1 :003 > Module.ancestors
=> [Module, Object, Kernel, BasicObject]
2.5.1 :004 > Module.superclass
=> Object
2.5.1 :005 > Object.ancestors
=> [Object, Kernel, BasicObject]
2.5.1 :006 > Object.superclass
=> BasicObject
2.5.1 :007 > BasicObject.superclass
=> nil
2.5.1 :008 > Kernel.ancestors
=> [Kernel]
2.5.1 :009 > BasicObject.ancestors
=> [BasicObject]

猴子补丁(Monkey Patch)

在元编程的所有技巧中,首先需要了解的就是Ruby的“猴子补丁”,这是一种打开类的技巧。不同于其他面向对象语言,Ruby的任何类都可以在运行态打“补丁”的,称之为Monkey Patch。

例如,对字符类String,可以在任何地方打开这个类,对它添加一些方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2.5.1 :001 > class String
2.5.1 :002?> def test_method
2.5.1 :003?> return self+"AAA"
2.5.1 :004?> end
2.5.1 :005?> end
=> :test_method
2.5.1 :006 > s="aaa"
=> "aaa"
2.5.1 :007 > s.class
=> String
2.5.1 :008 > s.test_method
=> "aaaAAA"
2.5.1 :009 >

在上面的操作中,我们可以看到,对于Ruby的内置类String,我们为其添加了test_method方法,在下文中,任何的String对象便具有了新添加的这个方法,这就是Ruby打开类的魔术。

你甚至可以对内核模块Kernel打一个Monkey Patch,让你的方法在下文中得到全局支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2.5.1 :001 > test
Traceback (most recent call last):
3: from /home/chorder/.rvm/rubies/ruby-2.5.1/bin/irb:11:in <main>
2: from (irb):1
1: from (irb):1:in test
ArgumentError (wrong number of arguments (given 0, expected 2..3))
2.5.1 :002 > module Kernel
2.5.1 :003?> def test
2.5.1 :004?> puts "OK"
2.5.1 :005?> end
2.5.1 :006?> end
=> :test
2.5.1 :007 > test
OK
=> nil
2.5.1 :008 >

不同的是,Kernel是一个模块,需要用module关键字来打开。test方法一开始是不存在的,把它作为“补丁”加入到Kernel模块以后,就可以在下文调用了。

Monkey Patch在有些编程语言里是个贬义词,但在Ruby里,它是一个很实用的功能。

细化(refine)

如你所见,Ruby打开类的作用是如此神奇,以至于Monkey Patch一不小心可能会引发一些大的问题。因此有时候需要配合类的细化功能一起使用。

细化的用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2.5.1 :001 > module Refine
2.5.1 :002?> refine String do
2.5.1 :003 > def test
2.5.1 :004?> return self+"TEST"
2.5.1 :005?> end
2.5.1 :006?> end
2.5.1 :007?> end
=> #<refinement:String@Refine>
2.5.1 :008 > "AAA".test
Traceback (most recent call last):
2: from /home/chorder/.rvm/rubies/ruby-2.5.1/bin/irb:11:in <main>
1: from (irb):8
NoMethodError (private method test called for "AAA":String)
2.5.1 :009 > module Test
2.5.1 :010?> using Refine
2.5.1 :011?> "AAA".test
2.5.1 :012?> end
=> "AAATEST"
2.5.1 :013 >

在一个模块中使用refine关键字定义细化之后,在另一个模块中使用using关键字引入这个用于定义细化的模块,细化定义的作用于就只限于调用细化模块的模块内部。

命名空间(Name Space)

在Ruby中,使用::标识引入不同作用域中的类和模块,用于界定类和模块,防止命名冲突,也让源代码的结构更加清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
2.5.1 :001 > module A
2.5.1 :002?> module B
2.5.1 :003?> class C
2.5.1 :004?> D="Constant"
2.5.1 :005?> end
2.5.1 :006?> end
2.5.1 :007?> end
=> "Constant"
2.5.1 :008 >
2.5.1 :009 > A::B::C::D
=> "Constant"
2.5.1 :010 >

Metasploit Resource脚本是MSF框架提供的非常方便的用于组织自动化任务的工具。简单来说,resource脚本目前可以支持两种类型的指令组合,分别是MSF命令和嵌入式Ruby代码。
以下分别就两种使用方法进行演示。

这里以 multi/handler 这个用于反弹shell监听器的模块来演示。

使用makerc命令制作metasploit命令行resource脚本

执行msfconsole启动MSF控制台以后,执行如下操作:

1
2
3
4
5
6
7
8
9
10
msf5 > use multi/handler
msf5 exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf5 exploit(multi/handler) > set lhost 0.0.0.0
lhost => 0.0.0.0
msf5 exploit(multi/handler) > set lport 4444
lport => 4444
msf5 exploit(multi/handler) > makerc /root/test.rc
[*] Saving last 4 commands to /root/test.rc ...
msf5 exploit(multi/handler) >

将会在/root/目录下生成test.rc文件,其中记录着刚刚输入的所有命令。下次需要执行同样操作的时候,只需要运行msfconsole -r /root/test.rc即可载入和执行这次的命令记录。

使用run_single函数编写基于嵌入式Ruby的resource脚本

除了直接编写指令代码以外,Resource脚本还可以支持嵌入式Ruby脚本,只需要在.rc文件中使用<ruby></ruby>标签引入Ruby代码即可。

这里将上面用于启动handler模块的脚本稍微改良一下,变成一个易于使用的rc文件,代码如下:

handle.rc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

<ruby>

def init_args

args = {}

if ARGV.join('') =~ /^help$/i

args[:help] = true

return args

end

args[:payload] = ARGV.shift
args[:host] = ARGV.shift
args[:port] = ARGV.shift

return args

end


# resource process entry

begin

args = init_args

if args[:help]

print_line("Usage:\nmsfconsole handler.rc PAYLOAD HOST PORT")

return

end

#run_single("workspace handler")
run_single("use multi/handler")
run_single("set payload #{args[:payload]}")
run_single("set lhost #{args[:host]}")
run_single("set lport #{args[:port]}")

rescue ArgumentError => e

print_error("Invalid argument: #{e.message}")

return

rescue RuntimeError => e

print_error(e.message)

return

rescue ::Exception => e

raise e

end

</ruby>

将上面的代码保存为handle.rc,启动MSF控制台时,执行:

1
msfconsole -r handler.rc windows/meterpreter/reverse_tcp 0.0.0.0 4444

即可快速地配置好配备相应payload的handler监听器。

其中Ruby代码中的run_single就是用于执行MSF框架指令的方法。

MSF自带了很多方便实用的脚本,位于MSF框架目录/scripts/resource/目录下,可以参考这些脚本的写法,写出更加方便的脚本。

参考链接:

https://metasploit.help.rapid7.com/docs/resource-scripts

在Debian系Linux中,用于标识应用的启动文件.desktop file是位于/usr/share/applications目录下的,Gnome会将这些文件在菜单中展示为启动图标,也可以固定在docker bar。

打开/usr/share/applications,可以看到有很多的.desktop文件,每一个文件就对应菜单中的一个启动图标。

如何手动编辑和制作这样一个.desktop文件呢,这里以IDEA集成开发环境为例。
我的IDEA安装在/opt/idea-IC-182.4505.22/目录,IDEA的启动脚本是/opt/idea-IC-182.4505.22/bin/idea.sh。打开/opt/idea-IC-182.4505.22/目录,还可以看到IDEA的图标文件/opt/idea-IC-182.4505.22/bin/idea.png

所以我们在/opt/idea-IC-182.4505.22/目录下创建IDEA.desktop文件,内容如下:

1
2
3
4
5
6
7
8
9
10
[Desktop Entry]
Name=IDEA
Encoding=UTF-8
Exec=/opt/idea-IC-182.4505.22/bin/idea.sh
Icon=/opt/idea-IC-182.4505.22/bin/idea.png
StartupNotify=false
Terminal=false
Type=Application
#Categories=

再将IDEA.desktop通过软链接添加到/usr/share/applications目录即可。

1
ls -s /opt/idea-IC-182.4505.22/IDEA.desktop /usr/share/applications/IDEA.desktop

再次打开菜单,即可看见创建的启动图标。如果看不到,可以先注销,再重新登录即可。

再分享两个常用的.desktop文件:

burpsuite(可用在kali上)

1
2
3
4
5
6
7
8
9
10
11
[Desktop Entry]
Name=burpsuite-pro-2
Encoding=UTF-8
Exec=sh -c "java -Xbootclasspath/p:/opt/burpsuite_pro_2.0.09/burp-loader-keygen.jar -jar /opt/burpsuite_pro_2.0.09/burpsuite_pro_v2.0.09beta.jar"
Icon=kali-burpsuite.png
StartupNotify=false
Terminal=false
Type=Application
#Categories=03-webapp-analysis;03-06-web-application-proxies;
X-Kali-Package=burpsuite

JD-GUI

1
2
3
4
5
6
7
8
9
10
[Desktop Entry]
Name=JD-GUI
Encoding=UTF-8
Exec=sh -c "java -jar /opt/jd-gui-1.4.0/jd-gui-1.4.0.jar"
Icon=/usr/share/icons/hicolor/128x128/apps/jd-gui.png
StartupNotify=false
Terminal=false
Type=Application
#Categories=03-webapp-analysis;03-06-web-application-proxies;
Name[en_US]=JD-GUI

jar命令是用来将java编译产生的.class文件打包成jar包的工具。
Jar包可以方便地对外发布,甚至就像exe一样易于使用。

这里以一个工具类JarParser为例,这个示例程序是用来遍历Jar包中的类的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*JarParser.java*/
/*源码转载自:https://blog.csdn.net/lululove19870526/article/details/78837119*/

import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class JarParser {

public static void main(String args[]) {
if(args.length<1) System.exit(0);
String jar = args[0];
try {
JarFile jarFile = new JarFile(jar);
Enumeration enu = jarFile.entries();
while (enu.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) enu.nextElement();
String name = jarEntry.getName();
if (name.endsWith(".class"))
System.out.println("Class: "+name.replace('/','.'));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}


使用javac命令编译这个Java文件:javac JarParser.java

接着创建一个文本文件,这个文件用来作为jar包中的MANIFEST.MF描述文件。

1
echo Main-Class: JarParser > test.mf

这句Main-Class的作用是指明Jar包的入口类。

最后使用jar命令打包:

1
jar cvfm JarParser.jar test.mf JarParser.class

生成的jar包使用java -jar命令运行。

系统环境:

  • Ruby: ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
  • Rails: Rails 5.2.2
  • Gem: 2.7.7
  • SQLite: sqlite3 (1.4.0, 1.3.13)

今天在基于上面的环境使用Rails新建项目的时候,报了一个ActiveRecord::ConnectionNotEstablished错误。
根据排查,是由于SQLite3 gem版本问题导致的。
具体的报错内容如下:

1
Puma caught this error: Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile. (LoadError)

页面报错如下:

1
2
3
ActiveRecord::ConnectionNotEstablished
No connection pool with 'primary' found.

后来找到了解决方法,修改Gemfile,将sqlite3的版本降级到1.4.0以下即可。
修改Gemfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.5.1'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.2'
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '< 1.4.0' # <<修改这里,将sqlite3版本设定为小于1.4.0
# Use Puma as the app server
gem 'puma', '~> 3.11'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'mini_racer', platforms: :ruby

# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false

group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '>= 2.15'
gem 'selenium-webdriver'
# Easy installation and use of chromedriver to run system tests with Chrome
gem 'chromedriver-helper'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]


修改完后执行bundle即可。

你在MongoDB启动时也遇到以下警告吗?

1
2
3
WARNING: You are running on a NUMA machine.
We suggest launching mongod like this to avoid performance problems:
numactl –interleave=all mongod [other options]

这是当MongoDB运行在非统一内存访问架构 NUMA(Non Uniform Memory Access Architecture)下产生的性能告警。
关于NUMA是什么,可以参考这篇文章:Non-uniform memory access

这里主要提一下在CentOS 7系统、NUMA架构服务器中上述告警该如何消除。

首先需要使用numactl这个命令,而这条命令服务器中默认是没有的,在支持NUMA架构的CentOS7服务器中,可以运行yum install numactl来安装它。

接着关闭MongoDB服务:
systemctl stop mongod

disable掉MongoDB服务的自动启动:
systemctl disable mongod

使用numactl来启动MongoDB:
numactl --interleave=all mongod --config /etc/mongod.conf

如果想要开机自动使用numactl启动MongoDB,将上述命令写到开机启动的脚本文件中即可。

笔者尝试修改mongod.service服务描述中的ExecStart选项,尝试将numctl作为mongod服务的执行体时报了很多错,如果你有相关的解决方法,欢迎来信或在评论里探讨。

笔者有一台CentOS 7系统的Linux服务器,在通过ssh远程连接的时候,一直存在两个问题。
一是连通速度缓慢,ping服务器速度很快,但是SSH连接的响应非常慢。
再有就是连接一段时间,客户端这边如果没有操作和输入,服务器就会自动断开,每次都要重新连,很麻烦。

昨天晚上仔细检查了一下CentOS 7 sshd的配置,终于解决了这两个问题。其实很easy,修改CentOS默认的sshd配置就行了。

针对第一个ssh连接响应缓慢的问题,在/etc/passwd文件里这样配置就行了:

大概是在第129行的位置,找到UseDNS选项,取消前面的注释,将yes改为no即可:

1
2
3
4
5
6
7
127 #ClientAliveCountMax 3
128 #ShowPatchLevel no
129 UseDNS no
130 #PidFile /var/run/sshd.pid
131 #MaxStartups 10:30:100
132 #PermitTunnel no
133 #ChrootDirectory none

针对第二个连接超时timeout的问题,同样是修改sshd配置,大概在第126行的位置,找到选项ClientAliveInterval,将其值修改为30:

1
2
3
4
5
6
123 UsePrivilegeSeparation sandbox          # Default for new installations.
124 #PermitUserEnvironment no
125 #Compression delayed
126 ClientAliveInterval 30
127 #ClientAliveCountMax 3
128 #ShowPatchLevel no

这样ssh服务器就会每隔30秒判断一次客户端是否超时,由于30秒一般是不会超时的,所以连接就能持续。
而第127行的选项也可以关注一下,它代表的是最大的超时次数。

完美修复文章开头提到的两个问题。

一、巢湖

乙亥年正月初六午后,动身回宁。
驱车至合安高速入口,进错车道,于是原本打算途径合肥的路线,临时改成先至马鞍山再北上。沿途一路雪景,甚是秀美。沿着省道过了巢湖,就来到了安徽的含山县。谁知大雪封路,高速不得上,只得在交警的指挥下改道。于是重新探路,在那含山县城乱转悠。此时汽油已经不多了,只能勉强再行个两百里。人生地不孰,一路留意着苏A牌照的车,想着最好遇个返程的老司机带路,免得找错。先是遇到个雪佛兰,那哥们和我一样,也是被警察叔叔拦着不让上高速的,等红灯的时候跟他聊了几句,听他说走和县,沿小路到马鞍山。我心里也差不多是这个思路,于是一脚油门先溜了。
走了不久就走错了路,含山县里多处修路、封路,找路很困难,偏偏车机这个时候也没什么信号。过了一会凭着感觉从拐错的路口绕了出来,过了个红灯以后,遇到一辆市区出租车,上前打了个招呼,问他是不是回南京,师傅人很好,直接说,你跟着我走吧。于是一起很省心的开了一段,车机信号这个时候恢复了,但是都没怎么看导航。老师傅就是老师傅,一路节奏带的飞起,好几段堵成深红色的路段,跟着他从加油站、小道绕路,足足节省了至少四十分钟!
中途有一个实在很堵的点,老师傅也过不去了,停下来观望,拿着电动剃须刀站在车外剃起了胡子。我带了包玉溪烟下车,他摆摆手说不抽。聊了聊路况和导航,他问我用的什么,我说腾讯地图,他说换高德,等下一起从乌江到江浦,过了长江大桥就到了。后来我下了一个高德,用起来其实差不多,反而林志玲的声音不如腾讯地图里的妲己。中间因为下载高德地图,跟丢了老师傅,我就一路自己开着两个导航,仪表盘上显示只能开90公里了。
路过一个国道上的石化加油站,去补充汽油,在加油站的洗手间里竟又遇到了老师傅。心想这回真是巧了,就打了个招呼,他说加个微信,等下他在前面开,让我不认识路就呼他,他可能以为我一直在后面跟着。加了微信,他还把我的会话置顶了,贴心啊。他说微信昵称是他法号,是位道教信徒。我表示对道家思想也挺有兴趣的,回南京有机会一起交流。

二、乌江

从含山继续出发,一路上和道友老师傅用微信简短地讨论了路况。一个小时左右,便到了和县乌江。
想到这曾是西楚霸王项羽自刎的地方,不禁唏嘘。
当年霸王被刘邦围困垓下,带领八百将士突围至此,夜半帐中楚歌四起,军心涣散,虞姬也先抹剑而去。为了面子,为了证明自己“不是不会打仗”,而是“天要亡我”,霸王带着必死的决心亲自迎斩敌将,吓的围军丧胆,也让一众部下服了最后一口气。但最终损员折将,失了兵马,身负重伤。因为面子而出战的人,也要为了面子而赴死,
其实何为天道?何为颜面?既信了天道,又怎谓人言?只怕是输在了心态。否则克制一时的崩溃渡到江东,江东富饶广阔,又有天险阻隔。刘邦阻击项羽,兵力早已残损。更有支持自己的江东子弟,一呼百应。无论如何都值得再去拼他一次。已经在沙场上失去了身旁的战友,又在帐中告别了心爱的女人,还有什么可以失去的呢?还有什么是害怕失去的呢。后人每当想起这位刚愎自用,却又威武神勇的霸王,都常常替他惋惜。李清照诗云:

生当作人杰,死亦为鬼雄。
至今思项羽,不肯过江东。

从历史角度看,项羽听信谗言,疏远了得力又忠心的老臣范增,就是他最终将会失败的一个前兆。《史记·高祖本纪》中记载,刘邦曾经如此评价项羽:”项羽有一范增而不能用,此其所以为我擒也。”。在昔日的对手眼里,即使世人视为神勇的霸王,也不过只是一个必须拔除的障碍罢了。
说到刘邦,就不得不说,项羽屡次亲自挑战,刘邦从不正面回应。项甚至摆下鸿门宴以图杀之,却依然让放低姿态的刘邦,骑着白马,借口脱逃。鸿门宴时项羽有范增、项庄,献计舞剑,沛公亦有樊哙、张良,鞍前马后,莫论成功与否,成事一定离不开用臣。自古先贤们的智慧碰撞揭示着人性的真相,小人沟通乃至大国邦交,谁能没有胸襟跟视野、谋略和手段、演技与心肠呢。
路过乌江时天色已晚,穿过乌江地域,便穿过了苏皖两地的交界,从和县到了江浦,渡过长江,即至金陵。

三、后记

傍晚开车请注意:道路千万条,安全第一条。行车不规范,钱包两行泪。

庆幸有这一片幽谷得以避世,有这一座空山可以守寂。

信步闲庭,纵情山水。

逆旅徒行,万物大千皆是过客。

喧嚣与纷扰,尘埃落定。繁华和落寞,过眼云烟。

令人亦步亦趋的,是这匆匆的旅途,浮沉起落之间。

令人沉醉又着迷的,是这人生的时光,如同白驹过隙。

那些刹那又永恒的幸福,纯粹的喜悦。

那些短暂的相聚,相聚又离别。

Python 3 通过SMTP库发送普通邮件(Through SSL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/python3

import smtplib
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

mail_host = "smtp.chorder.net"
mail_port = 994
mail_user = "[email protected]"
mail_pass = "password"

sender_name = "Example"
sender_account = '[email protected]'
receivers = ['[email protected]']
receiver_name = "Somebody"

subject = 'Mail Subject'
mail_msg = '''
<h1>This is a test main</h1>
<p>Some text</p>
'''


msgRoot = MIMEMultipart('related')
msgRoot['From'] = Header(sender_name, 'utf-8')
msgRoot['To'] = Header(receiver_name, 'utf-8')
msgRoot['Subject'] = Header(subject, 'utf-8')

msgAlternative = MIMEMultipart('alternative')
msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8'))

msgRoot.attach(msgAlternative)

try:
smtpObj = smtplib.SMTP_SSL( mail_host, mail_port )
#smtpObj.ehlo()
#smtpObj.set_debuglevel(1)
smtpObj.login( mail_user, mail_pass )
smtpObj.sendmail(sender_account, receivers, msgRoot.as_string())
print ("邮件发送成功")
except smtplib.SMTPException:
print ("Error: 无法发送邮件")

Python 3 通过SMTP库发送带图片的邮件(Through SSL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/python3

import smtplib
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

mail_host = "smtp.chorder.net"
mail_port = 994
mail_user = "[email protected]"
mail_pass = "password"

sender_name = "Example"
sender_account = '[email protected]'
receivers = ['[email protected]']
receiver_name = "Somebody"


subject = 'Mail Subject'
mail_msg = '''
<h1>This is a test main</h1>
<p>Some text</p>
<h2>Image</h2>
<p><img src="cid:image1"></p>
'''

# add image attachment
fp = open('test.png', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()

msgImage.add_header('Content-ID', '<image1>')

msgRoot = MIMEMultipart('related')

msgRoot['From'] = Header(sender_name, 'utf-8')
msgRoot['To'] = Header(receiver_name, 'utf-8')
msgRoot['Subject'] = Header(subject, 'utf-8')

msgAlternative = MIMEMultipart('alternative')
msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8'))

msgRoot.attach(msgAlternative)
msgRoot.attach(msgImage)

try:
smtpObj = smtplib.SMTP_SSL( mail_host, mail_port )
#smtpObj.ehlo()
#smtpObj.set_debuglevel(1)
smtpObj.login( mail_user, mail_pass )
smtpObj.sendmail(sender_account, receivers, msgRoot.as_string())
print ("邮件发送成功")
except smtplib.SMTPException:
print ("Error: 无法发送邮件")

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×