setgroups系统调用及示例

setgroups 函数详解

1. 函数介绍

setgroups 是Linux系统调用,用于设置进程的附加组ID列表。每个进程都可以属于多个组,除了主组ID(由setgid设置)外,还可以通过附加组ID列表拥有多个组的权限。这对于实现细粒度的权限控制和访问控制非常重要。

csdn:setgroups系统调用及示例-CSDN博客

2. 函数原型

#include <grp.h>
#include <unistd.h>
int setgroups(size_t size, const gid_t *list);

3. 功能

setgroups 设置调用进程的附加组ID列表,替换当前的附加组集合。这允许进程获得多个组的权限,从而可以访问属于这些组的文件和资源。

4. 参数

  • size_t size: 附加组ID列表的大小(元素个数)
  • *const gid_t list: 指向组ID数组的指针

5. 返回值

  • 成功: 返回0
  • 失败: 返回-1,并设置errno

6. 相似函数,或关联函数

  • getgroups: 获取当前附加组ID列表
  • setgid: 设置主组ID
  • setuid: 设置用户ID
  • initgroups: 根据用户初始化组列表

7. 示例代码

示例1:基础setgroups使用

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

/**
 * 显示当前用户和组信息
 */
void show_current_user_info() {
    uid_t uid = getuid();
    gid_t gid = getgid();
    uid_t euid = geteuid();
    gid_t egid = getegid();
    
    printf("=== 当前用户和组信息 ===\n");
    printf("真实用户ID: %d\n", uid);
    printf("真实组ID: %d\n", gid);
    printf("有效用户ID: %d\n", euid);
    printf("有效组ID: %d\n", egid);
    
    // 显示当前附加组
    int max_groups = getgroups(0, NULL);
    if (max_groups > 0) {
        gid_t *groups = malloc(max_groups * sizeof(gid_t));
        if (groups) {
            int num_groups = getgroups(max_groups, groups);
            if (num_groups > 0) {
                printf("附加组ID (%d个): ", num_groups);
                for (int i = 0; i < num_groups; i++) {
                    printf("%d ", groups[i]);
                }
                printf("\n");
                
                // 显示组名
                printf("附加组名: ");
                for (int i = 0; i < num_groups; i++) {
                    struct group *grp = getgrgid(groups[i]);
                    if (grp) {
                        printf("%s ", grp->gr_name);
                    } else {
                        printf("%d ", groups[i]);
                    }
                }
                printf("\n");
            }
            free(groups);
        }
    }
    printf("\n");
}

/**
 * 演示基础setgroups使用方法
 */
