searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

[macOS]通过代码获取root权限的三种方式

2023-12-06 09:59:56
753
0

macOS有着严格的用户权限管理,一般情况下App都是运行在普通用户权限下,这个时候App是无法修改系统配置,也不能往用户目录以外的目录读写文件。有些操作下我们必须要获取root权限才能执行,比如:

1. 往系统目录安装可执行文件(比如:/usr/bin);

2. 设置App为系统启动项;

3. 修改系统设置;

 

目前主要有三种方式可以获取系统的root权限:

1. 使用<Security.framework>的AuthorizationExecuteWithPrivileges:

这个接口允许我们在执行命令行命令的时候弹出提示框,用户输入root密码之后能够以root的权限执行命令。

- (BOOL)executeCommand:(NSString *)command withArgs:(NSArray *)argumentArray synchronous:(BOOL)sync
{
    FILE *communicationStream = NULL;
    int i;
    OSStatus myStatus;
    char outputString[1024];
    time_t startTime = time(NULL);

    char **copyArguments = malloc(sizeof(char *) * (argumentArray.count + 1));

    for (i = 0; i < argumentArray.count; i++) {
        copyArguments[i] = (char *)[argumentArray[i] UTF8String];
    }
    copyArguments[i] = NULL;

    myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef,
                                                  command.UTF8String,
                                                  kAuthorizationFlagDefaults,
                                                  copyArguments,
                                                  (sync ? &communicationStream : NULL)); // FILE HANDLE for I/O

    if (myStatus==errAuthorizationSuccess && sync) {
        while (!myStatus && !feof(communicationStream) && fgets(outputString, 1024, communicationStream) && time(NULL)-startTime<10) {
            if (strlen(outputString) > 1)
                NSLog(@"NSAuthorization: %s",outputString);
        }
        fclose(communicationStream);
    }

    free(copyArguments);

    if (myStatus != errAuthorizationSuccess)
        NSLog(@"Error: Executing %@ with Authorization: %d", command, (int)myStatus);

    return (myStatus == errAuthorizationSuccess);
}

运行效果,提示"xxx 想要进行更改":

需要注意的是这个接口在macOS 10.7以后就是Deprecated了,苹果推荐使用另一种方式进行提权操作。

 

2. 使用<ServiceManagement.framework> 的 SMJobBless:

这是苹果用于替换第一种方式的提权方案,它需要我们创建一个命令行程序Helper设置为随launchd启动(拥有root权限),App再通过SMJobBless接口实现和Helper通信,从而将需要root权限执行的命令交由Helper执行:

Helper命令行工具:

1. 配置Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleIdentifier</key>
	<string>com.apple.SMJobBlessHelper</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>SMJobBlessHelper</string>
	<key>CFBundleVersion</key>
	<string>1.5</string>
	<key>SMAuthorizedClients</key>
	<array>
		<string>identifier "com.apple.SMJobBlessHelper" and anchor apple generic and certificate leaf = "Apple Development: developer@apple.com (67UEKSL8V5)" and certificate 1[field.1.2] /* exists */</string>
	</array>
</dict>
</plist>

这里的SMAuthorizedClients是通过官方脚本生成:SMJobBlessUtil.py

$ ./SMJobBlessUtil.py setreq Build/Products/Debug/SMJobBlessApp.app SMJobBlessApp/SMJobBlessApp-Info.plist SMJobBlessHelper/SMJobBlessHelper-Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleIdentifier</key>
	<string>com.asbbl.ClinkMainHelper</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>ClinkMainHelper</string>
	<key>CFBundleVersion</key>
	<string>1.5</string>
	<key>SMAuthorizedClients</key>
	<array>
		<string>identifier "com.asbbl.ClinkMain" and anchor apple generic and certificate leaf= "Apple Development: developer@apple.com (67NQWSL8V5)" and certificate 1[field.1.2.8401] /* exists */</string>
	</array>
</dict>
</plist>

2. Launchd.plist 用于配置Helper自启动:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.apple.SMJobBlessHelper</string>
	<key>MachServices</key>
	<dict>
		<key>com.apple.SMJobBlessHelper</key>
		<true/>
	</dict>
	<key>RunAtLoad</key>
	<true/>
</dict>
</plist>

