Index: /pjproject/trunk/pjsip-apps/src/python/_pjsua.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/python/_pjsua.c	(revision 2155)
+++ /pjproject/trunk/pjsip-apps/src/python/_pjsua.c	(revision 2156)
@@ -341,4 +341,56 @@
 }
 
+/* 
+ * cb_on_incoming_subscribe
+ */
+static void cb_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)
+{
+    static char reason_buf[64];
+
+    PJ_UNUSED_ARG(rdata);
+    PJ_UNUSED_ARG(msg_data);
+
+    if (PyCallable_Check(g_obj_callback->on_incoming_subscribe))
+    {
+	PyObject *ret;
+
+	ENTER_PYTHON();
+
+        ret = PyObject_CallFunctionObjArgs(
+	    g_obj_callback->on_incoming_subscribe,
+	    Py_BuildValue("i", acc_id),
+	    Py_BuildValue("i", buddy_id),
+	    PyString_FromStringAndSize(from->ptr, from->slen),
+	    PyLong_FromLong((long)srv_pres),
+	    NULL
+	);
+
+	if (ret && PyTuple_Check(ret)) {
+	    if (PyTuple_Size(ret) >= 1)
+		*code = (int)PyInt_AsLong(PyTuple_GetItem(ret, 0));
+	    if (PyTuple_Size(ret) >= 2) {
+		if (PyTuple_GetItem(ret, 1) != Py_None) {
+		    pj_str_t tmp;
+		    tmp = PyString_to_pj_str(PyTuple_GetItem(ret, 1));
+		    reason->ptr = reason_buf;
+		    pj_strncpy(reason, &tmp, sizeof(reason_buf));
+		} else {
+		}
+	    }
+
+	} else if (ret) {
+	    Py_XDECREF(ret);
+	}
+
+	LEAVE_PYTHON();
+    }
+}
 
 /*
@@ -372,4 +424,6 @@
 			pjsip_rx_data *rdata, pjsua_acc_id acc_id)
 {
+    PJ_UNUSED_ARG(rdata);
+
     if (PyCallable_Check(g_obj_callback->on_pager))
     {
@@ -912,4 +966,5 @@
     	cfg_ua.cb.on_call_replaced = &cb_on_call_replaced;
     	cfg_ua.cb.on_reg_state = &cb_on_reg_state;
+	cfg_ua.cb.on_incoming_subscribe = &cb_on_incoming_subscribe;
     	cfg_ua.cb.on_buddy_state = &cb_on_buddy_state;
     	cfg_ua.cb.on_pager2 = &cb_on_pager;
@@ -1879,4 +1934,64 @@
 }
 
+
+/*
+ * py_pjsua_acc_pres_notify
+ */
+static PyObject *py_pjsua_acc_pres_notify
+(PyObject *pSelf, PyObject *pArgs)
+{
+    static char reason_buf[64];
+    int acc_id, state;
+    PyObject *arg_pres, *arg_msg_data;
+    void *srv_pres;
+    pjsua_msg_data msg_data;
+    const char *arg_reason;
+    pj_str_t reason;
+    pj_bool_t with_body;
+    pj_pool_t *pool = NULL;
+    int status;	
+    
+    PJ_UNUSED_ARG(pSelf);
+
+    if (!PyArg_ParseTuple(pArgs, "iOisO", &acc_id, &arg_pres, 
+			  &state, &arg_reason, &arg_msg_data))
+    {
+        return NULL;
+    }	
+    
+    srv_pres = (void*) PyLong_AsLong(arg_pres);
+    pjsua_msg_data_init(&msg_data);
+    with_body = (state != PJSIP_EVSUB_STATE_TERMINATED);
+
+    if (arg_reason) {
+	strncpy(reason_buf, arg_reason, sizeof(reason_buf));
+	reason.ptr = reason_buf;
+	reason.slen = strlen(arg_reason);
+    } else {
+	reason = pj_str("");
+    }
+
+    if (arg_msg_data && arg_msg_data != Py_None) {
+        PyObj_pjsua_msg_data *omd = (PyObj_pjsua_msg_data *)arg_msg_data;
+        msg_data.content_type.ptr = PyString_AsString(omd->content_type);
+        msg_data.content_type.slen = PyString_Size(omd->content_type);
+        msg_data.msg_body.ptr = PyString_AsString(omd->msg_body);
+        msg_data.msg_body.slen = PyString_Size(omd->msg_body);
+        pool = pjsua_pool_create("pytmp", POOL_SIZE, POOL_SIZE);
+        translate_hdr(pool, &msg_data.hdr_list, omd->hdr_list);
+    } else if (arg_msg_data) {
+	Py_XDECREF(arg_msg_data);    
+    }
+
+    status = pjsua_pres_notify(acc_id, (pjsua_srv_pres*)srv_pres,
+			       (pjsip_evsub_state)state, NULL,
+			       &reason, with_body, &msg_data);
+    
+    if (pool) {
+	pj_pool_release(pool);
+    }
+
+    return Py_BuildValue("i", status);
+}
 
 static char pjsua_acc_config_default_doc[] =
