diff options
-rw-r--r-- | src/kprop/kprop.c | 37 | ||||
-rw-r--r-- | src/kprop/kprop.h | 12 | ||||
-rw-r--r-- | src/kprop/kprop_util.c | 42 | ||||
-rw-r--r-- | src/kprop/kpropd.c | 33 | ||||
-rwxr-xr-x | src/tests/t_kprop.py | 34 |
5 files changed, 130 insertions, 28 deletions
diff --git a/src/kprop/kprop.c b/src/kprop/kprop.c index 11239ef..8f9fd69 100644 --- a/src/kprop/kprop.c +++ b/src/kprop/kprop.c @@ -25,6 +25,7 @@ */ #include "k5-int.h" +#include <inttypes.h> #include <locale.h> #include <sys/file.h> #include <signal.h> @@ -70,11 +71,11 @@ static void open_connection(krb5_context context, char *host, int *fd_out); static void kerberos_authenticate(krb5_context context, krb5_auth_context *auth_context, int fd, krb5_principal me, krb5_creds **new_creds); -static int open_database(krb5_context context, char *data_fn, int *size); +static int open_database(krb5_context context, char *data_fn, off_t *size); static void close_database(krb5_context context, int fd); static void xmit_database(krb5_context context, krb5_auth_context auth_context, krb5_creds *my_creds, - int fd, int database_fd, int in_database_size); + int fd, int database_fd, off_t in_database_size); static void send_error(krb5_context context, krb5_creds *my_creds, int fd, char *err_text, krb5_error_code err_code); static void update_last_prop_file(char *hostname, char *file_name); @@ -89,7 +90,8 @@ static void usage() int main(int argc, char **argv) { - int fd, database_fd, database_size; + int fd, database_fd; + off_t database_size; krb5_error_code retval; krb5_context context; krb5_creds *my_creds; @@ -331,7 +333,7 @@ kerberos_authenticate(krb5_context context, krb5_auth_context *auth_context, * in the size of the database file. */ static int -open_database(krb5_context context, char *data_fn, int *size) +open_database(krb5_context context, char *data_fn, off_t *size) { struct stat stbuf, stbuf_ok; char *data_ok_fn; @@ -405,19 +407,18 @@ close_database(krb5_context context, int fd) static void xmit_database(krb5_context context, krb5_auth_context auth_context, krb5_creds *my_creds, int fd, int database_fd, - int in_database_size) + off_t in_database_size) { krb5_int32 n; krb5_data inbuf, outbuf; - char buf[KPROP_BUFSIZ]; + char buf[KPROP_BUFSIZ], dbsize_buf[KPROP_DBSIZE_MAX_BUFSIZ]; krb5_error_code retval; krb5_error *error; - krb5_ui_4 database_size = in_database_size, send_size, sent_size; + uint64_t database_size = in_database_size, send_size, sent_size; /* Send over the size. */ - send_size = htonl(database_size); - inbuf.data = (char *)&send_size; - inbuf.length = sizeof(send_size); /* must be 4, really */ + inbuf = make_data(dbsize_buf, sizeof(dbsize_buf)); + encode_database_size(database_size, &inbuf); /* KPROP_CKSUMTYPE */ retval = krb5_mk_safe(context, auth_context, &inbuf, &outbuf, NULL); if (retval) { @@ -452,7 +453,7 @@ xmit_database(krb5_context context, krb5_auth_context auth_context, retval = krb5_mk_priv(context, auth_context, &inbuf, &outbuf, NULL); if (retval) { snprintf(buf, sizeof(buf), - "while encoding database block starting at %d", + "while encoding database block starting at %"PRIu64, sent_size); com_err(progname, retval, "%s", buf); send_error(context, my_creds, fd, buf, retval); @@ -463,14 +464,14 @@ xmit_database(krb5_context context, krb5_auth_context auth_context, if (retval) { krb5_free_data_contents(context, &outbuf); com_err(progname, retval, - _("while sending database block starting at %d"), + _("while sending database block starting at %"PRIu64), sent_size); exit(1); } krb5_free_data_contents(context, &outbuf); sent_size += n; if (debug) - printf("%d bytes sent.\n", sent_size); + printf("%"PRIu64" bytes sent.\n", sent_size); } if (sent_size != database_size) { com_err(progname, 0, _("Premature EOF found for database file!")); @@ -525,10 +526,14 @@ xmit_database(krb5_context context, krb5_auth_context auth_context, exit(1); } - memcpy(&send_size, outbuf.data, sizeof(send_size)); - send_size = ntohl(send_size); + retval = decode_database_size(&outbuf, &send_size); + if (retval) { + com_err(progname, retval, _("malformed sent database size message")); + exit(1); + } if (send_size != database_size) { - com_err(progname, 0, _("Kpropd sent database size %d, expecting %d"), + com_err(progname, 0, _("Kpropd sent database size %"PRIu64 + ", expecting %"PRIu64), send_size, database_size); exit(1); } diff --git a/src/kprop/kprop.h b/src/kprop/kprop.h index 75331cc..3a319b5 100644 --- a/src/kprop/kprop.h +++ b/src/kprop/kprop.h @@ -32,6 +32,7 @@ #define KPROP_PROT_VERSION "kprop5_01" #define KPROP_BUFSIZ 32768 +#define KPROP_DBSIZE_MAX_BUFSIZ 12 /* max length of an encoded DB size */ /* pathnames are in osconf.h, included via k5-int.h */ @@ -41,3 +42,14 @@ int sockaddr2krbaddr(krb5_context context, int family, struct sockaddr *sa, krb5_error_code sn2princ_realm(krb5_context context, const char *hostname, const char *sname, const char *realm, krb5_principal *princ_out); + +/* + * Encode size in four bytes (for backward compatibility) if it fits; otherwise + * use the larger encoding. buf must be allocated with at least + * KPROP_DBSIZE_MAX_BUFSIZ bytes. + */ +void encode_database_size(uint64_t size, krb5_data *buf); + +/* Decode a database size. Return KRB5KRB_ERR_GENERIC if buf has an invalid + * length or did not encode a 32-bit size compactly. */ +krb5_error_code decode_database_size(const krb5_data *buf, uint64_t *size_out); diff --git a/src/kprop/kprop_util.c b/src/kprop/kprop_util.c index c2b2e87..c0aa2c8 100644 --- a/src/kprop/kprop_util.c +++ b/src/kprop/kprop_util.c @@ -92,3 +92,45 @@ sn2princ_realm(krb5_context context, const char *hostname, const char *sname, *princ_out = princ; return 0; } + +void +encode_database_size(uint64_t size, krb5_data *buf) +{ + assert(buf->length >= 12); + if (size > 0 && size <= UINT32_MAX) { + /* Encode in 32 bits for backward compatibility. */ + store_32_be(size, buf->data); + buf->length = 4; + } else { + /* Set the first 32 bits to 0 and encode in the following 64 bits. */ + store_32_be(0, buf->data); + store_64_be(size, buf->data + 4); + buf->length = 12; + } +} + +krb5_error_code +decode_database_size(const krb5_data *buf, uint64_t *size_out) +{ + uint64_t size; + + if (buf->length == 12) { + /* A 12-byte buffer must have the first four bytes zeroed. */ + if (load_32_be(buf->data) != 0) + return KRB5KRB_ERR_GENERIC; + + /* The size is stored in the next 64 bits. Values from 1..2^32-1 must + * be encoded in four bytes. */ + size = load_64_be(buf->data + 4); + if (size > 0 && size <= UINT32_MAX) + return KRB5KRB_ERR_GENERIC; + } else if (buf->length == 4) { + size = load_32_be(buf->data); + } else { + /* Invalid buffer size. */ + return KRB5KRB_ERR_GENERIC; + } + + *size_out = size; + return 0; +} diff --git a/src/kprop/kpropd.c b/src/kprop/kpropd.c index 46bb9d1..f2341d7 100644 --- a/src/kprop/kpropd.c +++ b/src/kprop/kpropd.c @@ -55,6 +55,7 @@ #include "com_err.h" #include "fake-addrinfo.h" +#include <inttypes.h> #include <locale.h> #include <ctype.h> #include <sys/file.h> @@ -1345,9 +1346,10 @@ static void recv_database(krb5_context context, int fd, int database_fd, krb5_data *confmsg) { - krb5_ui_4 database_size, received_size; + uint64_t database_size, received_size; int n; char buf[1024]; + char dbsize_buf[KPROP_DBSIZE_MAX_BUFSIZ]; krb5_data inbuf, outbuf; krb5_error_code retval; @@ -1369,10 +1371,17 @@ recv_database(krb5_context context, int fd, int database_fd, _("while decoding database size from client")); exit(1); } - memcpy(&database_size, outbuf.data, sizeof(database_size)); + + retval = decode_database_size(&outbuf, &database_size); + if (retval) { + send_error(context, fd, retval, "malformed database size message"); + com_err(progname, retval, + _("malformed database size message from client")); + exit(1); + } + krb5_free_data_contents(context, &inbuf); krb5_free_data_contents(context, &outbuf); - database_size = ntohl(database_size); /* Initialize the initial vector. */ retval = krb5_auth_con_initivector(context, auth_context); @@ -1392,7 +1401,7 @@ recv_database(krb5_context context, int fd, int database_fd, retval = krb5_read_message(context, &fd, &inbuf); if (retval) { snprintf(buf, sizeof(buf), - "while reading database block starting at offset %d", + "while reading database block starting at offset %"PRIu64, received_size); com_err(progname, retval, "%s", buf); send_error(context, fd, retval, buf); @@ -1403,8 +1412,8 @@ recv_database(krb5_context context, int fd, int database_fd, retval = krb5_rd_priv(context, auth_context, &inbuf, &outbuf, NULL); if (retval) { snprintf(buf, sizeof(buf), - "while decoding database block starting at offset %d", - received_size); + "while decoding database block starting at offset %" + PRIu64, received_size); com_err(progname, retval, "%s", buf); send_error(context, fd, retval, buf); krb5_free_data_contents(context, &inbuf); @@ -1414,13 +1423,13 @@ recv_database(krb5_context context, int fd, int database_fd, krb5_free_data_contents(context, &inbuf); if (n < 0) { snprintf(buf, sizeof(buf), - "while writing database block starting at offset %d", + "while writing database block starting at offset %"PRIu64, received_size); send_error(context, fd, errno, buf); } else if ((unsigned int)n != outbuf.length) { snprintf(buf, sizeof(buf), "incomplete write while writing database block starting " - "at \noffset %d (%d written, %d expected)", + "at \noffset %"PRIu64" (%d written, %d expected)", received_size, n, outbuf.length); send_error(context, fd, KRB5KRB_ERR_GENERIC, buf); } @@ -1431,7 +1440,8 @@ recv_database(krb5_context context, int fd, int database_fd, /* OK, we've seen the entire file. Did we get too many bytes? */ if (received_size > database_size) { snprintf(buf, sizeof(buf), - "Received %d bytes, expected %d bytes for database file", + "Received %"PRIu64" bytes, expected %"PRIu64 + " bytes for database file", received_size, database_size); send_error(context, fd, KRB5KRB_ERR_GENERIC, buf); } @@ -1441,9 +1451,8 @@ recv_database(krb5_context context, int fd, int database_fd, /* Create message acknowledging number of bytes received, but * don't send it until kdb5_util returns successfully. */ - database_size = htonl(database_size); - inbuf.data = (char *)&database_size; - inbuf.length = sizeof(database_size); + inbuf = make_data(dbsize_buf, sizeof(dbsize_buf)); + encode_database_size(database_size, &inbuf); retval = krb5_mk_safe(context,auth_context,&inbuf,confmsg,NULL); if (retval) { com_err(progname, retval, "while encoding # of received bytes"); diff --git a/src/tests/t_kprop.py b/src/tests/t_kprop.py index d96f7c5..4421a7c 100755 --- a/src/tests/t_kprop.py +++ b/src/tests/t_kprop.py @@ -87,5 +87,39 @@ realm.run([kdb5_util, 'dump', dumpfile]) realm.run([kprop, '-f', dumpfile, '-P', str(realm.kprop_port()), hostname]) check_output(kpropd) realm.run([kadminl, 'listprincs'], replica3, expected_msg='wakawaka') +stop_daemon(kpropd) + +# This test is too resource-intensive to be included in "make check" +# by default, but it can be enabled in the environment to test the +# propagation of databases large enough to require a 12-byte encoding +# of the database size. +if 'KPROP_LARGE_DB_TEST' in os.environ: + output('Generating >4GB dumpfile\n') + with open(dumpfile, 'w') as f: + f.write('kdb5_util load_dump version 6\n') + f.write('princ\t38\t15\t3\t1\t0\tK/M@KRBTEST.COM\t64\t86400\t0\t0\t0' + '\t0\t0\t0\t8\t2\t0100\t9\t8\t0100010000000000\t2\t28' + '\tb93e105164625f6372656174696f6e404b5242544553542e434f4d00' + '\t1\t1\t18\t62\t2000408c027c250e8cc3b81476414f2214d57c1ce' + '38891e29792e87258247c73547df4d5756266931dd6686b62270e6568' + '95a31ec66bfe913b4f15226227\t-1;\n') + for i in range(1, 20000000): + f.write('princ\t38\t21\t1\t1\t0\tp%08d@KRBTEST.COM' % i) + f.write('\t0\t86400\t0\t0\t0\t0\t0\t0\t2\t27' + '\td73e1051757365722f61646d696e404b5242544553542e434f4d00' + '\t1\t1\t17\t46' + '\t10009c8ab7b3f89ccf3ca3ad98352a461b7f4f1b0c49' + '5605117591d9ad52ba4da0adef7a902126973ed2bdc3ffbf\t-1;\n') + assert os.path.getsize(dumpfile) > 4 * 1024 * 1024 * 1024 + with open(dumpfile + '.dump_ok', 'w') as f: + f.write('\0') + conf_large = {'dbmodules': {'db': {'database_name': '$testdir/db.large'}}, + 'realms': {'$realm': {'iprop_resync_timeout': '3600'}}} + large = realm.special_env('large', True, kdc_conf=conf_large) + kpropd = realm.start_kpropd(large, ['-d']) + realm.run([kprop, '-f', dumpfile, '-P', str(realm.kprop_port()), hostname]) + check_output(kpropd) + realm.run([kadminl, 'getprinc', 'p19999999'], env=large, + expected_msg='Principal: p19999999') success('kprop tests') |