#include #include #include #include "defaults.h" #include "format.h" #include "plugin.h" void format_free_data(char *data) { if (data != NULL) { free(data); } } void format_free_ndn_list(char **ndn_list) { if (ndn_list != NULL) { free(ndn_list); } } /* Retrieve a single value for an attribute. If there are no values, or more * than one, fail. */ static char * format_single(Slapi_PBlock *pb, Slapi_Entry *e, const char *attr, char ***visited_ndns) { char **values, *ret; int count; values = slapi_entry_attr_get_charray(e, attr); for (count = 0; (values != NULL) && (values[count] != NULL); count++) { continue; } if (count == 1) { ret = strdup(values[0]); } else { ret = NULL; } slapi_ch_array_free(values); return ret; } /* Find the matching closing marker. */ static const char * format_find_closer(const char *pair, const char *pattern) { int i, level = 0; for (i = 0; pattern[i] != '\0'; i++) { if (pattern[i] == pair[0]) { level++; } else { if (pattern[i] == pair[1]) { level--; } } if (level == 0) { return &pattern[i]; } } return NULL; } /* Recursively expand the expression into the output buffer, adding any entries * we visit (other than e) to the list of visited NDNs. Unless it's a literal, * treat the entire input format specifier as an expression. */ static int format_expand(struct plugin_state *state, Slapi_PBlock *pb, Slapi_Entry *e, const char *fmt, char *outbuf, int outbuf_len, char ***visited_ndns, PRBool literal) { int i, j; int exp_len, level; const char *fmtstart, *fmtend, *match, *attribute; char *tmp, *fnname, *spd_id; char exp[outbuf_len * 2]; const char *default_value, *alternate_value, *paramstart, *paramend; size_t spn; spd_id = state->plugin_desc->spd_id; slapi_log_error(SLAPI_LOG_PLUGIN, spd_id, "expanding %s\"%s\"%s\n", literal ? "" : "%{", fmt, literal ? "" : "}"); /* First, expand any subexpressions and call any "functions". */ level = 0; i = 0; j = 0; while (fmt[i] != '\0') { switch (fmt[i]) { case '%': /* This might be a subexpression, a "function" call, or * an escaped character. */ switch (fmt[i + 1]) { case '%': /* It's just an escaped "%". */ exp[j++] = '%'; i += 2; continue; break; case '{': /* Find the beginning of the subexpression. */ fmtstart = fmt + i; /* Find the end of the subexpression. */ match = format_find_closer("{}", fmtstart + 1); if (match == NULL) { slapi_log_error(SLAPI_LOG_PLUGIN, spd_id, "expansion failed: " "no closing brace\n"); return -1; } else { /* Mark the first character after the * subexpression. */ fmtend = match + 1; /* Make a copy of the subexpression. */ tmp = malloc(fmtend - fmtstart); if (tmp == NULL) { slapi_log_error(SLAPI_LOG_PLUGIN, spd_id, "expansion " "failed: out " "of memory\n"); return -1; } memcpy(tmp, fmtstart + 2, fmtend - fmtstart - 3); tmp[fmtend - fmtstart - 3] = '\0'; /* Recursively expand the * subexpression. */ exp_len = format_expand(state, pb, e, tmp, exp + j, sizeof(exp) - j, visited_ndns, FALSE); free(tmp); if ((exp_len < 0) || (exp_len + j >= (int) sizeof(exp))) { /* We'd be out of space, * FAIL. */ slapi_log_error(SLAPI_LOG_PLUGIN, spd_id, "expansion " "failed: result" " would be too " "big\n"); return -1; } else { /* It fit, so keep going. */ i = (match + 1) - fmt; j += exp_len; } } continue; break; default: /* Assume it's a "function" call. Pick out the * name of the function. */ paramstart = strchr(fmt + i, '{'); if (paramstart == NULL) { /* No start? Bad format. */ slapi_log_error(SLAPI_LOG_PLUGIN, spd_id, "expansion failed: " "bad function " "invocation\n"); return -1; } paramend = strchr(paramstart + 1, '}'); if (paramend == NULL) { /* No matching end? Bad format. */ slapi_log_error(SLAPI_LOG_PLUGIN, spd_id, "expansion failed: " "bad function " "invocation\n"); return -1; } fnname = malloc(paramstart - (fmt + i)); if (fnname == NULL) { /* Out of memory, FAIL. */ slapi_log_error(SLAPI_LOG_PLUGIN, spd_id, "expansion failed: " "out of memory\n"); return -1; } memcpy(fnname, fmt + i + 1, paramstart - (fmt + i + 1)); fnname[paramstart - (fmt + i + 1)] = '\0'; /* Isolate the parameter string. */ tmp = malloc(paramend - paramstart); if (tmp == NULL) { /* Out of memory, FAIL. */ slapi_log_error(SLAPI_LOG_PLUGIN, spd_id, "expansion failed: " "out of memory\n"); free(fnname); return -1; } memcpy(tmp, paramstart + 1, paramend - (paramstart + 1)); tmp[paramend - paramstart] = '\0'; slapi_log_error(SLAPI_LOG_PLUGIN, spd_id, "calling \"%s\"(\"%s\")\n", fnname, tmp); free(fnname); free(tmp); i = (paramend - fmt) + 1; continue; break; } break; default: /* Default is just a literal character. */ exp[j++] = fmt[i++]; break; } } exp[j] = '\0'; if (literal) { /* It's a literal string, so we're actually done. */ i = strlen(exp); if (i <= outbuf_len) { memcpy(outbuf, exp, i); } slapi_log_error(SLAPI_LOG_PLUGIN, "nis-plugin", "expanded to \"%s\"\n", exp); return i; } else { /* It's an expression, so evaluate it. */ slapi_log_error(SLAPI_LOG_PLUGIN, "nis-plugin", "looking up simple expression \%\{\"%s\"}\n", exp); /* Check if it uses a default/alternate value. */ spn = strcspn(exp, ":"); if (spn == strlen(exp)) { /* Simple expression: expect it to be a single-valued * attribute. */ tmp = format_single(pb, e, exp, visited_ndns); if (tmp != NULL) { /* Copy the string to the output buffer if * there's space for it. */ i = strlen(tmp); if (i <= outbuf_len) { memcpy(outbuf, tmp, i); } slapi_log_error(SLAPI_LOG_PLUGIN, "nis-plugin", "expanded to \"%s\"\n", tmp); free(tmp); /* Return the length of the expanded * expression. */ return i; } else { /* No value found? FAIL. */ slapi_log_error(SLAPI_LOG_PLUGIN, "nis-plugin", "no value for \"%s\"\n", exp); return -1; } } else { /* Make a copy of the attribute name. */ exp[spn] = '\0'; attribute = exp; alternate_value = NULL; default_value = NULL; /* Figure out if there's an alternate or default value * given. */ switch (exp[spn + 1]) { case '+': alternate_value = exp + spn + 2; break; case '-': default_value = exp + spn + 2; break; default: default_value = exp + spn + 1; break; } /* Retrieve the value. */ tmp = format_single(pb, e, attribute, visited_ndns); if (tmp == NULL) { /* The attribute is undefined, or we're * treating it as if it is. */ if (default_value != NULL) { /* Supply the default value. */ i = strlen(default_value); if (i <= outbuf_len) { memcpy(outbuf, default_value, i); } slapi_log_error(SLAPI_LOG_PLUGIN, "nis-plugin", "expanded to \"%s\"\n", default_value); return i; } else { /* No value, and no default: FAIL. */ slapi_log_error(SLAPI_LOG_PLUGIN, "nis-plugin", "failed to expand: no valid value, no default\n"); return -1; } } else { /* There's no value defined (or it's * multi-valued, which is usually trouble). */ if (alternate_value != NULL) { /* Supply the alternate value. */ i = strlen(alternate_value); if (i <= outbuf_len) { memcpy(outbuf, alternate_value, i); } slapi_log_error(SLAPI_LOG_PLUGIN, "nis-plugin", "expanded to \"%s\"\n", alternate_value); free(tmp); return i; } else { /* Supply the looked-up value. */ i = strlen(tmp); if (i <= outbuf_len) { memcpy(outbuf, tmp, i); } slapi_log_error(SLAPI_LOG_PLUGIN, "nis-plugin", "expanded to \"%s\"\n", tmp); free(tmp); return i; } } } } } static char * format_format(struct plugin_state *state, Slapi_PBlock *pb, Slapi_Entry *e, const char *fmt, char ***visited_ndns) { char buf[YPMAXRECORD], *tmp; const char *match, *fmtstart, *fmtend; unsigned int i, j; int exp_len; i = format_expand(state, pb, e, fmt, buf, sizeof(buf), visited_ndns, TRUE); if (i < sizeof(buf)) { buf[i] = '\0'; return strdup(buf); } else { return NULL; } } char * format_get_data(struct plugin_state *state, Slapi_PBlock *pb, Slapi_Entry *e, const char *fmt, char ***visited_ndns) { Slapi_PBlock *local_pb; char ***ndn_list, **local_ndn_list, *ret; /* Supply an NDN list if the caller didn't. */ if (visited_ndns != NULL) { ndn_list = visited_ndns; } else { ndn_list = &local_ndn_list; local_ndn_list = NULL; } /* Supply a PBlock if the caller didn't. */ if (pb == NULL) { local_pb = slapi_pblock_new(); pb = local_pb; } else { local_pb = NULL; } ret = format_format(state, pb, e, fmt, ndn_list); /* If we supplied a PBlock, clean it up. */ if (pb == local_pb) { slapi_pblock_destroy(local_pb); } /* If we supplied an NDN list, clean it up. */ if (ndn_list == &local_ndn_list) { format_free_ndn_list(local_ndn_list); } return ret; }