Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 2149)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 2150)
@@ -394,4 +394,9 @@
 typedef int pjsua_conf_port_id;
 
+/** Opaque declaration for server side presence subscription */
+typedef struct pjsua_srv_pres pjsua_srv_pres;
+
+/** Forward declaration for pjsua_msg_data */
+typedef struct pjsua_msg_data pjsua_msg_data;
 
 
@@ -792,4 +797,77 @@
      */
     void (*on_reg_state)(pjsua_acc_id acc_id);
+
+    /**
+     * Notification when incoming SUBSCRIBE request is received. Application
+     * may use this callback to authorize the incoming subscribe request
+     * (e.g. ask user permission if the request should be granted).
+     *
+     * If this callback is not implemented, all incoming presence subscription
+     * requests will be accepted.
+     *
+     * If this callback is implemented, application has several choices on
+     * what to do with the incoming request:
+     *	- it may reject the request immediately by specifying non-200 class
+     *    final response in the \a code argument.
+     *	- it may immediately accept the request by specifying 200 as the
+     *	  \a code argument. This is the default value if application doesn't
+     *	  set any value to the \a code argument. In this case, the library
+     *	  will automatically send NOTIFY request upon returning from this
+     *	  callback.
+     *  - it may delay the processing of the request, for example to request
+     *    user permission whether to accept or reject the request. In this 
+     *	  case, the application MUST set the \a code argument to 202, and 
+     *	  later calls #pjsua_pres_notify() to accept or reject the 
+     *	  subscription request.
+     *
+     * Any \a code other than 200 and 202 will be treated as 200.
+     *
+     * Application MUST return from this callback immediately (e.g. it must
+     * not block in this callback while waiting for user confirmation).
+     *
+     * @param srv_pres	    Server presence subscription instance. If
+     *			    application delays the acceptance of the request,
+     *			    it will need to specify this object when calling
+     *			    #pjsua_pres_notify().
+     * @param acc_id	    Account ID most appropriate for this request.
+     * @param buddy_id	    ID of the buddy matching the sender of the
+     *			    request, if any, or PJSUA_INVALID_ID if no
+     *			    matching buddy is found.
+     * @param from	    The From URI of the request.
+     * @param rdata	    The incoming request.
+     * @param code	    The status code to respond to the request. The
+     *			    default value is 200. Application may set this
+     *			    to other final status code to accept or reject
+     *			    the request.
+     * @param reason	    The reason phrase to respond to the request.
+     * @param msg_data	    If the application wants to send additional
+     *			    headers in the response, it can put it in this
+     *			    parameter.
+     */
+    void (*on_incoming_subscribe)(pjsua_acc_id acc_id,
+				  pjsua_srv_pres *srv_pres,
+				  pjsua_buddy_id buddy_id,
+				  const pj_str_t *from,
+				  pjsip_rx_data *rdata,
+				  pjsip_status_code *code,
+				  pj_str_t *reason,
+				  pjsua_msg_data *msg_data);
+
+    /**
+     * Notification when server side subscription state has changed.
+     * This callback is optional as application normally does not need
+     * to do anything to maintain server side presence subscription.
+     *
+     * @param acc_id	    The account ID.
+     * @param srv_pres	    Server presence subscription object.
+     * @param remote_uri    Remote URI string.
+     * @param state	    New subscription state.
+     * @param event	    PJSIP event that triggers the state change.
+     */
+    void (*on_srv_subscribe_state)(pjsua_acc_id acc_id,
+				   pjsua_srv_pres *srv_pres,
+				   const pj_str_t *remote_uri,
+				   pjsip_evsub_state state,
+				   pjsip_event *event);
 
     /**
@@ -1194,5 +1272,5 @@
  * \endcode
  */
