[svn-commits] edguy3: branch edguy3/sip-identity-trunk r256147 - in /team/edguy3/sip-identi...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Sat Apr 3 07:49:56 CDT 2010


Author: edguy3
Date: Sat Apr  3 07:49:53 2010
New Revision: 256147

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=256147
Log:
apply latest sip-identity-patch to latest trunk.  Add automerge properties 

Modified:
    team/edguy3/sip-identity-trunk/   (props changed)
    team/edguy3/sip-identity-trunk/channels/chan_sip.c
    team/edguy3/sip-identity-trunk/channels/sip/include/sip.h
    team/edguy3/sip-identity-trunk/configs/extensions.conf.sample
    team/edguy3/sip-identity-trunk/configs/sip.conf.sample

Propchange: team/edguy3/sip-identity-trunk/
------------------------------------------------------------------------------
    automerge = *

Propchange: team/edguy3/sip-identity-trunk/
------------------------------------------------------------------------------
    automerge-email = edguy at emcsw.com

Modified: team/edguy3/sip-identity-trunk/channels/chan_sip.c
URL: http://svnview.digium.com/svn/asterisk/team/edguy3/sip-identity-trunk/channels/chan_sip.c?view=diff&rev=256147&r1=256146&r2=256147
==============================================================================
--- team/edguy3/sip-identity-trunk/channels/chan_sip.c (original)
+++ team/edguy3/sip-identity-trunk/channels/chan_sip.c Sat Apr  3 07:49:53 2010
@@ -163,6 +163,7 @@
 
 /*** MODULEINFO
         <depend>chan_local</depend>
+		<depend>curl</depend>
  ***/
 
 /*!  \page sip_session_timers SIP Session Timers in Asterisk Chan_sip
@@ -776,6 +777,26 @@
 static int global_qualify_gap;          /*!< Time between our group of peer pokes */
 static int global_qualify_peers;        /*!< Number of peers to poke at a given time */
 
+/*! \brief RFC 4474 Identity related static variables */
+static struct identity_struct {
+ enum identity_mode mode;/*!< flag whether to sign the invite, validate or sign and validate*/
+ char private_url_prefix[IDENTITY_PRIV_URL_PREFIX]; /*!< private url prefix is stored here used to fetch
+								  the user private key from http or https server*/
+ char private_url_suffix[IDENTITY_PRIV_URL_SUFFIX]; /*!< private url suffix is stored here used to fetch
+								  the user private key from http or https server
+								  which can be .pem or .cer*/
+ char public_url_prefix[IDENTITY_PUB_URL_PREFIX];   /*!< public url prefix is stored here used to fetch the
+								  user public key from http or https server used */
+ char public_url_suffix[IDENTITY_PUB_URL_SUFFIX];   /*!< public url suffix is stored here used to fetch the
+								  user public key from http or https server used */
+ char private_path[AST_CONFIG_MAX_PATH];            /*!< location where private keys are stored */
+ char public_path[AST_CONFIG_MAX_PATH];		/*!< location where public keys are stored */
+ char cache_path[AST_CONFIG_MAX_PATH];	        /*!< location where keys and certs are cached */
+}  identity_general;						/*! identity settings for overall system */
+
+AST_THREADSTORAGE(id_query_buf);			/*! identity curl query buffer */
+AST_THREADSTORAGE(id_result_buf);			/*! identity curl result buffer */
+
 static enum st_mode global_st_mode;           /*!< Mode of operation for Session-Timers           */
 static enum st_refresher global_st_refresher; /*!< Session-Timer refresher                        */
 static int global_min_se;                     /*!< Lowest threshold for session refresh interval  */
@@ -1068,7 +1089,7 @@
 static void add_noncodec_to_sdp(const struct sip_pvt *p, int format,
 				struct ast_str **m_buf, struct ast_str **a_buf,
 				int debug);
-static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp, int add_audio, int add_t38);
+static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp, int add_audio, int add_t38, int first_message);
 static void do_setnat(struct sip_pvt *p);
 static void stop_media_flows(struct sip_pvt *p);
 
@@ -1524,6 +1545,927 @@
 	}
 	return res;
 }
