00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038 #include "asterisk.h"
00039
00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 328209 $")
00041
00042 #include <curl/curl.h>
00043
00044 #include "asterisk/lock.h"
00045 #include "asterisk/file.h"
00046 #include "asterisk/channel.h"
00047 #include "asterisk/pbx.h"
00048 #include "asterisk/cli.h"
00049 #include "asterisk/module.h"
00050 #include "asterisk/app.h"
00051 #include "asterisk/utils.h"
00052 #include "asterisk/threadstorage.h"
00053
00054 #define CURLVERSION_ATLEAST(a,b,c) \
00055 ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
00056
00057 #define CURLOPT_SPECIAL_HASHCOMPAT -500
00058
00059 static void curlds_free(void *data);
00060
00061 static struct ast_datastore_info curl_info = {
00062 .type = "CURL",
00063 .destroy = curlds_free,
00064 };
00065
00066 struct curl_settings {
00067 AST_LIST_ENTRY(curl_settings) list;
00068 CURLoption key;
00069 void *value;
00070 };
00071
00072 AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
00073
00074 static void curlds_free(void *data)
00075 {
00076 AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
00077 struct curl_settings *setting;
00078 if (!list) {
00079 return;
00080 }
00081 while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
00082 free(setting);
00083 }
00084 AST_LIST_HEAD_DESTROY(list);
00085 }
00086
00087 enum optiontype {
00088 OT_BOOLEAN,
00089 OT_INTEGER,
00090 OT_INTEGER_MS,
00091 OT_STRING,
00092 OT_ENUM,
00093 };
00094
00095 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
00096 {
00097 if (!strcasecmp(name, "header")) {
00098 *key = CURLOPT_HEADER;
00099 *ot = OT_BOOLEAN;
00100 } else if (!strcasecmp(name, "proxy")) {
00101 *key = CURLOPT_PROXY;
00102 *ot = OT_STRING;
00103 } else if (!strcasecmp(name, "proxyport")) {
00104 *key = CURLOPT_PROXYPORT;
00105 *ot = OT_INTEGER;
00106 } else if (!strcasecmp(name, "proxytype")) {
00107 *key = CURLOPT_PROXYTYPE;
00108 *ot = OT_ENUM;
00109 } else if (!strcasecmp(name, "dnstimeout")) {
00110 *key = CURLOPT_DNS_CACHE_TIMEOUT;
00111 *ot = OT_INTEGER;
00112 } else if (!strcasecmp(name, "userpwd")) {
00113 *key = CURLOPT_USERPWD;
00114 *ot = OT_STRING;
00115 } else if (!strcasecmp(name, "proxyuserpwd")) {
00116 *key = CURLOPT_PROXYUSERPWD;
00117 *ot = OT_STRING;
00118 } else if (!strcasecmp(name, "maxredirs")) {
00119 *key = CURLOPT_MAXREDIRS;
00120 *ot = OT_INTEGER;
00121 } else if (!strcasecmp(name, "referer")) {
00122 *key = CURLOPT_REFERER;
00123 *ot = OT_STRING;
00124 } else if (!strcasecmp(name, "useragent")) {
00125 *key = CURLOPT_USERAGENT;
00126 *ot = OT_STRING;
00127 } else if (!strcasecmp(name, "cookie")) {
00128 *key = CURLOPT_COOKIE;
00129 *ot = OT_STRING;
00130 } else if (!strcasecmp(name, "ftptimeout")) {
00131 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
00132 *ot = OT_INTEGER;
00133 } else if (!strcasecmp(name, "httptimeout")) {
00134 #if CURLVERSION_ATLEAST(7,16,2)
00135 *key = CURLOPT_TIMEOUT_MS;
00136 *ot = OT_INTEGER_MS;
00137 #else
00138 *key = CURLOPT_TIMEOUT;
00139 *ot = OT_INTEGER;
00140 #endif
00141 } else if (!strcasecmp(name, "conntimeout")) {
00142 #if CURLVERSION_ATLEAST(7,16,2)
00143 *key = CURLOPT_CONNECTTIMEOUT_MS;
00144 *ot = OT_INTEGER_MS;
00145 #else
00146 *key = CURLOPT_CONNECTTIMEOUT;
00147 *ot = OT_INTEGER;
00148 #endif
00149 } else if (!strcasecmp(name, "ftptext")) {
00150 *key = CURLOPT_TRANSFERTEXT;
00151 *ot = OT_BOOLEAN;
00152 } else if (!strcasecmp(name, "ssl_verifypeer")) {
00153 *key = CURLOPT_SSL_VERIFYPEER;
00154 *ot = OT_BOOLEAN;
00155 } else if (!strcasecmp(name, "hashcompat")) {
00156 *key = CURLOPT_SPECIAL_HASHCOMPAT;
00157 *ot = OT_BOOLEAN;
00158 } else {
00159 return -1;
00160 }
00161 return 0;
00162 }
00163
00164 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
00165 {
00166 struct ast_datastore *store;
00167 struct global_curl_info *list;
00168 struct curl_settings *cur, *new = NULL;
00169 CURLoption key;
00170 enum optiontype ot;
00171
00172 if (chan) {
00173 if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00174
00175 if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
00176 ast_log(LOG_ERROR, "Unable to allocate new datastore. Cannot set any CURL options\n");
00177 return -1;
00178 }
00179
00180 if (!(list = ast_calloc(1, sizeof(*list)))) {
00181 ast_log(LOG_ERROR, "Unable to allocate list head. Cannot set any CURL options\n");
00182 ast_datastore_free(store);
00183 }
00184
00185 store->data = list;
00186 AST_LIST_HEAD_INIT(list);
00187 ast_channel_datastore_add(chan, store);
00188 } else {
00189 list = store->data;
00190 }
00191 } else {
00192
00193 list = &global_curl_info;
00194 }
00195
00196 if (!parse_curlopt_key(name, &key, &ot)) {
00197 if (ot == OT_BOOLEAN) {
00198 if ((new = ast_calloc(1, sizeof(*new)))) {
00199 new->value = (void *)((long) ast_true(value));
00200 }
00201 } else if (ot == OT_INTEGER) {
00202 long tmp = atol(value);
00203 if ((new = ast_calloc(1, sizeof(*new)))) {
00204 new->value = (void *)tmp;
00205 }
00206 } else if (ot == OT_INTEGER_MS) {
00207 long tmp = atof(value) * 1000.0;
00208 if ((new = ast_calloc(1, sizeof(*new)))) {
00209 new->value = (void *)tmp;
00210 }
00211 } else if (ot == OT_STRING) {
00212 if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
00213 new->value = (char *)new + sizeof(*new);
00214 strcpy(new->value, value);
00215 }
00216 } else if (ot == OT_ENUM) {
00217 if (key == CURLOPT_PROXYTYPE) {
00218 long ptype =
00219 #if CURLVERSION_ATLEAST(7,10,0)
00220 CURLPROXY_HTTP;
00221 #else
00222 CURLPROXY_SOCKS5;
00223 #endif
00224 if (0) {
00225 #if CURLVERSION_ATLEAST(7,15,2)
00226 } else if (!strcasecmp(value, "socks4")) {
00227 ptype = CURLPROXY_SOCKS4;
00228 #endif
00229 #if CURLVERSION_ATLEAST(7,18,0)
00230 } else if (!strcasecmp(value, "socks4a")) {
00231 ptype = CURLPROXY_SOCKS4A;
00232 #endif
00233 #if CURLVERSION_ATLEAST(7,18,0)
00234 } else if (!strcasecmp(value, "socks5")) {
00235 ptype = CURLPROXY_SOCKS5;
00236 #endif
00237 #if CURLVERSION_ATLEAST(7,18,0)
00238 } else if (!strncasecmp(value, "socks5", 6)) {
00239 ptype = CURLPROXY_SOCKS5_HOSTNAME;
00240 #endif
00241 }
00242
00243 if ((new = ast_calloc(1, sizeof(*new)))) {
00244 new->value = (void *)ptype;
00245 }
00246 } else {
00247
00248 goto yuck;
00249 }
00250 }
00251
00252
00253 if (!new) {
00254 return -1;
00255 }
00256
00257 new->key = key;
00258 } else {
00259 yuck:
00260 ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
00261 return -1;
00262 }
00263
00264
00265 AST_LIST_LOCK(list);
00266 AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
00267 if (cur->key == new->key) {
00268 AST_LIST_REMOVE_CURRENT(list);
00269 free(cur);
00270 break;
00271 }
00272 }
00273 AST_LIST_TRAVERSE_SAFE_END
00274
00275
00276 ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
00277 AST_LIST_INSERT_TAIL(list, new, list);
00278 AST_LIST_UNLOCK(list);
00279
00280 return 0;
00281 }
00282
00283 static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
00284 {
00285 struct ast_datastore *store;
00286 struct global_curl_info *list[2] = { &global_curl_info, NULL };
00287 struct curl_settings *cur = NULL;
00288 CURLoption key;
00289 enum optiontype ot;
00290 int i;
00291
00292 if (parse_curlopt_key(data, &key, &ot)) {
00293 ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
00294 return -1;
00295 }
00296
00297 if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00298 list[0] = store->data;
00299 list[1] = &global_curl_info;
00300 }
00301
00302 for (i = 0; i < 2; i++) {
00303 if (!list[i]) {
00304 break;
00305 }
00306 AST_LIST_LOCK(list[i]);
00307 AST_LIST_TRAVERSE(list[i], cur, list) {
00308 if (cur->key == key) {
00309 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
00310 if (buf) {
00311 snprintf(buf, len, "%ld", (long) cur->value);
00312 } else {
00313 ast_str_set(bufstr, len, "%ld", (long) cur->value);
00314 }
00315 } else if (ot == OT_INTEGER_MS) {
00316 if ((long) cur->value % 1000 == 0) {
00317 if (buf) {
00318 snprintf(buf, len, "%ld", (long)cur->value / 1000);
00319 } else {
00320 ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
00321 }
00322 } else {
00323 if (buf) {
00324 snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
00325 } else {
00326 ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
00327 }
00328 }
00329 } else if (ot == OT_STRING) {
00330 ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
00331 if (buf) {
00332 ast_copy_string(buf, cur->value, len);
00333 } else {
00334 ast_str_set(bufstr, 0, "%s", (char *) cur->value);
00335 }
00336 } else if (key == CURLOPT_PROXYTYPE) {
00337 if (0) {
00338 #if CURLVERSION_ATLEAST(7,15,2)
00339 } else if ((long)cur->value == CURLPROXY_SOCKS4) {
00340 if (buf) {
00341 ast_copy_string(buf, "socks4", len);
00342 } else {
00343 ast_str_set(bufstr, 0, "socks4");
00344 }
00345 #endif
00346 #if CURLVERSION_ATLEAST(7,18,0)
00347 } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
00348 if (buf) {
00349 ast_copy_string(buf, "socks4a", len);
00350 } else {
00351 ast_str_set(bufstr, 0, "socks4a");
00352 }
00353 #endif
00354 } else if ((long)cur->value == CURLPROXY_SOCKS5) {
00355 if (buf) {
00356 ast_copy_string(buf, "socks5", len);
00357 } else {
00358 ast_str_set(bufstr, 0, "socks5");
00359 }
00360 #if CURLVERSION_ATLEAST(7,18,0)
00361 } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
00362 if (buf) {
00363 ast_copy_string(buf, "socks5hostname", len);
00364 } else {
00365 ast_str_set(bufstr, 0, "socks5hostname");
00366 }
00367 #endif
00368 #if CURLVERSION_ATLEAST(7,10,0)
00369 } else if ((long)cur->value == CURLPROXY_HTTP) {
00370 if (buf) {
00371 ast_copy_string(buf, "http", len);
00372 } else {
00373 ast_str_set(bufstr, 0, "http");
00374 }
00375 #endif
00376 } else {
00377 if (buf) {
00378 ast_copy_string(buf, "unknown", len);
00379 } else {
00380 ast_str_set(bufstr, 0, "unknown");
00381 }
00382 }
00383 }
00384 break;
00385 }
00386 }
00387 AST_LIST_UNLOCK(list[i]);
00388 if (cur) {
00389 break;
00390 }
00391 }
00392
00393 return cur ? 0 : -1;
00394 }
00395
00396 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00397 {
00398 return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
00399 }
00400
00401 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
00402 {
00403 return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
00404 }
00405
00406 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
00407 {
00408 register int realsize = size * nmemb;
00409 struct ast_str **pstr = (struct ast_str **)data;
00410
00411 ast_debug(3, "Called with data=%p, str=%p, realsize=%d, len=%zu, used=%zu\n", data, *pstr, realsize, ast_str_size(*pstr), ast_str_strlen(*pstr));
00412
00413 ast_str_append_substr(pstr, 0, ptr, realsize);
00414
00415 ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
00416
00417 return realsize;
00418 }
00419
00420 static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
00421
00422 static int curl_instance_init(void *data)
00423 {
00424 CURL **curl = data;
00425
00426 if (!(*curl = curl_easy_init()))
00427 return -1;
00428
00429 curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
00430 curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
00431 curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
00432 curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
00433
00434 return 0;
00435 }
00436
00437 static void curl_instance_cleanup(void *data)
00438 {
00439 CURL **curl = data;
00440
00441 curl_easy_cleanup(*curl);
00442
00443 ast_free(data);
00444 }
00445
00446 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
00447
00448 static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info, char *buf, struct ast_str **input_str, ssize_t len)
00449 {
00450 struct ast_str *str = ast_str_create(16);
00451 int ret = -1;
00452 AST_DECLARE_APP_ARGS(args,
00453 AST_APP_ARG(url);
00454 AST_APP_ARG(postdata);
00455 );
00456 CURL **curl;
00457 struct curl_settings *cur;
00458 struct ast_datastore *store = NULL;
00459 int hashcompat = 0;
00460 AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
00461
00462 if (buf) {
00463 *buf = '\0';
00464 }
00465
00466 if (!str) {
00467 return -1;
00468 }
00469
00470 if (ast_strlen_zero(info)) {
00471 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
00472 ast_free(str);
00473 return -1;
00474 }
00475
00476 AST_STANDARD_APP_ARGS(args, info);
00477
00478 if (chan) {
00479 ast_autoservice_start(chan);
00480 }
00481
00482 if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
00483 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
00484 return -1;
00485 }
00486
00487 AST_LIST_LOCK(&global_curl_info);
00488 AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
00489 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00490 hashcompat = (cur->value != NULL) ? 1 : 0;
00491 } else {
00492 curl_easy_setopt(*curl, cur->key, cur->value);
00493 }
00494 }
00495
00496 if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00497 list = store->data;
00498 AST_LIST_LOCK(list);
00499 AST_LIST_TRAVERSE(list, cur, list) {
00500 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00501 hashcompat = (cur->value != NULL) ? 1 : 0;
00502 } else {
00503 curl_easy_setopt(*curl, cur->key, cur->value);
00504 }
00505 }
00506 }
00507
00508 curl_easy_setopt(*curl, CURLOPT_URL, args.url);
00509 curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
00510
00511 if (args.postdata) {
00512 curl_easy_setopt(*curl, CURLOPT_POST, 1);
00513 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
00514 }
00515
00516 curl_easy_perform(*curl);
00517
00518 if (store) {
00519 AST_LIST_UNLOCK(list);
00520 }
00521 AST_LIST_UNLOCK(&global_curl_info);
00522
00523 if (args.postdata) {
00524 curl_easy_setopt(*curl, CURLOPT_POST, 0);
00525 }
00526
00527 if (ast_str_strlen(str)) {
00528 ast_str_trim_blanks(str);
00529
00530 ast_debug(3, "str='%s'\n", ast_str_buffer(str));
00531 if (hashcompat) {
00532 char *remainder = ast_str_buffer(str);
00533 char *piece;
00534 struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
00535 struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
00536 int rowcount = 0;
00537 while (fields && values && (piece = strsep(&remainder, "&"))) {
00538 char *name = strsep(&piece, "=");
00539 if (piece) {
00540 ast_uri_decode(piece);
00541 }
00542 ast_uri_decode(name);
00543 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", name);
00544 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", S_OR(piece, ""));
00545 rowcount++;
00546 }
00547 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
00548 if (buf) {
00549 ast_copy_string(buf, ast_str_buffer(values), len);
00550 } else {
00551 ast_str_set(input_str, len, "%s", ast_str_buffer(values));
00552 }
00553 ast_free(fields);
00554 ast_free(values);
00555 } else {
00556 if (buf) {
00557 ast_copy_string(buf, ast_str_buffer(str), len);
00558 } else {
00559 ast_str_set(input_str, len, "%s", ast_str_buffer(str));
00560 }
00561 }
00562 ret = 0;
00563 }
00564 ast_free(str);
00565
00566 if (chan)
00567 ast_autoservice_stop(chan);
00568
00569 return ret;
00570 }
00571
00572 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
00573 {
00574 return acf_curl_helper(chan, cmd, info, buf, NULL, len);
00575 }
00576
00577 static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
00578 {
00579 return acf_curl_helper(chan, cmd, info, NULL, buf, len);
00580 }
00581
00582 static struct ast_custom_function acf_curl = {
00583 .name = "CURL",
00584 .synopsis = "Retrieves the contents of a URL",
00585 .syntax = "CURL(url[,post-data])",
00586 .desc =
00587 " url - URL to retrieve\n"
00588 " post-data - Optional data to send as a POST (GET is default action)\n",
00589 .read = acf_curl_exec,
00590 .read2 = acf_curl2_exec,
00591 };
00592
00593 static struct ast_custom_function acf_curlopt = {
00594 .name = "CURLOPT",
00595 .synopsis = "Set options for use with the CURL() function",
00596 .syntax = "CURLOPT(<option>)",
00597 .desc =
00598 " cookie - Send cookie with request [none]\n"
00599 " conntimeout - Number of seconds to wait for connection\n"
00600 " dnstimeout - Number of seconds to wait for DNS response\n"
00601 " ftptext - For FTP, force a text transfer (boolean)\n"
00602 " ftptimeout - For FTP, the server response timeout\n"
00603 " header - Retrieve header information (boolean)\n"
00604 " httptimeout - Number of seconds to wait for HTTP response\n"
00605 " maxredirs - Maximum number of redirects to follow\n"
00606 " proxy - Hostname or IP to use as a proxy\n"
00607 " proxytype - http, socks4, or socks5\n"
00608 " proxyport - port number of the proxy\n"
00609 " proxyuserpwd - A <user>:<pass> to use for authentication\n"
00610 " referer - Referer URL to use for the request\n"
00611 " useragent - UserAgent string to use\n"
00612 " userpwd - A <user>:<pass> to use for authentication\n"
00613 " ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
00614 " hashcompat - Result data will be compatible for use with HASH()\n"
00615 "",
00616 .read = acf_curlopt_read,
00617 .read2 = acf_curlopt_read2,
00618 .write = acf_curlopt_write,
00619 };
00620
00621 static int unload_module(void)
00622 {
00623 int res;
00624
00625 res = ast_custom_function_unregister(&acf_curl);
00626 res |= ast_custom_function_unregister(&acf_curlopt);
00627
00628 return res;
00629 }
00630
00631 static int load_module(void)
00632 {
00633 int res;
00634
00635 if (!ast_module_check("res_curl.so")) {
00636 if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
00637 ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
00638 return AST_MODULE_LOAD_DECLINE;
00639 }
00640 }
00641
00642 res = ast_custom_function_register(&acf_curl);
00643 res |= ast_custom_function_register(&acf_curlopt);
00644
00645 return res;
00646 }
00647
00648 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
00649 .load = load_module,
00650 .unload = unload_module,
00651 .load_pri = AST_MODPRI_REALTIME_DEPEND2,
00652 );
00653