ngx_int_t
ngx_http_set_misc_set_decode_hex(ngx_http_request_t *r,
                                 ngx_str_t *res, ngx_http_variable_value_t *v)
{

    u_char      *p;
    ngx_int_t   n;
    ngx_int_t   i;

    if (v->len % 2 != 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "set_decode_hex: invalid value");
        return NGX_ERROR;
    }
    p = v->data;
    ndk_palloc_re(res->data, r->pool, v->len/2);
    for (i = 0; i < v->len/2; i++) {
        n = ngx_hextoi(p, 2);
        if (n == NGX_ERROR || n > 255) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "set_decode_hex: invalid value");
            return NGX_ERROR;
        }
        p += 2;
        res->data[i] = (u_char) n;
    }
    res->len = v->len/2;
    return NGX_OK;
}
static ngx_int_t ngx_let_toi(ngx_str_t* s) 
{
	return (s->len > 2 && s->data[0] == '0' && s->data[1] == 'x')

		? ngx_hextoi(s->data + 2, s->len - 2)

		: ngx_atoi(s->data, s->len);
}
/*
 *	添加cache文件到file_cache中
 *	参数:	name是完整文件名称
 */
static ngx_int_t
ngx_http_file_cache_add_file(ngx_tree_ctx_t *ctx, ngx_str_t *name)
{
    u_char                 *p;
    ngx_int_t               n;
    ngx_uint_t              i;
    ngx_http_cache_t        c;
    ngx_http_file_cache_t  *cache;

	//	因为name是文件的完整路径名称,不应该仅是文件名的长度
    if (name->len < 2 * NGX_HTTP_CACHE_KEY_LEN) {
        return NGX_ERROR;
    }

	//	检查cache文件大小,cache文件中存放的内容至少包含file_cache_header_t结构大小
    if (ctx->size < (off_t) sizeof(ngx_http_file_cache_header_t)) {
        ngx_log_error(NGX_LOG_CRIT, ctx->log, 0,
                      "cache file \"%s\" is too small", name->data);
        return NGX_ERROR;
    }

    ngx_memzero(&c, sizeof(ngx_http_cache_t));
    cache = ctx->data;

    c.length = ctx->size;						//	???
    c.fs_size = (ctx->fs_size + cache->bsize - 1) / cache->bsize;		//	???

	//	将HEX格式的文件名转换为整数形式存放于 c.key 数组中
    p = &name->data[name->len - 2 * NGX_HTTP_CACHE_KEY_LEN];

    for (i = 0; i < NGX_HTTP_CACHE_KEY_LEN; i++) {
        n = ngx_hextoi(p, 2);

        if (n == NGX_ERROR) {
            return NGX_ERROR;
        }

        p += 2;

        c.key[i] = (u_char) n;
    }

    return ngx_http_file_cache_add(cache, &c);
}
ngx_int_t 
ngx_http_qrcode_set_bg_color(ngx_http_request_t *r, ngx_int_t *color, ngx_array_t *compiled_args)
{
	ngx_str_t *arg;
	ngx_int_t i;

	arg = compiled_args->elts;

	if (arg[0].len != 6) {
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 
				"bg_color format error, it should be like FF00FF. error value %V", arg);	
		return NGX_ERROR;
	}

	for (i = 0; i < 3; i++) {
		*(color + i) = ngx_hextoi(arg[0].data + i*2, 2);
	}

	return NGX_OK;
}
static ngx_int_t
ngx_http_file_cache_add_file(ngx_tree_ctx_t *ctx, ngx_str_t *name)
{
    u_char                 *p;
    ngx_int_t               n;
    ngx_uint_t              i;
    ngx_http_cache_t        c;
    ngx_http_file_cache_t  *cache;

    if (name->len < 2 * NGX_HTTP_CACHE_KEY_LEN) {
        return NGX_ERROR;
    }

    if (ctx->size < (off_t) sizeof(ngx_http_file_cache_header_t)) {
        ngx_log_error(NGX_LOG_CRIT, ctx->log, 0,
                      "cache file \"%s\" is too small", name->data);
        return NGX_ERROR;
    }

    ngx_memzero(&c, sizeof(ngx_http_cache_t));
    cache = ctx->data;

    c.length = ctx->size;
    c.fs_size = (ctx->fs_size + cache->bsize - 1) / cache->bsize;

    p = &name->data[name->len - 2 * NGX_HTTP_CACHE_KEY_LEN];

    for (i = 0; i < NGX_HTTP_CACHE_KEY_LEN; i++) {
        n = ngx_hextoi(p, 2);

        if (n == NGX_ERROR) {
            return NGX_ERROR;
        }

        p += 2;

        c.key[i] = (u_char) n;
    }

    return ngx_http_file_cache_add(cache, &c);
}
ngx_int_t
ngx_http_set_misc_set_decode_hex(ngx_http_request_t *r, ngx_str_t *res,
    ngx_http_variable_value_t *v)
{

    u_char      *p;
    ngx_int_t    n;
    ngx_uint_t   i;
    size_t       len;

    if (v->len % 2 != 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "set_decode_hex: invalid value");
        return NGX_ERROR;
    }

    p = v->data;
    len = v->len >> 1;

    res->data = ngx_palloc(r->pool, len);
    if (res->data == NULL) {
        return NGX_ERROR;
    }

    for (i = 0; i < len; i++) {
        n = ngx_hextoi(p, 2);
        if (n == NGX_ERROR || n > 255) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "set_decode_hex: invalid value");
            return NGX_ERROR;
        }

        p += 2;
        res->data[i] = (u_char) n;
    }

    res->len = len;
    return NGX_OK;
}
Beispiel #7
0
static ngx_int_t
ngx_http_file_cache_add_file(ngx_tree_ctx_t *ctx, ngx_str_t *name)
{
    u_char                        *p;
    ngx_fd_t                       fd;
    ngx_int_t                      n;
    ngx_uint_t                     i;
    ngx_file_info_t                fi;
    ngx_http_cache_t               c;
    ngx_http_file_cache_t         *cache;
    ngx_http_file_cache_header_t   h;

    if (name->len < 2 * NGX_HTTP_CACHE_KEY_LEN) {
        return NGX_ERROR;
    }

    ngx_memzero(&c, sizeof(ngx_http_cache_t));

    fd = ngx_open_file(name->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);

    if (fd == NGX_INVALID_FILE) {
        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
                      ngx_open_file_n " \"%s\" failed", name->data);
        return NGX_ERROR;
    }

    c.file.fd = fd;
    c.file.name = *name;
    c.file.log = ctx->log;

    n = ngx_read_file(&c.file, (u_char *) &h,
                      sizeof(ngx_http_file_cache_header_t), 0);
    if (n == NGX_ERROR) {
        return NGX_ERROR;
    }

    if ((size_t) n < sizeof(ngx_http_file_cache_header_t)) {
        ngx_log_error(NGX_LOG_CRIT, ctx->log, 0,
                      "cache file \"%s\" is too small", name->data);
        return NGX_ERROR;
    }

    if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
                      ngx_fd_info_n " \"%s\" failed", name->data);

    } else {
        c.uniq = ngx_file_uniq(&fi);
        c.valid_sec = h.valid_sec;
        c.valid_msec = h.valid_msec;
        c.body_start = h.body_start;
        c.length = ngx_file_size(&fi);
    }

    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", name->data);
    }

    if (c.body_start == 0) {
        return NGX_ERROR;
    }

    p = &name->data[name->len - 2 * NGX_HTTP_CACHE_KEY_LEN];

    for (i = 0; i < NGX_HTTP_CACHE_KEY_LEN; i++) {
        n = ngx_hextoi(p, 2);

        if (n == NGX_ERROR) {
            return NGX_ERROR;
        }

        p += 2;

        c.key[i] = (u_char) n;
    }

    cache = ctx->data;

    return ngx_http_file_cache_add(cache, &c);
}
static ngx_int_t
ngx_http_secure_link_old_variable(ngx_http_request_t *r,
    ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
    uintptr_t data)
{
    u_char      *p, *start, *end, *last;
    size_t       len;
    ngx_int_t    n;
    ngx_uint_t   i;
    ngx_md5_t    md5;
    u_char       hash[16];

    p = &r->unparsed_uri.data[1];
    last = r->unparsed_uri.data + r->unparsed_uri.len;

    // 获取/p/5e814704a28d9bc1914ff19fa0c4a00a/link/xx请求中5e814704a28d9bc1914ff19fa0c4a00a字符串
    while (p < last) {
        if (*p++ == '/') {
            start = p;
            goto md5_start;
        }
    }

    goto not_found;

md5_start:
    // 获取/p/5e814704a28d9bc1914ff19fa0c4a00a/link/xx请求中/link/xx字符串做为新的uri
    while (p < last) {
        if (*p++ == '/') {
            end = p - 1;
            goto url_start;
        }
    }

    goto not_found;

url_start:

    len = last - p;

    //5e814704a28d9bc1914ff19fa0c4a00a必须满足32字节,因为MD5运算结果是32字节
    if (end - start != 32 || len == 0) {
        goto not_found;
    }

    //计算secure_link_secret配置的字符串的MD5校验值
    ngx_md5_init(&md5);
    ngx_md5_update(&md5, p, len);
    ngx_md5_update(&md5, conf->secret.data, conf->secret.len);
    ngx_md5_final(hash, &md5);

    for (i = 0; i < 16; i++) {
        n = ngx_hextoi(&start[2 * i], 2);
        if (n == NGX_ERROR || n != hash[i]) {
            goto not_found;
        }
    }

    v->len = len;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = p;

    return NGX_OK;

not_found:

    v->not_found = 1;

    return NGX_OK;
}
Beispiel #9
0
static ngx_int_t ngx_http_shmtest_get(ngx_http_request_t *r){
	ngx_int_t rc = NGX_HTTP_OK;
	ngx_str_t key = ngx_null_string;
	ngx_str_t value = ngx_null_string;
	int32_t ikey = 0;
	uint8_t value_type = VT_BINARY;
	uint32_t exptime = 0;
	uint32_t user_flags = 0;
	
	if(ngx_http_arg(r, (u_char*)"key", 3, &key)!=NGX_OK){
		NLOG_ERROR("get arg 'key' failed!");
		return NGX_HTTP_BAD_REQUEST;
	}
	if(key.len > 2 && key.data[0] == '0' &&	key.data[1] == 'x'){
		key.data += 2;
		key.len -= 2;
		ikey = ngx_hextoi(key.data, key.len);
		ngx_str_set_int32(&key, &ikey);
		NLOG_DEBUG("use int key ikey=%d", ikey);
	} 

	shmtest_main_conf_t* smcf;
	smcf = ngx_http_get_module_main_conf(r, ngx_http_shmtest_module);
	if(smcf == NULL){
		NLOG_ERROR("get module ngx_http_shmtest_module's main conf failed!");
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	ngx_shm_zone_t* zone = smcf->shmap;
	u_char* rsp = ngx_pcalloc(r->connection->pool, 256);
	int rsp_len = 0;

	rc = ngx_shmap_get(zone, &key, &value, &value_type,
				&exptime, &user_flags);

	if(ikey != 0){
		if(rc == 0){
			rsp_len = ngx_sprintf(rsp, "get(%d)={value=%V,exptime=%d,user_flags=%d}!\n",
							ikey,&value,exptime,user_flags)-rsp;
		}else{
			rsp_len = ngx_sprintf(rsp, "get(%d) failed!\n", ikey)-rsp;
		}
	}else{
		if(rc == 0){
			rsp_len = ngx_sprintf(rsp, "get(%V)={value=%V,exptime=%d,user_flags=%d}!\n",
							&key,&value,exptime,user_flags)-rsp;
		}else{
			rsp_len = ngx_sprintf(rsp, "get(%V) failed!\n", &key)-rsp;
		}
	}

	ngx_chain_t* chain = ngx_http_shmtest_resp(r, (char*)rsp, rsp_len);
	if(chain != NULL){
	    r->headers_out.content_length_n = rsp_len;
	}else{
		r->headers_out.content_length_n = 0;
	}

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        
    }else{
    	rc = ngx_http_output_filter(r, chain);
    }

	return rc;
}
Beispiel #10
0
static ngx_int_t ngx_http_shmtest_add_or_update(ngx_http_request_t *r,int func){
	ngx_int_t rc = NGX_HTTP_OK;
	ngx_str_t key = ngx_null_string;
	int32_t ikey = 0;
	ngx_str_t value = ngx_null_string;
 	char* szFunc = funcs[func];
	
	if(ngx_http_arg(r, (u_char*)"key", 3, &key)!=NGX_OK){
		NLOG_ERROR("get arg 'key' failed!");
		return NGX_HTTP_BAD_REQUEST;
	}

	if(ngx_http_arg(r, (u_char*)"value", 5, &value)!=NGX_OK){
		NLOG_ERROR("get arg 'value' failed!");
		return NGX_HTTP_BAD_REQUEST;
	}

	//如果key开始为0x 表示使用数字的KEY.
	if(key.len > 2 && key.data[0] == '0' &&	key.data[1] == 'x'){
		key.data += 2;
		key.len -= 2;
		ikey = ngx_hextoi(key.data, key.len);
		ngx_str_set_int32(&key, &ikey);
		NLOG_DEBUG("use int key ikey=%d", ikey);
	}
	
	uint64_t exptime = 0;
	ngx_str_t sexptime = ngx_null_string;
	if(ngx_http_arg(r, (u_char*)"exptime", 7, &sexptime)==NGX_OK){
		exptime = ngx_parse_time(&sexptime, 1);
	}

	if(ikey != 0){
		NLOG_DEBUG("%s(key=%d,value=%V,exptime=%d)", szFunc,ikey,&value,exptime);
	}else{
		NLOG_DEBUG("%s(key=%V,value=%V,exptime=%d)", szFunc,&key,&value,exptime);
	}
	shmtest_main_conf_t* smcf;
	smcf = ngx_http_get_module_main_conf(r, ngx_http_shmtest_module);
	if(smcf == NULL){
		NLOG_ERROR("get module ngx_http_shmtest_module's main conf failed!");
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	ngx_shm_zone_t* zone = smcf->shmap;
	
	int ret = 0;
	switch(func){
	case FUNC_ADD:
		ret = ngx_shmap_add(zone, &key,&value,VT_STRING,exptime,0);
	break;
	case FUNC_SET:
		ret = ngx_shmap_set(zone, &key,&value,VT_STRING,exptime,0);
	break;
	case FUNC_REPLACE:
		ret = ngx_shmap_replace(zone, &key,&value,VT_STRING,exptime,0);
	break;
	default:
		NLOG_ERROR("un process type [%d]", func);
		return NGX_HTTP_BAD_REQUEST;
	}

	char* rsp = ngx_pcalloc(r->connection->pool, 256);
	int rsp_len = 0;
	if(ret == 0){
		rsp_len = sprintf(rsp, "%s success!\n", szFunc);
	}else{
		rsp_len = sprintf(rsp, "%s failed!\n", szFunc);
	}

	ngx_chain_t* chain = ngx_http_shmtest_resp(r, rsp, rsp_len);
	if(chain != NULL){
	    r->headers_out.content_length_n = rsp_len;
	}else{
		r->headers_out.content_length_n = 0;
	}

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        
    }else{
    	rc = ngx_http_output_filter(r, chain);
    }

	return rc;
}
static ngx_int_t
ngx_http_secure_link_variable(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data)
{
    u_char                        *p, *start, *end, *last;
    size_t                         len;
    ngx_int_t                      n;
    ngx_uint_t                     i;
    ngx_md5_t                      md5;
    ngx_http_secure_link_conf_t  *conf;
    u_char                         hash[16];

    conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_link_module);

    if (conf->secret.len == 0) {
        goto not_found;
    }

    p = &r->unparsed_uri.data[1];
    last = r->unparsed_uri.data + r->unparsed_uri.len;

    while (p < last) {
        if (*p++ == '/') {
            start = p;
            goto md5_start;
        }
    }

    goto not_found;