-typedef struct pjsua_msg_data
+struct pjsua_msg_data
 {
     /**
@@ -1222,5 +1300,5 @@
     pj_str_t	msg_body;
 
-} pjsua_msg_data;
+};
 
 
@@ -2412,5 +2490,4 @@
 PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_id, 
 						pj_bool_t renew);
-
 
 /**
@@ -3353,4 +3430,20 @@
 
     /**
+     * If \a monitor_pres is enabled, this specifies the last state of the
+     * presence subscription. If presence subscription session is currently
+     * active, the value will be PJSIP_EVSUB_STATE_ACTIVE. If presence
+     * subscription request has been rejected, the value will be
+     * PJSIP_EVSUB_STATE_TERMINATED, and the termination reason will be
+     * specified in \a sub_term_reason.
+     */
+    pjsip_evsub_state	sub_state;
+
+    /**
+     * Specifies the last presence subscription terminatino reason. If 
+     * presence subscription is currently active, the value will be empty.
+     */
+    pj_str_t		sub_term_reason;
+
+    /**
      * Extended RPID information about the person.
      */
@@ -3520,4 +3613,33 @@
 PJ_DECL(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id);
 
+
+/**
+ * Send NOTIFY to inform account presence status or to terminate server
+ * side presence subscription. If application wants to reject the incoming
+ * request, it should set the \a state to PJSIP_EVSUB_STATE_TERMINATED.
+ *
+ * @param acc_id	Account ID.
+ * @param srv_pres	Server presence subscription instance.
+ * @param state		New state to set.
+ * @param state_str	Optionally specify the state string name, if state
+ *			is not "active", "pending", or "terminated".
+ * @param reason	If the new state is PJSIP_EVSUB_STATE_TERMINATED,
+ *			optionally specify the termination reason. 
+ * @param with_body	If the new state is PJSIP_EVSUB_STATE_TERMINATED,
+ *			this specifies whether the NOTIFY request should
+ *			contain message body containing account's presence
+ *			information.
+ * @param msg_data	Optional list of headers to be sent with the NOTIFY
+ *			request.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsua_pres_notify(pjsua_acc_id acc_id,
+				       pjsua_srv_pres *srv_pres,
+				       pjsip_evsub_state state,
+				       const pj_str_t *state_str,
+				       const pj_str_t *reason,
+				       pj_bool_t with_body,
+				       const pjsua_msg_data *msg_data);
 
 /**
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 2149)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 2150)
@@ -86,10 +86,13 @@
  * Server presence subscription list head.
  */
-typedef struct pjsua_srv_pres
+struct pjsua_srv_pres
 {
     PJ_DECL_LIST_MEMBER(struct pjsua_srv_pres);
-    pjsip_evsub	    *sub;
-    char	    *remote;
-} pjsua_srv_pres;
+    pjsip_evsub	    *sub;	    /**< The evsub.			    */
+    char	    *remote;	    /**< Remote URI.			    */
+    int		     acc_id;	    /**< Account ID.			    */
+    pjsip_dialog    *dlg;	    /**< Dialog.			    */
+    int		     expires;	    /**< "expires" value in the request.    */
+};
 
 
@@ -151,4 +154,7 @@
 } pjsua_transport_data;
 
+
+/** Maximum length of subscription termination reason. */
+#define PJSUA_BUDDY_SUB_TERM_REASON_LEN	    32
 
 /**
@@ -168,4 +174,5 @@
     pjsip_dialog	*dlg;	    /**< The underlying dialog.		*/
     pjsip_evsub		*sub;	    /**< Buddy presence subscription	*/
+    pj_str_t		 term_reason;/**< Subscription termination reason */
     pjsip_pres_status	 status;    /**< Buddy presence status.		*/
 
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_pres.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_pres.c	(revision 2149)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_pres.c	(revision 2150)
@@ -164,4 +164,27 @@
     info->monitor_pres = buddy->monitor;
 
+    /* subscription state and termination reason */
+    if (buddy->sub) {
+	info->sub_state = pjsip_evsub_get_state(buddy->sub);
+	if (info->sub_state == PJSIP_EVSUB_STATE_TERMINATED &&
+	    total < sizeof(info->buf_)) 
+	{
+	    info->sub_term_reason.ptr = info->buf_ + total;
+	    pj_strncpy(&info->sub_term_reason,
+		       pjsip_evsub_get_termination_reason(buddy->sub),
+		       sizeof(info->buf_) - total);
+	    total += info->sub_term_reason.slen;
+	} else {
+	    info->sub_term_reason = pj_str("");
+	}
+    } else if (total < sizeof(info->buf_)) {
+	info->sub_term_reason.ptr = info->buf_ + total;
+	pj_strncpy(&info->sub_term_reason, &buddy->term_reason,
+		   sizeof(info->buf_) - total);
+	total += info->sub_term_reason.slen;
+    } else {
+	info->sub_term_reason = pj_str("");
+    }
+
     PJSUA_UNLOCK();
     return PJ_SUCCESS;
@@ -223,4 +246,9 @@
 	buddy->pool = pjsua_pool_create(name, 512, 256);
     }
+
+    /* Init buffers for presence subscription status */
+    buddy->term_reason.ptr = (char*) 
+			     pj_pool_alloc(buddy->pool, 
+					   PJSUA_BUDDY_SUB_TERM_REASON_LEN);
 
     /* Get name and display name for buddy */
