android中backup介绍

  1. adb 备份
    adb backup -f ~/Desktop/log/a.ab -apk com.package.activity

  2. 下载 abe 包

  3. ab 文件解包
    java -jar ~/Downloads/abe/abe.jar unpack ~/Desktop/log/a.ab ~/Desktop/log/a.rar

  4. rar 软件解压 a.rar,即可

android各系统中,formatter适配

Android 中,格式化文件大小(Formatter.formatFileSize),Android8 之前用 1024 为单位,之后采用 1000 为单位。

Formatter 的源码解析

查看 Android 28(Android P)源码

1
2
3
4
5
6
7
8
9
10
11
12
13
public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) {
final int unit = ((flags & FLAG_IEC_UNITS) != 0) ? 1024 : 1000;
final boolean isNegative = (sizeBytes < 0);
float result = isNegative ? -sizeBytes : sizeBytes;
int suffix = com.android.internal.R.string.byteShort;
long mult = 1;
if (result > 900) {
suffix = com.android.internal.R.string.kilobyteShort;
mult = unit;
result = result / unit;
}
......
}

formatFileSize 方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 /* <p>As of O, the prefixes are used in their standard meanings in the SI system, so kB = 1000
* bytes, MB = 1,000,000 bytes, etc.</p>
*
* <p class="note">In {@link android.os.Build.VERSION_CODES#N} and earlier, powers of 1024 are
* used instead, with KB = 1024 bytes, MB = 1,048,576 bytes, etc.</p>
*/
public static String formatFileSize(@Nullable Context context, long sizeBytes) {
if (context == null) {
return "";
}
final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SI_UNITS);
return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
res.value, res.units));
}

意思就是在 Android 7 之后单位就变了,使用标准的单位制含义,即国际单位制,就像 1km = 1000 byte 一样;
在 Android 7 及更早的版本是 1024,即 1k = 1024B,这里就不贴代码了,感兴趣的同学可以去看源码。

解决方案

有两个方案,一个是反射设置 FLAG_SI_UNITS 的值,使得与 FLAG_IEC_UNITS 相与不为 0 ,
另外一个方案重新封装一个类,如下,用法与 Android 提供的一样

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
public class Formatter {
/** * get file format size * * @param context context * @param roundedBytes file size * @return file format size (like 2.12k) */
public static String formatFileSize(Context context, long roundedBytes) {
return formatFileSize(context, roundedBytes, false, Locale.US);
}

public static String formatFileSize(Context context, long roundedBytes, Locale locale) {
return formatFileSize(context, roundedBytes, false, locale);
}


private static String formatFileSize(Context context, long roundedBytes, boolean shorter, Locale locale) {
if (context == null) {
return "";
}
float result = roundedBytes;
String suffix = "B";
if (result > 900) {
suffix = "KB";
result = result / 1024;
}
if (result > 900) {
suffix = "MB";
result = result / 1024;
}
if (result > 900) {
suffix = "GB";
result = result / 1024;
}
if (result > 900) {
suffix = "TB";
result = result / 1024;
}
if (result > 900) {
suffix = "PB";
result = result / 1024;
}
String value;
if (result < 1) {
value = String.format(locale, "%.2f", result);
} else if (result < 10) {
if (shorter) {
value = String.format(locale, "%.1f", result);
} else {
value = String.format(locale, "%.2f", result);
}
} else if (result < 100) {
if (shorter) {
value = String.format(locale, "%.0f", result);
} else {
value = String.format(locale, "%.2f", result);
}
} else {
value = String.format(locale, "%.0f", result);
}
return String.format("%s%s", value, suffix);
}

}

android解析apk中manifest文件

通过 android 私有方法,快速解析 manifest,步骤如下:

  1. 首先通过 AssetManager 的 addAssetPath 方法获取 cookie,注意这个方法是隐藏的,所以需要通过反射来调用
  2. 然后通过 AssetManager 的 openXmlResourceParser 方法,传入 cookie, 返回解析器
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
public class TestLoadApk {
private static String TAG = "TestLoadApk";

public String getAppPackageId(Context context, String apkPath) {
String packageId = "";
try {
XmlResourceParser parser = getBinaryXmlParser(context, apkPath, "AndroidManifest.xml");
if (parser != null) {
int eventType;
while ((eventType = parser.nextToken()) != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
Log.i(TAG, "eventType:" + eventType + " name:" + parser.getName());
if (parser.getName().equals("manifest")) {
for (int i = 0; i < parser.getAttributeCount(); i++)
if (parser.getAttributeName(i).equals("package"))
packageId = parser.getAttributeValue(i);
break;
}
}
}

parser.close();
}
} catch (Exception e) {
e.printStackTrace();
}

Log.i(TAG, "packageId:" + packageId);

return packageId;
}

