Merge "res_pjsip: Add fax_detect_timeout endpoint option."
[asterisk/asterisk.git] / channels / sip / config_parser.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2010, Digium, Inc.
5  *
6  * See http://www.asterisk.org for more information about
7  * the Asterisk project. Please do not directly contact
8  * any of the maintainers of this project for assistance;
9  * the project provides a web site, mailing lists and IRC
10  * channels for your use.
11  *
12  * This program is free software, distributed under the terms of
13  * the GNU General Public License Version 2. See the LICENSE file
14  * at the top of the source tree.
15  */
16
17 /*!
18  * \file
19  * \brief sip config parsing functions and unit tests
20  */
21
22 /*** MODULEINFO
23         <support_level>extended</support_level>
24  ***/
25
26 #include "asterisk.h"
27
28 ASTERISK_REGISTER_FILE()
29
30 #include "include/sip.h"
31 #include "include/config_parser.h"
32 #include "include/sip_utils.h"
33
34 /*! \brief Parse register=> line in sip.conf
35  *
36  * \retval 0 on success
37  * \retval -1 on failure
38  */
39 int sip_parse_register_line(struct sip_registry *reg, int default_expiry, const char *value, int lineno)
40 {
41         int portnum = 0;
42         int domainport = 0;
43         enum ast_transport transport = AST_TRANSPORT_UDP;
44         char buf[256] = "";
45         char *userpart = NULL, *hostpart = NULL;
46         /* register => [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry] */
47         AST_DECLARE_APP_ARGS(pre1,
48                 AST_APP_ARG(peer);
49                 AST_APP_ARG(userpart);
50         );
51         AST_DECLARE_APP_ARGS(pre2,
52                 AST_APP_ARG(transport);
53                 AST_APP_ARG(blank);
54                 AST_APP_ARG(userpart);
55         );
56         AST_DECLARE_APP_ARGS(user1,
57                 AST_APP_ARG(userpart);
58                 AST_APP_ARG(secret);
59                 AST_APP_ARG(authuser);
60         );
61         AST_DECLARE_APP_ARGS(user2,
62                 AST_APP_ARG(user);
63                 AST_APP_ARG(domain);
64         );
65         AST_DECLARE_APP_ARGS(user3,
66                 AST_APP_ARG(authuser);
67                 AST_APP_ARG(domainport);
68         );
69         AST_DECLARE_APP_ARGS(host1,
70                 AST_APP_ARG(hostpart);
71                 AST_APP_ARG(expiry);
72         );
73         AST_DECLARE_APP_ARGS(host2,
74                 AST_APP_ARG(hostpart);
75                 AST_APP_ARG(extension);
76         );
77         AST_DECLARE_APP_ARGS(host3,
78                 AST_APP_ARG(host);
79                 AST_APP_ARG(port);
80         );
81
82         if (!reg) {
83                 return -1;
84         }
85
86         reg->expire = -1;
87         reg->timeout = -1;
88
89         if (!value) {
90                 return -1;
91         }
92
93         ast_copy_string(buf, value, sizeof(buf));
94
95         /*! register => [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry]
96          * becomes
97          *   userpart => [peer?][transport://]user[@domain][:secret[:authuser]]
98          *   hostpart => host[:port][/extension][~expiry]
99          */
100         if ((hostpart = strrchr(buf, '@'))) {
101                 *hostpart++ = '\0';
102                 userpart = buf;
103         }
104
105         if (ast_strlen_zero(userpart) || ast_strlen_zero(hostpart)) {
106                 ast_log(LOG_WARNING, "Format for registration is [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry] at line %d\n", lineno);
107                 return -1;
108         }
109
110         /*!
111          * pre1.peer => peer
112          * pre1.userpart => [transport://]user[@domain][:secret[:authuser]]
113          * hostpart => host[:port][/extension][~expiry]
114          */
115         AST_NONSTANDARD_RAW_ARGS(pre1, userpart, '?');
116         if (ast_strlen_zero(pre1.userpart)) {
117                 pre1.userpart = pre1.peer;
118                 pre1.peer = NULL;
119         }
120
121         /*!
122          * pre1.peer => peer
123          * pre2.transport = transport
124          * pre2.userpart => user[@domain][:secret[:authuser]]
125          * hostpart => host[:port][/extension][~expiry]
126          */
127         AST_NONSTANDARD_RAW_ARGS(pre2, pre1.userpart, '/');
128         if (ast_strlen_zero(pre2.userpart)) {
129                 pre2.userpart = pre2.transport;
130                 pre2.transport = NULL;
131         } else {
132                 pre2.transport[strlen(pre2.transport) - 1] = '\0'; /* Remove trailing : */
133         }
134
135         if (!ast_strlen_zero(pre2.blank)) {
136                 ast_log(LOG_WARNING, "Format for registration is [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry] at line %d\n", lineno);
137                 return -1;
138         }
139
140         /*!
141          * pre1.peer => peer
142          * pre2.transport = transport
143          * user1.userpart => user[@domain]
144          * user1.secret => secret
145          * user1.authuser => authuser
146          * hostpart => host[:port][/extension][~expiry]
147          */
148         AST_NONSTANDARD_RAW_ARGS(user1, pre2.userpart, ':');
149
150         /*!
151          * pre1.peer => peer
152          * pre2.transport = transport
153          * user1.userpart => user[@domain]
154          * user1.secret => secret
155          * user1.authuser => authuser
156          * host1.hostpart => host[:port][/extension]
157          * host1.expiry => [expiry]
158          */
159         AST_NONSTANDARD_RAW_ARGS(host1, hostpart, '~');
160
161         /*!
162          * pre1.peer => peer
163          * pre2.transport = transport
164          * user1.userpart => user[@domain]
165          * user1.secret => secret
166          * user1.authuser => authuser
167          * host2.hostpart => host[:port]
168          * host2.extension => [extension]
169          * host1.expiry => [expiry]
170          */
171         AST_NONSTANDARD_RAW_ARGS(host2, host1.hostpart, '/');
172
173         /*!
174          * pre1.peer => peer
175          * pre2.transport = transport
176          * user1.userpart => user[@domain]
177          * user1.secret => secret
178          * user1.authuser => authuser
179          * host3.host => host
180          * host3.port => port
181          * host2.extension => extension
182          * host1.expiry => expiry
183          */
184         AST_NONSTANDARD_RAW_ARGS(host3, host2.hostpart, ':');
185
186         /*!
187           * pre1.peer => peer
188           * pre2.transport = transport
189           * user2.user => user
190           * user2.domain => domain
191           * user1.secret => secret
192           * user1.authuser => authuser
193           * host3.host => host
194           * host3.port => port
195           * host2.extension => extension
196           * host1.expiry => expiry
197          */
198         AST_NONSTANDARD_RAW_ARGS(user2, user1.userpart, '@');
199
200         /*!
201           * pre1.peer => peer
202           * pre2.transport = transport
203           * user2.user => user
204           * user2.domain => domain
205           * user1.secret => secret
206           * user3.authuser => authuser
207           * user3.domainport => domainport
208           * host3.host => host
209           * host3.port => port
210           * host2.extension => extension
211           * host1.expiry => expiry
212          */
213         AST_NONSTANDARD_RAW_ARGS(user3, user1.authuser, ':');
214
215         /* Reordering needed due to fields being [(:secret[:username])|(:regdomainport:secret:username)]
216            but parsing being [secret[:username[:regdomainport]]] */
217         if (user3.argc == 2) {
218                 char *reorder = user3.domainport;
219                 user3.domainport = user1.secret;
220                 user1.secret = user3.authuser;
221                 user3.authuser = reorder;
222         }
223
224         if (host3.port) {
225                 if (!(portnum = port_str2int(host3.port, 0))) {
226                         ast_log(LOG_NOTICE, "'%s' is not a valid port number on line %d of sip.conf. using default.\n", host3.port, lineno);
227                 }
228         }
229         if (user3.domainport) {
230                 if (!(domainport = port_str2int(user3.domainport, 0))) {
231                         ast_log(LOG_NOTICE, "'%s' is not a valid domain port number on line %d of sip.conf. using default.\n", user3.domainport, lineno);
232                 }
233         }
234
235         /* set transport type */
236         if (!pre2.transport) {
237                 transport = AST_TRANSPORT_UDP;
238         } else if (!strncasecmp(pre2.transport, "tcp", 3)) {
239                 transport = AST_TRANSPORT_TCP;
240         } else if (!strncasecmp(pre2.transport, "tls", 3)) {
241                 transport = AST_TRANSPORT_TLS;
242         } else if (!strncasecmp(pre2.transport, "udp", 3)) {
243                 transport = AST_TRANSPORT_UDP;
244         } else {
245                 transport = AST_TRANSPORT_UDP;
246                 ast_log(LOG_NOTICE, "'%.3s' is not a valid transport type on line %d of sip.conf. defaulting to udp.\n", pre2.transport, lineno);
247         }
248
249         /* if no portnum specified, set default for transport */
250         if (!portnum) {
251                 if (transport == AST_TRANSPORT_TLS) {
252                         portnum = STANDARD_TLS_PORT;
253                 } else {
254                         portnum = STANDARD_SIP_PORT;
255                 }
256         }
257
258         /* copy into sip_registry object */
259         ast_string_field_set(reg, callback, ast_strip_quoted(S_OR(host2.extension, "s"), "\"", "\""));
260         ast_string_field_set(reg, username, ast_strip_quoted(S_OR(user2.user, ""), "\"", "\""));
261         ast_string_field_set(reg, hostname, ast_strip_quoted(S_OR(host3.host, ""), "\"", "\""));
262         ast_string_field_set(reg, authuser, ast_strip_quoted(S_OR(user3.authuser, ""), "\"", "\""));
263         ast_string_field_set(reg, secret, ast_strip_quoted(S_OR(user1.secret, ""), "\"", "\""));
264         ast_string_field_set(reg, peername, ast_strip_quoted(S_OR(pre1.peer, ""), "\"", "\""));
265         ast_string_field_set(reg, regdomain, ast_strip_quoted(S_OR(user2.domain, ""), "\"", "\""));
266
267         reg->transport = transport;
268         reg->portno = portnum;
269         reg->regdomainport = domainport;
270         reg->callid_valid = FALSE;
271         reg->ocseq = INITIAL_CSEQ;
272         reg->refresh = reg->expiry = reg->configured_expiry = (host1.expiry ? atoi(ast_strip_quoted(host1.expiry, "\"", "\"")) : default_expiry);
273
274         return 0;
275 }
276
277 #ifdef TEST_FRAMEWORK
278 AST_TEST_DEFINE(sip_parse_register_line_test)
279 {
280         int res = AST_TEST_PASS;
281         struct sip_registry *reg;
282         int default_expiry = 120;
283         const char *reg1 = "name@domain";
284         const char *reg2 = "name:pass@domain";
285         const char *reg3 = "name@namedomain:pass:authuser@domain";
286         const char *reg4 = "name@namedomain:pass:authuser@domain/extension";
287         const char *reg5 = "tcp://name@namedomain:pass:authuser@domain/extension";
288         const char *reg6 = "tls://name@namedomain:pass:authuser@domain/extension~111";
289         const char *reg7 = "peer?tcp://name@namedomain:pass:authuser@domain:1234/extension~111";
290         const char *reg8 = "peer?name@namedomain:pass:authuser@domain:1234/extension~111";
291         const char *reg9 = "peer?name:pass:authuser:1234/extension~111";
292         const char *reg10 = "@domin:1234";
293         const char *reg12 = "name@namedomain:4321:pass:authuser@domain";
294         const char *reg13 = "name@namedomain:4321::@domain";
295
296         switch (cmd) {
297         case TEST_INIT:
298                 info->name = "sip_parse_register_line_test";
299                 info->category = "/channels/chan_sip/";
300                 info->summary = "tests sip register line parsing";
301                 info->description =
302                                                         "Tests parsing of various register line configurations. "
303                                                         "Verifies output matches expected behavior.";
304                 return AST_TEST_NOT_RUN;
305         case TEST_EXECUTE:
306                 break;
307         }
308
309         /* ---Test reg 1, simple config --- */
310         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
311                 goto alloc_fail;
312         } else if (
313             sip_parse_register_line(reg, default_expiry, reg1, 1) ||
314                 strcmp(reg->callback, "s")           ||
315                 strcmp(reg->username, "name")       ||
316                 strcmp(reg->regdomain, "")          ||
317                 strcmp(reg->hostname, "domain")     ||
318                 strcmp(reg->authuser, "")           ||
319                 strcmp(reg->secret, "")             ||
320                 strcmp(reg->peername, "")           ||
321                 reg->transport != AST_TRANSPORT_UDP ||
322                 reg->timeout != -1                  ||
323                 reg->expire != -1                   ||
324                 reg->refresh != default_expiry ||
325                 reg->expiry != default_expiry ||
326                 reg->configured_expiry != default_expiry ||
327                 reg->portno != STANDARD_SIP_PORT    ||
328                 (reg->regdomainport)                ||
329                 reg->callid_valid != FALSE          ||
330                 reg->ocseq != INITIAL_CSEQ) {
331
332                 ast_test_status_update(test, "Test 1: simple config failed\n");
333                 res = AST_TEST_FAIL;
334         }
335         ast_string_field_free_memory(reg);
336         ast_free(reg);
337
338         /* ---Test reg 2, add secret --- */
339         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
340                 goto alloc_fail;
341         } else if (
342             sip_parse_register_line(reg, default_expiry, reg2, 1) ||
343                 strcmp(reg->callback, "s")           ||
344                 strcmp(reg->username, "name")       ||
345                 strcmp(reg->regdomain, "")          ||
346                 strcmp(reg->hostname, "domain")     ||
347                 strcmp(reg->authuser, "")           ||
348                 strcmp(reg->secret, "pass")         ||
349                 strcmp(reg->peername, "")           ||
350                 reg->transport != AST_TRANSPORT_UDP ||
351                 reg->timeout != -1                  ||
352                 reg->expire != -1                   ||
353                 reg->refresh != default_expiry ||
354                 reg->expiry != default_expiry ||
355                 reg->configured_expiry != default_expiry ||
356                 reg->portno != STANDARD_SIP_PORT    ||
357                 (reg->regdomainport)                ||
358                 reg->callid_valid != FALSE          ||
359                 reg->ocseq != INITIAL_CSEQ) {
360
361                 ast_test_status_update(test,  "Test 2: add secret failed\n");
362                 res = AST_TEST_FAIL;
363         }
364         ast_string_field_free_memory(reg);
365         ast_free(reg);
366
367         /* ---Test reg 3, add userdomain and authuser --- */
368         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
369                 goto alloc_fail;
370         } else if (
371             sip_parse_register_line(reg, default_expiry, reg3, 1) ||
372                 strcmp(reg->callback, "s")           ||
373                 strcmp(reg->username, "name") ||
374                 strcmp(reg->regdomain, "namedomain") ||
375                 strcmp(reg->hostname, "domain")     ||
376                 strcmp(reg->authuser, "authuser")           ||
377                 strcmp(reg->secret, "pass")         ||
378                 strcmp(reg->peername, "")           ||
379                 reg->transport != AST_TRANSPORT_UDP ||
380                 reg->timeout != -1                  ||
381                 reg->expire != -1                   ||
382                 reg->refresh != default_expiry ||
383                 reg->expiry != default_expiry ||
384                 reg->configured_expiry != default_expiry ||
385                 reg->portno != STANDARD_SIP_PORT    ||
386                 (reg->regdomainport)                ||
387                 reg->callid_valid != FALSE          ||
388                 reg->ocseq != INITIAL_CSEQ) {
389
390                 ast_test_status_update(test, "Test 3: add userdomain and authuser failed\n");
391                 res = AST_TEST_FAIL;
392         }
393         ast_string_field_free_memory(reg);
394         ast_free(reg);
395
396         /* ---Test reg 4, add callback extension --- */
397         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
398                 goto alloc_fail;
399         } else if (
400             sip_parse_register_line(reg, default_expiry, reg4, 1) ||
401                 strcmp(reg->callback, "extension")           ||
402                 strcmp(reg->username, "name") ||
403                 strcmp(reg->regdomain, "namedomain") ||
404                 strcmp(reg->hostname, "domain")     ||
405                 strcmp(reg->authuser, "authuser")           ||
406                 strcmp(reg->secret, "pass")         ||
407                 strcmp(reg->peername, "")           ||
408                 reg->transport != AST_TRANSPORT_UDP ||
409                 reg->timeout != -1                  ||
410                 reg->expire != -1                   ||
411                 reg->refresh != default_expiry ||
412                 reg->expiry != default_expiry ||
413                 reg->configured_expiry != default_expiry ||
414                 reg->portno != STANDARD_SIP_PORT    ||
415                 (reg->regdomainport)                ||
416                 reg->callid_valid != FALSE          ||
417                 reg->ocseq != INITIAL_CSEQ) {
418
419                 ast_test_status_update(test, "Test 4: add callback extension failed\n");
420                 res = AST_TEST_FAIL;
421         }
422         ast_string_field_free_memory(reg);
423         ast_free(reg);
424
425         /* ---Test reg 5, add transport --- */
426         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
427                 goto alloc_fail;
428         } else if (
429             sip_parse_register_line(reg, default_expiry, reg5, 1) ||
430                 strcmp(reg->callback, "extension")           ||
431                 strcmp(reg->username, "name") ||
432                 strcmp(reg->regdomain, "namedomain") ||
433                 strcmp(reg->hostname, "domain")     ||
434                 strcmp(reg->authuser, "authuser")           ||
435                 strcmp(reg->secret, "pass")         ||
436                 strcmp(reg->peername, "")           ||
437                 reg->transport != AST_TRANSPORT_TCP ||
438                 reg->timeout != -1                  ||
439                 reg->expire != -1                   ||
440                 reg->refresh != default_expiry ||
441                 reg->expiry != default_expiry ||
442                 reg->configured_expiry != default_expiry ||
443                 reg->portno != STANDARD_SIP_PORT    ||
444                 (reg->regdomainport)                ||
445                 reg->callid_valid != FALSE          ||
446                 reg->ocseq != INITIAL_CSEQ) {
447
448                 ast_test_status_update(test, "Test 5: add transport failed\n");
449                 res = AST_TEST_FAIL;
450         }
451         ast_string_field_free_memory(reg);
452         ast_free(reg);
453
454         /* ---Test reg 6, change to tls transport, add expiry  --- */
455         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
456                 goto alloc_fail;
457         } else if (
458             sip_parse_register_line(reg, default_expiry, reg6, 1) ||
459                 strcmp(reg->callback, "extension")           ||
460                 strcmp(reg->username, "name") ||
461                 strcmp(reg->regdomain, "namedomain") ||
462                 strcmp(reg->hostname, "domain")     ||
463                 strcmp(reg->authuser, "authuser")           ||
464                 strcmp(reg->secret, "pass")         ||
465                 strcmp(reg->peername, "")           ||
466                 reg->transport != AST_TRANSPORT_TLS ||
467                 reg->timeout != -1                  ||
468                 reg->expire != -1                   ||
469                 reg->refresh != 111 ||
470                 reg->expiry != 111 ||
471                 reg->configured_expiry != 111 ||
472                 reg->portno != STANDARD_TLS_PORT    ||
473                 (reg->regdomainport)                ||
474                 reg->callid_valid != FALSE          ||
475                 reg->ocseq != INITIAL_CSEQ) {
476
477                 ast_test_status_update(test, "Test 6: change to tls transport and add expiry failed\n");
478                 res = AST_TEST_FAIL;
479         }
480         ast_string_field_free_memory(reg);
481         ast_free(reg);
482
483         /* ---Test reg 7, change transport to tcp, add custom port, and add peer --- */
484         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
485                 goto alloc_fail;
486         } else if (
487             sip_parse_register_line(reg, default_expiry, reg7, 1) ||
488                 strcmp(reg->callback, "extension")           ||
489                 strcmp(reg->username, "name") ||
490                 strcmp(reg->regdomain, "namedomain") ||
491                 strcmp(reg->hostname, "domain")     ||
492                 strcmp(reg->authuser, "authuser")           ||
493                 strcmp(reg->secret, "pass")         ||
494                 strcmp(reg->peername, "peer")           ||
495                 reg->transport != AST_TRANSPORT_TCP ||
496                 reg->timeout != -1                  ||
497                 reg->expire != -1                   ||
498                 reg->refresh != 111 ||
499                 reg->expiry != 111 ||
500                 reg->configured_expiry != 111 ||
501                 reg->portno != 1234    ||
502                 (reg->regdomainport)                ||
503                 reg->callid_valid != FALSE          ||
504                 reg->ocseq != INITIAL_CSEQ) {
505
506                 ast_test_status_update(test, "Test 7, change transport to tcp, add custom port, and add peer failed.\n");
507                 res = AST_TEST_FAIL;
508         }
509         ast_string_field_free_memory(reg);
510         ast_free(reg);
511
512         /* ---Test reg 8, remove transport --- */
513         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
514                 goto alloc_fail;
515         } else if (
516             sip_parse_register_line(reg, default_expiry, reg8, 1) ||
517                 strcmp(reg->callback, "extension")           ||
518                 strcmp(reg->username, "name") ||
519                 strcmp(reg->regdomain, "namedomain") ||
520                 strcmp(reg->hostname, "domain")     ||
521                 strcmp(reg->authuser, "authuser")           ||
522                 strcmp(reg->secret, "pass")         ||
523                 strcmp(reg->peername, "peer")           ||
524                 reg->transport != AST_TRANSPORT_UDP ||
525                 reg->timeout != -1                  ||
526                 reg->expire != -1                   ||
527                 reg->refresh != 111 ||
528                 reg->expiry != 111 ||
529                 reg->configured_expiry != 111 ||
530                 reg->portno != 1234    ||
531                 (reg->regdomainport)                ||
532                 reg->callid_valid != FALSE          ||
533                 reg->ocseq != INITIAL_CSEQ) {
534
535                 ast_test_status_update(test, "Test 8, remove transport failed.\n");
536                 res = AST_TEST_FAIL;
537         }
538         ast_string_field_free_memory(reg);
539         ast_free(reg);
540
541         /* ---Test reg 9, missing domain, expected to fail --- */
542         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
543                 goto alloc_fail;
544         } else if (!sip_parse_register_line(reg, default_expiry, reg9, 1)) {
545                 ast_test_status_update(test,
546                                 "Test 9, missing domain, expected to fail but did not.\n");
547                 res = AST_TEST_FAIL;
548         }
549         ast_string_field_free_memory(reg);
550         ast_free(reg);
551
552         /* ---Test reg 10,  missing user, expected to fail --- */
553         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
554                 goto alloc_fail;
555         } else if (!sip_parse_register_line(reg, default_expiry, reg10, 1)) {
556                 ast_test_status_update(test,
557                                 "Test 10, missing user expected to fail but did not\n");
558                 res = AST_TEST_FAIL;
559         }
560         ast_string_field_free_memory(reg);
561         ast_free(reg);
562
563         /* ---Test reg 11, no registry object, expected to fail--- */
564         if (!sip_parse_register_line(NULL, default_expiry, reg1, 1)) {
565                 ast_test_status_update(test,
566                                 "Test 11, no registry object, expected to fail but did not.\n");
567                 res = AST_TEST_FAIL;
568         }
569
570         /* ---Test reg 12,  no registry line, expected to fail --- */
571         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
572                 goto alloc_fail;
573         } else if (!sip_parse_register_line(reg, default_expiry, NULL, 1)) {
574
575                 ast_test_status_update(test,
576                                 "Test 12, NULL register line expected to fail but did not.\n");
577                 res = AST_TEST_FAIL;
578         }
579         ast_string_field_free_memory(reg);
580         ast_free(reg);
581
582         /* ---Test reg13, add domain port --- */
583         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
584                 goto alloc_fail;
585         } else if (
586            sip_parse_register_line(reg, default_expiry, reg12, 1) ||
587                 strcmp(reg->callback, "s")           ||
588                 strcmp(reg->username, "name") ||
589                 strcmp(reg->regdomain, "namedomain") ||
590                 strcmp(reg->hostname, "domain")     ||
591                 strcmp(reg->authuser, "authuser")           ||
592                 strcmp(reg->secret, "pass")         ||
593                 strcmp(reg->peername, "")           ||
594                 reg->transport != AST_TRANSPORT_UDP ||
595                 reg->timeout != -1                  ||
596                 reg->expire != -1                   ||
597                 reg->refresh != default_expiry ||
598                 reg->expiry != default_expiry ||
599                 reg->configured_expiry != default_expiry ||
600                 reg->portno != STANDARD_SIP_PORT    ||
601                 reg->regdomainport != 4321          ||
602                 reg->callid_valid != FALSE          ||
603                 reg->ocseq != INITIAL_CSEQ) {
604
605                 ast_test_status_update(test, "Test 13, add domain port failed.\n");
606                 res = AST_TEST_FAIL;
607         }
608         ast_string_field_free_memory(reg);
609         ast_free(reg);
610
611         /* ---Test reg14, domain port without secret --- */
612         if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) {
613                 goto alloc_fail;
614         } else if (
615            sip_parse_register_line(reg, default_expiry, reg13, 1) ||
616                 strcmp(reg->callback, "s")           ||
617                 strcmp(reg->username, "name") ||
618                 strcmp(reg->regdomain, "namedomain") ||
619                 strcmp(reg->hostname, "domain")     ||
620                 strcmp(reg->authuser, "")           ||
621                 strcmp(reg->secret, "")         ||
622                 strcmp(reg->peername, "")           ||
623                 reg->transport != AST_TRANSPORT_UDP ||
624                 reg->timeout != -1                  ||
625                 reg->expire != -1                   ||
626                 reg->refresh != default_expiry ||
627                 reg->expiry != default_expiry ||
628                 reg->configured_expiry != default_expiry ||
629                 reg->portno != STANDARD_SIP_PORT    ||
630                 reg->regdomainport != 4321          ||
631                 reg->callid_valid != FALSE          ||
632                 reg->ocseq != INITIAL_CSEQ) {
633
634                 ast_test_status_update(test, "Test 14, domain port without secret failed.\n");
635                 res = AST_TEST_FAIL;
636         }
637         ast_string_field_free_memory(reg);
638         ast_free(reg);
639
640
641         return res;
642
643 alloc_fail:
644         ast_test_status_update(test, "Out of memory. \n");
645         return res;
646 }
647 #endif
648
649 int sip_parse_host(char *line, int lineno, char **hostname, int *portnum, enum ast_transport *transport)
650 {
651         char *port;
652
653         if (ast_strlen_zero(line)) {
654                 *hostname = NULL;
655                 return -1;
656         }
657         if ((*hostname = strstr(line, "://"))) {
658                 *hostname += 3;
659
660                 if (!strncasecmp(line, "tcp", 3)) {
661                         *transport = AST_TRANSPORT_TCP;
662                 } else if (!strncasecmp(line, "tls", 3)) {
663                         *transport = AST_TRANSPORT_TLS;
664                 } else if (!strncasecmp(line, "udp", 3)) {
665                         *transport = AST_TRANSPORT_UDP;
666                 } else if (lineno) {
667                         ast_log(LOG_NOTICE, "'%.3s' is not a valid transport type on line %d of sip.conf. defaulting to udp.\n", line, lineno);
668                 } else {
669                         ast_log(LOG_NOTICE, "'%.3s' is not a valid transport type in sip config. defaulting to udp.\n", line);
670                 }
671         } else {
672                 *hostname = line;
673                 *transport = AST_TRANSPORT_UDP;
674         }
675
676         if ((line = strrchr(*hostname, '@')))
677                 line++;
678         else
679                 line = *hostname;
680
681         if (ast_sockaddr_split_hostport(line, hostname, &port, 0) == 0) {
682                 if (lineno) {
683                         ast_log(LOG_WARNING, "Cannot parse host '%s' on line %d of sip.conf.\n",
684                                 line, lineno);
685                 } else {
686                         ast_log(LOG_WARNING, "Cannot parse host '%s' in sip config.\n", line);
687                 }
688                 return -1;
689         }
690
691         if (port) {
692                 if (!sscanf(port, "%5d", portnum)) {
693                         if (lineno) {
694                                 ast_log(LOG_NOTICE, "'%s' is not a valid port number on line %d of sip.conf. using default.\n", port, lineno);
695                         } else {
696                                 ast_log(LOG_NOTICE, "'%s' is not a valid port number in sip config. using default.\n", port);
697                         }
698                         port = NULL;
699                 }
700         }
701
702         if (!port) {
703                 if (*transport & AST_TRANSPORT_TLS) {
704                         *portnum = STANDARD_TLS_PORT;
705                 } else {
706                         *portnum = STANDARD_SIP_PORT;
707                 }
708         }
709
710         return 0;
711 }
712
713 #ifdef TEST_FRAMEWORK
714 AST_TEST_DEFINE(sip_parse_host_line_test)
715 {
716         int res = AST_TEST_PASS;
717         char *host;
718         int port;
719         enum ast_transport transport;
720         char host1[] = "www.blah.com";
721         char host2[] = "tcp://www.blah.com";
722         char host3[] = "tls://10.10.10.10";
723         char host4[] = "tls://10.10.10.10:1234";
724         char host5[] = "10.10.10.10:1234";
725
726         switch (cmd) {
727         case TEST_INIT:
728                 info->name = "sip_parse_host_line_test";
729                 info->category = "/channels/chan_sip/";
730                 info->summary = "tests sip.conf host line parsing";
731                 info->description =
732                                                         "Tests parsing of various host line configurations. "
733                                                         "Verifies output matches expected behavior.";
734                 return AST_TEST_NOT_RUN;
735         case TEST_EXECUTE:
736                 break;
737         }
738
739         /* test 1, simple host */
740         sip_parse_host(host1, 1, &host, &port, &transport);
741         if (port != STANDARD_SIP_PORT ||
742                         ast_strlen_zero(host) || strcmp(host, "www.blah.com") ||
743                         transport != AST_TRANSPORT_UDP) {
744                 ast_test_status_update(test, "Test 1: simple host failed.\n");
745                 res = AST_TEST_FAIL;
746         }
747
748         /* test 2, add tcp transport */
749         sip_parse_host(host2, 1, &host, &port, &transport);
750         if (port != STANDARD_SIP_PORT ||
751                         ast_strlen_zero(host) || strcmp(host, "www.blah.com") ||
752                         transport != AST_TRANSPORT_TCP) {
753                 ast_test_status_update(test, "Test 2: tcp host failed.\n");
754                 res = AST_TEST_FAIL;
755         }
756
757         /* test 3, add tls transport */
758         sip_parse_host(host3, 1, &host, &port, &transport);
759         if (port != STANDARD_TLS_PORT ||
760                         ast_strlen_zero(host) || strcmp(host, "10.10.10.10") ||
761                         transport != AST_TRANSPORT_TLS) {
762                 ast_test_status_update(test, "Test 3: tls host failed. \n");
763                 res = AST_TEST_FAIL;
764         }
765
766         /* test 4, add custom port with tls */
767         sip_parse_host(host4, 1, &host, &port, &transport);
768         if (port != 1234 || ast_strlen_zero(host) ||
769                         strcmp(host, "10.10.10.10") ||
770                         transport != AST_TRANSPORT_TLS) {
771                 ast_test_status_update(test, "Test 4: tls host with custom port failed.\n");
772                 res = AST_TEST_FAIL;
773         }
774
775         /* test 5, simple host with custom port */
776         sip_parse_host(host5, 1, &host, &port, &transport);
777         if (port != 1234 || ast_strlen_zero(host) ||
778                         strcmp(host, "10.10.10.10") ||
779                         transport != AST_TRANSPORT_UDP) {
780                 ast_test_status_update(test, "Test 5: simple host with custom port failed.\n");
781                 res = AST_TEST_FAIL;
782         }
783
784         /* test 6, expected failure with NULL input */
785         if (!sip_parse_host(NULL, 1, &host, &port, &transport)) {
786                 ast_test_status_update(test, "Test 6: expected error on NULL input did not occur.\n");
787                 res = AST_TEST_FAIL;
788         }
789
790         return res;
791
792 }
793 #endif
794
795 /*! \brief Parse the comma-separated nat= option values */
796 void sip_parse_nat_option(const char *value, struct ast_flags *mask, struct ast_flags *flags)
797 {
798         char *parse, *this;
799
800         if (!(parse = ast_strdupa(value))) {
801                 return;
802         }
803
804         /* Since we need to completely override the general settings if we are being called
805          * later for a peer, always set the flags for all options on the mask */
806         ast_set_flag(&mask[0], SIP_NAT_FORCE_RPORT);
807         ast_set_flag(&mask[1], SIP_PAGE2_SYMMETRICRTP);
808         ast_set_flag(&mask[2], SIP_PAGE3_NAT_AUTO_RPORT);
809         ast_set_flag(&mask[2], SIP_PAGE3_NAT_AUTO_COMEDIA);
810
811         while ((this = strsep(&parse, ","))) {
812                 if (ast_false(this)) {
813                         ast_clear_flag(&flags[0], SIP_NAT_FORCE_RPORT);
814                         ast_clear_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP);
815                         ast_clear_flag(&flags[2], SIP_PAGE3_NAT_AUTO_RPORT);
816                         ast_clear_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA);
817                         break; /* It doesn't make sense to have no + something else */
818                 } else if (!strcasecmp(this, "yes")) {
819                         ast_log(LOG_WARNING, "nat=yes is deprecated, use nat=force_rport,comedia instead\n");
820                         ast_set_flag(&flags[0], SIP_NAT_FORCE_RPORT);
821                         ast_set_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP);
822                         ast_clear_flag(&flags[2], SIP_PAGE3_NAT_AUTO_RPORT);
823                         ast_clear_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA);
824                         break; /* It doesn't make sense to have yes + something else */
825                 } else if (!strcasecmp(this, "force_rport") && !ast_test_flag(&flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
826                         ast_set_flag(&flags[0], SIP_NAT_FORCE_RPORT);
827                 } else if (!strcasecmp(this, "comedia") && !ast_test_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) {
828                         ast_set_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP);
829                 } else if (!strcasecmp(this, "auto_force_rport")) {
830                         ast_set_flag(&flags[2], SIP_PAGE3_NAT_AUTO_RPORT);
831                         /* In case someone did something dumb like nat=force_rport,auto_force_rport */
832                         ast_clear_flag(&flags[0], SIP_NAT_FORCE_RPORT);
833                 } else if (!strcasecmp(this, "auto_comedia")) {
834                         ast_set_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA);
835                         /* In case someone did something dumb like nat=comedia,auto_comedia*/
836                         ast_clear_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP);
837                 }
838         }
839 }
840
841 #ifdef TEST_FRAMEWORK
842 #define TEST_FORCE_RPORT      1 << 0
843 #define TEST_COMEDIA          1 << 1
844 #define TEST_AUTO_FORCE_RPORT 1 << 2
845 #define TEST_AUTO_COMEDIA     1 << 3
846 static int match_nat_options(int val, struct ast_flags *flags)
847 {
848         if ((!ast_test_flag(&flags[0], SIP_NAT_FORCE_RPORT)) != !(val & TEST_FORCE_RPORT)) {
849                 return 0;
850         }
851         if (!ast_test_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP) != !(val & TEST_COMEDIA)) {
852                 return 0;
853         }
854         if (!ast_test_flag(&flags[2], SIP_PAGE3_NAT_AUTO_RPORT) != !(val & TEST_AUTO_FORCE_RPORT)) {
855                 return 0;
856         }
857         if (!ast_test_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA) != !(val & TEST_AUTO_COMEDIA)) {
858            return 0;
859         }
860         return 1;
861 }
862
863 AST_TEST_DEFINE(sip_parse_nat_test)
864 {
865         int i, res = AST_TEST_PASS;
866         struct ast_flags mask[3] = {{0}}, flags[3] = {{0}};
867         struct {
868                 const char *str;
869                 int i;
870         } options[] = {
871                 { "yes", TEST_FORCE_RPORT | TEST_COMEDIA },
872                 { "no", 0 },
873                 { "force_rport", TEST_FORCE_RPORT },
874                 { "comedia", TEST_COMEDIA },
875                 { "auto_force_rport", TEST_AUTO_FORCE_RPORT },
876                 { "auto_comedia", TEST_AUTO_COMEDIA },
877                 { "force_rport,auto_force_rport", TEST_AUTO_FORCE_RPORT },
878                 { "auto_force_rport,force_rport", TEST_AUTO_FORCE_RPORT },
879                 { "comedia,auto_comedia", TEST_AUTO_COMEDIA },
880                 { "auto_comedia,comedia", TEST_AUTO_COMEDIA },
881                 { "force_rport,comedia", TEST_FORCE_RPORT | TEST_COMEDIA },
882                 { "force_rport,auto_comedia", TEST_FORCE_RPORT | TEST_AUTO_COMEDIA },
883                 { "force_rport,yes,no", TEST_FORCE_RPORT | TEST_COMEDIA },
884                 { "auto_comedia,no,yes", 0 },
885         };
886
887         switch (cmd) {
888         case TEST_INIT:
889                 info->name = "sip_parse_nat_test";
890                 info->category = "/channels/chan_sip/";
891                 info->summary = "tests sip.conf nat line parsing";
892                 info->description =
893                                                         "Tests parsing of various nat line configurations. "
894                                                         "Verifies output matches expected behavior.";
895                 return AST_TEST_NOT_RUN;
896         case TEST_EXECUTE:
897                 break;
898         }
899
900         for (i = 0; i < ARRAY_LEN(options); i++) {
901                 sip_parse_nat_option(options[i].str, mask, flags);
902                 if (!match_nat_options(options[i].i, flags)) {
903                         ast_test_status_update(test, "Failed nat=%s\n", options[i].str);
904                         res = AST_TEST_FAIL;
905                 }
906                 memset(flags, 0, sizeof(flags));
907                 memset(mask, 0, sizeof(mask));
908         }
909
910         return res;
911 }
912 #endif
913
914 /*! \brief SIP test registration */
915 void sip_config_parser_register_tests(void)
916 {
917         AST_TEST_REGISTER(sip_parse_register_line_test);
918         AST_TEST_REGISTER(sip_parse_host_line_test);
919         AST_TEST_REGISTER(sip_parse_nat_test);
920 }
921
922 /*! \brief SIP test registration */
923 void sip_config_parser_unregister_tests(void)
924 {
925         AST_TEST_UNREGISTER(sip_parse_register_line_test);
926         AST_TEST_UNREGISTER(sip_parse_host_line_test);
927         AST_TEST_UNREGISTER(sip_parse_nat_test);
928 }
929