1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2014 Richard Hughes <richard@hughsie.com>
4  *
5  * SPDX-License-Identifier: LGPL-2.1+
6  */
7 
8 /**
9  * SECTION:asb-package-rpm
10  * @short_description: Object representing a .RPM package file.
11  * @stability: Unstable
12  *
13  * This object represents one .rpm package file.
14  */
15 
16 #include "config.h"
17 
18 #include <limits.h>
19 #include <archive.h>
20 #include <archive_entry.h>
21 
22 #include <rpm/rpmlib.h>
23 #include <rpm/rpmts.h>
24 
25 #include "asb-package-rpm.h"
26 #include "asb-plugin.h"
27 
28 typedef struct
29 {
30 	Header		 h;
31 } AsbPackageRpmPrivate;
32 
G_DEFINE_TYPE_WITH_PRIVATE(AsbPackageRpm,asb_package_rpm,ASB_TYPE_PACKAGE)33 G_DEFINE_TYPE_WITH_PRIVATE (AsbPackageRpm, asb_package_rpm, ASB_TYPE_PACKAGE)
34 
35 #define GET_PRIVATE(o) (asb_package_rpm_get_instance_private (o))
36 
37 static void
38 asb_package_rpm_finalize (GObject *object)
39 {
40 	AsbPackageRpm *pkg = ASB_PACKAGE_RPM (object);
41 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg);
42 
43 	if (priv->h != NULL)
44 		headerFree (priv->h);
45 
46 	G_OBJECT_CLASS (asb_package_rpm_parent_class)->finalize (object);
47 }
48 
49 static void
asb_package_rpm_init(AsbPackageRpm * pkg)50 asb_package_rpm_init (AsbPackageRpm *pkg)
51 {
52 }
53 
54 static void
asb_package_rpm_set_license(AsbPackage * pkg,const gchar * license)55 asb_package_rpm_set_license (AsbPackage *pkg, const gchar *license)
56 {
57 	guint i;
58 	g_autofree gchar *new = NULL;
59 	g_auto(GStrv) tokens = NULL;
60 
61 	/* this isn't supposed to happen */
62 	if (license == NULL) {
63 		asb_package_log (pkg, ASB_PACKAGE_LOG_LEVEL_WARNING,
64 				 "no license!");
65 		return;
66 	}
67 
68 	/* tokenize the license string and log non SPDX licenses */
69 	new = as_utils_license_to_spdx (license);
70 	tokens = as_utils_spdx_license_tokenize (new);
71 	for (i = 0; tokens[i] != NULL; i++) {
72 
73 		/* ignore */
74 		if (tokens[i][0] == '(' ||
75 		    tokens[i][0] == ')' ||
76 		    tokens[i][0] == '&' ||
77 		    tokens[i][0] == '|')
78 			continue;
79 
80 		/* already SPDX */
81 		if (tokens[i][0] == '@')
82 			continue;
83 
84 		/* no matching SPDX entry */
85 		asb_package_log (pkg,
86 				 ASB_PACKAGE_LOG_LEVEL_WARNING,
87 				 "Unable to currently map Fedora "
88 				 "license '%s' to SPDX", tokens[i]);
89 	}
90 	asb_package_set_license (pkg, new);
91 }
92 
93 static void
asb_package_rpm_set_source(AsbPackage * pkg,const gchar * source)94 asb_package_rpm_set_source (AsbPackage *pkg, const gchar *source)
95 {
96 	gchar *tmp;
97 	g_autofree gchar *srcrpm = NULL;
98 
99 	/* this isn't supposed to happen */
100 	if (source == NULL) {
101 		asb_package_log (pkg, ASB_PACKAGE_LOG_LEVEL_WARNING,
102 				 "no source!");
103 		return;
104 	}
105 	srcrpm = g_strdup (source);
106 	tmp = g_strstr_len (srcrpm, -1, ".src.rpm");
107 	if (tmp != NULL)
108 		*tmp = '\0';
109 	asb_package_set_source (pkg, srcrpm);
110 
111 	/* get the srpm name */
112 	tmp = g_strrstr (srcrpm, "-");
113 	if (tmp != NULL)
114 		*tmp = '\0';
115 	tmp = g_strrstr (srcrpm, "-");
116 	if (tmp != NULL)
117 		*tmp = '\0';
118 	asb_package_set_source_pkgname (pkg, srcrpm);
119 }
120 
121 static gboolean
asb_package_rpm_ensure_nevra(AsbPackage * pkg,GError ** error)122 asb_package_rpm_ensure_nevra (AsbPackage *pkg, GError **error)
123 {
124 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
125 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
126 	rpmtd td;
127 
128 	td = rpmtdNew ();
129 	headerGet (priv->h, RPMTAG_NAME, td, HEADERGET_MINMEM);
130 	asb_package_set_name (pkg, rpmtdGetString (td));
131 	headerGet (priv->h, RPMTAG_VERSION, td, HEADERGET_MINMEM);
132 	asb_package_set_version (pkg, rpmtdGetString (td));
133 	headerGet (priv->h, RPMTAG_RELEASE, td, HEADERGET_MINMEM);
134 	asb_package_set_release (pkg, rpmtdGetString (td));
135 	headerGet (priv->h, RPMTAG_ARCH, td, HEADERGET_MINMEM);
136 	asb_package_set_arch (pkg, rpmtdGetString (td));
137 	headerGet (priv->h, RPMTAG_EPOCH, td, HEADERGET_MINMEM);
138 	asb_package_set_epoch (pkg, (guint) rpmtdGetNumber (td));
139 	rpmtdFree (td);
140 	return TRUE;
141 }
142 
143 static gboolean
asb_package_rpm_ensure_source(AsbPackage * pkg,GError ** error)144 asb_package_rpm_ensure_source (AsbPackage *pkg, GError **error)
145 {
146 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
147 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
148 	rpmtd td;
149 
150 	td = rpmtdNew ();
151 	headerGet (priv->h, RPMTAG_SOURCERPM, td, HEADERGET_MINMEM);
152 	asb_package_rpm_set_source (pkg, rpmtdGetString (td));
153 	rpmtdFree (td);
154 	return TRUE;
155 }
156 
157 static gboolean
asb_package_rpm_ensure_url(AsbPackage * pkg,GError ** error)158 asb_package_rpm_ensure_url (AsbPackage *pkg, GError **error)
159 {
160 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
161 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
162 	rpmtd td;
163 
164 	td = rpmtdNew ();
165 	headerGet (priv->h, RPMTAG_URL, td, HEADERGET_MINMEM);
166 	asb_package_set_url (pkg, rpmtdGetString (td));
167 	rpmtdFree (td);
168 	return TRUE;
169 }
170 
171 static gboolean
asb_package_rpm_ensure_vcs(AsbPackage * pkg,GError ** error)172 asb_package_rpm_ensure_vcs (AsbPackage *pkg, GError **error)
173 {
174 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
175 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
176 	rpmtd td;
177 
178 	td = rpmtdNew ();
179 	headerGet (priv->h, RPMTAG_VCS, td, HEADERGET_MINMEM);
180 	asb_package_set_vcs (pkg, rpmtdGetString (td));
181 	rpmtdFree (td);
182 	return TRUE;
183 }
184 
185 static gboolean
asb_package_rpm_ensure_license(AsbPackage * pkg,GError ** error)186 asb_package_rpm_ensure_license (AsbPackage *pkg, GError **error)
187 {
188 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
189 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
190 	rpmtd td;
191 
192 	td = rpmtdNew ();
193 	headerGet (priv->h, RPMTAG_LICENSE, td, HEADERGET_MINMEM);
194 	asb_package_rpm_set_license (pkg, rpmtdGetString (td));
195 	rpmtdFree (td);
196 	return TRUE;
197 }
198 
199 static void
asb_package_rpm_add_release(AsbPackage * pkg,guint64 timestamp,const gchar * name,const gchar * text)200 asb_package_rpm_add_release (AsbPackage *pkg,
201 			     guint64 timestamp,
202 			     const gchar *name,
203 			     const gchar *text)
204 {
205 	AsRelease *release;
206 	const gchar *version;
207 	gchar *tmp;
208 	gchar *vr;
209 	g_autofree gchar *name_dup = NULL;
210 
211 	/* get last string chunk */
212 	name_dup = g_strchomp (g_strdup (name));
213 	vr = g_strrstr (name_dup, " ");
214 	if (vr == NULL)
215 		return;
216 
217 	/* get last string chunk */
218 	version = vr + 1;
219 
220 	/* ignore version-less dashed email address, e.g.
221 	 * 'Fedora Release Engineering <rel-eng@lists.fedoraproject.org>' */
222 	if (g_strstr_len (version, -1, "@") != NULL ||
223 	    g_strstr_len (version, -1, "<") != NULL ||
224 	    g_strstr_len (version, -1, ">") != NULL)
225 		return;
226 
227 	tmp = g_strrstr_len (version, -1, "-");
228 	if (tmp != NULL)
229 		*tmp = '\0';
230 
231 	/* remove any epoch */
232 	tmp = g_strstr_len (version, -1, ":");
233 	if (tmp != NULL)
234 		version = tmp + 1;
235 
236 	/* remove any version prefix */
237 	if (version != NULL && version[0] == '-')
238 		version = version + 1;
239 
240 	/* is version already in the database */
241 	release = asb_package_get_release (pkg, version);
242 	if (release != NULL) {
243 		/* use the earlier timestamp to ignore auto-rebuilds with just
244 		 * a bumped release */
245 		if (timestamp < as_release_get_timestamp (release))
246 			as_release_set_timestamp (release, timestamp);
247 	} else {
248 		release = as_release_new ();
249 		as_release_set_version (release, version);
250 		as_release_set_timestamp (release, timestamp);
251 		asb_package_add_release (pkg, version, release);
252 		g_object_unref (release);
253 	}
254 }
255 
256 static gboolean
asb_package_rpm_ensure_releases(AsbPackage * pkg,GError ** error)257 asb_package_rpm_ensure_releases (AsbPackage *pkg, GError **error)
258 {
259 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
260 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
261 	guint i;
262 	rpmtd td[3] = { NULL, NULL, NULL };
263 
264 	/* read out the file list */
265 	for (i = 0; i < 3; i++)
266 		td[i] = rpmtdNew ();
267 	/* get the ChangeLog info */
268 	headerGet (priv->h, RPMTAG_CHANGELOGTIME, td[0], HEADERGET_MINMEM);
269 	headerGet (priv->h, RPMTAG_CHANGELOGNAME, td[1], HEADERGET_MINMEM);
270 	headerGet (priv->h, RPMTAG_CHANGELOGTEXT, td[2], HEADERGET_MINMEM);
271 	while (rpmtdNext (td[0]) != -1 &&
272 	       rpmtdNext (td[1]) != -1 &&
273 	       rpmtdNext (td[2]) != -1) {
274 		asb_package_rpm_add_release (pkg,
275 					     rpmtdGetNumber (td[0]),
276 					     rpmtdGetString (td[1]),
277 					     rpmtdGetString (td[2]));
278 	}
279 	for (i = 0; i < 3; i++) {
280 		rpmtdFreeData (td[i]);
281 		rpmtdFree (td[i]);
282 	}
283 	return TRUE;
284 }
285 
286 static gboolean
asb_package_rpm_ensure_deps(AsbPackage * pkg,GError ** error)287 asb_package_rpm_ensure_deps (AsbPackage *pkg, GError **error)
288 {
289 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
290 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
291 	const gchar *dep;
292 	gboolean ret = TRUE;
293 	gchar *tmp;
294 	gint rc;
295 	rpmtd td = NULL;
296 
297 	/* read out the dep list */
298 	td = rpmtdNew ();
299 	rc = headerGet (priv->h, RPMTAG_REQUIRENAME, td, HEADERGET_MINMEM);
300 	if (!rc) {
301 		ret = FALSE;
302 		g_set_error (error,
303 			     ASB_PLUGIN_ERROR,
304 			     ASB_PLUGIN_ERROR_FAILED,
305 			     "Failed to read list of requires %s",
306 			     asb_package_get_filename (pkg));
307 		goto out;
308 	}
309 	while (rpmtdNext (td) != -1) {
310 		g_autofree gchar *dep_no_qual = NULL;
311 		dep = rpmtdGetString (td);
312 		if (g_str_has_prefix (dep, "rpmlib"))
313 			continue;
314 		if (g_strcmp0 (dep, "/bin/sh") == 0)
315 			continue;
316 		dep_no_qual = g_strdup (dep);
317 		tmp = g_strstr_len (dep_no_qual, -1, "(");
318 		if (tmp != NULL)
319 			*tmp = '\0';
320 		asb_package_add_dep (pkg, dep_no_qual);
321 	}
322         /* Add the corresponding -lang package as a dependency */
323         tmp = g_strconcat (asb_package_get_name (pkg), "-lang", NULL);
324         asb_package_add_dep (pkg, tmp);
325         g_free (tmp);
326 out:
327 	rpmtdFreeData (td);
328 	rpmtdFree (td);
329 	return ret;
330 }
331 
332 static gboolean
asb_package_rpm_ensure_filelists(AsbPackage * pkg,GError ** error)333 asb_package_rpm_ensure_filelists (AsbPackage *pkg, GError **error)
334 {
335 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
336 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
337 	gboolean ret = TRUE;
338 	gint rc;
339 	guint i;
340 	rpmtd td[3] = { NULL, NULL, NULL };
341 	g_autofree const gchar **dirnames = NULL;
342 	g_autofree gint32 *dirindex = NULL;
343 	g_auto(GStrv) filelist = NULL;
344 
345 	/* is a virtual package with no files */
346 	if (!headerIsEntry (priv->h, RPMTAG_DIRINDEXES))
347 		return TRUE;
348 
349 	/* read out the file list */
350 	for (i = 0; i < 3; i++)
351 		td[i] = rpmtdNew ();
352 	rc = headerGet (priv->h, RPMTAG_DIRNAMES, td[0], HEADERGET_MINMEM);
353 	if (rc)
354 		rc = headerGet (priv->h, RPMTAG_BASENAMES, td[1], HEADERGET_MINMEM);
355 	if (rc)
356 		rc = headerGet (priv->h, RPMTAG_DIRINDEXES, td[2], HEADERGET_MINMEM);
357 	if (!rc) {
358 		ret = FALSE;
359 		g_set_error (error,
360 			     ASB_PLUGIN_ERROR,
361 			     ASB_PLUGIN_ERROR_FAILED,
362 			     "Failed to read package file list %s",
363 			     asb_package_get_filename (pkg));
364 		goto out;
365 	}
366 	i = 0;
367 	dirnames = g_new0 (const gchar *, rpmtdCount (td[0]) + 1);
368 	while (rpmtdNext (td[0]) != -1)
369 		dirnames[i++] = rpmtdGetString (td[0]);
370 	i = 0;
371 	dirindex = g_new0 (gint32, rpmtdCount (td[2]) + 1);
372 	while (rpmtdNext (td[2]) != -1)
373 		dirindex[i++] = (gint32) rpmtdGetNumber (td[2]);
374 	i = 0;
375 	filelist = g_new0 (gchar *, rpmtdCount (td[1]) + 1);
376 	while (rpmtdNext (td[1]) != -1) {
377 		filelist[i] = g_build_filename (dirnames[dirindex[i]],
378 						rpmtdGetString (td[1]),
379 						NULL);
380 		i++;
381 	}
382 	asb_package_set_filelist (pkg, filelist);
383 out:
384 	for (i = 0; i < 3; i++) {
385 		rpmtdFreeData (td[i]);
386 		rpmtdFree (td[i]);
387 	}
388 	return ret;
389 }
390 
391 static const gchar *
asb_package_rpm_strerror(rpmRC rc)392 asb_package_rpm_strerror (rpmRC rc)
393 {
394 	const gchar *str;
395 	switch (rc) {
396 	case RPMRC_OK:
397 		str = "Generic success";
398 		break;
399 	case RPMRC_NOTFOUND:
400 		str = "Generic not found";
401 		break;
402 	case RPMRC_FAIL:
403 		str = "Generic failure";
404 		break;
405 	case RPMRC_NOTTRUSTED:
406 		str = "Signature is OK, but key is not trusted";
407 		break;
408 	case RPMRC_NOKEY:
409 		str = "Public key is unavailable";
410 		break;
411 	default:
412 		str = "unknown";
413 		break;
414 	}
415 	return str;
416 }
417 
418 static gboolean
asb_package_rpm_close(AsbPackage * pkg,GError ** error)419 asb_package_rpm_close (AsbPackage *pkg, GError **error)
420 {
421 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
422 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
423 	if (priv->h != NULL)
424 		headerFree (priv->h);
425 	priv->h = NULL;
426 	return TRUE;
427 }
428 
429 static gboolean
asb_package_rpm_open(AsbPackage * pkg,const gchar * filename,GError ** error)430 asb_package_rpm_open (AsbPackage *pkg, const gchar *filename, GError **error)
431 {
432 	AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
433 	AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
434 	FD_t fd;
435 	gboolean ret = TRUE;
436 	rpmRC rc;
437 	rpmts ts;
438 
439 	/* open the file */
440 	ts = rpmtsCreate ();
441 	rpmtsSetVSFlags (ts, _RPMVSF_NODIGESTS | _RPMVSF_NOSIGNATURES);
442 	fd = Fopen (filename, "r");
443 	if (fd == NULL) {
444 		ret = FALSE;
445 		g_set_error (error,
446 			     ASB_PLUGIN_ERROR,
447 			     ASB_PLUGIN_ERROR_FAILED,
448 			     "Failed to open package %s", filename);
449 		goto out;
450 	}
451 
452 	/* create package */
453 	rc = rpmReadPackageFile (ts, fd, filename, &priv->h);
454 	if (rc == RPMRC_FAIL) {
455 		ret = FALSE;
456 		g_set_error (error,
457 			     ASB_PLUGIN_ERROR,
458 			     ASB_PLUGIN_ERROR_FAILED,
459 			     "Failed to read package %s: %s",
460 			     filename, asb_package_rpm_strerror (rc));
461 		goto out;
462 	}
463 
464 	/* read package stuff */
465 	ret = asb_package_rpm_ensure_nevra (pkg, error);
466 	if (!ret)
467 		goto out;
468 out:
469 	rpmtsFree (ts);
470 	Fclose (fd);
471 	return ret;
472 }
473 
474 static gboolean
asb_package_rpm_ensure(AsbPackage * pkg,AsbPackageEnsureFlags flags,GError ** error)475 asb_package_rpm_ensure (AsbPackage *pkg,
476 			AsbPackageEnsureFlags flags,
477 			GError **error)
478 {
479 	if ((flags & ASB_PACKAGE_ENSURE_NEVRA) > 0) {
480 		if (!asb_package_rpm_ensure_nevra (pkg, error))
481 			return FALSE;
482 	}
483 	if ((flags & ASB_PACKAGE_ENSURE_DEPS) > 0) {
484 		if (!asb_package_rpm_ensure_deps (pkg, error))
485 			return FALSE;
486 	}
487 	if ((flags & ASB_PACKAGE_ENSURE_RELEASES) > 0) {
488 		if (!asb_package_rpm_ensure_releases (pkg, error))
489 			return FALSE;
490 	}
491 	if ((flags & ASB_PACKAGE_ENSURE_FILES) > 0) {
492 		if (!asb_package_rpm_ensure_filelists (pkg, error))
493 			return FALSE;
494 	}
495 	if ((flags & ASB_PACKAGE_ENSURE_LICENSE) > 0) {
496 		if (!asb_package_rpm_ensure_license (pkg, error))
497 			return FALSE;
498 	}
499 	if ((flags & ASB_PACKAGE_ENSURE_URL) > 0) {
500 		if (!asb_package_rpm_ensure_url (pkg, error))
501 			return FALSE;
502 	}
503 	if ((flags & ASB_PACKAGE_ENSURE_SOURCE) > 0) {
504 		if (!asb_package_rpm_ensure_source (pkg, error))
505 			return FALSE;
506 	}
507 	if ((flags & ASB_PACKAGE_ENSURE_VCS) > 0) {
508 		if (!asb_package_rpm_ensure_vcs (pkg, error))
509 			return FALSE;
510 	}
511 	return TRUE;
512 }
513 
514 static gint
asb_package_rpm_compare(AsbPackage * pkg1,AsbPackage * pkg2)515 asb_package_rpm_compare (AsbPackage *pkg1, AsbPackage *pkg2)
516 {
517 	return rpmvercmp (asb_package_get_evr (pkg1),
518 			  asb_package_get_evr (pkg2));
519 }
520 
521 static void
asb_package_rpm_class_init(AsbPackageRpmClass * klass)522 asb_package_rpm_class_init (AsbPackageRpmClass *klass)
523 {
524 	AsbPackageClass *package_class = ASB_PACKAGE_CLASS (klass);
525 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
526 
527 	object_class->finalize = asb_package_rpm_finalize;
528 	package_class->open = asb_package_rpm_open;
529 	package_class->close = asb_package_rpm_close;
530 	package_class->ensure = asb_package_rpm_ensure;
531 	package_class->compare = asb_package_rpm_compare;
532 }
533 
534 static gpointer
asb_package_rpm_init_cb(gpointer user_data)535 asb_package_rpm_init_cb (gpointer user_data)
536 {
537 	rpmReadConfigFiles (NULL, NULL);
538 	return NULL;
539 }
540 
541 /**
542  * asb_package_rpm_new:
543  *
544  * Creates a new RPM package.
545  *
546  * Returns: a package
547  *
548  * Since: 0.1.0
549  **/
550 AsbPackage *
asb_package_rpm_new(void)551 asb_package_rpm_new (void)
552 {
553 	AsbPackage *pkg;
554 	static GOnce rpm_init = G_ONCE_INIT;
555 	g_once (&rpm_init, asb_package_rpm_init_cb, NULL);
556 	pkg = g_object_new (ASB_TYPE_PACKAGE_RPM, NULL);
557 	return ASB_PACKAGE (pkg);
558 }
559