3. 在主程序配置Helper安装(Build Phase):

通过以上3步就完成了Helper的配置,接下来看下如何和Helper通信:

1. Helper 初始化Listener,用于监听主App的连接请求:

@interface HelperListener : NSObject<NSXPCListenerDelegate>

// allows NSXPCListener to configure/accept/resume a new incoming NSXPCConnection
-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection;

@end

@implementation ExtendedNSXPCConnection

- (instancetype)init
{
    if ((self = [super init]) {
        //init listener
        listener = [[NSXPCListener alloc] initWithMachServiceName:MAIN_HELPER_ID];
        if(!listener) {
            os_log_error(logHandle, "ERROR: failed to create mach service %@", MAIN_HELPER_ID);
            return NO;
        } else {
            os_log_info(logHandle, "created mach service %@", MAIN_HELPER_ID);
        }
    
        // start listen
        listener.delegate = self;
        [listener resume];
    
        return self;
    }
    return nil;
}

-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
    newConnection.exportedObject = [[HelperInterface alloc] init];
    [newConnection resume];
    
    //dbg msg
    os_log_debug(logHandle, "allowed XPC connection: %@", newConnection);
    return YES;
}

其中HelperInterface是实现了协议XPCProtocol的类,就是真正实现功能的类型。

 

2. 主App通过SMJobBless和Helper通信:

- (BOOL)blessHelper
{
    BOOL wasBlessed = NO;
    
    AuthorizationRef authRef = NULL;
    CFErrorRef error = NULL;
    AuthorizationItem authItem = {};
    AuthorizationRights authRights = {};
    AuthorizationFlags authFlags = 0;
    
    //create auth
    if(errAuthorizationSuccess != AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef)) {
        NSLog(@"ERROR: failed to create authorization");
        goto bail;
    }
    
    //init auth item
    memset(&authItem, 0x0, sizeof(authItem));
    
    authItem.name = kSMRightBlessPrivilegedHelper;
    authRights.count = 1;
    authRights.items = &authItem;
    
    //init flags
    authFlags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
    
    //get auth rights
    if(errAuthorizationSuccess != AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, authFlags, NULL)) {
        NSLog(@"ERROR: failed to copy authorization rights");
        goto bail;
    }
    
    //bless
    if(YES != (BOOL)SMJobBless(kSMDomainSystemLaunchd, (__bridge CFStringRef)(MAIN_HELPER_ID), authRef, &error)) {
        //err msg
        NSLog(@"ERROR: failed to bless job (%@)", ((__bridge NSError*)error));
        goto bail;
    }
    
    //happy
    wasBlessed = YES;
    
bail:
    
    if(NULL != authRef) {
        AuthorizationFree(authRef, kAuthorizationFlagDefaults);
        authRef = NULL;
    }
    
    if(NULL != error) {
        CFRelease(error);
        error = NULL;
    }
    
    return wasBlessed;
}

- (void)invokeHelper
{
    // Bless Helper
     [self blessHelper];

    // Create XPCService
    xpcServiceConnection = [[NSXPCConnection alloc] initWithMachServiceName:MAIN_HELPER_ID options:0];
    xpcServiceConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
    [xpcServiceConnection resume];
}

XPCProtocol就是主App和Helper之间约定的API,主App创建XPCServiceConnection之后就可以直接通过该API实现接口调用:

    __block BOOL result = NO;
    [[xpcServiceConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * proxyError)
    {
        NSLog(@"ERROR: failed to execute 'uninstall' method on helper tool (error: %@)", proxyError);
        
    }] installDrivers:drivers reply:^(NSNumber *status, NSString *msg)
    {
        result = [status boolValue];
        NSLog(@"%@", msg);
    }];

XPCProtocol定义:

@protocol XPCProtocol

// install drivers
- (void)installDrivers:(NSArray<NSString *> *)drivers reply:(void (^)(NSNumber *status, NSString *msg))reply;

@end

需要注意使用SMJobBless的方式实现提权需要对Helper程序进行签名,并且要求是正式的付费开发者账号签名才有效,免费的个人开发者账号即使签名成功了,Helper程序也无法被launchd正常启动。

 

3. 直接通过AppleScript实现提权:

