Ejemplo n.º 1
0
/* gets the header "head" from msg. */
static int getheader(void *mc, const char *name, const char ***body)
{
    sieve_test_message_t *msg = (sieve_test_message_t *)mc;

    *body = spool_getheader(msg->headers, name);
    if (!*body)
	return SIEVE_FAIL;
    return SIEVE_OK;
}
Ejemplo n.º 2
0
EXPORTED const char *digest_recv_success(hdrcache_t hdrs)
{
    const char **hdr = spool_getheader(hdrs, "Authentication-Info");

    return (hdr ? hdr[0]: NULL);
}
Ejemplo n.º 3
0
static int login(struct backend *s, const char *userid,
                 sasl_callback_t *cb, const char **status,
                 int noauth __attribute__((unused)))
{
    int r = 0;
    socklen_t addrsize;
    struct sockaddr_storage saddr_l, saddr_r;
    char remoteip[60], localip[60];
    static struct buf buf = BUF_INITIALIZER;
    sasl_security_properties_t secprops =
        { 0, 0xFF, PROT_BUFSIZE, 0, NULL, NULL }; /* default secprops */
    const char *mech_conf, *pass, *clientout = NULL;
    struct auth_scheme_t *scheme = NULL;
    unsigned need_tls = 0, tls_done = 0, auth_done = 0, clientoutlen;
    hdrcache_t hdrs = NULL;

    if (status) *status = NULL;

    /* set the IP addresses */
    addrsize = sizeof(struct sockaddr_storage);
    if (getpeername(s->sock, (struct sockaddr *) &saddr_r, &addrsize) ||
        iptostring((struct sockaddr *) &saddr_r, addrsize, remoteip, 60)) {
        if (status) *status = "Failed to get remote IP address";
        return SASL_FAIL;
    }

    addrsize = sizeof(struct sockaddr_storage);
    if (getsockname(s->sock, (struct sockaddr *) &saddr_l, &addrsize) ||
        iptostring((struct sockaddr *) &saddr_l, addrsize, localip, 60)) {
        if (status) *status = "Failed to get local IP address";
        return SASL_FAIL;
    }

    /* Create callbacks, if necessary */
    if (!cb) {
        buf_setmap(&buf, s->hostname, strcspn(s->hostname, "."));
        buf_appendcstr(&buf, "_password");
        pass = config_getoverflowstring(buf_cstring(&buf), NULL);
        if (!pass) pass = config_getstring(IMAPOPT_PROXY_PASSWORD);
        cb = mysasl_callbacks(NULL, /* userid */
                              config_getstring(IMAPOPT_PROXY_AUTHNAME),
                              config_getstring(IMAPOPT_PROXY_REALM),
                              pass);
        s->sasl_cb = cb;
    }

    /* Create SASL context */
    r = sasl_client_new(s->prot->sasl_service, s->hostname,
                        localip, remoteip, cb, SASL_USAGE_FLAGS, &s->saslconn);
    if (r != SASL_OK) goto done;

    r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops);
    if (r != SASL_OK) goto done;

    /* Get SASL mechanism list.  We can force a particular
       mechanism using a <shorthost>_mechs option */
    buf_setmap(&buf, s->hostname, strcspn(s->hostname, "."));
    buf_appendcstr(&buf, "_mechs");
    if (!(mech_conf = config_getoverflowstring(buf_cstring(&buf), NULL))) {
        mech_conf = config_getstring(IMAPOPT_FORCE_SASL_CLIENT_MECH);
    }

    do {
        unsigned code;
        const char **hdr, *errstr, *serverin;
        char base64[BASE64_BUF_SIZE+1];
        unsigned int serverinlen;
        struct body_t resp_body;
#ifdef SASL_HTTP_REQUEST
        sasl_http_request_t httpreq = { "OPTIONS",      /* Method */
                                        "*",            /* URI */
                                        (u_char *) "",  /* Empty body */
                                        0,              /* Zero-length body */
                                        0 };            /* Persistent cxn? */
#endif

        /* Base64 encode any client response, if necessary */
        if (clientout && scheme && (scheme->flags & AUTH_BASE64)) {
            r = sasl_encode64(clientout, clientoutlen,
                              base64, BASE64_BUF_SIZE, &clientoutlen);
            if (r != SASL_OK) break;

            clientout = base64;
        }

        /* Send Authorization and/or Upgrade request to server */
        prot_puts(s->out, "OPTIONS * HTTP/1.1\r\n");
        prot_printf(s->out, "Host: %s\r\n", s->hostname);
        prot_printf(s->out, "User-Agent: %s\r\n", buf_cstring(&serverinfo));
        if (scheme) {
            prot_printf(s->out, "Authorization: %s %s\r\n",
                        scheme->name, clientout ? clientout : "");
            prot_printf(s->out, "Authorize-As: %s\r\n",
                        userid ? userid : "anonymous");
        }
        else {
            prot_printf(s->out, "Upgrade: %s\r\n", TLS_VERSION);
            if (need_tls) {
                prot_puts(s->out, "Connection: Upgrade\r\n");
                need_tls = 0;
            }
            prot_puts(s->out, "Authorization: \r\n");
        }
        prot_puts(s->out, "\r\n");
        prot_flush(s->out);

        serverin = clientout = NULL;
        serverinlen = clientoutlen = 0;

        /* Read response(s) from backend until final response or error */
        do {
            resp_body.flags = BODY_DISCARD;
            r = http_read_response(s, METH_OPTIONS, &code, NULL,
                                   &hdrs, &resp_body, &errstr);
            if (r) {
                if (status) *status = errstr;
                break;
            }

            if (code == 101) {  /* Switching Protocols */
                if (tls_done) {
                    r = HTTP_BAD_GATEWAY;
                    if (status) *status = "TLS already active";
                    break;
                }
                else if (backend_starttls(s, NULL, NULL, NULL)) {
                    r = HTTP_SERVER_ERROR;
                    if (status) *status = "Unable to start TLS";
                    break;
                }
                else tls_done = 1;
            }
        } while (code < 200);

        switch (code) {
        default: /* Failure */
            if (!r) {
                r = HTTP_BAD_GATEWAY;
                if (status) {
                    buf_reset(&buf);
                    buf_printf(&buf,
                               "Unexpected status code from backend: %u", code);
                    *status = buf_cstring(&buf);
                }
            }
            break;

        case 426: /* Upgrade Required */
            if (tls_done) {
                r = HTTP_BAD_GATEWAY;
                if (status) *status = "TLS already active";
            }
            else need_tls = 1;
            break;

        case 200: /* OK */
            if (scheme->recv_success &&
                (serverin = scheme->recv_success(hdrs))) {
                /* Process success data */
                serverinlen = strlen(serverin);
                goto challenge;
            }
            break;

        case 401: /* Unauthorized */
            if (auth_done) {
                r = SASL_BADAUTH;
                break;
            }

            if (!serverin) {
                int i = 0;

                hdr = spool_getheader(hdrs, "WWW-Authenticate");

                if (!scheme) {
                    unsigned avail_auth_schemes = 0;
                    const char *mech = NULL;
                    size_t len;

                    /* Compare authentication schemes offered in
                     * WWW-Authenticate header(s) to what we support */
                    buf_reset(&buf);
                    for (i = 0; hdr && hdr[i]; i++) {
                        len = strcspn(hdr[i], " ");

                        for (scheme = auth_schemes; scheme->name; scheme++) {
                            if (!strncmp(scheme->name, hdr[i], len) &&
                                !((scheme->flags & AUTH_NEED_PERSIST) &&
                                  (resp_body.flags & BODY_CLOSE))) {
                                /* Tag the scheme as available */
                                avail_auth_schemes |= (1 << scheme->idx);

                                /* Add SASL-based schemes to SASL mech list */
                                if (scheme->saslmech) {
                                    if (buf_len(&buf)) buf_putc(&buf, ' ');
                                    buf_appendcstr(&buf, scheme->saslmech);
                                }
                                break;
                            }
                        }
                    }

                    /* If we have a mech_conf, use it */
                    if (mech_conf && buf_len(&buf)) {
                        char *conf = xstrdup(mech_conf);
                        char *newmechlist =
                            intersect_mechlists(conf,
                                                (char *) buf_cstring(&buf));

                        if (newmechlist) {
                            buf_setcstr(&buf, newmechlist);
                            free(newmechlist);
                        }
                        else {
                            syslog(LOG_DEBUG, "%s did not offer %s",
                                   s->hostname, mech_conf);
                            buf_reset(&buf);
                        }
                        free(conf);
                    }

#ifdef SASL_HTTP_REQUEST
                    /* Set HTTP request as specified above (REQUIRED) */
                    httpreq.non_persist = (resp_body.flags & BODY_CLOSE);
                    sasl_setprop(s->saslconn, SASL_HTTP_REQUEST, &httpreq);
#endif

                    /* Try to start SASL exchange using available mechs */
                    r = sasl_client_start(s->saslconn, buf_cstring(&buf),
                                          NULL,         /* no prompts */
                                          NULL, NULL,   /* no initial resp */
                                          &mech);

                    if (mech) {
                        /* Find auth scheme associated with chosen SASL mech */
                        for (scheme = auth_schemes; scheme->name; scheme++) {
                            if (scheme->saslmech &&
                                !strcmp(scheme->saslmech, mech)) break;
                        }
                    }
                    else {
                        /* No matching SASL mechs - try Basic */
                        scheme = &auth_schemes[AUTH_BASIC];
                        if (!(avail_auth_schemes & (1 << scheme->idx))) {
                            need_tls = !tls_done;
                            break;  /* case 401 */
                        }
                    }

                    /* Find the associated WWW-Authenticate header */
                    for (i = 0; hdr && hdr[i]; i++) {
                        len = strcspn(hdr[i], " ");
                        if (!strncmp(scheme->name, hdr[i], len)) break;
                    }
                }

                /* Get server challenge, if any */
                if (hdr) {
                    const char *p = strchr(hdr[i], ' ');
                    serverin = p ? ++p : "";
                    serverinlen = strlen(serverin);
                }
            }

        challenge:
            if (serverin) {
                /* Perform the next step in the auth exchange */

                if (scheme->idx == AUTH_BASIC) {
                    /* Don't care about "realm" in server challenge */
                    const char *authid =
                        callback_getdata(s->saslconn, cb, SASL_CB_AUTHNAME);
                    pass = callback_getdata(s->saslconn, cb, SASL_CB_PASS);

                    buf_reset(&buf);
                    buf_printf(&buf, "%s:%s", authid, pass);
                    clientout = buf_cstring(&buf);
                    clientoutlen = buf_len(&buf);
                    auth_done = 1;
                }
                else {
                    /* Base64 decode any server challenge, if necessary */
                    if (serverin && (scheme->flags & AUTH_BASE64)) {
                        r = sasl_decode64(serverin, serverinlen,
                                          base64, BASE64_BUF_SIZE, &serverinlen);
                        if (r != SASL_OK) break;  /* case 401 */

                        serverin = base64;
                    }

                    /* SASL mech (Digest, Negotiate, NTLM) */
                    r = sasl_client_step(s->saslconn, serverin, serverinlen,
                                         NULL,          /* no prompts */
                                         &clientout, &clientoutlen);
                    if (r == SASL_OK) auth_done = 1;
                }
            }
            break;  /* case 401 */
        }

    } while (need_tls || clientout);

  done:
    if (hdrs) spool_free_hdrcache(hdrs);

    if (r && status && !*status) *status = sasl_errstring(r, NULL, NULL);

    return r;
}
Ejemplo n.º 4
0
/*
 * file in the message structure 'm' from 'pin', assuming a dot-stuffed
 * stream a la lmtp.
 *
 * returns 0 on success, imap error code on failure
 */
