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