1/* 2 * Unix SMB/CIFS implementation. 3 * RPC Pipe client / server routines 4 * Copyright (C) Andrew Tridgell 1992-2000, 5 * Copyright (C) Jean Fran�ois Micouleau 1998-2001. 6 * Copyright (C) Gerald Carter 2003. 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 */ 22 23 24#include "includes.h" 25#include "utils/net.h" 26 27 28/********************************************************* 29 utility function to parse an integer parameter from 30 "parameter = value" 31**********************************************************/ 32static uint32 get_int_param( const char* param ) 33{ 34 char *p; 35 36 p = strchr( param, '=' ); 37 if ( !p ) 38 return 0; 39 40 return atoi(p+1); 41} 42 43/********************************************************* 44 utility function to parse an integer parameter from 45 "parameter = value" 46**********************************************************/ 47static char* get_string_param( const char* param ) 48{ 49 char *p; 50 51 p = strchr( param, '=' ); 52 if ( !p ) 53 return NULL; 54 55 return (p+1); 56} 57 58/********************************************************* 59 Figure out if the input was an NT group or a SID string. 60 Return the SID. 61**********************************************************/ 62static BOOL get_sid_from_input(DOM_SID *sid, char *input) 63{ 64 GROUP_MAP map; 65 66 if (StrnCaseCmp( input, "S-", 2)) { 67 /* Perhaps its the NT group name? */ 68 if (!pdb_getgrnam(&map, input)) { 69 printf("NT Group %s doesn't exist in mapping DB\n", input); 70 return False; 71 } else { 72 *sid = map.sid; 73 } 74 } else { 75 if (!string_to_sid(sid, input)) { 76 printf("converting sid %s from a string failed!\n", input); 77 return False; 78 } 79 } 80 return True; 81} 82 83/********************************************************* 84 Dump a GROUP_MAP entry to stdout (long or short listing) 85**********************************************************/ 86 87static void print_map_entry ( GROUP_MAP map, BOOL long_list ) 88{ 89 fstring string_sid; 90 fstring group_type; 91 92 decode_sid_name_use(group_type, map.sid_name_use); 93 sid_to_string(string_sid, &map.sid); 94 95 if (!long_list) 96 d_printf("%s (%s) -> %s\n", map.nt_name, string_sid, gidtoname(map.gid)); 97 else { 98 d_printf("%s\n", map.nt_name); 99 d_printf("\tSID : %s\n", string_sid); 100 d_printf("\tUnix group: %s\n", gidtoname(map.gid)); 101 d_printf("\tGroup type: %s\n", group_type); 102 d_printf("\tComment : %s\n", map.comment); 103 } 104 105} 106/********************************************************* 107 List the groups. 108**********************************************************/ 109static int net_groupmap_list(int argc, const char **argv) 110{ 111 int entries; 112 BOOL long_list = False; 113 int i; 114 fstring ntgroup = ""; 115 fstring sid_string = ""; 116 117 if (opt_verbose || opt_long_list_entries) 118 long_list = True; 119 120 /* get the options */ 121 for ( i=0; i<argc; i++ ) { 122 if ( !StrCaseCmp(argv[i], "verbose")) { 123 long_list = True; 124 } 125 else if ( !StrnCaseCmp(argv[i], "ntgroup", strlen("ntgroup")) ) { 126 fstrcpy( ntgroup, get_string_param( argv[i] ) ); 127 if ( !ntgroup[0] ) { 128 d_printf("must supply a name\n"); 129 return -1; 130 } 131 } 132 else if ( !StrnCaseCmp(argv[i], "sid", strlen("sid")) ) { 133 fstrcpy( sid_string, get_string_param( argv[i] ) ); 134 if ( !sid_string[0] ) { 135 d_printf("must supply a SID\n"); 136 return -1; 137 } 138 } 139 else { 140 d_printf("Bad option: %s\n", argv[i]); 141 return -1; 142 } 143 } 144 145 /* list a single group is given a name */ 146 if ( ntgroup[0] || sid_string[0] ) { 147 DOM_SID sid; 148 GROUP_MAP map; 149 150 if ( sid_string[0] ) 151 fstrcpy( ntgroup, sid_string); 152 153 if (!get_sid_from_input(&sid, ntgroup)) { 154 return -1; 155 } 156 157 /* Get the current mapping from the database */ 158 if(!pdb_getgrsid(&map, sid)) { 159 d_printf("Failure to local group SID in the database\n"); 160 return -1; 161 } 162 163 print_map_entry( map, long_list ); 164 } 165 else { 166 GROUP_MAP *map=NULL; 167 /* enumerate all group mappings */ 168 if (!pdb_enum_group_mapping(SID_NAME_UNKNOWN, &map, &entries, ENUM_ALL_MAPPED)) 169 return -1; 170 171 for (i=0; i<entries; i++) { 172 print_map_entry( map[i], long_list ); 173 } 174 175 SAFE_FREE(map); 176 } 177 178 return 0; 179} 180 181/********************************************************* 182 Add a new group mapping entry 183**********************************************************/ 184 185static int net_groupmap_add(int argc, const char **argv) 186{ 187 DOM_SID sid; 188 fstring ntgroup = ""; 189 fstring unixgrp = ""; 190 fstring string_sid = ""; 191 fstring type = ""; 192 fstring ntcomment = ""; 193 enum SID_NAME_USE sid_type = SID_NAME_DOM_GRP; 194 uint32 rid = 0; 195 gid_t gid; 196 int i; 197 198 /* get the options */ 199 for ( i=0; i<argc; i++ ) { 200 if ( !StrnCaseCmp(argv[i], "rid", strlen("rid")) ) { 201 rid = get_int_param(argv[i]); 202 if ( rid < DOMAIN_GROUP_RID_ADMINS ) { 203 d_printf("RID must be greater than %d\n", (uint32)DOMAIN_GROUP_RID_ADMINS-1); 204 return -1; 205 } 206 } 207 else if ( !StrnCaseCmp(argv[i], "unixgroup", strlen("unixgroup")) ) { 208 fstrcpy( unixgrp, get_string_param( argv[i] ) ); 209 if ( !unixgrp[0] ) { 210 d_printf("must supply a name\n"); 211 return -1; 212 } 213 } 214 else if ( !StrnCaseCmp(argv[i], "ntgroup", strlen("ntgroup")) ) { 215 fstrcpy( ntgroup, get_string_param( argv[i] ) ); 216 if ( !ntgroup[0] ) { 217 d_printf("must supply a name\n"); 218 return -1; 219 } 220 } 221 else if ( !StrnCaseCmp(argv[i], "sid", strlen("sid")) ) { 222 fstrcpy( string_sid, get_string_param( argv[i] ) ); 223 if ( !string_sid[0] ) { 224 d_printf("must supply a SID\n"); 225 return -1; 226 } 227 } 228 else if ( !StrnCaseCmp(argv[i], "comment", strlen("comment")) ) { 229 fstrcpy( ntcomment, get_string_param( argv[i] ) ); 230 if ( !ntcomment[0] ) { 231 d_printf("must supply a comment string\n"); 232 return -1; 233 } 234 } 235 else if ( !StrnCaseCmp(argv[i], "type", strlen("type")) ) { 236 fstrcpy( type, get_string_param( argv[i] ) ); 237 switch ( type[0] ) { 238 case 'b': 239 case 'B': 240 sid_type = SID_NAME_WKN_GRP; 241 break; 242 case 'd': 243 case 'D': 244 sid_type = SID_NAME_DOM_GRP; 245 break; 246 case 'l': 247 case 'L': 248 sid_type = SID_NAME_ALIAS; 249 break; 250 } 251 } 252 else { 253 d_printf("Bad option: %s\n", argv[i]); 254 return -1; 255 } 256 } 257 258 if ( !unixgrp[0] ) { 259 d_printf("Usage: net groupmap add {rid=<int>|sid=<string>} unixgroup=<string> [type=<domain|local|builtin>] [ntgroup=<string>] [comment=<string>]\n"); 260 return -1; 261 } 262 263 if ( (gid = nametogid(unixgrp)) == (gid_t)-1 ) { 264 d_printf("Can't lookup UNIX group %s\n", unixgrp); 265 return -1; 266 } 267 268 if ( (rid == 0) && (string_sid[0] == '\0') ) { 269 d_printf("No rid or sid specified, choosing algorithmic mapping\n"); 270 rid = pdb_gid_to_group_rid(gid); 271 } 272 273 /* append the rid to our own domain/machine SID if we don't have a full SID */ 274 if ( !string_sid[0] ) { 275 sid_copy(&sid, get_global_sam_sid()); 276 sid_append_rid(&sid, rid); 277 sid_to_string(string_sid, &sid); 278 } 279 280 if (!ntcomment[0]) { 281 switch (sid_type) { 282 case SID_NAME_WKN_GRP: 283 fstrcpy(ntcomment, "Wellknown Unix group"); 284 break; 285 case SID_NAME_DOM_GRP: 286 fstrcpy(ntcomment, "Domain Unix group"); 287 break; 288 case SID_NAME_ALIAS: 289 fstrcpy(ntcomment, "Local Unix group"); 290 break; 291 default: 292 fstrcpy(ntcomment, "Unix group"); 293 break; 294 } 295 } 296 297 if (!ntgroup[0] ) 298 fstrcpy( ntgroup, unixgrp ); 299 300 301 if (!add_initial_entry(gid, string_sid, sid_type, ntgroup, ntcomment)) { 302 d_printf("adding entry for group %s failed!\n", ntgroup); 303 return -1; 304 } 305 306 d_printf("Successfully added group %s to the mapping db\n", ntgroup); 307 return 0; 308} 309 310static int net_groupmap_modify(int argc, const char **argv) 311{ 312 DOM_SID sid; 313 GROUP_MAP map; 314 fstring ntcomment = ""; 315 fstring type = ""; 316 fstring ntgroup = ""; 317 fstring unixgrp = ""; 318 fstring sid_string = ""; 319 enum SID_NAME_USE sid_type = SID_NAME_UNKNOWN; 320 int i; 321 gid_t gid; 322 323 /* get the options */ 324 for ( i=0; i<argc; i++ ) { 325 if ( !StrnCaseCmp(argv[i], "ntgroup", strlen("ntgroup")) ) { 326 fstrcpy( ntgroup, get_string_param( argv[i] ) ); 327 if ( !ntgroup[0] ) { 328 d_printf("must supply a name\n"); 329 return -1; 330 } 331 } 332 else if ( !StrnCaseCmp(argv[i], "sid", strlen("sid")) ) { 333 fstrcpy( sid_string, get_string_param( argv[i] ) ); 334 if ( !sid_string[0] ) { 335 d_printf("must supply a name\n"); 336 return -1; 337 } 338 } 339 else if ( !StrnCaseCmp(argv[i], "comment", strlen("comment")) ) { 340 fstrcpy( ntcomment, get_string_param( argv[i] ) ); 341 if ( !ntcomment[0] ) { 342 d_printf("must supply a comment string\n"); 343 return -1; 344 } 345 } 346 else if ( !StrnCaseCmp(argv[i], "unixgroup", strlen("unixgroup")) ) { 347 fstrcpy( unixgrp, get_string_param( argv[i] ) ); 348 if ( !unixgrp[0] ) { 349 d_printf("must supply a group name\n"); 350 return -1; 351 } 352 } 353 else if ( !StrnCaseCmp(argv[i], "type", strlen("type")) ) { 354 fstrcpy( type, get_string_param( argv[i] ) ); 355 switch ( type[0] ) { 356 case 'd': 357 case 'D': 358 sid_type = SID_NAME_DOM_GRP; 359 break; 360 case 'l': 361 case 'L': 362 sid_type = SID_NAME_ALIAS; 363 break; 364 } 365 } 366 else { 367 d_printf("Bad option: %s\n", argv[i]); 368 return -1; 369 } 370 } 371 372 if ( !ntgroup[0] && !sid_string[0] ) { 373 d_printf("Usage: net groupmap modify {ntgroup=<string>|sid=<SID>} [comment=<string>] [unixgroup=<string>] [type=<domain|local>]\n"); 374 return -1; 375 } 376 377 /* give preference to the SID; if both the ntgroup name and SID 378 are defined, use the SID and assume that the group name could be a 379 new name */ 380 381 if ( sid_string[0] ) { 382 if (!get_sid_from_input(&sid, sid_string)) { 383 return -1; 384 } 385 } 386 else { 387 if (!get_sid_from_input(&sid, ntgroup)) { 388 return -1; 389 } 390 } 391 392 /* Get the current mapping from the database */ 393 if(!pdb_getgrsid(&map, sid)) { 394 d_printf("Failure to local group SID in the database\n"); 395 return -1; 396 } 397 398 /* 399 * Allow changing of group type only between domain and local 400 * We disallow changing Builtin groups !!! (SID problem) 401 */ 402 if (sid_type != SID_NAME_UNKNOWN) { 403 if (map.sid_name_use == SID_NAME_WKN_GRP) { 404 d_printf("You can only change between domain and local groups.\n"); 405 return -1; 406 } 407 408 map.sid_name_use=sid_type; 409 } 410 411 /* Change comment if new one */ 412 if ( ntcomment[0] ) 413 fstrcpy( map.comment, ntcomment ); 414 415 if ( ntgroup[0] ) 416 fstrcpy( map.nt_name, ntgroup ); 417 418 if ( unixgrp[0] ) { 419 gid = nametogid( unixgrp ); 420 if ( gid == -1 ) { 421 d_printf("Unable to lookup UNIX group %s. Make sure the group exists.\n", 422 unixgrp); 423 return -1; 424 } 425 426 map.gid = gid; 427 } 428 429 if ( !pdb_update_group_mapping_entry(&map) ) { 430 d_printf("Could not update group database\n"); 431 return -1; 432 } 433 434 d_printf("Updated mapping entry for %s\n", map.nt_name); 435 436 return 0; 437} 438 439static int net_groupmap_delete(int argc, const char **argv) 440{ 441 DOM_SID sid; 442 fstring ntgroup = ""; 443 fstring sid_string = ""; 444 int i; 445 446 /* get the options */ 447 for ( i=0; i<argc; i++ ) { 448 if ( !StrnCaseCmp(argv[i], "ntgroup", strlen("ntgroup")) ) { 449 fstrcpy( ntgroup, get_string_param( argv[i] ) ); 450 if ( !ntgroup[0] ) { 451 d_printf("must supply a name\n"); 452 return -1; 453 } 454 } 455 else if ( !StrnCaseCmp(argv[i], "sid", strlen("sid")) ) { 456 fstrcpy( sid_string, get_string_param( argv[i] ) ); 457 if ( !sid_string[0] ) { 458 d_printf("must supply a SID\n"); 459 return -1; 460 } 461 } 462 else { 463 d_printf("Bad option: %s\n", argv[i]); 464 return -1; 465 } 466 } 467 468 if ( !ntgroup[0] && !sid_string[0]) { 469 d_printf("Usage: net groupmap delete {ntgroup=<string>|sid=<SID>}\n"); 470 return -1; 471 } 472 473 /* give preference to the SID if we have that */ 474 475 if ( sid_string[0] ) 476 fstrcpy( ntgroup, sid_string ); 477 478 if ( !get_sid_from_input(&sid, ntgroup) ) { 479 d_printf("Unable to resolve group %s to a SID\n", ntgroup); 480 return -1; 481 } 482 483 if ( !pdb_delete_group_mapping_entry(sid) ) { 484 printf("Failed to removing group %s from the mapping db!\n", ntgroup); 485 return -1; 486 } 487 488 d_printf("Sucessfully removed %s from the mapping db\n", ntgroup); 489 490 return 0; 491} 492 493static int net_groupmap_set(int argc, const char **argv) 494{ 495 const char *ntgroup = NULL; 496 struct group *grp = NULL; 497 GROUP_MAP map; 498 BOOL have_map = False; 499 500 if ((argc < 1) || (argc > 2)) { 501 d_printf("Usage: net groupmap set \"NT Group\" " 502 "[\"unix group\"] [-C \"comment\"] [-L] [-D]\n"); 503 return -1; 504 } 505 506 if ( opt_localgroup && opt_domaingroup ) { 507 d_printf("Can only specify -L or -D, not both\n"); 508 return -1; 509 } 510 511 ntgroup = argv[0]; 512 513 if (argc == 2) { 514 grp = getgrnam(argv[1]); 515 516 if (grp == NULL) { 517 d_printf("Could not find unix group %s\n", argv[1]); 518 return -1; 519 } 520 } 521 522 have_map = pdb_getgrnam(&map, ntgroup); 523 524 if (!have_map) { 525 DOM_SID sid; 526 have_map = ( (strncmp(ntgroup, "S-", 2) == 0) && 527 string_to_sid(&sid, ntgroup) && 528 pdb_getgrsid(&map, sid) ); 529 } 530 531 if (!have_map) { 532 533 /* Ok, add it */ 534 535 if (grp == NULL) { 536 d_printf("Could not find group mapping for %s\n", 537 ntgroup); 538 return -1; 539 } 540 541 map.gid = grp->gr_gid; 542 543 if (opt_rid == 0) { 544 opt_rid = pdb_gid_to_group_rid(map.gid); 545 } 546 547 sid_copy(&map.sid, get_global_sam_sid()); 548 sid_append_rid(&map.sid, opt_rid); 549 550 map.sid_name_use = SID_NAME_DOM_GRP; 551 fstrcpy(map.nt_name, ntgroup); 552 fstrcpy(map.comment, ""); 553 554 if (!pdb_add_group_mapping_entry(&map)) { 555 d_printf("Could not add mapping entry for %s\n", 556 ntgroup); 557 return -1; 558 } 559 } 560 561 /* Now we have a mapping entry, update that stuff */ 562 563 if ( opt_localgroup || opt_domaingroup ) { 564 if (map.sid_name_use == SID_NAME_WKN_GRP) { 565 d_printf("Can't change type of the BUILTIN group %s\n", 566 map.nt_name); 567 return -1; 568 } 569 } 570 571 if (opt_localgroup) 572 map.sid_name_use = SID_NAME_ALIAS; 573 574 if (opt_domaingroup) 575 map.sid_name_use = SID_NAME_DOM_GRP; 576 577 /* The case (opt_domaingroup && opt_localgroup) was tested for above */ 578 579 if (strlen(opt_comment) > 0) 580 fstrcpy(map.comment, opt_comment); 581 582 if (strlen(opt_newntname) > 0) 583 fstrcpy(map.nt_name, opt_newntname); 584 585 if (grp != NULL) 586 map.gid = grp->gr_gid; 587 588 if (!pdb_update_group_mapping_entry(&map)) { 589 d_printf("Could not update group mapping for %s\n", ntgroup); 590 return -1; 591 } 592 593 return 0; 594} 595 596static int net_groupmap_cleanup(int argc, const char **argv) 597{ 598 GROUP_MAP *map = NULL; 599 int i, entries; 600 601 if (!pdb_enum_group_mapping(SID_NAME_UNKNOWN, &map, &entries, 602 ENUM_ALL_MAPPED)) { 603 d_printf("Could not list group mappings\n"); 604 return -1; 605 } 606 607 for (i=0; i<entries; i++) { 608 609 if (map[i].sid_name_use == SID_NAME_WKN_GRP) 610 continue; 611 612 if (map[i].gid == -1) 613 printf("Group %s is not mapped\n", map[i].nt_name); 614 615 if (!sid_check_is_in_our_domain(&map[i].sid)) { 616 printf("Deleting mapping for NT Group %s, sid %s\n", 617 map[i].nt_name, 618 sid_string_static(&map[i].sid)); 619 pdb_delete_group_mapping_entry(map[i].sid); 620 } 621 } 622 623 SAFE_FREE(map); 624 625 return 0; 626} 627 628static int net_groupmap_addmem(int argc, const char **argv) 629{ 630 DOM_SID alias, member; 631 632 if ( (argc != 2) || 633 !string_to_sid(&alias, argv[0]) || 634 !string_to_sid(&member, argv[1]) ) { 635 d_printf("Usage: net groupmap addmem alias-sid member-sid\n"); 636 return -1; 637 } 638 639 if (!pdb_add_aliasmem(&alias, &member)) { 640 d_printf("Could not add sid %s to alias %s\n", 641 argv[1], argv[0]); 642 return -1; 643 } 644 645 return 0; 646} 647 648static int net_groupmap_delmem(int argc, const char **argv) 649{ 650 DOM_SID alias, member; 651 652 if ( (argc != 2) || 653 !string_to_sid(&alias, argv[0]) || 654 !string_to_sid(&member, argv[1]) ) { 655 d_printf("Usage: net groupmap delmem alias-sid member-sid\n"); 656 return -1; 657 } 658 659 if (!pdb_del_aliasmem(&alias, &member)) { 660 d_printf("Could not delete sid %s from alias %s\n", 661 argv[1], argv[0]); 662 return -1; 663 } 664 665 return 0; 666} 667 668static int net_groupmap_listmem(int argc, const char **argv) 669{ 670 DOM_SID alias; 671 DOM_SID *members; 672 int i, num; 673 NTSTATUS result; 674 675 if ( (argc != 1) || 676 !string_to_sid(&alias, argv[0]) ) { 677 d_printf("Usage: net groupmap listmem alias-sid\n"); 678 return -1; 679 } 680 681 if (!pdb_enum_aliasmem(&alias, &members, &num)) { 682 d_printf("Could not list members for sid %s: %s\n", 683 argv[0], nt_errstr(result)); 684 return -1; 685 } 686 687 for (i = 0; i < num; i++) { 688 printf("%s\n", sid_string_static(&(members[i]))); 689 } 690 691 SAFE_FREE(members); 692 693 return 0; 694} 695 696static int net_groupmap_memberships(int argc, const char **argv) 697{ 698 DOM_SID member; 699 DOM_SID *aliases; 700 int i, num; 701 NTSTATUS result; 702 703 if ( (argc != 1) || 704 !string_to_sid(&member, argv[0]) ) { 705 d_printf("Usage: net groupmap memberof sid\n"); 706 return -1; 707 } 708 709 if (!pdb_enum_alias_memberships(&member, 1, &aliases, &num)) { 710 d_printf("Could not list memberships for sid %s: %s\n", 711 argv[0], nt_errstr(result)); 712 return -1; 713 } 714 715 for (i = 0; i < num; i++) { 716 printf("%s\n", sid_string_static(&(aliases[i]))); 717 } 718 719 SAFE_FREE(aliases); 720 721 return 0; 722} 723 724int net_help_groupmap(int argc, const char **argv) 725{ 726 d_printf("net groupmap add"\ 727 "\n Create a new group mapping\n"); 728 d_printf("net groupmap modify"\ 729 "\n Update a group mapping\n"); 730 d_printf("net groupmap delete"\ 731 "\n Remove a group mapping\n"); 732 d_printf("net groupmap addmem"\ 733 "\n Add a foreign alias member\n"); 734 d_printf("net groupmap delmem"\ 735 "\n Delete a foreign alias member\n"); 736 d_printf("net groupmap listmem"\ 737 "\n List foreign group members\n"); 738 d_printf("net groupmap memberships"\ 739 "\n List foreign group memberships\n"); 740 d_printf("net groupmap list"\ 741 "\n List current group map\n"); 742 d_printf("net groupmap set"\ 743 "\n Set group mapping\n"); 744 d_printf("net groupmap cleanup"\ 745 "\n Remove foreign group mapping entries\n"); 746 747 return -1; 748} 749 750 751/*********************************************************** 752 migrated functionality from smbgroupedit 753 **********************************************************/ 754int net_groupmap(int argc, const char **argv) 755{ 756 struct functable func[] = { 757 {"add", net_groupmap_add}, 758 {"modify", net_groupmap_modify}, 759 {"delete", net_groupmap_delete}, 760 {"set", net_groupmap_set}, 761 {"cleanup", net_groupmap_cleanup}, 762 {"addmem", net_groupmap_addmem}, 763 {"delmem", net_groupmap_delmem}, 764 {"listmem", net_groupmap_listmem}, 765 {"memberships", net_groupmap_memberships}, 766 {"list", net_groupmap_list}, 767 {"help", net_help_groupmap}, 768 {NULL, NULL} 769 }; 770 771 /* we shouldn't have silly checks like this */ 772 if (getuid() != 0) { 773 d_printf("You must be root to edit group mappings.\nExiting...\n"); 774 return -1; 775 } 776 777 if ( argc ) 778 return net_run_function(argc, argv, func, net_help_groupmap); 779 780 return net_help_groupmap( argc, argv ); 781} 782 783