root/usr/src/lib/libslp/clib/SLPFindAttrs.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdlib.h>
#include <syslog.h>
#include <slp-internal.h>

struct attr_node {
        char *tag, *val;
};

static SLPError slp_packAttrRqst(slp_handle_impl_t *, const char *,
                                        const char *);
static int compare_tags(const void *, const void *);
static void collate_attrs(char *, void **, int *, int);
static void parens_attr(char *, void **, int *);
static void merge_attrs(struct attr_node *, char *);
static char *build_attrs_list(void *collator);
static void collect_attrs(void *, VISIT, int, void *);
static SLPBoolean unpackDAAdvert_attr(slp_handle_impl_t *, char *,
                                        SLPAttrCallback, void *,
                                        void **, int *);
static SLPBoolean unpackSAAdvert_attr(slp_handle_impl_t *, char *,
                                        SLPAttrCallback, void *,
                                        void **, int *);

SLPError SLPFindAttrs(SLPHandle hSLP, const char *pcURL, const char *pcScope,
                        const char *pcAttrIds,
                        SLPAttrCallback callback, void *pvUser) {
        SLPError err;
        int wantSAAdvert =
                strcasecmp(pcURL, "service:service-agent") == 0;
        int wantDAAdvert =
                strcasecmp(pcURL, "service:directory-agent") == 0;
        int isSpecial = wantSAAdvert || wantDAAdvert;
        SLPMsgReplyCB *unpack_cb;


        if (!hSLP || !pcURL || !pcScope || (!*pcScope && !isSpecial) ||
            !pcAttrIds || !callback) {
                return (SLP_PARAMETER_BAD);
        }

        if ((strlen(pcURL) > SLP_MAX_STRINGLEN) ||
            (strlen(pcScope) > SLP_MAX_STRINGLEN) ||
            (strlen(pcAttrIds) > SLP_MAX_STRINGLEN)) {
            return (SLP_PARAMETER_BAD);
        }

        if ((err = slp_start_call(hSLP)) != SLP_OK)
                return (err);

        /* Special packer and unpacker for DA and SA solicitations */
        if (wantDAAdvert) {
                unpack_cb = (SLPMsgReplyCB *)unpackDAAdvert_attr;
                err = slp_packSrvRqst(pcURL, "", hSLP);
                ((slp_handle_impl_t *)hSLP)->force_multicast = SLP_TRUE;
        } else if (wantSAAdvert) {
                unpack_cb = (SLPMsgReplyCB *)unpackSAAdvert_attr;
                err = slp_packSrvRqst(pcURL, "", hSLP);
                ((slp_handle_impl_t *)hSLP)->force_multicast = SLP_TRUE;
        } else {
                /* normal service request */
                unpack_cb = (SLPMsgReplyCB *)slp_UnpackAttrReply;
                /* format params into msgBuf */
                err = slp_packAttrRqst(hSLP, pcURL, pcAttrIds);
        }

        if (err == SLP_OK)
                err = slp_ua_common(hSLP, pcScope,
                    (SLPGenericAppCB *)(uintptr_t)callback, pvUser, unpack_cb);

        if (err != SLP_OK)
                slp_end_call(hSLP);

        return (err);
}

SLPBoolean slp_UnpackAttrReply(slp_handle_impl_t *hp, char *reply,
                                SLPAttrCallback cb, void *cookie,
                                void **collator, int *numResults) {
        char *pcAttrList;
        SLPError errCode;
        unsigned short protoErrCode;
        size_t len, off;
        int maxResults = slp_get_maxResults();
        SLPBoolean cont = SLP_TRUE;
        int auth_cnt;
        size_t tbv_len;
        char *attr_tbv;

        if (!reply) {
                /* no more results */
                if (!hp->async) {
                    pcAttrList = build_attrs_list(*collator);
                }

                if (!hp->async && pcAttrList) {
                    cb(hp, pcAttrList, SLP_OK, cookie);
                    free(pcAttrList);
                }
                cb(hp, NULL, SLP_LAST_CALL, cookie);
                return (SLP_FALSE);
        }

        /* parse reply into params */
        len = slp_get_length(reply);
        off = SLP_HDRLEN + slp_get_langlen(reply);
        /* err code */
        if (slp_get_sht(reply, len, &off, &protoErrCode) != SLP_OK)
                return (SLP_TRUE);
        /* internal errors should have been filtered out by the net code */
        if ((errCode = slp_map_err(protoErrCode)) != SLP_OK) {
                return (cb(hp, NULL, errCode, cookie));
        }

        /* attr list */
        attr_tbv = reply + off;
        tbv_len = off;
        if (slp_get_string(reply, len, &off, &pcAttrList) != SLP_OK)
                return (SLP_TRUE);
        tbv_len = off - tbv_len;

        /* number of attr auths */
        if (slp_get_byte(reply, len, &off, &auth_cnt) != SLP_OK) {
            goto cleanup;
        }

        /* get and verify auth blocks */
        if ((!hp->internal_call && slp_get_security_on()) || auth_cnt > 0) {
                size_t abLen = 0;
                struct iovec iov[1];

                iov[0].iov_base = attr_tbv;
                iov[0].iov_len = tbv_len;

                if (slp_verify(iov, 1,
                                reply + off,
                                len - off,
                                auth_cnt,
                                &abLen) != SLP_OK) {
                    goto cleanup;
                }
        }

        /* collate */
        if (!hp->async) {
                collate_attrs(pcAttrList, collator, numResults, maxResults);
        } else {
                /* async: invoke cb */
                cont = cb((SLPHandle) hp, pcAttrList, errCode, cookie);
                (*numResults)++;
        }

cleanup:
        free(pcAttrList);

        /* check maxResults */
        if (!hp->internal_call && *numResults == maxResults) {
                return (SLP_FALSE);
        }

        return (cont);
}

/*
 * unpackDAAdvert_attr follows the same logic stream as UnpackAttrReply,
 * except that reply contains a DAAdvert.
 */
static SLPBoolean unpackDAAdvert_attr(slp_handle_impl_t *hp, char *reply,
                                        SLPAttrCallback cb, void *cookie,
                                        void **collator, int *numResults) {
        char *surl, *scopes, *attrs, *spis;
        SLPBoolean cont = SLP_TRUE;
        SLPError errCode;
        int maxResults = slp_get_maxResults();

        if (!reply) {
                /* no more results */
                if (!hp->async) {
                    attrs = build_attrs_list(*collator);
                }

                if (!hp->async && attrs) {
                        cb(hp, attrs, SLP_OK, cookie);
                        free(attrs);
                }
                cb(hp, NULL, SLP_LAST_CALL, cookie);
                return (SLP_FALSE);
        }

        if (slp_unpackDAAdvert(reply, &surl, &scopes, &attrs, &spis, &errCode)
            != SLP_OK) {
                return (SLP_TRUE);
        }
        if (errCode != SLP_OK) {
                return (cb(hp, NULL, errCode, cookie));
        }

        /* collate */
        if (!hp->async) {
                collate_attrs(attrs, collator, numResults, maxResults);
        } else {
                /* async: invoke cb */
                cont = cb((SLPHandle) hp, attrs, errCode, cookie);
                (*numResults)++;
        }

        /* cleanup */
        free(surl);
        free(scopes);
        free(attrs);
        free(spis);

        /* check maxResults */
        if (!hp->internal_call && *numResults == maxResults) {
                return (SLP_FALSE);
        }

        return (cont);
}

/*
 * unpackSAAdvert_attr follows the same logic stream as UnpackAttrReply,
 * except that reply contains an SAAdvert.
 */
static SLPBoolean unpackSAAdvert_attr(slp_handle_impl_t *hp, char *reply,
                                        SLPAttrCallback cb, void *cookie,
                                        void **collator, int *numResults) {
        char *surl, *scopes, *attrs;
        SLPBoolean cont = SLP_TRUE;
        int maxResults = slp_get_maxResults();

        if (!reply) {
                /* no more results */
                if (!hp->async) {
                    attrs = build_attrs_list(*collator);
                }

                if (!hp->async && attrs) {
                        cb(hp, attrs, SLP_OK, cookie);
                        free(attrs);
                }
                cb(hp, NULL, SLP_LAST_CALL, cookie);
                return (SLP_FALSE);
        }

        if (slp_unpackSAAdvert(reply, &surl, &scopes, &attrs) != SLP_OK) {
                return (SLP_TRUE);
        }

        /* collate */
        if (!hp->async) {
                collate_attrs(attrs, collator, numResults, maxResults);
        } else {
                /* async: invoke cb */
                cont = cb((SLPHandle) hp, attrs, SLP_OK, cookie);
                (*numResults)++;
        }

        /* cleanup */
        free(surl);
        free(scopes);
        free(attrs);

        /* check maxResults */
        if (!hp->internal_call && *numResults == maxResults) {
                return (SLP_FALSE);
        }

        return (cont);
}