@@ -525,8 +553,21 @@
     uapres = (pjsua_srv_pres*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
     if (uapres) {
+	pjsip_evsub_state state;
+
 	PJ_LOG(4,(THIS_FILE, "Server subscription to %s is %s",
 		  uapres->remote, pjsip_evsub_get_state_name(sub)));
 
-	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+	state = pjsip_evsub_get_state(sub);
+
+	if (pjsua_var.ua_cfg.cb.on_srv_subscribe_state) {
+	    pj_str_t from;
+
+	    from = uapres->dlg->remote.info_str;
+	    (*pjsua_var.ua_cfg.cb.on_srv_subscribe_state)(uapres->acc_id, 
+							  uapres, &from,
+							  state, event);
+	}
+
+	if (state == PJSIP_EVSUB_STATE_TERMINATED) {
 	    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
 	    pj_list_erase(uapres);
@@ -549,10 +590,9 @@
     pjsip_evsub *sub;
     pjsip_evsub_user pres_cb;
-    pjsip_tx_data *tdata;
-    pjsip_pres_status pres_status;
     pjsip_dialog *dlg;
+    pjsip_status_code st_code;
+    pj_str_t reason;
     pjsip_expires_hdr *expires_hdr;
-    pjsip_evsub_state ev_state;
-    pjsua_buddy_id buddy_id;
+    pjsua_msg_data msg_data;
     pj_status_t status;
 
@@ -628,4 +668,6 @@
     uapres->sub = sub;
     uapres->remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE);
+    uapres->acc_id = acc_id;
+    uapres->dlg = dlg;
     status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri,
 			     uapres->remote, PJSIP_MAX_URL_SIZE);
@@ -641,6 +683,64 @@
 
 
-    /* Create and send 200 (OK) to the SUBSCRIBE request: */
-    status = pjsip_pres_accept(sub, rdata, 200, NULL);
+    /* Capture the value of Expires header. */
+    expires_hdr = (pjsip_expires_hdr*)
+    		  pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES,
+				     NULL);
+    if (expires_hdr)
+	uapres->expires = expires_hdr->ivalue;
+    else
+	uapres->expires = -1;
+
+    st_code = 200;
+    reason = pj_str("OK");
+    pjsua_msg_data_init(&msg_data);
+
+    /* Notify application callback, if any */
+    if (pjsua_var.ua_cfg.cb.on_incoming_subscribe) {
+	pjsua_buddy_id buddy_id;
+
+	buddy_id = pjsua_find_buddy(rdata->msg_info.from->uri);
+
+	(*pjsua_var.ua_cfg.cb.on_incoming_subscribe)(acc_id, uapres, buddy_id,
+						     &dlg->remote.info_str, 
+						     rdata, &st_code, &reason,
+						     &msg_data);
+    }
+
+    /* Handle rejection case */
+    if (st_code >= 300) {
+	pjsip_tx_data *tdata;
+
+	/* Create response */
+	status = pjsip_dlg_create_response(dlg, rdata, st_code, 
+					   &reason, &tdata);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Error creating response",  status);
+	    pj_list_erase(uapres);
+	    pjsip_pres_terminate(sub, PJ_FALSE);
+	    PJSUA_UNLOCK();
+	    return PJ_FALSE;
+	}
+
+	/* Add header list, if any */
+	pjsua_process_msg_data(tdata, &msg_data);
+
+	/* Send the response */
+	status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
+					 tdata);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Error sending response",  status);
+	    /* This is not fatal */
+	}
+
+	/* Terminate presence subscription */
+	pj_list_erase(uapres);
+	pjsip_pres_terminate(sub, PJ_FALSE);
+	PJSUA_UNLOCK();
+	return PJ_TRUE;
+    }
+
+    /* Create and send 2xx response to the SUBSCRIBE request: */
+    status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to accept presence subscription", 
@@ -652,32 +752,82 @@
     }
 
+    /* If code is 200, send NOTIFY now */
+    if (st_code == 200) {
+	pjsua_pres_notify(acc_id, uapres, PJSIP_EVSUB_STATE_ACTIVE, 
+			  NULL, NULL, PJ_TRUE, &msg_data);
+    }
+
+    /* Done: */
+
+    PJSUA_UNLOCK();
+
+    return PJ_TRUE;
+}
+
+
+/*
+ * Send NOTIFY.
+ */
+PJ_DEF(pj_status_t) pjsua_pres_notify( pjsua_acc_id acc_id,
+				       pjsua_srv_pres *srv_pres,
+				       pjsip_evsub_state ev_state,
+				       const pj_str_t *state_str,
+				       const pj_str_t *reason,
+				       pj_bool_t with_body,
+				       const pjsua_msg_data *msg_data)
+{
+    pjsua_acc *acc;
+    pjsip_pres_status pres_status;
+    pjsua_buddy_id buddy_id;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    /* Check parameters */
+    PJ_ASSERT_RETURN(acc_id!=-1 && srv_pres, PJ_EINVAL);
+
+    /* Check that account ID is valid */
+    PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+		     PJ_EINVAL);
+    /* Check that account is valid */
+    PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+    PJSUA_LOCK();
+
+    acc = &pjsua_var.acc[acc_id];
+
+    /* Check that the server presence subscription is still valid */
+    if (pj_list_find_node(&acc->pres_srv_list, srv_pres) == NULL) {
+	/* Subscription has been terminated */
+	PJSUA_UNLOCK();
+	return PJ_EINVALIDOP;
+    }
 
     /* Set our online status: */
     pj_bzero(&pres_status, sizeof(pres_status));
     pres_status.info_cnt = 1;
