Index: chan_sip.c
===================================================================
--- chan_sip.c	(revision 81188)
+++ chan_sip.c	(working copy)
@@ -211,6 +211,9 @@
 
 #define INITIAL_CSEQ                 101              /*!< our initial sip sequence number */
 
+#define DEFAULT_MAX_SE               1800             /*!< Session-Timer Default Session-Expires period (RFC 4028) */
+#define DEFAULT_MIN_SE               90               /*!< Session-Timer Default Min-SE period (RFC 4028) */
+
 /*! \brief Global jitterbuffer configuration - by default, jb is disabled */
 static struct ast_jb_conf default_jbconf =
 {
@@ -382,6 +385,21 @@
 		/* fatal - no chance to proceed */
 };
 
+/*! \brief Modes in which Astreisk can be configured to run SIP Session-Timers */
+enum st_mode {
+        SESSION_TIMER_MODE_ACCEPT,      /*!< Honor inbound Session-Timer requests */
+        SESSION_TIMER_MODE_ORIGINATE,   /*!< Originate outbound and honor inbound requests */
+        SESSION_TIMER_MODE_REFUSE       /*!< Ignore inbound Session-Timers requests */
+};
+
+/*! \brief The entity playing the refresher role for Session-Timers */
+enum st_refresher {
+        SESSION_TIMER_REFRESHER_AUTO,    /*!< Negotiated                      */
+        SESSION_TIMER_REFRESHER_UAC,     /*!< Session is refreshed by the UAC */
+        SESSION_TIMER_REFRESHER_UAS      /*!< Session is refreshed by the UAS */
+};
+
+
 /*! \brief definition of a sip proxy server
  *
  * For outbound proxies, this is allocated in the SIP peer dynamically or
@@ -458,7 +476,9 @@
 #define SIP_OPT_NOREFERSUB	(1 << 14)
 #define SIP_OPT_HISTINFO	(1 << 15)
 #define SIP_OPT_RESPRIORITY	(1 << 16)
+#define SIP_OPT_UNKNOWN		(1 << 17)
 
+
 /*! \brief List of well-known SIP options. If we get this in a require,
    we should check the list and answer accordingly. */
 static const struct cfsip_options {
@@ -472,8 +492,8 @@
 	{ SIP_OPT_REPLACES,	SUPPORTED,	"replace" },	
 	/* RFC3262: PRACK 100% reliability */
 	{ SIP_OPT_100REL,	NOT_SUPPORTED,	"100rel" },	
-	/* RFC4028: SIP Session Timers */
-	{ SIP_OPT_TIMER,	NOT_SUPPORTED,	"timer" },
+	/* RFC4028: SIP Session-Timers */
+	{ SIP_OPT_TIMER,	SUPPORTED,	"timer" },
 	/* RFC3959: SIP Early session support */
 	{ SIP_OPT_EARLY_SESSION, NOT_SUPPORTED,	"early-session" },
 	/* RFC3911: SIP Join header support */
@@ -509,7 +529,7 @@
 #define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY"
 
 /*! \brief SIP Extensions we support */
-#define SUPPORTED_EXTENSIONS "replaces" 
+#define SUPPORTED_EXTENSIONS "replaces, timer" 
 
 /*! \brief Standard SIP port from RFC 3261. DO NOT CHANGE THIS */
 #define STANDARD_SIP_PORT	5060
@@ -637,6 +657,12 @@
 
 static struct ast_flags global_flags[2] = {{0}};        /*!< global SIP_ flags */
 
+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  */
+static int global_max_se;                     /*!< Highest threshold for session refresh interval */
+
+
 AST_MUTEX_DEFINE_STATIC(netlock);
 
 /*! \brief Protect the monitoring thread, so only one process can kill or start it, and not
@@ -1053,6 +1079,7 @@
 
 	int timer_t1;				/*!< SIP timer T1, ms rtt */
 	unsigned int sipoptions;		/*!< Supported SIP options on the other end */
+	unsigned int reqsipoptions;		/*!< Required SIP options on the other end */
 	struct ast_codec_pref prefs;		/*!< codec prefs */
 	int capability;				/*!< Special capability (codec) */
 	int jointcapability;			/*!< Supported capability at both ends (codecs) */
@@ -1073,6 +1100,8 @@
 	char tag[11];				/*!< Our tag for this session */
 	int sessionid;				/*!< SDP Session ID */
 	int sessionversion;			/*!< SDP Session Version */
+	int sessionversion_remote;		/*!< Remote UA's SDP Session Version */
+	int session_modify;			/*!< Session modification request true/false  */
 	struct sockaddr_in sa;			/*!< Our peer */
 	struct sockaddr_in redirip;		/*!< Where our RTP should be going if not to us */
 	struct sockaddr_in vredirip;		/*!< Where our Video RTP should be going if not to us */
@@ -1121,8 +1150,16 @@
 							before strolling to the Grokyzpå
 							(A bit unsure of this, please correct if
 							you know more) */
+
+	int st_active;                          /*!< Session-Timers on/off */ 
+	int st_interval;                        /*!< Session-Timers negotiated session refresh interval */
+	int st_schedid;                         /*!< Session-Timers ast_sched scheduler id */
+	enum st_refresher st_ref;               /*!< Session-Timers session refresher */
+	int st_expirys;                         /*!< Session-Timers number of expirys */
+	int st_active_peer_ua;                  /*!< Session-Timers on/off in peer UA */
 };
 
+
 /*! Max entires in the history list for a sip_pvt */
 #define MAX_HISTORY_ENTRIES 50
 
@@ -1227,6 +1264,11 @@
 	struct ast_variable *chanvars;	/*!< Variables to set for channel created by user */
 	int maxcallbitrate;		/*!< Maximum Bitrate for a video call */
 	int autoframing;
+
+	enum st_mode st_mode_oper;      /*!< Mode of operation for Session-Timers           */
+	enum st_refresher st_ref;       /*!< Session-Timer refresher                        */
+	int st_min_se;                  /*!< Lowest threshold for session refresh interval  */
+	int st_max_se;                  /*!< Highest threshold for session refresh interval */
 };
 
 /*!
@@ -1312,6 +1354,11 @@
 	struct ast_variable *chanvars;	/*!<  Variables to set for channel created by user */
 	struct sip_pvt *mwipvt;		/*!<  Subscription for MWI */
 	int autoframing;
+
+	enum st_mode st_mode_oper;      /*!< Mode of operation for Session-Timers           */
+	enum st_refresher st_ref;       /*!< Session-Timer refresher                        */
+	int st_min_se;                  /*!< Lowest threshold for session refresh interval  */
+	int st_max_se;                  /*!< Highest threshold for session refresh interval */
 };
 
 
@@ -1487,7 +1534,7 @@
 static int transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req);
 static int transmit_response_reliable(struct sip_pvt *p, const char *msg, const struct sip_request *req);
 static int transmit_response_with_date(struct sip_pvt *p, const char *msg, const struct sip_request *req);
-static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable);
+static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp);
 static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported);
 static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *rand, enum xmittype reliable, const char *header, int stale);
 static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable);
@@ -1495,7 +1542,7 @@
 static int transmit_request(struct sip_pvt *p, int sipmethod, int inc, enum xmittype reliable, int newbranch);
 static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch);
 static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init);
