File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / common / sudo_debug.c
Revision 1.1.1.5 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Sun Jun 15 16:12:54 2014 UTC (10 years ago) by misho
Branches: sudo, MAIN
CVS tags: v1_8_10p3_0, v1_8_10p3, HEAD
sudo v 1.8.10p3

    1: /*
    2:  * Copyright (c) 2011-2013 Todd C. Miller <Todd.Miller@courtesan.com>
    3:  *
    4:  * Permission to use, copy, modify, and distribute this software for any
    5:  * purpose with or without fee is hereby granted, provided that the above
    6:  * copyright notice and this permission notice appear in all copies.
    7:  *
    8:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    9:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   10:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   11:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   12:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   13:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   14:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   15:  */
   16: 
   17: #include <config.h>
   18: 
   19: #include <sys/types.h>
   20: #include <sys/stat.h>
   21: #include <sys/uio.h>
   22: #include <stdio.h>
   23: #ifdef STDC_HEADERS
   24: # include <stdlib.h>
   25: # include <stddef.h>
   26: #else
   27: # ifdef HAVE_STDLIB_H
   28: #  include <stdlib.h>
   29: # endif
   30: #endif /* STDC_HEADERS */
   31: #ifdef HAVE_STDBOOL_H
   32: # include <stdbool.h>
   33: #else
   34: # include "compat/stdbool.h"
   35: #endif
   36: #ifdef HAVE_STRING_H
   37: # include <string.h>
   38: #endif /* HAVE_STRING_H */
   39: #ifdef HAVE_STRINGS_H
   40: # include <strings.h>
   41: #endif /* HAVE_STRINGS_H */
   42: #ifdef HAVE_UNISTD_H
   43: # include <unistd.h>
   44: #endif /* HAVE_UNISTD_H */
   45: #include <ctype.h>
   46: #include <errno.h>
   47: #include <fcntl.h>
   48: #include <time.h>
   49: 
   50: #define DEFAULT_TEXT_DOMAIN	"sudo"
   51: #include "gettext.h"		/* must be included before missing.h */
   52: 
   53: #include "missing.h"
   54: #include "alloc.h"
   55: #include "fatal.h"
   56: #include "sudo_plugin.h"
   57: #include "sudo_debug.h"
   58: #include "sudo_util.h"
   59: 
   60: /*
   61:  * The debug priorities and subsystems are currently hard-coded.
   62:  * In the future we might consider allowing plugins to register their
   63:  * own subsystems and provide direct access to the debugging API.
   64:  */
   65: 
   66: /* Note: this must match the order in sudo_debug.h */
   67: const char *const sudo_debug_priorities[] = {
   68:     "crit",
   69:     "err",
   70:     "warn",
   71:     "notice",
   72:     "diag",
   73:     "info",
   74:     "trace",
   75:     "debug",
   76:     NULL
   77: };
   78: 
   79: /* Note: this must match the order in sudo_debug.h */
   80: const char *const sudo_debug_subsystems[] = {
   81:     "main",
   82:     "args",
   83:     "exec",
   84:     "pty",
   85:     "utmp",
   86:     "conv",
   87:     "pcomm",
   88:     "util",
   89:     "netif",
   90:     "audit",
   91:     "edit",
   92:     "selinux",
   93:     "ldap",
   94:     "match",
   95:     "parser",
   96:     "alias",
   97:     "defaults",
   98:     "auth",
   99:     "env",
  100:     "logging",
  101:     "nss",
  102:     "rbtree",
  103:     "perms",
  104:     "plugin",
  105:     "hooks",
  106:     "sssd",
  107:     "event",
  108:     NULL
  109: };
  110: 
  111: #define NUM_SUBSYSTEMS	(sizeof(sudo_debug_subsystems) / sizeof(sudo_debug_subsystems[0]) - 1)
  112: 
  113: /* Values for sudo_debug_mode */
  114: #define SUDO_DEBUG_MODE_DISABLED	0
  115: #define SUDO_DEBUG_MODE_FILE		1
  116: #define SUDO_DEBUG_MODE_CONV		2
  117: 
  118: static int sudo_debug_settings[NUM_SUBSYSTEMS];
  119: static int sudo_debug_fd = -1;
  120: static int sudo_debug_mode;
  121: static char sudo_debug_pidstr[(((sizeof(int) * 8) + 2) / 3) + 3];
  122: static size_t sudo_debug_pidlen;
  123: static const int num_subsystems = NUM_SUBSYSTEMS;
  124: 
  125: /*
  126:  * Parse settings string from sudo.conf and open debugfile.
  127:  * Returns 1 on success, 0 if cannot open debugfile.
  128:  * Unsupported subsystems and priorities are silently ignored.
  129:  */
  130: int sudo_debug_init(const char *debugfile, const char *settings)
  131: {
  132:     char *buf, *cp, *subsys, *pri;
  133:     int i, j;
  134: 
  135:     /* Make sure we are not already initialized. */
  136:     if (sudo_debug_mode != SUDO_DEBUG_MODE_DISABLED)
  137: 	return 1;
  138: 
  139:     /* Init per-subsystems settings to -1 since 0 is a valid priority. */
  140:     for (i = 0; i < num_subsystems; i++)
  141: 	sudo_debug_settings[i] = -1;
  142: 
  143:     /* Open debug file if specified. */
  144:     if (debugfile != NULL) {
  145: 	if (sudo_debug_fd != -1)
  146: 	    close(sudo_debug_fd);
  147: 	sudo_debug_fd = open(debugfile, O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR);
  148: 	if (sudo_debug_fd == -1) {
  149: 	    /* Create debug file as needed and set group ownership. */
  150: 	    if (errno == ENOENT) {
  151: 		sudo_debug_fd = open(debugfile, O_WRONLY|O_APPEND|O_CREAT,
  152: 		    S_IRUSR|S_IWUSR);
  153: 	    }
  154: 	    if (sudo_debug_fd == -1)
  155: 		return 0;
  156: 	    ignore_result(fchown(sudo_debug_fd, (uid_t)-1, 0));
  157: 	}
  158: 	(void)fcntl(sudo_debug_fd, F_SETFD, FD_CLOEXEC);
  159: 	sudo_debug_mode = SUDO_DEBUG_MODE_FILE;
  160:     } else {
  161: 	/* Called from the plugin, no debug file. */
  162: 	sudo_debug_mode = SUDO_DEBUG_MODE_CONV;
  163:     }
  164: 
  165:     /* Stash the pid string so we only have to format it once. */
  166:     (void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ",
  167: 	(int)getpid());
  168:     sudo_debug_pidlen = strlen(sudo_debug_pidstr);
  169: 
  170:     /* Parse settings string. */
  171:     if ((buf = strdup(settings)) == NULL)
  172: 	return 0;
  173:     for ((cp = strtok(buf, ",")); cp != NULL; (cp = strtok(NULL, ","))) {
  174: 	/* Should be in the form subsys@pri. */
  175: 	subsys = cp;
  176: 	if ((pri = strchr(cp, '@')) == NULL)
  177: 	    continue;
  178: 	*pri++ = '\0';
  179: 
  180: 	/* Look up priority and subsystem, fill in sudo_debug_settings[]. */
  181: 	for (i = 0; sudo_debug_priorities[i] != NULL; i++) {
  182: 	    if (strcasecmp(pri, sudo_debug_priorities[i]) == 0) {
  183: 		for (j = 0; sudo_debug_subsystems[j] != NULL; j++) {
  184: 		    if (strcasecmp(subsys, "all") == 0) {
  185: 			sudo_debug_settings[j] = i;
  186: 			continue;
  187: 		    }
  188: 		    if (strcasecmp(subsys, sudo_debug_subsystems[j]) == 0) {
  189: 			sudo_debug_settings[j] = i;
  190: 			break;
  191: 		    }
  192: 		}
  193: 		break;
  194: 	    }
  195: 	}
  196:     }
  197:     free(buf);
  198: 
  199:     return 1;
  200: }
  201: 
  202: pid_t
  203: sudo_debug_fork(void)
  204: {
  205:     pid_t pid;
  206: 
  207:     if ((pid = fork()) == 0) {
  208: 	(void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ",
  209: 	    (int)getpid());
  210: 	sudo_debug_pidlen = strlen(sudo_debug_pidstr);
  211:     }
  212: 
  213:     return pid;
  214: }
  215: 
  216: void
  217: sudo_debug_enter(const char *func, const char *file, int line,
  218:     int subsys)
  219: {
  220:     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  221: 	"-> %s @ %s:%d", func, file, line);
  222: }
  223: 
  224: void
  225: sudo_debug_exit(const char *func, const char *file, int line,
  226:     int subsys)
  227: {
  228:     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  229: 	"<- %s @ %s:%d", func, file, line);
  230: }
  231: 
  232: void
  233: sudo_debug_exit_int(const char *func, const char *file, int line,
  234:     int subsys, int rval)
  235: {
  236:     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  237: 	"<- %s @ %s:%d := %d", func, file, line, rval);
  238: }
  239: 
  240: void
  241: sudo_debug_exit_long(const char *func, const char *file, int line,
  242:     int subsys, long rval)
  243: {
  244:     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  245: 	"<- %s @ %s:%d := %ld", func, file, line, rval);
  246: }
  247: 
  248: void
  249: sudo_debug_exit_size_t(const char *func, const char *file, int line,
  250:     int subsys, size_t rval)
  251: {
  252:     /* XXX - should use %zu but our snprintf.c doesn't support it */
  253:     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  254: 	"<- %s @ %s:%d := %lu", func, file, line, (unsigned long)rval);
  255: }
  256: 
  257: /* We use int, not bool, here for functions that return -1 on error. */
  258: void
  259: sudo_debug_exit_bool(const char *func, const char *file, int line,
  260:     int subsys, int rval)
  261: {
  262:     if (rval == true || rval == false) {
  263: 	sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  264: 	    "<- %s @ %s:%d := %s", func, file, line, rval ? "true" : "false");
  265:     } else {
  266: 	sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  267: 	    "<- %s @ %s:%d := %d", func, file, line, rval);
  268:     }
  269: }
  270: 
  271: void
  272: sudo_debug_exit_str(const char *func, const char *file, int line,
  273:     int subsys, const char *rval)
  274: {
  275:     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  276: 	"<- %s @ %s:%d := %s", func, file, line, rval ? rval : "(null)");
  277: }
  278: 
  279: void
  280: sudo_debug_exit_str_masked(const char *func, const char *file, int line,
  281:     int subsys, const char *rval)
  282: {
  283:     static const char stars[] = "********************************************************************************";
  284:     int len = rval ? strlen(rval) : sizeof("(null)") - 1;
  285: 
  286:     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  287: 	"<- %s @ %s:%d := %.*s", func, file, line, len, rval ? stars : "(null)");
  288: }
  289: 
  290: void
  291: sudo_debug_exit_ptr(const char *func, const char *file, int line,
  292:     int subsys, const void *rval)
  293: {
  294:     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
  295: 	"<- %s @ %s:%d := %p", func, file, line, rval);
  296: }
  297: 
  298: static void
  299: sudo_debug_write_conv(const char *func, const char *file, int lineno,
  300:     const char *str, int len, int errno_val)
  301: {
  302:     /* Remove trailing newlines. */
  303:     while (len > 0 && str[len - 1] == '\n')
  304: 	len--;
  305: 
  306:     if (len > 0) {
  307: 	if (func != NULL && file != NULL) {
  308: 	    if (errno_val) {
  309: 		sudo_printf(SUDO_CONV_DEBUG_MSG, "%.*s: %s @ %s() %s:%d",
  310: 		    len, str, strerror(errno_val), func, file, lineno);
  311: 	    } else {
  312: 		sudo_printf(SUDO_CONV_DEBUG_MSG, "%.*s @ %s() %s:%d",
  313: 		    len, str, func, file, lineno);
  314: 	    }
  315: 	} else {
  316: 	    if (errno_val) {
  317: 		sudo_printf(SUDO_CONV_DEBUG_MSG, "%.*s: %s",
  318: 		    len, str, strerror(errno_val));
  319: 	    } else {
  320: 		sudo_printf(SUDO_CONV_DEBUG_MSG, "%.*s", len, str);
  321: 	    }
  322: 	}
  323:     } else if (errno_val) {
  324: 	/* Only print error string. */
  325: 	if (func != NULL && file != NULL) {
  326: 	    sudo_printf(SUDO_CONV_DEBUG_MSG, "%s @ %s() %s:%d",
  327: 		strerror(errno_val), func, file, lineno);
  328: 	} else {
  329: 	    sudo_printf(SUDO_CONV_DEBUG_MSG, "%s", strerror(errno_val));
  330: 	}
  331:     }
  332: }
  333: 
  334: static void
  335: sudo_debug_write_file(const char *func, const char *file, int lineno,
  336:     const char *str, int len, int errno_val)
  337: {
  338:     char *timestr, numbuf[(((sizeof(int) * 8) + 2) / 3) + 2];
  339:     time_t now;
  340:     struct iovec iov[12];
  341:     int iovcnt = 3;
  342: 
  343:     /* Prepend program name and pid with a trailing space. */
  344:     iov[1].iov_base = (char *)getprogname();
  345:     iov[1].iov_len = strlen(iov[1].iov_base);
  346:     iov[2].iov_base = sudo_debug_pidstr;
  347:     iov[2].iov_len = sudo_debug_pidlen;
  348: 
  349:     /* Add string, trimming any trailing newlines. */
  350:     while (len > 0 && str[len - 1] == '\n')
  351: 	len--;
  352:     if (len > 0) {
  353: 	iov[iovcnt].iov_base = (char *)str;
  354: 	iov[iovcnt].iov_len = len;
  355: 	iovcnt++;
  356:     }
  357: 
  358:     /* Append error string if errno is specified. */
  359:     if (errno_val) {
  360: 	if (len > 0) {
  361: 	    iov[iovcnt].iov_base = ": ";
  362: 	    iov[iovcnt].iov_len = 2;
  363: 	    iovcnt++;
  364: 	}
  365: 	iov[iovcnt].iov_base = strerror(errno_val);
  366: 	iov[iovcnt].iov_len = strlen(iov[iovcnt].iov_base);
  367: 	iovcnt++;
  368:     }
  369: 
  370:     /* If function, file and lineno are specified, append them. */
  371:     if (func != NULL && file != NULL && lineno != 0) {
  372: 	iov[iovcnt].iov_base = " @ ";
  373: 	iov[iovcnt].iov_len = 3;
  374: 	iovcnt++;
  375: 
  376: 	iov[iovcnt].iov_base = (char *)func;
  377: 	iov[iovcnt].iov_len = strlen(func);
  378: 	iovcnt++;
  379: 
  380: 	iov[iovcnt].iov_base = "() ";
  381: 	iov[iovcnt].iov_len = 3;
  382: 	iovcnt++;
  383: 
  384: 	iov[iovcnt].iov_base = (char *)file;
  385: 	iov[iovcnt].iov_len = strlen(file);
  386: 	iovcnt++;
  387: 
  388: 	(void)snprintf(numbuf, sizeof(numbuf), ":%d", lineno);
  389: 	iov[iovcnt].iov_base = numbuf;
  390: 	iov[iovcnt].iov_len = strlen(numbuf);
  391: 	iovcnt++;
  392:     }
  393: 
  394:     /* Append newline. */
  395:     iov[iovcnt].iov_base = "\n";
  396:     iov[iovcnt].iov_len = 1;
  397:     iovcnt++;
  398: 
  399:     /* Do timestamp last due to ctime's static buffer. */
  400:     time(&now);
  401:     timestr = ctime(&now) + 4;
  402:     timestr[15] = ' ';	/* replace year with a space */
  403:     timestr[16] = '\0';
  404:     iov[0].iov_base = timestr;
  405:     iov[0].iov_len = 16;
  406: 
  407:     /* Write message in a single syscall */
  408:     ignore_result(writev(sudo_debug_fd, iov, iovcnt));
  409: }
  410: 
  411: void
  412: sudo_debug_write2(const char *func, const char *file, int lineno,
  413:     const char *str, int len, int errno_val)
  414: {
  415:     switch (sudo_debug_mode) {
  416:     case SUDO_DEBUG_MODE_CONV:
  417: 	sudo_debug_write_conv(func, file, lineno, str, len, errno_val);
  418: 	break;
  419:     case SUDO_DEBUG_MODE_FILE:
  420: 	sudo_debug_write_file(func, file, lineno, str, len, errno_val);
  421: 	break;
  422:     }
  423: }
  424: 
  425: /* XXX - turn into a macro */
  426: void
  427: sudo_debug_write(const char *str, int len, int errno_val)
  428: {
  429:     sudo_debug_write2(NULL, NULL, 0, str, len, errno_val);
  430: }
  431: 
  432: void
  433: sudo_debug_vprintf2(const char *func, const char *file, int lineno, int level,
  434:     const char *fmt, va_list ap)
  435: {
  436:     int buflen, pri, subsys, saved_errno = errno;
  437:     char *buf = NULL;
  438: 
  439:     if (!sudo_debug_mode)
  440: 	return;
  441: 
  442:     /* Extract pri and subsystem from level. */
  443:     pri = SUDO_DEBUG_PRI(level);
  444:     subsys = SUDO_DEBUG_SUBSYS(level);
  445: 
  446:     /* Make sure we want debug info at this level. */
  447:     if (subsys < num_subsystems && sudo_debug_settings[subsys] >= pri) {
  448: 	buflen = fmt ? vasprintf(&buf, fmt, ap) : 0;
  449: 	if (buflen != -1) {
  450: 	    int errcode = ISSET(level, SUDO_DEBUG_ERRNO) ? saved_errno : 0;
  451: 	    if (ISSET(level, SUDO_DEBUG_LINENO))
  452: 		sudo_debug_write2(func, file, lineno, buf, buflen, errcode);
  453: 	    else
  454: 		sudo_debug_write2(NULL, NULL, 0, buf, buflen, errcode);
  455: 	    free(buf);
  456: 	}
  457:     }
  458: 
  459:     errno = saved_errno;
  460: }
  461: 
  462: #ifdef NO_VARIADIC_MACROS
  463: void
  464: sudo_debug_printf_nvm(int pri, const char *fmt, ...)
  465: {
  466:     va_list ap;
  467: 
  468:     va_start(ap, fmt);
  469:     sudo_debug_vprintf2(NULL, NULL, 0, pri, fmt, ap);
  470:     va_end(ap);
  471: }
  472: #endif /* NO_VARIADIC_MACROS */
  473: 
  474: void
  475: sudo_debug_printf2(const char *func, const char *file, int lineno, int level,
  476:     const char *fmt, ...)
  477: {
  478:     va_list ap;
  479: 
  480:     va_start(ap, fmt);
  481:     sudo_debug_vprintf2(func, file, lineno, level, fmt, ap);
  482:     va_end(ap);
  483: }
  484: 
  485: void
  486: sudo_debug_execve2(int level, const char *path, char *const argv[], char *const envp[])
  487: {
  488:     char * const *av;
  489:     char *buf, *cp;
  490:     int buflen, pri, subsys, log_envp = 0;
  491:     size_t plen;
  492: 
  493:     if (!sudo_debug_mode)
  494: 	return;
  495: 
  496:     /* Extract pri and subsystem from level. */
  497:     pri = SUDO_DEBUG_PRI(level);
  498:     subsys = SUDO_DEBUG_SUBSYS(level);
  499: 
  500:     /* Make sure we want debug info at this level. */
  501:     if (subsys >= num_subsystems || sudo_debug_settings[subsys] < pri)
  502: 	return;
  503: 
  504:     /* Log envp for debug level "debug". */
  505:     if (sudo_debug_settings[subsys] >= SUDO_DEBUG_DEBUG - 1 && envp[0] != NULL)
  506: 	log_envp = 1;
  507: 
  508: #define EXEC_PREFIX "exec "
  509: 
  510:     /* Alloc and build up buffer. */
  511:     plen = strlen(path);
  512:     buflen = sizeof(EXEC_PREFIX) -1 + plen;
  513:     if (argv[0] != NULL) {
  514: 	buflen += sizeof(" []") - 1;
  515: 	for (av = argv; *av; av++)
  516: 	    buflen += strlen(*av) + 1;
  517: 	buflen--;
  518:     }
  519:     if (log_envp) {
  520: 	buflen += sizeof(" []") - 1;
  521: 	for (av = envp; *av; av++)
  522: 	    buflen += strlen(*av) + 1;
  523: 	buflen--;
  524:     }
  525:     buf = malloc(buflen + 1);
  526:     if (buf == NULL)
  527: 	return;
  528: 
  529:     /* Copy prefix and command. */
  530:     memcpy(buf, EXEC_PREFIX, sizeof(EXEC_PREFIX) - 1);
  531:     cp = buf + sizeof(EXEC_PREFIX) - 1;
  532:     memcpy(cp, path, plen);
  533:     cp += plen;
  534: 
  535:     /* Copy argv. */
  536:     if (argv[0] != NULL) {
  537: 	*cp++ = ' ';
  538: 	*cp++ = '[';
  539: 	for (av = argv; *av; av++) {
  540: 	    size_t avlen = strlen(*av);
  541: 	    memcpy(cp, *av, avlen);
  542: 	    cp += avlen;
  543: 	    *cp++ = ' ';
  544: 	}
  545: 	cp[-1] = ']';
  546:     }
  547: 
  548:     if (log_envp) {
  549: 	*cp++ = ' ';
  550: 	*cp++ = '[';
  551: 	for (av = envp; *av; av++) {
  552: 	    size_t avlen = strlen(*av);
  553: 	    memcpy(cp, *av, avlen);
  554: 	    cp += avlen;
  555: 	    *cp++ = ' ';
  556: 	}
  557: 	cp[-1] = ']';
  558:     }
  559: 
  560:     *cp = '\0';
  561: 
  562:     sudo_debug_write(buf, buflen, 0);
  563:     free(buf);
  564: }
  565: 
  566: /*
  567:  * Getter for the debug descriptor.
  568:  */
  569: int
  570: sudo_debug_fd_get(void)
  571: {
  572:     return sudo_debug_fd;
  573: }
  574: 
  575: /*
  576:  * Setter for the debug descriptor.
  577:  */
  578: int
  579: sudo_debug_fd_set(int fd)
  580: {
  581:     if (sudo_debug_fd != -1 && fd != sudo_debug_fd) {
  582: 	if (dup2(sudo_debug_fd, fd) == -1)
  583: 	    return -1;
  584: 	(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
  585: 	close(sudo_debug_fd);
  586: 	sudo_debug_fd = fd;
  587:     }
  588:     return sudo_debug_fd;
  589: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>