int demo_setgroups_basic() {
    gid_t current_groups[32];
    gid_t new_groups[] = {100, 200, 300, 400};  // 示例组ID
    int num_current, num_new = 4;
    int result;
    
    printf("=== 基础setgroups使用示例 ===\n");
    
    // 显示当前用户信息
    show_current_user_info();
    
    // 获取当前附加组
    num_current = getgroups(sizeof(current_groups) / sizeof(current_groups[0]), 
                           current_groups);
    if (num_current == -1) {
        printf("获取当前附加组失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("当前附加组数量: %d\n", num_current);
    if (num_current > 0) {
        printf("当前附加组ID: ");
        for (int i = 0; i < num_current; i++) {
            printf("%d ", current_groups[i]);
        }
        printf("\n");
    }
    
    // 尝试设置新的附加组(需要适当权限)
    printf("\n尝试设置新的附加组列表:\n");
    for (int i = 0; i < num_new; i++) {
        printf("  组ID: %d\n", new_groups[i]);
    }
    
    result = setgroups(num_new, new_groups);
    if (result == 0) {
        printf("✓ 成功设置附加组列表\n");
        
        // 验证设置结果
        gid_t verify_groups[32];
        int verify_count = getgroups(sizeof(verify_groups) / sizeof(verify_groups[0]), 
                                    verify_groups);
        if (verify_count > 0) {
            printf("验证附加组列表:\n");
            printf("  组数量: %d\n", verify_count);
            printf("  组ID: ");
            for (int i = 0; i < verify_count; i++) {
                printf("%d ", verify_groups[i]);
            }
            printf("\n");
        }
    } else {
        printf("✗ 设置附加组列表失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  原因:需要CAP_SETGID权限或root权限\n");
        } else if (errno == EINVAL) {
            printf("  原因:参数无效\n");
        }
        
        printf("注意:普通用户通常无法修改附加组列表\n");
    }
    
    return 0;
}

int main() {
    return demo_setgroups_basic();
}

示例2:权限检查和组管理

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

/**
 * 检查当前权限
 */
void check_permissions() {
    uid_t uid = getuid();
    uid_t euid = geteuid();
    gid_t gid = getgid();
    
    printf("=== 权限检查 ===\n");
    printf("真实用户ID: %d\n", uid);
    printf("有效用户ID: %d\n", euid);
    printf("主组ID: %d\n", gid);
    
    if (uid == 0 || euid == 0) {
        printf("✓ 当前具有root权限\n");
    } else {
        printf("✗ 当前没有root权限\n");
        printf("  提示:修改附加组需要CAP_SETGID权限或root权限\n");
    }
    printf("\n");
}

/**
 * 查找系统中的组
 */
void find_system_groups() {
    struct group *grp;
    int group_count = 0;
    
    printf("=== 系统中的部分组信息 ===\n");
    
    // 查找一些常见的组
    const char *common_groups[] = {"root", "daemon", "sys", "adm", "tty", "disk", NULL};
    
    for (int i = 0; common_groups[i]; i++) {
        grp = getgrnam(common_groups[i]);
        if (grp) {
            printf("  组名: %-10s ID: %d\n", grp->gr_name, grp->gr_gid);
            group_count++;
        }
    }
    
    printf("  找到 %d 个常用组\n\n", group_count);
}

/**
 * 演示权限检查和组管理
 */
int demo_permission_check() {
    gid_t test_groups[5];
    int num_groups = 0;
    
    printf("=== 权限检查和组管理演示 ===\n");
    
    // 检查当前权限
    check_permissions();
    
    // 查找系统组
    find_system_groups();
    
    // 准备测试组列表
    struct group *grp;
    
    // 尝试获取一些系统组ID
    grp = getgrnam("daemon");
    if (grp) {
        test_groups[num_groups++] = grp->gr_gid;
        printf("添加组 daemon (ID: %d) 到测试列表\n", grp->gr_gid);
    }
    
    grp = getgrnam("sys");
    if (grp) {
        test_groups[num_groups++] = grp->gr_gid;
        printf("添加组 sys (ID: %d) 到测试列表\n", grp->gr_gid);
    }
    
    grp = getgrnam("adm");
    if (grp) {
        test_groups[num_groups++] = grp->gr_gid;
        printf("添加组 adm (ID: %d) 到测试列表\n", grp->gr_gid);
    }
    
    if (num_groups == 0) {
        // 如果找不到系统组,使用示例ID
        test_groups[0] = 100;
        test_groups[1] = 200;
        test_groups[2] = 300;
        num_groups = 3;
        printf("使用示例组ID: 100, 200, 300\n");
    }
    
    // 显示当前附加组
    printf("\n当前附加组:\n");
    int current_count = getgroups(0, NULL);
    if (current_count > 0) {
        gid_t *current_groups = malloc(current_count * sizeof(gid_t));
        if (current_groups) {
            int actual_count = getgroups(current_count, current_groups);
            if (actual_count > 0) {
                printf("  ");
                for (int i = 0; i < actual_count; i++) {
                    struct group *g = getgrgid(current_groups[i]);
                    if (g) {
                        printf("%s(%d) ", g->gr_name, current_groups[i]);
                    } else {
                        printf("%d ", current_groups[i]);
                    }
                }
                printf("\n");
            }
            free(current_groups);
        }
    }
    
    // 尝试设置附加组
    printf("\n尝试设置附加组列表:\n");
    printf("  组数量: %d\n", num_groups);
    printf("  组ID: ");
    for (int i = 0; i < num_groups; i++) {
        struct group *g = getgrgid(test_groups[i]);
        if (g) {
            printf("%s(%d) ", g->gr_name, test_groups[i]);
        } else {
            printf("%d ", test_groups[i]);
        }
    }
    printf("\n");
    
    int result = setgroups(num_groups, test_groups);
    if (result == 0) {
        printf("✓ 附加组设置成功\n");
    } else {
        printf("✗ 附加组设置失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  需要root权限或CAP_SETGID能力\n");
        }
    }
    
    return 0;
}