-static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version);
+static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp);
 static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
 static int transmit_info_with_vidupdate(struct sip_pvt *p);
 static int transmit_message_with_text(struct sip_pvt *p, const char *text);
@@ -1549,7 +1596,7 @@
 static void add_noncodec_to_sdp(const struct sip_pvt *p, int format, int sample_rate,
 				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);
+static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp);
 static void do_setnat(struct sip_pvt *p, int natflags);
 static void stop_media_flows(struct sip_pvt *p);
 
@@ -1786,6 +1833,21 @@
 static struct ast_udptl *sip_get_udptl_peer(struct ast_channel *chan);
 static int sip_set_udptl_peer(struct ast_channel *chan, struct ast_udptl *udptl);
 
+/*------ Session-Timers functions --------- */
+static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp);
+static int  proc_session_timer(void *vp);
+static void stop_session_timer(struct sip_pvt *p);
+static void start_session_timer(struct sip_pvt *p);
+static void restart_session_timer(struct sip_pvt *p);
+static const char *strefresher2str (enum st_refresher r);
+static int parse_session_expires (const char *p_hdrval, int *const p_interval, enum st_refresher *const p_ref);
+static int parse_minse (const char *p_hdrval, int *const p_interval);
+static int st_get_max_se(struct sip_pvt *);
+static int st_get_min_se(struct sip_pvt *);
+static enum st_refresher st_get_refresher(struct sip_pvt*);
+static enum st_mode st_get_mode(struct sip_pvt*);
+
+
 /*! \brief Definition of this channel for PBX channel registration */
 static const struct ast_channel_tech sip_tech = {
 	.type = "SIP",
@@ -2056,6 +2118,15 @@
 				break;
 			}
 		}
+
+		/* This function is used to parse both Suported: and Require: headers.
+		Let the caller of this function know that an unknown option tag was 
+		encountered, so that if the UAC requires it then the request can be 
+		rejected with a 420 response. */
+		if (!found) {
+			profile |= SIP_OPT_UNKNOWN;
+		}
+
 		if (!found && sipdebug) {
 			if (!strncasecmp(next, "x-", 2))
 				ast_debug(3, "Found private SIP option, not supported: %s\n", next);
@@ -2465,6 +2536,10 @@
 	if (p->do_history)
 		append_history(p, "SchedDestroy", "%d ms", ms);
 	p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p));
+
+	if (p->st_active == TRUE && p->st_schedid != 0) {
+		stop_session_timer(p);
+	}
 }
 
 /*! \brief Cancel destruction of SIP dialog.
@@ -2805,7 +2880,7 @@
 		break;
 	case AST_STATE_UP:
 		if (!p->pendinginvite) {		/* We are up, and have no outstanding invite */
-			transmit_reinvite_with_sdp(p, FALSE);
+			transmit_reinvite_with_sdp(p, FALSE, FALSE);
 		} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
 			ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);	
 		}	
@@ -4205,7 +4280,7 @@
 			ast_debug(2,"T38State change to %d on channel %s\n", p->t38.state, ast->name);
 			res = transmit_response_with_t38_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL);
 		} else 
-			res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL);
+			res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL, FALSE);
 	}
 	sip_pvt_unlock(p);
 	return res;
@@ -4238,7 +4313,7 @@
 				if ((ast->_state != AST_STATE_UP) &&
 				    !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
 				    !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
-					transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+					transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
 					ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);	
 				}
 				p->lastrtptx = time(NULL);
@@ -4255,7 +4330,7 @@
 				if ((ast->_state != AST_STATE_UP) &&
 				    !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
 				    !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
-					transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+					transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
 					ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);	
 				}
 				p->lastrtptx = time(NULL);
@@ -4272,7 +4347,7 @@
 				if ((ast->_state != AST_STATE_UP) &&
 				    !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
 				    !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
-					transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+					transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
 					ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);	
 				}
 				p->lastrtptx = time(NULL);
@@ -4467,7 +4542,7 @@
 		    !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
 		    !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
 			p->invitestate = INV_EARLY_MEDIA;
-			transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+			transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
 			ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);	
 			break;
 		}
@@ -4907,7 +4982,7 @@
 			if (!p->pendinginvite) {
 				ast_debug(3, "Sending reinvite on SIP (%s) for T.38 negotiation.\n",ast->name);
 				p->t38.state = T38_LOCAL_REINVITE;
-				transmit_reinvite_with_sdp(p, TRUE);
+				transmit_reinvite_with_sdp(p, TRUE, FALSE);
 				ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, ast->name);
 			}
 		} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
@@ -4985,6 +5060,14 @@
 	p->autokillid = -1;
 	p->subscribed = NONE;
 	p->stateid = -1;
+	p->sessionversion_remote = -1;
+	p->session_modify = TRUE;
+	p->st_active = FALSE;			/* Session-Timers on/off */
+  	p->st_interval = 0;			/* Session-Timers negotiated session refresh interval */
+    	p->st_schedid = -1;			/* Session-Timers ast_sched scheduler id */
+    	p->st_ref = 0;				/* Session-Timers session refresher */
+    	p->st_expirys = 0;			/* Session-Timers number of expirys */
+    	p->st_active_peer_ua = FALSE;		/* Session-Timers on/off in peer UA */
 	p->prefs = default_prefs;		/* Set default codecs for this call */
 
 	if (intended_method != SIP_OPTIONS)	/* Peerpoke has it's own system */
@@ -5088,6 +5171,7 @@
 	}
 	ast_string_field_set(p, context, default_context);
 
+
 	/* Add to active dialog list */
 	dialoglist_lock();
 	p->next = dialoglist;
@@ -5493,6 +5577,9 @@
 	const char *m;		/* SDP media offer */
 	const char *c;
 	const char *a;
+	const char *o;		/* Pointer to o= line	*/
+	char *o_copy;		/* Copy of o= line	*/
+	char *token;
 	char host[258];
 	int len = -1;
 	int portno = -1;		/*!< RTP Audio port number */
@@ -5534,6 +5621,7 @@
 	int last_rtpmap_codec=0;
 
 	char buf[BUFSIZ];
