/* acap.c -- ACAP API implementation * * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* $Id: acap.c,v 1.30 2003/02/13 20:15:17 rjs3 Exp $ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lib/prot.h" /* xxx should be */ #include "../lib/iptostring.h" #include "../lib/xmalloc.h" #include "acap.h" #define ACAP_DEFAULT_PORT (674) /* types of callbacks */ typedef enum { CB_ANY, CB_CTX_ADDTO, CB_ALERT, CB_BYE, CB_CTX_CHANGE, CB_CONTINUATION, CB_DELETED, CB_ENTRY, CB_EXTENSION, CB_LANG, CB_LISTRIGHTS, CB_CTX_MODTIME, CB_MODTIME, CB_MYRIGHTS, CB_QUOTA, CB_REFER, CB_CTX_REMOVEFROM, CB_CMD_DONE } acap_cb_type; typedef void acap_cb_any(void *rock); typedef void acap_cb_alert(char *str, void *rock); typedef void acap_cb_bye(char *str, void *rock); typedef void acap_cb_deleted(char *entry_name, void *rock); typedef void acap_cb_continuation(acap_value_t *, void *); typedef void acap_cb_generic(void); struct acap_cb { acap_cb_type t; acap_cb_generic *cb; void *rock; struct acap_cb *next; }; struct acap_cmd_s { char *tag; const struct acap_requested *ask; acap_entry_t *got; struct acap_cb *callbacks; acap_cmd_t *next; }; struct acap_context_s { char *name; const struct acap_requested *ask; struct acap_cb *callbacks; acap_context_t *next; }; struct acap_conn_s { char *host; int sock; struct protstream *pin, *pout; int next_tag; sasl_conn_t *sasl; int sasl_error; int authed; acap_cmd_t *commands; acap_context_t *contexts; struct acap_cb *callbacks; /* global callbacks */ }; /* prototypes */ int acap_cmd_start(acap_conn_t *conn, acap_cmd_t **ret, char *fmt, ...); int acap_process_on_command(acap_conn_t *conn, acap_cmd_t *cmd, acap_result_t *res); static int acap_register_conn_callback(acap_conn_t *conn, acap_cb_type cbtype, acap_cb_generic *f, void *rock); static int acap_register_cmd_callback(acap_cmd_t *cmd, acap_cb_type cbtype, acap_cb_generic *f, void *rock); static int acap_register_context_callback(acap_context_t *context, acap_cb_type cbtype, acap_cb_generic *f, void *rock); int acap_init(void) { initialize_acap_error_table(); return ACAP_OK; } /* ----------------- opening connection stuff ----------------- */ /* this is an imperfect URL parser. */ static int acap_parse_url(const char *url, char **user, char **mech, char **host, char **port) { char *p, *q, *myurl; char buf[1024]; strncpy(buf, url, sizeof(buf)); buf[1023] = '\0'; myurl = buf; if (strncmp(myurl, "acap://", 7) != 0) { return ACAP_URL_NOT_RECOGNIZED; } myurl += 7; /* look for a user name */ p = strchr(myurl, '@'); if (p != NULL) { int user_len; /* there's a username */ if ((q = strstr(myurl, ";AUTH=")) != NULL) { user_len = q - myurl; *user = (char *) malloc(user_len + 1); strncpy(*user, myurl, user_len); (*user)[user_len] = '\0'; /* what authentication scheme? */ q += 6; if (*q == '*' && *(q+1) == ';') { *mech = NULL; /* any */ } else { int mech_len = p - q; *mech = (char *) malloc(mech_len + 1); strncpy(*mech, q, mech_len); (*mech)[mech_len] = '\0'; } } else { /* no AUTH=, so let's just grab the username */ user_len = p - myurl; *user = (char *) malloc(user_len + 1); strncpy(*user, myurl, user_len); (*user)[user_len] = '\0'; *mech = NULL; /* any auth */ } /* advance p beyond the '@' */ p++; } else { /* no '@' sign; want anonymous authentication */ *user = NULL; *mech = strdup("ANONYMOUS"); p = myurl; } /* p now points to a hostport SLASH stuff */ if ((q = strchr(p, '/')) != NULL) { /* ignore trailing URL foo */ *q = '\0'; } if ((q = strchr(p, ':')) != NULL) { /* there's a port */ int host_len; host_len = q - p; *host = (char *) malloc(host_len + 1); strncpy(*host, p, host_len); (*host)[host_len] = '\0'; *port = strdup(q+1); } else { /* no port */ *host = strdup(p); *port = NULL; } return ACAP_OK; } static int acap_conn_do_connect(acap_conn_t *conn, char *host, char *port) { struct addrinfo hints, *res0 = NULL, *res; int err; int sock = -1; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_CANONNAME; if ((err = getaddrinfo(host, port, &hints, &res0)) != 0) { return ACAP_NO_CONNECTION; } conn->host = strdup(res0->ai_canonname ? res0->ai_canonname : host); for (res = res0; res; res = res->ai_next) { sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock < 0) continue; if (connect(sock, res->ai_addr, res->ai_addrlen) >= 0) break; close(sock); sock = -1; } freeaddrinfo(res0); if (sock < 0) { return ACAP_NO_CONNECTION; } conn->sock = sock; conn->pin = prot_new(sock, 0); conn->pout = prot_new(sock, 1); prot_setflushonread(conn->pin, conn->pout); return ACAP_OK; } /* grab the * ACAP line, and if mech is non-NULL put the available mechs there. set any capabilities we care about in the conn struct */ static int acap_conn_do_capability(acap_conn_t *conn, char **mech) { char buf[4096]; char *s; s = prot_fgets(buf, sizeof(buf), conn->pin); if (s == NULL) { return ACAP_NO_CONNECTION; } if (mech) { /* find available mechanisms */ char *p; p = buf; while (*p) { if (*p == 's' || *p == 'S') { if (!strncasecmp(p, "SASL", 4)) { break; } } p++; } if (!p) { /* no mechanisms available? */ *mech = NULL; } else { char *q = strchr(p, ')'); if (!q) { *mech = NULL; return ACAP_PROTOCOL_ERROR; } *mech = malloc(q-p+1); strncpy(*mech, p, q-p); (*mech)[q-p] = '\0'; } } /* currently, there aren't any interesting capabilities */ return ACAP_OK; } static void acap_auth_step(acap_value_t *arg, void *rock) { int r; acap_conn_t *conn = (acap_conn_t *) rock; const char *out; unsigned int outlen; /* just got a response from the server; process it and generate our own */ r = sasl_client_step(conn->sasl, arg->data, arg->len, NULL, &out, &outlen); if (r == SASL_OK || r == SASL_CONTINUE) { if (outlen) { prot_printf(conn->pout, "{%d+}\r\n", outlen); prot_write(conn->pout, out, outlen); prot_printf(conn->pout, "\r\n"); } else { prot_printf(conn->pout, "\"\"\r\n"); } } else { /* cancel authentication */ prot_printf(conn->pout, "*\r\n"); } } static void acap_auth_done(acap_result_t res, void *rock) { acap_conn_t *conn = (acap_conn_t *) rock; /* it would be nice if we could get the SASL keyword here */ if (res == ACAP_RESULT_OK) { conn->authed = 1; } else { conn->authed = 0; } } static int mysasl_simple_cb(void *context, int id, const char **result, unsigned int *len) { if (!result) { return SASL_BADPARAM; } switch (id) { case SASL_CB_USER: *result = (char *)context; break; default: return SASL_BADPARAM; } if (len) { *len = *result ? strlen(*result) : 0; } return SASL_OK; } static sasl_security_properties_t *make_secprops(int min,int max) { sasl_security_properties_t *ret=(sasl_security_properties_t *) malloc(sizeof(sasl_security_properties_t)); ret->maxbufsize=1024; ret->min_ssf=min; ret->max_ssf=max; ret->security_flags=0; ret->property_names=NULL; ret->property_values=NULL; return ret; } static int acap_conn_do_auth(acap_conn_t *conn, const char *user, const char *mechs, sasl_callback_t *cb) { struct sockaddr_storage saddr_l, saddr_r; char localip[60], remoteip[60]; sasl_security_properties_t *secprops=NULL; const char *mech; const char *str; socklen_t salen; unsigned len; int r; acap_cmd_t *cmd; sasl_callback_t *mycb; int didusercb, i; /* copy the sasl callbacks, to ours, add the CB_USER */ i = 0; if (cb) { while (cb[i].id != SASL_CB_LIST_END) i++; } i += 2; /* one for SASL_CB_LIST_END, one for SASL_CB_USER */ mycb = (sasl_callback_t *) malloc(i * sizeof(sasl_callback_t)); i = 0; didusercb = 0; if (cb) { while (cb[i].id != SASL_CB_LIST_END) { if (cb[i].id == SASL_CB_USER) { /* override */ mycb[i].id = SASL_CB_USER; mycb[i].proc = &mysasl_simple_cb; mycb[i].context = (void *) user; didusercb = 1; } else { /* copy */ mycb[i].id = cb[i].id; mycb[i].proc = cb[i].proc; mycb[i].context = cb[i].context; } i++; } } if (!didusercb) { /* add on user callback */ mycb[i].id = SASL_CB_USER; mycb[i].proc = &mysasl_simple_cb; mycb[i].context = (void *) user; i++; } /* add SASL_CB_LIST_END */ mycb[i].id = SASL_CB_LIST_END; mycb[i].proc = NULL; mycb[i].context = NULL; salen = sizeof(saddr_r); if (getpeername(conn->sock, (struct sockaddr *) &saddr_r, &salen)) { free(mycb); return ACAP_NO_CONNECTION; } salen = sizeof(saddr_l); if (getsockname(conn->sock, (struct sockaddr *) &saddr_l, &salen)) { free(mycb); return ACAP_NO_CONNECTION; } if(iptostring((struct sockaddr *)&saddr_l, salen, localip, 60)) return ACAP_NO_CONNECTION; if(iptostring((struct sockaddr *)&saddr_r, salen, remoteip, 60)) return ACAP_NO_CONNECTION; r = sasl_client_new("acap", conn->host, localip, remoteip, mycb, 0, &conn->sasl); if (r != SASL_OK || !conn->sasl) { conn->sasl_error = r; free(mycb); return ACAP_NO_AUTHENTICATION; } secprops = make_secprops(0, 0); /* xxx */ if (secprops != NULL) { sasl_setprop(conn->sasl, SASL_SEC_PROPS, secprops); free(secprops); } r = sasl_client_start(conn->sasl, mechs, NULL, &str, &len, &mech); if (r == SASL_OK || r == SASL_CONTINUE) { if (str && len) { r = acap_cmd_start(conn, &cmd, "Authenticate %s %S", mech, len, str); } else { /* no initial response */ r = acap_cmd_start(conn, &cmd, "Authenticate %s", mech); } if (r != ACAP_OK) { return r; } } else { conn->sasl_error = r; free(mycb); return ACAP_NO_AUTHENTICATION; } /* register callbacks! */ acap_register_conn_callback(conn, CB_CONTINUATION, (acap_cb_generic *) &acap_auth_step, conn); acap_register_cmd_callback(cmd, CB_CMD_DONE, (acap_cb_generic *) &acap_auth_done, conn); r = acap_process_on_command(conn, cmd, NULL); if (r != ACAP_OK) { free(mycb); return r; } if (conn->authed) { prot_setsasl(conn->pin, conn->sasl); prot_setsasl(conn->pout, conn->sasl); free(mycb); return ACAP_OK; } else { free(mycb); return ACAP_NO_AUTHENTICATION; } } int acap_conn_connect(const char *url, sasl_callback_t *cb, acap_conn_t **ret) { char *user = NULL, *mech = NULL; char *host = NULL, *port = NULL; acap_conn_t *conn; int r; if (!url || !ret) return ACAP_BAD_PARAM; conn = (acap_conn_t *) malloc(sizeof(acap_conn_t)); *ret = conn; memset(conn, 0, sizeof(acap_conn_t)); r = acap_parse_url(url, &user, &mech, &host, &port); if (r != ACAP_OK) { return r; } r = acap_conn_do_connect(conn, host, port ? port : "acap"); free(host); if (port) free(port); if (r != ACAP_OK) { if (user) free(user); if (mech) free(mech); return r; } /* if mech != NULL, we don't want to get another list of mechs */ r = acap_conn_do_capability(conn, mech ? NULL : &mech); if (r != ACAP_OK) { if (user) free(user); if (mech) free(mech); return r; } r = acap_conn_do_auth(conn, user, mech, cb); if (user) free(user); if (mech) free(mech); if (r != ACAP_OK) { return r; } return ACAP_OK; } /* ----------- end of opening connection ----------- */ int acap_conn_get_sock(acap_conn_t *conn) { return conn->sock; } /* closes & frees; if the connection is already closed, just frees */ int acap_conn_close(acap_conn_t *conn) { if (!conn) return ACAP_BAD_PARAM; /* xxx the following values are not freed. not sure if they should be char *host; acap_cmd_t *commands; acap_context_t *contexts; struct acap_cb *callbacks; */ if (conn->sasl) sasl_dispose(&(conn->sasl)); if (conn->pin) prot_free(conn->pin); if (conn->pout) prot_free(conn->pout); close(conn->sock); return ACAP_OK; } /* -------------------- commands ----------------------- */ static int send_quoted_p(int l, char *a) { int i; if (l > 128) return 0; for (i = 0; i < l; i++) { if (a[i] == '*') continue; if (!isprint((int) a[i])) return 0; if (a[i] == '"') return 0; if (a[i] == '\\') return 0; } return 1; } acap_cmd_t *acap_cmd_new(acap_conn_t *conn) { char tag[50]; acap_cmd_t *ret; if (!conn) return NULL; ret = (acap_cmd_t *) malloc(sizeof(acap_cmd_t)); sprintf(tag, "%d", conn->next_tag++); ret->tag = strdup(tag); ret->ask = NULL; ret->got = NULL; ret->callbacks = NULL; /* lock conn here */ ret->next = conn->commands; conn->commands = ret; /* unlock conn here */ return ret; } /* fmt is expected to be: * %% => send % * %s => C-style string, sent either quoted or literal * %S => length & char *, sent as literal * %v => acap_value_t, sent either quoted or literal * %d => int (sent unquoted) * %c => char (sent unquoted) */ int acap_cmd_start(acap_conn_t *conn, acap_cmd_t **ret, char *fmt, ...) { va_list pvar; char *percent; char tag[50], buf[50]; struct protstream *out = conn->pout; acap_cmd_t *cmd; if (!conn) return ACAP_NO_CONNECTION; cmd = (acap_cmd_t *) malloc(sizeof(acap_cmd_t)); /* lock conn here */ /* generate tag */ sprintf(tag, "%d", conn->next_tag++); cmd->callbacks = NULL; cmd->next = conn->commands; conn->commands = cmd; /* unlock conn here */ prot_write(out, tag, strlen(tag)); prot_putc(' ', out); /* setup cmd structure */ cmd->tag = strdup(tag); *ret = cmd; /* send out command */ va_start(pvar, fmt); while ((percent = strchr(fmt, '%')) != NULL) { prot_write(out, fmt, percent - fmt); switch (*++percent) { case '%': prot_putc('%', out); break; case 'd': { int i = va_arg(pvar, int); sprintf(buf, "%d", i); prot_write(out, buf, strlen(buf)); break; } case 's': { char *s = va_arg(pvar, char *); int l = strlen(s); if (send_quoted_p(l, s)) { prot_putc('"', out); prot_write(out, s, l); prot_putc('"', out); } else { prot_printf(out, "{%d+}\r\n", l); prot_write(out, s, l); } break; } case 'S': { int l = va_arg(pvar, int); char *s = va_arg(pvar, char *); prot_printf(out, "{%d+}\r\n", l); prot_write(out, s, l); break; } case 'v': { acap_value_t *v = va_arg(pvar, acap_value_t *); if (send_quoted_p(v->len, v->data)) { prot_putc('"', out); prot_write(out, v->data, v->len); prot_putc('"', out); } else { prot_printf(out, "{%d+}\r\n", v->len); prot_write(out, v->data, v->len); } break; } case 'c': { int i = va_arg(pvar, int); prot_putc(i, out); break; } default: abort(); } fmt = percent+1; } prot_write(out, fmt, strlen(fmt)); va_end(pvar); prot_putc('\r', out); prot_putc('\n', out); return ACAP_OK; } void acap_cmd_free(acap_conn_t *conn, acap_cmd_t *cmd) { acap_cmd_t *p; if (!conn) return; p = conn->commands; if (p == cmd) { conn->commands = cmd->next; } else { while (p->next && (p->next != cmd)) { p = p->next; } p->next = cmd->next; } free(cmd->tag); while (cmd->callbacks) { struct acap_cb *ptr = cmd->callbacks->next; free(cmd->callbacks); cmd->callbacks = ptr; } free(cmd); } /* ------------------ callbacks ------------------------------- */ static int acap_register_conn_callback(acap_conn_t *conn, acap_cb_type cbtype, acap_cb_generic *f, void *rock) { struct acap_cb *new = (struct acap_cb *) malloc(sizeof(struct acap_cb)); new->t = cbtype; new->cb = f; new->rock = rock; /* lock conn here */ new->next = conn->callbacks; conn->callbacks = new; /* unlock conn here */ return ACAP_OK; } static int acap_register_cmd_callback(acap_cmd_t *cmd, acap_cb_type cbtype, acap_cb_generic *f, void *rock) { struct acap_cb *new = (struct acap_cb *) malloc(sizeof(struct acap_cb)); new->t = cbtype; new->cb = f; new->rock = rock; /* lock conn here */ new->next = cmd->callbacks; cmd->callbacks = new; /* unlock conn here */ return ACAP_OK; } static int acap_register_context_callback(acap_context_t *context, acap_cb_type cbtype, acap_cb_generic *f, void *rock) { struct acap_cb *new = (struct acap_cb *) malloc(sizeof(struct acap_cb)); new->t = cbtype; new->cb = f; new->rock = rock; /* lock conn here */ new->next = context->callbacks; context->callbacks = new; /* unlock conn here */ return ACAP_OK; } /* ----------------- datastructure stuff ------------------ */ acap_attribute_t *acap_attribute_new(char *name) { acap_attribute_t *ret = (acap_attribute_t *) malloc(sizeof(acap_attribute_t)); ret->name = strdup(name); ret->v = NULL; return ret; } acap_attribute_t *acap_attribute_new_simple (char *attrname, char *attrvalue) { acap_attribute_t *ret; /* call function to create new attribute */ ret = acap_attribute_new(attrname); if (ret == NULL) return NULL; if (attrvalue) { int attrlen = strlen(attrvalue); /* fill in it's single string */ ret->t = ACAP_VALUE_SINGLE; ret->v = (acap_value_t *) malloc(sizeof(acap_value_t) + attrlen + 1); ret->v->len = attrlen; ret->v->next = NULL; strcpy(ret->v->data, attrvalue); } else { ret->t = ACAP_VALUE_DEFAULT; ret->v = NULL; } return ret; } void acap_attribute_free(acap_attribute_t *a) { if (!a) return; if (a->name) free(a->name); while (a->v) { acap_value_t *w = a->v; a->v = w->next; free(w); } free(a); } static int attr_comp(const void *v1, const void *v2) { /* this depends on the fact that the _name_ is the first element in the attribute structure!!! */ const char **n1 = (const char **) v1; const char **n2 = (const char **) v2; return strcmp(*n1, *n2); } acap_entry_t *acap_entry_new(char *name) { acap_entry_t *ret = (acap_entry_t *) malloc(sizeof(acap_entry_t)); ret->name = strdup(name); ret->refcount = 1; ret->attrs = skiplist_new(10, 0.5, &attr_comp); return ret; } acap_entry_t *acap_entry_copy(acap_entry_t *e) { e->refcount++; return e; } void acap_entry_free(acap_entry_t *e) { if (!e) return; e->refcount--; if (e->refcount != 0) return; /* free it */ free(e->name); /* free the attributes & free the list */ skiplist_freeeach(e->attrs, (void (*)(const void *))acap_attribute_free); free(e); } char *acap_entry_getname(acap_entry_t *e) { return e->name; } acap_value_t *acap_entry_getattr(acap_entry_t *e, char *attrname) { acap_attribute_t *a = (acap_attribute_t *) ssearch(e->attrs, &attrname); if (a == NULL) return NULL; if (a->t == ACAP_VALUE_NIL || a->t == ACAP_VALUE_DEFAULT) return NULL; return a->v; } char *acap_entry_getattr_simple(acap_entry_t *e, char *attrname) { acap_attribute_t *a = (acap_attribute_t *) ssearch(e->attrs, &attrname); if (a == NULL) return NULL; if (a->t == ACAP_VALUE_NIL || a->t == ACAP_VALUE_DEFAULT) return NULL; return a->v->data; } /* ----------------------- process stuff --------------------- */ static void eatline(acap_conn_t *conn, int c) { struct protstream *in = conn->pin; int state = 0; char *statediagram = " {}\r"; int size = -1; for (;;) { if (c == '\n') return; if (c == statediagram[state+1]) { state++; if (state == 1) size = 0; else if (c == '\r') { /* Got a literal */ c = prot_getc(in);/* Eat newline */ while (size) { c = prot_getc(in); /* Eat contents */ } state = 0; /* Go back to scanning for eol */ } } else if (state == 1 && isdigit(c)) { size = size * 10 + c - '0'; } else state = 0; c = prot_getc(in); if (c == EOF) return; } } struct sbuf { char *s; int alloc; int len; }; #define BUFGROWSIZE 128 static int getstring(acap_conn_t *conn, struct sbuf *buf) { int c; int i, len =0; int sawdigit = 0; struct protstream *in = conn->pin; if (buf->alloc == 0) { buf->alloc = BUFGROWSIZE - 1; buf->s = malloc(buf->alloc + 1); } c = prot_getc(in); switch (c) { case '\"': /* quoted string; max size is 1024 */ for (;;) { c = prot_getc(in); if (c == '\\') { c = prot_getc(in); } else if (c == '\"') { buf->s[len] = '\0'; buf->len = len; return prot_getc(in); } else if (c == EOF || c == '\r' || c == '\n') { buf->s[len] = '\0'; if (c != EOF) prot_ungetc(c, in); return EOF; } if (len == buf->alloc) { buf->alloc += BUFGROWSIZE; buf->s = realloc(buf->s, buf->alloc+1); } buf->s[len++] = c; if (len > 1024) { buf->s[len] = '\0'; return EOF; } } case '{': /* literal */ buf->s[0] = '\0'; while ((c = prot_getc(in)) != EOF && isdigit(c)) { sawdigit = 1; len = len * 10 + c - '0'; } if (!sawdigit || c != '}') { if (c != EOF) prot_ungetc(c, in); return EOF; } c = prot_getc(in); if (c != '\r') { if (c != EOF) prot_ungetc(c, in); return EOF; } c = prot_getc(in); if (c != '\n') { if (c != EOF) prot_ungetc(c, in); return EOF; } if (len >= buf->alloc) { buf->alloc = len+1; buf->s = realloc(buf->s, buf->alloc + 1); } for (i = 0; i < len; i++) { c = prot_getc(in); if (c == EOF) { buf->s[len] = '\0'; return EOF; } buf->s[i] = c; } buf->s[len] = '\0'; buf->len = len; return prot_getc(in); default: /* not a string! */ buf->s[0] = '\0'; if (c != EOF) prot_ungetc(c, in); return EOF; } } static int getvalstr(acap_conn_t *conn, acap_value_t **ret) { int c; int i, len =0; int sawdigit = 0; struct protstream *in = conn->pin; acap_value_t *v; c = prot_getc(in); switch (c) { case '\"': /* quoted string; max size is 1024 */ v = (acap_value_t *) malloc(sizeof(acap_value_t) + 1024); v->next = NULL; for (;;) { c = prot_getc(in); if (c == '\\') { c = prot_getc(in); } else if (c == '\"') { v->data[len] = '\0'; v->len = len; *ret = v; return prot_getc(in); } else if (c == EOF || c == '\r' || c == '\n') { v->data[len] = '\0'; if (c != EOF) prot_ungetc(c, in); free(v); return EOF; } v->data[len++] = c; if (len == 1024) { free(v); return EOF; } } case '{': /* literal */ while ((c = prot_getc(in)) != EOF && isdigit(c)) { sawdigit = 1; len = len * 10 + c - '0'; } if (!sawdigit || c != '}') { if (c != EOF) prot_ungetc(c, in); return EOF; } c = prot_getc(in); if (c != '\r') { if (c != EOF) prot_ungetc(c, in); return EOF; } c = prot_getc(in); if (c != '\n') { if (c != EOF) prot_ungetc(c, in); return EOF; } v = (acap_value_t *) malloc(sizeof(acap_value_t) + len + 1); v->next = NULL; for (i = 0; i < len; i++) { c = prot_getc(in); if (c == EOF) { free(v); return EOF; } v->data[i] = c; } v->data[len] = '\0'; v->len = len; *ret = v; return prot_getc(in); default: /* not a string! */ if (c != EOF) prot_ungetc(c, in); return EOF; } } static int getattr(acap_conn_t *conn, char *attrname, acap_attribute_t **ret) { /* expecting either STRING or (STRING SP STRING ... STRING) or NIL */ int ch; acap_attribute_t *attr = acap_attribute_new(attrname); ch = prot_getc(conn->pin); switch (ch) { case '(': ch = ' '; attr->t = ACAP_VALUE_LIST; attr->v = NULL; while (ch == ' ') { acap_value_t *nv; ch = getvalstr(conn, &nv); if (ch != EOF) { nv->next = attr->v; attr->v = nv; } } if (ch == EOF) ch = prot_getc(conn->pin); if (ch != ')') { acap_attribute_free(attr); return EOF; } else { *ret = attr; return prot_getc(conn->pin); } break; case 'N': case 'n': ch = prot_getc(conn->pin); if (ch == 'i' || ch == 'I') ch = prot_getc(conn->pin); if (ch == 'l' || ch == 'L') { attr->v = NULL; attr->t = ACAP_VALUE_NIL; *ret = attr; return prot_getc(conn->pin); } else { if (ch != EOF) prot_ungetc(ch, conn->pin); return EOF; } break; default: prot_ungetc(ch, conn->pin); /* maybe it's a string? */ attr->t = ACAP_VALUE_SINGLE; ch = getvalstr(conn, &attr->v); if (ch == EOF) { acap_attribute_free(attr); return EOF; } else { *ret = attr; return ch; } break; } } static int get_integer(acap_conn_t *conn, unsigned int *ret) { int c; unsigned int acc = 0; int saw = 0; c = prot_getc(conn->pin); while ((c != EOF) && isdigit(c)) { saw = 1; acc = acc * 10 + c - '0'; c = prot_getc(conn->pin); } if (!saw || (c == EOF)) { if (c != EOF) prot_ungetc(c, conn->pin); return EOF; } *ret = acc; return c; } /* gets the entry describe in the cmd structure out */ static int get_entry(acap_conn_t *conn, const struct acap_requested *ask, char *name, acap_entry_t **ret) { int a = 0; /* the attribute in "ask" are we scanning */ acap_entry_t *e = acap_entry_new(name); int ch; static struct sbuf attrname; acap_attribute_t *attribute; *ret = NULL; ch = ' '; /* loop through ask, recording what we get in "got" */ for (a = 0; a < ask->n_attrs; a++) { /* getting cmd->ask->attrname[a]; this can be multiple entries if it ends in a star */ char *curattr = ask->attrs[a].attrname; int curmeta = ask->attrs[a].ret; if (ch != ' ') { /* error ! */ if (ch != EOF) prot_ungetc(ch, conn->pin); return EOF; } if (curattr[strlen(curattr) - 1] == '*') { /* expecting a list of ATTR VALUE */ ch = prot_getc(conn->pin); if (ch != '(') { if (ch != EOF) prot_ungetc(ch, conn->pin); return EOF; } ch = prot_getc(conn->pin); while (ch != ')') { /* now expect: "(" "ATTR" SP "VALUE" ")" [SP / ")"] */ if (curmeta == 0) { if (ch != '(') { if (ch != EOF) prot_ungetc(ch, conn->pin); return EOF; } ch = getstring(conn, &attrname); if (ch != ' ') { if (ch != EOF) prot_ungetc(ch, conn->pin); return EOF; } ch = getattr(conn, attrname.s, &attribute); if (ch != ')') { if (ch != EOF) prot_ungetc(ch, conn->pin); return EOF; } ch = prot_getc(conn->pin); /* insert (ATTR VALUE) into our entry */ sinsert(e->attrs, (void *) attribute); } else { /* we're looking at a list of metadata */ prot_ungetc(ch, conn->pin); ch = getstring(conn, &attrname); if ((ch != ' ') && (ch != ')')) { if (ch != EOF) prot_ungetc(ch, conn->pin); return EOF; } attribute = acap_attribute_new(attrname.s); /* insert (ATTR NULL) into our entry */ sinsert(e->attrs, (void *) attribute); } /* if we're looking at a SP, advance */ if (ch == ' ') ch = prot_getc(conn->pin); } ch = prot_getc(conn->pin); } else { /* expecting just a NIL or VALUE corresponding to curattr */ ch = getattr(conn, curattr, &attribute); sinsert(e->attrs, (void *) attribute); } } *ret = e; return ch; } static int process_continuation(acap_conn_t *conn) { int ch; struct acap_cb *ptr; acap_value_t *v; ch = getvalstr(conn, &v); if (ch != '\r') { if (ch != EOF) prot_ungetc(ch, conn->pin); return EOF; } /* look for callbacks */ ptr = conn->callbacks; while (ptr) { if (CB_CONTINUATION == ptr->t) { acap_cb_continuation *f = (acap_cb_continuation *) ptr->cb; f(v, ptr->rock); } ptr = ptr->next; } /* free value */ free(v); return ch; } static int process_atom(char *tag, char *atom, acap_conn_t *conn) { acap_cb_type t; acap_cmd_t *cmd = NULL; acap_context_t *ctxt = NULL; struct acap_cb *ptr, *gb_ptr; int ch = EOF; /* xxx??? */ static struct sbuf context; /* should be made connection-local ! */ static struct sbuf buf; acap_result_t res; /* first, let's find out if this will trigger any callbacks */ /* canonify tag ('*' => NULL) */ if (!strcmp(tag, "*")) { tag = NULL; } else { /* find the command structure for this call */ /* lock conn here */ cmd = conn->commands; /* unlock conn here */ while (cmd && strcmp(tag, cmd->tag)) { cmd = cmd->next; } if (!cmd) { /* what sort of weird tag did i get back here? */ return EOF; } } /* identify atom */ /* we'll assume it's an extension if we don't recognize it */ t = CB_EXTENSION; res = ACAP_RESULT_NOTDONE; switch (atom[0]) { case 'a': case 'A': if (!strcasecmp(atom, "ADDTO")) { /* ADDTO */ t = CB_CTX_ADDTO; } else if (!strcasecmp(atom, "ALERT")) { /* ALERT */ t = CB_ALERT; } break; case 'b': case 'B': if (!strcasecmp(atom, "BAD")) { /* BAD */ t = CB_CMD_DONE; res = ACAP_RESULT_BAD; } else if (!strcasecmp(atom, "BYE")) { /* BYE */ t = CB_BYE; } break; case 'c': case 'C': if (!strcasecmp(atom, "CHANGE")) { /* CHANGE */ t = CB_CTX_CHANGE; } break; case 'd': case 'D': if (!strcasecmp(atom, "DELETED")) { /* DELETED */ t = CB_DELETED; } break; case 'e': case 'E': if (!strcasecmp(atom, "ENTRY")) { /* ENTRY */ t = CB_ENTRY; } break; case 'l': case 'L': if (!strcasecmp(atom, "LANG")) { /* LANG */ t = CB_LANG; } else if (!strcasecmp(atom, "LISTRIGHTS")) { /* LISTRIGHTS */ t = CB_LISTRIGHTS; } break; case 'm': case 'M': if (!strcasecmp(atom, "MODTIME")) { /* MODTIME */ if (tag) { t = CB_MODTIME; } else { t = CB_CTX_MODTIME; } } else if (!strcasecmp(atom, "MYRIGHTS")) { /* MYRIGHTS */ t = CB_MYRIGHTS; } break; case 'n': case 'N': if (!strcasecmp(atom, "NO")) { /* NO */ t = CB_CMD_DONE; res = ACAP_RESULT_NO; } break; case 'o': case 'O': if (!strcasecmp(atom, "OK")) { /* OK */ t = CB_CMD_DONE; res = ACAP_RESULT_OK; } break; case 'q': case 'Q': if (!strcasecmp(atom, "QUOTA")) { /* QUOTA */ t = CB_QUOTA; } break; case 'r': case 'R': if (!strcasecmp(atom, "REFER")) { /* REFER */ t = CB_REFER; } else if (!strcasecmp(atom, "REMOVEFROM")) { /* REMOVEFROM */ t = CB_CTX_REMOVEFROM; } break; default: break; } /* do we need to get the context name? */ if ((t == CB_CTX_ADDTO) || (t == CB_CTX_CHANGE) || (t == CB_CTX_MODTIME) || (t == CB_CTX_REMOVEFROM)) { ch = getstring(conn, &context); if (ch != ' ') return ch; /* find the context */ ctxt = conn->contexts; while (ctxt && strcmp(context.s, ctxt->name)) { ctxt = ctxt->next; } if (!ctxt) { /* huh? */ return EOF; } } if (cmd) ptr = cmd->callbacks; else if (ctxt) ptr = ctxt->callbacks; else ptr = NULL; while (ptr && (t != ptr->t)) { ptr = ptr->next; } gb_ptr = conn->callbacks; while (gb_ptr && (t != gb_ptr->t)) { gb_ptr = gb_ptr->next; } /* no? return now (we eat the rest of the line when we return) */ if (ptr == NULL && gb_ptr == NULL) { return prot_getc(conn->pin); } /* yes? process the line & make callbacks, but not the CRLF */ switch (t) { case CB_CTX_ADDTO: /* ADDTO & REMOVEFROM are identical */ case CB_CTX_REMOVEFROM: { acap_entry_t *entry; unsigned position; ch = getstring(conn, &buf); if (ch != ' ') return ch; ch = get_integer(conn, &position); if (ch == EOF) return ch; /* grab entry information, if any */ if (t == CB_CTX_ADDTO) { if (ch != ' ') return ch; ch = get_entry(conn, ctxt->ask, buf.s, &entry); if (ch == EOF) return ch; } else { /* t == CB_CTX_REMOVEFROM, so no entry name */ entry = acap_entry_new(buf.s); } while (ptr != NULL) { if (t == ptr->t) { void (*f)(acap_entry_t *, unsigned, void *) = (void (*)(acap_entry_t *, unsigned, void *)) ptr->cb; f(entry, position, ptr->rock); } ptr = ptr->next; } while (gb_ptr != NULL) { if (t == gb_ptr->t) { void (*f)(acap_entry_t *, unsigned, void *) = (void (*)(acap_entry_t *, unsigned, void *)) gb_ptr->cb; f(entry, position, gb_ptr->rock); } gb_ptr = gb_ptr->next; } acap_entry_free(entry); break; } case CB_ALERT: { /* possible CODE here */ ch = prot_getc(conn->pin); if (ch == '(') { /* skip the code */ while (ch != ')') ch = prot_getc(conn->pin); ch = prot_getc(conn->pin); } else { prot_ungetc(ch, conn->pin); } ch = getstring(conn, &buf); if (ch == EOF) return ch; while (gb_ptr != NULL) { if (t == gb_ptr->t) { acap_cb_alert *falert = (acap_cb_alert *) gb_ptr->cb; falert(buf.s, gb_ptr->rock); } gb_ptr = gb_ptr->next; } break; } case CB_BYE: { /* possible CODE here */ ch = prot_getc(conn->pin); if (ch == '(') { /* skip the code */ while (ch != ')') ch = prot_getc(conn->pin); ch = prot_getc(conn->pin); } else { prot_ungetc(ch, conn->pin); } ch = getstring(conn, &buf); if (ch == EOF) return ch; while (gb_ptr != NULL) { if (t == gb_ptr->t) { acap_cb_bye *f = (acap_cb_bye *) gb_ptr->cb; f(buf.s, gb_ptr->rock); } gb_ptr = gb_ptr->next; } break; } case CB_CTX_CHANGE: { acap_entry_t *entry; unsigned old_position, new_position; ch = getstring(conn, &buf); if (ch != ' ') return ch; ch = get_integer(conn, &old_position); if (ch != ' ') return ch; ch = get_integer(conn, &new_position); if (ch != ' ') return ch; ch = get_entry(conn, ctxt->ask, buf.s, &entry); if (ch == EOF) return ch; while (ptr != NULL) { if (t == ptr->t) { void (*f)(acap_entry_t *, unsigned, unsigned, void *) = (void (*)(acap_entry_t *, unsigned, unsigned, void *)) ptr->cb; f(entry, old_position, new_position, ptr->rock); } ptr = ptr->next; } while (gb_ptr != NULL) { if (t == gb_ptr->t) { void (*f)(acap_entry_t *, unsigned, unsigned, void *) = (void (*)(acap_entry_t *, unsigned, unsigned, void *)) ptr->cb; f(entry, old_position, new_position, gb_ptr->rock); } gb_ptr = gb_ptr->next; } acap_entry_free(entry); break; } case CB_CONTINUATION: fatal("impossible",-1); break; case CB_DELETED: { ch = getstring(conn, &buf); if (ch == EOF) return ch; while (gb_ptr != NULL) { if (t == gb_ptr->t) { acap_cb_deleted *f = (acap_cb_deleted *) gb_ptr->cb; f(buf.s, gb_ptr->rock); } gb_ptr = gb_ptr->next; } break; } case CB_ENTRY: { acap_entry_t *entry; ch = getstring(conn, &buf); if (ch != ' ') return ch; ch = get_entry(conn, cmd->ask, buf.s, &entry); if (ch == EOF) return ch; while (ptr != NULL) { if (t == ptr->t) { void (*f)(acap_entry_t *, void *) = (void (*)(acap_entry_t *, void *)) ptr->cb; f(entry, ptr->rock); } ptr = ptr->next; } while (gb_ptr != NULL) { if (t == gb_ptr->t) { void (*f)(acap_entry_t *, void *) = (void (*)(acap_entry_t *, void *)) gb_ptr->cb; f(entry, gb_ptr->rock); } gb_ptr = gb_ptr->next; } acap_entry_free(entry); break; } case CB_EXTENSION: break; case CB_LANG: fatal("not implemented", 3); break; case CB_LISTRIGHTS: fatal("not implemented", 3); break; case CB_CTX_MODTIME: /* at this stage, these look the same */ case CB_MODTIME: { ch = getstring(conn, &buf); while (ptr != NULL) { if (t == ptr->t) { void (*f)(char *, void *) = (void (*)(char *, void *))ptr->cb; f(buf.s, ptr->rock); } ptr = ptr->next; } while (gb_ptr != NULL) { if (t == gb_ptr->t) { void (*f)(char *, void *) = (void (*)(char *, void *))gb_ptr->cb; f(buf.s, gb_ptr->rock); } gb_ptr = gb_ptr->next; } break; } case CB_MYRIGHTS: fatal("not implemented", 3); break; case CB_QUOTA: fatal("not implemented", 3); break; case CB_REFER: fatal("not implemented", 3); break; case CB_CMD_DONE: /* possible CODE here */ ch = prot_getc(conn->pin); if (ch == '(') { /* skip the code */ while (ch != ')') ch = prot_getc(conn->pin); ch = prot_getc(conn->pin); } else { /* oops, not a code! */ prot_ungetc(ch, conn->pin); } ch = getstring(conn, &buf); while (ptr != NULL) { if (t == ptr->t) { acap_cb_completion_t *f = (acap_cb_completion_t *) ptr->cb; f(res, ptr->rock); } ptr = ptr->next; } while (gb_ptr != NULL) { if (t == gb_ptr->t) { acap_cb_completion_t *f = (acap_cb_completion_t *) gb_ptr->cb; f(res, gb_ptr->rock); } gb_ptr = gb_ptr->next; } /* now delete the command from our "outstanding commands" list */ acap_cmd_free(conn, cmd); break; case CB_ANY: break; } /* now eat the rest of the line */ return ch; } /* setting ACAP_PROCESS_NOBLOCK ensures that we don't block waiting for a new line. however, we will block waiting for the completion of a response, which, depending on network/server conditions, could be a significant amount of time. let the user beware */ int acap_process_line(acap_conn_t *conn, int flag) { char tag[33]; char atom[1025]; int taglen, atomlen, ch; int r; enum { TAG, ATOM, CRLF } s; if (!conn) return ACAP_NO_CONNECTION; if (flag & ACAP_PROCESS_NOBLOCK) { prot_NONBLOCK(conn->pin); } else { prot_BLOCK(conn->pin); } /* start state */ r = ACAP_OK; taglen = 0; atomlen = 0; s = TAG; /* special case the first character */ errno = 0; ch = prot_getc(conn->pin); if (ch == EOF) { if (errno == EAGAIN) { return ACAP_WOULD_BLOCK; } else { return ACAP_NO_CONNECTION; } } prot_BLOCK(conn->pin); taglen = 0; for (;;) { if (ch == EOF) { if (conn->pin->eof) return ACAP_NO_CONNECTION; else { eatline(conn, ch); return ACAP_PROTOCOL_ERROR; } } switch (s) { case TAG: if (ch != ' ') { if (taglen < 32) { tag[taglen++] = ch; ch = prot_getc(conn->pin); } else { r = ACAP_PROTOCOL_ERROR; ch = prot_getc(conn->pin); s = CRLF; } } else { tag[taglen] = '\0'; /* check if tag represents a continuation */ if (taglen == 1 && tag[0] == '+') { /* ok, it's a continuation */ ch = process_continuation(conn); s = CRLF; } else { ch = prot_getc(conn->pin); s = ATOM; } } break; case ATOM: if (ch != ' ') { if (atomlen < 1024) { atom[atomlen++] = ch; ch = prot_getc(conn->pin); } else { r = ACAP_PROTOCOL_ERROR; ch = prot_getc(conn->pin); s = CRLF; } } else { atom[atomlen] = '\0'; ch = process_atom(tag, atom, conn); s = CRLF; } break; case CRLF: eatline(conn, ch); return r; } } } int acap_process_outstanding(acap_conn_t *conn) { int r = ACAP_OK; if (!conn) return ACAP_NO_CONNECTION; while (r == ACAP_OK) { r = acap_process_line(conn, ACAP_PROCESS_NOBLOCK); } if (r == ACAP_WOULD_BLOCK) r = ACAP_OK; return r; } static void cmd_done(acap_result_t res, void *rock) { acap_result_t *p = (acap_result_t *) rock; *p = res; } int acap_process_on_command(acap_conn_t *conn, acap_cmd_t *cmd, acap_result_t *res) { acap_result_t cmd_fini = ACAP_RESULT_NOTDONE; int r; if (!conn) return ACAP_NO_CONNECTION; r = acap_register_cmd_callback(cmd, CB_CMD_DONE, (acap_cb_generic *) &cmd_done, &cmd_fini); if (r != ACAP_OK) { return r; } while (cmd_fini == ACAP_RESULT_NOTDONE) { r = acap_process_line(conn, 0); if (r != ACAP_OK) { return r; } } if (res) *res = cmd_fini; return ACAP_OK; } /* ------------------- search stuff ------------------- */ acap_context_t *acap_context_new(acap_conn_t *conn, char *name, const struct acap_requested *ask) { acap_context_t *ret = (acap_context_t *) malloc(sizeof(acap_context_t)); ret->name = strdup(name); ret->callbacks = NULL; ret->ask = ask; /* lock conn here */ ret->next = conn->contexts; conn->contexts = ret; /* unlock conn here */ return ret; } void acap_context_free(acap_conn_t *conn, acap_context_t *context) { acap_cmd_t *cmd; int r; acap_context_t *p; r = acap_cmd_start(conn, &cmd, "FREECONTEXT %s", context->name); if (r == ACAP_OK) r = acap_process_on_command(conn, cmd, NULL); p = conn->contexts; if (p == context) { conn->contexts = context->next; } else { while (p->next && (p->next != context)) { p = p->next; } p->next = context->next; } while (context->callbacks) { struct acap_cb *ptr = context->callbacks->next; free(context->callbacks); context->callbacks = ptr; } free(context->name); free(context); } int acap_search_dataset(acap_conn_t *conn, const char *dataset, char *spec, int depth, const struct acap_requested *ret_attrs, const struct acap_sort *sort_order, const acap_cb_completion_t *cmd_cb, const struct acap_search_callback *search_cb, acap_context_t **context_ptr, const struct acap_context_callback *context_cb, void *rock, acap_cmd_t **ret) { char contextname[50]; acap_cmd_t *cmd; acap_context_t *context = NULL; if (!conn) return ACAP_NO_CONNECTION; if (!dataset) return ACAP_BAD_PARAM; if (!spec) return ACAP_BAD_PARAM; if (!context_ptr && context_cb) return ACAP_BAD_PARAM; if (depth < 0) return ACAP_BAD_PARAM; cmd = acap_cmd_new(conn); /* lock conn here */ cmd->ask = ret_attrs; if (ret) *ret = cmd; prot_printf(conn->pout, "%s SEARCH \"%s\" ", cmd->tag, dataset); if (search_cb && ret_attrs && (ret_attrs->n_attrs > 0)) { int i; prot_printf(conn->pout, "RETURN ("); for (i = 0; i < ret_attrs->n_attrs; i++) { if (i > 0) prot_putc(' ', conn->pout); prot_printf(conn->pout, "{%d+}\r\n%s", strlen(ret_attrs->attrs[i].attrname), ret_attrs->attrs[i].attrname); if (ret_attrs->attrs[i].ret & ATTRIBUTE) { prot_printf(conn->pout,"(\"attribute\")"); } if (ret_attrs->attrs[i].ret & VALUE) { prot_printf(conn->pout,"(\"value\")"); } if (ret_attrs->attrs[i].ret & SIZE) { prot_printf(conn->pout,"(\"size\")"); } } prot_printf(conn->pout, ") "); } prot_printf(conn->pout, "DEPTH %d ", depth); if (context_ptr) { sprintf(contextname, "%d", conn->next_tag++); context = acap_context_new(conn, contextname, ret_attrs); *context_ptr = context; prot_printf(conn->pout, "MAKECONTEXT "); if (sort_order) { prot_printf(conn->pout, "ENUMERATE "); } if (context_cb) { prot_printf(conn->pout, "NOTIFY "); } prot_printf(conn->pout, "\"%s\" ", contextname); } if (sort_order) { prot_printf(conn->pout, "SORT ("); while (sort_order) { prot_printf(conn->pout, "{%d+}\r\n%s {%d+}\r\n%s", strlen(sort_order->attrname), sort_order->attrname, strlen(sort_order->comparator),sort_order->comparator); if (sort_order->next) prot_putc(' ', conn->pout); sort_order = sort_order->next; } prot_printf(conn->pout, ") "); } /* finish off the command */ prot_printf(conn->pout, "%s\r\n", spec); /* add all the callbacks */ if (cmd_cb) { acap_register_cmd_callback(cmd, CB_CMD_DONE, (acap_cb_generic *) cmd_cb, rock); } if (search_cb) { if (search_cb->entry_data) { acap_register_cmd_callback(cmd, CB_ENTRY, (acap_cb_generic *) search_cb->entry_data, rock); } if (search_cb->modtime) { acap_register_cmd_callback(cmd, CB_MODTIME, (acap_cb_generic *) search_cb->modtime, rock); } } if (context_cb) { if (context_cb->addto) { acap_register_context_callback(context, CB_CTX_ADDTO, (acap_cb_generic *) context_cb->addto, rock); } if (context_cb->removefrom) { acap_register_context_callback(context, CB_CTX_REMOVEFROM, (acap_cb_generic *) context_cb->removefrom, rock); } if (context_cb->change) { acap_register_context_callback(context, CB_CTX_CHANGE, (acap_cb_generic *) context_cb->change, rock); } if (context_cb->modtime) { acap_register_context_callback(context, CB_CTX_MODTIME, (acap_cb_generic *) context_cb->modtime, rock); } } /* unlock conn here */ return ACAP_OK; } int acap_search_context(acap_conn_t *conn, acap_context_t *context, char *spec, struct acap_requested *ret_attrs, struct acap_sort *sort_order, acap_cb_completion_t *cmd_cb, struct acap_search_callback *search_cb, void *rock, acap_cmd_t **ret) { acap_cmd_t *cmd; if (!conn) return ACAP_NO_CONNECTION; if (!context) return ACAP_BAD_PARAM; if (!spec) return ACAP_BAD_PARAM; cmd = acap_cmd_new(conn); /* lock conn here */ cmd->ask = ret_attrs; if (ret) *ret = cmd; prot_printf(conn->pout, "%s SEARCH \"%s\" ", cmd->tag, context->name); if (search_cb && ret_attrs && (ret_attrs->n_attrs > 0)) { int i; prot_printf(conn->pout, "RETURN ("); for (i = 0; i < ret_attrs->n_attrs; i++) { if (i > 0) prot_putc(' ', conn->pout); prot_printf(conn->pout, "{%d+}\r\n%s", strlen(ret_attrs->attrs[i].attrname), ret_attrs->attrs[i].attrname); if (ret_attrs->attrs[i].ret & ATTRIBUTE) { prot_printf(conn->pout,"(\"attribute\")"); } if (ret_attrs->attrs[i].ret & VALUE) { prot_printf(conn->pout,"(\"value\")"); } if (ret_attrs->attrs[i].ret & SIZE) { prot_printf(conn->pout,"(\"size\")"); } } prot_printf(conn->pout, ") "); } if (sort_order) { prot_printf(conn->pout, "SORT ("); while (sort_order) { prot_printf(conn->pout, "{%d+}\r\n%s {%d+}\r\n%s", strlen(sort_order->attrname), sort_order->attrname, strlen(sort_order->comparator),sort_order->comparator); if (sort_order->next) prot_putc(' ', conn->pout); sort_order = sort_order->next; } prot_printf(conn->pout, ") "); } /* finish off the command */ prot_printf(conn->pout, "%s\r\n", spec); /* add all the callbacks */ if (cmd_cb) { acap_register_cmd_callback(cmd, CB_CMD_DONE, (acap_cb_generic *) cmd_cb, rock); } if (search_cb) { if (search_cb->entry_data) { acap_register_cmd_callback(cmd, CB_ENTRY, (acap_cb_generic *) search_cb->entry_data, rock); } if (search_cb->modtime) { acap_register_cmd_callback(cmd, CB_MODTIME, (acap_cb_generic *) search_cb->modtime, rock); } } /* unlock conn here */ return ACAP_OK; } int acap_updatecontext(acap_conn_t *conn, acap_context_t *context, acap_cb_completion_t *cmd_cb, void *rock, acap_cmd_t **ret) { acap_cmd_t *cmd; if (!conn) return ACAP_NO_CONNECTION; if (!context) return ACAP_BAD_PARAM; cmd = acap_cmd_new(conn); /* lock conn here */ if (ret) *ret = cmd; prot_printf(conn->pout, "%s UPDATECONTEXT \"%s\"\r\n", cmd->tag, context->name); if (cmd_cb) { acap_register_cmd_callback(cmd, CB_CMD_DONE, (acap_cb_generic *) cmd_cb, rock); } /* unlock conn here */ return ACAP_OK; } /* ------------------------- storing --------------------------- */ #define initial_time "20000121071919000000" /* xxx tim - what should this be? */ static void write_value(struct protstream *out, acap_value_t *v) { if (send_quoted_p(v->len, v->data)) { prot_putc('"', out); prot_write(out, v->data, v->len); prot_putc('"', out); } else { prot_printf(out, "{%d+}\r\n", v->len); prot_write(out, v->data, v->len); } } int acap_store_entry(acap_conn_t *conn, acap_entry_t *entry, acap_cb_completion_t *cmd_cb, void *rock, int flags, acap_cmd_t **ret) { acap_cmd_t *cmd; acap_value_t *v; acap_attribute_t *attr; char *modtime = NULL; skipnode *node; int first; if (!conn) return ACAP_NO_CONNECTION; if ((flags & ACAP_STORE_INITIAL) && (flags & ACAP_STORE_FORCE)) return ACAP_BAD_PARAM; if (!entry) return ACAP_BAD_PARAM; if (flags & ACAP_STORE_INITIAL) { modtime = initial_time; } else if (!(flags & ACAP_STORE_FORCE)) { v = acap_entry_getattr(entry, "modtime"); if (v != NULL) { modtime = v->data; } } cmd = acap_cmd_new(conn); /* lock conn */ prot_printf(conn->pout, "%s STORE (\"%s\" ", cmd->tag, entry->name); if (modtime) { prot_printf(conn->pout, "UNCHANGEDSINCE \"%s\" ", modtime); } first = 1; for (attr = sfirst(entry->attrs, &node); attr != NULL; attr = snext(&node)) { int l; if (!strcmp(attr->name, "modtime")) continue; if (!strcmp(attr->name, "entry")) continue; if (!first) { prot_putc(' ', conn->pout); } first = 0; l = strlen(attr->name); if (send_quoted_p(l, attr->name)) { prot_putc('"', conn->pout); prot_write(conn->pout, attr->name, l); prot_putc('"', conn->pout); } else { prot_printf(conn->pout, "{%d+}\r\n", l); prot_write(conn->pout, attr->name, l); } switch (attr->t) { case ACAP_VALUE_SINGLE: prot_putc(' ', conn->pout); write_value(conn->pout, attr->v); break; case ACAP_VALUE_LIST: prot_printf(conn->pout, " (\"value\" ("); for (v = attr->v; v != NULL; v = v->next) { write_value(conn->pout, v); if (v->next) { prot_putc(' ', conn->pout); } } prot_printf(conn->pout, "))"); break; case ACAP_VALUE_NIL: prot_printf(conn->pout, " NIL"); break; case ACAP_VALUE_DEFAULT: prot_printf(conn->pout, " DEFAULT"); break; } } prot_printf(conn->pout, ")\r\n"); /* add all the callbacks */ if (cmd_cb) { acap_register_cmd_callback(cmd, CB_CMD_DONE, (acap_cb_generic *) cmd_cb, rock); } /* unlock conn */ if (ret) *ret = cmd; return ACAP_OK; } int acap_store_attribute(acap_conn_t *conn, const char *entry, acap_attribute_t *attr, char *unchangedsince, acap_cb_completion_t *cmd_cb, void *rock, acap_cmd_t **ret) { acap_cmd_t *cmd; acap_value_t *v; int l; if (!conn) return ACAP_NO_CONNECTION; /* we can't explicitly set the modtime */ if (!strcmp(attr->name, "modtime")) return ACAP_BAD_PARAM; if (!strcmp(attr->name, "entry") && (attr->t == ACAP_VALUE_SINGLE || attr->t == ACAP_VALUE_LIST)) return ACAP_NOT_SUPPORTED; cmd = acap_cmd_new(conn); /* lock conn */ prot_printf(conn->pout, "%s STORE (\"%s\" ", cmd->tag, entry); if (unchangedsince) { prot_printf(conn->pout, "UNCHANGEDSINCE \"%s\" ", unchangedsince); } l = strlen(attr->name); prot_printf(conn->pout, "{%d+}\r\n", l); prot_write(conn->pout, attr->name, l); switch (attr->t) { case ACAP_VALUE_SINGLE: prot_putc(' ', conn->pout); write_value(conn->pout, attr->v); break; case ACAP_VALUE_LIST: prot_printf(conn->pout, " (\"value\" ("); for (v = attr->v; v != NULL; v = v->next) { write_value(conn->pout, v); if (v->next) { prot_putc(' ', conn->pout); } } prot_printf(conn->pout, "))"); case ACAP_VALUE_NIL: prot_printf(conn->pout, " NIL"); break; case ACAP_VALUE_DEFAULT: prot_printf(conn->pout, " DEFAULT"); break; } prot_printf(conn->pout, ")\r\n"); /* unlock conn */ if (ret) *ret = cmd; return ACAP_OK; } /* ---------------- deleting --------------------- */ int acap_delete_entry_name(acap_conn_t *conn, const char *entryname, acap_cb_completion_t *cmd_cb, void *rock, acap_cmd_t **ret) { return acap_delete_attribute(conn, (char *) entryname, "entry", cmd_cb, rock, ret); } int acap_delete_attribute(acap_conn_t *conn, char *entryname, char *attrname, acap_cb_completion_t *cmd_cb, void *rock, acap_cmd_t **ret) { int r; acap_attribute_t *attr; if (!conn) return ACAP_NO_CONNECTION; attr = acap_attribute_new (attrname); if (attr == NULL) return ACAP_NOMEM; attr->t = ACAP_VALUE_DEFAULT; r = acap_store_attribute(conn, entryname, attr, NULL, cmd_cb, rock, ret); acap_attribute_free(attr); return r; }