+
+
+/*
+ *  Begin RFC4474 Identity definitions configuration values & functions
+ *
+ */
+
+/*! \brief  helper function for rfc 4474  Return pointer to string with current SSL error
+ * \return Returns pointer to string with current SSL error
+ */
+static char* ssl_err(void)
+{
+	static char buf[1024];
+	ERR_error_string(ERR_get_error(), buf);
+	return buf;
+}
+
+/*! \brief  helper function for rfc 4474  Return pointer to string with result explanation
+ * \param  code the numeric (enum) code to be interpreted.
+ * \return Returns pointer to string with current SSL error
+ */
+static const char* ident_status(enum identity_result code)
+{
+	const char* rv = "SIP Identity status unknown";
+	switch(code) {
+		case IDENTITY_RES_NONE:
+			rv="SIP identity no result";
+			break;
+		case IDENTITY_RES_SIGN_OK:
+			rv="SIP identity sign ok";
+			break;
+		case IDENTITY_RES_SIGN_DISABLED:
+			rv="SIP identity sign disabled";
+			break;
+		case IDENTITY_RES_SIGN_BROKEN_KEY:
+			rv="SIP identity sign broken key";
+			break;
+		case IDENTITY_RES_SIGN_NO_PRIVATE_KEY:
+			rv="SIP identity sign no private key found";
+			break;
+		case IDENTITY_RES_VAL_OK:
+			rv="SIP identity validation ok";
+			break;
+		case IDENTITY_RES_VAL_DISABLED:
+			rv="SIP identity validation disabled";
+			break;
+		case IDENTITY_RES_VAL_BROKEN_CERT:
+			rv="SIP identity validation broken certificate";
+			break;
+		case IDENTITY_RES_VAL_NO_CERT:
+			rv="SIP identity validation no public certificate found";
+	}
+	return (rv);
+};
+
+
+/*! \brief  helper function for rfc 4474  generate cache file name
+ * \return Returns pointer to filename.  must be freed
+ */
+static char* id_cache_filename(const char*url,struct identity_struct *identity)
+{
+	char *p, *p2;
+	size_t len;
+	char buf[AST_CONFIG_MAX_PATH];
+	strncpy(buf,identity->cache_path,sizeof(buf));
+	len = strlen(buf);
+	p2 = buf + len;
+
+	ast_uri_encode(url,p2,sizeof(buf)-len,1);
+
+	/* substitute specials */
+	while ((p=strchr(buf,'<'))) {
+		*p='_';
+	}
+	while ((p=strchr(buf,'>'))) {
+		*p='_';
+	}
+	return ast_strdup(buf);
+}
+
+
+/*! \brief  helper function for rfc 4474  Fill buffer with cached content.
+ * \param  url	url of source file
+ * \param  data output buffer
+ * \param  datasize size of output buffer
+ * \return Returns true if found. out will contain file contents
+ */
+static int id_check_cache(const char*url,char *data,size_t datasize,struct identity_struct *identity)
+{
+	FILE* stream;
+	int rv = FALSE;
+	char *cached_file=id_cache_filename(url,identity);
+	if (sipdebug) {
+		ast_debug(2,"** Identity key searching in id_check_cache %s => %s\n",url,cached_file);
+	}
+
+	/* does file exist? */
+	if ((stream = fopen (cached_file, "r")) != (FILE *)0) {
+		ast_debug(2,"id_check_cache: opened %s / %d \n",cached_file,datasize);
+		fread(data, datasize, 1, stream);
+		if ( ferror(stream) ) {
+			ast_log(LOG_ERROR,"id_check_cache: private key not loaded from %s\n",cached_file);
+		}
+		else {
+			ast_debug(2,"id_check_cache: private key loaded from %s\n",cached_file);
+		}
+		fclose(stream);
+		rv = TRUE;
+	}
+	ast_debug(2,"id_get_private_key: done %d\n",rv);
+	ast_free(cached_file);
+	return rv;
+}
+
+/*! \brief  helper function for rfc 4474  store buffer to cache.
+ * \param  url	url of source file
+ * \param  data output buffer
+ * \param  datasize size of output buffer
+ * \return Returns void.
+ */
+static void id_store_cache(const char*url,char *data,size_t datasize,struct identity_struct *identity)
+{
+	FILE* stream;
+	char *cached_file=id_cache_filename(url,identity);
+	if (sipdebug) {
+		ast_debug(2,"** Identity info (datasize %d) to be stored in cache %s => %s\n",datasize,url,cached_file);
+	}
+
+	/* does file exist */
+	if ((stream = fopen (cached_file, "w")) != (FILE *)0) {
+		fwrite(data, datasize, 1, stream);
+		if (ferror(stream)) {
+			ast_log(LOG_ERROR,"id_store_cache: info NOT written to %s\n",cached_file);
+		}
+		else {
+			ast_log(LOG_NOTICE,"id_store_cache: info written to %s\n",cached_file);
+		}
+		fclose(stream);
+	}
+	else {
+		ast_log(LOG_ERROR,"** Identity id_store_cache cannot write to cache %s => %s\n",url,cached_file);
+	}
+	ast_free(cached_file);
+}
+
+
+/*! \brief helper function for rfc 4474  fetches public cert or private key for the user from server
+ * uses plain old socket to initiate http session to fetch keys from server
+ *
+ * \param type IDENTITY_PRIVATE for private key, IDENTITY_PUBLIC for public key
+ * \param user user name for whom the public or private key is fetched
+ * \param out  where the fetched key is placed
+ * \param port port no to connect on the server
+ * \param url  server name and path is fetched from url
+ * \return Returns integer result 1 is success, any other value means failure
+ */
+static int id_fetch_key_from_server(enum identity_key_type type,const char *user, char *out, size_t outsize,int port,const char *url_in,struct identity_struct *identity)
+{
+	int sockfd, portno, n,torecv=0,rcvd=0;
+	struct sockaddr_in serv_addr;
+	struct hostent *server;
+	struct ast_hostent ae;
+	char *p,*p1,*p2,tempbuf[20],buffer[IDENTITY_KEYBUF_SIZE],serveradd[PATH_MAX];
+	char url2[PATH_MAX];
+
+	/* ensure buffers are \0 terminated */
+	serveradd[0]='\0';
+	buffer[0]='\0';
+	out[0]='\0';
+
+	/* condition url */
+	p1=strstr(url_in,"<");
+	if (p1) {
+		p2=strstr(url_in,">");
+		if((p2-p1)>PATH_MAX) {
+			ast_log(LOG_ERROR, "Identity Error:  URL too long %s\n",url2);
+			return -1;
+		}
+		memcpy(url2,p1+1,p2-p1-1);
+		ast_verbose("** Identity  url:%s\n",url_in);
+		ast_verbose("** Identity url2:%s\n",url2);
+	}
+	else {
+		memcpy(url2,url_in,strlen(url_in)+1);
+	}
+
+	/* check local cache. */
+	if (id_check_cache(url2,out,outsize,identity)) {  
+		if (sipdebug) {
+			ast_debug(2,"** Identity key found in cache %s\n%s\n",url2,out);
+		}
+		/* out is filled with contents */
+		return TRUE;
+	}
+
+	/*  get url contents using CURL or a hand crafted method. ( curl gives us https, certs, etc. ) */
+	if (ast_custom_function_find("CURL")) {
+		/* use curl */
+		struct ast_str *query, *buffer;
+
+		/* allocate working buffers on thread */
+		if (!(query = ast_str_thread_get(&id_query_buf, 16))) {
+			ast_log(LOG_ERROR, "Identity Error:  Memory allocation failure.\n");
+		return -1;
+	}
+
+		if (!(buffer = ast_str_thread_get(&id_result_buf, 16))) {
+			ast_log(LOG_ERROR, "Identity Error:  Memory allocation failure.\n");
+			return -1;
+				}
+
+		ast_str_set(&query, 0, "${CURL(%s)}", url2);
+		ast_str_substitute_variables(&buffer, 0, NULL, ast_str_buffer(query));
+
+		/* copy results */
+		rcvd=ast_str_size(buffer);
+		if( rcvd > outsize) {
+			ast_log(LOG_ERROR, "Identity Error: http result from %s too large: %d.\n",url2, rcvd);
+			return -1;
+			}
+		if (rcvd>0) {
+			memcpy(out, ast_str_buffer(buffer),rcvd);
+		}
+		if (sipdebug) {
+			ast_log(LOG_NOTICE,
+					"** Identity url [%s], results [\n%s]\n",
+					url2, out);
+		}
+
+	}
+	else {
+		/* use the el cheapo method to keep embedded guys happy */
+		/* check port no */
+		if (port < 1 || port > IDENTITY_MAXPORT) {
+			ast_log(LOG_ERROR, "Identity Error invalid port %d\n", port);
+			return -1;
+		}
+
+		/* format the http header buffer to fetch the keys from server */
+		p = strstr(url2, "://");
+		if (p!=NULL) {
+			p1=strstr(p+4,"/");
+			if (p1!=NULL) {
+				memcpy(serveradd,p+3,p1-(p+3) );
+				serveradd[p1-(p+3)]='\0';
+					strcpy(buffer, "GET ");
+				strcat(buffer, p1);
+				if (sipdebug) {
+					ast_log(LOG_NOTICE,
+							"** Identity prv srv [%s], path [%s]\n",
+							serveradd, buffer);
+				}
+			}
+		}
+		if (p==NULL || p1==NULL) {
+			ast_log(
+					LOG_ERROR,
+					"** Identity error parsing private URL [%s], path [%s]\n",
+					serveradd, buffer);
+			return -1;
+		}
+
+	/* format the buffer to send to server */
+	strcat(buffer," HTTP/1.1\r\n");
+	strcat(buffer,"Host: ");
+	strcat(buffer,serveradd);
+	strcat(buffer,"\r\n");
+	strcat(buffer,"Accept: */*\r\n");
+	strcat(buffer,"\r\n");
+
+	server = ast_gethostbyname(serveradd,&ae);
+	if (server == NULL) {
+			ast_log(LOG_ERROR,
+					"** Identity ERROR cannot resolve hostname=%s\n", serveradd);
+		return -1;
+	}
+
+	/* populate connect to  server addr */
+	portno = port;
+	if (sipdebug) {
+			ast_log(
+					LOG_NOTICE,
+					"** Identity id_fetch_key_from_server(); srv: %s, buff: \n%s\nport: %d\n",
+					serveradd, buffer, portno);
+	}
+
+	sockfd = socket(AF_INET, SOCK_STREAM, 0);
+	if (sockfd < 0) {
+		ast_log(LOG_ERROR,"** Identity Error opening socket\n");
+		return -1;
+	}
+	memset((char *) &serv_addr,'\0', sizeof(serv_addr));
+	serv_addr.sin_family = AF_INET;
+		memcpy((char *) &serv_addr.sin_addr.s_addr, (char *) server->h_addr,
+				server->h_length);
+	serv_addr.sin_port = htons(portno);
+	if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0) {
+		ast_log(LOG_ERROR,"** Identity ERROR connecting \n");
+		return -1;
+	}
+	/* send to server the buffer */
+	n = write(sockfd,buffer,strlen(buffer));
+	if (n < 0) {
+		close(sockfd);
+		ast_log(LOG_ERROR,"** Identity ERROR writing to socket\n");
+		return -1;
+	}
+	/* read from server */
+	n = read(sockfd,buffer,sizeof(buffer));
+	if (n <= 0) {
+		close(sockfd);
+		ast_log(LOG_ERROR,"** Identity ERROR reading from socket\n");
+		return -1;
+	}
+	/* make sure content is null terminated */
+	buffer[(n<sizeof(buffer))?n:sizeof(buffer)-1]='\0';
+	/* write the read buffer from \r\n\r\n to out */
+	p=buffer;
+	p1=strstr(buffer,"\r\n\r\n");
+	if (p1!=NULL) {
+		memcpy(out,p1+4,strlen(buffer)-(p1+4-p));
+		out[strlen(buffer)-(p1+4-p)]='\0';
+	}
+	/* validate the response from host*/
+	p=strstr(buffer,"200 OK");
+	if (p==NULL) {
+		close(sockfd);
+			ast_log(LOG_ERROR,
+					"** Identity ERROR key not found on server [%s] %s\n",
+					serveradd, url2);
+		ast_log(LOG_ERROR,"********************\n");
+		ast_log(LOG_ERROR,"buffer %s\n",buffer);
+		ast_log(LOG_ERROR,"********************\n");
+		return -1;
+	}
+	/* get content length*/
+	p=strstr(buffer,"Content-Length:");
+	if (p!=NULL) {
+		char* p2 = p+sizeof("Content-Length:");
+		p1=strstr(p2,"\r\n");
+		if (p1!=NULL) {
+			memcpy(tempbuf,p2,p1-p2);
+			tempbuf[p1-p2]='\0';
+			torecv=strtol(tempbuf,NULL, 10);
+		}
+	}
+	/* check the length for validity */
+	if (torecv<=0) {
+			ast_log(LOG_ERROR, "** Identity ERROR nothing to receive [%d]\n",
+					torecv);
+		close(sockfd);
+		return -1;
+	}
+		/* find out what is received so far */
+	rcvd=strlen(out);
+	while (rcvd<torecv) {
+		buffer[0]='\0';
+		n = read(sockfd,buffer,sizeof(buffer));
+		if (n<0) {
+				ast_log(LOG_ERROR,
+						"** Identity ERROR socket read failed [%d]\n", torecv);
+			close(sockfd);
+			return -1;
+		}
+		memcpy(out+rcvd,buffer,n);
+		rcvd+=n;
+	}
+	out[rcvd]='\0';
+	close(sockfd);
+	}
+
+	if(rcvd>32) {
+		id_store_cache(url2,out,rcvd,identity);
+	}
+	return 1; /* success */
+}
+
+
+/*! \brief helper function for rfc 4474 gets private key from server and returns in EVP_PKEY format
+ *
+ * \param user  user name for whom the private key is fetched
+ * \return Returns private key in EVP_PKEY or NULL if failed
+ */
+
+static EVP_PKEY* id_get_private_key(const char *user,struct identity_struct *identity)
+{
+	BIO *pem_bio;
+	EVP_PKEY *pkey;
+	char data[IDENTITY_KEYBUF_SIZE];
+	char url[AST_CONFIG_MAX_PATH];
+	char filename[AST_CONFIG_MAX_PATH];
+	FILE *stream;
+
+	data[0]='\0';
+
+	/* get private key from file system */
+	strcpy(filename,identity->private_path);
+	strcat(filename,user);
+
+	if (sipdebug) {
+		ast_debug(2,"id_get_private_key: private key potentially to be loaded from %s\n",filename);
+	}
+	if ((stream = fopen (filename, "r")) != (FILE *)0) {
+		fread(data, sizeof(data), 1, stream);
+		if (ferror(stream)){
+			ast_log(LOG_ERROR,"id_get_private_key: private key loading error from %s\n",filename);
+		}
+		else {
+			if (sipdebug) {
+				ast_debug(2,"id_get_private_key: private key was loaded from %s\n",filename);
+			}
+		}
+		fclose(stream);
+	}
+	else {
+		strcpy(url,identity->private_url_prefix);
+		strcat(url,user);
+		strcat(url,identity->private_url_suffix);
+		/* fetch the private key from server */
+		if (id_fetch_key_from_server(IDENTITY_PRIVATE,user,data,sizeof(data),IDENTITY_SERVER_PORT,url,identity)!=1) {
+			ast_log(LOG_ERROR,"** id_get_private_key: get private key failed %s\n",user);
+			return NULL;
+		}
+	}
+	/* return private key in EVP_PKEY format */
+	if (NULL == (pem_bio = BIO_new_mem_buf(data, strlen(data)))) {
+	    ast_log(LOG_ERROR,"** id_get_private_key: unable to initialize BIO\n");
+	    return NULL;
+	}
+	if (NULL == (pkey = PEM_read_bio_PrivateKey(pem_bio, NULL, 0, NULL))) {
+	    ast_log(LOG_ERROR,"** id_get_private_key: could not decode private key from  %s\n",  ssl_err());
+	    BIO_free(pem_bio);
+	    return NULL;
+	}
+	BIO_free(pem_bio);
+	return pkey;
+}
+
+/*! \brief helper function for rfc 4474 gets public certificate from server and returns in X509 format
+ *
+ * \param user  user name for whom the public key is fetched
+ * \param idinfohdr  identity info header from sip request which contains the URL
+ * \return Returns private key in X509 or null
+ */
+static X509* id_get_public_cert(const char *user,const char *idinfohdr,struct identity_struct *identity)
+{
+	BIO *pem_bio;
+	X509 *cert;
+	char data[IDENTITY_KEYBUF_SIZE];
+	char filename[AST_CONFIG_MAX_PATH];
+	FILE *stream;
+
+	data[0]='\0';
+
+	/* get cert from file system */
+	strcpy(filename,identity->public_path);
+	strcat(filename,user);
+
+	if ((stream = fopen (filename, "r")) != (FILE *)0) {
+		fread(data, sizeof(data), 1, stream);
+		if (ferror(stream)) {
+			ast_log(LOG_ERROR,"id_get_public_cert: private key loading error from %s\n",filename);
+		}
+		else {
+			if (sipdebug) {
+				ast_debug(2,"id_get_public_cert: private key loaded from %s\n",filename);
+			}
+		}
+		fclose(stream);
+	}
+	else {
+		/* fetch the public cert from server */
+		if (id_fetch_key_from_server(IDENTITY_PUBLIC,user,data,sizeof(data),IDENTITY_SERVER_PORT,idinfohdr,identity)!=1) {
+			ast_log(LOG_ERROR,"** id_get_public_cert get public key failed %s\n",user);
+			return NULL;
+		}
+	}
+	if (sipdebug) {
+		ast_debug(2,"** id_get_public_cert: user: %s data: %s \n", user, data);
+		ast_verbose("** id_get_public_cert: user: %s data: %s \n", user, data);
+	}
+	/* return public key in EVP_PKEY format */
+	if (NULL == (pem_bio = BIO_new_mem_buf(data, strlen(data)))) {
+		ast_log(LOG_ERROR,"** id_get_public_cert: unable to initialize BIO\n");
+		return NULL;
+	}
+	if (NULL == (cert = PEM_read_bio_X509(pem_bio, NULL, 0, 0))) {
+		ast_log(LOG_ERROR,"** id_get_public_cert: could not decode public key from  %s\n", ssl_err());
+		BIO_free(pem_bio);
+		return NULL;
+	}
+	BIO_free(pem_bio);
+	return cert;
+}
+
+
+/*! \brief helper function for rfc 4474 gets public key from server and returns in EVP_PKEY format
+ *
+ * \param user  user name for whom the public key is fetched
+ * \param idinfohdr  identity info header from sip request which contains the URL
+ * \return Returns private key in EVP_PKEY or null
+ */
+static EVP_PKEY* id_get_public_key(X509* cert)
+{
+	EVP_PKEY *pkey;
+	if (NULL == (pkey = X509_get_pubkey(cert))) {
+		ast_log(LOG_ERROR,"** id_get_public_key: could not get public key from  %s\n", ssl_err());
+	}
+	return pkey;
+}
+
+/*! \brief helper function for rfc 4474 does SHA1 hashing
+ *
+ * \param buf  pointer to data to be SHA1 hashed
+ * \param retbuf pointer to hashed data
+ * \return Returns pointer to hashed data
+ */
+static char* id_hashstr(const char *buf,char *retbuf)
+{
+	SHA_CTX sha;
+	SHA1_Init(&sha);
+	SHA1_Update(&sha, buf, strlen(buf));
+	SHA1_Final((unsigned char*)retbuf, &sha);
+	return retbuf;
+}
+/*! \brief main function for rfc 4474 does signing
+ *
+ * \param tosign  pointer to data to be signed (RSA SHA1)
+ * \param res pointer to signed data
+ * \param user pointer to user for whom the key is fetched and data is signed using the private key of user
+ * \return Returns 1 for success any other value failure
+ */
+static int id_sign(const char *tosign,char *res,const char *user,struct identity_struct *identity)
+{
+	EVP_PKEY *pkey;
+	RSA *rsa;
+	unsigned char result[IDENTITY_SIGNBUF_SIZE];
+	unsigned int resultlen = sizeof(result);
+	char id[IDENTITY_SIGNBUF_SIZE];
+	unsigned int id_len = sizeof(id);
+
+	unsigned char sha1res[20];
+	/* hash the digest string */
+	if (id_hashstr(tosign,(char *) sha1res) == NULL) {
+                ast_log(LOG_ERROR,"** id_sign():  hash failed\n");
+		return FALSE;
+	}
+	/* get private key for user */
+	pkey = id_get_private_key(user,identity);
+	if (pkey == NULL) {
+		ast_log(LOG_ERROR,"** id_sign(): get private key failed\n");
+		return FALSE;
+	}
+
+	if (NULL == (rsa = EVP_PKEY_get1_RSA(pkey))) {
+		ast_log(LOG_ERROR,"** id_sign(): Unable to get RSA Key: %s\n", ssl_err());
+		EVP_PKEY_free(pkey);
+		return FALSE;
+	}
+	EVP_PKEY_free(pkey);
+	if (resultlen < RSA_size(rsa)) {
+		ast_log(LOG_ERROR,"** id_sign(): RSA Result too big: %d !>= %d\n", resultlen, RSA_size(rsa));
+		return FALSE;
+	}
+	/* do RSA sign */
+	if (RSA_sign(NID_sha1, sha1res, sizeof(sha1res), result, &resultlen, rsa) != 1) {
+		ast_log(LOG_ERROR,"** id_sign(): Error computing RSA results: %s\n", ssl_err());
+		RSA_free(rsa);
+		return FALSE;
+	}
+	RSA_free(rsa);
+	/* do base64 encoding */
+	ast_base64encode_full(id, result, resultlen, id_len, 0);
+	memcpy(res,id,strlen(id)+1);
+	return TRUE;
+}
+
+/*! \brief callback function for rfc 4474. Interprets errors
+ *
+ * \param ok  status
+ * \param ctx pointer to x509 store cotext
+ * \return Returns 1 for success, 0 value failure
+ */
+static int verify_id_cb(int ok, X509_STORE_CTX *ctx)
+{
+	char buf[512];		/* cert subject buffer. size ok */
+
+	if (sipdebug && ctx->current_cert) {
+		X509_NAME_oneline( X509_get_subject_name(ctx->current_cert),buf, sizeof(buf));
+		ast_debug(2,"** verify_id_cb(%d): %s\n",ok,buf);
+	}
+	if (!ok) {
+		ast_log(LOG_ERROR,"** verify_id_cb: error %d at %d depth lookup:%s\n",ctx->error,
+			ctx->error_depth, X509_verify_cert_error_string(ctx->error));
+		/* allow self signing as OK */
+		if (ctx->error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) {
+			ok=1;
+		}
+	}
+	return(ok);
+}
+
+
+
+
+/*! \brief main function for rfc 4474 does verifying the signature
+ *
+ * \param digeststr  pointer to digest data
+ * \param identityhdr pointer to identity header data from sip request
+ * \param user pointer to user for whom the key is fetched and data is verified using the public key of user
+ * \param identity_info pointer to identity-info header data from sip request
+ * \return Returns 1 for success any other value failure
+ */
+static int id_verify(const char *digeststr,const char *identityhdr,const char *user,const char *identity_info,struct identity_struct *identity)
+{
+	unsigned char sig[IDENTITY_SIGNBUF_SIZE];
+	EVP_PKEY *pkey;
+	RSA *rsa=NULL;
+	X509 *cert=NULL;
+	int retlen;
+	unsigned char sha1res[20];
+ 	X509_NAME *nm=NULL;
+	X509_STORE *ctx=NULL;
+	X509_STORE_CTX *csc=NULL;
+	int cn_match;
+	int lastpos;
+	int rv=FALSE;
+
+	if (sipdebug) {
+		ast_log(LOG_NOTICE,"** id_verify: digeststr: %s iden: %s user: %s idinfo: %s\n", digeststr, identityhdr, user, identity_info);
+	}
+
+	/* hash the digest string */
+	if (id_hashstr(digeststr, (char *)sha1res) == NULL) {
+		goto cleanup;
+	}
+
+	/* base 64 decode of the identity header */
+  	retlen=ast_base64decode(sig, identityhdr, sizeof(sig));
+	if (retlen <=0 ) {
+		ast_log(LOG_ERROR,"** id_verify(): Cannot decode Identity header\n");
+		goto cleanup;
+	}
+	/* get public key for the user */
+	cert = id_get_public_cert(user,identity_info,identity);
+	if (cert == NULL) {
+		ast_log(LOG_ERROR,"** id_verify: Unable to get public cert: %s\n", ssl_err());
+		goto cleanup;
+	}
+	pkey  = id_get_public_key(cert);
+	if (pkey == NULL) {
+		ast_log(LOG_ERROR,"** id_verify: Unable to get public Key: %s\n", ssl_err());
+		goto cleanup;
+	}
+	if (NULL == (rsa = EVP_PKEY_get1_RSA(pkey))) {
+		ast_log(LOG_ERROR,"** id_verify: Unable to get RSA Key: %s\n", ssl_err());
+		goto cleanup;
+	}
+	EVP_PKEY_free(pkey);
+
+	nm = X509_get_subject_name(cert);
+	if (sipdebug) {
+		char subject[256];
+		X509_NAME_oneline(nm, subject, sizeof(subject));
+		ast_log(LOG_NOTICE, "** id_verify Certificate Subject = %s\n", subject);
+        	ast_log(LOG_NOTICE, "** id_verify: size-sha1res: %d retlen: %d \n", sizeof(sha1res), retlen);
+	}
+
+	/*  do rsa verify */
+	if (RSA_verify(NID_sha1, sha1res, sizeof(sha1res),(unsigned char *) sig, retlen, rsa) != 1) {
+		ast_log(LOG_ERROR,"** id_verify: Verify failed: %s\n", ssl_err());
+		goto cleanup;
+	}
+	RSA_free(rsa);
+	rsa=NULL;
+
+	/* loop through the CNs */
+	cn_match = FALSE;
+	lastpos = -1 ;
+	do {
+		int name_len;
+		ASN1_STRING *tmp;
+		char *cname2;
+
+	        lastpos = X509_NAME_get_index_by_NID(nm, NID_commonName, lastpos);
+        	if (lastpos < 0) {
+                	break;
+		}
+		tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(nm,lastpos));
+		if (!tmp) {
+			ast_log(LOG_ERROR, "** id_verify: Error Processing certificate CN\n");
+			break;
+		}
+		name_len = ASN1_STRING_length(tmp);
+		cname2 = (char*)ast_malloc(name_len+1);		/* remember ASN1 does not count \0 */
+		memcpy(cname2, ASN1_STRING_data(tmp), ASN1_STRING_length(tmp));
+		cname2[name_len]='\0';
+
+		cn_match = (0==strncmp(user,cname2,name_len+1));
+		if (sipdebug) {
+			ast_log(LOG_NOTICE,"** id_verify: CNAME = %s match:%s\n",cname2,cn_match?"yes":"no");
+		}
+		ast_free(cname2);
+        }
+	while ( !cn_match );
+
+	if (!cn_match) {
+		ast_log(LOG_ERROR, "** id_verify: FAIL: user %s not found in cert CN\n",user);
+		goto cleanup;
+	}
+
+	if (sipdebug) {
+		ast_log(LOG_NOTICE, "** id_verify: Callerid(num) matches Common Name. ok\n");
+	}
+
+	/* now verify chain */
+	ctx=X509_STORE_new();
+	X509_STORE_set_verify_cb_func(ctx,verify_id_cb);
+
+	if (X509_STORE_set_default_paths(ctx) != 1) {
+		ast_log(LOG_ERROR, " id_verify: X509_STORE_set_default_paths failed\n");
+		goto cleanup;
+	}
+	csc = X509_STORE_CTX_new();
+	if (!csc) {
+		ast_log(LOG_ERROR, " id_verify: X509_STORE_CTX_new failed\n");
+		goto cleanup;
+	}
+	if (!X509_STORE_CTX_init(csc,ctx,cert,NULL)) {
+		ast_log(LOG_ERROR, " id_verify: X509_STORE_CTX_init failed\n");
+		goto cleanup;
+	}
+	if (!X509_verify_cert(csc)) {
+		ast_log(LOG_ERROR, " id_verify: X509_verify_cert failed.\n");
+		goto cleanup;
+	}
+
+	if (sipdebug) {
+		ast_log(LOG_NOTICE, "** id_verify: validates ok\n");
+	}
+
+	rv=TRUE;
+
+	/* verified ok. Now free everything */
+	cleanup:	{			/* gotos are not evil */
+		if(rsa)RSA_free(rsa);
+		if(ctx)X509_STORE_free(ctx);
+		if(csc)X509_STORE_CTX_free(csc);
+		if(cert)X509_free(cert);
+	}
+	return rv;
+}
+/*! \brief helper function for rfc 4474 gets the specific sip header requested
+ *
+ * \param req  pointer to sip_request strucutre
+ * \param name pointer to header value being requested
+ * \param out pointer to buffer where result is stored
+ */
+static void id_get_sip_header(struct sip_request *req,const char *name,char *out,size_t size)
+{
+	const char *p1,*p;
+	p=get_header(req, name);
+
+	if (p!=NULL) {
+		p1=strstr(p,"\r\n");
+		if (p1!=NULL) {
+			size_t len = p1-p;
+			/* reduce size if too long - will likely fail signing */
+			if ( len >= size ) {
+				len=size-1;
+				ast_log(LOG_ERROR,"** id_verify(): sip header too long: %s\n", p);
+			}
+			memcpy(out,p,size);
+			out[len]='\0';
+		}
+		else {
+			strncpy(out,p,size);
+			out[size-1]='\0';
+		}
+	}
+	else { /* no \r\n found so it should be good to copy */
+		strncpy(out,p,size);
+		out[size-1]='\0';	/* ensure null terminated */
+	}
+}
+
+/*! \brief helper function for rfc 4474 gets the address of record
+ *
+ * \param req  pointer to sip_request strucutre
+ * \param name pointer to Address of record value being requested
+ * \param out pointer to buffer where result is stored
+ */
+
+static void id_get_addr_of_record(struct sip_request *req,const char *name,char *out,size_t size)
+{
+	char HeaderReq[IDENTITY_HEADER_SIZE];
+	char *p,*p1;
+
+	id_get_sip_header(req, name,HeaderReq,sizeof(HeaderReq));
+	p=strchr(HeaderReq,'<');
+	if (p!=NULL) {
+		p1=strchr(p,'>');
+		if (p1!=NULL) {
+			if (p1-p-1 > size ) {
+				ast_log(LOG_ERROR,"** id_get_addr_of_record: field too large \n");
+				out[0]='\0';
+			}
+			else {
+				memcpy(out,p+1,p1-p-1);
+				out[p1-p-1]='\0';
+			}
+		}
+	}
+}
+
+/*! \brief entry function for rfc 4474 does signing or verifying signature
+ *
+ * \param req pointer to sip_request structure
+ * \param out pointer to where signing or verifying result is placed (out)
+ * \param sdp pointer to optional sdp data which can be passed as input
+ * \param type 0 for signing 1 for verifying
+ * \param userout pointer where user name is stored in case verifying is done (out)
+ */
+static int id_main(struct sip_request *req,char *out,const char *sdp,enum identity_action type,char *userout,size_t userout_size,struct identity_struct *identity)
+{
+	char digeststr[2*IDENTITY_HEADER_SIZE],signstr[IDENTITY_HEADER_SIZE],callid[IDENTITY_HEADER_SIZE],cseq[IDENTITY_HEADER_SIZE],date[IDENTITY_HEADER_SIZE];
+	char contactAor[IDENTITY_HEADER_SIZE],toAor[IDENTITY_HEADER_SIZE],fromAor[IDENTITY_HEADER_SIZE],user[40];
+	char idstoverify[IDENTITY_HEADER_SIZE],idinfo[IDENTITY_HEADER_SIZE];
+	char *p,*p1;
+	int rv=0; /* returned value. >1 is success */
+
+	/* ensure all buffers are null strings */
+	contactAor[0]=toAor[0]=fromAor[0]=user[0]= idstoverify[0]=idinfo[0]= digeststr[0]=signstr[0]=callid[0]=cseq[0]=date[0] ='\0';
+
+	if (type==IDENTITY_ACTION_VERIFY) { /* verify the signature*/
+		id_get_sip_header(req,"Identity-Info",idinfo,sizeof(idinfo));
+		if (strlen(idinfo)==0) {
+			return FALSE;
+		}
+		id_get_sip_header(req,"Identity",idstoverify,sizeof(idstoverify));
+		if (sipdebug) {
+			ast_log(LOG_NOTICE,"\n** id_main()\n idstoverify: %s \n idinfo: %s \n ", idstoverify, idinfo);
+		}
+	}
+
+	/* get various header fields */
+	id_get_sip_header(req, "Call-ID",callid,sizeof(callid));
+	id_get_sip_header(req, "CSeq",cseq,sizeof(cseq));
+	id_get_sip_header(req, "Date",date,sizeof(date));
+	/* get address of record */
+	id_get_addr_of_record(req,"To",toAor,sizeof(toAor));
+	id_get_addr_of_record(req,"From",fromAor,sizeof(fromAor));
+	id_get_addr_of_record(req,"Contact",contactAor,sizeof(contactAor));
+	/* get user from from header */
+	if (strlen(fromAor)>0) {
+		p1=strstr(fromAor,":"); /* find sip: */
+		if (p1!=NULL) {
+			p=strstr(p1+1,"@");
+			memcpy(user,p1+1,p-(p1+1));
+			user[p-(p1+1)]='\0';
+			strncpy(userout,user,userout_size);
+			userout[userout_size-1]='\0';	/* ensure null terminated */
+		}
+	}
+	/* build the digest string fromAOR| toAOR| Call id| cSEQ|date|Contact AOR |SDP */
+	strcpy(digeststr,fromAor);
+	strcat(digeststr,"|");
+	strcat(digeststr,toAor);
+	strcat(digeststr,"|");
+	strcat(digeststr,callid);
+	strcat(digeststr,"|");
+	strcat(digeststr,cseq);
+	strcat(digeststr,"|");
+	strcat(digeststr,date);
+	strcat(digeststr,"|");
+	strcat(digeststr,contactAor);
+	strcat(digeststr,"|");
+	/*if sdp is not passed then find it*/
+	if (sdp==NULL) {
+		if (find_sdp(req)) {
+			int x;
+			for (x = 0; x < (req->lines); x++) {
+				char *p,*line2;
+			 	line2=ast_strdup(REQ_OFFSET_TO_STR(req, line[x]));
+				p=strstr(line2,"\r\n");
+				if (p!=NULL) {
+					*p='\0';
+                                }
+				strcat(digeststr,line2);
+				strcat(digeststr,"\r\n");
+				ast_free(line2);
+			}
+		}
+	}
+	else {
+	    strcat(digeststr,sdp);
+	}
+
+	/* call appropriate function depending on type */
+	if (type==IDENTITY_ACTION_SIGN) { /* sign */
+		if ( (rv = id_sign(digeststr,signstr,user,identity)) && sipdebug) {
+			ast_debug(2,"** id_main() Signing Success \n");
+		}
+
+	}
+	else if (type==IDENTITY_ACTION_VERIFY) { /* verify */
+		rv = id_verify(digeststr,idstoverify,user,idinfo,identity);
+	}
+
+	strcpy(out,signstr);
+	if (sipdebug) {
+		ast_log(LOG_NOTICE,"** Identity id_main() size: %d digeststr: %s\n", strlen(digeststr), digeststr);
+	}
+	return rv;
+}
+
+/*
+ * End RFC4474 Identity definitions configuration values & functions
+ *
+ */
+
 
 static void tcptls_packet_destructor(void *obj)
 {
@@ -8905,12 +9847,17 @@
 }
 
 /*! \brief Add Session Description Protocol message
+ * \param resp
+ * \param p
+ * \param oldsdp
+ * \param add_audio
+ * \param add_t38
 
     If oldsdp is TRUE, then the SDP version number is not incremented. This mechanism
     is used in Session-Timers where RE-INVITEs are used for refreshing SIP sessions
     without modifying the media session in any way.
 */
-static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp, int add_audio, int add_t38)
+static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp, int add_audio, int add_t38, int first_message)
 {
 	int len = 0;
 	format_t alreadysent = 0;
@@ -8955,6 +9902,16 @@
 	char buf[SIPBUFSIZE];
 	char dummy_answer[256];
 
+	char signed_identity_digest[IDENTITY_SIGNBUF_SIZE];   /* signed identity digest header parameter */
+	char user[IDENTITY_SIGNBUF_SIZE];		      /* user CALLERID(NUM) */
+	char idinfo[IDENTITY_SIGNBUF_SIZE];		      /* identity-info header parameter */
+	char result_number[3];				      /* Result code */
+	struct ast_str *sdp1;				      /* working copy of sdp for identity */
+
+	signed_identity_digest[0]='\0';
+	user[0]='\0';
+	idinfo[0]='\0';
+
 	/* Set the SDP session name */
 	snprintf(subject, sizeof(subject), "s=%s\r\n", ast_strlen_zero(global_sdpsession) ? "-" : global_sdpsession);
 
@@ -9212,6 +10169,84 @@
 		len += m_modem->used + a_modem->used;
 
 	add_header(resp, "Content-Type", "application/sdp");
+
+	/* add identity header can be done only here */
+
+	/* build a disposable SDP for signing.  cant use real SDP because once add_line is called, cant add_header */
+	sdp1 = ast_str_create(1024);
+	ast_str_set(&sdp1,0,version);
+	ast_str_append(&sdp1,0,owner);
+	ast_str_append(&sdp1,0,subject);
+	ast_str_append(&sdp1,0,connection);
+
+	if (needvideo){
+		ast_str_append(&sdp1,0,bandwidth);
+	}
+
+	ast_str_append(&sdp1,0,session_time);
+	if (needaudio) {
+		ast_str_append(&sdp1,0,m_audio->str);
+		ast_str_append(&sdp1,0,a_audio->str);
+		ast_str_append(&sdp1,0,hold);
+	}
+
+	if (needvideo) { /* only if video response is appropriate */
+		ast_str_append(&sdp1,0,m_video->str);
+		ast_str_append(&sdp1,0,a_video->str);
+		ast_str_append(&sdp1,0,hold);
+	}
+	if (needtext) { /* only if text response is appropriate */
+		ast_str_append(&sdp1,0,m_text->str);
+		ast_str_append(&sdp1,0,a_text->str);
+		ast_str_append(&sdp1,0,hold);
+	}
+	if (add_t38) { /* only if t38 FAX response is appropriate */
+		ast_str_append(&sdp1,0,m_modem->str);
+		ast_str_append(&sdp1,0,a_modem->str);
+		ast_str_append(&sdp1,0,hold);
+	}
+
+	/* get and add the identity string */
+	struct identity_struct *identity = &identity_general;
+	/* FUTURE Select identity struct to use from domain or peer */
+	if (first_message && p->method==SIP_INVITE && !oldsdp && (identity->mode==IDENTITY_SIGN || identity->mode==IDENTITY_SIGN_AND_VALIDATE)) {
+		char idinfo[IDENTITY_SIGNBUF_SIZE];
+		/* call get digest string to get the signing result*/
+		id_get_sip_header(&p->initreq,"Identity-Info",idinfo,sizeof(idinfo));
+		if (debug) {
+			ast_debug(2,"set idinfo [%s]\n",idinfo);
+		}
+		id_main(resp,signed_identity_digest,ast_str_buffer(sdp1),IDENTITY_ACTION_SIGN,user,sizeof(user),identity);
+		ast_free(sdp1);
+		if (strlen(signed_identity_digest)>0) {
+			strcpy(idinfo,"<");
+			strcpy(&idinfo[1],identity->public_url_prefix);
+			strcat(idinfo,user);
+			strcpy(&idinfo[strlen(idinfo)],identity->public_url_suffix);
+			strcat(idinfo,">;alg=rsa-sha1");
+			add_header(resp, "Identity", signed_identity_digest);
+			add_header(resp, "Identity-Info",idinfo);
+			if (sipdebug) {
+				ast_log(LOG_NOTICE,"id [%s]\n idinfo [%s]\n",signed_identity_digest,idinfo);
+			}
+			p->initreq.sip_identity_result=IDENTITY_RES_SIGN_OK;
+		}
+		else {
+			p->initreq.sip_identity_result=IDENTITY_RES_SIGN_BROKEN_KEY;  /* assume broken for now */
+		}
+	}
+	else {
+		p->initreq.sip_identity_result=IDENTITY_RES_SIGN_DISABLED;
+	}
+
+	/* set channel variable with result */
+	sprintf(result_number,"%2d",p->initreq.sip_identity_result);
+	pbx_builtin_setvar_helper(p->owner,"IDENTITY_RESULT",result_number);
+	if (sipdebug) {
+		ast_debug(2,"** Set Chan_Var IDENTITY_RESULT SIGN result to test: [%d], %s\n",p->initreq.sip_identity_result,ident_status(p->initreq.sip_identity_result));
+	}
+
+	/* Add content length now */
 	add_header_contentLength(resp, len);
 	add_line(resp, version);
 	add_line(resp, owner);
@@ -9271,7 +10306,7 @@
 	}
 	respprep(&resp, p, msg, req);
 	if (p->udptl) {
-		add_sdp(&resp, p, 0, 0, 1);
+		add_sdp(&resp, p, 0, 0, 1, FALSE);
 	} else
 		ast_log(LOG_ERROR, "Can't add SDP to response, since we have no UDPTL session allocated. Call-ID %s\n", p->callid);
 	if (retrans && !p->pendinginvite)
@@ -9326,9 +10361,9 @@
 		ast_rtp_instance_activate(p->rtp);
 		try_suggested_sip_codec(p);
 		if (p->t38.state == T38_ENABLED) {
-			add_sdp(&resp, p, oldsdp, TRUE, TRUE);
+			add_sdp(&resp, p, oldsdp, TRUE, TRUE, FALSE);
 		} else {
-			add_sdp(&resp, p, oldsdp, TRUE, FALSE);
+			add_sdp(&resp, p, oldsdp, TRUE, FALSE, FALSE);
 		}
 	} else
 		ast_log(LOG_ERROR, "Can't add SDP to response, since we have no RTP session allocated. Call-ID %s\n", p->callid);
@@ -9417,9 +10452,9 @@
 
 	try_suggested_sip_codec(p);
 	if (t38version)
-		add_sdp(&req, p, oldsdp, FALSE, TRUE);
+		add_sdp(&req, p, oldsdp, FALSE, TRUE, FALSE);
 	else
-		add_sdp(&req, p, oldsdp, TRUE, FALSE);
+		add_sdp(&req, p, oldsdp, TRUE, FALSE, FALSE);
 
 	/* Use this as the basis */
 	initialize_initreq(p, &req);
@@ -9792,10 +10827,10 @@
 		memset(p->offered_media, 0, sizeof(p->offered_media));
 		if (p->udptl && p->t38.state == T38_LOCAL_REINVITE) {
 			ast_debug(1, "T38 is in state %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
-			add_sdp(&req, p, FALSE, FALSE, TRUE);
+			add_sdp(&req, p, FALSE, FALSE, TRUE, TRUE);
 		} else if (p->rtp) {
 			try_suggested_sip_codec(p);
-			add_sdp(&req, p, FALSE, TRUE, FALSE);
+			add_sdp(&req, p, FALSE, TRUE, FALSE, TRUE);
 		}
 	} else if (p->notify) {
 		for (var = p->notify->headers; var; var = var->next)
@@ -10337,7 +11372,7 @@
 			add_header(&req, "Allow", ALLOWED_METHODS);
 			add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
 			add_rpid(&req, p);
-			add_sdp(&req, p, FALSE, TRUE, FALSE);
+			add_sdp(&req, p, FALSE, TRUE, FALSE, FALSE);
 
 			initialize_initreq(p, &req);
 			p->lastinvite = p->ocseq;
@@ -15130,6 +16165,39 @@
 		ast_cli(a->fd, "  Save sys. name:         %s\n", cli_yesno(sip_cfg.rtsave_sysname));
 		ast_cli(a->fd, "  Auto Clear:             %d\n", sip_cfg.rtautoclear);
 	}
