Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F120837072
libconfig.testc
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
19 KB
Referenced Files
None
Subscribers
None
libconfig.testc
View Options
/* Copyright (c) 1994-2016 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <unistd.h>
#include <sysexits.h>
#include "cunit/cyrunit.h"
#include "lib/libconfig.h"
#include "lib/retry.h"
#include "lib/xmalloc.h"
#define DBDIR "test-libconfig-dbdir"
static int set_up(void)
{
return 0;
}
static int tear_down(void)
{
int r;
/* all these tests will initialise some config, clean up after! */
config_reset();
r = system("rm -rf " DBDIR);
return r;
}
static void test_int(void)
{
int boundary_limit = -1;
int archive_maxsize = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"boundary_limit: 120\n"
/* archive_maxsize: 1024 (default) */
);
/* test a value that has been set */
boundary_limit = config_getint(IMAPOPT_BOUNDARY_LIMIT);
CU_ASSERT_EQUAL(boundary_limit, 120);
/* test a value that is defaulted */
archive_maxsize = config_getint(IMAPOPT_ARCHIVE_MAXSIZE);
CU_ASSERT_EQUAL(archive_maxsize, 1024);
}
static void test_string(void)
{
const char *autocreate_sieve_folders = NULL;
const char *autocreate_sieve_script = NULL;
const char *addressbookprefix = NULL;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"autocreate_sieve_folders: Junk | Trash\n"
/* autocreate_sieve_script: NULL (default) */
/* addressbookprefix: #addressbooks (default) */
);
/* test a value that has been set */
autocreate_sieve_folders = config_getstring(IMAPOPT_AUTOCREATE_SIEVE_FOLDERS);
CU_ASSERT_PTR_NOT_NULL(autocreate_sieve_folders);
CU_ASSERT_STRING_EQUAL(autocreate_sieve_folders, "Junk | Trash");
/* test a value that has been defaulted to NULL */
autocreate_sieve_script = config_getstring(IMAPOPT_AUTOCREATE_SIEVE_SCRIPT);
CU_ASSERT_PTR_NULL(autocreate_sieve_script);
/* test a value that has been defaulted */
addressbookprefix = config_getstring(IMAPOPT_ADDRESSBOOKPREFIX);
CU_ASSERT_PTR_NOT_NULL(addressbookprefix);
CU_ASSERT_STRING_EQUAL(addressbookprefix, "#addressbooks");
}
static void test_switch(void)
{
int allowanonymouslogin = -1;
int allowapop = -1;
int allownewnews = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"allowanonymouslogin: 1\n"
/* allowapop: 1 (default) */
/* allownewnews: 0 (default) */
);
/* test a value that has been set */
allowanonymouslogin = config_getswitch(IMAPOPT_ALLOWANONYMOUSLOGIN);
CU_ASSERT_EQUAL(allowanonymouslogin, 1);
/* test a value that has been defaulted to 1 */
allowapop = config_getswitch(IMAPOPT_ALLOWAPOP);
CU_ASSERT_EQUAL(allowapop, 1);
/* test a value that has been defaulted to 0 */
allownewnews = config_getswitch(IMAPOPT_ALLOWNEWNEWS);
CU_ASSERT_EQUAL(allownewnews, 0);
}
static void test_stringlist_value(void)
{
const char *annotation_db = NULL;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"annotation_db: skiplist\n"
);
annotation_db = config_getstring(IMAPOPT_ANNOTATION_DB);
CU_ASSERT_PTR_NOT_NULL(annotation_db);
CU_ASSERT_STRING_EQUAL(annotation_db, "skiplist");
}
static void test_stringlist_default(void)
{
const char *annotation_db = NULL;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
/* annotation_db: twoskip (default) */
);
annotation_db = config_getstring(IMAPOPT_ANNOTATION_DB);
CU_ASSERT_PTR_NOT_NULL(annotation_db);
CU_ASSERT_STRING_EQUAL(annotation_db, "twoskip");
}
static void test_stringlist_invalid(void)
{
CU_EXPECT_CYRFATAL_BEGIN
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"annotation_db: junk\n"
);
CU_EXPECT_CYRFATAL_END(EX_CONFIG, "invalid value 'junk' for annotation_db in line 2");
}
static void test_enum_value(void)
{
int delete_mode = -1; /* an int, so we can start with a sentinel */
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"delete_mode: immediate\n"
);
delete_mode = config_getenum(IMAPOPT_DELETE_MODE);
CU_ASSERT_EQUAL(delete_mode, IMAP_ENUM_DELETE_MODE_IMMEDIATE);
}
static void test_enum_default(void)
{
int delete_mode = -1; /* an int, so we can start with a sentinel */
config_read_string(
"configdirectory: "DBDIR"/conf\n"
/* delete_mode: delayed (default) */
);
delete_mode = config_getenum(IMAPOPT_DELETE_MODE);
CU_ASSERT_EQUAL(delete_mode, IMAP_ENUM_DELETE_MODE_DELAYED);
}
static void test_enum_invalid(void)
{
CU_EXPECT_CYRFATAL_BEGIN
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"delete_mode: junk\n"
);
CU_EXPECT_CYRFATAL_END(EX_CONFIG, "invalid value 'junk' for delete_mode in line 2");
}
static void test_bitfield_value(void)
{
unsigned long httpmodules = (unsigned long) -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"httpmodules: carddav freebusy jmap\n"
);
httpmodules = config_getbitfield(IMAPOPT_HTTPMODULES);
CU_ASSERT_EQUAL(httpmodules,
IMAP_ENUM_HTTPMODULES_CARDDAV |
IMAP_ENUM_HTTPMODULES_FREEBUSY |
IMAP_ENUM_HTTPMODULES_JMAP);
}
static void test_bitfield_default(void)
{
unsigned long httpmodules = (unsigned long) -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
/* httpmodules: "" (default) */
);
httpmodules = config_getbitfield(IMAPOPT_HTTPMODULES);
CU_ASSERT_EQUAL(httpmodules, 0);
}
static void test_bitfield_invalid(void)
{
CU_EXPECT_CYRFATAL_BEGIN
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"httpmodules: caldav junk\n"
);
CU_EXPECT_CYRFATAL_END(EX_CONFIG, "invalid value 'junk' for httpmodules in line 2");
}
static void test_duration_value_days(void)
{
int timeout = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"timeout: 3d\n"
);
timeout = config_getduration(IMAPOPT_TIMEOUT, 's');
CU_ASSERT_EQUAL(timeout, 3 * 24 * 60 * 60);
}
static void test_duration_value_hours(void)
{
int timeout = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"timeout: 3h\n"
);
timeout = config_getduration(IMAPOPT_TIMEOUT, 's');
CU_ASSERT_EQUAL(timeout, 3 * 60 * 60);
}
static void test_duration_value_minutes(void)
{
int timeout = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"timeout: 45m\n"
);
timeout = config_getduration(IMAPOPT_TIMEOUT, 's');
CU_ASSERT_EQUAL(timeout, 45 * 60);
}
static void test_duration_value_seconds(void)
{
int timeout = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"timeout: 25s\n"
);
timeout = config_getduration(IMAPOPT_TIMEOUT, 's');
CU_ASSERT_EQUAL(timeout, 25);
}
static void test_duration_value_combined(void)
{
int timeout = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"timeout: 1h30m\n"
);
timeout = config_getduration(IMAPOPT_TIMEOUT, 's');
CU_ASSERT_EQUAL(timeout, 1.5 * 60 * 60);
}
static void test_duration_value_nounits(void)
{
int timeout = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"timeout: 13\n"
);
timeout = config_getduration(IMAPOPT_TIMEOUT, 's');
CU_ASSERT_EQUAL(timeout, 13);
timeout = config_getduration(IMAPOPT_TIMEOUT, 'm');
CU_ASSERT_EQUAL(timeout, 13 * 60);
timeout = config_getduration(IMAPOPT_TIMEOUT, 'h');
CU_ASSERT_EQUAL(timeout, 13 * 60 * 60);
timeout = config_getduration(IMAPOPT_TIMEOUT, 'd');
CU_ASSERT_EQUAL(timeout, 13 * 60 * 60 * 24);
}
static void test_duration_value_negative(void)
{
int caldav_historical_age = 0xdeadbeef;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"caldav_historical_age: -1\n"
);
caldav_historical_age = config_getduration(IMAPOPT_CALDAV_HISTORICAL_AGE, 'd');
CU_ASSERT_EQUAL(caldav_historical_age, -1 * 60 * 60 * 24);
}
static void test_duration_default(void)
{
int timeout = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
/* timeout: 32m (default) */
);
timeout = config_getduration(IMAPOPT_TIMEOUT, 's');
CU_ASSERT_EQUAL(timeout, 32 * 60);
}
static void test_duration_nodefault(void)
{
int plaintextloginpause = -1;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
/* plaintextloginpause: <none> (default) */
);
plaintextloginpause = config_getduration(IMAPOPT_PLAINTEXTLOGINPAUSE, 's');
CU_ASSERT_EQUAL(plaintextloginpause, 0);
}
static void test_duration_invalid(void)
{
CU_EXPECT_CYRFATAL_BEGIN
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"timeout: junk\n"
);
CU_EXPECT_CYRFATAL_END(EX_CONFIG, "unparsable duration 'junk' for timeout in line 2");
}
struct duration_parse_data {
const char *str;
int expected_result;
int expected_duration;
};
static const struct duration_parse_data duration_parse_tests[] = {
{ "", 0, 0 },
{ "0", 0, 0 },
{ "0s", 0, 0 },
{ "0m", 0, 0 },
{ "0h", 0, 0 },
{ "0d", 0, 0 },
{ "0h0m", 0, 0 },
{ "0h0m0s", 0, 0 },
{ "0h0m1s", 0, 1 },
{ "1", 0, 1 },
{ "1s", 0, 1 },
{ "1m", 0, 60 },
{ "1h", 0, 60 * 60 },
{ "1d", 0, 24 * 60 * 60 },
{ "123", 0, 123 },
{ "123s", 0, 123 },
{ "123m", 0, 123 * 60 },
{ "123h", 0, 123 * 60 * 60 },
{ "123d", 0, 123 * 24 * 60 * 60 },
{ "1m1", 0, 60 + 1 },
{ "1m1s", 0, 60 + 1 },
{ "1h1m", 0, (60 * 60) + 60 },
{ "1h1s", 0, (60 * 60) + 1 },
{ "1d1h", 0, (24 * 60 * 60) + (60 * 60) },
{ "1h1m1s", 0, (60 * 60) + 60 + 1 },
{ "1m1m", 0, 60 + 60 },
{ "123m456", 0, (123 * 60) + 456 },
{ "123m456s", 0, (123 * 60) + 456 },
{ "123h456m", 0, (123 * 60 * 60) + (456 * 60) },
{ "123h456s", 0, (123 * 60 * 60) + 456 },
{ "123d456h", 0, (123 * 24 * 60 * 60) + (456 * 60 * 60) },
{ "123h456m789s", 0, (123 * 60 * 60) + (456 * 60) + 789 },
{ "123m456m", 0, (123 * 60) + (456 * 60) },
{ "0c", -1, 0xdeadbeef },
{ "1c", -1, 0xdeadbeef },
{ "123c", -1, 0xdeadbeef },
{ "1h1c", -1, 0xdeadbeef },
{ "1c23s", -1, 0xdeadbeef },
{ "-1", 0, -1 },
{ "-0", 0, 0 },
{ "--1", -1, 0xdeadbeef },
{ "-1s", 0, -1 },
{ "-1m", 0, -60 },
{ "1-2m", -1, 0xdeadbeef },
{ "1h1h", 0, 2 * 60 * 60 }, /* silly, but let it work */
{ "1hh", -1, 0xdeadbeef }, /* bogus, reject it */
{ "1hhh", -1, 0xdeadbeef }, /* bogus, reject it */
};
static void test_duration_parse(void)
{
const size_t n = sizeof(duration_parse_tests) / sizeof(duration_parse_tests[0]);
size_t i;
for (i = 0; i < n; i++) {
const struct duration_parse_data *test = &duration_parse_tests[i];
int duration = 0xdeadbeef;
int r = config_parseduration(test->str, 's', &duration);
CU_ASSERT_EQUAL(r, test->expected_result);
CU_ASSERT_EQUAL(duration, test->expected_duration);
}
}
static void test_magic_configdirectory_value(void)
{
const char *idlesocket = NULL;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"idlesocket: {configdirectory}/some/thing\n"
);
idlesocket = config_getstring(IMAPOPT_IDLESOCKET);
CU_ASSERT_PTR_NOT_NULL(idlesocket);
CU_ASSERT_STRING_EQUAL(idlesocket, DBDIR"/conf/some/thing");
}
static void test_magic_configdirectory_default(void)
{
const char *idlesocket = NULL;
config_read_string(
"configdirectory: "DBDIR"/conf\n"
/* idlesocket: {configdirectory}/socket/idle (default) */
);
idlesocket = config_getstring(IMAPOPT_IDLESOCKET);
CU_ASSERT_PTR_NOT_NULL(idlesocket);
CU_ASSERT_STRING_EQUAL(idlesocket, DBDIR"/conf/socket/idle");
}
static void test_deprecated_int(void)
{
/* { "autocreatequota", 0, INT, "2.5.0", "autocreate_quota" } */
int val;
/* set the deprecated name */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"autocreatequota: 12\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 1);
/* should not be able to read the deprecated name */
CU_EXPECT_CYRFATAL_BEGIN
val = config_getint(IMAPOPT_AUTOCREATEQUOTA);
CU_EXPECT_CYRFATAL_END(EX_SOFTWARE,
"Option 'autocreatequota' is deprecated in favor of "
"'autocreate_quota' since version 2.5.0.");
/* should be able to read the value at the new name */
val = config_getint(IMAPOPT_AUTOCREATE_QUOTA);
CU_ASSERT_EQUAL(val, 12);
/* set the new name */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"autocreate_quota: 12\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 0);
/* should be able to read it at the new name */
val = config_getint(IMAPOPT_AUTOCREATE_QUOTA);
CU_ASSERT_EQUAL(val, 12);
/* set both names to different values */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"autocreatequota: 12\n"
"autocreate_quota: 15\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 1);
/* should read new value at the new name */
val = config_getint(IMAPOPT_AUTOCREATE_QUOTA);
CU_ASSERT_EQUAL(val, 15);
}
static void test_deprecated_string(void)
{
/* { "tlscache_db_path", NULL, STRING, "2.5.0", "tls_sessions_db_path" } */
const char *val;
/* set the deprecated name */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"tlscache_db_path: foo\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 1);
/* should not be able to read the deprecated name */
CU_EXPECT_CYRFATAL_BEGIN
val = config_getstring(IMAPOPT_TLSCACHE_DB_PATH);
CU_EXPECT_CYRFATAL_END(EX_SOFTWARE,
"Option 'tlscache_db_path' is deprecated in favor of "
"'tls_sessions_db_path' since version 2.5.0.");
/* should be able to read the value at the new name */
val = config_getstring(IMAPOPT_TLS_SESSIONS_DB_PATH);
CU_ASSERT_PTR_NOT_NULL(val);
CU_ASSERT_STRING_EQUAL(val, "foo");
/* set the new name */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"tls_sessions_db_path: foo\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 0);
/* should be able to read it at the new name */
val = config_getstring(IMAPOPT_TLS_SESSIONS_DB_PATH);
CU_ASSERT_PTR_NOT_NULL(val);
CU_ASSERT_STRING_EQUAL(val, "foo");
/* set both names to different values */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"tlscache_db_path: foo\n"
"tls_sessions_db_path: bar\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 1);
/* should read new value at the new name */
val = config_getstring(IMAPOPT_TLS_SESSIONS_DB_PATH);
CU_ASSERT_PTR_NOT_NULL(val);
CU_ASSERT_STRING_EQUAL(val, "bar");
}
static void test_deprecated_stringlist(void)
{
/* { "tlscache_db", "twoskip",
* STRINGLIST("skiplist", "sql", "twoskip", "zeroskip"),
* "2.5.0", "tls_sessions_db" }
*/
const char *val;
/* set the deprecated name */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"tlscache_db: sql\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 1);
/* should not be able to read the deprecated name */
CU_EXPECT_CYRFATAL_BEGIN
val = config_getstring(IMAPOPT_TLSCACHE_DB);
CU_EXPECT_CYRFATAL_END(EX_SOFTWARE,
"Option 'tlscache_db' is deprecated in favor of "
"'tls_sessions_db' since version 2.5.0.");
/* should be able to read it at the new name */
val = config_getstring(IMAPOPT_TLS_SESSIONS_DB);
CU_ASSERT_PTR_NOT_NULL(val);
CU_ASSERT_STRING_EQUAL(val, "sql");
/* set the new name */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"tls_sessions_db: sql\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 0);
/* should be able to read it at the new name */
val = config_getstring(IMAPOPT_TLS_SESSIONS_DB);
CU_ASSERT_PTR_NOT_NULL(val);
CU_ASSERT_STRING_EQUAL(val, "sql");
/* set both names to different values */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"tlscache_db: sql\n"
"tls_sessions_db: zeroskip\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 1);
/* should read new value at the new name */
val = config_getstring(IMAPOPT_TLS_SESSIONS_DB);
CU_ASSERT_PTR_NOT_NULL(val);
CU_ASSERT_STRING_EQUAL(val, "zeroskip");
}
static void test_deprecated_duration(void)
{
/* { "archive_days", "7d", DURATION, "3.1.8", "archive_after" } */
/* { "archive_after", "7d", DURATION } */
int val;
/* set the deprecated name only */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"archive_days: 8\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 1);
/* should not be able to read the deprecated name */
CU_EXPECT_CYRFATAL_BEGIN;
val = config_getduration(IMAPOPT_ARCHIVE_DAYS, 'd');
CU_EXPECT_CYRFATAL_END(EX_SOFTWARE,
"Option 'archive_days' is deprecated in favor of "
"'archive_after' since version 3.1.8.");
/* should be able to read it at the new name */
val = config_getduration(IMAPOPT_ARCHIVE_AFTER, 'd');
CU_ASSERT_EQUAL(val, 8 * 24 * 60 * 60);
/* set the new name */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"archive_after: 12d\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 0);
/* should be able to read it at the new name */
val = config_getduration(IMAPOPT_ARCHIVE_AFTER, 'd');
CU_ASSERT_EQUAL(val, 12 * 24 * 60 * 60);
/* set both names to different values */
CU_SYSLOG_MATCH("Option '.*' is deprecated");
config_read_string(
"configdirectory: "DBDIR"/conf\n"
"archive_days: 7\n"
"archive_after: 12d\n"
);
CU_ASSERT_SYSLOG(/*all*/0, 1);
/* should read new value at the new name */
val = config_getduration(IMAPOPT_ARCHIVE_AFTER, 'd');
CU_ASSERT_EQUAL(val, 12 * 24 * 60 * 60);
}
/* vim: set ft=c: */
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Fri, Apr 24, 1:39 PM (1 d, 19 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18833814
Default Alt Text
libconfig.testc (19 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline