diff options
Diffstat (limited to 'source4/torture/rpc/spoolss_notify.c')
-rw-r--r-- | source4/torture/rpc/spoolss_notify.c | 636 |
1 files changed, 636 insertions, 0 deletions
diff --git a/source4/torture/rpc/spoolss_notify.c b/source4/torture/rpc/spoolss_notify.c new file mode 100644 index 0000000..bd9dac4 --- /dev/null +++ b/source4/torture/rpc/spoolss_notify.c @@ -0,0 +1,636 @@ +/* + Unix SMB/CIFS implementation. + test suite for spoolss rpc notify operations + + Copyright (C) Jelmer Vernooij 2007 + Copyright (C) Guenther Deschner 2010 + + 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 + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "librpc/gen_ndr/ndr_spoolss_c.h" +#include "librpc/gen_ndr/ndr_spoolss.h" +#include "torture/rpc/torture_rpc.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/dcerpc_server_proto.h" +#include "rpc_server/service_rpc.h" +#include "samba/process_model.h" +#include "smb_server/smb_server.h" +#include "lib/socket/netif.h" +#include "ntvfs/ntvfs.h" +#include "param/param.h" + +static struct dcesrv_context_callbacks srv_cb = { + .log.successful_authz = log_successful_dcesrv_authz_event, + .auth.gensec_prepare = dcesrv_gensec_prepare, + .assoc_group.find = dcesrv_assoc_group_find_s4, +}; + +static NTSTATUS spoolss__op_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return NT_STATUS_OK; +} + +static void spoolss__op_unbind(struct dcesrv_connection_context *context, const struct dcesrv_interface *iface) +{ +} + +static NTSTATUS spoolss__op_ndr_pull(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct ndr_pull *pull, void **r) +{ + enum ndr_err_code ndr_err; + uint16_t opnum = dce_call->pkt.u.request.opnum; + + dce_call->fault_code = 0; + + if (opnum >= ndr_table_spoolss.num_calls) { + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NET_WRITE_FAULT; + } + + *r = talloc_size(mem_ctx, ndr_table_spoolss.calls[opnum].struct_size); + NT_STATUS_HAVE_NO_MEMORY(*r); + + /* unravel the NDR for the packet */ + ndr_err = ndr_table_spoolss.calls[opnum].ndr_pull(pull, NDR_IN, *r); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dce_call->fault_code = DCERPC_FAULT_NDR; + return NT_STATUS_NET_WRITE_FAULT; + } + + return NT_STATUS_OK; +} + +/* Note that received_packets are allocated on the NULL context + * because no other context appears to stay around long enough. */ +static struct received_packet { + uint16_t opnum; + void *r; + struct received_packet *prev, *next; +} *received_packets = NULL; + +static void free_received_packets(void) +{ + struct received_packet *rp; + struct received_packet *rp_next; + + for (rp = received_packets; rp; rp = rp_next) { + rp_next = rp->next; + DLIST_REMOVE(received_packets, rp); + talloc_unlink(rp, rp->r); + talloc_free(rp); + } + received_packets = NULL; +} + +static WERROR _spoolss_ReplyOpenPrinter(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct spoolss_ReplyOpenPrinter *r) +{ + DEBUG(1,("_spoolss_ReplyOpenPrinter\n")); + + NDR_PRINT_IN_DEBUG(spoolss_ReplyOpenPrinter, r); + + r->out.handle = talloc(r, struct policy_handle); + r->out.handle->handle_type = 42; + r->out.handle->uuid = GUID_random(); + r->out.result = WERR_OK; + + NDR_PRINT_OUT_DEBUG(spoolss_ReplyOpenPrinter, r); + + return WERR_OK; +} + +static WERROR _spoolss_ReplyClosePrinter(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct spoolss_ReplyClosePrinter *r) +{ + DEBUG(1,("_spoolss_ReplyClosePrinter\n")); + + NDR_PRINT_IN_DEBUG(spoolss_ReplyClosePrinter, r); + + ZERO_STRUCTP(r->out.handle); + r->out.result = WERR_OK; + + NDR_PRINT_OUT_DEBUG(spoolss_ReplyClosePrinter, r); + + return WERR_OK; +} + +static WERROR _spoolss_RouterReplyPrinterEx(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct spoolss_RouterReplyPrinterEx *r) +{ + DEBUG(1,("_spoolss_RouterReplyPrinterEx\n")); + + NDR_PRINT_IN_DEBUG(spoolss_RouterReplyPrinterEx, r); + + r->out.reply_result = talloc(r, uint32_t); + *r->out.reply_result = 0; + r->out.result = WERR_OK; + + NDR_PRINT_OUT_DEBUG(spoolss_RouterReplyPrinterEx, r); + + return WERR_OK; +} + +static NTSTATUS spoolss__op_dispatch(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r) +{ + uint16_t opnum = dce_call->pkt.u.request.opnum; + struct received_packet *rp; + + rp = talloc_zero(NULL, struct received_packet); + rp->opnum = opnum; + rp->r = talloc_reference(rp, r); + + DLIST_ADD_END(received_packets, rp); + + switch (opnum) { + case 58: { + struct spoolss_ReplyOpenPrinter *r2 = (struct spoolss_ReplyOpenPrinter *)r; + r2->out.result = _spoolss_ReplyOpenPrinter(dce_call, mem_ctx, r2); + break; + } + case 60: { + struct spoolss_ReplyClosePrinter *r2 = (struct spoolss_ReplyClosePrinter *)r; + r2->out.result = _spoolss_ReplyClosePrinter(dce_call, mem_ctx, r2); + break; + } + case 66: { + struct spoolss_RouterReplyPrinterEx *r2 = (struct spoolss_RouterReplyPrinterEx *)r; + r2->out.result = _spoolss_RouterReplyPrinterEx(dce_call, mem_ctx, r2); + break; + } + + default: + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + break; + } + + if (dce_call->fault_code != 0) { + return NT_STATUS_NET_WRITE_FAULT; + } + return NT_STATUS_OK; +} + + +static NTSTATUS spoolss__op_reply(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r) +{ + return NT_STATUS_OK; +} + + +static NTSTATUS spoolss__op_ndr_push(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct ndr_push *push, const void *r) +{ + enum ndr_err_code ndr_err; + uint16_t opnum = dce_call->pkt.u.request.opnum; + + ndr_err = ndr_table_spoolss.calls[opnum].ndr_push(push, NDR_OUT, r); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dce_call->fault_code = DCERPC_FAULT_NDR; + return NT_STATUS_NET_WRITE_FAULT; + } + + return NT_STATUS_OK; +} + +static const struct dcesrv_interface notify_test_spoolss_interface = { + .name = "spoolss", + .syntax_id = {{0x12345678,0x1234,0xabcd,{0xef,0x00},{0x01,0x23,0x45,0x67,0x89,0xab}},1.0}, + .bind = spoolss__op_bind, + .unbind = spoolss__op_unbind, + .ndr_pull = spoolss__op_ndr_pull, + .dispatch = spoolss__op_dispatch, + .reply = spoolss__op_reply, + .ndr_push = spoolss__op_ndr_push +}; + +static bool spoolss__op_interface_by_uuid(struct dcesrv_interface *iface, const struct GUID *uuid, uint32_t if_version) +{ + if (notify_test_spoolss_interface.syntax_id.if_version == if_version && + GUID_equal(¬ify_test_spoolss_interface.syntax_id.uuid, uuid)) { + memcpy(iface,¬ify_test_spoolss_interface, sizeof(*iface)); + return true; + } + + return false; +} + +static bool spoolss__op_interface_by_name(struct dcesrv_interface *iface, const char *name) +{ + if (strcmp(notify_test_spoolss_interface.name, name)==0) { + memcpy(iface, ¬ify_test_spoolss_interface, sizeof(*iface)); + return true; + } + + return false; +} + +static NTSTATUS spoolss__op_init_server(struct dcesrv_context *dce_ctx, const struct dcesrv_endpoint_server *ep_server) +{ + uint32_t i; + + for (i=0;i<ndr_table_spoolss.endpoints->count;i++) { + NTSTATUS ret; + const char *name = ndr_table_spoolss.endpoints->names[i]; + + ret = dcesrv_interface_register(dce_ctx, + name, + NULL, + ¬ify_test_spoolss_interface, + NULL); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1,("spoolss_op_init_server: failed to register endpoint '%s'\n",name)); + return ret; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS spoolss__op_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + return NT_STATUS_OK; +} + +static bool test_OpenPrinter(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *printername) +{ + struct spoolss_OpenPrinter r; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(r); + + r.in.printername = printername; + r.in.datatype = NULL; + r.in.devmode_ctr.devmode= NULL; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = handle; + + torture_comment(tctx, "Testing OpenPrinter(%s)\n", r.in.printername); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_OpenPrinter_r(b, tctx, &r), + "OpenPrinter failed"); + torture_assert_werr_ok(tctx, r.out.result, + "OpenPrinter failed"); + + return true; +} + +static struct spoolss_NotifyOption *setup_printserver_NotifyOption(struct torture_context *tctx) +{ + struct spoolss_NotifyOption *o; + + o = talloc_zero(tctx, struct spoolss_NotifyOption); + + o->version = 2; + o->flags = PRINTER_NOTIFY_OPTIONS_REFRESH; + + o->count = 2; + o->types = talloc_zero_array(o, struct spoolss_NotifyOptionType, o->count); + + o->types[0].type = PRINTER_NOTIFY_TYPE; + o->types[0].count = 1; + o->types[0].fields = talloc_array(o->types, union spoolss_Field, o->types[0].count); + o->types[0].fields[0].field = PRINTER_NOTIFY_FIELD_SERVER_NAME; + + o->types[1].type = JOB_NOTIFY_TYPE; + o->types[1].count = 1; + o->types[1].fields = talloc_array(o->types, union spoolss_Field, o->types[1].count); + o->types[1].fields[0].field = JOB_NOTIFY_FIELD_MACHINE_NAME; + + return o; +} + +#if 0 +static struct spoolss_NotifyOption *setup_printer_NotifyOption(struct torture_context *tctx) +{ + struct spoolss_NotifyOption *o; + + o = talloc_zero(tctx, struct spoolss_NotifyOption); + + o->version = 2; + o->flags = PRINTER_NOTIFY_OPTIONS_REFRESH; + + o->count = 1; + o->types = talloc_zero_array(o, struct spoolss_NotifyOptionType, o->count); + + o->types[0].type = PRINTER_NOTIFY_TYPE; + o->types[0].count = 1; + o->types[0].fields = talloc_array(o->types, union spoolss_Field, o->types[0].count); + o->types[0].fields[0].field = PRINTER_NOTIFY_FIELD_COMMENT; + + return o; +} +#endif + +static bool test_RemoteFindFirstPrinterChangeNotifyEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *address, + struct spoolss_NotifyOption *option) +{ + struct spoolss_RemoteFindFirstPrinterChangeNotifyEx r; + const char *local_machine = talloc_asprintf(tctx, "\\\\%s", address); + + torture_comment(tctx, "Testing RemoteFindFirstPrinterChangeNotifyEx(%s)\n", local_machine); + + r.in.flags = 0; + r.in.local_machine = local_machine; + r.in.options = 0; + r.in.printer_local = 0; + r.in.notify_options = option; + r.in.handle = handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_RemoteFindFirstPrinterChangeNotifyEx_r(b, tctx, &r), + "RemoteFindFirstPrinterChangeNotifyEx failed"); + torture_assert_werr_ok(tctx, r.out.result, + "error return code for RemoteFindFirstPrinterChangeNotifyEx"); + + return true; +} + +static bool test_RouterRefreshPrinterChangeNotify(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct spoolss_NotifyOption *options, + struct spoolss_NotifyInfo **info) +{ + struct spoolss_RouterRefreshPrinterChangeNotify r; + + torture_comment(tctx, "Testing RouterRefreshPrinterChangeNotify\n"); + + r.in.handle = handle; + r.in.change_low = 0; + r.in.options = options; + r.out.info = info; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_RouterRefreshPrinterChangeNotify_r(b, tctx, &r), + "RouterRefreshPrinterChangeNotify failed"); + torture_assert_werr_ok(tctx, r.out.result, + "error return code for RouterRefreshPrinterChangeNotify"); + + return true; +} + +#if 0 +static bool test_SetPrinter(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + union spoolss_PrinterInfo info; + struct spoolss_SetPrinter r; + struct spoolss_SetPrinterInfo2 info2; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + info2.servername = info.info2.servername; + info2.printername = info.info2.printername; + info2.sharename = info.info2.sharename; + info2.portname = info.info2.portname; + info2.drivername = info.info2.drivername; + info2.comment = talloc_asprintf(tctx, "torture_comment %d\n", (int)time(NULL)); + info2.location = info.info2.location; + info2.devmode_ptr = 0; + info2.sepfile = info.info2.sepfile; + info2.printprocessor = info.info2.printprocessor; + info2.datatype = info.info2.datatype; + info2.parameters = info.info2.parameters; + info2.secdesc_ptr = 0; + info2.attributes = info.info2.attributes; + info2.priority = info.info2.priority; + info2.defaultpriority = info.info2.defaultpriority; + info2.starttime = info.info2.starttime; + info2.untiltime = info.info2.untiltime; + info2.status = info.info2.status; + info2.cjobs = info.info2.cjobs; + info2.averageppm = info.info2.averageppm; + + info_ctr.level = 2; + info_ctr.info.info2 = &info2; + + 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 = 0; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_SetPrinter_r(b, tctx, &r), "SetPrinter failed"); + torture_assert_werr_ok(tctx, r.out.result, "SetPrinter failed"); + + return true; +} +#endif + +static bool test_start_dcerpc_server(struct torture_context *tctx, + struct tevent_context *event_ctx, + struct dcesrv_context **dce_ctx_p, + const char **address_p) +{ + struct dcesrv_endpoint_server ep_server; + NTSTATUS status; + struct dcesrv_context *dce_ctx; + const char *endpoints[] = { "spoolss", NULL }; + struct dcesrv_endpoint *e; + const char *address; + struct interface *ifaces; + + ntvfs_init(tctx->lp_ctx); + + /* fill in our name */ + ep_server.name = "spoolss"; + + ep_server.initialized = false; + + /* fill in all the operations */ + ep_server.init_server = spoolss__op_init_server; + ep_server.shutdown_server = spoolss__op_shutdown_server; + + ep_server.interface_by_uuid = spoolss__op_interface_by_uuid; + ep_server.interface_by_name = spoolss__op_interface_by_name; + + torture_assert_ntstatus_ok(tctx, dcerpc_register_ep_server(&ep_server), + "unable to register spoolss server"); + + lpcfg_set_cmdline(tctx->lp_ctx, "dcerpc endpoint servers", "spoolss"); + + load_interface_list(tctx, tctx->lp_ctx, &ifaces); + address = iface_list_first_v4(ifaces); + + torture_comment(tctx, "Listening for callbacks on %s\n", address); + + status = process_model_init(tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, + "unable to initialize process models"); + + status = smbsrv_add_socket(tctx, event_ctx, tctx->lp_ctx, + process_model_startup("single"), + address, NULL); + torture_assert_ntstatus_ok(tctx, status, "starting smb server"); + + status = dcesrv_init_context(tctx, tctx->lp_ctx, &srv_cb, &dce_ctx); + torture_assert_ntstatus_ok(tctx, status, + "unable to initialize DCE/RPC server"); + + status = dcesrv_init_ep_servers(dce_ctx, endpoints); + torture_assert_ntstatus_ok(tctx, + status, + "unable to initialize DCE/RPC ep servers"); + + for (e=dce_ctx->endpoint_list;e;e=e->next) { + status = dcesrv_add_ep(dce_ctx, tctx->lp_ctx, + e, tctx->ev, + process_model_startup("single"), NULL); + torture_assert_ntstatus_ok(tctx, status, + "unable listen on dcerpc endpoint server"); + } + + *dce_ctx_p = dce_ctx; + *address_p = address; + + return true; +} + +static struct received_packet *last_packet(struct received_packet *p) +{ + struct received_packet *tmp; + for (tmp = p; tmp->next; tmp = tmp->next) { + } + return tmp; +} + +static bool test_RFFPCNEx(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcesrv_context *dce_ctx; + struct policy_handle handle; + const char *address; + struct received_packet *tmp; + struct spoolss_NotifyOption *server_option = setup_printserver_NotifyOption(tctx); +#if 0 + struct spoolss_NotifyOption *printer_option = setup_printer_NotifyOption(tctx); +#endif + struct dcerpc_binding_handle *b = p->binding_handle; + const char *printername = NULL; + struct spoolss_NotifyInfo *info = NULL; + + free_received_packets(); + + /* Start DCE/RPC server */ + torture_assert(tctx, test_start_dcerpc_server(tctx, tctx->ev, &dce_ctx, &address), ""); + + printername = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + torture_assert(tctx, test_OpenPrinter(tctx, p, &handle, printername), ""); + torture_assert(tctx, test_RemoteFindFirstPrinterChangeNotifyEx(tctx, b, &handle, address, server_option), ""); + torture_assert(tctx, received_packets, "no packets received"); + torture_assert_int_equal(tctx, received_packets->opnum, NDR_SPOOLSS_REPLYOPENPRINTER, + "no ReplyOpenPrinter packet after RemoteFindFirstPrinterChangeNotifyEx"); + torture_assert(tctx, test_RouterRefreshPrinterChangeNotify(tctx, b, &handle, NULL, &info), ""); + torture_assert(tctx, test_RouterRefreshPrinterChangeNotify(tctx, b, &handle, server_option, &info), ""); + torture_assert(tctx, test_ClosePrinter(tctx, b, &handle), ""); + tmp = last_packet(received_packets); + torture_assert_int_equal(tctx, tmp->opnum, NDR_SPOOLSS_REPLYCLOSEPRINTER, + "no ReplyClosePrinter packet after ClosePrinter"); +#if 0 + printername = talloc_asprintf(tctx, "\\\\%s\\%s", dcerpc_server_name(p), name); + + torture_assert(tctx, test_OpenPrinter(tctx, p, &handle, "Epson AL-2600"), ""); + torture_assert(tctx, test_RemoteFindFirstPrinterChangeNotifyEx(tctx, p, &handle, address, printer_option), ""); + tmp = last_packet(received_packets); + torture_assert_int_equal(tctx, tmp->opnum, NDR_SPOOLSS_REPLYOPENPRINTER, + "no ReplyOpenPrinter packet after RemoteFindFirstPrinterChangeNotifyEx"); + torture_assert(tctx, test_RouterRefreshPrinterChangeNotify(tctx, p, &handle, NULL, &info), ""); + torture_assert(tctx, test_RouterRefreshPrinterChangeNotify(tctx, p, &handle, printer_option, &info), ""); + torture_assert(tctx, test_SetPrinter(tctx, p, &handle), ""); + tmp = last_packet(received_packets); + torture_assert_int_equal(tctx, tmp->opnum, NDR_SPOOLSS_ROUTERREPLYPRINTEREX, + "no RouterReplyPrinterEx packet after ClosePrinter"); + torture_assert(tctx, test_ClosePrinter(tctx, p, &handle), ""); + tmp = last_packet(received_packets); + torture_assert_int_equal(tctx, tmp->opnum, NDR_SPOOLSS_REPLYCLOSEPRINTER, + "no ReplyClosePrinter packet after ClosePrinter"); +#endif + /* Shut down DCE/RPC server */ + talloc_free(dce_ctx); + free_received_packets(); + + return true; +} + +/** Test that makes sure that calling ReplyOpenPrinter() + * on Samba 4 will cause an irpc broadcast call. + */ +static bool test_ReplyOpenPrinter(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct spoolss_ReplyOpenPrinter r; + struct spoolss_ReplyClosePrinter s; + struct policy_handle h; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skipping ReplyOpenPrinter server implementation test against s3\n"); + } + + r.in.server_name = "earth"; + r.in.printer_local = 2; + r.in.type = REG_DWORD; + r.in.bufsize = 0; + r.in.buffer = NULL; + r.out.handle = &h; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_ReplyOpenPrinter_r(b, tctx, &r), + "spoolss_ReplyOpenPrinter call failed"); + + torture_assert_werr_ok(tctx, r.out.result, "error return code"); + + s.in.handle = &h; + s.out.handle = &h; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_ReplyClosePrinter_r(b, tctx, &s), + "spoolss_ReplyClosePrinter call failed"); + + torture_assert_werr_ok(tctx, r.out.result, "error return code"); + + return true; +} + +struct torture_suite *torture_rpc_spoolss_notify(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "spoolss.notify"); + + struct torture_rpc_tcase *tcase = torture_suite_add_rpc_iface_tcase(suite, + "notify", &ndr_table_spoolss); + + torture_rpc_tcase_add_test(tcase, "testRFFPCNEx", test_RFFPCNEx); + torture_rpc_tcase_add_test(tcase, "testReplyOpenPrinter", test_ReplyOpenPrinter); + + return suite; +} |