md5_start:

    while (p < last) {
        if (*p++ == '/') {
            end = p - 1;
            goto url_start;
        }
    }

    goto not_found;

url_start:

    len = last - p;

    if (end - start != 32 || len == 0) {
        goto not_found;
    }

    ngx_md5_init(&md5);
    ngx_md5_update(&md5, p, len);
    ngx_md5_update(&md5, conf->secret.data, conf->secret.len);
    ngx_md5_final(hash, &md5);

    for (i = 0; i < 16; i++) {
        n = ngx_hextoi(&start[2 * i], 2);
        if (n == NGX_ERROR || n != hash[i]) {
            goto not_found;
        }
    }

    v->len = len;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = p;

    return NGX_OK;

not_found:

    v->not_found = 1;

    return NGX_OK;
}
static ngx_int_t
ngx_http_auth_digest_verify_hash(ngx_http_request_t *r, ngx_http_auth_digest_cred_t *fields, u_char *hashed_pw)
{
  u_char      *p;
  ngx_str_t    http_method;
  ngx_str_t    HA1, HA2, ha2_key;
  ngx_str_t    digest, digest_key;
  ngx_md5_t    md5;
  u_char       hash[16];

  //  the hashing scheme:
  //    digest: MD5(MD5(username:realm:password):nonce:nc:cnonce:qop:MD5(method:uri))
  //                ^- HA1                                           ^- HA2
  //    verify: fields->response == MD5($hashed_pw:nonce:nc:cnonce:qop:MD5(method:uri))

  // ha1 was precalculated and saved to the passwd file: md5(username:realm:password)
  HA1.len = 33;
  HA1.data = ngx_pcalloc(r->pool, HA1.len);
  p = ngx_cpymem(HA1.data, hashed_pw, 32);
  
  // calculate ha2: md5(method:uri)
  http_method.len = r->method_name.len+1;
  http_method.data = ngx_pcalloc(r->pool, http_method.len);
  if (http_method.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
  p = ngx_cpymem(http_method.data, r->method_name.data, r->method_end - r->method_name.data+1);
  
  ha2_key.len = http_method.len + r->uri.len + 1;
  ha2_key.data = ngx_pcalloc(r->pool, ha2_key.len);
  if (ha2_key.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
  p = ngx_cpymem(ha2_key.data, http_method.data, http_method.len-1); *p++ = ':';
  p = ngx_cpymem(p, r->uri.data, r->uri.len);

  HA2.len = 33;
  HA2.data = ngx_pcalloc(r->pool, HA2.len);
  ngx_md5_init(&md5);
  ngx_md5_update(&md5, ha2_key.data, ha2_key.len-1);
  ngx_md5_final(hash, &md5);  
  ngx_hex_dump(HA2.data, hash, 16);
  
  // calculate digest: md5(ha1:nonce:nc:cnonce:qop:ha2)
  digest_key.len = HA1.len-1 + fields->nonce.len-1 + fields->nc.len-1 + fields->cnonce.len-1 + fields->qop.len-1 + HA2.len-1 + 5 + 1;
  digest_key.data = ngx_pcalloc(r->pool, digest_key.len);
  if (digest_key.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
  
  p = ngx_cpymem(digest_key.data, HA1.data, HA1.len-1); *p++ = ':';  
  p = ngx_cpymem(p, fields->nonce.data, fields->nonce.len-1); *p++ = ':';
  p = ngx_cpymem(p, fields->nc.data, fields->nc.len-1); *p++ = ':';
  p = ngx_cpymem(p, fields->cnonce.data, fields->cnonce.len-1); *p++ = ':';
  p = ngx_cpymem(p, fields->qop.data, fields->qop.len-1); *p++ = ':';
  p = ngx_cpymem(p, HA2.data, HA2.len-1);  

  digest.len = 33;
  digest.data = ngx_pcalloc(r->pool, 33);
  if (digest.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
  ngx_md5_init(&md5);
  ngx_md5_update(&md5, digest_key.data, digest_key.len-1);
  ngx_md5_final(hash, &md5);  
  ngx_hex_dump(digest.data, hash, 16);

  // compare the hash of the full digest string to the response field of the auth header
  // and bail out if they don't match
  if (ngx_strcmp(digest.data, fields->response.data) != 0) return NGX_DECLINED;
  
  ngx_http_auth_digest_nonce_t     nonce;
  ngx_uint_t                       key;
  ngx_http_auth_digest_node_t     *found;
  ngx_slab_pool_t                 *shpool;
  ngx_http_auth_digest_loc_conf_t *alcf;
  ngx_table_elt_t                 *info_header;
  ngx_str_t                        hkey, hval;
  
  shpool = (ngx_slab_pool_t *)ngx_http_auth_digest_shm_zone->shm.addr;
  alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_digest_module);
  nonce.rnd = ngx_hextoi(fields->nonce.data, 8);
  nonce.t = ngx_hextoi(&fields->nonce.data[8], 8);
  key = ngx_crc32_short((u_char *) &nonce.rnd, sizeof nonce.rnd) ^
        ngx_crc32_short((u_char *) &nonce.t, sizeof(nonce.t));

  int nc = ngx_atoi(fields->nc.data, fields->nc.len-1);
  if (nc<0 || nc>=alcf->replays){ 
    fields->stale = 1;    
    return NGX_DECLINED; 
  }

  // make sure nonce and nc are both valid
  ngx_shmtx_lock(&shpool->mutex);    
  found = (ngx_http_auth_digest_node_t *)ngx_http_auth_digest_rbtree_find(key, ngx_http_auth_digest_rbtree->root, ngx_http_auth_digest_rbtree->sentinel);
  if (found!=NULL && ngx_bitvector_test(found->nc, nc)){    
    if (ngx_bitvector_test(found->nc, 0)){
      // if this is the first use of this nonce, switch the expiration time from the timeout
      // param to now+expires. using the 0th element of the nc vector to flag this...
      ngx_bitvector_set(found->nc, 0);
      found->expires = ngx_time() + alcf->expires;
    }
    
    // mark this nc as ‘used’ to prevent replays 
    ngx_bitvector_set(found->nc, nc);

    
    // todo: if the bitvector is now ‘full’, could preemptively expire the node from the rbtree
    // ngx_rbtree_delete(ngx_http_auth_digest_rbtree, found);
    // ngx_slab_free_locked(shpool, found);


    
    ngx_shmtx_unlock(&shpool->mutex);
    
    // recalculate the digest with a modified HA2 value (for rspauth) and emit the
    // Authentication-Info header    
    ngx_memset(ha2_key.data, 0, ha2_key.len);
    p = ngx_sprintf(ha2_key.data, ":%s", r->uri.data);

    ngx_memset(HA2.data, 0, HA2.len);
    ngx_md5_init(&md5);
    ngx_md5_update(&md5, ha2_key.data, r->uri.len);
    ngx_md5_final(hash, &md5);  
    ngx_hex_dump(HA2.data, hash, 16);

    ngx_memset(digest_key.data, 0, digest_key.len);
    p = ngx_cpymem(digest_key.data, HA1.data, HA1.len-1); *p++ = ':';  
    p = ngx_cpymem(p, fields->nonce.data, fields->nonce.len-1); *p++ = ':';
    p = ngx_cpymem(p, fields->nc.data, fields->nc.len-1); *p++ = ':';
    p = ngx_cpymem(p, fields->cnonce.data, fields->cnonce.len-1); *p++ = ':';
    p = ngx_cpymem(p, fields->qop.data, fields->qop.len-1); *p++ = ':';
    p = ngx_cpymem(p, HA2.data, HA2.len-1);  

    ngx_md5_init(&md5);
    ngx_md5_update(&md5, digest_key.data, digest_key.len-1);
    ngx_md5_final(hash, &md5);  
    ngx_hex_dump(digest.data, hash, 16);
    
    ngx_str_set(&hkey, "Authentication-Info");
    hval.len = sizeof("qop=\"auth\", rspauth=\"\", cnonce=\"\", nc=") + fields->cnonce.len + fields->nc.len + digest.len;
    hval.data = ngx_pcalloc(r->pool, hval.len);
    if (hval.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
    p = ngx_sprintf(hval.data, "qop=\"auth\", rspauth=\"%s\", cnonce=\"%s\", nc=%s", digest.data, fields->cnonce.data, fields->nc.data);
    
    info_header = ngx_list_push(&r->headers_out.headers);
    if (info_header == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;    
    info_header->key = hkey;
    info_header->value = hval;
    info_header->hash = 1;
    return NGX_OK;
  }else{
    // nonce is invalid/expired or client reused an nc value. suspicious...
    ngx_shmtx_unlock(&shpool->mutex);
    return NGX_DECLINED;
  }      
}