summaryrefslogtreecommitdiffstats
path: root/src/libutil/cxx/file_util.cxx
blob: 9baf062a5976485def22f99ec2966cc6e4767e02 (plain)
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
/*-
 * Copyright 2022 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "file_util.hxx"
#include <fmt/core.h>
#include "libutil/util.h"
#include "libutil/unix-std.h"

#define DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL

#include "doctest/doctest.h"

namespace rspamd::util {

auto raii_file::open(const char *fname, int flags) -> tl::expected<raii_file, error>
{
	int oflags = flags;
#ifdef O_CLOEXEC
	oflags |= O_CLOEXEC;
#endif

	if (fname == nullptr) {
		return tl::make_unexpected(error{"cannot open file; filename is nullptr", EINVAL, error_category::CRITICAL});
	}

	auto fd = ::open(fname, oflags);

	if (fd == -1) {
		return tl::make_unexpected(error{fmt::format("cannot open file {}: {}", fname, ::strerror(errno)), errno});
	}

	auto ret = raii_file{fname, fd, false};

	if (fstat(ret.fd, &ret.st) == -1) {
		return tl::make_unexpected(error{fmt::format("cannot stat file {}: {}", fname, ::strerror(errno)), errno});
	}

	return ret;
}

auto raii_file::create(const char *fname, int flags, int perms) -> tl::expected<raii_file, error>
{
	int oflags = flags | O_CREAT;
#ifdef O_CLOEXEC
	oflags |= O_CLOEXEC;
#endif

	if (fname == nullptr) {
		return tl::make_unexpected(error{"cannot create file; filename is nullptr", EINVAL, error_category::CRITICAL});
	}

	auto fd = ::open(fname, oflags, perms);

	if (fd == -1) {
		return tl::make_unexpected(error{fmt::format("cannot create file {}: {}", fname, ::strerror(errno)), errno});
	}

	auto ret = raii_file{fname, fd, false};

	if (fstat(ret.fd, &ret.st) == -1) {
		return tl::make_unexpected(error{fmt::format("cannot stat file {}: {}", fname, ::strerror(errno)), errno});
	}

	return ret;
}

auto raii_file::create_temp(const char *fname, int flags, int perms) -> tl::expected<raii_file, error>
{
	int oflags = flags;
#ifdef O_CLOEXEC
	oflags |= O_CLOEXEC | O_CREAT | O_EXCL;
#endif
	if (fname == nullptr) {
		return tl::make_unexpected(error{"cannot open file; filename is nullptr", EINVAL, error_category::CRITICAL});
	}

	auto fd = ::open(fname, oflags, perms);

	if (fd == -1) {
		return tl::make_unexpected(error{fmt::format("cannot create file {}: {}", fname, ::strerror(errno)), errno});
	}

	auto ret = raii_file{fname, fd, true};

	if (fstat(ret.fd, &ret.st) == -1) {
		return tl::make_unexpected(error{fmt::format("cannot stat file {}: {}", fname, ::strerror(errno)), errno});
	}

	return ret;
}

auto raii_file::mkstemp(const char *pattern, int flags, int perms) -> tl::expected<raii_file, error>
{
	int oflags = flags;
#ifdef O_CLOEXEC
	oflags |= O_CLOEXEC | O_CREAT | O_EXCL;
#endif
	if (pattern == nullptr) {
		return tl::make_unexpected(error{"cannot open file; pattern is nullptr", EINVAL, error_category::CRITICAL});
	}

	std::string mutable_pattern = pattern;

	auto fd = g_mkstemp_full(mutable_pattern.data(), oflags, perms);

	if (fd == -1) {
		return tl::make_unexpected(error{fmt::format("cannot create file {}: {}", pattern, ::strerror(errno)), errno});
	}

	auto ret = raii_file{mutable_pattern.c_str(), fd, true};

	if (fstat(ret.fd, &ret.st) == -1) {
		return tl::make_unexpected(error{fmt::format("cannot stat file {}: {}",
													 mutable_pattern, ::strerror(errno)),
										 errno});
	}

	return ret;
}

raii_file::~raii_file() noexcept
{
	if (fd != -1) {
		if (temp) {
			(void) unlink(fname.c_str());
		}
		close(fd);
	}
}

auto raii_file::update_stat() noexcept -> bool
{
	return fstat(fd, &st) != -1;
}

raii_file::raii_file(const char *fname, int fd, bool temp)
	: fd(fd), temp(temp)
{
	std::size_t nsz;

	/* Normalize path */
	this->fname = fname;
	rspamd_normalize_path_inplace(this->fname.data(), this->fname.size(), &nsz);
	this->fname.resize(nsz);
}