这种方式只能同步执行命令,会阻塞当前App主线程(子线程无法实现密码输入),所以只适合那种可以在前台并且不耗时的命令行任务。

- (BOOL)runProcessAsAdministrator:(NSString*)scriptPath
                     withArguments:(NSArray *)arguments
                            output:(NSString **)output
                  errorDescription:(NSString **)errorDescription
{
    NSString * allArgs = [arguments componentsJoinedByString:@" "];
    NSString * fullScript = [NSString stringWithFormat:@"%@ %@", scriptPath, allArgs];

    NSDictionary *errorInfo = [NSDictionary new];
    NSString *script =  [NSString stringWithFormat:@"do shell script \"%@\" with administrator privileges", fullScript];

    NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
    NSAppleEventDescriptor * eventResult = [appleScript executeAndReturnError:&errorInfo];

    // Check errorInfo
    if (! eventResult)
    {
        // Describe common errors
        *errorDescription = nil;
        if ([errorInfo valueForKey:NSAppleScriptErrorNumber])
        {
            NSNumber * errorNumber = (NSNumber *)[errorInfo valueForKey:NSAppleScriptErrorNumber];
            if ([errorNumber intValue] == -128)
                *errorDescription = @"The administrator password is required to do this.";
        }

        // Set error message from provided message
        if (*errorDescription == nil)
        {
            if ([errorInfo valueForKey:NSAppleScriptErrorMessage])
                *errorDescription =  (NSString *)[errorInfo valueForKey:NSAppleScriptErrorMessage];
        }

        return NO;
    }
    else
    {
        // Set output to the AppleScript's output
        *output = [eventResult stringValue];

        return YES;
    }
}

 

总结

三种方式中第一种最佳,不需要签名和开发者账号(每次都需要输入密码),虽然已经被苹果标为Deprecated,不过目前最新的系统版本仍能使用;第二种方式是苹果官方认可的唯一方式,缺点是繁琐、开发工作量大,而且需要付费开发者账号进行签名;第三种方式缺点是执行过程不可控,而且会阻塞主App运行,适用场景有限。

0条评论
0 / 1000
l****n
14文章数
1粉丝数
l****n
14 文章 | 1 粉丝
原创

[macOS]通过代码获取root权限的三种方式

2023-12-06 09:59:56
753
0

macOS有着严格的用户权限管理,一般情况下App都是运行在普通用户权限下,这个时候App是无法修改系统配置,也不能往用户目录以外的目录读写文件。有些操作下我们必须要获取root权限才能执行,比如:

1. 往系统目录安装可执行文件(比如:/usr/bin);

2. 设置App为系统启动项;

3. 修改系统设置;

 

目前主要有三种方式可以获取系统的root权限:

1. 使用<Security.framework>的AuthorizationExecuteWithPrivileges:

这个接口允许我们在执行命令行命令的时候弹出提示框,用户输入root密码之后能够以root的权限执行命令。

- (BOOL)executeCommand:(NSString *)command withArgs:(NSArray *)argumentArray synchronous:(BOOL)sync
{
    FILE *communicationStream = NULL;
    int i;
    OSStatus myStatus;
    char outputString[1024];
    time_t startTime = time(NULL);

    char **copyArguments = malloc(sizeof(char *) * (argumentArray.count + 1));

    for (i = 0; i < argumentArray.count; i++) {
        copyArguments[i] = (char *)[argumentArray[i] UTF8String];
    }
    copyArguments[i] = NULL;

    myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef,
                                                  command.UTF8String,
                                                  kAuthorizationFlagDefaults,
                                                  copyArguments,
                                                  (sync ? &communicationStream : NULL)); // FILE HANDLE for I/O

    if (myStatus==errAuthorizationSuccess && sync) {
        while (!myStatus && !feof(communicationStream) && fgets(outputString, 1024, communicationStream) && time(NULL)-startTime<10) {
            if (strlen(outputString) > 1)
                NSLog(@"NSAuthorization: %s",outputString);
        }
        fclose(communicationStream);
    }

    free(copyArguments);

    if (myStatus != errAuthorizationSuccess)
        NSLog(@"Error: Executing %@ with Authorization: %d", command, (int)myStatus);

    return (myStatus == errAuthorizationSuccess);
}

