torture/spoolss: issue GetJob after StartDocPrinter
[obnox/samba/samba-obnox.git] / source4 / torture / rpc / spoolss.c
index 630694d01adf757750d7eb627725d49c73708c15..23f501d7401802021f4f663ef1567a04ccde1cd1 100644 (file)
@@ -5,7 +5,7 @@
    Copyright (C) Tim Potter 2003
    Copyright (C) Stefan Metzmacher 2005
    Copyright (C) Jelmer Vernooij 2007
-   Copyright (C) Guenther Deschner 2009-2010
+   Copyright (C) Guenther Deschner 2009-2011,2013
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 #include "libcli/libcli.h"
 #include "libcli/raw/raw_proto.h"
 #include "libcli/resolve/resolve.h"
+#include "libcli/smb2/smb2.h"
+#include "libcli/smb2/smb2_calls.h"
 #include "lib/cmdline/popt_common.h"
 #include "system/filesys.h"
 #include "torture/ndr/ndr.h"
+#include "torture/smb2/proto.h"
 
 #define TORTURE_WELLKNOWN_PRINTER      "torture_wkn_printer"
 #define TORTURE_PRINTER                        "torture_printer"
 #define TORTURE_DRIVER_ADOBE           "torture_driver_adobe"
 #define TORTURE_DRIVER_EX_ADOBE                "torture_driver_ex_adobe"
 #define TORTURE_DRIVER_ADOBE_CUPSADDSMB        "torture_driver_adobe_cupsaddsmb"
+#define TORTURE_DRIVER_TIMESTAMPS      "torture_driver_timestamps"
+#define TORTURE_DRIVER_DELETER         "torture_driver_deleter"
+#define TORTURE_DRIVER_DELETERIN       "torture_driver_deleterin"
+#define TORTURE_PRINTER_STATIC1                "print1"
 
 #define TOP_LEVEL_PRINT_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print"
 #define TOP_LEVEL_PRINT_PRINTERS_KEY TOP_LEVEL_PRINT_KEY "\\Printers"
