Also document them in the README and add tests.
permitted and will not be written to disk. This is designed to prevent the
accidental loss of all users/groups on a system.
+The passwd/group files have the following size restrictions:
+- maximum number of entries: '2^64-1' (uint64_t)
+- maximum passwd entry size: 65543 bytes (including newline)
+- maximum group entry size: 65535 bytes (including newline, only one member)
+- maximum members per group: depends on the user name length,
+ with 9 bytes per user: 5460 users
+- `nsscash` checks for these restrictions and aborts with an error if they are
+ violated
+
nsscash is licensed under AGPL version 3 or later.
[1] https://github.com/google/nsscache
"sort"
"strconv"
"strings"
+ "math"
"github.com/pkg/errors"
)
}
// And the group members concatenated as above
data.Write(mems.Bytes())
+ // Ensure the offsets can fit the length of this entry
+ if data.Len() > math.MaxUint16 {
+ return nil, fmt.Errorf("group too large to serialize: %v, %v",
+ data.Len(), g)
+ }
size := uint16(data.Len())
var res bytes.Buffer // serialized result
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
#include "../cash_nss.h"
assert(rename("tests/group.nsscash.tmp", "tests/group.nsscash") == 0);
}
+static void test_limits(void) {
+ char large_member[65525];
+ memset(large_member, 'X', sizeof(large_member));
+ large_member[sizeof(large_member)-1] = '\0';
+
+ char many_members[54603]; // 5461 members
+ memset(many_members, 'X', sizeof(many_members));
+ for (int i = 9; i < (int)sizeof(many_members); i += 10) {
+ many_members[i-1] = (char)('A' + i % ('Z' - 'A'));
+ many_members[i] = ',';
+ }
+ many_members[sizeof(many_members)-1] = '\0';
+
+ int r;
+ FILE *fh;
+
+ const char *nsscache_cmd = "../nsscash convert group "
+ "tests/limits tests/limits.nsscash 2> /dev/null";
+
+ // Entries which will not fit in uint16_t, nsscash must abort
+
+ fh = fopen("tests/limits", "w");
+ assert(fh != NULL);
+ r = fprintf(fh, "test:x:42:A%s\n", large_member);
+ assert(r == 65536);
+ r = fclose(fh);
+ assert(r == 0);
+
+ r = system(nsscache_cmd);
+ assert(r != -1);
+ assert(WIFEXITED(r) && WEXITSTATUS(r) == 1);
+
+ fh = fopen("tests/limits", "w");
+ assert(fh != NULL);
+ r = fprintf(fh, "many:x:4711:%s%s\n", many_members, many_members);
+ assert(r == 109217);
+ r = fclose(fh);
+ assert(r == 0);
+
+ r = system(nsscache_cmd);
+ assert(r != -1);
+ assert(WIFEXITED(r) && WEXITSTATUS(r) == 1);
+
+ // Largest entries which will fit
+
+ fh = fopen("tests/limits", "w");
+ assert(fh != NULL);
+ r = fprintf(fh, "test:x:42:%s\n", large_member);
+ assert(r == 65535);
+ r = fprintf(fh, "many:x:4711:%s\n", many_members);
+ assert(r == 54615);
+ r = fclose(fh);
+ assert(r == 0);
+
+ r = system(nsscache_cmd);
+ assert(r != -1);
+ assert(WIFEXITED(r) && WEXITSTATUS(r) == 0);
+
+ r = rename("tests/group.nsscash", "tests/group.nsscash.tmp");
+ assert(r == 0);
+ r = rename("tests/limits.nsscash", "tests/group.nsscash");
+ assert(r == 0);
+
+ // Check if the entry can be retrieved
+
+ struct group g;
+ enum nss_status s;
+ char tmp[sizeof(char **) + 1*sizeof(char *) + 1*sizeof(uint16_t) +
+ 4+1 + 1+1 + 65525 + 1];
+ char tmp2[sizeof(char **) + 5462*sizeof(char *) + 5462*sizeof(uint16_t) +
+ 4+1 + 1+1 + 54603 + 1];
+ int errnop = 0;
+
+ s = _nss_cash_getgrgid_r(42, &g, tmp, sizeof(tmp), &errnop);
+ assert(s == NSS_STATUS_SUCCESS);
+ assert(!strcmp(g.gr_name, "test"));
+ assert(g.gr_gid == 42);
+ assert(g.gr_mem != NULL);
+ assert(!strcmp(g.gr_mem[0], large_member));
+ assert(g.gr_mem[1] == NULL);
+
+ s = _nss_cash_getgrgid_r(4711, &g, tmp2, sizeof(tmp2), &errnop);
+ assert(s == NSS_STATUS_SUCCESS);
+ assert(!strcmp(g.gr_name, "many"));
+ assert(g.gr_gid == 4711);
+ assert(g.gr_mem != NULL);
+ for (int i = 0; i < 5461-1; i++) {
+ char x[9+1];
+ memset(x, 'X', sizeof(x));
+ x[8] = (char)('A' + (i * 10 + 9) % ('Z' - 'A'));
+ x[9] = '\0';
+ assert(!strcmp(g.gr_mem[i], x));
+ }
+ assert(!strcmp(g.gr_mem[5461-1], "XX"));
+ assert(g.gr_mem[5461] == NULL);
+
+ r = rename("tests/group.nsscash.tmp", "tests/group.nsscash");
+ assert(r == 0);
+
+ r = unlink("tests/limits");
+ assert(r == 0);
+}
+
int main(void) {
test_getgrent();
test_getgrgid();
test_getgrnam();
+ test_limits();
+
return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
#include "../cash_nss.h"
assert(rename("tests/passwd.nsscash.tmp", "tests/passwd.nsscash") == 0);
}
+static void test_limits(void) {
+ char gecos[65508];
+ memset(gecos, 'X', sizeof(gecos));
+ gecos[sizeof(gecos)-1] = '\0';
+
+ int r;
+ FILE *fh;
+
+ const char *nsscache_cmd = "../nsscash convert passwd "
+ "tests/limits tests/limits.nsscash 2> /dev/null";
+
+ // Entries which will not fit in uint16_t, nsscash must abort
+
+ fh = fopen("tests/limits", "w");
+ assert(fh != NULL);
+ r = fprintf(fh, "test:xx:42:4711:%s:/home/test:/bin/zsh\n", gecos);
+ assert(r == 65544);
+ r = fclose(fh);
+ assert(r == 0);
+
+ r = system(nsscache_cmd);
+ assert(r != -1);
+ assert(WIFEXITED(r) && WEXITSTATUS(r) == 1);
+
+ fh = fopen("tests/limits", "w");
+ assert(fh != NULL);
+ r = fprintf(fh, "test:%s:42:4711:%s:/home/test:/bin/zsh\n", gecos, gecos);
+ assert(r == 131049);
+ r = fclose(fh);
+ assert(r == 0);
+
+ r = system(nsscache_cmd);
+ assert(r != -1);
+ assert(WIFEXITED(r) && WEXITSTATUS(r) == 1);
+
+ // Largest entry which will fit
+
+ fh = fopen("tests/limits", "w");
+ assert(fh != NULL);
+ r = fprintf(fh, "test:x:42:4711:%s:/home/test:/bin/zsh\n", gecos);
+ assert(r == 65543);
+ r = fclose(fh);
+ assert(r == 0);
+
+ r = system(nsscache_cmd);
+ assert(r != -1);
+ assert(WIFEXITED(r) && WEXITSTATUS(r) == 0);
+
+ r = rename("tests/passwd.nsscash", "tests/passwd.nsscash.tmp");
+ assert(r == 0);
+ r = rename("tests/limits.nsscash", "tests/passwd.nsscash");
+ assert(r == 0);
+
+ // Check if the entry can be retrieved
+
+ struct passwd p;
+ enum nss_status s;
+ char tmp[4+1 + 1+1 + 65508 + 10+1 + 8+1];
+ int errnop = 0;
+
+ s = _nss_cash_getpwuid_r(42, &p, tmp, sizeof(tmp), &errnop);
+ assert(s == NSS_STATUS_SUCCESS);
+ assert(!strcmp(p.pw_name, "test"));
+ assert(!strcmp(p.pw_passwd, "x"));
+ assert(p.pw_uid == 42);
+ assert(p.pw_gid == 4711);
+ assert(!strcmp(p.pw_gecos, gecos));
+ assert(!strcmp(p.pw_dir, "/home/test"));
+ assert(!strcmp(p.pw_shell, "/bin/zsh"));
+
+ r = rename("tests/passwd.nsscash.tmp", "tests/passwd.nsscash");
+ assert(r == 0);
+
+ r = unlink("tests/limits");
+ assert(r == 0);
+}
+
int main(void) {
test_getpwent();
test_getpwuid();
test_getpwnam();
+ test_limits();
+
return EXIT_SUCCESS;
}
"encoding/binary"
"fmt"
"io"
+ "math"
"sort"
"strconv"
"strings"
offShell := uint16(data.Len())
data.Write([]byte(p.Shell))
data.WriteByte(0)
+ // Ensure the offsets can fit the length of this entry
+ if data.Len() > math.MaxUint16 {
+ return nil, fmt.Errorf("passwd too large to serialize: %v, %v",
+ data.Len(), p)
+ }
size := uint16(data.Len())
var res bytes.Buffer // serialized result