raii_locked_file::~raii_locked_file() noexcept
{
	if (fd != -1) {
		(void) rspamd_file_unlock(fd, FALSE);
	}
}

auto raii_locked_file::lock_raii_file(raii_file &&unlocked) -> tl::expected<raii_locked_file, error>
{
	if (!rspamd_file_lock(unlocked.get_fd(), TRUE)) {
		return tl::make_unexpected(
			error{fmt::format("cannot lock file {}: {}", unlocked.get_name(), ::strerror(errno)), errno});
	}

	return raii_locked_file{std::move(unlocked)};
}

auto raii_locked_file::unlock() -> raii_file
{
	if (fd != -1) {
		(void) rspamd_file_unlock(fd, FALSE);
	}

	return raii_file{static_cast<raii_file &&>(std::move(*this))};
}

raii_mmaped_file::raii_mmaped_file(raii_file &&file, void *map, std::size_t sz)
	: file(std::move(file)), map(map), map_size(sz)
{
}

auto raii_mmaped_file::mmap_shared(raii_file &&file,
								   int flags, std::int64_t offset) -> tl::expected<raii_mmaped_file, error>
{
	void *map;

	if (file.get_stat().st_size < offset || offset < 0) {
		return tl::make_unexpected(error{
			fmt::format("cannot mmap file {} due to incorrect offset; offset={}, size={}",
						file.get_name(), offset, file.get_size()),
			EINVAL});
	}
	/* Update stat on file to ensure it is up-to-date */
	file.update_stat();
	map = mmap(nullptr, (std::size_t)(file.get_size() - offset), flags, MAP_SHARED, file.get_fd(), offset);

	if (map == MAP_FAILED) {
		return tl::make_unexpected(error{fmt::format("cannot mmap file {}: {}",
													 file.get_name(), ::strerror(errno)),
										 errno});
	}

	return raii_mmaped_file{std::move(file), map, (std::size_t)(file.get_size() - offset)};
}

auto raii_mmaped_file::mmap_shared(const char *fname, int open_flags,
								   int mmap_flags, std::int64_t offset) -> tl::expected<raii_mmaped_file, error>
{
	auto file = raii_file::open(fname, open_flags);

	if (!file.has_value()) {
		return tl::make_unexpected(file.error());
	}

	return raii_mmaped_file::mmap_shared(std::move(file.value()), mmap_flags, offset);
}

raii_mmaped_file::~raii_mmaped_file()
{
	if (map != nullptr) {
		munmap(map, map_size);
	}
}

raii_mmaped_file::raii_mmaped_file(raii_mmaped_file &&other) noexcept
	: file(std::move(other.file))
{
	std::swap(map, other.map);
	std::swap(map_size, other.map_size);
}

auto raii_file_sink::create(const char *fname, int flags, int perms,
							const char *suffix) -> tl::expected<raii_file_sink, error>
{
	if (!fname || !suffix) {
		return tl::make_unexpected(error{"cannot create file; filename is nullptr", EINVAL, error_category::CRITICAL});
	}

	auto tmp_fname = fmt::format("{}.{}", fname, suffix);
	auto file = raii_locked_file::create(tmp_fname.c_str(), flags, perms);

	if (!file.has_value()) {
		return tl::make_unexpected(file.error());
	}

	return raii_file_sink{std::move(file.value()), fname, std::move(tmp_fname)};
}

auto raii_file_sink::write_output() -> bool
{
	if (success) {
		/* We cannot write output twice */
		return false;
	}

	if (rename(tmp_fname.c_str(), output_fname.c_str()) == -1) {
		return false;
	}

	success = true;

	return true;
}

raii_file_sink::~raii_file_sink()
{
	if (!success) {
		/* Unlink sink */
		unlink(tmp_fname.c_str());
	}
}

raii_file_sink::raii_file_sink(raii_locked_file &&_file, const char *_output, std::string &&_tmp_fname)
	: file(std::move(_file)), output_fname(_output), tmp_fname(std::move(_tmp_fname)), success(false)
{
}