@@ -70,8 +77,8 @@ struct test_spoolss_context {
        union spoolss_PortInfo *ports[3];
 
        /* for EnumPrinterDrivers */
-       uint32_t driver_count[8];
-       union spoolss_DriverInfo *drivers[8];
+       uint32_t driver_count[9];
+       union spoolss_DriverInfo *drivers[9];
 
        /* for EnumMonitors */
        uint32_t monitor_count[3];
@@ -237,12 +244,12 @@ static bool PrinterInfo_to_SetPrinterInfo(struct torture_context *tctx,
                s->info2->drivername            = i->info2.drivername;
                s->info2->comment               = i->info2.comment;
                s->info2->location              = i->info2.location;
-               s->info2->devmode_ptr           = 0;
+               s->info2->devmode_ptr           = NULL;
                s->info2->sepfile               = i->info2.sepfile;
                s->info2->printprocessor        = i->info2.printprocessor;
                s->info2->datatype              = i->info2.datatype;
                s->info2->parameters            = i->info2.parameters;
-               s->info2->secdesc_ptr           = 0;
+               s->info2->secdesc_ptr           = NULL;
                s->info2->attributes            = i->info2.attributes;
                s->info2->priority              = i->info2.priority;
                s->info2->defaultpriority       = i->info2.defaultpriority;
@@ -506,30 +513,36 @@ static bool test_GetPrinterDriverDirectory(struct torture_context *tctx,
        return true;
 }
 
-static bool test_EnumPrinterDrivers_args(struct torture_context *tctx,
-                                        struct dcerpc_binding_handle *b,
-                                        const char *server_name,
-                                        const char *environment,
-                                        uint32_t level,
-                                        uint32_t *count_p,
-                                        union spoolss_DriverInfo **info_p)
+static bool test_EnumPrinterDrivers_buffers(struct torture_context *tctx,
+                                           struct dcerpc_binding_handle *b,
+                                           const char *server_name,
+                                           const char *environment,
+                                           uint32_t level,
+                                           uint32_t offered,
+                                           uint32_t *count_p,
+                                           union spoolss_DriverInfo **info_p)
 {
        struct spoolss_EnumPrinterDrivers r;
        uint32_t needed;
        uint32_t count;
        union spoolss_DriverInfo *info;
+       DATA_BLOB buffer;
+
+       if (offered > 0) {
+               buffer = data_blob_talloc_zero(tctx, offered);
+       }
 
        r.in.server             = server_name;
        r.in.environment        = environment;
        r.in.level              = level;
-       r.in.buffer             = NULL;
-       r.in.offered            = 0;
+       r.in.buffer             = offered ? &buffer : NULL;
+       r.in.offered            = offered;
        r.out.needed            = &needed;
        r.out.count             = &count;
        r.out.info              = &info;
 
-       torture_comment(tctx, "Testing EnumPrinterDrivers(%s) level %u\n",
-               r.in.environment, r.in.level);
+       torture_comment(tctx, "Testing EnumPrinterDrivers(%s) level %u, offered: %u\n",
+               r.in.environment, r.in.level, r.in.offered);
 
        torture_assert_ntstatus_ok(tctx,
                dcerpc_spoolss_EnumPrinterDrivers_r(b, tctx, &r),
@@ -560,6 +573,20 @@ static bool test_EnumPrinterDrivers_args(struct torture_context *tctx,
 
 }
 
+
+static bool test_EnumPrinterDrivers_args(struct torture_context *tctx,
+                                        struct dcerpc_binding_handle *b,
+                                        const char *server_name,
+                                        const char *environment,
+                                        uint32_t level,
+                                        uint32_t *count_p,
+                                        union spoolss_DriverInfo **info_p)
+{
+       return test_EnumPrinterDrivers_buffers(tctx, b, server_name,
+                                              environment, level, 0,
+                                              count_p, info_p);
+}
+
 static bool test_EnumPrinterDrivers_findone(struct torture_context *tctx,
                                            struct dcerpc_binding_handle *b,
                                            const char *server_name,
@@ -578,7 +605,7 @@ static bool test_EnumPrinterDrivers_findone(struct torture_context *tctx,
                "failed to enumerate printer drivers");
 
        for (i=0; i < count; i++) {
-               const char *driver_name_ret;
+               const char *driver_name_ret = "";
                switch (level) {
                case 1:
                        driver_name_ret = info[i].info1.driver_name;
@@ -635,6 +662,7 @@ static bool test_EnumPrinterDrivers(struct torture_context *tctx,
        struct dcerpc_pipe *p = ctx->spoolss_pipe;
        struct dcerpc_binding_handle *b = p->binding_handle;
        uint16_t levels[] = { 1, 2, 3, 4, 5, 6, 8 };
+       uint16_t buffer_sizes[] = { 0, 1024, 6040, 0xffff };
        int i, j, a;
 
        /* FIXME: gd, come back and fix "" as server, and handle
@@ -648,6 +676,15 @@ static bool test_EnumPrinterDrivers(struct torture_context *tctx,
 
        for (a=0;a<ARRAY_SIZE(environments);a++) {
 
+       for (i=0;i<ARRAY_SIZE(buffer_sizes);i++) {
+               torture_assert(tctx,
+                       test_EnumPrinterDrivers_buffers(tctx, b, server_name,
+                                                       environments[a], 3,
+                                                       buffer_sizes[i],
+                                                       NULL, NULL),
+                       "failed to enumerate drivers");
+       }
+
        for (i=0;i<ARRAY_SIZE(levels);i++) {
                int level = levels[i];
                uint32_t count;
@@ -672,9 +709,9 @@ static bool test_EnumPrinterDrivers(struct torture_context *tctx,
        for (i=0;i<ARRAY_SIZE(levels);i++) {
                int level = levels[i];
 
-               for (j=0;j<ctx->driver_count[level];j++) {
-                       union spoolss_DriverInfo *cur = &ctx->drivers[level][j];
-                       union spoolss_DriverInfo *ref = &ctx->drivers[8][j];
+               for (j=0;j<ctx->driver_count[level - 1];j++) {
+                       union spoolss_DriverInfo *cur = &ctx->drivers[level - 1][j];
+                       union spoolss_DriverInfo *ref = &ctx->drivers[7][j];
 
                        switch (level) {
                        case 1:
@@ -1240,8 +1277,9 @@ static bool test_SetPrinter(struct torture_context *tctx,
 
        torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_SetPrinter_r(b, tctx, &r),
                "failed to call SetPrinter");
-       torture_assert_werr_ok(tctx, r.out.result,
-               "failed to call SetPrinter");
+       torture_assert(tctx, (W_ERROR_EQUAL(r.out.result, WERR_OK)
+                          || W_ERROR_EQUAL(r.out.result, WERR_IO_PENDING)),
+                      "SetPrinter failed");
 
        return true;
 }
@@ -1410,8 +1448,8 @@ static bool test_SetPrinter_errors(struct torture_context *tctx,
 static void clear_info2(struct spoolss_SetPrinterInfoCtr *r)
 {
        if ((r->level == 2) && (r->info.info2)) {
-               r->info.info2->secdesc_ptr = 0;
-               r->info.info2->devmode_ptr = 0;
+               r->info.info2->secdesc_ptr = NULL;
+               r->info.info2->devmode_ptr = NULL;
        }
 }
 
@@ -1431,8 +1469,6 @@ static bool test_PrinterInfo(struct torture_context *tctx,
        bool ret = true;
        int i;
 
-       torture_skip(tctx, "Printer Info test is currently broken, skipping");
-
        uint32_t status_list[] = {
                /* these do not stick
                PRINTER_STATUS_PAUSED,
@@ -1511,6 +1547,9 @@ static bool test_PrinterInfo(struct torture_context *tctx,
                0x80000000 */
        };
 
+       torture_skip(tctx, "Printer Info test is currently broken, skipping");
+
+
        ZERO_STRUCT(devmode_ctr);
        ZERO_STRUCT(secdesc_ctr);
 
@@ -1615,11 +1654,13 @@ static bool test_PrinterInfo(struct torture_context *tctx,
                }
 
 #define TEST_PRINTERINFO_STRING_EXP_ERR(lvl1, field1, lvl2, field2, value, err) do { \
+               void *p; \
                torture_comment(tctx, "field test %d/%s vs %d/%s\n", lvl1, #field1, lvl2, #field2); \
                q.in.level = lvl1; \
                TESTGETCALL(GetPrinter, q) \
                info_ctr.level = lvl1; \
-               info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)(void *)&q.out.info->info ## lvl1; \
+               p = (void *)&q.out.info->info ## lvl1; \
+               info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)p; \
                info_ctr.info.info ## lvl1->field1 = value;\
                TESTSETCALL_EXP(SetPrinter, s, err) \
                info_ctr.info.info ## lvl1->field1 = ""; \
@@ -1628,7 +1669,8 @@ static bool test_PrinterInfo(struct torture_context *tctx,
                STRING_EQUAL(info_ctr.info.info ## lvl1->field1, value, field1); \
                q.in.level = lvl2; \
                TESTGETCALL(GetPrinter, q) \
-               info_ctr.info.info ## lvl2 = (struct spoolss_SetPrinterInfo ## lvl2 *)(void *)&q.out.info->info ## lvl2; \
+               p = (void *)&q.out.info->info ## lvl2; \
+               info_ctr.info.info ## lvl2 = (struct spoolss_SetPrinterInfo ## lvl2 *)p; \
                STRING_EQUAL(info_ctr.info.info ## lvl2->field2, value, field2); \
        } while (0)
 
@@ -1637,20 +1679,24 @@ static bool test_PrinterInfo(struct torture_context *tctx,
        } while (0);
 
 #define TEST_PRINTERINFO_INT_EXP(lvl1, field1, lvl2, field2, value, exp_value) do { \
+               void *p; \
                torture_comment(tctx, "field test %d/%s vs %d/%s\n", lvl1, #field1, lvl2, #field2); \
                q.in.level = lvl1; \
                TESTGETCALL(GetPrinter, q) \
                info_ctr.level = lvl1; \
-               info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)(void *)&q.out.info->info ## lvl1; \
+               p = (void *)&q.out.info->info ## lvl1; \
+               info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)p; \
                info_ctr.info.info ## lvl1->field1 = value; \
                TESTSETCALL(SetPrinter, s) \
                info_ctr.info.info ## lvl1->field1 = 0; \
                TESTGETCALL(GetPrinter, q) \
-               info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)(void *)&q.out.info->info ## lvl1; \
+               p = (void *)&q.out.info->info ## lvl1; \
+               info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)p; \
                INT_EQUAL(info_ctr.info.info ## lvl1->field1, exp_value, field1); \
                q.in.level = lvl2; \
                TESTGETCALL(GetPrinter, q) \
-               info_ctr.info.info ## lvl2 = (struct spoolss_SetPrinterInfo ## lvl2 *)(void *)&q.out.info->info ## lvl2; \
+               p = (void *)&q.out.info->info ## lvl2; \
+               info_ctr.info.info ## lvl2 = (struct spoolss_SetPrinterInfo ## lvl2 *)p; \
                INT_EQUAL(info_ctr.info.info ## lvl2->field2, exp_value, field1); \
        } while (0)
 
@@ -1858,13 +1904,14 @@ static bool test_sd_set_level(struct torture_context *tctx,
        struct spoolss_DevmodeContainer devmode_ctr;
        struct sec_desc_buf secdesc_ctr;
        union spoolss_SetPrinterInfo sinfo;
+       union spoolss_PrinterInfo info;
+       struct spoolss_SetPrinterInfo3 info3;
 
        ZERO_STRUCT(devmode_ctr);
        ZERO_STRUCT(secdesc_ctr);
 
        switch (level) {
        case 2: {
-               union spoolss_PrinterInfo info;
                torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), "");
                torture_assert(tctx, PrinterInfo_to_SetPrinterInfo(tctx, &info, 2, &sinfo), "");
 
@@ -1874,9 +1921,8 @@ static bool test_sd_set_level(struct torture_context *tctx,
                break;
        }
        case 3: {
-               struct spoolss_SetPrinterInfo3 info3;
 
-               info3.sec_desc_ptr = 0;
+               info3.sec_desc_ptr = NULL;
 
                info_ctr.level = 3;
                info_ctr.info.info3 = &info3;
@@ -2047,7 +2093,7 @@ static bool test_devmode_set_level(struct torture_context *tctx,
        case 8: {
                struct spoolss_SetPrinterInfo8 info8;
 
-               info8.devmode_ptr = 0;
+               info8.devmode_ptr = NULL;
 
                info_ctr.level = 8;
                info_ctr.info.info8 = &info8;
@@ -2124,7 +2170,6 @@ static bool test_devicemode_full(struct torture_context *tctx,
 {
        struct spoolss_SetPrinter s;
        struct spoolss_GetPrinter q;
-       struct spoolss_GetPrinter q0;
        struct spoolss_SetPrinterInfoCtr info_ctr;
        struct spoolss_SetPrinterInfo8 info8;
        union spoolss_PrinterInfo info;
@@ -2134,28 +2179,35 @@ static bool test_devicemode_full(struct torture_context *tctx,
        bool ret = true;
        NTSTATUS status;
 
-#define TEST_DEVMODE_INT_EXP(lvl1, field1, lvl2, field2, value, exp_value) do { \
+#define TEST_DEVMODE_INT_EXP_RESULT(lvl1, field1, lvl2, field2, value, exp_value, expected_result) do { \
                torture_comment(tctx, "field test %d/%s vs %d/%s\n", lvl1, #field1, lvl2, #field2); \
                q.in.level = lvl1; \
                TESTGETCALL(GetPrinter, q) \
                info_ctr.level = lvl1; \
                if (lvl1 == 2) {\
-                       info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)(void *)&q.out.info->info ## lvl1; \
+                       void *p = (void *)&q.out.info->info ## lvl1; \
+                       info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)p; \
                } else if (lvl1 == 8) {\
                        info_ctr.info.info ## lvl1 = &info8; \
                }\
                devmode_ctr.devmode = q.out.info->info ## lvl1.devmode; \
                devmode_ctr.devmode->field1 = value; \
-               TESTSETCALL(SetPrinter, s) \
-               TESTGETCALL(GetPrinter, q) \
-               INT_EQUAL(q.out.info->info ## lvl1.devmode->field1, exp_value, field1); \
-               q.in.level = lvl2; \
-               TESTGETCALL(GetPrinter, q) \
-               INT_EQUAL(q.out.info->info ## lvl2.devmode->field2, exp_value, field1); \
+               TESTSETCALL_EXP(SetPrinter, s, expected_result) \
+               if (W_ERROR_IS_OK(expected_result)) { \
+                       TESTGETCALL(GetPrinter, q) \
+                       INT_EQUAL(q.out.info->info ## lvl1.devmode->field1, exp_value, field1); \
+                       q.in.level = lvl2; \
+                       TESTGETCALL(GetPrinter, q) \
+                       INT_EQUAL(q.out.info->info ## lvl2.devmode->field2, exp_value, field1); \
+               }\
        } while (0)
 
+#define TEST_DEVMODE_INT_EXP(lvl1, field1, lvl2, field2, value, expected_result) do { \
+        TEST_DEVMODE_INT_EXP_RESULT(lvl1, field1, lvl2, field2, value, value, expected_result); \
+        } while (0)
+
 #define TEST_DEVMODE_INT(lvl1, field1, lvl2, field2, value) do { \
-        TEST_DEVMODE_INT_EXP(lvl1, field1, lvl2, field2, value, value); \
+        TEST_DEVMODE_INT_EXP_RESULT(lvl1, field1, lvl2, field2, value, value, WERR_OK); \
         } while (0)
 
        ZERO_STRUCT(devmode_ctr);
@@ -2170,16 +2222,27 @@ static bool test_devicemode_full(struct torture_context *tctx,
 
        q.in.handle = handle;
        q.out.info = &info;
-       q0 = q;
 
 #if 0
        const char *devicename;/* [charset(UTF16)] */
        enum spoolss_DeviceModeSpecVersion specversion;
        uint16_t driverversion;
-       uint16_t size;
        uint16_t __driverextra_length;/* [value(r->driverextra_data.length)] */
        uint32_t fields;
 #endif
+       TEST_DEVMODE_INT_EXP(8, size,           8, size, __LINE__, WERR_INVALID_PARAM);
+       TEST_DEVMODE_INT_EXP(8, size,           8, size, 0, WERR_INVALID_PARAM);
+       TEST_DEVMODE_INT_EXP(8, size,           8, size, 0xffff, WERR_INVALID_PARAM);
+       TEST_DEVMODE_INT_EXP(8, size,           8, size, ndr_size_spoolss_DeviceMode(devmode_ctr.devmode, 0), (devmode_ctr.devmode->__driverextra_length > 0 ) ? WERR_INVALID_PARAM : WERR_OK);
+       TEST_DEVMODE_INT(8, size,               8, size, ndr_size_spoolss_DeviceMode(devmode_ctr.devmode, 0) - devmode_ctr.devmode->__driverextra_length);
+
+       devmode_ctr.devmode->driverextra_data = data_blob_string_const("foobar");
+       torture_assert(tctx,
+               test_devmode_set_level(tctx, b, handle, 8, devmode_ctr.devmode),
+               "failed to set devmode");
+
+       TEST_DEVMODE_INT_EXP(8, size,           8, size, ndr_size_spoolss_DeviceMode(devmode_ctr.devmode, 0), (devmode_ctr.devmode->__driverextra_length > 0 ) ? WERR_INVALID_PARAM : WERR_OK);
+       TEST_DEVMODE_INT(8, size,               8, size, ndr_size_spoolss_DeviceMode(devmode_ctr.devmode, 0) - devmode_ctr.devmode->__driverextra_length);
 
        TEST_DEVMODE_INT(8, orientation,        8, orientation, __LINE__);
        TEST_DEVMODE_INT(8, papersize,          8, papersize, __LINE__);
@@ -2238,10 +2301,20 @@ static bool test_PrinterInfo_DevModes(struct torture_context *tctx,
 
        devmode = info.info8.devmode;
 
+       if (devmode && devmode->size == 0) {
+               torture_fail(tctx,
+                       "devmode of zero size!");
+       }
+
        torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), "");
 
        devmode2 = info.info2.devmode;
 
+       if (devmode2 && devmode2->size == 0) {
+               torture_fail(tctx,
+                       "devmode of zero size!");
+       }
+
        torture_assert(tctx, test_devicemode_equal(tctx, devmode, devmode2),
                "DM level 8 != DM level 2");
 
@@ -2383,6 +2456,11 @@ static bool test_PrinterInfo_DevMode(struct torture_context *tctx,
 
        devmode = info.info8.devmode;
 
+       if (devmode && devmode->size == 0) {
+               torture_fail(tctx,
+                       "devmode of zero size!");
+       }
+
        if (addprinter_devmode) {
                if (!test_devicemode_equal(tctx, devmode, addprinter_devmode)) {
                        torture_warning(tctx, "current global DM is != DM provided in addprinter");
@@ -2631,18 +2709,21 @@ static bool test_AddForm(struct torture_context *tctx,
                         WERROR expected_result)
 {
        struct spoolss_AddForm r;
+       struct spoolss_AddFormInfoCtr info_ctr;
+
+       info_ctr.level = level;
+       info_ctr.info = *info;
 
        if (level != 1) {
                torture_skip(tctx, "only level 1 supported");
        }
 
        r.in.handle     = handle;
-       r.in.level      = level;
-       r.in.info       = *info;
+       r.in.info_ctr   = &info_ctr;
 
        torture_comment(tctx, "Testing AddForm(%s) level %d, type %d\n",
-               r.in.info.info1->form_name, r.in.level,
-               r.in.info.info1->flags);
+               r.in.info_ctr->info.info1->form_name, level,
+               r.in.info_ctr->info.info1->flags);
 
        torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_AddForm_r(b, tctx, &r),
                "AddForm failed");
@@ -2670,14 +2751,17 @@ static bool test_SetForm(struct torture_context *tctx,
                         union spoolss_AddFormInfo *info)
 {
        struct spoolss_SetForm r;
+       struct spoolss_AddFormInfoCtr info_ctr;
+
+       info_ctr.level  = level;
+       info_ctr.info   = *info;
 
        r.in.handle     = handle;
        r.in.form_name  = form_name;
-       r.in.level      = level;
-       r.in.info       = *info;
+       r.in.info_ctr   = &info_ctr;
 
        torture_comment(tctx, "Testing SetForm(%s) level %d\n",
-               form_name, r.in.level);
+               form_name, level);
 
        torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_SetForm_r(b, tctx, &r),
                "SetForm failed");
@@ -3063,6 +3147,7 @@ static bool test_GetJob_args(struct torture_context *tctx,
        return true;
 }
 
+#if 0
 static bool test_GetJob(struct torture_context *tctx,
                        struct dcerpc_binding_handle *b,
                        struct policy_handle *handle,
@@ -3079,6 +3164,7 @@ static bool test_GetJob(struct torture_context *tctx,
 
        return true;
 }
+#endif
 
 static bool test_SetJob(struct torture_context *tctx,
                        struct dcerpc_binding_handle *b,
@@ -3152,11 +3238,13 @@ static bool test_AddJob(struct torture_context *tctx,
        torture_comment(tctx, "Testing AddJob\n");
 
        status = dcerpc_spoolss_AddJob_r(b, tctx, &r);
+       torture_assert_ntstatus_ok(tctx, status, "AddJob failed");
        torture_assert_werr_equal(tctx, r.out.result, WERR_UNKNOWN_LEVEL, "AddJob failed");
 
        r.in.level = 1;
 
        status = dcerpc_spoolss_AddJob_r(b, tctx, &r);
+       torture_assert_ntstatus_ok(tctx, status, "AddJob failed");
        torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAM, "AddJob failed");
 
        return true;
@@ -3167,6 +3255,7 @@ static bool test_EnumJobs_args(struct torture_context *tctx,
                               struct dcerpc_binding_handle *b,
                               struct policy_handle *handle,
                               uint32_t level,
+                              WERROR werr_expected,
                               uint32_t *count_p,
                               union spoolss_JobInfo **info_p)
 {
@@ -3200,13 +3289,15 @@ static bool test_EnumJobs_args(struct torture_context *tctx,
                status = dcerpc_spoolss_EnumJobs_r(b, tctx, &r);
 
                torture_assert_ntstatus_ok(tctx, status, "EnumJobs failed");
-               torture_assert_werr_ok(tctx, r.out.result, "EnumJobs failed");
+               torture_assert_werr_equal(tctx, r.out.result, werr_expected,
+                                         "EnumJobs failed");
                torture_assert(tctx, info, "No jobs returned");
 
                CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumJobs, *r.out.info, r.in.level, count, needed, 4);
 
        } else {
-               torture_assert_werr_ok(tctx, r.out.result, "EnumJobs failed");
+               torture_assert_werr_equal(tctx, r.out.result, werr_expected,
+                                         "EnumJobs failed");
        }
 
        if (count_p) {
@@ -3219,13 +3310,116 @@ static bool test_EnumJobs_args(struct torture_context *tctx,
        return true;
 }
 
-static bool test_DoPrintTest_add_one_job(struct torture_context *tctx,
+static bool test_JobPropertiesEnum(struct torture_context *tctx,
+                                  struct dcerpc_binding_handle *b,
+                                  struct policy_handle *handle,
+                                  uint32_t job_id)
+{
+       struct spoolss_RpcEnumJobNamedProperties r;
+       uint32_t pcProperties = 0;
+       struct RPC_PrintNamedProperty *ppProperties = NULL;
+
+       r.in.hPrinter = handle;
+       r.in.JobId = job_id;
+       r.out.pcProperties = &pcProperties;
+       r.out.ppProperties = &ppProperties;
+
+       torture_comment(tctx, "Testing RpcEnumJobNamedProperties(%d)\n", job_id);
+
+       torture_assert_ntstatus_ok(tctx,
+               dcerpc_spoolss_RpcEnumJobNamedProperties_r(b, tctx, &r),
+               "spoolss_RpcEnumJobNamedProperties failed");
+       torture_assert_werr_ok(tctx, r.out.result,
+               "spoolss_RpcEnumJobNamedProperties failed");
+
+       return true;
+}
+
+static bool test_JobPropertySet(struct torture_context *tctx,
+                               struct dcerpc_binding_handle *b,
+                               struct policy_handle *handle,
+                               uint32_t job_id,
+                               struct RPC_PrintNamedProperty *property)
+{
+       struct spoolss_RpcSetJobNamedProperty r;
+
+       r.in.hPrinter = handle;
+       r.in.JobId = job_id;
+       r.in.pProperty = property;
+
+       torture_comment(tctx, "Testing RpcSetJobNamedProperty(%d) %s - %d\n",
+               job_id, property->propertyName,
+               property->propertyValue.ePropertyType);
+
+       torture_assert_ntstatus_ok(tctx,
+               dcerpc_spoolss_RpcSetJobNamedProperty_r(b, tctx, &r),
+               "spoolss_RpcSetJobNamedProperty failed");
+       torture_assert_werr_ok(tctx, r.out.result,
+               "spoolss_RpcSetJobNamedProperty failed");
+
+       return true;
+}
+
+static bool test_JobPropertyGetValue(struct torture_context *tctx,
+                                    struct dcerpc_binding_handle *b,
+                                    struct policy_handle *handle,
+                                    uint32_t job_id,
+                                    const char *property_name,
+                                    struct RPC_PrintPropertyValue *value)
+{
+       struct spoolss_RpcGetJobNamedPropertyValue r;
+
+       r.in.hPrinter = handle;
+       r.in.JobId = job_id;
+       r.in.pszName = property_name;
+       r.out.pValue = value;
+
+       torture_comment(tctx, "Testing RpcGetJobNamedPropertyValue(%d) %s\n",
+               job_id, property_name);
+
+       torture_assert_ntstatus_ok(tctx,
+               dcerpc_spoolss_RpcGetJobNamedPropertyValue_r(b, tctx, &r),
+               "spoolss_RpcGetJobNamedPropertyValue failed");
+       torture_assert_werr_ok(tctx, r.out.result,
+               "spoolss_RpcGetJobNamedPropertyValue failed");
+
+       return true;
+}
+
+static bool test_JobPropertyDelete(struct torture_context *tctx,
+                                  struct dcerpc_binding_handle *b,
+                                  struct policy_handle *handle,
+                                  uint32_t job_id,
+                                  const char *property_name)
+{
+       struct spoolss_RpcDeleteJobNamedProperty r;
+
+       r.in.hPrinter = handle;
+       r.in.JobId = job_id;
+       r.in.pszName = property_name;
+
+       torture_comment(tctx, "Testing RpcDeleteJobNamedProperty(%d) %s\n",
+               job_id, property_name);
+
+       torture_assert_ntstatus_ok(tctx,
+               dcerpc_spoolss_RpcDeleteJobNamedProperty_r(b, tctx, &r),
+               "spoolss_RpcDeleteJobNamedProperty failed");
+       torture_assert_werr_ok(tctx, r.out.result,
+               "spoolss_RpcDeleteJobNamedProperty failed");
+
+       return true;
+}
+
+static bool test_DoPrintTest_add_one_job_common(struct torture_context *tctx,
                                         struct dcerpc_binding_handle *b,
                                         struct policy_handle *handle,
+                                        const char *document_name,
+                                        const char *datatype,
                                         uint32_t *job_id)
 {
        NTSTATUS status;
        struct spoolss_StartDocPrinter s;
+       struct spoolss_DocumentInfoCtr info_ctr;
        struct spoolss_DocumentInfo1 info1;
        struct spoolss_StartPagePrinter sp;
        struct spoolss_WritePrinter w;
@@ -3237,18 +3431,24 @@ static bool test_DoPrintTest_add_one_job(struct torture_context *tctx,
        torture_comment(tctx, "Testing StartDocPrinter\n");
 
        s.in.handle             = handle;
-       s.in.level              = 1;
-       s.in.info.info1         = &info1;
+       s.in.info_ctr           = &info_ctr;
        s.out.job_id            = job_id;
-       info1.document_name     = "TorturePrintJob";
+
+       info1.document_name     = document_name;
        info1.output_file       = NULL;
-       info1.datatype          = "RAW";
+       info1.datatype          = datatype;
+
+       info_ctr.level          = 1;
+       info_ctr.info.info1     = &info1;
 
        status = dcerpc_spoolss_StartDocPrinter_r(b, tctx, &s);
        torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_StartDocPrinter failed");
        torture_assert_werr_ok(tctx, s.out.result, "StartDocPrinter failed");
 
        for (i=1; i < 4; i++) {
+               union spoolss_JobInfo ginfo;
+               bool ok;
+
                torture_comment(tctx, "Testing StartPagePrinter: Page[%d], JobId[%d]\n", i, *job_id);
 
                sp.in.handle            = handle;
@@ -3258,6 +3458,11 @@ static bool test_DoPrintTest_add_one_job(struct torture_context *tctx,
                                           "dcerpc_spoolss_StartPagePrinter failed");
                torture_assert_werr_ok(tctx, sp.out.result, "StartPagePrinter failed");
 
+               ok = test_GetJob_args(tctx, b, handle, *job_id, 1, &ginfo);
+               if (!ok) {
+                       torture_comment(tctx, "test_GetJob failed for JobId[%d]\n", *job_id);
+               }
+
                torture_comment(tctx, "Testing WritePrinter: Page[%d], JobId[%d]\n", i, *job_id);
 
                w.in.handle             = handle;
@@ -3288,6 +3493,29 @@ static bool test_DoPrintTest_add_one_job(struct torture_context *tctx,
        return true;
 }
 
+static bool test_DoPrintTest_add_one_job(struct torture_context *tctx,
+                                        struct dcerpc_binding_handle *b,
+                                        struct policy_handle *handle,
+                                        const char *document_name,
+                                        uint32_t *job_id)
+{
+       test_DoPrintTest_add_one_job_common(tctx, b, handle, document_name, "RAW", job_id);
+
+       return true;
+}
+
+static bool test_DoPrintTest_add_one_job_v4(struct torture_context *tctx,
+                                        struct dcerpc_binding_handle *b,
+                                        struct policy_handle *handle,
+                                        const char *document_name,
+                                        uint32_t *job_id)
+{
+       test_DoPrintTest_add_one_job_common(tctx, b, handle, document_name, "XPS_PASS", job_id);
+
+       return true;
+}
+
+
 static bool test_DoPrintTest_check_jobs(struct torture_context *tctx,
                                        struct dcerpc_binding_handle *b,
                                        struct policy_handle *handle,
@@ -3303,7 +3531,7 @@ static bool test_DoPrintTest_check_jobs(struct torture_context *tctx,
                "AddJob failed");
 
        torture_assert(tctx,
-               test_EnumJobs_args(tctx, b, handle, 1, &count, &info),
+               test_EnumJobs_args(tctx, b, handle, 1, WERR_OK, &count, &info),
                "EnumJobs level 1 failed");
 
        torture_assert_int_equal(tctx, count, num_jobs, "unexpected number of jobs in queue");
@@ -3383,7 +3611,15 @@ static bool test_DoPrintTest(struct torture_context *tctx,
        job_ids = talloc_zero_array(tctx, uint32_t, num_jobs);
 
        for (i=0; i < num_jobs; i++) {
-               ret &= test_DoPrintTest_add_one_job(tctx, b, handle, &job_ids[i]);
+               ret &= test_DoPrintTest_add_one_job(tctx, b, handle, "TorturePrintJob", &job_ids[i]);
+       }
+
+       for (i=0; i < num_jobs; i++) {
+               ret &= test_SetJob(tctx, b, handle, job_ids[i], NULL, SPOOLSS_JOB_CONTROL_DELETE);
+       }
+
+       for (i=0; i < num_jobs; i++) {
+               ret &= test_DoPrintTest_add_one_job_v4(tctx, b, handle, "TorturePrintJob v4", &job_ids[i]);
        }
 
        for (i=0; i < num_jobs; i++) {
@@ -3410,7 +3646,7 @@ static bool test_DoPrintTest_extended(struct torture_context *tctx,
        job_ids = talloc_zero_array(tctx, uint32_t, num_jobs);
 
        for (i=0; i < num_jobs; i++) {
-               ret &= test_DoPrintTest_add_one_job(tctx, b, handle, &job_ids[i]);
+               ret &= test_DoPrintTest_add_one_job(tctx, b, handle, "TorturePrintJob", &job_ids[i]);
        }
 
        ret &= test_DoPrintTest_check_jobs(tctx, b, handle, num_jobs, job_ids);
@@ -3426,6 +3662,170 @@ static bool test_DoPrintTest_extended(struct torture_context *tctx,
        return ret;
 }
 
+static bool test_JobPrintProperties_equal(struct torture_context *tctx,
+                                         struct RPC_PrintPropertyValue *got,
+                                         struct RPC_PrintNamedProperty *exp)
+{
+       torture_assert_int_equal(tctx,
+                                got->ePropertyType,
+                                exp->propertyValue.ePropertyType,
+                                "ePropertyType");
+
+       switch (exp->propertyValue.ePropertyType) {
+       case kRpcPropertyTypeString:
+               torture_assert_str_equal(tctx,
+                                        got->value.propertyString,
+                                        exp->propertyValue.value.propertyString,
+                                        "propertyString");
+               break;
+       case kRpcPropertyTypeInt32:
+               torture_assert_int_equal(tctx,
+                                        got->value.propertyInt32,
+                                        exp->propertyValue.value.propertyInt32,
+                                        "propertyInt32");
+               break;
+       case kRpcPropertyTypeInt64:
+               torture_assert_u64_equal(tctx,
+                                        got->value.propertyInt64,
+                                        exp->propertyValue.value.propertyInt64,
+                                        "propertyInt64");
+               break;
+       case kRpcPropertyTypeByte:
+               torture_assert_int_equal(tctx,
+                                        got->value.propertyByte,
+                                        exp->propertyValue.value.propertyByte,
+                                        "propertyByte");
+               break;
+       case kRpcPropertyTypeBuffer:
+               torture_assert_int_equal(tctx,
+                                        got->value.propertyBlob.cbBuf,
+                                        exp->propertyValue.value.propertyBlob.cbBuf,
+                                        "propertyBlob.cbBuf");
+               torture_assert_mem_equal(tctx,
+                                        got->value.propertyBlob.pBuf,
+                                        exp->propertyValue.value.propertyBlob.pBuf,
+                                        exp->propertyValue.value.propertyBlob.cbBuf,
+                                        "propertyBlob.pBuf");
+
+               break;
+
+       }
+
+       return true;
+}
+
+static bool test_JobPrintProperties(struct torture_context *tctx,
+                                   struct dcerpc_binding_handle *b,
+                                   struct policy_handle *handle,
+                                   uint32_t job_id)
+{
+       struct RPC_PrintNamedProperty in;
+       struct RPC_PrintPropertyValue out;
+       int i;
+       DATA_BLOB blob = data_blob_string_const("blob");
+       struct {
+               const char *property_name;
+               enum RPC_EPrintPropertyType type;
+               union RPC_PrintPropertyValueUnion value;
+               WERROR expected_result;
+       } tests[] = {
+               {
+                       .property_name                  = "torture_property_string",
+                       .type                           = kRpcPropertyTypeString,
+                       .value.propertyString           = "torture_property_value_string",
+               },{
+                       .property_name                  = "torture_property_int32",
+                       .type                           = kRpcPropertyTypeInt32,
+                       .value.propertyInt32            = 42,
+               },{
+                       .property_name                  = "torture_property_int64",
+                       .type                           = kRpcPropertyTypeInt64,
+                       .value.propertyInt64            = 0xaffe,
+               },{
+                       .property_name                  = "torture_property_byte",
+                       .type                           = kRpcPropertyTypeByte,
+                       .value.propertyByte             = 0xab,
+               },{
+                       .property_name                  = "torture_property_buffer",
+                       .type                           = kRpcPropertyTypeBuffer,
+                       .value.propertyBlob.cbBuf       = blob.length,
+                       .value.propertyBlob.pBuf        = blob.data,
+               }
+       };
+
+       torture_assert(tctx,
+               test_JobPropertiesEnum(tctx, b, handle, job_id),
+               "failed to enum properties");
+
+       for (i=0; i <ARRAY_SIZE(tests); i++) {
+
+               in.propertyName                 = tests[i].property_name;
+               in.propertyValue.ePropertyType  = tests[i].type;
+               in.propertyValue.value          = tests[i].value;
+
+               torture_assert(tctx,
+                       test_JobPropertySet(tctx, b, handle, job_id, &in),
+                       "failed to set property");
+
+               torture_assert(tctx,
+                       test_JobPropertyGetValue(tctx, b, handle, job_id, in.propertyName, &out),
+                       "failed to get property");
+
+               torture_assert(tctx,
+                       test_JobPrintProperties_equal(tctx, &out, &in),
+                       "property unequal");
+
+               torture_assert(tctx,
+                       test_JobPropertiesEnum(tctx, b, handle, job_id),
+                       "failed to enum properties");
+
+               torture_assert(tctx,
+                       test_JobPropertyDelete(tctx, b, handle, job_id, in.propertyName),
+                       "failed to delete job property");
+       }
+
+       torture_assert(tctx,
+               test_JobPropertiesEnum(tctx, b, handle, job_id),
+               "failed to enum properties");
+
+       return true;
+}
+
+static bool test_DoPrintTest_properties(struct torture_context *tctx,
+                                       struct dcerpc_binding_handle *b,
+                                       struct policy_handle *handle)
+{
+       uint32_t num_jobs = 8;
+       uint32_t *job_ids;
+       int i;
+       torture_comment(tctx, "Testing real print operations (properties)\n");
+
+       job_ids = talloc_zero_array(tctx, uint32_t, num_jobs);
+
+       for (i=0; i < num_jobs; i++) {
+               torture_assert(tctx,
+                       test_DoPrintTest_add_one_job(tctx, b, handle, "TorturePrintJob", &job_ids[i]),
+                       "failed to create print job");
+       }
+
+       for (i=0; i < num_jobs; i++) {
+               torture_assert(tctx,
+                       test_JobPrintProperties(tctx, b, handle, job_ids[i]),
+                       "failed to test job properties");
+       }
+
+
+       for (i=0; i < num_jobs; i++) {
+               torture_assert(tctx,
+                       test_SetJob(tctx, b, handle, job_ids[i], NULL, SPOOLSS_JOB_CONTROL_DELETE),
+                       "failed to delete printjob");
+       }
+
+       torture_comment(tctx, "real print operations (properties) test succeeded\n\n");
+
+       return true;
+}
+
 static bool test_PausePrinter(struct torture_context *tctx,
                              struct dcerpc_binding_handle *b,
                              struct policy_handle *handle)
@@ -3492,6 +3892,37 @@ static bool test_ResumePrinter(struct torture_context *tctx,
        return true;
 }
 
+static bool test_printer_purge(struct torture_context *tctx,
+                              struct dcerpc_binding_handle *b,
+                              struct policy_handle *handle)
+{
+       NTSTATUS status;
+       struct spoolss_SetPrinter r;
+       struct spoolss_SetPrinterInfoCtr info_ctr;
+       struct spoolss_DevmodeContainer devmode_ctr;
+       struct sec_desc_buf secdesc_ctr;
+
+       info_ctr.level = 0;
+       info_ctr.info.info0 = NULL;
+
+       ZERO_STRUCT(devmode_ctr);
+       ZERO_STRUCT(secdesc_ctr);
+
+       r.in.handle             = handle;
+       r.in.info_ctr           = &info_ctr;
+       r.in.devmode_ctr        = &devmode_ctr;
+       r.in.secdesc_ctr        = &secdesc_ctr;
+       r.in.command            = SPOOLSS_PRINTER_CONTROL_PURGE;
+
+       torture_comment(tctx, "Testing SetPrinter: SPOOLSS_PRINTER_CONTROL_PURGE\n");
+
+       status = dcerpc_spoolss_SetPrinter_r(b, tctx, &r);
+       torture_assert_ntstatus_ok(tctx, status, "SetPrinter failed");
+       torture_assert_werr_ok(tctx, r.out.result, "SetPrinter failed");
+
+       return true;
+}
+
 static bool test_GetPrinterData_checktype(struct torture_context *tctx,
                                          struct dcerpc_binding_handle *b,
                                          struct policy_handle *handle,
@@ -4369,11 +4800,8 @@ do {\
                talloc_asprintf(tctx, "%s - %s mismatch", #wname, #iname));\
 } while(0);
 
-#define test_dm(wname, iname) \
+#define test_binary(wname, iname) \
 do {\
-       DATA_BLOB blob;\
-       struct spoolss_DeviceMode dm;\
-       enum ndr_err_code ndr_err;\
        enum winreg_Type w_type;\
        uint32_t w_size;\
        uint32_t w_length;\
@@ -4383,11 +4811,31 @@ do {\
                                       &w_type, &w_size, &w_length, &w_data),\
                "failed to query winreg");\
        torture_assert_int_equal(tctx, w_type, REG_BINARY, "unexpected type");\
-       blob = data_blob(w_data, w_size);\
-       ndr_err = ndr_pull_struct_blob(&blob, tctx, &dm,\
-               (ndr_pull_flags_fn_t)ndr_pull_spoolss_DeviceMode);\
-       torture_assert_ndr_success(tctx, ndr_err, "failed to unmarshall dm");\
-       torture_assert(tctx, test_devicemode_equal(tctx, &dm, iname),\
+       torture_assert_int_equal(tctx, w_size, iname.length, "unexpected length");\
+       torture_assert_mem_equal(tctx, w_data, iname.data, w_size, \
+               "binary unequal");\
+} while(0);
+
+
+#define test_dm(wname, iname) \
+do {\
+       DATA_BLOB blob;\
+       struct spoolss_DeviceMode dm;\
+       enum ndr_err_code ndr_err;\
+       enum winreg_Type w_type;\
+       uint32_t w_size;\
+       uint32_t w_length;\
+       uint8_t *w_data;\
+       torture_assert(tctx,\
+               test_winreg_QueryValue(tctx, winreg_handle, &key_handle, wname,\
+                                      &w_type, &w_size, &w_length, &w_data),\
+               "failed to query winreg");\
+       torture_assert_int_equal(tctx, w_type, REG_BINARY, "unexpected type");\
+       blob = data_blob(w_data, w_size);\
+       ndr_err = ndr_pull_struct_blob(&blob, tctx, &dm,\
+               (ndr_pull_flags_fn_t)ndr_pull_spoolss_DeviceMode);\
+       torture_assert_ndr_success(tctx, ndr_err, "failed to unmarshall dm");\
+       torture_assert(tctx, test_devicemode_equal(tctx, &dm, iname),\
                "dm unequal");\
 } while(0);
 
@@ -4575,6 +5023,11 @@ static const char *driver_winreg_date(TALLOC_CTX *mem_ctx, NTTIME nt)
 {
        time_t t;
        struct tm *tm;
+
+       if (nt == 0) {
+               return talloc_strdup(mem_ctx, "01/01/1601");
+       }
+
        t = nt_time_to_unix(nt);
        tm = localtime(&t);
 
@@ -4602,7 +5055,7 @@ static bool test_GetDriverInfo_winreg(struct torture_context *tctx,
                                      struct policy_handle *hive_handle,
                                      const char *server_name_slash)
 {
-       WERROR result;
+       WERROR result = WERR_OK;
        union spoolss_DriverInfo info;
        const char *driver_key;
        struct policy_handle key_handle;
@@ -4619,6 +5072,8 @@ static bool test_GetDriverInfo_winreg(struct torture_context *tctx,
        const char *driver_version;
        const char *inbox_driver_version;
 
+       ZERO_STRUCT(key_handle);
+
        torture_comment(tctx, "Testing Driver Info and winreg consistency\n");
 
        driver_key = talloc_asprintf(tctx, "%s\\%s\\Drivers\\Version-%d\\%s",
@@ -4631,11 +5086,8 @@ static bool test_GetDriverInfo_winreg(struct torture_context *tctx,
                test_winreg_OpenKey(tctx, winreg_handle, hive_handle, driver_key, &key_handle),
                "failed to open driver key");
 
-       if (torture_setting_bool(tctx, "samba3", false)) {
-               goto try_level3;
-       }
-
-       if (torture_setting_bool(tctx, "w2k3", false)) {
+       if (torture_setting_bool(tctx, "samba3", false) ||
+           torture_setting_bool(tctx, "w2k3", false)) {
                goto try_level6;
        }
 
@@ -4717,8 +5169,16 @@ static bool test_GetDriverInfo_winreg(struct torture_context *tctx,
        test_sz("Data File",                    data_file);
        test_sz("Datatype",                     info.info6.default_datatype);
        test_sz("Driver",                       driver_path);
-       test_sz("DriverDate",                   driver_date);
-       test_sz("DriverVersion",                driver_version);
+       if (torture_setting_bool(tctx, "w2k3", false)) {
+               DATA_BLOB blob = data_blob_talloc_zero(tctx, 8);
+               push_nttime(blob.data, 0, info.info6.driver_date);
+               test_binary("DriverDate",       blob);
+               SBVAL(blob.data, 0, info.info6.driver_version);
+               test_binary("DriverVersion",    blob);
+       } else {
+               test_sz("DriverDate",           driver_date);
+               test_sz("DriverVersion",        driver_version);
+       }
        test_sz("HardwareID",                   info.info6.hardware_id);
        test_sz("Help File",                    help_file);
        test_sz("Manufacturer",                 info.info6.manufacturer_name);
@@ -4731,8 +5191,6 @@ static bool test_GetDriverInfo_winreg(struct torture_context *tctx,
        test_dword("Version",                   info.info6.version);
 /*     test_dword("TempDir",                   ?); */
 
- try_level3:
-
        if (handle) {
                torture_assert(tctx,
                        test_GetPrinterDriver2_level(tctx, b, handle, driver_name, environment, 3, version, 0, &info, &result),
@@ -5437,6 +5895,7 @@ do {\
 
        TEST_SET_SZ("description", comment, "newval");
        TEST_SET_SZ("location", location, "newval");
+       TEST_SET_SZ("driverName", drivername, "newval");
 /*     TEST_SET_DWORD("priority", priority, 25); */
 
        torture_assert(tctx,
@@ -5479,6 +5938,106 @@ static bool test_print_processors_winreg(struct torture_context *tctx,
        return test_PrintProcessors_winreg(tctx, b, ctx->environment);
 }
 
+static bool test_AddPrintProcessor(struct torture_context *tctx,
+                                  struct dcerpc_binding_handle *b,
+                                  const char *environment,
+                                  const char *path_name,
+                                  const char *print_processor_name,
+                                  WERROR expected_error)
+{
+       struct spoolss_AddPrintProcessor r;
+
+       r.in.server = NULL;
+       r.in.architecture = environment;
+       r.in.path_name = path_name;
+       r.in.print_processor_name = print_processor_name;
+
+       torture_comment(tctx, "Testing AddPrintProcessor(%s)\n",
+               print_processor_name);
+
+       torture_assert_ntstatus_ok(tctx,
+               dcerpc_spoolss_AddPrintProcessor_r(b, tctx, &r),
+               "spoolss_AddPrintProcessor failed");
+       torture_assert_werr_equal(tctx, r.out.result, expected_error,
+               "spoolss_AddPrintProcessor failed");
+
+       return true;
+}
+
+static bool test_DeletePrintProcessor(struct torture_context *tctx,
+                                     struct dcerpc_binding_handle *b,
+                                     const char *environment,
+                                     const char *print_processor_name,
+                                     WERROR expected_error)
+{
+       struct spoolss_DeletePrintProcessor r;
+
+       r.in.server = NULL;
+       r.in.architecture = environment;
+       r.in.print_processor_name = print_processor_name;
+
+       torture_comment(tctx, "Testing DeletePrintProcessor(%s)\n",
+               print_processor_name);
+
+       torture_assert_ntstatus_ok(tctx,
+               dcerpc_spoolss_DeletePrintProcessor_r(b, tctx, &r),
+               "spoolss_DeletePrintProcessor failed");
+       torture_assert_werr_equal(tctx, r.out.result, expected_error,
+               "spoolss_DeletePrintProcessor failed");
+
+       return true;
+}
+
+static bool test_add_print_processor(struct torture_context *tctx,
+                                    void *private_data)
+{
+       struct test_spoolss_context *ctx =
+               talloc_get_type_abort(private_data, struct test_spoolss_context);
+       struct dcerpc_pipe *p = ctx->spoolss_pipe;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       int i;
+
+       struct {
+               const char *environment;
+               const char *path_name;
+               const char *print_processor_name;
+               WERROR expected_add_result;
+               WERROR expected_del_result;
+       } tests[] = {
+               {
+                       .environment            = ctx->environment,
+                       .path_name              = "",
+                       .print_processor_name   = "winprint",
+                       .expected_add_result    = WERR_PRINT_PROCESSOR_ALREADY_INSTALLED,
+                       .expected_del_result    = WERR_CAN_NOT_COMPLETE
+               },{
+                       .environment            = ctx->environment,
+                       .path_name              = "",
+                       .print_processor_name   = "unknown",
+                       .expected_add_result    = WERR_MOD_NOT_FOUND,
+                       .expected_del_result    = WERR_UNKNOWN_PRINTPROCESSOR
+               }
+       };
+
+       for (i=0; i < ARRAY_SIZE(tests); i++) {
+               torture_assert(tctx,
+                       test_AddPrintProcessor(tctx, b,
+                                              tests[i].environment,
+                                              tests[i].path_name,
+                                              tests[i].print_processor_name,
+                                              tests[i].expected_add_result),
+                       "add print processor failed");
+               torture_assert(tctx,
+                       test_DeletePrintProcessor(tctx, b,
+                                                 tests[i].environment,
+                                                 tests[i].print_processor_name,
+                                                 tests[i].expected_del_result),
+                       "delete print processor failed");
+       }
+
+       return true;
+}
+
 static bool test_GetChangeID_PrinterData(struct torture_context *tctx,
                                         struct dcerpc_binding_handle *b,
                                         struct policy_handle *handle,
@@ -5648,7 +6207,7 @@ static bool test_SecondaryClosePrinter(struct torture_context *tctx,
                                       struct policy_handle *handle)
 {
        NTSTATUS status;
-       struct dcerpc_binding *b;
+       const struct dcerpc_binding *binding2;
        struct dcerpc_pipe *p2;
        struct spoolss_ClosePrinter cp;
 
@@ -5659,10 +6218,8 @@ static bool test_SecondaryClosePrinter(struct torture_context *tctx,
 
        torture_comment(tctx, "Testing close on secondary pipe\n");
 
-       status = dcerpc_parse_binding(tctx, p->conn->binding_string, &b);
-       torture_assert_ntstatus_ok(tctx, status, "Failed to parse dcerpc binding");
-
-       status = dcerpc_secondary_connection(p, &p2, b);
+       binding2 = p->binding;
+       status = dcerpc_secondary_connection(p, &p2, binding2);
        torture_assert_ntstatus_ok(tctx, status, "Failed to create secondary connection");
 
        status = dcerpc_bind_auth_none(p2, &ndr_table_spoolss);
@@ -5710,8 +6267,8 @@ static bool test_OpenPrinter_badname(struct torture_context *tctx,
        opEx.in.datatype                = NULL;
        opEx.in.devmode_ctr.devmode     = NULL;
        opEx.in.access_mask             = 0;
-       opEx.in.level                   = 1;
-       opEx.in.userlevel.level1        = NULL;
+       opEx.in.userlevel_ctr.level             = 1;
+       opEx.in.userlevel_ctr.user_info.level1 = NULL;
        opEx.out.handle                 = &handle;
 
        torture_comment(tctx, "Testing OpenPrinterEx(%s) with bad name\n", opEx.in.printername);
@@ -5820,8 +6377,7 @@ static bool test_OpenPrinterEx(struct torture_context *tctx,
                               const char *datatype,
                               struct spoolss_DeviceMode *devmode,
                               uint32_t access_mask,
-                              uint32_t level,
-                              union spoolss_UserLevel *userlevel,
+                              struct spoolss_UserLevelCtr *userlevel_ctr,
                               struct policy_handle *handle,
                               WERROR expected_result)
 {
@@ -5831,8 +6387,7 @@ static bool test_OpenPrinterEx(struct torture_context *tctx,
        r.in.datatype           = datatype;
        r.in.devmode_ctr.devmode= devmode;
        r.in.access_mask        = access_mask;
-       r.in.level              = level;
-       r.in.userlevel          = *userlevel;
+       r.in.userlevel_ctr      = *userlevel_ctr;
        r.out.handle            = handle;
 
        torture_comment(tctx, "Testing OpenPrinterEx(%s)\n", r.in.printername);
@@ -5853,7 +6408,7 @@ static bool call_OpenPrinterEx(struct torture_context *tctx,
                               struct spoolss_DeviceMode *devmode,
                               struct policy_handle *handle)
 {
-       union spoolss_UserLevel userlevel;
+       struct spoolss_UserLevelCtr userlevel_ctr;
        struct spoolss_UserLevel1 userlevel1;
        struct dcerpc_binding_handle *b = p->binding_handle;
 
@@ -5865,12 +6420,12 @@ static bool call_OpenPrinterEx(struct torture_context *tctx,
        userlevel1.minor = 3;
        userlevel1.processor = 4;
 
-       userlevel.level1 = &userlevel1;
+       userlevel_ctr.level = 1;
+       userlevel_ctr.user_info.level1 = &userlevel1;
 
        return test_OpenPrinterEx(tctx, b, name, NULL, devmode,
                                  SEC_FLAG_MAXIMUM_ALLOWED,
-                                 1,
-                                 &userlevel,
+                                 &userlevel_ctr,
                                  handle,
                                  WERR_OK);
 }
@@ -5974,7 +6529,7 @@ static bool test_openprinter(struct torture_context *tctx,
                             struct dcerpc_binding_handle *b,
                             const char *real_printername)
 {
-       union spoolss_UserLevel userlevel;
+       struct spoolss_UserLevelCtr userlevel_ctr;
        struct policy_handle handle;
        struct spoolss_UserLevel1 userlevel1;
        const char *printername = NULL;
@@ -6052,13 +6607,14 @@ static bool test_openprinter(struct torture_context *tctx,
        userlevel1.minor = 3;
        userlevel1.processor = 4;
 
-       userlevel.level1 = &userlevel1;
+       userlevel_ctr.level = 1;
+       userlevel_ctr.user_info.level1 = &userlevel1;
 
        torture_comment(tctx, "Testing openprinterex printername pattern\n");
 
        torture_assert(tctx,
-               test_OpenPrinterEx(tctx, b, real_printername, NULL, NULL, 0, 1,
-                                  &userlevel, &handle,
+               test_OpenPrinterEx(tctx, b, real_printername, NULL, NULL, 0,
+                                  &userlevel_ctr, &handle,
                                   WERR_OK),
                "OpenPrinterEx failed");
        test_ClosePrinter(tctx, b, &handle);
@@ -6070,8 +6626,8 @@ static bool test_openprinter(struct torture_context *tctx,
                                              tests[i].suffix);
 
                torture_assert(tctx,
-                       test_OpenPrinterEx(tctx, b, printername, NULL, NULL, 0, 1,
-                                          &userlevel, &handle,
+                       test_OpenPrinterEx(tctx, b, printername, NULL, NULL, 0,
+                                          &userlevel_ctr, &handle,
                                           tests[i].expected_result),
                        "OpenPrinterEx failed");
                if (W_ERROR_IS_OK(tests[i].expected_result)) {
@@ -6457,7 +7013,7 @@ static bool test_EnumPrinters_servername(struct torture_context *tctx,
        return true;
 }
 
-
+#if 0
 static bool test_GetPrinterDriver(struct torture_context *tctx,
                                  struct dcerpc_binding_handle *b,
                                  struct policy_handle *handle,
@@ -6492,6 +7048,7 @@ static bool test_GetPrinterDriver(struct torture_context *tctx,
 
        return true;
 }
+#endif
 
 static bool test_GetPrinterDriver2_level(struct torture_context *tctx,
                                         struct dcerpc_binding_handle *b,
@@ -7192,6 +7749,7 @@ static bool compose_local_driver_directory(struct torture_context *tctx,
        return true;
 }
 
+#if 0
 static struct spoolss_DeviceMode *torture_devicemode(TALLOC_CTX *mem_ctx,
                                                     const char *devicename)
 {
@@ -7232,6 +7790,7 @@ static struct spoolss_DeviceMode *torture_devicemode(TALLOC_CTX *mem_ctx,
 
        return r;
 }
+#endif
 
 static bool test_architecture_buffer(struct torture_context *tctx,
                                     void *private_data)
@@ -7268,8 +7827,8 @@ static bool test_architecture_buffer(struct torture_context *tctx,
                r.in.datatype           = NULL;
                r.in.devmode_ctr.devmode= NULL;
                r.in.access_mask        = SEC_FLAG_MAXIMUM_ALLOWED;
-               r.in.level               = 1;
-               r.in.userlevel.level1   = &u1;
+               r.in.userlevel_ctr.level = 1;
+               r.in.userlevel_ctr.user_info.level1 = &u1;
                r.out.handle            = &handle;
 
                torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_OpenPrinterEx_r(b, tctx, &r), "");
@@ -7424,7 +7983,6 @@ static bool torture_rpc_spoolss_printer_setup_common(struct torture_context *tct
 
        t->driver.local.driver_directory= "/usr/share/cups/drivers";
 
-       t->info2.drivername             = "Microsoft XPS Document Writer";
        t->info2.portname               = "LPT1:";
 
        printer_name = t->info2.printername;
@@ -7442,6 +8000,8 @@ static bool torture_rpc_spoolss_printer_setup_common(struct torture_context *tct
                                               &t->driver.local.driver_directory),
                "failed to compose local driver directory");
 
+       t->info2.drivername             = "Microsoft XPS Document Writer";
+
        if (test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, t->driver.remote.environment, 3, t->info2.drivername, NULL)) {
                torture_comment(tctx, "driver '%s' (architecture: %s, version: 3) is present on server\n",
                        t->info2.drivername, t->driver.remote.environment);
@@ -7451,6 +8011,16 @@ static bool torture_rpc_spoolss_printer_setup_common(struct torture_context *tct
 
        torture_comment(tctx, "driver '%s' (architecture: %s, version: 3) does not exist on the server\n",
                t->info2.drivername, t->driver.remote.environment);
+
+       t->info2.drivername             = "Microsoft XPS Document Writer v4";
+
+       if (test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, t->driver.remote.environment, 3, t->info2.drivername, NULL)) {
+               torture_comment(tctx, "driver '%s' (architecture: %s, version: 4) is present on server\n",
+                       t->info2.drivername, t->driver.remote.environment);
+               t->have_driver = true;
+               goto try_add;
+       }
+
        torture_comment(tctx, "trying to upload own driver\n");
 
        if (!directory_exist(t->driver.local.driver_directory)) {
@@ -7525,8 +8095,9 @@ static bool torture_rpc_spoolss_printerwkn_setup(struct torture_context *tctx, v
        t->info2.printername    = TORTURE_WELLKNOWN_PRINTER;
        t->devmode              = NULL;
 
-       if (t->wellknown && torture_setting_bool(tctx, "samba3", false)) {
-               torture_skip(tctx, "skipping AddPrinter level 1 against samba");
+       /* FIXME */
+       if (t->wellknown) {
+               torture_skip(tctx, "skipping AddPrinter level 1");
        }
 
        return torture_rpc_spoolss_printer_setup_common(tctx, t);
@@ -7543,13 +8114,15 @@ static bool torture_rpc_spoolss_printerexwkn_setup(struct torture_context *tctx,
        t->info2.printername    = TORTURE_WELLKNOWN_PRINTER_EX;
        t->devmode              = NULL;
 
-       if (t->wellknown && torture_setting_bool(tctx, "samba3", false)) {
-               torture_skip(tctx, "skipping AddPrinter level 1 against samba");
+       /* FIXME */
+       if (t->wellknown) {
+               torture_skip(tctx, "skipping AddPrinterEx level 1");
        }
 
        return torture_rpc_spoolss_printer_setup_common(tctx, t);
 }
 
+#if 0
 static bool torture_rpc_spoolss_printerdm_setup(struct torture_context *tctx, void **data)
 {
        struct torture_printer_context *t;
@@ -7563,6 +8136,7 @@ static bool torture_rpc_spoolss_printerdm_setup(struct torture_context *tctx, vo
 
        return torture_rpc_spoolss_printer_setup_common(tctx, t);
 }
+#endif
 
 static bool torture_rpc_spoolss_printer_teardown_common(struct torture_context *tctx, struct torture_printer_context *t)
 {
@@ -7577,11 +8151,9 @@ static bool torture_rpc_spoolss_printer_teardown_common(struct torture_context *
                        "failed to remove printer driver");
        }
 
-       if (p) {
+       if (p && !t->wellknown) {
                b = p->binding_handle;
-       }
 
-       if (!t->wellknown) {
                torture_assert(tctx,
                        test_DeletePrinter(tctx, b, &t->handle),
                        "failed to delete printer");
@@ -7661,6 +8233,163 @@ static bool test_print_test_extended(struct torture_context *tctx,
        return ret;
 }
 
+static bool test_print_test_properties(struct torture_context *tctx,
+                                      void *private_data)
+{
+       struct torture_printer_context *t =
+               (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context);
+       struct dcerpc_pipe *p = t->spoolss_pipe;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+
+       if (torture_setting_bool(tctx, "samba3", false)) {
+               torture_skip(tctx, "skip printer job property tests against samba");
+       }
+
+       torture_assert(tctx,
+               test_PausePrinter(tctx, b, &t->handle),
+               "failed to pause printer");
+
+       torture_assert(tctx,
+               test_DoPrintTest_properties(tctx, b, &t->handle),
+               "failed to test print job properties");
+
+       torture_assert(tctx,
+               test_ResumePrinter(tctx, b, &t->handle),
+               "failed to resume printer");
+
+       return true;
+}
+
+/* use smbd file IO to spool a print job */
+static bool test_print_test_smbd(struct torture_context *tctx,
+                                void *private_data)
+{
+       struct torture_printer_context *t =
+               (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context);
+       struct dcerpc_pipe *p = t->spoolss_pipe;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       NTSTATUS status;
+       uint32_t count;
+       union spoolss_JobInfo *info = NULL;
+       int i;
+
+       struct smb2_tree *tree;
+       struct smb2_handle job_h;
+       struct cli_credentials *credentials = cmdline_credentials;
+       struct smbcli_options options;
+       TALLOC_CTX *mem_ctx = talloc_new(tctx);
+       /*
+        * Do not test against the dynamically added printers, printing via
+        * smbd means that a different spoolss process may handle the
+        * OpenPrinter request to the one that handled the AddPrinter request.
+        * This currently leads to an ugly race condition where one process
+        * sees the new printer and one doesn't.
+        */
+       const char *share = TORTURE_PRINTER_STATIC1;
+
+       torture_comment(tctx, "Testing smbd job spooling\n");
+       lpcfg_smbcli_options(tctx->lp_ctx, &options);
+
+       status = smb2_connect(mem_ctx,
+                             torture_setting_string(tctx, "host", NULL),
+                             lpcfg_smb_ports(tctx->lp_ctx),
+                             share,
+                             lpcfg_resolve_context(tctx->lp_ctx),
+                             credentials,
+                             &tree,
+                             tctx->ev,
+                             &options,
+                             lpcfg_socket_options(tctx->lp_ctx),
+                             lpcfg_gensec_settings(tctx, tctx->lp_ctx));
+       if (!NT_STATUS_IS_OK(status)) {
+               printf("Failed to connect to SMB2 printer %s - %s\n",
+                      share, nt_errstr(status));
+               return false;
+       }
+
+       status = torture_smb2_testfile(tree, "smbd_spooler_job", &job_h);
+       torture_assert_ntstatus_ok(tctx, status, "smbd spool job create");
+
+       status = smb2_util_write(tree, job_h, "exciting print job data", 0,
+                                sizeof("exciting print job data"));
+       torture_assert_ntstatus_ok(tctx, status, "smbd spool job write");
+
+       /* check back end spoolss job was created */
+       torture_assert(tctx,
+               test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK,
+                                  &count, &info),
+               "EnumJobs level 1 failed");
+
+       for (i = 0; i < count; i++) {
+               if (!strcmp(info[i].info1.document_name, "smbd_spooler_job")) {
+                       break;
+               }
+       }
+       torture_assert(tctx, (i != count), "smbd_spooler_job not found");
+
+       status = smb2_util_close(tree, job_h);
+       torture_assert_ntstatus_ok(tctx, status, "smbd spool job close");
+
+       /* disconnect from printer share */
+       talloc_free(mem_ctx);
+
+       return true;
+}
+
+static bool test_print_test_purge(struct torture_context *tctx,
+                                 void *private_data)
+{
+       struct torture_printer_context *t =
+          (struct torture_printer_context *)talloc_get_type_abort(private_data,
+                                               struct torture_printer_context);
+       struct dcerpc_pipe *p = t->spoolss_pipe;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       uint32_t num_jobs = 8;
+       uint32_t *job_ids;
+       int i;
+       bool ret = true;
+       uint32_t count;
+       union spoolss_JobInfo *info;
+
+       torture_assert(tctx,
+               test_PausePrinter(tctx, b, &t->handle),
+               "failed to pause printer");
+
+       job_ids = talloc_zero_array(tctx, uint32_t, num_jobs);
+       for (i=0; i < num_jobs; i++) {
+               ret = test_DoPrintTest_add_one_job(tctx, b, &t->handle,
+                                                  "TorturePrintJob",
+                                                  &job_ids[i]);
+               torture_assert(tctx, ret, "failed to add print job");
+       }
+
+       torture_assert(tctx,
+               test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK,
+                                  &count, &info),
+               "EnumJobs level 1 failed");
+
+       torture_assert_int_equal(tctx, count, num_jobs,
+                                "unexpected number of jobs in queue");
+
+       torture_assert(tctx,
+               test_printer_purge(tctx, b, &t->handle),
+               "failed to purge printer");
+
+       torture_assert(tctx,
+               test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK,
+                                  &count, &info),
+               "EnumJobs level 1 failed");
+
+       torture_assert_int_equal(tctx, count, 0,
+                                "unexpected number of jobs in queue");
+
+       torture_assert(tctx,
+               test_ResumePrinter(tctx, b, &t->handle),
+               "failed to resume printer");
+
+       return true;
+}
+
 static bool test_printer_sd(struct torture_context *tctx,
                            void *private_data)
 {
@@ -7817,55 +8546,435 @@ static bool test_printer_data_dsspooler(struct torture_context *tctx,
        return true;
 }
 
-static bool test_driver_info_winreg(struct torture_context *tctx,
-                                   void *private_data)
+static bool test_printer_ic(struct torture_context *tctx,
+                           void *private_data)
 {
        struct torture_printer_context *t =
-               (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context);
+               talloc_get_type_abort(private_data,
+                                     struct torture_printer_context);
        struct dcerpc_pipe *p = t->spoolss_pipe;
-       const char *driver_name = t->added_driver ? t->driver.info8.driver_name : t->info2.drivername;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       struct policy_handle gdi_handle;
 
-       if (!t->have_driver) {
-               torture_skip(tctx, "skipping driver info winreg test as we don't have a driver");
+       if (torture_setting_bool(tctx, "samba3", false)) {
+               torture_skip(tctx, "skip printer information context tests against samba");
        }
 
-       torture_assert(tctx,
-               test_DriverInfo_winreg(tctx, p, &t->handle, t->info2.printername, driver_name, t->driver.remote.environment, 3),
-               "failed to test driver info winreg");
+       {
+               struct spoolss_CreatePrinterIC r;
+               struct spoolss_DevmodeContainer devmode_ctr;
 
-       return true;
-}
+               ZERO_STRUCT(devmode_ctr);
 
-void torture_tcase_printer(struct torture_tcase *tcase)
-{
-       torture_tcase_add_simple_test(tcase, "openprinter", test_openprinter_wrap);
-       torture_tcase_add_simple_test(tcase, "csetprinter", test_csetprinter);
-       torture_tcase_add_simple_test(tcase, "print_test", test_print_test);
-       torture_tcase_add_simple_test(tcase, "print_test_extended", test_print_test_extended);
-       torture_tcase_add_simple_test(tcase, "printer_info", test_printer_info);
-       torture_tcase_add_simple_test(tcase, "sd", test_printer_sd);
-       torture_tcase_add_simple_test(tcase, "dm", test_printer_dm);
-       torture_tcase_add_simple_test(tcase, "printer_info_winreg", test_printer_info_winreg);
-       torture_tcase_add_simple_test(tcase, "change_id", test_printer_change_id);
-       torture_tcase_add_simple_test(tcase, "keys", test_printer_keys);
-       torture_tcase_add_simple_test(tcase, "printerdata_consistency", test_printer_data_consistency);
-       torture_tcase_add_simple_test(tcase, "printerdata_keys", test_printer_data_keys);
-       torture_tcase_add_simple_test(tcase, "printerdata_values", test_printer_data_values);
-       torture_tcase_add_simple_test(tcase, "printerdata_set", test_printer_data_set);
-       torture_tcase_add_simple_test(tcase, "printerdata_winreg", test_printer_data_winreg);
-       torture_tcase_add_simple_test(tcase, "printerdata_dsspooler", test_printer_data_dsspooler);
-       torture_tcase_add_simple_test(tcase, "driver_info_winreg", test_driver_info_winreg);
-       torture_tcase_add_simple_test(tcase, "printer_rename", test_printer_rename);
-}
+               r.in.handle = &t->handle;
+               r.in.devmode_ctr = &devmode_ctr;
+               r.out.gdi_handle = &gdi_handle;
 
-struct torture_suite *torture_rpc_spoolss_printer(TALLOC_CTX *mem_ctx)
-{
-       struct torture_suite *suite = torture_suite_create(mem_ctx, "printer");
-       struct torture_tcase *tcase;
+               torture_assert_ntstatus_ok(tctx,
+                       dcerpc_spoolss_CreatePrinterIC_r(b, tctx, &r),
+                       "CreatePrinterIC failed");
+               torture_assert_werr_ok(tctx, r.out.result,
+                       "CreatePrinterIC failed");
+       }
 
-       tcase = torture_suite_add_tcase(suite, "addprinter");
+       {
+               struct spoolss_PlayGDIScriptOnPrinterIC r;
+               DATA_BLOB in,out;
+               int i;
+               uint32_t num_fonts = 0;
 
-       torture_tcase_set_fixture(tcase,
+               in = data_blob_string_const("");
+
+               r.in.gdi_handle = &gdi_handle;
+               r.in.pIn = in.data;
+               r.in.cIn = in.length;
+               r.in.ul = 0;
+
+               for (i = 0; i < 4; i++) {
+
+                       out = data_blob_talloc_zero(tctx, i);
+
+                       r.in.cOut = out.length;
+                       r.out.pOut = out.data;
+
+                       torture_assert_ntstatus_ok(tctx,
+                               dcerpc_spoolss_PlayGDIScriptOnPrinterIC_r(b, tctx, &r),
+                               "PlayGDIScriptOnPrinterIC failed");
+                       torture_assert_werr_equal(tctx, r.out.result, WERR_NOMEM,
+                               "PlayGDIScriptOnPrinterIC failed");
+               }
+
+               out = data_blob_talloc_zero(tctx, 4);
+
+               r.in.cOut = out.length;
+               r.out.pOut = out.data;
+
+               torture_assert_ntstatus_ok(tctx,
+                       dcerpc_spoolss_PlayGDIScriptOnPrinterIC_r(b, tctx, &r),
+                       "PlayGDIScriptOnPrinterIC failed");
+               torture_assert_werr_equal(tctx, r.out.result, WERR_OK,
+                       "PlayGDIScriptOnPrinterIC failed");
+
+               /* now we should have the required length, so retry with a
+                * buffer which is large enough to carry all font ids */
+
+               num_fonts = IVAL(r.out.pOut, 0);
+
+               torture_comment(tctx, "PlayGDIScriptOnPrinterIC gave font count of %d\n", num_fonts);
+
+               out = data_blob_talloc_zero(tctx,
+                       num_fonts * sizeof(struct UNIVERSAL_FONT_ID) + 4);
+
+               r.in.cOut = out.length;
+               r.out.pOut = out.data;
+
+               torture_assert_ntstatus_ok(tctx,
+                       dcerpc_spoolss_PlayGDIScriptOnPrinterIC_r(b, tctx, &r),
+                       "PlayGDIScriptOnPrinterIC failed");
+               torture_assert_werr_equal(tctx, r.out.result, WERR_OK,
+                       "PlayGDIScriptOnPrinterIC failed");
+
+       }
+
+       {
+               struct spoolss_DeletePrinterIC r;
+
+               r.in.gdi_handle = &gdi_handle;
+               r.out.gdi_handle = &gdi_handle;
+
+               torture_assert_ntstatus_ok(tctx,
+                       dcerpc_spoolss_DeletePrinterIC_r(b, tctx, &r),
+                       "DeletePrinterIC failed");
+               torture_assert_werr_ok(tctx, r.out.result,
+                       "DeletePrinterIC failed");
+
+       }
+
+       return true;
+}
+
+static bool test_printer_bidi(struct torture_context *tctx,
+                             void *private_data)
+{
+       struct torture_printer_context *t =
+               talloc_get_type_abort(private_data,
+                                     struct torture_printer_context);
+       struct dcerpc_pipe *p = t->spoolss_pipe;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       struct spoolss_RpcSendRecvBidiData r;
+       struct RPC_BIDI_REQUEST_CONTAINER bidi_req;
+       struct RPC_BIDI_RESPONSE_CONTAINER *bidi_rep = NULL;
+
+       if (torture_setting_bool(tctx, "samba3", false)) {
+               torture_skip(tctx, "skip printer bidirectional tests against samba");
+       }
+
+       ZERO_STRUCT(bidi_req);
+
+       r.in.hPrinter = t->handle;
+       r.in.pAction = "foobar";
+       r.in.pReqData = &bidi_req;
+       r.out.ppRespData = &bidi_rep;
+
+       torture_assert_ntstatus_ok(tctx,
+               dcerpc_spoolss_RpcSendRecvBidiData_r(b, tctx, &r),
+               "RpcSendRecvBidiData failed");
+       torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED,
+               "RpcSendRecvBidiData failed");
+
+       if (!(t->info2.attributes & PRINTER_ATTRIBUTE_ENABLE_BIDI)) {
+               torture_skip(tctx, "skipping further tests as printer is not BIDI enabled");
+       }
+
+       r.in.pAction = BIDI_ACTION_ENUM_SCHEMA;
+
+       torture_assert_ntstatus_ok(tctx,
+               dcerpc_spoolss_RpcSendRecvBidiData_r(b, tctx, &r),
+               "RpcSendRecvBidiData failed");
+       torture_assert_werr_ok(tctx, r.out.result,
+               "RpcSendRecvBidiData failed");
+
+       return true;
+}
+
+static bool test_printer_set_publish(struct torture_context *tctx,
+                                      struct dcerpc_binding_handle *b,
+                                      struct policy_handle *handle)
+{
+       union spoolss_PrinterInfo info;
+       struct spoolss_SetPrinterInfo7 info7;
+       struct spoolss_SetPrinterInfoCtr info_ctr;
+       struct spoolss_DevmodeContainer devmode_ctr;
+       struct sec_desc_buf secdesc_ctr;
+
+       info7.guid = "";
+       info7.action = DSPRINT_PUBLISH;
+
+       ZERO_STRUCT(info_ctr);
+       ZERO_STRUCT(devmode_ctr);
+       ZERO_STRUCT(secdesc_ctr);
+       info_ctr.level = 7;
+       info_ctr.info.info7 = &info7;
+
+       torture_assert(tctx,
+                      test_SetPrinter(tctx, b, handle, &info_ctr,
+                                      &devmode_ctr, &secdesc_ctr, 0), "");
+
+       torture_assert(tctx,
+                      test_GetPrinter_level(tctx, b, handle, 2, &info),
+                      "");
+       torture_assert(tctx,
+                      (info.info2.attributes & PRINTER_ATTRIBUTE_PUBLISHED),
+                      "info2 publish flag not set");
+       torture_assert(tctx,
+                      test_GetPrinter_level(tctx, b, handle, 7, &info),
+                      "");
+       if (info.info7.action & DSPRINT_PENDING) {
+               torture_comment(tctx, "publish is pending\n");
+               torture_assert_int_equal(tctx,
+                                        info.info7.action,
+                                        (DSPRINT_PENDING | DSPRINT_PUBLISH),
+                                        "info7 publish flag not set");
+       } else {
+               struct GUID guid;
+               torture_assert_int_equal(tctx,
+                                        info.info7.action,
+                                        DSPRINT_PUBLISH,
+                                        "info7 publish flag not set");
+               torture_assert_ntstatus_ok(tctx,
+                                          GUID_from_string(info.info7.guid,
+                                          &guid),
+                                          "invalid published printer GUID");
+       }
+
+       return true;
+}
+
+static bool test_printer_set_unpublish(struct torture_context *tctx,
+                                      struct dcerpc_binding_handle *b,
+                                      struct policy_handle *handle)
+{
+       union spoolss_PrinterInfo info;
+       struct spoolss_SetPrinterInfo7 info7;
+       struct spoolss_SetPrinterInfoCtr info_ctr;
+       struct spoolss_DevmodeContainer devmode_ctr;
+       struct sec_desc_buf secdesc_ctr;
+
+       info7.action = DSPRINT_UNPUBLISH;
+       info7.guid = "";
+
+       ZERO_STRUCT(info_ctr);
+       ZERO_STRUCT(devmode_ctr);
+       ZERO_STRUCT(secdesc_ctr);
+       info_ctr.level = 7;
+       info_ctr.info.info7 = &info7;
+
+       torture_assert(tctx,
+                      test_SetPrinter(tctx, b, handle, &info_ctr,
+                                      &devmode_ctr, &secdesc_ctr, 0), "");
+
+       torture_assert(tctx,
+                      test_GetPrinter_level(tctx, b, handle, 2, &info),
+                      "");
+       torture_assert(tctx,
+                      !(info.info2.attributes & PRINTER_ATTRIBUTE_PUBLISHED),
+                      "info2 publish flag still set");
+       torture_assert(tctx,
+                      test_GetPrinter_level(tctx, b, handle, 7, &info),
+                      "");
+
+       if (info.info7.action & DSPRINT_PENDING) {
+               struct GUID guid;
+               torture_comment(tctx, "unpublish is pending\n");
+               torture_assert_int_equal(tctx,
+                                        info.info7.action,
+                                        (DSPRINT_PENDING | DSPRINT_UNPUBLISH),
+                                        "info7 unpublish flag not set");
+               torture_assert_ntstatus_ok(tctx,
+                                          GUID_from_string(info.info7.guid,
+                                          &guid),
+                                          "invalid printer GUID");
+       } else {
+               torture_assert_int_equal(tctx,
+                                        info.info7.action, DSPRINT_UNPUBLISH,
+                                        "info7 unpublish flag not set");
+       }
+
+       return true;
+}
+
+static bool test_printer_publish_toggle(struct torture_context *tctx,
+                                          void *private_data)
+{
+       struct torture_printer_context *t =
+               talloc_get_type_abort(private_data,
+                                     struct torture_printer_context);
+       struct dcerpc_pipe *p = t->spoolss_pipe;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       struct policy_handle *handle = &t->handle;
+       union spoolss_PrinterInfo info7;
+       union spoolss_PrinterInfo info2;
+
+       /* check publish status via level 7 and level 2 */
+       torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 7, &info7),
+                      "");
+       torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info2),
+                      "");
+
+       if (info2.info2.attributes & PRINTER_ATTRIBUTE_PUBLISHED) {
+               torture_assert_int_equal(tctx,
+                                        info7.info7.action, DSPRINT_PUBLISH,
+                                        "info7 publish flag not set");
+               torture_assert(tctx, test_printer_set_unpublish(tctx, b, handle), "");
+               torture_assert(tctx, test_printer_set_publish(tctx, b, handle), "");
+       } else {
+               torture_assert_int_equal(tctx,
+                                        info7.info7.action, DSPRINT_UNPUBLISH,
+                                        "info7 unpublish flag not set");
+               torture_assert(tctx, test_printer_set_publish(tctx, b, handle), "");
+               torture_assert(tctx, test_printer_set_unpublish(tctx, b, handle), "");
+       }
+
+       return true;
+}
+
+static bool test_driver_info_winreg(struct torture_context *tctx,
+                                   void *private_data)
+{
+       struct torture_printer_context *t =
+               (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context);
+       struct dcerpc_pipe *p = t->spoolss_pipe;
+       const char *driver_name = t->added_driver ? t->driver.info8.driver_name : t->info2.drivername;
+
+       if (!t->have_driver) {
+               torture_skip(tctx, "skipping driver info winreg test as we don't have a driver");
+       }
+
+       torture_assert(tctx,
+               test_DriverInfo_winreg(tctx, p, &t->handle, t->info2.printername, driver_name, t->driver.remote.environment, 3),
+               "failed to test driver info winreg");
+
+       return true;
+}
+
+static bool test_print_job_enum(struct torture_context *tctx,
+                               void *private_data)
+{
+       struct torture_printer_context *t =
+               (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context);
+       struct dcerpc_pipe *p = t->spoolss_pipe;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       bool ret = true;
+       uint32_t num_jobs = 8;
+       uint32_t *job_ids;
+       int i;
+       union spoolss_JobInfo *info = NULL;
+       uint32_t count;
+
+       torture_assert(tctx,
+               test_PausePrinter(tctx, b, &t->handle),
+               "failed to pause printer");
+
+       /* purge in case of any jobs from previous tests */
+       torture_assert(tctx,
+               test_printer_purge(tctx, b, &t->handle),
+               "failed to purge printer");
+
+       /* enum before jobs, valid level */
+       torture_assert(tctx,
+                      test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK,
+                                         &count, &info),
+                      "EnumJobs with valid level");
+       torture_assert_int_equal(tctx, count, 0, "EnumJobs count");
+       torture_assert(tctx,
+                      test_EnumJobs_args(tctx, b, &t->handle, 2, WERR_OK,
+                                         &count, &info),
+                      "EnumJobs with valid level");
+       torture_assert_int_equal(tctx, count, 0, "EnumJobs count");
+       /* enum before jobs, invalid level - expect failure */
+       torture_assert(tctx,
+                      test_EnumJobs_args(tctx, b, &t->handle, 100,
+                                         WERR_INVALID_LEVEL,
+                                         &count, &info),
+                      "EnumJobs with invalid level");
+
+       job_ids = talloc_zero_array(tctx, uint32_t, num_jobs);
+
+       for (i = 0; i < num_jobs; i++) {
+               ret = test_DoPrintTest_add_one_job(tctx, b, &t->handle,
+                                                   "TorturePrintJob",
+                                                   &job_ids[i]);
+               torture_assert(tctx, ret, "failed to add print job");
+       }
+
+       /* enum after jobs, valid level */
+       torture_assert(tctx,
+                      test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK,
+                                         &count, &info),
+                      "EnumJobs with valid level");
+       torture_assert_int_equal(tctx, count, num_jobs, "EnumJobs count");
+       torture_assert(tctx,
+                      test_EnumJobs_args(tctx, b, &t->handle, 2, WERR_OK,
+                                         &count, &info),
+                      "EnumJobs with valid level");
+       torture_assert_int_equal(tctx, count, num_jobs, "EnumJobs count");
+       /* enum after jobs, invalid level - expect failure */
+       torture_assert(tctx,
+                      test_EnumJobs_args(tctx, b, &t->handle, 100,
+                                         WERR_INVALID_LEVEL,
+                                         &count, &info),
+                      "EnumJobs with invalid level");
+
+       for (i = 0; i < num_jobs; i++) {
+               test_SetJob(tctx, b, &t->handle, job_ids[i], NULL,
+                           SPOOLSS_JOB_CONTROL_DELETE);
+       }
+
+       torture_assert(tctx,
+               test_ResumePrinter(tctx, b, &t->handle),
+               "failed to resume printer");
+
+       return true;
+}
+
+void torture_tcase_printer(struct torture_tcase *tcase)
+{
+       torture_tcase_add_simple_test(tcase, "openprinter", test_openprinter_wrap);
+       torture_tcase_add_simple_test(tcase, "csetprinter", test_csetprinter);
+       torture_tcase_add_simple_test(tcase, "print_test", test_print_test);
+       torture_tcase_add_simple_test(tcase, "print_test_extended", test_print_test_extended);
+       torture_tcase_add_simple_test(tcase, "print_test_smbd", test_print_test_smbd);
+       torture_tcase_add_simple_test(tcase, "print_test_properties", test_print_test_properties);
+       torture_tcase_add_simple_test(tcase, "print_test_purge", test_print_test_purge);
+       torture_tcase_add_simple_test(tcase, "printer_info", test_printer_info);
+       torture_tcase_add_simple_test(tcase, "sd", test_printer_sd);
+       torture_tcase_add_simple_test(tcase, "dm", test_printer_dm);
+       torture_tcase_add_simple_test(tcase, "printer_info_winreg", test_printer_info_winreg);
+       torture_tcase_add_simple_test(tcase, "change_id", test_printer_change_id);
+       torture_tcase_add_simple_test(tcase, "keys", test_printer_keys);
+       torture_tcase_add_simple_test(tcase, "printerdata_consistency", test_printer_data_consistency);
+       torture_tcase_add_simple_test(tcase, "printerdata_keys", test_printer_data_keys);
+       torture_tcase_add_simple_test(tcase, "printerdata_values", test_printer_data_values);
+       torture_tcase_add_simple_test(tcase, "printerdata_set", test_printer_data_set);
+       torture_tcase_add_simple_test(tcase, "printerdata_winreg", test_printer_data_winreg);
+       torture_tcase_add_simple_test(tcase, "printerdata_dsspooler", test_printer_data_dsspooler);
+       torture_tcase_add_simple_test(tcase, "driver_info_winreg", test_driver_info_winreg);
+       torture_tcase_add_simple_test(tcase, "printer_rename", test_printer_rename);
+       torture_tcase_add_simple_test(tcase, "printer_ic", test_printer_ic);
+       torture_tcase_add_simple_test(tcase, "bidi", test_printer_bidi);
+       torture_tcase_add_simple_test(tcase, "publish_toggle",
+                                     test_printer_publish_toggle);
+       torture_tcase_add_simple_test(tcase, "print_job_enum", test_print_job_enum);
+}
+
+struct torture_suite *torture_rpc_spoolss_printer(TALLOC_CTX *mem_ctx)
+{
+       struct torture_suite *suite = torture_suite_create(mem_ctx, "printer");
+       struct torture_tcase *tcase;
+
+       tcase = torture_suite_add_tcase(suite, "addprinter");
+
+       torture_tcase_set_fixture(tcase,
                                  torture_rpc_spoolss_printer_setup,
                                  torture_rpc_spoolss_printer_teardown);
 
@@ -7926,6 +9035,7 @@ struct torture_suite *torture_rpc_spoolss(TALLOC_CTX *mem_ctx)
        torture_tcase_add_simple_test(tcase, "enum_monitors", test_EnumMonitors);
        torture_tcase_add_simple_test(tcase, "enum_print_processors", test_EnumPrintProcessors);
        torture_tcase_add_simple_test(tcase, "print_processors_winreg", test_print_processors_winreg);
+       torture_tcase_add_simple_test(tcase, "add_processor", test_add_print_processor);
        torture_tcase_add_simple_test(tcase, "enum_printprocdata", test_EnumPrintProcDataTypes);
        torture_tcase_add_simple_test(tcase, "enum_printers", test_EnumPrinters);
        torture_tcase_add_simple_test(tcase, "enum_ports_old", test_EnumPorts_old);
@@ -8421,6 +9531,9 @@ static bool test_AddPrinterDriver_args_level_6(struct torture_context *tctx,
                }
        }
 
+       torture_assert_nttime_equal(tctx, info.info6.driver_date, info6.driver_date, "driverdate mismatch");
+       torture_assert_u64_equal(tctx, info.info6.driver_version, info6.driver_version, "driverversion mismatch");
+
        return true;
 }
 
@@ -8469,6 +9582,9 @@ static bool test_AddPrinterDriver_args_level_8(struct torture_context *tctx,
                }
        }
 
+       torture_assert_nttime_equal(tctx, info.info8.driver_date, r->driver_date, "driverdate mismatch");
+       torture_assert_u64_equal(tctx, info.info8.driver_version, r->driver_version, "driverversion mismatch");
+
        return true;
 }
 
@@ -8748,6 +9864,7 @@ static bool upload_printer_driver_file(struct torture_context *tctx,
 
        buf = talloc_array(tctx, uint8_t, maxwrite);
        if (!buf) {
+               x_fclose(f);
                return false;
        }
 
@@ -8854,6 +9971,84 @@ static bool upload_printer_driver(struct torture_context *tctx,
        return true;
 }
 
+static bool check_printer_driver_file(struct torture_context *tctx,
+                                     struct smbcli_state *cli,
+                                     struct torture_driver_context *d,
+                                     const char *file_name)
+{
+       const char *remote_arch_dir = driver_directory_dir(d->remote.driver_directory);
+       const char *remote_name = talloc_asprintf(tctx, "%s\\%d\\%s",
+                                                 remote_arch_dir,
+                                                 d->info8.version,
+                                                 file_name);
+       int fnum;
+
+       torture_assert(tctx, (file_name && strlen(file_name) != 0), "invalid filename");
+
+       torture_comment(tctx, "checking for driver file at %s\n", remote_name);
+
+       fnum = smbcli_open(cli->tree, remote_name, O_RDONLY, DENY_NONE);
+       if (fnum == -1) {
+               return false;
+       }
+
+       torture_assert_ntstatus_ok(tctx,
+               smbcli_close(cli->tree, fnum),
+               "failed to close driver file");
+
+       return true;
+}
+
+static bool check_printer_driver_files(struct torture_context *tctx,
+                                      const char *server_name,
+                                      struct torture_driver_context *d,
+                                      bool expect_exist)
+{
+       struct smbcli_state *cli;
+       const char *share_name = driver_directory_share(tctx, d->remote.driver_directory);
+       int i;
+
+       torture_assert(tctx,
+               connect_printer_driver_share(tctx, server_name, share_name, &cli),
+               "failed to connect to driver share");
+
+       torture_comment(tctx, "checking %sexistent driver files at \\\\%s\\%s\n",
+                       (expect_exist ? "": "non-"),
+                       server_name, share_name);
+
+       if (d->info8.driver_path && d->info8.driver_path[0]) {
+               torture_assert(tctx,
+                       check_printer_driver_file(tctx, cli, d, d->info8.driver_path) == expect_exist,
+                       "failed driver_path check");
+       }
+       if (d->info8.data_file && d->info8.data_file[0]) {
+               torture_assert(tctx,
+                       check_printer_driver_file(tctx, cli, d, d->info8.data_file) == expect_exist,
+                       "failed data_file check");
+       }
+       if (d->info8.config_file && d->info8.config_file[0]) {
+               torture_assert(tctx,
+                       check_printer_driver_file(tctx, cli, d, d->info8.config_file) == expect_exist,
+                       "failed config_file check");
+       }
+       if (d->info8.help_file && d->info8.help_file[0]) {
+               torture_assert(tctx,
+                       check_printer_driver_file(tctx, cli, d, d->info8.help_file) == expect_exist,
+                       "failed help_file check");
+       }
+       if (d->info8.dependent_files) {
+               for (i=0; d->info8.dependent_files->string && d->info8.dependent_files->string[i] != NULL; i++) {
+                       torture_assert(tctx,
+                               check_printer_driver_file(tctx, cli, d, d->info8.dependent_files->string[i]) == expect_exist,
+                               "failed dependent_files check");
+               }
+       }
+
+       talloc_free(cli);
+
+       return true;
+}
+
 static bool remove_printer_driver_file(struct torture_context *tctx,
                                       struct smbcli_state *cli,
                                       struct torture_driver_context *d,
@@ -8956,12 +10151,19 @@ static bool test_add_driver_arg(struct torture_context *tctx,
                upload_printer_driver(tctx, dcerpc_server_name(p), d),
                "failed to upload printer driver");
 
-       info8.version           = d->info8.version;
-       info8.driver_name       = d->info8.driver_name;
-       info8.architecture      = d->local.environment;
-       info8.driver_path       = d->info8.driver_path;
-       info8.data_file         = d->info8.data_file;
-       info8.config_file       = d->info8.config_file;
+       info8 = d->info8;
+       if (d->info8.dependent_files) {
+               info8.dependent_files = talloc_zero(tctx, struct spoolss_StringArray);
+               if (d->info8.dependent_files->string) {
+                       for (i=0; d->info8.dependent_files->string[i] != NULL; i++) {
+                       }
+                       info8.dependent_files->string = talloc_zero_array(info8.dependent_files, const char *, i+1);
+                       for (i=0; d->info8.dependent_files->string[i] != NULL; i++) {
+                               info8.dependent_files->string[i] = talloc_strdup(info8.dependent_files->string, d->info8.dependent_files->string[i]);
+                       }
+               }
+       }
+       info8.architecture      = d->local.environment;
 
        for (i=0; i < ARRAY_SIZE(levels); i++) {
 
@@ -8998,6 +10200,14 @@ static bool test_add_driver_arg(struct torture_context *tctx,
        if (d->info8.config_file) {
                info8.config_file       = talloc_asprintf(tctx, "%s\\%s", d->remote.driver_directory, d->info8.config_file);
        }
+       if (d->info8.help_file) {
+               info8.help_file = talloc_asprintf(tctx, "%s\\%s", d->remote.driver_directory, d->info8.help_file);
+       }
+       if (d->info8.dependent_files && d->info8.dependent_files->string) {
+               for (i=0; d->info8.dependent_files->string[i] != NULL; i++) {
+                       info8.dependent_files->string[i] = talloc_asprintf(tctx, "%s\\%s", d->remote.driver_directory, d->info8.dependent_files->string[i]);
+               }
+       }
 
        for (i=0; i < ARRAY_SIZE(levels); i++) {
 
@@ -9123,6 +10333,10 @@ static bool test_add_driver_adobe(struct torture_context *tctx,
 {
        struct torture_driver_context *d;
 
+       if (!torture_setting_bool(tctx, "samba3", false)) {
+               torture_skip(tctx, "skipping adobe test which only works against samba3");
+       }
+
        d = talloc_zero(tctx, struct torture_driver_context);
 
        d->info8.version                = SPOOLSS_DRIVER_VERSION_9X;
@@ -9181,6 +10395,285 @@ static bool test_add_driver_adobe_cupsaddsmb(struct torture_context *tctx,
        return test_add_driver_arg(tctx, p, d);
 }
 
+static bool test_add_driver_timestamps(struct torture_context *tctx,
+                                      struct dcerpc_pipe *p)
+{
+       struct torture_driver_context *d;
+       struct timeval t = timeval_current();
+
+       d = talloc_zero(tctx, struct torture_driver_context);
+
+       d->info8.version                = SPOOLSS_DRIVER_VERSION_200X;
+       d->info8.driver_name            = TORTURE_DRIVER_TIMESTAMPS;
+       d->info8.architecture           = NULL;
+       d->info8.driver_path            = talloc_strdup(d, "pscript5.dll");
+       d->info8.data_file              = talloc_strdup(d, "cups6.ppd");
+       d->info8.config_file            = talloc_strdup(d, "cupsui6.dll");
+       d->info8.driver_date            = timeval_to_nttime(&t);
+       d->local.environment            = talloc_strdup(d, "Windows NT x86");
+       d->local.driver_directory       = talloc_strdup(d, "/usr/share/cups/drivers/i386");
+       d->ex                           = true;
+
+       torture_assert(tctx,
+               test_add_driver_arg(tctx, p, d),
+               "");
+
+       unix_to_nt_time(&d->info8.driver_date, 1);
+
+       torture_assert(tctx,
+               test_add_driver_arg(tctx, p, d),
+               "");
+
+       return true;
+}
+
+static bool test_multiple_drivers(struct torture_context *tctx,
+                                 struct dcerpc_pipe *p)
+{
+       struct torture_driver_context *d;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       const char *server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p));
+       int i;
+       struct spoolss_AddDriverInfo8 info8;
+       uint32_t add_flags = APD_COPY_NEW_FILES;
+       uint32_t delete_flags = 0;
+
+       d = talloc_zero(tctx, struct torture_driver_context);
+
+       d->info8.version                = SPOOLSS_DRIVER_VERSION_200X;
+       d->info8.driver_path            = talloc_strdup(d, "pscript5.dll");
+       d->info8.data_file              = talloc_strdup(d, "cups6.ppd");
+       d->info8.config_file            = talloc_strdup(d, "cupsui6.dll");
+       d->local.environment            = talloc_strdup(d, "Windows NT x86");
+       d->local.driver_directory       = talloc_strdup(d, "/usr/share/cups/drivers/i386");
+       d->ex                           = true;
+
+       torture_assert(tctx,
+               fillup_printserver_info(tctx, p, d),
+               "failed to fillup printserver info");
+
+       if (!directory_exist(d->local.driver_directory)) {
+               torture_skip(tctx, "Skipping Printer Driver test as no local driver is available");
+       }
+
+       torture_assert(tctx,
+               upload_printer_driver(tctx, dcerpc_server_name(p), d),
+               "failed to upload printer driver");
+
+       info8 = d->info8;
+       info8.architecture      = d->local.environment;
+
+       for (i=0; i < 3; i++) {
+               info8.driver_name               = talloc_asprintf(d, "torture_test_driver_%d", i);
+
+               torture_assert(tctx,
+                       test_AddPrinterDriver_args_level_3(tctx, b, server_name_slash, &info8, add_flags, true, NULL),
+                       "failed to add driver");
+       }
+
+       torture_assert(tctx,
+               test_DeletePrinterDriverEx(tctx, b, server_name_slash, "torture_test_driver_0", info8.architecture, delete_flags, info8.version),
+               "failed to delete driver");
+
+       torture_assert(tctx,
+               test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, info8.architecture, 3, "torture_test_driver_1", NULL),
+               "torture_test_driver_1 no longer on the server");
+
+       torture_assert(tctx,
+               test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, info8.architecture, 3, "torture_test_driver_2", NULL),
+               "torture_test_driver_2 no longer on the server");
+
+       torture_assert(tctx,
+               test_DeletePrinterDriverEx(tctx, b, server_name_slash, "torture_test_driver_1", info8.architecture, delete_flags, info8.version),
+               "failed to delete driver");
+
+       torture_assert(tctx,
+               test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, info8.architecture, 3, "torture_test_driver_2", NULL),
+               "torture_test_driver_2 no longer on the server");
+
+       torture_assert(tctx,
+               test_DeletePrinterDriverEx(tctx, b, server_name_slash, "torture_test_driver_2", info8.architecture, delete_flags, info8.version),
+               "failed to delete driver");
+
+       torture_assert(tctx,
+               remove_printer_driver(tctx, dcerpc_server_name(p), d),
+               "failed to remove printer driver");
+
+       return true;
+}
+
+static bool test_del_driver_all_files(struct torture_context *tctx,
+                                     struct dcerpc_pipe *p)
+{
+       struct torture_driver_context *d;
+       struct spoolss_StringArray *a;
+       uint32_t add_flags = APD_COPY_NEW_FILES;
+       uint32_t delete_flags = DPD_DELETE_ALL_FILES;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       const char *server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p));
+
+       d = talloc_zero(tctx, struct torture_driver_context);
+
+       d->ex                           = true;
+       d->info8.version                = SPOOLSS_DRIVER_VERSION_200X;
+       d->info8.driver_name            = TORTURE_DRIVER_DELETER;
+       d->info8.architecture           = NULL;
+       d->info8.driver_path            = talloc_strdup(d, "pscript5.dll");
+       d->info8.data_file              = talloc_strdup(d, "cups6.ppd");
+       d->info8.config_file            = talloc_strdup(d, "cupsui6.dll");
+       d->info8.help_file              = talloc_strdup(d, "pscript.hlp");
+       d->local.environment            = talloc_strdup(d, SPOOLSS_ARCHITECTURE_x64);
+       d->local.driver_directory       = talloc_strdup(d, "/usr/share/cups/drivers/x64");
+
+       a                               = talloc_zero(d, struct spoolss_StringArray);
+       a->string                       = talloc_zero_array(a, const char *, 3);
+       a->string[0]                    = talloc_strdup(a->string, "cups6.inf");
+       a->string[1]                    = talloc_strdup(a->string, "cups6.ini");
+
+       d->info8.dependent_files        = a;
+       d->info8.architecture           = d->local.environment;
+
+       torture_assert(tctx,
+               fillup_printserver_info(tctx, p, d),
+               "failed to fillup printserver info");
+
+       if (!directory_exist(d->local.driver_directory)) {
+               torture_skip(tctx, "Skipping Printer Driver test as no local driver is available");
+       }
+
+       torture_assert(tctx,
+               upload_printer_driver(tctx, dcerpc_server_name(p), d),
+               "failed to upload printer driver");
+
+       torture_assert(tctx,
+               test_AddPrinterDriver_args_level_3(tctx, b, server_name_slash, &d->info8, add_flags, true, NULL),
+               "failed to add driver");
+
+       torture_assert(tctx,
+               test_DeletePrinterDriverEx(tctx, b, server_name_slash,
+                                          d->info8.driver_name,
+                                          d->local.environment,
+                                          delete_flags,
+                                          d->info8.version),
+               "failed to delete driver");
+
+       torture_assert(tctx,
+               check_printer_driver_files(tctx, dcerpc_server_name(p), d, false),
+               "printer driver file check failed");
+
+       talloc_free(d);
+       return true;
+}
+
+static bool test_del_driver_unused_files(struct torture_context *tctx,
+                                        struct dcerpc_pipe *p)
+{
+       struct torture_driver_context *d1;
+       struct torture_driver_context *d2;
+       uint32_t add_flags = APD_COPY_NEW_FILES;
+       struct dcerpc_binding_handle *b = p->binding_handle;
+       const char *server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p));
+
+       d1 = talloc_zero(tctx, struct torture_driver_context);
+       d1->ex                          = true;
+       d1->info8.version               = SPOOLSS_DRIVER_VERSION_200X;
+       d1->info8.driver_name           = TORTURE_DRIVER_DELETER;
+       d1->info8.architecture          = NULL;
+       d1->info8.driver_path           = talloc_strdup(d1, "pscript5.dll");
+       d1->info8.data_file             = talloc_strdup(d1, "cups6.ppd");
+       d1->info8.config_file           = talloc_strdup(d1, "cupsui6.dll");
+       d1->info8.help_file             = talloc_strdup(d1, "pscript.hlp");
+       d1->local.environment           = talloc_strdup(d1, SPOOLSS_ARCHITECTURE_x64);
+       d1->local.driver_directory      = talloc_strdup(d1, "/usr/share/cups/drivers/x64");
+       d1->info8.architecture          = d1->local.environment;
+
+       d2 = talloc_zero(tctx, struct torture_driver_context);
+       d2->ex                          = true;
+       d2->info8.version               = SPOOLSS_DRIVER_VERSION_200X;
+       d2->info8.driver_name           = TORTURE_DRIVER_DELETERIN;
+       d2->info8.architecture          = NULL;
+       d2->info8.driver_path           = talloc_strdup(d2, "pscript5.dll");    /* overlapping */
+       d2->info8.data_file             = talloc_strdup(d2, "cupsps6.dll");
+       d2->info8.config_file           = talloc_strdup(d2, "cups6.ini");
+       d2->info8.help_file             = talloc_strdup(d2, "pscript.hlp");     /* overlapping */
+       d2->local.environment           = talloc_strdup(d2, SPOOLSS_ARCHITECTURE_x64);
+       d2->local.driver_directory      = talloc_strdup(d2, "/usr/share/cups/drivers/x64");
+       d2->info8.architecture          = d2->local.environment;
+
+       torture_assert(tctx,
+               fillup_printserver_info(tctx, p, d1),
+               "failed to fillup printserver info");
+       torture_assert(tctx,
+               fillup_printserver_info(tctx, p, d2),
+               "failed to fillup printserver info");
+
+       if (!directory_exist(d1->local.driver_directory)) {
+               torture_skip(tctx, "Skipping Printer Driver test as no local driver is available");
+       }
+
+       torture_assert(tctx,
+               upload_printer_driver(tctx, dcerpc_server_name(p), d1),
+               "failed to upload printer driver");
+       torture_assert(tctx,
+               test_AddPrinterDriver_args_level_3(tctx, b, server_name_slash, &d1->info8, add_flags, true, NULL),
+               "failed to add driver");
+
+       torture_assert(tctx,
+               upload_printer_driver(tctx, dcerpc_server_name(p), d2),
+               "failed to upload printer driver");
+       torture_assert(tctx,
+               test_AddPrinterDriver_args_level_3(tctx, b, server_name_slash, &d2->info8, add_flags, true, NULL),
+               "failed to add driver");
+
+       /* some files are in use by a separate driver, should fail */
+       torture_assert(tctx,
+               test_DeletePrinterDriverEx_exp(tctx, b, server_name_slash,
+                                              d1->info8.driver_name,
+                                              d1->local.environment,
+                                              DPD_DELETE_ALL_FILES,
+                                              d1->info8.version,
+                                              WERR_PRINTER_DRIVER_IN_USE),
+               "invalid delete driver response");
+
+       /* should only delete files not in use by other driver */
+       torture_assert(tctx,
+               test_DeletePrinterDriverEx_exp(tctx, b, server_name_slash,
+                                              d1->info8.driver_name,
+                                              d1->local.environment,
+                                              DPD_DELETE_UNUSED_FILES,
+                                              d1->info8.version,
+                                              WERR_OK),
+               "failed to delete driver (unused files)");
+
+       /* check non-overlapping were deleted */
+       d1->info8.driver_path = NULL;
+       d1->info8.help_file = NULL;
+       torture_assert(tctx,
+               check_printer_driver_files(tctx, dcerpc_server_name(p), d1, false),
+               "printer driver file check failed");
+       /* d2 files should be uneffected */
+       torture_assert(tctx,
+               check_printer_driver_files(tctx, dcerpc_server_name(p), d2, true),
+               "printer driver file check failed");
+
+       torture_assert(tctx,
+               test_DeletePrinterDriverEx_exp(tctx, b, server_name_slash,
+                                              d2->info8.driver_name,
+                                              d2->local.environment,
+                                              DPD_DELETE_ALL_FILES,
+                                              d2->info8.version,
+                                              WERR_OK),
+               "failed to delete driver");
+
+       torture_assert(tctx,
+               check_printer_driver_files(tctx, dcerpc_server_name(p), d2, false),
+               "printer driver file check failed");
+
+       talloc_free(d1);
+       talloc_free(d2);
+       return true;
+}
+
 struct torture_suite *torture_rpc_spoolss_driver(TALLOC_CTX *mem_ctx)
 {
        struct torture_suite *suite = torture_suite_create(mem_ctx, "spoolss.driver");
@@ -9197,5 +10690,13 @@ struct torture_suite *torture_rpc_spoolss_driver(TALLOC_CTX *mem_ctx)
 
        torture_rpc_tcase_add_test(tcase, "add_driver_adobe_cupsaddsmb", test_add_driver_adobe_cupsaddsmb);
 
+       torture_rpc_tcase_add_test(tcase, "add_driver_timestamps", test_add_driver_timestamps);
+
+       torture_rpc_tcase_add_test(tcase, "multiple_drivers", test_multiple_drivers);
+
+       torture_rpc_tcase_add_test(tcase, "del_driver_all_files", test_del_driver_all_files);
+
+       torture_rpc_tcase_add_test(tcase, "del_driver_unused_files", test_del_driver_unused_files);
+
        return suite;
 }