int main() {
    return demo_permission_check();
}

示例3:initgroups替代实现

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

/**
 * 自定义initgroups实现
 */
int my_initgroups(const char *user, gid_t group) {
    struct group *grp;
    gid_t *groups = NULL;
    int ngroups = 0;
    int max_groups = 32;
    
    printf("=== 自定义initgroups实现 ===\n");
    printf("用户: %s, 主组: %d\n", user, group);
    
    // 分配初始组数组
    groups = malloc(max_groups * sizeof(gid_t));
    if (!groups) {
        perror("分配内存失败");
        return -1;
    }
    
    // 添加主组
    groups[ngroups++] = group;
    
    // 查找用户所属的所有组
    setgrent();  // 重新开始组文件扫描
    
    while ((grp = getgrent()) != NULL) {
        // 检查用户是否在该组的成员列表中
        if (grp->gr_mem) {
            for (char **member = grp->gr_mem; *member; member++) {
                if (strcmp(*member, user) == 0) {
                    // 确保不重复添加主组
                    if (grp->gr_gid != group) {
                        // 检查是否需要扩展数组
                        if (ngroups >= max_groups) {
                            max_groups *= 2;
                            gid_t *new_groups = realloc(groups, max_groups * sizeof(gid_t));
                            if (!new_groups) {
                                perror("重新分配内存失败");
                                free(groups);
                                endgrent();
                                return -1;
                            }
                            groups = new_groups;
                        }
                        
                        groups[ngroups++] = grp->gr_gid;
                        printf("  发现用户组: %s (ID: %d)\n", grp->gr_name, grp->gr_gid);
                    }
                    break;
                }
            }
        }
    }
    
    endgrent();
    
    printf("总共找到 %d 个组\n", ngroups);
    
    // 设置附加组
    int result = setgroups(ngroups, groups);
    if (result == 0) {
        printf("成功设置附加组列表\n");
    } else {
        printf("设置附加组失败: %s\n", strerror(errno));
    }
    
    free(groups);
    return result;
}

/**
 * 演示自定义initgroups
 */
int demo_custom_initgroups() {
    struct passwd *pwd;
    const char *username;
    
    printf("=== 自定义initgroups演示 ===\n");
    
    // 获取当前用户名
    pwd = getpwuid(getuid());
    if (pwd) {
        username = pwd->pw_name;
        printf("当前用户: %s (UID: %d, GID: %d)\n", 
               username, pwd->pw_uid, pwd->pw_gid);
    } else {
        printf("无法获取当前用户信息\n");
        return -1;
    }
    
    // 显示用户信息
    printf("\n用户详细信息:\n");
    printf("  用户名: %s\n", pwd->pw_name);
    printf("  用户ID: %d\n", pwd->pw_uid);
    printf("  主组ID: %d\n", pwd->pw_gid);
    printf("  主目录: %s\n", pwd->pw_dir);
    printf("  登录shell: %s\n", pwd->pw_shell);
    
    // 使用自定义initgroups
    printf("\n调用自定义initgroups:\n");
    int result = my_initgroups(username, pwd->pw_gid);
    
    if (result == 0) {
        printf("✓ 自定义initgroups执行成功\n");
        
        // 验证结果
        printf("\n验证附加组设置:\n");
        int max_groups = getgroups(0, NULL);
        if (max_groups > 0) {
            gid_t *groups = malloc(max_groups * sizeof(gid_t));
            if (groups) {
                int actual_count = getgroups(max_groups, groups);
                if (actual_count > 0) {
                    printf("  附加组数量: %d\n", actual_count);
                    printf("  附加组列表: ");
                    for (int i = 0; i < actual_count; i++) {
                        struct group *g = getgrgid(groups[i]);
                        if (g) {
                            printf("%s(%d) ", g->gr_name, groups[i]);
                        } else {
                            printf("%d ", groups[i]);
                        }
                    }
                    printf("\n");
                }
                free(groups);
            }
        }
    } else {
        printf("✗ 自定义initgroups执行失败\n");
    }
    
    return 0;
}