@@ -5543,4 +5658,8 @@
     },
     {
+	"acc_pres_notify", py_pjsua_acc_pres_notify, METH_VARARGS,
+	"Accept or reject subscription request"
+    },
+    {
         "enum_accs", py_pjsua_enum_accs, METH_VARARGS,
         pjsua_enum_accs_doc
Index: /pjproject/trunk/pjsip-apps/src/python/_pjsua.h
===================================================================
--- /pjproject/trunk/pjsip-apps/src/python/_pjsua.h	(revision 2155)
+++ /pjproject/trunk/pjsip-apps/src/python/_pjsua.h	(revision 2156)
@@ -508,4 +508,5 @@
     PyObject * on_call_replaced;
     PyObject * on_reg_state;
+    PyObject * on_incoming_subscribe;
     PyObject * on_buddy_state;
     PyObject * on_pager;
@@ -530,4 +531,5 @@
     Py_XDECREF(self->on_call_replaced);
     Py_XDECREF(self->on_reg_state);
+    Py_XDECREF(self->on_incoming_subscribe);
     Py_XDECREF(self->on_buddy_state);
     Py_XDECREF(self->on_pager);
@@ -617,4 +619,6 @@
             return NULL;
         }
+	Py_INCREF(Py_None);
+        self->on_incoming_subscribe = Py_None;
         Py_INCREF(Py_None);
         self->on_buddy_state = Py_None;
@@ -718,4 +722,9 @@
         "Notify application when registration status has changed. Application "
         "may then query the account info to get the registration details."
+    },
+    {
+        "on_incoming_subscribe", T_OBJECT_EX,
+        offsetof(PyObj_pjsua_callback, on_incoming_subscribe), 0,
+        "Notification when incoming SUBSCRIBE request is received."
     },
     {
@@ -2769,4 +2778,6 @@
     int		 monitor_pres;
     int		 activity;
+    int		 sub_state;
+    PyObject	*sub_term_reason;
 } PyObj_pjsua_buddy_info;
 
@@ -2782,4 +2793,5 @@
     Py_XDECREF(self->contact);
     Py_XDECREF(self->status_text);
+    Py_XDECREF(self->sub_term_reason);
     
     self->ob_type->tp_free((PyObject*)self);
@@ -2801,4 +2813,8 @@
     obj->monitor_pres = info->monitor_pres;
     obj->activity = info->rpid.activity;
+    obj->sub_state = info->sub_state;
+    Py_XDECREF(obj->sub_term_reason);
+    obj->sub_term_reason = PyString_FromStringAndSize(info->sub_term_reason.ptr, 
+						      info->sub_term_reason.slen);
 }
 
@@ -2824,5 +2840,5 @@
             Py_DECREF(self);
             return NULL;
-        }        
+        }
 	self->contact = PyString_FromString("");
         if (self->contact == NULL) {
@@ -2835,5 +2851,5 @@
             return NULL;
         }
-	
+	self->sub_term_reason = PyString_FromString("");
     }
     return (PyObject *)self;
@@ -2882,4 +2898,14 @@
         offsetof(PyObj_pjsua_buddy_info, activity), 0,
         "Activity type. "
+    },
+    {
+        "sub_state", T_INT, 
+        offsetof(PyObj_pjsua_buddy_info, sub_state), 0,
+        "Subscription state."
+    },
+    {
+        "sub_term_reason", T_INT, 
+        offsetof(PyObj_pjsua_buddy_info, sub_term_reason), 0,
+        "Subscription termination reason."
     },
     
Index: /pjproject/trunk/pjsip-apps/src/python/pjsua.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/python/pjsua.py	(revision 2155)
+++ /pjproject/trunk/pjsip-apps/src/python/pjsua.py	(revision 2156)
@@ -148,5 +148,5 @@
     Member documentation:
 
-    NONE        -- media is not available.
+    NULL        -- media is not available.
     ACTIVE      -- media is active.
     LOCAL_HOLD  -- media is put on-hold by local party.