+	int rua_version;
 
 	if (!p->rtp) {
 		ast_log(LOG_ERROR, "Got SDP but have no RTP session allocated.\n");
@@ -5559,7 +5647,51 @@
 	/* Update our last rtprx when we receive an SDP, too */
 	p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */
 
+	/* Store the SDP version number of remote UA. This will allow us to 
+	distinguish between session modifications and session refreshes. If 
+	the remote UA does not send an incremented SDP version number in a 
+	subsequent RE-INVITE then that means its not changing media session. 
+	The RE-INVITE may have been sent to update connected party, remote  
+	target or to refresh the session (Session-Timers).  Asterisk must not 
+	change media session and increment its own version number in answer 
+	SDP in this case. */ 
+	
+	o = get_sdp(req, "o");
+	if (ast_strlen_zero(o)) {
+		ast_log(LOG_WARNING, "SDP sytax error. SDP without an o= line\n");
+		return -1;
+	}
 
+	o_copy = ast_strdupa(o);
+	token = strsep (&o_copy, " ");  /* Skip username   */
+	if (!o_copy) { 
+		ast_log(LOG_WARNING, "SDP sytax error in o= line username\n");
+		return -1;
+	}
+	token = strsep (&o_copy, " ");  /* Skip session-id */
+	if (!o_copy) { 
+		ast_log(LOG_WARNING, "SDP sytax error in o= line session-id\n");
+		return -1;
+	}
+	token = strsep (&o_copy, " ");  /* Version         */
+	if (!o_copy) { 
+		ast_log(LOG_WARNING, "SDP sytax error in o= line\n");
+		return -1;
+	}
+	if (!sscanf(token, "%d", &rua_version)) {
+		ast_log(LOG_WARNING, "SDP sytax error in o= line version\n");
+		return -1;
+	}
+
+	if (p->sessionversion_remote < 0 || p->sessionversion_remote != rua_version) {
+ 		p->sessionversion_remote = rua_version;
+		p->session_modify = TRUE;
+	} else if (p->sessionversion_remote == rua_version) {
+		p->session_modify = FALSE;
+		ast_debug(2, "SDP version number same as previous SDP\n");
+		return 0;
+	} 
+
 	/* Try to find first media stream */
 	m = get_sdp(req, "m");
 	destiterator = req->sdp_start;
@@ -6431,6 +6563,17 @@
 		add_header(resp, "User-Agent", global_useragent);
 	add_header(resp, "Allow", ALLOWED_METHODS);
 	add_header(resp, "Supported", SUPPORTED_EXTENSIONS);
+
+	/* Add Session-Timers related headers if the feature is active for this session */
+	if(p->st_active == TRUE && p->st_active_peer_ua == TRUE) {
+		char se_hdr[256];
+		int se_len = 0;
+		se_len = sprintf (&se_hdr[se_len], "%d;refresher=%s", p->st_interval, strefresher2str(p->st_ref));
+		se_hdr[se_len] = '\0';
+		add_header(resp, "Require", "timer");
+		add_header(resp, "Session-Expires", se_hdr);
+	}
+
 	if (msg[0] == '2' && (p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER)) {
 		/* For registration responses, we also need expiry and
 		   contact info */
@@ -6562,6 +6705,20 @@
 		ast_string_field_free(p, url);
 	}
 
+	/* Add Session-Timers related headers if the feature is active for this session */
+	if(p->st_active == TRUE && p->st_active_peer_ua == TRUE) {
+		char se_hdr[256];
+		int se_len = 0;
+		se_len = sprintf (&se_hdr[se_len], "%d;refresher=%s", p->st_interval, strefresher2str(p->st_ref));
+		se_hdr[se_len] = '\0';
+		add_header(req, "Require", "timer");
+		add_header(req, "Session-Expires", se_hdr);
+		se_len = 0;
+		se_len = sprintf (&se_hdr[se_len], "%d", st_get_min_se(p));
+		se_hdr[se_len] = '\0';
+		add_header(req, "Min-SE", se_hdr);
+	}
+
 	return 0;
 }
 
@@ -6673,6 +6830,27 @@
 	return send_response(p, &resp, XMIT_UNRELIABLE, 0);
 }
 
+/*! \brief Transmit 422 response with Min-SE header (Session-Timers)  */
+static int transmit_response_with_minse(struct sip_pvt *p, const char *msg, const struct sip_request *req, int minse_int)
+{
+    struct sip_request resp;
+    char minse_str[20];
+    int minse_str_len;
+
+    respprep(&resp, p, msg, req);
+    append_date(&resp);
+
+    minse_str_len = sprintf (&minse_str[0], "%d", minse_int);
+    if (minse_str_len) {
+        minse_str[minse_str_len] = '\0';
+        add_header(&resp, "Min-SE", &minse_str[0]);
+    }
+
+    add_header_contentLength(&resp, 0);
+    return send_response(p, &resp, XMIT_UNRELIABLE, 0);
+}
+
+
 /*! \brief Transmit response, Make sure you get an ACK
 	This is only used for responses to INVITEs, where we need to make sure we get an ACK
 */
@@ -7036,8 +7214,13 @@
 
 #define SDP_SAMPLE_RATE(x) (x == AST_FORMAT_G722) ? 16000 : 8000
 
-/*! \brief Add Session Description Protocol message */
-static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p)
+/*! \brief Add Session Description Protocol message 
+
+    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 len = 0;
 	int alreadysent = 0;
@@ -7087,8 +7270,10 @@
 	if (!p->sessionid) {
 		p->sessionid = (int)ast_random();
 		p->sessionversion = p->sessionid;
-	} else
-		p->sessionversion++;
+	} else {
+		if (oldsdp == FALSE)
+			p->sessionversion++;
+	}
 
 	capability = p->jointcapability;
 
@@ -7350,7 +7535,7 @@
 /*! \brief Used for 200 OK and 183 early media 
 	\return Will return XMIT_ERROR for network errors.
 */
-static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable)
+static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp)
 {
 	struct sip_request resp;
 	int seqno;
@@ -7365,7 +7550,7 @@
 			ast_rtp_codec_setpref(p->rtp, &p->prefs);
 		}
 		try_suggested_sip_codec(p);	
-		add_sdp(&resp, p);
+		add_sdp(&resp, p, oldsdp);
 	} else 
 		ast_log(LOG_ERROR, "Can't add SDP to response, since we have no RTP session allocated. Call-ID %s\n", p->callid);
 	if (reliable && !p->pendinginvite)
@@ -7422,28 +7607,38 @@
 	
 	If t38version is TRUE, we send T38 SDP for re-invite from audio/video to
 	T38 UDPTL transmission on the channel
+
+    If oldsdp is TRUE then the SDP version number is not incremented. This
+    is needed for Session-Timers so we can send a re-invite to refresh the
+    SIP session without modifying the media session. 
 */
-static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version)
+static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp)
 {
-	struct sip_request req;
+    struct sip_request req;
 
-	reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ?  SIP_UPDATE : SIP_INVITE, 0, 1);
-	
-	add_header(&req, "Allow", ALLOWED_METHODS);
-	add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
-	if (sipdebug)
-		add_header(&req, "X-asterisk-Info", "SIP re-invite (External RTP bridge)");
-	if (p->do_history)
-		append_history(p, "ReInv", "Re-invite sent");
-	if (t38version)
-		add_t38_sdp(&req, p);
-	else
-		add_sdp(&req, p);
-	/* Use this as the basis */
-	initialize_initreq(p, &req);
-	p->lastinvite = p->ocseq;
-	ast_set_flag(&p->flags[0], SIP_OUTGOING);		/* Change direction of this dialog */
-	return send_request(p, &req, XMIT_CRITICAL, p->ocseq);
+    reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ?  SIP_UPDATE : SIP_INVITE, 0, 1);
+
+    add_header(&req, "Allow", ALLOWED_METHODS);
+    add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
+    if (sipdebug) {
+        if (oldsdp == TRUE)
+            add_header(&req, "X-asterisk-Info", "SIP re-invite (Session-Timers)");
+        else
+            add_header(&req, "X-asterisk-Info", "SIP re-invite (External RTP bridge)");
+    }
+
+    if (p->do_history)
+        append_history(p, "ReInv", "Re-invite sent");
+    if (t38version)
+        add_t38_sdp(&req, p);
+    else
+        add_sdp(&req, p, oldsdp);
+
+    /* Use this as the basis */
+    initialize_initreq(p, &req);
+    p->lastinvite = p->ocseq;
+    ast_set_flag(&p->flags[0], SIP_OUTGOING);       /* Change direction of this dialog */
+    return send_request(p, &req, XMIT_CRITICAL, p->ocseq);
 }
 
 /* \brief Remove URI parameters at end of URI, not in username part though */