+
+	struct identity_struct *identity = &identity_general;
+	/* FUTURE Select identity struct to use from domain or peer */
+	ast_cli(a->fd, "\nSIP Identity Settings:\n");
+	ast_cli(a->fd,   "--------------------------\n");
+	if (identity->mode==IDENTITY_DISABLED) {
+		ast_cli(a->fd, "  Mode:     			  no  (disabled)\n");
+	}
+	else {
+
+		char* mode;
+		switch(identity->mode) {
+			case IDENTITY_SIGN:
+				mode="sign";
+				break;
+			case IDENTITY_VALIDATE:
+				mode="sign";
+				break;
+			default:
+				mode="sign_and_val";
+		}
+
+		ast_cli(a->fd, "  Mode:                   %s\n", mode);
+		ast_cli(a->fd, "  Private URL Prefix:     %s\n", identity->private_url_prefix);
+		ast_cli(a->fd, "  Private URL Suffix:     %s\n", identity->private_url_suffix);
+		ast_cli(a->fd, "  Private File Path:      %s\n", identity->private_path);
+		ast_cli(a->fd, "  Public URL Prefix:      %s\n", identity->public_url_prefix);
+		ast_cli(a->fd, "  Public URL Suffix:      %s\n", identity->public_url_suffix);
+		ast_cli(a->fd, "  Public File Path:       %s\n", identity->public_path);
+		ast_cli(a->fd, "  Cache Path:             %s\n", identity->cache_path);
+
+	}
+
 	ast_cli(a->fd, "\n----\n");
 	return CLI_SUCCESS;
 }
@@ -19351,6 +20419,33 @@

[... 394 lines stripped ...]



More information about the svn-commits mailing list