static int savemsg(struct clientdata *cd,
		   const struct lmtp_func *func,
		   message_data_t *m)
{
    FILE *f;
    struct stat sbuf;
    const char **body;
    int r;
    int nrcpts = m->rcpt_num;
    time_t now = time(NULL);
    static unsigned msgid_count = 0;
    char datestr[RFC822_DATETIME_MAX+1], tls_info[250] = "";
    const char *skipheaders[] = {
	"Return-Path",  /* need to remove (we add our own) */
	NULL
    };
    char *addbody, *fold[5], *p;
    int addlen, nfold, i;

    /* Copy to spool file */
    f = func->spoolfile(m);
    if (!f) {
	prot_printf(cd->pout, 
		    "451 4.3.%c cannot create temporary file: %s\r\n",
		    (
#ifdef EDQUOT
			errno == EDQUOT ||
#endif
			errno == ENOSPC) ? '1' : '2',
		    error_message(errno));
	return IMAP_IOERROR;
    }

    prot_printf(cd->pout, "354 go ahead\r\n");

    if (m->return_path && func->addretpath) { /* add the return path */
	char *rpath = m->return_path;
	const char *hostname = 0;

	clean_retpath(rpath);
	/* Append our hostname if there's no domain in address */
	hostname = NULL;
	if (!strchr(rpath, '@') && strlen(rpath) > 0) {
	    hostname = config_servername;
	}

	addlen = 2 + strlen(rpath) + (hostname ? 1 + strlen(hostname) : 0);
	addbody = xmalloc(addlen + 1);
	sprintf(addbody, "<%s%s%s>",
		rpath, hostname ? "@" : "", hostname ? hostname : "");
	fprintf(f, "Return-Path: %s\r\n", addbody);
	spool_cache_header(xstrdup("Return-Path"), addbody, m->hdrcache);
    }

    /* add a received header */
    time_to_rfc822(now, datestr, sizeof(datestr));
    addlen = 8 + strlen(cd->lhlo_param) + strlen(cd->clienthost);
    if (m->authuser) addlen += 28 + strlen(m->authuser) + 5; /* +5 for ssf */
    addlen += 25 + strlen(config_servername) + strlen(cyrus_version());
#ifdef HAVE_SSL
    if (cd->tls_conn) {
	addlen += 3 + tls_get_info(cd->tls_conn, tls_info, sizeof(tls_info));
    }
#endif
    addlen += 2 + strlen(datestr);
    p = addbody = xmalloc(addlen + 1);

    nfold = 0;
    p += sprintf(p, "from %s (%s)", cd->lhlo_param, cd->clienthost);
    fold[nfold++] = p;
    if (m->authuser) {
	const void *ssfp;
	sasl_ssf_t ssf;
	sasl_getprop(cd->conn, SASL_SSF, &ssfp);
	ssf = *((sasl_ssf_t *) ssfp);
	p += sprintf(p, " (authenticated user=%s bits=%d)", m->authuser, ssf);
	fold[nfold++] = p;
    }

    /* We are always atleast "with LMTPA" -- no unauth delivery */
    p += sprintf(p, " by %s", config_servername);
    if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
	p += sprintf(p, " (Cyrus %s)", cyrus_version());
    }
    p += sprintf(p, " with LMTP%s%s",
		 cd->starttls_done ? "S" : "",
		 cd->authenticated != NOAUTH ? "A" : "");

    if (*tls_info) {
	fold[nfold++] = p;
	p += sprintf(p, " (%s)", tls_info);
    }

    strcat(p++, ";");
    fold[nfold++] = p;
    p += sprintf(p, " %s", datestr);
 
    fprintf(f, "Received: ");
    for (i = 0, p = addbody; i < nfold; p = fold[i], i++) {
	fprintf(f, "%.*s\r\n\t", (int) (fold[i] - p), p);
    }
    fprintf(f, "%s\r\n", p);
    spool_cache_header(xstrdup("Received"), addbody, m->hdrcache);

    /* add any requested headers */
    if (func->addheaders) {
	struct addheader *h;
	for (h = func->addheaders; h && h->name; h++) {
	    fprintf(f, "%s: %s\r\n", h->name, h->body);
	    spool_cache_header(xstrdup(h->name), xstrdup(h->body), m->hdrcache);
	}
    }

    /* fill the cache */
    r = spool_fill_hdrcache(cd->pin, f, m->hdrcache, skipheaders);

    /* now, using our header cache, fill in the data that we want */

    /* first check resent-message-id */
    if ((body = msg_getheader(m, "resent-message-id")) && body[0][0]) {
	m->id = xstrdup(body[0]);
    } else if ((body = msg_getheader(m, "message-id")) && body[0][0]) {
	m->id = xstrdup(body[0]);
    } else if (body) {
	r = IMAP_MESSAGE_BADHEADER;  /* empty message-id */
    } else {
	/* no message-id, create one */
	pid_t p = getpid();

	m->id = xmalloc(40 + strlen(config_servername));
	sprintf(m->id, "<cmu-lmtpd-%d-%d-%u@%s>", p, (int) now,
		msgid_count++, config_servername);
	fprintf(f, "Message-ID: %s\r\n", m->id);
	spool_cache_header(xstrdup("Message-ID"), xstrdup(m->id), m->hdrcache);
    }

    /* get date */
    if (!(body = spool_getheader(m->hdrcache, "date"))) {
	/* no date, create one */
	addbody = xstrdup(datestr);
	m->date = xstrdup(datestr);
	fprintf(f, "Date: %s\r\n", addbody);
	spool_cache_header(xstrdup("Date"), addbody, m->hdrcache);
    }
    else {
	m->date = xstrdup(body[0]);
    }

    if (!m->return_path &&
	(body = msg_getheader(m, "return-path"))) {
	/* let's grab return_path */
	m->return_path = xstrdup(body[0]);
	clean822space(m->return_path);
	clean_retpath(m->return_path);
    }

    r |= spool_copy_msg(cd->pin, f);
    if (r) {
	fclose(f);
	if (func->removespool) {
	    /* remove the spool'd message */
	    func->removespool(m);
	}
	while (nrcpts--) {
	    send_lmtp_error(cd->pout, r);
	}
	return r;
    }

    fflush(f);
    if (ferror(f)) {
	while (nrcpts--) {
	    prot_printf(cd->pout,
	       "451 4.3.%c cannot copy message to temporary file: %s\r\n",
		   (
#ifdef EDQUOT
		    errno == EDQUOT ||
#endif
		    errno == ENOSPC) ? '1' : '2',
		   error_message(errno));
	}
	fclose(f);
	if (func->removespool) func->removespool(m);
	return IMAP_IOERROR;
    }

    if (fstat(fileno(f), &sbuf) == -1) {
	while (nrcpts--) {
	    prot_printf(cd->pout,
			"451 4.3.2 cannot stat message temporary file: %s\r\n",
			error_message(errno));
	}
	fclose(f);
	if (func->removespool) func->removespool(m);
	return IMAP_IOERROR;
    }
    m->size = sbuf.st_size;
    m->f = f;
    m->data = prot_new(fileno(f), 0);

    return 0;
}
Ejemplo n.º 5
0
HIDDEN int ws_start_channel(struct transaction_t *txn, const char *protocol,
                            int (*data_cb)(struct buf *inbuf, struct buf *outbuf,
                                           struct buf *logbuf, void **rock))
{
    int r;
    const char **hdr, *accept = NULL;
    wslay_event_context_ptr ev;
    struct ws_context *ctx;
    struct wslay_event_callbacks callbacks = {
        recv_cb,
        send_cb,
        NULL,
        NULL,
        NULL,
        NULL,
        on_msg_recv_cb
    };