运行效果,提示"xxx 想要进行更改":

需要注意的是这个接口在macOS 10.7以后就是Deprecated了,苹果推荐使用另一种方式进行提权操作。

 

2. 使用<ServiceManagement.framework> 的 SMJobBless:

这是苹果用于替换第一种方式的提权方案,它需要我们创建一个命令行程序Helper设置为随launchd启动(拥有root权限),App再通过SMJobBless接口实现和Helper通信,从而将需要root权限执行的命令交由Helper执行:

Helper命令行工具:

1. 配置Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleIdentifier</key>
	<string>com.apple.SMJobBlessHelper</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>SMJobBlessHelper</string>
	<key>CFBundleVersion</key>
	<string>1.5</string>
	<key>SMAuthorizedClients</key>
	<array>
		<string>identifier "com.apple.SMJobBlessHelper" and anchor apple generic and certificate leaf = "Apple Development: developer@apple.com (67UEKSL8V5)" and certificate 1[field.1.2] /* exists */</string>
	</array>
</dict>
</plist>

这里的SMAuthorizedClients是通过官方脚本生成:SMJobBlessUtil.py

$ ./SMJobBlessUtil.py setreq Build/Products/Debug/SMJobBlessApp.app SMJobBlessApp/SMJobBlessApp-Info.plist SMJobBlessHelper/SMJobBlessHelper-Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleIdentifier</key>
	<string>com.asbbl.ClinkMainHelper</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>ClinkMainHelper</string>
	<key>CFBundleVersion</key>
	<string>1.5</string>
	<key>SMAuthorizedClients</key>
	<array>
		<string>identifier "com.asbbl.ClinkMain" and anchor apple generic and certificate leaf= "Apple Development: developer@apple.com (67NQWSL8V5)" and certificate 1[field.1.2.8401] /* exists */</string>
	</array>
</dict>
</plist>

2. Launchd.plist 用于配置Helper自启动:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.apple.SMJobBlessHelper</string>
	<key>MachServices</key>
	<dict>
		<key>com.apple.SMJobBlessHelper</key>
		<true/>
	</dict>
	<key>RunAtLoad</key>
	<true/>
</dict>
</plist>

3. 在主程序配置Helper安装(Build Phase):

通过以上3步就完成了Helper的配置,接下来看下如何和Helper通信:

1. Helper 初始化Listener,用于监听主App的连接请求:

@interface HelperListener : NSObject<NSXPCListenerDelegate>

// allows NSXPCListener to configure/accept/resume a new incoming NSXPCConnection
-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection;

@end

@implementation ExtendedNSXPCConnection

- (instancetype)init
{
    if ((self = [super init]) {
        //init listener
        listener = [[NSXPCListener alloc] initWithMachServiceName:MAIN_HELPER_ID];
        if(!listener) {
            os_log_error(logHandle, "ERROR: failed to create mach service %@", MAIN_HELPER_ID);
            return NO;
        } else {
            os_log_info(logHandle, "created mach service %@", MAIN_HELPER_ID);
        }
    
        // start listen
        listener.delegate = self;
        [listener resume];
    
        return self;
    }
    return nil;
}

-(BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
    newConnection.exportedObject = [[HelperInterface alloc] init];
    [newConnection resume];
    
    //dbg msg
    os_log_debug(logHandle, "allowed XPC connection: %@", newConnection);
    return YES;
}

其中HelperInterface是实现了协议XPCProtocol的类,就是真正实现功能的类型。

 

2. 主App通过SMJobBless和Helper通信:

- (BOOL)blessHelper
{
    BOOL wasBlessed = NO;
    
    AuthorizationRef authRef = NULL;
    CFErrorRef error = NULL;
    AuthorizationItem authItem = {};
    AuthorizationRights authRights = {};
    AuthorizationFlags authFlags = 0;
    
    //create auth
    if(errAuthorizationSuccess != AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef)) {
        NSLog(@"ERROR: failed to create authorization");
        goto bail;
    }
    
    //init auth item
    memset(&authItem, 0x0, sizeof(authItem));
    
    authItem.name = kSMRightBlessPrivilegedHelper;
    authRights.count = 1;
    authRights.items = &authItem;
    
    //init flags
    authFlags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
    
    //get auth rights
    if(errAuthorizationSuccess != AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, authFlags, NULL)) {
        NSLog(@"ERROR: failed to copy authorization rights");
        goto bail;
    }
    
    //bless
    if(YES != (BOOL)SMJobBless(kSMDomainSystemLaunchd, (__bridge CFStringRef)(MAIN_HELPER_ID), authRef, &error)) {
        //err msg
        NSLog(@"ERROR: failed to bless job (%@)", ((__bridge NSError*)error));
        goto bail;
    }
    
    //happy
    wasBlessed = YES;
    
