Mono DllImport libsmbclient не аутентифицируется

Мне нужно иметь доступ к общему ресурсу Samba/Cifs из Mono, используя определенные учетные данные.

Лучший вариант, который я нашел, - это использовать libsmbclient. К сожалению, я не могу получить его для проверки подлинности. Чтобы исключить брандмауэр / безопасность / и т.д., я попытался использовать smbclient исполняемый файл, который можно подключить без проблем.

Низкоуровневая библиотека DLLImport и некоторые жестко запрограммированные кредиты для тестирования...

public static void Initialise() {
    log.Trace("Initialising libsmbclient wrapper");
    try {
        smbc_init(callbackAuth, 1);
    } catch (Exception e) {
        log.Trace(String.Format("{0}: {1}", e.GetType().Name, e.ToString()));
        throw;
    }
}

public static void callbackAuth(
    [MarshalAs(UnmanagedType.LPStr)]String server,
    [MarshalAs(UnmanagedType.LPStr)]String share,
    [MarshalAs(UnmanagedType.LPStr)]String workgroup, int workgroupMaxLen,
    [MarshalAs(UnmanagedType.LPStr)]String username, int usernameMaxLen,
    [MarshalAs(UnmanagedType.LPStr)]String password, int passwordMaxLen) {
    server = "targetserver";
    share = "Public";
    username = "Management.Service";
    password = @"{SomeComplexPassword}";
    workgroup = "targetserver";
    usernameMaxLen = username.Length;
    passwordMaxLen = password.Length;
    workgroupMaxLen = workgroup.Length;
}

[DllImport("libsmbclient.so", SetLastError = true)]
extern internal static int smbc_init(smbCGetAuthDataFn callBackAuth, int debug);

[DllImport("libsmbclient.so", SetLastError = true)]
extern internal static int smbc_opendir([MarshalAs(UnmanagedType.LPStr)]String durl);

Я пытаюсь использовать это так...

public bool DirectoryExists(string path) {
    log.Trace("Checking directory exists {0}", path);
    int handle;
    string fullpath = @"smb:" + parseUNCPath(path);
    handle = SambaWrapper.smbc_opendir(fullpath);
    if (handle < 0) {
    var error = Stdlib.GetLastError().ToString();
        if (error == "ENOENT"
           |error == "EINVAL")
            return false;
        else
            throw new Exception(error);
    } else {
    WrapperSambaClient.smbc_close(fd);
    return true;
}
}

Initialise() вызов успешно, но когда DirectoryExists звонки SambaWrapper.smbc_opendir(fullpath) Я получаю отрицательную ручку и выбрасываю следующее исключение...

Slurpy.Exceptions.FetchException: исключение: не удалось получить: EACCES > (файл:////targetserver/Public/ValidSubfolder) ---> System.Exception: EACCES

Что я делаю неправильно? Есть ли способ отладить это?

Редактировать: Кажется, проблема в том, что значения из обратного вызова auth не имеют никакого эффекта (но обратный вызов определенно вызывается, поскольку там обрабатывается оператор журнала). Мне интересно, это как-то связано с неизменяемостью строк и созданием нового экземпляра строки с новыми значениями, а не с перезаписью старых?

Редактировать: удалена полная отладочная информация от libsmbclient, пытающегося подключиться. Вы можете увидеть это в истории редактирования, если требуется.

По запросу, определение метода из заголовков...

/**@ingroup misc
 * Initialize the samba client library.
 *
 * Must be called before using any of the smbclient API function
 *  
 * @param fn        The function that will be called to obtaion 
 *                  authentication credentials.
 *
 * @param debug     Allows caller to set the debug level. Can be
 *                  changed in smb.conf file. Allows caller to set
 *                  debugging if no smb.conf.
 *   
 * @return          0 on success, < 0 on error with errno set:
 *                  - ENOMEM Out of memory
 *                  - ENOENT The smb.conf file would not load
 *
 */

int smbc_init(smbc_get_auth_data_fn fn, int debug);

/**@ingroup callback
 * Authentication callback function type (traditional method)
 * 
 * Type for the the authentication function called by the library to
 * obtain authentication credentals
 *
 * For kerberos support the function should just be called without
 * prompting the user for credentials. Which means a simple 'return'
 * should work. Take a look at examples/libsmbclient/get_auth_data_fn.h
 * and examples/libsmbclient/testbrowse.c.
 *
 * @param srv       Server being authenticated to
 *
 * @param shr       Share being authenticated to
 *
 * @param wg        Pointer to buffer containing a "hint" for the
 *                  workgroup to be authenticated.  Should be filled in
 *                  with the correct workgroup if the hint is wrong.
 * 
 * @param wglen     The size of the workgroup buffer in bytes
 *
 * @param un        Pointer to buffer containing a "hint" for the
 *                  user name to be use for authentication. Should be
 *                  filled in with the correct workgroup if the hint is
 *                  wrong.
 * 
 * @param unlen     The size of the username buffer in bytes
 *
 * @param pw        Pointer to buffer containing to which password 
 *                  copied
 * 
 * @param pwlen     The size of the password buffer in bytes
 *           
 */
typedef void (*smbc_get_auth_data_fn)(const char *srv, 
                                      const char *shr,
                                      char *wg, int wglen, 
                                      char *un, int unlen,
                                      char *pw, int pwlen);

1 ответ

Решение

Ваш обратный вызов вызывается libsmbclient, и он передает буфер и его длину из неуправляемого кода, ожидая, что вы заполните имя пользователя и пароль. Поскольку эта выделенная память контролируется вызывающей стороной, вы не можете использовать строку или строитель строк. Я предлагаю использовать IntPtr и заполнять буфер на минимально возможном уровне.

(С другой стороны, server и share являются строками только для чтения, и не ожидается, что они изменятся, поэтому мы можем сохранить их в виде строки).

public static void callbackAuth(
    [MarshalAs(UnmanagedType.LPStr)]String server,
    [MarshalAs(UnmanagedType.LPStr)]String share,
    IntPtr workgroup, int workgroupMaxLen,
    IntPtr username, int usernameMaxLen,
    IntPtr password, int passwordMaxLen) 
{
    //server = "targetserver";
    //share = "Public"; 
    // should not be assigned - 
    // you must provide credentials for specified server

    SetString(username, "Management.Service", username.Length);
    SetString(password, @"{SomeComplexPassword}", password.Length);
    SetString(workgroup, "targetserver", workgroup.Length);
}

private void SetString(IntPtr dest, string str, int maxLen)
{
    // include null string terminator
    byte[] buffer = Encoding.ASCII.GetBytes(str + "\0");
    if (buffer.Length >= maxLen) return; // buffer is not big enough

    Marshal.Copy(buffer, 0, dest, buffer.Length);
}
Другие вопросы по тегам