raii_file_sink::raii_file_sink(raii_file_sink &&other) noexcept
	: file(std::move(other.file)),
	  output_fname(std::move(other.output_fname)),
	  tmp_fname(std::move(other.tmp_fname)),
	  success(other.success)
{
}

namespace tests {
template<class T>
static auto test_read_file(const T &f)
{
	auto fd = f.get_fd();
	(void) ::lseek(fd, 0, SEEK_SET);
	std::string buf('\0', (std::size_t) f.get_size());
	::read(fd, buf.data(), buf.size());
	return buf;
}
template<class T>
static auto test_write_file(const T &f, const std::string_view &buf)
{
	auto fd = f.get_fd();
	(void) ::lseek(fd, 0, SEEK_SET);
	return ::write(fd, buf.data(), buf.size());
}
auto random_fname(std::string_view extension)
{
	const auto *tmpdir = getenv("TMPDIR");
	if (tmpdir == nullptr) {
		tmpdir = G_DIR_SEPARATOR_S "tmp";
	}

	std::string out_fname{tmpdir};
	out_fname += G_DIR_SEPARATOR_S;

	unsigned char hexbuf[32];
	rspamd_random_hex(hexbuf, sizeof(hexbuf));
	out_fname.append((const char *) hexbuf, sizeof(hexbuf));
	if (!extension.empty()) {
		out_fname.append(".");
		out_fname.append(extension);
	}

	return out_fname;
}
TEST_SUITE("loked files utils")
{

	TEST_CASE("create and delete file")
	{
		auto fname = random_fname("tmp");
		{
			auto raii_locked_file = raii_locked_file::create_temp(fname.c_str(), O_RDONLY, 00600);
			CHECK(raii_locked_file.has_value());
			CHECK(raii_locked_file.value().get_extension() == "tmp");
			CHECK(::access(fname.c_str(), R_OK) == 0);
		}
		// File must be deleted after this call
		auto ret = ::access(fname.c_str(), R_OK);
		auto serrno = errno;
		CHECK(ret == -1);
		CHECK(serrno == ENOENT);
		// Create one more time
		{
			auto raii_locked_file = raii_locked_file::create_temp(fname.c_str(), O_RDONLY, 00600);
			CHECK(raii_locked_file.has_value());
			CHECK(::access(fname.c_str(), R_OK) == 0);
		}
		ret = ::access(fname.c_str(), R_OK);
		serrno = errno;
		CHECK(ret == -1);
		CHECK(serrno == ENOENT);
	}

	TEST_CASE("check lock")
	{
		auto fname = random_fname("");
		{
			auto raii_locked_file = raii_locked_file::create_temp(fname.c_str(), O_RDONLY, 00600);
			CHECK(raii_locked_file.has_value());
			CHECK(raii_locked_file.value().get_extension() == "");
			CHECK(::access(fname.c_str(), R_OK) == 0);
			auto raii_locked_file2 = raii_locked_file::open(fname.c_str(), O_RDONLY);
			CHECK(!raii_locked_file2.has_value());
			CHECK(::access(fname.c_str(), R_OK) == 0);
		}
		// File must be deleted after this call
		auto ret = ::access(fname.c_str(), R_OK);
		auto serrno = errno;
		CHECK(ret == -1);
		CHECK(serrno == ENOENT);
	}

	auto get_tmpdir()->std::string
	{
		const auto *tmpdir = getenv("TMPDIR");
		if (tmpdir == nullptr) {
			tmpdir = G_DIR_SEPARATOR_S "tmp";
		}

		std::size_t sz;
		std::string mut_fname = tmpdir;
		rspamd_normalize_path_inplace(mut_fname.data(), mut_fname.size(), &sz);
		mut_fname.resize(sz);

		if (!mut_fname.ends_with(G_DIR_SEPARATOR)) {
			mut_fname += G_DIR_SEPARATOR;
		}

		return mut_fname;
	}

	TEST_CASE("tempfile")
	{
		std::string tmpname;
		const std::string tmpdir{get_tmpdir()};
		{
			auto raii_locked_file = raii_locked_file::mkstemp(std::string(tmpdir + G_DIR_SEPARATOR_S + "doctest-XXXXXXXX").c_str(),
															  O_RDONLY, 00600);
			CHECK(raii_locked_file.has_value());
			CHECK(raii_locked_file.value().get_dir() == tmpdir);
			CHECK(access(raii_locked_file.value().get_name().data(), R_OK) == 0);
			auto raii_locked_file2 = raii_locked_file::open(raii_locked_file.value().get_name().data(), O_RDONLY);
			CHECK(!raii_locked_file2.has_value());
			CHECK(access(raii_locked_file.value().get_name().data(), R_OK) == 0);
			tmpname = raii_locked_file.value().get_name();
		}
		// File must be deleted after this call
		auto ret = ::access(tmpname.c_str(), R_OK);
		auto serrno = errno;
		CHECK(ret == -1);
		CHECK(serrno == ENOENT);
	}

	TEST_CASE("mmap")
	{
		std::string tmpname;
		const std::string tmpdir{get_tmpdir()};
		{
			auto raii_file = raii_file::mkstemp(std::string(tmpdir + G_DIR_SEPARATOR_S + "doctest-XXXXXXXX").c_str(),
												O_RDWR | O_CREAT | O_EXCL, 00600);
			CHECK(raii_file.has_value());
			CHECK(raii_file->get_dir() == tmpdir);
			CHECK(access(raii_file->get_name().data(), R_OK) == 0);
			tmpname = std::string{raii_file->get_name()};
			char payload[] = {'1', '2', '3'};
			CHECK(write(raii_file->get_fd(), payload, sizeof(payload)) == sizeof(payload));
			auto mmapped_file1 = raii_mmaped_file::mmap_shared(std::move(raii_file.value()), PROT_READ | PROT_WRITE);
			CHECK(mmapped_file1.has_value());
			CHECK(!raii_file->is_valid());
			CHECK(mmapped_file1->get_size() == sizeof(payload));
			CHECK(memcmp(mmapped_file1->get_map(), payload, sizeof(payload)) == 0);
			*(char *) mmapped_file1->get_map() = '2';
			auto mmapped_file2 = raii_mmaped_file::mmap_shared(tmpname.c_str(), O_RDONLY, PROT_READ);
			CHECK(mmapped_file2.has_value());
			CHECK(mmapped_file2->get_size() == sizeof(payload));
			CHECK(memcmp(mmapped_file2->get_map(), payload, sizeof(payload)) != 0);
			CHECK(memcmp(mmapped_file2->get_map(), mmapped_file1->get_map(), sizeof(payload)) == 0);
		}
		// File must be deleted after this call
		auto ret = ::access(tmpname.c_str(), R_OK);
		auto serrno = errno;
		CHECK(ret == -1);
		CHECK(serrno == ENOENT);
	}

}// TEST_SUITE

}// namespace tests

}// namespace rspamd::util
systemtags-public Nextcloud server, a safe home for all your data: https://github.com/nextcloud/serverwww-data
summaryrefslogtreecommitdiffstats
path: root/apps/user_ldap/l10n/zh_TW.json
blob: abbd1d6507174983a52e116596e7cb046e22e7cb (plain)
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
{ "translations": {
    "Failed to clear the mappings." : "清除映射失敗",
    "Failed to delete the server configuration" : "刪除伺服器設定時失敗",
    "The configuration is invalid: anonymous bind is not allowed." : "設定檔無效: 不允許匿名使者",
    "The configuration is valid and the connection could be established!" : "設定有效且連線可建立",
    "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "設定有效但連線無法建立,請檢查伺服器設定與認證資料。",
    "The configuration is invalid. Please have a look at the logs for further details." : "設定無效,更多細節請參閱 ownCloud 的記錄檔。",
    "No action specified" : "沒有指定操作",
    "No configuration specified" : "沒有指定設定",
    "No data specified" : "沒有指定資料",
    " Could not set configuration %s" : "無法設定 %s 為設定值",
    "Action does not exist" : "操作不存在",
    "The Base DN appears to be wrong" : "Base DN 出現問題",
    "Configuration incorrect" : "設定不正確",
    "Configuration incomplete" : "設定未完成",
    "Configuration OK" : "設定完成",
    "Select groups" : "選擇群組",
    "Select object classes" : "選擇物件",
    "Please check the credentials, they seem to be wrong." : "請檢查您的憑證,似乎出了點問題",
    "Please specify the port, it could not be auto-detected." : "請您指定您的連接阜,無法自動偵測",
    "Base DN could not be auto-detected, please revise credentials, host and port." : "Base DN 無法被自動偵測,請重新設定主機,連接阜以及認證",
    "Could not detect Base DN, please enter it manually." : "無法偵測到Base DN,請手動輸入",
    "{nthServer}. Server" : "{nthServer}. 伺服器",
    "No object found in the given Base DN. Please revise." : "在Base DN中找不到物件,請重新修改",
    "More than 1,000 directory entries available." : "目前有超過 1,000 個資料夾項目是可以使用的",
    "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "產生錯誤,請檢查Base DN,以及連線設定和驗證",
    "Do you really want to delete the current Server Configuration?" : "您真的要刪除現在的伺服器設定嗎?",
    "Confirm Deletion" : "確認刪除",
    "Mappings cleared successfully!" : "Mappings清除成功",
    "Error while clearing the mappings." : "清除Mappings發生錯誤",
    "Anonymous bind is not allowed. Please provide a User DN and Password." : "匿名是不允許的,請提供使用者DN和密碼",
    "LDAP Operations error. Anonymous bind might not be allowed." : "LDAP 錯誤,不允許匿名榜定",
    "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "存檔失敗,請確認資料庫運作中,請重新讀取一次然後再試一次",
    "Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?" : "切換模式會使LDAP自動抓取資訊,抓取資訊的時間依您的LDAP大小而定,可能會花一點時間,您確定要切換模式?",
    "Mode switch" : "模式切換",
    "Select attributes" : "選擇屬性",
    "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "找不到使用者,請檢查您的登入資料以及使用者名稱。驗證(複製貼上到命令提示位元做認證):<br/>",
    "User found and settings verified." : "使用者存在,設定值正確",
    "An unspecified error occurred. Please check the settings and the log." : "發生預期之外的錯誤,請檢查設定和記錄檔",
    "A connection error to LDAP / AD occurred, please check host, port and credentials." : "連線到 LDAP/AD出現錯誤,請檢查主機,連接阜和驗證資訊",
    "Please provide a login name to test against" : "請提供登入姓名以便再次測試",
    "The group box was disabled, because the LDAP / AD server does not support memberOf." : "群組盒已經停用,LDAP/AD 伺服器並不支援",
    "_%s group found_::_%s groups found_" : ["找到 %s 群組"],
    "_%s user found_::_%s users found_" : ["找到 %s 使用者"],
    "Could not detect user display name attribute. Please specify it yourself in advanced ldap settings." : "無法偵測使用者的顯示名稱,請您自行在ldap設定中指定",
    "Could not find the desired feature" : "無法找到所需的功能",
    "Invalid Host" : "無效的Host",
    "Server" : "伺服器",
    "Users" : "使用者",
    "Login Attributes" : "登入的設定",
    "Groups" : "群組",
    "Test Configuration" : "測試此設定",
    "Help" : "說明",
    "Only from these groups:" : "只從這些群組:",
    "Search groups" : "搜尋群組",
    "Available groups" : "可用的群組",
    "Selected groups" : "已選擇的群組",
    "Edit LDAP Query" : "編輯LDAP Query",
    "LDAP Filter:" : "LDAP 過慮器:",
    "Verify settings and count groups" : "驗證設定並計算群組數",
    "When logging in, %s will find the user based on the following attributes:" : "當登入%s時,將會根據以下屬性找到使用者 :",
    "LDAP / AD Username:" : "LDAP / AD 使用者名稱:",
    "LDAP / AD Email Address:" : "LDAP / AD 電子郵件:",
    "Other Attributes:" : "其他屬性:",
    "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: \"uid=%%uid\"" : "試圖登入時會定義要套用的篩選器。登入過程中%%uid會取代使用者名稱。例如:\"uid=%%uid\"",
    "Test Loginname" : "測試登入姓名",
    "Verify settings" : "驗證設定",
    "1. Server" : "1. 伺服器",
    "%s. Server:" : "%s. 伺服器:",
    "Add a new and blank configuration" : "新增一個空白的設定檔",
    "Copy current configuration into new directory binding" : "複製目前的設定檔到新目錄",
    "Delete the current configuration" : "刪除目前的設定檔",
    "Host" : "主機",
    "You can omit the protocol, except you require SSL. Then start with ldaps://" : "若您不需要 SSL 加密連線則不需輸入通訊協定,反之請輸入 ldaps://",
    "Port" : "連接埠",
    "Detect Port" : "偵測連接阜",
    "User DN" : "User DN",
    "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "客戶端使用者的DN與特定字詞的連結需要完善,例如:uid=agent,dc=example,dc=com。若是匿名連接,則將DN與密碼欄位留白。",
    "Password" : "密碼",
    "For anonymous access, leave DN and Password empty." : "匿名連接時請將 DN 與密碼欄位留白",
    "One Base DN per line" : "一行一個 Base DN",
    "You can specify Base DN for users and groups in the Advanced tab" : "您可以在進階標籤頁裡面指定使用者及群組的 Base DN",
    "Detect Base DN" : "偵測 Base DN",
    "Test Base DN" : "測試 Base DN",
    "Manually enter LDAP filters (recommended for large directories)" : "手動輸入 LDAP篩選器 (建議在大型的資料環境)",
    "Limit %s access to users meeting these criteria:" : "當遇到以下標準時,限制%s存取使用者:",
    "The filter specifies which LDAP users shall have access to the %s instance." : "篩選起指定哪些LDAP的使用者應該擁有存取%s的權限",
    "Verify settings and count users" : "驗證設定並計算使用者數",
    "Saving" : "儲存",
    "Back" : "返回",
    "Continue" : "繼續",
    "LDAP" : "LDAP",
    "Advanced" : "進階",
    "<b>Warning:</b> Apps user_ldap and user_webdavauth are incompatible. You may experience unexpected behavior. Please ask your system administrator to disable one of them." : "<b>警告:</b> 應用程式user_ldap和user_webdavauth互不相容。可能會造成無法預期的結果。請要求您的系統管理員將兩者其中之一停用。",
    "<b>Warning:</b> The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it." : "<b>警告:</b>沒有安裝 PHP LDAP 模組,後端系統將無法運作,請要求您的系統管理員安裝模組。",
    "Connection Settings" : "連線設定",
    "Configuration Active" : "設定使用中",
    "When unchecked, this configuration will be skipped." : "沒有被勾選時,此設定會被略過。",
    "Backup (Replica) Host" : "備用主機",
    "Give an optional backup host. It must be a replica of the main LDAP/AD server." : "可以選擇性設定備用主機,必須是 LDAP/AD 中央伺服器的複本。",
    "Backup (Replica) Port" : "備用(複本)連接埠",
    "Disable Main Server" : "停用主伺服器",
    "Turn off SSL certificate validation." : "關閉 SSL 憑證檢查",
    "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." : "不建議,請僅在測試時使用!如果連線只能在此設定下工作,請匯入LDAP伺服器的SSL認證到您的伺服器%s上",
    "Cache Time-To-Live" : "快取的存活時間",
    "in seconds. A change empties the cache." : "以秒為單位。變更後會清空快取。",
    "Directory Settings" : "目錄設定",
    "User Display Name Field" : "使用者顯示名稱欄位",
    "The LDAP attribute to use to generate the user's display name." : "LDAP設定值,用於產生使用者的顯示名稱",
    "Base User Tree" : "Base User Tree",
    "One User Base DN per line" : "一行一個使用者 Base DN",
    "User Search Attributes" : "User Search Attributes",
    "Optional; one attribute per line" : "非必要,一行一項屬性",
    "Group Display Name Field" : "群組顯示名稱欄位",
    "The LDAP attribute to use to generate the groups's display name." : "LDAP設定值,用於產生使用者群組的顯示名稱",
    "Base Group Tree" : "Base Group Tree",
    "One Group Base DN per line" : "一行一個 Group Base DN",
    "Group Search Attributes" : "Group Search Attributes",
    "Group-Member association" : "Group-Member association",
    "Special Attributes" : "特殊屬性",
    "Quota Field" : "配額欄位",
    "Quota Default" : "預設配額",
    "in bytes" : "以位元組為單位",
    "Email Field" : "電郵欄位",
    "User Home Folder Naming Rule" : "使用者家目錄的命名規則",
    "Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute." : "使用者名稱請留白(預設)。若不留白請指定一個LDAP/AD屬性。",
    "Internal Username" : "內部使用者名稱",
    "Internal Username Attribute:" : "內部使用者名稱屬性:",
    "Override UUID detection" : "偵測覆寫UUID",
    "UUID Attribute for Users:" : "使用者的UUID值:",
    "UUID Attribute for Groups:" : "群組的UUID值:"
},"pluralForm" :"nplurals=1; plural=0;"
}