int main() {
    return demo_custom_initgroups();
}

示例4:组管理工具

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

/**
 * 组信息结构
 */
typedef struct {
    gid_t gid;
    char name[256];
    int is_member;
} group_info_t;

/**
 * 显示组列表
 */
void show_group_list(group_info_t *groups, int count) {
    printf("组列表 (%d 个组):\n", count);
    printf("%-8s %-20s %-10s\n", "GID", "组名", "成员状态");
    printf("----------------------------------------\n");
    
    for (int i = 0; i < count; i++) {
        printf("%-8d %-20s %-10s\n", 
               groups[i].gid, groups[i].name,
               groups[i].is_member ? "是" : "否");
    }
    printf("\n");
}

/**
 * 获取当前附加组
 */
int get_current_groups(gid_t **groups) {
    int count = getgroups(0, NULL);
    if (count <= 0) {
        return count;
    }
    
    *groups = malloc(count * sizeof(gid_t));
    if (!*groups) {
        return -1;
    }
    
    count = getgroups(count, *groups);
    return count;
}

/**
 * 检查组是否在列表中
 */
int is_group_in_list(gid_t gid, gid_t *groups, int count) {
    for (int i = 0; i < count; i++) {
        if (groups[i] == gid) {
            return 1;
        }
    }
    return 0;
}

/**
 * 演示组管理工具
 */