private static XmlResourceParser getBinaryXmlParser(Context context, String binaryFilePath, String binaryXmlFileName)
throws ReflectiveOperationException, IOException {
if (TextUtils.isEmpty(binaryFilePath) || TextUtils.isEmpty(binaryXmlFileName)) {
return null;
}
AssetManager assetManager = context.getAssets();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.setAccessible(true);
int cookie = (int) addAssetPath.invoke(assetManager, binaryFilePath);
return assetManager.openXmlResourceParser(cookie, binaryXmlFileName);
}
}

sshj库sftp测试

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
public void testSftpListDir() {
new UploadAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

public static class UploadAsyncTask extends AsyncTask<Void, Void, Void> {
private String TAG = "UploadAsyncTask";
private long totalLen = 0;
private long sendByteLen = 0;
private long lastSendByteLen = 0;
private String user = "himalayas";
private String pwd = "1q2w3e4r5t";
private String fileName = "a.txt";
private boolean isExited = false;
private String path = "ssh://himalayas:1q2w3e4r5t@10.168.66.1:22/bigdata/pullstashroot/ST202302230001007/test0";

private CountDownTimer timer;

public UploadAsyncTask() {
timer = new CountDownTimer(1000 * 10000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
Log.i(TAG, "ssh send speed:" + (sendByteLen - lastSendByteLen) + "(byte/s)");

lastSendByteLen = sendByteLen;
}

@Override
public void onFinish() {
Log.i(TAG, "ssh send speed:" + (sendByteLen - lastSendByteLen) + "(byte/s)");
lastSendByteLen = sendByteLen;
}
};
timer.start();
}

private void download() {
CustomSshJConfig config = new CustomSshJConfig();
SSHClient client = new SSHClient((Config) config);
try {
client.addHostKeyVerifier(new HostKeyVerifier() {
@Override
public boolean verify(String hostname, int port, PublicKey key) {
return true;
}

@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return null;
}
});
client.connect("10.168.66.1", 22);
client.authPassword(user, pwd);

SFTPClient sftp = client.newSFTPClient();
String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "com.xinan.ubox.debug";
// String srcPath = rootPath + "/de.apk";
// String srcPath = rootPath + "/de.m4a";
// String srcPath = rootPath + "/de.apk";
String srcPath = rootPath + File.separator + fileName;
String destPath = "/bigdata/test" + File.separator + fileName;
final RemoteFile file = sftp.getSFTPEngine().open(destPath, EnumSet.of(OpenMode.CREAT, OpenMode.WRITE));
OutputStream os = file.new RemoteFileOutputStream(0, 10) {
@Override
public void close() throws IOException {
try {
super.close();
} finally {
file.close();
}
}
};

totalLen = new File(srcPath).length();
ReadableByteChannel inChannel = new RandomAccessFile(new File(srcPath), "r").getChannel();
WritableByteChannel outChannel = Channels.newChannel(os);
doCopy(inChannel, outChannel);

inChannel.close();
outChannel.close();

// sftp.put(filePath, "/bigdata/test");
} catch (UserAuthException e) {
e.printStackTrace();
} catch (TransportException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

private void listFolder() {
CustomSshJConfig config = new CustomSshJConfig();
SSHClient client = new SSHClient((Config) config);
try {
client.addHostKeyVerifier(new HostKeyVerifier() {
@Override
public boolean verify(String hostname, int port, PublicKey key) {
return true;
}

@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return null;
}
});
client.connect("10.168.66.1", 22);
client.authPassword(user, pwd);

SFTPClient sftp = client.newSFTPClient();
String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "com.xinan.ubox.debug";
HybridFileParcelable fileParcelable = new HybridFileParcelable();
fileParcelable.path = path;
fileParcelable.isDirectory = true;
List<HybridFileParcelable> sources = new ArrayList<>();
sources.add(fileParcelable);
_doListDir(sftp, sources, new OnFileFound() {
@Override
public void onFileFound(HybridFileParcelable file) {
Log.i(TAG, "onFileFound, name:" + file.name + " path:" + file.path + " dir:" + file.isDirectory + " size:" + file.size);
}

@Override
public void cancel() {

}
});
} catch (UserAuthException e) {
e.printStackTrace();
} catch (TransportException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

private void _doListDir(SFTPClient sftp, List<HybridFileParcelable> sources, OnFileFound fileFound) {
ArrayList<HybridFileParcelable> list = new ArrayList<>();
for (HybridFileParcelable file: sources) {
if (!file.isDirectory) {
continue;
}

String path = file.path;
try {
for (RemoteResourceInfo info :
sftp.ls(extractRemotePathFrom(path))) {
boolean isDirectory = false;
try {
isDirectory = isDirectory(sftp, info);
} catch (IOException ifBrokenSymlink) {
Log.e(TAG, "IOException checking isDirectory(): " + info.getPath());
continue;
}
// Log.i(TAG, "path:" + path + " isDir:" + isDirectory + " info:" + info.getPath());

HybridFileParcelable fileParcelable = new HybridFileParcelable();
fileParcelable.path = String.format("%s/%s", path, info.getName());
fileParcelable.name = info.getName();
fileParcelable.isDirectory = isDirectory;
fileParcelable.size = isDirectory ? 0 : info.getAttributes().getSize();
if (isDirectory) {
list.add(fileParcelable);
} else {
fileFound.onFileFound(fileParcelable);
}
}
} catch (IOException e) {
Log.e(TAG, "IOException", e);
}
}

if (list.size() > 0) {
_doListDir(sftp, list, fileFound);
}
}

interface OnFileFound {
void onFileFound(HybridFileParcelable file);
void cancel();
}

public class HybridFileParcelable {
private boolean isDirectory;
public String path;
public String name;
public long date, size;
}

public boolean isDirectory(@NonNull SFTPClient client, @NonNull RemoteResourceInfo info)
throws IOException {
boolean isDirectory = info.isDirectory();
if (info.getAttributes().getType().equals(FileMode.Type.SYMLINK)) {
try {
FileAttributes symlinkAttrs = client.stat(info.getPath());
isDirectory = symlinkAttrs.getType().equals(FileMode.Type.DIRECTORY);
} catch (IOException ifSymlinkIsBroken) {
Log.e(TAG, String.format("Symbolic link %s is broken, skipping", info.getPath()));
throw ifSymlinkIsBroken;
}
}
return isDirectory;
}

public String extractRemotePathFrom(@NonNull String fullUri) {
String hostPath = fullUri.substring(fullUri.indexOf('@'));
return hostPath.indexOf('/') == -1 ? "/" : hostPath.substring(hostPath.indexOf('/'));
}

@Override
protected Void doInBackground(Void... voids) {
listFolder();

return null;
}

@Override
protected void onPostExecute(Void unused) {
super.onPostExecute(unused);

timer.cancel();
Log.i(TAG, "upload file finish:" + sendByteLen + " totalLen:" + totalLen);
}

void doCopy(
@NonNull ReadableByteChannel from,
@NonNull WritableByteChannel to)
throws IOException {
final int DEFAULT_TRANSFER_QUANTUM = 65536;
ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_TRANSFER_QUANTUM);
long count = 0;

while ((from.read(buffer) != -1 || buffer.position() > 0) && !isExited) {
buffer.flip();
count = to.write(buffer);

sendByteLen += count;

buffer.compact();
}

buffer.flip();
while (buffer.hasRemaining() && !isExited) {
count = to.write(buffer);
// Log.i("Copy", "buffer count:" + count);
sendByteLen += count;
}

from.close();
to.close();
}
}

public static class CustomSshJConfig extends DefaultConfig {
static {
Security.removeProvider("BC");
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}

@Override
protected void initCipherFactories() {
List<Factory.Named<Cipher>> avail = new LinkedList<Factory.Named<Cipher>>(Arrays.<Factory.Named<Cipher>>asList(
StreamCiphers.Arcfour(),
BlockCiphers.BlowfishCBC(),
ChachaPolyCiphers.CHACHA_POLY_OPENSSH(),
BlockCiphers.AES128CBC(),
BlockCiphers.AES128CTR(),
BlockCiphers.AES192CBC(),
BlockCiphers.AES192CTR(),
BlockCiphers.AES256CBC(),
BlockCiphers.AES256CTR(),
GcmCiphers.AES128GCM(),
GcmCiphers.AES256GCM(),
BlockCiphers.BlowfishCTR(),
BlockCiphers.Cast128CBC(),
BlockCiphers.Cast128CTR(),
BlockCiphers.IDEACBC(),
BlockCiphers.IDEACTR(),
BlockCiphers.Serpent128CBC(),
BlockCiphers.Serpent128CTR(),
BlockCiphers.Serpent192CBC(),
BlockCiphers.Serpent192CTR(),
BlockCiphers.Serpent256CBC(),
BlockCiphers.Serpent256CTR(),
BlockCiphers.TripleDESCBC(),
BlockCiphers.TripleDESCTR(),
BlockCiphers.Twofish128CBC(),
BlockCiphers.Twofish128CTR(),
BlockCiphers.Twofish192CBC(),
BlockCiphers.Twofish192CTR(),
BlockCiphers.Twofish256CBC(),
BlockCiphers.Twofish256CTR(),
BlockCiphers.TwofishCBC(),
StreamCiphers.Arcfour128(),
StreamCiphers.Arcfour256())
);

boolean warn = false;
// Ref. https://issues.apache.org/jira/browse/SSHD-24
// "AES256 and AES192 requires unlimited cryptography extension"
for (Iterator<Factory.Named<Cipher>> i = avail.iterator(); i.hasNext(); ) {
final Factory.Named<Cipher> f = i.next();
try {
final Cipher c = f.create();
final byte[] key = new byte[c.getBlockSize()];
final byte[] iv = new byte[c.getIVSize()];
c.init(Cipher.Mode.Encrypt, key, iv);
} catch (Exception e) {
warn = true;
Log.e("CustomSshJConfig", e.getCause().getMessage());
i.remove();
}
}
if (warn)
Log.w("CustomSshJConfig", "Disabling high-strength ciphers: cipher strengths apparently limited by JCE policy");

setCipherFactories(avail);
Log.d("CustomSshJConfig", "Available cipher factories:" + avail);
}
}

app安全合规整改

弱密码

长度

  1. 长度 8-20 位

规则

  1. 密码长度为 6-20 位
  2. 数字(0-9),字母(a-zA-Z),特殊字符(@$!%*#?&)中至少两种组合
  3. 不能包含 4 位及以上连续相同的字符(1111,aaaa,@@@@)
  4. 不能包含连续的数字或者字母(1234,abcd,ABCD)

App 进入后台提示

APP 在安卓端进入后台运行时,展示 toast 提示“已进入后台运行”

越狱提示

APP 安卓系统的 root 环境提示框,支持用户点击「我知道了」关闭弹窗,关闭弹窗后,APP 可正常使用。
文案:您的手机当前处于 root 环境下,您的个人隐私和****的使用可能存在安全隐患。

个人中心增加权限管理

密码页面放截屏

首次使用 App,需要弹起隐私协议弹窗,同意后才能采集 隐私信息;同意后延时一下初始化隐私 sdk

登录、个人中心和注册页面需要有隐私协议入口

App 禁止备份

Android manifest 文件 Application 节点增加 android:allowBackup=”false”

iOS 也禁止备份

App 中本地证书不要明文展示

秘钥二进制固化到 App 中

文件名或扩展名隐私化处理

极光消息禁默启动

检查 manifest 有没有包安装等事件监听

sdk 说明

App 中使用 sdk 需要在隐私协议中,尽可能体现

App 中隐私协议和应用市场版本一致

App 中涉及到用户偏好的,需要到隐私协议中体现并个人中心有关闭入口

安全键盘

权限使用时才提示

登录页面隐私协议选择框,默认不勾选

App 退出清理剪切板

App 防抓包

同意隐私政策前,不用写读外部存储目录(外部存储 app 内部可以)

1
2
3
4
5
6
7
8
9
val rootPath =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath + File.separator + packageName
改为
val logPath = rootPath + File.separator + "log"
var rootFile = getExternalFilesDir(null)
if (rootFile == null) {
rootFile = filesDir
}

合规清单

SSH简介

Let’s face it: we don’t think about SSH all that much. We SSH into a server and merrily type away our commands. Until we need to write an SSH server.

This document describes the high level concepts of SSH: how do you open a connection, what are channels, and how do requests work.

This is a very high level overview, but should contain everything you need to get started with ContainerSSH development.

Handshake¶
When the user connects an SSH server the SSH keys are verified. We won’t discuss this here as for ContainerSSH the Go SSH library takes care of that.

The first thing we are concerned with is authentication. Authentication is described by RFC 4252 and it states the following:

The server drives the authentication by telling the client which authentication methods can be used to continue the exchange at any given time. The client has the freedom to try the methods listed by the server in any order.

In other words, when the user connects the SSH server tells the client which authentication method it supports. The client picks one of them and performs the authentication. The server can then decide to reject, allow, or show the client another list of methods (e.g. to perform two factor authentication). The Go library vastly simplifies this process and only allows a single means of authentication for each connection.

Each authentication request contains a username. The username may change between authentication attempts to authenticate against different systems, but this is not customary.

Connection¶
Once the authentication is complete the connection is open and both the client and the server may now send two types of messages: global requests and channels.

Global requests describe requests in either direction that one party wants from the other. For example, the OpenSSH extensions describe the no-more-sessions@openssh.com to indicate that no more session channels should be opened on this connection.

The channels, on the other hand are means of transporting data. For example, the session channel is responsible for executing a program and then transporting the standard input, output, and error data streams to and from the program. They also give both ends the ability to send channel-specific requests (e.g. setting environment variables, resizing the window, etc.).

Session channels¶
While there are theoretically other types of channels possible, we currently only support session channels. The client can request channels to be opened at any time.

We currently support the following requests on the session channel. These are described in RFC 4254.

env
Sets an environment variable for the soon to be executed program.
pty
Requests an interactive terminal for user input.
shell
Requests the default shell to be executed.
exec
Requests a specific program to be executed.
subsystem
Requests a well-known subsystem (e.g. sftp) to be executed.
window-change
Informs the server that an interactive terminal window has changed size. This is only sent once the program has been started with the requests above.
signal
Requests the server to send a signal to the currently running process.
In addition, we also send an exit-status request to the client from the server when the program exits to inform the client of the exit code.

Interactive terminals¶
As you can see above, the user can request an interactive terminal using the pty request. This is done automatically by SSH clients if they detect that their input is an interactive terminal.

Using interactive terminals changes the operation mode of stdin, stdout, and stderr. While programs normally write their standard output to stdout and their error output to stderr, programs running in interactive mode send their combined output to stdout using a special framing. (TTY multiplexing)

Thankfully, we don’t need to know too much about TTY multiplexing for writing an SSH server since it is transparently passed through from the container engine to the SSH channel and we don’t interact with it.

RFCs¶
The SSH protocol is governed by the following RFCs:

RFC 913: Simple File Transfer Protocol
This document describes the SFTP protocol used over SSH.
RFC 4250: The Secure Shell (SSH) Protocol Assigned Numbers
This document describes the protocol numbers and standard constants used in SSH.
RFC 4251: The Secure Shell (SSH) Protocol Architecture
This document describes the design decisions taken to work with SSH.
RFC 4252: The Secure Shell (SSH) Authentication Protocol
This document describes how user authentication works in SSH.
RFC 4253: The Secure Shell (SSH) Transport Layer Protocol
This document describes the details of how data is transported over SSH.
RFC 4254: The Secure Shell (SSH) Connection Protocol
This document contains the parts most interesting to us: how channels, sessions, etc. work.
RFC 4255: Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints
This document describes how to publish SSH fingerprints using DNS. It has not seen wide adoption.
RFC 4256: Generic Message Exchange Authentication for the Secure Shell Protocol (SSH)
This document describes the keyboard-interactive authentication for SSH, which is often used for two factor authentication.
RFC 4335: The Secure Shell (SSH) Session Channel Break Extension
This document describes the telnet-compatible break request for use in SSH.
RFC 4344, RFC 4345, RFC 4419, RFC 4432
These documents describe various encryption-related topics.
RFC 4462: Generic Security Service Application Program Interface (GSS-API) Authentication and Key Exchange for the Secure Shell (SSH) Protocol
This document describes the GSS-API authentication method that can be used to authenticate with a Kerberos ticket.
RFC 4716: The Secure Shell (SSH) Public Key File Format
This document describes the PEM-like format to store SSH keys in.
RFC 4819: Secure Shell Public Key Subsystem
This document describes the SSH public key subsystem usable for adding, removing, and listing public keys.
RFC 5647, RFC 5656, RFC 6187, RFC 6239, RFC 6594, RFC 6668
These documents describe various cryptography and authentication related topics.
RFC 7479: Using Ed25519 in SSHFP Resource Records
This document describes publishing ED25519 host keys using DNS.
RFC 5592: Secure Shell Transport Model for the Simple Network Management Protocol (SNMP)
This protocol describes using SNMP over SSH.
RFC 6242: Using the NETCONF Protocol over Secure Shell (SSH)
This document describes transporting the RFC 6241 Network Configuration Protocol over SSH. This can be used to manage networking equipment.
In addition, OpenSSH defines the following extensions:

The OpenSSH Protocol
This document describes new cryptographic methods, tunnel forwarding, domain socket forwarding, and many more changes.
The CertKeys Document
This document describes the OpenSSH CA method.
SSH Agent Protocol
Describes the protocol used by the SSH agent holding the SSH keys in escrow.

kotlin返回与跳转

Kotlin 有三种结构化跳转表达式:

return 默认从最直接包围它的函数或者匿名函数返回。
break 终止最直接包围它的循环。
continue 继续下一次最直接包围它的循环。
所有这些表达式都可以用作更大表达式的一部分:

val s = person.name ?: return
这些表达式的类型是 Nothing 类型。

Break 与 Continue 标签
在 Kotlin 中任何表达式都可以用标签来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@。 要为一个表达式加标签,我们只要在其前加标签即可。

loop@ for (i in 1..100) {
// ……
}
现在,我们可以用标签限定 break 或者 continue:

loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}
标签限定的 break 跳转到刚好位于该标签指定的循环后面的执行点。 continue 继续标签指定的循环的下一次迭代。

返回到标签
Kotlin 中函数可以使用函数字面量、局部函数与对象表达式实现嵌套。 标签限定的 return 允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。回想一下我们这么写的时候, 这个 return 表达式从最直接包围它的函数——foo 中返回:

//sampleStart
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // 非局部直接返回到 foo() 的调用者
print(it)
}
println(“this point is unreachable”)
}
//sampleEnd

