/* wydawca - automatic release submission daemon Copyright (C) 2007, 2008, 2009 Sergey Poznyakoff Wydawca is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Wydawca is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with wydawca. If not, see . */ #include "wydawca.h" #include "hash.h" #include static Hash_table *triplet_table; static size_t hash_triplet_hasher (void const *data, unsigned n_buckets) { struct file_triplet const *t = data; return hash_string (t->name, n_buckets); } /* Compare two strings for equality. */ static bool hash_triplet_compare (void const *data1, void const *data2) { struct file_triplet const *t1 = data1; struct file_triplet const *t2 = data2; return t1->name && t2->name && strcmp (t1->name, t2->name) == 0; } /* Reclaim memory storage associated with a table entry */ void hash_triplet_free (void *data) { int i; struct file_triplet *tp = data; for (i = 0; i < FILE_TYPE_COUNT; i++) { if (tp->file[i].name) free (tp->file[i].name); } free (tp->directive); free (tp->blurb); free (tp->tmp); free (tp->user); if (tp->user_data) { for (i = 0; i < NITEMS (tp->user_data); i++) free (tp->user_data[i]); free (tp->user_data); } free (tp); } /* Register a file in the triplet table */ void register_file (struct file_info *finfo) { struct file_triplet *tp, *ret; tp = xmalloc (sizeof(*tp) + finfo->root_len + 1); memset (tp, 0, sizeof (*tp)); tp->name = (char*)(tp + 1); memcpy (tp->name, finfo->name, finfo->root_len); tp->name[finfo->root_len] = 0; if (! ((triplet_table || (triplet_table = hash_initialize (0, 0, hash_triplet_hasher, hash_triplet_compare, hash_triplet_free))) && (ret = hash_insert (triplet_table, tp)))) xalloc_die (); if (ret != tp) free (tp); ret->file[finfo->type] = *finfo; } /* Return true if any part of the triplet TRP was modified more than TTL seconds ago */ static bool triplet_expired_p (struct file_triplet *trp, time_t ttl) { int i; time_t now = time (NULL); if (ttl == 0) return false; for (i = 0; i < FILE_TYPE_COUNT; i++) { if (trp->file[i].name && (now - trp->file[i].sb.st_mtime) > ttl) { if (debug_level) logmsg (LOG_DEBUG, _("file %s expired"), trp->file[i].name); return true; } } return false; } enum triplet_state { triplet_directive, /* Short triplet: only a directive is present, but nothing more is required */ triplet_complete, /* A complete triplet: all three files are present and have the same owner */ triplet_incomplete, /* Incomplete triplet: some files are missing */ triplet_bad, /* Bad triplet. Should be removed immediately. */ }; static enum triplet_state check_triplet_state (struct file_triplet *trp) { if (verify_directive_file (trp, trp->spool)) return triplet_bad; if (trp->file[file_directive].name) { if (trp->file[file_dist].name == 0 && trp->file[file_signature].name == 0) { if (directive_get_value (trp, "filename", NULL)) return triplet_directive; } else if (trp->file[file_dist].name && trp->file[file_signature].name) { if (trp->file[file_dist].sb.st_uid == trp->file[file_signature].sb.st_uid && trp->file[file_dist].sb.st_uid == trp->file[file_directive].sb.st_uid) return triplet_complete; else { if (debug_level) logmsg (LOG_DEBUG, _("%s: invalid triplet: UIDs differ"), trp->name); return triplet_bad; } } } return triplet_incomplete; } /* Unlink all parts of the triplet TRP */ static void remove_triplet (struct file_triplet *trp) { int i; for (i = 0; i < FILE_TYPE_COUNT; i++) { if (trp->file[i].name) { logmsg (LOG_NOTICE, _("removing %s"), trp->file[i].name); if (!dry_run_mode && unlink (trp->file[i].name)) logmsg (LOG_ERR, _("cannot remove %s: %s"), trp->file[i].name, strerror (errno)); } } } /* Process a single triplet from the table */ static bool triplet_processor (void *data, void *proc_data) { struct file_triplet *trp = data; const struct spool *spool = proc_data; trp->spool = spool; if (debug_level) logmsg (LOG_DEBUG, "FILE %s, DIST=%s, SIG=%s, DIRECTIVE=%s", trp->name, SP (trp->file[file_dist].name), SP (trp->file[file_signature].name), SP (trp->file[file_directive].name)); switch (check_triplet_state (trp)) { case triplet_directive: case triplet_complete: if (debug_level) logmsg (LOG_DEBUG, _("processing triplet `%s'"), trp->name); if (wydawca_set_triplet_privs (trp) == 0) { process_directives (trp, spool); wydawca_set_root_privs (); } return true; case triplet_incomplete: if (debug_level) logmsg (LOG_DEBUG, _("%s: incomplete triplet"), trp->name); /* ignore unless expired (see below); */ UPDATE_STATS (STAT_INCOMPLETE_TRIPLETS); break; case triplet_bad: UPDATE_STATS (STAT_BAD_TRIPLETS); remove_triplet (trp); return true; } if (triplet_expired_p (trp, spool->file_sweep_time)) { UPDATE_STATS (STAT_EXPIRED_TRIPLETS); remove_triplet (trp); } return true; } /* Process all triplets from the table according to the SPOOL */ void enumerate_triplets (const struct spool *spool) { if (debug_level) logmsg (LOG_DEBUG, _("processing spool %s (%s)"), spool->tag, mu_url_to_string (spool->dest_url)); if (triplet_table) { hash_do_for_each (triplet_table, triplet_processor, (void*) spool); hash_clear (triplet_table); } } size_t count_collected_triplets () { return triplet_table ? hash_get_n_entries (triplet_table) : 0; } static const char * expand_project_base (struct metadef *def, void *data) { struct file_triplet *trp = data; return trp->project; } static const char * expand_tag (struct metadef *def, void *data) { struct file_triplet *trp = data; return trp->spool->tag; } static const char * expand_url (struct metadef *def, void *data) { struct file_triplet *trp = data; return trp->spool->url; } static const char * expand_relative_dir (struct metadef *def, void *data) { struct file_triplet *trp = data; directive_get_value (trp, "directory", (const char**) &def->value); return def->value; } static const char * expand_dest_dir (struct metadef *def, void *data) { struct file_triplet *trp = data; return trp->spool->dest_dir; } static const char * expand_source_dir (struct metadef *def, void *data) { struct file_triplet *trp = data; return trp->spool->source_dir; } static void decode_file_mode (mode_t mode, char *string) { *string++ = mode & S_IRUSR ? 'r' : '-'; *string++ = mode & S_IWUSR ? 'w' : '-'; *string++ = (mode & S_ISUID ? (mode & S_IXUSR ? 's' : 'S') : (mode & S_IXUSR ? 'x' : '-')); *string++ = mode & S_IRGRP ? 'r' : '-'; *string++ = mode & S_IWGRP ? 'w' : '-'; *string++ = (mode & S_ISGID ? (mode & S_IXGRP ? 's' : 'S') : (mode & S_IXGRP ? 'x' : '-')); *string++ = mode & S_IROTH ? 'r' : '-'; *string++ = mode & S_IWOTH ? 'w' : '-'; *string++ = (mode & S_ISVTX ? (mode & S_IXOTH ? 't' : 'T') : (mode & S_IXOTH ? 'x' : '-')); *string = '\0'; } /* Width of "user/group size", with initial value chosen heuristically. This grows as needed, though this may cause some stairstepping in the output. Make it too small and the output will almost always look ragged. Make it too large and the output will be spaced out too far. */ static int ugswidth = 19; static int format_file_data (struct file_triplet *trp, enum file_type type, char **pret) { char modes[11]; struct file_info *info = trp->file + type; char timebuf[sizeof "YYYY-MM-DD HH:MM:SS +0000"]; struct group *grp; char sbuf[INT_BUFSIZE_BOUND (uintmax_t)]; char *sptr; size_t slen; int pad; char *user_name; char *group_name; struct tm *tm; char *buf; if (!info->name) return 1; /* MODE OWNER GROUP SIZE MTIME FILE_NAME MD5SUM? */ modes[0] = '-'; /* Only regular files are allowed */ decode_file_mode (info->sb.st_mode, modes + 1); /* File time */ tm = localtime (&info->sb.st_mtime); strftime (timebuf, sizeof timebuf, "%Y-%m-%d %H:%M:%S %z", tm); user_name = trp->user; /* FIXME: group name should be stored in TRP after verification */ grp = getgrgid (TRIPLET_GID (trp)); if (!grp) group_name = "UNKNOWN"; /* should not happen */ else group_name = grp->gr_name; /* Size */ sptr = umaxtostr (info->sb.st_size, sbuf); /* Figure out padding and format the buffer */ slen = strlen (sptr); pad = strlen (user_name) + 1 + strlen (group_name) + 1 + slen; if (pad > ugswidth) ugswidth = pad; asprintf (&buf, "%s %s %s %*s %s %s", modes, user_name, group_name, ugswidth - pad + slen, sptr, timebuf, info->name); *pret = buf; return 0; } static const char * expand_triplet_full (struct metadef *def, void *data) { struct file_triplet *trp = data; char *buf[FILE_TYPE_COUNT] = { NULL, NULL, NULL }; size_t size = 0; if (format_file_data (trp, file_dist, &buf[file_dist]) == 0) size += strlen (buf[file_dist]) + 1; if (format_file_data (trp, file_signature, &buf[file_signature]) == 0) size += strlen (buf[file_signature]) + 1; if (format_file_data (trp, file_directive, &buf[file_directive]) == 0) size += strlen (buf[file_directive]) + 1; def->value = def->storage = xmalloc (size + 1); def->value[0] = 0; if (buf[file_dist]) { strcat (def->value, buf[file_dist]); strcat (def->value, "\n"); } if (buf[file_signature]) { strcat (def->value, buf[file_signature]); strcat (def->value, "\n"); } if (buf[file_directive]) { strcat (def->value, buf[file_directive]); strcat (def->value, "\n"); } def->value[size-1] = 0; /* Kill terminating newline */ free (buf[file_dist]); free (buf[file_signature]); free (buf[file_directive]); return def->value; } static const char * expand_triplet_upload (struct metadef *def, void *data) { struct file_triplet *trp = data; char *buf[2] = { NULL, NULL }; size_t size = 0; if (format_file_data (trp, file_dist, &buf[file_dist]) == 0) size += strlen (buf[file_dist]) + 1; if (format_file_data (trp, file_signature, &buf[file_signature]) == 0) size += strlen (buf[file_signature]) + 1; def->value = def->storage = xmalloc (size + 1); def->value[0] = 0; if (buf[file_dist]) { strcat (def->value, buf[file_dist]); strcat (def->value, "\n"); } if (buf[file_signature]) { strcat (def->value, buf[file_signature]); strcat (def->value, "\n"); } def->value[size-1] = 0; /* Kill terminating newline */ free (buf[file_dist]); free (buf[file_signature]); return def->value; } static const char * expand_triplet_dist (struct metadef *def, void *data) { struct file_triplet *trp = data; format_file_data (trp, file_dist, &def->storage); def->value = def->storage; return def->value; } static const char * expand_triplet_sig (struct metadef *def, void *data) { struct file_triplet *trp = data; format_file_data (trp, file_signature, &def->storage); def->value = def->storage; return def->value; } static const char * expand_triplet_directive (struct metadef *def, void *data) { struct file_triplet *trp = data; format_file_data (trp, file_directive, &def->storage); def->value = def->storage; return def->value; } static const char * expand_user_name (struct metadef *def, void *data) { struct file_triplet *trp = data; return trp->user; } static void fill_user_data (struct file_triplet *trp) { int rc; struct access_method *method = trp->spool->access_method[user_data_method]; char *text; unsigned nrows, ncols; struct metadef def[5]; struct passwd *pw; void *md; if (trp->user_data) return; if (method->type == method_none) return; md = method_open (method); if (!md) return; pw = getpwuid (TRIPLET_UID (trp)); if (!pw) return; make_default_meta (def, pw->pw_name, trp->project); meta_escape (method, md, def); text = meta_expand_string (method->query, def, NULL); meta_free (def); rc = method_run (method, md, text); free (text); if (rc) { method_close (method, md); return; } nrows = method_num_rows (method); ncols = method_num_cols (method); if (nrows > 0) { int i; trp->user_data = xcalloc (ncols, sizeof (trp->user_data[0])); for (i = 0; i < ncols; i++) { const char *str = method_result (method, md, 0, i); if (str) trp->user_data[i] = xstrdup (str); } } method_close (method, md); } static const char * expand_user_real_name (struct metadef *def, void *data) { struct file_triplet *trp = data; fill_user_data (trp); if (trp->user_data && trp->user_data[1]) return trp->user_data[1]; def->value = "UNKNOWN"; return def->value; } static const char * expand_user_email (struct metadef *def, void *data) { struct file_triplet *trp = data; fill_user_data (trp); if (trp->user_data && trp->user_data[0]) return trp->user_data[0]; def->value = "UNKNOWN"; return def->value; } static const char * expand_report (struct metadef *def, void *data) { return report_string; } #define DECL_EXPAND_TIMER(what) \ static const char * \ __cat2__(expand_timer_,what) (struct metadef *def, void *data) \ { \ wydawca_timer_t t = timer_stop ((char*)def->data); \ def->storage = timer_format_time (__cat2__(timer_get_,what) (t)); \ return def->value = def->storage; \ } DECL_EXPAND_TIMER(real) DECL_EXPAND_TIMER(user) DECL_EXPAND_TIMER(system) #define DECL_TIMER(name,t) \ { "timer:" #name ":" #t, NULL, __cat2__(expand_timer_,t), NULL, #name } #define DECL_FULL_TIMER(name) \ DECL_TIMER(name, real), \ DECL_TIMER(name, user), \ DECL_TIMER(name, system) struct metadef triplet_meta[] = { { "project", NULL, expand_project_base, NULL }, { "url", NULL, expand_url, NULL }, { "spool", NULL, expand_tag, NULL }, { "dir", NULL, expand_relative_dir, NULL }, { "dest-dir", NULL, expand_dest_dir, NULL }, { "source-dir", NULL, expand_source_dir, NULL }, { "triplet:full", NULL, expand_triplet_full, NULL }, { "triplet:upload", NULL, expand_triplet_upload, NULL }, { "triplet:dist", NULL, expand_triplet_dist, NULL }, { "triplet:sig", NULL, expand_triplet_sig, NULL }, { "triplet:dir", NULL, expand_triplet_directive, NULL }, { "user", NULL, expand_user_name, NULL }, { "user:name", NULL, expand_user_name, NULL }, { "user:real-name", NULL, expand_user_real_name, NULL }, { "user:email", NULL, expand_user_email, NULL }, { "report", NULL, expand_report, NULL }, DECL_FULL_TIMER(wydawca), DECL_FULL_TIMER(triplet), DECL_FULL_TIMER(directory), { NULL } }; char * triplet_expand_param (const char *tmpl, struct file_triplet *trp) { char *p = meta_expand_string (tmpl, triplet_meta, trp); meta_free (triplet_meta); return p; }