@@ -154,5 +154,5 @@
     ERROR       -- media error (e.g. ICE negotiation failure).
     """
-    NONE = 0
+    NULL = 0
     ACTIVE = 1
     LOCAL_HOLD = 2
@@ -166,10 +166,10 @@
     Member documentation:
 
-    NONE              -- media is not active
+    NULL              -- media is not active
     ENCODING          -- media is active in transmit/encoding direction only.
     DECODING          -- media is active in receive/decoding direction only
     ENCODING_DECODING -- media is active in both directions.
     """
-    NONE = 0
+    NULL = 0
     ENCODING = 1
     DECODING = 2
@@ -189,4 +189,18 @@
     AWAY = 1
     BUSY = 2
+
+
+class SubscriptionState:
+    """Presence subscription state constants.
+
+    """
+    NULL = 0
+    SENT = 1
+    ACCEPTED = 2
+    PENDING = 3
+    ACTIVE = 4
+    TERMINATED = 5
+    UNKNOWN = 6
+
 
 class TURNConnType:
@@ -862,8 +876,35 @@
         reject the call with default status code.
 
-    Keyword arguments:
-    call    -- the new incoming call
+        Keyword arguments:
+        call    -- the new incoming call
         """
         call.hangup()
+
+    def on_incoming_subscribe(self, buddy, from_uri, pres_obj):
+        """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)
+
+        Keyword arguments:
+        buddy       -- The buddy object, if buddy is found. Otherwise
+                       the value is None.
+        from_uri    -- The URI string of the sender.
+        pres_obj    -- Opaque presence subscription object, which is
+                       needed by Account.pres_notify()
+
+        Return:
+            Tuple (code, reason), where:
+             code:      The status code. If code is >= 300, the
+                        request is rejected. If code is 200, the
+                        request is accepted and NOTIFY will be sent
+                        automatically. If code is 202, application
+                        must accept or reject the request later with
+                        Account.press_notify().
+             reason:    Optional reason phrase, or None to use the
+                        default reasoh phrase for the status code.
+        """
+        return (200, None)
 
     def on_pager(self, from_uri, contact, mime_type, body):
@@ -952,5 +993,5 @@
         id  -- the pjsua account ID.
         """
-        _cb = AccountCallback(self)
+        self._cb = AccountCallback(self)
         self._id = id
         self._lib = lib
@@ -1102,4 +1143,17 @@
         return Buddy(self._lib, buddy_id, self)
 
+    def pres_notify(self, pres_obj, state, reason="", hdr_list=None):
+        """Send NOTIFY to inform account presence status or to terminate
+        server side presence subscription.
+        
+        Keyword arguments:
+        pres_obj    -- The subscription object from on_incoming_subscribe()
+                       callback
+        state       -- Subscription state, from SubscriptionState
+        reason      -- Optional reason phrase.
+        hdr_list    -- Optional header list.
+        """
+        _pjsua.acc_pres_notify(self._id, pres_obj, state, reason, 
+                               Lib._create_msg_data(hdr_list))
 
 class CallCallback:
@@ -1276,6 +1330,6 @@
     last_code = 0
     last_reason = ""
-    media_state = MediaState.NONE
-    media_dir = MediaDir.NONE
+    media_state = MediaState.NULL
+    media_dir = MediaDir.NULL
     conf_slot = -1
     call_time = 0
@@ -1530,4 +1584,7 @@
     subscribed      -- specify whether buddy's presence status is currently
                        being subscribed.