fun main() {
foo()
}
注意,这种非局部的返回只支持传给内联函数的 lambda 表达式。 如需从 lambda 表达式中返回,可给它加标签并用以限定 return。

//sampleStart
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit // 局部返回到该 lambda 表达式的调用者——forEach 循环
print(it)
}
print(“ done with explicit label”)
}
//sampleEnd

fun main() {
foo()
}
现在,它只会从 lambda 表达式中返回。通常情况下使用隐式标签更方便,因为该标签与接受该 lambda 的函数同名。

//sampleStart
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach // 局部返回到该 lambda 表达式的调用者——forEach 循环
print(it)
}
print(“ done with implicit label”)
}
//sampleEnd

fun main() {
foo()
}
或者,我们用一个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数自身返回

//sampleStart
fun foo() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) return // 局部返回到匿名函数的调用者——forEach 循环
print(value)
})
print(“ done with anonymous function”)
}
//sampleEnd

fun main() {
foo()
}
请注意,前文三个示例中使用的局部返回类似于在常规循环中使用 continue。

并没有 break 的直接等价形式,不过可以通过增加另一层嵌套 lambda 表达式并从其中非局部返回来模拟:

//sampleStart
fun foo() {
run loop@{
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@loop // 从传入 run 的 lambda 表达式非局部返回
print(it)
}
}
print(“ done with nested loop”)
}
//sampleEnd

fun main() {
foo()
}
当要返一个回值的时候,解析器优先选用标签限定的返回:

return@a 1
这意味着“返回 1 到 @a”,而不是“返回一个标签标注的表达式 (@a 1)”。