1 5331 amw /* 2 5331 amw * CDDL HEADER START 3 5331 amw * 4 5331 amw * The contents of this file are subject to the terms of the 5 5331 amw * Common Development and Distribution License (the "License"). 6 5331 amw * You may not use this file except in compliance with the License. 7 5331 amw * 8 5331 amw * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 5331 amw * or http://www.opensolaris.org/os/licensing. 10 5331 amw * See the License for the specific language governing permissions 11 5331 amw * and limitations under the License. 12 5331 amw * 13 5331 amw * When distributing Covered Code, include this CDDL HEADER in each 14 5331 amw * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 5331 amw * If applicable, add the following below this CDDL HEADER, with the 16 5331 amw * fields enclosed by brackets "[]" replaced with your own identifying 17 5331 amw * information: Portions Copyright [yyyy] [name of copyright owner] 18 5331 amw * 19 5331 amw * CDDL HEADER END 20 5331 amw */ 21 5331 amw /* 22 8474 Jose * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 5331 amw * Use is subject to license terms. 24 5331 amw */ 25 5331 amw 26 5331 amw /* 27 5331 amw * Server side RPC handler. 28 5331 amw */ 29 5331 amw 30 5521 as200622 #include <sys/byteorder.h> 31 7052 amw #include <sys/errno.h> 32 7052 amw #include <sys/uio.h> 33 5331 amw #include <thread.h> 34 5331 amw #include <synch.h> 35 5331 amw #include <stdlib.h> 36 5331 amw #include <strings.h> 37 5331 amw #include <string.h> 38 5331 amw #include <time.h> 39 5331 amw 40 5331 amw #include <smbsrv/libsmb.h> 41 5521 as200622 #include <smbsrv/libmlrpc.h> 42 8474 Jose #include <smbsrv/ntaccess.h> 43 5331 amw 44 5331 amw /* 45 5331 amw * Fragment size (5680: NT style). 46 5331 amw */ 47 8334 Jose #define NDR_FRAG_SZ 5680 48 5331 amw 49 8334 Jose #define NDR_PIPE_BUFSZ 65536 50 8334 Jose #define NDR_PIPE_MAX 128 51 8334 Jose static ndr_pipe_t ndr_pipe_table[NDR_PIPE_MAX]; 52 8334 Jose static mutex_t ndr_pipe_lock; 53 7052 amw 54 8334 Jose static int ndr_pipe_transact(ndr_pipe_t *); 55 8334 Jose static ndr_pipe_t *ndr_pipe_lookup(int); 56 8334 Jose static void ndr_pipe_release(ndr_pipe_t *); 57 8334 Jose static ndr_pipe_t *ndr_pipe_allocate(int); 58 8334 Jose static void ndr_pipe_deallocate(ndr_pipe_t *); 59 8334 Jose static void ndr_pipe_rewind(ndr_pipe_t *); 60 8334 Jose static void ndr_pipe_flush(ndr_pipe_t *); 61 5331 amw 62 8334 Jose static int ndr_svc_process(ndr_xa_t *); 63 8334 Jose static int ndr_svc_bind(ndr_xa_t *); 64 8334 Jose static int ndr_svc_request(ndr_xa_t *); 65 8334 Jose static void ndr_reply_prepare_hdr(ndr_xa_t *); 66 8334 Jose static int ndr_svc_alter_context(ndr_xa_t *); 67 8334 Jose static void ndr_reply_fault(ndr_xa_t *, unsigned long); 68 8334 Jose static int ndr_build_reply(ndr_xa_t *); 69 8334 Jose static void ndr_build_frag(ndr_stream_t *, uint8_t *, uint32_t); 70 5331 amw 71 5331 amw /* 72 7052 amw * Allocate and associate a service context with a fid. 73 5331 amw */ 74 7052 amw int 75 8334 Jose ndr_pipe_open(int fid, uint8_t *data, uint32_t datalen) 76 5331 amw { 77 8334 Jose ndr_pipe_t *np; 78 7052 amw 79 8334 Jose (void) mutex_lock(&ndr_pipe_lock); 80 7052 amw 81 8334 Jose if ((np = ndr_pipe_lookup(fid)) != NULL) { 82 8334 Jose ndr_pipe_release(np); 83 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 84 7052 amw return (EEXIST); 85 7052 amw } 86 7052 amw 87 8334 Jose if ((np = ndr_pipe_allocate(fid)) == NULL) { 88 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 89 7052 amw return (ENOMEM); 90 7052 amw } 91 7052 amw 92 10122 Jordan if (smb_netuserinfo_decode(&np->np_user, data, datalen, NULL) == -1) { 93 8334 Jose ndr_pipe_release(np); 94 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 95 7052 amw return (EINVAL); 96 7052 amw } 97 7052 amw 98 8334 Jose ndr_svc_binding_pool_init(&np->np_binding, np->np_binding_pool, 99 8334 Jose NDR_N_BINDING_POOL); 100 7052 amw 101 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 102 7052 amw return (0); 103 7052 amw } 104 7052 amw 105 7052 amw /* 106 7052 amw * Release the context associated with a fid when an opipe is closed. 107 7052 amw */ 108 7052 amw int 109 8334 Jose ndr_pipe_close(int fid) 110 7052 amw { 111 8334 Jose ndr_pipe_t *np; 112 7052 amw 113 8334 Jose (void) mutex_lock(&ndr_pipe_lock); 114 7052 amw 115 8334 Jose if ((np = ndr_pipe_lookup(fid)) == NULL) { 116 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 117 7052 amw return (ENOENT); 118 7052 amw } 119 7052 amw 120 7052 amw /* 121 7052 amw * Release twice: once for the lookup above 122 7052 amw * and again to close the fid. 123 7052 amw */ 124 8334 Jose ndr_pipe_release(np); 125 8334 Jose ndr_pipe_release(np); 126 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 127 7052 amw return (0); 128 7052 amw } 129 7052 amw 130 7052 amw /* 131 7052 amw * Write RPC request data to the input stream. Input data is buffered 132 7052 amw * until the response is requested. 133 7052 amw */ 134 7052 amw int 135 8334 Jose ndr_pipe_write(int fid, uint8_t *buf, uint32_t len) 136 7052 amw { 137 8334 Jose ndr_pipe_t *np; 138 7052 amw ssize_t nbytes; 139 7052 amw 140 7052 amw if (len == 0) 141 7052 amw return (0); 142 7052 amw 143 8334 Jose (void) mutex_lock(&ndr_pipe_lock); 144 7052 amw 145 8334 Jose if ((np = ndr_pipe_lookup(fid)) == NULL) { 146 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 147 7052 amw return (ENOENT); 148 7052 amw } 149 7052 amw 150 8334 Jose nbytes = ndr_uiomove((caddr_t)buf, len, UIO_READ, &np->np_uio); 151 7052 amw 152 8334 Jose ndr_pipe_release(np); 153 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 154 7052 amw return ((nbytes == len) ? 0 : EIO); 155 7052 amw } 156 7052 amw 157 7052 amw /* 158 7052 amw * Read RPC response data. If the input stream contains an RPC request, 159 7052 amw * we need to process the RPC transaction, which will place the RPC 160 7052 amw * response in the output (frags) stream. Otherwise, read data from 161 7052 amw * the output stream. 162 7052 amw */ 163 7052 amw int 164 8334 Jose ndr_pipe_read(int fid, uint8_t *buf, uint32_t *len, uint32_t *resid) 165 7052 amw { 166 8334 Jose ndr_pipe_t *np; 167 7052 amw ssize_t nbytes = *len; 168 7052 amw int rc; 169 7052 amw 170 7052 amw if (nbytes == 0) { 171 7052 amw *resid = 0; 172 7052 amw return (0); 173 7052 amw } 174 7052 amw 175 8334 Jose (void) mutex_lock(&ndr_pipe_lock); 176 8334 Jose if ((np = ndr_pipe_lookup(fid)) == NULL) { 177 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 178 7052 amw return (ENOENT); 179 7052 amw } 180 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 181 7052 amw 182 8334 Jose if (np->np_uio.uio_offset) { 183 8334 Jose if ((rc = ndr_pipe_transact(np)) != 0) { 184 8334 Jose ndr_pipe_flush(np); 185 8334 Jose (void) mutex_lock(&ndr_pipe_lock); 186 8334 Jose ndr_pipe_release(np); 187 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 188 7052 amw return (rc); 189 7052 amw } 190 7052 amw 191 7052 amw } 192 7052 amw 193 8334 Jose *len = ndr_uiomove((caddr_t)buf, nbytes, UIO_WRITE, &np->np_frags.uio); 194 8334 Jose *resid = np->np_frags.uio.uio_resid; 195 7052 amw 196 7052 amw if (*resid == 0) { 197 7052 amw /* 198 7052 amw * Nothing left, cleanup the output stream. 199 7052 amw */ 200 8334 Jose ndr_pipe_flush(np); 201 7052 amw } 202 7052 amw 203 8334 Jose (void) mutex_lock(&ndr_pipe_lock); 204 8334 Jose ndr_pipe_release(np); 205 8334 Jose (void) mutex_unlock(&ndr_pipe_lock); 206 7052 amw return (0); 207 7052 amw } 208 7052 amw 209 7052 amw /* 210 7052 amw * Process a server-side RPC request. 211 7052 amw */ 212 7052 amw static int 213 8334 Jose ndr_pipe_transact(ndr_pipe_t *np) 214 7052 amw { 215 8334 Jose ndr_xa_t *mxa; 216 8334 Jose ndr_stream_t *recv_nds; 217 8334 Jose ndr_stream_t *send_nds; 218 8334 Jose char *data; 219 8334 Jose int datalen; 220 5331 amw 221 8334 Jose data = np->np_buf; 222 8334 Jose datalen = np->np_uio.uio_offset; 223 5331 amw 224 7052 amw if ((mxa = (ndr_xa_t *)malloc(sizeof (ndr_xa_t))) == NULL) 225 7052 amw return (ENOMEM); 226 5331 amw 227 8334 Jose bzero(mxa, sizeof (ndr_xa_t)); 228 8334 Jose mxa->fid = np->np_fid; 229 8334 Jose mxa->pipe = np; 230 8334 Jose mxa->binding_list = np->np_binding; 231 5331 amw 232 8334 Jose if ((mxa->heap = ndr_heap_create()) == NULL) { 233 5331 amw free(mxa); 234 7052 amw return (ENOMEM); 235 5331 amw } 236 5331 amw 237 8334 Jose recv_nds = &mxa->recv_nds; 238 8334 Jose nds_initialize(recv_nds, datalen, NDR_MODE_CALL_RECV, mxa->heap); 239 5331 amw 240 7052 amw /* 241 7052 amw * Copy the input data and reset the input stream. 242 7052 amw */ 243 8334 Jose bcopy(data, recv_nds->pdu_base_addr, datalen); 244 8334 Jose ndr_pipe_rewind(np); 245 5331 amw 246 8334 Jose send_nds = &mxa->send_nds; 247 8334 Jose nds_initialize(send_nds, 0, NDR_MODE_RETURN_SEND, mxa->heap); 248 5331 amw 249 8334 Jose (void) ndr_svc_process(mxa); 250 5331 amw 251 8334 Jose nds_finalize(send_nds, &np->np_frags); 252 8334 Jose nds_destruct(&mxa->recv_nds); 253 8334 Jose nds_destruct(&mxa->send_nds); 254 8334 Jose ndr_heap_destroy(mxa->heap); 255 5331 amw free(mxa); 256 8474 Jose return (0); 257 8474 Jose } 258 8474 Jose 259 8474 Jose /* 260 8334 Jose * Must be called with ndr_pipe_lock held. 261 5331 amw */ 262 8334 Jose static ndr_pipe_t * 263 8334 Jose ndr_pipe_lookup(int fid) 264 5331 amw { 265 8334 Jose ndr_pipe_t *np; 266 5331 amw int i; 267 5331 amw 268 8334 Jose for (i = 0; i < NDR_PIPE_MAX; ++i) { 269 8334 Jose np = &ndr_pipe_table[i]; 270 5331 amw 271 8334 Jose if (np->np_fid == fid) { 272 8334 Jose if (np->np_refcnt == 0) 273 7052 amw return (NULL); 274 5331 amw 275 8334 Jose np->np_refcnt++; 276 8334 Jose return (np); 277 5331 amw } 278 5331 amw } 279 5331 amw 280 7052 amw return (NULL); 281 5331 amw } 282 5331 amw 283 5331 amw /* 284 8334 Jose * Must be called with ndr_pipe_lock held. 285 5331 amw */ 286 7052 amw static void 287 8334 Jose ndr_pipe_release(ndr_pipe_t *np) 288 5331 amw { 289 8334 Jose np->np_refcnt--; 290 8334 Jose ndr_pipe_deallocate(np); 291 7052 amw } 292 7052 amw 293 7052 amw /* 294 8334 Jose * Must be called with ndr_pipe_lock held. 295 7052 amw */ 296 8334 Jose static ndr_pipe_t * 297 8334 Jose ndr_pipe_allocate(int fid) 298 7052 amw { 299 8334 Jose ndr_pipe_t *np = NULL; 300 5331 amw int i; 301 5331 amw 302 8334 Jose for (i = 0; i < NDR_PIPE_MAX; ++i) { 303 8334 Jose np = &ndr_pipe_table[i]; 304 5331 amw 305 8334 Jose if (np->np_fid == 0) { 306 8334 Jose bzero(np, sizeof (ndr_pipe_t)); 307 5331 amw 308 8334 Jose if ((np->np_buf = malloc(NDR_PIPE_BUFSZ)) == NULL) 309 7052 amw return (NULL); 310 7052 amw 311 8334 Jose ndr_pipe_rewind(np); 312 8334 Jose np->np_fid = fid; 313 8334 Jose np->np_refcnt = 1; 314 8334 Jose return (np); 315 5331 amw } 316 5331 amw } 317 5331 amw 318 7052 amw return (NULL); 319 7052 amw } 320 7052 amw 321 7052 amw /* 322 8334 Jose * Must be called with ndr_pipe_lock held. 323 7052 amw */ 324 7052 amw static void 325 8334 Jose ndr_pipe_deallocate(ndr_pipe_t *np) 326 7052 amw { 327 8334 Jose if (np->np_refcnt == 0) { 328 7052 amw /* 329 7052 amw * Ensure that there are no RPC service policy handles 330 7052 amw * (associated with this fid) left around. 331 7052 amw */ 332 8334 Jose ndr_hdclose(np->np_fid); 333 7052 amw 334 8334 Jose ndr_pipe_rewind(np); 335 8334 Jose ndr_pipe_flush(np); 336 8334 Jose free(np->np_buf); 337 10122 Jordan free(np->np_user.ui_domain); 338 10122 Jordan free(np->np_user.ui_account); 339 10122 Jordan free(np->np_user.ui_workstation); 340 8334 Jose bzero(np, sizeof (ndr_pipe_t)); 341 7052 amw } 342 7052 amw } 343 7052 amw 344 7052 amw /* 345 7052 amw * Rewind the input data stream, ready for the next write. 346 7052 amw */ 347 7052 amw static void 348 8334 Jose ndr_pipe_rewind(ndr_pipe_t *np) 349 7052 amw { 350 8334 Jose np->np_uio.uio_iov = &np->np_iov; 351 8334 Jose np->np_uio.uio_iovcnt = 1; 352 8334 Jose np->np_uio.uio_offset = 0; 353 8334 Jose np->np_uio.uio_segflg = UIO_USERSPACE; 354 8334 Jose np->np_uio.uio_resid = NDR_PIPE_BUFSZ; 355 8334 Jose np->np_iov.iov_base = np->np_buf; 356 8334 Jose np->np_iov.iov_len = NDR_PIPE_BUFSZ; 357 7052 amw } 358 7052 amw 359 7052 amw /* 360 7052 amw * Flush the output data stream. 361 7052 amw */ 362 7052 amw static void 363 8334 Jose ndr_pipe_flush(ndr_pipe_t *np) 364 7052 amw { 365 7052 amw ndr_frag_t *frag; 366 7052 amw 367 8334 Jose while ((frag = np->np_frags.head) != NULL) { 368 8334 Jose np->np_frags.head = frag->next; 369 7052 amw free(frag); 370 7052 amw } 371 7052 amw 372 8334 Jose free(np->np_frags.iov); 373 8334 Jose bzero(&np->np_frags, sizeof (ndr_fraglist_t)); 374 7052 amw } 375 7052 amw 376 7052 amw /* 377 7052 amw * Check whether or not the specified user has administrator privileges, 378 7052 amw * i.e. is a member of Domain Admins or Administrators. 379 7052 amw * Returns true if the user is an administrator, otherwise returns false. 380 7052 amw */ 381 7052 amw boolean_t 382 7052 amw ndr_is_admin(ndr_xa_t *xa) 383 7052 amw { 384 10122 Jordan smb_netuserinfo_t *ctx = &xa->pipe->np_user; 385 7052 amw 386 10122 Jordan return (ctx->ui_flags & SMB_ATF_ADMIN); 387 7052 amw } 388 7052 amw 389 7052 amw /* 390 7052 amw * Check whether or not the specified user has power-user privileges, 391 7052 amw * i.e. is a member of Domain Admins, Administrators or Power Users. 392 7052 amw * This is typically required for operations such as managing shares. 393 7052 amw * Returns true if the user is a power user, otherwise returns false. 394 7052 amw */ 395 7052 amw boolean_t 396 7052 amw ndr_is_poweruser(ndr_xa_t *xa) 397 7052 amw { 398 10122 Jordan smb_netuserinfo_t *ctx = &xa->pipe->np_user; 399 7052 amw 400 10122 Jordan return ((ctx->ui_flags & SMB_ATF_ADMIN) || 401 10122 Jordan (ctx->ui_flags & SMB_ATF_POWERUSER)); 402 7052 amw } 403 7052 amw 404 7052 amw int32_t 405 7052 amw ndr_native_os(ndr_xa_t *xa) 406 7052 amw { 407 10122 Jordan smb_netuserinfo_t *ctx = &xa->pipe->np_user; 408 7052 amw 409 10122 Jordan return (ctx->ui_native_os); 410 5331 amw } 411 5331 amw 412 5331 amw /* 413 5331 amw * This is the entry point for all server-side RPC processing. 414 5331 amw * It is assumed that the PDU has already been received. 415 5331 amw */ 416 5331 amw static int 417 8334 Jose ndr_svc_process(ndr_xa_t *mxa) 418 5331 amw { 419 5331 amw int rc; 420 5331 amw 421 8334 Jose rc = ndr_decode_pdu_hdr(mxa); 422 8334 Jose if (!NDR_DRC_IS_OK(rc)) 423 5331 amw return (-1); 424 5331 amw 425 8334 Jose (void) ndr_reply_prepare_hdr(mxa); 426 5331 amw 427 5331 amw switch (mxa->ptype) { 428 8334 Jose case NDR_PTYPE_BIND: 429 8334 Jose rc = ndr_svc_bind(mxa); 430 5331 amw break; 431 5331 amw 432 8334 Jose case NDR_PTYPE_REQUEST: 433 8334 Jose rc = ndr_svc_request(mxa); 434 5331 amw break; 435 5331 amw 436 8334 Jose case NDR_PTYPE_ALTER_CONTEXT: 437 8334 Jose rc = ndr_svc_alter_context(mxa); 438 5331 amw break; 439 5331 amw 440 5331 amw default: 441 8334 Jose rc = NDR_DRC_FAULT_RPCHDR_PTYPE_INVALID; 442 5331 amw break; 443 5331 amw } 444 5331 amw 445 8334 Jose if (NDR_DRC_IS_FAULT(rc)) 446 8334 Jose ndr_reply_fault(mxa, rc); 447 5331 amw 448 8334 Jose (void) ndr_build_reply(mxa); 449 5331 amw return (rc); 450 5331 amw } 451 5331 amw 452 5331 amw /* 453 5331 amw * Multiple p_cont_elem[]s, multiple transfer_syntaxes[] and multiple 454 5331 amw * p_results[] not supported. 455 5331 amw */ 456 5331 amw static int 457 8334 Jose ndr_svc_bind(ndr_xa_t *mxa) 458 5331 amw { 459 8334 Jose ndr_p_cont_list_t *cont_list; 460 8334 Jose ndr_p_result_list_t *result_list; 461 8334 Jose ndr_p_result_t *result; 462 5331 amw unsigned p_cont_id; 463 8334 Jose ndr_binding_t *mbind; 464 5772 as200622 ndr_uuid_t *as_uuid; 465 5772 as200622 ndr_uuid_t *ts_uuid; 466 5331 amw int as_vers; 467 5331 amw int ts_vers; 468 8334 Jose ndr_service_t *msvc; 469 5331 amw int rc; 470 8334 Jose ndr_port_any_t *sec_addr; 471 5331 amw 472 5331 amw /* acquire targets */ 473 5331 amw cont_list = &mxa->recv_hdr.bind_hdr.p_context_elem; 474 5331 amw result_list = &mxa->send_hdr.bind_ack_hdr.p_result_list; 475 5331 amw result = &result_list->p_results[0]; 476 5331 amw 477 5331 amw /* 478 5331 amw * Set up temporary secondary address port. 479 5331 amw * We will correct this later (below). 480 5331 amw */ 481 5331 amw sec_addr = &mxa->send_hdr.bind_ack_hdr.sec_addr; 482 5331 amw sec_addr->length = 13; 483 5331 amw (void) strcpy((char *)sec_addr->port_spec, "\\PIPE\\ntsvcs"); 484 5331 amw 485 5331 amw result_list->n_results = 1; 486 5331 amw result_list->reserved = 0; 487 5331 amw result_list->reserved2 = 0; 488 8334 Jose result->result = NDR_PCDR_ACCEPTANCE; 489 5331 amw result->reason = 0; 490 5331 amw bzero(&result->transfer_syntax, sizeof (result->transfer_syntax)); 491 5331 amw 492 5331 amw /* sanity check */ 493 5331 amw if (cont_list->n_context_elem != 1 || 494 5331 amw cont_list->p_cont_elem[0].n_transfer_syn != 1) { 495 8334 Jose ndo_trace("ndr_svc_bind: warning: multiple p_cont_elem"); 496 5331 amw } 497 5331 amw 498 5331 amw p_cont_id = cont_list->p_cont_elem[0].p_cont_id; 499 5331 amw 500 8334 Jose if ((mbind = ndr_svc_find_binding(mxa, p_cont_id)) != NULL) { 501 5331 amw /* 502 8334 Jose * Duplicate presentation context id. 503 5331 amw */ 504 8334 Jose ndo_trace("ndr_svc_bind: duplicate binding"); 505 8334 Jose return (NDR_DRC_FAULT_BIND_PCONT_BUSY); 506 5331 amw } 507 5331 amw 508 8334 Jose if ((mbind = ndr_svc_new_binding(mxa)) == NULL) { 509 5331 amw /* 510 5331 amw * No free binding slot 511 5331 amw */ 512 8334 Jose result->result = NDR_PCDR_PROVIDER_REJECTION; 513 8334 Jose result->reason = NDR_PPR_LOCAL_LIMIT_EXCEEDED; 514 8334 Jose ndo_trace("ndr_svc_bind: no resources"); 515 8334 Jose return (NDR_DRC_OK); 516 5331 amw } 517 5331 amw 518 5331 amw as_uuid = &cont_list->p_cont_elem[0].abstract_syntax.if_uuid; 519 5331 amw as_vers = cont_list->p_cont_elem[0].abstract_syntax.if_version; 520 5331 amw 521 5331 amw ts_uuid = &cont_list->p_cont_elem[0].transfer_syntaxes[0].if_uuid; 522 5331 amw ts_vers = cont_list->p_cont_elem[0].transfer_syntaxes[0].if_version; 523 5331 amw 524 8334 Jose msvc = ndr_svc_lookup_uuid(as_uuid, as_vers, ts_uuid, ts_vers); 525 8334 Jose if (msvc == NULL) { 526 8334 Jose result->result = NDR_PCDR_PROVIDER_REJECTION; 527 8334 Jose result->reason = NDR_PPR_ABSTRACT_SYNTAX_NOT_SUPPORTED; 528 8334 Jose return (NDR_DRC_OK); 529 5331 amw } 530 5331 amw 531 5331 amw /* 532 5331 amw * We can now use the correct secondary address port. 533 5331 amw */ 534 5331 amw sec_addr = &mxa->send_hdr.bind_ack_hdr.sec_addr; 535 5331 amw sec_addr->length = strlen(msvc->sec_addr_port) + 1; 536 5331 amw (void) strlcpy((char *)sec_addr->port_spec, msvc->sec_addr_port, 537 8334 Jose NDR_PORT_ANY_MAX_PORT_SPEC); 538 5331 amw 539 5331 amw mbind->p_cont_id = p_cont_id; 540 8334 Jose mbind->which_side = NDR_BIND_SIDE_SERVER; 541 5331 amw /* mbind->context set by app */ 542 5331 amw mbind->service = msvc; 543 5331 amw mbind->instance_specific = 0; 544 5331 amw 545 5331 amw mxa->binding = mbind; 546 5331 amw 547 5331 amw if (msvc->bind_req) { 548 5331 amw /* 549 5331 amw * Call the service-specific bind() handler. If 550 5331 amw * this fails, we shouild send a specific error 551 5331 amw * on the bind ack. 552 5331 amw */ 553 5331 amw rc = (msvc->bind_req)(mxa); 554 8334 Jose if (NDR_DRC_IS_FAULT(rc)) { 555 5331 amw mbind->service = 0; /* free binding slot */ 556 5331 amw mbind->which_side = 0; 557 5331 amw mbind->p_cont_id = 0; 558 5331 amw mbind->instance_specific = 0; 559 5331 amw return (rc); 560 5331 amw } 561 5331 amw } 562 5331 amw 563 5331 amw result->transfer_syntax = 564 5331 amw cont_list->p_cont_elem[0].transfer_syntaxes[0]; 565 5331 amw 566 8334 Jose return (NDR_DRC_BINDING_MADE); 567 5331 amw } 568 5331 amw 569 5331 amw /* 570 8334 Jose * ndr_svc_alter_context 571 5331 amw * 572 5331 amw * The alter context request is used to request additional presentation 573 7619 Jose * context for another interface and/or version. It is very similar to 574 7619 Jose * a bind request. 575 5331 amw */ 576 5331 amw static int 577 8334 Jose ndr_svc_alter_context(ndr_xa_t *mxa) 578 5331 amw { 579 8334 Jose ndr_p_result_list_t *result_list; 580 8334 Jose ndr_p_result_t *result; 581 8334 Jose ndr_p_cont_list_t *cont_list; 582 8334 Jose ndr_binding_t *mbind; 583 8334 Jose ndr_service_t *msvc; 584 5331 amw unsigned p_cont_id; 585 5772 as200622 ndr_uuid_t *as_uuid; 586 5772 as200622 ndr_uuid_t *ts_uuid; 587 5331 amw int as_vers; 588 5331 amw int ts_vers; 589 8334 Jose ndr_port_any_t *sec_addr; 590 5331 amw 591 7619 Jose result_list = &mxa->send_hdr.alter_context_rsp_hdr.p_result_list; 592 5331 amw result_list->n_results = 1; 593 5331 amw result_list->reserved = 0; 594 5331 amw result_list->reserved2 = 0; 595 5331 amw 596 5331 amw result = &result_list->p_results[0]; 597 8334 Jose result->result = NDR_PCDR_ACCEPTANCE; 598 5331 amw result->reason = 0; 599 5331 amw bzero(&result->transfer_syntax, sizeof (result->transfer_syntax)); 600 5331 amw 601 7619 Jose cont_list = &mxa->recv_hdr.alter_context_hdr.p_context_elem; 602 5331 amw p_cont_id = cont_list->p_cont_elem[0].p_cont_id; 603 5331 amw 604 8334 Jose if (ndr_svc_find_binding(mxa, p_cont_id) != NULL) 605 8334 Jose return (NDR_DRC_FAULT_BIND_PCONT_BUSY); 606 5331 amw 607 8334 Jose if ((mbind = ndr_svc_new_binding(mxa)) == NULL) { 608 8334 Jose result->result = NDR_PCDR_PROVIDER_REJECTION; 609 8334 Jose result->reason = NDR_PPR_LOCAL_LIMIT_EXCEEDED; 610 8334 Jose return (NDR_DRC_OK); 611 5331 amw } 612 5331 amw 613 5331 amw as_uuid = &cont_list->p_cont_elem[0].abstract_syntax.if_uuid; 614 5331 amw as_vers = cont_list->p_cont_elem[0].abstract_syntax.if_version; 615 5331 amw 616 5331 amw ts_uuid = &cont_list->p_cont_elem[0].transfer_syntaxes[0].if_uuid; 617 5331 amw ts_vers = cont_list->p_cont_elem[0].transfer_syntaxes[0].if_version; 618 5331 amw 619 8334 Jose msvc = ndr_svc_lookup_uuid(as_uuid, as_vers, ts_uuid, ts_vers); 620 8334 Jose if (msvc == NULL) { 621 8334 Jose result->result = NDR_PCDR_PROVIDER_REJECTION; 622 8334 Jose result->reason = NDR_PPR_ABSTRACT_SYNTAX_NOT_SUPPORTED; 623 8334 Jose return (NDR_DRC_OK); 624 5331 amw } 625 5331 amw 626 5331 amw mbind->p_cont_id = p_cont_id; 627 8334 Jose mbind->which_side = NDR_BIND_SIDE_SERVER; 628 5331 amw /* mbind->context set by app */ 629 5331 amw mbind->service = msvc; 630 5331 amw mbind->instance_specific = 0; 631 5331 amw mxa->binding = mbind; 632 5331 amw 633 7619 Jose sec_addr = &mxa->send_hdr.alter_context_rsp_hdr.sec_addr; 634 5331 amw sec_addr->length = 0; 635 8334 Jose bzero(sec_addr->port_spec, NDR_PORT_ANY_MAX_PORT_SPEC); 636 5331 amw 637 5331 amw result->transfer_syntax = 638 5331 amw cont_list->p_cont_elem[0].transfer_syntaxes[0]; 639 5331 amw 640 8334 Jose return (NDR_DRC_BINDING_MADE); 641 5331 amw } 642 5331 amw 643 5331 amw static int 644 8334 Jose ndr_svc_request(ndr_xa_t *mxa) 645 5331 amw { 646 8334 Jose ndr_binding_t *mbind; 647 8334 Jose ndr_service_t *msvc; 648 8334 Jose unsigned p_cont_id; 649 8334 Jose int rc; 650 5331 amw 651 5331 amw mxa->opnum = mxa->recv_hdr.request_hdr.opnum; 652 5331 amw p_cont_id = mxa->recv_hdr.request_hdr.p_cont_id; 653 5331 amw 654 8334 Jose if ((mbind = ndr_svc_find_binding(mxa, p_cont_id)) == NULL) 655 8334 Jose return (NDR_DRC_FAULT_REQUEST_PCONT_INVALID); 656 5331 amw 657 5331 amw mxa->binding = mbind; 658 5331 amw msvc = mbind->service; 659 5331 amw 660 5331 amw /* 661 5331 amw * Make room for the response hdr. 662 5331 amw */ 663 8334 Jose mxa->send_nds.pdu_scan_offset = NDR_RSP_HDR_SIZE; 664 5331 amw 665 5331 amw if (msvc->call_stub) 666 5331 amw rc = (*msvc->call_stub)(mxa); 667 5331 amw else 668 8334 Jose rc = ndr_generic_call_stub(mxa); 669 5331 amw 670 8334 Jose if (NDR_DRC_IS_FAULT(rc)) { 671 8334 Jose ndo_printf(0, 0, "%s[0x%02x]: 0x%04x", 672 5331 amw msvc->name, mxa->opnum, rc); 673 5331 amw } 674 5331 amw 675 5331 amw return (rc); 676 5331 amw } 677 5331 amw 678 5331 amw /* 679 8334 Jose * The transaction and the two nds streams use the same heap, which 680 5331 amw * should already exist at this point. The heap will also be available 681 5331 amw * to the stub. 682 5331 amw */ 683 5521 as200622 int 684 8334 Jose ndr_generic_call_stub(ndr_xa_t *mxa) 685 5331 amw { 686 8334 Jose ndr_binding_t *mbind = mxa->binding; 687 8334 Jose ndr_service_t *msvc = mbind->service; 688 8334 Jose ndr_typeinfo_t *intf_ti = msvc->interface_ti; 689 8334 Jose ndr_stub_table_t *ste; 690 5331 amw int opnum = mxa->opnum; 691 5331 amw unsigned p_len = intf_ti->c_size_fixed_part; 692 5331 amw char *param; 693 5331 amw int rc; 694 5331 amw 695 5331 amw if (mxa->heap == NULL) { 696 8334 Jose ndo_printf(0, 0, "%s[0x%02x]: no heap", msvc->name, opnum); 697 8334 Jose return (NDR_DRC_FAULT_OUT_OF_MEMORY); 698 5331 amw } 699 5331 amw 700 8334 Jose if ((ste = ndr_svc_find_stub(msvc, opnum)) == NULL) { 701 8334 Jose ndo_printf(0, 0, "%s[0x%02x]: invalid opnum", 702 5331 amw msvc->name, opnum); 703 8334 Jose return (NDR_DRC_FAULT_REQUEST_OPNUM_INVALID); 704 5331 amw } 705 5331 amw 706 8334 Jose if ((param = ndr_heap_malloc(mxa->heap, p_len)) == NULL) 707 8334 Jose return (NDR_DRC_FAULT_OUT_OF_MEMORY); 708 5331 amw 709 5331 amw bzero(param, p_len); 710 5331 amw 711 8334 Jose rc = ndr_decode_call(mxa, param); 712 8334 Jose if (!NDR_DRC_IS_OK(rc)) 713 5331 amw return (rc); 714 5331 amw 715 5331 amw rc = (*ste->func)(param, mxa); 716 8334 Jose if (rc == NDR_DRC_OK) 717 8334 Jose rc = ndr_encode_return(mxa, param); 718 5331 amw 719 5331 amw return (rc); 720 5331 amw } 721 5331 amw 722 5331 amw /* 723 5331 amw * We can perform some initial setup of the response header here. 724 5331 amw * We also need to cache some of the information from the bind 725 5331 amw * negotiation for use during subsequent RPC calls. 726 5331 amw */ 727 5331 amw static void 728 8334 Jose ndr_reply_prepare_hdr(ndr_xa_t *mxa) 729 5331 amw { 730 7619 Jose ndr_common_header_t *rhdr = &mxa->recv_hdr.common_hdr; 731 7619 Jose ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr; 732 5331 amw 733 5331 amw hdr->rpc_vers = 5; 734 5331 amw hdr->rpc_vers_minor = 0; 735 8334 Jose hdr->pfc_flags = NDR_PFC_FIRST_FRAG + NDR_PFC_LAST_FRAG; 736 5331 amw hdr->packed_drep = rhdr->packed_drep; 737 5331 amw hdr->frag_length = 0; 738 5331 amw hdr->auth_length = 0; 739 5331 amw hdr->call_id = rhdr->call_id; 740 5331 amw #ifdef _BIG_ENDIAN 741 8334 Jose hdr->packed_drep.intg_char_rep = NDR_REPLAB_CHAR_ASCII 742 8334 Jose | NDR_REPLAB_INTG_BIG_ENDIAN; 743 5331 amw #else 744 8334 Jose hdr->packed_drep.intg_char_rep = NDR_REPLAB_CHAR_ASCII 745 8334 Jose | NDR_REPLAB_INTG_LITTLE_ENDIAN; 746 5331 amw #endif 747 5331 amw 748 5331 amw switch (mxa->ptype) { 749 8334 Jose case NDR_PTYPE_BIND: 750 8334 Jose hdr->ptype = NDR_PTYPE_BIND_ACK; 751 5331 amw mxa->send_hdr.bind_ack_hdr.max_xmit_frag = 752 5331 amw mxa->recv_hdr.bind_hdr.max_xmit_frag; 753 5331 amw mxa->send_hdr.bind_ack_hdr.max_recv_frag = 754 5331 amw mxa->recv_hdr.bind_hdr.max_recv_frag; 755 5331 amw mxa->send_hdr.bind_ack_hdr.assoc_group_id = 756 5331 amw mxa->recv_hdr.bind_hdr.assoc_group_id; 757 5331 amw 758 5331 amw if (mxa->send_hdr.bind_ack_hdr.assoc_group_id == 0) 759 5331 amw mxa->send_hdr.bind_ack_hdr.assoc_group_id = time(0); 760 5331 amw 761 5331 amw /* 762 5331 amw * Save the maximum fragment sizes 763 5331 amw * for use with subsequent requests. 764 5331 amw */ 765 8334 Jose mxa->pipe->np_max_xmit_frag = 766 5331 amw mxa->recv_hdr.bind_hdr.max_xmit_frag; 767 8334 Jose mxa->pipe->np_max_recv_frag = 768 5331 amw mxa->recv_hdr.bind_hdr.max_recv_frag; 769 5331 amw break; 770 5331 amw 771 8334 Jose case NDR_PTYPE_REQUEST: 772 8334 Jose hdr->ptype = NDR_PTYPE_RESPONSE; 773 5331 amw /* mxa->send_hdr.response_hdr.alloc_hint */ 774 5331 amw mxa->send_hdr.response_hdr.p_cont_id = 775 5331 amw mxa->recv_hdr.request_hdr.p_cont_id; 776 5331 amw mxa->send_hdr.response_hdr.cancel_count = 0; 777 5331 amw mxa->send_hdr.response_hdr.reserved = 0; 778 5331 amw break; 779 5331 amw 780 8334 Jose case NDR_PTYPE_ALTER_CONTEXT: 781 8334 Jose hdr->ptype = NDR_PTYPE_ALTER_CONTEXT_RESP; 782 5331 amw /* 783 7619 Jose * The max_xmit_frag, max_recv_frag and assoc_group_id are 784 7619 Jose * ignored by the client but it's useful to fill them in. 785 5331 amw */ 786 7619 Jose mxa->send_hdr.alter_context_rsp_hdr.max_xmit_frag = 787 7619 Jose mxa->recv_hdr.alter_context_hdr.max_xmit_frag; 788 7619 Jose mxa->send_hdr.alter_context_rsp_hdr.max_recv_frag = 789 7619 Jose mxa->recv_hdr.alter_context_hdr.max_recv_frag; 790 7619 Jose mxa->send_hdr.alter_context_rsp_hdr.assoc_group_id = 791 7619 Jose mxa->recv_hdr.alter_context_hdr.assoc_group_id; 792 5331 amw break; 793 5331 amw 794 5331 amw default: 795 5331 amw hdr->ptype = 0xFF; 796 5331 amw } 797 5331 amw } 798 5331 amw 799 5331 amw /* 800 5331 amw * Signal an RPC fault. The stream is reset and we overwrite whatever 801 5331 amw * was in the response header with the fault information. 802 5331 amw */ 803 5331 amw static void 804 8334 Jose ndr_reply_fault(ndr_xa_t *mxa, unsigned long drc) 805 5331 amw { 806 7619 Jose ndr_common_header_t *rhdr = &mxa->recv_hdr.common_hdr; 807 7619 Jose ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr; 808 8334 Jose ndr_stream_t *nds = &mxa->send_nds; 809 5331 amw unsigned long fault_status; 810 5331 amw 811 8334 Jose NDS_RESET(nds); 812 5331 amw 813 5331 amw hdr->rpc_vers = 5; 814 5331 amw hdr->rpc_vers_minor = 0; 815 8334 Jose hdr->pfc_flags = NDR_PFC_FIRST_FRAG + NDR_PFC_LAST_FRAG; 816 5331 amw hdr->packed_drep = rhdr->packed_drep; 817 5331 amw hdr->frag_length = sizeof (mxa->send_hdr.fault_hdr); 818 5331 amw hdr->auth_length = 0; 819 5331 amw hdr->call_id = rhdr->call_id; 820 5331 amw #ifdef _BIG_ENDIAN 821 8334 Jose hdr->packed_drep.intg_char_rep = NDR_REPLAB_CHAR_ASCII 822 8334 Jose | NDR_REPLAB_INTG_BIG_ENDIAN; 823 5331 amw #else 824 8334 Jose hdr->packed_drep.intg_char_rep = NDR_REPLAB_CHAR_ASCII 825 8334 Jose | NDR_REPLAB_INTG_LITTLE_ENDIAN; 826 5331 amw #endif 827 5331 amw 828 8334 Jose switch (drc & NDR_DRC_MASK_SPECIFIER) { 829 8334 Jose case NDR_DRC_FAULT_OUT_OF_MEMORY: 830 8334 Jose case NDR_DRC_FAULT_ENCODE_TOO_BIG: 831 8334 Jose fault_status = NDR_FAULT_NCA_OUT_ARGS_TOO_BIG; 832 5331 amw break; 833 5331 amw 834 8334 Jose case NDR_DRC_FAULT_REQUEST_PCONT_INVALID: 835 8334 Jose fault_status = NDR_FAULT_NCA_INVALID_PRES_CONTEXT_ID; 836 5331 amw break; 837 5331 amw 838 8334 Jose case NDR_DRC_FAULT_REQUEST_OPNUM_INVALID: 839 8334 Jose fault_status = NDR_FAULT_NCA_OP_RNG_ERROR; 840 5331 amw break; 841 5331 amw 842 8334 Jose case NDR_DRC_FAULT_DECODE_FAILED: 843 8334 Jose case NDR_DRC_FAULT_ENCODE_FAILED: 844 8334 Jose fault_status = NDR_FAULT_NCA_PROTO_ERROR; 845 5331 amw break; 846 5331 amw 847 5331 amw default: 848 8334 Jose fault_status = NDR_FAULT_NCA_UNSPEC_REJECT; 849 5331 amw break; 850 5331 amw } 851 5331 amw 852 8334 Jose mxa->send_hdr.fault_hdr.common_hdr.ptype = NDR_PTYPE_FAULT; 853 5331 amw mxa->send_hdr.fault_hdr.status = fault_status; 854 5331 amw mxa->send_hdr.response_hdr.alloc_hint = hdr->frag_length; 855 5331 amw } 856 5331 amw 857 7619 Jose /* 858 7619 Jose * Note that the frag_length for bind ack and alter context is 859 7619 Jose * non-standard. 860 7619 Jose */ 861 5331 amw static int 862 8334 Jose ndr_build_reply(ndr_xa_t *mxa) 863 5331 amw { 864 7619 Jose ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr; 865 8334 Jose ndr_stream_t *nds = &mxa->send_nds; 866 6482 amw uint8_t *pdu_buf; 867 5331 amw unsigned long pdu_size; 868 5331 amw unsigned long frag_size; 869 5331 amw unsigned long pdu_data_size; 870 5331 amw unsigned long frag_data_size; 871 5331 amw 872 8334 Jose frag_size = NDR_FRAG_SZ; 873 8334 Jose pdu_size = nds->pdu_size; 874 8334 Jose pdu_buf = nds->pdu_base_addr; 875 5331 amw 876 5331 amw if (pdu_size <= frag_size) { 877 5331 amw /* 878 5331 amw * Single fragment response. The PDU size may be zero 879 5331 amw * here (i.e. bind or fault response). So don't make 880 5331 amw * any assumptions about it until after the header is 881 5331 amw * encoded. 882 5331 amw */ 883 5331 amw switch (hdr->ptype) { 884 8334 Jose case NDR_PTYPE_BIND_ACK: 885 8334 Jose hdr->frag_length = ndr_bind_ack_hdr_size(mxa); 886 5331 amw break; 887 5331 amw 888 8334 Jose case NDR_PTYPE_FAULT: 889 5331 amw /* already setup */ 890 5331 amw break; 891 5331 amw 892 8334 Jose case NDR_PTYPE_RESPONSE: 893 5331 amw hdr->frag_length = pdu_size; 894 5331 amw mxa->send_hdr.response_hdr.alloc_hint = 895 5331 amw hdr->frag_length; 896 7619 Jose break; 897 7619 Jose 898 8334 Jose case NDR_PTYPE_ALTER_CONTEXT_RESP: 899 8334 Jose hdr->frag_length = ndr_alter_context_rsp_hdr_size(); 900 5331 amw break; 901 5331 amw 902 5331 amw default: 903 5331 amw hdr->frag_length = pdu_size; 904 5331 amw break; 905 5331 amw } 906 5331 amw 907 8334 Jose nds->pdu_scan_offset = 0; 908 8334 Jose (void) ndr_encode_pdu_hdr(mxa); 909 8334 Jose pdu_size = nds->pdu_size; 910 8334 Jose ndr_build_frag(nds, pdu_buf, pdu_size); 911 5331 amw return (0); 912 5331 amw } 913 5331 amw 914 5331 amw /* 915 5331 amw * Multiple fragment response. 916 5331 amw */ 917 8334 Jose hdr->pfc_flags = NDR_PFC_FIRST_FRAG; 918 5331 amw hdr->frag_length = frag_size; 919 8334 Jose mxa->send_hdr.response_hdr.alloc_hint = pdu_size - NDR_RSP_HDR_SIZE; 920 8334 Jose nds->pdu_scan_offset = 0; 921 8334 Jose (void) ndr_encode_pdu_hdr(mxa); 922 8334 Jose ndr_build_frag(nds, pdu_buf, frag_size); 923 5331 amw 924 5331 amw /* 925 5331 amw * We need to update the 24-byte header in subsequent fragments. 926 5331 amw * 927 6482 amw * pdu_data_size: total data remaining to be handled 928 6482 amw * frag_size: total fragment size including header 929 6482 amw * frag_data_size: data in fragment 930 8334 Jose * (i.e. frag_size - NDR_RSP_HDR_SIZE) 931 5331 amw */ 932 8334 Jose pdu_data_size = pdu_size - NDR_RSP_HDR_SIZE; 933 8334 Jose frag_data_size = frag_size - NDR_RSP_HDR_SIZE; 934 5331 amw 935 6482 amw while (pdu_data_size) { 936 6482 amw mxa->send_hdr.response_hdr.alloc_hint -= frag_data_size; 937 6482 amw pdu_data_size -= frag_data_size; 938 6482 amw pdu_buf += frag_data_size; 939 5521 as200622 940 6482 amw if (pdu_data_size <= frag_data_size) { 941 6482 amw frag_data_size = pdu_data_size; 942 8334 Jose frag_size = frag_data_size + NDR_RSP_HDR_SIZE; 943 8334 Jose hdr->pfc_flags = NDR_PFC_LAST_FRAG; 944 5331 amw } else { 945 6482 amw hdr->pfc_flags = 0; 946 5331 amw } 947 5331 amw 948 6482 amw hdr->frag_length = frag_size; 949 8334 Jose nds->pdu_scan_offset = 0; 950 8334 Jose (void) ndr_encode_pdu_hdr(mxa); 951 8334 Jose bcopy(nds->pdu_base_addr, pdu_buf, NDR_RSP_HDR_SIZE); 952 5331 amw 953 8334 Jose ndr_build_frag(nds, pdu_buf, frag_size); 954 5331 amw 955 8334 Jose if (hdr->pfc_flags & NDR_PFC_LAST_FRAG) 956 6482 amw break; 957 5331 amw } 958 5331 amw 959 5331 amw return (0); 960 5331 amw } 961 6482 amw 962 6482 amw /* 963 8334 Jose * ndr_build_frag 964 6482 amw * 965 6482 amw * Build an RPC PDU fragment from the specified buffer. 966 6482 amw * If malloc fails, the client will see a header/pdu inconsistency 967 6482 amw * and report an error. 968 6482 amw */ 969 6482 amw static void 970 8334 Jose ndr_build_frag(ndr_stream_t *nds, uint8_t *buf, uint32_t len) 971 6482 amw { 972 6482 amw ndr_frag_t *frag; 973 6482 amw int size = sizeof (ndr_frag_t) + len; 974 6482 amw 975 6482 amw if ((frag = (ndr_frag_t *)malloc(size)) == NULL) 976 6482 amw return; 977 6482 amw 978 6482 amw frag->next = NULL; 979 6482 amw frag->buf = (uint8_t *)frag + sizeof (ndr_frag_t); 980 6482 amw frag->len = len; 981 6482 amw bcopy(buf, frag->buf, len); 982 6482 amw 983 8334 Jose if (nds->frags.head == NULL) { 984 8334 Jose nds->frags.head = frag; 985 8334 Jose nds->frags.tail = frag; 986 8334 Jose nds->frags.nfrag = 1; 987 6482 amw } else { 988 8334 Jose nds->frags.tail->next = frag; 989 8334 Jose nds->frags.tail = frag; 990 8334 Jose ++nds->frags.nfrag; 991 6482 amw } 992 6482 amw } 993