@@ -7739,6 +7934,27 @@
 		add_header(&req, "Require", "replaces");
 	}
 
+	/* Add Session-Timers related headers */
+	if (st_get_mode(p) == SESSION_TIMER_MODE_ORIGINATE) {
+		char i2astr[10];
+		int i2aidx;
+
+		if (!p->st_interval) {
+			p->st_interval = st_get_max_se(p);
+		} 
+
+		p->st_active = TRUE;
+		
+		i2aidx = sprintf (&i2astr[0], "%d", p->st_interval);
+		i2astr[i2aidx] = '\0';
+		add_header(&req, "Session-Expires", i2astr);
+
+		i2aidx = 0;
+		i2aidx = sprintf (&i2astr[0], "%d", st_get_min_se(p));
+		i2astr[i2aidx] = '\0';
+		add_header(&req, "Min-SE", i2astr);
+	}
+
 	add_header(&req, "Allow", ALLOWED_METHODS);
 	add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
 	if (p->options && p->options->addsipheaders && p->owner) {
@@ -7781,7 +7997,7 @@
 			ast_debug(1, "T38 is in state %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
 			add_t38_sdp(&req, p);
 		} else if (p->rtp) 
-			add_sdp(&req, p);
+			add_sdp(&req, p, FALSE);
 	} else {
 		add_header_contentLength(&req, 0);
 	}
@@ -10550,6 +10766,45 @@
 /*! \brief  Report Peer status in character string
  *  \return 0 if peer is unreachable, 1 if peer is online, -1 if unmonitored
  */
+
+
+/* Session-Timer Modes */
+static struct _map_x_s stmodes[] = {
+        { SESSION_TIMER_MODE_ACCEPT,    "Accept"},
+        { SESSION_TIMER_MODE_ORIGINATE, "Originate"},
+        { SESSION_TIMER_MODE_REFUSE,    "Refuse"},
+        { -1,                           NULL},
+};
+
+static const char *stmode2str(enum st_mode m)
+{
+	return map_x_s(stmodes, m, "Unknown");
+}
+
+static enum st_mode str2stmode(const char *s)
+{
+	return map_s_x(stmodes, s, -1);
+}
+
+/* Session-Timer Refreshers */
+static struct _map_x_s strefreshers[] = {
+        { SESSION_TIMER_REFRESHER_AUTO,     "auto"},
+        { SESSION_TIMER_REFRESHER_UAC,      "uac"},
+        { SESSION_TIMER_REFRESHER_UAS,      "uas"},
+        { -1,                               NULL},
+};
+
+static const char *strefresher2str(enum st_refresher r)
+{
+	return map_x_s(strefreshers, r, "Unknown");
+}
+
+static enum st_refresher str2strefresher(const char *s)
+{
+	return map_s_x(strefreshers, s, -1);
+}
+
+
 static int peer_status(struct sip_peer *peer, char *status, int statuslen)
 {
 	int res = 0;
@@ -11285,6 +11540,12 @@
 			for (v = peer->chanvars ; v ; v = v->next)
  				ast_cli(fd, "                 %s = %s\n", v->name, v->value);
 		}
+
+		ast_cli(fd, "  Session-Timers   : %s\n", stmode2str(peer->st_mode_oper));
+		ast_cli(fd, "  Session-Refresher: %s\n", strefresher2str(peer->st_ref));
+		ast_cli(fd, "  Session-Expires  : %d secs\n", peer->st_max_se);
+		ast_cli(fd, "  Min-SE           : %d secs\n", peer->st_min_se);
+
 		ast_cli(fd,"\n");
 		unref_peer(peer);
 	} else  if (peer && type == 1) { /* manager listing */
@@ -11418,7 +11679,13 @@
 			for (v = user->chanvars ; v ; v = v->next)
  				ast_cli(fd, "                 %s = %s\n", v->name, v->value);
 		}
+
+		ast_cli(fd, "  Session-Timers   : %s\n", stmode2str(user->st_mode_oper));
+		ast_cli(fd, "  Session-Refresher: %s\n", strefresher2str(user->st_ref));
+		ast_cli(fd, "  Session-Expires  : %d secs\n", user->st_max_se);
+		ast_cli(fd, "  Min-SE           : %d secs\n", user->st_min_se);
 		ast_cli(fd,"\n");
+
 		unref_user(user);
 	} else {
 		ast_cli(fd,"User %s not found.\n", argv[3]);
@@ -11613,6 +11880,10 @@
 	ast_cli(fd, "  Auto-Framing:           %s\n", cli_yesno(global_autoframing));
 	ast_cli(fd, "  Outb. proxy:            %s %s\n", ast_strlen_zero(global_outboundproxy.name) ? "<not set>" : global_outboundproxy.name,
 							global_outboundproxy.force ? "(forced)" : "");
+	ast_cli(fd, "  Session-Timers:         %s\n", stmode2str(global_st_mode));
+	ast_cli(fd, "  Session-Refresher:      %s\n", strefresher2str (global_st_refresher));
+	ast_cli(fd, "  Session-Expires:        %d secs\n", global_max_se);
+	ast_cli(fd, "  Min-SE:                 %d secs\n", global_min_se);
 
 	ast_cli(fd, "\nDefault Settings:\n");
 	ast_cli(fd, "-----------------\n");
@@ -11979,8 +12250,19 @@
 					if (cur->sipoptions & sip_options[x].id)
 						ast_cli(fd, "%s ", sip_options[x].text);
 				}
+				ast_cli(fd, "\n");
 			} else
 				ast_cli(fd, "(none)\n");
+
+			ast_cli(fd, "  Session-Timer:          %s\n", cur->st_active ? "Active" : "Inactive");
+			if (cur->st_active == TRUE) {
+				ast_cli(fd, "  Session-Timer Interval:  %d\n", cur->st_interval);
+				ast_cli(fd, "  Session-Timer Refresher: %s\n", strefresher2str(cur->st_ref));
+				ast_cli(fd, "  Session-Timer Expirys:   %d\n", cur->st_expirys);
+				ast_cli(fd, "  Session-Timer Sched Id:  %d\n", cur->st_schedid);
+				ast_cli(fd, "  Session-Timer Peer Sts:  %s\n", cur->st_active_peer_ua ? "Active" : "Inactive");
+			}
+
 			ast_cli(fd, "\n\n");
 			found++;
 		}
@@ -12953,7 +13235,7 @@
 	} else if (ast_test_flag(&p->flags[0], SIP_NEEDREINVITE)) {
 		ast_debug(2, "Sending pending reinvite on '%s'\n", p->callid);
 		/* Didn't get to reinvite yet, so do it now */
-		transmit_reinvite_with_sdp(p, FALSE);
+		transmit_reinvite_with_sdp(p, FALSE, FALSE);
 		ast_clear_flag(&p->flags[0], SIP_NEEDREINVITE);	
 	}
 }
