- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章使用PHP如何实现高效安全的ftp服务器(二)由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
在上篇文章给大家介绍了使用PHP如何实现高效安全的ftp服务器(一),感兴趣的朋友可以点击了解详情。接下来通过本篇文章给大家介绍使用PHP如何实现高效安全的ftp服务器(二),具体内容如下所示:
1.实现用户类CUser.
用户的存储采用文本形式,将用户数组进行json编码。 。
用户文件格式:
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
|
*
array
(
*
'user1'
=>
array
(
*
'pass'
=>
''
,
*
'group'
=>
''
,
*
'home'
=>
'/home/ftp/'
,
//ftp主目录
*
'active'
=>true,
*
'expired=>'
2015-12-12',
*
'description'
=>
''
,
*
'email'
=>
''
,
*
'folder'
=>
array
(
*
//可以列出主目录下的文件和目录,但不能创建和删除,也不能进入主目录下的目录
*
//前1-5位是文件权限,6-9是文件夹权限,10是否继承(inherit)
*
array
(
'path'
=>
'/home/ftp/'
,
'access'
=>
'RWANDLCNDI'
),
*
//可以列出/home/ftp/a/下的文件和目录,可以创建和删除,可以进入/home/ftp/a/下的子目录,可以创建和删除。
*
array
(
'path'
=>
'/home/ftp/a/'
,
'access'
=>
'RWAND-----'
),
* ),
*
'ip'
=>
array
(
*
'allow'
=>
array
(ip1,ip2,...),
//支持*通配符: 192.168.0.*
*
'deny'
=>
array
(ip1,ip2,...)
* )
* )
* )
*
* 组文件格式:
*
array
(
*
'group1'
=>
array
(
*
'home'
=>
'/home/ftp/dept1/'
,
*
'folder'
=>
array
(
*
* ),
*
'ip'
=>
array
(
*
'allow'
=>
array
(ip1,ip2,...),
*
'deny'
=>
array
(ip1,ip2,...)
* )
* )
* )
|
文件夹和文件的权限说明:
* 文件权限 * R读 : 允许用户读取(即下载)文件。该权限不允许用户列出目录内容,执行该操作需要列表权限。 * W写: 允许用户写入(即上传)文件。该权限不允许用户修改现有的文件,执行该操作需要追加权限。 * A追加: 允许用户向现有文件中追加数据。该权限通常用于使用户能够对部分上传的文件进行续传。 * N重命名: 允许用户重命名现有的文件。 * D删除: 允许用户删除文件。 * * 目录权限 * L列表: 允许用户列出目录中包含的文件。 * C创建: 允许用户在目录中新建子目录。 * N重命名: 允许用户在目录中重命名现有子目录。 * D删除: 允许用户在目录中删除现有子目录。注意: 如果目录包含文件,用户要删除目录还需要具有删除文件权限。 * * 子目录权限 * I继承: 允许所有子目录继承其父目录具有的相同权限。继承权限适用于大多数情况,但是如果访问必须受限于子文件夹,例如实施强制访问控制(Mandatory Access Control)时,则取消继承并为文件夹逐一授予权限。 * 。
实现代码如下: 。
。
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
|
class
User{
const
I = 1;
// inherit
const
FD = 2;
// folder delete
const
FN = 4;
// folder rename
const
FC = 8;
// folder create
const
FL = 16;
// folder list
const
D = 32;
// file delete
const
N = 64;
// file rename
const
A = 128;
// file append
const
W = 256;
// file write (upload)
const
R = 512;
// file read (download)
private
$hash_salt
=
''
;
private
$user_file
;
private
$group_file
;
private
$users
=
array
();
private
$groups
=
array
();
private
$file_hash
=
''
;
public
function
__construct(){
$this
->user_file = BASE_PATH.
'/conf/users'
;
$this
->group_file = BASE_PATH.
'/conf/groups'
;
$this
->reload();
}
/**
* 返回权限表达式
* @param int $access
* @return string
*/
public
static
function
AC(
$access
){
$str
=
''
;
$char
=
array
(
'R'
,
'W'
,
'A'
,
'N'
,
'D'
,
'L'
,
'C'
,
'N'
,
'D'
,
'I'
);
for
(
$i
= 0;
$i
< 10;
$i
++){
if
(
$access
& pow(2,9-
$i
))
$str
.=
$char
[
$i
];
else
$str
.=
'-'
;
}
return
$str
;
}
/**
* 加载用户数据
*/
public
function
reload(){
$user_file_hash
= md5_file(
$this
->user_file);
$group_file_hash
= md5_file(
$this
->group_file);
if
(
$this
->file_hash != md5(
$user_file_hash
.
$group_file_hash
)){
if
((
$user
=
file_get_contents
(
$this
->user_file)) !== false){
$this
->users = json_decode(
$user
,true);
if
(
$this
->users){
//folder排序
foreach
(
$this
->users
as
$user
=>
$profile
){
if
(isset(
$profile
[
'folder'
])){
$this
->users[
$user
][
'folder'
] =
$this
->sortFolder(
$profile
[
'folder'
]);
}
}
}
}
if
((
$group
=
file_get_contents
(
$this
->group_file)) !== false){
$this
->groups = json_decode(
$group
,true);
if
(
$this
->groups){
//folder排序
foreach
(
$this
->groups
as
$group
=>
$profile
){
if
(isset(
$profile
[
'folder'
])){
$this
->groups[
$group
][
'folder'
] =
$this
->sortFolder(
$profile
[
'folder'
]);
}
}
}
}
$this
->file_hash = md5(
$user_file_hash
.
$group_file_hash
);
}
}
/**
* 对folder进行排序
* @return array
*/
private
function
sortFolder(
$folder
){
uasort(
$folder
,
function
(
$a
,
$b
){
return
strnatcmp
(
$a
[
'path'
],
$b
[
'path'
]);
});
$result
=
array
();
foreach
(
$folder
as
$v
){
$result
[] =
$v
;
}
return
$result
;
}
/**
* 保存用户数据
*/
public
function
save(){
file_put_contents
(
$this
->user_file, json_encode(
$this
->users),LOCK_EX);
file_put_contents
(
$this
->group_file, json_encode(
$this
->groups),LOCK_EX);
}
/**
* 添加用户
* @param string $user
* @param string $pass
* @param string $home
* @param string $expired
* @param boolean $active
* @param string $group
* @param string $description
* @param string $email
* @return boolean
*/
public
function
addUser(
$user
,
$pass
,
$home
,
$expired
,
$active
=true,
$group
=
''
,
$description
=
''
,
$email
=
''
){
$user
=
strtolower
(
$user
);
if
(isset(
$this
->users[
$user
]) ||
empty
(
$user
)){
return
false;
}
$this
->users[
$user
] =
array
(
'pass'
=> md5(
$user
.
$this
->hash_salt.
$pass
),
'home'
=>
$home
,
'expired'
=>
$expired
,
'active'
=>
$active
,
'group'
=>
$group
,
'description'
=>
$description
,
'email'
=>
$email
,
);
return
true;
}
/**
* 设置用户资料
* @param string $user
* @param array $profile
* @return boolean
*/
public
function
setUserProfile(
$user
,
$profile
){
$user
=
strtolower
(
$user
);
if
(
is_array
(
$profile
) && isset(
$this
->users[
$user
])){
if
(isset(
$profile
[
'pass'
])){
$profile
[
'pass'
] = md5(
$user
.
$this
->hash_salt.
$profile
[
'pass'
]);
}
if
(isset(
$profile
[
'active'
])){
if
(!
is_bool
(
$profile
[
'active'
])){
$profile
[
'active'
] =
$profile
[
'active'
] ==
'true'
? true : false;
}
}
$this
->users[
$user
] =
array_merge
(
$this
->users[
$user
],
$profile
);
return
true;
}
return
false;
}
/**
* 获取用户资料
* @param string $user
* @return multitype:|boolean
*/
public
function
getUserProfile(
$user
){
$user
=
strtolower
(
$user
);
if
(isset(
$this
->users[
$user
])){
return
$this
->users[
$user
];
}
return
false;
}
/**
* 删除用户
* @param string $user
* @return boolean
*/
public
function
delUser(
$user
){
$user
=
strtolower
(
$user
);
if
(isset(
$this
->users[
$user
])){
unset(
$this
->users[
$user
]);
return
true;
}
return
false;
}
/**
* 获取用户列表
* @return array
*/
public
function
getUserList(){
$list
=
array
();
if
(
$this
->users){
foreach
(
$this
->users
as
$user
=>
$profile
){
$list
[] =
$user
;
}
}
sort(
$list
);
return
$list
;
}
/**
* 添加组
* @param string $group
* @param string $home
* @return boolean
*/
public
function
addGroup(
$group
,
$home
){
$group
=
strtolower
(
$group
);
if
(isset(
$this
->groups[
$group
])){
return
false;
}
$this
->groups[
$group
] =
array
(
'home'
=>
$home
);
return
true;
}
/**
* 设置组资料
* @param string $group
* @param array $profile
* @return boolean
*/
public
function
setGroupProfile(
$group
,
$profile
){
$group
=
strtolower
(
$group
);
if
(
is_array
(
$profile
) && isset(
$this
->groups[
$group
])){
$this
->groups[
$group
] =
array_merge
(
$this
->groups[
$group
],
$profile
);
return
true;
}
return
false;
}
/**
* 获取组资料
* @param string $group
* @return multitype:|boolean
*/
public
function
getGroupProfile(
$group
){
$group
=
strtolower
(
$group
);
if
(isset(
$this
->groups[
$group
])){
return
$this
->groups[
$group
];
}
return
false;
}
/**
* 删除组
* @param string $group
* @return boolean
*/
public
function
delGroup(
$group
){
$group
=
strtolower
(
$group
);
if
(isset(
$this
->groups[
$group
])){
unset(
$this
->groups[
$group
]);
foreach
(
$this
->users
as
$user
=>
$profile
){
if
(
$profile
[
'group'
] ==
$group
)
$this
->users[
$user
][
'group'
] =
''
;
}
return
true;
}
return
false;
}
/**
* 获取组列表
* @return array
*/
public
function
getGroupList(){
$list
=
array
();
if
(
$this
->groups){
foreach
(
$this
->groups
as
$group
=>
$profile
){
$list
[] =
$group
;
}
}
sort(
$list
);
return
$list
;
}
/**
* 获取组用户列表
* @param string $group
* @return array
*/
public
function
getUserListOfGroup(
$group
){
$list
=
array
();
if
(isset(
$this
->groups[
$group
]) &&
$this
->users){
foreach
(
$this
->users
as
$user
=>
$profile
){
if
(isset(
$profile
[
'group'
]) &&
$profile
[
'group'
] ==
$group
){
$list
[] =
$user
;
}
}
}
sort(
$list
);
return
$list
;
}
/**
* 用户验证
* @param string $user
* @param string $pass
* @param string $ip
* @return boolean
*/
public
function
checkUser(
$user
,
$pass
,
$ip
=
''
){
$this
->reload();
$user
=
strtolower
(
$user
);
if
(isset(
$this
->users[
$user
])){
if
(
$this
->users[
$user
][
'active'
] && time() <=
strtotime
(
$this
->users[
$user
][
'expired'
])
&&
$this
->users[
$user
][
'pass'
] == md5(
$user
.
$this
->hash_salt.
$pass
)){
if
(
empty
(
$ip
)){
return
true;
}
else
{
//ip验证
return
$this
->checkIP(
$user
,
$ip
);
}
}
else
{
return
false;
}
}
return
false;
}
/**
* basic auth
* @param string $base64
*/
public
function
checkUserBasicAuth(
$base64
){
$base64
= trim(
str_replace
(
'Basic '
,
''
,
$base64
));
$str
=
base64_decode
(
$base64
);
if
(
$str
!== false){
list(
$user
,
$pass
) =
explode
(
':'
,
$str
,2);
$this
->reload();
$user
=
strtolower
(
$user
);
if
(isset(
$this
->users[
$user
])){
$group
=
$this
->users[
$user
][
'group'
];
if
(
$group
==
'admin'
&&
$this
->users[
$user
][
'active'
] && time() <=
strtotime
(
$this
->users[
$user
][
'expired'
])
&&
$this
->users[
$user
][
'pass'
] == md5(
$user
.
$this
->hash_salt.
$pass
)){
return
true;
}
else
{
return
false;
}
}
}
return
false;
}
/**
* 用户登录ip验证
* @param string $user
* @param string $ip
*
* 用户的ip权限继承组的IP权限。
* 匹配规则:
* 1.进行组允许列表匹配;
* 2.如同通过,进行组拒绝列表匹配;
* 3.进行用户允许匹配
* 4.如果通过,进行用户拒绝匹配
*
*/
public
function
checkIP(
$user
,
$ip
){
$pass
= false;
//先进行组验证
$group
=
$this
->users[
$user
][
'group'
];
//组允许匹配
if
(isset(
$this
->groups[
$group
][
'ip'
][
'allow'
])){
foreach
(
$this
->groups[
$group
][
'ip'
][
'allow'
]
as
$addr
){
$pattern
=
'/'
.
str_replace
(
'*'
,
'\d+'
,
str_replace
(
'.'
,
'\.'
,
$addr
)).
'/'
;
if
(preg_match(
$pattern
,
$ip
) && !
empty
(
$addr
)){
$pass
= true;
break
;
}
}
}
//如果允许通过,进行拒绝匹配
if
(
$pass
){
if
(isset(
$this
->groups[
$group
][
'ip'
][
'deny'
])){
foreach
(
$this
->groups[
$group
][
'ip'
][
'deny'
]
as
$addr
){
$pattern
=
'/'
.
str_replace
(
'*'
,
'\d+'
,
str_replace
(
'.'
,
'\.'
,
$addr
)).
'/'
;
if
(preg_match(
$pattern
,
$ip
) && !
empty
(
$addr
)){
$pass
= false;
break
;
}
}
}
}
if
(isset(
$this
->users[
$user
][
'ip'
][
'allow'
])){
foreach
(
$this
->users[
$user
][
'ip'
][
'allow'
]
as
$addr
){
$pattern
=
'/'
.
str_replace
(
'*'
,
'\d+'
,
str_replace
(
'.'
,
'\.'
,
$addr
)).
'/'
;
if
(preg_match(
$pattern
,
$ip
) && !
empty
(
$addr
)){
$pass
= true;
break
;
}
}
}
if
(
$pass
){
if
(isset(
$this
->users[
$user
][
'ip'
][
'deny'
])){
foreach
(
$this
->users[
$user
][
'ip'
][
'deny'
]
as
$addr
){
$pattern
=
'/'
.
str_replace
(
'*'
,
'\d+'
,
str_replace
(
'.'
,
'\.'
,
$addr
)).
'/'
;
if
(preg_match(
$pattern
,
$ip
) && !
empty
(
$addr
)){
$pass
= false;
break
;
}
}
}
}
echo
date
(
'Y-m-d H:i:s'
).
" [debug]\tIP ACCESS:"
.
' '
.(
$pass
?
'true'
:
'false'
).
"\n"
;
return
$pass
;
}
/**
* 获取用户主目录
* @param string $user
* @return string
*/
public
function
getHomeDir(
$user
){
$user
=
strtolower
(
$user
);
$group
=
$this
->users[
$user
][
'group'
];
$dir
=
''
;
if
(
$group
){
if
(isset(
$this
->groups[
$group
][
'home'
]))
$dir
=
$this
->groups[
$group
][
'home'
];
}
$dir
= !
empty
(
$this
->users[
$user
][
'home'
])?
$this
->users[
$user
][
'home'
]:
$dir
;
return
$dir
;
}
//文件权限判断
public
function
isReadable(
$user
,
$path
){
$result
=
$this
->getPathAccess(
$user
,
$path
);
if
(
$result
[
'isExactMatch'
]){
return
$result
[
'access'
][0] ==
'R'
;
}
else
{
return
$result
[
'access'
][0] ==
'R'
&&
$result
[
'access'
][9] ==
'I'
;
}
}
public
function
isWritable(
$user
,
$path
){
$result
=
$this
->getPathAccess(
$user
,
$path
);
if
(
$result
[
'isExactMatch'
]){
return
$result
[
'access'
][1] ==
'W'
;
}
else
{
return
$result
[
'access'
][1] ==
'W'
&&
$result
[
'access'
][9] ==
'I'
;
}
}
public
function
isAppendable(
$user
,
$path
){
$result
=
$this
->getPathAccess(
$user
,
$path
);
if
(
$result
[
'isExactMatch'
]){
return
$result
[
'access'
][2] ==
'A'
;
}
else
{
return
$result
[
'access'
][2] ==
'A'
&&
$result
[
'access'
][9] ==
'I'
;
}
}
public
function
isRenamable(
$user
,
$path
){
$result
=
$this
->getPathAccess(
$user
,
$path
);
if
(
$result
[
'isExactMatch'
]){
return
$result
[
'access'
][3] ==
'N'
;
}
else
{
return
$result
[
'access'
][3] ==
'N'
&&
$result
[
'access'
][9] ==
'I'
;
}
}
public
function
isDeletable(
$user
,
$path
){
$result
=
$this
->getPathAccess(
$user
,
$path
);
if
(
$result
[
'isExactMatch'
]){
return
$result
[
'access'
][4] ==
'D'
;
}
else
{
return
$result
[
'access'
][4] ==
'D'
&&
$result
[
'access'
][9] ==
'I'
;
}
}
//目录权限判断
public
function
isFolderListable(
$user
,
$path
){
$result
=
$this
->getPathAccess(
$user
,
$path
);
if
(
$result
[
'isExactMatch'
]){
return
$result
[
'access'
][5] ==
'L'
;
}
else
{
return
$result
[
'access'
][5] ==
'L'
&&
$result
[
'access'
][9] ==
'I'
;
}
}
public
function
isFolderCreatable(
$user
,
$path
){
$result
=
$this
->getPathAccess(
$user
,
$path
);
if
(
$result
[
'isExactMatch'
]){
return
$result
[
'access'
][6] ==
'C'
;
}
else
{
return
$result
[
'access'
][6] ==
'C'
&&
$result
[
'access'
][9] ==
'I'
;
}
}
public
function
isFolderRenamable(
$user
,
$path
){
$result
=
$this
->getPathAccess(
$user
,
$path
);
if
(
$result
[
'isExactMatch'
]){
return
$result
[
'access'
][7] ==
'N'
;
}
else
{
return
$result
[
'access'
][7] ==
'N'
&&
$result
[
'access'
][9] ==
'I'
;
}
}
public
function
isFolderDeletable(
$user
,
$path
){
$result
=
$this
->getPathAccess(
$user
,
$path
);
if
(
$result
[
'isExactMatch'
]){
return
$result
[
'access'
][8] ==
'D'
;
}
else
{
return
$result
[
'access'
][8] ==
'D'
&&
$result
[
'access'
][9] ==
'I'
;
}
}
/**
* 获取目录权限
* @param string $user
* @param string $path
* @return array
* 进行最长路径匹配
*
* 返回:
* array(
* 'access'=>目前权限
* ,'isExactMatch'=>是否精确匹配
*
* );
*
* 如果精确匹配,则忽略inherit.
* 否则应判断是否继承父目录的权限,
* 权限位表:
* +---+---+---+---+---+---+---+---+---+---+
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
* +---+---+---+---+---+---+---+---+---+---+
* | R | W | A | N | D | L | C | N | D | I |
* +---+---+---+---+---+---+---+---+---+---+
* | FILE | FOLDER |
* +-------------------+-------------------+
*/
public
function
getPathAccess(
$user
,
$path
){
$this
->reload();
$user
=
strtolower
(
$user
);
$group
=
$this
->users[
$user
][
'group'
];
//去除文件名称
$path
=
str_replace
(
substr
(
strrchr
(
$path
,
'/'
),1),
''
,
$path
);
$access
= self::AC(0);
$isExactMatch
= false;
if
(
$group
){
if
(isset(
$this
->groups[
$group
][
'folder'
])){
foreach
(
$this
->groups[
$group
][
'folder'
]
as
$f
){
//中文处理
$t_path
= iconv(
'UTF-8'
,
'GB18030'
,
$f
[
'path'
]);
if
(
strpos
(
$path
,
$t_path
) === 0){
$access
=
$f
[
'access'
];
$isExactMatch
= (
$path
==
$t_path
?true:false);
}
}
}
}
if
(isset(
$this
->users[
$user
][
'folder'
])){
foreach
(
$this
->users[
$user
][
'folder'
]
as
$f
){
//中文处理
$t_path
= iconv(
'UTF-8'
,
'GB18030'
,
$f
[
'path'
]);
if
(
strpos
(
$path
,
$t_path
) === 0){
$access
=
$f
[
'access'
];
$isExactMatch
= (
$path
==
$t_path
?true:false);
}
}
}
echo
date
(
'Y-m-d H:i:s'
).
" [debug]\tACCESS:$access "
.
' '
.(
$isExactMatch
?
'1'
:
'0'
).
" $path\n"
;
return
array
(
'access'
=>
$access
,
'isExactMatch'
=>
$isExactMatch
);
}
/**
* 添加在线用户
* @param ShareMemory $shm
* @param swoole_server $serv
* @param unknown $user
* @param unknown $fd
* @param unknown $ip
* @return Ambigous <multitype:, boolean, mixed, multitype:unknown number multitype:Ambigous <unknown, number> >
*/
public
function
addOnline(ShareMemory
$shm
,
$serv
,
$user
,
$fd
,
$ip
){
$shm_data
=
$shm
->read();
if
(
$shm_data
!== false){
$shm_data
[
'online'
][
$user
.
'-'
.
$fd
] =
array
(
'ip'
=>
$ip
,
'time'
=>time());
$shm_data
[
'last_login'
][] =
array
(
'user'
=>
$user
,
'ip'
=>
$ip
,
'time'
=>time());
//清除旧数据
if
(
count
(
$shm_data
[
'last_login'
])>30)
array_shift
(
$shm_data
[
'last_login'
]);
$list
=
array
();
foreach
(
$shm_data
[
'online'
]
as
$k
=>
$v
){
$arr
=
explode
(
'-'
,
$k
);
if
(
$serv
->connection_info(
$arr
[1]) !== false){
$list
[
$k
] =
$v
;
}
}
$shm_data
[
'online'
] =
$list
;
$shm
->write(
$shm_data
);
}
return
$shm_data
;
}
/**
* 添加登陆失败记录
* @param ShareMemory $shm
* @param unknown $user
* @param unknown $ip
* @return Ambigous <number, multitype:, boolean, mixed>
*/
public
function
addAttempt(ShareMemory
$shm
,
$user
,
$ip
){
$shm_data
=
$shm
->read();
if
(
$shm_data
!== false){
if
(isset(
$shm_data
[
'login_attempt'
][
$ip
.
'||'
.
$user
][
'count'
])){
$shm_data
[
'login_attempt'
][
$ip
.
'||'
.
$user
][
'count'
] += 1;
}
else
{
$shm_data
[
'login_attempt'
][
$ip
.
'||'
.
$user
][
'count'
] = 1;
}
$shm_data
[
'login_attempt'
][
$ip
.
'||'
.
$user
][
'time'
] = time();
//清除旧数据
if
(
count
(
$shm_data
[
'login_attempt'
])>30)
array_shift
(
$shm_data
[
'login_attempt'
]);
$shm
->write(
$shm_data
);
}
return
$shm_data
;
}
/**
* 密码错误上限
* @param unknown $shm
* @param unknown $user
* @param unknown $ip
* @return boolean
*/
public
function
isAttemptLimit(ShareMemory
$shm
,
$user
,
$ip
){
$shm_data
=
$shm
->read();
if
(
$shm_data
!== false){
if
(isset(
$shm_data
[
'login_attempt'
][
$ip
.
'||'
.
$user
][
'count'
])){
if
(
$shm_data
[
'login_attempt'
][
$ip
.
'||'
.
$user
][
'count'
] > 10 &&
time() -
$shm_data
[
'login_attempt'
][
$ip
.
'||'
.
$user
][
'time'
] < 600){
return
true;
}
}
}
return
false;
}
/**
* 生成随机密钥
* @param int $len
* @return Ambigous <NULL, string>
*/
public
static
function
genPassword(
$len
){
$str
= null;
$strPol
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz@!#$%*+-"
;
$max
=
strlen
(
$strPol
)-1;
for
(
$i
=0;
$i
<
$len
;
$i
++){
$str
.=
$strPol
[rand(0,
$max
)];
//rand($min,$max)生成介于min和max两个数之间的一个随机整数
}
return
$str
;
}
}
|
2.共享内存操作类 。
这个相对简单,使用php的shmop扩展即可.
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
|
class
ShareMemory{
private
$mode
= 0644;
private
$shm_key
;
private
$shm_size
;
/**
* 构造函数
*/
public
function
__construct(){
$key
=
'F'
;
$size
= 1024*1024;
$this
->shm_key =
ftok
(
__FILE__
,
$key
);
$this
->shm_size =
$size
+ 1;
}
/**
* 读取内存数组
* @return array|boolean
*/
public
function
read(){
if
((
$shm_id
= shmop_open(
$this
->shm_key,
'c'
,
$this
->mode,
$this
->shm_size)) !== false){
$str
= shmop_read(
$shm_id
,1,
$this
->shm_size-1);
shmop_close(
$shm_id
);
if
((
$i
=
strpos
(
$str
,
"\0"
)) !== false)
$str
=
substr
(
$str
,0,
$i
);
if
(
$str
){
return
json_decode(
$str
,true);
}
else
{
return
array
();
}
}
return
false;
}
/**
* 写入数组到内存
* @param array $arr
* @return int|boolean
*/
public
function
write(
$arr
){
if
(!
is_array
(
$arr
))
return
false;
$str
= json_encode(
$arr
).
"\0"
;
if
(
strlen
(
$str
) >
$this
->shm_size)
return
false;
if
((
$shm_id
= shmop_open(
$this
->shm_key,
'c'
,
$this
->mode,
$this
->shm_size)) !== false){
$count
= shmop_write(
$shm_id
,
$str
,1);
shmop_close(
$shm_id
);
return
$count
;
}
return
false;
}
/**
* 删除内存块,下次使用时将重新开辟内存块
* @return boolean
*/
public
function
delete
(){
if
((
$shm_id
= shmop_open(
$this
->shm_key,
'c'
,
$this
->mode,
$this
->shm_size)) !== false){
$result
= shmop_delete(
$shm_id
);
shmop_close(
$shm_id
);
return
$result
;
}
return
false;
}
}
|
3.内置的web服务器类 。
这个主要是嵌入在ftp的http服务器类,功能不是很完善,进行ftp的管理还是可行的。不过需要注意的是,这个实现与apache等其他http服务器运行的方式可能有所不同。代码是驻留内存的.
。
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
|
class
CWebServer{
protected
$buffer_header
=
array
();
protected
$buffer_maxlen
= 65535;
//最大POST尺寸
const
DATE_FORMAT_HTTP =
'D, d-M-Y H:i:s T'
;
const
HTTP_EOF =
"\r\n\r\n"
;
const
HTTP_HEAD_MAXLEN = 8192;
//http头最大长度不得超过2k
const
HTTP_POST_MAXLEN = 1048576;
//1m
const
ST_FINISH = 1;
//完成,进入处理流程
const
ST_WAIT = 2;
//等待数据
const
ST_ERROR = 3;
//错误,丢弃此包
private
$requsts
=
array
();
private
$config
=
array
();
public
function
log(
$msg
,
$level
=
'debug'
){
echo
date
(
'Y-m-d H:i:s'
).
' ['
.
$level
.
"]\t"
.
$msg
.
"\n"
;
}
public
function
__construct(
$config
=
array
()){
$this
->config =
array
(
'wwwroot'
=> __DIR__.
'/wwwroot/'
,
'index'
=>
'index.php'
,
'path_deny'
=>
array
(
'/protected/'
),
);
}
public
function
onReceive(
$serv
,
$fd
,
$data
){
$ret
=
$this
->checkData(
$fd
,
$data
);
switch
(
$ret
){
case
self::ST_ERROR:
$serv
->close(
$fd
);
$this
->cleanBuffer(
$fd
);
$this
->log(
'Recevie error.'
);
break
;
case
self::ST_WAIT:
$this
->log(
'Recevie wait.'
);
return
;
default
:
break
;
}
//开始完整的请求
$request
=
$this
->requsts[
$fd
];
$info
=
$serv
->connection_info(
$fd
);
$request
=
$this
->parseRequest(
$request
);
$request
[
'remote_ip'
] =
$info
[
'remote_ip'
];
$response
=
$this
->onRequest(
$request
);
$output
=
$this
->parseResponse(
$request
,
$response
);
$serv
->send(
$fd
,
$output
);
if
(isset(
$request
[
'head'
][
'Connection'
]) &&
strtolower
(
$request
[
'head'
][
'Connection'
]) ==
'close'
){
$serv
->close(
$fd
);
}
unset(
$this
->requsts[
$fd
]);
$_REQUEST
=
$_SESSION
=
$_COOKIE
=
$_FILES
=
$_POST
=
$_SERVER
=
$_GET
=
array
();
}
/**
* 处理请求
* @param array $request
* @return array $response
*
* $request=array(
* 'time'=>
* 'head'=>array(
* 'method'=>
* 'path'=>
* 'protocol'=>
* 'uri'=>
* //other http header
* '..'=>value
* )
* 'body'=>
* 'get'=>(if appropriate)
* 'post'=>(if appropriate)
* 'cookie'=>(if appropriate)
*
*
* )
*/
public
function
onRequest(
$request
){
if
(
$request
[
'head'
][
'path'
][
strlen
(
$request
[
'head'
][
'path'
]) - 1] ==
'/'
){
$request
[
'head'
][
'path'
] .=
$this
->config[
'index'
];
}
$response
=
$this
->process(
$request
);
return
$response
;
}
/**
* 清除数据
* @param unknown $fd
*/
public
function
cleanBuffer(
$fd
){
unset(
$this
->requsts[
$fd
]);
unset(
$this
->buffer_header[
$fd
]);
}
/**
* 检查数据
* @param unknown $fd
* @param unknown $data
* @return string
*/
public
function
checkData(
$fd
,
$data
){
if
(isset(
$this
->buffer_header[
$fd
])){
$data
=
$this
->buffer_header[
$fd
].
$data
;
}
$request
=
$this
->checkHeader(
$fd
,
$data
);
//请求头错误
if
(
$request
=== false){
$this
->buffer_header[
$fd
] =
$data
;
if
(
strlen
(
$data
) > self::HTTP_HEAD_MAXLEN){
return
self::ST_ERROR;
}
else
{
return
self::ST_WAIT;
}
}
//post请求检查
if
(
$request
[
'head'
][
'method'
] ==
'POST'
){
return
$this
->checkPost(
$request
);
}
else
{
return
self::ST_FINISH;
}
}
/**
* 检查请求头
* @param unknown $fd
* @param unknown $data
* @return boolean|array
*/
public
function
checkHeader(
$fd
,
$data
){
//新的请求
if
(!isset(
$this
->requsts[
$fd
])){
//http头结束符
$ret
=
strpos
(
$data
,self::HTTP_EOF);
if
(
$ret
=== false){
return
false;
}
else
{
$this
->buffer_header[
$fd
] =
''
;
$request
=
array
();
list(
$header
,
$request
[
'body'
]) =
explode
(self::HTTP_EOF,
$data
,2);
$request
[
'head'
] =
$this
->parseHeader(
$header
);
$this
->requsts[
$fd
] =
$request
;
if
(
$request
[
'head'
] == false){
return
false;
}
}
}
else
{
//post 数据合并
$request
=
$this
->requsts[
$fd
];
$request
[
'body'
] .=
$data
;
}
return
$request
;
}
/**
* 解析请求头
* @param string $header
* @return array
* array(
* 'method'=>,
* 'uri'=>
* 'protocol'=>
* 'name'=>value,...
*
*
*
* }
*/
public
function
parseHeader(
$header
){
$request
=
array
();
$headlines
=
explode
(
"\r\n"
,
$header
);
list(
$request
[
'method'
],
$request
[
'uri'
],
$request
[
'protocol'
]) =
explode
(
' '
,
$headlines
[0],3);
foreach
(
$headlines
as
$k
=>
$line
){
$line
= trim(
$line
);
if
(
$k
&& !
empty
(
$line
) &&
strpos
(
$line
,
':'
) !== false){
list(
$name
,
$value
) =
explode
(
':'
,
$line
,2);
$request
[trim(
$name
)] = trim(
$value
);
}
}
return
$request
;
}
/**
* 检查post数据是否完整
* @param unknown $request
* @return string
*/
public
function
checkPost(
$request
){
if
(isset(
$request
[
'head'
][
'Content-Length'
])){
if
(
intval
(
$request
[
'head'
][
'Content-Length'
]) > self::HTTP_POST_MAXLEN){
return
self::ST_ERROR;
}
if
(
intval
(
$request
[
'head'
][
'Content-Length'
]) >
strlen
(
$request
[
'body'
])){
return
self::ST_WAIT;
}
else
{
return
self::ST_FINISH;
}
}
return
self::ST_ERROR;
}
/**
* 解析请求
* @param unknown $request
* @return Ambigous <unknown, mixed, multitype:string >
*/
public
function
parseRequest(
$request
){
$request
[
'time'
] = time();
$url_info
=
parse_url
(
$request
[
'head'
][
'uri'
]);
$request
[
'head'
][
'path'
] =
$url_info
[
'path'
];
if
(isset(
$url_info
[
'fragment'
]))
$request
[
'head'
][
'fragment'
] =
$url_info
[
'fragment'
];
if
(isset(
$url_info
[
'query'
])){
parse_str
(
$url_info
[
'query'
],
$request
[
'get'
]);
}
//parse post body
if
(
$request
[
'head'
][
'method'
] ==
'POST'
){
//目前只处理表单提交
if
(isset(
$request
[
'head'
][
'Content-Type'
]) &&
substr
(
$request
[
'head'
][
'Content-Type'
], 0, 33) ==
'application/x-www-form-urlencoded'
|| isset(
$request
[
'head'
][
'X-Request-With'
]) &&
$request
[
'head'
][
'X-Request-With'
] ==
'XMLHttpRequest'
){
parse_str
(
$request
[
'body'
],
$request
[
'post'
]);
}
}
//parse cookies
if
(!
empty
(
$request
[
'head'
][
'Cookie'
])){
$params
=
array
();
$blocks
=
explode
(
";"
,
$request
[
'head'
][
'Cookie'
]);
foreach
(
$blocks
as
$b
){
$_r
=
explode
(
"="
,
$b
, 2);
if
(
count
(
$_r
)==2){
list (
$key
,
$value
) =
$_r
;
$params
[trim(
$key
)] = trim(
$value
,
"\r\n \t\""
);
}
else
{
$params
[
$_r
[0]] =
''
;
}
}
$request
[
'cookie'
] =
$params
;
}
return
$request
;
}
public
function
parseResponse(
$request
,
$response
){
if
(!isset(
$response
[
'head'
][
'Date'
])){
$response
[
'head'
][
'Date'
] =
gmdate
(
"D, d M Y H:i:s T"
);
}
if
(!isset(
$response
[
'head'
][
'Content-Type'
])){
$response
[
'head'
][
'Content-Type'
] =
'text/html;charset=utf-8'
;
}
if
(!isset(
$response
[
'head'
][
'Content-Length'
])){
$response
[
'head'
][
'Content-Length'
] =
strlen
(
$response
[
'body'
]);
}
if
(!isset(
$response
[
'head'
][
'Connection'
])){
if
(isset(
$request
[
'head'
][
'Connection'
]) &&
strtolower
(
$request
[
'head'
][
'Connection'
]) ==
'keep-alive'
){
$response
[
'head'
][
'Connection'
] =
'keep-alive'
;
}
else
{
$response
[
'head'
][
'Connection'
] =
'close'
;
}
}
$response
[
'head'
][
'Server'
] = CFtpServer::
$software
.
'/'
.CFtpServer::VERSION;
$out
=
''
;
if
(isset(
$response
[
'head'
][
'Status'
])){
$out
.=
'HTTP/1.1 '
.
$response
[
'head'
][
'Status'
].
"\r\n"
;
unset(
$response
[
'head'
][
'Status'
]);
}
else
{
$out
.=
"HTTP/1.1 200 OK\r\n"
;
}
//headers
foreach
(
$response
[
'head'
]
as
$k
=>
$v
){
$out
.=
$k
.
': '
.
$v
.
"\r\n"
;
}
//cookies
if
(
$_COOKIE
){
$arr
=
array
();
foreach
(
$_COOKIE
as
$k
=>
$v
){
$arr
[] =
$k
.
'='
.
$v
;
}
$out
.=
'Set-Cookie: '
.implode(
';'
,
$arr
).
"\r\n"
;
}
//End
$out
.=
"\r\n"
;
$out
.=
$response
[
'body'
];
return
$out
;
}
/**
* 处理请求
* @param unknown $request
* @return array
*/
public
function
process(
$request
){
$path
=
$request
[
'head'
][
'path'
];
$isDeny
= false;
foreach
(
$this
->config[
'path_deny'
]
as
$p
){
if
(
strpos
(
$path
,
$p
) === 0){
$isDeny
= true;
break
;
}
}
if
(
$isDeny
){
return
$this
->httpError(403,
'服务器拒绝访问:路径错误'
);
}
if
(!in_array(
$request
[
'head'
][
'method'
],
array
(
'GET'
,
'POST'
))){
return
$this
->httpError(500,
'服务器拒绝访问:错误的请求方法'
);
}
$file_ext
=
strtolower
(trim(
substr
(
strrchr
(
$path
,
'.'
), 1)));
$path
=
realpath
(rtrim(
$this
->config[
'wwwroot'
],
'/'
).
'/'
. ltrim(
$path
,
'/'
));
$this
->log(
'WEB:['
.
$request
[
'head'
][
'method'
].
'] '
.
$request
[
'head'
][
'uri'
] .
' '
.json_encode(isset(
$request
[
'post'
])?
$request
[
'post'
]:
array
()));
$response
=
array
();
if
(
$file_ext
==
'php'
){
if
(
is_file
(
$path
)){
//设置全局变量
if
(isset(
$request
[
'get'
]))
$_GET
=
$request
[
'get'
];
if
(isset(
$request
[
'post'
]))
$_POST
=
$request
[
'post'
];
if
(isset(
$request
[
'cookie'
]))
$_COOKIE
=
$request
[
'cookie'
];
$_REQUEST
=
array_merge
(
$_GET
,
$_POST
,
$_COOKIE
);
foreach
(
$request
[
'head'
]
as
$key
=>
$value
){
$_key
=
'HTTP_'
.
strtoupper
(
str_replace
(
'-'
,
'_'
,
$key
));
$_SERVER
[
$_key
] =
$value
;
}
$_SERVER
[
'REMOTE_ADDR'
] =
$request
[
'remote_ip'
];
$_SERVER
[
'REQUEST_URI'
] =
$request
[
'head'
][
'uri'
];
//进行http auth
if
(isset(
$_GET
[
'c'
]) &&
strtolower
(
$_GET
[
'c'
]) !=
'site'
){
if
(isset(
$request
[
'head'
][
'Authorization'
])){
$user
=
new
User();
if
(
$user
->checkUserBasicAuth(
$request
[
'head'
][
'Authorization'
])){
$response
[
'head'
][
'Status'
] = self::
$HTTP_HEADERS
[200];
goto
process;
}
}
$response
[
'head'
][
'Status'
] = self::
$HTTP_HEADERS
[401];
$response
[
'head'
][
'WWW-Authenticate'
] =
'Basic realm="Real-Data-FTP"'
;
$_GET
[
'c'
] =
'Site'
;
$_GET
[
'a'
] =
'Unauthorized'
;
}
process:
ob_start();
try
{
include
$path
;
$response
[
'body'
] = ob_get_contents();
$response
[
'head'
][
'Content-Type'
] = APP::
$content_type
;
}
catch
(Exception
$e
){
$response
=
$this
->httpError(500,
$e
->getMessage());
}
ob_end_clean();
}
else
{
$response
=
$this
->httpError(404,
'页面不存在'
);
}
}
else
{
//处理静态文件
if
(
is_file
(
$path
)){
$response
[
'head'
][
'Content-Type'
] = isset(self::
$MIME_TYPES
[
$file_ext
]) ? self::
$MIME_TYPES
[
$file_ext
]:
"application/octet-stream"
;
//使用缓存
if
(!isset(
$request
[
'head'
][
'If-Modified-Since'
])){
$fstat
= stat(
$path
);
$expire
= 2592000;
//30 days
$response
[
'head'
][
'Status'
] = self::
$HTTP_HEADERS
[200];
$response
[
'head'
][
'Cache-Control'
] =
"max-age={$expire}"
;
$response
[
'head'
][
'Pragma'
] =
"max-age={$expire}"
;
$response
[
'head'
][
'Last-Modified'
] =
date
(self::DATE_FORMAT_HTTP,
$fstat
[
'mtime'
]);
$response
[
'head'
][
'Expires'
] =
"max-age={$expire}"
;
$response
[
'body'
] =
file_get_contents
(
$path
);
}
else
{
$response
[
'head'
][
'Status'
] = self::
$HTTP_HEADERS
[304];
$response
[
'body'
] =
''
;
}
}
else
{
$response
=
$this
->httpError(404,
'页面不存在'
);
}
}
return
$response
;
}
public
function
httpError(
$code
,
$content
){
$response
=
array
();
$version
= CFtpServer::
$software
.
'/'
.CFtpServer::VERSION;
$response
[
'head'
][
'Content-Type'
] =
'text/html;charset=utf-8'
;
$response
[
'head'
][
'Status'
] = self::
$HTTP_HEADERS
[
$code
];
$response
[
'body'
] = <<<html
<!DOCTYPE html>
<html lang=
"zh-CN"
>
<head>
<meta charset=
"utf-8"
>
<title>FTP后台管理 </title>
</head>
<body>
<p>{
$content
}</p>
<div style=
"text-align:center"
>
<hr>
{
$version
} Copyright &
copy
; 2015 by <a target=
'_new'
href=
'http://www.realdatamed.com'
>Real Data</a> All Rights Reserved.
</div>
</body>
</html>
html;
return
$response
;
}
static
$HTTP_HEADERS
=
array
(
100 =>
"100 Continue"
,
101 =>
"101 Switching Protocols"
,
200 =>
"200 OK"
,
201 =>
"201 Created"
,
204 =>
"204 No Content"
,
206 =>
"206 Partial Content"
,
300 =>
"300 Multiple Choices"
,
301 =>
"301 Moved Permanently"
,
302 =>
"302 Found"
,
303 =>
"303 See Other"
,
304 =>
"304 Not Modified"
,
307 =>
"307 Temporary Redirect"
,
400 =>
"400 Bad Request"
,
401 =>
"401 Unauthorized"
,
403 =>
"403 Forbidden"
,
404 =>
"404 Not Found"
,
405 =>
"405 Method Not Allowed"
,
406 =>
"406 Not Acceptable"
,
408 =>
"408 Request Timeout"
,
410 =>
"410 Gone"
,
413 =>
"413 Request Entity Too Large"
,
414 =>
"414 Request URI Too Long"
,
415 =>
"415 Unsupported Media Type"
,
416 =>
"416 Requested Range Not Satisfiable"
,
417 =>
"417 Expectation Failed"
,
500 =>
"500 Internal Server Error"
,
501 =>
"501 Method Not Implemented"
,
503 =>
"503 Service Unavailable"
,
506 =>
"506 Variant Also Negotiates"
,
);
static
$MIME_TYPES
=
array
(
'jpg'
=>
'image/jpeg'
,
'bmp'
=>
'image/bmp'
,
'ico'
=>
'image/x-icon'
,
'gif'
=>
'image/gif'
,
'png'
=>
'image/png'
,
'bin'
=>
'application/octet-stream'
,
'js'
=>
'application/javascript'
,
'css'
=>
'text/css'
,
'html'
=>
'text/html'
,
'xml'
=>
'text/xml'
,
'tar'
=>
'application/x-tar'
,
'ppt'
=>
'application/vnd.ms-powerpoint'
,
'pdf'
=>
'application/pdf'
,
'svg'
=>
' image/svg+xml'
,
'woff'
=>
'application/x-font-woff'
,
'woff2'
=>
'application/x-font-woff'
,
);
}
|
4.FTP主类 。
有了前面类,就可以在ftp进行引用了。使用ssl时,请注意进行防火墙passive 端口范围的nat配置。 。
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
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
|
defined(
'DEBUG_ON'
)
or
define(
'DEBUG_ON'
, false);
//主目录
defined(
'BASE_PATH'
)
or
define(
'BASE_PATH'
, __DIR__);
require_once
BASE_PATH.
'/inc/User.php'
;
require_once
BASE_PATH.
'/inc/ShareMemory.php'
;
require_once
BASE_PATH.
'/web/CWebServer.php'
;
require_once
BASE_PATH.
'/inc/CSmtp.php'
;
class
CFtpServer{
//软件版本
const
VERSION =
'2.0'
;
const
EOF =
"\r\n"
;
public
static
$software
"FTP-Server"
;
private
static
$server_mode
= SWOOLE_PROCESS;
private
static
$pid_file
;
private
static
$log_file
;
//待写入文件的日志队列(缓冲区)
private
$queue
=
array
();
private
$pasv_port_range
=
array
(55000,60000);
public
$host
=
'0.0.0.0'
;
public
$port
= 21;
public
$setting
=
array
();
//最大连接数
public
$max_connection
= 50;
//web管理端口
public
$manager_port
= 8080;
//tls
public
$ftps_port
= 990;
/**
* @var swoole_server
*/
protected
$server
;
protected
$connection
=
array
();
protected
$session
=
array
();
protected
$user
;
//用户类,复制验证与权限
//共享内存类
protected
$shm
;
//ShareMemory
/**
*
* @var embedded http server
*/
protected
$webserver
;
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 静态方法
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
public
static
function
setPidFile(
$pid_file
){
self::
$pid_file
=
$pid_file
;
}
/**
* 服务启动控制方法
*/
public
static
function
start(
$startFunc
){
if
(
empty
(self::
$pid_file
)){
exit
(
"Require pid file.\n"
);
}
if
(!
extension_loaded
(
'posix'
)){
exit
(
"Require extension `posix`.\n"
);
}
if
(!
extension_loaded
(
'swoole'
)){
exit
(
"Require extension `swoole`.\n"
);
}
if
(!
extension_loaded
(
'shmop'
)){
exit
(
"Require extension `shmop`.\n"
);
}
if
(!
extension_loaded
(
'openssl'
)){
exit
(
"Require extension `openssl`.\n"
);
}
$pid_file
= self::
$pid_file
;
$server_pid
= 0;
if
(
is_file
(
$pid_file
)){
$server_pid
=
file_get_contents
(
$pid_file
);
}
global
$argv
;
if
(
empty
(
$argv
[1])){
goto
usage;
}
elseif
(
$argv
[1] ==
'reload'
){
if
(
empty
(
$server_pid
)){
exit
(
"FtpServer is not running\n"
);
}
posix_kill(
$server_pid
, SIGUSR1);
exit
;
}
elseif
(
$argv
[1] ==
'stop'
){
if
(
empty
(
$server_pid
)){
exit
(
"FtpServer is not running\n"
);
}
posix_kill(
$server_pid
, SIGTERM);
exit
;
}
elseif
(
$argv
[1] ==
'start'
){
//已存在ServerPID,并且进程存在
if
(!
empty
(
$server_pid
)
and
posix_kill(
$server_pid
,(int) 0)){
exit
(
"FtpServer is already running.\n"
);
}
//启动服务器
$startFunc
();
}
else
{
usage:
exit
(
"Usage: php {$argv[0]} start|stop|reload\n"
);
}
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 方法
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
public
function
__construct(
$host
,
$port
){
$this
->user =
new
User();
$this
->shm =
new
ShareMemory();
$this
->shm->write(
array
());
$flag
= SWOOLE_SOCK_TCP;
$this
->server =
new
swoole_server(
$host
,
$port
,self::
$server_mode
,
$flag
);
$this
->host =
$host
;
$this
->port =
$port
;
$this
->setting =
array
(
'backlog'
=> 128,
'dispatch_mode'
=> 2,
);
}
public
function
daemonize(){
$this
->setting[
'daemonize'
] = 1;
}
public
function
getConnectionInfo(
$fd
){
return
$this
->server->connection_info(
$fd
);
}
/**
* 启动服务进程
* @param array $setting
* @throws Exception
*/
public
function
run(
$setting
=
array
()){
$this
->setting =
array_merge
(
$this
->setting,
$setting
);
//不使用swoole的默认日志
if
(isset(
$this
->setting[
'log_file'
])){
self::
$log_file
=
$this
->setting[
'log_file'
];
unset(
$this
->setting[
'log_file'
]);
}
if
(isset(
$this
->setting[
'max_connection'
])){
$this
->max_connection =
$this
->setting[
'max_connection'
];
unset(
$this
->setting[
'max_connection'
]);
}
if
(isset(
$this
->setting[
'manager_port'
])){
$this
->manager_port =
$this
->setting[
'manager_port'
];
unset(
$this
->setting[
'manager_port'
]);
}
if
(isset(
$this
->setting[
'ftps_port'
])){
$this
->ftps_port =
$this
->setting[
'ftps_port'
];
unset(
$this
->setting[
'ftps_port'
]);
}
if
(isset(
$this
->setting[
'passive_port_range'
])){
$this
->pasv_port_range =
$this
->setting[
'passive_port_range'
];
unset(
$this
->setting[
'passive_port_range'
]);
}
$this
->server->set(
$this
->setting);
$version
=
explode
(
'.'
, SWOOLE_VERSION);
if
(
$version
[0] == 1 &&
$version
[1] < 7 &&
$version
[2] <20){
throw
new
Exception(
'Swoole version require 1.7.20 +.'
);
}
//事件绑定
$this
->server->on(
'start'
,
array
(
$this
,
'onMasterStart'
));
$this
->server->on(
'shutdown'
,
array
(
$this
,
'onMasterStop'
));
$this
->server->on(
'ManagerStart'
,
array
(
$this
,
'onManagerStart'
));
$this
->server->on(
'ManagerStop'
,
array
(
$this
,
'onManagerStop'
));
$this
->server->on(
'WorkerStart'
,
array
(
$this
,
'onWorkerStart'
));
$this
->server->on(
'WorkerStop'
,
array
(
$this
,
'onWorkerStop'
));
$this
->server->on(
'WorkerError'
,
array
(
$this
,
'onWorkerError'
));
$this
->server->on(
'Connect'
,
array
(
$this
,
'onConnect'
));
$this
->server->on(
'Receive'
,
array
(
$this
,
'onReceive'
));
$this
->server->on(
'Close'
,
array
(
$this
,
'onClose'
));
//管理端口
$this
->server->addlistener(
$this
->host,
$this
->manager_port,SWOOLE_SOCK_TCP);
//tls
$this
->server->addlistener(
$this
->host,
$this
->ftps_port,SWOOLE_SOCK_TCP | SWOOLE_SSL);
$this
->server->start();
}
public
function
log(
$msg
,
$level
=
'debug'
,
$flush
= false){
if
(DEBUG_ON){
$log
=
date
(
'Y-m-d H:i:s'
).
' ['
.
$level
.
"]\t"
.
$msg
.
"\n"
;
if
(!
empty
(self::
$log_file
)){
$debug_file
= dirname(self::
$log_file
).
'/debug.log'
;
file_put_contents
(
$debug_file
,
$log
,FILE_APPEND);
if
(
filesize
(
$debug_file
) > 10485760){
//10M
unlink(
$debug_file
);
}
}
echo
$log
;
}
if
(
$level
!=
'debug'
){
//日志记录
$this
->queue[] =
date
(
'Y-m-d H:i:s'
).
"\t["
.
$level
.
"]\t"
.
$msg
;
}
if
(
count
(
$this
->queue)>10 && !
empty
(self::
$log_file
) ||
$flush
){
if
(
filesize
(self::
$log_file
) > 209715200){
//200M
rename(self::
$log_file
,self::
$log_file
.
'.'
.
date
(
'His'
));
}
$logs
=
''
;
foreach
(
$this
->queue
as
$q
){
$logs
.=
$q
.
"\n"
;
}
file_put_contents
(self::
$log_file
,
$logs
,FILE_APPEND);
$this
->queue =
array
();
}
}
public
function
shutdown(){
return
$this
->server->shutdown();
}
public
function
close(
$fd
){
return
$this
->server->close(
$fd
);
}
public
function
send(
$fd
,
$data
){
$data
=
strtr
(
$data
,
array
(
"\n"
=>
""
,
"\0"
=>
""
,
"\r"
=>
""
));
$this
->log(
"[-->]\t"
.
$data
);
return
$this
->server->send(
$fd
,
$data
.self::EOF);
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 事件回调
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
public
function
onMasterStart(
$serv
){
global
$argv
;
swoole_set_process_name(
'php '
.
$argv
[0].
': master -host='
.
$this
->host.
' -port='
.
$this
->port.
'/'
.
$this
->manager_port);
if
(!
empty
(
$this
->setting[
'pid_file'
])){
file_put_contents
(self::
$pid_file
,
$serv
->master_pid);
}
$this
->log(
'Master started.'
);
}
public
function
onMasterStop(
$serv
){
if
(!
empty
(
$this
->setting[
'pid_file'
])){
unlink(self::
$pid_file
);
}
$this
->shm->
delete
();
$this
->log(
'Master stop.'
);
}
public
function
onManagerStart(
$serv
){
global
$argv
;
swoole_set_process_name(
'php '
.
$argv
[0].
': manager'
);
$this
->log(
'Manager started.'
);
}
public
function
onManagerStop(
$serv
){
$this
->log(
'Manager stop.'
);
}
public
function
onWorkerStart(
$serv
,
$worker_id
){
global
$argv
;
if
(
$worker_id
>=
$serv
->setting[
'worker_num'
]) {
swoole_set_process_name(
"php {$argv[0]}: worker [task]"
);
}
else
{
swoole_set_process_name(
"php {$argv[0]}: worker [{$worker_id}]"
);
}
$this
->log(
"Worker {$worker_id} started."
);
}
public
function
onWorkerStop(
$serv
,
$worker_id
){
$this
->log(
"Worker {$worker_id} stop."
);
}
public
function
onWorkerError(
$serv
,
$worker_id
,
$worker_pid
,
$exit_code
){
$this
->log(
"Worker {$worker_id} error:{$exit_code}."
);
}
public
function
onConnect(
$serv
,
$fd
,
$from_id
){
$info
=
$this
->getConnectionInfo(
$fd
);
if
(
$info
[
'server_port'
] ==
$this
->manager_port){
//web请求
$this
->webserver =
new
CWebServer();
}
else
{
$this
->send(
$fd
,
"220---------- Welcome to "
. self::
$software
.
" ----------"
);
$this
->send(
$fd
,
"220-Local time is now "
.
date
(
"H:i"
));
$this
->send(
$fd
,
"220 This is a private system - No anonymous login"
);
if
(
count
(
$this
->server->connections) <=
$this
->max_connection){
if
(
$info
[
'server_port'
] ==
$this
->port && isset(
$this
->setting[
'force_ssl'
]) &&
$this
->setting[
'force_ssl'
]){
//如果启用强制ssl
$this
->send(
$fd
,
"421 Require implicit FTP over tls, closing control connection."
);
$this
->close(
$fd
);
return
;
}
$this
->connection[
$fd
] =
array
();
$this
->session =
array
();
$this
->queue =
array
();
}
else
{
$this
->send(
$fd
,
"421 Too many connections, closing control connection."
);
$this
->close(
$fd
);
}
}
}
public
function
onReceive(
$serv
,
$fd
,
$from_id
,
$recv_data
){
$info
=
$this
->getConnectionInfo(
$fd
);
if
(
$info
[
'server_port'
] ==
$this
->manager_port){
//web请求
$this
->webserver->onReceive(
$this
->server,
$fd
,
$recv_data
);
}
else
{
$read
= trim(
$recv_data
);
$this
->log(
"[<--]\t"
.
$read
);
$cmd
=
explode
(
" "
,
$read
);
$func
=
'cmd_'
.
strtoupper
(
$cmd
[0]);
$data
= trim(
str_replace
(
$cmd
[0],
''
,
$read
));
if
(!method_exists(
$this
,
$func
)){
$this
->send(
$fd
,
"500 Unknown Command"
);
return
;
}
if
(
empty
(
$this
->connection[
$fd
][
'login'
])){
switch
(
$cmd
[0]){
case
'TYPE'
:
case
'USER'
:
case
'PASS'
:
case
'QUIT'
:
case
'AUTH'
:
case
'PBSZ'
:
break
;
default
:
$this
->send(
$fd
,
"530 You aren't logged in"
);
return
;
}
}
$this
->
$func
(
$fd
,
$data
);
}
}
public
function
onClose(
$serv
,
$fd
,
$from_id
){
//在线用户
$shm_data
=
$this
->shm->read();
if
(
$shm_data
!== false){
if
(isset(
$shm_data
[
'online'
])){
$list
=
array
();
foreach
(
$shm_data
[
'online'
]
as
$u
=>
$info
){
if
(!preg_match(
'/\.*-'
.
$fd
.
'$/'
,
$u
,
$m
))
$list
[
$u
] =
$info
;
}
$shm_data
[
'online'
] =
$list
;
$this
->shm->write(
$shm_data
);
}
}
$this
->log(
'Socket '
.
$fd
.
' close. Flush the logs.'
,
'debug'
,true);
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 工具函数
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
/**
* 获取用户名
* @param $fd
*/
public
function
getUser(
$fd
){
return
isset(
$this
->connection[
$fd
][
'user'
])?
$this
->connection[
$fd
][
'user'
]:
''
;
}
/**
* 获取文件全路径
* @param $user
* @param $file
* @return string|boolean
*/
public
function
getFile(
$user
,
$file
){
$file
=
$this
->fillDirName(
$user
,
$file
);
if
(
is_file
(
$file
)){
return
realpath
(
$file
);
}
else
{
return
false;
}
}
/**
* 遍历目录
* @param $rdir
* @param $showHidden
* @param $format list/mlsd
* @return string
*
* list 使用local时间
* mlsd 使用gmt时间
*/
public
function
getFileList(
$user
,
$rdir
,
$showHidden
= false,
$format
=
'list'
){
$filelist
=
''
;
if
(
$format
==
'mlsd'
){
$stats
= stat(
$rdir
);
$filelist
.=
'Type=cdir;Modify='
.
gmdate
(
'YmdHis'
,
$stats
[
'mtime'
]).
';UNIX.mode=d'
.
$this
->mode2char(
$stats
[
'mode'
]).
'; '
.
$this
->getUserDir(
$user
).
"\r\n"
;
}
if
(
$handle
= opendir(
$rdir
)){
$isListable
=
$this
->user->isFolderListable(
$user
,
$rdir
);
while
(false !== (
$file
= readdir(
$handle
))){
if
(
$file
==
'.'
or
$file
==
'..'
){
continue
;
}
if
(
$file
{0} ==
"."
and
!
$showHidden
){
continue
;
}
//如果当前目录$rdir不允许列出,则判断当前目录下的目录是否配置为可以列出
if
(!
$isListable
){
$dir
=
$rdir
.
$file
;
if
(
is_dir
(
$dir
)){
$dir
=
$this
->joinPath(
$dir
,
'/'
);
if
(
$this
->user->isFolderListable(
$user
,
$dir
)){
goto
listFolder;
}
}
continue
;
}
listFolder:
$stats
= stat(
$rdir
.
$file
);
if
(
is_dir
(
$rdir
.
"/"
.
$file
))
$mode
=
"d"
;
else
$mode
=
"-"
;
$mode
.=
$this
->mode2char(
$stats
[
'mode'
]);
if
(
$format
==
'mlsd'
){
if
(
$mode
[0] ==
'd'
){
$filelist
.=
'Type=dir;Modify='
.
gmdate
(
'YmdHis'
,
$stats
[
'mtime'
]).
';UNIX.mode='
.
$mode
.
'; '
.
$file
.
"\r\n"
;
}
else
{
$filelist
.=
'Type=file;Size='
.
$stats
[
'size'
].
';Modify='
.
gmdate
(
'YmdHis'
,
$stats
[
'mtime'
]).
';UNIX.mode='
.
$mode
.
'; '
.
$file
.
"\r\n"
;
}
}
else
{
$uidfill
=
""
;
for
(
$i
=
strlen
(
$stats
[
'uid'
]);
$i
< 5;
$i
++)
$uidfill
.=
" "
;
$gidfill
=
""
;
for
(
$i
=
strlen
(
$stats
[
'gid'
]);
$i
< 5;
$i
++)
$gidfill
.=
" "
;
$sizefill
=
""
;
for
(
$i
=
strlen
(
$stats
[
'size'
]);
$i
< 11;
$i
++)
$sizefill
.=
" "
;
$nlinkfill
=
""
;
for
(
$i
=
strlen
(
$stats
[
'nlink'
]);
$i
< 5;
$i
++)
$nlinkfill
.=
" "
;
$mtime
=
date
(
"M d H:i"
,
$stats
[
'mtime'
]);
$filelist
.=
$mode
.
$nlinkfill
.
$stats
[
'nlink'
] .
" "
.
$stats
[
'uid'
] .
$uidfill
.
$stats
[
'gid'
] .
$gidfill
.
$sizefill
.
$stats
[
'size'
] .
" "
.
$mtime
.
" "
.
$file
.
"\r\n"
;
}
}
closedir
(
$handle
);
}
return
$filelist
;
}
/**
* 将文件的全新从数字转换为字符串
* @param int $int
*/
public
function
mode2char(
$int
){
$mode
=
''
;
$moded
= sprintf(
"%o"
, (
$int
& 000777));
$mode1
=
substr
(
$moded
, 0, 1);
$mode2
=
substr
(
$moded
, 1, 1);
$mode3
=
substr
(
$moded
, 2, 1);
switch
(
$mode1
) {
case
"0"
:
$mode
.=
"---"
;
break
;
case
"1"
:
$mode
.=
"--x"
;
break
;
case
"2"
:
$mode
.=
"-w-"
;
break
;
case
"3"
:
$mode
.=
"-wx"
;
break
;
case
"4"
:
$mode
.=
"r--"
;
break
;
case
"5"
:
$mode
.=
"r-x"
;
break
;
case
"6"
:
$mode
.=
"rw-"
;
break
;
case
"7"
:
$mode
.=
"rwx"
;
break
;
}
switch
(
$mode2
) {
case
"0"
:
$mode
.=
"---"
;
break
;
case
"1"
:
$mode
.=
"--x"
;
break
;
case
"2"
:
$mode
.=
"-w-"
;
break
;
case
"3"
:
$mode
.=
"-wx"
;
break
;
case
"4"
:
$mode
.=
"r--"
;
break
;
case
"5"
:
$mode
.=
"r-x"
;
break
;
case
"6"
:
$mode
.=
"rw-"
;
break
;
case
"7"
:
$mode
.=
"rwx"
;
break
;
}
switch
(
$mode3
) {
case
"0"
:
$mode
.=
"---"
;
break
;
case
"1"
:
$mode
.=
"--x"
;
break
;
case
"2"
:
$mode
.=
"-w-"
;
break
;
case
"3"
:
$mode
.=
"-wx"
;
break
;
case
"4"
:
$mode
.=
"r--"
;
break
;
case
"5"
:
$mode
.=
"r-x"
;
break
;
case
"6"
:
$mode
.=
"rw-"
;
break
;
case
"7"
:
$mode
.=
"rwx"
;
break
;
}
return
$mode
;
}
/**
* 设置用户当前的路径
* @param $user
* @param $pwd
*/
public
function
setUserDir(
$user
,
$cdir
){
$old_dir
=
$this
->session[
$user
][
'pwd'
];
if
(
$old_dir
==
$cdir
){
return
$cdir
;
}
if
(
$cdir
[0] !=
'/'
)
$cdir
=
$this
->joinPath(
$old_dir
,
$cdir
);
$this
->session[
$user
][
'pwd'
] =
$cdir
;
$abs_dir
=
realpath
(
$this
->getAbsDir(
$user
));
if
(!
$abs_dir
){
$this
->session[
$user
][
'pwd'
] =
$old_dir
;
return
false;
}
$this
->session[
$user
][
'pwd'
] =
$this
->joinPath(
'/'
,
substr
(
$abs_dir
,
strlen
(
$this
->session[
$user
][
'home'
])));
$this
->session[
$user
][
'pwd'
] =
$this
->joinPath(
$this
->session[
$user
][
'pwd'
],
'/'
);
$this
->log(
"CHDIR: $old_dir -> $cdir"
);
return
$this
->session[
$user
][
'pwd'
];
}
/**
* 获取全路径
* @param $user
* @param $file
* @return string
*/
public
function
fillDirName(
$user
,
$file
){
if
(
substr
(
$file
, 0, 1) !=
"/"
){
$file
=
'/'
.
$file
;
$file
=
$this
->joinPath(
$this
->getUserDir(
$user
),
$file
);
}
$file
=
$this
->joinPath(
$this
->session[
$user
][
'home'
],
$file
);
return
$file
;
}
/**
* 获取用户路径
* @param unknown $user
*/
public
function
getUserDir(
$user
){
return
$this
->session[
$user
][
'pwd'
];
}
/**
* 获取用户的当前文件系统绝对路径,非chroot路径
* @param $user
* @return string
*/
public
function
getAbsDir(
$user
){
$rdir
=
$this
->joinPath(
$this
->session[
$user
][
'home'
],
$this
->session[
$user
][
'pwd'
]);
return
$rdir
;
}
/**
* 路径连接
* @param string $path1
* @param string $path2
* @return string
*/
public
function
joinPath(
$path1
,
$path2
){
$path1
= rtrim(
$path1
,
'/'
);
$path2
= trim(
$path2
,
'/'
);
return
$path1
.
'/'
.
$path2
;
}
/**
* IP判断
* @param string $ip
* @return boolean
*/
public
function
isIPAddress(
$ip
){
if
(!
is_numeric
(
$ip
[0]) ||
$ip
[0] < 1 ||
$ip
[0] > 254) {
return
false;
}
elseif
(!
is_numeric
(
$ip
[1]) ||
$ip
[1] < 0 ||
$ip
[1] > 254) {
return
false;
}
elseif
(!
is_numeric
(
$ip
[2]) ||
$ip
[2] < 0 ||
$ip
[2] > 254) {
return
false;
}
elseif
(!
is_numeric
(
$ip
[3]) ||
$ip
[3] < 1 ||
$ip
[3] > 254) {
return
false;
}
elseif
(!
is_numeric
(
$ip
[4]) ||
$ip
[4] < 1 ||
$ip
[4] > 500) {
return
false;
}
elseif
(!
is_numeric
(
$ip
[5]) ||
$ip
[5] < 1 ||
$ip
[5] > 500) {
return
false;
}
else
{
return
true;
}
}
/**
* 获取pasv端口
* @return number
*/
public
function
getPasvPort(){
$min
=
is_int
(
$this
->pasv_port_range[0])?
$this
->pasv_port_range[0]:55000;
$max
=
is_int
(
$this
->pasv_port_range[1])?
$this
->pasv_port_range[1]:60000;
$max
=
$max
<= 65535 ?
$max
: 65535;
$loop
= 0;
$port
= 0;
while
(
$loop
< 10){
$port
= mt_rand(
$min
,
$max
);
if
(
$this
->isAvailablePasvPort(
$port
)){
break
;
}
$loop
++;
}
return
$port
;
}
public
function
pushPasvPort(
$port
){
$shm_data
=
$this
->shm->read();
if
(
$shm_data
!== false){
if
(isset(
$shm_data
[
'pasv_port'
])){
array_push
(
$shm_data
[
'pasv_port'
],
$port
);
}
else
{
$shm_data
[
'pasv_port'
] =
array
(
$port
);
}
$this
->shm->write(
$shm_data
);
$this
->log(
'Push pasv port: '
.implode(
','
,
$shm_data
[
'pasv_port'
]));
return
true;
}
return
false;
}
public
function
popPasvPort(
$port
){
$shm_data
=
$this
->shm->read();
if
(
$shm_data
!== false){
if
(isset(
$shm_data
[
'pasv_port'
])){
$tmp
=
array
();
foreach
(
$shm_data
[
'pasv_port'
]
as
$p
){
if
(
$p
!=
$port
){
$tmp
[] =
$p
;
}
}
$shm_data
[
'pasv_port'
] =
$tmp
;
}
$this
->shm->write(
$shm_data
);
$this
->log(
'Pop pasv port: '
.implode(
','
,
$shm_data
[
'pasv_port'
]));
return
true;
}
return
false;
}
public
function
isAvailablePasvPort(
$port
){
$shm_data
=
$this
->shm->read();
if
(
$shm_data
!== false){
if
(isset(
$shm_data
[
'pasv_port'
])){
return
!in_array(
$port
,
$shm_data
[
'pasv_port'
]);
}
return
true;
}
return
false;
}
/**
* 获取当前数据链接tcp个数
*/
public
function
getDataConnections(){
$shm_data
=
$this
->shm->read();
if
(
$shm_data
!== false){
if
(isset(
$shm_data
[
'pasv_port'
])){
return
count
(
$shm_data
[
'pasv_port'
]);
}
}
return
0;
}
/**
* 关闭数据传输socket
* @param $user
* @return bool
*/
public
function
closeUserSock(
$user
){
$peer
= stream_socket_get_name(
$this
->session[
$user
][
'sock'
], false);
list(
$ip
,
$port
) =
explode
(
':'
,
$peer
);
//释放端口占用
$this
->popPasvPort(
$port
);
fclose(
$this
->session[
$user
][
'sock'
]);
$this
->session[
$user
][
'sock'
] = 0;
return
true;
}
/**
* @param $user
* @return resource
*/
public
function
getUserSock(
$user
){
//被动模式
if
(
$this
->session[
$user
][
'pasv'
] == true){
if
(
empty
(
$this
->session[
$user
][
'sock'
])){
$addr
= stream_socket_get_name(
$this
->session[
$user
][
'serv_sock'
], false);
list(
$ip
,
$port
) =
explode
(
':'
,
$addr
);
$sock
= stream_socket_accept(
$this
->session[
$user
][
'serv_sock'
], 5);
if
(
$sock
){
$peer
= stream_socket_get_name(
$sock
, true);
$this
->log(
"Accept: success client is $peer."
);
$this
->session[
$user
][
'sock'
] =
$sock
;
//关闭server socket
fclose(
$this
->session[
$user
][
'serv_sock'
]);
}
else
{
$this
->log(
"Accept: failed."
);
//释放端口
$this
->popPasvPort(
$port
);
return
false;
}
}
}
return
$this
->session[
$user
][
'sock'
];
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ FTP Command
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
//==================
//RFC959
//==================
/**
* 登录用户名
* @param $fd
* @param $data
*/
public
function
cmd_USER(
$fd
,
$data
){
if
(preg_match(
"/^([a-z0-9.@]+)$/"
,
$data
)){
$user
=
strtolower
(
$data
);
$this
->connection[
$fd
][
'user'
] =
$user
;
$this
->send(
$fd
,
"331 User $user OK. Password required"
);
}
else
{
$this
->send(
$fd
,
"530 Login authentication failed"
);
}
}
/**
* 登录密码
* @param $fd
* @param $data
*/
public
function
cmd_PASS(
$fd
,
$data
){
$user
=
$this
->connection[
$fd
][
'user'
];
$pass
=
$data
;
$info
=
$this
->getConnectionInfo(
$fd
);
$ip
=
$info
[
'remote_ip'
];
//判断登陆失败次数
if
(
$this
->user->isAttemptLimit(
$this
->shm,
$user
,
$ip
)){
$this
->send(
$fd
,
"530 Login authentication failed: Too many login attempts. Blocked in 10 minutes."
);
return
;
}
if
(
$this
->user->checkUser(
$user
,
$pass
,
$ip
)){
$dir
=
"/"
;
$this
->session[
$user
][
'pwd'
] =
$dir
;
//ftp根目录
$this
->session[
$user
][
'home'
] =
$this
->user->getHomeDir(
$user
);
if
(
empty
(
$this
->session[
$user
][
'home'
]) || !
is_dir
(
$this
->session[
$user
][
'home'
])){
$this
->send(
$fd
,
"530 Login authentication failed: `home` path error."
);
}
else
{
$this
->connection[
$fd
][
'login'
] = true;
//在线用户
$shm_data
=
$this
->user->addOnline(
$this
->shm,
$this
->server,
$user
,
$fd
,
$ip
);
$this
->log(
'SHM: '
.json_encode(
$shm_data
) );
$this
->send(
$fd
,
"230 OK. Current restricted directory is "
.
$dir
);
$this
->log(
'User '
.
$user
.
' has login successfully! IP: '
.
$ip
,
'warn'
);
}
}
else
{
$this
->user->addAttempt(
$this
->shm,
$user
,
$ip
);
$this
->log(
'User '
.
$user
.
' login fail! IP: '
.
$ip
,
'warn'
);
$this
->send(
$fd
,
"530 Login authentication failed: check your pass or ip allow rules."
);
}
}
/**
* 更改当前目录
* @param $fd
* @param $data
*/
public
function
cmd_CWD(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
if
((
$dir
=
$this
->setUserDir(
$user
,
$data
)) != false){
$this
->send(
$fd
,
"250 OK. Current directory is "
.
$dir
);
}
else
{
$this
->send(
$fd
,
"550 Can't change directory to "
.
$data
.
": No such file or directory"
);
}
}
/**
* 返回上级目录
* @param $fd
* @param $data
*/
public
function
cmd_CDUP(
$fd
,
$data
){
$data
=
'..'
;
$this
->cmd_CWD(
$fd
,
$data
);
}
/**
* 退出服务器
* @param $fd
* @param $data
*/
public
function
cmd_QUIT(
$fd
,
$data
){
$this
->send(
$fd
,
"221 Goodbye."
);
unset(
$this
->connection[
$fd
]);
}
/**
* 获取当前目录
* @param $fd
* @param $data
*/
public
function
cmd_PWD(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$this
->send(
$fd
,
"257 \""
.
$this
->getUserDir(
$user
) .
"\" is your current location"
);
}
/**
* 下载文件
* @param $fd
* @param $data
*/
public
function
cmd_RETR(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$ftpsock
=
$this
->getUserSock(
$user
);
if
(!
$ftpsock
){
$this
->send(
$fd
,
"425 Connection Error"
);
return
;
}
if
((
$file
=
$this
->getFile(
$user
,
$data
)) != false){
if
(
$this
->user->isReadable(
$user
,
$file
)){
$this
->send(
$fd
,
"150 Connecting to client"
);
if
(
$fp
=
fopen
(
$file
,
"rb"
)){
//断点续传
if
(isset(
$this
->session[
$user
][
'rest_offset'
])){
if
(!
fseek
(
$fp
,
$this
->session[
$user
][
'rest_offset'
])){
$this
->log(
"RETR at offset "
.
ftell
(
$fp
));
}
else
{
$this
->log(
"RETR at offset "
.
ftell
(
$fp
).
' fail.'
);
}
unset(
$this
->session[
$user
][
'rest_offset'
]);
}
while
(!
feof
(
$fp
)){
$cont
=
fread
(
$fp
, 8192);
if
(!fwrite(
$ftpsock
,
$cont
))
break
;
}
if
(fclose(
$fp
)
and
$this
->closeUserSock(
$user
)){
$this
->send(
$fd
,
"226 File successfully transferred"
);
$this
->log(
$user
.
"\tGET:"
.
$file
,
'info'
);
}
else
{
$this
->send(
$fd
,
"550 Error during file-transfer"
);
}
}
else
{
$this
->send(
$fd
,
"550 Can't open "
.
$data
.
": Permission denied"
);
}
}
else
{
$this
->send(
$fd
,
"550 You're unauthorized: Permission denied"
);
}
}
else
{
$this
->send(
$fd
,
"550 Can't open "
.
$data
.
": No such file or directory"
);
}
}
/**
* 上传文件
* @param $fd
* @param $data
*/
public
function
cmd_STOR(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$ftpsock
=
$this
->getUserSock(
$user
);
if
(!
$ftpsock
){
$this
->send(
$fd
,
"425 Connection Error"
);
return
;
}
$file
=
$this
->fillDirName(
$user
,
$data
);
$isExist
= false;
if
(
file_exists
(
$file
))
$isExist
= true;
if
((!
$isExist
&&
$this
->user->isWritable(
$user
,
$file
)) ||
(
$isExist
&&
$this
->user->isAppendable(
$user
,
$file
))){
if
(
$isExist
){
$fp
=
fopen
(
$file
,
"rb+"
);
$this
->log(
"OPEN for STOR."
);
}
else
{
$fp
=
fopen
(
$file
,
'wb'
);
$this
->log(
"CREATE for STOR."
);
}
if
(!
$fp
){
$this
->send(
$fd
,
"553 Can't open that file: Permission denied"
);
}
else
{
//断点续传,需要Append权限
if
(isset(
$this
->session[
$user
][
'rest_offset'
])){
if
(!
fseek
(
$fp
,
$this
->session[
$user
][
'rest_offset'
])){
$this
->log(
"STOR at offset "
.
ftell
(
$fp
));
}
else
{
$this
->log(
"STOR at offset "
.
ftell
(
$fp
).
' fail.'
);
}
unset(
$this
->session[
$user
][
'rest_offset'
]);
}
$this
->send(
$fd
,
"150 Connecting to client"
);
while
(!
feof
(
$ftpsock
)){
$cont
=
fread
(
$ftpsock
, 8192);
if
(!
$cont
)
break
;
if
(!fwrite(
$fp
,
$cont
))
break
;
}
touch(
$file
);
//设定文件的访问和修改时间
if
(fclose(
$fp
)
and
$this
->closeUserSock(
$user
)){
$this
->send(
$fd
,
"226 File successfully transferred"
);
$this
->log(
$user
.
"\tPUT: $file"
,
'info'
);
}
else
{
$this
->send(
$fd
,
"550 Error during file-transfer"
);
}
}
}
else
{
$this
->send(
$fd
,
"550 You're unauthorized: Permission denied"
);
$this
->closeUserSock(
$user
);
}
}
/**
* 文件追加
* @param $fd
* @param $data
*/
public
function
cmd_APPE(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$ftpsock
=
$this
->getUserSock(
$user
);
if
(!
$ftpsock
){
$this
->send(
$fd
,
"425 Connection Error"
);
return
;
}
$file
=
$this
->fillDirName(
$user
,
$data
);
$isExist
= false;
if
(
file_exists
(
$file
))
$isExist
= true;
if
((!
$isExist
&&
$this
->user->isWritable(
$user
,
$file
)) ||
(
$isExist
&&
$this
->user->isAppendable(
$user
,
$file
))){
$fp
=
fopen
(
$file
,
"rb+"
);
if
(!
$fp
){
$this
->send(
$fd
,
"553 Can't open that file: Permission denied"
);
}
else
{
//断点续传,需要Append权限
if
(isset(
$this
->session[
$user
][
'rest_offset'
])){
if
(!
fseek
(
$fp
,
$this
->session[
$user
][
'rest_offset'
])){
$this
->log(
"APPE at offset "
.
ftell
(
$fp
));
}
else
{
$this
->log(
"APPE at offset "
.
ftell
(
$fp
).
' fail.'
);
}
unset(
$this
->session[
$user
][
'rest_offset'
]);
}
$this
->send(
$fd
,
"150 Connecting to client"
);
while
(!
feof
(
$ftpsock
)){
$cont
=
fread
(
$ftpsock
, 8192);
if
(!
$cont
)
break
;
if
(!fwrite(
$fp
,
$cont
))
break
;
}
touch(
$file
);
//设定文件的访问和修改时间
if
(fclose(
$fp
)
and
$this
->closeUserSock(
$user
)){
$this
->send(
$fd
,
"226 File successfully transferred"
);
$this
->log(
$user
.
"\tAPPE: $file"
,
'info'
);
}
else
{
$this
->send(
$fd
,
"550 Error during file-transfer"
);
}
}
}
else
{
$this
->send(
$fd
,
"550 You're unauthorized: Permission denied"
);
$this
->closeUserSock(
$user
);
}
}
/**
* 文件重命名,源文件
* @param $fd
* @param $data
*/
public
function
cmd_RNFR(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$file
=
$this
->fillDirName(
$user
,
$data
);
if
(
file_exists
(
$file
) ||
is_dir
(
$file
)){
$this
->session[
$user
][
'rename'
] =
$file
;
$this
->send(
$fd
,
"350 RNFR accepted - file exists, ready for destination"
);
}
else
{
$this
->send(
$fd
,
"550 Sorry, but that '$data' doesn't exist"
);
}
}
/**
* 文件重命名,目标文件
* @param $fd
* @param $data
*/
public
function
cmd_RNTO(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$old_file
=
$this
->session[
$user
][
'rename'
];
$new_file
=
$this
->fillDirName(
$user
,
$data
);
$isDir
= false;
if
(
is_dir
(
$old_file
)){
$isDir
= true;
$old_file
=
$this
->joinPath(
$old_file
,
'/'
);
}
if
((!
$isDir
&&
$this
->user->isRenamable(
$user
,
$old_file
)) ||
(
$isDir
&&
$this
->user->isFolderRenamable(
$user
,
$old_file
))){
if
(
empty
(
$old_file
)
or
!
is_dir
(dirname(
$new_file
))){
$this
->send(
$fd
,
"451 Rename/move failure: No such file or directory"
);
}
elseif
(rename(
$old_file
,
$new_file
)){
$this
->send(
$fd
,
"250 File successfully renamed or moved"
);
$this
->log(
$user
.
"\tRENAME: $old_file to $new_file"
,
'warn'
);
}
else
{
$this
->send(
$fd
,
"451 Rename/move failure: Operation not permitted"
);
}
}
else
{
$this
->send(
$fd
,
"550 You're unauthorized: Permission denied"
);
}
unset(
$this
->session[
$user
][
'rename'
]);
}
/**
* 删除文件
* @param $fd
* @param $data
*/
public
function
cmd_DELE(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$file
=
$this
->fillDirName(
$user
,
$data
);
if
(
$this
->user->isDeletable(
$user
,
$file
)){
if
(!
file_exists
(
$file
)){
$this
->send(
$fd
,
"550 Could not delete "
.
$data
.
": No such file or directory"
);
}
elseif
(unlink(
$file
)){
$this
->send(
$fd
,
"250 Deleted "
.
$data
);
$this
->log(
$user
.
"\tDEL: $file"
,
'warn'
);
}
else
{
$this
->send(
$fd
,
"550 Could not delete "
.
$data
.
": Permission denied"
);
}
}
else
{
$this
->send(
$fd
,
"550 You're unauthorized: Permission denied"
);
}
}
/**
* 创建目录
* @param $fd
* @param $data
*/
public
function
cmd_MKD(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$path
=
''
;
if
(
$data
[0] ==
'/'
){
$path
=
$this
->joinPath(
$this
->session[
$user
][
'home'
],
$data
);
}
else
{
$path
=
$this
->joinPath(
$this
->getAbsDir(
$user
),
$data
);
}
$path
=
$this
->joinPath(
$path
,
'/'
);
if
(
$this
->user->isFolderCreatable(
$user
,
$path
)){
if
(!
is_dir
(dirname(
$path
))){
$this
->send(
$fd
,
"550 Can't create directory: No such file or directory"
);
}
elseif
(
file_exists
(
$path
)){
$this
->send(
$fd
,
"550 Can't create directory: File exists"
);
}
else
{
if
(
mkdir
(
$path
)){
$this
->send(
$fd
,
"257 \""
.
$data
.
"\" : The directory was successfully created"
);
$this
->log(
$user
.
"\tMKDIR: $path"
,
'info'
);
}
else
{
$this
->send(
$fd
,
"550 Can't create directory: Permission denied"
);
}
}
}
else
{
$this
->send(
$fd
,
"550 You're unauthorized: Permission denied"
);
}
}
/**
* 删除目录
* @param $fd
* @param $data
*/
public
function
cmd_RMD(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$dir
=
''
;
if
(
$data
[0] ==
'/'
){
$dir
=
$this
->joinPath(
$this
->session[
$user
][
'home'
],
$data
);
}
else
{
$dir
=
$this
->fillDirName(
$user
,
$data
);
}
$dir
=
$this
->joinPath(
$dir
,
'/'
);
if
(
$this
->user->isFolderDeletable(
$user
,
$dir
)){
if
(
is_dir
(dirname(
$dir
))
and
is_dir
(
$dir
)){
if
(
count
(
glob
(
$dir
.
"/*"
))){
$this
->send(
$fd
,
"550 Can't remove directory: Directory not empty"
);
}
elseif
(
rmdir
(
$dir
)){
$this
->send(
$fd
,
"250 The directory was successfully removed"
);
$this
->log(
$user
.
"\tRMDIR: $dir"
,
'warn'
);
}
else
{
$this
->send(
$fd
,
"550 Can't remove directory: Operation not permitted"
);
}
}
elseif
(
is_dir
(dirname(
$dir
))
and
file_exists
(
$dir
)){
$this
->send(
$fd
,
"550 Can't remove directory: Not a directory"
);
}
else
{
$this
->send(
$fd
,
"550 Can't create directory: No such file or directory"
);
}
}
else
{
$this
->send(
$fd
,
"550 You're unauthorized: Permission denied"
);
}
}
/**
* 得到服务器类型
* @param
$fd
* @param
$data
*/
public
function
cmd_SYST(
$fd
,
$data
){
$this
->send(
$fd
,
"215 UNIX Type: L8"
);
}
/**
* 权限控制
* @param $fd
* @param $data
*/
public
function
cmd_SITE(
$fd
,
$data
){
if
(
substr
(
$data
, 0, 6) ==
"CHMOD "
){
$user
=
$this
->getUser(
$fd
);
$chmod
=
explode
(
" "
,
$data
, 3);
$file
=
$this
->fillDirName(
$user
,
$chmod
[2]);
if
(
$this
->user->isWritable(
$user
,
$file
)){
if
(
chmod
(
$file
, octdec(
$chmod
[1]))){
$this
->send(
$fd
,
"200 Permissions changed on {$chmod[2]}"
);
$this
->log(
$user
.
"\tCHMOD: $file to {$chmod[1]}"
,
'info'
);
}
else
{
$this
->send(
$fd
,
"550 Could not change perms on "
.
$chmod
[2] .
": Permission denied"
);
}
}
else
{
$this
->send(
$fd
,
"550 You're unauthorized: Permission denied"
);
}
}
else
{
$this
->send(
$fd
,
"500 Unknown Command"
);
}
}
/**
* 更改传输类型
* @param $fd
* @param $data
*/
public
function
cmd_TYPE(
$fd
,
$data
){
switch
(
$data
){
case
"A"
:
$type
=
"ASCII"
;
break
;
case
"I"
:
$type
=
"8-bit binary"
;
break
;
}
$this
->send(
$fd
,
"200 TYPE is now "
.
$type
);
}
/**
* 遍历目录
* @param $fd
* @param $data
*/
public
function
cmd_LIST(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$ftpsock
=
$this
->getUserSock(
$user
);
if
(!
$ftpsock
){
$this
->send(
$fd
,
"425 Connection Error"
);
return
;
}
$path
=
$this
->joinPath(
$this
->getAbsDir(
$user
),
'/'
);
$this
->send(
$fd
,
"150 Opening ASCII mode data connection for file list"
);
$filelist
=
$this
->getFileList(
$user
,
$path
, true);
fwrite(
$ftpsock
,
$filelist
);
$this
->send(
$fd
,
"226 Transfer complete."
);
$this
->closeUserSock(
$user
);
}
/**
* 建立数据传输通
* @param $fd
* @param $data
*/
// 不使用主动模式
// public function cmd_PORT($fd, $data){
// $user = $this->getUser($fd);
// $port = explode(",", $data);
// if (count($port) != 6){
// $this->send($fd, "501 Syntax error in IP address");
// }else{
// if (!$this->isIPAddress($port)){
// $this->send($fd, "501 Syntax error in IP address");
// return;
// }
// $ip = $port[0] . "." . $port[1] . "." . $port[2] . "." . $port[3];
// $port = hexdec(dechex($port[4]) . dechex($port[5]));
// if ($port < 1024){
// $this->send($fd, "501 Sorry, but I won't connect to ports < 1024");
// }elseif ($port > 65000){
// $this->send($fd, "501 Sorry, but I won't connect to ports > 65000");
// }else{
// $ftpsock = fsockopen($ip, $port);
// if ($ftpsock){
// $this->session[$user]['sock'] = $ftpsock;
// $this->session[$user]['pasv'] = false;
// $this->send($fd, "200 PORT command successful");
// }else{
// $this->send($fd, "501 Connection failed");
// }
// }
// }
// }
/**
* 被动模式
* @param unknown $fd
* @param unknown $data
*/
public
function
cmd_PASV(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$ssl
= false;
$pasv_port
=
$this
->getPasvPort();
if
(
$this
->connection[
$fd
][
'ssl'
] === true){
$ssl
= true;
$context
= stream_context_create();
// local_cert must be in PEM format
stream_context_set_option(
$context
,
'ssl'
,
'local_cert'
,
$this
->setting[
'ssl_cert_file'
]);
// Path to local private key file
stream_context_set_option(
$context
,
'ssl'
,
'local_pk'
,
$this
->setting[
'ssl_key_file'
]);
stream_context_set_option(
$context
,
'ssl'
,
'allow_self_signed'
, true);
stream_context_set_option(
$context
,
'ssl'
,
'verify_peer'
, false);
stream_context_set_option(
$context
,
'ssl'
,
'verify_peer_name'
, false);
stream_context_set_option(
$context
,
'ssl'
,
'passphrase'
,
''
);
// Create the server socket
$sock
= stream_socket_server(
'ssl://0.0.0.0:'
.
$pasv_port
,
$errno
,
$errstr
, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
$context
);
}
else
{
$sock
= stream_socket_server(
'tcp://0.0.0.0:'
.
$pasv_port
,
$errno
,
$errstr
, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
}
if
(
$sock
){
$addr
= stream_socket_get_name(
$sock
, false);
list(
$ip
,
$port
) =
explode
(
':'
,
$addr
);
$ipArr
= swoole_get_local_ip();
foreach
(
$ipArr
as
$nic
=>
$addr
){
$ip
=
$addr
;
}
$this
->log(
"ServerSock: $ip:$port"
);
$ip
=
str_replace
(
'.'
,
','
,
$ip
);
$this
->send(
$fd
,
"227 Entering Passive Mode ({$ip},"
.(
intval
(
$port
) >> 8 & 0xff).
","
.(
intval
(
$port
) & 0xff).
"). "
.
$port
.
" "
.(
$ssl
?
'ssl'
:
''
));
$this
->session[
$user
][
'serv_sock'
] =
$sock
;
$this
->session[
$user
][
'pasv'
] = true;
$this
->pushPasvPort(
$port
);
}
else
{
fclose(
$sock
);
$this
->send(
$fd
,
"500 failed to create data socket: "
.
$errstr
);
}
}
public
function
cmd_NOOP(
$fd
,
$data
){
$this
->send(
$fd
,
"200 OK"
);
}
//==================
//RFC2228
//==================
public
function
cmd_PBSZ(
$fd
,
$data
){
$this
->send(
$fd
,
'200 Command okay.'
);
}
public
function
cmd_PROT(
$fd
,
$data
){
if
(trim(
$data
) ==
'P'
){
$this
->connection[
$fd
][
'ssl'
] = true;
$this
->send(
$fd
,
'200 Set Private level on data connection.'
);
}
elseif
(trim(
$data
) ==
'C'
){
$this
->connection[
$fd
][
'ssl'
] = false;
$this
->send(
$fd
,
'200 Set Clear level on data connection.'
);
}
else
{
$this
->send(
$fd
,
'504 Command not implemented for that parameter.'
);
}
}
//==================
//RFC2389
//==================
public
function
cmd_FEAT(
$fd
,
$data
){
$this
->send(
$fd
,
'211-Features supported'
);
$this
->send(
$fd
,
'MDTM'
);
$this
->send(
$fd
,
'SIZE'
);
$this
->send(
$fd
,
'SITE CHMOD'
);
$this
->send(
$fd
,
'REST STREAM'
);
$this
->send(
$fd
,
'MLSD Type*;Size*;Modify*;UNIX.mode*;'
);
$this
->send(
$fd
,
'PBSZ'
);
$this
->send(
$fd
,
'PROT'
);
$this
->send(
$fd
,
'211 End'
);
}
//关闭utf8对中文文件名有影响
public
function
cmd_OPTS(
$fd
,
$data
){
$this
->send(
$fd
,
'502 Command not implemented.'
);
}
//==================
//RFC3659
//==================
/**
* 获取文件修改时间
* @param unknown $fd
* @param unknown $data
*/
public
function
cmd_MDTM(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
if
((
$file
=
$this
->getFile(
$user
,
$data
)) != false){
$this
->send(
$fd
,
'213 '
.
date
(
'YmdHis.u'
,
filemtime
(
$file
)));
}
else
{
$this
->send(
$fd
,
'550 No file named "'
.
$data
.
'"'
);
}
}
/**
* 获取文件大小
* @param $fd
* @param $data
*/
public
function
cmd_SIZE(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
if
((
$file
=
$this
->getFile(
$user
,
$data
)) != false){
$this
->send(
$fd
,
'213 '
.
filesize
(
$file
));
}
else
{
$this
->send(
$fd
,
'550 No file named "'
.
$data
.
'"'
);
}
}
/**
* 获取文件列表
* @param unknown $fd
* @param unknown $data
*/
public
function
cmd_MLSD(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$ftpsock
=
$this
->getUserSock(
$user
);
if
(!
$ftpsock
){
$this
->send(
$fd
,
"425 Connection Error"
);
return
;
}
$path
=
$this
->joinPath(
$this
->getAbsDir(
$user
),
'/'
);
$this
->send(
$fd
,
"150 Opening ASCII mode data connection for file list"
);
$filelist
=
$this
->getFileList(
$user
,
$path
, true,
'mlsd'
);
fwrite(
$ftpsock
,
$filelist
);
$this
->send(
$fd
,
"226 Transfer complete."
);
$this
->closeUserSock(
$user
);
}
/**
* 设置文件offset
* @param unknown $fd
* @param unknown $data
*/
public
function
cmd_REST(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$data
= preg_replace(
'/[^0-9]/'
,
''
,
$data
);
if
(
$data
!=
''
){
$this
->session[
$user
][
'rest_offset'
] =
$data
;
$this
->send(
$fd
,
'350 Restarting at '
.
$data
.
'. Send STOR or RETR'
);
}
else
{
$this
->send(
$fd
,
'500 Syntax error, offset unrecognized.'
);
}
}
/**
* 获取文件hash值
* @param unknown $fd
* @param unknown $data
*/
public
function
cmd_HASH(
$fd
,
$data
){
$user
=
$this
->getUser(
$fd
);
$ftpsock
=
$this
->getUserSock(
$user
);
if
((
$file
=
$this
->getFile(
$user
,
$data
)) != false){
if
(
is_file
(
$file
)){
$algo
=
'sha512'
;
$this
->send(
$fd
,
"200 "
.hash_file(
$algo
,
$file
));
}
else
{
$this
->send(
$fd
,
"550 Can't open "
.
$data
.
": No such file。"
);
}
}
else
{
$this
->send(
$fd
,
"550 Can't open "
.
$data
.
": No such file。"
);
}
}
/**
* 控制台命令
* @param unknown $fd
* @param unknown $data
*/
public
function
cmd_CONSOLE(
$fd
,
$data
){
$group
=
$this
->user->getUserProfile(
$this
->getUser(
$fd
));
$group
=
$group
[
'group'
];
if
(
$group
!=
'admin'
){
$this
->send(
$fd
,
"550 You're unauthorized: Permission denied"
);
return
;
}
$data
=
explode
(
'||'
,
$data
);
$cmd
=
strtoupper
(
$data
[0]);
switch
(
$cmd
){
case
'USER-ONLINE'
:
$shm_data
=
$this
->shm->read();
$list
=
array
();
if
(
$shm_data
!== false){
if
(isset(
$shm_data
[
'online'
])){
$list
=
$shm_data
[
'online'
];
}
}
$this
->send(
$fd
,
'200 '
.json_encode(
$list
));
break
;
//Format: user-add||{"user":"","pass":"","home":"","expired":"","active":boolean,"group":"","description":"","email":""}
case
'USER-ADD'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$user
= isset(
$json
[
'user'
])?
$json
[
'user'
]:
''
;
$pass
= isset(
$json
[
'pass'
])?
$json
[
'pass'
]:
''
;
$home
= isset(
$json
[
'home'
])?
$json
[
'home'
]:
''
;
$expired
= isset(
$json
[
'expired'
])?
$json
[
'expired'
]:
'1999-01-01'
;
$active
= isset(
$json
[
'active'
])?
$json
[
'active'
]:false;
$group
= isset(
$json
[
'group'
])?
$json
[
'group'
]:
''
;
$description
= isset(
$json
[
'description'
])?
$json
[
'description'
]:
''
;
$email
= isset(
$json
[
'email'
])?
$json
[
'email'
]:
''
;
if
(
$this
->user->addUser(
$user
,
$pass
,
$home
,
$expired
,
$active
,
$group
,
$description
,
$email
)){
$this
->user->save();
$this
->user->reload();
$this
->send(
$fd
,
'200 User "'
.
$user
.
'" added.'
);
}
else
{
$this
->send(
$fd
,
'550 Add fail!'
);
}
}
else
{
$this
->send(
$fd
,
'500 Syntax error: USER-ADD||{"user":"","pass":"","home":"","expired":"","active":boolean,"group":"","description":""}'
);
}
break
;
//Format: user-set-profile||{"user":"","profile":[]}
case
'USER-SET-PROFILE'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$user
= isset(
$json
[
'user'
])?
$json
[
'user'
]:
''
;
$profile
= isset(
$json
[
'profile'
])?
$json
[
'profile'
]:
array
();
if
(
$this
->user->setUserProfile(
$user
,
$profile
)){
$this
->user->save();
$this
->user->reload();
$this
->send(
$fd
,
'200 User "'
.
$user
.
'" profile changed.'
);
}
else
{
$this
->send(
$fd
,
'550 Set profile fail!'
);
}
}
else
{
$this
->send(
$fd
,
'500 Syntax error: USER-SET-PROFILE||{"user":"","profile":[]}'
);
}
break
;
//Format: user-get-profile||{"user":""}
case
'USER-GET-PROFILE'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$user
= isset(
$json
[
'user'
])?
$json
[
'user'
]:
''
;
$this
->user->reload();
if
(
$profile
=
$this
->user->getUserProfile(
$user
)){
$this
->send(
$fd
,
'200 '
.json_encode(
$profile
));
}
else
{
$this
->send(
$fd
,
'550 Get profile fail!'
);
}
}
else
{
$this
->send(
$fd
,
'500 Syntax error: USER-GET-PROFILE||{"user":""}'
);
}
break
;
//Format: user-delete||{"user":""}
case
'USER-DELETE'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$user
= isset(
$json
[
'user'
])?
$json
[
'user'
]:
''
;
if
(
$this
->user->delUser(
$user
)){
$this
->user->save();
$this
->user->reload();
$this
->send(
$fd
,
'200 User '
.
$user
.
' deleted.'
);
}
else
{
$this
->send(
$fd
,
'550 Delete user fail!'
);
}
}
else
{
$this
->send(
$fd
,
'500 Syntax error: USER-DELETE||{"user":""}'
);
}
break
;
case
'USER-LIST'
:
$this
->user->reload();
$list
=
$this
->user->getUserList();
$this
->send(
$fd
,
'200 '
.json_encode(
$list
));
break
;
//Format: group-add||{"group":"","home":""}
case
'GROUP-ADD'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$group
= isset(
$json
[
'group'
])?
$json
[
'group'
]:
''
;
$home
= isset(
$json
[
'home'
])?
$json
[
'home'
]:
''
;
if
(
$this
->user->addGroup(
$group
,
$home
)){
$this
->user->save();
$this
->user->reload();
$this
->send(
$fd
,
'200 Group "'
.
$group
.
'" added.'
);
}
else
{
$this
->send(
$fd
,
'550 Add group fail!'
);
}
}
else
{
$this
->send(
$fd
,
'500 Syntax error: GROUP-ADD||{"group":"","home":""}'
);
}
break
;
//Format: group-set-profile||{"group":"","profile":[]}
case
'GROUP-SET-PROFILE'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$group
= isset(
$json
[
'group'
])?
$json
[
'group'
]:
''
;
$profile
= isset(
$json
[
'profile'
])?
$json
[
'profile'
]:
array
();
if
(
$this
->user->setGroupProfile(
$group
,
$profile
)){
$this
->user->save();
$this
->user->reload();
$this
->send(
$fd
,
'200 Group "'
.
$group
.
'" profile changed.'
);
}
else
{
$this
->send(
$fd
,
'550 Set profile fail!'
);
}
}
else
{
$this
->send(
$fd
,
'500 Syntax error: GROUP-SET-PROFILE||{"group":"","profile":[]}'
);
}
break
;
//Format: group-get-profile||{"group":""}
case
'GROUP-GET-PROFILE'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$group
= isset(
$json
[
'group'
])?
$json
[
'group'
]:
''
;
$this
->user->reload();
if
(
$profile
=
$this
->user->getGroupProfile(
$group
)){
$this
->send(
$fd
,
'200 '
.json_encode(
$profile
));
}
else
{
$this
->send(
$fd
,
'550 Get profile fail!'
);
}
}
else
{
$this
->send(
$fd
,
'500 Syntax error: GROUP-GET-PROFILE||{"group":""}'
);
}
break
;
//Format: group-delete||{"group":""}
case
'GROUP-DELETE'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$group
= isset(
$json
[
'group'
])?
$json
[
'group'
]:
''
;
if
(
$this
->user->delGroup(
$group
)){
$this
->user->save();
$this
->user->reload();
$this
->send(
$fd
,
'200 Group '
.
$group
.
' deleted.'
);
}
else
{
$this
->send(
$fd
,
'550 Delete group fail!'
);
}
}
else
{
$this
->send(
$fd
,
'500 Syntax error: GROUP-DELETE||{"group":""}'
);
}
break
;
case
'GROUP-LIST'
:
$this
->user->reload();
$list
=
$this
->user->getGroupList();
$this
->send(
$fd
,
'200 '
.json_encode(
$list
));
break
;
//获取组用户列表
//Format: group-user-list||{"group":""}
case
'GROUP-USER-LIST'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$group
= isset(
$json
[
'group'
])?
$json
[
'group'
]:
''
;
$this
->user->reload();
$this
->send(
$fd
,
'200 '
.json_encode(
$this
->user->getUserListOfGroup(
$group
)));
}
else
{
$this
->send(
$fd
,
'500 Syntax error: GROUP-USER-LIST||{"group":""}'
);
}
break
;
// 获取磁盘空间
//Format: disk-total||{"path":""}
case
'DISK-TOTAL'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$path
= isset(
$json
[
'path'
])?
$json
[
'path'
]:
''
;
$size
= 0;
if
(
$path
){
$size
= disk_total_space(
$path
);
}
$this
->send(
$fd
,
'200 '
.
$size
);
}
else
{
$this
->send(
$fd
,
'500 Syntax error: DISK-TOTAL||{"path":""}'
);
}
break
;
// 获取磁盘空间
//Format: disk-total||{"path":""}
case
'DISK-FREE'
:
if
(isset(
$data
[1])){
$json
= json_decode(trim(
$data
[1]),true);
$path
= isset(
$json
[
'path'
])?
$json
[
'path'
]:
''
;
$size
= 0;
if
(
$path
){
$size
= disk_free_space(
$path
);
}
$this
->send(
$fd
,
'200 '
.
$size
);
}
else
{
$this
->send(
$fd
,
'500 Syntax error: DISK-FREE||{"path":""}'
);
}
break
;
case
'HELP'
:
$list
=
'USER-ONLINE USER-ADD USER-SET-PROFILE USER-GET-PROFILE USER-DELETE USER-LIST GROUP-ADD GROUP-SET-PROFILE GROUP-GET-PROFILE GROUP-DELETE GROUP-LIST GROUP-USER-LIST DISK-TOTAL DISK-FREE'
;
$this
->send(
$fd
,
'200 '
.
$list
);
break
;
default
:
$this
->send(
$fd
,
'500 Syntax error.'
);
}
}
}
|
总结:
至此,我们就可以实现一个完整的ftp服务器了。这个服务器的功能可以进行完全个性化定制。如果您有好的建议,也可以留言给我,谢谢.
最后此篇关于使用PHP如何实现高效安全的ftp服务器(二)的文章就讲到这里了,如果你想了解更多关于使用PHP如何实现高效安全的ftp服务器(二)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在学习 Spring 安全性,但我对它的灵活性感到困惑.. 我知道我可以通过在标签中定义规则来保护网址 然后我看到有一个@secure 注释可以保护方法。 然后还有其他注释来保护域(或 POJO)
假设有一个 key 加密 key 位于内存中并且未写入文件或数据库... byte[] kek = new byte[32]; secureRandom.nextBytes(kek); byte[]
我有 Spring Security 3.2.0 RC1 的问题 我正在使用标签来连接我 这表示“方法‘setF
我正在创建一个使用 Node Js 服务器 API 的 Flutter 应用程序。对于授权,我决定将 JWT 与私钥/公钥一起使用。服务器和移动客户端之间的通信使用 HTTPS。 Flutter 应用
在过去的几年里,我一直在使用范围从 Raphael.js 的 javascript 库。至 D3 ,我已经为自己的教育操纵了来自网络各地的动画。我已经从各种 git 存储库下载了 js 脚本,例如 s
在 python 中实现身份验证的好方法是什么?已经存在的东西也很好。我需要它通过不受信任的网络连接进行身份验证。它不需要太高级,只要足以安全地获取通用密码即可。我查看了 ssl 模块。但那个模块让我
我正在尝试学习“如何在 Hadoop 中实现 Kerberos?”我已经看过这个文档 https://issues.apache.org/jira/browse/HADOOP-4487我还了解了基本的
我有一个带有 apache2、php、mysql 的生产服务器。我现在只有一个站点 (mysite.com) 作为虚拟主机。我想把 phpmyadmin、webalizer 和 webmin 放在那里
前些天在网上看到防火墙软件OPNsense,对其有了兴趣,以前写过一个其前面的一个软件M0n0wall( 关于m0n0wa
我在 Spring Boot 和 oauth2(由 Google 提供)上编写了 rest 后端,在 "/login" 上自动重定向。除了 web 的 oauth 之外,我还想在移动后端进行 Fire
我想调用类 Foo,它的构造函数中有抽象类 Base。我希望能够从派生自 Base 的 Derived 调用 Foo 并使用 Derived覆盖方法而不是 Base 的方法。 我只能按照指示使用原始指
如何提高 session 的安全性? $this->session->userdata('userid') 我一直在为我的 ajax 调用扔掉这个小坏蛋。有些情况我没有。然后我想,使用 DOM 中的
我目前正在为某些人提供程序集编译服务。他们可以在在线编辑器中输入汇编代码并进行编译。然后编译它时,代码通过ajax请求发送到我的服务器,编译并返回程序的输出。 但是,我想知道我可以做些什么来防止对服务
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the he
目前,我通过将 session 中的 key 与 MySQl 数据库中的相同 key 相匹配来验证用户 session 。我使用随机数重新生成 session ,该随机数在每个页面加载时都受 MD5
Microsoft 模式与实践团队提供了一个很棒的 pdf,称为:“构建安全的 asp.net 应用程序”。 microsoft pdf 由于它是为 .Net 1.0 编写的,所以现在有点旧了。有谁知
在 Lua 中,通常会使用 math.random 生成随机值和/或字符串。 & math.randomseed , 其中 os.time用于 math.randomseed . 然而,这种方法有一个
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我们有一个严重依赖 Ajax 的应用程序。确保对服务器端脚本的请求不是通过独立程序而是通过坐在浏览器上的实际用户的好方法是什么 最佳答案 真的没有。 通过浏览器发送的任何请求都可以由独立程序伪造。 归
我正在寻找使用 WebSockets 与我们的服务器通信来实现 web (angular) 和 iPhone 应用程序。在过去使用 HTTP 请求时,我们使用请求数据、url、时间戳等的哈希值来验证和
我是一名优秀的程序员,十分优秀!