static SLPError slp_packAttrRqst(slp_handle_impl_t *hp, const char *url,
                                        const char *ids) {
        SLPError err;
        size_t len, tmplen, msgLen;
        slp_msg_t *msg = &(hp->msg);
        char *spi = NULL;

        if (slp_get_security_on()) {
            spi = (char *)SLPGetProperty(SLP_CONFIG_SPI);
        }

        if (!spi || !*spi) {
                spi = "";
        }

        /*
         * Allocate iovec for the messge. An AttrRqst is layed out thus:
         *  0: header
         *  1: prlist length
         *  2: prlist (filled in later by networking code)
         *  3: URL string
         *  4: scopes length
         *  5: scopes (filled in later by networking code)
         *  6: tag list string and SPI string
         */
        if (!(msg->iov = calloc(7, sizeof (*(msg->iov))))) {
                slp_err(LOG_CRIT, 0, "slp_packAttrRqst", "out of memory");
                return (SLP_MEMORY_ALLOC_FAILED);
        }
        msg->iovlen = 7;

        /* calculate msg length */
        msgLen = 2 +            /* prlist length */
            2 + strlen(url) +   /* URL */
            2 +                 /* scope list length */
            2 + strlen(ids) +   /* tag list */
            2 + strlen(spi);    /* SPI string */

        if (!(msg->msg = calloc(1, msgLen))) {
                free(msg->iov);
                slp_err(LOG_CRIT, 0, "slp_packAttrRqst", "out of memory");
                return (SLP_MEMORY_ALLOC_FAILED);
        }

        /* set pointer to PR list and scope list length spaces */
        msg->prlistlen.iov_base = msg->msg;
        msg->prlistlen.iov_len = 2;
        msg->iov[1].iov_base = msg->msg;
        msg->iov[1].iov_len = 2;

        msg->scopeslen.iov_base = msg->msg + 2;
        msg->scopeslen.iov_len = 2;
        msg->iov[4].iov_base = msg->msg + 2;
        msg->iov[4].iov_len = 2;

        /* set up the scopes and prlist pointers into iov */
        msg->prlist = &(msg->iov[2]);
        msg->scopes = &(msg->iov[5]);

        len = 4;

        /* Add URL string */
        msg->iov[3].iov_base = msg->msg + len;
        tmplen = len;

        err = slp_add_string(msg->msg, msgLen, url, &len);
        msg->iov[3].iov_len = len - tmplen;

        if (err != SLP_OK)
                goto error;

        /* Add tag list */
        msg->iov[6].iov_base = msg->msg + len;
        tmplen = len;

        err = slp_add_string(msg->msg, msgLen, ids, &len);

        if (err != SLP_OK)
                goto error;

        /* SPI string */
        err = slp_add_string(msg->msg, msgLen, spi, &len);

        msg->iov[6].iov_len = len - tmplen;

        hp->fid = ATTRRQST;
        if (err == SLP_OK) {
                return (SLP_OK);
        }

        /* else error */
error:
        free(msg->iov);
        free(msg->msg);

        return (err);
}

SLPError slp_packAttrRqst_single(const char *url,
                                const char *scopes,
                                const char *ids,
                                char **msg,
                                const char *lang) {
        SLPError err;
        size_t len, msgLen;

        msgLen =
                SLP_HDRLEN + strlen(lang) + 2 +
                2 + strlen(url) +
                2 + strlen(scopes) +
                2 + strlen(ids) +
                2; /* No SPI string for internal calls */

        if (!(*msg = calloc(msgLen, 1))) {
            slp_err(LOG_CRIT, 0, "slp_packAttrRqst_single", "out of memory");
            return (SLP_MEMORY_ALLOC_FAILED);
        }

        len = 0;
        err = slp_add_header(lang, *msg, msgLen, ATTRRQST, msgLen, &len);

        len += 2;       /* empty PR list */

        if (err == SLP_OK) {
            err = slp_add_string(*msg, msgLen, url, &len);
        }
        if (err == SLP_OK) {
            err = slp_add_string(*msg, msgLen, scopes, &len);
        }
        if (err == SLP_OK) {
            err = slp_add_string(*msg, msgLen, ids, &len);
        }
        /* empty SPI */
        if (err == SLP_OK) {
            err = slp_add_string(*msg, msgLen, "", &len);
        }

        return (err);
}