bail:
    
    if(NULL != authRef) {
        AuthorizationFree(authRef, kAuthorizationFlagDefaults);
        authRef = NULL;
    }
    
    if(NULL != error) {
        CFRelease(error);
        error = NULL;
    }
    
    return wasBlessed;
}

- (void)invokeHelper
{
    // Bless Helper
     [self blessHelper];

    // Create XPCService
    xpcServiceConnection = [[NSXPCConnection alloc] initWithMachServiceName:MAIN_HELPER_ID options:0];
    xpcServiceConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
    [xpcServiceConnection resume];
}

XPCProtocol就是主App和Helper之间约定的API,主App创建XPCServiceConnection之后就可以直接通过该API实现接口调用:

    __block BOOL result = NO;
    [[xpcServiceConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * proxyError)
    {
        NSLog(@"ERROR: failed to execute 'uninstall' method on helper tool (error: %@)", proxyError);
        
    }] installDrivers:drivers reply:^(NSNumber *status, NSString *msg)
    {
        result = [status boolValue];
        NSLog(@"%@", msg);
    }];

XPCProtocol定义:

@protocol XPCProtocol

// install drivers
- (void)installDrivers:(NSArray<NSString *> *)drivers reply:(void (^)(NSNumber *status, NSString *msg))reply;

@end

需要注意使用SMJobBless的方式实现提权需要对Helper程序进行签名,并且要求是正式的付费开发者账号签名才有效,免费的个人开发者账号即使签名成功了,Helper程序也无法被launchd正常启动。

 

3. 直接通过AppleScript实现提权:

这种方式只能同步执行命令,会阻塞当前App主线程(子线程无法实现密码输入),所以只适合那种可以在前台并且不耗时的命令行任务。

- (BOOL)runProcessAsAdministrator:(NSString*)scriptPath
                     withArguments:(NSArray *)arguments
                            output:(NSString **)output
                  errorDescription:(NSString **)errorDescription
{
    NSString * allArgs = [arguments componentsJoinedByString:@" "];
    NSString * fullScript = [NSString stringWithFormat:@"%@ %@", scriptPath, allArgs];

    NSDictionary *errorInfo = [NSDictionary new];
    NSString *script =  [NSString stringWithFormat:@"do shell script \"%@\" with administrator privileges", fullScript];

    NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
    NSAppleEventDescriptor * eventResult = [appleScript executeAndReturnError:&errorInfo];

    // Check errorInfo
    if (! eventResult)
    {
        // Describe common errors
        *errorDescription = nil;
        if ([errorInfo valueForKey:NSAppleScriptErrorNumber])
        {
            NSNumber * errorNumber = (NSNumber *)[errorInfo valueForKey:NSAppleScriptErrorNumber];
            if ([errorNumber intValue] == -128)
                *errorDescription = @"The administrator password is required to do this.";
        }

        // Set error message from provided message
        if (*errorDescription == nil)
        {
            if ([errorInfo valueForKey:NSAppleScriptErrorMessage])
                *errorDescription =  (NSString *)[errorInfo valueForKey:NSAppleScriptErrorMessage];
        }

        return NO;
    }
    else
    {
        // Set output to the AppleScript's output
        *output = [eventResult stringValue];

        return YES;
    }
}

 

总结

三种方式中第一种最佳,不需要签名和开发者账号(每次都需要输入密码),虽然已经被苹果标为Deprecated,不过目前最新的系统版本仍能使用;第二种方式是苹果官方认可的唯一方式,缺点是繁琐、开发工作量大,而且需要付费开发者账号进行签名;第三种方式缺点是执行过程不可控,而且会阻塞主App运行,适用场景有限。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0