int demo_group_manager() {
    gid_t *current_groups = NULL;
    int current_count = 0;
    struct group *grp;
    group_info_t test_groups[10];
    int test_count = 0;
    
    printf("=== 组管理工具演示 ===\n");
    
    // 获取当前附加组
    current_count = get_current_groups(&current_groups);
    if (current_count < 0) {
        printf("获取当前附加组失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("当前进程附加组数量: %d\n", current_count);
    if (current_count > 0) {
        printf("当前附加组ID: ");
        for (int i = 0; i < current_count; i++) {
            printf("%d ", current_groups[i]);
        }
        printf("\n\n");
    }
    
    // 准备测试组列表
    printf("准备测试组列表:\n");
    
    // 添加一些常见组
    const char *group_names[] = {"daemon", "sys", "adm", "tty", "disk", "audio", NULL};
    
    for (int i = 0; group_names[i] && test_count < 10; i++) {
        grp = getgrnam(group_names[i]);
        if (grp) {
            test_groups[test_count].gid = grp->gr_gid;
            strncpy(test_groups[test_count].name, grp->gr_name, 
                    sizeof(test_groups[test_count].name) - 1);
            test_groups[test_count].name[sizeof(test_groups[test_count].name) - 1] = '\0';
            test_groups[test_count].is_member = is_group_in_list(grp->gr_gid, 
                                                                current_groups, current_count);
            test_count++;
            printf("  添加组: %s (ID: %d) %s\n", 
                   grp->gr_name, grp->gr_gid,
                   test_groups[test_count-1].is_member ? "[当前成员]" : "");
        }
    }
    
    // 如果没找到足够的系统组,添加示例组
    if (test_count < 3) {
        for (int i = test_count; i < 3 && i < 10; i++) {
            test_groups[i].gid = 1000 + i;
            snprintf(test_groups[i].name, sizeof(test_groups[i].name), "group_%d", 1000 + i);
            test_groups[i].is_member = 0;
            test_count++;
        }
    }
    
    printf("\n");
    show_group_list(test_groups, test_count);
    
    // 演示设置附加组
    printf("尝试设置新的附加组列表:\n");
    gid_t new_groups[5];
    int new_count = 0;
    
    // 选择前3个组进行设置
    for (int i = 0; i < test_count && new_count < 5 && i < 3; i++) {
        new_groups[new_count++] = test_groups[i].gid;
        printf("  添加组: %s (ID: %d)\n", test_groups[i].name, test_groups[i].gid);
    }
    
    int result = setgroups(new_count, new_groups);
    if (result == 0) {
        printf("✓ 附加组设置成功\n");
        
        // 验证设置结果
        printf("\n验证设置结果:\n");
        gid_t *verify_groups = NULL;
        int verify_count = get_current_groups(&verify_groups);
        if (verify_count > 0) {
            printf("  验证组数量: %d\n", verify_count);
            printf("  验证组ID: ");
            for (int i = 0; i < verify_count; i++) {
                printf("%d ", verify_groups[i]);
            }
            printf("\n");
        }
        if (verify_groups) {
            free(verify_groups);
        }
    } else {
        printf("✗ 附加组设置失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  需要root权限或CAP_SETGID能力\n");
        }
    }
    
    // 清理资源
    if (current_groups) {
        free(current_groups);
    }
    
    return 0;
}

int main() {
    return demo_group_manager();
}

示例5:安全组切换演示

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <sys/capability.h>

/**
 * 显示进程权限信息
 */
void show_process_capabilities() {
    uid_t uid = getuid();
    gid_t gid = getgid();
    uid_t euid = geteuid();
    gid_t egid = getegid();
    
    printf("=== 进程权限信息 ===\n");
    printf("真实用户ID: %d\n", uid);
    printf("真实组ID: %d\n", gid);
    printf("有效用户ID: %d\n", euid);
    printf("有效组ID: %d\n", egid);
    
    // 显示附加组数量
    int ngids = getgroups(0, NULL);
    if (ngids >= 0) {
        printf("附加组数量: %d\n", ngids);
    }
    
    // 显示当前附加组
    if (ngids > 0) {
        gid_t *gids = malloc(ngids * sizeof(gid_t));
        if (gids) {
            int actual = getgroups(ngids, gids);
            if (actual > 0) {
                printf("附加组ID: ");
                for (int i = 0; i < actual; i++) {
                    printf("%d ", gids[i]);
                }
                printf("\n");
            }
            free(gids);
        }
    }
    printf("\n");
}

/**
 * 安全组切换演示
 */
int demo_secure_group_switching() {
    gid_t original_groups[32];
    gid_t test_groups[3] = {1000, 2000, 3000};
    int original_count, test_count = 3;
    
    printf("=== 安全组切换演示 ===\n");
    
    // 显示原始权限信息
    show_process_capabilities();
    
    // 保存原始附加组
    original_count = getgroups(sizeof(original_groups) / sizeof(original_groups[0]), 
                              original_groups);
    if (original_count == -1) {
        printf("获取原始附加组失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("原始附加组数量: %d\n", original_count);
    if (original_count > 0) {
        printf("原始附加组ID: ");
        for (int i = 0; i < original_count; i++) {
            printf("%d ", original_groups[i]);
        }
        printf("\n");
    }
    
    // 尝试设置测试组
    printf("\n1. 设置测试附加组:\n");
    printf("   测试组ID: ");
    for (int i = 0; i < test_count; i++) {
        printf("%d ", test_groups[i]);
    }
    printf("\n");
    
    int result = setgroups(test_count, test_groups);
    if (result == 0) {
        printf("   ✓ 测试组设置成功\n");
        show_process_capabilities();
    } else {
        printf("   ✗ 测试组设置失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   需要root权限或CAP_SETGID能力\n");
            printf("   演示将继续使用当前权限\n");
        }
    }
    
    // 演示临时组权限使用
    printf("\n2. 模拟使用组权限:\n");
    if (result == 0) {
        printf("   使用新设置的附加组权限...\n");
        // 这里可以进行需要特定组权限的操作
        printf("   模拟文件访问检查...\n");
    } else {
        printf("   使用当前附加组权限...\n");
    }
    
    // 恢复原始附加组
    printf("\n3. 恢复原始附加组:\n");
    if (original_count >= 0) {
        result = setgroups(original_count, original_groups);
        if (result == 0) {
            printf("   ✓ 原始附加组恢复成功\n");
        } else {
            printf("   ✗ 原始附加组恢复失败: %s\n", strerror(errno));
        }
    }
    
    show_process_capabilities();
    
    // 演示安全最佳实践
    printf("\n4. 安全最佳实践:\n");
    printf("   ✓ 始终保存原始权限状态\n");
    printf("   ✓ 在使用特殊权限后及时恢复\n");
    printf("   ✓ 检查权限操作的返回值\n");
    printf("   ✓ 记录权限变更日志\n");
    printf("   ✓ 最小权限原则:只请求必需的权限\n");
    
    // 显示组相关安全信息
    printf("\n5. 组安全相关信息:\n");
    printf("   Linux限制:最多支持 %d 个附加组\n", NGROUPS_MAX);
    printf("   权限要求:修改附加组需要CAP_SETGID能力\n");
    printf("   安全建议:避免在生产环境中频繁切换组权限\n");
    
    return 0;
}

int main() {
    return demo_secure_group_switching();
}

setgroups 使用注意事项

系统要求:

  1. 内核版本: 支持setgroups的Linux内核
  2. 权限要求: 需要CAP_SETGID能力或root权限
  3. 架构支持: 支持所有主流架构

参数限制:

  1. 数量限制: 系统限制附加组数量(通常NGROUPS_MAX)
  2. 组ID有效性: 组ID应该在系统中存在
  3. 空指针处理: list为NULL时size应该为0

错误处理:

  1. EPERM: 权限不足(需要CAP_SETGID或root权限)
  2. EINVAL: 参数无效(size过大或指针无效)
  3. ENOMEM: 内存不足
  4. EFAULT: 指针参数指向无效内存

性能考虑:

  1. 系统调用开销: 频繁调用有性能开销
  2. 内核数据结构: 需要更新内核中的组信息
  3. 权限检查: 每次调用都需要权限验证

安全考虑:

  1. 权限提升: 可能导致权限提升风险
  2. 审计日志: 建议记录权限变更操作
  3. 最小权限: 遵循最小权限原则
  4. 状态恢复: 及时恢复原始权限状态

最佳实践:

  1. 权限检查: 执行前检查是否具有足够权限
  2. 参数验证: 验证参数的有效性和安全性
  3. 错误处理: 妥善处理各种错误情况
  4. 状态保存: 保存原始状态以便恢复
  5. 日志记录: 记录权限变更操作
  6. 及时恢复: 使用完特殊权限后及时恢复

常见使用场景

1. 用户登录处理:

// 用户登录时初始化组权限
struct passwd *pwd = getpwnam(username);
if (pwd) {
    initgroups(username, pwd->pw_gid);
    setgid(pwd->pw_gid);
    setuid(pwd->pw_uid);
}

2. 服务权限管理:

// 服务启动时设置适当的组权限
gid_t service_groups[] = {GROUP_DAEMON, GROUP_LOG};
setgroups(2, service_groups);

3. 安全沙箱:

// 创建受限环境时移除不必要的组权限
setgroups(0, NULL);  // 清空附加组

组管理相关常量

系统限制:

#include <limits.h>
// NGROUPS_MAX: 系统支持的最大附加组数量
printf("最大附加组数量: %ld\n", sysconf(_SC_NGROUPS_MAX));

权限能力检查

检查CAP_SETGID能力:

#include <sys/capability.h>
// 检查进程是否具有设置组ID的能力

总结

setgroups 是Linux系统中重要的权限管理函数,提供了:

  1. 组权限控制: 精确控制进程的组权限
  2. 灵活配置: 支持动态设置附加组列表
  3. 安全机制: 通过权限检查保证系统安全
  4. 标准兼容: 符合POSIX标准

通过合理使用 setgroups,可以实现细粒度的权限控制,构建更加安全可靠的系统应用。在实际应用中,需要注意权限要求、错误处理和安全最佳实践。

此条目发表在linux文章分类目录,贴了标签。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注