-    pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
-    pres_status.info[0].id = pjsua_var.acc[acc_id].cfg.pidf_tuple_id;
+    pres_status.info[0].basic_open = acc->online_status;
+    pres_status.info[0].id = acc->cfg.pidf_tuple_id;
     //Both pjsua_var.local_uri and pjsua_var.contact_uri are enclosed in "<" and ">"
     //causing XML parsing to fail.
     //pres_status.info[0].contact = pjsua_var.local_uri;
 
-    pjsip_pres_set_status(sub, &pres_status);
+    pjsip_pres_set_status(srv_pres->sub, &pres_status);
 
     /* Check expires value. If it's zero, send our presense state but
      * set subscription state to TERMINATED.
      */
-    expires_hdr=(pjsip_expires_hdr*)
-    		pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
-
-    if (expires_hdr && expires_hdr->ivalue == 0)
+    if (srv_pres->expires == 0)
 	ev_state = PJSIP_EVSUB_STATE_TERMINATED;
-    else
-	ev_state = PJSIP_EVSUB_STATE_ACTIVE;
-
-    /* Create and send the first NOTIFY to active subscription: */
-    status = pjsip_pres_notify( sub, ev_state, NULL, NULL, &tdata);
+
+    /* Create and send the NOTIFY to active subscription: */
+    status = pjsip_pres_notify(srv_pres->sub, ev_state, state_str, 
+			       reason, &tdata);
     if (status == PJ_SUCCESS) {
-	pjsua_process_msg_data(tdata, NULL);
-	status = pjsip_pres_send_request( sub, tdata);
+	/* Force removal of message body if msg_body==FALSE */
+	if (!with_body) {
+	    tdata->msg->body = NULL;
+	}
+	pjsua_process_msg_data(tdata, msg_data);
+	status = pjsip_pres_send_request( srv_pres->sub, tdata);
     }
 
@@ -685,13 +835,13 @@
 	pjsua_perror(THIS_FILE, "Unable to create/send NOTIFY", 
 		     status);
-	pj_list_erase(uapres);
-	pjsip_pres_terminate(sub, PJ_FALSE);
-	PJSUA_UNLOCK();
-	return PJ_FALSE;
+	pj_list_erase(srv_pres);
+	pjsip_pres_terminate(srv_pres->sub, PJ_FALSE);
+	PJSUA_UNLOCK();
+	return status;
     }
 
 
     /* Subscribe to buddy's presence if we're not subscribed */
-    buddy_id = pjsua_find_buddy(dlg->remote.info->uri);
+    buddy_id = pjsua_find_buddy(srv_pres->dlg->remote.info->uri);
     if (buddy_id != PJSUA_INVALID_ID) {
 	pjsua_buddy *b = &pjsua_var.buddy[buddy_id];
@@ -703,9 +853,7 @@
     }
 
-    /* Done: */
-
     PJSUA_UNLOCK();
 
-    return PJ_TRUE;
+    return PJ_SUCCESS;
 }
 
@@ -1010,12 +1158,26 @@
 
 	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+	    if (buddy->term_reason.ptr == NULL) {
+		buddy->term_reason.ptr = (char*) 
+					 pj_pool_alloc(buddy->pool,
+					   PJSUA_BUDDY_SUB_TERM_REASON_LEN);
+	    }
+	    pj_strncpy(&buddy->term_reason, 
+		       pjsip_evsub_get_termination_reason(sub), 
+		       PJSUA_BUDDY_SUB_TERM_REASON_LEN);
+	} else {
+	    buddy->term_reason.slen = 0;
+	}
+
+	/* Call callback */
+	if (pjsua_var.ua_cfg.cb.on_buddy_state)
+	    (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index);
+
+	/* Clear subscription */
+	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
 	    buddy->sub = NULL;
 	    buddy->status.info_cnt = 0;
 	    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
 	}
-
-	/* Call callback */
-	if (pjsua_var.ua_cfg.cb.on_buddy_state)
-	    (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index);
     }
 