@@ -12966,6 +13248,8 @@
 	int xmitres = 0;
 	int reinvite = (p->owner && p->owner->_state == AST_STATE_UP);
 	struct ast_channel *bridgepeer = NULL;
+	char *p_hdrval;
+	int rtn;
 	
 	if (reinvite)
 		ast_debug(4, "SIP response %d to RE-invite on %s call %s\n", resp, outgoing ? "outgoing" : "incoming", p->callid);
@@ -13136,6 +13420,38 @@
 			if (!req->ignore)
 				ast_set_flag(&p->flags[0], SIP_PENDINGBYE);	
 		}
+
+		/* Check for Session-Timers related headers */
+		if (st_get_mode(p) != SESSION_TIMER_MODE_REFUSE && p->outgoing_call == TRUE && !reinvite) {
+			p_hdrval = (char*)get_header(req, "Session-Expires");
+        		if (!ast_strlen_zero(p_hdrval)) {
+				/* UAS supports Session-Timers */
+				enum st_refresher tmp_st_ref = SESSION_TIMER_REFRESHER_AUTO;
+				int tmp_st_interval = 0;
+				rtn = parse_session_expires(p_hdrval, &tmp_st_interval, &tmp_st_ref);
+				if (rtn != 0) {
+					ast_set_flag(&p->flags[0], SIP_PENDINGBYE);	
+				}
+				if (tmp_st_ref == SESSION_TIMER_REFRESHER_UAC || 
+					tmp_st_ref == SESSION_TIMER_REFRESHER_UAS) {
+					p->st_ref = tmp_st_ref;
+				} 
+				if (tmp_st_interval) {
+					p->st_interval = tmp_st_interval;
+				}
+				p->st_active = TRUE;
+				p->st_active_peer_ua = TRUE;
+			} else {
+				/* UAS doesn't support Session-Timers */
+				if (st_get_mode(p) == SESSION_TIMER_MODE_ORIGINATE) {
+					p->st_ref = SESSION_TIMER_REFRESHER_UAC;
+					p->st_active_peer_ua = FALSE;
+				}
+			}
+			start_session_timer(p);
+		}
+
+
 		/* If I understand this right, the branch is different for a non-200 ACK only */
 		p->invitestate = INV_TERMINATED;
 		xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, TRUE);
@@ -13190,6 +13506,13 @@
 			ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
 		sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
 		break;
+
+	case 422: /* Session-Timers: Session interval too small */
+		xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+		ast_string_field_free(p, theirtag);	
+		proc_422_rsp(p, req);
+		break;
+
 	case 487: /* Cancelled transaction */
 		/* We have sent CANCEL on an outbound INVITE 
 			This transaction is already scheduled to be killed by sip_hangup().
@@ -13662,6 +13985,13 @@
 				p->needdestroy = 1;
 			}
 			break;
+
+		case 422: /* Session-Timers: Session Interval Too Small */
+			if (sipmethod == SIP_INVITE) {
+				handle_response_invite(p, resp, rest, req, seqno);
+			}
+			break;
+
 		case 481: /* Call leg does not exist */
 			if (sipmethod == SIP_INVITE) {
 				handle_response_invite(p, resp, rest, req, seqno);
@@ -14360,7 +14690,7 @@
 		/* We should answer something here. If we are here, the
 			call we are replacing exists, so an accepted 
 			can't harm */
-		transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE);
+		transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE, FALSE);
 		/* Do something more clever here */
 		ast_channel_unlock(c);
 		sip_pvt_unlock(p->refer->refer_call);
@@ -14394,7 +14724,7 @@
 	   Targetcall is not touched by the masq */
 
 	/* Answer the incoming call and set channel to UP state */
-	transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE);
+	transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE, FALSE);
 		
 	ast_setstate(c, AST_STATE_UP);
 	
@@ -14490,7 +14820,18 @@
 	unsigned int required_profile = 0;
 	struct ast_channel *c = NULL;		/* New channel */
 	int reinvite = 0;
+	int rtn;
 