static int compare_tags(const void *n1, const void *n2) {
        return slp_strcasecmp(
                ((struct attr_node *)n1)->tag,
                ((struct attr_node *)n2)->tag);
}

static void merge_attrs(struct attr_node *n, char *vals) {
        char *p, *v;

        for (p = v = vals; p; v = p) {
                p = slp_utf_strchr(v, ',');
                if (p)
                        *p++ = 0;
                slp_add2list(v, &(n->val), SLP_TRUE);
        }
}

static void parens_attr(char *attr, void **collator, int *numResults) {
        char *open_paren, *close_paren, *equals;
        struct attr_node *n, **res;

        open_paren = attr + 1;
        close_paren = slp_utf_strchr(open_paren, ')');
        if (!close_paren)
                return; /* skip bad attr list */

        *close_paren = 0;
        if (!(equals = slp_utf_strchr(open_paren, '=')))
                return;

        *equals++ = 0;

        if (!(n = malloc(sizeof (*n)))) {
                slp_err(LOG_CRIT, 0, "collate_attrs", "out of memory");
                return;
        }

        if (!(n->tag = strdup(open_paren))) {
                free(n);
                slp_err(LOG_CRIT, 0, "collate_attrs", "out of memory");
                return;
        }
        n->val = NULL;

        res = slp_tsearch(n, collator, compare_tags);

        if (*res != n) {
                merge_attrs(*res, equals);
                free(n->tag); free(n);
        } else {
                /* not found; populate new attr node */
                (*numResults)++;
                if (!(n->val = strdup(equals))) {
                        slp_err(LOG_CRIT, 0, "collate_attrs", "out of memory");
                        return;
                }
        }
}

static void collate_attrs(char *attrs, void **collator,
                                int *numResults, int maxResults) {
        char *start, *end;
        struct attr_node *n, **res;

        for (start = attrs;
                        start &&
                        *start &&
                        *numResults != maxResults;
                                                start = end) {
                if (*start == ',') start++;
                if (*start == '(') {
                        /* form of (tag=val,val) */
                        if (!(end = slp_utf_strchr(start, ')')))
                                return;         /* skip bad attr */
                        parens_attr(start, collator, numResults);
                        end++;
                        continue;
                }
                end = slp_utf_strchr(start, ',');
                if (end)
                        *end++ = 0;
                /* create a new node with the tag only */
                if (!(n = malloc(sizeof (*n)))) {
                        slp_err(LOG_CRIT, 0, "collate_attrs", "out of memory");
                        return;
                }

                if (!(n->tag = strdup(start))) {
                        free(n);
                        slp_err(LOG_CRIT, 0, "collate_attrs", "out of memory");
                        return;
                }
                n->val = NULL;
                res = slp_tsearch(n, collator, compare_tags);
                if (*res != n) {
                        /* already in the tree, so just free resources */
                        free(n->tag); free(n);
                }
                (*numResults)++;
        }
}

static char *build_attrs_list(void *collator) {
        char *answer = NULL;

        if (!collator)
                return (NULL);

        slp_twalk(collator, collect_attrs, 0, &answer);
        return (answer);
}

/*ARGSUSED*/
static void collect_attrs(void *node, VISIT order, int level, void *cookie) {
        struct attr_node *n;
        char *attr, *p, **answer = (char **)cookie;

        if (order == endorder || order == leaf) {
                n = *(struct attr_node **)node;
                if (!n->val) {
                        /* no values, so no parens */
                        if (!(attr = malloc(strlen(n->tag) + 1))) {
                                slp_err(LOG_CRIT, 0, "collect_attrs",
                                        "out of memory");
                                return;
                        }
                        (void) strcpy(attr, n->tag);
                } else {
                        if (!(attr = malloc(1 + strlen(n->tag) + 1 +
                                            strlen(n->val) + 2))) {
                                slp_err(LOG_CRIT, 0, "collect_attrs",
                                        "out of memory");
                                return;
                        }
                        /* build attr string */
                        p = attr;
                        *p++ = '(';
                        (void) strcpy(p, n->tag); p += strlen(n->tag);
                        *p++ = '=';
                        (void) strcpy(p, n->val); p += strlen(n->val);
                        *p++ = ')'; *p = 0;
                }

                slp_add2list(attr, answer, SLP_FALSE);
                free(attr);
                free(n->tag); if (n->val) free(n->val); free(n);
                free(node);
        }
}