+    sub_state       -- SubscriptionState
+    sub_term_reason -- The termination reason string of the last presence
+                       subscription to this buddy, if any.
     """
     uri = ""
@@ -1537,4 +1594,6 @@
     activity = PresenceActivity.UNKNOWN
     subscribed = False
+    sub_state = SubscriptionState.NULL
+    sub_term_reason = ""
 
     def __init__(self, pjsua_bi=None):
@@ -1549,4 +1608,6 @@
         self.activity = inf.activity
         self.subscribed = inf.monitor_pres
+        self.sub_state = inf.sub_state
+        self.sub_term_reason = inf.sub_term_reason
 
 
@@ -1867,4 +1928,5 @@
         py_ua_cfg.cb.on_call_replaced = _cb_on_call_replaced
         py_ua_cfg.cb.on_reg_state = _cb_on_reg_state
+        py_ua_cfg.cb.on_incoming_subscribe = _cb_on_incoming_subscribe
         py_ua_cfg.cb.on_buddy_state = _cb_on_buddy_state
         py_ua_cfg.cb.on_pager = _cb_on_pager
@@ -2267,9 +2329,7 @@
 
     def _lookup_buddy(self, buddy_id, uri=None):
-        print "lookup_buddy, id=", buddy_id
         buddy = self.buddy.has_key(buddy_id) and self.buddy[buddy_id] or None
         if uri and not buddy:
             sip_uri = SIPUri(uri)
-            print "lookup_buddy, uri=", sip_uri.user, sip_uri.host
             buddy = self.buddy_by_uri.has_key( (sip_uri.user, sip_uri.host) ) \
                     and self.buddy_by_uri[(sip_uri.user, sip_uri.host)] or \
@@ -2289,4 +2349,12 @@
         if acc:
             acc._cb.on_reg_state()
+
+    def _cb_on_incoming_subscribe(self, acc_id, buddy_id, from_uri, pres_obj):
+        acc = self._lookup_account(acc_id)
+        if acc:
+            buddy = self._lookup_buddy(buddy_id)
+            return acc._cb.on_incoming_subscribe(buddy, from_uri, pres_obj)
+        else:
+            return (404, None)
 
     def _cb_on_incoming_call(self, acc_id, call_id, rdata):
@@ -2425,4 +2493,7 @@
     _lib._cb_on_reg_state(acc_id)
 
+def _cb_on_incoming_subscribe(acc_id, buddy_id, from_uri, pres):
+    return _lib._cb_on_incoming_subscribe(acc_id, buddy_id, from_uri, pres)
+
 def _cb_on_buddy_state(buddy_id):
     _lib._cb_on_buddy_state(buddy_id)
Index: /pjproject/trunk/pjsip-apps/src/python/samples/subscribe.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/python/samples/subscribe.py	(revision 2156)
+++ /pjproject/trunk/pjsip-apps/src/python/samples/subscribe.py	(revision 2156)
@@ -0,0 +1,94 @@
+# $Id$
+#
+# Authorization of incoming subscribe request
+#
+# Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+#
+import sys
+import pjsua as pj
+
+LOG_LEVEL = 3
+
+pending_pres = None
+
+def log_cb(level, str, len):
+    print str,
+
+class MyAccountCallback(pj.AccountCallback):
+    def __init__(self, account):
+        pj.AccountCallback.__init__(self, account)
+
+    def on_incoming_subscribe(self, buddy, from_uri, pres):
+        # Allow buddy to subscribe to our presence
+        global pending_pres
+
+        if buddy:
+            return (200, None)
+        print 'Incoming SUBSCRIBE request from', from_uri
+        print 'Press "A" to accept and add, "R" to reject the request'
+        pending_pres = pres
+        return (202, None)
+
+
+lib = pj.Lib()
+
+try:
+    # Init library with default config and some customized
+    # logging config.
+    lib.init(log_cfg = pj.LogConfig(level=LOG_LEVEL, callback=log_cb))
+
+    # Create UDP transport which listens to any available port
+    transport = lib.create_transport(pj.TransportType.UDP, 
+                                     pj.TransportConfig(0))
+    print "\nListening on", transport.info().host, 
+    print "port", transport.info().port, "\n"
+    
+    # Start the library
+    lib.start()
+
+    # Create local account
+    acc = lib.create_account_for_transport(transport)
+    acc.set_callback(MyAccountCallback(acc))
+
+    my_sip_uri = "sip:" + transport.info().host + \
+                 ":" + str(transport.info().port)
+
+    buddy = None
+
+    # Menu loop
+    while True:
+        print "My SIP URI is", my_sip_uri
+        print "Menu:  t=toggle online status, q=quit"
+
+        input = sys.stdin.readline().rstrip("\r\n")
+
+        if input == "t":
+            acc.set_basic_status(not acc.info().online_status)
+
+        elif input == "A":
+            if pending_pres:
+                acc.pres_notify(pending_pres, pj.SubscriptionState.ACTIVE)
+                pending_pres = None
+            else:
+                print "No pending request"
+
+        elif input == "R":
+            if pending_pres:
+                acc.pres_notify(pending_pres, pj.SubscriptionState.TERMINATED,
+                                "rejected")
+                pending_pres = None
+            else:
+                print "No pending request"
+        
+        elif input == "q":
+            break
+
+    # Shutdown the library
+    lib.destroy()
+    lib = None
+
+except pj.Error, e:
+    print "Exception: " + str(e)
+    lib.destroy()
+    lib = None
+