+	const char *p_uac_se_hdr;       /* UAC's Session-Expires header string                      */
+	const char *p_uac_min_se;       /* UAC's requested Min-SE interval (char string)            */
+	int uac_max_se = -1;            /* UAC's Session-Expires in integer format                  */
+	int uac_min_se = -1;            /* UAC's Min-SE in integer format                           */
+	int st_active = FALSE;          /* Session-Timer on/off boolean                             */
+	int st_interval = 0;            /* Session-Timer negotiated refresh interval                */
+	enum st_refresher st_ref;       /* Session-Timer session refresher                          */
+	int dlg_min_se = -1;
+	st_ref = SESSION_TIMER_REFRESHER_AUTO;
+
 	/* Find out what they support */
 	if (!p->sipoptions) {
 		const char *supported = get_header(req, "Supported");
@@ -14502,8 +14843,8 @@
 	required = get_header(req, "Require");
 	if (!ast_strlen_zero(required)) {
 		required_profile = parse_sip_options(NULL, required);
-		if (required_profile && required_profile != SIP_OPT_REPLACES) {
-			/* At this point we only support REPLACES */
+		if (required_profile && required_profile != SIP_OPT_REPLACES && required_profile != SIP_OPT_TIMER) {
+			/* At this point we only support REPLACES and Session-Timer */
 			transmit_response_with_unsupported(p, "420 Bad extension (unsupported)", req, required);
 			ast_log(LOG_WARNING,"Received SIP INVITE with unsupported required extension: %s\n", required);
 			p->invitestate = INV_COMPLETED;
@@ -14513,6 +14854,11 @@
 		}
 	}
 
+	/* The option tags may be present in Supported: or Require: headers.
+	Include the Require: option tags for further processing as well */
+	p->sipoptions |= required_profile;
+	p->reqsipoptions = required_profile;
+
 	/* Check if this is a loop */
 	if (ast_test_flag(&p->flags[0], SIP_OUTGOING) && p->owner && (p->owner->_state != AST_STATE_UP)) {
 		/* This is a call to ourself.  Send ourselves an error code and stop
@@ -14639,7 +14985,6 @@
 		}
 	}
 
-
 	/* Check if this is an INVITE that sets up a new dialog or
 	   a re-invite in an existing dialog */
 
@@ -14760,6 +15105,7 @@
 			sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
 			return 0;
 		} else {
+
 			/* If no extension was specified, use the s one */
 			/* Basically for calling to IP/Host name only */
 			if (ast_strlen_zero(p->exten))
@@ -14786,10 +15132,137 @@
 			else
 				ast_debug(2, "Got a SIP re-transmit of INVITE for call %s\n", p->callid);
 		}
+
 		reinvite = 1;
 		c = p->owner;
 	}
 
+	/* Session-Timers */
+	if (p->sipoptions == SIP_OPT_TIMER) {
+		/* The UAC has requested session-timers for this session. Negotiate
+		the session refresh interval and who will be the refresher */
+		ast_debug(2, "Incoming INVITE with 'timer' option enabled\n");
+
+		/* Parse the Session-Expires header */
+		p_uac_se_hdr = get_header(req, "Session-Expires");
+		if (!ast_strlen_zero(p_uac_se_hdr)) {
+			rtn = parse_session_expires(p_uac_se_hdr, &uac_max_se, &st_ref);
+			if (rtn != 0) {
+				transmit_response(p, "400 Session-Expires Invalid Syntax", req);
+				p->invitestate = INV_COMPLETED;
+				if (!p->lastinvite) {
+					sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+				}
+				return -1;
+			}
+		}
+
+		/* Parse the Min-SE header */
+		p_uac_min_se = get_header(req, "Min-SE");
+		if (!ast_strlen_zero(p_uac_min_se)) {
+			rtn = parse_minse(p_uac_min_se, &uac_min_se); 
+			if (rtn != 0) {
+        			transmit_response(p, "400 Min-SE Invalid Syntax", req);
+       	   			p->invitestate = INV_COMPLETED;
+       	   			if (!p->lastinvite) {
+					sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+				}
+				return -1;
+			}
+		}
+
+		dlg_min_se = st_get_min_se(p);
+		switch (st_get_mode(p)) {
+			case SESSION_TIMER_MODE_ACCEPT:
+			case SESSION_TIMER_MODE_ORIGINATE:
+				if (uac_max_se > 0 && uac_max_se < dlg_min_se) {
+					transmit_response_with_minse(p, "422 Session Interval Too Small", req, dlg_min_se);
+					p->invitestate = INV_COMPLETED;
+					if (!p->lastinvite) {
+						sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+					}
+					return -1;
+				}
+
+				p->st_active_peer_ua = TRUE;
+				st_active = TRUE;
+				if (st_ref == SESSION_TIMER_REFRESHER_AUTO) {
+					st_ref = st_get_refresher(p);
+				}
+
+				if (uac_max_se > 0) {
+					int dlg_max_se = st_get_max_se(p);
+					if (dlg_max_se >= uac_min_se) {
+						st_interval = (uac_max_se < dlg_max_se) ? uac_max_se : dlg_max_se;
+					} else {
+						st_interval = uac_max_se;
+					}
+				} else {
+					st_interval = uac_min_se;
+				}
+				break;
+
+			case SESSION_TIMER_MODE_REFUSE:
+				if (p->reqsipoptions == SIP_OPT_TIMER) {
+					transmit_response_with_unsupported(p, "420 Option Disabled", req, required);
+					ast_log(LOG_WARNING,"Received SIP INVITE with supported but disabled option: %s\n", required);
+					p->invitestate = INV_COMPLETED;
+					if (!p->lastinvite) {
+						sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+					}
+					return -1;
+				}
+				break;
+
+			default:
+				ast_log(LOG_ERROR, "Internal Error %d at %s:%d\n", st_get_mode(p), __FILE__, __LINE__);
+				break;
+		}
+	} else {
+		/* The UAC did not request session-timers.  Asterisk (UAS), will now decide
+		(based on session-timer-mode in sip.conf) whether to run session-timers for
+		this session or not. */
+		p->st_active_peer_ua = FALSE;
+		switch (st_get_mode(p)) {
+			case SESSION_TIMER_MODE_ORIGINATE:
+				st_active = TRUE;
+				st_interval = st_get_max_se(p);
+				st_ref = SESSION_TIMER_REFRESHER_UAS;
+				break;
+
+			default:
+				break;
+		}
+	}
+
+	if (reinvite == 0) {
+		/* Session-Timers: Start session refresh timer based on negotiation/config */
+		if (st_active == TRUE) {
+			p->st_active   = TRUE;
+			p->st_interval = st_interval;
+			p->st_ref      = st_ref;
+			start_session_timer(p);
+		}
+	} else {
+		if (p->st_active == TRUE) {
+			/* Session-Timers:  A re-invite request sent within a dialog will serve as 
+			a refresh request, no matter whether the re-invite was sent for refreshing 
+			the session or modifying it.*/
+			ast_debug (2, "Restarting session-timers on a refresh - %s\n", p->callid);
+
+			/* The UAC may be adjusting the session-timers mid-session */
+			if (st_interval > 0) {
+				p->st_interval = st_interval;
+				p->st_ref      = st_ref;
+			}
+
+			restart_session_timer(p);
+			if (p->st_expirys > 0) {
+				p->st_expirys--;
+			}
+		}
+	}
+
 	if (!req->ignore && p)
 		p->lastinvite = seqno;
 
@@ -14954,9 +15427,10 @@
 					}
 				} 
 				/* Respond to normal re-invite */
-				if (sendok)
+				if (sendok) {
 					/* If this is not a re-invite or something to ignore - it's critical */
-					transmit_response_with_sdp(p, "200 OK", req, (reinvite || req->ignore) ?  XMIT_UNRELIABLE : XMIT_CRITICAL);
+					transmit_response_with_sdp(p, "200 OK", req, (reinvite || req->ignore) ?  XMIT_UNRELIABLE : XMIT_CRITICAL, p->session_modify == TRUE ? FALSE:TRUE); 
+				}
 			}
 			p->invitestate = INV_TERMINATED;
 			break;
@@ -15576,7 +16050,9 @@
 	}
 
 	stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
+    stop_session_timer(p); /* Stop Session-Timer */
 
+
 	if (!ast_strlen_zero(get_header(req, "Also"))) {
 		ast_log(LOG_NOTICE, "Client '%s' using deprecated BYE/Also transfer method.  Ask vendor to support REFER instead\n",
 			ast_inet_ntoa(p->recv.sin_addr));
@@ -16557,6 +17033,263 @@
 	return 0;
 }
 
