radius.c (43401) | radius.c (43693) |
---|---|
1/* 2 * Copyright 1999 Internet Business Solutions Ltd., Switzerland 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright --- 9 unchanged lines hidden (view full) --- 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * | 1/* 2 * Copyright 1999 Internet Business Solutions Ltd., Switzerland 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright --- 9 unchanged lines hidden (view full) --- 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * |
26 * $Id: radius.c,v 1.1 1999/01/28 01:56:34 brian Exp $ | 26 * $Id: radius.c,v 1.2 1999/01/29 22:46:31 brian Exp $ |
27 * 28 */ 29 30#include <sys/param.h> 31#include <netinet/in_systm.h> 32#include <netinet/in.h> 33#include <netinet/ip.h> 34#include <arpa/inet.h> 35#include <sys/un.h> 36 37#include <errno.h> 38#include <radlib.h> | 27 * 28 */ 29 30#include <sys/param.h> 31#include <netinet/in_systm.h> 32#include <netinet/in.h> 33#include <netinet/ip.h> 34#include <arpa/inet.h> 35#include <sys/un.h> 36 37#include <errno.h> 38#include <radlib.h> |
39#include <signal.h> | |
40#include <stdio.h> 41#include <stdlib.h> 42#include <string.h> | 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> |
42#include <sys/time.h> |
|
43#include <termios.h> 44 45#include "defs.h" 46#include "log.h" 47#include "descriptor.h" 48#include "prompt.h" 49#include "timer.h" 50#include "fsm.h" 51#include "iplist.h" 52#include "slcompress.h" 53#include "throughput.h" 54#include "lqr.h" 55#include "hdlc.h" 56#include "mbuf.h" 57#include "ipcp.h" 58#include "route.h" 59#include "command.h" 60#include "filter.h" | 43#include <termios.h> 44 45#include "defs.h" 46#include "log.h" 47#include "descriptor.h" 48#include "prompt.h" 49#include "timer.h" 50#include "fsm.h" 51#include "iplist.h" 52#include "slcompress.h" 53#include "throughput.h" 54#include "lqr.h" 55#include "hdlc.h" 56#include "mbuf.h" 57#include "ipcp.h" 58#include "route.h" 59#include "command.h" 60#include "filter.h" |
61#include "server.h" | |
62#include "lcp.h" 63#include "ccp.h" 64#include "link.h" 65#include "mp.h" 66#include "radius.h" | 61#include "lcp.h" 62#include "ccp.h" 63#include "link.h" 64#include "mp.h" 65#include "radius.h" |
66#include "auth.h" 67#include "async.h" 68#include "physical.h" 69#include "chat.h" 70#include "cbcp.h" 71#include "chap.h" 72#include "datalink.h" |
|
67#include "bundle.h" 68 | 73#include "bundle.h" 74 |
69void 70radius_Init(struct radius *r) | 75/* 76 * rad_continue_send_request() has given us `got' (non-zero). Deal with it. 77 */ 78static void 79radius_Process(struct radius *r, int got) |
71{ | 80{ |
72 r->valid = 0; 73 *r->cfg.file = '\0';; 74} 75 76void 77radius_Destroy(struct radius *r) 78{ 79 r->valid = 0; 80 route_DeleteAll(&r->routes); 81} 82 83int 84radius_Authenticate(struct radius *r, struct bundle *bundle, const char *name, 85 const char *key, const char *challenge) 86{ 87 struct rad_handle *h; 88 sigset_t alrm, prevset; 89 const void *data; 90 int got, len, argc, addrs; | |
91 char *argv[MAXARGS], *nuke; | 81 char *argv[MAXARGS], *nuke; |
82 struct bundle *bundle; 83 int len, argc, addrs; |
|
92 struct in_range dest; 93 struct in_addr gw; | 84 struct in_range dest; 85 struct in_addr gw; |
86 const void *data; |
|
94 | 87 |
95 radius_Destroy(r); | 88 r->cx.fd = -1; /* Stop select()ing */ |
96 | 89 |
97 if (!*r->cfg.file) 98 return 0; 99 100 if ((h = rad_open()) == NULL) { 101 log_Printf(LogERROR, "rad_open: %s\n", strerror(errno)); 102 return 0; 103 } 104 105 if (rad_config(h, r->cfg.file) != 0) { 106 log_Printf(LogERROR, "rad_config: %s\n", rad_strerror(h)); 107 rad_close(h); 108 return 0; 109 } 110 111 if (rad_create_request(h, RAD_ACCESS_REQUEST) != 0) { 112 log_Printf(LogERROR, "rad_create_request: %s\n", rad_strerror(h)); 113 rad_close(h); 114 return 0; 115 } 116 117 if (rad_put_string(h, RAD_USER_NAME, name) != 0 || 118 rad_put_int(h, RAD_SERVICE_TYPE, RAD_FRAMED) != 0 || 119 rad_put_int(h, RAD_FRAMED_PROTOCOL, RAD_PPP) != 0) { 120 log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(h)); 121 rad_close(h); 122 return 0; 123 } 124 125 if (challenge != NULL) { /* CHAP */ 126 if (rad_put_string(h, RAD_CHAP_PASSWORD, key) != 0 || 127 rad_put_string(h, RAD_CHAP_CHALLENGE, challenge) != 0) { 128 log_Printf(LogERROR, "CHAP: rad_put_string: %s\n", rad_strerror(h)); 129 rad_close(h); 130 return 0; 131 } 132 } else if (rad_put_string(h, RAD_USER_PASSWORD, key) != 0) { /* PAP */ 133 /* We're talking PAP */ 134 log_Printf(LogERROR, "PAP: rad_put_string: %s\n", rad_strerror(h)); 135 rad_close(h); 136 return 0; 137 } 138 139 /* 140 * Having to do this is bad news. The right way is to grab the 141 * descriptor that rad_send_request() selects on and add it to 142 * our own selection list (making a full ``struct descriptor''), 143 * then to ``continue'' the call when the descriptor is ready. 144 * This requires altering libradius.... 145 */ 146 sigemptyset(&alrm); 147 sigaddset(&alrm, SIGALRM); 148 sigprocmask(SIG_BLOCK, &alrm, &prevset); 149 got = rad_send_request(h); 150 sigprocmask(SIG_SETMASK, &prevset, NULL); 151 | |
152 switch (got) { 153 case RAD_ACCESS_ACCEPT: | 90 switch (got) { 91 case RAD_ACCESS_ACCEPT: |
92 log_Printf(LogPHASE, "Radius: ACCEPT received\n"); |
|
154 break; 155 | 93 break; 94 |
95 case RAD_ACCESS_REJECT: 96 log_Printf(LogPHASE, "Radius: REJECT received\n"); 97 auth_Failure(r->cx.auth); 98 rad_close(r->cx.rad); 99 return; 100 |
|
156 case RAD_ACCESS_CHALLENGE: 157 /* we can't deal with this (for now) ! */ | 101 case RAD_ACCESS_CHALLENGE: 102 /* we can't deal with this (for now) ! */ |
158 log_Printf(LogPHASE, "Can't handle radius CHALLENGEs !\n"); 159 rad_close(h); 160 return 0; | 103 log_Printf(LogPHASE, "Radius: CHALLENGE received (can't handle yet)\n"); 104 auth_Failure(r->cx.auth); 105 rad_close(r->cx.rad); 106 return; |
161 162 case -1: | 107 108 case -1: |
163 log_Printf(LogPHASE, "radius: %s\n", rad_strerror(h)); 164 rad_close(h); 165 return 0; | 109 log_Printf(LogPHASE, "radius: %s\n", rad_strerror(r->cx.rad)); 110 auth_Failure(r->cx.auth); 111 rad_close(r->cx.rad); 112 return; |
166 167 default: 168 log_Printf(LogERROR, "rad_send_request: Failed %d: %s\n", | 113 114 default: 115 log_Printf(LogERROR, "rad_send_request: Failed %d: %s\n", |
169 got, rad_strerror(h)); 170 rad_close(h); 171 return 0; 172 173 case RAD_ACCESS_REJECT: 174 log_Printf(LogPHASE, "radius: Rejected !\n"); 175 rad_close(h); 176 return 0; | 116 got, rad_strerror(r->cx.rad)); 117 auth_Failure(r->cx.auth); 118 rad_close(r->cx.rad); 119 return; |
177 } 178 179 /* So we've been accepted ! Let's see what we've got in our reply :-I */ 180 r->ip.s_addr = r->mask.s_addr = INADDR_NONE; 181 r->mtu = 0; 182 r->vj = 0; | 120 } 121 122 /* So we've been accepted ! Let's see what we've got in our reply :-I */ 123 r->ip.s_addr = r->mask.s_addr = INADDR_NONE; 124 r->mtu = 0; 125 r->vj = 0; |
183 while ((got = rad_get_attr(h, &data, &len)) > 0) { | 126 while ((got = rad_get_attr(r->cx.rad, &data, &len)) > 0) { |
184 switch (got) { 185 case RAD_FRAMED_IP_ADDRESS: 186 r->ip = rad_cvt_addr(data); | 127 switch (got) { 128 case RAD_FRAMED_IP_ADDRESS: 129 r->ip = rad_cvt_addr(data); |
187 log_Printf(LogDEBUG, "radius: Got IP %s\n", inet_ntoa(r->ip)); | 130 log_Printf(LogPHASE, " IP %s\n", inet_ntoa(r->ip)); |
188 break; 189 190 case RAD_FRAMED_IP_NETMASK: 191 r->mask = rad_cvt_addr(data); | 131 break; 132 133 case RAD_FRAMED_IP_NETMASK: 134 r->mask = rad_cvt_addr(data); |
192 log_Printf(LogDEBUG, "radius: Got MASK %s\n", inet_ntoa(r->mask)); | 135 log_Printf(LogPHASE, " Netmask %s\n", inet_ntoa(r->mask)); |
193 break; 194 195 case RAD_FRAMED_MTU: 196 r->mtu = rad_cvt_int(data); | 136 break; 137 138 case RAD_FRAMED_MTU: 139 r->mtu = rad_cvt_int(data); |
197 log_Printf(LogDEBUG, "radius: Got MTU %lu\n", r->mtu); | 140 log_Printf(LogPHASE, " MTU %lu\n", r->mtu); |
198 break; 199 200 case RAD_FRAMED_ROUTING: 201 /* Disabled for now - should we automatically set up some filters ? */ 202 /* rad_cvt_int(data); */ 203 /* bit 1 = Send routing packets */ 204 /* bit 2 = Receive routing packets */ 205 break; 206 207 case RAD_FRAMED_COMPRESSION: 208 r->vj = rad_cvt_int(data) == 1 ? 1 : 0; | 141 break; 142 143 case RAD_FRAMED_ROUTING: 144 /* Disabled for now - should we automatically set up some filters ? */ 145 /* rad_cvt_int(data); */ 146 /* bit 1 = Send routing packets */ 147 /* bit 2 = Receive routing packets */ 148 break; 149 150 case RAD_FRAMED_COMPRESSION: 151 r->vj = rad_cvt_int(data) == 1 ? 1 : 0; |
209 log_Printf(LogDEBUG, "radius: Got VJ %sabled\n", r->vj ? "en" : "dis"); | 152 log_Printf(LogPHASE, " VJ %sabled\n", r->vj ? "en" : "dis"); |
210 break; 211 212 case RAD_FRAMED_ROUTE: 213 /* 214 * We expect a string of the format ``dest[/bits] gw [metrics]'' 215 * Any specified metrics are ignored. MYADDR and HISADDR are 216 * understood for ``dest'' and ``gw'' and ``0.0.0.0'' is the same 217 * as ``HISADDR''. 218 */ 219 220 if ((nuke = rad_cvt_string(data, len)) == NULL) { | 153 break; 154 155 case RAD_FRAMED_ROUTE: 156 /* 157 * We expect a string of the format ``dest[/bits] gw [metrics]'' 158 * Any specified metrics are ignored. MYADDR and HISADDR are 159 * understood for ``dest'' and ``gw'' and ``0.0.0.0'' is the same 160 * as ``HISADDR''. 161 */ 162 163 if ((nuke = rad_cvt_string(data, len)) == NULL) { |
221 log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(h)); 222 rad_close(h); 223 return 0; | 164 log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(r->cx.rad)); 165 rad_close(r->cx.rad); 166 return; |
224 } 225 | 167 } 168 |
169 log_Printf(LogPHASE, " Route: %s\n", nuke); 170 bundle = r->cx.auth->physical->dl->bundle; |
|
226 dest.ipaddr.s_addr = dest.mask.s_addr = INADDR_ANY; 227 dest.width = 0; 228 argc = command_Interpret(nuke, strlen(nuke), argv); 229 if (argc < 2) 230 log_Printf(LogWARN, "radius: %s: Invalid route\n", 231 argc == 1 ? argv[0] : "\"\""); 232 else if ((strcasecmp(argv[0], "default") != 0 && 233 !ParseAddr(&bundle->ncp.ipcp, argv[0], &dest.ipaddr, --- 21 unchanged lines hidden (view full) --- 255 route_Add(&r->routes, addrs, dest.ipaddr, dest.mask, gw); 256 } 257 free(nuke); 258 break; 259 } 260 } 261 262 if (got == -1) { | 171 dest.ipaddr.s_addr = dest.mask.s_addr = INADDR_ANY; 172 dest.width = 0; 173 argc = command_Interpret(nuke, strlen(nuke), argv); 174 if (argc < 2) 175 log_Printf(LogWARN, "radius: %s: Invalid route\n", 176 argc == 1 ? argv[0] : "\"\""); 177 else if ((strcasecmp(argv[0], "default") != 0 && 178 !ParseAddr(&bundle->ncp.ipcp, argv[0], &dest.ipaddr, --- 21 unchanged lines hidden (view full) --- 200 route_Add(&r->routes, addrs, dest.ipaddr, dest.mask, gw); 201 } 202 free(nuke); 203 break; 204 } 205 } 206 207 if (got == -1) { |
263 log_Printf(LogERROR, "rad_get_attr: %s\n", rad_strerror(h)); 264 rad_close(h); 265 return 0; | 208 log_Printf(LogERROR, "rad_get_attr: %s (failing!)\n", 209 rad_strerror(r->cx.rad)); 210 auth_Failure(r->cx.auth); 211 rad_close(r->cx.rad); 212 } else { 213 r->valid = 1; 214 auth_Success(r->cx.auth); 215 rad_close(r->cx.rad); |
266 } | 216 } |
217} |
|
267 | 218 |
268 rad_close(h); 269 r->valid = 1; 270 log_Printf(LogPHASE, "radius: SUCCESS\n"); | 219/* 220 * We've either timed out or select()ed on the read descriptor 221 */ 222static void 223radius_Continue(struct radius *r, int sel) 224{ 225 struct timeval tv; 226 int got; |
271 | 227 |
272 return 1; | 228 timer_Stop(&r->cx.timer); 229 if ((got = rad_continue_send_request(r->cx.rad, sel, &r->cx.fd, &tv)) == 0) { 230 log_Printf(LogPHASE, "Radius: Request re-sent\n"); 231 r->cx.timer.load = tv.tv_usec / TICKUNIT + tv.tv_sec * SECTICKS; 232 timer_Start(&r->cx.timer); 233 return; 234 } 235 236 radius_Process(r, got); |
273} 274 | 237} 238 |
239/* 240 * Time to call rad_continue_send_request() - timed out. 241 */ 242static void 243radius_Timeout(void *v) 244{ 245 radius_Continue((struct radius *)v, 0); 246} 247 248/* 249 * Time to call rad_continue_send_request() - something to read. 250 */ 251static void 252radius_Read(struct descriptor *d, struct bundle *bundle, const fd_set *fdset) 253{ 254 radius_Continue(descriptor2radius(d), 1); 255} 256 257/* 258 * Behave as a struct descriptor (descriptor.h) 259 */ 260static int 261radius_UpdateSet(struct descriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n) 262{ 263 struct radius *rad = descriptor2radius(d); 264 265 if (r && rad->cx.fd != -1) { 266 FD_SET(rad->cx.fd, r); 267 if (*n < rad->cx.fd + 1) 268 *n = rad->cx.fd + 1; 269 log_Printf(LogTIMER, "Radius: fdset(r) %d\n", rad->cx.fd); 270 return 1; 271 } 272 273 return 0; 274} 275 276/* 277 * Behave as a struct descriptor (descriptor.h) 278 */ 279static int 280radius_IsSet(struct descriptor *d, const fd_set *fdset) 281{ 282 struct radius *r = descriptor2radius(d); 283 284 return r && r->cx.fd != -1 && FD_ISSET(r->cx.fd, fdset); 285} 286 287/* 288 * Behave as a struct descriptor (descriptor.h) 289 */ 290static int 291radius_Write(struct descriptor *d, struct bundle *bundle, const fd_set *fdset) 292{ 293 /* We never want to write here ! */ 294 log_Printf(LogALERT, "radius_Write: Internal error: Bad call !\n"); 295 return 0; 296} 297 298/* 299 * Initialise ourselves 300 */ |
|
275void | 301void |
302radius_Init(struct radius *r) 303{ 304 r->valid = 0; 305 r->cx.fd = -1; 306 *r->cfg.file = '\0';; 307 r->desc.type = RADIUS_DESCRIPTOR; 308 r->desc.UpdateSet = radius_UpdateSet; 309 r->desc.IsSet = radius_IsSet; 310 r->desc.Read = radius_Read; 311 r->desc.Write = radius_Write; 312 memset(&r->cx.timer, '\0', sizeof r->cx.timer); 313} 314 315/* 316 * Forget everything and go back to initialised state. 317 */ 318void 319radius_Destroy(struct radius *r) 320{ 321 r->valid = 0; 322 timer_Stop(&r->cx.timer); 323 route_DeleteAll(&r->routes); 324 if (r->cx.fd != -1) { 325 r->cx.fd = -1; 326 rad_close(r->cx.rad); 327 } 328} 329 330/* 331 * Start an authentication request to the RADIUS server. 332 */ 333void 334radius_Authenticate(struct radius *r, struct authinfo *authp, const char *name, 335 const char *key, const char *challenge) 336{ 337 struct timeval tv; 338 int got; 339 340 if (!*r->cfg.file) 341 return; 342 343 if (r->cx.fd != -1) 344 /* 345 * We assume that our name/key/challenge is the same as last time, 346 * and just continue to wait for the RADIUS server(s). 347 */ 348 return; 349 350 radius_Destroy(r); 351 352 if ((r->cx.rad = rad_open()) == NULL) { 353 log_Printf(LogERROR, "rad_open: %s\n", strerror(errno)); 354 return; 355 } 356 357 if (rad_config(r->cx.rad, r->cfg.file) != 0) { 358 log_Printf(LogERROR, "rad_config: %s\n", rad_strerror(r->cx.rad)); 359 rad_close(r->cx.rad); 360 return; 361 } 362 363 if (rad_create_request(r->cx.rad, RAD_ACCESS_REQUEST) != 0) { 364 log_Printf(LogERROR, "rad_create_request: %s\n", rad_strerror(r->cx.rad)); 365 rad_close(r->cx.rad); 366 return; 367 } 368 369 if (rad_put_string(r->cx.rad, RAD_USER_NAME, name) != 0 || 370 rad_put_int(r->cx.rad, RAD_SERVICE_TYPE, RAD_FRAMED) != 0 || 371 rad_put_int(r->cx.rad, RAD_FRAMED_PROTOCOL, RAD_PPP) != 0) { 372 log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad)); 373 rad_close(r->cx.rad); 374 return; 375 } 376 377 if (challenge != NULL) { 378 /* We're talking CHAP */ 379 if (rad_put_string(r->cx.rad, RAD_CHAP_PASSWORD, key) != 0 || 380 rad_put_string(r->cx.rad, RAD_CHAP_CHALLENGE, challenge) != 0) { 381 log_Printf(LogERROR, "CHAP: rad_put_string: %s\n", 382 rad_strerror(r->cx.rad)); 383 rad_close(r->cx.rad); 384 return; 385 } 386 } else if (rad_put_string(r->cx.rad, RAD_USER_PASSWORD, key) != 0) { 387 /* We're talking PAP */ 388 log_Printf(LogERROR, "PAP: rad_put_string: %s\n", rad_strerror(r->cx.rad)); 389 rad_close(r->cx.rad); 390 return; 391 } 392 393 if ((got = rad_init_send_request(r->cx.rad, &r->cx.fd, &tv))) 394 radius_Process(r, got); 395 else { 396 log_Printf(LogPHASE, "Radius: Request sent\n"); 397 log_Printf(LogDEBUG, "Using radius_Timeout [%p]\n", radius_Timeout); 398 r->cx.timer.load = tv.tv_usec / TICKUNIT + tv.tv_sec * SECTICKS; 399 r->cx.timer.func = radius_Timeout; 400 r->cx.timer.name = "radius"; 401 r->cx.timer.arg = r; 402 r->cx.auth = authp; 403 timer_Start(&r->cx.timer); 404 } 405} 406 407/* 408 * How do things look at the moment ? 409 */ 410void |
|
276radius_Show(struct radius *r, struct prompt *p) 277{ 278 prompt_Printf(p, " Radius config: %s", *r->cfg.file ? r->cfg.file : "none"); 279 if (r->valid) { 280 prompt_Printf(p, "\n IP: %s\n", inet_ntoa(r->ip)); 281 prompt_Printf(p, " Netmask: %s\n", inet_ntoa(r->mask)); 282 prompt_Printf(p, " MTU: %lu\n", r->mtu); 283 prompt_Printf(p, " VJ: %sabled\n", r->vj ? "en" : "dis"); 284 if (r->routes) 285 route_ShowSticky(p, r->routes, " Routes", 16); 286 } else 287 prompt_Printf(p, " (not authenticated)\n"); 288} | 411radius_Show(struct radius *r, struct prompt *p) 412{ 413 prompt_Printf(p, " Radius config: %s", *r->cfg.file ? r->cfg.file : "none"); 414 if (r->valid) { 415 prompt_Printf(p, "\n IP: %s\n", inet_ntoa(r->ip)); 416 prompt_Printf(p, " Netmask: %s\n", inet_ntoa(r->mask)); 417 prompt_Printf(p, " MTU: %lu\n", r->mtu); 418 prompt_Printf(p, " VJ: %sabled\n", r->vj ? "en" : "dis"); 419 if (r->routes) 420 route_ShowSticky(p, r->routes, " Routes", 16); 421 } else 422 prompt_Printf(p, " (not authenticated)\n"); 423} |