    /* Check for supported WebSocket version */
    hdr = spool_getheader(txn->req_hdrs, "Sec-WebSocket-Version");
    if (!hdr) {
        txn->error.desc = "Missing WebSocket version";
        return HTTP_BAD_REQUEST;
    }
    else if (hdr[1]) {
        txn->error.desc = "Multiple WebSocket versions";
        return HTTP_BAD_REQUEST;
    }
    else if (strcmp(hdr[0], WS_VERSION)) {
        txn->error.desc = "Unsupported WebSocket version";
        return HTTP_UPGRADE;
    }

    if (protocol) {
        /* Check for supported WebSocket subprotocol */
        int i, found = 0;

        hdr = spool_getheader(txn->req_hdrs, "Sec-WebSocket-Protocol");
        if (!hdr) {
            txn->error.desc = "Missing WebSocket protocol";
            return HTTP_BAD_REQUEST;
        }

        for (i = 0; !found && hdr[i]; i++) {
            tok_t tok = TOK_INITIALIZER(hdr[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
            char *token;

            while ((token = tok_next(&tok))) {
                if (!strcmp(token, protocol)) {
                    found = 1;
                    break;
                }
            }
            tok_fini(&tok);
        }
        if (!found) {
            txn->error.desc = "Unsupported WebSocket protocol";
            return HTTP_BAD_REQUEST;
        }
    }

    if (txn->flags.ver == VER_1_1) {
        unsigned char sha1buf[SHA1_DIGEST_LENGTH];

        /* Check for WebSocket client key */
        hdr = spool_getheader(txn->req_hdrs, "Sec-WebSocket-Key");
        if (!hdr) {
            txn->error.desc = "Missing WebSocket client key";
            return HTTP_BAD_REQUEST;
        }
        else if (hdr[1]) {
            txn->error.desc = "Multiple WebSocket client keys";
            return HTTP_BAD_REQUEST;
        }
        else if (strlen(hdr[0]) != WS_CKEY_LEN) {
            txn->error.desc = "Invalid WebSocket client key";
            return HTTP_BAD_REQUEST;
        }

        /* Create WebSocket accept key */
        buf_setcstr(&txn->buf, hdr[0]);
        buf_appendcstr(&txn->buf, WS_GUID);
        xsha1((u_char *) buf_base(&txn->buf), buf_len(&txn->buf), sha1buf);

        buf_ensure(&txn->buf, WS_AKEY_LEN+1);
        accept = buf_base(&txn->buf);

        r = sasl_encode64((char *) sha1buf, SHA1_DIGEST_LENGTH,
                          (char *) accept, WS_AKEY_LEN+1, NULL);
        if (r != SASL_OK) syslog(LOG_WARNING, "sasl_encode64: %d", r);
    }

    /* Create server context */
    r = wslay_event_context_server_init(&ev, &callbacks, txn);
    if (r) {
        syslog(LOG_WARNING,
               "wslay_event_context_init: %s", wslay_strerror(r));
        return HTTP_SERVER_ERROR;
    }

    /* Create channel context */
    ctx = xzmalloc(sizeof(struct ws_context));
    ctx->event = ev;
    ctx->accept = accept;
    ctx->protocol = protocol;
    ctx->data_cb = data_cb;
    txn->ws_ctx = ctx;

    /* Check for supported WebSocket extensions */
    parse_extensions(txn);

    /* Prepare log buffer */

    /* Add client data */
    buf_printf(&ctx->log, "%s", txn->conn->clienthost);
    if (httpd_userid) buf_printf(&ctx->log, " as \"%s\"", httpd_userid);
    if ((hdr = spool_getheader(txn->req_hdrs, "User-Agent"))) {
        buf_printf(&ctx->log, " with \"%s\"", hdr[0]);
        if ((hdr = spool_getheader(txn->req_hdrs, "X-Client")))
            buf_printf(&ctx->log, " by \"%s\"", hdr[0]);
        else if ((hdr = spool_getheader(txn->req_hdrs, "X-Requested-With")))
            buf_printf(&ctx->log, " by \"%s\"", hdr[0]);
    }

    /* Add request-line */
    buf_printf(&ctx->log, "; \"WebSocket/%s via %s\"",
               protocol ? protocol : "echo" , txn->req_line.ver);
    ctx->log_tail = buf_len(&ctx->log);

    /* Tell client that WebSocket negotiation has succeeded */
    if (txn->conn->sess_ctx) {
        /* Treat WS data as chunked response */
        txn->flags.te = TE_CHUNKED;

        response_header(HTTP_OK, txn);

        /* Force the response to the client immediately */
        prot_flush(httpd_out);
    }
    else response_header(HTTP_SWITCH_PROT, txn);

    /* Set connection as non-blocking */
    prot_NONBLOCK(txn->conn->pin);

    /* Don't do telemetry logging in prot layer */
    prot_setlog(txn->conn->pin, PROT_NO_FD);
    prot_setlog(txn->conn->pout, PROT_NO_FD);

    return 0;
}
Ejemplo n.º 6
0
/* Parse Sec-WebSocket-Extensions header(s) for interesting extensions */
static void parse_extensions(struct transaction_t *txn)
{
    struct ws_context *ctx = (struct ws_context *) txn->ws_ctx;
    const char **ext_hdr =
        spool_getheader(txn->req_hdrs, "Sec-WebSocket-Extensions");
    int i;

    /* Look for interesting extensions.  Unknown == ignore */
    for (i = 0; ext_hdr && ext_hdr[i]; i++) {
        tok_t ext = TOK_INITIALIZER(ext_hdr[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
        char *token;

        while ((token = tok_next(&ext))) {
            struct ws_extension *extp = extensions;
            tok_t param;

            tok_initm(&param, token, ";", TOK_TRIMLEFT|TOK_TRIMRIGHT);
            token = tok_next(&param);

            /* Locate a matching extension */
            while (extp->name && strcmp(token, extp->name)) extp++;

            /* Check if client wants per-message compression */
            if (extp->flag == EXT_PMCE_DEFLATE) {
                unsigned client_max_wbits = MAX_WBITS;

                ctx->pmce.deflate.max_wbits = MAX_WBITS;

                /* Process parameters */
                while ((token = tok_next(&param))) {
                    char *value = strchr(token, '=');

                    if (value) *value++ = '\0';

                    if (!strcmp(token, "server_no_context_takeover")) {
                        ctx->pmce.deflate.no_context = 1;
                    }
                    else if (!strcmp(token, "client_no_context_takeover")) {
                        /* Don't HAVE to do anything here */
                    }
                    else if (!strcmp(token, "server_max_window_bits")) {
                        if (value) {
                            if (*value == '"') value++;
                            ctx->pmce.deflate.max_wbits = atoi(value);
                        }
                        else ctx->pmce.deflate.max_wbits = 0;  /* force error */
                    }
                    else if (!strcmp(token, "client_max_window_bits")) {
                        if (value) {
                            if (*value == '"') value++;
                            client_max_wbits = atoi(value);
                        }
                    }
                }
#ifdef HAVE_ZLIB
                /* Reconfigure compression context for raw deflate */
                if (txn->zstrm) deflateEnd(txn->zstrm);
                else txn->zstrm = xmalloc(sizeof(z_stream));

                if (deflateInit2(txn->zstrm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
                                 -ctx->pmce.deflate.max_wbits,
                                 MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
                    free(txn->zstrm);
                    txn->zstrm = NULL;
                }
                if (txn->zstrm) {
                    /* Configure decompression context for raw deflate */
                    ctx->pmce.deflate.zstrm = xmalloc(sizeof(z_stream));
                    if (inflateInit2(ctx->pmce.deflate.zstrm,
                                     -client_max_wbits) != Z_OK) {
                        free(ctx->pmce.deflate.zstrm);
                        ctx->pmce.deflate.zstrm = NULL;
                    }
                }
#endif /* HAVE_ZLIB */
                if (ctx->pmce.deflate.zstrm) {
                    /* Compression has been enabled */
                    wslay_event_config_set_allowed_rsv_bits(ctx->event,
                                                            WSLAY_RSV1_BIT);
                    ctx->ext = extp->flag;
                }
            }

            tok_fini(&param);
        }

        tok_fini(&ext);
    }
}