| OLD | NEW |
| (Empty) |
| 1 #ifdef _WINDOWS | |
| 2 | |
| 3 /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| 4 * contributor license agreements. See the NOTICE file distributed with | |
| 5 * this work for additional information regarding copyright ownership. | |
| 6 * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| 7 * (the "License"); you may not use this file except in compliance with | |
| 8 * the License. You may obtain a copy of the License at | |
| 9 * | |
| 10 * http://www.apache.org/licenses/LICENSE-2.0 | |
| 11 * | |
| 12 * Unless required by applicable law or agreed to in writing, software | |
| 13 * distributed under the License is distributed on an "AS IS" BASIS, | |
| 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 15 * See the License for the specific language governing permissions and | |
| 16 * limitations under the License. | |
| 17 */ | |
| 18 | |
| 19 /** NT Pipes network wrapper | |
| 20 * | |
| 21 * @author Mladen Turk | |
| 22 * @version $Id: ntpipe.c 1442587 2013-02-05 13:49:48Z rjung $ | |
| 23 */ | |
| 24 | |
| 25 | |
| 26 #ifndef _WIN32_WINNT | |
| 27 #define _WIN32_WINNT 0x0500 | |
| 28 #endif | |
| 29 #define STRICT | |
| 30 #include <winsock2.h> | |
| 31 #include <mswsock.h> | |
| 32 #include <ws2tcpip.h> | |
| 33 #include <sddl.h> | |
| 34 | |
| 35 #include "tcn.h" | |
| 36 #include "apr_thread_mutex.h" | |
| 37 #include "apr_poll.h" | |
| 38 | |
| 39 #ifdef TCN_DO_STATISTICS | |
| 40 #include "apr_atomic.h" | |
| 41 | |
| 42 static volatile apr_uint32_t ntp_created = 0; | |
| 43 static volatile apr_uint32_t ntp_closed = 0; | |
| 44 static volatile apr_uint32_t ntp_cleared = 0; | |
| 45 static volatile apr_uint32_t ntp_accepted = 0; | |
| 46 | |
| 47 void ntp_network_dump_statistics() | |
| 48 { | |
| 49 fprintf(stderr, "NT Network Statistics ..\n"); | |
| 50 fprintf(stderr, "Sockets created : %d\n", ntp_created); | |
| 51 fprintf(stderr, "Sockets accepted : %d\n", ntp_accepted); | |
| 52 fprintf(stderr, "Sockets closed : %d\n", ntp_closed); | |
| 53 fprintf(stderr, "Sockets cleared : %d\n", ntp_cleared); | |
| 54 } | |
| 55 | |
| 56 #endif | |
| 57 | |
| 58 #define DEFNAME "\\\\.\\PIPE\\TOMCATNATIVEPIPE" | |
| 59 #define DEFNAME_FMT "\\\\.\\PIPE\\TOMCATNATIVEPIPE%08X%08X" | |
| 60 #define DEFSIZE 8192 | |
| 61 #define DEFTIMEOUT 60000 | |
| 62 | |
| 63 #define TCN_NTP_UNKNOWN 0 | |
| 64 #define TCN_NTP_CLIENT 1 | |
| 65 #define TCN_NTP_SERVER 2 | |
| 66 | |
| 67 typedef struct { | |
| 68 apr_pool_t *pool; | |
| 69 apr_socket_t *sock; /* Dummy socket */ | |
| 70 OVERLAPPED rd_o; | |
| 71 OVERLAPPED wr_o; | |
| 72 HANDLE h_pipe; | |
| 73 HANDLE rd_event; | |
| 74 HANDLE wr_event; | |
| 75 DWORD timeout; | |
| 76 int mode; /* Client or server mode */ | |
| 77 int nmax; | |
| 78 DWORD sndbuf; | |
| 79 DWORD rcvbuf; | |
| 80 char name[MAX_PATH+1]; | |
| 81 SECURITY_ATTRIBUTES sa; | |
| 82 } tcn_ntp_conn_t; | |
| 83 | |
| 84 static const char *NTSD_STRING = "D:" /* Discretionary ACL */ | |
| 85 "(D;OICI;GA;;;BG)" /* Deny access to Built-in Guests */ | |
| 86 "(D;OICI;GA;;;AN)" /* Deny access to Anonymous Logon */ | |
| 87 "(A;OICI;GRGWGX;;;AU)" /* Allow read/write/execute to Authent
icated Users */ | |
| 88 "(A;OICI;GA;;;BA)" /* Allow full control to Administrator
s */ | |
| 89 "(A;OICI;GA;;;LS)" /* Allow full control to Local service
account */ | |
| 90 "(A;OICI;GA;;;SY)"; /* Allow full control to Local system
*/ | |
| 91 | |
| 92 | |
| 93 | |
| 94 static apr_status_t APR_THREAD_FUNC | |
| 95 ntp_socket_timeout_set(apr_socket_t *sock, apr_interval_time_t t) | |
| 96 { | |
| 97 tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock; | |
| 98 if (t < 0) | |
| 99 con->timeout = INFINITE; | |
| 100 else | |
| 101 con->timeout = (DWORD)(apr_time_as_msec(t)); | |
| 102 return APR_SUCCESS; | |
| 103 } | |
| 104 | |
| 105 static apr_status_t APR_THREAD_FUNC | |
| 106 ntp_socket_timeout_get(apr_socket_t *sock, apr_interval_time_t *t) | |
| 107 { | |
| 108 tcn_ntp_conn_t *con = (tcn_ntp_conn_t*)sock; | |
| 109 if (con->timeout == INFINITE) | |
| 110 *t = -1; | |
| 111 else | |
| 112 *t = con->timeout * 1000; | |
| 113 return APR_SUCCESS; | |
| 114 } | |
| 115 | |
| 116 static APR_INLINE apr_status_t APR_THREAD_FUNC | |
| 117 ntp_socket_opt_set(apr_socket_t *sock, apr_int32_t opt, apr_int32_t on) | |
| 118 { | |
| 119 tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock; | |
| 120 apr_status_t rv = APR_SUCCESS; | |
| 121 switch (opt) { | |
| 122 case APR_SO_SNDBUF: | |
| 123 con->sndbuf = (DWORD)on; | |
| 124 break; | |
| 125 case APR_SO_RCVBUF: | |
| 126 con->rcvbuf = (DWORD)on; | |
| 127 break; | |
| 128 default: | |
| 129 rv = APR_EINVAL; | |
| 130 break; | |
| 131 } | |
| 132 return rv; | |
| 133 } | |
| 134 | |
| 135 static APR_INLINE apr_status_t APR_THREAD_FUNC | |
| 136 ntp_socket_opt_get(apr_socket_t *sock, apr_int32_t opt, apr_int32_t *on) | |
| 137 { | |
| 138 tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock; | |
| 139 apr_status_t rv = APR_SUCCESS; | |
| 140 switch (opt) { | |
| 141 case APR_SO_SNDBUF: | |
| 142 *on = con->sndbuf; | |
| 143 break; | |
| 144 case APR_SO_RCVBUF: | |
| 145 *on = con->rcvbuf; | |
| 146 break; | |
| 147 default: | |
| 148 rv = APR_EINVAL; | |
| 149 break; | |
| 150 } | |
| 151 return rv; | |
| 152 } | |
| 153 | |
| 154 static apr_status_t ntp_cleanup(void *data) | |
| 155 { | |
| 156 tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)data; | |
| 157 | |
| 158 if (con) { | |
| 159 if (con->h_pipe) { | |
| 160 FlushFileBuffers(con->h_pipe); | |
| 161 CloseHandle(con->h_pipe); | |
| 162 con->h_pipe = NULL; | |
| 163 } | |
| 164 if (con->rd_event) { | |
| 165 CloseHandle(con->rd_event); | |
| 166 con->rd_event = NULL; | |
| 167 } | |
| 168 if (con->wr_event) { | |
| 169 CloseHandle(con->wr_event); | |
| 170 con->wr_event= NULL; | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 #ifdef TCN_DO_STATISTICS | |
| 175 apr_atomic_inc32(&ntp_cleared); | |
| 176 #endif | |
| 177 return APR_SUCCESS; | |
| 178 } | |
| 179 | |
| 180 static apr_status_t APR_THREAD_FUNC | |
| 181 ntp_socket_shutdown(apr_socket_t *sock, apr_shutdown_how_e how) | |
| 182 { | |
| 183 UNREFERENCED(how); | |
| 184 return ntp_cleanup(sock);; | |
| 185 } | |
| 186 | |
| 187 static apr_status_t APR_THREAD_FUNC | |
| 188 ntp_socket_close(apr_socket_t *sock) | |
| 189 { | |
| 190 #ifdef TCN_DO_STATISTICS | |
| 191 apr_atomic_inc32(&ntp_closed); | |
| 192 #endif | |
| 193 return ntp_cleanup(sock);; | |
| 194 } | |
| 195 | |
| 196 static apr_status_t APR_THREAD_FUNC | |
| 197 ntp_socket_recv(apr_socket_t *sock, char *buf, apr_size_t *len) | |
| 198 { | |
| 199 tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock; | |
| 200 DWORD readed; | |
| 201 | |
| 202 if (!ReadFile(con->h_pipe, buf, (DWORD)*len, &readed, &con->rd_o)) { | |
| 203 DWORD err = GetLastError(); | |
| 204 if (err == ERROR_IO_PENDING) { | |
| 205 DWORD r = WaitForSingleObject(con->rd_event, con->timeout); | |
| 206 if (r == WAIT_TIMEOUT) | |
| 207 return APR_TIMEUP; | |
| 208 else if (r != WAIT_OBJECT_0) | |
| 209 return APR_EOF; | |
| 210 } | |
| 211 else if (err == ERROR_BROKEN_PIPE || err == ERROR_NO_DATA) { | |
| 212 /* Server closed the pipe */ | |
| 213 return APR_EOF; | |
| 214 } | |
| 215 GetOverlappedResult(con->h_pipe, &con->rd_o, &readed, FALSE); | |
| 216 } | |
| 217 *len = readed; | |
| 218 return APR_SUCCESS; | |
| 219 } | |
| 220 | |
| 221 static apr_status_t APR_THREAD_FUNC | |
| 222 ntp_socket_send(apr_socket_t *sock, const char *buf, | |
| 223 apr_size_t *len) | |
| 224 { | |
| 225 tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock; | |
| 226 DWORD written; | |
| 227 | |
| 228 if (!WriteFile(con->h_pipe, buf, (DWORD)*len, &written, &con->wr_o)) { | |
| 229 DWORD err = GetLastError(); | |
| 230 if (err == ERROR_IO_PENDING) { | |
| 231 DWORD r = WaitForSingleObject(con->wr_event, con->timeout); | |
| 232 if (r == WAIT_TIMEOUT) | |
| 233 return APR_TIMEUP; | |
| 234 else if (r != WAIT_OBJECT_0) | |
| 235 return APR_EOF; | |
| 236 } | |
| 237 else if (err == ERROR_BROKEN_PIPE || err == ERROR_NO_DATA) { | |
| 238 /* Server closed the pipe */ | |
| 239 return APR_EOF; | |
| 240 } | |
| 241 GetOverlappedResult(con->h_pipe, &con->wr_o, &written, FALSE); | |
| 242 } | |
| 243 *len = written; | |
| 244 return APR_SUCCESS; | |
| 245 } | |
| 246 | |
| 247 static apr_status_t APR_THREAD_FUNC | |
| 248 ntp_socket_sendv(apr_socket_t *sock, | |
| 249 const struct iovec *vec, | |
| 250 apr_int32_t nvec, apr_size_t *len) | |
| 251 { | |
| 252 tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock; | |
| 253 apr_status_t rv; | |
| 254 apr_size_t written = 0; | |
| 255 apr_int32_t i; | |
| 256 | |
| 257 for (i = 0; i < nvec; i++) { | |
| 258 apr_size_t rd = vec[i].iov_len; | |
| 259 if ((rv = ntp_socket_send((apr_socket_t *)con, | |
| 260 vec[i].iov_base, &rd)) != APR_SUCCESS) { | |
| 261 *len = written; | |
| 262 return rv; | |
| 263 } | |
| 264 written += rd; | |
| 265 } | |
| 266 *len = written; | |
| 267 return APR_SUCCESS; | |
| 268 } | |
| 269 | |
| 270 static apr_status_t ntp_socket_cleanup(void *data) | |
| 271 { | |
| 272 tcn_socket_t *s = (tcn_socket_t *)data; | |
| 273 | |
| 274 if (s->net->cleanup) { | |
| 275 (*s->net->cleanup)(s->opaque); | |
| 276 s->net->cleanup = NULL; | |
| 277 } | |
| 278 #ifdef TCN_DO_STATISTICS | |
| 279 apr_atomic_inc32(&ntp_cleared); | |
| 280 #endif | |
| 281 return APR_SUCCESS; | |
| 282 } | |
| 283 | |
| 284 static tcn_nlayer_t ntp_socket_layer = { | |
| 285 TCN_SOCKET_NTPIPE, | |
| 286 ntp_cleanup, | |
| 287 ntp_socket_close, | |
| 288 ntp_socket_shutdown, | |
| 289 ntp_socket_opt_get, | |
| 290 ntp_socket_opt_set, | |
| 291 ntp_socket_timeout_get, | |
| 292 ntp_socket_timeout_set, | |
| 293 ntp_socket_send, | |
| 294 ntp_socket_sendv, | |
| 295 ntp_socket_recv | |
| 296 }; | |
| 297 | |
| 298 static BOOL create_DACL(LPSECURITY_ATTRIBUTES psa) | |
| 299 { | |
| 300 | |
| 301 return ConvertStringSecurityDescriptorToSecurityDescriptor( | |
| 302 NTSD_STRING, | |
| 303 SDDL_REVISION_1, | |
| 304 &(psa->lpSecurityDescriptor), | |
| 305 NULL); | |
| 306 } | |
| 307 | |
| 308 TCN_IMPLEMENT_CALL(jlong, Local, create)(TCN_STDARGS, jstring name, | |
| 309 jlong pool) | |
| 310 { | |
| 311 apr_pool_t *p = J2P(pool, apr_pool_t *); | |
| 312 tcn_socket_t *s = NULL; | |
| 313 tcn_ntp_conn_t *con = NULL; | |
| 314 TCN_ALLOC_CSTRING(name); | |
| 315 | |
| 316 UNREFERENCED(o); | |
| 317 TCN_ASSERT(pool != 0); | |
| 318 | |
| 319 #ifdef TCN_DO_STATISTICS | |
| 320 ntp_created++; | |
| 321 #endif | |
| 322 con = (tcn_ntp_conn_t *)apr_pcalloc(p, sizeof(tcn_ntp_conn_t)); | |
| 323 con->pool = p; | |
| 324 con->mode = TCN_NTP_UNKNOWN; | |
| 325 con->nmax = PIPE_UNLIMITED_INSTANCES; | |
| 326 con->timeout = DEFTIMEOUT; | |
| 327 con->sndbuf = DEFSIZE; | |
| 328 con->rcvbuf = DEFSIZE; | |
| 329 if (J2S(name)) { | |
| 330 strncpy(con->name, J2S(name), MAX_PATH); | |
| 331 con->name[MAX_PATH] = '\0'; | |
| 332 TCN_FREE_CSTRING(name); | |
| 333 } | |
| 334 else | |
| 335 strcpy(con->name, DEFNAME); | |
| 336 con->sa.nLength = sizeof(con->sa); | |
| 337 con->sa.bInheritHandle = TRUE; | |
| 338 if (!create_DACL(&con->sa)) { | |
| 339 tcn_ThrowAPRException(e, apr_get_os_error()); | |
| 340 return 0; | |
| 341 } | |
| 342 | |
| 343 s = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t)); | |
| 344 s->pool = p; | |
| 345 s->net = &ntp_socket_layer; | |
| 346 s->opaque = con; | |
| 347 apr_pool_cleanup_register(p, (const void *)s, | |
| 348 ntp_socket_cleanup, | |
| 349 apr_pool_cleanup_null); | |
| 350 | |
| 351 fflush(stderr); | |
| 352 return P2J(s); | |
| 353 | |
| 354 } | |
| 355 | |
| 356 TCN_IMPLEMENT_CALL(jint, Local, bind)(TCN_STDARGS, jlong sock, | |
| 357 jlong sa) | |
| 358 { | |
| 359 tcn_socket_t *s = J2P(sock, tcn_socket_t *); | |
| 360 UNREFERENCED_STDARGS; | |
| 361 UNREFERENCED(sa); | |
| 362 TCN_ASSERT(sock != 0); | |
| 363 if (s->net->type == TCN_SOCKET_NTPIPE) { | |
| 364 tcn_ntp_conn_t *c = (tcn_ntp_conn_t *)s->opaque; | |
| 365 c->mode = TCN_NTP_SERVER; | |
| 366 return APR_SUCCESS; | |
| 367 } | |
| 368 else | |
| 369 return APR_EINVAL; | |
| 370 } | |
| 371 | |
| 372 TCN_IMPLEMENT_CALL(jint, Local, listen)(TCN_STDARGS, jlong sock, | |
| 373 jint backlog) | |
| 374 { | |
| 375 tcn_socket_t *s = J2P(sock, tcn_socket_t *); | |
| 376 UNREFERENCED_STDARGS; | |
| 377 | |
| 378 TCN_ASSERT(sock != 0); | |
| 379 if (s->net->type == TCN_SOCKET_NTPIPE) { | |
| 380 tcn_ntp_conn_t *c = (tcn_ntp_conn_t *)s->opaque; | |
| 381 c->mode = TCN_NTP_SERVER; | |
| 382 if (backlog > 0) | |
| 383 c->nmax = backlog; | |
| 384 else | |
| 385 c->nmax = PIPE_UNLIMITED_INSTANCES; | |
| 386 return APR_SUCCESS; | |
| 387 } | |
| 388 else | |
| 389 return APR_EINVAL; | |
| 390 } | |
| 391 | |
| 392 TCN_IMPLEMENT_CALL(jlong, Local, accept)(TCN_STDARGS, jlong sock) | |
| 393 { | |
| 394 tcn_socket_t *s = J2P(sock, tcn_socket_t *); | |
| 395 apr_pool_t *p = NULL; | |
| 396 tcn_socket_t *a = NULL; | |
| 397 tcn_ntp_conn_t *con = NULL; | |
| 398 | |
| 399 UNREFERENCED(o); | |
| 400 TCN_ASSERT(sock != 0); | |
| 401 | |
| 402 TCN_THROW_IF_ERR(apr_pool_create(&p, s->pool), p); | |
| 403 if (s->net->type == TCN_SOCKET_NTPIPE) { | |
| 404 tcn_ntp_conn_t *c = (tcn_ntp_conn_t *)s->opaque; | |
| 405 con = (tcn_ntp_conn_t *)apr_pcalloc(p, sizeof(tcn_ntp_conn_t)); | |
| 406 con->pool = p; | |
| 407 con->mode = TCN_NTP_SERVER; | |
| 408 con->nmax = c->nmax; | |
| 409 con->timeout = c->timeout; | |
| 410 strcpy(con->name, c->name); | |
| 411 con->h_pipe = CreateNamedPipe(con->name, | |
| 412 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, | |
| 413 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE
_WAIT, | |
| 414 con->nmax, | |
| 415 con->sndbuf, | |
| 416 con->rcvbuf, | |
| 417 con->timeout, | |
| 418 &c->sa); | |
| 419 if (con->h_pipe == INVALID_HANDLE_VALUE) { | |
| 420 tcn_ThrowAPRException(e, apr_get_os_error()); | |
| 421 goto cleanup; | |
| 422 } | |
| 423 /* Block until a client connects */ | |
| 424 if (!ConnectNamedPipe(con->h_pipe, NULL)) { | |
| 425 DWORD err = GetLastError(); | |
| 426 if (err != ERROR_PIPE_CONNECTED) { | |
| 427 CloseHandle(con->h_pipe); | |
| 428 tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(err)); | |
| 429 goto cleanup; | |
| 430 } | |
| 431 } | |
| 432 /* Create overlapped events */ | |
| 433 con->rd_event = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 434 con->rd_o.hEvent = con->rd_event; | |
| 435 con->wr_event = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 436 con->wr_o.hEvent = con->wr_event; | |
| 437 } | |
| 438 else { | |
| 439 tcn_ThrowAPRException(e, APR_ENOTIMPL); | |
| 440 goto cleanup; | |
| 441 } | |
| 442 if (con) { | |
| 443 #ifdef TCN_DO_STATISTICS | |
| 444 apr_atomic_inc32(&ntp_accepted); | |
| 445 #endif | |
| 446 a = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t)); | |
| 447 a->pool = p; | |
| 448 a->net = &ntp_socket_layer; | |
| 449 a->opaque = con; | |
| 450 apr_pool_cleanup_register(p, (const void *)a, | |
| 451 ntp_socket_cleanup, | |
| 452 apr_pool_cleanup_null); | |
| 453 } | |
| 454 return P2J(a); | |
| 455 cleanup: | |
| 456 if (p) | |
| 457 apr_pool_destroy(p); | |
| 458 return 0; | |
| 459 } | |
| 460 | |
| 461 TCN_IMPLEMENT_CALL(jint, Local, connect)(TCN_STDARGS, jlong sock, | |
| 462 jlong sa) | |
| 463 { | |
| 464 tcn_socket_t *s = J2P(sock, tcn_socket_t *); | |
| 465 apr_pool_t *p = NULL; | |
| 466 tcn_socket_t *a = NULL; | |
| 467 tcn_ntp_conn_t *con = NULL; | |
| 468 | |
| 469 UNREFERENCED(o); | |
| 470 UNREFERENCED(sa); | |
| 471 TCN_ASSERT(sock != 0); | |
| 472 if (s->net->type != TCN_SOCKET_NTPIPE) | |
| 473 return APR_ENOTSOCK; | |
| 474 con = (tcn_ntp_conn_t *)s->opaque; | |
| 475 if (con->mode == TCN_NTP_SERVER) | |
| 476 return APR_EINVAL; | |
| 477 con->mode = TCN_NTP_CLIENT; | |
| 478 | |
| 479 while (TRUE) { | |
| 480 con->h_pipe = CreateFile(con->name, | |
| 481 GENERIC_WRITE | GENERIC_READ, | |
| 482 FILE_SHARE_READ | FILE_SHARE_WRITE , | |
| 483 NULL, | |
| 484 OPEN_EXISTING, | |
| 485 FILE_FLAG_OVERLAPPED, | |
| 486 NULL); | |
| 487 if (con->h_pipe != INVALID_HANDLE_VALUE) | |
| 488 break; | |
| 489 if (GetLastError() == ERROR_PIPE_BUSY) { | |
| 490 /* All pipe instances are busy, so wait for | |
| 491 * timeout value specified by the server process in | |
| 492 * the CreateNamedPipe function. | |
| 493 */ | |
| 494 if (!WaitNamedPipe(con->name, NMPWAIT_USE_DEFAULT_WAIT)) | |
| 495 return apr_get_os_error(); | |
| 496 } | |
| 497 else | |
| 498 return apr_get_os_error(); | |
| 499 } | |
| 500 | |
| 501 /* Create overlapped events */ | |
| 502 con->rd_event = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 503 con->rd_o.hEvent = con->rd_event; | |
| 504 con->wr_event = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 505 con->wr_o.hEvent = con->wr_event; | |
| 506 | |
| 507 return APR_SUCCESS; | |
| 508 } | |
| 509 | |
| 510 #endif | |
| 511 | |
| OLD | NEW |