+
+/*! \brief Session-Timers: Restart session timer */
+static void restart_session_timer (struct sip_pvt *p)
+{
+	if (p->st_active == TRUE) {
+		if (ast_sched_del(sched, p->st_schedid) != 0) {
+			ast_log(LOG_WARNING, "ast_sched_del failed: %d - %s\n", p->st_schedid, p->callid);
+		}
+
+		ast_debug(2, "Session timer stopped: %d - %s\n", p->st_schedid, p->callid);
+		start_session_timer(p);
+	}
+}
+
+
+/*! \brief Session-Timers: Stop session timer */
+static void stop_session_timer (struct sip_pvt *p)
+{
+	if (p->st_active == TRUE) {
+		p->st_active = FALSE;
+		ast_sched_del(sched, p->st_schedid);
+		ast_debug(2, "Session timer stopped: %d - %s\n", p->st_schedid, p->callid);
+	}
+}
+
+
+/*! \brief Session-Timers: Start session timer */
+static void start_session_timer (struct sip_pvt *p)
+{
+	p->st_schedid  = ast_sched_add(sched, p->st_interval*1000/2, proc_session_timer, p);
+	if (p->st_schedid < 0) {
+		ast_log(LOG_ERROR, "ast_sched_add failed.\n");
+	}
+	ast_debug(2, "Session timer started: %d - %s\n", p->st_schedid, p->callid);
+}
+
+
+/*! \brief Session-Timers: Process session refresh timeout event */
+static int proc_session_timer (void *vp)
+{
+	struct sip_pvt *p = (struct sip_pvt*) vp;
+	int sendreinv = FALSE;
+
+	ast_debug(2, "Session timer expired: %d - %s\n", p->st_schedid, p->callid);
+
+	if (!p->owner) {
+		if (p->st_active == TRUE) {
+			stop_session_timer(p);
+		}
+		return 0;
+	}
+
+	if ((p->st_active != TRUE) || (p->owner->_state != AST_STATE_UP)) {
+		return 0;
+	}
+
+	switch (p->st_ref) {
+		case SESSION_TIMER_REFRESHER_UAC:
+			if (p->outgoing_call == TRUE) {
+	  			sendreinv = TRUE;
+			}
+			break;
+
+		case SESSION_TIMER_REFRESHER_UAS:
+			if (p->outgoing_call != TRUE) {
+	  			sendreinv = TRUE;
+			}
+			break;
+
+		default:
+			ast_log(LOG_ERROR, "Unknown session refresher %d\n", p->st_ref);
+			return -1;
+	}
+
+	if (sendreinv == TRUE) {
+		transmit_reinvite_with_sdp(p, FALSE, TRUE);
+	} else {
+		p->st_expirys++;
+		if (p->st_expirys >= 2) {
+			ast_log(LOG_WARNING, "Session-Timer expired - %s\n", p->callid);
+			stop_session_timer(p);
+
+			while (p->owner && ast_channel_trylock(p->owner)) {
+				sip_pvt_unlock(p);
+				usleep(1);
+				sip_pvt_lock(p);
+          		}
+
+           		ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV);
+           		ast_channel_unlock(p->owner);
+		}
+	}
+	return 1;
+}
+
+
+/* Session-Timers: Function for parsing Min-SE header */
+int parse_minse (const char *p_hdrval, int *const p_interval)
+{
+	if (ast_strlen_zero(p_hdrval)) {
+		ast_log(LOG_WARNING, "Null Min-SE header\n");
+		return -1;
+	}
+
+	*p_interval = 0;
+	p_hdrval = ast_skip_blanks(p_hdrval);
+	if (!sscanf(p_hdrval, "%d", p_interval)) {
+		ast_log(LOG_WARNING, "Parsing of Min-SE header failed %s\n", p_hdrval);
+		return -1;
+	}
+
+	ast_debug(2, "Received Min-SE: %d\n", *p_interval);
+	return 0;
+}
+
+
+/* Session-Timers: Function for parsing Session-Expires header */
+int parse_session_expires (const char *p_hdrval, int *const p_interval, enum st_refresher *const p_ref)
+{
+	char *p_token;
+	int  ref_idx;
+	char *p_se_hdr;
+
+	if (ast_strlen_zero(p_hdrval)) {
+		ast_log(LOG_WARNING, "Null Session-Expires header\n");
+		return -1;
+	}
+
+	*p_ref = SESSION_TIMER_REFRESHER_AUTO;
+	*p_interval = 0;
+
+	p_se_hdr = ast_strdupa(p_hdrval);
+	p_se_hdr = ast_skip_blanks(p_se_hdr);
+
+	while ((p_token = strsep(&p_se_hdr, ";"))) {
+		p_token = ast_skip_blanks(p_token);
+		if (!sscanf(p_token, "%d", p_interval)) {
+			ast_log(LOG_WARNING, "Parsing of Session-Expires failed\n");
+			return -1;
+		}
+
+		ast_debug(2, "Session-Expires: %d\n", *p_interval);
+
+		if (p_se_hdr) {
+			ref_idx = strlen ("refresher=");
+			if(!strncasecmp(p_se_hdr, "refresher=", ref_idx)) {
+				p_se_hdr += ref_idx;
+				p_se_hdr = ast_skip_blanks(p_se_hdr);
+
+				if (!strncasecmp(p_se_hdr, "uac", strlen("uac"))) {
+					*p_ref = SESSION_TIMER_REFRESHER_UAC;
+					ast_debug(2, "Refresher: UAC\n");
+				} else if (!strncasecmp(p_se_hdr, "uas", strlen("uas"))) {
+					*p_ref = SESSION_TIMER_REFRESHER_UAS;
+					ast_debug(2, "Refresher: UAS\n");
+				} else {
+					ast_log(LOG_WARNING, "Invalid refresher value %s\n", p_se_hdr);
+					return -1;
+				}
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+
+static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp)
+{
+	int rtn;
+	const char *p_hdrval;
+	int minse;
+
+	p_hdrval = get_header(rsp, "Min-SE");
+	if (!ast_strlen_zero(p_hdrval)) {
+		rtn = parse_minse(p_hdrval, &minse);
+		if (rtn != 0) {
+			ast_log(LOG_WARNING, "Parsing of Min-SE header failed %s\n", p_hdrval);
+			return;
+		}
+		p->st_interval = minse;
+		transmit_invite(p, SIP_INVITE, 1, 2); 
+	} else {
+		ast_log(LOG_WARNING, "422 response without a Min-SE header %s\n", p_hdrval);
+	}
+}
+
+
+int st_get_max_se(struct sip_pvt *p)
+{
+	if (p->username) {
+		struct sip_user *up = find_user(p->username, 1);
+		if (up) {
+			return up->st_max_se;
+		}
+	} 
+	if (p->peername) {
+		struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+		if (pp) {
+			return pp->st_max_se;
+		}
+	}
+	return global_max_se;
+}
+
+int st_get_min_se(struct sip_pvt *p)
+{
+	if (p->username) {
+		struct sip_user *up = find_user(p->username, 1);
+		if (up) {
+			return up->st_min_se;
+		}
+	} 
+	if (p->peername) {
+		struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+		if (pp) {
+			return pp->st_min_se;
+		}
+	}
+	return global_min_se;
+}
+
+enum st_refresher st_get_refresher(struct sip_pvt *p)
+{
+	if (p->username) {
+		struct sip_user *up = find_user(p->username, 1);
+		if (up) {
+			return up->st_ref;
+		}
+	} 
+	if (p->peername) {
+		struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+		if (pp) {
+			return pp->st_ref;
+		}
+	}
+	return global_st_refresher;
+}
+
+enum st_mode st_get_mode(struct sip_pvt *p)
+{
+	if (p->username) {
+		struct sip_user *up = find_user(p->username, 1);
+		if (up) {
+			return up->st_mode_oper;
+		}
+	} 
+	if (p->peername) {
+		struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+		if (pp) {
+			return pp->st_mode_oper;
+		}
+	}
+	return global_st_mode;
+}
+
+
 /*! \brief React to lack of answer to Qualify poke */
 static int sip_poke_noanswer(void *data)
 {
@@ -17173,6 +17906,11 @@
 	user->maxcallbitrate = default_maxcallbitrate;
 	user->autoframing = global_autoframing;
 	user->prefs = default_prefs;
+	user->st_mode_oper = global_st_mode;	/* Session-Timers */
+	user->st_ref = global_st_refresher;
+	user->st_min_se = global_min_se;
+	user->st_max_se = global_max_se;
+
 	/* set default context */
 	strcpy(user->context, default_context);
 	strcpy(user->language, default_language);
@@ -17248,7 +17986,38 @@
 			user->maxcallbitrate = atoi(v->value);
 			if (user->maxcallbitrate < 0)
 				user->maxcallbitrate = default_maxcallbitrate;
+		} else if (!strcasecmp(v->name, "session-timers")) {
+			int i = (int) str2stmode(v->value); 
+			if (i < 0) {
+				ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config);
+				user->st_mode_oper = global_st_mode;
+			} else {
+				user->st_mode_oper = i;
+			}
+		} else if (!strcasecmp(v->name, "session-expires")) {
+			if (sscanf(v->value, "%d", &user->st_max_se) != 1) {
+				ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config);
+				user->st_max_se = global_max_se;
+			} 
+		} else if (!strcasecmp(v->name, "session-minse")) {
+			if (sscanf(v->value, "%d", &user->st_min_se) != 1) {
+				ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config);
+				user->st_min_se = global_min_se;
+			} 
+			if (user->st_min_se < 90) {
+				ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < 90 secs\n", v->value, v->lineno, config);
+				user->st_min_se = global_min_se;
+			} 
+		} else if (!strcasecmp(v->name, "session-refresher")) {
+			int i = (int) str2strefresher(v->value); 
+			if (i < 0) {
+				ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config);
+				user->st_ref = global_st_refresher;
+			} else {
+				user->st_ref = i;
+			}
 		}
