Asterisk - The Open Source Telephony Project  21.4.1
pjsip_session_reason_header.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2023, Sangoma Technologies Corporation
5  *
6  * George Joseph <gjoseph@sangoma.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 #include "asterisk.h"
20 #include "asterisk/res_pjsip_session.h"
21 #include "asterisk/utils.h"
22 #include "pjsip_session.h"
23 
24 static const pj_str_t reason_hdr_str = { "Reason", 6};
25 
27  char *protocol;
28  int response_code;
29  char *response_str;
30  int already_sent;
31 };
32 
33 static void return_reason_destructor(void *obj)
34 {
35  struct return_reason_data *rr = obj;
36  SCOPE_ENTER(3, "Destroying RR");
37  ast_free(rr->protocol);
38  ast_free(rr->response_str);
39  ast_free(rr);
40  SCOPE_EXIT("Done");
41 }
42 
43 #define RETURN_REASON_DATASTORE_NAME "pjsip_session_return_reason"
44 static struct ast_datastore_info return_reason_info = {
45  .type = RETURN_REASON_DATASTORE_NAME,
46  .destroy = return_reason_destructor,
47 };
48 
49 static void reason_header_outgoing_response(struct ast_sip_session *session,
50  struct pjsip_tx_data *tdata)
51 {
52  RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
53  pjsip_generic_string_hdr *reason_hdr;
54  pj_str_t reason_val;
55  RAII_VAR(char *, reason_str, NULL, ast_free);
56  struct return_reason_data *rr = NULL;
57  int rc = 0;
58  struct pjsip_status_line status = tdata->msg->line.status;
59  const char *tag = ast_sip_session_get_name(session);
60  SCOPE_ENTER(3, "%s: Response Code: %d\n", tag,
61  status.code);
62 
63  /*
64  * Include the Reason header if this is a provisional
65  * response other than a 100 OR it's a 200.
66  */
67  if (!((PJSIP_IS_STATUS_IN_CLASS(status.code, 100) && status.code != 100) || status.code == 200)) {
68  SCOPE_EXIT_RTN("%s: RC %d not eligible for Reason header\n", tag, status.code);
69  }
70 
71  datastore = ast_sip_session_get_datastore(session, RETURN_REASON_DATASTORE_NAME);
72  if (!datastore) {
73  SCOPE_EXIT_RTN("%s: No datastore on session. Nothing to do\n", tag);
74  }
75  rr = datastore->data;
76 
77  rc = ast_asprintf(&reason_str, "%s; cause=%d; text=\"%s\"",
78  rr->protocol, rr->response_code, rr->response_str);
79  if (rc < 0) {
80  ast_sip_session_remove_datastore(session, RETURN_REASON_DATASTORE_NAME);
81  SCOPE_EXIT_RTN("%s: Failed to create reason string\n", tag);
82  }
83  reason_val = pj_str(reason_str);
84 
85  /*
86  * pjproject re-uses the tdata for a transaction so if we've
87  * already sent the Reason header, it'll get sent again unless
88  * we remove it. It's possible something else is sending a Reason
89  * header so we need to ensure we only remove our own.
90  */
91  if (rr->already_sent) {
92  ast_trace(3, "%s: Reason already sent\n", tag);
93  reason_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &reason_hdr_str, NULL);
94  while (reason_hdr) {
95  ast_trace(3, "%s: Checking old reason: <" PJSTR_PRINTF_SPEC "> - <" PJSTR_PRINTF_SPEC "> \n",
96  tag,
97  PJSTR_PRINTF_VAR(reason_hdr->hvalue), PJSTR_PRINTF_VAR(reason_val));
98  if (pj_strcmp(&reason_hdr->hvalue, &reason_val) == 0) {
99  ast_trace(3, "%s: MATCH. Cleaning up old reason\n", tag);
100  pj_list_erase(reason_hdr);
101  break;
102  }
103  reason_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &reason_hdr_str, reason_hdr->next);
104  }
105  ast_sip_session_remove_datastore(session, RETURN_REASON_DATASTORE_NAME);
106  SCOPE_EXIT_RTN("%s: Done\n", tag);
107  }
108 
109  reason_hdr = pjsip_generic_string_hdr_create(tdata->pool, &reason_hdr_str, &reason_val);
110  if (reason_hdr) {
111  pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)reason_hdr);
112  rr->already_sent = 1;
113  ast_trace(1, "%s: Created reason header: Reason: %s\n",
114  tag, reason_str);
115  } else {
116  ast_trace(1, "%s: Failed to create reason header: Reason: %s\n",
117  tag, reason_str);
118  }
119 
120  SCOPE_EXIT_RTN("%s: Done\n", tag);
121 }
122 
123 int ast_sip_session_add_reason_header(struct ast_sip_session *session,
124  const char *protocol, int code, const char *text)
125 {
126  struct return_reason_data *rr;
127  RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
128  const char *tag = ast_sip_session_get_name(session);
129  SCOPE_ENTER(4, "%s: Adding Reason header %s %d %s\n",
130  tag, S_OR(protocol,"<missing protocol>"),
131  code, S_OR(text, "<missing text>"));
132 
133  if (ast_strlen_zero(protocol) || !text) {
134  SCOPE_EXIT_RTN_VALUE(-1, "%s: Missing protocol or text\n", tag);
135  }
136  rr = ast_calloc(1, sizeof(*rr));
137  if (!rr) {
138  SCOPE_EXIT_RTN_VALUE(-1, "%s: Failed to allocate datastore\n", tag);
139  }
140  datastore = ast_sip_session_alloc_datastore(
141  &return_reason_info, return_reason_info.type);
142  rr->protocol = ast_strdup(protocol);
143  rr->response_code = code;
144  rr->response_str = ast_strdup(text);
145  datastore->data = rr;
146  if (ast_sip_session_add_datastore(session, datastore) != 0) {
147  SCOPE_EXIT_RTN_VALUE(-1,
148  "%s: Failed to add datastore to session\n", tag);
149  }
150 
151  SCOPE_EXIT_RTN_VALUE(0, "%s: Done\n", tag);
152 }
153 
154 static struct ast_sip_session_supplement reason_header_supplement = {
155  .method = "INVITE",
156  .priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL + 1, /* Run AFTER channel creation */
157  .outgoing_response = reason_header_outgoing_response,
158 };
159 
160 void pjsip_reason_header_unload(void)
161 {
162  ast_sip_session_unregister_supplement(&reason_header_supplement);
163 }
164 
165 void pjsip_reason_header_load(void)
166 {
167  ast_sip_session_register_supplement(&reason_header_supplement);
168 }
const char * type
Definition: datastore.h:32
Asterisk main include file. File version handling, generic pbx functions.
Structure for a data store type.
Definition: datastore.h:31
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
Structure for a data store object.
Definition: datastore.h:64
A structure describing a SIP session.
Utility functions.
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
A supplement to SIP message processing.
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941