On Sun, Jan 26, 2014 at 1:10 AM, Simo Sorce <simo@redhat.com> wrote:
I have to Nack this one.

I will check the following patches in the next few days, but I am
traveling so it may take a while.

Comments inline.

Hi,
Sorry for the delay with my answer.

Comments inline.
For code style/conventions I haven't added comments. I'll just go with your conventions.

- Noam
 

On Thu, 2014-01-16 at 11:02 +0200, Noam Meltzer wrote:
> Implementation of design document:
> https://fedorahosted.org/sssd/wiki/DesignDocs/rpc.idmapd%20plugin
> ---
>  src/sss_client/common.c             |   5 +
>  src/sss_client/nfs/sss_nfs_client.c | 490 ++++++++++++++++++++++++++++++++++++
>  src/sss_client/sss_cli.h            |   2 +
>  3 files changed, 497 insertions(+)
>  create mode 100644 src/sss_client/nfs/sss_nfs_client.c
>
> diff --git a/src/sss_client/common.c b/src/sss_client/common.c
> index 6044af0..58a9eca 100644
> --- a/src/sss_client/common.c
> +++ b/src/sss_client/common.c
> @@ -936,6 +936,11 @@ int sss_ssh_make_request(enum sss_cli_command cmd,
>      return ret;
>  }
>
> +int sss_nfs_make_request(enum sss_cli_command cmd, struct sss_cli_req_data *rd,
> +                         uint8_t **rep, size_t *replen, int *errnop)
> +{
> +    return sss_nss_make_request(cmd, rd, rep, replen, errnop);
> +}
>
>  const char *ssscli_err2string(int err)
>  {
> diff --git a/src/sss_client/nfs/sss_nfs_client.c b/src/sss_client/nfs/sss_nfs_client.c
> new file mode 100644
> index 0000000..9f6e047
> --- /dev/null
> +++ b/src/sss_client/nfs/sss_nfs_client.c
> @@ -0,0 +1,490 @@
> +/*
> +   SSSD
> +
> +   NFS Client
> +
> +   Copyright (C) Noam Meltzer <noam@primarydata.com>    2013
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +*/
> +
> +#include <stddef.h>
> +#include <stdlib.h>
> +#include <sys/types.h>
> +#include <errno.h>
> +#include <string.h>
> +
> +#include <nfsidmap.h>
> +#include "nfsidmap_internal.h"
> +#include "cfg.h"
> +
> +#include "sss_client/sss_cli.h"
> +#include "sss_client/nss_mc.h"
> +
> +
> +/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
> +#define SSS_NFS_PLUGIN_NAME         "sss_nfs"
> +#define SSS_NFS_CONF_SECTION        "sss_nfs"
> +#define SSS_NFS_CONF_USE_MC         "memcache"
> +#define SSS_NFS_REPLY_ID_OFFSET     (8)
> +#define SSS_NFS_REPLY_NAME_OFFSET   (SSS_NFS_REPLY_ID_OFFSET + 8)
> +#define SSS_NFS_MCBUF_LEN           (4096)
> +#define SSS_NFS_USE_MC_DEFAULT      (1)
> +
> +
> +/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
> +static char s_sss_nfs_plugin_name[]     = SSS_NFS_PLUGIN_NAME;
> +static char s_sss_nfs_conf_section[]    = SSS_NFS_CONF_SECTION;
> +static char s_sss_nfs_conf_use_mc[]     = SSS_NFS_CONF_USE_MC;

What does the s_ stand for ?
We use sss_ as a namespacing measure, but it is strictly necessary only
for non static functions or variables.
We do not use s_sss_ for anything though, and we do not use hungarian
notations or similar.

Anyway it is unclear to me what is the point of defining a static string
that contains a const string ?

The libnfsidmap API is using "char *" (without const) where ever there's a need to pass a string. (for example, check the prototype of "conf_get_str()")
Just using a macro in these places will cause the following warning to be emitted: "initialization discards 'const' qualifier from pointer target type".
So I find that the cleanest approach to handle this would be to define a "static char[]" for each of these instances.

Now, personally I prefer to do something like this:
#define PLUGIN_NAME "sss_nfs"
static char sss_nfs_plugin_name[] = PLUGIN_NAME;

rather than:
static char sss_nfs_plugin_name[] = "sss_nfs";

but I don't mind taking the second approach if that's more aligned with sssd's style/conventions.

 

> +static char *s_mcbuf    = NULL;

You are defining s_mcbuf as a global, then using it liberally in
multiple calls.

This is not reentrant nor thread safe, the client code MUST be thread
safe.

rpc.idmapd is not multi-threaded that's why I allowed myself this liberty.
however, reading on I see that using dynamic allocation is a must, so this is an actual bug.

Anyway, if the client have to be reentrant, I'll also fix the locations using "strerror" to "strerror_r".
 

> +static int   s_use_mc   = SSS_NFS_USE_MC_DEFAULT;
> +
> +
> +/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
> +/* Forward declarations */
> +static int send_recv(uint8_t **repp, size_t *rep_lenp, enum sss_cli_command cmd,
> +                     const void *req, size_t req_len);
> +static int get_id_from_rep(id_t *idp, uint8_t *rep, size_t rep_len);

Please rename to get_id_from_reply() or reply_to_id()

> +static int get_name_from_rep(char *name, size_t len, uint8_t *rep,
> +                             size_t rep_len);

Please rename to get_name_from_reply() or reply_to_name()

> +/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
> +/* get from memcache functions */
> +static int get_uid_from_mc(id_t *uid, const char *name)
> +{
> +    int rc;
> +    struct passwd pwd;
> +    char *buf;

Use char buf[SSS_NFS_MCBUF_LEN]; here instead of a globally allocate
buffer.

It's unlikely a passwd structure will be bigger than 4K but to be
absolutely accurate you may want to do what I suggest later for the
group entries (for those it is mandatory).
ok
 

> +    size_t len = 0;
> +
> +    if (!s_use_mc) {
> +        return -1;
> +    }
> +
> +    buf = s_mcbuf;
> +    sss_strnlen(name, SSS_NAME_MAX, &len);

Missing error checking here, see _nss_sss_getpwnam_r()

not quite. the actual validation is done in the "entry" functions (sss_nfs_name_to_[ug]id(), sss_nfs_[ug]id_to_name()).
This and the other 3 locations mentioned below are internal functions, so it should be safe to assume a sanitised input.
The use of "sss_strnlen()" here is only needed in order to know the length of the string.

Another option is to pass the (already calculated) length as an argument to the function. However, I find it somewhat cumbersome, so I prefer the extra strnlen.



> +    rc = sss_nss_mc_getpwnam(name, len, &pwd, buf, SSS_NFS_MCBUF_LEN);
> +    if (rc == 0) {
> +        IDMAP_LOG(1, ("found user %s in memcache", name));
> +        *uid = pwd.pw_uid;
> +    } else {
> +        IDMAP_LOG(1, ("user %s not in memcache", name));
> +    }
> +
> +    return rc;
> +}
> +
> +static int get_gid_from_mc(id_t *gid, const char *name) {
> +    int rc;
> +    struct group grp;
> +    char *buf;
> +    size_t len;
> +
> +    if (!s_use_mc) {
> +        return -1;
> +    }
> +
> +    buf = s_mcbuf;
> +    sss_strnlen(name, SSS_NAME_MAX, &len);

No error checking as above here ^

For the following call using a fixed size buffer will not do, because
group memberships may be quite large in some environments.

You can use it for a first call, but if ERANGE is returned you must
retry with a bigger, allocated buffer.

You are not doing the retry at all now, which is a bug.

> +    rc = sss_nss_mc_getgrnam(name, len, &grp, buf, SSS_NFS_MCBUF_LEN);
> +    if (rc == 0) {
> +        IDMAP_LOG(1, ("found group %s in memcache", name));
> +        *gid = grp.gr_gid;
> +    } else {
> +        IDMAP_LOG(1, ("group %s not in memcache", name));
> +    }
> +
> +    return rc;
> +}
> +
> +static int get_user_from_mc(char *name, size_t len, uid_t uid)
> +{
> +    int rc;
> +    struct passwd pwd;
> +    char *buf;
> +    size_t cache_len;

Pleas rename to pw_name_len

> +    if (!s_use_mc) {
> +        return -1;
> +    }
> +
> +    buf = s_mcbuf;

Same comments as per get_uid_from_mc()

> +    rc = sss_nss_mc_getpwuid(uid, &pwd, buf, SSS_NFS_MCBUF_LEN);
> +    if (rc == 0) {
> +        cache_len = strlen(pwd.pw_name) + 1;
> +        if (cache_len > len) {
> +            IDMAP_LOG(0, ("%s: reply too long; cache_len=%lu, len=%lu",
> +                          __func__, cache_len, len));
> +            rc = -ENOBUFS;
> +        }
> +        memcpy(name, pwd.pw_name, cache_len);
> +    }
> +
> +    return rc;
> +}
> +
> +static int get_group_from_mc(char *name, size_t len, id_t gid)
> +{
> +    int rc;
> +    struct group grp;
> +    char *buf;
> +    size_t cache_len;
> +
> +    buf = s_mcbuf;

Same comments as per get_gid_from_mc()

> +    rc = sss_nss_mc_getgrgid(gid, &grp, buf, SSS_NFS_MCBUF_LEN);
> +    if (rc == 0) {
> +        cache_len = strlen(grp.gr_name) + 1;
> +        if (cache_len > len) {
> +            IDMAP_LOG(0, ("%s: reply too long; cache_len=%lu, len=%lu",
> +                          __func__, cache_len, len));
> +            rc = -ENOBUFS;
> +        }
> +        memcpy(name, grp.gr_name, cache_len);
> +    }
> +
> +    return rc;
> +}
> +
> +/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
> +static int name_to_id(const char *name, id_t *id, enum sss_cli_command cmd)
> +{
> +    int rc;
> +    uint8_t *rep = NULL;
> +    size_t rep_len = 0;
> +    size_t name_len;
> +
> +    sss_strnlen(name, SSS_NAME_MAX, &name_len);

missing error checking

> +    rc = send_recv(&rep, &rep_len, cmd, name, name_len + 1);
> +    if (rc == 0) {
> +        rc = get_id_from_rep(id, rep, rep_len);
> +    }
> +
> +    if (rep != NULL) {

Don't test for rep, just call free() straight, glibc knows to ignore
NULLs.

ok.
 

> +        free(rep);
> +    }
> +
> +    return rc;
> +}
> +
> +static int id_to_name(char *name, size_t len, id_t id,
> +                      enum sss_cli_command cmd)
> +{
> +    int rc;
> +    const void *req = NULL;
> +    uint8_t *rep = NULL;
> +    size_t req_len;
> +    size_t rep_len = 0;
> +
> +    req = (void *)(&id);
> +    req_len = sizeof(id);

This is not safe on some architectures (like ARM).

do this instead:

size_t req_len = sizeof(id_t);
uint8_t req[req_len];

memcpy(req, &id, req_len)

Good to know. Is there any place where I can read about this difference?
 

> +    rc = send_recv(&rep, &rep_len, cmd, req, req_len);
> +    if (rc == 0) {
> +        rc = get_name_from_rep(name, len, rep, rep_len);
> +    }
> +
> +    if (rep != NULL) {
> +        free(rep);
> +    }

Just call free() w/o checks.

> +
> +    return rc;
> +}
> +
> +/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
> +static int send_recv(uint8_t **repp, size_t *rep_lenp, enum sss_cli_command cmd,


Why send_recv instead of just calling it sss_nfs_make_request() and
avoid the wrapper by the same name that does nothing in common.c ?

I've put the wrapper in common.c for the sake of good order.
If I got it correctly, every client should have a "*_make_request()" function in common.c.
When compiling with optimisation, the compiler might just drop this wrapper.



If the wrapper needs to stay for some reason, then rename send_recv() to
sss_nfs_request()

uhm... this is not a public function, so I'm not sure the "sss_nfs" prefix is needed in the name.
also, "send_recv" sounds to me more descriptive of the actual action: send request *and* receive reply.


Also use rep and rep_len, without the p, we do not change names based on
whether they are pointers or not, the compiler will warn you anyway if
you use them incorrectly.

> +                     const void *req, size_t req_len)
> +{
> +    int err = 0;
> +    enum nss_status req_rc;
> +    struct sss_cli_req_data rd;
> +    uint8_t *rep;
> +    size_t rep_len;
> +
> +    rd.data = req;
> +    rd.len = req_len;
> +
> +    sss_nss_lock();
> +    req_rc = sss_nfs_make_request(cmd, &rd, &rep, &rep_len, &err);
> +    sss_nss_unlock();
> +
> +    if (req_rc == NSS_STATUS_NOTFOUND) {
> +        return -ENOENT;
> +    }
> +    if (req_rc != NSS_STATUS_SUCCESS) {
> +        IDMAP_LOG(0, ("no-make-request; err=%i (%s)", err, strerror(err)));
> +        return -EPIPE;
> +    }

Is -EPIPE really what rpc.idmap expects if a function fails ?
Good point. I rechecked libnfsidmap code, and I see that I had a detail wrong.
libnfsidmap iterates over all plugins (in the order from the config file) and calls their translation function.
If ENOENT is returned, then iteration continues to the next plugin. on every other return value (success or other failure) the iteration will stop.
The proper action should be to always return 0 or ENOENT. I'll fix the code accordingly.


 

> +    *repp = rep;
> +    *rep_lenp = rep_len;

Why the double assignment here ? Why don't you just pass the function
arguments directly to sss_nfs_make_request() ?
The internal function and it will not assign buffers in case of error,
so you can pass in the arguments directly w/o worry.
ok
 

> +    return 0;
> +}
> +
> +static int get_id_from_rep(id_t *idp, uint8_t *rep, size_t rep_len)
> +{
> +    int rc = 0;
> +    id_t id;
> +    uint32_t num_results = 0;
> +
> +    if (rep_len < sizeof(uint32_t)) {
> +        IDMAP_LOG(0, ("%s: reply too small; rep_len=%lu", __func__, rep_len));
> +        rc = -EBADMSG;
> +        goto return_rc;
> +    }
> +
> +    SAFEALIGN_COPY_UINT32(&num_results, rep, NULL);
> +    if (num_results > 1) {
> +        IDMAP_LOG(0, ("%s: too many results (%lu)", __func__, num_results));
> +        rc = -EBADMSG;
> +        goto return_rc;
> +    }
> +    if (num_results == 0) {
> +        rc = -ENOENT;
> +        goto return_rc;
> +    }

You need to check that rep_len is >= sizeof(uint32_t) +
SSS_NFS_REPLY_ID_OFFSET before trying to copy or you could segafault if
for some reason it is smaller.

Right. I shouldn't have missed that.
 

> +    SAFEALIGN_COPY_UINT32(&id, rep + SSS_NFS_REPLY_ID_OFFSET, NULL);
> +    *idp = id;
> +
> +return_rc:

Please use the label 'done' when you want to exit early.

> +    return rc;
> +}
> +
> +static int get_name_from_rep(char *name, size_t len, uint8_t *rep,
> +                             size_t rep_len)
> +{
> +    int rc = 0;
> +    uint32_t num_results = 0;
> +    const char *buf;
> +    size_t buf_len;
> +    size_t offset;
> +
> +    if (rep_len < sizeof(uint32_t)) {
> +        IDMAP_LOG(0, ("%s: reply too small; rep_len=%lu", __func__, rep_len));
> +        rc = -EBADMSG;
> +        goto return_rc;
> +    }
> +
> +    SAFEALIGN_COPY_UINT32(&num_results, rep, NULL);
> +    if (num_results > 1) {
> +        IDMAP_LOG(0, ("%s: too many results (%lu)", __func__, num_results));
> +        rc = -EBADMSG;
> +        goto return_rc;
> +    }
> +    if (num_results == 0) {
> +        rc = -ENOENT;
> +        goto return_rc;
> +    }

As above, please do boundary checks here too.

> +    buf = (const char *)(rep + SSS_NFS_REPLY_NAME_OFFSET);
> +    buf_len = rep_len - SSS_NFS_REPLY_NAME_OFFSET;
> +    offset = 0;
> +    rc = sss_readrep_copy_string(buf, &offset, &buf_len, &len, &name, NULL);
> +    if (rc != 0) {
> +        rc = -rc;
> +    }
> +
> +return_rc:

Use 'done'

> +    return rc;
> +}
> +
> +/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
> +/* configuration parsing aids */
> +static int nfs_strcasecmp(const char *s1, const char *s2)

You are changing the semantics of the original function here and
returning a boolean that is true if the strings are equal  and false if
they are not, the origin stncasecmp instead sets -1 0 or 1 depending on
the comparison, by keeping a similar name you provides false
expectations.

Please use instead the following prototype:

static bool str_equal(const char *s1, const char *s2)
ok
 


> +{
> +    int res = 0;
> +    size_t len1;
> +    size_t len2;
> +
> +    len1 = strlen(s1);
> +    len2 = strlen(s2);
> +
> +    if (len1 == len2) {
> +        res = (strncasecmp(s1, s2, len1) == 0);
> +    }
> +
> +    return res;
> +}


> +static int nfs_conf_get_bool(char *sect, char *attr, int def)
> +{

you are getting a boolean, then please return a 'bool'
Generally, I prefer not to use this extension of C (but rather return 1 or 0) but if it's common practice in sssd, I can live with it.


> +    int res;
> +    char *val;
> +
> +    res = def;
> +    val = conf_get_str(sect, attr);
> +    if (val) {
> +        res = (nfs_strcasecmp("1", val) ||
> +               nfs_strcasecmp("yes", val) ||
> +               nfs_strcasecmp("true", val) ||
> +               nfs_strcasecmp("on", val));
> +    }
> +
> +    return res;
> +}
> +
> +/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/
> +/* The external interface */
> +static int sss_nfs_init(void)
> +{
> +    s_mcbuf = malloc(SSS_NFS_MCBUF_LEN);
> +    if (s_mcbuf == NULL) {
> +        return -ENOMEM;
> +    }
> +
> +    s_use_mc = nfs_conf_get_bool(s_sss_nfs_conf_section, s_sss_nfs_conf_use_mc,
> +                                 SSS_NFS_USE_MC_DEFAULT);

In what case would you want to disable using the mmap_cache ?
Great question.
On servers which have both local user access (effectively meaning nss calls filling the mmap_cache) & also serving NFS, the mmap cache is useful.
On contrary, with dedicated NFS servers, there are several reasons why not to use mmap_cache:
1. There's no local users activity (ie. no nss calls filling the mmap_cache).
2. The kernel has its own cache of users/groups. If the kernel requests data from rpc.idmapd then it's probably because the kernel cache was expired (and then the mmap_cache becomes useless)
3. Tthe administrator of the Linux OS might not want to configure nss to access remote directories (but rather have one-two local users) so direct communication between rpc.idmapd and sssd is in need.

 

> +    return 0;
> +}
> +
> +static int sss_nfs_princ_to_ids(char *secname, char *princ, uid_t *uid,
> +                                gid_t *gid, extra_mapping_params **ex)
> +{
> +    (void)secname;
> +    (void)princ;
> +    (void)uid;
> +    (void)gid;
> +    (void)ex;

Why these (void)s ?
Best practice, IMHO. In case someone decided to enable " -Wunused-parameter" in the compile flags.
(I prefer that over using gcc's __attribute__(unused) )


> +    IDMAP_LOG(0, ("%s: not implemented", __func__));
> +    return -ENOSYS;
> +}
> +
> +static int sss_nfs_name_to_uid(char *name, uid_t *uid)
> +{
> +    int rc;
> +    size_t name_len = 0;
> +
> +    if (name == NULL) {
> +        IDMAP_LOG(0, ("%s: name is null", __func__));
> +        return -EINVAL;
> +    }
> +    if (uid == NULL) {
> +        IDMAP_LOG(0, ("%s: uid is null", __func__));
> +        return -EINVAL;
> +    }
> +
> +    rc = sss_strnlen(name, SSS_NAME_MAX, &name_len);
> +    if (rc != 0) {
> +        IDMAP_LOG(0, ("%s: no-strnlen; rc=%i (%s)", __func__, rc,
> +                      strerror(rc)));
> +        return -rc;
> +    }
> +
> +    rc = get_uid_from_mc(uid, name);
> +    if (rc != 0) {
> +        rc = name_to_id(name, uid, SSS_NSS_GETPWNAM);
> +    }
> +
> +    return rc;
> +}
> +
> +static int sss_nfs_name_to_gid(char *name, gid_t *gid)
> +{
> +    int rc;
> +    size_t name_len = 0;
> +
> +    if (name == NULL) {
> +        IDMAP_LOG(0, ("%s: name is null", __func__));
> +        return -EINVAL;
> +    }
> +    if (gid == NULL) {
> +        IDMAP_LOG(0, ("%s: gid is null", __func__));
> +        return -EINVAL;
> +    }
> +
> +    rc = sss_strnlen(name, SSS_NAME_MAX, &name_len);
> +    if (rc != 0) {
> +        IDMAP_LOG(0, ("%s: no-strnlen; rc=%i (%s)", __func__, rc,
> +                      strerror(rc)));
> +        return -rc;
> +    }
> +
> +    rc = get_gid_from_mc(gid, name);
> +    if (rc != 0) {
> +        rc = name_to_id(name, gid, SSS_NSS_GETGRNAM);
> +    }
> +
> +    return rc;
> +}
> +
> +static int sss_nfs_uid_to_name(uid_t uid, char *domain, char *name, size_t len)
> +{
> +    int rc;
> +
> +    if (name == NULL) {
> +        IDMAP_LOG(0, ("%s: name is null", __func__));
> +        return -EINVAL;
> +    }
> +
> +    rc = get_user_from_mc(name, len, uid);
> +    if (rc != 0) {
> +        rc = id_to_name(name, len, uid, SSS_NSS_GETPWUID);
> +    }
> +
> +    return rc;
> +}
> +
> +static int sss_nfs_gid_to_name(gid_t gid, char *domain, char *name, size_t len)
> +{
> +    int rc;
> +
> +    if (name == NULL) {
> +        IDMAP_LOG(0, ("%s: name is null", __func__));
> +        return -EINVAL;
> +    }
> +
> +    rc = get_group_from_mc(name, len, gid);
> +    if (rc != 0) {
> +        rc = id_to_name(name, len, gid, SSS_NSS_GETGRGID);
> +    }
> +
> +    return rc;
> +}
> +
> +static int sss_nfs_gss_princ_to_grouplist(
> +    char *secname, char *princ, gid_t *groups, int *ngroups,
> +    extra_mapping_params **ex)
> +{
> +    (void)secname;
> +    (void)princ;
> +    (void)groups;
> +    (void)ngroups;
> +    (void)ex;
> +    IDMAP_LOG(0, ("%s: not implemented", __func__));
> +    return -ENOSYS;
> +}
> +
> +static struct trans_func s_sss_nfs_trans = {
> +    .name = s_sss_nfs_plugin_name,
> +    .init = sss_nfs_init,
> +    .princ_to_ids = sss_nfs_princ_to_ids,
> +    .name_to_uid = sss_nfs_name_to_uid,
> +    .name_to_gid = sss_nfs_name_to_gid,
> +    .uid_to_name = sss_nfs_uid_to_name,
> +    .gid_to_name = sss_nfs_gid_to_name,
> +    .gss_princ_to_grouplist = sss_nfs_gss_princ_to_grouplist,
> +};
> +
> +struct trans_func *libnfsidmap_plugin_init(void)
> +{
> +    return (&s_sss_nfs_trans);
> +}
> diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
> index 285a297..7e488e4 100644
> --- a/src/sss_client/sss_cli.h
> +++ b/src/sss_client/sss_cli.h
> @@ -527,6 +527,8 @@ int sss_ssh_make_request(enum sss_cli_command cmd,
>                           struct sss_cli_req_data *rd,
>                           uint8_t **repbuf, size_t *replen,
>                           int *errnop);
> +int sss_nfs_make_request(enum sss_cli_command cmd, struct sss_cli_req_data *rd,
> +                         uint8_t **rep, size_t *replen, int *errnop);
>
>  #if 0
>


--
Simo Sorce * Red Hat, Inc * New York




--
Noam Meltzer
Linux Software Engineer
PRIMARY DATA

P.O. Box 12650, Herzliya Pituach 4673300
9 Hamenofim St. Akerstein Towers, Tower A, 5th fl. Herzliya

Office: +972-77-8981888 | Fax: +972-3-7617140 | Mobile: +972-54-5873843

Email: noam@primarydata.com