+
 		/* We can't just report unknown options here because this may be a
 		 * type=friend entry.  All user options are valid for a peer, but not
 		 * the other way around.  */
@@ -17300,6 +18069,11 @@
 	peer->pickupgroup = 0;
 	peer->maxms = default_qualify;
 	peer->prefs = default_prefs;
+	peer->st_mode_oper = global_st_mode;	/* Session-Timers */
+	peer->st_ref = global_st_refresher;
+	peer->st_min_se = global_min_se;
+	peer->st_max_se = global_max_se;
+
 	clear_peer_mailboxes(peer);
 }
 
@@ -17586,6 +18360,36 @@
 			peer->maxcallbitrate = atoi(v->value);
 			if (peer->maxcallbitrate < 0)
 				peer->maxcallbitrate = default_maxcallbitrate;
+		} else if (!strcasecmp(v->name, "session-timers")) {
+			int i = (int) str2stmode(v->value); 
+			if (i < 0) {
+				ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config);
+				peer->st_mode_oper = global_st_mode;
+			} else {
+				peer->st_mode_oper = i;
+			}
+		} else if (!strcasecmp(v->name, "session-expires")) {
+			if (sscanf(v->value, "%d", &peer->st_max_se) != 1) {
+				ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config);
+				peer->st_max_se = global_max_se;
+			} 
+		} else if (!strcasecmp(v->name, "session-minse")) {
+			if (sscanf(v->value, "%d", &peer->st_min_se) != 1) {
+				ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config);
+				peer->st_min_se = global_min_se;
+			} 
+			if (peer->st_min_se < 90) {
+				ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < 90 secs\n", v->value, v->lineno, config);
+				peer->st_min_se = global_min_se;
+			} 
+		} else if (!strcasecmp(v->name, "session-refresher")) {
+			int i = (int) str2strefresher(v->value); 
+			if (i < 0) {
+				ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config);
+				peer->st_ref = global_st_refresher;
+			} else {
+				peer->st_ref = i;
+			}
 		}
 	}
 	if (!sip_cfg.ignore_regexpire && peer->host_dynamic && realtime) {
@@ -17765,6 +18569,11 @@
 	ast_set_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP);		/* Default for peers, users: TRUE */
 	sip_cfg.peer_rtupdate = TRUE;
 
+	global_st_mode = SESSION_TIMER_MODE_ACCEPT;    /* Session-Timers */
+	global_st_refresher = SESSION_TIMER_REFRESHER_UAS;
+	global_min_se  = DEFAULT_MIN_SE;
+	global_max_se  = DEFAULT_MAX_SE;
+
 	/* Initialize some reasonable defaults at SIP reload (used both for channel and as default for peers and users */
 	ast_copy_string(default_context, DEFAULT_CONTEXT, sizeof(default_context));
 	default_subscribecontext[0] = '\0';
@@ -18047,6 +18856,36 @@
 				default_maxcallbitrate = DEFAULT_MAX_CALL_BITRATE;
 		} else if (!strcasecmp(v->name, "matchexterniplocally")) {
 			global_matchexterniplocally = ast_true(v->value);
+		} else if (!strcasecmp(v->name, "session-timers")) {
+			int i = (int) str2stmode(v->value); 
+			if (i < 0) {
+				ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config);
+				global_st_mode = SESSION_TIMER_MODE_ACCEPT;
+			} else {
+				global_st_mode = i;
+			}
+		} else if (!strcasecmp(v->name, "session-expires")) {
+			if (sscanf(v->value, "%d", &global_max_se) != 1) {
+				ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config);
+				global_max_se = DEFAULT_MAX_SE;
+			} 
+		} else if (!strcasecmp(v->name, "session-minse")) {
+			if (sscanf(v->value, "%d", &global_min_se) != 1) {
+				ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config);
+				global_min_se = DEFAULT_MIN_SE;
+			} 
+			if (global_min_se < 90) {
+				ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < 90 secs\n", v->value, v->lineno, config);
+				global_min_se = DEFAULT_MIN_SE;
+			} 
+		} else if (!strcasecmp(v->name, "session-refresher")) {
+			int i = (int) str2strefresher(v->value); 
+			if (i < 0) {
+				ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config);
+				global_st_refresher = SESSION_TIMER_REFRESHER_UAS;
+			} else {
+				global_st_refresher = i;
+			}
 		}
 	}
 
@@ -18274,7 +19113,7 @@
 	if (!ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
 		if (!p->pendinginvite) {
 			ast_debug(3, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(udptl ? p->udptlredirip.sin_addr : p->ourip.sin_addr), udptl ? ntohs(p->udptlredirip.sin_port) : 0);
-			transmit_reinvite_with_sdp(p, TRUE);
+			transmit_reinvite_with_sdp(p, TRUE, FALSE);
 		} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
 			ast_debug(3, "Deferring reinvite on SIP '%s' - It's UDPTL will be redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(udptl ? p->udptlredirip.sin_addr : p->ourip.sin_addr), udptl ? ntohs(p->udptlredirip.sin_port) : 0);
 			ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
@@ -18329,7 +19168,7 @@
 					ast_debug(3, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(p->udptlredirip.sin_addr), ntohs(p->udptlredirip.sin_port));
 				else
 					ast_debug(3, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to us (IP %s)\n", p->callid, ast_inet_ntoa(p->ourip.sin_addr));
-				transmit_reinvite_with_sdp(p, TRUE);
+				transmit_reinvite_with_sdp(p, TRUE, FALSE);
 			} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
 				if (flag)
 					ast_debug(3, "Deferring reinvite on SIP '%s' - It's UDPTL will be redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(p->udptlredirip.sin_addr), ntohs(p->udptlredirip.sin_port));
@@ -18509,7 +19348,7 @@
 			ast_debug(1, "Early remote bridge setting SIP '%s' - Sending media to %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip.sin_addr));
 		} else if (!p->pendinginvite) {		/* We are up, and have no outstanding invite */
 			ast_debug(3, "Sending reinvite on SIP '%s' - It's audio soon redirected to IP %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip.sin_addr));
-			transmit_reinvite_with_sdp(p, FALSE);
+			transmit_reinvite_with_sdp(p, FALSE, FALSE);
 		} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
 			ast_debug(3, "Deferring reinvite on SIP '%s' - It's audio will be redirected to IP %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip.sin_addr));
 			/* We have a pending Invite. Send re-invite when we're done with the invite */
