diff options
Diffstat (limited to 'source3/rpc_client/msrpc_spoolss.c')
-rw-r--r-- | source3/rpc_client/msrpc_spoolss.c | 809 |
1 files changed, 809 insertions, 0 deletions
diff --git a/source3/rpc_client/msrpc_spoolss.c b/source3/rpc_client/msrpc_spoolss.c new file mode 100644 index 0000000000..2267190d8e --- /dev/null +++ b/source3/rpc_client/msrpc_spoolss.c @@ -0,0 +1,809 @@ +/* + Unix SMB/CIFS implementation. + NT Domain Authentication SMB / MSRPC client + Copyright (C) Andrew Tridgell 1994-2000 + Copyright (C) Luke Kenneth Casson Leighton 1996-2000 + Copyright (C) Jean-Francois Micouleau 1999-2000 + + 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 2 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 "nterr.h" +#include "rpc_parse.h" +#include "rpcclient.h" + +#define DEBUG_TESTING + +extern FILE* out_hnd; + +extern struct user_creds *usr_creds; + +/******************************************************************** +initialize a spoolss NEW_BUFFER. +********************************************************************/ +void init_buffer(NEW_BUFFER *buffer, uint32 size, TALLOC_CTX *ctx) +{ + buffer->ptr = (size!=0)? 1:0; + buffer->size=size; + buffer->string_at_end=size; + prs_init(&buffer->prs, size, ctx, MARSHALL); + buffer->struct_start = prs_offset(&buffer->prs); +} + +static void decode_printer_info_0(NEW_BUFFER *buffer, uint32 returned, + PRINTER_INFO_0 **info) +{ + uint32 i; + PRINTER_INFO_0 *inf; + + inf=(PRINTER_INFO_0 *)malloc(returned*sizeof(PRINTER_INFO_0)); + + buffer->prs.data_offset=0; + + for (i=0; i<returned; i++) { + new_smb_io_printer_info_0("", buffer, &(inf[i]), 0); + } + + *info=inf; +} + +static void decode_printer_info_1(NEW_BUFFER *buffer, uint32 returned, + PRINTER_INFO_1 **info) +{ + uint32 i; + PRINTER_INFO_1 *inf; + + inf=(PRINTER_INFO_1 *)malloc(returned*sizeof(PRINTER_INFO_1)); + + buffer->prs.data_offset=0; + + for (i=0; i<returned; i++) { + new_smb_io_printer_info_1("", buffer, &(inf[i]), 0); + } + + *info=inf; +} + +static void decode_printer_info_2(NEW_BUFFER *buffer, uint32 returned, + PRINTER_INFO_2 **info) +{ + uint32 i; + PRINTER_INFO_2 *inf; + + inf=(PRINTER_INFO_2 *)malloc(returned*sizeof(PRINTER_INFO_2)); + + buffer->prs.data_offset=0; + + for (i=0; i<returned; i++) { + /* a little initialization as we go */ + inf[i].secdesc = NULL; + new_smb_io_printer_info_2("", buffer, &(inf[i]), 0); + } + + *info=inf; +} + +static void decode_printer_info_3(NEW_BUFFER *buffer, uint32 returned, + PRINTER_INFO_3 **info) +{ + uint32 i; + PRINTER_INFO_3 *inf; + + inf=(PRINTER_INFO_3 *)malloc(returned*sizeof(PRINTER_INFO_3)); + + buffer->prs.data_offset=0; + + for (i=0; i<returned; i++) { + new_smb_io_printer_info_3("", buffer, &(inf[i]), 0); + } + + *info=inf; +} + +static void decode_printer_driver_1(NEW_BUFFER *buffer, uint32 returned, + DRIVER_INFO_1 **info) +{ + uint32 i; + DRIVER_INFO_1 *inf; + + inf=(DRIVER_INFO_1 *)malloc(returned*sizeof(DRIVER_INFO_1)); + + buffer->prs.data_offset=0; + + for (i=0; i<returned; i++) { + new_smb_io_printer_driver_info_1("", buffer, &(inf[i]), 0); + } + + *info=inf; +} + +static void decode_printer_driver_2(NEW_BUFFER *buffer, uint32 returned, + DRIVER_INFO_2 **info) +{ + uint32 i; + DRIVER_INFO_2 *inf; + + inf=(DRIVER_INFO_2 *)malloc(returned*sizeof(DRIVER_INFO_2)); + + buffer->prs.data_offset=0; + + for (i=0; i<returned; i++) { + new_smb_io_printer_driver_info_2("", buffer, &(inf[i]), 0); + } + + *info=inf; +} + +static void decode_printer_driver_3(NEW_BUFFER *buffer, uint32 returned, + DRIVER_INFO_3 **info) +{ + uint32 i; + DRIVER_INFO_3 *inf; + + inf=(DRIVER_INFO_3 *)malloc(returned*sizeof(DRIVER_INFO_3)); + + buffer->prs.data_offset=0; + + for (i=0; i<returned; i++) { + new_smb_io_printer_driver_info_3("", buffer, &(inf[i]), 0); + } + + *info=inf; +} + +static void decode_printerdriverdir_info_1(NEW_BUFFER *buffer, DRIVER_DIRECTORY_1 *info) +{ +/* DRIVER_DIRECTORY_1 *inf; + + inf=(DRIVER_DIRECTORY_1 *)malloc(returned*sizeof(DRIVER_DIRECTORY_1)); +*/ + prs_set_offset(&buffer->prs, 0); + + new_smb_io_driverdir_1("", buffer, info, 0); + +/* *info=inf;*/ +} + +/********************************************************************** + Decode a PORT_INFO_1 struct from a NEW_BUFFER +**********************************************************************/ +void decode_port_info_1(NEW_BUFFER *buffer, uint32 returned, + PORT_INFO_1 **info) +{ + uint32 i; + PORT_INFO_1 *inf; + + inf=(PORT_INFO_1*)malloc(returned*sizeof(PORT_INFO_1)); + + prs_set_offset(&buffer->prs, 0); + + for (i=0; i<returned; i++) { + new_smb_io_port_info_1("", buffer, &(inf[i]), 0); + } + + *info=inf; +} + +/********************************************************************** + Decode a PORT_INFO_2 struct from a NEW_BUFFER +**********************************************************************/ +void decode_port_info_2(NEW_BUFFER *buffer, uint32 returned, + PORT_INFO_2 **info) +{ + uint32 i; + PORT_INFO_2 *inf; + + inf=(PORT_INFO_2*)malloc(returned*sizeof(PORT_INFO_2)); + + prs_set_offset(&buffer->prs, 0); + + for (i=0; i<returned; i++) { + new_smb_io_port_info_2("", buffer, &(inf[i]), 0); + } + + *info=inf; +} + + +/**************************************************************************** +nt spoolss query +****************************************************************************/ +BOOL msrpc_spoolss_enum_printers(char* srv_name, uint32 flags, + uint32 level, PRINTER_INFO_CTR ctr) +{ + NTSTATUS status; + NEW_BUFFER buffer; + uint32 needed; + uint32 returned; + TALLOC_CTX *mem_ctx = NULL; + + if ((mem_ctx=talloc_init()) == NULL) + { + DEBUG(0,("msrpc_spoolss_enum_printers: talloc_init failed!\n")); + return False; + } + init_buffer(&buffer, 0, mem_ctx); + + /* send a NULL buffer first */ + status=spoolss_enum_printers(flags, srv_name, level, &buffer, 0, + &needed, &returned); + + if (status==ERROR_INSUFFICIENT_BUFFER) { + init_buffer(&buffer, needed, mem_ctx); + status=spoolss_enum_printers(flags, srv_name, level, &buffer, + needed, &needed, &returned); + } + + if (status!=NT_STATUS_OK) + { + DEBUG(0,("spoolss_enum_printers: %s\n", nt_errstr(status))); + if (mem_ctx) + talloc_destroy(mem_ctx); + return False; + } + + /* is there anything to process? */ + if (returned != 0) + { + switch (level) { + case 1: + decode_printer_info_1(&buffer, returned, &(ctr.printers_1)); + break; + case 2: + decode_printer_info_2(&buffer, returned, &(ctr.printers_2)); + break; + case 3: + decode_printer_info_3(&buffer, returned, &(ctr.printers_3)); + break; + } + + display_printer_info_ctr(out_hnd, ACTION_HEADER , level, returned, ctr); + display_printer_info_ctr(out_hnd, ACTION_ENUMERATE, level, returned, ctr); + display_printer_info_ctr(out_hnd, ACTION_FOOTER , level, returned, ctr); + } + + if (mem_ctx) + talloc_destroy(mem_ctx); + + return True; +} + +/**************************************************************************** +nt spoolss query +****************************************************************************/ +BOOL msrpc_spoolss_enum_ports(char* srv_name, + uint32 level, PORT_INFO_CTR *ctr) +{ + NTSTATUS status; + NEW_BUFFER buffer; + uint32 needed; + uint32 returned; + TALLOC_CTX *mem_ctx = NULL; + + if ((mem_ctx=talloc_init()) == NULL) + { + DEBUG(0,("msrpc_spoolss_enum_ports: talloc_init failed!\n")); + return False; + } + + init_buffer(&buffer, 0, mem_ctx); + + /* send a NULL buffer first */ + status=spoolss_enum_ports(srv_name, level, &buffer, 0, + &needed, &returned); + + if (status==ERROR_INSUFFICIENT_BUFFER) { + init_buffer(&buffer, needed, mem_ctx); + status=spoolss_enum_ports(srv_name, level, &buffer, + needed, &needed, &returned); + } + + report(out_hnd, "\tstatus:[%d (%x)]\n", status, status); + + if (status!=NT_STATUS_OK) + { + if (mem_ctx) + talloc_destroy(mem_ctx); + return False; + } + + /* is there anything to process? */ + if (returned != 0) + { + switch (level) { + case 1: + decode_port_info_1(&buffer, returned, &ctr->port.info_1); + break; + case 2: + decode_port_info_2(&buffer, returned, &ctr->port.info_2); + break; + default: + DEBUG(0,("Unable to decode unknown PORT_INFO_%d\n", level)); + break; + } + + display_port_info_ctr(out_hnd, ACTION_HEADER , level, returned, ctr); + display_port_info_ctr(out_hnd, ACTION_ENUMERATE, level, returned, ctr); + display_port_info_ctr(out_hnd, ACTION_FOOTER , level, returned, ctr); + } + if (mem_ctx) + talloc_destroy(mem_ctx); + + + + return True; +} + +/**************************************************************************** +nt spoolss query +****************************************************************************/ +uint32 msrpc_spoolss_getprinterdata( const char* printer_name, + const char* station, + const char* user_name, + const char* value_name, + uint32 *type, + NEW_BUFFER *buffer, + void *fn) +{ + POLICY_HND hnd; + NTSTATUS status; + uint32 needed; + uint32 size; + char *data; + UNISTR2 uni_val_name; + TALLOC_CTX *mem_ctx = NULL; + + DEBUG(4,("spoolgetdata - printer: %s server: %s user: %s value: %s\n", + printer_name, station, user_name, value_name)); + + if(!spoolss_open_printer_ex( printer_name, 0, 0, station, user_name, + &hnd)) + { + return NT_STATUS_ACCESS_DENIED; + } + + init_unistr2(&uni_val_name, value_name, 0); + size = 0; + data = NULL; + + if ((mem_ctx=talloc_init()) == NULL) + { + DEBUG(0,("msrpc_spoolss_getprinterdata: talloc_init failed!\n")); + return False; + } + init_buffer(buffer, size, mem_ctx); + + status = spoolss_getprinterdata(&hnd, &uni_val_name, size, type, &size, + (unsigned char *)data, &needed); + + if (status == ERROR_INSUFFICIENT_BUFFER) + { + size = needed; + init_buffer(buffer, size, mem_ctx); + data = prs_data_p(&buffer->prs); + status = spoolss_getprinterdata(&hnd, &uni_val_name, + size, type, &size, + (unsigned char *)data, &needed); + } + + if (mem_ctx) + talloc_destroy(mem_ctx); + + if (status != NT_STATUS_OK) + { + if (!spoolss_closeprinter(&hnd)) + return NT_STATUS_ACCESS_DENIED; + return status; + } + +#if 0 + if (fn != NULL) + fn(printer_name, station, level, returned, *ctr); +#endif + + return status; +} + +/**************************************************************************** +nt spoolss query +****************************************************************************/ +BOOL msrpc_spoolss_enum_jobs( const char* printer_name, + const char* station, const char* user_name, + uint32 level, + void ***ctr, JOB_INFO_FN(fn)) +{ + POLICY_HND hnd; + NTSTATUS status; + NEW_BUFFER buffer; + uint32 needed; + uint32 returned; + uint32 firstjob=0; + uint32 numofjobs=0xffff; + TALLOC_CTX *mem_ctx = NULL; + + DEBUG(4,("spoolopen - printer: %s server: %s user: %s\n", + printer_name, station, user_name)); + + if(!spoolss_open_printer_ex( printer_name, 0, 0, station, user_name, &hnd)) + return False; + + if ((mem_ctx=talloc_init()) == NULL) + { + DEBUG(0,("msrpc_spoolss_enum_jobs: talloc_init failed!\n")); + return False; + } + init_buffer(&buffer, 0, mem_ctx); + status = spoolss_enum_jobs(&hnd, firstjob, numofjobs, level, + &buffer, 0, &needed, &returned); + + if (status == ERROR_INSUFFICIENT_BUFFER) + { + init_buffer(&buffer, needed, mem_ctx); + status = spoolss_enum_jobs( &hnd, firstjob, numofjobs, level, + &buffer, needed, &needed, &returned); + } + + if (mem_ctx) + talloc_destroy(mem_ctx); + + if (status!=NT_STATUS_OK) { + if (!spoolss_closeprinter(&hnd)) + return False; + return False; + } + + if (fn != NULL) + fn(printer_name, station, level, returned, *ctr); + + return True; +} + + +/**************************************************************************** +nt spoolss query +****************************************************************************/ +BOOL msrpc_spoolss_enum_printerdata( const char* printer_name, + const char* station, const char* user_name ) +{ + POLICY_HND hnd; + NTSTATUS status; + uint32 idx; + uint32 valuelen; + uint16 *value; + uint32 rvaluelen; + uint32 type; + uint32 datalen; + uint8 *data; + uint32 rdatalen; + uint32 maxvaluelen; + uint32 maxdatalen; + + DEBUG(4,("msrpc_spoolss_enum_printerdata - printer: %s\n", printer_name)); + + if(!spoolss_open_printer_ex( printer_name, 0, 0, station, user_name, &hnd)) + return False; + + + idx=0; + valuelen=0; + rvaluelen=0; + type=0; + datalen=0; + rdatalen=0; + + status = spoolss_enum_printerdata(&hnd, idx, &valuelen, value, + &rvaluelen, &type, &datalen, + data, &rdatalen); + + DEBUG(4,("spoolenum_printerdata - got size: biggest value:[%d], biggest data:[%d]\n", rvaluelen, rdatalen)); + + maxvaluelen=valuelen=rvaluelen; + maxdatalen=datalen=rdatalen; + + value=(uint16 *)malloc(valuelen*sizeof(uint16)); + data=(uint8 *)malloc(datalen*sizeof(uint8)); + + display_printer_enumdata(out_hnd, ACTION_HEADER, idx, valuelen, + value, rvaluelen, type, datalen, data, rdatalen); + + do { + valuelen=maxvaluelen; + datalen=maxdatalen; + + status = spoolss_enum_printerdata(&hnd, idx, &valuelen, + value, &rvaluelen, &type, + &datalen, data, &rdatalen); + display_printer_enumdata(out_hnd, ACTION_ENUMERATE, idx, + valuelen, value, rvaluelen, type, + datalen, data, rdatalen); + idx++; + + } while (status != 0x0103); /* NO_MORE_ITEMS */ + + display_printer_enumdata(out_hnd, ACTION_FOOTER, idx, valuelen, + value, rvaluelen, type, datalen, data, rdatalen); + + + if (status!=NT_STATUS_OK) { + /* + * the check on this if statement is redundant + * since is the status is bad we're going to + * return False anyways. The caller will be + * unable to determine if there really was a problem + * with the spoolss_closeprinter() call --jerry + */ + spoolss_closeprinter(&hnd); + return False; + } + + return True; +} + +/**************************************************************************** +nt spoolss query +****************************************************************************/ +BOOL msrpc_spoolss_getprinter( const char* printer_name, const uint32 level, + const char* station, const char* user_name, + PRINTER_INFO_CTR ctr) +{ + POLICY_HND hnd; + NTSTATUS status=0; + NEW_BUFFER buffer; + uint32 needed=1000; + TALLOC_CTX *mem_ctx = NULL; + + DEBUG(4,("spoolenum_getprinter - printer: %s\n", printer_name)); + + if(!spoolss_open_printer_ex( printer_name, "", PRINTER_ALL_ACCESS, station, user_name, &hnd)) + return False; + + if ((mem_ctx=talloc_init()) == NULL) + { + DEBUG(0,("msrpc_spoolss_getprinter: talloc_init failed!\n")); + return False; + } + init_buffer(&buffer, needed, mem_ctx); + + status = spoolss_getprinter(&hnd, level, &buffer, needed, &needed); + + if (status==ERROR_INSUFFICIENT_BUFFER) { + init_buffer(&buffer, needed, mem_ctx); + status = spoolss_getprinter(&hnd, level, &buffer, needed, &needed); + } + + report(out_hnd, "\tstatus:[%d (%x)]\n", status, status); + + if (status!=NT_STATUS_OK) + { + if (mem_ctx) + talloc_destroy(mem_ctx); + return False; + } + + switch (level) { + case 0: + decode_printer_info_0(&buffer, 1, &(ctr.printers_0)); + break; + case 1: + decode_printer_info_1(&buffer, 1, &(ctr.printers_1)); + break; + case 2: + decode_printer_info_2(&buffer, 1, &(ctr.printers_2)); + break; + case 3: + decode_printer_info_3(&buffer, 1, &(ctr.printers_3)); + break; + } + + display_printer_info_ctr(out_hnd, ACTION_HEADER , level, 1, ctr); + display_printer_info_ctr(out_hnd, ACTION_ENUMERATE, level, 1, ctr); + display_printer_info_ctr(out_hnd, ACTION_FOOTER , level, 1, ctr); + + if (mem_ctx) + talloc_destroy(mem_ctx); + + if (status!=NT_STATUS_OK) { + if (!spoolss_closeprinter(&hnd)) + return False; + return False; + } + + return True; +} + +/**************************************************************************** +nt spoolss query +****************************************************************************/ +BOOL msrpc_spoolss_getprinterdriver( const char* printer_name, + const char *environment, const uint32 level, + const char* station, const char* user_name, + PRINTER_DRIVER_CTR ctr) +{ + POLICY_HND hnd; + NTSTATUS status=0; + NEW_BUFFER buffer; + uint32 needed; + TALLOC_CTX *mem_ctx = NULL; + + DEBUG(4,("msrpc_spoolss_enum_getprinterdriver - printer: %s\n", printer_name)); + + if(!spoolss_open_printer_ex( printer_name, "", PRINTER_ALL_ACCESS, station, user_name, &hnd)) + return False; + + if ((mem_ctx=talloc_init()) == NULL) + { + DEBUG(0,("msrpc_spoolss_getprinterdriver: talloc_init failed!\n")); + return False; + } + init_buffer(&buffer, 0, mem_ctx); + + status = spoolss_getprinterdriver(&hnd, environment, level, &buffer, 0, &needed); + + if (status==ERROR_INSUFFICIENT_BUFFER) { + init_buffer(&buffer, needed, mem_ctx); + status = spoolss_getprinterdriver(&hnd, environment, level, &buffer, needed, &needed); + } + + /* report(out_hnd, "\tstatus:[%d (%x)]\n", status, status); */ + + if (status!=NT_STATUS_OK) + { + if (mem_ctx) + talloc_destroy(mem_ctx); + return False; + } + + switch (level) { + case 1: + decode_printer_driver_1(&buffer, 1, &(ctr.info1)); + break; + case 2: + decode_printer_driver_2(&buffer, 1, &(ctr.info2)); + break; + case 3: + decode_printer_driver_3(&buffer, 1, &(ctr.info3)); + break; + } + + display_printer_driver_ctr(out_hnd, ACTION_HEADER , level, 1, ctr); + display_printer_driver_ctr(out_hnd, ACTION_ENUMERATE, level, 1, ctr); + display_printer_driver_ctr(out_hnd, ACTION_FOOTER , level, 1, ctr); + + if (mem_ctx) + talloc_destroy(mem_ctx); + + if (status!=NT_STATUS_OK) { + if (!spoolss_closeprinter(&hnd)) + return False; + return False; + } + + return True; +} + +/**************************************************************************** +nt spoolss query +****************************************************************************/ +BOOL msrpc_spoolss_enumprinterdrivers( const char* srv_name, + const char *environment, const uint32 level, + PRINTER_DRIVER_CTR ctr) +{ + NTSTATUS status=0; + NEW_BUFFER buffer; + uint32 needed; + uint32 returned; + TALLOC_CTX *mem_ctx = NULL; + + DEBUG(4,("msrpc_spoolss_enum_enumprinterdrivers - server: %s\n", srv_name)); + + if ((mem_ctx=talloc_init()) == NULL) + { + DEBUG(0,("msrpc_spoolss_enumprinterdrivers: talloc_init failed!\n")); + return False; + } + init_buffer(&buffer, 0, mem_ctx); + + status = spoolss_enum_printerdrivers(srv_name, environment, + level, &buffer, 0, &needed, &returned); + + if (status == ERROR_INSUFFICIENT_BUFFER) + { + init_buffer(&buffer, needed, mem_ctx); + status = spoolss_enum_printerdrivers( srv_name, environment, + level, &buffer, needed, &needed, &returned); + } + + report(out_hnd, "\tstatus:[%d (%x)]\n", status, status); + + if (status!=NT_STATUS_OK) + { + if (mem_ctx) + talloc_destroy(mem_ctx); + return False; + } + + switch (level) + { + case 1: + decode_printer_driver_1(&buffer, returned, &(ctr.info1)); + break; + case 2: + decode_printer_driver_2(&buffer, returned, &(ctr.info2)); + break; + case 3: + decode_printer_driver_3(&buffer, returned, &(ctr.info3)); + break; + } + + display_printer_driver_ctr(out_hnd, ACTION_HEADER , level, returned, ctr); + display_printer_driver_ctr(out_hnd, ACTION_ENUMERATE, level, returned, ctr); + display_printer_driver_ctr(out_hnd, ACTION_FOOTER , level, returned, ctr); + + if (mem_ctx) + talloc_destroy(mem_ctx); + + return True; +} + +/**************************************************************************** +nt spoolss query +****************************************************************************/ +BOOL msrpc_spoolss_getprinterdriverdir(char* srv_name, char* env_name, uint32 level, DRIVER_DIRECTORY_CTR ctr) +{ + NTSTATUS status; + NEW_BUFFER buffer; + uint32 needed; + TALLOC_CTX *mem_ctx = NULL; + + if ((mem_ctx=talloc_init()) == NULL) + { + DEBUG(0,("msrpc_spoolss_getprinterdriverdir: talloc_init failed!\n")); + return False; + } + init_buffer(&buffer, 0, mem_ctx); + + /* send a NULL buffer first */ + status=spoolss_getprinterdriverdir(srv_name, env_name, level, &buffer, 0, &needed); + + if (status==ERROR_INSUFFICIENT_BUFFER) { + init_buffer(&buffer, needed, mem_ctx); + status=spoolss_getprinterdriverdir(srv_name, env_name, level, &buffer, needed, &needed); + } + + report(out_hnd, "\tstatus:[%d (%x)]\n", status, status); + + if (status!=NT_STATUS_OK) + { + if (mem_ctx) + talloc_destroy(mem_ctx); + return False; + } + + switch (level) { + case 1: + decode_printerdriverdir_info_1(&buffer, &(ctr.driver.info_1)); + break; + } + + display_printerdriverdir_info_ctr(out_hnd, ACTION_HEADER , level, ctr); + display_printerdriverdir_info_ctr(out_hnd, ACTION_ENUMERATE, level, ctr); + display_printerdriverdir_info_ctr(out_hnd, ACTION_FOOTER , level, ctr); + + if (mem_ctx) + talloc_destroy(mem_ctx); + + return True; +} |