00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 #include "includes.h"
00016 #include <sys/un.h>
00017 #include <sys/stat.h>
00018 #include <grp.h>
00019 #include <stddef.h>
00020
00021 #include "utils/common.h"
00022 #include "utils/eloop.h"
00023 #include "utils/list.h"
00024 #include "eapol_supp/eapol_supp_sm.h"
00025 #include "config.h"
00026 #include "wpa_supplicant_i.h"
00027 #include "ctrl_iface.h"
00028
00029
00030
00038 struct wpa_ctrl_dst {
00039 struct dl_list list;
00040 struct sockaddr_un addr;
00041 socklen_t addrlen;
00042 int debug_level;
00043 int errors;
00044 };
00045
00046
00047 struct ctrl_iface_priv {
00048 struct wpa_supplicant *wpa_s;
00049 int sock;
00050 struct dl_list ctrl_dst;
00051 };
00052
00053
00054 static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv,
00055 int level, const char *buf,
00056 size_t len);
00057
00058
00059 static int wpa_supplicant_ctrl_iface_attach(struct ctrl_iface_priv *priv,
00060 struct sockaddr_un *from,
00061 socklen_t fromlen)
00062 {
00063 struct wpa_ctrl_dst *dst;
00064
00065 dst = os_zalloc(sizeof(*dst));
00066 if (dst == NULL)
00067 return -1;
00068 os_memcpy(&dst->addr, from, sizeof(struct sockaddr_un));
00069 dst->addrlen = fromlen;
00070 dst->debug_level = MSG_INFO;
00071 dl_list_add(&priv->ctrl_dst, &dst->list);
00072 wpa_hexdump(MSG_DEBUG, "CTRL_IFACE monitor attached",
00073 (u8 *) from->sun_path,
00074 fromlen - offsetof(struct sockaddr_un, sun_path));
00075 return 0;
00076 }
00077
00078
00079 static int wpa_supplicant_ctrl_iface_detach(struct ctrl_iface_priv *priv,
00080 struct sockaddr_un *from,
00081 socklen_t fromlen)
00082 {
00083 struct wpa_ctrl_dst *dst;
00084
00085 dl_list_for_each(dst, &priv->ctrl_dst, struct wpa_ctrl_dst, list) {
00086 if (fromlen == dst->addrlen &&
00087 os_memcmp(from->sun_path, dst->addr.sun_path,
00088 fromlen - offsetof(struct sockaddr_un, sun_path))
00089 == 0) {
00090 dl_list_del(&dst->list);
00091 os_free(dst);
00092 wpa_hexdump(MSG_DEBUG, "CTRL_IFACE monitor detached",
00093 (u8 *) from->sun_path,
00094 fromlen -
00095 offsetof(struct sockaddr_un, sun_path));
00096 return 0;
00097 }
00098 }
00099 return -1;
00100 }
00101
00102
00103 static int wpa_supplicant_ctrl_iface_level(struct ctrl_iface_priv *priv,
00104 struct sockaddr_un *from,
00105 socklen_t fromlen,
00106 char *level)
00107 {
00108 struct wpa_ctrl_dst *dst;
00109
00110 wpa_printf(MSG_DEBUG, "CTRL_IFACE LEVEL %s", level);
00111
00112 dl_list_for_each(dst, &priv->ctrl_dst, struct wpa_ctrl_dst, list) {
00113 if (fromlen == dst->addrlen &&
00114 os_memcmp(from->sun_path, dst->addr.sun_path,
00115 fromlen - offsetof(struct sockaddr_un, sun_path))
00116 == 0) {
00117 wpa_hexdump(MSG_DEBUG, "CTRL_IFACE changed monitor "
00118 "level", (u8 *) from->sun_path,
00119 fromlen -
00120 offsetof(struct sockaddr_un, sun_path));
00121 dst->debug_level = atoi(level);
00122 return 0;
00123 }
00124 }
00125
00126 return -1;
00127 }
00128
00129
00130 static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx,
00131 void *sock_ctx)
00132 {
00133 struct wpa_supplicant *wpa_s = eloop_ctx;
00134 struct ctrl_iface_priv *priv = sock_ctx;
00135 char buf[256];
00136 int res;
00137 struct sockaddr_un from;
00138 socklen_t fromlen = sizeof(from);
00139 char *reply = NULL;
00140 size_t reply_len = 0;
00141 int new_attached = 0;
00142
00143 res = recvfrom(sock, buf, sizeof(buf) - 1, 0,
00144 (struct sockaddr *) &from, &fromlen);
00145 if (res < 0) {
00146 perror("recvfrom(ctrl_iface)");
00147 return;
00148 }
00149 buf[res] = '\0';
00150
00151 if (os_strcmp(buf, "ATTACH") == 0) {
00152 if (wpa_supplicant_ctrl_iface_attach(priv, &from, fromlen))
00153 reply_len = 1;
00154 else {
00155 new_attached = 1;
00156 reply_len = 2;
00157 }
00158 } else if (os_strcmp(buf, "DETACH") == 0) {
00159 if (wpa_supplicant_ctrl_iface_detach(priv, &from, fromlen))
00160 reply_len = 1;
00161 else
00162 reply_len = 2;
00163 } else if (os_strncmp(buf, "LEVEL ", 6) == 0) {
00164 if (wpa_supplicant_ctrl_iface_level(priv, &from, fromlen,
00165 buf + 6))
00166 reply_len = 1;
00167 else
00168 reply_len = 2;
00169 } else {
00170 reply = wpa_supplicant_ctrl_iface_process(wpa_s, buf,
00171 &reply_len);
00172 }
00173
00174 if (reply) {
00175 sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from,
00176 fromlen);
00177 os_free(reply);
00178 } else if (reply_len == 1) {
00179 sendto(sock, "FAIL\n", 5, 0, (struct sockaddr *) &from,
00180 fromlen);
00181 } else if (reply_len == 2) {
00182 sendto(sock, "OK\n", 3, 0, (struct sockaddr *) &from,
00183 fromlen);
00184 }
00185
00186 if (new_attached)
00187 eapol_sm_notify_ctrl_attached(wpa_s->eapol);
00188 }
00189
00190
00191 static char * wpa_supplicant_ctrl_iface_path(struct wpa_supplicant *wpa_s)
00192 {
00193 char *buf;
00194 size_t len;
00195 char *pbuf, *dir = NULL, *gid_str = NULL;
00196 int res;
00197
00198 if (wpa_s->conf->ctrl_interface == NULL)
00199 return NULL;
00200
00201 pbuf = os_strdup(wpa_s->conf->ctrl_interface);
00202 if (pbuf == NULL)
00203 return NULL;
00204 if (os_strncmp(pbuf, "DIR=", 4) == 0) {
00205 dir = pbuf + 4;
00206 gid_str = os_strstr(dir, " GROUP=");
00207 if (gid_str) {
00208 *gid_str = '\0';
00209 gid_str += 7;
00210 }
00211 } else
00212 dir = pbuf;
00213
00214 len = os_strlen(dir) + os_strlen(wpa_s->ifname) + 2;
00215 buf = os_malloc(len);
00216 if (buf == NULL) {
00217 os_free(pbuf);
00218 return NULL;
00219 }
00220
00221 res = os_snprintf(buf, len, "%s/%s", dir, wpa_s->ifname);
00222 if (res < 0 || (size_t) res >= len) {
00223 os_free(pbuf);
00224 os_free(buf);
00225 return NULL;
00226 }
00227 #ifdef __CYGWIN__
00228 {
00229
00230
00231 char *pos = buf;
00232 while (*pos) {
00233 if (*pos == '\\')
00234 *pos = '_';
00235 pos++;
00236 }
00237 }
00238 #endif
00239 os_free(pbuf);
00240 return buf;
00241 }
00242
00243
00244 static void wpa_supplicant_ctrl_iface_msg_cb(void *ctx, int level,
00245 const char *txt, size_t len)
00246 {
00247 struct wpa_supplicant *wpa_s = ctx;
00248 if (wpa_s == NULL || wpa_s->ctrl_iface == NULL)
00249 return;
00250 wpa_supplicant_ctrl_iface_send(wpa_s->ctrl_iface, level, txt, len);
00251 }
00252
00253
00254 struct ctrl_iface_priv *
00255 wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s)
00256 {
00257 struct ctrl_iface_priv *priv;
00258 struct sockaddr_un addr;
00259 char *fname = NULL;
00260 gid_t gid = 0;
00261 int gid_set = 0;
00262 char *buf, *dir = NULL, *gid_str = NULL;
00263 struct group *grp;
00264 char *endp;
00265
00266 priv = os_zalloc(sizeof(*priv));
00267 if (priv == NULL)
00268 return NULL;
00269 dl_list_init(&priv->ctrl_dst);
00270 priv->wpa_s = wpa_s;
00271 priv->sock = -1;
00272
00273 if (wpa_s->conf->ctrl_interface == NULL)
00274 return priv;
00275
00276 buf = os_strdup(wpa_s->conf->ctrl_interface);
00277 if (buf == NULL)
00278 goto fail;
00279 if (os_strncmp(buf, "DIR=", 4) == 0) {
00280 dir = buf + 4;
00281 gid_str = os_strstr(dir, " GROUP=");
00282 if (gid_str) {
00283 *gid_str = '\0';
00284 gid_str += 7;
00285 }
00286 } else {
00287 dir = buf;
00288 gid_str = wpa_s->conf->ctrl_interface_group;
00289 }
00290
00291 if (mkdir(dir, S_IRWXU | S_IRWXG) < 0) {
00292 if (errno == EEXIST) {
00293 wpa_printf(MSG_DEBUG, "Using existing control "
00294 "interface directory.");
00295 } else {
00296 perror("mkdir[ctrl_interface]");
00297 goto fail;
00298 }
00299 }
00300
00301 if (gid_str) {
00302 grp = getgrnam(gid_str);
00303 if (grp) {
00304 gid = grp->gr_gid;
00305 gid_set = 1;
00306 wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d"
00307 " (from group name '%s')",
00308 (int) gid, gid_str);
00309 } else {
00310
00311 gid = strtol(gid_str, &endp, 10);
00312 if (*gid_str == '\0' || *endp != '\0') {
00313 wpa_printf(MSG_ERROR, "CTRL: Invalid group "
00314 "'%s'", gid_str);
00315 goto fail;
00316 }
00317 gid_set = 1;
00318 wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d",
00319 (int) gid);
00320 }
00321 }
00322
00323 if (gid_set && chown(dir, -1, gid) < 0) {
00324 perror("chown[ctrl_interface]");
00325 goto fail;
00326 }
00327
00328
00329 if (gid_set &&
00330 chmod(dir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP) < 0) {
00331 wpa_printf(MSG_ERROR, "CTRL: chmod[ctrl_interface]: %s",
00332 strerror(errno));
00333 goto fail;
00334 }
00335
00336 if (os_strlen(dir) + 1 + os_strlen(wpa_s->ifname) >=
00337 sizeof(addr.sun_path)) {
00338 wpa_printf(MSG_ERROR, "ctrl_iface path limit exceeded");
00339 goto fail;
00340 }
00341
00342 priv->sock = socket(PF_UNIX, SOCK_DGRAM, 0);
00343 if (priv->sock < 0) {
00344 perror("socket(PF_UNIX)");
00345 goto fail;
00346 }
00347
00348 os_memset(&addr, 0, sizeof(addr));
00349 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
00350 addr.sun_len = sizeof(addr);
00351 #endif
00352 addr.sun_family = AF_UNIX;
00353 fname = wpa_supplicant_ctrl_iface_path(wpa_s);
00354 if (fname == NULL)
00355 goto fail;
00356 os_strlcpy(addr.sun_path, fname, sizeof(addr.sun_path));
00357 if (bind(priv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
00358 wpa_printf(MSG_DEBUG, "ctrl_iface bind(PF_UNIX) failed: %s",
00359 strerror(errno));
00360 if (connect(priv->sock, (struct sockaddr *) &addr,
00361 sizeof(addr)) < 0) {
00362 wpa_printf(MSG_DEBUG, "ctrl_iface exists, but does not"
00363 " allow connections - assuming it was left"
00364 "over from forced program termination");
00365 if (unlink(fname) < 0) {
00366 perror("unlink[ctrl_iface]");
00367 wpa_printf(MSG_ERROR, "Could not unlink "
00368 "existing ctrl_iface socket '%s'",
00369 fname);
00370 goto fail;
00371 }
00372 if (bind(priv->sock, (struct sockaddr *) &addr,
00373 sizeof(addr)) < 0) {
00374 perror("bind(PF_UNIX)");
00375 goto fail;
00376 }
00377 wpa_printf(MSG_DEBUG, "Successfully replaced leftover "
00378 "ctrl_iface socket '%s'", fname);
00379 } else {
00380 wpa_printf(MSG_INFO, "ctrl_iface exists and seems to "
00381 "be in use - cannot override it");
00382 wpa_printf(MSG_INFO, "Delete '%s' manually if it is "
00383 "not used anymore", fname);
00384 os_free(fname);
00385 fname = NULL;
00386 goto fail;
00387 }
00388 }
00389
00390 if (gid_set && chown(fname, -1, gid) < 0) {
00391 perror("chown[ctrl_interface/ifname]");
00392 goto fail;
00393 }
00394
00395 if (chmod(fname, S_IRWXU | S_IRWXG) < 0) {
00396 perror("chmod[ctrl_interface/ifname]");
00397 goto fail;
00398 }
00399 os_free(fname);
00400
00401 eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive,
00402 wpa_s, priv);
00403 wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);
00404
00405 os_free(buf);
00406 return priv;
00407
00408 fail:
00409 if (priv->sock >= 0)
00410 close(priv->sock);
00411 os_free(priv);
00412 if (fname) {
00413 unlink(fname);
00414 os_free(fname);
00415 }
00416 os_free(buf);
00417 return NULL;
00418 }
00419
00420
00421 void wpa_supplicant_ctrl_iface_deinit(struct ctrl_iface_priv *priv)
00422 {
00423 struct wpa_ctrl_dst *dst, *prev;
00424
00425 if (priv->sock > -1) {
00426 char *fname;
00427 char *buf, *dir = NULL, *gid_str = NULL;
00428 eloop_unregister_read_sock(priv->sock);
00429 if (!dl_list_empty(&priv->ctrl_dst)) {
00430
00431
00432
00433
00434
00435 wpa_printf(MSG_DEBUG, "CTRL_IFACE wait for attached "
00436 "monitors to receive messages");
00437 os_sleep(1, 0);
00438 }
00439 close(priv->sock);
00440 priv->sock = -1;
00441 fname = wpa_supplicant_ctrl_iface_path(priv->wpa_s);
00442 if (fname) {
00443 unlink(fname);
00444 os_free(fname);
00445 }
00446
00447 buf = os_strdup(priv->wpa_s->conf->ctrl_interface);
00448 if (buf == NULL)
00449 goto free_dst;
00450 if (os_strncmp(buf, "DIR=", 4) == 0) {
00451 dir = buf + 4;
00452 gid_str = os_strstr(dir, " GROUP=");
00453 if (gid_str) {
00454 *gid_str = '\0';
00455 gid_str += 7;
00456 }
00457 } else
00458 dir = buf;
00459
00460 if (rmdir(dir) < 0) {
00461 if (errno == ENOTEMPTY) {
00462 wpa_printf(MSG_DEBUG, "Control interface "
00463 "directory not empty - leaving it "
00464 "behind");
00465 } else {
00466 perror("rmdir[ctrl_interface]");
00467 }
00468 }
00469 os_free(buf);
00470 }
00471
00472 free_dst:
00473 dl_list_for_each_safe(dst, prev, &priv->ctrl_dst, struct wpa_ctrl_dst,
00474 list)
00475 os_free(dst);
00476 os_free(priv);
00477 }
00478
00479
00489 static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv,
00490 int level, const char *buf,
00491 size_t len)
00492 {
00493 struct wpa_ctrl_dst *dst, *next;
00494 char levelstr[10];
00495 int idx, res;
00496 struct msghdr msg;
00497 struct iovec io[2];
00498
00499 if (priv->sock < 0 || dl_list_empty(&priv->ctrl_dst))
00500 return;
00501
00502 res = os_snprintf(levelstr, sizeof(levelstr), "<%d>", level);
00503 if (res < 0 || (size_t) res >= sizeof(levelstr))
00504 return;
00505 io[0].iov_base = levelstr;
00506 io[0].iov_len = os_strlen(levelstr);
00507 io[1].iov_base = (char *) buf;
00508 io[1].iov_len = len;
00509 os_memset(&msg, 0, sizeof(msg));
00510 msg.msg_iov = io;
00511 msg.msg_iovlen = 2;
00512
00513 idx = 0;
00514 dl_list_for_each_safe(dst, next, &priv->ctrl_dst, struct wpa_ctrl_dst,
00515 list) {
00516 if (level >= dst->debug_level) {
00517 wpa_hexdump(MSG_DEBUG, "CTRL_IFACE monitor send",
00518 (u8 *) dst->addr.sun_path, dst->addrlen -
00519 offsetof(struct sockaddr_un, sun_path));
00520 msg.msg_name = (void *) &dst->addr;
00521 msg.msg_namelen = dst->addrlen;
00522 if (sendmsg(priv->sock, &msg, 0) < 0) {
00523 int _errno = errno;
00524 wpa_printf(MSG_INFO, "CTRL_IFACE monitor[%d]: "
00525 "%d - %s",
00526 idx, errno, strerror(errno));
00527 dst->errors++;
00528 if (dst->errors > 1000 ||
00529 (_errno != ENOBUFS && dst->errors > 10) ||
00530 _errno == ENOENT) {
00531 wpa_supplicant_ctrl_iface_detach(
00532 priv, &dst->addr,
00533 dst->addrlen);
00534 }
00535 } else
00536 dst->errors = 0;
00537 }
00538 idx++;
00539 }
00540 }
00541
00542
00543 void wpa_supplicant_ctrl_iface_wait(struct ctrl_iface_priv *priv)
00544 {
00545 char buf[256];
00546 int res;
00547 struct sockaddr_un from;
00548 socklen_t fromlen = sizeof(from);
00549
00550 for (;;) {
00551 wpa_printf(MSG_DEBUG, "CTRL_IFACE - %s - wait for monitor to "
00552 "attach", priv->wpa_s->ifname);
00553 eloop_wait_for_read_sock(priv->sock);
00554
00555 res = recvfrom(priv->sock, buf, sizeof(buf) - 1, 0,
00556 (struct sockaddr *) &from, &fromlen);
00557 if (res < 0) {
00558 perror("recvfrom(ctrl_iface)");
00559 continue;
00560 }
00561 buf[res] = '\0';
00562
00563 if (os_strcmp(buf, "ATTACH") == 0) {
00564
00565 if (!wpa_supplicant_ctrl_iface_attach(priv, &from,
00566 fromlen)) {
00567 sendto(priv->sock, "OK\n", 3, 0,
00568 (struct sockaddr *) &from, fromlen);
00569
00570 return;
00571 } else {
00572 sendto(priv->sock, "FAIL\n", 5, 0,
00573 (struct sockaddr *) &from, fromlen);
00574 }
00575 } else {
00576
00577 sendto(priv->sock, "FAIL\n", 5, 0,
00578 (struct sockaddr *) &from, fromlen);
00579 }
00580 }
00581 }
00582
00583
00584
00585
00586 struct ctrl_iface_global_priv {
00587 struct wpa_global *global;
00588 int sock;
00589 };
00590
00591
00592 static void wpa_supplicant_global_ctrl_iface_receive(int sock, void *eloop_ctx,
00593 void *sock_ctx)
00594 {
00595 struct wpa_global *global = eloop_ctx;
00596 char buf[256];
00597 int res;
00598 struct sockaddr_un from;
00599 socklen_t fromlen = sizeof(from);
00600 char *reply;
00601 size_t reply_len;
00602
00603 res = recvfrom(sock, buf, sizeof(buf) - 1, 0,
00604 (struct sockaddr *) &from, &fromlen);
00605 if (res < 0) {
00606 perror("recvfrom(ctrl_iface)");
00607 return;
00608 }
00609 buf[res] = '\0';
00610
00611 reply = wpa_supplicant_global_ctrl_iface_process(global, buf,
00612 &reply_len);
00613
00614 if (reply) {
00615 sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from,
00616 fromlen);
00617 os_free(reply);
00618 } else if (reply_len) {
00619 sendto(sock, "FAIL\n", 5, 0, (struct sockaddr *) &from,
00620 fromlen);
00621 }
00622 }
00623
00624
00625 struct ctrl_iface_global_priv *
00626 wpa_supplicant_global_ctrl_iface_init(struct wpa_global *global)
00627 {
00628 struct ctrl_iface_global_priv *priv;
00629 struct sockaddr_un addr;
00630
00631 priv = os_zalloc(sizeof(*priv));
00632 if (priv == NULL)
00633 return NULL;
00634 priv->global = global;
00635 priv->sock = -1;
00636
00637 if (global->params.ctrl_interface == NULL)
00638 return priv;
00639
00640 wpa_printf(MSG_DEBUG, "Global control interface '%s'",
00641 global->params.ctrl_interface);
00642
00643 priv->sock = socket(PF_UNIX, SOCK_DGRAM, 0);
00644 if (priv->sock < 0) {
00645 perror("socket(PF_UNIX)");
00646 goto fail;
00647 }
00648
00649 os_memset(&addr, 0, sizeof(addr));
00650 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
00651 addr.sun_len = sizeof(addr);
00652 #endif
00653 addr.sun_family = AF_UNIX;
00654 os_strlcpy(addr.sun_path, global->params.ctrl_interface,
00655 sizeof(addr.sun_path));
00656 if (bind(priv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
00657 perror("bind(PF_UNIX)");
00658 if (connect(priv->sock, (struct sockaddr *) &addr,
00659 sizeof(addr)) < 0) {
00660 wpa_printf(MSG_DEBUG, "ctrl_iface exists, but does not"
00661 " allow connections - assuming it was left"
00662 "over from forced program termination");
00663 if (unlink(global->params.ctrl_interface) < 0) {
00664 perror("unlink[ctrl_iface]");
00665 wpa_printf(MSG_ERROR, "Could not unlink "
00666 "existing ctrl_iface socket '%s'",
00667 global->params.ctrl_interface);
00668 goto fail;
00669 }
00670 if (bind(priv->sock, (struct sockaddr *) &addr,
00671 sizeof(addr)) < 0) {
00672 perror("bind(PF_UNIX)");
00673 goto fail;
00674 }
00675 wpa_printf(MSG_DEBUG, "Successfully replaced leftover "
00676 "ctrl_iface socket '%s'",
00677 global->params.ctrl_interface);
00678 } else {
00679 wpa_printf(MSG_INFO, "ctrl_iface exists and seems to "
00680 "be in use - cannot override it");
00681 wpa_printf(MSG_INFO, "Delete '%s' manually if it is "
00682 "not used anymore",
00683 global->params.ctrl_interface);
00684 goto fail;
00685 }
00686 }
00687
00688 eloop_register_read_sock(priv->sock,
00689 wpa_supplicant_global_ctrl_iface_receive,
00690 global, NULL);
00691
00692 return priv;
00693
00694 fail:
00695 if (priv->sock >= 0)
00696 close(priv->sock);
00697 os_free(priv);
00698 return NULL;
00699 }
00700
00701
00702 void
00703 wpa_supplicant_global_ctrl_iface_deinit(struct ctrl_iface_global_priv *priv)
00704 {
00705 if (priv->sock >= 0) {
00706 eloop_unregister_read_sock(priv->sock);
00707 close(priv->sock);
00708 }
00709 if (priv->global->params.ctrl_interface)
00710 unlink(priv->global->params.ctrl_interface);
00711 os_free(priv);
00712 }